From 42d06685a44608c88a22b9fa53f60ba3a17c6494 Mon Sep 17 00:00:00 2001 From: David Kaiser Date: Tue, 22 Jan 2013 14:12:35 -0800 Subject: [PATCH 001/196] ant build script --- build.xml | 81 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 81 insertions(+) create mode 100644 build.xml diff --git a/build.xml b/build.xml new file mode 100644 index 00000000..2a26bae5 --- /dev/null +++ b/build.xml @@ -0,0 +1,81 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From 40af7b34d8a4ea2a5eb0a3ff750597f228d6dc37 Mon Sep 17 00:00:00 2001 From: David Kaiser Date: Tue, 22 Jan 2013 14:13:28 -0800 Subject: [PATCH 002/196] Initial source tree --- DepFiles/public/jackson-core-asl-1.9.11.jar | Bin 0 -> 232131 bytes DepFiles/unittest/jersey-client-1.5.jar | Bin 0 -> 128096 bytes DepFiles/unittest/jersey-core-1.5.jar | Bin 0 -> 455665 bytes DepFiles/unittest/jts/jts-1.11.jar | Bin 0 -> 679870 bytes DepFiles/unittest/junit-4.8.2.jar | Bin 0 -> 237344 bytes 5 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 DepFiles/public/jackson-core-asl-1.9.11.jar create mode 100644 DepFiles/unittest/jersey-client-1.5.jar create mode 100644 DepFiles/unittest/jersey-core-1.5.jar create mode 100644 DepFiles/unittest/jts/jts-1.11.jar create mode 100644 DepFiles/unittest/junit-4.8.2.jar diff --git a/DepFiles/public/jackson-core-asl-1.9.11.jar b/DepFiles/public/jackson-core-asl-1.9.11.jar new file mode 100644 index 0000000000000000000000000000000000000000..145fc4892222e773e7807b6cb25c9c7162f7bfac GIT binary patch literal 232131 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%0Di*Io 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�pcp09 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 z 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 sc^g^m7M-Jkk0+RkQFO>YU`8qwzbXzaVr6 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 z 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 z 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�Ua>bx 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

0?OHVz9VA0VGpf&kpun1 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

Cfl2*u&`AhC0dFC#e}=Y^q;R3iIc7UbE}x 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%0A*VNYhV_u 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

Dv(Pi7&wzl(jR1epxe-cjumX}! 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%0A~;flF(JTeM=F278@94*w}~X(=06tnrx5K= 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

u!2%bTthtgo*~4fNymk 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 z%0A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

Q{o4+=N`ULivzE;8)YdXB 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 R0e%5~6Te0|zzmpy>HpM5O#%P_ literal 0 HcmV?d00001 diff --git a/DepFiles/unittest/junit-4.8.2.jar b/DepFiles/unittest/junit-4.8.2.jar new file mode 100644 index 0000000000000000000000000000000000000000..5b4bb849af9583fec1ea0a0ccc0d571ef49aa8ba GIT binary patch literal 237344 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 Date: Wed, 6 Feb 2013 13:08:45 -0800 Subject: [PATCH 003/196] adding Esri Tags --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index 38b57bde..7ed6897c 100644 --- a/README.md +++ b/README.md @@ -49,3 +49,7 @@ See the License for the specific language governing permissions and limitations under the License. A copy of the license is available in the repository's [license.txt]( https://raw.github.com/Esri/geometry-api-java/master/license.txt) file. + +[](Esri Tags: ArcGIS, Java, Geometry) +[](Esri Language: Java) + From 563042cee924bdbe67bd05882987f1a2d8665633 Mon Sep 17 00:00:00 2001 From: Sergey Tolstov Date: Thu, 7 Feb 2013 14:20:09 -0800 Subject: [PATCH 004/196] added gitignore --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..0bee86a6 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +bin/ +results/ \ No newline at end of file From 5552749b66654c4ae47d19dc6107aba38dcf5950 Mon Sep 17 00:00:00 2001 From: David Kaiser Date: Mon, 11 Feb 2013 11:13:47 -0800 Subject: [PATCH 005/196] removing javadoc from github commits; can be built by users --- .gitignore | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 0bee86a6..bbf89892 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ bin/ -results/ \ No newline at end of file +results/ +javadoc/ From c35c2fbe5432075bdb7e3dc366ad6e03a656814d Mon Sep 17 00:00:00 2001 From: David Kaiser Date: Mon, 11 Feb 2013 11:15:57 -0800 Subject: [PATCH 006/196] code cleanup --- DepFiles/public/jackson-core-asl-1.9.11.jar | Bin 232131 -> 0 bytes DepFiles/unittest/jersey-client-1.5.jar | Bin 128096 -> 0 bytes DepFiles/unittest/jersey-core-1.5.jar | Bin 455665 -> 0 bytes DepFiles/unittest/jts/jts-1.11.jar | Bin 679870 -> 0 bytes DepFiles/unittest/junit-4.8.2.jar | Bin 237344 -> 0 bytes 5 files changed, 0 insertions(+), 0 deletions(-) delete mode 100644 DepFiles/public/jackson-core-asl-1.9.11.jar delete mode 100644 DepFiles/unittest/jersey-client-1.5.jar delete mode 100644 DepFiles/unittest/jersey-core-1.5.jar delete mode 100644 DepFiles/unittest/jts/jts-1.11.jar delete mode 100644 DepFiles/unittest/junit-4.8.2.jar diff --git a/DepFiles/public/jackson-core-asl-1.9.11.jar b/DepFiles/public/jackson-core-asl-1.9.11.jar deleted file mode 100644 index 145fc4892222e773e7807b6cb25c9c7162f7bfac..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 232131 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%0Di*Io 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�pcp09 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

0?OHVz9VA0VGpf&kpun1 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

Cfl2*u&`AhC0dFC#e}=Y^q;R3iIc7UbE}x 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%0A*VNYhV_u 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

Dv(Pi7&wzl(jR1epxe-cjumX}! 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%0A~;flF(JTeM=F278@94*w}~X(=06tnrx5K= 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

u!2%bTthtgo*~4fNymk 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 z%0A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

Q{o4+=N`ULivzE;8)YdXB z5*uT4m6oaPP2W4OJz|Nh0(UOS>5l1-uNd>TShyMHQE}suIU-9OXtSX$Ag)qO%s_X% ztl7ww4A+Fw(Zv|^aV?l@PJJaf`NnD$^3pma(LwtOd9>+W4`klHl5kzJ|56z4vR82< zJ(nOQcVALtn3M9(dwiHk2KYYal68+cwiD?xlM0lH|dmR1Uj zuT(#&c|@ZLtqcFEW1(t!WP28tYk2jJaUYc2D;BnMVD6b-4-iP(SvHv_vj|POE!Re^ zo5-fi#H!2m_5F3#QL5=e*C47z36k<{RgX{}`2rsv;Tm$tdC5CXI(tZz>9jecoDftq zjBn-4+87&p1m-vSInm_aluM z(AoSM%kT{2*Y%eDHcikrQ^HkwwP4j2t%I=lEWsf8CVbekCSE;=~xu$ zRuo2#%u~MMDnuBLRM8i$Ru{(&$_GQ{!lL964Q6g(5tdnQgVL=(%aJHKSo)IJ9+X2> z=3VSd+6E*(m*Dph{p!XDY044@Z}k?=c`G4F3VNdQsT(a6)+%8w2(EG3?sYJI{_M(n z_^vj*k`8*vSyL)t-;?v?kuT>yes`4nc|nA_nKl!bNjLnEvhOCTuwE!);k1c-Jl&l6 zXTq5~ZSp04_|Sti(5&e$5N&F?op$dnGv7nyWkH_z5W%h_O>i6d-V?%;ChuxcwN!kj zdW_z~#>wN#-Xog5O9X2>Kk7RW65(k{^Y6VgU(=dENK#P6p4ou)ICSQC=PB)b*-R3t@OyOQUmC4YXpl#8_uB znMa@q^w0pI;vCO(#KjQ~gHUIk$V&)oe-*3Ak$1I`Vz&C2{9gj4X_5X5m)sglX6i4|?^#4p@vYIkmF zXF9KsVvy{Sy6)U%{KLA|ym~5*Tq(6wzn+Jd|1-^fhvKHG*tt{g4TyNuF6bF{Xe8dO zIuW9{r%-xO(FvV=;Z(AT!8QfLAnS}TpMJJTnU=~elT`5h0>(k41F_M%b;1?hSpv)L_d^-WmnepL(xIwkH9G-BJIioUPBqgKz^I=B_r`e#i=S}iacmv3vk2q z^3)@J#A&O<7)$slT#tThz76YJcZ6Lw9h1 zaPZi4N=If6xODn&x&_1h&ZMu?&ObRi9k&H_hu(c>*BsTN8CKafPp2BUQbu8aaw?UY zO+{lX(ExE`<7)HE(oF3g;~x&=-*lFEM73PSa002;-?u(}Y1ZocAx`31%a#v}I+R%TDDFC8j@2Y!gfY5h^Hj=1QgBd99b$nqJxT(RU(v zRAJFeTk}aZCkSWN@N}XGf5u{!vf`KAJfwXhU4WI+vkrNB`swfkv-^i-w~=FJ<@A&sq+@jgz&Z z-j}5CAB0XTvKG3J7E$mjI0hC%uorgb8HQJcFHo=-MEVwqjlKzS9j;!E_L~6SPf$=5 zPOTg1Cyz~>Z-`~^Mxmg&XN1@u_Exx-P3;QRB9qGPQPWN|QX2?KDA_^6Q9}@V4~b4z zLQ>>>OrK-Gm_O^DbXA)Sb>8_ZPP>?k$ zlpae6^r|~sf&AT~pgYugSzbCjoQv!r_%Tk!I8+&Ze%v;*wXf~tv9&}oEf#A<-ZHvn zvgI7l|T28(%#S!oI^6-NC`uGo}A?@sdwGaQ8QAsnA=j;C5B%Bjqa#2>C^3TTaTr1A_} z_P;arSdub8+pU?@=F_9zPF0hP?axQsu0Eo@DGs_kwizkcHg!s@P^ko(q1>gsYuorz z%6M7N4^t8Cy6w}fwYZsAS0;33z^fz)A|=t)u-j-y{IdoO(VJ-#2N+F^Ta1NuI|xRB zHj5KU|C|a6|M@PMTgY?Gc`9$#cb~LDtP{`&VKauEPlf*JX4|C{;Zscyo6FOfmsjCN z!YdMKjZQOj4!4)^oxbo^<)<5daEO=OA=lZhgFH!y*u6aC47D)M>*8>1Zlaf;{z&h) zTPl4J2&m*=a2OPq=XhvuA*%O3Z{}9M3zhZgo_BaB1TP;J?b4lc{1o~bO>4_AbrSXc_6TjE>7-@pbYX68jkbvrq z!ra9qJ0im%jEdQ#<_>OAE4=-#b`8rmkxfgELp~iW3?2K(JTEafuGDIuS*2zEpNbQ!|*f-H`va+Vd zB?K`|T9wHfEk2^2n+T^r4)zWgCW6%0__vq)8_&L@<5y8@;tTx$Rw(s9z;`sV`r8Yh zD8DTM#0UHNczM52#aO=$`jDXBg2WFa1Ow)a`yl(1E30iKY^!%O>)r&3kPqhL`-j}@ zIhow4K<&oF`G&`~NB{ZT@NWzsh$aS6T_R}6^0kUp)#h3~gLu5vTY_o?`U4Cumy_GB z-%c-1LzaSaQdgwTt^>p0iBm}Al>PXt7sGzVNr_NPt*z%WWVKzM*m!NmqkXyU}vD%r+S z)Ifc+C0ypjg?%-87B9qx!NQ2a_nb1*9Dk@L%oR~KQG!9+cW)`E)@^iDa__yDAw6UX zsq?um_#C939Dy>^+#qD(tpX1)2Scp3O}QTc!sKJ;LP6s zGRA(8c8j+p{~E&ZAi5A9zW&LJFUi~gIZP{v{i8HV_m3Es_)kY-8iVBmn6Wn`T&WAf zNCc4LJZ#YbL$W-0zD|y*vds}K0(Y!l?>#>dAzNOqyL{MsX~obafx0<26XWBXNtWN+ z-d>+TGknv2p(4apyS2V+=wSf;ozg)1EVUAYVc!0ps_((MdD4RFSM-iSdLCz!1Co++ ztYZlwMrTq=XW(qRIGu__wZYGj1yD#|h!QuD? z)LpXoo6$mckj4ye?E=OG8od|yUZDb8iet4}=UJxFD&#VV@Vp5amEGkk@;j{*ch^+? zr3`66ADl8qLoe5a5vYPX3-dfm0rZgg7*^+KCo9)8T1^L_98eg^8ip%VQizzY9~rH@ zgWv+Qvj&XOtz&TWlucMq-cV-A+{xk$(tN-{kK`q&W#PWidl9C&{6;3Uww?R zy=VZPD>1ql;hsS(xye%|sK^(5I;t8apY-)22&dO%X;hXC1gwOHwtbHW2sCmaE zHi2`#xO_La@qUg8lo#lKg+=>ulz!gV8BzX~ApOVDh2gL0qWF()UeSUz4Wy`0hi?x4 zBajv~1bGNQ!hT#Mxll)E%ck^6j8XErb^5EqJ18OdsXr_uU03XL{sM!98DUq06uZgP zsY};gW>)IU!`nK{H&Dd*lR-fsa%&>9(SjHu>OfB7L;Av;99{{&UPpMli4h~^-VDEW zsM;Lj_ZzbyKfP8C_5U3eSZ)H=L6A;lVdA+4igu{1sFr9Si6Qm2^+SgV5TcamgX^X5 zw=Cj7=V~8?rD{`}nz8vAoP5TC^i_mKO2;?rZ{6U$W?CXK!NL#t%Tsr1cDWQ zT4T)N&zge!YGB{Do`!8Z4W$|F6{e(q*f?eC6l{-CBvm?Er}0GjsVb^uUOwzsk*EL7 zMe#mYXOI&aaYM-t<_U?iOtlXfX&g28#%1|OzoH6+66ZjO;;rZ)gfd6vau!m*qzE_# z(psvrEQE|ABSKd7Ju*yKF_?(A-K$~WE-ch;@EsSHUP$cdfbsC|Vo#EbZe?c*){ zXmU^n1wwD3UxtSk6C)S-$FOoi#qq(c{nv1J4WV@J0rF!wyL~PL+mcxLcmy}VgppiO z2kX9`1=<%MY-8Tm3Zq^ul=mQPSk%OzEH{H zzxvl)^UHm0BN7rwLIdW_plZF%N{AXbi9qC{eJJsTnJ0U7gPVm*&&7;z7KArHp7SGI ziqhrR(^ReEQ#o9|G;|Xi9PK<_UT?p2kuk`HZ50Mdt;t&o@gvqh4qEed`GhKJTgrE` z!n`QSTdMJcJ5aH|)(i#X-^BU=C@cK@NFb6>7vzmhmK~+K_vi=0*Nqn$A3esI?WMev z`06pU+p%e0w$7^j@LeIOuv;Bl((9$B_pt@4L~Dc>%hZjk^%1lOZz8O=87PYmM5vwISB^E zM;llqm*Ic+pxx+b6c|2UB~&040O3cTfbtcw2_`YPOvW8{o9KetDN?g|cHt{&=GSa7 zlimL0F~1{kHgibYw_7$roxgh-YF#h78&lm}D*_&Yfd2Yh>5c>GWcnzxuRyDG2 z1tb=vAjRn@{FovP!opT0pyM4)VME8!qjmw+t9R&otg9AFMtm)Owl~#v2XabM8ogyf z#ekySR@9-|BEI1^FyK3VKriilI8+eDA9?nm!9P{3{TW;9N(flo+Xhh`X(BK*J$0wu zMa%tBhcj5PSXn3(^~e>Ztc5OH$(PMkt3-ACLpzk6d?Kfwn0Y)-$qgksrkB9^5LtOQ zjFm?tGM+z)7z&npxkr1z(GBJ@htWc)6Y)|ARY&eiaX*Og5n&_^hkpMw`USf+u>iL! zl5n0Zg-xzZq>g5=1J6a^5@VW2 zGQvQ$ijaawL0K7oj~5ZQ+*9a7C-J+6^F;9sGH9)^+bp(>;2{Jl)oE>pG@}Xf_$A&d|wl9|MwH>uh#tkZE`~&$~iR!RAS>6voWT8Wr}p6()(;ul~YQ{GNBq@=`U#L8}J$f4}@KY za(ok=S)<|G^IfaQG3ML!wodnVfT;$p-Sz+xC#|fO05%LXXWca_Qzs|g^+He5SN2=* zD5P#d2k@80es-Wk*;f>1ATrE&RY-ir$G(H{DuYk7y@YYuKJDz+{lzn6o=pK4myCjs zY@VKbQE@c1X+Y|Dn8Hv2BRH`=^-Gv@6TRyY9cnW z{d00r8|<#3DUx0j!=k2i1tsrQd}^YUvC}e^p>TT3z7TYd4fjHHQhluVJzUGU7VTpP+197$PHo1bugZ z4&soSmAJycg6+?j0%+<1i-E=a0Bq}BXXmTkUt(zwn-jO#RYo4DMj4TRn_o}d(HI5& ze?`?_>G0PS)BmE1?w_b?U(Vy2p;s)`htC{xNK?-&r_6$H_WLsc3IFt?!Nd!3kF}E6 z%3#iX^zr7{;bn^LF}u6l3;24bPIoUilmcLwQ|HHtjRDZ#B`b4tHQs6Ul>+`HAchsv zux5Dun#*}_qE9Sb8c`%Z!umi${vgD;hx7j5oC@c>bKX6@7tAq|ZX+@SHchA%z8S=L+i+>+ zoj;GvtT2pPn3Z)EYH5TX5;KMfG8-e&&!83|D~2+zmv16OZbL9kR{-CqgQAa&2r>}^ z^)4)pp)k+{hhn!!A&^N^X%DbAxf~{xgcHHTuIT z_OTSbag$oWb@sg~Lq0UrbN1AE6Px_rO43amdD3ZloXZ7vqF+v1Mt0vW+I3eDyMZ`Nl%e9YH@e^tZB0<~Kg@kuR3j z`X8~Zzk=0Y>2#*N+*fh|``J{<x(Q2lWzkHPng1sE=G#Cv%cj7ypHL9 z;JiNKy%+d{_NMpwJeqz=8(hEAorT8R6R5Ma49+xPA)XOkhV#A79LHsR*QhP5PO4eQ| z-JNvxaYruXAyNJ$Ft^s*Eb;5aCVD3Nx&$5_s|FzzklLBxUgs5J#}{h(x4xawXfcuI z2n7I%PgQ@pMR%&>oT!()H0xEjrVAOSK#47^czF1YN}k!lb$=3EV10+vIO+t8BxI?$e`k>!e2a<3B1)Y8X84(LWPTMX2ZQx$5Ya zdDlE!5(zMXNW4>|%MUmPbe_SQY5=|)uE=*GUDP-$!#_QT-Q`dR_ag?c9O%uT=ed`W z2M^4S!+B!8C7eV1R;il&BPut-+l}`J{!q3b1Efic&G5%F2p@3A<+wTz_0Vm?-v732 z>I9@wDCaBSQ2s|m>n~(5{*!3^Bkfc$Z;n1xg44k*0e$nK#tnhMk1km)SQ7jh>a}i7 z;y6+#;oL0sLH7AgNJ{ehznGTS{1?;W7@u1~jhicQIm+sM+HiQ9`l?0ibbTk)X9~5B zAj#QNj+0~##JnX#q{W*o@XG_D0tHnN8=#|_jPP5BFhJhs$qjNQn!fH8` zW#l#!T54BaDYksA5TN9rBN}F`km^U8gZYbUHCtU)sRbR|@fS@gvNK5hMYY<#sMh73 zQn9s4s0D61g%t+0Ks}VZguC+UQIX6Im$8)UBJPQ*WH6Hk+CVSJEKa;aN8u^A3-q?# zsayqo5)MzKb2!51h_&tbYNFw&Q}Jcmdu@#VLQP{Hp~`GD8xq%vlHHFxh|98;;5P_l zMan(jFe0e=g=yk!y!lXCBp5nMP|9-EUi)Gr^?1FMOH#c;evbb8@!pqEWB>Vtt-Vk6 z^^}vqBAFw!j@!6vKeHwRC_A_*KfM~N-E6eNKO$%eXNmxB#j_DJ6bJP7NJF&rOLGrQNJ`!(5^@G_IVADe4qIDS0oX;oEMI_?WR(P!M}QM z1E)hgApF2ahDd(`{2>iRqWft>#=lFE6DGD(w1|HZEj`u(PnlUcv1#!aU`H&xv1`Y@ zpU0@$22sp(L07V4fjBMCNH-|)BBG*%ULl5NFrIifq z{x&uUw*hy2{6(=ik^d8#^$*1VMYB{~0f>iaf25q9NhlHe8p(QPk>u-EzhUTqOJC95 zAY^1D^bO#hNs@LBHtfUx8X1JwpipLPYJ5}-uT(Cj;;Im*U@P$9s(kEQ%6hbJ^nQ%~ z+_<6tB0bd^P?t5|i$Xs8GZF!BkFwF6A zzcRvPtA#Z_SKz?x;c0VC7-rw?BVx;4yYh(N1QI#kmf%G1hI_oTB5?Nhn|ZwwX>*Nc z=sf0Q>&V##vFK=pRqy`h^bqJ(@VJ#C_DD0!QofGB`E94%+x%WKpr(9n1v>pAm$;Ro zKzk_ia~{ez$ko^tkQIE#=@A?p?AnsEljkP^p&e7>$QdUdVOnHdED3Ie)xVa4jNKRs z09_I0hB?1q3fEcNCgg`HHs}am zh$Sq4g((GfVqDD(TO6N=Ra%04H81J^G?i-!gJ!s!)JN$^Nnk*@x2cLs3XW}MDeK3w zGS~NwDDsmNriC~TWh}3#YSpdTcy5%cbk;E9JA4ClZ$yqWYSQM4cBUu)k#KMXfHL03 zm0HeDo@Xl;fildtPt^HjE%v9~3L*xD(V81&92H>vR zWNixeZz(p^0zGiA@a3pGi8%}_!Ep4)0FdlMfRPheopTvcaf&AknpnSeUI&EolN`j4 zbyxZLgeM37ce8BAF#Q#2!tipvBLbx@0K1m>4<~o8a8qb%`8^=5UK2v9u-JehAPdfm z_?9G0C>Lm`_zdADZ8}RXKE{Jz+IEe{GqG&L!cOrS4hFd&ge*)+s%^%?!2xAi{CGNx za)_&f*?MT0ntEfZZUlIWtORUx3kaojA5?6&u&vYd*2_ZXavG0A_i*kZ^a*|0uD%uE z$~2Di&yA#qRZ40a8Wg)dezLbui+$}Lvj8d>4KiFV1TlBfV)AT-uEfMQclmr{MH_|| zA0?4NfDxmV;k`Rk1rQu7`&F{-j1>YL&_ShrFw%3I0q z#54m^cmEaAJKvxMKx`1_hNLYIdD&O-9nD+MNCeWTQ$FWFfp%X9Wn8s*PxxGwav7R@ zE7XN5sXPnrQ)+N~m52BL*|3|u+>AkbPQd1V(By$6R z!-kNt`!Gu^Srt!--!mZ0IXae;G^}-S)nfn0CdtPoS~XNT6I~z64><)K)o!0S$2N`A ztP6$;ZHEM%e81opryHhOwkvdX=4Hc2(+%;166=9fz7EL5AIA@|fDB7Qr92q0Tdvv` zfkIUmrzxeZZlX|%IdZRy`-jVKd=27sm1a@wn=ank-DEerE@Frug^iKz4^k0mvz zh_?ND4^=o9Cm+I|!wXeHspK4(&&1KE`$#_UU2#_mq1R!EYLRHTo83@i7}rsY=8c`^ zeVouG!zzI%7EmBfm7LUF1(rINThn2PJcPDy#dP~cVO&G6NL`(t(M#^d9k>X+CEMDp zkM3)t#cL4=z@o4P!JrM9M9rH)1vxAE_T{I2<#)~x)0;eC&J$P`(!!h@t&F{=6%EWP z8sidc^J*vh>~|?QoFandeO1!>ypn+j<&JKYnRQ{$Th$6cKTH>3`rcZl4`Gu&n1+wZ zN+x0Q`80H7IYV~4@)PK5BhRTa_-|*oP=o3Ej(ww>w($ z7WPnY`Kq$vxP~GZ{Z{3rUhR9v$OzORQdLWr9Of>4w|Ie&c)4kFKo(KPh!U$_d5cdY zG(DC!L2mEw)l0}{a%lsVR?KU*s?!@wm9ftHk{DZvUZ1*4#-%&2%m9?@#~^*;N(!2+ z!NzmSz>J48F`UTy!&}U2b~l-C(m4s%>)*H~w^}hU?Bb@b@VYl87~>OMo?>aZ{ZP3u zhCcd6)_>k>Qw9fphd5)tbx4p@7DXKYkP_$)ZZH^gN@$Uv^&76JJAiUL)scmqP| zaibFj`R${#7d+9jJin_)r_x9m3}3X=yu=S0D&WL-8!BigCw2?jtEP|{5_dtWe}f<- z^>=KKZpxo=r%Us3W0b8jMou~;Q6ogt$gxCN+l*>hg&R`C=#PEj$rd|@%%_U27CFSDm z&w(CwQCU}Ti9L`~f+q~q1mr@ebq;YL!6ibw{@D-LLNOa*bvSPFp=h~SJ2cnOa_mU% z#5UVkmD35ODNJ#hKR^DpBXjf^`278gK2Cgjzj6HA>a>fIk*%efwGoZNf89xb-3d7x zS$|nhSn4@AWGemTays#iN$aNEfq_6fC4{dDmJLXJdCd_&_vt&1ZMI|S=bO86m2csqj|2g~J%8?j z@mSyPBl9FZ*J28Ch9u%DqXDf(dKj1Y^wdEClMOp_Z9ayzDKWi+43gq!jv7IrRlSIE zz;cJlFx;at^*=&PKZ(JlH(4llKsD12I1ri0l&3P)pR+43GJvbDKWI)ij1T!9aWh1C zjyFJ4O&4N{;nR)MDl${{Ta>6_Pr$ZB-eGJK=M8#Z+JgaCUI z6qvoH00>bO7~(2D7*mueqRG0&1(35h7bXLYH$%zr-&hyL`Dpg>d?N^Dh8^Uyu?%BS zU-JwC@8xXXaXy18ql3zu!djb!g5ae9@)7J(s}PBKonH9#YpHWn2Im&I+zz==-Q@E) zv=~GMW28k$g(Ko0Y$7Kx{y-CP2;>&j!bZsvq!4`JjcUYSTw8vUh#=990T8Lv$RcDU zveWb=vQk7BpwjFTLubeGz~|(zJ;W5=euw;4$?y;-xblMLAZ$_$8EUF=2flurS5Qns zRagWX$il}2_}mFM&7>36hZAk{q@1(rZ$2xBm1)*woL`CXSDlqyrd|U zu1$osQBt)E8;MMYk9(yV{7Sir{3brPUek=ygTxQ1!|BHj2CK9}5H=Bc1&KtUT<9;R_Gs$%NMu7hrL=I_Qn(NcrV4|NsKCNx7Y ze_lUlC~GjS;9NJEMshBwvq`m7Rc8LMT`0+}T+_1WmT#GG*{0IRmF&1((i4QG+w|&o{!e>*Mus3(o_gr1zn+XUqG7n=$c0- zJ++K-)%hsfZ5)qkno67BoUSol+RuHz@lmA*91E**@93Sk_YY!5$M4COr!{J|4&Pp= zktzBLe}Uh4U6TgqU8Iwm4BDdJH?U43(4)CZaas z9Jv4He!YfMgzBs>=fWRa!*hFh@RlLOn#g%}tg>w$UopZcXvRJ(I8f^-P@DbHN|C&X z$h}47{NQ*+PkTj;U6@+}Lb31yplHF_$Zo)ys4PQnPd`sjygKlxs=WrA-_y_;>e?Ig>2>H>huPL;)e0^+9a!j+brr zWu6|Dt)dwyd+HJvJ<;9qZkamwr6gOO<8Q7O(1INok_=dvK2b$+QDCuZF>Fq3W;rLuQ86K0 zz=7G)Z&E&ksyTVIcDXgzpc3z?3q>muZh@$EV_EAob+rBbdbCxE;_`{VWx6q@sqDa7kF6{N%x3;u-#TT8LX7f%u*8x95oO zS&Xok3Fos5ce#@N8={56)k=rK?p2<7S;+E;ulkf7)mDA-waZIU5xtP7F zFRT8Pvd*qhJb?nWGWqKd4e4lj3fYrDX=Zzq?_jh~wcG5%5 zJ$&O`S!s^tbeqetQz+$+>B8wF7e7!Mu-o=5A%fde-&#bq92vuY*E$LO(ZUE4DVFX$@6dO_)wq3-{mwP5l5<6HB8ARCR6*<_2~V{lkzRsjbg=qB zW2cMaYflB|@`+mz*7?7$kU9s@R-D18HTk}T_!)bf}=f+obff? z{K)V!5D2>y%uoeY2#qiU5b?)Nb%?4LI1tSrQjq`pJvBEtOdS{!<7Uw%%|CWdl5R8} zZnYm$tJRJc5OMFAjCwkbsZK37_x$r_jI%WbEB%HH!#8|{{s+(jxEOzvyxaXUbTYTK z`9Hbme~my7nk|lRLz^Y?`l+o#2oi|&`pAH-)m?{tTJdBCgWt*-p#7>_q{P!@eCT%P zv2_JvG(jwYP!u%6UC~ep@<~2?CdzWCFsr)OFqR=6Z$TVK@j{iE(K0tyGs;QuhbuW+ zm*{@`a_}aZWZqcfyh%3czi&+Z&A)N9jh;F$(f_*X{Qn@O>X4#{aMnw)BGTPR73+9Q55KY!ttT!1b;FWp#4G zm&JGQW~9f{^DKRWwOYBdT7Ki5x_baGOr)5KC0KITte*168NUvl=2qNha;FTzZ$J8d zXb6=d{(B%kCHrSAgX;HCoXP9SX|LnX#+QS`4~Sl@>y5fPGefAANF%EC%{qJJRvH?0 zwU)YmhNW|SyojVejxt6q4oRkRBP8ZJE?mCEdL?=csHMPli^RT7$@Ktzvbg%yvlk7b zEqG1sh)GZh6qihWs%Cz;jPvYC3_K9|`3Gk#^55WaFTX3 zoA!3-FBn~4vjYs3{yywcJMW#<(e46rTTf5o;|{!LdK-ik4z?9;0AVnVjR1ll0lWp?=54Rng67f$x|hp zg;&a_Ug@55d|{p($8PX?L_G3Z%5UM@^*Fg3XGhswao@JRLN{j;5Gcd7=#GTa=^Mhs z%a4~*=dxysZ;;(@LWv1&lYC${^QA4N{RH_Zd>jh|b-TWMJW}7K8|!}yAAp0Av4gO_ zlm5R!DDeugAcr!XyWxDnRzV<*BBvyb2}+rW?vCsPGdW6xFYtRe)uyu0sH($R_=`N6 z(f<|c0Dt$J7~`ZxGsSrHbJlUaw7cgd=fvha^1g!Vfoa;Grp4rg$F@z{F}6=SNIdN_ z9`C1({7e<01F+DTtD%J*@wC|#yLC3b0V zwcZ;mygSZ%oEs9>l-;si`h|A;4=}qineNMRkp!dshFm)($kgZYju7L;S#PLG)K`M6t0+7yw zK7kLbLJ0AO8kF%Do2M^m$d#|a21yCV9Y+(>)7mb3YJzaKFfoLlYWa?$m`*-VZT}d{t(CJr(2>O zcjyd8HE$r2S*CDrX%sb!a#2Z9<3_K0ZAx=aotBHQaj4c z{>NfSD@1M&NbaCf>g($X+b7~)w$yEi7W31;HGqyP3!*l^BPsDWD=_{4+i_6Qw{kZA zKT>7UN;9_DA{gEm+Md=UGNr{JjejQmC^XpJ3|P5(i>~RJKYiRFGl#*l zO!nw*DFZHMG$44+*>L{z0-GjlGvwF2+q@1oy|QWJS>Cf|WxIqu1xJf+xvf#yIKS#} zq=QZSCDm%G*U@rpjn=Yd-MaL^<-K#X7Yy5@YS%4jzG6#n+q|wRDRILF(6aqVt^w>; zrBHEJux9?DhY?nh0z;Z&$oc#6hhWx)iYeiKzlJVcbbhhx+GA9)I2_V{%%y_sdsy&S zDMpk8L-plT@+U$4q#dp>JEdUjRTAr%xKz3zmI(>}-Mgmc6}&d4+nIFy3Om+DG>WJ_ zUofSSRabhyrW<##`egAfQv;a8SEnIZP!Y8g*49(MgJWU$@z)4S1!_Y`!o|zEj#)!i zMU^bb`Gt;*m^gq_b%;8PP&4MoFrwH`8mTMMHUR9^4C8p*(Ji$Zr5$UV=);U2k1`?p zYc!=H9QvCK=nuCascmGpM8i(^00vBWFE;Z(0qI@t7e8}+$>+JeWcg?zjSn_7uDwzEtr7iq>`W4clQz>u9 zn>Y#Q;o&Y8mV*!+qGiL{v<#6$pLXJk`R#_A6+vW^oz^qEERMiYm}gv4R%1b9L@`zE zLx0EGA)|IhQt`5qvS5q%8cPNWDm3KBCE@JF78aB8lh@K*3O_fMYu3{ryEq3vi`csY zUaWOF1{tFp2U~{C_uGa8)PjE5x6jFY`oiQL;t!&PCS()NU(V{E5hC6LS%&SvoXu?R zFiCx2&{n~1?TqdIl?8hlQAK4Lz%UIF*_lJhw09kz3~LVs4}HRG*;tJkeB zgNs}}gg$q`ZAL@JNV%LnL*}b`n~{^=`8diJeMlaQm_CCy;KdY|qGayrH6I&VCI;q>Kj$4Q|EY{eK5~bp zv$FUij+4mlc7?HA#cDDojZ6IgI637rlLpKeg1-pqPHKVzHxVkB|0++*H~b}kPZL>s zh`RFlZ|SESsS;4`cYx^sUKIZ^Q2g_%37mAb^00;B1a~tCBn?nyoi3YUrez+Z#kZrVj{|JgOvTN&8|H=rFFe!1Gy>=`x0<7T zMsC~cMq(Z8sG%%6j^LUQ@$YJGo(KFoV``s%eu?>)Fy|8`UNQ|%s5;BDnuM$f|E-wL z%h^(YW(bh06LHyq<(;sxldoj}ttQiuAo9N=L7AkYAd43b*T2y%!kC|veh+ahY@W=q z28Ai^A^3gYkf`25wpm`>U`E4jJbloTNih$0kk>sM58}FBBkb{$YRl1xbu>l1BH6S~ z@(FR?8zr&G^GE%uZCt+mFyHamxUXm;J*aSofbnB-y*Qu0dafRyKm{M6%Ug80Xbx~c z_a`WQAWc4PJYC}C+?d$f1pYt$h(5RFU+@jm;qOKHAHfRvFE9S9HsFo!g9;Ep2>D`k zo*)Xq%a_vLbcKN8aYyKIT3;qrS%lTDHoceyew3R#@AKFHYx^4V_IDc}h*1RwIwTB% z2iqEza8oySY)>#umcATMC9}g=jcZzZMcjG0xN+IslzD?fekk}9RG(aC$aCwWi`AH2 z=dQm+3!ASzXo}!LVbL{b8;X=xpSvb2rD$OwPT-aSU!m{WkRfL+rm9ogBX*Iq<)YmeDj^@L2ZXqh5p({Z)%X|2@6kj2^+R zQ_Ogt!V>SK=uO)P?6wE|D!1Qr7lzUonVr3s{bq8qle$uB3Pi2e6xXMQW*()P)>{-# ziM&8&0uoh6DOa|bonkmKTQhu4r6NXkpEX<}Fp>gOw2p>B+X7JMLKMFVG0hPGh=6VD zZo_um#K+kvFJw5Yyrm(=_F%>%pFTl)Lxd<6)nvg`!Ms+ z?P$hCK)(%!_lNIRUT&~RreV{}>aI;P9--vK3xiE!7LPT%LK7QiWtEx`dSSmO+l`0c z;DQpAwD!AOX*zh-Y;yZNZq(7@Ah5!Y9QBVX#~pD$=9_ZeUYvJEfvjgR#=*;w^ z?V?E2r`Z2=^3#wqJ1+7+y_h}dZm&M$_2oQAQ@Wt=Z)`>rO`ac(vBt&)A=t+6G3pk4kv=OAPGYpGh; z5=DH7+|D9h#xl*8>#z9dj596yNBS7ovGekRyXcKdV>3{9(MwE>jRx;Z2DCSr#RT1D zL7W23rM@mROl6yacD0&p<5R@sPi|j(?Jbw~5Xay4aBy~M`;z4)pP>Kr!z9E56zsPj zR=*d`fAoW@l%SBUjiZyk&3|e7YR^f@4lp2q?WUK=tP7q2LnQ&%Row_sBkY%}{A@9= zBLZJZF(6m29S#7tqZs6n@2h)Yf7mFD=kM9(2hxKwMa&Xq_yxgkTbcUy^G7YB2Xl2) zbh3AZaUZaxV0K$BeHKE#jK`v&y5z#03dQfHcDeEkc7zUPT^q$}Ez&YW*Wjjtj^hZK zpt+jY7k@OtFZRC=l3@%b_oyqDHV;?QQe()}=O)5JsU4%pK9nS47<@Zgm(L6fG<1eR z55l$Tl#+KAO*ES_15B1_vy%W~7_FQ!wc<KrO)}TN z?HBH`T{rDwe7BqA@I3Hi#519X#4`uyjxm)FW9Gr>w>TH=3*`;+QJDkx!a$mM-BnZ-PoAGxxKd!xB zavr`;avZPS_vVma_<;0&YwQgJfwAJ)@0DR=wy6$}Y?=*u+ib>yv+$gTK*P7+);;bd z9c$8gX};f>aa@by;UygHI2Xdg%j_<^6uAp$h_Uh<4;xv&+nfKUzwjjh@GssHvrFC@ zu6-iI0?gdzgG)ElVsbn7kO>m+nBCu+Sol--nCE$@rySE&5qwHwqai+mY2J!idJ>LT z=08Oxf3OR_S?6>eFo zH@X_y%k4kbmo7A0Y&RS9pPo}%nxTTHxPd!?m+}SLDm!S=U_&~m6-Ld&f;5p2h;lsQ<4q*j$M%77Emyvyk_s5oIHdY6x{D1+E$xE1L zDjJ^TSG^*SrR}tOzIAh9Y~XqU#k8R4Ub^!%gm9M(t#*DxYLWKNXkKjMhAig92{Ej4 zgA1y2YXZ^Wu4BDbwUyS&tuPyyrcjuAiy~r#b(}CTV=I6O2)ex#$LjCdb2J1mZeNt5 z0AWBf$+B$r6qq^J2JIDQ!I<&BIn+p7b60)sN96H2W6ju2S|@~hSIUHYAl3L;v2r7S`x-W22Ta`QjuyttyV zRVPxe1_D%5r9l)Q`=x2W4H#!GorLGdwV{m9pq7WA?&<~I-PhyDnnrLF@enpP4AaP~ zN3Oc!nOT^;bEa#auox$HR*GGn&NNK|5LOW&Bknm3^Vn1E0VaT`V=adB^$>^yY7Dr+ zD7J$HKH^P%aji(Ea2)D_K~K?@G_8rPnC;zS(TF&KCRS_0WeupYme-JH7YUtoZZbnh z!k6}b?@nQmaPCfG!tnWs>a$AcXBTWjvi`YP+flScQz=~vw5C{7nw+ub&1%G29A@PS zDf_RvyYd z8S&XHJ0SB?A5pzy#brOZ;QH;anVuv&Whq3ko*7Z8r}(;WcetgG?>$D|ngW2c1bb4X zwi&_g$S5~CvuMdEtYl(MjZfFoSp^kJnNy2p|L8SkEQ^HVewh8Kn%=(Mp2_&O9)ayd4>}73rf3kl@<1a?sAr;Hz%FZ2JQcIQ==`|^pjR08gW)Gtp*^aCC=j=zhWmt1Y%m!AQ zV;btxjdVJdqm(XT^)slyAZ3#+ImW!@S>d*>X z*=L7Pa;5|sa`cNlBr3&A5K^^Z5(=e-CPqL(*M>^dlB8JF4VLqC+T&#<_&Wok4MT84 zGDYdlWF1_KRSk|cH57>N`@iZd@=?VVbi1lPeNUxOTsiHj*T?h#>%sc#BBmY5)iS)z z^IEDoveg_h7K@1m#81-n7((*nn9;3&X>bH5*c>m?25BmzDxb3~%(>L%+l^to4?cCU z6+BZI7^9r}2VdUi8Ih(bTNNG?7S<`u*PB3WXYNZD$}LV}ZA0JMv))Y$tf(v3hHTM( zv1CvxDQFrN&3q=pJmB{f{y94_h}c4HTdgBI%IxZ@FF<6TjbHNZJ^H9A+4G9qOcTzR5GGl)*+na;mLfu(w*gq1;COl z1Q573A(=s@=#!gswpE~@Ka7nvzd_tlMsjDZ1~ZVJ*f|F|rVGOIvfyxd64LEueG)n{ z7o3vfwR%acmaK}DV_u%1o}A$OtK9tIF@fANw6n$>3=(_+YKR2SLqERN2WmF}xv7TK z2;QV4V9ilw%U4n;@6EA-Bn{gl3uEfq0Ledm7*x>~%Zuo~<`3ban!p$C%Z41YQ4IK1 z&Mh6@(TtY5&wD8{ZQPReA|F##{S03)dviG=>}$eLCUKB|I)=hqjQ_17@Q335{YkwEJ>~!rGpP>DANqv`er%qCqC%!K-nShn@ezN~O8S^N zSrJ1F=m1}p=JCUE=f2~s##b(GMG$H{W8M!J2>vXE^o@MuPV6!Mtlqb!g=M~B$b6eu zl4=yl2%e`&FuB_^ER)DOK*A#cv@Y`uGUG|BIWSv;XaPPp1*pAK3b8R;ato=w{qz5d zWKG=+K5_Iq>*goo8Zc~69&`j>Rkuawy1VW%fURwZdfqaC+2M%YVqCKV3WpoYys_v$ zk?>7UsmFbTOk1NNO!hQ+eo6duIvS+psFlG2s5yy(7g+TM&}(ylnRgm{kJYzm=n;<|Hr};PDxf zm1=344}0z|FJ8ck$Y_P^0{V7x-OkjlZ1E7&3e0UQ&`$}{wA)tIPJop&^S$= zSTP>A9tsDhD7)t{s<6%tM4!zCS?4j}<_@opdiUEw=(BYPn?W9dZu%2>yHEPtGd=ze zsrxWFs8!}*Bh38`LFlJ9?H~2oiLZX|9G~pIKaHmwxVnSJ=FFsXq znk$|_s0j(@I3h8j_4}gjHkK_YHA!g|RJ0B}L8#Wp&hJgur^*9kpTT~A$K^ax6K}Dk zLRQk}cJx%d5_ffuSRYpI7|81L6uiO+IH>bFlx+cd*W2NEZ2sNOZppRz<1 zEg+OGq!leNBu$_1IsFwPutn^LoOj0_lGbEKj79u{~jH-D+#l;UzP+tbRX;*jBLjmmp~C6kMsUfQID zy8z@b7%hy|QlSc>ovT_eo{-6-D-tal732smDu#DJ=sYtgtq7m9-=UJ-z9GMwkliLD z?N}4vZpCn0dG!!!TJ)(@;&9GTw=T)Em=+YDQ)r;5ML8T3XwB2M?tcIsBXR_8o!O`0 z2`~z69VBtj9B8?rTx74FkQAa%jrtNDndHu3L5+tBvI2`(kcCeQre{Q3u!2V_W1cT* zRnQ1wpR`c)NH}orEhP-=e@pB11_;BgW{7`iUcso08a&D}2cq8Lbx*KJS-`br21ki} zNRB7a=%%XQG+&>I=!~e!(U2JRTjH*S{OBrtLbyLzU|Q+`#4jJX!f}^iwJjkXmTNF+ zNV2i2Riuz!C&Fau@26z;Ob=%Zm$>yrH2wYhH{zP=*DpPVZRcHPSwHO>Ay<2 zY*k3Fq#@KVfJ@?*72haQ0)oRX7K9C(zaE07fV`kUK^z4Ego<&ZSJ03V`}Hrs7IDo> zW6j9s7B?jUOAj@GxByCX%hToF+;U?p`1*2VwQBPh`^Qx)wj)AE&(1XagUNCC7x(d0 zW+;Ex1A^b#>u6wbTYs>vXIwC~@h$F|w`xWIK^opmPAGXo0x4BJH$x1Z-Xa@fHGJgB5A-avj0XbgMz6tzzkTIW){hmsC z&s6Znk!L8h=TYZOFyGsAXWdRPFx}?a@Gto5{w=&$I|}%hT^;u8L{k3bV;^&$<6oq3 z?hK&cRUUoOiysTT2STsVJWp1r*ln31cJ*!_{T;RXi{;)kHYOig_lP5%5BNKIA$gJe zeDF@kEqCyzIMH@?7<)*^q#X?kytzDO-$=S{BZ_)o#LtffYh$BcV>g)q+ZWx} zAy-t>M`j5(aUjpU%Bq^5$7)C@WurjxoVy{Hjk1LaN=Re=JNq>`}SCL6O@I96vH z;~Zj$a(hRq+fUQ1H7il@n_m>q`Bt^|P}<9zf~{e{a@tG{6?TYKRew->?Qh9ps~8!| z&%b-mu4zTN!^2Tx>@(G9k;dU@A}xU#8fZ2a%$>%O5OJ_jiee*xeD|aTu_HBkN@}8z zR>Cm>YIhS7loHYz4D}oxXO#YqF^78!4kjlSsPNpPLeyy-Oi7Qk(>o6Cb_xB(C{&mU zj_geCLnS)i1oy{|LZHPdT;iY;pc*J-mW-qYbYU*;sg^cb^vXXng-}V4Idg$5Tvb!l zY_1C)>bTZW%rPn%{c5nk3A5)v2e2Fwg6x4&0Q+TlI!C`g9G9KJxJ}ro+V(d$NaC-T zX%e+VcE|Nh`=!<0evu$q!w09QrDUR$PTFNsqT$IO^=Tg0V6yUvO?I7%v*8K?*+Rg& zx|>1&s9-HuOwxJ(>g9u~bfCWyooljkq1dELpK0Vt2y01?6ihf9Ss!!mI1~b|v_q?n z&u{GThpt=hEw#(>-#MTJ1{`1pk3a5{i7JZKIr=ma_jxkekTV819LrJHqV0&@6~v~N z(>F|`AK^L10MdBB-y(>1-OblwvqFZTP>Ht-z+^`-p`3{`26VO(wq1Vw)zgDa!7>w# z^e01fmUgJF<#itV=s>D(t6h3r`XG|ukp}ifIg-&TWAF*<6v}6@4l@q7%kp9(BhSHQ zV|RY2O27JDf|i?}f%xNxF@ z0@EKbFfa7iT)&#+E|VNvLWc|>UY(UCaWjh2WuriSxUs=#MOlOKh0b8VBK@}7Ma5yW z3y0Ai+JXH9{$jb!nAI7>IPW-kFFO- zmnM@X9;hu>m4*hpoNmsAo@*LqIFC=uE@gk5IKPv&)!=q>EkSU(D{qjNs0lDQlNQtwV45Az^$*Ffb{B&JW z2_8W;QyyoBSEV^tuczCID-B`Yzrx7_r*o+O6FWxMrhRz)VgMZ}k5ZnGM!^q0{XnGi1W+>HNAh9{ZEm(`Egb${zan{(jdL$HAU# zdfyFG%z4+E`UChQ-)3ndrEzL*a7e9hS)8tS&ErGz&;Dd`WU7;XJu)1}TedfXdgaHS z+CE1+kDVEO40O^=ma5|3`u@~T2mUmlQEf%J_)Dv4`F3nh&6$v_#wE$`-M*a6fNm5u zarwZmjm= z?N4lmGXkwj+F8(RK8256!W^H&EyeSxZoKww%wkLWo-yv8#S3%OkzsXdN1&6$FVM1n zjYR_S^lHrGi~;q#fCQ{TL54^-<7w1Um*;`DDU*}ATl#{fLQEPhqjigVvqrTnK{WLY zku^D*mV}|93=w;W^dUhHBwR^m%ht3l;Yf~GUf$VQM*mB13~Y`tFO7jt_=8niHQd!m z`#%^x@WDT7Kj>cgP(a6x)Zh3}QjKpyI%!V8q7a$Uq!B8M??A#J+LDl@!KA=>TksO= z{ZrntOm1`1gD1)+*)c1*S#)%f!lPfDplcs9ntV|?s~h8V^$Bx{fj;Dr%JfU?;E_G$ z+aC$Wt!h&iDx1B_cEz||=6T0(M>Z?mJsET2lZ3S-H-482 zQ!lP4UU5Ezl~8gW#J)>pO=UjvWPoVylix1!KyLn2q&cm0-hl3 z`fJK(u|#`OfYm><{FYYfnGI{#MS2J~Eu@=A^23hmJ&ohN{L`nxgM5i^G~>d?3oZ0? z_2abbgITL}fNcbB0%!h$B$URfo<0;HJ>@Vf$*W0=yu<`9&FyyvrK(=9L|#u)W(tyJ z6$?KK1@8G|{wU$f+uL|g5cr2MX-}VQKnz1K)+Dc@S$?S?lU(|2TTh9Gn^wt^R%uA$ zZH@AcIUD#0{WR+Z1G(>DbO4#6KM~U*(z<;}ng^1$)eWM(N}sl!J%J1dJHf$G!2w`b zW9*vZ1dz_Ro2-mW=S@Q>(>L=a64|-dlk}#Hu1_dOaErjmB^x#k+f~QwKRMkyVNBh5 zqd8KfY=%1Gisy1NDw<{2LuRvlgcZs&m9ft63Hcx)T?Q|Kd~Yxh$N|4eyqt8%I;C+K z*Mol$w?=IIx-{ph+{hO8YNTaP5lOYBt1SMY?@Xb5DoJc4!{mn>D8h7{50$_RQb5-Vj1XSaq}@8qVZ!(gBaq%~1r6!R#kxaUICV8_ z`UDHoFM(-J>#_l#d`WWWiIF-g*%`%vA{dg2n)p%5iiGsFAh^gVYl?!#%J`v|ypRV#-hTfwAE>o2+1tJMr_DJ;ieO^F&Ys3x~>u?L;M|Tob`ef3# zp!AX?Wa_=nctheqZ`@wxsNg%8d4h|F#rSu0Lz`FOGR}DHwy`El=XG9Hzq;B=Qx1D; z6zK-7LUuKjmT`an!NSF`p}vn}Vyba=#zQCvN+EZ^D1mAaLe(!(0TNw-B2xh~q0Bk0 zh`O3breAo!7Gk!)))SSk2{UJiT`I;XwutbIA?TLnEW*T5F*ii>zb=B?# zB3RtzXL*rS-XYi+Y6aqb>roH!5DHNRu|#1RF;EU9wPj$H2$`jXW-=0|RE4O+a)Me6 zPhvq4g`0cp+%P?8r?%b5%Nb*OtkZIvypNkm)5!Jp5O6||KKZF z^_a$6Y7`lOf6ZO^s7hzPa)go} zEbK5I-)60NPEwbfz1z9%??qm_VwyN_AFH`&F+L`9ovzWKieE6r<)iPV;J~G=9h}Zj zKN6-qv2YTg`K6Iy^Q=tC8Ys#w`6DdpN-_;J^A|Dlko0g})T2lDiw6mv6=%L}nJ=O7 zroEZjsAXFr0b2FpZy6e6g}!>p3^LetdPfE;OZj&R7|>c?_5+Y9!JCfF5VJS;5!&Ze zx5tzDu}RX>4^66tBG=jJ84S*coY9>V&IY;=KJEseMPk@Sw@|(Oi?@PgCdtt!O^w4K z9-LwO$zXd7v3`z_XNCeMr_o!c7%B)`8you;BMwAS>UD^=x27PfkxAi&XQabLxI%FB zehLHG0iMQ&_(YwF@8KdweVE;B2D*cYkk0*{JblI}&sUBpfE|AnI7gu#0Q60t2PGH4 z_)X6riXJkwVfJu1*c9KeQa}J^7reM`IwVQiz`L=%u z$&w&>3jJ`Z?MeFA2As{7OKbCfWKmZ$u_kv)!lP4%v;w8JtY-x%TJPMHY9&{tCbQLA zR=pngP(H%MF6#)Q4>sH#a#0_7_ee5;Ydn3;b$Zgp>9~yBmBW$QAPd!rtA!&#tC5h? zWzHd&?RWkhnr1MPV#;3RTk2lyTT1>o6~YCIrWdAi4;N6aos~#4bH%3<=v)xQbKVcn zx0Mj!q6JC8+W7-JNUYjD=U#P4=OJ}S^IV~$A}QlEw|)HZ>R;FwS|ZL~sD068gwuJZ zw655vi154t>~`b7Vyy&yAHE=9t*IX;*#tW&n?g@asUecrtx!EOI7q2Z+R!PL>(RjI z(N|-E=s3${Na^95$IEGUnxcNws^sBNOH}Paso~P8z_Y&MRPmQH_qsDYV|)4L=Qx*P zCO%R1Ct#1QU|9^IXdHdvFfN;|=$$~F4>kmB-Hiw}aQ|)6bf*1SECsPP#qEIHoYG@z zjZ>&?ym356Ix>`IWSB^JJyAj^}cZEcsEKvwTNPyty4^%-sVLADM2@(((X);2{AV1X5 z!#Eh}??(8~@K=jwk&TL_jc1)E6${I9z+g3))0^g(bz6>e5%WQi9GSi<-#wW)Sf@Y8~j;~?rFbsrH8tM zrzgw2;J?%BjRe>a(%MD3**6fXXRVruRYatdwJfRy_ z7nCeTgGO~2Oer)2mNgyi9W&dB_JH_KCZ3K*H)|HG3mB0A`|N@V$zxl-nktmmh!}_| z=6->=mA%c8dH>3R(w%+lg?Rj^_ok7|ZhZ0ifi(&dJ5 z3#w?=bmQB`EC&N~r!ji$LIXI@&tQh$9@;c$M-gL%xZpWxv3#?Yu7l3^o>5ADoY%RR z3DLi53-~b$tNUu`x)*rRNDbY_5Lb#vIB#V_a^PNvsr_Z<oMI;~96ujv2k<_si?D4w4w(xCNj|d1^B$SzUI; z=QMXp>njPiSK`7vCS>@cU_qVLlCo%a5o3esZYi+!jjrIBS$jY29Q9ig#FxhmZK|u5 zj*<3ku{O(VHTig>tm{dF<~h4GO7f&dH~A2Cl4jYhRuyAZh3OZP37U!|bHNN(uPxlH z?G!WLDj{7|h5U3|RdQ%0zN28zpHJZ-mUh+en|XUsTICXvO%|^-d}6jSDykIA`mnlv zE3E3VmYDS;6uYAo*eqJXm8|B45Ni}y8CD9pQx5(iLa)E|<|&jx3z-+Xs>V$11O~i0 zc2l5P*ND&&uO{ck6kI%5^Y>i5G!yf*iuQbP^qwhJj4GYX<|#WuI_+A?Hj1PYZG3LE zFIUoZIyM`$6LFE2Z()bl3#+32a%P}FfHQ=PSH4FOkdTLmsu*o7bgYT#hcfo3 zNp9Bmd*4GcI*ttvLYyb)WWX4tzqmQhvHl!4g@`5jZFU^OJV@Uy|8@Ho@ADOw&nZ5( zp&5QSC(1^hvqq&7y9H~eV3sYL=PBlwA7_m4fQ{W}w79B;UuK2WfGGs>k7j%2qy0xRGKEI~9 zy}M=Zrnb_$T9vtHSQAWuTd7?;i)U=80!^_@m#CAKCy(gHWV2wb*dd)Pbpo|&V7M!e zrCouqaxYKgla_8+pNC&`vg8_EhL2p;xYs%)noM=#MBU9O4vzIL;BwD7TVi|m5Uc%p zb%P6E3T91-hTUds+7D5>sq$hG*r89Y^=_2hNH(;B%RMC4XQ|F_GV;vZqsy293BG}2 zTDjQFnjIN@C3$g$ItH?{Lx|?XGZ43fb)Zxf5Q`U1osjAi-Z$J&;%SK?bfvJRh?8f&*`y5VaP+-B&c>#Inci=WPYqR zSC_L3x8*Ti)~Ysql-$tM+Ed#}wpuTw-nS`)A)R^ANi>(HgRP_)qcLnNDP(2bwBu%e zf_dbdfaOd$g@b;1bWo(Y1Y6jhDh7z9qS!w+G@oXqVNvwP*QJIT=>L6f=G(@cce7{+ zlO1#Y@Px5~mb{23Ca43hsH$blB=sILSwC5(K+E9z!hPP9f=m(0Ouv8=FgbV+Kr>Qp zOvzh35J12Xih~@6d_jQBlE5R-W~O5 z4CeuQHi7Fz>PzDq%@?U%Xk0$-!o?R$zv|^F9!VAd_J>HUB5nvTFBvbO34VYzs_FxF z`hPL@j=_~h-@11vouuP*Y}>Xv*|BZgW=9>{w#^-@W83zQ(Xn+??>)Ebp8tpQ-l|z^ zt*@(QjjB1vc%I+GST1PL0BI*eVAq$5o;n(N=J5B0x45Fd+9zYQ+{351e}sXaH!N$e z9%2YTh_@*dY4vSu;*!4c6E`hPJ6g9ijka1>RbI2N(qHvCI!pO=xZE6ZJw0^C^2?2E z2e9-6-})x#i4IU*t;kM!$7*wC21^{}#-_xsrsgF)Wvp~}e$bmj`s&m~1Jj!~(i@eC zGsay%18SWPmbo$}<3~dn^f_IT^$3Q>_y=T^$D#zo-sVE%a(f^a(IDU$B=dTX)i7=9 z+fjk`Br#;e@{C-ZtJPFx5^m$LV+os(kZ@IYYC?|SsL{R3C{R6#AvlPJPQ11`EK!LB zg--7(ZzyP5Lu#`+b#Dt-Tok*ZZ09f&!-*ObP7OHn{nHio2-60%Iigt8j`wgn?3^AIQ!YXY{DLXru|Jy1s^K55*wG42dXmI6~`#Qn|4{k$ZW&OFCi(gxj<90 zcABvzIop|mH>|CEDY=?^mD2<|KSy5K>=2hB$6%(YV?hhV>&b9fF$R*Efh`tC>y1~O zRDHZ$=%2E*+k+&Xq2wNfO;7ksdygTa#U*+v_}msIPt@v|yE;(2oNc#S?e84tg$HBJ zj(F5;Rj(_C6s@s$ z3ut9YJDZVJhnWIme~pKYbCv3}U;T`*=(GG_IpumJI@Gj<$$a*TH;*rd=jAl6uc9ly zk!gMA2S;)i0z>LoaWqUZG0fS|QySuqxf7OFMS8!%-!L4Mm7?$Bq;FcQx>GbED7+~) z(^gcu8Wm`5cqHXL%eiZy)1!$Cg(={Mz*q&R-1TamM=m?H*f4EX2Os%t;;StB|hok{O0!4lQs-&O>=slzDRhTT}NnmPE+o?v% zOGOBf?DJc(`W!%&YyQMJUFh9>&m=vXB^|JHSRf@;Cvpe^*ZfnhWJxIcby4Z>ET%he zr0?u8G=!adb?B*AY$3{)m1k%|1WnG2d5=dCO;Y31+|!o^B;1^%B^#MiJDFh1B^;zt z9L+W?BK|R7r#v>HnC&eb*!GjTZb;lyFB-_@%y{}X$f@LwI>@Q)Jq{D**Ms5bPwrbm zzD4XhXraVdut0QUN(*R5RTgoV$hMNsW~*5EVOuU}Qn_b!8`i2-STGY&$?_rfRQYwP z@}qsGJ+f~?|AlZcrW)1i*kuVg0CbrGy3he#t16v5x{jnsQc;Erp{%BqDPs%^qu-`d zg{tB=E#jnS$x`!0Ykqa;Scc9k5#)thgs|8);LFh$hRh4CH?n6b)7~Kbss^2mz#isz z#xUt2$=W&eq}unV^3r(2z-zPEHN_lgp+;l`$UpXiDt)kM6Tj=RE0jWo0BJ6uYYuyj z3wVaz1q2Vul`UwwvPpye!I-AbzMc`N*z9#mT}kZ{MQK!Z1zP&!l6 zm&kZ^G`SGes^|zBJT$Sj&QD&*>+;F9s|1GHFskaaM_n((+Zn{$YxzYmAB}WIaco9O zCmES27IjDB8Xk;tso%K(|Avh`za5PQu2B8JUtIs=MxSRGL^3;uy|jr(tN`b8?_ zsdfUB$!NBJS4>HkqbWwELAEK*s;T_Gh+{+9-QO+t+)|SH6U)INL7Af7CE4^~f_%f5+c9y?h^iA}M!fwYES< zuV3?qwPZc_mccLk=m4QAQQs)=4OV+!)@4llIKBlraoeD!Gm!Q`_w{A6P^N)qjPti3 z6UiF}?%ZEKQE~C7LXU}vE7~du#dtO1@(N~g0&zuf?GTmiD#B*Ay1!-B-bJB;M7N=A z7qw{&gvK>*(VQdO_{=(ngsmu*!fDby1f2EC?MCu-RBY(lHljeYa3qR4c5?Tu4CjNG z8z_nGOKObN;f-43eP2=Ie~+XXRG{j!dTbS_)<3wbvC(+i#kGP zt4k8@Ga`%%p>Y*~ve?|f&yfzZ$tBI?Y7W}m6k8QL;-*YnF$CBZ-5|VW>=F0u@RJ&v z2`Bnq%bH@YaaV$rwhBd1MelQs7F(;DQD7xvrf%g=muj&Q)z~!aXs}+V48F)!r;)KS z^;N{q4W}KJDx>NesZJa{ys>1>uxl_^+{(vu*uI?ar@x~ zCsXCsDA6cp)0;)j`p*%s1B$q@G31uQN(6#sF|^h-%Pp~tmQ{hDr=JPG{V_u$I)dH) z>5Qm!#Y&a+O{=UIe>B<-4vFiOdaq_4j-Q0g%lO?8xzw%Z^dOdAYJpqoDZ6{Y<@%0s zHg9h+zc+8*X^YM|8a5@{E|Lu4>hK=7GT|=+B7ZCHwqr%=FlSwy_IuWFskObFG-hjD z;`9X92(2qtwmz748hu;0=CukvD1%)(66B!M6Zw}L#|VdcBkF~#$3LbhIB1PQ(%n?h z;k9u5Pm*Xa$Z!)V?6Fx!ypc%Ir`VJkJzzwk)cWu<+&XNa34WK+Fy?GL?WjE_Oh;bC zt_NjVJ!~XeWS;^;M_)Aa%o+;EJcDN)%S8igf>=zDl__o5;2r5LYpDsl_vJ+q>kZuA zBuK~}9Fvz>!;cPrDKk2%eS4xS=9LP(zn>fNdZ8H-O6 zK8FNK9yQQBthZrchug>@4U116UXKWh5hXA+te19RXV}>2syVvX?>aqaU?&?H3p)3o zRHFOmfB4J%(3IQfP``ci{{m3_&;BxXX_fz5?^3Mk>8*0#^qG^n%A%9NWJr|ut)IXQ zB|uvx1=_^l0E5gh5*9mw7+#JAGyws27PF+4+8zm~rlr5Orj?5F>t*58>+56bp}cmf zy}6lTQA>NhtLtUzd6Snj(If#r_fh=c`}@?_y5T=^_dSxyd0#G!1=IMo?XahB_p{3Z z`AIfO8s}hS`djdEbpw$a%t~O<+g0=ROc$!+G1y@Kv#4vc$bRr9232_(~>nGIfC+J zYcCrWon+Ag6|f%sT@t0gv2&f}@vL>9h4Qp_nWgY-83oaSUE6qOfl4MDMGfpR%K~m( zD{-aE00h}L&~1w3^E7J|u?(Iz?#AgeDT*3y=W(-bc2z&uVDqcvETd(tO|(eodD53g z8)K$i6-DCC+>~My3n0XOlx1y|Y5h-Oo@cjAsyH=K{~_L!_019p|7*7Bw?`JGDAsAfOunvk-3L? zD=TvJ9fjt&$Y6{~K>_W+oz*V>KNt!?NO#h_r(~VK0mM+pSUH63SRi zFl(r3X|IDU*$T3h!w9*XyM45n$3Cv4RU}XtFIT~h!@hEhz@(BQS#8-|EZ>;-wIKuP z>%Y|dR)J(2sB&t+T&bhlqbnIZq=o@*(sF_rH5;x%R{&p@N>@Yj(l`S@vQ!DafVO3( zLS)(+)&~1dnm_5$jy&F8CmZrszw_&JUwoZ~i+$C;I2HCbe?Tap1NsZ$bEdhi(@{x^ zhKlhvyJ-54d}2}#id7~{o~jN@OD#90I0fJiOND4k-7#WoIqY#nZG{uG3O<`6DW=GT z0eA0Gg3$!k(Jq`_jmN)wTRlCnOu(wNQ#Jct8-rO&4*|b76nBH7|-oA<6)pDM%m}4Fj>Glv~6^4FJ z^QHNDcAH%dQ5mSE^Y}ZR5;Qs{szpnf!?sV;`G9@DIL#VFWBXGcrf z!?BoJoLECiDeC^tHRBd?km~+v)KcYX29u-4hiXEKS*|CmR2mbPuBr35W`1`3E>|Ib z8Qd`Hcdb%WV)EWiTjW;y(Jb_ZKJ?)FcUN!JN9IiS z$$Q!AE~Q*KW}azD$7~c2Z3?=Kw6(~VHniKxWlKs&evX>_uJYz$S6iQXsoX@jlP3a4 z*`upPjvC9&0bVIjM{N6b0Os)>dHWy9+qPD@b=9#~hJ_Q7RZiQxF|E?b6O=9#agD*w zHo-zjh~w5D`sRtpN{a6+_?WxG_^AQnspH1AGPOOyMiKJE$xkfieP9NstNOaVBR3xE zjP6~Ml^kRfwU?{dQ9-;uV&>;(GEGBWbN{wP=33bId3!OVU*)nG=NON?65*_KEB^VE zLjo;GvfwXGj$~xG=dOZwDe=XxsPdTr2j@k9d360>vs{Yh^h+vTHe!5u7>3AsV)jCL zK4ScSnnh_Mo!+6hYYl@u0?sbarI7LELFsgAxGBdTDDsY$8G~)h*L1!-P$2)?8W3cs z1TNTukFHl#S$2WKxx?%aA?6)z{BI^}O-zu4L13xl|EFqxY zC0H zqsH2zn$zv|&*7V8BT<>6N>uhX$wkEqciGa|wSbcvwuRnm& z!PCdH)x*oPHE8eJp5u0Q$KKv6$$9F6x>*IHmqci};?%n@k!zW=<=JGYe3-@O*_3oY zacPlxoag89$rgX26<9veWo@cFlA|ET%z;ZCJS6vkyhF zRtUb9L?&M<@%y!Qv~-YfETV2<{DXcVv+mQ~CEMl=-gwA_X(UI38wwn?&>2u2_i=K9 zk{N#41w5eNKEz+&nBAaVW^f0n@Jw!KKs?N}AiU{Lg5xU$KFn0B%x9HhEZ~q>C~e0ERkGy-LL%X6@g* z2%lN^c0;LF+86dbNf?=XUx{F5fW+`^CqP>XKbDPJWFhm?=Ke54=Qp`TPv(S*sfJhwJ2ijpSi+3+0Z;srx(N9&u(RYG?#G$+Q;1QD5f%f3DnkR~uH{kbojoSUa zZ~Q%?4w-c}!x3Cup?w-9G+raAMr5&E{+>hHKPfUyylnZ&r)irrFI zY;;{+GAOA>Uzt+;lO7LDk(oA53@IwcMdgHLbjOy2zHr@x?9&}|Gr6#Xvx0s|`}UsJ zx=+ZI9{~BD=8YzrsT3h=C^Y!GdxN+9qoXv<9I_z-sc;}p-wj&%hs#wRVXvdv z(F)Eqvf9XOa$g8Jkn(cB1z*|`vv6QF8u>5otEYR-!gPsQhX;eVcL{7Xx2kd^5>fYg ztFO#@9cQTSqva~mIwiBb16v!`yZ6xM_@$AUuE}e9DlTf0zEcvzqtnc<=&t-m2Vm?V z04KJMlDTOm|FFmAlUU|)9jWhVnmY{pCXG5))AxeN?{0!StPM#sW5UGFpo>F{FBLdt zoC&vjO495>v(6Jq4xbgg`owv6t-bF%p42fN6;-S`a*ug?Wc2#i0iCxi{92sZKC$%f z=b@lL-VAsY=;%>g@mNS80m*qqFo66?Nb8A?Jvnh!6qc&5Q~>H@A$q@ry0`0J>Big33O)-)BQ`$pmie5cG6yl?5{WQuI*nvBnQfL47j~JwpjSSya|R6jsP>) z@2U$HI$k~;p~~#Ru7{4n7+*~C7p36TKKwEC0C1cEpmTO<$mggyS|;|w0n&aL3P9)$PwtKWg07veiy^T#3X!@i)YHaz4E%%F zNYfi=>3IETusQ1Ru)#2Jf*e<8|Cd19E-@x7HD;nkmZ_a#M%!SD)?i9oa7vpvOS<*A z0Rwukq;kxvjx3I$vhV;~%ZNV7kJ!sF5E3XTkSiXHK`OP$&!G{^WP=Up zJ!PLn+9|%>o1Gm6-wEoZ$EPX(`^C;k>cB%*caUff-V-v-V?Nmx1P$iun=;pXj*=2X ztuoSXqkwm0H{)6;xFDkj87{`-u2~5^v!!%Ub0bnJQ}tMpD$K>5fJj>NL=ipe+KqsX z$r@~8v#`aYPnn@sv}7f+mMtL$OZ8OIB8;smp_Ed~Fil(ii{f_dVixeLM+UaBWJI#= zfr6(Je(eG~JXoG49jPD}2@Fdy;#SQMPcg)&A6BanOS7s=JZ%ls4zDaH;GD5KFNN9= zkM>Aga**FHa1ab*jY9#TgpxQ?Chqy;+G1w(=A7e-S^d(tQ%Fdr!UCfDo4a)M0EQf`CrLS`!;?0RV zT;w}U_hCNlfQJ8VocML)%y5~+DkP8#;k z?3)n0V$R+DC6_$>v(f|@)ay5@H3cq56FqSAfD8RL=kdr!NBf5>;7W9`l4opbR3?F~YxYTz6F9SZrOWnST759(TDRYkRqnt{OHKNoeNf&^Zg58*>VSA8TZjaW^*2&T z=|Y-R9YLliF%_;=>xW2{eZzv1j@30<@v+$x$w7N{0aUCgd}zp4aFZOfDc zN7ghYd1D%$CEhCo{z2CKw}K{!UU?~LbZke#OXXT#-$0d5r+a$Ri?|Dd4w?6-zU zq)LU#ex3ViNeC0~8-!@!_~3D8oEbH(7RSd8P8PYdfN{H9Asp_;V`e$7kWW7>xdt0k zXvJpHq)}Qe9CxC-W_4$QfI;RA+z24E5<+RJSuKNHQ;N1@97R@ghRwMGdr}&qdUa>Q<7g5wyn11RS1~WMHG$_~hB9Y`I%0y##15pl z@#9sq5UM)37OVcQ-56*33S;9l_TV=D;C41=+hSusOenj)&bsmebbasYXWPXv;hoy# zj4RU~n}n|OEDnp3fLM8uwo^9yZEHhvboEa~;L+*hc=t374&=YkwM>D`#5Rcw=0Jc; zcoeiADY+&$u`o5vXNx)3w8BbKCUmn*Sna^f?c10{iDjrkVUORA{A@P2&{gvLatfk zj}5TENIInKX`(_|z_`X+IY0U$rc_iEAXyw;D1eTOSR05*hG^iw3?xJyqt~%%mf{8N z9g8R6;@3`Erz$zs1{!)gT5w^NZA_PWr}po@W>FCC$9aTJ5A;(l670t9c>UrXIjPT$ z=bm^R@3)vGI7oU11dq`yx$yi5$=locS16ZHQtjO&ELzYpDO@~Rk+Vjpvc?iLs$X?_ zf_{r)X{PKoU#Bspk%T0b$cto9Q#9JiMidp~@U|XE&pA)np@Y*v1uqy%>+b!cD-tzJ#@jZJMJZ@)0E5eUj<)#t-xBO}eg$2I)GrZgDv(5Ti4B{|IA3Ez7iEnOJ#nLyoSJD;wKI#ke9WKGo zaChC039-^@?n-7dWb@ZY37CE>OBHK-fTx*Lhl(+o8YRreOr>Y&}gS z&oHu$nAah&nX>l>xMf$Z3H9q~J_I+oa+Y(9v8v7Z;XT<1YTAL7BD3otFoveR;0Rqh zKsZ5nv+xd;kAIt?$!FHvP=5)*hOWvxs+2RO((>JDb^$_V!s;xgu>p6dLHJOW~ z@JY{^gD+-=FNi zIpAX$K!<>RZRj_pk%XSY5ljE!xM9egUpZ)8fqdjwY&TIo4I|Fr)1>;SX^+l;9mqF2 zSZ~zll%AIOOK9(}V(od1Cxm|c`5fO98=nf{8_-F21$$G`V?81dsV6=KOGhe*>haxE zdFSXU@``)-9-YQ%;#+^E;2-Z;WjzoNN~H7^)bh0)cxN#E8$}(y`Ne+{mG(x9jf}qN zqZ?oJQO^HSp8kKzMZ(a|#KzQ#?7!as8w6CWdZvu3j>pdlqX|t*BzhLO2e(d^r%m|O zhgm>{LugBt5;K1?d5D6++2QEk-(&un?^ZHa#_GF({aGeAt?OpQv}2r>{rB>f_b6w( zt%LBx>m8#o{5U<@q(wzc9XDS~TwY}A7#|rUp+VLQ}&gJ0G z5_lQhR_Yn@LMmv;hvc_Bv+5;X+N8=Qv>(}0tV+8@zud7PS(KXCzq5jO5R4M39GFP- zWZm5c$pY$x~#q$rI)T;mGG~3a3*Ay7ghwqd*xG0N& zn~^l(SW1mMiE7o{Sz(yt*SZz9wU&X)j_NsWSmp8g`$wyaI8`U0ie*jex$#l9(_E2t z8*{9WDy30QTXt7Kyhz8C60n`2qfd-RjK}vT*h{>%hHA(rZw!l^r>_mUyW^dzfZ+!eI52ILY&^!ya@lfcg@7G(x7~;peNiu0)TE;y;gzD$m)>!UcU`*!w#}2 zeiejuSNfiTHH44a@r6Jne8}Yb69_K8&e~D55dz!S3_rbuTeHPwy~pd#Sm$odn$a5R z1rE5NxXY=iXfE(i2!Hi3*dr#ueiGeT;55&I`cz^4-VLj>o)7f=yEd(#^YXWtN;!8K ziYx6i>$#zRRzXkIz?=5S*3b2oLti~$wtv0#3duJYC!^`t-9hDsr?#8jRG)gFuc#BR zQb7Nk64F&*&Mv@fh`yb^=47F@L=&Vn6D3%OXL!l|YPl4=$0MTH`AZ7ZQrBc?JHPu0 zN*4}vHgcK{#AN!|-9tkJ@{N6hDVY87I_?F3$%Ez=pV@YRza0TFTl(9>{~^T_-IyB? z*}x%RL|Y%eHWogHQ7_d%!u%9{;eV0H^D@Ne&p;C&(j$3%A`KNP`0m&H$E2h0`x^7% zRx}piW>{Tij7kwPR77}w~_NBFg^lA$Vy0?jEWyw=H=MS0ATe@YEC41wpwQGTUHzM=g8L4*IJZ2zlV)wNx4)zLRerExTK$eKZ1&2z04 z7Zf+NBrpnAn3&6sO3Iv)nQ;q_{xQWsz9|hQGqG}IXj{+j{Ni8%Vg&jFWTN62o#i4~ zPqJXb9i*pypvDy(9fmbfi3ipSET1j3XpduhTD z&_p+x@UzZ_-BCxleEd-N=l?S4ps(vN}qLJG~J-7g+5fMe0WwZkAd4PyxehoU784Tdy9v<|-24$CX$lggbQC*euA&Tb>@eC>fX~mcPnXkkPKu zA=AxYJul5)2{yxey-)?D?Yf)-76{_FT7Q{z%s8wq`U2U3HO44KmM|Tam`j>yTB&pA zrsXI`z?us0KFt1b%f%vVw#1MTVEFE=;_eUq00nQ<{$MmFTO-`!X$1Q0`ZiR8bZHAs z@ro!`6pqt!mmxGcZl3D(jAT_*Hkzv9MA8tB%HrpbNuV$ywiVAH9?QLpRUS^vFXcp5 zSLA?74)TQv5$1==h6PM6MXY`jIsgn7r9AAn>{Ndex^(5JU`zHk4?{r3zkd_^kSW5ywMmS@$9=OGrzjv zLY2shx~8YAvKnGEbotX!@k%a>yyIL->58LMw^!C9G$3=f*5F%<#7f(t70O>R~yGfSvm9DqEusQo+@y;t3qv5NFYcUmw98$1n+(m==Sq#B(5T|jP z;kS&iv*+)vl*|m2#Y(s+%TiNknnW?^Kw+!gfcb}v4cne3Eu^OBrwK;qxAuw{^7#gpk47^S&)&gD z3uUXRy1xK=({#D_v9CTGA#rDUXt-5_I3O0@NuiDWSUt4CFMMS zEepVxVEG;v+3#GTSPiXQG=MGjZW{zl1eNvBmDZV}vwOTElV+<&WOr)Ebh_$5*yORt z=BhiDRlAWXG_Hf)Yn<&8snay%db{WeGg}@JwZzf>wX85%jzsdgfWQ^2rH4Tk?)VsM zt!DT^C#F~x#|ypWyYmI*-#nUCxhksJzZb)*^KHi#Dkt%G%qwz*3=J2w+9bza{hVzY zh4Y}s7-Hv;BsY3;19acquWos>`NLUrEu^PLruL(3<4Kq&MByS>1a z9#OnOaAj@Z{90VVz=Q$1$P$nAqKRD^f=>0c(d==pO-;XHkxi#Ohr6@PQPl4Na~m~P&Nvwk z01G<98T>@w=;b$zki_5d3STr0at!u-C@HM$ zAR3I+=N)n=Eo{Pn4Ieq0Ky*?I?8xd&a^TDb5F}v72#XB><}^g+JOGdo-9!QUEQP+R z0YrPmVII&V1OXC|5TVxun7x+&Ah_Jn?wXbDySu~kRVM;HUG`@+5f7H>3b$u^ws{n` zw0?qwPI{2>T=QIZ2&TNJY_b|~x>~S@v4}ol6;NB3Azjn?(m@9Dm$a=w!5N&^fNCF~ z`*%;9+wJAl;RimWccF6jW1C1N8-&Ie&8T-d+h!I zviBI`!#N#RCI9r`9{)zUH*$l~<&Qjm$hQC#qMmjueQc-g_?0q#Dk^nk6RgP{MHu57 z!BVRmZZ=qtlq(3=IG(;Ue9SDE(@bka>Y|it)K97GAg5B*U$Ea!G)SOxLA>a=? zC=b(bsO`m|xF@%k?smoWk6p{-bKhAWFV9AS;Uh7X(IK(JTVk1wAhvelBCr%qyx{?U zlTpEhkT#!1mRorWjih?_l1f+NqqT7k7gu&!54kk%hrsK)F;i*N{)^Ct{a%aH=1-_K zu4vKbe~|PybLpvwpVUKg#FvLu(%3=9E4|iX9MokzDK>YX{gE5&2(daW875H^du@a^ za{u8C*n&tSfsObi;F6ku=AZ&h`*SfJ7g~!dhHtX*%`p}1G6$BCi$5>leDIf>n&M^e z9zS}h9EnB@&0t?vX#YcGR06rQCuzbu#(%zpu~Sq zN+#ZBvWDazbYO{-(+Q8GVcAu$P)vwe%UQbG=z*KSN*k1hYM8~&TVJysJP9_99G!=myHWc6!;V#os4f}t-*A%=%F^Oy-^G)- z+Q-jn-VeZ#2X%>UlewytB|k{q{7_?CsCEeNc6BGbnR>Sj1%hZ@at5{f_@cpd0wp)r zXQTPrN0f*TAz#MMM=L4KW?#|&d&jFlh?3bF>D#v!^8eSm{BL?V4LI*4_2kc76Fuy% zI3^P%VH~Wy9wt;QDProllHe;?GBD2|;UH6q5GEIv#Hm1W_f*~Fi-JKZb#3x$4aa4h zGdt1XBKi~z?brGmwX?#t&^0ypO;z{Gn#E1q_ig*GXETa)w!qhM#z)64uj%*CY3|1j zucOr#!Y*h7e;ap_0DG>hNtTVEBh_nqRc~!{#5uCH(f^#snO{Zh9#nZ-}5;qLO6Be%DXiqHNS{j!%- zw>PQ9Ps?Gy@)ucN-|Extm1}L@k9C;ZzGq(5?t_8L>mQ2+hhM$V@Xy}Q3gG8a&w7RL zFoQ#ZSP(w7qG&mQPHrJ$k(h%rL)F$jtUsKGx*<{-Aw7>L12Cq_KTC`*CzC1_Dx=gi zPMnyXmj9w!VYYoSK(|<{#{R0##jJf_T9)yi!J{TpF7 ze0uxoff}8ZcnB1LaM^qhZLTrwgZwUsdeRk4skA#ejwvKaQ2FLrQe!o6sSHjL*@a~2g}dV(Sk=oHW4 zy$##~Z~90D7GIO+PT?AAMd^-teT#yn=t?B_(3fObQ^Mk3Or>1!12v=tl$Ely#qUSe1^^tRE=oRqgjzft5!SX2-JiP|;J1NDFoUukI~ExN?m67Z7+jK} zR^|!0(n$uZ-y!Z^Gzsvy{|G|e-Hx$3Utfkn*@eOi$&hhpALycGSd36ouPQ$$y6HqG zjN34nJ7;y!^4%Rr(_lRBynE4k0w&~a!$nE*?brcz6|Ixm*(@QkzdIn|7}b+^cuH>F zGm|Fgj3La@Ruu&yPf|Ta&k88L`g~sEH;{RfU!j40qLcq<} za&tnO^gz}xbV*wiYFhC{r+=vlRQRr4M@Im&*bxtDWoDSdss?{)?0W;p+sIQQ+(Vaw z7Iy__`4%zD@^L;;#7}Hu#Q}r`I4_P3A+0nh5>^ua9e2Pe+2g8lpHkB#T_C;KwLu$IgmxUTe!-j(XVPlO0zjZo+L0Bjbcbp zq`L(24=?>@G#Phyp?f*d{2G@hh2eZ&fE=@F;oujw8+sQPE3R<)nJUOB%MQBPA~11w z%K>Mrjm-dTP!1oCK=?f0S|l$8n>UWRL`T>}#je1HV9cHk&cfBS$FH64D3Rh_xF-@+ zTcD+AzbE`Gc52HJJ6~8R1gkUNr2OCBkq8fasbJ@r+)D9~qp^i9pHm!_2KshEtc&oV z-+bw^AW4D~B>Gzmr)s*UaTeJW5O)Wgr4v4XSnZCZo+$h{pMPzP@vu$FOp#`d`qXEA zi1{_UT`XSUC_;T-oJovFNBRz}(FV|w-!1T(v>HZMO-dTsKYpobm+(qcR&CVE3Rb-E zIb&%=t0og}u<^3<6>4!z?{+m03Gn-~xf1^>205@84vCe_0nEMU}$eUGPKA0@G8W4&Mr5N5VZ5Pr$k# zo#Zqio$n3%V1}b6nYT_oQzWGYht& z(tL}R2fukd*-SgA)Y?hVY==VRo6H~S?3tefE3~tp9vWvCt%|oJOJ_;{-xDC_vr^{C z*N_t-I6d2v_})Eo(w*v~*z!HaPju9Ato3zv2RGzNhx|zKl$IYUvUbjsKy9(Hyzieg z_f+A;QGrW<{1iZ0^cmA7+ppT!f)q6m4x=gvwNk;K-N6`NT|4%~qTi;mKNgX0fP#)V z;?c1&MGyOO_=g6q$}qf&`=xc7_yEM<k_$nPPCIlXe73cwO5NhUy0b4e3lY9%;40 zNC9vt%@0!Z?cum~LGcDNtEG`0Gu_*Zl0#dnMe#OkdUFL|Fmzs>j~GOa!yr*KA; zyHqUbBgNqSEb@&*6g%$$jXkn|OetJreLAQd_-elkR@a^tiecV=;0uf?MNbo@Ma1sP>E^<@?}AgEiA9QLRwkUkr;D z1+$2HteEeb-RcWZQmeeY#paiE*A%ubNS6-lT+39)HU z@Pq<})?>znEk5D=9J2F> zuYEdY2UJ#WnxX*pv!pVuEEq8C4QBOBrRdK{X4R z6epcAtthc~qNo=MyMEYm98GFe3IJedex7HBHIxJRG z81K2TgL*XjdiXQyZ8F*8eUM8kTV@vJfy0)q(^m8}sA}rVG2!VBHs$>{cF@z7;KNc6q*f&)anB&urE*#$IPamiCAzJ9jPdQPWq_;}q^lTXdFsqVo@Kd$J>0kkbds z6ngJ7pYjn_7U#aB-8g6?r@sv#D2H}r$^Bi-c}pSeR?TTkY-)YT-0qlUvq9YDzuvO1Y$7 zIOs9nfe|KD^64AQwn(4Pmz}Fs=mnT^;}&ilNmi$vz#Wt+)2BQ-PeVX#qvLFisY3a8edu{T)PWS~mH52O{X*s7m{|8PPDo?}WBu>S*cRbs0w-ddtaJi!eM@ox@r^2)aNR{{9(kXJ2HCd=`_XS`^-sgyN=f4}{>z*9Z zgOGW2va_SpdkoG<2vq%hQ;@N2ioYA9Oh*sjDcsxy{T)ybW@pss>VtFq?S5`v4PH~x z*jDoZPjjkE&Nc#6(mI|*#&q}m=Jw1trYWD&aOkPq*W)9ipR6@eS3MUWwuR#}?h}A{ zefN?YrETZnJ5hS&AA>ur9&99tglhfVi9V3Oh2doI>ogY zSp$ywqtg#*UgtqgZ`YcU6GGVZPRJOZXlkDD89D`-gyPA3y|g3qt)e--)X%@CoXvU~ zC;oW85>V|No6C4@*miE?h_4mu+++JxjN+*jLMJWL&pqDM$k27=K=LNWvW#%T+<6AP zVQk!hU&d`4M!Yj%)Nc}PbLx6Px;s#COlJ41K{*+NjZE)ZL+$%cDYG7>g}~DS5J7(| z`+s-GINRrN&vzV*^WSc(4zLeyGgh)R z67xz%0O`z>09#!-is9Wl3RW)XfE~f>^4P9Zt(wNgrq0e6`8QOCM{K1c%(a*SGh}+V1aEJH9vos=GaY4Q%fq%kQkz6D9e(RU|pmAY<=@Ay4m$nn)qZRdR(<+(xKt4 zCWBqnkp9-Mui&IQ!Z{r`2GHrS%hH~})v?6_W}4hG za?n?K)ocxI1qI>F$D1#Iup9+tHSV8pr2M9P%#Onql0JMweBQe_1kXLnB7(;4qvW_3 zTO44i!EUuPf>cM?U~)udM{RM)++7&opeCGYh~R|}{?}^#wvO?btzk9U(;m*~m^$~2 zLu88O@#U|)6<2R8sN3YeWVz#a<#32Kd>PR&mf*(ivkuCdW33CtLbc6V$$*-1LKZQHifvF%J|{^#7ObEf9jt>3#{`{lPj>(P4F zdOYPj2sBeLVRCHmZz@onI{BT z9B|0kGrZu3cfl+2*x7T7uNmcB=idUzN4|OAV38A&Nj@S?KjPKjlf097eh}BPEQp<; zcpjol->}TWd>Q7<*RB2>e*4{1QvN{2YLhDZE`B#u)XDi0CTzF2d<1r9dOyPnS^FaY zYKj?~^9=b?JO{6whm$9!zk_I69Zb4d6WCU~BY3exH-GJNHo2aaL*O=l;S+CkZHrG7 zd%&X8Pvb4YsCn0Qz#=?3sgGI2B5Cj>^bJO4AQ{-hSKlf77Em*Y?z5Ugb3haR=%I5W z`407;2w$-@snz%*>0!Pc#d!Z2!kPaC@h|Hpv?Yv>$&(s73x*ZYmC)~b3aHkh49qg>-h|_T86~=ft|AFbc$-_yH z6!4Xp_SOhYM53F?+m;hD6;7#97hXkz=zxWX@PT~@L&Sl|06hw!(M$F(f4ab`0{ZL1 zuD%cVsVN{UB8F~*u|k^QqdQi)`>6JiKp;^+rTh2!t_PCkE(P@7#Uc}_UAaI2L_UIv zw))9H_``=l-SM|>_F(vFwB$Wv-q0pvB8yD$ldkE7M=TpF4#)X(D(mF={tUo1#)a9< zHTay?dtZ7otVvyMTBHX1aP_EeRVQuR)_FRM6wfi=8|VCr$yzHvAE~BXwwpx85^a5Y zqh0kk{(ybZQ8TZ=n$E4dAqQGbjBV~3!RzHNaUx98Rqr>m?anv?+XN=-)3JVYg-(pY zjkSjLVNpzvtY*3KkVPA<2d#kEuaK2IB-R%#Mp+GSFy+1ZSudC<<#bZ8)x4reUj_g) zucYg-W@bvSVIZJUb=dTdj@{TPSaXg1M-_EGFl!NN4=+NLQUvqey_Lf>N5~>=M1-=g zrQGS-m}d!{3+rP`XRVwyR`=z@-?q?M5sOD4V@AodEo;}=wKjK*c3QQRmlL8$VK2WubNUwNJfUkTF8jWVE(cwddv^FFaS6wd4COZ~g;K(}^ zV^K4&5ntm?Gq(KO;86>L&-66lt=3TPJ{(=nepL2teNtX#AmXVq^0MSfP_EP+h6&B| zVbzrrB?bu|o?TOOLk91eQn_g$J;Jsxca4r!uEZUqiY0Wz1B1+6bO7m!cj^OITBJBj z!{H(%B!@8Evd8zWusoCkJdM6IJ^%P%4x81{F-CM zldVh(^ZUUml;9vXu!f!Fpf{sB@_Cp_|&keoJdVv+9GI)*FrKCUNme#o( z-{<)LzMNgc#)O*3XpNQ@dhV32HF?C`B=~r401e38#Cno6iL!$1Zlgcwy5ts0GpvKC z88U!d6(uj)>0$!+01hqHr(?!wOBlejk}zco@74Qc)1hS>`3sMh@MltTiQ^Q6GIs`a z9rnyfNO!6S4%6uTWX!d)-3S6tc)+>=b79I$baV;g)E=koM8KtTq;T6zRffmm+s>Hg zkSh6tEd~%zqnl*7R>cABMwdKx=^VkR35{tgN7wp&$|P6vPRzX7_CX|)X=cKkOJ_WW z98tS7OX}?^ZG5X>y%mqK5oFn?^ctgLLQ#1X7ghcc!XBW~mc$4bKVzL; z?|Ep0I?objroi500~*jOVlrzNc!IiF%*9Wp!|`Qx!}kfluDCeqO~gg9U;IY0__Mp5 zKygZQT#j0?pw$cgXRI6vwrVd|h*7V&dB$KvQ{Z7@{o&O>pbk8Ck~oW-wo0_8xWg&6 zE!sf@m6ZM@ntrR6cO~Yp{;3xO`Y>9QU$;=HSP#5k!T>?P)=;5czA##~7kt1{Y)tSQ zyW{f`%2ukIL~8Na%qPb#9g;Fra2RdnL9rJ)0+#k$Q;eZ+Kh|~j5NI$NG6E24t4ix_ z=X)Mo`Z(OSYeR8ma;b!BKUZWIcY=ew*bu`sj99?l5%|~Hyb?M;+C4!P@NDo16(gl=H=J*-?{B&W>ort=%a5WAM& zNa}MoMG? zj0^=-`JA3oQnQK{Py%%4g@^g@0!#?AEXT+l!~&8-1?tCv&S83qzEd~i&Pp$ML(f&GB0GD4#rpqQbais+P3r3zq(CrJ+8u3 zA{f?hSO-6Q)T{%>B@m7HVhWj~<&B_Xc8n4+?Kw#gOH?8E)sc)r{^NS(f3+kwPH3v= zzH+rAg~yGFw8<~6mOB*Lv(K13Cww1@OoL*^d8R@Kh@LdBt5kx8~ zf7&C1raq_#>)Ke9=m29OuMg{WhgrA4!_D{8<38PQdrz&Q2EgM!zT&&vSr!(>ZetY4 zLfBMJ7PLH~bZ(1XYm|3x0x=1c4)$(+Ly1Okf#M&9^S-qm(vvB>2>c9mRA-xy8dm)!m=?I zdfdB4!PFaIfy>S_HJt3F7)Ohy+i>P(*n$xq(oB1AR3|9RrS$(U+E_;6RTFgnAXne@ zn2WFwZH@A*6c|?l%DL^lONI%J#`H9AOT%z(4gi0s&zxnx`-w`gGlOa-5~1W~J#Yjt7Nua%#ov`fv1CrY zfcWc(&xI+{g})igATwwaAx-C3;jP9oPJ;uv-gNokls`_1W(5S4CT+!e7vbN}9_aU| ze5JBpyCSIZ8hgI=-8kKuC@}Ko7!Hx=?)>gSMPJ5e%-R zhYPx=iyhihdeDauUU0zf;KYv9BWG+P=Ptc3KlIZ!dvG^`{&-zkK&4;oL3ySc^CcY2 zOmJp(ggl}_+8V+WRX;)i`(S|kktr;GsH5WpUFF91v|&5!o7>kejlAhkG*?w8Y>Yqr zBCcQ<58!_Nm%s&Ab5n-pOW-mL{*O`0`me4+McWEZ5&46%Np`n!aaFp!saP>#(NtF3 zJB(VZ0`?j-E|;ly#v-^qrTL1X_l3m~%}_0(+B07OxfbcQ za6N3Dy}VlYv9*(?$y)VvU?e_iq0q%RcEJIxEHF)|$bH+TMHF|G7k*tu4x@L_p|v)~ zRRbR(a#MhJ1#V$IPJmeEypE$BjaHnIljQI@dFGN^jQBdRXNxY-`3M1|SEq(uxI5=|KvHF5I>#)P8b0T4>rjl4Cd+b@IL1Blr>&4S zl?I!}4LfN|JCr9D!|9tWO>C!rxQoxxZwsJ*`kLl=T?(vk9~R(CwftAWP$)KkLq4=i zZ4iq5A$FbMR+dia1vaXa1h7T6`Nr(pUsex6FV^Dt2HY8@SVW5in!M}uyAZO`r*N}k z4CFs&(HcaX;4Uf16#Vx?#9miU*Z+EmpkGR>f7sr#{jY~8KmQ&5WAdU_H<=L?UASHK z#|DJKK52US3^u7$Z81#n(cUz{Sgft|s))vC6+^Plpkt(8uVx9d=vBb>Fl_r9 zp_z_m+**)FFY{3mA_2@-bcz z#M9f6hv#MKNwT*VI4Zc=%g{R?X9b{cv0Akg3s0D9Qg>Cm69o(v9(BB;*gk1=DIJ&7 zqe7N;alne_f>qg?QeT$z0~tRW;t->5x))#?K09&n6Wt$Le4a>#uXjK6wRSS~6U{Jm zjrjgF9Xl|6MnL8>arbz6$$#_V9=SuW0wps=(5Ggr3+DgTJ^aMC?;3Y_yHxD`eMj`y z5@L8ZJa8*0sV^fsMlVnJr(%vBcK&4vli8Hi!PwcPKKH$le;xNwde_^byyESw`HUdH#2nv6$N5OWuWM{_RK0{6UfYKTSpzUCG)1e}Zxzv3eFE672W?bmh;wyvlFFT14!DWnd; z{qZ<~Is;ztCnj@rZT$MkE1w(!s^yZ83xhKqD%Ll-7o&j|FhUx56Bt7;q@KjocLH-1ga%r|I#6Bs&4L0PsP8nj@%DolVa*8fg4e#DKNhf2o zvk-D&+}5}mdjtxMC64uP1x8Io%Y!GmU+UFTMcg(`v#1@h%{%^|M{3<8;kGA?nb=}f zCv?%Nk8~njL1ST7y5&!;s#Hw#@82HCk) zTTSCFg{W4wgKv6dn$X2nt$fzH*b3T!G%l->997+kf$)>mF{!KL>zF$CXGyG;ZR){% z+iYsbT~Y4)l~;=V*EvjQ?ir3i_im(+P<-nWTVQ_1oL`D9K;cD+bmkNMY{>l8!;^|^ zS`4Y9q4b;Q2>cbQ04&Lk2TqmB1ar)y%$OB9GeSg zX;cyI)JLfC+zTJI>rE0j5%@C{eTjd`&s7bgua<-f_8+pV+3|R37LkmBMqn# zVW@(x2DpPAXroag!_QNUjRTEk1}$>f822J$vy;grM0`M*#)e0@srL8*clj0> zAIt#mv~<&0RyjX5{LwT{p`Tis+ZUofJ*1LWlPu0wT5hs6mCzKwM((h?{}|h3wBblU z^2^1e@$_lVs7*-cnOE^i!LNNY+Mnz`A{cmFRHst z%%10Rwp5QJWaGo(>??eBy0S*^VajCmj@&j$uXA$mVAtH4NA>*9iM)7T_%2Cf2~AB9 zn_hMc4jSarG|X&IWUatHAR3&9P$M$(pi}o9#RSGKuFi{+v^xTHc7tY)F}(gDGv4aT zK^c9bkI!j~3R-D8I?$hH-$X~{inbVkji9NxqCz9RsLJAbMp|_d+myA{hOg7I8CYK2 zY6(GA%1BVsY4jp3H=^#cwfm#7RpPKrEJ;w_MQ5SLBTC|)Ruav2oVq!1jMWrl+(2uv z-GAw-)t~E%67bVV^w0MpP{5%t2k|63cJ#cNrZ9-#iYSgKgfw#r;ziyTw$_LCF?u~s?);CE|iZq z)O5Qr3Av%PZKu|jG)>)VLoDMLTfnx+QkD0O^*XpVj`)fZO6n9Jvw0VnUD`C@+Odx% z77jpA^O4xP`_gBvELmXXm8%R@QBFAjr1EF6d0q^sEYFL7$Wd;Z*c77YoP^Yt)DO=K zzjthFA(2OO;`V5{Ryq?1=Qx~ghgS0V&{G(GRnET60+&SBi^5+b=4b}1U18BDBArnO zKS^|Lqt*d;NT_2Sc%ko+g$A6|Ho~?f446Bz{eUMlC`hu7uwlqV;bX)>NHP;Rm|6#g zWoT8uuAOO0E%1GNh*?_VkR|}?EF|GkYgl36e1jhp+APxk8?-5NglW3^XLEE*tO4ey4|Kt)mWV6l6Q*&qJvXqlYsXv*r>Bq->Mz4~X| zH2c3Qdo^u4JW=$I!6pZwthR`>;rwqB3o9;*ULsi$M5IA#~ z7VOlX3=u2$T8xu>=AJjIT|RPOhE}S4&TlxH_8mCQPyG3))r6<$%aZ5-$%y_)8LXM+QzM~UAWMTK z6udrj_*Yk|`PG$@kF+Q)lx3VgwCcj~7nN8~A&hfiq&AvqxtNW%(|5G0e5h$Yi%5Vwcn)*qZTy@g0RR2C} z2kBVImcz}VRGKiVEE=oE87b~)!<%Ip*ae5)L-dtoc@vw1ZB?>VlkRMDBq9224Fk}4 zje399>PD!s4cHeGKppe2;~Y`%Hcp}fYzv^25rU0AOFEBuw381{?vgqRe%mcz~lG8K}w}=H(PGwVqymiY_W2kM&pFa*6}+Tg+Rg|ngyMD(kkCmXpV%+;?r5poWem}#n`p~j*fGg17Cc>P%V%CeT>MI_M5(Pz9kM zKPe@TA%)-eb*uSy?wA|G6x^XKETbAh_4=XjnP%|F!hC-Ymf5hKf#*uhj#pe0p{$4{ z9(46}gZ0}Y4qlR8(}g|%fE)2T%2=eGvB@WWLU?~(-ow};tAL83SYys@j=~-rn!6V2 zS+RjHIA#OBLA-U(rk0zT0{GJ-q0UzMUHUWm&*hjY=R>TLN%6i&Dm88n(9IJSeuf#6 zKL1z105WHWFn$FL&40Yi!tt+wp{gy9CXD`3xKUex{-b@zXCWLtP!+`PUh1!@B}X#0 zk51zrt8rUE+nUxJM|$d0X&8(!+WiWY&lM`#);rE1o3!ZH!8uveN#IwM@OH9&eRz#f z|Mud*5hOdzX@umdH64-1`x1X7FC0+vWW*bN${i=dR?8q^_zC%Dk5 z7i|Y3*xl^`Gaxy|WU{)1m|w44t*}|hLbiqcm|9}Z(N42d!5dH^;&2QyuxdcI$9H`beXnE2E|`)Dn9xSM>*4F3jY+B(-VHr+&W=Dd?w&gNX< zY1v=qpgu|KT@qjZ!byZd3*`HVl#m$(B8}!hbc3_E;ZXTbe@fj*t*I}zZF+ILH zFX(28JY<+k$T4^YmSZOi$40)A0+Sb}w|}(Q1wIxS(1kz*-ys}di!wT9qp-gfT@c8T z#n8dO<2eZcOa%xfjKK5uEAMgC-3O`DKjYTm&%4Z$q8)-G@S0ozs-gkeM&`Obr>Xkf zojcY>6038)46nf5rB01+&eI%lqzcDdjY=X(M4?dpmblJajTa^q0ExiEeHFGaK7N%p zv|kJ*oO;;>Yrlg1bDpMV_0k$v?kU|YTmrg4+dKm_3XAj_3f(@TaR9CxD1LsNmM7>C zCs!=EDxN=@rbo$XIJC(<4|=jh*R(Bc!vpPVq7LqIK^u*zW^~!vy=$m*F`!iLO!V9w>l4GCqdG_O+@0mkTPKYtVU;gL^S@-?KGce2IIgKB*_ zg!UK=FsVz?iM@`D=QS)pI>S5*zfgf(LLYh=UxDa8g@SnZG7B1^{#58&I#LY?bV~)+ zW&`tR_M;w47C>rya~DWzQONMP98wRSYL|xW$y@92GUL>D_YLnUj)&6v^m5O+qv~Ww z{;Qv8vzPa?{{o@QKL#P^zd>lDj4qDsd!VK?4;cJ20-2)_d7zF zznQb=(hOc*>qyTeixo;Jee=&B6_Fh!&%Pb_+=i-9p-ocp{Z1?3MP-RBuwJ^7c7A;9 z+xmK(x%KgWZT2m%Idecb(s>tK?6GG+9COmxo-+b{5@+X`o^jg{lV}eOE*m$B1uhx| zvlg3+Z!fi)*V z<$8`)Domb=oA04zB8m9(hv;-M4xV_x;RYUT<3iR(Wna07obxze+k8tS36oeY}D2y>Q$ zS;y#jRWgFSEuyNNmJa<6^|Uq7Fl4*qbv)n?H+#7Ny|3X0#`4(I4RP_RkZR;xF0x<# z__tbc5jpVi(?2REtF5997oLNw{<6JW^H>*$rzRCA$k#+7Q7unjVN0$ms1Q5@+grgc$3^{S^Ipo{w9CUsixU)XOU+UbBn+)XndlT3`p)ggtqdCJmo9T6$L|r4Zki!Cny1sU06z^7kW!BKXG*9!eAE%u)3% zXbnsle7nEJGy9SjN{^>BlbUmQpkqOf=#Su#hi+XM^@V?HzYjaPheE8xmhYU}@!n9r`cE3l1y!pYrh2gvfLA*s#yrBhT zS@&A_ahB~lCl8c?z{fX$0Eb|JxIg}Xi7e970n0>)G_aDQU>)$ZecEaO8|1hp% z7}}^OQHcKz793b>%P4J!?np->iyM%T%$t9FCs{KfmUeF|Mi&w&)Cb@7%``Q!*WTkG zXsZv5AbOF~26uIE1E(TCFV88u^OoRvN?hm@QXlm(+#LcE#dEZPWe;gmMNLR(i4d(!01(uXb zR5qq(D1GSxV#(hZni+dta*{IL0;5-yV8F1~rO+iiki*I2$Xm23hGc)KpO=cEhA`yq zQeS^@R%Odb={ppkg^?MvNAKw~y~E!}jPF+20S*pTjBZLU!Ryh}Vfz~T+he4bfl6%H zmqCTQ6bLpr4BejpcZ-^mNYM_RkdFY{vdo z*p#i|et%@^Et#ufpAi{wzh>(V`exsn@grXHatt?0sKj7 z&IEi^3|9&|<18l7x+Xp4nL7bcURe8qEa|CS8a}K&x8y1r3}k4m{EHAJCE;KemTt6M za`yd3%`UD*q7IW@YCkZ%2yJ&gDj<07+FyqvTHb_ML33nhibnkn`uH3+lG^B-5B=Tf zk+a^Qvi4N1Jx8euVWf`o!~+-|JcU{3i-Svqq{*`Lo&Ct%c0iVqj&DxyriyyISib*i zs`xG3F8R)5i707}N7-k?_KQT?>vx-x5a}(lrq(B7M^|^tQnuIcw@dPaJt*q8z$~6J z$KnUNa3lxX0HJL;o;|=Mh-Z-_pal#__<<<^XxRY+;*9$I!d`Bpp8h1d+eLw-u0%=h zp?9htzR@tjw<@PX@T??|qcZ3740)(^X7^XD<@Bd>M>?dr(rov8+?k2PgvnuLoHigL zVhgC5f91CD(@L(c|LI4#CV$O&|9-bTc?kCj`k&~92>jM+@rB;de~e!4f1}q%SzGk$ z_BYddte#dsX@Bwqt%jX@+k>n&s7k&!Gc(hr9M9FeGJ6pzvfY$@?5O@o0`}J`@#`dkwJ#j!g^>~P4Jj*X6hF)i4TMLs$ zhpQJ)bG?5^)7xJg(-tp^2d?rf`KEGF-D*@Y2nm{8x70%j*Jne&tLzLKwc ze9p4@ylc?vK^ur^DVcf2U^uuyhF?S)#4t0%iEQKAfBvTpFrs5zgz_ z%2`KA#<@A@f#+6ZXs|FSP|gx5Hu{KY<%e|(sA|8vYW9Y2O7XxJTP>j$R>QxTkDN;L zTr0Van)gx!@aaJL53PoY>^M(G=H*6BT?YllN4b=1E>}AcWx((0e(f%#EY~UyHU?E$ zpKXJvULZUTGe6l;GOr(ROc&-jm~9zjyIa`pkn}BNSq#LMoQ?b`1uyWdp3rE;cBjC> z>&)`eG<+iO!l+=R5<_UC;+B&zKEjfDENWQ{FXq(Z)m)-L9o-1VWWmWg@f)0P>YV;t zvAe-2!k^SMd9H@1}%rdClK%MR953Q(44<*G#j>B;9^PDGQNYqf7FB5`uM+-kVdugZp$PET;>K9GKlP z;U*b1Y{mM7v%$Q9crkKhN)s69GiFK??VK&U9ZeMsMcXr`eC%K^zYZf;-(u_cVH=QP zoAJYmB?2R?GZ>jN)mKm*_V=L z8Owl@dErJ-_J6YP&)}^2Df_eo5>KU+7uNq~wI$8W%GSPE?d-25sDC#4;`ujz|JBJR z+S2?b4bE7~Rn@Z9wk2io*ASMZP1g?%EHc1JAz34+V+?clum2M2o;pGO@MzOK>n7 zq}w?q8`9SJ>};j3ksiw7FlCy`FK!(i`yOvlPlD+D^sR^--gdE}27l0Sc-|yaJsNPB zJ3xGBRl2Wfs0oR2E})xQLrmaMpGOwmmdX(uMHa#nEckcjKzN45$5+3Sp|f})nk3_} zvdp>n2HZpP>POAKIZta55TM->rml2Z94}{o4t;SN=qTD-35iI$VI;BW6wQ55qrdHo zAL0J}FHgu3Ht#26lOKPd$ljNJ$g$$<+T@p$F#^9aww;C`yrPvj?^@QMa#yuPNj!o& z;`i?<=Ts=I@txibqfH(`Iehma$zfs6=Q{Rd2o;zDF=!(ymbdlmbD!PV!Fb5bVR=d~ zbKyqI5(_2A&U=P}Y<)$egRzxuv=U!OHZ@BbI+{r=ooR|M^W!9`I|9uKc|pB?>UDnK zlK56D>^#G*$I>l&r?4f>AR{sTV&8IdfRH{6I*j=1`w_Nm{RgH^Hp=FarJ%^Z_P3?v z&Teio_P|SXO#+ZeY!%dmQF*za@uT$s|KI@0jvLf{gCEds^6@q+zs=JKm%kIOy{>*5o=SO2E{f#FQ+(cu9O4 z&W>@s6rYX-Gd^17XKh>0xlq-j!VW}EU2xm!2euq^%BLROj!s5kO4_bkB7dvLv&TNB zBVHp+Km`LkhzLU2A6^5f@x5emmuxqmqVrSShi#~tTU7#G_P4-KByRIY!TDakFJXRv1uEhCc_DZhZ^|@gnXae3YGXfsZ?xtFXIm~ z#Vg=OrZwf{Bt2Ul`E}>zYbS>2bvM}j@51ofQQC(5(VLXd^4YJS(q-)p_3bYcPP#>JRf&frQx%ueze~9pyF`?Sm1WHZnk!~x&r~*4=y8ogc*gwF6s7nq&3i)ap+ z^Ln-?>gstXodpUK?+EjgZyN}D1R}*??yB$)GCef^rXo*2L%z!YU4$L(Wjv?kX`|ej zv=0p+E)1P(9-&Alq4K1{(NI(u>Ij=;koZGwi*365_Vl}y0$YvX zy#x-?0ZTno>4^X*FyHVoowg)X8H;7y*qpl{h~9&{ezZ}RBe`Z|-`*#+r>LHF-06JY zSOCYdW8Nl}W<`$;Uww8RSt+yX1jZ!QZ-!E=b2LQ~O?Nkytfc6OBGH#8Z^frJIfuYE zVhoBZQ#85EGhX^3;UTP=>a-A6R`lc(D&(21a8#lA+IX3|XjrO-t!cj3M=j2Yu4f2XTE2o{;(@Ps0xtoz+dhrV1wJIy2R82m} zIxDLzoKvct5P&C%>nZn&mC)A~xdVJ)B)QE?lqubt2ddB)h-?=>#bj9*1>VbyY+#KQ zukuLMTJczDW>t`u${`C>4e;zQhb5Lq$FDaw?Kei<#AS+hG2E@CXTXjQDFuW_t(X|! z1zJZ3j(`==Ug9Lg<+97C6|3Wp)n6Z(pUGl+{qhihMY51&s4n%63 z->NUMKpY{X6n)oPNsi+%;Yb6XR`mIuPj7U;iTo6 zlN6?@7fK7A&PltU+UM=KkF8JWlUI=wqe|PuFBCRLmGwdxg_7QYVuEk84AmHdO_a-i zBhUW_!sDo4>`Lqa+5pWHs469|4&s$z{PqJYs;9|>=nW6o-y?6B2mmq>s2FO-2@Xf_ zo%GKz1Sq0{%=a+3MBeW>AZ$9sN8kQ}k30LpLKTziC&S_IeNzR;KLxEL&_@IrYU#d4 zRFLh~!Ad>+b_>JZ1A)o?9RUQ6hdEfw@RSsx$5YGP*#wtzd61qjm=aY7-3FQyZGOKp zm~t77YB?FE(FN#ZvOaTYfEjg4tn{Pw&mR!395AK?tF6A4CvaZh!N29;Zq>MF`sd4I zt~)la-=J2!onp0K}zgI8*>{;bK=zMd%eQ0 zZ1OXTjVIK(bJt&>1!5&};~vR8`?>hxMU7jdtzXySYD z#F-m#;^TYdEZ4D-f=}+Af08pr(h;V00sUrV8aLjd!hzVa-9k&?^K%dl_8}Oe8%V`0 z7Uy}~2q_UPig>RV>#4=$PjQpIHe_mi6E6qYsYu0gPdHt)9ENSXLG=sZ&*O_f--du_ zw8zOTT3E-GD@7XT?JVDyV-~7*69p#A2-E{@l~}f^)kidchoL%7l^ii5XS(|(N)j?W zn5T)431}V9(8f$&n>UCbdesl+H4xf39{>E$M*28mx~k{@F2cXmll=exWrekct%-r7 zqlqb#sQZ^R+Q?bv>kknR89O5bXA3*q|4DEI>K1Nze=&T;lGz*i-KBvl!G>Tk!&;=! z+x`Hs75Ktvz~1&YefM(uGTOc(FPkIqn+aUmiUo`$Kns_c;V!@naTwnl`kd#gfJ&&K zE9b6-&1l3`lJ#J^4@!6A(dY7I{gv^y>eJf;q#N>0c0d6X_5s?1*$+M#Z}?nxnFAZq+C;bzf4MZ-6k{U%td#E80PZGoZw6))9Uj#o>mCts?*h|>eYio^3aCg+`RiRTmZD8xtJSJnY~z*5%0OE+ z88e-J@2GKLbS1X+>rkCi*TU%miIMkg^}27Fz6_l?&B`?(d`Rt1Zr4177<*kNEi*|7krgnJ{9>?xlkFaZ^EuNPY?5Z-@7=qo&fo7akkRW(fS0Mh1p>M<h8LtdtW`<9~W~U=Pi-PjWtgfDG-_x|Kc~VJci+qn=yxRdc}jq=epe zelGPXN0yPLVxP=IvnZx0JuUyCF!9dReK`bcK`Dfp zvtd)(&}?C4-pq+{;Y`50HE#k1SKGKa;{E(jQJd?TzgFr#&MHpF(2ill3l6i2jGdOyjlQ-N(y?eX&Z)_`vu}f zqq-DxaJN0etp-O-7Dz-LnP0&g5EB8n<~V5pE4#$7ru%*gb&gE_h^MgB_@p%MAC7dauXTDHC3>M*eXGmr64CP%3$0NAGR4a(_4^>ccuNTNC1g)* zD@ixzKEe83!RQyw^w+TIs_7_@-1>wkgj)2rY7BtRMnH#q>{e$V_03kYyjJA4XyYv& z_b^sBS$hM5-gL+n2PJlXICRUkf7dCna;g&vLV;zqc%T)8rvZ>WI z&qaZ4Bub`Hq_*Y)Qk)_EngtUnQ|lkK%*43xsqww^IN}|iH*aQf=gRvTyU7LpQ=oVX z)!<-9B^NEW-Y-};)1VgN;nqUf#cbg>{>722!A%WD$ptx_b!S-1O8>;Gjl-^2PQ28r zh0=tvW8C?S760GTT7#DO0}H8xvfPt|zku6{w+8X zZb>_i`V%#PqhH$O)SfU9r)*FNdm@7|pZ0CulwTML$-Stzs>9v&6YD=yInV`6LOY%XbQY~n6&s$gef>-?|APtD41&J^7j$jdd9Z)0vTT96+p z%*e3A+E@SqnTO6h3zv_sg*`3{oTsRvTN5Uh6-jt%uL`hOOR?BB;;^Wni*3AoWm{vy z_R<@YZj)6p(4t*A6u4~RKjb;|+4%YSbZqu*NI!@r=X)>3bt-zy7;LaTg_H&D-Pq-o zugGVde;KtHbA>)+UCvNf$BnPSIL*;u5_2WZ5r%`OgD$_+E(_7$O#~7K=;K1>4vJg` z*b-{aNIulWR)dptAI&CQ)M)fEHL}lccYPK7vQ1}%G3T4I%7iKhJ|=7DC0}+% z66;iq_Z@51-UeqgK|U?rdiplQ)bz3#GfMJdtcq~PFM80NZDO$2cFJnZ#S&Gttb75B zeGS>_rzE|VtlMN#HCv*M#0$aQ4Afi>%Nxe{uVoEdknAQJ8**@na zsO3ZGmYF0plpTIfnof9^)p?K^;Sm@p6EeaIM?R;^f)CRYJ%SP@&f93aGd~d?qs*|x zAnvAKL!HloYHg={5EVIycN^caj3!@y=xLtI0p160TI4iUO zaOpD1X|IVg;H;ojVdpE2rDeil%Q)o*GeNDWv3 z;rA-Ty`FybIt(5t&e73SmC^J`GMuW8Ehv5Jsx-ARj%~;pd_fD(O=h5$&hk#2C zcNzTrl5mz4hl?Z7(Zb`a*3jm^ij+o_3U+&7ASfniDnQIl|VUrv zc89anwvALtato1+gt&cTPcAe`6nsSq+ul*ikbtS6_bb^|!_vYt8}pfVEAipdQ-@G1|rxJ+;h&5h+FQQ7~W3_ zU}Pp!Up_M+34WPza|=OnckWC{Mp^tM=;EIq6#X~$!y2JW(Xz2wm3#aa`8-8?oXy@yJC@i3p4iXDtO6p%9np9B-ze~@qEHP^!3Z8^-$ihp zkPaR25vT;TbKPy>IrK-w9;hH6(nKQ$f%Pz#J|P?XTQ9^luoMF4zW&_Q=DQ#Xfsm3o zRAuUrV}D4VfA5PklQ&)pG0<2<6CY4%5~IiS8w0(M2Z!BmeSa*qZdm3XG66acxnu{W zzz=fG?dYxiRy&h^zWzwYYhFC7#(z%45sVMKV#SBgiY(k|HRp9~df!p-f2GMf96^9hbPb-V-%uUwAPtj6m z@8`oH?JrmZ^-&rvDMM14v$iNoj%|_n&`PeTi?qI?5K9h)wE44YbL^G7m>Be~i9s0b z# z`2)aSL$IeKB*+9(>=_6d%0I@1<%cH7MAPran*GlN>R7r}Q)^Eb%UP0~=>@tCo9LC- zw;DDi0UI)c{qdxGp*|~D68UDHOS{j;wN^XL6VNiS7|&aIHpv0vJ9mH}Kx=cGB{4ZK zpB`&>Res6Jq4mgQg^>F-x(aq=0jL7|Vz1=+&?cgs^y^ou6fm4<&KUkj$zguh48Wi& zalRB9njTIAWnig;WC|^+DF&O)S|Fl?z=VUZwB_UgnnnT%z!bn+$+a>{Wa^o-T@Z>H zyO{u>EVQL80}e@V zBl}uuGst%&b0Id?{*7sVnq}_~F5zmY73=*@n)3(hF7B9DI%Xz2b{LE)7ge-KNmZFi zeF>IJzt^9|WOJWUFNi%S_X6AAeNQfSyPA#A-2faxb4G1oh=Gh(F(^PQ+N}HfyZmGA zMFK7JLcEpGHObjo<_zUfsmyMnU)5%B!~I$1SnYseVEp|J)tKsnI5Dp8y+-=Xai;g7 z5jNZ}=+HIt(PLz)zNeWXO z9tlewwbz$2rnFzz8T(Jjh=|btG(kt%w<7zeBRr9BKzZ>;we_fG!+)l(A1Jza&00*J zq-Z)>H0V>hA=A&sG;Y=l98wN(%wCUaOK;*MrdJ>-h{roB<^DR)ETyXOrpL_{Eby6How_wqpx|L2PyS~IDmU0wf|YkSW}dCzUN;#Bnm1>0OmT-gFR5P z8wfc;HmbasU1O(4;FMhO(9o`jS%%>VZ*Qfzftw&cL+^ykjCKF&+%7&{D}7Qi<47yGI1s1-`s5E zvX57U`Jlo>_p6p`kyzeW#5jOJ{9G>0gej2 zV*QN7+$KICGh6R~oN9kIzY#U|Es6GbZ2I1tsY`N9q`0RTIAZ0)y+Y%dGv3i(Bhov+ ze_34P2jZ$u;XG}tianRXO{ENWmQVWcn12L9e#=t4_JV)iv|v54!aPd`Qka_z%dd&^ z-{G0d$j%IyW0Ap&;JkL{O=NrDDE)yb20i-f3)!r1W1^q3?$k-1xkd!d+ZnorXbr%5B=lVQQ%O_j4KT= zrmYur6ld9W>Q)>wmrInZ){kIT8Yq`GD>SE)-LjCd7o2%;mEbAUuiLcggarEqI(Rzc zC-pr##o3M>Y&H*`y%?43R%tRIF^=>c46VnUOrnb!h^Y&dau~5BpL^)ESSX2Ih5Z={ z_^2Z>7v$~QWBPLTbao*Ov+D~LN^a`531YPjLeg326hXR2{Ybr*D%7sy0;ICvjE}?0 zI}Mb>CH|_h7L23o%fsuoWY0jCwuQ=1__v^KaU0YbkLuaWgKEsEMjSKh$msAFS)_be z>vfCvf0y!FZOF`3+_HcP_ofCfY)cXxuh+fS@1 zl$Y$Ny=o@uXtS`wfczV??ZeS2dGPgJW4S#axHJBJsFU+t4FgeMvKUf3bmR;>Ew3)i7mwGJ^8}1Gc&0Ofd8P zDJ5cM@+^r~-#f+Xpg`F3BZPIRD#u7nqyDk?F4cP#6y?U+#)OXlQ( zaC=jw>#wlC`m&8v&-NPcNE7a_W!chV67(apNSx_ADCY=WM%cVZvUWON`qI1K#HMYT zB0vFJn67z~rmYBRmonD*dnm#HS;R|i0VVPnxrDAMph_><5HBp-`Hf`Qxucz2(GK7W zV4JL1PQ3Q{mQekv-}brr^3U)u<~hroV1FFHyj}`7BfCXb@~u{C!N0q1*!SGe^_{z$ zt{=KuuPhD#x%&@@V0XCDO?OHqp^_~{EvS-T&ozHoE593*g>M;m5=E3T<3`xxuDM{C zQ&^{E*e*wgePDw=twu^thPzMvuW*%7pH0{wB7Q?6TH~)ZqXRmQW+^Rq!G7Qyx(ZwH zuraGcJmLbMnQGON4;Pe>Pd(H-Qcx9cOGjgQz^9f{0>E5OlJ4Z_OSFJPUexn}Q?699 zh*SU0{8QmlFyXK-p_SEoUVZ0cjcMD3ctp=`{&|;+OEhn5R=LD6ZsEr{Ni*|tT>ybJ zWSzh)P57l>2VRLNCx8dTid~1k?m6KhC<|yVl#a z4JHx(!A}%8mKS>1R3&sDQWXu%MkW@Lh*LE6NrCxN^hZMnPrOI_6^RyJwsk`O-h7X^ zT|j3j=%&=EdhA^d?G+D9&4o~cZEWQgTJds-!ryazei^**s{av9Ic@cpuVXNQQ@x`P zzQ80FOfc;`2`sS5+#EL~ftLbc9SRy?#N)6ZzUWbqHZ%WUm;9l!uMP`8n5hKyKb*$@ zgTv}Oi2ifqEZ}TzWn}E|@3?9GQx^Jzn`=h+RepX22Aceqf`CHRPu*k$cuZYMazD4c zWW$hiigw3Z-ec~n?NzEzGP=JxXYn~6_sa@b7Nel^mzZfA0J;`xTnms*|K@WVQ^sE#{nnWH1p#jOTRAhR5Gc8h@@N}OR zQqMq_KiIw(*)#}HEHu~htX$LQkvt`?#p%rP=h0+E<2kZKk0vbk4yAEUaB)%hNM<1jDD^6UBezP! z;GOs4Y+@`?HVaMc^%9W<3M9t7GbI6EJPNtumL{@$=TXPkgSzK%NhRF@r;gZjp)luU z16i{fOu3Qm=MlY?Y<3Wsc*Wu+D#o-j#$Gi?P2#i-~?3~Xsp^l-q{k57)sM$JyxFX0h7cRCA?AvFcEUuwc!-D zu26|Su{|5{Lt=*?4sorbKU!kN_m(X`G3$M-n!1r@Pi|!3<%~@^ByBe9eIPHheZnmI zIqDKHQ|YWB@GwR?wi;*(H`iI@l2AfIUYGm@7(x?&mJeSHI-w>}jE8`sj1 zO?-{IuM}IK_w?lS}OW>pM-S|x#afjjYU27J*+s~m6Mjrx!)1FW~>1RGpH&~Ks{C=qR%8uiZ zlg_Kx16O_zShG)WbN7UGc)&iwY7HnGC!8|}BJ%spy+Xz#y>zT0L{*(%ds>;>F@dx8 z0Q609sC`U5D2M1Iw^C%+QY(3ZAmGnVvJcXGc(W(UI}kS1oYSL%4L>U9nkrJedT@u7 zcY62DRfW!q%u@$)_Q(_XHJJg7V3k*OX)#-Mxil6Mx^Rs&R;|4f2E(EKcxS{~hvMW9 z5xe32&jeGT2XEK+2Q2%32qyFY==J|^U@7io{FCi$`%jciR+>@7`Vr-rxa#Ypr|1S` z@Bs@00yTv9m*J0)0W?4WLWoI85e!}fgQli0Uh>5&*yeDl>nI%c&!5HzDg_VU>$yM2 zHhlb@qU)5SREK@gag}{}aqhi-`GLxr-d|Ds`jDx2{#2?8ZVo0xG3kd#2&;qCH7lF{svtAIIQ&bNKR~wS}v(M zb)BFxd#*T!Cp&Z}t2CNq9qYYr#@a$aO&twG_=RCKR0I`m79OZZQ4>S@4dsVS`(La~ zliAEYcwDeUOtQP0rHiO19;&b8E~qprF+bSP4pS+v48J82l=jtQkz>!M+SnDpV&Lef zhJrFpeW>p(T^tRE?|)Av15#h@tEe$>Yd+4R(YCR}oXAcNJ#<-dw)hGU&fbY4V4uz+ zCO)iO?nAsb^caJU)#$eL4@M?eODestTlT_65`xNJ&)c+ za<0kIaaR$8quz1rfavPvMFLwJW-Npyv)2^UxtkBOs8Sj;1EP=T53z^v38(Fp_-)Fcl2+1;dEzvJU@1(yGIRsC~Gyn+~1Uc zmvK!B#=2qJRRn6{8C12sPWZ$ReMP{6L4Br(n<{d{@1vS3A#WPt;KDvYD%ERtF~eV{ zg`2H|y$rEhT5#i6nXeIf>bpwW#<{c&WFxrAVdP*aRy;x$fUE3Xer)KeLNsxnfzIr0 zexoStQNrw5u@*f@$Fr_Y$3TCDzSNfla_eSmSLnr+UL<4&(F($#h0}dSU>{@3RytG zB&dn>fg2A0G$7$f?}$GXojHMGm;suX90D==ldono3>tA?yrH1!u$;DV`ip+_*YPQJ_i-5hU3# zz!%isr3^?AG9^e$g*lddXgHU6a7w*sL)};$^E;pHEyjc>aK?=GP1grKQW&?Y^&X22 z?Fr&h!AePGMi~d=7sMqjgvr?v$B2xqp(dy#A*Un-i{vt-L{VBk;QnBcE`%$&s4`?@ z;5H02y$kTjrSfo-c%M0uq=*SO++7!+Kk>474QD_SCuX9gMDabg(k+^X<&giT(b~Ns zIC!%nNdEIoC*{A4U|}>vQi81I-|wTI0s>ks?oN@N9!7+OnhvPSgr1(JWTs6)K2#I* zChkWJAh;$)z`RW@BrQx5xr0^MDZbgKG@)dWx=O*E_ zU%vM!$C5Bc;r1Aiq*_Mua%H7x%jNk@j&+49`QfCiv45~vddYCWWV!J|M5wq{7b610 zyQHdACqTDMrw-_GY~&B)HB^< ztk5xFM8Z?JTVh+JeMz3s_bW;AB+_Vq&^)+tt%G`L+q6`B2kLHhFTlfY=;_Jm*Cl%6cbO}kGk9H>r1+zcJ?bNm$@DW zo8(zPGd`PZob@gxyw!eK2p&H;h4@|>&ntF~0+ui^6ZOfQ<41gz*Q@JV0VBEBw3Ot= zvK|mLJKH1_dKA`>%KjOsKpmK?V+{gkwAAI&Kb22vowEYTMPJ$;lH!FYzJu|15^i=G zfom{R4#+o}OgJ1q7_}a8FTy8*#;b;p{b^a=l3NvwjCOC~1>V95mUvIj?ssv1r^g^@ zj4~5QRZwVZrNrWa<9g6`fJxKs*&a5Pp9%&Q>i%kouXuav%pTuTGj2ah7v`;YH>~QZ z%_&b5p6`MG!+n?wdQuykvpS+`Q)N^FgUke#xO&sX1N^mo?;h=g=ke}Xp(P*sX!OT2 ziBe?Fu%{e0GF>Ij6j9~FfiHPOr zQ#?X^&Q!1yIUFkQU$xLE{S{zzH|$|5eZzN=O|98?r$xmyRD!i4h8pj8?jlg@&!|=# z@9h(DYM_#sK#paT8xQAdFZ3gy>mrHMp`$JMjN-NPjm_(X^_^QSp26z@rN#)8{UYi$ z3C5LFCvwT(R*d~Xo{^iYqKMGF#K#LHTV*D`O!~Tl}>7|cSeaWY58Zg~L8p$x% z2ruEw;>W919WH^}mReeGH9e!rGaBx;c`az~-b$N%M_d4`-IBuH7)PFlQM3;@PjPj9 z^d62`+g!2|xp>oxLiZ^1u2D^4bZ7E^(F<*|@Nr9hP3?gpEOKbYcEw5kwO94u@4f$$ zgr6aI<*UEi&~JOCY$KIrDeVko#C2r}Pmz{+)2y;Y8jDEXz8@o)VIx6E6#_CAJw}%8 z@#ZSGiAAM`-7q}dH3$a%@;)DF2+pRTJGuTGT zmg*`lpW$=hrJBCD=9VfN5nZf#K?_C4s*+}FSe#2s_WZ^TklpZh8u*v8_O5sB;&1|! zYL@lwvz5#@bFL#m+YKxfy}Iy~pWi=*ZBC{*Ov^~mkef4)-@0ZJ@ewu1OeGmqsR+>;5yD$=+7?k zM2^f-u}uAb56QwURS2!};3LVucjgT!#5iU37_#H17PEG(WCT*dQtm*J>KKS6z(pG4 z)aJ7Lz$;G*3KvDmZ=&4qwjh%-g7w>CZfd5!^q3`;EpsY1lI7cEZYuN-q2+^3XY4Dg z-G=pMh`a{$aVyC4M@%>2Yl8L7?<;Y!PN=~h5}SB3FsSy!bvM-sYDjd<3ulB+Xv#?0 zvYiJhW6%-CUzMS$W{~!srQXh#_-|MyC-z(_&SC0#YRt)hd2Y*&zkSfE-l&=SLU^seV zHF{eaEMU}KoU@5!j~Kl`8~JXy#QztyE;G#uu`3V9BrQpUq_DxkHuJa&r{kD zwkh`6db^Y{+m(}a+(R!{JeetC&K76Xt2XbJSy21=PWt;so$NsbIM;*9D3ILtPBzX3 z_%4vB)%saG>9KO8sj~a2vYTl>$Z4YPE(R%V>?vnMzra?>Z-JL(uafy?qaN32Iq8htb=#C{hBR8a4}m9$$_v)%@KYiFR=@8 zg@$@!J`eLzbIk62anw34*e}UT3+ZkPo3xm;m2sAOYdqp-UwcnPetv%&soqo`Tmy; zNLs%7GvAL%Rr)92>A#6?{SN}7f68h9DIxkl3+kWpA=yPa_%GPbt%hbTp{^=7eh?$| z6bgqqkXrBnnml!8d8EV!&EL%h3{BppN;Ff(67xKTT2D$yc!aU|S~ zjwU_n583I@7ca58zo7Nyhx0)FyS!k`PyOOcoJ0oIp)1)+`G>=lM2eY8473Ju4wbV~ zFF@KAXJ-Q-%4I6r$nut~Mj5&>Q#vVTRjE2FM>@J~RJ~eTey`RXU{dQ;N*!CTxBwEE&n?+$>bbFwE=yczsJ56`xi5r%*_#Sd9(Rz_HDtao+ar%UU5d6Q z(VVS@%Alk=YxOJ3$Z`!hX$>aDT%;}22b%lOY1b3$UB+Q!v(i->!m>JV3doOg{RUnq z#ub@zsu`(y?HjYyY}t(WwPWPWWPel zvwqV*a*R%BLzovTv|u$uazpk+ZlN;FT$W_Co^z<^Pppf(zzTDiN^V?S%ccI3W(0aH ztE-&YV?8y#Ja^IBoQ@5&`BLL}T|qEQu$CeBXVU!sMGaQIm7lY_{tU6--v^p;4Cd%e zv^~EVBu8Jia2fuL zEa6e+z%9%tZ%_J4EUYHj9XR)MPoC(Q)kLA4XNve-%v#`Ro-5aI?aZiHU;WR?#ca{C zmYB~#4j9l)z7-!W*|=#FvO>BNK_T?=oQ8pt=0)Yo>10tt6YR=dPk72arjuA;LU@$t zj~doi22>5zE~0lAC#M~?^erk67sTj|AMH@+_zumqE@JOisWIBaH~ZXtNF8dN%|B7- zYJhwHDK35?>r!VuK`2WI*EzV&P-y0UGoalVrTt$LTyiZ=?+brqBLFbJesTVf21oz- zT>b}XU$?6Ek8%Xfr$xMxXe)sU(JVd|64H7eQ4YLCxULBqs2H~iS%acZy5(%ysP)pu z;tpyKiq;8j6iO61+g=2eri)KhV`Ta?d-~P%5jAXP&1zvjUgngogz=>7N8$J0wL^M_ z>-+ry<>&p%H*y9~^cZmzrS?}~HTVm!9Kaq6Iv>Gwn4t+^5I<{BZ1-<&?56DQO7E_Z zQ5GD2fDbyVKp12lnaD5$NlGODb~MzvGru-sLF%xB@U2q46gUt%zp=5|J{>tBDSpxl zjC8Y%g$e&a&UTc#*z|d$$1=-gC)q8mnG)4uq+mJ7_nQ`*l+ve91i;n z@u!dg9Z1Omf9Eo=nA4DJtmIiO{o5%*s#?b44MZ9ht(qs`XYt|FZ^;~>jQFE?>BOiNCN2H98VT2&f-ZB85{?q#TFX`JiJHZ`rgf{duF>eR!=$XIIct}Z3}<)} zZtwMnBjE@EL6f<0GwzPG>kwbe9`A+oDvG_?4ieS=NH_6*D7KWQq<}3q9sW$V z^E8!P(0fFMp_7FIW6TE}nL-;@8MDNaV&-*54UybM_c6aGfqJ>$3G#A!eMxn#1GB_L zxeaWPCU(mH#J@44%EN*J)cL!~%hrw3Q6@awYIeP)lx?iZy$M8FSE~rF z#=0yH6F!j^G(vpdGlqn0C(WlJTKl!_Aqtauz%I~=^U5MD%m=0Hs>)rPWwEESg0yA* zh|^_V)>}wGsq;1lRpPl@+Rd#%CgTjxM5evV56PFV7mg+DW9)I9GWDNjA#+#Xqt+?& zSUc`Ynma%1Yx647KM^fb>BFefz+TBrCm2)9KH?7r5n3eW48A0S%Lu>D(~YUl6sAPd zz^tbYN4vUciams;P?>uc{7alVG|gUNSW$`}zIFm)^4H<{YtBlX;eoN7^~Wl% z&u}*Y@2q5)rHA@5fBm*t>U_OR@IvR^--EK?g>8FnupASm!G)MnfU^Zw2veyz2nPfI zkZY-*dvfQDca`%jwKR1U#{dflPDT9vCKSm!I(V6v6PIuRVGeNLumG@2XK{T+-Ajn(iiGtI;UBs>yE5(s+$-XI@#$6&EVBLypxL<@=(Jj6rAW5LrEJ;=Q~rqYlXt zX{+^^;hE=%n&_KnW?t5PafNqzABmM!A}Rv9HlMbq9m1SE!XRLdIUIA)asAovdxKc- z@Vk(_JQm)*6h!6kUobHIBi+r_BU`!Fl_($9<#nqqr36pRQ@WcY*C{*j=HbDH@qgFd zFWaKcsIn?zgcbb%uCuiF94EzXyT8ntMw($oD~|#`-2>(zJ*Pf6hED-Q)8+7+oKrn8 z4Uuqeq^+Zch9_~ z?er_N_Oh-i1Xl5LY8Hj8huQ@xQ8&NDdnP?QOzzVwAgp(dOE}NNnKWv}dmcq>K2YqN zree8j%8az07T7R5_vjnPHvHD#9Dvv!k2+9fc^7B7#~$wy(Z2hZT95u-{FkM{cia2# z$)BYG!++lv_g`?b|0o)qQr-HuIAHA_7WLl}1aeKf^-G96R#xVBfh`i4X7YG=VIUPQ zjqMPUtey6a{Ajw5%?G%Y6{{+yp~9V*d;{AepM3zZxon>pr*z+VV#rrx#((V`g%vbJ07?5U*e?N^x?8*2Vd$r;xa>7S!! z#NBYZuZQN231{4_cO^mNsJSS)-ZVmsST1E~8klw2kgyLyYBaL%iIaBlRF}qRLdt1o zT2hm@*8X^A-N=IBo}tr$**UO&aFQ~M{6uFQZgrRFLmG~2>2D4N15R;u2wKBH;X>}Y zTCF&yQtLRQt5S8&pA72cpkty}S~%NCF)npdbOw%g(XKOS+pI$fd5>m{WaX>gkSKlF z48L#}WoCwii)3{A+mFx&9gAT)d#IO!k}bt$Z8{QZ>X%9GOWuwzQ)F4(F#ld5Vp&v3 zEV`u73R1zCyJEDy$1?chT**Qk#M}j81N^J2uXQ76n zuG0Y7GE(Y12M(q)ze8hPp;-~2*p|1e+*Pv6MI-lKlgysqa=6-&u8&skY*(Yl=J9s+ zFrYRmHzj2?LgyyJOmZcKKOt>up?ScbKW%3+GKrEUHc}fa))MDRAdYkAg`(7}bo)z) z145b~QcFOC9~2YO2_(~vtX~dZP^BMD3L+)I{t(Ro22{RZ70=V14H9xy;g(rO5dzCN zg6=U%OO~kH<0^7Gg{2$5eL;HB{tT9pYSAgk&nXpF^I^gq>cM6lP+QXcJ(N4kMbCA#DUsdRfU_Y-^qU50*2EuNysAIU~n+ z;hjr$J)(5c=S%8M%$WtF$U&|2@onzoI;f`r!PQfcZRXy^-LY9K>y<=sug9i|oTb_O zV3U8UH%87C2J^27724>BaT1O_o|3hQm9uvfKOT?MBZ*0R4d=PP$TEpsg1yJ)y}@6u za5w%iv0TDX)Ed6|*}t=h(M2%FcoIJ0aiz3_Tw+}Qa#IWa0o+XHLsc=8`@0%xR*Yh2 z^UB)P#kXOY^{8?cJNE>QU8!#l=g%uS(jg8YRGqpwGoMRW;}R7969-4G zB8Wx;;{k;Cfb*)&K|MVI=xOAHtDRi-U|i;apgf|(qeAFbfOwHf0nANR-*JqDHOi?B{;U+hQz3Ax-5Q6sUAeP@3w!tEE%hp= zs+GdG`_m5W39d@;sixxOZ!o7#yI{J_$Zw2Wt91VTsPKu|re193>09&5%|mR?#}CfC z%+pDPh)8O9{RJz(T^_E%w@GNcQ+_6V9O`kmwt*=ma#bIflg~Wsalvguxb2hnKQC6l zr1RR!eqtune|(`V|203hu{AQcF{KsIcQiLtG&Z$1wsHDNm-zYgzxNQyikAP#80BuH z?yRGMLpU!~5k_!@YEawJ3zIVtAWBffL+EXD8s#Xuv~ZP->K(!D1Cs`h#(f3&B0I>w zlCy9xGHPUObiL+&$zp8u{rcJlqYF1j+N$%^>x&oM9%3Vr(%51)Uty**-p%pCNKJE&ObUX|eG2{yAkH|h$z1Z3` zfgtT3_P8NhD{A)imT`cNz}Y(-aRQwwdyx$SN~QkLXV4PI;IBRMqlu$cC<6oOp)$o( zaIdfQeyZRnjr3ml`PoR(>Wrj$>c_nv5Tp|-wNvKXz5zk5hPYv6pjc|Ie#MaTMjW6! zOV$+R+?aV#K87=*5e~;N8;WnGmx?(EQ;Cr1obAs(A#SC}gsr>a8@d_wA@I+JExl8J z5x904-&T_7QuUGdS=obM-rf0b5yxL2vUMM?G)*5vGzvS30S`>D1kwjs(GS3?Me`sL z0IV3sZGQN8>SHWa>SH1p)uHk7)>;`yI;vk_3OgCQ9*AD)zciwOLBv!cQkCwU3IZC4 zdw*L*pRb5Jm&DH%6_Wn=NG93}>`IU!vc=5aIjz|!si_`GpgM9ZJVfg0jGS9$X$+c! zL*|O~@j@-dH!coWRb`aQst5Z`|mb=JJ`A6;pdH3Y;m6n{WTpCac<3 zYi|9+52k+BaIF8=|GtR6qm#_P9Rn48D|4g&BP+;O(Nx6xN#yoWcS#wGi>6cuEU=0S z_w=uVhoGm*v*M$NGQ-1bFKg_aVlYZ8F)=}yptwny;ThT46)Z~_PN499=*Km7_b_Rw z(gM6I_WbM6^N@X$b)D1u{q_m>OI5$@R9L6KECT-C+6~080X=nUqrqLY*n}V=AVOwS zfv$WX`d(gY>`NQvMxP_%^Z%uoiFSMD}HtG!T9M;P_KK|O9to4vbC zPrP3g8h=3#jVfi>!)9TXi6Vy1vId`uwDp*SeX9y`!%lU@QFw5FA!WikG*w$amaYu8 zcDl1gUl}_@$XMRR znzz#?Afbw9$^a6t3CmEPtAj@5w3k~6UmuTvzHexyG>53v4bgss6KNRQnqg~Az_>o} z4+zEJ86ho_Qy|5J$u;oH|5hhghsPX1UYa3o9rqHfF?Rz3!f;9|LNtaMO!$cY*1SLr zy3b_yE@85(GAkpf)<~Vm`+{}gBK)Cc4XwOSar5(SCU;@d|0#=+F9NG zJ-nF8;e`g93&$I4?V@|&M>0}PPrCtKdR;};9pX4g$P4*A+S*k8>)COt+S$(5V)|Pj~IjR2~4Nn z_%H2!XLYQg>wnCd9w-TVnRb{&J#(qQ1eLeRWNXU4*p`>lx5$_|1_VOC7VA$M`)-S}cZ2TROGfL`p+ z!R)6Dzez#>30%X$p@<#}-q6Fjhf*1xyG|L;SBKwtUx=d;0dB)I62BOi#W^a;mrLh^ z51p~w$bbWyCA)gzw@FmP1N~T#*}?{h^E8n3Jb~=m0A3aGw##z&xt(Z`oZL`R$*w$4 z3Eny))pI{38&uCv??P#LOSV+vk#B#yui z?+g=*3IWP-DwZ(aCL(W2LRs(s<@Ua?1L*$yb9;yUL?a6Qj}YwNFK=Tfaho4;l%uiX zKQbLe%zuom|5Qm8EB%zc$RTsvLU%ZWkk}v%;VKHX(wE`o(eW9XK#a*9fJ3~hwlWt= zu4J?j`jCCY`_>5|^7`S9yXk6<;92Dvjcs(+zT`M=x8HQ{`1*VS(gk*fku?y6&{OM) z2;ny9LmDB-(o^cifXxVjr=3tjd8r-lIRelMK@(8wiT5!C8dWwYoLezxOAqS*L7_Ae ziRH8zXLBmV+6sG^5}^DNm_Z!CDxn2X>0qufYr|Sdo%+ ze8zN;XIyMd2BOT!XSaklEH}_*moutZIO;0DNa6=?e}omJvsF*|ljpC`UeInMhQx9QzNv$ z*QSeFUW}xZj?`yP?nxOga6w$>o?I^!>J+p~tLVDYY9G#)=~Br?`nsY?TKRQkK}d9K zT=(ZGG!NxxGyHz77Cp0zrEjbpQ-d*9(sppqbI~C zGXOcN`S)f@qAeUPwS__-hL*UB5PuJa1?@L&H_OeG!-C7SHcm+M1-4yC*@nD!{$cA8 zhj-^WENw%?b!jyR=63X+xYNgPh3_wt+m_L*XKs$cS?pR#FfXpXLSq%BOk|iFN|#cU zYyOQvAP<(p$3DKiStrkfm_#RRG?fm1g ziqB}m5VV-B^ssG>oNbb706sk4ldejmjRxt4k5?`5AO2sHu8*?V`nz0z*$CQX#}lwm z>sdS|MNN|uALoo&L%`mOVOV!*OtjF(ByaW6((h(zYmmrGrEvsTKO^Lz_NZeLxe36y z!Vxkgb2r)uo4oZ>jHcr!-37Gl*b{JMG%XO3Y8JuKpTQHArrdGgyKu=|rY@zA-5a9d zx2DNdIK%d>3)*CdOl7qS;1$H)g^+3j`3}(njXq~tgJ8AcWVka*=noWRK0*F7GoDt{ zMs)gN2i-rCL!STZ+4{HBWNz#5U!AI9m93xJ4*2hCZ`^uEX@Sh4HkhVag-gboM81rK zB68~tKYVKqEdhx5zIxOXg%g^NP9Xpkh`YYfq0qSX074=@JU2WsWae@{z<5-g*FyNl z%*Y3P%?S5BV;2*b6V}GY-mT6b!f*xmtCS8!7;{==HbNhN8saVr{-ltI2AqCADiST) z0D1<}oJcmYDzadi75!rj zYS!CiLbDMHcWKwDX3UlGu@`M3Z7SBvz3B<#LCREP%Zzp`s@EvCky&eyIlIl0v_5pW zhZ?cW7s9B0WJ>l6J3bTAfQm5mnCc5|IgEPC5gg;=lC)~?;~`NX!#*;*U}aio7aQ;3 zmgQSIgF9>tXRwz$!;QuOY*qxXrF<~ZlfxRUvdXxTC6m!)L!?#{<*|M5Gd)>2xxB)K z)%M2LxE+BwDKe#f?{9{{bg}DRW92kFug<%2A;M}~UAt_+7nfuSyxJx1usnADo~)Y$ z<}_=o=F8Y4>z3wB9JCqXf2gxJ&uCz1)|i3Y$T)6m4nTLIVBB!!!J54f`t zl`r@7uF`&($ihW3m^J7Mydx}ddb-yVQ# zjhsn-V!k#si8V#@{X;aw+;`Fy1G-GO3z-VVXMwQogOEq8*KA4K?uh&cVyUWha z#&@%fQ)JG^x~#}Wjw;{gmlaTL*uJ{0y*r6ImjG@73mw-l5d40TzaGKBFh^oTNrd)^ zBP1HGXrf{+@yw!M@X>IYFa49Z7y=}kf@!WR};w&#Ib5p1}q;Nz_d& zScG;E2>1viU2lEtIm=;HsjK_AP4**C|~FMo=Y&F^9jZ>5jujsP*RjjAi@ze6_XPK^A*@t(*_V_gCPXbo9n5g-IN#c#=)Vw8!(B{UwY0WbhDR;q^-qy=FME{cv#~pY=&1oj$YpK?#ydnuY-w*v$o%|> z5P-qy_32WMM8m&z@zc+kga=w>kHU!IA}rEZ4GEx}6zI<~F>vVV0N!N6Qx!OtYO*lr z=!(xZ^M);uP2{&X%nK$ixdGWI8_Mk;Bm6#WoE8kR%M#n>k=i)ns3}Y7QW!EfB2iZ2 zVj!FZ(XtM37wDGeKwIKrDlzR&zq=65OxxUU?^QeP$M}A4to(*HttYlx2SO*wD~)(E zrDWN*&h}jUT!VJ?{1GElHwTS8nO7j4712*wvT7hRQBy*)n|FRuBjz>p@w>y2vstaW zjGcas2HQV(AyHGqwm`OUI8Yk*n!9S6PrKLcWK2S1f=Q$Q?I31cF5gN{*nrpsu@t1~p2 zF-#WvbV-(34R@EZBvwGbH)~Souo|&A)eRWT{_x~bKyPA&j?`Z|-_)s)Voj8uXsxp! zy+Zd$(3);ZHSczQryH=u#ZI5j9P#GyQ17eyRqN=0;A|3XTBagPA{n;+1uW}3fRCq{ z;|VPu@9{;LuLmS>3m*PT8zSm%b)4f}GJC-|i~C-r zbK2MLIoh=M5>*JX|)I??mKqgwY zVNCW9zRc2u%ya0haOG%xNBINCJmup5V(lBdEbX=>D{b30Ds9`gZQHhO+qO|@+qO}u zN_HM~_j$X=J-5f`5Bmo^W6w2Lte6oo%bW0iRklYYSw`XO>cm9^P#eOYU!8_?f|sAL zjaBKB{OqnCJdrqWZ-MnqBnn+>iRmMfe^8!}^U&??<&o26j#X~|tjg&R0bj7vI+=Su%rA<59fgu5nn1s)NClBHk z`3p-Z%`F=3m14{(`)10Ncnx}taiYXaCd1pGF>MgM=NE|AP|A)`X77;b3oPD&r-tp_ z&37!VC|95LdwA|$TxT2Op3KI5weD!E55dlBu-66J=CZBHq9Acstox>2*^Mbn?k&J- zzzHCvE_^Rby%ebbdU)_+H^YI?~=1K*mY;crdSf1!Q& zzjXY6ya)fS?@K#=!&h&j>ZXLQ`e?&&aA4ry#j&FaAmRKBq%nbkd~Bd3G9>;HEAxc4 zlFJ<~E3|C({z$Ag?L%@XwAf-O_LOqM_Q#^PiV0on)>i65dM5(q6|J47C7mbT>9`+{ zyRE!Gqzu?Y)w4Z&TaTSM$HO%2k9Hx#;vgw-P=kQoKt&>AVXBlpm!E7BD0 zWrn)=@~k0~EMP1P>#S`urAI7m+e?=0AunnaXE$gjShkv@1*R_1mtrheW9IB=Itk%f zi_JBjax`g?(5tI2Haz_0*R_t?vM_V#IZHUH>^Qt1q|%K6jJGW?0f`)}>@5wK>VBS8 zT6+vzs0iBrylA1Y-YIXhB!x=HO{z3xo|37Y)qD3YQGRR#N|y3Rob)5M99cDW(rx5Hojg zfNbnnjtlm|y}F93&qoGLqh)^+J9?a7nrcEF1H%0r(uOdBU%4Y6Ca)61hF%CUt^u?eYB( zx2+dY&(GpdvYlZwxx?lTzc%2CMcy@Mz}P_3>5KG<2rvc8zpVXcz|j~k*=IcTo34R* zqxvM-*0iZ>=CKNG$x?fx)hKH=102X)@$_9X71$3g$0f?Kr#(HiD3X^(Djr1BSmK~b z2E~~%)`BUgw1sR8$}5F!4G7#wF=Jh(R^i0P1d35&uMJLlnts%HA))YczjHp5#f96(N=x21t|1=1sH>q_{-qT)bfC=1c!!?%ml3?HPfz$=~z8 z17ZDMGb*+ZTz|a$JiFCT<+c=P5qf}x`^4#%-IG_^&7V7vwey{t*J^lH( zxcMQXPuUi5#`3VQ1AIdP{)%@4m8g6Rz=Pb45lo8KZw2fKxlWHW)Ma)9)d)XSV3vwZ z{z4vL41#ut9}o&@kcMg^oE96bZhf#>2)<1CLL4xHSkOKe8rvwj0EJjo6~u_K;h;6; zkjmUx0wzvROdv-giJn8TdbF5QZ;XtyQPmELqnKHfNO^{xTYC;NxTiKvdw$Hek`@x= zRLPv7-cT9RDK9gHo|4n7)qQ>nvQp!4#2yJhB;4I}ssdAaD0Z5$w%F2yypmZSWu5Ur zjHpdyY|S~*j5y#5Jz+;iT!dJ<$KFQGKV3Ir4U)K~!CtGH+SD}`AA`}Emp-Af1iur_ zzqUmuW?r;JODsE2(J$fnxKCXlTqYyx>bTw_Uz7YnQSWCKUX?d1Kfk3e>r8WVS$Qo3 z-7;V8@LOVPX$aHfi8$wmJWm-xnoMyC1Pda4 zto*&fN2v=2^>|d=xZOzAYJt{!8=5F`8!D$`Sq7FvK(?q!p4C}ibu}YJK>O(%+Vbn|Iah zOCucQD`w%PZ1U3`-)9yP>F*l(4G-uEAa6V_=m;Xx-uB^kQ@f3sB}drQZ-!zOdy{h> z1dfEf2SrCF#&PSjoUea^!)fie;c*+xY{WF!G`)N3FZ{rZ`V<7qm0$5S5Lf zA;A5-v+e+d@CMHZQ=Oaf$@(ek$M`MN>*ARSS-BT-qmj*U(>~=4Bff&~W%=_CvZ^C_ zEg&J{3NLm|(AEIu4wip73p+4o3J$Y`@MmZcy`(#e?n-;KEs&)vP(~_Y2>E$hFNjf_ zhcI^?dG`^RsK=!(1}oZ|?3Ac$U`*%Q-LKlM@VAls7raVaILea}I5nyZ5Y>^lB1XB0 zuZ=)(cfT;4$RiFWT>=~C17hS?0W^WB^r!R~ME~5bke-wbWYwN_6E}p!VEiBP>6Z z>E0pl7vF%52Pi~$*n`V(DSwsw{fUWsg!Y?LqS<-Rwl+ekZc83_WGdScO5#U#;i;qFRoVxW4y(T1HgOG12Yd+`oy=OP`hgmju zsNA`YZLK#ADFMGu4Y{S=tr)jyZ$AGb+Lm&*toHb(&l$gm-@^Z~3rY+AU90`~CP@5G z9$nVO*uhHQj{om0`afpj#Y)ih2S64O3e#xn0rVnXh>9T@Bm%-Qb6KQZ z0f#g(8kw5m%j3PjKzza@&7B6`t>GDWH7XSZK`vP_INofrpKP{2U;BLB9Z~sVY)%zm zA#vN;PtmS4w)uBOj_JIRx23tz56%{Y40@3$MCVat6qG1?Z-A3H*O@!vD@%_)=8dml zX=OApaV>K(P|`9OO@_=OH9{+nPqR1DM9ly*=}fwB@3#=HqY{*EJA$Ke`&mjLi+QbdG>lQbJx<9aK~6MUh&&poeZ5RmtG03Dty(S2nyI zSGaFef@?l@vd9m-B#SX9`VNKR?ZaP36;w;XUk49{PDxS{5!Y$Zsegxq*F-KwnEEjO zv0hm2z8+Lhz(hsH^}AQVh)rW5@+^_L!ouJ_0wSVrtztcfsE6DN>(%$?)}+ZQGUi9wbg!d)wI_s!eCx;O!}I=E!Z{!adFar~5|anv8mx%Ee|9GR$jC zzWS7`idArchnRV7mG%53+Qvf5hent=l?uYjE4~>&^i0f~#Fv%XMmpDP6OF&`y~-}- z$708s?20fP44^uY3uT`jq}YCxNO9~k;~_W6PQLf-7w|FK)9g_rH`^yN%}wIM2ZUh6 z^2x7O$yGGA$W&yY;ivk;T*)>tO^M}(b(IS)+NP#$0_k>u_5snN9s=*5+*=F`pI~f* zXLEa#a_5W;=GZAd!cMhgK7saK(nWYTia25?bW!m_9HEiuBu&k<@{{6K6UdSX6W}#+ z>*o8Xr3%pmf@C8|&DbGkGl-#)6dga_ApRZwQ#Rn=pMUc`qTib==>DII`=52JYOac; zita z|Coi#`&{}Uy2#_o;4d`b;^E-1;_AKq-19uW^Y!@$&9A!+Y1omC>Oc#fC#!l0HY;@w zOQepi9X}(o;0$T1n2xrPHvj-y^b|! zBw7$PC;j;;yDas>5-XSt>M`)oicNBxkSdF zuHy_cHJBMa#NfY4n4KhUM>5`sffJGh^v$87FcC{4R+}7rnNOMGm=6FI z&>JNEtSaOb0KRu*9e{`;aTP@kS0F)#5oi1V+>oIMt}uk0Id={e7&q)gvDq;C>8^2M z=3-BnZ*8925I`vpfRZd$C{-RFV50q8w3t3*mco>DT=egH)iqhqm7GUxVr75BW#M4NMk}ay$Sc;-8#?`2+a+kZS zb{EKNbfFV+2RVyhqMu&hVP$+~dfMVhq+hg+VDn3Dm<)Q}!m#>#`8DZ*BZ%&9Nr2{y zLci*Z#xN^n56ZT8=#BAv-Y$!e(k+sY$}N-+3hp~8L65@$#-}m7LE_4EgPF%eWc&V{ zX_p5pTxHOz!HJ8$y?K?$XhxyuNcv-AfmRaE?~ve1#6yIoX*0zbO>rl($-> znP?*Z;sqOI6mj)=lHvW;q+EI%5DTpsYWkQWsyIbIu6;Pu=Q zKXi^Y?|+C)XztfY^9n#`b8NyD}-zy`lL{JTN^o`frGb64<6WDV~pg0 zaINE+p2LKQmFEp?cFlbkNlG$b@?He3p#uu)QMW>n^7nMjY(0VMJwm!>$c}11p)ZLBXW+Rk`n5s;wL!6Se>&Y zs(G?WN;xo*0kCH>#nb{hTL4f%y1RZH%BKe1`eqj%GvnF41T%t8NLLrUqtr=1ZIqiQ zj9PD}1^2v=umyco`9Rty+f;NBC|1(buJ=sOJ>SGV(382y^T|+?f{ROqKiLhM^K?S+ z@fTH8#@AI&@HZ%G{RTyv|J{4zf5{4@ot>QZt^Viuz}DfvfJ)8(Csd-Ksq@nfS1x3t zXQ8;48)BVm$nJ}~f?ms3c?Tdp55$A}H>`vR-mGGfF}dx@d6M;=U3DFstJ@2ZHq^}2 zK|cqCg*NFxA1!WVE%itk+7*p~)d;N}uct=-R&mTCB&bA$ve+!g;K1s1D4~qvFjalq zjG5X&%nV0#ZHa=4uHd09gyaYvX+0}Blu&hFO)4IIQQT4E*{{=*GtOAP(vHeQzXPvu zstGOI7+QM$ZlOp89bQo35Yp2S_wLi(GCl~lZNZo9-*DfpHv|?ee*r}q&f7;d?O3XX z)yZYh(hW9dzAOe03BW5L!My&i(nnPqxVEc?-`#VdAgB6yF zO-2gb-SuZ00sjTmzNgG;DqD=I640aj3Ng-eHb#)P({J;pJF8Sy06T0-T5yx!*{ePJ*|SxRN4J>A_iyBGKNXBq#T0G zR58;EH2l;=Y@#DFh@!me%pu5E*s)6t=mn-3Y*tbWAgg~-)If^tf3Sa7zQA|Pu*m<1 z>i-v(()=5i+CoAEO#nc?4bTW)wg69cv+Ty#2{SNkR|_Op`zgpUFiRK6h}5O&*{f^a zsiONjE{W$vldQ0Ee(tD>_saQ7B=MXsB@hR!|HolEi}A#3=kuv`#?|EW_2%0^&~#9W zuQN=6VGFW1ST4b|*AyiL?|-Ss$7CwRNS@L7Z5opUA7uNP(Q2#(?(k{=zSM^N%eagn zN>5CLA&jF)s4&-ZzSs~Il zrjj*R(a7e?aQYjj`UNA{Xkh+#Ol3E~G`X5qfrVXlj-gt*Xf`~zFo(P4!a)q|L{zMu zIy;}pL{^~qnv~Np*s*Sx(7AJV9D)xLpA~E6_!-l4vUjPK7AxX21 z_Nl|$Z!=di&z3?kUB#w5Y>`|%{UX^=qdx9jm86H13_grDl7nX zu8EM4s8A1b#Yjul4u}58 zdlh`+s*+vDCaP1E<4zDHQ=JEVTN;&DWH0}pAS=2QFPJ;zFQ_|XFFO6+{UdNM@&oZ` z&eZx}_vADktv#y{CgpS8L^FatdB`Y90qaAJTpWfKTcz5rn++V30_JUjuUYX;5<(wD z2TnprOI2p{3r%5)&cBvs5p8&M&!eI&vT}8&t~^yKgKDC3WZKdE?JjZhwr?A>X=C!m z(ra|s2H&EFSaS%`_S(p82wr(?q|D776UbP!Iubg!9jVOQ+}BWYo#XP&QL|l>s(PTN z&>*7KfX13iG}amd*%2YQSfkE}pkGK;=C?Bj))*xxpE;tf{{BD^L3Do6 zY=Rij36+guhNHqAp*gJxHwf6q4Hjbsebj^;=_6j>R$m0mu&`MB=Gd8$-$`317WF-h z0{AF^N-EHVu8nFVZYg;V+~q}Gm^sSRAi(zVr76u4dYx4< z8bEMZY<^Qo0>6Rw=MvG7rR6)A82mKlNNWafMm^EO_?{EQw9rpF&)yep-iehLY_Xba z$IWN1Y}V!+cSu^+SjQhzYELPfFrPHcpAN2_wfp8QZAe=jp+34~mR-x)x|XMUf_Q8s zt!!L(ZFl?@zns3P9$d(T^R5Z?L4}Xw4fn~og)I8(9(x;bINyfvO9|mdjD;Dt=*0_yKq#IK4h;vPASj$KIy7C3xl$eJktSnM~!diuDak zfm1a3>YJyfWpT7qm!vA1K-OBz^q)K`Qfe8MG0PzgG*%W`$7U8`g>f0g7T&U94=sWo z5{TbCRFr3M(<}3;#8qF8JU24VxViozo>CRb$bil&4|DD>1H`IK0`W|dje#>6} zvuWjTgq@19gOjn_zsCV)EARZx)b}Za*dV@&2uV=f45E=I&C;i0RhbmPOSfbOtxUN++uK`t)+}44r1A`4c=-DPL<wNwb1ThEinmi5WX}5M+IVAi7avZJgs!K>9~haSIO^nHeOj_jWQil6_H|SuR@Wyb zMa+E9aSP`CE{uWx67D5?IJe9VhfWaY(q17!jAP=CmLuj2qjn{1=I<^$K~XKh+OOGjPEzP@vP z#jD7ILVszPSSPRWTH;BXfvexkiF6!lOg3v5hN5a*D^hluF3NH+c>OrnlV5J27@SYq z@2#zYvi#3)d1g_JmyU(jf4DRbzA!>>9iC%M`biW$<&X_-jXRc0z6?7!XXr4LwNNI?S12K8#D04a1oi}!DML8t*e>{( zUYM0$(wJ5R#Inhcl4c*_ju#@`O<-cRu(Q;`LX@54kXw}98|#Q^hNrQYkIXB!U~WeX zzv~zH^A(xVz-P9T88ij#G0#S=LB=wj=tU{^)X0!#1Wq+R#`ck~;Agmf)SRJ~mw!F4 z!FZFF;v=#&PYJzX<;K^Ke?P}B!o4ME-)Fev+o1d3JI6-@0wTon?cesc1Z)c3?Jao&i{FZI*9bIvc_aleGZl z0B{1&PBJ^VXQyoQf~0kaM?Uj5A$q&?LKoE+QpakQPnmdj6GFu`la zuEloKJf>*eE9$?kTQ508hFtlrOd->Uvom@OEV!-sIT!HwjhtB zcM60G%w9`EuGbik*&BD$g19@JhBZ)|t`my;B7a*-=IG{21eiAV%NKA2Ibi3Id@hPj zxr~z9hYetIm|it7x3)AGDaT|=#*nOU8ZW5RQMFVJPDPm}AG0<+u&5N|7aF)*#(as@ z8?ldzYimBDX2@9yc<~EP7Ea&E+Pm&X^qm06pU+#ta9aAAdQ}>{+64D1z^xl^xjLcHrS?^8wV7XQJAMj zF1wF0{syr;*zsyP8f|iuQ5sTtZj{q#C2ZjkIX0y(dQsaGL34%~L45*E)7o1F>Qq&-G0~6u z-7hXPlyzEwuNz{XVYK>f)teu!Q;eU>ZIYnA1LG^Mhmy9}PD2tjAZ$@cbzO??tQWeS zQ_%*@7cvbM8X!`ZW~VdKb)Bu8{Tl`!SUac0kGD&b7vBYt+mX4AIdl>w!?-zcu+e#` zw|b6xB>FpTr%VgWOE>N2sq-^gN_VFm6&2Mr^$#jHHc@Roh-GReJuUwjAmw*vWHoOM zgcFU(DiM>dI(n&Et%?;6R^P9Sos@%WgSy^^cZAj+aKej9MI_lCr!AIas#jp!4#Ku1 zAmohOI|sed$7HkS(bN#JWKQUca(#i3#GSMT%JuOVsqb95p%-dBf)cKdfU&=biUPbr zkoOr9+=AqY+ihmP!9&2v#`uI4g-EUriAxObj6wfGx^<;kpzKQ>^AoW>Vl;XlVcn6RQv4DyI zjhp;{$j;oBDxXRKWC{Qx$P1z1LWh(M;TMM(!SwKVY9|FP50jCIhcC9ef)Q(4NmR2I z+mD2H#_!@qmXU8eD6DjzH?=HMjAyF#lk=ct;zv94zw9FU15iV0;a~uz$#Xgd{zen#uPBvCJdeM95DIF9fpmSB=7=Uhnsj!!T6d3=+uR&Ma1h>!jHN1E^ku}sl!OAy! zU_4&T_O!*fW1y3Cqapfb#ITZ!U1ji08l3l~Wzy=N3)@w&C<|5)Ki?x}u3OafQ;D*% zmOLZ-5;xLGOaXx%1JOOe^iU~7rp#dXSIKeTT(7B$PPM%g$2?}9xdp1((qT5@zKXks znhdlI9EnVLqw>5~KP`qQ;z8n=m#@MQ+frg$3gi@=GCf=RrL0ZKpX^_tHIB}KT%QBS zAF3=xy^Zx>fsdc`omadlts{S2SqC4g8e{>R`W*-j*by3{>O*--y$Ty0d1}2FhnFbQ z##trRV6ENwK%}){1tSGKaRyiJ(qAdxRi(F_M4T0sN~`|*bg<|gC#e2@IvjrgDE=d_ z_4gU~Kd39~=x*2mI^^IPVPIjfgQ7+#>IVR9>8vPN;_G*&q9O%BP2A~zu-p77nnu_5 zcCwv6*C%ftK%?~$^-=Ng%gr8K)qAQ-cG9b*sWsS=Ced-1MMa0r2o=00t^+Mc!-mrO zRMwz#tu=L$jbAVV3gn!N@6^Pup|-H?q7Y!uNzn$#(JJ#ET8-0Y%az0GYe9%CiK;2I z?aJ8;BESMac=^TB2!JCe}?B z>G@^pjAa`|r1>CHf-|?0kdhqxpNt>#={`2Te4GnDXR_qp({qGDe!nutq(b(#p1VA! zJ$<*iUoXCVe4+Yyy!Hjr#oA>z1o1#hWt%I+^0ir58U~6;17ifJjWta zyVIWb6X3l?<8C1TJa@SXm%a3$)q31!fYnmj6~TbucoQ8PZGapE#E)m51sJELdu;?I z+e(&TsMh5PNiZi$ZsNwpSP`8T4+k_AQ>#fN+>>F*Tq$3QCkywEIp_DbQkYR2 zIxSbzhs7Enkff7snQ|+m)E*!s;lF7>{V?EZCwk?R`vZ;@%4Hx-1SqRObPHeT^HUGIK9+8OTN-7cEuooxB8t*;>Ak)PxU3g}BmDCH$@dKiB66 z<$6gDdA{ulVMmy&1!0F-ZfRF&l~+;B&%KSV2gyj>EZzh1D%&HPjLhQB*VDag$`LoN zW*M~EKdx#Pi6r2|_x?~LznF#`s;}Syhh(WGK8@dlpfokCS`mZn3gMnhUy{3cP2BEP zspu1=d%93edEchvwh*DkOi50{D2?c#-_kgH+Q5W=I4f)L68GHv?-0-o={d(cBO6K z7%Lxs7dVL*Qp`f6-L@ZQ%%@BD%^pN7fM_f-9Z0n zw{}aoPCF>eTu|m0?x!b}G9EEE@K28ytbKrT4JzCQWzJvEBPwY}<71|hFRgao5tty} z@v?i0F9Z;A<;u6BW@H0%!d4lYudc<#hN}SwW&(how{=a6Qr6OTdp=H%rEnW{cwj}3 zDGV22))zkqXdav9m9hhnrUj+vG;0>ATS5w@app)LslzX0$M*Z}c66#Q&T%ZMk5yvc{^4qCGObK1V~m@k9iw2tNX0sHR^7oX!@*GhZOQVBWRwad@{E_q@u94G6>+0 zHNZ4V5m{{^H!_VAV$L6Vn!E8fI%cUW)T42#tpha_5qM3}p5-fiJC73X0rfb)$SiJe z*A%JEeU*4Rlub*ib|bbF)HhL0OF4I8D;(-cup!zC0Gpd>oQ}D6jMv0Tx9W>7M%oR*fG!JvO?^h~1GC4EtQrarZUsFAM={))I(^giQ4g6wb4^;LgjtO>4+>l$L#j^w;EQ% zs&(pCub+04YXna;2C{6V9=`71e|W2elnxQ8X;`Cc9aiJM%9M&%Et5zFn_481Gi!i@ z1H1a{G$JBgYTPJP1pjDkZ^LLfj>62jF!~7`A`@{jsIK zC&;RU=LZ2jwCbxA;q#26FFN zKcW&@Lnr-z?Us&K(pFrMgXeB75)Vg!2eDL+%44oh4tUY-jr&o6f_wYzskA27mzXzK zOcEWrV`VG+4C-M!@GE3|y^eL@VilH02$^x%I^eaw! zx9fr3oR*j_i%%znhyz%}S?iY?8SP#JlPY2x5wx{X#A3$pdddVj70uHD4_jB z3nL@oxR9I+NJnJO%`}TD+SaR~Y5lmAEj>B=QmM>VVsC3g{XGqQroOte8 z6E-r)j?rm7VFI3Qb-Vz4>5R{V6zHHj?%KyejxpH@*CvAt`CI)rTJfZa8rFfzJd{vJ9=wsO2*Cl$|EY zzf}c-OEke`htyg`5Un;{M9&C%y<20&ShJVo53!0#3L`~etcOYAU*GK+Is>#og;X^X zX)S9Z6nKrr7Qo}r_$Dv*ouj_HxgVivAmMDmyz&$3uxh{e0V*A*yoGS_waxg(J_9_^ zMPxUuxiJRC`wMG?^JaaT$6>&@y(n@MAo!rI>+ItSiB;R??UlD`Js~YI9{4w@?Df_3NSprLzMZn&M`ZN^XrxQR@%ynwI_E=>rX_Il?&fnE7cDz1YD% z2(95CkZi&B32DbA+yn4AyU;lV(I7^<_>l|_WqQDs=)#FN*(Y8HRR@$h`g|w zr77oO-YK-ujK^*AJS8cq%Dn_;SO$F{XL4rg$0pJUYt1(YK6vBS53NMRtQlCtMaU8j z5srBdi5?&gc?LAaMovMzxjMrI@qA(vYPEL>A|vHJ3$=wHX679q3&tIJ|KfKTFiP@r z{62J}-=qEiY=x3_F#is?GBy&lwRQN;z0!9w=67(=cmKzOC|gO|_Iqr=ZOfH&7eyu` z&Ur+{oLfwcgk&hVrBE77Sq7K{7yw^Xmxts@{*#SSp9>HdtP^0!Om$tor>$a{2Kk+ojfM?b56yI`eIIY`iO6P`4tH8m4M3uQN}L(c`S@9GnWy5?P*R#{$nH7CADND8{uy){e1TZ1}i3rt`NRH4n8`j|9ifS;4|9&I?wp84l> zEx67NPnT4HBJ*+Zqy%jh>V z7X@3>J$xJM;tQvI3o2Tu7DRgJ8$BEZO`-$+!U(V|^bdQIt%|cx6O>r8+adveTZrM3 z&OOP6oah6NEGg92@9uHAdCPqLH09=s61D2u-SgqXw>#W10mtSHLT!fl6HGZ-CaH#H zTi1b7yCaX`I!}J7I4j9O()|f}ePl70t9Y=qPw!I$%ch6^ez5owjAdc6DZQV_Ht@(} zVtWp$NBIImnAQnIl(r$b2RoVjkWTR!W$NdswPB>a+y0wP;?T_paby>WLm^v9RfFk} zcLKA&8}TyV%m;2`Gguuxs_lETkMY#WQLW9cb=`cQ}sBO_~dw z?M~l$9isa2W)!O{^}=mR6NZ|~XfFC-ZziqE6|jvN<5>a|Fz*VD$DC8S@)pS-_psEx zT(^=v?#0UYIK6JgM|6#_B}@0<>xEVW&n~;KNUU;9pgpdFL!8*xdSQ&T&@NwH?3kk@ z+=I9$9sMiOcWj8C+rC0%id_*rw1!ZXtHYiz=$0?Qh%0sBvUemrgU((tws#;tdL^KL zm`Ca(P`Jjwm(nx4uTVZ*aL6RI3Y@T0bLHbG&50Wbh{_@|Iyp&bDh6~=NG_vyD0}g?PLaGWee|6E`b#iJhak!)*SA1T=lcTv z&)y|PLw&1%I+uv)|8atol{Rb^_~5xSGqFaPNR&2zf(qCtu*gubQjq-QPmU9C2Uil{ zVxOkm8m%YsMCwQC1x75>BL2mQ7o6t&eXJm3inp>cW^&jWIXiiMeExvu#e{M=rMVy4 zYY)P}utJFyRvn-&Cpl?~f~JH-gj9q$hh%M{$+!2*U9|O&=7fcxiu$vr5Z)4bk`ryf zy$;~5YOmb6`Ldq=t{aQH>A5@NNgW^wv)+SRGbq!9(o=duXRt=Zh``zVy>(YMGI%00 zLnK*_6nBw>1X(;2g&~{_06mPfbE3cDSP+(IOys_LPa41SVtE}y!e67FozG}*HAB#9 z+D+hU+<4y_PJr_e60w}6TTTuE!h>$F>tZa1X+7WB&*tn@CL+0UBAJRc{wL zdjhZzI@;H>68>YS1-))=w$6MPwgq}~h#Teo^XmY&mZJF9RG*1UZj9<(jryRD+L|G?W=@x+OEuM9$BY_Qx18v0FT?nm! zUv>YA@FOd2mT-}I{JowCCq&uOMz^Kvmzg6+wuPp&j}YCN^4!@gidmA9>wE|FW3d@> zy}+w^f<_Tlqz?Cw%4HtHN1i8&xhx{Qyl_UQ+-whhloJDF?~pM&Tr?9kZjajeri|tr zG2V}e$%%8YqGev9iCYMEM5YiR>Tb@cypghv(S3RBp{#4(D1&`MS_F@j(E>=Bki5*v_;!4? zk^VTNyVC=7+SjVT=K?}{t=85F3SySJ*xxcbPC&w~0i* zGeruN(*+HRAZ)=cKHH6tu+9sM+N=ah$2PN{Mz%#RrYI91{U_ELFVQWs_ej6wJG=fO zI?#Mq6rf)RdYDT|bE;PmIs`kaxysiHW!|0mb}ne7q89O| z-EN;s2a7}08Q2U@9 z4>OFcYX`@E1(mS?7?{L!)fF?`Oyydb;ybeRk;Sw(5~VeK2$%GhXy!x3Gn#vXBh);r z#gr~O(P0K&6k!sd zY9UK18eFsyVNzcw7=z#Q&uBn|CgnXPD#{{iRhB*#TiUor*BEnkz(VgX z$Hyisl;g*NL5h;xjnQKleTAuZWN8utNCh+J1^RXKxS0n3suP1jYT+0eqyX7ez*&w8 ze5A^e2{B7?o=KV^h%`l`UQ{ZoDYU0T?wDgSi7|qwM5}cATO{*!*BQB37jW;S1$UZI zvXcmx08I&_id{7OU`}0h)!D2XKOLr_AB|%ORZF{+`LMk;E=lT>8Fh2s&$EwTQ_pak zf4bsP;GC*BhPuv77$DbM80SW-)Er>^Nmj2sI zaK>Gz38zVu#!erv9x@o)c&yY}j2ETgh@I3PJx>3g%; zL?=Zkt+KqQz3KQf4@I4>Rqe5H0|5a+UUyvvSoO%-3T5?O0hoiLEE=akU3cwetidD% z3f!M(5>RLuRV8FIi%8GayCY5Nx6@)y>#GgEiCLTu5tu__df3Sg;$AM*!M!o7bojsk zp(7s{8hB|9%3~jFRAxBB1%l!I`i^c`9dzHahZfCJr;Nbt@vF>h?Z{(F3R(cTRIM-C zliXacqo&%Lf7qu&(Y1U59aAk}E!xxc8p+grM2I-+Zta?Wlw^eRvVrWVBp24{PQTx_ zf22qSy0e~a?Rufgx-V&O(Xlh3@suCJ>1mi#=-*JbGt7Lg$H_$Nd#)Y!yCoYz<%Zh_B!aSs62&91$lL4=21qXK2!Tcn4av(DoQ##~&K(7zQ~5 z6}AT@wucvNBVzv*HzEk`U*0LGxhwY9Vcv@G^ou2U%j)k6`SABGa30r94dpj7@%x8^ zQu7*F%}AsVz|N2g*h`;bc3kxlV*C0awR4FyBJ2ldL!V{aLLE_BWTTG^5RcscsC^+;);ZKbSuhGM1PnX@Tff%DG)09QV7o5t2`&uM>|^gB3kia>0g!p=rMTl=tAdiHi;?Ag_#ZpnI;rlj z&4@iYa|{1=DzH4IccIHkVHY2+X1?sYscv@Yur#zru}=5oKkD!GeX8E8z7xc98eci$ z+K{(+A>h&DjX<__?r_i^V2e&iIaAGgcc9!QZx;7jn8ew23gCkG}?P7E!gx5Cw>1o5%iH&5Fo|iTB5MPmUM!>E2R#$2*+JRxm44&*4Et`T3Yr5 zIKO>fo}8f@r_L3KL7166vLgAN03EJlMnct;;Pap#MK|CldJ*JXDCjT+sHxavsOVPU zf+4zyloi{hj&9g?OUQIIXPGB1QY5|Ck$Q$dMD5%B);;wlnN92b} ztz-dGE)Thiui~`$1ByDtinLP?r*?_cw##nb9=Tw72C;*WG5SVB3$%WS|*lvzq-yI5HjTW)ZX zciwVK%s8*otJ`P~IfzeTQQ=yq{&Up0uUKzHHX(y4+C*WZLYLi<#L&Wls8@n{}}wd#}A8$5C(asR_{8 zycXd(KUJyL8q=e6cl2am;ZJB#S1M}}rH*w_9Lcw$oz|qYQ^nETx!52sG_F;tJvd66 z(I8RG{WReoxTGhB2E}z`OU+@YPpDC38XqeszxM9y(lzQhoS`k_?~>SMMwY8!ecPJVofX1T4$i5>FG>@m}077%D1>9&t1 zf9OY}?-=-2bbzVna+dRK3Ct_`u*s=V=WX7vj3IdqI0yTw4b@MvIItb303#RUwxxL$ z!{lIlE&fYBCK(^dxR2Lww1+&k^s9mrBN-Qf8?&>CS8vnl+6G*T9bCr+(^2f6r?Y-iEw? zBW=4}f+U8)jE>m0waXcLFmB=6*O?lQfUr726AOjj#-=nLpL=yPiRLMMlgZ?F!dCbp zP$oX}!62Hl#CdelNKdwWslbZU+!{qz)gsnY7*P>SnbaD$kmoH8(UkjBe>OJ%&!U08 zfQP949bHw%2)9*{qR!)ylwRfa zO6i0^S(RX)w}$w;!peri4Gh0Y9zT%Seh0K?SVwc1=LzF#|7*<)vcVpP{T{pj-W`p< zy4z%*kz3vr z*6o;%VY31I%f}us?>i~4E=l^@OAoi<#t(1s{~iGq&`PfwKO-N_Cq4N;)A{~l^8LRN z@Lz#1CH60JvWYA!J4Vn?B;4sO_26%Y$aQrIp7_p0?Ma<7{8Rmi*M);o;hMkL$9r;& zj^5k0zDzJhGJSFm$T17s>)2J~v1r!b@^NP3|b+c{q zbuXiTrmlXr6|qQCopaJi-lw(-lFpl0^h*u5mM=>I(pD@CQ7weD{$tO#$iq2FY9Rp* zam8WCfk;2H2=AYvKz{xS=Or2h0C)bjc-B?T0%9u zf6$Ws?Gq=-PcbZ|>&Bw$XESY$ze}FVSX$***OUmt8|PEgkFx8LuMU|G+$%hR9jE}E zh8cWMqrrhwt}m+uhn`cD!bq7+Z*|98K9^ozmoC%oe;x$_xhP3!29a+?9` zon&>hEJ_Z^Z^A6m6BUA%fy7X77W8j&rPPOA7f z0#$xONC`Ob`3yI&5g&Q8)#m5H{HDH+zK-XrdjjtQG|zQgO_~}(V!aJ!j|tz&qIj7s z9raZj)9{`jo$W(U@^*7?ZCff)lBzAZ%+nA8!52qqS|Vs#=H0zt5j*IZCA*yRW>PoQ z3vD-BpBF@cQ!W`7Y^3#Ny8vCJwgrOC^M;~by{=LtG!vX*rV?YV0TJKo{9tr=R8IrK zd5%Wvp4qxc2Q}U~#t62+1bUJj?Q{n|uYOn2Bvq$T)mwAr0dD-pC;5TLr$7M5$v28} zG_1=CjDr+cm~WSfZR5Z7K5Y4(TgYSq%FSK&X!4e(FAnYD4=UqTY^9ey=*ktq)1Hwu zw)3Ptr|}B^S(vJ#82`J6m?-M^SKP=Zi2$T+BNv}_=aZB%0LQgK*aVeYsbqMI5_$Wv z{h$k?g9WGGT8$&;;{QB0ovcHYCm=yddL= zxj<45Sj9SnD9xc3)r6g5nDrYFAkb41P%LKzlP85=QoNlK z@VCiC4CPV+=4Z)RkNMw_l>Q2u|A-c{RxYmpYWbm6br-SUwS%^ad1V9Euo1(q@mEv=*Zdxc;#+NlTsC+5O(#I@l!NH=wi4zre(PcSWY7M4|J(Ub{ZQQ3 zWXHn5tk;nNd#|Tlxb5lTNzV8f{SD%QYDoVbpzQ|vfRL9*=Q%gw0HWAxoaiIq*0U5& zAa$31*0qz8iVaHxTx_&(jfh72k>7R#&$9O~uxDAF> zUc()sgok)I#6Q>J1Ok191;3fV1lX?IAW3kdG;R8GaGZa+2X_t}>PdBWXT!>{ov0U0 z&M7J_{_%N-!%;6}mT7cjclG$EfRsq06z8@9XYqU6s2p$kWd| zD$g#&VREQE!_>f1^H?u>xY;)E$?aBO=#ExLHHWTe4LIrK=7{mNZ^~Ozen9EP;wWiZ zP$UjoHEE5ej7^Y<4G{npJvdQjXI}HwTeNfykfVmEh_jngm;94ck+G<@OeB)vrQnlN zYx`u2NEgIO)Z-wlQ$qlefIlJaGv7r4&~67Z^`#kozu zWfT!(&X7dwW5$Ff42n2Rhc*w{S zF)LS|uJ7zTnd&K;=y7gU=byW56c!iiTQJp3JxxjE(xGU?>N5Q^D~d5-yZ7^~y3OS!^X63u>D&@~@;h^!(*i{hT@6^}g_p z+O0a($L4ia;Y0hr*v%-xhYHcXmf=m15$$_&NeW1{C`7idW9idM319H&B0T$xC2eHgLxU9c z8-v)ILIQG`D5wfM7Q}bQkp4z{X`XX~wKIl$%bwCArxltStjdNt%jotD@%X8fD^?V( zn4;%)>s6d$XiX)0xs2CM5!*Z1s6U4q$#VzpAoUq%qv6T*2W80hhp%L-g!pJgAm461 zd3}2{Eehau$2Oce8RDrP0kd^k_ats*6~oJO!EXd z{;sx@3neqTFIY29y__W2$7ea^5vo4eZc=(=I3lT!*P}3!=+)Kc^^d@=0oA9jIuI== z2t{T))%ork4nfeskpc&8-)563%fcOt`_i5^{zl!h_Eg9_+Cp<1?HVERHSf?_Tlt*A)dUrNAB;d}@X5zAc#cD~bO2bvNH?QS*2aiN~8m7(+r6D7*5tg48 zj4 z5Iu?bjZCp!4LAMp4_h15o8MefOa)<#D-g22jd=DfpeLeI3$9BZnI;7XX(272ji3;(8R})xQ^d1bCDu& zA8g|Cr$#&biv zm(pHz#Ut(TLz%1F7s)F+N>ER#jNq9W4;80Z_NCnQix9jIQ(lr5PLAM(ODYwuON3dC z;VHoTbc(6SAqXU$pGkA`1c>>%3 znQ$>0pif)kapeRH08siwo!X2_^&y zGU3a}PyC-+r=Zlvy)tb+Z6+9e)}?I(7Lik`YeEX0lwQrZMOn8BBAwFcX}Wv>5r?D3 zA|17GXx*^@ZZiUrSHk1Fc6I^f^0ePZef*vtry?MFKk?_sH*STu8`K3(_&JB=zB;0b zc(U6c+{uZqM4vQ81NVEOYu@+=X6Ew~}l zwWnn`g9CT`(G&%t5K$gQOs|bFswL)kF^q5IQu03iGNfFo)~)AS7JuOKM0n%a*i+}l z3T4~|six-cI?hW|39YOrN$2;o5j}p1Eb7HrPIJ=U3qo5{_6TvFuKy0z3|El+kWcHO zL@+kgGUUgZPk{ayrb#Yu3MCp!rs^|zK!#J%E%XkUB|%>lpP{5nS@rayh~On5VHlVp zHg)k$l+5dg$HlUV?_KA%o1 zbkuECq&&5^3u#LTd8W-t_snKW?l*~oH9poyepPj!%@pw&Tt;{7H9~3~QoxIovkCPa zjUPxJR2Ot(HeUhpPGQin9)D`3254?K<6+K;6`rV?%*^N2P`#B+x#mg_gI?ntfH8z~ zqyrt8*S3wpe8WxobJ*qU6ivWw2pEgp(CT-K0Ba>pJ9KWzVx^0r);5qaPEK zwD0xTr^0B97W7%kxs{{Oo#CmsbRYAzD}f$=Fvh@kEMv(bM|AP8X|8YpEH%o)8BLyH zljt#VChQ@$z!tCZ`d5g+x`ZvH8H~*Wv=iNWymoCm2Wf}Rji%F6eJ9wGZX@s1hAjEX z?G|cK78;1w>}oBxN=>JW3Ws)iGM!tRPyVg;l)Af|eM<5Ewg(;1z;&Fb#NwqU4K*AC zG%95&=2c3ph4d||=Bz&TbX>R6>6EwB1-9q(2{cID(n7h{l1*abK=xkkLhyE4k4sl^Ys33jO3AGix|`ZdE0`)nK1C?Slf8IO4EKhZP%#Jk={sQo6%B9^d&$a_#X(Wy^J zu0VMaBI%5x31tk*@np3zkujAqcjg*EK{ftzqTC@(&ONSu@!N1{9xrI0yh|aEcld9| zH_M`S!wU*nuoE%@jVC=IBfbfEmSq0m2RtHVE=dd~))0;9HO~IZu|ar&Q3n&XHxeQw zr(wz+2;0{-L9~K56wG#9^Fuw-aMrn@(qh$J|68-aZFPyZ=JF64y^SjX*cKJV{RN1mfx`j@2?Nt-U^6Y=m10f6#w_O!&+FO@^N+U%}^t`mbjlDrT<#hd9(f8`vCW zITyR2K{Gzq*5JF|%W%s%EB63n34WqvUypa073-l6@_1I#H zf-5nwB&BgMI3#v;|Lj6h%EAHhYSPOmUi4eLu}V`~4Ifpr#Rfc*@WCEC(*wmT7o-7M zj@EKmXZ6t(yMnUj#4S6KgW)5;ioRd6V9TtVsoLVhVSDlMVMJT|VMk@TvZIUw2&f2g z{QypE2NAAkc=zYstkK**c{~Zv-K=hoA!Bu z{{?B~|8)C|QJj<>5JY+}87@-Sv03yOA$BF`H4&pk;|1qEv64$Q6X}3mwL{>~{7TRp zhC&fYtOkBuM=;?pz;bu<{2ReAVjK<=8zXq)#Iv-3SohFarBZ%WoQs4&-&${2hdh{X zm;2+50Uuhbt~S##If~6_%%A3JeT*Bu-6yGbsN`N}b#+R;kE7GF8@3@<2xs!mfjMjv zVIe;)>iA8^r2S#FH z2iHg5dLA7XV{jdlVqlhHurNb?vt62&0?)k2OHRqu?H@4|jzwL;!p6c*#}fRO5Rwp> z5ePA2Y-|XTWDBP&m>C696q*y56odM=MgQdb@l}t{*=W!w(Dh#@-@hH(KSM>g`d{cc zN<%zt{OyvlpOy7cJd*M!uUWN8S*Hwq;R|}%B3V1U%-!6jeLDC4cyug_|4owr4W{dB z2}?_Oj?-%q%R5>?@XJOPYhYq*@F^#pg~xR6AKq!M$BoA4yDI~K2*yZfs4yn0on|E8 z^z+^?`Wi>V;YTryC5}wd98OXhPPP**#fjIo4; z1sKU%taYY46@ic7-Ym=p$z%-sT<%nPsq*^VphvRhq&ek5haGe1P#8AvG>xmyOwCpf zYh10mWuETs2PN#h9BS0ZZM@B}=LHwcmMYv7nU1nZ&DqU!OLT#ktME4S(#SRO1tQ(t z4r#Uy%7g$P=jZ_$(7&r zU94O^Glv>IkE>>G*AC~B1Zp*DIm90=ra+djMW`ob7G&|Z5qdkOGJg4o_zj!UYRPJR zsoK4nwT?aqfc1wan7dHDv0^%g&KiQB&jHe_#=pdspeIZzIT*UbE{1aiP;mWqZGxE( zBa>3nY!??LvL4UMv~Bjd;Of=kvmqp;@ug9xceGF?Zkk}Y%neruVbtMuS#J1ivYx&W zz>sQ74h=?ente;Q8G*^>S1gx1AWSL1C7;@sc+w3Lw7L_-!&_c>nfZ#Xn2sp}6w&eyQsQBYT^9 zL^74Y4?fvo^J+g*59zZo@iBSy&IEO@r@-|YbV><_ujQ{9xJ!H&XHAP6{0*p@x*vHr zcB?GAO7GIEhw0MV5SV;_s9Ujn5utMIZoXMizK+c8rfuw1O!Bktpy`vVR7{{RcolO@ z7hv&y-B()j;9c&*LHeaTFaF#FC9wVF?S?M=3G6Agb?0Z8Ag>dmoxm?Mpp_5O551Bc z2@aqTf%*+EAK2m$XCUSmztWN5608cPXeRM%oaiVzaNKmF?j>PdU{F7Er4k!$Up{gr z(E;}9FghK&*`Ug7lT#su>6 z!feMmB?=Z%hJsuQ&M9vpy12zF1vm;R^IO|X*~_}>#Zn)(ZrHgf427F`jwdw38IB{| zQtwq`qFtA~fZ#E6<1dsIVGnNp#1&yv`sc5*em*Dk#zhW)0JB916`}9Zxi{Z#DNx@6 z?d~CGX58>Dng|-dw$NuoYdy7SYS-ke+ti7o8l=gYvH2sMH$@BLtK%vs;nekcr zSbg>q|Jh3G-y}=aT&-;Xn)9jZsLTtZy|=*2LGSuE5=UY(77O;$po!KO?8{amQX)K| zt>rYxW-^ZUk0{(=K2zA+RqrE+SPMM~(=(G+pHqKVhZ@Vy zpGW3rxK!ovd>Y%l9+}$*CtKEW6Ukq4DkxJ3N*Bun*I^e zdP4UJ(Wux+qhT&uw4v(q3iW@N54D%dy;iE$Itv)0&OV=(bi}@#w8)|JSZ*G@T8#}S zO(~J2ua@HZT9G0#_O?^_yU%|KBagrv>JQ2kdxl&3fW^{d2B?EZKZ4CT~{X=ff zR7&Dtj1NnW|1H+{+*UNhGh700eiE@=^1BLupdyG4^9>dRckfqLUCEj7?OK`4oeo2%O8FO($X0-%56Xrixjn!+&_x!DjYHmDCvg(55G&&&g zw{g$}+|Vdk5!M!o3<39mN70?O_@C${Uztm!VQukdTb^$jy;+Dq1CIFLZTzvhN8#S) z=~04-;Pz2y6WP6Bup)%$luckUPbLLP6gY+uh6wE<@x_(<3Z7_+=3N2e;UMCR=1ws^ zXkVB2{W;$Mmbx!o4~l|5r`Z{wIz9izG~3F~(bnuAbP2}#&`hM`T4t!KW++-ziI;l{2+H=s#xWXkHBRoO1!Gf8N;68FdWtB2yoj{ zk2Z}VA!}$BV&M3NCVLYDvp}_`uSTXi&TGQ>fM<}P7CJh=YxFL{Vs-4EYG~H;_kCnQ z%d=vSgU3NfjjcokZDtY?v|}l%-+z~!`x!ZOEF5V2ue)=c=N3-YE5j1-xawOqzg_&Q zX*_cU-M*li7ILG3t0?NbUUakY0j6N^Jz_B}r{tsGoH;-;Jj?4hIPbpjy>OW^UtlLkLkBo%b1u)|h#(>W-9DBBmHXxJa&6F%^m zcZ7dhy-}$RvR@C-yj^F+8D)6Z)OStyJsoTweV#qS0Gl16lAqsQuP~yGorf+LbYJZN zg>_g88DNilB2Zb;uuC77pLQ50#G+0~=;hdtk#*s9wE|0fO5hz;pa~OiW+jVZ=E=5+ zEv?*u@aTR;+mnES?iym71hk1?6O&VAfFa#-;&SnXm9O*(pBomuqN4mHd>)vQpq^V6 zKc@)&4bcTy{|F^b=(TRYWkE^tvjnwa`7;C2GXM`m6}WAr`8_LBeP*vSC=oJ^t(#Y8 zCePUTYUir^y8yy`+sFs>4o1Yb`3a9FrU%nBa*YWDB3Df08uP$%gwNOGh(A#CZGt>8 zE5rfEQq!d7ncQlLi<}T-dd7TedR%Kx%mH)XBin>qD8Q>SEGw->#rSHc`RBQ}3lnu^ z4^<%OxMk}tPo?$qMoWJu%-)ca{YH5ADsb`hPaiVFkRM`s8XYl?k)?C?2|n96VtT3sYBPOGn0LogkdXIt05#^_qm8PS7sCC;BC0a0@cIom|6`D@d;zp{B)c zk>~eY%ct7q_wTNk|X4b&oud~)A6JUOi*v2;#eA2FeJNED&)1z^O7<2O`@1udgI?h>w2eG0~6uUXdi~o zZDh66hFSB5AODc#jM)=nw&%UWx{Me6Jo3^L&I(k!AlMxh9Y1s9S!$p&u)i&IU?y`I z?EQS@jM2z|ZJhLhuGmgUj5^C7p=5#mU&i2chta_v>#Fv90>7dZ#iD~%T0%{HOyi)W zzp>I&vk)L)y1Vc7r&{GJChxPl`57 zeQw5*IKItOA8syX;NQccuDvH7Hz0(zR_(NDuJDDN6BegLYF|O=4#T`MYx*pFjhbrK zl7#%g2VoDVmDh%`TcQRk)qGmCWG}wLh99#UbB;6hVYiL$=t#~ozX(a1(1QQLGZ6ja z5}jPH-E)m=d_^*GD@!JdbtWMOH8&O?E9-v=b%wro4|efd8xA0(uT$s!MtqI-Xjujt zAZ&xtpA(s=RM0JD^}s=6-IJ_jAV|?)4hA=Bcli9iyZtBZCxHuY7(TN=l$UO=o@~Us>`VBT+wOO(w6VWQ>oHbK5fUtZIz@)%hAMo@NF?LgH zB9(--*VfNkDZ5R0!Dc#pPpzzB;`06LRe3TspvqG=6vxPMQ6YmwYUiG1Pp`!;s4w8WtnseO^2r=M{Os?058 zm=~g1Tkw8)54cfrdS>wJWzG6=e(>8*GMIu!U|KIo{$QMm#2rLEAJ4Al?qxKK6Fs0O zP`@p`4-^Pvv6J10dEIs^PDpDU!Sdl%6%BzC-L!JMJ4<6uj-ENuPLIYeQC1&RT?KH4 zsw``u>Bm`uy@(Muxx@YUi@e4Xa~%F`DtJG4fBy5DvWvZuqqBpXy(xo)nS-5~tFzbt zxX>JRJrz_7EI!;sT5_6^Fv+GN<%Q5|WV?FfA5E!|1xTW3QA;f{4IxAEavqq}zX$vS z0=B*8qqaEbE8R;H^Cw)r*WZJ-+)G&AFq@B8gOR8SIpMA@d%U*qvR!hI{oFsE9@7j! zT=&+Iv>6+~;`XxP3XQUXnpl{QN<*GdLb{2k$0B&-rfpSw(ojZSNW*Gflmjx<`Uv?r z9dJnv5)p+6;A zQ>)@*uACgU#W8S?3+YpBxlHpVoihS)_5_+eE*A{;>G?C}HV4fBmZO%juG(8jzrJD(|cO?p~R>B^rvN$)gc zR+)mYgAlh4ngWVWeMS&rEy=R>P+Y?c={76~Mjq873-#!DT>?te+3`>Dsj^76r8c8_ z&>P2`S!i0wRuk!&Yz_8CNx>Xi84qSnka`39Oe88$McB)FS!P<%XP6N%)XY>RMHVUq zk;09k_K!M^Tr}e|6tIT5xq3Ctq47Ut6id8SHqN~1yMX~x(d7N|N{2Y0N) z3)BXPcuS1s2YB^WK37^n?I}6yQdF%y0S=a&-@fBO&J^#$b)b>9Xdex{eP3;I@{2}2 zHtVkoQO}Cx_*5Ut!r2<;nb>OirH0gYr>FE7U01Fnw-3F(gjqDm+78p;=K^oABLe{>;M~ON>0jpM)ETb~8h8{yIsKT8-Nl8FASk=5!{EDP z=3b7Lox+-{rzx28{k3cZaei)oQ&guTs0$KlT&4;8G4{6FuHNASo!AyH zj3w8?ie^Br@dInl3ZR=~bx%@o`jGixx!Y?%XI6z<{oH*{m`ub^XSp6B%!&2xdud3X zGWxZY&wh@=OOc1!`~e%3SSC8qoj=BL3uxm;i%6#K^tDHG9iaMpV0W^k2`d2Pj%8a3 z^j0+=TH z6RQfKlsbH{UPpO_Nkvk;#tj;%|1DPLp@<#Td{|Esr|68m8?iE7HXc79K^ zXCREO&f*=S<@R*AU9KMHk`(K5-HG@Rfz(Uf9HV@ZTw&F>4sKF~L?fhtX-3KQh-9;kg3 z7e6U8Gj`w{-Lc(Jy%rEL2Pw*}T7z^>eo?;65K5=jg|R*H1+`wiFUsenFfW!FA}5fY zKkUw3bpC9umA1WoJr^glVoTJ7rE*K0LPo(Mg-%!;x~6;O3AB)E$gF(1!IMf*mfF#Z zL=H@Oy&z>GJq9C#7I19FO4LlKi}pjk@ZwFTwWLCNTwop3BdU%NB%P@b4|CXl>x{n9 zM?57Zfv>|+NLaRNz%spW#9!yH4GnrF6WT)d?|{`Q-xiIqD@c!(R<{bYgSWY0RXP~S zrn-5Lg);WLf?Oidt?!}Qbubuyj|dMyj-&Kqt@V7fz`lb-Ji{xDojbcT>X#D^>*kC9{OjFg5{BaoR>$a%kC4qOT! zB{6Ycib%!zg(zTo`^P)PwIhN!940n@6#>`0UC zbjtp|kS}Q5*BdEbq5~CSe!6LY^6=?Bmx2c`B7^t~OfXO8Z>6ls#kBBtZ%#h6>_&2$ z5!pF79<)3;Q8C=@=?T(nTI1N*Q4cy+jSLs*tJ&Bd=jZg==6LYChqOuLhnp=qa++2H zOyvXza#HrnVU`-XPLO-zW3o`|+QzMzD~j^z`Xc!AKOQWvW3^7Dw4z5xU+t-X?(fB| zuQo{y9sSX`R3x;>X1tP9u(a%`K~|ec&Ot`~*i>J0=4Ruq+w1kQ$6r4pf?TqrkcD4# zb~p63U70|@8K9D0x?dMH9~seZ@Cebj7#!21h_lMtA~k7mN*G<+KNYpDy})ZApF}gM zkj<1IHGofWyc=VxD!CEF7Y{+E*@HZanyE?#hg+j!u_@YTg@F&jdH)kl{< z#m}n%6rfyMneTd%N285TQxi^fepUBiudQw0=0lhKPJ^X%oA{4xd+|Sc!um4Or42KL>*6ucA`+P9oaE}M_<&Bw z)>w~16`*_5uW)ED%r&=-SUc`qjLRnvkYGg@op)A7aZdN_>kF&W15X`{wd>jUITHKwk@<0mwsW-Qx>WKEr&7|Qgiy@)tNERaboxW}OT|>N z1T`t6O0_tw_DZmuEIddmg!xRzk2%!}C(rMhZ7mdLM~|Bmz_9NJ)_Daqkgc^|P`J-F zlw$@uDX$5GQ=}T?-4#n0mup8g9&Kj`9%ek15Aq(}<9a+BZl#^hXk{t$-)x|8vVQXw zJvu&;kL~Vz6E}RJL-7Qdxm=4jae0!^xm-ivNc6iviQ;s*P$FtfPZ&!agi-RKSha^o zLV%mWJL!F+?{IGVHpZozvhqvvfcO0#{^pXShV2WRq7oOW()!N4D^SkHxhZ&mWf)rS)X*&NM0y|7IF zlo&^61UD~w{xI(p>;$$0uCb*%uQ-X~D`IqdW4?KILg8APDjnK;Iuv%a;CHbjY0WWf zaG^Pn4z_S=gg0+EBh*V1^0=A9!i~+iB+gKliPGTJGD1F`i32T;B>n7R|CCYYXX$nj z4<-7S{OX|BQJJ>Go3u33S;_*65dAjxU=bJzIezR*v(n9RV`q(r2`{RH6JB=R@Fqp& z=5IPAO3aJ>;ewuA^!%DxSGeyv$)**wtcJ<;xw>c?RVRYc=?65d;-=bay04@iHFzF+ zR^b@l)(Ggc!UmH>s-SJsROXtcrBL?nvnKFEKpZH~_j*WQLB5hv3C@r7mXJ89vz(;T zg4Y1(s}+qshqLR^&W&Kt8DvU3`?Iq6A`XE!ICg2Ng+J!GlNvI9-x{RLLti2@j*+f= zplVC`R8ccSrbT!QVJ&T!&2;n&^E{@PZM%Zy*u0^qeJ^Ago5D_>NGW>JEcymyPf0|d z;lwoo*u10SytV!O;s@B`Oy@3Q(bMf%XHEQLgFc=HnJLRt(QG$8CI_HhKJjWRQ#x?c z1cn>|Z;VoU>ESZgGb~&iOIY4qhTl=jZ9+b+=H|{1!@ZO6~Bk&~|y*Sq!g25`#3T zp0=co@O6T(XsP}rHw0*z{q(q5e{bpfD%0eUBUU$DsYzw>!JD~2-r>1V71)zi4C@=$ zdBq|+^;Ho%jng)uH{u?L=nXkA0D!PX_$n~0@&ug=RJcL$pPDz=VfHUitS3QKWWv^n z9BoskeHsUU;Y`{cm}abs?18&67I;q~lmRFheBGww==Uh^XG^D7Yl9r7lR>stHv4U} zu*31H`H>27$1@f%jsFL%J0L0KMI!Ye57TYW6*=UT7-pUx1_$Le`jEt5*r*Rvn|;FM zEHD**>udYvy|}%@^%MzjR7L6tZ>ASk`&i+FD9jVjseockKttQdP~V+{?Qj6bVL|zc z80tDjd6%UzJ>Aw`Y3W`@*l)KnmS@G-3QYO;zmZ6C3vYB({sA|93Tlb`M;!5gmm+_e z=>9Kaoyuqb>iEfr_)C_jT2;>hMF@=#ESautEwX9pNe!JwZ;gHk_<}6L%ATJBF5-J_ zrbW(ZnnBbFRxU7JR2_jhY!Q%rGbLTT1`uYOji;!zxA1(}u zfuChIK&wED)~MNA0s#~r?Z^1ecN^3%h?k6qi)r3sfS#x$sFT=};cz@UE0nuJD8tKltO*-jG=BxZ-eg-MtJml2l2x4V$S?8O`TZBD;&#d+l6f>OL7AElPUd zbL1DAVqAS3^N9vSz0@r3_uyMl?Co#dQ|+>Sf>dYFI2mJAw!sPReyE4|l;ur{yu(OB zE%~AO)7wyYW;Ov*F2J(2k%8uf0eD<3-FheVW$dw!N{hm1J)E}ois8Cf3nHw@580uG zRx6llVOaFgf$^ac&9$At6b8%~4X>^W$ZsU_&dso&7bdHW|>O(j$~j`iY| zEU`ekjo9Z~RQE;}uIT3&|HLj2M~{O0!1JQfl6wH`SDh)Qay$~Z$a{wz8z<2W`fI&@ z4ddkp~i2#f;?M3`xEu(UWR`!pHcC!8UH~675>D& zRsS!)*?*PP|M-Ugr~7lY+CSZ&=k0a0)n{OW!neyVgge8-34AH7M6IRmXodFI8+feJ zni@?#)^`xEhYb3X6nf${Wqld>pLgPzw^lLmdn4gnPP;wKEvC93Co(Rk4L(f`NA|2h z-)8nC4V5#gteEFai)T^Ohc~gzQQE>*v5t&AduKGh0QepQfu|EJm`wDITS8)6xH-kFj(KNb2O@ z9)*p%VyxqxT+yGce`+vuiZb6~7JaL@kdtMheRuJ)Y5!7xu#y~Q-r6oRx_9W&U+cWh z4(-~9(eiV!E{hx8`YQTiP?5j8@5eznQfOZgooBt$_Q7RGy;fm{X&+O}TIlYY^G1d$ zsowoLVSbhCYO)vBLKo7;WRa;j&C7MpE; zEm!{ypwGp5;gU<0(J&ep+q#zqqU^$u%u*0J|;iF_8dva1$YzrSfwkp zD#Pj%K^MVNPZ;zirSf$cZ^Xf*vPiIp3|0G!PUiMmuyJoc|OM7h#mIp+DZ| zg>PFbvURehqJc!*-2awMax$F69;$pAbf8#^y)ZaFNjK)mtOj&I8~A3Vb>UuZ?5Nf; zUh^m6xY+Snp^vpjq{lxHN?7?TT1XKZsDd>VZ$z-KGBqR|CZT{)q&TDWZ{nD(`3k7# z$~8411y~x#l0185qQCb#ub54Bv1`ctR;x(FYR>U8kw~r~?n3AQ&7Yawl4L z8RZ4Qm3Z2tO*8Td6GKA3OQu%L+>zF}NzaXrKOkSQ^yeXo$~d_teh7XwPh(i6wo?9&CU!%_XcWK&;Ly)V*It;;|fR!>k#1bGNX`MYEl(d<)$E0Ec= zQ^%tf{vA2*4WtLFm^2eB?@ei#MdSu1G_uLc;z+mWY9hnMyvO(L8d?zl(e2)_2=*&e zW^X!P?MYkY4S3CI_`WwNhbt*lK1pOyvg|KY7Zn_NGDn)gb+~da23<6p4XRFczYY|b zX){LYn(?;+69^DQti2l5x!@G6`^;nSA#|BG(vv)c=FHSpsF2Tf3g9lxk6t9yn#pCX z+dK4Hb9OP6mB3@xB6dcNfnHT_7O1zG;|*E?e^LqOXayLJU==tImUWX4K%H)@un`wo zJ0%=Flcm`75ggxL{~>J58uKH8&NzaU@}2&Wb1JRDh?`DX2u)Cnn+pZXa$|@N8<=$1 z6`St5ucE4IRIZ~)MiKX;6>2*|5UDaWP;vA;)9q}+T~>U(aH5h;J}`v88m73f6mB%7 zg<(MR*w^*dZJc5RyHolU=Q2DmzjO30zBYy3hD0kZoy9(BcTQeVP2QP#H&!FKeXSr_x&_RAjjN7#VkX-*aA1!%0f>D#GU?XJdCRs5lwE$(|)l`|NS(e?+T1Xd{b zDHhlpXJ&6xAP*%o?d!JaNZq9}&pCu!G6;&ra4+p8cDl%DFLpLte7{j>KYksy_ageP zrs=bzSBgDUXoCE741E|i;`owFkD3Ks#>9?2&F#Y_jalco@wQK{bzQRn(R*gZ$L4qD z>y;?TM+4?$kB#0-;}zQ++L?VuyY&tAix)JDQ>*j*R<=lbtz6cq zkrGuBHk=rovY3RDkNqZZ@mtE6@^eT{_SRTQ_Y;n}cnCnhymd77Rj zmf#&JY;dvy)22B-QG?yE^$H^t#SpBo;%3B9m6x*4^@<{la%8W{9o_?_jcrQTCd?vN zH?d3c!m5zbLsIWo3U6@>mQwwD< zJZFDpfggC+7Tj!+e-e3tEhFNiX`UzCEcVmS7-Px|%$e^URg_9^@u z{_qd7M}_K{E{-Xxm&uB8FC)PS8bA!TF{h-OT3u>Yh(NqT?p$NpKrAGGmHk{sFV@&J z1C!i(p;Veq$FhpGfwhBTQ@Z|_qD6Vu+xy$0_l~Z<$7EYyEl8r|eP1Wj)j|3ZN77UJ ziucD+p5-@$?JIeN7c8gV8TsJcQuY^-AGGb{9#SJt8i7N(W_{t2LwUtCnZE^w;uv2F z;kH6^5w;Q!igIx}^Y+^0=l9$hxhwVrez;$UpL0|7IWlrr?Y+vc2jU06AsRo&NO^08 zWo-v&y#1gXy^f7ca&89`xTuD#ss>)BFfHJHpgq=tt38Xmpxw5yW` z-`BwB^WZsUlz6y^PsydQFI*s@t>{~NEW5gG&Q-f_r*l}7mQW_0J3DY_{3_tq@SLAq z`>=-}X{-QNcnS$NCsl!?bmbS34pBelb0qFbSas*aL5l34*bT9xPeOHpdG zsly^Py)-b{SPp40mzEcGM!5aV0w}fsn3+}8thJsV->P#3~EZRxN zSh&@Eo)+U&s3U$GpEs^awh>B$jYdOSO_9YF*17PO#V<#uwcb3ReB#$FbUW`a+DaZ@ zjg?wD8-dX!#!;cP-8Z8>HMym^m(u`44yQi`=*nE-=NnKRedjZ2NH^+kt zUs*12TeSFSB&s#GJ}+}O1XEh@92cf@&Es3oSLk?X`3%)B3mLf^ z>qF}y(zgR&cjnK;dhkN!Fy76NGkeqM<7y{0)CRfP_X>GA$OG9rW0;NJHIKCAal&ec zAxSSfaIt-iK4P5wO#rD(;9$=Q+vn6@R9wVy7Kt+}&2qhF<}c1S8&XEpUkd0oA` z&yA%-S3x0hKzkYdG6CpZ+e!vm!hnXB{zxHHtgH z5IMw=wx893l=Zp?JH3nR_Z;-WWe5yOgB?-OSW#4g8 zFWv3d2H4wFVsFMXfOjfmL%5(BQTN+?TJ*js$eM-jH&3|PZmbyVYyHgjpG z2*is}1vW?em~VG&5n91IMMf#y!?{Xb8nzMh7%jv{y3*=Zf!{3}L-o%?r+G}yEFIOras=SfNhH!6 zVkjivl7^t=3ej|iqrk-J0ze9}^%|*nS#N|LH3WWn*AK@nXYIaTB&lvs(TCxYJ))TP z#u-1;kh~=Vyd?kV3P$ma+d+SnDE!(qdKCNB%2m0G0Ph!4wp_GB2=FjpxQ4!zqBHn3 zBU`beGU?Oa(C_RgR>V-nj|*C{xXoLL!D^MuGVivATZe)pZ@ z%;-tkL%Kl5HN=z6a3k;bT}u5UFg~J!Dw&m82OdHfD;1^l(kcD+pqnXR+W(-Ho>9`#9y| zP{@YFrtp%pK9}Mrb%B#gvAxtZfRXCv$}lyR^5V+4Hj^xI&MwoE*@B-$UMpA)qh{%5hh zo!vUIKx(%hLvE@MfXsX~ko&LGZoT=&{SLd$F&8NUQc!uh$4QdQgR0Ym;HS_@r>Y+@ z9An_Z!0zd0%Ts#Y6mJyQ-P5PnE#@CTwZ|Mx69|Ff;^g}r>Za#HM0GXZas#JvO*5l44zv;b&H!zcavbW;!t36 zJ4VKZtX7j*6;w!hjIlB2LL=5xtdx%Ke9AMKsd>-F^6&5EN57(yE((kQCo@uu)kCBP z9f2iJ$&Hx}_xTwk3bnHmq_e+W1*lQU$$rcyE2U!1i$YzL>qlz9oVpL8%!OdE6dG!$ zIHlIkOY2|d1)47p{pc6a>{;W^@7@@oh=3NlDG5R5ac5rK<>+ZT6W%}H~~Y{DI3;Bl*=iYs_+EYbAXpEdmf?hrZ)Ex^kWH&2G^ z$5q?PlF%4d;+isN!>SOXr9@S(JcXK0>*Id5LdgA~E__@XzMxGY4HZiL zfR!js++zyQM3R7!Vl}n8cl7B!yog;%+Gr!_{{am3^9^FtuffoY`KjzCBy7YM)%P=~ zo;SLhI)-s2_Xi-vY=m)Tc_%jH03fbdTMc`Y1feO$I|B$;fp8X1bwAFG!z&ew0$Gb4 zsWILtn4vrukU)GDkm>SmYYM28B9$b9)+8&utqJGR8$Ujoc&CYfp%4}ME9vePbVa~p zdB%irhR4HF+7yCfJ(;`ka&UO;@_o71|7X!`v^ha+sB%d<`b4%eoX|mLJfml&r zzOWSvXpJ`H-?y}ztIC1^Ott>XZ!&$a$#d7mzh2zm_y_P=-k`lzZqVVq2cWQQ40QO< zR1^h(jxzjn9Ru!)U}us7?5{b2n$Kc=plA^G6!4PIYJJvUY3?!Bx&`NV0lyxi!sARK zBGOQP_0&ce`zv(DpI^baXtZ`b(&RefumKPB+;FGp}M%)?kCy z7-P z2)y8H)V2e4wLsp4970Scb-RwY9462vb@8Wo`~`6k9a(x1$yh_B!GVGZta)r5Wi*S!wKwX5{38!guYQNQ{ zLj7^P-iDr}`vFom0#??;0No!zcYvx}m>$&iN+`6@1ZU0O>R$>RBP>1IK68TF2wP^D z>RTXY`-RHk_?qM35ts{oN!;>8?W(v8k+B{waTnCw7WSz-Xzw73;`Z|u=O-R>^peYI zdEp_g16BKe#@Qe20}ZjoKq8?ACcMac>K-~mCC%$~x~OoJX<;RTi3fjX{K=5Rh($)N zi`Z?A6dGCyGxh*H^Z2D2!+X@Am_g%tXhzT*<9Q}=7ih*|Nx^C9Z~T)k=(%ci!kLC% zT7-X&PL^Qzj|aFBm6D)llc8phzm@1kKIBFPzAuNc*Y!*kd2rbMoZyU3C2Xn@zj6xZcIk3m* zlX^>@_I1${8xkXBI3m5rxlu7@^R=SQ+$ak@IkzU+f7(q?NYP9gpMhm$)*{$;OFm+4 zl$>2d@x=UbMMlg_d!Tfr`b^P$ssF~4q?ZK!4lm#t$E>Z-(LS)>Ea1DDXp_F1!m98D z9tdrUNcG8`nJ);HqKAGB&sj1XQiQYg_pb6ma>&s*$x#}aPCwEzjGG3glS|Y^ci#R& z%qig*yR?~zh6OtD@Qymtuq!`|!1#I#LdP%~xl79=&P z9p9C&PLUv@2fx9s`AjA%OnQ0wlLel*$4@8;EsUAmFuP$SlIS10(U*{*E29{uw=~;c z+oq2z$L58lx3pv>l9PQv{r7zWj1PLwz}G$j`m6c)UzEkRU!ae^)0g8doxs0s5A+?} zWbOV_2(J2SsHlqjxlVi`9aoo#wh9V{EB8|xOdXT(HdbMDu|k}RB8oA7gLKKkGF8*Y zrqx+C=44ruZ2&c6H(iCyB86Eyi2l%U?j`;26MNTI+!{CM;^yD4Z1$+DJ4fCl4_V&l zt2MT7Rs-G^dV1`C+O8A6H42|XJ*Wc#zh<$SgxDU6TldI#=tS;UWXJEH1Cj=t-r%rJ zl>rHYAAs@$Y?jl$wh$CwvA>50KRY9?!0kY`j5}08>r)Rm!Li$iC!Uo0l|8cBsvOw) z_jSVzZZ+Dc}uEj^k*`DAfsV;g;G zDxK68<4YI(QuHsBB4^kHM%#1El*vfKC zOyv>g@(MDd=DrhG42*#tviz3A+Kd&V6z?()-HYMjcvBH@rz%~n!wjtV$>KDgN_B8} zTyeaw^)cE%?H!exnDNl|t`3G}TH%gA{{%RGM>u(0_w|)4UbxUsF6O}-E3nRs zs!>ML5$YnAL`#Lj?Q%%ums(DY!Bns_+DShexpKjET=;HDBhvYDnBq}#!<)uh_3#0~ z!X-T?fr&X`GFm}8GLv30m7}RaEI5>uyaRaqJIgo}fUA^WgmHgH3cL@Wpe{b8^bmF> z&C|>b-0=>%B-FAKM=*GW}4CRCQB3~-MiEmFm|JtT|F6-X9 zcJ=YD-t*oSgg8U%mE24O{p<}2AL!z9Q?*g%JANC^pVcrSU*%#icRaDybtuSoL^|VX zzL@vfB)aj#4T6%v<(-`?=*He9TG$QT71IAgi8)k}oYhSw9=!Q!CsB&Z;}T`ysOY50 z)-(TdXwW(UsU%V?jErgGlc(L_(cZW!4KJ&TAs~+0Ndzj#dH@b;BG^Jz7*LXawjL6J zqrTYz9R7Q60;evX6(PRTC%)$kv(-Pe(&?^*t(3I}o0M_ne#?yhPMCh7{3(#2VqU7C zHTn!+R8DSIibO}n29KXru)$Mdy!Thos5dDl3)S(|_^9-{y&=lt1ScbjQXK{Q8_}A! zOVTU3ZmuW`^k&gqb7O5Y@~+Za@oXTkP38mPWp=D8agVlb+8rsXlm95-ih0hwR@x=; zzV{k|AwA6ob-zOIW|m58q=o5DtdE~V7{gJ2G5N7U-nTrrrA28czkwXHyk z&r#@)4O#17wC4l?9mJ4-=g1)Jzh(S6uOSjnz5<)ZDP9_oI)R({?L@CJ3( zzuffqhGbC;SovknO7EAQJ|QTDx>u6GdzFLHirmw|h#+5m*1xl3>a*t*jD0U;Z!mlH>n=yK?SJw*C`n!E>trWa$t^O)c$A|F#Qy&U))qxfAEGrvMk z{PdQk93z?EklF)Ydfq&9P~xhyp+2lkr*E>c!L6Ls$n`}r{gG9`)`_DZAhLRZBVrS>knpq`WZj?Q_yi5;&;rYnxAFon>=PLS zkZ{}JGf&)$NW1U#uvjrS#GT|fom%~`wSG6H)1$;T!pPV2hw?(DB> z`<~3Yg0LW4XmM0A>O$@8XqZ3cJP@S}_Y&N|*=5%~vEYG$gHyAId{JCC0+=AHVIU2P z*?#+-i{d_DxvGsQ)7bHVv8RbWqKmggXshju;@GwtS9qG4^F@lFclkn+r?SeuNf0_Z z5=f7GbJ_ixihA)gXqash!t*hqWmu<0og8;d-|fy|dYuAA=Ms!3PEg$>(=Y(~4wl1QK&GeOT8#M3fS-goKbZE{23H6~q(YlRx)@F<1QjtF&L2U7!2+mb z1CtN#_m@O(udYrIY6eLLp+vpZZC|9M#xIE(qW??;>*^LD@U9G=Kx0J$xmFANVc8n? zAk%n;KINb>enNy3_Yo}ot;_~Gti4`y=1fPm!kV+H!d&=5Q{Q!Tq&id?DBof!@Yje- zwbUIKw3h2I!O8Y)qc*Vv$I#!Oo{u$(9^N!J=1{miOd|m=3FF!nWH%HE|HsiSw>}9F z5=_) z%w+%3Wc??Gs-o#QtMTK5HD73UIZ>nP!9}J);MZ)}lSM;8W9|=1B~hnznQY8dnV(XC z)92HzFQ2N?NE#+i&b>0eQ4UKe31nH#)AYOOj1DwQ@0ZSo#mzK)K0a?Sq#*|+LyRP( zUVndnsnBLZ{%)gaZdDSkK4)k)yEv$I0K>pW6Ny-JUywQP`a!2Vz=WQP@Jr3qV~Pcb zQj)8z!WIs1xKJ~mYN>`Qs_lxa%p{p;-E3*hVytv;G_YVTO4pjmGeB>2CNtrbWYuBD zUT-j?u^=6|Y@PA zwm8Pelf=+c1H5#VKvpU7?`B6yTgI!}Zav-D}5O z7p*ojain6xBs=TKwDKnLD1OSDLGTsD1H0k$O)$8%=x#hT=;<mHbZrGcr@a@3 z{?{}UP+OrZX=#ZoJbH^+MP`J!1j4SY%~(K$CwN)klMgk%j3a)BkLGs+EK@U=PUMg} zb|_%o-969Y#7AMulpvltp?8qj@-Ll|zA6u&=&C$Z6rW?XeheN>_Ior>iTO0htsGTz zpr6E)s#uzlw?Ne=Y<@f6?-m4(A#0yCf$=)m;7EY*^Z@lV?p73dOom``WC#Oywj_`E zo*;K0k*xph&jmn?ZOAa2@bwCW7IBT%q?zI*6?psBJOalW=#L$?MQ0ovBDc_Qe+EcL zn3WAf$hiwYN!6p4zetc_$*aVJb|L-DVp59XQ)OtL#y|sD&nwMm5ZQhQY!&wcLgI8A zO|N#A8niSu0+QAcFHpGYxvl^o+)+=Ro-FyXeTF|r*3&s?PT!M8hRg>$oKOGhd&w{f z3cdgJt@Qai`Mm$isTX(rkKL`T9kH;Dk)5r%jgz>IiS2(5RR5VTX2~U<)+%j|-8pJX zp>Qhp&;Z&`LaC4+41@aR6v=iIYqNi@p+jcs1rV!htk)kVvNTaOYxZFIC85Ga$jBn@)!>ZvA3G_DRY-A@ zORb~t+nQ&PDclHf8zsjSCRh*wARDLTWQ6$8eSf8O6O|a&N`YT~y&!SaE5>?~PR1)~?J6eMB*{pD6$SPYzYT z%shNDzbb~wm!J5*G=u-w`}2<(Jj7c`74_rt%T0V_pC2Ih=U2|JKmH^B%yI(I5Sj`S zc}B?5B#Di2<3!w?X-p_E&GQAc4V0_G@WlO77zt!dw{=EJ@v?sV}o@AVjz50V9VNDLQAUSo3S@73{kddLhN zIeYLYBEHIn)13nQp}-JF$CCU#)Q40($op~t9;mt~x5bE2BRxZ&#r-IpotJ%5`o;Zh z9?ztI18~qBg!;WChno)0TqUlFJE`_5aAjYsk+x87J@LK%;Gbe#>qC1H??*@Czg8<5 z(e^V5co7Of8b8bd-r%MZw~MQBs6>b|QEuC#_M<(xXv)B^N5XlH4V!23=3tkJW;>u9DW9p0NmP zaguVgE36szlRg->2=B%Uy7XERas0bMSO+Pywuxzq9cxKf;awDv;XOSoll6?Xf@pT} zhh_oBx6sr0flrk~LC!H^h)2jL2<%}Ln`ML18ca+UP@3dnp*0qN`Wm<2C_T@3oSxih zuTqJ&dF}FDR8rVUvN+KOnc76LCbE=?NRz}IL*t?8B2-w6Pbk*5{qcVj=nNh+^;cq6Ed=wHhi7>#spbv0zo~97i&;n-1{;+bt}IB&l8PjW zdfwHC`>W`a)I?P_V(h{gh1GMf_GAR;JO^KvFZk(bfpd({DO z#?re1i;S+O+X!G+eb$gRL%+c+Yh;Efz>01h zU8+VB(7-m1^)Gb=OOk)N(00K)>9_glX2;>u9|crE&l%NX0g+AN(g!#KAW&4kfgRPT z57EK5`@=$d1;BFBQc|}yMx!NllutA3|F(&Aej-uKi=FSLYR=zN2I~?j@>%kKgG_@z zA9#n174QlhZGwQkWH1~Bg)Ox!DsXR43FJ?#L^N;X^}#=R?^b<6Fm=OFgyEXa=l@37p@a9p(j z@Vb_@#2~+!&A`aZ4se!6^uf!E^do8+y%cM-+*K6{!R!dD4tdx?TT35+;1n zZ>(s3_>3TPv^;svZ<|?c;4%c3w4N`#xlA>95B<3%*{B7T5=Er@JN1?oX$gY*Ge$o~ z)n++gQp&^0_B<&HwSi5|AtL=lR#1j)C6)Fa4EpXGo*U!TUer%SupT6?oG1WMq9@Zk z!pcoo$VdgR`no}LAsh_$O;jtnxynsrIPoBsC&gH_27FY^$G5S3rBid*%O*n4m>6qI zlfggSYpm+x5*j=o>FVrz0STunI_P=KBh-xeb$#ZRj4(1t&qRSbL>V$EiR{KgeTH)z z=x|Ts!xLCLr-^Fp=^3b$Fc)zwMvt|=3@pD%f)am7qzhbT>BUdf>D2a3%z&tKx{7H=C1;d+l zzsuhWI{CheWs?7b3ttQPHM&?kGUM>)*M`U9kLg`RpiJ*xum17sH)b}z!yi+DNlvzz zyRRke<6D1`HDBK9m&@(nw z`W9)3DuZ-{#B>L-`aAr_X|&NB*IFao!UMyVi4m+Bq%FA)Pvj75y^lF2`FuS0%xOA7 zane%?>jn|Z9BOAR{wGL%m>DIGe*)Kr(`I<%T(Wq1-744vxJtqx+iP`t%tE@C)Wg^= zBz7(zeR#`{?Zjy32g6XS@jABKZCX<`5Yz8Z)Dt|S%}3DtB=dWqDh^6IL1EN_Ro9fX z3>vy5{YzHEY(){{zN-hst4^74J^A+>PCYjF01p;FJw$3y$C2iBDUHRRKD+)pea#@V2K!QqJbgl%tCV0*Q)h;$4qzQRXim z5b`4)Fy~JsD{O9u^D0L)3tEDZ%og8$C_84k!v9jfb~DltuL-Zy);2*^!p%6s!;tmh zm#g~1KC>r285VM@0yCX_4@`y(lkV0VBH=+tBM1xh18?Pg(HkU+o(anp2AZW` zrYj|;3knGfCG6!Ac94}LhZdOc%Out!>ywqxf@~ zqC(Z_m-2$OD<@ovv7u6g5vx5c;BrwX@ivm2Z;^*agn{7@kJ%Z*Z*IHbOL^QhAXvqD zsw%CF2+hGMMKjHDA=C5a3lOFCacT#HkCYK4dx`SeA`<~QhdEjs!5kL6Xk`J#iVJQ% z_`|D!Y8tsm3C1nsD)54|6PB%>9fK}p_^j>i=>#3)G>3T1(0wQTZusclX3&wFf{eV|yH`Su@PF z2hPp?<=Q->q!C;MG~@}Bwv2o1qAIrLL`tpZJr*;!Cy z3g$2G^yA)HxoocKz|VEJy5U7e30}|6tBu#m&JD-GtiLl*yx`fAlgrEgQLD}9QyS07 z5f9kqZH{x$0v{|zkMKo_E@KD|+J5$#4drYlw!ZaTMZ{xc=-LqSD zSZ~7}TWjVWb!@5|4q*=+%(o01zAAce+Z}u><~`Wfm)uUzkwNIUya2j2wIaZKT*O1@ z=hSm(K-H>Q&(i(a66%hw#ZA>SoAyTs+EzDg!!x3XV$x^*wQfW3b{CDuSZ~wA16S#x ztF_hO(~pN&${|FjSH9M_DzA4wZLeZo?|f4_2V^9f{#Z)v4H5W~#{9uSkQ?>grkT7T z9s_x2=w-kU%_NP6e%QY~HTjbwUGrzPQnFZ1>Xnjtv{c3Q<@NdE52O_+wGv6#)ausa zVn{fdSX@|=L!^wpW?Nzwgbu7;th}k>B!#s%N{lERaT7ucaW&RLRKcJqI~CKDP+_^q7)+^`*KzJ=XyQc1BhaFkJFJ(kA1; z)$BAH!1|NI_r&kfCW0TY*<8HI(npG=IagBWR@4F(A;C#(GA0(wLY8>BE{qB6x<3q# zb_Mb$TTg_}J@(0O1{FsDuYc>)QY4HXxMf!-@rxs*ttr4qwr6|Gdv{rEL+-c#BHojV zZa6|*mkMcByYZ&08E_{=n##%w)r;Jdvb<|mIQMJGq>q%8m&J=T{A9p=YS6JfE-;DZ zGhA={b?|VX?M}9ClvurPzFr8(^L&9(&TCMzb2oNYLh)x+Qzvz^Rq3#**D5^WNR1+4 z%#AD?{f3wifL`^BXSv)R2~V5M6dT0PaBvU*z5^3l5W}xrL~S zkjRrGTQ!ZY-{JZ5g!ixmMn9wXs2yWWWY&iI4Ba)b2)SRUnQ-xGQXQ-jg%)T<+@IIj`PDZ8#6S%ots38eZ*@PwINyW}OR5!D>D zTS$@O?B`mt7+D_EfPj}^!ml|X9S4SPh;Z5Wj)2$ghk)1Hm$-gBEbUN7)HKWq#Q<05 z=Xt`GHtIx`mY(FCKoRp0xlW2rZEnBsV(xqm$9udT0Yo`_Q@B;FF569KkM*`_b@Qx% znLX44&x`ao_9MEO-PzV|9{xBZDNKz$cd~<)<(MrXCaR4&sJ$(dthS!vI#aU%QpR;0 z9@ZMg5vAEDT5tBQhqAv_d(X$cphjB7R3LakS2Y=>5v>^q5pj`NjL?d@jNwT>a@NYW z;;zuSR^#jT;vNoz2%DFbw;eJo%6D^F?fzD}6>P_;xz8W5{Z*!M#5NJhr>azamQs9BiS1h=Mb7Z zOhtp~u&>iH74v!kB8nFur1q&JGou{m#C}^{9Sc|1Nn{Z$A2GDo3U{i_nT2?i`VAKo zh4Z$a^@pbJtWhs(Z*p1-7aj+r$KV1$bF6SCSc)2LeQ{FE|)iXT9@l<#}N7+nE zdETqE$Ioak@f9gA|AC#zDF}pj{mH6v+@Y$q1VwQ^IC?H8qmwsLwn(gL`nN*i=Y@4f z+h@hI?^g4?x?fLP3oo!~vTXTHFw9~}NJD2MW5CescU{;+!~LQved5Wy7FJ@tYU)%l z@$#{B@amokjP>TJdJK!@xf;h32NBUFi(k5$2|*G(Tf|aUZ5|Kq7+mvFEvdlO0>Xxs z3R-ZyGUg}zyDx!ecg-d^k{1ie=|O##GT?vooOH8FPTES5Mda?E{eS+||%E?%g=cu>Q)b)!lejJYKR+NgssN~t%eWiJ&ev&vJ3 z=}K(Pc(N*$oE(nY+d>*A!KAQOiU0jbNKRixzRvLGW^N@!sgU|N`oqNeGW?J57B!5d zJo5*-_<-KwqMf9yjP~5k?D#!u&y`(U>KHeRp*Pe5f~%rg@S-jr<#aySHT^|3wItJh zZ9HGcu&DT&dximRwN%D#Va`PEBW<-Hf_2#LC`#P>XO)7wOgD#hCRqfx!;zLjZVuGj zlkP=Y3@g>n zLD9`_#h@*Rou|IgMSlxkA3b-39y94=#8(&#(`~o_i{}--h}L3T$OplT3W6=>MB#S= zZv=RGDkJRyhn-`T>>Dh)AVHO46D%sTfdoM&vs@BY>AgTIHEGJfQM?ET)uiQX=H(HP zCez&g(ihA&;gEefzsT7m(gb>NY$8_)+*$2?1I}@5LKXup=+GZ{MP&c{eJRnTY0!*R zW%{Y(r}D`nPPF!1fTU4E|HPuFeF zMD26ScCqtj8M*L|{m;;bdpI0##4WG(DIR!6LiM{3&lIaO3wIzQ^uG0CA_RZp#MgVjXBG9G)NA-#8T(fMIfH-H!F&@RLldBhfk< zydlqC+O4zWB3Gvw{1S5}&DS#pEk#GDEy+QzZCalxu#OR@!=D&k>GDv#&E{c`M$i|+ zFqD@g^k^VHjTOuXVcJ2u3~IlAD~@a#QJih~NUYfq%lgL-Wv}vad#H<#$c(bn^#J;< zws4W$1kc|^lIQP)SS+evic~h?3QQ}!*>@^%re)?IwS_gtSekhtBK!zzw4Kj83RX=R ziA+ZCaHrS&hs&boccwKE_=0S_m7k&$v3KHm%kTahzAkT+UJ=(DQXgz}aBBWHbFZ?q zuxvX&Aq@59SwbN(7&ZZSg@tmTyn45Vsc(=NG`0-9JIPnZ(+*zHUtO+`w5rwB6%fJI zb-&v1PaEz`Dgv*I&ab%BBC>fo>3G3<#U<^W)S?>qv?vVs#+S4=>CT`gbzEr`1a~@v z1W_m5HUmIp5sBZqF*GQzq%xnVANSPmz1w0VUG=SJ4BiKTb#H9hr%DgMGZfDw;^F43 z+;J;gfUX+Udk*6iBfvJ_$1Cixgw!QooSq-zY3AxmFX0}!4xwv;jqm1NDmuQT<0f}x zS1O6jJp5^J-!ZqCh5~wakQ;DI@9#?c2q<`989H`Wm-hyHzcy*1=(61) zdHi|v3GzjQxS0Q3miLx~(RsD*ncwq9w2k(1<6~~+Qf`@Sdj-8-<-%_Dxp-uW`~|s; zrCcXk5ZUSYVakP-e9$F(pplGlHRxV!MPa@&ar z(BO!2ABCq!yrebyGjkE#iFxo_DGBFq?g!?*WhsK$&OFR-Bh8=tmIlrE%75HBVM|?( zACqbQIB>e2cNIydT+P*EA;B@cEc20RYJJ~qx?R*GY}EbwY7smgsAPtJhmQmrNYv{^ z6#>Z_Yw+5aF!KCRcRcSc*cR((^NX#(WUvg)oH6LI4A+x?ji;eiQws3*&pv=p=fDlUMDNmk1zz(pexoYV6z}~nfg{^@UMoA^+t2?_)&n5= z-m-m>^>z`zeG~mxfTsU8JBr)bIXg+2+Zg{l_+)Nk{tv`{KtRw59Ms3GRD}jP zR+n%_MLNB){tJ8fsq>87-ouqmsPju8_mInV$Z&`-mWcA;VAJu6=PCUv{ceNlt}JTv z{o616PC!qby%{@EANpQ1qU-=U4^7_+`()1#bCtl{;&`KKqvW_95AZvC=4}d0D@@wq z$0Q>=BemeX@KCrgWbQovDd|HPtormo_d%+IRLjH-%sKU1Uw^@OJ5sI~8YANZ`Bx!U zt_lK^B=n?p=r$|!V@@Xda_W1!wK10x75hZ3G?l+}%?%%SXiq~_313}YS|v-qzUl}X zlyI4x{V@|!q6OwyjaFGcf!$$oph%s^W=&yfqq2*1%|OM7BH5G-`mHIfqUlhS>5%OA z6Zbdrh>CE|Q4F%h!7(*ty!b{mF)lTC$zCk)?5qpc@nI z6{id{YAH?2#~od141H-btEiR378x|fYOfljTCaRttSm5IyW0GGytwtUQNRf8UnuG^ zm!rUt=i!>w8148Fu%5wr&9~_x;km;_;ouDWe?h9lpJecXf{its=?TM)Z$pC>(VK7E z?-Ml}ZL4#eZZmdD(TdrMk#*vhw=Pp|+iS zid+%q!maeWj7M4gaGwcft$cJq{T1u@n&=#Q*QvP(l9LfT!p;)s^aWyYTn!X89lPqG z8|+)7yUwn^aQjYkTzMg`p4?-B#&zJbA$bT=xQ?l2G7UFk+#csReY?Hby5K++U#~^~ z8Lsoe13__T4LmFYYF=Iyn)6iXijr@5;_k3{-c5|Yazx6``HyrJs2>y-c)=si5_<%_ zr4Favg?DvpI<(Pk*rUXp1-me}-^1jd{Am{Ninxa23)K;&W|ug+nVBCu`z)-sWUBWY zXRRK-!TeicfS8!P7IyC#(&C=T(b`V3&x~3J&HU50JyIf0NT^tOJ_r@E zgE>~FvIj%Dh#ldyr$J4S)J0R6tFT4vxHwg1b*fKs4j zN3*=VvMcMm_MIR{AZa9>!-KQRIks6pn|P6J&G8l~W@lfmcha}6Amny`Q9Fn|AjHrO zCFu=T;f?2GLK$^0qEhn^beG?1ma&;WbU!S2_pOJFJVoRjFj(P_*DbpMg_tc0MsEI` zsZ0S$zauMml^UyR?BKij`?^>g`X^pU<9qZ6r-3fKS=pJlc~@fuCg1)uWJH&EVOK)| z*6X{)`O~ix52`#ErC+HPM_&#{wgLOn4S~z znPPhPoWE)Kcxct|oE_0f&Wddc{hkNQG`?9=>Z}dj#1zRq#)AS3e&D zqIHWa6yq&&%||-&u5Zk_qlEO>34p?OLIX^*zH! zd?rSE4QYL5l6-&(zU(E^BfRS4s9ZCXK)3&i1=o=i$JW2EMQ@mKp`wwA%jms6UtpJdLT1$GgX>omNnX!z1$S^H>wBQ##IXVCC> z>3o!Ufk!1oi?MigP74>TZpf>vLs;=WpVRZA<@0~h=*97+W})Kldx&}Hizv zO_b1QuOW;-@BcXzT+0&`Df$b<2crG2Mvwo)UiTkt`T|vTpymSdM-{k0ucgtMREtm7t)}lr)!2v-#C3_-C26a5|yU zO8e#S^5dCiQc-6;UBM@g_E>0-h|Ieu@6&iX)7>YN>EwFZ(#PxW*f*DLYr4z9q4s8| zr_pPBy30N?iAs@2+5A_@%FWiFp0nLxI`KP-j2;?+P{u&9z@U!h08u#Z(*XjU~d?!$4KAKSgoB#VAb$p)#$k;WG8Je8yxqE zFiK|ZjyagA(M=j;C&}U#>oqRe)&pbpRmhts-Re#F`PVb+ATt%Oot)utaq+mhrz^5S zVGgu!)F`b7W<|@*25C*QC3@DZ$zolmCDsT66l#vSxj8Argd^A2mKa7o8F4wBnhl#p z(ZWzmU%4vzIGNYyP%)t45O6LKaK48}l4GbadUQudQ87P2?CpJo3=0v*hl(FxP!ZXlI@Qg|3iN@qJArq}Il($fM8aR0fP6!V^Cs$5} z-CNwB=5}x;#t~;i9&oYReOFjTNvqD2f-AvZ=LNI`;FX<^_@lx)%Qzp!o*R$;K3p2B z70~5wzcYYv0^*w|$XwhuL;3%QvUlJWEsD}?@9{mhZQHhO z+qP}nwr$(iJ+^JTPrcW9c~zZMx|015_RiWXbB=F(3NXk~3T#s8*pSZZ;qi}2Nao`` zeC|m%&#lB zovAqJ5+t^uRz+$8Pfx3`{j06>Ru^rr+@2MIFYa8oG8%LtLu?XkHx4H7ROs)mYMgf? zz=;kmz28VvU0?OWRlnHWBRWb$o_?Uw&Syjn53$mDnp92m9hFOnSKI8UP_$yoR+)gw zUJ;2th!+nT{Zum5ezI`)kMn&~P)}mbzE#s|+-XU|Um?7F0=lDGS@7;JQ=Hh2cOyY> z@+4HCK6>aj4(-B);tuS zywBWav#+vgvY%x2`p0FGs$j0Cz=6^J4;V5{XWn^y*$&+u$q3Ri^_K|?d>=DJ&p@Og zR_`!px0mwiITW;!q}k2Ary@ONe3YpHzOSz;9!dkLXc-!cFbmZ2-HHM^{^!V$t?(zd z&#X%~+|q7+q6=MhU2`1C;0$s~Op13em;slej!tk&%2B4$HfnzZ+r!7}op4s53y~K= zbtf9Hog#yp$nUg@C#>aqeh zP=bwS|Fab?@6Rr>OBd{MX9sOB@j{^@_(mTtTIAHLGE&&^rIG4CiDE03CP~#p$I90{ zLs#3~cN`Lv6OdJ%W+O_fOK-IV?J1m@_{;Qj2iH8oEo){ zJk|OK|74$KzKkT7W_3hoWkyYrsaIq3?5K-<^MRASVqQz~pF7UA7s(Rx-B4<{ua6@} zWe3f8fK-QZ3TL#TU9;^K?P5!37$FNWim?uo`@vjOmy)diUa^#y(=WbE4|JAx)qJ8u zuaDY8O0aKBlx@fb#wzq=^Zr~L{B=z{RL3oSWSW z-7U!dLOZU=+;(y3h6MFy(NAK8U9qrBLot5T4JXV>Z=xJ=)}lUne?oIafGb*_KFMHD ziheWEgK1)wE!0xDv!d~X7_%J{HG3s{cu6CTez>0sIo z#qI+>Lg2Av*86~>0G2fDxY&xM&o%lhJO8qX;h0*YkvUj3OV`m$0g1wn&7VYgClz`gw4WUHH5XwJ?vFDnf)1#WzXe~Wp~Zlf>&$8 zS>Hmnwv992VZZvil}FkPMeAkA%6HId^K6q+CY9BG4q>Xj7JG539hV45zYC{4-$|fP zPJkbOkx#`yn~9D|e;X%0W0J4DFh`$S_FL?v;G)qNzzWQgdKZAPO4RS^%#YkIGcNW~ zoNVdvq5RGd-=E}T4~yJntH>JN+@J$R<6 z-2%qj$H{!#OjDOwkCQ1*-{Ph#WQ>#sUuAtuA5-Pb^yNso8V$MP7X4%kfocn3!Y55- z&l)O~dX+@6`2}J2>P2b0P=k0cGtQywLz(;?rqB|4_DOW6m$!8y;m_J!5y;9!u8$&t z*6byp<;FgZhe)L>0|%Nx4`*bSiphF$ZGUSE-vRYgTg|u;$bftW3rB3WK$ylQ|xiP;5g$ZShm|S9J>1Sv^xw8%qzT7#xnH*`}C`mdw9Y|No z8viCKrOz5h?vh8V7Ch;%ZZzm@A?>+DT3iSO? z^B1|_M(W?n2V)1v|4nTwN!ns6BYltH#A1(%XRMODH#6Nh<vO@>8KUZFvV>JgvWO`YAy5!Oh}H4qJ=YzB^}#3$WI1x9bB=%rRb_e3 zWIN8jWZ&@g{5(X0{UNEx5kh;QJ{8E44vA!mu??+R*iIEd{J zZ68X;F|Nsg$1Pgu;Y4X9JbtxXbH zQxnp)QZ9grf@Beg&ZnIsL4$H$tn0a9CGemm%H{AVr zQ5pF`j$<~~I~V6$kNSS>Go?pwAd5Z+7tD+ zBf-uh*mE<(`UJyj{sH#KSQUO9zh(hoEW6?caO=_d2Vi8Y7B)C15B6>yaGYv*xs$i# zkJ$9@HWUs|(>%bJ@6|T0$`%49wPHyd#t;VV*$t8_U|JC-Q3>K`QO-wN(~ za1F~Lr2-%<7W`+Jc=yz>Zc+9d#6z%vT}CI0zjMTFM+jLCe;Y|1zBo*f&N_6W~q_FTq)(BPLwdB9%aIf}kN zZsY>Aq*IPMi2~=!OZtd3#&O^`Q-MGKWWPq(tgSdX_TZ1%h7Y(7eSt|Gh)(U5b`&Bw zDu>0>R0%-l3_HRs30s6SpGM?W-^_I_(c|!ky5nYZzGz+Ml|3+G&*+|?yM$Zht*LO) zUK4f;OFuoZoWEEqe$}+UPb;tpl2zrtl=+X@HXXlk5JYU`5wN8Cmu4cwV}^5CNf?!| z!?*i~9frz6a0QH&2_ACjIEA}u5qx`^hKv3Fp5_d15X!gBgt2)axRk@85aZ)T;m{ph zOk}FU|0{D|O;G3~@LTPB^~;=d{f|ex9e_*Nf%q z%r%uWJuB-38cahj4VJaSIsr|iD;6+B>C8?RLTLjW<0o(kz@AmNS}vqGWTRizA9gZ5 zA6CJ=h%gd>JiHLU&=LL<=gT&cwQ5U&@1g6>*Vc}^_X+38CfDcpmgk=-RUBB1dgZ}k z93*{9aG4?E1}%z7;|8soJqXy%#&HV={hbQ@I(dZaxS>i4z}kmDRVVZ2Rlirb#atkWffIm_T7;+22jFG=kq&#C>fv<~ z>Wg`s?jR7(KmAHf-h_)l1BcGdFSQhJ9IVLDp~@|SCq#9eU%y?$r_|uACu?cMt06cjg(v1 zvc;GK*(2a3ZruXrYzaxxC1MoSbmiuXWu!<&8D?@WZhh#Hq0vbLLq%h(iax?zIZ%{J zzHvt~fgi{DVpK^iWreF@Ph?Zvd3=IZqhSl?IIh^J;lc}&)48k79jEMy$_KySb+qP!-nEKBA(wh| zg5#-9-X6x3!AsOc7ZHGvoG~Sg6-6hD5Nr*zRbmQj#hNaZi?Ov<8dP`q0DMSwP(G{Ud#mP>iq2uJR6sQ=O zV(51okgUN(_;4z{XPsJKUCq2{kwjZJ$4>|?vKSB(AW2|Y+XqKjNIEkiT|nh5^n}-} ztkiiaX?67b)P@zH7YCFw=ZL%1U4gTgL{YuH5tJ4}TfSE8zBL#z{G*JyFcXQ4Q8apE zlUb_As6L$hnRj*0iXsVSKQKqY$eFt%%2~K=iqYLoNqR+fbpL zzmk3z@9l7Gqb%L*qGO*xBHZj30~KOeMUw8%yP4V!9>XLg-;6y19%(F&i&ZTepmf@k zkVKbJVAmoDsER8lgLVF?7$DiI6gZ>6zS-k#=&Cgg3l3o^c2X{ia6ZzDnhXEe+9sLXp#Tp#+WJtn?z-;g?Pmo|yr=XC*)N#OV^&sA8 z!LeWOM21l4ph^}`XAnh0r&YSdBof5j75guig9j9%Fs5_S-qO@5d@V&}IH}QA(PUK= zJhXx{d51i<2*a@IOpL2ND%TMuT=K1l-elUM#L_FdPrW<6H~F*Bk>CfNA`6)G2i*bZ z{>C6)O5?#J!uzO;xfZLvE9H)Blxd~eOQImnwGVLTPm(R1@CBG$7i7#p^YBUK+XRVSm#gF!{WXdD6N}LG&I2^K*;|btp;DiGJKH(OX3Ic(UP89Q$|SUxH>JFE zZGE0U7xjVG77g_7xc~=qUi`~As3LMLS#5S~6r;a{-p?T#w8ByQYD=6c<;d!pfS>4m5HmgCC9MO(kOK~k7 zS9}g@*xA&uXa>+XRo4Ze`qb&5l8(vsa21NKwcelPFTq-rs2Ms&ca}{yFvL4!B{^l{ znPDxjC8q1bb%`AK#9;%xn-MM|Db~%yOH(yy1Pb0Bt5BW8#5&Kw+&dp7wd0*=L9;|3 z<*VUKDj4hXW_1PCOhfT(!9VvLRfRecI4UQP9F1v;C-)r^-4FNPJ$29sWw8VlmhQ+$VIa-DyO%XmI@`8SxXd4a#8-tvrvq^hYB8LRG zqlui^CS92KcQ$qUshwI~nGaid{iN)?th&I`KAc?;zBFCOeD)|0@1Z(4*^d`=AS;L` zm}5(r(zKOj)d@P`sltapQi-(_QOu%EI$p~yKJ2ob?$su=6)kPbi&Isz=6m~L7T|a<5V%fs??pAMQ2_{&5&-_!Q!hzF)YJC?2D7{51@|0^l1bFSbL`-{& zT+dO2A9g6Tk>WcXrhOMp+wWa709eFBb?rotb$d2(oS8Bis)!Kn-Pcqx+n}mHbBzw1 zr8Pvm)N9d(5p$vU8n)r)?L!M&yi9YvPW%A*D94~}p=?+9tE&Q**suop z&-pO0^|riHFx&#iToVmmQO&n(^lpGBcQ=bDT*Fx0)0;eo1?}--LiJ-pG6&;+-|VgvlB&aUZg5Z@aQuePSuE@?}y)RfO!?;PPMGlokji-XiA))3z(CmKN- zL#Fff(#)~)fj#=nWa(@ju!z&|jQ*5&92p-VJ)}S%be?ddHMm09iK@CjS}2lbHs&a} zll!j#RGlj-GW*oHgEB{dITMU>SNJuaVYzkvx?-xuMx@|>C}aj7u$F1#{Al;hzE!mX zRM{wWkLWz2rQe+eK0#>?a~mRWNos~P-w<-ANoKS&C8YZ?*8+1S)N0`Y7AECYNCJ(^B1Y?oA zGDv{sY^3Cg%ao4PswnD&+(VhOCcFmKlWf@}AD~YxUpubW*jZ5ce{`}|sT%D`RY0mH zlT`=Rn+y-Pkyi%JR^_`44>#o>c9oXWYAkk@eO{ISG+TVrOw>v;Y@R!OoZCXU8Vg3b;u#{VfNFs%XZm9*5%`!bV~ z?zPs6E~X|#lrH|a2=15F7z6Z=8zQq`&^CTd5}K)HbB{G}RiNZuIofki+xWAr zqq*-AZf?LKd&H=;3&*IHM9G9|Vsbx46=LG(h|cQhc5y^Qf#jGtjBJ22i|n8{6upA) zMUz1oaaAeyGfONnTAaCC$z% z*`_|>N%?v%VUt2XIq9ws0$ceykxWxBq4cn6VPnx&?i|h`KRb;^QjM`;xEfW&y-1?Uk3Fi9+h4H7-qgV{K@%{&pP=WxYS0>CS(!hho#xF~m}z zedLlXc)1I$+9?$3#>whq!*Nxa6|(DJI@vQ(7<);nJ9ng~n_L7mrQRwCj8z~iXCh07 z1Tj=O*qz^(4D@a1l_wilhMcr@f?I_ib`9MG;_(5v=cdhs5e|`|-B|Ces6ATJvHaXY zjcqNxM2+1HZGE*Q&FqZ*w8VThT^*ocL9nNW)yDEB2n4|2m~nBjalX#Ee!gr~X1U)} z+|k_8o!>sr#Za4HUY*}kQ|N7IxmX~R%UKxV`-)_h{k!YSr4d%*VzyFtN~2y z4EFX-vzfh)3+)7A*e~WLSi}j3ptQM)d`7v2xruohBP;-9Uo|A*I&4_n4KURjwb3mV z*XWp6I*j}DG7PNTGe0lpi}xEZB6oQKE-0>4E}a3{_cwzITj&&sSEJj6v|s+{+^3RU z+2&)yL9+s`4^WZ8aCelIrGiuKSTiE%gJwWNQhF9K`^?`KV?x_mQ(Z@c0>c=PQt>17I+()d|T=K91(_YYg|%dFdYx281H(m^Qt;1Ex;w zc2kYm&-V~X+!l{}l;WOTpD)!H6)L4}c+3@~wm!8b4VA_Sy=pqaJ;8zdUrR3PuLiJvn?l1VIL1p#1?s z%Bj(;?ZE@!KN3Qy{i=Ho9OxnTktSFn`USsU5(ERyXHoUe^CWJrxs|@wyQgWxmkkKa zmS~7l{H(Or|L89~-4ggxE96bdTUQthZGc`nrLm>4>0y#I6f_mQDr&tCMj2AKD+E(z zV_MumxdiEM@QlMu>mo+lz55dj_I!~=pVMLI8jZVP`4LK)LOvOBg?=jv)aE~3wuVQy zm1W)KR^jX<@l3l*a_7gC0f)nonahZ&-G~`*XQqcer8)=wgT^u<;kJOOOlSW@PhV{n zoX11w7S2j==fEnwUkyOSMBk#@Me7`>?eDW4wwsI|MgGlVXiB$;pA zq@-5`SE6U0XleuH3JU)$<4e`2q?R8E;xW1|po)3P50hcVXZeL6b!mrDRG6kB#)3vz zim_DjGP;Dmiurd5RKT8>sS?zyC0=@^JcO;;7^=(p5lXK_2vc~vD7L^aoE6lgE`x!? z3i|mv>Y}AFqtvP1`ja#)I{0{q`H2D()#;0ObGqafn2~ympGevxtV#51s~-T^S@LBU z(B`GkdH9vOY&jbe5^@P}y@;=EBaJ4T39-5Pz1jS@*(3%lcIXQqD4!b2-5-vWmu)0# znHescq78pDnAmZQAHPPjnFQ?$0;J?#<6jI#k@PTTU2_+ukx^z%%NSo1rDXUj)>j>37m?4?um`sX?Dn}NF%cik_EEO(y>5JjagQUbr1E*Y$|@;{ z0%)|>*P9@eh)s{xDC%{BNwBIgubuG*qloCl4ke)7O}k{9C_A&;sS)`(=~3xusuZsf zr3wH=F|<5(lmdQvSpy^CSTvODBV(lrheafIR=1zw9*nIq7#1@SV|0@)9uFV zomlTsMKC`j5bMd^;3h7cvcFf|;PEK>U>8ZW`rR;TzCbLz_F^A3FHam~WOb|Sx#eea zzHajv3_DT+`ANYF&`AAL0oy$@D+@$m>H zKS-65KHoH!ynhQ8z5V&rT)+8VdjCD}Coy7$Kfej~TlTql@cN3D!w3&_7baWLZ>$;J z&09e%&=5=zkBejBjh~UcnAjR^W(N zoyUKa!rcjp< zk=&#&@nWJVa;HAQfregU1-g(ZQ;;oE;( zVG@?CM5HoLATLchY*7Is3ms>HSk#8>aOv;#%+IhQNO1!gqRye}#M-U-<$erG?3|C6 z(g_?jwH`Mh*)|*FGwVJ9vgV$=Jn-)W2d{JVJ*d?#XtM`$Ik9QeGelt~;bia~*GmF< zy2$SMVDx4B;hoBujm|nOIKS^q5H8&|=-$3#mukAK$-N3^gVd~~XqwNKK}Uw%3^wqr zlN_<=$iEHHgSNWhr9UlZ&Vxh3+KJb?Ak>UAyU4}^7}Q;YzVojH@Eu8`r`@%V@G6e6 z6M~v6;4FOITD0f&U~ABf-|OHmNL2Nd!iYtnp8-FfwlCn5RYw?e*X_>f(= z2Kv=P+ST(3-!Wy#*v5Dw-P1+KRY1zuZ%+1Lm=s;0&ygKm7%c~$T?kSJIB~|cG_Ppr zrfzxPuzYA-&0tiHcRT@@MTxA+vgr>ozH1E2rf)oTr{k7!%%q`m_(8)Z)!y^@z+_s< z+G`a=W7{blk-y?|CK;DrS@Xpd!vn;?3xtph5T46uO%Cq!;Aj7Z)%~QNa9T3ZtfA4|<`8T8Vrv@4B?kikpg^BOuY9jGo0vB6p<%rBgDRP5 zQ$J&2q7m=m}6sFrfU0hxbN%A8s;K|esQ6dhPupj9dk`}9Ndm1iELyv8it1yk4iyC3T|-@`I_N6FLtQq#OlE> zoO&R}Euj$aTk@7%F{l)3(0PC^Gzqc9@RR?uip@QZCkT;c9dy(O$q9&PTG1m6YZ<>mVsnRxSb7)&c~QEZi^xgm>kyvP>9p0ZxUMAWePBf)J3K1{iaVYLln) z6iK#Dve{n{`wDzyr%;9L!&)8@Ej6$%EUw)B&QvJE+kD-L!?(09#xa~3Jp@ZoZ!Q@Y zF{c0_lJwNKRWeA!mbORgk^B2XF;o%w`+w{j^2&Izl~?0(4jusa5HKC%bnt=)%K;zt zFp*^#1zlJd@Ul59G$!S6Sno788e9WG(zD*ysMC`)jIJ1=ab9-x-8 ziZu)mIRu^2py#1#{o_}nLCQJRy#>fp?X3{)-G2UP?P~lzRH;A$E0^Dt;T+0lB7=z? z?|9CXx>2oyKzm@RtM+_a6k~B3v`2^HVfcP;YzhG%D{1b$@#Hm*DgAK@=rkue^O7rd z--5G7M;^A%!q&#le-czm_N`~5Iri!3|8f&2HloM9mfpvwfEyhrm65A=3rY!7{6`dG zR{4r|g@o&G@0E0o9J?rP?-8YiLw|H1rqCr9X9JbM>yP2^23Q1r0KXKn&#$%RhVfK0 z!W1x4m}EGYX~Qmwsn$H;L0)eXn>q;R{XxK4&m)GbM2^@OI5L55u|7>8xHJ5)+=?3& zrEANwoH_jjZ((k5NX*BHf{W-(?)CnDv0rJl4Z)=%>|VkDmJm6`ncpNq(RW3FUCc`ZDuxdBfcPMj#T~b2NIDMT|qi=DXj&ZOsgkw0e%Ghsi1ZvKZ2w{XW zow}w(>}cLSRY<)c-B}b`lelP|b4ll9-E4#^E(#m2XWZXa-Q7*7sZ3O@+?oh^IV^&@> zSTa^FM>M?>Rh$-|`aGCqPWWG;X6s(~unnrjDb3O|Mt!^XRb4d4&0HJMLCUp3DrSCk za)#(Z;ISK^pedhC}2s^b`4fs-oXTBV}>y^0Oyow)Hdmk2<)V=lLSrv zq60ZMHRY+I1G~3;P2P>5VsA>NP)QZMq*1pT@k$tIxhd01A4ZQD>%z)zWyV+$aaop4 z0n(vOdReZG0=<6rS?|Rwt@MQN%AIa5U!`P~0*h%rRn{8{9wl^@y(nobZ9m#<{^dKoQ4f*GEABfwhuG$>0Kqes3rxr4W+NWT@kc= z#^|14or`9E+F#9R8yd}6E-@F$cJHtTibOS#@^UJ(&|u2yLJzGwwxk)x?qOn^N_(rI zmIp*DQTKXKcq=KcB+YSx)_84~1t)aBM^}YrD1~aMMU3`L45P4hV_fT$FQ?>9K~CeW z=W%ZnfTq9c3ol43(0$L|@BS2ipwhnBOpH1FPvR03r!K^@A*+t<4A^-;LQPEvKV;mo zh+2QX;&kXf?dXqb4SDy5;0G$4+^{&*Tw`h~#1smNh=(haOz$Sr#<7tzG!O1O36Q^2 zBj#M4g*!O8GdYmb&RUSKYQ|hlI~Df;<6f^LHVIs9Y=burcE?h#W{gbFD$+&{OuW0$ z4WF3%xu>&dCDQuk`wniW_|nR<&eGG5U2=(ikElSd4DDJbWMUIF%gvX|bf;9_C+PJZ znfItRY*70%q{(8r~AAz$?4LY@QiNlV; z&K2Iva}e@MqaKQ$S>D^f`+uT+Dfwji5QZjG^oE}&56+dX>nKeiKy+-$ZA6-+ zdB^(zhw%b`M)_1FC4&k6gfj%T+Xeh6jOd2n@0R*86}83coBjn|5FwXTWf-3{X&qV8TjUus}6-Duqpcc1@SxnS?2ErO^S7>NcIWzG|jW90b*_? zXce-M>K)#sI+TMvb@ej|Jl`o?Udi3YMx(!Sr_$CX5JQojLQrte^a@EhG9^>>agjPt zF^wRdMUu_I#~Js{V&U=z0v^o8w2|)m6Eg(q*Sd1Gi;CC&tC%3CPhH$JzeduT8LKbI z8+Rt{WQ0RBX-jM@Z>%Ih&zV&ejgfE2IIk8{v7+PT(HXDrupZfvV~C-DvX}P(it**Hx8)knNxa}DkUPWs}yJaRovppp^z29>^!Iz@hlqYL%Nk^5;%xArK5NR{Kk zq@jR(fyY)W^V3pFii3vmR+_S*zJEq~gqSM8ekpSg>a53{=j84U{}ZS!Gi`(LKfm!~ zNHWu97U~q$$Kyyc47I~TFpq8CR6lKb9aG&X20U?xc9aRT z!i}j{)RE!Grz^$BJ%>{O&+gSxOPZrBmsU%jv%Jk&v(rQo|6U;vLDUA*(wawUfKyiq z)~3q68*f`lh3HHQrW*({Vn`Q}#4uK0Am2&gf1pY?>|sU9;6nL*Fm$UM<-9kPa;V>O z$Iz0zAd_y#fxNEVEFjol=(;=yByKLM9(%?oFbx@1NPaUtQibLzXH8=XveGGl>*nbI zPcvAEA~0!FICSaUdUTmYStj*#isWfy@zUJ5rP-y*F=jrE!b$J}58&hpKdApXK#Gg) zW8m;h{TN~Xw-w<3-DW}2*y%ql7lzf`y_A-azimw183zgd1rW)}fCzKWQ?Hk<0Z7`gKeQDhmvH5GU}!4BUGhq&|U5Fdaaru^U~G{VL*s>K*f1BWqCo z{X5)qu$+G=v=?_!n%waLCWDva*iOP;JltcW9Z--ULsK6_| z&wei038^hF^KB#C@$L!3C&AdBJ$eotyDWy=MBgF=Kj+#~wzgR@2XYBhYYB&?=Os=j zb)tlm>6H?LVkb{#x%G)d^{v|4W61$BptTDLrfiD_M1_36)s@VP+C%4+P-e19+JlUN zg0>0=O>MJf=Sp?sLbK#@^`zQb2tX$}S6MN$V&Fj8@zY(78?`Z!Meu;}4;H7nsjQqD zDmjeJfv3NscSk@g{*s8~WX4s|(!y5zvP5lIt#q8ic9umnsZ4CCwYzKhwpD1hJloxt znYN8F*lYe)v4Vzs(Yu~3@PW0pd&tC?|An-4z}8NNMDfO$ocBoFwgnsuC;F@gQk7M3 z1i*bp10Xwb9w0bJKRYI&e)Z%6(4pc(OK^MuOR|_+^+36ul>Ns?tC=|Q6_eLU{8X4G~P{Gs{8gwI4Of`^&!I9ZK1k(vrD`ARW#wM<%Y@2487GkK; zUTfi+cn`Az^jWf~v)X9V;>9~@1vqIhxY9JUAwWb@!~AezGlqH4$v#?XuE>-Bh_|S^ z^$*qFVIt!8i=xX2s>nsjt_D4bO-wGjPq>(ANaNm7h5}Cm>P#xAg+b;cmUbn`-xt!*|Uyzp3{sG(UkS2jAFQOK?Vlr85epkso zd2cxsGMzm-4Xd(TFck9u(V&=Oaz%Uy1>+JK^SaWJMbyBdK|*v%83dwgxrAc$@KOnf zRERxA72OLrGw0>QyLPA1l?O+bF&@Eiqm!K~;n_6{A%r0qvoKIb(tTk0ql_rmqJgy& zk>t44d1^#g9KZzmKp0c|(MQn!M$v{v1-J*AMrAK_QzL=3bBRZlu}Z3N#o|qT zSAiQ`=zb|fMu#5Z!M03sb_p>C`g;`UliktGI4d!jM4TO(n0fpl=J6qzcs0&*WiwN9 zeKrTCSbfvIFpZ+pKwjoIOy@!Pv(oyE%N!7-?8NMudTzG4nHssa(jAV@0dTn53F!Ey zU|Uz0c2ZP)pxp-9RhJPLzV+Rl4d$F1v^xI1*Nm`zJn>hN%z{X%LRHg71NIwW#iFOZ z)?b_!|3|r^dU-bPyJmFzMj!UlQ_yqYX;Na-HZp9aLt<@4qqK55jvw71nMxCH<2PQ6 zg)s57SkON@Nn)&H>fua5@8dagylWKq<^uJcYE$*WwkHEXxVfStj&k$dWa`<0rlei- zt1_NQ?^Ly)CWQlo8wj&%t9PKGlE&4)Wm*0_b zBMK3Fkfe1L(TqZ%j^Am)*Tm1C!n`{Cz1`c=k;7cTloj?fNbN9*TT@mfkdCH2#ehHw zn_sm7R`2iDzp)8IYSHVO5+S<`RKJM~@m3Z+q-${ff&3atw!Ec$(+UPxi?+zhZ_efm zM=bLsKI7pbwYQEwjz)osPIy6zrifs?!;fZ-fOS0H%gSzzyh;3Pk*YfS{N6I)`Gb>G z6LZDCW)Z9UCwfBPsarJ)wdsgh%^i;m6V!8^!igZDD8#jjuep(9%rD!9T(dy+kS zkY{~J(jASl7fk4j0X(@fO|gXHxfuIEF;+f#qImkQ0*RT#8;+EgG?D1Dz5>jRXW0&r zJSd=>{NKFH_%?vWy7a2>WxmFz$&!gmFivP}D&=P9+(iU8A^Q4Z{A;)RpW7SlHp zpX&Rm>Vk#BmI4BmrOnrsHL7BKQ@{nGOZwq8Q4+c;8omV=Xv~Z zC`Ku)8&zaDrMu5%OW>|lq=valc&X$14@uiD<?t#G1x@rj39FEI&}Iz%wPJ?c4tLJzc4hs_OP%f!~13%?GK5-Ap@ zxAQJw)U8mpZraO>L8gJKmy>i6#zEtpK_~FjE`Gv+?{1&*PP0|q^2Z2t<{XZ~g}k!$ zYBqKi^GE%VF{mJ*nyI{PuR-gwun-`PR+RG_WMY1awcvD|<*JNGXi&+T9m+{H9RJMI z%uI89`aKn_kPWmB*W0od1CDDW@a{uJm*Fq7AQU|uN+2ljqJM9*%e%N}`mTuzvafy_p9gv09hT)VKA$BA! z$na`Slh{esZU|^ir*y^*j*R28f-3Ep<3-PsE!)mXPntDGo&PH=^x>(=gaPvBkHqh_ zUhsba^8f!}Ar)f>Cu27$b4RED&GW15I3gM&ac>Z9F;NBI7Y0YjAxZim1^lH@fN0Kd zN^2^v9tKos$&_Ag7!R2)QFs!5L-mpyMa9N(ucNq%ih`N#dBtVV=~Pcpkgy&dpK9%V z>3rFAym7rY`FTHi;RVPV`aYa7z-**|DQ|-xi%qdSdki7OmY)5Wr)r~8Sl1-#m7;%mMR-{5J z`X}h$`&_#NK?vLpMFrf#86sUyXc#+IsbsKjnR{=S7|SlxJ9&z@QoF-M>EF~SE8E>E zJ7qij0SmlX8JQV7$6mn2fKRhShkS5x>OS{$}_+gCl75aB8a4=M|%>#{4@ z&66V`l$MX)H!5DUQ#zjsI5+;3OAK6<(d`Q=fSIt*k_tW}32~cFIDTb)?1y*oSa)kl ztyD)rqB;fYld?=Z1xKE>OAbCdZxVwyme>89SCrMoldapX`l}c!%1kp)=}QW&cITJL zKqy1o?x%$ip3d7L*^;ex_WPN;VTU3=+Egn7rmOIux)(8b1u|kl9WtkpnUU?+fI>oI zT!&v3wRMFEVpt`);cb}3hrrtD2%^VCM1(}P73~iEU@(GoW{$G*^y*F26Swjh{BsoV zHC>Lr^bBhpa+T!j#93OgZL{#UdACiLuSqItiy_f*IqpBkH0+2*k9SRV?=9I->Nscr zwpWn7%f!50>l?8_s}V0M89|a7Zsnp2uZDD0G-l>5p9}m%1^RL85N7JKgg1Xzm00an z818D}hc|Zgj+9;edk1qWQzIZwAv>2~LNM67?Z_x83XaMu}H`}@P5C3%#>yO!7C?a$f zoNs`07SW2KHvaM$WnGa=|GpB4S|q@7eW{$OVrAHILT`StEr|7akMdBDpj&!~3Wkf8 zt;snFN}TZ*yVGsvE*^t*rdW0aAG zKgu_F{G6e?o6T6Q-U(w_JCb{ouJjz%`!kr(K&)UaW){rD1yEZwcG{rKrhYX?`xIki zf0lc$YC?KiY5#>(-v!z8e%0&+o8gXesu1iA^Wz=s0a3%M5t>d)%(!EjOicHr&5$}e zBvm}_eDerC0$Y^0d}j_T$CE*!=NiMUbD6%sK-*}^8j;2TanEwe-YsWE0N`~kc?}wp zYY>6k4y_D^HtjVPrhSa70@hA6zkMtqcodiE_utaxf6e;xJSR8s(g`;w|;|lLNdB~Xu2$DuJvzUJhMoezWpcSH2lbeUKqQ0k!EjzuxL94 z%4n`U!#p!5-&Pdu0W-)>M`9OzjJ};WPsjzY84TWSA{p5LK1n=*SiC;GZ-i<3Mo6ic zU^@q}OkV=Nd|$}elizAD?}J2~3?hR*J(T~oxI0@=!bRj)$2Iw@tjlmLI4cJNgh z7@7@vPe(%p5#A`SIuyR2$f1FYIGTLgoZU_&!wYzkEJ$Ct4qRBB9rJ(D_KrceM$5Kf z?Xqp#wr$(CZSAsc+qSjKw#{9(-F41Ax8FOryCdGa9WUbhv)1=(#mbpEW{#0L{G%0l zvcSjFEbk7Q@(hj5SmxjaDPgeIw(2m z(6X)CGZ@8ikywGWZ9RU+h`U#=PR?qouCwz=idG8i$En<9H&~4kHmN(At>%%dCBlr1 zT_28mC>*8^H-9jkbm7I(rp4Iq;ViFXVKOnE#%{hCzg)UH?=^z0Lj1K~EI&Mvi#pt2 zui5ilt+pc$=fr62`C6W=O@c%97$Xyd)o!6Fp1Ay%iBB_7Byv%7xVjSgh^##~~zn|^!c_7|@*dYPa>ItZENB&tq+nUM$?^uDob zMhZ^0Lm|7Rj3`V6)BzC=-a$Z2WZJ@QIJ1#BCH4zm1S-*#%!7AFoK%!d7Ab7--}Oy1 z2Zm41{JL-vi>0@@w=I>w6UrTjM+lX766MfWMK%%=OU=r2`YCDBGeso7Tx)5JG@hdy zkDPPzSGPZPvK^0OHq0IKVUs0^(={jO9XhSUe8?&nqqta!2B#)DO{ZVlEow)-Tx!f{ zM7HT1I+vZ}l;3;dF`L|I&WBWd>O63%~D4m#8uD^`WN2lO5Q;&{tV&QIRd8$- zWf$DDN57<4oV)+ZKAU`L+*!AOo#E;Fv_N-%t2z3m7QZp9%YEw?&bgNG!AJG)+k!sq z^+R6Yb-)J@W*uSm%urNoo1Pe$?Qn1ot8}rnEgc39iH=R(7PL9fI;D5yDF(TbVNBZR zL>~NL)To3NhK>sb&T|IMdt$Ym{sV9MdzNS=?@L*NiIrM^vsYK&WTyHTUd>JrtQ(>Z z<}i94Za+LFHkwC`xVg`Vgn|p5E;}$cpgX5qi_bVmfhRc6oArcSkfz^}GUe*mfLA?# zG4tCOt@T>???W@uW*w9!7&TL5)}seHm7gFXOY z5#^z}xkp|6UuF@7m*iF?Mt$nNEfmYTqfk%x+4!{X#!%mgX{|I{_>N#?A7dkc-aTuL z&izoa!5?)+N~`KR?IAYaEK7de;@>I+u)wE?R9)_V)^@I|F)eL0_m!3%j6KbYFb{QU zS*4-#BW)bdjfxG?cJXWJ6&2Gl$mp8(^#CT?Hr;S;AH#hUbq(d7Y`kD!w?V^rqP|13 zINF6dMl@<*8-@XwjTuf~5GM{mM-d&W8)XhFa8Krek3tQ)-J)&A@8ZRH9To=!>U2AA zn4O?`eZ{nF*L+o;+(n%iIM~#;vA0W&13asPPlGM}{EM_3ErGf~@hkTzbK{}>&}b)} zk>!Zh}-r=;~L+`c9@CtSyBeHm*QHr*nK_0$~ zLs)Ta(Xl6X#wy+kh^WPy&KE13eE!?z*=Mu1UkE=!ci8GgZr28*Rf)98F-jrhBT%Ah zTH^Rd#5cCCL%JZ}WDTEa7pHiZ3(nJa1r+3R$OmN*&v6oV{!=k^0bHU7QANro%f&j7 zkv6RTF^A`#ga3LzD#0DKXZbN^`~6d><^DH{^nZ>x3!B(Gn@ign8T>46ApYkEB@;6n z6Wf2?m8wcC7wQP$0;A?IqY@eRJnZ?jXIAQ%r*YXZ82x;w#PBR}t>Ky+Dk$i}a;XGWV=`oxfaao|o)(!!^hSc= zV&%Jt&u~0M&9}}ju0?V%%1KilC>eFtAZ4%)4JtVU84a~qXn2<3S8uZZ9~YOpPo znf>x02b5O*lpwAd3a4*QZGVM_677Y(krmNa!>Lbotm=|MQ_DIkgaQNK{O%x)7?LS!q_ z20kuN25dEne~0kyqZl1o^2%tLXTj!IhX8#mf^;yL1p41yh$WZ>;F7``B1PH#J1N=v z($UKMb2?q&0RRO5f1c3)Xkb*UZ8%|>p?p`%vR>ie2Ct40xdB^4E>+s(Lf?ffjW9oAu7v3z?rFdp_LVX=J@s8Mg5gC z9*zb)Br*5&3E&Ig7YW%UOO%Rt#iUp$N;+e!Mrx#vW3kl&qa`p(ORSiZgH_U%?~2F;oY(F}&xMJ{G%{I~ErVt}9TR(a={~;e-kQ#^DP%mN zFR2EvXB(O$YeK`s$BlXIp64Ub(q3y>n$B?FiChIo2e@Ff{)#gdit@tQAR+VzlcTDY zK7+n0(35Z~KI7^lFNe2EtK?xzK+kuzNTV}FD1Hhfg?7umfS%b{{g!tHEoMa6XuA&F z@=}o;GJt~Ux=IlqruHN?#(G&Y1EXSnZnKzwjH1jPh^G18lhA-zXJ#H#E@7%Cld;@@ zfyjOIjesffLd#QL>&R8-9M7(w)aa@j{jcqDBY-M0dO8D$43*$&A_mjE^3YL4d7djm zR81FlT=Ge^leW33BG`k`*0B5+0qLKf2pcU+O%7Ghpu`DB9cE6|AugFn3KQaIVx<6% zG-euv)ARW8TkRbRlSFf6JiO8gPL!8K5m+i?)4pJ#onzv!saRE6rDi&vjn3mer-v$$fWkeL}6>a1zN2w*k&wgDW``Nn7kmY4nknV|+eV9d6e^o75 z%*GvODdg)dd#BfV6{kz`duR1lujtzXqc;n~;nT&CiFH+(3A8g}yPTWx)R%&Yb!Cma zMpta5S^029L9#SlCZ|FF?bD}6ho_ZspN_CJXdRih$!30FthJ!U}UAYA?ZaTG5taqA3=lRGaYXJQ2R33JGAsISF7o4$Lc~PJhh4Zr(F3p=c})Pph>GouqtVdVPgx?5)t|Sp_U?Xv zX!w6q?7eH1)c<(GU#F(D@kxPmSobURH_k$5Vv{G~+x2^1lzR9IhI8ZY0RjGBTLQ&<-aXHY0+3#Lmd!IE5nVKVQkJxiessoQ(7V#XtjjLe)CKQ1?yNHyp2 zfhC$NUT?pM>m0<1o!*t50ZCuQD6F`7%Hz7u%MrZSC}ZelVhB|cBV^P<%MvZ3SyaT! z5LpZqlYL1Je_c}h+?m+~{JsgX514l&$UW(1fCtN%fY)e>O~??a zjpmp@3JmjYC=Dm|M3obqrs!xSd5SQ+eIP!R7II^!_y(@AOW<37sE~jeRo(ylguy!a z`1ik8=iZRdtk@s?EcdhX%;A5dI=dQJTNwX<=6@NN{c|c=#=zae#>GbXhiNddHTqZU zi}>eT8H@jLLH-|atp9I6`EC^*J0vk=o`b6HRqDe+czR$=e2LFrYXEyx{>X-81aZKn z5aRgmtL|iDz+P+@HPuDMa3IPmXHI%AFh1mnaVg>-0AB*%(nHzxIAS(R)(A@p*OS?6 zUf1bNW~RI!k5@c?zMIkcK*ui7!|?sDyZ;}1xDl%Jm@jnVn<}%l?;q4 z#*^aEdIudBTeIn&tE2V<_0{P_8s`;(T4?p;Cfp;B#A+OM<&8 z(d2>nXd!<+qen@M?CF;1zw(yKMNZu_R(wd0Q8`r*MHCx$hZs54nvI+#1PxKf{P`v& zIOxcs!LDE%$xUiA{;ha7w|`Ft4Ky{(7(~*-0?IlDuZg2?-t<-X@kLES{gzh%(-r-wMfh*~J{IM}7ZSeB8z~z<@hpI~QTpR6B{BgQSKRUo*}pmFgNJ z{0RHN$?*!CJpdStv!oMvsEagu8w6o!Ezs4+5*GO z&C73P5~0kZQR-RK3^!C&_Z(M`Iu8FO#&~$~qR%N1rVFA!iz68G;bS<>cXA$#6z9~R zD-y-w)~bSGW1XV#96ORnEK{b@`5tNzd4dEwLP_S$$TuBjzb$TPt(;jp9RwHOd|ZHw zVbMulf!D0p*K}4$WM_$1eswU<3SOh((0XnO-g%d&#Gp4DWW*kTuN5Ayok`_!HUCOk z7e4s{)!gQstz!KIiht@IXq5#?z2nf*AT5U=bEwd~ zmGd+wTI4vx<kyVNEyz>r}zLVjubK=kj~VE_&4 z%o`8@;2IPF!1n)Cc98#vLHhq|YEVA6L=r&$B5h?kx3sEwv|ZGlEH*vYk^h)WLM5;q zQlxe_NRXa~$lR(+&Fcxh&eyLde*xf4ayLz2#;-0xH{SbMhuygIoXuqRcVwn^=U0;b z%Amm{dKwGDfHWrQ-=uW4zY!5f(uWH2jRpRdppu~0ArM8#-5`oN&S2e<9QQ?YRR@|(3Eai)r>@T%7{_!dQ z@TEtv9rH7ILm;VHgiERwCqw29e4KR&)^l}-2VY;|bXQ1e(u?`1!AD$+Sr>wBrTtjz zE?0Sr?g`t^U7+*I(MzR`KLs4*R6dLU)=#sHxN(DlrfUCi$L#a|E7CI^A%Fa(8m$=V z)*cLvijne_z0;3?-Atc9NgSim79s+)LbKT?1J!E3(T5XK{q0f)ok+>Iv1hyBt7sFB ziL=yiPR7C?cDzWCd1fk}a4(y^t9?2c?=o(l%z%%lcL?BC7%TmlU&!~dvQCvpCC~KK1jnFHrt798NoBF~J zrH7!UJ@ADUaloQMepB?UP?TN36;q;tF0Ri>aC;D=OBl75yk?mhRaOz%vxFVP$IPK- z=A~mMjmBH>jtLr5QH13?K!};xbMGOJUPu`r)Sq&!W)CnNH^Ibhtbtw5DuP;SkcUz0 zJ&zdtb%54b5-byQ_{`>HV6ZG?TzA+4Q$~}i-cL#VzfwV$^mzbyKUMM-8~{M=|HL`? zkBe=$s+HVN!1E|+TBat8DF6eDfLs7++y-+inu zfWrm56&3<7#LO|2ju|fYA`Fv&nL>GH;=qL4_kgEE?@;_JD`^bXQn!hr;4DKmU?wts z1RKa}r?DyVchWE(4TIDCRI%HIzyL+^3cK|lIuHtj%PvVo!#L8l<`8m&5_7|LZnbD6 zM0-HzcmddE94RBfFpa|^QD--XqExh_ss0a6q4i(kW#{(%AnNpf4rHOY=)``+E2j|i zLubkYE9kGH>?3FzsQ&%PV($ztDpO9eoST4b1}Cc$qQgz6w=Eihe0AQ=-JlFIDcN)- zY>}R%+Tgiy$Iyp!EN0G!oIPYFXqi(?ZlMj*T2cv+2;v#hf@7p3S;2hg38gBBks~ga zA%E#$y@`bLI?k3EKGn}hs`x>dl|h_Z%aD(61;t(;@u(K-RBRTqJO5q*7`sdpr4bit z2I4OsCC-l7>H|zpmraq_m^S7Gae8YVWx+!W&j9hWv_Dcfjp&OEp=!{0wDbrG-Z^_) z?MjSI<4G7C3+v3=C9NJIx+X9PuZk9&^M2!EH0!@JOiOSXp@Olu#J)B^B{p0e`U$gj z+a=ar3wUxMC{hP323n*0HXIm>%>_Vc{~@+bC@pNj z@DT=QNidG8*=RdZpr%9!LWsOBRLIc3!20lNduWf?k!JLxKxBh~2`5_JqjW|8#C-w) z<*^o;I+aJfl|rde;5bH(($(1g^I}};naLKeJpY7?-y{p*?_*y6GK-r*jJfs5qs@yO z$Ohcz$65q#s(|pI!e7DZg`mLcqKP6Roz1Ho+q6bxqfk_hYoUcR{|wwBZBGT<=m|Mb zAmS;P53Qs~91|OWT{0Mq|82<${|58#wW%iZt|sjVwHBZN0LcFL&*Xm=9@YN|IxjFcN;U>F?s}{qe2wKG{}&SidWzZd?Po4Pji>4w~Id{oEkjTm7EFSBZAJ1vZ18U)8SLw|tyO9>3#@pP^p( z;n@xX1q@rk+fKw9Bb` ztM`dHdn=EOID2cG=FU3&hQI$|2+0D~{p3JyExb!W!EX6scq3;Z*pY?7LxU&c@o@)* zI70%%42Ap3{0e_@WA_mWU=I}j^41+O5}+PH`>4zfd>gxP_$3GaVhErIxjVtxQ*a;w z^Oo{Nwftmjxss<-ZD9JGQTg-ZH<~jV`6M@)T%6wOfy3$s%1HkGc4R9(EopL7Z z7@Jx@1r|x~*CE?D8k9y*6M9MFY~qwvsiUM6A~HP7$>sYEcml> zHdubXNXupOc&Ox~>e{%L+9V7Sk+ErLYLo62$=kX!ZK3{~@k@4iobt1P`NNe83kQBd zKJrA91+Q@{rN1Gu*fn`ibXY)Ucr0034~J~sltd@7fP}hL$9l!)$k8;Qb73GlQKQW^ zs?PWjoMM92$){>3;&Suk(0T>8j=NNmQ3D7nkrec!@TM`4K8UbB|;{ej2i2OsdDLEdBVyB%r#>o zKek%BgNNP$%aNFXh+(*&scB2KUHx^Dy2q;db)y6tbvevtaABh74X47Y}j$mE!35ZY*x?gE6(!^1{uwHD%=ti;1T z!`m+ZsW6NakxKvQw#`12RpVVoXH4}_SD^E`@`Fmyo{(CsReUNP^Of5qf4f~wARot7 z#5&Y!&K%Yo8SVkd-`*9d!g>18esSUrjQ@0k%F}Y^jXbtUlMj1zG!YL(fSj`y>A)p; zMQu5KY4N|_?F9WnjaZ|9SnP4kN83$`fGe-d%Q{}gS4HNBj)b);6Qwm7)|lh?dqXUW zl{FKmG6HqRCnoe&F*W6c{33&XGu^m-4D|7dO#bf1WJok+H3(0`96ICH zp~IXor`Yv+ccrIfUQ9{pkV)NBuy|tF@Y!n-ZQ-~d52s(UXewpA+6}^Dh54&ZB@zSz>>oi37F%}w#yluTyLN2PDe65 zNQOV&?FX~x5UWD*j=VrZmuaHw80)oaDH+R(<0P>L z_XI&_dO3)qIrgNhmC|p2a9C^q8D80>+nat~?>cr6r#dj$QwpzjS25o{50gaUT;Fmd z5@)7Cjau}C(-cIc8|m(oAj&8cb(8o_c^Se6p6h{zav|02^KlIso1_x5sulo`$1BqRiO3#oiR z11q>^O}N;$ooRRTS7&?mGMZ<5wem$poUTB$h3M=+(dhLz&K~OYQy!uU_OXa)zKHig zS(y1i8S-u_td-M%Bq-YYn~E-c=4&EM|kE+@>_nSB_u#CPqx-Uws_ z#W=Q>ealA!F1gbJ3hAbD`LvUR153IZ3h`o<#9nb1yF-GPSi)HjoFyo?npkRUY3RZb zA!~Hd=L%7J6L6)l)4`|waW{F{#h?BMK1xM59%pE|n0zZ-D&8NA5&2#hlxe)RP9UI_{HEe3kqI6i`C209M%?bvf+7WE) zfnax}=eIgQh7LEb>~*=sHS~Z&Xyk&|@)EN=fTz1G_FmN1eV9SY@ZNZD$ms}2z)&PCe>p_ThpB?09#R}o>4CaO#kPJ^gI zH#oGkn1$@E$TUpJbhV|PcBKZk`azqcqpQ_5SsMCU%zfH}DnZKPWEVO;E1lkfb-w}g z2_`A^k74p#{Id1Bes)825=u@U1Y>Dsk#BO$LfGxQGffZCfG^yk9k5mn;y_PwjX<>j zL1L69X!R0oPc;`Y`Tm)NpSadoE>8KR~y)8X9Gl*#1{`Y{g7lb=o7oV=Z z^BcWDD-)F^X1_nBLL8{kcC2nZH9gwxQcSHV!9++kQ|rUBb!~x>~ zut>z_-Jx2|@~u8nJ-8oKMHXYSp>?;KvZ{v4@*VNGz~&A;#YSh5IYMesW1-#~m?}v# z6}FO*zBB&FL`UH?xuEHH!Eog;N}L3HL{l)AWosr*3f9}D-4r2~w~;}LD(nK`x@ zBaYs6*E=H{>2nfu3d?Njp9gidNU{hrxLd|TXF$=ON2xBNr8_@O+lwNcv0BbKquSkxBZ zN131hYr*@lE%&+1SE%>?aJsPLVdg4OWd8o*&vbp%b^V&{}9;;l27Sl}i&$R^B9vY$!VL((ZXas!qdgJoDH?%$lY6WoEeWb z@nE++_Q>X$)S0-~u4UsZAeeKS#AaL2Siq2gd&#+sx_NMH-Z?OpuFuOikZ822IfvPZ zIo0Csu(Q}A9DK@DvNDb%w7H`HRG%~AQr%fSi96ykgf*f0BOA0Pu+V6NOKa&&$x7>+ z(Q|Hb@@5yZnRP_*JuhS=7Nf4q$!%P$wbZ2-Y*S~CpH-8&}_{{=w&e zo#c0N#zKn1;M$CIhtHaVpRCYGE=j}dL#DNq072gv^z(4uVW9UF(tTx0Hej6d7WxKG z7;oTRG0cZxfovGxD$1}C9n^zJ_RGS+>Ukn^vE0F~Ez5pHfPK7;v!o6(m@gpHR^)3b zhlgsPOQ`a`Nkai8tVlBjHODCvX>xrbuMN8ta!my;jslDQi3bjS(`6C zv(TtYTm~riKpz&0i>(C6NB@|<`a#06AebY-vmg_UPZ_T zzNiSrGMr>%tSv_j$asA7)MC=y9JPdWi|7I0?)|AP89IS>e@O5q0RHg=W>ab}O=eV#EEGez1qmq3J0b;{+$$sChn!75%Ej#-+l< zSS9WF@^W>~NgSHWy!imID_74E#W2fe-pcg3UbGY+hKS5*8K+j!DRk?+ZBf2Xweb&Y zU_+j4l(ZcLL!+md$Q;N(1$X{J`yzx(TTK?JtIF*l5Nmv*b(xHKPh=vFug8G``>~ zJoL7%j227N!a#D8re3tJiHc}7WNs9&1ce;>$$;+JP#v5eEX}dtMDvP9j<9r3Jk)wH z$nv|#0zfriKlw$jS$B*1zaGY>8i{+D>fHSp^;)T^JZhBoh`S_L6a@rN%LA1`MHn>% zx$3>?hDZ@)HzqiJte9PVpI1oA)vQYW`2~V1A<%OMZa5Vjhbxt`Gvyd&7DR6nOX3$` z6um(BCE(tIB^hPWGstnW@Q_@J=~TKjHR`llb^2Q6K*9%P2;d$pgjMKK2GCpf1nJ*6 zqV*UtAKRp(*nhb5wdn!z^Pw`jR0&3%!5gAL3!E@0)glk>VN+4y8sTUh)%Dz8%0*yP zM?2whr*cLA1d%)m=C($`T?(Elcc51&f4p z1&M}pnZ;!_sui|9aR+_@F7JwLhNr5x)6F=!3C(Q@iCFa*g4G4H8ZNX|USRwDW!Gi6 zHKo^NjqW5H3+lV`G-) zFRse*AZpT!_KI;^Qj?Y@{4v_9(O|{}$4_v@de0Cs!Ias%YtYxQ?98dOBAi+<&jIR| z@R!vC@`NJzWfxz6tgdU<*t9Kxv`r#5lbCyruntCj2q#fVA^4dl@;uhn&?qQYp?+0j z2`c062a;hhUz`u?gq%-OdGc2*Khq14j@1pMj_sNjxU-`tz5(D>*B=`+h$|^Ln`S(n zPWwPw-aU1Vqo(erB>i1Z!@2kHjoRQd1b~mIY6o%jcs#sl|8LGLM7GX;m}5c0=kFjQ z3jacp6EY3Xh}K4P1qpb4z~7+Hk9GK0xr1i_*koEDxV?Hwq^a)u2n8Q8$e%p6BY5Bk zvF3fbt5{$Ch_B~_B#Kk6W{rtAb1vdhU59`Z6%iO*MkvPkZTku1F9gFEf@ps_ch3^FZi#`_$eKVbZ`U2 zXTh|7XKli+2QQ8JA=&gL-+_KyKgr}+6%o<>~6NM97 zz1aoEX+S#7dPq7IvFn8(VWASQ*`c$Vi!wgF=^?b2cHhcECuN>=nuSiD1|>;kr&dRE zfsF};MLLs`rmIRLG>2<&#Amt!d>Y4ePWlRs3@nn?(520mQT3Xq;XL`OCT($T zT4YIlV`-GOW-EAUI@eKTi*7pFOGPPZrUh$<4G(U0Ig3Gt>iX#1UA^askqbPnWr)fz z7D^rxM!>D@JcYGgHY)j42?*`yCd>Xw)5oN&Lr)P|2alXmtqnXCX^%D!Eko2aZJt#Jc;jShhpfX=%Blw?NkT*7XIpo-D>hu{HVfC~ZE#e| z;@ydLe6*>H18Aa)s$Y~In5b=M5Lx`6>Xhdx<+BYMO^RrM)@3i~vC3UBuqe5s zd!?(&`36!Vk~LC+*0mncZ2skHO0gi*5^AIknzV`PtLp?P5sV#EnSYASPe6{(^lg+l z6bN#RDIX`7dqOM5jRqrg_|s5f0~PSsl{Wh0ox9(^g;M9#W-mQN_if{s+}E5)o+`+c z3Qxf$JX>pznN`ZmZ)%S$iy0+Z0i|*Ta{F7S2EDJHv|6DTGz~eky_vOW&!OJUj9D?D>TzmT5%^7P~q3UJ|`kr87Z13n^p2hU|xy?#pQ&-1FQoUO! z#Pbq)nyZ2)+!Mb+)4Q%ffA@{h_)neCm3c+{J((#xkNgDK;YB4^948cs_;^PzB)>ss zhyaw>p(&OLPlfjmiE+x+aW4n2t8YHuC1%9_D_pY#ShC+_Og~o2IDo#L_Wl6sJYdCK>qUcsk5-hY$5& zk1AV>rV#o_`UYQUGsbz<@Rh0Jfu*VkrVpJrG z3){E@yQWP_2%n0#z#fZX*e=%zXCHV#;8=zqt5};`jrYi%$mqCk_T3Ta_5yHS7r7#b zw}sD@8~9NY`GGLvXu?iuKw@-TIBny&c@qbkdh*v~#Jj1>@9N54Iv7qmSV!p>n)cda zp4A!DigM6T2XrvVt}F5Ler6W)k~)7Vp67Os;5ROzIhp;9s(Bw~tXnqtvO6&rm^fy* z;Cw1O5%(7Xn9?DD(%_J?oS1S!*93%F-Yr>EO>dvo4$;S^M1+*Bpq~`5k$3?5E7-s1 zzJT)3T+bf{>*R;Q694bql>V9VY5( zkFKi8B*ukt@AST8;2KC_%O3u>EA6?;)pJLk6inUrQ>ohZx|NTR?z21J;rlZZ4Hs`W z$-G)(tR5wr4!{C@_|zYh&GyLw9NZEUl{Youf`iiyz~Lyhf{kqs4VUB0WR%vP!>S~Y zM!RV$&Z7-koeC#=ix)T(p}aaR3f-w{t~2vmW;|~D^QR7_6cSVuI`@V%I`#%#a7m-| zk_zGZ1B!gKkf8df@+065QPr*5KtV%AT^(E zDS$#1(w!H|YL|9u%bSE6u*{rV^AlZ9yE=Ugyof^Has}S}quHHd1+{9Fea(1+7UEuU zr7DB%K(Z#R1dR232pTb80*&D(`wjKY8-DQ^0@gs`QT6^kYM58>m*j>`iS%X1=(2lI zyiQ1?2lRH;sPw!82sHtmb|g~_nxAOb{8q`1Ei^xlLD|p+c=6gv=i!sr^aZaMRu>9f zM11+K8k$Ndm|OX?k@ODXmfEBz$T3EmrI=QKK*|?wqc|bExu$i3^k608z8JbuR2@ei zPPY{G%+9c+eG>5-gF1_q%*wFy`xnRPT^PlyT`T+*wtZui<%4j@(T&aqYjFUln<3Dl zK)A>LT7yK=uf9|hx%-anIur%&QqX{nCj1G#&)$RL^lmyroR^F9b9Xb!8L>#fcz$4OzI-D&;<8(KGz)k5^^V%eG7Y)SGO`TYDHq68QGJQ+7q%YtMj zQt!dW6A5UeJf=ZZKm7?7)E+IcO>FKO>C0>UOGv9x(cn#awMu!m3L(W9Y--#m=~SO= z0WrM;&miO6|8yeP&4kioW%NOevAS-ba*v0x`V*=o5tHj1_utQV>&rky(2r7M?Ppf} ze~Aq<{+CLNPRP#I*2Ku!#8|}IdY=lzOj7M(^=n{wu4aEH zId46gga1J ztt^{!_NfGyASPlFeRlNFDHYQ(Mr_457YXL0fPo5o4bJ-vnH0%^8Qny#K^x#*rp7Im z4F+oLYq|5W6ZWlfpI8*l*lk$`dBom5%T+e_H_eTg@h0kfUYWAwiyb!76x<=F?#|F# z_x)&&@dm_BDV&`W_IKTR4{F~aXp0``7EL;k4j+uIsX#l&CSzMDu`6xAwSx<8h~ZF< zI?adUqP37Eg?GR+4APOn!?P|X78AZ$$<{qMs6;Ck14pPGHAT&B$lc9-;iX>k>R<_V=*H81{b-4-n&xy&(Ndk%_ykEI-|P>X^7DgIrcFI_(-w`Fea}z{C);bM8Z`N|%BnQQ`9wl^T7sUSU>4Ekdxmb85YMCk z;b@4Ox5S}X|1}KT*PEQ z6c=l#&s>^fbAUD`=5cBYwN>&6vR=xRCPyBKGG6WusWf7u{i|S&fOJ4b+sX*iSxlGX z9L)2HURcNzjuYk$EGjcMA1BXkLQ?acUi0KV;tO~?t5}Dm3@z=?I>q`XgAFZWv3aeS z15)pQ>HL|WUrcEI#5?g%yi5JB)U*H3c>foa|DS?g$;uA#M}+~@1f`W%+}wjFQg-cG zQoVZ9-_a=L?yP2MY{3(`E?O*Cy$zPI+0+@jTaK>OdgNT}QRLhqOQ$F3rYpfzhe9F6 zo9r&Wp}U7(0;hJLP}5dUJBlR+N?}LK?FzTrsVgL{Y&hHdHQsv=YO29R?X@r4FmBMV z7Ir}mv#s4VddI+mkZPpxNlC!-jzx#@*IGU#WHAf$=&aw7@F1?D1JUe1rsvrA>Um%3 z#AfJHCh3QTkXPtqPc!_@(ys%`;qYQTmNhZ0>dCuT>PWd*!W#q*!T^x>HA`DNc|9Rq!h63 ze&plEwMH64*rM}J5zFMSEcW$Yfr zl4gK}%#eAJ)AyliQ6zWlxE(MT-Li2Rm(ebD`W<(TsQalFao@o`DV2d&@0D1G4~wCC!v zNf7IE*R~M7a61MW!)vm?V4Gyd_mryhbjS6U>+Lfj}4OzXk81fr=eJVDR9kjlE8Lsp;x9+kdx}M)gr~7o7g&FZq2Eg2(50An~CtD%XC? zoWpR7#D;mJM$X{#FWv2vOGgM}KMSni4@fuin7QZ9Pwk(I)^(+tO)5(1OYWn7_Q9h z4+fMQ|XYh`r@=yC(9U#8OvW2Q6%eVXZxmEXpxK#$ZH%) ziiz3XO$e%2couY1GJ{Ni%)5xNXOLTzDo#=GM%(~Q*)KD4(JHlL$;?qM^jlIwQ=>i+Be>_jSF8r6wY?R86_?gf zM%Q|kVI$fC=H{Aj+|Ke;Q_%CG{Q+obn z;`=XuFN$e+{YQjx=0r_F17QZE7=3Tqbb_Z<)7wUBim>w=1(eU zv7(yENn8SOlyy&aebm1qoyQ8I7t%j*;+#r*Gyf!yO>b1(8hA2LKY6(y<#HC z7!T&N$BYSwYJ{M_lY{jR*iZ@Cb8+-7K>GW+CtGi{KBoIQtJKUtu8AxND0g)zW662- z_n%R}{P;3n5`*~;=ty&#Z?M0}BZJO8y8687^8b!~O`+-zO6?y`kbYV2*%3aWK>bE@ zPz!H^F>k#=z_1ajzpJ$fQ!#ZDWA4UGMHjA;poDi00}KC&gwB-O2<}alxf7w=PlM8E5Y4E5_I;;)XGg zE7qSbXpGXu;-eOgyE3@g<9fES4LkBNm9AWu-MjdL6$W{!fjYAwX)S35XD31FH7yPi zshf0}vzkRsnVn!=w?9aYoq-1hHOyhzU~e8tqp7`!$N5U}!n(xz9O6^58P$+zN=^6L zL4)95;%kz!+>d5~DmZz<79!ZIZ%J!6BCMU7=1n~Tl78WWPVNsmhZ{=8jL_g#R%={$ z2sRRdXkgBXi5)VR^?EC_hmJ9iTpE0O!ps%5;u$aH8r*ZNQdKtm5``=iT zfVoTCg+ym$1`%+n9S8^c_M8A{*z-yDhjFOly=zM+Y#YFzx_QTP{2+EcBHSy3wMX zH6U86E_gk(sc`TVHIxR^;NecWnkZ9?x@&-J9C9)eVL>%*m?L*c3`t= zpr%%RgIuY$-bJxF&X28vt0l@1SB$84*0pf>{>=QnQG(spd4;#9>>#TDDOZQHEawr$(ov2EKn|Mbk?eO=v0GjqBR_qX1~ zXWc6lzA2OeWs{w4tZ9?w5#<9uBbTPs11zbj@Nx~)70Q1t2;pRKy4rB*o7CvCs4pI=>iP5je$jSkIYIJYw;YRzn)n`u|tsw7d#1gC7@CD3Y=JyXADgfQV* zv5}3!J%F4$w{aq-Hg{;E3j~?c$@H@5>4{F^YKcDpL-xLCa6M)=#@=7xUyT+2mnqozjbu zGhFCc3`(-h{6{BLo?YU_Is(i-EfE7}Ld5Jd%6oZgIF0pr*Pu;(S8}w>4TEI@cPHoQH}dV3w`pb(=Cmre{d640*H?? z5;$=%t^;zlBvz`p05*F4lqQ{WM$ZjAZ`1qbc@}$k&y<{zv?g9^N~v4gWw}0|Wegq< z1!5cHd-~_qvB4Q ztw^Uo?F;BEAV0h75u=`rj}W+Sc;OQMBTa!2EsdoV$-s;PSRi+uH;s@p*j6&wQP^wP zn5)0NOM0mdbf)5JgTk;Fdv7u_B7RW*VF=w{$R=OzU=?*S6{oJU>z}e5R|&wS8JWmj zHQgtbA=!A37k$L8o?3}q#gDyaQ!1a5t<>%w8lc(iYLdLgT%j36Zgz#E8B9i3_-jJ* z_Nzp_MG?q<9<5Iy7}^{gqzwO|47h&Y%v3(oRF1J$bAmse9q(-3vVT5Rw2W=I1rAFJ`RI4Y@c2&XbtA9v%&@{ z6iHrVn=aak#a&KvNY6=c8$Unad2I4qi}z^t((b(&i7Xmg2>d&=pAV~GoS>2%{?^XT zhgmk}^eKAjswv%BjiYd3d7C2bC2EWSd2WDRBMDsN4p&fG4!zt8W(g1M!2_%j{$h@o zRQ8}+=oeA~znsV4l63aR_DYH}4_H&6<_vn&se@dqjR*4Q?w373)|7x7aMe*6Zz~t# z!6at_hn+wCfwZ%M8oA41?)$Kv-t<0*|EajKY{Xwjq!?CUF{Bt`EJt80G%|mEL)juo z|0Z)FcLr0BY`8Z1sT{mudh)(O#<83?^6Z`7xr)3wRnB53y|xzHF?j5-#~A5T`Z^I_-W!*g2?w|+ODAT(Q*4wQ^&eGM%ehqm>j%E|Lhb;_1$ftpx2)~Y;(2GhZ*rPYnF2WB=1P8e!z{z@r-AFIh0y;4dSjPS zY7NkDgdCgK4p_JdDd(^aT@5~4Agc}ccr5wAe!qiqOEGImk5Q>XiRc}2duYRoa$wpA zxgT)Lc{=pnELRXdi6u@Dh_>g|iHCe;@btUYTz5-v@|f}g`u8d^$b=I{?Yq+<^xf&; z{a;myB1Se=MvnGw|Lo}fcdeMIAZ0Vhi{xd|{!*5nFv|~1Bg-wm%j+f7@JlH7SF)_2 zKL|hnu$Z!5et(oDnICL-APxq?FFB>%9>i@C;QI6!UMQ=cI1>}&qfWPa3!jh27udg2 z`wAg@@+kD0Nrz!fI1~&jd&7gV1}RRDqm!AL7=a&|AfOJIhhbe9!c`bb>&nSzTPJ#= zi|@`uPc0bqmI>rF9(iP^xm%~gey114XshtjFCK=jXdRpSq5&N{u1=p}d}l~>9>g`r zZ+u#=*8R}fYd^9=tQpUL!ecu-6dg<|VMgTX9Kof=T|a9#YaR3&G_U53^N&=wR)*m~ zVIjr|-^&NUt@)g7ySQ$TPFKOY*)nq{keuuzNZp%`@_iab4M0SDgM{b9jv716Y0Cv+ zj&{tDu5TCJTS|L_CH!PIsENvbzqpk|X4fV^?Le|sq31RbP5iKy-E#@#oGrWoUA1iE zTIz>ZC6ydc9oF3x#1>1{&~177GMS?LJ#vwB#(W;UKw2#%t^j^^PtTtd7pnxu z!deP2cLDA^N_e#QXF+%<00iiP^4M-aMo{f^<<>t2UVe{yqSy)}M`6?F7;sQnllo2| z%dR{Shp4?4OdHv{UL6h&gwLGtOORW#20&&jY98S&H{a}X+TO1~l5h5#iW0_unynM! zP(AGTus!yBMT6siVz!oM*5CbSdlRGI|KR2+INF<8oBX%M7Al`9psJvKZFKx#N&bsu zmj9^tlLb-NiYl7FKpZ42Xc`7Qvhsq|19~igx>L>c#N-RQ+hwrYsi zY@HFFU6m2W89ajbAx17qhx$wn}AW zA;(M&?r!Myg%IdqH4giLL;sj7VkzPtUW4kC5&5uJ=Oi>rHM9kK_x>= z4H>p+)We74`+ykEP`j!grukh5WnpXexP1md1NR$k9>vI7u9vt+DRa{lO-~6|dNc@A25D%bH2eSc0t22APgbAi(@u6$J^B&$;E<2m-1%KkwEv+Z@-k|1Q0ql}f_!=meiG!Cl zlZ|&j*5%PF4geKDj?xGAN*~I~)eAko3u-eZ&>P$W?ynivcMU(+H6y(;;klLL~_L>VN9B{66t^j}*L_$8ZbA z4sayr^Ct&@8ZLw|;^x_~XF&Zy_V~N`7QrRB@BKHjEb&tSj>)bpv^S@Cx_|l=;Da!i zFV#;$d^Gt9r=|7=U%YUXO!QTZOyq`#{o#1O3vtsDN`MyffkK-rYNs)XYmMBll+ps9 zUe(JkzFde)-c{efk3z3V4M-@*6+Q_j7gS}&Y!QvfU+x=XfOa13 z`FR38tLI%nl(4cxx@qcxee)P#lGuD7MMuh6IpXslLjdk+7<`}a(75y+8ioINK1TA} z+erPx+X$N3|M&Fk+v)s&ccUorj{y0}Vz)c*q(Pp*@7lz9T7U>jfhMYfjveO5AC^!i z%C*xUeiqZeuo}AEr?UgGt4zCN$D8pY7otYSTZ&Cl*wm1f`f->(H2M0E&10Z(Ju&~F zJ8YDeoE<{i5hH#8F)iXQOx0+l1!WNvrf8`sV?V}55DqnC2R#gWwRO{alEs^LC02_z z;7_@)&EvGK~ zb6v!bjxeRQh5)LEDmmzHboUNlHPR*mMv~$L_RT`8Z3{(ewkX9Bj5c)3$F#F+Qj0cJ z^<>OxJ8h8YHa*-b0pd;;1RU2}bGLdBf%TWPpDdhZIG>ZN?0$6!xyk+bKAn;oNi_wf z=S1R#IDSZ4xjK!+&@V^4xfD72@;SIv_Nu+0uyl5Y6csy(AuH zYK9cA29Iho0MLUefGWs=VWV9FGGj?Gds;dI*9DA>&Ai~&aokYN27&=RttIet_fo0@ zP1VnS_&pd$z%S25AX?tkO`nu)sifVTw*v5`PHtwei^+JaMOVvl=-w*c0Tw-HeZnl| zad_<%^Ja*cO!w)vb>yc%q~#;PbU?yG2^!cS8?@sInGyZ?@N)$#D8HinT&f5-yb?XXY<9{iyTup3XM}iU6iengAVw~B ziVX7+9N9lnc~7ZQ0G^VC8=K=Obs95Z#c|Ji1ONBz4^ahIL;x5F$o|{Rx&MzQ{*QS2 z|8qY%DVaWAv<#X+@yeVM*GJ-n0MgUmYFB#uGHtqbjO7H9LXx$;(p-NNskdj;Fh&f4qJ^gG~>_!s0|yiZ8p^Zg0o*Mi~)es_&3zf=jd}>`_Nzv`WTP zryQywLGKF8-aC-QAE)Ko6QQPVF*vf|nGUEcZfw(i{~OEeQdVDBT< zuF$oQkITfBAg9;zeB};DW+8k;FVhl+mM!DQuV&r|$yy{+e}aldjVF~U)ag%Cro_+H z_hX~#3*pdZQ@0Z{Qle3~zK3EWJN6NGEg&rr8{Zjd&P+wPIOuhhkq(pmt-2=5=Yle* zgNa3(Ky`)g9D89C@!vlWBb;fN^S7)Vg)4#v%yAIfb59#^RD|H{8Q4zANX5bidN=& zIc6@6igxJ{oyIk|0r?55LzZ*+HS_jJr!D79XX^=@qxBz8lC|T^P}L)y>{HPDbu~XY z>l@V#WhKYb>QBLv#V)}WkM=QQCAv&F9`bU^SllgwreooP&8yDTnhahuT8g#5nC*+2 zlL>=YyG~xZwZ6pLiCkkilzmJ&fi&Rur7yte%u}LH76RqjYL$kCSTjrb#vWZKj(pvh zum%A+MSYKmw42)PPX|COjbY{S1+VkvhUa7tLB;5PhtY>}~WdJ&( zIkS3z)#Z~4Luj8s?bZy(Bi=7j;gY2}p`g)4*s#t+UruFV^zJlK zyzhj%d0x}3;^nv#5r?Mku;-{)psd3AxU4q2tdZ&_pwipK6L9P(nYu}TK@vSJQl;QL z|K5il4x5M?5yj~+MO4Qn{uVv4&F-guTGzle& z;M6n+(Pwk`!!${#gfRi4gn_I(vSgQG9pb9UX{cr>qEt9rbajz@c71?@hT2eqJ%cOF zAqvQn#4%TKNeM?eBYkz~mh7vX`xY|Ym}qVX@-gOJCSGS2-q8=Re}B@MEeb~C-yRqB zyLjRHKR#)DV*3B@a22#9aK5z!@%@(d*7+%`<`=;WiuxccXcY1qp3tf=GqUoY*%foF zdW<;6WWYXQJOVZ3UNMi2JZ1NjZHrxshLDGs>wr#^%T|Y{rK>9yptNOMx&Ap6w(XC`#{?0z^!JL!Ur6u$`YHQ28=(Z4zi~Q3&Dk$w~Y(q8TKoOnF2D&j9;=7bWI+*`>5zA(uw;rO}RWOMJQTG(A6 z>c2m&U87Z84cl(tV6qeY6(ilguhCzr^LGJfD~3Ztf7H;5yC#~gF1k9OE?T^Aw)*dU#x)*AG8H5Z_<@>;&fo*08A9$5DH6YR2E+t z{t9q@KePB8c#zx$=Q<8cQcLHi`gkaH9u4%yFmVfT^T4u#Y)Tqp!pg>}Isf<%^9anBVlkXQ>KRuyz89!0@5u<`?z{Su}2^5Lc zhR_xI3qlMCZhd(=>rHT3XJ;VaPW)~cUw>*fZ88>iI zfGE7K%Wk#oMUj~rhwjI-c+Kvew+jz{WXCrk!`uo=^ z{awn+=Xe+HEV+sR+FNpTm3CNN)|$NahsW8mAM;fJT)Ty9$!H4Ln*b7mYaDn;urqDs z?g3C37NwLBY})Wn6}n=5ZNOVg*$y`@+9j^nUkgg_V%?>$G-hn?XhfxVJ^*G^$Q}>M zs?Yp2*}EEG`pAiIM;Cp4zb(m_#Ncg+tW>UdvhXml-yq+ogOvZMn2QB47hYQoZ( zk`-Ujpmg5|z|&Qveqg(a(tHDNNmZ{RH@>c0%{JvjnMp#m<#3sJ6@;{mCAedJdr{r zUcY{Fz35K1XvUq>*k~Tav?OMuYdpW8@g_16MwJy{w0>ENTz(J_?PF_^PjlAGGtsznP3 zWgsxwGDiJaSwFWh8&tVgQW=Z!QO}N1VE&3mW_hLuNe1&qC@x-%9I~kHN9fvVj-kvZ)Mr-!#Yiu|3JwpglmN#LCouJq2!|Br0;jB`H!&89I44nhY(ez=3BV2QmkH!-hgj+)PcTy*m6yo= zGk>BR!}Ir$UoU3z=*n+^FDamFtm%7D!CdQzzfq$-Y?J-HA8a;N-J3F8Zaj_fb0W=I z1L~hvC2WYybm2f;J{koM_!pVJY@T|~J6GT!XRfO~3Y>ezCvhg2R%EG(Q}PE65%n0J zDvk8f-GjI#`u#RfS_@aGT-&HzC&(MhamPVtA=V{spkraFANf5M;C!PBhL0$X-Q&yN zfspQLje84(%JAIfHB;v4Q(=GTq8H3Ala4QqhHWwcq&z=4?p`qQ_1K-q3G5+yR|0*+ zsD;}Oktc@M$inCDnJf0+Dp@5Kj#OK;pXM6So41irtJ9EM2V~~7d3@cqX5)A}2He21 zpP=gC8Y9HYCi5Lw*=NP%wfG}GSH{1VMVE$3F2b}mpBwHvg^FFXb{u>OK1k!YY2sYd zKU;?dQyxN*H_4EVGTEqI+>{eFQK*O17c&9Y8=cvSWFnUB7_Ckb>(*j@eyf`IT>{%K z;d#xI-}q*mI!C3}UmTr;Xc2-(xoDhNj$6x0ipGA^Tz-5>gcOW+F3N!cRPcu_riV&> zT0{TY4-fkJ=ri-3R+4|GmAwD^v{J#y&dKN-s`;!HtQXgf}QO<1G@=OP9fO=OM<6bt92I!8LD}Q5K45scNXh0jW+N4A$I4 zCU1<>LP>G7*@TDGVpyxgaWHJl6lBOu|J5h61x`B%KR0N<$G@9JsqW#R5D#{5H0k8!D{Wq;m{-uib% zYy_dWo34tR;Rz4K`KUxIQj87i-TPUBTNUT#oV2(1t8ICY2MF+6^K(OQSNL-zYKZV1 z70i&DbYf*WB)5Lt#kt#$BH1z7cU*;S7GwL0O5F0r_rP69MI0Sj(@()09C3^sCA1ZM zJA>?&+NtCo3!h#K+*Uvi`1-`@rCrfmfv)_@f6}cwGVn^ck%54)$^K7$pMROlf0rSZ zYTx?MC8y759q_XPaQMdismS40qNu;6 z)|f&s`e~MH@i-#Wmc_39c#B$CojO^>6o$-JNu_bgq|#Wlx{WQc4|twB)qwOM)bG8X zGF`ShkFpMU#_%?uCt!brr2KvpXu;Zo+WFCe*Yiiry9U> zd~Jo-h1$ab?D;2va^txZX>jiXiSnijVGD7)6RGS56wh1L*QY<4f7$OR)q@QB@t}<3 z^AXHDthc(a&p`Ld^%d}YFw9*x@Vg3B{r3rG!Px{nD;N%SQM_N+Usv(S?F7)YRc8C`FR!Ryx&G5*<8Ax*?!W zga%Hc#zx3d0vAh;jqG^arRKq;Il$gO#c}}sxJNGSki%)7J9f4M8}7n zrAEuvO~l6BWUlJD%2kIn5p#S{VMKB+%Cd-pS-05Wbu}S`>>|^-ZOi)f*Fx#M`GzMO zd=t%Lx>Vs44E9ui@z&wHIf64wXY+>IWah!BlaV=HD*CYcDC(3Yres_fA02_g*0)H= zDqH=RvbF0hK^1loAHz0s3$uxoCtFyRTdl{Hhy(n=CaH|6#r; z$&xhbjD^1Cvo@+oxq%@wSl9GABmKyhPCRMg1borR7|WJ!zyXQ6@+^iTi7C3)-Uiu> z?MX2ef$bq;sZtS|jw-d`oK)@?syc{X-{C@+>5LKeTYVXM>R4Q+B3c=C3ByY*Rh;-- zJ8|Fs!L)`aI_@-2$)f%=Ey4ZQepSdBR-=IfJLJ^$I8fm!-DGIeIo<|_`2H6Buao_e z4`-I>=stUKbse5+#rf#HHs4-Le3vk=Wa9iXD@Vt?d?dTXjN>y}>XhMl>1Iy0={BsT z{Cb6yJ-yvap(lX|cM$+z?G9FsP&RCoTB zM54+efpS&=m^6&WVX`kj=K?i885N|=59Ms3$y_}ts4-`j6*2!qv!@G4?fTTLm1Tz; za~X`El!H6uyudwk$skM7@}X=S{JTaBG1FGrzFXQW>5}Wr>LggXiw2pzniPVFDXM6f z>|k79PY~mLKD8JBXkB0QSOAD?HgjeaS+vHBAiJekP~69eCa?{KmOZ~KnJr4Tvopqy z%bHhGw;J6|KAs+L5;RFQXGzF9kodir1!GS{0$m7Q`?_Pq-m6UPw+KTIMp%ZENB9Dv5mN{{BhwN(Z#%qsfFW6=R z0#*A>n&LnWWP=H=VC}g+<$LvGe=9wlR~k!^^*)6A?7#_%O!#<7;54p&t~X2w8NJIg zNA@NadW#XGy6LzdYW-D>OzrA*s!6J|l(3-ott8)D4kTkvE&Npt`;^4go}x+MhJhqh zg*3m)sm+E&sMTmJz#Js#A+WXq5ixf{!5?1P99Byg5he`tP12|)TC0=OeZXoaBkrw9Y>xh^CE+Hg zaix}-qsTw z?Y;QIh*?_dn8rmz~oLyq_0`O~ntM{l0Pu8$=5UT4kkm^_WhH-^eB_p7Q85 zxUq}HyJD5#iDX(;4HrdE`ZvTp3oYP{qC|7guch{qHZI=I(kdhb=&X^YguoV`KA3HYCB5^owV#U^XH6~j^R*S z0>W>9e^$_^lDE-A z6=gZ+(!vC-)^@XEtUg%YG+>!%!taZH&%-|3kZGj&pURN7wIMx7E*+RD3m4fu$M#Y? ze1_VK*$(Mn_;%*w?FD}_5}Z`qKR|$Fc}wXsr@;>mgKkQ`BiuqUq^$0l(QuTd-!V$= zQ`5W2@8Lz5*f+PNZ8l6)xT`n2czwJzACS1b7k}|9n^o}yYE0cS7H-~Akzwr}JG3Zf z9AH%BHJ;|(MD>PQEqMLVKzATtC`k!Sw}S=Z8ErTrQ!2{raH~uGBlCE7IFhu<>5}p! zW~t~lwcQeL(+m)RF%84zQ`^VnOposIz~qkej`IaT33CR^RSA$22Fv9pJVfZF?7ISk z;4bxsZ}qY^%_!LYG2Yc4AMi>E-VzGap*ezryJpS7H8afEBN^EnV|z*+mMGzb%6>W- zhE{aO6+lP)xkRBgM3*%mpj=%%pt2W^V6cn2j9PgEZ!in3*!&9co}x2X2i21Ylsr71 zXw(qkV)pJc;QpPua};1zbv5$@p9A;~>PfPF6Ls~jp!$rK3#LjdZnZc5Oh_J+3V^p? z-i%BicWxHnvD*m352cs9OeAmzQDS!`31{=iPvynpnB5LAy(TibVbIhBrr8uPLZ5OC zcYiDk!Co8~Z6k;~k>T%Tk=0Bsp9|(etM(o%*7!xd5UcYSrWsMB|iwODWn3e%M}a%N+S`=eodS z)mma(^j{>R{$MQvWxMREvIu-Y#RS;Sx!yPhE(XKTpJaYPhf$5OwhNiWK?H%O;wb9kRxFvYlkd(k4&0|}5XZi66q4@yEyXm9^{zlJa zPCd4=7@(P0?n!gLo(4i)umkH5 zxOOKMjv_46(dK{PQ)E{>@LgYm@TO6<=B8xrlGg0vvw24czC~9*FK^h^$w$x`3Um#> zxrW#DZN{SCVwhcGLus3p5rpKHc7$1C-t#7GkMQXrYRCSV27+1MJp?fh9-KHGCra5` zp^GT08bqevzSt<&v;4JQkOlnHP1Y2=9WNk|ZzBvF6g55H z(_A{~A{*&~H0Xjn@N}H+f)%kfgxwL0{|=RYgPK2z9o7^S_086~c36<~j6>cba{ZAG zHEp`GONlMmTyDy&ycc~v#K{9`eN6%MnVE}r{0`{v>ZZbgTV+9x#pnu=+AN%(!tb)3mKTEw*= zxlGeE;1QGV@VYAm{Ez_E0QRQj3~%#g1FkFPjmiHiKPQ}1q?XkkO=6y=640`q=xl~@ zH(aNDMNeG}T5b9e=ta~LhkqxMV)~Iq)FQ4`$FF3mXkNH7P=Svrye3y+4j<%St*c3l zOPpP^H3lz`ufzChxugXC)hLez<$-yVBj3T4j_5%A7Xtd!N5Y^rphlQ-nkebBo5{Tp z#;kMzk6{JV5a(k{Al@?#T5awPj8lxV1m+OFZi+diC|7Gxg^~3RtLlz!XyY#r!9ihY zHFL@JjHSi38Yvx5d?TdKXe*G+3+`Ox-UY^sqD}M3mEbq*!7W+Do>u3y#q)t~5w|~} zrdeTY5&tjG`2{An7u73F{aS92ABm*yC~m@trt1O!{IF0-d^mEV`*^aOX)IKdIMD}P zc9P8~AU7(-bJSE&`1 z`7vQzDJ;>7qH(-$0tdu=E%jP;9}k$s*b{@2;UKVLu$SJIiNj0LU4f?q^nm>QixKju z5j3e7RS`4}p<0-`T9k0B-^jK&bu79nsnv1Zqi4<{D(CEML#nJp5E-xv!Q<*r9k7F6I4mg z2@3%yAn7Dp(|Y)^Rca|;ZVGBKk)nw`-x6eTuGFhFwF=@Pu32v5_^#nOa(;7&yh|oj zv(glL9t>gITt-4xgW1p4i8shdqvt84eFZc=lftD6&(h*EV$EwU zua-v%Ihl=aSHL-M;JWY}DdrRhy^MUs32;RmTvZY^yAh&wZ09niOHWdJjmYDU^h)$c zUy#UUugHP+`tP3rfm=IH!lxsRN@>D3a;-|_Pg8u9=(OR3fjA-4eqB$9Egf5;cWKlI z?WSgb2wd(#ckH3zfhlie#?J`FzaCLX)Y7yFl4%=o%UI0lRMCm0LVTM!N!lzC-`0GE z-uo@dx+O|H0`0lS7tq(6 zzdWzLiA3u{b#uIvJ0rt_35Rc(XL5AN7Imtp=_6{EXLwi2_K75QLoIcKD|N$JzoKVM zPm00jXN}cM>IVE_4=2VJ^_`(6)r>8c&~#%^Iqr3Cff4=Ne#bg*dxb9VhRPKV(4*lC>{JDn}=>K8@^k z!`kF1tfP5pcC+Ye!s&Yg&)DI`yxC>2T*s!q*SQa6ecm-RR`v;6l`ZjLLs?>Dt$9ls z-){jIx+F(pqg&Sz3Y87JPvs^ksmqx61Xe8uUxq2k@0yvJ#_z$sNvtGnAiUDNqDVHp zc~PFRViuuk%#f=Ckb`+yx-uT+H#r|#(nNh;xid3)ce+y!>Zae*c($4Ag-Iv8yK4UT z`S2I!T5s0(J&cX-&E5ae=DCftk-ep!+dn`H4&Ts)Z^VMEjisB3jkWx@!d1Z3$iTwr zUxPp^hwlT?-c{6QMsAs`-q6U-A!cH=OLAZctOEqV2DmChvGQwn-@h%*s%wjEI;l{h z7K5updD)!6(nuMn=xzIR&}8N^Mn>ut9cY+5V|)-vWjhoulLG5A;zdtod3^JvvhLO$ zrnJ0nw-CRex5+;f`ae=kg@2O@Nrti}7o=5g5=5#Mx)fB<-p^)uI4=|j8=R;(TgVw zkN1tE-mX3ik2)*B*Kd@vAT&JulyTL~c3Vfs0H>Bg4s$!lS7#^)W-Ifojfqd$fff3;t-Mtnc|~ z;3hevCrNKm$JsBeGcHzh-Tc^fN3FX~{;?B!i7I-MV?aWh7av7PPJ|LYu!y@Cbq+OR zK6&Aq6aK2Voh+%C0B_L97l+dE@W;6khrpURDGm&^4#?m~q5I&5QE&`ciiHYkhIu7Q z6j&6OG9}0O^*us~=PG=L{&9lgh@mMYS>4B){$UF9o)EFJvCE`kY5~%3vu>E7E-=hX z-Rsf{uo(WeOe6*C2Zs>deXUzw1ml)abdSZIq)S~pX=>;^4#zTr@N0RzUgzxfs68EP z4prWuBTWQYW<7<5iXp~BN+w8ZK+s)(29Dwx3-po!ZkIj+ryl{9sPt=N&T)ow;pf#? zo;^3^cv=me=`JuX%M95r)pJ>>fop#cEGezQKo2_V))1m(M@TXn!c|F)R7b?m+rL+7 zV5ob~5bZ;YMbTisorD}VyCSM`qF1BEp>mv7yXYO}+hw6k*9NFOy)y9c*DLGhm$r=*GX8BYx%$Xyt}oq^OuXh6J=@u}F)=Q-#}y zp7%^xZJ19Ld%)zb6{7rZ?cyMi5C>sbxk6f$&f7Re)>%s>iMK1C?HjJqm~bDTdGN2y zS|r?YYh5^_T1=jJyj#Ruw*yB%o$8D8J5fISD5E0-uzH<9U{YSKz3fTlkoJFFHNonGOiKr!c>mZRIUHm;*ty zUvIWrk7jj(1x*lPy`w%N^mJA~lU2TlAXGkwNUQM03Hj8j7FDX=+ngC zbXNG|F*eq^tse8dBrDY3a#s#+#>2sAwvVVi!!QV`n^bR42`&(oNP|18ky zd<3yV@5+`5-AWf5P0HUKCrYhUb&CB_O9ZEpxnXP@d)$$-$u|mWvU>BK_~Vmw-lK8P z0PGIx#3x~mv%hA@jf=C0Aba->a(H==-opc z1L=2=F!pb|OwyohjyvM&Gi(I8Egw9{S*d<^8FtFLBT(FyAl3WMq9^P8duV;vIH5n2 zw{M4}g@iVGwM_DbjqFL72bj_p)E)Pys`F=>$ps5-%)EZ`Zp^q!fZAi(4zuB7hFP2z zl^wi}xJw3>X}p>@fvwE~d20 zAa2vbw6it90HneMpmrIlF)9d>3y&brwCkGxCGIU*x!T+B?Q_qd7|8YqhKq z>jx_xH0V*vG?UMK#*s`CEcGMS;;aKF%~U)?dU2s>IUDs&^LV(e+sN;2O4^$w?S7o= z8`7nYK%^^uET3-_GXP!c8n+YSx>zQuf%JHS;D*dio5Qu5N5CAOz%KstVM1QjX4+=k zYu}NA_~uX6eLQg&y3dYVvY$DCG8S*~W3OWJMB@we-@~90Ys`Y#xAJl2TLbey+VA*h z82qp84kJft8^eEwy+Wlmc~mttEi%XB>4rRDzK*j911SEf5;%S|NdIJ3f(YT<{Yrvj zeud0SMHj$v@iTZAyAn}?ywS^f_||sxam5@oQcenAQJeWw>qY0%YO({jm)93?58WZ0 zBVhKX-WRL_q2MMQWr^Jm?~h{y=Jdj2H3U1I`8Mg`{G=K~xZ;)T=){bW}M9A7N<6@*ahWtA21W!7tf{ocAkS1XssYhD7`kC>n%l%Z@Hi4y_Nanc6z zQF)N|q?N4h7=x2y!h@Ula(I;3?GNE-Gf+)@w301Z*%3z6Y&h2t#+FG~Zr&cTm z5~|LoxrpXI)${}zVQ+LKdi1i^Gwg&N6 z*n+e|QLbE+eAIPgi&(q?DKo8Y3{t0h&jkk)LvesnT z@6kLH8u}yrFi9w0@l`)O`C4FZ*MxXx@@pN!aaRic&-6{kV!Zkqd&CLf!Esqr&?ISg zoVqKdg0AM-(RN4=RfckYKFH4*=B#s>s-5L&?5OU~N2BA3A1xB1a+`(YsLN;+T)Ys7 z(9Jzp$oVP}d@@MVzB1?0Es~~7rBA3;{RHV2C}KCK!@7UL-04H9Ax;QDVO7MWjSR^j z+1~#{!4_2q!aL>fb5W< z;HOI^3E@nWCSddTSPAg*%kfpFpU1#iI?t}alc-vjS5~gBLYhliR;sU=DkX@s2&$V~ zcs+hpd1<`4BzPZ9o?D|pS-yWfx*SesI!u0aIZSf77;k-UjU5414EgxC{Gh;dM&d3Q zwS|We^yCj9BBUWiEx<&x6YMARXAR5!6}1y(?+G(BW=F8Dj&FV&A6W+~4_GH*rgL|nyR}ot(_5@R`Jfk+u!~Tpn^LA*IjVXuV4REmzlb!# z;+{kyB6QT0LrxLH^^k&`*!_QQXt20Qch~qhqsR;c$qtlhJ~Dr0^|>7MJ~>95x~lt6 z;ZIM`&lo(3M5~Ha2(C&a7*lg3M&XYZMjx+4I%w@M}7BXpwBlEZ#m3VR%ec4p0IQe(cAzpdZ zQszGG1c54ZhOT>0_Fa@ye9lnOxI}&@<)qiJ$Um$~`ox&Z$f(Jb71^Dqz+}KCDd6Y?+I6lgw?Kk9ZloL1RD4DRQKaohn!FE3bF zC>ejulpB~ECRDi@b3&yMYo6g#tT91HzVB4e%LdFL%n+{*;1^Ur>QW-R1U;_$b9vD= z<%nBE`C7(}xhfmYB-&Ld{^nAlq@ynzU^JeTQaaXu9vknw)UfqU4GJQJYMnv0IY@1p zvoO{#4RA{+OlW7@M z%mGh__k_}bI~OeW{b1G2!GUz-G}wM~RUUGH$QlyE`;F;}TDcaBWzT89gUuR^LK%2LfLG6%v02UD1 z-;%KFB465!#hWDG<{JmlZlndB(R!WGXJ+sGy=DmSV3lyai`{3g&z!wzh|d5Xoab;> zPm!eOa^LQqD~MNJtY^s2(mfrB&qx>ecgU-oWj7wHaHX8O%!p$5{Ps3PdTi{fEHA1h zy5Pnp0a4Rsq_S?$+{?o_I}tV8WTr{sEiFmP01Y!cQ~P==hi3YVlZIFQJEo?N#gbg( zu$zJ;HMgA#h|b?xmrT*dius$rBh@z)k1`}-^uxnv&d!9u1s&HEpi6bslRXwQazQuR z=VWy%s9vGIuVjTY-v;w0i%mP6)J`;I1|;hH&meU*Y?<;k8`V`;3REVaNyJN$nD;hR zoa2dnPJ5pyp_C&uo9RFd&o@oPTV*F2bECq{<3G69o#o6M7|d+&kd{yuJsK3TX64KU z=E5qJ&x`eGtian8CPo#iQY2k8up6&)up9nA&dw>kvMBA=6;*88wr$(C?WAI}!isI% zwyho8wpqzZ|EK$+``r9}o@d{zn|-s^Tw|_pjxk;U*pPBMPDkk5>P&I3kJrVknDoAsy>HgP`Aw#B5dmi9XX^<_0z+MwYUf3|Vk z&JK9en|my{SYha#kn1DI#Hc=fjGz6m#uTvZF@o^o!wkoXcZ+guz+8u{jrRRKHiJk+ zZGjYO$l|<;Ht0RoZWq0e{#cMwveBR=^3kA{kmklO;b9e#@&}rzj!7XYF~k)4JlUs|uT7Q8SZ7t##%<@>O3bxK38cYnZNp)2BOrHN3y?Hfi8yF+U)fJLNA5UJN@7|zG&g{oem%~5v&tH zTD$1C^GuCRYTOjIvP=Y+(F+4YnqDxTZck+^f4(KDfc8SgE?Z3KzsnzdMiy7s&5T6~nB+YJ+bE zpps3hNOPK!y_a5$j(~W^I+XM>uA8cb!xXi#M~xOC z&_t|2kW24(hkuIsJU-mKA5p{|0ZtPVjk1SLoSX+^tcw4z( zgJ=s)k&&JLVq&h<1JhwM<%#{PTeAmuT!-BBmDu}KZQXgEPqXAX;7Xi2Ast>|$Qo}> zJo~rcCM8qQC+2d`^m9ZJ6)_|--u3)kFwpzk(P{Xq9Sr0i{QjT_aF1fW0hQqyL*Xq7 zXKz!uAA~>tjX#adpGj`#Ip}j2vNM-;qqIoZ#29c>NM$vyswhm}B5vG}L&YWjQDqM| ziZ@e4BZM|LcW*B{vo=4u#H=w5AIHTrF?ELn{q8~cF2rX&u^PIOUsDhbZ4ZLx@J=E@ zJ6d^2Rn043&#Ui9#-1s?kHRZKv4&Obw8D`Eau{kzQ}vi);<(5VmvEvPjQ5%sFBE)f^IIGnFGo-a20 zx+@ePKLO7RrM-BKfy!|$7asw)mnNF5rp-qG`>^Rm!-|T4TAyq0b&gf%FhVz9Sy;_z zO@q+x+``G9@(`<^4-DdffrN4Oum42INI?rc{UgEE^Su-LUj^*`wF~+m+O?t0KR##v zG06L;(Ms)HXSK9)FkYES)c3f6lE8JZ)f&%z4`8%t#7nRbeDa|08$zpn-`*CTX zgIpR0{0>S4Wo9CMmntWC|6*V<^Wp;nbY2L%DpH9EM?FY&bqs`=*JI)aUIhSc6 z&Mp@*M~PYDDAuoui9|-4nJ^bac}qfTs1=-*k|%K43qvQ-N@18x$Y#4%%sz0#QH`1z z-)>f&Cbn8fm3_Km>bgi>A$pY=25ZZh5a$lz4oF%WFR;Q*(skVlC+=>xeJY>`85wW_TvgGVOeIwx{O0)M|Zt zZl&gDnCv1Q-eJ2o#3I~yaaui+8@hq@n}*HK-;id(7qgJ)+35qUjvq2(Rm7qVF>WP> z>A9J0=H|vP+nlvCS<^8c@rsE=n8paBS>}E9*R<2HW5Yp@ZHbup7Si*k(YqbzlYJ-- z7?P)aw~?0E4|IXYVdS;)?&6BZStZtn3rO`u8Lxmr>inAdUtf5X^jxSdz0Ac72i5u? ztaSaT4@M=y(ok?la(#%C)EWXkfiQH^t@%4t)r>O1T)#z`sshhb%~v#2SkBwu+Gj~A z#8E6pnPk}XaiT5NYVpBI4p26FrxZA%B@HB4E`OBgDR|^|4h^LAyCZkoGt?jSkQ{j^ z)OVjFO(B4HV_Q=F$*alSy&_ydyvT4NngdU4i2nvD5;dBHGF6%m2UdGrihf=&1}mbsEMWW@7zN*2 zf~5cwwO0#28g514-zLNu$d)W)n|rA4xNI@?XuD^`TB+erDi(xsHw0@;B2Wcd`hEI1 zS02}Ik-TG^Ljj3xfgI6P=>Bjv(!yoRF9;MV;*oNaVsV8xwMqVRm=2m2?~O9z?I;|O zN^xjnZR|(_3`svHh0gE_bUBgnhqaGf#l>4}8=PW-GW}#jM4gWF367hnReXkMRYi4a z*@U+!$fDqhuqk%-H+gWi^4r~@$*ei}2qw?tV>HtC57Fp&rn=WL0ex&AOOiMu-K+)Z zA|g#=zRwr=f1gYsM)?oTe>-gtzn!-K-HNufv$XwxR`fr8w#EEcxhe`GAGEpg3Xxqe z`4-XXr4*98f{^xNDd$HTPM7grw#Hx7?tQ`FpFch+_AeeQCGvNpo*B3s(q4};UhU+& z@o_;c4C(@z>{J|(TQb`&_hVu?1D|zVlY9hUIv1c^$xkY}t;o5qG>bYUYpoLq;@goi z>spXI6~#i;nzx&YS3k5pZ(^bw&1$KHDyDKZ*NGDK&YMYOnpD*79=O&4Hs9k-`(kY_ zl-p#6@u5}Iv8&SHn+ti{hisPqkbo;`!*%i%}ni%~DAUay1NT2&b4~l7ek; z0-i}$`o7r0%|H5W?7H*U!XBeH}{j@q$=ms!cW4_GXz*c z?e7DVVGepIw~np`OQ%j7tzugN7r`3On5Z3rpI?AtLguz0Kj_t>)C%lE*#$?Ao#yAX8%h7p_D&xE4Dl*WNP#e-@Sa+`RN7nn*y=!3MF_LoiETdAQK2eA=?)es;E+6Bxf)oRqg(*nq~iJmY>v82J75D(pvMDGMmi8VaQ|$alIa5 zO5PQC3==E2*m<1nRfZRG&hVr{Yo8I{It0~LDe40$co;Qz0HJL~tSWp^Jtc3cCg?6t zgfkU!>iNj1t#}>CGN&yQV5@DxFl#Q(u3l6C&nLXh9J;p6d2pY+PaWVH=#%kbQ2AU~ zEJO=HG~~UAupVflt)1BUGVXyHVQzY+yq@eV>Up+5E)~zi9infki^b^@eJ|?0xT0uJ z^aze(@Z$LCCFM)6FiC0x<`MDw`$wb(Q6YLn(&-^q9c6zKdLfrMy@cEFXjGif$DVWg zi;Y~PGQOAAkd~4!@0`8&f3zi?Rm3L*eh)PK8Ncu7|Hs|@SEFv1CZxB@;v)a?lnj}S z9s!M@kOmP%@YqivO`#uzBnVT$!L&PUV?6yQCS;Q`{ubvEuKIOWMir%1j(zR%wWUo~ zi|vioi)(0A9j;}6yM3*zUO$h!+3m?jnQo7-C$8C_vpTZ8rrXTBJU%mgVv!XY{Z7rE z5E{s$2ZW(lDE~|dqt_oDvB0;j@3OBJl8}I~QRRPids#l25r?-W{wM^L`J?p#b|P zHt{S8MQKE`-5DjGrT$$26ba>%BO>@DhdLHgyX>PprPD;G-!`e)eMLOPLFRY2fJvu& z8_aBC@u|mI9l8Xw?2HLflKUb}0%B-hZ(#a(v$`nN(Kk}SBwDZCg56)_{=rVfut@0NCt3~%m2PU-3o8~87OKZ+?%JH8ajnrdy; zw)(t!BUM))Tx4}-Q?+2>xm0=-?S1HGY&Z%M<~Fru!vGf+vYGg^^78SdBv;fVroG*kAP&yvQ6$R3i-8PTCP8^EO1C;Wyv7z; z`8JNNl$CTMsQV9ny^2BJ!c7YXYXAiWEyvrIlr_*Q7$71?*s|yXc#DaMj}CdaUX1DFJG&R;^uNstMYzlwcx)$!;!hp*R(UDMyd3b z5r_v4#Dc87on-fVjJMF0HE5u zPv-$AQly1%(L@RT#N&gVNm%*`h@d^^f+`9I#ffeK?*^?N>92@(cNG<=#f^6Y6bRbA zDqme&;|r`DRLztKrU0#C$spmdW{kR|=Cui~;;lIKBf623yCCvRK*4V&ui zFtHta#S|>f{obverCK4q`-|BlgEZVu;z5Z1juv2HL5RSLTRBrOaPBa4^r=}wyl$;#2iP`q*RJFW+~$w2B3!UYoS86l!p*(#?gav1j=%#LHCZCibpl))~uF7 zfCqIqm+rQRq~S3&52KcIZY<>$#!eHsI%FxGpafGv3+7<2O!A&~OGvwp+S>$SB$NgJ zY7!_Bwo**@4sP$OB8m@R%OrT>zU`#hI zeju!wo9b3*K-2-iDU0o}$#kO0i8q4WvNI!8Ai9;YJ6=M>ufo!3XrYf$kp`A03#*Br7X}tEu3sfLR0He>}eVkpheh`Seyn!I~90slITSBymr$?9j z4?gw%lAuGAOOpb}n{<5x+pL)%cnF_9zJ29#o%fLd0Z$ZyicxoIkEj+Skm01BKmFN` zC>GyZC>j@DGmzQ-o6Geq{>bc1M33>$D8wOlDb8R_RUz8s4H%cMJpa+Izud7xff*;3KmqnBX}@r@Uup_>ZE9dXL>`57s2r zio1NEp177V^eN~NV-ML-#PI|i<{jvt(@Y7ReV@K@YBe_&_j9HVc1i3@lDM3GeTaV5Y1qgopEj1sn76 zUO-mTC@gl=ju>@@HFsqxoZsCW%6O%}i6bej7&Nd~CI4yx4V6gjC8l6sBXdaPIepKU z+gaGKX{rjm6s%RiKrZLDCmN@Y$mw;rBk^d*a&OD)lsSd`Jz2r43~O&@jX z0y}f-7la6AEtPU{+}~=ZG9%olOGOmpa3$B_=G5|^q+f7l)8XI3Q(aWpeF~$4tzaUh ztK8PMiOym@aY(7&PMNZipQuK8A$w>@^2w8k8cs!M2P*z&)Zg4TMU-(dMS9bXT`b=qR=I#rHW@58vSS$jgsbQ70BpsSy#(} za52H2Ge(AUB*OeRW*j-RzmW=H7YlcjUn>#J4EjRKK(fH4oP`0uFW!~)IaG!1K9%B+ zTDCk#$?sPvZVRRk&@tA_L|qYkY}g_RgUsMraF3V?BN`k3qYr5JC__<|JVyk-1JA@S zT_8cI8n%)EZ)X_vu8{OBL8Q+tJ-t_!(Jd5#kJy7f`K3hP&Rm%aaYRZJ2nJaY;6U;j zV|?c8O7TgyGpSEMS!}2u#4{u#KL-Ecw~Hvb3IUJJI3x*6aMlZ<0wX!<-nYw8LiMV6 zlp*0wq6v*1TS+V_Bxk9~KQzM-t9q4H_VivS$$TKS!%!F5!GepcJfN5=`5FbP1exu~ zrgGHRcsUw1s;6&KbivESDhVAsT*sxEeNCc7f>(p^RN_IIoCr{=12LKLi$KA{uNR9nZzQa7m4Bxt6S0i{Yrc6%J!}7 zW}l5d^Fnqlw@-QO()4k*K=>Q%3l-Ipi%vw|c7t1&(>kX;E3KtFmx||<$myI3U#i=Z zk1KPx4G=xZ8)8YMvLN?lp7L?Y;L^=q2dWFa`Z0v1=5=EJ;Cz7_v7MFbx_4pMKEc(2 zSs>>f=7r09ka_KzughR&bA{)0$#rsKnkcr2u=V>Jm(>=1RqVAeH?SCRLzpP}TwdVn zG*2f0=epyw6fk2bRvRM5?G``5NU!cQJ_3=fe4syiM{oEsyr=HJt@Du_bCn;pLFdHX zF?PDb+Mci>$(wdAb2g^)bBW~Afy6yc;M77rpm}k}8Wt76MO6U1bEWd&jY871M?X`j zbSnh4R`h~Aib6ZFUNEEzPbaigbB~7dq?GQ)--hi)>B9~Sd;*kOm*L`{_5=L7m=KbI z8VC-IqtcNdR@_a5-t@XSZYvOv%ZxqRbGF!zsZbInBvad=NDvFevsfWjsx10QIqK|` zMoZe9{I)g*&mZ_tAYS8wF)lBtgu8nxUY)x}RCj32NXEJ}=a#sIi_j~N)sc>aM|M9C zK*!SfY1WPuHP9F;j2&IWg2khRh5bHK<9K+5T~D0;&@&6b zBV>$IWHBP7I#QKX-QcG_r4^)baBEFFx0Id`!kcJLhfrEO9a+J(v@VT&lMxQt6&ABD zoDi&2u`Hax$PU)yjX19IqqQJVOeXfMNQhBE^P5z=_g?C^z z9=4nYAFB+RqYVEKLu&)j=b?)`CjZ9-S*jVRl^0%=J-5mulEkyr8hGp@g~8g_G#}MhRm}h z75J8ZAOJ+6m)xHlmO2O2SDCgspZq6Z-M8^jh=gWCx&f1;9%$0gCRO{G_AIads#_lE z0$y2LuaL&`x7fVB;Sl}0o&`&cXSy#e(FOv5*+9U>IbZfkq;~%IK-QgzHR3IMrQE;Ye;vg&62Y zwS!iZ@_dOk(#=Fe!zvjDg`eia59Aov+zx-P1M{9OP<$XDeMCeb7$={V(}i>9As@h0 z=hRiC{ld84^f`)ZPz#_1t{B|E&>c$PE;7LlH$ak){0~zI5PpM{tkUEZg9ZvgbNGTw zz`UD!4%Juv+(9~e&d}0RRW{J=s}6iYU7%8=P(xjg>^OmXrE{HhGVRc5ndcN;{nJ}d z2Nm>nCbyQjpBRALaiW6ERTk;eR9u`P7f*Om9nF1U=;~F9*C1zYQl^vp9mj=|eu6)Ixjt9=RRvmemCS0aGbt6}5HN)hJfbwCQ87q1DLNR|%!3 zBYdhO&8Z`f+?L(h8CZNPrPW(e7O}iy#)zn8lrD;fO6@d6ldN@6JTK%aoEAd%gZD))N}V(-7D1$3d`thFyk3Th+|rL-QR^`p5HF{-Y9gT&k_-j`B0W zQl^>ecv!VfQRUTTw@kwToYUHh~{J=J%iAADq4bD>xIDuF8peJ0hXt zWkm2~L>Og6xY-)nR$N~OUEfgsab_KD3x#^of-noKGWC%7 zkco%N{kFN702YM9PjoX{m+QnDZMq70(s;BNlSx!$RVpTbHy0%g+nP^160Ojmi_obC zcN!?O-&GJD{LF}uVH*?$Ykz+tJE0#QLfn?ba+{~q5jY68%wHm4Q6_DSAq#^IOU(iv_go+AGiR*RvQeb_WTp~;>0C0iDx5e$fpWsHQeHOJb3$v?F^g;! zS@lA7it%7b-ES6C!2pMn=6|prUuEmHL%=lxAh6|uo&UpbF#>R0Q^}YP{?6^2p^|}} zDJ4Rxz~TzA8oUyzww7xfz5NHf`stU^QtS7D20rwE zX}A0zyxqUMwbXRAu~iX#Lq)UIydZ7a`O z#En*6+ydY#PgFP$9qas3$)zk<&uBkBC}~PG=k^HrCau;LaXD{f5_7a?Fn4*Z*LJq< zdIC?~!rDqJk1eWrghicIV<+!MLwaNTCiBcB0wjmi)9P>sw6|3v+tw(fFT}l4Km!wg zX^~So4giqbrV6iUtR%m$SH^V!I6;p9vsrC+oq)Krv=@J5&H7R)m;Gz9PV{W(l!DP`dN=Y++N$l_iqn{1W@uDXBkTj1WqoM*7xPlA9EO{m&WSDX=r7DZb_m?v@eCHr3^GPS zKd800Mjt0OsS)+u%T(u~Iy(<1bpfsA4NzU_9yw|ka9Vv!UKk}sDL&^=?1l)pWPc4w zOv}x?VgTmf6ENmo+?MhBy352Qy&IO2@_N;k`5U6z4A0>?*Bqfr2z>gOKN74rVZtJe z8$>~EtaiSQXRpZn{j5l5bANeI-(Ga>ST+ESX`>?}U~lEwATXP5p)+v;6o)4VnphSM zi^Qs|qtm!wzzXt>EK^O9yX3LT*HF==a&L-2RwwKs!-!SDJaHf7Qxu**2}jRN<2MUA zO)v|Oi$?iG4%9)DpA{Rz3<~Yxo8nIJ&U_zy^a)o(wc=~yx+2J6@=A`6c&h)G1YLfgZtFa0H60Mc@&TR)%0`Q3AH-Qcvi2Ht5dqct11LWIxa7 zwOG*|e9<3u`>yBYtR|Wa%xllhb#l8TzWB88dFgN#O@0u@x0Jz22lvQKs!^15VD$4w z86^3F&xZ?>N3yQ9UEgEm!|Zc}Rru0&jM@F6(hM@21auhY99pG)|43zDkS1^ety{c4 zU^hxy1h5U$*}^LkZx`EyM?W%C!Y#i5$sxik2yyI05te zdOnC2?K{I3ph)kunINye3-{+24wtFk(y0Y^U9FgzFJkYWm0>F zej9%SG>>sU%c!4V{YH=i@iLZ$i0xQ zG9x3v6n&|G;|>`x`e-5gae4AvzN#b3z@~0flUM?HwBR)(${Qg`#1b{522XF@eO{NRT9l;CKQ*0n_ zWcac5FjIGtkpiynB17-~#+G!<5_8i^VqkL%+^|7lc(-J@&ZJLXS znwT0YJ-?!`X8XJhnRbX?9` zU6r`H)a9CRS{Z9Abth?Bv56?%Jc1*AbR{fbJTGO50qFaodU^%#D#x*&AqavK2im}W#A)mT%O-g%JKZIo7 zzw6U9z1-MU=bUDo-;`{k>hQZGGqiKH>1bk2rr{`lB4EQ6F#mmO5gzk7{=_ZA5*{f6 zC+pR4h!BdIQdK{d50n7)qsioeMth^TDKbXeX8CUIFD%Ex#}71Y?JAW3xf&>ZSl6)T z+}@M=DK)BYx{d}%jPf>P0IC8+`o$Fr=A0Ib%!Sl2m&`Kepf-f_7=;m0i8Ds_Q{AQ` z<881;T60zu6MGj8sv99K#=9-T2#4v06gyn0*)ZW^smg+-bW9piCp?gKy>c2xT`>|C z*B8C-u#3L0ndI}CYlQXUD>1pcWG>^#=JkFtYMCvTS!`>V^dt#=825NpqjkmDL#Zta zt{GQDKOzH4J%Y=l3`#|dHB*?-qRG14hSU&~Q)rIiuGV1!1S0Q#TSo=A@zK4~6p*)% zMzBZwcX53sBD|ZclZUAIQu71WdOSiYHtSiNiO{(bXjMZ8E>{vMosz1K>JcOli1ZDH z{$U=Sb7tyz{!gq~3xWt_S01~ab@%>WK1{XTp&<8pENd%4nTAZ zw}k=MyYF1tOgA7rwmV@ji099#OnCwL6Vo|6C0xE{JI73~VA*rEFI~kZb&n}r*{IOj z%r`uE%s0S#EI08IJ^f`rr@=6P2ZeoM(Z`;a?oAr(pz_GluTO5E)=X#Gt5>KbS2rsy zzSCC8#arX{Fy2u4TJN~MbcMDR#BhQ5^)mmAU*TfviF;)8nUQ;_T-h@GHO39QxJh{` zU(Wo?l_skhsmw}})G9s1dMDOhR^vS(;2WmM^=Y+Z|I!iQx5JA2#d1UcvEJ)<(;mYP z)A{=g%N^Pt`vrn-uJ(kNjr(FTk+~jSQbcaGF$AvfsYFdl_pb(5s?9zDPnED5Th9&oacHQp~+VuCykt?4xls`dSb7GvM?^p+k9J(uV zvAa`=yd^3})9;o8$#2?6U7iga(r#w_0(+ggPyDd|6lj_%8w&B+=zJ#b?5TpQLI@rZz1oICtTOw|wO> zP~ygnaz7OM`JY&&j1mU6p>Cvsr8&D}8|D?p*?SLWe>BeOCE8#JoPyj0pW*pG<2Bi0 zj!3z4&>gw+3@hJ~Q0Y#SE7xbt1)B*-I$>}#vEfl~605a}s|26D?e6n;;eU3X`FpN{ z22tHnQ9Dsl*FurEQEBTWxgmtd_C%h!70{@#`wL`1^PWM52tN-XKfvVjoWX*12{RM3 zkf#+Jm0I}YbvCS96g6)I5}O6ImMV+*P{JjmXVonIJu zRXj(n9bah>Ra%mZehcjO#o6tGg(Z?n*&Qv#gE|A@pgMzp%jsQEn5CaTd&}jww#AQf zP}mMCNYX)k0QI?w?8^wu<^h8~O%w=qXm{jjHiVlV@wPFyd0-gjlX0fYtw#;{ftR*DK+%C1ZO3fQL;C8?df_og*cxVy;+G_u z<@D-eWhz36o?OYMTLiNqiB1j880U-PZYJh!K>g9Y59><&^^QW&pc;V~8IHPE$>Uk=tu>)dye4)XX9wb}8Nux>p%HS=PUb)L950M6)3+*AF>Lf{Y&62OE8#nQ^eYIDM&0_sKyzW4-Q9LJB{nZ9%5lea?e*vUdq(GBiw5Vm8JAC(;{%<& zGeR^=wdQ=S9JOGF+IWo~eLD;8W;#B1|b=`wboD`-O&dxBYqsGfg4n3mF zZ&=WPc;|ZRCC--O?+^3vbNP-90}BcY$q--GiqV?FqX%O{!KqVh@8aBzQi)lr-DNVv zbT&-pM#|I@Sy2_}qYty6?{V{qEtACH=(|SwII-=S+bjn*whn)XXL9u`Y!(l&w?w;z zP4RQ}A{v0Z+P|lK8h;T^ni;yR6zMy&b$x_yB?;%Js96B`?G$X|iT5?Ssvt7;eigph59BXtq>B4mVx zq-he!rZ|>jQNu*V=($C*tt_(Al#PX!*cvwwm%w2YM3LscKX`7y+tu*jmPP_)iPjjZm;!R>2vdWX|8bA5Y*%nv^QJepmIe-U+~i5ZJzX1_Z}#J zy#l_J>u^aKk111?>%dIsc=C1vBRUjN!tc*-NPP2mO7=dP!H}VR=?sNE?`9v$P7A`q zlus8H4$9_)2bh(`NhOGp(nE1O4ltYDCP!z|kP_oFIE}c#Ai0N``lqwl{-F$0;r*zaa?^Q+jehg(U<2KBZa4fOjn!tEB0<(-X)0g{esolT~gu^qTfW7E&CyxmCR5z)&(b}vM zX@o31tAsl%fZOt1@*b!_MYwbgCgazu+8IvsgxMBlI!g&Ejn^PmYI85bo7$_~BBjjV zyAUUCQDy-`u!!KhXDCL1bNyDL1;R@b%bDJKasDabH^_ElSbBS?y9`&7jdX-8EA?qa z2W{D$ZnQk$7$$_TvL)1e!v{1Dcq|0aWO>xLu#YdiJLCeqI#G>@Jp80!|Sf<2d zO|K;290kQ$ob1LiTKRxMwFC7YjjCNf5>}p&5>%doUG$fx(8$)pT~3~wU9is59ro`! z^RVqb4={C>54fIX_ZSjVm&N^vV4v+%)>#KoV=S|q+LNxrJaiwd;+x!1{vA_T@|%-% zLbYHSjL(8l^_%kGB~e&&o%zBTtncU;wY%(q>PHA`LwBo1YiN&@#qHD~EO$yK7qhh) zMzPw_;GHLJp1wY>WslPK{$q_eEqa@FRvFXs%+;`*JzJ#|BHUZf%Py6 z$#Savhl|aA3OPbQ*#iOG+W|l8y-}Y*Puy3fR_5wh3H2YuLm%@G9|_}Iuwb%xG=xv` z1NJ!&><&co(34jTtjtZPj*Y+LtRm}&byqDu6(BY(0)`!!h!_45c7x&Dq8FfZ@4q=m zS2+~hIXWh;5nvNMVBmTiQB+BV158FmAkx;}=^0{Q_ILjePoN05>kU z+h0QqhHTKFQ8<}BN%TJ(p3J zkIE&8ONhfp%IS93ZfxCOkPC=iWS5%Bxe1G%7A0iq$;%J5wc(DK6e}6+mnBtEta3-Z zyKSXxdT)67J#k+hFYn#{i?0=N_bA`UjrbesPc1*R=r8bp7s?ju_B-%zq5SjRNF@4S z3*|r3-2a!h`EOELq^9evtcs$`-ZmyXa9AK9MI8)GRVYc&XcUJQxf0P>h+;w6=*=oi zcbLr5Leg@mT*k~(ArEyD1$TA=GQ9(1Yi?4!p8((ex^vQVgCB04Q~29oh>LChzV4dy zy|U?P>v&85^UV>c7j6S)kKrwFC7f80WzPaud<5nJe3x48XCWuulmSA8V}I`$L9(Gl zGmNyeRB=fWC-Iaf$nhW#QH8P*W|E?o4Q86wXNDi2njb6AekY5ulh#@<7)W#IVTg{E zQ}3A8s|ua1j*C0n~5DmOmH`7S}GB-ow*H zIu*J>Ee6q!BhKt3mzj(9(XtwxDcr!n@|e$2Dq-YtkIK9*GcSKxgp2E43{I`L)jd@g z3&MJqBoN*t8S(S5GFlyiD-~#JsgY;7P;sY@uQSnOx;_WWn!SIWLzu@)jk4^@XusN= zSqXu@jpY5!bc)_7kr|`GwSSB}zPGHldrZ}-tr6B_TSJ1#%Z>wsUKZ@Pd>ETPU-wXeTB$^7 z6Xmu#k?oV#q+j~}^?O1C5sVX6xn@Q~MAGe7#7e`?3m|wm_dLC4p2wz4|$X2p4wJoTWk^$(5gr8ByKMqU|}Zk2PB$@$W+|$4GGb zCR-|G#Pq>eR_w6QJTnJ!mg#kA^si#R>L)i6Wex{%lj^+If{!%dPe0w|^MBhWHjucT zTm;Rn;53f%@bH&5G1k?A_y3Xq!y%rrqq=nY;TeEMTASc&LSpWjczhIp4F76MnC&Kd zyc=De8iMBox;@MD(ZG#T5_qYhVx$*g}~1Nhy_mac(ZZ;%+XQM=6G_?q^Lw5?=quwroZUZ%ran z`1Z;CjPK|=V)0AG!zAy+P``8OU&xB}2a6!QoRM@k{h(`emXZG0TF=tHZybd)L>9#M zkk}sFMqj&z<27=uy+@NhzMsCLBXtvqC&Vd283|-visqa+Z%jVO$jf=3B#ycZ7esl? z!`iHzQM67&3Nbz}Y`jAlz@t>=7sR`SqB3#b5+ajdt?8AFFBf!$Bxij(fRQ>tnZ4$)0{<_~lCQBAUX0SwA>`U>%r7fL#q90gt_nz{>>zIlvv=9X4L8~ zlugJwUojCw(v#YGWL!_lWsk|O*R+cFOrhY3pm-hwD3VsL8{~C4nvtP+@)a}GFZ)F{ z9TJz(?xXrLZJL$zQ;%_?6F=*C20f27Zs@Ec8(`~tm>Y$gf zc?tb0u9I`hy>ad~>CXWXhc^6#)$(I6Y(lLA@27Oo2hb1u(q_B7nFH!7SJo&uu&C`* z&j)U%(bIJ=?V58xA89E}3&YLE$0sSkSXtLf#^py~ts2tCF%~`%++JRI$`BquVgFre z%&;fFfWC85k>901lK-{R{6k+P&E!l?O-xPx8J_xA+@?xJ`#t3>r&|;Mo175 zK?-_P$xf*0H&J>zfNbfHyNBjl9-U>!)KzmpPQG8Sx{OM=n7-_X;xO|(@UTi5q@~kQ zW}0)i^LsnLukYtig3!VUFjyEyLKnh4toGVcB18gX$pNN4Dm9kM2+RhIc0A zS}M;0n^e&es%72za;~@T^OHSYKbgReC&PF`1J+huHaKV2P|wze(&jh5*2 zYyx8ik9N(4(|cN-c5%jMlq$nGcc?8?hwCZj5T+T|&{HBB=7(BUT42%XaK2j0Cz}0Y zt1X1GJ*=2pxtQ!sg2c5cC;gq#m5UK5K1&5%3$7jlAbxxD0bR=|u2$~*n#*7%GK>%D72ly;*F|&T`~n+Rm>zpEzS~t{hZRw_+E@FC5Gg~hTaUsaEMoRfZIkXsym)8aV!dk! z6ldUwRDmEoBNf63YFi8qwIgW(Bsi@5zh zH?~lW+#zck@gV`t^3C{1kpDucgs~F#FzemY4*3_NJLNouP?d~iJy<-}Zz^(yY2+8v z8(2ub4Hr%jb?qIXs3wrRCZP289~Pase|lgQUVwA4FZ~zT1fah_jRGTR2@?D}T|$XQ zzKhHCcfTZZ@pC;3JRB+RU!To)25#bX@?8flnmnc8b1-!Xk3~L5y&L( z|HvKK7yXsn0;V2o?xIYiBWxfQB5$p)s-zMg$9_p%;C^9Cyluhw7!Y62<|m>E;Yus) zW0{DQ+_L?XRG8wWA`qeBm~-^y{{c8?69wbRV>GXgsifg1O)J=AGovs|w=+A{jU(o> zC8g9Pd+ChfT#X21H9CZ0?L$b~Py6~0>ML}U8QRA`;;Y{~y8kMZ_&=kMf2_0rTwiL| z&d915zH;$q8K%&dd5225V2A(`QUt|WV5_nrBZ<1caom*TY0iE*$6fZHZ|^PqKQc$)vFuI}ULwMP;agmz3Q7$b1v9w4 z@0wyr6WzU1TDDI#j?{s-?;K+7ukSBGaEaYZcM)eq@$k;W_aSSt@6I|v{|{;B*ri#t zW$DOZhHcw6Gi=+oZ9BuZZQHhO+cr9GS5;TvJNiRa^%(Cjc+WoTtU32u&y=iy%B#o+ zEY(xOQ%;oD+S%ymUoAaWR~|2Om99M1h`Kyn@}eDnZcS$r-QG-rN4vAEw_7e1TW%Pa z=bZo-9H*@6Dcr=vsG`!qv2tL|aNn#oq2AVrb1Ej*0+hVbWMoCb1e{c4WA5uzPl2#d zya-mn*MLEy(m+XnL_XbO{$T+!$lHz?Tdg`$Z-Irxo`Lw)>(vr2h6CCa&lbT!I_|}k zE<4A&U>vA#9RUs>tn9ZUK!;az3)KW0z0W-wre;IDc$~G)L}8k)g=tr-mY2^ZQV<7s zM80WG^LQ`TQGd7f>}^rBO8VU|Q$>4apt1?dM;6ga*pQeq4Z?<$&*{Wz*B^B!p{9rj zO~>$w?+r;QQQE2O!m)z|>lN{kvH6lsVHeQzm?P$ z@SkCAv2=Ejj87*`rYY$a2g2r+#M%BlMI&~b(lQUi)#>VVY6aaRpM`KgSliK_;g^9P zvyS0)N$!d$N6;&_XmPffu2p_f+_Ksc^-|DksTH7YQtDd;M$Iex7-^vDFIoPRS7VEk?P(gC zYQ%sO{TPOIc;~s65vp6XJ!<4S5a4 zQ>s<$EN;iL=4CaE1RKApXiJdJWMH>K)y?|vi5r7T<_lILn+mB(U4{#jbndjwmcVy( zvkm;MZmNW_-Ix{yB$ku33ibQR-?HhXzq#^4ZT06V`c5m1#kJC)iCTnDHqqC(`6Ik} z?~n&R9gQJl_a1Jg1bvhduIEeUp|FZxe^1R(Y&Z3UKW3+!Y=!}spf zw}FRu^&1#rqa85k7#OM0ji58g@1f^9w3}!~Cs&A(I7@#O;fIlZ0*6Hi5#t*1VXz~B zH6Y^XoXYPl6f51ddt$5}EA8md%txybh_JhaIx~#`vyClIIn|LjVMBO};GSHc3CB3# zq-BR@mFg>Ciwbk2{5Lb+C?a9Yg%fkq#NhST3T^P=0VTN2dab%4V-SFOkm$&L&axu^ zHAS4Jj1vm+89Yuh?owqG^og7K-1nY*lzvUls2}ACm0{MCWT@$6oz0fBJ>J=mU#;My zYk9vW|Cg1&C=W3M-!r^tktyMa&Qh)plw@}99Yh?E)Oq=(I_wnC%mH$y%747w` z9gJ=4t^UJRE>!wyUgY4uq}v^HsT3i!Wfjjr)~E|ha`;3IYRfbd&6Rh>cKX znwbY)uBvbDa=J|O{_@TY;Qpj+Pq9;5MZ{j{$7f}xW~SOyd3b-lJpujT3-eEUbTOfb z4r9$j{yHX<;Ex*T6~tK$|F%A9K^MZb9yL%LPKjEF!ZsRSQ?r-QRF#fptl4TGm=LYR(1*>@<&0iydvWQqCdRgI_d4&%Xe3vP%P6Z1$xC5-KQ6^sa~z5 z70+hYUjtr<$7bB`jJ=HS^YbQ2sS#?xZkU%;A5=P5Vpt1kKZP!)2N{T6@+^gcE)pI4 z8ERsWN9w?5h#3DE>fqK_@2opGUGJf#4?)MA=?k{rR{m<=wnoq3Fx{6)1xDcznM00ayvY zW^y!89SjXd3&yNPHc`x2j8RQ9JhYXDVUO%DNtlv2_22N38%zm)L|d{2PcCGt+J&eZ zJ7p%ci)L!r-zVAdUlsiTCqFDyj5(r0ZdO790rqLHbF*11SwU`>z00uI z^{^gIfe;s--Fq{GQQnz;f^?L2HEPyd%+Qh51KRg8=?>3ZX(=?0K%&?npKI~{J0giVT z+c_AAa%F_d%D~Vbyu)@_DoKgbq5~RdQ_4zD_nI0l%4=lG59T#?+6u@zMzu^<{p2PD ztWD*`s{`e3xB}}+g{^xG)Q&?jf_F+g>O zyohoIbSjw1H^6KgF=fgU<;R1OH^&(&dy%Nri>L52*V0 zHe~wpR-;)>ZPEq1gtq59@Dg{wpstKLk^MMMGTQrV6ZygxmxcL@z{=FI1}m-)c-PL# z71^NSFl6_I%WiTd<3}CoUAb>Hf+5@0t|g9L)y-{+cA)4VzKb=2JxEzH5;7JlUMUll z7|CLknG|Uglw9hZ(`C5oR!)w=gTD z)Qn~LT(xh>&G)n4hy63^J4HG${Y^DB%;t^jgE0#82O(Dxgv{|pd0L11R>C}L!7WdDEH zUkRIk)ZZ#@9MUIUe>?ef5lAoKb$pg+{#tx$rr?+*BHp=}z`GPzk||)5p@?Dl)1tBk zDORmoJ%s{mRV8y`(;U%~^RKY7KLro%2E$_DQVON9_D36x$DYTW$5|f3-Jh@HkO1sd zA~6G^tXn-oS zHu##e898hjEL8BW*jA}8f>bRqCRw@RLKx1i3yevEll4dGL0nL2G_fyfrZH}cc~uEZ zbX+JttR-Bj+i$W^Z22$3wYN_WGB{38L{Cq%N?z9)tZs#x zNw5wZQQKjVQuSL(a#%6W2hP>k$7#AWV)tgW&lgI%$>}WJPpFJ1R|XH;77A|xYM@eC zq~B+!uzx-Vl*Y9FjE&%pV7!b4hpYR;7J9!(DGB zUZ}!nbf~Y0r<%;0daeNo%^78(%CauZ(0;0B<9-yZjo7=AHU=7a`PP|aU$9uRWN6J< z$lHLtjI+o48HyxA9^oNhQ3KCBU$mBRPO>}}XC&E-!#KgY%d{Eh)*A2Gy$;{(eBftisA^f;;g9Em>$pg%aN4~aOT>qxi!tT( z5J)HeP{8T(P`iBXr;t9hp};Ex?P|44CNosQ_xLU%mP~D(@ODU7fd}NoTFW^@c^_m2 z(RGu&*b{pRW-zafJ7fF=70ew_+(9!zsBeaX`7id?FNf}LC_Y=Ntvs%tMW)MNJcz4G zp%r()zsUVp{YYiK@A6^fpXebXltVYAHqdIF5f81tGjXF`ZmPZ$#b4ai;I{;PILDFS zfL|kwpU0ryQ}nR8j=IGeg+SpAy=H)@pJ7ZFX!&2#s0evK;SAJo{?M^Z9M3oe8Y|~U z8Q}`Jh1tr9kx$PM%R&|MPtQ=_0C*Ijz`r2R=A}uMhJdQ%a=X6{??hwsd*sODh@fr_ z(`4WU#k2M0yJ84sZ-jXI1llkXmCwE7f!eNI&&Y<{z=6IR_#%$HaCHUjnSr@jYPk<~=oRv%KYw-YXeN!r$eMZT19!YLr;cGXNt?HH0 z?JZf;BZk5h1h1K&(F;A17j^yu$a;%xy~|_0&$+c-=eEtzHDyNyN8ZZ{@AHgU2a?Ic zGl0GpSPd-)`I2U7Iltr2Tr@O}f`OfjtdWDtazNNAp=HYGPdQV|wiIyhpJ zrja55s}_|fNIgY${JBQ+@h?u@1GAVJ?vIqb`pN2*{-4$3f5V8$8#y>xIx7ELi~OG* zv4w}G60-O7#+0GGs6gD7C?JqNXR2lqHMF^a`FsI*ubF&0oap(b;Smw{rQsBt0|}GT z)gxf2e`phOBelLDC6Xl#v9GJ!$Bu|k2#@ze`dgL5)I=cC85fsW)~ZQ$=d$+s=C!)V zV0`#>g{GFJ-!N>>iY9Pp`ZQ4@<>YEO-{YtefxtQ!kmcW5O36Z@f-a^BV&1lD)#;&V4ZZB6nWw{1uy7U8cu zg~M=i`z3>LczWuu$2pwLA~^Zij>xG`H1jShigwshUJ?YmHvRZBjT)S}k@SH-C|dU7 zrKUe6d(bavoHJFV(-C?wIGS_*ap*K7wl4C)c8TS#sKBN(eZGHnEGCK7UU`eAQrYN{ z9-oN882O&uU?yRH;bT^8gV@R6(`q4i#umu{=mX4eW@LDY)34;6J<5k~3k`v!(G1$}<|w9zhjZlp;@XhlWAPBy(>MwlJ5rgwtGq#Sa64F z=(H15Dv`B*O_(-YZLrZdzV!o-MhqR(20vfOO37f)+2kK4Zi^Ned^N+LOtO^;j&#jw zTI>b;CV8#3a&OttLvAH7lLq^CbK_p_=lpqsI^EVsOR1aTBgm%ShHu^l4 z8dstIMJti++=XqAyPq{rM}lL9#EQ7J+29+a&sR8I@k$%P9yQ7Gti-tHF9y&KZx!5A z$%%8Xb*daXOA^Q9dm}c~%MZJF)DH>_@CScpS|E%VwzgWKw!3R`p9)fRu^EX3jZ6~IgDdN~704i(fJgi}5QK+fA{CPQj zD1UdUJ)zoLK5}+7wYanGz0TbT4JQ*k2bo;rZpiy?*WVfi?&Z1=hRg1c5%k@XEKudK zbU|3zqc>CK(QIwi`b#mOxWhiie?8TX@(!;U);L(gH;<|I7x*MfePCEWUh8SJM+l!K z3;`zA>Y7oh;%ErYYaD7_;8DOC`AROt1-RGMBtmTjh0H{Spht;uO{P(`N3_4tOJlQj z@?$*EppOSexY0W@H@l7h-K)oICQvt!SPvw=baIAJk zScRAsR^@}Q2lXpRn07^>;Nq0cs2-pE;b)N9hncAUg+&b%wbvq`0uBC!&^3|B)9S;W zO{<{6vUPcF1#h!FTeidCEZ%*{Vg0-dv_X$xV|*tcY1??-uS44-^k3URVN!g>RR&n=OZ)lbvW zb9O_p+u0C2n#<6F51a8|h{!_%i3@pn@{09oWys)&4t<+cbrin0ihr(SAwGmsESN!P zbd25$`QQ^}P@UlQmf}ln-sFZX;cga<(Iv%|oh%x8k=x$Cy$F9|uHC!&&grj(+Hws7 z{P7H`&I*=AA<;X@2d?KJa?Zfh$=-r9L!33Ba(cupY~u&==ma+7z1#~8AD6{@Z>NOC z$OZ0QWi<%?1lUkB%8t!9b^mzK+u4II))d=ZYkDA*+0pg%!aE-A?1+>SAW6z z1gmpP0x-vMyQZX7rIo#Py_UVLM@MJ>lHAh=%|xrC%isePz#h>Z=raX1i*VXQvOh{- z9jcYBp|jTW&*g^8{Z=eP4z5r+guYs8{H-ao@W3RnjtmdM-5)b?h>9-1Y_w`}@>F76 z)zx-T-tI}BBN|OA!KHd2j`jzp=%KEJe!`8o^^8SOAi;tm=Zc}CRY}X?Fs-`9PHXi> zsVCh!04#5!@p@`JM=cf~wj*UT(nS3>?kN%fQ4rWsf8_XUv29P`g9Kft{HP&!3YfcI z&^%I|G2%)My&n;!oGzj= zM9MGH+7R8{C9IlaFuf=s((6PY&DyU+3UgPT&1xTNYK<-Ao~6jvZ?`)tpot_8{ibGb zOS|licygy9PlX;wpY^Zsr6+Mg8zKMPI{xm{nXV|6sZcAC>GCzge9@hKYA)^Z=con8 zw{I}NX+vxMt~IZ^^;!g#*BW-V0oj4EMv*^X%KNbHd_0{)oj9!2|&NW%Ug1AQ;O`?c=UnT<6!T7uYKsRULBOcp#ZAuu_C> z&{I*E#BV|pm|X0BfH%987%NHHCAl(EPWV-{}bnR|cX>R1p!gJMNm=%VSV1!HzesG{svf6C9-++_|jS#d8_3YW!_#YjRd7 zpl(8xo)9Mk=B$F~y<&)`R}*f#czJT>)Z4TWeiNL)HkTiK{F_UPvTjurET?a2;BrjD z*!qJKoyz=y5MqQk55y%^Y))dix3u<03^Px3X-9GxRVWUDv6V|$F4B3aM#l|AC{VcF zS0tQs*GmO7Ne2w;2Zn>SgT9ITrVVz3E=wl1ZPC0Qsh08tme)10 zdiWwF(2T)M7({VGGHoz)#O1O!=LM;qI;#4!E)CI5w^8`0a3zHjugBL`MU5a(nl*9! z&@IG+Meg2~;Oh}u64i|*nmon_(K7Mo&c`awgX(ex=t0({ipjm;nKm?eJU=w~f8lS; zCN5L4281QZSZ4&Ash!PKDoRjU&klkBuj2M6q;SMB95bG>L*EN4BBU`WEMK=)5-?__ zD5+7noV)SdhLfgcI_kmd^iO{_?13^=Poz=rTSm7^35d1b(WR~tI<2cu((IBo8`H;i>r%%LWRu>N;4&biQ;kS2p|d&U=Fx8d*zjU=dc zZ?)r3=p=K8wJszJ!^kS9nt);8VSi8cEhcp1vJX7e2Z|VWH{*@R-x2N=;~nS$=#O7M z3%8^|K0`W=9cDY!9acNo9qY$qz3gPx9?>@0U-_J@R``+h;X?UH;lc0?h5o%VsHY;<<42r-$$07#QMFpfC6?eNnA6tD`jTY@>~P-!0!F!* zG5cN9C)(Q$?6!*H?kEdINlV72gcGOcOf=?Yklh$zw%Hk`kf|3{aw7)UFAEMGWf$j! zyd1`+n&qXM5_if8wFZJaF+RzZJPe?3@5LHjp(Dq;q`}2?!d}Y^Z-RD>ghSUcc;-G6 z2~WP5ikZ#i#fprq$J=cW%lrc99k>9^G|AX85 zf@YqpN0W28+ts^CxT|eSS?0cLR&n>#r*S@S{;6)2a|h*ViRdo+2dF#vvqi`FS0uDT z^(6RVOgRf{f<)Jv?uZZkEB>EmL-d=eDM*lVkJx%#zpQaoG7b2nEa94^V(Ggl1IdwN zYYd|M$tlX+qm_mNUa}I?2U+RL?&G_unD3T41DtM3xruB2wN7BZg$eg2g7&4)3HzbnA z<*SLMA$obJDvp&nAcs&zmAM-3|9SHsMS=8vgEN_fCgI_WEm(^v%NZ6Oh@hsk>6)R?|w;+lqD#q%K4Z6jvPiMOK|irKFo>) zBkmB{MedmhT;D#EdA9r?7V4;7=@7HHHkv#~?*44$wm>F+qrhwdct)QJFpD>4;KkKn zpkF%xe}#qfdXZLhh`fVG6OvSipzsX(IghuGa zQ`!fdr~U|1ZUfdl#>xxb6i8rkxr^L@7{#xFvbi<%Uc-p0I zsKft2MM4k58de;pxTq<2xHJ()(QGJfZMfKeDkQ1*>)}z~oVLi$fTQ|xk~?|(_q@syAA$hw%v6UTh};9f?!w>q=nK(YMGH|eAch5@E;*>-{bSJcIiXL zBD@Yv(BVurB$LN*F04%y6^}GaIUo9VwKycof4wK6-_=e~62y9`D`j1uFRiJo^uSc}0G3Nq#0+_gnKqyQ1&>7s+DGp5qkTAj)?h^cE86G(>IDB?_53u2 zf@bzcKWfMDAHCtfXF0@c{bMdUcze~_MZ!!7{sREH+p4)I?FD(z7GIW-C6Nq27PG#g zpTTg|${96}PcUDz4XQi< z3jh*x`;>`qu|M|AM*3@kld?Nj$$wD0#%-HtA(}3*YYErncC3a8ZU{XXqb_p#1w?2r zrFS~*fOI+zpZL>Q`*YquoF?eDRleknr@~{J^|zA33X+~Pn6Kz-gh2OmZ-ltUZ@T6r zI`-MS5ZiL+S8LMw(x z{gd`9*>Ypf7fOdQ3G#k`OHF8HA;S7Id%k)#mFoO<5(~>&8rPskWaIN0+Y&`MRde9& zT-HR?lS*X^3;YS}4`X>78DjWSPQj1jGAm!wvk^~3xX%aIB69jDf)4{SR}`0q%;a$u z*Uu_M?H%$>F9a(v8XYtTqmj6pKJN3YcH(G#w+NuJRC;iQ2xDwRHzz0U1~ssD0#7D8 z2;6&so%dKw;DSjcsSA>-OzFCD$wP<@?jc-Wes<+7?9638K=ms$Oza00@F(qbT_p#4 z7&4_ZAP8MT^38S#E_A^hlR1fTy`dB&m2zPwV;oXRMHP%}5~gz)`PtIn+5N9bgGFfQ z+09*jFA2!Z3HPNG^5TGV1W>4Su_S-+)W|JW5x?||nTmmz3tyfGyRHK1%aSu_4mK(tYeUG9{JtcW`~-T~7^Z1edDk67 zpK#g`6*7MAH~rQP8*7)wwDkZO^&tF3K~emM`pg<}`^3lf{hBC()n4^#<%Ee?Uwf z0tZ3}#rT+et0Psf%ZH~Mz|q#SU$8O)@C*fp8jC(9Hv(8-9=9~LIR=tlT9HkrFbu&a zjYJzS^jU0PrZK|bB3NEUn4we8)JAIoLpEQ7xkz|1qAPnknE%h6o~Y!c3Ly?mOA@+v ztT9I{xPXfS7&7D}gfp=H-x}{Zj?xuGPk zTIwB$i*D|2PAEeE;y0-w+omj52-hCR3|E$`gi-D)#!euO$s93yLdT8nmh zY3(DHDfyZ@y_IPtuTB22&_XKD~}&L!sx%xGi? zCTsH19H3!*EeI=o>WcF>P|$PrHtWkboC>Yx#rMo>?NQjcC773Jne9)U8RZaq5ShSRwGS(htN{h&lRL1w%q<>$Cq}xG+Ty1iQ8AYtloMK zPy7hlJqf)k?qZRwW?PB~aZmP$>Kza*Z@G}}<{KrWC)l@JX6!Gykm~arXzZ@i*p`WQys^bE|@bn$hzk8Hhwz=~ry8@9N+_~~jV$(h)!*oVzVjWIHui+2iuZ_^lKPc3DP<64_E z>+X5IBv=kBez-|5OZFsa%L!9#)LPifY^|507Hh5Ys|i+8P1lk~b8nZ_NvEs9jPxVU z9IXZ3OD@Liu#HBHr<)P-gO9LEg^`)!Kp0xxkWipoRO6iI!Urz_j(wwK32oX}~LDgn9 zI>!t+ky|bhWA`B(zpaco;%O=nCjRVS&SU71zd(S^9eB*DsT&0Bp_St&e@K%Q zssN^1xodEnYX$epW!WD=qC^bH_je?+qQ*oRFfkJhKhUTeq3Vl07Mo;ku@jit?J z&^Xxp7v}fL6OFDrhm?~<2VA;ph-a8d7}?Aj?({uMFG4MCuUUGum!}{X^Px%>I%E-$ zW`7|lNXC}l>M8kHsL(7Q9O2P^S6ouK8V0gxyCs!9Ycj8_Db0iuH=3Ei8O7U`S|-y2 zA(rzaQlVS%o-$8PWU?1)kV|1*bQ6Q&D0wd>ButUj-h4UI=&W4mLs5ha*K3z5TiiTs zrN~V}ICl|Vxnn@Ck5!&v`MY!qcuvh_54T*+#Y{9bmYWyDfT!YHqmmhJM(9}}Z)w`s z(7&Z_XKkK28n(A;#WDQLuPjZ;_VtFgkgCgPYFR{gg zp}{XsLxs$u7Exmd)S(#BgjoF*dkeLl~=q?7@x(s;L~g>g>}l${c4 zNTXQlaHGF>7=)^xEc69!8`TKZjfebd#^d&4Gu2=NTPYK`Is1$V!L4o7l!k@WsA4li z%w?lWWsQ?7`uo45Ae1{Izoh7FfQvTtkG6s!mZto2x@wFCJBQ5dpY!Tlh8!=Htm|+% znT*WycRK-85km@!`Dg>inyc#Q*K*UZ}pPwvXu_a-FrS*2O z9Oav(>=SaD^(73H+mmG5IB{TC6*U>IUfH>MOopmOfo@O}(w^G9@7P`qWe|#aT{)O? zdFj>Amm--6o`ksel+dOO<8K=hddVfxfHdyfQa@+83dWO!KDJtl#4ecP z5XPHtVc22B9>E*>+kd8>MqX`$F;4RiR`R`ec zw%tSJ3gGQSy?yL!L-o@bFZdz09CHnY8?B7lVE|&O;Dsg<`M`Ip=&>jpz%S`yx}e05 zS}N*tI`@Qtr~J)a39>he7_Uw5=3CH%t-{No({DlW5M!^HOaObJ->QFUp95Nx?^;kX zi6Qxb?+$Sap5m(U6U{;GMR%dp#NF*;8D+(~cDLW;Rm8NT!H^4=Qk6C$-%{GB9z+XZ z_5GV-a8s72?C$F1%p50(THqMgk&%FccEA(Z(0Chzi!amJ+E${k(mQDf>uA*x+FBU( z2EF)5aAEfD7D&LOb$u* zFG22`Z2{A63`tS!6V?3;_40!27A-1%b6%5O zR}qQjM}p0>oy;YyG8v?JEDyTVAK|=Y?Od<$L|8;+Kl~Y(c75$ja{VZvZKT@`U&8Kx z3H*a`dup=-4A@TiC5m^YF%kq0K28Np;GOkcjFNP&egjP5G90C+Pk;n2R3Tb}sx&KF zf*KE+LtAL%Ck^3+JwzBye$waYXnFQ0_+_2t9Sv{S4cSGs z=hT97iDbnk7j~ z0hW0n9niT}DBs5uY402-;w#3t$s0MD95k#1U36T2+C0Bt7TK&k$Yql`hjrcmkoAQN zd(s#e2Z&qrvz^WLCk(x%eZYm{SrfIARWTO{qfU7_tw0RBftYz|HZ!I2N3WR z9=;bKvun!jomkolor4kV{;3?{h;o`KBXp5Ja4J<BeKH|q7{95!xD)=71oKS@z}9(CM}+}J%+>*Ps4)HY!X&TXc@7vN%h^seV5 zOxlHUk{;^xqkbv9o`YwYPA8KD zF5`fmWZ!(kF>vOygXC!TajJt`ab`S^x_)IyRsBJ*;%O94$2uM&%}x#nZHJeKo4y9L-XvGL&L0*{4X z-X-(w#&30E==X?Uu5x1BpVNaOkYCLHZycSEm=S3<4A$KOx!vOzHwfiR;}yWwGp5yb zfSp5V-`b@#6?%0}=PNvIQ`j#6LKXYcwRujiBv}*JoRNP`YU?~>7%NRZ%RDGtH_J`m zx!BYDEb@kHePJY1x%e<0kHo}f$i&}(GC>7gwu^DMqmYdvUfyeD_jA+ADqu*LA0D0u zK?Acy(tptM$6pz|-hK`@67Hwfss%n$klnoqFC_2&+p;m+bDTaZ2ra>9@_wHi(CHNL zx+Oa1z)QZ{A?-INMZp*PH|;!~0pxgMU42YDtD0l3>Q-A?4eZpN3k5 zUm#@|RrU?CfUp>5e@!?~YTsRv3v_r0(*^%xk#I?`8pqC3`eng>!rl)$7pjaW! z#BGEZ!uv?beAjQ3VlB+oXi{Ux93m8Hjc6iN`4Kn%7Q;Fg!_H%Xl`!N<8V9UM2x?J9 z$(2Jf2&XDkOVW#`5-BH&NlGjn*bpU`FVYFGb9tB8uTeVKX)~i-)&ep!a!AMdBlW71 z5b4GJ%p8Rd7_~+Veh^>rcZi5Q^PY{KITJ%0Y%UNmR645!2qX=NU zszXvDx)4a?CtX|!5&RPDd`umsSRk=nuGQaWxIL8@Bnkp#^>JCuQeB>CMcKvON5v z;Kve-230}sTm0NQ!BTa|lXcK4BQRJoS|>5%cA1)f)~yJv;1Wr6i{pze3UMvr&(}#v zSMXpVMt`D0*MXfGCKnjVOK{C&H}oEG^t(G2RfW>~m|rZWC{p~bbG)7~zB$#;Dx%%B z&xVU5y~f0j=_DG|tNjaKe;KTU7wnSc*L+paLzcah*S>ua&lc%_h7NB~@iu2IB4Nst z8mN;R^q(6>INg;8lm(f!6#6op@~c*f6mIyE+yAaV6efvCKgnc;J*%d<4bw|a#$sAa zLN8d;;THU$nHzRf@3b?hcm+miPX)L%P@R<@{;h^y?4UI;T5I~1}XISljM zt;*`V+`x3Fz2}8HH1`h<1F1S0nVIcT8dW?hC??IpX{#J%ex4*L` z;Lmvj=YB6wg-Z1zY215(0w75jlpi<8JpvpjSNttCZ5x`&n<4}XsualYLFO*iUo56( z2WP^;x-GDgU0|!2W>n-cA?|P0xQ1t$7e06u~Vl*r9`z9 z8!1-u`jS)Oz!`2Q7!rvGMD|_VKCIiOc!K@6e&2xGjRb~9Z-cESYU`B^J7k>#UM%t$ znNO%<8SHS0|0UVCxk2a2Tyzu>kNh;g{(-Hg=@-B3%^6!L$b4h=Q%oeE{?W-L(AG8f zu6^j3#~FGwM<8>$7D210xg|bIUj~bl;e{QS2+$6dw`aW#1&=*@9sb7sXAdN0FLAN# z$DjiJF{}RkkBusOBKm)GKr$nxAo}Rwf^Il*O2=E_#6I{ z(8OnG_QJqv{JV+0RFu z0e59yponDQVSj7Ck_}WC6e@v~Ff<;dW@fI|o6tN>>*#dZH-VLm9#9X zLKP^g2>YfY225PH)54ik+01!*oc3{-e)JOt5qj%HV=9+;s4An0>-zm@m{a-{mq}`o zP8Hdp#_g|M=_PzbsD_awc7_CWp&NA2=!QhIFCL*!k^-eN$~wFzQv=b6JL1jT^#-20 z=3>~r(?@Tb^&5cpuV#2?ql_;q$_|L;aO8_UWb;UIk4}s->7)7O?~7@o@cU4ZsHk;% zvBtK5c1L|}KNrOh0fO-~Y;+>5RmIA(XpS=uFf5^t^`ssRZ0uE_+IiI=JBreqHQzfT3pU*a~ zJ&!jWraO7ww^g*kY}vIT@d5<_tk50_(RtA?#HS)&?Nk7GBF6BD38)DO^)&|qLk*PJ zqDCn)dNv8Ru7|;Zud)D?ceUwXa&U1WcKTmL0i;@F20ry~d`)4)Ttx@^8J^|faS?At zk#PnKaHBzL;7N^cT1;_KZ^7YZLKoxnyyORp1wGfIr(fs~hZQh#_^zc$CKYA+M2jaI zg4cK`&0vh>l^M3M!&Awph{8ZUY5t+OA9J^#Z|Te?PDM4Ma~| zWGW4fGI<)>(^p6~nKvn9{AipKmqr7pWZ|{vk5@(}bnZ*YW>zc`nGo!or3?(se{)t7 zqpa5uP?I>X0kw(7X57SjijKyiR}dJA1vWW*zbJ`CW06ma*c7ud9>`0MN;Hly7>`hu z6mS^d!-p&kYcY#XOI;`ku9RR$h`;e(jw(qj%%z8|VUjj@B6G^PlKx+u zy;GE?;gYRem9}l$wr$(CZQHhO+qP}nS(%m2lf8SL?!DF+dyHOv{a=26#2Yaq<`W|| zjmjU;r~Z=%ATEHEIZ7;oEsY|BC&uAp8o5~+-GERupa%wZ&Q$IBw%P5&SjnZ>Q-1K# z8Rvge#E1&;UG!ldVi$vxN>AN{hLEqI0u#rl!qV#tj2O&fOAAhTBx78)ah{I_bVqTp zmh7R|XrFb9U?}Wzm+gUTj!KRlma-+#D}>M8Kx zUJ7Dp_sy|q_tc>^CD>OHrhjA4?oV>m47fmRe~SYCJ*4E2*{?*HS#|Q#esCgmxqJb! znY)+fsyzgTXFWv0Zr$(Zuo->-cu5JlrX(lDrafo^x=V<$m0Y7W)EJF26NOu*k7UPw zbQ2z}e2EFcrre@h@!LoM%9MV2BNSctU%OH5c&utRMD1-$b*;SUwy)Qjxa@PN1<~mQ zhGxB;DC6}_S$ztNzxioxm!{nrH@y|0F&gpQ$rD8b=vh+spj#T7C`(6iup1Li7zP8y zVk!Kpi?MA^UDiy6#uH!$j}#+BU4NI0NED`N<1PJ!docsE(&$uBIz1{)DTZlQXYK`L z!c~u!6T^&33l#;E9f5ww?BSRnry} z6;^vn!-waU(a2t=#NJK2n(MFxttIavCK)>eVY2qfr;`BX{`g^y1@_4#*HACDAB0;` zuHvJFp|UQE{gW}S>MqRR&2yj5ty`L>>ix!?R;Xi6b`PNAB|po+#ycQg;r0DI7RXzs z3HCeeoM2xJWcFr;QjmZRrV61mf(lv0;Xqe;8)4$(>OXFNzI|G;lJ;Pmc7W=z35Rg* zTVXaV7d9H_F|J>i{vZAIpq|<$qhTk{G8qBX4l_RvwYH?sEz_dd^NQb% zKDy*P=%a$90F{%0q!WxOMg~C9fGem&Jy7xIXd)SziO2ELk39mp>%+sva-m?sQ43@` z%y1LFvH{+nYjLY`dJd4wo79Ow?8>i7>{DJZ@T`96eer`egXgMip;%=Ob6hBY)Xv&x zLe4$8Tg}bw-k>~$xU|Nxx_AW3G`fOhp46`gX3AuHGO7ZuHe~dxiUOa^^|6j>bd3-` zb8Pmxx-a$4hWYj>yyJ)56K(}CwgK|?Yrl|Jo=~;En7U!umQ`7q9+ApRc4nppcZTDe z=1Dc|n^Xjno^o3uU|d6Em5?mYnkIJw)7l>)ir^!Qm$t57jyM#X+0Y$NbCGG5$Zf7@ z-Gl^gxTJe|vUD`IpRJJnAQTsT`!BwT)juPm4_1xtFo!)@eRV^jBb?bC=knD&A2UMC zU@YgcRtmNys-rk`gJ{0lV~mgGY;-FeDsF_D=gbJ!MH=gKy-hBWRK{moOa&|Av8Q8Z zy5l3w5Y+*Cg^_oT*gvre-?#~003lyut!#V`+Fu?8WFy+*llK{(bF5@BG{UULqN(s? zHx5S7sE#(1Mp>Ccts|L6dK;3yMv}g>iN59M(4d72>gYNChMH;9fcN-nBec$oNR;A> zsZxV(&g4~4vjwi814U+kC33#}__A0>zvX3E*(8${$)@f11ep!89_a!}F4qdRoIAMJ zP@$#;CiCByVqA}(1aS{+cD&H!Kj#z(VQm59T?wkx+~;O4m1q_jd+ z#)nd5p~rK2I6Y*p;nDzG51sv%04xJ{S|50~!@>ZOOgfvjK}7!_H+r(WTlAPI6lG#f zL}vMiWKHOx1fOS%J=4jomD1z~2?37Zq{kGg!aVbAT!fZTp_(`($Tt`@mhLxCCcH9e`X=ZPb-osw>pos?9Gi=t0*{b z0uI5cSy7_(#9yEkZ39VnxkMw^jbl@V;>{2atz&4wbb%%a0qm#O(~7+ualT4`^!bUOCw5uN`uzT3Nc14FJNxzoQa{ z<|)c5RF6!9NxEEp61`+VwjRYF7*(?~3=L^}n&`t^t)vd4!qG8J?Po@4u3GMq9ax_A z{Py)ge>2+e3;|;{wuwb?KCTUAMc2l+-b0^ABUxz;v_h4Bnf9QQXs(m4PotzHp-Ms4 zsv-ZTwLZxM=W8$JdQNqxdjt%YBD2*QV-Tc>w@Qchu7~s&?Lf3Hs4E_)<8%2^uYele zPjaRTn~ae<>E>(*;~&xTA@_jX#?620 z;~*$^!k*`da&|g5x57TraQpDa0G$^;oCRtKVDrI0yL|b@dbaF|56AuPZ}*JfE?Ax5 z!ef&*8NvYe#zWAWQCDwwZvtl6t76uN#^x6oJxCjt%+j0Y6LN!$W_hIZL4cn==&a_k z5AT7(sy8b<8ufed16kC((XtQi6}npEMr{_$|Q6SjmP4B}}gMvcR!!oc~`LgOtz^QwWcFK#Pe zD@aY2vdeG#^WU`rOE$K3C%3OAx{~F2+{2cE;j1KPD5$pXDpZf65Zo zijy)({7Br)i&Bi%kPtj~&|z$hc-M$>Q1tuyQhbQ?h<(b7iURzK(jAs09dsCl^!K1% z!$aAatV99fki31occxLT9vL9gLQ+#ir59W6e^=T|D|@@`W1?2_Bf4qh|h^A71=+p53E~Qn`JIT8BHz@vOf^dM|3L!i~Q+eSTu2QyQ zJ6^SYL8z3th*0=tVJNE+z3~Ns(#e^BoOJ~5zLp{IzQfe&Wi5xZ7X+@#OUUuP!;R&B^l2| za^T3ZVA{X=JpYrM&cqVv;t|2pJqz`Dwm8iB7Sn{G_eWh5Of!5tg3<3}!L&}n1v*_} z7NeokzMp3Rh2ob9*yNaDipKV^PXtz3>bwVBnClywM`DwuWCkZaD27CC+-q9P)NsGV z)<_L0ku?<7z<$vSXFXN5B=;p9Nx5K)D>k&PUKPpvmgSKo2)Pci!37>Q?QTJ|nPE0d zk4GypsV?kEB37kL^>o&B`MWuWo_`Q^g1Mwo+K@cZSsw()J>2RYn4w#gJy$+|j=&ze zF;A?hMeU?aw)y~kMKNfsCz}Y4v02?=xoF@}GqCgq;UTxwAz;+itx5Nqfr{cn`o2)s z@)zPizn2d2ckh%x9<0?LXNt(b7Y_fS^!&dj}RS3mAa#TN^3Rm64&LpL&w|~g9`(a=R(^d>9ibMyqR+?+uBVR=N&qO27 z?o|WcurcPeN$+CU?IX|azNabA*W)MbZzzNQKK(390nnMseSARFyE=b-Eu~G-B^R}7 z8gw_LK@gF`H^$V$(u*lgIlw3Rfd%hyns)RWPh$4kT-oa(4M zt!mfJ4N%5;!N3t9i~3%&*?MAw)k*h4%&H|_?qnCi(W5+m6w9Nogk0B=Vu+Xc^lOeS z@cq04lLH?v;{zTn=K1hWXCrAT1BuZ^l9KzVa8J9TAt(ou8{yO#b?jM+dv?TCyX_(9 zsY|z+p~o|3{e0O`ND`ePNr1xd&`1)U5Nf;E-j(n^v1%ew@Cj;npR{w0 zifm#=+cZzyqaN)oAqOIC@l2j<@ob)K1vZ5SK@cp;ZS(B82hR4_f`LY5%`Xv|_8FV@ z95-G7VD}`y_%GoJd`bPxiqYZ;?Kx2E8ZMHUead9|&Ryuj$37ZwQN-?0 z#2OxP@)en!yd&|pA1tw=?-oL(i}1D(@(7_L&!HgOUrV2DY|b5c3eXyK3hXM>ll+XB zIo|(8{}XeVdb9a?nK^%0vI75JI#T#~ko9d$t&IQwI{qI^(JGpXh^9ZO2rDc+k^mw? zA-Dnz9IZ(?lEAuO5FU(dCE}?sp-SI;Etm+;teSgm!*_45n03c)u{@!E#%Uu{=g)}l zeDD0Td+PM>N+KE3MBClJn}6@RMvrEDeP5CL_&?D4_`o0n9iW2eqSWG+?7%ST36@^r zX!MAJcB;Q zu?lv@SPLN}I;0h_6T9jH#VLT5M7yc9CXfs2f9q}K#LQ$qnh+S4o-~wL8y6_9OEH<> z`3%WWlUQE{3Gg||Xg)$vUs}iwiLEn&D6!C(bx-HlT=dg-1HZ{>$IgwZs zO||7P7oa|zUpWGnBv;rhGBk2SR3``TxPSE%eY+ul;2p{lbT3=_7nkp>V z20F5>dLO1|$xbz)5Y{bEj?g`(B(3%A zZU~8Sti~caxQ^M}=n=AccLKv)Qg&R#ywVByw$dm*34St@vNH|Br?}YyEIV@jNgo59 zl;j34n)V^b(4f)x(@cWJky$zNTnQ{yd?-UYALWI67?%M9ouPUXCWot*HcmA%xYaP& zQ^DDC>x5Ko-){IuvgB)T)7*~GcxuzDsr6ZvYyB#V;^LB=q`SuXZu!Q*`^7a#aEO-z z$A>K*X4w765ikI26wqr|;)bj1<5#9GUv#kJ9aOWZ%AU3h(uP~e3s8%KIp#o7hRU#Z zp@8t5j3K{Le&BZ`S5Z5>)*~v`FFtDN_knoRyHN9<3v>WpWUi%wQAgNW5)JbalnoTw zr^*aAp-MZl`*DVe2K(#S&S%U)S@^tTXi6}ifZa~4m?Jn1gbCHCVjQ0w>JM1wU(8+W z{Jfmuy=T?+d)4r_?DpAqy1GWyY((G0`KRN|b zMaHOE7*ma&B-Iwb8cg3i&eKtDuS8!no!gvu+zrNA7qqAwjxcJ>zF|!T&%-C%jUP0k z2&$xG5L`Eu9_w)o`)?>D05me7KH+l^y9H@gxeB?=!Igq{iU>c=hO|h%ex*1Y*|w4n zZN{Sv^AP&a)V%d2g=GVogy9Eo>nYe_Y2hl<38`uX1i^TY6WF~6y=_0*^ zT^FD9o^DG;UCoR)QMMz*_3NbVn~9!wIG^2yzsoXN$0cHpN+)atk5nH3nR_huJNi?u z=(m9h&UETwjtk-9>`6mJmwCJOUN`?@dA6j}B~$njV?=($7@_}VHvjJm^B)`L%IdO+ zmS{da8w@To>Qxi)v6vAOlM1u)_T&V`R0T~|%`~h=Xe4Y#S>`2zh&~ZtAht3E#PdUz zj$f0nkRzwq63asQK(r^>x7#_c?e1(I zu~jklTyu2!f}%2GMR8prF5z z=j#Jb^-USNZVFW7W$eK*U8gF5gU}NpjmE-ho2J2Sw&_XMY$*fD?u(wuuKrdH6JJ%ryzGVt=BAl;qbZ$2xZAl*KGKo zQchu!JazkfPj+=h5+XKDNw~u@yP_%CboSM4Ulfc=-$B#etVwr`5AKa8`DW}#v>s=! zL{SU!d?wOISz#~>>VKFGO6mP72cprFQ-MW%fiz{FBEAEQMf`yWV4N@`4HcBpJ#lW( z`lBGI4JG8!P;#*uyd$c?r-c`~XyTxp=r%tud-Xw-gyu}_=SCqyqD{P6KRI}Ds3?Bop4 z`3PS;@)&R4hvK~%vYVDh(->;kq**A4-9m70f9&KD;Fi73tEOX86Fss%vkO-`#dQi> zJZ4Zub)Ck{Yq%Ii4j7(~scaLmBqq)O-7BT@kr)(H0=i3>LgMxAY4*)RNCZ!eO@vJ_ zK^OCApCrcJnIndXyf0z!P>>?H()XRaKJ>!6c$)|Rs)l{f8YaBs6ZEBJ#Q8#4MMkeA zI%+vou;dg$(h^v6K*zj2=!|7+mH<5+?m53h=1l5Y=$r+SyxVN_1DE-}oA36w*)mWl zlsaLV>=`s_JQ@+ZHB3lR9IGEqbc^6p8Ane8kL+yEbZKNu9G1kKHAtaG(=U?$Vr-iM zNL72=jY2K{5=k4LuKnkRFvI0+_v0sKb$=Sde^Vb(G&Z$1wsHEeh&8vhQ8IS?FCfRT zvZkV@GSZie!(Px)lN=t|@Gli%2Ym1*0&9wv;)40K6_EY|=2Y%|I($$E88lninYDTJ zQB<7!e$e+hP;dK@1%6Yk+)uzS%*$3;1pp-whQlaCwbt%uo!yQr@2`*d)!pAi@(+jL zT1)ohu&^{P`oGA9X)fKyV`oB)RpL{T+Y82)f7R&I_+7dQ$GcibhK=siiXWPLhkhUtb72r^?J zrgvrZ0?id1=l{b|KtdN58>I$hr#f@n>?25l>I&Q(&04dl@i*?OFW!9 z5d%%6%qvq#0jl{%1)Ndo%&s}AJRXW3pjZWx!L{n~o3)xr>FaZQ*)^^CYhUR%Gntr- z%Xy=$qNwAX{3RNC1vyC=V0_NSgX)!kw)0Fjy1AKeGX0^{!N=}f3d08<7F{a)R#r|* zxqg#KF*izm=`4!lrF@(7LjBqMF%a2h2l!#)uS*)+!IQ*w0n(o3I(R%N&9iV+Nb^^q z>{8+ls<~DhyA8wr7yK)9&L-?$H!QWl0)Ka6hYxN~%Gxr*CpG@HY97~BISkasX&Zei#=SQI^%?2IegK1tavctxNWNeiSP9%p@|BETkEp`FGa ztds5n!92e;C^sitBho7{_U2jFXm6-5`Ee;Xm8m*j9UQk1-4H+&{S!C=*Q|2zKENSV z`&z+JCE>^}P$8oWVrs&HSeu$@+z^p0%0;%B9`ZI9zy9`uiQv5UABYb>TI8_XJ9nm{ zWU@Dx`BPygq54VWW^?MY;fxtp%$ajFImhnL8c61e->M~fNVyxeSW5#iZ^`bU{nj*Q zRx8-jTb?h6*tF-QppkUL3W3MhhW?`4N`+PeQ0gTeQ%fvqE2dIckQ==PyRB4{g$F~g z5jmwr_f@uSrvaK-)!heH^~KQ#R{9wU6f==FN9n%5{yE1eGU4<}|8#jiKTvM2|4Cio zU~Kgt6Po{U0}9(12{@Zu{r75-%0G^UbY@yF20NH?hdv4mGxfrob*~yWyAEwmjx0Dj^XJNw z=Jwcq=DmmA_v3T2_Ls|^2^wbHyd6~(iP44~7U|JlNl2o8SCADNei!n{=GgdtQV4## zmj;Zf#(YSl16oSyjQDw12&4l8!U%^}(jYN(p}vzr74$70?cX0svtYl1?<`Kci82si zx_$@#H}PRc3ZLf;jfUXzx&0)kZ4{@Lda5HSlX zd4|2hdIZy+2Vi$3ila-rfO7w@mha5dfMXd{u0p2s&ZJUn|CnPc$TF1Jxkfvn6|PHC zY|FWE1?5~>^UNRWtQNxBZd$cf{C;vZc)KoS;dsr9%Z%GfR9_BiE8||JX+_Q{q~Hn_ zw2PEmihSvJ&?WGRZ7tFx#DIo>gpo=$J$^%yr2ofOnIih?lT@cz+rt93;g-XfAhBI; zB565-EtSFpTOCyW?_G;%*JdQI!PBh@R{iWcWA}CrbZy!SFEF?q4uYfq;byu3J^iP>7(RHt_;Adq<4u+sni6O z&ie%uN*AO>^^w~aHE_NS3nV2CXF*lnKqvi|p6joa_EjqG<$^c`+SN4rtkL*9Wps6hFe#@+0Z3W)aP}ssr|JCU z@BJo%g$l9If(YDWYO=pYXQS&WD+T#Y7|RRz{PN}dX1;HIf=P?1evo(%kdjR**bQ6+m;qnJqgFZd|GW!g&TKqVw_SlD3 zxh1oMc7FLW>-@Or@I6R+D)M`g^ujh9=7yx`@@M8yopqU>@LEU2w99`><0VRn zMcW>o3Y4`&$VC`G{a}9|FeStt(l8yYYSdp@i%S^Vypl`+4A!ZMI|MW`WQ?J&T+|w$ z8~FtjA5Cwvs?v4y$n_G0`Z3(DH7==>Wcg_!L_Bkb-6WDMYGguBoy|l2uy-vyv}CR} z6n`WrDbif*Wp(fEJ$#SpL{IK{hi&`dDdL@pZkPIcKUiQjwt9bh`SR%e@yhYl<+}^v zJkvNp^o4W4b#;halfP2tJrsL*_Ai`*Dhk#R$Dd`q{U3)g=l>>6+Zb8>15Hq^YUzmi zgRD|%O+HReWRnjw#0mwwj0Il*|@JSg=3tSgoKww>$Nai(VX zYqr)GQV)$cR*zaH%uQoR9J|zri4aUr!9VOiSE3#HXYj*tV=a)se76x@oUwp--I*+R;c1w~;V$M{o#vda9bUwCAKTWg+8!~RD z!^60`=gJIHHkNai0KI#Ol2k@})!|0*xtBw%B8`#M->tU9`j{lgm9gy@BWluI=jrtG zqpym`_%A~Y08e+B!3z3Lz39}(#1zk^tSS3a$EkD%$5E-(l;&1+GU?4I_swlDHpUdo z35IhG_C}*+t!w=#sOWVWmf*_$edMDu{=;#h%W?~}4$72CK*Sl1VtU7giFw(s?;X=* zn$Mu?1iigBjFrc&gX5BJ?~A6z=PwRGj?}VcHa*mc6wL*_xkssXBlr3u6#?+^QPah> zj^o35tFMKFrnb#yDVm(Qx%smfJO5T3+1j~Q-cu=aGsL=u;3#Jm!KRVGZY*@QKTTDO zz#~SGPL>$ivTNv(Y`_QR^er8)@gaI#)i}Tr7U(pyMq)jG@DnbzZMn;_P^!t-d5luD z-m5~Dbp}(upNJ0A?5)R51iK;{uZeIIv%?YOr&Xmjq2_Lr%8@ly#08B8$U7BR3R@@! ziIJJ8NTIs?P@%hm9zk?Q+@Uiy2}}K94+KF#?w#Rl&D1)@Lbhuxa}Y1a3n4)e(CFwQ ztQ)Wx?kIE?@1X%V=?(Ury%a|=L3)C1G26l)cGG>X_i6rv1T}jdRJ4~bIsQM?;7SSMY(~g+BK$_kb?)QEQfdd2L4>l95RrGK zjm>wck6*k<^rm~FFZD6GXfNbGCVOZ&sq4VdnyO24OlLa8mS?ub;$alFY!g2s{OI%= z@J5$upg7INgI!eU-*uXDQ)7&1sHVBS3TX*9x9(w=mzvtdk-jfnk7@q5LxR9|i%Uy( z-shU)&!m(osgOB$=khT}w+ASCtaH=K%O4~2(XveDu>GZ#crbr4A3ycVa))yve2YvA z-g013>tvss*y<;dA9hv3^(YM`tW$Nd6lBW&r9c^wchYHSV)-(4C=fUFb zG%7i2ZdEe^S?V6cwp`pm%UAu06&meC#z03G=?VQBll;M1dpdeG|B>8S-fwK67)PiX zLMB*ld(i#EN3+iGFtg^}ydGHb2^k+f0K&5o7ON4MjonDPw#{m?0hu z$&e5%oVI9X9L0A!pzIr8+2If>v9Q}C7}2)F(pgRx zFW4(Kuui~ki^x@-VS0L}TLZivGzQuNXnT*ZC-9x&XILuAtS9w3lHX>wcN{E49`2gA z`7UhpoUzceZ_Vw&zSJR^0!a@*s%GRpmms@V+r0k1` z6nJD-ur9#dSRuwIzu_++;KObpIutylV6_Cp$cO((f&nY|w+HvWD)g)E5eR%K)%~R) z2<&dHqc1jF!6x+Co-XGdU49h$1|GWmfus^Ed~qpyq@VW=Fww+G)jlvDF!!A4lCs&B zE#(SeI(sTz;OD3XG9j+GUuq(QIb@n}J0~XXl)e~Iz!5zqHc`R>n_EWN0i9VUHXs}+ z`b@iRiFq2MN;`p`%qO0iwEv|dna`K&8Nk~iYOZ0CqtS51ecF0IvK(W4cjbVx=jsTu z15G8?QjqTSq&OpP>z6!}vwMD_Tyr8sfzmOwvk1J%JVCLc5lPi!_gh}~$2=RL9gowc zA7}+zkY4B&2(zVs+B90Ty@=Wv=uryJk0e$-R*ADyY99`}bjPk9^gzN@G$s#b-8XLA zq+DbPx86g!v8zdO(=|p4gwAzRLr+#*4_jLzihqQ&# zEiV9+a*aw32jfrP6-E_5I}ejw|NCp?EcJ&YeONTaH?PA7;=v1}cJH*;w*b^vMS#TI zwjk6oqEKJ#`wJ=9ixO!Ka7U!`YY@aAapS+`-vIyIauMx>@udH(y#)QVUH>DO%fGf< z|M8Y9I_dvowLV+%2Q?%Q$K9&Erhqc90SC-m5q8~DM@C=(|M62WxA5eld39RbZb-KL z+(1pa+rzy_y|XgFGc)jFyia|hTx#E>pjQIC8q7{lQ(;(J4iaO*bPTo9W^p5Bv*D!Eg)^?^P*c<2ww-$49$6!& z&K<96Swp*uqnXsY)FkrB&(mE24_nvm_n_08uJgr!^$Pcn#RpBCss=haU2N_E96WMJ z@M3uJAGJWSmbTQx|!-}=Qf;VlB*IKK=EeoTI78)Rl0PnZX(~aN1`mb1ZryB8bYrO@jQLG#~}jy~OnJ zl|ds~pRQsn)80VIQwgj zAW**x;FMYmY%uikxDA*cGEpCBdt{VKtLe@O?U)T$1T3}bF}CTYE!cEG?JB}sAkviD ziy=w^xoQYE^q$;{BZ{}*bl`>Ri%H6#cd_TR=_QMWw{{q{>7@Wgx8d%XZsq)s?EY-? zGxGHpB=wgpwaG~N-}rssg*+>m-z&*uZ(ffw|g*1JcCtHEB4 zmsYxtyIYMk@bCO)RHt~|vy<=gVPEq{ckGv2x-ZaPCBK~2=AH`gk~9NFK1Crm3LflY z3jTXXd2lm$C~=`jOH7Gq5!q4M(NM7&@T}|DVo{)K+RlX` zcv5DLgqY~;C>*2d@se4rB@|PT#BzO7!mue`XTOBcdU~0MVNqbs(+qfOwb7d|H5TmN z>_?Pga`Z`#LC_FUr^SjM#rnm1vQv_R_l5Jd^d2`7Np{X0E^Nu6(CFlV5q8%LiQ~g8m!{U`~5QD(uykRrIl( zsKuyk)eUmylOn^7s1odEPLCn44uwL)!ywUZo}(4d_wme;X~@tf0v>6?{+n0p7}C(} z&BDN$v9iRenS;E;E8JMpknBGKRtmZ}VlbYJ$x$(l%5_a+tvQLj? zQf@3s?)o59NRSFA)9R-h>o{}Ri{(Yw>#T419DzmOL<|B#*iBCkqZI6qfMWgTC7AC4 z@_Xmy2+TkBBoX%KUe@fUq9K!Opcw^C>*`mXJ3{bgEs@nd=Egm$jcGGT+UkK)Fn@VU zHi|Oeks_TjxZbXBc$k4opK`@-ZSTZ+{Iz=04q-{A+l3q9L&TAaE)C7f`7yvBGYa0K zL?g0@+|%&CceP%}FNNPrxvUghA11#z^G^ocSYXn}{faKuX$jDVU!R##^4!x(r>LDRG-o=ItLm>ID>puvp2jJO>{WZ8)nr7mwZ z?p{)F=ufFUve0Ca5G0TD%v@^_$FN?ff|ty&GG9T~II_TLZdqwrss6BRG>#NnpI=;T zO!noru4=U?4lRDp-0|w=Ecn!Mm5rs={JH;KPlU0yK(XbJY1Mj+Vcu#KO3i7UN{#JS zki@)1qv^|7&H*&0#HJuw{!( z)QT&-6s4~s9p9fkk{vf(&4I{cFkF!-$1EAmzKie_WGHH+p8>=SG310pIaOQ`eFRQ#^EuN@okvVGp%I^@?E*d4csJi${aLnvytSOy@ov2ylU)H#pz|L z(IstFK{8xCk_Yfl-#Sw_r;Hc_5)x!DqkX*F*`u&|73^Z7+z~T)&l22bFW_lM22(CSYJn3HNyk%<;YJC)jMR|OGL78K ziRr@SDWjTvCUMs1bfxiMWw4lUG)d%F9Tc*c1bNyg*A&9JlhVZ=s~@=}4As~QaOiap z5I3urWF`D9R5|EqvP4>#NoTDIx=ylOj(oMW3OOl9!;xzk4^T(sTN)L@L|=G|wipXt zaIfclmQJ#9@lu_Sw=p?R7E6jmQ>Y=Sp|c0|t_sV24M82&{9Tmy(flA(^NC4fN9p|8 zDJa?r+ClLp)b7j!W;0|RT^@~7ta_x% zr&HCNUm1c~c6a##c8B43s1m3Yt+>PRMEySF#l?;gyjW!ie1HE|39L%d_8Yc1JCP(a zW#kHvfUF~Q_#L^{ou(~Gqi0@p4v@la{ETMU{u@8V8%z&3MUQtS4|DpApAyCZ_HCWh zPgUNaxRY^V>{m$%${l_T)rj`zk+W8Dl%Z}{ki<<(S1maj)JYwI1J2}!HM`Prox*#Z)TxqxPk>SctC4l%}5El=>v zGNDgCNuT#8cB?Zmp$c*rWl0^_Mf4~o(Dm_lRnZTu9W?Nl<|77ZX<3vy4*zAUSh)Ja zp$jw(JI0^M#OA$&)BLLF*&)0HaoBX_+3ezsx zPVGUg`-y+9Oz+`%Vq4LxIJJ?us{~)7^h4{g_A^@LGvCisfAle*n$-}_HWxq275h(4 zXwjFXdp1W<&EGzl88ilodA+X$7{I3W8OhTDrts)^NG_5v(uj(?*(buq=}%xO4x!9$ zq$~&Yg_=R1mSv0EZK0i-YLTmZtx&8O5L;$_wu-R&w7DiYtj`U`_E+@;3{0*l_4)1G zb4h}do^i)1&kY@Whib~mdhJydZYZT0l!fe8*?dx~hL3DBRP@ z)*>dh+I?d{rN%Ap444^|t(fT+$?Ezm>Q#VA?ID1Rznm6^_XzFabpx5*0z%yZGVefx z-U-`KQ0>8F4@o*~(Qr%sXW`Cd_jtHMTlXB|s~uI~!tNmA0oV0|jlxUyP@rAzAH!w>3<*m+M~`0Wz2EZ+!vCt`u7&*p(> z`8x<5=fP%IvCal|N9uF=8ur9Fc{!`}_eoAy%_U?<$wS8tcAiakg{G%pbLx?uwP+p3 zSvdAwM*QZge2Iv1YWAp)#uHTs2h|~8=f=U+4R}>mivwGq$Ege!G*uM zXKP--@n7>>ShG#o@cY;~e_Y0+&mhq^V4TBT2BPmMmCr1q@8Ev^STER?`BvLv+8BB> zo}oeueM-R7&G5j2KN1qXfh#rxlI2g`AjS6p5`U58I!gT(273XLY8c6o`xUqm>)GWI zcvB>QISY_TpmPif#0J?(P0$nR4;2HLs|R%!KYYU%i=z2y?f51wU?jmH3yAqT!gRwL zDkt)eVSLw`L6}{8+%C}P3)?da@cTxRA}IkWn{DiYBrWb)1Q|o8h(lWxpbj~&TsXW4-Q@Iq`~|il37n? z>72)or2-yh?$EFBa@*O6(T4C2W`)*iBwPB9rOY|`e$Hd-Bq(~fu28**`Gw`#(q1EX z46lt&Xs5_xb|@q>6=fktkUh+wFMCR^4yT@odd@%)+T3PuaKYK=0Nx(L(V{qt-6p|N zr|nXx_?#!nVK4TKdee%r&|fSN_SA|7;Do^6JQz!|@q`nLo4i+F40x6$jLV5uIURE`wRBFsf!SLp+8TZ@U@2$gwm(P5;Q=Q!% z#|JftT*c$y~ z#F4D{pNxylOw3g{4ZN*N_Q^)25++>MpQy)UGS#OKxWLc@R8R$g0Str};35JHu zgbFrgNb(5X7cAb64Y^T^DaIA$FEBrR_DWx;vzm^YcsbkCH&7|LMc_Q0EMMufzgUn#@~ z-F~aEh#yTN*kj9Koc7M9L{O%yU`6G^5JIpo8jiBVGkCZN+${%*9N8m&>sWnM>coJ~ zZnUP;l%P+H{<;NrTW+?N(PWD&6n+uOZ62cb#+ft%kLw7^l7h{}H!QHs5A6)834>Fx zzehkr&z0@Eu?XKKCBO@30tcg&gpNbqLlY9sNXpZH47h{XkFJ@G)C8R%(!`FNpzn^? z)E){V&{}(-%-sPQSslcJ0Qo7<3LM+~w|){(+_g^bo@=(s{d~5gPA0Yv?9sS?O6KfV z?npk1*mC(oQ@5WdF|(ijZPUmf+oN&TKBuc09Ew;=%$%x9FIg-~G?K^(CRILX!G(Dc2Ml40G@D0(`cnAcU|m|3|;TzMj~B3%Mq)s9)Yo#oI+7%k8tjNuzF_>I0QF$$Yw zZ_^ho8@-Ux*r+XMW$RxLl0#>4N?87&X2gDWto|J#?!W(TB0ootY#rSHa~(2SVNzy* z4~cuQQbQgdI2Y6PAduurDjHH=HE2jwOfBXbC^QKMT?64;LX{}v=ZZ?wy^OV%nbO(8 zPJ8KT&U>1>r>_sN+-M9mBr>EjusxA7o5S1un7m+qJ2*Bq z01o&mhrM-${dl+7IcRGCbCVc7nNG4(`Cd*d273 zQjIOtZqBEjuikhkEXFX+OFdLzkIa)vzjWt>W^#uy#)sEW@HNE;Pk$@?-?4H;njb`Z z@RCR8!v#qsxriC~x*q%`o_7IOt^r#>ucNjc(L$wfJk=D^2A9~J|;@Bbn#mBss0 zH2;XHn?Ldr_x~zw{9oAQY!!7)L{lUm&;%(kLOcQt${PVN$*oi~YvA}2=z5zr7xV;p zkU=0)@Br)P%8d<`Yn#nTo#f4DcPqE5*~QVzoj$hVkBhiCNim`+ zEE%TJo2=bkQBYiEraa)W_pG@xMSwd8B*c7naHBOUjIebwq@!osv2mCcAH4a+&qx#x z3l5kcF%YzuDNnX(HQlSr@KTh~S6AtuZV@azHq8Bhl)Y1QW>K^*Sg~!}wr$(CZC1s$ zZQC~g*tYFdR7pAq-Q(W#bozBa@3H2bACuHjQYSebYGI^ylN%YO_dLlRC}M_6af?GK z;Kj>==MlxT0u?*TG9aAxpSi0kw9=p=+{8p6Yp{0)+2KCw4Mbo+(5|#~o2oo@hIkGx zsB5hx_rfDR*t)}B*nIyWpxM$#vB44ScSho1+aH*~Wnl*KKc(F}~`SW!eP89pUqHJNgY-HMb__5ClRrgcy=j*{t zxi0o~P1)$FO;+h0LMO!v82$%N=Ad!3Y$tMK^CfyNUYXF9WWVa9l!($5t5ZyDMqpuF zyc;~huIM0!n`9%bP77hZ!pK>$vOLbdTZM~tl4Yat$byu90^Nh|E!Wk}NKaz7JF9h) zYh`z&&;N=haJJ^oPLV&HyE@Gtyf>9Rc!@Acm_c>q#Be)k5zJPd+~b=dFe!oInPn$t zIPGl2J^x#_*+(EL>y%ZH=AXdb(%tNc8jyj~M9K|B_79I_K~VBCht8$){2g?w;a@2T zv5Ng=0vxs{gz+2N=M@6lQcOj(egtYDm-e_2+(9vZ~pA^6%s(nF1k`!Qn4ov4XoNX*l?+D>+%gsdrsj|Ey)u0 z9mX^E;lc;op5WYCnsjtt~E zw6e#2zimjdYwBmug@xzH_(Q>U4~ElK8)YXN?ga2-xHJcIX%FJk8U1Mfje7@sp2S@? zL2Om$R&NUR*KZ1Uk`d=@+oqEif6pFNc~sjM!F#Ovc{F5T+{u>J5O-~Rrf{lO8%em& zhayRbXF5e|9;M@Y{L6X$AHH(KKgnJdKWk*(k6?!V|MxslaQv@vK9aI2kD`Pc`Yor> z3@ZVH78f2~qF9Q{D;kZFC^@%4ZYhaqUuwn-+nQs}UE_ENra&68Ac{=TAIALUV=g%2 zXenZxdDF9Up?#V4d7E+`m$%ysl0I4li9bphpFA0?tIR|xDb9#h!h$A(X=Noh=_uW4 z4T=>|Uz5fvf#I1+r)}0^#6DG%saDBTs#a0KBQMud*;yl{@vOY8=AqL(vD{g-2?Ly$ zXt9jVeJG>rI<}+JuDdeaVKs@yY~!sIs=+$O?(z0(K+Gme;%qt-jZcFC!}TLaosx+; zz9mxIO0G%n{6nY!D%#*iD_s{6@_z5Fvfnle`MAn<&L|a(xhC&?Pg;zRMur9})sB19 z?72j(&erO$eynosii|hU%bW)1vZ!G#9CO)2hZa)~2Jl-IUJct!WRu;BC!M%BJqz&1 zx1J^ca()5NxAa7UOsf-IL!0_G{Y9Ou7tr=t6K78m8|Pbwx`pO|-IkIgfyn|kn&;L| zl<)woxmgq!*!GzcV;oZe1qM4$sykq%a`Wx5UQngbw4VNgmhMzCYZ8DU;O>d3i1Kui9o2jj6Ca1GlN@{Fzsu~5aPY#FJb>P zo3ZCmBQ5w@NjI^s2(Dv|kaoACC}S`vjn7+8tkH_}794>F5&tmX&3r99KuLUKuI4y3MM%lVcB>y|GF zRwxwZ!%)=wMa5)sxQ59?HzF1tlhyKpg<)s!bQWtdw_V$43}d(Scm31Y-~Xdn9N6j~ zoCox;UmZW)hC=_FpWn^?#~i>QV&r0CWNK#m-=#Kc|MBs^T|Sy#otP$p{!H^v}?3vfBH!uvGsF)Ql6v;|DdpNQfttasL@88fn&r6S&)S@87QQVftP0wlWBhP8>v3&0LZIS|r<)A}g zI~8aM!o*zxqQ+nK_)e`*1dRid*K*ho>U$<%-f)Fr#6gOnb}~?uwuu9~#s{hlf9=rO z@buXU$1d2sDp9__P?M)-uYt0Cn1g9|W*Pos<#iJq{zknbp>=?}XTlp;Bapw~PHDEg zbWtzCjtn<2`~JQn@B)N3yU8t91-)cLZDub*>wc8sVQ9kc=DvlFQtW&w8k*}Z6kHjy zvZz+f+IwW8`SyoErxGjw)cz^_9c8-#{HMT@N8QF~_cj`Pj1xN+{hO&4CNDX5M zh^qK3=XEb{F@x`!ubk^^jprwOhrY_1=fsuPnJ*%+ibA>sVD5#n!4&*Yo6b~V~L>5|5`h~t;^}38tN$Q%+ z3a)864CA%#GB(^biUm?(E;6P>HURdt$v~1322wutl%~-d(*bEX&V)j7tl}Y*E)N|p z53=?y%-S!d`%YmZ<;=l_c4_fj96pgyIpDb&QWq8a{*y!8g>|jQxke@)vB`9<4qyv) z@{*)xMN_3U@9x)yAX+(pa;X_T!Y8X-n^( zRQ~ujmLjp48CVy>TT>#Li!N-m5+S-bV&GV^=$GW2-J}+l5ox*IIas^KugrC+6F2Vb z(G}&(cj4Ik-vg>jWrMwK>`NdTg$1{)8E`aoV9`W8T(i}9^4bh7Jzaq4PMZ{YJ}fF( z(v0DP-YZf2;fwkb!!V4h9=Sl-g5z(>UGkYRAGz!xm|*YWVe;-VQD6(udpwlg5g?Sf zg9gGMxB?WOI$JyPn%tbR3h_73i24!%vy&z(e*_76Z&;ZuZ_I7hnQ&V~RRXJ1h2rd9 z{xAQPVT8LOLVwePu{GlZil1Zvg?kp1*~8O8ZqtL$_J!aOpb#^cw6)Ahva(Ls4GjXH z7IOXmC~|ib!f=E1S2BI*12dGq!9NiiqG!p9bq8!HR?!*bMTA|dU)=uF@D26QcQ+&p zAE^v0Xj*O8z8`CzM$%1sC#*Qz4X?Zrth74->o^0me6w^=RTW-WTO#NNO>cxu(TGov z8z~b}?vcSL<%;}d5Us8Yz&N%tbH4M|k8 z`L`r@*1owygj?NHswl!;jl>x3$?e+a9mD0|U2u;4G+Wc3#SJuU;!PRwW^Ymoh2VpU ze}p)Zx?lF%zo&H8$D3l#&yF8kX)~lFRM0!BC*>76PN+S`t0KMb1ta)EA2@o@%HSip zU`Ag#kq{sxa1%2oVUCNu{2w{4O}tN`jLV1pIT)a(ycE*$8(Nk)VB}96vdF)@DT5TC z*{JeV?fh5wWV1)Mo1Qn!X@$UGj?rsvkEO%-fm8_sup?&%SJASB)`6xsr+XJ zo8z_59S?PDih%vI!|akvip|=YeE=l7Y{?d)%R;Y34MC8+7f>}J15WWK9Aa06?J}oO zbwV^^!YYO0!A)o#gWsb4&Kr^Z0sTgxXn%+5;6-z+I*gmj6>T93uvC>Fk+#!Bo$`D3gD$q= zOJs%B&+NCi?aEG*u!caUowH7w1x;6tCZyl+Y(^&FG+dXt;*|7BYTJtv22ryE+Hr(q zJX~E-9R3^^jtE0f`mD+TE>FQB-l&O-dgxmc#Go7N1$$(dR`0ns>wf7Ww0>k=9=?8# z1f5@XaVdDC)_FdU+fMo>?PU$#;5L~t^-N(C5j_y+@jomJf(ae|eLUnz;mULEiZ!_` z0SZ9k2JyXRXyxJ7I7Txbwq4S`B=}d~v#Q?u;@NNLf`UfP-FT$itO0GIg8A2>uMkW8 zL%`U12n8lNM9*CTDS{kdlNrx5WQ)v);6L*A3C~*PLuSv7f#cJ!lH4$DHu-#NY?{NI zT9tIMZQS5L=pR?zpw$mc>Rz>{lK`|A@R7-pn`)gj<{Jq++ijw12lhfEk?DhC5Z&k9 zpkt*TTpbJD>9{8U7J;7iy?5xK_be?Gbxb#5utKBXoP@mir7^HzcNJ*EGTndDA}-M~ zKPvyRM7mwIRkQV(jQ-O%*#U&tp6uqk53C<`MWx!|>Ey3%B`#spYh4js2 z^kee|!ih9-dKoR`+>+`w6<~6VHkI$jl{`JMlw0^&|5YebR~Pv}N*qya62SjUFiORL zmC66^>TC4&&a(Z4Q9WwV0Q7r^>Xpa%6~gIXlKdNn%zPh~#P|Jy0|?GIqfDaE40&-g zjZJ#q2rS-{F4q})nzMh-!9fy4tIeUlGt6(1D=yV~=Q+P(>O=WA&i<5%bQuYgYP&Le zN}|eavo2sFHL1LG&5Fu;akV1-W$SVr+4pX!z+>P64b=@B3b+43`cQ*7`){Sc!b`&7y3LHQkD9A`*OLeO}%c-khUB0 zN|nldvHZ|Fb>yR}EJ<&?x)c=FGJOiZ?(ojO(e2$rH`;1Hxd++o4Ut*>p?W>aI`{Nj z(ooBln4Vw&I!AJPVoU?03!Qj(j8ho(iFPp;Ci|ceh3eoa(Qww}&@l|&Zy*x~p41BW zr3x6Q&Q8dzrIEG5&K4{U&I{fdA4&^kk%Ho1x3{W9^*OvZhvP zGlT~%efNkc)Hw41Gk%+yzBq8|Q~ zk?nEu>~ZVtgZn*uG?Z>2C>Y%=EwP6 ztBj@fu3$mgWt_W?a(q)hcV*2ryJ4f3A^5y(`SiK;y3BIDTzx&i98&xWZ74Am4(Tt( zEma+6s@Y3om!Fy34~&H5Pf->h5JRin`jQO^wd!CY36p{5*FYMo-uf!UOG`)@%8taX zEl(c{#I3FTF<}SV0Nd%rsy)zW%#`+@+YYQRwbj^FX}VuUT5y##_U@-Vv)NxFn|R+` zLs4_>be^vyYagFPE8T&H0-wuNA7(|UN(V!>N0dfBrX*Udp$uf>zkDG)wKu{AaTpW4G4kDGI@xFko6pK+av=^&!R!K$c9)dkQ;LX z|D+$AFa>5iPIo7-zHKxKdIR;=FfBV+FfDN9eS3dYk~osB@j zIRN&-dza?I+PrWO(D(yt#j8_w7friaX-&k2BcE;adac;K(?Enevv|rET6*{)JL)N>UQhsOhovcvl(HfmW}IJ47hu ztakPqWti>KN68+J^7D>8QJ8}mguqv_UkDL;{n21pl5bXD1BMB>74Ns17IEuVJmsxa zIwHH0GBt?38Qz1=5?p@|h%FB4a2jxn*@QJnJMC^BS5Q9az)5wo%|dMq!aGqeflts3 z_#0@W?9jWC3~w1Xd(S$%Q0Fw zzWH%J%X7~oq#etFm^_h#FM;2s1pDRvdd<>nAPF)8qH#<9$h~3qwJjq<1uSZ~liNS! zqxQ)CR=lXda8^wH%$=uUzcyAiX56yGX_BN7sFkb{B-WR;d51rm#oFeqFBGH(Oil{~pc%UT+MGIaoXOCQQWkFnDW2Lex z#iS%8)#Clv1~6US90n@JMqn1Xf4m5QBX0snJu_n)0uiTeIFyAPPUG+OWfuGPaS)Ys)KaGJG&wD=s{edcIe?rA9wx2le3;>jE ztemBaHmd{9x#iUOdJk}KbHaRtY7$#av|Q3gvb{XKC6vydAgz(Scv&+5CoF)k^Wa>%)OA-5jWarJD1a4<1KAT$(4?D;SAZK+TOdK@ zE7Ah$0t2UPs+l&me{^0sW$20^v;4A#e8Hei`s| zi7Jn|mUh5qx3q`0su%|XgcoPLWRO+>A=69hg&fe6g~8 z5%_Nd^{BzD1z)^p$k7L3TUe}x#z%RvrYqP^cXFhnNa{0AxoK)#jEsG(MIWMXQyZ*M z?Qi=6$-$iW6|$yM>F#+0HxX`4YfOzT9u0wLPFrt0_Rp#Mne8N%)_{o$xf|fIAlF$JO~J=sW!?PgZapf{o?4MB8YhAwUc z(wyjh#`7wK?LXYyab2}V1*~?oavWj9ZnjWjo`koV{F>}NX~uB6gx>@t6usfNXT+i1GShtEBrA$srX! zSSoE%5eeC^-|?( z!QDIg>^aGWSL@9ph=n6OIo@!4Bm0rDv$QcBXV1bQr}9JH!z+w{>?|TWgDQCNE`C}O zgqFn9I-@GO@Gj`3w#AmFW$E4)q-%gYGw1k40=i}G4zz^-;*zaBnh9lVjtcC9E8Qh6 z4KO4I=e6iM-F*yKhc}dnT_JhG>3C(Wx~%b_Moty+k%wZQLVJ_Un>BBSiDvh>o`^SB84}RS(Me&Hvea}AlG{+Z+b%AoLnAt5e?}=b>&ri0&M%{Ob8o(v} z>~+-eM@F-_eY+p#6;FRqBAJuJYze;<*uE?sR;8M^xFU2zzmIl>#-8-@o|%m^)--+L z)d=8`=q@kQi;8wf!upulB7%DEKS7PqXR#gKU^%j}K?(1MgyoDs)YSY}%zS8xf=33Q zXXHU*@QLEW`gbY*UyO>9R{t5?7s4ak9UU~EdmFBJIt~6 zvmmF5cu`|t(Gj)Q1;2$jMg%5hSsAh{-Q~(bZaCI| z26+&6Z^>xuU?WT_#BX%<<=3zm-n_UiiK3pFE)X0Y=!E^SQ{u{#A(D6X|Id@6{q^vpUw9SYz1$2-^RG^?8$qU*O zYBC+Dly+*8Nml9HfMiqU3J+8V&%W;dq)7||Uqy^&>cJ|THx%DHfmetnhdf#Gke1{# zW4Gt*p4qFO*_Z34*<3)~?=M%3eso`?(R)bN?D&0CP#dSp+=X!1G7`-I7UCQzr#1sJJ!g#QeIAch4wQlkNa+*~_Zd|`OeY7vA9p~~d z2o_^+s_4Mb(CYIe_>zSmv}Cc~i}Ly?v1z3`g^Jr9(30)T#C46HWSqh~HQ);4lKZ9) z=eAyh&AxP0T-|0BuIaT(%P5>s>xOa|rSYoCaC}}j5VMTl!d@lUA`A=9jioZQKt$sJ zpXQuozVB+ia3cK}pCrF-p#mo3>cFH(?X>l%Fpz__fXr4QNj5v~xkO@f+_c58WOGc{ zrZ99LHu*wZ(T-757^+$A{=geva~wgTl-CfsDDMSG>#*l+A~oE!n1EPj)XL0chRH?G ztxQhL#dEdQ8A2ZM6C5@_NT=#JDMV%bDa~GKp3VlJKKNFG%d(ytB~Oi`uMJ#fAQ>&M zM)_d=?YHF20Dlx=SK+muk`mO+_;}#hI0{EGI%cC#Mr&!=9QlH1|G>_{Vm#TC1}9f$ zt|BGgm?WY;Ut(iAOog^^LL^gJef-tAPW%_!{d2aFC4N{Pgo&4afgeOq)55R^R;(SI zd(J99SPLZ1C@3I?`6+VS2$$&&m_F>s6)o01tex&G{U#fhk>zyCJ>BrGe>y-5%Kn+a2Qp+a2{{Itv7{dgqTxfcdi`TWRXcEeRIHXC~Q(+&2U$ z+*Jh3-f<;W$X==PXYulL*_G;%v)FX{hsmsX;#Kn5V{NZPcSEFVsj^KH!|99!3G1F8 zzS#0HYq*x=g2+ew(w=~@Fq})W_=O<;E+8jlZWYb4s+w(51JjYj%eGM7eXr`QCwVS8Z*fj;KTHr*< z@yc2*&pn`1z9^piKvf_3lh`J2jfFXFv=6B-e}4xha*}$RcvToj0fE z*>YbL<0w}qkyo5N?@aBKpTt-q?zEEvD-2HL%T4}3uhq2RM-l{zwHfC%%6 z&6qkog&d0%w@aFx6%L9q$4ROk{tZ`%M^amg6OO35=*H?BS28_$axWR^{aAmCk#D9j z=b>8J%Y_|%Az{1e| zRH6#Y;*6x3+pZIH0`J+}=XT7od z`%|ATL0ueJ9PojA%yh#(L7Z_XQ|1d^zB!0Rs9mdEwyeilf=u?`4hQsh}Kg zoOrgtI|-{WVk%FMsI)R`k^K5n#cMb04BJTWI?)5&c4X0)cl}w70dU95!!69YJLHHP zJBDYg$otx~#L?-$$TbxFMS)u67J;t!8r-S#bbW15m;i?Jj&$vY*;AR+??Boj+Fo0M zF7!rzB1sX4{zYvw2D|7Lfx$PsK=V$vIpci(lNDEg2HJ|N(1*~KcdSRB=)bU;uPq~x zOC0da=Tb|8n>#8#=?HtCmgG5_3Fns?6KqRJ{fV_V>o%7tsRRXdQ3Q{A$T0(bM4Sw_?Vre zNcnWHT@Lk}rS0|}kF*`%@oU{+M0}Wf362HvPYsAP^3wM45l` z=~>BuB7?44plCKz)yXMYW&7<$+w7{K7aH>$TjDfT`^iD~r99KJy80w~_r)ps zKxs~CGlrkqaJ{w`3lntG%~{Q^ zu8*aj8oa_*0LiA5>4f!deaQi=F!%lkQ(m-LRFwLOVp%EPiJW*g7crFNN=xgIjA@GV zgA6p!R^9s7Wg;#_F4kRPnxgthX{ZV@Ka)v9ahHSdW=_wg_wE$c;8wtZA=ZANE3=f8 z>VTf^y)jUfHa(2a3i-!gT8gKjti)oY3xA zi7{u^g02l+9!m3I5(}f7F7T^$vrgx)zB@iQ_2_Dxo);>)ex^J*94KNVo6iGY_QIe`ZKlGDN@zSvOKNc6_1O8 zsvNmvOFog5F=(Nt$Px5l5Z~U^;TbBHUsYyVzKuDXo1knx6+A&sWbO@yFy&%}zCjKL zmr_vAvT27t_1seWn8EJSK<4MQAcVaQt5k8=1&%-V0!urDqRJ7dv)B*e7#Z-!x!ey- zOBx9T`^b{v+4gJx-Va2eKM5OKA`uGvL|u1p5_%T%wldLd@-8`m;6MuG(y=vwU?1uy zun-I6a^8Gz>@@=DM>DS*9Cz-y((mG_KL!KtAE^fZ1Nz_+ilaJ!2n=A50dgGGI$ zjh6>gr?p$YuMPOT>T3T*V3c7ec?w9FphyZRL;#tH$Q19hg6}r5l-DlkVC5JKkA&%; z<8|#QJ){$PjC|B-P#)_3w!__Ht69kGSjX3D^+%Cfi`0`9xV+JBV?W3?Bw(e(gf<8K%q8!&C z_V=M&5eUvcwvHnyEc+kyjY_ji*lZ;8M4*IeApM>bMA5wW%=EIgEseZ&?VdJN$Sif% zP9!~p?%=+(Eln5h4Awawd!Yg)lY1uVWIn4__Dr*5IfISWR0@;zd(-T7Q|2CG(N6gQ z1uX^n$v;xQGl8}4c6C446LdQ&0u6Y!K7>qi@uVCA9(H~i{$3n|+0|~z3F=_g=xv{0%hj`{hsMLT?nlI$fK3@s=%(}`b^#(Od%NjyB z7!p-8S5#>{eD2zoE|GUVFkcAYN?ATg5u;>)xm?Bpue!S#xr6kgmd*;>Rku>h1hBqZ z`t_PEX$GOwE>m&V5QH@4o_v=!n=Fyb_3zSFnR|v~Zg~6J z$Gws#>h-=2I+AS02J0dBC-d+OTtTmMx!o03Abp*>c??!i5_59J+R!8gm1*81Y_E#h6 zh-sPpxJWQiUJRoTVH=9=2#04%8VI^Go6_#MLy~{qyES9xHKV9FVHP>VRt{~0?}?_w z1LL`qa|#0KT66Rt>P9n~U7^j5;k@vQb0xo6QZ%?GHz;e5x*VkyZT=c}ho+A9q~{y) z#2$>+84-|=4)%EFEpA6i@$A8Mc{`cTeqlfeD~5Z4OAtnpBoz61S#K*hUP?w6w45p? zA-KIV9#FG3Nf%_4&>p>%!7jJz)x|5Ey5wAMr-Ga5(%Ry*QR=eR0*=eXlG;d!XQ|=} zY6$|^{4qW+Z-$iQ>;>TRs3+&;a>e^B7)rV7J1xF*NA(~)-;LkxXvp`FabskhaOsOY z>hjq%m}_baLo{eaWvo^!WXnKh4`LHsKzc5SX78a(qO3eKg{j<{YJ;8eHX0Jnx@83- zf`WekbFIJUm$OF5n-7Ix^y3+2y&8Jb4EGlZg;E|HA_RJ1#B`Z)U+#Pr`IMK1UqyBf zFP6KBCsZBY)U5`jK@6f4ZVIKHuMN2NhL##kCU(cx?0J{Y!=(q|K%IFn?5F};BCeRD z_a*hcFU?FZWtB>=ip_AB>PZ7dr~VR85HF)nE6;I;e>122wPY%*+ zukp$s81qBM+BU@DnqU{6tWiAI0s6uAq?-;HiaqPMCp-n%56(Jzc8FBv?(^xF5(%qcuW`S#NO5a`ls&N!tpri4;A-t3N=qB)H)ux zdwSQ$cKuZNlznav3ooM;7E8*z&iq~S{ztDPJAPaF3`?Hd1yZDnoY$N+?yz}cgf38W zlO{9i{qs&>+`%4g`tatKY|kQ{K0aN;RQIKw?Q5*>8JOVeTe$TMJozNed#CL9hqmv~ zlf5FZyo|`2Ba}2V?dU|b#Ac_}GwUTcYrYN1)nKIFRMn6n*sGz@rj4{YH zqUP*}uD<`FtN&Lb%>QpScCq?NX|{J2ayB#iKlkDibsZN}G1M>aE~CWhU>R7$LArcH zcaRK7pd<8-koHbcNkpOpGxxT2*X*1KmL{e@k?%0o50PrUS|czB%4Vs;RkXQY_XH6J zv+y{;9Y_SNAFc{{KYaCjw!_DK-|sKDe2m@VdblyTY{>$kv8)C(3b<_Pf`(SknlK^0 zY$+p1hm!b_O>a*L0zk>@esyZ7jwAf(v*SR&B(xo-+&=Nn@88Y=OTy+Fvy=~di8#OM24%hKS zHIB4Ee8}8m93*R6u?GIbHNYvLHw5;Kh0JdhyO31m+q<}6S+9=LH_$iqCb`eaMT3@9!rg7jSQ9C76dDB9YX1CW{{5L zKDUu(o|=K;T+Gq;AHSxa>&UXvC7jQ}N3m3DdZBSNJNMluqV)NsY4`GT)8>(3x$c1SnP@FP(_B}q61`cC*&H~ZC@z9t!cwx2 zs8Emucnh@u6mov#>+othMaBN5TBEE`syo0q!YZk2HtFTLSoV~4Eh2O@1k_^RKco=) zmbS^d8u295)>FuKN9K^b@1db|-DL(8LPgksOOuZ_tOg6`ML-qHU9C|s)7IX({# zk)H33@MXQC_6GEehDSzFT_3R6*{nWTFAlTf27JAG7#ySq)Uk%w zX^nmt?Sig|FCVYj1Gv-j_O3;H22XDNmt|Wwi1jU zU}G*^92MT!+6Q-#0Z6UWQbw^mSVI>bJhGbk_T%ODO;aYef`mO=Pje?TW_YM)=QH#l zvyhu_sq3R0N{I^S$oM7Ta@zB<;8Ere7^mWQzwxYMv1%hF?_lOqIuH`xVBj=vNSK01 zN5mtm%nePw&F5Sh8s~3Ii(rE z`V~%0e`xx(-IOHx3j#Zgur(J2Pm|u)kY2M+D1go3_}H5fzZB&4;;?p&VfG$V4|oas zR6GC{9)H<@*%ze^atDN2@^p1Yrdj{m_+2o3{7%izJ3I=xlQZ^Bnm584<3_q8*DgfM z;S_)5gW5(ctM}9zL5bkl1|&E92o5iXgE$_#vgdQ4 zfESj8H)VMWVryLS9oibLZ!fRf5b|>fyylF(AUCafAR^r1HT+hH7C^j>6MaJgzh{{| zU?07+K@|~T!W%}%HByyI6%~kO0>@jO_tqtOZm(TA#17}}c_aA0RX>um+mp>d#=e1{ zaB9*24RQWoWshOvlsr-Kg-kU1_py2=4ORAxzg8LuRpQb0qML|#~d3Fk>$*Er% z!?fgow5EhdW&ivl%96n@#lp_AU)T;JqL09yq&9d`s%fNcVMPE0RY)Q?Pdor12tI`_ zJZ|epK$)pv+Wpd+@@}(z-X3oa{6XsR2u2$4DOisV%LgK~tB0zv~Ro3^j)&aiy~3 zS;gQ=LzMy~0W9QWpzf&RTM=8yY-VVFtMm}5m#-c7{j({u9>ViSom?y+4xKu++<*%$ zc>so{8YV|+G_Kc`?!W^G{s=G!k@oGf;L z*_u~Jd?xN9!j!QAVZ6T5BGT$}WU2|MvPScybM@Yz{#vvQ{8JBeNkZRYx=dg=ut_GL z36!By4ole0Ei2k~l2ATV&s5!6yU-w}C?oFXZ1T=qEa!|YX4|TCmD-FE{9{c46JAOS z{OMYT3?Z|w*%sW+Y&nZc4{lS`DA?NxqeObF+$HUM)st7L0=+?!`1*hNs}-73IpaRrXHv7>x88QOjNN^|)hC)YVfx$ESF^i4}-T#G`L%80C6|$a86oifJbq z{_CkMM)TGCjb<2+XlwJz?>Nk4=7%8m>o`ntI?R5Tsem!}2yZ+qTr^AV>OVud7P6Df za|wgu0n^L<24m#i`6eK_s|{$ItjVrd!|Cuahuk}XHHVO~7wLbBhDJyyD?5YF+F=#O z%x&lQE+G|Hm_pjvbmy63s(?RcIgv{$`sHF8`<2C|&3W0V?o*t%{@}n5cmk|1U8Rqte^#=ZKKdwM9_#%W}TEHr5o zz;)F0yPek5M@TJ18^m#PkG1EggN^F`+56!n9TQVB&- zy5Dm&i*3v2Ff#p2h@OvjoqkwMp?c1IG?+yzH$#suC&e=l%W#Pv)OODG7hO#kdlRnW zbY%xZ7AcsXvKjD3Bn0f2Z>EuVIm7S(tr**yi^(q?B}IVeGj!L=aps}XEhrQ1nM6$@$fjkn;S zOsOo-0>Usn7i<@6Ii5y=qQ7Ne4N5IuiV^cPpf1LsU7t}dQy|5u)Y{{?_BmqR;1*oV z+P6eb2=9d{&rdMj7|5K`Rk3spwPpjI=vRaGX4ClZ1*qW);~1ywdZR`3OD*ab8Dc&C z9SiP;vI3ZYpfk572>rpdT?+B(ed zB!wJKx(@(8?5`^gXA2J>X6b+i2+NyrGLzqeFuS6hOYQGK+KCOte}Bzh*Vxs5B1hOV zvj=U=UE?4*J?70iK*pX9wqaxw2(ygws<ZrT^3K^vc{nJDvTN*J1JciZ{qO zbC1nCyLn4o`>6E~%cHNK%y(Mj{)P85fVYkE6kc2nQ34Ae@Qo~jL1qB-QBQ#F)s`Rh zD=MKY_U=rWPQe%W@E1!5QXX?#7aF9%D3NopB{X)Ntw9*g^SjA z=#o(cd?B-xYHfzl;ZVo=UrBppYL08WCQY2c$T@?kvo*n>fijW6xKK7nO$7R7`Gq~G z%zY|}Ce@6SZ}_g{_3L83QE%w^jvSa#eBD21PGnsl|D(#NE_5as9Oc(92!{Wqz3;!L zf{Xfp;LrcBma0elCqr=gCqpo6o_G3eJrQF)mE%YcTMAu&ps~g<7$blVXLBu;X~4va z7B1Q%G$X+T5(pT6-6}I=MLwSvia3~@{EyiIZLyT3NG8|zv7`0pE^ zNAqIMmGtIMe%IZumu;WaZ`zk^CWXG&gg6jQ+P1KJIA4}Phy6`%Ux~8a7(<|5i~+M( zf6&=|2&AX|FeBXII9E^oFainA?tBM>L9a@N6K`9eK-8zT^rEWsw z>D)n;{wemUT}W$&g~O|Hb8<&78g;`jBP~hAz@xZwGX-LC=j_W*c^NrcA@)nQt)D1j1+340&-+EfwhZO;;p#Fe`+;wbCjM!q{n{4xpnwa#_`v zFh$!T*Cdi#trU!;jv!qnt8e@Gr_d&pw9-Zy#wG5OOuA_n3`uW03W?GutG%zNQ&o{P zimz-e;12+8`w8k742iW?Hys-fyvowzC>(Q)E`}YvT|EbeWce(FX(Ld;Q+T_a9|`g& z$RBIh0Zz3)+DlsM9a;?SiQ7nWdD~g|<`(lLpY2X}LtXzO5@am^UTZa(=V{`50v(B0 z=Lv2**k`5P<+Gi23^(dDA%ZTmnZMm-hy6#jejajK&6QMd=3ZiZQZ(*J8iZuD<+5dK5FsTaDpGkECFy za9(-~e28@|eYm%!-znOC&QH`#k|cY$7CpPle7|38kFrm{RX|)SIR+OlyS&z6G38{y zn2p2p9|-N4pq$Is7ieI}B|s7hjKUFji+(s{TqwHNgY?Su*$i~hsm5s@>{NTnG7`9#F5(>2G54n7z=X1t zI!ZGE%m!*?dAr+J%?_EsJr$zQZR|P{c>lf1lfSletE713bh27t%j;Q9Gmk`{nqx;Y z$TlQ#7@+Xr+K`o*+F0m&VU}e(r6vWoGC%Q~q_XKFS`^drcmQkD7JH)IhMzv~Fhov+ zwZ*T+k6)neX-lhE*0Qt|3FOK`|4qg)Wf;pI_-anYB=WN*@!{c%pCxNHEg$cg0?7bf zd^%`I3c9bFN%n}6r@kmq3(suVkZDk*&fPZk;HY{aHqn zLw%QNOUj6t8tJ&qRh*UC0lQ46_`c3w5x;OY+e9&WmJoZVjm7KC*r82l zF@Gy6+Zm0l?9LST=<|!-7K^&L;h9{!PG#%-$e~@IfP~uij$W4PNT~hTou>7$Z|lnK z%nIoRoB%W{>z*T3?Fo~lEUY$az_RKD7Eor>uNzfr>&?Ynz<&~L2# z4wKWdTf}v4ceK5OU&JPr!1SF5B43PR~60aTrM(ng=h^;&eO}F9B z=KqJab8zki+|qPM9ot67=-BAkwr$&Xez9%awr$%^I<_`Bh-I}etRr^1D-_d){ z^DcJ^wr_~~2d(p64n1I;-;00H41I|0+}^Ht*7V@hbdy!Ms)4&1 zl|Bv%&A-;KrRYfOGaFsM3Z!=U3s-W))#6+&i7DoXwy72PF8)e#O%Z9+4{4ymaPGSb zv%voJR0_hb?hwbWFA8>e$)s4DQTvqn&AgF{IWpYHJez$qLc>_55V7TYMblpg32Xg` z?iI3^MkU_fp_rEFj%V!jLUGJD)J05xC<`0nJ~-z)b9*zebHPKHy~&pw1qu*Ao!g@ zV-R5>(i8*dnL@i*Ya=>VR$MLaUVof;k-v0Jn&wFv7sQI$Z6 zzRTybx@#?HJtd}WQ^^f@!EiSbqCxVP6Jw9k&XQME-Pq>q@A4(&n&LF8gRwBWH~|>7 zDjuN2iAY@Z{xdqUGwb3Ik#`VkYk;;uV%`L&s}EuX_TZgga&9>LCm8!qbN6D3`2EkO z^YJBk{F4OMdt;zBMtumRa73etVq0Scgpzmz+EokgRE+{D453n*sb`gvxP1+L6$j#( z&GM{)rvWL`iI`hs5rh*s0|WCZi&A37*#m9@C0~NGpc19u8s*G*ZpQE;qwZ4(M^O#$;3JQ_ zNcMW!nkfgLYrmcz8DrC3e%j-cwu*?5Ji$e8aB|yIv^n@ryOXeHA1wTpnUycD&EcG3 z3JlW7n-oxGVu0b{|6ME>5UtO_6MT1V*I&C;c<4|;HtZ5R@5oV$4H$cqj($Quz&)r} zaAQrlj*7F{C-+B5&fgR;Ju|n4%o4n_{#cSnv8OL%^G@UU4(8_(3h+j&zh=jJFk{-k zQ;qWJ!}9dAyB9i2pvBzHucGU^&P36^8zC|#Q+Z}~jOy;)#)9t-u-+@X(SYyk0Cct} zhAh!L&Wyf%9?%Cy=MRy~6Zfsg>D3n&h9sjhccCD&dnXRPu!_GhPjyMJboZis(iqR! zFrs^dQ{8A8=Uy3bXYGID(=C67wqChi3`4(mE6refixYb*>yLFr3ul?)6c62{7KRsj zHlsfjhxxS)GWs<#noS%&<+hp~iB;&jItyut>zPsb9hMAQ?5rk3OXqkcjhkD~Qzt8F z(5a(S3tgD{!gSHU4i)yZV)?2>0blEJAY4NWuhOPI`V79^EQ6l1cF1~%qgB!NU)7*> zA6vbE`@e};4u*GhX!lX2Qg{g3(kSfvg4)$9?l?p4!cA>cB(`Yuxylkf$0+KB-bIhH zd5RD1-^oYPN<`8R@eKVXPP^k~^5fV>a=27u5^Pk1t|L-4Tmp8T(P^L8$=CA~;1Lkm z5~{B>f+NSewVqCOs?wv|WUg zpDD}LFn3mbId;8`kEUJrJX-aTbZ$$2Sxh`yEGVC2En})6IbxpFbcZUBx+~&|Z2Fr9 zaV(%Z2|a{*n*K{DEl4~y64D;;Xhd`3u!9fn`O~VliyUI^1bS|S$vdA+lyK;Zw+F&L zx2TM{pRq7}VeO5Ms!X;K{>%K})mpA(n)Io7cC#`*p|Df? zSJh7>r9PY+O$ZNpvDJv%&9aB-lsJ1_csD;c*AwsdWkY}m3(G<}KR^BDERJT9thUQb zxfh#?eRId|;C#pcjF#rZR}Z!NLRj@tx5LpO@1oF0doZRIOKvrKaZbaYs`7?JV_yu- zxbNl1`OokcnxXA_enp?p9NJ6l4&J3S&-tVyvKfl|6!$JoN>`+0`W&@lcs+++%qawJ zGw~!s>E&GQI#ptJrcqzoMW*2U5J@F1y}gsb-kR0qhSlL~L^P5r#c2mx3WF~*%iV{~ zMv@ijm!>ALbN{Ts^!v%smdZ-~C3?l5Yv4NWRjpNf^qQZbO&S{vgF(!OOHjTZ*v_ge z+|P3czr;CP!`ya^i;6Yqw6w#6?5XIxU?=TSD(yL!#?EV%wgRngl{IOsgVv?l(Qk)5 zQKIFJu!8ELyhjkuD6}!g-?N4={3v;oX|p27!k=am&RBCq5u)dE2E8Ojz$u zQMJ<#f=`l))}UAgS7~Zh%uUUBg0!rhGe6YQLO$X@K%F&U=$prM)YLTGV31+aP@S9! zVa-jP^b1~}WIvwf2Jw0<-C_toyenehX9w~4l07jv599_8h~ch1y190@*&-`^{`A>G z>w(#{%v-ivI@UJ6kfT-@BCYJQ>OkVh1-5z&bhI{0kgOL*$BO&*Hm5u7kx81o83N&; z=~hWtazt`(cG$PK15*Vw@}b9{N;UGq#{-f$`s$xdWi>=z|0cGbvyo!{;MZJXWf(xC zJh^2LUO{*LYJIQ{bsgOzvePB<8l^|QiC_+9^ zR5P^pG=kfbbw;zMmPRJ8M#zyGYSY~PjdZ76;nmM!WV%SxaQGiT`SC4TVi-rcf<~Pr z^8qbd=N~KzDzl0;BMK?}0ifIgIQu4Vff*?+UnuiJH_d*|P>>e%e3n55G<=nfF!Pm% zDJGS?0gXsfOO({j$_9yzKhZTTYG=)d3zvjI@ANgToiKXfkTTp9_p?hQCXqP&edx{Q z9Pw*#d2~5pYLeWjel!h&O!NxBQ*BeGG-xEOFYgP0_vRh zxaYZ|F+@p^e-`zjKMZJu)GJ43UMjp#q#>D{!_iq_|0Lm#yj)qHu%XgnRs8&r`DeX_ z`Pv3M?OSij`qo>7{(Zeg&PmVE(cZ}De-K=h*KJUgFutUo7(7y&R@Be@cl>?Z*OP?8 zHPy+LbByfNlAI~zc~k#NH!Bn~UQSpRmgFEJ>qvSs$QeYWiG~u3>$iTJ)!g=)eBnK~ z7&osIHCP(dc}%%<-EG>Ke0vQiZ~wR$0!Os$g~UvSNV6r1FtL*#vOoka3#m|*4kJMy zG&}gU;>)<_Ow7Mg(t|o+uRN5}!bv8o6HO3&=WovC58Z;%R|v7yla8nvOliQcnT;SJ zCoC3BN+{d3xJm)(Q72?U%3VEuPBP3uS!JCn?Y`zTXEd31V>;I#$h54DfmZBYx7McS zOl&zKU4Cbt#wE;A&iT8Vzv{4T$j(8#uc{HWSS$~7se;t?h|+wtk=Y8?z_GqSONW`?7j?b9KWP9T|e_bfdsR& zjIYU$)t0Tnou*m=9HU(HK{Jq|c7)dXQ=C8*-6Z+vvlaF9L;xeBc~sOEkbl!3{c%@H zdxGrE zmwtIsT8LsO31CzT0Q(9YQm5}xZ=|X(Q=?SHtT$B@q$gdrPUfgy){XLRRClk?#J56B zGLDO0Q@{K?w6+zbsxEc`c0irfjszgm>SvtAo#AL~j(P9NS5rmprEV1cqgi>moZ#ipRG2cm!MBOtK|w$R!ebP) z!XY+qh#l=VMIn0#pKIYOSE8lZBs)P)9{+Nwo$Rt=afz-BgaDgel$uF;n5@OVV(w<2 z=?CE5qRK?blaz$Tvi0he(MQLrmz)sScb@|hNwJB@{%s3C(IOvLZknOp!N_BiE)P~# zT~=$ZLhf8SSG!~Ixv#cDPrv45$vEo@FSZwK zbn<9he|zup@{8iN4D;kTx`LpVdjd1yn|m7lrh`5~Yz@ThAhFf)d<6AeT!w*B{C330 z^~zN}fgkxyzkXX|*%C3KMv1@LPxac3x;_pq($5tN^lG*NElB_JbQjp;_}i`J;st}N zDZ{|0*07HfPB^@9e0l+)Z3KEpzJpWx+paU>u7+-k?2@Po@eYzcn{O~VrH7S#kMnjT zF9Tohk)LgJ=YYcC4+uK34;r{F0)v}NUi2X9~C2zuoE(I z!}qfl{SMnleEUnvk*7Dg6WoDmxnMQw*cbqXT#>6ieS*i~kkNUNZ*kgI8ghQf zelUvn`7ak|w=4K)G~YW*yWei}y#Idimp8IC`Zk3A2FICMJN|c}Ju~S)7#qXg$$`|= z63Fvt(82{XaXK^<13mRm&iDv$ySUs1G!^xN|BbFc9zM=;xPt8d`j}_@fh;Qov!_h^dlwh+YL6Re9@rRk z0syhoBznsZ*< zP>QBtiE9E?VDS=munsjI;WZqvX{awcu$Ork@@ROTpb_l)wof~ovvRa6=^5gOiaEro zVA6u-u|ScsuTj_yBjB=CDc)gVQKtLGSb?#oH#@UQXB1qHYsOf>DKXiRYbKuM<4EL) zdI!TgWj;z!g^kKsOK)t*@ z-Z$SN@4WbDD9P6flGAm7@j?nL-wqh2~5ur&28;oMQy||!3W?Y*Ei3gZefuDqgO<% z0v#@M#P^RmN?r^1DJHo2-%^bEt(*EVt%@ z2WVKhy9~jtS9bFir=Q{n=EwTMjV5d_a-g>vFRVb0U7B~13BnL{p+VFqYKy?W)`9J` zAFiz*mueS)=qMw0Eh@xIbcH7^|8qU|pxz*Y_}6EjA_Id`zXhC#;hBizOSz6qtQG!!lL2L{;&3>n1z``6?L^W50qdy$B zvx5M|or+hWQvZtq45BzJx;hWLs816CtgN0dYG94Hs|AWHYW#}nPM}zMRJ`?0LFr#s zt_(5wV~MpFGd!n$9j+mQXr34e&Fb9;-W0` z7*;l6Y@Ga*+p24+;p6qMe{o1`Xu9h1{tiVG-=Rq0-w#C!W>&VA|6>*UU&NnPLqh68 z{s7^MA$1F+9KQ9ucTPaEyWoKG89o==va&v(ZRjyL^Qjh&_{Y_p)1G4C_W8o=4WOe$xKRoQ=*P` zM&kygL(@cJQHBEDxIz)tqNju0-7!i0)wTth$*8>)^JGPek=jJW_+VjOBK#EdP=E~i zxX}_}y()_7`d0=e*}PJVcp0GW*sdP>mu-pROX8#mu37xDBZCrm{lnZir}MX6YkaAh zU&-)E&`T{rd~oJyiHy2DBRHh>dXk1;-TmD7T9#rW!iEs_^&cwP=1XGT3t(GX!qkbD z`U|uQ!cy`1SIa6H%2Z5O%w^Ct7h=|(wj-@VP7oT`*&f6J#{<}oC`1riLTg==W!MNk z%^g7}PwbSQ0zZn48N2x&8p-n%dRy!(Xh)efabC!6u?eyE6guRxq%abt#K6mE&6?8w zK*r?j<4VDxu&0#zxSLN_OU|~uy!tVH!%m6^9JRelIVg;WJGifeoo*RrDbYsOub#-7 zR{+j>f4zM<*R*|UaOgehhEY}}345mm>V8-I;yA}6gh7%-6xD~@Uk z?qz1ySP)8kBQr;4ceMVT*?Fr78gSCez1ZB?;_QlSsUspRtbfEcpN z{BVLnTYtt?epJ{PFuB;%(Z$ly{|SHc0&Bo!$f}I9NeUkzSSV_na%C@?A4$DMxOlk$ zkah&&c&4gIqQZ}Vl!n?N=jmM+T%Zf_a1DVd%&|$8GU<8Ax0;dxU(J5O{^vGZx1WW~ z;+s#+_I=;4`2P^zT#SrtEzPX|j|fLB<7{Masb|agee(amsl7@PHuJm)pDcF!Oip6h zWBFX-xw*vB*v&H_=0HG7e6e{bv1K7!I3|gf2P?hKb7(+cejwnVKc2)<5Wojg@q<#F zfaR%#pv1gXr>CYo@1BRwb-KI1e#8u`1KVw2g#f@>3y@nPg-~6PMhMFJRbmxt2j~dj z9lzq{W;(Rg+|~5fBB2RXr#{O*0;lq6=@v8jOHnQq2l-#gktx5j42Cw>N|S5!i3d`J3_5et5FGTtD9%s&#lAIjDA~k zSuVDFht;h=Qh!Q{${)Qp(Qngn76lV0F`K3=#PkFLI(~vEj_v8zDpi=bgnPgg))yw~ zi65D)6e%z^QNdb+!N7ktcNP=mV~Q<)=!xZtecLr6T$A=ol4@{e5+L5VF8oIDDl)Rm zwhl$j+wI5n91Vz?&2e7ko|sQW2alv8a>Sn7nMsRN5NvaZiC!S17kV>`%bMp|IMM07 z-=HUIvyV8DGfjaaJh-6(hOx~m2|blYi>0 zqMoMA&cQj&MkKt!*2hO&&L;!b=8ISYDcg(b(MG<&ZtLCVA8x1fYy5rR4QhX2mk2Cz ziY114tJ&i;A%`^Y=o~;C)NrR4n6Btc4ImBv1V{J%^UsePflU{f?)zaU!~ggp{Qu!` z|0ltsQAJG!MFrv09Sti@IKpCPKr({9KQEwt2e8M0_>&02KWuHHVTR2y{_oG(i?G$F zLCa{j8$ur9V{&t@(a})>yj%2Joj6Y?6HrOeVG{7yDyQ-;b?e25GOK=wcKXj$*4uAQCznO~*INflT+)is%vyr1;ro%+lxXR|J4Ag zYfe@^Hd|1@oHGdc_m#7Ru#r}3HpI+-BRE@o#-kyvVzA%>7IH2l$sp zyDqqF@YSVkvsgxIP-Kl3uVoM%C=1yRHHoa_EHBjA7*OuiOjO(D{@LgJCBc$UAqN1 ztE&FJ$4M4nCb5uE;WR(A$2vP^Ngy|RA!^JicfFP#%Cr*}C`2~oYW9s|h;RI0YEgoz zoO)~OE7R~#yp8aqC(Bj<${j#-TyP#{uE;{Z<8j%TQYZtBg{o;-sei#Eip>d4^<)as zNqf&AxcnJ4AgVGNOtnhEWD@iX{l4Zl0@0R?Q!yn)0Xvh>pz2Zev{#OKQY5o7qZeQ< zSEgrF?~G7kZfUt-tEW6D9)0}P&lDRbY_=p;pOtjRaULw-d}0{ZJ&t`>|L84ZeF#v~ zt4vfh(#L;HH~kSm(097Axc9cCCZ!xL$T0;D^$9E6%s z{yyVBE;4Y@B|XEcnJ&6?$Z9MprjKb{=oabeEKS@Hk2cjun5dS-^P0@exQ;t?-m^ckqvMU@u^st+hdyWR88mS>l%{6{42Hi|=rmxDsEHvI$K)94} zG2Wh+$)7NtB+UP95kW0m+S^Fm@iePzd|**7!s7h9g{Ar=w)g-4BH20Yf(<_&{#Q=ZBsm%2cS zhsjG=Hx=3O&0xVwtj;I6yPf~*$iZ-u+Opz3=a_RBp%BlsmAy!&EQ7Z*Va__2*E#uJ zoO-pT@xyb5>=}q&umH;zCH6JP-PmfeUa@95mma7}uRW=L{~Z!Rue;(eC6N;=!eu*P z1$E%Ofr`Amyc_sK#EE6l8FJ6ESz*Yy>pZxS0(&Zu8a zv%D4W@wzy0-%#)m7P!lJQhk3(JTm`$Fma#Xt=c)+3)v7C7bYViN*s2@N z#z+#+G*&HukaPq83R0G^w7WFto2%`jM4d4ECN0HJh}`>`?auovxeI=LID47^1E>q( zK2Zkg0Te`eYO+CVEIo9}8GmlYD!_+!nq0<=oFXOBRZJeGPn^2Rwlp-&IKpw=5k^Qj z=nZj_Pq^#-T-s0I#p>PIBUh<+my_D?P$6_KcUUU(J?2^qS4)T0y*e9NH5Fu&(81qA z#g!dNlt0KI?ubP|V5+G3)?f}4A_5837YIA_UU!*3=<*UcRDUoXo;u^mW+lz?6`E#v zVaXu7lO%Et$VqBNvS^2}xhX63It%Nu!!Gipl_ zc}`pkD3$v)NSlRAzfP1d5xH{t6UEX^RDi$!#UM%mI+yMIyPwSX?k5HR{hXv|V{2n+ zW8(H7d&7GFsI7$T?QQJ;Gb0r$X*nP(VR%cYk*3GY)$!)~wo&S1L&?d?m3oqBwAD=} zS&|Dcv`9(!FByH0x5lo2y|*oKUg6M!7EbuGY1{2a+j~xru2mII(CrItUEFnSUu8XA z==l732k8;3K))eM1MBd$5HhUI7Y)D)GtmN`Y1RGlS`nU!!cu?;Il@%$i^bdSuNZ*b>^fO%@|^cJ8hyZ z_4S}_K=gm6>8Vt@YgI$R3e92%FCvUl_v2}>9)}c2{R~z@2gn7F2FrDCY{{kPkHvE_ zG)pF!U{q^qFho)&uA@!GDVKy488se1ocE&6tsq zMw?nL?Q%P?`pL#xYT^0f(a1Wm{x#>XOh&hpldWCY*#;?@ELqDI#;JrFux$y}8;)Mu z_#5iGdh=>Gp>mTs`>x~CCr}wNtItK)^qQ{9!8y|cB0Xbm`>mVej3?mQGwt=%F^VU7 zLS?L+R-yL>+;}!h1F~6JZl75a2NgDvmWJ-ZaE7rXLU=;b(Is9e)**aUc66Rat!{iF zSwh{Mm{U$2hE*t;XEbZQ1Q*wCDw6vnjBA_}M2WU|DiSFH%6-W>pu<_!)kr)&?7N=1 zflqwQGdxn(w3l<{v!|24iT1X!^2!L=#9=J%%YOcz$|k+h>iH zx6EqYD{5QE`d*DBhzYwr{5y7&4sHct0c`f|n&H;=)A=L($Y#jJ0E669x|E73)~bdPc}&D`kFyaauzQamg0K~=4vIL3ehd$`vuswz~e76gX|OwcvT zZte^2IZYkp{R2B!k$k%+knHd?rT*k&ISG7brJ_Qp4}v8QZG^DbsgLe}k8C~-DvJe3 ztz?u9pEEkRMtz4&@9a0N+Ey9--s0%1>&5I%<19gsB6jT}k58etpdtF8~ zcYL1-M0a}MRW7-*AkTc(sE<0HU3@&O6~buvx3fYxwrkn@j|$Ic9)^0Ens%;szt}&q zozb)Ha}P+PVi9rg0Gn(={V)JC*nWA~T1B zTD(dZQYD^#D*xE>G(K@os<5F3A)1>+K%)7m?B;MJAs2GnRRRumy zae*QZ>;Yjk&pqYf_e1Ch)vb$BwuVEf7>i-;+iQhC^A*3HanE&TsYHO+Y z&961F*RwUH6*RN|58{uIp~-(abXF?AdEh8vdXuegu1-kD8KnoJRDjYNF^aW+vwKM! z)RESN;~Qcyh(nFyGA&81o%=3`t$4%peD}a;U3;<7XDNkq*DN*4z=VWus-C$%Pl9}U ze0aU4CoV|hA*b^`>5e_`GVeCKuQJ_kbfz_dYmjM2Rh0>L1%b(G<9ae%B&e_AZ$3!} zY$^TPZ-BtCq(hhC0f%_Co}$$F%HMV{$O|9(FQUji0PulZdNBN9GSI7dD7=AU&bO=;u8DkQcN}P75vEkS~fiNgXMi!{)=sd zaaPofp=Lu*jiGp?1ffk9s43Xh48mLw!y)w%>hy8rCey=YoOwzV#ye^H`jlnpw#@p6 z!PbZt4gx|33k_%|K!lna;oOd@CYp|ea-WfD;Zq&*kvQ#B1*Hc#9nghl99uKeU+;*ls4&~Qh3ZiZ^wbIi2S z`Fzi`emwf{DA7TJ{rDLUeYA+$!XUH$teN7Vp@mT9{9`hwb(AX}1qPCaMRH7M z7!4Qdbikh4{Ntocphj_XunS@9Z1jqgajzqsC|jeMH>={IPZvhTisBP7W(+q)5~?w~$usV9yf-s98Tz<=r<2eL0MZAE$EEb+HWNFZ={@Af=5wF%U`c0ft$o5TyjPhvzBdnM>j+2>H90wHpA#CBU z4KorfFiW(McJH)s@YHRWkVI@A{(G&c#IyJ+(#$9da;n7u`_&&?Cckk2`ZD*JsGvie zI_ea9$i3LDm-@h!Z?~1vkbRi?(W_ubTXCq8#AArf5Rj*(e7+)`%h#8527%vT;`Cm+e3Mw{^?Oe^|&ET#IK3~Xz9B#RwdRJ z=|dq?I_Jv_;@yvum$HQbp&ru{?E3?ncQ9)&;_c)H;ctGSe(bFbbVNu{W(_(pcvpP8 zy=V`~D80k{Nxtz1=*E?CtAPn5bXA@W=B%sZVKoQ!QWsC&%ccG;ZttJv zXpEHzO`8ul7!ng<@@sEQ99(8~8+X4q-f?GXA8kQUswzbmB(|)E?@Lk7s}-dC>kS4Z zV~5I~f0|2(&RH%UbvBjp9L(XKJKfD|yH*<}&z)UwIiH$O6#}1RCM_u*@fK%*(upG;79)Rxd9 zgNtLFM;sp7B@G}kFZ9LFsixPa|GJkaH`10p3_FZX(!q`cp-Jnqd9+*&!>HrHaSv5& zH%uAQN&;#Qf-LLog`{$auDZYN44;O8STp2v9r}aOW&7|-kltl`|4NX{g>0dfQ*UB3 zOm{%+k;bFJu>(Wi+x*3GpfqXrTbO^7>s_X2aef>uRsGW;(he9 z_dBxOD;Uo5RIXV3QjRINg{Wz#g>2*F2v%BS7LxkXOxdNXfIMgTftuz}Wuk_)&VoS! zmoR0s%o0a_=B=Bhnn3yzg^TcXz85U638nQD-yw(4arDe}rD<<_Em*T2)mDE4{;>1L z)cq+}<`A3l2=hiw6fc^!{)$vxjvza6G7^}XZE3TDPrgJLk>GQ|4P4P5@t1LWe(|CS z@}c)@Mr)2Ah#{JjRue{3?ZHY{{x+(8ayWNEZ*CeXkzom=Cctw^RGYh-B`vrP?P3_6 zh^#?uvrSmC`+*$vQDF$XhIIakvb!$&41z7}rZ0*m*ORU)p|(V(FIlp?8vH?vIoDpf zeF}*wZ=Sm?H4}ef#{S6u!ml3L9+RfRwkg55a-}4A*DbkOUkSzLR`E~5heG9<%7Qb) zBw1mAx0z!ARvl=aO1-|nc={#k`irXayc^906fYMfn!ne8`8NsW?@2}X9rZt1#`M6n zyipm#1@tS>GE3xURpg`gEZijdJQ*bJ5UTm=Hjv3GO61nO$tTnGsDWj#L?vnmCFJ4; z4hLZ~&;7^cS%+4H)nTV6yLX^@v5U9k`=Klqai)j+dGl_LRX-VSkhDG-t9o?I=gO5T zEL^M8wZnWopt~lIA#vqmZ7TySNi^x#(l^xl62aY@g{*0JnLlE)!dHY>ysgg4klLDr zTMDXII90XZ2Nr?|ANUIWxXCOt594xF7nnZ6^ z8fdkC{gFLmy)tE76VKYSlL+{z2${1J3H-AN`B}_ilP_SCcbx?X)?_IxU18Ue%{RBH zmk;mbt@oGd>TVFWD1DSow#r>e6rhZ#9B6|&Oq*WNsj;IV@(|iFZE2Qx`;jg710%*l zJr00!M)V$uW(@Z4vE7IbC$aBoSIVNTTF$Dr6B(3HJ<&{-4#QH!M43e*gIoC?95j=X zE8T?%G1j)Px={Bnc^8wwH=l%gF& z>+*iTyczEqSM70r#0q`#B{QCNljc0V<~rhu^yA_oMe$K9yFAmTUHwT)QF3lZ571P^ zYLi0ESn&8NO7vf%JX8Cwres^7an#UDNsKOeEZu6B@sEg0VhJ>9OL{CYbe3s(_52}VGd=aaK zNMwbcqr4uBm!rbR_KWC2poB3G*ZR^WQRdQAbQ{RsLPy?S6n{cQaFH9jcO zF;-8tpweRu2mJ0)8hj&r2LK+OjZ*{*`2|L#~)R&llv@PnIAa zj~w9_uPd$<(lL5NF+XR@H-ml45DwR|ud+Y#g?-e0HK9evmFH)jyrwPFU$Q6c`!R1% zWN;7$=J{cknb=Rf-RDerki8I}FQ|pNYy!e-I6v#yTdlZ`;l9r4g>8K_o@?TQmbG!; zLLq-@)iiJX+rH`7pJNOvG9IgY`Gug~RiJQ|=qaWMs>Oe=tj>6d#;nTTQZ%z?ZnNj| zn$;So&lb$O+6tEyH;@mn_MhtZeYe*e$n06Euy3=X=f8wU+1$GVpJ=XZfwVk+vGSX&K2zCw!#iHA=;C8ynrv%lc~S<5)qCM`hxHiv)uZg*(@y;kSHwKz)KGV`)1pJQWfPPhb6 zP|qOmgD0fe2FOMu&`@y*2rTp^VH6gAaaLMw_>eZQXW?H#UC(sLg9Cy(Qt$Z@$6HAZ z#}Xo*El*h<9@9^mcbW5#%U3pken=X~348T?#0JroPnX!-<70~rpGz6+a zVgkikuSMT`3GluSs?SoAr5n#LSSXjuu~v%HxT#i?l#^8Et-duWbBppYS>>y;Iv&L% zBWTi58GFg`60kJV@M_jM`k(1AQVqH1cUY`|Sv3oi1z0G{QCV3ujXwU(RsBO}0bKNc z-R3D=aZb?gtQ!XE_=`mM2nG;y9O3JiCTHWrYc}@wK7z=JtnrZO4BnzUr;2(#mke{R z7_p&d%J0#nqfFZa*?nr+-SZy(YEV~{^^kMJ63h~gm0Z(LyDo#*eX1o!)D-^ z*m=u*MaW>)&(9tv&c2= zSOAqpTi2{DBMh=puRc5-(4!hi$wdXQVbDks#~c@LyP7(fy2oNQ0#EF0fCJNbgZ~(j zK4O3&#T3amw*_t~gb-afzAFSs{TB>xBw5s~1JwL5J`M5&6d%u#?EaJ!IzX+zWn}kP zi=Z<6>My)^P9meHdh;t6P-ozq_o$Q+A3qG0=-G5#@J*L?h)4&tnJVXxI1CByi zF2WHh|E)gq+m>Kp>pcpj72%)LYyRWzy+)lob=tQ17!CpBvstIarP+j0g&KHTG3_0D zG5u4h@bY~|n!9Dudu7lK$H1j9XwM>(*uqYrcS1wyeFjkF^ zb@iv(3WM7X%vv)RZ5l0?L=&xASSC26qp-xcT0REx2J)nc zVD^WwtmKN+dTwW9rkU~YNW0^)Oipyt-;$a&c79>Hj(VDJHZ3>R2-CPlQf48()>B>w z8QV{p9>(6EFORrApnn@=XAE1Ah74hVG{05dU<>+&9rpxhR>KyG{z^J)M!}Z)g7xVL zY7h;RP27p_ddAdT9Ai7cZJ0#D=>8`{_(n*uA8cSP?LhJcWJMeM=0Cld@DcTb%2dMc4n!yUWbT-z zT+@tX916uZ~T9Z$Fr8Ai5845B;79%+xRHeKMig^6%t2p8v z9u|%%LT1;p-u-PEY63!GW`{^6P}oONO%02v^8Y3yHbg6vcB8|Wwj%M;y>y%Z8iROnTcaiRWk`hIdYUAqj zKS*l!43bW*!0AV3EQi=0OcSst$6T9{Hf{R4?XORq&`Wi2_6gp)lJYR6%VG?f;>=C7 zqrltxTg_mlD5ie|J;x46 zrL)9{5K7Il>=eirQ~1FGoQCCulNjodRC}PBu31-Vd@<8dIQo+?y;1sGRj}4u?CjdF z6Yn>#GOj$MJwBfP_`E~vVsWZv^y{PjOsZ?~gNN}<35q(zvDP2#Dcews=V`7Z*QM!i zUbk=k#kzEO8>`8aBr(|&>?Z{q79cB7yL7Fw`0IG2cD>63gQIJ?>Tk%Uxx!4>-kaa0 zt1>wnQ-P%%d?jyxBVP*0@@+*9rYg9{q7oTGr0j}?vqG;Z-5N^nyX7{@t_r^^)+2cE zRpHvbRV`hEL1~i>u9{L?%feNgBA-d{vPz2-D)TUMdPt=fqNQ5mDLFd$(%Goqn0k}flWb%_@M`W*j!Ga>Ow475n=!cu z@1dP-lDT!w-*5L27&m1h#_!P!bb8-&G9LROdNGwEWSZLuk!zpTlS`T71}&+96j)`gac*Bs;Aoq%GKClfA>B@rf;* z^tax4j~DW~YtHVOLxQOM#Hdl<0JQ7ufLlcBGYl$nGQGhbR8?9_6d*W^5FYc5JMh@* z`^3ExWE)uS&Iak&FR#G{eo)uR3WH@ke@APSsLhm=6ePYPzpYH+~HFU7H3yV`vL4zj`%sdhvQBvx$uZF1m^b+Nlc*5AA&GM9;!nF(_v9Ae*1m;`^-_~ z2uo#-CO5CMyiZPfNUoGuq1mnpCrQ3zvDO}haExIj@pne|przGNv~MF~-p@QfR4 z9J=b;C6q%!$yR}E;_UoGWW({qkdZ_({Jx7gGz{bthfV#|zxyy`T5-`LHE=JclE1g7 z%4aM&9isabl~i>L-m&dB?F$obpoL6@;pho%#L>usY3DVJ;|Ny_!EZk(u81OqWyVsY z%3w(83-x8FU_aC+ylFg&&3E}CdR6po)%~OGj*fXc)&pc*uDSZ@y-R76XTAL&eYuS7 zq=LG5YN$=##&2O!J>M4Ud>tL=H!I9BjS`rp`Ti`sR17_Fw-$1=c7{9|1h=-ZxUC*^ z>z@4KlX*OTGJqHB5@4wAK>Pd`KX@pW*V=z9n@xY;bu z9K!IINphW|nsuXo=k|DIopxE1tQUg89H@o9SY#KjZlOeyRvoqHZ>fc!QW4YM`ugrX!`H1o%Qg8v1TIIpoe-)bRPP zfx}Q)N%1&4t9D7w17d@D_QImN6%8k2@_kQej>DUj!z6n|jmJzK6{R)fKMz9x+&IHt2#!<$01VWG1vZ4R9%fG4VfP~`+KRv` z$)Re4iREER#dIf`2UK9~h?%$s8NsHRFLYJ|}bgg4bL6V|2V_ zG2*p>z1LpLkt^g(Mz+8;rdmDz43y-7+~7?=o-bG6Gj?A6 zUb0=lIvYg*lF&*mFtS%Jl`V2WwAlBV)CRQMS4q0L?0y%4>Z4!EFuXn*K7`&l{~VeM z)ca~^vB>`$nOvGB{f4EG#A%DR+3O(Pi5l38hpPiYI?w=jq zFVG%sz<98_{hLp=1cV;KAsG?Ew{I-}-oaJ)$IR&;H1VJ8Q0>hb*%jr>CbMdjmbOF%N-6(X0v;ln zs${G6aUO4;2vx;k(`J)avX#UV4D|+NSepkyRKffM4Aq=OMsOhyW5f~X3H_M8?ye7E zck_=~Qt6q@ApE-T?xF9Fcb>aV?U(x-mA~q%h8XR$w}U_^$oqpLND4j(9?YbJHaAcz ziysWC0R}OXOZ23RL;Oo%H@Xq!v{F1gOzPLUMLoChrM6^g9Ys=4(w+= z7G`X@G4ZF}1)E_z`?(=+tENzHomU&EIde`uEpN4JTo`!sIGrnj1UG5UXg=0fjhKsG zl&rap2>D@E6Jy6v@}`E_YS^Pg{$}&flPrKvwCIo7P&nvgJ=PK%AW8=d(;<3<#3V*$ zzbfEc0(;RY3o@CuA#al%!x*&I@UU z^oZDWG(}@E5uuDat`aTe_v{xzVjSfs2Nrg+XQ}vRZRi*k+-wqncfuh~{IREUw>4X! zV5hvok$!>!A!cNutR_?RDW|&#G$t}=4mvLq(RWwK5@r$GX!7JpWjmL1MZ3fG2)*!U zm==p6#2A8;SFXykU6=2}4aetMp#6enBv$mSqi`G>bA_G{#3FtFWdO}%&ZaC_< zOr5BY_j>g7-imd5sNI!&tUKPg_7ofu?7SY*19IJ01KfDoO1E@!)E@9R(zhq+u3+Z3 z0f9{%W1}m`ITPb;qD1G2zmx^3H%O|-nQXxD4~QYFoUmP!VU}2~VBWpK#U@0ptmN8* zbva?OaL8o#ljnkiJ9=kb(5Gw1$sDW+##J6@BhKdgQ!?KDZ_nMJJII&!+SdhwH^*w4 zR@|xMugewa39?th5gU8STh;?0TQ%Cou4i34xEIltJ4RM_01++v$N)1h{@16H4b$I ze;zF_;y3efc;3bz9jzPpS{BZCVIa*>dDWKu1nQ(*@b;V`m0(rhTXTl=MBkT*q(j;! z@uC}i2itbZ56CrjSa60es1-)UV#L2_fYEmReno{wbY09Gza?Rw+9F$gmGAC1lXkl@ zn($))+$BpYhfE;DRq|W?axkpRb#XE9WUOov3zbhl4Vk;8d1x%BaW9cy)^K zTRT`BjOaJf_t`Q*99hTGez($Z?pgbcYrL5KIvE48Gv8Jyi7tfPe~m{j5pyRnHsWPv zfEUcU9%K&NK%PO6D!7EpR$tkxf;@eyt9E$yjd!R%$UrEgWR-JB8~)IHM0 zmxNo25{cR^ZVB5VY-Hz;1=Dp+YS$HdnUthSc7U=aQIU8>i^|MC7Ma*0Q}4LIf=zQu z6OFCfvh*A;i^|$MzP6??TAbWo`6OpQH1eT|C`y#9mI7Cnmjvo9s{D=j;DD?88W4IB zhcYfNvOcGB+ozE88TtJdTkMwG^#Yc8+|8*oQs`(tyLg{n*m>d8CdrB&cAD;Tu!jtj zFe^GfT}sb_rR%pW)AbOZ;3%e+vl4cl0j2DrsV=J{O*EDHc?F-(YW-x&YBS4-K|=pQnPfATFAZRI5al+RY!;^KWeRYk>a0m}e_ z!OYiJqQYQ=C!$@eb27`!T1}4S0b+&ZX5zA9)CVuY zHfJ8`O_bL1bdz%CM$V$)`D~s=dDj@Ld#w!z3Z%)*=R$0o3MjG}q_{~iYU}C?(NI7B zu+l9Xs_Hx@=u+kx2BaEP+g-RI7Wo_wohE}~xmb7@Z*BhZB{}WI$F{}1Y%?ebokK|#bHnd+NH&^(bbt?eO6o&CDQ0Wd-m+Ja3AX*hwSVI|cBr_VGDp4aXA+e#r z2pRwFGc}S09OHm>JYbnVUWua276%6t6G@a|lD_(GG|BKZoXoxqD%HzZNy;bj7Hv!0 zR6<1EcOn4ll!W+VqVVjOw&}dprJz1)V1tSF80Fo{!KdCg`O?ESUrGs*)kQWFFDFqz zm@x5UOvm&fV={qO+R3PtdT0mey5*z z3HfEMUg?I4*ds6nrlqT2`I@5G70$lLn#my4O$ISUG0GTfs^!k8$a<~kxy4IFL^pad z<{!qfA5-N(qHbZ_&ikPAb6U#t*8n%Sgu2C7pYZ)GoL0wB{VL(`SWkb?=99MSQ-RYT z1lgOFIR&;n79GPsn>A?fs=>ii|MLeX>LcODzmmugU>SQ%K;>ftaI}#6_pYO%jk&#( zrHh5_e<9~mhpR=cs4WoK!l2PW z_0UZ~NhjHKb`@;A|Jjl2TjAeR=UxicGUxI;OLHsjE9xtJ_GI5DTFa0vOUh`v;XLW# znS0ND`1SGmLf;3B(cgykY|Ikr+g@%YBaAG@$`fSHYT_VSY(qWjs2O^qB4aVnP+`bk zYq1u4!nz^bazIsEk-jn?i>c`;I1+`$HKI&jJ8_!`Lto_~+NzJLEF7u{%Xy@lesI^h zHW$G{zmXaVhFV7%KFulSsW*@yGfP*BRwGJ%vk?+$zD~;Cve!}JG(dqFTbI&#IaNfI zr!?=gL8ZBa2`1bi7V}<;5-fh%U16X+N|muL<+$Qfi_rvD>tb@5t(eY)BmL`Xq@LZr z18Px_2s4Cpv!VOV$ZD7_WjfpZ7dds)r``tDv2560+jPcGs4n(DGv?#IYZivdL z6E77t=sY^feaJ_)()IibyQ2r>(uAx!Rc9`a@lfE2j>yj3eGkn(U`5K|V)G-9MfN($ zoIa9h2x+U)X*!l~mOVwGjG>@g1_(_LsfYLrzbCAoFR_?y7i&22meuJYr)e-euag5G#3RXdJO*W{axbVP@g;OWXjfFyU?GC_(g8R<%&SiHXi)+7c zi+dCP1evXJR~!3s+Z!kXVu`dI=9)cd*&6QpwsIHn`pH1SG?5-#=BW1STYPBjQ+S|^ zn>U~qZ&eS$#W#j7bSjOj4>?zN7ZdpKC9Zw&^qjG1M#sw6C4dSh%gD(962q#Y$HDnF zB`Y$p61K6C`HKmozv0Gdm@f4yQIm*dhP8)sszpLz;CZbGXAZ(*+oV^G(Zyz2Ie!-0 zZlzn1n2We(DbI!WP&Wy>@aI~KCQL<8pm9Tk0Itn8s#Qr`voX5$3k9Nev9vg?dx*#8 zP)<7Nn#}7M(k6sXxahRedY5EPqb}J@dLH2bliuAq?WqU@jrH3tp9E^yXAovrUW*nu zaD^$WfzbKHCHyk0uW27U^K^>Wcjz=~I{5Pf@nh>lr009vro1>RHu*EtWTNDC5{^+0 zX=mQyIyC)2B>9ivdHC#*`LTu=c>3Ev$fIT=3jrMF+SVHq`S`b_ca!`^BySE?Z{6}; z5Pirm^DI5V4rHIiLaHK}eqzqDD)djgqEUf2355u%7KNrsvZ~E$1@K{AL#jM(&{q*2 z?jDe~?hTZ|Rc-hm8SPDhw)k2;EVMoiu7z0?ZVaDt*~4_L)!XgSdiXbtM40_}#SX#c zaz@bLj>*w(2OOd8)HY=2Hd?QZJh5YcbQ(Zb*Qe*@?&n?Mua2M%$95%3T3n9hi!j%gkp+D3C2)fh(ZisK}U zu}aZ?J;US@J;$?radqx;D8J##*0XVk+nA$v6)Qb)!9^aSJH0rNtm9S`mPPX3U*Xza z+dW@YJVn)~rF$r5`P&FJn+E-z69B;GfZp_f$AmhC(M%SO9)v8oFF3di}w-w7p(+qEGvpjQEUY%^NS~x zT1mPHyC9GE_5v_*>AUZTIbzec_9&Q@xVR4CZx{Ra@Ndz>0dd7_#|rc~OBpZTyD#4G zbKbA505gdx`)|+te<)gwqANNEsZXzFe1!u(-N3#h~1uG3P40Rxk^>p-LhL-E|_Y;9Q|#M}@b0VJ)&uyQCItI#f(H_{Fn8FT`B)Y_e{MAL!~C9Gs^}%52}>e8-zX z?K0df^MXn={hU|WC(=9d6{qqRcYvOs7^& z)O=I~#xxotd~*niboI<6_>5OPBRx ztMv353#~w`gUVj`GROotHh;mg>t{8_VJU{-9Myp@QD`jsU1F>u&Pu|d8?L2^3r`d- zmKE1|y$es+iRM-*CE zTU_<}@qsrctA!JQ)ICGTKY~@F-^}x392$%J7;M2G%wMm8~%qV>f9! zhibr*xgCDhBmQ9eT93xe>hv1*=s|=->xKFd1U;2bmx0#tub(LiVUsbThK{b!CV9CR zOH+HUX&^T4gj z2)af&-~G5uK7kbWG35)7i#8F{YO#YAN@85rPh$PP&gfA%D7Ik2M9P* z_<&#%mdf{|o)M=R8p9FE1BIxZneRtzEe5zDz}2`BQexOGI|!OeF(nPOCa}_6-S7N* zrcn)S{OCSll$F4dI=(~v3VbQ}WfXQzoE)_k=2n+PLTSi?SuE#|_9ef5dTgwVYXdBP z%Onq0x2MYgicg~&ZSJ(PzCM(a9m{2uAplUr20YZLf$!zWaGx7VJ|eFQirPP#3qX-p{akL$;yU3dqz(pOGSijAzpp?^ zu~XhP0P50t01h+%H$?b%3Dy0e1ay6%Ge+XlTAr>p+YXji>at5#`4HTQGq3e#q$DoQxrS?IM6VY17wTK{Gv zHkN>hMmr1CHr1tSZmSx}RFn1Us+q3Jl7}63so9rKT7@-uSJy0!l1E;%B-fz@8tp1> zJx00Tx&m2P>#GtLu}}3F)6~e*vIK*n&cNo+s1CW6d#VQ4oBaW``>0s3mvFFivf&=3cZ~nwbWp;}o7a{2@!C;)$*Zs&U8} z{iTG6JcEva#p44TYb%A#+ETyq0={P@OYYTqf`HGc-$ z==!bma>s?ZYrX~YC=6u&IR54AT3ILfz0Ra7Tt79Ll5%_Clum~qX;5BRXM#_?aW18} z4-zcSI=NjgNk3d4A}p~jg6y{otA1@eX-WtB6 zntlDsDC{S=WX|-zw|>0hQuhRLi(4PrlTg3iekph4q z%~58dtK7k*E?`=TROFdg?1m@qK3X<*TmA-ve_#4L0;o%*`zZkifuR64CjWnj07|B| z_5h0slKKI^K+cOG@%!UXa&+-RBgtwVkhXE#yFK@0P6SuRJ8DgXeSm5RBJ)+dmX#x5+>U~+8>4)+9F`7^PIAO@Ly}Z z=+bK*A>Kz=O}rIpo@TJm4Xth;h_C!DJL}L&#`Eb^21ww*?%GczSAW3W+^z>9bi9u_=9dM{5#BO=E+_D`G(;*<@E?89N#_`QdP0nAB7DUjS`KgBHO z_=F)IG&tUVLx3m^gUoYUegG*?!u=FhE>rcNHa5oS$w`{kXSN#k7~5+R809~tPg#VP zldTPNk9A0mS3jTXlFRr zv9!9ry^)^H6$!i&Y2K*tFzB88r%b*}B*P_CyT@;c{~n+AQT0i)z)^$&)@&c%7&ILz z<8T05O&Rs59(M@3tX=e~R-w2e)ZGbk^Jg|O8=p?}RN5mBML=-$kjbA%rr0C1qYT(g z4+m3=CyX+JFIX)Z2MR6zCX0)q@Aa`=E+4sidL8bG++H*)sZ*p`)?zq)Ky?fKubmv7 z9QlnKAiyXPAW0Yge>=V^PKI{QHh^>c*9xz)tu;WQ=_j|z(mHB4tGVm?lWVh?hRE*$ zwi+h5aBs99p32f|+fMR2<*2pYJ`n1>4^a)2A0XsZJm}DL#5Yl5jdy;?KJw%QO#a7K z?Z2(e*9?xlK~1(BOpQR%8IGieMp4sc($ym2B!yBDH~&tM3yjDfH!MADNYFAJ7&jSc z-Sd;baG_AO7^W3kzZ|dj%yD>Nn5|L~VKN>cCRsS|Wm!i~IE|3V&|hCuH513cY*?|a zDDIH=C7SdQ?C_`>hlqerQ6lIOBleY+g}U#IOUUYF0ifkHilDcuA%|$)SEd-Q&j{fvh>b&-Ta>14w}gxajk&i5ZkU zEeKVG7vJ|R&p$uN3`5(%^J&&~fpGt{h8Qe#g*;HO{T}z77!gy*y;pO^dN{HkJ_uggCIvB@E2J(a;>zR;1)NyH|lFeC|GS&bS^X9eaGbwAVBy7 z6rZf(W0wx!i9Y@us+7)d;|}((-XX~D6TCIr6Qw=@3f^l&c(Fr4SeD3HRkV7rI+ki9t^-E7PG~3)VJ5`!-T4zyNYKH0*)c*CD*j2}={}n*kAX2NeKm8Cd1J(BXOD^0$Yt&WPoec9M z;)qLFfE7BMu(cT5$Fr?_qK4JBNrc)~+DwnS#yXYG$DVP|=1k@KmJ~zLs!uJSjAZU+ zkOy|FfiJG(au`Xty8$BfnaEvEeKuZ-1l!)H>iE9q!Cb>G*|CUas8jKd1kok95~GyP zdE8YL$s)PaJF%tN>RQXw@wS_69(iEhwx)36bU-yUAFx~ z>^qmKjBpAgNQzu@!vlgt6Abd#GBywsxv`!Kmy07wVz5Pu(qeSsy_0j+32tZtGpXYa zIRVM`Zn&5P`o^qW#IFId8<-mqbjmeyFZ0uqSC=98*l?d86^(R>gldmcDsAO?-Tr)? zf!b%K&kMptjq2VWkdpK4y3N2sa&DuHz`I-V7c8y)@`nVr(?~}M zcJbuuK@P}m7G~`+dhoX?SxBc6WzUElA`GfIS#StkF`N+$rSo!LXz98?IpCEweA|V~ zWhAzR)phbB3FhbZU~4CIvv~z+HHzHe9qH363%`#??D+<3_xhA{>T(lZ8hG7cEQV@u z3OUmy74YK2BcO=|M~^ce#i;y(SU7(CrkctpBHbxR6dSJn171(L6ZKv_;s#6XE|hP< z^|#WF5#o(bVg*%#Rt;y}3di-*l4SUjg&45pIr4UM%e55>IoIlyQUz1YuSLu_*`5Jg zyFlQ=J(C~#ztFaadw$+(9oW%3CV%FOayfcfqLT_c0*{H~#w;jCwP4_)`GNfFweyaK zPr?JRkY)gki~oDq?k~2X@K_6JDC>X+ z%Y8u?5lKn~g>pW!kYk; zraY&j_PJS#C2W3~&fu^s0wju?8UaWYRtwI#JG3o7s_;0?bfwuRQ~yXMjVmwz_gjqqopQIg|YC`>A7gD+UcTu z?-GPTw@@^`u-LLl!Dg`NmC?vMi0Xx2h2LM<;;yixP-+mn!dc#2i%=x8r8=$L%2P24 zj26I>Sm{DnA55KLCX}0KxhiG#M2q5;BdB_Zw}I)zxh%B-y5i?gk7d8dHTihKu6)=M zakRWPs;WaQj7h6pB6J74HLX=%Mw^iP+p98DKA}csCfjFb8N=?+^fDcjyWfP4WYePh zzaGIusCU|Y0P@oTN@1>l=Lm}0Ss28GTmY-Jasdk)`6cXi=7nhvDRqKnQ$&K{Qt8S{=|N#i;0PPf za9jetgEE3yb~k9D+H``EDDS=#-V+~pvm^Vjj|1v&#vXJ*?~XJ+yre758uHp6qk~OQXOvfG7Fq@5`Wi|MW*?nZ4E6cEZHrcF z+k2*E8wMtGf%?u^(sKDDH=r@6jc}-Pw96l*J*BHIypPy567PBzbs_~lSnaI1i!D<- zrejqdPY>=yQB_G$npdKB2%W5!isAP!To&u5KDklYVCrN^s;G%6j-Ub9tyl?z4vVqL zU9}AD^#|3QZbNseSJ1Tdi?VIqdd})4F|52Tm30PRFBWDWW=k)#=M>TAi<@T3d~?KG zPsmWE`S=%?W2j7i`DraDER!ULt=%3j8J)c@6RYxp!Cxl#wYY-b9k4#3X_IJ~Wh9rHR2&%39VSgGqhr%}xDIft+DJSVpI6_bC zqqu1(86qt!xVZE7RV;{?_bTIv-u@UKr-vm~A9khZ&FVU^L$eq5l_3uK&Mhhc`QC1} z3FZOu-tjU4=92kC0RFJw;_j48DP>Wt#N}EVqYk6ZP)!O+MjnIH72 z-b;yn1iaFgG;+Q`aZA|3yo2v=t~5;V8rh`AJhK=^*5Ro{i-tIcY$M8{UDTIXrSXAfJ&x6wmjt=F zjk%Vsq09bM53c6fcpXxt4qzV)bE>&?t-IKhL0|HWmxZ~%m6$rwKY<2@NNQ=h(pegAv$=UN6<|^{q4m|GNw2bW7{Q_OrFm$yLI(%#Z_8u7V}R4B+dbL zeJ%eTNc{i!G=KkA|J5>(cQAGOhn3QQe7qjDZF_7{gs&RklPKLXOKcs7DRIJ54mTz4 z0IYgpDTyouhb1<;lnp-JIccYn-lpqWL?A-G!7*+Xf=Li1eGgb9mT)NP?tA##-EVy@ z*y_nFu165kJKGsKZ!?$Ems_8At97*B$ZyqP-tUB9ggGz)0d{*M7%bf=LsJx^3WLc3 z>P8s-%~*1n9zXk|H+*LLs{@#U6AVV4l|2J88jn|;tDY-$pSHD_>vhNGbm2dAhLTdR zlBQKJy^6?AO0?4g+T8r2`ber|rM*Z4EKFx0)l3YuL30~%xzo`&a;~vG9~1rAF4Ri; z71rI3jGFvpz0hZ@S)Jfgbf>CynRvAq+%qz{Qv1jMC^XexsM?Of9Cd^~${a@t_zXC< zt1|oQD@i_I*jIk9icTe~awE9a`+Xh+L$@Swo$F*N-a%_GRm6J;d74@XikDvWRSA*x z1s33|mNu1tHCkS(0X*i-9owJ33TNB`E2KRX+eLz%Xn?D>{){=+fQPHF2+He#58+tk zx=AT*&Z%LTqiI)T#`aO83-SPr@LKp~b=}oXwt-IcXr~8@4BX`HPk~U?E<2a#+}g=c z%uSu$ZdaS>2y=+Y+KmSo%$NvyOZzKnGTe&c*caOD+2BMO@S1wb=LvD3k9q2Nge(4} zp$ahPp5leb+(806)DRNw|BdhE9}p$8BWIUT>uNuU*$TZXZM!)b+IBqqsEZD~-%QnN zGY2aqnRmWK7q!q#bM{^l!4tK$9YjAKd+k0lcaN=1&0CE2MLQ#=!M>Y-OJ+aoadY(% z8ToUdzh^A-j>lP6)SLavg<36%O3nMOrH3ef(^T~UBM)C#0rx)M3j9j*&mcqvn;@5< zRi;X~Gfh*30@*wBAb%p%KEoJ~Ls_nMWZ@A{E^AIw73zFHhv9#Gd42~; z8ym_BM{6l|!)_?VqZ1kR8|$@4T-dDQP!}U)(qrMm2r3p`kT|aqIYL8>i7D<~;2p-< z2Hgn@fM*DY_e_tA9~>J$L`IR2-)Ejabav~ecLdVkr?L|`u&&<2W^qi?+EqM!cSG3u z)u0S>nB)P)1;Q{&z&|7WFuvVieTW&|4ALgIZhkgGAncLd&2S~N&skj25T1lVDql7u zX%dc9Fj6$0Xs+O%E@I)A>!_8@xG6u@ZRRO<{ij_Bp zI4fS>l1D#B!fBj6a0I)sla2Q0Z#wRD8=#hR0JaoD|F@ws4u6p=`u`qV{#kXaZL49c zqI}9BnI)dBH8;0Hsai*{%K8&N=|j=Nf-RBFCq@=~G0Vg_c6K92SmuUBRgJa=2j*O{c6exSwNn;d#upA;3q~ zFfg#QXXB*gnTvgQZrwZd)3e^_X_&BeX;1; z3I&-+B1z<@r&_1dTCp#-epuCVyD;fI(=2JS`f6WthuH+}o2i_tByqUnRkh+@Fi+!#A$_9oEn`5awxnguAQGP_PE=8O8{N@Fz-xT0`|AWA3Sj zpsGJli4ccrf5^rO%!+^9gve?2;BsXwy+CdsLAND`ip=Q!Z?rZF^%L+C1%q&N(E_u= z*Z9J|fg!sD%yo+qPYpAw3Uzgg8Re041NlM(s{f5Y$S=1 zv}KIA(q-u%CHy%f{2Pp#S)2n#k8oWcVrX}4zPT_<>M}Tt3+NW8$g)DSMNVo706WDX zKJlI6ZREu0rEETd?o@k1Ma;P?<5TwvK%xNFOg|3g_L!@du6AAG}!F19*@$e}Pvy#PlPN1S+jaAUK3NlhG$ zp>VC8q&E!b5x1Hdw_5TRg{H^OkZ;VqHN7FFE{$lXm_ZDFT*?$W;Iu~q2eXLwQ>KuJWY&#jZkH7_CP}*9BD=%0 z93{~dPW&DUIwV{a$@3wAwXg}?jO>puHBSO~$;SlhcZr0DZzFK{N2*xj5MHF2CK894 z>2Q0*yjV$}{Sekj#i`cuG+ZZ}4n7SHUn>xt= z%(MT2E|zAN|Jejq)m6qeMe*D5vEf@wovT?B*|Z8U0SR7&QnR3pFARVfSC7Iq*GZ(M zfrU)6N%z{vb${0DnRoPB(&i>+AeQTSK|hUictkIs(*)+=avPK|18De~=H9$^9_aZ0 z-2H+6_NN_xPyz+2h{!IY9aofmRHz*}B(wp7_K=87L?&_^;@l2GSuq=VuS=2TkXNyh zsFe6I3o&8I?*n%yFW8GeSQG54Ra;epZ~S;l`wZ87j8bK4B=aI!`sZV*wrt&IvwZ4Q zN}zJ1j<*P*M%BhcZQ#v77*q{d*|}CpcaI9)2I@LRIn0pSm20T4<#M)iBl^`7ZPyW# zP5ZfxEqYE^9t90K79F{B>Lf_gG4O?T>Tpv_4Qp4UNe!x{@>m)G$cs@YGmoW#Q&wNu;fnj@%(E^N5C(ju#_j2}rj z5+51!qENwJ+;C{{#o=Ol@5QpgDs?U?3XA8*(HB}rU z(Or0;qPup7uDT8*tTKjQK$#sbQ@XH&r=I_E_3;P8jZ(TqN_DLcZx8T>q7{LfHfRq!5-m@tVQ+p45S%fs3!CK?acGj<$4Zrx+K;PydOc=!cd!(SXr)FMrT~r0E*xP!p1p}kT*nD1+PeM z9*Nb1Rfh!U^o;`y?xjGYE{9X-ME|W@Vk){QcvJ2Ho3eyt=6X}uspWL}$>vCJ*B6>J zcSOlgXmzIDCk=jN+6Fe62W}C)4}>-0q#rE5DCHLhTPV>qHdN2o^ZGLV=5Q!*1o($J z+1&U}`cUhP>!8^D{zzj~2(#2hvhsQOr}{SUY(6b~r7iPq$W>g6BbP{K7?t!?(~d;` zPWWYy0g~R&I(-Z7JM5dQynS2%@}ciKAWXScOvioZRJ3!<&;$PJ(0KHW>srC|77UzIU)hxle6>X#PVQ@1V_`WjLCkf;q+@d+MxX#;MpJF_fR%(HGiyq`D0f9U)xBVc?k#M04QpJ z{q{}f{~C(^3x-HU6#g-oGP8HG{R@<8)V0Ym% z5``epE}t`G2NxKEpCBC#Ocm9gBmY5lq1D#H*Qu)eq)2<02@N=~S|LfScKwyj>wR;x z>7BQ3{_Ez)=Ld8@1)k_a2sZ8Ma5Dy<mp~h%rbau3AsNHt2eLxpZ6DC?**i zi?exkb=s5cCQS!m#PGbq;-bc%y0wfE!Fztu@&rVUEuQ zv&(bcaS9wOL^XWIXdYvH6&ufX;@8}SRNORX(^mjHHDS3iHcfU_(I{vco63_Ql-RA z$uy;I|ATCQqNiaeO7!f}iJbO2^|2bn+l`i1~0d zQWD^9C{i9yi6*8F{kjqnMdV1>Fz3kh~dR zerU!xiT8tyy&@MdzhVVHE>stqZ_$W*V|WHM5SFjZ5R3o@r^!1ylRbL3z>)K4hCqsUp+u<0BnH9p{hN)^7kh4Lm(xgZ z`jFT2%CDMC9>xL*M^R6GJL5AH^Q(GDs-$#g+#h{-enW6Gk!F;+!XCe}1r?y;S4+iZ5 zU8s>ViPu}=MkU>0rEmKKx+G!l3m+3CPw~^r2oyZhT|9J)@GJth7sI5&h3c81nZj^@ zjrDFB{V1#%U_w%fHYCx?>@C4gp5~zG6E4xPio!e-d_f)ZOdw%p4v8Vvz+la@+EdJ> z)f(_#8O_f0L4(gf-?nMxG4xxF++bVDkzhg zh7PjA9bQ#nrYMk6#F`?j@tsMY1soPaw8nm!1ECc(vjGHwFR@ zN@^MoxQ~c{K^EJ;aUcIt{+OEE0?d;n0gFr?f89e{z#W+w^QIODkXPY7i+_@1iyX^kH_< z^Vlw>_9QiRY$2V2ece-v0zCsPxSHq?v>m9G4C;$c0jXAel>l^j&jh>q>>-#|s{>E;mo3f10zA|Cf+4{@ZtMQsG zL+!&-*sBW|u)RWMyD8fxk%aZs8+rk+IN7+ehDS6x+MYL0eEcu( zT=SdIpit@$I@r6Z5v+@We3Y{3fdjtDC0`LrpomL8k>_s-F}@;SLYMOAL>PyDv1s}B zFpw}vgcC`wr$LV0_(uA}BG5PJUL$YW(^rJOI5~Oa{9mx>_sDH#04F#bcwc_;ksJCi z&Om%|{1-*8n7g8Ml66NZ<8~1RT&qm`Pzz_sq+)~ld#0qtWHRmjmPa5yk^l}?b;p?T zOr=WEGsq`IzOlwR1aAL^1n*G4+5q2l6VG4aQJ5RLrK>3n&*KI9+_7e^IpvpEY`?9b zdEdTHSK4xJS1HP32go;XC)j+z*U>)eWVBY9SAlwb{F|^le6zI|93R-98OBr6V@QpE zG=szj*J|pi zV*lWIss=zOK@3o9{x|ZZ|7>*pFLzrFut?+bS4_o-@+LqSfxvsAlMN~?Ev0qcT+%Fu zb5NQbr4kHUk^)8NO98sTPOdp97K>{`PLwyu%>PYE8c`zLoDYieLPSwj0yQ`O7~s;L z;XFHi{rU0q#O;rCZJKYWH}V97GF|2f1--bX(2y>MB)xcwd88N*6dG9=nMEZ9Q_+Td z{o>{sL}>rBJ9QmZR}r443>j(;cG?Uh9ahdyx;=YPYYTQ21BvLU#-Grf^{UoZsot3H z(YMPyLg1SG23kEB9wYMiTKOB4u+Mgtb13B5j}=SRj<2m+uA>Ppy%{&ER{A5az34D| z&+nq4w%Oejlf+OOnb3;9Zx-ppny}^!1NAIs?5s3J z)rMfEsBJ9O2d$yp?NjylH$6t_JbHXVe8A%4kuvKEhHf%xH7XlZ$*u>s8ZSJ3bUNHe z)=a(A^$tpa)U)H*)zVb6U~DIT0z(%V{86$ueaAOmt4Q6txOR8%^pdfvfPI!8HBmgC z9w*TqU@YVR!P+~eRZo4NqdB9<3ofmm{1zeOS_de#7l`8(Vk5|K%xF+DK2b}qM$O>~K8pA7 z{vTAXs7pr~SCqyOrvVle6Ii~O4vqo~1=UzFp|_wDmj`?*Z^|LQWEPu*O8W7Bh%p$B zhpa4&aj5))Ao9$Ru;4c0!%e>9#0>$`?J~j8bO$h!3Ko9oYY-^Y65o_6IKpr9!0`@L zV5?7mU?NfwZ1(4WakTpauw&1DuT|N<-^2d}%MQ*lGE)v< z+pIP}At3m=4{D=a0k%Wb8*hPuY0NU{-KKX=uHDtVP4ZWkPXL5?e}Lbgf70X1mNlGY zAefDd_c{BTdzbfgYf2xJAJ}HEB#0#!K(!yjB8`xB45LP)L7p7uM^YGFzj`$SYEL)D zB60YaD#`|CULR}TDU!!6|=Xx7>l>~i(X$!FXose5h*_^_j6#D zPT2}4k%qS;5frN}c7%M9pgP&RU)x-Q75CNaCn$fyA04ooiz*pt?JpFGV}tcH{n&0_ zemK$mNH-DAJz~fE(Hr{x*&dn?iUQ~BAw^w4!8jl0RMi@;yY4R9lGZE4j8~BRnahFV zBIFd_6?GJBT>~kJ0*o|L4bpF!X#D%6Bo_`5u@2 zcbb*|6e2E8mUia~8wCFZUbz99JVtQTX}>*>R){&sl1 zy92}-z>ZFM;EevxT(nmTgU!lI)-fWTnWm|huIz~G{^G(B>}m2c5*Wy+@?exe{;aVt zjV|3jHw6l=-|%7$zWZPRwR_iC-LFtjB3Ogfs_C?-Hj#`ZZ9X+}l-Xi-TzM=s(=b_L z<+M^kaZfR!NqM-gM$J!6tigV-3T;ksVWQL$nG*Quz}sPPbZ;hymGw&UN@Bh8qpBq^ z2^q_KscJMcfeZlhT7LJgut1|ZqQz{?TUuX?4Xvw8=2%L8W($d}U(8gO>>NBlcF>{a zIMR$6odP2Hwsc&rsKI6vmB(v1?`fhaP$B2!r5$f-;i&D={ zN{A010h}7ob95^)0Jy45IL%V3(03DvftA*t1Wv$N0RmpW4Y*eAv$?bik^`jbgB$D9 zv5tLB)MQau2C1UVA_21urjrrXr7c4VSgl5xT11wcYkx5967<7w&`5DhL}%c zw+XgiOR!cxBHR$dd+YJEF7ebd2tH=di`Gm3MX807ielFEK?1;UE}@3=v0La(-X-HR3o)ShYu5!^prR7FdT9(d?^? zxis8&hliiIX$-LYA2cn9;qdI%@;mHZuGiK zea6*lrLl*BxI;Rr5Bv$Pocb710TDCaO3TIB;P4>e0Z>k z{+V|t1*IZ)^@Lh)I}Vznj8(K7_xbRSBH}NeA_q($-mvr$nNynf#6*_aHq`7VB9Hm| znL?l|asJsx=uMQ0pg z9>5Fj3#uD24&k{W;=VlwEOg2q4*Y<;NfgqZ7Z|HFesLckb@Gn)=yMm51U=6MynUaG zw&1_j{!T9oNFfM;yJ*d5rDebSwS928iQWcYlAP z)ZV;)MYn4D+)sTf`tMP1tklL1QRGZ63qn#D79ky^Al@ z;YwZNJT#Q|T8A;TCapM`f56W?GzLc7iwl8#K-C1Z>^nlI&Y9l@bFN$H0+#JX+7Bz^ zyo2eH_981051gV_i?$)*8LK57W0eP4Z2I-Lx^0f?)$pXVA{kUAmMSE#u%Ex+Ui5A{ zfqdLN@?kbfrIi0FI6S(j=SUPFd(BtE#&t=pW3hm!<-B;R{WQeJqP)`IL>)gYeKb0yt-%l=hj&10090zkROr4W(?yzhx!V zwg@P_23-^LmISgCEI}_g4IHmwNk}){xh$jej7X4lh717tjm=jR@IHYIWl*v>zNF3C zEZz4;>plOJQvu?bIHd0^USvCO`eqXn%c6$3Q{Q>!E%p@8h}?ZL$+nFOB3n+9vdF*> zupi0P!G7TWt8;cKQ!zZow}|Ht1`v?If8$B`uT}Pcjivvs-nOW}yQ{3A|Gk>d5vRZu z5(2?y0lh;?%;15Nq5`(0O^*K&EI^X#KN;Ua#vx~RFfHq0fllX3wHc{J-6Be$RXw7X zOx6vv(!0#pztYnAX8h~V+$XG5pYMrTA`&R{l$qM>_A}4Z>r>nF#`~81m-lZPe=8kC z18Rh~8yg@7ylG+hUiukg%U;qM_mWpVpxrTN5?(?BXAM35_!Ch}KcX2#(>-#+STF^u zNSND3wmOODs>P3S?@NA#Uo400ma$S_ai*unB%dOgYsZgeuP@lKr-$&_@h8P_*?0TR zJiBlP$UBMjzZe#=1s~mj`ue#SKa^7Q9{0C_Du-+JUvI48YlBM+HI>CH%Sw0uv>jxq z6wb9OO~}Z$ZQuSr!)GNr+HZdjip6)+gyQWB>tIDPgt=;-r zMw&*jm-IMtae91mi0%++7wSJ|*`l3O%c-yk+&C$kj$>kCSdL^fy(K{KJ+=8`43tzWE8b5b?&DlrLkhb zxILF#GO!xA!HrccAWM(da0*N8({0(3AD)L%mQ)wl7+Ss-7u7d7zIVb5K0avNVld>y zFWl}|@zxG((+cM8q5`@Y!T>qQI(%hu6pBvu?{1Vvt+t6bG$?%Nk-W>fr7Ue3(Mg#* zC5y`}a9u_jkjA$?n`HKRrq^)4j7=uDKNkO z#0Dn~Y#%QoTgX0^a8Ey`lFfOdQjS>NHU+SIWQKWHdT#amw(s^kS^jC5?6=}E_-4%c zc+B;4b=&VrYgpzZ>vP-y&*EbB=LZn$FgYk@+7e+)Q%qzB$XzhalgEVEf#QebdO)Rt z{WS%ZKcEIxHwpSSwJkQCw0Bquvxe}CLiBwZ@4vqB2L07f2!8?0tIvJB&TLRL*PnOO zz?O&gqH<9_!b14hW{>fuDxhzIK;B3}cpIOQP+O_%jpd169SQ!?UH_XiwXz^PoS4I& z<>sn&>fidqFa-3}$t9RdO!3Sxv}wNp6)iC6v87Gcf0{ z>G7d~cN!@*={Nf{P2I}}i~?J4T1v`B!^W01?H@9C@)haZ`b5P53Q;o5AJs3%ZFNKN zHZt5jaZ@Dj=|4~8E=~#L{^U~CQ+DrIr%#a68J?`m{Cb0ZKKmjSGSke{E@#Ty6_$QY zIX-m9a7HFiD>i%7x#G!4T}i^$7z31%ud&5;3h#>^V)ItZUQ-;s>ytrAL6E86%h(6XcE<1tW>3JDeT} z&bCu_A+H+cyeU+x_Op{iyI>yI8TNqM#DGrYY7SnCyCpFSEhKIf~DcI;h5Vg~jtX3L;Ym<)sn$@yn9}oGtpt#Y{V5 zCeh})g`^gy?25PdDuKrA@SLp4s`VI`+(YFIesJ8S!4H-{hoe2f8Eta)$o0S(c|9pQ zz%&iL&Hl8H;8@#tUGsdP?e(%3$xjUl+##MWzX$KR(sJ{NBT#2$D2lJ8seFd~wAyS0 zo}P{*jvNu()@`LOKQE9)Zs{U;KrA>)*U~!R3Qd&Iw18^*bH8knYY>&3@-k>bu5_bMNuyX9#Lq`@-vk^3Zipv92FpO%AZ`8)A1_bS-#v=GW_eum@{u_)3zw!r$RXAC5LCU+x>@oVIoJ7 zX*X7l5J!z)whZOCFr}>w@~^ssYRuPcRFku7BIzoopdYnHyG8^P;ub{?tGS2OIjwan z*)6nBI#O1O*%1#|uL~V!tZp=f(YbG36DY7zS zmo3GTGB?B|&k~sm{WnLZiTRTr(M^U&iCHC}ZBe97XVRpq!cc@k;{3P~YD&3PCePv8 zrL;o1q$P98ONJq2iMmXXn3KCMP^&QD%`$whIxK&N(=RVIj9Xc20a+z%ncO+K} z^E$4`_w0DW>4P9K9Gin)yeP{>!W!i_*&VNSit&viJtEh51%3LXB;7)ZdZtdim3!NB zvKY5u6|~XMRCF%!vuksl^_e+{3R%piZArLcgPNanHo; zn$U0+$*lKZ(pu=CVB>Hn1<+GKxx;gz;y6`v9BJ4)XdvF`PA^j5DqW|$`jZ}Xh^7Q- z;9(CZw(U9Q9ZSDvIxOq@qKI);s^@i*gEYFZQXYUL4Tz>jNmtvXed}rz7wX5U2T7oD zWF@&Hc1O@fqtDII;Xi&j&hv>4@_Pf_ko$kD=XCzpb~8uidnc%f z^jEIyQmD(R42BBRzFFHKA&QEqyavl?Bn$;5KCPrHRk*dQxQqNn`2(CM3kpm!TAuIa zEbm4?+_`nc$?|)if0~`abw9&#Iz2nbuipoZHE<54RWK6vb8EBqj4n}g7(nBv?*{dxBa5--@Oc$;*)w2nEqyo0GjG;zR zY2lP6na7vtZc?Q};G5d3?KMm+r#QK`wHBgu&d`gFY@N~!|44aFdGXoffGp3X+HTRI zj3KN&ET%L91vCYyH@iq97^^32oX!{+*cXg)5}&_Y71fCWn+rq&hj?7^V&VGU(C`uOg@yK4AJ8UlfFm}}UA1KE_z#Vs(AY!B*|se8jBYDqUlH#6%%KmQP?dqwyE zAr5?xuLQ(zHpktV6BcBdz!4{2#?J?vy}2rn@(o@QS1FAS+HMu3MDUd#WfbuUY7PC_ z<6>ie7bp`#TRAL(<<${H;vUHCwYfu7srOT`i&%X;LN*C)6Bf5HcnMB_14p5bf!jsq z5^%XbdJZ9-Fs7vn2bx0)GM5!pgp^d}~2F_)B-pSj#+_A+HavnC1nN&O1g;}EQX4flH# zk9a1x)Uh%q<79F_+2(S->iA}b_W8bm5e0G6sK|sMk2VKlgRh}aS3>QDMsjW83F1M6 zOM+8_TZYEBF>Y_IcieM62kA(2QdajC^>uMp4A^ZByCAT4)%**2^!Q%$0Z9sZnV+ zL+$K-$HpJpu@K=JrN^Hoo+52`>hBHcI7g34_)N+b|QBo zZ(gg(;9Q7+Df^%Szv8NK8>=i+r|G7Pz{dDnTN`#Yp5F*8ImyJKAzP`@h#NZ~`9n4P zYR#^+lH(DoFg=baP_c5ME*PzZJ1!!C_`S{-u74X?iVGWD3rurMsUB&7#E#YGtIX0A zD~ZCQ)K0ZIW}AkCCv-$Gdlt~rY3n$}O>#GO>{Iq$7n5h!O0-Wq^dRb7gA3tqxnyn$ zu!rjsjIyOY<5wolv!IUoo0yMr4&Qic*ZtCEJ~hS~aY!w*AXyq{7<`qk?L|yl9y{; z!FWSxa6$^gT`>nm69!d7x!eDYAVu>@Q1-Fz7r`Y2j>zO04D%QGQFw}-3N25;M+zr* zq2L4W-XnngjH=(lT1M&9@sBM?+V8rO9bD=^zx9UDb;x`8jFPvKma#YGLL%<*Zt(wk z=nFNK-xTv5@3Y_WuJQk0y#Jr|a;m!DURzjxv*}f|qG*D*px~kxodXmHl)=OC5;722 ztqwz~zY&h(YjeuB6UqMqeFc7jpn^XRp4ISAzT_f=isaBWNy@+X`p(|_<@tTxzQX!r z^QU87RK^wCHUgG}_pE7Gu*u#9rF2D^a~FWT*f$M^m{_CyJ=2wi4Pw zHcpZU-3~r?=-gc}9f8q4AMG3w#waj^7LhtqLz#%axD{9TyjuUzk$W??iRi0z-rc{) zznyBnk@#`Sy1mG9mT=kGT?k^3{^^dfd8XDr9Pfn2D5`q4<5g)UeI*X3Tm~EeK@aZw z`{q<=#iLMJ(ah~K)YbsW6BGhsT#OQRjM$P2t36B;{uzFH9~gYqRt=74m=rYbcDSFa zmZ|#d>&hto91U?okK3NI^)bUL9mDH3eA0j$0r7V~)qFz3>bV1^YK0a<-8y^aT|YGV z>xI(_nHlWaMsJorVDnhcrTJMMJqCYZ%~RvK5Y;%1oi(fMLXia9m8`67_dV5ciHBW& zjWoisJP14AS@w~hcTP|jx)ahd_Xzf(Ribh6Y!x^?< zVT|sL*j$PIgm}&RAptJE!~V=Wius%ug)m{-cQ6C2m!!o?_F5P)w!urA#Vz1fXKP8d z(*^=xHQW{oT|K5&lg=Mg)h@O*ywz%d@N?AYOJ=KohBNXu-9FGq^{=Ay!j~1;3-mvy zfE}lN|IN2ZgYH|TA@u(-1^#0PVylevE!bcv9Pd2fPHAo-Ye8lT_mxbdX`ne8NwugP zPVSwlb_hDB>zckA9Jc?&{+uUVm{rLR&3N+urBZgPqad?XEOQBY1>idMoaK7X?)tbr zz260*+wWom(SRqeTJQ-AV<+HSK^pRc!o?BNFwc=Ca3VembNt5PT{W1{2fyQtYTO5e zLfcrrz~#l|`-jo(AQ*}ZC&v|epbcVzsKb=fKl`jTS0To{nGH45EcTdd^}2vR&;wHK zW?6XTy+Bxbw+pS;ca+C!8no7%&*8s%U93RXU`@+XEi+}C4!G2YIPd{ec&~b;QU`G7 z61%*5=;0c0W~+ZvUzbgiwd)!meoBpH6e`wC!f@|D!a-Xl7j!pafsMk5i!F^K1*a5% zE@JTwA+1Y3oEq|N*pm%pD<@1#>Dh2tK`zqcKw{*`U}QHMUgFkHyk8mCSKhHHGV|`% zPc@!tEFOpWkHf;f5SV&h?mF);<4;u(@H>@IZc~K(6_N^VEVX^J2ASX+lD$f@{#zh< zw3PdB&*+UQbEIimNb{-to1N}!T8${-j9XKh?e@nBSZ&3(Yf`OQE1k3&y!BAc539weYd+ekpTaQHIr_TYt2ZAn0nvcioL0$g zj;zF-jUvFs@nyh1e&v&=Q%3r!{%086}D;!{?(?4DA@$(a)rrg=^kY1kEY<;!M~ai;H~ez+;7T7u`u)Ma9H^n zFd1sVwN~uPZ3{f%Oe;e?U^_*+5p3TK@D^mM5;^Aj96bx-bUVWvN>i7dQ7Gzw=nW>J zQrPCGs_#`38gyNv1hNGd4u)e;aiDgS^t}cb*fi3JLS>A?Z?qvws4@)s{7)Y_whEsP zLF2&QdP^Bppd?Z5(eZdVtC5BU>)DuJ1d<9JXxiZ)H>6?!ENa<5WmuoQkFbMi%1761 zv{-{QqX{E)S33C-IZMNx%+P*Xoou^F^wy@-k<5`sA7Fu4%|9qOej!ous(4 zP{N{*=q7RSNFak)dsG+O` zeezLuTLtt)qG|-qBEy%sc!H9Am;B(BxrQl9#wIjw~Zt z4==O6h09H;>m`nxlf0L;%uPE9(drI&Zt;NLO*}D0-&MQKfok3FYT=Ai2YU%5>c$zc ze#xEAQNHc8xe)_%-wTYq7DpOEG&u=F))~%P>@GPFr`b_Mn#ibKxa8b$ksCNUD(5Sp zysNb&@5G;CQL6pD9)`wNjK_Uy=DOsTRBC&(#5zZ~i7sumJ@y+?!fi^WfIwS5c`Uwo5*%3Dg0xj#0m$es=@L7T(UZsh9i6~ADToLe zv}47pzNgA+XrgP}s82{a#&Vav2dMOx0l09Z2N%*0!3=K8ZN^`F1O|MphbQ1Kiqp;7 z$o!<<##!xP%=?_E_ntiBx<(vq1T+x3HX4`aaM@^h?X1orpzk^j_h1D@7V*P1Z+nd!{8hz2Hg z0=7e0n3K$A=^f^xv3Gh4R5n%TKP5{XMG{Lv0E-==mP)MTWQ7YWpADwu9h1rJOlj5L z3UhO^SpdffBN_`j;Xlq5#sSKzlZfF`w~SAZ;ECi`FrvH;!U#MUo|P8>@k4&J>{FNk zh$y#q+H%AiG&&n*nA);5kx3$XV{;LsDx}xP3MYzePBbhNF8JHkH#p%!v}Knniz(T0 zxRu}(%{y)anG!u-(eo$`U{>uGnQ(6kE}M+4}g@%*H@(r7(p6nP$vAC{Q( z>{~Gi@b8WSL#GZ31N7t}VU3{!JmvdxTO|i$IDUb7RuX%xIDApQIDVn-Y;b$P;8{{p zosF8G)$3v-bL<}=ecvSSQ0fno=v>e)5J#KnuCbDY%=AXWa4>Et;eU;y!xQdT zM)p~}RN?t;Z%g+wDE)>p0;YPorQ`mR6=wXY@}Vn?6B??#$NXle1#YDGEMt2sdk~PI zDD3mR19Wp9^aDF7ndz9IZHCpe$OPEyNnsBTeY!lVW&B1uHE@{OD1;U0GN*AGhi8@t{*qmXIUtM5*?w!cZ61GJOY8!?f&~ znqKuXnyu#f$y#@6_b>omH+NoL`X+0b`P~DV)zx|-&f}D2*id;i@m=Q7N_%0mR6o;G zSa-0w+QeRmYYKLrnwLZ1^uf*_vGHtUNBEFLLK6>(gy7|FZ>6S0{20S46Ej}Bh_=Ag zn%Y^Kgp{BsL0Z6q=i2Mh5B!A&P~+C#wFa2v53xPlurs&WYRHK7f?=k=rEgj8dqRX* zJriq&OAK7CdT!78Ww-gtQG zq+s;8R31*f1T}(z#K#piX{n<7w7N6o#_A5N9ulD;JnePBem`^St5TSc`rN2~1Mc$5 z?c(SP{+_AfgYbu~QraK+T9mQUaTs(%=Vcx;VZoONhB1~dA<7|#Pe+xGFDCO?LtOG! z16xfMxoR>=v>u_TEFAqnzW(WMZ>oWSVs&k`AH@=V_K5+eA{<&Icg&cO=z)1KZ9{#I zIFat5zk>sWsyV?d^5Gr9YZ0dp$5V+rThFv=O76GHBHGSosBKZ!{f2 z$kYZ;9>ojgC-ZeTBn;*4Tvq^X++Gxq5$ZLC?+c3iMgXKEzc{hcaJ&eA4he}njsC{H z4?+|FS>8|VtT7Dm&zk#}0CyD}oy~Y{NH!TZxwgd~Y3mY-71m!g6I~$)K-~iIn2KS}(E({l z$L;uxn8(%>1*k7DZA#Sd%-Iik^olM8!PlE9Jflb>)%iZ%MsH}0cbZgJaWjVMi);1# zd~w?_5Su>m_SX08-NM<$t_a?xwV!Q|i!7O^&<`+{di`qMQkiwk2zrc*(RW#4zmE;s z?t0EB*`B?RhKm5VuJV09F9t|+k3&kYmV9EXMkX89;hvttV5RKJ1dEbcvUg5@Zz$DQ zQ6BtCrA&&l*(H0^ULWZNx5uQfT5OGK9N~$*5sUNip|QLSw29nG^W}5*x}~o$!$46f zNW=<>_e<+nHhrxuO|SF#{UGS|0jAsrCBzZI2+F59Lj-`klH&)?Fteqz8 zCo*2O7QoiSUi9@ZXZR=u{6xekKtOr4|0Vt8zuPzbhrYB#3)UxjCFLtml7%(H#}i2p zTL7QnARZDq8D<|D1Ufwtj0{;q!ExM&j5H~OiB)i3t>&OewMxSlIOVNUV^>SEzuI-J zw)Lsw(ynTIWn1;!&AsZfb(?S5ZsV(iH6u8F5A6QkvG;xZI@fvbTiy4?W&Y6h%lB^< z(1}zXETI?C3l-w8fv@BJI!-YXhX{{7G{`>5R7mv$(litAy#eu#b|I%^PThmMd?Kzx zwZy97BCHk1D=o?o zu9ecIo(>dzN~23UPMwf`T*48I@}*O7Y12nZ;_;eJrP5~J3e@J*w_PT&koUI<+ETB{ zEV(LXXXQGQA>t=eB{00&>f9hh3@^>nt7lE$+_@4- zadR&P2J7ucIo0m|pqNHI$)-e%l_Mc?++>^O4sfX-)RwA?r6x_A$0WR$BT@_t9rRKR zGbK0v&RmJV-da_kt<_OfjPpv=>T}e;PSRhUCw{O843XnA{6<3yB`FZ0LfSDHLn(0M zW}vcar>kdG*hqp3p~V%t9X1lw+q%Ni**nor!i79Q|m zli9H#)AQ6M(aQ)A0ZE9>bs#cSzdC~kyEF*X^#q1=#bd+0Qpm3Ah+;*vtZNwx+>{Ak z5#Ka!twe@OqMk>EzOa-06+22bw?q(g(d?eHi&{FpQ8aN;&()VXEH~_y)#Mf?9HCku zF&_(E$*O2dPKnweK$U2rw&SW8n+QV1Xzk5JOhqUg%K%WFU}px@>Y&=}2g$s0TiSZB zRSIy&HEP$J*jU+MP0BigA6;EZuCp_9D`0NCIo7rSc(cL3DPNpn4tlO!BW6VuF;BC+r2~JYmK#5+I=GNA@Rg>4VPiGv2T5$L93vl1@ zvQx7ybQs^A6%TGn2n9k5C)dc&qe%X8xt0l=FHVRPwr_a!%9duMNrNtpcDmZ+!u2Q> zF%NH4Gdsft6gJc<6dNZSP=^B|1R)F8Gyd*#GJWW}V#&Ae%Q0#&S2meo{h-jtjXXF9pcs0Qxa4crUCZ@sy@y)39q|8j*kVK@eGJqtV zyR$ukxFkLMaj2m;T}e*hJpx}?w}1=&iF}NnZ`SrciO#b5F|Kyop_^VV5XhI7&G@wdhu;qq^s~zp$Ius+<7KbDd*ly4LSjJy8Ad&WpN9>nzfp> z6ypzn2;*EhIDtw7>Cx|DwqXgx8xOpB`pS0fkZY) zrZ992)3rrL)Za^N9crjNl%wpsg{@nO{4M~368!0t!LZ4gOjI&OtzLC*Mgyf~>HJ3$ zNPYm`@L3x>V+&xcBsbg5NKnXF?;eDb0wQqWa4tT$E)ebcPyech1nrn&%}n5sag$Lr z#3px3rlguDD6?XGHK6bI<_=MD`SXHfwdNazdwy z$Np|svoY^MXwfMLHn~v2N6n80J&DO@HgRu@Q&r-cShL*j^z9>OCv|m8%T5m+RN+I7eLgvnXv>Dgsv)z-)>J>z)Q(qJ4*zZrhM9x0m zh)B5|FiSIx88hq)x=*2F*bUUA>QHy*H!_p4!PXsg6*WaCr>VruX>bM>F}zljt52+4 z5VJU|r0E#%hrLAXWh)ENw4CCm`pDIqF4)+yO}C`X92r*3H`!*TW`VN{*5LU#hHTN~ zk*`e5Or`7LvC~wvo3@>8U=5W8{kS zOZUrbR98$~88C8VFvACU7^SQVg5EWA?Wa^!Gio}OZH;WcB|VQ~_*KfiVZ70I$GC@s zY?i3JLfP}>&9!^#>`J|~Hd0~~5$($C3anjgPs+nut5xgnDyegCuc5@eb%G>p_s0Kr zQ6bzt&H;nbN6CIP+Js!#PGpoOYdXVy2!UHD#&d-AYb4g?VU~kW{_F8&w%|SzSxpQ~b)x%t!>&r*q+#h&#(> zm{N*?`AN=XMBu2Jg}2b{qg&e##{rr5;C+6XoK1VgH&7zq)*tzwE^=x3=lS+@JR@SJ zL1Ox5j>du2ZZ?qkzn@W-OcoH8Emi72mQ6}uV!FE(wm7B3Zd*!7Uv4xy3rq6O+*u&% zTk(RH0;@)sZG8^%K*Y|ZY%&?~^7Oe&AsTG4@QOiAm;Ke5Y~{d=B4mX}YlKHLaXELA zeVQo!#BGl!Fj_+?MYbceb0i5H7eZ zHO~dbNrGc2CQ2bNiWxL5A0+Jn$XuG*3!-l;#ydVlC_C2MG2UkjL4U;8r_&MRs%*0x z(~T6dG}$YaMYSSyk?$z=X-IhH4q=EUWoZ1=8+aEMu}ETfjG9`FWv80%d?db zv}AVr)}L!(=rrD>RS$+ciMnHioMq}@XsAOt7efq^^dQbY{V*DwuFMlXVdHa2pZTxI zMgOoH_IpB5To21%BusUGmAW5@l-CH~t-HAoo2{fh+12Z*0<*yjU0 z>Q=pal)1Vnw({cDA!A;W=3I>T?CyfE1$fWd07sJQ=Lrgjny9 zK_jPQX#1jn>B7%^W9Z(HzEinZthlsW7;+gg2K+1T<%I%%@RH^i|7(~eNA_pMtv^ex)=sw*=fDe_fUM1TN~+;XzE9^Lf&3>^ryY%Cmz+wf)uQf zK6z#akz%-h{mn(he}zA&OhfT?YR=82ibzIjhi&;LkEl+}3T~0A{ZuNSbG9}3=my4( zI8C6POsS=cah6P9k+!lkMnf~A*8uGqL9dT?ADk_p@=Ac~eXAb+J~sI6-YuJ5Hk0>K zm`r=Uk$eQ@GyQn><${2pY)WA*%ZB)7Q{ciMgL$h%d5gm0U*W%4yrzHPROo-8@k?7i zq^LaAT#fm7opOzwJ{bvaDEde#9(3!`@dS#Rj!_;-frXTSefW^Vyc!R)w8;*A$;KL%96LTSO7dDV~u)rCw=5G!C@tk z3gaE7K6N`DP$St|wO*MCT=_Zv zICeEF-A7O2m^R(a1vAWO)a}P4d(pvH7g*T30L7N6CU!; zJ%YTk{ML+PxBNq#NK>a|h5b7zV(z{&)lOWsL~D=>Sl~6Kq0lmE-{#w0;>P{g*cPly z7Y!rVoS0-G&pGEqAdD7k*)EBk$}n1Xe_entzBARlfXERHyn$k^#ht7kU^GTw4}~xy zSn=g`3SJeSyzeD{ii~tI%UvDQ6NuDog;8`6(%%7T9Shndgw2!3!9`3<{1K5$)%r54 zRN!dc-&O9vQF}{Xsh}atE8EJad>}CAQ)ZbkRylyyxI02hIxcet06ZwEr}s@%%cSHb z(PLvYr<6zoi=ay@@$BVjvUgSzBFbV^XO-%j5VIxC6!)}jjGi(VWv@S6doxt-kS1-_hbF=pSO) zc!EMa)P-p291{G16z`z3#krBLy?2N~{F+cCC_Uf}Qi`-vhA2!e`eLCvPBr>yqmJWM zGTEYuqIB3-&z?HD@U9MumLGp^U-D?|m9c=~NOWO;Eb4Cg;1Z#Esq+OXRP4Rjn?kr> z4AW3`>nL4#Da509>E7^_D7Q{Ns-IS*dT7M{Q{g17dIff&b9CD&D_7gttv1>A zYHg*Dy&t2SI=Tn!(Il&Qaq4W0X{G3geccsK?kPhk`?w zX0G);PM`J5^jK>=R>v@7j4P+|^;tU_Cj=wUY z4e;EI(fx!kX3W*KV!rmtF>bWJjb=Zz#v2WyIw+)Nvmw^fW?GuGOTp31JY}O_b{&&P zFdPZ3hX68CojK?SdbL`!q0(7`n5#|2FQAMIF|Z3Wo3}qY#c^Ak0=Hm4cOAZL=xzqS z{ssT8?nRAU^(_cF`o0AGFBrW4GKF$>aWb^Da}oW=8q3)9AN&GQ4`WjYm+$4(ziq8b z{-GQE2TP=+?NX^8SVe$dtzny_%D>R*R*4Z9#|TV1eBY2-7<=*RN0;(J-27Ky1W3ZL zIHAw;HP)O>6%pD}a9D0Gm-Fniuk*>p$IIg*uD{~67*ln?ekw}_RU;1EQ6ZM%IRFO* zE2aRoRP00!%$9vhz&%8j+G!0n_S5Z(3pC68Y?`~3l3p95Mk*&v$U2Ch zU+tZr3Ct={0*1WI>M9(+VV^6dvsh|wD>Ya11Z|Exwu{(C`i>f+=TC}y@YY=rTvWzj zYm0FP`V>Q!^dh>OXQgia#c!q1*zTqQ?C<-Clc>i(Ki$)69$`ci!OKKW8_!&t?I41R z#cW%EVB}%7$2CKDFi@IAjS{s7jmPuI(^x7B^@uAXQQlO$5&2Q!&Y~F$X6F9WMF}7qf}m*VdCL+Z^C|yYwoK6D5<u3-&*wk@E6c>1F!VW%@Xh zw#AhX^b$S&20JqjW|Aby!vl>u#8Kk2*b1}vM_?D^novjq6|*}6+9WhMM7mu`_%?}* zC!@wr7mk)dNvBK%&!bxLf01UJCsWG(DenJqF2O-9s}F+2!bj@1oLWz)a|iq=CWIQV zn5D8hv-{8Lkl4qnzQ%7=h|u@(wDAAEI^_R4AC$ithb{{C-WB?3zQtuIfLMk_n~K*x z1iyypX6({UO*Ekn`npnmsJd=BZg>D-yw}RLaL5B%szR4+90EP7E8e+O<z(i`t7KNm&Q#x7;s61H>d>lH#{t0-(d3R7<2h_oKV^(2c!<>=xSGCatV5N?wS zk7Xc_PyM)_c4wji-s|`d`d6%2j3Q)5z`{#*@3-^#5ea7{j_HzuO729;9D8W?BaVxx z36!sXMML>52L$FCwo&1IQ=z)Kc;4IbRU{49A^42Q-xuZV8<^BXO`_;zrs9GaiCx}1 zLSdANzevQM0`@0ez*3yJxM61aROd~*1X(Kn|L(IOXyD|(il~N?Xv_J`^x~dD zu3|;kO97=z=yVIhCHMG_^8g@ajpj@juOxGzDY<*d^r2aWw^YhC3thpCRbCb^nZ#2U z2)pQ|hDdgz-I^jfGq|+|#dHF+($FT)(7O(O_PAcMK6Pci(c!6a#gsJQsvD(wn_IiKhNRi68%DPBV2v9dB0ovpWVMwYZFMUR}fW_okZCp`+eKSLeHvB1Y`nH8d6L@&X$6tWutLY4ce36Oy+E19hy)Y!<*_pQwLHcIUqsqv)SN4Nu+>{wKg1zR>wZHmQ-n&;tMp=@0;VYl10&v^gkqmZQ% z7ZQCdHQA8)f|3$-ZFf{vtnIQi8J3(@OG{~FpqI8S~ zOe!KK*- zrMtAY<6CYgPxS$Bm>o<7)KXg)4K3i0Uicx2B0 zFgd;l&P6j4_#KOAfFrNX^T>441REx+7#GT)NBUrXlN%pI%%NC$8*9A>Ed&;m@5)Md zUQg0o{L^GK`s(N`ANhI(vWTYwUel0wvo48emo>~3MZ`eYQROV#VKiq(JhGTPBr*08 z1kzVjnts75Zb-Tps+mDnrKQG?)Go^q8!gZNFPr5o_w4w$K?Ai?H)$}nLRwh!n%TjI znoeQKcmPpbvCCd8_;32r0#;J_Y9%9Hsx8koUBFDb<#m2`T}R%A5n@&Z z2`s6F$fJo&-q}ptabll8`ewzte&U@PbB-|>)$8Jco-;*9x3y`~UIOnH zQDU(UiE>A6XDO!KO6qTy9h0kC`D@W9o`%6WEC;Q5I9J5U(p7|*X;;92IlZAo%^{`+ zWz;<+w|$0Qtl6~U9(!>U)pl$>@QZ-gQwsyCi8~tc@)PscjYtj*j9Mr*CNqZW4T7Re z{d=Cvm*9m<0^yjw!FTEU<>NaU8KmoMDF(3-2m<%F6(voz+a3?!+S>}tX6 z?A3>&%~kJtSR<^y#7mGs=pw8>-e}MzR9m)&`e3%tmgS-OV8Z6ruqQ?xfo0|+O_TdU zT;O3d7`xJ*O{^VCtL^(JwG6c+j~&sB5&sgc;rF7aK-RSIO(pUu!-HIp}V@SDo_YECxo;YvHwhO^X%e>GpM+I--JkuErgQ6 zFrqc&^yC1hZ!PPsP&+b7edCKVA=UPp#=|CyyYbPzQ+tfv2RGf`_U;!H3KKlg0(skM zj-W;95$w1vrz2MDa(?tVYPD8GEe?x2002c=>adb~h8N6u9l{L!29cMUs93n-UFwP- z*pXsbk27YqycBF#q<4T$=SttPK5qR=UlN;SEkq`cF>5LGork2jZl-B&d>hwc4^$k# z+PO|CpjvGP9138Mz`5;Dfg}D6=JKojc3?2O%{MYRKOhgFEqfzA%v;Fi{U0xX*ty>q zYf+aPy|6S?7b-R^Iu4s7HVebH23G^JVcA?~p<9?biQ1Z`d!=kyLN?FtTbpF^o`$>p zj_El?FAj{I1EHN6A&wa+XBahqnk;6REDGDX#z({h(F~L(wSfGw{9;ahsUTH2MoTK~ zQGqv(I)|6RC|(%B4$;(v?x+jv=#^T{46vRlj2oG8wn!hm!%jbYw~y48Jsw&}0!WZ! zGz$i5p#wsj?6p9wPJm#C+t*Ou)yB+&Pm! z$>XzP^0g*0`zUwdU^YMA3W~sw><|ymAKTs-_UmtU@eI3_qb=W!AM&?KjQGD-mnCeC z9RFh`G_d~f)##sR3SFw_O5cLIUpn;?_|^avAOaOb`JwU5nsv?cZ;(|Y+i~-nRqe!S z_67}59axZ>RW;=Y&wX~|Xm|%faMScI!=e>Tu{j@N4>j;JYd4Cg%7FtHlWR^hUB^Dt zUB@kaeyeWK z?x;M_o*ru>>U>5((J+kY`$zOfM9`u?{{RLt=;|8VQ$!#4w^GA9-XCDFklDAh*&HW2 zS<0GdZ%lfPH*X#$ACvNkVW_g&Vg8by{v8uN`OW+Zk8cQ$e%>4}TF3@u8SmPqq~fy7 zoM)(PM24?CXsu6n6U?P*HzKJr0@NIzjz2lmH5vQ#w$Pj!AjS0PQR^Hv+8jpFp&` zu~hC1(P_?|&_vM=INuiW;?m&Q+rIwJ+_3|k_0Xhw&e8^&VG-Kssjyf~oTeD;_Ac(D z=jj6~IQQzaw~%(7kyf0#Mi(yIU9oJ=oe?e0om1G?`l7p&(@?3J^EC)b4LLG~Ac(O@Y;MUs00+Z@($O@=;a@8MVX;M;zci1On zsy6{D%v)~xHa_G`tgXB#Fo6S~a*0k8xjfBQu9j~EI?^}fMKclyoGXRhKFs=Dl~ZkV zY%&^r&r^@1Ei{}j?-OaKbh^xiuA<9YuP?SrFM2>YcWDnxnHHK&UeLzXI;;f7hD=eT zWG2MZd=1J|CG7MK!9?7CYAs1pkWJAon02T!7wJl7crB&x@!KEd|0JFKU`|Y(6N@X1 zn~Re8`T@+73--OglOz=9;&-|L3s>Jo9XGUTfe&v&$yM=rr|4ftWG3}Hj&e`e=c)4- z)r1j~4^S%2&wKiz=svh}AF`()41i~J^zdAOQm2w~?<`v2C$(V%4D!0)f`F(l%tK%h zpr^2nCxtBU;9zm|2d1itcLv{*KA03b38_)rAgfm4 zfl-tU5mNIX3>Kfy@N72-+e2jTaC*Bw-Vty&GzuKQ$jB*4P5wYMzIo{C#_iYR8D(tW zu>30AVw`7?XIBEVsUJbZVTJ* zP4@smC_oVV{^0vd9MIlhysWm-j@R|~)J5f=k(rtufWy!xG^|dy8bs%AV-lT?kznBiQCDZ!M$Nib8n?xq%M1yeUbiKO5xv6YUy%biY zf}2ZZg|efP_i8UE0=Q6(5DFPFDF_Z0FDn*Rka5BaMwK>sQ2XQ~)m=gpAODNPpqD?qTG$w{xf~u8Udt%J{$??Y_W%d`aKFEqV zW99xU^9xYqAz9)&+Mz7?9oR4%x>-k#f;toUyGnXuWqxzyPnUp@xSomR1Tt?)Vz5O$ zBB~a?ysXrP^}k#Z?H!=3k~MPu4p}Wc#@G6B=AqYnGV7}Ut{5~Gq-LV@%}+Y`<`n#! zh^(xgt;l!LwKFg>|BI3OKcY}Y${UVIV(>ghQCmy(-TpG)lJ&H|2`3n#1|{N6u(%Rs zz!NV7DJbJ_YEwZyq8p_osMLNv!%%vRvJuk}qLe@Tzf1TM4*g8Q&dj%8Mk%vwFKH>6 zIsJ9@a-PGt^P@GE2CIbLPRYq;vscClB`7CCixt}2)(8=bwYxWMIJ?Jhxks<@gQ{zD zSxPrYi&=rb&e%|+S6#6iQVm#?sR0fIaVVDngmip7ZAsTl%eDKyS*tON7R;sQarT_N z3l9@%FZK))dEznm@~Vty=>)V&w-m7F{3=w3t*~Y1qN!89eW_RP&v|xl#Wi`4hS#>@ zo@(y9{r4BRMgbnpSLCXX=AICN)IyqRa~c)`_rgkYIfQ@0oGka%Z>=qsT1b@%*mh70 zUwcG9K~sFw2fYo(q($u^1B5$O%WaR9k5QDIZ3RzIKv=kEEzZE^?chZgb z#q@kVhMYhT=0P#JcYo!MX|P@|NFCCN56ImhPbv$AoIEp4zq z7#YN+&QkY#*9sYA(};1!KoYi|-O6yQ5A=%j8T1dfchno~cqx0Sp_2Zk-=B)!!rzz8 zC)c*N=ZN6cBj8$v3>WUu55A+;@1h5-%qH%oXu!W2M=Y|e7<51qF~wh}?8!WdSIycA zHeTuDeU++*GXYp_gj|GXYNWEIFFZ)|#;T#Iyhql%o_H68iwoQL*pUI@_Jf9921UAWOM20MUVoC4kcFLNTxm-Rco4op?z>SEJ%?8yVA$;% zRZsTV85pnH**}rZ~Rlij%#eOKJH;e?8IVXc+?_j~YUd)lCAc z+QU|~P6p`o>TLd=tlr0!Y@P7~fvd%E@PpL4$8ua*FX1L5ykyEUdq67P1B7B8Z$;7W zhjz;7=Omh`g**=T08uk==<&DWPAKe*hVEGx?()~LgsocpDBp!BASEBEuaqPiS(jEm zhEv2KL%vDx<%7Q)2{(1M$0rOEUpNHzsSMu)=Mf`x_wjO4sNB^0*J2H7_U8PadzUu5Y09I?L-y z=kxVXOUaLEfjE6q*nmI#=>3(*P7c_8G3ZU12!{vZ`l_(oY3o(!GImn80myigZ(NRf z_(Pq@Zba(gyF>x}ZKUzjbXrzzFir#7Wg18AHkv7#woBDVlAy=;rbFcDMJxq|a^S(L z&RS^b<27ekp4F9Vl5HDO6~pz~P+=2^&NxtDX4x#bw-uh^vW)PTH_VtNHAE z^qk^aj?9WURP-hqODGNf=}0^qFT)U(IcO&wsf}i&Az#^9g@`m5u6@fD&HSWGZ2(US zkPFwICLvy(P_^Xn(nT8Hb=!zz%u5Qpokn>8Qv()_h%EDeTOOPB~2w6!`$LqW2l`^iy*{Iq&}v<~9<`B5GQe zl!Bx@AvW5NebexP1`Vaom$gwd@5Vw=50-#SW+)0}&>$h z%E8bjbKhEI@w!>u7Tw~VHK2QJqjwCU?T}T&u@=m<<}=dvb4cm+eJ)XCl}EzBMcZVz zV$j@BhEqLblCUGF4+q=xV(;DzD&u#)#hGT3d6Ihx&$@w~tDl>|0okGemRlrpiaD42 zbku{KRCc~?c5%wmo4E^MC!Lg7t6nym;UM?{v|o3sAzo~E5m;-r#%*J zEJ~~s!?ZYl`-4Vg&I>NI2P{?-3`xk~C~DFg8rf~hTyE{^h~1F6gGP|)x)&&yCzXr8 z|L{fL&pa4n6%=l!&Sr8lHT#`u_Z$1`WhIL52lasfbcKX@Yih5{AyTq!cq8fjn}l?f z0VLjN{jb8kVsQ|SND11O!4~3BIFS$W#Tazkk(;{E@91OBqFdQiz3SzyJ^!_F@!Wjw z-o~>t!9x8j&1R%rGbZ)ud4=WVfm|yVw5pWg_a&Q6&%V_tHdg*_`(^uOdiN0cSnB0! zm8_J=&rLU*5k0$h*@J2q{U#o%fv4NP&|BoVu+N&#gg`B`2c`X&7|~0f*Hzp78o>9F)!aR@^`W35`&|!Ebn}K z%xqGIx1-xA{3jWq`EUt#)BeVU&emg$q42WDlYPI!ZCq5nN;| z(;S9+S$SD%gS`zJto2Yg`K7_g>zp|-V?mN&Dvcq)bq>_ycj;@HjmcOdOfS_riZ zZ*a5(A;0!w=ESu&UqgBBXi-Kir-2xGS{wE`r*WcJY-ZT9Ggwro33ZPrSf*C!1LJGu zD2O*JvL7mZbWa(GL%ew%YvkW>AzptB?c{Co37T6`yg~1og%<{8Yb9$PI21&Q>n{Qo zY%`sWm(qO{*LRM)NDg&Iva8;*R_~GGt*p2^!WxXB;3J=6Gt^lgYV>JnvcRrn0yNK* zg|*I@#=%d>IwQ=`x+aX$;u5Y6MU;FvGle_S(4TTnP#6NXxT`8sn0XT1pa$?uBxcc! z0L}~KvydX{d$LXu2enEZb@ZLIgc+WGsBpNOuJJu4**Mn|4IB&nByh5W<$7I z@a}F7toVUV(TNq^>otCT_P6c$n8B>S*msXZ^{t)oZ_XzFSOEV+*7Tpw&B6xG2Hz4n z-_FDTc`c%<^X+(n?kkH}A72SiXsD2HNd8-B)6znrpb0D7NT`HT91Ie1lTUPc`}(6GvNu_bAL`XCLA(wPTW zFzn_WeHAE^rHAT(4onXCiN=Pj<3X|2bh;HF&H~l%P>aeA)xKR}QGE9)O3kZqf4&55 zc<|^ojS*Sn!fp5bMkMtxN5)JkSfGq} zy8&LmpRWQzV$Tp?{z-3>qMUriX8O8??h!Ee3;58Z%y zmGV?h*^jWp`M8pk+$rr@WWIQ6wxp9KiTgB-XW@QOHqjt^n}+n(#MWj#1&jVJYCA1q z=*-$WN zUxs!=bJhDr1<2<#9j4oS8U4czIDDaxhavXi4mYdc0`3jkW88g*iL=*?vMUNT;OBh@ zIOMy(t}AnZnt8SijBPubF5rjeAxCosp1<3-r88Q=C1 zZj+*Ft|t(?7>uM;e*RHMWNPb|r=2I(D^r&AAKL@*puiH|y2sx$8R52#wLD?)287A8 z6Q3H}=R5=n`$ zmd>xSo)ua5NDbg&i#r)i&c3q@<8C{J7j}`&Cu8UYNfp7h3tZJ}c-|Xq^zwCHZ5RFY zw|(8uFrL(jzxO>qH!h=?zcCcAZ;s{v#T@JZT%N1iIojFUSqNJgm^m8Q{7r`8#SQCQ zcX@c(Fix*?cQqz)J~0(u#5JF_kRJz=tvPG3?12AZl|5j-j+s0umL-dAS{9DSRi~$qE+4wG zq8%IsrMQYH-MP;>p^QNCx0a!4?3{9_o6l)hkr|v~Q|L7BH7Q+NlXx(fxZt7FHY!sNPgQmj$hVzbu3@*~2B6a(m7?&Ev#$z)Z zq85v*jNiykME5TwyDK2VB&>T{JN2k2wazhglIY{bx7oP^Q6@kMF7jy$RQxQ>swxzx zDgX<}c~szBh<}y^qS>s^h)dL)=d`RzL8%h3)lZ)0>pWyvs_$&&?glH}l{J=g%~D~G z2H$>*aDh#4fb2LjHCv@XE#D~vK`Lkiw&JS<-?_ZiMC*4 zcy*TFfOQt%aBj;#7sRtUXygT=gs5~D-LO^<-japz9pHrY?AAc~4eY>dKI-Q1=(<+L zNi{#*wly>#I-&RIUW3#x@wco>`6j&h*6(IfshuY()jlk6qC8pns5NsM-|)kP9xT(A zpz?IPO_4_FH-x=7i>s81R#wum(kDUFGzmy;TxiO$&q^QEf;=zNd%srQn`LGBiK3aY z!d`B&8*;rcO{PwJ0^&3Y+GVa=H}&q*1Wq==wA-FmxR7=uFrP`3(FxMjLN~uQX9EyGI*X_ z{CR`XXdoE$poRei|I-H&44lY*06?46UkPxfIr0e$e>xZF4-0-dW`2Hq5aunuDBvIv zfFU&te@D&G_e$Yn1-CQ^ULF+QkOFt8k~_8s zx4`|h)xp!8bU6+2N5))uRRjV{ta)5PHcZuS`o@;I2z?N)w!INb zlzg18!$R|i<*o7ia(!9@wP<^#g~JTfYJF_1-asB;#@~sA>V&09@v3&0g)b;s$qZ30 z++i)<2%k`Gt@Kn_>uaXzR8f7$~vkvS(w#QB9x4n7_&XlX= zWn(Z_Y%K(V{2prc)t9xZy88N?Q@FJ%V0_p&4srX9L&*PoID~_Xfw76PnF*c9_s_Rz zlC6o6vx%|te`qCHn|zB7x|%pT8Cd@(ewz9pL&V4z*_yF|1_*`zLgcfpZv2vD1)w5f zem;UQ1-WMfF;}Z|Qd39fq`L~;JL08@UF%h;K{-?Qw#)GKT#dpT9~ncgsu{lSMbdGW z&(ojIZ|A(Em9Or1q+akV^wlr~oYVkxQO>!b1w}5RAvVRV6hq~a7SJ6Zvs|R(LUy}d zz~Msl>waL`v-HK=E`BlORw$K{GjM3+<;x5k6{3{}NZTW^5@q`Vb8UQ%J=INfMf_jW z)DY$iz7fB-8XbO@AE}V3H>&YWKcK$nU(&1vP>oDU(0HDN0)`0A(P^=?{7x0zDS+!i z)I3)(pX<@lsPJ?>ROeHF!2=uvctOiRLv>V6K4E7v_O{&qrc>&e@=Ld9!d)#&)~h%h#qM$k7N@s{{?mV7Pi-%01C`Fqy&m zaQ=!OHKCsi^9|_!I`=^8dhQ=Yz9XgmUVE5Krnd%G03ux?_YFd1F+0FCkX9dr#(wW} z)0r6JDmZO_s`uIioafk;fIpPJbIWSoSDZH*&Rr;$>4z=8X7Y4gHkCUkqLyRvtuImy zw(d#4C_Jd+Kw-Oq$gb5mfB;5ZNty8)LtSV%<#V(}S7=wuH+%i|?ckClQ<8+QMXYx0nGIl66N)zu>ejA*$6#cknKFx~7?SiMVc_sgfV3fPX& zme>;FY|}Ow2wO)m3v)JUD|ob9@M4>EpDy=fx()qF>8c3C9uw29)ArMMEMe^#l)}4c zRqud5DeYK|;nUSLM`&UxVHFVLY)HddUs(VYGgKyg5!4|bJp#)zWTh8FM%(elXB)Vu zqcz5?(H#_)!Doysut4V5gt0NFP@-DcC)E>MV{Ff?2NPG;MsVHj?;IMku1PFzbm+-C z(|c%p0S+!^KXGOFLq=c+6>-;zqvNsy(!H#&+ZbDejo47`B=gK(4;d01-pKT}NsMfYhItI-EUW zOzM5;$0axXAY76aK+w!nVnNe)A8Jifdg}hZ z9as2n#3lb9gI?Oe$ywCS#nxEiI}QAm0i^AW4F1bUuS!|xFNO)vLaMD6sz7fbVdS%l zrty&w|3nx#vp~h5KK-rDMm4+fsioV-jh6QVnm!@AH$CrLLA06cL>M_7wQ1to&yK$& zOs3oJ?^jx`e~8v*31VH5<`ESX=OLF=W;jwnALvrWp;xuWDZm&}5HnOyB4Og1r!Ufz zz`LCnMOuSXBTr1H&c(xMIN)?#NtB#nX8y8rXR%OaJ8M4G7!GUKsH4P6y-kcVpTE|~;O%G(V`$VpH+or0h?nVvE?*THW-Q64SnBClAEdQ}}O?_!iFnqLf0D7TGh`K?$Zoet;I64J{?9%TACk0k8mp4T*gwOOQi); zc9r+lm=RlA=;E9wHzMMEW-b;MOJ(VW@`3r6bTSc)2M$yE5`BpgZkz3{JFJ2(sI4qIhd=9l0L~^og2qF%gln6$`I|$$Z?YEC7LE3}MHPKv z-qE!O!5RE<# z1XlY)QWoT2xaOfK%hRXtu1tu=SkVunE3M~2pEjR2Z=Lka3-^id7r=dzA7nS<<5>YR z$XrcswmD7z^tygnxxVK6aiTtIkpCm}e0+1%NHHdbyWDaSl08O~50pU#pD8;?mmfhC zm+d}%%z!ma3wn*89eSz#h%F}eLY(Vni#)j-4L#F9pFQy12x1x6CdBXTe@(h5^$+%X50a?zUWPhVg;>!KRPC6fj3*ie=8%q`K{pV#Q~| zS#7de`F!ywiGXDVl>Cm*>6m?lL2K0T86-mp7NfrV16>!g1R3E9bjU3)qm2PqzdYf$ zPENAzB5E9;dVUcs{MNjmN#LQhiY&6Oy>NOphGj~Ua^37rK{LrO@sz&0E4KM16LeA7 z9SaUkJ+wmXssrq0pP6<)NJ)2F*pk;taidytO#I( z68tnfkJ)jXc20qBqckgUz;MRor3k5CJ#grvU!)f%havTCeoR^lDd$XDG<$0fLvgIX z5!4cIhlKWKR#-!pE+H;Mj)nMc>7iBHxmmRH_y#Oq)8x05%gmRf0?W}nLM&Hi!xM&R~X_gPXD-U2^+NKT`&Cm5tDAjA#O ztA`a9a{Y8p=1YAB!8rc13RT7tjm>x@WX z*~ceGktQN>s6->`vR2)R=@bu1U<->ZM605UkA9{aCiw(d8|#Hrl;)Qz-zE}D3LGK> zZ0`oJxd)j&19d8j+-7f7Ew^p~O2{Wq9Md|@xOLxedD-fH?B(>l()jbVW(d*%g{-+| zv_D`oI2qDLScF?dIt)!lX~5sNrcW4Fhrh^zwz!ka8Q&298sD|xCK@n1S~)KK(>d9N zKY&*BXA8MIt#8VmA}H-v60(MJ5Hq%-wuk#@G8Od#q+{OM^1k|VF|!DxMZ*!RS)*op zMS6u&CxeYjwa79>w!tYm+Y2QTN_C)#mI}HO%0SR-j!(CRmdUr%u)SDN&0|=bUW$b? zSypyhU5PfW&}?di8lvPTX*))EEqo>$3x(Ksuq^&GwO*%1^w^B7YPgj{qk)*sY`jt? z{o!~uM;Z1@4SAh*i^Q}h-Iw7loY{i4jhN+V@myJOui()HFMd6JB@{(|Q=Db&i#W%6 zdg+3~HS3G+4@5|?MrJ0fh^SPoY9`fwJ7IN)+~f~GOJ3V)3hYgMwunS-;}sjXaF<1@ zfmSXXHWWsc37(}|v#DW}5HC+QYLjw{wt;rW=ErSyOhkGW9o@MpdW5B%sJhWum(6M2f{IW5tH|bDd7G!^pt9G5qp1+5oaMI9LvxsGLj%m8Al)s~a z?FA-N7j0bxRO7q0#%co_sk)>xL53{dmuhC&X;8D}oJTV_c42Po*rigf|8}>l9M=@W zb;uGOs!nRtouM3!=D3fcI0w+O&5b>xegfydzKtQ*-}S|2twrJi9dDiK)yQJ9*PRsM zw%!d)uQ6;1V1UM2jROJ|Epk(wlw~Lx+>EU$yvYc-K6pglroSQGW~`Nx7dt%Ga>xv5 zxs45w;I`b==`67y!8}5aye_#32*BGF3aQ#tR5;Q50x0n2R;PlbI{X>{)oa1(SZPii zNrO6`3}v~hbVR~FHYi(_S*e^b(Ne#Uh?f-i3TyUr*NNr6GqTd{GF4vwysW5f%AQ|C zIxxd(wqiks?x}shD}BE%+f=u0jkzw>dJc^$keTN6vT-E+c0|Lw2XD9(=*V)b?g5gQ zm@Z-0dI3CpXK9RizAW8=9ZutlFx)W~c6Y;&h2kjWRY7`=wsn@L zpR=sz33PGmKYL(1RjqS{#dns&A=|(+$&Vwo%B^^_u3!Onz{o}3?g~vBWvJv)G(6Mor8x;_!yB}nj#KUMA)$2}5|duk`==z1n${yzB?L|1^$a^8#^5#V%y z_mmlSA6IX_>msU9W1OdyWUG(Jr6yGwc7uS%f{x@1(~F{*W3cLB{yl;23La@)v6x{s z_E1C-1G$LCUGcnb=Lp$J>M*KZruwio^JKRwtM~k*7c2Qm<`d->)uVGN#xG2;M+)`2 z$$B&%74Lz)wBb-@EE?3=*5@8OJA^{A7685e%3dR0Rf#wX>CdziZGz;*q#i4RMDTEIxJAqx zC+SGVI{E~@`C~a6@Ub6zSkzK*d;WOcL3>6$Vw9gugGP8^=I=!yg+xCnz(&qFdD;h< zKP55!v_#@`Udf%u$@m20)Yap9#7^cY>;QJ!cHCs6?p?4hUcL&3_;8-%MBjGM^@fN( ze&$`3L8A~QuxJSH5?O;uclPnVC~>z&XMgXpR)Nx1ZQ!N;ybNc^8b7hWF)Gw? z!UtH-tGE`1NpbVmL-atS%vYkCqhFzE{Sr=BP~285Df4WGWVE4)6M_+Qv)c2wO5c3j zms;>$;jzB2u>Z{q?EmVV|5@q(WvtAO6@cug|0ZiMu9s7x;R>=%0kkix2ngHbEyybx z23a6-XMWR3^9F$*esD(Z^}TyA&8&O?HzI6G1nRQ&Ypcr(G!=XkGMJ(( z-H4o2Z}aXshgX;GRhzSo$tr`fd$QHj^ z`5^llCDLOhn)&`Hwg&Q##z#8#1A7ok8_mdf{*5DAf@q4@`uzg9-!JfQuH^sQ3n;l5 z8e6!2yVTp+5;OegN&Q6Wp50r6ejnF{(MIlTr--cKSxoe5cuG3c8n7Skw*Bg}@ zGMUlyhB&g==BXR;)#ib)3}VCnUBzT=c^_!XSk*UL4jc_1$Y_w_D|tO#2>fKQYmZG1 z96+%C*Y%<-_{F-@wwT~4zNi~&7N+;9AnL)Xy4Dx%>Ucf_3QL#2Jx+WJty|;wU`74i z3>y6ZYy|($$N4ApC}iPiwlg5i$X*kS+GL) zB8#bH$NM;w!>m}Z`}>GqAKT6O1bx;4Cx|m2eLkVTHWYzFV&bM0dzd*LlazT=KYR*O z4A==eVbMN8fTq=CdbXPwyBpg!wY9}Gr5yQG+tH>%vyq$NI3^d98-Z7722&(kG6gNz zEG(1N4KNNZgbr}Uh#nOu%qyh)=q;-^F3s9)p)w+a{LPjHI{5tgx9D!BKsIruCIDND zV8!GFiY+g`SwPlOt#UC5Zys3eSgKjLq;hY(R8>nd1zlVK#49SsK5&Y_JkJ&(4uS@B zSpljV`w&$1c}mDP(x#g~V=8Zrx!>-V|S3s$NcEuz}<%gzqIfgDdwNV?L zM=836o?w&f^X#x6TXtJ&X%(HYbuw5O5C@SQCyf>q>(2b2Fem zX!N6T{U&u`E;LO^Y$WjvthDWfOc;-(pdZHs7~D$iB9=i6JgWB>E!Zkfs75Hh1P?KlWERmFAk6-u*x0K4Y4XZSyTsCV#}@ zIbL;{%{Xn(biLeP@3;YEjihmgIw%ZO#)8v68G@%VYcC^M;Z&19so>!4XT);ICs$UJl;^gg5B*eq(lbN(?E!snZp@QyGS$Vt&d;kiy&O75}%&e!d*>S-Pa6a#D z!+AE9f%lVu7;i@bC&?x=-yMrTWr9X-t|~S`9A?pvS*`?62=}8sMVB}cqHLs%T&;(mbe&e3*VyaBs|ipm)H%h9oMH`wi42EsNPK{ z?pl%gN1(J|4}SH%*kG-{8p0`tmCd4IyL1)!R>HHsg8`DPIG9{wMG+3pguEuPHv_r> zEHEt?E!=L5H|e1u&|##iyfEa&6saBT=;nRiRL)`80x``1nKP-2gBT$FKSY0_R z16;Q_7MVoxlr2+0&kajgg<48wGl3`3Pgejs+Ax}+;fs~|1~--o+68+1hWor@xdb7L z{)S?hd6QMX7Y9ZJsG`+MALb8qrO`mIPc#O3-sBIM91M4V86dd2qCF=VJ$mczj^g~X zQ)GvxdYbY@-It(Mud=t4e)S~;)T)NC+l=DrN8^}1MwJ_Ro(wMb$UX8VIWi9AkE`V! zYkk`_)`2?Zy+`Ta3z^7qbUkQ$AjsR!vFA}<%#_eU0UHWPuFwO{XZT6?zGxDs8Alsf zCUT&HItl?5R~^g2jH_iIn5#9~o;6Vs+`JgSn-P~Jwc$LVqBxm? zy~4&O`-Rt%eZ4OsGWiZxEnk^iopvN`E&28<=l7&<#qbRCrykeOZ9+jK=gbFU=$C&Oz#u@MuJyK(Cc<9qmGW8w+m@dX%pBO^8D>`Q3xk~yU0^b_*MnDEA;@CK;( zf@*mqqWGdFn$=F3&F-3_UjptYs{@%i#O9xqY$Gcgn}HyS6A5*SX!kSM4;GAWXee}G zyp{;eKuLSg%JyLV3P`_uO;fuXMoBvUOxje}k^x2&%pfxIm)!{^Z*@5NJ8j8}1bRv9 zyVTZv3rXqzFXoNEfPW`x3n%A)%^J!Zw%-}lrpC~E*;PKBtc=N>ZZwbOR*kl%vam{!8wtpTlnVFf6yd52n z%>1BiO%VhPK~5wuZA}&g4k0E;BXKl=6iHU5E^RH}#|%2_$fxDbA<(+U0^HOZ3;NIh+4SE`FZqsUjo;euQu4&1Z=_2#&f548qv$vMJe*JY71{q-TtnG(_ zPD)1dL4l!IkI*O$c^j_tyr8NqDXmyK-Z7LWwq8t$6^}1jI%WSB&bW9C`v;C#@`EaM zII||d|3~Q3{-BNqq?4L19pmC>0J>0poyi;4CYnvC@&Lpps~*bT=zUK_>1aQ*G|?1& zgWh4}Ii^;~BYvoD1wvWK8^gYa0s z5izmQHscl*bKYp`oBVhVyvLl_1CHTFFvaLRmk3odj&RDidVF{Dgcz(-d;w3~0Z>)g zATcfDQA;>Yr`S7?GRVwuQ8z)AH(b$gw^JYrizA1?L3G*uMSf*CAAC}g9q6KcL}lM- zV`Qvx=>zCGq7(fGa++E$xRo0&X~aM4cWSrFalpsif-M^f1VhVQ z5mYY{mWluVg!>>b@O_4PfpywPf z!?kKOn+LKuP^vLij0{nhTV1M@chilr|yF<6A#VT;Us__P)`!u zN{9>{oG^Ck!kJ|l`4QT5(VOx!YC!FK@mb{eV>rJ2!gCI^WEUekat9LATV}Ld?z7KO zmDJP_xFGpMA+bV0|9V_nCY^nVen(FK68Aiv%>fLG^D0AqFfrB(y6KBmtdZ2=nf9ak z*D{+&5NY{pT!+xfj(-J^TadvXF?wUZFMbAe6tl;N+AmZ6!{^R$qdcS;Y&v zSmOZ7M^uc1nKnMr27rz$(}wjyDq@OfggJgdG%pZin)&hReqddSN>G-Wvyht7og?vq z$6dPJ1SQ^aUDAySoVvs;Ax?*3d)&0n?-~qn=JPw}LZyYslzi=hORxr__7RPRw~pF~ z7M*a-D@NY4C<6&~qK}<|Up2#7fmXL1F%Ioa!bSE_X5>jx&OkzcpRTze;-2l%zQqB# z|84!D|J!W&|8e$CQMN_Pnr_;*ZCfjC+qP}n=1SYPR@%00+s;+F&)KJ1?Op9w)w!*W z@h~4}^yo37N00daf0ooWA-$EC+W&TYnMshbgCypG%!(RAG9wd@Vo_-I=YT?J8LE>d zWg6Qj{)nKWP_@u4{#h(uSM62Rsw_a&QXn)uSGel?wwbDy%Z94kuak|+| z4+fB!9j8CZ^7?JJGmRPd_hPIx;*{t+C{ENdBDK`Cen$)o+}jxxywL(a)$@%A-=jK? ziC&N9i^ZbE>kSG2NRyhmlUwOsV}A0P=8p;DnBP6?NSJ92%h5gO%*2C_^5D(OoUty! zl;BK3geCjf3XuhxSJlJvA`<>vo1kclt}Ye+#Gz5UzZPL(3Xo6AZJlNYReI&`xeA<` zJx$E&1k-Ak3*#DD(F>ENz6tCjqog>~2UE-?=uj|B9jZc6Oq&E#D>%D@D<<@N1!#6i z)hEo~b66G`#f%~}wDV1#?Nefv9299poSi}RNn{xVSK8DvVW~#EniB#RT*YgYGTO7G zAgYQPH8|JD@a`U|aaiuHliu>8sa_s&aV_JMs@?q)`1cCp1R<{j4JVTHdPhfwUS8_obcQVW}^Sh)MMgg#*!Q!a@H1h5ym*iNMLpXAq=QgTvxP ziP1ypk&02+Q||MdDyR}9+0BgQ&BasIrUf(e9N?xTc-NNjnYd$Z>AE_a?cQfCk?Sd} zsMOS{%gQucE!{1a8x2FK7gj5b4x~!9B2~l*FfJtQKcRsvsa9(`W2$m$Yqc(s(#uS( zOk>XkOQV^15tr&hQnbKCaEQAueBjQws8FSaVX6XZWPRcf@m9jIFim8Pzitb0I)xM! zQ%AvWyEH+@s&30N7%a!ynEGOL7*{_4lPDUsVM45Z(c0kT`=g?)6!)TSRJg@)v5=q{ zE^q@4sELIb`@*4;fH^B;9IYwKG9`#-VZFUL|wmI_tpSPKM)E;r5i#h;7};(d&I zxVc=)&?My8%+*e58pw4V5?aB?(zR$Zp-iP9PZ&d5T1aNd8{}mgYiTD*Sqoyb`DWA5 zB8x;a7;@C5(rj`1bf}q`KI2Hbl0+rqiyKG{$62O4MvfFXNYr}sMHq5g%C}y5m2nd^ z*qK>HUSh=wOL!eqO|;v!e1XO-UkwxS>C8g1P=oG%RH1J$@#83jWO+jOh8n8Llby@K4)2Z2+k zRqrN=)8pF)g@+hej%dYCgA-g8}PDx|{vnw%&NHHQw zXs|4`Xg06@#`352wG@fJMF*3vp{B58F>P4<63AJm&BxB5oLnXAwn6s=qgpptbE+`a zN3?DQ=A}Q7&!#ld%a7UKY|bZcXrxwYMtv+Epc`z?=D%jEruA?|%_ZZ7uUj4@zRG{K z#g6V^#!DCyG)*K@66}PX=j9RfDLI!YuL>=}K4~%1Q8{K6pM5c? zbaKPgOy9!C`$0>Q4cL^wJux=-J73t_l=X;Xy`Nc~*HJt1`rz7Nb?}P2mp2a&x zBW6T)(2*L_zuNP$h*>ma>mXd;K$D(QDE+fhf+`0ohqAjd1%Ys@A1?cOuYznRYbA0J zg>$tpl+2QGM`)oq}(aNKPH4Q3}<_+(ln6YHkE1FYH zhIzw6LWEMjB=)9pTNJXWh~n@N*cV`LM82^y>Whv_Lk1L2p?xG6l5o+c)GHw16hm6L zA7ugKQCl=jGFZfsyj|*R+*h^H5eeX5(}pX16*qCHufs{Bv<&Z__vIbtTNHzMN5G5P7xFGnJ%h8FzR^Yw^0)fq z1lVU|q!+?+T44XA@Y>FjpC(BwfxYz_&X%6SX|IGDp@Cf$et~UCuV6VxvAiq@1mP73 zf%!z+8dl&y%~r@0wZkOzwC{YW4L(@}@d<^&c1vY+6u(umh2O+g5WZ%1Mhtj_yJZr&rZN&dWYk)omzMf@6wO2rxtk#ALj&t zy)p2OHyMj_YpS)%ldPk%;}$Z$GWu$Za~a+$Tr<8xzPI;{7(bG98Q9n6eKFzQ-z$On zWOoy4;WNBifa%EHHiCV@e4GIU6xI6E`o{J|?k2ZXq2>!?J2v&rJV5LE&h4hYngr?X z9q9Tl?bhB6P{h9oJ@C zQ7_w#)-$W{n`x-)(+JGl>zZf* zTl>Zi_E(kr#_jWWU(ZQvg49f+YDvObneIlD5DzCUuNL5L|7GlQS_goYvxEv(fX*NB z=Gs(oEzVBwtT&R%9Por|c9D!Eo6!P{8ipgP=*>t67xNWUFO;*@%gLpn{zStYu#V^AQirP^^3oaKr-QzdmbDsk-1wEY!F*w>~}M;>N(+;9i2 zx6-vZi8d^waxeo1Rf@5(;>Mnli#hk^Mqj-QAVgG04r9JV0<1eHFTv5JNuNn(gwrkZ z{Yb>tLOC&9i(&>);w3(Pj_=wl#j3F`BtnSov29!(NDw$l;0+T$jDV@<=|fw}KStSQ zc?)vXLdwQHV$cBBEqIjUQq%k7Ehc8T3gog%{`XQ~ZSJ6<_V9w1^}eJNljkm_$2POI zE#y1FVp`!99R*|JWGz!>y}tnuA6MT6a#q4x<-NB$W->vQX;uUewGG^|IV0E1W+pqj z9O}@Q8PO;yc}ndzDdc8)J066Y+ESBwQ{Os`X;^FNBF;e#dZYeg=I}CYTR?$x#ihld zG;?5?V0l5oz#>^T49uzbHzo5@VM<*rk-=M7K-e*Gh8JTanRYTI!(@fREg^-J$Im2`J#1=o2Wl`xKiS|9;+AuRex%mEXd_w zb4&#al{=`6(pD#=f<68I<4>I@$0w`O-1X$XEI>z))6W)sptv2grjTD=NeG-B7#dG+4AvSt& zLu%&(Z-Sd|-E%^DkDHIfhffIn(4L0x5PWa!dx(g2M9(Ss_MIq56^@%f_stKVeE^>2 z6gP`;NmnuAP9s}FnFGod!R22M7-(K3xGsB89znOeLQ%FQ#9WqiJTI#$2&oBox2VXj zBZ)7+iIPO;Jb!YgJ0wHA1+LJD6~V;v*^vvU>0O`eogC6E6i~=wOPX=#q(gk{DLLGB z-4^92aPN^i^@LND#Yks$>X+sCsC&O;bn$VkhC^2bZHS@GF^qhjAK}u_XJSWzcVHpH z4yj`~mdzD)l`hrA(yrSVN>xgM!E{O}ZcD{mTG7bllKg4GDMJo?bu>GLhjE0;638oD zcsB6J`zNwwR#y`c7l-sRtW^WqR*7!6(r@ox*R1nAcFu+vR*!t*Pr(` zd|>tMP{a#621o;Wm)O^RjQdp_w9}Z?TR81j3uIL~y5NoAoGhL_0O(O9nk8$rl-{7o z=oyjAI!QMal@XksV(f2X(0n1;jY1x*RS;~(nS$peFSiun0GyVAlGka0*n6Kl1#swI zuX|Q32S}X|zw6%tF))%{il@~ubPC{MTfe-xiPy!;MvjLRn9^qss^>Cs;_%!&Bep7r z=WrNkpjkPMn0O`6Xu4%{C5wzI#PM8RQ2$zi(OQ%6?p{D$emTWjqIUo8=!DR%_Qzz} z_tLZU00h}zwEgS=`(pgB$CL=XmCm$N6lWYuwww*rq^AgMb7=sl$l zk8FYq_~uk!h}SRVn&LOJRPW3-MLh(gUnwo~I|#eqvel0}m~`vM_M4(J)UhLd`+D*? z=mTAPKXYlIO{oly245zgLvS`Hh9W?JS>J*3-!P{7dB#y`+-{cMI_GM_}2u8IBtKKPV#+`O%12+W)Elw3X zlg`K&Xz14;g&$)XlTWIY`RWy61swsfb(?t$dOD zj#)G)lkI_xpGckI*cLH%#th%NW{eY39WdzhlhN*>X^vMb57{V>B*5rc+pDkg@2Jp> zo_b+e=vN4i>4csAUbq;SrWc6+?yio)=lJ6eN^SV);%l#{fD5p8gj5)(cmz0}I`j5? z;_C>Ge>dUDk0eoEgg)AbPoq-P)dcqSXc?{k>fJG#VycY9dxv_s2frZPjU?(ut22pT zobHV~wTIn%7}J!+Cu;S248vFDgi+P_bRB@tTN{04_(#6Jcmb#Gwc;v_PZ*?eb=^F3 zRows@uH_OSCZ3R`5hl6~?kwzmn0Ff<6~`ayda^FD9uCy#O1w8bjuMBy5<*;=5>>5P zc;$#Gv5t(=mD3YUG+ewtIb<0s$a4J(eSjk(BTD~h5WauBo1d3g*yEESdm^kn4L`(y zTNH57s}WEi@rB=ZgbAi#KEGwYWEob?3)!SN6o3I)wF^HBZ9(`QvgoW}&3-Z0kOb_G z`MrH=>1dU>GZj=(WPm&BUb#8p?#z{!8AIL#gfC9~0Vv!rVfaDHnYRa~UoT&gH-~VW zDQuT%q~1@UrwOvXzhzQUf4s4YV`!e>ydISaQldtiRAv3tCzq`Df}mY?{v4Yn!w=+< zZ+g_c7;arrOP1;Vw`0}=VYy!ZZDRDZ)q9MLB=)p~Zx(?C%>d6K^8gQ6T}7+^$II*% zKGXOiB;5Bu9!UU2;xDxJiT-ynZz&Pe=XolWm&(fOXvLv$!I83z{OKi(MTYx(aY)|0 z>P?<&fEwWO1_lVU_BaLF#1fim`WN-6oW+6>O>X!7IekB~FTU}8KLV^TMs*pe9 ztR+yiQK}Za>qSTM>Juu`)c^@eh$h>Lvg(l>PTH7DR4IX81{rT{FP$+gG?STd4`g59 zlAxju+d!&+i+-wGV{4K{*tgj+I&K^6IhI-wtg#5eMp`hb6QjR_pn8vvauoM$V?wdF zg6B~RzgFF}OyX1YyF_$3Q&lvRQ+QKTJm{!hrmeDnx$jrN)YA$y5MK$7&y&rI+aSrz zlP$2=Kuyh+2wMAA%mXRcf|g5UN+8;xxmY@#I@-`(EsdWYtaHCdx5!5{B3jE8h9i|? z&s9rAECi)nJnE%A>|-6P1x^Vbc4$JXR=wMb)w%r&>;iU~j>)qlvF-N@2_LHXdmP?| zvty|D@%a>&G}4UwGSO7GQlO_s+|7PWv1HRwTsy!04!js>8$@c88m=%_DGJigI55gq z94(ES>3XMV78Cd!=CaMCz?#c6_C-F{66vi8dt}wjqN*i?Zh2u(t0R|cj&0uElAvmy zY2T28)eOeeEE;K{WggcOQ?t~vP})>yRM8wB>lcQ@)7D`vxaF1v#q@!5cGXy%8b_fo zVMkNr_*dvJv;`=o=3$(lFtuZ8D@RzQKnHd3Y7($t;kfJ1gXTP?2c(IuhG2_BIg zbV_o9{>qip-3YY>ks3*a^(Ta38HC{}7TJyzGRsy4{tn!yJ97DAZ=_*Y@am;!0Sq^; zXf?vk3866XGnd4HSG2G@+DCe>{J=RhQlN4o)s1Mm!KocX?WEiQ8(^#;)VsM7{&ub0 zqd7B=zU#8NGLLg*kaZB_8V%ivo`znF@kVa}hZ5^n+#U1#Qzt~fO-d{F1zbCxaW=Zn{+ znnb+T92zB9LRd!ywuv%uI9Vvy>aZYRGO!|qomm?W^A^~aQ($-&N9mM1@c66D*q=Qd z-ucI;?B`~e#`<+F%f#lpo7VAh+>){O0JYwAdYf(na#twjK72+0P&iXiH_0$(I<5}r zMx50>m}$E$-?bscd|{D_GXRRa7o2884r<$!S3L(k>Sw^;%=DLG~(f ztv9a>0lp|LD%MKjB#!6TY?BI=t4;tCr&aAdJEq&{*LrOqHf@Fd&);o);PCO6GGk zDnbxR1~k+tbNMl^>RGBK=IhCXytA^X?nnuI5wnQ({8l|*;WT-{N&AyhK|YH@j#=xf zu5QyPA70>-l`UJ+hczt4^!qPre{3=OYCVe~vph0|o5l^XVb{_1ymB*<9hC~8OUh-j zt@W2bh8X4QRxwrj*1UJ!CNd0{Xu!JYQmBTAZbSz~E#?X<6s_fgpEqv4qZ`Ux;LO)?;5HzpkA4@JuvFMa1ID53y}@3+EVOr zQiy4epjrjf(vP+wIB~DAA8OfZ}jn)5Av7p{TJWAws$1ys_fGJ&|4U=el$-0 zf8J;PR~OVjJH^y=oR!rux6Hn6nc5}9Un$3e`Lx8Dk`TiA{e$(z0f`Ag1{rVy~Kab`+~NjYa!vm zzC<#YnKV(WvgDvrM=Irba5InEfR~d zDfkrR!1q{uIR>kvBbNtT^CaGEYb9((U;+hjqvyRVMIfiP1PM8sMy2GCBl920{4_`l z4Yl&$?HmD+&ma|$sWYqrt2q6p$K5qqh5IH8RSJHY@%s8U6f4a-#R;-%z=}20Gfu4I zrv^rkZ3(}d^G$YJ)@sN$=5A)N8ivujLNO_l=AFwPu;WuUR3yuvb4S;#B+EOQwo9af zd%K)!X{&-n)N&%TShJj_b3kdUgN*JX+{GfjX7{4UF-={uLohK*X%yCws4k>phmarV zoMld>jH|4d$5y%>AC{E#sbajH*|!4iKBj6*!y2!-t+)P{$WrZA69r!w2g(x!$?&fjIRWWV7sk5B6nt+sec!)#7 z%Bc)48IEL34fcYU=2-$%hC~Wu3B7d&*qyzRcjj-SW6kbx3fqI-7w3X_ad(CvxVnRG z&fNh|oM}d&0;cDs0%8HZ#RsCey2Hw19~#d3KykzDdt&4E!$WT_I3n9(NhtilUC&9u6;7-3rI6ufL)@bH^kDmfJ-OY+m<+%`JPlZ{+w=m0;i z`FcRY46?g_VGr0_qZecjM)#wvat+^+riCdC!troW*g8#=1@Ja6kAMrv)@t#EVW9!XSoa+)J`F5K z3O`|1y~TFH?1{H1qM^EEEkL>%$i``p>XE(gSe-EobA{c{<2%2}3bcFGC&04j>NB4` z3tp_;s&C2d_e+rSRv%-VB%cjaT~1WFq;=b!jYjLeZYGj!Pz^>9!Zwq7cNOu|oOAO! z1;zsfj0^V{6VrF4^s2`L=T`hfX%a$PtRgiA@UC>cLvGvsM_OBD!SOVHFo z%LZywR;?qQZ|IaqXuqb?G%Ct1yiKe=(XbK1(PB8L+B$78fVH7ey;<74NV2+|k9Bj{@3&zZZ(pfVkk9bKi@G+t?3$FG zN2ONqcB%X)Y;^A}}$NKnsjN+OwsL?mR@yK^3N>{)n zSu|KrY^ieA?{a@}^}{ct6TT2FZq+?1i}kcv&fvvZ_0|XiUmxMu=tXjayz*x7$>0j6 z=69Kxmbd}r_0OE;X#--w`6=~mtq`;}f3KeTt`T0mCHkSQwHg5~6bXd@9XgF~eJ6sl{zt3xGD z6{Bs)W@5=Kt1|q1E6dOIm`)YtI!j2#UL&u=-f%~K|E*W(RU^wIC(Ujn;!H<*wL$k3 zp+_c)9fF%aJF?_G+#cT0MyYj~tBpPwN`}L7 zR(DolGxV!F3iL-oWj!?=E4BO$Dd~v329=j8r-f*LUKB&{1Ldl&K;{R-0(WH?_Iubp zZ6~MCJ7%(W&aG$n#_Dl_o@X+K?m;otgA#h)H}g&Rw?fz(;WXUs+UBv#Ze>}_=;Bqg zSZZ70!#$>w9sLJt<0aSG4#88c&3hT}Ng^9H{lvDx-B|`ZrgBeV;g0e5-yD@E_?+JH zU#2nG>WtolHTHocD+a}8t?P*urzq!kJ~&?qQhyIQV=J@^D;FJ5oa2ZI?M|UQh2J`P z$?7!NE>{kprM$K?eMsiE?55|wmte)rC8m!gS>Y<87Ep&cMA&wj6MT(J<;r3c-+!Uz-i%~7 zhjN|PzrQjke;VND1z@GqtC^Dow_E1bZ_}CtQ`QK2tbh&8`aJ@yaU!_fjulsb$Pr&_ z9}BY^qlUOg#(LGF5Vn5QcQv|&V-2Ajw!Mt-iTV50J{L!iO0PAy=dQJ-ld%#5zSqyA zX`3HbWDydds6XzA$X^w)A1mJ=sH`dFDUrE<3!{*!-3(hN2HS%*9*a7Sf>IaW`_VdC zE!c;pD^|g@9)0%%xzn5r&3Fm3F42rXNODi~1ZMPOQ)1r_1rN*+Fwl_DxgJ95wNr`a z`OrEkvypl$OD7fvvKZQlZT|J|zxAh=jReSy|1|E0{xsbFKQgNRw+0IpXN!LlFtU}j z{xRq}UPjPGn}N~xFp!Xoj)e&u zhGg?Quh5$Uig>7joJD&oo~!YvptE2_v_}GczYk(1O%KiQDXkO+?%U7P_!kvDPzjV0 zQ4y{^o+NuA^DP-60}?)25rUwa!x;*9!9JMJwCfziL0%Zy9eCkj{}_vPcN%JGA{rdR1kqdeS=mqsnt4DhrgCHCjSseOH*Hu zsKwxQXSUmZaPb2eWgL$;!9YqRsCEwCu+}<-*TtTu^hoWva4Sf{=Q>G0Hz zs6eyc>8l9ZH=?Rv{dD8yLmURblA&BygzRr(;D#`2=XwTh9>d1tFnF8Dyn*R2k~65# zGU;$4mP|#0K=f3Q#vr(clwc<=viny{0FCr0WdB&=^yd}$Z=PQM>(ufuhDeF#gzINO z5k3i{;TP#6%!}|+6bcDOpny%0s2WX(5b4B;4FtBQ2$qo;K`AVqJMG$zBlkyY1QH3L zBz%|`ms+i&`w-npOVw==r8Xo}-3|J4@@*aBGLB9oTegpFOF zB#H3*b(QR1l3z;QQZV#jcsG zqvekZ&j%3z0LOpxzyD{q;eVdjrqtcsl~z#s)Rr6E*;=^cvr%w114-!#LiLjX@diuu znQ?+7fKrOX;z^}M6JuZ@*KD|$#j%(&iX=9QsyCv+GK1N4B-TraZRS&)Z@SzTjxTOD zUmhQ6j6?tWzkfL2aK3DLJ#)Tj^Zi{{l0#Mi?m8|&bzT)nqHCB{=2e+T49z**EBA&G z^LXWe&$XXjpmUrvAooc;k-NLKAom%T$ThjeM3?287;xhDj+OAv$)~@)<%ZWg+>?TP zXPQx>dxz@komJC$Wc5r4OU3y56Vz>>C$pX#HEm!qoo{#lY86I zJ9B@V71W(6w^MlY>kOP`89w`wnEY#1gfBn${+1d1OFQ1hXJlPhpg>AqK#Tjg^qWyR zub;E5+z&s3lrb}p z^9onXv*XXoqwCWs=`RjA=n4(|aCp|(P_cnNV{WcQC?WshKII0+BEv3PJUNg7Mn6d% zP|uSvqrJrtgtC&Ztqt(LjeP-?+0CcDv~?aPmOL`gFJOhLwv`d!Z}VI}L{n|OrV`h% zq`(YQ!=GCLb#oFZ(-<$&PXSg8MeL77VM$zIr-^Ash|TF<4&eY0MUaVUpFm4FO~@;V z#g@>;MK%Es)@@hVvz3yow8V8~VoVf$Mj`(A+F`GIG^dh%tvpp4k}{MNu&=`JVTxqy zMb)(uD&?4dnvfJgr~D-%>B|~pL6ouh_Y9BmX}$ob+mnqc=Eus3MfUPO1$_YpiW=| z-eFD(JTc$Ll@7^Krxd~za0IWSp==*PdkKf8eB!-v@>4nS)Zy~H9l9|giX9HZF@op$ z_Vz~HI6@Ik(fKVJ!yc>bgCgppj+t0e<@P&WX4^jG6v_1<(th6&J}bC#HmYIBmPi>6 zar2C5@?jpt$dIq#?l`-KJ-__t`h^_QVi<7DmN!h2hS5%-t)s*4NWG`Z>hgDYAbd~d zP#y>c;B%Zi97~=Z7_Rt?6q($-6k!Ru6*$9`99uwM9D@A&nn`&RAtP%*neX@~fL<@_q5>N^zmKgnLxXlSNUm3ep6<-hy%w3?^iG25}#M zh4{+($(!R}xy|CEz9Yv_Kbqmy88Y}eODRo2@ap6$vmQ-I@Q0?p zFiOz3DrBGDyXa}R&;feJ{c6B^M*Ug=t?9R}ySQZDw7z9%{*`s1`vG?`y)6kr)@fVa zlt|r$#%Fq$*#(^g#y-+HT5$Posb7+AN4|fMi11fX9=x zTTZnDli_)Vi^=HlHc-|aZ!N%c;iv)zCr7|xPGES8!EzO|;f1>VtrHJOxkg|)%=)yg z?W?khyx?53S64+u7$6#{SKu%&Bg^4`#rJ6=xy(=0g@M|?=MY>VAG#0bnWtW0laPvR z6e=NU%Q)q_nda^!KYTu?PoJ3O_K=*sNu4o$ePW$XWt=>`4TVwi86l-A_u!ATWKJg7 zr4j3CW=sWm36QT)I1phcd9g_So{(u+h0(Ad!PrYB?_tFuRr`xF>9IU8hjI&4eLZB%)JjrK#ezSgWUX-r0hVxmWAwsWlY~7$HC-TB@&7d1Ar}Je;LASM8SP9 zYl2_ZnA_OdQE$`!frl$?iM^qt)xTmgG8~Ekb zI}q4q!0)k(nlHewY7YZ+E#R!;?}_5tAT=@U?B@(@R;yy_t%o$mv5gC9axx=Brl*EA zuPmr4ld*R&=bCArDh#yNbi)mJ#WTwwvq5=~e@~(EOuvjN?A(i)UeMUkxZ8(mPW}{_ z9+=Cr-jz4iQIXan@$4`0%nt_#pUI(&iBkb{CMGvZXuOXw-nU0gzFW6a7Kr_byu|g? z6ygkK@e4F|c5O}8eN&mP0+!4OV#7IL@p)$`sv+)t!Q7G zbKk0dS=I=(Jey3MWVf=iwoZ2L!Wk0xJsGWSdR49RMrxu)4pC>}eR7z~#wf49!bV~S z5!b%Nti4?q>B#c9j#qfO6Saj&%azs#@j%KsP4~BIyZpjb-8kX^l^9GV&Rlh3m=d@# zEho$?l`;i3O-O6_nBSy<&Q+5*^pZ?ell;21MW^k%ZR01hMvnKA^4}FqrrM-LC`i)c z$)U2el}uZQ<$~TY?TomcK#7WRA{r>^Gi7}QI{rQ%=5F)_EypCSUJ|ExYF;`{Xufu5 z$J)G<(Gi>~q4|kHJ@7K&5}(E^itL4%sBJ|}4EWoE5wgB^rN?(PvG>LiwDHSZut~41 z&eDE`y{rOuxy1;IIr~P@4gn^md)V&_w}aqCijKHhZ@onU-Xe-W@uv zaWYN8n>@GuOE%-z4gd$80b-?<1^9`&l?qrM!UIH2V~War&WiXnJ4eqFD3t};Kk7@) zl_*lP0zq5Q5Q^x>a3n({wAar|g_YG7E$`lrEk@~Pr6Xtc2S9G_S?ZM!xQ)m4o|kvg z{JdmT=VOFe<~8O;9)^vQdH>Y~jB5!0v>7Afii=n*MoTCNy^ZWsyc`C=8onC_XA zHSO-#n{Zm13fS1ETlc=Y{86WH{IXe`#+#Tgz$dXKOWwrw6CIN)Nc8*$)lWl_YFhP9 z)inlPC8Mbk5bSDVbCN>^F%=@eXwh*MgUH@aEm&F1mkRUKe(%Q~>sDeZZj&DA#zORH zjnlsR!FYaE0Zt?O^6MfYp!Ohb!ez)=YE{jnH`6k$wv!7LwXA}QxtaKd_#~dY^PBvt z9J)iOwszo^jX5K8hg{cg(vsSm09a(Qrvq^Jrp(xpu1_cj)Y(J}e`}6PH5x5lxhvE)EHj75(keXa4xJeNpMu z;o)Rnd{ha=j_!rR%cCzM1c2V1Ly`L^@4|#4SVhhU%%bK|*>v6j6i3z&eN7}Br5?MO zMEYTfV4e_&S@aBu5rlcTOl;7i6r;qTKFk&k(qbH3pPC^}(+W)w%N?aLef6vecQ(1G$O?Uqe_!tug*(F2bFLgUie(1gCB zai9J(-Tn!}+bnm5(Oz=kWuQSap3^x#%NkzQU8P@79~*7}KblVYzyplhec^%HFo?e4 zyL4vPcWXYN@P_h?RffqgVV%+C7;1On1CXdmFw>Q3inr>0$N{Ix7U_7P35&B8{9jo* z$w1Yh3yD(S(|?>*AD@s+kU%$Pr7Hgz-8-dKy|hTH>F4RM{%Y58TA}&lI`$&H;pnuH zlUZ1ooavx*=I%jd@`pNhW<}9TyHWvO7J`;w8RU<9f1xkhzkDML1UGC?=JOqWiok|MsV zEagNI)#(lE6>_I%ufj}?iDi7zLmSWMvGN?i4+R1bse0=%xiI@ZF!Ygd5FP4#R>+D) z+;k55S_6O|NzKBpXipcOJ`O2kra~lD4JIYst7yjLAY7r zGxvh@Kp4<9{8JuI>wN9s-Qeb5(V@axq@Np>QoT0uQ$)Hy_v*ze=h+oh!4({ajV?uY zr_MD#eULFRRV~c*Wet`}@DqUCxcxgxxZMn@HpG2wmdT~`%DNBR-c=dz>IAVDWRC=UHAX7`WK(IUgNpY&S0Vp+gDr6YC&k z#bf#>6@qtUfyfrAi9q|dj$mi<0c<3j$!?Y^U7bWqBN1Y^NIGa%b8mdwq>b~;@3K$j zQ)DN3Dx4a1E6|#jdL=x&-@iI?|8)g62Q4mIexexp&kAtOD=<_GkKSfS!3-7D}F<{z4(MWPgIYZvTgGx}FSgREdcP*iFs!tp* z7A;p8Kbt@GAyt`w$29CGVmI3!6~(JoaOr66ETeIgznhtLSGhnZ1hK=YZALSxV6qA! zh3VlVocznThYW$+B!ZU@J(CaTS$j?F$Sina0P%GT`LD*1=gf;h`U&k`KcW5qApQ3r z?)b09_)i;DD^Do?gJp{w3L_ypcq@Mk%F95Hgn~%Q4FWP0sb1_~tU?#c&?`R>JuOYK z`ho22f268op0DET@AbDg1wTJ0C-^o}MUpdW!{=kW`($&0=j-XDlpdg=4L>BbC{h`P zki%s@Yfn!FR9oLvRiHi^`(`kp!7GtGIxv8 zR&PhS>cF$NL8KD?iJT*g9ebA@2Ta9C8sb^iOn?KaD(YE35-OBs*3QHuXck;kmD(P6 z0~@Hx&y{Nr@1Kaxn8@y9B$J~lzcYVzv^fx>5*m(2t4clVahbeXz-Ti5V7;yPi<;fa zydN^uH6pgXrknDYf`ilm!97(d3yhKQ0EMj;#z0#nB&-`lxuN8M&gKF`j6}fV&Wp$v zoFe+^uRuEPznWMNxeU~90d52dRX$?-VQQd@s9TwgY@fTi9-`G(y%uYf4SGM~pbU|u zx#zCra3fZ>7Mp$s;f3^cP)*D0a-YZ@l?Sm#m|U4krub6|x5xeYX`LCv6N$DSr-IRP zk)J3K9+OQDo<2CBBkzl1;ci*Mv(nT%xE840th8AcMKsOM>*kG`U( zXJU&y;xx+(7~$r)dz{N2Uv$JM4J<&LkHi>s!H)IVPX<9Tn4i0wsg_tm#W6!Z7@fD>g9f z`9;zJ$2EZNBrB>u(oU4ZN~j%`fVf>lmC%*RniGz9a7-iHv4^Z-?ceFXY7UM2c1BbMlBJ~e`V9Ptr+UwI_?>E-=qmJ~Gxm4VAiUcQ+j(9x{qfY9@ zDTvDI#T!S2lhsp;^)eElX~!zB{!tRaCx(lY`rXeg&-dI?%`1_h(V#F9c%w4+vVA-Q z4;JYD%3ps)`-@s&k|?f1!LM?{QTL9_ef?0YJPSiSbg95W&`G!1r{R1Yylc=7@UES3 zPp-qsAEjwu0ijNDihH@jP$xeFr>5M#mHRa55cliTg6<2`Uk2aq`#2n(UiPX88fL1FA9?SK5C6X?I=4?;gIap!;W2L&0aAI6za zW|2&$s8*$FwQlDUh0IXY>^Ug`gb2K3U#7*MWm*fg`#HTM5qS{!Zvek!4hbn0`LeLB zbPwj6E9~d5gC|&iPc)+qUgN+Z8x|HRsWq0Mrdjk*aF>??sa*RL4O;&AT59MwR) ziZES2+^%1J1S@yUJALI~M>gdIXnNBVg%;M3k30)Y4-zE$B0*ycKU2h7OZsdiFyVyv z#i=radYI2JFhB}&AWfP*Fb?@$ z_SdqG(~-O;0w|K~eijq%Zx^u;DQdkBJH~qI7vhWgQqYjJENY!pPf37lQDG79shG<6 z7}^$?3T{89$bctcYn1wxLxkpErg^rO+KoQn`$n8IwgzWb@_IQ?$~@zk#NZ z-yq3-e-`l14>0D3F92Xiij<-j zwm%>#v;TD^v;V1^Zq?zC$@Nvh9{@m`oX9~cn<*b3*;YgqiPA?{25u|cP^87l)|@T= zMR^O}W-mZQz+o>4L*Se#yV<)A30yfaruW3%L#zGk z@Pf%Vgs{s79CrK6k=)6+a>jL47 z^p@4y^zSR^H{4&%_F5n?jSeB1{iGGsCH8{X%owaIN4`t-LHb96 z8!a1qHW|tT7jLw(tV;Y`iAtOGA1whA>OZ(OhWOz&K;Q!|NifyAibCtWzzeQrjOFiI z9vCI2m?L@I<*s8myE&P19JvEKvUBFAa(8Y9p^||CU_K#2wF&Z`6R9yGOi{}GE09WT zCZT;P8FaI~-0UIlua-i6j36kH=?Gt2kyt8C3-_kj**B7sQP~Bxq))2q#$##*C0u?+ zXyCHrdw}%^2v(tIlzjh}+1uDQK7<*utX|%(ye!0=oCW@-BoB?Iq;%AEGY0gNM)MRi z;$vuvnNUc@ga;*I^G~R7NX+S7YnW}~T4~j3-u!dv0(oeS>Mtx}C|#l_45!0+s5RiQ z8lDg`6uuomR%rOqA%osO$O1)6(2~T0RRZFf3oBM7r*|`9B@b-(p~vSVC=?t zRGKU0u3DwC+45puwX*qd`^}LF6HNT`K!fW1%xzNsmyiF!+IPTHz5oAf zphP4yO0sA6C?k9CJ#%o#<{+yjdlMonSxG2DHjy1!A(TCev_(??_fhKm={m>J?|&Zm zT=#Ly^Z9rKdg{N)LlLj?Q&wDiDq@QlCzIU~`JkfG8dtK+s z`4xApx<^hIS5n8{ewg@z=r_S*cw?n@?}j{oGqFjLw3-Eb8c!^qtkww_s6N$To$q|L7c1GJJeb)7uz?CtXJj)f9tnd@eVSF=2$u@XF1 zm>?53|0Yse3(qyOth{A<1tIoC8{tDYXxR2WB1uA2fAGm1T)F+Vwt%zn0e< zJhIjcM~aZ&{j^^#fihWgawX-xcsutY*#c?u1mP(H1YSU2@eo55i^%nP zIESa}!CJ!)_e+)Crd|oif;X7b@2?N_2F5Og%+X$)h?fWmEwVY|=ysZaF@H)g>c;cD zn`ZrwiHrKphlfXJuVcLl4|LFYpq5Ura9g06eyzprd2o`0%>`yk{&^nrZ@DmCrIycz z*^GEm_fI`9jC84_A7THv%1n|>gU8$3Z(lTwn?a?Qcqc+vud-cOBP1zetIo*qm=+Id1>d#b)CEilHFcH~yY-i{dMsFI5Q$N{sfj~U&R z47_O7pfAwtEnhnbzU_*lJlC*V5@XDUFr=a)_;x62?VZ0b z$B?}lwvZ{eKqRFn@ns(8T}cGSCzJ2V-K$^ZdPkBb@NU0Xjjrjv*q3Wr36F1*l6`kt zyyho5)p!Ft z57Hy}cHkb1viQLt_|=h@d9!;JUHKL8`O-CrkKDel^fcK#L(uuye2-vaL>y0?+)VUH z3xAp9=gTfXsNhS>Z`N62Sr`R-2dxz^rKJ{xALX_@tmEj-(sAro3|o}(!*fG)nQx=D zY1;+hk#Sl9=S^2sFGMVRG%a4qj%hX)%zGWBSu_}$#3a(kyW1+V$+z0RTg9c!I!Y(x z{7bSY{#}ySUb?3)lwIS9F+E~A;Bw)O>70eCa;r)h&kyTmZNMcyc{ukjnem+%S zz9h0lC|PM+9lqXz-FU~-h73FE+soWjx(%G7!zspv#6|_3Ic2kllB`zw+Pu^DUlb^y z>*+k&7Tld*!n0r>WKBv9Qo)*6&>18rp)T=Xdvx2((V-?uq>L`LUZ@}67PY9>up94 zl?Uu^A1XR*S7C8<>{N8)G4&$y*F++zxY36{9fXaVPA~b3&r>}vPrvzAs+CI8EULLe z-EL~am7#9&wDTdKzz9i7r@>SATACvLZM<>b5hDbRvcJr|?)hv?`3T2)1`*zu+vF1? zzQh&Ym7?x*`(fq9eN%4{*dtVe=WfL=&ID&Dg)s-VrS+!2I5^LGb@qO$a`z}-cbCh9 z2fIA%#t}YY2SlFlx^7D7bBx10lkSA#A=5G~mWBk{3|6apF?yt z6h6Wvt%|ez?8n3Nlg`Ax#DAMs!@Tckz5k)Nd-M)N7m_1fHOyrSRy1(Y2 zr2|SdRDb6rLJ&0ar3DNknj>^ca?2H!)3mBB91-w*4MDNna0}e2$&> zs5i<@%+;mHXn53~?CAUS^ggGqC452CU{kE*Ky-(U|wBQt5 z3fv@;m?21g*g$j*E2qSN7XMaw-5t7=BB^;I?9rBV;gsvU6SPy$2x$gN z<=eglWg4VPfsOZKuVAuP@}~?CZ4WG4!S%&+eL`B(yG`d19tDjzifwmQnh-^W+FQqw zdq$Iod?sH1f;bY}PVM&UIi*ZnOB~%Cv7Bo8R|)M!X_wruyR=9B`bbhugyr88X4u{a z>ulPu4%Ml1@|~y-AH_0-QB=2|dVgeLbs<;tL)0a;Bx0Ceh;tILV0r1|%n07>ugA36 z82yf)en6`0Z>2RbPW99>MQ7%O+Nyf)H)fq(2@?UzKD(ECsE?5eVbeU4E3Kz`p(Xz4 z*|ihN{dj3ssvRlg#g2bVC9g6$)J~43)71SOKutOuZ|lnPvnn3UvpM< z?h)&sUX)rgO>rlhGbQrgr*(s`i?U%dnLDlK$g+MwwJ~C!k|%?4B_Vc@ddJJ+i>)*o ztdFO5hqc~3vfKsNG-^0f8}6$v_hB%0cLd+E?J1iGyDlEneNH`tgoJi+BI2F#=0Y#q zD(VjU{`l+ecd7#1uD#$f@&z;``63kHWpgvV9wyRBnxn3w->#%jRV{fdF#3B z&l)^iXUz4m4#{`N*hPWl?EZFb4($T%RzljixD0i+Yu#5%uHYQ_{Kcv&)}HAFym+Ml zL%JB(>y<0wR`cWGKm3HG?$GgyeHf3u9MdRVm8>$Vz!ly$J?gqtGgk$k4S3#p_Q$vP zOjk0yNkxJl#!@sH2*?O92+R#VVbibo!fq_DpFK)R?qzKGAnVK{hGotN9x}~U3P;8) zv}Rx3?WJ6yq3qx(-2a#@$zop?xx0_(7+>;ip2eV`abH==x1@#XyEvWX4pl|AhDmlQ zWYKPo2&%Z)AwInqNmrPAEIYaG@+VL~v^o{rvKOz0sI6d*eBXdGRgP()jbON!ipQH~ zGGC?+qw7Yz##&6d27Np)gne(MEv8+GH^`X`U1mM>qUxMsb9Th%(4O)zQTbRv?RZR0N!@Fs1>zsusBTetUA?y#5oS8>j4It`W)DuNe@f(9Ro z3a=eC=}Srq-EXY-{)DOKdl6H0?q(}$T^f@6um%;3Hu$Gt= zudf=#%03@q-?mG(>hD=%SHe5@@==odKL62oeRrd=b8Vr)vu`u)Nw=!fR_NQ+eP9X} zFDw>vWYjKIYEQn5UO!)P%$x1ek?hc?uO7;Q0w7*Kf!Bw zFNKyO=<-QqPWm`K6b;r|Vk4o+I(Kl-C+xUIPHBOMe!gSON9ySfLYB`AdB%U!xJ-VK zcIp=ME=EzeR&4#-yV*lr%r#Q1a3e`3<&SwZdlAU(PN^o>fEWV(jtoHcX30Cu1MFig?Pq2?kuE_gt5w-o(Zo#!|LN1MARzpz$jHsUwcOK!hV?x z&mJ+8@+eLSDYW3@B_2FuEiWXd&LB&?PFyGTonLZM7nhr#{`?90^Q_=+CTz=b*KjUDxslq?p;JZ_nReGAArW*x8;V_xbTmJ5&15qySFS7YZ`*@${8LElp}i z>G5bocB@(p(7k6Abr%b%yF*#t?XdJoF3~%9g7o~`sT<43U)75D9wYwBnO4)1y1I|t zFU#}356$xPXY%=LMG3=2$C{5(PvN`ZB=3DH{knROf&G+!d6>a_UdNELr?Q3*zNW4> zN)z=f`Es2oZcXeU-FFJ;V4mUQIfASBSXen=Fu&c*^Oia0XJRSU_j1#IM)~8#6U(w{ zORH;?Jam&d{B8I5Cg3m~Cp}z$?u_LLA{Q7BWurXLVWvIwWo7XhB~3@koh1S$Yttpo zn+kLto*9-Fl$56Rxrzi`9!_(-eY>DVcWrIS=OZ!V2yta}w=c&$#=fjCy{&39 zx+(gD_=>?wYee!z_41~m=U|kt~$a=l~uCDc-k?xJ0{fqpV=myKKIN6^#%6rUtzp7bk#cB%i z&ZbL!k?#EAF&T6DtNBGsnp9=+;@BKx&aoS9j0bY4tbkZ6ue51zytzg_&_8hWhF`^!Z=vyZBc1hc;`Ki$n|b?`N_y>2(7CzXd#&VA zn)9G!IP7|+UYY4b@5x)v%k*n&owzkZA8*(Xdfp;QV*J=4tTpfjaTo7w;`khe6>}2L zYTV;TF(e*K-)Gvoo_H^fikcZ&8R|aQt-A1o=hEeX{W0Z;8f@YcoPjSnA6e}3ueT+Q zUswF9X07hyO4a9;qkZu5+u~8#oRdxt+4N8MGwgXFeD`uXLkGu7afqAVS*7I*8YlE< z^}3lxo{wEqiP=4So^0CYX|BUDYZ~Gw(zH%EeSLcJvj+~)Gn&d;!7kHsERB|^9IQNN z!_);<6y-DQ7q8TCEcDH=M?I{XU>43+^?=nEzMxgMzOXFOoua}Tk65}3%#7wTYb$1ANf6DOX zB@*louE0@kAt$KzPaxp4xaI5GZJw@^1TU{Ylg!7K<%K=wI;FE`ti=w;w8gG}Bm}#) zo~n2nw-+4N<#{Jn^fJyWZN=+=6V=M0mo8H+I4_%ga9$?NDozu8leStX5$Aok>)?^0 z&;^2T)OFGEb#7Bqd+Lb&!=kysEffJ-k*D~kL$b1Jll||B*G+|%q?815zq;g~#FTV& zQIY)M$pyRz&9UP>gI9_RW8CkbPnn^6JzNsJ2NrGkZUHiK) zim$CBe~6hh5*I}0raB8#oXxX;aZ>Q;bHiToi6ocl7vYIQ8a8vdN z?deIFSiWG@uS^sI<;?j@lbdv^uR z%b`!}@0xN5Gs5o$rBdSxj==XO;+_;FV;r|MzH<0Jb*cQhmRM!yjw1aJG1Yt_o`y#H z^};+IR3o^AH58e377QZ#{JXpC8ogE{!uYA-Lsw6`k{GXYr6>i_3gh%w2&qYxM()?) zK4SX@B!2eXY@xBPg#9z2Dpt~RQ`55J<@VGGUDf0FONNXP?^=d6JCA?Myd@A`ls)q# z_)KZUeo_ho_s4ur`%^`8z_&?=KoS6TXzIgu#n z)z0~(`!Hj&1*x~B2+?QistW0*6QnWq$4`WGDvEwu#ig5-E4X1iJ6ZgG&h8th>oe}D zr#CG-`&UdXo(SR+9v?mSSxEJ0!}MP9Ve%Pkni2h5op}7gED_&xFW*ZIUdZLIS6O`V zq<-uwmSfZVp+JN3R7F?Z*y7X+FCO_kdNE%wIz3%O-=}Ka@m+WH>2tz&Z#YGQUkp9{ zzH9N(-Nl-)AZH@NDWbKyrpy)IV~tFn#ue}U@uoERDb?=W@H9;IwbtfMdnWC9fOb;t zTG&3;`KFT(4=tU;jVx_GR?_mabna+Lix^8+X2K)|i^ZV@;%_+Q3$a=mWW|CBV~aW; zU0679BlSwl!`V_2vorLiIs$m(ZWeb-q#B%&?jbp8@qWMNg$26rZ@U#b#sybmnrA+Z zC}(k|T^6sg44u~Th^f{YeH2T!--zXi4lQ+p-K|5!bW2(McLYd1-Wwcs$(fYYzt?7R zGxOGy9y5Oe%HaTPn1#|lCV6WmN0aJm0nP8!s)pn)*^}RcwN7ab%8103;?Rfxp(>f@P7bhU@5-|v^hcQ}$nH`DV07RhcNzw6|~W7WzFr0uJB6Wgg4@lDO< z-Wd+J8ofI4QtsrmJ6{{S)q8>3_M%Qt+=tv+UnWGYeq2nQqPkIZfMrFG3o(--Dr5cR zDgUn2qT@OiMP0G9haMp2V6!SJgx4-F2;K9q8VYnwExKcA9@RC3Z+}Ab>c=^=%&yd^ zDz<1HSA)+>4?b8r3tFYMhYT4#IjmZmcbkcCKPT8BVB@y4Ea&gW|tn|7-`jAbgO zu5005*(3a(N%{8c{;88s*3Wtst$_LHF@t$A+A1-I1K%GV{;Kw2mUs58t8r77qb8mp zGwzax>f%huWs~;_bTdsf@oE+Ew{mh3?nlIQTxRqNSaWsr6yr5@l1T5p^LIMY^0l>= zIcBW4`rzRo1f2`Ec^yGXgS0UDqM;tvdGk5#qt)&Vt@Vg!g%zfu7TRn&>Yk!H{I5p* zaQmJa^PHAo$kTJ}R=bPu`pgY~{$qc>YJ<+Fi5B9#p#&T3?i|+73i~F_XBzc{?r9d3 zs7lIb=f@Jw9xJq>S84K&4V7WEXti=F>F>=iS5nq06){&Oo_Qal!%=L}-^6x;lytc2hJsl7gTV~>jLCQTZmHOueh|nWxmRDJ@sJ*UKD;v_^swHkIDR_P zC|R~fo#eZ5>il_dmBf#svp5UiKQ*iK@hL}SpQy$U)l->iQO$gEGyIf}U!V5TBii_3 z6qO(Q@$263&qw-S4RsdDY42I)9>4awg|dCu;}ZV>gEK1o z@8(e7ASQ#GC5PT=j=p%0vSH9Iujy;0YH7l5>zq@MKUApR{_06K>c|0eC+Rq5Vyl`N z7nlerpJ8QA zqW)=(+F;s{Q|vu4SN08Dj-KzyZF&q|Na36`*_GdEI^eb34F-0Su|OfOk3%NN_8B^Rw&$tQbLaEGb^-=u<1!b(;t$j^XQ&pTfB2Jz~A z@2=oOp-OKE?+)xzA2uuy&kr|R7r@Ss<9#^k-SNuygGP#OlWj)!8W)=mQ`A?IkEimM z^Og=p=Gn{Xlx4DZ#OMp2lVb=EG8=t-uI+7jsQd$isnow8YdLoE?Sv8+=Ilb@vlxOdarQ)_c@mqCSeQTfKgF=U`%HP(yMs<04e-T~% zTzB%vZfAnDaG5x`_Wh2=i^s%bV;bi~M8;uJK^ONE!m~6~C$2=Db$-`Da&UEpRIXNa zf=X;vm;KxBA5ocO+3)1p$#$P%|3;yM@O;u?h^JGuqNdMjN_dUL*8Kfj=6hiQjq}_` z3HGb(SEFb?KR~G;0yA$cI|XLGaFbrW(#}R~c1d38!Z~qQ^KDBW2_SGW$qamDTygr%(CzytMQNK zm^pWrGW$c?8)5#7edDKjj#awG zISkgwN_m`fA+&k*P#c@f9{XXDVq{+4*OQGCyLCyaU4&HLF*;z&4GP&#_;{NZh`D}_ zyvi{;MWn!}eTkFUwkgVJfjD<@@>pZ`L&0kk+L!Nqxyw-_OXcxZ`hZQm%8Ljl-X9BQ zb)6E)C*XClJ<|P=k#4nvO3!0Og`ek`)yebB3~N}-$$sW#?9w0{GfrL9V@*;@P{@51 z7!u~jLH;U@fh)|TXm8+`a{{st48CfAnq%$!xaVUB#qE@Xdz%RNwe_{0Q|7G2T5G&L zI{sw@*L5X2gP7>uA+a3p&M1|YcdUEs1fFNI)egyA{f@mps4tUq$dbML+6e~VZqm^Y z@$)1BisTmRTmP z4p`jKWPZRWIa_km>SbQJTIi?veWYi5s$4qfK8h<(Jy8>%GG-PXYf_%IXMPkmm+MK( z+IwMO304^9!83;||51vb|AYyi!k*F&jW@QJZ%G`UrPa6~ec)pF$L{-ASREt@PUmJS z)Jd;fe51Z;*5`zOF=%oL?>n0cyRyZE4WYr2#j(I8NxL(aSCdMo-}aV09bWxw^E8wE z-Qh^Ed#V$I-BW7jwqWP(#?C1?g8tXfpSd9Znp)WK8H(ncAwB))?%Gu?Mn{m3ZqSg(<*j#3M!Iu$J_D3C`>2idzKaXQo z3Uk&WqrI&lWRh=bZ1{#Mc`VjjpjY=?@b}lO5v~>2hwq7Z2zarJvs%o{zdS25lDi+w z$LD6uPQ=%b{17uvFA#L|b^Yi`Pr{nPeYF~IzKbAclKbj-e+bp{ zu{S@`I()OIU}=V?-c=;<$6qF%^{+4B1LidX)8i+%nHq{^)#Cc=_A652RmP;Ml#p(if2>ZhCx|GqRP>dwjh4->KZK%%pMX>9Ob&SZBi5ti9iI zd@xmj+$*Qk!_P6S%IVW}nHc*%ZZ|tM50wcyiRa>c3Z?ygDUPMq%TLPY^y!y|OiQPH z_H^CV4Whc#f8-ED%?qo&wCST$f8%4k2FceNRzKTcq_p(wIV6#{BG(l<*_${Z3<2ESF^w zdy_K}W`60({gW4`-xI+_@NPZCX*i^O=)~vv=Z%Fx;}XbkbT}rKuk!N~GGYb%;w=SQ74QenruG|Ec7wU;lbGYh@~FB?c52 zabQRMb`GeHuyI1zxOr)~!`a_Qyj{4iWm$=BQ=%#5_Gsw`|bSvI!K z9+yTO^?mwUM%Eq!&!0DcJ^FbdSL5VyMpNe=s*_y41vDY=hiS%3>!dGqRW+84 zCnYL$`OKyW&xt(!n6^6g_UxrSTz*aLINW_vsmxZ+^arXwez@v+GE|s#cIY7fc)90E z_>ELj*AspTyq*kxv3ROlbyUo`*RI};O&`!6GBW)<_|2TH2lif>dTjyzLV3kzETwQ^ zI__#}a%FpaQ*L|vqvAqba>LS@GDB%hKU;9Twdq#SD~9n*g0iO18J0=#_pD^$>i!i z-Fbs8CcJBY_PHUQfnfbTDmHsI*m%pg+(QAk8q!Al%LK?1^!!4dyZYT~qz(La?zvG* zyYNQ~R9qW(VPiW*Z*}?(KS^;yrmpe+E*rWIx<#w3l@ymFgz}Ld{bFaVjMAm1E;^DS*M@=pAh3!wNj}7-uRi$Xs5+QYhiKKow zeAhm0u6?+*7%lk0(`#>dYN?scG?tiRm%IxjVEd z4(^*CX%(V!UZkn?TT|{GV{AI37fLHR&&QIZB5l0z-n^z@=na|Ld8dUEa9DFO65&Yo zoa6Orf>a!(F{{{erfb&kjfWStOfwitJ~%9vH~Cbs5gcVT7Ko(SbwlxGMN!@pcYz7g zrSmOIbE>=E_SC996CY2z*lM)0PwpMJ&X33S6tBV+Vfcq1(^4}MDSdAy={~!6`kk`n z2Z|O`3Rk_okB+kUBqtobU)z#Z;Xa2WzVc!;;B;*%`^B3EuVKN@U-6098j2|=5+sdy zI=4vQrg?Ewq`2f}Y#aIL&6AXCc&;l+!*Xk`5kYCj075_C4QM8NUOqc$*NKb zc~1Mi(DMLxalH5&FJ9}fG|*L%uQpbLsvx>WKyaK6_4}ar{D4KB-`-N`Y;%x1U`Oj9jqXgkJ9?r^|!yniKKlhw))S z$JWnjb>C6=)NtMQt}*q1d&Gctrm0SO<>(2HJF=w(w-4&*y)H9!C(K-6^{7#OYBYJh z?`#S#s$&`Hrrf zRG{qT__k(8WgGAokE5oz!q8$qkB?o6pW2;_&xHITnZ|4P-gsKQ@~X2;35+Z*h4;_i zyqwiizAE?K7`A*bL`Wj`rP3a1=`-@Ore~O6=hL$maC6b}J)bHuN>`S(dsw}PvE|{R zXupwt?JEAYLgF)R%aYZp2ll}zT)Ytq;V1mU=5Oate^(7q>urCW?bZE7Wd4}ffk$8b znJ&wCQXLYO#x@+Myo%i%JgAf9OkfcHaMzcW`rF*&dvJ$+88VK!8#vLvvmty(l95Pu zkJ2WNaIe@DZ`v}2N2O%Bn~`#?8~!LG_v~?;yVwJ-{0P3XcO2^|>EL;Ne^jQs$>{ji zFzI)fs7!|&4+MvNdB>N>R!A#XqUa?VnhO^``N^1}|1ci+OCO7lta_=drnL2PZtGF0 z#OtchQ}j+qTkuHlk$ify*z$uvj`g}gbG5+a+A7mI>@?!X4+NH6&W5@2adNiWWlS04 zbn4{CJw5S#Y0uZ%Obd5h&DxMI<>C^)R*F$RCGCJ%tPRF`Okp+pr)o_eZXG@eW zEhu2Q=bgxS=c$d|weOS?JvIx~e$Equ(6F>LYxpJus=jVe^@(m1mO73$F79xt-;Xl? z=des_>_y~Dl+8CgDoHEx4pTF~PI05ZS4*OUzRhCh^#}Z#cHS@v4~nl9J}L_GYc+Bo=F~O{U= zmQPuf>?E4_SMzgQI~)St$g)dwCo4l4NIURt=Ur>tqykwUIJk3U9>!}KdW~&|A4<1d z(Q575mkVRw4w{L*cTP8uP8^ZSP#?{C;eM};l2Li$gIsD2&X{+^65 z3bB`sJhbZaWT{Y7)Ojth;iY@)3GMx1x!5d1_A%7zJj9k1mBA7tpH9_wzKXZ8T&)Yw zJP{?|!e>RH`u+U%Q;gg)w?)RO-BToWTL|*Go)8QsL?-J$b2*@M^q~e^fBh-1Wvld2 z;p-zj0!JJi`y7lY3PW!0Wgv3$bi<};Kk!DzGsy)uDAH9(iu;&C*5N4u$3!nfD;W-n zC|%_<`^gg*jVZf5U$I@KW}c49k+Zux$>(FJ7W+l$gYScEsr$W5;Uc2a<}`RW6nM1O z)W@{E_gbHbmR!IqP@=ydZcuE^lJSM;lenB(;K$ac6@t7tjwf+qXGH7gt;28hveTSd z3;k4e_8iCXd2*Y4nbudP2I0!FkFlrsx6i-eYbMi$lQ&XaP6Hez}xU> zQIV%9{-7QNnGS^p{|N7ZtUSFTz<;83q51!FL@8g=!6!=3GI9MRhyJO}aXgEW&N8c-*+=7wg$YiMz-9qf6~ zfUA?n9}@KaKo_3gX(va=Tt0T9a5} z0*_Hm0!>8unBXGa$^nkBMjtvX5Pf{c1lZ|39AOVbUjlGKU`4rDC1!VU94w_QewiMDz=o0sg7nF)#C4 z&_E;L7Bc1_HM7AKq z09g{*dBYH1s!o=0O?P`Zy^N!!x)Uf@|Neum^x z^eXQ5ZZ`kYkd>18G#t=~05G^gp6S^9Wch}GvDuCrvbDYUv`3rgg2WUKbj-WS0Dflq zPJVo&h6a zOrQ)(wyg;`UHv}~1GB#Z*@X|HFm5m}i7~-O%x}#=Es=Da9GK)SV333`n22M-;N*B7 z?kVqXZ%?lYv$TQPOF20>!$6sj$|eE0f@EdD$997}{--EM`dB}p-(I5Md|Qk2hDZJ8 zRUpbDNX35&MkM@mRSe+e|12(SjsMUyf+QK>9|64u@GM79Y`yWKw5zYWwYZ>$+q^gkm|B)CxObYk^a19YEvE{ z&Us82Y#ePI+#NQ0A0UvF4Z;Fdfl*MzN&F5J;~tfvolWljW-(h5?7FlcT1Sxx1^|_WcXzwen^nV3|@7olrN}6SEU0jC?L(#8-zN z_}o+QJ@Z>Aq+dHHVnpVq2?xVGSJ2DA%ZewhuQjVEZ5c z-rD7KKUuMcf^MuEtW!a|vX@Di;wvMULr^9DrT}dd8xWqpfCQRtDl#!3(1dwOA)H)Y zk^YRl6Ax8z%HwnIUj#Y|0ak(1c;#Y3gQ2H!?Fq>81=37_+zHLl)pszakw(UhgN-B1 z@z=H4e;fS=A8k5vRGSKBI-!vhTZl0WM$X)uYJQ{^sEicI1eJ%m5(6eJ(_Jv+1Nq6o`ujc4PZd`YbjX)xfBQgIY?JJk{72I#*Oc)T2>4PRgUe`v$j zR!B^rPQDGuromSlLV9TebJ7iyt2u7!7;V4ibAM<8>C@7{?y?ZZph?UbAqn(<%)Ygh z`zYIP*MMxD4Ad(L;k28>fb-`D10)NewXugGH!`chVXp276vNO`-^T_=0G9>m?VzR% zeuXjL?`(=8Dd&d-jw~SDckmU12#Na!V^R#cxG3B^ksDKpLCipnh`aRfTwp8=makD& zD$}LT)5XAX(m-#8ynuc4ljWQL7Bwf>&>`#O=q6|H<$O*P4zomsPV)78mnuM42k6ia zJ^vG0^i8M#bLacU^+Q`bqHRnAehSbV6L1b_fug>OmPG+B$-<6|PhBU*l9 z5zyz#gU*iwuxx&^d{3@#!?3Z+gvJb^MsIcz0@oD>28V`9+3sB^(>-vd4a`55RtKe( z0;oxmRXjcS){1&|OX!s-F!vtZzfDey{jXeGGp$Wr8_)zS@)BR>sJK*&;RoJ?YpRCl8j5N&;*X#4d^%_bS$)((`{{I z*|(XmWPvC*0Y0=RYhgr*y|L@-&rIE)E77)}el&b%tab|YVkRI6p_X?5Z?m8%jQ--= zS`zoXUCRnUl2m}M2+`7e@CGtw$Qv7)UFjulEN#FdATk+lFHunc5BfZif(-$;G=w*U zZ->0HP6!Ve!V+{_=%;Yg%^8q$IoKe2psBb}U}ub5Yx2<9@4K!6NuGc&6#x7gO!3hT z#f&nG_Ok;k6@$SKwBywWH2`LoQU)W!ty|AZAOA1_*n<(+1KN@F%3+GG1qb`w!G6`P z8CrTH2q-{)@dLRLn(gl>?37_UTSxIy4wV8sR04@kL%i##>Q1?S-C(%2P|xzpJ!$~~ z1t2;FA!1Y8G133LpYivo#K!7=_+DTdbKn5{5FQEjo${bq<#Md9Jn9eN=MMg@vqZ3# zi_X8K;Z`uPLii`Op?8Gu?tSI4ptC6kWYFa4ZupNxXl*1vb93ku;8O+3nhm16b6}Aa ztzes%R&H&4j%Qzk>On@1IEIDA3IWbGLk}zimY?jwW}>Ze<%KnrfF`gaL8GDBzQ6%J z?q(H?stOVhTj)m4m(>7Qf+A};VTjDLb(@b;3LGS&025fiqEKBJxL}67H7-KnX4e|e>>FKyXet>FXw>* z2h8RoY&^F%Gp(x&t2QBcF^}Qz5!0EbCtrhZ>UQR<7^l+bms-eT3rb&#DH%9AQW2=>2=E?ycu>!u( z>ce^-J+_*K8w_kUMect2+2f(=nbvu87VUsN1wj_p+;l$h6D+sC+9}263i2;iY85>J8l$f+N;Ud`d4`Js zkp%);wtF|)RH^=R5AfD)HDJkha|Upi!53OFc^<|H7F9lA+q?Kw0a#fNL>SbOxWT+X zy40?|xxEzZ#)I3_YkQ$8p5_!a(w>0DTA9Ot_a2>OJE7QGGs8N=d$vxKb2QtagVkSD zYB5>jEY<*OVFB?A?Xi=Nqes8p=HRRtSThGX2+S}cmpM23N-40Q0gmLM7)~#k z9l!e?$SDOlp~H$&5bEecV}nx?=4xZHNw_(b*;=Cy#6w-MK)v4pzR<~AEIkaEFeAC7 zdK&u}h(0!;Mrfq#=>G!=T60A{r~Yn4R^q`T`l(DU9KoEun=;kT?!K2 zp(g)C^Lv1R!X=mu^|Qi(kvKpB2%Td)X1)VLH1)uNH(bY#0Fm@S@__D(`)08t1~kQB z0e7A9eqf!CfCuUcw&!-vfj)?i9KQoYF4CERoDU7$r?&sjrH(+JCQkL@Qw4%nYQyj?x`7f7GSgQ zU}OW$@|JEpX9HsXJn6M{P9mx3s+R)AMlz^2pcAmKJ<*eGoPUw9vI0Zl-%C7b#%sZL zyvB7vhm4@)lZ5CnF!UdZB;9T7Q7%y5%2~@d1)iY;2KZ3EY%pDi&O1;FhBgd=_yS@JVfseg0G-GM*cq8LDM7TDh*h^?(S>H2zh8U?*TexYJMmdnUlODGIX0H zveRn$e1X-@fXG&WSWWHL4oGAyZIDaae{83=c@`OkY#NG|?SSqUu$>BoRxoSlw40{< z*@|tbFSfsrJOS)S4yM(hBRl)-9aC>D9d@2c+X_(XJx~@1L8O}~*a-!C|9{ne{H_N` zkzO=^ujrgC+5yY&+=VL6WKOEY8v|*CfGb$Yg>bV0Bd_1a+2~ld89RHrO{{@B8tVj*3F_JI;If!)GyX=kwvF1t2vapcWsAiL zD4;os<_Sg=8|B(IBwjNgu7KGatN~yjX@~;5x-cNoI_HFNL!QZS2ir(2H**8pt(3mC zSbQEpB(?ukqf$MXFd}Ee-Czi`{iA@Hk_au}zy%@84w35MAX<)}r>dpl&ZxF!^HC0K zi-V!TJJP==WxPf)LEhL40k@Q~v_>(~*JsaJatCF^b+CL0wfg?q9TFhbv~|+tu*w!y z1p1$$`g;@ecRP$P-7~=n~wo$Gm2pf->mw>J+70iu6OW^aM z=tq+`{wU~wNlA z9ODMdINZAxgdsi{mTcX2LW1w!2+*mdfW!%Hdx;qa*yQ&5M)~Hrlpcp=o;Gp2-4j6!Ifa|uv=4KSr#RqO6;@*LL^8 z1hxbu_DsOtf7>SRp9+M?^z#T0iaKL&CoVz{B)?-I%|h+P5`Zz?ue}eyyFFB$#pzF4 z+GRktA>iIJ5ZPXYV@|el_zzPTu`|+JIS%Bj1lbolqF;{QAtgAdjl3rgIfC0((s-|2 z;~`+dFCaC`K_tBhwh>@x#h<5iFekN;al3N}sMiM&Lo1ico0yYs$|!Rl>{r@49^Mi$ z-1PZaE=o0H{HcG0WL9nqliL8*&4t6*rH0&dQ z4EMno+7}Jq-i83|8goF4ed}=4IyiiW)d>WErt=56+hA)sgE8)}<9Aydp+i&8?jcBg zbD*<>c62Z9Yy-Y|%cZ2v#%-5dz{`+shV z-Hb*}ZE(Y_B&ZWD&Y|{kt&#WD3qVTX5`V4WX$=26t1=vBg__gyPVIwbzzKo~O9LWl z`slxNBKhc%|B~9~pvjFclKYo_DH>{6Y%n*9HZagA8nw80ADv6<2S!Z=X+#ks?$pGN z__V+bAK2Znv1JCu;+v%IEAT(UT1Ob%c$w5pRkb`05-P2pE zKW&fN5e}e!GEhB1H6%}hDFf<0w#-js-Uql4K9B)A)NTYvsnF_w^Lp#e%}c1w5bAyR zSUreed5|)pV~kj;9g=8iYa&(k^VSFC9e1v%)_grOKMh<4q3!dZWwvtae5-yB~pe zL#=twY8%+!4c~_1bCb?V5kP_5rwX0&iaUpnLLCNf9^PiZ%aeT{@dyC+5csk~v_fr* z4qH!25^Uy0-Z+P9Ky;HO$>IX2`4njX*0(FT0Ts2I17vj9q7(Zgzk)46bTExurzS7`wjN@!UBZt;cCZJ^P?UMA71 z>^%(D&_RlV`ZHt7Hn8XvLG)lXd>lC15Ksm*`HA002fOj+jGT({HeNB=mThhhGSeu? zOwcio;{#0Ku=#)CK_Sjq%oL9lpy~P#&FjT~p+O;ys!DqTm|4aO03HXG#vdHV+Ah-n>(nR) z2q|2tL7afb;6Jfb`uSgI__y}yDrYrcXF%irADUCE|3V|MHBIi9dJbzqlkp##UF-is zBe*pUwm(*-4WJ4B4^15w_CMH0Xloh;YwNosAaHJiL;!Vx%X|NYMtEzQ_`?H>$a{Z8 z0F?}+Kq$if7n+UY>{mKPu^eejE;1Jcq*?{@vCzJfeg8jDZY|fV*zS9ekg5au8hQX~ z9}!9td$5}Uw(%_7uWiW4?G2J%=r{TFSqxh$f$WAHoI(vnM~(rLGThM`-28{UdsfW~ z&8fJ^f$LA?08Jw3N1+q(P1HN1fmaOAAt4 zLG%F@ZBUg$jdPs|6P67#ZEk2pyC5oAdwl#dV5$f5K!-7nVBQ5qBK_kANJYUA=r6>P z5+-E30J;N){~Q{L=f;>~Q)OFQCGnQQ@)?kFR6utC?L;k4V?y!kvXEa+hT8f!ZRbB^ z0w3E2#y8M;mTzJ^=RxDlKg!q0o&dA({HK~)k=`j0I(PoAl3tFS);0i{0J=g(t%#Dx z@#kxf8;6&FZN~u#4do>+dgb?Qke97=0v~{;GYXaMIAme2ZeUGg`<=xL&bw;90zm>n zry~tX)598=kZj8FKl=#5u0>fJkaiGT`&^g439%axs|G|4bSg|hXFJA?r2z2M%EnLR z3d%NI>JKuAkhf4i2Yv^QQXE5!xPEa+SfUh1-z?q|Y!|_51|bA>?Ul1Tq4=pXG(wvNL9ccpaadrmfI3--2V#SSWre`7G-z40_1KpUf|I(5J#Z2*$!SsY9j+67ts*( zzdQnic|+FX;~!oF>&X3Q{dC!GCzMb)hd~7f?QYwAw&Mh5M7~|J zDLk4C6=Pxa%?y}_7ppa?0_V~nLTAT9WTStsZe?FuHa+b8d)voh@*K>w2;GQtvN6TKwVPH-k$y)G5Rhla zp*iho;&wz@7U1o_pBW9++=|@<_6Ou@-c^t`q40*_y)P6++OJ;vf8kM#+2z$9M}tI# z#SD5E=&C_s>JH$yR*iFE=i_Y9ZXHm{Lbr@tfl6wdw!^I*fDwMDE6__KpQS?Xr(#d~SR)SJblkFC z$#boTdDXZx5c_-pQu}C(Cv%!~j2FNT+5c8>6+Zt8K0lPWry*N)eI7$v+)f9IMx|X> zM2%+?u@!vpYvomk^I<-vPzoxq(rw1DSngsIcEhAJ00(GY*69~| z!JR#idlTw5KR>7)DAt)f%wC}M{BX&N3nP;mr?3~BiGYSeyH3;%P$HBqfLDvX?ovg!A<)T=IO#-^6*zyX(+cEGVCc`Y z0$B#R3Q~+9aKSj8=;Sb?Es5!ml4Ej|KYFqqbY?VyLh9g8U(k-`eYB(o7EsdS&0Qd^ zA;74Ew_j5PW9^@W)(>G~QJkw#sZ4Q8@_!4@f@5gr?sK*GqMyGK=W0LCo%vU^;klV? z6p|ZCKp!_$-l^f+lJyF2OjWwGJX-D7@FYmU%Rirt#F_>gpa#1|X(Z&ZQQSG=6l0rH zUfCitCzUJ#Nj3y3R3c{@>cW!${IQ!+2T#E5J=4+G^?lz=!{^6&Y&NSaEK2x`oBwRu zxb$K#Xx7g-)D9v>)4YRTpuyRw>|n`5fsb~ZUCDMvy?9@fSzum4b6-KW^aBn}PtAi1)`x*R3vq_3LS@jJ4l9bH5TKlaGSGCjrMF(JWY0LE zHG;fel)Apybpt3)luc3}_orDqQi9Fqw&t2Wt~@JOIodu@G|mOO(p&rPSt+9op&_K@ z0$!guvPUcI_^g+QnP_9zOhbTTW$V{OXl*7uBR?>wfZ-z(a@29KSW6cm?4%ozt zV2P>n2J30Odwn2OcQ%Zco|jL2tO1SPYG<)jOqkFvf^%Qce}6!mmp~Q{$38b9gHTeT z%D!447Ay+cpJ>%SVvSSoA7n;w?*cB3=emAs3{$zQNcTpWQ}bYniC`o>+kL*v7#62R z&f<;9)_owX84Fg4jx74z7?@&8px@DHcN~OQU4mmIRgj%1Cp02Ag}t?`?d6v+P{q_4 zG*P|3^o?#HsiC|gSsyQYU{(KnOr}<$zN*pTfN?Z=8@t)^$o3_O7+8~a>aspYX9iUk zI)xYWh0I&J;W^pNCeb{O*Wk=6iYs z7F*DOdwqT5KQHP4Vy4ZF_2U8~E;A$B3M^{S)2{2l5n@41UP0uo+#2nk>BYOgfj*+5 z)CX(yO+8rlY@~+SePJAJ7FP zGz;ZI9k@24g58XMgR`HR2&z*T9Z)PeTa#q==49o4M`gFo7mxE=MgKa~t)^uySin;`TXls6h*%~ONHqfq*5m*(t zgp9xT97kLb0arpLIdOmyxSGznO3rx4;m@|P&iUWskU-h`3DWJFjjrbKIM6d;f;etl zi13uIUN@gey!;?w?I^jTXOlj$MqtG}kYJR9teAza+_>_Oo`dae!QHdiX6Y$mZ@eL7 zaUaxlBD-tho<60J?hjC?B%;G3o;HH*-El1rW<&W-*Land!fQ5lT=)SZ!9?WU=oRE3 z)Wxd`J4c+Og4Py7T&=uGh5PU5!lG(60y&n@nV6;z$4em~)#K`=+j6>N_N^(O*$+W| z+-4M2IkL92n^N=^&h#&I@^uVHGK`{eV(SGz&;9OQ5aka7X(0bofl)ZcFm!aIsgBm* zVmT}{o^bM)LgQeyj-FFfX#S6D$S-j5yAjyaGlXN3VQBeHY-D6)&=%|{E8z`d2$gFl zYk`s`YOoMk7%U{XAAV!FdLu9W8;qR>#>NrIYP_*fH^3T5X*^#|L5-V>>zaN3I+|OU z4#M~c65ONMPj%oj8EV9gb?v*B4LbW`=Zqqlj#;`elaP$DuuFD{&+_6NA3nBo62v(W z;*2JEedZa%t0hz(abezXKivT0Sa6j}^DHVOba2(1?5@E>b<}Vx&QuBqsN^1PwxZtJ zRD?a7UeY8j(SYl+I#?E(xi;Jmas$ivy7nq_!y1pn(a~V}>Iwr$?BS<8D?@&q(w`Nr zOa@uhz3p0U5JAiBduST3Yt^)=<6C5B0_bEO=u>~Ct*aT^%2K)N>8NX?O z5>6?#`F#NnZ#Q_p_UWOaYw)VpbO8dR`mySg9z<`BEYfAQ@Ze&tg*&=_YpW zie@rOp*%p${qI4eO`(@RW{Sj<62=g_v`Zu*Bx`n$aB@O9$-r0H~s9FN5Vu+EA zZF-lwB8p;@`nqW&>*Ard%~_wf{IaX|yuxopEE{};78_sPh0@^;H znF-pj_iIKIYj(7em%8)uP&R%UMHxONai6u<4k&dd^2H1hvlQHtxF)?*{lbAhA2}x) zQmzQ8`wHZ}r#bM=-oJy*i@;`T9lg3~#+3Tm3mPj96mq$g@)T8nz5m2qxX>6>;?d_? zlfxAebA(v}LhVbK`my!fNnuUi15u^$Yt&0Sdh3Qz4tjna^WaL>B>aD%=3%0u&VHI; zl#jx#TBQbGhB5}?)4j;2tB}W1*k}IEBq0W1k(4m@TVD3j?`_UUV_(6<==z&SYXVV@ zp8`^@l`~^Pt*=i{$nLS4KdtxzIOEMV}q%y3G){kV$3- zM};KBputfD@{Si3A>Hgbi``ZjhVIfC4vTx3)x&PSgOUZ-*ak)E(~rG_2!YD!PPL$o{$+0 za~8}}M;UI($PoKCa7$aw4fns%6lDAerJ(Hk=v7@HoT8_$VDcNtf(17R8b&7bQ#^x$4Qb6b`TX zEw*ZIs=Dc$1KPF6DQ#&t-_5h{#yW#59S>6`bh&3WR(4Sp-N8?v?PZzO^KkErZC$5sSo`rw6PkO9AgVfQaD##+)~YGcV-j<>jdYZ2Y7>SFpf zeC0|GZj?`=Jc9-oKenp#V&si+&X^I$Sb{3Nm2a?L{+KaHNjc^5R_!i{-wR7DgErF5 zW5AQf@Ra+FL?8I)jdvkaHa{YY$ky6l9~RC`;y%H(GQZth6Bh&LH~fhtc+LTO@!09c z!8ht-8ADyqBPP7w!8d&|K0+UysGjly8+&?ueSZk+5g15kLR7b*iYOlRfC-Cq<3y}5 zRkB~nxcsgV%MJXYxzT*QIa1iy|MpcJ>)57-$94s2)*R&t**|=0|DEMVLxDrjAXk(4 zIA*8AA@*ft3KlZZlg%0OIP8gK|7r>*u~E7-kn|s}2S#25N88jJ{SgS52?Ux!vZbhf zUGm66?=oKQp?)K0Tn<3kfD#Il_pDYBp$`TRq}kPmp=@U2W_W&Dg4TB=A0*m3&XXZ^ zO=71`1o=Fq7NJR(LXZS*hi4R_L~Wam(SRf`h}LTwRM$hez4MmL$Rs=equ$uso|c?x z$&uc(ghbo%P?X&!D701RbSX2a6lasSK?Ie_u`C04@3p-W`&02|2g&KI}1U^VS zE|^>jEv!RhSARbf>h#4e>Qqt0|7yax=V5o=mMXZs@~~c&@=O0^^0qZS0D$Q^J`JVj z7wZ6#|M-;?3$M%uUtWVvqsXP^XJtvtfABv(%!TS8dv0N($Ldk2y}M?&9b`icp2WwI zd8+wYS#r6I|MBEpXHLE)H!V|s)#<4_Zp45k`@s@gXE$Ri|HCNOdclKYS&ql3kJXw? R0e%5~6Te0|zzmpy>HpM5O#%P_ diff --git a/DepFiles/unittest/junit-4.8.2.jar b/DepFiles/unittest/junit-4.8.2.jar deleted file mode 100644 index 5b4bb849af9583fec1ea0a0ccc0d571ef49aa8ba..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 237344 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 Date: Mon, 11 Feb 2013 11:26:04 -0800 Subject: [PATCH 007/196] new codebase --- DepFiles/public/jackson-core-asl-1.9.11.jar | Bin 0 -> 232131 bytes DepFiles/unittest/jersey-client-1.5.jar | Bin 0 -> 128096 bytes DepFiles/unittest/jersey-core-1.5.jar | Bin 0 -> 455665 bytes DepFiles/unittest/jts/jts-1.11.jar | Bin 0 -> 679870 bytes DepFiles/unittest/junit-4.8.2.jar | Bin 0 -> 237344 bytes 5 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 DepFiles/public/jackson-core-asl-1.9.11.jar create mode 100644 DepFiles/unittest/jersey-client-1.5.jar create mode 100644 DepFiles/unittest/jersey-core-1.5.jar create mode 100644 DepFiles/unittest/jts/jts-1.11.jar create mode 100644 DepFiles/unittest/junit-4.8.2.jar diff --git a/DepFiles/public/jackson-core-asl-1.9.11.jar b/DepFiles/public/jackson-core-asl-1.9.11.jar new file mode 100644 index 0000000000000000000000000000000000000000..145fc4892222e773e7807b6cb25c9c7162f7bfac GIT binary patch literal 232131 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%0Di*Io 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�pcp09 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

0?OHVz9VA0VGpf&kpun1 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

Cfl2*u&`AhC0dFC#e}=Y^q;R3iIc7UbE}x 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%0A*VNYhV_u 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

Dv(Pi7&wzl(jR1epxe-cjumX}! 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%0A~;flF(JTeM=F278@94*w}~X(=06tnrx5K= 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

u!2%bTthtgo*~4fNymk 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 z%0A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

Q{o4+=N`ULivzE;8)YdXB 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 R0e%5~6Te0|zzmpy>HpM5O#%P_ literal 0 HcmV?d00001 diff --git a/DepFiles/unittest/junit-4.8.2.jar b/DepFiles/unittest/junit-4.8.2.jar new file mode 100644 index 0000000000000000000000000000000000000000..5b4bb849af9583fec1ea0a0ccc0d571ef49aa8ba GIT binary patch literal 237344 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 Date: Mon, 11 Feb 2013 12:40:34 -0800 Subject: [PATCH 008/196] sync to starteam --- build.xml | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/build.xml b/build.xml index 2a26bae5..43d9a2b9 100644 --- a/build.xml +++ b/build.xml @@ -2,6 +2,7 @@ + @@ -22,7 +23,7 @@ - + @@ -35,8 +36,16 @@ + + + + + + + + - + From a18447a3d1c5a3f70cb4bb3934960d1fea3bb22d Mon Sep 17 00:00:00 2001 From: David Kaiser Date: Mon, 11 Feb 2013 13:04:19 -0800 Subject: [PATCH 009/196] adding jar to gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index bbf89892..96fc06a9 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ bin/ results/ javadoc/ +esri-geometry-api.jar From 3710be087cba4018665d40c2c400d1f48152d120 Mon Sep 17 00:00:00 2001 From: David Kaiser Date: Mon, 11 Feb 2013 15:22:49 -0800 Subject: [PATCH 010/196] newline issue --- .gitignore | 2 -- 1 file changed, 2 deletions(-) diff --git a/.gitignore b/.gitignore index 96fc06a9..12941761 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,2 @@ bin/ results/ -javadoc/ -esri-geometry-api.jar From abcc6337ce582aa37832bda9c29e032b35a22855 Mon Sep 17 00:00:00 2001 From: David Kaiser Date: Thu, 14 Feb 2013 11:20:18 -0800 Subject: [PATCH 011/196] removing unneeded paths from tests --- build.xml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/build.xml b/build.xml index 43d9a2b9..f12d1495 100644 --- a/build.xml +++ b/build.xml @@ -62,10 +62,6 @@ - - - - From 11bef3a2ca36063b141bc7c615d51a5a5a162d3f Mon Sep 17 00:00:00 2001 From: David Kaiser Date: Tue, 26 Feb 2013 13:50:26 -0800 Subject: [PATCH 012/196] Update README.md with correct copyright year (2013). --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 7ed6897c..f8cafd2f 100644 --- a/README.md +++ b/README.md @@ -34,7 +34,7 @@ Find a bug or want to request a new feature? Please let us know by submitting a Anyone and everyone is welcome to contribute. ## Licensing -Copyright 2012 Esri +Copyright 2013 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. From e27bc73827ea284c6142ca017fcefb35ca489116 Mon Sep 17 00:00:00 2001 From: David Kaiser Date: Wed, 27 Feb 2013 12:47:39 -0800 Subject: [PATCH 013/196] naming jar file output with java version --- build.xml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/build.xml b/build.xml index f12d1495..ee148127 100644 --- a/build.xml +++ b/build.xml @@ -7,9 +7,9 @@ + - @@ -30,8 +30,8 @@ - - + + @@ -56,7 +56,7 @@ - + From fc3ea7945d7bef166f6473af6f41d298a5b90b93 Mon Sep 17 00:00:00 2001 From: David Kaiser Date: Wed, 27 Feb 2013 13:10:38 -0800 Subject: [PATCH 014/196] typo in variable name --- build.xml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/build.xml b/build.xml index ee148127..58271ca7 100644 --- a/build.xml +++ b/build.xml @@ -7,7 +7,7 @@ - + @@ -30,8 +30,8 @@ - - + + From 5a737d4dfa2b8f7974d773fbbcd88091922cacf5 Mon Sep 17 00:00:00 2001 From: Sergey Tolstov Date: Thu, 21 Mar 2013 17:14:10 -0700 Subject: [PATCH 015/196] latest build script --- build.xml | 72 ++++++++++++++++++++++++++++--------------------------- 1 file changed, 37 insertions(+), 35 deletions(-) diff --git a/build.xml b/build.xml index 58271ca7..c085c8aa 100644 --- a/build.xml +++ b/build.xml @@ -7,10 +7,11 @@ - + + @@ -18,64 +19,64 @@ - + - + - + - - + - - + + - - - - - + + + - - - - - - - - - - - - - - + + - - - - - + + + + + + + + + + - - - + + + + + + + + + + + + + @@ -83,4 +84,5 @@ + From 3a1dd5ac3fdf01acbe867ccceb84e1b53f25ef0e Mon Sep 17 00:00:00 2001 From: Sergey Tolstov Date: Thu, 21 Mar 2013 17:19:02 -0700 Subject: [PATCH 016/196] updating .gitignore to exclude javadoc and jarfile --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index 12941761..96fc06a9 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,4 @@ bin/ results/ +javadoc/ +esri-geometry-api.jar From ee7645f9dfc74312be2171744c8ec5c34d1c10b8 Mon Sep 17 00:00:00 2001 From: Sergey Tolstov Date: Thu, 21 Mar 2013 17:20:46 -0700 Subject: [PATCH 017/196] open source release 2013-03-21 --- .../core/geometry/AttributeStreamBase.java | 506 ++ .../core/geometry/AttributeStreamOfDbl.java | 810 +++ .../core/geometry/AttributeStreamOfFloat.java | 607 ++ .../core/geometry/AttributeStreamOfInt16.java | 592 ++ .../core/geometry/AttributeStreamOfInt32.java | 714 +++ .../core/geometry/AttributeStreamOfInt64.java | 592 ++ .../core/geometry/AttributeStreamOfInt8.java | 643 +++ src/com/esri/core/geometry/Boundary.java | 179 + src/com/esri/core/geometry/BucketSort.java | 184 + src/com/esri/core/geometry/Bufferer.java | 1594 ++++++ .../esri/core/geometry/ByteBufferCursor.java | 50 + src/com/esri/core/geometry/ClassicSort.java | 35 + src/com/esri/core/geometry/Clipper.java | 1268 +++++ src/com/esri/core/geometry/Clusterer.java | 584 ++ .../esri/core/geometry/ConstructOffset.java | 1005 ++++ src/com/esri/core/geometry/ConvexHull.java | 735 +++ .../esri/core/geometry/CrackAndCluster.java | 118 + src/com/esri/core/geometry/Cracker.java | 541 ++ src/com/esri/core/geometry/Cutter.java | 1428 +++++ src/com/esri/core/geometry/DirtyFlags.java | 64 + src/com/esri/core/geometry/ECoordinate.java | 373 ++ src/com/esri/core/geometry/EditShape.java | 2234 ++++++++ src/com/esri/core/geometry/Envelope.java | 1104 ++++ src/com/esri/core/geometry/Envelope1D.java | 192 + src/com/esri/core/geometry/Envelope2D.java | 1075 ++++ .../geometry/Envelope2DIntersectorImpl.java | 905 +++ src/com/esri/core/geometry/Envelope3D.java | 231 + src/com/esri/core/geometry/GeoDist.java | 467 ++ .../core/geometry/GeoJsonImportFlags.java | 29 + .../esri/core/geometry/GeodeticCurveType.java | 46 + src/com/esri/core/geometry/Geometry.java | 600 ++ .../core/geometry/GeometryAccelerators.java | 58 + .../esri/core/geometry/GeometryCursor.java | 42 + .../core/geometry/GeometryCursorAppend.java | 52 + .../esri/core/geometry/GeometryEngine.java | 796 +++ .../esri/core/geometry/GeometryException.java | 55 + .../core/geometry/GeometrySerializer.java | 116 + .../esri/core/geometry/IndexHashTable.java | 235 + .../esri/core/geometry/IndexMultiDCList.java | 310 + .../esri/core/geometry/IndexMultiList.java | 266 + src/com/esri/core/geometry/InternalUtils.java | 493 ++ src/com/esri/core/geometry/Interop.java | 35 + .../esri/core/geometry/IntervalTreeImpl.java | 1325 +++++ src/com/esri/core/geometry/JSONUtils.java | 53 + src/com/esri/core/geometry/JsonCursor.java | 46 + .../esri/core/geometry/JsonParserCursor.java | 50 + src/com/esri/core/geometry/Line.java | 1002 ++++ src/com/esri/core/geometry/MapGeometry.java | 90 + .../esri/core/geometry/MapGeometryCursor.java | 47 + .../esri/core/geometry/MapOGCStructure.java | 29 + src/com/esri/core/geometry/MathUtils.java | 147 + .../core/geometry/MgrsConversionMode.java | 60 + src/com/esri/core/geometry/MultiPath.java | 770 +++ src/com/esri/core/geometry/MultiPathImpl.java | 2608 +++++++++ src/com/esri/core/geometry/MultiPoint.java | 362 ++ .../esri/core/geometry/MultiPointImpl.java | 346 ++ .../core/geometry/MultiVertexGeometry.java | 227 + .../geometry/MultiVertexGeometryImpl.java | 1166 ++++ .../esri/core/geometry/NonSimpleResult.java | 81 + src/com/esri/core/geometry/NumberUtils.java | 141 + src/com/esri/core/geometry/OGCStructure.java | 32 + .../esri/core/geometry/ObjectCacheTable.java | 65 + src/com/esri/core/geometry/Operator.java | 105 + .../esri/core/geometry/OperatorBoundary.java | 60 + .../core/geometry/OperatorBoundaryLocal.java | 43 + .../geometry/OperatorBoundaryLocalCursor.java | 69 + .../esri/core/geometry/OperatorBuffer.java | 65 + .../core/geometry/OperatorBufferCursor.java | 78 + .../core/geometry/OperatorBufferLocal.java | 57 + src/com/esri/core/geometry/OperatorClip.java | 55 + .../core/geometry/OperatorClipCursor.java | 65 + .../esri/core/geometry/OperatorClipLocal.java | 46 + .../esri/core/geometry/OperatorContains.java | 42 + .../core/geometry/OperatorContainsLocal.java | 35 + .../core/geometry/OperatorConvexHull.java | 78 + .../geometry/OperatorConvexHullCursor.java | 161 + .../geometry/OperatorConvexHullLocal.java | 43 + .../esri/core/geometry/OperatorCrosses.java | 43 + .../core/geometry/OperatorCrossesLocal.java | 36 + src/com/esri/core/geometry/OperatorCut.java | 65 + .../esri/core/geometry/OperatorCutCursor.java | 179 + .../esri/core/geometry/OperatorCutLocal.java | 84 + .../geometry/OperatorDensifyByLength.java | 80 + .../OperatorDensifyByLengthCursor.java | 160 + .../OperatorDensifyByLengthLocal.java | 48 + .../core/geometry/OperatorDifference.java | 75 + .../geometry/OperatorDifferenceCursor.java | 66 + .../geometry/OperatorDifferenceLocal.java | 392 ++ .../esri/core/geometry/OperatorDisjoint.java | 44 + .../core/geometry/OperatorDisjointLocal.java | 35 + .../esri/core/geometry/OperatorDistance.java | 50 + .../core/geometry/OperatorDistanceLocal.java | 414 ++ .../esri/core/geometry/OperatorEquals.java | 43 + .../core/geometry/OperatorEqualsLocal.java | 35 + .../geometry/OperatorExportToESRIShape.java | 63 + .../OperatorExportToESRIShapeCursor.java | 902 +++ .../OperatorExportToESRIShapeLocal.java | 58 + .../core/geometry/OperatorExportToJson.java | 57 + .../geometry/OperatorExportToJsonCursor.java | 432 ++ .../geometry/OperatorExportToJsonLocal.java | 41 + .../core/geometry/OperatorExportToWkb.java | 64 + .../geometry/OperatorExportToWkbLocal.java | 1249 +++++ .../core/geometry/OperatorExportToWkt.java | 42 + .../geometry/OperatorExportToWktLocal.java | 887 +++ .../esri/core/geometry/OperatorFactory.java | 43 + .../core/geometry/OperatorFactoryLocal.java | 252 + .../core/geometry/OperatorGeneralize.java | 60 + .../geometry/OperatorGeneralizeCursor.java | 171 + .../geometry/OperatorGeneralizeLocal.java | 47 + .../core/geometry/OperatorGeodeticArea.java | 77 + .../geometry/OperatorGeodeticAreaLocal.java | 40 + .../core/geometry/OperatorGeodeticLength.java | 79 + .../geometry/OperatorGeodeticLengthLocal.java | 40 + .../geometry/OperatorImportFromESRIShape.java | 63 + .../OperatorImportFromESRIShapeCursor.java | 1012 ++++ .../OperatorImportFromESRIShapeLocal.java | 52 + .../geometry/OperatorImportFromGeoJson.java | 61 + .../OperatorImportFromGeoJsonLocal.java | 593 ++ .../core/geometry/OperatorImportFromJson.java | 60 + .../OperatorImportFromJsonCursor.java | 559 ++ .../geometry/OperatorImportFromJsonLocal.java | 44 + .../core/geometry/OperatorImportFromWkb.java | 65 + .../geometry/OperatorImportFromWkbLocal.java | 1045 ++++ .../core/geometry/OperatorImportFromWkt.java | 58 + .../geometry/OperatorImportFromWktLocal.java | 647 +++ ...eratorImportMapGeometryFromJsonParser.java | 51 + ...ImportMapGeometryFromJsonParserCursor.java | 513 ++ ...rImportMapGeometryFromJsonParserLocal.java | 47 + .../OperatorInternalRelationUtils.java | 733 +++ .../core/geometry/OperatorIntersection.java | 98 + .../geometry/OperatorIntersectionCursor.java | 806 +++ .../geometry/OperatorIntersectionLocal.java | 82 + .../core/geometry/OperatorIntersects.java | 38 + .../geometry/OperatorIntersectsLocal.java | 38 + .../esri/core/geometry/OperatorOffset.java | 124 + .../core/geometry/OperatorOffsetCursor.java | 73 + .../core/geometry/OperatorOffsetLocal.java | 48 + .../esri/core/geometry/OperatorOverlaps.java | 39 + .../core/geometry/OperatorOverlapsLocal.java | 33 + .../esri/core/geometry/OperatorProject.java | 81 + .../core/geometry/OperatorProjectLocal.java | 52 + .../core/geometry/OperatorProximity2D.java | 77 + .../geometry/OperatorProximity2DLocal.java | 322 ++ .../esri/core/geometry/OperatorRelate.java | 49 + .../core/geometry/OperatorRelateLocal.java | 36 + .../core/geometry/OperatorSimpleRelation.java | 62 + .../esri/core/geometry/OperatorSimplify.java | 72 + .../core/geometry/OperatorSimplifyCursor.java | 81 + .../geometry/OperatorSimplifyCursorOGC.java | 76 + .../core/geometry/OperatorSimplifyLocal.java | 57 + .../geometry/OperatorSimplifyLocalHelper.java | 2225 ++++++++ .../geometry/OperatorSimplifyLocalOGC.java | 56 + .../core/geometry/OperatorSimplifyOGC.java | 69 + .../geometry/OperatorSymmetricDifference.java | 65 + .../OperatorSymmetricDifferenceCursor.java | 65 + .../OperatorSymmetricDifferenceLocal.java | 168 + .../esri/core/geometry/OperatorTouches.java | 44 + .../core/geometry/OperatorTouchesLocal.java | 35 + src/com/esri/core/geometry/OperatorUnion.java | 61 + .../core/geometry/OperatorUnionCursor.java | 360 ++ .../core/geometry/OperatorUnionLocal.java | 48 + .../esri/core/geometry/OperatorWithin.java | 45 + .../core/geometry/OperatorWithinLocal.java | 36 + src/com/esri/core/geometry/PathFlags.java | 40 + src/com/esri/core/geometry/PeDouble.java | 37 + .../geometry/PlaneSweepCrackerHelper.java | 1594 ++++++ src/com/esri/core/geometry/Point.java | 602 ++ src/com/esri/core/geometry/Point2D.java | 442 ++ src/com/esri/core/geometry/Point3D.java | 97 + .../core/geometry/PointInPolygonHelper.java | 359 ++ src/com/esri/core/geometry/Polygon.java | 147 + src/com/esri/core/geometry/PolygonUtils.java | 427 ++ src/com/esri/core/geometry/Polyline.java | 109 + src/com/esri/core/geometry/PolylinePath.java | 77 + .../esri/core/geometry/ProgressTracker.java | 37 + .../geometry/ProjectionTransformation.java | 29 + .../esri/core/geometry/Proximity2DResult.java | 104 + .../geometry/Proximity2DResultComparator.java | 40 + src/com/esri/core/geometry/QuadTree.java | 206 + src/com/esri/core/geometry/QuadTreeImpl.java | 888 +++ .../core/geometry/RasterizedGeometry2D.java | 139 + .../geometry/RasterizedGeometry2DImpl.java | 37 + .../core/geometry/RelationalOperations.java | 4993 +++++++++++++++++ .../geometry/RelationalOperationsMatrix.java | 1920 +++++++ .../core/geometry/RingOrientationFixer.java | 529 ++ src/com/esri/core/geometry/Segment.java | 981 ++++ src/com/esri/core/geometry/SegmentBuffer.java | 69 + src/com/esri/core/geometry/SegmentFlags.java | 36 + .../core/geometry/SegmentIntersector.java | 435 ++ .../esri/core/geometry/SegmentIterator.java | 205 + .../core/geometry/SegmentIteratorImpl.java | 456 ++ .../esri/core/geometry/ShapeExportFlags.java | 42 + .../esri/core/geometry/ShapeImportFlags.java | 34 + .../esri/core/geometry/ShapeModifiers.java | 43 + src/com/esri/core/geometry/ShapeType.java | 54 + .../core/geometry/SimpleByteBufferCursor.java | 56 + .../core/geometry/SimpleGeometryCursor.java | 73 + .../esri/core/geometry/SimpleJsonCursor.java | 62 + .../core/geometry/SimpleJsonParserCursor.java | 64 + .../geometry/SimpleMapGeometryCursor.java | 64 + src/com/esri/core/geometry/Simplificator.java | 1031 ++++ .../esri/core/geometry/SpatialReference.java | 177 + .../core/geometry/SpatialReferenceImpl.java | 239 + .../geometry/SpatialReferenceSerializer.java | 63 + .../geometry/StridedIndexTypeCollection.java | 247 + src/com/esri/core/geometry/StringUtils.java | 68 + .../esri/core/geometry/SweepComparator.java | 677 +++ .../core/geometry/SweepMonkierComparator.java | 134 + src/com/esri/core/geometry/TopoGraph.java | 2346 ++++++++ .../core/geometry/TopologicalOperations.java | 2058 +++++++ .../esri/core/geometry/Transformation2D.java | 922 +++ .../esri/core/geometry/Transformation3D.java | 271 + src/com/esri/core/geometry/Treap.java | 964 ++++ .../core/geometry/UserCancelException.java | 32 + .../esri/core/geometry/VertexDescription.java | 393 ++ .../VertexDescriptionDesignerImpl.java | 213 + .../core/geometry/VertexDescriptionHash.java | 109 + src/com/esri/core/geometry/WkbByteOrder.java | 30 + .../esri/core/geometry/WkbExportFlags.java | 38 + .../esri/core/geometry/WkbGeometryType.java | 85 + .../esri/core/geometry/WkbImportFlags.java | 30 + src/com/esri/core/geometry/Wkid.java | 182 + src/com/esri/core/geometry/Wkt.java | 111 + .../esri/core/geometry/WktExportFlags.java | 44 + .../esri/core/geometry/WktImportFlags.java | 29 + src/com/esri/core/geometry/WktParser.java | 705 +++ .../core/geometry/gcs_id_to_tolerance.txt | 671 +++ src/com/esri/core/geometry/gcs_tolerances.txt | 61 + .../geometry/intermediate_to_old_wkid.txt | 0 .../esri/core/geometry/new_to_old_wkid.txt | 483 ++ .../ogc/OGCConcreteGeometryCollection.java | 310 + src/com/esri/core/geometry/ogc/OGCCurve.java | 27 + .../esri/core/geometry/ogc/OGCGeometry.java | 618 ++ .../geometry/ogc/OGCGeometryCollection.java | 14 + .../esri/core/geometry/ogc/OGCLineString.java | 111 + .../esri/core/geometry/ogc/OGCLinearRing.java | 40 + .../esri/core/geometry/ogc/OGCMultiCurve.java | 24 + .../core/geometry/ogc/OGCMultiLineString.java | 73 + .../esri/core/geometry/ogc/OGCMultiPoint.java | 99 + .../core/geometry/ogc/OGCMultiPolygon.java | 87 + .../core/geometry/ogc/OGCMultiSurface.java | 17 + src/com/esri/core/geometry/ogc/OGCPoint.java | 82 + .../esri/core/geometry/ogc/OGCPolygon.java | 106 + .../esri/core/geometry/ogc/OGCSurface.java | 20 + .../core/geometry/pcs_id_to_tolerance.txt | 4549 +++++++++++++++ src/com/esri/core/geometry/pcs_tolerances.txt | 7 + .../com/esri/core/geometry/GeometryUtils.java | 259 + .../geometry/RandomCoordinateGenerator.java | 388 ++ .../com/esri/core/geometry/TestBuffer.java | 357 ++ unittest/com/esri/core/geometry/TestClip.java | 342 ++ .../esri/core/geometry/TestCommonMethods.java | 254 + .../com/esri/core/geometry/TestContains.java | 32 + .../esri/core/geometry/TestConvexHull.java | 888 +++ unittest/com/esri/core/geometry/TestCut.java | 520 ++ .../esri/core/geometry/TestDifference.java | 650 +++ .../com/esri/core/geometry/TestDistance.java | 156 + .../com/esri/core/geometry/TestEditShape.java | 397 ++ .../geometry/TestEnvelope2DIntersector.java | 303 + .../com/esri/core/geometry/TestEquals.java | 168 + .../com/esri/core/geometry/TestFailed.java | 65 + .../esri/core/geometry/TestGeneralize.java | 93 + .../com/esri/core/geometry/TestGeodetic.java | 72 + ...omToJSonExportSRFromWkiOrWkt_CR181369.java | 579 ++ .../esri/core/geometry/TestImportExport.java | 1929 +++++++ .../geometry/TestInterpolateAttributes.java | 197 + .../esri/core/geometry/TestIntersect2.java | 366 ++ .../esri/core/geometry/TestIntersection.java | 919 +++ .../esri/core/geometry/TestIntervalTree.java | 311 + .../esri/core/geometry/TestJSonGeometry.java | 47 + .../TestJSonToGeomFromWkiOrWkt_CR177613.java | 123 + .../esri/core/geometry/TestJsonParser.java | 663 +++ .../com/esri/core/geometry/TestMathUtils.java | 41 + .../esri/core/geometry/TestMultiPoint.java | 338 ++ unittest/com/esri/core/geometry/TestOGC.java | 561 ++ .../com/esri/core/geometry/TestOffset.java | 154 + .../com/esri/core/geometry/TestPoint.java | 114 + .../com/esri/core/geometry/TestPolygon.java | 1408 +++++ .../esri/core/geometry/TestPolygonUtils.java | 132 + .../esri/core/geometry/TestProximity2D.java | 239 + .../com/esri/core/geometry/TestQuadTree.java | 140 + .../com/esri/core/geometry/TestRelation.java | 4700 ++++++++++++++++ .../esri/core/geometry/TestSerialization.java | 294 + .../core/geometry/TestShapePreserving.java | 151 + .../com/esri/core/geometry/TestSimplify.java | 1328 +++++ .../com/esri/core/geometry/TestTouch.java | 546 ++ .../com/esri/core/geometry/TestTreap.java | 81 + .../com/esri/core/geometry/TestUnion.java | 39 + .../esri/core/geometry/TestWKBSupport.java | 101 + .../geometry/TestWkbImportOnPostgresST.java | 48 + unittest/com/esri/core/geometry/TestWkid.java | 22 + .../com/esri/core/geometry/TestWktParser.java | 1087 ++++ unittest/com/esri/core/geometry/Utils.java | 73 + .../esri/core/geometry/savedAngularUnit.txt | Bin 0 -> 277 bytes .../com/esri/core/geometry/savedAreaUnit.txt | Bin 0 -> 272 bytes .../com/esri/core/geometry/savedEnvelope.txt | Bin 0 -> 532 bytes .../esri/core/geometry/savedLinearUnit.txt | Bin 0 -> 259 bytes .../esri/core/geometry/savedMultiPoint.txt | Bin 0 -> 587 bytes .../com/esri/core/geometry/savedPoint.txt | Bin 0 -> 421 bytes .../com/esri/core/geometry/savedPolygon.txt | Bin 0 -> 734 bytes .../com/esri/core/geometry/savedPolyline.txt | Bin 0 -> 719 bytes .../savedProjectionTransformation.txt | Bin 0 -> 1847 bytes .../core/geometry/savedSpatialReference.txt | Bin 0 -> 723 bytes 302 files changed, 105088 insertions(+) create mode 100644 src/com/esri/core/geometry/AttributeStreamBase.java create mode 100644 src/com/esri/core/geometry/AttributeStreamOfDbl.java create mode 100644 src/com/esri/core/geometry/AttributeStreamOfFloat.java create mode 100644 src/com/esri/core/geometry/AttributeStreamOfInt16.java create mode 100644 src/com/esri/core/geometry/AttributeStreamOfInt32.java create mode 100644 src/com/esri/core/geometry/AttributeStreamOfInt64.java create mode 100644 src/com/esri/core/geometry/AttributeStreamOfInt8.java create mode 100644 src/com/esri/core/geometry/Boundary.java create mode 100644 src/com/esri/core/geometry/BucketSort.java create mode 100644 src/com/esri/core/geometry/Bufferer.java create mode 100644 src/com/esri/core/geometry/ByteBufferCursor.java create mode 100644 src/com/esri/core/geometry/ClassicSort.java create mode 100644 src/com/esri/core/geometry/Clipper.java create mode 100644 src/com/esri/core/geometry/Clusterer.java create mode 100644 src/com/esri/core/geometry/ConstructOffset.java create mode 100644 src/com/esri/core/geometry/ConvexHull.java create mode 100644 src/com/esri/core/geometry/CrackAndCluster.java create mode 100644 src/com/esri/core/geometry/Cracker.java create mode 100644 src/com/esri/core/geometry/Cutter.java create mode 100644 src/com/esri/core/geometry/DirtyFlags.java create mode 100644 src/com/esri/core/geometry/ECoordinate.java create mode 100644 src/com/esri/core/geometry/EditShape.java create mode 100644 src/com/esri/core/geometry/Envelope.java create mode 100644 src/com/esri/core/geometry/Envelope1D.java create mode 100644 src/com/esri/core/geometry/Envelope2D.java create mode 100644 src/com/esri/core/geometry/Envelope2DIntersectorImpl.java create mode 100644 src/com/esri/core/geometry/Envelope3D.java create mode 100644 src/com/esri/core/geometry/GeoDist.java create mode 100644 src/com/esri/core/geometry/GeoJsonImportFlags.java create mode 100644 src/com/esri/core/geometry/GeodeticCurveType.java create mode 100644 src/com/esri/core/geometry/Geometry.java create mode 100644 src/com/esri/core/geometry/GeometryAccelerators.java create mode 100644 src/com/esri/core/geometry/GeometryCursor.java create mode 100644 src/com/esri/core/geometry/GeometryCursorAppend.java create mode 100644 src/com/esri/core/geometry/GeometryEngine.java create mode 100644 src/com/esri/core/geometry/GeometryException.java create mode 100644 src/com/esri/core/geometry/GeometrySerializer.java create mode 100644 src/com/esri/core/geometry/IndexHashTable.java create mode 100644 src/com/esri/core/geometry/IndexMultiDCList.java create mode 100644 src/com/esri/core/geometry/IndexMultiList.java create mode 100644 src/com/esri/core/geometry/InternalUtils.java create mode 100644 src/com/esri/core/geometry/Interop.java create mode 100644 src/com/esri/core/geometry/IntervalTreeImpl.java create mode 100644 src/com/esri/core/geometry/JSONUtils.java create mode 100644 src/com/esri/core/geometry/JsonCursor.java create mode 100644 src/com/esri/core/geometry/JsonParserCursor.java create mode 100644 src/com/esri/core/geometry/Line.java create mode 100644 src/com/esri/core/geometry/MapGeometry.java create mode 100644 src/com/esri/core/geometry/MapGeometryCursor.java create mode 100644 src/com/esri/core/geometry/MapOGCStructure.java create mode 100644 src/com/esri/core/geometry/MathUtils.java create mode 100644 src/com/esri/core/geometry/MgrsConversionMode.java create mode 100644 src/com/esri/core/geometry/MultiPath.java create mode 100644 src/com/esri/core/geometry/MultiPathImpl.java create mode 100644 src/com/esri/core/geometry/MultiPoint.java create mode 100644 src/com/esri/core/geometry/MultiPointImpl.java create mode 100644 src/com/esri/core/geometry/MultiVertexGeometry.java create mode 100644 src/com/esri/core/geometry/MultiVertexGeometryImpl.java create mode 100644 src/com/esri/core/geometry/NonSimpleResult.java create mode 100644 src/com/esri/core/geometry/NumberUtils.java create mode 100644 src/com/esri/core/geometry/OGCStructure.java create mode 100644 src/com/esri/core/geometry/ObjectCacheTable.java create mode 100644 src/com/esri/core/geometry/Operator.java create mode 100644 src/com/esri/core/geometry/OperatorBoundary.java create mode 100644 src/com/esri/core/geometry/OperatorBoundaryLocal.java create mode 100644 src/com/esri/core/geometry/OperatorBoundaryLocalCursor.java create mode 100644 src/com/esri/core/geometry/OperatorBuffer.java create mode 100644 src/com/esri/core/geometry/OperatorBufferCursor.java create mode 100644 src/com/esri/core/geometry/OperatorBufferLocal.java create mode 100644 src/com/esri/core/geometry/OperatorClip.java create mode 100644 src/com/esri/core/geometry/OperatorClipCursor.java create mode 100644 src/com/esri/core/geometry/OperatorClipLocal.java create mode 100644 src/com/esri/core/geometry/OperatorContains.java create mode 100644 src/com/esri/core/geometry/OperatorContainsLocal.java create mode 100644 src/com/esri/core/geometry/OperatorConvexHull.java create mode 100644 src/com/esri/core/geometry/OperatorConvexHullCursor.java create mode 100644 src/com/esri/core/geometry/OperatorConvexHullLocal.java create mode 100644 src/com/esri/core/geometry/OperatorCrosses.java create mode 100644 src/com/esri/core/geometry/OperatorCrossesLocal.java create mode 100644 src/com/esri/core/geometry/OperatorCut.java create mode 100644 src/com/esri/core/geometry/OperatorCutCursor.java create mode 100644 src/com/esri/core/geometry/OperatorCutLocal.java create mode 100644 src/com/esri/core/geometry/OperatorDensifyByLength.java create mode 100644 src/com/esri/core/geometry/OperatorDensifyByLengthCursor.java create mode 100644 src/com/esri/core/geometry/OperatorDensifyByLengthLocal.java create mode 100644 src/com/esri/core/geometry/OperatorDifference.java create mode 100644 src/com/esri/core/geometry/OperatorDifferenceCursor.java create mode 100644 src/com/esri/core/geometry/OperatorDifferenceLocal.java create mode 100644 src/com/esri/core/geometry/OperatorDisjoint.java create mode 100644 src/com/esri/core/geometry/OperatorDisjointLocal.java create mode 100644 src/com/esri/core/geometry/OperatorDistance.java create mode 100644 src/com/esri/core/geometry/OperatorDistanceLocal.java create mode 100644 src/com/esri/core/geometry/OperatorEquals.java create mode 100644 src/com/esri/core/geometry/OperatorEqualsLocal.java create mode 100644 src/com/esri/core/geometry/OperatorExportToESRIShape.java create mode 100644 src/com/esri/core/geometry/OperatorExportToESRIShapeCursor.java create mode 100644 src/com/esri/core/geometry/OperatorExportToESRIShapeLocal.java create mode 100644 src/com/esri/core/geometry/OperatorExportToJson.java create mode 100644 src/com/esri/core/geometry/OperatorExportToJsonCursor.java create mode 100644 src/com/esri/core/geometry/OperatorExportToJsonLocal.java create mode 100644 src/com/esri/core/geometry/OperatorExportToWkb.java create mode 100644 src/com/esri/core/geometry/OperatorExportToWkbLocal.java create mode 100644 src/com/esri/core/geometry/OperatorExportToWkt.java create mode 100644 src/com/esri/core/geometry/OperatorExportToWktLocal.java create mode 100644 src/com/esri/core/geometry/OperatorFactory.java create mode 100644 src/com/esri/core/geometry/OperatorFactoryLocal.java create mode 100644 src/com/esri/core/geometry/OperatorGeneralize.java create mode 100644 src/com/esri/core/geometry/OperatorGeneralizeCursor.java create mode 100644 src/com/esri/core/geometry/OperatorGeneralizeLocal.java create mode 100644 src/com/esri/core/geometry/OperatorGeodeticArea.java create mode 100644 src/com/esri/core/geometry/OperatorGeodeticAreaLocal.java create mode 100644 src/com/esri/core/geometry/OperatorGeodeticLength.java create mode 100644 src/com/esri/core/geometry/OperatorGeodeticLengthLocal.java create mode 100644 src/com/esri/core/geometry/OperatorImportFromESRIShape.java create mode 100644 src/com/esri/core/geometry/OperatorImportFromESRIShapeCursor.java create mode 100644 src/com/esri/core/geometry/OperatorImportFromESRIShapeLocal.java create mode 100644 src/com/esri/core/geometry/OperatorImportFromGeoJson.java create mode 100644 src/com/esri/core/geometry/OperatorImportFromGeoJsonLocal.java create mode 100644 src/com/esri/core/geometry/OperatorImportFromJson.java create mode 100644 src/com/esri/core/geometry/OperatorImportFromJsonCursor.java create mode 100644 src/com/esri/core/geometry/OperatorImportFromJsonLocal.java create mode 100644 src/com/esri/core/geometry/OperatorImportFromWkb.java create mode 100644 src/com/esri/core/geometry/OperatorImportFromWkbLocal.java create mode 100644 src/com/esri/core/geometry/OperatorImportFromWkt.java create mode 100644 src/com/esri/core/geometry/OperatorImportFromWktLocal.java create mode 100644 src/com/esri/core/geometry/OperatorImportMapGeometryFromJsonParser.java create mode 100644 src/com/esri/core/geometry/OperatorImportMapGeometryFromJsonParserCursor.java create mode 100644 src/com/esri/core/geometry/OperatorImportMapGeometryFromJsonParserLocal.java create mode 100644 src/com/esri/core/geometry/OperatorInternalRelationUtils.java create mode 100644 src/com/esri/core/geometry/OperatorIntersection.java create mode 100644 src/com/esri/core/geometry/OperatorIntersectionCursor.java create mode 100644 src/com/esri/core/geometry/OperatorIntersectionLocal.java create mode 100644 src/com/esri/core/geometry/OperatorIntersects.java create mode 100644 src/com/esri/core/geometry/OperatorIntersectsLocal.java create mode 100644 src/com/esri/core/geometry/OperatorOffset.java create mode 100644 src/com/esri/core/geometry/OperatorOffsetCursor.java create mode 100644 src/com/esri/core/geometry/OperatorOffsetLocal.java create mode 100644 src/com/esri/core/geometry/OperatorOverlaps.java create mode 100644 src/com/esri/core/geometry/OperatorOverlapsLocal.java create mode 100644 src/com/esri/core/geometry/OperatorProject.java create mode 100644 src/com/esri/core/geometry/OperatorProjectLocal.java create mode 100644 src/com/esri/core/geometry/OperatorProximity2D.java create mode 100644 src/com/esri/core/geometry/OperatorProximity2DLocal.java create mode 100644 src/com/esri/core/geometry/OperatorRelate.java create mode 100644 src/com/esri/core/geometry/OperatorRelateLocal.java create mode 100644 src/com/esri/core/geometry/OperatorSimpleRelation.java create mode 100644 src/com/esri/core/geometry/OperatorSimplify.java create mode 100644 src/com/esri/core/geometry/OperatorSimplifyCursor.java create mode 100644 src/com/esri/core/geometry/OperatorSimplifyCursorOGC.java create mode 100644 src/com/esri/core/geometry/OperatorSimplifyLocal.java create mode 100644 src/com/esri/core/geometry/OperatorSimplifyLocalHelper.java create mode 100644 src/com/esri/core/geometry/OperatorSimplifyLocalOGC.java create mode 100644 src/com/esri/core/geometry/OperatorSimplifyOGC.java create mode 100644 src/com/esri/core/geometry/OperatorSymmetricDifference.java create mode 100644 src/com/esri/core/geometry/OperatorSymmetricDifferenceCursor.java create mode 100644 src/com/esri/core/geometry/OperatorSymmetricDifferenceLocal.java create mode 100644 src/com/esri/core/geometry/OperatorTouches.java create mode 100644 src/com/esri/core/geometry/OperatorTouchesLocal.java create mode 100644 src/com/esri/core/geometry/OperatorUnion.java create mode 100644 src/com/esri/core/geometry/OperatorUnionCursor.java create mode 100644 src/com/esri/core/geometry/OperatorUnionLocal.java create mode 100644 src/com/esri/core/geometry/OperatorWithin.java create mode 100644 src/com/esri/core/geometry/OperatorWithinLocal.java create mode 100644 src/com/esri/core/geometry/PathFlags.java create mode 100644 src/com/esri/core/geometry/PeDouble.java create mode 100644 src/com/esri/core/geometry/PlaneSweepCrackerHelper.java create mode 100644 src/com/esri/core/geometry/Point.java create mode 100644 src/com/esri/core/geometry/Point2D.java create mode 100644 src/com/esri/core/geometry/Point3D.java create mode 100644 src/com/esri/core/geometry/PointInPolygonHelper.java create mode 100644 src/com/esri/core/geometry/Polygon.java create mode 100644 src/com/esri/core/geometry/PolygonUtils.java create mode 100644 src/com/esri/core/geometry/Polyline.java create mode 100644 src/com/esri/core/geometry/PolylinePath.java create mode 100644 src/com/esri/core/geometry/ProgressTracker.java create mode 100644 src/com/esri/core/geometry/ProjectionTransformation.java create mode 100644 src/com/esri/core/geometry/Proximity2DResult.java create mode 100644 src/com/esri/core/geometry/Proximity2DResultComparator.java create mode 100644 src/com/esri/core/geometry/QuadTree.java create mode 100644 src/com/esri/core/geometry/QuadTreeImpl.java create mode 100644 src/com/esri/core/geometry/RasterizedGeometry2D.java create mode 100644 src/com/esri/core/geometry/RasterizedGeometry2DImpl.java create mode 100644 src/com/esri/core/geometry/RelationalOperations.java create mode 100644 src/com/esri/core/geometry/RelationalOperationsMatrix.java create mode 100644 src/com/esri/core/geometry/RingOrientationFixer.java create mode 100644 src/com/esri/core/geometry/Segment.java create mode 100644 src/com/esri/core/geometry/SegmentBuffer.java create mode 100644 src/com/esri/core/geometry/SegmentFlags.java create mode 100644 src/com/esri/core/geometry/SegmentIntersector.java create mode 100644 src/com/esri/core/geometry/SegmentIterator.java create mode 100644 src/com/esri/core/geometry/SegmentIteratorImpl.java create mode 100644 src/com/esri/core/geometry/ShapeExportFlags.java create mode 100644 src/com/esri/core/geometry/ShapeImportFlags.java create mode 100644 src/com/esri/core/geometry/ShapeModifiers.java create mode 100644 src/com/esri/core/geometry/ShapeType.java create mode 100644 src/com/esri/core/geometry/SimpleByteBufferCursor.java create mode 100644 src/com/esri/core/geometry/SimpleGeometryCursor.java create mode 100644 src/com/esri/core/geometry/SimpleJsonCursor.java create mode 100644 src/com/esri/core/geometry/SimpleJsonParserCursor.java create mode 100644 src/com/esri/core/geometry/SimpleMapGeometryCursor.java create mode 100644 src/com/esri/core/geometry/Simplificator.java create mode 100644 src/com/esri/core/geometry/SpatialReference.java create mode 100644 src/com/esri/core/geometry/SpatialReferenceImpl.java create mode 100644 src/com/esri/core/geometry/SpatialReferenceSerializer.java create mode 100644 src/com/esri/core/geometry/StridedIndexTypeCollection.java create mode 100644 src/com/esri/core/geometry/StringUtils.java create mode 100644 src/com/esri/core/geometry/SweepComparator.java create mode 100644 src/com/esri/core/geometry/SweepMonkierComparator.java create mode 100644 src/com/esri/core/geometry/TopoGraph.java create mode 100644 src/com/esri/core/geometry/TopologicalOperations.java create mode 100644 src/com/esri/core/geometry/Transformation2D.java create mode 100644 src/com/esri/core/geometry/Transformation3D.java create mode 100644 src/com/esri/core/geometry/Treap.java create mode 100644 src/com/esri/core/geometry/UserCancelException.java create mode 100644 src/com/esri/core/geometry/VertexDescription.java create mode 100644 src/com/esri/core/geometry/VertexDescriptionDesignerImpl.java create mode 100644 src/com/esri/core/geometry/VertexDescriptionHash.java create mode 100644 src/com/esri/core/geometry/WkbByteOrder.java create mode 100644 src/com/esri/core/geometry/WkbExportFlags.java create mode 100644 src/com/esri/core/geometry/WkbGeometryType.java create mode 100644 src/com/esri/core/geometry/WkbImportFlags.java create mode 100644 src/com/esri/core/geometry/Wkid.java create mode 100644 src/com/esri/core/geometry/Wkt.java create mode 100644 src/com/esri/core/geometry/WktExportFlags.java create mode 100644 src/com/esri/core/geometry/WktImportFlags.java create mode 100644 src/com/esri/core/geometry/WktParser.java create mode 100644 src/com/esri/core/geometry/gcs_id_to_tolerance.txt create mode 100644 src/com/esri/core/geometry/gcs_tolerances.txt create mode 100644 src/com/esri/core/geometry/intermediate_to_old_wkid.txt create mode 100644 src/com/esri/core/geometry/new_to_old_wkid.txt create mode 100644 src/com/esri/core/geometry/ogc/OGCConcreteGeometryCollection.java create mode 100644 src/com/esri/core/geometry/ogc/OGCCurve.java create mode 100644 src/com/esri/core/geometry/ogc/OGCGeometry.java create mode 100644 src/com/esri/core/geometry/ogc/OGCGeometryCollection.java create mode 100644 src/com/esri/core/geometry/ogc/OGCLineString.java create mode 100644 src/com/esri/core/geometry/ogc/OGCLinearRing.java create mode 100644 src/com/esri/core/geometry/ogc/OGCMultiCurve.java create mode 100644 src/com/esri/core/geometry/ogc/OGCMultiLineString.java create mode 100644 src/com/esri/core/geometry/ogc/OGCMultiPoint.java create mode 100644 src/com/esri/core/geometry/ogc/OGCMultiPolygon.java create mode 100644 src/com/esri/core/geometry/ogc/OGCMultiSurface.java create mode 100644 src/com/esri/core/geometry/ogc/OGCPoint.java create mode 100644 src/com/esri/core/geometry/ogc/OGCPolygon.java create mode 100644 src/com/esri/core/geometry/ogc/OGCSurface.java create mode 100644 src/com/esri/core/geometry/pcs_id_to_tolerance.txt create mode 100644 src/com/esri/core/geometry/pcs_tolerances.txt create mode 100644 unittest/com/esri/core/geometry/GeometryUtils.java create mode 100644 unittest/com/esri/core/geometry/RandomCoordinateGenerator.java create mode 100644 unittest/com/esri/core/geometry/TestBuffer.java create mode 100644 unittest/com/esri/core/geometry/TestClip.java create mode 100644 unittest/com/esri/core/geometry/TestCommonMethods.java create mode 100644 unittest/com/esri/core/geometry/TestContains.java create mode 100644 unittest/com/esri/core/geometry/TestConvexHull.java create mode 100644 unittest/com/esri/core/geometry/TestCut.java create mode 100644 unittest/com/esri/core/geometry/TestDifference.java create mode 100644 unittest/com/esri/core/geometry/TestDistance.java create mode 100644 unittest/com/esri/core/geometry/TestEditShape.java create mode 100644 unittest/com/esri/core/geometry/TestEnvelope2DIntersector.java create mode 100644 unittest/com/esri/core/geometry/TestEquals.java create mode 100644 unittest/com/esri/core/geometry/TestFailed.java create mode 100644 unittest/com/esri/core/geometry/TestGeneralize.java create mode 100644 unittest/com/esri/core/geometry/TestGeodetic.java create mode 100644 unittest/com/esri/core/geometry/TestGeomToJSonExportSRFromWkiOrWkt_CR181369.java create mode 100644 unittest/com/esri/core/geometry/TestImportExport.java create mode 100644 unittest/com/esri/core/geometry/TestInterpolateAttributes.java create mode 100644 unittest/com/esri/core/geometry/TestIntersect2.java create mode 100644 unittest/com/esri/core/geometry/TestIntersection.java create mode 100644 unittest/com/esri/core/geometry/TestIntervalTree.java create mode 100644 unittest/com/esri/core/geometry/TestJSonGeometry.java create mode 100644 unittest/com/esri/core/geometry/TestJSonToGeomFromWkiOrWkt_CR177613.java create mode 100644 unittest/com/esri/core/geometry/TestJsonParser.java create mode 100644 unittest/com/esri/core/geometry/TestMathUtils.java create mode 100644 unittest/com/esri/core/geometry/TestMultiPoint.java create mode 100644 unittest/com/esri/core/geometry/TestOGC.java create mode 100644 unittest/com/esri/core/geometry/TestOffset.java create mode 100644 unittest/com/esri/core/geometry/TestPoint.java create mode 100644 unittest/com/esri/core/geometry/TestPolygon.java create mode 100644 unittest/com/esri/core/geometry/TestPolygonUtils.java create mode 100644 unittest/com/esri/core/geometry/TestProximity2D.java create mode 100644 unittest/com/esri/core/geometry/TestQuadTree.java create mode 100644 unittest/com/esri/core/geometry/TestRelation.java create mode 100644 unittest/com/esri/core/geometry/TestSerialization.java create mode 100644 unittest/com/esri/core/geometry/TestShapePreserving.java create mode 100644 unittest/com/esri/core/geometry/TestSimplify.java create mode 100644 unittest/com/esri/core/geometry/TestTouch.java create mode 100644 unittest/com/esri/core/geometry/TestTreap.java create mode 100644 unittest/com/esri/core/geometry/TestUnion.java create mode 100644 unittest/com/esri/core/geometry/TestWKBSupport.java create mode 100644 unittest/com/esri/core/geometry/TestWkbImportOnPostgresST.java create mode 100644 unittest/com/esri/core/geometry/TestWkid.java create mode 100644 unittest/com/esri/core/geometry/TestWktParser.java create mode 100644 unittest/com/esri/core/geometry/Utils.java create mode 100644 unittest/com/esri/core/geometry/savedAngularUnit.txt create mode 100644 unittest/com/esri/core/geometry/savedAreaUnit.txt create mode 100644 unittest/com/esri/core/geometry/savedEnvelope.txt create mode 100644 unittest/com/esri/core/geometry/savedLinearUnit.txt create mode 100644 unittest/com/esri/core/geometry/savedMultiPoint.txt create mode 100644 unittest/com/esri/core/geometry/savedPoint.txt create mode 100644 unittest/com/esri/core/geometry/savedPolygon.txt create mode 100644 unittest/com/esri/core/geometry/savedPolyline.txt create mode 100644 unittest/com/esri/core/geometry/savedProjectionTransformation.txt create mode 100644 unittest/com/esri/core/geometry/savedSpatialReference.txt diff --git a/src/com/esri/core/geometry/AttributeStreamBase.java b/src/com/esri/core/geometry/AttributeStreamBase.java new file mode 100644 index 00000000..42c71a58 --- /dev/null +++ b/src/com/esri/core/geometry/AttributeStreamBase.java @@ -0,0 +1,506 @@ +/* + Copyright 1995-2013 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ + + +package com.esri.core.geometry; + +import com.esri.core.geometry.VertexDescription.Persistence; +import java.nio.ByteBuffer; + +/** + * Base class for AttributeStream instances. + */ +abstract class AttributeStreamBase { + + protected boolean m_bLockedInSize; + protected boolean m_bReadonly; + + public AttributeStreamBase() { + m_bReadonly = false; + m_bLockedInSize = false; + } + + /** + * Returns the number of elements in the stream. + */ + public abstract int virtualSize(); + + /** + * Returns the Persistence type of the stream. + */ + public abstract int getPersistence(); + + /** + * Reads given element and returns it as double. + */ + public abstract double readAsDbl(int offset); + + /** + * Writes given element as double. The double is cast to the internal + * representation (truncated when int). + */ + public abstract void writeAsDbl(int offset, double d); + + /** + * Reads given element and returns it as int (truncated if double). + */ + public abstract int readAsInt(int offset); + + /** + * Writes given element as int. The int is cast to the internal + * representation. + */ + public abstract void writeAsInt(int offset, int d); + + /** + * Reads given element and returns it as int (truncated if double). + */ + public abstract long readAsInt64(int offset); + + /** + * Writes given element as int. The int is cast to the internal + * representation. + */ + public abstract void writeAsInt64(int offset, long d); + + /** + * Resizes the AttributeStream to the new size. + */ + public abstract void resize(int newSize, double defaultValue); + + /** + * Resizes the AttributeStream to the new size. + */ + public abstract void resize(int newSize); + + /** + * Resizes the AttributeStream to the new size. Does not change the capacity + * of the stream. + */ + public abstract void resizePreserveCapacity(int newSize);// java only method + + /** + * Same as resize(0) + */ + void clear(boolean bFreeMemory) { + if (bFreeMemory) + resize(0); + else + resizePreserveCapacity(0); + } + + /** + * Adds a range of elements from the source stream. The streams must be of + * the same type. + * + * @param src + * The source stream to read elements from. + * @param srcStart + * The index of the element in the source stream to start reading + * from. + * @param count + * The number of elements to add. + * @param bForward + * True if adding the elements in order of the incoming source + * stream. False if adding the elements in reverse. + * @param stride + * The number of elements to be grouped together if adding the + * elements in reverse. + */ + public abstract void addRange(AttributeStreamBase src, int srcStart, + int count, boolean bForward, int stride); + + /** + * Inserts a range of elements from the source stream. The streams must be + * of the same type. + * + * @param start + * The index where to start the insert. + * @param src + * The source stream to read elements from. + * @param srcStart + * The index of the element in the source stream to start reading + * from. + * @param count + * The number of elements to read from the source stream. + * @param validSize + * The number of valid elements in this stream. + */ + public abstract void insertRange(int start, AttributeStreamBase src, + int srcStart, int count, boolean bForward, int stride, int validSize); + + /** + * Inserts a range of elements of the given value. + * + * @param start + * The index where to start the insert. + * @param value + * The value to be inserted. + * @param count + * The number of elements to be inserted. + * @param validSize + * The number of valid elements in this stream. + */ + public abstract void insertRange(int start, double value, int count, + int validSize); + + /** + * Inserts the attributes of a given semantics from a Point geometry. + * + * @param start + * The index where to start the insert. + * @param pt + * The Point geometry holding the attributes to be inserted. + * @param semantics + * The attribute semantics that are being inserted. + * @param validSize + * The number of valid elements in this stream. + */ + public abstract void insertAttributes(int start, Point pt, int semantics, + int validSize); + + /** + * Sets a range of values to given value. + * + * @param value + * The value to set stream elements to. + * @param start + * The index of the element to start writing to. + * @param count + * The number of elements to set. + */ + public abstract void setRange(double value, int start, int count); + + /** + * Adds a range of elements from the source byte buffer. This stream is + * resized automatically to accomodate required number of elements. + * + * @param startElement + * the index of the element in this stream to start setting + * elements from. + * @param count + * The number of AttributeStream elements to read. + * @param src + * The source ByteBuffer to read elements from. + * @param sourceStart + * The offset from the start of the ByteBuffer in bytes. + * @param bForward + * When False, the source is written in reversed order. + * @param stride + * Used for reversed writing only to indicate the unit of + * writing. elements inside a stride are not reversed. Only the + * strides are reversed. + */ + public abstract void writeRange(int startElement, int count, + AttributeStreamBase src, int sourceStart, boolean bForward, + int stride); + + /** + * Adds a range of elements from the source byte buffer. The stream is + * resized automatically to accomodate required number of elements. + * + * @param startElement + * the index of the element in this stream to start setting + * elements from. + * @param count + * The number of AttributeStream elements to read. + * @param src + * The source ByteBuffer to read elements from. + * @param offsetBytes + * The offset from the start of the ByteBuffer in bytes. + */ + public abstract void writeRange(int startElement, int count, + ByteBuffer src, int offsetBytes, boolean bForward); + + /** + * Write a range of elements to the source byte buffer. + * + * @param srcStart + * The element index to start writing from. + * @param count + * The number of AttributeStream elements to write. + * @param dst + * The destination ByteBuffer. The buffer must be large enough or + * it will throw. + * @param dstOffsetBytes + * The offset in the destination ByteBuffer to start write + * elements from. + */ + public abstract void readRange(int srcStart, int count, ByteBuffer dst, + int dstOffsetBytes, boolean bForward); + + /** + * Erases a range from the buffer and defragments the result. + * + * @param index + * The index in this stream where the erasing starts. + * @param count + * The number of elements to be erased. + * @param validSize + * The number of valid elements in this stream. + */ + public abstract void eraseRange(int index, int count, int validSize); + + /** + * Reverses a range from the buffer. + * + * @param index + * The index in this stream where the reversing starts. + * @param count + * The number of elements to be reversed. + * @param stride + * The number of elements to be grouped together when doing the + * reverse. + */ + public abstract void reverseRange(int index, int count, int stride); + + /** + * Creates a new attribute stream for storing bytes. + * + * @param size + * The number of elements in the stream. + */ + public static AttributeStreamBase createByteStream(int size) { + AttributeStreamBase newStream = new AttributeStreamOfInt8(size); + return newStream; + } + + /** + * Creates a new attribute stream for storing bytes. + * + * @param size + * The number of elements in the stream. + * @param defaultValue + * The default value to fill the stream with. + */ + public static AttributeStreamBase createByteStream(int size, + byte defaultValue) { + AttributeStreamBase newStream = new AttributeStreamOfInt8(size, + defaultValue); + return newStream; + + } + + /** + * Creates a new attribute stream for storing doubles. + * + * @param size + * The number of elements in the stream. + */ + public static AttributeStreamBase createDoubleStream(int size) { + AttributeStreamBase newStream = new AttributeStreamOfDbl(size); + return newStream; + } + + /** + * Creates a new attribute stream for storing doubles. + * + * @param size + * The number of elements in the stream. + * @param defaultValue + * The default value to fill the stream with. + */ + public static AttributeStreamBase createDoubleStream(int size, + double defaultValue) { + AttributeStreamBase newStream = new AttributeStreamOfDbl(size, + defaultValue); + return newStream; + } + + /** + * Creats a copy of the stream that contains upto maxsize elements. + */ + public abstract AttributeStreamBase restrictedClone(int maxsize); + + /** + * Makes the stream to be readonly. Any operation that changes the content + * or size of the stream will throw. + */ + public void setReadonly() { + m_bReadonly = true; + m_bLockedInSize = true; + } + + public boolean isReadonly() { + return m_bReadonly; + } + + /** + * Lock the size of the stream. Any operation that changes the size of the + * stream will throw. + */ + public void lockSize() { + m_bLockedInSize = true; + } + + public boolean isLockedSize() { + return m_bLockedInSize; + } + + /** + * Creates a new attribute stream of given persistence type and size. + * + * @param persistence + * The persistence type of the stream (see VertexDescription). + * @param size + * The number of elements (floats, doubles, or 32 bit integers) + * of the given type in the stream. + */ + public static AttributeStreamBase createAttributeStreamWithPersistence( + int persistence, int size) { + AttributeStreamBase newStream; + switch (persistence) { + case (Persistence.enumFloat): + newStream = new AttributeStreamOfFloat(size); + break; + case (Persistence.enumDouble): + newStream = new AttributeStreamOfDbl(size); + break; + case (Persistence.enumInt32): + newStream = new AttributeStreamOfInt32(size); + break; + case (Persistence.enumInt64): + newStream = new AttributeStreamOfInt64(size); + break; + case (Persistence.enumInt8): + newStream = new AttributeStreamOfInt8(size); + break; + case (Persistence.enumInt16): + newStream = new AttributeStreamOfInt16(size); + break; + default: + throw new GeometryException("Internal Error"); + } + return newStream; + } + + /** + * Creates a new attribute stream of given persistence type and size. + * + * @param persistence + * The persistence type of the stream (see VertexDescription). + * @param size + * The number of elements (floats, doubles, or 32 bit integers) + * of the given type in the stream. + * @param defaultValue + * The default value to fill the stream with. + */ + public static AttributeStreamBase createAttributeStreamWithPersistence( + int persistence, int size, double defaultValue) { + AttributeStreamBase newStream; + switch (persistence) { + case (Persistence.enumFloat): + newStream = new AttributeStreamOfFloat(size, (float) defaultValue); + break; + case (Persistence.enumDouble): + newStream = new AttributeStreamOfDbl(size, (double) defaultValue); + break; + case (Persistence.enumInt32): + newStream = new AttributeStreamOfInt32(size, (int) defaultValue); + break; + case (Persistence.enumInt64): + newStream = new AttributeStreamOfInt64(size, (long) defaultValue); + break; + case (Persistence.enumInt8): + newStream = new AttributeStreamOfInt8(size, (byte) defaultValue); + break; + case (Persistence.enumInt16): + newStream = new AttributeStreamOfInt16(size, (short) defaultValue); + break; + default: + throw new GeometryException("Internal Error"); + } + return newStream; + } + + /** + * Creates a new attribute stream for the given semantics and vertex count. + * + * @param semantics + * The semantics of the attribute (see VertexDescription). + * @param vertexCount + * The number of vertices in the geometry. The actual number of + * elements in the stream is vertexCount * ncomponents. + */ + public static AttributeStreamBase createAttributeStreamWithSemantics( + int semantics, int vertexCount) { + int ncomps = VertexDescription.getComponentCount(semantics); + int persistence = VertexDescription.getPersistence(semantics); + return createAttributeStreamWithPersistence(persistence, vertexCount + * ncomps, VertexDescription.getDefaultValue(semantics)); + } + + /** + * Creates a new attribute stream for storing vertex indices. + * + * @param size + * The number of elements in the stream. + */ + public static AttributeStreamBase createIndexStream(int size) { + int persistence = Persistence.enumInt32;// VertexDescription.getPersistenceFromInt(NumberUtils::SizeOf((int)0)); + AttributeStreamBase newStream; + switch (persistence) { + case (Persistence.enumInt32): + newStream = new AttributeStreamOfInt32(size); + break; + case (Persistence.enumInt64): + newStream = new AttributeStreamOfInt64(size); + break; + default: + throw new GeometryException("Internal Error"); + } + return newStream; + + } + + /** + * Creates a new attribute stream for storing vertex indices. + * + * @param size + * The number of elements in the stream. + * @param defaultValue + * The default value to fill the stream with. + */ + public static AttributeStreamBase createIndexStream(int size, + int defaultValue) { + int persistence = Persistence.enumInt32;// VertexDescription.getPersistenceFromInt(NumberUtils::SizeOf((int)0)); + AttributeStreamBase newStream; + switch (persistence) { + case (Persistence.enumInt32): + newStream = new AttributeStreamOfInt32(size, (int) defaultValue); + break; + case (Persistence.enumInt64): + newStream = new AttributeStreamOfInt64(size, (long) defaultValue); + break; + default: + throw new GeometryException("Internal Error"); + } + return newStream; + } + + public abstract int calculateHashImpl(int hashCode, int start, int end); + + public abstract boolean equals(AttributeStreamBase other, int start, int end); + +} diff --git a/src/com/esri/core/geometry/AttributeStreamOfDbl.java b/src/com/esri/core/geometry/AttributeStreamOfDbl.java new file mode 100644 index 00000000..ec56a58b --- /dev/null +++ b/src/com/esri/core/geometry/AttributeStreamOfDbl.java @@ -0,0 +1,810 @@ +/* + Copyright 1995-2013 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ + + +package com.esri.core.geometry; + +import com.esri.core.geometry.VertexDescription.Persistence; +import java.nio.ByteBuffer; +import java.util.Arrays; + +final class AttributeStreamOfDbl extends AttributeStreamBase { + + double[] m_buffer = null; + int m_size; + + public int size() { + return m_size; + } + + public void reserve(int reserve)// only in Java + { + if (reserve <= 0) + return; + if (m_buffer == null) + m_buffer = new double[reserve]; + else { + if (reserve <= m_buffer.length) + return; + double[] buf = new double[reserve]; + System.arraycopy(m_buffer, 0, buf, 0, m_size); + m_buffer = buf; + } + + } + + public AttributeStreamOfDbl(int size) { + int sz = size; + if (sz < 2) + sz = 2; + m_buffer = new double[sz]; + m_size = size; + } + + public AttributeStreamOfDbl(int size, double defaultValue) { + int sz = size; + if (sz < 2) + sz = 2; + m_buffer = new double[sz]; + m_size = size; + Arrays.fill(m_buffer, 0, size, defaultValue); + } + + public AttributeStreamOfDbl(AttributeStreamOfDbl other) { + m_buffer = other.m_buffer.clone(); + m_size = other.m_size; + } + + public AttributeStreamOfDbl(AttributeStreamOfDbl other, int maxSize) { + m_size = other.size(); + if (m_size > maxSize) + m_size = maxSize; + int sz = m_size; + if (sz < 2) + sz = 2; + m_buffer = new double[sz]; + System.arraycopy(other.m_buffer, 0, m_buffer, 0, m_size); + } + + /** + * Reads a value from the buffer at given offset. + * + * @param offset + * is the element number in the stream. + */ + public double read(int offset) { + return m_buffer[offset]; + } + + public double get(int offset) { + return m_buffer[offset]; + } + + /** + * Overwrites given element with new value. + * + * @param offset + * is the element number in the stream. + * @param value + * is the value to write. + */ + public void write(int offset, double value) { + if (m_bReadonly) { + throw new RuntimeException("invalid_call"); + } + m_buffer[offset] = value; + } + + public void set(int offset, double value) { + if (m_bReadonly) { + throw new RuntimeException("invalid_call"); + } + m_buffer[offset] = value; + } + + /** + * Reads a value from the buffer at given offset. + * + * @param offset + * is the element number in the stream. + */ + public void read(int offset, Point2D outPoint) { + outPoint.x = m_buffer[offset]; + outPoint.y = m_buffer[offset + 1]; + } + + /** + * Overwrites given element with new value. + * + * @param offset + * is the element number in the stream. + * @param value + * is the value to write. + */ + void write(int offset, Point2D point) { + if (m_bReadonly) { + throw new RuntimeException("invalid_call"); + } + m_buffer[offset] = point.x; + m_buffer[offset + 1] = point.y; + } + + /** + * Adds a new value at the end of the stream. + */ + public void add(double v) { + resize(m_size + 1); + m_buffer[m_size - 1] = v; + } + + @Override + public AttributeStreamBase restrictedClone(int maxsize) { + AttributeStreamOfDbl clone = new AttributeStreamOfDbl(this, maxsize); + return clone; + } + + @Override + public int virtualSize() { + return size(); + } + + // @Override + // public void addRange(AttributeStreamBase src, int srcStartIndex, int + // count) { + // if ((src == this) || !(src instanceof AttributeStreamOfDbl)) + // throw new IllegalArgumentException(); + // + // AttributeStreamOfDbl as = (AttributeStreamOfDbl) src; + // + // int len = as.size(); + // int oldSize = m_size; + // resize(oldSize + len, 0); + // for (int i = 0; i < len; i++) { + // m_buffer[oldSize + i] = as.read(i); + // } + // } + + @Override + public int getPersistence() { + return Persistence.enumDouble; + } + + @Override + public double readAsDbl(int offset) { + return read(offset); + } + + @Override + public int readAsInt(int offset) { + return (int) read(offset); + } + + @Override + public long readAsInt64(int offset) { + return (long) read(offset); + } + + @Override + public void resize(int newSize) { + if (m_bLockedInSize) + throw new GeometryException( + "invalid call. Attribute Stream is locked and cannot be resized."); + + if (newSize <= m_size) { + if ((newSize * 5) / 4 < m_buffer.length) {// decrease when the 25% + // margin is exceeded + double[] newBuffer = new double[newSize]; + System.arraycopy(m_buffer, 0, newBuffer, 0, newSize); + m_buffer = newBuffer; + } + m_size = newSize; + } else { + if (newSize > m_buffer.length) { + int sz = (newSize < 64) ? Math.max(newSize * 2, 4) + : (newSize * 5) / 4; + double[] newBuffer = new double[sz]; + System.arraycopy(m_buffer, 0, newBuffer, 0, m_size); + m_buffer = newBuffer; + } + + m_size = newSize; + } + } + + @Override + public void resizePreserveCapacity(int newSize)// java only method + { + if (m_buffer == null || newSize > m_buffer.length) + resize(newSize); + if (m_bLockedInSize) + throw new GeometryException( + "invalid call. Attribute Stream is locked and cannot be resized."); + + m_size = newSize; + } + + @Override + public void resize(int newSize, double defaultValue) { + if (m_bLockedInSize) + throw new GeometryException( + "invalid call. Attribute Stream is locked and cannot be resized."); + if (newSize <= m_size) { + if ((newSize * 5) / 4 < m_buffer.length) {// decrease when the 25% + // margin is exceeded + double[] newBuffer = new double[newSize]; + System.arraycopy(m_buffer, 0, newBuffer, 0, newSize); + m_buffer = newBuffer; + } + m_size = newSize; + } else { + if (newSize > m_buffer.length) { + int sz = (newSize < 64) ? Math.max(newSize * 2, 4) + : (newSize * 5) / 4; + double[] newBuffer = new double[sz]; + System.arraycopy(m_buffer, 0, newBuffer, 0, m_size); + m_buffer = newBuffer; + } + + Arrays.fill(m_buffer, m_size, newSize, defaultValue); + + m_size = newSize; + } + } + + @Override + public void writeAsDbl(int offset, double d) { + write(offset, d); + } + + @Override + public void writeAsInt64(int offset, long d) { + write(offset, (double) d); + } + + @Override + public void writeAsInt(int offset, int d) { + write(offset, (double) d); + } + + /** + * Sets the envelope from the attribute stream. The attribute stream stores + * interleaved x and y. The envelope will be set to empty if the pointCount + * is zero. + */ + public void setEnvelopeFromPoints(int pointCount, Envelope2D inOutEnv) { + if (pointCount == 0) { + inOutEnv.setEmpty(); + return; + } + if (pointCount < 0) + pointCount = size() / 2; + else if (pointCount * 2 > size()) + throw new IllegalArgumentException(); + + inOutEnv.setCoords(read(0), read(1)); + for (int i = 1; i < pointCount; i++) { + inOutEnv.mergeNE(read(i * 2), read(i * 2 + 1)); + } + } + + @Override + public int calculateHashImpl(int hashCodeIn, int start, int end) { + int hashCode = hashCodeIn; + for (int i = start, n = size(); i < n && i < end; i++) + hashCode = NumberUtils.hash(hashCode, read(i)); + + return hashCode; + } + + @Override + public boolean equals(AttributeStreamBase other, int start, int end) { + if (other == null) + return false; + + if (!(other instanceof AttributeStreamOfDbl)) + return false; + + AttributeStreamOfDbl _other = (AttributeStreamOfDbl) other; + + int size = size(); + int sizeOther = _other.size(); + + if (end > size || end > sizeOther && (size != sizeOther)) + return false; + + if (end > size) + end = size; + + for (int i = start; i < end; i++) + if (read(i) != _other.read(i)) + return false; + + return true; + } + + @Override + public void addRange(AttributeStreamBase src, int start, int count, + boolean bForward, int stride) { + if (m_bReadonly) + throw new GeometryException("invalid_call"); + + if (!bForward && (stride < 1 || count % stride != 0)) + throw new IllegalArgumentException(); + + int oldSize = m_size; + int newSize = oldSize + count; + resize(newSize); + + if (bForward) { + System.arraycopy(((AttributeStreamOfDbl) src).m_buffer, start, + m_buffer, oldSize, count); + } else { + int n = count; + + for (int i = 0; i < count; i += stride) { + n -= stride; + + for (int s = 0; s < stride; s++) { + m_buffer[oldSize + i + s] = ((AttributeStreamOfDbl) src).m_buffer[start + + n + s]; + } + } + } + } + + // public void addRange(AttributeStreamBase src, int start, + // int count, boolean bForward, int stride) { + // + // if (m_bReadonly) + // throw new GeometryException("invalid_call"); + // + // if (!bForward && (stride < 1 || count % stride != 0)) + // throw new IllegalArgumentException(); + // + // if (bForward) + // { + // double[] otherbuffer = ((AttributeStreamOfDbl) src).m_buffer; + // // int newSize = size() + count; + // // resize(newSize); + // // System.arraycopy(otherbuffer, start, m_buffer, pos, count); + // for (int i = 0; i < count; i++) { + // add(otherbuffer[start + i]); + // } + // } else { + // throw new GeometryException("not implemented for reverse add"); + // } + // } + + @Override + public void insertRange(int start, AttributeStreamBase src, int srcStart, + int count, boolean bForward, int stride, int validSize) { + if (m_bReadonly) + throw new GeometryException("invalid_call"); + + if (!bForward && (stride < 1 || count % stride != 0)) + throw new IllegalArgumentException(); + + System.arraycopy(m_buffer, start, m_buffer, start + count, validSize + - start); + + if (m_buffer == ((AttributeStreamOfDbl) src).m_buffer) { + if (start < srcStart) + srcStart += count; + } + + if (bForward) { + System.arraycopy(((AttributeStreamOfDbl) src).m_buffer, srcStart, + m_buffer, start, count); + } else { + int n = count; + + for (int i = 0; i < count; i += stride) { + n -= stride; + + for (int s = 0; s < stride; s++) { + m_buffer[start + i + s] = ((AttributeStreamOfDbl) src).m_buffer[srcStart + + n + s]; + } + } + } + } + + @Override + public void insertRange(int start, double value, int count, int validSize) { + if (m_bReadonly) + throw new GeometryException("invalid_call"); + + System.arraycopy(m_buffer, start, m_buffer, start + count, validSize + - start); + + for (int i = 0; i < count; i++) { + m_buffer[start + i] = value; + } + } + + @Override + public void insertAttributes(int start, Point pt, int semantics, + int validSize) { + if (m_bReadonly) + throw new GeometryException("invalid_call"); + + int comp = VertexDescription.getComponentCount(semantics); + + System.arraycopy(m_buffer, start, m_buffer, start + comp, validSize + - start); + + for (int c = 0; c < comp; c++) { + m_buffer[start + c] = pt.getAttributeAsDbl(semantics, c); + } + } + + public void insert(int index, Point2D point, int validSize) { + if (m_bReadonly) + throw new GeometryException("invalid_call"); + + System.arraycopy(m_buffer, index, m_buffer, index + 2, validSize + - index); + m_buffer[index] = point.x; + m_buffer[index + 1] = point.y; + } + + // special case for .net 2d array syntax [,] + // writes count doubles, 2 at a time, into this stream. count is assumed to + // be even, arrayOffset is an index of the zeroth dimension (i.e. you can't + // start writing from dst[0,1]) + public void writeRange(int streamOffset, int count, double[][] src, + int arrayOffset, boolean bForward) { + if (streamOffset < 0 || count < 0 || arrayOffset < 0 + || count > NumberUtils.intMax()) + throw new IllegalArgumentException(); + + if (src.length * 2 < (int) ((arrayOffset << 1) + count)) + throw new IllegalArgumentException(); + if (count == 0) + return; + + if (size() < count + streamOffset) + resize(count + streamOffset); + + int j = streamOffset; + if (!bForward) + j += count - 1; + + final int dj = bForward ? 2 : -2; + + int end = arrayOffset + (count >> 1); + for (int i = arrayOffset; i < end; i++) { + m_buffer[j] = (double) src[i][0]; + m_buffer[j + 1] = (double) src[i][1]; + j += dj; + } + } + + public void writeRange(int streamOffset, int count, double[] src, + int arrayOffset, boolean bForward) { + if (streamOffset < 0 || count < 0 || arrayOffset < 0) + throw new IllegalArgumentException(); + + if (src.length < arrayOffset + count) + throw new IllegalArgumentException(); + if (count == 0) + return; + + if (size() < count + streamOffset) + resize(count + streamOffset); + + if (bForward) { + System.arraycopy(src, arrayOffset, m_buffer, streamOffset, count); + } else { + int j = streamOffset; + if (!bForward) + j += count - 1; + + int end = arrayOffset + count; + for (int i = arrayOffset; i < end; i++) { + m_buffer[j] = src[i]; + j--; + } + } + } + + // reads count doubles, 2 at a time, into dst. count is assumed to be even, + // arrayOffset is an index of the zeroth dimension (i.e. you can't start + // reading into dst[0,1]) + // void AttributeStreamOfDbl::ReadRange(int streamOffset, int count, + // array^ dst, int arrayOffset, bool bForward) + + public void readRange(int streamOffset, int count, double[][] dst, + int arrayOffset, boolean bForward) { + if (streamOffset < 0 || count < 0 || arrayOffset < 0 + || count > NumberUtils.intMax() + || size() < count + streamOffset) + throw new IllegalArgumentException(); + + if (dst.length * 2 < (int) ((arrayOffset << 1) + count)) + throw new IllegalArgumentException(); + + if (count == 0) + return; + + int j = streamOffset; + if (!bForward) + j += count - 1; + + final int dj = bForward ? 2 : -2; + + int end = arrayOffset + (count >> 1); + for (int i = arrayOffset; i < end; i++) { + dst[i][0] = m_buffer[j]; + dst[i][1] = m_buffer[j + 1]; + j += dj; + } + + } + + @Override + public void eraseRange(int index, int count, int validSize) { + if (m_bReadonly) + throw new GeometryException("invalid_call"); + + if (index + count > m_size) + throw new GeometryException("invalid_call"); + + System.arraycopy(m_buffer, index + count, m_buffer, index, validSize + - (index + count)); + m_size -= count; + } + + @Override + public void readRange(int srcStart, int count, ByteBuffer dst, + int dstOffset, boolean bForward) { + if (srcStart < 0 || count < 0 || dstOffset < 0 + || size() < count + srcStart) + throw new IllegalArgumentException(); + + final int elmSize = NumberUtils.sizeOf((double) 0); + + if (dst.capacity() < (int) (dstOffset + elmSize * count)) + throw new IllegalArgumentException(); + + if (count == 0) + return; + + int j = srcStart; + if (!bForward) + j += count - 1; + + final int dj = bForward ? 1 : -1; + + int offset = dstOffset; + for (int i = 0; i < count; i++, offset += elmSize) { + dst.putDouble(offset, m_buffer[j]); + j += dj; + } + } + + @Override + public void reverseRange(int index, int count, int stride) { + if (m_bReadonly) + throw new GeometryException("invalid_call"); + + if (stride < 1 || count % stride != 0) + throw new GeometryException("invalid_call"); + + int cIterations = count >> 1; + int n = count; + + for (int i = 0; i < cIterations; i += stride) { + n -= stride; + + for (int s = 0; s < stride; s++) { + double temp = m_buffer[index + i + s]; + m_buffer[index + i + s] = m_buffer[index + n + s]; + m_buffer[index + n + s] = temp; + } + } + } + + @Override + public void setRange(double value, int start, int count) { + if (start < 0 || count < 0 || start < 0 || count + start > size()) + throw new IllegalArgumentException(); + + double v = value; + Arrays.fill(m_buffer, start, start + count, v); + // for (int i = start, n = start + count; i < n; i++) + // write(i, v); + } + + @Override + public void writeRange(int startElement, int count, + AttributeStreamBase _src, int srcStart, boolean bForward, int stride) { + if (startElement < 0 || count < 0 || srcStart < 0) + throw new IllegalArgumentException(); + + if (!bForward && (stride <= 0 || (count % stride != 0))) + throw new IllegalArgumentException(); + + AttributeStreamOfDbl src = (AttributeStreamOfDbl) _src; // the input + // type must + // match + + if (src.size() < (int) (srcStart + count)) + throw new IllegalArgumentException(); + + if (count == 0) + return; + + if (size() < count + startElement) + resize(count + startElement); + + if (_src == (AttributeStreamBase) this) { + _selfWriteRangeImpl(startElement, count, srcStart, bForward, stride); + return; + } + + if (bForward) { + int j = startElement; + int offset = srcStart; + for (int i = 0; i < count; i++) { + m_buffer[j] = src.m_buffer[offset]; + j++; + offset++; + } + } else { + int j = startElement; + int offset = srcStart + count - stride; + if (stride == 1) { + for (int i = 0; i < count; i++) { + m_buffer[j] = src.m_buffer[offset]; + j++; + offset--; + } + } else { + for (int i = 0, n = count / stride; i < n; i++) { + for (int k = 0; k < stride; k++) + m_buffer[j + k] = src.m_buffer[offset + k]; + + j += stride; + offset -= stride; + } + } + } + } + + private void _selfWriteRangeImpl(int toElement, int count, int fromElement, + boolean bForward, int stride) { + + // writing from to this stream. + if (bForward) { + if (toElement == fromElement) + return; + } + + System.arraycopy(m_buffer, fromElement, m_buffer, toElement, count); + + if (bForward) + return; + // reverse what we written + int j = toElement; + int offset = toElement + count - stride; + int dj = stride; + for (int i = 0, n = count / 2; i < n; i++) { + for (int k = 0; k < stride; k++) { + double v = m_buffer[j + k]; + m_buffer[j + k] = m_buffer[offset + k]; + m_buffer[offset + k] = v; + } + j += stride; + offset -= stride; + } + + } + + @Override + public void writeRange(int startElement, int count, ByteBuffer src, + int offsetBytes, boolean bForward) { + if (startElement < 0 || count < 0 || offsetBytes < 0) + throw new IllegalArgumentException(); + + final int elmSize = NumberUtils.sizeOf((double) 0); + if (src.capacity() < (int) (offsetBytes + elmSize * count)) + throw new IllegalArgumentException(); + + if (count == 0) + return; + + if (size() < count + startElement) + resize(count + startElement); + + int j = startElement; + if (!bForward) + j += count - 1; + + final int dj = bForward ? 1 : -1; + + int offset = offsetBytes; + for (int i = 0; i < count; i++, offset += elmSize) { + m_buffer[j] = src.getDouble(offset); + j += dj; + } + + } + + public void writeRange(int streamOffset, int pointCount, Point2D[] src, + int arrayOffset, boolean bForward) { + if (streamOffset < 0 || pointCount < 0 || arrayOffset < 0) + throw new IllegalArgumentException(); + + // if (src->Length < (int)(arrayOffset + pointCount)) jt: we have lost + // the length check, not sure about this + // GEOMTHROW(invalid_argument); + + if (pointCount == 0) + return; + + if (size() < (pointCount << 1) + streamOffset) + resize((pointCount << 1) + streamOffset); + + int j = streamOffset; + if (!bForward) + j += (pointCount - 1) << 1; + + final int dj = bForward ? 2 : -2; + + // TODO: refactor to take advantage of the known block array structure + + final int i0 = arrayOffset; + pointCount += i0; + for (int i = i0; i < pointCount; i++) { + m_buffer[j] = src[i].x; + m_buffer[j + 1] = src[i].y; + j += dj; + } + } + + // Less efficient as boolean bForward set to false, as it is looping through + // half + // of the elements of the array + public void readRange(int srcStart, int count, double[] dst, int dstOffset, + boolean bForward) { + if (srcStart < 0 || count < 0 || dstOffset < 0 + || size() < count + srcStart) + throw new IllegalArgumentException(); + + if (bForward) + System.arraycopy(m_buffer, srcStart, dst, dstOffset, count); + else { + int j = dstOffset + count - 1; + for (int i = srcStart; i < count; i++) { + dst[j] = m_buffer[i]; + j--; + } + } + } + + public void sort(int start, int end) { + Arrays.sort(m_buffer, start, end); + } +} diff --git a/src/com/esri/core/geometry/AttributeStreamOfFloat.java b/src/com/esri/core/geometry/AttributeStreamOfFloat.java new file mode 100644 index 00000000..3577267b --- /dev/null +++ b/src/com/esri/core/geometry/AttributeStreamOfFloat.java @@ -0,0 +1,607 @@ +/* + Copyright 1995-2013 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ + + +package com.esri.core.geometry; + +import com.esri.core.geometry.VertexDescription.Persistence; +import java.nio.ByteBuffer; + +final class AttributeStreamOfFloat extends AttributeStreamBase { + + float[] m_buffer = null; + int m_size; + + public int size() { + return m_size; + } + + public void reserve(int reserve)// only in Java + { + if (reserve <= 0) + return; + if (m_buffer == null) + m_buffer = new float[reserve]; + else { + if (reserve <= m_buffer.length) + return; + float[] buf = new float[reserve]; + System.arraycopy(m_buffer, 0, buf, 0, m_size); + m_buffer = buf; + } + + } + + public AttributeStreamOfFloat(int size) { + int sz = size; + if (sz < 2) + sz = 2; + m_buffer = new float[sz]; + m_size = size; + } + + public AttributeStreamOfFloat(int size, float defaultValue) { + int sz = size; + if (sz < 2) + sz = 2; + m_buffer = new float[sz]; + m_size = size; + for (int i = 0; i < size; i++) + m_buffer[i] = defaultValue; + } + + public AttributeStreamOfFloat(AttributeStreamOfFloat other) { + m_buffer = other.m_buffer.clone(); + m_size = other.m_size; + } + + public AttributeStreamOfFloat(AttributeStreamOfFloat other, int maxSize) { + m_size = other.size(); + if (m_size > maxSize) + m_size = maxSize; + int sz = m_size; + if (sz < 2) + sz = 2; + m_buffer = new float[sz]; + System.arraycopy(other.m_buffer, 0, m_buffer, 0, m_size); + } + + /** + * Reads a value from the buffer at given offset. + * + * @param offset + * is the element number in the stream. + */ + public float read(int offset) { + return m_buffer[offset]; + } + + /** + * Overwrites given element with new value. + * + * @param offset + * is the element number in the stream. + * @param value + * is the value to write. + */ + public void write(int offset, float value) { + if (m_bReadonly) { + throw new RuntimeException("invalid_call"); + } + m_buffer[offset] = value; + } + + /** + * Adds a new value at the end of the stream. + * + * @param offset + * is the element number in the stream. + * @param value + * is the value to write. + */ + public void add(float v) { + resize(m_size + 1); + m_buffer[m_size - 1] = v; + } + + @Override + public AttributeStreamBase restrictedClone(int maxsize) { + int len = m_size; + int newSize = maxsize < len ? maxsize : len; + float[] newBuffer = new float[newSize]; + System.arraycopy(m_buffer, 0, newBuffer, 0, newSize); + m_buffer = newBuffer; + m_size = newSize; + return this; + } + + @Override + public int virtualSize() { + return size(); + } + + @Override + public int getPersistence() { + return Persistence.enumFloat; + } + + @Override + public double readAsDbl(int offset) { + return read(offset); + } + + @Override + public int readAsInt(int offset) { + return (int) read(offset); + } + + @Override + public long readAsInt64(int offset) { + return (long) read(offset); + } + + @Override + public void resize(int newSize) { + if (m_bLockedInSize) + throw new GeometryException( + "invalid call. Attribute Stream is locked and cannot be resized."); + + if (newSize <= m_size) { + if ((newSize * 5) / 4 < m_buffer.length) {// decrease when the 25% + // margin is exceeded + float[] newBuffer = new float[newSize]; + System.arraycopy(m_buffer, 0, newBuffer, 0, newSize); + m_buffer = newBuffer; + } + m_size = newSize; + } else { + if (newSize > m_buffer.length) { + int sz = (newSize < 64) ? Math.max(newSize * 2, 4) + : (newSize * 5) / 4; + float[] newBuffer = new float[sz]; + System.arraycopy(m_buffer, 0, newBuffer, 0, m_size); + m_buffer = newBuffer; + } + + m_size = newSize; + } + } + + @Override + public void resizePreserveCapacity(int newSize)// java only method + { + if (m_buffer == null || newSize > m_buffer.length) + resize(newSize); + if (m_bLockedInSize) + throw new GeometryException( + "invalid call. Attribute Stream is locked and cannot be resized."); + + m_size = newSize; + } + + @Override + public void resize(int newSize, double defaultValue) { + if (m_bLockedInSize) + throw new GeometryException( + "invalid call. Attribute Stream is locked and cannot be resized."); + if (newSize <= m_size) { + if ((newSize * 5) / 4 < m_buffer.length) {// decrease when the 25% + // margin is exceeded + float[] newBuffer = new float[newSize]; + System.arraycopy(m_buffer, 0, newBuffer, 0, newSize); + m_buffer = newBuffer; + } + m_size = newSize; + } else { + if (newSize > m_buffer.length) { + int sz = (newSize < 64) ? Math.max(newSize * 2, 4) + : (newSize * 5) / 4; + float[] newBuffer = new float[sz]; + System.arraycopy(m_buffer, 0, newBuffer, 0, m_size); + m_buffer = newBuffer; + } + + for (int i = m_size; i < newSize; i++) + m_buffer[i] = (float) defaultValue; + + m_size = newSize; + } + } + + @Override + public void writeAsDbl(int offset, double d) { + write(offset, (float) d); + } + + @Override + public void writeAsInt64(int offset, long d) { + write(offset, (float) d); + } + + @Override + public void writeAsInt(int offset, int d) { + write(offset, (float) d); + } + + // @Override + // public void writeRange(int srcStart, int count, ByteBuffer dst, + // int dstOffsetBytes) { + // // TODO Auto-generated method stub + // + // } + + @Override + public int calculateHashImpl(int hashCode, int start, int end) { + for (int i = start, n = size(); i < n && i < end; i++) + hashCode = NumberUtils.hash(hashCode, read(i)); + + return hashCode; + } + + @Override + public boolean equals(AttributeStreamBase other, int start, int end) { + if (other == null) + return false; + + if (!(other instanceof AttributeStreamOfFloat)) + return false; + + AttributeStreamOfFloat _other = (AttributeStreamOfFloat) other; + + int size = size(); + int sizeOther = _other.size(); + + if (end > size || end > sizeOther && (size != sizeOther)) + return false; + + if (end > size) + end = size; + + for (int i = start; i < end; i++) + if (read(i) != _other.read(i)) + return false; + + return true; + } + + // public void addRange(AttributeStreamBase src, int srcStartIndex, int + // count) { + // if ((src == this) || !(src instanceof AttributeStreamOfFloat)) + // throw new IllegalArgumentException(); + // + // AttributeStreamOfFloat as = (AttributeStreamOfFloat) src; + // + // int len = as.size(); + // int oldSize = m_size; + // resize(oldSize + len, 0); + // for (int i = 0; i < len; i++) { + // m_buffer[oldSize + i] = as.read(i); + // } + // } + + @Override + public void addRange(AttributeStreamBase src, int start, int count, + boolean bForward, int stride) { + if (m_bReadonly) + throw new GeometryException("invalid_call"); + + if (!bForward && (stride < 1 || count % stride != 0)) + throw new IllegalArgumentException(); + + int oldSize = m_size; + int newSize = oldSize + count; + resize(newSize); + + if (bForward) { + System.arraycopy(((AttributeStreamOfDbl) src).m_buffer, start, + m_buffer, oldSize, count); + } else { + int n = count; + + for (int i = 0; i < count; i += stride) { + n -= stride; + + for (int s = 0; s < stride; s++) { + m_buffer[oldSize + i + s] = ((AttributeStreamOfFloat) src).m_buffer[start + + n + s]; + } + } + } + } + + @Override + public void insertRange(int start, AttributeStreamBase src, int srcStart, + int count, boolean bForward, int stride, int validSize) { + if (m_bReadonly) + throw new GeometryException("invalid_call"); + + if (!bForward && (stride < 1 || count % stride != 0)) + throw new IllegalArgumentException(); + + System.arraycopy(m_buffer, start, m_buffer, start + count, validSize + - start); + + if (m_buffer == ((AttributeStreamOfFloat) src).m_buffer) { + if (start < srcStart) + srcStart += count; + } + + if (bForward) { + System.arraycopy(((AttributeStreamOfFloat) src).m_buffer, srcStart, + m_buffer, start, count); + } else { + int n = count; + + for (int i = 0; i < count; i += stride) { + n -= stride; + + for (int s = 0; s < stride; s++) { + m_buffer[start + i + s] = ((AttributeStreamOfFloat) src).m_buffer[srcStart + + n + s]; + } + } + } + } + + @Override + public void insertRange(int start, double value, int count, int validSize) { + if (m_bReadonly) + throw new GeometryException("invalid_call"); + + System.arraycopy(m_buffer, start, m_buffer, start + count, validSize + - start); + + float v = (float) value; + for (int i = 0; i < count; i++) { + m_buffer[start + i] = v; + } + } + + @Override + public void insertAttributes(int start, Point pt, int semantics, + int validSize) { + if (m_bReadonly) + throw new GeometryException("invalid_call"); + + int comp = VertexDescription.getComponentCount(semantics); + + System.arraycopy(m_buffer, start, m_buffer, start + comp, validSize + - start); + + for (int c = 0; c < comp; c++) { + m_buffer[start + c] = (float) pt.getAttributeAsDbl(semantics, c); + } + } + + @Override + public void eraseRange(int index, int count, int validSize) { + if (m_bReadonly) + throw new GeometryException("invalid_call"); + + if (index + count > m_size) + throw new GeometryException("invalid_call"); + + System.arraycopy(m_buffer, index + count, m_buffer, index, validSize + - (index + count)); + m_size -= count; + } + + @Override + public void readRange(int srcStart, int count, ByteBuffer dst, + int dstOffset, boolean bForward) { + if (srcStart < 0 || count < 0 || dstOffset < 0 + || size() < count + srcStart) + throw new IllegalArgumentException(); + + final int elmSize = NumberUtils.sizeOf((double) 0); + + if (dst.capacity() < (int) (dstOffset + elmSize * count)) + throw new IllegalArgumentException(); + + if (count == 0) + return; + + int j = srcStart; + if (!bForward) + j += count - 1; + + final int dj = bForward ? 1 : -1; + + int offset = dstOffset; + for (int i = 0; i < count; i++, offset += elmSize) { + dst.putFloat(offset, m_buffer[j]); + j += dj; + } + + } + + @Override + public void reverseRange(int index, int count, int stride) { + if (m_bReadonly) + throw new GeometryException("invalid_call"); + + if (stride < 1 || count % stride != 0) + throw new GeometryException("invalid_call"); + + int cIterations = count >> 1; + int n = count; + + for (int i = 0; i < cIterations; i += stride) { + n -= stride; + + for (int s = 0; s < stride; s++) { + float temp = m_buffer[index + i + s]; + m_buffer[index + i + s] = m_buffer[index + n + s]; + m_buffer[index + n + s] = temp; + } + } + } + + @Override + public void setRange(double value, int start, int count) { + if (start < 0 || count < 0 || start < 0 || count + start > size()) + throw new IllegalArgumentException(); + + float v = (float) value; + for (int i = start, n = start + count; i < n; i++) + write(i, v); + } + + @Override + public void writeRange(int startElement, int count, + AttributeStreamBase _src, int srcStart, boolean bForward, int stride) { + if (startElement < 0 || count < 0 || srcStart < 0) + throw new IllegalArgumentException(); + + if (!bForward && (stride <= 0 || (count % stride != 0))) + throw new IllegalArgumentException(); + + AttributeStreamOfFloat src = (AttributeStreamOfFloat) _src; // the input + // type must + // match + + if (src.size() < (int) (srcStart + count)) + throw new IllegalArgumentException(); + + if (count == 0) + return; + + if (size() < count + startElement) + resize(count + startElement); + + if (_src == (AttributeStreamBase) this) { + _selfWriteRangeImpl(startElement, count, srcStart, bForward, stride); + return; + } + + if (bForward) { + int j = startElement; + int offset = srcStart; + for (int i = 0; i < count; i++) { + m_buffer[j] = src.m_buffer[offset]; + j++; + offset++; + } + } else { + int j = startElement; + int offset = srcStart + count - stride; + if (stride == 1) { + for (int i = 0; i < count; i++) { + m_buffer[j] = src.m_buffer[offset]; + j++; + offset--; + } + } else { + for (int i = 0, n = count / stride; i < n; i++) { + for (int k = 0; k < stride; k++) + m_buffer[j + k] = src.m_buffer[offset + k]; + + j += stride; + offset -= stride; + } + } + } + } + + private void _selfWriteRangeImpl(int toElement, int count, int fromElement, + boolean bForward, int stride) { + + // writing from to this stream. + if (bForward) { + if (toElement == fromElement) + return; + } + + int offset; + int j; + int dj; + + if (fromElement < toElement) { + offset = fromElement + count - stride; + j = toElement + count - stride; + for (int i = 0, n = count / 2; i < n; i++) { + for (int k = 0; k < stride; k++) { + m_buffer[j + k] = m_buffer[offset + k]; + } + j -= stride; + offset -= stride; + } + } else { + offset = fromElement; + j = toElement; + dj = 1; + for (int i = 0; i < count; i++) { + m_buffer[j] = m_buffer[offset]; + j += 1; + offset++; + } + } + + if (!bForward) { + // reverse what we written + j = toElement; + offset = toElement + count - stride; + dj = stride; + for (int i = 0, n = count / 2; i < n; i++) { + for (int k = 0; k < stride; k++) { + float v = m_buffer[j + k]; + m_buffer[j + k] = m_buffer[offset + k]; + m_buffer[offset + k] = v; + } + j += stride; + offset -= stride; + } + } + } + + @Override + public void writeRange(int startElement, int count, ByteBuffer src, + int offsetBytes, boolean bForward) { + if (startElement < 0 || count < 0 || offsetBytes < 0) + throw new IllegalArgumentException(); + + final int elmSize = NumberUtils.sizeOf((double) 0); + if (src.capacity() < (int) (offsetBytes + elmSize * count)) + throw new IllegalArgumentException(); + + if (count == 0) + return; + + if (size() < count + startElement) + resize(count + startElement); + + int j = startElement; + if (!bForward) + j += count - 1; + + final int dj = bForward ? 1 : -1; + + int offset = offsetBytes; + for (int i = 0; i < count; i++, offset += elmSize) { + m_buffer[j] = src.getFloat(offset); + j += dj; + } + + } +} diff --git a/src/com/esri/core/geometry/AttributeStreamOfInt16.java b/src/com/esri/core/geometry/AttributeStreamOfInt16.java new file mode 100644 index 00000000..5e9cf181 --- /dev/null +++ b/src/com/esri/core/geometry/AttributeStreamOfInt16.java @@ -0,0 +1,592 @@ +/* + Copyright 1995-2013 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ + + +package com.esri.core.geometry; + +import com.esri.core.geometry.VertexDescription.Persistence; +import java.nio.ByteBuffer; + +final class AttributeStreamOfInt16 extends AttributeStreamBase { + + short[] m_buffer = null; + int m_size; + + public int size() { + return m_size; + } + + public void reserve(int reserve)// only in Java + { + if (reserve <= 0) + return; + if (m_buffer == null) + m_buffer = new short[reserve]; + else { + if (reserve <= m_buffer.length) + return; + short[] buf = new short[reserve]; + System.arraycopy(m_buffer, 0, buf, 0, m_size); + m_buffer = buf; + } + + } + + public AttributeStreamOfInt16(int size) { + int sz = size; + if (sz < 2) + sz = 2; + m_buffer = new short[sz]; + m_size = size; + } + + public AttributeStreamOfInt16(int size, short defaultValue) { + int sz = size; + if (sz < 2) + sz = 2; + m_buffer = new short[sz]; + m_size = size; + for (int i = 0; i < size; i++) + m_buffer[i] = defaultValue; + } + + public AttributeStreamOfInt16(AttributeStreamOfInt16 other) { + m_buffer = other.m_buffer.clone(); + m_size = other.m_size; + } + + public AttributeStreamOfInt16(AttributeStreamOfInt16 other, int maxSize) { + m_size = other.size(); + if (m_size > maxSize) + m_size = maxSize; + int sz = m_size; + if (sz < 2) + sz = 2; + m_buffer = new short[sz]; + System.arraycopy(other.m_buffer, 0, m_buffer, 0, m_size); + } + + /** + * Reads a value from the buffer at given offset. + * + * @param offset + * is the element number in the stream. + */ + public short read(int offset) { + return m_buffer[offset]; + } + + /** + * Overwrites given element with new value. + * + * @param offset + * is the element number in the stream. + * @param value + * is the value to write. + */ + public void write(int offset, short value) { + if (m_bReadonly) { + throw new RuntimeException("invalid_call"); + } + m_buffer[offset] = value; + } + + /** + * Adds a new value at the end of the stream. + * + * @param offset + * is the element number in the stream. + * @param value + * is the value to write. + */ + public void add(short v) { + resize(m_size + 1); + m_buffer[m_size - 1] = v; + } + + @Override + public AttributeStreamBase restrictedClone(int maxsize) { + int len = m_size; + int newSize = maxsize < len ? maxsize : len; + short[] newBuffer = new short[newSize]; + System.arraycopy(m_buffer, 0, newBuffer, 0, newSize); + m_buffer = newBuffer; + m_size = newSize; + return this; + } + + @Override + public int virtualSize() { + return size(); + } + + @Override + public int getPersistence() { + return Persistence.enumInt16; + } + + @Override + public double readAsDbl(int offset) { + return read(offset); + } + + @Override + public int readAsInt(int offset) { + return (int) read(offset); + } + + @Override + public long readAsInt64(int offset) { + return (long) read(offset); + } + + @Override + public void resize(int newSize) { + if (m_bLockedInSize) + throw new GeometryException( + "invalid call. Attribute Stream is locked and cannot be resized."); + + if (newSize <= m_size) { + if ((newSize * 5) / 4 < m_buffer.length) {// decrease when the 25% + // margin is exceeded + short[] newBuffer = new short[newSize]; + System.arraycopy(m_buffer, 0, newBuffer, 0, newSize); + m_buffer = newBuffer; + } + m_size = newSize; + } else { + if (newSize > m_buffer.length) { + int sz = (newSize < 64) ? Math.max(newSize * 2, 4) + : (newSize * 5) / 4; + short[] newBuffer = new short[sz]; + System.arraycopy(m_buffer, 0, newBuffer, 0, m_size); + m_buffer = newBuffer; + } + + m_size = newSize; + } + } + + @Override + public void resizePreserveCapacity(int newSize)// java only method + { + if (m_buffer == null || newSize > m_buffer.length) + resize(newSize); + if (m_bLockedInSize) + throw new GeometryException( + "invalid call. Attribute Stream is locked and cannot be resized."); + + m_size = newSize; + } + + @Override + public void resize(int newSize, double defaultValue) { + if (m_bLockedInSize) + throw new GeometryException( + "invalid call. Attribute Stream is locked and cannot be resized."); + if (newSize <= m_size) { + if ((newSize * 5) / 4 < m_buffer.length) {// decrease when the 25% + // margin is exceeded + short[] newBuffer = new short[newSize]; + System.arraycopy(m_buffer, 0, newBuffer, 0, newSize); + m_buffer = newBuffer; + } + m_size = newSize; + } else { + if (newSize > m_buffer.length) { + int sz = (newSize < 64) ? Math.max(newSize * 2, 4) + : (newSize * 5) / 4; + short[] newBuffer = new short[sz]; + System.arraycopy(m_buffer, 0, newBuffer, 0, m_size); + m_buffer = newBuffer; + } + + for (int i = m_size; i < newSize; i++) + m_buffer[i] = (short) defaultValue; + + m_size = newSize; + } + } + + @Override + public void writeAsDbl(int offset, double d) { + write(offset, (short) d); + } + + @Override + public void writeAsInt64(int offset, long d) { + write(offset, (short) d); + } + + @Override + public void writeAsInt(int offset, int d) { + write(offset, (short) d); + } + + // @Override + // public void writeRange(int srcStart, int count, ByteBuffer dst, + // int dstOffsetBytes) { + // // TODO Auto-generated method stub + // + // } + + @Override + public int calculateHashImpl(int hashCode, int start, int end) { + for (int i = start, n = size(); i < n && i < end; i++) + hashCode = NumberUtils.hash(hashCode, read(i)); + + return hashCode; + } + + @Override + public boolean equals(AttributeStreamBase other, int start, int end) { + if (other == null) + return false; + + if (!(other instanceof AttributeStreamOfInt16)) + return false; + + AttributeStreamOfInt16 _other = (AttributeStreamOfInt16) other; + + int size = size(); + int sizeOther = _other.size(); + + if (end > size || end > sizeOther && (size != sizeOther)) + return false; + + if (end > size) + end = size; + + for (int i = start; i < end; i++) + if (read(i) != _other.read(i)) + return false; + + return true; + } + + @Override + public void addRange(AttributeStreamBase src, int start, int count, + boolean bForward, int stride) { + if (m_bReadonly) + throw new GeometryException("invalid_call"); + + if (!bForward && (stride < 1 || count % stride != 0)) + throw new IllegalArgumentException(); + + int oldSize = m_size; + int newSize = oldSize + count; + resize(newSize); + + if (bForward) { + System.arraycopy(((AttributeStreamOfDbl) src).m_buffer, start, + m_buffer, oldSize, count); + } else { + int n = count; + + for (int i = 0; i < count; i += stride) { + n -= stride; + + for (int s = 0; s < stride; s++) { + m_buffer[oldSize + i + s] = ((AttributeStreamOfInt16) src).m_buffer[start + + n + s]; + } + } + } + } + + @Override + public void insertRange(int start, AttributeStreamBase src, int srcStart, + int count, boolean bForward, int stride, int validSize) { + if (m_bReadonly) + throw new GeometryException("invalid_call"); + + if (!bForward && (stride < 1 || count % stride != 0)) + throw new IllegalArgumentException(); + + System.arraycopy(m_buffer, start, m_buffer, start + count, validSize + - start); + + if (m_buffer == ((AttributeStreamOfInt16) src).m_buffer) { + if (start < srcStart) + srcStart += count; + } + + if (bForward) { + System.arraycopy(((AttributeStreamOfInt16) src).m_buffer, srcStart, + m_buffer, start, count); + } else { + int n = count; + + for (int i = 0; i < count; i += stride) { + n -= stride; + + for (int s = 0; s < stride; s++) { + m_buffer[start + i + s] = ((AttributeStreamOfInt16) src).m_buffer[srcStart + + n + s]; + } + } + } + } + + @Override + public void insertRange(int start, double value, int count, int validSize) { + if (m_bReadonly) + throw new GeometryException("invalid_call"); + + System.arraycopy(m_buffer, start, m_buffer, start + count, validSize + - start); + + short v = (short) value; + for (int i = 0; i < count; i++) { + m_buffer[start + i] = v; + } + } + + @Override + public void insertAttributes(int start, Point pt, int semantics, + int validSize) { + if (m_bReadonly) + throw new GeometryException("invalid_call"); + + int comp = VertexDescription.getComponentCount(semantics); + + System.arraycopy(m_buffer, start, m_buffer, start + comp, validSize + - start); + + for (int c = 0; c < comp; c++) { + m_buffer[start + c] = (short) pt.getAttributeAsDbl(semantics, c); + } + } + + @Override + public void eraseRange(int index, int count, int validSize) { + if (m_bReadonly) + throw new GeometryException("invalid_call"); + + if (index + count > m_size) + throw new GeometryException("invalid_call"); + + System.arraycopy(m_buffer, index + count, m_buffer, index, validSize + - (index + count)); + m_size -= count; + } + + @Override + public void readRange(int srcStart, int count, ByteBuffer dst, + int dstOffset, boolean bForward) { + if (srcStart < 0 || count < 0 || dstOffset < 0 + || size() < count + srcStart) + throw new IllegalArgumentException(); + + final int elmSize = NumberUtils.sizeOf((double) 0); + + if (dst.capacity() < (int) (dstOffset + elmSize * count)) + throw new IllegalArgumentException(); + + if (count == 0) + return; + + int j = srcStart; + if (!bForward) + j += count - 1; + + final int dj = bForward ? 1 : -1; + + int offset = dstOffset; + for (int i = 0; i < count; i++, offset += elmSize) { + dst.putShort(offset, m_buffer[j]); + j += dj; + } + + } + + @Override + public void reverseRange(int index, int count, int stride) { + if (m_bReadonly) + throw new GeometryException("invalid_call"); + + if (stride < 1 || count % stride != 0) + throw new GeometryException("invalid_call"); + + int cIterations = count >> 1; + int n = count; + + for (int i = 0; i < cIterations; i += stride) { + n -= stride; + + for (int s = 0; s < stride; s++) { + short temp = m_buffer[index + i + s]; + m_buffer[index + i + s] = m_buffer[index + n + s]; + m_buffer[index + n + s] = temp; + } + } + } + + @Override + public void setRange(double value, int start, int count) { + if (start < 0 || count < 0 || start < 0 || count + start > size()) + throw new IllegalArgumentException(); + + short v = (short) value; + for (int i = start, n = start + count; i < n; i++) + write(i, v); + } + + @Override + public void writeRange(int startElement, int count, + AttributeStreamBase _src, int srcStart, boolean bForward, int stride) { + if (startElement < 0 || count < 0 || srcStart < 0) + throw new IllegalArgumentException(); + + if (!bForward && (stride <= 0 || (count % stride != 0))) + throw new IllegalArgumentException(); + + AttributeStreamOfInt16 src = (AttributeStreamOfInt16) _src; // the input + // type must + // match + + if (src.size() < (int) (srcStart + count)) + throw new IllegalArgumentException(); + + if (count == 0) + return; + + if (size() < count + startElement) + resize(count + startElement); + + if (_src == (AttributeStreamBase) this) { + _selfWriteRangeImpl(startElement, count, srcStart, bForward, stride); + return; + } + + if (bForward) { + int j = startElement; + int offset = srcStart; + for (int i = 0; i < count; i++) { + m_buffer[j] = src.m_buffer[offset]; + j++; + offset++; + } + } else { + int j = startElement; + int offset = srcStart + count - stride; + if (stride == 1) { + for (int i = 0; i < count; i++) { + m_buffer[j] = src.m_buffer[offset]; + j++; + offset--; + } + } else { + for (int i = 0, n = count / stride; i < n; i++) { + for (int k = 0; k < stride; k++) + m_buffer[j + k] = src.m_buffer[offset + k]; + + j += stride; + offset -= stride; + } + } + } + } + + private void _selfWriteRangeImpl(int toElement, int count, int fromElement, + boolean bForward, int stride) { + + // writing from to this stream. + if (bForward) { + if (toElement == fromElement) + return; + } + + int offset; + int j; + int dj; + + if (fromElement < toElement) { + offset = fromElement + count - stride; + j = toElement + count - stride; + for (int i = 0, n = count / 2; i < n; i++) { + for (int k = 0; k < stride; k++) { + m_buffer[j + k] = m_buffer[offset + k]; + } + j -= stride; + offset -= stride; + } + } else { + offset = fromElement; + j = toElement; + dj = 1; + for (int i = 0; i < count; i++) { + m_buffer[j] = m_buffer[offset]; + j += 1; + offset++; + } + } + + if (!bForward) { + // reverse what we written + j = toElement; + offset = toElement + count - stride; + dj = stride; + for (int i = 0, n = count / 2; i < n; i++) { + for (int k = 0; k < stride; k++) { + short v = m_buffer[j + k]; + m_buffer[j + k] = m_buffer[offset + k]; + m_buffer[offset + k] = v; + } + j += stride; + offset -= stride; + } + } + } + + @Override + public void writeRange(int startElement, int count, ByteBuffer src, + int offsetBytes, boolean bForward) { + if (startElement < 0 || count < 0 || offsetBytes < 0) + throw new IllegalArgumentException(); + + final int elmSize = NumberUtils.sizeOf((double) 0); + if (src.capacity() < (int) (offsetBytes + elmSize * count)) + throw new IllegalArgumentException(); + + if (count == 0) + return; + + if (size() < count + startElement) + resize(count + startElement); + + int j = startElement; + if (!bForward) + j += count - 1; + + final int dj = bForward ? 1 : -1; + + int offset = offsetBytes; + for (int i = 0; i < count; i++, offset += elmSize) { + m_buffer[j] = src.getShort(offset); + j += dj; + } + + } +} diff --git a/src/com/esri/core/geometry/AttributeStreamOfInt32.java b/src/com/esri/core/geometry/AttributeStreamOfInt32.java new file mode 100644 index 00000000..0f7488ec --- /dev/null +++ b/src/com/esri/core/geometry/AttributeStreamOfInt32.java @@ -0,0 +1,714 @@ +/* + Copyright 1995-2013 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ + + +package com.esri.core.geometry; + +import com.esri.core.geometry.VertexDescription.Persistence; +import java.nio.ByteBuffer; +import java.util.Arrays; + +final class AttributeStreamOfInt32 extends AttributeStreamBase { + + int[] m_buffer = null; + int m_size; + + public void reserve(int reserve)// only in Java + { + if (reserve <= 0) + return; + if (m_buffer == null) + m_buffer = new int[reserve]; + else { + if (reserve <= m_buffer.length) + return; + int[] buf = new int[reserve]; + System.arraycopy(m_buffer, 0, buf, 0, m_buffer.length); + m_buffer = buf; + } + + } + + public int size() { + return m_size; + } + + public AttributeStreamOfInt32(int size) { + int sz = size; + if (sz < 2) + sz = 2; + m_buffer = new int[sz]; + m_size = size; + } + + public AttributeStreamOfInt32(int size, int defaultValue) { + int sz = size; + if (sz < 2) + sz = 2; + m_buffer = new int[sz]; + m_size = size; + Arrays.fill(m_buffer, 0, size, defaultValue); + // for (int i = 0; i < size; i++) + // m_buffer[i] = defaultValue; + } + + public AttributeStreamOfInt32(AttributeStreamOfInt32 other) { + m_buffer = other.m_buffer.clone(); + m_size = other.m_size; + } + + public AttributeStreamOfInt32(AttributeStreamOfInt32 other, int maxSize) { + m_size = other.size(); + if (m_size > maxSize) + m_size = maxSize; + int sz = m_size; + if (sz < 2) + sz = 2; + m_buffer = new int[sz]; + System.arraycopy(other.m_buffer, 0, m_buffer, 0, m_size); + } + + /** + * Reads a value from the buffer at given offset. + * + * @param offset + * is the element number in the stream. + */ + public int read(int offset) { + return m_buffer[offset]; + } + + public int get(int offset) { + return m_buffer[offset]; + } + + /** + * Overwrites given element with new value. + * + * @param offset + * is the element number in the stream. + * @param value + * is the value to write. + */ + public void write(int offset, int value) { + if (m_bReadonly) { + throw new RuntimeException("invalid_call"); + } + m_buffer[offset] = value; + } + + public void set(int offset, int value) { + if (m_bReadonly) { + throw new RuntimeException("invalid_call"); + } + m_buffer[offset] = value; + } + + /** + * Adds a new value at the end of the stream. + * + * @param offset + * is the element number in the stream. + * @param value + * is the value to write. + */ + public void add(int v) { + resize(m_size + 1); + m_buffer[m_size - 1] = v; + } + + @Override + public AttributeStreamBase restrictedClone(int maxsize) { + int len = m_size; + int newSize = maxsize < len ? maxsize : len; + int[] newBuffer = new int[newSize]; + System.arraycopy(m_buffer, 0, newBuffer, 0, newSize); + m_buffer = newBuffer; + m_size = newSize; + return this; + } + + @Override + public int virtualSize() { + return size(); + } + + @Override + public int getPersistence() { + return Persistence.enumInt32; + } + + @Override + public double readAsDbl(int offset) { + return read(offset); + } + + @Override + public int readAsInt(int offset) { + return (int) read(offset); + } + + @Override + public long readAsInt64(int offset) { + return (long) read(offset); + } + + @Override + public void resize(int newSize) { + if (m_bLockedInSize) + throw new GeometryException( + "invalid call. Attribute Stream is locked and cannot be resized."); + + if (newSize <= m_size) { + if ((newSize * 5) / 4 < m_buffer.length) {// decrease when the 25% + // margin is exceeded + int[] newBuffer = new int[newSize]; + System.arraycopy(m_buffer, 0, newBuffer, 0, newSize); + m_buffer = newBuffer; + } + m_size = newSize; + } else { + if (newSize > m_buffer.length) { + int sz = (newSize < 64) ? Math.max(newSize * 2, 4) + : (newSize * 5) / 4; + int[] newBuffer = new int[sz]; + System.arraycopy(m_buffer, 0, newBuffer, 0, m_size); + m_buffer = newBuffer; + } + + m_size = newSize; + } + } + + @Override + public void resizePreserveCapacity(int newSize)// java only method + { + if (m_buffer == null || newSize > m_buffer.length) + resize(newSize); + if (m_bLockedInSize) + throw new GeometryException( + "invalid call. Attribute Stream is locked and cannot be resized."); + + m_size = newSize; + } + + @Override + public void resize(int newSize, double defaultValue) { + if (m_bLockedInSize) + throw new GeometryException( + "invalid call. Attribute Stream is locked and cannot be resized."); + if (newSize <= m_size) { + if ((newSize * 5) / 4 < m_buffer.length) {// decrease when the 25% + // margin is exceeded + int[] newBuffer = new int[newSize]; + System.arraycopy(m_buffer, 0, newBuffer, 0, newSize); + m_buffer = newBuffer; + } + m_size = newSize; + } else { + if (newSize > m_buffer.length) { + int sz = (newSize < 64) ? Math.max(newSize * 2, 4) + : (newSize * 5) / 4; + int[] newBuffer = new int[sz]; + System.arraycopy(m_buffer, 0, newBuffer, 0, m_size); + m_buffer = newBuffer; + } + + Arrays.fill(m_buffer, m_size, newSize, (int) defaultValue); + // for (int i = m_size; i < newSize; i++) + // m_buffer[i] = (int)defaultValue; + + m_size = newSize; + } + } + + @Override + public void writeAsDbl(int offset, double d) { + write(offset, (int) d); + } + + @Override + public void writeAsInt64(int offset, long d) { + write(offset, (int) d); + } + + @Override + public void writeAsInt(int offset, int d) { + write(offset, (int) d); + } + + // @Override + // public void writeRange(int srcStart, int count, ByteBuffer dst, + // int dstOffsetBytes) { + // // TODO Auto-generated method stub + // + // } + + @Override + public int calculateHashImpl(int hashCode, int start, int end) { + for (int i = start, n = size(); i < n && i < end; i++) + hashCode = NumberUtils.hash(hashCode, read(i)); + + return hashCode; + } + + @Override + public boolean equals(AttributeStreamBase other, int start, int end) { + if (other == null) + return false; + + if (!(other instanceof AttributeStreamOfInt32)) + return false; + + AttributeStreamOfInt32 _other = (AttributeStreamOfInt32) other; + + int size = size(); + int sizeOther = _other.size(); + + if (end > size || end > sizeOther && (size != sizeOther)) + return false; + + if (end > size) + end = size; + + for (int i = start; i < end; i++) + if (read(i) != _other.read(i)) + return false; + + return true; + } + + public int getLast() { + return m_buffer[m_size - 1]; + } + + public void removeLast() { + resize(m_size - 1); + } + + // Finds element in the unsorted array and returns its index. Returns -1 if + // the element could not be found. + int findElement(int value) { + for (int i = 0, n = m_size; i < n; i++) { + if (m_buffer[i] == value) + return i; + } + return -1; + } + + // Returns True if element could be found in the array. + boolean hasElement(int value) { + return findElement(value) >= 0; + } + + // Removes the element from the array in constant time. + // It moves the last element of the array to the index and decrements the + // array size by 1. + void popElement(int index) { + assert (index >= 0 && index < m_size); + if (index < m_size - 1) { + m_buffer[index] = m_buffer[m_size - 1]; + } + + resize(m_size - 1); + } + + @Override + public void addRange(AttributeStreamBase src, int start, int count, + boolean bForward, int stride) { + if (m_bReadonly) + throw new GeometryException("invalid_call"); + + if (!bForward && (stride < 1 || count % stride != 0)) + throw new IllegalArgumentException(); + + int oldSize = m_size; + int newSize = oldSize + count; + resize(newSize); + + if (bForward) { + System.arraycopy(((AttributeStreamOfDbl) src).m_buffer, start, + m_buffer, oldSize, count); + } else { + int n = count; + + for (int i = 0; i < count; i += stride) { + n -= stride; + + for (int s = 0; s < stride; s++) { + m_buffer[oldSize + i + s] = ((AttributeStreamOfInt32) src).m_buffer[start + + n + s]; + } + } + } + } + + @Override + public void insertRange(int start, AttributeStreamBase src, int srcStart, + int count, boolean bForward, int stride, int validSize) { + if (m_bReadonly) + throw new GeometryException("invalid_call"); + + if (!bForward && (stride < 1 || count % stride != 0)) + throw new IllegalArgumentException(); + + System.arraycopy(m_buffer, start, m_buffer, start + count, validSize + - start); + + if (m_buffer == ((AttributeStreamOfInt32) src).m_buffer) { + if (start < srcStart) + srcStart += count; + } + + if (bForward) { + System.arraycopy(((AttributeStreamOfInt32) src).m_buffer, srcStart, + m_buffer, start, count); + } else { + int n = count; + + for (int i = 0; i < count; i += stride) { + n -= stride; + + for (int s = 0; s < stride; s++) { + m_buffer[start + i + s] = ((AttributeStreamOfInt32) src).m_buffer[srcStart + + n + s]; + } + } + } + } + + @Override + public void insertRange(int start, double value, int count, int validSize) { + if (m_bReadonly) + throw new GeometryException("invalid_call"); + + System.arraycopy(m_buffer, start, m_buffer, start + count, validSize + - start); + + int v = (int) value; + Arrays.fill(m_buffer, start, start + count, v); + // for (int i = 0; i < count; i++) + // { + // m_buffer[start + i] = v; + // } + } + + @Override + public void insertAttributes(int start, Point pt, int semantics, + int validSize) { + if (m_bReadonly) + throw new GeometryException("invalid_call"); + + int comp = VertexDescription.getComponentCount(semantics); + + System.arraycopy(m_buffer, start, m_buffer, start + comp, validSize + - start); + + for (int c = 0; c < comp; c++) { + m_buffer[start + c] = (int) pt.getAttributeAsDbl(semantics, c); + } + } + + @Override + public void eraseRange(int index, int count, int validSize) { + if (m_bReadonly) + throw new GeometryException("invalid_call"); + + if (index + count > m_size) + throw new GeometryException("invalid_call"); + + System.arraycopy(m_buffer, index + count, m_buffer, index, validSize + - (index + count)); + m_size -= count; + } + + @Override + public void readRange(int srcStart, int count, ByteBuffer dst, + int dstOffset, boolean bForward) { + if (srcStart < 0 || count < 0 || dstOffset < 0 + || size() < count + srcStart) + throw new IllegalArgumentException(); + + final int elmSize = NumberUtils.sizeOf((double) 0); + + if (dst.capacity() < (int) (dstOffset + elmSize * count)) + throw new IllegalArgumentException(); + + if (count == 0) + return; + + int j = srcStart; + if (!bForward) + j += count - 1; + final int dj = bForward ? 1 : -1; + int offset = dstOffset; + for (int i = 0; i < count; i++, offset += elmSize) { + dst.putInt(offset, m_buffer[j]); + j += dj; + } + } + + @Override + public void reverseRange(int index, int count, int stride) { + if (m_bReadonly) + throw new GeometryException("invalid_call"); + + if (stride < 1 || count % stride != 0) + throw new GeometryException("invalid_call"); + + int cIterations = count >> 1; + int n = count; + + for (int i = 0; i < cIterations; i += stride) { + n -= stride; + + for (int s = 0; s < stride; s++) { + int temp = m_buffer[index + i + s]; + m_buffer[index + i + s] = m_buffer[index + n + s]; + m_buffer[index + n + s] = temp; + } + } + } + + @Override + public void setRange(double value, int start, int count) { + if (start < 0 || count < 0 || start < 0 || count + start > size()) + throw new IllegalArgumentException(); + + int v = (int) value; + Arrays.fill(m_buffer, start, start + count, v); + // for (int i = start, n = start + count; i < n; i++) + // write(i, v); + } + + @Override + public void writeRange(int startElement, int count, + AttributeStreamBase _src, int srcStart, boolean bForward, int stride) { + if (startElement < 0 || count < 0 || srcStart < 0) + throw new IllegalArgumentException(); + + if (!bForward && (stride <= 0 || (count % stride != 0))) + throw new IllegalArgumentException(); + + AttributeStreamOfInt32 src = (AttributeStreamOfInt32) _src; // the input + // type must + // match + + if (src.size() < (int) (srcStart + count)) + throw new IllegalArgumentException(); + + if (count == 0) + return; + + if (size() < count + startElement) + resize(count + startElement); + + if (_src == (AttributeStreamBase) this) { + _selfWriteRangeImpl(startElement, count, srcStart, bForward, stride); + return; + } + + if (bForward) { + System.arraycopy(src.m_buffer, srcStart, m_buffer, startElement, + count); + // int j = startElement; + // int offset = srcStart; + // for (int i = 0; i < count; i++) + // { + // m_buffer[j] = src.m_buffer[offset]; + // j++; + // offset++; + // } + } else { + int j = startElement; + int offset = srcStart + count - stride; + if (stride == 1) { + for (int i = 0; i < count; i++) { + m_buffer[j] = src.m_buffer[offset]; + j++; + offset--; + } + } else { + for (int i = 0, n = count / stride; i < n; i++) { + for (int k = 0; k < stride; k++) + m_buffer[j + k] = src.m_buffer[offset + k]; + + j += stride; + offset -= stride; + } + } + } + } + + private void _selfWriteRangeImpl(int toElement, int count, int fromElement, + boolean bForward, int stride) { + + // writing from to this stream. + if (bForward) { + if (toElement == fromElement) + return; + } + + System.arraycopy(m_buffer, fromElement, m_buffer, toElement, count); + if (bForward) + return; + + // reverse what we written + int j = toElement; + int offset = toElement + count - stride; + int dj = stride; + for (int i = 0, n = count / 2; i < n; i++) { + for (int k = 0; k < stride; k++) { + int v = m_buffer[j + k]; + m_buffer[j + k] = m_buffer[offset + k]; + m_buffer[offset + k] = v; + } + j += stride; + offset -= stride; + } + } + + @Override + public void writeRange(int startElement, int count, ByteBuffer src, + int offsetBytes, boolean bForward) { + if (startElement < 0 || count < 0 || offsetBytes < 0) + throw new IllegalArgumentException(); + + final int elmSize = NumberUtils.sizeOf((double) 0); + if (src.capacity() < (int) (offsetBytes + elmSize * count)) + throw new IllegalArgumentException(); + + if (count == 0) + return; + + if (size() < count + startElement) + resize(count + startElement); + + int j = startElement; + if (!bForward) + j += count - 1; + + final int dj = bForward ? 1 : -1; + + int offset = offsetBytes; + for (int i = 0; i < count; i++, offset += elmSize) { + m_buffer[j] = src.getInt(offset); + j += dj; + } + + } + + static public abstract class IntComparator { + public abstract int compare(int v1, int v2); + }; + + static class RandomSeed { + public int random; + + public RandomSeed() { + random = 1973; + } + } + + public void Sort(int start, int end, IntComparator compare) { + if (end - start < 10) + insertionsort(start, end, compare); + else { + quicksort(start, end - 1, compare, new RandomSeed()); + } + } + + void insertionsort(int start, int end, IntComparator compare) { + for (int j = start; j < end; j++)// insertion sort + { + int key = m_buffer[j]; + int i = j - 1; + while (i >= start && compare.compare(m_buffer[i], key) > 0) { + m_buffer[i + 1] = m_buffer[i]; + i--; + } + m_buffer[i + 1] = key; + } + } + + void swap(int left, int right) { + int tmp = m_buffer[right]; + m_buffer[right] = m_buffer[left]; + m_buffer[left] = tmp; + } + + void quicksort(int leftIn, int rightIn, IntComparator compare, + RandomSeed seed) { + if (leftIn >= rightIn) + return; + + int left = leftIn; + int right = rightIn; + + while (true)// tail recursion loop + { + if (right - left < 9) { + insertionsort(left, right + 1, compare); + return; + } + // Select random index for the pivot + seed.random = NumberUtils.nextRand(seed.random); + long nom = ((long) (right - left)) * seed.random; + int pivotIndex = (int) (nom / NumberUtils.intMax()) + left; + // Get the pivot value + int pivotValue = m_buffer[pivotIndex]; + + // Start partition + // Move pivot to the right + swap(pivotIndex, right); + int storeIndex = left; + for (int i = left; i < right; i++) { + int elm = m_buffer[i]; + if (compare.compare(elm, pivotValue) <= 0) { + swap(storeIndex, i); + storeIndex = storeIndex + 1; + } + } + + // Move pivot to its final place + swap(storeIndex, right); + // End partition + + // Shorter part is regular recursion + // Longer part is tail recursion + if (storeIndex - left < right - storeIndex) { + quicksort(left, storeIndex - 1, compare, seed); + left = storeIndex + 1; + } else { + quicksort(storeIndex + 1, right, compare, seed); + right = storeIndex - 1; + } + + } + } + + public void sort(int start, int end) { + Arrays.sort(m_buffer, start, end); + } +} diff --git a/src/com/esri/core/geometry/AttributeStreamOfInt64.java b/src/com/esri/core/geometry/AttributeStreamOfInt64.java new file mode 100644 index 00000000..3498770b --- /dev/null +++ b/src/com/esri/core/geometry/AttributeStreamOfInt64.java @@ -0,0 +1,592 @@ +/* + Copyright 1995-2013 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ + + +package com.esri.core.geometry; + +import com.esri.core.geometry.VertexDescription.Persistence; +import java.nio.ByteBuffer; + +final class AttributeStreamOfInt64 extends AttributeStreamBase { + + long[] m_buffer = null; + int m_size; + + public int size() { + return m_size; + } + + public void reserve(int reserve)// only in Java + { + if (reserve <= 0) + return; + if (m_buffer == null) + m_buffer = new long[reserve]; + else { + if (reserve <= m_buffer.length) + return; + long[] buf = new long[reserve]; + System.arraycopy(m_buffer, 0, buf, 0, m_size); + m_buffer = buf; + } + + } + + public AttributeStreamOfInt64(int size) { + int sz = size; + if (sz < 2) + sz = 2; + m_buffer = new long[sz]; + m_size = size; + } + + public AttributeStreamOfInt64(int size, long defaultValue) { + int sz = size; + if (sz < 2) + sz = 2; + m_buffer = new long[sz]; + m_size = size; + for (int i = 0; i < size; i++) + m_buffer[i] = defaultValue; + } + + public AttributeStreamOfInt64(AttributeStreamOfInt64 other) { + m_buffer = other.m_buffer.clone(); + m_size = other.m_size; + } + + public AttributeStreamOfInt64(AttributeStreamOfInt64 other, int maxSize) { + m_size = other.size(); + if (m_size > maxSize) + m_size = maxSize; + int sz = m_size; + if (sz < 2) + sz = 2; + m_buffer = new long[sz]; + System.arraycopy(other.m_buffer, 0, m_buffer, 0, m_size); + } + + /** + * Reads a value from the buffer at given offset. + * + * @param offset + * is the element number in the stream. + */ + public long read(int offset) { + return m_buffer[offset]; + } + + /** + * Overwrites given element with new value. + * + * @param offset + * is the element number in the stream. + * @param value + * is the value to write. + */ + public void write(int offset, long value) { + if (m_bReadonly) { + throw new RuntimeException("invalid_call"); + } + m_buffer[offset] = value; + } + + /** + * Adds a new value at the end of the stream. + * + * @param offset + * is the element number in the stream. + * @param value + * is the value to write. + */ + public void add(long v) { + resize(m_size + 1); + m_buffer[m_size - 1] = v; + } + + @Override + public AttributeStreamBase restrictedClone(int maxsize) { + int len = m_size; + int newSize = maxsize < len ? maxsize : len; + long[] newBuffer = new long[newSize]; + System.arraycopy(m_buffer, 0, newBuffer, 0, newSize); + m_buffer = newBuffer; + m_size = newSize; + return this; + } + + @Override + public int virtualSize() { + return size(); + } + + @Override + public int getPersistence() { + return Persistence.enumInt64; + } + + @Override + public double readAsDbl(int offset) { + return read(offset); + } + + @Override + public int readAsInt(int offset) { + return (int) read(offset); + } + + @Override + public long readAsInt64(int offset) { + return (long) read(offset); + } + + @Override + public void resize(int newSize) { + if (m_bLockedInSize) + throw new GeometryException( + "invalid call. Attribute Stream is locked and cannot be resized."); + + if (newSize <= m_size) { + if ((newSize * 5) / 4 < m_buffer.length) {// decrease when the 25% + // margin is exceeded + long[] newBuffer = new long[newSize]; + System.arraycopy(m_buffer, 0, newBuffer, 0, newSize); + m_buffer = newBuffer; + } + m_size = newSize; + } else { + if (newSize > m_buffer.length) { + int sz = (newSize < 64) ? Math.max(newSize * 2, 4) + : (newSize * 5) / 4; + long[] newBuffer = new long[sz]; + System.arraycopy(m_buffer, 0, newBuffer, 0, m_size); + m_buffer = newBuffer; + } + + m_size = newSize; + } + } + + @Override + public void resizePreserveCapacity(int newSize)// java only method + { + if (m_buffer == null || newSize > m_buffer.length) + resize(newSize); + if (m_bLockedInSize) + throw new GeometryException( + "invalid call. Attribute Stream is locked and cannot be resized."); + + m_size = newSize; + } + + @Override + public void resize(int newSize, double defaultValue) { + if (m_bLockedInSize) + throw new GeometryException( + "invalid call. Attribute Stream is locked and cannot be resized."); + if (newSize <= m_size) { + if ((newSize * 5) / 4 < m_buffer.length) {// decrease when the 25% + // margin is exceeded + long[] newBuffer = new long[newSize]; + System.arraycopy(m_buffer, 0, newBuffer, 0, newSize); + m_buffer = newBuffer; + } + m_size = newSize; + } else { + if (newSize > m_buffer.length) { + int sz = (newSize < 64) ? Math.max(newSize * 2, 4) + : (newSize * 5) / 4; + long[] newBuffer = new long[sz]; + System.arraycopy(m_buffer, 0, newBuffer, 0, m_size); + m_buffer = newBuffer; + } + + for (int i = m_size; i < newSize; i++) + m_buffer[i] = (long) defaultValue; + + m_size = newSize; + } + } + + @Override + public void writeAsDbl(int offset, double d) { + write(offset, (long) d); + } + + @Override + public void writeAsInt64(int offset, long d) { + write(offset, (long) d); + } + + @Override + public void writeAsInt(int offset, int d) { + write(offset, (long) d); + } + + // @Override + // public void writeRange(int srcStart, int count, ByteBuffer dst, + // int dstOffsetBytes) { + // // TODO Auto-generated method stub + // + // } + + @Override + public int calculateHashImpl(int hashCode, int start, int end) { + for (int i = start, n = size(); i < n && i < end; i++) + hashCode = NumberUtils.hash(hashCode, read(i)); + + return hashCode; + } + + @Override + public boolean equals(AttributeStreamBase other, int start, int end) { + if (other == null) + return false; + + if (!(other instanceof AttributeStreamOfInt64)) + return false; + + AttributeStreamOfInt64 _other = (AttributeStreamOfInt64) other; + + int size = size(); + int sizeOther = _other.size(); + + if (end > size || end > sizeOther && (size != sizeOther)) + return false; + + if (end > size) + end = size; + + for (int i = start; i < end; i++) + if (read(i) != _other.read(i)) + return false; + + return true; + } + + @Override + public void addRange(AttributeStreamBase src, int start, int count, + boolean bForward, int stride) { + if (m_bReadonly) + throw new GeometryException("invalid_call"); + + if (!bForward && (stride < 1 || count % stride != 0)) + throw new IllegalArgumentException(); + + int oldSize = m_size; + int newSize = oldSize + count; + resize(newSize); + + if (bForward) { + System.arraycopy(((AttributeStreamOfDbl) src).m_buffer, start, + m_buffer, oldSize, count); + } else { + int n = count; + + for (int i = 0; i < count; i += stride) { + n -= stride; + + for (int s = 0; s < stride; s++) { + m_buffer[oldSize + i + s] = ((AttributeStreamOfInt64) src).m_buffer[start + + n + s]; + } + } + } + } + + @Override + public void insertRange(int start, AttributeStreamBase src, int srcStart, + int count, boolean bForward, int stride, int validSize) { + if (m_bReadonly) + throw new GeometryException("invalid_call"); + + if (!bForward && (stride < 1 || count % stride != 0)) + throw new IllegalArgumentException(); + + System.arraycopy(m_buffer, start, m_buffer, start + count, validSize + - start); + + if (m_buffer == ((AttributeStreamOfInt64) src).m_buffer) { + if (start < srcStart) + srcStart += count; + } + + if (bForward) { + System.arraycopy(((AttributeStreamOfInt64) src).m_buffer, srcStart, + m_buffer, start, count); + } else { + int n = count; + + for (int i = 0; i < count; i += stride) { + n -= stride; + + for (int s = 0; s < stride; s++) { + m_buffer[start + i + s] = ((AttributeStreamOfInt64) src).m_buffer[srcStart + + n + s]; + } + } + } + } + + @Override + public void insertRange(int start, double value, int count, int validSize) { + if (m_bReadonly) + throw new GeometryException("invalid_call"); + + System.arraycopy(m_buffer, start, m_buffer, start + count, validSize + - start); + + long v = (long) value; + for (int i = 0; i < count; i++) { + m_buffer[start + i] = v; + } + } + + @Override + public void insertAttributes(int start, Point pt, int semantics, + int validSize) { + if (m_bReadonly) + throw new GeometryException("invalid_call"); + + int comp = VertexDescription.getComponentCount(semantics); + + System.arraycopy(m_buffer, start, m_buffer, start + comp, validSize + - start); + + for (int c = 0; c < comp; c++) { + m_buffer[start + c] = (long) pt.getAttributeAsDbl(semantics, c); + } + } + + @Override + public void eraseRange(int index, int count, int validSize) { + if (m_bReadonly) + throw new GeometryException("invalid_call"); + + if (index + count > m_size) + throw new GeometryException("invalid_call"); + + System.arraycopy(m_buffer, index + count, m_buffer, index, validSize + - (index + count)); + m_size -= count; + } + + @Override + public void readRange(int srcStart, int count, ByteBuffer dst, + int dstOffset, boolean bForward) { + if (srcStart < 0 || count < 0 || dstOffset < 0 + || size() < count + srcStart) + throw new IllegalArgumentException(); + + final int elmSize = NumberUtils.sizeOf((double) 0); + + if (dst.capacity() < (int) (dstOffset + elmSize * count)) + throw new IllegalArgumentException(); + + if (count == 0) + return; + + int j = srcStart; + if (!bForward) + j += count - 1; + + final int dj = bForward ? 1 : -1; + + int offset = dstOffset; + for (int i = 0; i < count; i++, offset += elmSize) { + dst.putLong(offset, m_buffer[j]); + j += dj; + } + + } + + @Override + public void reverseRange(int index, int count, int stride) { + if (m_bReadonly) + throw new GeometryException("invalid_call"); + + if (stride < 1 || count % stride != 0) + throw new GeometryException("invalid_call"); + + int cIterations = count >> 1; + int n = count; + + for (int i = 0; i < cIterations; i += stride) { + n -= stride; + + for (int s = 0; s < stride; s++) { + long temp = m_buffer[index + i + s]; + m_buffer[index + i + s] = m_buffer[index + n + s]; + m_buffer[index + n + s] = temp; + } + } + } + + @Override + public void setRange(double value, int start, int count) { + if (start < 0 || count < 0 || start < 0 || count + start > size()) + throw new IllegalArgumentException(); + + long v = (long) value; + for (int i = start, n = start + count; i < n; i++) + write(i, v); + } + + @Override + public void writeRange(int startElement, int count, + AttributeStreamBase _src, int srcStart, boolean bForward, int stride) { + if (startElement < 0 || count < 0 || srcStart < 0) + throw new IllegalArgumentException(); + + if (!bForward && (stride <= 0 || (count % stride != 0))) + throw new IllegalArgumentException(); + + AttributeStreamOfInt64 src = (AttributeStreamOfInt64) _src; // the input + // type must + // match + + if (src.size() < (int) (srcStart + count)) + throw new IllegalArgumentException(); + + if (count == 0) + return; + + if (size() < count + startElement) + resize(count + startElement); + + if (_src == (AttributeStreamBase) this) { + _selfWriteRangeImpl(startElement, count, srcStart, bForward, stride); + return; + } + + if (bForward) { + int j = startElement; + int offset = srcStart; + for (int i = 0; i < count; i++) { + m_buffer[j] = src.m_buffer[offset]; + j++; + offset++; + } + } else { + int j = startElement; + int offset = srcStart + count - stride; + if (stride == 1) { + for (int i = 0; i < count; i++) { + m_buffer[j] = src.m_buffer[offset]; + j++; + offset--; + } + } else { + for (int i = 0, n = count / stride; i < n; i++) { + for (int k = 0; k < stride; k++) + m_buffer[j + k] = src.m_buffer[offset + k]; + + j += stride; + offset -= stride; + } + } + } + } + + private void _selfWriteRangeImpl(int toElement, int count, int fromElement, + boolean bForward, int stride) { + + // writing from to this stream. + if (bForward) { + if (toElement == fromElement) + return; + } + + int offset; + int j; + int dj; + + if (fromElement < toElement) { + offset = fromElement + count - stride; + j = toElement + count - stride; + for (int i = 0, n = count / 2; i < n; i++) { + for (int k = 0; k < stride; k++) { + m_buffer[j + k] = m_buffer[offset + k]; + } + j -= stride; + offset -= stride; + } + } else { + offset = fromElement; + j = toElement; + dj = 1; + for (int i = 0; i < count; i++) { + m_buffer[j] = m_buffer[offset]; + j += 1; + offset++; + } + } + + if (!bForward) { + // reverse what we written + j = toElement; + offset = toElement + count - stride; + dj = stride; + for (int i = 0, n = count / 2; i < n; i++) { + for (int k = 0; k < stride; k++) { + long v = m_buffer[j + k]; + m_buffer[j + k] = m_buffer[offset + k]; + m_buffer[offset + k] = v; + } + j += stride; + offset -= stride; + } + } + } + + @Override + public void writeRange(int startElement, int count, ByteBuffer src, + int offsetBytes, boolean bForward) { + if (startElement < 0 || count < 0 || offsetBytes < 0) + throw new IllegalArgumentException(); + + final int elmSize = NumberUtils.sizeOf((double) 0); + if (src.capacity() < (int) (offsetBytes + elmSize * count)) + throw new IllegalArgumentException(); + + if (count == 0) + return; + + if (size() < count + startElement) + resize(count + startElement); + + int j = startElement; + if (!bForward) + j += count - 1; + + final int dj = bForward ? 1 : -1; + + int offset = offsetBytes; + for (int i = 0; i < count; i++, offset += elmSize) { + m_buffer[j] = src.getLong(offset); + j += dj; + } + + } +} diff --git a/src/com/esri/core/geometry/AttributeStreamOfInt8.java b/src/com/esri/core/geometry/AttributeStreamOfInt8.java new file mode 100644 index 00000000..2d10396a --- /dev/null +++ b/src/com/esri/core/geometry/AttributeStreamOfInt8.java @@ -0,0 +1,643 @@ +/* + Copyright 1995-2013 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ + + +package com.esri.core.geometry; + +import com.esri.core.geometry.VertexDescription.Persistence; +import java.nio.ByteBuffer; + +final class AttributeStreamOfInt8 extends AttributeStreamBase { + + byte[] m_buffer = null; + int m_size; + + public int size() { + return m_size; + } + + public void reserve(int reserve)// only in Java + { + if (reserve <= 0) + return; + if (m_buffer == null) + m_buffer = new byte[reserve]; + else { + if (reserve <= m_buffer.length) + return; + byte[] buf = new byte[reserve]; + System.arraycopy(m_buffer, 0, buf, 0, m_size); + m_buffer = buf; + } + + } + + public AttributeStreamOfInt8(int size) { + int sz = size; + if (sz < 2) + sz = 2; + m_buffer = new byte[sz]; + m_size = size; + } + + public AttributeStreamOfInt8(int size, byte defaultValue) { + int sz = size; + if (sz < 2) + sz = 2; + m_buffer = new byte[sz]; + m_size = size; + for (int i = 0; i < size; i++) + m_buffer[i] = defaultValue; + } + + public AttributeStreamOfInt8(AttributeStreamOfInt8 other) { + m_buffer = other.m_buffer.clone(); + m_size = other.m_size; + } + + public AttributeStreamOfInt8(AttributeStreamOfInt8 other, int maxSize) { + m_size = other.size(); + if (m_size > maxSize) + m_size = maxSize; + int sz = m_size; + if (sz < 2) + sz = 2; + m_buffer = new byte[sz]; + System.arraycopy(other.m_buffer, 0, m_buffer, 0, m_size); + } + + /** + * Reads a value from the buffer at given offset. + * + * @param offset + * is the element number in the stream. + */ + public byte read(int offset) { + return m_buffer[offset]; + } + + /** + * Overwrites given element with new value. + * + * @param offset + * is the element number in the stream. + * @param value + * is the value to write. + */ + public void write(int offset, byte value) { + if (m_bReadonly) { + throw new RuntimeException("invalid_call"); + } + m_buffer[offset] = value; + } + + public void set(int offset, byte value) { + if (m_bReadonly) { + throw new RuntimeException("invalid_call"); + } + m_buffer[offset] = value; + } + + /** + * Adds a new value at the end of the stream. + * + * @param offset + * is the element number in the stream. + * @param value + * is the value to write. + */ + public void add(byte v) { + resize(m_size + 1); + m_buffer[m_size - 1] = v; + } + + @Override + public AttributeStreamBase restrictedClone(int maxsize) { + int len = m_size; + int newSize = maxsize < len ? maxsize : len; + byte[] newBuffer = new byte[newSize]; + System.arraycopy(m_buffer, 0, newBuffer, 0, newSize); + m_buffer = newBuffer; + m_size = newSize; + return this; + } + + @Override + public int virtualSize() { + return size(); + } + + @Override + public int getPersistence() { + return Persistence.enumInt8; + } + + @Override + public double readAsDbl(int offset) { + return read(offset); + } + + int get(int offset) { + return m_buffer[offset]; + } + + @Override + public int readAsInt(int offset) { + return (int) read(offset); + } + + @Override + public long readAsInt64(int offset) { + return (long) read(offset); + } + + @Override + public void resize(int newSize) { + if (m_bLockedInSize) + throw new GeometryException( + "invalid call. Attribute Stream is locked and cannot be resized."); + + if (newSize <= m_size) { + if ((newSize * 5) / 4 < m_buffer.length) {// decrease when the 25% + // margin is exceeded + byte[] newBuffer = new byte[newSize]; + System.arraycopy(m_buffer, 0, newBuffer, 0, newSize); + m_buffer = newBuffer; + } + m_size = newSize; + } else { + if (newSize > m_buffer.length) { + int sz = (newSize < 64) ? Math.max(newSize * 2, 4) + : (newSize * 5) / 4; + byte[] newBuffer = new byte[sz]; + System.arraycopy(m_buffer, 0, newBuffer, 0, m_size); + m_buffer = newBuffer; + } + + m_size = newSize; + } + } + + @Override + public void resizePreserveCapacity(int newSize)// java only method + { + if (m_buffer == null || newSize > m_buffer.length) + resize(newSize); + if (m_bLockedInSize) + throw new GeometryException( + "invalid call. Attribute Stream is locked and cannot be resized."); + + m_size = newSize; + } + + @Override + public void resize(int newSize, double defaultValue) { + if (m_bLockedInSize) + throw new GeometryException( + "invalid call. Attribute Stream is locked and cannot be resized."); + if (newSize <= m_size) { + if ((newSize * 5) / 4 < m_buffer.length) {// decrease when the 25% + // margin is exceeded + byte[] newBuffer = new byte[newSize]; + System.arraycopy(m_buffer, 0, newBuffer, 0, newSize); + m_buffer = newBuffer; + } + m_size = newSize; + } else { + if (newSize > m_buffer.length) { + int sz = (newSize < 64) ? Math.max(newSize * 2, 4) + : (newSize * 5) / 4; + byte[] newBuffer = new byte[sz]; + System.arraycopy(m_buffer, 0, newBuffer, 0, m_size); + m_buffer = newBuffer; + } + + for (int i = m_size; i < newSize; i++) + m_buffer[i] = (byte) defaultValue; + + m_size = newSize; + } + } + + @Override + public void writeAsDbl(int offset, double d) { + write(offset, (byte) d); + } + + @Override + public void writeAsInt64(int offset, long d) { + write(offset, (byte) d); + } + + @Override + public void writeAsInt(int offset, int d) { + write(offset, (byte) d); + } + + // @Override + // public void writeRange(int srcStart, int count, ByteBuffer dst, + // int dstOffsetBytes) { + // // TODO Auto-generated method stub + // + // } + /** + * OR's the given element with new value. + * + * @param offset + * is the element number in the stream. + * @param value + * is the value to OR. + */ + public void setBits(int offset, byte mask) { + if (m_bReadonly) + throw new GeometryException( + "invalid call. Attribute Stream is read only."); + + m_buffer[offset] = (byte) (m_buffer[offset] | mask); + } + + /** + * Clears bits in the given element that a set in the value param. + * + * @param offset + * is the element number in the stream. + * @param value + * is the mask to clear. + */ + void clearBits(int offset, byte mask) { + + if (m_bReadonly) + throw new GeometryException( + "invalid call. Attribute Stream is read only."); + + m_buffer[offset] = (byte) (m_buffer[offset] & (~mask)); + } + + @Override + public int calculateHashImpl(int hashCode, int start, int end) { + for (int i = start, n = size(); i < n && i < end; i++) + hashCode = NumberUtils.hash(hashCode, read(i)); + + return hashCode; + } + + @Override + public boolean equals(AttributeStreamBase other, int start, int end) { + if (other == null) + return false; + + if (!(other instanceof AttributeStreamOfInt8)) + return false; + + AttributeStreamOfInt8 _other = (AttributeStreamOfInt8) other; + + int size = size(); + int sizeOther = _other.size(); + + if (end > size || end > sizeOther && (size != sizeOther)) + return false; + + if (end > size) + end = size; + + for (int i = start; i < end; i++) + if (read(i) != _other.read(i)) + return false; + + return true; + } + + public byte getLast() { + return m_buffer[m_size - 1]; + } + + public void removeLast() { + resize(m_size - 1); + } + + @Override + public void addRange(AttributeStreamBase src, int start, int count, + boolean bForward, int stride) { + if (m_bReadonly) + throw new GeometryException("invalid_call"); + + if (!bForward && (stride < 1 || count % stride != 0)) + throw new IllegalArgumentException(); + + int oldSize = m_size; + int newSize = oldSize + count; + resize(newSize); + + if (bForward) { + System.arraycopy(((AttributeStreamOfDbl) src).m_buffer, start, + m_buffer, oldSize, count); + } else { + int n = count; + + for (int i = 0; i < count; i += stride) { + n -= stride; + + for (int s = 0; s < stride; s++) { + m_buffer[oldSize + i + s] = ((AttributeStreamOfInt8) src).m_buffer[start + + n + s]; + } + } + } + } + + @Override + public void insertRange(int start, AttributeStreamBase src, int srcStart, + int count, boolean bForward, int stride, int validSize) { + if (m_bReadonly) + throw new GeometryException("invalid_call"); + + if (!bForward && (stride < 1 || count % stride != 0)) + throw new IllegalArgumentException(); + + System.arraycopy(m_buffer, start, m_buffer, start + count, validSize + - start); + + if (m_buffer == ((AttributeStreamOfInt8) src).m_buffer) { + if (start < srcStart) + srcStart += count; + } + + if (bForward) { + System.arraycopy(((AttributeStreamOfInt8) src).m_buffer, srcStart, + m_buffer, start, count); + } else { + int n = count; + + for (int i = 0; i < count; i += stride) { + n -= stride; + + for (int s = 0; s < stride; s++) { + m_buffer[start + i + s] = ((AttributeStreamOfInt8) src).m_buffer[srcStart + + n + s]; + } + } + } + } + + @Override + public void insertRange(int start, double value, int count, int validSize) { + if (m_bReadonly) + throw new GeometryException("invalid_call"); + + System.arraycopy(m_buffer, start, m_buffer, start + count, validSize + - start); + + byte v = (byte) value; + for (int i = 0; i < count; i++) { + m_buffer[start + i] = v; + } + } + + @Override + public void insertAttributes(int start, Point pt, int semantics, + int validSize) { + if (m_bReadonly) + throw new GeometryException("invalid_call"); + + int comp = VertexDescription.getComponentCount(semantics); + + System.arraycopy(m_buffer, start, m_buffer, start + comp, validSize + - start); + + for (int c = 0; c < comp; c++) { + m_buffer[start + c] = (byte) pt.getAttributeAsDbl(semantics, c); + } + } + + @Override + public void eraseRange(int index, int count, int validSize) { + if (m_bReadonly) + throw new GeometryException("invalid_call"); + + if (index + count > m_size) + throw new GeometryException("invalid_call"); + + System.arraycopy(m_buffer, index + count, m_buffer, index, validSize + - (index + count)); + m_size -= count; + } + + @Override + public void readRange(int srcStart, int count, ByteBuffer dst, + int dstOffset, boolean bForward) { + if (srcStart < 0 || count < 0 || dstOffset < 0 + || size() < count + srcStart) + throw new IllegalArgumentException(); + + final int elmSize = NumberUtils.sizeOf((double) 0); + + if (dst.capacity() < (int) (dstOffset + elmSize * count)) + throw new IllegalArgumentException(); + + if (count == 0) + return; + + int j = srcStart; + if (!bForward) + j += count - 1; + + final int dj = bForward ? 1 : -1; + + int offset = dstOffset; + for (int i = 0; i < count; i++, offset += elmSize) { + dst.put(offset, m_buffer[j]); + j += dj; + } + + } + + @Override + public void reverseRange(int index, int count, int stride) { + if (m_bReadonly) + throw new GeometryException("invalid_call"); + + if (stride < 1 || count % stride != 0) + throw new GeometryException("invalid_call"); + + int cIterations = count >> 1; + int n = count; + + for (int i = 0; i < cIterations; i += stride) { + n -= stride; + + for (int s = 0; s < stride; s++) { + byte temp = m_buffer[index + i + s]; + m_buffer[index + i + s] = m_buffer[index + n + s]; + m_buffer[index + n + s] = temp; + } + } + } + + @Override + public void setRange(double value, int start, int count) { + if (start < 0 || count < 0 || start < 0 || count + start > size()) + throw new IllegalArgumentException(); + + byte v = (byte) value; + for (int i = start, n = start + count; i < n; i++) + write(i, v); + } + + @Override + public void writeRange(int startElement, int count, + AttributeStreamBase _src, int srcStart, boolean bForward, int stride) { + if (startElement < 0 || count < 0 || srcStart < 0) + throw new IllegalArgumentException(); + + if (!bForward && (stride <= 0 || (count % stride != 0))) + throw new IllegalArgumentException(); + + AttributeStreamOfInt8 src = (AttributeStreamOfInt8) _src; // the input + // type must + // match + + if (src.size() < (int) (srcStart + count)) + throw new IllegalArgumentException(); + + if (count == 0) + return; + + if (size() < count + startElement) + resize(count + startElement); + + if (_src == (AttributeStreamBase) this) { + _selfWriteRangeImpl(startElement, count, srcStart, bForward, stride); + return; + } + + if (bForward) { + int j = startElement; + int offset = srcStart; + for (int i = 0; i < count; i++) { + m_buffer[j] = src.m_buffer[offset]; + j++; + offset++; + } + } else { + int j = startElement; + int offset = srcStart + count - stride; + if (stride == 1) { + for (int i = 0; i < count; i++) { + m_buffer[j] = src.m_buffer[offset]; + j++; + offset--; + } + } else { + for (int i = 0, n = count / stride; i < n; i++) { + for (int k = 0; k < stride; k++) + m_buffer[j + k] = src.m_buffer[offset + k]; + + j += stride; + offset -= stride; + } + } + } + } + + private void _selfWriteRangeImpl(int toElement, int count, int fromElement, + boolean bForward, int stride) { + + // writing from to this stream. + if (bForward) { + if (toElement == fromElement) + return; + } + + int offset; + int j; + int dj; + + if (fromElement < toElement) { + offset = fromElement + count - stride; + j = toElement + count - stride; + for (int i = 0, n = count / 2; i < n; i++) { + for (int k = 0; k < stride; k++) { + m_buffer[j + k] = m_buffer[offset + k]; + } + j -= stride; + offset -= stride; + } + } else { + offset = fromElement; + j = toElement; + dj = 1; + for (int i = 0; i < count; i++) { + m_buffer[j] = m_buffer[offset]; + j += 1; + offset++; + } + } + + if (!bForward) { + // reverse what we written + j = toElement; + offset = toElement + count - stride; + dj = stride; + for (int i = 0, n = count / 2; i < n; i++) { + for (int k = 0; k < stride; k++) { + byte v = m_buffer[j + k]; + m_buffer[j + k] = m_buffer[offset + k]; + m_buffer[offset + k] = v; + } + j += stride; + offset -= stride; + } + } + } + + @Override + public void writeRange(int startElement, int count, ByteBuffer src, + int offsetBytes, boolean bForward) { + if (startElement < 0 || count < 0 || offsetBytes < 0) + throw new IllegalArgumentException(); + + final int elmSize = NumberUtils.sizeOf((double) 0); + if (src.capacity() < (int) (offsetBytes + elmSize * count)) + throw new IllegalArgumentException(); + + if (count == 0) + return; + + if (size() < count + startElement) + resize(count + startElement); + + int j = startElement; + if (!bForward) + j += count - 1; + + final int dj = bForward ? 1 : -1; + + int offset = offsetBytes; + for (int i = 0; i < count; i++, offset += elmSize) { + m_buffer[j] = src.get(offset); + j += dj; + } + + } +} diff --git a/src/com/esri/core/geometry/Boundary.java b/src/com/esri/core/geometry/Boundary.java new file mode 100644 index 00000000..28d0916f --- /dev/null +++ b/src/com/esri/core/geometry/Boundary.java @@ -0,0 +1,179 @@ +/* + Copyright 1995-2013 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ +package com.esri.core.geometry; + +class Boundary { + static Geometry calculate(Geometry geom, ProgressTracker progress_tracker) { + int gt = geom.getType().value(); + if (gt == Geometry.GeometryType.Polygon) { + Polyline dst = new Polyline(geom.getDescription()); + if (!geom.isEmpty()) + geom.copyTo(dst); + + return dst; + } else if (gt == Geometry.GeometryType.Polyline) { + return calculatePolylineBoundary_(geom._getImpl(), progress_tracker); + } else if (gt == Geometry.GeometryType.Envelope) { + Polyline dst = new Polyline(geom.getDescription()); + if (!geom.isEmpty()) + dst.addEnvelope((Envelope) geom, false); + + return dst; + } else if (Geometry.isSegment(gt)) { + MultiPoint mp = new MultiPoint(geom.getDescription()); + if (!geom.isEmpty() && !((Segment) geom).isClosed()) { + Point pt = new Point(); + ((Segment) geom).queryStart(pt); + mp.add(pt); + ((Segment) geom).queryEnd(pt); + mp.add(pt); + } + return mp; + } else if (Geometry.isPoint(gt)) { + // returns empty point for points and multipoints. + return null; + } + + throw new IllegalArgumentException(); + } + + private static final class MultiPathImplBoundarySorter extends ClassicSort { + AttributeStreamOfDbl m_xy; + + static final class CompareIndices extends + AttributeStreamOfInt32.IntComparator { + AttributeStreamOfDbl m_xy; + Point2D pt1_helper; + Point2D pt2_helper; + + CompareIndices(AttributeStreamOfDbl xy) { + m_xy = xy; + pt1_helper = new Point2D(); + pt2_helper = new Point2D(); + } + + @Override + public int compare(int v1, int v2) { + m_xy.read(2 * v1, pt1_helper); + m_xy.read(2 * v2, pt2_helper); + return pt1_helper.compare(pt2_helper); + } + } + + MultiPathImplBoundarySorter(AttributeStreamOfDbl xy) { + m_xy = xy; + } + + @Override + public void userSort(int begin, int end, AttributeStreamOfInt32 indices) { + indices.Sort(begin, end, new CompareIndices(m_xy)); + } + + @Override + public double getValue(int index) { + return m_xy.read(2 * index + 1); + } + } + + static MultiPoint calculatePolylineBoundary_(Object impl, + ProgressTracker progress_tracker) { + MultiPathImpl mpImpl = (MultiPathImpl) impl; + MultiPoint dst = new MultiPoint(mpImpl.getDescription()); + if (!mpImpl.isEmpty()) { + AttributeStreamOfInt32 indices = new AttributeStreamOfInt32(0); + indices.reserve(mpImpl.getPathCount() * 2); + for (int ipath = 0, nPathCount = mpImpl.getPathCount(); ipath < nPathCount; ipath++) { + int path_size = mpImpl.getPathSize(ipath); + if (path_size > 0 && !mpImpl.isClosedPathInXYPlane(ipath))// closed + // paths + // of + // polyline + // do + // not + // contribute + // to + // the + // boundary. + { + int start = mpImpl.getPathStart(ipath); + indices.add(start); + int end = mpImpl.getPathEnd(ipath) - 1; + indices.add(end); + } + } + if (indices.size() > 0) { + BucketSort sorter = new BucketSort(); + AttributeStreamOfDbl xy = (AttributeStreamOfDbl) (mpImpl + .getAttributeStreamRef(VertexDescription.Semantics.POSITION)); + sorter.sort(indices, 0, indices.size(), + new MultiPathImplBoundarySorter(xy)); + Point2D ptPrev = new Point2D(); + xy.read(2 * indices.get(0), ptPrev); + int ind = 0; + int counter = 1; + Point point = new Point(); + Point2D pt = new Point2D(); + for (int i = 1, n = indices.size(); i < n; i++) { + xy.read(2 * indices.get(i), pt); + if (pt.isEqual(ptPrev)) { + if (indices.get(ind) > indices.get(i)) { + // remove duplicate point + indices.set(ind, NumberUtils.intMax()); + ind = i;// just for the heck of it, have the first + // point in the order to be added to the + // boundary. + } else + indices.set(i, NumberUtils.intMax()); + + counter++; + } else { + if ((counter & 1) == 0) {// remove boundary point + indices.set(ind, NumberUtils.intMax()); + } + + ptPrev.setCoords(pt); + ind = i; + counter = 1; + } + } + + if ((counter & 1) == 0) {// remove the point + indices.set(ind, NumberUtils.intMax()); + } + + indices.sort(0, indices.size()); + + for (int i = 0, n = indices.size(); i < n; i++) { + if (indices.get(i) == NumberUtils.intMax()) + break; + + mpImpl.getPointByVal(indices.get(i), point); + dst.add(point); + } + } + } + + return dst; + } +} diff --git a/src/com/esri/core/geometry/BucketSort.java b/src/com/esri/core/geometry/BucketSort.java new file mode 100644 index 00000000..abd698c7 --- /dev/null +++ b/src/com/esri/core/geometry/BucketSort.java @@ -0,0 +1,184 @@ +/* + Copyright 1995-2013 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ +package com.esri.core.geometry; + +final class BucketSort { + AttributeStreamOfInt32 m_buckets; + AttributeStreamOfInt32 m_bucketed_indices; + double m_min_value; + double m_max_value; + double m_dy; + + static int MAXBUCKETS = 65536; + + public BucketSort() { + m_buckets = new AttributeStreamOfInt32(0); + m_bucketed_indices = new AttributeStreamOfInt32(0); + m_min_value = 1; + m_max_value = -1; + m_dy = NumberUtils.TheNaN; + } + + /** + * Executes sort on the Bucket_sort. The result is fed into the indices + * array in the range between begin (inclusive) and end (exclusive). Uses + * user supplied sorter to execute sort on each bucket. Users either supply + * the sorter and use this method of Bucket_sort class, or use other methods + * to form the buckets and take care of bucket sorting themselves. + */ + public void sort(AttributeStreamOfInt32 indices, int begin, int end, + ClassicSort sorter) { + if (end - begin < 32) { + sorter.userSort(begin, end, indices); + return; + } + boolean b_fallback = true; + try { + double miny = NumberUtils.positiveInf(); + double maxy = NumberUtils.negativeInf(); + for (int i = begin; i < end; i++) { + double y = sorter.getValue(indices.get(i)); + if (y < miny) + miny = y; + if (y > maxy) + maxy = y; + } + + if (reset(end - begin, miny, maxy, end - begin)) { + for (int i = begin; i < end; i++) { + int vertex = indices.get(i); + double y = sorter.getValue(vertex); + int bucket = getBucket(y); + m_buckets.set(bucket, m_buckets.get(bucket) + 1);// counting + // values + // in a + // bucket. + m_bucketed_indices.write(i - begin, vertex); + } + + // Recalculate buckets to contain start positions of buckets. + int c = m_buckets.get(0); + m_buckets.set(0, 0); + for (int i = 1, n = m_buckets.size(); i < n; i++) { + int b = m_buckets.get(i); + m_buckets.set(i, c); + c += b; + } + + for (int i = begin; i < end; i++) { + int vertex = m_bucketed_indices.read(i - begin); + double y = sorter.getValue(vertex); + int bucket = getBucket(y); + int bucket_index = m_buckets.get(bucket); + indices.set(bucket_index + begin, vertex); + m_buckets.set(bucket, bucket_index + 1); + } + + b_fallback = false; + } + } catch (Exception e) { + m_buckets.resize(0); + m_bucketed_indices.resize(0); + } + + if (b_fallback) { + sorter.userSort(begin, end, indices); + return; + } + + int j = 0; + for (int i = 0, n = m_buckets.size(); i < n; i++) { + int j0 = j; + j = m_buckets.get(i); + if (j > j0) + sorter.userSort(begin + j0, begin + j, indices); + } + assert (j == end); + + if (getBucketCount() > 100) // some heuristics to preserve memory + { + m_buckets.resize(0); + m_bucketed_indices.resize(0); + } + } + + /** + * Clears and resets Bucket_sort to the new state, preparing for the + * accumulation of new data. + * + * @param bucket_count + * - the number of buckets. Usually equal to the number of + * elements to sort. + * @param min_value + * - the minimum value of elements to sort. + * @param max_value + * - the maximum value of elements to sort. + * @param capacity + * - the number of elements to sort (-1 if not known). The + * bucket_count are usually equal. + * @return Returns False, if the bucket sort cannot be used with the given + * parameters. The method also can throw out of memory exception. In + * the later case, one should fall back to the regular sort. + */ + private boolean reset(int bucket_count, double min_value, double max_value, + int capacity) { + if (bucket_count < 2 || max_value == min_value) + return false; + + int bc = Math.min(MAXBUCKETS, bucket_count); + m_buckets.reserve(bc); + m_buckets.resize(bc); + m_buckets.setRange(0, 0, m_buckets.size()); + m_min_value = min_value; + m_max_value = max_value; + m_bucketed_indices.resize(capacity); + + m_dy = (max_value - min_value) / (bc - 1); + return true; + } + + /** + * Adds new element to the bucket builder. The value must be between + * min_value and max_value. + * + * @param The + * value used for bucketing. + * @param The + * index of the element to store in the buffer. Usually it is an + * index into some array, where the real elements are stored. + */ + private int getBucket(double value) { + assert (value >= m_min_value && value <= m_max_value); + int bucket = (int) ((value - m_min_value) / m_dy); + return bucket; + } + + /** + * Returns the bucket count. + */ + private int getBucketCount() { + return m_buckets.size(); + } + +} diff --git a/src/com/esri/core/geometry/Bufferer.java b/src/com/esri/core/geometry/Bufferer.java new file mode 100644 index 00000000..db4318ad --- /dev/null +++ b/src/com/esri/core/geometry/Bufferer.java @@ -0,0 +1,1594 @@ +/* + Copyright 1995-2013 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ +package com.esri.core.geometry; + +import java.util.ArrayList; + +class Bufferer { + /** + * Result is always a polygon. For non positive distance and non-areas + * returns an empty polygon. For points returns circles. + */ + static Geometry buffer(Geometry geometry, double distance, + SpatialReference sr, double densify_dist, + int max_vertex_in_complete_circle, ProgressTracker progress_tracker) { + if (geometry == null) + throw new IllegalArgumentException(); + + if (densify_dist < 0) + throw new IllegalArgumentException(); + + if (geometry.isEmpty()) + return new Polygon(geometry.getDescription()); + + Envelope2D env2D = new Envelope2D(); + geometry.queryLooseEnvelope2D(env2D); + if (distance > 0) + env2D.inflate(distance, distance); + + Bufferer bufferer = new Bufferer(progress_tracker); + bufferer.m_spatialReference = sr; + bufferer.m_geometry = geometry; + bufferer.m_tolerance = InternalUtils.calculateToleranceFromGeometry(sr, + env2D, true);// conservative to have same effect as simplify + bufferer.m_small_tolerance = InternalUtils + .calculateToleranceFromGeometry(null, env2D, true);// conservative + // to have + // same + // effect as + // simplify + bufferer.m_distance = distance; + bufferer.m_original_geom_type = geometry.getType().value(); + if (max_vertex_in_complete_circle <= 0) { + max_vertex_in_complete_circle = 96;// 96 is the value used by SG. + // This is the number of + // vertices in the full circle. + } + + bufferer.m_abs_distance = Math.abs(bufferer.m_distance); + bufferer.m_abs_distance_reversed = bufferer.m_abs_distance != 0 ? 1.0 / bufferer.m_abs_distance + : 0; + + if (NumberUtils.isNaN(densify_dist) || densify_dist == 0) { + densify_dist = bufferer.m_abs_distance * 1e-5; + } else { + if (densify_dist > bufferer.m_abs_distance * 0.5) + densify_dist = bufferer.m_abs_distance * 0.5;// do not allow too + // large densify + // distance (the + // value will be + // adjusted + // anyway later) + } + + if (max_vertex_in_complete_circle < 12) + max_vertex_in_complete_circle = 12; + + double max_dd = Math.abs(distance) + * (1 - Math.cos(Math.PI / max_vertex_in_complete_circle)); + + if (max_dd > densify_dist) + densify_dist = max_dd;// the densify distance has to agree with the + // max_vertex_in_complete_circle + else { + double vertex_count = Math.PI + / Math.acos(1.0 - densify_dist / Math.abs(distance)); + if (vertex_count < (double) max_vertex_in_complete_circle - 1.0) { + max_vertex_in_complete_circle = (int) vertex_count; + if (max_vertex_in_complete_circle < 12) { + max_vertex_in_complete_circle = 12; + densify_dist = Math.abs(distance) + * (1 - Math.cos(Math.PI + / max_vertex_in_complete_circle)); + } + } + } + + bufferer.m_densify_dist = densify_dist; + bufferer.m_max_vertex_in_complete_circle = max_vertex_in_complete_circle; + // when filtering close points we do not want the filter to distort + // generated buffer too much. + bufferer.m_filter_tolerance = Math.min(bufferer.m_small_tolerance, + densify_dist * 0.25); + return bufferer.buffer_(); + } + + private Geometry m_geometry; + + private static final class BufferCommand { + private interface Flags { + static final int enum_line = 1; + static final int enum_arc = 2; + static final int enum_dummy = 4; + static final int enum_concave_dip = 8; + static final int enum_connection = enum_arc | enum_line; + } + + private Point2D m_from; + private Point2D m_to; + private Point2D m_center; + private int m_next; + private int m_prev; + private int m_type; + + private BufferCommand(Point2D from, Point2D to, Point2D center, + int type, int next, int prev) { + m_from = new Point2D(); + m_to = new Point2D(); + m_center = new Point2D(); + m_from.setCoords(from); + m_to.setCoords(to); + m_center.setCoords(center); + m_type = type; + m_next = next; + m_prev = prev; + } + + private BufferCommand(Point2D from, Point2D to, int next, int prev, + String dummy) { + m_from = new Point2D(); + m_to = new Point2D(); + m_center = new Point2D(); + m_from.setCoords(from); + m_to.setCoords(to); + m_center.setNaN(); + m_type = 4; + m_next = next; + m_prev = prev; + } + } + + private ArrayList m_buffer_commands; + + private int m_original_geom_type; + private ProgressTracker m_progress_tracker; + private int m_max_vertex_in_complete_circle; + private SpatialReference m_spatialReference; + private double m_tolerance; + private double m_small_tolerance; + private double m_filter_tolerance; + private double m_densify_dist; + private double m_distance; + private double m_abs_distance; + private double m_abs_distance_reversed; + private double m_dA; + private boolean m_b_output_loops; + private boolean m_bfilter; + private ArrayList m_circle_template; + private ArrayList m_left_stack; + private ArrayList m_middle_stack; + private Line m_helper_line_1; + private Line m_helper_line_2; + private Point2D[] m_helper_array; + private int m_progress_counter; + + private void generateCircleTemplate_() { + if (m_circle_template == null) { + m_circle_template = new ArrayList(0); + } else if (!m_circle_template.isEmpty()) { + return; + } + + int N = calcN_(4); + + assert (N >= 4); + int real_size = (N + 3) / 4; + double dA = (Math.PI * 0.5) / real_size; + m_dA = dA; + + for (int i = 0; i < real_size * 4; i++) + m_circle_template.add(null); + + double dcos = Math.cos(dA); + double dsin = Math.sin(dA); + Point2D pt = new Point2D(0.0, 1.0); + + for (int i = 0; i < real_size; i++) { + m_circle_template.set(i + real_size * 0, new Point2D(pt.y, -pt.x)); + m_circle_template.set(i + real_size * 1, new Point2D(-pt.x, -pt.y)); + m_circle_template.set(i + real_size * 2, new Point2D(-pt.y, pt.x)); + m_circle_template.set(i + real_size * 3, pt); + pt = new Point2D(pt.x, pt.y); + pt.rotateReverse(dcos, dsin); + } + // the template is filled with the index 0 corresponding to the point + // (0, 0), following clockwise direction (0, -1), (-1, 0), (1, 0) + } + + private static final class GeometryCursorForMultiPoint extends + GeometryCursor { + private int m_index; + private Geometry m_buffered_polygon; + private MultiPoint m_mp; + private SpatialReference m_spatialReference; + private double m_distance; + private double m_densify_dist; + private double m_x; + private double m_y; + private int m_max_vertex_in_complete_circle; + private ProgressTracker m_progress_tracker; + + GeometryCursorForMultiPoint(MultiPoint mp, double distance, + SpatialReference sr, double densify_dist, + int max_vertex_in_complete_circle, + ProgressTracker progress_tracker) { + m_index = 0; + m_mp = mp; + m_x = 0; + m_y = 0; + m_distance = distance; + m_spatialReference = sr; + m_densify_dist = densify_dist; + m_max_vertex_in_complete_circle = max_vertex_in_complete_circle; + m_progress_tracker = progress_tracker; + } + + @Override + public Geometry next() { + Point point = new Point(); + while (true) { + if (m_index == m_mp.getPointCount()) + return null; + + m_mp.getPointByVal(m_index, point); + m_index++; + if (point.isEmpty()) + continue; + break; + } + + boolean b_first = false; + if (m_buffered_polygon == null) { + m_x = point.getX(); + m_y = point.getY(); + + m_buffered_polygon = Bufferer.buffer(point, m_distance, + m_spatialReference, m_densify_dist, + m_max_vertex_in_complete_circle, m_progress_tracker); + b_first = true; + } + + Geometry res; + if (m_index < m_mp.getPointCount()) { + res = new Polygon(); + m_buffered_polygon.copyTo(res); + } else { + res = m_buffered_polygon; // do not clone the last geometry. + } + + if (!b_first)// don't apply transformation unnecessary + { + Transformation2D transform = new Transformation2D(); + double dx = point.getX() - m_x; + double dy = point.getY() - m_y; + transform.setShift(dx, dy); + res.applyTransformation(transform); + } + + return res; + } + + @Override + public int getGeometryID() { + return 0; + } + } + + private static final class GeometryCursorForPolyline extends GeometryCursor { + private Bufferer m_bufferer; + private int m_index; + private boolean m_bfilter; + + GeometryCursorForPolyline(Bufferer bufferer, boolean bfilter) { + m_bufferer = bufferer; + m_index = 0; + m_bfilter = bfilter; + } + + @Override + public Geometry next() { + MultiPathImpl mp = (MultiPathImpl) (m_bufferer.m_geometry + ._getImpl()); + if (m_index < mp.getPathCount()) { + int ind = m_index; + m_index++; + if (!mp.isClosedPathInXYPlane(ind)) { + Point2D prev_end = mp.getXY(mp.getPathEnd(ind) - 1); + while (m_index < mp.getPathCount()) { + Point2D start = mp.getXY(mp.getPathStart(m_index)); + if (mp.isClosedPathInXYPlane(m_index)) + break; + if (start != prev_end) + break; + + prev_end = mp.getXY(mp.getPathEnd(m_index) - 1); + m_index++; + } + } + + if (m_index - ind == 1) + return m_bufferer.bufferPolylinePath_( + (Polyline) (m_bufferer.m_geometry), ind, m_bfilter); + else { + Polyline tmp_polyline = new Polyline( + m_bufferer.m_geometry.getDescription()); + tmp_polyline.addPath((Polyline) (m_bufferer.m_geometry), + ind, true); + for (int i = ind + 1; i < m_index; i++) { + ((MultiPathImpl) tmp_polyline._getImpl()) + .addSegmentsFromPath( + (MultiPathImpl) m_bufferer.m_geometry + ._getImpl(), i, 0, mp + .getSegmentCount(i), false); + } + // Operator_factory_local::SaveJSONToTextFileDbg("c:/temp/buffer_ppp.txt", + // tmp_polyline, nullptr); + Polygon res = m_bufferer.bufferPolylinePath_(tmp_polyline, + 0, m_bfilter); + // Operator_factory_local::SaveJSONToTextFileDbg("c:/temp/buffer_ppp_res.txt", + // *res, nullptr); + return res; + } + } + + return null; + } + + @Override + public int getGeometryID() { + return 0; + } + } + + private static final class GeometryCursorForPolygon extends GeometryCursor { + private Bufferer m_bufferer; + private int m_index; + + GeometryCursorForPolygon(Bufferer bufferer) { + m_bufferer = bufferer; + m_index = 0; + } + + @Override + public Geometry next() { + Polygon input_polygon = (Polygon) (m_bufferer.m_geometry); + if (m_index < input_polygon.getPathCount()) { + int ind = m_index; + double area = input_polygon.calculateRingArea2D(m_index); + assert (area > 0); + m_index++; + while (m_index < input_polygon.getPathCount()) { + double hole_area = input_polygon + .calculateRingArea2D(m_index); + if (hole_area > 0) + break;// not a hole + m_index++; + } + + if (ind == 0 && m_index == input_polygon.getPathCount()) { + return m_bufferer.bufferPolygonImpl_(input_polygon, 0, + input_polygon.getPathCount()); + } else { + return m_bufferer.bufferPolygonImpl_(input_polygon, ind, + m_index); + } + } + + return null; + } + + @Override + public int getGeometryID() { + return 0; + } + } + + private Bufferer(ProgressTracker progress_tracker) { + m_buffer_commands = new ArrayList(0); + m_progress_tracker = progress_tracker; + m_tolerance = 0; + m_small_tolerance = 0; + m_filter_tolerance = 0; + m_distance = 0; + m_original_geom_type = Geometry.GeometryType.Unknown; + m_abs_distance_reversed = 0; + m_abs_distance = 0; + m_densify_dist = -1; + m_dA = -1; + m_b_output_loops = true; + m_bfilter = true; + } + + private Geometry buffer_() { + int gt = m_geometry.getType().value(); + if (Geometry.isSegment(gt)) {// convert segment to a polyline and repeat + // the call + Polyline polyline = new Polyline(m_geometry.getDescription()); + polyline.addSegment((Segment) (m_geometry), true); + m_geometry = polyline; + return buffer_(); + } + + if (m_distance <= m_tolerance) { + if (Geometry.isArea(gt)) { + if (m_distance <= 0) { + // if the geometry is area type, then the negative distance + // may produce a degenerate shape. Check for this and return + // empty geometry. + Envelope2D env = new Envelope2D(); + m_geometry.queryEnvelope2D(env); + if (env.getWidth() <= -m_distance * 2 + || env.getHeight() <= m_distance * 2) + return new Polygon(m_geometry.getDescription()); + } + } else { + return new Polygon(m_geometry.getDescription());// return an + // empty polygon + // for distance + // <= + // m_tolerance + // and any input + // other than + // polygon. + } + } + + // Operator_factory_local::SaveJSONToTextFileDbg("c:/temp/buffer_input.txt", + // *m_geometry, nullptr); + + // Complex cases: + switch (m_geometry.getType().value()) { + case Geometry.GeometryType.Point: + return bufferPoint_(); + case Geometry.GeometryType.MultiPoint: + return bufferMultiPoint_(); + case Geometry.GeometryType.Polyline: + return bufferPolyline_(); + case Geometry.GeometryType.Polygon: + return bufferPolygon_(); + case Geometry.GeometryType.Envelope: + return bufferEnvelope_(); + default: + throw new GeometryException("internal error"); + } + } + + private Geometry bufferPolyline_() { + if (isDegenerateGeometry_(m_geometry)) { + Point point = new Point(); + ((MultiVertexGeometry) m_geometry).getPointByVal(0, point); + Envelope2D env2D = new Envelope2D(); + m_geometry.queryEnvelope2D(env2D); + point.setXY(env2D.getCenter()); + return bufferPoint_(point); + } + + assert (m_distance > 0); + m_geometry = preparePolyline_((Polyline) (m_geometry)); + + GeometryCursorForPolyline cursor = new GeometryCursorForPolyline(this, + m_bfilter); + GeometryCursor union_cursor = ((OperatorUnion) OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Union)).execute( + cursor, m_spatialReference, m_progress_tracker); + Geometry result = union_cursor.next(); + return result; + } + + private Geometry bufferPolygon_() { + if (m_distance == 0) + return m_geometry;// return input to the output. + + OperatorSimplify simplify = (OperatorSimplify) OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Simplify); + + generateCircleTemplate_(); + m_geometry = simplify.execute(m_geometry, null, false, + m_progress_tracker); + + if (m_distance < 0) { + Polygon poly = (Polygon) (m_geometry); + Polygon buffered_result = bufferPolygonImpl_(poly, 0, + poly.getPathCount()); + return simplify.execute(buffered_result, m_spatialReference, false, + m_progress_tracker); + } else { + if (isDegenerateGeometry_(m_geometry)) { + Point point = new Point(); + ((MultiVertexGeometry) m_geometry).getPointByVal(0, point); + Envelope2D env2D = new Envelope2D(); + m_geometry.queryEnvelope2D(env2D); + point.setXY(env2D.getCenter()); + return bufferPoint_(point); + } + + // For the positive distance we need to process polygon in the parts + // such that each exterior ring with holes is processed separatelly. + GeometryCursorForPolygon cursor = new GeometryCursorForPolygon(this); + GeometryCursor union_cursor = ((OperatorUnion) OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Union)).execute( + cursor, m_spatialReference, m_progress_tracker); + Geometry result = union_cursor.next(); + return result; + } + } + + private Polygon bufferPolygonImpl_(Polygon input_geom, int ipath_begin, + int ipath_end) { + MultiPath input_mp = (MultiPath) (input_geom); + MultiPathImpl mp_impl = (MultiPathImpl) (input_mp._getImpl()); + Polygon intermediate_polygon = new Polygon(input_geom.getDescription()); + for (int ipath = ipath_begin; ipath < ipath_end; ipath++) { + if (mp_impl.getPathSize(ipath) < 1) + continue; + + double path_area = mp_impl.calculateRingArea2D(ipath); + Envelope2D env2D = new Envelope2D(); + mp_impl.queryPathEnvelope2D(ipath, env2D); + + if (m_distance > 0) { + if (path_area > 0) { + if (isDegeneratePath_(mp_impl, ipath)) {// if a path is + // degenerate + // (almost a point), + // then we can draw + // a circle instead + // of it as a buffer + // and nobody would + // notice :) + Point point = new Point(); + mp_impl.getPointByVal(mp_impl.getPathStart(ipath), + point); + point.setXY(env2D.getCenter()); + addCircle_( + (MultiPathImpl) intermediate_polygon._getImpl(), + point); + } else { + Polyline result_polyline = new Polyline( + input_geom.getDescription()); + MultiPathImpl result_mp = (MultiPathImpl) result_polyline + ._getImpl(); + + // We often see convex hulls, buffering those is an + // extremely simple task. + boolean bConvex = ConvexHull.isPathConvex( + (Polygon) (m_geometry), ipath, + m_progress_tracker); + if (bConvex + || bufferClosedPath_(m_geometry, ipath, + result_mp, true, 1) == 2) { + Polygon buffered_path = bufferConvexPath_(input_mp, + ipath); + intermediate_polygon.add(buffered_path, false); + } else { + Polygon buffered_path = bufferCleanup_( + result_polyline, false); + intermediate_polygon.add(buffered_path, false); + } + } + } else { + if (env2D.getWidth() + m_tolerance <= 2 * m_abs_distance + || env2D.getHeight() + m_tolerance <= 2 * m_abs_distance) // skip + // parts + // that + // will + // dissapear + continue; + + Polyline result_polyline = new Polyline( + input_geom.getDescription()); + MultiPathImpl result_mp = (MultiPathImpl) result_polyline + ._getImpl(); + bufferClosedPath_(m_geometry, ipath, result_mp, true, 1); + if (!result_polyline.isEmpty()) { + Envelope2D env = new Envelope2D(); + env.setCoords(env2D); + env.inflate(m_abs_distance, m_abs_distance); + result_mp.addEnvelope(env, false); + Polygon buffered_path = bufferCleanup_(result_polyline, + false); + // intermediate_polygon.reserve(intermediate_polygon.getPointCount() + // + buffered_path.getPointCount() - 4); + for (int i = 1, n = buffered_path.getPathCount(); i < n; i++) + intermediate_polygon + .addPath(buffered_path, i, true); + } + } + } else { + if (path_area > 0) { + if (env2D.getWidth() + m_tolerance <= 2 * m_abs_distance + || env2D.getHeight() + m_tolerance <= 2 * m_abs_distance) // skip + // parts + // that + // will + // dissapear + continue; + + Polyline result_polyline = new Polyline( + input_geom.getDescription()); + MultiPathImpl result_mp = (MultiPathImpl) result_polyline + ._getImpl(); + bufferClosedPath_(m_geometry, ipath, result_mp, true, -1);// this + // will + // provide + // a + // shape + // buffered + // inwards. + // It + // has + // counterclockwise + // orientation + if (!result_polyline.isEmpty()) { + Envelope2D env = new Envelope2D(); + result_mp.queryLooseEnvelope2D(env); + env.inflate(m_abs_distance, m_abs_distance); + result_mp.addEnvelope(env, false);// add an envelope + // exterior shell + Polygon buffered_path = bufferCleanup_(result_polyline, + false);// simplify with winding rule + // extract all parts but the first one (which is the + // envelope we added previously) + for (int i = 1, npaths = buffered_path.getPathCount(); i < npaths; i++) { + // the extracted parts have inverted orientation. + intermediate_polygon + .addPath(buffered_path, i, true); + } + } else { + // the path has been erased + } + } else { + // When buffering a hole with negative distance, buffer it + // as if it is an exterior ring buffered with positive + // distance + Polyline result_polyline = new Polyline( + input_geom.getDescription()); + MultiPathImpl result_mp = (MultiPathImpl) result_polyline + ._getImpl(); + bufferClosedPath_(m_geometry, ipath, result_mp, true, -1);// this + // will + // provide + // a + // shape + // buffered + // inwards. + Polygon buffered_path = bufferCleanup_(result_polyline, + false); + for (int i = 0, npaths = buffered_path.getPathCount(); i < npaths; i++) { + intermediate_polygon.addPath(buffered_path, i, true);// adds + // buffered + // hole + // reversed + // as + // if + // it + // is + // exteror + // ring + } + } + + // intermediate_polygon has inverted orientation. + } + } + + if (m_distance > 0) { + if (intermediate_polygon.getPathCount() > 1) { + Polygon cleaned_polygon = bufferCleanup_(intermediate_polygon, + false); + return cleaned_polygon; + } else { + return setWeakSimple_(intermediate_polygon); + } + } else { + Envelope2D polyenv = new Envelope2D(); + intermediate_polygon.queryLooseEnvelope2D(polyenv); + if (!intermediate_polygon.isEmpty()) { + // negative buffer distance. We got buffered holes and exterior + // rings. They all have wrong orientation. + // we need to apply winding simplify again to ensure all holes + // are unioned. + // For that create a big envelope and add all rings of the + // intermediate_polygon to it. + polyenv.inflate(m_abs_distance, m_abs_distance); + intermediate_polygon.addEnvelope(polyenv, false); + Polygon cleaned_polygon = bufferCleanup_(intermediate_polygon, + false); + // intermediate_polygon.reset();//free memory + + Polygon result_polygon = new Polygon( + cleaned_polygon.getDescription()); + for (int i = 1, n = cleaned_polygon.getPathCount(); i < n; i++) { + result_polygon.addPath(cleaned_polygon, i, false); + } + return setWeakSimple_(result_polygon); + } else { + return setWeakSimple_(intermediate_polygon); + } + } + } + + private Geometry bufferPoint_() { + return bufferPoint_((Point) (m_geometry)); + } + + private Geometry bufferPoint_(Point point) { + assert (m_distance > 0); + Polygon resultPolygon = new Polygon(m_geometry.getDescription()); + addCircle_((MultiPathImpl) resultPolygon._getImpl(), point); + return setStrongSimple_(resultPolygon); + } + + private Geometry bufferMultiPoint_() { + assert (m_distance > 0); + GeometryCursorForMultiPoint mpCursor = new GeometryCursorForMultiPoint( + (MultiPoint) (m_geometry), m_distance, m_spatialReference, + m_densify_dist, m_max_vertex_in_complete_circle, + m_progress_tracker); + GeometryCursor c = ((OperatorUnion) OperatorFactoryLocal.getInstance() + .getOperator(Operator.Type.Union)).execute(mpCursor, + m_spatialReference, m_progress_tracker); + return c.next(); + } + + private Geometry bufferEnvelope_() { + Polygon polygon = new Polygon(m_geometry.getDescription()); + if (m_distance <= 0) { + if (m_distance == 0) + polygon.addEnvelope((Envelope) (m_geometry), false); + else { + Envelope env = new Envelope(); + m_geometry.queryEnvelope(env); + env.inflate(m_distance, m_distance); + polygon.addEnvelope(env, false); + } + + return polygon;// nothing is easier than negative buffer on the + // envelope. + } + + polygon.addEnvelope((Envelope) (m_geometry), false); + m_geometry = polygon; + return bufferConvexPath_(polygon, 0); + } + + private Polygon bufferConvexPath_(MultiPath src, int ipath) { + generateCircleTemplate_(); + + Polygon resultPolygon = new Polygon(src.getDescription()); + MultiPathImpl result_mp = (MultiPathImpl) resultPolygon._getImpl(); + + // resultPolygon.reserve((m_circle_template.size() / 10 + 4) * + // src.getPathSize(ipath)); + + Point2D pt_1_tmp = new Point2D(), pt_1 = new Point2D(); + Point2D pt_2_tmp = new Point2D(), pt_2 = new Point2D(); + Point2D pt_3_tmp = new Point2D(), pt_3 = new Point2D(); + Point2D v_1 = new Point2D(); + Point2D v_2 = new Point2D(); + MultiPathImpl src_mp = (MultiPathImpl) src._getImpl(); + int path_size = src.getPathSize(ipath); + int path_start = src.getPathStart(ipath); + for (int i = 0, n = src.getPathSize(ipath); i < n; i++) { + src_mp.getXY(path_start + i, pt_1); + src_mp.getXY(path_start + (i + 1) % path_size, pt_2); + src_mp.getXY(path_start + (i + 2) % path_size, pt_3); + v_1.sub(pt_2, pt_1); + if (v_1.length() == 0) + throw new GeometryException("internal error"); + + v_1.leftPerpendicular(); + v_1.normalize(); + v_1.scale(m_abs_distance); + pt_1_tmp.add(v_1, pt_1); + pt_2_tmp.add(v_1, pt_2); + if (i == 0) + result_mp.startPath(pt_1_tmp); + else { + result_mp.lineTo(pt_1_tmp); + } + + result_mp.lineTo(pt_2_tmp); + + v_2.sub(pt_3, pt_2); + if (v_2.length() == 0) + throw new GeometryException("internal error"); + + v_2.leftPerpendicular(); + v_2.normalize(); + v_2.scale(m_abs_distance); + pt_3_tmp.add(v_2, pt_2); + + addJoin_(result_mp, pt_2, pt_2_tmp, pt_3_tmp, false, false); + } + + return setWeakSimple_(resultPolygon); + } + + private Polygon bufferPolylinePath_(Polyline polyline, int ipath, + boolean bfilter) { + assert (m_distance != 0); + generateCircleTemplate_(); + + MultiPath input_multi_path = polyline; + MultiPathImpl mp_impl = (MultiPathImpl) (input_multi_path._getImpl()); + + if (mp_impl.getPathSize(ipath) < 1) + return null; + + if (isDegeneratePath_(mp_impl, ipath) && m_distance > 0) {// if a path + // is + // degenerate + // (almost a + // point), + // then we + // can draw + // a circle + // instead + // of it as + // a buffer + // and + // nobody + // would + // notice :) + Point point = new Point(); + mp_impl.getPointByVal(mp_impl.getPathStart(ipath), point); + Envelope2D env2D = new Envelope2D(); + mp_impl.queryPathEnvelope2D(ipath, env2D); + point.setXY(env2D.getCenter()); + return (Polygon) (bufferPoint_(point)); + } + + Polyline result_polyline = new Polyline(polyline.getDescription()); + // result_polyline.reserve((m_circle_template.size() / 10 + 4) * + // mp_impl.getPathSize(ipath)); + + MultiPathImpl result_mp = (MultiPathImpl) result_polyline._getImpl(); + boolean b_closed = mp_impl.isClosedPathInXYPlane(ipath); + + if (b_closed) { + bufferClosedPath_(input_multi_path, ipath, result_mp, bfilter, 1); + bufferClosedPath_(input_multi_path, ipath, result_mp, bfilter, -1); + } else { + Polyline tmpPoly = new Polyline(input_multi_path.getDescription()); + tmpPoly.addPath(input_multi_path, ipath, false); + ((MultiPathImpl) tmpPoly._getImpl()).addSegmentsFromPath( + (MultiPathImpl) input_multi_path._getImpl(), ipath, 0, + input_multi_path.getSegmentCount(ipath), false); + bufferClosedPath_(tmpPoly, 0, result_mp, bfilter, 1); + // Operator_factory_local::SaveJSONToTextFileDbg("c:/temp/buffer_prepare.txt", + // *result_polyline, nullptr); + } + + return bufferCleanup_(result_polyline, false); + } + + private void progress_() { + m_progress_counter++; + if (m_progress_counter % 1024 == 0) { + if ((m_progress_tracker != null) + && !(m_progress_tracker.progress(-1, -1))) + throw new RuntimeException("user_canceled"); + } + } + + private Polygon bufferCleanup_(MultiPath multi_path, boolean simplify_result) { + double tol = simplify_result ? m_tolerance : m_small_tolerance; + Polygon resultPolygon = (Polygon) (TopologicalOperations + .planarSimplify(multi_path, tol, true, !simplify_result, + m_progress_tracker)); + assert (InternalUtils.isWeakSimple(resultPolygon, 0.0)); + return resultPolygon; + } + + private int calcN_(int minN) { + if (m_densify_dist == 0) + return m_max_vertex_in_complete_circle; + + double r = m_densify_dist * Math.abs(m_abs_distance_reversed); + double cos_a = 1 - r; + double N; + if (cos_a < -1) + N = minN; + else + N = 2.0 * Math.PI / Math.acos(cos_a) + 0.5; + + if (N < minN) + N = minN; + else if (N > m_max_vertex_in_complete_circle) + N = m_max_vertex_in_complete_circle; + + return (int) N; + } + + private void addJoin_(MultiPathImpl dst, Point2D center, Point2D fromPt, + Point2D toPt, boolean bStartPath, boolean bFinishAtToPt) { + generateCircleTemplate_(); + + Point2D v_1 = new Point2D(); + v_1.sub(fromPt, center); + v_1.scale(m_abs_distance_reversed); + Point2D v_2 = new Point2D(); + v_2.sub(toPt, center); + v_2.scale(m_abs_distance_reversed); + double angle_from = Math.atan2(v_1.y, v_1.x); + double dindex_from = angle_from / m_dA; + if (dindex_from < 0) + dindex_from = (double) m_circle_template.size() + dindex_from; + + dindex_from = (double) m_circle_template.size() - dindex_from; + + double angle_to = Math.atan2(v_2.y, v_2.x); + double dindex_to = angle_to / m_dA; + if (dindex_to < 0) + dindex_to = (double) m_circle_template.size() + dindex_to; + + dindex_to = (double) m_circle_template.size() - dindex_to; + + if (dindex_to < dindex_from) + dindex_to += (double) m_circle_template.size(); + assert (dindex_to >= dindex_from); + + int index_to = (int) dindex_to; + int index_from = (int) Math.ceil(dindex_from); + + if (bStartPath) { + dst.startPath(fromPt); + bStartPath = false; + } + + Point2D p = new Point2D(); + p.setCoords(m_circle_template.get(index_from % m_circle_template.size())); + p.scaleAdd(m_abs_distance, center); + double ddd = m_tolerance * 10; + p.sub(fromPt); + if (p.length() < ddd)// if too close to the fromPt, then use the next + // point + index_from += 1; + + p.setCoords(m_circle_template.get(index_to % m_circle_template.size())); + p.scaleAdd(m_abs_distance, center); + p.sub(toPt); + if (p.length() < ddd)// if too close to the toPt, then use the prev + // point + index_to -= 1; + + int count = index_to - index_from; + count++; + + for (int i = 0, j = index_from % m_circle_template.size(); i < count; i++, j = (j + 1) + % m_circle_template.size()) { + p.setCoords(m_circle_template.get(j)); + p.scaleAdd(m_abs_distance, center); + dst.lineTo(p); + progress_(); + } + + if (bFinishAtToPt) { + dst.lineTo(toPt); + } + } + + private int bufferClosedPath_(Geometry input_geom, int ipath, + MultiPathImpl result_mp, boolean bfilter, int dir) { + // Use temporary polyline for the path buffering. + EditShape edit_shape = new EditShape(); + int geom = edit_shape.addPathFromMultiPath((MultiPath) input_geom, + ipath, true); + edit_shape.filterClosePoints(m_filter_tolerance, false); + if (edit_shape.getPointCount(geom) < 2) {// Got degenerate output. + // Wither bail out or + // produce a circle. + if (dir < 0) + return 1;// negative direction produces nothing. + + MultiPath mpIn = (MultiPath) input_geom; + // Add a circle + Point pt = new Point(); + mpIn.getPointByVal(mpIn.getPathStart(ipath), pt); + addCircle_(result_mp, pt); + return 1; + } + + assert (edit_shape.getFirstPath(geom) != -1); + assert (edit_shape.getFirstVertex(edit_shape.getFirstPath(geom)) != -1); + + Point2D origin = edit_shape.getXY(edit_shape.getFirstVertex(edit_shape + .getFirstPath(geom))); + Transformation2D tr = new Transformation2D(); + tr.setShift(-origin.x, -origin.y); + // move the path to origin for better accuracy in calculations. + edit_shape.applyTransformation(tr); + + if (bfilter) { + // try removing the noise that does not contribute to the buffer. + int res_filter = filterPath_(edit_shape, geom, dir, true); + assert (res_filter == 1); + // Operator_factory_local::SaveJSONToTextFileDbg("c:/temp/buffer_filter.txt", + // *edit_shape.get_geometry(geom), nullptr); + if (edit_shape.getPointCount(geom) < 2) {// got degenerate output. + // Wither bail out or + // produce a circle. + if (dir < 0) + return 1;// negative direction produces nothing. + + MultiPath mpIn = (MultiPath) input_geom; + // Add a circle + Point pt = new Point(); + mpIn.getPointByVal(mpIn.getPathStart(ipath), pt); + addCircle_(result_mp, pt); + return 1; + } + } + + m_buffer_commands.clear(); + int path = edit_shape.getFirstPath(geom); + int ivert = edit_shape.getFirstVertex(path); + int iprev = dir == 1 ? edit_shape.getPrevVertex(ivert) : edit_shape + .getNextVertex(ivert); + int inext = dir == 1 ? edit_shape.getNextVertex(ivert) : edit_shape + .getPrevVertex(ivert); + boolean b_first = true; + Point2D pt_current = new Point2D(), pt_after = new Point2D(), pt_before = new Point2D(), pt_left_prev = new Point2D(), pt = new Point2D(), pt1 = new Point2D(); + Point2D v_after = new Point2D(), v_before = new Point2D(), v_left = new Point2D(), v_left_prev = new Point2D(); + double abs_d = m_abs_distance; + int ncount = edit_shape.getPathSize(path); + + // write out buffer commands as a set of arcs and line segments. + // if we'd convert this directly to a polygon and draw using winding + // fill rule, we'd get the buffered result. + for (int index = 0; index < ncount; index++) { + edit_shape.getXY(inext, pt_after); + + if (b_first) { + edit_shape.getXY(ivert, pt_current); + edit_shape.getXY(iprev, pt_before); + v_before.sub(pt_current, pt_before); + v_before.normalize(); + v_left_prev.leftPerpendicular(v_before); + v_left_prev.scale(abs_d); + pt_left_prev.add(v_left_prev, pt_current); + } + + v_after.sub(pt_after, pt_current); + v_after.normalize(); + + v_left.leftPerpendicular(v_after); + v_left.scale(abs_d); + pt.add(pt_current, v_left); + double cross = v_before.crossProduct(v_after); + double dot = v_before.dotProduct(v_after); + boolean bDoJoin = cross < 0 || (dot < 0 && cross == 0); + if (bDoJoin) { + m_buffer_commands.add(new BufferCommand(pt_left_prev, pt, + pt_current, BufferCommand.Flags.enum_arc, + m_buffer_commands.size() + 1, + m_buffer_commands.size() - 1)); + } else if (!pt_left_prev.equals(pt)) { + m_buffer_commands.add(new BufferCommand(pt_left_prev, + pt_current, m_buffer_commands.size() + 1, + m_buffer_commands.size() - 1, "dummy")); + m_buffer_commands.add(new BufferCommand(pt_current, pt, + m_buffer_commands.size() + 1, + m_buffer_commands.size() - 1, "dummy")); + } + + pt1.add(pt_after, v_left); + m_buffer_commands + .add(new BufferCommand(pt, pt1, pt_current, + BufferCommand.Flags.enum_line, m_buffer_commands + .size() + 1, m_buffer_commands.size() - 1)); + + pt_left_prev.setCoords(pt1); + v_left_prev.setCoords(v_left); + pt_before.setCoords(pt_current); + pt_current.setCoords(pt_after); + v_before.setCoords(v_after); + iprev = ivert; + ivert = inext; + b_first = false; + inext = dir == 1 ? edit_shape.getNextVertex(ivert) : edit_shape + .getPrevVertex(ivert); + } + + m_buffer_commands.get(m_buffer_commands.size() - 1).m_next = 0; + m_buffer_commands.get(0).m_prev = m_buffer_commands.size() - 1; + processBufferCommands_(result_mp); + tr.setShift(origin.x, origin.y);// move the path to improve precision. + result_mp.applyTransformation(tr, result_mp.getPathCount() - 1); + return 1; + } + + private void processBufferCommands_(MultiPathImpl result_mp) { + int ifirst_seg = cleanupBufferCommands_(); + boolean first = true; + int iseg_next = ifirst_seg + 1; + for (int iseg = ifirst_seg; iseg_next != ifirst_seg; iseg = iseg_next) { + BufferCommand command = m_buffer_commands.get(iseg); + iseg_next = command.m_next != -1 ? command.m_next : (iseg + 1) + % m_buffer_commands.size(); + if (command.m_type == 0) + continue;// deleted segment + + if (first) { + result_mp.startPath(command.m_from); + first = false; + } + + if (command.m_type == BufferCommand.Flags.enum_arc) {// arc + addJoin_(result_mp, command.m_center, command.m_from, + command.m_to, false, true); + } else { + result_mp.lineTo(command.m_to); + } + first = false; + } + } + + private int cleanupBufferCommands_() { + // The purpose of this function is to remove as many self intersections + // from the buffered shape as possible. + // The buffer works without cleanup also, but slower. + + if (m_helper_array == null) + m_helper_array = new Point2D[9]; + + int istart = 0; + for (int iseg = 0, nseg = m_buffer_commands.size(); iseg < nseg;) { + BufferCommand command = m_buffer_commands.get(iseg); + if ((command.m_type & BufferCommand.Flags.enum_connection) != 0) { + istart = iseg; + break; + } + + iseg = command.m_next; + } + + int iseg_next = istart + 1; + for (int iseg = istart; iseg_next != istart; iseg = iseg_next) { + BufferCommand command = m_buffer_commands.get(iseg); + iseg_next = command.m_next; + int count = 1; + BufferCommand command_next = null; + while (iseg_next != iseg) {// find next segement + command_next = m_buffer_commands.get(iseg_next); + if ((command_next.m_type & BufferCommand.Flags.enum_connection) != 0) + break; + + iseg_next = command_next.m_next; + count++; + } + + if (count == 1) { + // Next segment starts where this one ends. Skip this case as it + // is simple. + assert (command.m_to.isEqual(command_next.m_from)); + continue; + } + + if ((command.m_type & command_next.m_type) == BufferCommand.Flags.enum_line) {// simplest + // cleanup + // - + // intersect + // lines + if (m_helper_line_1 == null) { + m_helper_line_1 = new Line(); + m_helper_line_2 = new Line(); + } + m_helper_line_1.setStartXY(command.m_from); + m_helper_line_1.setEndXY(command.m_to); + m_helper_line_2.setStartXY(command_next.m_from); + m_helper_line_2.setEndXY(command_next.m_to); + + int count_ = m_helper_line_1.intersect(m_helper_line_2, + m_helper_array, null, null, m_small_tolerance); + if (count_ == 1) { + command.m_to.setCoords(m_helper_array[0]); + command_next.m_from.setCoords(m_helper_array[0]); + command.m_next = iseg_next;// skip until iseg_next + command_next.m_prev = iseg; + } else if (count_ == 2) {// TODO: this case needs improvement + } + } + } + + return istart; + } + + private boolean isGap_(Point2D pt_before, Point2D pt_current, + Point2D pt_after) { + Point2D v_gap = new Point2D(); + v_gap.sub(pt_after, pt_before); + double gap_length = v_gap.length(); + double sqr_delta = m_abs_distance * m_abs_distance - gap_length + * gap_length * 0.25; + if (sqr_delta > 0) { + double delta = Math.sqrt(sqr_delta); + v_gap.normalize(); + v_gap.rightPerpendicular(); + Point2D p = new Point2D(); + p.sub(pt_current, pt_before); + double d = p.dotProduct(v_gap); + if (d + delta >= m_abs_distance) { + return true; + } + } + + return false; + } + + private int filterPath_(EditShape edit_shape, int geom, int dir, + boolean closed) { + // **********************!!!!!!!!!!!!!!!!!!!!!!!!!!!! + // return 1; + + boolean bConvex = true; + for (int pass = 0; pass < 1; pass++) { + boolean b_filtered = false; + int ipath = edit_shape.getFirstPath(geom); + int isize = edit_shape.getPathSize(ipath); + if (isize == 0) + return 0; + + int ncount = isize; + if (isize < 3) + return 1; + + if (closed && !edit_shape.isClosedPath(ipath))// the path is closed + // only virtually + { + ncount = isize - 1; + } + + assert (dir == 1 || dir == -1); + int ivert = edit_shape.getFirstVertex(ipath); + if (!closed) + edit_shape.getNextVertex(ivert); + + int iprev = dir > 0 ? edit_shape.getPrevVertex(ivert) : edit_shape + .getNextVertex(ivert); + int inext = dir > 0 ? edit_shape.getNextVertex(ivert) : edit_shape + .getPrevVertex(ivert); + int ibefore = iprev; + boolean reload = true; + Point2D pt_current = new Point2D(), pt_after = new Point2D(), pt_before = new Point2D(), pt_before_before = new Point2D(), pt_middle = new Point2D(), pt_gap_last = new Point2D( + 0, 0); + Point2D v_after = new Point2D(), v_before = new Point2D(), v_gap = new Point2D(); + Point2D temp = new Point2D(); + double abs_d = m_abs_distance; + // When the path is open we cannot process the first and the last + // vertices, so we process size - 2. + // When the path is closed, we can process all vertices. + int iter_count = closed ? ncount : isize - 2; + int gap_counter = 0; + for (int iter = 0; iter < iter_count;) { + edit_shape.getXY(inext, pt_after); + + if (reload) { + edit_shape.getXY(ivert, pt_current); + edit_shape.getXY(iprev, pt_before); + ibefore = iprev; + } + + v_before.sub(pt_current, pt_before); + v_before.normalize(); + + v_after.sub(pt_after, pt_current); + v_after.normalize(); + + if (ibefore == inext) { + break; + } + + double cross = v_before.crossProduct(v_after); + double dot = v_before.dotProduct(v_after); + boolean bDoJoin = cross < 0 || (dot < 0 && cross == 0); + boolean b_write = true; + if (!bDoJoin) { + if (isGap_(pt_before, pt_current, pt_after)) { + pt_gap_last.setCoords(pt_after); + b_write = false; + ++gap_counter; + b_filtered = true; + } + + bConvex = false; + } + + if (b_write) { + if (gap_counter > 0) { + for (;;) {// re-test back + int ibefore_before = dir > 0 ? edit_shape + .getPrevVertex(ibefore) : edit_shape + .getNextVertex(ibefore); + if (ibefore_before == ivert) + break; + + edit_shape.getXY(ibefore_before, pt_before_before); + if (isGap_(pt_before_before, pt_before, pt_gap_last)) { + pt_before.setCoords(pt_before_before); + ibefore = ibefore_before; + b_write = false; + ++gap_counter; + continue; + } else { + if (ibefore_before != inext + && isGap_(pt_before_before, pt_before, + pt_after) + && isGap_(pt_before_before, pt_current, + pt_after)) {// now the current + // point is a part + // of the gap also. + // We retest it. + pt_before.setCoords(pt_before_before); + ibefore = ibefore_before; + b_write = false; + ++gap_counter; + } + } + break; + } + } + + if (!b_write) + continue;// retest forward + + if (gap_counter > 0) { + // remove all but one gap vertices. + int p = dir > 0 ? edit_shape.getPrevVertex(iprev) + : edit_shape.getNextVertex(iprev); + for (int i = 1; i < gap_counter; i++) { + int pp = dir > 0 ? edit_shape.getPrevVertex(p) + : edit_shape.getNextVertex(p); + edit_shape.removeVertex(p, true); + p = pp; + } + + v_gap.sub(pt_current, pt_before); + double gap_length = v_gap.length(); + double sqr_delta = abs_d * abs_d - gap_length + * gap_length * 0.25; + double delta = Math.sqrt(sqr_delta); + if (abs_d - delta > m_densify_dist * 0.5) { + pt_middle.add(pt_before, pt_current); + pt_middle.scale(0.5); + v_gap.normalize(); + v_gap.rightPerpendicular(); + temp.setCoords(v_gap); + temp.scale(abs_d - delta); + pt_middle.add(temp); + edit_shape.setXY(iprev, pt_middle); + } else { + // the gap is too short to be considered. Can close + // it with the straight segment; + edit_shape.removeVertex(iprev, true); + } + + gap_counter = 0; + } + + pt_before.setCoords(pt_current); + ibefore = ivert; + } + + pt_current.setCoords(pt_after); + iprev = ivert; + ivert = inext; + // reload = false; + inext = dir > 0 ? edit_shape.getNextVertex(ivert) : edit_shape + .getPrevVertex(ivert); + iter++; + reload = false; + } + + if (gap_counter > 0) { + int p = dir > 0 ? edit_shape.getPrevVertex(iprev) : edit_shape + .getNextVertex(iprev); + for (int i = 1; i < gap_counter; i++) { + int pp = dir > 0 ? edit_shape.getPrevVertex(p) : edit_shape + .getNextVertex(p); + edit_shape.removeVertex(p, true); + p = pp; + } + + pt_middle.add(pt_before, pt_current); + pt_middle.scale(0.5); + + v_gap.sub(pt_current, pt_before); + double gap_length = v_gap.length(); + double sqr_delta = abs_d * abs_d - gap_length * gap_length + * 0.25; + assert (sqr_delta > 0); + double delta = Math.sqrt(sqr_delta); + v_gap.normalize(); + v_gap.rightPerpendicular(); + temp.setCoords(v_gap); + temp.scale(abs_d - delta); + pt_middle.add(temp); + edit_shape.setXY(iprev, pt_middle); + } + + edit_shape.filterClosePoints(m_filter_tolerance, false); + + if (!b_filtered) + break; + } + + return 1; + } + + private boolean isDegeneratePath_(MultiPathImpl mp_impl, int ipath) { + if (mp_impl.getPathSize(ipath) == 1) + return true; + Envelope2D env = new Envelope2D(); + mp_impl.queryPathEnvelope2D(ipath, env); + if (Math.max(env.getWidth(), env.getHeight()) < m_densify_dist * 0.5) + return true; + + return false; + } + + private boolean isDegenerateGeometry_(Geometry geom) { + Envelope2D env = new Envelope2D(); + geom.queryEnvelope2D(env); + if (Math.max(env.getWidth(), env.getHeight()) < m_densify_dist * 0.5) + return true; + + return false; + } + + private Polyline preparePolyline_(Polyline input_geom) { + // Generalize it firstly using 25% of the densification deviation as a + // criterion. + Polyline generalized_polyline = (Polyline) ((OperatorGeneralize) OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Generalize)).execute( + input_geom, m_densify_dist * 0.25, false, m_progress_tracker); + + int path_point_count = 0; + for (int i = 0, npath = generalized_polyline.getPathCount(); i < npath; i++) { + path_point_count = Math.max(generalized_polyline.getPathSize(i), + path_point_count); + } + + if (path_point_count < 32) { + m_bfilter = false; + return generalized_polyline; + } else { + m_bfilter = true; + // If we apply a filter to the polyline, then we have to resolve all + // self intersections. + Polyline simple_polyline = (Polyline) (TopologicalOperations + .planarSimplify(generalized_polyline, m_small_tolerance, + false, true, m_progress_tracker)); + // Operator_factory_local::SaveJSONToTextFileDbg("c:/temp/buffer_simplify.txt", + // *simple_polyline, nullptr); + return simple_polyline; + } + } + + private void addCircle_(MultiPathImpl result_mp, Point point) { + // Uses same calculations for each of the quadrants, generating a + // symmetric distribution of points. + Point2D center = point.getXY(); + if (m_circle_template != null && !m_circle_template.isEmpty()) {// use + // template + // if + // available. + Point2D p = new Point2D(); + p.setCoords(m_circle_template.get(0)); + p.scaleAdd(m_abs_distance, center); + result_mp.startPath(p); + for (int i = 1, n = (int) m_circle_template.size(); i < n; i++) { + p.setCoords(m_circle_template.get(i)); + p.scaleAdd(m_abs_distance, center); + result_mp.lineTo(p); + } + return; + } + + // avoid unnecessary memory allocation for the circle template. Just do + // the point here. + + int N = calcN_(4); + int real_size = (N + 3) / 4; + double dA = (Math.PI * 0.5) / real_size; + // result_mp.reserve(real_size * 4); + + double dcos = Math.cos(dA); + double dsin = Math.sin(dA); + Point2D pt = new Point2D(); + for (int quadrant = 3; quadrant >= 0; quadrant--) { + pt.setCoords(0.0, m_abs_distance); + switch (quadrant) { + case 0: {// upper left quadrant + for (int i = 0; i < real_size; i++) { + result_mp.lineTo(pt.x + center.x, pt.y + center.y); + pt.rotateReverse(dcos, dsin); + } + break; + } + case 1: {// upper left quadrant + for (int i = 0; i < real_size; i++) {// m_circle_template.set(i + // + real_size * 1, + // Point_2D::construct(-pt.y, + // pt.x)); + result_mp.lineTo(-pt.y + center.x, pt.x + center.y); + pt.rotateReverse(dcos, dsin); + } + break; + } + case 2: {// lower left quadrant + // m_circle_template.set(i + real_size * 2, + // Point_2D::construct(-pt.x, -pt.y)); + for (int i = 0; i < real_size; i++) { + result_mp.lineTo(-pt.x + center.x, -pt.y + center.y); + pt.rotateReverse(dcos, dsin); + } + break; + } + default:// case 3: + {// lower right quadrant + // m_circle_template.set(i + real_size * 3, + // Point_2D::construct(pt.y, -pt.x)); + result_mp.startPath(pt.y + center.x, -pt.x + center.y);// we + // start + // at + // the + // quadrant + // 3. + // The + // first + // point + // is + // (0, + // -m_distance) + // + + // center + for (int i = 1; i < real_size; i++) { + pt.rotateReverse(dcos, dsin); + result_mp.lineTo(pt.y + center.x, -pt.x + center.y); + } + break; + } + } + + progress_(); + } + } + + private static Polygon setWeakSimple_(Polygon poly) { + ((MultiPathImpl) poly._getImpl()).setIsSimple( + MultiVertexGeometryImpl.GeometryXSimple.Weak, 0.0, false); + return poly; + } + + private Polygon setStrongSimple_(Polygon poly) { + ((MultiPathImpl) poly._getImpl()).setIsSimple( + MultiVertexGeometryImpl.GeometryXSimple.Strong, m_tolerance, + false); + ((MultiPathImpl) poly._getImpl())._updateOGCFlags(); + return poly; + } + +} diff --git a/src/com/esri/core/geometry/ByteBufferCursor.java b/src/com/esri/core/geometry/ByteBufferCursor.java new file mode 100644 index 00000000..53f0121a --- /dev/null +++ b/src/com/esri/core/geometry/ByteBufferCursor.java @@ -0,0 +1,50 @@ +/* + Copyright 1995-2013 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ +package com.esri.core.geometry; + +import java.nio.ByteBuffer; + +/** + * An abstract ByteBuffer Cursor class. + */ +abstract class ByteBufferCursor { + + /** + * Moves the cursor to the next ByteBuffer. Returns null when reached the + * end. + */ + public abstract ByteBuffer next(); + + /** + * Returns the ID of the current ByteBuffer. The ID is propagated across the + * operations (when possible). + * + * Returns an ID associated with the current Geometry. The ID is passed + * along and is returned by some operators to preserve relationship between + * the input and output geometry classes. It is not always possible to + * preserve an ID during an operation. + */ + public abstract int getByteBufferID(); + +} diff --git a/src/com/esri/core/geometry/ClassicSort.java b/src/com/esri/core/geometry/ClassicSort.java new file mode 100644 index 00000000..57691017 --- /dev/null +++ b/src/com/esri/core/geometry/ClassicSort.java @@ -0,0 +1,35 @@ +/* + Copyright 1995-2013 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ +/* + * To change this template, choose Tools | Templates + * and open the template in the editor. + */ +package com.esri.core.geometry; + +abstract class ClassicSort { + public abstract void userSort(int begin, int end, + AttributeStreamOfInt32 indices); + + public abstract double getValue(int index); +} diff --git a/src/com/esri/core/geometry/Clipper.java b/src/com/esri/core/geometry/Clipper.java new file mode 100644 index 00000000..0fe2e9e6 --- /dev/null +++ b/src/com/esri/core/geometry/Clipper.java @@ -0,0 +1,1268 @@ +/* + Copyright 1995-2013 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ +package com.esri.core.geometry; + +class Clipper { + Envelope2D m_extent; + EditShape m_shape; + int m_geometry; + int m_vertices_on_extent_index; + AttributeStreamOfInt32 m_vertices_on_extent; + + int checkSegmentIntersection_(Envelope2D seg_env, int side, + double clip_value) { + switch (side) { + case 0: + if (seg_env.xmin < clip_value && seg_env.xmax <= clip_value) { + return 0; // outside (or on the border) + } else if (seg_env.xmin >= clip_value) { + return 1;// inside + } else + return -1; // intersects + case 1: + if (seg_env.ymin < clip_value && seg_env.ymax <= clip_value) { + return 0; + } else if (seg_env.ymin >= clip_value) { + return 1; + } else + return -1; + case 2: + if (seg_env.xmin >= clip_value && seg_env.xmax > clip_value) { + return 0; + } else if (seg_env.xmax <= clip_value) { + return 1; + } else + return -1; + case 3: + if (seg_env.ymin >= clip_value && seg_env.ymax > clip_value) { + return 0; + } else if (seg_env.ymax <= clip_value) { + return 1; + } else + return -1; + } + assert (false);// cannot be here + return 0; + } + + MultiPath clipMultiPath2_(MultiPath multi_path_in, double tolerance, + double densify_dist) { + boolean b_is_polygon = multi_path_in.getType() == Geometry.Type.Polygon; + if (b_is_polygon) + return clipPolygon2_((Polygon) multi_path_in, tolerance, + densify_dist); + else + return clipPolyline_((Polyline) multi_path_in, tolerance); + } + + MultiPath clipPolygon2_(Polygon polygon_in, double tolerance, + double densify_dist) { + // If extent is degenerate, return 0. + if (m_extent.getWidth() == 0 || m_extent.getHeight() == 0) + return (MultiPath) polygon_in.createInstance(); + + Envelope2D orig_env2D = new Envelope2D(); + polygon_in.queryLooseEnvelope(orig_env2D); + + // m_shape = GCNEW Edit_shape(); + m_geometry = m_shape.addGeometry(polygon_in); + + // Forward decl for java port + Envelope2D seg_env = new Envelope2D(); + Envelope2D sub_seg_env = new Envelope2D(); + Point2D pt_1 = new Point2D(); + Point2D pt_2 = new Point2D(); + double[] result_ordinates = new double[9]; + double[] parameters = new double[9]; + SegmentBuffer sub_segment_buffer = new SegmentBuffer(); + Line line = new Line(); + AttributeStreamOfInt32 delete_candidates = new AttributeStreamOfInt32(0); + delete_candidates.reserve(Math.min(100, polygon_in.getPointCount())); + // clip the polygon successively by each plane + boolean b_all_outside = false; + for (int iclip_plane = 0; !b_all_outside && iclip_plane < 4; iclip_plane++) { + boolean b_intersects_plane = false; + boolean b_axis_x = (iclip_plane & 1) != 0; + double clip_value = 0; + switch (iclip_plane) { + case 0: + clip_value = m_extent.xmin; + b_intersects_plane = orig_env2D.xmin <= clip_value + && orig_env2D.xmax >= clip_value; + assert (b_intersects_plane || clip_value < orig_env2D.xmin); + break; + case 1: + clip_value = m_extent.ymin; + b_intersects_plane = orig_env2D.ymin <= clip_value + && orig_env2D.ymax >= clip_value; + assert (b_intersects_plane || clip_value < orig_env2D.ymin); + break; + case 2: + clip_value = m_extent.xmax; + b_intersects_plane = orig_env2D.xmin <= clip_value + && orig_env2D.xmax >= clip_value; + assert (b_intersects_plane || clip_value > orig_env2D.xmax); + break; + case 3: + clip_value = m_extent.ymax; + b_intersects_plane = orig_env2D.ymin <= clip_value + && orig_env2D.ymax >= clip_value; + assert (b_intersects_plane || clip_value > orig_env2D.ymax); + break; + } + + if (!b_intersects_plane) + continue;// Optimize for common case when only few sides of the + // clipper envelope intersect the geometry. + + b_all_outside = true; + for (int path = m_shape.getFirstPath(m_geometry); path != -1;) { + int inside = -1; + int firstinside = -1; + int first = m_shape.getFirstVertex(path); + int vertex = first; + do { + Segment segment = m_shape.getSegment(vertex); + if (segment == null) { + segment = line; + m_shape.getXY(vertex, pt_1); + segment.setStartXY(pt_1); + m_shape.getXY(m_shape.getNextVertex(vertex), pt_2); + segment.setEndXY(pt_2); + } + segment.queryEnvelope2D(seg_env); + int seg_plane_intersection_status = checkSegmentIntersection_( + seg_env, iclip_plane, clip_value); + int split_count = 0; + int next_vertex = -1; + + if (seg_plane_intersection_status == -1) // intersects plane + { + int count = segment.intersectionWithAxis2D(b_axis_x, + clip_value, result_ordinates, parameters); + if (count > 0) { + split_count = m_shape.splitSegment(vertex, + parameters, count); + } else { + assert (count == 0);// might be -1 when the segment + // is almost parallel to the + // clip lane. Just to see this + // happens. + split_count = 0; + } + + // add +1 to ensure we check the original segment if no + // split produced due to degeneracy. + // Also +1 is necessary to check the last segment of the + // split + split_count += 1;// split_count will never be 0 after + // this if-block. + + int split_vert = vertex; + int next_split_vert = m_shape.getNextVertex(split_vert); + for (int i = 0; i < split_count; i++) { + m_shape.getXY(split_vert, pt_1); + m_shape.getXY(next_split_vert, pt_2); + + Segment sub_seg = m_shape.getSegment(split_vert); + if (sub_seg == null) { + sub_seg = line; + sub_seg.setStartXY(pt_1); + sub_seg.setEndXY(pt_2); + } + + sub_seg.queryEnvelope2D(sub_seg_env); + int sub_segment_plane_intersection_status = checkSegmentIntersection_( + sub_seg_env, iclip_plane, clip_value); + if (sub_segment_plane_intersection_status == -1) { + // subsegment is intertsecting the plane. We + // need to snap one of the endpoints to ensure + // no intersection. + // TODO: ensure this works for curves. For + // curves we have to adjust the curve shape. + if (!b_axis_x) { + assert ((pt_1.x < clip_value && pt_2.x > clip_value) || (pt_1.x > clip_value && pt_2.x < clip_value)); + double d_1 = Math.abs(pt_1.x - clip_value); + double d_2 = Math.abs(pt_2.x - clip_value); + if (d_1 < d_2) { + pt_1.x = clip_value; + m_shape.setXY(split_vert, pt_1); + } else { + pt_2.x = clip_value; + m_shape.setXY(next_split_vert, pt_2); + } + } else { + assert ((pt_1.y < clip_value && pt_2.y > clip_value) || (pt_1.y > clip_value && pt_2.y < clip_value)); + double d_1 = Math.abs(pt_1.y - clip_value); + double d_2 = Math.abs(pt_2.y - clip_value); + if (d_1 < d_2) { + pt_1.y = clip_value; + m_shape.setXY(split_vert, pt_1); + } else { + pt_2.y = clip_value; + m_shape.setXY(next_split_vert, pt_2); + } + } + + // after the endpoint has been adjusted, recheck + // the segment. + sub_seg = m_shape.getSegment(split_vert); + if (sub_seg == null) { + sub_seg = line; + sub_seg.setStartXY(pt_1); + sub_seg.setEndXY(pt_2); + } + sub_seg.queryEnvelope2D(sub_seg_env); + sub_segment_plane_intersection_status = checkSegmentIntersection_( + sub_seg_env, iclip_plane, clip_value); + } + + assert (sub_segment_plane_intersection_status != -1); + + int old_inside = inside; + inside = sub_segment_plane_intersection_status; + if (firstinside == -1) + firstinside = inside; + + // add connections along the clipping plane line + if (old_inside == 0 && inside == 1) { + // going from outside to inside. Do nothing + } else if (old_inside == 1 && inside == 0) { + // going from inside to outside + } else if (old_inside == 0 && inside == 0) { + // staying outside + // remember the start point of the outside + // segment to be deleted. + delete_candidates.add(split_vert); // is a + // candidate + // to be + // deleted + } + + if (inside == 1) { + b_all_outside = false; + } + + split_vert = next_split_vert; + next_vertex = split_vert; + next_split_vert = m_shape + .getNextVertex(next_split_vert); + } + } + + if (split_count == 0) { + assert (seg_plane_intersection_status != -1);// cannot + // happen. + int old_inside = inside; + inside = seg_plane_intersection_status; + if (firstinside == -1) + firstinside = inside; + + if (old_inside == 0 && inside == 1) { + // going from outside to inside. + } else if (old_inside == 1 && inside == 0) { + // going from inside to outside + } else if (old_inside == 0 && inside == 0) { + // remember the start point of the outside segment + // to be deleted. + delete_candidates.add(vertex); // is a candidate to + // be deleted + } + + if (inside == 1) { + b_all_outside = false; + } + + next_vertex = m_shape.getNextVertex(vertex); + } + vertex = next_vertex; + } while (vertex != first); + + if (firstinside == 0 && inside == 0) {// first vertex need to be + // deleted. + delete_candidates.add(first); // is a candidate to be + // deleted + } + + for (int i = 0, n = delete_candidates.size(); i < n; i++) { + int delete_vert = delete_candidates.get(i); + m_shape.removeVertex(delete_vert, false); + } + delete_candidates.clear(false); + if (m_shape.getPathSize(path) < 3) { + path = m_shape.removePath(path); + } else { + path = m_shape.getNextPath(path); + } + } + } + + if (b_all_outside) + return (MultiPath) polygon_in.createInstance(); + + // After the clipping, we could have produced unwanted segment overlaps + // along the clipping envelope boundary. + // Detect and resolve that case if possible. + resolveBoundaryOverlaps_(); + if (densify_dist > 0) + densifyAlongClipExtent_(densify_dist); + + return (MultiPath) m_shape.getGeometry(m_geometry); + } + + MultiPath clipPolyline_(Polyline polyline_in, double tolerance) { + // Forward decl for java port + Envelope2D seg_env = new Envelope2D(); + Envelope2D sub_seg_env = new Envelope2D(); + double[] result_ordinates = new double[9]; + double[] parameters = new double[9]; + SegmentBuffer sub_segment_buffer = new SegmentBuffer(); + MultiPath result_poly = polyline_in; + Envelope2D orig_env2D = new Envelope2D(); + polyline_in.queryLooseEnvelope(orig_env2D); + for (int iclip_plane = 0; iclip_plane < 4; iclip_plane++) { + boolean b_intersects_plane = false; + boolean b_axis_x = (iclip_plane & 1) != 0; + double clip_value = 0; + switch (iclip_plane) { + case 0: + clip_value = m_extent.xmin; + b_intersects_plane = orig_env2D.xmin <= clip_value + && orig_env2D.xmax >= clip_value; + assert (b_intersects_plane || clip_value < orig_env2D.xmin); + break; + case 1: + clip_value = m_extent.ymin; + b_intersects_plane = orig_env2D.ymin <= clip_value + && orig_env2D.ymax >= clip_value; + assert (b_intersects_plane || clip_value < orig_env2D.ymin); + break; + case 2: + clip_value = m_extent.xmax; + b_intersects_plane = orig_env2D.xmin <= clip_value + && orig_env2D.xmax >= clip_value; + assert (b_intersects_plane || clip_value > orig_env2D.xmax); + break; + case 3: + clip_value = m_extent.ymax; + b_intersects_plane = orig_env2D.ymin <= clip_value + && orig_env2D.ymax >= clip_value; + assert (b_intersects_plane || clip_value > orig_env2D.ymax); + break; + } + + if (!b_intersects_plane) + continue;// Optimize for common case when only few sides of the + // clipper envelope intersect the geometry. + + MultiPath src_poly = result_poly; + result_poly = (MultiPath) polyline_in.createInstance(); + + MultiPathImpl mp_impl_src = (MultiPathImpl) src_poly._getImpl(); + SegmentIteratorImpl seg_iter = mp_impl_src.querySegmentIterator(); + seg_iter.resetToFirstPath(); + Point2D pt_prev; + Point2D pt = new Point2D(); + while (seg_iter.nextPath()) { + int inside = -1; + boolean b_start_new_path = true; + while (seg_iter.hasNextSegment()) { + Segment segment = seg_iter.nextSegment(); + segment.queryEnvelope2D(seg_env); + int seg_plane_intersection_status = checkSegmentIntersection_( + seg_env, iclip_plane, clip_value); + if (seg_plane_intersection_status == -1) // intersects plane + { + int count = segment.intersectionWithAxis2D(b_axis_x, + clip_value, result_ordinates, parameters); + if (count > 0) { + double t0 = 0.0; + pt_prev = segment.getStartXY(); + for (int i = 0; i <= count; i++) { + double t = i < count ? parameters[i] : 1.0; + if (t0 == t) + continue; + + segment.cut(t0, t, sub_segment_buffer); + Segment sub_seg = sub_segment_buffer.get(); + sub_seg.setStartXY(pt_prev); + if (i < count) {// snap to plane + if (b_axis_x) { + pt.x = result_ordinates[i]; + pt.y = clip_value; + } else { + pt.x = clip_value; + pt.y = result_ordinates[i]; + } + sub_seg.setEndXY(pt); + } + + sub_seg.queryEnvelope2D(sub_seg_env); + int sub_segment_plane_intersection_status = checkSegmentIntersection_( + sub_seg_env, iclip_plane, clip_value); + + if (sub_segment_plane_intersection_status == -1) { + // subsegment is intertsecting the plane. We + // need to snap one of the endpoints to + // ensure no intersection. + // TODO: ensure this works for curves. For + // curves we have to adjust the curve shape. + Point2D pt_1 = sub_seg.getStartXY(); + Point2D pt_2 = sub_seg.getEndXY(); + if (!b_axis_x) { + assert ((pt_1.x < clip_value && pt_2.x > clip_value) || (pt_1.x > clip_value && pt_2.x < clip_value)); + double d_1 = Math.abs(pt_1.x + - clip_value); + double d_2 = Math.abs(pt_2.x + - clip_value); + if (d_1 < d_2) { + pt_1.x = clip_value; + sub_seg.setStartXY(pt_1); + } else { + pt_2.x = clip_value; + sub_seg.setEndXY(pt_2); + } + } else { + assert ((pt_1.y < clip_value && pt_2.y > clip_value) || (pt_1.y > clip_value && pt_2.y < clip_value)); + double d_1 = Math.abs(pt_1.y + - clip_value); + double d_2 = Math.abs(pt_2.y + - clip_value); + if (d_1 < d_2) { + pt_1.y = clip_value; + sub_seg.setStartXY(pt_1); + } else { + pt_2.y = clip_value; + sub_seg.setEndXY(pt_2); + } + } + + // after the endpoint has been adjusted, + // recheck the segment. + sub_seg.queryEnvelope2D(sub_seg_env); + sub_segment_plane_intersection_status = checkSegmentIntersection_( + sub_seg_env, iclip_plane, + clip_value); + } + + assert (sub_segment_plane_intersection_status != -1); + + pt_prev = sub_seg.getEndXY(); + t0 = t; + + inside = sub_segment_plane_intersection_status; + if (inside == 1) { + result_poly.addSegment(sub_seg, + b_start_new_path); + b_start_new_path = false; + } else + b_start_new_path = true; + } + } + } else { + inside = seg_plane_intersection_status; + if (inside == 1) { + result_poly.addSegment(segment, b_start_new_path); + b_start_new_path = false; + } else + b_start_new_path = true; + } + } + } + } + + return result_poly; + } + + void resolveBoundaryOverlaps_() { + m_vertices_on_extent_index = -1; + splitSegments_(false, m_extent.xmin); + splitSegments_(false, m_extent.xmax); + splitSegments_(true, m_extent.ymin); + splitSegments_(true, m_extent.ymax); + + m_vertices_on_extent.resize(0); + m_vertices_on_extent.reserve(100); + m_vertices_on_extent_index = m_shape.createUserIndex(); + + Point2D pt = new Point2D(); + for (int path = m_shape.getFirstPath(m_geometry); path != -1; path = m_shape + .getNextPath(path)) { + int vertex = m_shape.getFirstVertex(path); + for (int ivert = 0, nvert = m_shape.getPathSize(path); ivert < nvert; ivert++, vertex = m_shape + .getNextVertex(vertex)) { + m_shape.getXY(vertex, pt); + if (m_extent.xmin == pt.x || m_extent.xmax == pt.x + || m_extent.ymin == pt.y || m_extent.ymax == pt.y) { + m_shape.setUserIndex(vertex, m_vertices_on_extent_index, + m_vertices_on_extent.size()); + m_vertices_on_extent.add(vertex); + } + } + } + // dbg_check_path_first_(); + resolveOverlaps_(false, m_extent.xmin); + // dbg_check_path_first_(); + resolveOverlaps_(false, m_extent.xmax); + // dbg_check_path_first_(); + resolveOverlaps_(true, m_extent.ymin); + // dbg_check_path_first_(); + resolveOverlaps_(true, m_extent.ymax); + fixPaths_(); + } + + void densifyAlongClipExtent_(double densify_dist) { + assert (densify_dist > 0); + Point2D pt_1 = new Point2D(); + Point2D pt_2 = new Point2D(); + double[] split_scalars = new double[2048]; + for (int path = m_shape.getFirstPath(m_geometry); path != -1; path = m_shape + .getNextPath(path)) { + int first_vertex = m_shape.getFirstVertex(path); + int vertex = first_vertex; + do { + int next_vertex = m_shape.getNextVertex(vertex); + m_shape.getXY(vertex, pt_1); + int b_densify_x = -1; + if (pt_1.x == m_extent.xmin) { + m_shape.getXY(next_vertex, pt_2); + if (pt_2.x == m_extent.xmin) { + b_densify_x = 1; + } + } else if (pt_1.x == m_extent.xmax) { + m_shape.getXY(next_vertex, pt_2); + if (pt_2.x == m_extent.xmax) { + b_densify_x = 1; + } + } + + if (pt_1.y == m_extent.ymin) { + m_shape.getXY(next_vertex, pt_2); + if (pt_2.y == m_extent.ymin) { + b_densify_x = 0; + } + } else if (pt_1.y == m_extent.ymax) { + m_shape.getXY(next_vertex, pt_2); + if (pt_2.y == m_extent.ymax) { + b_densify_x = 0; + } + } + + if (b_densify_x == -1) { + vertex = next_vertex; + continue; + } + + double len = Point2D.distance(pt_1, pt_2); + int num = (int) Math.min(Math.ceil(len / densify_dist), 2048.0); + if (num <= 1) { + vertex = next_vertex; + continue; + } + + for (int i = 1; i < num; i++) { + split_scalars[i - 1] = (1.0 * i) / num; + } + + int actual_splits = m_shape.splitSegment(vertex, split_scalars, + num - 1); + assert (actual_splits == num - 1); + vertex = next_vertex; + } while (vertex != first_vertex); + } + } + + void splitSegments_(boolean b_axis_x, double clip_value) { + // After the clipping, we could have produced unwanted segment overlaps + // along the clipping envelope boundary. + // Detect and resolve that case if possible. + int usage_index = m_shape.createUserIndex(); + Point2D pt = new Point2D(); + AttributeStreamOfInt32 sorted_vertices = new AttributeStreamOfInt32(0); + sorted_vertices.reserve(100); + for (int path = m_shape.getFirstPath(m_geometry); path != -1; path = m_shape + .getNextPath(path)) { + int vertex = m_shape.getFirstVertex(path); + for (int ivert = 0, nvert = m_shape.getPathSize(path); ivert < nvert; ivert++) { + int next_vertex = m_shape.getNextVertex(vertex); + m_shape.getXY(vertex, pt); + if (b_axis_x ? pt.y == clip_value : pt.x == clip_value) { + m_shape.getXY(next_vertex, pt); + if (b_axis_x ? pt.y == clip_value : pt.x == clip_value) { + if (m_shape.getUserIndex(vertex, usage_index) != 1) { + sorted_vertices.add(vertex); + m_shape.setUserIndex(vertex, usage_index, 1); + } + + if (m_shape.getUserIndex(next_vertex, usage_index) != 1) { + sorted_vertices.add(next_vertex); + m_shape.setUserIndex(next_vertex, usage_index, 1); + } + } + } + vertex = next_vertex; + } + } + + m_shape.removeUserIndex(usage_index); + if (sorted_vertices.size() < 4) { + return; + } + + // SORTDYNAMICARRAYEX(&sorted_vertices, int, 0, sorted_vertices.size(), + // Clipper_vertex_comparer, this); + sorted_vertices.Sort(0, sorted_vertices.size(), + new ClipperVertexComparer(this)); + // std::sort(sorted_vertices.get_ptr(), sorted_vertices.get_ptr() + + // sorted_vertices.size(), Clipper_vertex_comparer(this)); + + Point2D pt_tmp = new Point2D(); // forward declare for java port + // optimization + Point2D pt_0 = new Point2D(); + Point2D pt_1 = new Point2D(); + pt_0.setNaN(); + int index_0 = -1; + AttributeStreamOfInt32 active_intervals = new AttributeStreamOfInt32(0); + AttributeStreamOfInt32 new_active_intervals = new AttributeStreamOfInt32( + 0); + + int node1 = m_shape.createUserIndex(); + int node2 = m_shape.createUserIndex(); + for (int index = 0, n = sorted_vertices.size(); index < n; index++) { + int vert = sorted_vertices.get(index); + m_shape.getXY(vert, pt); + if (!pt.isEqual(pt_0)) { + if (index_0 != -1) { + // add new intervals, that started at pt_0 + for (int i = index_0; i < index; i++) { + int v = sorted_vertices.get(i); + int nextv = m_shape.getNextVertex(v); + int prevv = m_shape.getPrevVertex(v); + boolean bAdded = false; + if (compareVertices_(v, nextv) < 0) { + m_shape.getXY(nextv, pt_tmp); + if (b_axis_x ? pt_tmp.y == clip_value + : pt_tmp.x == clip_value) { + active_intervals.add(v); + bAdded = true; + m_shape.setUserIndex(v, node2, 1); + } + } + if (compareVertices_(v, prevv) < 0) { + m_shape.getXY(prevv, pt_tmp); + if (b_axis_x ? pt_tmp.y == clip_value + : pt_tmp.x == clip_value) { + if (!bAdded) + active_intervals.add(v); + m_shape.setUserIndex(v, node1, 1); + } + } + } + + // Split all active intervals at new point + for (int ia = 0, na = active_intervals.size(); ia < na; ia++) { + int v = active_intervals.get(ia); + int n_1 = m_shape.getUserIndex(v, node1); + int n_2 = m_shape.getUserIndex(v, node2); + if (n_1 == 1) { + int prevv = m_shape.getPrevVertex(v); + m_shape.getXY(prevv, pt_1); + double[] t = new double[1]; + t[0] = 0; + if (!pt_1.isEqual(pt)) {// Split the active segment + double active_segment_length = Point2D + .distance(pt_0, pt_1); + t[0] = Point2D.distance(pt_1, pt) + / active_segment_length; + assert (t[0] >= 0 && t[0] <= 1.0); + if (t[0] == 0) + t[0] = NumberUtils.doubleEps();// some + // roundoff + // issue. + // split + // anyway. + else if (t[0] == 1.0) { + t[0] = 1.0 - NumberUtils.doubleEps();// some + // roundoff + // issue. + // split + // anyway. + assert (t[0] != 1.0); + } + + int split_count = m_shape.splitSegment(prevv, + t, 1); + assert (split_count > 0); + int v_1 = m_shape.getPrevVertex(v); + m_shape.setXY(v_1, pt); + new_active_intervals.add(v_1); + m_shape.setUserIndex(v_1, node1, 1); + m_shape.setUserIndex(v_1, node2, -1); + } else { + // The active segment ends at the current point. + // We skip it, and it goes away. + } + } + if (n_2 == 1) { + int nextv = m_shape.getNextVertex(v); + m_shape.getXY(nextv, pt_1); + double[] t = new double[1]; + t[0] = 0; + if (!pt_1.isEqual(pt)) { + double active_segment_length = Point2D + .distance(pt_0, pt_1); + t[0] = Point2D.distance(pt_0, pt) + / active_segment_length; + assert (t[0] >= 0 && t[0] <= 1.0); + if (t[0] == 0) + t[0] = NumberUtils.doubleEps();// some + // roundoff + // issue. + // split + // anyway. + else if (t[0] == 1.0) { + t[0] = 1.0 - NumberUtils.doubleEps();// some + // roundoff + // issue. + // split + // anyway. + assert (t[0] != 1.0); + } + + int split_count = m_shape.splitSegment(v, t, 1); + assert (split_count > 0); + int v_1 = m_shape.getNextVertex(v); + m_shape.setXY(v_1, pt); + new_active_intervals.add(v_1); + m_shape.setUserIndex(v_1, node1, -1); + m_shape.setUserIndex(v_1, node2, 1); + } + } + } + + AttributeStreamOfInt32 tmp = active_intervals; + active_intervals = new_active_intervals; + new_active_intervals = tmp; + new_active_intervals.clear(false); + } + + index_0 = index; + pt_0.setCoords(pt); + } + } + + m_shape.removeUserIndex(node1); + m_shape.removeUserIndex(node2); + } + + void resolveOverlaps_(boolean b_axis_x, double clip_value) { + // Along the envelope boundary there could be overlapped segments. + // Example, exterior ring with a hole is cut with a line, that + // passes through the center of the hole. + // Detect pairs of opposite overlapping segments and get rid of them + Point2D pt = new Point2D(); + AttributeStreamOfInt32 sorted_vertices = new AttributeStreamOfInt32(0); + sorted_vertices.reserve(100); + int sorted_index = m_shape.createUserIndex(); + // DEBUGPRINTF(L"ee\n"); + for (int ivert = 0, nvert = m_vertices_on_extent.size(); ivert < nvert; ivert++) { + int vertex = m_vertices_on_extent.get(ivert); + if (vertex == -1) + continue; + + int next_vertex = m_shape.getNextVertex(vertex); + m_shape.getXY(vertex, pt); + // DEBUGPRINTF(L"%f\t%f\n", pt.x, pt.y); + if (b_axis_x ? pt.y == clip_value : pt.x == clip_value) { + m_shape.getXY(next_vertex, pt); + if (b_axis_x ? pt.y == clip_value : pt.x == clip_value) { + assert (m_shape.getUserIndex(next_vertex, + m_vertices_on_extent_index) != -1); + if (m_shape.getUserIndex(vertex, sorted_index) != -2) { + sorted_vertices.add(vertex);// remember the vertex. The + // attached segment belongs + // to the given clip plane. + m_shape.setUserIndex(vertex, sorted_index, -2); + } + + if (m_shape.getUserIndex(next_vertex, sorted_index) != -2) { + sorted_vertices.add(next_vertex); + m_shape.setUserIndex(next_vertex, sorted_index, -2); + } + } + } + } + + if (sorted_vertices.size() == 0) { + m_shape.removeUserIndex(sorted_index); + return; + } + + sorted_vertices.Sort(0, sorted_vertices.size(), + new ClipperVertexComparer(this)); + // std::sort(sorted_vertices.get_ptr(), sorted_vertices.get_ptr() + + // sorted_vertices.size(), Clipper_vertex_comparer(this)); + + // DEBUGPRINTF(L"**\n"); + for (int index = 0, n = sorted_vertices.size(); index < n; index++) { + int vert = sorted_vertices.get(index); + m_shape.setUserIndex(vert, sorted_index, index); + // Point_2D pt; + // m_shape.get_xy(vert, pt); + // DEBUGPRINTF(L"%f\t%f\t%d\n", pt.x, pt.y, vert); + } + + Point2D pt_tmp = new Point2D(); + Point2D pt_0 = new Point2D(); + pt_0.setNaN(); + int index_0 = -1; + for (int index = 0, n = sorted_vertices.size(); index < n; index++) { + int vert = sorted_vertices.get(index); + if (vert == -1) + continue; + + m_shape.getXY(vert, pt); + if (!pt.isEqual(pt_0)) { + if (index_0 != -1) { + while (true) { + boolean b_overlap_resolved = false; + int index_to = index - index_0 > 1 ? index - 1 : index; + for (int i = index_0; i < index_to; i++) { + int v = sorted_vertices.get(i); + if (v == -1) + continue; + int nextv = -1; + int nv = m_shape.getNextVertex(v); + if (compareVertices_(v, nv) < 0) { + m_shape.getXY(nv, pt_tmp); + if (b_axis_x ? pt_tmp.y == clip_value + : pt_tmp.x == clip_value) + nextv = nv; + } + int prevv = -1; + int pv = m_shape.getPrevVertex(v); + if (compareVertices_(v, pv) < 0) { + m_shape.getXY(pv, pt_tmp); + if (b_axis_x ? pt_tmp.y == clip_value + : pt_tmp.x == clip_value) + prevv = pv; + } + + if (nextv != -1 && prevv != -1) { + // we have a cusp here. remove the vertex. + beforeRemoveVertex_(v, sorted_vertices, + sorted_index); + m_shape.removeVertex(v, false); + beforeRemoveVertex_(nextv, sorted_vertices, + sorted_index); + m_shape.removeVertex(nextv, false); + b_overlap_resolved = true; + continue; + } + + if (nextv == -1 && prevv == -1) + continue; + + for (int j = i + 1; j < index; j++) { + int v_1 = sorted_vertices.get(j); + if (v_1 == -1) + continue; + int nv1 = m_shape.getNextVertex(v_1); + int nextv1 = -1; + if (compareVertices_(v_1, nv1) < 0) { + m_shape.getXY(nv1, pt_tmp); + if (b_axis_x ? pt_tmp.y == clip_value + : pt_tmp.x == clip_value) + nextv1 = nv1; + } + + int pv1 = m_shape.getPrevVertex(v_1); + int prevv_1 = -1; + if (compareVertices_(v_1, pv1) < 0) { + m_shape.getXY(pv1, pt_tmp); + if (b_axis_x ? pt_tmp.y == clip_value + : pt_tmp.x == clip_value) + prevv_1 = pv1; + } + if (nextv1 != -1 && prevv_1 != -1) { + // we have a cusp here. remove the vertex. + beforeRemoveVertex_(v_1, sorted_vertices, + sorted_index); + m_shape.removeVertex(v_1, false); + beforeRemoveVertex_(nextv1, + sorted_vertices, sorted_index); + m_shape.removeVertex(nextv1, false); + b_overlap_resolved = true; + break; + } + if (nextv != -1 && prevv_1 != -1) { + removeOverlap_(sorted_vertices, v, nextv, + v_1, prevv_1, sorted_index); + b_overlap_resolved = true; + break; + } else if (prevv != -1 && nextv1 != -1) { + removeOverlap_(sorted_vertices, v_1, + nextv1, v, prevv, sorted_index); + b_overlap_resolved = true; + break; + } + } + + if (b_overlap_resolved) + break; + } + + if (!b_overlap_resolved) + break; + } + } + + index_0 = index; + pt_0.setCoords(pt); + } + } + + m_shape.removeUserIndex(sorted_index); + } + + void beforeRemoveVertex_(int v_1, AttributeStreamOfInt32 sorted_vertices, + int sorted_index) { + int ind = m_shape.getUserIndex(v_1, sorted_index); + sorted_vertices.set(ind, -1); + ind = m_shape.getUserIndex(v_1, m_vertices_on_extent_index); + m_vertices_on_extent.set(ind, -1); + int path = m_shape.getPathFromVertex(v_1); + if (path != -1) { + int first = m_shape.getFirstVertex(path); + if (first == v_1) { + m_shape.setFirstVertex_(path, -1); + m_shape.setLastVertex_(path, -1); + } + } + } + + void removeOverlap_(AttributeStreamOfInt32 sorted_vertices, int v, + int nextv, int v_1, int prevv_1, int sorted_index) { + assert (m_shape.isEqualXY(v, v_1)); + assert (m_shape.isEqualXY(nextv, prevv_1)); + assert (m_shape.getNextVertex(v) == nextv); + assert (m_shape.getNextVertex(prevv_1) == v_1); + m_shape.setNextVertex_(v, v_1); + m_shape.setPrevVertex_(v_1, v); + m_shape.setPrevVertex_(nextv, prevv_1); + m_shape.setNextVertex_(prevv_1, nextv); + + beforeRemoveVertex_(v_1, sorted_vertices, sorted_index); + m_shape.removeVertexInternal_(v_1, false); + beforeRemoveVertex_(prevv_1, sorted_vertices, sorted_index); + m_shape.removeVertexInternal_(prevv_1, true); + } + + void fixPaths_() { + for (int ivert = 0, nvert = m_vertices_on_extent.size(); ivert < nvert; ivert++) { + int vertex = m_vertices_on_extent.get(ivert); + if (vertex != -1) + m_shape.setPathToVertex_(vertex, -1); + } + + int path_count = 0; + int geometry_size = 0; + for (int path = m_shape.getFirstPath(m_geometry); path != -1;) { + int first = m_shape.getFirstVertex(path); + if (first == -1 || path != m_shape.getPathFromVertex(first)) { // The + // path's + // first + // vertex + // has + // been + // deleted. + // Or + // the + // path + // first + // vertex + // is + // now + // part + // of + // another + // path. + // We + // have + // to + // delete + // such + // path + // object. + int p = path; + path = m_shape.getNextPath(path); + m_shape.setFirstVertex_(p, -1); + m_shape.removePathOnly_(p); + continue; + } + assert (path == m_shape.getPathFromVertex(first)); + int vertex = first; + int path_size = 0; + do { + m_shape.setPathToVertex_(vertex, path); + path_size++; + vertex = m_shape.getNextVertex(vertex); + } while (vertex != first); + + if (path_size <= 2) { + int ind = m_shape.getUserIndex(first, + m_vertices_on_extent_index); + m_vertices_on_extent.set(ind, -1); + int nv = m_shape.removeVertex(first, false); + if (path_size == 2) { + ind = m_shape.getUserIndex(nv, m_vertices_on_extent_index); + m_vertices_on_extent.set(ind, -1); + m_shape.removeVertex(nv, false); + } + int p = path; + path = m_shape.getNextPath(path); + m_shape.setFirstVertex_(p, -1); + m_shape.removePathOnly_(p); + continue; + } + + m_shape.setRingAreaValid_(path, false); + m_shape.setLastVertex_(path, m_shape.getPrevVertex(first)); + m_shape.setPathSize_(path, path_size); + geometry_size += path_size; + path_count++; + path = m_shape.getNextPath(path); + } + + for (int ivert = 0, nvert = m_vertices_on_extent.size(); ivert < nvert; ivert++) { + int vertex = m_vertices_on_extent.get(ivert); + if (vertex == -1) + continue; + int path = m_shape.getPathFromVertex(vertex); + if (path != -1) + continue; + + path = m_shape.insertPath(m_geometry, -1); + int path_size = 0; + int first = vertex; + do { + m_shape.setPathToVertex_(vertex, path); + path_size++; + vertex = m_shape.getNextVertex(vertex); + } while (vertex != first); + + if (path_size <= 2) { + int ind = m_shape.getUserIndex(first, + m_vertices_on_extent_index); + m_vertices_on_extent.set(ind, -1); + int nv = m_shape.removeVertex(first, false); + if (path_size == 2) { + ind = m_shape.getUserIndex(nv, m_vertices_on_extent_index); + m_vertices_on_extent.set(ind, -1); + m_shape.removeVertex(nv, false); + } + + int p = path; + path = m_shape.getNextPath(path); + m_shape.setFirstVertex_(p, -1); + m_shape.removePathOnly_(p); + continue; + } + + m_shape.setClosedPath(path, true); + m_shape.setPathSize_(path, path_size); + m_shape.setFirstVertex_(path, first); + m_shape.setLastVertex_(path, m_shape.getPrevVertex(first)); + m_shape.setRingAreaValid_(path, false); + geometry_size += path_size; + path_count++; + } + + m_shape.setGeometryPathCount_(m_geometry, path_count); + m_shape.setGeometryVertexCount_(m_geometry, geometry_size); + + int total_point_count = 0; + for (int geometry = m_shape.getFirstGeometry(); geometry != -1; geometry = m_shape + .getNextGeometry(geometry)) { + total_point_count += m_shape.getPointCount(geometry); + } + + m_shape.setTotalPointCount_(total_point_count); + } + + static Geometry clipMultiPath_(MultiPath multipath, Envelope2D extent, + double tolerance, double densify_dist) { + Clipper clipper = new Clipper(extent); + return clipper.clipMultiPath2_(multipath, tolerance, densify_dist); + } + + Clipper(Envelope2D extent) { + m_extent = extent; + m_shape = new EditShape(); + m_vertices_on_extent = new AttributeStreamOfInt32(0); + } + + // static std::shared_ptr create_polygon_from_polyline(const + // std::shared_ptr& polyline, const Envelope_2D& env_2D, bool + // add_envelope, double tolerance, double densify_dist, int + // corner_is_inside); + static Geometry clip(Geometry geometry, Envelope2D extent, + double tolerance, double densify_dist) { + if (geometry.isEmpty()) + return geometry; + + if (extent.isEmpty()) + return geometry.createInstance(); // return an empty geometry + + int geomtype = geometry.getType().value(); + + // Test firstly the simplest geometry types point and envelope. + // After that we'll check the envelope intersection for the optimization + if (geomtype == Geometry.Type.Point.value()) { + Point2D pt = ((Point) geometry).getXY(); + if (extent.contains(pt)) + return geometry; + else + return geometry.createInstance(); // return an empty geometry + } else if (geomtype == Geometry.Type.Envelope.value()) { + Envelope2D env = new Envelope2D(); + geometry.queryEnvelope2D(env); + if (env.intersect(extent)) { + Envelope result_env = new Envelope(); + geometry.copyTo(result_env); + result_env.setEnvelope2D(env); + return result_env; + } else + return geometry.createInstance(); // return an empty geometry + } + + // Test the geometry envelope + Envelope2D env_2D = new Envelope2D(); + geometry.queryLooseEnvelope2D(env_2D); + if (extent.contains(env_2D)) + return geometry;// completely inside of bounds + if (!extent.isIntersecting(env_2D)) + return geometry.createInstance();// outside of bounds. return empty + // geometry. + + MultiVertexGeometryImpl impl = (MultiVertexGeometryImpl) geometry + ._getImpl(); + GeometryAccelerators accel = impl._getAccelerators(); + if (accel != null) { + RasterizedGeometry2D rgeom = accel.getRasterizedGeometry(); + if (rgeom != null) { + RasterizedGeometry2D.HitType hit = rgeom + .queryEnvelopeInGeometry(extent); + if (hit == RasterizedGeometry2D.HitType.Inside) { + if (geomtype != Geometry.Type.Polygon.value()) + throw new GeometryException("internal error"); + + Polygon poly = new Polygon(geometry.getDescription()); + poly.addEnvelope(extent, false); + return poly; + } else if (hit == RasterizedGeometry2D.HitType.Outside) { + return geometry.createInstance();// outside of bounds. + // return empty + // geometry. + } + } + } + + switch (geomtype) { + case Geometry.GeometryType.MultiPoint: { + MultiPoint multi_point = (MultiPoint) geometry; + MultiPoint multi_point_out = null; + int npoints = multi_point.getPointCount(); + AttributeStreamOfDbl xy = (AttributeStreamOfDbl) ((MultiPointImpl) multi_point + ._getImpl()) + .getAttributeStreamRef(VertexDescription.Semantics.POSITION); + // create the new geometry only if there are points that has been + // clipped out. + // If all vertices are inside of the envelope, it returns the input + // multipoint. + int ipoints0 = 0; + for (int ipoints = 0; ipoints < npoints; ipoints++) { + Point2D pt = new Point2D(); + xy.read(2 * ipoints, pt); + + if (!extent.contains(pt)) {// vertex is outside of the envelope + if (ipoints0 == 0) + multi_point_out = (MultiPoint) multi_point + .createInstance(); + + if (ipoints0 < ipoints) + multi_point_out.add(multi_point, ipoints0, ipoints); + + ipoints0 = ipoints + 1;// ipoints0 contains index of vertex + // right after the last clipped out + // vertex. + } + } + + // add the rest of the batch to the result multipoint (only if + // something has been already clipped out) + if (ipoints0 > 0) + multi_point_out.add(multi_point, ipoints0, npoints); + + if (ipoints0 == 0) + return multi_point;// everything is inside, so return the input + // geometry + else + return multi_point_out;// clipping has happend, return the + // clipped geometry + } + case Geometry.GeometryType.Polygon: + case Geometry.GeometryType.Polyline: + return clipMultiPath_((MultiPath) geometry, extent, tolerance, + densify_dist); + default: + assert (false); + throw new GeometryException("internal error"); + } + } + + int compareVertices_(int v_1, int v_2) { + Point2D pt_1 = new Point2D(); + m_shape.getXY(v_1, pt_1); + Point2D pt_2 = new Point2D(); + m_shape.getXY(v_2, pt_2); + int res = pt_1.compare(pt_2); + return res; + } + + static final class ClipperVertexComparer extends + AttributeStreamOfInt32.IntComparator { + Clipper m_clipper; + + ClipperVertexComparer(Clipper clipper) { + m_clipper = clipper; + } + + @Override + public int compare(int v1, int v2) { + return m_clipper.compareVertices_(v1, v2); + } + + } +} diff --git a/src/com/esri/core/geometry/Clusterer.java b/src/com/esri/core/geometry/Clusterer.java new file mode 100644 index 00000000..9fb3cb46 --- /dev/null +++ b/src/com/esri/core/geometry/Clusterer.java @@ -0,0 +1,584 @@ +/* + Copyright 1995-2013 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ +package com.esri.core.geometry; + +/** + * Implementation for the vertex clustering. + * + * Used by the TopoGraph and Simplify. + */ +final class Clusterer { + // Clusters vertices of the shape. Returns True, if some vertices were moved + // (clustered). + // Uses reciprocal clustering (cluster vertices that are mutual nearest + // neighbours) + static boolean executeReciprocal(EditShape shape, double tolerance) { + Clusterer clusterer = new Clusterer(); + clusterer.m_shape = shape; + clusterer.m_tolerance = tolerance; + clusterer.m_cell_size = 2 * tolerance; + return clusterer.clusterReciprocal_(); + } + + // Clusters vertices of the shape. Returns True, if some vertices were moved + // (clustered). + // Uses non-reciprocal clustering (cluster any vertices that are closer than + // the tolerance in the first-found-first-clustered order) + static boolean executeNonReciprocal(EditShape shape, double tolerance) { + Clusterer clusterer = new Clusterer(); + clusterer.m_shape = shape; + clusterer.m_tolerance = tolerance; + clusterer.m_cell_size = 2 * tolerance;// revisit this value. Probably + // should be m_tolerance? + return clusterer.clusterNonReciprocal_(); + } + + // Use b_conservative == True for simplify, and False for IsSimple. This + // makes sure Simplified shape is more robust to transformations. + static boolean isClusterCandidate(double x_1, double y1, double x2, + double y2, double tolerance) { + double dx = x_1 - x2; + double dy = y1 - y2; + return Math.sqrt(dx * dx + dy * dy) <= tolerance; + } + + Point2D m_origin = new Point2D(); + double m_tolerance; + double m_cell_size; + int[] m_bucket_array = new int[4];// temporary 4 element array + int m_dbg_candidate_check_count; + int m_hash_values; + + static int hashFunction_(double xi, double yi) { + int h = NumberUtils.hash(xi); + return NumberUtils.hash(h, yi); + } + + class ClusterHashFunction extends IndexHashTable.HashFunction { + IndexMultiList m_clusters; + EditShape m_shape; + double m_tolerance; + double m_cell_size; + Point2D m_origin = new Point2D(); + Point2D m_pt = new Point2D(); + Point2D m_pt_2 = new Point2D(); + int m_hash_values; + + public ClusterHashFunction(IndexMultiList clusters, EditShape shape, + Point2D origin, double tolerance, double cell_size, + int hash_values) { + m_clusters = clusters; + m_shape = shape; + m_tolerance = tolerance; + m_cell_size = cell_size; + m_origin = origin; + m_hash_values = hash_values; + m_pt.setNaN(); + m_pt_2.setNaN(); + } + + @Override + public int getHash(int element) { + int vertex = m_clusters.getFirstElement(element); + return m_shape.getUserIndex(vertex, m_hash_values); + } + + int calculateHash(int element) { + int vertex = m_clusters.getFirstElement(element); + m_shape.getXY(vertex, m_pt); + double dx = m_pt.x - m_origin.x; + double xi = Math.round(dx / m_cell_size); + double dy = m_pt.y - m_origin.y; + double yi = Math.round(dy / m_cell_size); + return hashFunction_(xi, yi); + } + + @Override + public boolean equal(int element_1, int element_2) { + int xyindex_1 = m_clusters.getFirstElement(element_1); + m_shape.getXY(xyindex_1, m_pt); + int xyindex_2 = m_clusters.getFirstElement(element_2); + m_shape.getXY(xyindex_2, m_pt_2); + return isClusterCandidate(m_pt.x, m_pt.y, m_pt_2.x, m_pt_2.y, + m_tolerance); + } + + @Override + public int getHash(Object element_descriptor) { + return 0; + } + + @Override + public boolean equal(Object element_descriptor, int element) { + return false; + } + }; + + EditShape m_shape; + IndexMultiList m_clusters; + ClusterHashFunction m_hash_function; + IndexHashTable m_hash_table; + + static class ClusterCandidate { + public int cluster; + double distance; + }; + + void getNearestNeighbourCandidate_(int xyindex, Point2D pointOfInterest, + int bucket_ptr, ClusterCandidate candidate) { + candidate.cluster = IndexMultiList.nullNode(); + candidate.distance = NumberUtils.doubleMax(); + + Point2D pt = new Point2D(); + for (int node = bucket_ptr; node != IndexHashTable.nullNode(); node = m_hash_table + .getNextInBucket(node)) { + int cluster = m_hash_table.getElement(node); + int xyind = m_clusters.getFirstElement(cluster); + if (xyindex == xyind) + continue; + m_shape.getXY(xyind, pt); + if (isClusterCandidate(pointOfInterest.x, pointOfInterest.y, pt.x, + pt.y, m_tolerance)) { + pt.sub(pointOfInterest); + double l = pt.length(); + if (l < candidate.distance) { + candidate.distance = l; + candidate.cluster = cluster; + } + } + } + } + + void findClusterCandidate_(int xyindex, ClusterCandidate candidate) { + Point2D pointOfInterest = new Point2D(); + m_shape.getXY(xyindex, pointOfInterest); + double x_0 = pointOfInterest.x - m_origin.x; + double x = x_0 / m_cell_size; + double y0 = pointOfInterest.y - m_origin.y; + double y = y0 / m_cell_size; + + double xi = Math.round(x - 0.5); + double yi = Math.round(y - 0.5); + + // find the nearest neighbour in the 4 neigbouring cells. + + candidate.cluster = IndexHashTable.nullNode(); + candidate.distance = NumberUtils.doubleMax(); + ClusterCandidate c = new ClusterCandidate(); + for (double dx = 0; dx <= 1.0; dx += 1.0) { + for (double dy = 0; dy <= 1.0; dy += 1.0) { + int bucket_ptr = m_hash_table.getFirstInBucket(hashFunction_(xi + + dx, yi + dy)); + if (bucket_ptr != IndexHashTable.nullNode()) { + getNearestNeighbourCandidate_(xyindex, pointOfInterest, + bucket_ptr, c); + if (c.cluster != IndexHashTable.nullNode() + && c.distance < candidate.distance) { + candidate = c; + } + } + } + } + } + + void collectClusterCandidates_(int xyindex, + AttributeStreamOfInt32 candidates) { + Point2D pointOfInterest = new Point2D(); + m_shape.getXY(xyindex, pointOfInterest); + double x_0 = pointOfInterest.x - m_origin.x; + double x = x_0 / m_cell_size; + double y0 = pointOfInterest.y - m_origin.y; + double y = y0 / m_cell_size; + + double xi = Math.round(x - 0.5); + double yi = Math.round(y - 0.5); + for (int i = 0; i < 4; i++) + m_bucket_array[i] = -1; + int bucket_count = 0; + // find all nearest neighbours in the 4 neigbouring cells. + // Note, because we check four neighbours, there should be 4 times more + // bins in the hash table to reduce collision probability in this loop. + for (double dx = 0; dx <= 1.0; dx += 1.0) { + for (double dy = 0; dy <= 1.0; dy += 1.0) { + int bucket_ptr = m_hash_table.getFirstInBucket(hashFunction_(xi + + dx, yi + dy)); + if (bucket_ptr != IndexHashTable.nullNode()) { + // Check if we already have this bucket. + // There could be a hash collision for neighbouring buckets. + for (int j = 0; j < bucket_count; j++) { + if (m_bucket_array[j] == bucket_ptr) { + bucket_ptr = -1;// hash values for two neighbouring + // cells have collided. + break; + } + } + + if (bucket_ptr != -1) { + m_bucket_array[bucket_count] = bucket_ptr; + bucket_count++; + } + } + } + } + + for (int i = 0; i < 4; i++) { + int bucket_ptr = m_bucket_array[i]; + if (bucket_ptr == -1) + break; + + collectNearestNeighbourCandidates_(xyindex, pointOfInterest, + bucket_ptr, candidates); + } + } + + void collectNearestNeighbourCandidates_(int xyindex, + Point2D pointOfInterest, int bucket_ptr, + AttributeStreamOfInt32 candidates) { + Point2D pt = new Point2D(); + for (int node = bucket_ptr; node != IndexHashTable.nullNode(); node = m_hash_table + .getNextInBucket(node)) { + int cluster = m_hash_table.getElement(node); + int xyind = m_clusters.getFirstElement(cluster); + if (xyindex == xyind) + continue; + m_shape.getXY(xyind, pt); + m_dbg_candidate_check_count++; + if (isClusterCandidate(pointOfInterest.x, pointOfInterest.y, pt.x, + pt.y, m_tolerance)) { + candidates.add(node);// note that we add the cluster node + // instead of the cluster. + } + } + } + + boolean mergeClusters_(int cluster_1, int cluster_2) { + int xyindex_1 = m_clusters.getFirstElement(cluster_1); + int xyindex_2 = m_clusters.getFirstElement(cluster_2); + boolean res = mergeVertices_(xyindex_1, xyindex_2); + m_clusters.concatenateLists(cluster_1, cluster_2); + int hash = m_hash_function.calculateHash(cluster_1); + m_shape.setUserIndex(xyindex_1, m_hash_values, hash); + return res; + } + + boolean mergeVertices_(int vert_1, int vert_2) { + Point2D pt_1 = new Point2D(); + m_shape.getXY(vert_1, pt_1); + Point2D pt_2 = new Point2D(); + m_shape.getXY(vert_2, pt_2); + + double w_1 = m_shape.getWeight(vert_1); + double w_2 = m_shape.getWeight(vert_2); + double w = w_1 + w_2; + int r = 0; + double x = pt_1.x; + if (pt_1.x != pt_2.x) { + x = (pt_1.x * w_1 + pt_2.x * w_2) / w; + r++; + } + double y = pt_1.y; + if (pt_1.y != pt_2.y) { + y = (pt_1.y * w_1 + pt_2.y * w_2) / w; + r++; + } + + if (r > 0) + m_shape.setXY(vert_1, x, y); + m_shape.setWeight(vert_1, w); + return r != 0; + } + + boolean clusterReciprocal_() { + m_hash_values = m_shape.createUserIndex(); + int point_count = m_shape.getTotalPointCount(); + + m_shape.getXY(m_shape.getFirstVertex(m_shape.getFirstPath(m_shape + .getFirstGeometry())), m_origin); + + // This holds clusters. + m_clusters.clear(); + m_clusters.reserveLists(m_shape.getTotalPointCount()); + m_clusters.reserveNodes(m_shape.getTotalPointCount()); + + // Make the hash table. It serves a purpose of fine grain grid. + // Make it 25% larger than the point count to reduce the chance of + // collision. + m_hash_table = new IndexHashTable((point_count * 5) / 4, + new ClusterHashFunction(m_clusters, m_shape, m_origin, + m_tolerance, m_cell_size, m_hash_values)); + m_hash_table.reserveElements(m_shape.getTotalPointCount()); + + boolean b_clustered = false; + + // Go through all vertices stored in the m_shape and put the handles of + // the vertices into the clusters and the hash table. + for (int geometry = m_shape.getFirstGeometry(); geometry != -1; geometry = m_shape + .getNextGeometry(geometry)) { + for (int path = m_shape.getFirstPath(geometry); path != -1; path = m_shape + .getNextPath(path)) { + int vertex = m_shape.getFirstVertex(path); + for (int index = 0, nindex = m_shape.getPathSize(path); index < nindex; index++) { + assert (vertex != -1); + int cluster = m_clusters.createList(); + m_clusters.addElement(cluster, vertex); // initially each + // cluster consist + // of a single + // vertex + int hash = m_hash_function.calculateHash(cluster); + m_shape.setUserIndex(vertex, m_hash_values, hash); + m_hash_table.addElement(cluster); // add cluster to the hash + // table + vertex = m_shape.getNextVertex(vertex); + } + } + } + + AttributeStreamOfInt32 nn_chain = new AttributeStreamOfInt32(0); + AttributeStreamOfDbl nn_chain_distances = new AttributeStreamOfDbl(0);// array + // of + // distances + // between + // neighbour + // elements + // on + // nn_chain. + // nn_chain_distances->size() + // + + // 1 + // == + // nn_chain->size() + + ClusterCandidate candidate = new ClusterCandidate(); + + // Reciprocal nearest neighbour clustering, using a hash table. + while (m_hash_table.size() != 0 || nn_chain.size() != 0) { + if (nn_chain.size() == 0) { + int cluster = m_hash_table.getAnyElement(); + nn_chain.add(cluster); + continue; + } + + int cluster_1 = nn_chain.getLast(); + int xyindex = m_clusters.getFirstElement(cluster_1); + findClusterCandidate_(xyindex, candidate); + if (candidate.cluster == IndexHashTable.nullNode()) {// no candidate + // for + // clustering + // has been + // found for + // the + // cluster_1. + assert (nn_chain.size() == 1); + nn_chain.removeLast(); + continue; + } + + if (nn_chain.size() == 1) { + m_hash_table.deleteElement(candidate.cluster); + + if (candidate.distance == 0) {// coincident points. Cluster them + // at once. + // cluster xyNearestNeighbour + // with xyindex. The coordinates + // do not need to be changed, + // but weight need to be doubled + m_clusters.concatenateLists(cluster_1, candidate.cluster); + int cluster_weight_1 = m_clusters + .getFirstElement(cluster_1); + int cluster_weight_2 = m_clusters + .getFirstElement(candidate.cluster); + m_shape.setWeight( + cluster_weight_1, + m_shape.getWeight(cluster_weight_1) + + m_shape.getWeight(cluster_weight_2)); + } else + nn_chain.add(candidate.cluster); + + continue; + } + + assert (nn_chain.size() > 1); + if (nn_chain.get(nn_chain.size() - 2) == candidate.cluster) {// reciprocal + // NN + nn_chain.clear(false); + b_clustered |= mergeClusters_(cluster_1, candidate.cluster); + m_hash_table.addElement(cluster_1); + } else { + if (nn_chain_distances.get(nn_chain_distances.size()) <= candidate.distance) {// this + // neighbour + // is + // not + // better + // than + // the + // previous + // one + // (can + // happen + // when + // there + // are + // equidistant + // points). + nn_chain.clear(false); + b_clustered |= mergeClusters_(cluster_1, candidate.cluster); + m_hash_table.addElement(cluster_1); + } else { + nn_chain.add(candidate.cluster); + nn_chain_distances.add(candidate.distance); + } + } + }// while (hashTable->size() != 0 || nn_chain->size() != 0) + + if (b_clustered) { + applyClusterPositions_(); + } + + m_shape.removeUserIndex(m_hash_values); + return b_clustered; + } + + boolean clusterNonReciprocal_() { + int point_count = m_shape.getTotalPointCount(); + + { + int geometry = m_shape.getFirstGeometry(); + int path = m_shape.getFirstPath(geometry); + int vertex = m_shape.getFirstVertex(path); + m_shape.getXY(vertex, m_origin); + } + + // This holds clusters. + if (m_clusters == null) + m_clusters = new IndexMultiList(); + m_clusters.clear(); + m_clusters.reserveLists(m_shape.getTotalPointCount()); + m_clusters.reserveNodes(m_shape.getTotalPointCount()); + + m_hash_values = m_shape.createUserIndex(); + + // Make the hash table. It serves a purpose of fine grain grid. + // Make it 25% larger than the 4 times point count to reduce the chance + // of collision. + // The 4 times comes from the fact that we check four neighbouring cells + // in the grid for each point. + m_hash_function = new ClusterHashFunction(m_clusters, m_shape, + m_origin, m_tolerance, m_cell_size, m_hash_values); + m_hash_table = new IndexHashTable(point_count * 5, m_hash_function); // N + // * + // 4 + // * + // 1.25 + // = + // N + // * + // 5. + m_hash_table.reserveElements(m_shape.getTotalPointCount()); + + boolean b_clustered = false; + + // Go through all vertices stored in the m_shape and put the handles of + // the vertices into the clusters and the hash table. + for (int geometry = m_shape.getFirstGeometry(); geometry != -1; geometry = m_shape + .getNextGeometry(geometry)) { + for (int path = m_shape.getFirstPath(geometry); path != -1; path = m_shape + .getNextPath(path)) { + int vertex = m_shape.getFirstVertex(path); + for (int index = 0, nindex = m_shape.getPathSize(path); index < nindex; index++) { + assert (vertex != -1); + int cluster = m_clusters.createList(); + m_clusters.addElement(cluster, vertex); // initially each + // cluster consist + // of a single + // vertex + int hash = m_hash_function.calculateHash(cluster); + m_shape.setUserIndex(vertex, m_hash_values, hash); + m_hash_table.addElement(cluster); // add cluster to the hash + // table + vertex = m_shape.getNextVertex(vertex); + } + } + } + + // m_hash_table.dbg_print_bucket_histogram_(); + + {// scope for candidates array + AttributeStreamOfInt32 candidates = new AttributeStreamOfInt32(0); + while (m_hash_table.size() != 0) { + int node = m_hash_table.getAnyNode(); + assert (node != IndexHashTable.nullNode()); + int cluster_1 = m_hash_table.getElement(node); + m_hash_table.deleteNode(node); + int xyindex = m_clusters.getFirstElement(cluster_1); + collectClusterCandidates_(xyindex, candidates); + if (candidates.size() == 0) {// no candidate for clustering has + // been found for the cluster_1. + continue; + } + + for (int candidate_index = 0, ncandidates = candidates.size(); candidate_index < ncandidates; candidate_index++) { + int cluster_node = candidates.get(candidate_index); + int cluster = m_hash_table.getElement(cluster_node); + m_hash_table.deleteNode(cluster_node); + b_clustered |= mergeClusters_(cluster_1, cluster); + } + m_hash_table.addElement(cluster_1); + candidates.clear(false); + } + } + + if (b_clustered) { + applyClusterPositions_(); + } + + // m_hash_table.reset(); + // m_hash_function.reset(); + m_shape.removeUserIndex(m_hash_values); + + return b_clustered; + } + + void applyClusterPositions_() { + Point2D cluster_pt = new Point2D(); + // move vertices to the clustered positions. + for (int list = m_clusters.getFirstList(); list != IndexMultiList + .nullNode(); list = m_clusters.getNextList(list)) { + int node = m_clusters.getFirst(list); + assert (node != IndexMultiList.nullNode()); + int vertex = m_clusters.getElement(node); + m_shape.getXY(vertex, cluster_pt); + for (node = m_clusters.getNext(node); node != IndexMultiList + .nullNode(); node = m_clusters.getNext(node)) { + int vertex_1 = m_clusters.getElement(node); + m_shape.setXY(vertex_1, cluster_pt); + } + } + } + + Clusterer() { + m_hash_values = -1; + m_dbg_candidate_check_count = 0; + } + +} diff --git a/src/com/esri/core/geometry/ConstructOffset.java b/src/com/esri/core/geometry/ConstructOffset.java new file mode 100644 index 00000000..6565e114 --- /dev/null +++ b/src/com/esri/core/geometry/ConstructOffset.java @@ -0,0 +1,1005 @@ +/* + Copyright 1995-2013 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ +package com.esri.core.geometry; + +import java.util.ArrayList; + +// Note: m_distance<0 offsets to the left, m_distance>0 offsets to the right + +class ConstructOffset { + ProgressTracker m_progressTracker; + Geometry m_inputGeometry; + double m_distance; + double m_tolerance; + OperatorOffset.JoinType m_joins; + double m_miterLimit; + + // multipath offset + static class GraphicPoint { + double x, y; + int m_next, m_prev; + double m; + int type; + + GraphicPoint(double x_, double y_) { + x = x_; + y = y_; + type = 0; + m = 0; + } + + GraphicPoint(Point2D r) { + x = r.x; + y = r.y; + type = 0; + m = 0; + } + + GraphicPoint(GraphicPoint pt) { + x = pt.x; + y = pt.y; + type = pt.type; + m = pt.m; + } + + GraphicPoint(GraphicPoint srcPt, double d, double angle) { + x = srcPt.x + d * Math.cos(angle); + y = srcPt.y + d * Math.sin(angle); + type = srcPt.type; + m = srcPt.m; + } + + GraphicPoint(GraphicPoint pt1, GraphicPoint pt2) { + x = (pt1.x + pt2.x) * 0.5; + y = (pt1.y + pt2.y) * 0.5; + type = pt1.type; + m = pt1.m; + } + + GraphicPoint(GraphicPoint pt1, GraphicPoint pt2, double ratio) { + x = pt1.x + (pt2.x - pt1.x) * ratio; + y = pt1.y + (pt2.y - pt1.y) * ratio; + type = pt1.type; + m = pt1.m; + } + + }; + + static class GraphicRect { + double x1, x2, y1, y2; + }; + + static class IntersectionInfo { + GraphicPoint pt; + double rFirst; + double rSecond; + boolean atExistingPt; + }; + + ArrayList m_srcPts; + int m_srcPtCount; + ArrayList m_offsetPts; + int m_offsetPtCount; + + MultiPath m_resultPath; + int m_resultPoints; + double m_a1, m_a2; + boolean m_bBadSegs; + + ConstructOffset(ProgressTracker progressTracker) { + m_progressTracker = progressTracker; + } + + // static + static Geometry execute(Geometry inputGeometry, double distance, + OperatorOffset.JoinType joins, double miterLimit, double tolerance, + ProgressTracker progressTracker) { + if (inputGeometry == null) + throw new IllegalArgumentException(); + if (inputGeometry.getDimension() < 1)// can offset Polygons and + // Polylines only + throw new IllegalArgumentException(); + if (distance == 0 || inputGeometry.isEmpty()) + return inputGeometry; + ConstructOffset offset = new ConstructOffset(progressTracker); + offset.m_inputGeometry = inputGeometry; + offset.m_distance = distance; + offset.m_tolerance = tolerance; + offset.m_joins = joins; + offset.m_miterLimit = miterLimit; + return offset._ConstructOffset(); + } + + Geometry _OffsetLine() { + Line line = (Line) m_inputGeometry; + Point2D start = line.getStartXY(); + Point2D end = line.getEndXY(); + Point2D v = new Point2D(); + v.sub(end, start); + v.normalize(); + v.leftPerpendicular(); + v.scale(m_distance); + start.add(v); + end.add(v); + Line resLine = (Line) line.createInstance(); + line.setStartXY(start); + line.setEndXY(end); + return resLine; + } + + Geometry _OffsetEnvelope() { + Envelope envelope = (Envelope) m_inputGeometry; + if ((m_distance > 0) && (m_joins != OperatorOffset.JoinType.Miter)) { + Polygon poly = new Polygon(); + poly.addEnvelope(envelope, false); + m_inputGeometry = poly; + return _ConstructOffset(); + } + + Envelope resEnv = new Envelope(envelope.m_envelope); + resEnv.inflate(m_distance, m_distance); + return resEnv; + } + + private final double pi = Math.PI;// GEOMETRYX_PI; + private final double two_pi = Math.PI * 2;// GEOMETRYX_2PI; + private final double half_pi = Math.PI / 2;// GEOMETRYX_HalfPI; + private final double sqrt2 = 1.4142135623730950488016887242097; + private final double oneDegree = 0.01745329251994329576923690768489; + + private final int BAD_SEG = 0x0100; + private final int IS_END = 0x0200; + private final int CLOSING_SEG = 0x0400; + + void addPoint(GraphicPoint pt) { + m_offsetPts.add(pt); + m_offsetPtCount++; + } + + double scal(GraphicPoint pt1, GraphicPoint pt2, GraphicPoint pt3, + GraphicPoint pt4) { + return (pt2.x - pt1.x) * (pt4.x - pt3.x) + (pt2.y - pt1.y) + * (pt4.y - pt3.y); + } + + // offPt is the point to add. + // this point corresponds to the offset version of the end of seg1. + // it could generate a segment going in the opposite direction of the + // original segment + // this situation is handled here by adding an additional "bad" segment + void addPoint(GraphicPoint offPt, int i_src) { + if (m_offsetPtCount == 0) // TODO: can we have this outside of this + // method? + { + addPoint(offPt); + return; + } + + int n_src = m_srcPtCount; + GraphicPoint pt1, pt; + pt1 = m_srcPts.get(i_src == 0 ? n_src - 1 : i_src - 1); + pt = m_srcPts.get(i_src); + + // calculate scalar product to determine if the offset segment goes in + // the same/opposite direction compared to the original one + double s = scal(pt1, pt, m_offsetPts.get(m_offsetPtCount - 1), offPt); + if (s > 0) + // original segment and offset segment go in the same direction. Just + // add the point + { + addPoint(offPt); + return; + } + + if (s < 0) { + // we will add a loop. We need to make sure the points we introduce + // don't generate a "reversed" segment + // let's project the first point of the reversed segment + // (m_offsetPts + m_offsetPtCount - 1) to check + // if it falls on the good side of the original segment (scalar + // product sign again) + if (scal(pt1, pt, pt, m_offsetPts.get(m_offsetPtCount - 1)) > 0) { + GraphicPoint p; + + // change value of m_offsetPts + m_offsetPtCount - 1 + int k; + if (i_src == 0) + k = n_src - 2; + else if (i_src == 1) + k = n_src - 1; + else + k = i_src - 2; + GraphicPoint pt0 = m_srcPts.get(k); + + double a = Math.atan2(pt1.y - pt0.y, pt1.x - pt0.x); + p = new GraphicPoint(pt1, m_distance, a - half_pi); + m_offsetPts.set(m_offsetPtCount - 1, p); + + if (m_joins == OperatorOffset.JoinType.Bevel + || m_joins == OperatorOffset.JoinType.Miter) { + // this block is added as well as the commented BAD_SEG in + // the next block + p = new GraphicPoint(p, pt1); + addPoint(p); + + // "bad" segment + p = new GraphicPoint(pt1, m_distance, m_a1 + half_pi); + + GraphicPoint p_ = new GraphicPoint(p, pt1); + p_.type |= BAD_SEG; + addPoint(p_); + + addPoint(p); + } else { + // the working stuff for round and square + + // "bad" segment + p = new GraphicPoint(pt1, m_distance, m_a1 + half_pi); + p.type |= BAD_SEG; + addPoint(p); + } + + // add offPt + addPoint(offPt, i_src); + } else { + GraphicPoint p; + + // we don't add offPt but the loop containing the "bad" segment + p = new GraphicPoint(pt, m_distance, m_a1 + half_pi); + addPoint(p); + + if (m_joins == OperatorOffset.JoinType.Bevel + || m_joins == OperatorOffset.JoinType.Miter) { + // this block is added as well as the commented BAD_SEG in + // the next block + p = new GraphicPoint(p, pt); + addPoint(p); + + p = new GraphicPoint(pt, m_distance, m_a2 - half_pi); + GraphicPoint p_ = new GraphicPoint(p, pt); + p_.type |= BAD_SEG; + addPoint(p_); + + addPoint(p); + } else { + // the working stuff for round and square + p = new GraphicPoint(pt, m_distance, m_a2 - half_pi); + p.type |= BAD_SEG; + addPoint(p); + } + } + } + } + + boolean buildOffset() { + // make sure we have at least three points and no identical points + int i; + double a1, a2; + GraphicPoint pt, pt1, pt2; + GraphicPoint p; + + // number of points to deal with + int n = m_srcPtCount; + + m_offsetPtCount = 0; + + double flattenTolerance = m_tolerance * 0.5; + + double a1_0 = 0; + double a2_0 = 0; + for (i = 0; i < n; i++) { + pt = m_srcPts.get(i); + + // point before + if (i == 0) + pt1 = m_srcPts.get(n - 1); + else + pt1 = m_srcPts.get(i - 1); + + // point after + if (i == n - 1) + pt2 = m_srcPts.get(0); + else + pt2 = m_srcPts.get(i + 1); + + // angles of enclosing segments + double dx1 = pt1.x - pt.x; + double dy1 = pt1.y - pt.y; + double dx2 = pt2.x - pt.x; + double dy2 = pt2.y - pt.y; + a1 = Math.atan2(dy1, dx1); + a2 = Math.atan2(dy2, dx2); + m_a1 = a1; + m_a2 = a2; + if (i == 0) { + a1_0 = a1; + a2_0 = a2; + } + + // double dot_product = dx1 * dx2 + dy1 * dy2; + double cross_product = dx1 * dy2 - dx2 * dy1; + // boolean bInnerAngle = (cross_product == 0) ? (m_distance > 0) : + // (cross_product * m_distance >= 0.0); + + // check for inner angles (always managed the same, whatever the + // type of join) + double saved_a2 = a2; + if (a2 < a1) + a2 += two_pi; // this guaranties that (a1 + a2) / 2 is on the + // right side of the curve + if (cross_product * m_distance > 0.0) // inner angle + { + // inner angle + if (m_joins == OperatorOffset.JoinType.Bevel + || m_joins == OperatorOffset.JoinType.Miter) { + p = new GraphicPoint(pt, m_distance, a1 + half_pi); + addPoint(p); + + // this block is added as well as the commented BAD_SEG in + // the next block + double ratio = 0.001; // TODO: the higher the ratio, the + // better the result (shorter + // segments) + p = new GraphicPoint(pt, p, ratio); + addPoint(p); + + // this is the "bad" segment + p = new GraphicPoint(pt, m_distance, a2 - half_pi); + + GraphicPoint p_ = new GraphicPoint(pt, p, ratio); + p_.type |= BAD_SEG; + addPoint(p_); + + addPoint(p); + } else { + // this method works for square and round, but not bevel + double r = (a2 - a1) * 0.5; + double d = m_distance / Math.abs(Math.sin(r)); + p = new GraphicPoint(pt, d, (a1 + a2) * 0.5); + addPoint(p, i); // will deal with reversed segments + } + continue; + } + + // outer angles + // check if we have an end point first + if ((pt.type & IS_END) != 0) { + // TODO: deal with other options. assume rounded and + // perpendicular for now + // we need to use the outer regular polygon of the round join + // TODO: explain this in a doc + + // calculate the number of points based on a flatten tolerance + double r = 1.0 - flattenTolerance / Math.abs(m_distance); + long na = 1; + double da = (m_distance < 0) ? -pi : pi; // da is negative when + // m_offset is + // negative (???) + if (r > -1.0 && r < 1.0) { + double a = Math.acos(r) * 2; // angle where "arrow?" is less + // than flattenTolerance + // do not consider an angle smaller than a degree + if (a < oneDegree) + a = oneDegree; + na = (long) (pi / a + 1.5); + if (na > 1) + da /= na; + } + // add first point + double a = a1 + half_pi; + p = new GraphicPoint(pt, m_distance, a); + if (i == 0) + p.type |= CLOSING_SEG; // TODO: should we simplify this by + // considering the last point + // instead of the first one?? + addPoint(p, i); // will deal with reversed segments + + double d = m_distance / Math.cos(da / 2); + a += da / 2; + p = new GraphicPoint(pt, d, a); + p.type |= CLOSING_SEG; + addPoint(p); + + while (--na > 0) { + a += da; + p = new GraphicPoint(pt, d, a); + p.type |= CLOSING_SEG; + addPoint(p); + } + + // last point (optional except for the first point) + p = new GraphicPoint(pt, m_distance, a2 - half_pi); // this one + // is + // optional + // except + // for the + // first + // point + p.type |= CLOSING_SEG; + addPoint(p); + + continue; + } + + else if (m_joins == OperatorOffset.JoinType.Bevel) // bevel + { + p = new GraphicPoint(pt, m_distance, a1 + half_pi); + addPoint(p, i); // will deal with reversed segments + p = new GraphicPoint(pt, m_distance, a2 - half_pi); + addPoint(p); + continue; + } + + else if (m_joins == OperatorOffset.JoinType.Round) { + // we need to use the outer regular polygon of the round join + // TODO: explain this in a doc + + // calculate the number of points based on a flatten tolerance + double r = 1.0 - flattenTolerance / Math.abs(m_distance); + long na = 1; + double da = (a2 - half_pi) - (a1 + half_pi); // da is negative + // when + // m_distance is + // negative + if (r > -1.0 && r < 1.0) { + double a = Math.acos(r) * 2.0; // angle where "arrow?" is + // less than + // flattenTolerance + // do not consider an angle smaller than a degree + if (a < oneDegree) + a = oneDegree; + na = (long) (Math.abs(da) / a + 1.5); + if (na > 1) + da /= na; + } + double d = m_distance / Math.cos(da * 0.5); + double a = a1 + half_pi + da * 0.5; + p = new GraphicPoint(pt, d, a); + addPoint(p, i); // will deal with reversed segments + while (--na > 0) { + a += da; + p = new GraphicPoint(pt, d, a); + addPoint(p); + } + continue; + } else if (m_joins == OperatorOffset.JoinType.Miter) { + dx1 = pt1.x - pt.x; + dy1 = pt1.y - pt.y; + dx2 = pt2.x - pt.x; + dy2 = pt2.y - pt.y; + double d1 = Math.sqrt(dx1 * dx1 + dy1 * dy1); + double d2 = Math.sqrt(dx2 * dx2 + dy2 * dy2); + double cosa = (dx1 * dx2 + dy1 * dy2) / d1 / d2; + if (cosa > 1.0 - 1.0e-8) { + // there's a spike in the polygon boundary; this could + // happen when filtering out short segments in Init() + p = new GraphicPoint(pt, sqrt2 * m_distance, a2 - pi * 0.25); + addPoint(p, i); + p = new GraphicPoint(pt, sqrt2 * m_distance, a2 + pi * 0.25); + addPoint(p); + continue; + } + // original miter code + // if (m_miterLimit * m_miterLimit * (1 - cosa) < 2) + // { + // // bevel join + // p = new GraphicPoint(pt, m_distance, a1 + half_pi); + // AddPoint(p, src_poly, srcPtCount, i); // will deal with + // reversed segments + // p = new GraphicPoint(pt, m_distance, a2 - half_pi); + // AddPoint(p); + // continue; + // } + double distanceFromCorner = Math.abs(m_distance + / Math.sin(Math.acos(cosa) * 0.5)); + double bevelDistance = Math.abs(m_miterLimit * m_distance); + if (distanceFromCorner > bevelDistance) { + double r = (a2 - a1) * 0.5; + double d = m_distance / Math.abs(Math.sin(r)); + p = new GraphicPoint(pt, d, (a1 + a2) * 0.5); + + // construct bevel points, see comment in + // c:\ArcGIS\System\Geometry\Geometry\ConstructCurveImpl.cpp, + // ESRI::OffsetCurve::EstimateBevelPoints + Point2D corner = new Point2D(p.x, p.y); + Point2D through = new Point2D(pt.x, pt.y); + Point2D delta = new Point2D(); + delta.sub(corner, through); + + // Point2D midPoint = through + delta * (bevelDistance / + // delta.Length()); + Point2D midPoint = new Point2D(); + midPoint.scaleAdd(bevelDistance / delta.length(), delta, + through); + + double sideLength = Math.sqrt(distanceFromCorner + * distanceFromCorner - m_distance * m_distance), halfWidth = (distanceFromCorner - bevelDistance) + * Math.abs(m_distance) / sideLength; + + // delta = delta.RotateDirect(0.0, m_distance > 0.0 ? -1.0 : + // 1.0) * (halfWidth/delta.Length()); + if (m_distance > 0.0) + delta.leftPerpendicular(); + else + delta.rightPerpendicular(); + delta.scale(halfWidth / delta.length()); + + Point2D from = new Point2D(); + from.add(midPoint, delta); + Point2D to = new Point2D(); + to.sub(midPoint, delta); + p = new GraphicPoint(from); + // _ASSERT(::_finite(p.x)); + // _ASSERT(::_finite(p.y)); + addPoint(p, i); + p = new GraphicPoint(to); + // _ASSERT(::_finite(p.x)); + // _ASSERT(::_finite(p.y)); + addPoint(p); + continue; + } + // miter join + double r = (a2 - a1) * 0.5; + double d = m_distance / Math.abs(Math.sin(r)); // r should not + // be null + // (trapped by + // the bevel + // case) + p = new GraphicPoint(pt, d, (a1 + a2) * 0.5); + addPoint(p, i); // will deal with reversed segments + continue; + } else // the new "square" join + { + a2 = saved_a2; + + // identify if angle is less than pi/2 + // in this case, we introduce a segment that is perpendicular to + // the bissector of the angle + // TODO: see figure X for details + boolean bAddSegment; + if (m_distance > 0.0) { + if (a2 > a1) // > and not >= + a2 -= two_pi; + bAddSegment = (a1 - a2 < half_pi); + } else { + if (a2 < a1) // < and not <= + a2 += two_pi; + bAddSegment = (a2 - a1 < half_pi); + } + if (bAddSegment) { + // make it continuous when angle is pi/2 (but not tangent to + // the round join) + double d = m_distance * sqrt2; + double a; + + if (d < 0.0) + a = a1 + pi * 0.25; + else + a = a1 + 3.0 * pi * 0.25; + p = new GraphicPoint(pt, d, a); + addPoint(p, i); + + if (d < 0) + a = a2 - pi * 0.25; + else + a = a2 - 3.0 * pi * 0.25; + p = new GraphicPoint(pt, d, a); + addPoint(p); + } else // standard case: we just add the intersection point of + // offset segments + { + double r = (a2 - a1) * 0.5; + double d = m_distance / Math.abs(Math.sin(r)); + if (a2 < a1) + a2 += two_pi; // this guaranties that (a1 + a2) / 2 is + // on the right side with a positive + // offset + p = new GraphicPoint(pt, d, (a1 + a2) / 2); + addPoint(p, i); + } + } + } + + // closing point + m_a1 = a1_0; + m_a2 = a2_0; + addPoint(m_offsetPts.get(0), 0); + + // make sure the first point matches the last (in case a problem of + // reversed segment happens there) + pt = new GraphicPoint(m_offsetPts.get(m_offsetPtCount - 1)); + m_offsetPts.set(0, pt); + + // remove loops + return removeBadSegsFast(); + } + + void addPart(int iStart, int cPts) { + if (cPts < 2) + return; + + for (int i = 0; i < cPts; i++) { + GraphicPoint pt = m_offsetPts.get(iStart + i); + if (i != 0) + m_resultPath.lineTo(new Point2D(pt.x, pt.y)); + else + m_resultPath.startPath(new Point2D(pt.x, pt.y)); + } + } + + void _OffsetPath(MultiPath multiPath, int pathIndex, MultiPath resultingPath) { + int startVertex = multiPath.getPathStart(pathIndex); + int endVertex = multiPath.getPathEnd(pathIndex); + + m_offsetPts = new ArrayList(); + + // test if part is closed + m_resultPath = resultingPath; + m_resultPoints = 0; + if (multiPath.isClosedPath(pathIndex)) { + // check if last point is a duplicate of first + Point2D ptStart = multiPath.getXY(startVertex); + while (multiPath.getXY(endVertex - 1).equals(ptStart)) + endVertex--; + + // we need at least three points for a polygon + if (endVertex - startVertex >= 2) { + m_srcPtCount = endVertex - startVertex; + m_srcPts = new ArrayList(m_srcPtCount); + // TODO: may throw std::bad:alloc() + for (int i = startVertex; i < endVertex; i++) + m_srcPts.add(new GraphicPoint(multiPath.getXY(i))); + + if (buildOffset()) + addPart(0, m_offsetPtCount - 1); // do not repeat closing + // point + } + } else { + // remove duplicate points at extremities + Point2D ptStart = multiPath.getXY(startVertex); + while ((startVertex < endVertex) + && multiPath.getXY(startVertex + 1).equals(ptStart)) + startVertex++; + Point2D ptEnd = multiPath.getXY(endVertex - 1); + while ((startVertex < endVertex) + && multiPath.getXY(endVertex - 2).equals(ptEnd)) + endVertex--; + + // we need at least two points for a polyline + if (endVertex - startVertex >= 2) { + // close the line and mark the opposite segments as non valid + m_srcPtCount = (endVertex - startVertex) * 2 - 2; + m_srcPts = new ArrayList(m_srcPtCount); + // TODO: may throw std::bad:alloc() + + GraphicPoint pt = new GraphicPoint(multiPath.getXY(startVertex)); + pt.type |= IS_END + CLOSING_SEG; + m_srcPts.add(pt); + + for (int i = startVertex + 1; i < endVertex - 1; i++) { + pt = new GraphicPoint(multiPath.getXY(i)); + m_srcPts.add(pt); + } + + pt = new GraphicPoint(multiPath.getXY(endVertex - 1)); + pt.type |= IS_END; + m_srcPts.add(pt); + + for (int i = endVertex - 2; i >= startVertex + 1; i--) { + pt = new GraphicPoint(multiPath.getXY(i)); + pt.type |= CLOSING_SEG; + m_srcPts.add(pt); + } + + if (buildOffset()) + + if (m_offsetPts.size() >= 2) { + // extract the part that doesn't have the CLOSING_SEG + // attribute + + int iStart = -1; + int iEnd = -1; + boolean prevClosed = (m_offsetPts + .get(m_offsetPtCount - 1).type & CLOSING_SEG) != 0; + if (!prevClosed) + iStart = 0; + for (int i = 1; i < m_offsetPtCount; i++) { + boolean closed = (m_offsetPts.get(i).type & CLOSING_SEG) != 0; + if (!closed) { + if (prevClosed) { + // if ((m_offsetPts[i - 1].type & MOVE_TO) + // == 0) + // m_offsetPts[i - 1].type += MOVE_TO - + // LINE_TO; + iStart = i - 1; + } + } else { + if (!prevClosed) { + iEnd = i - 1; + // for (long i = iStart; i <= iEnd; i++) + // m_offsetPts[i].type &= OUR_FLAGS_MASK; + if (iEnd - iStart + 1 > 1) + addPart(iStart, iEnd - iStart + 1); + } + } + prevClosed = closed; + } + if (!prevClosed) { + iEnd = m_offsetPtCount - 1; + // for (long i = iStart; i <= iEnd; i++) + // m_offsetPts[i].type &= OUR_FLAGS_MASK; + if (iEnd - iStart + 1 > 1) + addPart(iStart, iEnd - iStart + 1); + } + } else { + int iStart = 0; + int iEnd = m_offsetPtCount - 1; + if (iStart >= 0 && iEnd - iStart >= 1) { + // for (long i = iStart; i <= iEnd; i++) + // m_offsetPts[i].type &= OUR_FLAGS_MASK; + addPart(iStart, iEnd - iStart + 1); + } + } + } + } + + // clear source + m_srcPts = null; + m_srcPtCount = 0; + // free offset buffer + m_offsetPts = null; + m_offsetPtCount = 0; + } + + boolean removeBadSegsFast() { + boolean bWrong = false; + + // initialize circular doubly-linked list + // skip last point which is dup of first point + for (int i = 0; i < m_offsetPtCount; i++) { + GraphicPoint pt = m_offsetPts.get(i); + pt.m_next = i + 1; + pt.m_prev = i - 1; + m_offsetPts.set(i, pt); + } + + // need to update the first and last elements + GraphicPoint pt; + + pt = m_offsetPts.get(0); + pt.m_prev = m_offsetPtCount - 2; + m_offsetPts.set(0, pt); + + pt = m_offsetPts.get(m_offsetPtCount - 2); + pt.m_next = 0; + m_offsetPts.set(m_offsetPtCount - 2, pt); + + int w = 0; + for (int i = 0; i < m_offsetPtCount; i++) { + if ((m_offsetPts.get(w).type & BAD_SEG) != 0) { + int wNext = deleteClosedSeg(w); + if (wNext != -1) + w = wNext; + else { + bWrong = true; + break; + } + } else + w = m_offsetPts.get(w).m_next; + } + + if (bWrong) + return false; + + // w is the index of a known good (i.e. surviving ) point in the offset + // array + compressOffsetArray(w); + return true; + } + + int deleteClosedSeg(int seg) { + int n = m_offsetPtCount - 1; // number of segments + + // check combinations of segments + int ip0 = seg, ip, im; + + for (int i = 1; i <= n - 2; i++) { + ip0 = m_offsetPts.get(ip0).m_next; + + ip = ip0; + im = seg; + + for (int j = 1; j <= i; j++) { + im = m_offsetPts.get(im).m_prev; + + if ((m_offsetPts.get(im).type & BAD_SEG) == 0 + && (m_offsetPts.get(ip).type & BAD_SEG) == 0) { + int rSegNext = handleClosedIntersection(im, ip); + if (rSegNext != -1) + return rSegNext; + } + + ip = m_offsetPts.get(ip).m_prev; + } + } + + return -1; + } + + // line segments defined by (im-1, im) and (ip-1, ip) + int handleClosedIntersection(int im, int ip) { + GraphicPoint pt1, pt2, pt3, pt4; + pt1 = m_offsetPts.get(m_offsetPts.get(im).m_prev); + pt2 = m_offsetPts.get(im); + pt3 = m_offsetPts.get(m_offsetPts.get(ip).m_prev); + pt4 = m_offsetPts.get(ip); + + if (!sectGraphicRect(pt1, pt2, pt3, pt4)) + return -1; + + // intersection + IntersectionInfo ii = new IntersectionInfo(); + if (findIntersection(pt1, pt2, pt3, pt4, ii) && !ii.atExistingPt) + if (Math.signum((pt2.x - pt1.x) * (pt4.y - pt3.y) - (pt2.y - pt1.y) + * (pt4.x - pt3.x)) != Math.signum(m_distance)) { + int prev0 = m_offsetPts.get(im).m_prev; + + ii.pt.type = pt2.type; + ii.pt.m_next = ip; + ii.pt.m_prev = prev0; + m_offsetPts.set(im, ii.pt); + + ii.pt = m_offsetPts.get(ip); + ii.pt.m_prev = im; + m_offsetPts.set(ip, ii.pt); + + return ip; + } + return -1; + } + + boolean sectGraphicRect(GraphicPoint pt1, GraphicPoint pt2, + GraphicPoint pt3, GraphicPoint pt4) { + return (Math.max(pt1.x, pt2.x) >= Math.min(pt3.x, pt4.x) + && Math.max(pt3.x, pt4.x) >= Math.min(pt1.x, pt2.x) + && Math.max(pt1.y, pt2.y) >= Math.min(pt3.y, pt4.y) && Math + .max(pt3.y, pt4.y) >= Math.min(pt1.y, pt2.y)); + } + + boolean findIntersection(GraphicPoint bp1, GraphicPoint bp2, + GraphicPoint bp3, GraphicPoint bp4, + IntersectionInfo intersectionInfo) { + intersectionInfo.atExistingPt = false; + + // Note: test if rectangles intersect already done by caller + + // intersection + double i, j, r, r1; + i = (bp2.y - bp1.y) * (bp4.x - bp3.x) - (bp2.x - bp1.x) + * (bp4.y - bp3.y); + j = (bp3.y - bp1.y) * (bp2.x - bp1.x) - (bp3.x - bp1.x) + * (bp2.y - bp1.y); + if (i == 0.0) + r = 2.0; + else + r = j / i; + + if ((r >= 0.0) && (r <= 1.0)) { + r1 = r; + i = (bp4.y - bp3.y) * (bp2.x - bp1.x) - (bp4.x - bp3.x) + * (bp2.y - bp1.y); + j = (bp1.y - bp3.y) * (bp4.x - bp3.x) - (bp1.x - bp3.x) + * (bp4.y - bp3.y); + + if (i == 0.0) + r = 2.0; + else + r = j / i; + + if ((r >= 0.0) && (r <= 1.0)) { + intersectionInfo.pt = new GraphicPoint(bp1.x + r + * (bp2.x - bp1.x), bp1.y + r * (bp2.y - bp1.y)); + intersectionInfo.pt.m = bp3.m + r1 * (bp4.m - bp3.m); + if (((r1 == 0.0) || (r1 == 1.0)) && ((r == 0.0) || (r == 1.0))) + intersectionInfo.atExistingPt = true; + + intersectionInfo.rFirst = r; + intersectionInfo.rSecond = r1; + + if (((r1 == 0.0) || (r1 == 1.0)) && ((r > 0.0) && (r < 1.0)) + || ((r == 0.0) || (r == 1.0)) + && ((r1 > 0.0) && (r1 < 1.0))) { + return false; + } + + return true; + } + } + return false; + } + + // i0 is the index of a known good point in the offset points array; that + // is, its the index of a point that isn't part of a deleted loop + void compressOffsetArray(int i0) { + int i_ = i0; + while (m_offsetPts.get(i_).m_prev < i_) + i_ = m_offsetPts.get(i_).m_prev; + + int j = 0, i = i_; + + do { + GraphicPoint pt = m_offsetPts.get(i); + m_offsetPts.set(j, pt); + i = pt.m_next; + j++; + } while (i != i_); + + m_offsetPts.set(j, m_offsetPts.get(0)); // duplicate closing point + + m_offsetPtCount = j + 1; + } + + void _OffsetMultiPath(MultiPath resultingPath) { + // we process all path independently, then merge the results + MultiPath multiPath = (MultiPath) m_inputGeometry; + SegmentIterator segmentIterator = multiPath.querySegmentIterator(); + if (segmentIterator == null) + return; // TODO: strategy on error? + + segmentIterator.resetToFirstPath(); + int pathIndex = -1; + while (segmentIterator.nextPath()) { + pathIndex++; + _OffsetPath(multiPath, pathIndex, resultingPath); + } + } + + Geometry _ConstructOffset() { + int gt = m_inputGeometry.getType().value(); + if (gt == Geometry.GeometryType.Line) { + return _OffsetLine(); + } + if (gt == Geometry.GeometryType.Envelope) { + return _OffsetEnvelope(); + } + if (Geometry.isSegment(gt)) { + Polyline poly = new Polyline(); + poly.addSegment((Segment) m_inputGeometry, true); + m_inputGeometry = poly; + return _ConstructOffset(); + } + if (gt == Geometry.GeometryType.Polyline) { + Polyline polyline = new Polyline(); + _OffsetMultiPath(polyline); + return polyline; + } + if (gt == Geometry.GeometryType.Polygon) { + Polygon polygon = new Polygon(); + _OffsetMultiPath(polygon); + return polygon; + } + // throw new GeometryException("not implemented"); + return null; + } +} diff --git a/src/com/esri/core/geometry/ConvexHull.java b/src/com/esri/core/geometry/ConvexHull.java new file mode 100644 index 00000000..7c5a4e5a --- /dev/null +++ b/src/com/esri/core/geometry/ConvexHull.java @@ -0,0 +1,735 @@ +/* + Copyright 1995-2013 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ +package com.esri.core.geometry; + +class ConvexHull { + /* + * Constructor for a Convex_hull object.Used for dynamic insertion of + * geometries to create a convex hull. + */ + ConvexHull() { + m_tree_hull = new Treap(); + m_tree_hull.setCapacity(20); + m_shape = new EditShape(); + m_geometry_handle = m_shape.createGeometry(Geometry.Type.MultiPoint); + m_path_handle = m_shape.insertPath(m_geometry_handle, -1); + m_call_back = new CallBackShape(this); + } + + private ConvexHull(MultiVertexGeometry mvg) { + m_tree_hull = new Treap(); + m_tree_hull.setCapacity(20); + m_mvg = mvg; + m_call_back = new CallBackMvg(this); + } + + private ConvexHull(Point2D[] points) { + m_tree_hull = new Treap(); + m_tree_hull.setCapacity(20); + m_points = points; + m_call_back = new CallBackPoints(this); + } + + /** + * Adds a geometry to the current bounding geometry using an incremental + * algorithm for dynamic insertion. \param geometry The geometry to add to + * the bounding geometry. + */ + + void addGeometry(Geometry geometry) { + int type = geometry.getType().value(); + + if (MultiVertexGeometry.isMultiVertex(type)) + addMultiVertexGeometry_((MultiVertexGeometry) geometry); + else if (MultiPath.isSegment(type)) + addSegment_((Segment) geometry); + else if (type == Geometry.GeometryType.Envelope) + addEnvelope_((Envelope) geometry); + else if (type == Geometry.GeometryType.Point) + addPoint_((Point) geometry); + else + throw new IllegalArgumentException("invalid shape type"); + } + + /** + * Gets the current bounding geometry. Returns a Geometry. + */ + + Geometry getBoundingGeometry() { + // Extracts the convex hull from the tree. Reading the tree in order + // from first to last is the resulting convex hull. + Point point = new Point(); + int first = m_tree_hull.getFirst(-1); + Polygon hull = new Polygon(m_shape.getVertexDescription()); + m_shape.queryPoint(m_tree_hull.getElement(first), point); + hull.startPath(point); + + for (int i = m_tree_hull.getNext(first); i != -1; i = m_tree_hull + .getNext(i)) { + m_shape.queryPoint(m_tree_hull.getElement(i), point); + hull.lineTo(point); + } + + return hull; + } + + /** + * Static method to construct the convex hull of a Multi_vertex_geometry. + * Returns a Polygon. \param mvg The geometry used to create the convex + * hull. + */ + + static Polygon construct(MultiVertexGeometry mvg) { + ConvexHull convex_hull = new ConvexHull(mvg); + + int N = mvg.getPointCount(); + int t0 = 0, tm = 1; + Point2D pt_0 = new Point2D(); + Point2D pt_m = new Point2D(); + Point2D pt_p = new Point2D(); + + mvg.getXY(t0, pt_0); + + while (true) { + mvg.getXY(tm, pt_m); + if (!(pt_m.isEqual(pt_0, NumberUtils.doubleEps()) && tm < N - 1)) + break; + + tm++; // We don't want to close the gap between t0 and tm. + } + + convex_hull.m_tree_hull.addElement(t0, -1); + convex_hull.m_tree_hull.addBiggestElement(tm, -1); + + for (int tp = tm + 1; tp < mvg.getPointCount(); tp++) {// Dynamically + // insert into + // the current + // convex hull + + mvg.getXY(tp, pt_p); + int p = convex_hull.treeHull_(pt_p); + + if (p != -1) + convex_hull.m_tree_hull.setElement(p, tp); // reset the place + // holder to the + // point index. + } + + // Extracts the convex hull from the tree. Reading the tree in order + // from first to last is the resulting convex hull. + Point point = new Point(); + int first = convex_hull.m_tree_hull.getFirst(-1); + Polygon hull = new Polygon(mvg.getDescription()); + mvg.getPointByVal(convex_hull.m_tree_hull.getElement(first), point); + hull.startPath(point); + + for (int i = convex_hull.m_tree_hull.getNext(first); i != -1; i = convex_hull.m_tree_hull + .getNext(i)) { + mvg.getPointByVal(convex_hull.m_tree_hull.getElement(i), point); + hull.lineTo(point); + } + + return hull; + } + + /** + * Static method to construct the convex hull from an array of points. The + * out_convex_hull array will be populated with the subset of index + * positions which contribute to the convex hull. Returns the number of + * points in the convex hull. \param points The points used to create the + * convex hull. \param count The number of points in the input Point2D + * array. \param out_convex_hull An index array allocated by the user at + * least as big as the size of the input points array. + */ + static int construct(Point2D[] points, int count, int[] bounding_geometry) { + ConvexHull convex_hull = new ConvexHull(points); + + int t0 = 0, tm = 1; + Point2D pt_0 = points[t0]; + + while (points[tm].isEqual(pt_0, NumberUtils.doubleEps()) + && tm < count - 1) + tm++; // We don't want to close the gap between t0 and tm. + + convex_hull.m_tree_hull.addElement(t0, -1); + convex_hull.m_tree_hull.addBiggestElement(tm, -1); + + for (int tp = tm + 1; tp < count; tp++) {// Dynamically insert into the + // current convex hull. + + Point2D pt_p = points[tp]; + int p = convex_hull.treeHull_(pt_p); + + if (p != -1) + convex_hull.m_tree_hull.setElement(p, tp); // reset the place + // holder to the + // point index. + } + + // Extracts the convex hull from the tree. Reading the tree in order + // from first to last is the resulting convex hull. + int out_count = 0; + for (int i = convex_hull.m_tree_hull.getFirst(-1); i != -1; i = convex_hull.m_tree_hull + .getNext(i)) + bounding_geometry[out_count++] = convex_hull.m_tree_hull + .getElement(i); + + return out_count; + } + + /** + * Returns true if the given path of the input MultiPath is convex. Returns + * false otherwise. \param multi_path The MultiPath to check if the path is + * convex. \param path_index The path of the MultiPath to check if its + * convex. + */ + static boolean isPathConvex(MultiPath multi_path, int path_index, + ProgressTracker progress_tracker) { + MultiPathImpl mimpl = (MultiPathImpl) multi_path._getImpl(); + int path_start = mimpl.getPathStart(path_index); + int path_end = mimpl.getPathEnd(path_index); + + boolean bxyclosed = !mimpl.isClosedPath(path_index) + && mimpl.isClosedPathInXYPlane(path_index); + + AttributeStreamOfDbl position = (AttributeStreamOfDbl) (mimpl + .getAttributeStreamRef(VertexDescription.Semantics.POSITION)); + int position_start = 2 * path_start; + int position_end = 2 * path_end; + + if (bxyclosed) + position_end -= 2; + + if (position_end - position_start < 6) + return true; + + // This matches the logic for case 1 of the tree hull algorithm. The + // idea is inductive. We assume we have a convex hull pt_0,...,pt_m, and + // we see if + // a new point (pt_pivot) is among the transitive tournament for pt_0, + // knowing that pt_pivot comes after pt_m. + + // We check three conditions: + // 1) pt_m->pt_pivot->pt_0 is clockwise (closure across the boundary is + // convex) + // 2) pt_1->pt_pivot->pt_0 is clockwise (the first step forward is + // convex) (pt_1 is the next point after pt_0) + // 3) pt_m->pt_pivot->pt_m_prev is clockwise (the first step backwards + // is convex) (pt_m_prev is the previous point before pt_m) + + // If all three of the above conditions are clockwise, then pt_pivot is + // among the transitive tournament for pt_0, and therefore the polygon + // pt_0, ..., pt_m, pt_pivot is convex. + + Point2D pt_0 = new Point2D(), pt_m = new Point2D(), pt_pivot = new Point2D(); + position.read(position_start, pt_0); + position.read(position_start + 2, pt_m); + position.read(position_start + 4, pt_pivot); + + // Initial inductive step + ECoordinate det_ec = determinant_(pt_m, pt_pivot, pt_0); + + if (det_ec.isFuzzyZero() || !isClockwise_(det_ec.value())) + return false; + + Point2D pt_1 = new Point2D(pt_m.x, pt_m.y); + Point2D pt_m_prev = new Point2D(); + + // Assume that pt_0,...,pt_m is convex. Check if the next point, + // pt_pivot, maintains the convex invariant. + for (int i = position_start + 6; i < position_end; i += 2) { + pt_m_prev.setCoords(pt_m); + pt_m.setCoords(pt_pivot); + position.read(i, pt_pivot); + + det_ec = determinant_(pt_m, pt_pivot, pt_0); + + if (det_ec.isFuzzyZero() || !isClockwise_(det_ec.value())) + return false; + + det_ec = determinant_(pt_1, pt_pivot, pt_0); + + if (det_ec.isFuzzyZero() || !isClockwise_(det_ec.value())) + return false; + + det_ec = determinant_(pt_m, pt_pivot, pt_m_prev); + + if (det_ec.isFuzzyZero() || !isClockwise_(det_ec.value())) + return false; + } + + return true; + } + + // Dynamically inserts each geometry into the convex hull. + private void addMultiVertexGeometry_(MultiVertexGeometry mvg) { + Point point = new Point(); + Point2D pt_p = new Point2D(); + + for (int i = 0; i < mvg.getPointCount(); i++) { + mvg.getXY(i, pt_p); + int p = addPoint_(pt_p); + + if (p != -1) { + mvg.getPointByVal(i, point); + int tp = m_shape.addPoint(m_path_handle, point); + m_tree_hull.setElement(p, tp); // reset the place holder to tp + } + } + } + + private void addEnvelope_(Envelope envelope) { + Point point = new Point(); + Point2D pt_p = new Point2D(); + + for (int i = 0; i < 4; i++) { + envelope.queryCorner(i, pt_p); + int p = addPoint_(pt_p); + + if (p != -1) { + envelope.queryCornerByVal(i, point); + int tp = m_shape.addPoint(m_path_handle, point); + m_tree_hull.setElement(p, tp); // reset the place holder to tp + } + } + } + + private void addSegment_(Segment segment) { + Point point = new Point(); + + Point2D pt_start = segment.getStartXY(); + int p_start = addPoint_(pt_start); + + if (p_start != -1) { + segment.queryStart(point); + int t_start = m_shape.addPoint(m_path_handle, point); + m_tree_hull.setElement(p_start, t_start); // reset the place holder + // to tp + } + + Point2D pt_end = segment.getEndXY(); + int p_end = addPoint_(pt_end); + + if (p_end != -1) { + segment.queryEnd(point); + int t_end = m_shape.addPoint(m_path_handle, point); + m_tree_hull.setElement(p_end, t_end); // reset the place holder to + // tp + } + } + + private void addPoint_(Point point) { + Point2D pt_p = point.getXY(); + int p = addPoint_(pt_p); + + if (p != -1) { + int tp = m_shape.addPoint(m_path_handle, point); + m_tree_hull.setElement(p, tp); // reset the place holder to tp + } + } + + private int addPoint_(Point2D pt_p) { + int p = -1; + + if (m_tree_hull.size(-1) == 0) { + p = m_tree_hull.addElement(-4, -1); // set place holder to -4 to + // indicate the first element + // being added (t0). + return p; + } + + if (m_tree_hull.size(-1) == 1) { + int t0 = m_tree_hull.getElement(m_tree_hull.getFirst(-1)); + Point2D pt_0 = m_shape.getXY(t0); + + if (!pt_p.isEqual(pt_0, NumberUtils.doubleEps())) // We don't want + // to close the + // gap between + // t0 and tm. + p = m_tree_hull.addBiggestElement(-5, -1); // set place holder + // to -5 to indicate + // the second + // element being + // added (tm). + + return p; + } + + p = treeHull_(pt_p); + return p; + } + + // Algorithm taken from "Axioms and Hulls" by D.E. Knuth, Lecture Notes in + // Computer Science 606, page 47. + private int treeHull_(Point2D pt_pivot) { + assert (m_tree_hull.size(-1) >= 2); + + int p = -1; + + do { + int first = m_tree_hull.getFirst(-1); + int last = m_tree_hull.getLast(-1); + int t0 = m_tree_hull.getElement(first); + int tm = m_tree_hull.getElement(last); + + Point2D pt_0 = new Point2D(); // should the memory be cached? + Point2D pt_m = new Point2D(); // should the memory be cached? + m_call_back.getXY(t0, pt_0); + m_call_back.getXY(tm, pt_m); + + assert (!pt_0.isEqual(pt_m, NumberUtils.doubleEps())); // assert + // that the + // gap is + // not + // closed + + int orient_m_p_0 = Point2D.orientationRobust(pt_m, pt_pivot, pt_0); // determines + // case + // 1, + // 2, + // 3 + + if (isClockwise_(orient_m_p_0)) {// Case 1: tp->t0->tm is clockwise + + p = m_tree_hull.addBiggestElement(-1, -1); // set place holder + // to -1 for case 1. + int l = treeHullWalkBackward_(pt_pivot, last, first); + + if (l != first) + treeHullWalkForward_(pt_pivot, first, + m_tree_hull.getPrev(l)); + + continue; + } + + if (isCounterClockwise_(orient_m_p_0)) {// Case 2: tp->tm->t0 is + // clockwise + int k = m_tree_hull.getRoot(-1), k_min = m_tree_hull + .getFirst(-1), k_max = m_tree_hull.getLast(-1), k_prev; + int tk, tk_prev; + Point2D pt_k = new Point2D(); + Point2D pt_k_prev = new Point2D(); + + while (k_min != m_tree_hull.getPrev(k_max)) {// binary search to + // find k such + // that + // t0->tp->tj + // holds (i.e. + // clockwise) + // for j >= k. + // Hence, + // tj->tp->t0 is + // clockwise (or + // degenerate) + // for j < k. + tk = m_tree_hull.getElement(k); + m_call_back.getXY(tk, pt_k); + int orient_k_p_0 = Point2D.orientationRobust(pt_k, + pt_pivot, pt_0); + + if (isCounterClockwise_(orient_k_p_0)) { + k_max = k; + k = m_tree_hull.getLeft(k); + } else { + k_min = k; + k = m_tree_hull.getRight(k); + } + } + + k = k_max; + k_prev = k_min; + tk = m_tree_hull.getElement(k); + tk_prev = m_tree_hull.getElement(k_prev); + m_call_back.getXY(tk, pt_k); + m_call_back.getXY(tk_prev, pt_k_prev); + assert (isCounterClockwise_(Point2D.orientationRobust(pt_k, + pt_pivot, pt_0)) && !isCounterClockwise_(Point2D + .orientationRobust(pt_k_prev, pt_pivot, pt_0))); + assert (k_prev != first || isCounterClockwise_(Point2D + .orientationRobust(pt_k, pt_pivot, pt_0))); + + if (k_prev != first) { + int orient_k_prev_p_k = Point2D.orientationRobust( + pt_k_prev, pt_pivot, pt_k); + + if (!isClockwise_(orient_k_prev_p_k)) + continue; // pt_pivot is inside the hull (or on the + // boundary) + } + + p = m_tree_hull.addElementAtPosition(k_prev, k, -2, true, + false, -1); // set place holder to -2 for case 2. + treeHullWalkForward_(pt_pivot, k, last); + treeHullWalkBackward_(pt_pivot, k_prev, first); + + continue; + } + + assert (isDegenerate_(orient_m_p_0)); + {// Case 3: degenerate + + if (m_line == null) + m_line = new Line(); + + m_line.setStartXY(pt_m); + m_line.setEndXY(pt_0); + + double scalar = m_line.getClosestCoordinate(pt_pivot, true); // if + // scalar + // is + // between + // 0 + // and + // 1, + // then + // we + // don't + // need + // to + // add + // tp. + + if (scalar < 0.0) { + int l = m_tree_hull.getPrev(last); + m_tree_hull.deleteNode(last, -1); + p = m_tree_hull.addBiggestElement(-3, -1); // set place + // holder to -3 + // for case 3. + treeHullWalkBackward_(pt_pivot, l, first); + } else if (scalar > 1.0) { + int j = m_tree_hull.getNext(first); + m_tree_hull.deleteNode(first, -1); + p = m_tree_hull.addElementAtPosition(-1, j, -3, true, + false, -1); // set place holder to -3 for case 3. + treeHullWalkForward_(pt_pivot, j, last); + } + + continue; + } + + } while (false); + + return p; + } + + private int treeHullWalkForward_(Point2D pt_pivot, int start, int end) { + if (start == end) + return end; + + int j = start; + int tj = m_tree_hull.getElement(j); + int j_next = m_tree_hull.getNext(j); + Point2D pt_j = new Point2D(); + Point2D pt_j_next = new Point2D(); + + m_call_back.getXY(tj, pt_j); + + while (j != end && m_tree_hull.size(-1) > 2) {// Stops when we find a + // clockwise triple + // containting the pivot + // point, or when the + // tree_hull size is 2. + // Deletes non-clockwise + // triples along the + // way. + int tj_next = m_tree_hull.getElement(j_next); + m_call_back.getXY(tj_next, pt_j_next); + + int orient_j_next_p_j = Point2D.orientationRobust(pt_j_next, + pt_pivot, pt_j); + + if (isClockwise_(orient_j_next_p_j)) + break; + + int ccw = j; + + j = j_next; + pt_j.setCoords(pt_j_next); + j_next = m_tree_hull.getNext(j); + m_call_back.deleteNode(ccw); + } + + return j; + } + + private int treeHullWalkBackward_(Point2D pt_pivot, int start, int end) { + if (start == end) + return end; + + int l = start; + int tl = m_tree_hull.getElement(l); + int l_prev = m_tree_hull.getPrev(l); + Point2D pt_l = new Point2D(); + Point2D pt_l_prev = new Point2D(); + + m_call_back.getXY(tl, pt_l); + + while (l != end && m_tree_hull.size(-1) > 2) {// Stops when we find a + // clockwise triple + // containting the pivot + // point, or when the + // tree_hull size is 2. + // Deletes non-clockwise + // triples along the + // way. + int tl_prev = m_tree_hull.getElement(l_prev); + m_call_back.getXY(tl_prev, pt_l_prev); + + int orient_l_p_l_prev = Point2D.orientationRobust(pt_l, pt_pivot, + pt_l_prev); + + if (isClockwise_(orient_l_p_l_prev)) + break; + + int ccw = l; + + l = l_prev; + pt_l.setCoords(pt_l_prev); + l_prev = m_tree_hull.getPrev(l); + m_call_back.deleteNode(ccw); + } + + return l; + } + + // Orientation predicates + private static ECoordinate determinant_(Point2D p, Point2D q, Point2D r) { + ECoordinate det_ec = new ECoordinate(); + det_ec.set(q.x); + det_ec.sub(p.x); + + ECoordinate rp_y_ec = new ECoordinate(); + rp_y_ec.set(r.y); + rp_y_ec.sub(p.y); + + ECoordinate qp_y_ec = new ECoordinate(); + qp_y_ec.set(q.y); + qp_y_ec.sub(p.y); + + ECoordinate rp_x_ec = new ECoordinate(); + rp_x_ec.set(r.x); + rp_x_ec.sub(p.x); + + det_ec.mul(rp_y_ec); + qp_y_ec.mul(rp_x_ec); + det_ec.sub(qp_y_ec); + return det_ec; + } + + private static boolean isClockwise_(double det) { + return det < 0.0; + } + + private static boolean isCounterClockwise_(double det) { + return det > 0.0; + } + + private static boolean isDegenerate_(double det) { + return det == 0.0; + } + + private static boolean isClockwise_(int orientation) { + return orientation < 0.0; + } + + private static boolean isCounterClockwise_(int orientation) { + return orientation > 0.0; + } + + private static boolean isDegenerate_(int orientation) { + return orientation == 0.0; + } + + private static abstract class CallBack { + abstract void getXY(int ti, Point2D pt); + + abstract void deleteNode(int i); + } + + private static final class CallBackShape extends CallBack { + private ConvexHull m_convex_hull; + + CallBackShape(ConvexHull convex_hull) { + m_convex_hull = convex_hull; + } + + @Override + void getXY(int ti, Point2D pt) { + m_convex_hull.m_shape.getXY(ti, pt); + } + + @Override + void deleteNode(int i) { + int ti = m_convex_hull.m_tree_hull.getElement(i); + m_convex_hull.m_tree_hull.deleteNode(i, -1); + m_convex_hull.m_shape.removeVertex(ti, false); + } + } + + private static final class CallBackMvg extends CallBack { + private ConvexHull m_convex_hull; + + CallBackMvg(ConvexHull convex_hull) { + m_convex_hull = convex_hull; + } + + @Override + void getXY(int ti, Point2D pt) { + m_convex_hull.m_mvg.getXY(ti, pt); + } + + @Override + void deleteNode(int i) { + m_convex_hull.m_tree_hull.deleteNode(i, -1); + } + } + + private static final class CallBackPoints extends CallBack { + private ConvexHull m_convex_hull; + + CallBackPoints(ConvexHull convex_hull) { + m_convex_hull = convex_hull; + } + + @Override + void getXY(int ti, Point2D pt) { + pt.setCoords(m_convex_hull.m_points[ti]); + } + + @Override + void deleteNode(int i) { + m_convex_hull.m_tree_hull.deleteNode(i, -1); + } + } + + // Members + private Treap m_tree_hull; + private EditShape m_shape; + private MultiVertexGeometry m_mvg; + private Point2D[] m_points; + private int m_geometry_handle; + private int m_path_handle; + private Line m_line; + private CallBack m_call_back; +} diff --git a/src/com/esri/core/geometry/CrackAndCluster.java b/src/com/esri/core/geometry/CrackAndCluster.java new file mode 100644 index 00000000..cb8b63ee --- /dev/null +++ b/src/com/esri/core/geometry/CrackAndCluster.java @@ -0,0 +1,118 @@ +/* + Copyright 1995-2013 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ +package com.esri.core.geometry; + +//Implementation of the cracking and clustering algorithm. +//Cracks and clusters all segments and vertices in the EditShape. + +final class CrackAndCluster { + private EditShape m_shape = null; + private ProgressTracker m_progressTracker = null; + private double m_tolerance; + + private CrackAndCluster(ProgressTracker progressTracker) { + m_progressTracker = progressTracker; + } + + public static boolean execute(EditShape shape, double tolerance, + ProgressTracker progressTracker) { + CrackAndCluster cracker = new CrackAndCluster(progressTracker); + cracker.m_shape = shape; + cracker.m_tolerance = tolerance; + return cracker._do(); + } + + private boolean _cluster(double toleranceCluster) { + boolean res = Clusterer.executeNonReciprocal(m_shape, toleranceCluster); + // if (false) + // { + // Geometry geometry = + // m_shape.getGeometry(m_shape.getFirstGeometry());//extract the result + // of simplify + // ((MultiPathImpl)geometry._GetImpl()).SaveToTextFileDbg("c:/temp/_simplifyDbg.txt"); + // } + + return res; + } + + private boolean _crack() { + boolean res = Cracker.execute(m_shape, m_tolerance, m_progressTracker); + // if (false) + // { + // for (int geom = m_shape.getFirstGeometry(); geom != -1; geom = + // m_shape.getNextGeometry(geom)) + // { + // Geometry geometry = m_shape.getGeometry(geom);//extract the result of + // simplify + // ((MultiPathImpl)geometry._getImpl()).SaveToTextFileDbg("c:/temp/_simplifyDbg.txt");//NOTE: + // It ovewrites the previous one! + // } + // } + + return res; + } + + private boolean _do() { + double toleranceCluster = m_tolerance * Math.sqrt(2.0) * 1.00001; + boolean bChanged = false; + int max_iter = m_shape.getTotalPointCount() + 10 > 30 ? 1000 : (m_shape + .getTotalPointCount() + 10) + * (m_shape.getTotalPointCount() + 10); + int iter = 0; + for (;; iter++) { + if (iter > max_iter) + throw new GeometryException( + "Internal Error: max number of iterations exceeded");// too + // many + // iterations + + boolean bClustered = _cluster(toleranceCluster); // find close + // vertices and + // clamp them + // together. + bChanged |= bClustered; + + boolean bFiltered = (m_shape.filterClosePoints(toleranceCluster, + true) != 0); // remove all degenerate segments. + bChanged |= bFiltered; + // _ASSERT(!m_shape.hasDegenerateSegments(toleranceCluster)); + boolean bCracked = _crack(); // crack all segments at intersection + // points and touch points. + bChanged |= bCracked; + + if (!bCracked) + break; + else { + // Loop while cracking happens. + } + + if (m_progressTracker != null + && !m_progressTracker.progress(-1, -1)) + throw new UserCancelException(); + } + + return bChanged; + } + +} diff --git a/src/com/esri/core/geometry/Cracker.java b/src/com/esri/core/geometry/Cracker.java new file mode 100644 index 00000000..822f8bbb --- /dev/null +++ b/src/com/esri/core/geometry/Cracker.java @@ -0,0 +1,541 @@ +/* + Copyright 1995-2013 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ +package com.esri.core.geometry; + +import java.util.Arrays; + +/** + * Implementation for the segment cracking. + * + * Finds and splits all intersecting segments. Used by the TopoGraph and + * Simplify. + */ +class Cracker { + private EditShape m_shape; + private Envelope2D m_extent; + private ProgressTracker m_progress_tracker; + private NonSimpleResult m_non_simple_result; + private double m_tolerance; + private double m_tolerance_cluster; + private Treap m_sweep_structure; + private SweepComparator m_sweep_comparator; + + boolean crackBruteForce_() { + EditShape.VertexIterator iter_1 = m_shape.queryVertexIterator(false); + boolean b_cracked = false; + Line line_1 = new Line(); + Line line_2 = new Line(); + Envelope2D seg_1_env = new Envelope2D(); + seg_1_env.setEmpty(); + Envelope2D seg_2_env = new Envelope2D(); + seg_2_env.setEmpty(); + boolean b_needs_filter = false; + boolean assume_intersecting = false; + Point helper_point = new Point(); + SegmentIntersector segment_intersector = new SegmentIntersector(); + + for (int vertex_1 = iter_1.next(); vertex_1 != -1; vertex_1 = iter_1 + .next()) { + if ((m_progress_tracker != null) + && !(m_progress_tracker.progress(-1, -1))) + throw new RuntimeException("user_canceled"); + + int GT_1 = m_shape.getGeometryType(iter_1.currentGeometry()); + + Segment seg_1 = null; + if (!Geometry.isPoint(GT_1)) { + seg_1 = m_shape.getSegment(vertex_1); + + if (seg_1 == null) { + if (!m_shape.queryLineConnector(vertex_1, line_1)) + continue; + seg_1 = line_1; + line_1.queryEnvelope2D(seg_1_env); + } else { + seg_1.queryEnvelope2D(seg_1_env); + } + + seg_1_env.inflate(m_tolerance, m_tolerance); + + if (seg_1.isDegenerate(m_tolerance))// do not crack with + // degenerate segments + { + b_needs_filter = true; + continue; + } + } + + EditShape.VertexIterator iter_2 = m_shape + .queryVertexIterator(iter_1); + int vertex_2 = iter_2.next(); + if (vertex_2 != -1) + vertex_2 = iter_2.next(); + + for (; vertex_2 != -1; vertex_2 = iter_2.next()) { + int GT_2 = m_shape.getGeometryType(iter_2.currentGeometry()); + + Segment seg_2 = null; + if (!Geometry.isPoint(GT_2)) { + seg_2 = m_shape.getSegment(vertex_2); + if (seg_2 == null) { + if (!m_shape.queryLineConnector(vertex_2, line_2)) + continue; + seg_2 = line_2; + line_2.queryEnvelope2D(seg_2_env); + } else + seg_2.queryEnvelope2D(seg_2_env); + + if (seg_2.isDegenerate(m_tolerance))// do not crack with + // degenerate segments + { + b_needs_filter = true; + continue; + } + } + + int split_count_1 = 0; + int split_count_2 = 0; + if (seg_1 != null && seg_2 != null) { + if (seg_1_env.isIntersectingNE(seg_2_env)) { + segment_intersector.pushSegment(seg_1); + segment_intersector.pushSegment(seg_2); + segment_intersector.intersect(m_tolerance, + assume_intersecting); + split_count_1 = segment_intersector + .getResultSegmentCount(0); + split_count_2 = segment_intersector + .getResultSegmentCount(1); + if (split_count_1 + split_count_2 > 0) { + m_shape.splitSegment_(vertex_1, + segment_intersector, 0, true); + m_shape.splitSegment_(vertex_2, + segment_intersector, 1, true); + } + segment_intersector.clear(); + } + } else { + if (seg_1 != null) { + Point2D pt = new Point2D(); + m_shape.getXY(vertex_2, pt); + if (seg_1_env.contains(pt)) { + segment_intersector.pushSegment(seg_1); + m_shape.queryPoint(vertex_2, helper_point); + segment_intersector.intersect(m_tolerance, + helper_point, 0, 1.0, assume_intersecting); + split_count_1 = segment_intersector + .getResultSegmentCount(0); + if (split_count_1 > 0) { + m_shape.splitSegment_(vertex_1, + segment_intersector, 0, true); + m_shape.setPoint(vertex_2, + segment_intersector.getResultPoint()); + } + segment_intersector.clear(); + } + } else if (seg_2 != null) { + Point2D pt = new Point2D(); + m_shape.getXY(vertex_1, pt); + seg_2_env.inflate(m_tolerance, m_tolerance); + if (seg_2_env.contains(pt)) { + segment_intersector.pushSegment(seg_2); + m_shape.queryPoint(vertex_1, helper_point); + segment_intersector.intersect(m_tolerance, + helper_point, 0, 1.0, assume_intersecting); + split_count_2 = segment_intersector + .getResultSegmentCount(0); + if (split_count_2 > 0) { + m_shape.splitSegment_(vertex_2, + segment_intersector, 0, true); + m_shape.setPoint(vertex_1, + segment_intersector.getResultPoint()); + } + segment_intersector.clear(); + } + } else { + continue;// points on points + } + } + + if (split_count_1 + split_count_2 != 0) { + if (split_count_1 != 0) { + seg_1 = m_shape.getSegment(vertex_1);// reload segment + // after split + if (seg_1 == null) { + if (!m_shape.queryLineConnector(vertex_1, line_1)) + continue; + seg_1 = line_1; + line_1.queryEnvelope2D(seg_1_env); + } else + seg_1.queryEnvelope2D(seg_1_env); + + if (seg_1.isDegenerate(m_tolerance))// do not crack with + // degenerate + // segments + { + b_needs_filter = true; + break; + } + } + + b_cracked = true; + } + } + } + + return b_cracked; + } + + boolean crackerPlaneSweep_() { + boolean b_cracked = planeSweep_(); + return b_cracked; + } + + boolean planeSweep_() { + PlaneSweepCrackerHelper plane_sweep = new PlaneSweepCrackerHelper(); + boolean b_cracked = plane_sweep.sweep(m_shape, m_tolerance); + return b_cracked; + } + + boolean needsCrackingImpl_() { + if (m_sweep_structure == null) + m_sweep_structure = new Treap(); + + boolean b_needs_cracking = false; + + AttributeStreamOfInt32 event_q = new AttributeStreamOfInt32(0); + event_q.reserve(m_shape.getTotalPointCount() + 1); + + EditShape.VertexIterator iter = m_shape.queryVertexIterator(); + for (int vert = iter.next(); vert != -1; vert = iter.next()) { + event_q.add(vert); + } + assert (m_shape.getTotalPointCount() == event_q.size()); + + m_shape.sortVerticesSimpleByY_(event_q, 0, event_q.size()); + event_q.add(-1);// for termination; + // create user indices to store edges that end at vertices. + int edge_index_1 = m_shape.createUserIndex(); + int edge_index_2 = m_shape.createUserIndex(); + m_sweep_comparator = new SweepComparator(m_shape, m_tolerance, true); + m_sweep_structure.setComparator(m_sweep_comparator); + + AttributeStreamOfInt32 new_edges = new AttributeStreamOfInt32(0); + AttributeStreamOfInt32 swept_edges_to_delete = new AttributeStreamOfInt32( + 0); + AttributeStreamOfInt32 edges_to_insert = new AttributeStreamOfInt32(0); + + // Go throught the sorted vertices + int event_q_index = 0; + + // sweep-line algorithm: + for (int vertex = event_q.get(event_q_index++); vertex != -1;) { + Point2D cluster_pt = m_shape.getXY(vertex); + do { + int next_vertex = m_shape.getNextVertex(vertex); + int prev_vertex = m_shape.getPrevVertex(vertex); + + if (next_vertex != -1 + && m_shape.compareVerticesSimpleY_(vertex, next_vertex) < 0) { + edges_to_insert.add(vertex); + edges_to_insert.add(next_vertex); + } + + if (prev_vertex != -1 + && m_shape.compareVerticesSimpleY_(vertex, prev_vertex) < 0) { + edges_to_insert.add(prev_vertex); + edges_to_insert.add(prev_vertex); + } + + // Continue accumulating current cluster + int attached_edge_1 = m_shape + .getUserIndex(vertex, edge_index_1); + if (attached_edge_1 != -1) { + swept_edges_to_delete.add(attached_edge_1); + m_shape.setUserIndex(vertex, edge_index_1, -1); + } + int attached_edge_2 = m_shape + .getUserIndex(vertex, edge_index_2); + if (attached_edge_2 != -1) { + swept_edges_to_delete.add(attached_edge_2); + m_shape.setUserIndex(vertex, edge_index_2, -1); + } + vertex = event_q.get(event_q_index++); + } while (vertex != -1 && m_shape.isEqualXY(vertex, cluster_pt)); + + boolean b_continuing_segment_chain_optimization = swept_edges_to_delete + .size() == 1 && edges_to_insert.size() == 2; + + int new_left = -1; + int new_right = -1; + // Process the cluster + for (int i = 0, n = swept_edges_to_delete.size(); i < n; i++) { + // Find left and right neighbour of the edges that terminate at + // the cluster (there will be atmost only one left and one + // right). + int edge = swept_edges_to_delete.get(i); + int left = m_sweep_structure.getPrev(edge); + if (left != -1 && !swept_edges_to_delete.hasElement(left))// Note: + // for + // some + // heavy + // cases, + // it + // could + // be + // better + // to + // use + // binary + // search. + { + assert (new_left == -1); + // if (false) + // { + // dbg_print_sweep_structure_(); + // } + // if (false) + // { + // dbg_print_sweep_edge_(edge); + // dbg_print_sweep_edge_(left); + // dbg_print_sweep_edge_(new_left); + // } + new_left = left; + } + + int right = m_sweep_structure.getNext(edge); + if (right != -1 && !swept_edges_to_delete.hasElement(right)) { + assert (new_right == -1); + new_right = right; + } + // #ifndef DEBUG + // if (new_left != -1 && new_right != -1) + // break; + // #endif + } + + assert (new_left == -1 || new_left != new_right); + + // if (false) + // { + // dbg_print_sweep_structure_(); + // } + + m_sweep_comparator.setSweepY(cluster_pt.y, cluster_pt.x); + + // Delete the edges that terminate at the cluster. + for (int i = 0, n = swept_edges_to_delete.size(); i < n; i++) { + int edge = swept_edges_to_delete.get(i); + m_sweep_structure.deleteNode(edge, -1); + } + swept_edges_to_delete.resizePreserveCapacity(0); + + if (new_left != -1 && new_right != -1) { + if (checkForIntersections_(new_left, new_right)) { + b_needs_cracking = true; + m_non_simple_result = m_sweep_comparator.getResult(); + break; + } + } + + for (int i = 0, n = edges_to_insert.size(); i < n; i += 2) { + int v = edges_to_insert.get(i); + int otherv = edges_to_insert.get(i + 1); + ; + + int new_edge_1; + if (b_continuing_segment_chain_optimization) { + new_edge_1 = m_sweep_structure.addElementAtPosition( + new_left, new_right, v, true, true, -1); + b_continuing_segment_chain_optimization = false; + } else { + new_edge_1 = m_sweep_structure.addElement(v, -1); // the + // sweep + // structure + // consist + // of + // the + // origin + // vertices + // for + // edges. + // One + // can + // always + // get + // the + // other + // endpoint + // as + // the + // next + // vertex. + } + + // DbgCheckSweepStructure(); + if (m_sweep_comparator.intersectionDetected()) { + m_non_simple_result = m_sweep_comparator.getResult(); + b_needs_cracking = true; + break; + } + + int e_1 = m_shape.getUserIndex(otherv, edge_index_1); + if (e_1 == -1) + m_shape.setUserIndex(otherv, edge_index_1, new_edge_1); + else { + assert (m_shape.getUserIndex(otherv, edge_index_2) == -1); + m_shape.setUserIndex(otherv, edge_index_2, new_edge_1); + } + } + /* + * for (int i = 0, n = new_edges.size(); i < n; i++) { int edge = + * new_edges.get(i); int left = m_sweep_structure.get_prev(edge); + * int right = m_sweep_structure.get_next(edge); if (left != -1) { + * if (check_for_intersections_(left, edge)) { b_needs_cracking = + * true; m_non_simple_result = m_sweep_comparator->get_result(); + * break; } } if (right != -1) { if (check_for_intersections_(right, + * edge)) { b_needs_cracking = true; m_non_simple_result = + * m_sweep_comparator->get_result(); break; } } } + */ + + if (b_needs_cracking) + break; + + // Start accumulating new cluster + edges_to_insert.resizePreserveCapacity(0); + } + + m_shape.removeUserIndex(edge_index_1); + m_shape.removeUserIndex(edge_index_2); + // DEBUGPRINTF("number of compare calls: %d\n", g_dbg); + // DEBUGPRINTF("total point count: %d\n", + // m_shape->get_total_point_count()); + return b_needs_cracking; + } + + boolean checkForIntersections_(int sweep_edge_1, int sweep_edge_2) { + assert (sweep_edge_1 != sweep_edge_2); + int left = m_sweep_structure.getElement(sweep_edge_1); + assert (left != m_sweep_structure.getElement(sweep_edge_2)); + m_sweep_comparator.compare(m_sweep_structure, left, sweep_edge_2);// compare + // detects + // intersections + boolean b_intersects = m_sweep_comparator.intersectionDetected(); + m_sweep_comparator.clearIntersectionDetectedFlag(); + return b_intersects; + } + + // void dbg_print_sweep_edge_(int edge); + // void dbg_print_sweep_structure_(); + // void dbg_check_sweep_structure_(); + Cracker(ProgressTracker progress_tracker) { + m_progress_tracker = progress_tracker; + } + + static boolean canBeCracked(EditShape shape) { + for (int geometry = shape.getFirstGeometry(); geometry != -1; geometry = shape + .getNextGeometry(geometry)) { + if (!Geometry.isMultiPath(shape.getGeometryType(geometry))) + continue; + return true; + } + return false; + } + + static boolean execute(EditShape shape, Envelope2D extent, + double tolerance, ProgressTracker progress_tracker) { + if (!canBeCracked(shape)) // make sure it contains some segments, + // otherwise no need to crack. + return false; + + Cracker cracker = new Cracker(progress_tracker); + cracker.m_shape = shape; + cracker.m_tolerance = tolerance; + cracker.m_tolerance_cluster = -1; + cracker.m_extent = extent; + // Use brute force for smaller shapes, and a planesweep for bigger + // shapes. + boolean b_cracked = false; + if (shape.getTotalPointCount() < 15) // what is a good number? + { + b_cracked = cracker.crackBruteForce_(); + } else { + // bool b_cracked_1 = cracker.crack_quad_tree_(); + // bool b_cracked_2 = cracker.crack_quad_tree_(); + // assert(!b_cracked_2); + + boolean b_cracked_1 = cracker.crackerPlaneSweep_(); + // bool b_cracked_1 = cracker.crack_quad_tree_(); + // bool b_cracked_1 = cracker.crack_envelope_2D_intersector_(); + return b_cracked_1; + } + return b_cracked; + } + + static boolean execute(EditShape shape, double tolerance, + ProgressTracker progress_tracker) { + return Cracker.execute(shape, shape.getEnvelope2D(), tolerance, + progress_tracker); + } + + // Used for IsSimple. + static boolean needsCracking(EditShape shape, double tolerance, + NonSimpleResult result, ProgressTracker progress_tracker) { + if (!canBeCracked(shape)) + return false; + + Envelope2D shape_env = shape.getEnvelope2D(); + Cracker cracker = new Cracker(progress_tracker); + cracker.m_shape = shape; + cracker.m_tolerance = tolerance; + cracker.m_extent = shape_env; + if (cracker.needsCrackingImpl_()) { + if (result != null) + result.Assign(cracker.m_non_simple_result); + return true; + } + + // Now swap the coordinates to catch horizontal cases. + Transformation2D transform = new Transformation2D(); + transform.setSwapCoordinates(); + shape.applyTransformation(transform); + transform.transform(shape_env); + + cracker = new Cracker(progress_tracker); + cracker.m_shape = shape; + cracker.m_tolerance = tolerance; + cracker.m_extent = shape_env; + boolean b_res = cracker.needsCrackingImpl_(); + + transform.setSwapCoordinates(); + shape.applyTransformation(transform);// restore shape + + if (b_res) { + if (result != null) + result.Assign(cracker.m_non_simple_result); + return true; + } + + return false; + } +} diff --git a/src/com/esri/core/geometry/Cutter.java b/src/com/esri/core/geometry/Cutter.java new file mode 100644 index 00000000..66710733 --- /dev/null +++ b/src/com/esri/core/geometry/Cutter.java @@ -0,0 +1,1428 @@ +/* + Copyright 1995-2013 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ + + +package com.esri.core.geometry; + +import com.esri.core.geometry.OperatorCutLocal; + +import java.lang.reflect.Array; +import java.util.ArrayList; +import java.util.Arrays; + +class Cutter { + static class CompareVertices { + int m_orderIndex; + EditShape m_editShape; + + CompareVertices(int orderIndex, EditShape editShape) { + m_orderIndex = orderIndex; + m_editShape = editShape; + } + + int _compareVertices(int v1, int v2) { + Point2D pt1 = new Point2D(); + m_editShape.getXY(v1, pt1); + Point2D pt2 = new Point2D(); + m_editShape.getXY(v2, pt2); + int res = pt1.compare(pt2); + if (res != 0) + return res; + int z1 = m_editShape.getUserIndex(v1, m_orderIndex); + int z2 = m_editShape.getUserIndex(v2, m_orderIndex); + if (z1 < z2) + return -1; + if (z1 == z2) + return 0; + return 1; + } + } + + static class CutterVertexComparer extends + AttributeStreamOfInt32.IntComparator { + CompareVertices m_compareVertices; + + CutterVertexComparer(CompareVertices _compareVertices) { + m_compareVertices = _compareVertices; + } + + @Override + public int compare(int v1, int v2) { + return m_compareVertices._compareVertices(v1, v2); + } + } + + static class CutEvent { + int m_ivertexCuttee; + int m_ipartCuttee; + double m_scalarCuttee0; + double m_scalarCuttee1; + int m_count; + int m_ivertexCutter; + int m_ipartCutter; + double m_scalarCutter0; + double m_scalarCutter1; + + CutEvent(int ivertexCuttee, int ipartCuttee, double scalarCuttee0, + double scalarCuttee1, int count, int ivertexCutter, + int ipartCutter, double scalarCutter0, double scalarCutter1) { + m_ivertexCuttee = ivertexCuttee; + m_ipartCuttee = ipartCuttee; + m_scalarCuttee0 = scalarCuttee0; + m_scalarCuttee1 = scalarCuttee1; + m_count = count; + m_ivertexCutter = ivertexCutter; + m_ipartCutter = ipartCutter; + m_scalarCutter0 = scalarCutter0; + m_scalarCutter1 = scalarCutter1; + } + } + + static EditShape CutPolyline(boolean bConsiderTouch, Polyline cuttee, + Polyline cutter, double tolerance, + ArrayList cutPairs, + AttributeStreamOfInt32 segmentCounts) { + if (cuttee.isEmpty()) { + OperatorCutLocal.CutPair cutPair; + cutPair = new OperatorCutLocal.CutPair(cuttee, + OperatorCutLocal.Side.Uncut, -1, -1, NumberUtils.NaN(), + OperatorCutLocal.Side.Uncut, -1, -1, NumberUtils.NaN(), -1, + -1, NumberUtils.NaN(), -1, -1, NumberUtils.NaN()); + cutPairs.add(cutPair); + return null; + } + + EditShape editShape = new EditShape(); + int cutteeHandle = editShape.addGeometry(cuttee); + int cutterHandle = editShape.addGeometry(cutter); + CrackAndCluster.execute(editShape, tolerance, null); + + int order = 0; + int orderIndex = editShape.createUserIndex(); + for (int igeometry = editShape.getFirstGeometry(); igeometry != -1; igeometry = editShape + .getNextGeometry(igeometry)) + for (int ipath = editShape.getFirstPath(igeometry); ipath != -1; ipath = editShape + .getNextPath(ipath)) + for (int ivertex = editShape.getFirstVertex(ipath), i = 0, n = editShape + .getPathSize(ipath); i < n; ivertex = editShape + .getNextVertex(ivertex), i++) + editShape.setUserIndex(ivertex, orderIndex, order++); + + ArrayList cutEvents = _getCutEvents(orderIndex, editShape); + _Cut(bConsiderTouch, false, cutEvents, editShape, cutPairs, + segmentCounts); + return editShape; + } + + private static ArrayList _getCutEvents(int orderIndex, + EditShape editShape) { + int pointCount = editShape.getTotalPointCount(); + + // Sort vertices lexicographically + // Firstly copy allvertices to an array. + AttributeStreamOfInt32 vertices = new AttributeStreamOfInt32(0); + + for (int igeometry = editShape.getFirstGeometry(); igeometry != -1; igeometry = editShape + .getNextGeometry(igeometry)) + for (int ipath = editShape.getFirstPath(igeometry); ipath != -1; ipath = editShape + .getNextPath(ipath)) + for (int ivertex = editShape.getFirstVertex(ipath), i = 0, n = editShape + .getPathSize(ipath); i < n; ivertex = editShape + .getNextVertex(ivertex), i++) + vertices.add(ivertex); + + // Sort + CompareVertices compareVertices = new CompareVertices(orderIndex, + editShape); + vertices.Sort(0, pointCount, new CutterVertexComparer(compareVertices)); + // SORTDYNAMICARRAYEX(vertices, index_type, 0, pointCount, + // CutterVertexComparer, compareVertices); + + // Find Cut Events + ArrayList cutEvents = new ArrayList(0); + ArrayList cutEventsTemp = new ArrayList(0); + + int eventIndex = editShape.createUserIndex(); + int eventIndexTemp = editShape.createUserIndex(); + + int cutteeHandle = editShape.getFirstGeometry(); + int cutterHandle = editShape.getNextGeometry(cutteeHandle); + + Point2D pointCuttee = new Point2D(); + Point2D pointCutter = new Point2D(); + + int ivertexCuttee = vertices.get(0); + ; + int ipartCuttee = editShape.getPathFromVertex(ivertexCuttee); + int igeometryCuttee = editShape.getGeometryFromPath(ipartCuttee); + editShape.getXY(ivertexCuttee, pointCuttee); + + int istart = 1; + int ivertex = 0; + while (istart < pointCount - 1) { + boolean bCutEvent = false; + for (int i = istart; i < pointCount; i++) { + if (i == ivertex) + continue; + + int ivertexCutter = vertices.get(i); + int ipartCutter = editShape.getPathFromVertex(ivertexCutter); + int igeometryCutter = editShape + .getGeometryFromPath(ipartCutter); + editShape.getXY(ivertexCutter, pointCutter); + + if (pointCuttee.isEqual(pointCutter)) { + boolean bCondition = igeometryCuttee == cutteeHandle + && igeometryCutter == cutterHandle; + + if (bCondition) + bCutEvent = _cutteeCutterEvents(eventIndex, + eventIndexTemp, editShape, cutEvents, + cutEventsTemp, ipartCuttee, ivertexCuttee, + ipartCutter, ivertexCutter); + } else + break; + } + + if (bCutEvent || ivertex == istart - 1) { + if (bCutEvent && (ivertex == istart - 1)) + istart--; + + if (++ivertex == pointCount) + break; + + ivertexCuttee = vertices.get(ivertex); + ipartCuttee = editShape.getPathFromVertex(ivertexCuttee); + igeometryCuttee = editShape.getGeometryFromPath(ipartCuttee); + editShape.getXY(ivertexCuttee, pointCuttee); + } + + if (!bCutEvent) + istart = ivertex + 1; + } + + ArrayList cutEventsSorted = new ArrayList(0); + + // Sort CutEvents + int icutEvent; + int icutEventTemp; + for (int igeometry = editShape.getFirstGeometry(); igeometry != -1; igeometry = editShape + .getNextGeometry(igeometry)) { + for (int ipath = editShape.getFirstPath(igeometry); ipath != -1; ipath = editShape + .getNextPath(ipath)) { + for (int iv = editShape.getFirstVertex(ipath), i = 0, n = editShape + .getPathSize(ipath); i < n; iv = editShape + .getNextVertex(iv), i++) { + icutEventTemp = editShape.getUserIndex(iv, eventIndexTemp); + if (icutEventTemp >= 0) { + // _ASSERT(cutEventsTemp.get(icutEventTemp).m_ivertexCuttee + // == iv); + while (icutEventTemp < cutEventsTemp.size() + && cutEventsTemp.get(icutEventTemp).m_ivertexCuttee == iv) + cutEventsSorted.add(cutEventsTemp + .get(icutEventTemp++)); + } + + icutEvent = editShape.getUserIndex(iv, eventIndex); + if (icutEvent >= 0) { + // _ASSERT(cutEvents->Get(icutEvent)->m_ivertexCuttee == + // iv); + while (icutEvent < cutEvents.size() + && cutEvents.get(icutEvent).m_ivertexCuttee == iv) + cutEventsSorted.add(cutEvents.get(icutEvent++)); + } + } + } + } + + // _ASSERT(cutEvents->Size() + cutEventsTemp->Size() == + // cutEventsSorted->Size()); + editShape.removeUserIndex(eventIndex); + editShape.removeUserIndex(eventIndexTemp); + return cutEventsSorted; + } + + static boolean _cutteeCutterEvents(int eventIndex, int eventIndexTemp, + EditShape editShape, ArrayList cutEvents, + ArrayList cutEventsTemp, int ipartCuttee, + int ivertexCuttee, int ipartCutter, int ivertexCutter) { + int ilastVertexCuttee = editShape.getLastVertex(ipartCuttee); + int ilastVertexCutter = editShape.getLastVertex(ipartCutter); + int ifirstVertexCuttee = editShape.getFirstVertex(ipartCuttee); + int ifirstVertexCutter = editShape.getFirstVertex(ipartCutter); + int ivertexCutteePrev = editShape.getPrevVertex(ivertexCuttee); + int ivertexCutterPrev = editShape.getPrevVertex(ivertexCutter); + + boolean bEndEnd = false; + boolean bEndStart = false; + boolean bStartEnd = false; + boolean bStartStart = false; + + if (ivertexCuttee != ifirstVertexCuttee) { + if (ivertexCutter != ifirstVertexCutter) + bEndEnd = _cutteeEndCutterEndEvent(eventIndex, editShape, + cutEvents, ipartCuttee, ivertexCutteePrev, ipartCutter, + ivertexCutterPrev); + + if (ivertexCutter != ilastVertexCutter) + bEndStart = _cutteeEndCutterStartEvent(eventIndex, editShape, + cutEvents, ipartCuttee, ivertexCutteePrev, ipartCutter, + ivertexCutter); + } + + if (ivertexCuttee != ilastVertexCuttee) { + if (ivertexCutter != ifirstVertexCutter) + bStartEnd = _cutteeStartCutterEndEvent(eventIndexTemp, + editShape, cutEventsTemp, ipartCuttee, ivertexCuttee, + ipartCutter, ivertexCutterPrev, ifirstVertexCuttee); + + if (ivertexCutter != ilastVertexCutter) + bStartStart = _cutteeStartCutterStartEvent(eventIndexTemp, + editShape, cutEventsTemp, ipartCuttee, ivertexCuttee, + ipartCutter, ivertexCutter, ifirstVertexCuttee); + } + + if (bEndEnd && bEndStart && bStartEnd) { + int iendstart = cutEvents.size() - 1; + int istartend = (bStartStart ? cutEventsTemp.size() - 2 + : cutEventsTemp.size() - 1); + + if (cutEventsTemp.get(istartend).m_count == 2) { + // Replace bEndEnd with bEndStart, and remove duplicate + // bEndStart (get rid of bEndEnd) + cutEvents.set(iendstart - 1, cutEvents.get(iendstart)); + cutEvents.remove(cutEvents.size() - 1); + } + } else if (bEndEnd && bEndStart && bStartStart) { + int istartstart = cutEventsTemp.size() - 1; + + if (cutEventsTemp.get(istartstart).m_count == 2) { + // Remove bEndStart + CutEvent lastEvent = cutEvents.get(cutEvents.size() - 1); + cutEvents.remove(cutEvents.get(cutEvents.size() - 1)); + int icutEvent = editShape.getUserIndex( + lastEvent.m_ivertexCuttee, eventIndex); + if (icutEvent == cutEvents.size()) + editShape.setUserIndex(lastEvent.m_ivertexCuttee, + eventIndex, -1); + } + } + + return bEndEnd || bEndStart || bStartEnd || bStartStart; + } + + private static boolean _cutteeEndCutterEndEvent(int eventIndex, + EditShape editShape, ArrayList cutEvents, + int ipartCuttee, int ivertexCuttee, int ipartCutter, + int ivertexCutter) { + Segment segmentCuttee; + Segment segmentCutter; + Line lineCuttee = new Line(); + Line lineCutter = new Line(); + double[] scalarsCuttee = new double[2]; + double[] scalarsCutter = new double[2]; + + CutEvent cutEvent; + + segmentCuttee = editShape.getSegment(ivertexCuttee); + if (segmentCuttee == null) { + editShape.queryLineConnector(ivertexCuttee, lineCuttee); + segmentCuttee = lineCuttee; + } + + segmentCutter = editShape.getSegment(ivertexCutter); + if (segmentCutter == null) { + editShape.queryLineConnector(ivertexCutter, lineCutter); + segmentCutter = lineCutter; + } + + int count = segmentCuttee.intersect(segmentCutter, null, scalarsCuttee, + scalarsCutter, 0.0); + // _ASSERT(count > 0); + int icutEvent; + + // If count == 2 (i.e. when they overlap), this this event would have + // been discovered by _CutteeStartCutterStartEvent at the previous index + if (count < 2) { + cutEvent = new CutEvent(ivertexCuttee, ipartCuttee, + scalarsCuttee[0], NumberUtils.NaN(), count, ivertexCutter, + ipartCutter, scalarsCutter[0], NumberUtils.NaN()); + cutEvents.add(cutEvent); + icutEvent = editShape.getUserIndex(ivertexCuttee, eventIndex); + + if (icutEvent < 0) + editShape.setUserIndex(ivertexCuttee, eventIndex, + cutEvents.size() - 1); + } + + return true; + } + + private static boolean _cutteeEndCutterStartEvent(int eventIndex, + EditShape editShape, ArrayList cutEvents, + int ipartCuttee, int ivertexCuttee, int ipartCutter, + int ivertexCutter) { + Segment segmentCuttee; + Segment segmentCutter; + Line lineCuttee = new Line(); + Line lineCutter = new Line(); + double[] scalarsCuttee = new double[2]; + double[] scalarsCutter = new double[2]; + + CutEvent cutEvent; + + segmentCuttee = editShape.getSegment(ivertexCuttee); + if (segmentCuttee == null) { + editShape.queryLineConnector(ivertexCuttee, lineCuttee); + segmentCuttee = lineCuttee; + } + + segmentCutter = editShape.getSegment(ivertexCutter); + if (segmentCutter == null) { + editShape.queryLineConnector(ivertexCutter, lineCutter); + segmentCutter = lineCutter; + } + + int count = segmentCuttee.intersect(segmentCutter, null, scalarsCuttee, + scalarsCutter, 0.0); + // _ASSERT(count > 0); + int icutEvent; + + // If count == 2 (i.e. when they overlap), this this event would have + // been discovered by _CutteeStartCutterEndEvent at the previous index + if (count < 2) { + cutEvent = new CutEvent(ivertexCuttee, ipartCuttee, + scalarsCuttee[0], NumberUtils.NaN(), count, ivertexCutter, + ipartCutter, scalarsCutter[0], NumberUtils.NaN()); + cutEvents.add(cutEvent); + icutEvent = editShape.getUserIndex(ivertexCuttee, eventIndex); + + if (icutEvent < 0) + editShape.setUserIndex(ivertexCuttee, eventIndex, + cutEvents.size() - 1); + + return true; + } + + return false; + } + + private static boolean _cutteeStartCutterEndEvent(int eventIndex, + EditShape editShape, ArrayList cutEvents, + int ipartCuttee, int ivertexCuttee, int ipartCutter, + int ivertexCutter, int ifirstVertexCuttee) { + Segment segmentCuttee; + Segment segmentCutter; + Line lineCuttee = new Line(); + Line lineCutter = new Line(); + double[] scalarsCuttee = new double[2]; + double[] scalarsCutter = new double[2]; + + CutEvent cutEvent; + + segmentCuttee = editShape.getSegment(ivertexCuttee); + if (segmentCuttee == null) { + editShape.queryLineConnector(ivertexCuttee, lineCuttee); + segmentCuttee = lineCuttee; + } + + segmentCutter = editShape.getSegment(ivertexCutter); + if (segmentCutter == null) { + editShape.queryLineConnector(ivertexCutter, lineCutter); + segmentCutter = lineCutter; + } + + int count = segmentCuttee.intersect(segmentCutter, null, scalarsCuttee, + scalarsCutter, 0.0); + // _ASSERT(count > 0); + int icutEvent; + + if (count == 2) { + cutEvent = new CutEvent(ivertexCuttee, ipartCuttee, + scalarsCuttee[0], scalarsCuttee[1], count, ivertexCutter, + ipartCutter, scalarsCutter[0], scalarsCutter[1]); + cutEvents.add(cutEvent); + icutEvent = editShape.getUserIndex(ivertexCuttee, eventIndex); + + if (icutEvent < 0) + editShape.setUserIndex(ivertexCuttee, eventIndex, + cutEvents.size() - 1); + + return true; + } else { + boolean bCutEvent = false; + + if (ivertexCuttee == ifirstVertexCuttee) { + cutEvent = new CutEvent(ivertexCuttee, ipartCuttee, + scalarsCuttee[0], NumberUtils.NaN(), count, + ivertexCutter, ipartCutter, scalarsCutter[0], + NumberUtils.NaN()); + cutEvents.add(cutEvent); + icutEvent = editShape.getUserIndex(ivertexCuttee, eventIndex); + + if (icutEvent < 0) + editShape.setUserIndex(ivertexCuttee, eventIndex, + cutEvents.size() - 1); + + bCutEvent = true; + } + + return bCutEvent; + } + + } + + private static boolean _cutteeStartCutterStartEvent(int eventIndex, + EditShape editShape, ArrayList cutEvents, + int ipartCuttee, int ivertexCuttee, int ipartCutter, + int ivertexCutter, int ifirstVertexCuttee) { + Segment segmentCuttee; + Segment segmentCutter; + Line lineCuttee = new Line(); + Line lineCutter = new Line(); + double[] scalarsCuttee = new double[2]; + double[] scalarsCutter = new double[2]; + + CutEvent cutEvent; + + segmentCuttee = editShape.getSegment(ivertexCuttee); + if (segmentCuttee == null) { + editShape.queryLineConnector(ivertexCuttee, lineCuttee); + segmentCuttee = lineCuttee; + } + + segmentCutter = editShape.getSegment(ivertexCutter); + if (segmentCutter == null) { + editShape.queryLineConnector(ivertexCutter, lineCutter); + segmentCutter = lineCutter; + } + + int count = segmentCuttee.intersect(segmentCutter, null, scalarsCuttee, + scalarsCutter, 0.0); + // _ASSERT(count > 0); + int icutEvent; + + if (count == 2) { + cutEvent = new CutEvent(ivertexCuttee, ipartCuttee, + scalarsCuttee[0], scalarsCuttee[1], count, ivertexCutter, + ipartCutter, scalarsCutter[0], scalarsCutter[1]); + cutEvents.add(cutEvent); + icutEvent = editShape.getUserIndex(ivertexCuttee, eventIndex); + + if (icutEvent < 0) + editShape.setUserIndex(ivertexCuttee, eventIndex, + cutEvents.size() - 1); + + return true; + } else { + boolean bCutEvent = false; + + if (ivertexCuttee == ifirstVertexCuttee) { + cutEvent = new CutEvent(ivertexCuttee, ipartCuttee, + scalarsCuttee[0], NumberUtils.NaN(), count, + ivertexCutter, ipartCutter, scalarsCutter[0], + NumberUtils.NaN()); + cutEvents.add(cutEvent); + icutEvent = editShape.getUserIndex(ivertexCuttee, eventIndex); + + if (icutEvent < 0) + editShape.setUserIndex(ivertexCuttee, eventIndex, + cutEvents.size() - 1); + + bCutEvent = true; + } + + return bCutEvent; + } + + } + + static void _Cut(boolean bConsiderTouch, boolean bLocalCutsOnly, + ArrayList cutEvents, EditShape shape, + ArrayList cutPairs, + AttributeStreamOfInt32 segmentCounts) { + OperatorCutLocal.CutPair cutPair; + + Point2D[] tangents = new Point2D[4]; + tangents[0] = new Point2D(); + tangents[1] = new Point2D(); + tangents[2] = new Point2D(); + tangents[3] = new Point2D(); + Point2D tangent0 = new Point2D(); + Point2D tangent1 = new Point2D(); + Point2D tangent2 = new Point2D(); + Point2D tangent3 = new Point2D(); + + SegmentBuffer segmentBufferCuttee = null; + if (cutPairs != null) { + segmentBufferCuttee = new SegmentBuffer(); + segmentBufferCuttee.createLine(); + } + + Segment segmentCuttee = null; + int icutEvent = 0; + MultiPath multipath = null; + + Line lineCuttee = new Line(); + Line lineCutter = new Line(); + + int polyline = shape.getFirstGeometry(); + for (int ipath = shape.getFirstPath(polyline); ipath != -1; ipath = shape + .getNextPath(ipath)) { + int cut; + int cutPrev = OperatorCutLocal.Side.Uncut; + int ipartCuttee = -1; + int ivertexCuttee = -1; + double scalarCuttee = NumberUtils.NaN(); + int ipartCutteePrev = -1; + int ivertexCutteePrev = -1; + double scalarCutteePrev = NumberUtils.NaN(); + int ipartCutter = -1; + int ivertexCutter = -1; + double scalarCutter = NumberUtils.NaN(); + int ipartCutterPrev = -1; + int ivertexCutterPrev = -1; + double scalarCutterPrev = NumberUtils.NaN(); + boolean bNoCutYet = true; // Indicates whether a cut as occured for + // the current part + boolean bCoincidentNotAdded = false; // Indicates whether the + // current coincident + // multipath has been added + // to cutPairs + boolean bCurrentMultiPathNotAdded = true; // Indicates whether there + // is a multipath not + // yet added to cutPairs + // (left, right, or + // undefined) + boolean bStartNewPath = true; + boolean bCreateNewMultiPath = true; + int segmentCount = 0; + + ipartCutteePrev = ipath; + scalarCutteePrev = 0.0; + + for (int ivertex = shape.getFirstVertex(ipath), n = shape + .getPathSize(ipath), i = 0; i < n; ivertex = shape + .getNextVertex(ivertex), i++) { + segmentCuttee = shape.getSegment(ivertex); + if (segmentCuttee == null) { + if (!shape.queryLineConnector(ivertex, lineCuttee)) + continue; + segmentCuttee = lineCuttee; + } + + if (ivertexCutteePrev == -1) + ivertexCutteePrev = ivertex; + + double lastScalarCuttee = 0.0; // last scalar along the current + // segment + + while (icutEvent < cutEvents.size() + && ivertex == cutEvents.get(icutEvent).m_ivertexCuttee) { + ipartCuttee = cutEvents.get(icutEvent).m_ipartCuttee; + ivertexCuttee = cutEvents.get(icutEvent).m_ivertexCuttee; + scalarCuttee = cutEvents.get(icutEvent).m_scalarCuttee0; + ipartCutter = cutEvents.get(icutEvent).m_ipartCutter; + ivertexCutter = cutEvents.get(icutEvent).m_ivertexCutter; + scalarCutter = cutEvents.get(icutEvent).m_scalarCutter0; + + if (cutEvents.get(icutEvent).m_count == 2) { + // We have an overlap + + if (!bCoincidentNotAdded) { + ipartCutteePrev = ipartCuttee; + ivertexCutteePrev = ivertexCuttee; + scalarCutteePrev = scalarCuttee; + ipartCutterPrev = ipartCutter; + ivertexCutterPrev = ivertexCutter; + scalarCutterPrev = scalarCutter; + cutPrev = OperatorCutLocal.Side.Coincident; + + // Create new multipath + if (cutPairs != null) + multipath = new Polyline(); + else + segmentCount = 0; + + bCreateNewMultiPath = false; + bStartNewPath = true; + } + + scalarCuttee = cutEvents.get(icutEvent).m_scalarCuttee1; + scalarCutter = cutEvents.get(icutEvent).m_scalarCutter1; + + if (cutPairs != null) { + segmentCuttee.cut(lastScalarCuttee, + cutEvents.get(icutEvent).m_scalarCuttee1, + segmentBufferCuttee); + multipath.addSegment(segmentBufferCuttee.get(), + bStartNewPath); + } else + segmentCount++; + + lastScalarCuttee = scalarCuttee; + + bCoincidentNotAdded = true; + bNoCutYet = false; + bStartNewPath = false; + + if (icutEvent + 1 == cutEvents.size() + || cutEvents.get(icutEvent + 1).m_count != 2 + || cutEvents.get(icutEvent + 1).m_ivertexCuttee == ivertexCuttee + && cutEvents.get(icutEvent + 1).m_scalarCuttee0 != lastScalarCuttee) { + if (cutPairs != null) { + cutPair = new OperatorCutLocal.CutPair( + (Geometry) multipath, + OperatorCutLocal.Side.Coincident, + ipartCuttee, ivertexCuttee, + scalarCuttee, cutPrev, ipartCutteePrev, + ivertexCutteePrev, scalarCutteePrev, + ipartCutter, ivertexCutter, + scalarCutter, ipartCutterPrev, + ivertexCutterPrev, scalarCutterPrev); + cutPairs.add(cutPair); + } else { + segmentCounts.add(segmentCount); + } + + ipartCutteePrev = ipartCuttee; + ivertexCutteePrev = ivertexCuttee; + scalarCutteePrev = scalarCuttee; + ipartCutterPrev = ipartCutter; + ivertexCutterPrev = ivertexCutter; + scalarCutterPrev = scalarCutter; + cutPrev = OperatorCutLocal.Side.Coincident; + + bNoCutYet = false; + bCoincidentNotAdded = false; + bCreateNewMultiPath = true; + bStartNewPath = true; + } + + icutEvent++; + continue; + } + + int ivertexCutteePlus = shape.getNextVertex(ivertexCuttee); + int ivertexCutterPlus = shape.getNextVertex(ivertexCutter); + int ivertexCutterMinus = shape.getPrevVertex(ivertexCutter); + + if (icutEvent < cutEvents.size() - 1 + && cutEvents.get(icutEvent + 1).m_ivertexCuttee == ivertexCutteePlus + && cutEvents.get(icutEvent + 1).m_ivertexCutter == ivertexCutter + && cutEvents.get(icutEvent + 1).m_count == 2) { + if (scalarCuttee != lastScalarCuttee) { + if (bCreateNewMultiPath) { + if (cutPairs != null) + multipath = new Polyline(); + else + segmentCount = 0; + } + + if (icutEvent > 0 + && cutEvents.get(icutEvent - 1).m_ipartCuttee == ipartCuttee) { + if (cutPrev == OperatorCutLocal.Side.Right) + cut = OperatorCutLocal.Side.Left; + else if (cutPrev == OperatorCutLocal.Side.Left) + cut = OperatorCutLocal.Side.Right; + else + cut = OperatorCutLocal.Side.Undefined; + } else + cut = OperatorCutLocal.Side.Undefined; + + if (cutPairs != null) { + segmentCuttee.cut(lastScalarCuttee, + scalarCuttee, segmentBufferCuttee); + multipath.addSegment(segmentBufferCuttee.get(), + bStartNewPath); + cutPair = new OperatorCutLocal.CutPair( + multipath, cut, ipartCuttee, + ivertexCuttee, scalarCuttee, cutPrev, + ipartCutteePrev, ivertexCutteePrev, + scalarCutteePrev, ipartCutter, + ivertexCutter, scalarCutter, + ipartCutterPrev, ivertexCutterPrev, + scalarCutterPrev); + cutPairs.add(cutPair); + } else { + segmentCount++; + segmentCounts.add(segmentCount); + } + + lastScalarCuttee = scalarCuttee; + + ipartCutteePrev = ipartCuttee; + ivertexCutteePrev = ivertexCuttee; + scalarCutteePrev = scalarCuttee; + ipartCutterPrev = ipartCutter; + ivertexCutterPrev = ivertexCutter; + scalarCutterPrev = scalarCutter; + cutPrev = cut; + + bCurrentMultiPathNotAdded = false; + bNoCutYet = false; + bCreateNewMultiPath = true; + bStartNewPath = true; + } + + icutEvent++; + continue; + } + + boolean bContinue = _cutterTangents(bConsiderTouch, shape, + cutEvents, icutEvent, tangent0, tangent1); + if (bContinue) { + icutEvent++; + continue; + } + + _cutteeTangents(shape, cutEvents, icutEvent, ipath, + ivertex, tangent2, tangent3); + + boolean bCut = false; + boolean bTouch = false; + boolean bCutRight = true; + + if (!tangent0.isEqual(tangent2) + && !tangent1.isEqual(tangent2) + && !tangent0.isEqual(tangent3) + && !tangent1.isEqual(tangent3)) { + tangents[0].setCoords(tangent0); + tangents[1].setCoords(tangent1); + tangents[2].setCoords(tangent2); + tangents[3].setCoords(tangent3); + + Arrays.sort(tangents, new Point2D.CompareVectors()); + // SORTARRAY(tangents, Point2D, + // Point2D::_CompareVectors); + + Point2D value0 = (Point2D) tangents[0]; + Point2D value1 = (Point2D) tangents[1]; + Point2D value2 = (Point2D) tangents[2]; + Point2D value3 = (Point2D) tangents[3]; + + if (value0.isEqual(tangent0)) { + if (value1.isEqual(tangent1)) { + if (!bConsiderTouch) + bCut = false; + else { + bCut = true; + bTouch = true; + bCutRight = false; + } + } else if (value3.isEqual(tangent1)) { + if (!bConsiderTouch) + bCut = false; + else { + bCut = true; + bTouch = true; + bCutRight = true; + } + } else { + bCut = true; + bCutRight = value1.isEqual(tangent2); + } + } else if (value1.isEqual(tangent0)) { + if (value2.isEqual(tangent1)) { + if (!bConsiderTouch) + bCut = false; + else { + bCut = true; + bTouch = true; + bCutRight = false; + } + } else if (value0.isEqual(tangent1)) { + if (!bConsiderTouch) + bCut = false; + else { + bCut = true; + bTouch = true; + bCutRight = true; + } + } else { + bCut = true; + bCutRight = value2.isEqual(tangent2); + } + } else if (value2.isEqual(tangent0)) { + if (value3.isEqual(tangent1)) { + if (!bConsiderTouch) + bCut = false; + else { + bCut = true; + bTouch = true; + bCutRight = false; + } + } else if (value1.isEqual(tangent1)) { + if (!bConsiderTouch) + bCut = false; + else { + bCut = true; + bTouch = true; + bCutRight = true; + } + } else { + bCut = true; + bCutRight = value3.isEqual(tangent2); + } + } else { + if (value0.isEqual(tangent1)) { + if (!bConsiderTouch) + bCut = false; + else { + bCut = true; + bTouch = true; + bCutRight = false; + } + } else if (value2.isEqual(tangent1)) { + if (!bConsiderTouch) + bCut = false; + else { + bCut = true; + bTouch = true; + bCutRight = true; + } + } else { + bCut = true; + bCutRight = value0.isEqual(tangent2); + } + } + } + + if (bCut) { + boolean bIsFirstSegmentInPath = (ivertex == ivertexCuttee); + + if (scalarCuttee != lastScalarCuttee + || bIsFirstSegmentInPath + && lastScalarCuttee == 0.0) { + if (bCreateNewMultiPath) { + if (cutPairs != null) + multipath = new Polyline(); + else + segmentCount = 0; + } + + if (cutPairs != null) { + segmentCuttee.cut(lastScalarCuttee, + scalarCuttee, segmentBufferCuttee); + multipath.addSegment(segmentBufferCuttee.get(), + bStartNewPath); + } else + segmentCount++; + } + + if (bCutRight) { + if (cutPrev != OperatorCutLocal.Side.Right + || bLocalCutsOnly) { + if (scalarCuttee != lastScalarCuttee + || bIsFirstSegmentInPath + && lastScalarCuttee == 0.0 + || bLocalCutsOnly) { + if (cutPairs != null) { + cutPair = new OperatorCutLocal.CutPair( + multipath, + OperatorCutLocal.Side.Right, + ipartCuttee, ivertexCuttee, + scalarCuttee, cutPrev, + ipartCutteePrev, + ivertexCutteePrev, + scalarCutteePrev, ipartCutter, + ivertexCutter, scalarCutter, + ipartCutterPrev, + ivertexCutterPrev, + scalarCutterPrev); + cutPairs.add(cutPair); + } else { + segmentCounts.add(segmentCount); + } + } + + if (!bTouch) + cutPrev = OperatorCutLocal.Side.Right; + else if (icutEvent == cutEvents.size() - 2 + || cutEvents.get(icutEvent + 2).m_ipartCuttee != ipartCuttee) + cutPrev = OperatorCutLocal.Side.Left; + } else { + if (scalarCuttee != lastScalarCuttee + || bIsFirstSegmentInPath + && lastScalarCuttee == 0.0 + || bLocalCutsOnly) { + if (cutPairs != null) { + cutPair = new OperatorCutLocal.CutPair( + multipath, + OperatorCutLocal.Side.Undefined, + ipartCuttee, ivertexCuttee, + scalarCuttee, cutPrev, + ipartCutteePrev, + ivertexCutteePrev, + scalarCutteePrev, ipartCutter, + ivertexCutter, scalarCutter, + ipartCutterPrev, + ivertexCutterPrev, + scalarCutterPrev); + cutPairs.add(cutPair); + } else { + segmentCounts.add(segmentCount); + } + } + + cutPrev = OperatorCutLocal.Side.Right; + } + } else { + if (cutPrev != OperatorCutLocal.Side.Left + || bLocalCutsOnly) { + if (scalarCuttee != lastScalarCuttee + || bIsFirstSegmentInPath + && lastScalarCuttee == 0.0 + || bLocalCutsOnly) { + if (cutPairs != null) { + cutPair = new OperatorCutLocal.CutPair( + multipath, + OperatorCutLocal.Side.Left, + ipartCuttee, ivertexCuttee, + scalarCuttee, cutPrev, + ipartCutteePrev, + ivertexCutteePrev, + scalarCutteePrev, ipartCutter, + ivertexCutter, scalarCutter, + ipartCutterPrev, + ivertexCutterPrev, + scalarCutterPrev); + cutPairs.add(cutPair); + } else { + segmentCounts.add(segmentCount); + } + } + + if (!bTouch) + cutPrev = OperatorCutLocal.Side.Left; + else if (icutEvent == cutEvents.size() - 2 + || cutEvents.get(icutEvent + 2).m_ipartCuttee != ipartCuttee) + cutPrev = OperatorCutLocal.Side.Right; + } else { + if (scalarCuttee != lastScalarCuttee + || bIsFirstSegmentInPath + && lastScalarCuttee == 0.0 + || bLocalCutsOnly) { + if (cutPairs != null) { + cutPair = new OperatorCutLocal.CutPair( + multipath, + OperatorCutLocal.Side.Undefined, + ipartCuttee, ivertexCuttee, + scalarCuttee, cutPrev, + ipartCutteePrev, + ivertexCutteePrev, + scalarCutteePrev, ipartCutter, + ivertexCutter, scalarCutter, + ipartCutterPrev, + ivertexCutterPrev, + scalarCutterPrev); + cutPairs.add(cutPair); + } else { + segmentCounts.add(segmentCount); + } + } + + cutPrev = OperatorCutLocal.Side.Left; + } + } + + if (scalarCuttee != lastScalarCuttee + || bIsFirstSegmentInPath + && lastScalarCuttee == 0.0 || bLocalCutsOnly) { + lastScalarCuttee = scalarCuttee; + + ipartCutteePrev = ipartCuttee; + ivertexCutteePrev = ivertexCuttee; + scalarCutteePrev = scalarCuttee; + ipartCutterPrev = ipartCutter; + ivertexCutterPrev = ivertexCutter; + scalarCutterPrev = scalarCutter; + + bCurrentMultiPathNotAdded = false; + bNoCutYet = false; + bCreateNewMultiPath = true; + bStartNewPath = true; + } + } + + icutEvent++; + } + + if (lastScalarCuttee != 1.0) { + if (bCreateNewMultiPath) { + if (cutPairs != null) + multipath = new Polyline(); + else + segmentCount = 0; + } + + if (cutPairs != null) { + segmentCuttee.cut(lastScalarCuttee, 1.0, + segmentBufferCuttee); + multipath.addSegment(segmentBufferCuttee.get(), + bStartNewPath); + } else + segmentCount++; + + bCreateNewMultiPath = false; + bStartNewPath = false; + bCurrentMultiPathNotAdded = true; + } + } + + if (bCurrentMultiPathNotAdded) { + scalarCuttee = 1.0; + ivertexCuttee = shape.getLastVertex(ipath); + ivertexCuttee = shape.getPrevVertex(ivertexCuttee); + + ipartCutter = -1; + ivertexCutter = -1; + scalarCutter = NumberUtils.NaN(); + + if (bNoCutYet) { + if (cutPairs != null) { + cutPair = new OperatorCutLocal.CutPair(multipath, + OperatorCutLocal.Side.Uncut, ipartCuttee, + ivertexCuttee, scalarCuttee, cutPrev, + ipartCutteePrev, ivertexCutteePrev, + scalarCutteePrev, ipartCutter, ivertexCutter, + scalarCutter, ipartCutterPrev, + ivertexCutterPrev, scalarCutterPrev); + cutPairs.add(cutPair); + } else { + segmentCounts.add(segmentCount); + } + } else { + if (cutPrev == OperatorCutLocal.Side.Right) + cut = OperatorCutLocal.Side.Left; + else if (cutPrev == OperatorCutLocal.Side.Left) + cut = OperatorCutLocal.Side.Right; + else + cut = OperatorCutLocal.Side.Undefined; + + if (cutPairs != null) { + cutPair = new OperatorCutLocal.CutPair(multipath, cut, + ipartCuttee, ivertexCuttee, scalarCuttee, + cutPrev, ipartCutteePrev, ivertexCutteePrev, + scalarCutteePrev, ipartCutter, ivertexCutter, + scalarCutter, ipartCutterPrev, + ivertexCutterPrev, scalarCutterPrev); + cutPairs.add(cutPair); + } else { + segmentCounts.add(segmentCount); + } + } + } + } + } + + static boolean _cutterTangents(boolean bConsiderTouch, EditShape shape, + ArrayList cutEvents, int icutEvent, Point2D tangent0, + Point2D tangent1) { + double scalarCutter = cutEvents.get(icutEvent).m_scalarCutter0; + + if (scalarCutter == 1.0) + return _cutterEndTangents(bConsiderTouch, shape, cutEvents, + icutEvent, tangent0, tangent1); + + if (scalarCutter == 0.0) + return _cutterStartTangents(bConsiderTouch, shape, cutEvents, + icutEvent, tangent0, tangent1); + + throw new GeometryException("internal error"); + } + + static boolean _cutterEndTangents(boolean bConsiderTouch, EditShape shape, + ArrayList cutEvents, int icutEvent, Point2D tangent0, + Point2D tangent1) { + Line lineCutter = new Line(); + Segment segmentCutter; + + int ivertexCuttee = cutEvents.get(icutEvent).m_ivertexCuttee; + int ipartCutter = cutEvents.get(icutEvent).m_ipartCutter; + int ivertexCutter = cutEvents.get(icutEvent).m_ivertexCutter; + + int ivertexCutteePrev = -1; + int ipartCutterPrev = -1; + int ivertexCutterPrev = -1; + int countPrev = -1; + + if (!bConsiderTouch && icutEvent > 0) { + CutEvent cutEvent = cutEvents.get(icutEvent - 1); + ivertexCutteePrev = cutEvent.m_ivertexCuttee; + ipartCutterPrev = cutEvent.m_ipartCutter; + ivertexCutterPrev = cutEvent.m_ivertexCutter; + countPrev = cutEvent.m_count; + } + + int ivertexCutteeNext = -1; + int ipartCutterNext = -1; + int ivertexCutterNext = -1; + int countNext = -1; + + if (icutEvent < cutEvents.size() - 1) { + CutEvent cutEvent = cutEvents.get(icutEvent + 1); + ivertexCutteeNext = cutEvent.m_ivertexCuttee; + ipartCutterNext = cutEvent.m_ipartCutter; + ivertexCutterNext = cutEvent.m_ivertexCutter; + countNext = cutEvent.m_count; + } + + int ivertexCutteePlus = shape.getNextVertex(ivertexCuttee); + int ivertexCutterPlus = shape.getNextVertex(ivertexCutter); + + if (!bConsiderTouch) { + if ((icutEvent > 0 && ivertexCutteePrev == ivertexCuttee + && ipartCutterPrev == ipartCutter + && ivertexCutterPrev == ivertexCutterPlus && countPrev == 2) + || (icutEvent < cutEvents.size() - 1 + && ivertexCutteeNext == ivertexCutteePlus + && ipartCutterNext == ipartCutter + && ivertexCutterNext == ivertexCutterPlus && countNext == 2)) { + segmentCutter = shape.getSegment(ivertexCutter); + if (segmentCutter == null) { + shape.queryLineConnector(ivertexCutter, lineCutter); + segmentCutter = lineCutter; + } + + tangent1.setCoords(segmentCutter._getTangent(1.0)); + tangent0.negate(tangent1); + tangent1.normalize(); + tangent0.normalize(); + + return false; + } + + if (icutEvent < cutEvents.size() - 1 + && ivertexCutteeNext == ivertexCuttee + && ipartCutterNext == ipartCutter + && ivertexCutterNext == ivertexCutterPlus) { + segmentCutter = shape.getSegment(ivertexCutter); + if (segmentCutter == null) { + shape.queryLineConnector(ivertexCutter, lineCutter); + segmentCutter = lineCutter; + } + + tangent0.setCoords(segmentCutter._getTangent(1.0)); + + segmentCutter = shape.getSegment(ivertexCutterPlus); + if (segmentCutter == null) { + shape.queryLineConnector(ivertexCutterPlus, lineCutter); + segmentCutter = lineCutter; + } + + tangent1.setCoords(segmentCutter._getTangent(0.0)); + tangent0.negate(); + tangent1.normalize(); + tangent0.normalize(); + + return false; + } + + return true; + } + + if (icutEvent == cutEvents.size() - 1 + || ivertexCutteeNext != ivertexCuttee + || ipartCutterNext != ipartCutter + || ivertexCutterNext != ivertexCutterPlus || countNext == 2) { + segmentCutter = shape.getSegment(ivertexCutter); + if (segmentCutter == null) { + shape.queryLineConnector(ivertexCutter, lineCutter); + segmentCutter = lineCutter; + } + + tangent1.setCoords(segmentCutter._getTangent(1.0)); + tangent0.negate(tangent1); + tangent1.normalize(); + tangent0.normalize(); + + return false; + } + + segmentCutter = shape.getSegment(ivertexCutter); + if (segmentCutter == null) { + shape.queryLineConnector(ivertexCutter, lineCutter); + segmentCutter = lineCutter; + } + + tangent0.setCoords(segmentCutter._getTangent(1.0)); + + segmentCutter = shape.getSegment(ivertexCutterPlus); + if (segmentCutter == null) { + shape.queryLineConnector(ivertexCutterPlus, lineCutter); + segmentCutter = lineCutter; + } + + tangent1.setCoords(segmentCutter._getTangent(0.0)); + tangent0.negate(); + tangent1.normalize(); + tangent0.normalize(); + + return false; + } + + static boolean _cutterStartTangents(boolean bConsiderTouch, + EditShape shape, ArrayList cutEvents, int icutEvent, + Point2D tangent0, Point2D tangent1) { + Line lineCutter = new Line(); + Segment segmentCutter; + + int ivertexCuttee = cutEvents.get(icutEvent).m_ivertexCuttee; + int ipartCutter = cutEvents.get(icutEvent).m_ipartCutter; + int ivertexCutter = cutEvents.get(icutEvent).m_ivertexCutter; + + int ivertexCutteeNext = -1; + int ipartCutterNext = -1; + int ivertexCutterNext = -1; + int countNext = -1; + + if (!bConsiderTouch && icutEvent < cutEvents.size() - 1) { + CutEvent cutEvent = cutEvents.get(icutEvent + 1); + ivertexCutteeNext = cutEvent.m_ivertexCuttee; + ipartCutterNext = cutEvent.m_ipartCutter; + ivertexCutterNext = cutEvent.m_ivertexCutter; + countNext = cutEvent.m_count; + } + + int ivertexCutteePrev = -1; + int ipartCutterPrev = -1; + int ivertexCutterPrev = -1; + int countPrev = -1; + + if (icutEvent > 0) { + CutEvent cutEvent = cutEvents.get(icutEvent - 1); + ivertexCutteePrev = cutEvent.m_ivertexCuttee; + ipartCutterPrev = cutEvent.m_ipartCutter; + ivertexCutterPrev = cutEvent.m_ivertexCutter; + countPrev = cutEvent.m_count; + } + + int ivertexCutteePlus = shape.getNextVertex(ivertexCuttee); + int ivertexCutterMinus = shape.getPrevVertex(ivertexCutter); + + if (!bConsiderTouch) { + if ((icutEvent > 0 && ivertexCutteePrev == ivertexCuttee + && ipartCutterPrev == ipartCutter + && ivertexCutterPrev == ivertexCutterMinus && countPrev == 2) + || (icutEvent < cutEvents.size() - 1 + && ivertexCutteeNext == ivertexCutteePlus + && ipartCutterNext == ipartCutter + && ivertexCutterNext == ivertexCutterMinus && countNext == 2)) { + segmentCutter = shape.getSegment(ivertexCutter); + if (segmentCutter == null) { + shape.queryLineConnector(ivertexCutter, lineCutter); + segmentCutter = lineCutter; + } + + tangent1.setCoords(segmentCutter._getTangent(0.0)); + tangent0.negate(tangent1); + tangent1.normalize(); + tangent0.normalize(); + + return false; + } + + return true; + } + + if (icutEvent == 0 || ivertexCutteePrev != ivertexCuttee + || ipartCutterPrev != ipartCutter + || ivertexCutterPrev != ivertexCutterMinus || countPrev == 2) { + segmentCutter = shape.getSegment(ivertexCutter); + if (segmentCutter == null) { + shape.queryLineConnector(ivertexCutter, lineCutter); + segmentCutter = lineCutter; + } + + tangent1.setCoords(segmentCutter._getTangent(0.0)); + tangent0.negate(tangent1); + tangent1.normalize(); + tangent0.normalize(); + + return false; + } + + // Already processed the event + + return true; + } + + static boolean _cutteeTangents(EditShape shape, + ArrayList cutEvents, int icutEvent, int ipath, + int ivertex, Point2D tangent2, Point2D tangent3) { + Line lineCuttee = new Line(); + Segment segmentCuttee = shape.getSegment(ivertex); + if (segmentCuttee == null) { + shape.queryLineConnector(ivertex, lineCuttee); + segmentCuttee = lineCuttee; + } + + CutEvent cutEvent = cutEvents.get(icutEvent); + int ivertexCuttee = cutEvent.m_ivertexCuttee; + double scalarCuttee = cutEvent.m_scalarCuttee0; + + int ivertexCutteePlus = shape.getNextVertex(ivertexCuttee); + + if (scalarCuttee == 1.0) { + tangent2.setCoords(segmentCuttee._getTangent(1.0)); + + if (ivertexCutteePlus != -1 + && ivertexCutteePlus != shape.getLastVertex(ipath)) { + segmentCuttee = shape.getSegment(ivertexCutteePlus); + if (segmentCuttee == null) { + shape.queryLineConnector(ivertexCutteePlus, lineCuttee); + segmentCuttee = lineCuttee; + } + + tangent3.setCoords(segmentCuttee._getTangent(0.0)); + + segmentCuttee = shape.getSegment(ivertexCuttee); + if (segmentCuttee == null) { + shape.queryLineConnector(ivertexCuttee, lineCuttee); + segmentCuttee = lineCuttee; + } + } else + tangent3.setCoords(tangent2); + + tangent2.negate(); + + tangent3.normalize(); + tangent2.normalize(); + + return false; + } + + if (scalarCuttee == 0.0) { + tangent3.setCoords(segmentCuttee._getTangent(scalarCuttee)); + tangent2.negate(tangent3); + tangent3.normalize(); + tangent2.normalize(); + + return false; + } + + throw new GeometryException("internal error"); + } +} diff --git a/src/com/esri/core/geometry/DirtyFlags.java b/src/com/esri/core/geometry/DirtyFlags.java new file mode 100644 index 00000000..030539fe --- /dev/null +++ b/src/com/esri/core/geometry/DirtyFlags.java @@ -0,0 +1,64 @@ +/* + Copyright 1995-2013 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ +package com.esri.core.geometry; + +interface DirtyFlags { + public static final int dirtyIsKnownSimple = 1; // !<0 when is_weak_simple + // or is_strong_simple flag + // is valid + public static final int isWeakSimple = 2; // ! 0.01 * fabsdivis) {// more accurate error calculation + // for very inaccurate divisor + double rr = divis.m_eps / fabsdivis; + e *= (1. + (1. + rr) * rr); + } + m_value = r; + m_eps = e + epsCoordinate() * Math.abs(r); + } + + void div(double v) { + double fabsdivis = Math.abs(v); + m_value /= v; + m_eps = m_eps / fabsdivis + epsCoordinate() * Math.abs(m_value); + } + + void div(ECoordinate v_1, ECoordinate v_2) { + set(v_1); + div(v_2); + } + + void div(double v_1, double v_2) { + m_value = v_1 / v_2; + m_eps = epsCoordinate() * Math.abs(m_value); + } + + void div(ECoordinate v_1, double v_2) { + set(v_1); + div(v_2); + } + + void div(double v_1, ECoordinate v_2) { + set(v_1); + div(v_2); + } + + void sqrt() { + double r, dr; + + if (m_value >= 0) { // assume non-negative input + r = Math.sqrt(m_value); + if (m_value > 10. * m_eps) { + dr = 0.5 * m_eps / r; + } else { + dr = (m_value > m_eps) ? r - Math.sqrt(m_value - m_eps) : Math + .max(r, Math.sqrt(m_value + m_eps) - r); + } + + dr += epsCoordinate() * Math.abs(r); + } else { + if (m_value < -m_eps) { // Assume negative input. Return value + // undefined + r = NumberUtils.TheNaN; + dr = NumberUtils.TheNaN; + } else { // assume zero input + r = 0.0; + dr = Math.sqrt(m_eps); + } + } + + m_value = r; + m_eps = dr; + } + + void sqr() { + double r = m_value * m_value; + m_eps = 2 * m_eps * m_value + m_eps * m_eps + epsCoordinate() * r; + m_value = r; + } + + // Assigns sin(angle) to this coordinate. + void sin(ECoordinate angle) { + double sinv = Math.sin(angle.m_value); + double cosv = Math.cos(angle.m_value); + m_value = sinv; + double absv = Math.abs(sinv); + m_eps = (Math.abs(cosv) + absv * 0.5 * angle.m_eps) * angle.m_eps + + epsCoordinate() * absv; + } + + // Assigns cos(angle) to this coordinate. + void cos(ECoordinate angle) { + double sinv = Math.sin(angle.m_value); + double cosv = Math.cos(angle.m_value); + m_value = cosv; + double absv = Math.abs(cosv); + m_eps = (Math.abs(sinv) + absv * 0.5 * angle.m_eps) * angle.m_eps + + epsCoordinate() * absv; + } + + // Calculates natural log of v and assigns to this coordinate + void log(ECoordinate v) { + double d = v.m_eps / v.m_value; + m_value = Math.log(v.m_value); + m_eps = d * (1.0 + 0.5 * d) + epsCoordinate() * Math.abs(m_value); + } + + // void SinAndCos(ECoordinate& _sin, ECoordinate& _cos); + // ECoordinate abs(); + // ECoordinate exp(); + // ECoordinate acos(); + // ECoordinate asin(); + // ECoordinate atan(); + + boolean eq(ECoordinate v) // == + { + return Math.abs(m_value - v.m_value) <= m_eps + v.m_eps; + } + + boolean ne(ECoordinate v) // != + { + return !eq(v); + } + + boolean GT(ECoordinate v) // > + { + return m_value - v.m_value > m_eps + v.m_eps; + } + + boolean lt(ECoordinate v) // < + { + return v.m_value - m_value > m_eps + v.m_eps; + } + + boolean ge(ECoordinate v) // >= + { + return !lt(v); + } + + boolean le(ECoordinate v) // <= + { + return !GT(v); + } + + // The following methods take into account the rounding erros as well as + // user defined tolerance. + boolean tolEq(ECoordinate v, double tolerance) // ! == with tolerance + { + return Math.abs(m_value - v.m_value) <= tolerance || eq(v); + } + + boolean tol_ne(ECoordinate v, double tolerance) // ! != + { + return !tolEq(v, tolerance); + } + + boolean tolGT(ECoordinate v, double tolerance) // ! > + { + return (m_value - v.m_value > tolerance) && GT(v); + } + + boolean tollt(ECoordinate v, double tolerance) // ! < + { + return (v.m_value - m_value > tolerance) && lt(v); + } + + boolean tolge(ECoordinate v, double tolerance) // ! >= + { + return !tollt(v, tolerance); + } + + boolean tolle(ECoordinate v, double tolerance) // ! <= + { + return !tolGT(v, tolerance); + } + + boolean isZero() { + return Math.abs(m_value) <= m_eps; + } + + boolean isFuzzyZero() { + return isZero() && m_eps != 0.0; + } + + boolean tolIsZero(double tolerance) { + return Math.abs(m_value) <= Math.max(m_eps, tolerance); + } + + void setPi() { + set(Math.PI, epsCoordinate()); + } + + void setE() { + set(2.71828182845904523536, epsCoordinate()); + } +} diff --git a/src/com/esri/core/geometry/EditShape.java b/src/com/esri/core/geometry/EditShape.java new file mode 100644 index 00000000..565dd98f --- /dev/null +++ b/src/com/esri/core/geometry/EditShape.java @@ -0,0 +1,2234 @@ +/* + Copyright 1995-2013 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ +package com.esri.core.geometry; + +import java.util.ArrayList; + +/** + * A helper geometry structure that can store MultiPoint, Polyline, Polygon + * geometries in linked lists. It allows constant time manipulation of geometry + * vertices. + */ +class EditShape { + interface PathFlags_ { + static final int closedPath = 1; + static final int exteriorPath = 2; + static final int ringAreaValid = 4; + } + + private int m_geometryCount; + private int m_path_count; + private int m_point_count; + private int m_first_geometry; + private int m_last_geometry; + + private StridedIndexTypeCollection m_vertex_index_list; + + // ****************Vertex Data****************** + private MultiPoint m_vertices_mp; // vertex coordinates are stored here + // Attribute_stream_of_index_type::SPtr m_indexRemap; + private MultiPointImpl m_vertices; // Internals of m_vertices_mp + AttributeStreamOfDbl m_xy_stream; // The xy stream of the m_vertices. + VertexDescription m_vertex_description;// a shortcut to the vertex + // description. + boolean m_b_has_attributes; // a short cut to know if we have something in + // addition to x and y. + + ArrayList m_segments;// may be NULL if all segments a Lines, + // otherwise contains NULLs for Line + // segments. Curves are not NULL. + AttributeStreamOfDbl m_weights;// may be NULL if no weights are provided. + // NULL weights assumes weight value of 1. + ArrayList m_indices;// user indices are here + // ****************End Vertex Data************** + StridedIndexTypeCollection m_path_index_list; // doubly connected list. Path + // index into the Path Data + // arrays, Prev path, next + // path. + // ******************Path Data****************** + AttributeStreamOfDbl m_path_areas; + AttributeStreamOfDbl m_path_lengths; + // Block_array::SPtr m_path_envelopes; + ArrayList m_pathindices;// path user indices are + // here + // *****************End Path Data*************** + StridedIndexTypeCollection m_geometry_index_list; + ArrayList m_geometry_indices;// geometry user + // indices are here + + // *********** Helpers for Bucket sort************** + static class EditShapeBucketSortHelper extends ClassicSort { + EditShape m_shape; + + EditShapeBucketSortHelper(EditShape shape) { + m_shape = shape; + } + + @Override + public void userSort(int begin, int end, AttributeStreamOfInt32 indices) { + m_shape.sortVerticesSimpleByYHelper_(indices, begin, end); + } + + @Override + public double getValue(int index) { + return m_shape.getY(index); + } + }; + + BucketSort m_bucket_sort; + + // Envelope::SPtr m_envelope; //the BBOX for all attributes + Point m_helper_point; // a helper point for intermediate operations + + Segment getSegmentFromIndex_(int vindex) { + return m_segments != null ? m_segments.get(vindex) : null; + } + + void setSegmentToIndex_(int vindex, Segment seg) { + if (m_segments == null) { + if (seg == null) + return; + m_segments = new ArrayList(); + for (int i = 0, n = m_vertices.getPointCount(); i < n; i++) + m_segments.add(null); + } + m_segments.set(vindex, seg); + } + + void setPrevPath_(int path, int prev) { + m_path_index_list.setField(path, 1, prev); + } + + void setNextPath_(int path, int next) { + m_path_index_list.setField(path, 2, next); + } + + void setPathFlags_(int path, int flags) { + m_path_index_list.setField(path, 6, flags); + } + + int getPathFlags_(int path) { + return m_path_index_list.getField(path, 6); + } + + void setPathGeometry_(int path, int geom) { + m_path_index_list.setField(path, 7, geom); + } + + int getPathIndex_(int path) { + return m_path_index_list.getField(path, 0); + } + + void setNextGeometry_(int geom, int next) { + m_geometry_index_list.setField(geom, 1, next); + } + + void setPrevGeometry_(int geom, int prev) { + m_geometry_index_list.setField(geom, 0, prev); + } + + int getGeometryIndex_(int geom) { + return m_geometry_index_list.getField(geom, 7); + } + + int getFirstPath_(int geom) { + return m_geometry_index_list.getField(geom, 3); + } + + void setFirstPath_(int geom, int firstPath) { + m_geometry_index_list.setField(geom, 3, firstPath); + } + + void setLastPath_(int geom, int path) { + m_geometry_index_list.setField(geom, 4, path); + } + + int newGeometry_(int gt) { + // Index_type index = m_first_free_geometry; + if (m_geometry_index_list == null) + m_geometry_index_list = new StridedIndexTypeCollection(8); + + int index = m_geometry_index_list.newElement(); + // m_geometry_index_list.set(index + 0, -1);//prev + // m_geometry_index_list.set(index + 1, -1);//next + m_geometry_index_list.setField(index, 2, gt);// Geometry_type + // m_geometry_index_list.set(index + 3, -1);//first path + // m_geometry_index_list.set(index + 4, -1);//last path + m_geometry_index_list.setField(index, 5, 0);// point count + m_geometry_index_list.setField(index, 6, 0);// path count + m_geometry_index_list.setField(index, 7, + m_geometry_index_list.elementToIndex(index));// geometry index + + return index; + } + + void freeGeometry_(int geom) { + m_geometry_index_list.deleteElement(geom); + } + + int newPath_(int geom) { + if (m_path_index_list == null) { + m_path_index_list = new StridedIndexTypeCollection(8); + m_vertex_index_list = new StridedIndexTypeCollection(5); + m_path_areas = new AttributeStreamOfDbl(0); + m_path_lengths = new AttributeStreamOfDbl(0); + } + + int index = m_path_index_list.newElement(); + int pindex = m_path_index_list.elementToIndex(index); + m_path_index_list.setField(index, 0, pindex);// size + // m_path_index_list.set(index + 1, -1);//prev + // m_path_index_list.set(index + 2, -1);//next + m_path_index_list.setField(index, 3, 0);// size + // m_path_index_list.set(index + 4, -1);//first vertex handle + // m_path_index_list.set(index + 5, -1);//last vertex handle + m_path_index_list.setField(index, 6, 0);// path flags + m_path_index_list.setField(index, 7, geom);// geometry handle + if (pindex >= m_path_areas.size()) { + int sz = pindex < 16 ? 16 : (pindex * 3) / 2; + m_path_areas.resize(sz); + m_path_lengths.resize(sz); + // if (m_path_envelopes) + // m_path_envelopes.resize(sz); + } + m_path_areas.set(pindex, 0); + m_path_lengths.set(pindex, 0); + // if (m_path_envelopes) + // m_path_envelopes.set(pindex, nullptr); + + m_path_count++; + return index; + } + + void freePath_(int path) { + m_path_index_list.deleteElement(path); + m_path_count--; + } + + void freeVertex_(int vertex) { + m_vertex_index_list.deleteElement(vertex); + m_point_count--; + } + + int newVertex_(int vindex) { + assert (vindex >= 0 || vindex == -1);// vindex is not a handle + + if (m_path_index_list == null) { + m_path_index_list = new StridedIndexTypeCollection(8); + m_vertex_index_list = new StridedIndexTypeCollection(5); + m_path_areas = new AttributeStreamOfDbl(0); + m_path_lengths = new AttributeStreamOfDbl(0); + } + + int index = m_vertex_index_list.newElement(); + int vi = vindex >= 0 ? vindex : m_vertex_index_list + .elementToIndex(index); + m_vertex_index_list.setField(index, 0, vi); + if (vindex < 0) { + if (vi >= m_vertices.getPointCount()) { + int sz = vi < 16 ? 16 : (vi * 3) / 2; + // m_vertices.reserveRounded(sz); + m_vertices.resize(sz); + if (m_segments != null) { + for (int i = 0; i < sz; i++) + m_segments.add(null); + } + + if (m_weights != null) + m_weights.resize(sz); + + m_xy_stream = (AttributeStreamOfDbl) m_vertices + .getAttributeStreamRef(VertexDescription.Semantics.POSITION); + } + + m_vertices.setXY(vi, -1e38, -1e38); + + if (m_segments != null) + m_segments.set(vi, null); + + if (m_weights != null) + m_weights.write(vi, 1.0); + } else { + // We do not set vertices or segments here, because we assume those + // are set correctly already. + // We only here to create linked list of indices on existing vertex + // value. + // m_segments->set(m_point_count, nullptr); + } + + m_vertex_index_list.setField(index, 4, vi * 2); + m_point_count++; + return index; + } + + void free_vertex_(int vertex) { + m_vertex_index_list.deleteElement(vertex); + m_point_count--; + } + + int insertVertex_(int path, int before, Point point) { + int prev = before != -1 ? getPrevVertex(before) : getLastVertex(path); + int next = prev != -1 ? getNextVertex(prev) : -1; + + int vertex = newVertex_(point == null ? m_point_count : -1); + int vindex = getVertexIndex(vertex); + if (point != null) + m_vertices.setPointByVal(vindex, point); + + setPathToVertex_(vertex, path); + setNextVertex_(vertex, next); + setPrevVertex_(vertex, prev); + + if (next != -1) + setPrevVertex_(next, vertex); + + if (prev != -1) + setNextVertex_(prev, vertex); + + boolean b_closed = isClosedPath(path); + int first = getFirstVertex(path); + if (before == -1) + setLastVertex_(path, vertex); + + if (before == first) + setFirstVertex_(path, vertex); + + if (b_closed && next == -1) { + setNextVertex_(vertex, vertex); + setPrevVertex_(vertex, vertex); + } + + setPathSize_(path, getPathSize(path) + 1); + int geometry = getGeometryFromPath(path); + setGeometryVertexCount_(geometry, getPointCount(geometry) + 1); + + return vertex; + } + + Point getHelperPoint_() { + if (m_helper_point == null) + m_helper_point = new Point(m_vertices.getDescription()); + return m_helper_point; + } + + int addMultiPath_(MultiPath multi_path) { + int newgeom = createGeometry(multi_path.getType(), + multi_path.getDescription()); + appendMultiPath_(newgeom, multi_path); + return newgeom; + } + + int addMultiPoint_(MultiPoint multi_point) { + int newgeometry = createGeometry(multi_point.getType(), + multi_point.getDescription()); + appendMultiPoint_(newgeometry, multi_point); + return newgeometry; + } + + void appendMultiPath_(int dstGeom, MultiPath multi_path) { + MultiPathImpl mp_impl = (MultiPathImpl) multi_path._getImpl(); + // m_vertices->reserve_rounded(m_vertices->get_point_count() + + // mp_impl->get_point_count());//ensure reallocation happens by blocks + // so that already allocated vertices do not get reallocated. + m_vertices_mp.add(multi_path, 0, mp_impl.getPointCount()); + m_xy_stream = (AttributeStreamOfDbl) m_vertices + .getAttributeStreamRef(VertexDescription.Semantics.POSITION); + boolean b_some_segments = m_segments != null + && mp_impl.getSegmentFlagsStreamRef() != null; + + for (int ipath = 0, npath = mp_impl.getPathCount(); ipath < npath; ipath++) { + if (mp_impl.getPathSize(ipath) < 2) // CR249862 - Clipping geometry + // which has empty part produces + // a crash + continue; + + int path = insertPath(dstGeom, -1); + setClosedPath(path, mp_impl.isClosedPath(ipath)); + for (int ivertex = mp_impl.getPathStart(ipath), iend = mp_impl + .getPathEnd(ipath); ivertex < iend; ivertex++) { + int vertex = insertVertex_(path, -1, null); + if (b_some_segments) { + int vindex = getVertexIndex(vertex); + if ((mp_impl.getSegmentFlags(ivertex) & (byte) SegmentFlags.enumLineSeg) != 0) { + setSegmentToIndex_(vindex, null); + } else { + SegmentBuffer seg_buffer = new SegmentBuffer(); + mp_impl.getSegment(ivertex, seg_buffer, true); + setSegmentToIndex_(vindex, seg_buffer.get()); + } + } + } + } + + // {//debug + // #ifdef DEBUG + // for (Index_type geometry = get_first_geometry(); geometry != -1; + // geometry = get_next_geometry(geometry)) + // { + // for (Index_type path = get_first_path(geometry); path != -1; path = + // get_next_path(path)) + // { + // Index_type first = get_first_vertex(path); + // Index_type v = first; + // for (get_next_vertex(v); v != first; v = get_next_vertex(v)) + // { + // assert(get_next_vertex(get_prev_vertex(v)) == v); + // } + // } + // } + // #endif + // } + } + + void appendMultiPoint_(int dstGeom, MultiPoint multi_point) { + // m_vertices->reserve_rounded(m_vertices->get_point_count() + + // multi_point.get_point_count());//ensure reallocation happens by + // blocks so that already allocated vertices do not get reallocated. + m_vertices_mp.add(multi_point, 0, multi_point.getPointCount()); + m_xy_stream = (AttributeStreamOfDbl) m_vertices + .getAttributeStreamRef(VertexDescription.Semantics.POSITION); + + int path = insertPath(dstGeom, -1); + for (int ivertex = 0, iend = multi_point.getPointCount(); ivertex < iend; ivertex++) { + insertVertex_(path, -1, null); + } + } + + void splitSegmentForward_(int origin_vertex, + SegmentIntersector intersector, int intersector_index) { + int last_vertex = getNextVertex(origin_vertex); + if (last_vertex == -1) + throw new IllegalArgumentException("internal error"); + Point point = getHelperPoint_(); + int path = getPathFromVertex(origin_vertex); + int vertex = origin_vertex; + for (int i = 0, n = intersector + .getResultSegmentCount(intersector_index); i < n; i++) { + int vindex = getVertexIndex(vertex); + int next_vertex = getNextVertex(vertex); + Segment seg = intersector.getResultSegment(intersector_index, i); + + if (i == 0) { + seg.queryStart(point); + // #ifdef DEBUG + // Point2D pt = new Point2D(); + // getXY(vertex, pt); + // assert(Point2D.distance(point.getXY(), pt) <= + // intersector.get_tolerance_()); + // #endif + setPoint(vertex, point); + } + + if (seg.getType().value() == Geometry.GeometryType.Line) + setSegmentToIndex_(vindex, null); + else + setSegmentToIndex_(vindex, (Segment) Geometry._clone(seg)); + + seg.queryEnd(point); + if (i < n - 1) { + int inserted_vertex = insertVertex_(path, next_vertex, point); + vertex = inserted_vertex; + } else { + // #ifdef DEBUG + // Point_2D pt; + // get_xy(last_vertex, pt); + // assert(Point_2D::distance(point->get_xy(), pt) <= + // intersector.getTolerance_()); + // #endif + setPoint(last_vertex, point); + assert (last_vertex == next_vertex); + } + } + } + + void splitSegmentBackward_(int origin_vertex, + SegmentIntersector intersector, int intersector_index) { + int last_vertex = getNextVertex(origin_vertex); + if (last_vertex == -1) + throw new IllegalArgumentException("internal error"); + Point point = getHelperPoint_(); + int path = getPathFromVertex(origin_vertex); + int vertex = origin_vertex; + for (int i = 0, n = intersector + .getResultSegmentCount(intersector_index); i < n; i++) { + int vindex = getVertexIndex(vertex); + int next_vertex = getNextVertex(vertex); + Segment seg = intersector.getResultSegment(intersector_index, n - i + - 1); + + if (i == 0) { + seg.queryEnd(point); + // #ifdef DEBUG + // Point2D pt = new Point2D(); + // getXY(vertex, pt); + // assert(Point2D.distance(point.getXY(), pt) <= + // intersector.getTolerance_()); + // #endif + setPoint(vertex, point); + } + + if (seg.getType().value() == Geometry.GeometryType.Line) + setSegmentToIndex_(vindex, null); + else + setSegmentToIndex_(vindex, (Segment) Geometry._clone(seg)); + + seg.queryStart(point); + if (i < n - 1) { + int inserted_vertex = insertVertex_(path, next_vertex, point); + vertex = inserted_vertex; + } else { + // #ifdef DEBUG + // Point2D pt = new Point2D(); + // getXY(last_vertex, pt); + // assert(Point2D.distance(point.getXY(), pt) <= + // intersector.getTolerance_()); + // #endif + setPoint(last_vertex, point); + assert (last_vertex == next_vertex); + } + } + } + + EditShape() { + m_path_count = 0; + m_first_geometry = -1; + m_last_geometry = -1; + m_point_count = 0; + m_geometryCount = 0; + m_b_has_attributes = false; + m_vertices = null; + m_xy_stream = null; + m_vertex_description = null; + } + + // Total point count in all geometries + int getTotalPointCount() { + return m_point_count; + } + + // Returns envelope of all coordinates. + Envelope2D getEnvelope2D() { + Envelope2D env = new Envelope2D(); + env.setEmpty(); + VertexIterator vert_iter = queryVertexIterator(); + Point2D pt = new Point2D(); + boolean b_first = true; + for (int ivertex = vert_iter.next(); ivertex != -1; ivertex = vert_iter + .next()) { + getXY(ivertex, pt); + if (b_first) + env.merge(pt.x, pt.y); + else + env.mergeNE(pt.x, pt.y); + + b_first = false; + } + + return env; + } + + // Returns geometry count in the edit shape + int getGeometryCount() { + return m_geometryCount; + } + + // Adds a Geometry to the Edit_shape + int addGeometry(Geometry geometry) { + Geometry.Type gt = geometry.getType(); + if (Geometry.isMultiPath(gt.value())) + return addMultiPath_((MultiPath) geometry); + if (gt == Geometry.Type.MultiPoint) + return addMultiPoint_((MultiPoint) geometry); + + throw new IllegalArgumentException("internal error"); + } + + // Append a Geometry to the given geometry of the Edit_shape + void appendGeometry(int dstGeometry, Geometry srcGeometry) { + Geometry.Type gt = srcGeometry.getType(); + if (Geometry.isMultiPath(gt.value())) { + appendMultiPath_(dstGeometry, (MultiPath) srcGeometry); + return; + } else if (gt.value() == Geometry.GeometryType.MultiPoint) { + appendMultiPoint_(dstGeometry, (MultiPoint) srcGeometry); + return; + } + + throw new IllegalArgumentException("internal error"); + } + + // Adds a path + int addPathFromMultiPath(MultiPath multi_path, int ipath, boolean as_polygon) { + int newgeom = createGeometry(as_polygon ? Geometry.Type.Polygon + : Geometry.Type.Polyline, multi_path.getDescription()); + + MultiPathImpl mp_impl = (MultiPathImpl) multi_path._getImpl(); + + // m_vertices->reserve_rounded(m_vertices->get_point_count() + + // multi_path.get_path_size(ipath));//ensure reallocation happens by + // blocks so that already allocated vertices do not get reallocated. + m_vertices_mp.add(multi_path, multi_path.getPathStart(ipath), + mp_impl.getPathEnd(ipath)); + m_xy_stream = (AttributeStreamOfDbl) m_vertices + .getAttributeStreamRef(VertexDescription.Semantics.POSITION); + + int path = insertPath(newgeom, -1); + setClosedPath(path, mp_impl.isClosedPath(ipath) || as_polygon); + + boolean b_some_segments = m_segments != null + && mp_impl.getSegmentFlagsStreamRef() != null; + + for (int ivertex = mp_impl.getPathStart(ipath), iend = mp_impl + .getPathEnd(ipath); ivertex < iend; ivertex++) { + int vertex = insertVertex_(path, -1, null); + if (b_some_segments) { + int vindex = getVertexIndex(vertex); + if ((mp_impl.getSegmentFlags(ivertex) & SegmentFlags.enumLineSeg) != 0) { + setSegmentToIndex_(vindex, null); + } else { + SegmentBuffer seg_buffer = new SegmentBuffer(); + mp_impl.getSegment(ivertex, seg_buffer, true); + setSegmentToIndex_(vindex, seg_buffer.get()); + } + } + } + + return newgeom; + } + + // Extracts a geometry from the Edit_shape. The method creates a new + // Geometry instance and initializes it with the Edit_shape data for the + // given geometry. + Geometry getGeometry(int geometry) { + int gt = getGeometryType(geometry); + Geometry geom = InternalUtils.createGeometry(gt, + m_vertices_mp.getDescription()); + int point_count = getPointCount(geometry); + + if (point_count == 0) + return geom; + + if (Geometry.isMultiPath(gt)) { + MultiPathImpl mp_impl = (MultiPathImpl) geom._getImpl(); + int path_count = getPathCount(geometry); + AttributeStreamOfInt32 parts = (AttributeStreamOfInt32) (AttributeStreamBase + .createIndexStream(path_count + 1)); + AttributeStreamOfInt8 pathFlags = (AttributeStreamOfInt8) (AttributeStreamBase + .createByteStream(path_count + 1, (byte) 0)); + VertexDescription description = geom.getDescription(); + + for (int iattrib = 0, nattrib = description.getAttributeCount(); iattrib < nattrib; iattrib++) { + int semantics = description.getSemantics(iattrib); + int ncomps = VertexDescription.getComponentCount(semantics); + AttributeStreamBase dst_stream = AttributeStreamBase + .createAttributeStreamWithSemantics(semantics, + point_count); + AttributeStreamBase src_stream = m_vertices + .getAttributeStreamRef(semantics); + int dst_index = 0; + int ipath = 0; + int nvert = 0; + for (int path = getFirstPath(geometry); path != -1; path = getNextPath(path)) { + byte flag_mask = 0; + if (isClosedPath(path)) { + flag_mask |= (byte) PathFlags.enumClosed; + } else { + assert (gt != Geometry.GeometryType.Polygon); + } + + if (isExterior(path)) { + flag_mask |= (byte) PathFlags.enumOGCStartPolygon; + } + + if (flag_mask != 0) + pathFlags.setBits(ipath, flag_mask); + + int path_size = getPathSize(path); + parts.write(ipath++, nvert); + nvert += path_size; + if (semantics == VertexDescription.Semantics.POSITION) { + AttributeStreamOfDbl src_stream_dbl = (AttributeStreamOfDbl) (src_stream); + AttributeStreamOfDbl dst_stream_dbl = (AttributeStreamOfDbl) (dst_stream); + Point2D pt = new Point2D(); + for (int vertex = getFirstVertex(path); dst_index < nvert; vertex = getNextVertex(vertex), dst_index++) { + int src_index = getVertexIndex(vertex); + src_stream_dbl.read(src_index * 2, pt); + dst_stream_dbl.write(dst_index * 2, pt); + } + } else { + for (int vertex = getFirstVertex(path); dst_index < nvert; vertex = getNextVertex(vertex), dst_index++) { + int src_index = getVertexIndex(vertex); + for (int icomp = 0; icomp < ncomps; icomp++) { + double d = src_stream.readAsDbl(src_index + * ncomps + icomp); + dst_stream.writeAsDbl(dst_index * ncomps + + icomp, d); + } + } + } + } + + assert (nvert == point_count);// Inconsistent content in the + // Edit_shape. Please, fix. + assert (ipath == path_count); + mp_impl.setAttributeStreamRef(semantics, dst_stream); + parts.write(path_count, point_count); + } + + mp_impl.setPathFlagsStreamRef(pathFlags); + mp_impl.setPathStreamRef(parts); + mp_impl.notifyModified(DirtyFlags.dirtyAll); + } else if (gt == Geometry.GeometryType.MultiPoint) { + MultiPointImpl mp_impl = (MultiPointImpl) geom._getImpl(); + VertexDescription description = geom.getDescription(); + // mp_impl.reserve(point_count); + mp_impl.resize(point_count); + + for (int iattrib = 0, nattrib = description.getAttributeCount(); iattrib < nattrib; iattrib++) { + int semantics = description.getSemantics(iattrib); + int ncomps = VertexDescription.getComponentCount(semantics); + AttributeStreamBase dst_stream = mp_impl + .getAttributeStreamRef(semantics); + // std::shared_ptr dst_stream = + // Attribute_stream_base::create_attribute_stream(semantics, + // point_count); + AttributeStreamBase src_stream = m_vertices + .getAttributeStreamRef(semantics); + int dst_index = 0; + assert (getPathCount(geometry) == 1); + int path = getFirstPath(geometry); + int path_size = getPathSize(path); + for (int vertex = getFirstVertex(path); dst_index < path_size; vertex = getNextVertex(vertex), dst_index++) { + int src_index = getVertexIndex(vertex); + for (int icomp = 0; icomp < ncomps; icomp++) { + double d = src_stream.readAsDbl(src_index * ncomps + + icomp); + dst_stream.writeAsDbl(dst_index * ncomps + icomp, d); + } + } + + mp_impl.setAttributeStreamRef(semantics, dst_stream); + } + + mp_impl.notifyModified(DirtyFlags.dirtyAll); + } else { + assert (false); + } + + return geom; + } + + // create a new empty geometry of the given type + int createGeometry(Geometry.Type geometry_type) { + return createGeometry(geometry_type, + VertexDescriptionDesignerImpl.getDefaultDescriptor2D()); + } + + // Deletes existing geometry from the edit shape and returns the next one. + int removeGeometry(int geometry) { + for (int path = getFirstPath(geometry); path != -1; path = removePath(path)) { + // removing paths in a loop + } + + int prev = getPrevGeometry(geometry); + int next = getNextGeometry(geometry); + if (prev != -1) + setNextGeometry_(prev, next); + else { + m_first_geometry = next; + } + if (next != -1) + setPrevGeometry_(next, prev); + else { + m_last_geometry = prev; + } + + freeGeometry_(geometry); + return next; + } + + // create a new empty geometry of the given type and attribute set. + int createGeometry(Geometry.Type geometry_type, + VertexDescription description) { + int newgeom = newGeometry_(geometry_type.value()); + if (m_vertices == null) { + m_vertices_mp = new MultiPoint(description); + m_vertices = (MultiPointImpl) m_vertices_mp._getImpl(); + } else + m_vertices_mp.mergeVertexDescription(description); + + m_vertex_description = m_vertices_mp.getDescription();// this + // description + // will be a + // merge of + // existing + // description + // and the + // description + // of the + // multi_path + m_b_has_attributes = m_vertex_description.getAttributeCount() > 1; + + if (m_first_geometry == -1) { + m_first_geometry = newgeom; + m_last_geometry = newgeom; + } else { + setPrevGeometry_(newgeom, m_last_geometry); + setNextGeometry_(m_last_geometry, newgeom); + m_last_geometry = newgeom; + } + return newgeom; + } + + // Returns the first geometry in the Edit_shape. + int getFirstGeometry() { + return m_first_geometry; + } + + // Returns the next geometry in the Edit_shape. Returns -1 when there are no + // more geometries. + int getNextGeometry(int geom) { + return m_geometry_index_list.getField(geom, 1); + } + + // Returns the previous geometry in the Edit_shape. Returns -1 when there + // are no more geometries. + int getPrevGeometry(int geom) { + return m_geometry_index_list.getField(geom, 0); + } + + // Returns the type of the Geometry. + int getGeometryType(int geom) { + return m_geometry_index_list.getField(geom, 2); + } + + // Sets value to the given user index on a geometry. + void setGeometryUserIndex(int geom, int index, int value) { + AttributeStreamOfInt32 stream = m_geometry_indices.get(index); + int pindex = getGeometryIndex_(geom); + if (pindex >= stream.size()) + stream.resize(Math.max((int) (pindex * 1.25), (int) 16), -1); + stream.write(pindex, value); + } + + // Returns the value of the given user index of a geometry + int getGeometryUserIndex(int geom, int index) { + int pindex = getGeometryIndex_(geom); + AttributeStreamOfInt32 stream = m_geometry_indices.get(index); + if (pindex < stream.size()) + return stream.read(pindex); + else + return -1; + } + + // Creates new user index on a geometry. The geometry index allows to store + // an integer user value on the geometry. + // Until set_geometry_user_index is called for a given geometry, the index + // stores -1 for that geometry. + int createGeometryUserIndex() { + if (m_geometry_indices == null) + m_geometry_indices = new ArrayList(4); + + // Try getting existing index. Use linear search. We do not expect many + // indices to be created. + for (int i = 0; i < m_geometry_indices.size(); i++) { + if (m_geometry_indices.get(i) == null) { + m_geometry_indices.set(i, + (AttributeStreamOfInt32) AttributeStreamBase + .createIndexStream(0)); + return i; + } + } + + m_geometry_indices.add((AttributeStreamOfInt32) AttributeStreamBase + .createIndexStream(0)); + return m_geometry_indices.size() - 1; + } + + // Removes the geometry user index. + void removeGeometryUserIndex(int index) { + m_geometry_indices.set(index, null); + } + + // Returns the first path of the geometry. + int getFirstPath(int geometry) { + return m_geometry_index_list.getField(geometry, 3); + } + + // Returns the first path of the geometry. + int getLastPath(int geometry) { + return m_geometry_index_list.getField(geometry, 4); + } + + // Point count in a geometry + int getPointCount(int geom) { + return m_geometry_index_list.getField(geom, 5); + } + + // Path count in a geometry + int getPathCount(int geom) { + return m_geometry_index_list.getField(geom, 6); + } + + // Filters degenerate segments in all multipath geometries + // Returns 1 if a non-zero length segment has been removed. -1, if only zero + // length segments have been removed. + // 0 if no segments have been removed. + // When b_remove_last_vertices and the result path is < 3 for polygon or < 2 + // for polyline, it'll be removed. + int filterClosePoints(double tolerance, boolean b_remove_last_vertices) { + int res = 0; + for (int geometry = getFirstGeometry(); geometry != -1; geometry = getNextGeometry(geometry)) { + if (!Geometry.isMultiPath(getGeometryType(geometry))) + continue; + + boolean b_polygon = getGeometryType(geometry) == Geometry.GeometryType.Polygon; + + for (int path = getFirstPath(geometry); path != -1;) { + // We go from the start to the half of the path first, then we + // go from the end to the half of the path. + int vertex_counter = 0; + for (int vertex = getFirstVertex(path); vertex_counter < getPathSize(path) / 2;) { + int next = getNextVertex(vertex); + if (next == -1) + break; + int vindex = getVertexIndex(vertex); + Segment seg = getSegmentFromIndex_(vindex); + double length = 0; + if (seg != null) { + length = seg.calculateLength2D(); + } else { + int vindex_next = getVertexIndex(next); + length = m_vertices._getShortestDistance(vindex, + vindex_next); + } + + if (length <= tolerance) { + if (length == 0) { + if (res == 0) + res = -1; + } else + res = 1; + + if (next != getLastVertex(path)) { + transferAllDataToTheVertex(next, vertex); + removeVertex(next, true); + } + } else { + vertex = getNextVertex(vertex); + } + vertex_counter++; + } + + int first_vertex = getFirstVertex(path); + for (int vertex = isClosedPath(path) ? first_vertex + : getLastVertex(path); getPathSize(path) > 0;) { + int prev = getPrevVertex(vertex); + if (prev != -1) { + int vindex_prev = getVertexIndex(prev); + Segment seg = getSegmentFromIndex_(vindex_prev); + double length = 0; + if (seg != null) { + length = seg.calculateLength2D(); + } else { + int vindex = getVertexIndex(vertex); + length = m_vertices._getShortestDistance(vindex, + vindex_prev); + } + + if (length <= tolerance) { + if (length == 0) { + if (res == 0) + res = -1; + } else + res = 1; + + transferAllDataToTheVertex(prev, vertex); + removeVertex(prev, false); + if (first_vertex == prev) + first_vertex = getFirstVertex(path); + } else { + vertex = getPrevVertex(vertex); + if (vertex == first_vertex) + break; + } + } else { + removeVertex(vertex, true);// remove the last vertex in + // the path + if (res == 0) + res = -1; + break; + } + } + + int path_size = getPathSize(path); + if (b_remove_last_vertices + && (b_polygon ? path_size < 3 : path_size < 2)) { + path = removePath(path); + res = path_size > 0 ? 1 : (res == 0 ? -1 : res); + } else + path = getNextPath(path); + } + } + + return res; + } + + // Checks if there are degenerate segments in any of multipath geometries + boolean hasDegenerateSegments(double tolerance) { + for (int geometry = getFirstGeometry(); geometry != -1; geometry = getNextGeometry(geometry)) { + if (!Geometry.isMultiPath(getGeometryType(geometry))) + continue; + + boolean b_polygon = getGeometryType(geometry) == Geometry.GeometryType.Polygon; + + for (int path = getFirstPath(geometry); path != -1;) { + int path_size = getPathSize(path); + if (b_polygon ? path_size < 3 : path_size < 2) + return true; + + int vertex = getFirstVertex(path); + for (int index = 0; index < path_size; index++) { + int next = getNextVertex(vertex); + if (next == -1) + break; + int vindex = getVertexIndex(vertex); + Segment seg = getSegmentFromIndex_(vindex); + double length = 0; + if (seg != null) { + length = seg.calculateLength2D(); + } else { + int vindex_next = getVertexIndex(next); + length = m_vertices._getShortestDistance(vindex, + vindex_next); + } + + if (length <= tolerance) + return true; + + vertex = next; + } + + path = getNextPath(path); + } + } + + return false; + } + + void transferAllDataToTheVertex(int from_vertex, int to_vertex) { + int vindexFrom = getVertexIndex(from_vertex); + int vindexTo = getVertexIndex(to_vertex); + if (m_weights != null) { + double weight = m_weights.read(vindexFrom); + m_weights.write(vindexTo, weight); + } + + if (m_b_has_attributes) { + // TODO: implement copying of attributes with exception of x and y + // + // for (int i = 0, nattrib = 0; i < nattrib; i++) + // { + // m_vertices->get_attribute + // } + } + // Copy user index data + if (m_indices != null) { + for (int i = 0, n = (int) m_indices.size(); i < n; i++) { + if (m_indices.get(i) != null) { + int value = getUserIndex(from_vertex, i); + if (value != -1) + setUserIndex(to_vertex, i, value); + } + } + } + } + + // Splits segment originating from the origingVertex split_count times at + // splitScalar points and inserts new vertices into the shape. + // The split is not done, when the splitScalar[i] is 0 or 1, or is equal to + // the splitScalar[i - 1]. + // Returns the number of splits actually happend (0 if no splits have + // happend). + int splitSegment(int origin_vertex, double[] split_scalars, int split_count) { + int actual_splits = 0; + int next_vertex = getNextVertex(origin_vertex); + if (next_vertex == -1) + throw new IllegalArgumentException("internal error"); + + int vindex = getVertexIndex(origin_vertex); + int vindex_next = getVertexIndex(next_vertex); + Segment seg = getSegmentFromIndex_(vindex); + double seg_length = seg == null ? m_vertices._getShortestDistance( + vindex, vindex_next) : seg.calculateLength2D(); + double told = 0.0; + for (int i = 0; i < split_count; i++) { + double t = split_scalars[i]; + if (told < t && t < 1.0) { + double f = t; + if (seg != null) { + f = seg_length > 0 ? seg._calculateSubLength(t) + / seg_length : 0.0; + } + + m_vertices._interpolateTwoVertices(vindex, vindex_next, f, + getHelperPoint_());// use this call mainly to + // interpolate the attributes. XYs + // are interpolated incorrectly for + // curves and are recalculated when + // segment is cut below. + int inserted_vertex = insertVertex_( + getPathFromVertex(origin_vertex), next_vertex, + getHelperPoint_()); + actual_splits++; + if (seg != null) { + Segment subseg = seg.cut(told, t); + int prev_vertex = getPrevVertex(inserted_vertex); + int vindex_prev = getVertexIndex(prev_vertex); + setSegmentToIndex_(vindex_prev, subseg); + setXY(inserted_vertex, subseg.getEndXY()); // fix XY + // coordinates + // to be + // parameter + // based + // (interpolate_two_vertices_) + if (i == split_count - 1 || split_scalars[i + 1] == 1.0) {// last + // chance + // to + // set + // last + // split + // segment + // here: + Segment subseg_end = seg.cut(t, 1.0); + setSegmentToIndex_(vindex_prev, subseg_end); + } + } + } + } + + return actual_splits; + } + + // interpolates the attributes for the specified path between from_vertex + // and to_vertex + void interpolateAttributesForClosedPath(int path, int from_vertex, + int to_vertex) { + assert (isClosedPath(path)); + + if (!m_b_has_attributes) + return; + + double sub_length = calculateSubLength2D(path, from_vertex, to_vertex); + + if (sub_length == 0.0) + return; + + int nattr = m_vertex_description.getAttributeCount(); + + for (int iattr = 1; iattr < nattr; iattr++) { + int semantics = m_vertex_description.getSemantics(iattr); + + int interpolation = VertexDescription.getInterpolation(semantics); + if (interpolation == VertexDescription.Interpolation.ANGULAR) + continue; + + int components = VertexDescription.getComponentCount(semantics); + + for (int ordinate = 0; ordinate < components; ordinate++) + interpolateAttributesForClosedPath_(semantics, path, + from_vertex, to_vertex, sub_length, ordinate); + } + + return; + } + + // calculates the length for the specified path between from_vertex and + // to_vertex + double calculateSubLength2D(int path, int from_vertex, int to_vertex) { + int shape_from_index = getVertexIndex(from_vertex); + int shape_to_index = getVertexIndex(to_vertex); + + if (shape_from_index < 0 || shape_to_index > getTotalPointCount() - 1) + throw new IllegalArgumentException("invalid call"); + + if (shape_from_index > shape_to_index) { + if (!isClosedPath(path)) + throw new IllegalArgumentException( + "cannot iterate across an open path"); + } + + double sub_length = 0.0; + + for (int vertex = from_vertex; vertex != to_vertex; vertex = getNextVertex(vertex)) { + int vertex_index = getVertexIndex(vertex); + Segment segment = getSegmentFromIndex_(vertex_index); + if (segment != null) { + sub_length += segment.calculateLength2D(); + } else { + int next_vertex_index = getVertexIndex(getNextVertex(vertex)); + sub_length += m_vertices._getShortestDistance(vertex_index, + next_vertex_index); + } + } + + return sub_length; + } + + // set_point modifies the vertex and associated segments. + void setPoint(int vertex, Point new_coord) { + int vindex = getVertexIndex(vertex); + m_vertices.setPointByVal(vindex, new_coord); + Segment seg = getSegmentFromIndex_(vindex); + if (seg != null) { + seg.setStart(new_coord); + } + int prev = getPrevVertex(vertex); + if (prev != -1) { + int vindex_p = getVertexIndex(prev); + Segment seg_p = getSegmentFromIndex_(vindex_p); + if (seg_p != null) { + seg.setEnd(new_coord); + } + } + } + + // Queries point for a given vertex. + void queryPoint(int vertex, Point point) { + int vindex = getVertexIndex(vertex); + m_vertices.getPointByVal(vindex, point); + // assert(getXY(vertex) == point.getXY()); + } + + // set_xy modifies the vertex and associated segments. + void setXY(int vertex, Point2D new_coord) { + setXY(vertex, new_coord.x, new_coord.y); + } + + // set_xy modifies the vertex and associated segments. + void setXY(int vertex, double new_x, double new_y) { + int vindex = getVertexIndex(vertex); + m_vertices.setXY(vindex, new_x, new_y); + Segment seg = getSegmentFromIndex_(vindex); + if (seg != null) { + seg.setStartXY(new_x, new_y); + } + int prev = getPrevVertex(vertex); + if (prev != -1) { + int vindex_p = getVertexIndex(prev); + Segment seg_p = getSegmentFromIndex_(vindex_p); + if (seg_p != null) { + seg.setEndXY(new_x, new_y); + } + } + } + + Point2D getXY(int vertex) { + Point2D pt = new Point2D(); + int vindex = getVertexIndex(vertex); + pt.setCoords(m_vertices.getXY(vindex)); + return pt; + } + + // Returns the coordinates of the vertex. + void getXY(int vertex, Point2D ptOut) { + int vindex = getVertexIndex(vertex); + ptOut.setCoords(m_vertices.getXY(vindex)); + } + + void getXYWithIndex(int index, Point2D ptOut) { + m_xy_stream.read(2 * index, ptOut); + } + + // Gets the attribute for the given semantics and ordinate. + double getAttributeAsDbl(int semantics, int vertex, int ordinate) { + return m_vertices.getAttributeAsDbl(semantics, getVertexIndex(vertex), + ordinate); + } + + // Sets the attribute for the given semantics and ordinate. + void setAttribute(int semantics, int vertex, int ordinate, double value) { + m_vertices.setAttribute(semantics, getVertexIndex(vertex), ordinate, + value); + } + + // Sets the attribute for the given semantics and ordinate. + void setAttribute(int semantics, int vertex, int ordinate, int value) { + m_vertices.setAttribute(semantics, getVertexIndex(vertex), ordinate, + value); + } + + // Returns a reference to the vertex description + VertexDescription getVertexDescription() { + return m_vertex_description; + } + + int getMinPathVertexY(int path) { + int first_vert = getFirstVertex(path); + int minv = first_vert; + int vert = getNextVertex(first_vert); + while (vert != -1 && vert != first_vert) { + if (compareVerticesSimpleY_(vert, minv) < 0) + minv = vert; + vert = getNextVertex(vert); + } + return minv; + } + + // Returns an index value for the vertex inside of the underlying array of + // vertices. + // This index is for the use with the get_xy_with_index. get_xy is + // equivalent to calling get_vertex_index and get_xy_with_index. + int getVertexIndex(int vertex) { + return m_vertex_index_list.getField(vertex, 0); + } + + // Returns the y coordinate of the vertex. + double getY(int vertex) { + Point2D pt = new Point2D(); + getXY(vertex, pt); + return pt.y; + } + + // returns True if xy coordinates at vertices are equal. + boolean isEqualXY(int vertex_1, int vertex_2) { + int vindex1 = getVertexIndex(vertex_1); + int vindex2 = getVertexIndex(vertex_2); + return m_vertices.getXY(vindex1).isEqual(m_vertices.getXY(vindex2)); + } + + // returns True if xy coordinates at vertices are equal. + boolean isEqualXY(int vertex, Point2D pt) { + int vindex = getVertexIndex(vertex); + return m_vertices.getXY(vindex).isEqual(pt); + } + + // Sets weight to the vertex. Weight is used by clustering and cracking. + void setWeight(int vertex, double weight) { + if (weight < 1.0) + weight = 1.0; + + if (m_weights == null) { + if (weight == 1.0) + return; + + m_weights = (AttributeStreamOfDbl) (AttributeStreamBase + .createDoubleStream(m_vertices.getPointCount(), 1.0)); + } + + int vindex = getVertexIndex(vertex); + m_weights.write(vindex, weight); + } + + double getWeight(int vertex) { + if (m_weights == null) + return 1.0; + + int vindex = getVertexIndex(vertex); + return m_weights.read(vindex); + } + + // Removes associated weights + void removeWeights() { + m_weights = null; + } + + // Sets value to the given user index. + void setUserIndex(int vertex, int index, int value) { + // CHECKVERTEXHANDLE(vertex); + AttributeStreamOfInt32 stream = m_indices.get(index); + // assert(get_prev_vertex(vertex) != -0x7eadbeaf);//using deleted vertex + int vindex = getVertexIndex(vertex); + if (stream.size() < m_vertices.getPointCount()) + stream.resize(m_vertices.getPointCount(), -1); + stream.write(vindex, value); + } + + int getUserIndex(int vertex, int index) { + // CHECKVERTEXHANDLE(vertex); + int vindex = getVertexIndex(vertex); + AttributeStreamOfInt32 stream = m_indices.get(index); + if (vindex < stream.size()) { + int val = stream.read(vindex); + return val; + } else + return -1; + } + + // Creates new user index. The index have random values. The index allows to + // store an integer user value on the vertex. + int createUserIndex() { + if (m_indices == null) + m_indices = new ArrayList(0); + + // Try getting existing index. Use linear search. We do not expect many + // indices to be created. + for (int i = 0; i < m_indices.size(); i++) { + if (m_indices.get(i) == null) { + m_indices.set(i, (AttributeStreamOfInt32) AttributeStreamBase + .createIndexStream(0, -1)); + return i; + } + } + + m_indices.add((AttributeStreamOfInt32) AttributeStreamBase + .createIndexStream(0, -1)); + return m_indices.size() - 1; + } + + // Removes the user index. + void removeUserIndex(int index) { + m_indices.set(index, null); + } + + // Returns segment, connecting currentVertex and next vertex. Returns NULL + // if it is a Line. + Segment getSegment(int vertex) { + if (m_segments != null) { + int vindex = getVertexIndex(vertex); + return m_segments.get(vindex); + } + return null; + } + + // Returns a straight line that connects this and next vertices. No + // attributes. Returns false if no next vertex exists (end of polyline + // part). + // Can be used together with get_segment. + boolean queryLineConnector(int vertex, Line line) { + int next = getNextVertex(vertex); + if (next == -1) + return false; + + if (!m_b_has_attributes) { + Point2D pt = new Point2D(); + getXY(vertex, pt); + line.setStartXY(pt); + getXY(next, pt); + line.setEndXY(pt); + } else { + Point pt = new Point(); + queryPoint(vertex, pt); + line.setStart(pt); + queryPoint(next, pt); + line.setEnd(pt); + } + + return true; + } + + // Inserts an empty path before the given one. If before_path is -1, adds + // path at the end. + int insertPath(int geometry, int before_path) { + int prev = -1; + + if (before_path != -1) { + if (geometry != getGeometryFromPath(before_path)) + throw new IllegalArgumentException("internal error"); + + prev = getPrevPath(before_path); + } else + prev = getLastPath(geometry); + + int newpath = newPath_(geometry); + if (before_path != -1) + setPrevPath_(before_path, newpath); + + setNextPath_(newpath, before_path); + setPrevPath_(newpath, prev); + if (prev != -1) + setNextPath_(prev, newpath); + else + setFirstPath_(geometry, newpath); + + if (before_path == -1) + setLastPath_(geometry, newpath); + + setGeometryPathCount_(geometry, getPathCount(geometry) + 1); + return newpath; + } + + // Removes a path, gets rid of all its vertices, and returns the next one + int removePath(int path) { + int prev = getPrevPath(path); + int next = getNextPath(path); + int geometry = getGeometryFromPath(path); + if (prev != -1) + setNextPath_(prev, next); + else { + setFirstPath_(geometry, next); + } + if (next != -1) + setPrevPath_(next, prev); + else { + setLastPath_(geometry, prev); + } + + clearPath(path); + + setGeometryPathCount_(geometry, getPathCount(geometry) - 1); + freePath_(path); + return next; + } + + // Clears all vertices from the path + void clearPath(int path) { + int first_vertex = getFirstVertex(path); + if (first_vertex != -1) { + // TODO: can ve do this in one shot? + int vertex = first_vertex; + for (int i = 0, n = getPathSize(path); i < n; i++) { + int v = vertex; + vertex = getNextVertex(vertex); + freeVertex_(v); + } + int geometry = getGeometryFromPath(path); + setGeometryVertexCount_(geometry, getPointCount(geometry) + - getPathSize(path)); + } + setPathSize_(path, 0); + } + + // Returns the next path (-1 if there are no more paths in the geometry). + int getNextPath(int currentPath) { + return m_path_index_list.getField(currentPath, 2); + } + + // Returns the previous path (-1 if there are no more paths in the + // geometry). + int getPrevPath(int currentPath) { + return m_path_index_list.getField(currentPath, 1); + } + + // Returns the number of vertices in the path. + int getPathSize(int path) { + return m_path_index_list.getField(path, 3); + } + + // Returns True if the path is closed. + boolean isClosedPath(int path) { + return (getPathFlags_(path) & PathFlags_.closedPath) != 0; + } + + // Makes path closed. Closed paths are circular lists. get_next_vertex + // always succeeds + void setClosedPath(int path, boolean b_yes_no) { + if (isClosedPath(path) == b_yes_no) + return; + if (getPathSize(path) > 0) { + int first = getFirstVertex(path); + int last = getLastVertex(path); + if (b_yes_no) { + // make a circular list + setNextVertex_(last, first); + setPrevVertex_(first, last); + // set segment to NULL (just in case) + int vindex = getVertexIndex(last); + setSegmentToIndex_(vindex, null); + } else { + setNextVertex_(last, -1); + setPrevVertex_(first, -1); + int vindex = getVertexIndex(last); + setSegmentToIndex_(vindex, null); + } + } + + int oldflags = getPathFlags_(path); + int flags = (oldflags | (int) PathFlags_.closedPath) + - (int) PathFlags_.closedPath;// clear the bit; + setPathFlags_(path, flags + | (b_yes_no ? (int) PathFlags_.closedPath : 0)); + } + + // Closes all paths of the geometry (has to be a polyline or polygon). + void closeAllPaths(int geometry) { + if (getGeometryType(geometry) == Geometry.GeometryType.Polygon) + return; + if (!Geometry.isLinear(getGeometryType(geometry))) + throw new IllegalArgumentException("internal error"); + + for (int path = getFirstPath(geometry); path != -1; path = getNextPath(path)) { + setClosedPath(path, true); + } + } + + // Returns geometry from path + int getGeometryFromPath(int path) { + return m_path_index_list.getField(path, 7); + } + + // Returns True if the path is exterior. + boolean isExterior(int path) { + return (getPathFlags_(path) & PathFlags_.exteriorPath) != 0; + } + + // Sets exterior flag + void setExterior(int path, boolean b_yes_no) { + int oldflags = getPathFlags_(path); + int flags = (oldflags | (int) PathFlags_.exteriorPath) + - (int) PathFlags_.exteriorPath;// clear the bit; + setPathFlags_(path, flags + | (b_yes_no ? (int) PathFlags_.exteriorPath : 0)); + } + + // Returns the ring area + double getRingArea(int path) { + if (isRingAreaValid_(path)) + return m_path_areas.get(getPathIndex_(path)); + + Line line = new Line(); + int vertex = getFirstVertex(path); + if (vertex == -1) + return 0; + Point2D pt0 = new Point2D(); + getXY(vertex, pt0); + double area = 0; + for (int i = 0, n = getPathSize(path); i < n; i++, vertex = getNextVertex(vertex)) { + Segment seg = getSegment(vertex); + if (seg == null) { + if (!queryLineConnector(vertex, line)) + continue; + + seg = line; + } + + double a = seg._calculateArea2DHelper(pt0.x, pt0.y); + area += a; + } + + setRingAreaValid_(path, true); + m_path_areas.set(getPathIndex_(path), area); + + return area; + } + + // Sets value to the given user index on a path. + void setPathUserIndex(int path, int index, int value) { + AttributeStreamOfInt32 stream = m_pathindices.get(index); + int pindex = getPathIndex_(path); + if (stream.size() < m_path_areas.size()) + stream.resize(m_path_areas.size(), -1); + stream.write(pindex, value); + } + + // Returns the value of the given user index of a path + int getPathUserIndex(int path, int index) { + int pindex = getPathIndex_(path); + AttributeStreamOfInt32 stream = m_pathindices.get(index); + if (pindex < stream.size()) + return stream.read(pindex); + else + return -1; + } + + // Creates new user index on a path. The index have random values. The path + // index allows to store an integer user value on the path. + int createPathUserIndex() { + if (m_pathindices == null) + m_pathindices = new ArrayList(0); + // Try getting existing index. Use linear search. We do not expect many + // indices to be created. + for (int i = 0; i < m_pathindices.size(); i++) { + if (m_pathindices.get(i) == null) { + m_pathindices.set(i, + (AttributeStreamOfInt32) (AttributeStreamBase + .createIndexStream(0))); + return i; + } + } + + m_pathindices.add((AttributeStreamOfInt32) (AttributeStreamBase + .createIndexStream(0))); + return (int) (m_pathindices.size() - 1); + } + + // Removes the path user index. + void removePathUserIndex(int index) { + m_pathindices.set(index, null); + } + + // Moves a path from any geometry before a given path in the dst_geom + // geometry. The path_handle do not change after the operation. + // before_path can be -1, then the path is moved to the end of the dst_geom. + void movePath(int geom, int before_path, int path_to_move) { + if (path_to_move == -1) + throw new IllegalArgumentException(); + + if (before_path == path_to_move) + return; + + int next = getNextPath(path_to_move); + int prev = getPrevPath(path_to_move); + int geom_src = getGeometryFromPath(path_to_move); + if (prev == -1) { + setFirstPath_(geom_src, next); + } else { + setNextPath_(prev, next); + } + + if (next == -1) { + setLastPath_(geom_src, prev); + } else { + setPrevPath_(next, prev); + } + + setGeometryVertexCount_(geom_src, getPointCount(geom_src) + - getPathSize(path_to_move)); + setGeometryPathCount_(geom_src, getPathCount(geom_src) - 1); + + if (before_path == -1) + prev = getLastPath(geom); + else + prev = getPrevPath(before_path); + + setPrevPath_(path_to_move, prev); + setNextPath_(path_to_move, before_path); + if (before_path == -1) + setLastPath_(geom, path_to_move); + else + setPrevPath_(before_path, path_to_move); + if (prev == -1) + setFirstPath_(geom, path_to_move); + else + setNextPath_(prev, path_to_move); + setGeometryVertexCount_(geom, getPointCount(geom) + + getPathSize(path_to_move)); + setGeometryPathCount_(geom, getPathCount(geom) + 1); + setPathGeometry_(path_to_move, geom); + } + + // Adds a copy of a vertex to a path. Connects with a straight line. + // Returns new vertex handle. + int addVertex(int path, int vertex) { + m_vertices.getPointByVal(getVertexIndex(vertex), getHelperPoint_()); + return insertVertex_(path, -1, getHelperPoint_()); + } + + // Removes vertex from path. Uses either left or right segments to + // reconnect. Returns next vertex after erased one. + int removeVertex(int vertex, boolean b_left_segment) { + int path = getPathFromVertex(vertex); + int prev = getPrevVertex(vertex); + int next = getNextVertex(vertex); + if (prev != -1) + setNextVertex_(prev, next); + + int path_size = getPathSize(path); + + if (vertex == getFirstVertex(path)) { + setFirstVertex_(path, path_size > 1 ? next : -1); + } + + if (next != -1) + setPrevVertex_(next, prev); + + if (vertex == getLastVertex(path)) { + setLastVertex_(path, path_size > 1 ? prev : -1); + } + + if (prev != -1 && next != -1) { + int vindex_prev = getVertexIndex(prev); + int vindex_next = getVertexIndex(next); + if (b_left_segment) { + Segment seg = getSegmentFromIndex_(vindex_prev); + if (seg != null) { + Point2D pt = new Point2D(); + m_vertices.getXY(vindex_next, pt); + seg.setEndXY(pt); + } + } else { + int vindex_erased = getVertexIndex(vertex); + Segment seg = getSegmentFromIndex_(vindex_erased); + setSegmentToIndex_(vindex_prev, seg); + if (seg != null) { + Point2D pt = m_vertices.getXY(vindex_prev); + seg.setStartXY(pt); + } + } + } + + setPathSize_(path, path_size - 1); + int geometry = getGeometryFromPath(path); + setGeometryVertexCount_(geometry, getPointCount(geometry) - 1); + freeVertex_(vertex); + return next; + } + + // Returns first vertex of the given path. + int getFirstVertex(int path) { + return m_path_index_list.getField(path, 4); + } + + // Returns last vertex of the given path. For the closed paths + // get_next_vertex for the last vertex returns the first vertex. + int getLastVertex(int path) { + return m_path_index_list.getField(path, 5); + } + + // Returns next vertex. Closed paths are circular lists, so get_next_vertex + // always returns vertex. Open paths return -1 for last vertex. + int getNextVertex(int currentVertex) { + return m_vertex_index_list.getField(currentVertex, 2); + } + + // Returns previous vertex. Closed paths are circular lists, so + // get_prev_vertex always returns vertex. Open paths return -1 for first + // vertex. + int getPrevVertex(int currentVertex) { + return m_vertex_index_list.getField(currentVertex, 1); + } + + // Returns a path the vertex belongs to. + int getPathFromVertex(int vertex) { + return m_vertex_index_list.getField(vertex, 3); + } + + // Adds a copy of the point to a path. Connects with a straight line. + // Returns new vertex handle. + int addPoint(int path, Point point) { + return insertVertex_(path, -1, point); + } + + // Vertex iterator allows to go through all vertices of the Edit_shape. + static class VertexIterator { + private EditShape m_parent; + private int m_geometry; + private int m_path; + private int m_vertex; + private int m_first_vertex; + private int m_index; + boolean m_b_first; + boolean m_b_skip_mulit_points; + + private VertexIterator(EditShape parent, int geometry, int path, + int vertex, int first_vertex, int index, + boolean b_skip_mulit_points) { + m_parent = parent; + m_geometry = geometry; + m_path = path; + m_vertex = vertex; + m_index = index; + m_b_skip_mulit_points = b_skip_mulit_points; + m_first_vertex = first_vertex; + m_b_first = true; + } + + int moveToNext_() { + if (m_b_first) { + m_b_first = false; + return m_vertex; + } + + if (m_vertex != -1) { + m_vertex = m_parent.getNextVertex(m_vertex); + m_index++; + if (m_vertex != -1 && m_vertex != m_first_vertex) + return m_vertex; + + return moveToNextHelper_();// separate into another function for + // inlining + } + + return -1; + } + + int moveToNextHelper_() { + m_path = m_parent.getNextPath(m_path); + m_index = 0; + while (m_geometry != -1) { + for (; m_path != -1; m_path = m_parent.getNextPath(m_path)) { + m_vertex = m_parent.getFirstVertex(m_path); + m_first_vertex = m_vertex; + if (m_vertex != -1) + return m_vertex; + } + + m_geometry = m_parent.getNextGeometry(m_geometry); + if (m_geometry == -1) + break; + + if (m_b_skip_mulit_points + && !Geometry.isMultiPath(m_parent + .getGeometryType(m_geometry))) { + continue; + } + + m_path = m_parent.getFirstPath(m_geometry); + } + + return -1; + } + + // moves to next vertex. Returns -1 when there are no more vertices. + VertexIterator(VertexIterator source) { + m_parent = source.m_parent; + m_geometry = source.m_geometry; + m_path = source.m_path; + m_vertex = source.m_vertex; + m_index = source.m_index; + m_b_skip_mulit_points = source.m_b_skip_mulit_points; + m_first_vertex = source.m_first_vertex; + m_b_first = true; + } + + public int next() { + return moveToNext_(); + } + + public int currentGeometry() { + assert (m_vertex != -1); + return m_geometry; + } + + public int currentPath() { + assert (m_vertex != -1); + return m_path; + } + + public static VertexIterator create_(EditShape parent, int geometry, + int path, int vertex, int first_vertex, int index, + boolean b_skip_mulit_points) { + return new VertexIterator(parent, geometry, path, vertex, + first_vertex, index, b_skip_mulit_points); + } + }; + + // Returns the vertex iterator that allows iteration through all vertices of + // all paths of all geometries. + VertexIterator queryVertexIterator() { + return queryVertexIterator(false); + } + + VertexIterator queryVertexIterator(VertexIterator source) { + return new VertexIterator(source); + } + + // Returns the vertex iterator that allows iteration through all vertices of + // all paths of all geometries. + // If bSkipMultiPoints is true, then the iterator will skip the Multi_point + // vertices + VertexIterator queryVertexIterator(boolean b_skip_multi_points) { + int geometry = -1; + int path = -1; + int vertex = -1; + int first_vertex = -1; + int index = 0; + boolean bFound = false; + + for (geometry = getFirstGeometry(); geometry != -1; geometry = getNextGeometry(geometry)) { + if (b_skip_multi_points + && !Geometry.isMultiPath(getGeometryType(geometry))) + continue; + + for (path = getFirstPath(geometry); path != -1; path = getNextPath(path)) { + vertex = getFirstVertex(path); + first_vertex = vertex; + index = 0; + if (vertex == -1) + continue; + + bFound = true; + break; + } + + if (bFound) + break; + } + + return VertexIterator.create_(this, geometry, path, vertex, + first_vertex, index, b_skip_multi_points); + } + + // Applies affine transformation + void applyTransformation(Transformation2D transform) { + m_vertices_mp.applyTransformation(transform); + if (m_segments != null) { + for (int i = 0, n = m_segments.size(); i < n; i++) { + if (m_segments.get(i) != null) { + m_segments.get(i).applyTransformation(transform); + } + } + } + } + + void interpolateAttributesForClosedPath_(int semantics, int path, + int from_vertex, int to_vertex, double sub_length, int ordinate) { + if (from_vertex == to_vertex) + return; + + double from_attribute = getAttributeAsDbl(semantics, from_vertex, + ordinate); + double to_attribute = getAttributeAsDbl(semantics, to_vertex, ordinate); + double cumulative_length = 0.0; + double prev_interpolated_attribute = from_attribute; + + for (int vertex = from_vertex; vertex != to_vertex; vertex = getNextVertex(vertex)) { + setAttribute(semantics, vertex, ordinate, + prev_interpolated_attribute); + + int vertex_index = getVertexIndex(vertex); + Segment segment = getSegmentFromIndex_(vertex_index); + double segment_length; + + if (segment != null) { + segment_length = segment.calculateLength2D(); + } else { + int next_vertex_index = getVertexIndex(getNextVertex(vertex)); + segment_length = m_vertices._getShortestDistance(vertex_index, + next_vertex_index); + } + cumulative_length += segment_length; + double t = cumulative_length / sub_length; + prev_interpolated_attribute = (1.0 - t) * from_attribute + t + * to_attribute; + } + + return; + } + + void SetGeometryType_(int geom, int gt) { + m_geometry_index_list.setField(geom, 2, gt); + } + + void splitSegment_(int origin_vertex, SegmentIntersector intersector, + int intersector_index, boolean b_forward) { + if (b_forward) { + splitSegmentForward_(origin_vertex, intersector, intersector_index); + } else { + splitSegmentBackward_(origin_vertex, intersector, intersector_index); + } + } + + void setPrevVertex_(int vertex, int prev) { + m_vertex_index_list.setField(vertex, 1, prev); + } + + void setNextVertex_(int vertex, int next) { + m_vertex_index_list.setField(vertex, 2, next); + } + + void setPathToVertex_(int vertex, int path) { + m_vertex_index_list.setField(vertex, 3, path); + } + + void setPathSize_(int path, int size) { + m_path_index_list.setField(path, 3, size); + } + + void setFirstVertex_(int path, int first_vertex) { + m_path_index_list.setField(path, 4, first_vertex); + } + + void setLastVertex_(int path, int last_vertex) { + m_path_index_list.setField(path, 5, last_vertex); + } + + void setGeometryPathCount_(int geom, int path_count) { + m_geometry_index_list.setField(geom, 6, path_count); + } + + void setGeometryVertexCount_(int geom, int vertex_count) { + m_geometry_index_list.setField(geom, 5, vertex_count); + } + + boolean ringParentageCheckInternal_(int vertex_1, int vertex_2) { + if (vertex_1 == vertex_2) + return true; + int vprev_1 = vertex_1; + int vprev_2 = vertex_2; + for (int v_1 = getNextVertex(vertex_1), v_2 = getNextVertex(vertex_2); v_1 != vertex_1 + && v_2 != vertex_2; v_1 = getNextVertex(v_1), v_2 = getNextVertex(v_2)) { + if (v_1 == vertex_2) + return true; + if (v_2 == vertex_1) + return true; + + assert (getPrevVertex(v_1) == vprev_1);// detect malformed list + assert (getPrevVertex(v_2) == vprev_2);// detect malformed list + vprev_1 = v_1; + vprev_2 = v_2; + } + + return false; + } + + void reverseRingInternal_(int vertex) { + int v = vertex; + do { + int prev = getPrevVertex(v); + int next = getNextVertex(v); + setNextVertex_(v, prev); + setPrevVertex_(v, next); + v = next; + } while (v != vertex); + // Path's last becomes invalid. Do not attempt to fix it here, because + // this is not the intent of the method + // Note: only last is invalid. other things sould not change. + } + + void setTotalPointCount_(int count) { + m_point_count = count; + } + + void removePathOnly_(int path) { + int prev = getPrevPath(path); + int next = getNextPath(path); + int geometry = getGeometryFromPath(path); + if (prev != -1) + setNextPath_(prev, next); + else { + setFirstPath_(geometry, next); + } + if (next != -1) + setPrevPath_(next, prev); + else { + setLastPath_(geometry, prev); + } + + setFirstVertex_(path, -1); + setLastVertex_(path, -1); + freePath_(path); + } + + // void DbgVerifyIntegrity(int vertex); + // void dbg_verify_vertex_counts(); + int removeVertexInternal_(int vertex, boolean b_left_segment) { + int prev = getPrevVertex(vertex); + int next = getNextVertex(vertex); + if (prev != -1) + setNextVertex_(prev, next); + + if (next != -1) + setPrevVertex_(next, prev); + + if (prev != -1 && next != -1) { + int vindex_prev = getVertexIndex(prev); + int vindex_next = getVertexIndex(next); + if (b_left_segment) { + Segment seg = getSegmentFromIndex_(vindex_prev); + if (seg != null) { + Point2D pt = new Point2D(); + m_vertices.getXY(vindex_next, pt); + seg.setEndXY(pt); + } + } else { + int vindex_erased = getVertexIndex(vertex); + Segment seg = getSegmentFromIndex_(vindex_erased); + setSegmentToIndex_(vindex_prev, seg); + if (seg != null) { + Point2D pt = new Point2D(); + m_vertices.getXY(vindex_prev, pt); + seg.setStartXY(pt); + } + } + } + freeVertex_(vertex); + return next; + } + + boolean isRingAreaValid_(int path) { + return (getPathFlags_(path) & PathFlags_.ringAreaValid) != 0; + } + + // Sets exterior flag + void setRingAreaValid_(int path, boolean b_yes_no) { + int oldflags = getPathFlags_(path); + int flags = (oldflags | (int) PathFlags_.ringAreaValid) + - (int) PathFlags_.ringAreaValid;// clear the bit; + setPathFlags_(path, flags + | (b_yes_no ? (int) PathFlags_.ringAreaValid : 0)); + } + + int compareVerticesSimpleY_(int v_1, int v_2) { + Point2D pt_1 = new Point2D(); + getXY(v_1, pt_1); + Point2D pt_2 = new Point2D(); + getXY(v_2, pt_2); + int res = pt_1.compare(pt_2); + return res; + } + + int compareVerticesSimpleX_(int v_1, int v_2) { + Point2D pt_1 = new Point2D(); + getXY(v_1, pt_1); + Point2D pt_2 = new Point2D(); + getXY(v_2, pt_2); + int res = pt_1.compare(pt_2); + return res; + } + + public static class SimplificatorVertexComparerY extends + AttributeStreamOfInt32.IntComparator { + EditShape parent; + + SimplificatorVertexComparerY(EditShape parent_) { + parent = parent_; + } + + @Override + public int compare(int i_1, int i_2) { + return parent.compareVerticesSimpleY_(i_1, i_2); + } + } + + public static class SimplificatorVertexComparerX extends + AttributeStreamOfInt32.IntComparator { + EditShape parent; + + SimplificatorVertexComparerX(EditShape parent_) { + parent = parent_; + } + + @Override + public int compare(int i_1, int i_2) { + return parent.compareVerticesSimpleX_(i_1, i_2); + } + } + + // void sort_vertices_simple_by_y_heap_merge(Dynamic_array& points, + // const Dynamic_array* geoms); + + void sortVerticesSimpleByY_(AttributeStreamOfInt32 points, int begin_, + int end_) { + if (m_bucket_sort == null) + m_bucket_sort = new BucketSort(); + m_bucket_sort.sort(points, begin_, end_, new EditShapeBucketSortHelper( + this)); + } + + void sortVerticesSimpleByYHelper_(AttributeStreamOfInt32 points, + int begin_, int end_) { + points.Sort(begin_, end_, new SimplificatorVertexComparerY(this)); + } + + void sortVerticesSimpleByX_(AttributeStreamOfInt32 points, int begin_, + int end_) { + points.Sort(begin_, end_, new SimplificatorVertexComparerX(this)); + } + + // Approximate size of the structure in memory. + // The estimated size can be very slightly less than the actual size. + // int estimate_memory_size() const; + +} diff --git a/src/com/esri/core/geometry/Envelope.java b/src/com/esri/core/geometry/Envelope.java new file mode 100644 index 00000000..2005a29c --- /dev/null +++ b/src/com/esri/core/geometry/Envelope.java @@ -0,0 +1,1104 @@ +/* + Copyright 1995-2013 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ + + +package com.esri.core.geometry; + +import java.io.Serializable; + +import com.esri.core.geometry.VertexDescription.Semantics; + +/** + * Envelopes are the rectangular window that contain a specific element. + */ +public final class Envelope extends Geometry implements Serializable { + private static final long serialVersionUID = 2L; + + Envelope2D m_envelope = new Envelope2D(); + + double[] m_attributes;// use doubles to store everything (int64 are bitcast) + + /** + * Creates an envelope by defining its center, width, and height. + * + * @param center + * The center point of the envelope. + * @param width + * The width of the envelope. + * @param height + * The height of the envelope. + */ + public Envelope(Point center, double width, double height) { + m_description = VertexDescriptionDesignerImpl.getDefaultDescriptor2D(); + m_envelope.setEmpty(); + if (center.isEmpty()) + return; + + _setFromPoint(center, width, height); + } + + Envelope(Envelope2D env2D) { + m_description = VertexDescriptionDesignerImpl.getDefaultDescriptor2D(); + m_envelope.setCoords(env2D); + m_envelope.normalize(); + } + + Envelope(VertexDescription vd) { + if (vd == null) + throw new IllegalArgumentException(); + m_description = vd; + m_envelope.setEmpty(); + } + + Envelope(VertexDescription vd, Envelope2D env2D) { + if (vd == null) + throw new IllegalArgumentException(); + m_description = vd; + m_envelope.setCoords(env2D); + m_envelope.normalize(); + } + + /** + * Constructs an empty envelope. + */ + public Envelope() { + m_description = VertexDescriptionDesignerImpl.getDefaultDescriptor2D(); + m_envelope.setEmpty(); + } + + /** + * Constructs an envelope that covers the given point. The coordinates of + * the point are used to set the envelope's extent. + * + * @param point + * - The point that the envelope covers. + */ + public Envelope(Point point) { + m_description = VertexDescriptionDesignerImpl.getDefaultDescriptor2D(); + m_envelope.setEmpty(); + if (point.isEmpty()) + return; + + _setFromPoint(point); + } + + /** + * Constructs an envelope with the specified X and Y extents. + * + * @param xmin + * The minimum X coordinate of the envelope. + * @param ymin + * The minimum Y coordinate of the envelope. + * @param xmax + * The maximum X coordinate of the envelope. + * @param ymax + * The maximum Y coordinate of the envelope. + */ + public Envelope(double xmin, double ymin, double xmax, double ymax) { + m_description = VertexDescriptionDesignerImpl.getDefaultDescriptor2D(); + setCoords(xmin, ymin, xmax, ymax); + } + + /** + * Sets the 2 dimensional extents of the envelope. + * + * @param xmin + * The minimum X coordinate of the envelope. + * @param ymin + * The minimum Y coordinate of the envelope. + * @param xmax + * The maximum X coordinate of the envelope. + * @param ymax + * The maximum Y coordinate of the envelope. + */ + public void setCoords(double xmin, double ymin, double xmax, double ymax) { + _touch(); + m_envelope.setCoords(xmin, ymin, xmax, ymax); + } + + /** + * Sets the envelope from the array of points. The result envelope is a + * bounding box of all the points in the array. If the array has zero + * length, the envelope will be empty. + * + * @param points + * The point array. + */ + void setCoords(Point[] points) { + _touch(); + setEmpty(); + for (int i = 0, n = points.length; i < n; i++) + merge(points[i]); + } + + void setEnvelope2D(Envelope2D e2d) { + _touch(); + if (!e2d.isValid()) + throw new IllegalArgumentException(); + + m_envelope.setCoords(e2d); + } + + /** + * Removes all points from this geometry. + */ + @Override + public void setEmpty() { + _touch(); + m_envelope.setEmpty(); + } + + /** + * Indicates whether this envelope contains any points. + * + * @return boolean Returns true if the envelope is empty. + */ + @Override + public boolean isEmpty() { + return m_envelope.isEmpty(); + } + + /** + * The width of the envelope. + * + * @return The width of the envelope. + */ + + public double getWidth() { + return m_envelope.getWidth(); + } + + /** + * The height of the envelope. + * + * @return The height of the envelope. + */ + public double getHeight() { + return m_envelope.getHeight(); + } + + /** + * The X coordinate of center of the envelope. + * + * @return The center's X coordinate of the envelope. + */ + public double getCenterX() { + return m_envelope.getCenterX(); + } + + /** + * The Y coordinate fo the center of the envelope. + * + * @return The center's Y coordinate of the envelope. + */ + public double getCenterY() { + return m_envelope.getCenterY(); + } + + Point2D getCenterXY() { + return m_envelope.getCenter(); + } + + void getCenter(Point point_out) { + point_out.assignVertexDescription(m_description); + if (isEmpty()) { + point_out.setEmpty(); + return; + } + + int nattrib = m_description.getAttributeCount(); + for (int i = 1; i < nattrib; i++) { + int semantics = m_description.getSemantics(i); + int ncomp = VertexDescription.getComponentCount(semantics); + for (int iord = 0; iord < ncomp; iord++) { + double v = 0.5 * (getAttributeAsDblImpl_(0, semantics, iord) + getAttributeAsDblImpl_( + 1, semantics, iord)); + point_out.setAttribute(semantics, iord, v); + } + } + point_out.setXY(m_envelope.getCenter()); + } + + void merge(Point2D pt) { + _touch(); + m_envelope.merge(pt); + } + + /** + * Merges this envelope with the extent of the given envelope. If the + * original envelope is empty, the coordinates of the envelope to merge with + * are assigned. If the envelope to merge with is empty, the original + * envelope stays unchanged. + * + * @param other + * The envelope to merge with. + */ + public void merge(Envelope other) { + _touch(); + if (other.isEmpty()) + return; + + VertexDescription otherVD = other.m_description; + if (otherVD != m_description) + mergeVertexDescription(otherVD); + m_envelope.merge(other.m_envelope); + for (int iattrib = 1, nattrib = otherVD.getAttributeCount(); iattrib < nattrib; iattrib++) { + int semantics = otherVD.getSemantics(iattrib); + int ncomps = VertexDescription.getComponentCount(semantics); + for (int iord = 0; iord < ncomps; iord++) { + Envelope1D intervalOther = other.queryInterval(semantics, iord); + Envelope1D interval = queryInterval(semantics, iord); + interval.merge(intervalOther); + setInterval(semantics, iord, interval); + } + } + } + + /** + * Merges this envelope with the point. The boundary of the envelope is + * increased to include the point. If the envelope is empty, the coordinates + * of the point to merge are assigned. If the point is empty, the original + * envelope stays unchanged. + * + * @param point + * The point to be merged with. + */ + public void merge(Point point) { + _touch(); + if (point.isEmptyImpl()) + return; + + VertexDescription pointVD = point.m_description; + if (m_description != pointVD) + mergeVertexDescription(pointVD); + + if (isEmpty()) { + _setFromPoint(point); + return; + } + + m_envelope.merge(point.getXY()); + for (int iattrib = 1, nattrib = pointVD.getAttributeCount(); iattrib < nattrib; iattrib++) { + int semantics = pointVD._getSemanticsImpl(iattrib); + int ncomps = VertexDescription.getComponentCount(semantics); + for (int iord = 0; iord < ncomps; iord++) { + double v = point.getAttributeAsDbl(semantics, iord); + Envelope1D interval = queryInterval(semantics, iord); + interval.merge(v); + setInterval(semantics, iord, interval); + } + } + } + + void _setFromPoint(Point centerPoint, double width, double height) { + m_envelope.setCoords(centerPoint.getXY(), width, height); + VertexDescription pointVD = centerPoint.m_description; + for (int iattrib = 1, nattrib = pointVD.getAttributeCount(); iattrib < nattrib; iattrib++) { + int semantics = pointVD._getSemanticsImpl(iattrib); + int ncomps = VertexDescription.getComponentCount(semantics); + for (int iord = 0; iord < ncomps; iord++) { + double v = centerPoint.getAttributeAsDbl(semantics, iord); + setInterval(semantics, iord, v, v); + } + } + } + + void _setFromPoint(Point centerPoint) { + m_envelope.setCoords(centerPoint.m_attributes[0], + centerPoint.m_attributes[1]); + VertexDescription pointVD = centerPoint.m_description; + for (int iattrib = 1, nattrib = pointVD.getAttributeCount(); iattrib < nattrib; iattrib++) { + int semantics = pointVD._getSemanticsImpl(iattrib); + int ncomps = VertexDescription.getComponentCount(semantics); + for (int iord = 0; iord < ncomps; iord++) { + double v = centerPoint.getAttributeAsDbl(semantics, iord); + setInterval(semantics, iord, v, v); + } + } + } + + void merge(Envelope2D other) { + _touch(); + m_envelope.merge(other); + } + + public void setInterval(int semantics, int ordinate, double vmin, + double vmax) { + setInterval(semantics, ordinate, new Envelope1D(vmin, vmax)); + } + + /** + * Re-aspects this envelope to fit within the specified width and height. + * + * @param arWidth + * The width within which to fit the envelope. + * @param arHeight + * The height within which to fit the envelope. + */ + public void reaspect(double arWidth, double arHeight) { + _touch(); + m_envelope.reaspect(arWidth, arHeight); + } + + /** + * Changes the dimensions of the envelope, preserving the center. New width + * is Width + 2 * dx, new height is Height + 2 * dy. If the result envelope + * width or height becomes negative, the envelope is set to be empty. + * + * @param dx + * The inflation along the X axis. + * @param dy + * The inflation along the Y axis. + */ + public void inflate(double dx, double dy) { + _touch(); + m_envelope.inflate(dx, dy); + } + + @Override + public void applyTransformation(Transformation2D transform) { + _touch(); + transform.transform(m_envelope); + } + + @Override + void applyTransformation(Transformation3D transform) { + _touch(); + if (!m_envelope.isEmpty()) { + Envelope3D env = new Envelope3D(); + queryEnvelope3D(env); + if (env.isEmptyZ()) + env.setEmpty(); // Z components is empty, the + // AffineTransformation3D makes the whole + // envelope empty. Consider + // throwing an assert instead. + else + transform.transform(env); + } + } + + @Override + public void copyTo(Geometry dst) { + if (dst.getType() != getType()) + throw new IllegalArgumentException(); + + Envelope envDst = (Envelope) dst; + dst._touch(); + envDst.m_description = m_description; + envDst.m_envelope.setCoords(m_envelope); + envDst._resizeAttributes(m_description._getTotalComponents() - 2); + _attributeCopy(m_attributes, 0, envDst.m_attributes, 0, + (m_description._getTotalComponents() - 2) * 2); + } + + @Override + public Geometry createInstance() { + return new Envelope(m_description); + } + + @Override + public double calculateArea2D() { + return m_envelope.getArea(); + } + + @Override + public double calculateLength2D() { + return m_envelope.getLength(); + } + + @Override + public Geometry.Type getType() { + return Type.Envelope; + } + + @Override + public int getDimension() { + return 2; + } + + @Override + public void queryEnvelope(Envelope env) { + copyTo(env); + } + + @Override + void queryEnvelope2D(Envelope2D env) { + env.xmin = m_envelope.xmin; + env.ymin = m_envelope.ymin; + env.xmax = m_envelope.xmax; + env.ymax = m_envelope.ymax; + } + + @Override + void queryEnvelope3D(Envelope3D env) { + env.xmin = m_envelope.xmin; + env.ymin = m_envelope.ymin; + env.xmax = m_envelope.xmax; + env.ymax = m_envelope.ymax; + env.setCoords(m_envelope.xmin, m_envelope.ymin, + _getAttributeAsDbl(0, Semantics.Z, 0), m_envelope.xmax, + m_envelope.ymax, _getAttributeAsDbl(1, Semantics.Z, 0)); + } + + @Override + public Envelope1D queryInterval(int semantics, int ordinate) { + Envelope1D env = new Envelope1D(); + env.setCoords(_getAttributeAsDbl(0, semantics, ordinate), + _getAttributeAsDbl(1, semantics, ordinate)); + return env; + } + + public void setInterval(int semantics, int ordinate, Envelope1D env) { + _touch(); + if (semantics == Semantics.POSITION) { + if (ordinate == 0) { + m_envelope.xmin = env.vmin; + m_envelope.xmax = env.vmax; + } else if (ordinate == 1) { + m_envelope.ymin = env.vmin; + m_envelope.ymax = env.vmax; + } else + throw new IndexOutOfBoundsException(); + } else { + _setAttributeAsDbl(0, semantics, ordinate, env.vmin); + _setAttributeAsDbl(1, semantics, ordinate, env.vmax); + } + } + + void queryCoordinates(Point2D[] dst) { + if (dst == null || dst.length < 4 || m_envelope.isEmpty()) + throw new IllegalArgumentException(); + + m_envelope.queryCorners(dst); + } + + /** + * Sets the point's coordinates to the coordinates of the envelope at the + * given corner. + * + * @param index + * The index of the envlope's corners from 0 to 3. + *

+ * 0 = lower left corner + *

+ * 1 = top-left corner + *

+ * 2 = top right corner + *

+ * 3 = bottom right corner + * @param ptDst + * The point whose coordinates are used to set the envelope's + * coordinate at a specified corner. + */ + public void queryCornerByVal(int index, Point ptDst) { + ptDst.assignVertexDescription(m_description); + int nattrib = getDescription().getAttributeCount() - 1; + switch (index) { + case 0: { + for (int i = 0; i < nattrib; i++) { + int semantics = m_description.getSemantics(i); + int ncomp = VertexDescription.getComponentCount(semantics); + for (int iord = 0; iord < ncomp; iord++) + ptDst.setAttribute(semantics, iord, + _getAttributeAsDbl(0, semantics, iord)); + } + ptDst.setXY(m_envelope.xmin, m_envelope.ymin); + return; + } + + case 1: { + for (int i = 0; i < nattrib; i++) { + int semantics = m_description.getSemantics(i); + int ncomp = VertexDescription.getComponentCount(semantics); + for (int iord = 0; iord < ncomp; iord++) + ptDst.setAttribute(semantics, iord, + _getAttributeAsDbl(1, semantics, iord)); + } + ptDst.setXY(m_envelope.xmin, m_envelope.ymax); + return; + } + case 2: { + for (int i = 0; i < nattrib; i++) { + int semantics = m_description.getSemantics(i); + int ncomp = VertexDescription.getComponentCount(semantics); + for (int iord = 0; iord < ncomp; iord++) + ptDst.setAttribute(semantics, iord, + _getAttributeAsDbl(0, semantics, iord)); + } + ptDst.setXY(m_envelope.xmax, m_envelope.ymax); + + return; + } + case 3: { + for (int i = 0; i < nattrib; i++) { + int semantics = m_description.getSemantics(i); + int ncomp = VertexDescription.getComponentCount(semantics); + for (int iord = 0; iord < ncomp; iord++) + ptDst.setAttribute(semantics, iord, + _getAttributeAsDbl(1, semantics, iord)); + } + ptDst.setXY(m_envelope.xmax, m_envelope.ymin); + return; + } + default: + throw new IndexOutOfBoundsException(); + } + } + + void queryCorner(int index, Point2D ptDst) { + Point2D p = m_envelope.queryCorner(index); + ptDst.setCoords(p.x, p.y); + } + + int getEndPointOffset(VertexDescription descr, int end_point) { + return end_point * (descr.getTotalComponentCount() - 2); + } + + double getAttributeAsDblImpl_(int end_point, int semantics, int ordinate) { + if (m_envelope.isEmpty()) + throw new GeometryException("empty geometry"); + + assert (end_point == 0 || end_point == 1); + + if (semantics == VertexDescription.Semantics.POSITION) { + if (end_point != 0) { + return ordinate != 0 ? m_envelope.ymax : m_envelope.xmax; + } else { + return ordinate != 0 ? m_envelope.ymin : m_envelope.xmin; + } + } + + int ncomps = VertexDescription.getComponentCount(semantics); + if (ordinate >= ncomps) + throw new IllegalArgumentException(); + + int attribute_index = m_description.getAttributeIndex(semantics); + if (attribute_index >= 0) { + return m_attributes[getEndPointOffset(m_description, end_point) + + m_description.getPointAttributeOffset_(attribute_index) + - 2 + ordinate]; + } + + return VertexDescription.getDefaultValue(semantics); + } + + void setAttributeAsDblImpl_(int end_point, int semantics, int ordinate, + double value) { + assert (end_point == 0 || end_point == 1); + + if (semantics == VertexDescription.Semantics.POSITION) { + if (end_point != 0) { + if (ordinate != 0) + m_envelope.ymax = value; + else + m_envelope.xmax = value; + } else { + if (ordinate != 0) + m_envelope.ymin = value; + else + m_envelope.xmin = value; + } + } + + int ncomps = VertexDescription.getComponentCount(semantics); + if (ordinate >= ncomps) + throw new IllegalArgumentException(); + + addAttribute(semantics); + int attribute_index = m_description.getAttributeIndex(semantics); + m_attributes[getEndPointOffset(m_description, end_point) + + m_description.getPointAttributeOffset_(attribute_index) - 2 + + ordinate] = value; + } + + void _resizeAttributes(int newSize) {// copied from + // Segment::_ResizeAttributes + _touch(); + if (m_attributes == null) { + m_attributes = new double[newSize * 2]; + } else if (m_attributes.length < newSize * 2) { + double[] newBuffer = new double[newSize * 2]; + System.arraycopy(m_attributes, 0, newBuffer, 0, m_attributes.length); + m_attributes = newBuffer; + } + } + + @Override + void _beforeDropAttributeImpl(int semantics) {// copied from + // Segment::_BeforeDropAttributeImpl + if (m_envelope.isEmpty()) + return; + + // _ASSERT(semantics != enum_value2(VertexDescription, Semantics, + // POSITION)); + int attributeIndex = m_description.getAttributeIndex(semantics); + int offset = m_description._getPointAttributeOffset(attributeIndex) - 2; + int comps = VertexDescription.getComponentCount(semantics); + int totalCompsOld = m_description._getTotalComponents() - 2; + if (totalCompsOld > comps) { + int offset0 = _getEndPointOffset(0); + for (int i = offset + comps; i < totalCompsOld * 2; i++) + m_attributes[offset0 + i - comps] = m_attributes[offset0 + i]; + + int offset1 = _getEndPointOffset(1) - comps; // -comp is for deleted + // attribute of + // start vertex + for (int i = offset + comps; i < totalCompsOld; i++) + m_attributes[offset1 + i - comps] = m_attributes[offset1 + i]; + } + } + + @Override + void _afterAddAttributeImpl(int semantics) {// copied from + // Segment::_AfterAddAttributeImpl + int attributeIndex = m_description.getAttributeIndex(semantics); + int offset = m_description._getPointAttributeOffset(attributeIndex) - 2; + int comps = VertexDescription.getComponentCount(semantics); + int totalComps = m_description._getTotalComponents() - 2; + _resizeAttributes(totalComps); + int totalCompsOld = totalComps - comps; // the total number of + // components before resize. + + int offset0 = _getEndPointOffset(0); + int offset1 = _getEndPointOffset(1); + int offset1old = offset1 - comps; + for (int i = totalCompsOld - 1; i >= 0; i--) {// correct the position of + // the End attributes + m_attributes[offset1 + i] = m_attributes[offset1old + i]; + } + + // move attributes for start end end points that go after the insertion + // point + for (int i = totalComps - 1; i >= offset + comps; i--) { + m_attributes[offset0 + i] = m_attributes[offset0 + i - comps]; + m_attributes[offset1 + i] = m_attributes[offset1 + i - comps]; + } + + // initialize added attribute to the default value. + double dv = VertexDescription.getDefaultValue(semantics); + for (int i = 0; i < comps; i++) { + m_attributes[offset0 + offset + i] = dv; + m_attributes[offset1 + offset + i] = dv; + } + } + + static void _attributeCopy(double[] src, int srcStart, double[] dst, + int dstStart, int count) { + // FIXME performance!!!! + // System.arraycopy(src, srcStart, dst, dstStart, count); + for (int i = 0; i < count; i++) + dst[dstStart + i] = src[i + srcStart]; + } + + double _getAttributeAsDbl(int endPoint, int semantics, int ordinate) { + if (m_envelope.isEmpty()) + throw new GeometryException( + "This operation was performed on an Empty Geometry."); + + // _ASSERT(endPoint == 0 || endPoint == 1); + + if (semantics == Semantics.POSITION) { + if (endPoint != 0) { + return ordinate != 0 ? m_envelope.ymax : m_envelope.xmax; + } else { + return ordinate != 0 ? m_envelope.ymin : m_envelope.xmin; + } + } + + int ncomps = VertexDescription.getComponentCount(semantics); + if (ordinate >= ncomps) + throw new IndexOutOfBoundsException(); + + int attributeIndex = m_description.getAttributeIndex(semantics); + if (attributeIndex >= 0) { + if (null != m_attributes) + _resizeAttributes(m_description._getTotalComponents() - 2); + + return m_attributes[_getEndPointOffset(endPoint) + + m_description._getPointAttributeOffset(attributeIndex) + - 2 + ordinate]; + } else + return VertexDescription.getDefaultValue(semantics); + } + + void _setAttributeAsDbl(int endPoint, int semantics, int ordinate, + double value) { + _touch(); + // _ASSERT(endPoint == 0 || endPoint == 1); + + if (semantics == Semantics.POSITION) { + if (endPoint != 0) { + if (ordinate != 0) + m_envelope.ymax = value; + else + m_envelope.xmax = value; + } else { + if (ordinate != 0) + m_envelope.ymin = value; + else + m_envelope.xmin = value; + } + } + + int ncomps = VertexDescription.getComponentCount(semantics); + if (ordinate >= ncomps) + throw new IndexOutOfBoundsException(); + + if (!hasAttribute(semantics)) { + if (VertexDescription.isDefaultValue(semantics, value)) + return; + addAttribute(semantics); + } + + int attributeIndex = m_description.getAttributeIndex(semantics); + if (null == m_attributes) + _resizeAttributes(m_description._getTotalComponents() - 2); + + m_attributes[_getEndPointOffset(endPoint) + + m_description._getPointAttributeOffset(attributeIndex) - 2 + + ordinate] = value; + } + + int _getAttributeAsInt(int endPoint, int semantics, int ordinate) { + return (int) _getAttributeAsDbl(endPoint, semantics, ordinate); + } + + int _getEndPointOffset(int endPoint) { + return endPoint * (m_description._getTotalComponents() - 2); + } + + boolean isIntersecting(Envelope2D other) { + return m_envelope.isIntersecting(other); + } + + /** + * Changes this envelope to be the intersection of itself with the other + * envelope. + * + * @param other + * The envelope to intersect with. + * @return Returns true if the result is not empty. + */ + public boolean intersect(Envelope other) { + _touch(); + Envelope2D e2d = new Envelope2D(); + other.queryEnvelope2D(e2d); + return m_envelope.intersect(e2d); + } + + /** + * Returns true if the envelope and the other given envelope intersect. + * + * @param other + * The envelope to test intersection with. + * @return Returns true if the two envelopes are intersecting. + */ + public boolean isIntersecting(Envelope other) {// TODO: attributes. + return m_envelope.isIntersecting(other.m_envelope); + } + + /** + * Sets the envelope's corners to be centered around the specified point, + * using it's center, width, and height. + * + * @param c + * The point around which to center the envelope. + * @param w + * The width to be set for the envelope. + * @param h + * The height to be set for this envelope. + */ + public void centerAt(Point c, double w, double h) { + _touch(); + if (c.isEmpty()) { + setEmpty(); + return; + } + + _setFromPoint(c, w, h); + } + + /** + * Offsets the envelope by the specified distances along x and y + * coordinates. + * + * @param dx + * The X offset to be applied. + * @param dy + * The Y offset to be applied. + */ + public void offset(double dx, double dy) { + _touch(); + m_envelope.offset(dx, dy); + } + + /** + * Normalizes envelopes if the minimum dimension is larger then then the + * maximum dimension. + */ + public void normalize() {// TODO: attributes + _touch(); + m_envelope.normalize(); + } + + /** + * Gets the center point of the envelope. The center point occurs at: ((XMin + * + XMax) / 2, (YMin + YMax) / 2). + * + * @return The center point of the envelope. + */ + Point2D getCenter2D() { + return m_envelope.getCenter(); + } + + /** + * Returns the center point of the envelope. + * + * @return The center point of the envelope. + */ + public Point getCenter() { + Point pointOut = new Point(m_description); + if (isEmpty()) { + return pointOut; + } + int nattrib = m_description.getAttributeCount(); + for (int i = 1; i < nattrib; i++) { + int semantics = m_description._getSemanticsImpl(i); + int ncomp = VertexDescription.getComponentCount(semantics); + for (int iord = 0; iord < ncomp; iord++) { + double v = 0.5 * (_getAttributeAsDbl(0, semantics, iord) + _getAttributeAsDbl( + 1, semantics, iord)); + pointOut.setAttribute(semantics, iord, v); + } + } + pointOut.setXY(m_envelope.getCenterX(), m_envelope.getCenterY()); + return pointOut; + } + + /** + * Centers the envelope around the specified point preserving the envelope's + * width and height. + * + * @param c + * The new center point. + */ + public void centerAt(Point c) { + _touch(); + if (c.isEmpty()) { + setEmpty(); + return; + } + m_envelope.centerAt(c.getX(), c.getY()); + } + + /** + * Returns the envelope's lower left corner point. + * + * @return Returns the lower left corner point. + */ + public Point getLowerLeft() { + return m_envelope.getLowerLeft(); + } + + /** + * Returns the envelope's upper right corner point. + * + * @return Returns the upper right corner point. + */ + public Point getUpperRight() { + return m_envelope.getUpperRight(); + } + + /** + * Returns the envelope's lower right corner point. + * + * @return Returns the lower right corner point. + */ + public Point getLowerRight() { + return m_envelope.getLowerRight(); + } + + /** + * Returns the envelope's upper left corner point. + * + * @return Returns the upper left corner point. + */ + public Point getUpperLeft() { + return m_envelope.getUpperLeft(); + } + + /** + * Checks if this envelope contains (covers) the specified point. + * + * @param p + * The Point to be tested for coverage. + * @return TRUE if this envelope contains (covers) the specified point. + */ + public boolean contains(Point p) { + if (p.isEmpty()) + return false; + return m_envelope.contains(p.getX(), p.getY()); + } + + /** + * Checks if this envelope contains (covers) other envelope. + * + * @param env + * The envelope to be tested for coverage. + * @return TRUE if this envelope contains (covers) the specified envelope. + */ + public boolean contains(Envelope env) { + return m_envelope.contains(env.m_envelope); + } + + /** + * Returns TRUE when this geometry has exactly same type, properties, and + * coordinates as the other geometry. + */ + @Override + public boolean equals(Object _other) { + if (_other == this) + return true; + + if (!(_other instanceof Envelope)) + return false; + + Envelope other = (Envelope) _other; + + if (m_description != other.m_description) + return false; + + if (isEmpty()) + if (other.isEmpty()) + return true; + else + return false; + + if (!this.m_envelope.equals(other.m_envelope)) + return false; + + for (int i = 0, n = (m_description._getTotalComponents() - 2) * 2; i < n; i++) + if (m_attributes[i] != other.m_attributes[i]) + return false; + + return true; + } + + /** + * Returns a hash code value for this envelope. + * + * @return A hash code value for this envelope. + */ + @Override + public int hashCode() { + int hashCode = m_description.hashCode(); + hashCode = NumberUtils.hash(hashCode, m_envelope.hashCode()); + if (!isEmpty() && m_attributes != null) { + for (int i = 0, n = (m_description._getTotalComponents() - 2) * 2; i < n; i++) { + hashCode = NumberUtils.hash(hashCode, m_attributes[i]); + } + } + return hashCode; + } + + /** + * Returns the X coordinate of the left corners of the envelope. + * + * @return The X coordinate of the left corners. + */ + public final double getXMin() { + return m_envelope.xmin; + } + + /** + * Returns the Y coordinate of the bottom corners of the envelope. + * + * @return The Y coordinate of the bottom corners. + */ + public final double getYMin() { + return m_envelope.ymin; + } + + /** + * Returns the X coordinate of the right corners of the envelope. + * + * @return The X coordinate of the right corners. + */ + public final double getXMax() { + return m_envelope.xmax; + } + + /** + * Returns the Y coordinate of the top corners of the envelope. + * + * @return The Y coordinate of the top corners. + */ + public final double getYMax() { + return m_envelope.ymax; + } + + /** + * Sets the left X coordinate. + * + * @param x + * The X coordinate of the left corner + */ + public void setXMin(double x) { + _touch(); + m_envelope.xmin = x; + } + + /** + * Sets the right X coordinate. + * + * @param x + * The X coordinate of the right corner. + */ + public void setXMax(double x) { + _touch(); + m_envelope.xmax = x; + } + + /** + * Sets the bottom Y coordinate. + * + * @param y + * the Y coordinate of the bottom corner. + */ + public void setYMin(double y) { + _touch(); + m_envelope.ymin = y; + } + + /** + * Sets the top Y coordinate. + * + * @param y + * The Y coordinate of the top corner. + */ + public void setYMax(double y) { + _touch(); + m_envelope.ymax = y; + } +} diff --git a/src/com/esri/core/geometry/Envelope1D.java b/src/com/esri/core/geometry/Envelope1D.java new file mode 100644 index 00000000..762531b1 --- /dev/null +++ b/src/com/esri/core/geometry/Envelope1D.java @@ -0,0 +1,192 @@ +/* + Copyright 1995-2013 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ + + +package com.esri.core.geometry; + +/** + * A One-dimensional interval + */ +public final class Envelope1D { + + public double vmin; + + public double vmax; + + public Envelope1D() { + + } + + public Envelope1D(double _vmin, double _vmax) { + setCoords(_vmin, _vmax); + } + + public void setCoords(double _vmin, double _vmax) { + vmin = _vmin; + vmax = _vmax; + normalize(); + } + + void normalize() { + if (NumberUtils.isNaN(vmin)) + return; + if (vmin > vmax) { + double v = vmin; + vmin = vmax; + vmax = v; + } + if (NumberUtils.isNaN(vmax))// vmax can be NAN + { + setEmpty(); + } + } + + public void setEmpty() { + vmin = NumberUtils.NaN(); + } + + public boolean isEmpty() { + return NumberUtils.isNaN(vmin); + } + + public void setInfinite() { + vmin = NumberUtils.negativeInf(); + vmax = NumberUtils.positiveInf(); + } + + public void merge(double v) { + if (isEmpty()) { + vmin = v; + vmax = v; + return; + } + + // no need to check for NaN, because all comparisons with NaN are false. + mergeNE(v); + } + + public void merge(Envelope1D other) { + if (other.isEmpty()) + return; + + if (isEmpty()) { + vmin = other.vmin; + vmax = other.vmax; + return; + } + + if (vmin > other.vmin) + vmin = other.vmin; + if (vmax < other.vmax) + vmax = other.vmax; + + if (vmin > vmax) + setEmpty(); + } + + public void mergeNE(double v) { + // Note, if v is NaN, vmin and vmax are unchanged + if (v < vmin) + vmin = v; + else if (v > vmax) + vmax = v; + } + + public boolean contains(double v) { + // If vmin is NaN, return false. No need to check for isEmpty. + return v >= vmin && v <= vmax; + } + + /** + * Returns True if the envelope contains the other envelope (boundary + * inclusive). Note: Will return false if either envelope is empty. + */ + public boolean contains(/* const */Envelope1D other) /* const */ + { + return other.vmin >= vmin && other.vmax <= vmax; + } + + public void intersect(Envelope1D other) { + if (isEmpty() || other.isEmpty()) { + setEmpty(); + return; + } + + if (vmin < other.vmin) + vmin = other.vmin; + if (vmax > other.vmax) + vmax = other.vmax; + + if (vmin > vmax) + setEmpty(); + } + + public void inflate(double delta) { + if (isEmpty()) + return; + + vmin -= delta; + vmax += delta; + if (vmax < vmin) + setEmpty(); + } + + double _calculateToleranceFromEnvelope() { + if (isEmpty()) + return NumberUtils.doubleEps() * 100.0; // GEOMTERYX_EPSFACTOR + // 100.0; + double r = Math.abs(vmin) + Math.abs(vmax) + 1; + return r * NumberUtils.doubleEps() * 100.0; // GEOMTERYX_EPSFACTOR + // 100.0; + } + + void normalizeNoNaN_() { + if (vmin > vmax) { + double v = vmin; + vmin = vmax; + vmax = v; + } + } + + void setCoordsNoNaN_(double vmin_, double vmax_) { + vmin = vmin_; + vmax = vmax_; + normalizeNoNaN_(); + } + + double snapClip(double v) /* const */ + { + return NumberUtils.snap(v, vmin, vmax); + } + + public double getWidth() /* const */ + { + return vmax - vmin; + } + + public double getCenter() /* const */ + { + return 0.5 * (vmin + vmax); + } +} diff --git a/src/com/esri/core/geometry/Envelope2D.java b/src/com/esri/core/geometry/Envelope2D.java new file mode 100644 index 00000000..5d1f237a --- /dev/null +++ b/src/com/esri/core/geometry/Envelope2D.java @@ -0,0 +1,1075 @@ +/* + Copyright 1995-2013 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ + +package com.esri.core.geometry; + +/** + * A class that represents axis parallel 2D rectangle. + */ +final class Envelope2D { + + private final int XLESSXMIN = 1; + // private final int XGREATERXMAX = 2; + private final int YLESSYMIN = 4; + // private final int YGREATERYMAX = 8; + private final int XMASK = 3; + private final int YMASK = 12; + + public double xmin; + + public double ymin; + + public double xmax; + + public double ymax; + + public static Envelope2D construct(double _xmin, double _ymin, + double _xmax, double _ymax) { + Envelope2D env = new Envelope2D(); + env.xmin = _xmin; + env.ymin = _ymin; + env.xmax = _xmax; + env.ymax = _ymax; + return env; + } + + public Envelope2D() { + setEmpty(); + } + + public Envelope2D(double _xmin, double _ymin, double _xmax, double _ymax) { + xmin = _xmin; + ymin = _ymin; + xmax = _xmax; + ymax = _ymax; + } + + public void setCoords(double _x, double _y) { + xmin = _x; + ymin = _y; + xmax = _x; + ymax = _y; + } + + public void setCoords(double _xmin, double _ymin, double _xmax, double _ymax) { + xmin = _xmin; + ymin = _ymin; + xmax = _xmax; + ymax = _ymax; + normalize(); + } + + public void setCoords(Point2D center, double width, double height) { + xmin = center.x - width * 0.5; + xmax = xmin + width; + ymin = center.y - height * 0.5; + ymax = ymin + height; + normalize(); + } + + void setCoords(Point2D pt) { + xmin = pt.x; + ymin = pt.y; + xmax = pt.x; + ymax = pt.y; + } + + public void setCoords(Envelope2D envSrc) { + setCoords(envSrc.xmin, envSrc.ymin, envSrc.xmax, envSrc.ymax); + } + + Envelope2D getInflated(double dx, double dy) { + Envelope2D env = new Envelope2D(); + env.setCoords(this.xmin, this.ymin, this.xmax, this.ymax); + env.inflate(dx, dy); + return env; + } + + /** + * Sets the envelope from the array of points. The envelope will be set to + * empty if the array is null. + */ + // TODO This should either call setFromPoints(points, int count) or vice + // versa + public void setFromPoints(Point2D[] points) { + if (points == null || points.length == 0) { + setEmpty(); + return; + } + + Point2D pt = points[0]; + setCoords(pt.x, pt.y); + for (int i = 1; i < points.length; i++) { + Point2D pt2d = points[i]; + mergeNE(pt2d.x, pt2d.y); + } + } + + public void setEmpty() { + xmin = NumberUtils.TheNaN; + ymin = NumberUtils.TheNaN; + xmax = NumberUtils.TheNaN; + ymax = NumberUtils.TheNaN; + } + + public void setInfinite() { + xmin = NumberUtils.negativeInf(); + xmax = NumberUtils.positiveInf(); + ymin = NumberUtils.negativeInf(); + ymax = NumberUtils.positiveInf(); + } + + public boolean isEmpty() { + return NumberUtils.isNaN(xmin); + } + + public void setCoords(Envelope1D xinterval, Envelope1D yinterval) { + if (xinterval.isEmpty() || yinterval.isEmpty()) { + setEmpty(); + return; + } + + xmin = xinterval.vmin; + xmax = xinterval.vmax; + ymin = yinterval.vmin; + ymax = yinterval.vmax; + } + + public void merge(double x, double y) { + if (isEmpty()) { + xmin = x; + ymin = y; + xmax = x; + ymax = y; + } else { + if (xmin > x) + xmin = x; + else if (xmax < x) + xmax = x; + + if (ymin > y) + ymin = y; + else if (ymax < y) + ymax = y; + } + } + + /** + * Merges a point with this envelope without checkin if the envelope is + * empty. Use with care. + */ + public void mergeNE(double x, double y) { + if (xmin > x) + xmin = x; + else if (xmax < x) + xmax = x; + + if (ymin > y) + ymin = y; + else if (ymax < y) + ymax = y; + } + + public void merge(Point2D pt) { + merge(pt.x, pt.y); + } + + public void merge(Point3D pt) { + merge(pt.x, pt.y); + } + + public void merge(Envelope2D other) { + if (other.isEmpty()) + return; + + merge(other.xmin, other.ymin); + merge(other.xmax, other.ymax); + } + + public void inflate(double dx, double dy) { + if (isEmpty()) + return; + xmin -= dx; + xmax += dx; + ymin -= dy; + ymax += dy; + if (xmin > xmax || ymin > ymax) + setEmpty(); + } + + public void scale(double f) { + if (f < 0.0) + setEmpty(); + + if (isEmpty()) + return; + + xmin *= f; + xmax *= f; + ymin *= f; + ymax *= f; + } + + public void zoom(double factorX, double factorY) { + if (!isEmpty()) + setCoords(getCenter(), factorX * getWidth(), factorY * getHeight()); + } + + /** + * Returns True if this envelope intersects the other. + */ + public boolean isIntersecting(Envelope2D other) { + return !isEmpty() + && !other.isEmpty() + && ((xmin <= other.xmin) ? xmax >= other.xmin + : other.xmax >= xmin) + && // check that x projections overlap + ((ymin <= other.ymin) ? ymax >= other.ymin : other.ymax >= ymin); // check + // that + // y + // projections + // overlap + } + + /** + * Returns True if this envelope intersects the other. Assumes this and + * other envelopes are not empty. + */ + public boolean isIntersectingNE(Envelope2D other) { + return ((xmin <= other.xmin) ? xmax >= other.xmin : other.xmax >= xmin) + && // check that x projections overlap + ((ymin <= other.ymin) ? ymax >= other.ymin : other.ymax >= ymin); // check + // that + // y + // projections + // overlap + } + + /** + * Intersects this envelope with the other. Returns True if the result is + * not empty. + */ + + public boolean intersect(Envelope2D other) { + if (isEmpty() || other.isEmpty()) + return false; + + if (other.xmin > xmin) + xmin = other.xmin; + + if (other.xmax < xmax) + xmax = other.xmax; + + if (other.ymin > ymin) + ymin = other.ymin; + + if (other.ymax < ymax) + ymax = other.ymax; + + boolean bIntersecting = xmin <= xmax && ymin <= ymax; + + if (!bIntersecting) + setEmpty(); + + return bIntersecting; + } + + /** + * Returns a corner of the envelope. Starts from the lowerleft corner and + * goes clockwise. + */ + public Point2D queryCorner(int index) { + switch (index) { + case 0: + return Point2D.construct(xmin, ymin); + case 1: + return Point2D.construct(xmin, ymax); + case 2: + return Point2D.construct(xmax, ymax); + case 3: + return Point2D.construct(xmax, ymin); + default: + throw new IndexOutOfBoundsException(); + + } + } + + /** + * Queries corners into a given array. The array length must have at least + * 4. Starts from the lowerleft corner and goes clockwise. + */ + public void queryCorners(Point2D[] corners) { + if ((corners == null) || (corners.length < 4)) + throw new IllegalArgumentException(); + corners[0] = new Point2D(xmin, ymin); + corners[1] = new Point2D(xmin, ymax); + corners[2] = new Point2D(xmax, ymax); + corners[3] = new Point2D(xmax, ymin); + } + + /** + * Queries corners into a given array in reversed order. The array length + * must have at least 4. Starts from the lowerleft corner and goes + * counterclockwise. + */ + public void queryCornersReversed(Point2D[] corners) { + if (corners == null || ((corners != null) && (corners.length < 4))) + throw new IllegalArgumentException(); + corners[0] = new Point2D(xmin, ymin); + corners[1] = new Point2D(xmax, ymin); + corners[2] = new Point2D(xmax, ymax); + corners[3] = new Point2D(xmin, ymax); + } + + public double getArea() { + if (isEmpty()) + return 0; + return getWidth() * getHeight(); + } + + public double getLength() { + if (isEmpty()) + return 0; + return 2.0 * (getWidth() + getHeight()); + } + + public void setFromPoints(Point2D[] points, int count) { + if (count == 0) { + setEmpty(); + return; + } + xmin = points[0].x; + ymin = points[0].y; + xmax = xmin; + ymax = ymin; + for (int i = 1; i < count; i++) { + Point2D pt = points[i]; + if (pt.x < xmin) + xmin = pt.x; + else if (pt.x > xmax) + xmax = pt.x; + if (pt.y < ymin) + ymin = pt.y; + else if (pt.y > ymax) + ymax = pt.y; + } + } + + public void reaspect(double arWidth, double arHeight) { + if (isEmpty()) + return; + double newAspectRatio = arWidth / arHeight; + double widthHalf = getWidth() * 0.5; + double heightHalf = getHeight() * 0.5; + + double newWidthHalf = heightHalf * newAspectRatio; + if (widthHalf <= newWidthHalf) {// preserve height, increase width + double xc = getCenterX(); + xmin = xc - newWidthHalf; + xmax = xc + newWidthHalf; + } else {// preserve the width, increase height + double newHeightHalf = widthHalf / newAspectRatio; + double yc = getCenterY(); + ymin = yc - newHeightHalf; + ymax = yc + newHeightHalf; + } + + normalize(); + } + + public double getCenterX() { + double cx = (xmax + xmin) / 2d; + return cx; + } + + public double getCenterY() { + double cy = (ymax + ymin) / 2d; + return cy; + } + + public double getWidth() { + return xmax - xmin; + } + + public double getHeight() { + return ymax - ymin; + } + + // public Envelope2D getCopy() { + // Envelope2D ret = new Envelope2D(xmin, ymin, xmax, ymax); + // return ret; + // } + + /** + * Moves the Envelope by given distance. + */ + public void move(double dx, double dy) { + if (isEmpty()) + return; + xmin += dx; + ymin += dy; + xmax += dx; + ymax += dy; + } + + public void centerAt(double x, double y) { + move(x - getCenterX(), y - getCenterY()); + } + + public void centerAt(Point2D pt) { + centerAt(pt.x, pt.y); + } + + public void offset(double dx, double dy) { + xmin += dx;// NaN remains NaN + xmax += dx; + ymin += dy; + ymax += dy; + } + + public void normalize() { + if (isEmpty()) + return; + + double min = Math.min(xmin, xmax); + double max = Math.max(xmin, xmax); + xmin = min; + xmax = max; + min = Math.min(ymin, ymax); + max = Math.max(ymin, ymax); + ymin = min; + ymax = max; + } + + void queryLowerLeft(Point2D pt) { + pt.setCoords(xmin, ymin); + } + + void queryLowerRight(Point2D pt) { + pt.setCoords(xmax, ymin); + } + + void queryUpperLeft(Point2D pt) { + pt.setCoords(xmin, ymax); + } + + void queryUpperRight(Point2D pt) { + pt.setCoords(xmax, ymax); + } + + /** + * Returns True if this envelope is valid (empty, or has xmin less or equal + * to xmax, or ymin less or equal to ymax). + */ + public boolean isValid() { + return isEmpty() || (xmin <= xmax && ymin <= ymax); + } + + /** + * Gets the center point of the envelope. The Center Point occurs at: ((XMin + * + XMax) / 2, (YMin + YMax) / 2). + * + * @return the center point + */ + public Point2D getCenter() { + return new Point2D((xmax + xmin) / 2d, (ymax + ymin) / 2d); + } + + public void queryCenter(Point2D center) { + center.x = (xmax + xmin) / 2d; + center.y = (ymax + ymin) / 2d; + } + + public void centerAt(Point c) { + double cx = (xmax - xmin) / 2d; + double cy = (ymax - ymin) / 2d; + + xmin = c.getX() - cx; + xmax = c.getX() + cx; + ymin = c.getY() - cy; + ymax = c.getY() + cy; + } + + public Point getLowerLeft() { + return new Point(xmin, ymin); + } + + public Point getUpperLeft() { + return new Point(xmin, ymax); + } + + public Point getLowerRight() { + return new Point(xmax, ymin); + } + + public Point getUpperRight() { + return new Point(xmax, ymax); + } + + public boolean contains(Point p) { + return contains(p.getX(), p.getY()); + } + + public boolean contains(Point2D p) { + return contains(p.x, p.y); + } + + public boolean contains(double x, double y) { + return (!isEmpty() && (x >= xmin && x <= xmax && y >= ymin && y <= ymax)); + } + + /** + * Returns True if the envelope contains the other envelope (boundary + * inclusive). + */ + public boolean contains(Envelope2D other) {// Note: Will return False, if + // either envelope is empty. + return other.xmin >= xmin && other.xmax <= xmax && other.ymin >= ymin + && other.ymax <= ymax; + } + + /** + * Returns True if the envelope contains the point (boundary exclusive). + */ + boolean containsExclusive(double x, double y) { + // Note: This will return False, if envelope is empty, thus no need to + // call is_empty(). + return x > xmin && x < xmax && y > ymin && y < ymax; + } + + /** + * Returns True if the envelope contains the point (boundary exclusive). + */ + boolean containsExclusive(Point2D pt) { + return contains(pt.x, pt.y); + } + + /** + * Returns True if the envelope contains the other envelope (boundary + * exclusive). + */ + boolean containsExclusive(Envelope2D other) { + // Note: This will return False, if either envelope is empty, thus no + // need to call is_empty(). + return other.xmin > xmin && other.xmax < xmax && other.ymin > ymin + && other.ymax < ymax; + } + + @Override + public boolean equals(Object _other) { + if (_other == this) + return true; + + if (!(_other instanceof Envelope2D)) + return false; + + Envelope2D other = (Envelope2D) _other; + if (isEmpty() && other.isEmpty()) + return true; + + if (xmin != other.xmin || ymin != other.ymin || xmax != other.xmax + || ymax != other.ymax) + return false; + + return true; + } + + @Override + public int hashCode() { + + long bits = Double.doubleToLongBits(xmin); + int hc = (int) (bits ^ (bits >>> 32)); + + int hash = NumberUtils.hash(hc); + + bits = Double.doubleToLongBits(xmax); + hc = (int) (bits ^ (bits >>> 32)); + hash = NumberUtils.hash(hash, hc); + + bits = Double.doubleToLongBits(ymin); + hc = (int) (bits ^ (bits >>> 32)); + hash = NumberUtils.hash(hash, hc); + + bits = Double.doubleToLongBits(ymax); + hc = (int) (bits ^ (bits >>> 32)); + hash = NumberUtils.hash(hash, hc); + + return hash; + } + + Point2D _snapToBoundary(Point2D pt) { + Point2D p = new Point2D(); + p.setCoords(pt); + if (p._isNan()) + return p; + + if (isEmpty()) { + p._setNan(); + return p; + } + + if (p.x < xmin) + p.x = xmin; + else if (p.x > xmax) + p.x = xmax; + + if (p.y < ymin) + p.y = ymin; + else if (p.y > ymax) + p.y = ymax; + + if (!p.equals(pt)) + return p; + + // p is inside envelope + Point2D center = getCenter(); + double deltax = p.x < center.x ? p.x - xmin : xmax - p.x; + double deltay = p.y < center.y ? p.y - ymin : ymax - p.y; + + if (deltax < deltay) + p.x = p.x < center.x ? xmin : xmax; + else + p.y = p.y < center.y ? ymin : ymax; + + return p; + } + + // Calculates distance of point from lower left corner of envelope, + // moving clockwise along the envelope boundary. + // The input point is assumed to lie exactly on envelope boundary + // If this is not the case then a projection to the nearest position on the + // envelope boundary is performed. + // (If the user knows that the input point does most likely not lie on the + // boundary, + // it is more efficient to perform ProjectToBoundary before using this + // function). + public double _boundaryDistance(Point2D pt) { + if (isEmpty()) + return NumberUtils.NaN(); + + if (pt.x == xmin) + return pt.y - ymin; + + double height = ymax - ymin; + double width = xmax - xmin; + + if (pt.y == ymax) + return height + pt.x - xmin; + + if (pt.x == xmax) + return height + width + ymax - pt.y; + + if (pt.y == ymin) + return height * 2.0 + width + xmax - pt.x; + + return _boundaryDistance(_snapToBoundary(pt)); + } + + // returns 0,..3 depending on which side pt lies. + public int _envelopeSide(Point2D pt) { + + if (isEmpty()) + return -1; + + double boundaryDist = _boundaryDistance(pt); + double height = ymax - ymin; + double width = xmax - xmin; + + if (boundaryDist < height) + return 0; + + if ((boundaryDist -= height) < width) + return 1; + + return boundaryDist - width < height ? 2 : 3; + } + + double _calculateToleranceFromEnvelope() { + if (isEmpty()) + return NumberUtils.doubleEps() * 100.0; // GEOMTERYX_EPSFACTOR + // 100.0; + double r = Math.abs(xmin) + Math.abs(xmax) + Math.abs(ymin) + + Math.abs(ymax) + 1; + return r * NumberUtils.doubleEps() * 100.0; // GEOMTERYX_EPSFACTOR + // 100.0; + } + + int clipLine(Point2D p1, Point2D p2) + // Modified Cohen-Sutherland Line-Clipping Algorithm + // returns: + // 0 - the segment is outside of the clipping window + // 1 - p1 was modified + // 2 - p2 was modified + // 3 - p1 and p2 were modified + // 4 - the segment is complitely inside of the clipping window + { + int c1 = _clipCode(p1), c2 = _clipCode(p2); + + if ((c1 & c2) != 0)// (c1 & c2) + return 0; + + if ((c1 | c2) == 0)// (!(c1 | c2)) + return 4; + + final int res = ((c1 != 0) ? 1 : 0) | ((c2 != 0) ? 2 : 0);// (c1 ? 1 : + // 0) | (c2 + // ? 2 : 0); + + do { + double dx = p2.x - p1.x, dy = p2.y - p1.y; + + boolean bDX = dx > dy; + + if (bDX) { + if ((c1 & XMASK) != 0)// (c1 & XMASK) + { + if ((c1 & XLESSXMIN) != 0)// (c1 & XLESSXMIN) + { + p1.y += dy * (xmin - p1.x) / dx; + p1.x = xmin; + } else { + p1.y += dy * (xmax - p1.x) / dx; + p1.x = xmax; + } + + c1 = _clipCode(p1); + } else if ((c2 & XMASK) != 0)// (c2 & XMASK) + { + if ((c2 & XLESSXMIN) != 0) { + p2.y += dy * (xmin - p2.x) / dx; + p2.x = xmin; + } else { + p2.y += dy * (xmax - p2.x) / dx; + p2.x = xmax; + } + + c2 = _clipCode(p2); + } else if (c1 != 0)// (c1) + { + if ((c1 & YLESSYMIN) != 0)// (c1 & YLESSYMIN) + { + p1.x += dx * (ymin - p1.y) / dy; + p1.y = ymin; + } else { + p1.x += dx * (ymax - p1.y) / dy; + p1.y = ymax; + } + + c1 = _clipCode(p1); + } else { + if ((c2 & YLESSYMIN) != 0)// (c2 & YLESSYMIN) + { + p2.x += dx * (ymin - p2.y) / dy; + p2.y = ymin; + } else { + p2.x += dx * (ymax - p2.y) / dy; + p2.y = ymax; + } + + c2 = _clipCode(p2); + } + } else { + if ((c1 & YMASK) != 0)// (c1 & YMASK) + { + if ((c1 & YLESSYMIN) != 0)// (c1 & YLESSYMIN) + { + p1.x += dx * (ymin - p1.y) / dy; + p1.y = ymin; + } else { + p1.x += dx * (ymax - p1.y) / dy; + p1.y = ymax; + } + + c1 = _clipCode(p1); + } else if ((c2 & YMASK) != 0)// (c2 & YMASK) + { + if ((c2 & YLESSYMIN) != 0) // (c2 & YLESSYMIN) + { + p2.x += dx * (ymin - p2.y) / dy; + p2.y = ymin; + } else { + p2.x += dx * (ymax - p2.y) / dy; + p2.y = ymax; + } + + c2 = _clipCode(p2); + } else if (c1 != 0)// (c1) + { + if ((c1 & XLESSXMIN) != 0)// (c1 & XLESSXMIN) + { + p1.y += dy * (xmin - p1.x) / dx; + p1.x = xmin; + } else { + p1.y += dy * (xmax - p1.x) / dx; + p1.x = xmax; + } + + c1 = _clipCode(p1); + } else { + if ((c2 & XLESSXMIN) != 0)// (c2 & XLESSXMIN) + { + p2.y += dy * (xmin - p2.x) / dx; + p2.x = xmin; + } else { + p2.y += dy * (xmax - p2.x) / dx; + p2.x = xmax; + } + + c2 = _clipCode(p2); + } + + /* + * if (c1) //original code. Faster, but less robust numerically. + * ( //The Cohen-Sutherland Line-Clipping Algorithm) { if (c1 & + * XLESSXMIN) { p1.y += dy * (xmin - p1.x) / dx; p1.x = xmin; } + * else if (c1 & XGREATERXMAX) { p1.y += dy * (xmax - p1.x) / + * dx; p1.x = xmax; } else if (c1 & YLESSYMIN) { p1.x += dx * + * (ymin - p1.y) / dy; p1.y = ymin; } else if (c1 & + * YGREATERYMAX) { p1.x += dx * (ymax - p1.y) / dy; p1.y = ymax; + * } + * + * c1 = _clipCode(p1, ClipRect); } else { if (c2 & XLESSXMIN) { + * p2.y += dy * (xmin - p2.x) / dx; p2.x = xmin; } else if (c2 & + * XGREATERXMAX) { p2.y += dy * (xmax - p2.x) / dx; p2.x = xmax; + * } else if (c2 & YLESSYMIN) { p2.x += dx * (ymin - p2.y) / dy; + * p2.y = ymin; } else if (c2 & YGREATERYMAX) { p2.x += dx * + * (ymax - p2.y) / dy; p2.y = ymax; } + * + * c2 = _clipCode(p2, ClipRect); } + */ + } + + if ((c1 & c2) != 0)// (c1 & c2) + return 0; + + } while ((c1 | c2) != 0);// (c1 | c2); + + return res; + } + + int _clipCode(Point2D p)// returns a code from the Cohen-Sutherland (0000 is + // boundary inclusive) + { + int left = (p.x < xmin) ? 1 : 0; + int right = (p.x > xmax) ? 1 : 0; + int bottom = (p.y < ymin) ? 1 : 0; + int top = (p.y > ymax) ? 1 : 0; + return left | right << 1 | bottom << 2 | top << 3; + } + + // Clips and optionally extends line within envelope; modifies point 'from', + // 'to'. + // Algorithm: Liang-Barsky parametric line-clipping (Foley, vanDam, Feiner, + // Hughes, second edition, 117-124) + // lineExtension: 0 no line eExtension, 1 extend line at from point, 2 + // extend line at endpoint, 3 extend line at both ends + // boundaryDistances can be NULLPTR. + // returns: + // 0 - the segment is outside of the clipping window + // 1 - p1 was modified + // 2 - p2 was modified + // 3 - p1 and p2 were modified + // 4 - the segment is complitely inside of the clipping window + int clipLine(Point2D p0, Point2D p1, int lineExtension, double[] segParams, + double[] boundaryDistances) { + if (boundaryDistances != null) { + boundaryDistances[0] = -1.0; + boundaryDistances[1] = -1.0; + } + + double[] tOld = new double[2];// LOCALREFCLASS1(ArrayOf(double), int, + // tOld, 2); + int modified = 0; + + Point2D delta = new Point2D(p1.x - p0.x, p1.y - p0.y); + + if (delta.x == 0.0 && delta.y == 0.0) // input line degenerates to a + // point + { + segParams[0] = 0.0; + segParams[1] = 0.0; + return contains(p0) ? 4 : 0; + } + + segParams[0] = ((lineExtension & 1) != 0) ? NumberUtils.negativeInf() + : 0.0; + segParams[1] = ((lineExtension & 2) != 0) ? NumberUtils.positiveInf() + : 1.0; + tOld[0] = segParams[0]; + tOld[1] = segParams[1]; + + if (clipLineAuxiliary(delta.x, xmin - p0.x, segParams) + && clipLineAuxiliary(-delta.x, p0.x - xmax, segParams) + && clipLineAuxiliary(delta.y, ymin - p0.y, segParams) + && clipLineAuxiliary(-delta.y, p0.y - ymax, segParams)) { + if (segParams[1] < tOld[1]) { + p1.scaleAdd(segParams[1], delta, p0); + _snapToBoundary(p1); // needed for accuracy + modified |= 2; + + if (boundaryDistances != null) + boundaryDistances[1] = _boundaryDistance(p1); + } + if (segParams[0] > tOld[0]) { + p0.scaleAdd(segParams[0], delta, p0); + _snapToBoundary(p0); // needed for accuracy + modified |= 1; + + if (boundaryDistances != null) + boundaryDistances[0] = _boundaryDistance(p0); + } + } + + return modified; + } + + boolean clipLineAuxiliary(double denominator, double numerator, + double[] segParams) { + double t = numerator / denominator; + if (denominator > 0.0) { + if (t > segParams[1]) + return false; + + if (t > segParams[0]) { + segParams[0] = t; + return true; + } + } else if (denominator < 0.0) { + if (t < segParams[0]) + return false; + + if (t < segParams[1]) { + segParams[1] = t; + return true; + } + } else + return numerator <= 0.0; + + return true; + } + + /** + * Returns True, envelope is degenerate (Width or Height are less than + * tolerance). Note: this returns False for Empty envelope. + */ + boolean isDegenerate(double tolerance) { + return !isEmpty() + && (getWidth() <= tolerance || getHeight() <= tolerance); + } + + Point2D _snapClip(Point2D pt)// clips the point if it is outside, then snaps + // it to the boundary. + { + double x = NumberUtils.snap(pt.x, xmin, xmax); + double y = NumberUtils.snap(pt.y, ymin, ymax); + return new Point2D(x, y); + } + + boolean isPointOnBoundary(Point2D pt, double tolerance) { + return Math.abs(pt.x - xmin) <= tolerance + || Math.abs(pt.x - xmax) <= tolerance + || Math.abs(pt.y - ymin) <= tolerance + || Math.abs(pt.y - ymax) <= tolerance; + } + + double distance(/* const */Envelope2D other) /* const */ + { + return Math.sqrt(sqrDistance(other)); + } + + double distance(/* const */Point2D pt2D) /* const */ + { + return Math.sqrt(sqrDistance(pt2D)); + } + + double sqrDistance(/* const */Envelope2D other) /* const */ + { + // code from SG's windist + double dx = 0; + double dy = 0; + double nn; + + nn = xmin - other.xmax; + if (nn > dx) + dx = nn; + + nn = ymin - other.ymax; + if (nn > dy) + dy = nn; + + nn = other.xmin - xmax; + if (nn > dx) + dx = nn; + + nn = other.ymin - ymax; + if (nn > dy) + dy = nn; + + return dx * dx + dy * dy; + } + + double sqrDistance(/* const */Point2D pt2D) /* const */ + { + // code from SG's windist + double dx = 0; + double dy = 0; + double nn; + + nn = xmin - pt2D.x; + if (nn > dx) + dx = nn; + + nn = ymin - pt2D.y; + if (nn > dy) + dy = nn; + + nn = pt2D.x - xmax; + if (nn > dx) + dx = nn; + + nn = pt2D.y - ymax; + if (nn > dy) + dy = nn; + + return dx * dx + dy * dy; + } + + void queryIntervalX(Envelope1D env1D) /* const */ + { + if (isEmpty()) { + env1D.setEmpty(); + } else { + env1D.setCoords(xmin, xmax); + } + } + + void queryIntervalY(Envelope1D env1D) /* const */ + { + if (isEmpty()) { + env1D.setEmpty(); + } else { + env1D.setCoords(ymin, ymax); + } + } +} diff --git a/src/com/esri/core/geometry/Envelope2DIntersectorImpl.java b/src/com/esri/core/geometry/Envelope2DIntersectorImpl.java new file mode 100644 index 00000000..c4f4e227 --- /dev/null +++ b/src/com/esri/core/geometry/Envelope2DIntersectorImpl.java @@ -0,0 +1,905 @@ +/* + Copyright 1995-2013 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ +package com.esri.core.geometry; + +import java.util.ArrayList; + +class Envelope2DIntersectorImpl { + /* + * Constructor for Envelope_2D_intersector. + */ + Envelope2DIntersectorImpl() { + m_tolerance = 0.0; + m_function = -1; + m_sweep_index_red = -1; + m_sweep_index_blue = -1; + m_queued_list_red = -1; + m_queued_list_blue = -1; + m_b_done = true; + } + + /* + * Constructor for Envelope_2D_intersector_impl to find all pairs of + * intersecting envelopes from the same set in O(nlogn + t) time, where n is + * the number of envelopes in the array and t is the number of intersecting + * pairs.\param envelopes A Dynamic_array of Envelope_2D objects to be + * iterated over for pair-wise intersection.\param tolerance The tolerance + * used to determine intersection. + */ + Envelope2DIntersectorImpl(ArrayList envelopes, double tolerance) { + m_envelopes_red = envelopes; + m_tolerance = tolerance; + m_sweep_index_red = -1; + m_sweep_index_blue = -1; + m_queued_list_red = -1; + m_queued_list_blue = -1; + + if (m_envelopes_red != null && m_envelopes_red.size() > 0) { + m_function = State.initialize; + m_b_done = false; + } else { + m_function = -1; + m_envelope_handle_a = -1; + m_envelope_handle_b = -1; + m_b_done = true; + } + } + + /* + * Constructor for Envelope_2D_intersector_impl to find all pairs of + * intersecting envelopes from a set of red envelopes and blue envelopes in + * O(max(n,m)log(max(n,m)) + t) time, where n is the number of red + * envelopes, m is the number of blue envelopes, and t is the number of + * intersecting pairs.\param envelopes_red A Dynamic_array of Envelope_2D + * objects to be iterated over for pair-wise intersection with the blue + * envelopes.\param envelopes_blue A Dynamic_array of Envelope_2D objects to + * be iterated over for pair-wise intersection with the red envelopes.\param + * tolerance The tolerance used to determine intersection. + */ + Envelope2DIntersectorImpl(ArrayList envelopes_red, + ArrayList envelopes_blue, double tolerance) { + m_envelopes_red = envelopes_red; + m_envelopes_blue = envelopes_blue; + m_tolerance = tolerance; + m_sweep_index_red = -1; + m_sweep_index_blue = -1; + m_queued_list_red = -1; + m_queued_list_blue = -1; + + if (m_envelopes_red != null && m_envelopes_red.size() > 0 + && m_envelopes_blue != null && m_envelopes_blue.size() > 0) { + m_function = State.initializeRedBlue; + m_b_done = false; + } else { + m_function = -1; + m_envelope_handle_a = -1; + m_envelope_handle_b = -1; + m_b_done = true; + } + } + + /* + * Moves the iterator to the next intersecting pair of envelopes.Returns + * true if an intersecting pair is found. You can call get_handle_a() and + * get_handle_b() to get the index of each envelope in the current + * intersection. Otherwise if false is returned, then are no more + * intersections (if at all). + */ + boolean next() { + if (m_b_done) + return false; + + boolean b_searching = true; + while (b_searching) { + switch (m_function) { + case State.initialize: + b_searching = initialize_(); + break; + case State.initializeRed: + b_searching = initializeRed_(); + break; + case State.initializeBlue: + b_searching = initializeBlue_(); + break; + case State.initializeRedBlue: + b_searching = initializeRedBlue_(); + break; + case State.sweep: + b_searching = sweep_(); + break; + case State.sweepBruteForce: + b_searching = sweepBruteForce_(); + break; + case State.sweepRedBlueBruteForce: + b_searching = sweepRedBlueBruteForce_(); + break; + case State.sweepRedBlue: + b_searching = sweepRedBlue_(); + break; + case State.sweepRed: + b_searching = sweepRed_(); + break; + case State.sweepBlue: + b_searching = sweepBlue_(); + break; + case State.iterate: + b_searching = iterate_(); + break; + case State.iterateRed: + b_searching = iterateRed_(); + break; + case State.iterateBlue: + b_searching = iterateBlue_(); + break; + case State.iterateBruteForce: + b_searching = iterateBruteForce_(); + break; + case State.iterateRedBlueBruteForce: + b_searching = iterateRedBlueBruteForce_(); + break; + case State.resetRed: + b_searching = resetRed_(); + break; + case State.resetBlue: + b_searching = resetBlue_(); + break; + default: + throw new GeometryException("internal error"); + } + } + + if (m_b_done) + return false; + + return true; + } + + /* + * Returns the index of the first envelope in the intersection. In the + * red/blue case, this will be an index to the red envelopes. + */ + int getHandleA() { + return m_envelope_handle_a; + } + + /* + * Returns the index of the second envelope in the intersection. In the + * red/blue case, this will be an index to the blue envelopes. + */ + int getHandleB() { + return m_envelope_handle_b; + } + + /* + * Sets the intersector to do red on red intersection\param envelopes A + * Dynamic_array of Envelope_2D objects to be iterated over for pair-wise + * intersection. + */ + void setEnvelopes(ArrayList envelopes) { + m_envelopes_red = envelopes; + + if (m_envelopes_red != null && m_envelopes_red.size() > 0) { + m_function = State.initialize; + m_b_done = false; + } + } + + /* + * Sets the intersector to do red on blue intersection\param envelopes_red A + * Dynamic_array of Envelope_2D objects to be iterated over for pair-wise + * intersection with the blue envelopes. + */ + void setRedEnvelopes(ArrayList envelopes_red) { + m_envelopes_red = envelopes_red; + + if (m_envelopes_red != null && m_envelopes_red.size() > 0) { + if (m_function == State.initializeBlue) + m_function = State.initializeRedBlue; + else if (m_function != State.initializeRedBlue) + m_function = State.initializeRed; + + m_b_done = false; + } + } + + /* + * Sets the intersector to do red on blue intersection\param envelopes_blue + * A Dynamic_array of Envelope_2D objects to be iterated over for pair-wise + * intersection with the red envelopes. + */ + void setBlueEnvelopes(ArrayList envelopes_blue) { + m_envelopes_blue = envelopes_blue; + + if (m_envelopes_blue != null && m_envelopes_blue.size() > 0) { + if (m_function == State.initializeRed) + m_function = State.initializeRedBlue; + else if (m_function != State.initializeRedBlue) + m_function = State.initializeBlue; + + m_b_done = false; + } + } + + /* + * Sets the tolerance used for the intersection tests.\param tolerance The + * tolerance used to determine intersection. + */ + void setTolerance(double tolerance) { + m_tolerance = tolerance; + } + + /* + * Returns a reference to the red envelope at handle_a. + */ + Envelope2D getRedEnvelope(int handle_a) { + return m_envelopes_red.get(handle_a); + } + + /* + * Returns a reference to the blue envelope at handle_b. + */ + Envelope2D getBlueEnvelope(int handle_b) { + return m_envelopes_blue.get(handle_b); + } + + private double m_tolerance; + private ArrayList m_envelopes_red; + private ArrayList m_envelopes_blue; + private int m_sweep_index_red; + private int m_sweep_index_blue; + private int m_envelope_handle_a; + private int m_envelope_handle_b; + private IntervalTreeImpl m_interval_tree_red; + private IntervalTreeImpl m_interval_tree_blue; + private IntervalTreeImpl.IntervalTreeIteratorImpl m_iterator_red; + private IntervalTreeImpl.IntervalTreeIteratorImpl m_iterator_blue; + private Envelope2D m_envelope_helper = new Envelope2D(); + + private AttributeStreamOfInt32 m_sorted_end_indices_red; + private AttributeStreamOfInt32 m_sorted_end_indices_blue; + + private int m_queued_list_red; + private int m_queued_list_blue; + private IndexMultiDCList m_queued_envelopes; + private AttributeStreamOfInt32 m_queued_indices_red; + private AttributeStreamOfInt32 m_queued_indices_blue; + + boolean m_b_done; + + private static boolean isTop_(int y_end_point_handle) { + return (y_end_point_handle & 0x1) == 1; + } + + private static boolean isBottom_(int y_end_point_handle) { + return (y_end_point_handle & 0x1) == 0; + } + + private boolean initialize_() { + m_envelope_handle_a = -1; + m_envelope_handle_b = -1; + + if (m_envelopes_red.size() < 10) { + m_sweep_index_red = m_envelopes_red.size(); + m_function = State.sweepBruteForce; + return true; + } + + if (m_interval_tree_red == null) { + m_interval_tree_red = new IntervalTreeImpl(m_envelopes_red, true, + true); + m_iterator_red = m_interval_tree_red.getIterator(); + m_sorted_end_indices_red = new AttributeStreamOfInt32(0); + } else { + m_interval_tree_red.reset(m_envelopes_red, true, true); + } + + m_sorted_end_indices_red.reserve(2 * m_envelopes_red.size()); + m_sorted_end_indices_red.resize(0); + + for (int i = 0; i < 2 * m_envelopes_red.size(); i++) + m_sorted_end_indices_red.add(i); + + sortYEndIndices_(m_sorted_end_indices_red, 0, + 2 * m_envelopes_red.size(), true); + + m_sweep_index_red = 2 * m_envelopes_red.size(); + + m_function = State.sweep; // overwrite initialize_ + + return true; + } + + private boolean initializeRed_() { + m_envelope_handle_a = -1; + m_envelope_handle_b = -1; + + if (m_envelopes_red.size() < 10 || m_envelopes_blue.size() < 10) { + m_sweep_index_red = m_envelopes_red.size(); + m_function = State.sweepRedBlueBruteForce; + return true; + } + + if (m_interval_tree_red == null) { + m_interval_tree_red = new IntervalTreeImpl(m_envelopes_red, true, + true); + m_iterator_red = m_interval_tree_red.getIterator(); + m_sorted_end_indices_red = new AttributeStreamOfInt32(0); + } else { + m_interval_tree_red.reset(m_envelopes_red, true, true); + } + + m_sorted_end_indices_red.reserve(2 * m_envelopes_red.size()); + m_sorted_end_indices_red.resize(0); + + for (int i = 0; i < 2 * m_envelopes_red.size(); i++) + m_sorted_end_indices_red.add(i); + + sortYEndIndices_(m_sorted_end_indices_red, 0, + m_sorted_end_indices_red.size(), true); + m_sweep_index_red = m_sorted_end_indices_red.size(); + + if (m_queued_list_red != -1) { + m_queued_envelopes.deleteList(m_queued_list_red); + m_queued_indices_red.resize(0); + m_queued_list_red = -1; + } + + m_function = State.sweepRedBlue; // overwrite initialize_ + + return resetBlue_(); + } + + private boolean initializeBlue_() { + m_envelope_handle_a = -1; + m_envelope_handle_b = -1; + + if (m_envelopes_red.size() < 10 || m_envelopes_blue.size() < 10) { + m_sweep_index_red = m_envelopes_red.size(); + m_function = State.sweepRedBlueBruteForce; + return true; + } + + if (m_interval_tree_blue == null) { + m_interval_tree_blue = new IntervalTreeImpl(m_envelopes_blue, true, + true); + m_iterator_blue = m_interval_tree_blue.getIterator(); + m_sorted_end_indices_blue = new AttributeStreamOfInt32(0); + } else { + m_interval_tree_blue.reset(m_envelopes_blue, true, true); + } + + m_sorted_end_indices_blue.reserve(2 * m_envelopes_blue.size()); + m_sorted_end_indices_blue.resize(0); + + for (int i = 0; i < 2 * m_envelopes_blue.size(); i++) + m_sorted_end_indices_blue.add(i); + + sortYEndIndices_(m_sorted_end_indices_blue, 0, + m_sorted_end_indices_blue.size(), false); + m_sweep_index_blue = m_sorted_end_indices_blue.size(); + + if (m_queued_list_blue != -1) { + m_queued_envelopes.deleteList(m_queued_list_blue); + m_queued_indices_blue.resize(0); + m_queued_list_blue = -1; + } + + m_function = State.sweepRedBlue; // overwrite initialize_ + + return resetRed_(); + } + + private boolean initializeRedBlue_() { + m_envelope_handle_a = -1; + m_envelope_handle_b = -1; + + if (m_envelopes_red.size() < 10 || m_envelopes_blue.size() < 10) { + m_sweep_index_red = m_envelopes_red.size(); + m_function = State.sweepRedBlueBruteForce; + return true; + } + + if (m_interval_tree_red == null) { + m_interval_tree_red = new IntervalTreeImpl(m_envelopes_red, true, + true); + m_iterator_red = m_interval_tree_red.getIterator(); + m_sorted_end_indices_red = new AttributeStreamOfInt32(0); + } else { + m_interval_tree_red.reset(m_envelopes_red, true, true); + } + + if (m_interval_tree_blue == null) { + m_interval_tree_blue = new IntervalTreeImpl(m_envelopes_blue, true, + true); + m_iterator_blue = m_interval_tree_blue.getIterator(); + m_sorted_end_indices_blue = new AttributeStreamOfInt32(0); + } else { + m_interval_tree_blue.reset(m_envelopes_blue, true, true); + } + + m_sorted_end_indices_red.reserve(2 * m_envelopes_red.size()); + m_sorted_end_indices_blue.reserve(2 * m_envelopes_blue.size()); + m_sorted_end_indices_red.resize(0); + m_sorted_end_indices_blue.resize(0); + + for (int i = 0; i < 2 * m_envelopes_red.size(); i++) + m_sorted_end_indices_red.add(i); + + for (int i = 0; i < 2 * m_envelopes_blue.size(); i++) + m_sorted_end_indices_blue.add(i); + + sortYEndIndices_(m_sorted_end_indices_red, 0, + m_sorted_end_indices_red.size(), true); + sortYEndIndices_(m_sorted_end_indices_blue, 0, + m_sorted_end_indices_blue.size(), false); + + m_sweep_index_red = m_sorted_end_indices_red.size(); + m_sweep_index_blue = m_sorted_end_indices_blue.size(); + + if (m_queued_list_red != -1) { + m_queued_envelopes.deleteList(m_queued_list_red); + m_queued_indices_red.resize(0); + m_queued_list_red = -1; + } + + if (m_queued_list_blue != -1) { + m_queued_envelopes.deleteList(m_queued_list_blue); + m_queued_indices_blue.resize(0); + m_queued_list_blue = -1; + } + + m_function = State.sweepRedBlue; // overwrite initialize_ + + return true; + } + + private boolean sweep_() { + int y_end_point_handle = m_sorted_end_indices_red + .get(--m_sweep_index_red); + int envelope_handle = y_end_point_handle >> 1; + + if (isBottom_(y_end_point_handle)) { + m_interval_tree_red.remove(envelope_handle); + + if (m_sweep_index_red == 0) { + m_envelope_handle_a = -1; + m_envelope_handle_b = -1; + m_b_done = true; + return false; + } + + return true; + } + + m_iterator_red.resetIterator(m_envelopes_red.get(envelope_handle).xmin, + m_envelopes_red.get(envelope_handle).xmax, m_tolerance); + m_envelope_handle_a = envelope_handle; + m_function = State.iterate; + + return true; + } + + private boolean sweepBruteForce_() {// this isn't really a sweep, it just + // walks along the array of red + // envelopes backward. + if (--m_sweep_index_red == -1) { + m_envelope_handle_a = -1; + m_envelope_handle_b = -1; + m_b_done = true; + return false; + } + + m_envelope_handle_a = m_sweep_index_red; + m_sweep_index_blue = m_sweep_index_red; + m_function = State.iterateBruteForce; + + return true; + } + + private boolean sweepRedBlueBruteForce_() {// this isn't really a sweep, it + // just walks along the array of + // red envelopes backward. + if (--m_sweep_index_red == -1) { + m_envelope_handle_a = -1; + m_envelope_handle_b = -1; + m_b_done = true; + return false; + } + + m_envelope_handle_a = m_sweep_index_red; + m_sweep_index_blue = m_envelopes_blue.size(); + m_function = State.iterateRedBlueBruteForce; + + return true; + } + + private boolean sweepRedBlue_() {// controls whether we want to sweep the + // red envelopes or sweep the blue + // envelopes + int y_end_point_handle_red = m_sorted_end_indices_red + .get(m_sweep_index_red - 1); + int y_end_point_handle_blue = m_sorted_end_indices_blue + .get(m_sweep_index_blue - 1); + + double y_red = getAdjustedValue_(y_end_point_handle_red, true); + double y_blue = getAdjustedValue_(y_end_point_handle_blue, false); + + if (y_red > y_blue) + return sweepRed_(); + if (y_red < y_blue) + return sweepBlue_(); + + if (isTop_(y_end_point_handle_red)) + return sweepRed_(); + if (isTop_(y_end_point_handle_blue)) + return sweepBlue_(); + + return sweepRed_(); // arbitrary. can call sweep_blue_ instead and would + // also work correctly + } + + private boolean sweepRed_() { + int y_end_point_handle_red = m_sorted_end_indices_red + .get(--m_sweep_index_red); + int envelope_handle_red = y_end_point_handle_red >> 1; + + if (isBottom_(y_end_point_handle_red)) { + if (m_queued_list_red != -1 + && m_queued_indices_red.get(envelope_handle_red) != -1) { + m_queued_envelopes.deleteElement(m_queued_list_red, + m_queued_indices_red.get(envelope_handle_red)); + m_queued_indices_red.set(envelope_handle_red, -1); + } else + m_interval_tree_red.remove(envelope_handle_red); + + if (m_sweep_index_red == 0) { + m_envelope_handle_a = -1; + m_envelope_handle_b = -1; + m_b_done = true; + return false; + } + + return true; + } + + if (m_queued_list_blue != -1 + && m_queued_envelopes.getListSize(m_queued_list_blue) > 0) { + int node = m_queued_envelopes.getFirst(m_queued_list_blue); + while (node != -1) { + int e = m_queued_envelopes.getData(node); + m_interval_tree_blue.insert(e); + m_queued_indices_blue.set(e, -1); + int next_node = m_queued_envelopes.getNext(node); + m_queued_envelopes.deleteElement(m_queued_list_blue, node); + node = next_node; + } + } + + if (m_interval_tree_blue.size() > 0) { + m_iterator_blue.resetIterator( + m_envelopes_red.get(envelope_handle_red).xmin, + m_envelopes_red.get(envelope_handle_red).xmax, m_tolerance); + m_envelope_handle_a = envelope_handle_red; + m_function = State.iterateBlue; + } else { + if (m_queued_list_red == -1) { + if (m_queued_envelopes == null) + m_queued_envelopes = new IndexMultiDCList(); + + m_queued_indices_red = new AttributeStreamOfInt32(0); + m_queued_indices_red.resize(m_envelopes_red.size(), -1); + m_queued_indices_red.setRange(-1, 0, m_envelopes_red.size()); + m_queued_list_red = m_queued_envelopes.createList(1); + } + + m_queued_indices_red.set(envelope_handle_red, m_queued_envelopes + .addElement(m_queued_list_red, envelope_handle_red)); + m_function = State.sweepRedBlue; + } + + return true; + } + + private boolean sweepBlue_() { + int y_end_point_handle_blue = m_sorted_end_indices_blue + .get(--m_sweep_index_blue); + int envelope_handle_blue = y_end_point_handle_blue >> 1; + + if (isBottom_(y_end_point_handle_blue)) { + if (m_queued_list_blue != -1 + && m_queued_indices_blue.get(envelope_handle_blue) != -1) { + m_queued_envelopes.deleteElement(m_queued_list_blue, + m_queued_indices_blue.get(envelope_handle_blue)); + m_queued_indices_blue.set(envelope_handle_blue, -1); + } else + m_interval_tree_blue.remove(envelope_handle_blue); + + if (m_sweep_index_blue == 0) { + m_envelope_handle_a = -1; + m_envelope_handle_b = -1; + m_b_done = true; + return false; + } + + return true; + } + + if (m_queued_list_red != -1 + && m_queued_envelopes.getListSize(m_queued_list_red) > 0) { + int node = m_queued_envelopes.getFirst(m_queued_list_red); + while (node != -1) { + int e = m_queued_envelopes.getData(node); + m_interval_tree_red.insert(e); + m_queued_indices_red.set(e, -1); + int next_node = m_queued_envelopes.getNext(node); + m_queued_envelopes.deleteElement(m_queued_list_red, node); + node = next_node; + } + } + + if (m_interval_tree_red.size() > 0) { + m_iterator_red.resetIterator( + m_envelopes_blue.get(envelope_handle_blue).xmin, + m_envelopes_blue.get(envelope_handle_blue).xmax, + m_tolerance); + m_envelope_handle_b = envelope_handle_blue; + m_function = State.iterateRed; + } else { + if (m_queued_list_blue == -1) { + if (m_queued_envelopes == null) + m_queued_envelopes = new IndexMultiDCList(); + + m_queued_indices_blue = new AttributeStreamOfInt32(0); + m_queued_indices_blue.resize(m_envelopes_blue.size(), -1); + m_queued_indices_blue.setRange(-1, 0, m_envelopes_blue.size()); + m_queued_list_blue = m_queued_envelopes.createList(0); + } + + m_queued_indices_blue.set(envelope_handle_blue, m_queued_envelopes + .addElement(m_queued_list_blue, envelope_handle_blue)); + m_function = State.sweepRedBlue; + } + + return true; + } + + private boolean iterate_() { + m_envelope_handle_b = m_iterator_red.next(); + if (m_envelope_handle_b != -1) + return false; + + int envelope_handle = m_sorted_end_indices_red.get(m_sweep_index_red) >> 1; + m_interval_tree_red.insert(envelope_handle); + m_function = State.sweep; + + return true; + } + + private boolean iterateRed_() { + m_envelope_handle_a = m_iterator_red.next(); + if (m_envelope_handle_a != -1) + return false; + + m_envelope_handle_a = -1; + m_envelope_handle_b = -1; + + int envelope_handle_blue = m_sorted_end_indices_blue + .get(m_sweep_index_blue) >> 1; + m_interval_tree_blue.insert(envelope_handle_blue); + m_function = State.sweepRedBlue; + + return true; + } + + private boolean iterateBlue_() { + m_envelope_handle_b = m_iterator_blue.next(); + if (m_envelope_handle_b != -1) + return false; + + int envelope_handle_red = m_sorted_end_indices_red + .get(m_sweep_index_red) >> 1; + m_interval_tree_red.insert(envelope_handle_red); + m_function = State.sweepRedBlue; + + return true; + } + + private boolean iterateBruteForce_() { + if (--m_sweep_index_blue == -1) { + m_function = State.sweepBruteForce; + return true; + } + + m_envelope_helper.setCoords(m_envelopes_red.get(m_sweep_index_red)); + Envelope2D envelope_b = m_envelopes_red.get(m_sweep_index_blue); + + m_envelope_helper.inflate(m_tolerance, m_tolerance); + if (m_envelope_helper.isIntersecting(envelope_b)) { + m_envelope_handle_b = m_sweep_index_blue; + return false; + } + + return true; + } + + private boolean iterateRedBlueBruteForce_() { + if (--m_sweep_index_blue == -1) { + m_function = State.sweepRedBlueBruteForce; + return true; + } + + m_envelope_helper.setCoords(m_envelopes_red.get(m_sweep_index_red)); + Envelope2D envelope_b = m_envelopes_blue.get(m_sweep_index_blue); + + m_envelope_helper.inflate(m_tolerance, m_tolerance); + if (m_envelope_helper.isIntersecting(envelope_b)) { + m_envelope_handle_b = m_sweep_index_blue; + return false; + } + + return true; + } + + private boolean resetRed_() { + if (m_interval_tree_red == null) { + m_b_done = true; + return false; + } + + m_sweep_index_red = m_sorted_end_indices_red.size(); + + if (m_interval_tree_red.size() > 0) + m_interval_tree_red.reset(); + + if (m_queued_list_red != -1) { + m_queued_envelopes.deleteList(m_queued_list_red); + m_queued_indices_red.resize(0); + m_queued_list_red = -1; + } + + m_b_done = false; + return true; + } + + private boolean resetBlue_() { + if (m_interval_tree_blue == null) { + m_b_done = true; + return false; + } + + m_sweep_index_blue = m_sorted_end_indices_blue.size(); + + if (m_interval_tree_blue.size() > 0) + m_interval_tree_blue.reset(); + + if (m_queued_list_blue != -1) { + m_queued_envelopes.deleteList(m_queued_list_blue); + m_queued_indices_blue.resize(0); + m_queued_list_blue = -1; + } + + m_b_done = false; + return true; + } + + private int m_function; + + private interface State { + static final int initialize = 0; + static final int initializeRed = 1; + static final int initializeBlue = 2; + static final int initializeRedBlue = 3; + static final int sweep = 4; + static final int sweepBruteForce = 5; + static final int sweepRedBlueBruteForce = 6; + static final int sweepRedBlue = 7; + static final int sweepRed = 8; + static final int sweepBlue = 9; + static final int iterate = 10; + static final int iterateRed = 11; + static final int iterateBlue = 12; + static final int iterateBruteForce = 13; + static final int iterateRedBlueBruteForce = 14; + static final int resetRed = 15; + static final int resetBlue = 16; + } + + // *********** Helpers for Bucket sort************** + private BucketSort m_bucket_sort; + + private void sortYEndIndices_(AttributeStreamOfInt32 end_indices, + int begin_, int end_, boolean b_red) { + if (m_bucket_sort == null) + m_bucket_sort = new BucketSort(); + + Envelope2DBucketSortHelper sorter = new Envelope2DBucketSortHelper( + this, b_red); + m_bucket_sort.sort(end_indices, begin_, end_, sorter); + } + + private void sortYEndIndicesHelper_(AttributeStreamOfInt32 end_indices, + int begin_, int end_, boolean b_red) { + end_indices.Sort(begin_, end_, new EndPointsComparer(this, b_red)); + } + + private double getAdjustedValue_(int e, boolean b_red) { + double dy = 0.5 * m_tolerance; + if (b_red) { + Envelope2D envelope_red = m_envelopes_red.get(e >> 1); + double y = (isBottom_(e) ? envelope_red.ymin - dy + : envelope_red.ymax + dy); + return y; + } + + Envelope2D envelope_blue = m_envelopes_blue.get(e >> 1); + double y = (isBottom_(e) ? envelope_blue.ymin - dy : envelope_blue.ymax + + dy); + return y; + } + + private static final class EndPointsComparer extends + AttributeStreamOfInt32.IntComparator {// For user sort + EndPointsComparer(Envelope2DIntersectorImpl intersector, boolean b_red) { + m_intersector = intersector; + m_b_red = b_red; + } + + @Override + public int compare(int e_1, int e_2) { + double y1 = m_intersector.getAdjustedValue_(e_1, m_b_red); + double y2 = m_intersector.getAdjustedValue_(e_2, m_b_red); + + if (y1 < y2 || (y1 == y2 && isBottom_(e_1) && isTop_(e_2))) + return -1; + + return 1; + } + + private Envelope2DIntersectorImpl m_intersector; + private boolean m_b_red; + } + + private static final class Envelope2DBucketSortHelper extends ClassicSort {// For + // bucket + // sort + Envelope2DBucketSortHelper(Envelope2DIntersectorImpl intersector, + boolean b_red) { + m_intersector = intersector; + m_b_red = b_red; + } + + @Override + public void userSort(int begin, int end, AttributeStreamOfInt32 indices) { + m_intersector.sortYEndIndicesHelper_(indices, begin, end, m_b_red); + } + + @Override + public double getValue(int index) { + return m_intersector.getAdjustedValue_(index, m_b_red); + } + + private Envelope2DIntersectorImpl m_intersector; + private boolean m_b_red; + } +} diff --git a/src/com/esri/core/geometry/Envelope3D.java b/src/com/esri/core/geometry/Envelope3D.java new file mode 100644 index 00000000..37edf611 --- /dev/null +++ b/src/com/esri/core/geometry/Envelope3D.java @@ -0,0 +1,231 @@ +/* + Copyright 1995-2013 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ + + +package com.esri.core.geometry; + +/** + * A class that represents axis parallel 3D rectangle. + */ +final class Envelope3D { + public double xmin; + + public double ymin; + + public double zmin; + + public double xmax; + + public double ymax; + + public double zmax; + + public static Envelope3D construct(double _xmin, double _ymin, + double _zmin, double _xmax, double _ymax, double _zmax) { + Envelope3D env = new Envelope3D(); + env.xmin = _xmin; + env.ymin = _ymin; + env.zmin = _zmin; + env.xmax = _xmax; + env.ymax = _ymax; + env.zmax = _zmax; + return env; + } + + public Envelope3D() { + + } + + public void setInfinite() { + xmin = NumberUtils.negativeInf(); + xmax = NumberUtils.positiveInf(); + ymin = NumberUtils.negativeInf(); + ymax = NumberUtils.positiveInf(); + zmin = NumberUtils.negativeInf(); + zmax = NumberUtils.positiveInf(); + } + + public void setEmpty() { + xmin = NumberUtils.NaN(); + zmin = NumberUtils.NaN(); + } + + public boolean isEmpty() { + return NumberUtils.isNaN(xmin); + } + + public void setEmptyZ() { + zmin = NumberUtils.NaN(); + } + + public boolean isEmptyZ() { + return NumberUtils.isNaN(zmin); + } + + public boolean hasEmptyDimension() { + return isEmpty() || isEmptyZ(); + } + + public void setCoords(double _xmin, double _ymin, double _zmin, + double _xmax, double _ymax, double _zmax) { + xmin = _xmin; + ymin = _ymin; + zmin = _zmin; + xmax = _xmax; + ymax = _ymax; + zmax = _zmax; + } + + public void setCoords(double _x, double _y, double _z) { + xmin = _x; + ymin = _y; + zmin = _z; + xmax = _x; + ymax = _y; + zmax = _z; + } + + public void setCoords(Point3D center, double width, double height, + double depth) { + xmin = center.x - width * 0.5; + xmax = xmin + width; + ymin = center.y - height * 0.5; + ymax = ymin + height; + zmin = center.z - depth * 0.5; + zmax = zmin + depth; + } + + public void move(Point3D vector) { + xmin += vector.x; + ymin += vector.y; + zmin += vector.z; + xmax += vector.x; + ymax += vector.y; + zmax += vector.z; + } + + public void copyTo(Envelope2D env) { + env.xmin = xmin; + env.ymin = ymin; + env.xmax = xmax; + env.ymax = ymax; + } + + public void mergeNE(double x, double y, double z) { + if (xmin > x) + xmin = x; + else if (xmax < x) + xmax = x; + + if (ymin > y) + ymin = y; + else if (ymax < y) + ymax = y; + + if (zmin != NumberUtils.NaN()) { + if (zmin > z) + zmin = z; + else if (zmax < z) + zmax = z; + } else { + zmin = z; + zmax = z; + } + } + + public void merge(double x, double y, double z) { + if (isEmpty()) { + xmin = x; + ymin = y; + zmin = z; + xmax = x; + ymax = y; + zmax = z; + } else { + mergeNE(x, y, z); + } + } + + public void merge(Point3D pt) { + merge(pt.x, pt.y, pt.z); + } + + public void merge(Envelope3D other) { + if (other.isEmpty()) + return; + + merge(other.xmin, other.ymin, other.zmin); + merge(other.xmax, other.ymax, other.zmax); + } + + public void merge(double x1, double y1, double z1, double x2, double y2, + double z2) { + merge(x1, y1, z1); + merge(x2, y2, z2); + } + + public void construct(Envelope1D xinterval, Envelope1D yinterval, + Envelope1D zinterval) { + if (xinterval.isEmpty() || yinterval.isEmpty()) { + setEmpty(); + return; + } + + xmin = xinterval.vmin; + xmax = xinterval.vmax; + ymin = yinterval.vmin; + ymax = yinterval.vmax; + zmin = zinterval.vmin; + zmax = zinterval.vmax; + } + + public void queryCorners(Point3D[] corners) { + if ((corners == null) || (corners.length < 8)) + throw new IllegalArgumentException(); + + corners[0] = new Point3D(xmin, ymin, zmin); + corners[1] = new Point3D(xmin, ymax, zmin); + corners[2] = new Point3D(xmax, ymax, zmin); + corners[3] = new Point3D(xmax, ymin, zmin); + corners[4] = new Point3D(xmin, ymin, zmax); + corners[5] = new Point3D(xmin, ymax, zmax); + corners[6] = new Point3D(xmax, ymax, zmax); + corners[7] = new Point3D(xmax, ymin, zmax); + + } + + public void setFromPoints(Point3D[] points) { + if (points == null || points.length == 0) { + setEmpty(); + return; + } + + Point3D p = points[0]; + setCoords(p.x, p.y, p.z); + for (int i = 1; i < points.length; i++) { + Point3D pt = points[i]; + mergeNE(pt.x, pt.y, pt.z); + } + } +} diff --git a/src/com/esri/core/geometry/GeoDist.java b/src/com/esri/core/geometry/GeoDist.java new file mode 100644 index 00000000..9a1b410d --- /dev/null +++ b/src/com/esri/core/geometry/GeoDist.java @@ -0,0 +1,467 @@ +/* + Copyright 1995-2013 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ + +package com.esri.core.geometry; + +import com.esri.core.geometry.PeDouble; + +final class GeoDist { + private static final double PE_PI = 3.14159265358979323846264; + private static final double PE_PI2 = 1.57079632679489661923132; + private static final double PE_2PI = 6.283185307179586476925287; + private static final double PE_EPS = 3.55271367880050092935562e-15; + + /** Get the absolute value of a number */ + static private double PE_ABS(double a) { + return (a < 0) ? -a : a; + } + + /** Assign the sign of the second number to the first */ + static private double PE_SGN(double a, double b) { + return (b >= 0) ? PE_ABS(a) : -PE_ABS(a); + } + + /** Determine if two doubles are equal within a default tolerance */ + static private boolean PE_EQ(double a, double b) { + return (a == b) + || PE_ABS(a - b) <= PE_EPS * (1 + (PE_ABS(a) + PE_ABS(b)) / 2); + } + + /** Determine if a double is within a given tolerance of zero */ + static private boolean PE_ZERO(double a) { + return (a == 0.0) || (PE_ABS(a) <= PE_EPS); + } + + static private double lam_delta(double lam) { + double d = Math.IEEEremainder(lam, PE_2PI); + + return (PE_ABS(d) <= PE_PI) ? d : ((d < 0) ? d + PE_2PI : d - PE_2PI); + } + + static private void lam_phi_reduction(PeDouble p_lam, PeDouble p_phi) { + p_lam.val = lam_delta(p_lam.val); + p_phi.val = lam_delta(p_phi.val); + + if (PE_ABS(p_phi.val) > PE_PI2) { + p_lam.val = lam_delta(p_lam.val + PE_PI); + p_phi.val = PE_SGN(PE_PI, p_phi.val) - p_phi.val; + } + } + + static private double q90(double a, double e2) { + /* + * Rapp // Geometric Geodesy (Part I) // p. 39. Adams, O.S. // Latitude + * Developments ... // pp. 122-127. Terms extended past n4 by David + * Burrows, ESRI + */ + + /* Calculate meridional arc distance from equator to pole */ + + /* + * q90 = a * PE_PI2 * (1 + 1/4 n2 + 1/64 n4 + 1/256 n6 + 25/16384 n8 + + * 49/65536 n10 + ...)/(1.0 + n) + */ + + double t = Math.sqrt(1.0 - e2); + double n = (1.0 - t) / (1.0 + t); + double n2 = n * n; + + return a / (1.0 + n) + * (1.0 + n2 * (1. / 4. + n2 * (1. / 64. + n2 * (1. / 256.)))) + * PE_PI2; + } + + static public void geodesic_distance_ngs(double a, double e2, double lam1, + double phi1, double lam2, double phi2, PeDouble p_dist, + PeDouble p_az12, PeDouble p_az21) { + /* Highly edited version (plus lots of additions) of NGS FORTRAN code */ + + /* + * inverse for long-line and antipodal cases.* latitudes may be 90 + * degrees exactly.* latitude positive north, longitude positive east, + * radians.* azimuth clockwise from north, radians.* original programmed + * by thaddeus vincenty, 1975, 1976* removed back side solution option, + * debugged, revised -- 2011may01 -- dgm* this version of code is + * interim -- antipodal boundary needs work + * + * * output (besides az12, az21, and dist):* These have been removed + * from this esri version of the ngs code* it, iteration count* sigma, + * spherical distance on auxiliary sphere* lam_sph, longitude difference + * on auxiliary sphere* kind, solution flag: kind=1, long-line; kind=2, + * antipodal + * + * + * All references to Rapp are Part II + */ + + double tol = 1.0e-14; + double eps = 1.0e-15; + + double boa = 0.0; + double dlam = 0.0; + double eta1 = 0.0, sin_eta1 = 0.0, cos_eta1 = 0.0; + double eta2 = 0.0, sin_eta2 = 0.0, cos_eta2 = 0.0; + double prev = 0.0, test = 0.0; + double sin_lam_sph = 0.0, cos_lam_sph = 0.0, temp = 0.0, sin_sigma = 0.0, cos_sigma = 0.0; + double sin_azeq = 0.0, cos2_azeq = 0.0, costm = 0.0, costm2 = 0.0, c = 0.0, d = 0.0; + double tem1 = 0.0, tem2 = 0.0, ep2 = 0.0, bige = 0.0, bigf = 0.0, biga = 0.0, bigb = 0.0, z = 0.0, dsigma = 0.0; + boolean q_continue_looping; + + double f = 0.0; + + double az12 = 0.0, az21 = 0.0, dist = 0.0; + double sigma = 0.0, lam_sph = 0.0; + int it = 0, kind = 0; + + PeDouble lam = new PeDouble(); + PeDouble phi = new PeDouble(); + + /* Are there any values to calculate? */ + if (p_dist == null && p_az12 == null && p_az21 == null) { + return; + } + + /* Normalize point 1 and 2 */ + lam.val = lam1; + phi.val = phi1; + lam_phi_reduction(lam, phi); + lam1 = lam.val; + phi1 = phi.val; + + lam.val = lam2; + phi.val = phi2; + lam_phi_reduction(lam, phi); + lam2 = lam.val; + phi2 = phi.val; + + dlam = lam_delta(lam2 - lam1); /* longitude difference [-Pi, Pi] */ + + if (PE_EQ(phi1, phi2) && (PE_ZERO(dlam) || PE_EQ(PE_ABS(phi1), PE_PI2))) { + /* Check that the points are not the same */ + if (p_dist != null) + p_dist.val = 0.0; + if (p_az12 != null) + p_az12.val = 0.0; + if (p_az21 != null) + p_az21.val = 0.0; + + return; + } else if (PE_EQ(phi1, -phi2)) { + /* Check if they are perfectly antipodal */ + if (PE_EQ(PE_ABS(phi1), PE_PI2)) { + /* Check if they are at opposite poles */ + if (p_dist != null) + p_dist.val = 2.0 * q90(a, e2); + + if (p_az12 != null) + p_az12.val = phi1 > 0.0 ? lam_delta(PE_PI - lam_delta(lam2)) + : lam_delta(lam2); + + if (p_az21 != null) + p_az21.val = phi1 > 0.0 ? lam_delta(lam2) : lam_delta(PE_PI + - lam_delta(lam2)); + + return; + } else if (PE_EQ(PE_ABS(dlam), PE_PI)) { + /* Other antipodal */ + if (p_dist != null) + p_dist.val = 2.0 * q90(a, e2); + if (p_az12 != null) + p_az12.val = 0.0; + if (p_az21 != null) + p_az21.val = 0.0; + return; + } + } + + if (PE_ZERO(e2)) /* Sphere */ + { + double cos_phi1, cos_phi2; + double sin_phi1, sin_phi2; + + cos_phi1 = Math.cos(phi1); + cos_phi2 = Math.cos(phi2); + sin_phi1 = Math.sin(phi1); + sin_phi2 = Math.sin(phi2); + + if (p_dist != null) { + tem1 = Math.sin((phi2 - phi1) / 2.0); + tem2 = Math.sin(dlam / 2.0); + sigma = 2.0 * Math.asin(Math.sqrt(tem1 * tem1 + cos_phi1 + * cos_phi2 * tem2 * tem2)); + p_dist.val = sigma * a; + } + + if (p_az12 != null) { + if (PE_EQ(PE_ABS(phi1), PE_PI2)) /* Origin at N or S Pole */ + { + p_az12.val = phi1 < 0.0 ? lam2 : lam_delta(PE_PI - lam2); + } else { + p_az12.val = Math.atan2(cos_phi2 * Math.sin(dlam), cos_phi1 + * sin_phi2 - sin_phi1 * cos_phi2 * Math.cos(dlam)); + } + } + + if (p_az21 != null) { + if (PE_EQ(PE_ABS(phi2), PE_PI2)) /* Destination at N or S Pole */ + { + p_az21.val = phi2 < 0.0 ? lam1 : lam_delta(PE_PI - lam1); + } else { + p_az21.val = Math.atan2(cos_phi1 * Math.sin(dlam), sin_phi2 + * cos_phi1 * Math.cos(dlam) - cos_phi2 * sin_phi1); + p_az21.val = lam_delta(p_az21.val + PE_PI); + } + } + + return; + } + + f = 1.0 - Math.sqrt(1.0 - e2); + boa = 1.0 - f; + + eta1 = Math.atan(boa * Math.tan(phi1)); /* better reduced latitude */ + sin_eta1 = Math.sin(eta1); + cos_eta1 = Math.cos(eta1); + + eta2 = Math.atan(boa * Math.tan(phi2)); /* better reduced latitude */ + sin_eta2 = Math.sin(eta2); + cos_eta2 = Math.cos(eta2); + + prev = dlam; + test = dlam; + it = 0; + kind = 1; + lam_sph = dlam; /* v13 (Rapp ) */ + + /* top of the long-line loop (kind = 1) */ + + q_continue_looping = true; + while (q_continue_looping == true) { + it = it + 1; + + if (kind == 1) { + sin_lam_sph = Math.sin(lam_sph); + + /* + * if ( PE_ABS(PE_PI - PE_ABS(dlam)) < 2.0e-11 ) sin_lam_sph = + * 0.0 no--troublesome + */ + + cos_lam_sph = Math.cos(lam_sph); + tem1 = cos_eta2 * sin_lam_sph; + temp = cos_eta1 * sin_eta2 - sin_eta1 * cos_eta2 * cos_lam_sph; + sin_sigma = Math.sqrt(tem1 * tem1 + temp * temp); /* + * v14 (Rapp + * 1.87) + */ + cos_sigma = sin_eta1 * sin_eta2 + cos_eta1 * cos_eta2 + * cos_lam_sph; /* v15 (Rapp 1.88) */ + sigma = Math.atan2(sin_sigma, cos_sigma); /* (Rapp 1.89) */ + + if (PE_ABS(sin_sigma) < eps) /* avoid division by 0 */ + { + sin_azeq = cos_eta1 * cos_eta2 * sin_lam_sph + / PE_SGN(eps, sin_sigma); + } else { + sin_azeq = cos_eta1 * cos_eta2 * sin_lam_sph / sin_sigma; + /* v17 (Rapp 1.90) */ + } + + cos2_azeq = 1.0 - sin_azeq * sin_azeq; + + if (PE_ABS(cos2_azeq) < eps) /* avoid division by 0 */ + { + costm = cos_sigma - 2.0 + * (sin_eta1 * sin_eta2 / PE_SGN(eps, cos2_azeq)); + } else { + costm = cos_sigma - 2.0 * (sin_eta1 * sin_eta2 / cos2_azeq); + /* v18 (Rapp 1.91) */ + } + costm2 = costm * costm; + c = ((-3.0 * cos2_azeq + 4.0) * f + 4.0) * cos2_azeq * f / 16.0; /* + * v10 + * ( + * Rapp + * 1.83 + * ) + */ + } + + /* entry point of the antipodal loop (kind = 2) */ + d = (1.0 - c) + * f + * (sigma + c * sin_sigma + * (costm + cos_sigma * c * (2.0 * costm2 - 1.0))); + /* v11 (Rapp 1.84) */ + + if (kind == 1) { + lam_sph = dlam + d * sin_azeq; + if (PE_ABS(lam_sph - test) < tol) { + q_continue_looping = false; + continue; + } + + if (PE_ABS(lam_sph) > PE_PI) { + kind = 2; + lam_sph = PE_PI; + if (dlam < 0.0) { + lam_sph = -lam_sph; + } + sin_azeq = 0.0; + cos2_azeq = 1.0; + test = 2.0; + prev = test; + + sigma = PE_PI + - PE_ABS(Math.atan(sin_eta1 / cos_eta1) + + Math.atan(sin_eta2 / cos_eta2)); + sin_sigma = Math.sin(sigma); + cos_sigma = Math.cos(sigma); + c = ((-3.0 * cos2_azeq + 4.0) * f + 4.0) * cos2_azeq * f + / 16.0; /* v10 (Rapp 1.83) */ + + if (PE_ABS(sin_azeq - prev) < tol) { + q_continue_looping = false; + continue; + } + if (PE_ABS(cos2_azeq) < eps) /* avoid division by 0 */ + { + costm = cos_sigma + - 2.0 + * (sin_eta1 * sin_eta2 / PE_SGN(eps, cos2_azeq)); + } else { + costm = cos_sigma - 2.0 + * (sin_eta1 * sin_eta2 / cos2_azeq); + /* v18 (Rapp 1.91) */ + } + costm2 = costm * costm; + continue; + } + + if (((lam_sph - test) * (test - prev)) < 0.0 && it > 5) { + /* refined converge */ + lam_sph = (2.0 * lam_sph + 3.0 * test + prev) / 6.0; + } + prev = test; + test = lam_sph; + continue; + } else /* kind == 2 */ + { + sin_azeq = (lam_sph - dlam) / d; + if (((sin_azeq - test) * (test - prev)) < 0.0 && it > 5) { + /* refined converge */ + sin_azeq = (2.0 * sin_azeq + 3.0 * test + prev) / 6.0; + } + prev = test; + test = sin_azeq; + cos2_azeq = 1.0 - sin_azeq * sin_azeq; + sin_lam_sph = sin_azeq * sin_sigma / (cos_eta1 * cos_eta2); + cos_lam_sph = -Math + .sqrt(PE_ABS(1.0 - sin_lam_sph * sin_lam_sph)); + lam_sph = Math.atan2(sin_lam_sph, cos_lam_sph); + tem1 = cos_eta2 * sin_lam_sph; + temp = cos_eta1 * sin_eta2 - sin_eta1 * cos_eta2 * cos_lam_sph; + sin_sigma = Math.sqrt(tem1 * tem1 + temp * temp); + cos_sigma = sin_eta1 * sin_eta2 + cos_eta1 * cos_eta2 + * cos_lam_sph; + sigma = Math.atan2(sin_sigma, cos_sigma); + c = ((-3.0 * cos2_azeq + 4.0) * f + 4.0) * cos2_azeq * f / 16.0; /* + * v10 + * ( + * Rapp + * 1.83 + * ) + */ + if (PE_ABS(sin_azeq - prev) < tol) { + q_continue_looping = false; + continue; + } + if (PE_ABS(cos2_azeq) < eps) /* avoid division by 0 */ + { + costm = cos_sigma - 2.0 + * (sin_eta1 * sin_eta2 / PE_SGN(eps, cos2_azeq)); + } else { + costm = cos_sigma - 2.0 * (sin_eta1 * sin_eta2 / cos2_azeq); + /* v18 (Rapp 1.91) */ + } + costm2 = costm * costm; + continue; + } + } /* End of while q_continue_looping */ + + /* Convergence */ + + if (p_dist != null) { + /* + * Helmert 1880 from Vincenty's + * "Geodetic inverse solution between antipodal points" + */ + + ep2 = 1.0 / (boa * boa) - 1.0; + bige = Math.sqrt(1.0 + ep2 * cos2_azeq); /* 15 */ + bigf = (bige - 1.0) / (bige + 1.0); /* 16 */ + biga = (1.0 + bigf * bigf / 4.0) / (1.0 - bigf); /* 17 */ + bigb = bigf * (1.0 - 0.375 * bigf * bigf); /* 18 */ + z = bigb / 6.0 * costm * (-3.0 + 4.0 * sin_sigma * sin_sigma) + * (-3.0 + 4.0 * costm2); + dsigma = bigb + * sin_sigma + * (costm + bigb / 4.0 + * (cos_sigma * (-1.0 + 2.0 * costm2) - z)); /* 19 */ + dist = (boa * a) * biga * (sigma - dsigma); /* 20 */ + + p_dist.val = dist; + } + + if (p_az12 != null || p_az21 != null) { + if (kind == 2) /* antipodal */ + { + az12 = sin_azeq / cos_eta1; + az21 = Math.sqrt(1.0 - az12 * az12); + if (temp < 0.0) { + az21 = -az21; + } + az12 = Math.atan2(az12, az21); + tem1 = -sin_azeq; + tem2 = sin_eta1 * sin_sigma - cos_eta1 * cos_sigma * az21; + az21 = Math.atan2(tem1, tem2); + } else /* long-line */ + { + tem1 = cos_eta2 * sin_lam_sph; + tem2 = cos_eta1 * sin_eta2 - sin_eta1 * cos_eta2 * cos_lam_sph; + az12 = Math.atan2(tem1, tem2); + tem1 = -cos_eta1 * sin_lam_sph; + tem2 = sin_eta1 * cos_eta2 - cos_eta1 * sin_eta2 * cos_lam_sph; + az21 = Math.atan2(tem1, tem2); + } + + if (p_az12 != null) { + p_az12.val = lam_delta(az12); + } + if (p_az21 != null) { + p_az21.val = lam_delta(az21); + } + } + } +} diff --git a/src/com/esri/core/geometry/GeoJsonImportFlags.java b/src/com/esri/core/geometry/GeoJsonImportFlags.java new file mode 100644 index 00000000..e2539151 --- /dev/null +++ b/src/com/esri/core/geometry/GeoJsonImportFlags.java @@ -0,0 +1,29 @@ +/* + Copyright 1995-2013 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ +package com.esri.core.geometry; + +public interface GeoJsonImportFlags { + static final int geoJsonImportDefaults = 0; + static final int geoJsonImportNonTrusted = 2; +} diff --git a/src/com/esri/core/geometry/GeodeticCurveType.java b/src/com/esri/core/geometry/GeodeticCurveType.java new file mode 100644 index 00000000..b39d57fd --- /dev/null +++ b/src/com/esri/core/geometry/GeodeticCurveType.java @@ -0,0 +1,46 @@ +/* + Copyright 1995-2013 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ +package com.esri.core.geometry; + +/** + * Values for use in Geodetic length and area calculations + */ +interface GeodeticCurveType { + /** + * Shortest distance between two points on an ellipsoide + */ + public final static int Geodesic = 0; + /** + * A line of constant bearing or azimuth. Also known as a rhmub line + */ + public final static int Loxodrome = 1; + /** + * The line on a spheroid defined along the intersection at the surface by a + * plane that passes through the center of the spheroid. When the spheroid + * flattening is equal to zero (sphere) then a Great Elliptic is a Great + * Circle + */ + public final static int GreatElliptic = 2; + public final static int NormalSection = 3; +} diff --git a/src/com/esri/core/geometry/Geometry.java b/src/com/esri/core/geometry/Geometry.java new file mode 100644 index 00000000..c4dea92f --- /dev/null +++ b/src/com/esri/core/geometry/Geometry.java @@ -0,0 +1,600 @@ +/* + Copyright 1995-2013 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ + + +package com.esri.core.geometry; + +import com.esri.core.geometry.VertexDescription.Semantics; +import java.io.ObjectStreamException; +import java.io.Serializable; + +/** + * Common properties and methods shared by all geometric objects. Geometries are + * objects that define a spatial location and and associated geometric shape. + */ +public abstract class Geometry implements Serializable { + // Note: We use writeReplace with GeometrySerializer. This field is + // irrelevant. Need to be removed after final. + private static final long serialVersionUID = 2L; + + VertexDescription m_description; + volatile int m_touchFlag; + + Geometry() { + m_description = null; + m_touchFlag = 0; + } + + /** + * Geometry types + */ + static interface GeometryType { + + public final static int Unknown = 0; + public final static int Point = 1 + 0x20; // points + public final static int Line = 2 + 0x40 + 0x100; // lines, segment + final static int Bezier = 3 + 0x40 + 0x100; // lines, segment + final static int EllipticArc = 4 + 0x40 + 0x100; // lines, segment + public final static int Envelope = 5 + 0x40 + 0x80; // lines, areas + public final static int MultiPoint = 6 + 0x20 + 0x200; // points, + // multivertex + public final static int Polyline = 7 + 0x40 + 0x200 + 0x400; // lines, + // multivertex, + // multipath + public final static int Polygon = 8 + 0x40 + 0x80 + 0x200 + 0x400; + } + + /** + * The type of this geometry. + */ + static public enum Type { + /** + * Used to indicate that the geometry type is not known before executing + * a method. + */ + Unknown(GeometryType.Unknown), + /** + * The value representing a point as geometry type. + */ + + Point(GeometryType.Point), + /** + * The value representing a line as geometry type. + */ + + Line(GeometryType.Line), + /** + * The value representing an envelope as geometry type. + */ + + Envelope(GeometryType.Envelope), + /** + * The value representing a multipoint as geometry type. + */ + + MultiPoint(GeometryType.MultiPoint), + /** + * The value representing a polyline as geometry type. + */ + + Polyline(GeometryType.Polyline), + /** + * The value representing a polygon as geometry type. + */ + + Polygon(GeometryType.Polygon); + + private int enumValue; + + /** + * Returns the integer representation of the enumeration value. + */ + public int value() { + return enumValue; + } + + Type(int val) { + enumValue = val; + } + } + + /** + * Returns the geometry type. + * + * @return Returns the geometry type. + */ + public abstract Geometry.Type getType(); + + /** + * Returns the topological dimension of the geometry object based on the + * geometry's type. + *

+ * Returns 0 for point and multipoint. + *

+ * Returns 1 for lines and polylines. + *

+ * Returns 2 for polygons and envelopes + *

+ * Returns 3 for objects with volume + * + * @return Returns the integer value of the dimension of geometry. + */ + public abstract int getDimension(); + + /** + * Returns the VertexDescription of this geomtry. + */ + public VertexDescription getDescription() { + return m_description; + } + + /** + * Assigns the new VertexDescription by adding or dropping attributes. The + * Geometry will have the src description as a result. + */ + void assignVertexDescription(VertexDescription src) { + _touch(); + if (src == m_description) + return; + + for (int i = Semantics.POSITION; i < Semantics.MAXSEMANTICS; i++) { + if (m_description.hasAttribute(i) && (!src.hasAttribute(i))) + _beforeDropAttributeImpl(i); + } + + VertexDescription olddescription = m_description; + m_description = src; + + for (int i = Semantics.POSITION; i < Semantics.MAXSEMANTICS; i++) { + if (!olddescription.hasAttribute(i) && src.hasAttribute(i)) + _afterAddAttributeImpl(i); + } + } + + /** + * Merges the new VertexDescription by adding missing attributes from the + * src. The Geometry will have a union of the current and the src + * descriptions. + */ + void mergeVertexDescription(VertexDescription src) { + _touch(); + if (src == m_description) + return; + + // check if we need to do anything (if the src has same attributes) + boolean bNeedAction = false; + VertexDescriptionDesignerImpl vdd = null; + for (int i = Semantics.POSITION; i < Semantics.MAXSEMANTICS; i++) { + if (!m_description.hasAttribute(i) && src.hasAttribute(i)) { + if (!bNeedAction) { + bNeedAction = true; + vdd = new VertexDescriptionDesignerImpl(m_description); + } + + vdd.addAttribute(i); + } + } + + if (!bNeedAction) + return; + + VertexDescription olddescription = m_description; + m_description = vdd.getDescription(); + for (int i = Semantics.POSITION; i < Semantics.MAXSEMANTICS; i++) { + if (!olddescription.hasAttribute(i) + && m_description.hasAttribute(i)) { + _afterAddAttributeImpl(i); + } + } + } + + /** + * A shortcut for getDescription().hasAttribute() + */ + public boolean hasAttribute(int semantics) { + return getDescription().hasAttribute(semantics); + } + + /** + * Adds a new attribute to the Geometry. + * + * @param semantics + */ + public void addAttribute(int semantics) { + _touch(); + if (m_description.hasAttribute(semantics)) + return; + + // Create a designer instance out of existing description. + VertexDescriptionDesignerImpl vdd = new VertexDescriptionDesignerImpl( + m_description); + // Add the attribute to the description designer. + vdd.addAttribute(semantics); + // Obtain new description. + m_description = vdd.getDescription(); + // Let it be known we have added the attribute. + _afterAddAttributeImpl(semantics); + } + + /** + * Drops an attribute from the Geometry. Dropping the attribute is + * equivalent to setting the attribute to the default value for each vertex, + * However, it is faster and the result Geometry has smaller memory + * footprint and smaller size when persisted. + */ + public void dropAttribute(int semantics) { + _touch(); + if (!m_description.hasAttribute(semantics)) + return; + + // Create a designer instance out of existing description. + VertexDescriptionDesignerImpl vdd = new VertexDescriptionDesignerImpl( + m_description); + // Remove the attribute from the description designer. + vdd.removeAttribute(semantics); + // Let know we are dropping the attribute. + _beforeDropAttributeImpl(semantics); + // Obtain new description. + m_description = vdd.getDescription(); + } + + /** + * Drops all attributes from the Geometry with exception of POSITON. + */ + public void dropAllAttributes() { + assignVertexDescription(VertexDescriptionDesignerImpl + .getDefaultDescriptor2D()); + } + + /** + * Returns the min and max attribute values at the ordinate of the Geometry + */ + public abstract Envelope1D queryInterval(int semantics, int ordinate); + + /** + * Returns the axis aligned bounding box of the geometry. + * + * @param env + * The envelope to return the result in. + */ + public abstract void queryEnvelope(Envelope env); + + // { + // Envelope2D e2d = new Envelope2D(); + // queryEnvelope2D(e2d); + // env.setEnvelope2D(e2d); + // } + + /** + * Returns tight bbox of the Geometry in X, Y plane. + */ + abstract void queryEnvelope2D(Envelope2D env); + + /** + * Returns tight bbox of the Geometry in 3D. + */ + abstract void queryEnvelope3D(Envelope3D env); + + /** + * Returns the conservative bbox of the Geometry in X, Y plane. This is a + * faster method than QueryEnvelope2D. However, the bbox could be larger + * than the tight box. + */ + void queryLooseEnvelope2D(Envelope2D env) { + queryEnvelope2D(env); + } + + /** + * Returns tight conservative box of the Geometry in 3D. This is a faster + * method than the QueryEnvelope3D. However, the box could be larger than + * the tight box. + */ + void queryLooseEnvelope3D(Envelope3D env) { + queryEnvelope3D(env); + } + + /** + * IsEmpty returns TRUE when the Geometry object does not contain geometric + * information beyond its original initialization state. + * + * @return boolean Returns TRUE if this geometry is empty. + */ + public abstract boolean isEmpty(); + + /** + * Returns the geometry to its original initialization state by releasing + * all data referenced by the geometry. + */ + public abstract void setEmpty(); + + /** + * Applies 2D affine transformation in XY plane. + * + * @param transform + * The affine transformation to be applied to this geometry. + */ + public abstract void applyTransformation(Transformation2D transform); + + /** + * Applies 3D affine transformation. Adds Z attribute if it is missing. + * + * @param transform + * The affine transformation to be applied to this geometry. + */ + abstract void applyTransformation(Transformation3D transform); + + /** + * Creates an instance of an empty geometry of the same type. + */ + public abstract Geometry createInstance(); + + /** + * Copies this geometry to another geometry of the same type. The result + * geometry is an exact copy. + * + * @exception GeometryException + * invalid_argument if the geometry is of different type. + */ + public abstract void copyTo(Geometry dst); + + /** + * Calculates the area of the geometry. If the spatial reference is a + * Geographic Coordinate System (WGS84) then the 2D area calculation is + * defined in angular units. + * + * @return A double value representing the 2D area of the geometry. + */ + public double calculateArea2D() { + return 0; + } + + /** + * Calculates the length of the geometry. If the spatial reference is a + * Geographic Coordinate System (a system where coordinates are defined + * using angular units such as longitude and latitude) then the 2D distance + * calculation is returned in angular units. In cases where length must be + * calculated on a Geographic Coordinate System consider the using the + * geodeticLength method on the {@link GeometryEngine} + * + * @return A double value representing the 2D length of the geometry. + */ + public double calculateLength2D() { + return 0; + } + + /* + * Called before the new description pointer is set. Caller is supposed to + * get rid of the Geometry attribute value. + */ + abstract void _beforeDropAttributeImpl(int semantics); + + /* + * Called after the new description pointer is set. Caller is supposed to + * add the Geometry attribute value. + */ + abstract void _afterAddAttributeImpl(int semantics); + + protected Object _getImpl() { + throw new RuntimeException("invalid call"); + } + + /** + * Adds the Z attribute to this Geometry + */ + void addZ() { + addAttribute(VertexDescription.Semantics.Z); + } + + /** + * Returns true if this Geometry has the Z attribute + * + * @return true if this Geometry has the Z attribute + */ + public boolean hasZ() { + return hasAttribute(VertexDescription.Semantics.Z); + } + + /** + * Adds the M attribute to this Geometry + */ + public void addM() { + addAttribute(VertexDescription.Semantics.M); + } + + /** + * Returns true if this Geometry has an M attribute + * + * @return true if this Geometry has an M attribute + */ + public boolean hasM() { + return hasAttribute(VertexDescription.Semantics.M); + } + + /** + * Adds the ID attribute to this Geometry + */ + public void addID() { + addAttribute(VertexDescription.Semantics.ID); + } + + /** + * Returns true if this Geometry has an ID attribute + * + * @return true if this Geometry has an ID attribute + */ + public boolean hasID() { + return hasAttribute(VertexDescription.Semantics.ID); + } + + /** + * Returns this geometry's dimension. + *

+ * Returns 0 for point and multipoint. + *

+ * Returns 1 for lines and polylines. + *

+ * Returns 2 for polygons and envelopes + *

+ * Returns 3 for objects with volume + * + * @param type + * The integer value from geometry enumeration. You can use the + * method {@link Type#value()} to get at the integer value. + * @return The integer dimension of this geometry. + */ + public static int getDimensionFromType(int type) { + return (((type & (0x40 | 0x80)) >> 6) + 1) >> 1; + } + + /** + * Indicates if the integer value of the enumeration is a point type + * (dimension 0). + * + * @param type + * The integer value from geometry enumeration. You can use the + * method {@link Type#value()} to get at the integer value. + * @return TRUE if the geometry is a point. + */ + public static boolean isPoint(int type) { + return (type & 0x20) != 0; + } + + /** + * Indicates if the integer value of the enumeration is linear (dimension + * 1). + * + * @param type + * The integer value from geometry enumeration. You can use the + * method {@link Type#value()} to get at the integer value. + * @return TRUE if the geometry is a line. + */ + public static boolean isLinear(int type) { + return (type & 0x40) != 0; + } + + /** + * Indicates if the integer value of the enumeration is an area (dimension + * 2). + * + * @param type + * The integer value from geometry enumeration. You can use the + * method {@link Type#value()} to get at the integer value. + * @return TRUE if the geometry is a polygon. + */ + public static boolean isArea(int type) { + return (type & 0x80) != 0; + } + + /** + * Indicates if the integer value of the enumeration is a segment. + * + * @param type + * The integer value from geometry enumeration. You can use the + * method {@link Type#value()} to get at the integer value. + * @return TRUE if the geometry is a segment. + */ + public static boolean isSegment(int type) { + return (type & 0x100) != 0; + } + + /** + * Indicates if the integer value of the enumeration is a multivertex (ie, + * multipoint, line, or area). + * + * @param type + * The integer value from geometry enumeration. You can use the + * method {@link Type#value()} to get at the integer value. + * @return TRUE if the geometry has multiple vertices. + */ + public static boolean isMultiVertex(int type) { + return (type & 0x200) != 0; + } + + /** + * Indicates if the integer value of the enumeration is a multipath (ie, + * line or area). + * + * @param type + * The integer value from geometry enumeration. You can use the + * method {@link Type#value()} to get at the integer value. + * @return TRUE if the geometry is a multipath. + */ + public static boolean isMultiPath(int type) { + return (type & 0x400) != 0; + } + + /** + * Creates a copy of the geometry. + * + * @return Returns a copy of this geometry. + */ + public Geometry copy() { + Geometry geom = createInstance(); + this.copyTo(geom); + return geom; + } + + static Geometry _clone(Geometry src) { + Geometry geom = src.createInstance(); + src.copyTo(geom); + return geom; + } + + /** + * The stateFlag value changes with changes applied to this geometry. This + * allows the user to keep track of the geometry's state. + * + * @return The state of the geometry. + */ + public int getStateFlag() { + m_touchFlag &= 0x7FFFFFFF; + return m_touchFlag; + } + + // Called whenever geometry changes + synchronized void _touch() { + if (m_touchFlag >= 0) { + m_touchFlag += 0x80000001; + } + } + + /** + * Describes the degree of acceleration of the geometry. + */ + enum GeometryAccelerationDegree { + enumMild, // 0 ? SpatialReference.create(wkid) : null, geometry); + } + + /** + * Exports the specified geometry instance to it's JSON representation. M + * and Z values are not imported from JSON representation. + * + * @param spatialReference + * The spatial reference of associated object. + * @param geometry + * The geometry. + * @return The JSON representation of the specified geometry. + */ + public static String geometryToJson(SpatialReference spatialReference, + Geometry geometry) { + OperatorExportToJson exporter = (OperatorExportToJson) factory + .getOperator(Operator.Type.ExportToJson); + + return exporter.execute(spatialReference, geometry); + } + + /** + * Imports geometry from the ESRI shape file format. + * + * @param esriShapeBuffer + * The buffer containing geometry in the ESRI shape file format. + * @param geometryType + * The required type of the Geometry to be imported. Use + * Geometry.Type.Unknown if the geometry type needs to be + * determined from the buffer content. + * @return The geometry or null if the buffer contains null shape. + * @throws GeometryException + * when the geometryType is not Geometry.Type.Unknown and the + * buffer contains geometry that cannot be converted to the + * given geometryType. or the buffer is corrupt. Another + * exception possible is IllegalArgumentsException. + */ + public static Geometry geometryFromEsriShape(byte[] esriShapeBuffer, + Geometry.Type geometryType) { + OperatorImportFromESRIShape op = (OperatorImportFromESRIShape) factory + .getOperator(Operator.Type.ImportFromESRIShape); + return op + .execute( + ShapeImportFlags.ShapeImportNonTrusted, + geometryType, + ByteBuffer.wrap(esriShapeBuffer).order( + ByteOrder.LITTLE_ENDIAN)); + } + + /** + * Exports geometry to the ESRI shape file format. + * + * @param geometry + * The geometry to export. (null value is not allowed) + * @return Array containing the exported ESRI shape file. + */ + public static byte[] geometryToEsriShape(Geometry geometry) { + if (geometry == null) + throw new IllegalArgumentException(); + OperatorExportToESRIShape op = (OperatorExportToESRIShape) factory + .getOperator(Operator.Type.ExportToESRIShape); + return op.execute(0, geometry).array(); + } + + /** + * Imports a geometry from a WKT string. + * @param wkt The string containing the geometry in WKT format. + * @param importFlags Use the {@link WktImportFlags} interface. + * @param geometryType The required type of the Geometry to be imported. Use Geometry.Type.Unknown if the geometry type needs to be determined from the WKT context. + * @return The geometry. + * @throws GeometryException when the geometryType is not Geometry.Type.Unknown and the WKT contains a geometry that cannot be converted to the given geometryType. + * @throws IllegalArgument exception if an error is found while parsing the WKT string. + */ + public static Geometry geometryFromWkt(String wkt, int importFlags, + Geometry.Type geometryType) { + OperatorImportFromWkt op = (OperatorImportFromWkt) factory + .getOperator(Operator.Type.ImportFromWkt); + return op.execute(importFlags, geometryType, wkt, null); + } + + /** + * Imports a geometry from a geoJson string. + * @param geoJson The string containing the geometry in geoJson format. + * @param importFlags Use the {@link geoJsonImportFlags} interface. + * @param geometryType The required type of the Geometry to be imported. Use Geometry.Type.Unknown if the geometry type needs to be determined from the geoJson context. + * @return The geometry. + * @throws GeometryException when the geometryType is not Geometry.Type.Unknown and the geoJson contains a geometry that cannot be converted to the given geometryType. + * @throws IllegalArgument exception if an error is found while parsing the geoJson string. + */ + public static MapGeometry geometryFromGeoJson(String geoJson, + int importFlags, Geometry.Type geometryType) throws JSONException { + OperatorImportFromGeoJson op = (OperatorImportFromGeoJson) factory + .getOperator(Operator.Type.ImportFromGeoJson); + return op.execute(importFlags, geometryType, geoJson, null); + } + + /** + * Exports a geometry to a string in WKT format. + * @param geometry The geometry to export. (null value is not allowed) + * @param exportFlags Use the {@link WktExportFlags} interface. + * @return A String containing the exported geometry in WKT format. + */ + public static String geometryToWkt(Geometry geometry, int exportFlags) { + OperatorExportToWkt op = (OperatorExportToWkt) factory + .getOperator(Operator.Type.ExportToWkt); + return op.execute(exportFlags, geometry, null); + } + + /** + * Constructs a new geometry by union an array of geometries. All inputs + * must be of the same type of geometries and share one spatial reference. + * + * @param geometries + * The geometries to union. + * @param spatialReference + * The spatial reference of the geometries. + * @return The geometry object representing the resultant union. + */ + public static Geometry union(Geometry[] geometries, + SpatialReference spatialReference) { + OperatorUnion op = (OperatorUnion) factory + .getOperator(Operator.Type.Union); + + SimpleGeometryCursor inputGeometries = new SimpleGeometryCursor( + geometries); + GeometryCursor result = op.execute(inputGeometries, spatialReference, + null); + return result.next(); + } + + // TODO Remove this method from geometry engine + /** + * constructs the set-theoretic difference between an array of geometries + * and another geometry. The dimension of the input geometry has to be equal + * to or greater than that of any element in the array of "geometries". + * + * @param inputGeometries + * an array of geometry objects being subtracted + * @param subtractor + * geometry object to subtract from + * @return any array of geometry objects showing the difference + * */ + static Geometry[] difference(Geometry[] inputGeometries, + Geometry subtractor, SpatialReference spatialReference) { + OperatorDifference op = (OperatorDifference) factory + .getOperator(Operator.Type.Difference); + SimpleGeometryCursor inputGeometriesCursor = new SimpleGeometryCursor( + inputGeometries); + SimpleGeometryCursor subtractorCursor = new SimpleGeometryCursor( + subtractor); + GeometryCursor result = op.execute(inputGeometriesCursor, + subtractorCursor, spatialReference, null); + + ArrayList resultGeoms = new ArrayList(); + Geometry g; + while ((g = result.next()) != null) { + resultGeoms.add(g); + } + Geometry[] resultarr = resultGeoms.toArray(new Geometry[0]); + return resultarr; + } + + /** + * Creates the difference of two geometries. The dimension of geometry2 has + * to be equal to or greater than that of geometry1. + * + * @param geometry1 + * The geometry being subtracted. + * @param substractor + * The geometry object to subtract from. + * @param spatialReference + * The spatial reference of the geometries. + * @return The geometry of the differences. + */ + public static Geometry difference(Geometry geometry1, Geometry substractor, + SpatialReference spatialReference) { + OperatorDifference op = (OperatorDifference) factory + .getOperator(Operator.Type.Difference); + Geometry result = op.execute(geometry1, substractor, spatialReference, + null); + return result; + } + + /** + * Creates the symmetric difference of two geometries. + * + * @param leftGeometry + * is one of the Geometry instances in the XOR operation. + * @param rightGeometry + * is one of the Geometry instances in the XOR operation. + * @param spatialReference + * The spatial reference of the geometries. + * @return Returns the result of the symmetric difference. + */ + public static Geometry symmetricDifference(Geometry leftGeometry, + Geometry rightGeometry, SpatialReference spatialReference) { + OperatorSymmetricDifference op = (OperatorSymmetricDifference) factory + .getOperator(Operator.Type.SymmetricDifference); + Geometry result = op.execute(leftGeometry, rightGeometry, + spatialReference, null); + return result; + } + + /** + * Indicates if two geometries are equal. + * + * @param geometry1 + * Geometry. + * @param geometry2 + * Geometry. + * @param spatialReference + * The spatial reference of the geometries. + * @return TRUE if both geometry objects are equal. + */ + public static boolean equals(Geometry geometry1, Geometry geometry2, + SpatialReference spatialReference) { + OperatorEquals op = (OperatorEquals) factory + .getOperator(Operator.Type.Equals); + boolean result = op.execute(geometry1, geometry2, spatialReference, + null); + return result; + } + + public static boolean disjoint(Geometry geometry1, Geometry geometry2, + SpatialReference spatialReference) { + OperatorDisjoint op = (OperatorDisjoint) factory + .getOperator(Operator.Type.Disjoint); + boolean result = op.execute(geometry1, geometry2, spatialReference, + null); + return result; + } + + /** + * Constructs the set-theoretic intersection between an array of geometries + * and another geometry. + * + * @param inputGeometries + * An array of geometry objects. + * @param geometry + * The geometry object. + * @return Any array of geometry objects showing the intersection. + */ + static Geometry[] intersect(Geometry[] inputGeometries, Geometry geometry, + SpatialReference spatialReference) { + OperatorIntersection op = (OperatorIntersection) factory + .getOperator(Operator.Type.Intersection); + SimpleGeometryCursor inputGeometriesCursor = new SimpleGeometryCursor( + inputGeometries); + SimpleGeometryCursor intersectorCursor = new SimpleGeometryCursor( + geometry); + GeometryCursor result = op.execute(inputGeometriesCursor, + intersectorCursor, spatialReference, null); + + ArrayList resultGeoms = new ArrayList(); + Geometry g; + while ((g = result.next()) != null) { + resultGeoms.add(g); + } + + Geometry[] resultarr = resultGeoms.toArray(new Geometry[0]); + return resultarr; + } + + /** + * Creates a geometry through intersection between two geometries. + * + * @param geometry1 + * The first geometry. + * @param intersector + * The geometry to intersect the first geometry. + * @param spatialReference + * The spatial reference of the geometries. + * @return The geometry created through intersection. + */ + public static Geometry intersect(Geometry geometry1, Geometry intersector, + SpatialReference spatialReference) { + OperatorIntersection op = (OperatorIntersection) factory + .getOperator(Operator.Type.Intersection); + Geometry result = op.execute(geometry1, intersector, spatialReference, + null); + return result; + } + + /** + * Indicates if one geometry is within another geometry. + * + * @param geometry1 + * The base geometry that is tested for within relationship to + * the other geometry. + * @param geometry2 + * The comparison geometry that is tested for the contains + * relationship to the other geometry. + * @param spatialReference + * The spatial reference of the geometries. + * @return TRUE if the first geometry is within the other geometry. + */ + public static boolean within(Geometry geometry1, Geometry geometry2, + SpatialReference spatialReference) { + OperatorWithin op = (OperatorWithin) factory + .getOperator(Operator.Type.Within); + boolean result = op.execute(geometry1, geometry2, spatialReference, + null); + return result; + } + + /** + * Indicates if one geometry contains another geometry. + * + * @param geometry1 + * The geometry that is tested for the contains relationship to + * the other geometry.. + * @param geometry2 + * The geometry that is tested for within relationship to the + * other geometry. + * @param spatialReference + * The spatial reference of the geometries. + * @return TRUE if geometry1 contains geometry2. + */ + public static boolean contains(Geometry geometry1, Geometry geometry2, + SpatialReference spatialReference) { + OperatorContains op = (OperatorContains) factory + .getOperator(Operator.Type.Contains); + boolean result = op.execute(geometry1, geometry2, spatialReference, + null); + return result; + } + + /** + * Indicates if one geometry crosses another geometry. + * + * @param geometry1 + * The geometry to cross. + * @param geometry2 + * The geometry being crossed. + * @param spatialReference + * The spatial reference of the geometries. + * @return TRUE if geometry1 crosses geometry2. + */ + public static boolean crosses(Geometry geometry1, Geometry geometry2, + SpatialReference spatialReference) { + OperatorCrosses op = (OperatorCrosses) factory + .getOperator(Operator.Type.Crosses); + boolean result = op.execute(geometry1, geometry2, spatialReference, + null); + return result; + } + + /** + * Indicates if one geometry touches another geometry. + * + * @param geometry1 + * The geometry to touch. + * @param geometry2 + * The geometry to be touched. + * @param spatialReference + * The spatial reference of the geometries. + * @return TRUE if geometry1 touches geometry2. + */ + public static boolean touches(Geometry geometry1, Geometry geometry2, + SpatialReference spatialReference) { + OperatorTouches op = (OperatorTouches) factory + .getOperator(Operator.Type.Touches); + boolean result = op.execute(geometry1, geometry2, spatialReference, + null); + return result; + } + + /** + * Indicates if one geometry overlaps another geometry. + * + * @param geometry1 + * The geometry to overlap. + * @param geometry2 + * The geometry to be overlapped. + * @param spatialReference + * The spatial reference of the geometries. + * @return TRUE if geometry1 overlaps geometry2. + */ + public static boolean overlaps(Geometry geometry1, Geometry geometry2, + SpatialReference spatialReference) { + OperatorOverlaps op = (OperatorOverlaps) factory + .getOperator(Operator.Type.Overlaps); + boolean result = op.execute(geometry1, geometry2, spatialReference, + null); + return result; + } + + /** + * Indicates if the given relation holds for the two geometries. + * + * @param geometry1 + * The first geometry for the relation. + * @param geometry2 + * The second geometry for the relation. + * @param spatialReference + * The spatial reference of the geometries. + * @param relation + * The DE-9IM relation. + * @return TRUE if the given relation holds between geometry1 and geometry2. + */ + public static boolean relate(Geometry geometry1, Geometry geometry2, + SpatialReference spatialReference, String relation) { + OperatorRelate op = (OperatorRelate) factory + .getOperator(Operator.Type.Relate); + boolean result = op.execute(geometry1, geometry2, spatialReference, + relation, null); + return result; + } + + /** + * Calculates the 2D planar distance between two geometries. + * + * @param geometry1 + * Geometry. + * @param geometry2 + * Geometry. + * @param spatialReference + * The spatial reference of the geometries. This parameter is not + * used and can be null. + * @return The distance between the two geometries. + */ + public static double distance(Geometry geometry1, Geometry geometry2, + SpatialReference spatialReference) { + OperatorDistance op = (OperatorDistance) factory + .getOperator(Operator.Type.Distance); + double result = op.execute(geometry1, geometry2, null); + return result; + } + + /** + * Calculates the clipped geometry from a target geometry using an envelope. + * + * @param geometry + * The geometry to be clipped. + * @param envelope + * The envelope used to clip. + * @param spatialReference + * The spatial reference of the geometries. + * @return The geometry created by clipping. + */ + public static Geometry clip(Geometry geometry, Envelope envelope, + SpatialReference spatialReference) { + OperatorClip op = (OperatorClip) factory + .getOperator(Operator.Type.Clip); + Geometry result = op.execute(geometry, Envelope2D.construct( + envelope.getXMin(), envelope.getYMin(), envelope.getXMax(), + envelope.getYMax()), spatialReference, null); + return result; + } + + /** + * Calculates the cut geometry from a target geometry using a polyline. For + * Polylines, all left cuts will be grouped together in the first Geometry, + * Right cuts and coincident cuts are grouped in the second Geometry, and + * each undefined cut, along with any uncut parts, are output as separate + * Polylines. For Polygons, all left cuts are grouped in the first Polygon, + * all right cuts are in the second Polygon, and each undefined cut, along + * with any left-over parts after cutting, are output as a separate Polygon. + * If there were no cuts then the array will be empty. An undefined cut will + * only be produced if a left cut or right cut was produced, and there was a + * part left over after cutting or a cut is bounded to the left and right of + * the cutter. + * + * @param cuttee + * The geometry to be cut. + * @param cutter + * The polyline to cut the geometry. + * @param spatialReference + * The spatial reference of the geometries. + * @return An array of geometries created from cutting. + */ + public static Geometry[] cut(Geometry cuttee, Polyline cutter, + SpatialReference spatialReference) { + if (cuttee == null || cutter == null) + return null; + + OperatorCut op = (OperatorCut) factory.getOperator(Operator.Type.Cut); + GeometryCursor cursor = op.execute(true, cuttee, cutter, + spatialReference, null); + ArrayList cutsList = new ArrayList(); + + Geometry geometry; + while ((geometry = cursor.next()) != null) { + if (!geometry.isEmpty()) { + cutsList.add(geometry); + } + } + + return cutsList.toArray(new Geometry[0]); + } + + /** + * Calculates a buffer polygon for each geometry at each of the + * corresponding specified distances. It is assumed all geometries have + * the same spatial reference. The unit variable defines the unit of + * all the distances. If unit == null then the unit of distances is assumed + * to be that of the spatial reference. There is an option to union the + * returned geometries. + * @param geometries An array of geometries to be buffered. + * @param spatialReference The spatial reference of the geometries. + * @param distances The corresponding distances for the input geometries to be buffered. + * @param unit The unit of the values in the distances array. Must be of the same unit type as spatial reference. + * @param toUnionResults TRUE if all geometries buffered at a given distance are to be unioned into a single polygon. + * @return The buffer of the geometries. + * */ + public static Polygon[] buffer(Geometry[] geometries, + SpatialReference spatialReference, double[] distances, + boolean toUnionResults) { + // initially assume distances are in unit of spatial reference + double[] bufferDistances = distances; + + OperatorBuffer op = (OperatorBuffer) factory + .getOperator(Operator.Type.Buffer); + + if (toUnionResults) { + SimpleGeometryCursor inputGeometriesCursor = new SimpleGeometryCursor( + geometries); + GeometryCursor result = op.execute(inputGeometriesCursor, + spatialReference, bufferDistances, toUnionResults, null); + + ArrayList resultGeoms = new ArrayList(); + Geometry g; + while ((g = result.next()) != null) { + resultGeoms.add((Polygon) g); + } + Polygon[] buffers = resultGeoms.toArray(new Polygon[0]); + return buffers; + } else { + Polygon[] buffers = new Polygon[geometries.length]; + for (int i = 0; i < geometries.length; i++) { + buffers[i] = (Polygon) op.execute(geometries[i], + spatialReference, bufferDistances[i], null); + } + return buffers; + } + } + + /** + * Calculates a buffer polygon of the geometry as specified by the + * distance input. If unit == null, then the distance is assumed + * to be in the unit of the spatial reference. + * @param geometry Geometry to be buffered. + * @param spatialReference The spatial reference of the geometry. + * @param distance The specified distance for buffer. + * @param unit The unit of the values in the distances array. Must be of the same unit type as spatial reference. + * @return The buffer polygon at the specified distances. + * */ + public static Polygon buffer(Geometry geometry, + SpatialReference spatialReference, double distance) { + double bufferDistance = distance; + + OperatorBuffer op = (OperatorBuffer) factory + .getOperator(Operator.Type.Buffer); + Geometry result = op.execute(geometry, spatialReference, + bufferDistance, null); + return (Polygon) result; + } + + /** + * Calculates the convex hull geometry. + * + * @param geometry + * The input geometry. \return Returns the convex hull. + * + * For a Point - returns the same point. For an Envelope - + * returns the same envelope. For a MultiPoint - If the point + * count is one, returns the same multipoint. If the point count + * is two, returns a polyline of the points. Otherwise computes + * and returns the convex hull polygon. For a Segment - returns a + * polyline consisting of the segment. For a Polyline - If + * consists of only one segment, returns the same polyline. + * Otherwise computes and returns the convex hull polygon. For a + * Polygon - If more than one path, or if the path isn't already + * convex, computes and returns the convex hull polygon. + * Otherwise returns the same polygon. + */ + public static Geometry convexHull(Geometry geometry) { + OperatorConvexHull op = (OperatorConvexHull) factory + .getOperator(Operator.Type.ConvexHull); + return op.execute(geometry, null); + } + + /** + * Calculates the convex hull. + * + * @param geometries + * The input geometry array. + * @param b_merge + * Put true if you want the convex hull of all the geometries in + * the array combined. Put false if you want the convex hull of + * each geometry in the array individually. + * @return Returns an array of convex hulls. If b_merge is true, the result + * will be a one element array consisting of the merged convex hull. + */ + public static Geometry[] convexHull(Geometry[] geometries, boolean b_merge) { + OperatorConvexHull op = (OperatorConvexHull) factory + .getOperator(Operator.Type.ConvexHull); + SimpleGeometryCursor simple_cursor = new SimpleGeometryCursor( + geometries); + GeometryCursor cursor = op.execute(simple_cursor, b_merge, null); + + ArrayList resultGeoms = new ArrayList(); + Geometry g; + while ((g = cursor.next()) != null) { + resultGeoms.add(g); + } + + Geometry[] output = new Geometry[resultGeoms.size()]; + + for (int i = 0; i < resultGeoms.size(); i++) + output[i] = resultGeoms.get(i); + + return output; + } + + /** + * Finds the coordinate of the geometry which is closest to the specified + * point. + * + * @param inputPoint + * The point to find the nearest coordinate in the geometry for. + * @param geometry + * The geometry to consider. + * @return Proximity2DResult containing the nearest coordinate. + * */ + public static Proximity2DResult getNearestCoordinate(Geometry geometry, + Point inputPoint, boolean bTestPolygonInterior) { + + OperatorProximity2D proximity = (OperatorProximity2D) factory + .getOperator(com.esri.core.geometry.Operator.Type.Proximity2D); + Proximity2DResult result = proximity.getNearestCoordinate(geometry, + inputPoint, bTestPolygonInterior); + return result; + } + + /** + * Finds nearest vertex on the geometry which is closed to the specified + * point. + * + * @param inputPoint + * The point to find the nearest vertex of the geometry for. + * @param geometry + * The geometry to consider. + * @return Proximity2DResult containing the nearest vertex. + * */ + public static Proximity2DResult getNearestVertex(Geometry geometry, + Point inputPoint) { + OperatorProximity2D proximity = (OperatorProximity2D) factory + .getOperator(com.esri.core.geometry.Operator.Type.Proximity2D); + Proximity2DResult result = proximity.getNearestVertex(geometry, + inputPoint); + return result; + } + + /** + * Finds all vertices in the given distance from the specified point, sorted + * from the closest to the furthest. + * + * @param inputPoint + * The point to start from. + * @param geometry + * The geometry to consider. + * @param searchRadius + * The search radius. + * @param maxVertexCountToReturn + * The maximum number number of vertices to return. + * @return Proximity2DResult containing the array of nearest vertices. + * */ + public static Proximity2DResult[] getNearestVertices(Geometry geometry, + Point inputPoint, double searchRadius, int maxVertexCountToReturn) { + OperatorProximity2D proximity = (OperatorProximity2D) factory + .getOperator(com.esri.core.geometry.Operator.Type.Proximity2D); + + Proximity2DResult[] results = proximity.getNearestVertices(geometry, + inputPoint, searchRadius, maxVertexCountToReturn); + + return results; + } + + /** + * Performs the simplify operation on the geometry. + * + * @param geometry + * The geometry to be simplified. + * @param spatialReference + * The spatial reference of the geometry to be simplified. + * @return The simplified geometry. + */ + public static Geometry simplify(Geometry geometry, + SpatialReference spatialReference) { + OperatorSimplify op = (OperatorSimplify) factory + .getOperator(Operator.Type.Simplify); + Geometry result = op.execute(geometry, spatialReference, false, null); + return result; + } + + /** + * Checks if the Geometry is simple. + * + * @param geometry + * The geometry to be checked. + * @param spatialReference + * The spatial reference of the geometry. + * @return TRUE if the geometry is simple. + */ + static boolean isSimple(Geometry geometry, SpatialReference spatialReference) { + OperatorSimplify op = (OperatorSimplify) factory + .getOperator(Operator.Type.Simplify); + boolean result = op.isSimpleAsFeature(geometry, spatialReference, null); + return result; + } + + /** + * A geodesic distance is the shortest distance between any two points on the earth's surface when the earth's + * surface is approximated by a spheroid. The function returns the shortest distance between two points on the + * WGS84 spheroid. + * @param ptFrom The "from" point: long, lat in degrees. + * @param ptTo The "to" point: long, lat in degrees. + * @return The geodesic distance between two points in meters. + */ + public static double geodesicDistanceOnWGS84(Point ptFrom, Point ptTo) { + return SpatialReferenceImpl.geodesicDistanceOnWGS84Impl(ptFrom, ptTo); + } +} diff --git a/src/com/esri/core/geometry/GeometryException.java b/src/com/esri/core/geometry/GeometryException.java new file mode 100644 index 00000000..23e86829 --- /dev/null +++ b/src/com/esri/core/geometry/GeometryException.java @@ -0,0 +1,55 @@ +/* + Copyright 1995-2013 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ + + +package com.esri.core.geometry; + +/** + * A runtime exception raised when a geometry related exception occurs. + */ +public class GeometryException extends RuntimeException { + + private static final long serialVersionUID = 1L; + /** + * The internal code for geometry exception. + */ + public int internalCode; + + /** + * Constructs a Geometry Exception with the given error string/message. + * + * @param str + * - The error string. + */ + GeometryException(String str) { + super(str); + internalCode = 0; + } + + GeometryException(String str, int sgCode) { + super(str); + internalCode = sgCode; + } + +} diff --git a/src/com/esri/core/geometry/GeometrySerializer.java b/src/com/esri/core/geometry/GeometrySerializer.java new file mode 100644 index 00000000..8bf9f401 --- /dev/null +++ b/src/com/esri/core/geometry/GeometrySerializer.java @@ -0,0 +1,116 @@ +/* + Copyright 1995-2013 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ +package com.esri.core.geometry; + +import java.io.InvalidObjectException; +import java.io.ObjectStreamException; +import java.io.Serializable; + +final class GeometrySerializer implements Serializable { + private static final long serialVersionUID = 1L; + + static class BaseGeometryData implements Serializable { + Geometry.Type geometryType; + byte[] esriShape = null; + } + + static class MultiVertexData extends BaseGeometryData { + int simpleFlag = 0; + double tolerance = 0; + } + + static class MultiPathData extends MultiVertexData { + boolean[] ogcFlags = null; + } + + BaseGeometryData geometryData; + + Object readResolve() throws ObjectStreamException { + Geometry geometry = null; + try { + geometry = GeometryEngine.geometryFromEsriShape( + geometryData.esriShape, geometryData.geometryType); + if (Geometry.isMultiVertex(geometry.getType().value())) { + MultiVertexData mvd = (MultiVertexData) geometryData; + MultiVertexGeometryImpl mvImpl = (MultiVertexGeometryImpl) geometry + ._getImpl(); + if (!geometry.isEmpty() + && Geometry.isMultiPath(geometry.getType().value())) { + MultiPathData mpd = (MultiPathData) geometryData; + MultiPathImpl mpImpl = (MultiPathImpl) geometry._getImpl(); + AttributeStreamOfInt8 pathFlags = mpImpl + .getPathFlagsStreamRef(); + for (int i = 0, n = mpImpl.getPathCount(); i < n; i++) { + if (mpd.ogcFlags[i]) + pathFlags.setBits(i, + (byte) PathFlags.enumOGCStartPolygon); + } + } + mvImpl.setIsSimple(mvd.simpleFlag, mvd.tolerance, false); + } + + } catch (Exception ex) { + throw new InvalidObjectException("Cannot read geometry from stream"); + } + return geometry; + } + + public void setGeometryByValue(Geometry geometry) + throws ObjectStreamException { + try { + if (Geometry.isMultiPath(geometry.getType().value())) { + geometryData = new MultiPathData(); + } else if (Geometry.isMultiVertex(geometry.getType().value())) { + geometryData = new MultiVertexData(); + } else { + geometryData = new BaseGeometryData(); + } + geometryData.esriShape = GeometryEngine + .geometryToEsriShape(geometry); + geometryData.geometryType = geometry.getType(); + if (Geometry.isMultiVertex(geometryData.geometryType.value())) { + MultiVertexData mvd = (MultiVertexData) geometryData; + MultiVertexGeometryImpl mvImpl = (MultiVertexGeometryImpl) geometry + ._getImpl(); + mvd.tolerance = mvImpl.m_simpleTolerance; + mvd.simpleFlag = mvImpl.getIsSimple(0); + if (!geometry.isEmpty() + && Geometry.isMultiPath(geometryData.geometryType + .value())) { + MultiPathData mpd = (MultiPathData) geometryData; + MultiPathImpl mpImpl = (MultiPathImpl) geometry._getImpl(); + mpd.ogcFlags = new boolean[mpImpl.getPathCount()]; + AttributeStreamOfInt8 pathFlags = mpImpl + .getPathFlagsStreamRef(); + for (int i = 0, n = mpImpl.getPathCount(); i < n; i++) { + mpd.ogcFlags[i] = (pathFlags.read(i) & (byte) PathFlags.enumOGCStartPolygon) != 0; + } + } + + } + } catch (Exception ex) { + throw new InvalidObjectException("Cannot serialize this geometry"); + } + } +} diff --git a/src/com/esri/core/geometry/IndexHashTable.java b/src/com/esri/core/geometry/IndexHashTable.java new file mode 100644 index 00000000..c9189770 --- /dev/null +++ b/src/com/esri/core/geometry/IndexHashTable.java @@ -0,0 +1,235 @@ +/* + Copyright 1995-2013 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ +package com.esri.core.geometry; + +class IndexHashTable { + // The hash function abstract class that user need to define to use the + // IndexHashTable. + public static abstract class HashFunction { + public abstract int getHash(int element); + + public abstract boolean equal(int element1, int element2); + + public abstract int getHash(Object elementDescriptor); + + public abstract boolean equal(Object elementDescriptor, int element); + } + + int m_random; + AttributeStreamOfInt32 m_hashBuckets; + IndexMultiList m_lists; + HashFunction m_hash; + + // Create hash table. size is the bin count in the table. The hashFunction + // is the function to use. + public IndexHashTable(int size, HashFunction hashFunction) { + m_hashBuckets = new AttributeStreamOfInt32(size, nullNode()); + m_lists = new IndexMultiList(); + m_hash = hashFunction; + } + + public void reserveElements(int capacity) { + m_lists.reserveLists(Math.min(m_hashBuckets.size(), capacity)); + m_lists.reserveNodes(capacity); + } + + // Adds new element to the hash table. + public void addElement(int element) { + int hash = m_hash.getHash(element); + int bucket = hash % m_hashBuckets.size(); + int list = m_hashBuckets.get(bucket); + if (list == IndexMultiList.nullNode()) { + list = m_lists.createList(); + m_hashBuckets.set(bucket, list); + } + m_lists.addElement(list, element); + } + + // Removes element from the hash table. + public void deleteElement(int element) { + int hash = m_hash.getHash(element); + int bucket = hash % m_hashBuckets.size(); + int list = m_hashBuckets.get(bucket); + if (list == IndexMultiList.nullNode()) + throw new IllegalArgumentException(); + + int ptr = m_lists.getFirst(list); + int prev = IndexMultiList.nullNode(); + while (ptr != IndexMultiList.nullNode()) { + int e = m_lists.getElement(ptr); + int nextptr = m_lists.getNext(ptr); + if (e == element) { + m_lists.deleteElement(list, prev, ptr); + if (m_lists.getFirst(list) == IndexMultiList.nullNode()) { + m_lists.deleteList(list);// do not keep empty lists + m_hashBuckets.set(bucket, IndexMultiList.nullNode()); + } + } else { + prev = ptr; + } + ptr = nextptr; + } + + } + + // Returns the first node in the hash table bucket defined by the given + // hashValue. + public int getFirstInBucket(int hashValue) { + int bucket = hashValue % m_hashBuckets.size(); + int list = m_hashBuckets.get(bucket); + if (list == IndexMultiList.nullNode()) + return IndexMultiList.nullNode(); + + return m_lists.getFirst(list); + + } + + // Returns next node in a bucket. Can be used together with GetFirstInBucket + // only. + public int getNextInBucket(int elementHandle) { + return m_lists.getNext(elementHandle); + } + + // Returns a node of the first element in the hash table, that is equal to + // the given one. + public int findNode(int element) { + int hash = m_hash.getHash(element); + int bucket = hash % m_hashBuckets.size(); + int list = m_hashBuckets.get(bucket); + if (list == IndexMultiList.nullNode()) + return IndexMultiList.nullNode(); + + int ptr = m_lists.getFirst(list); + while (ptr != IndexMultiList.nullNode()) { + int e = m_lists.getElement(ptr); + if (m_hash.equal(e, element)) { + return ptr; + } + ptr = m_lists.getNext(ptr); + } + + return IndexMultiList.nullNode(); + + } + + // Returns a node to the first element in the hash table, that is equal to + // the given element descriptor. + public int findNode(Object elementDescriptor) { + int hash = m_hash.getHash(elementDescriptor); + int bucket = hash % m_hashBuckets.size(); + int list = m_hashBuckets.get(bucket); + if (list == IndexMultiList.nullNode()) + return IndexMultiList.nullNode(); + + int ptr = m_lists.getFirst(list); + while (ptr != IndexMultiList.nullNode()) { + int e = m_lists.getElement(ptr); + if (m_hash.equal(elementDescriptor, e)) { + return ptr; + } + ptr = m_lists.getNext(ptr); + } + + return IndexMultiList.nullNode(); + + } + + // Gets next equal node. + public int getNextNode(int elementHandle) { + int element = m_lists.getElement(elementHandle); + int ptr = m_lists.getNext(elementHandle); + while (ptr != IndexMultiList.nullNode()) { + int e = m_lists.getElement(ptr); + if (m_hash.equal(e, element)) { + return ptr; + } + ptr = m_lists.getNext(ptr); + } + + return IndexMultiList.nullNode(); + + } + + // Removes a node. + public void deleteNode(int node) { + int element = getElement(node); + int hash = m_hash.getHash(element); + int bucket = hash % m_hashBuckets.size(); + int list = m_hashBuckets.get(bucket); + if (list == IndexMultiList.nullNode()) + throw new IllegalArgumentException(); + + int ptr = m_lists.getFirst(list); + int prev = IndexMultiList.nullNode(); + while (ptr != IndexMultiList.nullNode()) { + if (ptr == node) { + m_lists.deleteElement(list, prev, ptr); + if (m_lists.getFirst(list) == IndexMultiList.nullNode()) { + m_lists.deleteList(list);// do not keep empty lists + m_hashBuckets.set(bucket, IndexMultiList.nullNode()); + } + return; + } + prev = ptr; + ptr = m_lists.getNext(ptr); + } + + throw new IllegalArgumentException(); + + } + + // Returns a value of the element stored in the given node. + public int getElement(int elementHandle) { + return m_lists.getElement(elementHandle); + } + + // Returns any existing element from the hash table. Throws if the table is + // empty. + public int getAnyElement() { + return m_lists.getFirstElement(m_lists.getFirstList()); + } + + // Returns a node for any existing element from the hash table or NullNode + // if the table is empty. + public int getAnyNode() { + return m_lists.getFirst(m_lists.getFirstList()); + } + + public static int nullNode() { + return IndexMultiList.nullNode(); + } + + // Removes all elements from the hash table. + public void clear() { + m_hashBuckets = new AttributeStreamOfInt32(m_hashBuckets.size(), + nullNode()); + m_lists.clear(); + + } + + // Returns the number of elements in the hash table + public int size() { + return m_lists.getNodeCount(); + } +} diff --git a/src/com/esri/core/geometry/IndexMultiDCList.java b/src/com/esri/core/geometry/IndexMultiDCList.java new file mode 100644 index 00000000..b7a0a119 --- /dev/null +++ b/src/com/esri/core/geometry/IndexMultiDCList.java @@ -0,0 +1,310 @@ +/* + Copyright 1995-2013 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ +package com.esri.core.geometry; + +class IndexMultiDCList { + + StridedIndexTypeCollection m_list_nodes; // stores lists and list elements. + // Each list element is Index, + // Prev, next. + StridedIndexTypeCollection m_lists; // stores lists. Each list is Head, + // Tail, PrevList, NextList, NodeCount, + // ListData. + int m_list_of_lists; + boolean m_b_store_list_index_with_node; + + void freeNode_(int node) { + m_list_nodes.deleteElement(node); + } + + int newNode_() { + int node = m_list_nodes.newElement(); + return node; + } + + void freeList_(int list) { + m_lists.deleteElement(list); + } + + int newList_() { + int list = m_lists.newElement(); + return list; + } + + void setPrev_(int node, int prev) { + m_list_nodes.setField(node, 1, prev); + } + + void setNext_(int node, int next) { + m_list_nodes.setField(node, 2, next); + } + + void setData_(int node, int data) { + m_list_nodes.setField(node, 0, data); + } + + void setList_(int node, int list) { + m_list_nodes.setField(node, 3, list); + } + + void setListSize_(int list, int newsize) { + m_lists.setField(list, 4, newsize); + } + + void setNextList_(int list, int next) { + m_lists.setField(list, 3, next); + } + + void setPrevList_(int list, int prev) { + m_lists.setField(list, 2, prev); + } + + // Same as Index_multi_dc_list(true). + IndexMultiDCList() { + m_list_nodes = new StridedIndexTypeCollection(3); + m_lists = new StridedIndexTypeCollection(6); + m_b_store_list_index_with_node = false; + m_list_of_lists = nullNode(); + } + + // When bStoreListIndexWithNode is true, the each node stores a pointer to + // the list. Otherwise it does not. + // The get_list() method cannot be used if bStoreListIndexWithNode is false. + IndexMultiDCList(boolean b_store_list_index_with_node) { + m_list_nodes = new StridedIndexTypeCollection(3); + m_lists = new StridedIndexTypeCollection(6); + m_b_store_list_index_with_node = false; + m_list_of_lists = nullNode(); + } + + // Creates new list and returns it's handle. + // listData is user's info associated with the list + int createList(int listData) { + int list = newList_(); + // m_lists.set_field(list, 0, null_node());//head + // m_lists.set_field(list, 1, null_node());//tail + // m_lists.set_field(list, 2, null_node());//prev list + m_lists.setField(list, 3, m_list_of_lists); // next list + m_lists.setField(list, 4, 0);// node count in the list + m_lists.setField(list, 5, listData); + if (m_list_of_lists != nullNode()) + setPrevList_(m_list_of_lists, list); + + m_list_of_lists = list; + return list; + } + + // Deletes a list and returns the index of the next list. + int deleteList(int list) { + clear(list); + int prevList = m_lists.getField(list, 2); + int nextList = m_lists.getField(list, 3); + if (prevList != nullNode()) + setNextList_(prevList, nextList); + else + m_list_of_lists = nextList; + + if (nextList != nullNode()) + setPrevList_(nextList, prevList); + + freeList_(list); + return nextList; + } + + // Reserves memory for the given number of lists. + void reserveLists(int listCount) { + m_lists.setCapacity(listCount); + } + + // returns user's data associated with the list + int getListData(int list) { + return m_lists.getField(list, 5); + } + + // returns the list associated with the node_index. Do not use if list is + // created with bStoreListIndexWithNode == false. + int getList(int node_index) { + assert (m_b_store_list_index_with_node); + return m_list_nodes.getField(node_index, 3); + } + + // sets the user data to the list + void setListData(int list, int data) { + m_lists.setField(list, 5, data); + } + + // Adds element to a given list. The element is added to the end. Returns + // the new + int addElement(int list, int data) { + return insertElement(list, -1, data); + } + + // Inserts a new node before the given one . + int insertElement(int list, int beforeNode, int data) { + int node = newNode_(); + int prev = -1; + if (beforeNode != nullNode()) { + prev = getPrev(beforeNode); + setPrev_(beforeNode, node); + } + + setNext_(node, beforeNode); + if (prev != nullNode()) + setNext_(prev, node); + + int head = m_lists.getField(list, 0); + + if (beforeNode == head) + m_lists.setField(list, 0, node); + if (beforeNode == nullNode()) { + int tail = m_lists.getField(list, 1); + setPrev_(node, tail); + if (tail != -1) + setNext_(tail, node); + + m_lists.setField(list, 1, node); + } + + setData(node, data); + setListSize_(list, getListSize(list) + 1); + + if (m_b_store_list_index_with_node) + setList_(node, list); + + return node; + } + + // Deletes a node from a list. Returns the next node after the deleted one. + int deleteElement(int list, int node) { + int prev = getPrev(node); + int next = getNext(node); + if (prev != nullNode()) + setNext_(prev, next); + else + m_lists.setField(list, 0, next);// change head + if (next != nullNode()) + setPrev_(next, prev); + else + m_lists.setField(list, 1, prev);// change tail + + freeNode_(node); + setListSize_(list, getListSize(list) - 1); + return next; + } + + // Reserves memory for the given number of nodes. + void reserveNodes(int nodeCount) { + m_list_nodes.setCapacity(nodeCount); + } + + // Returns the data from the given list node. + int getData(int node_index) { + return m_list_nodes.getField(node_index, 0); + } + + // Sets the data to the given list node. + void setData(int node_index, int element) { + m_list_nodes.setField(node_index, 0, element); + } + + // Returns index of next node for the give node. + int getNext(int node_index) { + return m_list_nodes.getField(node_index, 2); + } + + // Returns index of previous node for the give node. + int getPrev(int node_index) { + return m_list_nodes.getField(node_index, 1); + } + + // Returns the first node in the list + int getFirst(int list) { + return m_lists.getField(list, 0); + } + + // Returns the last node in the list + int getLast(int list) { + return m_lists.getField(list, 1); + } + + // Check if the node is Null (does not exist) + static int nullNode() { + return -1; + } + + // Clears all nodes and removes all lists. + void clear() { + for (int list = getFirstList(); list != -1;) { + list = deleteList(list); + } + } + + // Clears all nodes from the list. + void clear(int list) { + int last = getLast(list); + while (last != nullNode()) { + int n = last; + last = getPrev(n); + freeNode_(n); + } + m_lists.setField(list, 0, -1); + m_lists.setField(list, 1, -1); + setListSize_(list, 0); + } + + // Returns True if the given list is empty. + boolean isEmpty(int list) { + return m_lists.getField(list, 0) == -1; + } + + // Returns True if the multilist is empty + boolean isEmpty() { + return m_list_nodes.size() == 0; + } + + // Returns node count in all lists + int getNodeCount() { + return m_list_nodes.size(); + } + + // returns the number of lists + int getListCount() { + return m_lists.size(); + } + + // Returns the node count in the given list + int getListSize(int list) { + return m_lists.getField(list, 4); + } + + // returns the first list + int getFirstList() { + return m_list_of_lists; + } + + // returns the next list + int getNextList(int list) { + return m_lists.getField(list, 3); + } +} diff --git a/src/com/esri/core/geometry/IndexMultiList.java b/src/com/esri/core/geometry/IndexMultiList.java new file mode 100644 index 00000000..44b1da78 --- /dev/null +++ b/src/com/esri/core/geometry/IndexMultiList.java @@ -0,0 +1,266 @@ +/* + Copyright 1995-2013 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ +package com.esri.core.geometry; + +class IndexMultiList { + + StridedIndexTypeCollection m_listNodes; // stores lists and list elements. + // Each list element is Index, next. + StridedIndexTypeCollection m_lists; // stores lists. Each list is Head, + // Tail, [PrevList, NextList]. + int m_list_of_lists; + boolean m_b_allow_navigation_between_lists;// when False, get_first_list, + // get_next_list return -1. + + void freeNode_(int node) { + m_listNodes.deleteElement(node); + } + + int newNode_() { + int node = m_listNodes.newElement(); + return node; + } + + void freeList_(int list) { + m_lists.deleteElement(list); + } + + int newList_() { + int list = m_lists.newElement(); + return list; + } + + // Same as Index_multi_list(true); + IndexMultiList() { + m_listNodes = new StridedIndexTypeCollection(2); + m_lists = new StridedIndexTypeCollection(4); + m_list_of_lists = nullNode(); + m_b_allow_navigation_between_lists = true; + } + + // When b_allow_navigation_between_lists is False, the get_first_list and + // get_next_list do not work. + // There will be two Index_type elements per list and two Index_type + // elements per list element + // When b_allow_navigation_between_lists is True, the get_first_list and + // get_next_list will work. + // There will be four Index_type elements per list and two Index_type + // elements per list element + IndexMultiList(boolean b_allow_navigation_between_lists) { + m_listNodes = new StridedIndexTypeCollection(2); + m_lists = new StridedIndexTypeCollection( + b_allow_navigation_between_lists ? 4 : 2); + m_list_of_lists = nullNode(); + m_b_allow_navigation_between_lists = b_allow_navigation_between_lists; + } + + // Creates new list and returns it's handle. + int createList() { + int node = newList_(); + if (m_b_allow_navigation_between_lists) { + m_lists.setField(node, 3, m_list_of_lists); + if (m_list_of_lists != nullNode()) + m_lists.setField(m_list_of_lists, 2, node); + m_list_of_lists = node; + } + + return node; + } + + // Deletes a list. + void deleteList(int list) { + int ptr = getFirst(list); + while (ptr != nullNode()) { + int p = ptr; + ptr = getNext(ptr); + freeNode_(p); + } + + if (m_b_allow_navigation_between_lists) { + int prevList = m_lists.getField(list, 2); + int nextList = m_lists.getField(list, 3); + if (prevList != nullNode()) + m_lists.setField(prevList, 3, nextList); + else + m_list_of_lists = nextList; + + if (nextList != nullNode()) + m_lists.setField(nextList, 2, prevList); + } + + freeList_(list); + } + + // Reserves memory for the given number of lists. + void reserveLists(int listCount) { + m_lists.setCapacity(listCount); + } + + // Adds element to a given list. The element is added to the end. Returns + // the new + int addElement(int list, int element) { + int head = m_lists.getField(list, 0); + int tail = m_lists.getField(list, 1); + int node = newNode_(); + if (tail != nullNode()) { + assert (head != nullNode()); + m_listNodes.setField(tail, 1, node); + m_lists.setField(list, 1, node); + } else {// empty list + assert (head == nullNode()); + m_lists.setField(list, 0, node); + m_lists.setField(list, 1, node); + } + + m_listNodes.setField(node, 0, element); + return node; + } + + // Reserves memory for the given number of nodes. + void reserveNodes(int nodeCount) { + m_listNodes.setCapacity(nodeCount); + } + + // Deletes a node from a list, given the previous node (previous node is + // required, because the list is singly connected). + void deleteElement(int list, int prevNode, int node) { + if (prevNode != nullNode()) { + assert (m_listNodes.getField(prevNode, 1) == node); + m_listNodes.setField(prevNode, 1, m_listNodes.getField(node, 1)); + if (m_lists.getField(list, 1) == node)// deleting a tail + { + m_lists.setField(list, 1, prevNode); + } + } else { + assert (m_lists.getField(list, 0) == node); + m_lists.setField(list, 0, m_listNodes.getField(node, 1)); + if (m_lists.getField(list, 1) == node) {// removing last element + assert (m_listNodes.getField(node, 1) == nullNode()); + m_lists.setField(list, 1, nullNode()); + } + } + freeNode_(node); + } + + // Concatenates list1 and list2. The nodes of list2 are added to the end of + // list1. The list2 index becomes invalid. + // Returns list1. + int concatenateLists(int list1, int list2) { + int tailNode1 = m_lists.getField(list1, 1); + int headNode2 = m_lists.getField(list2, 0); + if (headNode2 != nullNode())// do not concatenate empty lists + { + if (tailNode1 != nullNode()) { + // connect head of list2 to the tail of list1. + m_listNodes.setField(tailNode1, 1, headNode2); + // set the tail of the list1 to be the tail of list2. + m_lists.setField(list1, 1, m_lists.getField(list2, 1)); + } else {// list1 is empty, while list2 is not. + m_lists.setField(list1, 0, headNode2); + m_lists.setField(list1, 1, m_lists.getField(list2, 1)); + } + } + + if (m_b_allow_navigation_between_lists) { + int prevList = m_lists.getField(list2, 2); + int nextList = m_lists.getField(list2, 3); + if (prevList != nullNode()) + m_lists.setField(prevList, 3, nextList); + else + m_list_of_lists = nextList; + + if (nextList != nullNode()) + m_lists.setField(nextList, 2, prevList); + } + + freeList_(list2); + return list1; + } + + // Returns the data from the given list node. + int getElement(int node_index) { + return m_listNodes.getField(node_index, 0); + } + + // Sets the data to the given list node. + void setElement(int node_index, int element) { + m_listNodes.setField(node_index, 0, element); + } + + // Returns index of next node for the give node. + int getNext(int node_index) { + return m_listNodes.getField(node_index, 1); + } + + // Returns the first node in the least + int getFirst(int list) { + return m_lists.getField(list, 0); + } + + // Returns the element from the first node in the least. Equivalent to + // get_element(get_first(list)); + int getFirstElement(int list) { + int f = getFirst(list); + return getElement(f); + } + + // Check if the node is Null (does not exist) + static int nullNode() { + return -1; + } + + // Clears all nodes and removes all lists. Frees the memory. + void clear() { + m_listNodes.deleteAll(true); + m_lists.deleteAll(true); + m_list_of_lists = nullNode(); + } + + // Returns True if the given list is empty. + boolean isEmpty(int list) { + return m_lists.getField(list, 0) == nullNode(); + } + + boolean isEmpty() { + return m_listNodes.size() == 0; + } + + int getNodeCount() { + return m_listNodes.size(); + } + + int getListCount() { + return m_lists.size(); + } + + int getFirstList() { + assert (m_b_allow_navigation_between_lists); + return m_list_of_lists; + } + + int getNextList(int list) { + assert (m_b_allow_navigation_between_lists); + return m_lists.getField(list, 3); + } +} diff --git a/src/com/esri/core/geometry/InternalUtils.java b/src/com/esri/core/geometry/InternalUtils.java new file mode 100644 index 00000000..83ccbc0c --- /dev/null +++ b/src/com/esri/core/geometry/InternalUtils.java @@ -0,0 +1,493 @@ +/* + Copyright 1995-2013 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ + +package com.esri.core.geometry; + +import java.util.ArrayList; + +class InternalUtils { + + // p0 and p1 have to be on left/right boundary of fullRange2D (since this + // fuction can be called recursively, p0 or p1 can also be fullRange2D + // corners) + static int addPointsToArray(Point2D p0In, Point2D p1In, + Point2D[] pointsArray, int idx, Envelope2D fullRange2D, + boolean clockwise, double densifyDist)// PointerOfArrayOf(Point2D) + // pointsArray, int idx, + // Envelope2D fullRange2D, + // boolean clockwise, double + // densifyDist) + { + Point2D p0 = new Point2D(); + p0.setCoords(p0In); + Point2D p1 = new Point2D(); + p1.setCoords(p1In); + fullRange2D._snapToBoundary(p0); + fullRange2D._snapToBoundary(p1); + // //_ASSERT((p0.x == fullRange2D.xmin || p0.x == fullRange2D.xmax) && + // (p1.x == fullRange2D.xmin || p1.x == fullRange2D.xmax)); + double boundDist0 = fullRange2D._boundaryDistance(p0); + double boundDist1 = fullRange2D._boundaryDistance(p1); + if (boundDist1 == 0.0) + boundDist1 = fullRange2D.getLength(); + + if ((p0.x == p1.x || p0.y == p1.y + && (p0.y == fullRange2D.ymin || p0.y == fullRange2D.ymax)) + && (boundDist1 > boundDist0) == clockwise) { + Point2D delta = new Point2D(); + delta.setCoords(p1.x - p0.x, p1.y - p0.y); + if (densifyDist != 0)// if (densifyDist) + { + long cPoints = (long) (delta._norm(0) / densifyDist); + if (cPoints > 0) // if (cPoints) + { + delta.scale(1.0 / (cPoints + 1)); + for (long i = 0; i < cPoints; i++) { + p0.add(delta); + pointsArray[idx++].setCoords(p0.x, p0.y);// ARRAYELEMENT(pointsArray, + // idx++).setCoords(p0.x, + // p0.y); + } + } + } + } else { + int side0 = fullRange2D._envelopeSide(p0); + int side1 = fullRange2D._envelopeSide(p1); + // create up to four corner points; the order depends on boolean + // clockwise + Point2D corner; + int deltaSide = clockwise ? 1 : 3; // 3 is equivalent to -1 + do { + side0 = (side0 + deltaSide) & 3; + corner = fullRange2D.queryCorner(side0); + if (densifyDist != 0)// if (densifyDist) + { + idx = addPointsToArray(p0, corner, pointsArray, idx, + fullRange2D, clockwise, densifyDist); + } + pointsArray[idx++].setCoords(corner.x, corner.y);// ARRAYELEMENT(pointsArray, + // idx++).setCoords(corner.x, + // corner.y); + p0 = corner; + } while ((side0 & 3) != side1); + + if (densifyDist != 0)// if (densifyDist) + idx = addPointsToArray(p0, p1, pointsArray, idx, fullRange2D, + clockwise, densifyDist); + } + + return idx; + } + + void shiftPath(MultiPath inputGeom, int iPath, double shift) { + MultiVertexGeometryImpl vertexGeometryImpl = (MultiVertexGeometryImpl) inputGeom + ._getImpl(); + AttributeStreamOfDbl xyStream = (AttributeStreamOfDbl) vertexGeometryImpl + .getAttributeStreamRef(VertexDescription.Semantics.POSITION); + + int i1 = inputGeom.getPathStart(iPath); + int i2 = inputGeom.getPathEnd(iPath); + Point2D pt = new Point2D();// = null; + + // FIXME test to see if Point2D should be null + while (i1 < i2) { + xyStream.read(i1, pt); + pt.x += shift; + xyStream.write(i1, pt); + i1++; + } + } + + static double calculateToleranceFromGeometry(SpatialReference sr, + Envelope2D env2D, boolean bConservative) { + double gtolerance = env2D._calculateToleranceFromEnvelope(); + double stolerance = sr != null ? sr + .getTolerance(VertexDescription.Semantics.POSITION) : 0; + if (bConservative) { + gtolerance *= 4; + stolerance *= 1.1; + } + return Math.max(stolerance, gtolerance); + } + + static double calculateToleranceFromGeometry(SpatialReference sr, + Geometry geometry, boolean bConservative) { + Envelope2D env2D = new Envelope2D(); + geometry.queryEnvelope2D(env2D); + return calculateToleranceFromGeometry(sr, env2D, bConservative); + } + + static double calculateZToleranceFromGeometry(SpatialReference sr, + Geometry geometry, boolean bConservative) { + Envelope1D env1D = geometry.queryInterval( + VertexDescription.Semantics.Z, 0); + double gtolerance = env1D._calculateToleranceFromEnvelope(); + double stolerance = sr != null ? sr + .getTolerance(VertexDescription.Semantics.Z) : 0; + if (bConservative) { + gtolerance *= 4; + stolerance *= 1.1; + } + return Math.max(stolerance, gtolerance); + } + + double calculateZToleranceFromGeometry(SpatialReference sr, + Geometry geometry) { + Envelope1D env1D = geometry.queryInterval( + VertexDescription.Semantics.Z, 0); + double tolerance = env1D._calculateToleranceFromEnvelope(); + return Math + .max(sr != null ? sr + .getTolerance(VertexDescription.Semantics.Z) : 0, + tolerance); + } + + public static Envelope2D getMergedExtent(Geometry geom1, Envelope2D env2) { + Envelope2D env1 = new Envelope2D(); + geom1.queryLooseEnvelope2D(env1); + env1.merge(env2); + return env1; + } + + public static Envelope2D getMergedExtent(Geometry geom1, Geometry geom2) { + Envelope2D env1 = new Envelope2D(); + geom1.queryLooseEnvelope2D(env1); + Envelope2D env2 = new Envelope2D(); + geom2.queryLooseEnvelope2D(env2); + env1.merge(env2); + return env1; + } + + public static Geometry createGeometry(int gt, VertexDescription vdIn) { + VertexDescription vd = vdIn; + if (vd == null) + vd = VertexDescriptionDesignerImpl.getDefaultDescriptor2D(); + switch (gt) { + case Geometry.GeometryType.Point: + return new Point(vd); + case Geometry.GeometryType.Line: + return new Line(vd); + // case enum_value2(Geometry, GeometryType, enumBezier): + // break; + // case enum_value2(Geometry, GeometryType, enumEllipticArc): + // break; + case Geometry.GeometryType.Envelope: + return new Envelope(vd); + case Geometry.GeometryType.MultiPoint: + return new MultiPoint(vd); + case Geometry.GeometryType.Polyline: + return new Polyline(vd); + case Geometry.GeometryType.Polygon: + return new Polygon(vd); + default: + throw new GeometryException("invalid argument."); + } + } + + static boolean isClockwiseRing(MultiPathImpl polygon, int iring) { + int high_point_index = polygon.getHighestPointIndex(iring); + int path_start = polygon.getPathStart(iring); + int path_end = polygon.getPathEnd(iring); + + Point2D q = polygon.getXY(high_point_index); + Point2D p, r; + + if (high_point_index == path_start) { + p = polygon.getXY(path_end - 1); + r = polygon.getXY(path_start + 1); + } else if (high_point_index == path_end - 1) { + p = polygon.getXY(high_point_index - 1); + r = polygon.getXY(path_start); + } else { + p = polygon.getXY(high_point_index - 1); + r = polygon.getXY(high_point_index + 1); + } + + int orientation = Point2D.orientationRobust(p, q, r); + + if (orientation == 0) + return polygon.calculateRingArea2D(iring) > 0.0; + + return orientation == -1; + } + + static QuadTreeImpl buildQuadTree(MultiPathImpl multipathImpl) { + Envelope2D extent = new Envelope2D(); + multipathImpl.queryLooseEnvelope2D(extent); + QuadTreeImpl quad_tree_impl = new QuadTreeImpl(extent, 8); + int hint_index = -1; + SegmentIteratorImpl seg_iter = multipathImpl.querySegmentIterator(); + Envelope2D boundingbox = new Envelope2D(); + boolean resized_extent = false; + while (seg_iter.nextPath()) { + while (seg_iter.hasNextSegment()) { + Segment segment = seg_iter.nextSegment(); + int index = seg_iter.getStartPointIndex(); + segment.queryEnvelope2D(boundingbox); + hint_index = quad_tree_impl.insert(index, boundingbox, + hint_index); + + if (hint_index == -1) { + if (resized_extent) + throw new GeometryException("internal error"); + + // resize extent + multipathImpl.calculateEnvelope2D(extent, false); + resized_extent = true; + quad_tree_impl.reset(extent, 8); + seg_iter.resetToFirstPath(); + break; + } + } + } + + return quad_tree_impl; + } + + static QuadTreeImpl buildQuadTree(MultiPathImpl multipathImpl, + Envelope2D extentOfInterest) { + Envelope2D extent = new Envelope2D(); + multipathImpl.queryLooseEnvelope2D(extent); + QuadTreeImpl quad_tree_impl = new QuadTreeImpl(extent, 8); + int hint_index = -1; + Envelope2D boundingbox = new Envelope2D(); + SegmentIteratorImpl seg_iter = multipathImpl.querySegmentIterator(); + + boolean resized_extent = false; + while (seg_iter.nextPath()) { + while (seg_iter.hasNextSegment()) { + Segment segment = seg_iter.nextSegment(); + int index = seg_iter.getStartPointIndex(); + segment.queryEnvelope2D(boundingbox); + + if (boundingbox.isIntersecting(extentOfInterest)) { + hint_index = quad_tree_impl.insert(index, boundingbox, + hint_index); + + if (hint_index == -1) { + if (resized_extent) + throw new GeometryException("internal error"); + + // resize extent + multipathImpl.calculateEnvelope2D(extent, false); + resized_extent = true; + quad_tree_impl.reset(extent, 8); + seg_iter.resetToFirstPath(); + break; + } + } + } + } + + return quad_tree_impl; + } + + static QuadTreeImpl buildQuadTree(MultiPointImpl multipointImpl) { + Envelope2D extent = new Envelope2D(); + multipointImpl.queryLooseEnvelope2D(extent); + QuadTreeImpl quad_tree_impl = new QuadTreeImpl(extent, 8); + + Point2D pt = new Point2D(); + Envelope2D boundingbox = new Envelope2D(); + boolean resized_extent = false; + for (int i = 0; i < multipointImpl.getPointCount(); i++) { + multipointImpl.getXY(i, pt); + boundingbox.setCoords(pt); + int element_handle = quad_tree_impl.insert(i, boundingbox); + + if (element_handle == -1) { + if (resized_extent) + throw new GeometryException("internal error"); + + // resize extent + multipointImpl.calculateEnvelope2D(extent, false); + resized_extent = true; + quad_tree_impl.reset(extent, 8); + i = -1; // resets the for-loop + continue; + } + } + + return quad_tree_impl; + } + + static QuadTreeImpl buildQuadTree(MultiPointImpl multipointImpl, + Envelope2D extentOfInterest) { + QuadTreeImpl quad_tree_impl = new QuadTreeImpl(extentOfInterest, 8); + Point2D pt = new Point2D(); + boolean resized_extent = false; + Envelope2D boundingbox = new Envelope2D(); + for (int i = 0; i < multipointImpl.getPointCount(); i++) { + multipointImpl.getXY(i, pt); + + if (!extentOfInterest.contains(pt)) + continue; + + boundingbox.setCoords(pt); + int element_handle = quad_tree_impl.insert(i, boundingbox); + + if (element_handle == -1) { + if (resized_extent) + throw new GeometryException("internal error"); + + // resize extent + resized_extent = true; + Envelope2D extent = new Envelope2D(); + multipointImpl.calculateEnvelope2D(extent, false); + quad_tree_impl.reset(extent, 8); + i = -1; // resets the for-loop + continue; + } + } + + return quad_tree_impl; + } + + static Envelope2DIntersectorImpl getEnvelope2DIntersector( + MultiPathImpl multipathImplA, MultiPathImpl multipathImplB, + double tolerance, AttributeStreamOfInt32 verticesA, + AttributeStreamOfInt32 verticesB) { + Envelope2D env_a = new Envelope2D(), env_b = new Envelope2D(); + multipathImplA.queryLooseEnvelope2D(env_a); + multipathImplB.queryLooseEnvelope2D(env_b); + env_a.inflate(tolerance, tolerance); + env_b.inflate(tolerance, tolerance); + + Envelope2D envInter = new Envelope2D(); + envInter.setCoords(env_a); + envInter.intersect(env_b); + + SegmentIteratorImpl segIterA = multipathImplA.querySegmentIterator(); + SegmentIteratorImpl segIterB = multipathImplB.querySegmentIterator(); + ArrayList envelopes_a = new ArrayList(0); + ArrayList envelopes_b = new ArrayList(0); + + while (segIterA.nextPath()) { + while (segIterA.hasNextSegment()) { + Segment segmentA = segIterA.nextSegment(); + segmentA.queryEnvelope2D(env_a); + + if (!env_a.isIntersecting(envInter)) + continue; + + Envelope2D env = new Envelope2D(); + env.setCoords(env_a); + envelopes_a.add(env); + verticesA.add(segIterA.getStartPointIndex()); + } + } + + if (envelopes_a.isEmpty()) + return null; + + while (segIterB.nextPath()) { + while (segIterB.hasNextSegment()) { + Segment segmentB = segIterB.nextSegment(); + segmentB.queryEnvelope2D(env_b); + + if (!env_b.isIntersecting(envInter)) + continue; + + Envelope2D env = new Envelope2D(); + env.setCoords(env_b); + envelopes_b.add(env); + verticesB.add(segIterB.getStartPointIndex()); + } + } + + if (envelopes_b.isEmpty()) + return null; + + Envelope2DIntersectorImpl intersector = new Envelope2DIntersectorImpl( + envelopes_a, envelopes_b, tolerance); + return intersector; + } + + static Envelope2DIntersectorImpl getEnvelope2DIntersectorForOGCParts( + MultiPathImpl multipathImplA, MultiPathImpl multipathImplB, + double tolerance, AttributeStreamOfInt32 parts_a, + AttributeStreamOfInt32 parts_b) { + int type_a = multipathImplA.getType().value(); + int type_b = multipathImplB.getType().value(); + + Envelope2D env_a = new Envelope2D(), env_b = new Envelope2D(); + multipathImplA.queryLooseEnvelope2D(env_a); + multipathImplB.queryLooseEnvelope2D(env_b); + env_a.inflate(tolerance, tolerance); + env_b.inflate(tolerance, tolerance); + + Envelope2D envInter = new Envelope2D(); + envInter.setCoords(env_a); + envInter.intersect(env_b); + + ArrayList envelopes_a = new ArrayList(0); + ArrayList envelopes_b = new ArrayList(0); + + for (int ipath_a = 0; ipath_a < multipathImplA.getPathCount(); ipath_a++) { + if (type_a == Geometry.GeometryType.Polygon + && !multipathImplA.isExteriorRing(ipath_a)) + continue; + + multipathImplA.queryPathEnvelope2D(ipath_a, env_a); + + if (!env_a.isIntersecting(envInter)) + continue; + + Envelope2D env = new Envelope2D(); + env.setCoords(env_a); + envelopes_a.add(env); + parts_a.add(ipath_a); + } + + if (envelopes_a.isEmpty()) + return null; + + for (int ipath_b = 0; ipath_b < multipathImplB.getPathCount(); ipath_b++) { + if (type_b == Geometry.GeometryType.Polygon + && !multipathImplB.isExteriorRing(ipath_b)) + continue; + + multipathImplB.queryPathEnvelope2D(ipath_b, env_b); + + if (!env_b.isIntersecting(envInter)) + continue; + + Envelope2D env = new Envelope2D(); + env.setCoords(env_b); + envelopes_b.add(env); + parts_b.add(ipath_b); + } + + if (envelopes_b.isEmpty()) + return null; + + Envelope2DIntersectorImpl intersector = new Envelope2DIntersectorImpl( + envelopes_a, envelopes_b, tolerance); + return intersector; + } + + static boolean isWeakSimple(MultiVertexGeometry geom, double tol) { + return ((MultiVertexGeometryImpl) geom._getImpl()).getIsSimple(tol) > 0; + } +} diff --git a/src/com/esri/core/geometry/Interop.java b/src/com/esri/core/geometry/Interop.java new file mode 100644 index 00000000..38df1b52 --- /dev/null +++ b/src/com/esri/core/geometry/Interop.java @@ -0,0 +1,35 @@ +/* + Copyright 1995-2013 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ + +package com.esri.core.geometry; + +class Interop { + public static double translateFromAVNaN(double n) { + return (n < -1.0e38) ? NumberUtils.NaN() : n; + } + + public static double translateToAVNaN(double n) { + return (NumberUtils.isNaN(n)) ? -Double.MAX_VALUE : n; + } +} diff --git a/src/com/esri/core/geometry/IntervalTreeImpl.java b/src/com/esri/core/geometry/IntervalTreeImpl.java new file mode 100644 index 00000000..e8d0584d --- /dev/null +++ b/src/com/esri/core/geometry/IntervalTreeImpl.java @@ -0,0 +1,1325 @@ +/* + Copyright 1995-2013 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ +package com.esri.core.geometry; + +import java.util.ArrayList; + +final class IntervalTreeImpl { + static final class IntervalTreeIteratorImpl { + /** + * Resets the iterator to a starting state on the Interval_tree_impl + * using the input Envelope_1D interval as the query \param query The + * Envelope_1D interval used for the query. \param tolerance The + * tolerance used for the intersection tests. + */ + void resetIterator(Envelope1D query, double tolerance) { + m_query.vmin = query.vmin - tolerance; + m_query.vmax = query.vmax + tolerance; + m_tertiary_stack.resize(0); + m_function_index = 0; + m_function_stack[0] = State.initialize; + } + + /** + * Resets the iterator to a starting state on the Interval_tree_impl + * using the input Envelope_1D interval as the query \param query The + * Envelope_1D interval used for the query. \param tolerance The + * tolerance used for the intersection tests. + */ + void resetIterator(double query_min, double query_max, double tolerance) { + if (query_min > query_max) + throw new IllegalArgumentException(); + + m_query.vmin = query_min - tolerance; + m_query.vmax = query_max + tolerance; + m_tertiary_stack.resize(0); + m_function_index = 0; + m_function_stack[0] = State.initialize; + } + + /** + * Resets the iterator to a starting state on the Interval_tree_impl + * using the input double as the stabbing query \param query The double + * used for the query. \param tolerance The tolerance used for the + * intersection tests. + */ + void resetIterator(double query, double tolerance) { + m_query.vmin = query - tolerance; + m_query.vmax = query + tolerance; + m_tertiary_stack.resize(0); + m_function_index = 0; + m_function_stack[0] = State.initialize; + } + + /** + * Iterates over all intervals which interset the query interval. + * Returns an index to an interval that intersects the query. + */ + int next() { + if (m_function_index < 0) + return -1; + + boolean b_searching = true; + + while (b_searching) { + switch (m_function_stack[m_function_index]) { + case State.pIn: + b_searching = pIn_(); + break; + case State.pL: + b_searching = pL_(); + break; + case State.pR: + b_searching = pR_(); + break; + case State.pT: + b_searching = pT_(); + break; + case State.right: + b_searching = right_(); + break; + case State.left: + b_searching = left_(); + break; + case State.all: + b_searching = all_(); + break; + case State.initialize: + b_searching = initialize_(); + break; + default: + throw new GeometryException("internal error"); + } + } + + if (m_current_end_handle != -1) + return getCurrentEndIndex_() >> 1; + + return -1; + } + + // Creates an iterator on the input Interval_tree using the input + // Envelope_1D interval as the query. + IntervalTreeIteratorImpl(IntervalTreeImpl interval_tree, + Envelope1D query, double tolerance) { + m_interval_tree = interval_tree; + m_tertiary_stack.reserve(20); + resetIterator(query, tolerance); + } + + // Creates an iterator on the input Interval_tree using the input double + // as the stabbing query. + IntervalTreeIteratorImpl(IntervalTreeImpl interval_tree, double query, + double tolerance) { + m_interval_tree = interval_tree; + m_tertiary_stack.reserve(20); + resetIterator(query, tolerance); + } + + // Creates an iterator on the input Interval_tree. + IntervalTreeIteratorImpl(IntervalTreeImpl interval_tree) { + m_interval_tree = interval_tree; + m_tertiary_stack.reserve(20); + m_function_index = -1; + } + + private IntervalTreeImpl m_interval_tree; + private Envelope1D m_query = new Envelope1D(); + private int m_primary_handle; + private int m_next_primary_handle; + private int m_forked_handle; + private int m_current_end_handle; + private int m_next_end_handle; + private AttributeStreamOfInt32 m_tertiary_stack = new AttributeStreamOfInt32( + 0); + private int m_function_index; + private int[] m_function_stack = new int[2]; + + private interface State { + static final int initialize = 0; + static final int pIn = 1; + static final int pL = 2; + static final int pR = 3; + static final int pT = 4; + static final int right = 5; + static final int left = 6; + static final int all = 7; + } + + private boolean initialize_() { + m_primary_handle = -1; + m_next_primary_handle = -1; + m_forked_handle = -1; + m_current_end_handle = -1; + + if (m_interval_tree.m_primary_nodes != null + && m_interval_tree.m_primary_nodes.size() > 0) { + m_function_stack[0] = State.pIn; // overwrite initialize + m_next_primary_handle = m_interval_tree.m_root; + return true; + } + + m_function_index = -1; + return false; + } + + private boolean pIn_() { + m_primary_handle = m_next_primary_handle; + + if (m_primary_handle == -1) { + m_function_index = -1; + m_current_end_handle = -1; + return false; + } + + double discriminant = m_interval_tree + .getDiscriminant_(m_primary_handle); + + if (m_query.vmax < discriminant) { + int secondary_handle = m_interval_tree + .getSecondaryFromPrimary(m_primary_handle); + m_next_primary_handle = m_interval_tree + .getLPTR_(m_primary_handle); + + if (secondary_handle != -1) { + m_next_end_handle = m_interval_tree + .getFirst_(secondary_handle); + m_function_stack[++m_function_index] = State.left; + } + + return true; + } + + if (discriminant < m_query.vmin) { + int secondary_handle = m_interval_tree + .getSecondaryFromPrimary(m_primary_handle); + m_next_primary_handle = m_interval_tree + .getRPTR_(m_primary_handle); + + if (secondary_handle != -1) { + m_next_end_handle = m_interval_tree + .getLast_(secondary_handle); + m_function_stack[++m_function_index] = State.right; + } + + return true; + } + + assert (m_query.contains(discriminant)); + + m_function_stack[m_function_index] = State.pL; // overwrite pIn + m_forked_handle = m_primary_handle; + int secondary_handle = m_interval_tree + .getSecondaryFromPrimary(m_primary_handle); + m_next_primary_handle = m_interval_tree.getLPTR_(m_primary_handle); + + if (secondary_handle != -1) { + m_next_end_handle = m_interval_tree.getFirst_(secondary_handle); + m_function_stack[++m_function_index] = State.all; + } + + return true; + } + + private boolean pL_() { + m_primary_handle = m_next_primary_handle; + + if (m_primary_handle == -1) { + m_function_stack[m_function_index] = State.pR; // overwrite pL + m_next_primary_handle = m_interval_tree + .getRPTR_(m_forked_handle); + return true; + } + + double discriminant = m_interval_tree + .getDiscriminant_(m_primary_handle); + + if (discriminant < m_query.vmin) { + int secondary_handle = m_interval_tree + .getSecondaryFromPrimary(m_primary_handle); + m_next_primary_handle = m_interval_tree + .getRPTR_(m_primary_handle); + + if (secondary_handle != -1) { + m_next_end_handle = m_interval_tree + .getLast_(secondary_handle); + m_function_stack[++m_function_index] = State.right; + } + + return true; + } + + assert (m_query.contains(discriminant)); + + int secondary_handle = m_interval_tree + .getSecondaryFromPrimary(m_primary_handle); + m_next_primary_handle = m_interval_tree.getLPTR_(m_primary_handle); + + if (secondary_handle != -1) { + m_next_end_handle = m_interval_tree.getFirst_(secondary_handle); + m_function_stack[++m_function_index] = State.all; + } + + int rptr = m_interval_tree.getRPTR_(m_primary_handle); + + if (rptr != -1) { + m_tertiary_stack.add(rptr); // we'll search this in the pT state + } + + return true; + } + + private boolean pR_() { + m_primary_handle = m_next_primary_handle; + + if (m_primary_handle == -1) { + m_function_stack[m_function_index] = State.pT; // overwrite pR + return true; + } + + double discriminant = m_interval_tree + .getDiscriminant_(m_primary_handle); + + if (m_query.vmax < discriminant) { + int secondary_handle = m_interval_tree + .getSecondaryFromPrimary(m_primary_handle); + m_next_primary_handle = m_interval_tree + .getLPTR_(m_primary_handle); + + if (secondary_handle != -1) { + m_next_end_handle = m_interval_tree + .getFirst_(secondary_handle); + m_function_stack[++m_function_index] = State.left; + } + + return true; + } + + assert (m_query.contains(discriminant)); + + int secondary_handle = m_interval_tree + .getSecondaryFromPrimary(m_primary_handle); + + m_next_primary_handle = m_interval_tree.getRPTR_(m_primary_handle); + + if (secondary_handle != -1) { + m_next_end_handle = m_interval_tree.getFirst_(secondary_handle); + m_function_stack[++m_function_index] = State.all; + } + + int lptr = m_interval_tree.getLPTR_(m_primary_handle); + + if (lptr != -1) { + m_tertiary_stack.add(lptr); // we'll search this in the pT state + } + + return true; + } + + private boolean pT_() { + if (m_tertiary_stack.size() == 0) { + m_function_index = -1; + m_current_end_handle = -1; + return false; + } + + m_primary_handle = m_tertiary_stack + .get(m_tertiary_stack.size() - 1); + m_tertiary_stack.resize(m_tertiary_stack.size() - 1); + + int secondary_handle = m_interval_tree + .getSecondaryFromPrimary(m_primary_handle); + + if (secondary_handle != -1) { + m_next_end_handle = m_interval_tree.getFirst_(secondary_handle); + m_function_stack[++m_function_index] = State.all; + } + + if (m_interval_tree.getLPTR_(m_primary_handle) != -1) + m_tertiary_stack + .add(m_interval_tree.getLPTR_(m_primary_handle)); + + if (m_interval_tree.getRPTR_(m_primary_handle) != -1) + m_tertiary_stack + .add(m_interval_tree.getRPTR_(m_primary_handle)); + + return true; + } + + private boolean left_() { + m_current_end_handle = m_next_end_handle; + + if (m_current_end_handle != -1 + && IntervalTreeImpl.isLeft_(getCurrentEndIndex_()) + && m_interval_tree.getValue_(getCurrentEndIndex_()) <= m_query.vmax) { + m_next_end_handle = getNext_(); + return false; + } + + m_function_index--; + return true; + } + + private boolean right_() { + m_current_end_handle = m_next_end_handle; + + if (m_current_end_handle != -1 + && IntervalTreeImpl.isRight_(getCurrentEndIndex_()) + && m_interval_tree.getValue_(getCurrentEndIndex_()) >= m_query.vmin) { + m_next_end_handle = getPrev_(); + return false; + } + + m_function_index--; + return true; + } + + private boolean all_() { + m_current_end_handle = m_next_end_handle; + + if (m_current_end_handle != -1 + && IntervalTreeImpl.isLeft_(getCurrentEndIndex_())) { + m_next_end_handle = getNext_(); + return false; + } + + m_function_index--; + return true; + } + + private int getNext_() { + if (!m_interval_tree.m_b_offline_dynamic) + return m_interval_tree.m_secondary_lists + .getNext(m_current_end_handle); + + return m_interval_tree.m_secondary_treaps + .getNext(m_current_end_handle); + } + + private int getPrev_() { + if (!m_interval_tree.m_b_offline_dynamic) + return m_interval_tree.m_secondary_lists + .getPrev(m_current_end_handle); + + return m_interval_tree.m_secondary_treaps + .getPrev(m_current_end_handle); + } + + private int getCurrentEndIndex_() { + if (!m_interval_tree.m_b_offline_dynamic) + return m_interval_tree.m_secondary_lists + .getData(m_current_end_handle); + + return m_interval_tree.m_secondary_treaps + .getElement(m_current_end_handle); + } + } + + /** + * Default constructor for an Interval_tree_impl. You may call the reset() + * function with an array of intervals to populate or reuse the + * Interval_tree_impl. + */ + IntervalTreeImpl() { + m_root = -1; + m_c_count = 0; + } + + /** + * Constructor for a static Interval_tree_impl. \param intervals A + * Dynamic_array of Envelope_1D intervals to be constructed into a static + * Interval_tree_impl. The Interval_tree_impl holds a reference to the input + * intervals. + */ + IntervalTreeImpl(ArrayList intervals) { + reset(intervals, false); + } + + /** + * Constructor for a static Interval_tree_impl with the option for offline + * dynamic insertion. \param intervals A Dynamic_array of Envelope_1D + * intervals to be constructed into a static Interval_tree_impl. The + * Interval_tree_impl holds a reference to the input intervals. \param + * b_offline_dynamic If set to true, then this gives the option to insert + * dynamically at anytime and in and order, only if the inserted intervals + * are from the same set of intervals as the input array. Otherwise if set + * to false, then the Interval_tree_impl is constructed statically. + */ + IntervalTreeImpl(ArrayList intervals, boolean b_offline_dynamic) { + reset(intervals, b_offline_dynamic); + } + + /** + * Constructor for a static Interval_tree_impl with the option for offline + * dynamic insertion. \param envelopes A Dynamic_array of Envelope_2D + * objects to be constructed into a static Interval_tree_impl along the + * x-axis. The Interval_tree_impl holds a reference to the input envelopes. + * \param b_offline_dynamic If set to true, then this gives the option to + * insert dynamically at anytime and in and order, only if the inserted + * envelopes are from the same set of envelopes as the input array. + * Otherwise if set to false, then the Interval_tree_impl is constructed + * statically. + */ + IntervalTreeImpl(ArrayList envelopes, + boolean b_offline_dynamic, boolean b_along_x/* + * currently ignored and + * assumed tru + */) { + reset(envelopes, b_offline_dynamic, b_along_x); + } + + /** + * Inserts the interval from the given index into the Interval_tree_impl. + * This operation can only be performed in the offline dynamic case. \param + * index The index containing the interval to be inserted. + */ + void insert(int index) { + if (!m_b_offline_dynamic) + throw new IllegalArgumentException("invalid call"); + + if (m_root == -1) { + if (m_primary_nodes == null) { + m_primary_nodes = new StridedIndexTypeCollection(8); + m_interval_nodes = new StridedIndexTypeCollection(3); + m_end_indices_unique = new AttributeStreamOfInt32(0); + m_interval_handles = new AttributeStreamOfInt32(0); + } + + if (m_secondary_treaps == null) { + m_secondary_treaps = new Treap(); + m_secondary_treaps.setComparator(new SecondaryComparator(this)); + } + + int size = (m_intervals != null ? m_intervals.size() : m_envelopes + .size()); + + if (m_b_sort_intervals) { + // sort + AttributeStreamOfInt32 end_point_indices_sorted = new AttributeStreamOfInt32( + 0); + end_point_indices_sorted.reserve(2 * size); + querySortedEndPointIndices_(end_point_indices_sorted); + + // remove duplicates + m_end_indices_unique.reserve(2 * size); + m_end_indices_unique.resize(0); + querySortedDuplicatesRemoved_(end_point_indices_sorted); + m_interval_handles.resize(size, -1); + m_interval_handles.setRange(-1, 0, size); + m_b_sort_intervals = false; + } + + m_root = createPrimaryNode_(); + } + + int interval_handle = insertIntervalEnd_(index << 1, m_root); + int secondary_handle = getSecondaryFromInterval_(interval_handle); + int right_end_handle = m_secondary_treaps.addElement((index << 1) + 1, + secondary_handle); + setRightEnd_(interval_handle, right_end_handle); + m_interval_handles.set(index, interval_handle); + m_c_count++; + // assert(check_validation_()); + } + + /** + * Deletes the interval from the Interval_tree_impl. \param index The index + * containing the interval to be deleted from the Interval_tree_impl. + */ + void remove(int index) { + int interval_handle = m_interval_handles.get(index); + + if (interval_handle == -1) + throw new IllegalArgumentException( + "the interval does not exist in the interval tree"); + + m_interval_handles.set(index, -1); + + assert (getSecondaryFromInterval_(interval_handle) != -1); + assert (getLeftEnd_(interval_handle) != -1); + assert (getRightEnd_(interval_handle) != -1); + + m_c_count--; + + int size; + int secondary_handle = getSecondaryFromInterval_(interval_handle); + int primary_handle; + + if (m_b_offline_dynamic) { + primary_handle = m_secondary_treaps.getTreapData(secondary_handle); + m_secondary_treaps.deleteNode(getLeftEnd_(interval_handle), + secondary_handle); + m_secondary_treaps.deleteNode(getRightEnd_(interval_handle), + secondary_handle); + size = m_secondary_treaps.size(secondary_handle); + + if (size == 0) { + m_secondary_treaps.deleteTreap(secondary_handle); + setSecondaryToPrimary_(primary_handle, -1); + } + } else { + primary_handle = m_secondary_lists.getListData(secondary_handle); + m_secondary_lists.deleteElement(secondary_handle, + getLeftEnd_(interval_handle)); + m_secondary_lists.deleteElement(secondary_handle, + getRightEnd_(interval_handle)); + size = m_secondary_lists.getListSize(secondary_handle); + + if (size == 0) { + m_secondary_lists.deleteList(secondary_handle); + setSecondaryToPrimary_(primary_handle, -1); + } + } + + m_interval_nodes.deleteElement(interval_handle); + int tertiary_handle = getPPTR_(primary_handle); + int lptr = getLPTR_(primary_handle); + int rptr = getRPTR_(primary_handle); + + int iterations = 0; + while (!(size > 0 || primary_handle == m_root || (lptr != -1 && rptr != -1))) { + assert (size == 0); + assert (lptr == -1 || rptr == -1); + assert (primary_handle != 0); + + if (primary_handle == getLPTR_(tertiary_handle)) { + if (lptr != -1) { + setLPTR_(tertiary_handle, lptr); + setPPTR_(lptr, tertiary_handle); + setLPTR_(primary_handle, -1); + setPPTR_(primary_handle, -1); + } else if (rptr != -1) { + setLPTR_(tertiary_handle, rptr); + setPPTR_(rptr, tertiary_handle); + setRPTR_(primary_handle, -1); + setPPTR_(primary_handle, -1); + } else { + setLPTR_(tertiary_handle, -1); + setPPTR_(primary_handle, -1); + } + } else { + if (lptr != -1) { + setRPTR_(tertiary_handle, lptr); + setPPTR_(lptr, tertiary_handle); + setLPTR_(primary_handle, -1); + setPPTR_(primary_handle, -1); + } else if (rptr != -1) { + setRPTR_(tertiary_handle, rptr); + setPPTR_(rptr, tertiary_handle); + setRPTR_(primary_handle, -1); + setPPTR_(primary_handle, -1); + } else { + setRPTR_(tertiary_handle, -1); + setPPTR_(primary_handle, -1); + } + } + + iterations++; + primary_handle = tertiary_handle; + secondary_handle = getSecondaryFromPrimary(primary_handle); + size = (secondary_handle != -1 ? m_secondary_treaps + .size(secondary_handle) : 0); + lptr = getLPTR_(primary_handle); + rptr = getRPTR_(primary_handle); + tertiary_handle = getPPTR_(primary_handle); + } + + assert (iterations <= 2); + // assert(check_validation_()); + } + + /* + * Resets the Interval_tree_impl to an empty state, but maintains a handle + * on the current intervals. + */ + void reset() { + if (!m_b_offline_dynamic) + throw new IllegalArgumentException("invalid call"); + + reset_(false); + } + + /* + * Resets the Interval_tree_impl to an empty state and resets the intervals. + */ + void reset(ArrayList intervals, boolean b_offline_dynamic) { + m_b_offline_dynamic = b_offline_dynamic; + + reset_(true); + m_intervals = intervals; + m_envelopes = null; + + if (!m_b_offline_dynamic) { + insertIntervalsStatic_(); + m_c_count = m_intervals.size(); + // assert(check_validation_()); + } + } + + /* + * Resets the Interval_tree_impl to an empty state and resets the intervals. + */ + void reset(ArrayList envelopes, boolean b_offline_dynamic, + boolean b_along_x /* currently ignored and assumed true */) { + m_b_offline_dynamic = b_offline_dynamic; + + reset_(true); + m_envelopes = envelopes; + m_intervals = null; + + if (!m_b_offline_dynamic) { + insertIntervalsStatic_(); + // assert(check_validation_()); + } + } + + /** + * Returns the number of intervals stored in the Interval_tree_impl + */ + int size() { + return m_c_count; + } + + /** + * Gets an iterator on the Interval_tree_impl using the input Envelope_1D + * interval as the query. To reuse the existing iterator on the same + * Interval_tree_impl but with a new query, use the reset_iterator function + * on the Interval_tree_iterator_impl. \param query The Envelope_1D interval + * used for the query. \param tolerance The tolerance used for the + * intersection tests. + */ + IntervalTreeIteratorImpl getIterator(Envelope1D query, double tolerance) { + return new IntervalTreeImpl.IntervalTreeIteratorImpl(this, query, + tolerance); + } + + /** + * Gets an iterator on the Interval_tree_impl using the input double as the + * stabbing query. To reuse the existing iterator on the same + * Interval_tree_impl but with a new query, use the reset_iterator function + * on the Interval_tree_iterator_impl. \param query The double used for the + * stabbing query. \param tolerance The tolerance used for the intersection + * tests. + */ + IntervalTreeIteratorImpl getIterator(double query, double tolerance) { + return new IntervalTreeImpl.IntervalTreeIteratorImpl(this, query, + tolerance); + } + + /** + * Gets an iterator on the Interval_tree_impl. + */ + IntervalTreeIteratorImpl getIterator() { + return new IntervalTreeImpl.IntervalTreeIteratorImpl(this); + } + + private static final class SecondaryComparator extends Treap.Comparator { + SecondaryComparator(IntervalTreeImpl interval_tree) { + m_interval_tree = interval_tree; + } + + @Override + public int compare(Treap treap, int e_1, int node) { + int e_2 = treap.getElement(node); + double v_1 = m_interval_tree.getValue_(e_1); + double v_2 = m_interval_tree.getValue_(e_2); + + if (v_1 < v_2) + return -1; + if (v_1 == v_2) { + if (isLeft_(e_1) && isRight_(e_2)) + return -1; + if (isLeft_(e_2) && isRight_(e_1)) + return 1; + return 0; + } + return 1; + } + + private IntervalTreeImpl m_interval_tree; + }; + + private boolean m_b_offline_dynamic; + private ArrayList m_intervals; + private ArrayList m_envelopes; + private StridedIndexTypeCollection m_primary_nodes; + private StridedIndexTypeCollection m_interval_nodes; + private AttributeStreamOfInt32 m_interval_handles; + private IndexMultiDCList m_secondary_lists; // for static case + private Treap m_secondary_treaps; // for off-line dynamic case + private AttributeStreamOfInt32 m_end_indices_unique; + private int m_c_count; + private int m_root; + private boolean m_b_sort_intervals; + + private void querySortedEndPointIndices_(AttributeStreamOfInt32 end_indices) { + int size = (m_intervals != null ? m_intervals.size() : m_envelopes + .size()); + + for (int i = 0; i < 2 * size; i++) + end_indices.add(i); + + sortEndIndices_(end_indices, 0, 2 * size); + } + + private void querySortedDuplicatesRemoved_( + AttributeStreamOfInt32 end_indices_sorted) { + // remove duplicates + + double prev = NumberUtils.TheNaN; + for (int i = 0; i < end_indices_sorted.size(); i++) { + int e = end_indices_sorted.get(i); + double v = getValue_(e); + + if (v != prev) { + m_end_indices_unique.add(e); + prev = v; + } + } + } + + private void insertIntervalsStatic_() { + int size = (m_intervals != null ? m_intervals.size() : m_envelopes + .size()); + + assert (m_b_sort_intervals); + + if (m_primary_nodes == null) { + m_primary_nodes = new StridedIndexTypeCollection(8); + m_interval_nodes = new StridedIndexTypeCollection(3); + m_end_indices_unique = new AttributeStreamOfInt32(0); + m_interval_handles = new AttributeStreamOfInt32(0); + } + + if (m_secondary_lists == null) + m_secondary_lists = new IndexMultiDCList(); + + // sort + AttributeStreamOfInt32 end_indices_sorted = new AttributeStreamOfInt32( + 0); + end_indices_sorted.reserve(2 * size); + querySortedEndPointIndices_(end_indices_sorted); + + // remove duplicates + m_end_indices_unique.reserve(2 * size); + m_end_indices_unique.resize(0); + querySortedDuplicatesRemoved_(end_indices_sorted); + + assert (m_primary_nodes.size() == 0); + m_interval_nodes.setCapacity(size); // one for each interval being + // inserted. each element contains a + // primary node, a left secondary + // node, and a right secondary node. + m_interval_handles.resize(size, -1); // one for each interval being + // inserted. contains a handle + // for each interval to the + // above collection. + m_interval_handles.setRange(-1, 0, size); + m_secondary_lists.reserveNodes(2 * size); // one for each end point of + // the original interval set + // (not the unique set) + + m_root = createPrimaryNode_(); + + for (int i = 0; i < end_indices_sorted.size(); i++) { + int e = end_indices_sorted.get(i); + int interval_handle = m_interval_handles.get(e >> 1); + + if (interval_handle != -1) {// insert the right end point + assert (isRight_(e)); + int secondary_handle = getSecondaryFromInterval_(interval_handle); + setRightEnd_(interval_handle, + m_secondary_lists.addElement(secondary_handle, e)); + } else {// insert the left end point + assert (isLeft_(e)); + interval_handle = insertIntervalEnd_(e, m_root); + m_interval_handles.set(e >> 1, interval_handle); + } + } + + assert (m_secondary_lists.getNodeCount() == 2 * size); + } + + private int insertIntervalEnd_(int end_index, int root) { + assert (isLeft_(end_index)); + int primary_handle = root; + int tertiary_handle = root; + int ptr = root; + int secondary_handle; + int interval_handle = -1; + int il = 0, ir = m_end_indices_unique.size() - 1, im = 0; + int index = end_index >> 1; + double discriminant_tertiary = NumberUtils.TheNaN; + double discriminant_ptr = NumberUtils.TheNaN; + boolean bSearching = true; + + while (bSearching) { + if (il < ir) { + im = il + (ir - il) / 2; + + if (getDiscriminantIndex1_(primary_handle) == -1) + setDiscriminantIndices_(primary_handle, + m_end_indices_unique.get(im), + m_end_indices_unique.get(im + 1)); + } else { + assert (il == ir); + assert (getMin_(index) == getMax_(index)); + + if (getDiscriminantIndex1_(primary_handle) == -1) + setDiscriminantIndices_(primary_handle, + m_end_indices_unique.get(il), + m_end_indices_unique.get(il)); + } + + double discriminant = getDiscriminant_(primary_handle); + assert (!NumberUtils.isNaN(discriminant)); + + if (getMax_(index) < discriminant) { + if (ptr != -1) { + if (ptr == primary_handle) { + tertiary_handle = primary_handle; + discriminant_tertiary = discriminant; + ptr = getLPTR_(primary_handle); + + if (ptr != -1) + discriminant_ptr = getDiscriminant_(ptr); + else + discriminant_ptr = NumberUtils.TheNaN; + } else if (discriminant_ptr > discriminant) { + if (discriminant < discriminant_tertiary) + setLPTR_(tertiary_handle, primary_handle); + else + setRPTR_(tertiary_handle, primary_handle); + + setPPTR_(primary_handle, tertiary_handle); + setRPTR_(primary_handle, ptr); + setPPTR_(ptr, primary_handle); + + tertiary_handle = primary_handle; + discriminant_tertiary = discriminant; + ptr = -1; + discriminant_ptr = NumberUtils.TheNaN; + + assert (getLPTR_(primary_handle) == -1); + assert (getRightPrimary_(primary_handle) != -1); + } + } + + int left_handle = getLeftPrimary_(primary_handle); + + if (left_handle == -1) { + left_handle = createPrimaryNode_(); + setLeftPrimary_(primary_handle, left_handle); + } + + primary_handle = left_handle; + ir = im; + + continue; + } + + if (getMin_(index) > discriminant) { + if (ptr != -1) { + if (ptr == primary_handle) { + tertiary_handle = primary_handle; + discriminant_tertiary = discriminant; + ptr = getRPTR_(primary_handle); + + if (ptr != -1) + discriminant_ptr = getDiscriminant_(ptr); + else + discriminant_ptr = NumberUtils.TheNaN; + } else if (discriminant_ptr < discriminant) { + if (discriminant < discriminant_tertiary) + setLPTR_(tertiary_handle, primary_handle); + else + setRPTR_(tertiary_handle, primary_handle); + + setPPTR_(primary_handle, tertiary_handle); + setLPTR_(primary_handle, ptr); + setPPTR_(ptr, primary_handle); + + tertiary_handle = primary_handle; + discriminant_tertiary = discriminant; + ptr = -1; + discriminant_ptr = NumberUtils.TheNaN; + + assert (getRPTR_(primary_handle) == -1); + assert (getLeftPrimary_(primary_handle) != -1); + } + } + + int right_handle = getRightPrimary_(primary_handle); + + if (right_handle == -1) { + right_handle = createPrimaryNode_(); + setRightPrimary_(primary_handle, right_handle); + } + + primary_handle = right_handle; + il = im + 1; + + continue; + } + + secondary_handle = getSecondaryFromPrimary(primary_handle); + + if (secondary_handle == -1) { + secondary_handle = createSecondary_(primary_handle); + setSecondaryToPrimary_(primary_handle, secondary_handle); + } + + int left_end_handle = addEndIndex(secondary_handle, end_index); + interval_handle = createIntervalNode_(); + setSecondaryToInterval_(interval_handle, secondary_handle); + setLeftEnd_(interval_handle, left_end_handle); + + if (primary_handle != ptr) { + assert (primary_handle != -1); + assert (getLPTR_(primary_handle) == -1 + && getRPTR_(primary_handle) == -1 && getPPTR_(primary_handle) == -1); + + if (discriminant < discriminant_tertiary) + setLPTR_(tertiary_handle, primary_handle); + else + setRPTR_(tertiary_handle, primary_handle); + + setPPTR_(primary_handle, tertiary_handle); + + if (ptr != -1) { + if (discriminant_ptr < discriminant) + setLPTR_(primary_handle, ptr); + else + setRPTR_(primary_handle, ptr); + + setPPTR_(ptr, primary_handle); + } + } + + bSearching = false; + } + + return interval_handle; + } + + private int createPrimaryNode_() { + return m_primary_nodes.newElement(); + } + + private int createSecondary_(int primary_handle) { + if (!m_b_offline_dynamic) + return m_secondary_lists.createList(primary_handle); + + return m_secondary_treaps.createTreap(primary_handle); + } + + private int createIntervalNode_() { + return m_interval_nodes.newElement(); + } + + private void reset_(boolean b_new_intervals) { + if (m_secondary_lists != null) + m_secondary_lists.clear(); + + if (m_secondary_treaps != null) + m_secondary_treaps.clear(); + + if (m_primary_nodes != null) { + m_interval_nodes.deleteAll(false); + m_primary_nodes.deleteAll(false); + } + + m_root = -1; + m_c_count = 0; + + if (b_new_intervals) { + m_b_sort_intervals = true; + } else { + assert (m_b_offline_dynamic); + m_b_sort_intervals = false; + } + } + + private void setDiscriminantIndices_(int primary_handle, int e_1, int e_2) { + setDiscriminantIndex1_(primary_handle, e_1); + setDiscriminantIndex2_(primary_handle, e_2); + } + + private double getDiscriminant_(int primary_handle) { + int e_1 = getDiscriminantIndex1_(primary_handle); + if (e_1 == -1) + return NumberUtils.TheNaN; + + int e_2 = getDiscriminantIndex2_(primary_handle); + assert (e_2 != -1); + + double v_1 = getValue_(e_1); + double v_2 = getValue_(e_2); + + if (v_1 == v_2) + return v_1; + + return 0.5 * (v_1 + v_2); + } + + private boolean isActive_(int primary_handle) { + int secondary_handle = getSecondaryFromPrimary(primary_handle); + + if (secondary_handle != -1) + return true; + + int left_handle = getLeftPrimary_(primary_handle); + + if (left_handle == -1) + return false; + + int right_handle = getRightPrimary_(primary_handle); + + if (right_handle == -1) + return false; + + return true; + } + + private void setDiscriminantIndex1_(int primary_handle, int end_index) { + m_primary_nodes.setField(primary_handle, 0, end_index); + } + + private void setDiscriminantIndex2_(int primary_handle, int end_index) { + m_primary_nodes.setField(primary_handle, 1, end_index); + } + + private void setLeftPrimary_(int primary_handle, int left_handle) { + m_primary_nodes.setField(primary_handle, 3, left_handle); + } + + private void setRightPrimary_(int primary_handle, int right_handle) { + m_primary_nodes.setField(primary_handle, 4, right_handle); + } + + private void setSecondaryToPrimary_(int primary_handle, int secondary_handle) { + m_primary_nodes.setField(primary_handle, 2, secondary_handle); + } + + private void setLPTR_(int primary_handle, int lptr) { + m_primary_nodes.setField(primary_handle, 5, lptr); + } + + private void setRPTR_(int primary_handle, int rptr) { + m_primary_nodes.setField(primary_handle, 6, rptr); + } + + private void setPPTR_(int primary_handle, int pptr) { + m_primary_nodes.setField(primary_handle, 7, pptr); + } + + private void setSecondaryToInterval_(int interval_handle, + int secondary_handle) { + m_interval_nodes.setField(interval_handle, 0, secondary_handle); + } + + private int addEndIndex(int secondary_handle, int end_index) { + int end_index_handle; + + if (!m_b_offline_dynamic) + end_index_handle = m_secondary_lists.addElement(secondary_handle, + end_index); + else + end_index_handle = m_secondary_treaps.addElement(end_index, + secondary_handle); + + return end_index_handle; + } + + private void setLeftEnd_(int interval_handle, int left_end_handle) { + m_interval_nodes.setField(interval_handle, 1, left_end_handle); + } + + private void setRightEnd_(int interval_handle, int right_end_handle) { + m_interval_nodes.setField(interval_handle, 2, right_end_handle); + } + + private int getFirst_(int secondary_handle) { + if (!m_b_offline_dynamic) + return m_secondary_lists.getFirst(secondary_handle); + + return m_secondary_treaps.getFirst(secondary_handle); + } + + private int getLast_(int secondary_handle) { + if (!m_b_offline_dynamic) + return m_secondary_lists.getLast(secondary_handle); + + return m_secondary_treaps.getLast(secondary_handle); + } + + private static boolean isLeft_(int end_index) { + return (end_index & 0x1) == 0; + } + + private static boolean isRight_(int end_index) { + return (end_index & 0x1) == 1; + } + + private int getDiscriminantIndex1_(int primary_handle) { + return m_primary_nodes.getField(primary_handle, 0); + } + + private int getDiscriminantIndex2_(int primary_handle) { + return m_primary_nodes.getField(primary_handle, 1); + } + + private int getSecondaryFromPrimary(int primary_handle) { + return m_primary_nodes.getField(primary_handle, 2); + } + + private int getLeftPrimary_(int primary_handle) { + return m_primary_nodes.getField(primary_handle, 3); + } + + private int getRightPrimary_(int primary_handle) { + return m_primary_nodes.getField(primary_handle, 4); + } + + private int getLPTR_(int primary_handle) { + return m_primary_nodes.getField(primary_handle, 5); + } + + private int getRPTR_(int primary_handle) { + return m_primary_nodes.getField(primary_handle, 6); + } + + private int getPPTR_(int primary_handle) { + return m_primary_nodes.getField(primary_handle, 7); + } + + private int getSecondaryFromInterval_(int interval_handle) { + return m_interval_nodes.getField(interval_handle, 0); + } + + private int getLeftEnd_(int interval_handle) { + return m_interval_nodes.getField(interval_handle, 1); + } + + private int getRightEnd_(int interval_handle) { + return m_interval_nodes.getField(interval_handle, 2); + } + + private Envelope1D getInterval_(int end_index) { + return m_intervals.get(end_index >> 1); + } + + private double getMin_(int i) { + if (m_intervals != null) { + Envelope1D interval = m_intervals.get(i); + return interval.vmin; + } + + assert (m_envelopes != null); + Envelope2D envelope = m_envelopes.get(i); + return envelope.xmin; + } + + private double getMax_(int i) { + if (m_intervals != null) { + Envelope1D interval = m_intervals.get(i); + return interval.vmax; + } + + assert (m_envelopes != null); + Envelope2D envelope = m_envelopes.get(i); + return envelope.xmax; + } + + // *********** Helpers for Bucket sort************** + private BucketSort m_bucket_sort; + + private void sortEndIndices_(AttributeStreamOfInt32 end_indices, + int begin_, int end_) { + if (m_bucket_sort == null) + m_bucket_sort = new BucketSort(); + + IntervalTreeBucketSortHelper sorter = new IntervalTreeBucketSortHelper( + this); + m_bucket_sort.sort(end_indices, begin_, end_, sorter); + } + + private void sortEndIndicesHelper_(AttributeStreamOfInt32 end_indices, + int begin_, int end_) { + end_indices.Sort(begin_, end_, new EndPointsComparer(this)); + } + + private double getValue_(int e) { + if (m_intervals != null) { + Envelope1D interval = m_intervals.get(e >> 1); + double v = (isLeft_(e) ? interval.vmin : interval.vmax); + return v; + } + + assert (m_envelopes != null); + Envelope2D envelope = m_envelopes.get(e >> 1); + double v = (isLeft_(e) ? envelope.xmin : envelope.xmax); + return v; + } + + private static final class EndPointsComparer extends + AttributeStreamOfInt32.IntComparator { // For user sort + EndPointsComparer(IntervalTreeImpl interval_tree) { + m_interval_tree = interval_tree; + } + + @Override + public int compare(int e_1, int e_2) { + double v_1 = m_interval_tree.getValue_(e_1); + double v_2 = m_interval_tree.getValue_(e_2); + + if (v_1 < v_2 || (v_1 == v_2 && isLeft_(e_1) && isRight_(e_2))) + return -1; + + return 1; + } + + private IntervalTreeImpl m_interval_tree; + } + + private class IntervalTreeBucketSortHelper extends ClassicSort { // For + // bucket + // sort + IntervalTreeBucketSortHelper(IntervalTreeImpl interval_tree) { + m_interval_tree = interval_tree; + } + + @Override + public void userSort(int begin, int end, AttributeStreamOfInt32 indices) { + m_interval_tree.sortEndIndicesHelper_(indices, begin, end); + } + + @Override + public double getValue(int e) { + return m_interval_tree.getValue_(e); + } + + private IntervalTreeImpl m_interval_tree; + } +} diff --git a/src/com/esri/core/geometry/JSONUtils.java b/src/com/esri/core/geometry/JSONUtils.java new file mode 100644 index 00000000..e401df70 --- /dev/null +++ b/src/com/esri/core/geometry/JSONUtils.java @@ -0,0 +1,53 @@ +/* + Copyright 1995-2013 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ +package com.esri.core.geometry; + +import java.io.IOException; +import org.codehaus.jackson.JsonParseException; +import org.codehaus.jackson.JsonParser; +import org.codehaus.jackson.JsonToken; + +final class JSONUtils { + + static boolean isObjectStart(JsonParser parser) throws Exception { + return parser.getCurrentToken() == null ? parser.nextToken() == JsonToken.START_OBJECT + : parser.getCurrentToken() == JsonToken.START_OBJECT; + } + + static double readDouble(JsonParser parser) throws JsonParseException, + IOException, Exception { + if (parser.getCurrentToken() == JsonToken.VALUE_NUMBER_FLOAT) + return parser.getDoubleValue(); + else if (parser.getCurrentToken() == JsonToken.VALUE_NUMBER_INT) + return parser.getIntValue(); + else if (parser.getCurrentToken() == JsonToken.VALUE_NULL) + return NumberUtils.NaN(); + else if (parser.getCurrentToken() == JsonToken.VALUE_STRING) + if (parser.getText().equals("NaN")) + return NumberUtils.NaN(); + + throw new GeometryException("invalid parameter"); + } + +} diff --git a/src/com/esri/core/geometry/JsonCursor.java b/src/com/esri/core/geometry/JsonCursor.java new file mode 100644 index 00000000..eba7987d --- /dev/null +++ b/src/com/esri/core/geometry/JsonCursor.java @@ -0,0 +1,46 @@ +/* + Copyright 1995-2013 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ +package com.esri.core.geometry; + +/** + * An abstract Json String Cursor class. + */ +abstract class JsonCursor { + + /** + * Moves the cursor to the next string. Returns null when reached the end. + */ + public abstract String next(); + + /** + * Returns the ID of the current geometry. The ID is propagated across the + * operations (when possible). + * + * Returns an ID associated with the current Geometry. The ID is passed + * along and is returned by some operators to preserve relationship between + * the input and output geometry classes. It is not always possible to + * preserve an ID during an operation. + */ + public abstract int getID(); +} diff --git a/src/com/esri/core/geometry/JsonParserCursor.java b/src/com/esri/core/geometry/JsonParserCursor.java new file mode 100644 index 00000000..fe3605ab --- /dev/null +++ b/src/com/esri/core/geometry/JsonParserCursor.java @@ -0,0 +1,50 @@ +/* + Copyright 1995-2013 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ +package com.esri.core.geometry; + +import org.codehaus.jackson.JsonParser; + +/** + * An abstract JsonParser Cursor class. + */ +abstract class JsonParserCursor { + + /** + * Moves the cursor to the next JsonParser. Returns null when reached the + * end. + */ + public abstract JsonParser next(); + + /** + * Returns the ID of the current geometry. The ID is propagated across the + * operations (when possible). + * + * Returns an ID associated with the current Geometry. The ID is passed + * along and is returned by some operators to preserve relationship between + * the input and output geometry classes. It is not always possible to + * preserve an ID during an operation. + */ + public abstract int getID(); + +} diff --git a/src/com/esri/core/geometry/Line.java b/src/com/esri/core/geometry/Line.java new file mode 100644 index 00000000..c076476c --- /dev/null +++ b/src/com/esri/core/geometry/Line.java @@ -0,0 +1,1002 @@ +/* + Copyright 1995-2013 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ + + +package com.esri.core.geometry; + +import com.esri.core.geometry.VertexDescription.Semantics; +import java.io.Serializable; + +/** + * A straight line between a pair of points. + * + */ +public final class Line extends Segment implements Serializable { + + // UPDATED PORT TO MATCH NATIVE AS OF JAN 30 2011 + + private static final long serialVersionUID = 2L;// TODO:remove as we use + // writeReplace and + // GeometrySerializer + + // HEADER DEF + @Override + public Geometry.Type getType() { + return Type.Line; + } + + @Override + public double calculateLength2D() { + double dx = m_xStart - m_xEnd; + double dy = m_yStart - m_yEnd; + return Math.sqrt(dx * dx + dy * dy); + } + + @Override + boolean isDegenerate(double tolerance) { + double dx = m_xStart - m_xEnd; + double dy = m_yStart - m_yEnd; + return Math.sqrt(dx * dx + dy * dy) <= tolerance; + } + + /** + * Indicates if the line segment is a curve. + */ + @Override + public boolean isCurve() { + return false; + } + + @Override + Point2D _getTangent(double t) { + Point2D pt = new Point2D(); + pt.sub(getEndXY(), getStartXY()); + return pt; + } + + @Override + boolean _isDegenerate(double tolerance) { + return calculateLength2D() <= tolerance; + } + + @Override + double _calculateSubLength(double t) { + Point2D pt = getCoord2D(t); + pt.sub(getStartXY()); + return pt.length(); + } + + // HEADER DEF + + // Cpp + /** + * Creates a line segment. + */ + public Line() { + m_description = VertexDescriptionDesignerImpl.getDefaultDescriptor2D(); + } + + Line(VertexDescription vd) { + m_description = vd; + } + + Line(double x1, double y1, double x2, double y2) { + m_description = VertexDescriptionDesignerImpl.getDefaultDescriptor2D(); + setStartXY(x1, y1); + setEndXY(x2, y2); + } + + @Override + public void queryEnvelope(Envelope env) { + env.setEmpty(); + env.assignVertexDescription(m_description); + Envelope2D env2D = new Envelope2D(); + queryEnvelope2D(env2D); + env.setEnvelope2D(env2D); + + for (int i = 1, n = m_description.getAttributeCount(); i < n; i++) { + int semantics = m_description.getSemantics(i); + for (int iord = 0, nord = VertexDescription + .getComponentCount(semantics); i < nord; i++) { + Envelope1D interval = queryInterval(semantics, iord); + env.setInterval(semantics, iord, interval); + } + } + } + + @Override + void queryEnvelope2D(Envelope2D env) { + env.setCoords(m_xStart, m_yStart, m_xEnd, m_yEnd); + env.normalize(); + } + + @Override + void queryEnvelope3D(Envelope3D env) { + env.setEmpty(); + env.merge(m_xStart, m_yStart, _getAttributeAsDbl(0, Semantics.Z, 0)); + env.merge(m_xEnd, m_yEnd, _getAttributeAsDbl(1, Semantics.Z, 0)); + } + + @Override + public void applyTransformation(Transformation2D transform) { + _touch(); + Point2D pt = new Point2D(); + pt.x = m_xStart; + pt.y = m_yStart; + transform.transform(pt, pt); + m_xStart = pt.x; + m_yStart = pt.y; + pt.x = m_xEnd; + pt.y = m_yEnd; + transform.transform(pt, pt); + m_xEnd = pt.x; + m_yEnd = pt.y; + } + + @Override + void applyTransformation(Transformation3D transform) { + _touch(); + Point3D pt = new Point3D(); + pt.x = m_xStart; + pt.y = m_yStart; + pt.z = _getAttributeAsDbl(0, Semantics.Z, 0); + pt = transform.transform(pt); + m_xStart = pt.x; + m_yStart = pt.y; + _setAttribute(0, Semantics.Z, 0, pt.z); + pt.x = m_xEnd; + pt.y = m_yEnd; + pt.z = _getAttributeAsDbl(1, Semantics.Z, 0); + pt = transform.transform(pt); + m_xEnd = pt.x; + m_yEnd = pt.y; + _setAttribute(1, Semantics.Z, 0, pt.z); + } + + @Override + public Geometry createInstance() { + return new Line(m_description); + } + + @Override + double _calculateArea2DHelper(double xorg, double yorg) { + return ((m_xEnd - xorg) - (m_xStart - xorg)) + * ((m_yEnd - yorg) + (m_yStart - yorg)) * 0.5; + } + + double getCoordX_(double t) { + // double x = m_x_end * t + (1.0 - t) * m_x_start; < 1.0 || (x >= m_xStart && x <= m_xEnd) || (x <= m_xStart && x >= m_xEnd)); + return x; + } + + double getCoordY_(double t) { + // Must match query_coord_2D and vice verse + // Also match get_attribute_as_dbl + double y; + if (t <= 0.5) { + y = m_yStart + (m_yEnd - m_yStart) * t; + } else { + y = m_yEnd - (m_yEnd - m_yStart) * (1.0 - t); + } + assert (t < 0 || t > 1.0 || (y >= m_yStart && y <= m_yEnd) || (y <= m_yStart && y >= m_yEnd)); + return y; + } + + @Override + void getCoord2D(double t, Point2D pt) { + // We want: + // 1. When t == 0, get exactly Start + // 2. When t == 1, get exactly End + // 3. When m_x_end == m_x_start, we want m_x_start exactly + // 4. When m_y_end == m_y_start, we want m_y_start exactly + if (t <= 0.5) { + pt.x = m_xStart + (m_xEnd - m_xStart) * t; + pt.y = m_yStart + (m_yEnd - m_yStart) * t; + } else { + pt.x = m_xEnd - (m_xEnd - m_xStart) * (1.0 - t); + pt.y = m_yEnd - (m_yEnd - m_yStart) * (1.0 - t); + } + // assert(t < 0 || t > 1.0 || (pt.x >= m_x_start && pt.x <= m_x_end) || + // (pt.x <= m_x_start && pt.x >= m_x_end)); + // assert(t < 0 || t > 1.0 || (pt.y >= m_y_start && pt.y <= m_y_end) || + // (pt.y <= m_y_start && pt.y >= m_y_end)); + } + + @Override + Segment cut(double t1, double t2) { + SegmentBuffer segmentBuffer = new SegmentBuffer(); + cut(t1, t2, segmentBuffer); + return segmentBuffer.get(); + } + + @Override + void cut(double t1, double t2, SegmentBuffer subSegmentBuffer) { + if (subSegmentBuffer == null) + throw new IllegalArgumentException(); + + subSegmentBuffer.createLine();// Make sure buffer contains Line class. + Segment subSegment = subSegmentBuffer.get(); + subSegment.assignVertexDescription(m_description); + + Point2D point = new Point2D(); + getCoord2D(t1, point); + subSegment.setStartXY(point.x, point.y); + getCoord2D(t2, point); + subSegment.setEndXY(point.x, point.y); + + for (int iattr = 1, nattr = m_description.getAttributeCount(); iattr < nattr; iattr++) { + int semantics = m_description._getSemanticsImpl(iattr); + int ncomps = VertexDescription.getComponentCount(semantics); + + for (int ordinate = 0; ordinate < ncomps; ordinate++) { + double value1 = getAttributeAsDbl(t1, semantics, ordinate); + subSegment.setStartAttribute(semantics, ordinate, value1); + + double value2 = getAttributeAsDbl(t2, semantics, ordinate); + subSegment.setEndAttribute(semantics, ordinate, value2); + } + } + } + + @Override + public double getAttributeAsDbl(double t, int semantics, int ordinate) { + if (semantics == VertexDescription.Semantics.POSITION) + return ordinate == 0 ? getCoord2D(t).x : getCoord2D(t).y; + + int interpolation = VertexDescription.getInterpolation(semantics); + switch (interpolation) { + case VertexDescription.Interpolation.NONE: + if (t < 0.5) + return getStartAttributeAsDbl(semantics, ordinate); + else + return getEndAttributeAsDbl(semantics, ordinate); + case VertexDescription.Interpolation.LINEAR: { + double s = getStartAttributeAsDbl(semantics, ordinate); + double e = getEndAttributeAsDbl(semantics, ordinate); + return s * (1.0 - t) + e * t; + } + case VertexDescription.Interpolation.ANGULAR: { + throw new GeometryException("not implemented"); + } + } + + throw new GeometryException("internal error"); + } + + @Override + double getClosestCoordinate(Point2D inputPt, boolean bExtrapolate) { + double vx = m_xEnd - m_xStart; + double vy = m_yEnd - m_yStart; + double v2 = vx * vx + vy * vy; + if (v2 == 0) + return 0.5; + double rx = inputPt.x - m_xStart; + double ry = inputPt.y - m_yStart; + double t = (rx * vx + ry * vy) / v2; + if (!bExtrapolate) { + if (t < 0.0) + t = 0.0; + else if (t > 1.0) + t = 1.0; + } + + return t; + } + + @Override + public int intersectionWithAxis2D(boolean b_axis_x, double ordinate, + double[] result_ordinates, double[] parameters) { + if (b_axis_x) { + double a = (m_yEnd - m_yStart); + + if (a == 0) + return (ordinate == m_yEnd) ? -1 : 0; + + double t = (ordinate - m_yStart) / a; + + if (t < 0.0 || t > 1.0) + return 0; + + if (result_ordinates != null) + (result_ordinates)[0] = getCoordX_(t); + + if (parameters != null) + (parameters)[0] = t; + + return 1; + } else { + double a = (m_xEnd - m_xStart); + + if (a == 0) + return (ordinate == m_xEnd) ? -1 : 0; + + double t = (ordinate - m_xStart) / a; + + if (t < 0.0 || t > 1.0) + return 0; + + if (result_ordinates != null) + (result_ordinates)[0] = getCoordY_(t); + + if (parameters != null) + (parameters)[0] = t; + + return 1; + } + } + + // line segment can have 0 or 1 intersection interval with clipEnv2D. + // The function return 0 or 2 segParams (e.g. 0.0, 0.4; or 0.1, 0.9; or 0.6, + // 1.0; or 0.0, 1.0) + // segParams will be sorted in ascending order; the order of the + // envelopeDistances will correspond (i.e. the envelopeDistances may not be + // in ascending order); + // an envelopeDistance can be -1.0 if the corresponding endpoint is properly + // inside clipEnv2D. + int intersectionWithEnvelope2D(Envelope2D clipEnv2D, + boolean includeEnvBoundary, double[] segParams, + double[] envelopeDistances) { + Point2D p1 = getStartXY(); + Point2D p2 = getEndXY(); + + // includeEnvBoundary xxx ??? + + int modified = clipEnv2D.clipLine(p1, p2, 0, segParams, + envelopeDistances); + return modified != 0 ? 2 : 0; + + } + + @Override + double intersectionOfYMonotonicWithAxisX(double y, double x_parallel) { + double a = (m_yEnd - m_yStart); + + if (a == 0) + return (y == m_yEnd) ? x_parallel : NumberUtils.NaN(); + + double t = (y - m_yStart) / a; + assert (t >= 0 && t <= 1.0); + // double t_1 = 1.0 - t; + // assert(t + t_1 == 1.0); + double resx = getCoordX_(t); + if (t == 1.0) + resx = m_xEnd; + assert ((resx >= m_xStart && resx <= m_xEnd) || (resx <= m_xStart && resx >= m_xEnd)); + return resx; + } + + @Override + boolean _isIntersectingPoint(Point2D pt, double tolerance, + boolean bExcludeExactEndpoints) { + return _intersection(pt, tolerance, bExcludeExactEndpoints) >= 0;// must + // use + // same + // method + // that + // the + // intersection + // routine + // uses. + } + + /** + * Returns True if point and the segment intersect (not disjoint) for the + * given tolerance. + */ + @Override + boolean isIntersecting(Point2D pt, double tolerance) { + return _isIntersectingPoint(pt, tolerance, false); + } + + void orientBottomUp_() { + if (m_yEnd < m_yStart || (m_yEnd == m_yStart && m_xEnd < m_xStart)) { + double x = m_xStart; + m_xStart = m_xEnd; + m_xEnd = x; + + double y = m_yStart; + m_yStart = m_yEnd; + m_yEnd = y; + for (int i = 0, n = m_description.getTotalComponentCount() - 2; i < n; i++) { + double a = m_attributes[i]; + m_attributes[i] = m_attributes[i + n]; + m_attributes[i + n] = a; + } + } + } + + // return -1 for the left side from the infinite line passing through thais + // Line, 1 for the right side of the line, 0 if on the line (in the bounds + // of the roundoff error) + int _side(Point2D pt) { + return _side(pt.x, pt.y); + } + + // return -1 for the left side from the infinite line passing through thais + // Line, 1 for the right side of the line, 0 if on the line (in the bounds + // of the roundoff error) + int _side(double ptX, double ptY) { + Point2D v1 = new Point2D(ptX, ptY); + v1.sub(getStartXY()); + Point2D v2 = new Point2D(); + v2.sub(getEndXY(), getStartXY()); + double cross = v2.crossProduct(v1); + double crossError = 4 * NumberUtils.doubleEps() + * (Math.abs(v2.x * v1.y) + Math.abs(v2.y * v1.x)); + return cross > crossError ? -1 : cross < -crossError ? 1 : 0; + } + + double _intersection(Point2D pt, double tolerance, + boolean bExcludeExactEndPoints) { + Point2D v = new Point2D(); + Point2D start = new Point2D(); + + // Test start point distance to pt. + start.setCoords(m_xStart, m_yStart); + v.sub(pt, start); + double vlength = v.length(); + double vLengthError = vlength * 3 * NumberUtils.doubleEps(); + if (vlength <= Math.max(tolerance, vLengthError)) { + assert (vlength != 0 || pt.isEqual(start));// probably never asserts + if (bExcludeExactEndPoints && vlength == 0) + return NumberUtils.TheNaN; + else + return 0; + } + + Point2D end2D = getEndXY(); + // Test end point distance to pt. + v.sub(pt, end2D); + vlength = v.length(); + vLengthError = vlength * 3 * NumberUtils.doubleEps(); + if (vlength <= Math.max(tolerance, vLengthError)) { + assert (vlength != 0 || pt.isEqual(end2D));// probably never asserts + if (bExcludeExactEndPoints && vlength == 0) + return NumberUtils.TheNaN; + else + return 1.0; + } + + // Find a distance from the line to pt. + v.setCoords(m_xEnd - m_xStart, m_yEnd - m_yStart); + double len = v.length(); + if (len > 0) { + double invertedLength = 1.0 / len; + v.scale(invertedLength); + Point2D relativePoint = new Point2D(); + relativePoint.sub(pt, start); + double projection = relativePoint.dotProduct(v); + double projectionError = 8 * relativePoint._dotProductAbs(v) + * NumberUtils.doubleEps();// See Error Estimation Rules In + // Borg.docx + v.leftPerpendicular();// get left normal to v + double distance = relativePoint.dotProduct(v); + double distanceError = 8 * relativePoint._dotProductAbs(v) + * NumberUtils.doubleEps();// See Error Estimation Rules In + // Borg.docx + + double perror = Math.max(tolerance, projectionError); + if (projection < -perror || projection > len + perror) + return NumberUtils.TheNaN; + + double merror = Math.max(tolerance, distanceError); + if (Math.abs(distance) <= merror) { + double t = projection * invertedLength; + t = NumberUtils.snap(t, 0.0, 1.0); + Point2D ptOnLine = new Point2D(); + getCoord2D(t, ptOnLine); + if (Point2D.distance(ptOnLine, pt) <= tolerance) { + if (t < 0.5) { + if (Point2D.distance(ptOnLine, start) <= tolerance)// the + // projected + // point + // is + // close + // to + // the + // start + // point. + // Need + // to + // return + // 0. + return 0; + } else if (Point2D.distance(ptOnLine, end2D) <= tolerance)// the + // projected + // point + // is + // close + // to + // the + // end + // point. + // Need + // to + // return + // 1.0. + return 1.0; + + return t; + } + } + } + + return NumberUtils.TheNaN; + } + + boolean equals(Line other) { + if (other == this) + return true; + + // FIXME review use of Java's instance of to see if it complies with + // Borgs + if (other instanceof Line)// (!IS_INSTANCE_OF(other, Line)) + return false; + + return _equalsImpl((Segment) other); + } + + boolean _projectionIntersectHelper(Line other, Point2D v, boolean bStart) { + // v is the vector in the direction of this line == end - start. + double orgX = bStart ? m_xStart : m_xEnd; + double orgY = bStart ? m_yStart : m_yEnd; + Point2D m = new Point2D(); + m.x = other.getEndX() - orgX; + m.y = other.getEndY() - orgY; + double dot = v.dotProduct(m); + double dotError = 3 * NumberUtils.doubleEps() * v._dotProductAbs(m); + if (dot > dotError) { + m.x = other.getStartX() - orgX; + m.y = other.getStartY() - orgY; + double dot2 = v.dotProduct(m); + double dotError2 = 3 * NumberUtils.doubleEps() + * v._dotProductAbs(m); + return dot2 <= dotError2; + } + + return true; + } + + boolean _projectionIntersect(Line other) { + // This function returns true, if the "other"'s projection on "this" + Point2D v = new Point2D(); + v.x = m_xEnd - m_xStart; + v.y = m_yEnd - m_yStart; + if (!_projectionIntersectHelper(other, v, false)) + return false; // Both other.Start and other.End projections on + // "this" lie to the right of the this.End + + v.negate(); + if (!_projectionIntersectHelper(other, v, true)) + return false; // Both other.Start and other.End projections on + // "this" lie to the left of the this.End + + return true; + } + + // Tests if two lines intersect using projection of one line to another. + static boolean _isIntersectingHelper(Line line1, Line line2) { + int s11 = line1._side(line2.m_xStart, line2.m_yStart); + int s12 = line1._side(line2.m_xEnd, line2.m_yEnd); + if (s11 < 0 && s12 < 0 || s11 > 0 && s12 > 0) + return false;// no intersection. The line2 lies to one side of an + // infinite line passing through line1 + + int s21 = line2._side(line1.m_xStart, line1.m_yStart); + int s22 = line2._side(line1.m_xEnd, line1.m_yEnd); + if (s21 < 0 && s22 < 0 || s21 > 0 && s22 > 0) + return false;// no intersection.The line1 lies to one side of an + // infinite line passing through line2 + + double len1 = line1.calculateLength2D(); + double len2 = line2.calculateLength2D(); + if (len1 > len2) { + return line1._projectionIntersect(line2); + } else { + return line2._projectionIntersect(line1); + } + } + + static Point2D _intersectHelper1(Line line1, Line line2, double tolerance) { + Point2D result = new Point2D(NumberUtils.NaN(), NumberUtils.NaN()); + double k1x = line1.m_xEnd - line1.m_xStart; + double k1y = line1.m_yEnd - line1.m_yStart; + double k2x = line2.m_xEnd - line2.m_xStart; + double k2y = line2.m_yEnd - line2.m_yStart; + + double det = k2x * k1y - k1x * k2y; + if (det == 0) + return result; + + // estimate roundoff error for det: + double errdet = 4 * NumberUtils.doubleEps() + * (Math.abs(k2x * k1y) + Math.abs(k1x * k2y)); + + double bx = line2.m_xStart - line1.m_xStart; + double by = line2.m_yStart - line1.m_yStart; + + double a0 = (k2x * by - bx * k2y); + double a0error = 4 * NumberUtils.doubleEps() + * (Math.abs(k2x * by) + Math.abs(bx * k2y)); + double t0 = a0 / det; + double absdet = Math.abs(det); + double t0error = (a0error * absdet + errdet * Math.abs(a0)) + / (det * det) + NumberUtils.doubleEps() * Math.abs(t0); + if (t0 < -t0error || t0 > 1.0 + t0error) + return result; + + double a1 = (k1x * by - bx * k1y); + double a1error = 4 * NumberUtils.doubleEps() + * (Math.abs(k1x * by) + Math.abs(bx * k1y)); + double t1 = a1 / det; + double t1error = (a1error * absdet + errdet * Math.abs(a1)) + / (det * det) + NumberUtils.doubleEps() * Math.abs(t1); + + if (t1 < -t1error || t1 > 1.0 + t1error) + return result; + + double t0r = NumberUtils.snap(t0, 0.0, 1.0); + double t1r = NumberUtils.snap(t1, 0.0, 1.0); + Point2D pt0 = line1.getCoord2D(t0r); + Point2D pt1 = line2.getCoord2D(t1r); + Point2D pt = new Point2D(); + pt.sub(pt0, pt1); + if (pt.length() > tolerance) { + // Roundoff errors cause imprecise result. Try recalculate. + // 1. Use averaged point and recalculate the t values + // Point2D pt; + pt.add(pt0, pt1); + pt.scale(0.5); + t0r = line1.getClosestCoordinate(pt, false); + t1r = line2.getClosestCoordinate(pt, false); + Point2D pt01 = line1.getCoord2D(t0r); + Point2D pt11 = line2.getCoord2D(t1r); + pt01.sub(pt11); + if (pt01.length() > tolerance) { + // Seems to be no intersection here actually. Return NaNs + return result; + } + } + + result.setCoords(t0r, t1r); + return result; + } + + static int _isIntersectingLineLine(Line line1, Line line2, + double tolerance, boolean bExcludeExactEndpoints) { + // _ASSERT(line1 != line2); + // Check for the endpoints. + // The bExcludeExactEndpoints is True, means we care only about overlaps + // and real intersections, but do not care if the endpoints are exactly + // equal. + // bExcludeExactEndpoints is used in Cracking check test, because during + // cracking test all points are either coincident or further than the + // tolerance. + int counter = 0; + if (line1.m_xStart == line2.m_xStart + && line1.m_yStart == line2.m_yStart + || line1.m_xStart == line2.m_xEnd + && line1.m_yStart == line2.m_yEnd) { + counter++; + if (!bExcludeExactEndpoints) + return 1; + } + + if (line1.m_xEnd == line2.m_xStart && line1.m_yEnd == line2.m_yStart + || line1.m_xEnd == line2.m_xEnd && line1.m_yEnd == line2.m_yEnd) { + counter++; + if (counter == 2) + return 2; // counter == 2 means both endpoints coincide (Lines + // overlap). + if (!bExcludeExactEndpoints) + return 1; + } + + if (line2._isIntersectingPoint(line1.getStartXY(), tolerance, true)) + return 1;// return true; + if (line2._isIntersectingPoint(line1.getEndXY(), tolerance, true)) + return 1;// return true; + if (line1._isIntersectingPoint(line2.getStartXY(), tolerance, true)) + return 1;// return true; + if (line1._isIntersectingPoint(line2.getEndXY(), tolerance, true)) + return 1;// return true; + + if (bExcludeExactEndpoints && (counter != 0)) + return 0;// return false; + + return _isIntersectingHelper(line1, line2) == false ? 0 : 1; + } + + int _intersectLineLineExact(Line line1, Line line2, + Point2D[] intersectionPoints, double[] param1, double[] param2) { + int counter = 0; + if (line1.m_xStart == line2.m_xStart + && line1.m_yStart == line2.m_yStart) { + if (param1 != null)// if (param1) + param1[counter] = 0.0; + if (param2 != null)// if (param2) + param2[counter] = 0.0; + + if (intersectionPoints != null)// if (intersectionPoints) + intersectionPoints[counter] = Point2D.construct(line1.m_xStart, + line1.m_yStart); + + counter++; + } + + if (line1.m_xStart == line2.m_xEnd && line1.m_yStart == line2.m_yEnd) { + if (param1 != null)// if (param1) + param1[counter] = 0.0; + if (param2 != null)// if (param2) + param2[counter] = 1.0; + + if (intersectionPoints != null)// if (intersectionPoints) + intersectionPoints[counter] = Point2D.construct(line1.m_xStart, + line1.m_yStart); + + counter++; + } + + if (line1.m_xEnd == line2.m_xStart && line1.m_yEnd == line2.m_yStart) { + if (counter == 2) {// both segments a degenerate + if (param1 != null)// if (param1) + { + param1[0] = 0.0; + param1[1] = 1.0; + } + if (param2 != null)// if (param2) + { + param2[0] = 1.0; + } + + if (intersectionPoints != null)// if (intersectionPoints) + { + intersectionPoints[0] = Point2D.construct(line1.m_xEnd, + line1.m_yEnd); + intersectionPoints[1] = Point2D.construct(line1.m_xEnd, + line1.m_yEnd); + } + + return counter; + } + + if (param1 != null)// if (param1) + param1[counter] = 1.0; + if (param2 != null)// if (param2) + param2[counter] = 0.0; + + if (intersectionPoints != null)// if (intersectionPoints) + intersectionPoints[counter] = Point2D.construct(line1.m_xEnd, + line1.m_yEnd); + + counter++; + } + + if (line1.m_xEnd == line2.m_xEnd && line1.m_yEnd == line2.m_yEnd) { + if (counter == 2) {// both segments are degenerate + if (param1 != null)// if (param1) + { + param1[0] = 0.0; + param1[1] = 1.0; + } + if (param2 != null)// if (param2) + { + param2[0] = 1.0; + } + + if (intersectionPoints != null)// if (intersectionPoints) + { + intersectionPoints[0] = Point2D.construct(line1.m_xEnd, + line1.m_yEnd); + intersectionPoints[1] = Point2D.construct(line1.m_xEnd, + line1.m_yEnd); + } + + return counter; + } + + if (param1 != null)// if (param1) + param1[counter] = 1.0; + if (param2 != null)// if (param2) + param2[counter] = 1.0; + + if (intersectionPoints != null)// if (intersectionPoints) + intersectionPoints[counter] = Point2D.construct(line1.m_xEnd, + line1.m_yEnd); + counter++; + } + + return counter; + } + + static int _intersectLineLine(Line line1, Line line2, + Point2D[] intersectionPoints, double[] param1, double[] param2, + double tolerance) { + // _ASSERT(!param1 && !param2 || param1); + int counter = 0; + // Test the end points for exact coincidence. + double t11 = line1._intersection(line2.getStartXY(), tolerance, false); + double t12 = line1._intersection(line2.getEndXY(), tolerance, false); + double t21 = line2._intersection(line1.getStartXY(), tolerance, false); + double t22 = line2._intersection(line1.getEndXY(), tolerance, false); + + if (!NumberUtils.isNaN(t11)) { + if (param1 != null)// if (param1) + param1[counter] = t11; + if (param2 != null)// if (param2) + param2[counter] = 0; + + if (intersectionPoints != null)// if (intersectionPoints) + intersectionPoints[counter] = Point2D.construct(line2.m_xStart, + line2.m_yStart); + counter++; + } + + if (!NumberUtils.isNaN(t12)) { + if (param1 != null)// if (param1) + param1[counter] = t12; + if (param2 != null)// if (param2) + param2[counter] = 1.0; + + if (intersectionPoints != null)// if (intersectionPoints) + intersectionPoints[counter] = Point2D.construct(line2.m_xEnd, + line2.m_yEnd); + counter++; + } + + if (counter != 2 && !NumberUtils.isNaN(t21)) { + if (!(t11 == 0 && t21 == 0) && !(t12 == 0 && t21 == 1.0))// the "if" + // makes + // sure + // this + // has + // not + // been + // already + // calculated + { + if (param1 != null)// if (param1) + param1[counter] = 0; + if (param2 != null)// if (param2) + param2[counter] = t21; + + if (intersectionPoints != null)// if (intersectionPoints) + intersectionPoints[counter] = Point2D.construct( + line1.m_xStart, line1.m_yStart); + counter++; + } + } + + if (counter != 2 && !NumberUtils.isNaN(t22)) { + if (!(t11 == 1.0 && t22 == 0) && !(t12 == 1.0 && t22 == 1.0))// the + // "if" + // makes + // sure + // this + // has + // not + // been + // already + // calculated + { + if (param1 != null)// if (param1) + param1[counter] = 1.0; + if (param2 != null)// if (param2) + param2[counter] = t22; + + if (intersectionPoints != null)// if (intersectionPoints) + intersectionPoints[counter] = Point2D.construct( + line2.m_xEnd, line2.m_yEnd); + counter++; + } + } + + if (counter > 0) { + if (counter == 2 && param1 != null && param1[0] > param1[1]) {// make + // sure + // the + // intersection + // events + // are + // sorted + // along + // the + // line1 + // can't + // swap + // doulbes + // in + // java + // NumberUtils::Swap(param1[0], + // param1[1]); + double zeroParam1 = param1[0]; + param1[0] = param1[1]; + param1[1] = zeroParam1; + + if (param2 != null)// if (param2) + { + double zeroParam2 = param2[0]; + param2[0] = param2[1]; + param2[1] = zeroParam2;// NumberUtils::Swap(ARRAYELEMENT(param2, + // 0), ARRAYELEMENT(param2, 1)); + } + + if (intersectionPoints != null)// if (intersectionPoints) + { + Point2D tmp = new Point2D(intersectionPoints[0].x, + intersectionPoints[0].y); + intersectionPoints[0] = intersectionPoints[1]; + intersectionPoints[1] = tmp; + } + } + + return counter; + } + + Point2D params = _intersectHelper1(line1, line2, tolerance); + if (NumberUtils.isNaN(params.x)) + return 0; + + if (intersectionPoints != null)// if (intersectionPoints) + { + intersectionPoints[0] = line1.getCoord2D(params.x); + } + + if (param1 != null)// if (param1) + { + param1[0] = params.x; + } + + if (param2 != null)// if (param2) + { + param2[0] = params.y; + } + + return 1; + } + + @Override + int getYMonotonicParts(SegmentBuffer[] monotonicSegments) { + return 0; + } + + // FIXME In Native borg this has no implementation + @Override + void _copyToImpl(Segment dst) { + // TODO Auto-generated method stub + + } + +} diff --git a/src/com/esri/core/geometry/MapGeometry.java b/src/com/esri/core/geometry/MapGeometry.java new file mode 100644 index 00000000..32150cbd --- /dev/null +++ b/src/com/esri/core/geometry/MapGeometry.java @@ -0,0 +1,90 @@ +/* + Copyright 1995-2013 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ + + +package com.esri.core.geometry; + +import java.io.Serializable; + +/** + * The MapGeometry class bundles the geometry with its spatial reference + * together. To work with a geometry object in a map it is necessary to have a + * spatial reference defined for this geometry. + */ +public final class MapGeometry implements Serializable { + private static final long serialVersionUID = 1L; + + Geometry m_geometry = null; + SpatialReference sr = null; + + /** + * Construct a MapGeometry instance using the specified geometry instance + * and its corresponding spatial reference. + * + * @param g + * The geometry to construct the new MapGeometry object. + * @param _sr + * The spatial reference of the geometry. + */ + public MapGeometry(Geometry g, SpatialReference _sr) { + m_geometry = g; + sr = _sr; + } + + /** + * Gets the only geometry without the spatial reference from the + * MapGeometry. + */ + public Geometry getGeometry() { + return m_geometry; + } + + /** + * Sets the geometry for this MapGeometry. + * + * @param geometry + * The geometry. + */ + + public void setGeometry(Geometry geometry) { + this.m_geometry = geometry; + } + + /** + * Sets the spatial reference for this MapGeometry. + * + * @param sr + * The spatial reference. + */ + public void setSpatialReference(SpatialReference sr) { + this.sr = sr; + } + + /** + * Gets the spatial reference for this MapGeometry. + */ + public SpatialReference getSpatialReference() { + return sr; + } +} diff --git a/src/com/esri/core/geometry/MapGeometryCursor.java b/src/com/esri/core/geometry/MapGeometryCursor.java new file mode 100644 index 00000000..81b0b195 --- /dev/null +++ b/src/com/esri/core/geometry/MapGeometryCursor.java @@ -0,0 +1,47 @@ +/* + Copyright 1995-2013 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ +package com.esri.core.geometry; + +/** + * An abstract Geometry Cursor class. + */ +abstract class MapGeometryCursor { + + /** + * Moves the cursor to the next ProjectedGeometry. Returns null when reached + * the end. + */ + public abstract MapGeometry next(); + + /** + * Returns the ID of the current geometry. The ID is propagated across the + * operations (when possible). + * + * Returns an ID associated with the current Geometry. The ID is passed + * along and is returned by some operators to preserve relationship between + * the input and output geometry classes. It is not always possible to + * preserve an ID during an operation. + */ + public abstract int getGeometryID(); +} diff --git a/src/com/esri/core/geometry/MapOGCStructure.java b/src/com/esri/core/geometry/MapOGCStructure.java new file mode 100644 index 00000000..61ec600e --- /dev/null +++ b/src/com/esri/core/geometry/MapOGCStructure.java @@ -0,0 +1,29 @@ +/* + Copyright 1995-2013 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ +package com.esri.core.geometry; + +public class MapOGCStructure { + public OGCStructure m_ogcStructure; + public SpatialReference m_spatialReference; +} diff --git a/src/com/esri/core/geometry/MathUtils.java b/src/com/esri/core/geometry/MathUtils.java new file mode 100644 index 00000000..84e9ec07 --- /dev/null +++ b/src/com/esri/core/geometry/MathUtils.java @@ -0,0 +1,147 @@ +/* + Copyright 1995-2013 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ + +package com.esri.core.geometry; + +class MathUtils { + /** + * The implementation of the Kahan summation algorithm. Use to get better + * precision when adding a lot of values. + */ + static final class KahanSummator { + private double sum; // the accumulated sum + private double compensation; + private double startValue; // the Base (the class returns sum + + // startValue) + + /** + * initialize to the given start value. \param startValue_ The value to + * be added to the accumulated sum. + */ + KahanSummator(double startValue_) { + startValue = startValue_; + reset(); + } + + /** + * Resets the accumulated sum to zero. The getResult() returns + * startValue_ after this call. + */ + void reset() { + sum = 0; + compensation = 0; + } + + /** + * add a value. + */ + void add(double v) { + double y = v - compensation; + double t = sum + y; + double h = t - sum; + compensation = h - y; + sum = t; + } + + /** + * Subtracts a value. + */ + void sub(double v) { + add(-v); + } + + /** + * add another summator. + */ + void add(/* const */KahanSummator v) { + double y = (v.getResult() + v.compensation) - compensation; + double t = sum + y; + double h = t - sum; + compensation = h - y; + sum = t; + } + + /** + * Subtracts another summator. + */ + void sub(/* const */KahanSummator v) { + double y = -(v.getResult() - v.compensation) - compensation; + double t = sum + y; + double h = t - sum; + compensation = h - y; + sum = t; + } + + /** + * Returns current value of the sum. + */ + double getResult() /* const */{ + return startValue + sum; + } + + KahanSummator plusEquals(double v) { + add(v); + return this; + } + + KahanSummator minusEquals(double v) { + add(-v); + return this; + } + + KahanSummator plusEquals(/* const */KahanSummator v) { + add(v); + return this; + } + + KahanSummator minusEquals(/* const */KahanSummator v) { + sub(v); + return this; + } + } + + /** + * Returns one value with the sign of another (like copysign). + */ + static double copySign(double x, double y) { + return y >= 0.0 ? Math.abs(x) : -Math.abs(x); + } + + /** + * C fmod function. + */ + static double FMod(double x, double y) { + return x - Math.floor(x / y) * y; + } + + + + + /** + * Rounds double to the closest integer value. + */ + static double round(double v) { + return Math.floor(v + 0.5); + } +} diff --git a/src/com/esri/core/geometry/MgrsConversionMode.java b/src/com/esri/core/geometry/MgrsConversionMode.java new file mode 100644 index 00000000..21b54da8 --- /dev/null +++ b/src/com/esri/core/geometry/MgrsConversionMode.java @@ -0,0 +1,60 @@ +/* + Copyright 1995-2013 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ + +package com.esri.core.geometry; + +/** + * The modes for converting between military grid reference system (MGRS) + * notation and coordinates. + * */ +public interface MgrsConversionMode { + /** + * Uses the spheroid to determine the military grid string. + */ + public static final int mgrsAutomatic = 0;// PE_MGRS_STYLE_AUTO + /** + * Treats all spheroids as new, like WGS 1984, when creating or reading a + * military grid string. The 180 longitude falls into zone 60. + */ + public static final int mgrsNewStyle = 0x100; // PE_MGRS_STYLE_NEW + /** + * Treats all spheroids as old, like Bessel 1841, when creating or reading a + * military grid string. The 180 longitude falls into zone 60. + */ + public static final int mgrsOldStyle = 0x200; // PE_MGRS_STYLE_OLD + /** + * Treats all spheroids as new, like WGS 1984, when creating or reading a + * military grid string. The 180 longitude falls into zone 01. + */ + public static final int mgrsNewWith180InZone01 = 0x1000 + 0x100; // PE_MGRS_180_ZONE_1_PLUS + // | + // PE_MGRS_STYLE_NEW + /** + * Treats all spheroids as old, like Bessel 1841, when creating or reading a + * military grid string. The 180 longitude falls into zone 01. + */ + public static final int mgrsOldWith180InZone01 = 0x1000 + 0x200; // PE_MGRS_180_ZONE_1_PLUS + // | + // PE_MGRS_STYLE_OLD +} diff --git a/src/com/esri/core/geometry/MultiPath.java b/src/com/esri/core/geometry/MultiPath.java new file mode 100644 index 00000000..5dfd1349 --- /dev/null +++ b/src/com/esri/core/geometry/MultiPath.java @@ -0,0 +1,770 @@ +/* + Copyright 1995-2013 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ + +package com.esri.core.geometry; + +import java.io.Serializable; + +/** + * The MulitPath class is a base class for polygons and polylines. + */ +public abstract class MultiPath extends MultiVertexGeometry implements + Serializable { + MultiPathImpl m_impl; + + @Override + public VertexDescription getDescription() { + return m_impl.getDescription(); + } + + @Override + void assignVertexDescription(VertexDescription src) { + m_impl.assignVertexDescription(src); + } + + @Override + void mergeVertexDescription(VertexDescription src) { + m_impl.mergeVertexDescription(src); + } + + @Override + public void addAttribute(int semantics) { + m_impl.addAttribute(semantics); + } + + @Override + public void dropAttribute(int semantics) { + m_impl.dropAttribute(semantics); + } + + @Override + public void dropAllAttributes() { + m_impl.dropAllAttributes(); + } + + @Override + public int getPointCount() { + return m_impl.getPointCount(); + } + + @Override + public Point getPoint(int index) { + return m_impl.getPoint(index); + } + + @Override + public void setPoint(int index, Point point) { + m_impl.setPoint(index, point); + } + + @Override + public boolean isEmpty() { + return m_impl.isEmptyImpl(); + } + + @Override + public double calculateArea2D() { + return m_impl.calculateArea2D(); + } + + @Override + public double calculateLength2D() { + return m_impl.calculateLength2D(); + } + + public double calculatePathLength2D(int pathIndex) { + return m_impl.calculatePathLength2D(pathIndex); + } + + @Override + public double getAttributeAsDbl(int semantics, int index, int ordinate) { + return m_impl.getAttributeAsDbl(semantics, index, ordinate); + } + + @Override + public int getAttributeAsInt(int semantics, int index, int ordinate) { + return m_impl.getAttributeAsInt(semantics, index, ordinate); + } + + @Override + public void setAttribute(int semantics, int index, int ordinate, + double value) { + m_impl.setAttribute(semantics, index, ordinate, value); + } + + @Override + public void setAttribute(int semantics, int index, int ordinate, int value) { + m_impl.setAttribute(semantics, index, ordinate, value); + } + + @Override + public Point2D getXY(int index) { + return m_impl.getXY(index); + } + + @Override + void getXY(int index, Point2D pt) { + m_impl.getXY(index, pt); + } + + @Override + void setXY(int index, Point2D pt) { + m_impl.setXY(index, pt); + } + + @Override + Point3D getXYZ(int index) { + return m_impl.getXYZ(index); + } + + @Override + void setXYZ(int index, Point3D pt) { + m_impl.setXYZ(index, pt); + } + + @Override + public void queryEnvelope(Envelope env) { + m_impl.queryEnvelope(env); + } + + @Override + void queryEnvelope2D(Envelope2D env) { + m_impl.queryEnvelope2D(env); + } + + @Override + void queryEnvelope3D(Envelope3D env) { + m_impl.queryEnvelope3D(env); + } + + void queryLooseEnvelope(Envelope2D env) { + m_impl.queryLooseEnvelope2D(env); + } + + void queryLooseEnvelope(Envelope3D env) { + m_impl.queryLooseEnvelope3D(env); + } + + @Override + public Envelope1D queryInterval(int semantics, int ordinate) { + return m_impl.queryInterval(semantics, ordinate); + } + + @Override + public void copyTo(Geometry dst) { + m_impl.copyTo((Geometry) dst._getImpl()); + } + + Geometry getBoundary() { + return m_impl.getBoundary(); + } + + @Override + void queryCoordinates(Point2D[] dst) { + m_impl.queryCoordinates(dst); + } + + @Override + void queryCoordinates(Point3D[] dst) { + m_impl.queryCoordinates(dst); + } + + @Override + void queryCoordinates(Point[] dst) { + m_impl.queryCoordinates(dst); + + } + + /** + * Returns TRUE if the multipath contains non-linear segments. + */ + boolean hasNonLinearSegments() { + return m_impl.hasNonLinearSegments(); + } + + /** + * Returns total segment count in the MultiPath. + */ + public int getSegmentCount() { + return m_impl.getSegmentCount(); + } + + /** + * Returns the segment count in the given multipath path. + * + * @param pathIndex + * The path to determine the segment. + * @return The segment of the multipath. + */ + public int getSegmentCount(int pathIndex) { + int segCount = getPathSize(pathIndex); + if (!isClosedPath(pathIndex)) + segCount--; + return segCount; + } + + /** + * Appends all paths from another multipath. + * + * @param src + * The multipath to append to this multipath. + * @param bReversePaths + * TRUE if the multipath is added should be added with its paths + * reversed. + */ + public void add(MultiPath src, boolean bReversePaths) { + m_impl.add((MultiPathImpl) src._getImpl(), bReversePaths); + } + + /** + * Copies a path from another multipath. + * + * @param src + * The multipath to copy from. + * @param srcPathIndex + * The index of the path in the the source MultiPath. + * @param bForward + * When FALSE, the points are inserted in reverse order. + */ + public void addPath(MultiPath src, int srcPathIndex, boolean bForward) { + m_impl.addPath((MultiPathImpl) src._getImpl(), srcPathIndex, bForward); + } + + /** + * Adds a new path to this multipath. + * + * @param points + * The array of points to add to this multipath. + * @param count + * The number of points added to the mulitpath. + * @param bForward + * When FALSE, the points are inserted in reverse order. + */ + void addPath(Point2D[] points, int count, boolean bForward) { + m_impl.addPath(points, count, bForward); + } + + /** + * Adds a new segment to this multipath. + * + * @param segment + * The segment to be added to this mulitpath. + * @param bStartNewPath + * TRUE if a new path will be added. + */ + public void addSegment(Segment segment, boolean bStartNewPath) { + m_impl.addSegment(segment, bStartNewPath); + } + + /** + * Reverses the order of the vertices in each path. + */ + public void reverseAllPaths() { + m_impl.reverseAllPaths(); + } + + /** + * Reverses the order of vertices in the path. + * + * @param pathIndex + * The start index of the path to reverse the order. + */ + public void reversePath(int pathIndex) { + m_impl.reversePath(pathIndex); + } + + /** + * Removes the path at the given index. + * + * @param pathIndex + * The start index to remove the path. + */ + public void removePath(int pathIndex) { + m_impl.removePath(pathIndex); + } + + /** + * Inserts a path from another multipath. + * + * @param pathIndex + * The start index of the multipath to insert. + * @param src + * The multipath to insert into this multipath. Can be the same + * as the multipath being modified. + * @param srcPathIndex + * The start index to insert the path into the multipath. + * @param bForward + * When FALSE, the points are inserted in reverse order. + */ + public void insertPath(int pathIndex, MultiPath src, int srcPathIndex, + boolean bForward) { + m_impl.insertPath(pathIndex, (MultiPathImpl) src._getImpl(), + srcPathIndex, bForward); + } + + /** + * Inserts a path from an array of 2D Points. + * + * @param pathIndex + * The path index of the multipath to place the new path. + * @param points + * The array of points defining the new path. + * @param pointsOffset + * The offset into the array to start reading. + * @param count + * The number of points to insert into the new path. + * @param bForward + * When FALSE, the points are inserted in reverse order. + */ + void insertPath(int pathIndex, Point2D[] points, int pointsOffset, + int count, boolean bForward) { + m_impl.insertPath(pathIndex, points, pointsOffset, count, bForward); + } + + /** + * Inserts vertices from the given multipath into this multipath. All added + * vertices are connected by linear segments with each other and with the + * existing vertices. + * + * @param pathIndex + * The path index in this multipath to insert points to. Must + * correspond to an existing path. + * @param beforePointIndex + * The point index before all other vertices to insert in the + * given path of this multipath. This value must be between 0 and + * GetPathSize(pathIndex), or -1 to insert points at the end of + * the given path. + * @param src + * The source multipath. + * @param srcPathIndex + * The source path index to copy points from. + * @param srcPointIndexFrom + * The start point in the source path to start copying from. + * @param srcPointCount + * The count of points to add. + * @param bForward + * When FALSE, the points are inserted in reverse order. + */ + public void insertPoints(int pathIndex, int beforePointIndex, + MultiPath src, int srcPathIndex, int srcPointIndexFrom, + int srcPointCount, boolean bForward) { + m_impl.insertPoints(pathIndex, beforePointIndex, + (MultiPathImpl) src._getImpl(), srcPathIndex, + srcPointIndexFrom, srcPointCount, bForward); + } + + /** + * Inserts a part of a path from the given array. + * + * @param pathIndex + * The path index in this class to insert points to. Must + * correspond to an existing path. + * @param beforePointIndex + * The point index in the given path of this MultiPath before + * which the vertices need to be inserted. This value must be + * between 0 and GetPathSize(pathIndex), or -1 to insert points + * at the end of the given path. + * @param src + * The source array + * @param srcPointIndexFrom + * The start point in the source array to start copying from. + * @param srcPointCount + * The count of points to add. + * @param bForward + * When FALSE, the points are inserted in reverse order. + */ + void insertPoints(int pathIndex, int beforePointIndex, Point2D[] src, + int srcPointIndexFrom, int srcPointCount, boolean bForward) { + m_impl.insertPoints(pathIndex, beforePointIndex, src, + srcPointIndexFrom, srcPointCount, bForward); + } + + /** + * Inserts a point. + * + * @param pathIndex + * The path index in this class to insert the point to. Must + * correspond to an existing path. + * @param beforePointIndex + * The point index in the given path of this multipath. This + * value must be between 0 and GetPathSize(pathIndex), or -1 to + * insert the point at the end of the given path. + * @param pt + * The point to be inserted. + */ + void insertPoint(int pathIndex, int beforePointIndex, Point2D pt) { + m_impl.insertPoint(pathIndex, beforePointIndex, pt); + } + + /** + * Inserts a point. + * + * @param pathIndex + * The path index in this class to insert the point to. Must + * correspond to an existing path. + * @param beforePointIndex + * The point index in the given path of this multipath. This + * value must be between 0 and GetPathSize(pathIndex), or -1 to + * insert the point at the end of the given path. + * @param pt + * The point to be inserted. + */ + public void insertPoint(int pathIndex, int beforePointIndex, Point pt) { + m_impl.insertPoint(pathIndex, beforePointIndex, pt); + } + + /** + * Removes a point at a given index. + * + * @param pathIndex + * The path from whom to remove the point. + * @param pointIndex + * The index of the point to be removed. + */ + public void removePoint(int pathIndex, int pointIndex) { + m_impl.removePoint(pathIndex, pointIndex); + } + + /** + * Returns the number of paths in this multipath. + * + * @return The number of paths in this multipath. + */ + public int getPathCount() { + return m_impl.getPathCount(); + } + + /** + * Returns the number of vertices in a path. + * + * @param pathIndex + * The index of the path to return the number of vertices from. + * @return The number of vertices in a path. + */ + public int getPathSize(int pathIndex) { + return m_impl.getPathSize(pathIndex); + } + + /** + * Returns the start index of the path. + * + * @param pathIndex + * The index of the path to return the start index from. + * @return The start index of the path. + * + */ + public int getPathStart(int pathIndex) { + return m_impl.getPathStart(pathIndex); + } + + /** + * Returns the index immediately following the last index of the path. + * + * @param pathIndex + * The index of the path to return the end index from. + * @return Integer index after last index of path + */ + public int getPathEnd(int pathIndex) { + return m_impl.getPathEnd(pathIndex); + } + + /** + * Returns the path index from the point index. This is O(log N) operation. + * + * @param pointIndex + * The index of the point. + * @return The index of the path. + */ + public int getPathIndexFromPointIndex(int pointIndex) { + return m_impl.getPathIndexFromPointIndex(pointIndex); + } + + /** + * Starts a new path at given coordinates. + * + * @param x + * The X coordinate of the start point. + * @param y + * The Y coordinate of the start point. + */ + public void startPath(double x, double y) { + m_impl.startPath(x, y); + } + + void startPath(Point2D point) { + m_impl.startPath(point); + } + + void startPath(Point3D point) { + m_impl.startPath(point); + } + + /** + * Starts a new path at a point. + * + * @param point + * The point to start the path from. + */ + public void startPath(Point point) { + m_impl.startPath(point); + } + + /** + * Adds a line segment from the last point to the given end coordinates. + * + * @param x + * The X coordinate to the end point. + * @param y + * The Y coordinate to the end point. + */ + public void lineTo(double x, double y) { + m_impl.lineTo(x, y); + } + + void lineTo(Point2D endPoint) { + m_impl.lineTo(endPoint); + } + + void lineTo(Point3D endPoint) { + m_impl.lineTo(endPoint); + } + + /** + * Adds a Line Segment to the given end point. + * + * @param endPoint + * The end point to which the newly added line segment should + * point. + */ + public void lineTo(Point endPoint) { + m_impl.lineTo(endPoint); + } + + /** + * Adds a Cubic Bezier Segment to the current Path. The Bezier Segment + * connects the current last Point and the given endPoint. + */ + void bezierTo(Point2D controlPoint1, Point2D controlPoint2, Point2D endPoint) { + m_impl.bezierTo(controlPoint1, controlPoint2, endPoint); + } + + /** + * Closes the last path of this multipath with a line segment. The closing + * segment is a segment that connects the last and the first points of the + * path. This is a virtual segment. The first point is not duplicated to + * close the path. + * + * Call this method only for polylines. For polygons this method is + * implicitly called for the Polygon class. + */ + public void closePathWithLine() { + m_impl.closePathWithLine(); + } + + /** + * Closes last path of the MultiPath with the Bezier Segment. + * + * The start point of the Bezier is the last point of the path and the last + * point of the bezier is the first point of the path. + */ + void closePathWithBezier(Point2D controlPoint1, Point2D controlPoint2) { + m_impl.closePathWithBezier(controlPoint1, controlPoint2); + } + + /** + * Closes last path of the MultiPath with the Arc Segment. + */ + void closePathWithArc() { + throw new RuntimeException("not implemented"); /* FIXME */ + } + + /** + * Closes all open paths by adding an implicit line segment from the end + * point to the start point. Call this method only for polylines.For + * polygons this method is implicitly called for the Polygon class. + */ + public void closeAllPaths() { + m_impl.closeAllPaths(); + } + + /** + * Indicates if the given path is closed (represents a ring). A closed path + * has a virtual segment that connects the last and the first points of the + * path. The first point is not duplicated to close the path. Polygons + * always have all paths closed. + * + * @param pathIndex + * The index of the path to check to be closed. + * @return TRUE if the given path is closed (represents a Ring). + */ + public boolean isClosedPath(int pathIndex) { + return m_impl.isClosedPath(pathIndex); + } + + public boolean isClosedPathInXYPlane(int pathIndex) { + return m_impl.isClosedPathInXYPlane(pathIndex); + } + + /** + * Returns TRUE if the given path might have non-linear segments. + */ + boolean hasNonLinearSegments(int pathIndex) { + return m_impl.hasNonLinearSegments(pathIndex); + } + + // //Elliptic Arc + // //returns true, if parameters of the arc are correct (conform with + // equation of elliptic arc) + // //returns false, if parameters are incorrect. + // //In the latter case the command will be executed anyway, however, the + // EllipseAxes will be automatically corrected + // //to conform with equation of the elliptic arc. + // //Elliptic arcs are stored as rational bezier curves. Use + // CalcEllipticArcParams function + // //to calculate geometric parameters of arc + // //bCW - clockwise (true) or anti-clockwise (false). + // //The type parameter helps to resolve situation, when start and end point + // of arc are too close. + // //It gives a hint to the function how to deal with the situation, when + // start point equal or almost equal to the end point. + // //If type == unknownArc and EndPoint == start point, the arc is a point. + // //If type == minorArc, and abs(SweepAngle - 2 * PI) < 0.001 the arc is + // replaced by the line from start point to EndPoint. + // //If type == majorArc, and abs(SweepAngle) < 0.001 the SweepAngle is + // replaced by SweepAngle + (bCW ? -1. : 1.) * 2. * PI. + // //Here SweepAngle is calculated sweep angle of the arc. + // boolean ArcTo(Point2D endPoint, Point2D ellipseAxes, Point2D center, + // double axisXRotationRad, boolean bCW, enum enumArcType type = + // unknownArc); + + /** + * Adds a rectangular closed Path to the MultiPathImpl. + * + * @param envSrc + * is the source rectangle. + * @param bReverse + * Creates reversed path. + */ + void addEnvelope(Envelope2D envSrc, boolean bReverse) { + m_impl.addEnvelope(envSrc, bReverse); + } + + /** + * Adds a rectangular closed path to this multipath. + * + * @param envSrc + * Is the envelope to add to this mulitpath. + * @param bReverse + * Adds the path reversed (counter-clockwise). + */ + public void addEnvelope(Envelope envSrc, boolean bReverse) { + m_impl.addEnvelope(envSrc, bReverse); + } + + // void AddRoundRect(Envelope2D envSrc, double EllipseWidth, double + // EllipseHeight, boolean bReverse); + + // //Adds ellipse with center point Center and rotation angle between axis X + // and ellipse axes Axes.X equal to RotationAngle. + // boolean AddEllipse(Point2D center, Point2D axes, double rotationAngle, + // boolean bReverse); + + // //adds a pie - a closed figure, consisting of two lines and a segment of + // an ellipse + // //pie is drawn always ccw (when axis X is left-right, axis Y is + // bottom-up). negative sweep angle is converted to positive. + // //angles greater than 2 * pi are converted back into the 2 * pi range. + // boolean AddPie(const DRect & rect, double startAngle, double sweepAngle, + // boolean bReverse); + + /** + * Returns a SegmentIterator that is set right before the beginning of the + * multipath. Calling nextPath() will set the iterator to the first path of + * this multipath. + * + * @return The SegmentIterator for this mulitpath. + */ + public SegmentIterator querySegmentIterator() { + return new SegmentIterator(m_impl.querySegmentIterator()); + } + + /** + * Returns a SegmentIterator that is set to a specific vertex of the + * MultiPath. The call to nextSegment() will return the segment that starts + * at the vertex. Calling PreviousSegment () will return the segment that + * starts at the previous vertex. + * + * @param startVertexIndex + * The start index of the SegementIterator. + * @return The SegmentIterator for this mulitpath at the specified vertex. + */ + public SegmentIterator querySegmentIteratorAtVertex(int startVertexIndex) { + return new SegmentIterator( + m_impl.querySegmentIteratorAtVertex(startVertexIndex)); + } + + @Override + public void setEmpty() { + m_impl.setEmpty(); + } + + @Override + public void applyTransformation(Transformation2D transform) { + m_impl.applyTransformation(transform); + } + + @Override + void applyTransformation(Transformation3D transform) { + m_impl.applyTransformation(transform); + } + + @Override + protected Object _getImpl() { + return m_impl; + } + + /** + * Returns the hash code for the multipath. + */ + @Override + public int hashCode() { + return m_impl.hashCode(); + } + + @Override + void getPointByVal(int index, Point outPoint) { + m_impl.getPointByVal(index, outPoint); + } + + @Override + void setPointByVal(int index, Point point) { + m_impl.setPointByVal(index, point); + } + + @Override + public int getStateFlag() { + return m_impl.getStateFlag(); + } + +} diff --git a/src/com/esri/core/geometry/MultiPathImpl.java b/src/com/esri/core/geometry/MultiPathImpl.java new file mode 100644 index 00000000..beca8173 --- /dev/null +++ b/src/com/esri/core/geometry/MultiPathImpl.java @@ -0,0 +1,2608 @@ +/* + Copyright 1995-2013 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ + + +package com.esri.core.geometry; + +final class MultiPathImpl extends MultiVertexGeometryImpl { + + protected boolean m_bPolygon; + protected Point m_moveToPoint; + protected double m_cachedLength2D; + protected double m_cachedArea2D; + + protected AttributeStreamOfDbl m_cachedRingAreas2D; + protected boolean m_bPathStarted; + + // Contains starting points of the parts. The size is getPartCount() + 1. + // First element is 0, last element is equal to the getPointCount(). + protected AttributeStreamOfInt32 m_paths; + // same size as m_parts. Holds flags for each part (whether the part is + // closed, etc. See PathFlags) + protected AttributeStreamOfInt8 m_pathFlags; + // The segment flags. Size is getPointCount(). This is not a vertex + // attribute, because we may want to use indexed access later (via an index + // buffer). + // Can be NULL if the MultiPathImpl contains straight lines only. + protected AttributeStreamOfInt8 m_segmentFlags; + // An index into the m_segmentParams stream. Size is getPointCount(). Can be + // NULL if the MultiPathImpl contains straight lines only. + protected AttributeStreamOfInt32 m_segmentParamIndex; + protected AttributeStreamOfDbl m_segmentParams; + protected int m_curveParamwritePoint; + private int m_currentPathIndex; + + static int[] _segmentParamSizes = { 0, 0, 6, 0, 8, 0 }; // None, Line, + // Bezier, XXX, Arc, + // XXX; + + public boolean hasNonLinearSegments() { + return m_curveParamwritePoint > 0; + } + + // / Cpp /// + // Reviewed vs. Native Jan 11, 2011 + public MultiPathImpl(boolean bPolygon) { + m_bPolygon = bPolygon; + + m_bPathStarted = false; + m_curveParamwritePoint = 0; + m_cachedLength2D = 0; + m_cachedArea2D = 0; + m_pointCount = 0; + m_description = VertexDescriptionDesignerImpl.getDefaultDescriptor2D(); + m_cachedRingAreas2D = null; + m_currentPathIndex = 0; + } + + // Reviewed vs. Native Jan 11, 2011 + public MultiPathImpl(boolean bPolygon, VertexDescription description) { + if (description == null) + throw new IllegalArgumentException(); + + m_bPolygon = bPolygon; + + m_bPathStarted = false; + m_curveParamwritePoint = 0; + m_cachedLength2D = 0; + m_cachedArea2D = 0; + m_pointCount = 0; + m_description = description; + m_cachedRingAreas2D = null; + m_currentPathIndex = 0; + } + + // Reviewed vs. Native Jan 11, 2011 + protected void _initPathStartPoint() { + _touch(); + if (m_moveToPoint == null) + m_moveToPoint = new Point(m_description); + else + m_moveToPoint.assignVertexDescription(m_description); + } + + // Reviewed vs. Native Jan 11, 2011 + /** + * Starts a new Path at the Point. + */ + public void startPath(double x, double y) { + Point2D endPoint = new Point2D(); + endPoint.x = x; + endPoint.y = y; + startPath(endPoint); + } + + // Reviewed vs. Native Jan 11, 2011 + public void startPath(Point2D point) { + _initPathStartPoint(); + m_moveToPoint.setXY(point); + m_bPathStarted = true; + } + + // Reviewed vs. Native Jan 11, 2011 + public void startPath(Point3D point) { + _initPathStartPoint(); + m_moveToPoint.setXYZ(point); + assignVertexDescription(m_moveToPoint.getDescription()); + m_bPathStarted = true; + } + + // Reviewed vs. Native Jan 11, 2011 + public void startPath(Point point) { + if (point.isEmpty()) + // FIXME exc + throw new IllegalArgumentException();// throw new + // IllegalArgumentException(); + + _initPathStartPoint(); + point.copyTo(m_moveToPoint); + + // TODO check MultiPathImpl.cpp comment + // "//the description will be merged later" + // assignVertexDescription(m_moveToPoint.getDescription()); + m_bPathStarted = true; + } + + // Reviewed vs. Native Jan 11, 2011 + protected void _beforeNewSegment(int resizeBy) { + // Called for each new segment being added. + if (m_bPathStarted) { + _initPathStartPoint();// make sure the m_movetoPoint exists and has + // right vertex description + + // The new path is started. Need to grow m_parts and m_pathFlags. + if (m_paths == null) { + m_paths = (AttributeStreamOfInt32) AttributeStreamBase + .createIndexStream(2); + m_paths.write(0, 0); + m_pathFlags = (AttributeStreamOfInt8) AttributeStreamBase + .createByteStream(2, (byte) 0); + } else { + // _ASSERT(m_parts.size() >= 2); + m_paths.resize(m_paths.size() + 1, 0); + m_pathFlags.resize(m_pathFlags.size() + 1, 0); + } + + if (m_bPolygon) { + // Mark the path as closed + m_pathFlags.write(m_pathFlags.size() - 2, + (byte) PathFlags.enumClosed); + } + + resizeBy++; // +1 for the StartPath point. + } + + int oldcount = m_pointCount; + m_paths.write(m_paths.size() - 1, m_pointCount + resizeBy); // The + // NotifyModified + // will + // update + // the + // m_pointCount + // with this + // value. + _resizeImpl(oldcount + resizeBy); + m_pathFlags.write(m_paths.size() - 1, (byte) 0); + + if (m_bPathStarted) { + setPointByVal(oldcount, m_moveToPoint);// setPoint(oldcount, + // m_moveToPoint); //finally + // set the start point to + // the geometry + m_bPathStarted = false; + } + } + + // Reviewed vs. Native Jan 11, 2011 + protected void _finishLineTo() { + } + + // Reviewed vs. Native Jan 11, 2011 + /** + * adds a Line Segment from the last Point to the given endPoint. + */ + public void lineTo(double x, double y) { + _beforeNewSegment(1); + setXY(m_pointCount - 1, x, y); + _finishLineTo(); + // Point2D endPoint = new Point2D(); + // endPoint.x = x; endPoint.y = y; + // lineTo(endPoint); + } + + // Reviewed vs. Native Jan 11, 2011 + public void lineTo(Point2D endPoint) { + _beforeNewSegment(1); + setXY(m_pointCount - 1, endPoint); + _finishLineTo(); + } + + // Reviewed vs. Native Jan 11, 2011 + public void lineTo(Point3D endPoint) { + _beforeNewSegment(1); + setXYZ(m_pointCount - 1, endPoint); + _finishLineTo(); + } + + // Reviewed vs. Native Jan 11, 2011 + public void lineTo(Point endPoint) { + _beforeNewSegment(1); + setPointByVal(m_pointCount - 1, endPoint); + _finishLineTo(); + } + + // Reviewed vs. Native Jan 11, 2011 + protected void _initSegmentData(int sz) { + if (m_segmentParamIndex == null) { + m_segmentFlags = (AttributeStreamOfInt8) AttributeStreamBase + .createByteStream(m_pointCount, + (byte) SegmentFlags.enumLineSeg); + m_segmentParamIndex = (AttributeStreamOfInt32) AttributeStreamBase + .createIndexStream(m_pointCount, -1); + } + + int size = m_curveParamwritePoint + sz; + if (m_segmentParams == null) { + m_segmentParams = (AttributeStreamOfDbl) AttributeStreamBase + .createAttributeStreamWithPersistence( + VertexDescription.Persistence.enumDouble, size); + } else { + m_segmentParams.resize(size, 0); + } + } + + // Reviewed vs. Native Jan 11, 2011 + protected void _finishBezierTo() { + // _ASSERT(m_segmentFlags != null); + // _ASSERT(m_segmentParamIndex != null); + + m_segmentFlags.write(m_pointCount - 2, + (byte) SegmentFlags.enumBezierSeg); + } + + // Reviewed vs. Native Jan 11, 2011 + /** + * adds a Cubic Bezier Segment to the current Path. The Bezier Segment + * connects the current last Point and the given endPoint. + */ + public void bezierTo(Point2D controlPoint1, Point2D controlPoint2, + Point2D endPoint) { + _beforeNewSegment(1); + setXY(m_pointCount - 1, endPoint); + double z; + _initSegmentData(6); + m_pathFlags.setBits(m_pathFlags.size() - 1, + (byte) PathFlags.enumHasNonlinearSegments); + m_segmentParamIndex.write(m_pointCount - 2, m_curveParamwritePoint); + m_curveParamwritePoint += 6; + int curveIndex = m_curveParamwritePoint; + m_segmentParams.write(curveIndex, controlPoint1.x); + m_segmentParams.write(curveIndex + 1, controlPoint1.y); + z = 0;// TODO: calculate me. + m_segmentParams.write(curveIndex + 2, z); + m_segmentParams.write(curveIndex + 3, controlPoint2.x); + m_segmentParams.write(curveIndex + 4, controlPoint2.y); + z = 0;// TODO: calculate me. + m_segmentParams.write(curveIndex + 5, z); + _finishBezierTo(); + } + + // Reviewed vs. Native Jan 11, 2011 + public void openPath(int pathIndex) { + _touch(); + if (m_bPolygon) + // FIXME exc + throw new GeometryException("internal error");// do not call this + // method on a + // polygon + + int pathCount = getPathCount(); + if (pathIndex > getPathCount()) + // FIXME exc + throw new IllegalArgumentException(); + + if (m_pathFlags == null) + // FIXME exc + throw new GeometryException("internal error"); + + m_pathFlags.clearBits(pathIndex, (byte) PathFlags.enumClosed); + } + + // Reviewed vs. Native Jan 11, 2011 + // Major Changes on 16th of January + public void openPathAndDuplicateStartVertex(int pathIndex) { + _touch(); + if (m_bPolygon) + throw new GeometryException("internal error");// do not call this + // method on a + // polygon + + int pathCount = getPathCount(); + if (pathIndex > pathCount) + throw new GeometryException("internal error"); + + if (!isClosedPath(pathIndex)) + return;// do not open if open + + if (m_pathFlags == null)// if (!m_pathFlags) + throw new GeometryException("nternal_error"); + + int oldPointCount = m_pointCount; + int pathIndexStart = getPathStart(pathIndex); + int pathIndexEnd = getPathEnd(pathIndex); + _resizeImpl(m_pointCount + 1); // resize does not write into m_paths + // anymore! + _verifyAllStreams(); + for (int iattr = 0, nattr = m_description.getAttributeCount(); iattr < nattr; iattr++) { + if (m_vertexAttributes[iattr] != null)// if + // (m_vertexAttributes[iattr]) + { + int semantics = m_description._getSemanticsImpl(iattr); + int comp = VertexDescription.getComponentCount(semantics); + m_vertexAttributes[iattr].insertRange(comp * pathIndexEnd, + m_vertexAttributes[iattr], comp * pathIndexStart, comp, + true, 1, comp * oldPointCount); + } + } + + for (int ipath = pathCount; ipath > pathIndex; ipath--) { + int iend = m_paths.read(ipath); + m_paths.write(ipath, iend + 1); + } + + m_pathFlags.clearBits(pathIndex, (byte) PathFlags.enumClosed); + } + + // Reviewed vs. Native Jan 11, 2011 + // Major Changes on 16th of January + public void openAllPathsAndDuplicateStartVertex() { + _touch(); + if (m_bPolygon) + // FIXME + throw new GeometryException("internal error");// do not call this + // method on a + // polygon + + if (m_pathFlags == null)// if (!m_pathFlags) + throw new GeometryException("nternal_error"); + + _verifyAllStreams(); + + int closedPathCount = 0; + int pathCount = getPathCount(); + for (int i = 0; i < pathCount; i++) { + if (m_pathFlags.read(i) == (byte) PathFlags.enumClosed) { + closedPathCount++; + } + } + + for (int iattr = 0, nattr = m_description.getAttributeCount(); iattr < nattr; iattr++) { + if (m_vertexAttributes[iattr] != null) { + int semantics = m_description._getSemanticsImpl(iattr);// int + // semantics + // = + // m_description._getSemanticsImpl(iattr); + int comp = VertexDescription.getComponentCount(semantics); + int newSize = comp * (m_pointCount + closedPathCount); + m_vertexAttributes[iattr].resize(newSize); + + int offset = closedPathCount; + int ipath = pathCount; + for (int i = m_pointCount - 1; i >= 0; i--) { + if (i + 1 == m_paths.read(ipath)) { + ipath--; + if (m_pathFlags.read(ipath) == (byte) PathFlags.enumClosed) { + int istart = m_paths.read(ipath); + + for (int c = 0; c < comp; c++) { + double v = m_vertexAttributes[iattr] + .readAsDbl(comp * istart + c); + m_vertexAttributes[iattr].writeAsDbl(comp + * (offset + i) + c, v); + } + + if (--offset == 0) + break; + } + } + + for (int c = 0; c < comp; c++) { + double v = m_vertexAttributes[iattr].readAsDbl(comp * i + + c); + m_vertexAttributes[iattr].writeAsDbl(comp + * (offset + i) + c, v); + } + } + } + } + + int offset = closedPathCount; + for (int ipath = pathCount; ipath > 0; ipath--) { + int iend = m_paths.read(ipath); + m_paths.write(ipath, iend + offset); + + if (m_pathFlags.read(ipath - 1) == (byte) PathFlags.enumClosed) { + m_pathFlags.clearBits(ipath - 1, (byte) PathFlags.enumClosed); + + if (--offset == 0) { + break; + } + } + } + + m_pointCount += closedPathCount; + } + + void closePathWithLine(int path_index) { + // touch_(); + throwIfEmpty(); + + byte pf = m_pathFlags.read(path_index); + m_pathFlags.write(path_index, (byte) (pf | PathFlags.enumClosed)); + if (m_segmentFlags != null) { + int vindex = getPathEnd(path_index) - 1; + m_segmentFlags.write(vindex, (byte) SegmentFlags.enumLineSeg); + m_segmentParamIndex.write(vindex, -1); + } + } + + void closePathWithLine() { + throwIfEmpty(); + m_bPathStarted = false; + closePathWithLine(getPathCount() - 1); + } + + // Reviewed vs. Native Jan 11, 2011 + /** + * Closes all open curves by adding an implicit line segment from the end + * point to the start point. + */ + public void closeAllPaths() { + _touch(); + if (m_bPolygon || isEmptyImpl()) + return; + + m_bPathStarted = false; + + for (int ipath = 0, npart = m_paths.size() - 1; ipath < npart; ipath++) { + if (isClosedPath(ipath)) + continue; + + byte pf = m_pathFlags.read(ipath); + m_pathFlags.write(ipath, (byte) (pf | PathFlags.enumClosed)); + // if (m_segmentFlags) + // { + // m_segmentFlags.write(m_pointCount - 1, + // (byte)SegmentFlags.LineSeg)); + // m_segmentParamIndex.write(m_pointCount - 1, -1); + // } + } + } + + // Reviewed vs. Native Jan 11, 2011 + /** + * Returns the size of the segment data for the given segment type. + * + * @param flag + * is one of the segment flags from the SegmentFlags enum. + * @return the size of the segment params as the number of doubles. + */ + public static int getSegmentDataSize(byte flag) { + return _segmentParamSizes[flag]; + } + + // Reviewed vs. Native Jan 11, 2011 + /** + * Closes last path of the MultiPathImpl with the Bezier Segment. + * + * The start point of the Bezier is the last point of the path and the last + * point of the bezier is the first point of the path. + */ + public void closePathWithBezier(Point2D controlPoint1, Point2D controlPoint2) { + _touch(); + if (isEmptyImpl()) + throw new GeometryException( + "Invalid call. This operation cannot be performed on an empty geometry."); + + m_bPathStarted = false; + + int pathIndex = m_paths.size() - 2; + byte pf = m_pathFlags.read(pathIndex); + m_pathFlags + .write(pathIndex, + (byte) (pf | PathFlags.enumClosed | PathFlags.enumHasNonlinearSegments)); + _initSegmentData(6); + + byte oldType = m_segmentFlags + .read((byte) ((m_pointCount - 1) & SegmentFlags.enumSegmentMask)); + m_segmentFlags.write(m_pointCount - 1, + (byte) (SegmentFlags.enumBezierSeg)); + + int curveIndex = m_curveParamwritePoint; + if (getSegmentDataSize(oldType) < getSegmentDataSize((byte) SegmentFlags.enumBezierSeg)) { + m_segmentParamIndex.write(m_pointCount - 1, m_curveParamwritePoint); + m_curveParamwritePoint += 6; + } else { + // there was a closing bezier curve or an arc here. We can reuse the + // storage. + curveIndex = m_segmentParamIndex.read(m_pointCount - 1); + } + + double z; + m_segmentParams.write(curveIndex, controlPoint1.x); + m_segmentParams.write(curveIndex + 1, controlPoint1.y); + z = 0;// TODO: calculate me. + m_segmentParams.write(curveIndex + 2, z); + + m_segmentParams.write(curveIndex + 3, controlPoint2.x); + m_segmentParams.write(curveIndex + 4, controlPoint2.y); + z = 0;// TODO: calculate me. + m_segmentParams.write(curveIndex + 5, z); + } + + // Reviewed vs. Native Jan 11, 2011 + /** + * Returns True if the given path is closed (represents a Ring). + */ + public boolean isClosedPath(int ipath) { + // Should we make a function called _UpdateClosedPathFlags and call it + // here? + return ((byte) (m_pathFlags.read(ipath) & PathFlags.enumClosed)) != 0; + } + + public boolean isClosedPathInXYPlane(int path_index) { + if (isClosedPath(path_index)) + return true; + int istart = getPathStart(path_index); + int iend = getPathEnd(path_index) - 1; + if (istart > iend) + return false; + Point2D ptS = getXY(istart); + Point2D ptE = getXY(iend); + return ptS.isEqual(ptE); + } + + // Reviewed vs. Native Jan 11, 2011 + /** + * Returns True if the given path might have non-linear segments. + */ + public boolean hasNonLinearSegments(int ipath) { + // Should we make a function called _UpdateHasNonLinearSegmentsFlags and + // call it here? + return (m_pathFlags.read(ipath) & PathFlags.enumHasNonlinearSegments) != 0; + } + + // Reviewed vs. Native Jan 11, 2011 + public void addSegment(Segment segment, boolean bStartNewPath) { + mergeVertexDescription(segment.getDescription()); + if (segment.getType() == Type.Line) { + Point point = new Point(); + if (bStartNewPath || isEmpty()) { + // FIXME change getStart to queryStart!!!!!!! + segment.queryStart(point); + startPath(point); + } + // FIXME change getStart to queryEnd + segment.queryEnd(point); + lineTo(point); + } else { + // FIXME + throw new GeometryException("internal error"); + } + } + + // Reviewed vs. Native Jan 11, 2011 + /** + * adds a rectangular closed Path to the MultiPathImpl. + * + * @param envSrc + * is the source rectangle. + * @param bReverse + * Creates reversed path. + */ + public void addEnvelope(Envelope2D envSrc, boolean bReverse) { + boolean bWasEmpty = m_pointCount == 0; + + startPath(envSrc.xmin, envSrc.ymin); + if (bReverse) { + lineTo(envSrc.xmax, envSrc.ymin); + lineTo(envSrc.xmax, envSrc.ymax); + lineTo(envSrc.xmin, envSrc.ymax); + } else { + lineTo(envSrc.xmin, envSrc.ymax); + lineTo(envSrc.xmax, envSrc.ymax); + lineTo(envSrc.xmax, envSrc.ymin); + } + + closePathWithLine(); + m_bPathStarted = false; + + if (bWasEmpty && !bReverse) { + _setDirtyFlag(DirtyFlags.DirtyIsEnvelope, false);// now we no(sic?) + // the polypath + // is envelope + } + } + + // Reviewed vs. Native Jan 11, 2011 + /** + * adds a rectangular closed Path to the MultiPathImpl. + * + * @param envSrc + * is the source rectangle. + * @param bReverse + * Creates reversed path. + */ + public void addEnvelope(Envelope envSrc, boolean bReverse) { + if (envSrc.isEmpty()) + return; + + boolean bWasEmpty = m_pointCount == 0; + Point pt = new Point(m_description);// getDescription()); + for (int i = 0, n = 4; i < n; i++) { + int j = bReverse ? n - i - 1 : i; + + envSrc.queryCornerByVal(j, pt); + if (i == 0) + startPath(pt); + else + lineTo(pt); + } + + closePathWithLine(); + m_bPathStarted = false; + + if (bWasEmpty && !bReverse) + _setDirtyFlag(DirtyFlags.DirtyIsEnvelope, false);// now we know the + // polypath is + // envelope + } + + // Reviewed vs. Native Jan 11, 2011 + public void add(MultiPathImpl src, boolean bReversePaths) { + for (int i = 0; i < src.getPathCount(); i++) + addPath(src, i, !bReversePaths); + } + + // Reviewed vs. Native Jan 11, 2011 + // FIXME THERE IS POTENTIALLY A BUG WITH the use of AttributeStream + // InsertRange + public void addPath(MultiPathImpl src, int srcPathIndex, boolean bForward) { + insertPath(-1, src, srcPathIndex, bForward); + } + + // Reviewed vs. Native Jan 11, 2011 Significant changes to last for loop + public void addPath(Point2D[] _points, int count, boolean bForward) { + insertPath(-1, _points, 0, count, bForward); + } + + // FIXME add add to attributestream base + // public void addPath(double[][] _points, int count, boolean bForward) + // { + // m_bPathStarted = false; + // + // int oldPointCount = m_pointCount; + // _verifyAllStreams(); + // + // int newPointCount = oldPointCount + count; + // if (oldPointCount > 0) + // m_paths.add(newPointCount); + // else + // { + // // _ASSERT(m_paths.size() == 2); + // m_paths.write(1, newPointCount); + // } + // + // _resizeImpl(newPointCount); + // + // _verifyAllStreams(); + // + // if (m_segmentParamIndex != null) + // { + // m_segmentParamIndex.resize(m_pointCount, -1); + // m_segmentFlags.resize(m_pointCount, (byte)SegmentFlags.enumLineSeg); + // } + // + // if (oldPointCount > 0) + // m_pathFlags.add(0); + // + // // _ASSERT(m_pathFlags.size() == m_paths.size()); + // + // if (m_bPolygon) + // { + // //Marc the path as closed + // m_pathFlags.write(m_pathFlags.size() - 2, (byte)PathFlags.enumClosed); + // } + // + // int j = oldPointCount; + // AttributeStreamOfDbl points = + // (AttributeStreamOfDbl)m_vertexAttributes[0]; + // for (int i = 0; i < count; i++, j++) + // { + // int index = (bForward ? i : count - i - 1); + // points.write(2 * j, _points[index][0]); + // points.write(2 * j + 1, _points[index][1]); + // } + // } + // + + public void addSegmentsFromPath(MultiPathImpl src, int src_path_index, + int src_segment_from, int src_segment_count, + boolean b_start_new_path) { + if (!b_start_new_path && getPathCount() == 0) + b_start_new_path = true; + + if (src_path_index < 0) + src_path_index = src.getPathCount() - 1; + + if (src_path_index >= src.getPathCount() || src_segment_from < 0 + || src_segment_count < 0 + || src_segment_count > src.getSegmentCount(src_path_index)) + throw new GeometryException("index out of bounds"); + + if (src_segment_count == 0) + return; + + boolean bIncludesClosingSegment = src.isClosedPath(src_path_index) + && src_segment_from + src_segment_count == src + .getSegmentCount(src_path_index); + + if (bIncludesClosingSegment && src_segment_count == 1) + return;// cannot add a closing segment alone. + + m_bPathStarted = false; + + mergeVertexDescription(src.getDescription()); + int src_point_count = src_segment_count; + int srcFromPoint = src.getPathStart(src_path_index) + src_segment_from + + 1; + if (b_start_new_path)// adding a new path. + { + src_point_count++;// add start point. + srcFromPoint--; + } + + if (bIncludesClosingSegment) { + src_point_count--; + } + + int oldPointCount = m_pointCount; + _resizeImpl(m_pointCount + src_point_count); + _verifyAllStreams(); + + if (b_start_new_path) { + if (src_point_count == 0) + return;// happens when adding a single closing segment to the + // new path + + m_paths.add(m_pointCount); + + byte flags = src.m_pathFlags.read(src_path_index); + flags &= ~(byte) PathFlags.enumCalcMask;// remove calculated flags + + if (m_bPolygon) + flags |= (byte) PathFlags.enumClosed; + + m_pathFlags.write(m_pathFlags.size() - 1, flags); + m_pathFlags.add((byte) 0); + } else { + m_paths.write(m_pathFlags.size() - 1, m_pointCount); + } + + // Index_type absoluteIndex = pathStart + before_point_index; + + for (int iattr = 0, nattr = m_description.getAttributeCount(); iattr < nattr; iattr++) { + int semantics = m_description.getSemantics(iattr); + int comp = VertexDescription.getComponentCount(semantics); + + int isrcAttr = src.m_description.getAttributeIndex(semantics); + if (isrcAttr < 0 || src.m_vertexAttributes[isrcAttr] == null) {// The + // source + // does + // not + // have + // the + // attribute. + // insert + // default + // value + double v = VertexDescription.getDefaultValue(semantics); + m_vertexAttributes[iattr].insertRange(comp * oldPointCount, v, + src_point_count * comp, comp * oldPointCount); + continue; + } + + // add vertices to the given stream + boolean b_forward = true; + m_vertexAttributes[iattr].insertRange(comp * oldPointCount, + src.m_vertexAttributes[isrcAttr], comp * srcFromPoint, + src_point_count * comp, b_forward, comp, comp + * oldPointCount); + } + + if (hasNonLinearSegments()) { + // TODO: implement me. For example as a while loop over all curves. + // Replace, calling ReplaceSegment + throw new GeometryException("internal error"); + // m_segment_flags->write_range((get_path_start(path_index) + + // before_point_index + src_point_count), (oldPointCount - + // get_path_start(path_index) - before_point_index), + // m_segment_flags, (get_path_start(path_index) + + // before_point_index), true, 1); + // m_segment_param_index->write_range((get_path_start(path_index) + + // before_point_index + src_point_count), (oldPointCount - + // get_path_start(path_index) - before_point_index), + // m_segment_param_index, (get_path_start(path_index) + + // before_point_index), true, 1); + // for (Index_type i = get_path_start(path_index) + + // before_point_index, n = get_path_start(path_index) + + // before_point_index + src_point_count; i < n; i++) + // { + // m_segment_flags->write(i, (int8_t)enum_value1(Segment_flags, + // enum_line_seg)); + // m_segment_param_index->write(i, -1); + // } + } + + if (src.hasNonLinearSegments(src_path_index)) { + // TODO: implement me. For example as a while loop over all curves. + // Replace, calling ReplaceSegment + throw new GeometryException("internal error"); + } + + notifyModified(DirtyFlags.DirtyCoordinates); + } + + // Reviewed vs. Native Jan 11, 2011 + public void reverseAllPaths() { + for (int i = 0, n = getPathCount(); i < n; i++) { + reversePath(i); + } + } + + // Reviewed vs. Native Jan 11, 2011 + public void reversePath(int pathIndex) { + _verifyAllStreams(); + int pathCount = getPathCount(); + if (pathIndex >= pathCount) + // FIXME exc + throw new IllegalArgumentException(); + + int reversedPathStart = getPathStart(pathIndex); + int reversedPathSize = getPathSize(pathIndex); + int offset = isClosedPath(pathIndex) ? 1 : 0; + + // TODO: a bug for the non linear segments here. + // There could be an issue here if someone explicity closes the path + // with the same start/end point. + for (int iattr = 0, nattr = m_description.getAttributeCount(); iattr < nattr; iattr++) { + if (m_vertexAttributes[iattr] != null) { + int semantics = m_description._getSemanticsImpl(iattr); + int comp = VertexDescription.getComponentCount(semantics); + m_vertexAttributes[iattr].reverseRange(comp + * (reversedPathStart + offset), comp + * (reversedPathSize - offset), comp); + } + } + + notifyModified(DirtyFlags.DirtyCoordinates); + } + + // Reviewed vs. Native Jan 11, 2011 + // TODO: Nonlinearsegments + public void removePath(int pathIndex) { + _verifyAllStreams(); + int pathCount = getPathCount(); + + if (pathIndex < 0) + pathIndex = pathCount - 1; + + if (pathIndex >= pathCount) + throw new IllegalArgumentException(); + + boolean bDirtyRingAreas2D = _hasDirtyFlag(DirtyFlags.DirtyRingAreas2D); + + int removedPathStart = getPathStart(pathIndex); + int removedPathSize = getPathSize(pathIndex); + + // Remove the attribute values for the path + for (int iattr = 0, nattr = m_description.getAttributeCount(); iattr < nattr; iattr++) { + if (m_vertexAttributes[iattr] != null) { + int semantics = m_description._getSemanticsImpl(iattr); + int comp = VertexDescription.getComponentCount(semantics); + m_vertexAttributes[iattr].eraseRange(comp * removedPathStart, + comp * removedPathSize, comp * m_pointCount); + } + } + + // Change the start of each path after the removed path + for (int i = pathIndex + 1; i <= pathCount; i++) { + int istart = m_paths.read(i); + m_paths.write(i - 1, istart - removedPathSize); + } + + if (m_pathFlags == null) { + for (int i = pathIndex + 1; i <= pathCount; i++) { + byte flags = m_pathFlags.read(i); + m_pathFlags.write(i - 1, flags); + } + } + + m_paths.resize(pathCount); + m_pathFlags.resize(pathCount); + m_pointCount -= removedPathSize; + m_reservedPointCount -= removedPathSize; + + notifyModified(DirtyFlags.DirtyCoordinates); + } + + // TODO: Nonlinearsegments + public void insertPath(int pathIndex, MultiPathImpl src, int srcPathIndex, + boolean bForward) { + if (src == this) + throw new IllegalArgumentException(); + + if (srcPathIndex >= src.getPathCount()) + throw new IllegalArgumentException(); + + int oldPathCount = getPathCount(); + if (pathIndex > oldPathCount) + throw new IllegalArgumentException(); + + if (pathIndex < 0) + pathIndex = oldPathCount; + + if (srcPathIndex < 0) + srcPathIndex = src.getPathCount() - 1; + + m_bPathStarted = false; + + mergeVertexDescription(src.m_description);// merge attributes from the + // source + + src._verifyAllStreams();// the source need to be correct. + + int srcPathIndexStart = src.getPathStart(srcPathIndex); + int srcPathSize = src.getPathSize(srcPathIndex); + int oldPointCount = m_pointCount; + int offset = src.isClosedPath(srcPathIndex) && !bForward ? 1 : 0; + + _resizeImpl(m_pointCount + srcPathSize); + _verifyAllStreams(); + int pathIndexStart = pathIndex < oldPathCount ? getPathStart(pathIndex) + : oldPointCount; + + // Copy all attribute values. + for (int iattr = 0, nattr = m_description.getAttributeCount(); iattr < nattr; iattr++) { + int semantics = m_description._getSemanticsImpl(iattr); + int isrcAttr = src.m_description.getAttributeIndex(semantics); + + int comp = VertexDescription.getComponentCount(semantics); + + if (isrcAttr >= 0 && src.m_vertexAttributes[isrcAttr] != null) { + if (offset != 0) + m_vertexAttributes[iattr].insertRange( + pathIndexStart * comp, + src.m_vertexAttributes[isrcAttr], comp + * srcPathIndexStart, comp, true, comp, comp + * oldPointCount); + m_vertexAttributes[iattr].insertRange((pathIndexStart + offset) + * comp, src.m_vertexAttributes[isrcAttr], comp + * (srcPathIndexStart + offset), comp + * (srcPathSize - offset), bForward, comp, comp + * (oldPointCount + offset)); + } else { + // Need to make room for the attributes, so we copy default + // values in + + double v = VertexDescription.getDefaultValue(semantics); + m_vertexAttributes[iattr].insertRange(pathIndexStart * comp, v, + comp * srcPathSize, comp * oldPointCount); + } + } + + int newPointCount = oldPointCount + srcPathSize; + m_paths.add(newPointCount); + + for (int ipath = oldPathCount; ipath >= pathIndex + 1; ipath--) { + int iend = m_paths.read(ipath - 1); + m_paths.write(ipath, iend + srcPathSize); + } + + // ========================== todo: NonLinearSegments ================= + if (src.hasNonLinearSegments(srcPathIndex)) { + + } + + m_pathFlags.add((byte) 0); + + // _ASSERT(m_pathFlags.size() == m_paths.size()); + + for (int ipath = oldPathCount - 1; ipath >= pathIndex + 1; ipath--) { + byte flags = m_pathFlags.read(ipath); + flags &= ~(byte) PathFlags.enumCalcMask;// remove calculated flags + m_pathFlags.write(ipath + 1, flags); + } + + AttributeStreamOfInt8 srcPathFlags = src.getPathFlagsStreamRef(); + byte flags = srcPathFlags.read(srcPathIndex); + flags &= ~(byte) PathFlags.enumCalcMask;// remove calculated flags + + if (m_bPolygon) + flags |= (byte) PathFlags.enumClosed; + + m_pathFlags.write(pathIndex, flags); + } + + public void insertPath(int pathIndex, Point2D[] points, int pointsOffset, + int count, boolean bForward) { + int oldPathCount = getPathCount(); + if (pathIndex > oldPathCount) + throw new IllegalArgumentException(); + + if (pathIndex < 0) + pathIndex = oldPathCount; + + m_bPathStarted = false; + + int oldPointCount = m_pointCount; + + // Copy all attribute values. + if (points != null) { + _resizeImpl(m_pointCount + count); + _verifyAllStreams(); + + int pathStart = pathIndex < oldPathCount ? getPathStart(pathIndex) + : oldPointCount; + + for (int iattr = 0, nattr = m_description.getAttributeCount(); iattr < nattr; iattr++) { + int semantics = m_description._getSemanticsImpl(iattr); + + if (semantics == VertexDescription.Semantics.POSITION) { + // copy range to make place for new vertices + m_vertexAttributes[iattr].writeRange( + 2 * (pathStart + count), + 2 * (oldPointCount - pathIndex), + m_vertexAttributes[iattr], 2 * pathStart, true, 2); + + AttributeStreamOfDbl position = (AttributeStreamOfDbl) (AttributeStreamBase) getAttributeStreamRef(semantics); + + int j = pathStart; + for (int i = 0; i < count; i++, j++) { + int index = (bForward ? pointsOffset + i : pointsOffset + + count - i - 1); + position.write(2 * j, points[index].x); + position.write(2 * j + 1, points[index].y); + } + } else { + // Need to make room for the attributes, so we copy default + // values in + + int comp = VertexDescription.getComponentCount(semantics); + double v = VertexDescription.getDefaultValue(semantics); + m_vertexAttributes[iattr].insertRange(pathStart * comp, v, + comp * count, comp * oldPointCount); + } + } + } else { + _verifyAllStreams(); + } + + m_paths.add(m_pointCount); + + for (int ipath = oldPathCount; ipath >= pathIndex + 1; ipath--) { + int iend = m_paths.read(ipath - 1); + m_paths.write(ipath, iend + count); + } + + m_pathFlags.add((byte) 0); + + // _ASSERT(m_pathFlags.size() == m_paths.size()); + + for (int ipath = oldPathCount - 1; ipath >= pathIndex + 1; ipath--) { + byte flags = m_pathFlags.read(ipath); + flags &= ~(byte) PathFlags.enumCalcMask;// remove calculated flags + m_pathFlags.write(ipath + 1, flags); + } + + if (m_bPolygon) + m_pathFlags.write(pathIndex, (byte) PathFlags.enumClosed); + } + + public void insertPoints(int pathIndex, int beforePointIndex, + MultiPathImpl src, int srcPathIndex, int srcPointIndexFrom, + int srcPointCount, boolean bForward) { + if (pathIndex < 0) + pathIndex = getPathCount(); + + if (srcPathIndex < 0) + srcPathIndex = src.getPathCount() - 1; + + if (pathIndex > getPathCount() + || beforePointIndex > getPathSize(pathIndex) + || srcPathIndex >= src.getPathCount() + || srcPointCount > src.getPathSize(srcPathIndex)) + throw new GeometryException("index out of bounds"); + + if (srcPointCount == 0) + return; + + mergeVertexDescription(src.m_description); + + if (pathIndex == getPathCount())// adding a new path. + { + m_paths.add(m_pointCount); + + byte flags = src.m_pathFlags.read(srcPathIndex); + flags &= ~(byte) PathFlags.enumCalcMask;// remove calculated flags + + if (!m_bPolygon) + m_pathFlags.add(flags); + else + m_pathFlags.add((byte) (flags | PathFlags.enumClosed)); + } + + if (beforePointIndex < 0) + beforePointIndex = getPathSize(pathIndex); + + int oldPointCount = m_pointCount; + _resizeImpl(m_pointCount + srcPointCount); + _verifyAllStreams(); + src._verifyAllStreams(); + + int pathStart = getPathStart(pathIndex); + int absoluteIndex = pathStart + beforePointIndex; + + if (srcPointCount < 0) + srcPointCount = src.getPathSize(srcPathIndex); + + int srcPathStart = src.getPathStart(srcPathIndex); + int srcAbsoluteIndex = srcPathStart + srcPointCount; + + for (int iattr = 0, nattr = m_description.getAttributeCount(); iattr < nattr; iattr++) { + int semantics = m_description._getSemanticsImpl(iattr); + int comp = VertexDescription.getComponentCount(semantics); + + int isrcAttr = src.m_description.getAttributeIndex(semantics); + if (isrcAttr < 0 || src.m_vertexAttributes[isrcAttr] == null) // The + // source + // does + // not + // have + // the + // attribute. + { + double v = VertexDescription.getDefaultValue(semantics); + m_vertexAttributes[iattr].insertRange(comp * absoluteIndex, v, + srcAbsoluteIndex * comp, comp * oldPointCount); + continue; + } + + // add vertices to the given stream + m_vertexAttributes[iattr].insertRange(comp + * (pathStart + beforePointIndex), + src.m_vertexAttributes[isrcAttr], comp + * (srcPathStart + srcPointIndexFrom), srcPointCount + * comp, bForward, comp, comp * oldPointCount); + } + + if (hasNonLinearSegments()) {// TODO: probably a bug here when a new + // path is added. + m_segmentFlags.writeRange((getPathStart(pathIndex) + + beforePointIndex + srcPointCount), (oldPointCount + - getPathStart(pathIndex) - beforePointIndex), + m_segmentFlags, + (getPathStart(pathIndex) + beforePointIndex), true, 1); + m_segmentParamIndex.writeRange((getPathStart(pathIndex) + + beforePointIndex + srcPointCount), (oldPointCount + - getPathStart(pathIndex) - beforePointIndex), + m_segmentParamIndex, + (getPathStart(pathIndex) + beforePointIndex), true, 1); + for (int i = getPathStart(pathIndex) + beforePointIndex, n = getPathStart(pathIndex) + + beforePointIndex + srcPointCount; i < n; i++) { + m_segmentFlags.write(i, (byte) SegmentFlags.enumLineSeg); + m_segmentParamIndex.write(i, -1); + } + } + + if (src.hasNonLinearSegments(srcPathIndex)) { + // TODO: implement me. For example as a while loop over all curves. + // Replace, calling ReplaceSegment + throw new GeometryException("internal error"); + } + + for (int ipath = pathIndex + 1, npaths = getPathCount(); ipath <= npaths; ipath++) { + int num = m_paths.read(ipath); + m_paths.write(ipath, num + srcPointCount); + } + } + + public void insertPoints(int pathIndex, int beforePointIndex, + Point2D[] src, int srcPointIndexFrom, int srcPointCount, + boolean bForward) { + if (pathIndex < 0) + pathIndex = getPathCount(); + + if (pathIndex > getPathCount() + || beforePointIndex > getPathSize(pathIndex) + || srcPointIndexFrom < 0 || srcPointCount > src.length) + throw new GeometryException("index out of bounds"); + + if (srcPointCount == 0) + return; + + if (pathIndex == getPathCount())// adding a new path. + { + m_paths.add(m_pointCount); + + if (!m_bPolygon) + m_pathFlags.add((byte) 0); + else + m_pathFlags.add((byte) PathFlags.enumClosed); + } + + if (beforePointIndex < 0) + beforePointIndex = getPathSize(pathIndex); + + _verifyAllStreams(); + int oldPointCount = m_pointCount; + _resizeImpl(m_pointCount + srcPointCount); + _verifyAllStreams(); + for (int iattr = 0, nattr = m_description.getAttributeCount(); iattr < nattr; iattr++) { + int semantics = m_description._getSemanticsImpl(iattr); + int comp = VertexDescription.getComponentCount(semantics); + // copy range to make place for new vertices + m_vertexAttributes[iattr] + .writeRange( + comp + * (getPathStart(pathIndex) + + beforePointIndex + srcPointCount), + (oldPointCount - getPathStart(pathIndex) - beforePointIndex) + * comp, + m_vertexAttributes[iattr], + comp * (getPathStart(pathIndex) + beforePointIndex), + true, comp); + + if (iattr == 0) { + // add vertices to the given stream + ((AttributeStreamOfDbl) (AttributeStreamBase) m_vertexAttributes[iattr]) + .writeRange(comp + * (getPathStart(pathIndex) + beforePointIndex), + srcPointCount, src, srcPointIndexFrom, bForward); + } else { + double v = VertexDescription.getDefaultValue(semantics); + m_vertexAttributes[iattr].setRange(v, + (getPathStart(pathIndex) + beforePointIndex) * comp, + srcPointCount * comp); + } + } + + if (hasNonLinearSegments()) { + m_segmentFlags.writeRange((getPathStart(pathIndex) + + beforePointIndex + srcPointCount), (oldPointCount + - getPathStart(pathIndex) - beforePointIndex), + m_segmentFlags, + (getPathStart(pathIndex) + beforePointIndex), true, 1); + m_segmentParamIndex.writeRange((getPathStart(pathIndex) + + beforePointIndex + srcPointCount), (oldPointCount + - getPathStart(pathIndex) - beforePointIndex), + m_segmentParamIndex, + (getPathStart(pathIndex) + beforePointIndex), true, 1); + m_segmentFlags.setRange((byte) SegmentFlags.enumLineSeg, + getPathStart(pathIndex) + beforePointIndex, srcPointCount); + m_segmentParamIndex.setRange(-1, getPathStart(pathIndex) + + beforePointIndex, srcPointCount); + } + + for (int ipath = pathIndex + 1, npaths = getPathCount(); ipath <= npaths; ipath++) { + m_paths.write(ipath, m_paths.read(ipath) + srcPointCount); + } + } + + public void insertPoint(int pathIndex, int beforePointIndex, Point2D pt) { + int pathCount = getPathCount(); + + if (pathIndex < 0) + pathIndex = getPathCount(); + + if (pathIndex >= pathCount || beforePointIndex > getPathSize(pathIndex)) + throw new GeometryException("index out of bounds"); + + if (pathIndex == getPathCount())// adding a new path. + { + m_paths.add(m_pointCount); + + if (!m_bPolygon) + m_pathFlags.add((byte) 0); + else + m_pathFlags.add((byte) PathFlags.enumClosed); + } + + if (beforePointIndex < 0) + beforePointIndex = getPathSize(pathIndex); + + int oldPointCount = m_pointCount; + _resizeImpl(m_pointCount + 1); + _verifyAllStreams(); + + int pathStart = getPathStart(pathIndex); + + ((AttributeStreamOfDbl) (AttributeStreamBase) m_vertexAttributes[0]) + .insert(2 * (pathStart + beforePointIndex), pt, + 2 * oldPointCount); + + for (int iattr = 1, nattr = m_description.getAttributeCount(); iattr < nattr; iattr++) { + int semantics = m_description._getSemanticsImpl(iattr); + int comp = VertexDescription.getComponentCount(semantics); + + // Need to make room for the attribute, so we copy a default value + // in + double v = VertexDescription.getDefaultValue(semantics); + m_vertexAttributes[iattr].insertRange(comp + * (pathStart + beforePointIndex), v, comp, comp + * oldPointCount); + } + + for (int ipath = pathIndex + 1, npaths = pathCount; ipath <= npaths; ipath++) { + m_paths.write(ipath, m_paths.read(ipath) + 1); + } + } + + public void insertPoint(int pathIndex, int beforePointIndex, Point pt) { + int pathCount = getPathCount(); + + if (pathIndex < 0) + pathIndex = getPathCount(); + + if (pathIndex >= pathCount || beforePointIndex > getPathSize(pathIndex)) + throw new GeometryException("index out of bounds"); + + if (pathIndex == getPathCount())// adding a new path. + { + m_paths.add(m_pointCount); + + if (!m_bPolygon) + m_pathFlags.add((byte) 0); + else + m_pathFlags.add((byte) PathFlags.enumClosed); + } + + if (beforePointIndex < 0) + beforePointIndex = getPathSize(pathIndex); + + mergeVertexDescription(pt.getDescription()); + int oldPointCount = m_pointCount; + _resizeImpl(m_pointCount + 1); + _verifyAllStreams(); + + int pathStart = getPathStart(pathIndex); + + for (int iattr = 0, nattr = m_description.getAttributeCount(); iattr < nattr; iattr++) { + int semantics = m_description._getSemanticsImpl(iattr); + int comp = VertexDescription.getComponentCount(semantics); + + if (pt.hasAttribute(semantics)) { + m_vertexAttributes[iattr].insertAttributes(comp + * (pathStart + beforePointIndex), pt, semantics, comp + * oldPointCount); + } else { + // Need to make room for the attribute, so we copy a default + // value in + double v = VertexDescription.getDefaultValue(semantics); + m_vertexAttributes[iattr].insertRange(comp + * (pathStart + beforePointIndex), v, comp, comp + * oldPointCount); + } + } + + for (int ipath = pathIndex + 1, npaths = pathCount; ipath <= npaths; ipath++) { + m_paths.write(ipath, m_paths.read(ipath) + 1); + } + + notifyModified(DirtyFlags.DirtyCoordinates); + } + + public void removePoint(int pathIndex, int pointIndex) { + int pathCount = getPathCount(); + + if (pathIndex < 0) + pathIndex = pathCount - 1; + + if (pathIndex >= pathCount || pointIndex >= getPathSize(pathIndex)) + throw new GeometryException("index out of bounds"); + + _verifyAllStreams(); + + int pathStart = getPathStart(pathIndex); + + if (pointIndex < 0) + pointIndex = getPathSize(pathIndex) - 1; + + int absoluteIndex = pathStart + pointIndex; + + // Remove the attribute values for the path + for (int iattr = 0, nattr = m_description.getAttributeCount(); iattr < nattr; iattr++) { + if (m_vertexAttributes[iattr] != null) { + int semantics = m_description._getSemanticsImpl(iattr); + int comp = VertexDescription.getComponentCount(semantics); + m_vertexAttributes[iattr].eraseRange(comp * absoluteIndex, + comp, comp * m_pointCount); + } + } + + for (int ipath = pathCount; ipath >= pathIndex + 1; ipath--) { + int iend = m_paths.read(ipath); + m_paths.write(ipath, iend - 1); + } + + m_pointCount--; + m_reservedPointCount--; + notifyModified(DirtyFlags.DirtyCoordinates); + } + + public double calculatePathLength2D(int pathIndex) /* const */ + { + SegmentIteratorImpl segIter = querySegmentIteratorAtVertex(getPathStart(pathIndex)); + + MathUtils.KahanSummator len = new MathUtils.KahanSummator(0); + while (segIter.hasNextSegment()) { + len.add(segIter.nextSegment().calculateLength2D()); + } + + return len.getResult(); + } + + double calculateSubLength2D(int from_path_index, int from_point_index, + int to_path_index, int to_point_index) { + int absolute_from_index = getPathStart(from_path_index) + + from_point_index; + int absolute_to_index = getPathStart(to_path_index) + to_point_index; + + if (absolute_to_index < absolute_from_index || absolute_from_index < 0 + || absolute_to_index > getPointCount() - 1) + throw new IllegalArgumentException(); + + SegmentIteratorImpl seg_iter = querySegmentIterator(); + + double sub_length = 0.0; + + seg_iter.resetToVertex(absolute_from_index); + + do { + while (seg_iter.hasNextSegment()) { + Segment segment = seg_iter.nextSegment(); + + if (seg_iter.getStartPointIndex() == absolute_to_index) + break; + + double segment_length = segment.calculateLength2D(); + sub_length += segment_length; + } + + if (seg_iter.getStartPointIndex() == absolute_to_index) + break; + + } while (seg_iter.nextPath()); + + return sub_length; + } + + double calculateSubLength2D(int path_index, int from_point_index, + int to_point_index) { + int absolute_from_index = getPathStart(path_index) + from_point_index; + int absolute_to_index = getPathStart(path_index) + to_point_index; + + if (absolute_from_index < 0 || absolute_to_index > getPointCount() - 1) + throw new IllegalArgumentException(); + + SegmentIteratorImpl seg_iter = querySegmentIterator(); + + if (absolute_from_index > absolute_to_index) { + if (!isClosedPath(path_index)) + throw new IllegalArgumentException( + "cannot iterate across an open path"); + + seg_iter.setCirculator(true); + } + + double prev_length = 0.0; + double sub_length = 0.0; + + seg_iter.resetToVertex(absolute_from_index); + + do { + assert (seg_iter.hasNextSegment()); + sub_length += prev_length; + Segment segment = seg_iter.nextSegment(); + prev_length = segment.calculateLength2D(); + + } while (seg_iter.getStartPointIndex() != absolute_to_index); + + return sub_length; + } + + Geometry getBoundary() { + return Boundary.calculate(this, null); + } + + // TODO: Add code fore interpolation type (none and angular) + void interpolateAttributes(int from_path_index, int from_point_index, + int to_path_index, int to_point_index) { + for (int ipath = from_path_index; ipath < to_path_index - 1; ipath++) { + if (isClosedPath(ipath)) + throw new IllegalArgumentException( + "cannot interpolate across closed paths"); + } + + int nattr = m_description.getAttributeCount(); + + if (nattr == 1) + return; // only has position + + double sub_length = calculateSubLength2D(from_path_index, + from_point_index, to_path_index, to_point_index); + + if (sub_length == 0.0) + return; + + for (int iattr = 1; iattr < nattr; iattr++) { + int semantics = m_description.getSemantics(iattr); + + int interpolation = VertexDescription.getInterpolation(semantics); + if (interpolation == VertexDescription.Interpolation.ANGULAR) + continue; + + int components = VertexDescription.getComponentCount(semantics); + + for (int ordinate = 0; ordinate < components; ordinate++) + interpolateAttributes_(semantics, from_path_index, + from_point_index, to_path_index, to_point_index, + sub_length, ordinate); + } + } + + // TODO: Add code for interpolation type (none and angular) + void interpolateAttributesForSemantics(int semantics, int from_path_index, + int from_point_index, int to_path_index, int to_point_index) { + if (semantics == VertexDescription.Semantics.POSITION) + return; + + if (!hasAttribute(semantics)) + throw new IllegalArgumentException( + "does not have the given attribute"); + + int interpolation = VertexDescription.getInterpolation(semantics); + if (interpolation == VertexDescription.Interpolation.ANGULAR) + throw new IllegalArgumentException( + "not implemented for the given semantics"); + + for (int ipath = from_path_index; ipath < to_path_index - 1; ipath++) { + if (isClosedPath(ipath)) + throw new IllegalArgumentException( + "cannot interpolate across closed paths"); + } + + double sub_length = calculateSubLength2D(from_path_index, + from_point_index, to_path_index, to_point_index); + + if (sub_length == 0.0) + return; + + int components = VertexDescription.getComponentCount(semantics); + + for (int ordinate = 0; ordinate < components; ordinate++) + interpolateAttributes_(semantics, from_path_index, + from_point_index, to_path_index, to_point_index, + sub_length, ordinate); + } + + void interpolateAttributes(int path_index, int from_point_index, + int to_point_index) { + int nattr = m_description.getAttributeCount(); + + if (nattr == 1) + return; // only has position + + double sub_length = calculateSubLength2D(path_index, from_point_index, + to_point_index); + + if (sub_length == 0.0) + return; + + for (int iattr = 1; iattr < nattr; iattr++) { + int semantics = m_description.getSemantics(iattr); + + int interpolation = VertexDescription.getInterpolation(semantics); + if (interpolation == VertexDescription.Interpolation.ANGULAR) + continue; + + int components = VertexDescription.getComponentCount(semantics); + + for (int ordinate = 0; ordinate < components; ordinate++) + interpolateAttributes_(semantics, path_index, from_point_index, + to_point_index, sub_length, ordinate); + } + } + + void interpolateAttributesForSemantics(int semantics, int path_index, + int from_point_index, int to_point_index) { + if (semantics == VertexDescription.Semantics.POSITION) + return; + + if (!hasAttribute(semantics)) + throw new IllegalArgumentException( + "does not have the given attribute"); + + int interpolation = VertexDescription.getInterpolation(semantics); + if (interpolation == VertexDescription.Interpolation.ANGULAR) + throw new IllegalArgumentException( + "not implemented for the given semantics"); + + double sub_length = calculateSubLength2D(path_index, from_point_index, + to_point_index); + + if (sub_length == 0.0) + return; + + int components = VertexDescription.getComponentCount(semantics); + + for (int ordinate = 0; ordinate < components; ordinate++) + interpolateAttributes_(semantics, path_index, from_point_index, + to_point_index, sub_length, ordinate); + } + + // TODO: Add code fore interpolation type (none and angular) + void interpolateAttributes_(int semantics, int from_path_index, + int from_point_index, int to_path_index, int to_point_index, + double sub_length, int ordinate) { + SegmentIteratorImpl seg_iter = querySegmentIterator(); + + int absolute_from_index = getPathStart(from_path_index) + + from_point_index; + int absolute_to_index = getPathStart(to_path_index) + to_point_index; + + double from_attribute = getAttributeAsDbl(semantics, + absolute_from_index, ordinate); + double to_attribute = getAttributeAsDbl(semantics, absolute_to_index, + ordinate); + double interpolated_attribute = from_attribute; + double cumulative_length = 0.0; + + seg_iter.resetToVertex(absolute_from_index); + + do { + if (seg_iter.hasNextSegment()) { + seg_iter.nextSegment(); + + if (seg_iter.getStartPointIndex() == absolute_to_index) + return; + + setAttribute(semantics, seg_iter.getStartPointIndex(), + ordinate, interpolated_attribute); + + seg_iter.previousSegment(); + + do { + Segment segment = seg_iter.nextSegment(); + + if (seg_iter.getEndPointIndex() == absolute_to_index) + return; + + double segment_length = segment.calculateLength2D(); + cumulative_length += segment_length; + double t = cumulative_length / sub_length; + interpolated_attribute = (1.0 - t) * from_attribute + t + * to_attribute; + + if (!seg_iter.isClosingSegment()) + setAttribute(semantics, seg_iter.getEndPointIndex(), + ordinate, interpolated_attribute); + + } while (seg_iter.hasNextSegment()); + } + + } while (seg_iter.nextPath()); + } + + void interpolateAttributes_(int semantics, int path_index, + int from_point_index, int to_point_index, double sub_length, + int ordinate) { + assert (m_bPolygon); + SegmentIteratorImpl seg_iter = querySegmentIterator(); + + int absolute_from_index = getPathStart(path_index) + from_point_index; + int absolute_to_index = getPathStart(path_index) + to_point_index; + + if (absolute_to_index == absolute_from_index) + return; + + double from_attribute = getAttributeAsDbl(semantics, + absolute_from_index, ordinate); + double to_attribute = getAttributeAsDbl(semantics, absolute_to_index, + ordinate); + double cumulative_length = 0.0; + + seg_iter.resetToVertex(absolute_from_index); + seg_iter.setCirculator(true); + + double prev_interpolated_attribute = from_attribute; + + do { + Segment segment = seg_iter.nextSegment(); + setAttribute(semantics, seg_iter.getStartPointIndex(), ordinate, + prev_interpolated_attribute); + + double segment_length = segment.calculateLength2D(); + cumulative_length += segment_length; + double t = cumulative_length / sub_length; + prev_interpolated_attribute = (1.0 - t) * from_attribute + t + * to_attribute; + + } while (seg_iter.getEndPointIndex() != absolute_to_index); + } + + @Override + public void setEmpty() { + m_curveParamwritePoint = 0; + m_bPathStarted = false; + m_paths = null; + m_pathFlags = null; + m_segmentParamIndex = null; + m_segmentFlags = null; + m_segmentParams = null; + _setEmptyImpl(); + } + + @Override + public void applyTransformation(Transformation2D transform) { + applyTransformation(transform, -1); + } + + public void applyTransformation(Transformation2D transform, int pathIndex) { + if (isEmpty()) + return; + + if (transform.isIdentity()) + return; + + _verifyAllStreams(); + AttributeStreamOfDbl points = (AttributeStreamOfDbl) m_vertexAttributes[0]; + Point2D ptStart = new Point2D(); + Point2D ptControl = new Point2D(); + + boolean bHasNonLinear; + int fistIdx; + int lastIdx; + if (pathIndex < 0) { + bHasNonLinear = hasNonLinearSegments(); + fistIdx = 0; + lastIdx = m_pointCount; + } else { + bHasNonLinear = hasNonLinearSegments(pathIndex); + fistIdx = getPathStart(pathIndex); + lastIdx = getPathEnd(pathIndex); + } + + for (int ipoint = fistIdx; ipoint < lastIdx; ipoint++) { + ptStart.x = points.read(ipoint * 2); + ptStart.y = points.read(ipoint * 2 + 1); + + if (bHasNonLinear) { + int segIndex = m_segmentParamIndex.read(ipoint); + if (segIndex >= 0) { + int segmentType = (int) m_segmentFlags.read(ipoint); + int type = segmentType & SegmentFlags.enumSegmentMask; + switch (type) { + case SegmentFlags.enumBezierSeg: { + ptControl.x = m_segmentParams.read(segIndex); + ptControl.y = m_segmentParams.read(segIndex + 1); + // FIXME rohit has transform returning the object rather + // than transforming the input + transform.transform(ptControl, ptControl); + m_segmentParams.write(segIndex, ptControl.x); + m_segmentParams.write(segIndex + 1, ptControl.y); + + ptControl.x = m_segmentParams.read(segIndex + 3); + ptControl.y = m_segmentParams.read(segIndex + 4); + // FIXME rohit has transform returning the object rather + // than transforming the input + transform.transform(ptControl, ptControl); + m_segmentParams.write(segIndex + 3, ptControl.x); + m_segmentParams.write(segIndex + 4, ptControl.y); + } + break; + case SegmentFlags.enumArcSeg: + throw new GeometryException("internal error");// FIXME + + } + } + } + // FIXME rohit has transform returning the object rather than + // transforming the input + transform.transform(ptStart, ptStart); + points.write(ipoint * 2, ptStart.x); + points.write(ipoint * 2 + 1, ptStart.y); + } + + notifyModified(DirtyFlags.DirtyCoordinates); + // REFACTOR: reset the exact envelope only and transform the loose + // envelope + } + + @Override + public void applyTransformation(Transformation3D transform) { + if (isEmpty()) + return; + + addAttribute(VertexDescription.Semantics.Z); + _verifyAllStreams(); + AttributeStreamOfDbl points = (AttributeStreamOfDbl) m_vertexAttributes[0]; + AttributeStreamOfDbl zs = (AttributeStreamOfDbl) m_vertexAttributes[1]; + Point3D ptStart = new Point3D(); + Point3D ptControl = new Point3D(); + boolean bHasNonLinear = hasNonLinearSegments(); + for (int ipoint = 0; ipoint < m_pointCount; ipoint++) { + ptStart.x = points.read(ipoint * 2); + ptStart.y = points.read(ipoint * 2 + 1); + ptStart.z = zs.read(ipoint); + + if (bHasNonLinear) { + int segIndex = m_segmentParamIndex.read(ipoint); + if (segIndex >= 0) { + int segmentType = (int) m_segmentFlags.read(ipoint); + int type = segmentType & (int) SegmentFlags.enumSegmentMask; + switch (type) { + case SegmentFlags.enumBezierSeg: { + ptControl.x = m_segmentParams.read(segIndex); + ptControl.y = m_segmentParams.read(segIndex + 1); + ptControl.z = m_segmentParams.read(segIndex + 2); + ptControl = transform.transform(ptControl); + m_segmentParams.write(segIndex, ptControl.x); + m_segmentParams.write(segIndex + 1, ptControl.y); + m_segmentParams.write(segIndex + 1, ptControl.z); + + ptControl.x = m_segmentParams.read(segIndex + 3); + ptControl.y = m_segmentParams.read(segIndex + 4); + ptControl.z = m_segmentParams.read(segIndex + 5); + ptControl = transform.transform(ptControl); + m_segmentParams.write(segIndex + 3, ptControl.x); + m_segmentParams.write(segIndex + 4, ptControl.y); + m_segmentParams.write(segIndex + 5, ptControl.z); + } + break; + case SegmentFlags.enumArcSeg: + throw new GeometryException("internal error");// FIXME + + } + } + } + ptStart = transform.transform(ptStart); + points.write(ipoint * 2, ptStart.x); + points.write(ipoint * 2 + 1, ptStart.y); + zs.write(ipoint, ptStart.z); + } + + // REFACTOR: reset the exact envelope only and transform the loose + // envelope + + notifyModified(DirtyFlags.DirtyCoordinates); + } + + @Override + protected void _verifyStreamsImpl() { + if (m_paths == null) { + m_paths = (AttributeStreamOfInt32) AttributeStreamBase + .createIndexStream(1, 0); + m_pathFlags = (AttributeStreamOfInt8) AttributeStreamBase + .createByteStream(1, (byte) 0); + } + + if (m_segmentFlags != null) { + m_segmentFlags.resize(m_reservedPointCount, + (byte) SegmentFlags.enumLineSeg); + m_segmentParamIndex.resize(m_reservedPointCount, -1); + } + } + + @Override + void _copyToImpl(MultiVertexGeometryImpl dst) { + MultiPathImpl dstPoly = (MultiPathImpl) dst; + dstPoly.m_bPathStarted = false; + dstPoly.m_curveParamwritePoint = m_curveParamwritePoint; + + // FIXME there is no cloning in here. Is this necessary? + if (m_paths != null) + dstPoly.m_paths = new AttributeStreamOfInt32(m_paths); + else + dstPoly.m_paths = null; + + if (m_pathFlags != null) + dstPoly.m_pathFlags = new AttributeStreamOfInt8(m_pathFlags); + else + dstPoly.m_pathFlags = null; + + if (m_segmentParamIndex != null) + dstPoly.m_segmentParamIndex = new AttributeStreamOfInt32( + m_segmentParamIndex); + else + dstPoly.m_segmentParamIndex = null; + + if (m_segmentFlags != null) + dstPoly.m_segmentFlags = new AttributeStreamOfInt8(m_segmentFlags); + else + dstPoly.m_segmentFlags = null; + + if (m_segmentParams != null) + dstPoly.m_segmentParams = new AttributeStreamOfDbl(m_segmentParams); + else + dstPoly.m_segmentParams = null; + + dstPoly.m_cachedLength2D = m_cachedLength2D; + dstPoly.m_cachedArea2D = m_cachedArea2D; + + if (!_hasDirtyFlag(DirtyFlags.DirtyRingAreas2D)) { + dstPoly.m_cachedRingAreas2D = (AttributeStreamOfDbl) m_cachedRingAreas2D; + } else + dstPoly.m_cachedRingAreas2D = null; + + } + + @Override + public double calculateLength2D() { + if (!_hasDirtyFlag(DirtyFlags.DirtyLength2D)) { + return m_cachedLength2D; + } + + SegmentIteratorImpl segIter = querySegmentIterator(); + MathUtils.KahanSummator len = new MathUtils.KahanSummator(0); + while (segIter.nextPath()) { + while (segIter.hasNextSegment()) { + len.add(segIter.nextSegment().calculateLength2D()); + } + } + + m_cachedLength2D = len.getResult(); + _setDirtyFlag(DirtyFlags.DirtyLength2D, false); + + return len.getResult(); + } + + // FIXME figure out hascode + // int getHashCode() + // { + // int hashCode = MultiVertexGeometryImpl.getHashCode(); + // + // if (!isEmptyImpl()) + // { + // int pathCount = getPathCount(); + // + // if (m_paths != null) + // m_paths.calculateHashImpl(hashCode, 0, pathCount + 1); + // + // if (m_pathFlags != null) + // m_pathFlags.calculateHashImpl(hashCode, 0, pathCount); + // } + // + // return hashCode; + // } + + @Override + public boolean equals(Object other) { + if (other == this) + return true; + + if (!(other instanceof MultiPathImpl)) + return false; + + if (!super.equals(other)) + return false; + + MultiPathImpl otherMultiPath = (MultiPathImpl) other; + + int pathCount = getPathCount(); + int pathCountOther = otherMultiPath.getPathCount(); + + if (pathCount != pathCountOther) + return false; + + if (m_paths != null + && !m_paths.equals(otherMultiPath.m_paths, 0, pathCount + 1)) + return false; + + if (m_pathFlags != null + && !m_pathFlags + .equals(otherMultiPath.m_pathFlags, 0, pathCount)) + return false; + + return super.equals(other); + } + + /** + * Returns a SegmentIterator that set to a specific vertex of the + * MultiPathImpl. The call to NextSegment will return the segment that + * starts at the vertex. Call to PreviousSegment will return the segment + * that starts at the previous vertex. + */ + public SegmentIteratorImpl querySegmentIteratorAtVertex(int startVertexIndex) { + if (startVertexIndex < 0 || startVertexIndex >= getPointCount()) + // FIXME + throw new IndexOutOfBoundsException(); + + SegmentIteratorImpl iter = new SegmentIteratorImpl(this, + startVertexIndex); + return iter; + } + + // void QuerySegmentIterator(int fromVertex, SegmentIterator iterator); + public SegmentIteratorImpl querySegmentIterator() { + return new SegmentIteratorImpl(this); + } + + @Override + public void _updateXYImpl(boolean bExact) { + super._updateXYImpl(bExact); + boolean bHasCurves = hasNonLinearSegments(); + if (bHasCurves) { + SegmentIteratorImpl segIter = querySegmentIterator(); + while (segIter.nextPath()) { + while (segIter.hasNextSegment()) { + Segment curve = segIter.nextCurve(); + if (curve != null) { + Envelope2D env2D = new Envelope2D(); + curve.queryEnvelope2D(env2D); + m_envelope.merge(env2D); + } else + break; + } + } + } + } + + @Override + void calculateEnvelope2D(Envelope2D env, boolean bExact) { + super.calculateEnvelope2D(env, bExact); + boolean bHasCurves = hasNonLinearSegments(); + if (bHasCurves) { + SegmentIteratorImpl segIter = querySegmentIterator(); + while (segIter.nextPath()) { + while (segIter.hasNextSegment()) { + Segment curve = segIter.nextCurve(); + if (curve != null) { + Envelope2D env2D = new Envelope2D(); + curve.queryEnvelope2D(env2D); + env.merge(env2D); + } else + break; + } + } + } + } + + @Override + public void _notifyModifiedAllImpl() { + if (m_paths == null || m_paths.size() == 0)// if (m_paths == null || + // !m_paths.size()) + m_pointCount = 0; + else + m_pointCount = m_paths.read(m_paths.size() - 1); + } + + @Override + public double calculateArea2D() { + if (!m_bPolygon) + return 0.0; + + _updateRingAreas2D(); + + return m_cachedArea2D; + } + + /** + * Returns True if the ring is an exterior ring. Valid only for simple + * polygons. + */ + public boolean isExteriorRing(int ringIndex) { + if (!m_bPolygon) + return false; + + if (!_hasDirtyFlag(DirtyFlags.DirtyOGCFlags)) + return (m_pathFlags.read(ringIndex) & (byte) PathFlags.enumOGCStartPolygon) != 0; + + _updateRingAreas2D(); + return m_cachedRingAreas2D.read(ringIndex) > 0; + // Should we make a function called _UpdateHasNonLinearSegmentsFlags and + // call it here? + } + + public double calculateRingArea2D(int pathIndex) { + if (!m_bPolygon) + return 0.0; + + _updateRingAreas2D(); + + return m_cachedRingAreas2D.read(pathIndex); + } + + public void _updateRingAreas2D() { + if (_hasDirtyFlag(DirtyFlags.DirtyRingAreas2D)) { + int pathCount = getPathCount(); + + if (m_cachedRingAreas2D == null) + m_cachedRingAreas2D = new AttributeStreamOfDbl(pathCount); + else if (m_cachedRingAreas2D.size() != pathCount) + m_cachedRingAreas2D.resize(pathCount); + + MathUtils.KahanSummator totalArea = new MathUtils.KahanSummator(0); + MathUtils.KahanSummator pathArea = new MathUtils.KahanSummator(0); + Point2D pt = new Point2D(); + int ipath = 0; + SegmentIteratorImpl segIter = querySegmentIterator(); + while (segIter.nextPath()) { + pathArea.reset(); + getXY(getPathStart(segIter.getPathIndex()), pt);// get the area + // calculation + // origin to be + // the origin of + // the ring. + while (segIter.hasNextSegment()) { + pathArea.add(segIter.nextSegment()._calculateArea2DHelper( + pt.x, pt.y)); + } + + totalArea.add(pathArea.getResult()); + + int i = ipath++; + m_cachedRingAreas2D.write(i, pathArea.getResult()); + } + + m_cachedArea2D = totalArea.getResult(); + _setDirtyFlag(DirtyFlags.DirtyRingAreas2D, false); + } + } + + int getOGCPolygonCount() { + if (!m_bPolygon) + return 0; + + _updateOGCFlags(); + + int polygonCount = 0; + int partCount = getPathCount(); + for (int ipart = 0; ipart < partCount; ipart++) { + if (((int) m_pathFlags.read(ipart) & (int) PathFlags.enumOGCStartPolygon) != 0) + polygonCount++; + } + + return polygonCount; + } + + protected void _updateOGCFlags() { + if (_hasDirtyFlag(DirtyFlags.DirtyOGCFlags)) { + _updateRingAreas2D(); + + int pathCount = getPathCount(); + if (m_pathFlags == null || m_pathFlags.size() < pathCount) + m_pathFlags = (AttributeStreamOfInt8) AttributeStreamBase + .createByteStream(pathCount + 1); + + for (int ipath = 0; ipath < pathCount; ipath++) { + double area = m_cachedRingAreas2D.read(ipath); + if (area > 0.0) + m_pathFlags.setBits(ipath, + (byte) PathFlags.enumOGCStartPolygon); + else + m_pathFlags.clearBits(ipath, + (byte) PathFlags.enumOGCStartPolygon); + } + _setDirtyFlag(DirtyFlags.DirtyOGCFlags, false); + } + } + + public int getPathIndexFromPointIndex(int pointIndex) { + int positionHint = m_currentPathIndex;// in case of multithreading + // thiswould simply produce an + // invalid value + int pathCount = getPathCount(); + + // Try using the hint position first to get the path index. + if (positionHint >= 0 && positionHint < pathCount) { + if (pointIndex < getPathEnd(positionHint)) { + if (pointIndex >= getPathStart(positionHint)) + return positionHint; + positionHint--; + } else { + positionHint++; + } + + if (positionHint >= 0 && positionHint < pathCount) { + if (pointIndex >= getPathStart(positionHint) + && pointIndex < getPathEnd(positionHint)) { + m_currentPathIndex = positionHint; + return positionHint; + } + } + } + + if (pathCount < 5) {// TODO: time the performance to choose when to use + // linear search. + for (int i = 0; i < pathCount; i++) { + if (pointIndex < getPathEnd(i)) { + m_currentPathIndex = i; + return i; + } + } + throw new GeometryException("corrupted geometry"); + } + + // Do binary search: + int minPathIndex = 0; + int maxPathIndex = pathCount - 1; + while (maxPathIndex > minPathIndex) { + int mid = minPathIndex + ((maxPathIndex - minPathIndex) >> 1); + int pathStart = getPathStart(mid); + if (pointIndex < pathStart) + maxPathIndex = mid - 1; + else { + int pathEnd = getPathEnd(mid); + if (pointIndex >= pathEnd) + minPathIndex = mid + 1; + else { + m_currentPathIndex = mid; + return mid; + } + } + } + + m_currentPathIndex = minPathIndex; + return minPathIndex; + } + + int getHighestPointIndex(int path_index) { + assert (path_index >= 0 && path_index < getPathCount()); + + AttributeStreamOfDbl position = (AttributeStreamOfDbl) (getAttributeStreamRef(VertexDescription.Semantics.POSITION)); + AttributeStreamOfInt32 paths = (AttributeStreamOfInt32) (getPathStreamRef()); + + int path_end = getPathEnd(path_index); + int path_start = getPathStart(path_index); + int max_index = -1; + Point2D max_point = new Point2D(), pt = new Point2D(); + max_point.y = NumberUtils.negativeInf(); + max_point.x = NumberUtils.negativeInf(); + + for (int i = path_start + 0; i < path_end; i++) { + position.read(2 * i, pt); + if (max_point.compare(pt) == -1) { + max_index = i; + max_point.setCoords(pt); + } + } + + return max_index; + } + + /** + * Returns total segment count in the MultiPathImpl. + */ + public int getSegmentCount() { + int segCount = getPointCount(); + if (!m_bPolygon) { + segCount -= getPathCount(); + for (int i = 0, n = getPathCount(); i < n; i++) + if (isClosedPath(i)) + segCount++; + } + + return segCount; + } + + public int getSegmentCount(int path_index) { + int segCount = getPathSize(path_index); + if (!isClosedPath(path_index)) + segCount--; + return segCount; + } + + // HEADER defintions + @Override + public Geometry createInstance() { + return new MultiPathImpl(m_bPolygon, getDescription()); + } + + @Override + public int getDimension() { + return m_bPolygon ? 2 : 1; + } + + @Override + public Geometry.Type getType() { + return m_bPolygon ? Type.Polygon : Type.Polyline; + } + + /** + * Returns True if the class is envelope. THis is not an exact method. Only + * addEnvelope makes this true. + */ + public boolean isEnvelope() { + return !_hasDirtyFlag(DirtyFlags.DirtyIsEnvelope); + } + + /** + * Returns a reference to the AttributeStream of MultiPathImpl parts + * (Paths). + * + * For the non empty MultiPathImpl, that stream contains start points of the + * MultiPathImpl curves. In addition, the last element is the total point + * count. The number of vertices in a given part is parts[i + 1] - parts[i]. + */ + public AttributeStreamOfInt32 getPathStreamRef() { + throwIfEmpty(); + return m_paths; + } + + /** + * sets a reference to an AttributeStream of MultiPathImpl paths (Paths). + */ + public void setPathStreamRef(AttributeStreamOfInt32 paths) { + m_paths = paths; + notifyModified(DirtyFlags.DirtyAll); + } + + /** + * Returns a reference to the AttributeStream of Segment flags (SegmentFlags + * flags). Can be NULL when no non-linear segments are present. + * + * Segment flags indicate what kind of segment originates (starts) on the + * given point. The last vertices of open Path parts has enumNone flag. + */ + public AttributeStreamOfInt8 getSegmentFlagsStreamRef() { + throwIfEmpty(); + return m_segmentFlags; + } + + /** + * Returns a reference to the AttributeStream of Path flags (PathFlags + * flags). + * + * Each start point of a path has a flag set to indicate if the Path is open + * or closed. + */ + public AttributeStreamOfInt8 getPathFlagsStreamRef() { + throwIfEmpty(); + return m_pathFlags; + } + + /** + * sets a reference to an AttributeStream of Path flags (PathFlags flags). + */ + public void setPathFlagsStreamRef(AttributeStreamOfInt8 pathFlags) { + m_pathFlags = pathFlags; + notifyModified(DirtyFlags.DirtyAll); + } + + public AttributeStreamOfInt32 getSegmentIndexStreamRef() { + throwIfEmpty(); + return m_segmentParamIndex; + } + + public AttributeStreamOfDbl getSegmentDataStreamRef() { + throwIfEmpty(); + return m_segmentParams; + } + + public int getPathCount() { + return (m_paths != null) ? m_paths.size() - 1 : 0; + } + + public int getPathEnd(int partIndex) { + return m_paths.read(partIndex + 1); + } + + public int getPathSize(int partIndex) { + return m_paths.read(partIndex + 1) - m_paths.read(partIndex); + } + + public int getPathStart(int partIndex) { + return m_paths.read(partIndex); + } + + @Override + public Object _getImpl() { + return this; + } + + public void setDirtyOGCFlags(boolean bYesNo) { + _setDirtyFlag(DirtyFlags.DirtyOGCFlags, bYesNo); + } + + public boolean hasDirtyOGCStartFlags() { + return _hasDirtyFlag(DirtyFlags.DirtyOGCFlags); + } + + public void setDirtyRingAreas2D(boolean bYesNo) { + _setDirtyFlag(DirtyFlags.DirtyRingAreas2D, bYesNo); + } + + public boolean hasDirtyRingAreas2D() { + return _hasDirtyFlag(DirtyFlags.DirtyRingAreas2D); + } + + public void setRingAreasStreamRef(AttributeStreamOfDbl ringAreas) { + m_cachedRingAreas2D = ringAreas; + _setDirtyFlag(DirtyFlags.DirtyRingAreas2D, false); + } + + // HEADER defintions + + // // TODO check this against current implementation in native + // public void notifyModified(int flags) + // { + // if(flags == DirtyFlags.DirtyAll) + // { + // m_reservedPointCount = -1; + // _notifyModifiedAllImpl(); + // } + // m_flagsMask |= flags; + // _clearAccelerators(); + // + // + // // ROHIT's implementation + // // if (m_paths == null || 0 == m_paths.size()) + // // m_pointCount = 0; + // // else + // // m_pointCount = m_paths.read(m_paths.size() - 1); + // // + // // super.notifyModified(flags); + // } + + @Override + public boolean _buildRasterizedGeometryAccelerator(double toleranceXY, + GeometryAccelerationDegree accelDegree) { + if (m_accelerators == null)// (!m_accelerators) + { + m_accelerators = new GeometryAccelerators(); + } + + int rasterSize = RasterizedGeometry2D + .rasterSizeFromAccelerationDegree(accelDegree); + RasterizedGeometry2D rgeom = m_accelerators.getRasterizedGeometry(); + if (rgeom != null) { + if (rgeom.getToleranceXY() < toleranceXY + || rasterSize > rgeom.getRasterSize()) { + m_accelerators._setRasterizedGeometry(null); + } else + return true; + } + + rgeom = RasterizedGeometry2D.create(this, toleranceXY, rasterSize); + m_accelerators._setRasterizedGeometry(rgeom); + return true; + } + + @Override + public int hashCode() { + int hashCode = super.hashCode(); + + if (!isEmptyImpl()) { + int pathCount = getPathCount(); + + if (m_paths != null) + m_paths.calculateHashImpl(hashCode, 0, pathCount + 1); + + if (m_pathFlags != null) + m_pathFlags.calculateHashImpl(hashCode, 0, pathCount); + } + + return hashCode; + } + + public byte getSegmentFlags(int ivertex) { + if (m_segmentFlags != null) + return m_segmentFlags.read(ivertex); + else + return (byte) SegmentFlags.enumLineSeg; + } + + public void getSegment(int startVertexIndex, SegmentBuffer segBuffer, + boolean bStripAttributes) { + int ipath = getPathIndexFromPointIndex(startVertexIndex); + if (startVertexIndex == getPathEnd(ipath) - 1 && !isClosedPath(ipath)) + throw new GeometryException("index out of bounds"); + + _verifyAllStreams(); + AttributeStreamOfInt8 segFlagStream = getSegmentFlagsStreamRef(); + int segFlag = SegmentFlags.enumLineSeg; + if (segFlagStream != null) + segFlag = segFlagStream.read(startVertexIndex) + & SegmentFlags.enumSegmentMask; + + switch (segFlag) { + case SegmentFlags.enumLineSeg: + segBuffer.createLine(); + break; + case SegmentFlags.enumBezierSeg: + throw new GeometryException("internal error"); + case SegmentFlags.enumArcSeg: + throw new GeometryException("internal error"); + default: + throw new GeometryException("internal error"); + } + + Segment currentSegment = segBuffer.get(); + if (!bStripAttributes) + currentSegment.assignVertexDescription(m_description); + else + currentSegment + .assignVertexDescription(VertexDescriptionDesignerImpl + .getDefaultDescriptor2D()); + + int endVertexIndex; + if (startVertexIndex == getPathEnd(ipath) - 1 && isClosedPath(ipath)) { + endVertexIndex = getPathStart(ipath); + } else + endVertexIndex = startVertexIndex + 1; + + Point2D pt = new Point2D(); + getXY(startVertexIndex, pt); + currentSegment.setStartXY(pt); + getXY(endVertexIndex, pt); + currentSegment.setEndXY(pt); + + if (!bStripAttributes) { + for (int i = 1, nattr = m_description.getAttributeCount(); i < nattr; i++) { + int semantics = m_description._getSemanticsImpl(i); + int ncomp = VertexDescription.getComponentCount(semantics); + for (int ord = 0; ord < ncomp; ord++) { + double vs = getAttributeAsDbl(semantics, startVertexIndex, + ord); + currentSegment.setStartAttribute(semantics, ord, vs); + double ve = getAttributeAsDbl(semantics, endVertexIndex, + ord); + currentSegment.setEndAttribute(semantics, ord, ve); + } + } + } + } + + void queryPathEnvelope2D(int path_index, Envelope2D envelope) { + if (path_index >= getPathCount()) + throw new IllegalArgumentException(); + + if (isEmpty()) { + envelope.setEmpty(); + return; + } + + if (hasNonLinearSegments(path_index)) { + throw new GeometryException("not implemented"); + } else { + AttributeStreamOfDbl stream = (AttributeStreamOfDbl) getAttributeStreamRef(VertexDescription.Semantics.POSITION); + Point2D pt = new Point2D(); + Envelope2D env = new Envelope2D(); + env.setEmpty(); + for (int i = getPathStart(path_index), iend = getPathEnd(path_index); i < iend; i++) { + stream.read(2 * i, pt); + env.merge(pt); + } + envelope.setCoords(env); + } + } + + @Override + public boolean _buildQuadTreeAccelerator(GeometryAccelerationDegree d) { + if (m_accelerators == null)// (!m_accelerators) + { + m_accelerators = new GeometryAccelerators(); + } + + if (d == GeometryAccelerationDegree.enumMild || getPointCount() < 16) + return false; + + QuadTreeImpl quad_tree_impl = InternalUtils.buildQuadTree(this); + m_accelerators._setQuadTree(quad_tree_impl); + + return true; + } + +} diff --git a/src/com/esri/core/geometry/MultiPoint.java b/src/com/esri/core/geometry/MultiPoint.java new file mode 100644 index 00000000..0a7baf04 --- /dev/null +++ b/src/com/esri/core/geometry/MultiPoint.java @@ -0,0 +1,362 @@ +/* + Copyright 1995-2013 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ + +package com.esri.core.geometry; + +import java.io.Serializable; + +/** + * A Multipoint is a collection of points. A multipoint is a one-dimensional + * geometry object. Multipoints can be used to store a collection of point-based + * information where the order and individual identity of each point is not an + * essential characteristic of the point set. + */ +public final class MultiPoint extends MultiVertexGeometry implements + Serializable { + + private static final long serialVersionUID = 2L; + + private MultiPointImpl m_impl; + + /** + * Creates a new empty multipoint. + */ + public MultiPoint() { + m_impl = new MultiPointImpl(); + } + + public MultiPoint(VertexDescription description) { + m_impl = new MultiPointImpl(description); + } + + @Override + public double getAttributeAsDbl(int semantics, int index, int ordinate) { + return m_impl.getAttributeAsDbl(semantics, index, ordinate); + } + + @Override + public int getAttributeAsInt(int semantics, int index, int ordinate) { + return m_impl.getAttributeAsInt(semantics, index, ordinate); + } + + @Override + public Point getPoint(int index) { + return m_impl.getPoint(index); + } + + @Override + public int getPointCount() { + return m_impl.getPointCount(); + } + + @Override + public Point2D getXY(int index) { + return m_impl.getXY(index); + } + + @Override + void getXY(int index, Point2D pt) { + m_impl.getXY(index, pt); + } + + @Override + Point3D getXYZ(int index) { + return m_impl.getXYZ(index); + } + + @Override + void queryCoordinates(Point2D[] dst) { + m_impl.queryCoordinates(dst); + } + + @Override + void queryCoordinates(Point[] dst) { + m_impl.queryCoordinates(dst); + } + + @Override + protected Object _getImpl() { + return m_impl; + } + + /** + * Adds a point multipoint. + * + * @param point + * The Point to be added to this multipoint. + */ + public void add(Point point) { + m_impl.add(point); + } + + /** + * Adds a point with the specified X, Y coordinates to this multipoint. + * + * @param x + * The new Point's X coordinate. + * @param y + * The new Point's Y coordinate. + */ + public void add(double x, double y) { + m_impl.add(x, y); + } + + /** + * Adds a 3DPoint with the specified X, Y, Z coordinates to this multipoint. + * + * @param x + * The new Point's X coordinate. + * @param y + * The new Point's Y coordinate. + * @param z + * The new Point's Z coordinate. + */ + void add(double x, double y, double z) { + m_impl.add(x, y, z); + } + + /** + * Appends points from another multipoint at the end of this multipoint. + * + * @param src + * The mulitpoint to append to this multipoint. + * @param srcFrom + * The start index in the source multipoint from which to start + * appending points. + * @param srcTo + * The end index in the source multipoint right after the last + * point to be appended. Use -1 to indicate the rest of the + * source multipoint. + */ + public void add(MultiVertexGeometry src, int srcFrom, int srcTo) { + m_impl.add((MultiVertexGeometryImpl) src._getImpl(), srcFrom, srcTo); + } + + void addPoints(Point2D[] points) { + m_impl.addPoints(points); + } + + void addPoints(Point[] points) { + m_impl.addPoints(points); + } + + /** + * Inserts a point to this multipoint. + * + * @param beforePointIndex + * The index right before the new point to insert. + * @param pt + * The point to insert. + */ + public void insertPoint(int beforePointIndex, Point pt) { + m_impl.insertPoint(beforePointIndex, pt); + } // inserts a point. The point is connected with Lines + + /** + * Removes a point from this multipoint. + * + * @param pointIndex + * The index of the point to be removed. + */ + public void removePoint(int pointIndex) { + m_impl.removePoint(pointIndex); + } + + /** + * Resizes the multipoint to have the given size. + * + * @param pointCount + * - The number of points in this multipoint. + */ + public void resize(int pointCount) { + m_impl.resize(pointCount); + } + + @Override + void queryCoordinates(Point3D[] dst) { + m_impl.queryCoordinates(dst); + } + + @Override + public void setAttribute(int semantics, int index, int ordinate, + double value) { + m_impl.setAttribute(semantics, index, ordinate, value); + } + + @Override + public void setAttribute(int semantics, int index, int ordinate, int value) { + m_impl.setAttribute(semantics, index, ordinate, value); + } + + @Override + public void setPoint(int index, Point pointSrc) { + m_impl.setPoint(index, pointSrc); + } + + @Override + void setXY(int index, Point2D pt) { + m_impl.setXY(index, pt); + } + + @Override + void setXYZ(int index, Point3D pt) { + m_impl.setXYZ(index, pt); + } + + @Override + public void applyTransformation(Transformation2D transform) { + m_impl.applyTransformation(transform); + } + + @Override + void applyTransformation(Transformation3D transform) { + m_impl.applyTransformation(transform); + } + + @Override + public void copyTo(Geometry dst) { + m_impl.copyTo((Geometry) dst._getImpl()); + } + + @Override + public Geometry createInstance() { + return new MultiPoint(getDescription()); + } + + @Override + public int getDimension() { + return 0; + } + + @Override + public Geometry.Type getType() { + return Type.MultiPoint; + } + + @Override + public VertexDescription getDescription() { + return m_impl.getDescription(); + } + + @Override + public void addAttribute(int semantics) { + m_impl.addAttribute(semantics); + } + + @Override + void assignVertexDescription(VertexDescription src) { + m_impl.assignVertexDescription(src); + } + + @Override + public void dropAllAttributes() { + m_impl.dropAllAttributes(); + } + + @Override + public void dropAttribute(int semantics) { + m_impl.dropAttribute(semantics); + } + + @Override + void mergeVertexDescription(VertexDescription src) { + m_impl.mergeVertexDescription(src); + } + + @Override + public boolean isEmpty() { + return m_impl.isEmpty(); + } + + @Override + public void queryEnvelope(Envelope env) { + m_impl.queryEnvelope(env); + } + + @Override + public void queryEnvelope2D(Envelope2D env) { + m_impl.queryEnvelope2D(env); + } + + @Override + void queryEnvelope3D(Envelope3D env) { + m_impl.queryEnvelope3D(env); + } + + @Override + public Envelope1D queryInterval(int semantics, int ordinate) { + return m_impl.queryInterval(semantics, ordinate); + } + + @Override + public void setEmpty() { + m_impl.setEmpty(); + } + + /** + * Returns TRUE when this geometry has exactly same type, properties, and + * coordinates as the other geometry. + */ + @Override + public boolean equals(Object other) { + if (other == null) + return false; + + if (other == this) + return true; + + if (other.getClass() != getClass()) + return false; + + return m_impl.equals(((MultiPoint) other)._getImpl()); + } + + /** + * Returns a hash code value for this multipoint. + */ + @Override + public int hashCode() { + return m_impl.hashCode(); + } + + int queryCoordinates(Point2D[] dst, int dstSize, int beginIndex, + int endIndex) { + return m_impl.queryCoordinates(dst, dstSize, beginIndex, endIndex); + } + + @Override + void getPointByVal(int index, Point outPoint) { + m_impl.getPointByVal(index, outPoint); + } + + @Override + void setPointByVal(int index, Point pointSrc) { + m_impl.setPointByVal(index, pointSrc); + } + + @Override + public int getStateFlag() { + return m_impl.getStateFlag(); + } +} diff --git a/src/com/esri/core/geometry/MultiPointImpl.java b/src/com/esri/core/geometry/MultiPointImpl.java new file mode 100644 index 00000000..65d7ca48 --- /dev/null +++ b/src/com/esri/core/geometry/MultiPointImpl.java @@ -0,0 +1,346 @@ +/* + Copyright 1995-2013 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ + +package com.esri.core.geometry; + +import com.esri.core.geometry.VertexDescription.Semantics; + +/** + * The MultiPoint is a collection of points. + */ +final class MultiPointImpl extends MultiVertexGeometryImpl { + + public MultiPointImpl() { + super(); + m_description = VertexDescriptionDesignerImpl.getDefaultDescriptor2D(); + m_pointCount = 0; + } + + public MultiPointImpl(VertexDescription description) { + super(); + if (description == null) + throw new IllegalArgumentException(); + + m_description = description; + m_pointCount = 0; + } + + @Override + public Geometry createInstance() { + return new MultiPoint(m_description); + } + + /** + * Adds a Point to this MultiPoint. + */ + public void add(Point point) { + resize(m_pointCount + 1); + setPoint(m_pointCount - 1, point); + } + + /** + * Adds a Point to this MultiPoint with given x, y coordinates. + */ + public void add(double x, double y) { + resize(m_pointCount + 1); + Point2D pt = new Point2D(); + pt.setCoords(x, y); + setXY(m_pointCount - 1, pt); + } + + /** + * Adds a Point to this MultiPoint with given x, y, z coordinates. + */ + public void add(double x, double y, double z) { + resize(m_pointCount + 1); + Point3D pt = new Point3D(); + pt.setCoords(x, y, z); + setXYZ(m_pointCount - 1, pt); + } + + /** + * Appends points from another MultiVertexGeometryImpl at the end of this + * one. + * + * @param src + * The source MultiVertexGeometryImpl + */ + public void add(MultiVertexGeometryImpl src, int beginIndex, int endIndex) { + int endIndexC = endIndex < 0 ? src.getPointCount() : endIndex; + if (beginIndex < 0 || beginIndex > src.getPointCount() + || endIndexC < beginIndex) + throw new IllegalArgumentException(); + + if (beginIndex == endIndexC) + return; + + mergeVertexDescription(src.getDescription()); + int count = endIndexC - beginIndex; + int oldPointCount = m_pointCount; + resize(m_pointCount + count); + _verifyAllStreams(); + for (int iattrib = 0, nattrib = src.getDescription() + .getAttributeCount(); iattrib < nattrib; iattrib++) { + int semantics = src.getDescription()._getSemanticsImpl(iattrib); + int ncomps = VertexDescription.getComponentCount(semantics); + AttributeStreamBase stream = getAttributeStreamRef(semantics); + AttributeStreamBase srcStream = src + .getAttributeStreamRef(semantics); + stream.insertRange(oldPointCount * ncomps, srcStream, beginIndex + * ncomps, count * ncomps, true, 1, oldPointCount * ncomps); + } + } + + public void addPoints(Point2D[] points) { + int count = points.length; + int oldPointCount = m_pointCount; + resize(m_pointCount + count); + for (int i = 0; i < count; i++) + setXY(oldPointCount + i, points[i]); + } + + public void insertPoint(int beforePointIndex, Point pt) { + if (beforePointIndex > getPointCount()) + throw new GeometryException("index out of bounds"); + + if (beforePointIndex < 0) + beforePointIndex = getPointCount(); + + mergeVertexDescription(pt.getDescription()); + int oldPointCount = m_pointCount; + _resizeImpl(m_pointCount + 1); + _verifyAllStreams(); + + for (int iattr = 0, nattr = m_description.getAttributeCount(); iattr < nattr; iattr++) { + int semantics = m_description._getSemanticsImpl(iattr); + int comp = VertexDescription.getComponentCount(semantics); + + AttributeStreamBase stream = AttributeStreamBase + .createAttributeStreamWithSemantics(semantics, 1); + if (pt.hasAttribute(semantics)) { + m_vertexAttributes[iattr] + .insertAttributes(comp * beforePointIndex, pt, + semantics, comp * oldPointCount); + } else { + // Need to make room for the attribute, so we copy a default + // value in + + double v = VertexDescription.getDefaultValue(semantics); + m_vertexAttributes[iattr].insertRange(comp * beforePointIndex, + v, comp, comp * oldPointCount); + } + } + + notifyModified(DirtyFlags.DirtyCoordinates); + } + + void removePoint(int pointIndex) { + if (pointIndex < 0 || pointIndex >= getPointCount()) + throw new GeometryException("index out of bounds"); + + _verifyAllStreams(); + + // Remove the attribute value for the path + for (int iattr = 0, nattr = m_description.getAttributeCount(); iattr < nattr; iattr++) { + if (m_vertexAttributes[iattr] != null) { + int semantics = m_description._getSemanticsImpl(iattr); + int comp = VertexDescription.getComponentCount(semantics); + m_vertexAttributes[iattr].eraseRange(comp * pointIndex, comp, + comp * m_pointCount); + } + } + + m_pointCount--; + m_reservedPointCount--; + notifyModified(DirtyFlags.DirtyCoordinates); + } + + /** + * Resizes the MultiPoint to have the given size. + */ + public void resize(int pointCount) { + _resizeImpl(pointCount); + } + + @Override + void _copyToImpl(MultiVertexGeometryImpl mvg) { + } + + @Override + public void setEmpty() { + super._setEmptyImpl(); + } + + @Override + public void applyTransformation(Transformation2D transform) { + if (isEmpty()) + return; + + _verifyAllStreams(); + AttributeStreamOfDbl points = (AttributeStreamOfDbl) m_vertexAttributes[0]; + Point2D pt2 = new Point2D(); + + for (int ipoint = 0; ipoint < m_pointCount; ipoint++) { + pt2.x = points.read(ipoint * 2); + pt2.y = points.read(ipoint * 2 + 1); + + transform.transform(pt2, pt2); + points.write(ipoint * 2, pt2.x); + points.write(ipoint * 2 + 1, pt2.y); + } + + // REFACTOR: reset the exact envelope only and transform the loose + // envelope + notifyModified(DirtyFlags.DirtyCoordinates); + } + + @Override + void applyTransformation(Transformation3D transform) { + if (isEmpty()) + return; + + _verifyAllStreams(); + addAttribute(Semantics.Z); + _verifyAllStreams(); + AttributeStreamOfDbl points = (AttributeStreamOfDbl) m_vertexAttributes[0]; + AttributeStreamOfDbl zs = (AttributeStreamOfDbl) m_vertexAttributes[1]; + Point3D pt3 = new Point3D(); + for (int ipoint = 0; ipoint < m_pointCount; ipoint++) { + pt3.x = points.read(ipoint * 2); + pt3.y = points.read(ipoint * 2 + 1); + pt3.z = zs.read(ipoint); + + Point3D res = transform.transform(pt3); + points.write(ipoint * 2, res.x); + points.write(ipoint * 2 + 1, res.y); + zs.write(ipoint, res.z); + } + + // REFACTOR: reset the exact envelope only and transform the loose + // envelope + notifyModified(DirtyFlags.DirtyCoordinates); + } + + @Override + public int getDimension() { + return 0; + } + + @Override + public Geometry.Type getType() { + return Type.MultiPoint; + } + + @Override + public double calculateArea2D() { + return 0; + } + + @Override + public double calculateLength2D() { + return 0; + } + + @Override + public Object _getImpl() { + return this; + } + + @Override + public boolean equals(Object other) { + if (other == this) + return true; + + if (!(other instanceof MultiPointImpl)) + return false; + + return super.equals(other); + } + + public void addPoints(Point[] points) { + int count = points.length; + // int oldPointCount = m_pointCount; + resize(m_pointCount + count); + for (int i = 0; i < count; i++) + setPoint(i, points[i]); + } + + public int queryCoordinates(Point2D[] dst, int dstSize, int beginIndex, + int endIndex) { + int endIndexC = endIndex < 0 ? m_pointCount : endIndex; + endIndexC = Math.min(endIndexC, beginIndex + dstSize); + + if (beginIndex < 0 || beginIndex >= m_pointCount + || endIndexC < beginIndex || dst.length != dstSize) + throw new IllegalArgumentException();// GEOMTHROW(invalid_argument); + + AttributeStreamOfDbl xy = (AttributeStreamOfDbl) getAttributeStreamRef(VertexDescription.Semantics.POSITION); + int pointCountToRead = endIndexC - beginIndex; + double[] dstArray = new double[pointCountToRead * 2]; + xy.readRange(2 * beginIndex, pointCountToRead * 2, dstArray, 0, true); + + for (int i = 0; i < pointCountToRead; i++) { + dst[i] = new Point2D(dstArray[i * 2], dstArray[i * 2 + 1]); + } + + return endIndexC; + } + + @Override + protected void _notifyModifiedAllImpl() { + // TODO Auto-generated method stub + + } + + @Override + protected void _verifyStreamsImpl() { + // TODO Auto-generated method stub + + } + + @Override + public boolean _buildRasterizedGeometryAccelerator(double toleranceXY, + GeometryAccelerationDegree accelDegree) { + // TODO Auto-generated method stub + return false; + } + + @Override + public boolean _buildQuadTreeAccelerator(GeometryAccelerationDegree accelDegree) { + // TODO Auto-generated method stub + return false; + } + + // @Override + // void _notifyModifiedAllImpl() { + // // TODO Auto-generated method stub + // + // } + + // @Override + // protected void _verifyStreamsImpl() { + // // TODO Auto-generated method stub + // + // } +} diff --git a/src/com/esri/core/geometry/MultiVertexGeometry.java b/src/com/esri/core/geometry/MultiVertexGeometry.java new file mode 100644 index 00000000..cbbf9b9d --- /dev/null +++ b/src/com/esri/core/geometry/MultiVertexGeometry.java @@ -0,0 +1,227 @@ +/* + Copyright 1995-2013 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ + +package com.esri.core.geometry; + +import java.io.Serializable; + +/** + * This class is a base for geometries with many vertices. + * + * The vertex attributes are stored in separate arrays of corresponding type. + * There are as many arrays as there are attributes in the vertex. + */ +abstract class MultiVertexGeometry extends Geometry implements + Serializable { + + @Override + void _afterAddAttributeImpl(int semantics) { + // TODO Auto-generated method stub + + } + + @Override + void _beforeDropAttributeImpl(int semantics) { + // TODO Auto-generated method stub + + } + + // Multipart methods: + /** + * Returns the total vertex count in this Geometry. + */ + public abstract int getPointCount(); + + /** + * Returns given vertex of the Geometry. + */ + public abstract Point getPoint(int index);// Java only + + /** + * Returns given vertex of the Geometry by value. + */ + public void getPoint(int index, Point ptOut) { + getPointByVal(index, ptOut); + }// Java only + + /** + * Sets the vertex at given index of the Geometry. + * + * @param index + * The index of the vertex being changed. + * @param pointSrc + * The Point instance to set given vertex attributes from. The + * pointSrc can not be empty.
+ * The method throws if the pointSrc is not of the Point type.
+ * The attributes, that are present in the pointSrc and missing + * in this Geometry, will be added to the Geometry.
+ * The vertex attributes missing in the pointSrc but present in + * the Geometry will be set to the default values (see + * VertexDescription::GetDefaultValue). + */ + public abstract void setPoint(int index, Point pointSrc);// Java only + + /** + * Returns XY coordinates of the given vertex of the Geometry. + */ + public abstract Point2D getXY(int index); + + abstract void getXY(int index, Point2D pt); + + /** + * Sets XY coordinates of the given vertex of the Geometry. All other + * attributes are unchanged. + */ + abstract void setXY(int index, Point2D pt); + + /** + * Returns XYZ coordinates of the given vertex of the Geometry. If the + * Geometry has no Z's, the default value for Z is returned (0). + */ + abstract Point3D getXYZ(int index); + + /** + * Sets XYZ coordinates of the given vertex of the Geometry. If Z attribute + * is not present in this Geometry, it is added. All other attributes are + * unchanged. + */ + abstract void setXYZ(int index, Point3D pt); + + /** + * Returns XY coordinates as an array. + */ + public Point2D[] getCoordinates2D() { + Point2D[] arr = new Point2D[getPointCount()]; + queryCoordinates(arr); + return arr; + } + + /** + * Returns XYZ coordinates as an array. + */ + Point3D[] getCoordinates3D() { + Point3D[] arr = new Point3D[getPointCount()]; + queryCoordinates(arr); + return arr; + } + + abstract void queryCoordinates(Point[] dst); + + /** + * Queries XY coordinates as an array. The array must be larg enough (See + * GetPointCount()). + */ + abstract void queryCoordinates(Point2D[] dst); + + /** + * Queries XYZ coordinates as an array. The array must be larg enough (See + * GetPointCount()). + */ + abstract void queryCoordinates(Point3D[] dst); + + /** + * Returns value of the given vertex attribute as double. + * + * @param semantics + * The atribute semantics. + * @param index + * is the vertex index in the Geometry. + * @param ordinate + * is the ordinate of a vertex attribute (for example, y has + * ordinate of 1, because it is second ordinate of POSITION) + * + * If attribute is not present, the default value is returned. + * See VertexDescription::GetDefaultValue() method. + */ + public abstract double getAttributeAsDbl(int semantics, int index, + int ordinate); + + /** + * Returns value of the given vertex attribute as int. + * + * @param semantics + * The atribute semantics. + * @param index + * is the vertex index in the Geometry. + * @param ordinate + * is the ordinate of a vertex attribute (for example, y has + * ordinate of 1, because it is second ordinate of POSITION) + * + * If attribute is not present, the default value is returned. + * See VertexDescription::GetDefaultValue() method. Avoid using + * this method on non-integer atributes. + */ + public abstract int getAttributeAsInt(int semantics, int index, int ordinate); + + /** + * Sets the value of given attribute at given posisiotnsis. + * + * @param semantics + * The atribute semantics. + * @param index + * is the vertex index in the Geometry. + * @param ordinate + * is the ordinate of a vertex attribute (for example, y has + * ordinate of 1, because it is seond ordinate of POSITION) + * @param value + * is the value to set. as well as the number of components of + * the attribute. + * + * If the attribute is not present in this Geometry, it is added. + */ + public abstract void setAttribute(int semantics, int index, int ordinate, + double value); + + /** + * Same as above, but works with ints. Avoid using this method on + * non-integer atributes because some double attributes may have NaN default + * values (e.g. Ms) + */ + public abstract void setAttribute(int semantics, int index, int ordinate, + int value); + + /** + * Returns given vertex of the Geometry. The outPoint will have same + * VertexDescription as this Geometry. + */ + abstract void getPointByVal(int index, Point outPoint); + + /** + * Sets the vertex at given index of the Geometry. + * + * @param index + * The index of the vertex being changed. + * @param pointSrc + * The Point instance to set given vertex attributes from. The + * pointSrc can not be empty.
+ * The method throws if the pointSrc is not of the Point type.
+ * The attributes, that are present in the pointSrc and missing + * in this Geometry, will be added to the Geometry.
+ * The vertex attributes missing in the pointSrc but present in + * the Geometry will be set to the default values (see + * VertexDescription::GetDefaultValue). + */ + abstract void setPointByVal(int index, Point pointSrc); + +} diff --git a/src/com/esri/core/geometry/MultiVertexGeometryImpl.java b/src/com/esri/core/geometry/MultiVertexGeometryImpl.java new file mode 100644 index 00000000..e3ffebad --- /dev/null +++ b/src/com/esri/core/geometry/MultiVertexGeometryImpl.java @@ -0,0 +1,1166 @@ +/* + Copyright 1995-2013 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ + + +package com.esri.core.geometry; + +import com.esri.core.geometry.Geometry.GeometryAccelerationDegree; +import com.esri.core.geometry.VertexDescription.Semantics; + +/** + * This class is a base for geometries with many vertices. + * + * The vertex attributes are stored in separate arrays of corresponding type. + * There are as many arrays as there are attributes in the vertex. It uses lazy + * allocation for the vertex attributes. This means, the actual AttributeStream + * is allocated only when the users asks for it, or sets a non-default value. + * + */ +abstract class MultiVertexGeometryImpl extends MultiVertexGeometry { + + // HEADER DEFINED + public interface GeometryXSimple { + final int Unknown = -1; // not know if simple or not + final int Not = 0; // not simple + final int Weak = 1; // weak simple (no self intersections, ring + // orientation is correct, but ring order is not) + final int Strong = 2; // same as weak simple + OGC ring order. + } + + // TODO Remove? + /** + * \internal CHildren implement this method to copy additional information + */ + abstract void _copyToImpl(MultiVertexGeometryImpl mvg); + + protected abstract void _notifyModifiedAllImpl(); + + /** + * \internal Called inside of the VerifyAllStreams to get a child class a + * chance to do additional verify. + */ + protected abstract void _verifyStreamsImpl(); + + public interface DirtyFlags { + public static final int DirtyIsKnownSimple = 1; // !<0 when IsWeakSimple + // flag is valid + public static final int IsWeakSimple = 2; // != m_pointCount) + // TODO + throw new GeometryException("index out of bounds"); + + // _ASSERT(!IsEmpty()); + // _ASSERT(m_vertexAttributes != null); + + _verifyAllStreams(); + + Point outPoint = dst; + outPoint.assignVertexDescription(m_description); + if (outPoint.isEmpty()) + outPoint._setToDefault(); + + for (int attributeIndex = 0; attributeIndex < m_description + .getAttributeCount(); attributeIndex++) { + // fix semantics + int semantics = m_description._getSemanticsImpl(attributeIndex); + + // VertexDescription.getComponentCount(semantics); + for (int icomp = 0, ncomp = VertexDescription + .getComponentCount(semantics); icomp < ncomp; icomp++) { + double v = m_vertexAttributes[attributeIndex].readAsDbl(ncomp + * index + icomp); + outPoint.setAttribute(semantics, icomp, v); + } + } + } + + // Checked vs. Jan 11, 2011 + @Override + protected void setPointByVal(int index, Point src) { + if (index < 0 || index >= m_pointCount) + throw new GeometryException("index out of bounds"); + + Point point = src; + + if (src.isEmpty())// can not assign an empty point to a multipoint + // vertex + throw new IllegalArgumentException(); + + _verifyAllStreams();// verify all allocated streams are of necessary + // size. + VertexDescription vdin = point.getDescription(); + for (int attributeIndex = 0; attributeIndex < vdin.getAttributeCount(); attributeIndex++) { + int semantics = vdin._getSemanticsImpl(attributeIndex); + int ncomp = VertexDescription.getComponentCount(semantics); + for (int icomp = 0; icomp < ncomp; icomp++) { + double v = point.getAttributeAsDbl(semantics, icomp); + setAttribute(semantics, index, icomp, v); + } + } + } + + // Checked vs. Jan 11, 2011 + @Override + public Point2D getXY(int index) { + Point2D pt = new Point2D(); + getXY(index, pt); + return pt; + } + + @Override + public void getXY(int index, Point2D pt) { + if (index < 0 || index >= getPointCount()) + throw new IndexOutOfBoundsException(); + + _verifyAllStreams(); + // AttributeStreamOfDbl v = (AttributeStreamOfDbl) + // m_vertexAttributes[0]; + AttributeStreamOfDbl v = (AttributeStreamOfDbl) m_vertexAttributes[0]; + v.read(index * 2, pt); + } + + // Checked vs. Jan 11, 2011 + @Override + public void setXY(int index, Point2D pt) { + if (index < 0 || index >= m_pointCount) + // TODO exception + throw new IndexOutOfBoundsException(); + + _verifyAllStreams(); + // AttributeStreamOfDbl v = (AttributeStreamOfDbl) + // m_vertexAttributes[0]; + AttributeStreamOfDbl v = (AttributeStreamOfDbl) m_vertexAttributes[0]; + v.write(index * 2, pt); + notifyModified(DirtyFlags.DirtyCoordinates); + } + + // Checked vs. Jan 11, 2011 + public void setXY(int index, double x, double y) { + if (index < 0 || index >= m_pointCount) + // TODO exc + throw new IndexOutOfBoundsException(); + + _verifyAllStreams(); + // AttributeStreamOfDbl v = (AttributeStreamOfDbl) + // m_vertexAttributes[0]; + // TODO ask sergey about casts + AttributeStreamOfDbl v = (AttributeStreamOfDbl) m_vertexAttributes[0]; + v.write(index * 2, x); + v.write(index * 2 + 1, y); + notifyModified(DirtyFlags.DirtyCoordinates); + } + + // Checked vs. Jan 11, 2011 + @Override + public Point3D getXYZ(int index) { + if (index < 0 || index >= getPointCount()) + // FIXME exc + throw new IndexOutOfBoundsException(); + + _verifyAllStreams(); + // AttributeStreamOfDbl v = (AttributeStreamOfDbl) + // m_vertexAttributes[0]; + AttributeStreamOfDbl v = (AttributeStreamOfDbl) m_vertexAttributes[0]; + Point3D pt = new Point3D(); + pt.x = v.read(index * 2); + pt.y = v.read(index * 2 + 1); + + // TODO check excluded if statement componenet + if (hasAttribute(Semantics.Z))// && (m_vertexAttributes[1] != null)) + pt.z = m_vertexAttributes[1].readAsDbl(index); + else + pt.z = VertexDescription.getDefaultValue(Semantics.Z); + + return pt; + } + + // Checked vs. Jan 11, 2011 + @Override + public void setXYZ(int index, Point3D pt) { + if (index < 0 || index >= getPointCount()) + // FIXME exc + throw new IndexOutOfBoundsException(); + + addAttribute(Semantics.Z); + + _verifyAllStreams(); + notifyModified(DirtyFlags.DirtyCoordinates); + // AttributeStreamOfDbl v = (AttributeStreamOfDbl) + // m_vertexAttributes[0]; + AttributeStreamOfDbl v = (AttributeStreamOfDbl) m_vertexAttributes[0]; + v.write(index * 2, pt.x); + v.write(index * 2 + 1, pt.y); + m_vertexAttributes[1].writeAsDbl(index, pt.z); + } + + // Checked vs. Jan 11, 2011 + @Override + public double getAttributeAsDbl(int semantics, int offset, int ordinate) { + if (offset < 0 || offset >= m_pointCount) + // FIXME exc + throw new IndexOutOfBoundsException(); + + int ncomps = VertexDescription.getComponentCount(semantics); + if (ordinate >= ncomps) + // FIXME exc + throw new IndexOutOfBoundsException(); + + _verifyAllStreams(); + int attributeIndex = m_description.getAttributeIndex(semantics); + // TODO check if statement + if (attributeIndex >= 0)// && m_vertexAttributes[attributeIndex] != + // null) { + { + return m_vertexAttributes[attributeIndex].readAsDbl(offset * ncomps + + ordinate); + } + return VertexDescription.getDefaultValue(semantics); + } + + // Checked vs. Jan 11, 2011 + @Override + public int getAttributeAsInt(int semantics, int offset, int ordinate) { + return (int) getAttributeAsDbl(semantics, offset, ordinate); + } + + // Checked vs. Jan 11, 2011 + @Override + public void setAttribute(int semantics, int offset, int ordinate, + double value) { + if (offset < 0 || offset >= m_pointCount) + // FIXME exc + throw new IndexOutOfBoundsException(); + + int ncomps = VertexDescription.getComponentCount(semantics); + if (ordinate >= ncomps) + // FIXME exc + throw new IndexOutOfBoundsException(); + + addAttribute(semantics); + _verifyAllStreams(); + int attributeIndex = m_description.getAttributeIndex(semantics); + notifyModified(DirtyFlags.DirtyCoordinates); + m_vertexAttributes[attributeIndex].writeAsDbl(offset * ncomps + + ordinate, value); + } + + // Checked vs. Jan 11, 2011 + @Override + public void setAttribute(int semantics, int offset, int ordinate, int value) { + setAttribute(semantics, offset, ordinate, (double) value); + } + + // FIXME change semantics to an enum + // Checked vs. Jan 11, 2011 + public AttributeStreamBase getAttributeStreamRef(int semantics) { + throwIfEmpty(); + + addAttribute(semantics); + _verifyAllStreams(); + + int attributeIndex = m_description.getAttributeIndex(semantics); + return m_vertexAttributes[attributeIndex]; + } + + // FIXME change semantics to an enum + // Checked vs. Jan 11, 2011 + /** + * Sets a reference to the given AttributeStream of the Geometry. Once the + * buffer has been obtained, the vertices of the Geometry can be manipulated + * directly. The AttributeStream parameters are not checked for the size.
+ * If the attribute is missing, it will be added.
+ * Note, that this method does not change the vertex count in the Geometry.
+ * The stream can have more elements, than the Geometry point count, but + * only necessary part will be saved when exporting to a ESRI shape or other + * format. @param semantics Semantics of the attribute to assign the stream + * to. @param stream The input AttributeStream that will be assigned by + * reference. If one changes the stream later through the reference, one has + * to call NotifyStreamChanged. \exception Throws invalid_argument exception + * if the input stream type does not match that of the semantics + * persistence. + */ + public void setAttributeStreamRef(int semantics, AttributeStreamBase stream) { + // int test1 = VertexDescription.getPersistence(semantics); + // int test2 = stream.getPersistence(); + + if ((stream != null) + && VertexDescription.getPersistence(semantics) != stream + .getPersistence())// input stream has wrong persistence + // FIXME exc + throw new IllegalArgumentException(); + + // Do not check for the stream size here to allow several streams to be + // attached before the point count is changed. + addAttribute(semantics); + int attributeIndex = m_description.getAttributeIndex(semantics); + if (m_vertexAttributes == null) + m_vertexAttributes = new AttributeStreamBase[m_description + .getAttributeCount()]; + + m_vertexAttributes[attributeIndex] = stream; + notifyModified(DirtyFlags.DirtyAll); + } + + // Checked vs. Jan 11, 2011 + @Override + void _beforeDropAttributeImpl(int semantics) { + _touch(); + int attributeIndex = m_description.getAttributeIndex(semantics); + // _ASSERT(attributeIndex >= 0); + + AttributeStreamBase[] newAttributes = null; + if (m_vertexAttributes != null) { + newAttributes = new AttributeStreamBase[m_description + .getAttributeCount() - 1]; + + for (int i = 0; i < attributeIndex; i++) + newAttributes[i] = m_vertexAttributes[i]; + for (int i = attributeIndex + 1; i < m_description + .getAttributeCount(); i++) + newAttributes[i - 1] = m_vertexAttributes[i]; + } + + m_vertexAttributes = newAttributes; // late assignment to try to stay + // valid if out-of memory happens. + notifyModified(DirtyFlags.DirtyAll); + } + + // Checked vs. Jan 11, 2011 + @Override + void _afterAddAttributeImpl(int semantics) { + _touch(); + int attributeIndex = m_description.getAttributeIndex(semantics); + // _ASSERT(attributeIndex >= 0); + + AttributeStreamBase[] newAttributes = null; + if (m_vertexAttributes != null) { + newAttributes = new AttributeStreamBase[m_description + .getAttributeCount()]; + + if (m_vertexAttributes != null) { + // Do not create new stream The stream will be created when one + // queries or sets a point to it. + for (int i = 0; i < attributeIndex; i++) + newAttributes[i] = m_vertexAttributes[i]; + for (int i = attributeIndex + 1; i < m_description + .getAttributeCount(); i++) + newAttributes[i] = m_vertexAttributes[i - 1]; + } + } + + m_vertexAttributes = newAttributes; // late assignment to try to stay + // valid if out-of memory happens. + m_reservedPointCount = -1;// we need to recreate the new attribute then + notifyModified(DirtyFlags.DirtyAll); + } + + // Checked vs. Jan 11, 2011 + protected void _updateEnvelope(Envelope2D env) { + _updateAllDirtyIntervals(true); + m_envelope.queryEnvelope2D(env); + } // note: overload for polylines/polygons with curves + + // Checked vs. Jan 11, 2011 + protected void _updateEnvelope(Envelope3D env) { + _updateAllDirtyIntervals(true); + m_envelope.queryEnvelope3D(env); + } // note: overload for polylines/polygons with curves + + // Checked vs. Jan 11, 2011 + protected void _updateLooseEnvelope(Envelope2D env) { + // TODO ROHIT has this set to true? + _updateAllDirtyIntervals(false); + m_envelope.queryEnvelope2D(env); + } // note: overload for polylines/polygons with curves + + // Checked vs. Jan 11, 2011 + /** + * \internal Calculates loose envelope. Returns True if the calculation + * renders exact envelope. + */ + protected void _updateLooseEnvelope(Envelope3D env) { + // TODO ROHIT has this set to true? + _updateAllDirtyIntervals(false); + m_envelope.queryEnvelope3D(env); + } // note: overload for polylines/polygons with curves + + // Checked vs. Jan 11, 2011 + @Override + public void queryEnvelope(Envelope env) { + _updateAllDirtyIntervals(true); + m_envelope.copyTo(env); + } + + // TODO rename to remove 2D + // Checked vs. Jan 11, 2011 + @Override + public void queryEnvelope2D(Envelope2D env) { + _updateEnvelope(env); + } + + // Checked vs. Jan 11, 2011 + // TODO rename to remove 3D + @Override + public void queryEnvelope3D(Envelope3D env) { + _updateEnvelope(env); + } + + // Checked vs. Jan 11, 2011 + // TODO rename to remove 2D + @Override + public void queryLooseEnvelope2D(Envelope2D env) { + _updateLooseEnvelope(env); + } + + // Checked vs. Jan 11, 2011 + // TODO rename to remove 3D + @Override + public void queryLooseEnvelope3D(Envelope3D env) { + _updateLooseEnvelope(env); + } + + // Checked vs. Jan 11, 2011 + @Override + public Envelope1D queryInterval(int semantics, int ordinate) { + Envelope1D env = new Envelope1D(); + if (isEmptyImpl()) { + env.setEmpty(); + return env; + } + + _updateAllDirtyIntervals(true); + return m_envelope.queryInterval(semantics, ordinate); + } + + // Checked vs. Jan 11, 2011 + // TODO Rename to getHashCode + @Override + public int hashCode() { + int hashCode = m_description.hashCode(); + + if (!isEmptyImpl()) { + int pointCount = getPointCount(); + for (int i = 0, n = m_description.getAttributeCount(); i < n; i++) { + int components = VertexDescription + .getComponentCount(m_description._getSemanticsImpl(i)); + AttributeStreamBase stream = m_vertexAttributes[i]; + hashCode = stream.calculateHashImpl(hashCode, 0, pointCount + * components); + } + } + + return hashCode; + } + + // Checked vs. Jan 11, 2011 + @Override + public boolean equals(Object other) { + // Java checks + if (other == this) + return true; + + if (!(other instanceof MultiVertexGeometryImpl)) + return false; + + // Borg Implementation + MultiVertexGeometryImpl otherMulti = (MultiVertexGeometryImpl) other; + + if (!(m_description.equals(otherMulti.m_description))) + return false; + + if (isEmptyImpl() != otherMulti.isEmptyImpl()) + return false; + + if (isEmptyImpl()) + return true; // both geometries are empty + + int pointCount = getPointCount(); + int pointCountOther = otherMulti.getPointCount(); + + if (pointCount != pointCountOther) + return false; + + for (int i = 0; i < m_description.getAttributeCount(); i++) { + int semantics = m_description.getSemantics(i); + + AttributeStreamBase stream = getAttributeStreamRef(semantics); + AttributeStreamBase streamOther = otherMulti + .getAttributeStreamRef(semantics); + + int components = VertexDescription.getComponentCount(semantics); + + if (!stream.equals(streamOther, 0, pointCount * components)) + return false; + } + + return true; + } + + // Checked vs. Jan 11, 2011 + /** + * Sets the envelope of the Geometry. The Envelope description must match + * that of the Geometry. + */ + public void setEnvelope(Envelope env) { + if (!m_description.equals(env.getDescription())) + // FIXME exc + throw new IllegalArgumentException(); + + // m_envelope = (Envelope) env.clone(); + m_envelope = (Envelope) env.createInstance(); + env.copyTo(m_envelope); + _setDirtyFlag(DirtyFlags.DirtyIntervals, false); + } + + // Checked vs. Jan 11, 2011 + @Override + public void copyTo(Geometry dstGeom) { + // Consider this: + // Imagine if we produce 100 copies, each will have dirty envelope to + // calculate - not good. + // However, if the copied geometry is changed immediately, we do not + // want to call _UpdateDirtyParams twice. + // That is why this is commented out so far. + // _UpdateDirtyParams(); + + MultiVertexGeometryImpl dst = (MultiVertexGeometryImpl) dstGeom; + if (dst.getType() != getType()) + // FIXME exc + throw new IllegalArgumentException(); + + _verifyAllStreams(); + dst.m_description = m_description; + dst.m_vertexAttributes = null; + int nattrib = m_description.getAttributeCount(); + AttributeStreamBase[] cloneAttributes = null; + if (m_vertexAttributes != null) { + cloneAttributes = new AttributeStreamBase[nattrib]; + for (int i = 0; i < nattrib; i++) { + if (m_vertexAttributes[i] != null) { + int ncomps = VertexDescription + .getComponentCount(m_description + ._getSemanticsImpl(i)); + cloneAttributes[i] = m_vertexAttributes[i] + .restrictedClone(getPointCount() * ncomps); + } + } + } + + if (m_envelope != null) { + dst.m_envelope = (Envelope) m_envelope.createInstance(); + m_envelope.copyTo(dst.m_envelope); + // dst.m_envelope = (Envelope) m_envelope.clone(); + } else + dst.m_envelope = null; + + dst.m_pointCount = m_pointCount; + dst.m_flagsMask = m_flagsMask; + dst.m_vertexAttributes = cloneAttributes; + + // FIXME accelerators + // if(m_accelerators != null) + // dst.m_accelerators = m_accelerators; + + try { + _copyToImpl(dst); // copy child props + } catch (Exception ex) { + dst.setEmpty(); + // TODO fix exception + throw new RuntimeException(ex); + } + } + + // Checked vs. Jan 11, 2011 + public boolean _attributeStreamIsAllocated(int semantics) { + throwIfEmpty(); + + int attributeIndex = m_description.getAttributeIndex(semantics); + + if (attributeIndex >= 0 && m_vertexAttributes[attributeIndex] != null) + return true; + + return false; + } + + // Checked vs. Jan 11, 2011 + void _setEmptyImpl() { + m_pointCount = 0; + m_reservedPointCount = -1; + m_vertexAttributes = null;// release it all streams. + notifyModified(DirtyFlags.DirtyAll); + } + + // Checked vs. Jan 11, 2011 + /** + * Notifies the Geometry of changes made to the vertices so that it could + * reset cached structures. + */ + public void notifyModified(int flags) { + if (flags == DirtyFlags.DirtyAll) { + m_reservedPointCount = -1;// forget the reserved point number + _notifyModifiedAllImpl(); + } + m_flagsMask |= flags; + + // FIXME acceler + _clearAccelerators(); + _touch(); + } + + // Checked vs. Jan 11, 2011 + /** + * @param bExact + * True, when the exact envelope need to be calculated and false + * for the loose one. + */ + protected void _updateAllDirtyIntervals(boolean bExact) { + _verifyAllStreams(); + if (_hasDirtyFlag(DirtyFlags.DirtyIntervals)) { + if (null == m_envelope) + m_envelope = new Envelope(m_description); + else + m_envelope.assignVertexDescription(m_description); + + if (isEmpty()) { + m_envelope.setEmpty(); + return; + } + + _updateXYImpl(bExact);// efficient method for xy's + // now go through other attribues. + for (int attributeIndex = 1; attributeIndex < m_description + .getAttributeCount(); attributeIndex++) { + int semantics = m_description._getSemanticsImpl(attributeIndex); + int ncomps = VertexDescription.getComponentCount(semantics); + AttributeStreamBase stream = m_vertexAttributes[attributeIndex]; + for (int iord = 0; iord < ncomps; iord++) { + Envelope1D interval = new Envelope1D(); + interval.setEmpty(); + for (int i = 0; i < m_pointCount; i++) { + double value = stream.readAsDbl(i * ncomps + iord);// some + // optimization + // is + // possible + // if + // non-virtual + // method + // is + // used + interval.merge(value); + } + m_envelope.setInterval(semantics, iord, interval); + } + } + if (bExact) + _setDirtyFlag(DirtyFlags.DirtyIntervals, false); + } + } + + // Checked vs. Jan 11, 2011 + /** + * \internal Updates x, y intervals. + */ + public void _updateXYImpl(boolean bExact) { + m_envelope.setEmpty(); + AttributeStreamOfDbl stream = (AttributeStreamOfDbl) m_vertexAttributes[0]; + Point2D pt = new Point2D(); + for (int i = 0; i < m_pointCount; i++) { + stream.read(2 * i, pt); + m_envelope.merge(pt); + } + } + + void calculateEnvelope2D(Envelope2D env, boolean bExact) { + env.setEmpty(); + AttributeStreamOfDbl stream = (AttributeStreamOfDbl) m_vertexAttributes[0]; + Point2D pt = new Point2D(); + for (int i = 0; i < m_pointCount; i++) { + stream.read(2 * i, pt); + env.merge(pt); + } + } + + // Checked vs. Jan 11, 2011 lots of changes + /** + * \internal Verifies all streams (calls _VerifyStream for every attribute). + */ + protected void _verifyAllStreamsImpl() { + // This method checks that the streams are of correct size. + // It resizes the streams to ensure they are not shorter than + // m_PointCount + // _ASSERT(_HasDirtyFlag(enum_value1(DirtyFlags, + // DirtyVerifiedStreams))); + if (m_reservedPointCount < m_pointCount) // an optimization to skip this + // expensive loop when + // adding point by point + { + if (m_vertexAttributes == null) + m_vertexAttributes = new AttributeStreamBase[m_description + .getAttributeCount()]; + + m_reservedPointCount = NumberUtils.intMax(); + for (int attributeIndex = 0; attributeIndex < m_description + .getAttributeCount(); attributeIndex++) { + int semantics = m_description._getSemanticsImpl(attributeIndex); + if (m_vertexAttributes[attributeIndex] != null) { + int ncomp = VertexDescription.getComponentCount(semantics); + int size = m_vertexAttributes[attributeIndex].virtualSize() + / ncomp; + if (size < m_pointCount) { + size = (m_reservedPointCount > m_pointCount + 5) ? (m_pointCount * 5 + 3) / 4 + : m_pointCount;// reserve 25% more than user + // asks + m_vertexAttributes[attributeIndex].resize(size * ncomp, + VertexDescription.getDefaultValue(semantics)); + } + + if (size < m_reservedPointCount) + m_reservedPointCount = size; + } else { + m_vertexAttributes[attributeIndex] = AttributeStreamBase + .createAttributeStreamWithSemantics(semantics, + m_pointCount); + // FIXME when attribute stream is updated, update this code. + // m_vertexAttributes[attributeIndex] = + // AttributeStreamBase.createAttributeStream(semantics, + // m_pointCount); + m_reservedPointCount = m_pointCount; + } + } + } + _verifyStreamsImpl(); + + _setDirtyFlag(DirtyFlags.DirtyVerifiedStreams, false); + } + + // Checked vs. Jan 11, 2011 + void _resizeImpl(int pointCount) { + if (pointCount < 0) + throw new IllegalArgumentException(); + + if (pointCount == m_pointCount) + return; + + m_pointCount = pointCount; + notifyModified(DirtyFlags.DirtyAllInternal); + } + + // Checked vs. Jan 11, 2011 + int QueryCoordinates(Point2D[] dst, int dstSize, int beginIndex, + int endIndex) { + int endIndexC = endIndex < 0 ? m_pointCount : endIndex; + endIndexC = Math.min(endIndexC, beginIndex + dstSize); + + if (beginIndex < 0 || beginIndex >= m_pointCount + || endIndexC < beginIndex) + throw new IllegalArgumentException(); + + AttributeStreamOfDbl xy = (AttributeStreamOfDbl) getAttributeStreamRef(VertexDescription.Semantics.POSITION); + int j = 0; + double[] dstArray = new double[dst.length * 2]; + xy.readRange(2 * beginIndex, endIndexC - beginIndex, dstArray, j, true); + + for (int i = 0; i < dst.length; i++) { + dst[i] = new Point2D(dstArray[i * 2], dstArray[i * 2 + 1]); + } + + // for (int i = beginIndex; i < endIndexC; i++, j++) + // { + // xy.read(2 * i, dst[j]); + // } + + return endIndexC; + } + + // Checked vs. Jan 11, 2011 + int QueryCoordinates(Point3D[] dst, int dstSize, int beginIndex, + int endIndex) { + int endIndexC = endIndex < 0 ? m_pointCount : endIndex; + endIndexC = Math.min(endIndexC, beginIndex + dstSize); + + if (beginIndex < 0 || beginIndex >= m_pointCount + || endIndexC < beginIndex) + // TODO replace geometry exc + throw new IllegalArgumentException(); + + AttributeStreamOfDbl xy = (AttributeStreamOfDbl) getAttributeStreamRef(VertexDescription.Semantics.POSITION); + AttributeStreamOfDbl z = null; + double v = VertexDescription + .getDefaultValue(VertexDescription.Semantics.Z); + boolean bHasZ = hasAttribute(VertexDescription.Semantics.Z); + if (bHasZ) + z = (AttributeStreamOfDbl) getAttributeStreamRef(VertexDescription.Semantics.Z); + int j = 0; + for (int i = beginIndex; i < endIndexC; i++, j++) { + dst[j].x = xy.read(2 * i); + dst[j].y = xy.read(2 * i + 1); + dst[j].z = bHasZ ? z.read(i) : v; + + dst[j] = getXYZ(i); + } + + return endIndexC; + } + + // Checked vs. Jan 11, 2011 + // -1 : DirtySimple is true (whether or not the MultiPath is Simple is + // unknown) + // 0 : DirtySimple is false and the MultiPath is not Weak Simple + // 1 : DirtySimple is false and the MultiPath is Weak Simple but not ring + // ordering may be invalid + // 2 : DirtySimple is false and the MultiPath is Strong Simple (Weak Simple + // and valid ring ordering) + public int getIsSimple(double tolerance) { + if (!_hasDirtyFlag(DirtyFlags.DirtyIsKnownSimple)) { + if (!_hasDirtyFlag(DirtyFlags.IsWeakSimple)) { + return 0; + } + if (m_simpleTolerance >= tolerance) { + if (!_hasDirtyFlag(DirtyFlags.DirtyOGCFlags)) + return 2; + + return 1; + } + + return -1; + } + return -1; + } + + void setIsSimple(int isSimpleRes, double tolerance, boolean ogc_known) { + m_simpleTolerance = tolerance; + if (isSimpleRes == GeometryXSimple.Unknown) { + _setDirtyFlag(DirtyFlags.DirtyIsKnownSimple, true); + _setDirtyFlag(DirtyFlags.DirtyOGCFlags, true); + return; + } + _setDirtyFlag(DirtyFlags.DirtyIsKnownSimple, false); + + if (!ogc_known) + _setDirtyFlag(DirtyFlags.DirtyOGCFlags, true); + + if (isSimpleRes == GeometryXSimple.Not) { + _setDirtyFlag(DirtyFlags.IsWeakSimple, false); + _setDirtyFlag(DirtyFlags.IsStrongSimple, false); + } else if (isSimpleRes == GeometryXSimple.Weak) { + _setDirtyFlag(DirtyFlags.IsWeakSimple, true); + _setDirtyFlag(DirtyFlags.IsStrongSimple, false); + } else if (isSimpleRes == GeometryXSimple.Strong) { + _setDirtyFlag(DirtyFlags.IsWeakSimple, true); + _setDirtyFlag(DirtyFlags.IsStrongSimple, true); + } else + throw new GeometryException("internal error");// what? + } + + double _getSimpleTolerance() { + return m_simpleTolerance; + } + + public GeometryAccelerators _getAccelerators() { + return m_accelerators; + } + + void _clearAccelerators() { + if (m_accelerators != null) + m_accelerators = null; + } + + void _interpolateTwoVertices(int vertex1, int vertex2, double f, + Point outPoint) { + if (vertex1 < 0 || vertex1 >= m_pointCount) + throw new GeometryException("index out of bounds."); + if (vertex2 < 0 || vertex2 >= m_pointCount) + throw new GeometryException("index out of bounds."); + + // _ASSERT(!IsEmpty()); + // _ASSERT(m_vertexAttributes != NULLPTR); + + _verifyAllStreams(); + + outPoint.assignVertexDescription(m_description); + if (outPoint.isEmpty()) + outPoint._setToDefault(); + + for (int attributeIndex = 0; attributeIndex < m_description + .getAttributeCount(); attributeIndex++) { + int semantics = m_description._getSemanticsImpl(attributeIndex); + for (int icomp = 0, ncomp = VertexDescription + .getComponentCount(semantics); icomp < ncomp; icomp++) { + double v1 = m_vertexAttributes[attributeIndex].readAsDbl(ncomp + * vertex1 + icomp); + double v2 = m_vertexAttributes[attributeIndex].readAsDbl(ncomp + * vertex2 + icomp); + outPoint.setAttribute(semantics, icomp, v1 * (1.0 - f) + v2 * f); + } + } + } + + double _getShortestDistance(int vertex1, int vertex2) { + Point2D pt = getXY(vertex1); + pt.sub(getXY(vertex2)); + return pt.length(); + } + + // FIXME Remove this method. It is not in the MultiVertexGeometryImpl... + + // /** + // * Returns a reference to the given AttributeStream of the Geometry. Once + // * the stream has been obtained, the vertices of the Geometry can be + // * manipulated directly. Call notifyModified, when finished. The method + // * allocates the stream if not present. + // * + // * @param semantics + // * Semantics of the attribute to return stream for. + // * @throws Throws + // * empty_geometry for the empty geometry. + // */ + // public AttributeStreamBase getAttributeStream(int semantics) { + // if (isEmpty()) + // throw new GeometryException( + // "This operation was performed on an Empty Geometry."); + // + // addAttribute(semantics); + // _verifyAllStreams(); + // + // int attributeIndex = m_description.getAttributeIndex(semantics); + // return m_vertexAttributes[attributeIndex]; + // } + + // FIXME + // ////////////////// METHODS To REMOVE /////////////////////// + @Override + public Point getPoint(int index) { + if (index < 0 || index >= m_pointCount) + throw new IndexOutOfBoundsException(); + + // _ASSERT(!IsEmpty()); + // _ASSERT(m_vertexAttributes != null); + + _verifyAllStreams(); + + Point outPoint = new Point(); + outPoint.assignVertexDescription(m_description); + if (outPoint.isEmpty()) + outPoint._setToDefault(); + + for (int attributeIndex = 0; attributeIndex < m_description + .getAttributeCount(); attributeIndex++) { + int semantics = m_description.getSemantics(attributeIndex); + for (int icomp = 0, ncomp = VertexDescription + .getComponentCount(semantics); icomp < ncomp; icomp++) { + double v = m_vertexAttributes[attributeIndex].readAsDbl(ncomp + * index + icomp); + outPoint.setAttribute(semantics, icomp, v); + } + } + return outPoint; + } + + @Override + public void setPoint(int index, Point src) { + if (index < 0 || index >= m_pointCount) + throw new IndexOutOfBoundsException(); + + Point point = src; + + if (src.isEmpty())// can not assign an empty point to a multipoint + // vertex + throw new IllegalArgumentException(); + + _verifyAllStreams();// verify all allocated streams are of necessary + // size. + VertexDescription vdin = point.getDescription(); + for (int attributeIndex = 0; attributeIndex < vdin.getAttributeCount(); attributeIndex++) { + int semantics = vdin.getSemantics(attributeIndex); + int ncomp = VertexDescription.getComponentCount(semantics); + for (int icomp = 0; icomp < ncomp; icomp++) { + double v = point.getAttributeAsDbl(semantics, icomp); + setAttribute(semantics, index, icomp, v); + } + } + } + + @Override + public void queryCoordinates(Point[] dst) { + int sz = m_pointCount; + if (dst.length < sz) + throw new IllegalArgumentException(); + + // TODO: refactor to a better AttributeAray call (ReadRange?) + for (int i = 0; i < sz; i++) { + dst[i] = getPoint(i); + } + } + + @Override + public void queryCoordinates(Point2D[] dst) { + int sz = m_pointCount; + if (dst.length < sz) + throw new IllegalArgumentException(); + + // TODO: refactor to a better AttributeAray call (ReadRange?) + for (int i = 0; i < sz; i++) { + dst[i] = getXY(i); + } + } + + @Override + public void queryCoordinates(Point3D[] dst) { + int sz = m_pointCount; + if (dst.length < sz) + throw new IllegalArgumentException(); + + // TODO: refactor to a better AttributeAray call (ReadRange?) + for (int i = 0; i < sz; i++) { + dst[i] = getXYZ(i); + } + } + + // FIXME + + public abstract boolean _buildRasterizedGeometryAccelerator( + double toleranceXY, GeometryAccelerationDegree accelDegree); + + public abstract boolean _buildQuadTreeAccelerator( + GeometryAccelerationDegree d); + // //////////////////METHODS To REMOVE /////////////////////// + +} diff --git a/src/com/esri/core/geometry/NonSimpleResult.java b/src/com/esri/core/geometry/NonSimpleResult.java new file mode 100644 index 00000000..b8d9d029 --- /dev/null +++ b/src/com/esri/core/geometry/NonSimpleResult.java @@ -0,0 +1,81 @@ +/* + Copyright 1995-2013 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ +package com.esri.core.geometry; + +class NonSimpleResult { + public enum Reason { + NotDetermined, // maxv ? maxv : v; + } + + static long snap(long v, long minv, long maxv) { + return v < minv ? minv : v > maxv ? maxv : v; + } + + public static double snap(double v, double minv, double maxv) { + return v < minv ? minv : v > maxv ? maxv : v; + } + + public static int sizeOf(double v) { + return 8; + } + + public static int sizeOfDouble() { + return 8; + } + + public static int sizeOf(int v) { + return 4; + } + + public static int sizeOf(long v) { + return 8; + } + + public static int sizeOf(byte v) { + return 1; + } + + public static boolean isNaN(double d) { + return Double.isNaN(d); + } + + public final static double TheNaN = Double.NaN; + + public static double NaN() { + return Double.NaN; + } + + // public static void main(String[] args) + // { + // System.out.println(Long.toHexString(Double.doubleToLongBits(Double.NaN))); + // System.out.println(Double.longBitsToDouble(0x7FF8000000000001L)); + // + // System.out.println(Long.toHexString(Double.doubleToLongBits(Double.NEGATIVE_INFINITY))); + // System.out.println(Double.longBitsToDouble(0xFFF8000000000000L)); + // System.out.println(Double.NEGATIVE_INFINITY); + // } + + public static int hash(int n) { + int hash = 5381; + hash = ((hash << 5) + hash) + (n & 0xFF); /* hash * 33 + c */ + hash = ((hash << 5) + hash) + ((n >> 8) & 0xFF); + hash = ((hash << 5) + hash) + ((n >> 16) & 0xFF); + hash = ((hash << 5) + hash) + ((n >> 24) & 0xFF); + hash &= 0x7FFFFFFF; + return hash; + } + + public static int hash(double d) { + long bits = Double.doubleToLongBits(d); + int hc = (int) (bits ^ (bits >>> 32)); + return hash(hc); + } + + public static int hash(int hashIn, int n) { + int hash = ((hashIn << 5) + hashIn) + (n & 0xFF); /* hash * 33 + c */ + hash = ((hash << 5) + hash) + ((n >> 8) & 0xFF); + hash = ((hash << 5) + hash) + ((n >> 16) & 0xFF); + hash = ((hash << 5) + hash) + ((n >> 24) & 0xFF); + hash &= 0x7FFFFFFF; + return hash; + } + + public static int hash(int hash, double d) { + long bits = Double.doubleToLongBits(d); + int hc = (int) (bits ^ (bits >>> 32)); + return hash(hash, hc); + } + + public static long doubleToInt64Bits(double d) { + return Double.doubleToLongBits(d); + } + + public static double negativeInf() { + return Double.NEGATIVE_INFINITY; + } + + public static double positiveInf() { + return Double.POSITIVE_INFINITY; + } + + public static int intMax() { + return Integer.MAX_VALUE; + } + + public static double doubleEps() { + return 2.2204460492503131e-016; + } + + public static double doubleMax() { + return Double.MAX_VALUE; + } + + public static int nextRand(int prevRand) { + return (1103515245 * prevRand + 12345) & intMax(); // according to Wiki, + // this is gcc's + } + +} diff --git a/src/com/esri/core/geometry/OGCStructure.java b/src/com/esri/core/geometry/OGCStructure.java new file mode 100644 index 00000000..14dde139 --- /dev/null +++ b/src/com/esri/core/geometry/OGCStructure.java @@ -0,0 +1,32 @@ +/* + Copyright 1995-2013 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ +package com.esri.core.geometry; + +import java.util.List; + +public class OGCStructure { + public int m_type; + public List m_structures; + public Geometry m_geometry; +} diff --git a/src/com/esri/core/geometry/ObjectCacheTable.java b/src/com/esri/core/geometry/ObjectCacheTable.java new file mode 100644 index 00000000..b655ed70 --- /dev/null +++ b/src/com/esri/core/geometry/ObjectCacheTable.java @@ -0,0 +1,65 @@ +/* + Copyright 1995-2013 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ +package com.esri.core.geometry; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +class ObjectCacheTable { + private Map m_hashTable = Collections + .synchronizedMap(new HashMap()); + private Object[] m_lru; + private boolean[] m_places; + private int m_index; + + public ObjectCacheTable(int maxSize) { + m_lru = new Object[maxSize]; + m_places = new boolean[maxSize]; + m_index = 0; + for (int i = 0; i < maxSize; i++) + m_places[i] = false; + } + + boolean contains(K key) { + return m_hashTable.containsKey(key); + } + + T get(K key) { + return m_hashTable.get(key); + } + + void add(K key, T value) { + if (m_places[m_index]) {// remove existing element from the cache + m_places[m_index] = false; + m_hashTable.remove(m_lru[m_index]); + } + + m_hashTable.put(key, value); + m_lru[m_index] = key; + m_places[m_index] = true; + m_index = (m_index + 1) % m_lru.length; + } + +} diff --git a/src/com/esri/core/geometry/Operator.java b/src/com/esri/core/geometry/Operator.java new file mode 100644 index 00000000..2526c1a1 --- /dev/null +++ b/src/com/esri/core/geometry/Operator.java @@ -0,0 +1,105 @@ +/* + Copyright 1995-2013 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ +package com.esri.core.geometry; + +import com.esri.core.geometry.Geometry.GeometryAccelerationDegree; + +/** + * The base class for Geometry Operators. + */ +public abstract class Operator { + /** + * The operator type enum. + */ + public enum Type { + Project, // = 10300,// m_cuts; + + OperatorCutCursor(boolean bConsiderTouch, Geometry cuttee, Polyline cutter, + SpatialReference spatialReference, ProgressTracker progressTracker) { + if (cuttee == null) + throw new GeometryException("invalid argument"); + + m_bConsiderTouch = bConsiderTouch; + m_cuttee = cuttee; + m_cutter = cutter; + m_tolerance = spatialReference != null ? spatialReference + .getTolerance(Semantics.POSITION) : InternalUtils + .calculateToleranceFromGeometry(null, cuttee, false); + if (m_tolerance > 0.001) + m_tolerance = 0.001; + m_cutIndex = -1; + m_cuts = null; + } + + @Override + public int getGeometryID() { + return 0; + } + + @Override + public Geometry next() { + if (m_cuts == null) { + int type = m_cuttee.getType().value(); + switch (type) { + case Geometry.GeometryType.Polyline: + m_cuts = _cutPolyline(); + break; + + case Geometry.GeometryType.Polygon: + m_cuts = _cutPolygon(); + break; + } + } + + if (++m_cutIndex < m_cuts.size()) + return (Geometry) m_cuts.get(m_cutIndex); + + return null; + } + + private ArrayList _cutPolyline() { + MultiPath left = new Polyline(); + MultiPath right = new Polyline(); + MultiPath uncut = new Polyline(); + + ArrayList cuts = new ArrayList(2); + cuts.add(left); + cuts.add(right); + + ArrayList cutPairs = new ArrayList( + 0); + Cutter.CutPolyline(m_bConsiderTouch, (Polyline) m_cuttee, m_cutter, + m_tolerance, cutPairs, null); + + for (int icut = 0; icut < cutPairs.size(); icut++) { + OperatorCutLocal.CutPair cutPair = cutPairs.get(icut); + if (cutPair.m_side == Side.Left) { + left.add((MultiPath) cutPair.m_geometry, false); + } else if (cutPair.m_side == Side.Right + || cutPair.m_side == Side.Coincident) { + right.add((MultiPath) cutPair.m_geometry, false); + } else if (cutPair.m_side == Side.Undefined) { + cuts.add((MultiPath) cutPair.m_geometry); + } else { + uncut.add((MultiPath) cutPair.m_geometry, false); + } + } + + if (!uncut.isEmpty() + && (!left.isEmpty() || !right.isEmpty() || cuts.size() >= 3)) + cuts.add(uncut); + + return cuts; + } + + ArrayList _cutPolygon() { + AttributeStreamOfInt32 cutHandles = new AttributeStreamOfInt32(0); + EditShape shape = new EditShape(); + int sideIndex = shape.createGeometryUserIndex(); + int cutteeHandle = shape.addGeometry(m_cuttee); + int cutterHandle = shape.addGeometry(m_cutter); + TopologicalOperations topoOp = new TopologicalOperations(); + topoOp.setEditShapeCrackAndCluster(shape, m_tolerance, null); + topoOp.cut(sideIndex, cutteeHandle, cutterHandle, cutHandles); + Polygon cutteeRemainder = (Polygon) shape.getGeometry(cutteeHandle); + + MultiPath left = new Polygon(); + MultiPath right = new Polygon(); + + ArrayList cuts = new ArrayList(2); + cuts.add(left); + cuts.add(right); + + for (int icutIndex = 0; icutIndex < cutHandles.size(); icutIndex++) { + Geometry cutGeometry; + { + // intersection + EditShape shapeIntersect = new EditShape(); + int geometryA = shapeIntersect.addGeometry(cutteeRemainder); + int geometryB = shapeIntersect.addGeometry(shape + .getGeometry(cutHandles.get(icutIndex))); + topoOp.setEditShape(shapeIntersect); + int intersectHandle = topoOp.intersection(geometryA, geometryB); + cutGeometry = shapeIntersect.getGeometry(intersectHandle); + + if (cutGeometry.isEmpty()) + continue; + + int side = shape.getGeometryUserIndex( + cutHandles.get(icutIndex), sideIndex); + if (side == 2) + left.add((MultiPath) cutGeometry, false); + else if (side == 1) + right.add((MultiPath) cutGeometry, false); + else + cuts.add((MultiPath) cutGeometry); // Undefined + } + + { + // difference + EditShape shapeDifference = new EditShape(); + int geometryA = shapeDifference.addGeometry(cutteeRemainder); + int geometryB = shapeDifference.addGeometry(shape + .getGeometry(cutHandles.get(icutIndex))); + topoOp.setEditShape(shapeDifference); + cutteeRemainder = (Polygon) shapeDifference.getGeometry(topoOp + .difference(geometryA, geometryB)); + } + } + + if (!cutteeRemainder.isEmpty() && cutHandles.size() > 0) + cuts.add((MultiPath) cutteeRemainder); + + return cuts; + } + +} diff --git a/src/com/esri/core/geometry/OperatorCutLocal.java b/src/com/esri/core/geometry/OperatorCutLocal.java new file mode 100644 index 00000000..98613301 --- /dev/null +++ b/src/com/esri/core/geometry/OperatorCutLocal.java @@ -0,0 +1,84 @@ +/* + Copyright 1995-2013 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ + +package com.esri.core.geometry; + +class OperatorCutLocal extends OperatorCut { + public static interface Side { + public static final int Left = 0; + public static final int Right = 1; + public static final int Coincident = 2; + public static final int Undefined = 3; + public static final int Uncut = 4; + } + + public static class CutPair { + public CutPair(Geometry geometry, int side, int ipartCuttee, + int ivertexCuttee, double scalarCuttee, int sidePrev, + int ipartCutteePrev, int ivertexCutteePrev, + double scalarCutteePrev, int ipartCutter, int ivertexCutter, + double scalarCutter, int ipartCutterPrev, + int ivertexCutterPrev, double scalarCutterPrev) { + m_geometry = geometry; + m_side = side; + m_ipartCuttee = ipartCuttee; + m_ivertexCuttee = ivertexCuttee; + m_scalarCuttee = scalarCuttee; + m_sidePrev = sidePrev; + m_ipartCutteePrev = ipartCutteePrev; + m_ivertexCutteePrev = ivertexCutteePrev; + m_scalarCutteePrev = scalarCutteePrev; + m_ipartCutter = ipartCutter; + m_ivertexCutter = ivertexCutter; + m_scalarCutter = scalarCutter; + m_ipartCutterPrev = ipartCutterPrev; + m_ivertexCutterPrev = ivertexCutterPrev; + m_scalarCutterPrev = scalarCutterPrev; + } + + Geometry m_geometry; + int m_side; + int m_ipartCuttee; + int m_ivertexCuttee; + double m_scalarCuttee; + int m_sidePrev; + int m_ipartCutteePrev; + int m_ivertexCutteePrev; + double m_scalarCutteePrev; + int m_ipartCutter; + int m_ivertexCutter; + double m_scalarCutter; + int m_ipartCutterPrev; + int m_ivertexCutterPrev; + double m_scalarCutterPrev; + }; + + @Override + public GeometryCursor execute(boolean bConsiderTouch, Geometry cuttee, + Polyline cutter, SpatialReference spatialReference, + ProgressTracker progressTracker) { + return new OperatorCutCursor(bConsiderTouch, cuttee, cutter, + spatialReference, progressTracker); + } +} diff --git a/src/com/esri/core/geometry/OperatorDensifyByLength.java b/src/com/esri/core/geometry/OperatorDensifyByLength.java new file mode 100644 index 00000000..c01ec80d --- /dev/null +++ b/src/com/esri/core/geometry/OperatorDensifyByLength.java @@ -0,0 +1,80 @@ +/* + Copyright 1995-2013 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ + +package com.esri.core.geometry; + +import com.esri.core.geometry.Operator.Type; + +/** + * Densifies MultiPath geometries by length so that no segments are longer than + * given threshold value. + */ +public abstract class OperatorDensifyByLength extends Operator { + @Override + public Type getType() { + return Type.DensifyByLength; + } + + /** + * Performs the Densify operation on the geometry set. + * + * @param inputGeometries + * The geometries to be densified. + * @param maxLength + * The maximum segment length allowed. Must be a positive value. + * Curves are densified to straight segments using the + * maxSegmentLength. Curves are split into shorter subcurves such + * that the length of subcurves is shorter than maxSegmentLength. + * After that the curves are replaced with straight segments. + * @param progressTracker + * @return Returns the densified geometries (It does nothing to geometries + * with dim < 1, but simply passes them along). + */ + public abstract GeometryCursor execute(GeometryCursor inputGeometries, + double maxLength, ProgressTracker progressTracker); + + /** + * Performs the Densify operation on the geometry set. + * + * @param inputGeometry + * The geometry to be densified. + * @param maxLength + * The maximum segment length allowed. Must be a positive value. + * Curves are densified to straight segments using the + * maxSegmentLength. Curves are split into shorter subcurves such + * that the length of subcurves is shorter than maxSegmentLength. + * After that the curves are replaced with straight segments. + * @param progressTracker + * @return Returns the densified geometry. (It does nothing to geometries + * with dim < 1, but simply passes them along). + */ + public abstract Geometry execute(Geometry inputGeometry, double maxLength, + ProgressTracker progressTracker); + + public static OperatorDensifyByLength local() { + return (OperatorDensifyByLength) OperatorFactoryLocal.getInstance() + .getOperator(Type.DensifyByLength); + } + +} diff --git a/src/com/esri/core/geometry/OperatorDensifyByLengthCursor.java b/src/com/esri/core/geometry/OperatorDensifyByLengthCursor.java new file mode 100644 index 00000000..d876a60c --- /dev/null +++ b/src/com/esri/core/geometry/OperatorDensifyByLengthCursor.java @@ -0,0 +1,160 @@ +/* + Copyright 1995-2013 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ + +package com.esri.core.geometry; + +class OperatorDensifyByLengthCursor extends GeometryCursor { + + GeometryCursor m_inputGeoms; + // SpatialReferenceImpl m_spatialReference; + double m_maxLength; + int m_index; + + public OperatorDensifyByLengthCursor(GeometryCursor inputGeoms1, + double maxLength, ProgressTracker progressTracker) { + m_index = -1; + m_inputGeoms = inputGeoms1; + m_maxLength = maxLength; + } + + @Override + public int getGeometryID() { + return m_index; + } + + @Override + public Geometry next() { + Geometry geom; + if ((geom = m_inputGeoms.next()) != null) { + m_index = m_inputGeoms.getGeometryID(); + return densifyByLength(geom); + } + return null; + } + + private Geometry densifyByLength(Geometry geom) { + if (geom.isEmpty() || geom.getDimension() < 1) + return geom; + + int geometryType = geom.getType().value(); + + // TODO implement IsMultiPath and remove Polygon and Polyline call to + // match Native + // if (Geometry.IsMultiPath(geometryType)) + if (geometryType == Geometry.GeometryType.Polygon) + return densifyMultiPath((MultiPath) geom); + else if (Geometry.GeometryType.Polyline == geometryType) + return densifyMultiPath((MultiPath) geom); + else if (Geometry.isSegment(geometryType)) + return densifySegment((Segment) geom); + else if (geometryType == Geometry.GeometryType.Envelope) + return densifyEnvelope((Envelope) geom); + else + // TODO fix geometry exception to match native implementation + throw new GeometryException("internal error");// GEOMTHROW(internal_error); + + // unreachable in java + // return null; + } + + private Geometry densifySegment(Segment geom) { + double length = geom.calculateLength2D(); + if (length <= m_maxLength) + return (Geometry) geom; + + Polyline polyline = new Polyline(geom.getDescription()); + polyline.addSegment(geom, true); + return densifyMultiPath((MultiPath) polyline); + } + + private Geometry densifyEnvelope(Envelope geom) { + Polygon polygon = new Polygon(geom.getDescription()); + polygon.addEnvelope(geom, false); + + Envelope2D env2D = new Envelope2D(); + geom.queryEnvelope2D(env2D); + double w = env2D.getWidth(); + double h = env2D.getHeight(); + if (w <= m_maxLength && h <= m_maxLength) + return (Geometry) polygon; + + return densifyMultiPath((MultiPath) polygon); + } + + private Geometry densifyMultiPath(MultiPath geom) { + MultiPath densifiedPoly = (MultiPath) geom.createInstance(); + SegmentIterator iter = geom.querySegmentIterator(); + while (iter.nextPath()) { + boolean bStartNewPath = true; + while (iter.hasNextSegment()) { + Segment seg = iter.nextSegment(); + if (seg.getType().value() != Geometry.GeometryType.Line) + throw new GeometryException("not implemented"); + + boolean bIsClosing = iter.isClosingSegment(); + + double len = seg.calculateLength2D(); + if (len > m_maxLength) {// need to split + double dcount = Math.ceil(len / m_maxLength); + + Point point = new Point(geom.getDescription());// LOCALREFCLASS1(Point, + // VertexDescription, + // point, + // geom.getDescription()); + if (bStartNewPath) { + bStartNewPath = false; + seg.queryStart(point); + densifiedPoly.startPath(point); + } + double dt = 1.0 / dcount; + double t = dt; + for (int i = 0, n = (int) dcount - 1; i < n; i++) { + seg.queryCoord(t, point); + densifiedPoly.lineTo(point); + t += dt; + } + + if (!bIsClosing) { + seg.queryEnd(point); + densifiedPoly.lineTo(point); + } else { + densifiedPoly.closePathWithLine(); + } + + bStartNewPath = false; + } else { + if (!bIsClosing) + densifiedPoly.addSegment(seg, bStartNewPath); + else + densifiedPoly.closePathWithLine(); + + bStartNewPath = false; + } + } + } + + return (Geometry) densifiedPoly; + } + +} diff --git a/src/com/esri/core/geometry/OperatorDensifyByLengthLocal.java b/src/com/esri/core/geometry/OperatorDensifyByLengthLocal.java new file mode 100644 index 00000000..dbffbf4b --- /dev/null +++ b/src/com/esri/core/geometry/OperatorDensifyByLengthLocal.java @@ -0,0 +1,48 @@ +/* + Copyright 1995-2013 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ +package com.esri.core.geometry; + +class OperatorDensifyByLengthLocal extends OperatorDensifyByLength { + + @Override + public GeometryCursor execute(GeometryCursor inputGeometries, + double maxLength, ProgressTracker progressTracker) { + if (maxLength <= 0) + // TODO fix geometry exception to match native implementation + throw new IllegalArgumentException();// GEOMTHROW(invalid_argument); + + return new OperatorDensifyByLengthCursor(inputGeometries, maxLength, + progressTracker); + } + + @Override + public Geometry execute(Geometry inputGeometry, double maxLength, + ProgressTracker progressTracker) { + SimpleGeometryCursor inputCursor = new SimpleGeometryCursor( + inputGeometry); + GeometryCursor outputCursor = execute(inputCursor, maxLength, + progressTracker); + return outputCursor.next(); + } +} diff --git a/src/com/esri/core/geometry/OperatorDifference.java b/src/com/esri/core/geometry/OperatorDifference.java new file mode 100644 index 00000000..1dbfc9ec --- /dev/null +++ b/src/com/esri/core/geometry/OperatorDifference.java @@ -0,0 +1,75 @@ +/* + Copyright 1995-2013 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ + +package com.esri.core.geometry; + +import com.esri.core.geometry.Operator.Type; + +/** + * Difference of geometries. + */ +public abstract class OperatorDifference extends Operator { + + @Override + public Type getType() { + return Type.Difference; + } + + /** + * Performs the Topological Difference operation on the geometry set. + * + * @param inputGeometries + * is the set of Geometry instances to be subtracted by the + * subtractor + * @param subtractor + * is the Geometry being subtracted. + * @return Returns the result of the subtraction. + * + * The operator subtracts subtractor from every geometry in + * inputGeometries. + */ + public abstract GeometryCursor execute(GeometryCursor inputGeometries, + GeometryCursor subtractor, SpatialReference sr, + ProgressTracker progressTracker); + + /** + * Performs the Topological Difference operation on the two geometries. + * + * @param inputGeometry + * is the Geometry instance on the left hand side of the + * subtraction. + * @param subtractor + * is the Geometry on the right hand side being subtracted. + * @return Returns the result of subtraction. + */ + public abstract Geometry execute(Geometry inputGeometry, + Geometry subtractor, SpatialReference sr, + ProgressTracker progressTracker); + + public static OperatorDifference local() { + return (OperatorDifference) OperatorFactoryLocal.getInstance() + .getOperator(Type.Difference); + } + +} diff --git a/src/com/esri/core/geometry/OperatorDifferenceCursor.java b/src/com/esri/core/geometry/OperatorDifferenceCursor.java new file mode 100644 index 00000000..021d1512 --- /dev/null +++ b/src/com/esri/core/geometry/OperatorDifferenceCursor.java @@ -0,0 +1,66 @@ +/* + Copyright 1995-2013 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ + +package com.esri.core.geometry; + +class OperatorDifferenceCursor extends GeometryCursor { + + GeometryCursor m_inputGeoms; + ProgressTracker m_progress_tracker; + SpatialReference m_Spatial_reference; + Geometry m_geomSubtractor; + int m_index; + boolean m_bEmpty; + + OperatorDifferenceCursor(GeometryCursor inputGeoms, + GeometryCursor geomSubtractor, SpatialReference sr, + ProgressTracker progress_tracker) { + m_bEmpty = (geomSubtractor == null); + m_index = -1; + m_inputGeoms = inputGeoms; + m_Spatial_reference = sr; + m_geomSubtractor = geomSubtractor.next(); + m_progress_tracker = progress_tracker; + } + + @Override + public Geometry next() { + if (m_bEmpty) + return null; + + Geometry geom; + if ((geom = m_inputGeoms.next()) != null) { + m_index = m_inputGeoms.getGeometryID(); + return OperatorDifferenceLocal.difference(geom, m_geomSubtractor, + m_Spatial_reference, m_progress_tracker); + } + return null; + } + + @Override + public int getGeometryID() { + return m_index; + } + +} diff --git a/src/com/esri/core/geometry/OperatorDifferenceLocal.java b/src/com/esri/core/geometry/OperatorDifferenceLocal.java new file mode 100644 index 00000000..2121729b --- /dev/null +++ b/src/com/esri/core/geometry/OperatorDifferenceLocal.java @@ -0,0 +1,392 @@ +/* + Copyright 1995-2013 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ + +package com.esri.core.geometry; + +class OperatorDifferenceLocal extends OperatorDifference { + + @Override + public GeometryCursor execute(GeometryCursor inputGeometries, + GeometryCursor subtractor, SpatialReference sr, + ProgressTracker progressTracker) { + return new OperatorDifferenceCursor(inputGeometries, subtractor, sr, + progressTracker); + } + + @Override + public Geometry execute(Geometry inputGeometry, Geometry subtractor, + SpatialReference sr, ProgressTracker progressTracker) { + SimpleGeometryCursor inputGeomCurs = new SimpleGeometryCursor( + inputGeometry); + SimpleGeometryCursor subractorCurs = new SimpleGeometryCursor( + subtractor); + GeometryCursor geometryCursor = execute(inputGeomCurs, subractorCurs, + sr, progressTracker); + + return geometryCursor.next(); + } + + static Geometry difference(Geometry geometry_a, Geometry geometry_b, + SpatialReference spatial_reference, ProgressTracker progress_tracker) { + if (geometry_a.isEmpty() || geometry_b.isEmpty()) + return geometry_a; + + int dimension_a = geometry_a.getDimension(); + int dimension_b = geometry_b.getDimension(); + + if (dimension_a > dimension_b) + return geometry_a; + + int type_a = geometry_a.getType().value(); + int type_b = geometry_b.getType().value(); + + Envelope2D env_a = new Envelope2D(), env_b = new Envelope2D(), env_merged = new Envelope2D(); + geometry_a.queryEnvelope2D(env_a); + geometry_b.queryEnvelope2D(env_b); + env_merged.setCoords(env_a); + env_merged.merge(env_b); + + double tolerance = InternalUtils.calculateToleranceFromGeometry( + spatial_reference, env_merged, false); + double tolerance_cluster = tolerance * Math.sqrt(2.0) * 1.00001; + + Envelope2D env_a_inflated = new Envelope2D(); + env_a_inflated.setCoords(env_a); + env_a_inflated.inflate(tolerance_cluster, tolerance_cluster); // inflate + // by + // cluster + // tolerance + + if (!env_a_inflated.isIntersecting(env_b)) + return geometry_a; + + if (dimension_a == 1 && dimension_b == 2) + return polylineMinusArea_(geometry_a, geometry_b, type_b, + spatial_reference, progress_tracker); + + if (type_a == Geometry.GeometryType.Point) { + Geometry geometry_b_; + if (MultiPath.isSegment(type_b)) { + geometry_b_ = new Polyline(geometry_b.getDescription()); + ((Polyline) (geometry_b_)).addSegment((Segment) (geometry_b), + true); + } else { + geometry_b_ = geometry_b; + } + switch (type_b) { + case Geometry.GeometryType.Polygon: + return pointMinusPolygon_((Point) (geometry_a), + (Polygon) (geometry_b_), tolerance, progress_tracker); + case Geometry.GeometryType.Polyline: + return pointMinusPolyline_((Point) (geometry_a), + (Polyline) (geometry_b_), tolerance, progress_tracker); + case Geometry.GeometryType.MultiPoint: + return pointMinusMultiPoint_((Point) (geometry_a), + (MultiPoint) (geometry_b_), tolerance, progress_tracker); + case Geometry.GeometryType.Envelope: + return pointMinusEnvelope_((Point) (geometry_a), + (Envelope) (geometry_b_), tolerance, progress_tracker); + case Geometry.GeometryType.Point: + return pointMinusPoint_((Point) (geometry_a), + (Point) (geometry_b_), tolerance, progress_tracker); + default: + throw new IllegalArgumentException(); + } + } else if (type_a == Geometry.GeometryType.MultiPoint) { + switch (type_b) { + case Geometry.GeometryType.Polygon: + return multiPointMinusPolygon_((MultiPoint) (geometry_a), + (Polygon) (geometry_b), tolerance, progress_tracker); + case Geometry.GeometryType.Envelope: + return multiPointMinusEnvelope_((MultiPoint) (geometry_a), + (Envelope) (geometry_b), tolerance, progress_tracker); + case Geometry.GeometryType.Point: + return multiPointMinusPoint_((MultiPoint) (geometry_a), + (Point) (geometry_b), tolerance, progress_tracker); + default: + break; + } + } + return TopologicalOperations.difference(geometry_a, geometry_b, + spatial_reference, progress_tracker); + } + + // these are special implementations, all others delegate to the topo-graph. + static Geometry pointMinusPolygon_(Point point, Polygon polygon, + double tolerance, ProgressTracker progress_tracker) { + PolygonUtils.PiPResult result = PolygonUtils.isPointInPolygon2D( + polygon, point, tolerance); + + if (result == PolygonUtils.PiPResult.PiPOutside) + return point; + + return point.createInstance(); + } + + static Geometry pointMinusPolyline_(Point point, Polyline polyline, + double tolerance, ProgressTracker progress_tracker) { + Point2D pt = point.getXY(); + SegmentIterator seg_iter = polyline.querySegmentIterator(); + + double tolerance_cluster = tolerance * Math.sqrt(2.0) * 1.00001; + double tolerance_cluster_sq = tolerance_cluster * tolerance_cluster; + Envelope2D env = new Envelope2D(); + + while (seg_iter.nextPath()) { + while (seg_iter.hasNextSegment()) { + Segment segment = seg_iter.nextSegment(); + + segment.queryEnvelope2D(env); + env.inflate(tolerance_cluster, tolerance_cluster); + + if (!env.contains(pt)) + continue; + + if (segment.isIntersecting(pt, tolerance)) + return point.createInstance(); + + // check segment end points to the cluster tolerance + Point2D end_point = segment.getStartXY(); + + if (Point2D.sqrDistance(pt, end_point) <= tolerance_cluster_sq) + return point.createInstance(); + + end_point = segment.getEndXY(); + + if (Point2D.sqrDistance(pt, end_point) <= tolerance_cluster_sq) + return point.createInstance(); + } + } + + return point; + } + + static Geometry pointMinusMultiPoint_(Point point, MultiPoint multi_point, + double tolerance, ProgressTracker progress_tracker) { + MultiPointImpl multipointImpl = (MultiPointImpl) (multi_point + ._getImpl()); + AttributeStreamOfDbl position = (AttributeStreamOfDbl) multipointImpl + .getAttributeStreamRef(VertexDescription.Semantics.POSITION); + int point_count = multi_point.getPointCount(); + Point2D point2D = point.getXY(); + Point2D pt = new Point2D(); + + double tolerance_cluster = tolerance * Math.sqrt(2.0) * 1.00001; + double tolerance_cluster_sq = tolerance_cluster * tolerance_cluster; + + for (int i = 0; i < point_count; i++) { + position.read(2 * i, pt); + double sqr_dist = Point2D.sqrDistance(pt, point2D); + if (sqr_dist <= tolerance_cluster_sq) + return point.createInstance();// return an empty point. + } + + return point;// return the input point + } + + static Geometry pointMinusEnvelope_(Point point, Envelope envelope, + double tolerance, ProgressTracker progress_tracker) { + Envelope2D env = new Envelope2D(); + envelope.queryEnvelope2D(env); + env.inflate(tolerance, tolerance); + + Point2D pt = point.getXY(); + + if (!env.contains(pt)) + return point; + + return point.createInstance(); + } + + static Geometry pointMinusPoint_(Point point_a, Point point_b, + double tolerance, ProgressTracker progress_tracker) { + double tolerance_cluster = tolerance * Math.sqrt(2.0) * 1.00001; + double tolerance_cluster_sq = tolerance_cluster * tolerance_cluster; + + Point2D pt_a = point_a.getXY(); + Point2D pt_b = point_b.getXY(); + + if (Point2D.sqrDistance(pt_a, pt_b) <= tolerance_cluster_sq) + return point_a.createInstance(); // return empty point + + return point_a; + } + + static Geometry multiPointMinusPolygon_(MultiPoint multi_point, + Polygon polygon, double tolerance, ProgressTracker progress_tracker) { + Envelope2D env = new Envelope2D(); + polygon.queryEnvelope2D(env); + env.inflate(tolerance, tolerance); + + int point_count = multi_point.getPointCount(); + + boolean b_found_covered = false; + boolean[] covered = new boolean[point_count]; + for (int i = 0; i < point_count; i++) + covered[i] = false; + + Point2D pt = new Point2D(); + + for (int i = 0; i < point_count; i++) { + multi_point.getXY(i, pt); + + if (!env.contains(pt)) + continue; + + PolygonUtils.PiPResult result = PolygonUtils.isPointInPolygon2D( + polygon, pt, tolerance); + + if (result == PolygonUtils.PiPResult.PiPOutside) + continue; + + b_found_covered = true; + covered[i] = true; + } + + if (!b_found_covered) + return multi_point; + + MultiPoint new_multipoint = (MultiPoint) multi_point.createInstance(); + + for (int i = 0; i < point_count; i++) { + if (!covered[i]) + new_multipoint.add(multi_point, i, i + 1); + } + + return new_multipoint; + } + + static Geometry multiPointMinusEnvelope_(MultiPoint multi_point, + Envelope envelope, double tolerance, + ProgressTracker progress_tracker) { + Envelope2D env = new Envelope2D(); + envelope.queryEnvelope2D(env); + env.inflate(tolerance, tolerance); + + int point_count = multi_point.getPointCount(); + + boolean b_found_covered = false; + boolean[] covered = new boolean[point_count]; + for (int i = 0; i < point_count; i++) + covered[i] = false; + + Point2D pt = new Point2D(); + + for (int i = 0; i < point_count; i++) { + multi_point.getXY(i, pt); + + if (!env.contains(pt)) + continue; + + b_found_covered = true; + covered[i] = true; + } + + if (!b_found_covered) + return multi_point; + + MultiPoint new_multipoint = (MultiPoint) multi_point.createInstance(); + + for (int i = 0; i < point_count; i++) { + if (!covered[i]) + new_multipoint.add(multi_point, i, i + 1); + } + + return new_multipoint; + } + + static Geometry multiPointMinusPoint_(MultiPoint multi_point, Point point, + double tolerance, ProgressTracker progress_tracker) { + MultiPointImpl multipointImpl = (MultiPointImpl) (multi_point + ._getImpl()); + AttributeStreamOfDbl position = (AttributeStreamOfDbl) (multipointImpl + .getAttributeStreamRef(VertexDescription.Semantics.POSITION)); + int point_count = multi_point.getPointCount(); + Point2D point2D = point.getXY(); + Point2D pt = new Point2D(); + + boolean b_found_covered = false; + boolean[] covered = new boolean[point_count]; + for (int i = 0; i < point_count; i++) + covered[i] = false; + + double tolerance_cluster = tolerance * Math.sqrt(2.0) * 1.00001; + double tolerance_cluster_sq = tolerance_cluster * tolerance_cluster; + + for (int i = 0; i < point_count; i++) { + position.read(2 * i, pt); + + double sqr_dist = Point2D.sqrDistance(pt, point2D); + + if (sqr_dist <= tolerance_cluster_sq) { + b_found_covered = true; + covered[i] = true; + } + } + + if (!b_found_covered) + return multi_point; + + MultiPoint new_multipoint = (MultiPoint) (multi_point.createInstance()); + + for (int i = 0; i < point_count; i++) { + if (!covered[i]) + new_multipoint.add(multi_point, i, i + 1); + } + + return new_multipoint; + } + + static Geometry polylineMinusArea_(Geometry geometry, Geometry area, + int area_type, SpatialReference sr, ProgressTracker progress_tracker) { + // construct the complement of the Polygon (or Envelope) + Envelope envelope = new Envelope(); + geometry.queryEnvelope(envelope); + Envelope2D env_2D = new Envelope2D(); + area.queryEnvelope2D(env_2D); + envelope.merge(env_2D); + double dw = 0.1 * envelope.getWidth(); + double dh = 0.1 * envelope.getHeight(); + envelope.inflate(dw, dh); + + Polygon complement = new Polygon(); + complement.addEnvelope(envelope, false); + + MultiPathImpl complementImpl = (MultiPathImpl) (complement._getImpl()); + + if (area_type == Geometry.GeometryType.Polygon) { + MultiPathImpl polygonImpl = (MultiPathImpl) (area._getImpl()); + complementImpl.add(polygonImpl, true); + } else + complementImpl.addEnvelope((Envelope) (area), true); + + OperatorFactoryLocal projEnv = OperatorFactoryLocal.getInstance(); + OperatorIntersection operatorIntersection = (OperatorIntersection) projEnv + .getOperator(Operator.Type.Intersection); + Geometry difference = operatorIntersection.execute(geometry, + complement, sr, progress_tracker); + return difference; + } + +} diff --git a/src/com/esri/core/geometry/OperatorDisjoint.java b/src/com/esri/core/geometry/OperatorDisjoint.java new file mode 100644 index 00000000..9892661b --- /dev/null +++ b/src/com/esri/core/geometry/OperatorDisjoint.java @@ -0,0 +1,44 @@ +/* + Copyright 1995-2013 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ + +package com.esri.core.geometry; + +import com.esri.core.geometry.Operator.Type; + +/** + * Relational operation Disjoint. + */ +public abstract class OperatorDisjoint extends OperatorSimpleRelation { + + @Override + public Type getType() { + return Type.Disjoint; + } + + public static OperatorDisjoint local() { + return (OperatorDisjoint) OperatorFactoryLocal.getInstance() + .getOperator(Type.Disjoint); + } + +} diff --git a/src/com/esri/core/geometry/OperatorDisjointLocal.java b/src/com/esri/core/geometry/OperatorDisjointLocal.java new file mode 100644 index 00000000..e65c0b3e --- /dev/null +++ b/src/com/esri/core/geometry/OperatorDisjointLocal.java @@ -0,0 +1,35 @@ +/* + Copyright 1995-2013 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ + +package com.esri.core.geometry; + +class OperatorDisjointLocal extends OperatorDisjoint { + @Override + public boolean execute(Geometry inputGeom1, Geometry inputGeom2, + SpatialReference sr, ProgressTracker progressTracker) { + return RelationalOperations.relate(inputGeom1, inputGeom2, sr, + RelationalOperations.Relation.disjoint, progressTracker); + } + +} diff --git a/src/com/esri/core/geometry/OperatorDistance.java b/src/com/esri/core/geometry/OperatorDistance.java new file mode 100644 index 00000000..206bbf93 --- /dev/null +++ b/src/com/esri/core/geometry/OperatorDistance.java @@ -0,0 +1,50 @@ +/* + Copyright 1995-2013 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ + +package com.esri.core.geometry; + +import com.esri.core.geometry.Operator.Type; + +/** + * Calculates distance between geometries. + */ +public abstract class OperatorDistance extends Operator { + + @Override + public Type getType() { + return Type.Distance; + } + + /** + * Calculates distance between two geometries. + */ + public abstract double execute(Geometry geom1, Geometry geom2, + ProgressTracker progressTracker); + + public static OperatorDistance local() { + return (OperatorDistance) OperatorFactoryLocal.getInstance() + .getOperator(Type.Distance); + } + +} diff --git a/src/com/esri/core/geometry/OperatorDistanceLocal.java b/src/com/esri/core/geometry/OperatorDistanceLocal.java new file mode 100644 index 00000000..12bc7c48 --- /dev/null +++ b/src/com/esri/core/geometry/OperatorDistanceLocal.java @@ -0,0 +1,414 @@ +/* + Copyright 1995-2013 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ + +package com.esri.core.geometry; + +class OperatorDistanceLocal extends OperatorDistance { + + /** + * Performs the Distance operation on two geometries + * + * @return Returns a double. + */ + @Override + public double execute(Geometry geom1, Geometry geom2, + ProgressTracker progressTracker) { + if (null == geom1 || null == geom2) { + throw new IllegalArgumentException(); + } + + Geometry geometryA = geom1; + Geometry geometryB = geom2; + + Polygon polygonA; + Polygon polygonB; + MultiPoint multiPointA; + MultiPoint multiPointB; + + // if geometryA is an envelope use a polygon instead (if geom1 was + // folded, then geometryA will already be a polygon) + // if geometryA is a point use a multipoint instead + if (geometryA.getType().equals(Geometry.Type.Point)) { + multiPointA = new MultiPoint(); + multiPointA.add((Point) geometryA); + geometryA = multiPointA; + } else if (geometryA.getType().equals(Geometry.Type.Envelope)) { + polygonA = new Polygon(); + polygonA.addEnvelope((Envelope) geometryA, false); + geometryB = polygonA; + } + + // if geom_2 is an envelope use a polygon instead + // if geom_2 is a point use a multipoint instead + if (geometryB.getType().equals(Geometry.Type.Point)) { + multiPointB = new MultiPoint(); + multiPointB.add((Point) geometryB); + geometryB = multiPointB; + } else if (geometryB.getType().equals(Geometry.Type.Envelope)) { + polygonB = new Polygon(); + polygonB.addEnvelope((Envelope) geometryB, false); + geometryB = polygonB; + } + + DistanceCalculator distanceCalculator = new DistanceCalculator( + progressTracker); + double distance = distanceCalculator.calculate(geometryA, geometryB); + return distance; + } + + // Implementation of distance algorithm. + class DistanceCalculator { + private ProgressTracker m_progressTracker; + private Envelope2D m_env2DgeometryA; + private Envelope2D m_env2DgeometryB; + + private void swapEnvelopes_() { + double temp; + // swap xmin + temp = m_env2DgeometryA.xmin; + m_env2DgeometryA.xmin = m_env2DgeometryB.xmin; + m_env2DgeometryB.xmin = temp; + // swap xmax + temp = m_env2DgeometryA.xmax; + m_env2DgeometryA.xmax = m_env2DgeometryB.xmax; + m_env2DgeometryB.xmax = temp; + // swap ymin + temp = m_env2DgeometryA.ymin; + m_env2DgeometryA.ymin = m_env2DgeometryB.ymin; + m_env2DgeometryB.ymin = temp; + // swap ymax + temp = m_env2DgeometryA.ymax; + m_env2DgeometryA.ymax = m_env2DgeometryB.ymax; + m_env2DgeometryB.ymax = temp; + } + + private double executeBruteForce_(/* const */Geometry geometryA, /* const */ + Geometry geometryB) { + if ((m_progressTracker != null) + && !(m_progressTracker.progress(-1, -1))) + throw new RuntimeException("user_canceled"); + + boolean geometriesAreDisjoint = !m_env2DgeometryA + .isIntersecting(m_env2DgeometryB); + if (Geometry.isMultiPath(geometryA.getType().value()) + && Geometry.isMultiPath(geometryB.getType().value())) { // MultiPath + // vs. + // MultiPath + // choose + // the + // multipath + // with + // more + // points + // to + // be + // geometryA, + // this + // way + // more + // of + // geometryA + // segments + // can + // be + // disqualified + // more + // quickly + // by + // testing + // segmentA + // envelope + // vs. + // geometryB + // envelope + if (((MultiPath) geometryA).getPointCount() > ((MultiPath) geometryB) + .getPointCount()) + return bruteForceMultiPathMultiPath_((MultiPath) geometryA, + (MultiPath) geometryB, geometriesAreDisjoint); + swapEnvelopes_(); + double answer = bruteForceMultiPathMultiPath_( + (MultiPath) geometryB, (MultiPath) geometryA, + geometriesAreDisjoint); + swapEnvelopes_(); + return answer; + } else if (geometryA.getType() == Geometry.Type.MultiPoint + && Geometry.isMultiPath(geometryB.getType().value())) { // MultiPoint + // vs. + // MultiPath + swapEnvelopes_(); + double answer = bruteForceMultiPathMultiPoint_( + (MultiPath) geometryB, (MultiPoint) geometryA, + geometriesAreDisjoint); + swapEnvelopes_(); + return answer; + } else if (geometryB.getType() == Geometry.Type.MultiPoint + && Geometry.isMultiPath(geometryA.getType().value())) { // MultiPath + // vs. + // MultiPoint + return bruteForceMultiPathMultiPoint_((MultiPath) geometryA, + (MultiPoint) geometryB, geometriesAreDisjoint); + } else if (geometryA.getType() == Geometry.Type.MultiPoint + && geometryB.getType() == Geometry.Type.MultiPoint) { // MultiPoint + // vs. + // MultiPoint + // choose + // the + // multipoint + // with + // more + // vertices + // to + // be + // the + // "geometryA", + // this + // way + // more + // points + // can + // be + // potentially + // excluded + // by + // envelope + // distance + // tests. + if (((MultiPoint) geometryA).getPointCount() > ((MultiPoint) geometryB) + .getPointCount()) + return bruteForceMultiPointMultiPoint_( + (MultiPoint) geometryA, (MultiPoint) geometryB, + geometriesAreDisjoint); + swapEnvelopes_(); + double answer = bruteForceMultiPointMultiPoint_( + (MultiPoint) geometryB, (MultiPoint) geometryA, + geometriesAreDisjoint); + swapEnvelopes_(); + return answer; + } + return 0.0; + } + + private double bruteForceMultiPathMultiPath_( + /* const */MultiPath geometryA, /* const */MultiPath geometryB, + boolean geometriesAreDisjoint) { + // It may be beneficial to have the geometry with less vertices + // always be geometryA. + SegmentIterator segIterA = geometryA.querySegmentIterator(); + SegmentIterator segIterB = geometryB.querySegmentIterator(); + Envelope2D env2DSegmentA = new Envelope2D(); + Envelope2D env2DSegmentB = new Envelope2D(); + + double minSqrDistance = NumberUtils.doubleMax(); + + if (!geometriesAreDisjoint) { + // Geometries might be non-disjoint. Check if they intersect + // using point-in-polygon tests + if (weakIntersectionTest_(geometryA, geometryB, segIterA, + segIterB)) + return 0.0; + } + + // if geometries are known disjoint, don't bother to do any tests + // for polygon containment + + // nested while-loop insanity + while (segIterA.nextPath()) { + while (segIterA.hasNextSegment()) { + /* const */Segment segmentA = segIterA.nextSegment(); + segmentA.queryEnvelope2D(env2DSegmentA); + if (env2DSegmentA.sqrDistance(m_env2DgeometryB) > minSqrDistance) + continue; + + while (segIterB.nextPath()) { + while (segIterB.hasNextSegment()) { + /* const */Segment segmentB = segIterB + .nextSegment(); + segmentB.queryEnvelope2D(env2DSegmentB); + if (env2DSegmentA.sqrDistance(env2DSegmentB) < minSqrDistance) { + // get distance between segments + double sqrDistance = segmentA.distance( + segmentB, geometriesAreDisjoint); + sqrDistance *= sqrDistance; + if (sqrDistance < minSqrDistance) { + if (sqrDistance == 0.0) + return 0.0; + + minSqrDistance = sqrDistance; + } + } + } + } + segIterB.resetToFirstPath(); + } + } + + return Math.sqrt(minSqrDistance); + } + + private double bruteForceMultiPathMultiPoint_( + /* const */MultiPath geometryA, /* const */ + MultiPoint geometryB, boolean geometriesAreDisjoint) { + SegmentIterator segIterA = geometryA.querySegmentIterator(); + + Envelope2D env2DSegmentA = new Envelope2D(); + + double minSqrDistance = NumberUtils.doubleMax(); + + Point2D inputPoint = new Point2D(); + double t = -1; + double sqrDistance = minSqrDistance; + /* const */MultiPointImpl multiPointImplB = (MultiPointImpl) geometryB + ._getImpl(); + int pointCountB = multiPointImplB.getPointCount(); + boolean bDoPiPTest = !geometriesAreDisjoint + && (geometryA.getType() == Geometry.Type.Polygon); + + while (segIterA.nextPath()) { + while (segIterA.hasNextSegment()) { + /* const */Segment segmentA = segIterA.nextSegment(); + segmentA.queryEnvelope2D(env2DSegmentA); + // if multipointB has only 1 vertex then it is faster to not + // test for + // env2DSegmentA.distance(env2DgeometryB) + if (pointCountB > 1 + && env2DSegmentA.sqrDistance(m_env2DgeometryB) > minSqrDistance) + continue; + + for (int i = 0; i < pointCountB; i++) { + multiPointImplB.getXY(i, inputPoint); + if (bDoPiPTest) { + // Test for polygon containment. This takes the + // place of a more general intersection test at the + // beginning of the operator + if (PolygonUtils.isPointInPolygon2D( + (Polygon) geometryA, inputPoint, 0) != PolygonUtils.PiPResult.PiPOutside) + return 0.0; + } + + t = segmentA.getClosestCoordinate(inputPoint, false); + inputPoint.sub(segmentA.getCoord2D(t)); + sqrDistance = inputPoint.sqrLength(); + if (sqrDistance < minSqrDistance) { + if (sqrDistance == 0.0) + return 0.0; + + minSqrDistance = sqrDistance; + } + } + + // No need to do point-in-polygon anymore (if it is a + // polygon vs polyline) + bDoPiPTest = false; + } + } + return Math.sqrt(minSqrDistance); + } + + private double bruteForceMultiPointMultiPoint_( + /* const */MultiPoint geometryA, /* const */ + MultiPoint geometryB, boolean geometriesAreDisjoint) { + double minSqrDistance = NumberUtils.doubleMax(); + + Point2D pointA = new Point2D(); + Point2D pointB = new Point2D(); + + double sqrDistance = minSqrDistance; + /* const */MultiPointImpl multiPointImplA = (/* const */MultiPointImpl) geometryA + ._getImpl(); + /* const */MultiPointImpl multiPointImplB = (/* const */MultiPointImpl) geometryB + ._getImpl(); + int pointCountA = multiPointImplA.getPointCount(); + int pointCountB = multiPointImplB.getPointCount(); + for (int i = 0; i < pointCountA; i++) { + multiPointImplA.getXY(i, pointA); + + if (pointCountB > 1 + && m_env2DgeometryB.sqrDistance(pointA) > minSqrDistance) + continue; + + for (int j = 0; j < pointCountB; j++) { + multiPointImplB.getXY(j, pointB); + sqrDistance = Point2D.sqrDistance(pointA, pointB); + if (sqrDistance < minSqrDistance) { + if (sqrDistance == 0.0) + return 0.0; + + minSqrDistance = sqrDistance; + } + } + } + + return Math.sqrt(minSqrDistance); + } + + // resets Iterators if they are used. + private boolean weakIntersectionTest_(/* const */Geometry geometryA, /* const */ + Geometry geometryB, SegmentIterator segIterA, SegmentIterator segIterB) { + if (geometryA.getType() == Geometry.Type.Polygon) { + // test PolygonA vs. first segment of each of geometryB's paths + while (segIterB.nextPath()) { + if (segIterB.hasNextSegment()) { + /* const */Segment segmentB = segIterB.nextSegment(); + if (PolygonUtils.isPointInPolygon2D( + (Polygon) geometryA, segmentB.getEndXY(), 0) != PolygonUtils.PiPResult.PiPOutside) + return true; + } + } + segIterB.resetToFirstPath(); + } + + if (geometryB.getType() == Geometry.Type.Polygon) { + // test PolygonB vs. first segment of each of geometryA's paths + while (segIterA.nextPath()) { + if (segIterA.hasNextSegment()) { + /* const */Segment segmentA = segIterA.nextSegment(); + if (PolygonUtils.isPointInPolygon2D( + (Polygon) geometryB, segmentA.getEndXY(), 0) != PolygonUtils.PiPResult.PiPOutside) + return true; + } + } + segIterA.resetToFirstPath(); + } + return false; + } + + DistanceCalculator(ProgressTracker progressTracker) { + m_progressTracker = progressTracker; + m_env2DgeometryA = new Envelope2D(); + m_env2DgeometryA.setEmpty(); + m_env2DgeometryB = new Envelope2D(); + m_env2DgeometryB.setEmpty(); + } + + double calculate(/* const */Geometry geometryA, /* const */ + Geometry geometryB) { + if (geometryA.isEmpty() || geometryB.isEmpty()) + return 0.0; + + geometryA.queryEnvelope2D(m_env2DgeometryA); + geometryB.queryEnvelope2D(m_env2DgeometryB); + return executeBruteForce_(geometryA, geometryB); + } + } +} diff --git a/src/com/esri/core/geometry/OperatorEquals.java b/src/com/esri/core/geometry/OperatorEquals.java new file mode 100644 index 00000000..7fb1d5b7 --- /dev/null +++ b/src/com/esri/core/geometry/OperatorEquals.java @@ -0,0 +1,43 @@ +/* + Copyright 1995-2013 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ + +package com.esri.core.geometry; + +import com.esri.core.geometry.Operator.Type; + +/** + * Relational operation Equals. + */ +public abstract class OperatorEquals extends OperatorSimpleRelation { + @Override + public Type getType() { + return Type.Equals; + } + + public static OperatorEquals local() { + return (OperatorEquals) OperatorFactoryLocal.getInstance().getOperator( + Type.Equals); + } + +} diff --git a/src/com/esri/core/geometry/OperatorEqualsLocal.java b/src/com/esri/core/geometry/OperatorEqualsLocal.java new file mode 100644 index 00000000..d265c55a --- /dev/null +++ b/src/com/esri/core/geometry/OperatorEqualsLocal.java @@ -0,0 +1,35 @@ +/* + Copyright 1995-2013 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ + +package com.esri.core.geometry; + +class OperatorEqualsLocal extends OperatorEquals { + @Override + public boolean execute(Geometry inputGeom1, Geometry inputGeom2, + SpatialReference sr, ProgressTracker progressTracker) { + return RelationalOperations.relate(inputGeom1, inputGeom2, sr, + RelationalOperations.Relation.equals, progressTracker); + } + +} diff --git a/src/com/esri/core/geometry/OperatorExportToESRIShape.java b/src/com/esri/core/geometry/OperatorExportToESRIShape.java new file mode 100644 index 00000000..c0e86e78 --- /dev/null +++ b/src/com/esri/core/geometry/OperatorExportToESRIShape.java @@ -0,0 +1,63 @@ +/* + Copyright 1995-2013 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ + +package com.esri.core.geometry; + +import java.nio.ByteBuffer; + +/** + *Export to ESRI shape format. + */ +public abstract class OperatorExportToESRIShape extends Operator { + @Override + public Type getType() { + return Type.ExportToESRIShape; + } + + /** + * Performs the ExportToESRIShape operation + * + * @return Returns a ByteBufferCursor. + */ + abstract ByteBufferCursor execute(int exportFlags, + GeometryCursor geometryCursor); + + /** + * Performs the ExportToESRIShape operation. + * @param exportFlags Use the {@link ShapeExportFlags} interface. + * @param geometry The Geometry being exported. + * @return Returns a ByteBuffer object containing the Geometry in ESRIShape format. + */ + public abstract ByteBuffer execute(int exportFlags, Geometry geometry); + + /** + * Performs the ExportToESRIShape operation. + * @param exportFlags Use the {@link ShapeExportFlags} interface. + * @param geometry The Geometry being exported. + * @param shapeBuffer The ByteBuffer to contain the exported Geometry in ESRIShape format. + * @return If the input buffer is null, then the size needed for the buffer is returned. Otherwise the number of bytes written to the buffer is returned. + */ + public abstract int execute(int exportFlags, Geometry geometry, + ByteBuffer shapeBuffer); +} diff --git a/src/com/esri/core/geometry/OperatorExportToESRIShapeCursor.java b/src/com/esri/core/geometry/OperatorExportToESRIShapeCursor.java new file mode 100644 index 00000000..f460633a --- /dev/null +++ b/src/com/esri/core/geometry/OperatorExportToESRIShapeCursor.java @@ -0,0 +1,902 @@ +/* + Copyright 1995-2013 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ + +package com.esri.core.geometry; + +import com.esri.core.geometry.VertexDescription.Semantics; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + +class OperatorExportToESRIShapeCursor extends ByteBufferCursor { + GeometryCursor m_inputGeometryCursor; + int m_exportFlags; + int m_index; + ByteBuffer m_shapeBuffer; + + public OperatorExportToESRIShapeCursor(int exportFlags, + GeometryCursor geometryCursor) { + m_index = -1; + if (geometryCursor == null) + throw new GeometryException("invalid argument"); + + m_exportFlags = exportFlags; + m_inputGeometryCursor = geometryCursor; + m_shapeBuffer = null; + } + + @Override + public int getByteBufferID() { + return m_index; + } + + @Override + public ByteBuffer next() { + Geometry geometry = m_inputGeometryCursor.next(); + if (geometry != null) { + m_index = m_inputGeometryCursor.getGeometryID(); + + int size = exportToESRIShape(m_exportFlags, geometry, null); + if (m_shapeBuffer == null || size > m_shapeBuffer.capacity()) + m_shapeBuffer = ByteBuffer.allocate(size).order( + ByteOrder.LITTLE_ENDIAN); + exportToESRIShape(m_exportFlags, geometry, m_shapeBuffer); + return m_shapeBuffer; + } + return null; + } + + static int exportToESRIShape(int exportFlags, Geometry geometry, + ByteBuffer shapeBuffer) { + if (geometry == null) { + if (shapeBuffer != null) + shapeBuffer.putInt(0, ShapeType.ShapeNull); + + return 4; + } + + int type = geometry.getType().value(); + switch (type) { + case Geometry.GeometryType.Polygon: + return exportMultiPathToESRIShape(true, exportFlags, + (MultiPath) geometry, shapeBuffer); + case Geometry.GeometryType.Polyline: + return exportMultiPathToESRIShape(false, exportFlags, + (MultiPath) geometry, shapeBuffer); + case Geometry.GeometryType.MultiPoint: + return exportMultiPointToESRIShape(exportFlags, + (MultiPoint) geometry, shapeBuffer); + case Geometry.GeometryType.Point: + return exportPointToESRIShape(exportFlags, (Point) geometry, + shapeBuffer); + case Geometry.GeometryType.Envelope: + return exportEnvelopeToESRIShape(exportFlags, (Envelope) geometry, + shapeBuffer); + default: { + throw new GeometryException("internal error"); + // return -1; + } + } + } + + private static int exportEnvelopeToESRIShape(int exportFlags, + Envelope envelope, ByteBuffer shapeBuffer) { + boolean bExportZs = envelope.hasAttribute(Semantics.Z) + && (exportFlags & ShapeExportFlags.ShapeExportStripZs) == 0; + boolean bExportMs = envelope.hasAttribute(Semantics.M) + && (exportFlags & ShapeExportFlags.ShapeExportStripMs) == 0; + boolean bExportIDs = envelope.hasAttribute(Semantics.ID) + && (exportFlags & ShapeExportFlags.ShapeExportStripIDs) == 0; + boolean bArcViewNaNs = (exportFlags & ShapeExportFlags.ShapeExportTrueNaNs) == 0; + + boolean bEmpty = envelope.isEmpty(); + int partCount = bEmpty ? 0 : 1; + int pointCount = bEmpty ? 0 : 5; + + int size = (4 /* type */) + (4 * 8 /* envelope */) + (4 /* part count */) + + (4 /* point count */) + (partCount * 4 /* start indices */) + + pointCount * 2 * 8 /* xy coordinates */; + + if (bExportZs) + size += (2 * 8 /* min max */) + (pointCount * 8 /* zs */); + if (bExportMs) + size += (2 * 8 /* min max */) + (pointCount * 8 /* ms */); + if (bExportIDs) + size += (pointCount * 4 /* ids */); + + if (shapeBuffer == null) + return size; + else if (shapeBuffer.capacity() < size) + throw new GeometryException("buffer is too small"); + + int type; + + // Determine the shape type + if (!bExportZs && !bExportMs) { + if (bExportIDs) + type = ShapeType.ShapeGeneralPolygon + | ShapeModifiers.ShapeHasIDs; + else + type = ShapeType.ShapePolygon; + } else if (bExportZs && !bExportMs) { + if (bExportIDs) + type = ShapeType.ShapeGeneralPolygon + | ShapeModifiers.ShapeHasZs + | ShapeModifiers.ShapeHasIDs; + else + type = ShapeType.ShapePolygonZ; + } else if (bExportMs && !bExportZs) { + if (bExportIDs) + type = ShapeType.ShapeGeneralPolygon + | ShapeModifiers.ShapeHasMs + | ShapeModifiers.ShapeHasIDs; + else + type = ShapeType.ShapePolygonM; + } else { + if (bExportIDs) + type = ShapeType.ShapeGeneralPolygon + | ShapeModifiers.ShapeHasZs | ShapeModifiers.ShapeHasMs + | ShapeModifiers.ShapeHasIDs; + else + type = ShapeType.ShapePolygonZM; + } + + int offset = 0; + + // write type + shapeBuffer.putInt(offset, type); + offset += 4; + + // write Envelope + Envelope2D env = new Envelope2D(); + envelope.queryEnvelope2D(env); // calls _VerifyAllStreams + shapeBuffer.putDouble(offset, env.xmin); + offset += 8; + shapeBuffer.putDouble(offset, env.ymin); + offset += 8; + shapeBuffer.putDouble(offset, env.xmax); + offset += 8; + shapeBuffer.putDouble(offset, env.ymax); + offset += 8; + + // write part count + shapeBuffer.putInt(offset, partCount); + offset += 4; + + // write pointCount + shapeBuffer.putInt(offset, pointCount); + offset += 4; + + if (!bEmpty) { + // write start index + shapeBuffer.putInt(offset, 0); + offset += 4; + + // write xy coordinates + shapeBuffer.putDouble(offset, env.xmin); + offset += 8; + shapeBuffer.putDouble(offset, env.ymin); + offset += 8; + + shapeBuffer.putDouble(offset, env.xmin); + offset += 8; + shapeBuffer.putDouble(offset, env.ymax); + offset += 8; + + shapeBuffer.putDouble(offset, env.xmax); + offset += 8; + shapeBuffer.putDouble(offset, env.ymax); + offset += 8; + + shapeBuffer.putDouble(offset, env.xmax); + offset += 8; + shapeBuffer.putDouble(offset, env.ymin); + offset += 8; + + shapeBuffer.putDouble(offset, env.xmin); + offset += 8; + shapeBuffer.putDouble(offset, env.ymin); + offset += 8; + } + // write Zs + if (bExportZs) { + Envelope1D zInterval; + zInterval = envelope.queryInterval(Semantics.Z, 0); + + double zmin = bArcViewNaNs ? Interop + .translateToAVNaN(zInterval.vmin) : zInterval.vmin; + double zmax = bArcViewNaNs ? Interop + .translateToAVNaN(zInterval.vmax) : zInterval.vmax; + + // write min max values + shapeBuffer.putDouble(offset, zmin); + offset += 8; + shapeBuffer.putDouble(offset, zmax); + offset += 8; + + if (!bEmpty) { + // write arbitrary z values + shapeBuffer.putDouble(offset, zmin); + offset += 8; + shapeBuffer.putDouble(offset, zmax); + offset += 8; + shapeBuffer.putDouble(offset, zmin); + offset += 8; + shapeBuffer.putDouble(offset, zmax); + offset += 8; + shapeBuffer.putDouble(offset, zmin); + offset += 8; + } + } + // write Ms + if (bExportMs) { + Envelope1D mInterval; + mInterval = envelope.queryInterval(Semantics.M, 0); + + double mmin = bArcViewNaNs ? Interop + .translateToAVNaN(mInterval.vmin) : mInterval.vmin; + double mmax = bArcViewNaNs ? Interop + .translateToAVNaN(mInterval.vmax) : mInterval.vmax; + + // write min max values + shapeBuffer.putDouble(offset, mmin); + offset += 8; + shapeBuffer.putDouble(offset, mmax); + offset += 8; + + if (!bEmpty) { + // write arbitrary m values + shapeBuffer.putDouble(offset, mmin); + offset += 8; + shapeBuffer.putDouble(offset, mmax); + offset += 8; + shapeBuffer.putDouble(offset, mmin); + offset += 8; + shapeBuffer.putDouble(offset, mmax); + offset += 8; + shapeBuffer.putDouble(offset, mmin); + offset += 8; + } + } + + // write IDs + if (bExportIDs && !bEmpty) { + Envelope1D idInterval; + idInterval = envelope.queryInterval(Semantics.ID, 0); + + int idmin = (int) idInterval.vmin; + int idmax = (int) idInterval.vmax; + + // write arbitrary id values + shapeBuffer.putInt(offset, idmin); + offset += 4; + shapeBuffer.putInt(offset, idmax); + offset += 4; + shapeBuffer.putInt(offset, idmin); + offset += 4; + shapeBuffer.putInt(offset, idmax); + offset += 4; + shapeBuffer.putInt(offset, idmin); + offset += 4; + } + + return offset; + } + + private static int exportPointToESRIShape(int exportFlags, Point point, + ByteBuffer shapeBuffer) { + boolean bExportZ = point.hasAttribute(Semantics.Z) + && (exportFlags & ShapeExportFlags.ShapeExportStripZs) == 0; + boolean bExportM = point.hasAttribute(Semantics.M) + && (exportFlags & ShapeExportFlags.ShapeExportStripMs) == 0; + boolean bExportID = point.hasAttribute(Semantics.ID) + && (exportFlags & ShapeExportFlags.ShapeExportStripIDs) == 0; + boolean bArcViewNaNs = (exportFlags & ShapeExportFlags.ShapeExportTrueNaNs) == 0; + + int size = (4 /* type */) + (2 * 8 /* xy coordinate */); + + if (bExportZ) + size += 8; + if (bExportM) + size += 8; + if (bExportID) + size += 4; + + if (shapeBuffer == null) + return size; + else if (shapeBuffer.capacity() < size) + throw new GeometryException("buffer is too small"); + + int type; + + // Determine the shape type + if (!bExportZ && !bExportM) { + if (bExportID) + type = ShapeType.ShapeGeneralPoint | ShapeModifiers.ShapeHasIDs; + else + type = ShapeType.ShapePoint; + } else if (bExportZ && !bExportM) { + if (bExportID) + type = ShapeType.ShapeGeneralPoint | ShapeModifiers.ShapeHasZs + | ShapeModifiers.ShapeHasIDs; + else + type = ShapeType.ShapePointZ; + } else if (bExportM && !bExportZ) { + if (bExportID) + type = ShapeType.ShapeGeneralPoint | ShapeModifiers.ShapeHasMs + | ShapeModifiers.ShapeHasIDs; + else + type = ShapeType.ShapePointM; + } else { + if (bExportID) + type = ShapeType.ShapeGeneralPoint | ShapeModifiers.ShapeHasZs + | ShapeModifiers.ShapeHasMs + | ShapeModifiers.ShapeHasIDs; + else + type = ShapeType.ShapePointZM; + } + + int offset = 0; + + // write type + + shapeBuffer.putInt(offset, type); + offset += 4; + + boolean bEmpty = point.isEmpty(); + + // write xy + double x = !bEmpty ? point.getX() : NumberUtils.NaN(); + double y = !bEmpty ? point.getY() : NumberUtils.NaN(); + shapeBuffer.putDouble(offset, + bArcViewNaNs ? Interop.translateToAVNaN(x) : x); + offset += 8; + shapeBuffer.putDouble(offset, + bArcViewNaNs ? Interop.translateToAVNaN(y) : y); + offset += 8; + + // write Z + if (bExportZ) { + double z = !bEmpty ? point.getZ() : NumberUtils.NaN(); + shapeBuffer.putDouble(offset, + bArcViewNaNs ? Interop.translateToAVNaN(z) : z); + offset += 8; + } + + // WriteM + if (bExportM) { + double m = !bEmpty ? point.getM() : NumberUtils.NaN(); + shapeBuffer.putDouble(offset, + bArcViewNaNs ? Interop.translateToAVNaN(m) : m); + offset += 8; + } + + // write ID + if (bExportID) { + int id = !bEmpty ? point.getID() : 0; + shapeBuffer.putInt(offset, id); + offset += 4; + } + + return offset; + } + + private static int exportMultiPointToESRIShape(int exportFlags, + MultiPoint multipoint, ByteBuffer shapeBuffer) { + MultiPointImpl multipointImpl = (MultiPointImpl) multipoint._getImpl(); + boolean bExportZs = multipointImpl.hasAttribute(Semantics.Z) + && (exportFlags & ShapeExportFlags.ShapeExportStripZs) == 0; + boolean bExportMs = multipointImpl.hasAttribute(Semantics.M) + && (exportFlags & ShapeExportFlags.ShapeExportStripMs) == 0; + boolean bExportIDs = multipointImpl.hasAttribute(Semantics.ID) + && (exportFlags & ShapeExportFlags.ShapeExportStripIDs) == 0; + boolean bArcViewNaNs = (exportFlags & ShapeExportFlags.ShapeExportTrueNaNs) == 0; + + int pointCount = multipointImpl.getPointCount(); + + int size = (4 /* type */) + (4 * 8 /* envelope */) + (4 /* point count */) + + (pointCount * 2 * 8 /* xy coordinates */); + + if (bExportZs) + size += (2 * 8 /* min max */) + (pointCount * 8 /* zs */); + if (bExportMs) + size += (2 * 8 /* min max */) + (pointCount * 8 /* ms */); + if (bExportIDs) + size += pointCount * 4 /* ids */; + + if (size >= NumberUtils.intMax()) + throw new GeometryException("invalid call"); + + if (shapeBuffer == null) + return size; + else if (shapeBuffer.capacity() < size) + throw new GeometryException("buffer is too small"); + + int type; + + // Determine the shape type + if (!bExportZs && !bExportMs) { + if (bExportIDs) + type = ShapeType.ShapeGeneralMultiPoint + | ShapeModifiers.ShapeHasIDs; + else + type = ShapeType.ShapeMultiPoint; + } else if (bExportZs && !bExportMs) { + if (bExportIDs) + type = ShapeType.ShapeGeneralMultiPoint + | ShapeModifiers.ShapeHasZs + | ShapeModifiers.ShapeHasIDs; + else + type = ShapeType.ShapeMultiPointZ; + } else if (bExportMs && !bExportZs) { + if (bExportIDs) + type = ShapeType.ShapeGeneralMultiPoint + | ShapeModifiers.ShapeHasMs + | ShapeModifiers.ShapeHasIDs; + else + type = ShapeType.ShapeMultiPointM; + } else { + if (bExportIDs) + type = ShapeType.ShapeGeneralMultiPoint + | ShapeModifiers.ShapeHasZs | ShapeModifiers.ShapeHasMs + | ShapeModifiers.ShapeHasIDs; + else + type = ShapeType.ShapeMultiPointZM; + } + + // write type + int offset = 0; + + shapeBuffer.putInt(offset, type); + offset += 4; + + // write Envelope + Envelope2D env = new Envelope2D(); + multipointImpl.queryEnvelope2D(env); // calls _VerifyAllStreams + shapeBuffer.putDouble(offset, env.xmin); + offset += 8; + shapeBuffer.putDouble(offset, env.ymin); + offset += 8; + shapeBuffer.putDouble(offset, env.xmax); + offset += 8; + shapeBuffer.putDouble(offset, env.ymax); + offset += 8; + + // write point count + shapeBuffer.putInt(offset, pointCount); + offset += 4; + + if (pointCount > 0) { + // write xy coordinates + AttributeStreamBase positionStream = multipointImpl + .getAttributeStreamRef(Semantics.POSITION); + AttributeStreamOfDbl position = (AttributeStreamOfDbl) positionStream; + for (int i = 0; i < pointCount; i++) { + double x = position.read(2 * i); + double y = position.read(2 * i + 1); + shapeBuffer.putDouble(offset, x); + offset += 8; + shapeBuffer.putDouble(offset, y); + offset += 8; + } + } + + // write Zs + if (bExportZs) { + Envelope1D zInterval = multipointImpl.queryInterval(Semantics.Z, 0); + shapeBuffer.putDouble(offset, + bArcViewNaNs ? Interop.translateToAVNaN(zInterval.vmin) + : zInterval.vmin); + offset += 8; + shapeBuffer.putDouble(offset, + bArcViewNaNs ? Interop.translateToAVNaN(zInterval.vmax) + : zInterval.vmax); + offset += 8; + + if (pointCount > 0) { + if (multipointImpl._attributeStreamIsAllocated(Semantics.Z)) { + AttributeStreamOfDbl zs = (AttributeStreamOfDbl) multipointImpl + .getAttributeStreamRef(Semantics.Z); + for (int i = 0; i < pointCount; i++) { + double z = zs.read(i); + shapeBuffer.putDouble(offset, + bArcViewNaNs ? Interop.translateToAVNaN(z) : z); + offset += 8; + } + } else { + double z = VertexDescription.getDefaultValue(Semantics.Z); + + if (bArcViewNaNs) + z = Interop.translateToAVNaN(z); + + // Can we write a function that writes all these values at + // once instead of doing a for loop? + for (int i = 0; i < pointCount; i++) + shapeBuffer.putDouble(offset, z); + offset += 8; + } + } + } + + // write Ms + if (bExportMs) { + Envelope1D mInterval = multipointImpl.queryInterval(Semantics.M, 0); + shapeBuffer.putDouble(offset, + bArcViewNaNs ? Interop.translateToAVNaN(mInterval.vmin) + : mInterval.vmin); + offset += 8; + shapeBuffer.putDouble(offset, + bArcViewNaNs ? Interop.translateToAVNaN(mInterval.vmax) + : mInterval.vmax); + offset += 8; + + if (pointCount > 0) { + if (multipointImpl._attributeStreamIsAllocated(Semantics.M)) { + AttributeStreamOfDbl ms = (AttributeStreamOfDbl) multipointImpl + .getAttributeStreamRef(Semantics.M); + for (int i = 0; i < pointCount; i++) { + double m = ms.read(i); + shapeBuffer.putDouble(offset, + bArcViewNaNs ? Interop.translateToAVNaN(m) : m); + offset += 8; + } + } else { + double m = VertexDescription.getDefaultValue(Semantics.M); + + if (bArcViewNaNs) + m = Interop.translateToAVNaN(m); + + for (int i = 0; i < pointCount; i++) + shapeBuffer.putDouble(offset, m); + offset += 8; + } + } + } + + // write IDs + if (bExportIDs) { + if (pointCount > 0) { + if (multipointImpl._attributeStreamIsAllocated(Semantics.ID)) { + AttributeStreamOfInt32 ids = (AttributeStreamOfInt32) multipointImpl + .getAttributeStreamRef(Semantics.ID); + for (int i = 0; i < pointCount; i++) { + int id = ids.read(i); + shapeBuffer.putInt(offset, id); + offset += 4; + } + } else { + int id = (int) VertexDescription + .getDefaultValue(Semantics.ID); + for (int i = 0; i < pointCount; i++) + shapeBuffer.putInt(offset, id); + offset += 4; + } + } + } + + return offset; + } + + private static int exportMultiPathToESRIShape(boolean bPolygon, + int exportFlags, MultiPath multipath, ByteBuffer shapeBuffer) { + MultiPathImpl multipathImpl = (MultiPathImpl) multipath._getImpl(); + + boolean bExportZs = multipathImpl.hasAttribute(Semantics.Z) + && (exportFlags & ShapeExportFlags.ShapeExportStripZs) == 0; + boolean bExportMs = multipathImpl.hasAttribute(Semantics.M) + && (exportFlags & ShapeExportFlags.ShapeExportStripMs) == 0; + boolean bExportIDs = multipathImpl.hasAttribute(Semantics.ID) + && (exportFlags & ShapeExportFlags.ShapeExportStripIDs) == 0; + boolean bHasCurves = multipathImpl.hasNonLinearSegments(); + boolean bArcViewNaNs = (exportFlags & ShapeExportFlags.ShapeExportTrueNaNs) == 0; + + int partCount = multipathImpl.getPathCount(); + int pointCount = multipathImpl.getPointCount(); + + if (!bPolygon) { + for (int ipart = 0; ipart < partCount; ipart++) + if (multipath.isClosedPath(ipart)) + pointCount++; + } else + pointCount += partCount; + + int size = (4 /* type */) + (4 * 8 /* envelope */) + (4 /* part count */) + + (4 /* point count */) + (partCount * 4 /* start indices */) + + pointCount * 2 * 8 /* xy coordinates */; + + if (bExportZs) + size += (2 * 8 /* min max */) + (pointCount * 8 /* zs */); + if (bExportMs) + size += (2 * 8 /* min max */) + (pointCount * 8 /* ms */); + if (bExportIDs) + size += pointCount * 4 /* ids */; + if (bHasCurves) { + // to-do: curves + } + + if (size >= NumberUtils.intMax()) + throw new GeometryException("invalid call"); + + if (shapeBuffer == null) + return size; + else if (shapeBuffer.capacity() < size) + throw new GeometryException("buffer is too small"); + + int offset = 0; + + // Determine the shape type + int type; + if (!bExportZs && !bExportMs) { + if (bExportIDs || bHasCurves) { + type = bPolygon ? ShapeType.ShapeGeneralPolygon + : ShapeType.ShapeGeneralPolyline; + if (bExportIDs) + type |= ShapeModifiers.ShapeHasIDs; + if (bHasCurves) + type |= ShapeModifiers.ShapeHasCurves; + } else + type = bPolygon ? ShapeType.ShapePolygon + : ShapeType.ShapePolyline; + } else if (bExportZs && !bExportMs) { + if (bExportIDs || bHasCurves) { + type = bPolygon ? ShapeType.ShapeGeneralPolygon + : ShapeType.ShapeGeneralPolyline; + type |= ShapeModifiers.ShapeHasZs; + if (bExportIDs) + type |= ShapeModifiers.ShapeHasIDs; + if (bHasCurves) + type |= ShapeModifiers.ShapeHasCurves; + } else + type = bPolygon ? ShapeType.ShapePolygonZ + : ShapeType.ShapePolylineZ; + } else if (bExportMs && !bExportZs) { + if (bExportIDs || bHasCurves) { + type = bPolygon ? ShapeType.ShapeGeneralPolygon + : ShapeType.ShapeGeneralPolyline; + type |= ShapeModifiers.ShapeHasMs; + if (bExportIDs) + type |= ShapeModifiers.ShapeHasIDs; + if (bHasCurves) + type |= ShapeModifiers.ShapeHasCurves; + } else + type = bPolygon ? ShapeType.ShapePolygonM + : ShapeType.ShapePolylineM; + } else { + if (bExportIDs || bHasCurves) { + type = bPolygon ? ShapeType.ShapeGeneralPolygon + : ShapeType.ShapeGeneralPolyline; + type |= ShapeModifiers.ShapeHasZs | ShapeModifiers.ShapeHasMs; + if (bExportIDs) + type |= ShapeModifiers.ShapeHasIDs; + if (bHasCurves) + type |= ShapeModifiers.ShapeHasCurves; + } else + type = bPolygon ? ShapeType.ShapePolygonZM + : ShapeType.ShapePolylineZM; + } + + // write type + shapeBuffer.putInt(offset, type); + offset += 4; + + // write Envelope + Envelope2D env = new Envelope2D(); + multipathImpl.queryEnvelope2D(env); // calls _VerifyAllStreams + shapeBuffer.putDouble(offset, env.xmin); + offset += 8; + shapeBuffer.putDouble(offset, env.ymin); + offset += 8; + shapeBuffer.putDouble(offset, env.xmax); + offset += 8; + shapeBuffer.putDouble(offset, env.ymax); + offset += 8; + + // write part count + shapeBuffer.putInt(offset, partCount); + offset += 4; // to-do: return error if larger than 2^32 - 1 + + // write pointCount + shapeBuffer.putInt(offset, pointCount); + offset += 4; + + // write start indices for each part + int pointIndexDelta = 0; + for (int ipart = 0; ipart < partCount; ipart++) { + int istart = multipathImpl.getPathStart(ipart) + pointIndexDelta; + shapeBuffer.putInt(offset, istart); + offset += 4; + if (bPolygon || multipathImpl.isClosedPath(ipart)) + pointIndexDelta++; + } + + if (pointCount > 0) { + // write xy coordinates + AttributeStreamBase positionStream = multipathImpl + .getAttributeStreamRef(Semantics.POSITION); + AttributeStreamOfDbl position = (AttributeStreamOfDbl) positionStream; + + for (int ipart = 0; ipart < partCount; ipart++) { + int partStart = multipathImpl.getPathStart(ipart); + int partEnd = multipathImpl.getPathEnd(ipart); + for (int i = partStart; i < partEnd; i++) { + double x = position.read(2 * i); + double y = position.read(2 * i + 1); + + shapeBuffer.putDouble(offset, x); + offset += 8; + shapeBuffer.putDouble(offset, y); + offset += 8; + } + + // If the part is closed, then we need to duplicate the start + // point + if (bPolygon || multipathImpl.isClosedPath(ipart)) { + double x = position.read(2 * partStart); + double y = position.read(2 * partStart + 1); + + shapeBuffer.putDouble(offset, x); + offset += 8; + shapeBuffer.putDouble(offset, y); + offset += 8; + } + } + } + + // write Zs + if (bExportZs) { + Envelope1D zInterval = multipathImpl.queryInterval(Semantics.Z, 0); + shapeBuffer.putDouble(offset, + bArcViewNaNs ? Interop.translateToAVNaN(zInterval.vmin) + : zInterval.vmin); + offset += 8; + shapeBuffer.putDouble(offset, + bArcViewNaNs ? Interop.translateToAVNaN(zInterval.vmax) + : zInterval.vmax); + offset += 8; + + if (pointCount > 0) { + if (multipathImpl._attributeStreamIsAllocated(Semantics.Z)) { + AttributeStreamOfDbl zs = (AttributeStreamOfDbl) multipathImpl + .getAttributeStreamRef(Semantics.Z); + for (int ipart = 0; ipart < partCount; ipart++) { + int partStart = multipathImpl.getPathStart(ipart); + int partEnd = multipathImpl.getPathEnd(ipart); + for (int i = partStart; i < partEnd; i++) { + double z = zs.read(i); + shapeBuffer.putDouble(offset, + bArcViewNaNs ? Interop.translateToAVNaN(z) + : z); + offset += 8; + } + + // If the part is closed, then we need to duplicate the + // start z + if (bPolygon || multipathImpl.isClosedPath(ipart)) { + double z = zs.read(partStart); + shapeBuffer.putDouble(offset, z); + offset += 8; + } + } + } else { + double z = VertexDescription.getDefaultValue(Semantics.Z); + + if (bArcViewNaNs) + z = Interop.translateToAVNaN(z); + + for (int i = 0; i < pointCount; i++) + shapeBuffer.putDouble(offset, z); + offset += 8; + } + } + } + + // write Ms + if (bExportMs) { + Envelope1D mInterval = multipathImpl.queryInterval(Semantics.M, 0); + shapeBuffer.putDouble(offset, + bArcViewNaNs ? Interop.translateToAVNaN(mInterval.vmin) + : mInterval.vmin); + offset += 8; + shapeBuffer.putDouble(offset, + bArcViewNaNs ? Interop.translateToAVNaN(mInterval.vmax) + : mInterval.vmax); + offset += 8; + + if (pointCount > 0) { + if (multipathImpl._attributeStreamIsAllocated(Semantics.M)) { + AttributeStreamOfDbl ms = (AttributeStreamOfDbl) multipathImpl + .getAttributeStreamRef(Semantics.M); + for (int ipart = 0; ipart < partCount; ipart++) { + int partStart = multipathImpl.getPathStart(ipart); + int partEnd = multipathImpl.getPathEnd(ipart); + for (int i = partStart; i < partEnd; i++) { + double m = ms.read(i); + shapeBuffer.putDouble(offset, + bArcViewNaNs ? Interop.translateToAVNaN(m) + : m); + offset += 8; + } + + // If the part is closed, then we need to duplicate the + // start m + if (bPolygon || multipathImpl.isClosedPath(ipart)) { + double m = ms.read(partStart); + shapeBuffer.putDouble(offset, m); + offset += 8; + } + } + } else { + double m = VertexDescription.getDefaultValue(Semantics.M); + + if (bArcViewNaNs) + m = Interop.translateToAVNaN(m); + + for (int i = 0; i < pointCount; i++) + shapeBuffer.putDouble(offset, m); + offset += 8; + } + } + } + + // write Curves + if (bHasCurves) { + // to-do: We'll finish this later + } + + // write IDs + if (bExportIDs) { + if (pointCount > 0) { + if (multipathImpl._attributeStreamIsAllocated(Semantics.ID)) { + AttributeStreamOfInt32 ids = (AttributeStreamOfInt32) multipathImpl + .getAttributeStreamRef(Semantics.ID); + for (int ipart = 0; ipart < partCount; ipart++) { + int partStart = multipathImpl.getPathStart(ipart); + int partEnd = multipathImpl.getPathEnd(ipart); + for (int i = partStart; i < partEnd; i++) { + int id = ids.read(i); + shapeBuffer.putInt(offset, id); + offset += 4; + } + + // If the part is closed, then we need to duplicate the + // start id + if (bPolygon || multipathImpl.isClosedPath(ipart)) { + int id = ids.read(partStart); + shapeBuffer.putInt(offset, id); + offset += 4; + } + } + } else { + int id = (int) VertexDescription + .getDefaultValue(Semantics.ID); + for (int i = 0; i < pointCount; i++) + shapeBuffer.putInt(offset, id); + offset += 4; + } + } + } + + return offset; + } + +} diff --git a/src/com/esri/core/geometry/OperatorExportToESRIShapeLocal.java b/src/com/esri/core/geometry/OperatorExportToESRIShapeLocal.java new file mode 100644 index 00000000..9704054e --- /dev/null +++ b/src/com/esri/core/geometry/OperatorExportToESRIShapeLocal.java @@ -0,0 +1,58 @@ +/* + Copyright 1995-2013 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ + +package com.esri.core.geometry; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + +/** + * OperatorExportToESRIShape implementation. + */ +class OperatorExportToESRIShapeLocal extends OperatorExportToESRIShape { + + @Override + ByteBufferCursor execute(int exportFlags, GeometryCursor geometryCursor) { + return new OperatorExportToESRIShapeCursor(exportFlags, geometryCursor); + } + + @Override + public ByteBuffer execute(int exportFlags, Geometry geometry) { + ByteBuffer shapeBuffer = null; + int size = OperatorExportToESRIShapeCursor.exportToESRIShape( + exportFlags, geometry, shapeBuffer); + shapeBuffer = ByteBuffer.allocate(size).order(ByteOrder.LITTLE_ENDIAN); + OperatorExportToESRIShapeCursor.exportToESRIShape(exportFlags, + geometry, shapeBuffer); + return shapeBuffer; + } + + @Override + public int execute(int exportFlags, Geometry geometry, + ByteBuffer shapeBuffer) { + shapeBuffer.order(ByteOrder.LITTLE_ENDIAN); + return OperatorExportToESRIShapeCursor.exportToESRIShape(exportFlags, + geometry, shapeBuffer); + } +} diff --git a/src/com/esri/core/geometry/OperatorExportToJson.java b/src/com/esri/core/geometry/OperatorExportToJson.java new file mode 100644 index 00000000..8bf1240e --- /dev/null +++ b/src/com/esri/core/geometry/OperatorExportToJson.java @@ -0,0 +1,57 @@ +/* + Copyright 1995-2013 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ +package com.esri.core.geometry; + +import com.esri.core.geometry.Operator.Type; + +/** + *Export to JSON format. + */ +public abstract class OperatorExportToJson extends Operator { + @Override + public Type getType() { + return Type.ExportToJson; + } + + /** + * Performs the ExportToJson operation + * + * @return Returns a JsonCursor. + */ + abstract JsonCursor execute(SpatialReference spatialReference, + GeometryCursor geometryCursor); + + /** + *Performs the ExportToJson operation + *@return Returns a String. + */ + public abstract String execute(SpatialReference spatialReference, + Geometry geometry); + + public static OperatorExportToJson local() { + return (OperatorExportToJson) OperatorFactoryLocal.getInstance() + .getOperator(Type.ExportToJson); + } + +} diff --git a/src/com/esri/core/geometry/OperatorExportToJsonCursor.java b/src/com/esri/core/geometry/OperatorExportToJsonCursor.java new file mode 100644 index 00000000..6de353f1 --- /dev/null +++ b/src/com/esri/core/geometry/OperatorExportToJsonCursor.java @@ -0,0 +1,432 @@ +/* + Copyright 1995-2013 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ +package com.esri.core.geometry; + +import com.esri.core.geometry.VertexDescription.Semantics; +import java.io.IOException; +import java.io.StringWriter; +import org.codehaus.jackson.JsonFactory; +import org.codehaus.jackson.JsonGenerationException; +import org.codehaus.jackson.JsonGenerator; + +class OperatorExportToJsonCursor extends JsonCursor { + GeometryCursor m_inputGeometryCursor; + int m_index; + int m_wkid = -1; + int m_latest_wkid = -1; + String m_wkt = null; + + private static JsonFactory factory = new JsonFactory(); + + public OperatorExportToJsonCursor(SpatialReference spatialReference, + GeometryCursor geometryCursor) { + m_index = -1; + if (geometryCursor == null) + throw new IllegalArgumentException(); + if (spatialReference != null && !spatialReference.isLocal()) { + m_wkid = spatialReference.getOldID(); + m_wkt = spatialReference.getText(); + m_latest_wkid = spatialReference.getLatestID(); + } + m_inputGeometryCursor = geometryCursor; + } + + @Override + public int getID() { + return m_index; + } + + @Override + public String next() { + Geometry geometry; + if ((geometry = m_inputGeometryCursor.next()) != null) { + m_index = m_inputGeometryCursor.getGeometryID(); + return exportToJson(geometry); + } + return null; + } + + private String exportToJson(Geometry geometry) { + StringWriter sw = new StringWriter(); + try { + JsonGenerator gen = factory.createJsonGenerator(sw); + int type = geometry.getType().value(); + switch (type) { + case Geometry.GeometryType.Point: + exportPointToJson(gen, (Point) geometry); + break; + + case Geometry.GeometryType.MultiPoint: + exportMultiPointToJson(gen, (MultiPoint) geometry); + break; + + case Geometry.GeometryType.Polyline: + exportPolylineToJson(gen, (Polyline) geometry); + break; + + case Geometry.GeometryType.Polygon: + exportPolygonToJson(gen, (Polygon) geometry); + break; + + case Geometry.GeometryType.Envelope: + exportEnvelopeToJson(gen, (Envelope) geometry); + break; + + default: + throw new RuntimeException( + "not implemented for this geometry type"); + } + + return sw.getBuffer().toString(); + } catch (IOException e) { + e.printStackTrace(); + return null; + } + + } + + private void exportPolygonToJson(JsonGenerator g, Polygon pp) + throws JsonGenerationException, IOException { + exportPolypathToJson(g, pp, "rings"); + } + + private void exportPolylineToJson(JsonGenerator g, Polyline pp) + throws JsonGenerationException, IOException { + exportPolypathToJson(g, pp, "paths"); + } + + private void exportPolypathToJson(JsonGenerator g, MultiPath pp, String name) + throws JsonGenerationException, IOException { + boolean bExportZs = pp.hasAttribute(Semantics.Z); + boolean bExportMs = pp.hasAttribute(Semantics.M); + + g.writeStartObject(); + + if (bExportZs) { + g.writeFieldName("hasZ"); + g.writeBoolean(true); + } + + if (bExportMs) { + g.writeFieldName("hasM"); + g.writeBoolean(true); + } + + g.writeFieldName(name); + + g.writeStartArray(); + + if (!pp.isEmpty()) { + int n = pp.getPathCount(); // rings or paths + + MultiPathImpl mpImpl = (MultiPathImpl) pp._getImpl();// get impl for + // faster + // access + AttributeStreamOfDbl zs = null; + AttributeStreamOfDbl ms = null; + + if (bExportZs) + zs = (AttributeStreamOfDbl) mpImpl + .getAttributeStreamRef(Semantics.Z); + + if (bExportMs) + ms = (AttributeStreamOfDbl) mpImpl + .getAttributeStreamRef(Semantics.M); + + boolean bPolygon = pp instanceof Polygon; + Point2D pt = new Point2D(); + + for (int i = 0; i < n; i++) { + g.writeStartArray(); + int startindex = pp.getPathStart(i); + int numVertices = pp.getPathSize(i); + for (int j = startindex; j < startindex + numVertices; j++) { + pp.getXY(j, pt); + + g.writeStartArray(); + + writeDouble(pt.x, g); + writeDouble(pt.y, g); + + if (bExportZs) { + double z = zs.get(j); + writeDouble(z, g); + } + + if (bExportMs) { + double m = ms.get(j); + writeDouble(m, g); + } + + g.writeEndArray(); + } + + // Close the Path/Ring by writing the Point at the start index + if (bPolygon) { + pp.getXY(startindex, pt); + // getPoint(startindex); + g.writeStartArray(); + + g.writeNumber(pt.x); + g.writeNumber(pt.y); + + if (bExportZs) { + double z = zs.get(startindex); + writeDouble(z, g); + } + + if (bExportMs) { + double m = ms.get(startindex); + writeDouble(m, g); + } + + g.writeEndArray(); + } + + g.writeEndArray(); + } + } + + g.writeEndArray(); + + writeSR(g); + + g.writeEndObject(); + g.close(); + } + + private void exportMultiPointToJson(JsonGenerator g, MultiPoint mpt) + throws JsonGenerationException, IOException { + boolean bExportZs = mpt.hasAttribute(Semantics.Z); + boolean bExportMs = mpt.hasAttribute(Semantics.M); + + g.writeStartObject(); + + if (bExportZs) { + g.writeFieldName("hasZ"); + g.writeBoolean(true); + } + + if (bExportMs) { + g.writeFieldName("hasM"); + g.writeBoolean(true); + } + + g.writeFieldName("points"); + + g.writeStartArray(); + + if (!mpt.isEmpty()) { + MultiPointImpl mpImpl = (MultiPointImpl) mpt._getImpl();// get impl + // for + // faster + // access + AttributeStreamOfDbl zs = null; + AttributeStreamOfDbl ms = null; + + if (bExportZs) + zs = (AttributeStreamOfDbl) mpImpl + .getAttributeStreamRef(Semantics.Z); + + if (bExportMs) + ms = (AttributeStreamOfDbl) mpImpl + .getAttributeStreamRef(Semantics.M); + + Point2D pt = new Point2D(); + int n = mpt.getPointCount(); + for (int i = 0; i < n; i++) { + mpt.getXY(i, pt); + + g.writeStartArray(); + + writeDouble(pt.x, g); + writeDouble(pt.y, g); + + if (bExportZs) { + double z = zs.get(i); + writeDouble(z, g); + } + + if (bExportMs) { + double m = ms.get(i); + writeDouble(m, g); + } + + g.writeEndArray(); + } + } + + g.writeEndArray(); + + writeSR(g); + + g.writeEndObject(); + g.close(); + } + + private void exportPointToJson(JsonGenerator g, Point pt) + throws JsonGenerationException, IOException { + boolean bExportZs = pt.hasAttribute(Semantics.Z); + boolean bExportMs = pt.hasAttribute(Semantics.M); + + g.writeStartObject(); + + if (pt.isEmpty()) { + g.writeFieldName("x"); + g.writeNull(); + g.writeFieldName("y"); + g.writeNull(); + + if (bExportZs) { + g.writeFieldName("z"); + g.writeNull(); + } + + if (bExportMs) { + g.writeFieldName("m"); + g.writeNull(); + } + } else { + g.writeFieldName("x"); + writeDouble(pt.getX(), g); + g.writeFieldName("y"); + writeDouble(pt.getY(), g); + + if (bExportZs) { + g.writeFieldName("z"); + writeDouble(pt.getZ(), g); + } + + if (bExportMs) { + g.writeFieldName("m"); + writeDouble(pt.getM(), g); + } + } + + writeSR(g); + + g.writeEndObject(); + g.close(); + } + + private void exportEnvelopeToJson(JsonGenerator g, Envelope env) + throws JsonGenerationException, IOException { + boolean bExportZs = env.hasAttribute(Semantics.Z); + boolean bExportMs = env.hasAttribute(Semantics.M); + + g.writeStartObject(); + + if (env.isEmpty()) { + g.writeFieldName("xmin"); + g.writeNull(); + g.writeFieldName("ymin"); + g.writeNull(); + g.writeFieldName("xmax"); + g.writeNull(); + g.writeFieldName("ymax"); + g.writeNull(); + + if (bExportZs) { + g.writeFieldName("zmin"); + g.writeNull(); + g.writeFieldName("zmax"); + g.writeNull(); + } + + if (bExportMs) { + g.writeFieldName("mmin"); + g.writeNull(); + g.writeFieldName("mmax"); + g.writeNull(); + } + } else { + g.writeFieldName("xmin"); + writeDouble(env.getXMin(), g); + g.writeFieldName("ymin"); + writeDouble(env.getYMin(), g); + g.writeFieldName("xmax"); + writeDouble(env.getXMax(), g); + g.writeFieldName("ymax"); + writeDouble(env.getYMax(), g); + + if (bExportZs) { + Envelope1D z = env.queryInterval(Semantics.Z, 0); + g.writeFieldName("zmin"); + writeDouble(z.vmin, g); + g.writeFieldName("zmax"); + writeDouble(z.vmax, g); + } + + if (bExportMs) { + Envelope1D m = env.queryInterval(Semantics.M, 0); + g.writeFieldName("mmin"); + writeDouble(m.vmin, g); + g.writeFieldName("mmax"); + writeDouble(m.vmax, g); + } + } + + writeSR(g); + + g.writeEndObject(); + g.close(); + } + + private void writeDouble(double d, JsonGenerator g) throws IOException, + JsonGenerationException { + if (NumberUtils.isNaN(d)) { + g.writeNull(); + } else { + g.writeNumber(d); + } + + return; + } + + private void writeSR(JsonGenerator g) throws IOException, + JsonGenerationException { + if (m_wkid > 0) { + g.writeFieldName("spatialReference"); + g.writeStartObject(); + + g.writeFieldName("wkid"); + g.writeNumber(m_wkid); + + if (m_latest_wkid > 0 && m_latest_wkid != m_wkid) { + g.writeFieldName("latestWkid"); + g.writeNumber(m_latest_wkid); + } + + g.writeEndObject(); + } else if (m_wkt != null) { + g.writeFieldName("spatialReference"); + g.writeStartObject(); + g.writeFieldName("wkt"); + g.writeString(m_wkt); + g.writeEndObject(); + } else + return; + } + +} diff --git a/src/com/esri/core/geometry/OperatorExportToJsonLocal.java b/src/com/esri/core/geometry/OperatorExportToJsonLocal.java new file mode 100644 index 00000000..32586c5f --- /dev/null +++ b/src/com/esri/core/geometry/OperatorExportToJsonLocal.java @@ -0,0 +1,41 @@ +/* + Copyright 1995-2013 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ +package com.esri.core.geometry; + +class OperatorExportToJsonLocal extends OperatorExportToJson { + + @Override + JsonCursor execute(SpatialReference spatialReference, + GeometryCursor geometryCursor) { + return new OperatorExportToJsonCursor(spatialReference, geometryCursor); + } + + @Override + public String execute(SpatialReference spatialReference, Geometry geometry) { + SimpleGeometryCursor gc = new SimpleGeometryCursor(geometry); + JsonCursor cursor = new OperatorExportToJsonCursor(spatialReference, gc); + return cursor.next(); + } + +} diff --git a/src/com/esri/core/geometry/OperatorExportToWkb.java b/src/com/esri/core/geometry/OperatorExportToWkb.java new file mode 100644 index 00000000..d4418b2c --- /dev/null +++ b/src/com/esri/core/geometry/OperatorExportToWkb.java @@ -0,0 +1,64 @@ +/* + Copyright 1995-2013 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ + +package com.esri.core.geometry; + +import java.nio.ByteBuffer; + +import com.esri.core.geometry.Operator.Type; + +/** + *Export to WKB format. + */ +public abstract class OperatorExportToWkb extends Operator { + @Override + public Type getType() { + return Type.ExportToWkb; + } + + /** + * Performs the ExportToWKB operation. + * @param exportFlags Use the {@link WkbExportFlags} interface. + * @param geometry The Geometry being exported. + * @return Returns a ByteBuffer object containing the Geometry in WKB format + */ + public abstract ByteBuffer execute(int exportFlags, Geometry geometry, + ProgressTracker progressTracker); + + /** + * Performs the ExportToWKB operation. + * @param exportFlags Use the {@link WkbExportFlags} interface. + * @param geometry The Geometry being exported. + * @param wkbBuffer The ByteBuffer to contain the exported Geometry in WKB format. + * @return If the input buffer is null, then the size needed for the buffer is returned. Otherwise the number of bytes written to the buffer is returned. + */ + public abstract int execute(int exportFlags, Geometry geometry, + ByteBuffer wkbBuffer, ProgressTracker progressTracker); + + public static OperatorExportToWkb local() { + return (OperatorExportToWkb) OperatorFactoryLocal.getInstance() + .getOperator(Type.ExportToWkb); + } + +} diff --git a/src/com/esri/core/geometry/OperatorExportToWkbLocal.java b/src/com/esri/core/geometry/OperatorExportToWkbLocal.java new file mode 100644 index 00000000..3b4fedfe --- /dev/null +++ b/src/com/esri/core/geometry/OperatorExportToWkbLocal.java @@ -0,0 +1,1249 @@ +/* + Copyright 1995-2013 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ + +package com.esri.core.geometry; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + +class OperatorExportToWkbLocal extends OperatorExportToWkb { + + @Override + public ByteBuffer execute(int exportFlags, Geometry geometry, + ProgressTracker progressTracker) { + int size = exportToWKB(exportFlags, geometry, null); + ByteBuffer wkbBuffer = ByteBuffer.allocate(size).order( + ByteOrder.nativeOrder()); + exportToWKB(exportFlags, geometry, wkbBuffer); + return wkbBuffer; + } + + @Override + public int execute(int exportFlags, Geometry geometry, + ByteBuffer wkbBuffer, ProgressTracker progressTracker) { + return exportToWKB(exportFlags, geometry, wkbBuffer); + } + + private static int exportToWKB(int exportFlags, Geometry geometry, + ByteBuffer wkbBuffer) { + if (geometry == null) + return 0; + + int type = geometry.getType().value(); + switch (type) { + case Geometry.GeometryType.Polygon: + if ((exportFlags & WkbExportFlags.wkbExportLineString) != 0 + || (exportFlags & WkbExportFlags.wkbExportMultiLineString) != 0 + || (exportFlags & WkbExportFlags.wkbExportPoint) != 0 + || (exportFlags & WkbExportFlags.wkbExportMultiPoint) != 0) + throw new GeometryException("invalid argument"); + + return exportPolygonToWKB(exportFlags, (Polygon) geometry, + wkbBuffer); + case Geometry.GeometryType.Polyline: + if ((exportFlags & WkbExportFlags.wkbExportPolygon) != 0 + || (exportFlags & WkbExportFlags.wkbExportMultiPolygon) != 0 + || (exportFlags & WkbExportFlags.wkbExportPoint) != 0 + || (exportFlags & WkbExportFlags.wkbExportMultiPoint) != 0) + throw new GeometryException("invalid argument"); + return exportPolylineToWKB(exportFlags, (Polyline) geometry, + wkbBuffer); + + case Geometry.GeometryType.MultiPoint: + if ((exportFlags & WkbExportFlags.wkbExportLineString) != 0 + || (exportFlags & WkbExportFlags.wkbExportMultiLineString) != 0 + || (exportFlags & WkbExportFlags.wkbExportPolygon) != 0 + || (exportFlags & WkbExportFlags.wkbExportMultiPolygon) != 0) + throw new GeometryException("invalid argument"); + return exportMultiPointToWKB(exportFlags, (MultiPoint) geometry, + wkbBuffer); + + case Geometry.GeometryType.Point: + if ((exportFlags & WkbExportFlags.wkbExportLineString) != 0 + || (exportFlags & WkbExportFlags.wkbExportMultiLineString) != 0 + || (exportFlags & WkbExportFlags.wkbExportPolygon) != 0 + || (exportFlags & WkbExportFlags.wkbExportMultiPolygon) != 0) + throw new GeometryException("invalid argument"); + return exportPointToWKB(exportFlags, (Point) geometry, wkbBuffer); + + case Geometry.GeometryType.Envelope: + if ((exportFlags & WkbExportFlags.wkbExportLineString) != 0 + || (exportFlags & WkbExportFlags.wkbExportMultiLineString) != 0 + || (exportFlags & WkbExportFlags.wkbExportPoint) != 0 + || (exportFlags & WkbExportFlags.wkbExportMultiPoint) != 0) + throw new GeometryException("invalid argument"); + return exportEnvelopeToWKB(exportFlags, (Envelope) geometry, + wkbBuffer); + + default: { + throw new GeometryException("internal error"); + // return -1; + } + } + } + + private static int exportPolygonToWKB(int exportFlags, Polygon _polygon, + ByteBuffer wkbBuffer) { + MultiPathImpl polygon = (MultiPathImpl) _polygon._getImpl(); + + if ((exportFlags & (int) WkbExportFlags.wkbExportFailIfNotSimple) != 0) { + int simple = polygon.getIsSimple(0.0); + + if (simple != MultiVertexGeometryImpl.GeometryXSimple.Strong) + throw new GeometryException("non simple geometry"); + } + + boolean bExportZs = polygon.hasAttribute(VertexDescription.Semantics.Z) + && (exportFlags & (int) WkbExportFlags.wkbExportStripZs) == 0; + boolean bExportMs = polygon.hasAttribute(VertexDescription.Semantics.M) + && (exportFlags & (int) WkbExportFlags.wkbExportStripMs) == 0; + + int polygonCount = polygon.getOGCPolygonCount(); + if ((exportFlags & (int) WkbExportFlags.wkbExportPolygon) != 0 + && polygonCount > 1) + throw new IllegalArgumentException(); + + int partCount = polygon.getPathCount(); + int point_count = polygon.getPointCount(); + point_count += partCount; // add 1 point per part + + if (point_count > 0 && polygonCount == 0) + throw new GeometryException("corrupted geometry"); + + // In the WKB_export_defaults case, polygons gets exported as a + // WKB_multi_polygon. + + // get size for buffer + int size = 0; + if ((exportFlags & (int) WkbExportFlags.wkbExportPolygon) == 0 + || polygonCount == 0) + size += 1 /* byte order */+ 4 /* wkbType */+ 4 /* numPolygons */; + + size += polygonCount + * (1 /* byte order */+ 4 /* wkbType */+ 4/* numRings */) + + partCount * (4 /* num_points */) + point_count * (2 * 8 /* + * xy + * coordinates + */); + + if (bExportZs) + size += (point_count * 8 /* zs */); + if (bExportMs) + size += (point_count * 8 /* ms */); + + if (size >= NumberUtils.intMax()) + throw new GeometryException("invalid call"); + + if (wkbBuffer == null) + return (int) size; + else if (wkbBuffer.capacity() < size) + throw new GeometryException("buffer is too small"); + + int offset = 0; + + byte byteOrder = (byte) (wkbBuffer.order() == ByteOrder.LITTLE_ENDIAN ? WkbByteOrder.wkbNDR + : WkbByteOrder.wkbXDR); + + // Determine the wkb type + int type; + if (!bExportZs && !bExportMs) { + type = WkbGeometryType.wkbPolygon; + + if ((exportFlags & WktExportFlags.wktExportPolygon) == 0) { + wkbBuffer.put(offset, byteOrder); + offset += 1; + wkbBuffer.putInt(offset, WkbGeometryType.wkbMultiPolygon); + offset += 4; + wkbBuffer.putInt(offset, polygonCount); + offset += 4; + } else if (polygonCount == 0) { + wkbBuffer.put(offset, byteOrder); + offset += 1; + wkbBuffer.putInt(offset, WkbGeometryType.wkbPolygon); + offset += 4; + wkbBuffer.putInt(offset, 0); + offset += 4; + } + } else if (bExportZs && !bExportMs) { + type = WkbGeometryType.wkbPolygonZ; + + if ((exportFlags & WkbExportFlags.wkbExportPolygon) == 0) { + wkbBuffer.put(offset, byteOrder); + offset += 1; + wkbBuffer.putInt(offset, WkbGeometryType.wkbMultiPolygonZ); + offset += 4; + wkbBuffer.putInt(offset, polygonCount); + offset += 4; + } else if (polygonCount == 0) { + wkbBuffer.put(offset, byteOrder); + offset += 1; + wkbBuffer.putInt(offset, WkbGeometryType.wkbPolygonZ); + offset += 4; + wkbBuffer.putInt(offset, 0); + offset += 4; + } + } else if (bExportMs && !bExportZs) { + type = WkbGeometryType.wkbPolygonM; + + if ((exportFlags & WkbExportFlags.wkbExportPolygon) == 0) { + wkbBuffer.put(offset, byteOrder); + offset += 1; + wkbBuffer.putInt(offset, WkbGeometryType.wkbMultiPolygonM); + offset += 4; + wkbBuffer.putInt(offset, (int) polygonCount); + offset += 4; + } else if (polygonCount == 0) { + wkbBuffer.put(offset, byteOrder); + offset += 1; + wkbBuffer.putInt(offset, WkbGeometryType.wkbPolygonM); + offset += 4; + wkbBuffer.putInt(offset, 0); + offset += 4; + } + } else { + type = WkbGeometryType.wkbPolygonZM; + + if ((exportFlags & WkbExportFlags.wkbExportPolygon) == 0) { + wkbBuffer.put(offset, byteOrder); + offset += 1; + wkbBuffer.putInt(offset, WkbGeometryType.wkbMultiPolygonZM); + offset += 4; + wkbBuffer.putInt(offset, polygonCount); + offset += 4; + } else if (polygonCount == 0) { + wkbBuffer.put(offset, byteOrder); + offset += 1; + wkbBuffer.putInt(offset, WkbGeometryType.wkbPolygonZM); + offset += 4; + wkbBuffer.putInt(offset, 0); + offset += 4; + } + } + + if (polygonCount == 0) + return offset; + + AttributeStreamOfDbl position = (AttributeStreamOfDbl) (polygon + .getAttributeStreamRef(VertexDescription.Semantics.POSITION)); + + AttributeStreamOfDbl zs = null; + if (bExportZs) { + if (polygon + ._attributeStreamIsAllocated(VertexDescription.Semantics.Z)) + zs = (AttributeStreamOfDbl) (polygon + .getAttributeStreamRef(VertexDescription.Semantics.Z)); + } + + AttributeStreamOfDbl ms = null; + if (bExportMs) { + if (polygon + ._attributeStreamIsAllocated(VertexDescription.Semantics.M)) + ms = (AttributeStreamOfDbl) (polygon + .getAttributeStreamRef(VertexDescription.Semantics.M)); + } + + int ipartend = 0; + int ipolygonend = 0; + + for (int ipolygon = 0; ipolygon < (int) polygonCount; ipolygon++) { + // write byte order + wkbBuffer.put(offset, byteOrder); + offset += 1; + + // write type + wkbBuffer.putInt(offset, type); + offset += 4; + + // get partcount for the ith polygon + AttributeStreamOfInt8 pathFlags = polygon.getPathFlagsStreamRef(); + + int ipolygonstart = ipolygonend; + ipolygonend++; + + while (ipolygonend < partCount + && (pathFlags.read(ipolygonend) & PathFlags.enumOGCStartPolygon) == 0) + ipolygonend++; + + // write numRings + wkbBuffer.putInt(offset, ipolygonend - ipolygonstart); + offset += 4; + + for (int ipart = ipolygonstart; ipart < ipolygonend; ipart++) { + // get num_points + int ipartstart = ipartend; + ipartend = (int) polygon.getPathEnd(ipart); + + // write num_points + wkbBuffer.putInt(offset, ipartend - ipartstart + 1); + offset += 4; + + // duplicate the start point + double x = position.read(2 * ipartstart); + double y = position.read(2 * ipartstart + 1); + + wkbBuffer.putDouble(offset, x); + offset += 8; + wkbBuffer.putDouble(offset, y); + offset += 8; + + if (bExportZs) { + double z; + if (zs != null) + z = zs.read(ipartstart); + else + z = VertexDescription + .getDefaultValue(VertexDescription.Semantics.Z); + + wkbBuffer.putDouble(offset, z); + offset += 8; + } + + if (bExportMs) { + double m; + if (ms != null) + m = ms.read(ipartstart); + else + m = VertexDescription + .getDefaultValue(VertexDescription.Semantics.M); + + wkbBuffer.putDouble(offset, m); + offset += 8; + } + + // We must write to the buffer backwards - ogc polygon format is + // opposite of shapefile format + for (int i = ipartend - 1; i >= ipartstart; i--) { + x = position.read(2 * i); + y = position.read(2 * i + 1); + + wkbBuffer.putDouble(offset, x); + offset += 8; + wkbBuffer.putDouble(offset, y); + offset += 8; + + if (bExportZs) { + double z; + if (zs != null) + z = zs.read(i); + else + z = VertexDescription + .getDefaultValue(VertexDescription.Semantics.Z); + + wkbBuffer.putDouble(offset, z); + offset += 8; + } + + if (bExportMs) { + double m; + if (ms != null) + m = ms.read(i); + else + m = VertexDescription + .getDefaultValue(VertexDescription.Semantics.M); + + wkbBuffer.putDouble(offset, m); + offset += 8; + } + } + } + } + + return offset; + } + + private static int exportPolylineToWKB(int exportFlags, Polyline _polyline, + ByteBuffer wkbBuffer) { + MultiPathImpl polyline = (MultiPathImpl) _polyline._getImpl(); + + if ((exportFlags & WkbExportFlags.wkbExportFailIfNotSimple) != 0) { + int simple = polyline.getIsSimple(0.0); + + if (simple < 1) + throw new GeometryException("corrupted geometry"); + } + + boolean bExportZs = polyline + .hasAttribute(VertexDescription.Semantics.Z) + && (exportFlags & WkbExportFlags.wkbExportStripZs) == 0; + boolean bExportMs = polyline + .hasAttribute(VertexDescription.Semantics.M) + && (exportFlags & WkbExportFlags.wkbExportStripMs) == 0; + + int partCount = polyline.getPathCount(); + if ((exportFlags & WkbExportFlags.wkbExportLineString) != 0 + && partCount > 1) + throw new IllegalArgumentException(); + + int point_count = polyline.getPointCount(); + + for (int ipart = 0; ipart < partCount; ipart++) + if (polyline.isClosedPath(ipart)) + point_count++; + + // In the WKB_export_defaults case, polylines gets exported as a + // WKB_multi_line_string + + // get size for buffer + int size = 0; + if ((exportFlags & WkbExportFlags.wkbExportLineString) == 0 + || partCount == 0) + size += 1 /* byte order */+ 4 /* wkbType */+ 4 /* numLineStrings */; + + size += partCount + * (1 /* byte order */+ 4 /* wkbType */+ 4/* num_points */) + + point_count * (2 * 8 /* xy coordinates */); + + if (bExportZs) + size += (point_count * 8 /* zs */); + if (bExportMs) + size += (point_count * 8 /* ms */); + + if (size >= NumberUtils.intMax()) + throw new GeometryException("invalid call"); + + if (wkbBuffer == null) + return (int) size; + else if (wkbBuffer.capacity() < (int) size) + throw new GeometryException("buffer is too small"); + + int offset = 0; + + byte byteOrder = (byte) (wkbBuffer.order() == ByteOrder.LITTLE_ENDIAN ? WkbByteOrder.wkbNDR + : WkbByteOrder.wkbXDR); + + // Determine the wkb type + int type; + if (!bExportZs && !bExportMs) { + type = WkbGeometryType.wkbLineString; + + if ((exportFlags & WkbExportFlags.wkbExportLineString) == 0) { + wkbBuffer.put(offset, byteOrder); + offset += 1; + wkbBuffer.putInt(offset, WkbGeometryType.wkbMultiLineString); + offset += 4; + wkbBuffer.putInt(offset, (int) partCount); + offset += 4; + } else if (partCount == 0) { + wkbBuffer.put(offset, byteOrder); + offset += 1; + wkbBuffer.putInt(offset, WkbGeometryType.wkbLineString); + offset += 4; + wkbBuffer.putInt(offset, 0); + offset += 4; + } + } else if (bExportZs && !bExportMs) { + type = WkbGeometryType.wkbLineStringZ; + + if ((exportFlags & WkbExportFlags.wkbExportLineString) == 0) { + wkbBuffer.put(offset, byteOrder); + offset += 1; + wkbBuffer.putInt(offset, WkbGeometryType.wkbMultiLineStringZ); + offset += 4; + wkbBuffer.putInt(offset, (int) partCount); + offset += 4; + } else if (partCount == 0) { + wkbBuffer.put(offset, byteOrder); + offset += 1; + wkbBuffer.putInt(offset, WkbGeometryType.wkbLineStringZ); + offset += 4; + wkbBuffer.putInt(offset, 0); + offset += 4; + } + } else if (bExportMs && !bExportZs) { + type = WkbGeometryType.wkbLineStringM; + + if ((exportFlags & WkbExportFlags.wkbExportLineString) == 0) { + wkbBuffer.put(offset, byteOrder); + offset += 1; + wkbBuffer.putInt(offset, WkbGeometryType.wkbMultiLineStringM); + offset += 4; + wkbBuffer.putInt(offset, (int) partCount); + offset += 4; + } else if (partCount == 0) { + wkbBuffer.put(offset, byteOrder); + offset += 1; + wkbBuffer.putInt(offset, WkbGeometryType.wkbLineStringM); + offset += 4; + wkbBuffer.putInt(offset, 0); + offset += 4; + } + } else { + type = WkbGeometryType.wkbLineStringZM; + + if ((exportFlags & WkbExportFlags.wkbExportLineString) == 0) { + wkbBuffer.put(offset, byteOrder); + offset += 1; + wkbBuffer.putInt(offset, WkbGeometryType.wkbMultiLineStringZM); + offset += 4; + wkbBuffer.putInt(offset, partCount); + offset += 4; + } else if (partCount == 0) { + wkbBuffer.put(offset, byteOrder); + offset += 1; + wkbBuffer.putInt(offset, WkbGeometryType.wkbLineStringZM); + offset += 4; + wkbBuffer.putInt(offset, 0); + offset += 4; + } + } + + if (partCount == 0) + return offset; + + AttributeStreamOfDbl position = (AttributeStreamOfDbl) (polyline + .getAttributeStreamRef(VertexDescription.Semantics.POSITION)); + + AttributeStreamOfDbl zs = null; + if (bExportZs) { + if (polyline + ._attributeStreamIsAllocated(VertexDescription.Semantics.Z)) + zs = (AttributeStreamOfDbl) (polyline + .getAttributeStreamRef(VertexDescription.Semantics.Z)); + } + + AttributeStreamOfDbl ms = null; + if (bExportMs) { + if (polyline + ._attributeStreamIsAllocated(VertexDescription.Semantics.M)) + ms = (AttributeStreamOfDbl) (polyline + .getAttributeStreamRef(VertexDescription.Semantics.M)); + } + + int ipartend = 0; + for (int ipart = 0; ipart < (int) partCount; ipart++) { + // write byte order + wkbBuffer.put(offset, byteOrder); + offset += 1; + + // write type + wkbBuffer.putInt(offset, type); + offset += 4; + + // get start and end indices + int ipartstart = ipartend; + ipartend = (int) polyline.getPathEnd(ipart); + + // write num_points + int num_points = ipartend - ipartstart; + if (polyline.isClosedPath(ipart)) + num_points++; + + wkbBuffer.putInt(offset, num_points); + offset += 4; + + // write points + for (int i = ipartstart; i < ipartend; i++) { + double x = position.read(2 * i); + double y = position.read(2 * i + 1); + + wkbBuffer.putDouble(offset, x); + offset += 8; + wkbBuffer.putDouble(offset, y); + offset += 8; + + if (bExportZs) { + double z; + if (zs != null) + z = zs.read(i); + else + z = VertexDescription + .getDefaultValue(VertexDescription.Semantics.Z); + + wkbBuffer.putDouble(offset, z); + offset += 8; + } + + if (bExportMs) { + double m; + if (ms != null) + m = ms.read(i); + else + m = VertexDescription + .getDefaultValue(VertexDescription.Semantics.M); + + wkbBuffer.putDouble(offset, m); + offset += 8; + } + } + + // duplicate the start point if the Polyline is closed + if (polyline.isClosedPath(ipart)) { + double x = position.read(2 * ipartstart); + double y = position.read(2 * ipartstart + 1); + + wkbBuffer.putDouble(offset, x); + offset += 8; + wkbBuffer.putDouble(offset, y); + offset += 8; + + if (bExportZs) { + double z; + if (zs != null) + z = zs.read(ipartstart); + else + z = VertexDescription + .getDefaultValue(VertexDescription.Semantics.Z); + + wkbBuffer.putDouble(offset, z); + offset += 8; + } + + if (bExportMs) { + double m; + if (ms != null) + m = ms.read(ipartstart); + else + m = VertexDescription + .getDefaultValue(VertexDescription.Semantics.M); + + wkbBuffer.putDouble(offset, m); + offset += 8; + } + } + } + + return offset; + } + + private static int exportMultiPointToWKB(int exportFlags, + MultiPoint _multipoint, ByteBuffer wkbBuffer) { + MultiPointImpl multipoint = (MultiPointImpl) _multipoint._getImpl(); + + boolean bExportZs = multipoint + .hasAttribute(VertexDescription.Semantics.Z) + && (exportFlags & WkbExportFlags.wkbExportStripZs) == 0; + boolean bExportMs = multipoint + .hasAttribute(VertexDescription.Semantics.M) + && (exportFlags & WkbExportFlags.wkbExportStripMs) == 0; + + int point_count = multipoint.getPointCount(); + if ((exportFlags & WkbExportFlags.wkbExportPoint) != 0 + && point_count > 1) + throw new IllegalArgumentException(); + + // get size for buffer + int size; + if ((exportFlags & WkbExportFlags.wkbExportPoint) == 0) { + size = 1 /* byte order */+ 4 /* wkbType */+ 4 /* num_points */ + + point_count + * (1 /* byte order */+ 4 /* wkbType */+ 2 * 8 /* + * xy + * coordinates + */); + + if (bExportZs) + size += (point_count * 8 /* zs */); + if (bExportMs) + size += (point_count * 8 /* ms */); + } else { + size = 1 /* byte order */+ 4 /* wkbType */+ 2 * 8 /* xy coordinates */; + + if (bExportZs) + size += 8 /* z */; + if (bExportMs) + size += 8 /* m */; + } + + if (size >= NumberUtils.intMax()) + throw new GeometryException("invalid call"); + + if (wkbBuffer == null) + return (int) size; + else if (wkbBuffer.capacity() < (int) size) + throw new GeometryException("buffer is too small"); + + int offset = 0; + + byte byteOrder = (byte) (wkbBuffer.order() == ByteOrder.LITTLE_ENDIAN ? WkbByteOrder.wkbNDR + : WkbByteOrder.wkbXDR); + + // Determine the wkb type + int type; + if (!bExportZs && !bExportMs) { + type = WkbGeometryType.wkbPoint; + + if ((exportFlags & WkbExportFlags.wkbExportPoint) == 0) { + wkbBuffer.put(offset, byteOrder); + offset += 1; + wkbBuffer.putInt(offset, WkbGeometryType.wkbMultiPoint); + offset += 4; + wkbBuffer.putInt(offset, (int) point_count); + offset += 4; + } else if (point_count == 0) { + wkbBuffer.put(offset, byteOrder); + offset += 1; + wkbBuffer.putInt(offset, type); + offset += 4; + wkbBuffer.putDouble(offset, NumberUtils.TheNaN); + offset += 8; + wkbBuffer.putDouble(offset, NumberUtils.TheNaN); + offset += 8; + } + } else if (bExportZs && !bExportMs) { + type = WkbGeometryType.wkbPointZ; + + if ((exportFlags & WkbExportFlags.wkbExportPoint) == 0) { + wkbBuffer.put(offset, byteOrder); + offset += 1; + wkbBuffer.putInt(offset, WkbGeometryType.wkbMultiPointZ); + offset += 4; + wkbBuffer.putInt(offset, (int) point_count); + offset += 4; + } else if (point_count == 0) { + wkbBuffer.put(offset, byteOrder); + offset += 1; + wkbBuffer.putInt(offset, type); + offset += 4; + wkbBuffer.putDouble(offset, NumberUtils.TheNaN); + offset += 8; + wkbBuffer.putDouble(offset, NumberUtils.TheNaN); + offset += 8; + wkbBuffer.putDouble(offset, NumberUtils.TheNaN); + offset += 8; + } + } else if (bExportMs && !bExportZs) { + type = WkbGeometryType.wkbPointM; + + if ((exportFlags & WkbExportFlags.wkbExportPoint) == 0) { + wkbBuffer.put(offset, byteOrder); + offset += 1; + wkbBuffer.putInt(offset, WkbGeometryType.wkbMultiPointM); + offset += 4; + wkbBuffer.putInt(offset, (int) point_count); + offset += 4; + } else if (point_count == 0) { + wkbBuffer.put(offset, byteOrder); + offset += 1; + wkbBuffer.putInt(offset, type); + offset += 4; + wkbBuffer.putDouble(offset, NumberUtils.TheNaN); + offset += 8; + wkbBuffer.putDouble(offset, NumberUtils.TheNaN); + offset += 8; + wkbBuffer.putDouble(offset, NumberUtils.TheNaN); + offset += 8; + } + } else { + type = WkbGeometryType.wkbPointZM; + + if ((exportFlags & WkbExportFlags.wkbExportPoint) == 0) { + wkbBuffer.put(offset, byteOrder); + offset += 1; + wkbBuffer.putInt(offset, WkbGeometryType.wkbMultiPolygonZM); + offset += 4; + wkbBuffer.putInt(offset, point_count); + offset += 4; + } else if (point_count == 0) { + wkbBuffer.put(offset, byteOrder); + offset += 1; + wkbBuffer.putInt(offset, type); + offset += 4; + wkbBuffer.putDouble(offset, NumberUtils.TheNaN); + offset += 8; + wkbBuffer.putDouble(offset, NumberUtils.TheNaN); + offset += 8; + wkbBuffer.putDouble(offset, NumberUtils.TheNaN); + offset += 8; + wkbBuffer.putDouble(offset, NumberUtils.TheNaN); + offset += 8; + } + } + + if (point_count == 0) + return offset; + + AttributeStreamOfDbl position = (AttributeStreamOfDbl) (multipoint + .getAttributeStreamRef(VertexDescription.Semantics.POSITION)); + + AttributeStreamOfDbl zs = null; + if (bExportZs) { + if (multipoint + ._attributeStreamIsAllocated(VertexDescription.Semantics.Z)) + zs = (AttributeStreamOfDbl) (multipoint + .getAttributeStreamRef(VertexDescription.Semantics.Z)); + } + + AttributeStreamOfDbl ms = null; + if (bExportMs) { + if (multipoint + ._attributeStreamIsAllocated(VertexDescription.Semantics.M)) + ms = (AttributeStreamOfDbl) (multipoint + .getAttributeStreamRef(VertexDescription.Semantics.M)); + } + + for (int i = 0; i < (int) point_count; i++) { + // write byte order + wkbBuffer.put(offset, byteOrder); + offset += 1; + + // write type + wkbBuffer.putInt(offset, type); + offset += 4; + + // write xy coordinates + double x = position.read(2 * i); + double y = position.read(2 * i + 1); + + wkbBuffer.putDouble(offset, x); + offset += 8; + wkbBuffer.putDouble(offset, y); + offset += 8; + + // write Z + if (bExportZs) { + double z; + if (zs != null) + z = zs.read(i); + else + z = VertexDescription + .getDefaultValue(VertexDescription.Semantics.Z); + + wkbBuffer.putDouble(offset, z); + offset += 8; + } + + // write M + if (bExportMs) { + double m; + if (ms != null) + m = ms.read(i); + else + m = VertexDescription + .getDefaultValue(VertexDescription.Semantics.M); + + wkbBuffer.putDouble(offset, m); + offset += 8; + } + } + + return offset; + } + + private static int exportPointToWKB(int exportFlags, Point point, + ByteBuffer wkbBuffer) { + boolean bExportZs = point.hasAttribute(VertexDescription.Semantics.Z) + && (exportFlags & WkbExportFlags.wkbExportStripZs) == 0; + boolean bExportMs = point.hasAttribute(VertexDescription.Semantics.M) + && (exportFlags & WkbExportFlags.wkbExportStripMs) == 0; + + boolean bEmpty = point.isEmpty(); + int point_count = bEmpty ? 0 : 1; + + // get size for buffer + int size; + if ((exportFlags & WkbExportFlags.wkbExportMultiPoint) != 0) { + size = 1 /* byte order */+ 4 /* wkbType */+ 4 /* num_points */ + + point_count + * (1 /* byte order */+ 4 /* wkbType */+ 2 * 8 /* + * xy + * coordinates + */); + + if (bExportZs) + size += (point_count * 8 /* zs */); + if (bExportMs) + size += (point_count * 8 /* ms */); + } else { + size = 1 /* byte order */+ 4 /* wkbType */+ 2 * 8 /* xy coordinates */; + + if (bExportZs) + size += 8 /* z */; + if (bExportMs) + size += 8 /* m */; + } + + if (size >= NumberUtils.intMax()) + throw new GeometryException("invalid call"); + + if (wkbBuffer == null) + return size; + else if (wkbBuffer.capacity() < size) + throw new GeometryException("buffer is too small"); + + int offset = 0; + + byte byteOrder = (byte) (wkbBuffer.order() == ByteOrder.LITTLE_ENDIAN ? WkbByteOrder.wkbNDR + : WkbByteOrder.wkbXDR); + + // Determine the wkb type + int type; + if (!bExportZs && !bExportMs) { + type = WkbGeometryType.wkbPoint; + + if ((exportFlags & WkbExportFlags.wkbExportMultiPoint) != 0) { + wkbBuffer.put(offset, byteOrder); + offset += 1; + wkbBuffer.putInt(offset, WkbGeometryType.wkbMultiPoint); + offset += 4; + wkbBuffer.putInt(offset, (int) point_count); + offset += 4; + } else if (point_count == 0) { + wkbBuffer.put(offset, byteOrder); + offset += 1; + wkbBuffer.putInt(offset, type); + offset += 4; + wkbBuffer.putDouble(offset, NumberUtils.TheNaN); + offset += 8; + wkbBuffer.putDouble(offset, NumberUtils.TheNaN); + offset += 8; + } + } else if (bExportZs && !bExportMs) { + type = WkbGeometryType.wkbPointZ; + + if ((exportFlags & WkbExportFlags.wkbExportMultiPoint) != 0) { + wkbBuffer.put(offset, byteOrder); + offset += 1; + wkbBuffer.putInt(offset, WkbGeometryType.wkbMultiPointZ); + offset += 4; + wkbBuffer.putInt(offset, (int) point_count); + offset += 4; + } else if (point_count == 0) { + wkbBuffer.put(offset, byteOrder); + offset += 1; + wkbBuffer.putInt(offset, type); + offset += 4; + wkbBuffer.putDouble(offset, NumberUtils.TheNaN); + offset += 8; + wkbBuffer.putDouble(offset, NumberUtils.TheNaN); + offset += 8; + wkbBuffer.putDouble(offset, NumberUtils.TheNaN); + offset += 8; + } + } else if (bExportMs && !bExportZs) { + type = WkbGeometryType.wkbPointM; + + if ((exportFlags & WkbExportFlags.wkbExportMultiPoint) != 0) { + wkbBuffer.put(offset, byteOrder); + offset += 1; + wkbBuffer.putInt(offset, WkbGeometryType.wkbMultiPointM); + offset += 4; + wkbBuffer.putInt(offset, (int) point_count); + offset += 4; + } else if (point_count == 0) { + wkbBuffer.put(offset, byteOrder); + offset += 1; + wkbBuffer.putInt(offset, type); + offset += 4; + wkbBuffer.putDouble(offset, NumberUtils.TheNaN); + offset += 8; + wkbBuffer.putDouble(offset, NumberUtils.TheNaN); + offset += 8; + wkbBuffer.putDouble(offset, NumberUtils.TheNaN); + offset += 8; + } + } else { + type = WkbGeometryType.wkbPointZM; + + if ((exportFlags & WkbExportFlags.wkbExportMultiPoint) != 0) { + wkbBuffer.put(offset, byteOrder); + offset += 1; + wkbBuffer.putInt(offset, WkbGeometryType.wkbMultiPointZM); + offset += 4; + wkbBuffer.putInt(offset, (int) point_count); + offset += 4; + } else if (point_count == 0) { + wkbBuffer.put(offset, byteOrder); + offset += 1; + wkbBuffer.putInt(offset, type); + offset += 4; + wkbBuffer.putDouble(offset, NumberUtils.TheNaN); + offset += 8; + wkbBuffer.putDouble(offset, NumberUtils.TheNaN); + offset += 8; + wkbBuffer.putDouble(offset, NumberUtils.TheNaN); + offset += 8; + wkbBuffer.putDouble(offset, NumberUtils.TheNaN); + offset += 8; + } + } + + if (point_count == 0) + return offset; + + // write byte order + wkbBuffer.put(offset, byteOrder); + offset += 1; + + // write type + wkbBuffer.putInt(offset, type); + offset += 4; + + // write xy coordinate + double x = point.getX(); + double y = point.getY(); + wkbBuffer.putDouble(offset, x); + offset += 8; + wkbBuffer.putDouble(offset, y); + offset += 8; + + // write Z + if (bExportZs) { + double z = point.getZ(); + wkbBuffer.putDouble(offset, z); + offset += 8; + } + + // write M + if (bExportMs) { + double m = point.getM(); + wkbBuffer.putDouble(offset, m); + offset += 8; + } + + return offset; + } + + private static int exportEnvelopeToWKB(int exportFlags, Envelope envelope, + ByteBuffer wkbBuffer) { + boolean bExportZs = envelope + .hasAttribute(VertexDescription.Semantics.Z) + && (exportFlags & WkbExportFlags.wkbExportStripZs) == 0; + boolean bExportMs = envelope + .hasAttribute(VertexDescription.Semantics.M) + && (exportFlags & WkbExportFlags.wkbExportStripMs) == 0; + boolean bEmpty = envelope.isEmpty(); + + int partCount = bEmpty ? 0 : 1; + int point_count = bEmpty ? 0 : 5; + + // Envelope by default is exported as a WKB_polygon + + // get size for buffer + int size = 0; + if ((exportFlags & WkbExportFlags.wkbExportMultiPolygon) != 0 + || partCount == 0) + size += 1 /* byte order */+ 4 /* wkbType */+ 4 /* numPolygons */; + + size += partCount + * (1 /* byte order */+ 4 /* wkbType */+ 4/* numRings */) + + partCount * (4 /* num_points */) + point_count * (2 * 8 /* + * xy + * coordinates + */); + + if (bExportZs) + size += (point_count * 8 /* zs */); + if (bExportMs) + size += (point_count * 8 /* ms */); + + if (size >= NumberUtils.intMax()) + throw new GeometryException("invalid call"); + + if (wkbBuffer == null) + return size; + else if (wkbBuffer.capacity() < size) + throw new GeometryException("buffer is too small"); + + int offset = 0; + + byte byteOrder = (byte) (wkbBuffer.order() == ByteOrder.LITTLE_ENDIAN ? WkbByteOrder.wkbNDR + : WkbByteOrder.wkbXDR); + + // Determine the wkb type + int type; + if (!bExportZs && !bExportMs) { + type = WkbGeometryType.wkbPolygon; + + if ((exportFlags & WkbExportFlags.wkbExportMultiPolygon) != 0) { + wkbBuffer.put(offset, byteOrder); + offset += 1; + wkbBuffer.putInt(offset, WkbGeometryType.wkbMultiPolygon); + offset += 4; + wkbBuffer.putInt(offset, (int) partCount); + offset += 4; + } else if (partCount == 0) { + wkbBuffer.put(offset, byteOrder); + offset += 1; + wkbBuffer.putInt(offset, WkbGeometryType.wkbPolygon); + offset += 4; + wkbBuffer.putInt(offset, 0); + offset += 4; + } + } else if (bExportZs && !bExportMs) { + type = WkbGeometryType.wkbPolygonZ; + + if ((exportFlags & WkbExportFlags.wkbExportPolygon) != 0) { + wkbBuffer.put(offset, byteOrder); + offset += 1; + wkbBuffer.putInt(offset, WkbGeometryType.wkbMultiPolygonZ); + offset += 4; + wkbBuffer.putInt(offset, partCount); + offset += 4; + } else if (partCount == 0) { + wkbBuffer.put(offset, byteOrder); + offset += 1; + wkbBuffer.putInt(offset, WkbGeometryType.wkbPolygonZ); + offset += 4; + wkbBuffer.putInt(offset, 0); + offset += 4; + } + } else if (bExportMs && !bExportZs) { + type = WkbGeometryType.wkbPolygonM; + + if ((exportFlags & WkbExportFlags.wkbExportMultiPolygon) != 0) { + wkbBuffer.put(offset, byteOrder); + offset += 1; + wkbBuffer.putInt(offset, WkbGeometryType.wkbMultiPolygonM); + offset += 4; + wkbBuffer.putInt(offset, partCount); + offset += 4; + } else if (partCount == 0) { + wkbBuffer.put(offset, byteOrder); + offset += 1; + wkbBuffer.putInt(offset, WkbGeometryType.wkbPolygonM); + offset += 4; + wkbBuffer.putInt(offset, 0); + offset += 4; + } + } else { + type = WkbGeometryType.wkbPolygonZM; + + if ((exportFlags & WkbExportFlags.wkbExportMultiPolygon) != 0) { + wkbBuffer.put(offset, byteOrder); + offset += 1; + wkbBuffer.putInt(offset, WkbGeometryType.wkbMultiPolygonZM); + offset += 4; + wkbBuffer.putInt(offset, partCount); + offset += 4; + } else if (partCount == 0) { + wkbBuffer.put(offset, byteOrder); + offset += 1; + wkbBuffer.putInt(offset, WkbGeometryType.wkbPolygonZM); + offset += 4; + wkbBuffer.putInt(offset, 0); + offset += 4; + } + } + + if (partCount == 0) + return offset; + + // write byte order + wkbBuffer.put(offset, byteOrder); + offset += 1; + + // write type + wkbBuffer.putInt(offset, type); + offset += 4; + + // write numRings + wkbBuffer.putInt(offset, 1); + offset += 4; + + // write num_points + wkbBuffer.putInt(offset, 5); + offset += 4; + + Envelope2D env = new Envelope2D(); + envelope.queryEnvelope2D(env); + + Envelope1D z_interval = null; + if (bExportZs) + z_interval = envelope.queryInterval(VertexDescription.Semantics.Z, + 0); + + Envelope1D mInterval = null; + if (bExportMs) + mInterval = envelope + .queryInterval(VertexDescription.Semantics.M, 0); + + wkbBuffer.putDouble(offset, env.xmin); + offset += 8; + wkbBuffer.putDouble(offset, env.ymin); + offset += 8; + + if (bExportZs) { + wkbBuffer.putDouble(offset, z_interval.vmin); + offset += 8; + } + + if (bExportMs) { + wkbBuffer.putDouble(offset, mInterval.vmin); + offset += 8; + } + + wkbBuffer.putDouble(offset, env.xmax); + offset += 8; + wkbBuffer.putDouble(offset, env.ymin); + offset += 8; + + if (bExportZs) { + wkbBuffer.putDouble(offset, z_interval.vmax); + offset += 8; + } + + if (bExportMs) { + wkbBuffer.putDouble(offset, mInterval.vmax); + offset += 8; + } + + wkbBuffer.putDouble(offset, env.xmax); + offset += 8; + wkbBuffer.putDouble(offset, env.ymax); + offset += 8; + + if (bExportZs) { + wkbBuffer.putDouble(offset, z_interval.vmin); + offset += 8; + } + + if (bExportMs) { + wkbBuffer.putDouble(offset, mInterval.vmin); + offset += 8; + } + + wkbBuffer.putDouble(offset, env.xmin); + offset += 8; + wkbBuffer.putDouble(offset, env.ymax); + offset += 8; + + if (bExportZs) { + wkbBuffer.putDouble(offset, z_interval.vmax); + offset += 8; + } + + if (bExportMs) { + wkbBuffer.putDouble(offset, mInterval.vmax); + offset += 8; + } + + wkbBuffer.putDouble(offset, env.xmin); + offset += 8; + wkbBuffer.putDouble(offset, env.ymin); + offset += 8; + + if (bExportZs) { + wkbBuffer.putDouble(offset, z_interval.vmin); + offset += 8; + } + + if (bExportMs) { + wkbBuffer.putDouble(offset, mInterval.vmin); + offset += 8; + } + + return offset; + } + +} diff --git a/src/com/esri/core/geometry/OperatorExportToWkt.java b/src/com/esri/core/geometry/OperatorExportToWkt.java new file mode 100644 index 00000000..6e893b14 --- /dev/null +++ b/src/com/esri/core/geometry/OperatorExportToWkt.java @@ -0,0 +1,42 @@ +/* + Copyright 1995-2013 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ +package com.esri.core.geometry; + +import com.esri.core.geometry.Operator.Type; + +public abstract class OperatorExportToWkt extends Operator { + @Override + public Type getType() { + return Type.ExportToWkt; + } + + public abstract String execute(int exportFlags, Geometry geometry, + ProgressTracker progress_tracker); + + public static OperatorExportToWkt local() { + return (OperatorExportToWkt) OperatorFactoryLocal.getInstance() + .getOperator(Type.ExportToWkt); + } + +} diff --git a/src/com/esri/core/geometry/OperatorExportToWktLocal.java b/src/com/esri/core/geometry/OperatorExportToWktLocal.java new file mode 100644 index 00000000..cdcefd84 --- /dev/null +++ b/src/com/esri/core/geometry/OperatorExportToWktLocal.java @@ -0,0 +1,887 @@ +/* + Copyright 1995-2013 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ +package com.esri.core.geometry; + +class OperatorExportToWktLocal extends OperatorExportToWkt { + @Override + public String execute(int export_flags, Geometry geometry, + ProgressTracker progress_tracker) { + StringBuilder string = new StringBuilder(); + exportToWkt(export_flags, geometry, string); + + return string.toString(); + } + + static void exportToWkt(int export_flags, Geometry geometry, + StringBuilder string) { + int type = geometry.getType().value(); + switch (type) { + case Geometry.GeometryType.Polygon: + if ((export_flags & WktExportFlags.wktExportLineString) != 0 + || (export_flags & WktExportFlags.wktExportMultiLineString) != 0 + || (export_flags & WktExportFlags.wktExportPoint) != 0 + || (export_flags & WktExportFlags.wktExportMultiPoint) != 0) + throw new IllegalArgumentException(); + + exportPolygonToWkt(export_flags, (Polygon) geometry, string); + return; + + case Geometry.GeometryType.Polyline: + if ((export_flags & WktExportFlags.wktExportPolygon) != 0 + || (export_flags & WktExportFlags.wktExportMultiPolygon) != 0 + || (export_flags & WktExportFlags.wktExportPoint) != 0 + || (export_flags & WktExportFlags.wktExportMultiPoint) != 0) + throw new IllegalArgumentException(); + + exportPolylineToWkt(export_flags, (Polyline) geometry, string); + return; + + case Geometry.GeometryType.MultiPoint: + if ((export_flags & WktExportFlags.wktExportLineString) != 0 + || (export_flags & WktExportFlags.wktExportMultiLineString) != 0 + || (export_flags & WktExportFlags.wktExportPolygon) != 0 + || (export_flags & WktExportFlags.wktExportMultiPolygon) != 0) + throw new IllegalArgumentException(); + + exportMultiPointToWkt(export_flags, (MultiPoint) geometry, string); + return; + + case Geometry.GeometryType.Point: + if ((export_flags & WktExportFlags.wktExportLineString) != 0 + || (export_flags & WktExportFlags.wktExportMultiLineString) != 0 + || (export_flags & WktExportFlags.wktExportPolygon) != 0 + || (export_flags & WktExportFlags.wktExportMultiPolygon) != 0) + throw new IllegalArgumentException(); + + exportPointToWkt(export_flags, (Point) geometry, string); + return; + + case Geometry.GeometryType.Envelope: + if ((export_flags & WktExportFlags.wktExportLineString) != 0 + || (export_flags & WktExportFlags.wktExportMultiLineString) != 0 + || (export_flags & WktExportFlags.wktExportPoint) != 0 + || (export_flags & WktExportFlags.wktExportMultiPoint) != 0) + throw new IllegalArgumentException(); + + exportEnvelopeToWkt(export_flags, (Envelope) geometry, string); + return; + + default: { + throw new GeometryException("internal error"); + } + } + } + + static void exportPolygonToWkt(int export_flags, Polygon polygon, + StringBuilder string) { + MultiPathImpl polygon_impl = (MultiPathImpl) polygon._getImpl(); + + if ((export_flags & WktExportFlags.wktExportFailIfNotSimple) != 0) { + int simple = polygon_impl.getIsSimple(0.0); + + if (simple != MultiPathImpl.GeometryXSimple.Strong) + throw new GeometryException("corrupted geometry"); + } + + int point_count = polygon.getPointCount(); + int polygon_count = polygon_impl.getOGCPolygonCount(); + + if (point_count > 0 && polygon_count == 0) + throw new GeometryException("corrupted geometry"); + + int precision = 17 - (7 & (export_flags >> 13)); + boolean b_export_zs = polygon_impl + .hasAttribute(VertexDescription.Semantics.Z) + && (export_flags & WktExportFlags.wktExportStripZs) == 0; + boolean b_export_ms = polygon_impl + .hasAttribute(VertexDescription.Semantics.M) + && (export_flags & WktExportFlags.wktExportStripMs) == 0; + + int path_count = 0; + AttributeStreamOfDbl position = null; + AttributeStreamOfDbl zs = null; + AttributeStreamOfDbl ms = null; + AttributeStreamOfInt8 path_flags = null; + AttributeStreamOfInt32 paths = null; + + if (point_count > 0) { + position = (AttributeStreamOfDbl) (polygon_impl + .getAttributeStreamRef(VertexDescription.Semantics.POSITION)); + path_flags = polygon_impl.getPathFlagsStreamRef(); + paths = polygon_impl.getPathStreamRef(); + path_count = polygon_impl.getPathCount(); + + if (b_export_zs) { + if (polygon_impl + ._attributeStreamIsAllocated(VertexDescription.Semantics.Z)) + zs = (AttributeStreamOfDbl) polygon_impl + .getAttributeStreamRef(VertexDescription.Semantics.Z); + } + + if (b_export_ms) { + if (polygon_impl + ._attributeStreamIsAllocated(VertexDescription.Semantics.M)) + ms = (AttributeStreamOfDbl) polygon_impl + .getAttributeStreamRef(VertexDescription.Semantics.M); + } + } + + if ((export_flags & WktExportFlags.wktExportPolygon) != 0) { + if (polygon_count > 1) + throw new IllegalArgumentException(); + + polygonTaggedText_(precision, b_export_zs, b_export_ms, zs, ms, + position, path_flags, paths, path_count, string); + } else { + multiPolygonTaggedText_(precision, b_export_zs, b_export_ms, zs, + ms, position, path_flags, paths, polygon_count, path_count, + string); + } + } + + static void exportPolylineToWkt(int export_flags, Polyline polyline, + StringBuilder string) { + MultiPathImpl polyline_impl = (MultiPathImpl) polyline._getImpl(); + + int point_count = polyline_impl.getPointCount(); + int path_count = polyline_impl.getPathCount(); + + if (point_count > 0 && path_count == 0) + throw new GeometryException("corrupted geometry"); + + int precision = 17 - (7 & (export_flags >> 13)); + boolean b_export_zs = polyline_impl + .hasAttribute(VertexDescription.Semantics.Z) + && (export_flags & WktExportFlags.wktExportStripZs) == 0; + boolean b_export_ms = polyline_impl + .hasAttribute(VertexDescription.Semantics.M) + && (export_flags & WktExportFlags.wktExportStripMs) == 0; + + AttributeStreamOfDbl position = null; + AttributeStreamOfDbl zs = null; + AttributeStreamOfDbl ms = null; + AttributeStreamOfInt8 path_flags = null; + AttributeStreamOfInt32 paths = null; + + if (point_count > 0) { + position = (AttributeStreamOfDbl) polyline_impl + .getAttributeStreamRef(VertexDescription.Semantics.POSITION); + path_flags = polyline_impl.getPathFlagsStreamRef(); + paths = polyline_impl.getPathStreamRef(); + + if (b_export_zs) { + if (polyline_impl + ._attributeStreamIsAllocated(VertexDescription.Semantics.Z)) + zs = (AttributeStreamOfDbl) (polyline_impl + .getAttributeStreamRef(VertexDescription.Semantics.Z)); + } + + if (b_export_ms) { + if (polyline_impl + ._attributeStreamIsAllocated(VertexDescription.Semantics.M)) + ms = (AttributeStreamOfDbl) (polyline_impl + .getAttributeStreamRef(VertexDescription.Semantics.M)); + } + } + + if ((export_flags & WktExportFlags.wktExportLineString) != 0) { + if (path_count > 1) + throw new IllegalArgumentException(); + + lineStringTaggedText_(precision, b_export_zs, b_export_ms, zs, ms, + position, path_flags, paths, string); + } else { + multiLineStringTaggedText_(precision, b_export_zs, b_export_ms, zs, + ms, position, path_flags, paths, path_count, string); + } + } + + static void exportMultiPointToWkt(int export_flags, MultiPoint multipoint, + StringBuilder string) { + MultiPointImpl multipoint_impl = (MultiPointImpl) multipoint._getImpl(); + + int point_count = multipoint_impl.getPointCount(); + + int precision = 17 - (7 & (export_flags >> 13)); + boolean b_export_zs = multipoint_impl + .hasAttribute(VertexDescription.Semantics.Z) + && (export_flags & WktExportFlags.wktExportStripZs) == 0; + boolean b_export_ms = multipoint_impl + .hasAttribute(VertexDescription.Semantics.M) + && (export_flags & WktExportFlags.wktExportStripMs) == 0; + + AttributeStreamOfDbl position = null; + AttributeStreamOfDbl zs = null; + AttributeStreamOfDbl ms = null; + + if (point_count > 0) { + position = (AttributeStreamOfDbl) (multipoint_impl + .getAttributeStreamRef(VertexDescription.Semantics.POSITION)); + + if (b_export_zs) { + if (multipoint_impl + ._attributeStreamIsAllocated(VertexDescription.Semantics.Z)) + zs = (AttributeStreamOfDbl) (multipoint_impl + .getAttributeStreamRef(VertexDescription.Semantics.Z)); + } + + if (b_export_ms) { + if (multipoint_impl + ._attributeStreamIsAllocated(VertexDescription.Semantics.M)) + ms = (AttributeStreamOfDbl) (multipoint_impl + .getAttributeStreamRef(VertexDescription.Semantics.M)); + } + } + + if ((export_flags & WktExportFlags.wktExportPoint) != 0) { + if (point_count > 1) + throw new IllegalArgumentException(); + + pointTaggedTextFromMultiPoint_(precision, b_export_zs, b_export_ms, + zs, ms, position, string); + } else { + multiPointTaggedText_(precision, b_export_zs, b_export_ms, zs, ms, + position, point_count, string); + } + } + + static void exportPointToWkt(int export_flags, Point point, + StringBuilder string) { + int precision = 17 - (7 & (export_flags >> 13)); + boolean b_export_zs = point.hasAttribute(VertexDescription.Semantics.Z) + && (export_flags & WktExportFlags.wktExportStripZs) == 0; + boolean b_export_ms = point.hasAttribute(VertexDescription.Semantics.M) + && (export_flags & WktExportFlags.wktExportStripMs) == 0; + + double x = NumberUtils.TheNaN; + double y = NumberUtils.TheNaN; + double z = NumberUtils.TheNaN; + double m = NumberUtils.TheNaN; + + if (!point.isEmpty()) { + x = point.getX(); + y = point.getY(); + + if (b_export_zs) + z = point.getZ(); + + if (b_export_ms) + m = point.getM(); + } + + if ((export_flags & WktExportFlags.wktExportMultiPoint) != 0) { + multiPointTaggedTextFromPoint_(precision, b_export_zs, b_export_ms, + x, y, z, m, string); + } else { + pointTaggedText_(precision, b_export_zs, b_export_ms, x, y, z, m, + string); + } + } + + static void exportEnvelopeToWkt(int export_flags, Envelope envelope, + StringBuilder string) { + int precision = 17 - (7 & (export_flags >> 13)); + boolean b_export_zs = envelope + .hasAttribute(VertexDescription.Semantics.Z) + && (export_flags & WktExportFlags.wktExportStripZs) == 0; + boolean b_export_ms = envelope + .hasAttribute(VertexDescription.Semantics.M) + && (export_flags & WktExportFlags.wktExportStripMs) == 0; + + double xmin = NumberUtils.TheNaN; + double ymin = NumberUtils.TheNaN; + double xmax = NumberUtils.TheNaN; + double ymax = NumberUtils.TheNaN; + double zmin = NumberUtils.TheNaN; + double zmax = NumberUtils.TheNaN; + double mmin = NumberUtils.TheNaN; + double mmax = NumberUtils.TheNaN; + Envelope1D interval; + + if (!envelope.isEmpty()) { + xmin = envelope.getXMin(); + ymin = envelope.getYMin(); + xmax = envelope.getXMax(); + ymax = envelope.getYMax(); + + if (b_export_zs) { + interval = envelope.queryInterval( + VertexDescription.Semantics.Z, 0); + zmin = interval.vmin; + zmax = interval.vmax; + } + + if (b_export_ms) { + interval = envelope.queryInterval( + VertexDescription.Semantics.M, 0); + mmin = interval.vmin; + mmax = interval.vmax; + } + } + + if ((export_flags & WktExportFlags.wktExportMultiPolygon) != 0) { + multiPolygonTaggedTextFromEnvelope_(precision, b_export_zs, + b_export_ms, xmin, ymin, xmax, ymax, zmin, zmax, mmin, + mmax, string); + } else { + polygonTaggedTextFromEnvelope_(precision, b_export_zs, b_export_ms, + xmin, ymin, xmax, ymax, zmin, zmax, mmin, mmax, string); + } + } + + static void multiPolygonTaggedText_(int precision, boolean b_export_zs, + boolean b_export_ms, AttributeStreamOfDbl zs, + AttributeStreamOfDbl ms, AttributeStreamOfDbl position, + AttributeStreamOfInt8 path_flags, AttributeStreamOfInt32 paths, + int polygon_count, int path_count, StringBuilder string) { + string.append("MULTIPOLYGON "); + + if (b_export_zs && b_export_ms) + string.append("ZM "); + else if (b_export_zs && !b_export_ms) + string.append("Z "); + else if (!b_export_zs && b_export_ms) + string.append("M "); + + if (position == null) { + string.append("EMPTY"); + return; + } + + string.append('('); + + multiPolygonText_(precision, b_export_zs, b_export_ms, zs, ms, + position, path_flags, paths, polygon_count, path_count, string); + + string.append(')'); + } + + static void multiPolygonTaggedTextFromEnvelope_(int precision, + boolean b_export_zs, boolean b_export_ms, double xmin, double ymin, + double xmax, double ymax, double zmin, double zmax, double mmin, + double mmax, StringBuilder string) { + string.append("MULTIPOLYGON "); + + if (b_export_zs && b_export_ms) + string.append("ZM "); + else if (b_export_zs && !b_export_ms) + string.append("Z "); + else if (!b_export_zs && b_export_ms) + string.append("M "); + + if (NumberUtils.isNaN(xmin)) { + string.append("EMPTY"); + return; + } + + string.append('('); + + writeEnvelopeAsWktPolygon_(precision, b_export_zs, b_export_ms, xmin, + ymin, xmax, ymax, zmin, zmax, mmin, mmax, string); + + string.append(')'); + } + + static void multiLineStringTaggedText_(int precision, boolean b_export_zs, + boolean b_export_ms, AttributeStreamOfDbl zs, + AttributeStreamOfDbl ms, AttributeStreamOfDbl position, + AttributeStreamOfInt8 path_flags, AttributeStreamOfInt32 paths, + int path_count, StringBuilder string) { + string.append("MULTILINESTRING "); + + if (b_export_zs && b_export_ms) + string.append("ZM "); + else if (b_export_zs && !b_export_ms) + string.append("Z "); + else if (!b_export_zs && b_export_ms) + string.append("M "); + + if (position == null) { + string.append("EMPTY"); + return; + } + + string.append('('); + + multiLineStringText_(precision, b_export_zs, b_export_ms, zs, ms, + position, path_flags, paths, path_count, string); + + string.append(')'); + } + + static void multiPointTaggedText_(int precision, boolean b_export_zs, + boolean b_export_ms, AttributeStreamOfDbl zs, + AttributeStreamOfDbl ms, AttributeStreamOfDbl position, + int point_count, StringBuilder string) { + string.append("MULTIPOINT "); + + if (b_export_zs && b_export_ms) + string.append("ZM "); + else if (b_export_zs && !b_export_ms) + string.append("Z "); + else if (!b_export_zs && b_export_ms) + string.append("M "); + + if (position == null) { + string.append("EMPTY"); + return; + } + + string.append('('); + + multiPointText_(precision, b_export_zs, b_export_ms, zs, ms, position, + point_count, string); + + string.append(')'); + } + + static void multiPointTaggedTextFromPoint_(int precision, + boolean b_export_zs, boolean b_export_ms, double x, double y, + double z, double m, StringBuilder string) { + string.append("MULTIPOINT "); + + if (b_export_zs && b_export_ms) + string.append("ZM "); + else if (b_export_zs && !b_export_ms) + string.append("Z "); + else if (!b_export_zs && b_export_ms) + string.append("M "); + + if (NumberUtils.isNaN(x)) { + string.append("EMPTY"); + return; + } + + string.append('('); + + pointText_(precision, b_export_zs, b_export_ms, x, y, z, m, string); + + string.append(')'); + } + + static void polygonTaggedText_(int precision, boolean b_export_zs, + boolean b_export_ms, AttributeStreamOfDbl zs, + AttributeStreamOfDbl ms, AttributeStreamOfDbl position, + AttributeStreamOfInt8 path_flags, AttributeStreamOfInt32 paths, + int path_count, StringBuilder string) { + string.append("POLYGON "); + + if (b_export_zs && b_export_ms) + string.append("ZM "); + else if (b_export_zs && !b_export_ms) + string.append("Z "); + else if (!b_export_zs && b_export_ms) + string.append("M "); + + if (position == null) { + string.append("EMPTY"); + return; + } + + polygonText_(precision, b_export_zs, b_export_ms, zs, ms, position, + path_flags, paths, 0, path_count, string); + } + + static void polygonTaggedTextFromEnvelope_(int precision, + boolean b_export_zs, boolean b_export_ms, double xmin, double ymin, + double xmax, double ymax, double zmin, double zmax, double mmin, + double mmax, StringBuilder string) { + string.append("POLYGON "); + + if (b_export_zs && b_export_ms) + string.append("ZM "); + else if (b_export_zs && !b_export_ms) + string.append("Z "); + else if (!b_export_zs && b_export_ms) + string.append("M "); + + if (NumberUtils.isNaN(xmin)) { + string.append("EMPTY"); + return; + } + + writeEnvelopeAsWktPolygon_(precision, b_export_zs, b_export_ms, xmin, + ymin, xmax, ymax, zmin, zmax, mmin, mmax, string); + } + + static void lineStringTaggedText_(int precision, boolean b_export_zs, + boolean b_export_ms, AttributeStreamOfDbl zs, + AttributeStreamOfDbl ms, AttributeStreamOfDbl position, + AttributeStreamOfInt8 path_flags, AttributeStreamOfInt32 paths, + StringBuilder string) { + string.append("LINESTRING "); + + if (b_export_zs && b_export_ms) + string.append("ZM "); + else if (b_export_zs && !b_export_ms) + string.append("Z "); + else if (!b_export_zs && b_export_ms) + string.append("M "); + + if (position == null) { + string.append("EMPTY"); + return; + } + + boolean b_closed = ((path_flags.read(0) & PathFlags.enumClosed) != 0); + + lineStringText_(false, b_closed, precision, b_export_zs, b_export_ms, + zs, ms, position, paths, 0, string); + } + + static void pointTaggedText_(int precision, boolean b_export_zs, + boolean b_export_ms, double x, double y, double z, double m, + StringBuilder string) { + string.append("POINT "); + + if (b_export_zs && b_export_ms) + string.append("ZM "); + else if (b_export_zs && !b_export_ms) + string.append("Z "); + else if (!b_export_zs && b_export_ms) + string.append("M "); + + if (NumberUtils.isNaN(x)) { + string.append("EMPTY"); + return; + } + + pointText_(precision, b_export_zs, b_export_ms, x, y, z, m, string); + } + + static void pointTaggedTextFromMultiPoint_(int precision, + boolean b_export_zs, boolean b_export_ms, AttributeStreamOfDbl zs, + AttributeStreamOfDbl ms, AttributeStreamOfDbl position, + StringBuilder string) { + string.append("POINT "); + + if (b_export_zs && b_export_ms) + string.append("ZM "); + else if (b_export_zs && !b_export_ms) + string.append("Z "); + else if (!b_export_zs && b_export_ms) + string.append("M "); + + if (position == null) { + string.append("EMPTY"); + return; + } + + pointText_(precision, b_export_zs, b_export_ms, zs, ms, position, 0, + string); + } + + static void multiPolygonText_(int precision, boolean b_export_zs, + boolean b_export_ms, AttributeStreamOfDbl zs, + AttributeStreamOfDbl ms, AttributeStreamOfDbl position, + AttributeStreamOfInt8 path_flags, AttributeStreamOfInt32 paths, + int polygon_count, int path_count, StringBuilder string) { + int polygon_start = 0; + int polygon_end = 1; + + while (polygon_end < path_count + && (path_flags.read(polygon_end) & PathFlags.enumOGCStartPolygon) == 0) + polygon_end++; + + polygonText_(precision, b_export_zs, b_export_ms, zs, ms, position, + path_flags, paths, polygon_start, polygon_end, string); + + for (int ipolygon = 1; ipolygon < polygon_count; ipolygon++) { + polygon_start = polygon_end; + polygon_end++; + + while (polygon_end < path_count + && (path_flags.read(polygon_end) & PathFlags.enumOGCStartPolygon) == 0) + polygon_end++; + + string.append(", "); + polygonText_(precision, b_export_zs, b_export_ms, zs, ms, position, + path_flags, paths, polygon_start, polygon_end, string); + } + } + + static void multiLineStringText_(int precision, boolean b_export_zs, + boolean b_export_ms, AttributeStreamOfDbl zs, + AttributeStreamOfDbl ms, AttributeStreamOfDbl position, + AttributeStreamOfInt8 path_flags, AttributeStreamOfInt32 paths, + int path_count, StringBuilder string) { + boolean b_closed = ((path_flags.read(0) & PathFlags.enumClosed) != 0); + + lineStringText_(false, b_closed, precision, b_export_zs, b_export_ms, + zs, ms, position, paths, 0, string); + + for (int path = 1; path < path_count; path++) { + string.append(", "); + + b_closed = ((path_flags.read(path) & PathFlags.enumClosed) != 0); + + lineStringText_(false, b_closed, precision, b_export_zs, + b_export_ms, zs, ms, position, paths, path, string); + } + } + + static void multiPointText_(int precision, boolean b_export_zs, + boolean b_export_ms, AttributeStreamOfDbl zs, + AttributeStreamOfDbl ms, AttributeStreamOfDbl position, + int point_count, StringBuilder string) { + pointText_(precision, b_export_zs, b_export_ms, zs, ms, position, 0, + string); + + for (int point = 1; point < point_count; point++) { + string.append(", "); + pointText_(precision, b_export_zs, b_export_ms, zs, ms, position, + point, string); + } + } + + static void polygonText_(int precision, boolean b_export_zs, + boolean b_export_ms, AttributeStreamOfDbl zs, + AttributeStreamOfDbl ms, AttributeStreamOfDbl position, + AttributeStreamOfInt8 path_flags, AttributeStreamOfInt32 paths, + int polygon_start, int polygon_end, StringBuilder string) { + string.append('('); + + lineStringText_(true, true, precision, b_export_zs, b_export_ms, zs, + ms, position, paths, polygon_start, string); + + for (int path = polygon_start + 1; path < polygon_end; path++) { + string.append(", "); + lineStringText_(true, true, precision, b_export_zs, b_export_ms, + zs, ms, position, paths, path, string); + } + + string.append(')'); + } + + static void lineStringText_(boolean bRing, boolean b_closed, int precision, + boolean b_export_zs, boolean b_export_ms, AttributeStreamOfDbl zs, + AttributeStreamOfDbl ms, AttributeStreamOfDbl position, + AttributeStreamOfInt32 paths, int path, StringBuilder string) { + int istart = paths.read(path); + int iend = paths.read(path + 1); + + if (istart == iend) { + string.append("EMPTY"); + return; + } + + string.append('('); + + if (bRing) { + point_(precision, b_export_zs, b_export_ms, zs, ms, position, + istart, string); + string.append(", "); + + for (int point = iend - 1; point >= istart + 1; point--) { + point_(precision, b_export_zs, b_export_ms, zs, ms, position, + point, string); + string.append(", "); + } + + point_(precision, b_export_zs, b_export_ms, zs, ms, position, + istart, string); + } else { + for (int point = istart; point < iend - 1; point++) { + point_(precision, b_export_zs, b_export_ms, zs, ms, position, + point, string); + string.append(", "); + } + + point_(precision, b_export_zs, b_export_ms, zs, ms, position, + iend - 1, string); + + if (b_closed) { + string.append(", "); + point_(precision, b_export_zs, b_export_ms, zs, ms, position, + istart, string); + } + } + + string.append(')'); + } + + static int pointText_(int precision, boolean b_export_zs, + boolean b_export_ms, double x, double y, double z, double m, + StringBuilder string) { + string.append('('); + point_(precision, b_export_zs, b_export_ms, x, y, z, m, string); + string.append(')'); + + return 1; + } + + static void pointText_(int precision, boolean b_export_zs, + boolean b_export_ms, AttributeStreamOfDbl zs, + AttributeStreamOfDbl ms, AttributeStreamOfDbl position, int point, + StringBuilder string) { + double x = position.read(2 * point); + double y = position.read(2 * point + 1); + double z = NumberUtils.TheNaN; + double m = NumberUtils.TheNaN; + + if (b_export_zs) + z = (zs != null ? zs.read(point) : VertexDescription + .getDefaultValue(VertexDescription.Semantics.Z)); + + if (b_export_ms) + m = (ms != null ? ms.read(point) : VertexDescription + .getDefaultValue(VertexDescription.Semantics.M)); + + pointText_(precision, b_export_zs, b_export_ms, x, y, z, m, string); + } + + static void point_(int precision, boolean b_export_zs, boolean b_export_ms, + double x, double y, double z, double m, StringBuilder string) { + writeSignedNumericLiteral_(x, precision, string); + string.append(' '); + writeSignedNumericLiteral_(y, precision, string); + + if (b_export_zs) { + string.append(' '); + writeSignedNumericLiteral_(z, precision, string); + } + + if (b_export_ms) { + string.append(' '); + writeSignedNumericLiteral_(m, precision, string); + } + } + + static void point_(int precision, boolean b_export_zs, boolean b_export_ms, + AttributeStreamOfDbl zs, AttributeStreamOfDbl ms, + AttributeStreamOfDbl position, int point, StringBuilder string) { + double x = position.read(2 * point); + double y = position.read(2 * point + 1); + double z = NumberUtils.TheNaN; + double m = NumberUtils.TheNaN; + + if (b_export_zs) + z = (zs != null ? zs.read(point) : VertexDescription + .getDefaultValue(VertexDescription.Semantics.Z)); + + if (b_export_ms) + m = (ms != null ? ms.read(point) : VertexDescription + .getDefaultValue(VertexDescription.Semantics.M)); + + point_(precision, b_export_zs, b_export_ms, x, y, z, m, string); + } + + static boolean writeSignedNumericLiteral_(double v, int precision, + StringBuilder string) { + if (NumberUtils.isNaN(v)) { + string.append("NAN"); + return false; + } + + StringUtils.appendDouble(v, precision, string); + return true; + } + + static void writeEnvelopeAsWktPolygon_(int precision, boolean b_export_zs, + boolean b_export_ms, double xmin, double ymin, double xmax, + double ymax, double zmin, double zmax, double mmin, double mmax, + StringBuilder string) { + string.append("(("); + + writeSignedNumericLiteral_(xmin, precision, string); + string.append(' '); + writeSignedNumericLiteral_(ymin, precision, string); + + if (b_export_zs) { + string.append(' '); + writeSignedNumericLiteral_(zmin, precision, string); + } + + if (b_export_ms) { + string.append(' '); + writeSignedNumericLiteral_(mmin, precision, string); + } + + string.append(", "); + + writeSignedNumericLiteral_(xmax, precision, string); + string.append(' '); + writeSignedNumericLiteral_(ymin, precision, string); + + if (b_export_zs) { + string.append(' '); + writeSignedNumericLiteral_(zmax, precision, string); + } + + if (b_export_ms) { + string.append(' '); + writeSignedNumericLiteral_(mmax, precision, string); + } + + string.append(", "); + + writeSignedNumericLiteral_(xmax, precision, string); + string.append(' '); + writeSignedNumericLiteral_(ymax, precision, string); + + if (b_export_zs) { + string.append(' '); + writeSignedNumericLiteral_(zmin, precision, string); + } + + if (b_export_ms) { + string.append(' '); + writeSignedNumericLiteral_(mmin, precision, string); + } + + string.append(", "); + + writeSignedNumericLiteral_(xmin, precision, string); + string.append(' '); + writeSignedNumericLiteral_(ymax, precision, string); + + if (b_export_zs) { + string.append(' '); + writeSignedNumericLiteral_(zmax, precision, string); + } + + if (b_export_ms) { + string.append(' '); + writeSignedNumericLiteral_(mmax, precision, string); + } + + string.append(", "); + + writeSignedNumericLiteral_(xmin, precision, string); + string.append(' '); + writeSignedNumericLiteral_(ymin, precision, string); + + if (b_export_zs) { + string.append(' '); + writeSignedNumericLiteral_(zmin, precision, string); + } + + if (b_export_ms) { + string.append(' '); + writeSignedNumericLiteral_(mmin, precision, string); + } + + string.append("))"); + } +} diff --git a/src/com/esri/core/geometry/OperatorFactory.java b/src/com/esri/core/geometry/OperatorFactory.java new file mode 100644 index 00000000..680cdfee --- /dev/null +++ b/src/com/esri/core/geometry/OperatorFactory.java @@ -0,0 +1,43 @@ +/* + Copyright 1995-2013 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ + + +package com.esri.core.geometry; + +import com.esri.core.geometry.Operator.Type; + +/** + *An abstract class that represent the basic OperatorFactory interface. + */ +public abstract class OperatorFactory { + /** + *Returns True if the given operator exists. The type is one of the Operator::Type values or a user defined value. + */ + public abstract boolean isOperatorSupported(Type type); + + /** + *Returns an operator of the given type. Throws an exception if the operator is not supported. + */ + public abstract Operator getOperator(Type type); +} diff --git a/src/com/esri/core/geometry/OperatorFactoryLocal.java b/src/com/esri/core/geometry/OperatorFactoryLocal.java new file mode 100644 index 00000000..a50951fb --- /dev/null +++ b/src/com/esri/core/geometry/OperatorFactoryLocal.java @@ -0,0 +1,252 @@ +/* + Copyright 1995-2013 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ + +package com.esri.core.geometry; + +import com.esri.core.geometry.Operator.Type; +import java.io.BufferedReader; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.InputStreamReader; +import java.io.PrintStream; +import java.io.Reader; +import java.util.HashMap; +import org.codehaus.jackson.JsonFactory; +import org.codehaus.jackson.JsonParser; + +/** + *An abstract class that represent the basic OperatorFactory interface. + */ +public class OperatorFactoryLocal extends OperatorFactory { + private static final OperatorFactoryLocal INSTANCE = new OperatorFactoryLocal(); + + private static final HashMap st_supportedOperators = new HashMap(); + + static { + // Register all implemented operator allocators in the dictionary + + st_supportedOperators.put(Type.Project, new OperatorProjectLocal()); + st_supportedOperators.put(Type.ExportToJson, + new OperatorExportToJsonLocal()); + st_supportedOperators.put(Type.ImportMapGeometryFromJson, + new OperatorImportFromJsonLocal()); + + st_supportedOperators.put(Type.ExportToESRIShape, + new OperatorExportToESRIShapeLocal()); + st_supportedOperators.put(Type.ImportFromESRIShape, + new OperatorImportFromESRIShapeLocal()); + + st_supportedOperators.put(Type.Proximity2D, + new OperatorProximity2DLocal()); + st_supportedOperators.put(Type.DensifyByLength, + new OperatorDensifyByLengthLocal()); + + st_supportedOperators.put(Type.Relate, new OperatorRelateLocal()); + st_supportedOperators.put(Type.Equals, new OperatorEqualsLocal()); + st_supportedOperators.put(Type.Disjoint, new OperatorDisjointLocal()); + + st_supportedOperators.put(Type.Intersects, + new OperatorIntersectsLocal()); + st_supportedOperators.put(Type.Within, new OperatorWithinLocal()); + st_supportedOperators.put(Type.Contains, new OperatorContainsLocal()); + st_supportedOperators.put(Type.Crosses, new OperatorCrossesLocal()); + st_supportedOperators.put(Type.Touches, new OperatorTouchesLocal()); + st_supportedOperators.put(Type.Overlaps, new OperatorOverlapsLocal()); + + st_supportedOperators.put(Type.SimplifyOGC, + new OperatorSimplifyLocalOGC()); + st_supportedOperators.put(Type.Simplify, new OperatorSimplifyLocal()); + st_supportedOperators.put(Type.Offset, new OperatorOffsetLocal()); + + st_supportedOperators.put(Type.GeodeticLength, + new OperatorGeodeticLengthLocal()); + st_supportedOperators.put(Type.GeodeticArea, + new OperatorGeodeticAreaLocal()); + + st_supportedOperators.put(Type.Buffer, new OperatorBufferLocal()); + st_supportedOperators.put(Type.Distance, new OperatorDistanceLocal()); + st_supportedOperators.put(Type.Intersection, + new OperatorIntersectionLocal()); + st_supportedOperators.put(Type.Difference, + new OperatorDifferenceLocal()); + st_supportedOperators.put(Type.SymmetricDifference, + new OperatorSymmetricDifferenceLocal()); + st_supportedOperators.put(Type.Clip, new OperatorClipLocal()); + st_supportedOperators.put(Type.Cut, new OperatorCutLocal()); + st_supportedOperators.put(Type.ExportToWkb, + new OperatorExportToWkbLocal()); + st_supportedOperators.put(Type.ImportFromWkb, + new OperatorImportFromWkbLocal()); + st_supportedOperators.put(Type.ExportToWkt, + new OperatorExportToWktLocal()); + st_supportedOperators.put(Type.ImportFromWkt, + new OperatorImportFromWktLocal()); + st_supportedOperators.put(Type.ImportFromGeoJson, + new OperatorImportFromGeoJsonLocal()); + st_supportedOperators.put(Type.Union, new OperatorUnionLocal()); + + st_supportedOperators.put(Type.Generalize, + new OperatorGeneralizeLocal()); + st_supportedOperators.put(Type.ConvexHull, + new OperatorConvexHullLocal()); + st_supportedOperators.put(Type.Boundary, new OperatorBoundaryLocal()); + + // LabelPoint, + // Simplify, + // + + } + + private OperatorFactoryLocal() { + m_bNewTopo = false;// use sg by default + // m_bNewTopo = true;//use sg by default + + } + + /** + * A temporary way to switch to new topo engine from SG. Set it to true, to + * switch. Need to be changed once at the startup of the program. + */ + boolean m_bNewTopo; + + /** + *Returns a reference to the singleton. + */ + public static OperatorFactoryLocal getInstance() { + return INSTANCE; + } + + @Override + public Operator getOperator(Type type) { + if (st_supportedOperators.containsKey(type)) { + return st_supportedOperators.get(type); + } else { + throw new IllegalArgumentException(); + } + } + + @Override + public boolean isOperatorSupported(Operator.Type type) { + return st_supportedOperators.containsKey(type); + } + + public static void saveJSONToTextFileDbg(String file_name, + Geometry geometry, SpatialReference spatial_ref) { + if (file_name == null) { + throw new IllegalArgumentException(); + } + + OperatorFactoryLocal engine = OperatorFactoryLocal.getInstance(); + OperatorExportToJson exporterJSON = (OperatorExportToJson) engine + .getOperator(Operator.Type.ExportToJson); + String jsonString = exporterJSON.execute(spatial_ref, geometry); + + try { + FileOutputStream outfile = new FileOutputStream(file_name); + PrintStream p = new PrintStream(outfile); + p.print(jsonString); + p.close(); + } catch (Exception ex) { + } + } + + public static MapGeometry loadGeometryFromJSONFileDbg(String file_name) { + if (file_name == null) { + throw new IllegalArgumentException(); + } + + String jsonString = null; + try { + FileInputStream stream = new FileInputStream(file_name); + Reader reader = new BufferedReader(new InputStreamReader(stream)); + StringBuilder builder = new StringBuilder(); + char[] buffer = new char[8192]; + int read; + while ((read = reader.read(buffer, 0, buffer.length)) > 0) { + builder.append(buffer, 0, read); + } + stream.close(); + + jsonString = builder.toString(); + } catch (Exception ex) { + } + + OperatorFactoryLocal engine = OperatorFactoryLocal.getInstance(); + OperatorImportFromJson importerJSON = (OperatorImportFromJson) engine + .getOperator(Operator.Type.ImportMapGeometryFromJson); + + JsonFactory jf = new JsonFactory(); + JsonParser jp = null; + try { + jp = jf.createJsonParser(jsonString); + jp.nextToken(); + } catch (Exception ex) { + } + MapGeometry mapGeom = importerJSON.execute(Geometry.Type.Unknown, jp); + return mapGeom; + } + + public static void saveToWKTFileDbg(String file_name, + Geometry geometry, SpatialReference spatial_ref) { + if (file_name == null) { + throw new IllegalArgumentException(); + } + + String jsonString = OperatorExportToWkt.local().execute(0, geometry, null); + + try { + FileOutputStream outfile = new FileOutputStream(file_name); + PrintStream p = new PrintStream(outfile); + p.print(jsonString); + p.close(); + } catch (Exception ex) { + } + } + + public static Geometry loadGeometryFromWKTFileDbg(String file_name) { + if (file_name == null) { + throw new IllegalArgumentException(); + } + + String s = null; + try { + FileInputStream stream = new FileInputStream(file_name); + Reader reader = new BufferedReader(new InputStreamReader(stream)); + StringBuilder builder = new StringBuilder(); + char[] buffer = new char[8192]; + int read; + while ((read = reader.read(buffer, 0, buffer.length)) > 0) { + builder.append(buffer, 0, read); + } + stream.close(); + + s = builder.toString(); + } catch (Exception ex) { + } + + return OperatorImportFromWkt.local().execute(0, Geometry.Type.Unknown, s, null); + } + + +} diff --git a/src/com/esri/core/geometry/OperatorGeneralize.java b/src/com/esri/core/geometry/OperatorGeneralize.java new file mode 100644 index 00000000..bf78aaab --- /dev/null +++ b/src/com/esri/core/geometry/OperatorGeneralize.java @@ -0,0 +1,60 @@ +/* + Copyright 1995-2013 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ +package com.esri.core.geometry; + +import com.esri.core.geometry.Operator.Type; + +/** + * Generalizes geometries using Douglas-Poiker algorithm. + */ +public abstract class OperatorGeneralize extends Operator { + @Override + public Type getType() { + return Type.Generalize; + } + + /** + * Performs the Generalize operation on the geometry set. Point and + * MultiPoint geometries are left unchanged. Envelope is converted to a + * Polygon. + * + */ + public abstract GeometryCursor execute(GeometryCursor geoms, + double maxDeviation, boolean bRemoveDegenerateParts, + ProgressTracker progressTracker); + + /** + * Performs the Generalize operation on single geometry. Point and + * MultiPoint geometries are left unchanged. Envelope is converted to a + * Polygon. + */ + public abstract Geometry execute(Geometry geom, double maxDeviation, + boolean bRemoveDegenerateParts, ProgressTracker progressTracker); + + public static OperatorGeneralize local() { + return (OperatorGeneralize) OperatorFactoryLocal.getInstance() + .getOperator(Type.Generalize); + } + +} diff --git a/src/com/esri/core/geometry/OperatorGeneralizeCursor.java b/src/com/esri/core/geometry/OperatorGeneralizeCursor.java new file mode 100644 index 00000000..8e04928a --- /dev/null +++ b/src/com/esri/core/geometry/OperatorGeneralizeCursor.java @@ -0,0 +1,171 @@ +/* + Copyright 1995-2013 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ +package com.esri.core.geometry; + +final class OperatorGeneralizeCursor extends GeometryCursor { + ProgressTracker m_progressTracker; + GeometryCursor m_geoms; + double m_maxDeviation; + boolean m_bRemoveDegenerateParts; + + public OperatorGeneralizeCursor(GeometryCursor geoms, double maxDeviation, + boolean bRemoveDegenerateParts, ProgressTracker progressTracker) { + m_geoms = geoms; + m_maxDeviation = maxDeviation; + m_progressTracker = progressTracker; + m_bRemoveDegenerateParts = bRemoveDegenerateParts; + } + + @Override + public Geometry next() { + // TODO Auto-generated method stub + Geometry geom = m_geoms.next(); + if (geom == null) + return null; + return Generalize(geom); + } + + @Override + public int getGeometryID() { + // TODO Auto-generated method stub + return m_geoms.getGeometryID(); + } + + private Geometry Generalize(Geometry geom) { + Geometry.Type gt = geom.getType(); + if (Geometry.isPoint(gt.value())) + return geom; + if (gt == Geometry.Type.Envelope) { + Polygon poly = new Polygon(geom.getDescription()); + poly.addEnvelope((Envelope) geom, false); + return Generalize(poly); + } + if (geom.isEmpty()) + return geom; + MultiPath mp = (MultiPath) geom; + if (mp == null) + throw new GeometryException("internal error"); + MultiPath dstmp = (MultiPath) geom.createInstance(); + Line line = new Line(); + for (int ipath = 0, npath = mp.getPathCount(); ipath < npath; ipath++) { + GeneralizePath((MultiPathImpl) mp._getImpl(), ipath, + (MultiPathImpl) dstmp._getImpl(), line); + } + + return dstmp; + } + + private void GeneralizePath(MultiPathImpl mpsrc, int ipath, + MultiPathImpl mpdst, Line lineHelper) { + if (mpsrc.getPathSize(ipath) < 2) + return; + int start = mpsrc.getPathStart(ipath); + int end = mpsrc.getPathEnd(ipath) - 1; + AttributeStreamOfDbl xy = (AttributeStreamOfDbl) mpsrc + .getAttributeStreamRef(VertexDescription.Semantics.POSITION); + boolean bClosed = mpsrc.isClosedPath(ipath); + + AttributeStreamOfInt32 stack = new AttributeStreamOfInt32(0); + stack.reserve(mpsrc.getPathSize(ipath) + 1); + AttributeStreamOfInt32 resultStack = new AttributeStreamOfInt32(0); + resultStack.reserve(mpsrc.getPathSize(ipath) + 1); + stack.add(bClosed ? start : end); + stack.add(start); + Point2D pt = new Point2D(); + while (stack.size() > 1) { + int i1 = stack.getLast(); + stack.removeLast(); + int i2 = stack.getLast(); + mpsrc.getXY(i1, pt); + lineHelper.setStartXY(pt); + mpsrc.getXY(i2, pt); + lineHelper.setEndXY(pt); + int mid = FindGreatestDistance(lineHelper, pt, xy, i1, i2, end); + if (mid >= 0) { + stack.add(mid); + stack.add(i1); + } else { + resultStack.add(i1); + } + } + + if (!bClosed) + resultStack.add(stack.get(0)); + + if (resultStack.size() == stack.size()) { + mpdst.addPath(mpsrc, ipath, true); + } else { + if (resultStack.size() >= 2) { + if (m_bRemoveDegenerateParts && resultStack.size() == 2) { + if (bClosed) + return; + double d = Point2D.distance( + mpsrc.getXY(resultStack.get(0)), + mpsrc.getXY(resultStack.get(1))); + if (d <= m_maxDeviation) + return; + } + Point point = new Point(); + for (int i = 0, n = resultStack.size(); i < n; i++) { + mpsrc.getPointByVal(resultStack.get(i), point); + if (i == 0) + mpdst.startPath(point); + else + mpdst.lineTo(point); + } + + if (bClosed) { + if (!m_bRemoveDegenerateParts && resultStack.size() == 2) + mpdst.lineTo(point); + mpdst.closePathWithLine(); + } + } + } + } + + private int FindGreatestDistance(Line line, Point2D ptHelper, + AttributeStreamOfDbl xy, int start, int end, int pathEnd) { + int to = end - 1; + if (end <= start) {// closed path case. end is equal to the path start. + to = pathEnd; + } + int mid = -1; + double maxd = -1.0; + for (int i = start + 1; i <= to; i++) { + xy.read(2 * i, ptHelper); + double x1 = ptHelper.x; + double y1 = ptHelper.y; + double t = line.getClosestCoordinate(ptHelper, false); + line.getCoord2D(t, ptHelper); + ptHelper.x -= x1; + ptHelper.y -= y1; + double dist = ptHelper.length(); + if (dist > m_maxDeviation && dist > maxd) { + mid = i; + maxd = dist; + } + } + return mid; + } +} diff --git a/src/com/esri/core/geometry/OperatorGeneralizeLocal.java b/src/com/esri/core/geometry/OperatorGeneralizeLocal.java new file mode 100644 index 00000000..cf759c4b --- /dev/null +++ b/src/com/esri/core/geometry/OperatorGeneralizeLocal.java @@ -0,0 +1,47 @@ +/* + Copyright 1995-2013 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ +package com.esri.core.geometry; + +final class OperatorGeneralizeLocal extends OperatorGeneralize { + + @Override + public GeometryCursor execute(GeometryCursor geoms, double maxDeviation, + boolean bRemoveDegenerateParts, ProgressTracker progressTracker) { + // TODO Auto-generated method stub + + return new OperatorGeneralizeCursor(geoms, maxDeviation, + bRemoveDegenerateParts, progressTracker); + } + + @Override + public Geometry execute(Geometry geom, double maxDeviation, + boolean bRemoveDegenerateParts, ProgressTracker progressTracker) { + SimpleGeometryCursor inputGeomCurs = new SimpleGeometryCursor(geom); + GeometryCursor geometryCursor = execute(inputGeomCurs, maxDeviation, + bRemoveDegenerateParts, progressTracker); + + return geometryCursor.next(); + } + +} diff --git a/src/com/esri/core/geometry/OperatorGeodeticArea.java b/src/com/esri/core/geometry/OperatorGeodeticArea.java new file mode 100644 index 00000000..3e45ed36 --- /dev/null +++ b/src/com/esri/core/geometry/OperatorGeodeticArea.java @@ -0,0 +1,77 @@ +/* + Copyright 1995-2013 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ + +package com.esri.core.geometry; + +import com.esri.core.geometry.Operator.Type; + +/** + * A base class for the ExportToESRIShape Operator. + */ +abstract class OperatorGeodeticArea extends Operator { + + @Override + public Type getType() { + return Type.GeodeticArea; + } + + /** + * Calculates the geodetic area of each geometry in the geometry cursor. + * + * @param geoms + * The geometry cursor to be iterated over to perform the + * Geodetic Area calculation. + * @param sr + * The SpatialReference of the geometries. + * @param geodeticCurveType + * Use the {@link GeodeticCurveType} interface to choose the + * interpretation of a line connecting two points. + * @param progressTracker + * @return Returns an array of the geodetic areas of the geometries. + */ + public abstract double[] execute(GeometryCursor geoms, SpatialReference sr, + int geodeticCurveType, ProgressTracker progressTracker); + + /** + * Calculates the geodetic area of the input Geometry. + * + * @param geom + * The input Geometry for the geodetic area calculation. + * @param sr + * The SpatialReference of the Geometry. + * @param geodeticCurveType + * Use the {@link GeodeticCurveType} interface to choose the + * interpretation of a line connecting two points. + * @param progressTracker + * @return Returns the geodetic area of the Geometry. + */ + public abstract double execute(Geometry geom, SpatialReference sr, + int geodeticCurveType, ProgressTracker progressTracker); + + public static OperatorGeodeticArea local() { + return (OperatorGeodeticArea) OperatorFactoryLocal.getInstance() + .getOperator(Type.GeodeticArea); + } + +} diff --git a/src/com/esri/core/geometry/OperatorGeodeticAreaLocal.java b/src/com/esri/core/geometry/OperatorGeodeticAreaLocal.java new file mode 100644 index 00000000..441f0ec0 --- /dev/null +++ b/src/com/esri/core/geometry/OperatorGeodeticAreaLocal.java @@ -0,0 +1,40 @@ +/* + Copyright 1995-2013 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ + +package com.esri.core.geometry; + +//This is a stub +class OperatorGeodeticAreaLocal extends OperatorGeodeticArea { + @Override + public double[] execute(GeometryCursor geoms, SpatialReference sr, + int geodeticCurveType, ProgressTracker progressTracker) { + throw new GeometryException("not implemented"); + } + + @Override + public double execute(Geometry geom, SpatialReference sr, + int geodeticCurveType, ProgressTracker progressTracker) { + throw new GeometryException("not implemented"); + } +} diff --git a/src/com/esri/core/geometry/OperatorGeodeticLength.java b/src/com/esri/core/geometry/OperatorGeodeticLength.java new file mode 100644 index 00000000..d43fe141 --- /dev/null +++ b/src/com/esri/core/geometry/OperatorGeodeticLength.java @@ -0,0 +1,79 @@ +/* + Copyright 1995-2013 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ + +package com.esri.core.geometry; + +import com.esri.core.geometry.Operator.Type; + +/** + * + * Geodetic length calculation. + * + */ +abstract class OperatorGeodeticLength extends Operator { + + @Override + public Type getType() { + return Operator.Type.GeodeticLength; + } + + /** + * Calculates the geodetic length of each geometry in the geometry cursor. + * + * @param geoms + * The geometry cursor to be iterated over to perform the + * Geodetic Length calculation. + * @param sr + * The SpatialReference of the geometries. + * @param geodeticCurveType + * Use the {@link GeodeticCurveType} interface to choose the + * interpretation of a line connecting two points. + * @param progressTracker + * @return Returns an array of the geoetic lengths of the geometries. + */ + public abstract double[] execute(GeometryCursor geoms, SpatialReference sr, + int geodeticCurveType, ProgressTracker progressTracker); + + /** + * Calculates the geodetic length of the input Geometry. + * + * @param geom + * The input Geometry for the geodetic length calculation. + * @param sr + * The SpatialReference of the Geometry. + * @param geodeticCurveType + * Use the {@link GeodeticCurveType} interface to choose the + * interpretation of a line connecting two points. + * @param progressTracker + * @return Returns the geoetic length of the Geometry. + */ + public abstract double execute(Geometry geom, SpatialReference sr, + int geodeticCurveType, ProgressTracker progressTracker); + + public static OperatorGeodeticLength local() { + return (OperatorGeodeticLength) OperatorFactoryLocal.getInstance() + .getOperator(Type.GeodeticLength); + } + +} diff --git a/src/com/esri/core/geometry/OperatorGeodeticLengthLocal.java b/src/com/esri/core/geometry/OperatorGeodeticLengthLocal.java new file mode 100644 index 00000000..4c57ca28 --- /dev/null +++ b/src/com/esri/core/geometry/OperatorGeodeticLengthLocal.java @@ -0,0 +1,40 @@ +/* + Copyright 1995-2013 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ + +package com.esri.core.geometry; + +//This is a stub +class OperatorGeodeticLengthLocal extends OperatorGeodeticLength { + @Override + public double[] execute(GeometryCursor geoms, SpatialReference sr, + int geodeticCurveType, ProgressTracker progressTracker) { + throw new GeometryException("not implemented"); + } + + @Override + public double execute(Geometry geom, SpatialReference sr, + int geodeticCurveType, ProgressTracker progressTracker) { + throw new GeometryException("not implemented"); + } +} diff --git a/src/com/esri/core/geometry/OperatorImportFromESRIShape.java b/src/com/esri/core/geometry/OperatorImportFromESRIShape.java new file mode 100644 index 00000000..32c7f778 --- /dev/null +++ b/src/com/esri/core/geometry/OperatorImportFromESRIShape.java @@ -0,0 +1,63 @@ +/* + Copyright 1995-2013 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ + +package com.esri.core.geometry; + +import java.nio.ByteBuffer; + +import com.esri.core.geometry.Operator.Type; + +/** + *Import from ESRI shape format. + */ +public abstract class OperatorImportFromESRIShape extends Operator { + @Override + public Type getType() { + return Type.ImportFromESRIShape; + } + + /** + * Performs the ImportFromESRIShape operation on a stream of shape buffers + * + * @return Returns a GeometryCursor. + */ + abstract GeometryCursor execute(int importFlags, Geometry.Type type, + ByteBufferCursor shapeBuffers); + + /** + * Performs the ImportFromESRIShape operation. + * @param importFlags Use the {@link ShapeImportFlags} interface. + * @param type Use the {@link Geometry.Type} enum. + * @param shapeBuffer The buffer holding the Geometry in ESRIShape format. + * @return Returns the imported Geometry. + */ + public abstract Geometry execute(int importFlags, Geometry.Type type, + ByteBuffer shapeBuffer); + + public static OperatorImportFromESRIShape local() { + return (OperatorImportFromESRIShape) OperatorFactoryLocal.getInstance() + .getOperator(Type.ImportFromESRIShape); + } + +} diff --git a/src/com/esri/core/geometry/OperatorImportFromESRIShapeCursor.java b/src/com/esri/core/geometry/OperatorImportFromESRIShapeCursor.java new file mode 100644 index 00000000..982abe03 --- /dev/null +++ b/src/com/esri/core/geometry/OperatorImportFromESRIShapeCursor.java @@ -0,0 +1,1012 @@ +/* + Copyright 1995-2013 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ + +package com.esri.core.geometry; + +import com.esri.core.geometry.MultiVertexGeometryImpl.GeometryXSimple; +import com.esri.core.geometry.VertexDescription.Semantics; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + +class OperatorImportFromESRIShapeCursor extends GeometryCursor { + + ByteBufferCursor m_inputShapeBuffers; + int m_importFlags; + int m_type; + int m_index; + + public OperatorImportFromESRIShapeCursor(int importFlags, int type, + ByteBufferCursor shapeBuffers) { + m_index = -1; + if (shapeBuffers == null) + throw new GeometryException("invalid argument"); + + m_importFlags = importFlags; + m_type = type; + m_inputShapeBuffers = shapeBuffers; + } + + @Override + public Geometry next() { + ByteBuffer shapeBuffer = m_inputShapeBuffers.next(); + if (shapeBuffer != null) { + m_index = m_inputShapeBuffers.getByteBufferID(); + return importFromESRIShape(shapeBuffer); + } + return null; + } + + @Override + public int getGeometryID() { + return m_index; + } + + private Geometry importFromESRIShape(ByteBuffer shapeBuffer) { + // read type + int shapetype = shapeBuffer.getInt(0); + + // Extract general type and modifiers + int generaltype; + int modifiers; + switch (shapetype & ShapeModifiers.ShapeBasicTypeMask) { + // Polygon + case ShapeType.ShapePolygon: + generaltype = ShapeType.ShapeGeneralPolygon; + modifiers = 0; + break; + case ShapeType.ShapePolygonZM: + generaltype = ShapeType.ShapeGeneralPolygon; + modifiers = ShapeModifiers.ShapeHasZs | ShapeModifiers.ShapeHasMs; + break; + case ShapeType.ShapePolygonM: + generaltype = ShapeType.ShapeGeneralPolygon; + modifiers = ShapeModifiers.ShapeHasMs; + break; + case ShapeType.ShapePolygonZ: + generaltype = ShapeType.ShapeGeneralPolygon; + modifiers = ShapeModifiers.ShapeHasZs; + break; + case ShapeType.ShapeGeneralPolygon: + generaltype = ShapeType.ShapeGeneralPolygon; + modifiers = shapetype & ShapeModifiers.ShapeModifierMask; + break; + + // Polyline + case ShapeType.ShapePolyline: + generaltype = ShapeType.ShapeGeneralPolyline; + modifiers = 0; + break; + case ShapeType.ShapePolylineZM: + generaltype = ShapeType.ShapeGeneralPolyline; + modifiers = ShapeModifiers.ShapeHasZs + | (int) ShapeModifiers.ShapeHasMs; + break; + case ShapeType.ShapePolylineM: + generaltype = ShapeType.ShapeGeneralPolyline; + modifiers = ShapeModifiers.ShapeHasMs; + break; + case ShapeType.ShapePolylineZ: + generaltype = ShapeType.ShapeGeneralPolyline; + modifiers = ShapeModifiers.ShapeHasZs; + break; + case ShapeType.ShapeGeneralPolyline: + generaltype = ShapeType.ShapeGeneralPolyline; + modifiers = shapetype & ShapeModifiers.ShapeModifierMask; + break; + + // MultiPoint + case ShapeType.ShapeMultiPoint: + generaltype = ShapeType.ShapeGeneralMultiPoint; + modifiers = 0; + break; + case ShapeType.ShapeMultiPointZM: + generaltype = ShapeType.ShapeGeneralMultiPoint; + modifiers = (int) ShapeModifiers.ShapeHasZs + | (int) ShapeModifiers.ShapeHasMs; + break; + case ShapeType.ShapeMultiPointM: + generaltype = ShapeType.ShapeGeneralMultiPoint; + modifiers = ShapeModifiers.ShapeHasMs; + break; + case ShapeType.ShapeMultiPointZ: + generaltype = ShapeType.ShapeGeneralMultiPoint; + modifiers = ShapeModifiers.ShapeHasZs; + break; + case ShapeType.ShapeGeneralMultiPoint: + generaltype = ShapeType.ShapeGeneralMultiPoint; + modifiers = shapetype & ShapeModifiers.ShapeModifierMask; + break; + + // Point + case ShapeType.ShapePoint: + generaltype = ShapeType.ShapeGeneralPoint; + modifiers = 0; + break; + case ShapeType.ShapePointZM: + generaltype = ShapeType.ShapeGeneralPoint; + modifiers = ShapeModifiers.ShapeHasZs + | (int) ShapeModifiers.ShapeHasMs; + break; + case ShapeType.ShapePointM: + generaltype = ShapeType.ShapeGeneralPoint; + modifiers = ShapeModifiers.ShapeHasMs; + break; + case ShapeType.ShapePointZ: + generaltype = ShapeType.ShapeGeneralPoint; + modifiers = ShapeModifiers.ShapeHasZs; + break; + case ShapeType.ShapeGeneralPoint: + generaltype = ShapeType.ShapeGeneralPoint; + modifiers = shapetype & ShapeModifiers.ShapeModifierMask; + break; + + // Null Geometry + case ShapeType.ShapeNull: + return null; + + default: + throw new GeometryException("invalid shape type"); + } + + ByteOrder initialOrder = shapeBuffer.order(); + shapeBuffer.order(ByteOrder.LITTLE_ENDIAN); + + try { + switch (generaltype) { + case ShapeType.ShapeGeneralPolygon: + if (m_type != Geometry.GeometryType.Polygon + && m_type != Geometry.GeometryType.Unknown + && m_type != Geometry.GeometryType.Envelope) + throw new GeometryException("invalid shape type"); + return importFromESRIShapeMultiPath(true, modifiers, + shapeBuffer); + + case ShapeType.ShapeGeneralPolyline: + if (m_type != Geometry.GeometryType.Polyline + && m_type != Geometry.GeometryType.Unknown + && m_type != Geometry.GeometryType.Envelope) + throw new GeometryException("invalid shape type"); + return importFromESRIShapeMultiPath(false, modifiers, + shapeBuffer); + + case ShapeType.ShapeGeneralMultiPoint: + if (m_type != Geometry.GeometryType.MultiPoint + && m_type != Geometry.GeometryType.Unknown + && m_type != Geometry.GeometryType.Envelope) + throw new GeometryException("invalid shape type"); + return importFromESRIShapeMultiPoint(modifiers, shapeBuffer); + + case ShapeType.ShapeGeneralPoint: + if (m_type != Geometry.GeometryType.Point + && m_type != Geometry.GeometryType.MultiPoint + && m_type != Geometry.GeometryType.Unknown + && m_type != Geometry.GeometryType.Envelope) + throw new GeometryException("invalid shape type"); + return importFromESRIShapePoint(modifiers, shapeBuffer); + } + + return null; + } finally { + shapeBuffer.order(initialOrder); + } + } + + private Geometry importFromESRIShapeMultiPath(boolean bPolygon, + int modifiers, ByteBuffer shapeBuffer) { + int offset = 4; + + boolean bZs = (modifiers & (int) ShapeModifiers.ShapeHasZs) != 0; + boolean bMs = (modifiers & (int) ShapeModifiers.ShapeHasMs) != 0; + boolean bIDs = (modifiers & (int) ShapeModifiers.ShapeHasIDs) != 0; + + boolean bHasAttributes = bZs || bMs || bIDs; + boolean bHasBadRings = false; + + // read Envelope + double xmin = shapeBuffer.getDouble(offset); + offset += 8; + double ymin = shapeBuffer.getDouble(offset); + offset += 8; + double xmax = shapeBuffer.getDouble(offset); + offset += 8; + double ymax = shapeBuffer.getDouble(offset); + offset += 8; + + // read part count + int originalPartCount = shapeBuffer.getInt(offset); + offset += 4; + int partCount = 0; + + // read point count + int pointCount = shapeBuffer.getInt(offset); + offset += 4; + + AttributeStreamOfDbl position = null; + AttributeStreamOfDbl zs = null; + AttributeStreamOfDbl ms = null; + AttributeStreamOfInt32 ids = null; + AttributeStreamOfInt32 parts = null; + AttributeStreamOfInt8 pathFlags = null; + + Envelope bbox = null; + MultiPath multipath = null; + MultiPathImpl multipathImpl = null; + if (m_type == Geometry.GeometryType.Polygon + || m_type == Geometry.GeometryType.Polyline + || m_type == Geometry.GeometryType.Unknown) { + if (bPolygon) + multipath = new Polygon(); + else + multipath = new Polyline(); + + multipathImpl = (MultiPathImpl) multipath._getImpl(); + + if (pointCount > 0) { + bbox = new Envelope(); + bbox.setCoords(xmin, ymin, xmax, ymax); + parts = (AttributeStreamOfInt32) AttributeStreamBase + .createIndexStream(originalPartCount + 1); + + int previstart = -1; + int lastCount = 0; + for (int i = 0; i < originalPartCount; i++) { + int istart = shapeBuffer.getInt(offset); + offset += 4; + lastCount = istart; + if (previstart > istart || istart < 0)// check that the part + // indices in the + // buffer are not + // corrupted + throw new GeometryException("corrupted geometry"); + + if (istart != previstart) { + parts.write(partCount, istart); + previstart = istart; + partCount++; + } + } + + parts.resize(partCount + 1); + if (pointCount < lastCount)// check that the point count in the + // buffer is not corrupted + throw new GeometryException("corrupted geometry"); + + parts.write(partCount, pointCount); + pathFlags = (AttributeStreamOfInt8) AttributeStreamBase + .createByteStream(parts.size(), (byte) 0); + + // Create empty position stream + position = (AttributeStreamOfDbl) AttributeStreamBase + .createAttributeStreamWithSemantics(Semantics.POSITION, + pointCount); + + int startpart = parts.read(0); + // read xy coordinates + int xyindex = 0; + for (int ipart = 0; ipart < partCount; ipart++) { + int endpartActual = parts.read(ipart + 1); + // for polygons we read one point less, then analyze if the + // polygon is closed. + int endpart = (bPolygon) ? endpartActual - 1 + : endpartActual; + + double startx = shapeBuffer.getDouble(offset); + offset += 8; + double starty = shapeBuffer.getDouble(offset); + offset += 8; + position.write(2 * xyindex, startx); + position.write(2 * xyindex + 1, starty); + xyindex++; + + for (int i = startpart + 1; i < endpart; i++) { + double x = shapeBuffer.getDouble(offset); + offset += 8; + double y = shapeBuffer.getDouble(offset); + offset += 8; + position.write(2 * xyindex, x); + position.write(2 * xyindex + 1, y); + xyindex++; + } + + if (endpart - startpart < 2) {// a part with only one point + multipathImpl.setIsSimple(GeometryXSimple.Unknown, 0.0, + false); + } + + if (bPolygon) {// read the last point of the part to decide + // if we need to close the polygon + if (startpart == endpart) {// a part with only one point + parts.write(ipart + 1, xyindex); + } else { + double x = shapeBuffer.getDouble(offset); + offset += 8; + double y = shapeBuffer.getDouble(offset); + offset += 8; + + if (x != startx || y != starty) {// bad polygon. The + // last point is + // not the same + // as the last + // one. We need + // to add it so + // that we do + // not loose it. + position.write(2 * xyindex, x); + position.write(2 * xyindex + 1, y); + xyindex++; + multipathImpl.setIsSimple( + GeometryXSimple.Unknown, 0.0, false); + bHasBadRings = true; + // write part count to indicate we need to + // account for one extra point + // The count will be fixed after the attributes + // are processed. So we write negative only when + // there are attributes. + parts.write(ipart + 1, + bHasAttributes ? -xyindex : xyindex); + } else + parts.write(ipart + 1, xyindex); + } + + pathFlags.setBits(ipart, (byte) PathFlags.enumClosed); + } + + startpart = endpartActual; + } + + if (bZs) + bbox.addAttribute(Semantics.Z); + + if (bMs) + bbox.addAttribute(Semantics.M); + + if (bIDs) + bbox.addAttribute(Semantics.ID); + } + } else { + bbox = new Envelope(); + + if (bZs) + bbox.addAttribute(Semantics.Z); + + if (bMs) + bbox.addAttribute(Semantics.M); + + if (bIDs) + bbox.addAttribute(Semantics.ID); + + if (pointCount > 0) { + bbox.setCoords(xmin, ymin, xmax, ymax); + offset += pointCount * 16 + originalPartCount * 4; + } else + return (Geometry) bbox; + } + + // read Zs + if (bZs) { + if (pointCount > 0) { + double zmin = Interop.translateFromAVNaN(shapeBuffer + .getDouble(offset)); + offset += 8; + double zmax = Interop.translateFromAVNaN(shapeBuffer + .getDouble(offset)); + offset += 8; + + Envelope1D env = new Envelope1D(); + env.setCoords(zmin, zmax); + bbox.setInterval(Semantics.Z, 0, env); + + if (m_type == Geometry.GeometryType.Polygon + || m_type == Geometry.GeometryType.Polyline + || m_type == Geometry.GeometryType.Unknown) { + zs = (AttributeStreamOfDbl) AttributeStreamBase + .createAttributeStreamWithSemantics(Semantics.Z, + pointCount); + + boolean bCreate = false; + int startpart = parts.read(0); + for (int ipart = 0; ipart < partCount; ipart++) { + int endpartActual = parts.read(ipart + 1); + int endpart = Math.abs(endpartActual); + + double startz = Interop.translateFromAVNaN(shapeBuffer + .getDouble(offset)); + offset += 8; + zs.write(startpart, startz); + if (!VertexDescription.isDefaultValue(Semantics.Z, + startz)) + bCreate = true; + + for (int i = startpart + 1; i < endpart; i++) { + double z = Interop.translateFromAVNaN(shapeBuffer + .getDouble(offset)); + offset += 8; + zs.write(i, z); + if (!VertexDescription.isDefaultValue(Semantics.Z, + z)) + bCreate = true; + } + + if (bPolygon && endpartActual > 0) { + offset += 8; + } + + startpart = endpart; + } + + if (!bCreate) + zs = null; + } else + offset += pointCount * 8; + } + + if (m_type == Geometry.GeometryType.Polygon + || m_type == Geometry.GeometryType.Polyline + || m_type == Geometry.GeometryType.Unknown) + multipathImpl.setAttributeStreamRef(Semantics.Z, zs); + } + + // read Ms + if (bMs) { + if (pointCount > 0) { + double mmin = Interop.translateFromAVNaN(shapeBuffer + .getDouble(offset)); + offset += 8; + double mmax = Interop.translateFromAVNaN(shapeBuffer + .getDouble(offset)); + offset += 8; + + Envelope1D env = new Envelope1D(); + env.setCoords(mmin, mmax); + bbox.setInterval(Semantics.M, 0, env); + + if (m_type == Geometry.GeometryType.Polygon + || m_type == Geometry.GeometryType.Polyline + || m_type == Geometry.GeometryType.Unknown) { + ms = (AttributeStreamOfDbl) AttributeStreamBase + .createAttributeStreamWithSemantics(Semantics.M, + pointCount); + + boolean bCreate = false; + int startpart = parts.read(0); + for (int ipart = 0; ipart < partCount; ipart++) { + int endpartActual = parts.read(ipart + 1); + int endpart = Math.abs(endpartActual); + + double startm = Interop.translateFromAVNaN(shapeBuffer + .getDouble(offset)); + offset += 8; + ms.write(startpart, startm); + if (!VertexDescription.isDefaultValue(Semantics.M, + startm)) + bCreate = true; + + for (int i = startpart + 1; i < endpart; i++) { + double m = Interop.translateFromAVNaN(shapeBuffer + .getDouble(offset)); + offset += 8; + ms.write(i, m); + if (!VertexDescription.isDefaultValue(Semantics.M, + m)) + bCreate = true; + } + + if (bPolygon && endpartActual > 0) { + offset += 8; + } + + startpart = endpart; + } + + if (!bCreate) + ms = null; + } else + offset += pointCount * 8; + } + + if (m_type == Geometry.GeometryType.Polygon + || m_type == Geometry.GeometryType.Polyline + || m_type == Geometry.GeometryType.Unknown) + multipathImpl.setAttributeStreamRef(Semantics.M, ms); + } + + // read IDs + if (bIDs) { + if (pointCount > 0) { + double idmin = NumberUtils.doubleMax(); + double idmax = -NumberUtils.doubleMax(); + + if (m_type == Geometry.GeometryType.Polygon + || m_type == Geometry.GeometryType.Polyline + || m_type == Geometry.GeometryType.Unknown) { + ids = (AttributeStreamOfInt32) AttributeStreamBase + .createAttributeStreamWithSemantics(Semantics.ID, + pointCount); + + boolean bCreate = false; + int startpart = parts.read(0); + for (int ipart = 0; ipart < partCount; ipart++) { + int endpartActual = parts.read(ipart + 1); + int endpart = Math.abs(endpartActual); + + int startid = shapeBuffer.getInt(offset); + offset += 4; + ids.write(startpart, startid); + if (!VertexDescription.isDefaultValue(Semantics.ID, + startid)) + bCreate = true; + + for (int i = startpart + 1; i < endpart; i++) { + int id = shapeBuffer.getInt(offset); + offset += 4; + ids.write(i, id); + if (!bCreate + && !VertexDescription.isDefaultValue( + Semantics.ID, id)) + bCreate = true; + + if (idmin > id) + idmin = id; + else if (idmax < id) + idmax = id; + } + + if (bPolygon && endpartActual > 0) { + offset += 4; + } + + startpart = endpart; + } + + if (!bCreate) + ids = null; + } else { + for (int i = 0; i < pointCount; i++) { + int id = shapeBuffer.getInt(offset); + offset += 4; + + if (idmin > id) + idmin = id; + else if (idmax < id) + idmax = id; + } + } + + Envelope1D env = new Envelope1D(); + env.setCoords(idmin, idmax); + bbox.setInterval(Semantics.ID, 0, env); + } + + if (m_type == Geometry.GeometryType.Polygon + || m_type == Geometry.GeometryType.Polyline + || m_type == Geometry.GeometryType.Unknown) + multipathImpl.setAttributeStreamRef(Semantics.ID, ids); + } + + if (bHasBadRings && bHasAttributes) {// revert our hack for bad polygons + for (int ipart = 1; ipart < partCount + 1; ipart++) { + int v = parts.read(ipart); + if (v < 0) + parts.write(ipart, -v); + } + } + + if (m_type == Geometry.GeometryType.Envelope) + return (Geometry) bbox; + + if (pointCount > 0) { + multipathImpl.setPathStreamRef(parts); + multipathImpl.setPathFlagsStreamRef(pathFlags); + multipathImpl.setAttributeStreamRef(Semantics.POSITION, position); + multipathImpl.setEnvelope(bbox); + } + + if ((m_importFlags & ShapeImportFlags.ShapeImportNonTrusted) == 0) + multipathImpl.setIsSimple(GeometryXSimple.Weak, 0.0, false);// We + // use + // tolerance + // of 0. + // What + // should + // we + // instead? + + return (Geometry) multipath; + } + + private Geometry importFromESRIShapeMultiPoint(int modifiers, + ByteBuffer shapeBuffer) { + int offset = 4; + + boolean bZs = (modifiers & (int) ShapeModifiers.ShapeHasZs) != 0; + boolean bMs = (modifiers & (int) ShapeModifiers.ShapeHasMs) != 0; + boolean bIDs = (modifiers & modifiers & (int) ShapeModifiers.ShapeHasIDs) != 0; + + double xmin = shapeBuffer.getDouble(offset); + offset += 8; + double ymin = shapeBuffer.getDouble(offset); + offset += 8; + double xmax = shapeBuffer.getDouble(offset); + offset += 8; + double ymax = shapeBuffer.getDouble(offset); + offset += 8; + + int cPoints = shapeBuffer.getInt(offset); + offset += 4; + + AttributeStreamOfDbl position = null; + AttributeStreamOfDbl zs = null; + AttributeStreamOfDbl ms = null; + AttributeStreamOfInt32 ids = null; + + Envelope bbox = null; + MultiPoint multipoint = null; + MultiPointImpl multipointImpl = null; + + if (m_type == Geometry.GeometryType.MultiPoint + || m_type == Geometry.GeometryType.Unknown) { + multipoint = new MultiPoint(); + multipointImpl = (MultiPointImpl) multipoint._getImpl(); + + if (cPoints > 0) { + bbox = new Envelope(); + multipointImpl.resize(cPoints); + position = (AttributeStreamOfDbl) AttributeStreamBase + .createAttributeStreamWithSemantics(Semantics.POSITION, + cPoints); + + for (int i = 0; i < cPoints; i++) { + double x = shapeBuffer.getDouble(offset); + offset += 8; + double y = shapeBuffer.getDouble(offset); + offset += 8; + position.write(2 * i, x); + position.write(2 * i + 1, y); + } + + multipointImpl.resize(cPoints); + bbox.setCoords(xmin, ymin, xmax, ymax); + + if (bZs) + bbox.addAttribute(Semantics.Z); + + if (bMs) + bbox.addAttribute(Semantics.M); + + if (bIDs) + bbox.addAttribute(Semantics.ID); + } + } else { + bbox = new Envelope(); + + if (bZs) + bbox.addAttribute(Semantics.Z); + + if (bMs) + bbox.addAttribute(Semantics.M); + + if (bIDs) + bbox.addAttribute(Semantics.ID); + + if (cPoints > 0) { + bbox.setCoords(xmin, ymin, xmax, ymax); + offset += cPoints * 16; + } else + return (Geometry) bbox; + } + + if (bZs) { + if (cPoints > 0) { + double zmin = Interop.translateFromAVNaN(shapeBuffer + .getDouble(offset)); + offset += 8; + double zmax = Interop.translateFromAVNaN(shapeBuffer + .getDouble(offset)); + offset += 8; + + Envelope1D env = new Envelope1D(); + env.setCoords(zmin, zmax); + bbox.setInterval(Semantics.Z, 0, env); + + if (m_type == Geometry.GeometryType.MultiPoint + || m_type == Geometry.GeometryType.Unknown) { + zs = (AttributeStreamOfDbl) AttributeStreamBase + .createAttributeStreamWithSemantics(Semantics.Z, + cPoints); + + boolean bCreate = false; + for (int i = 0; i < cPoints; i++) { + double value = Interop.translateFromAVNaN(shapeBuffer + .getDouble(offset)); + offset += 8; + zs.write(i, value); + if (!VertexDescription.isDefaultValue(Semantics.Z, + value)) + bCreate = true; + } + + if (!bCreate) + zs = null; + } else + offset += cPoints * 8; + } + + if (m_type == Geometry.GeometryType.MultiPoint + || m_type == Geometry.GeometryType.Unknown) + multipointImpl.setAttributeStreamRef(Semantics.Z, zs); + } + + if (bMs) { + if (cPoints > 0) { + double mmin = Interop.translateFromAVNaN(shapeBuffer + .getDouble(offset)); + offset += 8; + double mmax = Interop.translateFromAVNaN(shapeBuffer + .getDouble(offset)); + offset += 8; + + Envelope1D env = new Envelope1D(); + env.setCoords(mmin, mmax); + bbox.setInterval(Semantics.M, 0, env); + if (m_type == Geometry.GeometryType.MultiPoint + || m_type == Geometry.GeometryType.Unknown) { + ms = (AttributeStreamOfDbl) AttributeStreamBase + .createAttributeStreamWithSemantics(Semantics.M, + cPoints); + + boolean bCreate = false; + for (int i = 0; i < cPoints; i++) { + double value = Interop.translateFromAVNaN(shapeBuffer + .getDouble(offset)); + offset += 8; + ms.write(i, value); + if (!VertexDescription.isDefaultValue(Semantics.M, + value)) + bCreate = true; + } + + if (!bCreate) + ms = null; + } else + offset += cPoints * 8; + } + + if (m_type == Geometry.GeometryType.MultiPoint + || m_type == Geometry.GeometryType.Unknown) + multipointImpl.setAttributeStreamRef(Semantics.M, ms); + } + + if (bIDs) { + if (cPoints > 0) { + double idmin = NumberUtils.doubleMax(); + double idmax = -NumberUtils.doubleMax(); + + if (m_type == Geometry.GeometryType.MultiPoint + || m_type == Geometry.GeometryType.Unknown) { + ids = (AttributeStreamOfInt32) AttributeStreamBase + .createAttributeStreamWithSemantics(Semantics.ID, + cPoints); + + boolean bCreate = false; + for (int i = 0; i < cPoints; i++) { + int value = shapeBuffer.getInt(offset); + offset += 4; + ids.write(i, value); + if (!VertexDescription.isDefaultValue(Semantics.ID, + value)) + bCreate = true; + + if (idmin > value) + idmin = value; + else if (idmax < value) + idmax = value; + } + + if (!bCreate) + ids = null; + } else { + for (int i = 0; i < cPoints; i++) { + int id = shapeBuffer.getInt(offset); + offset += 4; + + if (idmin > id) + idmin = id; + else if (idmax < id) + idmax = id; + } + } + + Envelope1D env = new Envelope1D(); + env.setCoords(idmin, idmax); + bbox.setInterval(Semantics.ID, 0, env); + } + + if (m_type == Geometry.GeometryType.MultiPoint + || m_type == Geometry.GeometryType.Unknown) + multipointImpl.setAttributeStreamRef(Semantics.ID, ids); + } + + if (m_type == Geometry.GeometryType.Envelope) + return (Geometry) bbox; + + if (cPoints > 0) { + multipointImpl.setAttributeStreamRef(Semantics.POSITION, position); + multipointImpl.setEnvelope(bbox); + } + + return (Geometry) multipoint; + } + + private Geometry importFromESRIShapePoint(int modifiers, + ByteBuffer shapeBuffer) { + int offset = 4; + + boolean bZs = (modifiers & (int) ShapeModifiers.ShapeHasZs) != 0; + boolean bMs = (modifiers & (int) ShapeModifiers.ShapeHasMs) != 0; + boolean bIDs = (modifiers & modifiers & (int) ShapeModifiers.ShapeHasIDs) != 0; + + // read XY + double x = shapeBuffer.getDouble(offset); + offset += 8; + double y = shapeBuffer.getDouble(offset); + offset += 8; + + boolean bEmpty = NumberUtils.isNaN(x); + + double z = NumberUtils.NaN(); + if (bZs) { + z = Interop.translateFromAVNaN(shapeBuffer.getDouble(offset)); + offset += 8; + } + + double m = NumberUtils.NaN(); + if (bMs) { + m = Interop.translateFromAVNaN(shapeBuffer.getDouble(offset)); + offset += 8; + } + + int id = -1; + if (bIDs) { + id = shapeBuffer.getInt(offset); + offset += 4; + } + + if (m_type == Geometry.GeometryType.MultiPoint) { + MultiPoint newmultipoint = new MultiPoint(); + MultiPointImpl multipointImpl = (MultiPointImpl) newmultipoint + ._getImpl(); + + if (!bEmpty) { + AttributeStreamBase newPositionStream = AttributeStreamBase + .createAttributeStreamWithSemantics(Semantics.POSITION, + 1); + AttributeStreamOfDbl position = (AttributeStreamOfDbl) newPositionStream; + position.write(0, x); + position.write(1, y); + + multipointImpl.setAttributeStreamRef(Semantics.POSITION, + newPositionStream); + multipointImpl.resize(1); + } + + if (bZs) { + multipointImpl.addAttribute(Semantics.Z); + if (!bEmpty + && !VertexDescription.isDefaultValue(Semantics.Z, z)) { + AttributeStreamBase newZStream = AttributeStreamBase + .createAttributeStreamWithSemantics(Semantics.Z, 1); + newZStream.writeAsDbl(0, z); + multipointImpl.setAttributeStreamRef(Semantics.Z, + newZStream); + } + } + + if (bMs) { + multipointImpl.addAttribute(Semantics.M); + if (!bEmpty + && !VertexDescription.isDefaultValue(Semantics.M, m)) { + AttributeStreamBase newMStream = AttributeStreamBase + .createAttributeStreamWithSemantics(Semantics.M, 1); + newMStream.writeAsDbl(0, m); + multipointImpl.setAttributeStreamRef(Semantics.M, + newMStream); + } + } + + if (bIDs) { + multipointImpl.addAttribute(Semantics.ID); + if (!bEmpty + && !VertexDescription.isDefaultValue(Semantics.ID, id)) { + AttributeStreamBase newIDStream = AttributeStreamBase + .createAttributeStreamWithSemantics(Semantics.ID, 1); + newIDStream.writeAsInt(0, id); + multipointImpl.setAttributeStreamRef(Semantics.ID, + newIDStream); + } + } + + return (Geometry) newmultipoint; + } else if (m_type == Geometry.GeometryType.Envelope) { + Envelope envelope = new Envelope(); + envelope.setCoords(x, y, x, y); + + if (bZs) { + Envelope1D interval = new Envelope1D(); + interval.vmin = z; + interval.vmax = z; + envelope.addAttribute(Semantics.Z); + envelope.setInterval(Semantics.Z, 0, interval); + } + + if (bMs) { + Envelope1D interval = new Envelope1D(); + interval.vmin = m; + interval.vmax = m; + envelope.addAttribute(Semantics.M); + envelope.setInterval(Semantics.M, 0, interval); + } + + if (bIDs) { + Envelope1D interval = new Envelope1D(); + interval.vmin = id; + interval.vmax = id; + envelope.addAttribute(Semantics.ID); + envelope.setInterval(Semantics.ID, 0, interval); + } + + return (Geometry) envelope; + } + + Point point = new Point(); + + if (!bEmpty) { + point.setX(Interop.translateFromAVNaN(x)); + point.setY(Interop.translateFromAVNaN(y)); + } + + // read Z + if (bZs) { + point.addAttribute(Semantics.Z); + if (!bEmpty) + point.setZ(Interop.translateFromAVNaN(z)); + } + + // read M + if (bMs) { + point.addAttribute(Semantics.M); + if (!bEmpty) + point.setM(Interop.translateFromAVNaN(m)); + } + + // read ID + if (bIDs) { + point.addAttribute(Semantics.ID); + if (!bEmpty) + point.setID(id); + } + + return (Geometry) point; + } + +} diff --git a/src/com/esri/core/geometry/OperatorImportFromESRIShapeLocal.java b/src/com/esri/core/geometry/OperatorImportFromESRIShapeLocal.java new file mode 100644 index 00000000..4bcdd3a0 --- /dev/null +++ b/src/com/esri/core/geometry/OperatorImportFromESRIShapeLocal.java @@ -0,0 +1,52 @@ +/* + Copyright 1995-2013 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ + +package com.esri.core.geometry; + +import java.nio.ByteBuffer; + +/** + * OperatorImportFromESRIShapeLocal implementation. + */ +class OperatorImportFromESRIShapeLocal extends OperatorImportFromESRIShape { + + @Override + GeometryCursor execute(int importFlags, Geometry.Type type, + ByteBufferCursor shapeBuffers) { + return new OperatorImportFromESRIShapeCursor(importFlags, type.value(), + shapeBuffers); + } + + @Override + public Geometry execute(int importFlags, Geometry.Type type, + ByteBuffer shapeBuffer) { + SimpleByteBufferCursor byteBufferCursor = new SimpleByteBufferCursor( + shapeBuffer); + GeometryCursor geometryCursor = execute(importFlags, type, + byteBufferCursor); + + return geometryCursor.next(); + } + +} diff --git a/src/com/esri/core/geometry/OperatorImportFromGeoJson.java b/src/com/esri/core/geometry/OperatorImportFromGeoJson.java new file mode 100644 index 00000000..9032c913 --- /dev/null +++ b/src/com/esri/core/geometry/OperatorImportFromGeoJson.java @@ -0,0 +1,61 @@ +/* + Copyright 1995-2013 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ +package com.esri.core.geometry; + +import org.json.JSONException; + +public abstract class OperatorImportFromGeoJson extends Operator { + @Override + public Type getType() { + return Type.ImportFromGeoJson; + } + + /** + * Performs the ImportFromGeoJson operation. + * @param import_flags Use the {@link GeoJsonImportFlags} interface. + * @param type Use the {@link Geometry.Type} enum. + * @param geoJsonString The string holding the Geometry in geoJson format. + * @return Returns the imported Geometry. + * @throws JSONException + */ + public abstract MapGeometry execute(int import_flags, Geometry.Type type, + String geoJsonString, ProgressTracker progress_tracker) + throws JSONException; + + /** + * Performs the ImportFromGeoJson operation. + * @param import_flags Use the {@link GeoJsonImportFlags} interface. + * @param geoJsonString The string holding the Geometry in geoJson format. + * @return Returns the imported MapOGCStructure. + * @throws JSONException + */ + public abstract MapOGCStructure executeOGC(int import_flags, + String geoJsonString, ProgressTracker progress_tracker) + throws JSONException; + + public static OperatorImportFromGeoJson local() { + return (OperatorImportFromGeoJson) OperatorFactoryLocal.getInstance() + .getOperator(Type.ImportFromGeoJson); + } +} diff --git a/src/com/esri/core/geometry/OperatorImportFromGeoJsonLocal.java b/src/com/esri/core/geometry/OperatorImportFromGeoJsonLocal.java new file mode 100644 index 00000000..00d44a7f --- /dev/null +++ b/src/com/esri/core/geometry/OperatorImportFromGeoJsonLocal.java @@ -0,0 +1,593 @@ +/* + Copyright 1995-2013 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ +package com.esri.core.geometry; + +import com.esri.core.geometry.VertexDescription.Semantics; +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; +import java.util.ArrayList; + +class OperatorImportFromGeoJsonLocal extends OperatorImportFromGeoJson { + @Override + public MapGeometry execute(int importFlags, Geometry.Type type, + String geoJsonString, ProgressTracker progress_tracker) + throws JSONException { + JSONObject geoJsonObject = new JSONObject(geoJsonString); + Geometry geometry = importGeometryFromGeoJson_(importFlags, type, + geoJsonObject); + SpatialReference spatialReference = importSpatialReferenceFromGeoJson_(geoJsonObject); + MapGeometry mapGeometry = new MapGeometry(geometry, spatialReference); + return mapGeometry; + } + + @Override + public MapOGCStructure executeOGC(int import_flags, String geoJsonString, + ProgressTracker progress_tracker) throws JSONException { + JSONObject geoJsonObject = new JSONObject(geoJsonString); + ArrayList structureStack = new ArrayList(0); + ArrayList objectStack = new ArrayList(0); + AttributeStreamOfInt32 indices = new AttributeStreamOfInt32(0); + AttributeStreamOfInt32 numGeometries = new AttributeStreamOfInt32(0); + + OGCStructure root = new OGCStructure(); + root.m_structures = new ArrayList(0); + structureStack.add(root); // add dummy root + objectStack.add(geoJsonObject); + indices.add(0); + numGeometries.add(1); + + while (!objectStack.isEmpty()) { + if (indices.getLast() == numGeometries.getLast()) { + structureStack.remove(structureStack.size() - 1); + indices.removeLast(); + numGeometries.removeLast(); + continue; + } + + OGCStructure lastStructure = structureStack.get(structureStack + .size() - 1); + JSONObject lastObject = objectStack.get(objectStack.size() - 1); + objectStack.remove(objectStack.size() - 1); + indices.write(indices.size() - 1, indices.getLast() + 1); + String typeString = lastObject.getString("type"); + + if (typeString.equalsIgnoreCase("GeometryCollection")) { + OGCStructure next = new OGCStructure(); + next.m_type = 7; + next.m_structures = new ArrayList(0); + lastStructure.m_structures.add(next); + structureStack.add(next); + + JSONArray geometries = lastObject.getJSONArray("geometries"); + indices.add(0); + numGeometries.add(geometries.length()); + + for (int i = geometries.length() - 1; i >= 0; i--) + objectStack.add(geometries.getJSONObject(i)); + } else { + int ogcType; + + if (typeString.equalsIgnoreCase("Point")) + ogcType = 1; + else if (typeString.equalsIgnoreCase("LineString")) + ogcType = 2; + else if (typeString.equalsIgnoreCase("Polygon")) + ogcType = 3; + else if (typeString.equalsIgnoreCase("MultiPoint")) + ogcType = 4; + else if (typeString.equalsIgnoreCase("MultiLineString")) + ogcType = 5; + else if (typeString.equalsIgnoreCase("MultiPolygon")) + ogcType = 6; + else + throw new UnsupportedOperationException(); + + Geometry geometry = importGeometryFromGeoJson_(import_flags, + Geometry.Type.Unknown, lastObject); + + OGCStructure leaf = new OGCStructure(); + leaf.m_type = ogcType; + leaf.m_geometry = geometry; + lastStructure.m_structures.add(leaf); + } + } + + MapOGCStructure mapOGCStructure = new MapOGCStructure(); + mapOGCStructure.m_ogcStructure = root; + mapOGCStructure.m_spatialReference = importSpatialReferenceFromGeoJson_(geoJsonObject); + + return mapOGCStructure; + } + + private static SpatialReference importSpatialReferenceFromGeoJson_( + JSONObject crsJSONObject) throws JSONException { + String wkidString = crsJSONObject.optString("crs", ""); + + if (wkidString.equals("")) { + return SpatialReference.create(4326); + } + + // wkidString will be of the form "EPSG:#" where # is an integer, the + // EPSG ID. + // If the ID is below 32,767, then the EPSG ID will agree with the + // well-known (WKID). + + if (wkidString.length() <= 5) { + throw new IllegalArgumentException(); + } + + // Throws a JSON exception if this cannot appropriately be converted to + // an integer. + int wkid = Integer.valueOf(wkidString.substring(5)).intValue(); + + return SpatialReference.create(wkid); + } + + private static Geometry importGeometryFromGeoJson_(int importFlags, + Geometry.Type type, JSONObject geometryJSONObject) + throws JSONException { + String typeString = geometryJSONObject.getString("type"); + JSONArray coordinateArray = geometryJSONObject + .getJSONArray("coordinates"); + + if (typeString.equalsIgnoreCase("MultiPolygon")) { + if (type != Geometry.Type.Polygon && type != Geometry.Type.Unknown) + throw new IllegalArgumentException("invalid shapetype"); + return polygonTaggedText_(true, importFlags, coordinateArray); + } else if (typeString.equalsIgnoreCase("MultiLineString")) { + if (type != Geometry.Type.Polyline && type != Geometry.Type.Unknown) + throw new IllegalArgumentException("invalid shapetype"); + return lineStringTaggedText_(true, importFlags, coordinateArray); + } else if (typeString.equalsIgnoreCase("MultiPoint")) { + if (type != Geometry.Type.MultiPoint + && type != Geometry.Type.Unknown) + throw new IllegalArgumentException("invalid shapetype"); + return multiPointTaggedText_(importFlags, coordinateArray); + } else if (typeString.equalsIgnoreCase("Polygon")) { + if (type != Geometry.Type.Polygon && type != Geometry.Type.Unknown) + throw new IllegalArgumentException("invalid shapetype"); + return polygonTaggedText_(false, importFlags, coordinateArray); + } else if (typeString.equalsIgnoreCase("LineString")) { + if (type != Geometry.Type.Polyline && type != Geometry.Type.Unknown) + throw new IllegalArgumentException("invalid shapetype"); + return lineStringTaggedText_(false, importFlags, coordinateArray); + } else if (typeString.equalsIgnoreCase("Point")) { + if (type != Geometry.Type.Point && type != Geometry.Type.Unknown) + throw new IllegalArgumentException("invalid shapetype"); + return pointTaggedText_(importFlags, coordinateArray); + } else { + return null; + } + } + + private static Geometry polygonTaggedText_(boolean bMultiPolygon, + int importFlags, JSONArray coordinateArray) throws JSONException { + MultiPath multiPath; + MultiPathImpl multiPathImpl; + AttributeStreamOfDbl zs = null; + AttributeStreamOfDbl ms = null; + AttributeStreamOfDbl position; + AttributeStreamOfInt32 paths; + AttributeStreamOfInt8 path_flags; + + position = (AttributeStreamOfDbl) AttributeStreamBase + .createDoubleStream(0); + paths = (AttributeStreamOfInt32) AttributeStreamBase.createIndexStream( + 1, 0); + path_flags = (AttributeStreamOfInt8) AttributeStreamBase + .createByteStream(1, (byte) 0); + + multiPath = new Polygon(); + multiPathImpl = (MultiPathImpl) multiPath._getImpl(); + + int pointCount; + + if (bMultiPolygon) { + pointCount = multiPolygonText_(zs, ms, position, paths, path_flags, + coordinateArray); + } else { + pointCount = polygonText_(zs, ms, position, paths, path_flags, 0, + coordinateArray); + } + + if (pointCount != 0) { + assert (2 * pointCount == position.size()); + multiPathImpl.setAttributeStreamRef( + VertexDescription.Semantics.POSITION, position); + multiPathImpl.setPathStreamRef(paths); + multiPathImpl.setPathFlagsStreamRef(path_flags); + + if (zs != null) { + multiPathImpl.setAttributeStreamRef( + VertexDescription.Semantics.Z, zs); + } + + if (ms != null) { + multiPathImpl.setAttributeStreamRef( + VertexDescription.Semantics.M, ms); + } + + multiPathImpl.notifyModified(MultiPathImpl.DirtyFlags.DirtyAll); + + if (!InternalUtils.isClockwiseRing(multiPathImpl, 0)) { + multiPathImpl.reverseAllPaths(); + } + } + + if ((importFlags & (int) GeoJsonImportFlags.geoJsonImportNonTrusted) == 0) { + multiPathImpl.setIsSimple(MultiPathImpl.GeometryXSimple.Weak, 0.0, + false); + } + + multiPathImpl.setDirtyOGCFlags(false); + + return multiPath; + } + + private static Geometry lineStringTaggedText_(boolean bMultiLineString, + int importFlags, JSONArray coordinateArray) throws JSONException { + MultiPath multiPath; + MultiPathImpl multiPathImpl; + AttributeStreamOfDbl zs = null; + AttributeStreamOfDbl ms = null; + AttributeStreamOfDbl position; + AttributeStreamOfInt32 paths; + AttributeStreamOfInt8 path_flags; + + position = (AttributeStreamOfDbl) AttributeStreamBase + .createDoubleStream(0); + paths = (AttributeStreamOfInt32) AttributeStreamBase.createIndexStream( + 1, 0); + path_flags = (AttributeStreamOfInt8) AttributeStreamBase + .createByteStream(1, (byte) 0); + + multiPath = new Polyline(); + multiPathImpl = (MultiPathImpl) multiPath._getImpl(); + + int pointCount; + + if (bMultiLineString) { + pointCount = multiLineStringText_(zs, ms, position, paths, + path_flags, coordinateArray); + } else { + pointCount = lineStringText_(false, zs, ms, position, paths, + path_flags, coordinateArray); + } + + if (pointCount != 0) { + assert (2 * pointCount == position.size()); + multiPathImpl.setAttributeStreamRef( + VertexDescription.Semantics.POSITION, position); + multiPathImpl.setPathStreamRef(paths); + multiPathImpl.setPathFlagsStreamRef(path_flags); + + if (zs != null) { + multiPathImpl.setAttributeStreamRef( + VertexDescription.Semantics.Z, zs); + } + + if (ms != null) { + multiPathImpl.setAttributeStreamRef( + VertexDescription.Semantics.M, ms); + } + + multiPathImpl.notifyModified(MultiPathImpl.DirtyFlags.DirtyAll); + } + + return multiPath; + } + + private static Geometry multiPointTaggedText_(int importFlags, + JSONArray coordinateArray) throws JSONException { + MultiPoint multiPoint; + MultiPointImpl multiPointImpl; + AttributeStreamOfDbl zs = null; + AttributeStreamOfDbl ms = null; + AttributeStreamOfDbl position; + + position = (AttributeStreamOfDbl) AttributeStreamBase + .createDoubleStream(0); + + multiPoint = new MultiPoint(); + multiPointImpl = (MultiPointImpl) multiPoint._getImpl(); + + int pointCount = multiPointText_(zs, ms, position, coordinateArray); + + if (pointCount != 0) { + assert (2 * pointCount == position.size()); + multiPointImpl.resize(pointCount); + multiPointImpl.setAttributeStreamRef( + VertexDescription.Semantics.POSITION, position); + + multiPointImpl.notifyModified(MultiPointImpl.DirtyFlags.DirtyAll); + } + + return multiPoint; + } + + private static Geometry pointTaggedText_(int importFlags, + JSONArray coordinateArray) throws JSONException { + Point point = new Point(); + + int length = coordinateArray.length(); + + if (length == 0) { + point.setEmpty(); + return point; + } + + point.setXY(getDouble_(coordinateArray, 0), + getDouble_(coordinateArray, 1)); + + return point; + } + + private static int multiPolygonText_(AttributeStreamOfDbl zs, + AttributeStreamOfDbl ms, AttributeStreamOfDbl position, + AttributeStreamOfInt32 paths, AttributeStreamOfInt8 path_flags, + JSONArray coordinateArray) throws JSONException { + // At start of MultiPolygonText + + int totalPointCount = 0; + int length = coordinateArray.length(); + + if (length == 0) + return totalPointCount; + + for (int current = 0; current < length; current++) { + JSONArray subArray = coordinateArray.optJSONArray(current); + if (subArray == null) { + // Entry should be a JSONArray representing a polygon, but it is + // not a JSONArray. + throw new IllegalArgumentException(""); + } + + // At start of PolygonText + + totalPointCount = polygonText_(zs, ms, position, paths, path_flags, + totalPointCount, subArray); + } + + return totalPointCount; + } + + private static int multiLineStringText_(AttributeStreamOfDbl zs, + AttributeStreamOfDbl ms, AttributeStreamOfDbl position, + AttributeStreamOfInt32 paths, AttributeStreamOfInt8 path_flags, + JSONArray coordinateArray) throws JSONException { + // At start of MultiLineStringText + + int totalPointCount = 0; + int length = coordinateArray.length(); + + if (length == 0) + return totalPointCount; + + for (int current = 0; current < length; current++) { + JSONArray subArray = coordinateArray.optJSONArray(current); + if (subArray == null) { + // Entry should be a JSONArray representing a line string, but + // it is not a JSONArray. + throw new IllegalArgumentException(""); + } + + // At start of LineStringText + + totalPointCount += lineStringText_(false, zs, ms, position, paths, + path_flags, subArray); + } + + return totalPointCount; + } + + private static int multiPointText_(AttributeStreamOfDbl zs, + AttributeStreamOfDbl ms, AttributeStreamOfDbl position, + JSONArray coordinateArray) throws JSONException { + // At start of MultiPointText + + int pointCount = 0; + + for (int current = 0; current < coordinateArray.length(); current++) { + JSONArray subArray = coordinateArray.optJSONArray(current); + if (subArray == null) { + // Entry should be a JSONArray representing a point, but it is + // not a JSONArray. + throw new IllegalArgumentException(""); + } + + pointCount += pointText_(zs, ms, position, subArray); + } + + return pointCount; + } + + private static int polygonText_(AttributeStreamOfDbl zs, + AttributeStreamOfDbl ms, AttributeStreamOfDbl position, + AttributeStreamOfInt32 paths, AttributeStreamOfInt8 path_flags, + int totalPointCount, JSONArray coordinateArray) + throws JSONException { + // At start of PolygonText + + int length = coordinateArray.length(); + + if (length == 0) { + return totalPointCount; + } + + boolean bFirstLineString = true; + + for (int current = 0; current < length; current++) { + JSONArray subArray = coordinateArray.optJSONArray(current); + if (subArray == null) { + // Entry should be a JSONArray representing a line string, but + // it is not a JSONArray. + throw new IllegalArgumentException(""); + } + + // At start of LineStringText + + int pointCount = lineStringText_(true, zs, ms, position, paths, + path_flags, subArray); + + if (pointCount != 0) { + if (bFirstLineString) { + bFirstLineString = false; + path_flags.setBits(path_flags.size() - 2, + (byte) PathFlags.enumOGCStartPolygon); + } + + path_flags.setBits(path_flags.size() - 2, + (byte) PathFlags.enumClosed); + totalPointCount += pointCount; + } + } + + return totalPointCount; + } + + private static int lineStringText_(boolean bRing, AttributeStreamOfDbl zs, + AttributeStreamOfDbl ms, AttributeStreamOfDbl position, + AttributeStreamOfInt32 paths, AttributeStreamOfInt8 path_flags, + JSONArray coordinateArray) throws JSONException { + // At start of LineStringText + + int pointCount = 0; + int length = coordinateArray.length(); + + if (length == 0) + return pointCount; + + boolean bStartPath = true; + double startX = NumberUtils.TheNaN; + double startY = NumberUtils.TheNaN; + double startZ = NumberUtils.TheNaN; + double startM = NumberUtils.TheNaN; + + for (int current = 0; current < length; current++) { + JSONArray subArray = coordinateArray.optJSONArray(current); + if (subArray == null) { + // Entry should be a JSONArray representing a single point, but + // it is not a JSONArray. + throw new IllegalArgumentException(""); + } + + // At start of x + + double x = getDouble_(subArray, 0); + double y = getDouble_(subArray, 1); + double z = NumberUtils.TheNaN; + double m = NumberUtils.TheNaN; + + boolean bAddPoint = true; + + if (bRing && pointCount >= 2 && current == length - 1) { + // If the last point in the ring is not equal to the start + // point, then let's add it. + + if ((startX == x || (NumberUtils.isNaN(startX) && NumberUtils + .isNaN(x))) + && (startY == y || (NumberUtils.isNaN(startY) && NumberUtils + .isNaN(y)))) { + bAddPoint = false; + } + } + + if (bAddPoint) { + if (bStartPath) { + bStartPath = false; + startX = x; + startY = y; + startZ = z; + startM = m; + } + + pointCount++; + addToStreams_(zs, ms, position, x, y, z, m); + } + } + + if (pointCount == 1) { + pointCount++; + addToStreams_(zs, ms, position, startX, startY, startZ, startM); + } + + paths.add(position.size() / 2); + path_flags.add((byte) 0); + + return pointCount; + } + + private static int pointText_(AttributeStreamOfDbl zs, + AttributeStreamOfDbl ms, AttributeStreamOfDbl position, + JSONArray coordinateArray) throws JSONException { + // At start of PointText + + int length = coordinateArray.length(); + + if (length == 0) + return 0; + + // At start of x + + double x = getDouble_(coordinateArray, 0); + double y = getDouble_(coordinateArray, 1); + double z = NumberUtils.TheNaN; + double m = NumberUtils.TheNaN; + + addToStreams_(zs, ms, position, x, y, z, m); + + return 1; + } + + private static void addToStreams_(AttributeStreamOfDbl zs, + AttributeStreamOfDbl ms, AttributeStreamOfDbl position, double x, + double y, double z, double m) { + position.add(x); + position.add(y); + + if (zs != null) + zs.add(z); + + if (ms != null) + ms.add(m); + } + + private static double getDouble_(JSONArray coordinateArray, int index) + throws JSONException { + if (index < 0 || index >= coordinateArray.length()) { + throw new IllegalArgumentException(""); + } + + if (coordinateArray.isNull(index)) { + return NumberUtils.TheNaN; + } + + if (coordinateArray.optDouble(index, NumberUtils.TheNaN) != NumberUtils.TheNaN) { + return coordinateArray.getDouble(index); + } + + throw new IllegalArgumentException(""); + } +} diff --git a/src/com/esri/core/geometry/OperatorImportFromJson.java b/src/com/esri/core/geometry/OperatorImportFromJson.java new file mode 100644 index 00000000..a4335b57 --- /dev/null +++ b/src/com/esri/core/geometry/OperatorImportFromJson.java @@ -0,0 +1,60 @@ +/* + Copyright 1995-2013 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ + +package com.esri.core.geometry; + +import org.codehaus.jackson.JsonParser; + +import com.esri.core.geometry.Operator.Type; + +/** + *Import from JSON format. + */ +public abstract class OperatorImportFromJson extends Operator { + @Override + public Type getType() { + return Type.ImportMapGeometryFromJson; + } + + /** + * Performs the ImportFromJson operation on a number of Json Strings + * + * @return Returns a MapGeometryCursor. + */ + abstract MapGeometryCursor execute(Geometry.Type type, + JsonParserCursor jsonParserCursor); + + /** + *Performs the ImportFromJson operation on a single Json string + *@return Returns a MapGeometry. + */ + public abstract MapGeometry execute(Geometry.Type type, + JsonParser jsonParser); + + public static OperatorImportFromJson local() { + return (OperatorImportFromJson) OperatorFactoryLocal.getInstance() + .getOperator(Type.ImportFromJson); + } + +} diff --git a/src/com/esri/core/geometry/OperatorImportFromJsonCursor.java b/src/com/esri/core/geometry/OperatorImportFromJsonCursor.java new file mode 100644 index 00000000..ac982316 --- /dev/null +++ b/src/com/esri/core/geometry/OperatorImportFromJsonCursor.java @@ -0,0 +1,559 @@ +/* + Copyright 1995-2013 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ + +package com.esri.core.geometry; + +import com.esri.core.geometry.MultiVertexGeometryImpl.DirtyFlags; +import com.esri.core.geometry.VertexDescription.Semantics; +import java.io.IOException; +import org.codehaus.jackson.JsonParseException; +import org.codehaus.jackson.JsonParser; +import org.codehaus.jackson.JsonToken; + +class OperatorImportFromJsonCursor extends MapGeometryCursor { + JsonParserCursor m_inputJsonParsers; + + int m_type; + + int m_index; + + public OperatorImportFromJsonCursor(int type, JsonParserCursor jsonParsers) { + m_index = -1; + if (jsonParsers == null) + throw new IllegalArgumentException(); + + m_type = type; + m_inputJsonParsers = jsonParsers; + } + + @Override + public int getGeometryID() { + return m_index; + } + + @Override + public MapGeometry next() { + JsonParser jsonParser; + if ((jsonParser = m_inputJsonParsers.next()) != null) { + m_index = m_inputJsonParsers.getID(); + return importFromJsonParser(m_type, jsonParser); + } + return null; + } + + private static MapGeometry importFromJsonParser(int gt, JsonParser parser) { + MapGeometry mp; + + try { + if (!JSONUtils.isObjectStart(parser)) + return null; + + boolean bFoundSpatial_reference = false; + boolean bFoundHasZ = false; + boolean bFoundHasM = false; + boolean bFoundPolygon = false; + boolean bFoundPolyline = false; + boolean bFoundMultiPoint = false; + boolean bFoundX = false; + boolean bFoundY = false; + boolean bFoundZ = false; + boolean bFoundM = false; + boolean bFoundXMin = false; + boolean bFoundYMin = false; + boolean bFoundXMax = false; + boolean bFoundYMax = false; + boolean bFoundZMin = false; + boolean bFoundZMax = false; + boolean bFoundMMin = false; + boolean bFoundMMax = false; + double x = NumberUtils.NaN(); + double y = NumberUtils.NaN(); + double z = NumberUtils.NaN(); + double m = NumberUtils.NaN(); + double xmin = NumberUtils.NaN(); + double ymin = NumberUtils.NaN(); + double xmax = NumberUtils.NaN(); + double ymax = NumberUtils.NaN(); + double zmin = NumberUtils.NaN(); + double zmax = NumberUtils.NaN(); + double mmin = NumberUtils.NaN(); + double mmax = NumberUtils.NaN(); + boolean bHasZ = false; + boolean bHasM = false; + AttributeStreamOfDbl as = (AttributeStreamOfDbl) AttributeStreamBase + .createDoubleStream(0); + AttributeStreamOfDbl bs = (AttributeStreamOfDbl) AttributeStreamBase + .createDoubleStream(0); + + Geometry geometry = null; + SpatialReference spatial_reference = null; + + while (parser.nextToken() != JsonToken.END_OBJECT) { + String name = parser.getCurrentName(); + parser.nextToken(); + + if (!bFoundSpatial_reference && name.equals("spatialReference")) { + bFoundSpatial_reference = true; + + if (parser.getCurrentToken() == JsonToken.START_OBJECT) { + spatial_reference = SpatialReference.fromJson(parser); + } else { + if (parser.getCurrentToken() != JsonToken.VALUE_NULL) + throw new GeometryException( + "failed to parse spatial reference: object or null is expected"); + } + } else if (!bFoundHasZ && name.equals("hasZ")) { + bFoundHasZ = true; + bHasZ = (parser.getCurrentToken() == JsonToken.VALUE_TRUE); + } else if (!bFoundHasM && name.equals("hasM")) { + bFoundHasM = true; + bHasM = (parser.getCurrentToken() == JsonToken.VALUE_TRUE); + } else if (!bFoundPolygon + && name.equals("rings") + && (gt == Geometry.GeometryType.Unknown || gt == Geometry.GeometryType.Polygon)) { + bFoundPolygon = true; + geometry = importFromJsonMultiPath(true, parser, as, bs); + continue; + } else if (!bFoundPolyline + && name.equals("paths") + && (gt == Geometry.GeometryType.Unknown || gt == Geometry.GeometryType.Polyline)) { + bFoundPolyline = true; + geometry = importFromJsonMultiPath(false, parser, as, bs); + continue; + } else if (!bFoundMultiPoint + && name.equals("points") + && (gt == Geometry.GeometryType.Unknown || gt == Geometry.GeometryType.MultiPoint)) { + bFoundMultiPoint = true; + geometry = importFromJsonMultiPoint(parser, as, bs); + continue; + } else if (!bFoundX + && name.equals("x") + && (gt == Geometry.GeometryType.Unknown || gt == Geometry.GeometryType.Point)) { + bFoundX = true; + x = readDouble(parser); + } else if (!bFoundY + && name.equals("y") + && (gt == Geometry.GeometryType.Unknown || gt == Geometry.GeometryType.Point)) { + bFoundY = true; + y = readDouble(parser); + } else if (!bFoundZ + && name.equals("z") + && (gt == Geometry.GeometryType.Unknown || gt == Geometry.GeometryType.Point)) { + bFoundZ = true; + z = readDouble(parser); + } else if (!bFoundM + && name.equals("m") + && (gt == Geometry.GeometryType.Unknown || gt == Geometry.GeometryType.Point)) { + bFoundM = true; + m = readDouble(parser); + } + if (!bFoundXMin + && name.equals("xmin") + && (gt == Geometry.GeometryType.Unknown || gt == Geometry.GeometryType.Envelope)) { + bFoundXMin = true; + xmin = readDouble(parser); + } else if (!bFoundYMin + && name.equals("ymin") + && (gt == Geometry.GeometryType.Unknown || gt == Geometry.GeometryType.Envelope)) { + bFoundYMin = true; + ymin = readDouble(parser); + } else if (!bFoundMMin + && name.equals("mmin") + && (gt == Geometry.GeometryType.Unknown || gt == Geometry.GeometryType.Envelope)) { + bFoundMMin = true; + mmin = readDouble(parser); + } else if (!bFoundZMin + && name.equals("zmin") + && (gt == Geometry.GeometryType.Unknown || gt == Geometry.GeometryType.Envelope)) { + bFoundZMin = true; + zmin = readDouble(parser); + } else if (!bFoundXMax + && name.equals("xmax") + && (gt == Geometry.GeometryType.Unknown || gt == Geometry.GeometryType.Envelope)) { + bFoundXMax = true; + xmax = readDouble(parser); + } else if (!bFoundYMax + && name.equals("ymax") + && (gt == Geometry.GeometryType.Unknown || gt == Geometry.GeometryType.Envelope)) { + bFoundYMax = true; + ymax = readDouble(parser); + } else if (!bFoundMMax + && name.equals("mmax") + && (gt == Geometry.GeometryType.Unknown || gt == Geometry.GeometryType.Envelope)) { + bFoundMMax = true; + mmax = readDouble(parser); + } else if (!bFoundZMax + && name.equals("zmax") + && (gt == Geometry.GeometryType.Unknown || gt == Geometry.GeometryType.Envelope)) { + bFoundZMax = true; + zmax = readDouble(parser); + } else { + windup(parser); + } + } + + if (bFoundPolygon || bFoundPolyline || bFoundMultiPoint) { + assert (geometry != null); + MultiVertexGeometryImpl mvImpl = (MultiVertexGeometryImpl) geometry + ._getImpl(); + + AttributeStreamBase zs = null; + AttributeStreamBase ms = null; + + if (bHasZ) { + geometry.addAttribute(Semantics.Z); + zs = as; + } + if (bHasM) { + geometry.addAttribute(Semantics.M); + ms = !bHasZ ? as : bs; + } + + if (bHasZ && zs != null) { + mvImpl.setAttributeStreamRef(Semantics.Z, zs); + } + + if (bHasM && ms != null) { + mvImpl.setAttributeStreamRef(Semantics.M, ms); + } + + mvImpl.notifyModified(DirtyFlags.DirtyAll); + } else if (bFoundX || bFoundY || bFoundY || bFoundZ) { + if (NumberUtils.isNaN(y)) + x = NumberUtils.NaN(); + + Point p = new Point(x, y); + + if (bFoundZ) + p.setZ(z); + + if (bFoundM) + p.setM(m); + + geometry = p; + } else if (bFoundXMin || bFoundYMin || bFoundXMax || bFoundYMax + || bFoundZMin || bFoundZMax || bFoundMMin || bFoundMMax) { + if (NumberUtils.isNaN(ymin) || NumberUtils.isNaN(xmax) + || NumberUtils.isNaN(ymax)) + xmin = NumberUtils.NaN(); + + Envelope e = new Envelope(xmin, ymin, xmax, ymax); + + if (bFoundZMin && bFoundZMax) + e.setInterval(Semantics.Z, 0, zmin, zmax); + + if (bFoundMMin && bFoundMMax) + e.setInterval(Semantics.M, 0, mmin, mmax); + + geometry = e; + } + + mp = new MapGeometry(geometry, spatial_reference); + + } catch (Exception e) { + e.printStackTrace(); + return null; + } + + return mp; + } + + public static MapGeometry fromJsonToUnknown(JsonParser parser) + throws Exception { + + return importFromJsonParser(Geometry.GeometryType.Unknown, parser); + } + + public static MapGeometry fromJsonToEnvelope(JsonParser parser) + throws Exception { + return importFromJsonParser(Geometry.GeometryType.Envelope, parser); + } + + public static MapGeometry fromJsonToPoint(JsonParser parser) + throws Exception { + return importFromJsonParser(Geometry.GeometryType.Point, parser); + } + + public static MapGeometry fromJsonToPolygon(JsonParser parser) + throws Exception { + return importFromJsonParser(Geometry.GeometryType.Polygon, parser); + } + + public static MapGeometry fromJsonToPolyline(JsonParser parser) + throws Exception { + return importFromJsonParser(Geometry.GeometryType.Polyline, parser); + } + + public static MapGeometry fromJsonToMultiPoint(JsonParser parser) + throws Exception { + return importFromJsonParser(Geometry.GeometryType.MultiPoint, parser); + } + + private static void windup(JsonParser parser) throws IOException, + JsonParseException { + parser.skipChildren(); + } + + private static double readDouble(JsonParser parser) throws IOException, + JsonParseException { + if (parser.getCurrentToken() == JsonToken.VALUE_NULL + || parser.getCurrentToken() == JsonToken.VALUE_STRING + && parser.getCurrentName().equals("NaN")) + return NumberUtils.NaN(); + else + return parser.getValueAsDouble(); + } + + private static Geometry importFromJsonMultiPoint(JsonParser parser, + AttributeStreamOfDbl as, AttributeStreamOfDbl bs) throws Exception { + if (parser.getCurrentToken() != JsonToken.START_ARRAY) + throw new GeometryException( + "failed to parse multipoint: array of vertices is expected"); + + int point_count = 0; + MultiPoint multipoint; + + multipoint = new MultiPoint(); + + AttributeStreamOfDbl position = (AttributeStreamOfDbl) (AttributeStreamBase + .createDoubleStream(2, 0)); + + // At start of rings + int sz; + double[] buf = new double[4]; + while (parser.nextToken() != JsonToken.END_ARRAY) { + if (parser.getCurrentToken() != JsonToken.START_ARRAY) + throw new GeometryException( + "failed to parse multipoint: array is expected, multipoint vertices consist of arrays of cooridinates"); + + sz = 0; + while (parser.nextToken() != JsonToken.END_ARRAY) { + buf[sz++] = readDouble(parser); + } + + if (sz < 2) + throw new GeometryException( + "failed to parse multipoint: each vertex array has to have at least 2 elements"); + + if (position.size() == 2 * point_count) { + int c = point_count * 3; + if (c % 2 != 0) + c++;// have to be even + position.resize(c); + } + + position.write(2 * point_count, buf[0]); + position.write(2 * point_count + 1, buf[1]); + + if (as.size() == point_count) { + int c = (point_count * 3) / 2; + if (c < 4) + c = 4; + else if (c < 16) + c = 16; + + as.resize(c); + } + + if (sz > 2) { + as.write(point_count, buf[2]); + } else + as.write(point_count, NumberUtils.NaN()); + + if (bs.size() == point_count) { + int c = (point_count * 3) / 2; + if (c < 4) + c = 4; + else if (c < 16) + c = 16; + + bs.resize(c); + } + + if (sz > 3) { + bs.write(point_count, buf[3]); + } else + bs.write(point_count, NumberUtils.NaN()); + + point_count++; + } + + if (point_count != 0) { + MultiPointImpl mp_impl = (MultiPointImpl) multipoint._getImpl(); + mp_impl.resize(point_count); + mp_impl.setAttributeStreamRef(Semantics.POSITION, position); + } + return multipoint; + } + + private static Geometry importFromJsonMultiPath(boolean b_polygon, + JsonParser parser, AttributeStreamOfDbl as, AttributeStreamOfDbl bs) + throws Exception { + if (parser.getCurrentToken() != JsonToken.START_ARRAY) + throw new GeometryException( + "failed to parse multipath: array of array of vertices is expected"); + + MultiPath multipath; + + if (b_polygon) + multipath = new Polygon(); + else + multipath = new Polyline(); + + AttributeStreamOfInt32 parts = (AttributeStreamOfInt32) AttributeStreamBase + .createIndexStream(0); + AttributeStreamOfDbl position = (AttributeStreamOfDbl) AttributeStreamBase + .createDoubleStream(2, 0); + AttributeStreamOfInt8 pathFlags = (AttributeStreamOfInt8) AttributeStreamBase + .createByteStream(0); + + // set up min max variables + double[] buf = new double[4]; + double[] start = new double[4]; + + int point_count = 0; + int path_count = 0; + byte pathFlag = b_polygon ? (byte) PathFlags.enumClosed : 0; + int requiredSize = b_polygon ? 3 : 2; + + // At start of rings + while (parser.nextToken() != JsonToken.END_ARRAY) { + if (parser.getCurrentToken() != JsonToken.START_ARRAY) + throw new GeometryException( + "failed to parse multipath: ring/path array is expected"); + + int pathPointCount = 0; + boolean b_first = true; + int sz = 0; + int szstart = 0; + + parser.nextToken(); + while (parser.getCurrentToken() != JsonToken.END_ARRAY) { + if (parser.getCurrentToken() != JsonToken.START_ARRAY) + throw new GeometryException( + "failed to parse multipath: array is expected, rings/paths vertices consist of arrays of cooridinates"); + + sz = 0; + while (parser.nextToken() != JsonToken.END_ARRAY) { + buf[sz++] = readDouble(parser); + } + + if (sz < 2) + throw new GeometryException( + "failed to parse multipath: each vertex array has to have at least 2 elements"); + + parser.nextToken(); + + do { + if (position.size() == point_count * 2) { + int c = point_count * 3; + + if (c % 2 != 0) + c++;// have to be even + if (c < 8) + c = 8; + else if (c < 32) + c = 32; + + position.resize(c); + } + + position.write(2 * point_count, buf[0]); + position.write(2 * point_count + 1, buf[1]); + + if (as.size() == point_count) { + int c = (point_count * 3) / 2;// have to be even + if (c < 4) + c = 4; + else if (c < 16) + c = 16; + as.resize(c); + } + + if (sz > 2) { + as.write(point_count, buf[2]); + } else + as.write(point_count, NumberUtils.NaN()); + + if (bs.size() == point_count) { + int c = (point_count * 3) / 2;// have to be even + if (c < 4) + c = 4; + else if (c < 16) + c = 16; + bs.resize(c); + } + + if (sz > 3) { + bs.write(point_count, buf[3]); + } else + bs.write(point_count, NumberUtils.NaN()); + + if (b_first) { + path_count++; + parts.add(point_count); + pathFlags.add(pathFlag); + b_first = false; + szstart = sz; + start[0] = buf[0]; + start[1] = buf[1]; + start[2] = buf[2]; + start[3] = buf[3]; + } + point_count++; + pathPointCount++; + } while (pathPointCount < requiredSize + && parser.getCurrentToken() == JsonToken.END_ARRAY); + } + + if (b_polygon && pathPointCount > requiredSize && sz == szstart + && start[0] == buf[0] && start[1] == buf[1] + && start[2] == buf[2] && start[3] == buf[3]) { + // remove the end point that is equal to the start point. + point_count--; + pathPointCount--; + } + + if (pathPointCount == 0) + continue;// skip empty paths + } + + if (point_count != 0) { + parts.resize(path_count); + pathFlags.resize(path_count); + + if (point_count > 0) { + parts.add(point_count); + pathFlags.add((byte) 0); + } + + MultiPathImpl mp_impl = (MultiPathImpl) multipath._getImpl(); + mp_impl.setAttributeStreamRef(Semantics.POSITION, position); + mp_impl.setPathFlagsStreamRef(pathFlags); + mp_impl.setPathStreamRef(parts); + } + return multipath; + } + +} diff --git a/src/com/esri/core/geometry/OperatorImportFromJsonLocal.java b/src/com/esri/core/geometry/OperatorImportFromJsonLocal.java new file mode 100644 index 00000000..823e95c9 --- /dev/null +++ b/src/com/esri/core/geometry/OperatorImportFromJsonLocal.java @@ -0,0 +1,44 @@ +/* + Copyright 1995-2013 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ +package com.esri.core.geometry; + +import org.codehaus.jackson.JsonParser; + +class OperatorImportFromJsonLocal extends OperatorImportFromJson { + + @Override + public MapGeometryCursor execute(Geometry.Type type, + JsonParserCursor jsonParserCursor) { + return new OperatorImportFromJsonCursor(type.value(), jsonParserCursor); + } + + @Override + public MapGeometry execute(Geometry.Type type, JsonParser jsonParser) { + SimpleJsonParserCursor jsonParserCursor = new SimpleJsonParserCursor( + jsonParser); + OperatorImportFromJsonCursor cursor = new OperatorImportFromJsonCursor( + type.value(), jsonParserCursor); + return cursor.next(); + } +} diff --git a/src/com/esri/core/geometry/OperatorImportFromWkb.java b/src/com/esri/core/geometry/OperatorImportFromWkb.java new file mode 100644 index 00000000..8f55cfa8 --- /dev/null +++ b/src/com/esri/core/geometry/OperatorImportFromWkb.java @@ -0,0 +1,65 @@ +/* + Copyright 1995-2013 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ + +package com.esri.core.geometry; + +import java.nio.ByteBuffer; + +import com.esri.core.geometry.Operator.Type; + +/** + *Import from WKB format. + */ +public abstract class OperatorImportFromWkb extends Operator { + + @Override + public Type getType() { + return Type.ImportFromWkb; + } + + /** + * Performs the ImportFromWKB operation. + * @param importFlags Use the {@link WkbImportFlags} interface. + * @param type Use the {@link Geometry.Type} enum. + * @param wkbBuffer The buffer holding the Geometry in wkb format. + * @return Returns the imported Geometry. + */ + public abstract Geometry execute(int importFlags, Geometry.Type type, + ByteBuffer wkbBuffer, ProgressTracker progress_tracker); + + /** + * Performs the ImportFromWkb operation. + * @param importFlags Use the {@link WkbImportFlags} interface. + * @param wkbBuffer The buffer holding the Geometry in wkb format. + * @return Returns the imported OGCStructure. + */ + public abstract OGCStructure executeOGC(int importFlags, + ByteBuffer wktBuffer, ProgressTracker progress_tracker); + + public static OperatorImportFromWkb local() { + return (OperatorImportFromWkb) OperatorFactoryLocal.getInstance() + .getOperator(Type.ImportFromWkb); + } + +} diff --git a/src/com/esri/core/geometry/OperatorImportFromWkbLocal.java b/src/com/esri/core/geometry/OperatorImportFromWkbLocal.java new file mode 100644 index 00000000..c5ee10aa --- /dev/null +++ b/src/com/esri/core/geometry/OperatorImportFromWkbLocal.java @@ -0,0 +1,1045 @@ +/* + Copyright 1995-2013 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ + +package com.esri.core.geometry; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.util.ArrayList; + +/** + * OperatorImportFromWkbLocal implementation. + */ +class OperatorImportFromWkbLocal extends OperatorImportFromWkb { + + static final class WkbHelper { + WkbHelper(ByteBuffer buffer) { + wkbBuffer = buffer; + adjustment = 0; + } + + int getInt(int offset) { + return wkbBuffer.getInt(adjustment + offset); + } + + double getDouble(int offset) { + return wkbBuffer.getDouble(adjustment + offset); + } + + ByteBuffer wkbBuffer; + int adjustment; + } + + @Override + public Geometry execute(int importFlags, Geometry.Type type, + ByteBuffer wkbBuffer, ProgressTracker progress_tracker) { + + ByteOrder initialOrder = wkbBuffer.order(); + + // read byte ordering + int byteOrder = wkbBuffer.get(0); + + if (byteOrder == WkbByteOrder.wkbNDR) + wkbBuffer.order(ByteOrder.LITTLE_ENDIAN); + else + wkbBuffer.order(ByteOrder.BIG_ENDIAN); + + WkbHelper wkbHelper = new WkbHelper(wkbBuffer); + + try { + return importFromWkb(importFlags, type, wkbHelper); + } finally { + wkbBuffer.order(initialOrder); + } + } + + @Override + public OGCStructure executeOGC(int importFlags, ByteBuffer wkbBuffer, + ProgressTracker progress_tracker) { + + ByteOrder initialOrder = wkbBuffer.order(); + + // read byte ordering + int byteOrder = wkbBuffer.get(0); + + if (byteOrder == WkbByteOrder.wkbNDR) + wkbBuffer.order(ByteOrder.LITTLE_ENDIAN); + else + wkbBuffer.order(ByteOrder.BIG_ENDIAN); + + ArrayList stack = new ArrayList(0); + AttributeStreamOfInt32 numGeometries = new AttributeStreamOfInt32(0); + AttributeStreamOfInt32 indices = new AttributeStreamOfInt32(0); + WkbHelper wkbHelper = new WkbHelper(wkbBuffer); + + OGCStructure root = new OGCStructure(); + root.m_structures = new ArrayList(0); + stack.add(root); // add dummy root + numGeometries.add(1); + indices.add(0); + + boolean bCheckConsistentAttributes = false; + boolean bHasZs = false; + boolean bHasMs = false; + + try { + + while (!stack.isEmpty()) { + + if (indices.getLast() == numGeometries.getLast()) { + stack.remove(stack.size() - 1); + indices.removeLast(); + numGeometries.removeLast(); + continue; + } + + OGCStructure last = stack.get(stack.size() - 1); + indices.write(indices.size() - 1, indices.getLast() + 1); + Geometry geometry; + + int wkbType = wkbHelper.getInt(1); + int ogcType; + + // strip away attributes from type identifier + + if (wkbType > 3000) { + ogcType = wkbType - 3000; + + if (bCheckConsistentAttributes) { + if (!bHasZs || !bHasMs) + throw new IllegalArgumentException(); + } else { + bHasZs = true; + bHasMs = true; + bCheckConsistentAttributes = true; + } + } else if (wkbType > 2000) { + ogcType = wkbType - 2000; + + if (bCheckConsistentAttributes) { + if (bHasZs || !bHasMs) + throw new IllegalArgumentException(); + } else { + bHasZs = false; + bHasMs = true; + bCheckConsistentAttributes = true; + } + } else if (wkbType > 1000) { + ogcType = wkbType - 1000; + + if (bCheckConsistentAttributes) { + if (!bHasZs || bHasMs) + throw new IllegalArgumentException(); + } else { + bHasZs = true; + bHasMs = false; + bCheckConsistentAttributes = true; + } + } else { + ogcType = wkbType; + + if (bCheckConsistentAttributes) { + if (bHasZs || bHasMs) + throw new IllegalArgumentException(); + } else { + bHasZs = false; + bHasMs = false; + bCheckConsistentAttributes = true; + } + } + if (ogcType == 7) { + int count = wkbHelper.getInt(5); + wkbHelper.adjustment += 9; + + OGCStructure next = new OGCStructure(); + next.m_type = ogcType; + next.m_structures = new ArrayList(0); + last.m_structures.add(next); + stack.add(next); + indices.add(0); + numGeometries.add(count); + } else { + geometry = importFromWkb(importFlags, + Geometry.Type.Unknown, wkbHelper); + OGCStructure leaf = new OGCStructure(); + leaf.m_type = ogcType; + leaf.m_geometry = geometry; + last.m_structures.add(leaf); + } + } + } finally { + wkbBuffer.order(initialOrder); + } + + return root; + } + + private static Geometry importFromWkb(int importFlags, Geometry.Type type, + WkbHelper wkbHelper) { + + // read type + int wkbType = wkbHelper.getInt(1); + + switch (wkbType) { + case WkbGeometryType.wkbPolygon: + if (type.value() != Geometry.GeometryType.Polygon + && type.value() != Geometry.GeometryType.Unknown) + throw new GeometryException("invalid shape type"); + return importFromWkbPolygon(false, importFlags, false, false, + wkbHelper); + + case WkbGeometryType.wkbPolygonM: + if (type.value() != Geometry.GeometryType.Polygon + && type.value() != Geometry.GeometryType.Unknown) + throw new GeometryException("invalid shape type"); + return importFromWkbPolygon(false, importFlags, false, true, + wkbHelper); + + case WkbGeometryType.wkbPolygonZ: + if (type.value() != Geometry.GeometryType.Polygon + && type.value() != Geometry.GeometryType.Unknown) + throw new GeometryException("invalid shape type"); + return importFromWkbPolygon(false, importFlags, true, false, + wkbHelper); + + case WkbGeometryType.wkbPolygonZM: + if (type.value() != Geometry.GeometryType.Polygon + && type.value() != Geometry.GeometryType.Unknown) + throw new GeometryException("invalid shape type"); + return importFromWkbPolygon(false, importFlags, true, true, + wkbHelper); + + case WkbGeometryType.wkbMultiPolygon: + if (type.value() != Geometry.GeometryType.Polygon + && type.value() != Geometry.GeometryType.Unknown) + throw new GeometryException("invalid shape type"); + return importFromWkbPolygon(true, importFlags, false, false, + wkbHelper); + + case WkbGeometryType.wkbMultiPolygonM: + if (type.value() != Geometry.GeometryType.Polygon + && type.value() != Geometry.GeometryType.Unknown) + throw new GeometryException("invalid shape type"); + return importFromWkbPolygon(true, importFlags, false, true, + wkbHelper); + + case WkbGeometryType.wkbMultiPolygonZ: + if (type.value() != Geometry.GeometryType.Polygon + && type.value() != Geometry.GeometryType.Unknown) + throw new GeometryException("invalid shape type"); + return importFromWkbPolygon(true, importFlags, true, false, + wkbHelper); + + case WkbGeometryType.wkbMultiPolygonZM: + if (type.value() != Geometry.GeometryType.Polygon + && type.value() != Geometry.GeometryType.Unknown) + throw new GeometryException("invalid shape type"); + return importFromWkbPolygon(true, importFlags, true, true, + wkbHelper); + + case WkbGeometryType.wkbLineString: + if (type.value() != Geometry.GeometryType.Polyline + && type.value() != Geometry.GeometryType.Unknown) + throw new GeometryException("invalid shape type"); + return importFromWkbPolyline(false, importFlags, false, false, + wkbHelper); + + case WkbGeometryType.wkbLineStringM: + if (type.value() != Geometry.GeometryType.Polyline + && type.value() != Geometry.GeometryType.Unknown) + throw new GeometryException("invalid shape type"); + return importFromWkbPolyline(false, importFlags, false, true, + wkbHelper); + + case WkbGeometryType.wkbLineStringZ: + if (type.value() != Geometry.GeometryType.Polyline + && type.value() != Geometry.GeometryType.Unknown) + throw new GeometryException("invalid shape type"); + return importFromWkbPolyline(false, importFlags, true, false, + wkbHelper); + + case WkbGeometryType.wkbLineStringZM: + if (type.value() != Geometry.GeometryType.Polyline + && type.value() != Geometry.GeometryType.Unknown) + throw new GeometryException("invalid shape type"); + return importFromWkbPolyline(false, importFlags, true, true, + wkbHelper); + + case WkbGeometryType.wkbMultiLineString: + if (type.value() != Geometry.GeometryType.Polyline + && type.value() != Geometry.GeometryType.Unknown) + throw new GeometryException("invalid shape type"); + return importFromWkbPolyline(true, importFlags, false, false, + wkbHelper); + + case WkbGeometryType.wkbMultiLineStringM: + if (type.value() != Geometry.GeometryType.Polyline + && type.value() != Geometry.GeometryType.Unknown) + throw new GeometryException("invalid shape type"); + return importFromWkbPolyline(true, importFlags, false, true, + wkbHelper); + + case WkbGeometryType.wkbMultiLineStringZ: + if (type.value() != Geometry.GeometryType.Polyline + && type.value() != Geometry.GeometryType.Unknown) + throw new GeometryException("invalid shape type"); + return importFromWkbPolyline(true, importFlags, true, false, + wkbHelper); + + case WkbGeometryType.wkbMultiLineStringZM: + if (type.value() != Geometry.GeometryType.Polyline + && type.value() != Geometry.GeometryType.Unknown) + throw new GeometryException("invalid shape type"); + return importFromWkbPolyline(true, importFlags, true, true, + wkbHelper); + + case WkbGeometryType.wkbMultiPoint: + if (type.value() != Geometry.GeometryType.MultiPoint + && type.value() != Geometry.GeometryType.Unknown) + throw new GeometryException("invalid shape type"); + return importFromWkbMultiPoint(importFlags, false, false, wkbHelper); + + case WkbGeometryType.wkbMultiPointM: + if (type.value() != Geometry.GeometryType.MultiPoint + && type.value() != Geometry.GeometryType.Unknown) + throw new GeometryException("invalid shape type"); + return importFromWkbMultiPoint(importFlags, false, true, wkbHelper); + + case WkbGeometryType.wkbMultiPointZ: + if (type.value() != Geometry.GeometryType.MultiPoint + && type.value() != Geometry.GeometryType.Unknown) + throw new GeometryException("invalid shape type"); + return importFromWkbMultiPoint(importFlags, true, false, wkbHelper); + + case WkbGeometryType.wkbMultiPointZM: + if (type.value() != Geometry.GeometryType.MultiPoint + && type.value() != Geometry.GeometryType.Unknown) + throw new GeometryException("invalid shape type"); + return importFromWkbMultiPoint(importFlags, true, true, wkbHelper); + + case WkbGeometryType.wkbPoint: + if (type.value() != Geometry.GeometryType.Point + && type.value() != Geometry.GeometryType.Unknown) + throw new GeometryException("invalid shape type"); + return importFromWkbPoint(importFlags, false, false, wkbHelper); + + case WkbGeometryType.wkbPointM: + if (type.value() != Geometry.GeometryType.Point + && type.value() != Geometry.GeometryType.Unknown) + throw new GeometryException("invalid shape type"); + return importFromWkbPoint(importFlags, false, true, wkbHelper); + + case WkbGeometryType.wkbPointZ: + if (type.value() != Geometry.GeometryType.Point + && type.value() != Geometry.GeometryType.Unknown) + throw new GeometryException("invalid shape type"); + return importFromWkbPoint(importFlags, true, false, wkbHelper); + + case WkbGeometryType.wkbPointZM: + if (type.value() != Geometry.GeometryType.Point + && type.value() != Geometry.GeometryType.Unknown) + throw new GeometryException("invalid shape type"); + return importFromWkbPoint(importFlags, true, true, wkbHelper); + + default: + throw new GeometryException("invalid shape type"); + } + } + + private static Geometry importFromWkbPolygon(boolean bMultiPolygon, + int importFlags, boolean bZs, boolean bMs, WkbHelper wkbHelper) { + int offset; + int polygonCount; + + if (bMultiPolygon) { + polygonCount = wkbHelper.getInt(5); + offset = 9; + } else { + polygonCount = 1; + offset = 0; + } + + // Find total point count and part count + int point_count = 0; + int partCount = 0; + int tempOffset = offset; + for (int ipolygon = 0; ipolygon < polygonCount; ipolygon++) { + tempOffset += 5; // skip redundant byte order and type fields + int ipartcount = wkbHelper.getInt(tempOffset); + tempOffset += 4; + + for (int ipart = 0; ipart < ipartcount; ipart++) { + int ipointcount = wkbHelper.getInt(tempOffset); + tempOffset += 4; + + // If ipointcount == 0, then we have an empty part + if (ipointcount == 0) + continue; + + if (ipointcount <= 2) { + tempOffset += ipointcount * 2 * 8; + + if (bZs) + tempOffset += ipointcount * 8; + + if (bMs) + tempOffset += ipointcount * 8; + + if (ipointcount == 1) + point_count += ipointcount + 1; + else + point_count += ipointcount; + + partCount++; + + continue; + } + + double startx = wkbHelper.getDouble(tempOffset); + tempOffset += 8; + double starty = wkbHelper.getDouble(tempOffset); + tempOffset += 8; + double startz = NumberUtils.TheNaN; + double startm = NumberUtils.TheNaN; + + if (bZs) { + startz = wkbHelper.getDouble(tempOffset); + tempOffset += 8; + } + + if (bMs) { + startm = wkbHelper.getDouble(tempOffset); + tempOffset += 8; + } + + tempOffset += (ipointcount - 2) * 2 * 8; + + if (bZs) + tempOffset += (ipointcount - 2) * 8; + + if (bMs) + tempOffset += (ipointcount - 2) * 8; + + double endx = wkbHelper.getDouble(tempOffset); + tempOffset += 8; + double endy = wkbHelper.getDouble(tempOffset); + tempOffset += 8; + double endz = NumberUtils.TheNaN; + double endm = NumberUtils.TheNaN; + + if (bZs) { + endz = wkbHelper.getDouble(tempOffset); + tempOffset += 8; + } + + if (bMs) { + endm = wkbHelper.getDouble(tempOffset); + tempOffset += 8; + } + + if ((startx == endx || (NumberUtils.isNaN(startx) && NumberUtils + .isNaN(endx))) + && (starty == endy || (NumberUtils.isNaN(starty) && NumberUtils + .isNaN(endy))) + && (!bZs || startz == endz || (NumberUtils + .isNaN(startz) && NumberUtils.isNaN(endz))) + && (!bMs || startm == endm || (NumberUtils + .isNaN(startm) && NumberUtils.isNaN(endm)))) { + point_count += ipointcount - 1; + } else { + point_count += ipointcount; + } + + partCount++; + } + } + + AttributeStreamOfDbl position = null; + AttributeStreamOfDbl zs = null; + AttributeStreamOfDbl ms = null; + AttributeStreamOfInt32 parts = null; + AttributeStreamOfInt8 pathFlags = null; + + Geometry newPolygon; + MultiPathImpl polygon; + + newPolygon = new Polygon(); + polygon = (MultiPathImpl) newPolygon._getImpl(); + + if (bZs) + polygon.addAttribute(VertexDescription.Semantics.Z); + + if (bMs) + polygon.addAttribute(VertexDescription.Semantics.M); + + if (point_count > 0) { + parts = (AttributeStreamOfInt32) (AttributeStreamBase + .createIndexStream(partCount + 1, 0)); + pathFlags = (AttributeStreamOfInt8) (AttributeStreamBase + .createByteStream(parts.size(), (byte) PathFlags.enumClosed)); + position = (AttributeStreamOfDbl) (AttributeStreamBase + .createAttributeStreamWithSemantics( + VertexDescription.Semantics.POSITION, point_count)); + + if (bZs) + zs = (AttributeStreamOfDbl) (AttributeStreamBase + .createAttributeStreamWithSemantics( + VertexDescription.Semantics.Z, point_count)); + + if (bMs) + ms = (AttributeStreamOfDbl) (AttributeStreamBase + .createAttributeStreamWithSemantics( + VertexDescription.Semantics.M, point_count)); + } + + boolean bCreateMs = false, bCreateZs = false; + int ipartend = 0; + int ipolygonend = 0; + int part_index = 0; + + // read Coordinates + for (int ipolygon = 0; ipolygon < polygonCount; ipolygon++) { + offset += 5; // skip redundant byte order and type fields + int ipartcount = wkbHelper.getInt(offset); + offset += 4; + int ipolygonstart = ipolygonend; + ipolygonend = ipolygonstart + ipartcount; + + for (int ipart = ipolygonstart; ipart < ipolygonend; ipart++) { + int ipointcount = wkbHelper.getInt(offset); + offset += 4; + + if (ipointcount == 0) + continue; + + int ipartstart = ipartend; + ipartend += ipointcount; + boolean bSkipLastPoint = true; + + if (ipointcount == 1) { + ipartstart++; + ipartend++; + bSkipLastPoint = false; + } else if (ipointcount == 2) { + bSkipLastPoint = false; + } else { + // Check if start point is equal to end point + + tempOffset = offset; + + double startx = wkbHelper.getDouble(tempOffset); + tempOffset += 8; + double starty = wkbHelper.getDouble(tempOffset); + tempOffset += 8; + double startz = NumberUtils.TheNaN; + double startm = NumberUtils.TheNaN; + + if (bZs) { + startz = wkbHelper.getDouble(tempOffset); + tempOffset += 8; + } + + if (bMs) { + startm = wkbHelper.getDouble(tempOffset); + tempOffset += 8; + } + + tempOffset += (ipointcount - 2) * 2 * 8; + + if (bZs) + tempOffset += (ipointcount - 2) * 8; + + if (bMs) + tempOffset += (ipointcount - 2) * 8; + + double endx = wkbHelper.getDouble(tempOffset); + tempOffset += 8; + double endy = wkbHelper.getDouble(tempOffset); + tempOffset += 8; + double endz = NumberUtils.TheNaN; + double endm = NumberUtils.TheNaN; + + if (bZs) { + endz = wkbHelper.getDouble(tempOffset); + tempOffset += 8; + } + + if (bMs) { + endm = wkbHelper.getDouble(tempOffset); + tempOffset += 8; + } + + if ((startx == endx || (NumberUtils.isNaN(startx) && NumberUtils + .isNaN(endx))) + && (starty == endy || (NumberUtils.isNaN(starty) && NumberUtils + .isNaN(endy))) + && (!bZs || startz == endz || (NumberUtils + .isNaN(startz) && NumberUtils.isNaN(endz))) + && (!bMs || startm == endm || (NumberUtils + .isNaN(startm) && NumberUtils.isNaN(endm)))) + ipartend--; + else + bSkipLastPoint = false; + } + + if (ipart == ipolygonstart) + pathFlags.setBits(ipart, + (byte) PathFlags.enumOGCStartPolygon); + + parts.write(++part_index, ipartend); + + // We must write from the buffer backwards - ogc polygon + // format is opposite of shapefile format + for (int i = ipartstart; i < ipartend; i++) { + double x = wkbHelper.getDouble(offset); + offset += 8; + double y = wkbHelper.getDouble(offset); + offset += 8; + + position.write(2 * i, x); + position.write(2 * i + 1, y); + + if (bZs) { + double z = wkbHelper.getDouble(offset); + offset += 8; + + zs.write(i, z); + if (!VertexDescription.isDefaultValue( + VertexDescription.Semantics.Z, z)) + bCreateZs = true; + } + + if (bMs) { + double m = wkbHelper.getDouble(offset); + offset += 8; + + ms.write(i, m); + if (!VertexDescription.isDefaultValue( + VertexDescription.Semantics.M, m)) + bCreateMs = true; + } + } + + if (bSkipLastPoint) { + offset += 2 * 8; + + if (bZs) + offset += 8; + + if (bMs) + offset += 8; + } else if (ipointcount == 1) { + double x = position.read(2 * ipartstart); + double y = position.read(2 * ipartstart + 1); + position.write(2 * (ipartstart - 1), x); + position.write(2 * (ipartstart - 1) + 1, y); + + if (bZs) { + double z = zs.read(ipartstart); + zs.write(ipartstart - 1, z); + } + + if (bMs) { + double m = ms.read(ipartstart); + ms.write(ipartstart - 1, m); + } + } + } + } + + // set envelopes and assign AttributeStreams + + if (point_count > 0) { + polygon.setPathStreamRef(parts); // sets m_parts + polygon.setPathFlagsStreamRef(pathFlags); + polygon.setAttributeStreamRef(VertexDescription.Semantics.POSITION, + position); + + if (bZs) { + if (!bCreateZs) + zs = null; + + polygon.setAttributeStreamRef(VertexDescription.Semantics.Z, zs); + } + + if (bMs) { + if (!bCreateMs) + ms = null; + + polygon.setAttributeStreamRef(VertexDescription.Semantics.M, ms); + } + + polygon.notifyModified(MultiPathImpl.DirtyFlags.DirtyAll); + + if (!InternalUtils.isClockwiseRing(polygon, 0)) + polygon.reverseAllPaths(); + } + + if ((importFlags & (int) WkbImportFlags.wkbImportNonTrusted) == 0) + polygon.setIsSimple(MultiVertexGeometryImpl.GeometryXSimple.Weak, + 0.0, false); + + polygon.setDirtyOGCFlags(false); + wkbHelper.adjustment += offset; + + return newPolygon; + } + + private static Geometry importFromWkbPolyline(boolean bMultiPolyline, + int importFlags, boolean bZs, boolean bMs, WkbHelper wkbHelper) { + int offset; + int originalPartCount; + + if (bMultiPolyline) { + originalPartCount = wkbHelper.getInt(5); + offset = 9; + } else { + originalPartCount = 1; + offset = 0; + } + + // Find total point count and part count + int point_count = 0; + int partCount = 0; + int tempOffset = offset; + for (int ipart = 0; ipart < originalPartCount; ipart++) { + tempOffset += 5; // skip redundant byte order and type fields + int ipointcount = wkbHelper.getInt(tempOffset); + tempOffset += 4; + + // If ipointcount == 0, then we have an empty part + if (ipointcount == 0) + continue; + + point_count += ipointcount; + partCount++; + + if (ipointcount == 1) + point_count++; + + tempOffset += ipointcount * 2 * 8; + + if (bZs) + tempOffset += ipointcount * 8; + + if (bMs) + tempOffset += ipointcount * 8; + } + + AttributeStreamOfDbl position = null; + AttributeStreamOfDbl zs = null; + AttributeStreamOfDbl ms = null; + AttributeStreamOfInt32 parts = null; + AttributeStreamOfInt8 pathFlags = null; + + Polyline newpolyline; + MultiPathImpl polyline; + + newpolyline = new Polyline(); + polyline = (MultiPathImpl) newpolyline._getImpl(); + + if (bZs) + polyline.addAttribute(VertexDescription.Semantics.Z); + + if (bMs) + polyline.addAttribute(VertexDescription.Semantics.M); + + if (point_count > 0) { + parts = (AttributeStreamOfInt32) (AttributeStreamBase + .createIndexStream(partCount + 1, 0)); + pathFlags = (AttributeStreamOfInt8) (AttributeStreamBase + .createByteStream(parts.size(), (byte) 0)); + position = (AttributeStreamOfDbl) (AttributeStreamBase + .createAttributeStreamWithSemantics( + VertexDescription.Semantics.POSITION, point_count)); + + if (bZs) + zs = (AttributeStreamOfDbl) (AttributeStreamBase + .createAttributeStreamWithSemantics( + VertexDescription.Semantics.Z, point_count)); + + if (bMs) + ms = (AttributeStreamOfDbl) (AttributeStreamBase + .createAttributeStreamWithSemantics( + VertexDescription.Semantics.M, point_count)); + } + + boolean bCreateMs = false, bCreateZs = false; + int ipartend = 0; + int part_index = 0; + + // read Coordinates + for (int ipart = 0; ipart < originalPartCount; ipart++) { + offset += 5; // skip redundant byte order and type fields + + int ipointcount = wkbHelper.getInt(offset); + offset += 4; + + if (ipointcount == 0) + continue; + + int ipartstart = ipartend; + ipartend = ipartstart + ipointcount; + + if (ipointcount == 1) { + ipartstart++; + ipartend++; + } + + parts.write(++part_index, ipartend); + + for (int i = ipartstart; i < ipartend; i++) { + double x = wkbHelper.getDouble(offset); + offset += 8; + double y = wkbHelper.getDouble(offset); + offset += 8; + + position.write(2 * i, x); + position.write(2 * i + 1, y); + + if (bZs) { + double z = wkbHelper.getDouble(offset); + offset += 8; + + zs.write(i, z); + if (!VertexDescription.isDefaultValue( + VertexDescription.Semantics.Z, z)) + bCreateZs = true; + } + + if (bMs) { + double m = wkbHelper.getDouble(offset); + offset += 8; + + ms.write(i, m); + if (!VertexDescription.isDefaultValue( + VertexDescription.Semantics.M, m)) + bCreateMs = true; + } + } + + if (ipointcount == 1) { + double x = position.read(2 * ipartstart); + double y = position.read(2 * ipartstart + 1); + position.write(2 * (ipartstart - 1), x); + position.write(2 * (ipartstart - 1) + 1, y); + + if (bZs) { + double z = zs.read(ipartstart); + zs.write(ipartstart - 1, z); + } + + if (bMs) { + double m = ms.read(ipartstart); + ms.write(ipartstart - 1, m); + } + } + } + + // set envelopes and assign AttributeStreams + + if (point_count > 0) { + polyline.setPathStreamRef(parts); // sets m_parts + polyline.setPathFlagsStreamRef(pathFlags); + polyline.setAttributeStreamRef( + VertexDescription.Semantics.POSITION, position); + + if (bZs) { + if (!bCreateZs) + zs = null; + + polyline.setAttributeStreamRef(VertexDescription.Semantics.Z, + zs); + } + + if (bMs) { + if (!bCreateMs) + ms = null; + + polyline.setAttributeStreamRef(VertexDescription.Semantics.M, + ms); + } + + polyline.notifyModified(MultiPathImpl.DirtyFlags.DirtyAll); + } + + wkbHelper.adjustment += offset; + + return newpolyline; + } + + private static Geometry importFromWkbMultiPoint(int importFlags, + boolean bZs, boolean bMs, WkbHelper wkbHelper) { + int offset = 5; // skip byte order and type + + // set point count + int point_count = wkbHelper.getInt(offset); + offset += 4; + + AttributeStreamOfDbl position = null; + AttributeStreamOfDbl zs = null; + AttributeStreamOfDbl ms = null; + + MultiPoint newmultipoint; + MultiPointImpl multipoint; + + newmultipoint = new MultiPoint(); + multipoint = (MultiPointImpl) newmultipoint._getImpl(); + + if (bZs) + multipoint.addAttribute(VertexDescription.Semantics.Z); + + if (bMs) + multipoint.addAttribute(VertexDescription.Semantics.M); + + if (point_count > 0) { + position = (AttributeStreamOfDbl) (AttributeStreamBase + .createAttributeStreamWithSemantics( + VertexDescription.Semantics.POSITION, point_count)); + + if (bZs) + zs = (AttributeStreamOfDbl) (AttributeStreamBase + .createAttributeStreamWithSemantics( + VertexDescription.Semantics.Z, point_count)); + + if (bMs) + ms = (AttributeStreamOfDbl) (AttributeStreamBase + .createAttributeStreamWithSemantics( + VertexDescription.Semantics.M, point_count)); + } + + boolean bCreateMs = false, bCreateZs = false; + for (int i = 0; i < point_count; i++) { + offset += 5; // skip redundant byte order and type fields + + // read xy coordinates + double x = wkbHelper.getDouble(offset); + offset += 8; + double y = wkbHelper.getDouble(offset); + offset += 8; + + position.write(2 * i, x); + position.write(2 * i + 1, y); + + if (bZs) { + double z = wkbHelper.getDouble(offset); + offset += 8; + + zs.write(i, z); + if (!VertexDescription.isDefaultValue( + VertexDescription.Semantics.Z, z)) + bCreateZs = true; + } + + if (bMs) { + double m = wkbHelper.getDouble(offset); + offset += 8; + + ms.write(i, m); + if (!VertexDescription.isDefaultValue( + VertexDescription.Semantics.M, m)) + bCreateMs = true; + } + } + + // set envelopes and assign AttributeStreams + + if (point_count > 0) { + multipoint.resize(point_count); + multipoint.setAttributeStreamRef( + VertexDescription.Semantics.POSITION, position); + + if (bZs) { + if (!bCreateZs) + zs = null; + + multipoint.setAttributeStreamRef(VertexDescription.Semantics.Z, + zs); + } + + if (bMs) { + if (!bCreateMs) + ms = null; + + multipoint.setAttributeStreamRef(VertexDescription.Semantics.M, + ms); + } + + multipoint.notifyModified(MultiPointImpl.DirtyFlags.DirtyAll); + } + + wkbHelper.adjustment += offset; + + return newmultipoint; + } + + private static Geometry importFromWkbPoint(int importFlags, boolean bZs, + boolean bMs, WkbHelper wkbHelper) { + int offset = 5; // skip byte order and type + + // set xy coordinate + double x = wkbHelper.getDouble(offset); + offset += 8; + double y = wkbHelper.getDouble(offset); + offset += 8; + + double z = NumberUtils.TheNaN; + if (bZs) { + z = wkbHelper.getDouble(offset); + offset += 8; + } + + double m = NumberUtils.TheNaN; + if (bMs) { + m = wkbHelper.getDouble(offset); + offset += 8; + } + + boolean bEmpty = NumberUtils.isNaN(x); + Point point = new Point(); + + if (!bEmpty) { + point.setX(x); + point.setY(y); + } + + // set Z + if (bZs) { + point.addAttribute(VertexDescription.Semantics.Z); + if (!bEmpty) + point.setZ(z); + } + + // set M + if (bMs) { + point.addAttribute(VertexDescription.Semantics.M); + if (!bEmpty) + point.setM(m); + } + + wkbHelper.adjustment += offset; + + return point; + } +} diff --git a/src/com/esri/core/geometry/OperatorImportFromWkt.java b/src/com/esri/core/geometry/OperatorImportFromWkt.java new file mode 100644 index 00000000..b64c70ac --- /dev/null +++ b/src/com/esri/core/geometry/OperatorImportFromWkt.java @@ -0,0 +1,58 @@ +/* + Copyright 1995-2013 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ +package com.esri.core.geometry; + +import com.esri.core.geometry.Operator.Type; + +public abstract class OperatorImportFromWkt extends Operator { + @Override + public Type getType() { + return Type.ImportFromWkb; + } + + /** + * Performs the ImportFromWkt operation. + * @param import_flags Use the {@link WktImportFlags} interface. + * @param type Use the {@link Geometry.Type} enum. + * @param wkt_string The string holding the Geometry in wkt format. + * @return Returns the imported Geometry. + */ + public abstract Geometry execute(int import_flags, Geometry.Type type, + String wkt_string, ProgressTracker progress_tracker); + + /** + * Performs the ImportFromWkt operation. + * @param import_flags Use the {@link WktImportFlags} interface. + * @param wkt_string The string holding the Geometry in wkt format. + * @return Returns the imported OGCStructure. + */ + public abstract OGCStructure executeOGC(int import_flags, + String wkt_string, ProgressTracker progress_tracker); + + public static OperatorImportFromWkt local() { + return (OperatorImportFromWkt) OperatorFactoryLocal.getInstance() + .getOperator(Type.ImportFromWkt); + } + +} diff --git a/src/com/esri/core/geometry/OperatorImportFromWktLocal.java b/src/com/esri/core/geometry/OperatorImportFromWktLocal.java new file mode 100644 index 00000000..d742caa9 --- /dev/null +++ b/src/com/esri/core/geometry/OperatorImportFromWktLocal.java @@ -0,0 +1,647 @@ +/* + Copyright 1995-2013 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ +package com.esri.core.geometry; + +import java.util.ArrayList; + +class OperatorImportFromWktLocal extends OperatorImportFromWkt { + @Override + public Geometry execute(int import_flags, Geometry.Type type, + String wkt_string, ProgressTracker progress_tracker) { + WktParser wkt_parser = new WktParser(wkt_string); + int current_token = wkt_parser.nextToken(); + return importFromWkt(import_flags, type, wkt_parser); + } + + @Override + public OGCStructure executeOGC(int import_flags, String wkt_string, + ProgressTracker progress_tracker) { + ArrayList stack = new ArrayList(0); + WktParser wkt_parser = new WktParser(wkt_string); + + OGCStructure root = new OGCStructure(); + root.m_structures = new ArrayList(0); + stack.add(root); // add dummy root + + while (wkt_parser.nextToken() != WktParser.WktToken.not_available) { + int current_token = wkt_parser.currentToken(); + + if (current_token == WktParser.WktToken.right_paren) { + stack.remove(stack.size() - 1); + continue; + } + + int ogc_type = current_token; + OGCStructure last = stack.get(stack.size() - 1); + + if (current_token == WktParser.WktToken.geometrycollection) { + current_token = wkt_parser.nextToken(); + + if (current_token == WktParser.WktToken.attribute_z + || current_token == WktParser.WktToken.attribute_m + || current_token == WktParser.WktToken.attribute_zm) + wkt_parser.nextToken(); + + OGCStructure next = new OGCStructure(); + next.m_type = ogc_type; + next.m_structures = new ArrayList(0); + last.m_structures.add(next); + + if (current_token != WktParser.WktToken.empty) + stack.add(next); + continue; + } + + Geometry geometry = importFromWkt(import_flags, + Geometry.Type.Unknown, wkt_parser); + + OGCStructure leaf = new OGCStructure(); + leaf.m_type = ogc_type; + leaf.m_geometry = geometry; + last.m_structures.add(leaf); + } + + return root; + } + + static Geometry importFromWkt(int import_flags, Geometry.Type type, + WktParser wkt_parser) { + int current_token = wkt_parser.currentToken(); + + switch (current_token) { + case WktParser.WktToken.multipolygon: + if (type != Geometry.Type.Polygon && type != Geometry.Type.Unknown) + throw new IllegalArgumentException("invalid shapetype"); + return polygonTaggedText(true, import_flags, wkt_parser); + + case WktParser.WktToken.multilinestring: + if (type != Geometry.Type.Polyline && type != Geometry.Type.Unknown) + throw new IllegalArgumentException("invalid shapetype"); + return lineStringTaggedText(true, import_flags, wkt_parser); + + case WktParser.WktToken.multipoint: + if (type != Geometry.Type.MultiPoint + && type != Geometry.Type.Unknown) + throw new IllegalArgumentException("invalid shapetype"); + return multiPointTaggedText(import_flags, wkt_parser); + + case WktParser.WktToken.polygon: + if (type != Geometry.Type.Polygon && type != Geometry.Type.Unknown) + throw new IllegalArgumentException("invalid shapetype"); + return polygonTaggedText(false, import_flags, wkt_parser); + + case WktParser.WktToken.linestring: + if (type != Geometry.Type.Polyline && type != Geometry.Type.Unknown) + throw new IllegalArgumentException("invalid shapetype"); + return lineStringTaggedText(false, import_flags, wkt_parser); + + case WktParser.WktToken.point: + if (type != Geometry.Type.Point && type != Geometry.Type.Unknown) + throw new IllegalArgumentException("invalid shapetype"); + return pointTaggedText(import_flags, wkt_parser); + + default: + break; // warning fix + } + + return null; + } + + static Geometry polygonTaggedText(boolean b_multi_polygon, + int import_flags, WktParser wkt_parser) { + MultiPath multi_path; + MultiPathImpl multi_path_impl; + AttributeStreamOfDbl zs = null; + AttributeStreamOfDbl ms = null; + AttributeStreamOfDbl position; + AttributeStreamOfInt32 paths; + AttributeStreamOfInt8 path_flags; + + position = (AttributeStreamOfDbl) AttributeStreamBase + .createDoubleStream(0); + paths = (AttributeStreamOfInt32) AttributeStreamBase.createIndexStream( + 1, 0); + path_flags = (AttributeStreamOfInt8) AttributeStreamBase + .createByteStream(1, (byte) 0); + + multi_path = new Polygon(); + multi_path_impl = (MultiPathImpl) multi_path._getImpl(); + + int current_token = wkt_parser.nextToken(); + + if (current_token == WktParser.WktToken.attribute_z) { + zs = (AttributeStreamOfDbl) AttributeStreamBase.createDoubleStream( + 0, NumberUtils.TheNaN); + multi_path_impl.addAttribute(VertexDescription.Semantics.Z); + wkt_parser.nextToken(); + } else if (current_token == WktParser.WktToken.attribute_m) { + ms = (AttributeStreamOfDbl) AttributeStreamBase.createDoubleStream( + 0, NumberUtils.TheNaN); + multi_path_impl.addAttribute(VertexDescription.Semantics.M); + wkt_parser.nextToken(); + } else if (current_token == WktParser.WktToken.attribute_zm) { + zs = (AttributeStreamOfDbl) AttributeStreamBase.createDoubleStream( + 0, NumberUtils.TheNaN); + ms = (AttributeStreamOfDbl) AttributeStreamBase.createDoubleStream( + 0, NumberUtils.TheNaN); + multi_path_impl.addAttribute(VertexDescription.Semantics.Z); + multi_path_impl.addAttribute(VertexDescription.Semantics.M); + wkt_parser.nextToken(); + } + + int point_count; + + if (b_multi_polygon) + point_count = multiPolygonText(zs, ms, position, paths, path_flags, + wkt_parser); + else + point_count = polygonText(zs, ms, position, paths, path_flags, 0, + wkt_parser); + + if (point_count != 0) { + assert (2 * point_count == position.size()); + multi_path_impl.setAttributeStreamRef( + VertexDescription.Semantics.POSITION, position); + multi_path_impl.setPathStreamRef(paths); + multi_path_impl.setPathFlagsStreamRef(path_flags); + + if (zs != null) + multi_path_impl.setAttributeStreamRef( + VertexDescription.Semantics.Z, zs); + + if (ms != null) + multi_path_impl.setAttributeStreamRef( + VertexDescription.Semantics.M, ms); + + multi_path_impl.notifyModified(MultiPathImpl.DirtyFlags.DirtyAll); + + if (!InternalUtils.isClockwiseRing(multi_path_impl, 0)) + multi_path_impl.reverseAllPaths(); + } + + if ((import_flags & (int) WktImportFlags.wktImportNonTrusted) == 0) + multi_path_impl.setIsSimple(MultiPathImpl.GeometryXSimple.Weak, + 0.0, false); + + multi_path_impl.setDirtyOGCFlags(false); + + return multi_path; + } + + static Geometry lineStringTaggedText(boolean b_multi_linestring, + int import_flags, WktParser wkt_parser) { + MultiPath multi_path; + MultiPathImpl multi_path_impl; + AttributeStreamOfDbl zs = null; + AttributeStreamOfDbl ms = null; + AttributeStreamOfDbl position; + AttributeStreamOfInt32 paths; + AttributeStreamOfInt8 path_flags; + + position = (AttributeStreamOfDbl) AttributeStreamBase + .createDoubleStream(0); + paths = (AttributeStreamOfInt32) AttributeStreamBase.createIndexStream( + 1, 0); + path_flags = (AttributeStreamOfInt8) AttributeStreamBase + .createByteStream(1, (byte) 0); + + multi_path = new Polyline(); + multi_path_impl = (MultiPathImpl) multi_path._getImpl(); + + int current_token = wkt_parser.nextToken(); + + if (current_token == WktParser.WktToken.attribute_z) { + zs = (AttributeStreamOfDbl) AttributeStreamBase.createDoubleStream( + 0, NumberUtils.TheNaN); + multi_path_impl.addAttribute(VertexDescription.Semantics.Z); + wkt_parser.nextToken(); + } else if (current_token == WktParser.WktToken.attribute_m) { + ms = (AttributeStreamOfDbl) AttributeStreamBase.createDoubleStream( + 0, NumberUtils.TheNaN); + multi_path_impl.addAttribute(VertexDescription.Semantics.M); + wkt_parser.nextToken(); + } else if (current_token == WktParser.WktToken.attribute_zm) { + zs = (AttributeStreamOfDbl) AttributeStreamBase.createDoubleStream( + 0, NumberUtils.TheNaN); + ms = (AttributeStreamOfDbl) AttributeStreamBase.createDoubleStream( + 0, NumberUtils.TheNaN); + multi_path_impl.addAttribute(VertexDescription.Semantics.Z); + multi_path_impl.addAttribute(VertexDescription.Semantics.M); + wkt_parser.nextToken(); + } + + int point_count; + + if (b_multi_linestring) + point_count = multiLineStringText(zs, ms, position, paths, + path_flags, wkt_parser); + else + point_count = lineStringText(false, zs, ms, position, paths, + path_flags, wkt_parser); + + if (point_count != 0) { + assert (2 * point_count == position.size()); + multi_path_impl.setAttributeStreamRef( + VertexDescription.Semantics.POSITION, position); + multi_path_impl.setPathStreamRef(paths); + multi_path_impl.setPathFlagsStreamRef(path_flags); + + if (zs != null) + multi_path_impl.setAttributeStreamRef( + VertexDescription.Semantics.Z, zs); + + if (ms != null) + multi_path_impl.setAttributeStreamRef( + VertexDescription.Semantics.M, ms); + + multi_path_impl.notifyModified(MultiPathImpl.DirtyFlags.DirtyAll); + } + + return multi_path; + } + + static Geometry multiPointTaggedText(int import_flags, WktParser wkt_parser) { + MultiPoint multi_point; + MultiPointImpl multi_point_impl; + AttributeStreamOfDbl zs = null; + AttributeStreamOfDbl ms = null; + AttributeStreamOfDbl position; + + position = (AttributeStreamOfDbl) AttributeStreamBase + .createDoubleStream(0); + + multi_point = new MultiPoint(); + multi_point_impl = (MultiPointImpl) multi_point._getImpl(); + + int current_token = wkt_parser.nextToken(); + + if (current_token == WktParser.WktToken.attribute_z) { + zs = (AttributeStreamOfDbl) AttributeStreamBase.createDoubleStream( + 0, NumberUtils.TheNaN); + multi_point_impl.addAttribute(VertexDescription.Semantics.Z); + wkt_parser.nextToken(); + } else if (current_token == WktParser.WktToken.attribute_m) { + ms = (AttributeStreamOfDbl) AttributeStreamBase.createDoubleStream( + 0, NumberUtils.TheNaN); + multi_point_impl.addAttribute(VertexDescription.Semantics.M); + wkt_parser.nextToken(); + } else if (current_token == WktParser.WktToken.attribute_zm) { + zs = (AttributeStreamOfDbl) AttributeStreamBase.createDoubleStream( + 0, NumberUtils.TheNaN); + ms = (AttributeStreamOfDbl) AttributeStreamBase.createDoubleStream( + 0, NumberUtils.TheNaN); + multi_point_impl.addAttribute(VertexDescription.Semantics.Z); + multi_point_impl.addAttribute(VertexDescription.Semantics.M); + wkt_parser.nextToken(); + } + + int point_count = multiPointText(zs, ms, position, wkt_parser); + + if (point_count != 0) { + assert (2 * point_count == position.size()); + multi_point_impl.resize(point_count); + multi_point_impl.setAttributeStreamRef( + VertexDescription.Semantics.POSITION, position); + + if (zs != null) + multi_point_impl.setAttributeStreamRef( + VertexDescription.Semantics.Z, zs); + + if (ms != null) + multi_point_impl.setAttributeStreamRef( + VertexDescription.Semantics.M, ms); + + multi_point_impl.notifyModified(MultiPointImpl.DirtyFlags.DirtyAll); + } + + return multi_point; + } + + static Geometry pointTaggedText(int import_flags, WktParser wkt_parser) { + Point point = new Point(); + + int current_token = wkt_parser.nextToken(); + + if (current_token == WktParser.WktToken.attribute_z) { + point.addAttribute(VertexDescription.Semantics.Z); + wkt_parser.nextToken(); + } else if (current_token == WktParser.WktToken.attribute_m) { + point.addAttribute(VertexDescription.Semantics.M); + wkt_parser.nextToken(); + } else if (current_token == WktParser.WktToken.attribute_zm) { + point.addAttribute(VertexDescription.Semantics.Z); + point.addAttribute(VertexDescription.Semantics.M); + wkt_parser.nextToken(); + } + // At start of PointText + + current_token = wkt_parser.currentToken(); + + if (current_token != WktParser.WktToken.empty) { + wkt_parser.nextToken(); + + double x = wkt_parser.currentNumericLiteral(); + wkt_parser.nextToken(); + + double y = wkt_parser.currentNumericLiteral(); + wkt_parser.nextToken(); + + point.setXY(x, y); + + if (wkt_parser.hasZs()) { + double z = wkt_parser.currentNumericLiteral(); + wkt_parser.nextToken(); + point.setZ(z); + } + + if (wkt_parser.hasMs()) { + double m = wkt_parser.currentNumericLiteral(); + wkt_parser.nextToken(); + point.setM(m); + } + } + + return point; + } + + static int multiPolygonText(AttributeStreamOfDbl zs, + AttributeStreamOfDbl ms, AttributeStreamOfDbl position, + AttributeStreamOfInt32 paths, AttributeStreamOfInt8 path_flags, + WktParser wkt_parser) { + // At start of MultiPolygonText + + int current_token = wkt_parser.currentToken(); + + int total_point_count = 0; + + if (current_token == WktParser.WktToken.empty) + return total_point_count; + + current_token = wkt_parser.nextToken(); + + while (current_token != WktParser.WktToken.right_paren) { + // At start of PolygonText + + total_point_count = polygonText(zs, ms, position, paths, + path_flags, total_point_count, wkt_parser); + current_token = wkt_parser.nextToken(); + } + + return total_point_count; + } + + static int multiLineStringText(AttributeStreamOfDbl zs, + AttributeStreamOfDbl ms, AttributeStreamOfDbl position, + AttributeStreamOfInt32 paths, AttributeStreamOfInt8 path_flags, + WktParser wkt_parser) { + // At start of MultiLineStringText + + int current_token = wkt_parser.currentToken(); + + int total_point_count = 0; + + if (current_token == WktParser.WktToken.empty) + return total_point_count; + + current_token = wkt_parser.nextToken(); + + while (current_token != WktParser.WktToken.right_paren) { + // At start of LineStringText + + int point_count = lineStringText(false, zs, ms, position, paths, + path_flags, wkt_parser); + total_point_count += point_count; + + current_token = wkt_parser.nextToken(); + } + + return total_point_count; + } + + static int multiPointText(AttributeStreamOfDbl zs, AttributeStreamOfDbl ms, + AttributeStreamOfDbl position, WktParser wkt_parser) { + // At start of MultiPointText + + int current_token = wkt_parser.currentToken(); + + int point_count = 0; + + if (current_token == WktParser.WktToken.empty) + return point_count; + + current_token = wkt_parser.nextToken(); + + while (current_token != WktParser.WktToken.right_paren) { + // At start of PointText + + point_count += pointText(zs, ms, position, wkt_parser); + + if (current_token == WktParser.WktToken.left_paren + || current_token == WktParser.WktToken.empty) + current_token = wkt_parser.nextToken(); // ogc standard + else + current_token = wkt_parser.currentToken(); // not ogc standard. + // treat as + // linestring + } + + return point_count; + } + + static int polygonText(AttributeStreamOfDbl zs, AttributeStreamOfDbl ms, + AttributeStreamOfDbl position, AttributeStreamOfInt32 paths, + AttributeStreamOfInt8 path_flags, int total_point_count, + WktParser wkt_parser) { + // At start of PolygonText + + int current_token = wkt_parser.currentToken(); + + if (current_token == WktParser.WktToken.empty) + return total_point_count; + + boolean b_first_line_string = true; + + current_token = wkt_parser.nextToken(); + + while (current_token != WktParser.WktToken.right_paren) { + // At start of LineStringText + + int point_count = lineStringText(true, zs, ms, position, paths, + path_flags, wkt_parser); + + if (point_count != 0) { + if (b_first_line_string) { + b_first_line_string = false; + path_flags.setBits(path_flags.size() - 2, + (byte) PathFlags.enumOGCStartPolygon); + } + + path_flags.setBits(path_flags.size() - 2, + (byte) PathFlags.enumClosed); + total_point_count += point_count; + } + + current_token = wkt_parser.nextToken(); + } + + return total_point_count; + } + + static int lineStringText(boolean b_ring, AttributeStreamOfDbl zs, + AttributeStreamOfDbl ms, AttributeStreamOfDbl position, + AttributeStreamOfInt32 paths, AttributeStreamOfInt8 path_flags, + WktParser wkt_parser) { + // At start of LineStringText + + int current_token = wkt_parser.currentToken(); + + int point_count = 0; + + if (current_token == WktParser.WktToken.empty) + return point_count; + + boolean b_start_path = true; + double startx = NumberUtils.TheNaN; + double starty = NumberUtils.TheNaN; + double startz = NumberUtils.TheNaN; + double startm = NumberUtils.TheNaN; + + current_token = wkt_parser.nextToken(); + + while (current_token != WktParser.WktToken.right_paren) { + // At start of x + + double x = wkt_parser.currentNumericLiteral(); + wkt_parser.nextToken(); + + double y = wkt_parser.currentNumericLiteral(); + wkt_parser.nextToken(); + + double z = NumberUtils.TheNaN, m = NumberUtils.TheNaN; + + if (wkt_parser.hasZs()) { + z = wkt_parser.currentNumericLiteral(); + wkt_parser.nextToken(); + } + + if (wkt_parser.hasMs()) { + m = wkt_parser.currentNumericLiteral(); + wkt_parser.nextToken(); + } + + current_token = wkt_parser.currentToken(); + boolean b_add_point = true; + + if (b_ring && point_count >= 2 + && current_token == WktParser.WktToken.right_paren) { + // If the last point in the ring is not equal to the start + // point, then let's add it. + + if ((startx == x || (NumberUtils.isNaN(startx) && NumberUtils + .isNaN(x))) + && (starty == y || (NumberUtils.isNaN(starty) && NumberUtils + .isNaN(y))) + && (!wkt_parser.hasZs() || startz == z || (NumberUtils + .isNaN(startz) && NumberUtils.isNaN(z))) + && (!wkt_parser.hasMs() || startm == m || (NumberUtils + .isNaN(startm) && NumberUtils.isNaN(m)))) + b_add_point = false; + } + + if (b_add_point) { + if (b_start_path) { + b_start_path = false; + startx = x; + starty = y; + startz = z; + startm = m; + } + + point_count++; + addToStreams(zs, ms, position, x, y, z, m); + } + } + + if (point_count == 1) { + point_count++; + addToStreams(zs, ms, position, startx, starty, startz, startm); + } + + paths.add(position.size() / 2); + path_flags.add((byte) 0); + + return point_count; + } + + static int pointText(AttributeStreamOfDbl zs, AttributeStreamOfDbl ms, + AttributeStreamOfDbl position, WktParser wkt_parser) { + // At start of PointText + + int current_token = wkt_parser.currentToken(); + + if (current_token == WktParser.WktToken.empty) + return 0; + + if (current_token == WktParser.WktToken.left_paren) + wkt_parser.nextToken(); // ogc standard + + // At start of x + + double x = wkt_parser.currentNumericLiteral(); + wkt_parser.nextToken(); + + double y = wkt_parser.currentNumericLiteral(); + wkt_parser.nextToken(); + + double z = NumberUtils.TheNaN; + double m = NumberUtils.TheNaN; + + if (zs != null) { + z = wkt_parser.currentNumericLiteral(); + wkt_parser.nextToken(); + } + + if (ms != null) { + m = wkt_parser.currentNumericLiteral(); + wkt_parser.nextToken(); + } + + addToStreams(zs, ms, position, x, y, z, m); + + return 1; + } + + static void addToStreams(AttributeStreamOfDbl zs, AttributeStreamOfDbl ms, + AttributeStreamOfDbl position, double x, double y, double z, + double m) { + position.add(x); + position.add(y); + + if (zs != null) + zs.add(z); + + if (ms != null) + ms.add(m); + } +} diff --git a/src/com/esri/core/geometry/OperatorImportMapGeometryFromJsonParser.java b/src/com/esri/core/geometry/OperatorImportMapGeometryFromJsonParser.java new file mode 100644 index 00000000..7b69e2c0 --- /dev/null +++ b/src/com/esri/core/geometry/OperatorImportMapGeometryFromJsonParser.java @@ -0,0 +1,51 @@ +/* + Copyright 1995-2013 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ + +package com.esri.core.geometry; + +import org.codehaus.jackson.JsonParser; + +abstract class OperatorImportMapGeometryFromJsonParser extends Operator { + @Override + public Type getType() { + return Type.ImportMapGeometryFromJson; + } + + /** + * Performs the ImportFromJson operation on a number of Json Strings + * + * @return Returns a MapGeometryCursor. + */ + public abstract MapGeometryCursor execute(Geometry.Type type, + JsonParserCursor jsonParserCursor); + + /** + * Performs the ImportFromJson operation on a single Json string + * + * @return Returns a MapGeometry. + */ + public abstract MapGeometry execute(Geometry.Type type, + JsonParser jsonParser); + +} diff --git a/src/com/esri/core/geometry/OperatorImportMapGeometryFromJsonParserCursor.java b/src/com/esri/core/geometry/OperatorImportMapGeometryFromJsonParserCursor.java new file mode 100644 index 00000000..fc12df1e --- /dev/null +++ b/src/com/esri/core/geometry/OperatorImportMapGeometryFromJsonParserCursor.java @@ -0,0 +1,513 @@ +/* + Copyright 1995-2013 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ + +package com.esri.core.geometry; + +import java.io.IOException; +import org.codehaus.jackson.JsonParseException; +import org.codehaus.jackson.JsonParser; +import org.codehaus.jackson.JsonToken; + +class OperatorImportMapGeometryFromJsonParserCursor extends MapGeometryCursor { + JsonParserCursor m_inputJsonParsers; + + int m_type; + + int m_index; + + public OperatorImportMapGeometryFromJsonParserCursor(int type, + JsonParserCursor jsonParsers) { + m_index = -1; + if (jsonParsers == null) + throw new IllegalArgumentException(); + + m_type = type; + m_inputJsonParsers = jsonParsers; + } + + @Override + public int getGeometryID() { + return m_index; + } + + @Override + public MapGeometry next() { + JsonParser jsonParser; + if ((jsonParser = m_inputJsonParsers.next()) != null) { + m_index = m_inputJsonParsers.getID(); + return importFromJsonParser(jsonParser); + } + return null; + } + + private MapGeometry importFromJsonParser(JsonParser parser) { + try { + if (!JSONUtils.isObjectStart(parser)) + return null; + + switch (m_type) { + + case Geometry.GeometryType.Envelope: + return fromJsonToEnvelope(parser); + + case Geometry.GeometryType.Point: + return fromJsonToPoint(parser); + + case Geometry.GeometryType.Polygon: + return fromJsonToPolygon(parser); + + case Geometry.GeometryType.Polyline: + return fromJsonToPolyline(parser); + + case Geometry.GeometryType.MultiPoint: + return fromJsonToMultiPoint(parser); + + case Geometry.GeometryType.Unknown: + return fromJsonToUnknown(parser); + } + + } catch (Exception e) { + e.printStackTrace(); + return null; + } + return null; + } + + public static MapGeometry fromJsonToUnknown(JsonParser parser) + throws Exception { + + SpatialReference spatialReference = null; + Geometry geom = null; + + boolean isPoint = false; + boolean isEnvelope = false; + + double x = Double.NaN; + double y = Double.NaN; + double xmin = Double.NaN; + double ymin = Double.NaN; + double xmax = Double.NaN; + double ymax = Double.NaN; + + while (parser.nextToken() != JsonToken.END_OBJECT) { + String fieldName = parser.getCurrentName(); + parser.nextToken(); + + if ("paths".equals(fieldName)) + geom = parsePolyline(parser); + else if ("rings".equals(fieldName)) + geom = parsePolygon(parser); + else if ("points".equals(fieldName)) + geom = parseMultiPoint(parser); + else if ("spatialReference".equals(fieldName)) + spatialReference = SpatialReference.fromJson(parser); + else if ("x".equals(fieldName)) { + isPoint = true; + x = JSONUtils.readDouble(parser);// x = parser.getDoubleValue(); + } else if ("y".equals(fieldName)) + y = JSONUtils.readDouble(parser); + else if ("xmin".equals(fieldName)) { + isEnvelope = true; + xmin = JSONUtils.readDouble(parser); + } else if ("ymin".equals(fieldName)) + ymin = JSONUtils.readDouble(parser); + else if ("xmax".equals(fieldName)) + xmax = JSONUtils.readDouble(parser); + else if ("ymax".equals(fieldName)) + ymax = JSONUtils.readDouble(parser); + else + windup(parser); + } + + if (isPoint && !NumberUtils.isNaN(x) && !NumberUtils.isNaN(y)) + geom = new Point(x, y); + else if (isPoint) + geom = new Point(); + else if (isEnvelope && !NumberUtils.isNaN(xmin) + && !NumberUtils.isNaN(ymin) && !NumberUtils.isNaN(xmax) + && !NumberUtils.isNaN(ymax)) + geom = new Envelope(xmin, ymin, xmax, ymax); + else if (isEnvelope) + geom = new Envelope(); + + return new MapGeometry(geom, spatialReference); + } + + public static MapGeometry fromJsonToEnvelope(JsonParser parser) + throws Exception { + SpatialReference spatialReference = null; + + double xmin = Double.NaN; + double ymin = Double.NaN; + double xmax = Double.NaN; + double ymax = Double.NaN; + + while (parser.nextToken() != JsonToken.END_OBJECT) { + String fieldName = parser.getCurrentName(); + // Let's move to value + parser.nextToken(); + + if ("xmin".equals(fieldName)) + xmin = JSONUtils.readDouble(parser); + else if ("ymin".equals(fieldName)) + ymin = JSONUtils.readDouble(parser); + else if ("xmax".equals(fieldName)) + xmax = JSONUtils.readDouble(parser); + else if ("ymax".equals(fieldName)) + ymax = JSONUtils.readDouble(parser); + else if ("spatialReference".equals(fieldName)) + spatialReference = SpatialReference.fromJson(parser); + else + windup(parser); + } + + Envelope env; + if (!NumberUtils.isNaN(xmin) && !NumberUtils.isNaN(ymin) + && !NumberUtils.isNaN(xmax) && !NumberUtils.isNaN(ymax)) + env = new Envelope(xmin, ymin, xmax, ymax); + else + env = new Envelope(); + + return new MapGeometry(env, spatialReference); + } + + public static MapGeometry fromJsonToPoint(JsonParser parser) + throws Exception { + SpatialReference spatialReference = null; + + double x = Double.NaN; + double y = Double.NaN; + while (parser.nextToken() != JsonToken.END_OBJECT) { + String fieldName = parser.getCurrentName(); + // Let's move to value + parser.nextToken(); + + if ("x".equals(fieldName)) + x = JSONUtils.readDouble(parser); + else if ("y".equals(fieldName)) + y = JSONUtils.readDouble(parser); + else if ("spatialReference".equals(fieldName)) + spatialReference = SpatialReference.fromJson(parser); + else + windup(parser); + } + + Point pt; + if (!NumberUtils.isNaN(x) && !NumberUtils.isNaN(y)) + pt = new Point(x, y); + else + pt = new Point(); + + return new MapGeometry(pt, spatialReference); + } + + public static MapGeometry fromJsonToPolygon(JsonParser parser) + throws Exception { + SpatialReference spatialReference = null; + Geometry geom = null; + + while (parser.nextToken() != JsonToken.END_OBJECT) { + String fieldName = parser.getCurrentName(); + parser.nextToken(); + + if ("rings".equals(fieldName)) + geom = parsePolygon(parser); + else if ("spatialReference".equals(fieldName)) + spatialReference = SpatialReference.fromJson(parser); + else + windup(parser); + } + return new MapGeometry(geom, spatialReference); + } + + public static MapGeometry fromJsonToPolyline(JsonParser parser) + throws Exception { + SpatialReference spatialReference = null; + Geometry geom = null; + + while (parser.nextToken() != JsonToken.END_OBJECT) { + String fieldName = parser.getCurrentName(); + parser.nextToken(); + + if ("paths".equals(fieldName)) + geom = parsePolyline(parser); + else if ("spatialReference".equals(fieldName)) + spatialReference = SpatialReference.fromJson(parser); + else + windup(parser); + } + + return new MapGeometry(geom, spatialReference); + } + + public static MapGeometry fromJsonToMultiPoint(JsonParser parser) + throws Exception { + SpatialReference spatialReference = null; + Geometry geom = null; + + while (parser.nextToken() != JsonToken.END_OBJECT) { + String fieldName = parser.getCurrentName(); + parser.nextToken(); + + if ("points".equals(fieldName)) + geom = parseMultiPoint(parser); + else if ("spatialReference".equals(fieldName)) + spatialReference = SpatialReference.fromJson(parser); + else + windup(parser); + + } + + return new MapGeometry(geom, spatialReference); + } + + private static void windup(JsonParser parser) throws IOException, + JsonParseException { + parser.skipChildren(); + } + + private static Geometry parseMultiPoint(JsonParser parser)// FIXME add when + // z and m are + // supported: , + // boolean + // hasZs, + // boolean + // hasMs) + throws Exception { + MultiPoint mpt = new MultiPoint(); + + double x; + double y; + // FIXME add when z and m are supported + // double z = 0; + // double m = 0; + + if (parser.getCurrentToken() == JsonToken.START_ARRAY) { + while (parser.nextToken() != JsonToken.END_ARRAY) { + if (parser.getCurrentToken() == JsonToken.START_ARRAY) { + parser.nextToken(); // x + x = JSONUtils.readDouble(parser);// parser.getDoubleValue(); + + parser.nextToken(); // y + y = JSONUtils.readDouble(parser);// parser.getDoubleValue(); + + // move from VALUE_NUMBER_FLOAT to either END_ARRAY or + // VALUE_NUMBER_FLOAT + parser.nextToken(); + + if (parser.getCurrentToken() != JsonToken.END_ARRAY) { + // FIXME add when z and m are supported + // if(hasZs) + // z = JSONUtils.readDouble(parser); + // else if(hasMs) + // m = JSONUtils.readDouble(parser); + + // move from VALUE_NUMBER_FLOAT to either END_ARRAY or + // VALUE_NUMBER_FLOAT + parser.nextToken(); + if (parser.getCurrentToken() != JsonToken.END_ARRAY) { + // FIXME add when z and m are supported + // if(hasMs) + // m = JSONUtils.readDouble(parser); + + // move from VALUE_NUMBER_FLOAT to END_ARRAY + parser.nextToken(); + } + } + + // // possible z token + // if(parser.getCurrentToken() != JsonToken.END_ARRAY) + // parser.nextToken(); + // + // // possible m token + // if(parser.getCurrentToken() != JsonToken.END_ARRAY) + // parser.nextToken(); + + mpt.add(x, y); + + } + } + } + return mpt; + } + + private static Geometry parsePolygon(JsonParser parser)// FIXME add when z + // and m are + // supported: , + // boolean hasZs, + // boolean hasMs) + throws Exception { + Polygon polygon = new Polygon(); + if (parser.getCurrentToken() == JsonToken.START_ARRAY) { + + while (parser.nextToken() != JsonToken.END_ARRAY) { + // System.out.println("ring start..."); + boolean newPath = true; + double x = 0; + double y = 0; + // FIXME add when z and m are supported + // double z = 0; + // double m = 0; + + if (parser.getCurrentToken() == JsonToken.START_ARRAY) { + + JsonToken nextToken = parser.nextToken(); + while (nextToken != JsonToken.END_ARRAY) { + if (parser.getCurrentToken() == JsonToken.START_ARRAY) { + parser.nextToken(); // x + x = JSONUtils.readDouble(parser); + parser.nextToken(); // y + y = JSONUtils.readDouble(parser); + } + + // move from VALUE_NUMBER_FLOAT to either END_ARRAY or + // VALUE_NUMBER_FLOAT + parser.nextToken(); + // possible m or z token + if (parser.getCurrentToken() != JsonToken.END_ARRAY) { + // FIXME add when z and m are supported + // if(hasZs) + // z = JSONUtils.readDouble(parser); + // else if(hasMs) + // m = JSONUtils.readDouble(parser); + + // move from VALUE_NUMBER_FLOAT to either END_ARRAY + // or VALUE_NUMBER_FLOAT + parser.nextToken(); + if (parser.getCurrentToken() != JsonToken.END_ARRAY) { + // FIXME add when z and m are supported + // if(hasMs) + // m = JSONUtils.readDouble(parser); + + // move from VALUE_NUMBER_FLOAT to END_ARRAY + parser.nextToken(); + } + } + + nextToken = parser.nextToken(); + + if (newPath) { + newPath = false; + polygon.startPath(x, y); + } else { + if (nextToken != JsonToken.END_ARRAY) // keep adding + // lines + // except + // for the + // last + // (repeated) + // point in + // the + // ring + polygon.lineTo(x, y); + } + + } + // polygon.closePathWithLine(); + // System.out.println("ring end..."); + } + } + } + + return polygon; + } + + private static Geometry parsePolyline(JsonParser parser)// FIXME add when z + // and m are + // supported: , + // boolean hasZs, + // boolean hasMs) + throws Exception { + Polyline polyline = new Polyline(); + if (parser.getCurrentToken() == JsonToken.START_ARRAY) { + + while (parser.nextToken() != JsonToken.END_ARRAY) { + // System.out.println("ring start..."); + boolean newPath = true; + double x; + double y; + // FIXME add when z and m are supported + // double z = 0; + // double m = 0; + + if (parser.getCurrentToken() == JsonToken.START_ARRAY) { + + while (parser.nextToken() != JsonToken.END_ARRAY) { + if (parser.getCurrentToken() == JsonToken.START_ARRAY) { + parser.nextToken(); // x + x = JSONUtils.readDouble(parser); + parser.nextToken(); // y + y = JSONUtils.readDouble(parser); + + // move from VALUE_NUMBER_FLOAT to either END_ARRAY + // or VALUE_NUMBER_FLOAT + parser.nextToken(); + // possible m or z token + if (parser.getCurrentToken() != JsonToken.END_ARRAY) { + // FIXME add when z and m are supported + // if(hasZs) + // z = JSONUtils.readDouble(parser); + // else if(hasMs) + // m = JSONUtils.readDouble(parser); + + // move from VALUE_NUMBER_FLOAT to either + // END_ARRAY or VALUE_NUMBER_FLOAT + parser.nextToken(); + if (parser.getCurrentToken() != JsonToken.END_ARRAY) { + // FIXME add when z and m are supported + // if(hasMs) + // m = JSONUtils.readDouble(parser); + + // move from VALUE_NUMBER_FLOAT to END_ARRAY + parser.nextToken(); + } + } + + // // possible z token + // if(parser.getCurrentToken() != + // JsonToken.END_ARRAY) + // parser.nextToken(); + // + // // possible m token + // if(parser.getCurrentToken() != + // JsonToken.END_ARRAY) + // parser.nextToken(); + + if (newPath) { + newPath = false; + polyline.startPath(x, y); + } else { + polyline.lineTo(x, y); + } + + } + } + // System.out.println("ring end..."); + } + } + } + return polyline; + } + +} diff --git a/src/com/esri/core/geometry/OperatorImportMapGeometryFromJsonParserLocal.java b/src/com/esri/core/geometry/OperatorImportMapGeometryFromJsonParserLocal.java new file mode 100644 index 00000000..4f3f2783 --- /dev/null +++ b/src/com/esri/core/geometry/OperatorImportMapGeometryFromJsonParserLocal.java @@ -0,0 +1,47 @@ +/* + Copyright 1995-2013 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ +package com.esri.core.geometry; + +import org.codehaus.jackson.JsonParser; + +class OperatorImportMapGeometryFromJsonParserLocal extends + OperatorImportMapGeometryFromJsonParser { + + @Override + public MapGeometryCursor execute(Geometry.Type type, + JsonParserCursor jsonParserCursor) { + return new OperatorImportMapGeometryFromJsonParserCursor(type.value(), + jsonParserCursor); + } + + @Override + public MapGeometry execute(Geometry.Type type, JsonParser jsonParser) { + SimpleJsonParserCursor jsonParserCursor = new SimpleJsonParserCursor( + jsonParser); + OperatorImportMapGeometryFromJsonParserCursor cursor = new OperatorImportMapGeometryFromJsonParserCursor( + type.value(), jsonParserCursor); + return cursor.next(); + } + +} diff --git a/src/com/esri/core/geometry/OperatorInternalRelationUtils.java b/src/com/esri/core/geometry/OperatorInternalRelationUtils.java new file mode 100644 index 00000000..cc7fa41d --- /dev/null +++ b/src/com/esri/core/geometry/OperatorInternalRelationUtils.java @@ -0,0 +1,733 @@ +/* + Copyright 1995-2013 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ +package com.esri.core.geometry; + +import com.esri.core.geometry.RasterizedGeometry2D.HitType; + +class OperatorInternalRelationUtils { + + interface Relation { + public final int Unknown = 0; + public final int Contains = 1; + public final int Within = 2; + public final int Equals = 3; // == Within | Contains tests both within + // and contains + public final int Disjoint = 4; + public final int Touches = 8; + public final int Crosses = 16; + public final int Overlaps = 32; + + public final int NoThisRelation = 64; // returned when the relation is + // not satisified + public final int Intersects = 0x40000000;// this means not_disjoint. + // Used for early bailout + public final int IntersectsOrDisjoint = Intersects | Disjoint; + } + + public static int quickTest2D(Geometry geomA, Geometry geomB, + double tolerance, int testType) { + if (geomB.isEmpty() || geomA.isEmpty()) + return (int) Relation.Disjoint; + + int geomAtype = geomA.getType().value(); + int geomBtype = geomB.getType().value(); + + // We do not support segments directly for now. Convert to Polyline + Polyline autoPolyA; + if (Geometry.isSegment(geomAtype)) { + autoPolyA = new Polyline(geomA.getDescription()); + geomA = (Geometry) autoPolyA; + autoPolyA.addSegment((Segment) geomA, true); + } + + Polyline autoPolyB; + if (Geometry.isSegment(geomBtype)) { + autoPolyB = new Polyline(geomB.getDescription()); + geomB = (Geometry) autoPolyB; + autoPolyB.addSegment((Segment) geomB, true); + } + + // Now process GeometryxGeometry case by case + switch (geomAtype) { + case Geometry.GeometryType.Point: { + switch (geomBtype) { + case Geometry.GeometryType.Point: + return quickTest2DPointPoint((Point) geomA, (Point) geomB, + tolerance); + case Geometry.GeometryType.Envelope: + return reverseResult(quickTest2DEnvelopePoint((Envelope) geomB, + (Point) geomA, tolerance)); + case Geometry.GeometryType.MultiPoint: + return reverseResult(quickTest2DMultiPointPoint( + (MultiPoint) geomB, (Point) geomA, tolerance)); + case Geometry.GeometryType.Polyline: + return reverseResult(quickTest2DPolylinePoint((Polyline) geomB, + (Point) geomA, tolerance, testType)); + case Geometry.GeometryType.Polygon: + return reverseResult(quickTest2DPolygonPoint((Polygon) geomB, + (Point) geomA, tolerance)); + } + throw new GeometryException("internal error");// GEOMTHROW(internal_error);//what + // else? + } + case Geometry.GeometryType.Envelope: { + switch (geomBtype) { + case Geometry.GeometryType.Point: + return quickTest2DEnvelopePoint((Envelope) geomA, + (Point) geomB, tolerance); + case Geometry.GeometryType.Envelope: + return quickTest2DEnvelopeEnvelope((Envelope) geomA, + (Envelope) geomB, tolerance); + case Geometry.GeometryType.MultiPoint: + return reverseResult(quickTest2DMultiPointEnvelope( + (MultiPoint) geomB, (Envelope) geomA, tolerance, + testType)); + case Geometry.GeometryType.Polyline: + return reverseResult(quickTest2DPolylineEnvelope( + (Polyline) geomB, (Envelope) geomA, tolerance)); + case Geometry.GeometryType.Polygon: + return reverseResult(quickTest2DPolygonEnvelope( + (Polygon) geomB, (Envelope) geomA, tolerance)); + } + throw new GeometryException("internal error");// GEOMTHROW(internal_error);//what + // else? + } + case Geometry.GeometryType.MultiPoint: { + switch (geomBtype) { + case Geometry.GeometryType.Point: + return quickTest2DMultiPointPoint((MultiPoint) geomA, + (Point) geomB, tolerance); + case Geometry.GeometryType.Envelope: + return quickTest2DMultiPointEnvelope((MultiPoint) geomA, + (Envelope) geomB, tolerance, testType); + case Geometry.GeometryType.MultiPoint: + return quickTest2DMultiPointMultiPoint((MultiPoint) geomA, + (MultiPoint) geomB, tolerance, testType); + case Geometry.GeometryType.Polyline: + return reverseResult(quickTest2DPolylineMultiPoint( + (Polyline) geomB, (MultiPoint) geomA, tolerance)); + case Geometry.GeometryType.Polygon: + return reverseResult(quickTest2DPolygonMultiPoint( + (Polygon) geomB, (MultiPoint) geomA, tolerance)); + } + throw new GeometryException("internal error");// GEOMTHROW(internal_error);//what + // else? + } + case Geometry.GeometryType.Polyline: { + switch (geomBtype) { + case Geometry.GeometryType.Point: + return quickTest2DPolylinePoint((Polyline) geomA, + (Point) geomB, tolerance, testType); + case Geometry.GeometryType.Envelope: + return quickTest2DPolylineEnvelope((Polyline) geomA, + (Envelope) geomB, tolerance); + case Geometry.GeometryType.MultiPoint: + return quickTest2DPolylineMultiPoint((Polyline) geomA, + (MultiPoint) geomB, tolerance); + case Geometry.GeometryType.Polyline: + return quickTest2DPolylinePolyline((Polyline) geomA, + (Polyline) geomB, tolerance); + case Geometry.GeometryType.Polygon: + return reverseResult(quickTest2DPolygonPolyline( + (Polygon) geomB, (Polyline) geomA, tolerance)); + } + throw new GeometryException("internal error");// GEOMTHROW(internal_error);//what + // else? + } + case Geometry.GeometryType.Polygon: { + switch (geomBtype) { + case Geometry.GeometryType.Point: + return quickTest2DPolygonPoint((Polygon) geomA, (Point) geomB, + tolerance); + case Geometry.GeometryType.Envelope: + return quickTest2DPolygonEnvelope((Polygon) geomA, + (Envelope) geomB, tolerance); + case Geometry.GeometryType.MultiPoint: + return quickTest2DPolygonMultiPoint((Polygon) geomA, + (MultiPoint) geomB, tolerance); + case Geometry.GeometryType.Polyline: + return quickTest2DPolygonPolyline((Polygon) geomA, + (Polyline) geomB, tolerance); + case Geometry.GeometryType.Polygon: + return quickTest2DPolygonPolygon((Polygon) geomA, + (Polygon) geomB, tolerance); + } + throw new GeometryException("internal error");// GEOMTHROW(internal_error);//what + // else? + } + + default: + throw new GeometryException("internal error");// GEOMTHROW(internal_error);//what + // else? + // return 0; + } + } + + private static int quickTest2DPointPoint(Point geomA, Point geomB, + double tolerance) { + Point2D ptA = geomA.getXY(); + Point2D ptB = geomB.getXY(); + return quickTest2DPointPoint(ptA, ptB, tolerance); + } + + private static int quickTest2DPointPoint(Point2D ptA, Point2D ptB, + double tolerance) { + ptA.sub(ptB); + double len = ptA.sqrLength();// Should we test against 2*tol or tol? + if (len <= tolerance * tolerance)// Two points are equal if they are not + // Disjoint. We consider a point to + // be a disk of radius tolerance. + // Any intersection of two disks + // produces same disk. + return (int) Relation.Within | (int) Relation.Contains;// ==Equals + + return (int) Relation.Disjoint; + } + + private static int quickTest2DEnvelopePoint(Envelope geomA, Point geomB, + double tolerance) { + Envelope2D geomAEnv = new Envelope2D(); + geomA.queryEnvelope2D(geomAEnv); + Point2D ptB; + ptB = geomB.getXY(); + return quickTest2DEnvelopePoint(geomAEnv, ptB, tolerance); + } + + private static int quickTest2DEnvelopePoint(Envelope2D geomAEnv, + Point2D ptB, double tolerance) { + Envelope2D envAMinus = geomAEnv; + envAMinus.inflate(-tolerance, -tolerance); + if (envAMinus.contains(ptB)) + return (int) Relation.Contains;// clementini's contains + Envelope2D envAPlus = geomAEnv; + envAPlus.inflate(tolerance, tolerance); + if (envAPlus.contains(ptB)) + return (int) Relation.Touches;// clementini's touches + + return (int) Relation.Disjoint;// clementini's disjoint + } + + private static int quickTest2DEnvelopePoint(Envelope2D envAPlus, + Envelope2D envAMinus, Point2D ptB, double tolerance) { + if (envAMinus.contains(ptB)) + return (int) Relation.Contains;// clementini's contains + if (envAPlus.contains(ptB)) + return (int) Relation.Touches;// clementini's touches + + return (int) Relation.Disjoint;// clementini's disjoint + } + + private static int quickTest2DEnvelopeEnvelope(Envelope geomA, + Envelope geomB, double tolerance) { + Envelope2D geomAEnv = new Envelope2D(); + geomA.queryEnvelope2D(geomAEnv); + Envelope2D geomBEnv = new Envelope2D(); + geomB.queryEnvelope2D(geomBEnv); + return quickTest2DEnvelopeEnvelope(geomAEnv, geomBEnv, tolerance); + } + + private static int quickTest2DEnvelopeEnvelope(Envelope2D geomAEnv, + Envelope2D geomBEnv, double tolerance) { + // firstly check for contains and within to give a chance degenerate + // envelopes to work. + // otherwise, if there are two degenerate envelopes that are equal, + // Touch relation may occur. + int res = 0; + if (geomAEnv.contains(geomBEnv)) + res |= (int) Relation.Contains; + + if (geomBEnv.contains(geomAEnv)) + res |= (int) Relation.Within; + + if (res != 0) + return res; + + Envelope2D envAMinus = geomAEnv; + envAMinus.inflate(-tolerance, -tolerance);// Envelope A interior + Envelope2D envBMinus = geomBEnv; + envBMinus.inflate(-tolerance, -tolerance);// Envelope B interior + if (envAMinus.isIntersecting(envBMinus)) { + Envelope2D envAPlus = geomAEnv; + envAPlus.inflate(tolerance, tolerance);// Envelope A interior plus + // boundary + res = envAPlus.contains(geomBEnv) ? (int) Relation.Contains : 0; + Envelope2D envBPlus = geomBEnv; + envBPlus.inflate(tolerance, tolerance);// Envelope A interior plus + // boundary + res |= envBPlus.contains(geomAEnv) ? (int) Relation.Within : 0; + if (res != 0) + return res; + + return (int) Relation.Overlaps; // Clementini's Overlap + } else { + Envelope2D envAPlus = geomAEnv; + envAPlus.inflate(tolerance, tolerance);// Envelope A interior plus + // boundary + Envelope2D envBPlus = geomBEnv; + envBPlus.inflate(tolerance, tolerance);// Envelope A interior plus + // boundary + if (envAPlus.isIntersecting(envBPlus)) { + return (int) Relation.Touches; // Clementini Touch + } else { + return (int) Relation.Disjoint; // Clementini Disjoint + } + } + } + + private static int quickTest2DMultiPointPoint(MultiPoint geomA, + Point geomB, double tolerance) { + Point2D ptB; + ptB = geomB.getXY(); + return quickTest2DMultiPointPoint(geomA, ptB, tolerance); + } + + private static int quickTest2DMultiPointPoint(MultiPoint geomA, + Point2D ptB, double tolerance) { + // TODO: Add Geometry accelerator. (RasterizedGeometry + kd-tree or + // alike) + for (int i = 0, n = geomA.getPointCount(); i < n; i++) { + Point2D ptA; + ptA = geomA.getXY(i); + int res = quickTest2DPointPoint(ptA, ptB, tolerance); + if (res != (int) Relation.Disjoint) { + if ((res & (int) Relation.Within) != 0 && n != 1) { + // _ASSERT(res & (int)Relation.Contains); + return (int) Relation.Contains; + } + + return res; + } + } + + return (int) Relation.Disjoint; + } + + private static int quickTest2DMultiPointEnvelope(MultiPoint geomA, + Envelope geomB, double tolerance, int testType) { + Envelope2D geomBEnv = new Envelope2D(); + geomB.queryEnvelope2D(geomBEnv); + return quickTest2DMultiPointEnvelope(geomA, geomBEnv, tolerance, + testType); + } + + private static int quickTest2DMultiPointEnvelope(MultiPoint geomA, + Envelope2D geomBEnv, double tolerance, int testType) { + // Add early bailout for disjoint test. + Envelope2D envBMinus = geomBEnv; + envBMinus.inflate(-tolerance, -tolerance); + Envelope2D envBPlus = geomBEnv; + envBPlus.inflate(tolerance, tolerance); + int dres = 0; + for (int i = 0, n = geomA.getPointCount(); i < n; i++) { + Point2D ptA; + ptA = geomA.getXY(i); + int res = reverseResult(quickTest2DEnvelopePoint(envBPlus, + envBMinus, ptA, tolerance)); + if (res != (int) Relation.Disjoint) { + dres |= res; + if (testType == (int) Relation.Disjoint) + return (int) Relation.Intersects; + } + } + + if (dres == 0) + return (int) Relation.Disjoint; + + if (dres == (int) Relation.Within) + return dres; + + return (int) Relation.Overlaps; + } + + private static int quickTest2DMultiPointMultiPoint(MultiPoint geomA, + MultiPoint geomB, double tolerance, int testType) { + int counter = 0; + for (int ib = 0, nb = geomB.getPointCount(); ib < nb; ib++) { + Point2D ptB; + ptB = geomB.getXY(ib); + int res = quickTest2DMultiPointPoint(geomA, ptB, tolerance); + if (res != (int) Relation.Disjoint) { + counter++; + if (testType == (int) Relation.Disjoint) + return (int) Relation.Intersects; + } + } + + if (counter > 0) { + if (counter == geomB.getPointCount())// every point from B is within + // A. Means the A contains B + { + if (testType == (int) Relation.Equals) {// This is slow. + // Refactor. + int res = quickTest2DMultiPointMultiPoint(geomB, geomA, + tolerance, (int) Relation.Contains); + return res == (int) Relation.Contains ? (int) Relation.Equals + : (int) Relation.Unknown; + } + return (int) Relation.Contains; + } else { + return (int) Relation.Overlaps; + } + } + + return 0; + } + + private static int quickTest2DPolylinePoint(Polyline geomA, Point geomB, + double tolerance, int testType) { + Point2D ptB; + ptB = geomB.getXY(); + return quickTest2DPolylinePoint(geomA, ptB, tolerance, testType); + } + + private static int quickTest2DMVPointRasterOnly(MultiVertexGeometry geomA, + Point2D ptB, double tolerance) { + // Use rasterized Geometry: + RasterizedGeometry2D rgeomA = null; + MultiVertexGeometryImpl mpImpl = (MultiVertexGeometryImpl) geomA + ._getImpl(); + GeometryAccelerators gaccel = mpImpl._getAccelerators(); + if (gaccel != null) { + rgeomA = gaccel.getRasterizedGeometry(); + } + + if (rgeomA != null) { + RasterizedGeometry2D.HitType hitres = rgeomA.queryPointInGeometry( + ptB.x, ptB.y); + if (hitres == RasterizedGeometry2D.HitType.Outside) + return (int) Relation.Disjoint; + + if (hitres == RasterizedGeometry2D.HitType.Inside) + return (int) Relation.Contains; + } else + return -1; + + return 0; + } + + private static int quickTest2DPolylinePoint(Polyline geomA, Point2D ptB, + double tolerance, int testType) { + int mask = Relation.Touches | Relation.Contains | Relation.Within + | Relation.Disjoint | Relation.Intersects; + + if ((testType & mask) == 0) + return Relation.NoThisRelation; + + int res = quickTest2DMVPointRasterOnly(geomA, ptB, tolerance); + if (res > 0) + return res; + + // Go through the segments: + double toleranceSqr = tolerance * tolerance; + MultiPathImpl mpImpl = (MultiPathImpl) geomA._getImpl(); + SegmentIteratorImpl iter = mpImpl.querySegmentIterator(); + while (iter.nextPath()) { + int pathIndex = iter.getPathIndex(); + if (!geomA.isClosedPath(pathIndex)) { + int pathSize = geomA.getPathSize(pathIndex); + int pathStart = geomA.getPathStart(pathIndex); + if (pathSize == 0) + continue; + + if (Point2D.sqrDistance(geomA.getXY(pathStart), ptB) <= toleranceSqr + || (pathSize > 1 && Point2D.sqrDistance( + geomA.getXY(pathStart + pathSize - 1), ptB) <= toleranceSqr)) { + return (int) Relation.Touches; + } + } + + if (testType != Relation.Touches) { + while (iter.hasNextSegment()) { + Segment segment = iter.nextSegment(); + double t = segment.getClosestCoordinate(ptB, false); + Point2D pt = segment.getCoord2D(t); + if (Point2D.sqrDistance(pt, ptB) <= toleranceSqr) { + if ((testType & Relation.IntersectsOrDisjoint) != 0) { + return Relation.Intersects; + } + + return (int) Relation.Contains; + } + } + } + } + + return (testType & Relation.IntersectsOrDisjoint) != 0 ? Relation.Disjoint + : Relation.NoThisRelation; + } + + private static int quickTest2DPolylineEnvelope(Polyline geomA, + Envelope geomB, double tolerance) { + Envelope2D geomBEnv = new Envelope2D(); + geomB.queryEnvelope2D(geomBEnv); + return quickTest2DPolylineEnvelope(geomA, geomBEnv, tolerance); + } + + private static int quickTest2DPolylineEnvelope(Polyline geomA, + Envelope2D geomBEnv, double tolerance) { + int res = quickTest2DMVEnvelopeRasterOnly(geomA, geomBEnv, tolerance); + if (res > 0) + return res; + + // TODO: implement me + return 0; + } + + private static int quickTest2DMVEnvelopeRasterOnly( + MultiVertexGeometry geomA, Envelope2D geomBEnv, double tolerance) { + // Use rasterized Geometry only: + RasterizedGeometry2D rgeomA; + MultiVertexGeometryImpl mpImpl = (MultiVertexGeometryImpl) geomA + ._getImpl(); + GeometryAccelerators gaccel = mpImpl._getAccelerators(); + if (gaccel != null) { + rgeomA = gaccel.getRasterizedGeometry(); + } else + return -1; + + if (rgeomA != null) { + HitType hitres = rgeomA.queryEnvelopeInGeometry(geomBEnv); + if (hitres == RasterizedGeometry2D.HitType.Outside) + return (int) Relation.Disjoint; + + if (hitres == RasterizedGeometry2D.HitType.Inside) + return (int) Relation.Contains; + } else + return -1; + + return 0; + } + + private static int quickTest2DPolylineMultiPoint(Polyline geomA, + MultiPoint geomB, double tolerance) { + Envelope2D geomBEnv = new Envelope2D(); + geomB.queryEnvelope2D(geomBEnv); + int res = quickTest2DMVEnvelopeRasterOnly(geomA, geomBEnv, tolerance); + if (res > 0) + return res; + + // TODO: implement me + return 0; + } + + private static int quickTest2DMVMVRasterOnly(MultiVertexGeometry geomA, + MultiVertexGeometry geomB, double tolerance) { + Envelope2D geomBEnv = new Envelope2D(); + geomB.queryEnvelope2D(geomBEnv); + int res = quickTest2DMVEnvelopeRasterOnly(geomA, geomBEnv, tolerance); + if (res > 0) + return res; + + if (res == -1) { + Envelope2D geomAEnv = new Envelope2D(); + geomA.queryEnvelope2D(geomAEnv); + res = quickTest2DMVEnvelopeRasterOnly(geomB, geomAEnv, tolerance); + if (res > 0) + return reverseResult(res); + } + + // TODO: implement me + return 0; + } + + private static int quickTest2DPolylinePolyline(Polyline geomA, + Polyline geomB, double tolerance) { + int res = quickTest2DMVMVRasterOnly(geomA, geomB, tolerance); + if (res > 0) + return res; + + // TODO: implement me + return 0; + } + + private static int quickTest2DPolygonPoint(Polygon geomA, Point geomB, + double tolerance) { + Point2D ptB; + ptB = geomB.getXY(); + return quickTest2DPolygonPoint(geomA, ptB, tolerance); + } + + private static int quickTest2DPolygonPoint(Polygon geomA, Point2D ptB, + double tolerance) { + PolygonUtils.PiPResult pipres = PolygonUtils.isPointInPolygon2D(geomA, + ptB, tolerance);// this method uses the accelerator if available + if (pipres == PolygonUtils.PiPResult.PiPOutside) + return (int) Relation.Disjoint;// clementini's disjoint + + if (pipres == PolygonUtils.PiPResult.PiPInside) + return (int) Relation.Contains;// clementini's contains + + if (pipres == PolygonUtils.PiPResult.PiPBoundary) + return (int) Relation.Touches;// clementini's touches + + throw new GeometryException("internal error");// GEOMTHROW(internal_error); + // //what else + // return 0; + } + + private static int quickTest2DPolygonEnvelope(Polygon geomA, + Envelope geomB, double tolerance) { + Envelope2D geomBEnv = new Envelope2D(); + geomB.queryEnvelope2D(geomBEnv); + return quickTest2DPolygonEnvelope(geomA, geomBEnv, tolerance); + } + + private static int quickTest2DPolygonEnvelope(Polygon geomA, + Envelope2D geomBEnv, double tolerance) { + int res = quickTest2DMVEnvelopeRasterOnly(geomA, geomBEnv, tolerance); + if (res > 0) + return res; + + // TODO: implement me + return 0; + } + + private static int quickTest2DPolygonMultiPoint(Polygon geomA, + MultiPoint geomB, double tolerance) { + int res = quickTest2DMVMVRasterOnly(geomA, geomB, tolerance); + if (res > 0) + return res; + + // TODO: implement me + return 0; + } + + private static int quickTest2DPolygonPolyline(Polygon geomA, + Polyline geomB, double tolerance) { + int res = quickTest2DMVMVRasterOnly(geomA, geomB, tolerance); + if (res > 0) + return res; + + // TODO: implement me + return 0; + } + + private static int quickTest2DPolygonPolygon(Polygon geomA, Polygon geomB, + double tolerance) { + int res = quickTest2DMVMVRasterOnly(geomA, geomB, tolerance); + if (res > 0) + return res; + + // TODO: implement me + return 0; + } + + public static int quickTest2D_Accelerated_DisjointOrContains( + Geometry geomA, Geometry geomB, double tolerance) { + int gtA = geomA.getType().value(); + int gtB = geomB.getType().value(); + GeometryAccelerators accel; + boolean endWhileStatement = false; + do { + if (Geometry.isMultiVertex(gtA)) { + MultiVertexGeometryImpl impl = (MultiVertexGeometryImpl) geomA + ._getImpl(); + accel = impl._getAccelerators(); + if (accel != null) { + RasterizedGeometry2D rgeom = accel.getRasterizedGeometry(); + if (rgeom != null) { + if (gtB == Geometry.GeometryType.Point) { + Point2D ptB = ((Point) geomB).getXY(); + HitType hit = rgeom.queryPointInGeometry(ptB.x, + ptB.y); + if (hit == RasterizedGeometry2D.HitType.Inside) { + return (int) Relation.Contains; + } else if (hit == RasterizedGeometry2D.HitType.Outside) { + return (int) Relation.Disjoint; + } + + break; + } + Envelope2D envB = new Envelope2D(); + geomB.queryEnvelope2D(envB); + RasterizedGeometry2D.HitType hit = rgeom + .queryEnvelopeInGeometry(envB); + if (hit == RasterizedGeometry2D.HitType.Inside) { + return (int) Relation.Contains; + } else if (hit == RasterizedGeometry2D.HitType.Outside) { + return (int) Relation.Disjoint; + } + + break; + } + } + } + } while (endWhileStatement); + + accel = null; + do { + if (Geometry.isMultiVertex(gtB)) { + MultiVertexGeometryImpl impl = (MultiVertexGeometryImpl) geomB + ._getImpl(); + accel = impl._getAccelerators(); + if (accel != null) { + RasterizedGeometry2D rgeom = accel.getRasterizedGeometry(); + if (rgeom != null) { + if (gtA == Geometry.GeometryType.Point) { + Point2D ptA = ((Point) geomA).getXY(); + RasterizedGeometry2D.HitType hit = rgeom + .queryPointInGeometry(ptA.x, ptA.y); + if (hit == RasterizedGeometry2D.HitType.Inside) { + return (int) Relation.Within; + } else if (hit == RasterizedGeometry2D.HitType.Outside) { + return (int) Relation.Disjoint; + } + + break; + } + + Envelope2D envA = new Envelope2D(); + geomA.queryEnvelope2D(envA); + RasterizedGeometry2D.HitType hit = rgeom + .queryEnvelopeInGeometry(envA); + if (hit == RasterizedGeometry2D.HitType.Inside) { + return (int) Relation.Within; + } else if (hit == RasterizedGeometry2D.HitType.Outside) { + return (int) Relation.Disjoint; + } + + break; + } + } + } + } while (endWhileStatement); + + return 0; + } + + private static int reverseResult(int resIn) { + int res = resIn; + if ((res & (int) Relation.Contains) != 0) { + res &= ~(int) Relation.Contains; + res |= (int) Relation.Within; + } + if ((res & (int) Relation.Within) != 0) { + res &= ~(int) Relation.Within; + res |= (int) Relation.Contains; + } + + return res; + } + +} diff --git a/src/com/esri/core/geometry/OperatorIntersection.java b/src/com/esri/core/geometry/OperatorIntersection.java new file mode 100644 index 00000000..f69c86ec --- /dev/null +++ b/src/com/esri/core/geometry/OperatorIntersection.java @@ -0,0 +1,98 @@ +/* + Copyright 1995-2013 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ + +package com.esri.core.geometry; + +import com.esri.core.geometry.Operator.Type; + +/** + *Intersection of geometries by a given geometry. + */ +public abstract class OperatorIntersection extends Operator { + @Override + public Type getType() { + return Type.Intersection; + } + + /** + *Performs the Topological Intersection operation on the geometry set. + *@param inputGeometries is the set of Geometry instances to be intersected by the intersector. + *@param intersector is the intersector Geometry. + * + *The operator intersects every geometry in the inputGeometries with the first geometry of the intersector and returns the result. + */ + public abstract GeometryCursor execute(GeometryCursor inputGeometries, + GeometryCursor intersector, SpatialReference sr, + ProgressTracker progressTracker); + + /** + *Performs the Topological intersection operation on the geometry set. + *@param input_geometries is the set of Geometry instances to be intersected by the intersector. + *@param intersector is the intersector Geometry. Only single intersector is used, therefore, the intersector.next() is called only once. + *@param sr The spatial reference is used to get tolerance value. Can be null, then the tolerance is not used and the operation is performed with + *a small tolerance value just enough to make the operation robust. + *@param progress_tracker Allows to cancel the operation. Can be null. + *@param dimensionMask The dimension of the intersection. The value is either -1, or a bitmask mask of values (1 << dim). + *The value of -1 means the lower dimension in the intersecting pair. + *This is a fastest option when intersecting polygons with polygons or polylines. + *The bitmask of values (1 << dim), where dim is the desired dimension value, is used to indicate + *what dimensions of geometry one wants to be returned. For example, to return + *multipoints and lines only, pass (1 << 0) | (1 << 1), which is equivalen to 1 | 2, or 3. + *\return Returns the cursor of the intersection result. The cursors' get_geometry_ID method returns the current ID of the input geometry + *being processed. Wh dimensionMask is a bitmask, there will be n result geometries per one input geometry returned, where n is the number + *of bits set in the bitmask. For example, if the dimensionMask is 5, there will be two geometries per one input geometry. + * + *The operator intersects every geometry in the input_geometries with the first geometry of the intersector and returns the result. + * + *Note, when the dimensionMask is -1, then for each intersected pair of geometries, + *the result has the lower of dimentions of the two geometries. That is, the dimension of the Polyline/Polyline intersection + *is always 1 (that is, for polylines it never returns crossing points, but the overlaps only). + *If dimensionMask is 7, the operation will return any possible + */ + public abstract GeometryCursor execute(GeometryCursor input_geometries, + GeometryCursor intersector, SpatialReference sr, + ProgressTracker progress_tracker, int dimensionMask); + + /** + *Performs the Topological Intersection operation on the geometry. + *The result has the lower of dimentions of the two geometries. That is, the dimension of the + *Polyline/Polyline intersection is always 1 (that is, for polylines it never returns crossing + *points, but the overlaps only). + *The call is equivalent to calling the overloaded method using cursors: + *execute(new SimpleGeometryCursor(input_geometry), new SimpleGeometryCursor(intersector), sr, progress_tracker, mask).next(); + *where mask can be either -1 or min(1 << input_geometry.getDimension(), 1 << intersector.getDimension()); + *@param inputGeometry is the Geometry instance to be intersected by the intersector. + *@param intersector is the intersector Geometry. + *@return Returns the intersected Geometry. + */ + public abstract Geometry execute(Geometry inputGeometry, + Geometry intersector, SpatialReference sr, + ProgressTracker progressTracker); + + public static OperatorIntersection local() { + return (OperatorIntersection) OperatorFactoryLocal.getInstance() + .getOperator(Type.Intersection); + } + +} diff --git a/src/com/esri/core/geometry/OperatorIntersectionCursor.java b/src/com/esri/core/geometry/OperatorIntersectionCursor.java new file mode 100644 index 00000000..4f835010 --- /dev/null +++ b/src/com/esri/core/geometry/OperatorIntersectionCursor.java @@ -0,0 +1,806 @@ +/* + Copyright 1995-2013 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ + +package com.esri.core.geometry; + +import java.util.ArrayList; + +class OperatorIntersectionCursor extends GeometryCursor { + + GeometryCursor m_inputGeoms; + GeometryCursor m_smallCursor; + ProgressTracker m_progress_tracker; + SpatialReference m_spatial_reference; + Geometry m_geomIntersector; + Geometry m_geomIntersectorEmptyGeom;// holds empty geometry of intersector + // type. + int m_geomIntersectorType; + int m_currentGeomType; + int m_index; + int m_dimensionMask; + boolean m_bEmpty; + + OperatorIntersectionCursor(GeometryCursor inputGeoms, + GeometryCursor geomIntersector, SpatialReference sr, + ProgressTracker progress_tracker, int dimensionMask) { + m_bEmpty = geomIntersector == null; + m_index = -1; + m_inputGeoms = inputGeoms; + m_spatial_reference = sr; + m_geomIntersector = geomIntersector.next(); + m_geomIntersectorType = m_geomIntersector.getType().value(); + m_currentGeomType = Geometry.Type.Unknown.value(); + m_progress_tracker = progress_tracker; + m_dimensionMask = dimensionMask; + if (m_dimensionMask != -1 + && (m_dimensionMask <= 0 || m_dimensionMask > 7)) + throw new IllegalArgumentException("bad dimension mask");// dimension + // mask + // can + // be + // -1, + // for + // the + // default + // behavior, + // or a + // value + // between + // 1 and + // 7. + } + + @Override + public Geometry next() { + if (m_bEmpty) + return null; + + Geometry geom; + if (m_smallCursor != null) {// when dimension mask is used, we produce a + geom = m_smallCursor.next(); + if (geom != null) + return geom; + else + m_smallCursor = null;// done with the small cursor + } + + while ((geom = m_inputGeoms.next()) != null) { + m_index = m_inputGeoms.getGeometryID(); + if (m_dimensionMask == -1) { + Geometry resGeom = intersect(geom); + assert (resGeom != null); + return resGeom; + } else { + m_smallCursor = intersectEx(geom); + Geometry resGeom = m_smallCursor.next(); + assert (resGeom != null); + return resGeom; + } + } + return null; + } + + @Override + public int getGeometryID() { + return m_index; + } + + Geometry intersect(Geometry input_geom) { + Geometry dst_geom = tryNativeImplementation_(input_geom); + if (dst_geom != null) + return dst_geom; + + Envelope2D commonExtent = InternalUtils.getMergedExtent( + m_geomIntersector, input_geom); + + // return Topological_operations::intersection(input_geom, + // m_geomIntersector, m_spatial_reference, m_progress_tracker); + // Preprocess geometries to be clipped to the extent of intersection to + // get rid of extra segments. + double tol = 0; + Envelope2D env = new Envelope2D(); + m_geomIntersector.queryEnvelope2D(env); + Envelope2D env1 = new Envelope2D(); + input_geom.queryEnvelope2D(env1); + env.intersect(env1); + assert (!env.isEmpty()); + double t = InternalUtils.calculateToleranceFromGeometry( + m_spatial_reference, commonExtent, true) * 10; + env.inflate(10 * t, 10 * t); + Geometry clippedIntersector = Clipper.clip(m_geomIntersector, env, tol, + 0.0); + Geometry clippedInputGeom = Clipper.clip(input_geom, env, tol, 0.0); + // perform the clip + return TopologicalOperations.intersection(clippedInputGeom, + clippedIntersector, m_spatial_reference, m_progress_tracker); + } + + // Parses the input vector to ensure the out result contains only geometries + // as indicated with the dimensionMask + GeometryCursor prepareVector_(VertexDescription descr, int dimensionMask, + Geometry[] res_vec) { + int inext = 0; + if ((dimensionMask & 1) != 0) { + if (res_vec[0] == null) + res_vec[0] = new Point(descr); + inext++; + } else { + for (int i = 0; i < res_vec.length - 1; i++) + res_vec[i] = res_vec[i + 1]; + } + + if ((dimensionMask & 2) != 0) { + if (res_vec[inext] == null) + res_vec[inext] = new Polyline(descr); + inext++; + } else { + for (int i = inext; i < res_vec.length - 1; i++) + res_vec[i] = res_vec[i + 1]; + } + + if ((dimensionMask & 4) != 0) { + if (res_vec[inext] == null) + res_vec[inext] = new Polygon(descr); + } else { + for (int i = inext; i < res_vec.length - 1; i++) + res_vec[i] = res_vec[i + 1]; + } + + return new SimpleGeometryCursor(res_vec); + } + + GeometryCursor intersectEx(Geometry input_geom) { + assert (m_dimensionMask != -1); + Geometry dst_geom = tryNativeImplementation_(input_geom); + if (dst_geom != null) { + Geometry[] res_vec = new Geometry[3]; + res_vec[1 << dst_geom.getDimension()] = dst_geom; + return prepareVector_(input_geom.getDescription(), m_dimensionMask, + res_vec); + } + + Envelope2D commonExtent = InternalUtils.getMergedExtent( + m_geomIntersector, input_geom); + // Preprocess geometries to be clipped to the extent of intersection to + // get rid of extra segments. + double tol = 0; + Envelope2D env = new Envelope2D(); + m_geomIntersector.queryEnvelope2D(env); + Envelope2D env1 = new Envelope2D(); + input_geom.queryEnvelope2D(env1); + env.intersect(env1); + assert (!env.isEmpty()); + double t = InternalUtils.calculateToleranceFromGeometry( + m_spatial_reference, commonExtent, true) * 10; + env.inflate(10 * t, 10 * t); + Geometry clippedIntersector = Clipper.clip(m_geomIntersector, env, tol, + 0.0); + Geometry clippedInputGeom = Clipper.clip(input_geom, env, tol, 0.0); + // perform the clip + Geometry[] res_vec; + res_vec = TopologicalOperations.intersectionEx(clippedInputGeom, + clippedIntersector, m_spatial_reference, m_progress_tracker); + return prepareVector_(input_geom.getDescription(), m_dimensionMask, + res_vec); + } + + Geometry tryNativeImplementation_(Geometry input_geom) { + // A note on attributes: + // 1. The geometry with lower dimension wins in regard to the + // attributes. + // 2. If the dimensions are the same, the input_geometry attributes win. + // 3. The exception to the 2. is when the input is an Envelope, and the + // intersector is a polygon, then the intersector wins. + + // A note on the tolerance: + // This operator performs a simple intersection operation. Should it use + // the tolerance? + // Example: Point is intersected by the envelope. + // If it is slightly outside of the envelope, should we still return it + // if it is closer than the tolerance? + // Should we do crack and cluster and snap the point coordinates to the + // envelope boundary? + // + // Consider floating point arithmetics approach. When you compare + // doubles, you should use an epsilon (equals means ::fabs(a - b) < + // eps), however when you add/subtract, etc them, you do not use + // epsilon. + // Shouldn't we do same here? Relational operators use tolerance, but + // the action operators don't. + + Envelope2D mergedExtent = InternalUtils.getMergedExtent(input_geom, + m_geomIntersector); + double tolerance = InternalUtils.calculateToleranceFromGeometry( + m_spatial_reference, mergedExtent, false); + + int gtInput = input_geom.getType().value(); + boolean bInputEmpty = input_geom.isEmpty(); + boolean bGeomIntersectorEmpty = m_geomIntersector.isEmpty(); + boolean bResultIsEmpty = bInputEmpty || bGeomIntersectorEmpty; + if (!bResultIsEmpty) {// test envelopes + Envelope2D env2D1 = new Envelope2D(); + input_geom.queryEnvelope2D(env2D1); + Envelope2D env2D2 = new Envelope2D(); + m_geomIntersector.queryEnvelope2D(env2D2); + bResultIsEmpty = !env2D1.isIntersecting(env2D2); + } + + if (!bResultIsEmpty) {// try accelerated test + int res = OperatorInternalRelationUtils + .quickTest2D_Accelerated_DisjointOrContains( + m_geomIntersector, input_geom, tolerance); + if (res == OperatorInternalRelationUtils.Relation.Disjoint) {// disjoint + bResultIsEmpty = true; + } else if ((res & OperatorInternalRelationUtils.Relation.Within) != 0) {// intersector + // is + // within + // the + // input_geom + // TODO: + // assign + // input_geom + // attributes + // first + return m_geomIntersector; + } else if ((res & OperatorInternalRelationUtils.Relation.Contains) != 0) {// intersector + // contains + // input_geom + return input_geom; + } + } + + if (bResultIsEmpty) {// When one geometry or both are empty, we need to + // return an empty geometry. + // Here we do that end also ensure the type is + // correct. + // That is the lower dimension need to be + // returned. Also, for Point vs Multi_point, an + // empty Point need to be returned. + int dim1 = Geometry.getDimensionFromType(gtInput); + int dim2 = Geometry.getDimensionFromType(m_geomIntersectorType); + if (dim1 < dim2) + return returnEmpty_(input_geom, bInputEmpty); + else if (dim1 > dim2) + return returnEmptyIntersector_(); + else if (dim1 == 0) { + if (gtInput == Geometry.GeometryType.MultiPoint + && m_geomIntersectorType == Geometry.GeometryType.Point) {// point + // vs + // Multi_point + // need + // special + // treatment + // to + // ensure + // Point + // is + // returned + // always. + return returnEmptyIntersector_(); + } else + // Both input and intersector have same gtype, or input is + // Point. + return returnEmpty_(input_geom, bInputEmpty); + } else + return returnEmpty_(input_geom, bInputEmpty); + } + + // Note: No empty geometries after this point! + + // Warning: Do not try clip for polylines and polygons. + + // Try clip of Envelope with Envelope. + if ((m_dimensionMask == -1 || m_dimensionMask == (1 << 2)) + && gtInput == Geometry.GeometryType.Envelope + && m_geomIntersectorType == Geometry.GeometryType.Envelope) { + Envelope env1 = (Envelope) input_geom; + Envelope env2 = (Envelope) m_geomIntersector; + Envelope2D env2D_1 = new Envelope2D(); + env1.queryEnvelope2D(env2D_1); + Envelope2D env2D_2 = new Envelope2D(); + env2.queryEnvelope2D(env2D_2); + env2D_1.intersect(env2D_2); + Envelope result_env = new Envelope(); + env1.copyTo(result_env); + result_env.setEnvelope2D(env2D_1); + return result_env; + } + + // Use clip for Point and Multi_point with Envelope + if ((gtInput == Geometry.GeometryType.Envelope && Geometry + .getDimensionFromType(m_geomIntersectorType) == 0) + || (m_geomIntersectorType == Geometry.GeometryType.Envelope && Geometry + .getDimensionFromType(gtInput) == 0)) { + Envelope env = gtInput == Geometry.GeometryType.Envelope ? (Envelope) input_geom + : (Envelope) m_geomIntersector; + Geometry other = gtInput == Geometry.GeometryType.Envelope ? m_geomIntersector + : input_geom; + Envelope2D env_2D = new Envelope2D(); + env.queryEnvelope2D(env_2D); + return Clipper.clip(other, env_2D, tolerance, 0); + } + + if ((Geometry.getDimensionFromType(gtInput) == 0 && Geometry + .getDimensionFromType(m_geomIntersectorType) > 0) + || (Geometry.getDimensionFromType(gtInput) > 0 && Geometry + .getDimensionFromType(m_geomIntersectorType) == 0)) {// multipoint + // intersection + double tolerance1 = InternalUtils.calculateToleranceFromGeometry( + m_spatial_reference, input_geom, false); + if (gtInput == Geometry.GeometryType.MultiPoint) + return TopologicalOperations.intersection( + (MultiPoint) input_geom, m_geomIntersector, tolerance1); + if (gtInput == Geometry.GeometryType.Point) + return TopologicalOperations.intersection((Point) input_geom, + m_geomIntersector, tolerance1); + if (m_geomIntersectorType == Geometry.GeometryType.MultiPoint) + return TopologicalOperations.intersection( + (MultiPoint) m_geomIntersector, input_geom, tolerance1); + if (m_geomIntersectorType == Geometry.GeometryType.Point) + return TopologicalOperations.intersection( + (Point) m_geomIntersector, input_geom, tolerance1); + throw new GeometryException("internal error"); + } + + // Try Polyline vs Polygon + if ((m_dimensionMask == -1 || m_dimensionMask == (1 << 1)) + && (gtInput == Geometry.GeometryType.Polyline) + && (m_geomIntersectorType == Geometry.GeometryType.Polygon)) { + return tryFastIntersectPolylinePolygon_((Polyline) (input_geom), + (Polygon) (m_geomIntersector)); + } + + // Try Polygon vs Polyline + if ((m_dimensionMask == -1 || m_dimensionMask == (1 << 1)) + && (gtInput == Geometry.GeometryType.Polygon) + && (m_geomIntersectorType == Geometry.GeometryType.Polyline)) { + return tryFastIntersectPolylinePolygon_( + (Polyline) (m_geomIntersector), (Polygon) (input_geom)); + } + + return null; + } + + Geometry tryFastIntersectPolylinePolygon_(Polyline polyline, Polygon polygon) { + MultiPathImpl polylineImpl = (MultiPathImpl) polyline._getImpl(); + MultiPathImpl polygonImpl = (MultiPathImpl) polygon._getImpl(); + + double tolerance = InternalUtils.calculateToleranceFromGeometry( + m_spatial_reference, polygon, false); + Envelope2D clipEnvelope = new Envelope2D(); + { + polygonImpl.queryEnvelope2D(clipEnvelope); + Envelope2D env1 = new Envelope2D(); + polylineImpl.queryEnvelope2D(env1); + clipEnvelope.intersect(env1); + assert (!clipEnvelope.isEmpty()); + } + + clipEnvelope.inflate(10 * tolerance, 10 * tolerance); + + if (true) { + double tol = 0; + Geometry clippedPolyline = Clipper.clip(polyline, clipEnvelope, + tol, 0.0); + polyline = (Polyline) clippedPolyline; + polylineImpl = (MultiPathImpl) polyline._getImpl(); + } + + AttributeStreamOfInt32 clipResult = new AttributeStreamOfInt32(0); + int unresolvedSegments = -1; + GeometryAccelerators accel = polygonImpl._getAccelerators(); + if (accel != null) { + RasterizedGeometry2D rgeom = accel.getRasterizedGeometry(); + if (rgeom != null) { + unresolvedSegments = 0; + clipResult.reserve(polylineImpl.getPointCount() + + polylineImpl.getPathCount()); + Envelope2D seg_env = new Envelope2D(); + SegmentIteratorImpl iter = polylineImpl.querySegmentIterator(); + while (iter.nextPath()) { + while (iter.hasNextSegment()) { + Segment seg = iter.nextSegment(); + seg.queryEnvelope2D(seg_env); + RasterizedGeometry2D.HitType hit = rgeom + .queryEnvelopeInGeometry(seg_env); + if (hit == RasterizedGeometry2D.HitType.Inside) { + clipResult.add(1); + } else if (hit == RasterizedGeometry2D.HitType.Outside) { + clipResult.add(0); + } else { + clipResult.add(-1); + unresolvedSegments++; + } + } + } + } + } + + if (polygon.getPointCount() > 5) { + double tol = 0; + Geometry clippedPolygon = Clipper.clip(polygon, clipEnvelope, tol, + 0.0); + + polygon = (Polygon) clippedPolygon; + polygonImpl = (MultiPathImpl) polygon._getImpl(); + } + + if (unresolvedSegments < 0) { + unresolvedSegments = polylineImpl.getSegmentCount(); + } + + // Some heuristics to decide if it makes sense to go with fast intersect + // vs going with the regular planesweep. + double totalPoints = (double) (polylineImpl.getPointCount() + polygonImpl + .getPointCount()); + double thisAlgorithmComplexity = ((double) unresolvedSegments * polygonImpl + .getPointCount());// assume the worst case. + double planesweepComplexity = Math.log(totalPoints) * totalPoints; + double empiricConstantFactorPlaneSweep = 4; + if (thisAlgorithmComplexity > planesweepComplexity + * empiricConstantFactorPlaneSweep) { + // Based on the number of input points, we deduced that the + // plansweep performance should be better than the brute force + // performance. + return null; // resort to planesweep if quadtree does not help + } + + QuadTreeImpl polygonQuadTree = null; + SegmentIteratorImpl polygonIter = polygonImpl.querySegmentIterator(); + // Some logic to decide if it makes sense to build a quadtree on the + // polygon segments + if (accel != null && accel.getQuadTree() != null) + polygonQuadTree = accel.getQuadTree(); + + if (polygonQuadTree == null && polygonImpl.getPointCount() > 20) { + Envelope2D env = new Envelope2D(); + polygon.queryEnvelope2D(env); + QuadTreeImpl polygonQuadTreeNew = new QuadTreeImpl(env, 8); + while (polygonIter.nextPath()) { + while (polygonIter.hasNextSegment()) { + Segment seg = polygonIter.nextSegment(); + seg.queryEnvelope2D(env); + polygonQuadTreeNew.insert(polygonIter.getStartPointIndex(), + env); + } + } + polygonQuadTree = polygonQuadTreeNew; + } + + Polyline result_polyline = (Polyline) polyline.createInstance(); + MultiPathImpl resultPolylineImpl = (MultiPathImpl) result_polyline + ._getImpl(); + QuadTreeImpl.QuadTreeIteratorImpl qIter = null; + SegmentIteratorImpl polylineIter = polylineImpl.querySegmentIterator(); + double[] params = new double[9]; + AttributeStreamOfDbl intersections = new AttributeStreamOfDbl(0); + SegmentBuffer segmentBuffer = new SegmentBuffer(); + int start_index = -1; + int inCount = 0; + int segIndex = 0; + boolean bOptimized = clipResult.size() > 0; + + // The algorithm is like that: + // Loop through all the segments of the polyline. + // For each polyline segment, intersect it with each of the polygon + // segments. + // If no intersections found then, + // If the polyline segment is completely inside, it is added to the + // result polyline. + // If it is outside, it is thrown out. + // If it intersects, then cut the polyline segment to pieces and test + // each part of the intersected result. + // The cut pieces will either have one point inside, or one point + // outside, or the middle point inside/outside. + // + int polylinePathIndex = -1; + + while (polylineIter.nextPath()) { + polylinePathIndex = polylineIter.getPathIndex(); + int stateNewPath = 0; + int stateAddSegment = 1; + int stateManySegments = 2; + int stateManySegmentsContinuePath = 2; + int stateManySegmentsNewPath = 3; + int state = stateNewPath; + start_index = -1; + inCount = 0; + + while (polylineIter.hasNextSegment()) { + int clipStatus = bOptimized ? (int) clipResult.get(segIndex) + : -1; + segIndex++; + Segment polylineSeg = polylineIter.nextSegment(); + if (clipStatus < 0) { + assert (clipStatus == -1); + // Analyse polyline segment for intersection with the + // polygon. + if (polygonQuadTree != null) { + if (qIter == null) { + qIter = polygonQuadTree.getIterator(polylineSeg, + tolerance); + } else { + qIter.resetIterator(polylineSeg, tolerance); + } + + int path_index = -1; + for (int ind = qIter.next(); ind != -1; ind = qIter + .next()) { + polygonIter.resetToVertex(polygonQuadTree + .getElement(ind)); // path_index + path_index = polygonIter.getPathIndex(); + Segment polygonSeg = polygonIter.nextSegment(); + // intersect polylineSeg and polygonSeg. + int count = polylineSeg.intersect(polygonSeg, null, + params, null, tolerance); + for (int i = 0; i < count; i++) + intersections.add(params[i]); + } + } else {// no quadtree built + polygonIter.resetToFirstPath(); + while (polygonIter.nextPath()) { + while (polygonIter.hasNextSegment()) { + Segment polygonSeg = polygonIter.nextSegment(); + // intersect polylineSeg and polygonSeg. + int count = polylineSeg.intersect(polygonSeg, + null, params, null, tolerance); + for (int i = 0; i < count; i++) + intersections.add(params[i]); + } + } + } + + if (intersections.size() > 0) {// intersections detected. + intersections.sort(0, intersections.size()); // std::sort(intersections.begin(), + // intersections.end()); + + double t0 = 0; + intersections.add(1.0); + int status = -1; + for (int i = 0, n = intersections.size(); i < n; i++) { + double t = intersections.get(i); + if (t == t0) { + continue; + } + boolean bWholeSegment = false; + Segment resSeg; + if (t0 != 0 || t != 1.0) { + polylineSeg.cut(t0, t, segmentBuffer); + resSeg = segmentBuffer.get(); + } else { + resSeg = polylineSeg; + bWholeSegment = true; + } + + if (state >= stateManySegments) { + resultPolylineImpl.addSegmentsFromPath( + polylineImpl, polylinePathIndex, + start_index, inCount, + state == stateManySegmentsNewPath); + if (analyseClipSegment_(polygon, + resSeg.getStartXY(), tolerance) != 1) { + if (analyseClipSegment_(polygon, resSeg, + tolerance) != 1) { + assert (false);// something went wrong. + return null; + } + } + + resultPolylineImpl.addSegment(resSeg, false); + state = stateAddSegment; + } else { + status = analyseClipSegment_(polygon, resSeg, + tolerance); + switch (status) { + case 1: + if (!bWholeSegment) { + resultPolylineImpl.addSegment(resSeg, + state == stateNewPath); + state = stateAddSegment; + } else { + if (state < stateManySegments) { + start_index = polylineIter + .getStartPointIndex() + - polylineImpl + .getPathStart(polylinePathIndex); + inCount = 1; + + if (state == stateNewPath) + state = stateManySegmentsNewPath; + else { + assert (state == stateAddSegment); + state = stateManySegmentsContinuePath; + } + } else + inCount++; + } + + break; + case 0: + state = stateNewPath; + start_index = -1; + inCount = 0; + break; + default: + return null;// may happen if a segment + // coincides with the border. + } + } + + t0 = t; + } + } else { + clipStatus = analyseClipSegment_(polygon, + polylineSeg.getStartXY(), tolerance);// simple + // case + // no + // intersection. + // Both + // points + // must + // be + // inside. + if (clipStatus < 0) { + assert (clipStatus >= 0);// E-mail the repro case to + // the Geometry team to + // investigate. + return null;// something goes wrong, resort to + // planesweep + } + + assert (analyseClipSegment_(polygon, + polylineSeg.getEndXY(), tolerance) == clipStatus); + if (clipStatus == 1) {// the whole segment inside + if (state < stateManySegments) { + assert (inCount == 0); + start_index = polylineIter.getStartPointIndex() + - polylineImpl + .getPathStart(polylinePathIndex); + if (state == stateNewPath) + state = stateManySegmentsNewPath; + else { + assert (state == stateAddSegment); + state = stateManySegmentsContinuePath; + } + } + + inCount++; + } else { + assert (state < stateManySegments); + start_index = -1; + inCount = 0; + } + } + + intersections.clear(false); + } else {// clip status is determined by other means + if (clipStatus == 0) {// outside + assert (analyseClipSegment_(polygon, polylineSeg, + tolerance) == 0); + assert (start_index < 0); + assert (inCount == 0); + continue; + } + + if (clipStatus == 1) { + assert (analyseClipSegment_(polygon, polylineSeg, + tolerance) == 1); + if (state == stateNewPath) { + state = stateManySegmentsNewPath; + start_index = polylineIter.getStartPointIndex() + - polylineImpl + .getPathStart(polylinePathIndex); + } else if (state == stateAddSegment) { + state = stateManySegmentsContinuePath; + start_index = polylineIter.getStartPointIndex() + - polylineImpl + .getPathStart(polylinePathIndex); + } else + assert (state >= stateManySegments); + + inCount++; + continue; + } + } + } + + if (state >= stateManySegments) { + resultPolylineImpl.addSegmentsFromPath(polylineImpl, + polylinePathIndex, start_index, inCount, + state == stateManySegmentsNewPath); + start_index = -1; + } + } + + return result_polyline; + } + + int analyseClipSegment_(Polygon polygon, Point2D pt, double tol) { + int v = PointInPolygonHelper.isPointInPolygon(polygon, pt, tol); + return v; + } + + int analyseClipSegment_(Polygon polygon, Segment seg, double tol) { + Point2D pt_1 = seg.getStartXY(); + Point2D pt_2 = seg.getEndXY(); + int v_1 = PointInPolygonHelper.isPointInPolygon(polygon, pt_1, tol); + int v_2 = PointInPolygonHelper.isPointInPolygon(polygon, pt_2, tol); + if ((v_1 == 1 && v_2 == 0) || (v_1 == 0 && v_2 == 1)) { + // Operator_factory_local::SaveJSONToTextFileDbg("c:/temp/badPointInPolygon.json", + // polygon, m_spatial_reference); + assert (false);// if happens + return -1;// something went wrong. One point is inside, the other is + // outside. Should not happen. We'll resort to + // planesweep. + } + if (v_1 == 0 || v_2 == 0) + return 0; + if (v_1 == 1 || v_2 == 1) + return 1; + + Point2D midPt = new Point2D(); + midPt.add(pt_1, pt_2); + midPt.scale(0.5);// calculate midpoint + int v = PointInPolygonHelper.isPointInPolygon(polygon, midPt, tol); + if (v == 0) { + return 0; + } + + if (v == 1) { + return 1; + } + + return -1; + } + + Geometry normalizeIntersectionOutput(Geometry geom, int GT_1, int GT_2) { + if (GT_1 == Geometry.GeometryType.Point + || GT_2 == Geometry.GeometryType.Point) { + assert (geom.getType().value() == Geometry.GeometryType.Point); + } + if (GT_1 == Geometry.GeometryType.MultiPoint) { + if (geom.getType().value() == Geometry.GeometryType.Point) { + MultiPoint mp = new MultiPoint(geom.getDescription()); + if (!geom.isEmpty()) + mp.add((Point) geom); + return mp; + } + } + + return geom; + } + + static Geometry returnEmpty_(Geometry geom, boolean bEmpty) { + return bEmpty ? geom : geom.createInstance(); + } + + Geometry returnEmptyIntersector_() { + if (m_geomIntersectorEmptyGeom == null) + m_geomIntersectorEmptyGeom = m_geomIntersector.createInstance(); + + return m_geomIntersectorEmptyGeom; + } + + // virtual boolean IsRecycling() OVERRIDE { return false; } +} diff --git a/src/com/esri/core/geometry/OperatorIntersectionLocal.java b/src/com/esri/core/geometry/OperatorIntersectionLocal.java new file mode 100644 index 00000000..f9adfbca --- /dev/null +++ b/src/com/esri/core/geometry/OperatorIntersectionLocal.java @@ -0,0 +1,82 @@ +/* + Copyright 1995-2013 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ + +package com.esri.core.geometry; + +import com.esri.core.geometry.Geometry.GeometryAccelerationDegree; + +class OperatorIntersectionLocal extends OperatorIntersection { + + @Override + public GeometryCursor execute(GeometryCursor inputGeometries, + GeometryCursor intersector, SpatialReference sr, + ProgressTracker progressTracker) { + + return new OperatorIntersectionCursor(inputGeometries, intersector, sr, + progressTracker, -1); + } + + @Override + public GeometryCursor execute(GeometryCursor input_geometries, + GeometryCursor intersector, SpatialReference sr, + ProgressTracker progress_tracker, int dimensionMask) { + return new OperatorIntersectionCursor(input_geometries, intersector, + sr, progress_tracker, dimensionMask); + } + + @Override + public Geometry execute(Geometry inputGeometry, Geometry intersector, + SpatialReference sr, ProgressTracker progressTracker) { + SimpleGeometryCursor inputGeomCurs = new SimpleGeometryCursor( + inputGeometry); + SimpleGeometryCursor intersectorCurs = new SimpleGeometryCursor( + intersector); + GeometryCursor geometryCursor = execute(inputGeomCurs, intersectorCurs, + sr, progressTracker); + + return geometryCursor.next(); + } + + @Override + public boolean accelerateGeometry(Geometry geometry, + SpatialReference spatialReference, + GeometryAccelerationDegree accelDegree) { + if (!canAccelerateGeometry(geometry)) + return false; + + double tol = spatialReference != null ? spatialReference + .getTolerance(VertexDescription.Semantics.POSITION) : 0; + boolean accelerated = ((MultiVertexGeometryImpl) geometry._getImpl()) + ._buildQuadTreeAccelerator(accelDegree); + accelerated |= ((MultiVertexGeometryImpl) geometry._getImpl()) + ._buildRasterizedGeometryAccelerator(tol, accelDegree); + return accelerated; + } + + @Override + public boolean canAccelerateGeometry(Geometry geometry) { + return RasterizedGeometry2D.canUseAccelerator(geometry); + } + +} diff --git a/src/com/esri/core/geometry/OperatorIntersects.java b/src/com/esri/core/geometry/OperatorIntersects.java new file mode 100644 index 00000000..d7984fa8 --- /dev/null +++ b/src/com/esri/core/geometry/OperatorIntersects.java @@ -0,0 +1,38 @@ +/* + Copyright 1995-2013 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ + +package com.esri.core.geometry; + +public abstract class OperatorIntersects extends OperatorSimpleRelation { + @Override + public Type getType() { + return Type.Intersects; + } + + public static OperatorIntersects local() { + return (OperatorIntersects) OperatorFactoryLocal.getInstance() + .getOperator(Type.Intersects); + } + +} diff --git a/src/com/esri/core/geometry/OperatorIntersectsLocal.java b/src/com/esri/core/geometry/OperatorIntersectsLocal.java new file mode 100644 index 00000000..5a492095 --- /dev/null +++ b/src/com/esri/core/geometry/OperatorIntersectsLocal.java @@ -0,0 +1,38 @@ +/* + Copyright 1995-2013 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ + +package com.esri.core.geometry; + +class OperatorIntersectsLocal extends OperatorIntersects { + + OperatorDisjoint m_disjoint = (OperatorDisjoint) OperatorFactoryLocal + .getInstance().getOperator(Type.Disjoint); + + @Override + public boolean execute(Geometry inputGeom1, Geometry inputGeom2, + SpatialReference sr, ProgressTracker progressTracker) { + return !m_disjoint.execute(inputGeom1, inputGeom2, sr, progressTracker); + } + +} diff --git a/src/com/esri/core/geometry/OperatorOffset.java b/src/com/esri/core/geometry/OperatorOffset.java new file mode 100644 index 00000000..910ccd8a --- /dev/null +++ b/src/com/esri/core/geometry/OperatorOffset.java @@ -0,0 +1,124 @@ +/* + Copyright 1995-2013 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ +package com.esri.core.geometry; + +import com.esri.core.geometry.Operator.Type; + +public abstract class OperatorOffset extends Operator { + @Override + public Operator.Type getType() { + return Operator.Type.Offset; + } + + /** + * Join types for the offset operation. + */ + public enum JoinType { + Round, Bevel, Miter, Square, + }; + + /** + * Creates offset version of the input geometries. + * + * The offset operation creates a geometry that is a constant distance from + * an input polyline or polygon. It is similar to buffering, but produces a + * one sided result. If offsetDistance > 0, then the offset geometry is + * constructed to the right of the oriented input geometry, otherwise it is + * constructed to the left. For a simple polygon, the orientation of outer + * rings is clockwise and for inner rings it is counter clockwise. So the + * "right side" of a simple polygon is always its inside. The bevelRatio is + * multiplied by the offset distance and the result determines how far a + * mitered offset intersection can be from the input curve before it is + * beveled. + * + * @param inputGeometries + * The geometries to calculate offset for. Point and MultiPoint + * are not supported. + * @param sr + * The SpatialReference of the Geometries. + * @param distance + * The offset distance for the Geometries. + * @param joins + * The join type of the offset geometry. + * @param bevelRatio + * The ratio used to produce a bevel join instead of a miter join + * (used only when joins is Miter) + * @param flattenError + * The maximum distance of the resulting segments compared to the + * true circular arc (used only when joins is Round). If + * flattenError is 0, tolerance value is used. Also, the + * algorithm never produces more than around 180 vertices for + * each round join. + * @return Returns the result of the offset operation. + */ + public abstract GeometryCursor execute(GeometryCursor inputGeometries, + SpatialReference sr, double distance, JoinType joins, + double bevelRatio, double flattenError, + ProgressTracker progressTracker); + + /** + * Creates offset version of the input geometry. + * + * The offset operation creates a geometry that is a constant distance from + * an input polyline or polygon. It is similar to buffering, but produces a + * one sided result. If offsetDistance > 0, then the offset geometry is + * constructed to the right of the oriented input geometry, otherwise it is + * constructed to the left. For a simple polygon, the orientation of outer + * rings is clockwise and for inner rings it is counter clockwise. So the + * "right side" of a simple polygon is always its inside. The bevelRatio is + * multiplied by the offset distance and the result determines how far a + * mitered offset intersection can be from the input curve before it is + * beveled. + * + * @param inputGeometry + * The geometry to calculate offset for. Point and MultiPoint are + * not supported. + * @param sr + * The SpatialReference of the Geometries. + * @param distance + * The offset distance for the Geometries. + * @param joins + * The join type of the offset geometry. + * @param bevelRatio + * The ratio used to produce a bevel join instead of a miter join + * (used only when joins is Miter) + * @param flattenError + * The maximum distance of the resulting segments compared to the + * true circular arc (used only when joins is Round). If + * flattenError is 0, tolerance value is used. Also, the + * algorithm never produces more than around 180 vetices for each + * round join. + * @return Returns the result of the offset operation. + */ + public abstract Geometry execute(Geometry inputGeometry, + SpatialReference sr, double distance, JoinType joins, + double bevelRatio, double flattenError, + ProgressTracker progressTracker); + + public static OperatorOffset local() { + return (OperatorOffset) OperatorFactoryLocal.getInstance().getOperator( + Type.Offset); + } + +} diff --git a/src/com/esri/core/geometry/OperatorOffsetCursor.java b/src/com/esri/core/geometry/OperatorOffsetCursor.java new file mode 100644 index 00000000..5f27c667 --- /dev/null +++ b/src/com/esri/core/geometry/OperatorOffsetCursor.java @@ -0,0 +1,73 @@ +/* + Copyright 1995-2013 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ +package com.esri.core.geometry; + +class OperatorOffsetCursor extends GeometryCursor { + GeometryCursor m_inputGeoms; + SpatialReferenceImpl m_spatialReference; + ProgressTracker m_progressTracker; + double m_distance; + double m_miterLimit; + OperatorOffset.JoinType m_joins; + double m_flattenError; + int m_index; + + OperatorOffsetCursor(GeometryCursor inputGeometries, SpatialReference sr, + double distance, OperatorOffset.JoinType joins, double bevelRatio, + double flattenError, ProgressTracker progressTracker) { + m_index = -1; + m_inputGeoms = inputGeometries; + m_spatialReference = (SpatialReferenceImpl) sr; + m_distance = distance; + m_joins = joins; + m_miterLimit = bevelRatio; + m_flattenError = flattenError; + m_progressTracker = progressTracker; + } + + public Geometry next() { + Geometry geom = m_inputGeoms.next(); + if (geom != null) { + m_index = m_inputGeoms.getGeometryID(); + return Offset(geom); + } + return null; + } + + public int getGeometryID() { + return m_index; + } + + Geometry Offset(Geometry geom) { + double tolerance; + if (m_flattenError <= 0) + tolerance = InternalUtils.calculateToleranceFromGeometry( + m_spatialReference, geom, false); + else + tolerance = m_flattenError; + return ConstructOffset.execute(geom, m_distance, m_joins, m_miterLimit, + tolerance, m_progressTracker); + } + +} diff --git a/src/com/esri/core/geometry/OperatorOffsetLocal.java b/src/com/esri/core/geometry/OperatorOffsetLocal.java new file mode 100644 index 00000000..7006b42d --- /dev/null +++ b/src/com/esri/core/geometry/OperatorOffsetLocal.java @@ -0,0 +1,48 @@ +/* + Copyright 1995-2013 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ +package com.esri.core.geometry; + +class OperatorOffsetLocal extends OperatorOffset { + + @Override + public GeometryCursor execute(GeometryCursor inputGeometries, + SpatialReference sr, double distance, JoinType joins, + double bevelRatio, double flattenError, + ProgressTracker progressTracker) { + return new OperatorOffsetCursor(inputGeometries, sr, distance, joins, + bevelRatio, flattenError, progressTracker); + } + + @Override + public Geometry execute(Geometry inputGeometry, SpatialReference sr, + double distance, JoinType joins, double bevelRatio, + double flattenError, ProgressTracker progressTracker) { + SimpleGeometryCursor inputCursor = new SimpleGeometryCursor( + inputGeometry); + GeometryCursor outCursor = execute(inputCursor, sr, distance, joins, + bevelRatio, flattenError, progressTracker); + return outCursor.next(); + } + +} diff --git a/src/com/esri/core/geometry/OperatorOverlaps.java b/src/com/esri/core/geometry/OperatorOverlaps.java new file mode 100644 index 00000000..d1a362b4 --- /dev/null +++ b/src/com/esri/core/geometry/OperatorOverlaps.java @@ -0,0 +1,39 @@ +/* + Copyright 1995-2013 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ +package com.esri.core.geometry; + +import com.esri.core.geometry.Operator.Type; + +public abstract class OperatorOverlaps extends OperatorSimpleRelation { + @Override + public Type getType() { + return Type.Equals; + } + + public static OperatorOverlaps local() { + return (OperatorOverlaps) OperatorFactoryLocal.getInstance() + .getOperator(Type.Overlaps); + } + +} diff --git a/src/com/esri/core/geometry/OperatorOverlapsLocal.java b/src/com/esri/core/geometry/OperatorOverlapsLocal.java new file mode 100644 index 00000000..f767cafd --- /dev/null +++ b/src/com/esri/core/geometry/OperatorOverlapsLocal.java @@ -0,0 +1,33 @@ +/* + Copyright 1995-2013 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ +package com.esri.core.geometry; + +class OperatorOverlapsLocal extends OperatorOverlaps { + @Override + public boolean execute(Geometry inputGeom1, Geometry inputGeom2, + SpatialReference sr, ProgressTracker progressTracker) { + return RelationalOperations.relate(inputGeom1, inputGeom2, sr, + RelationalOperations.Relation.overlaps, progressTracker); + } +} diff --git a/src/com/esri/core/geometry/OperatorProject.java b/src/com/esri/core/geometry/OperatorProject.java new file mode 100644 index 00000000..cfd1bec1 --- /dev/null +++ b/src/com/esri/core/geometry/OperatorProject.java @@ -0,0 +1,81 @@ +/* + Copyright 1995-2013 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ +package com.esri.core.geometry; + +import com.esri.core.geometry.Operator.Type; + +/** + * Projection of geometries to different coordinate systems. + */ +abstract class OperatorProject extends Operator { + + @Override + public Type getType() { + return Type.Project; + } + + /** + * Performs the Project operation on a geometry cursor + * + * @return Returns a GeometryCursor. + */ + public abstract GeometryCursor execute(GeometryCursor inputGeoms, + ProjectionTransformation projection, ProgressTracker progressTracker); + + /** + * Performs the Project operation on a single geometry instance + * + * @return Returns the Geometry after projection + */ + public abstract Geometry execute(Geometry geometry, + ProjectionTransformation projection, ProgressTracker progressTracker); + + /** + * Transforms an array of points. Returns the number of points transformed. + */ + public abstract int transform(ProjectionTransformation transform, + Point[] coordsSrc, int length, Point[] coordsDst); + + /** + * Transforms an array of 2D points and returns it. The points are stored in + * an interleaved array (x0, y0, x1, y1, x2, y2, ...). + * + * @param transform + * ProjectionTransformation + * @param coordsSrc + * source coordinates to project. + * @param pointCount + * the point count in the coordSrc. THere has to be at least + * pointCount * 2 elements in the coordsSrc array. + * @return projected coordinates in the interleaved form. + */ + public abstract double[] transform(ProjectionTransformation transform, + double[] coordsSrc, int pointCount); + + public static OperatorProject local() { + return (OperatorProject) OperatorFactoryLocal.getInstance() + .getOperator(Type.Project); + } + +} diff --git a/src/com/esri/core/geometry/OperatorProjectLocal.java b/src/com/esri/core/geometry/OperatorProjectLocal.java new file mode 100644 index 00000000..c44c7e1d --- /dev/null +++ b/src/com/esri/core/geometry/OperatorProjectLocal.java @@ -0,0 +1,52 @@ +/* + Copyright 1995-2013 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ + +package com.esri.core.geometry; + +//This is a stub +class OperatorProjectLocal extends OperatorProject { + + @Override + public GeometryCursor execute(GeometryCursor inputGeoms, + ProjectionTransformation transform, ProgressTracker progressTracker) { + throw new GeometryException("not implemented"); + } + + public Geometry execute(Geometry inputGeom, + ProjectionTransformation transform, ProgressTracker progressTracker) { + throw new GeometryException("not implemented"); + } + + @Override + public int transform(ProjectionTransformation transform, Point[] pointsIn, + int count, Point[] pointsOut) { + throw new GeometryException("not implemented"); + } + + public double[] transform(ProjectionTransformation transform, + double[] coordsSrc, int pointCount) { + throw new GeometryException("not implemented"); + } + +} diff --git a/src/com/esri/core/geometry/OperatorProximity2D.java b/src/com/esri/core/geometry/OperatorProximity2D.java new file mode 100644 index 00000000..d8b5b33b --- /dev/null +++ b/src/com/esri/core/geometry/OperatorProximity2D.java @@ -0,0 +1,77 @@ +/* + Copyright 1995-2013 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ + +package com.esri.core.geometry; + +import com.esri.core.geometry.Operator.Type; + +/** + * Finds closest vertices of the Geometry. + */ +public abstract class OperatorProximity2D extends Operator { + @Override + public Type getType() { + return Type.Proximity2D; + } + + /** + * Returns the nearest coordinate on the Geometry to the given input point. + */ + public abstract Proximity2DResult getNearestCoordinate(Geometry geom, + Point inputPoint, boolean bTestPolygonInterior); + + /** + * Returns the nearest vertex of the Geometry to the given input point. + */ + public abstract Proximity2DResult getNearestVertex(Geometry geom, + Point inputPoint); + + /** + * Returns vertices of the Geometry that are closer to the given point than + * the given radius. + * + * @param geom + * The input Geometry. + * @param inputPoint + * The query point. + * @param searchRadius + * The maximum distance to the query point of the vertices. + * @param maxVertexCountToReturn + * The maximum vertex count to return. The function returns no + * more than this number of vertices. + * @return The array of vertices that are in the given search radius to the + * point. The array is sorted by distance to the queryPoint with the + * closest point first. When there are more than the + * maxVertexCountToReturn vertices to return, it returns the closest + * vertices. The array will be empty when geom is empty. + */ + public abstract Proximity2DResult[] getNearestVertices(Geometry geom, + Point inputPoint, double searchRadius, int maxVertexCountToReturn); + + public static OperatorProximity2D local() { + return (OperatorProximity2D) OperatorFactoryLocal.getInstance() + .getOperator(Type.Proximity2D); + } + +} diff --git a/src/com/esri/core/geometry/OperatorProximity2DLocal.java b/src/com/esri/core/geometry/OperatorProximity2DLocal.java new file mode 100644 index 00000000..89171166 --- /dev/null +++ b/src/com/esri/core/geometry/OperatorProximity2DLocal.java @@ -0,0 +1,322 @@ +/* + Copyright 1995-2013 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ + +package com.esri.core.geometry; + +import com.esri.core.geometry.VertexDescription.Semantics; +import java.util.ArrayList; +import java.util.Collections; + +class OperatorProximity2DLocal extends OperatorProximity2D { + + @Override + public Proximity2DResult getNearestCoordinate(Geometry geom, + Point inputPoint, boolean bTestPolygonInterior) { + if (geom.isEmpty()) + return new Proximity2DResult(); + + Point2D inputPoint2D = inputPoint.getXY(); + + Geometry proxmityTestGeom = geom; + int gt = geom.getType().value(); + + if (gt == Geometry.GeometryType.Envelope) { + Polygon polygon = new Polygon(); + polygon.addEnvelope((Envelope) geom, false); + proxmityTestGeom = polygon; + gt = Geometry.GeometryType.Polygon; + } + switch (gt) { + case Geometry.GeometryType.Point: + return pointGetNearestVertex((Point) proxmityTestGeom, inputPoint2D); + case Geometry.GeometryType.MultiPoint: + return multiVertexGetNearestVertex( + (MultiVertexGeometry) proxmityTestGeom, inputPoint2D); + case Geometry.GeometryType.Polyline: + case Geometry.GeometryType.Polygon: + return polyPathGetNearestCoordinate((MultiPath) proxmityTestGeom, + inputPoint2D, bTestPolygonInterior); + default: { + throw new GeometryException("not implemented"); + } + } + } + + @Override + public Proximity2DResult getNearestVertex(Geometry geom, Point inputPoint) { + if (geom.isEmpty()) + return new Proximity2DResult(); + + Point2D inputPoint2D = inputPoint.getXY(); + + Geometry proxmityTestGeom = geom; + int gt = geom.getType().value(); + + if (gt == Geometry.GeometryType.Envelope) { + Polygon polygon = new Polygon(); + polygon.addEnvelope((Envelope) geom, false); + proxmityTestGeom = polygon; + gt = Geometry.GeometryType.Polygon; + } + switch (gt) { + case Geometry.GeometryType.Point: + return pointGetNearestVertex((Point) proxmityTestGeom, inputPoint2D); + case Geometry.GeometryType.MultiPoint: + case Geometry.GeometryType.Polyline: + case Geometry.GeometryType.Polygon: + return multiVertexGetNearestVertex( + (MultiVertexGeometry) proxmityTestGeom, inputPoint2D); + default: { + throw new GeometryException("not implemented"); + } + } + } + + @Override + public Proximity2DResult[] getNearestVertices(Geometry geom, + Point inputPoint, double searchRadius, int maxVertexCountToReturn) { + if (maxVertexCountToReturn < 0) + throw new IllegalArgumentException(); + + if (geom.isEmpty()) + return new Proximity2DResult[] {}; + + Point2D inputPoint2D = inputPoint.getXY(); + + Geometry proxmityTestGeom = geom; + int gt = geom.getType().value(); + + if (gt == Geometry.GeometryType.Envelope) { + Polygon polygon = new Polygon(); + polygon.addEnvelope((Envelope) geom, false); + proxmityTestGeom = polygon; + gt = Geometry.GeometryType.Polygon; + } + switch (gt) { + case Geometry.GeometryType.Point: + return pointGetNearestVertices((Point) proxmityTestGeom, + inputPoint2D, searchRadius, maxVertexCountToReturn); + case Geometry.GeometryType.MultiPoint: + case Geometry.GeometryType.Polyline: + case Geometry.GeometryType.Polygon: + return multiVertexGetNearestVertices( + (MultiVertexGeometry) proxmityTestGeom, inputPoint2D, + searchRadius, maxVertexCountToReturn); + default: { + throw new GeometryException("not implemented"); + } + } + } + + Proximity2DResult polyPathGetNearestCoordinate(MultiPath geom, + Point2D inputPoint, boolean bTestPolygonInterior) { + Proximity2DResult result = new Proximity2DResult(); + + if (geom.getType() == (Geometry.Type.Polygon) && bTestPolygonInterior) { + OperatorFactoryLocal factory = OperatorFactoryLocal.getInstance(); + OperatorDisjoint operatorDisjoint = (OperatorDisjoint) factory + .getOperator(Type.Disjoint); + + Point point = new Point(geom.getDescription()); + point.setXY(inputPoint.x, inputPoint.y); + + boolean disjoint = operatorDisjoint + .execute(geom, point, null, null); + if (!disjoint) { + result._setParams(inputPoint.x, inputPoint.y, 0, 0.0); + return result; + } + } + + MultiPathImpl mpImpl = (MultiPathImpl) geom._getImpl(); + SegmentIteratorImpl segIter = mpImpl.querySegmentIterator(); + + Point2D closest = null;// new Point2D(); + int closestIndex = 0; + double closestDistanceSq = NumberUtils.doubleMax(); + + while (segIter.nextPath()) { + while (segIter.hasNextSegment()) { + Segment segment = segIter.nextSegment(); + double t = segment.getClosestCoordinate(inputPoint, false); + Point2D point = segment.getCoord2D(t); + + double distanceSq = Point2D.sqrDistance(point, inputPoint); + if (distanceSq < closestDistanceSq) { + closest = point; + closestIndex = segIter.getStartPointIndex(); + closestDistanceSq = distanceSq; + } + } + } + + result._setParams(closest.x, closest.y, closestIndex, + Math.sqrt(closestDistanceSq)); + + return result; + } + + Proximity2DResult pointGetNearestVertex(Point geom, Point2D inputPoint) { + Proximity2DResult result = new Proximity2DResult(); + + Point2D pt = geom.getXY(); + double distance = Point2D.distance(pt, inputPoint); + result._setParams(pt.x, pt.y, 0, distance); + + return result; + } + + Proximity2DResult multiVertexGetNearestVertex(MultiVertexGeometry geom, + Point2D inputPoint) { + MultiVertexGeometryImpl mpImpl = (MultiVertexGeometryImpl) geom + ._getImpl(); + AttributeStreamOfDbl position = (AttributeStreamOfDbl) mpImpl + .getAttributeStreamRef((Semantics.POSITION)); + int pointCount = geom.getPointCount(); + + int closestIndex = 0; + double closestx = 0.0; + double closesty = 0.0; + double closestDistanceSq = NumberUtils.doubleMax(); + for (int i = 0; i < pointCount; i++) { + Point2D pt = new Point2D(); + position.read(2 * i, pt); + + double distanceSq = Point2D.sqrDistance(pt, inputPoint); + if (distanceSq < closestDistanceSq) { + closestx = pt.x; + closesty = pt.y; + closestIndex = i; + closestDistanceSq = distanceSq; + } + } + + Proximity2DResult result = new Proximity2DResult(); + result._setParams(closestx, closesty, closestIndex, + Math.sqrt(closestDistanceSq)); + + return result; + } + + Proximity2DResult[] pointGetNearestVertices(Point geom, Point2D inputPoint, + double searchRadius, int maxVertexCountToReturn) { + Proximity2DResult[] resultArray; + + if (maxVertexCountToReturn == 0) { + resultArray = new Proximity2DResult[] {}; + return resultArray; + } + + double searchRadiusSq = searchRadius * searchRadius; + Point2D pt = geom.getXY(); + + double distanceSq = Point2D.sqrDistance(pt, inputPoint); + if (distanceSq <= searchRadiusSq) { + resultArray = new Proximity2DResult[1]; + + Proximity2DResult result = new Proximity2DResult(); + result._setParams(pt.x, pt.y, 0, Math.sqrt(distanceSq)); + resultArray[0] = result; + } else { + resultArray = new Proximity2DResult[0]; + } + + return resultArray; + } + + Proximity2DResult[] multiVertexGetNearestVertices(MultiVertexGeometry geom, + Point2D inputPoint, double searchRadius, int maxVertexCountToReturn) { + Proximity2DResult[] resultArray; + + if (maxVertexCountToReturn == 0) { + resultArray = new Proximity2DResult[0]; + return resultArray; + } + + MultiVertexGeometryImpl mpImpl = (MultiVertexGeometryImpl) geom + ._getImpl(); + AttributeStreamOfDbl position = (AttributeStreamOfDbl) mpImpl + .getAttributeStreamRef((Semantics.POSITION)); + int pointCount = geom.getPointCount(); + + ArrayList v = new ArrayList( + maxVertexCountToReturn); + + int count = 0; + double searchRadiusSq = searchRadius * searchRadius; + for (int i = 0; i < pointCount; i++) { + double x = position.read(2 * i); + double y = position.read(2 * i + 1); + + double xDiff = inputPoint.x - x; + double yDiff = inputPoint.y - y; + + double distanceSq = xDiff * xDiff + yDiff * yDiff; + if (distanceSq <= searchRadiusSq) { + Proximity2DResult result = new Proximity2DResult(); + result._setParams(x, y, i, Math.sqrt(distanceSq)); + + count++; + v.add(result); + + } + } + + int vsize = v.size(); + Collections.sort(v, new Proximity2DResultComparator()); + + if (maxVertexCountToReturn >= vsize) + return v.toArray(new Proximity2DResult[0]); + return v.subList(0, maxVertexCountToReturn).toArray( + new Proximity2DResult[0]); + + } + + /* + * if (distanceSq <= searchRadiusSq) { if (count >= maxVertexCountToReturn + + * 1) { count++; double frontDistance = v.get(0).getDistance(); if + * (frontDistance * frontDistance <= distanceSq) continue; } + * + * Proximity2DResult result = new Proximity2DResult(); result._setParams(x, + * y, i, Math.sqrt(distanceSq)); + * + * count++; + * + * if (count <= maxVertexCountToReturn) { v.add(result); } // else // { // + * if (count == maxVertexCountToReturn + 1) // MAKEHEAP(v, + * Proximity2DResult, Proximity2DResult::_Compare); // // PUSHHEAP(v, + * result, Proximity2DResult, Proximity2DResult::_Compare); // POPHEAP(v, + * Proximity2DResult, Proximity2DResult::_Compare); // } } } + * + * int vsize = v.size(); Collections.sort(v, new + * Proximity2DResultComparator()); + * + * // SORTDYNAMICARRAY(v, Proximity2DResult, 0, vsize, + * Proximity2DResult::_Compare); resultArray = new Proximity2DResult[vsize]; + * for (int i = 0; i < vsize; i++) { resultArray[i] = + * (Proximity2DResult)v.get(i); } + * + * return resultArray; } + */ +} diff --git a/src/com/esri/core/geometry/OperatorRelate.java b/src/com/esri/core/geometry/OperatorRelate.java new file mode 100644 index 00000000..d82cb2a6 --- /dev/null +++ b/src/com/esri/core/geometry/OperatorRelate.java @@ -0,0 +1,49 @@ +/* + Copyright 1995-2013 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ + +package com.esri.core.geometry; + +import com.esri.core.geometry.Operator.Type; + +public abstract class OperatorRelate extends Operator { + @Override + public Type getType() { + return Type.Relate; + } + + /** + * Performs the Relation operation between two geometries using the Shape + * Comparison Language string. + * + * @return Returns True if the relation holds, False otherwise. + */ + public abstract boolean execute(Geometry inputGeom1, Geometry inputGeom2, + SpatialReference sr, String scl, ProgressTracker progressTracker); + + public static OperatorRelate local() { + return (OperatorRelate) OperatorFactoryLocal.getInstance().getOperator( + Type.Relate); + } + +} diff --git a/src/com/esri/core/geometry/OperatorRelateLocal.java b/src/com/esri/core/geometry/OperatorRelateLocal.java new file mode 100644 index 00000000..c83fbee1 --- /dev/null +++ b/src/com/esri/core/geometry/OperatorRelateLocal.java @@ -0,0 +1,36 @@ +/* + Copyright 1995-2013 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ + +package com.esri.core.geometry; + +class OperatorRelateLocal extends OperatorRelate { + + @Override + public boolean execute(Geometry inputGeom1, Geometry inputGeom2, + SpatialReference sr, String scl, ProgressTracker progress_tracker) { + return RelationalOperationsMatrix.relate(inputGeom1, inputGeom2, sr, + scl, progress_tracker); + } + +} diff --git a/src/com/esri/core/geometry/OperatorSimpleRelation.java b/src/com/esri/core/geometry/OperatorSimpleRelation.java new file mode 100644 index 00000000..2cb90a96 --- /dev/null +++ b/src/com/esri/core/geometry/OperatorSimpleRelation.java @@ -0,0 +1,62 @@ +/* + Copyright 1995-2013 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ + +package com.esri.core.geometry; + +import com.esri.core.geometry.Geometry.GeometryAccelerationDegree; + +/** + * A base class for simple relation operators. + */ +public abstract class OperatorSimpleRelation extends Operator { + + /** + * Performs the given relation operation between two geometries. + * + * @return Returns True if the relation holds, False otherwise. + */ + public abstract boolean execute(Geometry inputGeom1, Geometry inputGeom2, + SpatialReference sr, ProgressTracker progressTracker); + + @Override + public boolean canAccelerateGeometry(Geometry geometry) { + return RasterizedGeometry2D.canUseAccelerator(geometry); + } + + @Override + public boolean accelerateGeometry(Geometry geometry, + SpatialReference spatialReference, + GeometryAccelerationDegree accelDegree) { + if (!canAccelerateGeometry(geometry)) + return false; + + double tol = spatialReference != null ? spatialReference + .getTolerance(VertexDescription.Semantics.POSITION) : 0; + boolean accelerated = ((MultiVertexGeometryImpl) geometry._getImpl()) + ._buildQuadTreeAccelerator(accelDegree); + accelerated |= ((MultiVertexGeometryImpl) geometry._getImpl()) + ._buildRasterizedGeometryAccelerator(tol, accelDegree); + return accelerated; + } +} diff --git a/src/com/esri/core/geometry/OperatorSimplify.java b/src/com/esri/core/geometry/OperatorSimplify.java new file mode 100644 index 00000000..86d063ec --- /dev/null +++ b/src/com/esri/core/geometry/OperatorSimplify.java @@ -0,0 +1,72 @@ +/* + Copyright 1995-2013 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ +package com.esri.core.geometry; + +import com.esri.core.geometry.Operator.Type; + +/** + * Simplifies geometry or determines if geometry is simple. + * + */ +public abstract class OperatorSimplify extends Operator { + @Override + public Operator.Type getType() { + return Operator.Type.Simplify; + } + + /** + *Tests if the Geometry is simple. + *@param geom The Geometry to be tested. + *@param bForceTest When True, the Geometry will be tested regardless of the IsKnownSimple flag. + */ + public abstract boolean isSimpleAsFeature(Geometry geom, + SpatialReference spatialRef, boolean bForceTest, + NonSimpleResult result, ProgressTracker progressTracker); + + // Reviewed vs. Feb 8 2011 + public boolean isSimpleAsFeature(Geometry geom, + SpatialReference spatialRef, ProgressTracker progressTracker) { + return isSimpleAsFeature(geom, spatialRef, false, null, progressTracker); + } + + /** + *Performs the Simplify operation on the geometry set. + *@return Returns a GeometryCursor of simplified geometries. + */ + public abstract GeometryCursor execute(GeometryCursor geoms, + SpatialReference sr, boolean bForceSimplify, + ProgressTracker progressTracker); + + /** + *Performs the Simplify operation on a Geometry + *@return Returns a simple Geometry. + */ + public abstract Geometry execute(Geometry geom, SpatialReference sr, + boolean bForceSimplify, ProgressTracker progressTracker); + + public static OperatorSimplify local() { + return (OperatorSimplify) OperatorFactoryLocal.getInstance() + .getOperator(Type.Simplify); + } +} diff --git a/src/com/esri/core/geometry/OperatorSimplifyCursor.java b/src/com/esri/core/geometry/OperatorSimplifyCursor.java new file mode 100644 index 00000000..4be821a8 --- /dev/null +++ b/src/com/esri/core/geometry/OperatorSimplifyCursor.java @@ -0,0 +1,81 @@ +/* + Copyright 1995-2013 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ +package com.esri.core.geometry; + +class OperatorSimplifyCursor extends GeometryCursor { + + GeometryCursor m_inputGeometryCursor; + SpatialReference m_spatialReference; + ProgressTracker m_progressTracker; + + int m_index; + boolean m_bForceSimplify; + + // Reviewed vs. Feb 8 2011 + OperatorSimplifyCursor(GeometryCursor geoms, SpatialReference spatialRef, + boolean bForceSimplify, ProgressTracker progressTracker) { + m_progressTracker = progressTracker; + m_bForceSimplify = bForceSimplify; + m_index = -1; + if (geoms == null) + throw new IllegalArgumentException(); + + m_inputGeometryCursor = geoms; + + m_spatialReference = spatialRef; + } + + // Reviewed vs. Feb 8 2011 + @Override + public Geometry next() { + Geometry geometry; + if ((geometry = m_inputGeometryCursor.next()) != null)// if (geometry = + // m_inputGeometryCursor->Next()) + { + m_index = m_inputGeometryCursor.getGeometryID(); + if ((m_progressTracker != null) + && !(m_progressTracker.progress(-1, -1))) + throw new RuntimeException("user_canceled"); + return simplify(geometry); + } + return null; + } + + // Reviewed vs. Feb 8 2011 + @Override + public int getGeometryID() { + return m_index; + } + + // Reviewed vs. Feb 8 2011 + Geometry simplify(Geometry geometry) { + if (geometry == null) + throw new IllegalArgumentException(); + + // Geometry.Type type = geometry.getType(); + + return OperatorSimplifyLocalHelper.simplifyAsFeature(geometry, + m_spatialReference, m_bForceSimplify, m_progressTracker); + } +} diff --git a/src/com/esri/core/geometry/OperatorSimplifyCursorOGC.java b/src/com/esri/core/geometry/OperatorSimplifyCursorOGC.java new file mode 100644 index 00000000..277724d6 --- /dev/null +++ b/src/com/esri/core/geometry/OperatorSimplifyCursorOGC.java @@ -0,0 +1,76 @@ +/* + Copyright 1995-2013 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ +package com.esri.core.geometry; + +class OperatorSimplifyCursorOGC extends GeometryCursor { + + GeometryCursor m_inputGeometryCursor; + SpatialReference m_spatialReference; + ProgressTracker m_progressTracker; + + int m_index; + boolean m_bForceSimplify; + + OperatorSimplifyCursorOGC(GeometryCursor geoms, + SpatialReference spatialRef, boolean bForceSimplify, + ProgressTracker progressTracker) { + m_progressTracker = progressTracker; + m_bForceSimplify = bForceSimplify; + m_index = -1; + if (geoms == null) + throw new IllegalArgumentException(); + + m_inputGeometryCursor = geoms; + + m_spatialReference = spatialRef; + } + + @Override + public Geometry next() { + Geometry geometry; + if ((geometry = m_inputGeometryCursor.next()) != null)// if (geometry = + // m_inputGeometryCursor->Next()) + { + m_index = m_inputGeometryCursor.getGeometryID(); + if ((m_progressTracker != null) + && !(m_progressTracker.progress(-1, -1))) + throw new RuntimeException("user_canceled"); + return simplify(geometry); + } + return null; + } + + @Override + public int getGeometryID() { + return m_index; + } + + Geometry simplify(Geometry geometry) { + if (geometry == null) + throw new IllegalArgumentException(); + + return OperatorSimplifyLocalHelper.simplifyOGC(geometry, + m_spatialReference, m_bForceSimplify, m_progressTracker); + } +} diff --git a/src/com/esri/core/geometry/OperatorSimplifyLocal.java b/src/com/esri/core/geometry/OperatorSimplifyLocal.java new file mode 100644 index 00000000..6cb16dc1 --- /dev/null +++ b/src/com/esri/core/geometry/OperatorSimplifyLocal.java @@ -0,0 +1,57 @@ +/* + Copyright 1995-2013 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ +package com.esri.core.geometry; + +class OperatorSimplifyLocal extends OperatorSimplify { + + // Reviewed vs. Feb 8 2011 + @Override + public GeometryCursor execute(GeometryCursor geoms, + SpatialReference spatialRef, boolean bForceSimplify, + ProgressTracker progressTracker) { + return new OperatorSimplifyCursor(geoms, spatialRef, bForceSimplify, + progressTracker); + } + + // Reviewed vs. Feb 8 2011 + @Override + public boolean isSimpleAsFeature(Geometry geom, + SpatialReference spatialRef, boolean bForceTest, + NonSimpleResult result, ProgressTracker progressTracker) { + int res = OperatorSimplifyLocalHelper.isSimpleAsFeature(geom, + spatialRef, bForceTest, result, progressTracker); + return res > 0; + } + + // Reviewed vs. Feb 8 2011 + @Override + public Geometry execute(Geometry geom, SpatialReference spatialRef, + boolean bForceSimplify, ProgressTracker progressTracker) { + SimpleGeometryCursor inputCursor = new SimpleGeometryCursor(geom); + GeometryCursor outputCursor = execute(inputCursor, spatialRef, + bForceSimplify, progressTracker); + + return outputCursor.next(); + } +} diff --git a/src/com/esri/core/geometry/OperatorSimplifyLocalHelper.java b/src/com/esri/core/geometry/OperatorSimplifyLocalHelper.java new file mode 100644 index 00000000..2f726e53 --- /dev/null +++ b/src/com/esri/core/geometry/OperatorSimplifyLocalHelper.java @@ -0,0 +1,2225 @@ +/* + Copyright 1995-2013 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ +package com.esri.core.geometry; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Comparator; + +import com.esri.core.geometry.MultiVertexGeometryImpl.GeometryXSimple; + +class OperatorSimplifyLocalHelper { + private static final class Edge { + Edge() { + m_flags = 0; + // m_segment.createInstance(); + } + + Segment m_segment; + int m_vertexIndex; + int m_pathIndex; + int m_flags; + + void setReversed(boolean bYesNo) { + m_flags &= (~1); + m_flags = m_flags | (bYesNo ? 1 : 0); + } + + // The value returned by GetReversed is interpreted differently in + // checkSelfIntersections_ and checkValidRingOrientation_ + boolean getReversed() /* const */ + { + return (m_flags & 1) != 0; + } + + int getRightSide() /* const */ + { + return getReversed() ? 0 : 1; // 0 means there should be an + // emptiness on the right side of + // the edge, 1 means there is + // interior + } + } + + private final VertexDescription m_description; + private Geometry m_geometry; + private SpatialReferenceImpl m_sr; + private int m_dbgCounter; // debugging counter(for breakpoints) + private double m_toleranceIsSimple; + private double m_toleranceSimplify; + // private double m_toleranceCluster; //cluster tolerance needs to be + // sqrt(2) times larger than the tolerance of the other simplify processes. + private int m_knownSimpleResult; + private int m_attributeCount; + + private ArrayList m_edges; + private AttributeStreamOfInt32 m_FreeEdges; + private ArrayList m_lineEdgesRecycle; + private AttributeStreamOfInt32 m_newEdges; + private SegmentIteratorImpl m_recycledSegIter; + private IndexMultiDCList m_crossOverHelperList; + private AttributeStreamOfInt32 m_paths_for_OGC_tests; + + private ProgressTracker m_progressTracker; + + private Treap m_AET; + private AttributeStreamOfInt32 m_xyToNode1; // for each vertex, contains -1, + // or the edge node. + private AttributeStreamOfInt32 m_xyToNode2; // for each vertex, contains -1, + // or the edge node. + private AttributeStreamOfInt32 m_pathOrientations; // 0 if undefined, -1 for + // counterclockwise, 1 + // for clockwise. + private AttributeStreamOfInt32 m_pathParentage; + private int m_unknownOrientationPathCount; + private double m_yScanline; + + private AttributeStreamOfDbl m_xy; + private AttributeStreamOfInt32 m_pairs; + private AttributeStreamOfInt32 m_pairIndices; + + private EditShape m_editShape; + private boolean m_bOGCRestrictions; + private boolean m_bPlanarSimplify; + + private int isSimplePlanarImpl_() { + m_bPlanarSimplify = true; + if (Geometry.isMultiPath(m_geometry.getType().value())) { + if (!checkStructure_()) // check structure of geometry(no zero + // length paths, etc) + return 0; + + if (!checkDegenerateSegments_(false)) // check for degenerate + // segments(only 2D,no zs or + // other attributes) + return 0; + } + + if (!checkClustering_()) // check clustering(points are either + // coincident,or further than tolerance) + return 0; + + if (!Geometry.isMultiPath(m_geometry.getType().value())) + return 2; // multipoint is simple + + if (!checkCracking_()) // check that there are no self intersections and + // overlaps among segments. + return 0; + + if (m_geometry.getType() == Geometry.Type.Polyline) { + if (!checkSelfIntersectionsPolylinePlanar_()) + return 0; + + return 2; // polyline is simple + } + + if (!checkSelfIntersections_()) // check that there are no other self + // intersections (for the cases of + // several segments connect in a point) + return 0; + + // check that every hole is counterclockwise, and every exterior is + // clockwise. + // for the strong simple also check that exterior rings are followed by + // the interior rings. + return checkValidRingOrientation_(); + } + + private boolean testToleranceDistance_(int xyindex1, int xyindex2) { + double x1 = m_xy.read(2 * xyindex1); + double y1 = m_xy.read(2 * xyindex1 + 1); + double x2 = m_xy.read(2 * xyindex2); + double y2 = m_xy.read(2 * xyindex2 + 1); + boolean b = !Clusterer.isClusterCandidate(x1, y1, x2, y2, + m_toleranceIsSimple); + if (!b) { + if (m_geometry.getDimension() == 0) + return false; + + return (x1 == x2 && y1 == y2); // points either coincide or + // further,than the tolerance + } + + return b; + } + + private boolean checkStructure_() { + MultiPathImpl multiPathImpl = (MultiPathImpl) m_geometry._getImpl(); + int minsize = multiPathImpl.m_bPolygon ? 3 : 2; + for (int ipath = 0, npath = multiPathImpl.getPathCount(); ipath < npath; ipath++) { + if (multiPathImpl.getPathSize(ipath) < minsize) { + m_nonSimpleResult = new NonSimpleResult( + NonSimpleResult.Reason.Structure, ipath, 0); + return false; + } + } + + return true; + } + + private boolean checkDegenerateSegments_(boolean bTestZs) { + MultiPathImpl multiPathImpl = (MultiPathImpl) m_geometry._getImpl(); + SegmentIteratorImpl segIter = multiPathImpl.querySegmentIterator(); + // Envelope2D env2D; + boolean bHasZ = multiPathImpl + .hasAttribute(VertexDescription.Semantics.Z); + double ztolerance = !bHasZ ? 0 : InternalUtils + .calculateZToleranceFromGeometry(m_sr, multiPathImpl, false); + while (segIter.nextPath()) { + while (segIter.hasNextSegment()) { + /* const */Segment seg = segIter.nextSegment(); + double length = seg.calculateLength2D(); + if (length > m_toleranceIsSimple) + continue; + + if (bTestZs && bHasZ) { + double z0 = seg.getStartAttributeAsDbl( + VertexDescription.Semantics.Z, 0); + double z1 = seg.getStartAttributeAsDbl( + VertexDescription.Semantics.Z, 0); + if (Math.abs(z1 - z0) > ztolerance) + continue; + } + + m_nonSimpleResult = new NonSimpleResult( + NonSimpleResult.Reason.DegenerateSegments, + segIter.getStartPointIndex(), -1); + return false; + } + } + + return true; + } + + private boolean checkClustering_() { + MultiVertexGeometryImpl multiVertexImpl = (MultiVertexGeometryImpl) m_geometry + ._getImpl(); + + MultiPathImpl multiPathImpl = null; + if (Geometry.isMultiPath(m_geometry.getType().value())) + multiPathImpl = (MultiPathImpl) m_geometry._getImpl(); + + boolean get_paths = (m_bPlanarSimplify || m_bOGCRestrictions) + && multiPathImpl != null; + + int pointCount = multiVertexImpl.getPointCount(); + m_xy = (AttributeStreamOfDbl) multiVertexImpl + .getAttributeStreamRef(VertexDescription.Semantics.POSITION); + m_pairs = new AttributeStreamOfInt32(0); + m_pairs.reserve(pointCount * 2); + m_pairIndices = new AttributeStreamOfInt32(0); + m_pairIndices.reserve(pointCount * 2); + if (get_paths) { + if (m_paths_for_OGC_tests == null) + m_paths_for_OGC_tests = new AttributeStreamOfInt32(0); + m_paths_for_OGC_tests.reserve(pointCount); + } + int ipath = 0; + for (int i = 0; i < pointCount; i++) { + m_pairs.add(2 * i); // y - tol(BOTTOM) + m_pairs.add(2 * i + 1); // y + tol(TOP) + m_pairIndices.add(2 * i); + m_pairIndices.add(2 * i + 1); + if (get_paths) { + while (i >= multiPathImpl.getPathEnd(ipath)) + ipath++; + + m_paths_for_OGC_tests.add(ipath); + } + + } + + BucketSort sorter = new BucketSort(); + sorter.sort(m_pairIndices, 0, 2 * pointCount, new IndexSorter(this, + get_paths)); + + m_AET.clear(); + m_AET.setComparator(new ClusterTestComparator(this)); + m_AET.setCapacity(pointCount); + for (int index = 0, n = pointCount * 2; index < n; index++) { + int pairIndex = m_pairIndices.get(index); + int pair = m_pairs.get(pairIndex); + int xyindex = pair >> 1; // k = 2n or 2n + 1 represent a vertical + // segment for the same vertex. + // Therefore, k / 2 represents a vertex + // index + // Points need to be either exactly equal or further than 2 * + // tolerance apart. + if ((pair & 1) == 0) {// bottom element + int aetNode = m_AET.addElement(xyindex, -1); + // add it to the AET,end test it against its left and right + // neighbours. + int leftneighbour = m_AET.getPrev(aetNode); + if (leftneighbour != Treap.nullNode() + && !testToleranceDistance_( + m_AET.getElement(leftneighbour), xyindex)) { + m_nonSimpleResult = new NonSimpleResult( + NonSimpleResult.Reason.Clustering, xyindex, + m_AET.getElement(leftneighbour)); + return false; + } + int rightneighbour = m_AET.getNext(aetNode); + if (rightneighbour != Treap.nullNode() + && !testToleranceDistance_( + m_AET.getElement(rightneighbour), xyindex)) { + m_nonSimpleResult = new NonSimpleResult( + NonSimpleResult.Reason.Clustering, xyindex, + m_AET.getElement(rightneighbour)); + return false; + } + } else { // top + // get left and right neighbours, and remove the element + // from AET. Then test the neighbours with the + // tolerance. + int aetNode = m_AET.search(xyindex, -1); + int leftneighbour = m_AET.getPrev(aetNode); + int rightneighbour = m_AET.getNext(aetNode); + m_AET.deleteNode(aetNode, -1); + if (leftneighbour != Treap.nullNode() + && rightneighbour != Treap.nullNode() + && !testToleranceDistance_( + m_AET.getElement(leftneighbour), + m_AET.getElement(rightneighbour))) { + m_nonSimpleResult = new NonSimpleResult( + NonSimpleResult.Reason.Clustering, + m_AET.getElement(leftneighbour), + m_AET.getElement(rightneighbour)); + return false; + } + } + } + + return true; + } + + private boolean checkCracking_() { + MultiVertexGeometryImpl multiVertexImpl = (MultiVertexGeometryImpl) m_geometry + ._getImpl(); + int pointCount = multiVertexImpl.getPointCount(); + if (pointCount < 10)// use brute force for smaller polygons + { + return checkCrackingBrute_(); + } else { + return checkCrackingPlanesweep_(); + } + } + + private boolean checkCrackingPlanesweep_() // cracker,that uses planesweep + // algorithm. + { + EditShape editShape = new EditShape(); + editShape.addGeometry(m_geometry); + NonSimpleResult result = new NonSimpleResult(); + boolean bNonSimple = Cracker.needsCracking(editShape, + m_toleranceIsSimple, result, m_progressTracker); + if (bNonSimple) { + result.m_vertexIndex1 = editShape + .getVertexIndex(result.m_vertexIndex1); + result.m_vertexIndex2 = editShape + .getVertexIndex(result.m_vertexIndex2); + m_nonSimpleResult.Assign(result); + return false; + } else + return true; + } + + private boolean checkCrackingBrute_() // cracker, that uses brute force (a + // double loop) to find segment + // intersections. + { + MultiPathImpl multiPathImpl = (MultiPathImpl) m_geometry._getImpl(); + // Implementation without a QuadTreeImpl accelerator + SegmentIteratorImpl segIter1 = multiPathImpl.querySegmentIterator(); + SegmentIteratorImpl segIter2 = multiPathImpl.querySegmentIterator(); + // Envelope2D env2D; + while (segIter1.nextPath()) { + while (segIter1.hasNextSegment()) { + /* const */Segment seg1 = segIter1.nextSegment(); + if (!segIter1.isLastSegmentInPath() || !segIter1.isLastPath()) { + segIter2.resetTo(segIter1); + do { + while (segIter2.hasNextSegment()) { + /* const */Segment seg2 = segIter2.nextSegment(); + int res = seg1._isIntersecting(seg2, + m_toleranceIsSimple, true); + if (res != 0) { + NonSimpleResult.Reason reason = res == 2 ? NonSimpleResult.Reason.CrossOver + : NonSimpleResult.Reason.Cracking; + m_nonSimpleResult = new NonSimpleResult(reason, + segIter1.getStartPointIndex(), + segIter2.getStartPointIndex()); + return false; + } + } + } while (segIter2.nextPath()); + } + } + } + return true; + } + + private boolean checkSelfIntersections_() { + MultiPathImpl multiPathImpl = (MultiPathImpl) m_geometry._getImpl(); + m_edges.clear(); + m_edges.ensureCapacity(20);// we reuse the edges while going through a + // polygon. + m_lineEdgesRecycle.clear(); + m_lineEdgesRecycle.ensureCapacity(20);// we reuse the edges while going + // through a polygon. + + m_recycledSegIter = multiPathImpl.querySegmentIterator(); + m_recycledSegIter.setCirculator(true); + + AttributeStreamOfInt32 bunch = new AttributeStreamOfInt32(0);// stores + // coincident + // vertices + bunch.reserve(10); + int pointCount = multiPathImpl.getPointCount(); + double xprev = NumberUtils.TheNaN; + double yprev = 0; + // We already have a sorted list of vertices from clustering check. + for (int index = 0, n = pointCount * 2; index < n; index++) { + int pairIndex = m_pairIndices.get(index); + int pair = m_pairs.get(pairIndex); + if ((pair & 1) != 0) + continue; // m_pairs array is redundant. See checkClustering_. + + int xyindex = pair >> 1; + + double x = m_xy.read(2 * xyindex); + double y = m_xy.read(2 * xyindex + 1); + if (bunch.size() != 0) { + if (x != xprev || y != yprev) { + if (!processBunchForSelfIntersectionTest_(bunch)) + return false; + if (bunch != null) + bunch.clear(false); + } + } + + bunch.add(xyindex); + xprev = x; + yprev = y; + } + + assert (bunch.size() > 0);// cannot be empty + + if (!processBunchForSelfIntersectionTest_(bunch)) + return false; + + return true; + } + + static final class Vertex_info { + double x, y; + int ipath; + int ivertex; + boolean boundary; + }; + + static final class Vertex_info_pl { + double x; + double y; + int ipath; + int ivertex; + boolean boundary; + boolean end_point; + }; + + boolean checkSelfIntersectionsPolylinePlanar_() { + MultiPathImpl multiPathImpl = (MultiPathImpl) m_geometry._getImpl(); + + boolean closedPaths[] = new boolean[multiPathImpl.getPathCount()]; + for (int ipath = 0, npaths = multiPathImpl.getPathCount(); ipath < npaths; ipath++) { + closedPaths[ipath] = multiPathImpl.isClosedPathInXYPlane(ipath); + } + + Vertex_info_pl vi_prev = new Vertex_info_pl(); + boolean is_closed_path; + int path_start; + int path_last; + + Point2D pt = new Point2D(); + + {// scope + int pairIndex = m_pairIndices.get(0); + int pair = m_pairs.get(pairIndex); + int xyindex = pair >> 1; + m_xy.read(2 * xyindex, pt); + int ipath = m_paths_for_OGC_tests.get(xyindex); + is_closed_path = closedPaths[ipath]; + path_start = multiPathImpl.getPathStart(ipath); + path_last = multiPathImpl.getPathEnd(ipath) - 1; + vi_prev.end_point = (xyindex == path_start) + || (xyindex == path_last); + if (m_bOGCRestrictions) + vi_prev.boundary = !is_closed_path && vi_prev.end_point; + else + // for regular planar simplify, only the end points are allowed + // to coincide + vi_prev.boundary = vi_prev.end_point; + vi_prev.ipath = ipath; + vi_prev.x = pt.x; + vi_prev.y = pt.y; + vi_prev.ivertex = xyindex; + } + + Vertex_info_pl vi = new Vertex_info_pl(); + + for (int index = 1, n = m_pairIndices.size(); index < n; index++) { + int pairIndex = m_pairIndices.get(index); + int pair = m_pairs.get(pairIndex); + if ((pair & 1) != 0) + continue; + + int xyindex = pair >> 1; + m_xy.read(2 * xyindex, pt); + int ipath = m_paths_for_OGC_tests.get(xyindex); + if (ipath != vi_prev.ipath) { + is_closed_path = closedPaths[ipath]; + path_start = multiPathImpl.getPathStart(ipath); + path_last = multiPathImpl.getPathEnd(ipath) - 1; + } + boolean boundary; + boolean end_point = (xyindex == path_start) + || (xyindex == path_last); + if (m_bOGCRestrictions) + boundary = !is_closed_path && vi_prev.end_point; + else + // for regular planar simplify, only the end points are allowed + // to coincide + boundary = vi_prev.end_point; + + vi.x = pt.x; + vi.y = pt.y; + vi.ipath = ipath; + vi.ivertex = xyindex; + vi.boundary = boundary; + vi.end_point = end_point; + + if (vi.x == vi_prev.x && vi.y == vi_prev.y) { + if (m_bOGCRestrictions) { + if (!vi.boundary || !vi_prev.boundary) { + if ((vi.ipath != vi_prev.ipath) + || (!vi.end_point && !vi_prev.end_point))// check + // that + // this + // is + // not + // the + // endpoints + // of + // a + // closed + // path + { + // one of coincident vertices is not on the boundary + // this is either Non_simple_result::cross_over or + // Non_simple_result::ogc_self_tangency. + // too expensive to distinguish between the two. + m_nonSimpleResult = new NonSimpleResult( + NonSimpleResult.Reason.OGCPolylineSelfTangency, + vi.ivertex, vi_prev.ivertex); + return false;// common point not on the boundary + } + } + } else { + if (!vi.end_point || !vi_prev.end_point) {// one of + // coincident + // vertices is + // not an + // endpoint + m_nonSimpleResult = new NonSimpleResult( + NonSimpleResult.Reason.CrossOver, vi.ivertex, + vi_prev.ivertex); + return false;// common point not on the boundary + } + } + } + + Vertex_info_pl tmp = vi_prev; + vi_prev = vi; + vi = tmp; + } + + return true; + } + + final static class Vertex_info_pg { + double x; + double y; + int ipath; + int ivertex; + int ipolygon; + + Vertex_info_pg(double x_, double y_, int ipath_, int xyindex_, + int polygon_) { + x = x_; + y = y_; + ipath = ipath_; + ivertex = xyindex_; + ipolygon = polygon_; + } + + boolean is_equal(Vertex_info_pg other) { + return x == other.x && y == other.y && ipath == other.ipath + && ivertex == other.ivertex && ipolygon == other.ipolygon; + } + }; + + boolean check_self_intersections_polygons_OGC_() { + MultiPathImpl multiPathImpl = (MultiPathImpl) (m_geometry._getImpl()); + // OGC MultiPolygon is simple when each Polygon is simple and Polygons a + // allowed only touch at finite number of vertices. + // OGC Polygon is simple if it consist of simple LinearRings. + // LinearRings cannot cross. + // Any two LinearRings of a OGC Polygon are allowed to touch at single + // vertex only. + // The OGC Polygon interior has to be a connected set. + + // At this point we assume that the ring order has to be correct (holes + // follow corresponding exterior ring). + // No Rings cross. Exterior rings can only touch at finite number of + // vertices. + + // Fill a mapping of ring to + int[] ring_to_polygon = new int[multiPathImpl.getPathCount()]; + int exteriors = -1; + boolean has_holes = false; + for (int ipath = 0, n = multiPathImpl.getPathCount(); ipath < n; ipath++) { + if (multiPathImpl.isExteriorRing(ipath)) { + has_holes = false; + exteriors++; + if (ipath < n - 1) { + if (!multiPathImpl.isExteriorRing(ipath + 1)) + has_holes = true; + } + } + + // For OGC polygons with no holes, store -1. + // For polygons with holes, store polygon index for each ring. + ring_to_polygon[ipath] = has_holes ? exteriors : -1; + } + + // Use already sorted m_pairIndices + Vertex_info_pg vi_prev = null; + Point2D pt = new Point2D(); + {// scope + int pairIndex = m_pairIndices.get(0); + int pair = m_pairs.get(pairIndex); + int xyindex = pair >> 1; + m_xy.read(2 * xyindex, pt); + int ipath = m_paths_for_OGC_tests.get(xyindex); + vi_prev = new Vertex_info_pg(pt.x, pt.y, ipath, xyindex, + ring_to_polygon[ipath]); + } + + ArrayList intersections = new ArrayList( + multiPathImpl.getPathCount() * 2); + for (int index = 1, n = m_pairIndices.size(); index < n; index++) { + int pairIndex = m_pairIndices.get(index); + int pair = m_pairs.get(pairIndex); + if ((pair & 1) != 0) + continue; + int xyindex = pair >> 1; + m_xy.read(2 * xyindex, pt); + int ipath = m_paths_for_OGC_tests.get(xyindex); + Vertex_info_pg vi = new Vertex_info_pg(pt.x, pt.y, ipath, xyindex, + ring_to_polygon[ipath]); + + if (vi.x == vi_prev.x && vi.y == vi_prev.y) { + if (vi.ipath == vi_prev.ipath) {// the ring has self tangency + m_nonSimpleResult = new NonSimpleResult( + NonSimpleResult.Reason.OGCPolygonSelfTangency, + vi.ivertex, vi_prev.ivertex); + return false; + } else if (ring_to_polygon[vi.ipath] >= 0 + && ring_to_polygon[vi.ipath] == ring_to_polygon[vi_prev.ipath]) {// only + // add + // rings + // from + // polygons + // with + // holes. + // Only + // interested + // in + // touching + // rings + // that + // belong + // to + // the + // same + // polygon + if (intersections.size() == 0 + || intersections.get(intersections.size() - 1) != vi_prev) + intersections.add(vi_prev); + intersections.add(vi); + } + } + + vi_prev = vi; + } + + if (intersections.size() == 0) + return true; + + // Find disconnected interior cases (OGC spec: Interior of polygon has + // to be a closed set) + + // Note: Now we'll reuse ring_to_polygon for different purpose - to + // store mapping from the rings to the graph nodes. + + IndexMultiDCList graph = new IndexMultiDCList(true); + Arrays.fill(ring_to_polygon, -1); + int vnode_index = -1; + Point2D prev = new Point2D(); + prev.setNaN(); + for (int i = 0, n = intersections.size(); i < n; i++) { + Vertex_info_pg cur = intersections.get(i); + if (cur.x != prev.x || cur.y != prev.y) { + vnode_index = graph.createList(0); + prev.x = cur.x; + prev.y = cur.y; + } + + int rnode_index = ring_to_polygon[cur.ipath]; + if (rnode_index == -1) { + rnode_index = graph.createList(2); + ring_to_polygon[cur.ipath] = rnode_index; + } + graph.addElement(rnode_index, vnode_index); // add to rnode + // adjacency list the + // current vnode + graph.addElement(vnode_index, rnode_index); // add to vnode + // adjacency list the + // rnode + } + + AttributeStreamOfInt32 depth_first_stack = new AttributeStreamOfInt32(0); + depth_first_stack.reserve(10); + + for (int node = graph.getFirstList(); node != -1; node = graph + .getNextList(node)) { + int ncolor = graph.getListData(node); + if ((ncolor & 1) != 0 || (ncolor & 2) == 0) + continue;// already visited or this is a vnode (we do not want + // to start from vnode). + + int bad_rnode = -1; + depth_first_stack.add(node); + depth_first_stack.add(-1);// parent + while (depth_first_stack.size() > 0) { + int cur_node_parent = depth_first_stack.getLast(); + depth_first_stack.removeLast(); + int cur_node = depth_first_stack.getLast(); + depth_first_stack.removeLast(); + int color = graph.getListData(cur_node); + if ((color & 1) != 0) { + // already visited this node. This means we found a loop. + if ((color & 2) == 0) {// closing on vnode + bad_rnode = cur_node_parent; + } else + bad_rnode = cur_node; + + // assert(bad_rnode != -1); + break; + } + + graph.setListData(cur_node, color | 1); + for (int adjacent_node = graph.getFirst(cur_node); adjacent_node != -1; adjacent_node = graph + .getNext(adjacent_node)) { + int adjacent_node_data = graph.getData(adjacent_node); + if (adjacent_node_data == cur_node_parent) + continue;// avoid going back to where we just came from + depth_first_stack.add(adjacent_node_data); + depth_first_stack.add(cur_node);// push cur_node as parent + // of adjacent_node + } + } + + if (bad_rnode != -1) { + int bad_ring_index = -1; + for (int i = 0, n = ring_to_polygon.length; i < n; i++) + if (ring_to_polygon[i] == bad_rnode) { + bad_ring_index = i; + break; + } + + // bad_ring_index is any ring in a problematic chain of touching + // rings. + // When chain of touching rings form a loop, the result is a + // disconnected interior, + // which is non-simple for OGC spec. + m_nonSimpleResult = new NonSimpleResult( + NonSimpleResult.Reason.OGCDisconnectedInterior, + bad_ring_index, -1); + return false; + } + } + + return true; + } + + private int checkValidRingOrientation_() { + MultiPathImpl multiPathImpl = (MultiPathImpl) m_geometry._getImpl(); + double totalArea = multiPathImpl.calculateArea2D(); + if (totalArea <= 0) { + m_nonSimpleResult = new NonSimpleResult( + NonSimpleResult.Reason.RingOrientation, + multiPathImpl.getPathCount() == 1 ? 1 : -1, -1); + return 0; + } + + if (multiPathImpl.getPathCount() == 1) {// optimization for a single + // polygon + if (m_bOGCRestrictions) { + if (!check_self_intersections_polygons_OGC_()) + return 0; + } + + return 2; + } + + // 1.Go through all vertices in the sorted order. + // 2.For each vertex,insert any non-horizontal segment that has the + // vertex as low point(there can be max two segments) + m_pathOrientations = new AttributeStreamOfInt32( + multiPathImpl.getPathCount(), 0); + + m_pathParentage = new AttributeStreamOfInt32( + multiPathImpl.getPathCount(), -1); + + int parent_ring = -1; + double exteriorArea = 0; + for (int ipath = 0, n = multiPathImpl.getPathCount(); ipath < n; ipath++) { + double area = multiPathImpl.calculateRingArea2D(ipath); + m_pathOrientations.write(ipath, area < 0 ? 0 : 256); // 8th bit + // is + // existing + // orientation + if (area > 0) { + parent_ring = ipath; + exteriorArea = area; + } else if (area == 0) { + m_nonSimpleResult = new NonSimpleResult( + NonSimpleResult.Reason.RingOrientation, ipath, -1); + return 0; + } else { + // area < 0: this is a hole. + // We write the parent exterior + // ring for it (assumed to be first previous exterior ring) + if (parent_ring < 0 || exteriorArea < Math.abs(area)) { + // The first ring is a hole - this is a wrong ring ordering. + // Or the hole's area is bigger than the previous exterior + // area - this means ring order is broken, + // because holes are always smaller. This is not the only + // condition when ring order is broken though. + m_nonSimpleResult = new NonSimpleResult( + NonSimpleResult.Reason.RingOrder, ipath, -1); + if (m_bOGCRestrictions) + return 0; + } + m_pathParentage.write(ipath, parent_ring); + } + } + + m_unknownOrientationPathCount = multiPathImpl.getPathCount(); + m_newEdges = new AttributeStreamOfInt32(0); + m_newEdges.reserve(10); + + int pointCount = multiPathImpl.getPointCount(); + m_yScanline = NumberUtils.TheNaN; + AttributeStreamOfInt32 bunch = new AttributeStreamOfInt32(0); // stores + // coincident + // vertices + bunch.reserve(10); + // Each vertex has two edges attached.These two arrays map vertices to + // edges as nodes in the m_AET + m_xyToNode1 = new AttributeStreamOfInt32(pointCount, Treap.nullNode()); + m_xyToNode2 = new AttributeStreamOfInt32(pointCount, Treap.nullNode()); + if (m_FreeEdges != null) + m_FreeEdges.clear(false); + else + m_FreeEdges = new AttributeStreamOfInt32(0); + m_FreeEdges.reserve(10); + + m_AET.clear(); + m_AET.setComparator(new RingOrientationTestComparator(this)); + for (int index = 0, n = pointCount * 2; m_unknownOrientationPathCount > 0 + && index < n; index++) { + int pairIndex = m_pairIndices.get(index); + int pair = m_pairs.get(pairIndex); + if ((pair & 1) != 0) + continue;// m_pairs array is redundant.See checkClustering_. + + int xyindex = pair >> 1; + double y = m_xy.read(2 * xyindex + 1); + + if (y != m_yScanline && bunch.size() != 0) { + if (!processBunchForRingOrientationTest_(bunch)) { + // m_nonSimpleResult is set in the + // processBunchForRingOrientationTest_ + return 0; + } + if (bunch != null) + bunch.clear(false); + } + + bunch.add(xyindex);// all vertices that have same y are added to the + // bunch + m_yScanline = y; + } + + if (m_unknownOrientationPathCount > 0 + && !processBunchForRingOrientationTest_(bunch)) { + // m_nonSimpleResult is set in the + // processBunchForRingOrientationTest_ + return 0; + } + + if (m_bOGCRestrictions) { + if (m_nonSimpleResult.m_reason != NonSimpleResult.Reason.NotDetermined) + return 0;// cannot proceed with OGC verification if the ring + // order is broken (cannot decide polygons then). + + if (!check_self_intersections_polygons_OGC_()) + return 0; + + return 2;// everything is good + } else { + if (m_nonSimpleResult.m_reason == NonSimpleResult.Reason.NotDetermined) + return 2;// everything is good + + // weak simple + return 1; + } + } + + private boolean processBunchForSelfIntersectionTest_( + AttributeStreamOfInt32 bunch) { + assert (bunch.size() > 0); + if (bunch.size() == 1) + return true; + + assert (m_edges.size() == 0); + + // Bunch contains vertices that have exactly same x and y. + // We populate m_edges array with the edges that originate in the + // vertices of the bunch. + for (int i = 0, n = bunch.size(); i < n; i++) { + int xyindex = bunch.get(i); + m_recycledSegIter.resetToVertex(xyindex);// the iterator is + // circular. + /* const */Segment seg1 = m_recycledSegIter.previousSegment(); + m_edges.add(createEdge_(seg1, xyindex, + m_recycledSegIter.getPathIndex(), true)); + m_recycledSegIter.nextSegment();// Need to skip one,because of the + // previousSegment call + // before (otherwise will get same segment again) + /* const */Segment seg2 = m_recycledSegIter.nextSegment(); + m_edges.add(createEdge_(seg2, xyindex, + m_recycledSegIter.getPathIndex(), false)); + } + + assert ((m_edges.size() & 1) == 0); // even size + + // Analyze the bunch edges for self intersections(the edges touch at the + // end points only at this stage of IsSimple) + // 1.sort the edges by angle between edge and the unit vector along axis + // x,using the cross product sign.Precondition:no overlaps occur at this + // stage. + + Collections.sort(m_edges, new EdgeComparerForSelfIntersection(this)); + + // 2.Analyze the bunch.There can be no edges between edges that share + // same vertex coordinates. + // We populate a doubly linked list with the edge indices and iterate + // over this list getting rid of the neighbouring pairs of vertices. + // The process is similar to peeling an onion. + // If the list becomes empty,there are no crossovers,otherwise,the + // geometry has cross-over. + int list = m_crossOverHelperList.getFirstList(); + + if (list == -1) + list = m_crossOverHelperList.createList(0); + + m_crossOverHelperList.reserveNodes(m_edges.size()); + + for (int i = 0, n = m_edges.size(); i < n; i++) { + m_crossOverHelperList.addElement(list, i); + } + + // Peel the onion + boolean bContinue = true; + int i1 = -1; + int i2 = -1; + while (bContinue) { + bContinue = false; + int listnode = m_crossOverHelperList.getFirst(list); + if (listnode == -1) + break; + + int nextnode = m_crossOverHelperList.getNext(listnode); + while (nextnode != -1) { + int edgeindex1 = m_crossOverHelperList.getData(listnode); + int edgeindex2 = m_crossOverHelperList.getData(nextnode); + i1 = m_edges.get(edgeindex1).m_vertexIndex; + i2 = m_edges.get(edgeindex2).m_vertexIndex; + if (i1 == i2) { + bContinue = true; + m_crossOverHelperList.deleteElement(list, listnode); + listnode = m_crossOverHelperList.getPrev(nextnode); + nextnode = m_crossOverHelperList.deleteElement(list, + nextnode); + if (nextnode == -1 || listnode == -1) + break; + else + continue; + } + listnode = nextnode; + nextnode = m_crossOverHelperList.getNext(listnode); + } + } + + int listSize = m_crossOverHelperList.getListSize(list); + m_crossOverHelperList.clear(list); + if (listSize > 0) { + // There is self-intersection here. + m_nonSimpleResult = new NonSimpleResult( + NonSimpleResult.Reason.CrossOver, i1, i2); + return false; + } + + // Recycle the bunch to save on object creation + for (int i = 0, n = bunch.size(); i < n; i++) { + recycleEdge_(m_edges.get(i)); + } + + m_edges.clear(); + return true; + } + + private boolean processBunchForRingOrientationTest_( + AttributeStreamOfInt32 bunch) { + m_dbgCounter++; + assert (bunch.size() > 0); + + // remove nodes that go out of scope + for (int i = 0, n = bunch.size(); i < n; i++) { + int xyindex = bunch.get(i); + int aetNode = m_xyToNode1.read(xyindex); + if (aetNode != Treap.nullNode()) {// We found that there is an edge + // in AET, attached to the + // xyindex vertex. This edge + // goes out of scope. Delete it + // from AET. + int edgeIndex = m_AET.getElement(aetNode); + m_FreeEdges.add(edgeIndex); + m_AET.deleteNode(aetNode, -1); + recycleEdge_(m_edges.get(edgeIndex)); + m_edges.set(edgeIndex, null); + m_xyToNode1.write(xyindex, Treap.nullNode()); + } + + aetNode = m_xyToNode2.read(xyindex); + if (aetNode != Treap.nullNode()) {// We found that there is an edge + // in AET, attached to the + // xyindex vertex. This edge + // goes out of scope. Delete it + // from AET. + int edgeIndex = m_AET.getElement(aetNode); + m_FreeEdges.add(edgeIndex); + m_AET.deleteNode(aetNode, -1); + recycleEdge_(m_edges.get(edgeIndex)); + m_edges.set(edgeIndex, null); + m_xyToNode2.write(xyindex, Treap.nullNode()); + } + } + + // add new edges to AET + for (int i = 0, n = bunch.size(); i < n; i++) { + int xyindex = bunch.get(i); + m_recycledSegIter.resetToVertex(xyindex);// the iterator is + // circular. + Segment seg1 = m_recycledSegIter.previousSegment();// this + // segment + // has + // end + // point + // at + // xyindex + if (seg1.getStartY() > seg1.getEndY())// do not allow horizontal + // segments in here + { + // get the top vertex index.We use it to determine what segments + // to get rid of. + int edgeTopIndex = m_recycledSegIter.getStartPointIndex(); + Edge edge = createEdge_(seg1, xyindex, + m_recycledSegIter.getPathIndex(), true); + int edgeIndex; + if (m_FreeEdges.size() > 0) { + edgeIndex = m_FreeEdges.getLast(); + m_FreeEdges.removeLast(); + m_edges.set(edgeIndex, edge); + } else { + edgeIndex = m_edges.size(); + m_edges.add(edge); + } + + int aetNode = m_AET.addElement(edgeIndex, -1); + // Remember AET nodes in the vertex to AET node maps. + if (m_xyToNode1.read(edgeTopIndex) == Treap.nullNode()) + m_xyToNode1.write(edgeTopIndex, aetNode); + else { + assert (m_xyToNode2.read(edgeTopIndex) == Treap.nullNode()); + m_xyToNode2.write(edgeTopIndex, aetNode); + } + + // If this edge belongs to a path that has not have direction + // figured out yet, + // add it to m_newEdges for post processing + if ((m_pathOrientations.read(m_recycledSegIter.getPathIndex()) & 3) == 0) + m_newEdges.add(aetNode); + } + + m_recycledSegIter.nextSegment();// Need to skip one,because of the + // previousSegment call + // before(otherwise will get same + // segment again) + // seg1 is invalid now + Segment seg2 = m_recycledSegIter.nextSegment(); + // start has to be lower than end for this one + if (seg2.getStartY() < seg2.getEndY())// do not allow horizontal + // segments in here + { + // get the top vertex index.We use it to determine what segments + // to get rid of. + int edgeTopIndex = m_recycledSegIter.getEndPointIndex(); + Edge edge = createEdge_(seg2, xyindex, + m_recycledSegIter.getPathIndex(), false); + int edgeIndex; + if (m_FreeEdges.size() > 0) { + edgeIndex = m_FreeEdges.getLast(); + m_FreeEdges.removeLast(); + m_edges.set(edgeIndex, edge); + } else { + edgeIndex = m_edges.size(); + m_edges.add(edge); + } + + int aetNode = m_AET.addElement(edgeIndex, -1); + if (m_xyToNode1.read(edgeTopIndex) == Treap.nullNode()) + m_xyToNode1.write(edgeTopIndex, aetNode); + else { + assert (m_xyToNode2.read(edgeTopIndex) == Treap.nullNode()); + m_xyToNode2.write(edgeTopIndex, aetNode); + } + + // If this edge belongs to a path that has not have direction + // figured out yet, + // add it to m_newEdges for post processing + if ((m_pathOrientations.read(m_recycledSegIter.getPathIndex()) & 3) == 0) + m_newEdges.add(aetNode); + } + } + + for (int i = 0, n = m_newEdges.size(); i < n + && m_unknownOrientationPathCount > 0; i++) { + int aetNode = m_newEdges.get(i); + int edgeIndexInitial = m_AET.getElement(aetNode); + Edge edgeInitial = m_edges.get(edgeIndexInitial); + int pathIndexInitial = edgeInitial.m_pathIndex; + int directionInitial = m_pathOrientations.read(pathIndexInitial); + if ((directionInitial & 3) == 0) { + int prevExteriorPath = -1; + int node = m_AET.getPrev(aetNode); + int prevNode = aetNode; + int oddEven = 0; + {// scope + int edgeIndex = -1; + Edge edge = null; + int pathIndex = -1; + int dir = 0; + // find the leftmost edge for which the ring orientation is + // known + while (node != Treap.nullNode()) { + edgeIndex = m_AET.getElement(node); + edge = m_edges.get(edgeIndex); + pathIndex = edge.m_pathIndex; + dir = m_pathOrientations.read(pathIndex); + if ((dir & 3) != 0) + break; + + prevNode = node; + node = m_AET.getPrev(node); + } + + if (node == Treap.nullNode()) {// if no edges have ring + // orientation known, then + // start + // from the left most and it + // has + // to be exterior ring. + oddEven = 1; + node = prevNode; + } else { + if ((dir & 3) == 1) { + prevExteriorPath = pathIndex; + } else { + prevExteriorPath = m_pathParentage.read(pathIndex); + } + + oddEven = (edge.getRightSide() != 0) ? 0 : 1; + node = m_AET.getNext(node); + } + } + + do { + int edgeIndex = m_AET.getElement(node); + Edge edge = m_edges.get(edgeIndex); + int pathIndex = edge.m_pathIndex; + int direction = m_pathOrientations.read(pathIndex); + if ((direction & 3) == 0) { + if (oddEven != edge.getRightSide()) { + m_nonSimpleResult = new NonSimpleResult( + NonSimpleResult.Reason.RingOrientation, + pathIndex, -1); + return false;// wrong ring orientation + } + + int dir = (oddEven != 0 && !edge.getReversed()) ? 1 : 2; + direction = (direction & 0xfc) | dir; + m_pathOrientations.write(pathIndex, dir); + if (dir == 2 + && m_nonSimpleResult.m_reason == NonSimpleResult.Reason.NotDetermined) { + // check that this hole has a correct parent + // exterior ring. + int parent = m_pathParentage.read(pathIndex); + if (parent != prevExteriorPath) { + m_nonSimpleResult = new NonSimpleResult( + NonSimpleResult.Reason.RingOrder, + pathIndex, -1); + if (m_bOGCRestrictions) + return false; + } + } + + m_unknownOrientationPathCount--; + if (m_unknownOrientationPathCount == 0)// if(!m_unknownOrientationPathCount) + return true; + } + + if ((direction & 3) == 1) { + prevExteriorPath = pathIndex; + } + + prevNode = node; + node = m_AET.getNext(node); + oddEven = oddEven != 0 ? 0 : 1; + } while (prevNode != aetNode); + } + } + + if (m_newEdges != null) + m_newEdges.clear(false); + else + m_newEdges = new AttributeStreamOfInt32(0); + return true; + } + + private Edge createEdge_(/* const */Segment seg, int xyindex, int pathIndex, + boolean bReversed) { + Edge edge; + Geometry.Type gt = seg.getType(); + if (gt == Geometry.Type.Line) { + edge = createEdgeLine_(seg); + } else { + throw new GeometryException("internal error"); // implement + // recycling for + // curves + } + edge.m_vertexIndex = xyindex; + edge.m_pathIndex = pathIndex; + edge.m_flags = 0; + edge.setReversed(bReversed); + + return edge; + } + + private Edge createEdgeLine_(/* const */Segment seg) { + Edge edge = null; + if (m_lineEdgesRecycle.size() > 0) { + int indexLast = m_lineEdgesRecycle.size() - 1; + edge = m_lineEdgesRecycle.get(indexLast); + m_lineEdgesRecycle.remove(indexLast); + seg.copyTo(edge.m_segment); + } else { + edge = new Edge(); + edge.m_segment = (Segment) Segment._clone(seg); + } + + return edge; + } + + private void recycleEdge_(/* const */Edge edge) { + Geometry.Type gt = edge.m_segment.getType(); + if (gt == Geometry.Type.Line) { + m_lineEdgesRecycle.add(edge); + } + } + + private static final class ClusterTestComparator extends Treap.Comparator { + OperatorSimplifyLocalHelper m_helper; + + ClusterTestComparator(OperatorSimplifyLocalHelper helper) { + m_helper = helper; + } + + @Override + int compare(/* const */Treap treap, int xy1, int node) { + int xy2 = treap.getElement(node); + double x1 = m_helper.m_xy.read(2 * xy1); + double x2 = m_helper.m_xy.read(2 * xy2); + double dx = x1 - x2; + return dx < 0 ? -1 : (dx > 0 ? 1 : 0); + } + } + + private static final class RingOrientationTestComparator extends + Treap.Comparator { + private OperatorSimplifyLocalHelper m_helper; + + RingOrientationTestComparator(OperatorSimplifyLocalHelper helper) { + m_helper = helper; + } + + @Override + int compare(/* const */Treap treap, int left, int node) { + int right = treap.getElement(node); + Edge edge1 = m_helper.m_edges.get(left); + Edge edge2 = m_helper.m_edges.get(right); + boolean bEdge1Reversed = edge1.getReversed(); + boolean bEdge2Reversed = edge2.getReversed(); + + double x1 = edge1.m_segment.intersectionOfYMonotonicWithAxisX( + m_helper.m_yScanline, 0); + double x2 = edge2.m_segment.intersectionOfYMonotonicWithAxisX( + m_helper.m_yScanline, 0); + + if (x1 == x2) { + // apparently these edges originate from same vertex and the + // scanline is on the vertex.move scanline a little. + double y1 = bEdge1Reversed ? edge1.m_segment.getStartY() + : edge1.m_segment.getEndY(); + double y2 = bEdge2Reversed ? edge2.m_segment.getStartY() + : edge2.m_segment.getEndY(); + double miny = Math.min(y1, y2); + double y = (miny - m_helper.m_yScanline) * 0.5 + + m_helper.m_yScanline; + if (y == m_helper.m_yScanline) { + // assert(0); //ST: not a bug. just curious to see this + // happens. + y = miny; // apparently, one of the segments is almost + // horizontal line. + } + x1 = edge1.m_segment.intersectionOfYMonotonicWithAxisX(y, 0); + x2 = edge2.m_segment.intersectionOfYMonotonicWithAxisX(y, 0); + assert (x1 != x2); + } + + return x1 < x2 ? -1 : (x1 > x2 ? 1 : 0); + } + } + + int multiPointIsSimpleAsFeature_() { + MultiVertexGeometryImpl multiVertexImpl = (MultiVertexGeometryImpl) m_geometry + ._getImpl(); + // sort lexicographically: by y,then by x, then by other attributes in + // the order. + // Go through the sorted list and make sure no points coincide exactly + // (no tolerance is taken into account). + int pointCount = multiVertexImpl.getPointCount(); + + AttributeStreamOfInt32 indices = new AttributeStreamOfInt32(0); + + for (int i = 0; i < pointCount; i++) { + indices.add(i); + } + + indices.Sort(0, pointCount, new MultiPointVertexComparer(this)); + + for (int i = 1; i < pointCount; i++) { + if (compareVerticesMultiPoint_(indices.get(i - 1), indices.get(i)) == 0) { + m_nonSimpleResult = new NonSimpleResult( + NonSimpleResult.Reason.Clustering, indices.get(i - 1), + indices.get(i)); + return 0;// points are coincident-simplify. + } + } + + return 2; + } + + int polylineIsSimpleAsFeature_() { + if (!checkStructure_()) + return 0; + // Non planar IsSimple. + // Go through all line segments and make sure no line segments are + // degenerate. + // Degenerate segment is the one which has its length shorter than + // tolerance or Z projection shorter than z tolerance. + return checkDegenerateSegments_(true) ? 2 : 0; + } + + int polygonIsSimpleAsFeature_() { + return isSimplePlanarImpl_(); + } + + MultiPoint multiPointSimplifyAsFeature_() { + MultiVertexGeometryImpl multiVertexImpl = (MultiVertexGeometryImpl) m_geometry + ._getImpl(); + // sort lexicographically:by y,then by x,then by other attributes in the + // order. + int pointCount = multiVertexImpl.getPointCount(); + assert (pointCount > 0); + + AttributeStreamOfInt32 indices = new AttributeStreamOfInt32(0); + + for (int i = 0; i < pointCount; i++) { + indices.add(i); + } + + indices.Sort(0, pointCount, new MultiPointVertexComparer2(this)); + + // Mark vertices that are unique + boolean[] indicesOut = new boolean[pointCount]; + + indicesOut[indices.get(0)] = true; + + for (int i = 1; i < pointCount; i++) { + int ind1 = indices.get(i - 1); + int ind2 = indices.get(i); + if (compareVerticesMultiPoint_(ind1, ind2) == 0) { + indicesOut[ind2] = false; + continue; + } + + indicesOut[ind2] = true; + } + + // get rid of non-unique vertices. + // We preserve the order of MultiPoint vertices.Among duplicate + // vertices,those that have + // higher index are deleted. + MultiPoint dst = (MultiPoint) m_geometry.createInstance(); + MultiPoint src = (MultiPoint) m_geometry; + int istart = 0; + int iend = 1; + for (int i = 0; i < pointCount; i++) { + if (indicesOut[i]) + iend = i + 1; + else { + if (istart < iend) { + dst.add(src, istart, iend); + } + + istart = i + 1; + } + } + + if (istart < iend) { + dst.add(src, istart, iend); + } + + ((MultiVertexGeometryImpl) dst._getImpl()).setIsSimple( + GeometryXSimple.Strong, m_toleranceSimplify, false); + return dst; + } + + Polyline polylineSimplifyAsFeature_() { + // Non planar simplify. + // Go through all line segments and make sure no line segments are + // degenerate. + // Degenerate segment is the one which has its length shorter than + // tolerance or Z projection shorter than z tolerance. + // The algorithm processes each path symmetrically from each end to + // ensure the result of simplify does not depend on the direction of the + // path. + + MultiPathImpl multiPathImpl = (MultiPathImpl) m_geometry._getImpl(); + SegmentIteratorImpl segIterFwd = multiPathImpl.querySegmentIterator(); + SegmentIteratorImpl segIterBwd = multiPathImpl.querySegmentIterator(); + Polyline dst = (Polyline) m_geometry.createInstance(); + Polyline src = (Polyline) m_geometry; + // Envelope2D env2D; + boolean bHasZ = multiPathImpl + .hasAttribute(VertexDescription.Semantics.Z); + double ztolerance = !bHasZ ? 0.0 : InternalUtils + .calculateZToleranceFromGeometry(m_sr, multiPathImpl, true); + AttributeStreamOfInt32 fwdStack = new AttributeStreamOfInt32(0); + AttributeStreamOfInt32 bwdStack = new AttributeStreamOfInt32(0); + fwdStack.reserve(multiPathImpl.getPointCount() / 2 + 1); + bwdStack.reserve(multiPathImpl.getPointCount() / 2 + 1); + while (segIterFwd.nextPath()) { + segIterBwd.nextPath(); + if (multiPathImpl.getPathSize(segIterFwd.getPathIndex()) < 2) + continue; + + segIterBwd.resetToLastSegment(); + double lengthFwd = 0; + double lengthBwd = 0; + boolean bFirst = true; + while (segIterFwd.hasNextSegment()) { + assert (segIterBwd.hasPreviousSegment()); + + /* const */Segment segFwd = segIterFwd.nextSegment(); + /* const */Segment segBwd = segIterBwd.previousSegment(); + + int idx1 = segIterFwd.getStartPointIndex(); + int idx2 = segIterBwd.getStartPointIndex(); + if (idx1 > idx2) + break; + + if (bFirst) { + // add the very first and the very last point indices + fwdStack.add(segIterFwd.getStartPointIndex());// first goes + // to + // fwdStack + bwdStack.add(segIterBwd.getEndPointIndex());// last goes to + // bwdStack + bFirst = false; + } + + { + int index0 = fwdStack.getLast(); + int index1 = segIterFwd.getEndPointIndex(); + if (index1 - index0 > 1) { + Point2D pt = new Point2D(); + pt.sub(multiPathImpl.getXY(index0), + multiPathImpl.getXY(index1)); + lengthFwd = pt.length(); + } else { + lengthFwd = segFwd.calculateLength2D(); + } + } + + { + int index0 = bwdStack.getLast(); + int index1 = segIterBwd.getStartPointIndex(); + if (index1 - index0 > 1) { + Point2D pt = new Point2D(); + pt.sub(multiPathImpl.getXY(index0), + multiPathImpl.getXY(index1)); + lengthBwd = pt.length(); + } else { + lengthBwd = segBwd.calculateLength2D(); + } + } + + if (lengthFwd > m_toleranceSimplify) { + fwdStack.add(segIterFwd.getEndPointIndex()); + lengthFwd = 0; + } else { + if (bHasZ) { + double z0 = multiPathImpl.getAttributeAsDbl( + VertexDescription.Semantics.Z, + fwdStack.getLast(), 0); + double z1 = segFwd.getEndAttributeAsDbl( + VertexDescription.Semantics.Z, 0); + if (Math.abs(z1 - z0) > ztolerance) { + fwdStack.add(segIterFwd.getEndPointIndex()); + lengthFwd = 0; + } + } + } + + if (lengthBwd > m_toleranceSimplify) { + bwdStack.add(segIterBwd.getStartPointIndex()); + lengthBwd = 0; + } else { + if (bHasZ) { + double z0 = multiPathImpl.getAttributeAsDbl( + VertexDescription.Semantics.Z, + bwdStack.getLast(), 0); + double z1 = segBwd.getEndAttributeAsDbl( + VertexDescription.Semantics.Z, 0); + if (Math.abs(z1 - z0) > ztolerance) { + bwdStack.add(segIterBwd.getStartPointIndex()); + lengthBwd = 0; + } + } + } + } + + // assert(fwdStack.getLast() <= bwdStack.getLast()); + if (fwdStack.getLast() < bwdStack.getLast()) { + // There is degenerate segment in the middle. Remove. + // If the path degenerate, this will make fwdStack.size() + + // bwdStack.size() < 2. + if (fwdStack.size() > bwdStack.size()) + fwdStack.removeLast(); + else + bwdStack.removeLast(); + } else if (fwdStack.getLast() == bwdStack.getLast()) { + bwdStack.removeLast(); + } else { + assert (fwdStack.getLast() - bwdStack.getLast() == 1); + bwdStack.removeLast(); + bwdStack.removeLast(); + } + + if (bwdStack.size() + fwdStack.size() >= 2) { + // Completely ignore the curves for now. + Point point = new Point(); + for (int i = 0, n = fwdStack.size(); i < n; i++) { + src.getPointByVal(fwdStack.get(i), point); + if (i == 0) + dst.startPath(point); + else + dst.lineTo(point); + } + + // int prevIdx = fwdStack.getLast(); + for (int i = bwdStack.size() - 1; i > 0; i--) { + src.getPointByVal(bwdStack.get(i), point); + dst.lineTo(point); + } + + if (src.isClosedPath(segIterFwd.getPathIndex())) { + dst.closePathWithLine(); + } else { + if (bwdStack.size() > 0) { + src.getPointByVal(bwdStack.get(0), point); + dst.lineTo(point); + } + } + } else { + // degenerate path won't be added + } + + if (fwdStack != null) + fwdStack.clear(false); + if (bwdStack != null) + bwdStack.clear(false); + } + + ((MultiVertexGeometryImpl) dst._getImpl()).setIsSimple( + GeometryXSimple.Strong, m_toleranceSimplify, false); + return dst; + } + + Polygon polygonSimplifyAsFeature_() { + return (Polygon) simplifyPlanar_(); + } + + MultiVertexGeometry simplifyPlanar_() { + // do clustering/cracking loop + // if (false) + // { + // ((MultiPathImpl)m_geometry._getImpl()).saveToTextFileDbg("c:/temp/_simplifyDbg0.txt"); + // } + + m_editShape = new EditShape(); + m_editShape.addGeometry(m_geometry); + + assert (m_knownSimpleResult != GeometryXSimple.Strong); + if (m_knownSimpleResult != GeometryXSimple.Weak) { + CrackAndCluster.execute(m_editShape, m_toleranceSimplify, + m_progressTracker); + } + + // if (false)// FIXME:do not forget to change this to if(false)!!! + // { + // OperatorSimplify simplify = (OperatorSimplify) + // (OperatorFactoryLocal.getInstance().getOperator(Operator.Type.Simplify)); + // NonSimpleResult nsres = new NonSimpleResult(); + // Geometry geometry = + // m_editShape.getGeometry(m_editShape.getFirstGeometry());// extract + // the result of simplify + // boolean res = simplify.isSimpleAsFeature(geometry, m_sr, true, nsres, + // null); + // if (false) + // { + // ((MultiPathImpl)geometry._getImpl()).saveToTextFileDbg("c:/temp/_simplifyDbg.txt"); + // } + // if (!res) + // { + // assert (nsres.m_reason.compareTo(NonSimpleResult.Reason.CrossOver) >= + // 0); + // } + // } + + if (m_geometry.getType().equals(Geometry.Type.Polygon)) { + Simplificator.execute(m_editShape, m_editShape.getFirstGeometry(), + m_knownSimpleResult); + } + + m_geometry = m_editShape.getGeometry(m_editShape.getFirstGeometry()); // extract + // the + // result + // of + // simplify + + if (m_geometry.getType().equals(Geometry.Type.Polygon)) + ((MultiPathImpl) m_geometry._getImpl())._updateOGCFlags(); + + // We have simplified the geometry using the given tolerance. Now mark + // the geometry as strong simple, + // So that the next call will not have to repeat these steps. + + ((MultiVertexGeometryImpl) m_geometry._getImpl()).setIsSimple( + GeometryXSimple.Strong, m_toleranceSimplify, false); + + return (MultiVertexGeometry) (m_geometry); + } + + NonSimpleResult m_nonSimpleResult; + + OperatorSimplifyLocalHelper(Geometry geometry, + SpatialReference spatialReference, int knownSimpleResult, + ProgressTracker progressTracker, boolean bOGCRestrictions) { + + m_description = geometry.getDescription(); + m_geometry = geometry; + m_sr = (SpatialReferenceImpl) spatialReference; + m_dbgCounter = 0; + m_toleranceIsSimple = InternalUtils.calculateToleranceFromGeometry( + m_sr, geometry, false); + m_toleranceSimplify = InternalUtils.calculateToleranceFromGeometry( + m_sr, geometry, true); + // m_toleranceCluster = m_toleranceSimplify * Math.sqrt(2.0) * 1.00001; + m_knownSimpleResult = knownSimpleResult; + m_attributeCount = m_description.getAttributeCount(); + m_edges = new ArrayList(); + m_lineEdgesRecycle = new ArrayList(); + m_crossOverHelperList = new IndexMultiDCList(); + m_AET = new Treap(); + m_nonSimpleResult = new NonSimpleResult(); + m_bOGCRestrictions = bOGCRestrictions; + m_bPlanarSimplify = m_bOGCRestrictions; + } + + // Returns 0 non-simple, 1 weak simple, 2 strong simple + /** + * The code is executed in the 2D plane only.Attributes are ignored. + * MultiPoint-check for clustering. Polyline -check for clustering and + * cracking. Polygon -check for clustering,cracking,absence of + * self-intersections,and correct ring ordering. + */ + static protected int isSimplePlanar(/* const */Geometry geometry, /* const */ + SpatialReference spatialReference, boolean bForce, + ProgressTracker progressTracker) { + assert (false); // this code is not called yet. + if (geometry.isEmpty()) + return 1; + Geometry.Type gt = geometry.getType(); + if (gt == Geometry.Type.Point) + return 1; + else if (gt == Geometry.Type.Envelope) { + Envelope2D env2D = new Envelope2D(); + geometry.queryEnvelope2D(env2D); + boolean bReturnValue = !env2D.isDegenerate(InternalUtils + .calculateToleranceFromGeometry(spatialReference, geometry, + false)); + return bReturnValue ? 1 : 0; + } else if (Geometry.isSegment(gt.value())) { + throw new GeometryException("internal error"); + // return seg.IsSimple(m_tolerance); + } else if (!Geometry.isMultiVertex(gt.value())) { + throw new GeometryException("internal error");// What else? + } + + double tolerance = InternalUtils.calculateToleranceFromGeometry( + spatialReference, geometry, false); + + double geomTolerance = 0; + int isSimple = ((MultiVertexGeometryImpl) geometry._getImpl()) + .getIsSimple(tolerance); + int knownSimpleResult = bForce ? -1 : isSimple; + // TODO: need to distinguish KnownSimple between SimpleAsFeature and + // SimplePlanar. The SimplePlanar implies SimpleAsFeature. + if (knownSimpleResult != -1) + return knownSimpleResult; + + if (knownSimpleResult == GeometryXSimple.Weak) { + assert (tolerance <= geomTolerance); + tolerance = geomTolerance;// OVERRIDE the tolerance. + } + + OperatorSimplifyLocalHelper helper = new OperatorSimplifyLocalHelper( + geometry, spatialReference, knownSimpleResult, progressTracker, + false); + knownSimpleResult = helper.isSimplePlanarImpl_(); + ((MultiVertexGeometryImpl) geometry._getImpl()).setIsSimple( + knownSimpleResult, tolerance, false); + return knownSimpleResult; + } + + /** + * Checks if Geometry is simple for storing in DB: + * + * MultiPoint:check that no points coincide.tolerance is ignored. + * Polyline:ensure there no segments degenerate segments. Polygon:Same as + * IsSimplePlanar. + */ + static protected int isSimpleAsFeature(/* const */Geometry geometry, /* const */ + SpatialReference spatialReference, boolean bForce, NonSimpleResult result, + ProgressTracker progressTracker) { + if (result != null) { + result.m_reason = NonSimpleResult.Reason.NotDetermined; + result.m_vertexIndex1 = -1; + result.m_vertexIndex2 = -1; + } + if (geometry.isEmpty()) + return 1; + Geometry.Type gt = geometry.getType(); + if (gt == Geometry.Type.Point) + return 1; + + double tolerance = InternalUtils.calculateToleranceFromGeometry( + spatialReference, geometry, false); + if (gt == Geometry.Type.Envelope) { + /* const */Envelope env = (Envelope) geometry; + Envelope2D env2D = new Envelope2D(); + env.queryEnvelope2D(env2D); + if (env2D.isDegenerate(tolerance)) { + if (result != null) { + result.m_reason = NonSimpleResult.Reason.DegenerateSegments; + result.m_vertexIndex1 = -1; + result.m_vertexIndex2 = -1; + } + return 0; + } + return 1; + } else if (Geometry.isSegment(gt.value())) { + /* const */Segment seg = (Segment) geometry; + Polyline polyline = new Polyline(seg.getDescription()); + polyline.addSegment(seg, true); + return isSimpleAsFeature(polyline, spatialReference, bForce, + result, progressTracker); + } + + // double geomTolerance = 0; + int isSimple = ((MultiVertexGeometryImpl) geometry._getImpl()) + .getIsSimple(tolerance); + int knownSimpleResult = bForce ? -1 : isSimple; + // TODO: need to distinguish KnownSimple between SimpleAsFeature and + // SimplePlanar. + // From the first sight it seems the SimplePlanar implies + // SimpleAsFeature. + if (knownSimpleResult != -1) + return knownSimpleResult; + + OperatorSimplifyLocalHelper helper = new OperatorSimplifyLocalHelper( + geometry, spatialReference, knownSimpleResult, progressTracker, + false); + + if (gt == Geometry.Type.MultiPoint) { + knownSimpleResult = helper.multiPointIsSimpleAsFeature_(); + } else if (gt == Geometry.Type.Polyline) { + knownSimpleResult = helper.polylineIsSimpleAsFeature_(); + } else if (gt == Geometry.Type.Polygon) { + knownSimpleResult = helper.polygonIsSimpleAsFeature_(); + } else { + throw new GeometryException("internal error");// what else? + } + + ((MultiVertexGeometryImpl) (geometry._getImpl())).setIsSimple( + knownSimpleResult, tolerance, false); + if (result != null && knownSimpleResult == 0) + result.Assign(helper.m_nonSimpleResult); + return knownSimpleResult; + } + + static int isSimpleOGC(/* const */Geometry geometry, /* const */ + SpatialReference spatialReference, boolean bForce, NonSimpleResult result, + ProgressTracker progressTracker) { + if (result != null) { + result.m_reason = NonSimpleResult.Reason.NotDetermined; + result.m_vertexIndex1 = -1; + result.m_vertexIndex2 = -1; + } + if (geometry.isEmpty()) + return 1; + Geometry.Type gt = geometry.getType(); + if (gt == Geometry.Type.Point) + return 1; + + double tolerance = InternalUtils.calculateToleranceFromGeometry( + spatialReference, geometry, false); + if (gt == Geometry.Type.Envelope) { + /* const */Envelope env = (Envelope) geometry; + Envelope2D env2D = new Envelope2D(); + env.queryEnvelope2D(env2D); + if (env2D.isDegenerate(tolerance)) { + if (result != null) { + result.m_reason = NonSimpleResult.Reason.DegenerateSegments; + result.m_vertexIndex1 = -1; + result.m_vertexIndex2 = -1; + } + return 0; + } + return 1; + } else if (Geometry.isSegment(gt.value())) { + /* const */Segment seg = (Segment) geometry; + Polyline polyline = new Polyline(seg.getDescription()); + polyline.addSegment(seg, true); + return isSimpleAsFeature(polyline, spatialReference, bForce, + result, progressTracker); + } + + int knownSimpleResult = -1; + + OperatorSimplifyLocalHelper helper = new OperatorSimplifyLocalHelper( + geometry, spatialReference, knownSimpleResult, progressTracker, + true); + + if (gt == Geometry.Type.MultiPoint || gt == Geometry.Type.Polyline + || gt == Geometry.Type.Polygon) { + knownSimpleResult = helper.isSimplePlanarImpl_(); + } else { + throw new GeometryException("internal error");// what else? + } + + if (result != null) + result.Assign(helper.m_nonSimpleResult); + + return knownSimpleResult; + } + + /** + * Simplifies geometries for storing in DB: + * + * MultiPoint:check that no points coincide.tolerance is ignored. + * Polyline:ensure there no segments degenerate segments. Polygon:cracks and + * clusters using cluster tolerance and resolves all self intersections, + * orients rings properly and arranges the rings in the OGC order. + * + * Returns simplified geometry. + */ + static protected Geometry simplifyAsFeature(/* const */Geometry geometry, /* const */ + SpatialReference spatialReference, boolean bForce, + ProgressTracker progressTracker) { + if (geometry.isEmpty()) + return geometry; + Geometry.Type gt = geometry.getType(); + if (gt == Geometry.Type.Point) + return geometry; + + double tolerance = InternalUtils.calculateToleranceFromGeometry( + spatialReference, geometry, false); + if (gt == Geometry.Type.Envelope) { + Envelope env = (Envelope) geometry; + Envelope2D env2D = new Envelope2D(); + env.queryEnvelope2D(env2D); + if (env2D.isDegenerate(tolerance)) { + return (Geometry) (env.createInstance()); // return empty + // geometry + } + return geometry; + } else if (Geometry.isSegment(gt.value())) { + Segment seg = (Segment) geometry; + Polyline polyline = new Polyline(seg.getDescription()); + polyline.addSegment(seg, true); + return simplifyAsFeature(polyline, spatialReference, bForce, + progressTracker); + } + + double geomTolerance = 0; + int isSimple = ((MultiVertexGeometryImpl) geometry._getImpl()) + .getIsSimple(tolerance); + int knownSimpleResult = bForce ? GeometryXSimple.Unknown : isSimple; + + // TODO: need to distinguish KnownSimple between SimpleAsFeature and + // SimplePlanar. + // From the first sight it seems the SimplePlanar implies + // SimpleAsFeature. + if (knownSimpleResult == GeometryXSimple.Strong) { + return geometry; + } + + OperatorSimplifyLocalHelper helper = new OperatorSimplifyLocalHelper( + geometry, spatialReference, knownSimpleResult, progressTracker, + false); + + Geometry result; + + if (gt == Geometry.Type.MultiPoint) { + result = (Geometry) (helper.multiPointSimplifyAsFeature_()); + } else if (gt == Geometry.Type.Polyline) { + result = (Geometry) (helper.polylineSimplifyAsFeature_()); + } else if (gt == Geometry.Type.Polygon) { + result = (Geometry) (helper.polygonSimplifyAsFeature_()); + } else { + throw new GeometryException("internal error"); // what else? + } + + return result; + } + + /** + * Simplifies geometries for storing in OGC format: + * + * MultiPoint:check that no points coincide.tolerance is ignored. + * Polyline:ensure there no segments degenerate segments. Polygon:cracks and + * clusters using cluster tolerance and resolves all self intersections, + * orients rings properly and arranges the rings in the OGC order. + * + * Returns simplified geometry. + */ + static Geometry simplifyOGC(/* const */Geometry geometry, /* const */ + SpatialReference spatialReference, boolean bForce, + ProgressTracker progressTracker) { + if (geometry.isEmpty()) + return geometry; + Geometry.Type gt = geometry.getType(); + if (gt == Geometry.Type.Point) + return geometry; + + double tolerance = InternalUtils.calculateToleranceFromGeometry( + spatialReference, geometry, false); + if (gt == Geometry.Type.Envelope) { + Envelope env = (Envelope) geometry; + Envelope2D env2D = new Envelope2D(); + env.queryEnvelope2D(env2D); + if (env2D.isDegenerate(tolerance)) { + return (Geometry) (env.createInstance()); // return empty + // geometry + } + return geometry; + } else if (Geometry.isSegment(gt.value())) { + Segment seg = (Segment) geometry; + Polyline polyline = new Polyline(seg.getDescription()); + polyline.addSegment(seg, true); + return simplifyOGC(polyline, spatialReference, bForce, + progressTracker); + } + + if (!Geometry.isMultiVertex(gt.value())) { + throw new GeometryException("internal error"); // what else? + } + + MultiVertexGeometry result = TopologicalOperations.planarSimplify( + (MultiVertexGeometry) geometry, tolerance, false, false, + progressTracker); + + return result; + } + + private int compareVertices_(int i1, int i2, boolean get_paths) { + if (i1 == i2) + return 0; + + int pair1 = m_pairs.get(i1); + int pair2 = m_pairs.get(i2); + int xy1 = pair1 >> 1; + int xy2 = pair2 >> 1; + Point2D pt1 = new Point2D(); + Point2D pt2 = new Point2D(); + m_xy.read(2 * xy1, pt1); + pt1.y += (((pair1 & 1) != 0) ? m_toleranceIsSimple + : -m_toleranceIsSimple); + m_xy.read(2 * xy2, pt2); + pt2.y += (((pair2 & 1) != 0) ? m_toleranceIsSimple + : -m_toleranceIsSimple); + int res = pt1.compare(pt2); + if (res == 0 && get_paths) { + int di = m_paths_for_OGC_tests.get(xy1) + - m_paths_for_OGC_tests.get(xy2); + return di < 0 ? -1 : di > 0 ? 1 : 0; + } + return res; + } + + private static final class VertexComparer extends + AttributeStreamOfInt32.IntComparator { + OperatorSimplifyLocalHelper parent; + boolean get_paths; + + VertexComparer(OperatorSimplifyLocalHelper parent_, boolean get_paths_) { + parent = parent_; + get_paths = get_paths_; + } + + @Override + public int compare(int i1, int i2) { + return parent.compareVertices_(i1, i2, get_paths); + } + } + + private static final class IndexSorter extends ClassicSort { + OperatorSimplifyLocalHelper parent; + private boolean get_paths; + private Point2D pt1_dummy = new Point2D(); + + IndexSorter(OperatorSimplifyLocalHelper parent_, boolean get_paths_) { + parent = parent_; + get_paths = get_paths_; + } + + @Override + public void userSort(int begin, int end, AttributeStreamOfInt32 indices) { + indices.Sort(begin, end, new VertexComparer(parent, get_paths)); + } + + @Override + public double getValue(int index) /* const */ + { + int pair = parent.m_pairs.get(index); + int xy1 = pair >> 1; + parent.m_xy.read(2 * xy1, pt1_dummy); + double y = pt1_dummy.y + + (((pair & 1) != 0) ? parent.m_toleranceIsSimple + : -parent.m_toleranceIsSimple); + return y; + } + } + + private int compareVerticesMultiPoint_(int i1, int i2) { + if (i1 == i2) + return 0; + MultiVertexGeometryImpl multiVertexImpl = (MultiVertexGeometryImpl) m_geometry + ._getImpl(); + Point2D pt1 = multiVertexImpl.getXY(i1); + Point2D pt2 = multiVertexImpl.getXY(i2); + + if (pt1.x < pt2.x) + return -1; + if (pt1.x > pt2.x) + return 1; + if (pt1.y < pt2.y) + return -1; + if (pt1.y > pt2.y) + return 1; + + for (int attrib = 1; attrib < m_attributeCount; attrib++) { + int semantics = m_description.getSemantics(attrib); + int nords = VertexDescription.getComponentCount(semantics); + for (int ord = 0; ord < nords; ord++) { + double v1 = multiVertexImpl.getAttributeAsDbl(semantics, i1, + ord); + double v2 = multiVertexImpl.getAttributeAsDbl(semantics, i2, + ord); + if (v1 < v2) + return -1; + if (v1 > v2) + return 1; + } + } + + return 0; + } + + private int compareVerticesMultiPoint2_(int i1, int i2) { + int res = compareVerticesMultiPoint_(i1, i2); + if (res == 0) + return i1 < i2 ? -1 : 1; + else + return res; + } + + private static final class EdgeComparerForSelfIntersection implements + Comparator { + OperatorSimplifyLocalHelper parent; + + EdgeComparerForSelfIntersection(OperatorSimplifyLocalHelper parent_) { + parent = parent_; + } + + // Recall that the total ordering [<] induced by compare satisfies e1 + // [<] e2 if and only if compare(e1, e2) < 0. + + @Override + public int compare(Edge e1, Edge e2) { + // C++ style with bool operator() would be + // return parent->edgeAngleCompare_(*e1,*e2) < 0; + + return parent.edgeAngleCompare_(e1, e2); + } + } + + private static final class MultiPointVertexComparer extends + AttributeStreamOfInt32.IntComparator { + OperatorSimplifyLocalHelper parent; + + MultiPointVertexComparer(OperatorSimplifyLocalHelper parent_) { + parent = parent_; + } + + @Override + public int compare(int i1, int i2) { + return parent.compareVerticesMultiPoint_(i1, i2); + } + } + + private static final class MultiPointVertexComparer2 extends + AttributeStreamOfInt32.IntComparator { + OperatorSimplifyLocalHelper parent; + + MultiPointVertexComparer2(OperatorSimplifyLocalHelper parent_) { + parent = parent_; + } + + @Override + public int compare(int i1, int i2) { + return parent.compareVerticesMultiPoint2_(i1, i2); + } + } + + // compares angles between two edges + private int edgeAngleCompare_(/* const */Edge edge1, /* const */Edge edge2) { + if (edge1.equals(edge2)) + return 0; + + Point2D v1 = edge1.m_segment._getTangent(edge1.getReversed() ? 1.0 + : 0.0); + if (edge1.getReversed()) + v1.negate(); + Point2D v2 = edge2.m_segment._getTangent(edge2.getReversed() ? 1.0 + : 0.0); + if (edge2.getReversed()) + v2.negate(); + + int q1 = v1._getQuarter(); + int q2 = v2._getQuarter(); + + if (q2 == q1) { + double cross = v1.crossProduct(v2); + double crossError = 4 * NumberUtils.doubleEps() + * (Math.abs(v2.x * v1.y) + Math.abs(v2.y * v1.x)); + if (Math.abs(cross) <= crossError) { + cross--; // To avoid warning of "this line has no effect" from + // cross = cross. + cross++; + } + assert (Math.abs(cross) > crossError); + return cross < 0 ? 1 : (cross > 0 ? -1 : 0); + } else { + return q1 < q2 ? -1 : 1; + } + } +}; diff --git a/src/com/esri/core/geometry/OperatorSimplifyLocalOGC.java b/src/com/esri/core/geometry/OperatorSimplifyLocalOGC.java new file mode 100644 index 00000000..8f6bce0f --- /dev/null +++ b/src/com/esri/core/geometry/OperatorSimplifyLocalOGC.java @@ -0,0 +1,56 @@ +/* + Copyright 1995-2013 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ +package com.esri.core.geometry; + +class OperatorSimplifyLocalOGC extends OperatorSimplifyOGC { + + // Reviewed vs. Feb 8 2011 + @Override + public GeometryCursor execute(GeometryCursor geoms, + SpatialReference spatialRef, boolean bForceSimplify, + ProgressTracker progressTracker) { + return new OperatorSimplifyCursorOGC(geoms, spatialRef, bForceSimplify, + progressTracker); + } + + @Override + public boolean isSimpleOGC(Geometry geom, SpatialReference spatialRef, + boolean bForceTest, NonSimpleResult result, + ProgressTracker progressTracker) { + int res = OperatorSimplifyLocalHelper.isSimpleOGC(geom, spatialRef, + bForceTest, result, progressTracker); + return res > 0; + } + + // Reviewed vs. Feb 8 2011 + @Override + public Geometry execute(Geometry geom, SpatialReference spatialRef, + boolean bForceSimplify, ProgressTracker progressTracker) { + SimpleGeometryCursor inputCursor = new SimpleGeometryCursor(geom); + GeometryCursor outputCursor = execute(inputCursor, spatialRef, + bForceSimplify, progressTracker); + + return outputCursor.next(); + } +} diff --git a/src/com/esri/core/geometry/OperatorSimplifyOGC.java b/src/com/esri/core/geometry/OperatorSimplifyOGC.java new file mode 100644 index 00000000..aaf89f50 --- /dev/null +++ b/src/com/esri/core/geometry/OperatorSimplifyOGC.java @@ -0,0 +1,69 @@ +/* + Copyright 1995-2013 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ +package com.esri.core.geometry; + +/** + * Simplifies geometry or determines if geometry is simple. Tries to follow OGC specification. + * Uses tolerance to determine equal vertices or points of intersection. + * + */ +public abstract class OperatorSimplifyOGC extends Operator { + @Override + public Operator.Type getType() { + return Operator.Type.SimplifyOGC; + } + + /** + *Tests if the Geometry is simple for OGC spec. + *@param geom The Geometry to be tested. + *@param bForceTest When True, the Geometry will be tested regardless of the IsKnownSimple flag. + * + *Note: As other methods in the OperatorSimplify, this method uses tolerance from the spatial reference. + *Points that are within the tolerance are considered equal. + */ + public abstract boolean isSimpleOGC(Geometry geom, + SpatialReference spatialRef, boolean bForceTest, + NonSimpleResult result, ProgressTracker progressTracker); + + /** + *Performs the Simplify operation on the geometry set. + *@return Returns a GeometryCursor of simplified geometries. + */ + public abstract GeometryCursor execute(GeometryCursor geoms, + SpatialReference sr, boolean bForceSimplify, + ProgressTracker progressTracker); + + /** + *Performs the Simplify operation on a Geometry + *@return Returns a simple Geometry. + */ + public abstract Geometry execute(Geometry geom, SpatialReference sr, + boolean bForceSimplify, ProgressTracker progressTracker); + + public static OperatorSimplifyOGC local() { + return (OperatorSimplifyOGC) OperatorFactoryLocal.getInstance() + .getOperator(Type.SimplifyOGC); + } + +} diff --git a/src/com/esri/core/geometry/OperatorSymmetricDifference.java b/src/com/esri/core/geometry/OperatorSymmetricDifference.java new file mode 100644 index 00000000..b21f8745 --- /dev/null +++ b/src/com/esri/core/geometry/OperatorSymmetricDifference.java @@ -0,0 +1,65 @@ +/* + Copyright 1995-2013 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ +package com.esri.core.geometry; + +import com.esri.core.geometry.Operator.Type; + +/** + * Symmetric difference (XOR) operation between geometries. + * + */ +public abstract class OperatorSymmetricDifference extends Operator { + @Override + public Type getType() { + return Type.Difference; + } + + /** + *Performs the Symmetric Difference operation on the geometry set. + *@param inputGeometries is the set of Geometry instances to be XOR'd by rightGeometry. + *@param rightGeometry is the Geometry being XOR'd with the inputGeometies. + *@return Returns the result of the symmetric difference. + * + *The operator XOR's every geometry in inputGeometries with rightGeometry. + */ + public abstract GeometryCursor execute(GeometryCursor inputGeometries, + GeometryCursor rightGeometry, SpatialReference sr, + ProgressTracker progressTracker); + + /** + *Performs the Symmetric Difference operation on the two geometries. + *@param leftGeometry is one of the Geometry instances in the XOR operation. + *@param rightGeometry is one of the Geometry instances in the XOR operation. + *@return Returns the result of the symmetric difference. + */ + public abstract Geometry execute(Geometry leftGeometry, + Geometry rightGeometry, SpatialReference sr, + ProgressTracker progressTracker); + + public static OperatorSymmetricDifference local() { + return (OperatorSymmetricDifference) OperatorFactoryLocal.getInstance() + .getOperator(Type.SymmetricDifference); + } + +} diff --git a/src/com/esri/core/geometry/OperatorSymmetricDifferenceCursor.java b/src/com/esri/core/geometry/OperatorSymmetricDifferenceCursor.java new file mode 100644 index 00000000..92450ce3 --- /dev/null +++ b/src/com/esri/core/geometry/OperatorSymmetricDifferenceCursor.java @@ -0,0 +1,65 @@ +/* + Copyright 1995-2013 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ +package com.esri.core.geometry; + +class OperatorSymmetricDifferenceCursor extends GeometryCursor { + + GeometryCursor m_inputGeoms; + ProgressTracker m_progress_tracker; + SpatialReference m_spatial_reference; + Geometry m_rightGeom; + int m_index; + boolean m_bEmpty; + + OperatorSymmetricDifferenceCursor(GeometryCursor inputGeoms, + GeometryCursor rightGeom, SpatialReference sr, + ProgressTracker progress_tracker) { + m_bEmpty = rightGeom == null; + m_index = -1; + m_inputGeoms = inputGeoms; + m_spatial_reference = sr; + m_rightGeom = rightGeom.next(); + m_progress_tracker = progress_tracker; + } + + @Override + public Geometry next() { + if (m_bEmpty) + return null; + + Geometry leftGeom; + if ((leftGeom = m_inputGeoms.next()) != null) { + m_index = m_inputGeoms.getGeometryID(); + return OperatorSymmetricDifferenceLocal.symmetricDifference( + leftGeom, m_rightGeom, m_spatial_reference, + m_progress_tracker); + } + return null; + } + + @Override + public int getGeometryID() { + return m_index; + } +} diff --git a/src/com/esri/core/geometry/OperatorSymmetricDifferenceLocal.java b/src/com/esri/core/geometry/OperatorSymmetricDifferenceLocal.java new file mode 100644 index 00000000..e5de43db --- /dev/null +++ b/src/com/esri/core/geometry/OperatorSymmetricDifferenceLocal.java @@ -0,0 +1,168 @@ +/* + Copyright 1995-2013 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ +package com.esri.core.geometry; + +class OperatorSymmetricDifferenceLocal extends OperatorSymmetricDifference { + + @Override + public GeometryCursor execute(GeometryCursor inputGeometries, + GeometryCursor rightGeometry, SpatialReference sr, + ProgressTracker progressTracker) { + return new OperatorSymmetricDifferenceCursor(inputGeometries, + rightGeometry, sr, progressTracker); + } + + @Override + public Geometry execute(Geometry leftGeometry, Geometry rightGeometry, + SpatialReference sr, ProgressTracker progressTracker) { + SimpleGeometryCursor leftGeomCurs = new SimpleGeometryCursor( + leftGeometry); + SimpleGeometryCursor rightGeomCurs = new SimpleGeometryCursor( + rightGeometry); + GeometryCursor geometryCursor = execute(leftGeomCurs, rightGeomCurs, + sr, progressTracker); + return geometryCursor.next(); + } + + static Geometry symmetricDifference(Geometry geometry_a, + Geometry geometry_b, SpatialReference spatial_reference, + ProgressTracker progress_tracker) { + int dim_a = geometry_a.getDimension(); + int dim_b = geometry_b.getDimension(); + + if (geometry_a.isEmpty() && geometry_b.isEmpty()) + return dim_a > dim_b ? geometry_a : geometry_b; + + if (geometry_a.isEmpty()) + return geometry_b; + if (geometry_b.isEmpty()) + return geometry_a; + + Envelope2D env_a = new Envelope2D(), env_b = new Envelope2D(), env_merged = new Envelope2D(); + geometry_a.queryEnvelope2D(env_a); + geometry_b.queryEnvelope2D(env_b); + env_merged.setCoords(env_a); + env_merged.merge(env_b); + + double tolerance = InternalUtils.calculateToleranceFromGeometry( + spatial_reference, env_merged, false); + + int type_a = geometry_a.getType().value(); + int type_b = geometry_b.getType().value(); + + if (type_a == Geometry.GeometryType.Point + && type_b == Geometry.GeometryType.Point) + return pointSymDiffPoint_((Point) (geometry_a), + (Point) (geometry_b), tolerance, progress_tracker); + + if (type_a != type_b) { + if (dim_a > 0 || dim_b > 0) + return dim_a > dim_b ? geometry_a : geometry_b; + + // Multi_point/Point case + + if (type_a == Geometry.GeometryType.MultiPoint) + return multiPointSymDiffPoint_((MultiPoint) (geometry_a), + (Point) (geometry_b), tolerance, progress_tracker); + + return multiPointSymDiffPoint_((MultiPoint) (geometry_b), + (Point) (geometry_a), tolerance, progress_tracker); + } + + return TopologicalOperations.symmetricDifference(geometry_a, + geometry_b, spatial_reference, progress_tracker); + } + + static Geometry pointSymDiffPoint_(Point point_a, Point point_b, + double tolerance, ProgressTracker progress_tracker) { + double tolerance_cluster = tolerance * Math.sqrt(2.0) * 1.00001; + double tolerance_cluster_sq = tolerance_cluster * tolerance_cluster; + + Point2D pt_a = point_a.getXY(); + Point2D pt_b = point_b.getXY(); + + MultiPoint multi_point = new MultiPoint(point_a.getDescription()); + + if (Point2D.sqrDistance(pt_a, pt_b) > tolerance_cluster_sq) { + multi_point.add(point_a); + multi_point.add(point_b); + } + + return multi_point; + } + + static Geometry multiPointSymDiffPoint_(MultiPoint multi_point, + Point point, double tolerance, ProgressTracker progress_tracker) { + MultiPointImpl multipointImpl = (MultiPointImpl) (multi_point + ._getImpl()); + AttributeStreamOfDbl position = (AttributeStreamOfDbl) (multipointImpl + .getAttributeStreamRef(VertexDescription.Semantics.POSITION)); + int point_count = multi_point.getPointCount(); + Point2D point2D = point.getXY(); + + MultiPoint new_multipoint = (MultiPoint) (multi_point.createInstance()); + double tolerance_cluster = tolerance * Math.sqrt(2.0) * 1.00001; + + Envelope2D env = new Envelope2D(); + multi_point.queryEnvelope2D(env); + env.inflate(tolerance_cluster, tolerance_cluster); + + if (env.contains(point2D)) { + double tolerance_cluster_sq = tolerance_cluster * tolerance_cluster; + + boolean b_found_covered = false; + boolean[] covered = new boolean[point_count]; + for (int i = 0; i < point_count; i++) + covered[i] = false; + + for (int i = 0; i < point_count; i++) { + double x = position.read(2 * i); + double y = position.read(2 * i + 1); + + double dx = x - point2D.x; + double dy = y - point2D.y; + + if (dx * dx + dy * dy <= tolerance_cluster_sq) { + b_found_covered = true; + covered[i] = true; + } + } + + if (!b_found_covered) { + new_multipoint.add(multi_point, 0, point_count); + new_multipoint.add(point); + } else { + for (int i = 0; i < point_count; i++) { + if (!covered[i]) + new_multipoint.add(multi_point, i, i + 1); + } + } + } else { + new_multipoint.add(multi_point, 0, point_count); + new_multipoint.add(point); + } + + return new_multipoint; + } +} diff --git a/src/com/esri/core/geometry/OperatorTouches.java b/src/com/esri/core/geometry/OperatorTouches.java new file mode 100644 index 00000000..a98de285 --- /dev/null +++ b/src/com/esri/core/geometry/OperatorTouches.java @@ -0,0 +1,44 @@ +/* + Copyright 1995-2013 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ + +package com.esri.core.geometry; + +import com.esri.core.geometry.Operator.Type; + +/** + * Relational operation Touches. + * + */ +public abstract class OperatorTouches extends OperatorSimpleRelation { + @Override + public Type getType() { + return Type.Touches; + } + + public static OperatorTouches local() { + return (OperatorTouches) OperatorFactoryLocal.getInstance() + .getOperator(Type.Touches); + } + +} diff --git a/src/com/esri/core/geometry/OperatorTouchesLocal.java b/src/com/esri/core/geometry/OperatorTouchesLocal.java new file mode 100644 index 00000000..f18f3541 --- /dev/null +++ b/src/com/esri/core/geometry/OperatorTouchesLocal.java @@ -0,0 +1,35 @@ +/* + Copyright 1995-2013 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ + +package com.esri.core.geometry; + +class OperatorTouchesLocal extends OperatorTouches { + @Override + public boolean execute(Geometry inputGeom1, Geometry inputGeom2, + SpatialReference sr, ProgressTracker progressTracker) { + return RelationalOperations.relate(inputGeom1, inputGeom2, sr, + RelationalOperations.Relation.touches, progressTracker); + } + +} diff --git a/src/com/esri/core/geometry/OperatorUnion.java b/src/com/esri/core/geometry/OperatorUnion.java new file mode 100644 index 00000000..c7cf49b4 --- /dev/null +++ b/src/com/esri/core/geometry/OperatorUnion.java @@ -0,0 +1,61 @@ +/* + Copyright 1995-2013 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ + +package com.esri.core.geometry; + +import com.esri.core.geometry.Operator.Type; + +/** + * + * Union of geometries. + * + */ +public abstract class OperatorUnion extends Operator { + @Override + public Type getType() { + return Type.Union; + } + + /** + *Performs the Topological Union operation on the geometry set. + *@param inputGeometries is the set of Geometry instances to be unioned. + * + */ + public abstract GeometryCursor execute(GeometryCursor inputGeometries, + SpatialReference sr, ProgressTracker progressTracker); + + /** + *Performs the Topological Union operation on two geometries. + *@param geom1 and geom2 are the geometry instances to be unioned. + * + */ + public abstract Geometry execute(Geometry geom1, Geometry geom2, + SpatialReference sr, ProgressTracker progressTracker); + + public static OperatorUnion local() { + return (OperatorUnion) OperatorFactoryLocal.getInstance().getOperator( + Type.Union); + } + +} diff --git a/src/com/esri/core/geometry/OperatorUnionCursor.java b/src/com/esri/core/geometry/OperatorUnionCursor.java new file mode 100644 index 00000000..3ef0323f --- /dev/null +++ b/src/com/esri/core/geometry/OperatorUnionCursor.java @@ -0,0 +1,360 @@ +/* + Copyright 1995-2013 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ + +package com.esri.core.geometry; + +import java.util.ArrayList; + +class OperatorUnionCursor extends GeometryCursor { + + private GeometryCursor m_inputGeoms; + private ProgressTracker m_progress_tracker; + private SpatialReferenceImpl m_spatial_reference; + private int m_index; + private boolean m_b_done; + + OperatorUnionCursor(GeometryCursor inputGeoms1, SpatialReference sr, + ProgressTracker progress_tracker) { + m_index = -1; + m_b_done = false; + m_inputGeoms = inputGeoms1; + m_spatial_reference = (SpatialReferenceImpl) (sr); + m_progress_tracker = progress_tracker; + } + + @Override + public Geometry next() { + return dissolve_(); + } + + @Override + public int getGeometryID() { + return m_index; + } + + private Geometry dissolve_() { + if (m_b_done) + return null; + + m_b_done = true;// m_b_done is added to avoid calling + // m_inputGeoms->next() second time after it returned + // NULL. + // Otherwise, unsupported use patternes could be produced. + + m_index = m_inputGeoms.getGeometryID(); + + // Geometries are placed into the unionBins. + // Each bin stores geometries of certain size range. + // The bin number is calculated as log(N), where N is the number of + // vertices in geoemtry and the log is to a + // certain base (now it is 4). + ArrayList> unionBins = new ArrayList>( + 0); + AttributeStreamOfInt32 binSizes = new AttributeStreamOfInt32(0); + unionBins.ensureCapacity(128); + binSizes.reserve(128); + + for (int i = 0; i < 16; i++) + unionBins.add(null); + + int dimension = -1; + boolean bFinished = false; + int totalToUnion = 0; + int totalVertexCount = 0; + int binVertexThreshold = 10000; + boolean dissolved_something = false; + + ArrayList batchToUnion = new ArrayList(0);// a batch + // of + // geometries + // to + // union + batchToUnion.ensureCapacity(32); + + boolean bLocalDone = false; + while (!bLocalDone) { + if (!bFinished) { + Geometry geom = m_inputGeoms.next(); + + if ((m_progress_tracker != null) + && !(m_progress_tracker.progress(-1, -1))) + throw new RuntimeException("user_canceled"); + + if (geom != null) { + if (geom.getDimension() > dimension)// only master can + // change dimension var. + { + GeomPair pair = new GeomPair(); + pair.init(); + pair.geom = geom; + pair.dim = geom.getDimension(); + int sz = getVertexCount_(geom); + pair.vertex_count = sz; + int level = getLevel_(sz); + + { + unionBins.clear(); + int resize = Math.max(16, level + 1); + for (int i = 0; i < resize; i++) + unionBins.add(null); + binSizes.resize(resize, 0); + unionBins.set(level, new ArrayList(0)); + unionBins.get(level).add(pair); + binSizes.set(level, sz); + totalToUnion = 1; + totalVertexCount = sz; + dimension = geom.getDimension(); + } + } else if (!geom.isEmpty() + && geom.getDimension() == dimension) { + GeomPair pair = new GeomPair(); + pair.init(); + pair.geom = geom; + pair.dim = geom.getDimension(); + int sz = getVertexCount_(geom); + pair.vertex_count = sz; + int level = getLevel_(sz); + + { + int resize = Math.max(unionBins.size(), level + 1); + if (resize > unionBins.size()) { + int grow = resize - unionBins.size(); + for (int i = 0; i < grow; i++) + unionBins.add(null); + } + binSizes.resize(resize, 0); + if (unionBins.get(level) == null) + unionBins + .set(level, new ArrayList(0)); + + unionBins.get(level).add(pair); + binSizes.write(level, binSizes.read(level) + sz); + + totalToUnion++; + totalVertexCount += sz; + } + } else { + // skip empty or geometries of lower dimension + } + } else { + bFinished = true; + } + } + + while (true)// union features that are in the unionBins + { + if (!bFinished) {// when we are still loading geometries, union + // geometries of the same level, starting + // with the biggest level. + int imax = -1; + int maxSz = 0; + // Find a bin that contains more than one geometry and has + // the max vertex count. + for (int i = 0, n = unionBins.size(); i < n; i++) { + if (unionBins.get(i) != null + && unionBins.get(i).size() > 1 + && binSizes.read(i) > binVertexThreshold) { + if (maxSz < binSizes.read(i)) { + maxSz = binSizes.read(i); + imax = i; + } + } + } + + if (maxSz > 0) { + // load the found bin into the batchToUnion. + while (unionBins.get(imax).size() > 0) { + ArrayList bin = unionBins.get(imax); + batchToUnion.add(bin.get(bin.size() - 1)); + bin.remove(bin.size() - 1); + totalVertexCount -= batchToUnion.get(batchToUnion + .size() - 1).vertex_count; + binSizes.write( + imax, + binSizes.read(imax) + - batchToUnion.get(batchToUnion + .size() - 1).vertex_count); + } + } + } else if (totalToUnion > 1) {// bFinished_shared == true - we + // loaded all geometries + int level = 0; + int vertexCount = 0; + for (int i = 0, n = unionBins.size(); i < n + && (batchToUnion.size() < 2 || vertexCount < binVertexThreshold); i++) { + if (unionBins.get(i) != null) { + while (!unionBins.get(i).isEmpty() + && (batchToUnion.size() < 2 || vertexCount < binVertexThreshold)) { + ArrayList bin = unionBins.get(i); + batchToUnion.add(bin.get(bin.size() - 1)); + bin.remove(bin.size() - 1); + level = i; + totalVertexCount -= batchToUnion + .get(batchToUnion.size() - 1).vertex_count; + vertexCount += batchToUnion.get(batchToUnion + .size() - 1).vertex_count; + binSizes.write( + i, + binSizes.read(i) + - batchToUnion.get(batchToUnion + .size() - 1).vertex_count); + continue; + } + } + } + + if (batchToUnion.size() == 1)// never happens? + {// only one element. Put it back. + unionBins.get(level).add( + batchToUnion.get(batchToUnion.size() - 1)); + totalVertexCount += batchToUnion.get(batchToUnion + .size() - 1).vertex_count; + binSizes.write( + level, + binSizes.read(level) + + batchToUnion.get(batchToUnion.size() - 1).vertex_count); + batchToUnion.remove(batchToUnion.size() - 1); + } + } + + if (!batchToUnion.isEmpty()) { + Geometry resGeom; + int resDim; + ArrayList geoms = new ArrayList(0); + geoms.ensureCapacity(batchToUnion.size()); + for (int i = 0, n = batchToUnion.size(); i < n; i++) { + geoms.add(batchToUnion.get(i).geom); + } + + resGeom = TopologicalOperations.dissolveDirty(geoms, + m_spatial_reference, m_progress_tracker); + // assert(Operator_factory_local::get_instance()->CanDoNewTopo(pair1.geom->get_geometry_type(), + // pair2.geom->get_geometry_type())); + // resGeom = + // Topological_operations::dissolve(batchToUnion[0].geom, + // batchToUnion[1].geom, m_spatial_reference, + // m_progress_tracker); + // Operator_factory_local::SaveJSONToTextFileDbg("c:/temp/buffer_dissolve.txt", + // *resGeom, nullptr); + resDim = resGeom.getDimension(); + + dissolved_something = true; + GeomPair pair = new GeomPair(); + pair.init(); + pair.geom = resGeom; + pair.dim = resDim; + int sz = getVertexCount_(resGeom); + pair.vertex_count = sz; + int level = getLevel_(sz); + + int resize = Math.max(unionBins.size() + 1, level); + if (resize > unionBins.size()) { + int grow = resize - unionBins.size(); + for (int i = 0; i < grow; i++) + unionBins.add(null); + } + binSizes.resize(resize, 0); + + if (unionBins.get(level) == null) + unionBins.set(level, new ArrayList(0)); + + unionBins.get(level).add(pair); + binSizes.write(level, binSizes.read(level) + sz); + totalToUnion -= (batchToUnion.size() - 1); + + batchToUnion.clear(); + } else { + boolean bCanGo = totalToUnion == 1; + if (bFinished) + bLocalDone = true; + + break; + } + } + } + + Geometry resGeom = null; + for (int i = 0; i < unionBins.size(); i++) { + if (unionBins.get(i) != null && unionBins.get(i).size() > 0) + resGeom = unionBins.get(i).get(0).geom; + } + + if (resGeom == null) + return resGeom; + + if (dissolved_something) { + OperatorFactoryLocal engine = OperatorFactoryLocal.getInstance(); + OperatorSimplify simplify = (OperatorSimplify) engine + .getOperator(Operator.Type.Simplify); + resGeom = simplify.execute(resGeom, m_spatial_reference, false, + m_progress_tracker); + } + + if (resGeom.getType().value() == Geometry.GeometryType.Point) {// must + // return + // multipoint + // for + // points + MultiPoint mp = new MultiPoint(resGeom.getDescription()); + if (!resGeom.isEmpty()) + mp.add((Point) resGeom); + resGeom = mp; + } + + return resGeom; + } + + private static final class GeomPair { + void init() { + geom = null; + dim = -1; + vertex_count = -1; + } + + Geometry geom; + int vertex_count; + int dim; + } + + private static int getVertexCount_(Geometry geom) { + int gt = geom.getType().value(); + if (Geometry.isMultiVertex(gt)) { + return ((MultiVertexGeometry) geom).getPointCount(); + } else if (gt == Geometry.GeometryType.Point) { + return 1; + } else if (gt == Geometry.GeometryType.Envelope) { + return 4; + } else if (Geometry.isSegment(gt)) { + return 2; + } else { + throw new GeometryException("internal error"); + } + } + + private static int getLevel_(int sz) {// calculates logarithm of sz to base + // 4. + return sz > 0 ? (int) (Math.log((double) sz) / Math.log(4.0) + 0.5) + : (int) 0; + } +} diff --git a/src/com/esri/core/geometry/OperatorUnionLocal.java b/src/com/esri/core/geometry/OperatorUnionLocal.java new file mode 100644 index 00000000..e3cf5ea7 --- /dev/null +++ b/src/com/esri/core/geometry/OperatorUnionLocal.java @@ -0,0 +1,48 @@ +/* + Copyright 1995-2013 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ + +package com.esri.core.geometry; + +class OperatorUnionLocal extends OperatorUnion { + + @Override + public GeometryCursor execute(GeometryCursor inputGeometries, + SpatialReference sr, ProgressTracker progressTracker) { + return new OperatorUnionCursor(inputGeometries, sr, progressTracker); + } + + @Override + public Geometry execute(Geometry geom1, Geometry geom2, + SpatialReference sr, ProgressTracker progressTracker) { + Geometry[] geomArray = new Geometry[] { geom1, geom2 }; + + SimpleGeometryCursor inputGeometries = new SimpleGeometryCursor( + geomArray); + GeometryCursor outputCursor = execute(inputGeometries, sr, + progressTracker); + + return outputCursor.next(); + } + +} diff --git a/src/com/esri/core/geometry/OperatorWithin.java b/src/com/esri/core/geometry/OperatorWithin.java new file mode 100644 index 00000000..9032718f --- /dev/null +++ b/src/com/esri/core/geometry/OperatorWithin.java @@ -0,0 +1,45 @@ +/* + Copyright 1995-2013 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ + +package com.esri.core.geometry; + +import com.esri.core.geometry.Operator.Type; + +/** + * + * Relational operation Within. + * + */ +public abstract class OperatorWithin extends OperatorSimpleRelation { + @Override + public Type getType() { + return Type.Within; + } + + public static OperatorWithin local() { + return (OperatorWithin) OperatorFactoryLocal.getInstance().getOperator( + Type.Within); + } + +} diff --git a/src/com/esri/core/geometry/OperatorWithinLocal.java b/src/com/esri/core/geometry/OperatorWithinLocal.java new file mode 100644 index 00000000..9958ff90 --- /dev/null +++ b/src/com/esri/core/geometry/OperatorWithinLocal.java @@ -0,0 +1,36 @@ +/* + Copyright 1995-2013 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ + +package com.esri.core.geometry; + +class OperatorWithinLocal extends OperatorWithin { + + @Override + public boolean execute(Geometry inputGeom1, Geometry inputGeom2, + SpatialReference sr, ProgressTracker progressTracker) { + return RelationalOperations.relate(inputGeom1, inputGeom2, sr, + RelationalOperations.Relation.within, progressTracker); + } + +} diff --git a/src/com/esri/core/geometry/PathFlags.java b/src/com/esri/core/geometry/PathFlags.java new file mode 100644 index 00000000..b163559e --- /dev/null +++ b/src/com/esri/core/geometry/PathFlags.java @@ -0,0 +1,40 @@ +/* + Copyright 1995-2013 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ + +package com.esri.core.geometry; + +interface PathFlags { + public static final int enumClosed = 1; + public static final int enumHasNonlinearSegments = 2;// set when the given + // part has + // non-linear + // segments + public static final int enumOGCStartPolygon = 4;// set at the start of a + // Polygon when viewed as an + // OGC MultiPolygon + public static final int enumCalcMask = 4;// mask of flags that are obtained + // by calculation and depend on + // the order of MultiPath parts. + +} diff --git a/src/com/esri/core/geometry/PeDouble.java b/src/com/esri/core/geometry/PeDouble.java new file mode 100644 index 00000000..90bf9abf --- /dev/null +++ b/src/com/esri/core/geometry/PeDouble.java @@ -0,0 +1,37 @@ +/* + Copyright 1995-2013 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ + +package com.esri.core.geometry; + +final class PeDouble { + double val; + + PeDouble() { + val = 0.0; + } + + PeDouble(double v) { + val = v; + } +} diff --git a/src/com/esri/core/geometry/PlaneSweepCrackerHelper.java b/src/com/esri/core/geometry/PlaneSweepCrackerHelper.java new file mode 100644 index 00000000..902d2fb4 --- /dev/null +++ b/src/com/esri/core/geometry/PlaneSweepCrackerHelper.java @@ -0,0 +1,1594 @@ +/* + Copyright 1995-2013 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ +/* + * To change this template, choose Tools | Templates + * and open the template in the editor. + */ +package com.esri.core.geometry; + +final class PlaneSweepCrackerHelper { + PlaneSweepCrackerHelper() { + m_edges = new StridedIndexTypeCollection(8); + m_clusters = new StridedIndexTypeCollection(5); + m_cluster_vertices = new IndexMultiList(); + m_edge_vertices = new IndexMultiList(); + m_complications = false; + m_sweep_point = new Point2D(); + m_sweep_point.setCoords(0, 0); + m_tolerance = 0; + m_vertex_cluster_index = -1; + m_b_cracked = false; + m_shape = null; + + m_event_q = new Treap(); + m_sweep_structure = new Treap(); + m_edges_to_insert_in_sweep_structure = new AttributeStreamOfInt32(0); + m_segment_intersector = new SegmentIntersector(); + m_temp_edge_buffer = new AttributeStreamOfInt32(0); + m_modified_clusters = new AttributeStreamOfInt32(0); + m_helper_point = new Point(); + } + + // For use in Cluster/Cracker loop + boolean sweep(EditShape shape, double tolerance) { + Transformation2D transform = new Transformation2D(); + transform.setSwapCoordinates(); + shape.applyTransformation(transform);// swap coordinates for the sweep + // along x + setEditShape_(shape); + m_b_cracked = false; + m_tolerance = tolerance; + m_tolerance_sqr = tolerance * tolerance; + // #ifdef _DEBUG_CRACKING_REPORT + // { + // DEBUGPRINTF(L"PlaneSweep along x\n"); + // } + // #endif + + boolean b_cracked = sweepImpl_(); + // #ifdef _DEBUG_CRACKING_REPORT + // { + // DEBUGPRINTF(L"PlaneSweep along y\n"); + // } + // #endif + shape.applyTransformation(transform); + fillEventQueuePass2(); + b_cracked |= sweepImpl_(); + m_shape.removeUserIndex(m_vertex_cluster_index); + m_shape = null; + return m_b_cracked; + } + + // Does one pass sweep vertically + boolean sweepVertical(EditShape shape, double tolerance) { + setEditShape_(shape); + m_b_cracked = false; + m_tolerance = tolerance; + m_tolerance_sqr = tolerance * tolerance; + m_complications = false; + boolean bresult = sweepImpl_(); + if (!m_complications) { + int filtered = shape.filterClosePoints(tolerance, true); + m_complications = filtered == 1; + } + return bresult; + } + + boolean hadCompications() { + return m_complications; + } + + private EditShape m_shape; + private StridedIndexTypeCollection m_edges; + private StridedIndexTypeCollection m_clusters; + private IndexMultiList m_cluster_vertices; + private IndexMultiList m_edge_vertices; + private Point m_helper_point; + + private Treap m_event_q; + private Treap m_sweep_structure; + + boolean m_complications; + + static final class SimplifySweepComparator extends SweepComparator { + PlaneSweepCrackerHelper m_parent; + + SimplifySweepComparator(PlaneSweepCrackerHelper parent) { + super(parent.m_shape, parent.m_tolerance, false); + m_parent = parent; + } + + @Override + int compare(Treap treap, int elm, int node) { + // Compares two segments on a sweep line passing through m_sweep_y, + // m_sweep_x. + if (m_b_intersection_detected) + return -1; + + int vertex_list_left = m_parent.getEdgeOriginVertices(elm); + int left = m_parent.m_edge_vertices + .getFirstElement(vertex_list_left); + + int right_elm = treap.getElement(node); + assert (m_parent.getEdgeSweepNode(right_elm) == node); + int vertex_list_right = m_parent.getEdgeOriginVertices(right_elm); + int right = m_parent.m_edge_vertices + .getFirstElement(vertex_list_right); + + m_current_node = node; + return compareSegments(elm, left, right_elm, right); + } + }; + + static final class SimplifySweepMonikerComparator extends + SweepMonkierComparator { + PlaneSweepCrackerHelper m_parent; + + SimplifySweepMonikerComparator(PlaneSweepCrackerHelper parent) { + super(parent.m_shape, parent.m_tolerance); + m_parent = parent; + } + + @Override + int compare(Treap treap, int node) { + // Compares two segments on a sweep line passing through m_sweep_y, + // m_sweep_x. + if (m_b_intersection_detected) + return -1; + + int elm = treap.getElement(node); + int vertexList = m_parent.getEdgeOriginVertices(elm); + int vertex = m_parent.m_edge_vertices.getFirstElement(vertexList); + + m_current_node = node; + return compareVertex_(treap, node, vertex); + } + }; + + SimplifySweepComparator m_sweep_comparator; + + AttributeStreamOfInt32 m_temp_edge_buffer; + AttributeStreamOfInt32 m_modified_clusters; + AttributeStreamOfInt32 m_edges_to_insert_in_sweep_structure; + + int m_prev_neighbour; + int m_next_neighbour; + boolean m_b_continuing_segment_chain_optimization;// set to true, when the + // cluster has two edges + // attached, one is + // below and another + // above the sweep line + + SegmentIntersector m_segment_intersector; + + Line m_line_1; + Line m_line_2; + + Point2D m_sweep_point; + double m_tolerance; + double m_tolerance_sqr; + + int m_sweep_point_cluster; + int m_vertex_cluster_index; + + boolean m_b_cracked; + boolean m_b_sweep_point_cluster_was_modified;// set to true if the + // coordinates of the + // cluster, where the sweep + // line was, has been + // changed. + + int getEdgeCluster(int edge, int end) { + assert (end == 0 || end == 1); + return m_edges.getField(edge, 0 + end); + } + + void setEdgeCluster_(int edge, int end, int cluster) { + assert (end == 0 || end == 1); + m_edges.setField(edge, 0 + end, cluster); + } + + // Edge may have several origin vertices, when there are two or more equal + // segements in that edge + // We have to store edge origin separately from the cluster vertices, + // because cluster can have several different edges started on it. + int getEdgeOriginVertices(int edge) { + return m_edges.getField(edge, 2); + } + + void setEdgeOriginVertices_(int edge, int vertices) { + m_edges.setField(edge, 2, vertices); + } + + int getNextEdgeEx(int edge, int end) { + assert (end == 0 || end == 1); + return m_edges.getField(edge, 3 + end); + } + + void setNextEdgeEx_(int edge, int end, int next_edge) { + assert (end == 0 || end == 1); + m_edges.setField(edge, 3 + end, next_edge); + } + + // int get_prev_edge_ex(int edge, int end) + // { + // assert(end == 0 || end == 1); + // return m_edges.get_field(edge, 5 + end); + // } + // void set_prev_edge_ex_(int edge, int end, int prevEdge) + // { + // assert(end == 0 || end == 1); + // m_edges.set_field(edge, 5 + end, prevEdge); + // } + + int getEdgeSweepNode(int edge) { + return m_edges.getField(edge, 7); + } + + void setEdgeSweepNode_(int edge, int sweepNode) { + m_edges.setField(edge, 7, sweepNode); + } + + int getNextEdge(int edge, int cluster) { + int end = getEdgeEnd(edge, cluster); + assert (end == 0 || end == 1); + return m_edges.getField(edge, 3 + end); + } + + void setNextEdge_(int edge, int cluster, int next_edge) { + int end = getEdgeEnd(edge, cluster); + assert (end == 0 || end == 1); + m_edges.setField(edge, 3 + end, next_edge); + } + + int getPrevEdge(int edge, int cluster) { + int end = getEdgeEnd(edge, cluster); + assert (end == 0 || end == 1); + return m_edges.getField(edge, 5 + end); + } + + void setPrevEdge_(int edge, int cluster, int prevEdge) { + int end = getEdgeEnd(edge, cluster); + assert (end == 0 || end == 1); + m_edges.setField(edge, 5 + end, prevEdge); + } + + int getClusterVertices(int cluster) { + return m_clusters.getField(cluster, 0); + } + + void setClusterVertices_(int cluster, int vertices) { + m_clusters.setField(cluster, 0, vertices); + } + + int getClusterVertexIndex(int cluster) { + return m_clusters.getField(cluster, 4); + } + + void setClusterVertexIndex_(int cluster, int vindex) { + m_clusters.setField(cluster, 4, vindex); + } + + int getClusterSweepEdgeList(int cluster) { + return m_clusters.getField(cluster, 2); + } + + void setClusterSweepEdgeList_(int cluster, int sweep_edges) { + m_clusters.setField(cluster, 2, sweep_edges); + } + + int getClusterFirstEdge(int cluster) { + return m_clusters.getField(cluster, 1); + } + + void setClusterFirstEdge_(int cluster, int first_edge) { + m_clusters.setField(cluster, 1, first_edge); + } + + int getClusterEventQNode(int cluster) { + return m_clusters.getField(cluster, 3); + } + + void setClusterEventQNode_(int cluster, int node) { + m_clusters.setField(cluster, 3, node); + } + + int newCluster_(int vertex) { + int cluster = m_clusters.newElement(); + int vertexList = m_cluster_vertices.createList(); + setClusterVertices_(cluster, vertexList); + if (vertex != -1) { + m_cluster_vertices.addElement(vertexList, vertex); + assert (m_shape.getUserIndex(vertex, m_vertex_cluster_index) == -1); + m_shape.setUserIndex(vertex, m_vertex_cluster_index, cluster); + setClusterVertexIndex_(cluster, m_shape.getVertexIndex(vertex)); + } else { + setClusterVertexIndex_(cluster, -1); + } + + return cluster; + } + + void deleteCluster_(int cluster) { + m_clusters.deleteElement(cluster); + } + + void addVertexToCluster_(int cluster, int vertex) { + assert (m_shape.getUserIndex(vertex, m_vertex_cluster_index) == -1); + int vertexList = getClusterVertices(cluster); + m_cluster_vertices.addElement(vertexList, vertex); + m_shape.setUserIndex(vertex, m_vertex_cluster_index, cluster); + } + + // Creates a new unattached edge with the given origin. + int newEdge_(int origin_vertex) { + int edge = m_edges.newElement(); + int edgeVertices = m_edge_vertices.createList(); + setEdgeOriginVertices_(edge, edgeVertices); + if (origin_vertex != -1) + m_edge_vertices.addElement(edgeVertices, origin_vertex); + + return edge; + } + + void addVertexToEdge_(int edge, int vertex) { + int vertexList = getEdgeOriginVertices(edge); + m_edge_vertices.addElement(vertexList, vertex); + } + + void deleteEdge_(int edge) { + m_edges.deleteElement(edge); + int ind = m_edges_to_insert_in_sweep_structure.findElement(edge); + if (ind >= 0) + m_edges_to_insert_in_sweep_structure.popElement(ind); + } + + void addEdgeToCluster(int edge, int cluster) { + if (getEdgeCluster(edge, 0) == -1) { + assert (getEdgeCluster(edge, 1) != cluster); + setEdgeCluster_(edge, 0, cluster); + } else if (getEdgeCluster(edge, 1) == -1) { + assert (getEdgeCluster(edge, 0) != cluster); + setEdgeCluster_(edge, 1, cluster); + } else + throw new GeometryException("internal error"); + + addEdgeToClusterImpl_(edge, cluster);// simply adds the edge to the list + // of cluster edges. + } + + void addEdgeToClusterImpl_(int edge, int cluster) { + int first_edge = getClusterFirstEdge(cluster); + if (first_edge != -1) { + int next = getNextEdge(first_edge, cluster); + setPrevEdge_(next, cluster, edge); + setNextEdge_(edge, cluster, next); + setNextEdge_(first_edge, cluster, edge); + setPrevEdge_(edge, cluster, first_edge); + } else { + setPrevEdge_(edge, cluster, edge);// point to itself + setNextEdge_(edge, cluster, edge); + setClusterFirstEdge_(cluster, edge); + } + } + + int getEdgeEnd(int edge, int cluster) { + if (getEdgeCluster(edge, 0) == cluster) { + assert (getEdgeCluster(edge, 1) != cluster); + return 0; + } else { + assert (getEdgeCluster(edge, 1) == cluster); + return 1; + } + } + + // Merges two coincident clusters into one. The cluster2 becomes invalid. + void mergeClusters_(int cluster_1, int cluster2) { + // dbg_check_cluster_(cluster_1); + // dbg_check_cluster_(cluster2); + int eventQnode = getClusterEventQNode(cluster2); + if (eventQnode != -1) { + m_event_q.deleteNode(eventQnode, -1); + setClusterEventQNode_(cluster2, -1); + } + + int firstEdge1 = getClusterFirstEdge(cluster_1); + int firstEdge2 = getClusterFirstEdge(cluster2); + + if (firstEdge2 != -1) {// scope + int edge2 = firstEdge2; + int lastEdge = firstEdge2; + boolean bForceContinue = false; + // Delete edges that connect cluster_1 and cluster2. + do { + // dbg_check_edge_(edge2); + bForceContinue = false; + // assert(!StridedIndexTypeCollection.isValidElement(getEdgeSweepNode(edge2))); + int end = getEdgeEnd(edge2, cluster2); + int nextEdge2 = getNextEdgeEx(edge2, end); + if (getEdgeCluster(edge2, (end + 1) & 1) == cluster_1) { // Snapping + // clusters + // that + // are + // connected + // with + // an + // edge + // Delete + // the + // edge. + disconnectEdge_(edge2); + int edgeOrigins2 = getEdgeOriginVertices(edge2); + m_edge_vertices.deleteList(edgeOrigins2); + deleteEdge_(edge2); + if (edge2 == nextEdge2) {// deleted last edge connecting to + // the cluster2 (all connections + // are degenerate) + firstEdge2 = -1; + break; + } + if (firstEdge2 == edge2) { + firstEdge2 = getClusterFirstEdge(cluster2); + lastEdge = nextEdge2; + bForceContinue = true; + } + } else { + assert (edge2 != getClusterFirstEdge(cluster_1)); + } + edge2 = nextEdge2; + } while (edge2 != lastEdge || bForceContinue); + + if (firstEdge2 != -1) { + // set the cluster to the edge ends + do { + int end = getEdgeEnd(edge2, cluster2); + int nextEdge2 = getNextEdgeEx(edge2, end); + assert (edge2 != getClusterFirstEdge(cluster_1)); + setEdgeCluster_(edge2, end, cluster_1); + edge2 = nextEdge2; + } while (edge2 != lastEdge); + + firstEdge1 = getClusterFirstEdge(cluster_1); + if (firstEdge1 != -1) { + int next1 = getNextEdge(firstEdge1, cluster_1); + int next2 = getNextEdge(firstEdge2, cluster_1); + if (next1 == firstEdge1) { + setClusterFirstEdge_(cluster_1, firstEdge2); + addEdgeToClusterImpl_(firstEdge1, cluster_1); + setClusterFirstEdge_(cluster_1, firstEdge1); + } else if (next2 == firstEdge2) { + addEdgeToClusterImpl_(firstEdge2, cluster_1); + } + + setNextEdge_(firstEdge2, cluster_1, next1); + setPrevEdge_(next1, cluster_1, firstEdge2); + setNextEdge_(firstEdge1, cluster_1, next2); + setPrevEdge_(next2, cluster_1, firstEdge1); + } else { + setClusterFirstEdge_(cluster_1, firstEdge2); + } + } + } + + int vertices1 = getClusterVertices(cluster_1); + int vertices2 = getClusterVertices(cluster2); + // Update cluster info on vertices. + for (int vh = m_cluster_vertices.getFirst(vertices2); vh != -1; vh = m_cluster_vertices + .getNext(vh)) { + int v = m_cluster_vertices.getElement(vh); + m_shape.setUserIndex(v, m_vertex_cluster_index, cluster_1); + } + m_cluster_vertices.concatenateLists(vertices1, vertices2); + deleteCluster_(cluster2); + // dbg_check_cluster_(cluster_1); + } + + // Merges two coincident edges into one. The edge2 becomes invalid. + void mergeEdges_(int edge1, int edge2) { + // dbg_check_edge_(edge1); + int cluster_1 = getEdgeCluster(edge1, 0); + int cluster2 = getEdgeCluster(edge1, 1); + int cluster21 = getEdgeCluster(edge2, 0); + int cluster22 = getEdgeCluster(edge2, 1); + + int originVertices1 = getEdgeOriginVertices(edge1); + int originVertices2 = getEdgeOriginVertices(edge2); + m_edge_vertices.concatenateLists(originVertices1, originVertices2); + if (edge2 == getClusterFirstEdge(cluster_1)) + setClusterFirstEdge_(cluster_1, edge1); + if (edge2 == getClusterFirstEdge(cluster2)) + setClusterFirstEdge_(cluster2, edge1); + + disconnectEdge_(edge2);// disconnects the edge2 from the clusters. + deleteEdge_(edge2); + + if (!((cluster_1 == cluster21 && cluster2 == cluster22) || (cluster2 == cluster21 && cluster_1 == cluster22))) { + // Merged edges have different clusters (clusters have not yet been + // merged) + // merge clusters before merging the edges + Point2D pt11 = getClusterXY(cluster_1); + Point2D pt21 = getClusterXY(cluster21); + // #ifdef _DEBUG_TOPO + // Point_2D pt12, pt22; + // pt12 = get_cluster_xy(cluster2); + // pt22 = get_cluster_xy(cluster22); + // assert((pt11.is_equal(pt21) && pt12.is_equal(pt22)) || + // (pt12.is_equal(pt21) && pt11.is_equal(pt22))); + // #endif + if (pt11.isEqual(pt21)) { + if (cluster_1 != cluster21) { + mergeClusters_(cluster_1, cluster21); + assert (!m_modified_clusters.hasElement(cluster21)); + } + if (cluster2 != cluster22) { + mergeClusters_(cluster2, cluster22); + assert (!m_modified_clusters.hasElement(cluster22)); + } + } else { + if (cluster2 != cluster21) { + mergeClusters_(cluster2, cluster21); + assert (!m_modified_clusters.hasElement(cluster21)); + } + if (cluster_1 != cluster22) { + mergeClusters_(cluster_1, cluster22); + assert (!m_modified_clusters.hasElement(cluster22)); + } + } + } else { + // Merged edges have equal clusters. + } + // dbg_check_edge_(edge1); + } + + // Disconnects the edge from its clusters. + void disconnectEdge_(int edge) { + int cluster_1 = getEdgeCluster(edge, 0); + int cluster2 = getEdgeCluster(edge, 1); + disconnectEdgeFromCluster_(edge, cluster_1); + disconnectEdgeFromCluster_(edge, cluster2); + } + + // Disconnects the edge from a cluster it is connected to. + void disconnectEdgeFromCluster_(int edge, int cluster) { + int next = getNextEdge(edge, cluster); + assert (getPrevEdge(next, cluster) == edge); + int prev = getPrevEdge(edge, cluster); + assert (getNextEdge(prev, cluster) == edge); + int first_edge = getClusterFirstEdge(cluster); + if (next != edge) { + setNextEdge_(prev, cluster, next); + setPrevEdge_(next, cluster, prev); + if (first_edge == edge) + setClusterFirstEdge_(cluster, next); + } else + setClusterFirstEdge_(cluster, -1); + } + + void applyIntersectorToEditShape_(int edgeOrigins, + SegmentIntersector intersector, int intersector_index) { + // Split Edit_shape segments and produce new vertices. Modify + // coordinates as necessary. No vertices are deleted. + int vertexHandle = m_edge_vertices.getFirst(edgeOrigins); + int first_vertex = m_edge_vertices.getElement(vertexHandle); + + int cluster_1 = getClusterFromVertex(first_vertex); + int cluster2 = getClusterFromVertex(m_shape.getNextVertex(first_vertex)); + boolean bComplexCase = cluster_1 == cluster2; + assert (!bComplexCase);// if it ever asserts there will be a bug. Should + // be a case of a curve that forms a loop. + + m_shape.splitSegment_(first_vertex, intersector, intersector_index, + true); + for (vertexHandle = m_edge_vertices.getNext(vertexHandle); vertexHandle != -1; vertexHandle = m_edge_vertices + .getNext(vertexHandle)) { + int vertex = m_edge_vertices.getElement(vertexHandle); + boolean b_forward = getClusterFromVertex(vertex) == cluster_1; + assert ((b_forward && getClusterFromVertex(m_shape + .getNextVertex(vertex)) == cluster2) || (getClusterFromVertex(vertex) == cluster2 && getClusterFromVertex(m_shape + .getNextVertex(vertex)) == cluster_1)); + m_shape.splitSegment_(vertex, intersector, intersector_index, + b_forward); + } + + // Now apply the updated coordinates to all vertices in the cluster_1 + // and cluster2. + Point2D pt_0; + Point2D pt_1; + pt_0 = intersector.getResultSegment(intersector_index, 0).getStartXY(); + pt_1 = intersector.getResultSegment(intersector_index, + intersector.getResultSegmentCount(intersector_index) - 1) + .getEndXY(); + updateClusterXY(cluster_1, pt_0); + updateClusterXY(cluster2, pt_1); + } + + void createEdgesAndClustersFromSplitEdge_(int edge1, + SegmentIntersector intersector, int intersector_index) { + // dbg_check_new_edges_array_(); + // The method uses m_temp_edge_buffer for temporary storage and clears + // it at the end. + int edgeOrigins1 = getEdgeOriginVertices(edge1); + + // create new edges and clusters + // Note that edge1 is disconnected from its clusters already (the + // cluster's edge list does not contain it). + int cluster_1 = getEdgeCluster(edge1, 0); + int cluster2 = getEdgeCluster(edge1, 1); + int prevEdge = newEdge_(-1); + m_edges_to_insert_in_sweep_structure.add(prevEdge); + int c_3 = StridedIndexTypeCollection.impossibleIndex3(); + setEdgeSweepNode_(prevEdge, c_3);// mark that its in + // m_edges_to_insert_in_sweep_structure + m_temp_edge_buffer.add(prevEdge); + addEdgeToCluster(prevEdge, cluster_1); + for (int i = 1, n = intersector + .getResultSegmentCount(intersector_index); i < n; i++) {// each + // iteration + // adds + // new + // Cluster + // and + // Edge. + int newCluster = newCluster_(-1); + m_modified_clusters.add(newCluster); + m_temp_edge_buffer.add(newCluster); + addEdgeToCluster(prevEdge, newCluster); + int newEdge = newEdge_(-1); + m_edges_to_insert_in_sweep_structure.add(newEdge); + setEdgeSweepNode_(newEdge, c_3);// mark that its in + // m_edges_to_insert_in_sweep_structure + m_temp_edge_buffer.add(newEdge); + addEdgeToCluster(newEdge, newCluster); + prevEdge = newEdge; + } + addEdgeToCluster(prevEdge, cluster2); + // set the Edit_shape vertices to the new clusters and edges. + for (int vertexHandle = m_edge_vertices.getFirst(edgeOrigins1); vertexHandle != -1; vertexHandle = m_edge_vertices + .getNext(vertexHandle)) { + int vertex = m_edge_vertices.getElement(vertexHandle); + int cluster = getClusterFromVertex(vertex); + if (cluster == cluster_1) {// connecting from cluster_1 to cluster2 + int i = 0; + do { + if (i > 0) { + int c = m_temp_edge_buffer.get(i - 1); + addVertexToCluster_(c, vertex); + if (getClusterVertexIndex(c) == -1) + setClusterVertexIndex_(c, + m_shape.getVertexIndex(vertex)); + } + + int edge = m_temp_edge_buffer.get(i); + i += 2; + addVertexToEdge_(edge, vertex); + vertex = m_shape.getNextVertex(vertex); + } while (i < m_temp_edge_buffer.size()); + assert (getClusterFromVertex(vertex) == cluster2); + } else {// connecting from cluster2 to cluster_1 + assert (cluster == cluster2); + int i = m_temp_edge_buffer.size() - 1; + do { + if (i < m_temp_edge_buffer.size() - 2) { + int c = m_temp_edge_buffer.get(i + 1); + addVertexToCluster_(c, vertex); + if (getClusterVertexIndex(c) < 0) + setClusterVertexIndex_(c, + m_shape.getVertexIndex(vertex)); + } + + assert (i % 2 == 0); + int edge = m_temp_edge_buffer.get(i); + i -= 2; + addVertexToEdge_(edge, vertex); + vertex = m_shape.getNextVertex(vertex); + } while (i >= 0); + assert (getClusterFromVertex(vertex) == cluster_1); + } + } + + // #ifdef _DEBUG_TOPO + // for (int i = 0, j = 0, n = + // intersector->get_result_segment_count(intersector_index); i < n; i++, + // j+=2) + // { + // int edge = m_temp_edge_buffer.get(j); + // dbg_check_edge_(edge); + // } + // #endif + + m_temp_edge_buffer.clear(false); + // dbg_check_new_edges_array_(); + } + + int getVertexFromClusterIndex(int cluster) { + int vertexList = getClusterVertices(cluster); + int vertex = m_cluster_vertices.getFirstElement(vertexList); + return vertex; + } + + int getClusterFromVertex(int vertex) { + return m_shape.getUserIndex(vertex, m_vertex_cluster_index); + } + + static final class QComparator extends Treap.Comparator { + EditShape m_shape; + Point2D pt_1 = new Point2D(); + Point2D pt_2 = new Point2D(); + + QComparator(EditShape shape) { + m_shape = shape; + } + + @Override + int compare(Treap treap, int vertex, int node) { + m_shape.getXY(vertex, pt_1); + int v_2 = treap.getElement(node); + m_shape.getXY(v_2, pt_2); + return pt_1.compare(pt_2); + } + } + + static final class QMonikerComparator extends Treap.MonikerComparator { + EditShape m_shape; + Point2D m_point = new Point2D(); + Point2D m_pt = new Point2D(); + + QMonikerComparator(EditShape shape) { + m_shape = shape; + } + + void setPoint(Point2D pt) { + m_point.setCoords(pt); + } + + @Override + int compare(Treap treap, int node) { + int v = treap.getElement(node); + m_shape.getXY(v, m_pt); + return m_point.compare(m_pt); + } + }; + + void processSplitHelper1_(int index, int edge, + SegmentIntersector intersector) { + // Collect all edges that are affected by the split and that are in the + // sweep structure. + int count = intersector.getResultSegmentCount(index); + Segment seg = intersector.getResultSegment(index, 0); + Point2D newStart = seg.getStartXY(); + int clusterStart = getEdgeCluster(edge, 0); + Point2D pt = getClusterXY(clusterStart); + if (!pt.isEqual(newStart)) { + // This cluster's position needs to be changed + getAffectedEdges(clusterStart, m_temp_edge_buffer); + m_modified_clusters.add(clusterStart); + } + + seg = intersector.getResultSegment(index, count - 1); + Point2D newEnd = seg.getEndXY(); + int clusterEnd = getEdgeCluster(edge, 1); + pt = getClusterXY(clusterEnd); + if (!pt.isEqual(newEnd)) { + // This cluster's position needs to be changed + getAffectedEdges(clusterEnd, m_temp_edge_buffer); + m_modified_clusters.add(clusterEnd); + } + + m_temp_edge_buffer.add(edge); + // Delete all nodes from the sweep structure that are affected by the + // change. + for (int i = 0, n = m_temp_edge_buffer.size(); i < n; i++) { + int e = m_temp_edge_buffer.get(i); + int sweepNode = getEdgeSweepNode(e); + if (StridedIndexTypeCollection.isValidElement(sweepNode)) { + m_sweep_structure.deleteNode(sweepNode, -1); + setEdgeSweepNode_(e, -1); + } + + int c_3 = StridedIndexTypeCollection.impossibleIndex3(); + if (e != edge && getEdgeSweepNode(e) != c_3)// c_3 means the edge is + // already in the + // m_edges_to_insert_in_sweep_structure + { + m_edges_to_insert_in_sweep_structure.add(e); + setEdgeSweepNode_(e, c_3); + } + } + m_temp_edge_buffer.clear(false); + } + + boolean checkAndFixIntersection_(int leftSweepNode, int rightSweepNode) { + int leftEdge = m_sweep_structure.getElement(leftSweepNode); + m_sweep_comparator.compare(m_sweep_structure, leftEdge, rightSweepNode); + if (m_sweep_comparator.intersectionDetected()) { + m_sweep_comparator.clearIntersectionDetectedFlag(); + fixIntersection_(leftSweepNode, rightSweepNode); + return true; + } + + return false; + } + + void fixIntersection_(int left, int right) { + // static int dbg = 0; + // dbg++; + m_b_cracked = true; + int edge1 = m_sweep_structure.getElement(left); + int edge2 = m_sweep_structure.getElement(right); + assert (edge1 != edge2); + Segment seg_1; + Segment seg_2; + int vertexList1 = getEdgeOriginVertices(edge1); + int origin1 = m_edge_vertices.getFirstElement(vertexList1); + int vertexList2 = getEdgeOriginVertices(edge2); + int origin2 = m_edge_vertices.getFirstElement(vertexList2); + seg_1 = m_shape.getSegment(origin1); + if (seg_1 == null) { + if (m_line_1 == null) + m_line_1 = new Line(); + m_shape.queryLineConnector(origin1, m_line_1); + seg_1 = m_line_1; + } + + seg_2 = m_shape.getSegment(origin2); + if (seg_2 == null) { + if (m_line_2 == null) + m_line_2 = new Line(); + m_shape.queryLineConnector(origin2, m_line_2); + seg_2 = m_line_2; + } + + // #ifdef _DEBUG_CRACKING_REPORT + // { + // Point_2D pt11, pt12, pt21, pt22; + // pt11 = seg_1->get_start_xy(); + // pt12 = seg_1->get_end_xy(); + // pt21 = seg_2->get_start_xy(); + // pt22 = seg_2->get_end_xy(); + // DEBUGPRINTF(L"Intersecting %d (%0.4f, %0.4f - %0.4f, %0.4f) and %d (%0.4f, %0.4f - %0.4f, %0.4f)\n", + // edge1, pt11.x, pt11.y, pt12.x, pt12.y, edge2, pt21.x, pt21.y, pt22.x, + // pt22.y); + // } + // #endif + + m_segment_intersector.pushSegment(seg_1); + m_segment_intersector.pushSegment(seg_2); + m_segment_intersector.intersect(m_tolerance, true); + // #ifdef _DEBUG_CRACKING_REPORT + // { + // for (int resi = 0; resi < 2; resi++) + // { + // DEBUGPRINTF(L"intersection result %d:\n", resi); + // for (int i = 0; i < + // m_segment_intersector.get_result_segment_count(resi); i++) + // { + // Point_2D pt_1 = m_segment_intersector.get_result_segment(resi, + // i)->get_start_xy(); + // Point_2D pt_2 = m_segment_intersector.get_result_segment(resi, + // i)->get_end_xy(); + // DEBUGPRINTF(L"(%0.17f, %0.17f --- %0.17f, %0.17f)\n", pt_1.x, + // pt_1.y, pt_2.x, pt_2.y); + // } + // } + // } + // #endif + splitEdge_(edge1, edge2, -1, m_segment_intersector); + m_segment_intersector.clear(); + } + + void fixIntersectionPointSegment_(int cluster, int node) { + m_b_cracked = true; + int edge1 = m_sweep_structure.getElement(node); + Segment seg_1; + int vertexList1 = getEdgeOriginVertices(edge1); + int origin1 = m_edge_vertices.getFirstElement(vertexList1); + seg_1 = m_shape.getSegment(origin1); + if (seg_1 == null) { + if (m_line_1 == null) + m_line_1 = new Line(); + m_shape.queryLineConnector(origin1, m_line_1); + seg_1 = m_line_1; + } + + // #ifdef _DEBUG_CRACKING_REPORT + // { + // Point_2D pt11, pt12, pt21; + // pt11 = seg_1->get_start_xy(); + // pt12 = seg_1->get_end_xy(); + // get_cluster_xy(cluster, pt21); + // DEBUGPRINTF(L"Intersecting edge %d (%0.4f, %0.4f - %0.4f, %0.4f) and cluster %d (%0.4f, %0.4f)\n", + // edge1, pt11.x, pt11.y, pt12.x, pt12.y, cluster, pt21.x, pt21.y); + // } + // #endif + + int clusterVertex = getClusterFirstVertex(cluster); + m_segment_intersector.pushSegment(seg_1); + + m_shape.queryPoint(clusterVertex, m_helper_point); + m_segment_intersector.intersect(m_tolerance, m_helper_point, 0, 1.0, + true); + // #ifdef _DEBUG_CRACKING_REPORT + // { + // DEBUGPRINTF(L"intersection result\n"); + // for (int i = 0; i < + // m_segment_intersector.get_result_segment_count(0); i++) + // { + // Point_2D pt_1 = m_segment_intersector.get_result_segment(0, + // i)->get_start_xy(); + // Point_2D pt_2 = m_segment_intersector.get_result_segment(0, + // i)->get_end_xy(); + // DEBUGPRINTF(L"(%0.17f, %0.17f --- %0.17f, %0.17f)\n", pt_1.x, + // pt_1.y, pt_2.x, pt_2.y); + // } + // } + // #endif + splitEdge_(edge1, -1, cluster, m_segment_intersector); + + m_segment_intersector.clear(); + } + + void insertNewEdges_() { + if (m_edges_to_insert_in_sweep_structure.size() == 0) + return; + + // dbg_check_new_edges_array_(); + + while (m_edges_to_insert_in_sweep_structure.size() != 0) { + if (m_edges_to_insert_in_sweep_structure.size() > Math.max( + (int) 100, m_shape.getTotalPointCount())) { + assert (false); + m_edges_to_insert_in_sweep_structure.clear(false); + m_complications = true; + break;// something strange going on here. bail out, forget about + // these edges and continue with sweep line. We'll + // iterate on the data one more time. + } + + int edge = m_edges_to_insert_in_sweep_structure + .get(m_edges_to_insert_in_sweep_structure.size() - 1); + m_edges_to_insert_in_sweep_structure + .resize(m_edges_to_insert_in_sweep_structure.size() - 1); + + assert (getEdgeSweepNode(edge) == StridedIndexTypeCollection + .impossibleIndex3()); + setEdgeSweepNode_(edge, -1); + int terminatingCluster = isEdgeOnSweepLine_(edge); + if (terminatingCluster != -1) { + insertNewEdgeToSweepStructure_(edge, terminatingCluster); + } + m_b_continuing_segment_chain_optimization = false; + } + } + + boolean insertNewEdgeToSweepStructure_(int edge, int terminatingCluster) { + assert (getEdgeSweepNode(edge) == -1); + // #ifdef _DEBUG_CRACKING_REPORT + // { + // Point_2D pt11, pt12; + // int c_1 = get_edge_cluster(edge, 0); + // int c_2 = get_edge_cluster(edge, 1); + // get_cluster_xy(c_1, pt11); + // get_cluster_xy(c_2, pt12); + // DEBUGPRINTF(L"Inserting edge %d (%0.4f, %0.4f - %0.4f, %0.4f) to sweep structure\n", + // edge, pt11.x, pt11.y, pt12.x, pt12.y); + // } + // #endif + int newEdgeNode; + if (m_b_continuing_segment_chain_optimization) { + // st_counter_insertions++; + // st_counter_insertions_optimized++; + newEdgeNode = m_sweep_structure.addElementAtPosition( + m_prev_neighbour, m_next_neighbour, edge, true, true, -1); + m_b_continuing_segment_chain_optimization = false; + } else { + // st_counter_insertions++; + // st_counter_insertions_unique++; + newEdgeNode = m_sweep_structure.addUniqueElement(edge, -1); + } + + if (newEdgeNode == -1) {// a coinciding edge. + int existingNode = m_sweep_structure.getDuplicateElement(-1); + int existingEdge = m_sweep_structure.getElement(existingNode); + // #ifdef _DEBUG_CRACKING_REPORT + // DEBUGPRINTF(L"Edge %d is a duplicate of %d. Merged\n", edge, + // existingEdge); + // #endif + mergeEdges_(existingEdge, edge); + return false; + } + + // Remember the sweep structure node in the edge. + setEdgeSweepNode_(edge, newEdgeNode); + + if (m_sweep_comparator.intersectionDetected()) { + // #ifdef _DEBUG_CRACKING_REPORT + // DEBUGPRINTF(L"intersection detected\n"); + // #endif + + // The edge has been inserted into the sweep structure and an + // intersection has beebn found. The edge will be split and removed. + m_sweep_comparator.clearIntersectionDetectedFlag(); + int intersectionNode = m_sweep_comparator.getLastComparedNode(); + fixIntersection_(intersectionNode, newEdgeNode); + return true; + } else { + // The edge has been inserted into the sweep structure without + // problems (it does not intersect its neighbours) + } + + return false; + } + + int isEdgeOnSweepLine_(int edge) { + int cluster_1 = getEdgeCluster(edge, 0); + int cluster2 = getEdgeCluster(edge, 1); + Point2D pt_1 = getClusterXY(cluster_1); + Point2D pt_2 = getClusterXY(cluster2); + if (Point2D.sqrDistance(pt_1, pt_2) <= m_tolerance_sqr) {// avoid + // degenerate + // segments + m_complications = true; + return -1; + } + int cmp1 = pt_1.compare(m_sweep_point); + int cmp2 = pt_2.compare(m_sweep_point); + if (cmp1 <= 0 && cmp2 > 0) { + return cluster2; + } + + if (cmp2 <= 0 && cmp1 > 0) { + return cluster_1; + } + + return -1; + } + + // void set_edit_shape(Edit_shape* shape); + // Fills the event queue and merges coincident clusters. + void fillEventQueue() { + AttributeStreamOfInt32 event_q = new AttributeStreamOfInt32(0); + event_q.reserve(m_shape.getTotalPointCount());// temporary structure to + // sort and find + // clusters + EditShape.VertexIterator iter = m_shape.queryVertexIterator(); + for (int vert = iter.next(); vert != -1; vert = iter.next()) { + event_q.add(vert); + } + + assert (m_shape.getTotalPointCount() == event_q.size()); + + // Now we can merge coincident clusters and form the envent structure. + + // sort vertices lexicographically. + m_shape.sortVerticesSimpleByY_(event_q, 0, event_q.size()); + // int perPoint = m_shape->estimate_memory_size() / + // m_shape->get_total_point_count(); + // perPoint = 0; + + // The m_event_q is the event structure for the planesweep algorithm. + // We could use any data structure that allows log(n) insertion and + // deletion in the sorted order and + // allow to iterate through in the sorted order. + + m_event_q.clear(); + // Populate the event structure + m_event_q.setCapacity(event_q.size()); + { + // The comparator is used to sort vertices by the m_event_q + m_event_q.setComparator(new QComparator(m_shape)); + } + + // create the vertex clusters and fill the event structure m_event_q. + // Because most vertices are expected to be non clustered, we create + // clusters only for actual clusters to save some memory. + Point2D cluster_pt = new Point2D(); + cluster_pt.setNaN(); + int cluster = -1; + for (int index = 0, nvertex = event_q.size(); index < nvertex; index++) { + int vertex = event_q.get(index); + Point2D pt = m_shape.getXY(vertex); + if (pt.isEqual(cluster_pt)) { + int vertexCluster = m_shape.getUserIndex(vertex, + m_vertex_cluster_index); + mergeClusters_(cluster, vertexCluster); + continue; + } + + cluster = getClusterFromVertex(vertex); + // add a vertex to the event queue + cluster_pt = m_shape.getXY(vertex); + int eventQnode = m_event_q.addBiggestElement(vertex, -1); // this + // method + // does + // not + // call + // comparator's + // compare, + // assuming + // sorted + // order. + setClusterEventQNode_(cluster, eventQnode); + } + } + + void fillEventQueuePass2() { + AttributeStreamOfInt32 event_q = new AttributeStreamOfInt32(0); + event_q.reserve(m_shape.getTotalPointCount());// temporary structure to + // sort and find + // clusters + for (int node = m_event_q.getFirst(-1); node != -1; node = m_event_q + .getNext(node)) { + int v = m_event_q.getElement(node); + event_q.add(v); + } + + assert (event_q.size() == m_event_q.size(-1)); + + m_event_q.clear(); + + // sort vertices lexicographically. + m_shape.sortVerticesSimpleByY_(event_q, 0, event_q.size()); + + for (int index = 0, nvertex = event_q.size(); index < nvertex; index++) { + int vertex = event_q.get(index); + int cluster = getClusterFromVertex(vertex); + int eventQnode = m_event_q.addBiggestElement(vertex, -1); // this + // method + // does + // not + // call + // comparator's + // compare, + // assuming + // sorted + // order. + setClusterEventQNode_(cluster, eventQnode); + } + } + + // Returns edges already in the sweep structure that are affected by the + // change of cluster coordinate. + void getAffectedEdges(int cluster, AttributeStreamOfInt32 edges) { + int first_edge = getClusterFirstEdge(cluster); + if (first_edge == -1) + return; + + int edge = first_edge; + do { + int sweepNode = getEdgeSweepNode(edge); + if (StridedIndexTypeCollection.isValidElement(sweepNode)) { + edges.add(edge); + } + edge = getNextEdge(edge, cluster); + } while (edge != first_edge); + } + + // Updates all vertices of the cluster to new coordinate + void updateClusterXY(int cluster, Point2D pt) { + int vertexList = getClusterVertices(cluster); + for (int vh = m_cluster_vertices.getFirst(vertexList); vh != -1; vh = m_cluster_vertices + .getNext(vh)) { + int vertex = m_cluster_vertices.getElement(vh); + m_shape.setXY(vertex, pt); + } + } + + // Modifies the given edges given the intersector class and the result + // index. + // The function updates the the event structure and puts new edges into the + // m_edges_to_insert_in_sweep_structure. + void splitEdge_(int edge1, int edge2, int intersectionCluster, + SegmentIntersector intersector) { + // #ifdef _DEBUG_CRACKING_REPORT + // { + // if (edge2 != -1) + // DEBUGPRINTF(L"Splitting edge1 (%d) and edge2 (%d)\n", edge1, edge2); + // else + // DEBUGPRINTF(L"Splitting edge (%d)\n", edge1); + // } + // #endif + // dbg_check_edge_(edge1); + // + // if (edge2 != -1) + // dbg_check_edge_(edge2); + + disconnectEdge_(edge1);// disconnects the edge from the clusters. The + // edge still remembers the clusters. + if (edge2 != -1) + disconnectEdge_(edge2);// disconnects the edge from the clusters. + // The edge still remembers the clusters. + + // Collect all edges that are affected when the clusters change position + // due to snapping + // The edges are collected in m_edges_to_insert_in_sweep_structure. + // Collect the modified clusters in m_modified_clusters. + processSplitHelper1_(0, edge1, intersector); + if (edge2 != -1) + processSplitHelper1_(1, edge2, intersector); + + if (intersectionCluster != -1) { + Point2D pt = intersector.getResultPoint().getXY(); + Point2D ptCluster = getClusterXY(intersectionCluster); + if (!ptCluster.isEqual(pt)) + m_modified_clusters.add(intersectionCluster); + } + + // remove modified clusters from the event queue. We'll reincert them + // later + for (int i = 0, n = m_modified_clusters.size(); i < n; i++) { + int cluster = m_modified_clusters.get(i); + int eventQnode = getClusterEventQNode(cluster); + if (eventQnode != -1) { + m_event_q.deleteNode(eventQnode, -1); + setClusterEventQNode_(cluster, -1); + } + } + + int edgeOrigins1 = getEdgeOriginVertices(edge1); + int edgeOrigins2 = (edge2 != -1) ? getEdgeOriginVertices(edge2) : -1; + + // Adjust the vertex coordinates and split the segments in the the edit + // shape. + applyIntersectorToEditShape_(edgeOrigins1, intersector, 0); + if (edge2 != -1) + applyIntersectorToEditShape_(edgeOrigins2, intersector, 1); + + // Produce clusters, and new edges. The new edges are added to + // m_edges_to_insert_in_sweep_structure. + createEdgesAndClustersFromSplitEdge_(edge1, intersector, 0); + if (edge2 != -1) + createEdgesAndClustersFromSplitEdge_(edge2, intersector, 1); + + m_edge_vertices.deleteList(edgeOrigins1); + deleteEdge_(edge1); + + if (edge2 != -1) { + m_edge_vertices.deleteList(edgeOrigins2); + deleteEdge_(edge2); + } + + // insert clusters into the event queue and the edges into the sweep + // structure. + for (int i = 0, n = m_modified_clusters.size(); i < n; i++) { + int cluster = m_modified_clusters.get(i); + if (cluster == m_sweep_point_cluster) + m_b_sweep_point_cluster_was_modified = true; + + int eventQnode = getClusterEventQNode(cluster); + if (eventQnode == -1) { + int vertex = getClusterFirstVertex(cluster); + assert (getClusterFromVertex(vertex) == cluster); + + // #ifdef _DEBUG_CRACKING_REPORT + // { + // Point_2D pt; + // m_shape->get_xy(vertex, pt); + // DEBUGPRINTF(L"Inserting vertex %d, cluster %d (%0.3f, %0.3f)\n", + // vertex, cluster, pt.x, pt.y); + // } + // #endif + // st_counter_insertions++; + // st_counter_insertions_unique++; + + eventQnode = m_event_q.addUniqueElement(vertex, -1);// O(logN) + // operation + if (eventQnode == -1) {// the cluster is coinciding with another + // one. merge. + int existingNode = m_event_q.getDuplicateElement(-1); + int v = m_event_q.getElement(existingNode); + assert (m_shape.isEqualXY(vertex, v)); + int existingCluster = getClusterFromVertex(v); + // #ifdef _DEBUG_CRACKING_REPORT + // { + // Point_2D pt; + // m_shape->get_xy(v, pt); + // DEBUGPRINTF(L"Already in the queue %d, cluster %d (%0.3f, %0.3f)\n", + // v, existingCluster, pt.x, pt.y); + // } + // #endif + mergeClusters_(existingCluster, cluster); + } else + setClusterEventQNode_(cluster, eventQnode); + } else { + // if already inserted (probably impossible) case + } + } + + m_modified_clusters.clear(false); + } + + // Returns a cluster's xy. + Point2D getClusterXY(int cluster) { + Point2D p = new Point2D(); + int vindex = getClusterVertexIndex(cluster); + m_shape.getXYWithIndex(vindex, p); + return p; + } + + int getClusterFirstVertex(int cluster) { + int vertexList = getClusterVertices(cluster); + int vertex = m_cluster_vertices.getFirstElement(vertexList); + return vertex; + } + + boolean sweepImpl_() { + m_b_sweep_point_cluster_was_modified = false; + m_sweep_point_cluster = -1; + if (m_sweep_comparator == null) { + m_sweep_structure.disableBalancing(); + m_sweep_comparator = new SimplifySweepComparator(this); + m_sweep_structure.setComparator(m_sweep_comparator); + } + + AttributeStreamOfInt32 edgesToDelete = new AttributeStreamOfInt32(0); + SimplifySweepMonikerComparator sweepMoniker = null; + QMonikerComparator moniker = null; + + int iterationCounter = 0; + m_prev_neighbour = -1; + m_next_neighbour = -1; + m_b_continuing_segment_chain_optimization = false; + + int c_2 = StridedIndexTypeCollection.impossibleIndex2(); + int c_3 = StridedIndexTypeCollection.impossibleIndex3(); + assert (c_2 != c_3); + + for (int eventQnode = m_event_q.getFirst(-1); eventQnode != -1;) { + iterationCounter++; + m_b_continuing_segment_chain_optimization = false; + + int vertex = m_event_q.getElement(eventQnode); + m_sweep_point_cluster = getClusterFromVertex(vertex); + m_shape.getXY(vertex, m_sweep_point); + // #ifdef _DEBUG_CRACKING_REPORT + // { + // DEBUGPRINTF(L"next event node. Cluster %d, Vertex %d, (%0.3f, %0.3f)\n", + // m_sweep_point_cluster, vertex, m_sweep_point.x, m_sweep_point.y); + // } + // #endif + + m_sweep_comparator.setSweepY(m_sweep_point.y, m_sweep_point.x);// move + // the + // sweep + // line + + boolean bDisconnectedCluster = false; + {// scope + int first_edge = getClusterFirstEdge(m_sweep_point_cluster); + bDisconnectedCluster = first_edge == -1; + if (!bDisconnectedCluster) { + int edge = first_edge; + do { + int sweepNode = getEdgeSweepNode(edge); + if (sweepNode == -1) { + m_edges_to_insert_in_sweep_structure.add(edge); + setEdgeSweepNode_(edge, c_3);// mark that its in + // m_edges_to_insert_in_sweep_structure + } else if (sweepNode != c_3) { + // assert(StridedIndexTypeCollection.isValidElement(sweepNode)); + edgesToDelete.add(sweepNode); + } + edge = getNextEdge(edge, m_sweep_point_cluster); + } while (edge != first_edge); + } + } + + // st_counter_insertions_peaks += edgesToDelete.size() == 0 && + // m_edges_to_insert_in_sweep_structure.size() > 0; + // First step is to detelete the edges that terminate in the + // cluster. + // During that step we also determine the left and right neighbours + // of the deleted bunch and then check if those left and right + // intersect or not. + if (edgesToDelete.size() > 0) { + m_b_continuing_segment_chain_optimization = (edgesToDelete + .size() == 1 && m_edges_to_insert_in_sweep_structure + .size() == 1); + // st_counter_insertions_all_potential += + // m_edges_to_insert_in_sweep_structure.size() > 0; + + // Mark nodes that need to be deleted by setting c_2 to the + // edge's sweep node member. + for (int i = 0, n = edgesToDelete.size(); i < n; i++) { + int edge = m_sweep_structure.getElement(edgesToDelete + .get(i)); + setEdgeSweepNode_(edge, c_2); + } + + int left = c_2; + int right = c_2; + // Determine left and right nodes for the bunch of nodes we are + // deleting. + for (int i = 0, n = edgesToDelete.size(); i < n; i++) { + int sweepNode = edgesToDelete.get(i); + if (left == c_2) { + int localleft = m_sweep_structure.getPrev(sweepNode); + if (localleft != -1) { + int edge = m_sweep_structure.getElement(localleft); + int node = getEdgeSweepNode(edge); + if (node != c_2) + left = localleft; + } else + left = -1; + } + + if (right == c_2) { + int localright = m_sweep_structure.getNext(sweepNode); + if (localright != -1) { + int edge = m_sweep_structure.getElement(localright); + int node = getEdgeSweepNode(edge); + if (node != c_2) + right = localright; + } else + right = -1; + } + + if (left != c_2 && right != c_2) + break; + } + + assert (left != c_2 && right != c_2); + // Now delete the bunch. + for (int i = 0, n = edgesToDelete.size(); i < n; i++) { + int sweepNode = edgesToDelete.get(i); + int edge = m_sweep_structure.getElement(sweepNode); + m_sweep_structure.deleteNode(sweepNode, -1); + setEdgeSweepNode_(edge, -1); + } + + edgesToDelete.clear(false); + + m_prev_neighbour = left != -1 ? left : -1; + m_next_neighbour = right != -1 ? right : -1; + + // Now check if the left and right we found intersect or not. + if (left != -1 && right != -1) { + boolean bIntersected = checkAndFixIntersection_(left, right); + if (bIntersected) { + // We'll insert the results of intersection below in + // insert_new_edges_ + m_b_continuing_segment_chain_optimization = false; + } + } else { + if ((left == -1) && (right == -1)) + m_b_continuing_segment_chain_optimization = false; + } + } else { + // edgesToDelete.size() == 0 - nothing to delete here. This is a + // cluster which has all edges directed up or a disconnected + // cluster. + + if (bDisconnectedCluster) {// check standalone cluster (point or + // multipoint) if it cracks an edge. + if (sweepMoniker == null) + sweepMoniker = new SimplifySweepMonikerComparator(this); + + sweepMoniker.setPoint(m_sweep_point); + m_sweep_structure.searchUpperBound(sweepMoniker, -1); + if (sweepMoniker.intersectionDetected()) { + sweepMoniker.clearIntersectionDetectedFlag(); + fixIntersectionPointSegment_(m_sweep_point_cluster, + sweepMoniker.getCurrentNode()); + } + } + } + + // Now insert edges that start at the cluster and go up + insertNewEdges_(); + + if (m_b_sweep_point_cluster_was_modified) { + m_b_sweep_point_cluster_was_modified = false; + if (moniker == null) + moniker = new QMonikerComparator(m_shape); + moniker.setPoint(m_sweep_point); + eventQnode = m_event_q.searchUpperBound(moniker, -1); + } else + eventQnode = m_event_q.getNext(eventQnode); + } + + return m_b_cracked; + } + + void setEditShape_(EditShape shape) { + // Populate the cluster and edge structures. + m_shape = shape; + m_vertex_cluster_index = m_shape.createUserIndex(); + + m_edges.setCapacity(shape.getTotalPointCount() + 32); + + m_clusters.setCapacity(shape.getTotalPointCount()); + + m_cluster_vertices.reserveLists(shape.getTotalPointCount()); + m_cluster_vertices.reserveNodes(shape.getTotalPointCount()); + + m_edge_vertices.reserveLists(shape.getTotalPointCount() + 32); + m_edge_vertices.reserveNodes(shape.getTotalPointCount() + 32); + + for (int geometry = m_shape.getFirstGeometry(); geometry != -1; geometry = m_shape + .getNextGeometry(geometry)) { + boolean bMultiPath = Geometry.isMultiPath(m_shape + .getGeometryType(geometry)); + + if (!bMultiPath) {// for multipoints do not add edges. + assert (m_shape.getGeometryType(geometry) == Geometry.GeometryType.MultiPoint); + + for (int path = m_shape.getFirstPath(geometry); path != -1; path = m_shape + .getNextPath(path)) { + int vertex = m_shape.getFirstVertex(path); + for (int i = 0, n = m_shape.getPathSize(path); i < n; i++) { + // int cluster + newCluster_(vertex); + vertex = m_shape.getNextVertex(vertex); + } + } + continue; + } + + for (int path = m_shape.getFirstPath(geometry); path != -1; path = m_shape + .getNextPath(path)) { + int path_size = m_shape.getPathSize(path); + assert (path_size > 1); + int first_vertex = m_shape.getFirstVertex(path); + // #ifdef _DEBUG_TOPO + // LOCALREFCLASS(Line, line); + // m_shape.query_line_connector(first_vertex, line); + // assert(line.calculate_length_2D() > m_tolerance);//no + // degenerate lines at the input + // #endif + + // first------------------ + int firstCluster = newCluster_(first_vertex); + int first_edge = newEdge_(first_vertex); + addEdgeToCluster(first_edge, firstCluster); + int prevEdge = first_edge; + int vertex = m_shape.getNextVertex(first_vertex); + for (int index = 0, n = path_size - 2; index < n; index++) { + // ------------x------------ + int cluster = newCluster_(vertex); + addEdgeToCluster(prevEdge, cluster); + int newEdge = newEdge_(vertex); + addEdgeToCluster(newEdge, cluster); + prevEdge = newEdge; + vertex = m_shape.getNextVertex(vertex); + } + + // ------------------lastx + int cluster = newCluster_(vertex); + addEdgeToCluster(prevEdge, cluster); + if (m_shape.isClosedPath(path)) {// close the path + // lastx------------------firstx + int newEdge = newEdge_(vertex); + addEdgeToCluster(newEdge, cluster); + addEdgeToCluster(newEdge, firstCluster); + } + } + } + + fillEventQueue(); + + // int perPoint = estimate_memory_size() / + // m_shape.get_total_point_count(); + // perPoint = 0; + } +} diff --git a/src/com/esri/core/geometry/Point.java b/src/com/esri/core/geometry/Point.java new file mode 100644 index 00000000..29d0263f --- /dev/null +++ b/src/com/esri/core/geometry/Point.java @@ -0,0 +1,602 @@ +/* + Copyright 1995-2013 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ + +package com.esri.core.geometry; + +import com.esri.core.geometry.VertexDescription.Semantics; +import java.io.Serializable; + +/** + * A Point is a zero-dimensional object that represents a specific (X,Y) + * location in a two-dimensional XY-Plane. In case of Geographic Coordinate + * Systems, the X coordinate is the longitude and the Y is the latitude. + */ +public final class Point extends Geometry implements Serializable { + private static final long serialVersionUID = 2L;// TODO:remove as we use + // writeReplace and + // GeometrySerializer + + double[] m_attributes; // use doubles to store everything (long are bitcast) + + /** + * Creates an empty 2D point. + */ + public Point() { + m_description = VertexDescriptionDesignerImpl.getDefaultDescriptor2D(); + } + + Point(VertexDescription vd) { + if (vd == null) + throw new IllegalArgumentException(); + m_description = vd; + } + + /** + * Creates a 2D Point with specified X and Y coordinates. In case of + * Geographic Coordinate Systems, the X coordinate is the longitude and the + * Y is the latitude. + * + * @param x + * The X coordinate of the new 2D point. + * @param y + * The Y coordinate of the new 2D point. + */ + public Point(double x, double y) { + m_description = VertexDescriptionDesignerImpl.getDefaultDescriptor2D(); + setXY(x, y); + } + + /** + * Creates a 3D point with specified X, Y and Z coordinates. In case of + * Geographic Coordinate Systems, the X coordinate is the longitude and the + * Y is the latitude. + * + * @param x + * The X coordinate of the new 3D point. + * @param y + * The Y coordinate of the new 3D point. + * @param z + * The Z coordinate of the new 3D point. + * + */ + public Point(double x, double y, double z) { + m_description = VertexDescriptionDesignerImpl.getDefaultDescriptor2D(); + Point3D pt = new Point3D(); + pt.setCoords(x, y, z); + setXYZ(pt); + + } + + /** + * Returns XY coordinates of this point. + */ + Point2D getXY() { + if (isEmptyImpl()) + throw new GeometryException( + "This operation should not be performed on an empty geometry."); + + Point2D pt = new Point2D(); + pt.setCoords(m_attributes[0], m_attributes[1]); + return pt; + } + + /** + * Sets the XY coordinates of this point. param pt The point to create the X + * and Y coordinate from. + */ + void setXY(Point2D pt) { + _touch(); + setXY(pt.x, pt.y); + } + + /** + * Returns XYZ coordinates of the point. Z will be set to 0 if Z is missing. + */ + Point3D getXYZ() { + if (isEmptyImpl()) + throw new GeometryException( + "This operation should not be performed on an empty geometry."); + + Point3D pt = new Point3D(); + pt.x = m_attributes[0]; + pt.y = m_attributes[1]; + if (m_description.hasZ()) + pt.z = m_attributes[2]; + else + pt.z = VertexDescription.getDefaultValue(Semantics.Z); + + return pt; + } + + /** + * Sets the XYZ coordinates of this point. + * + * @param pt + * The point to create the XYZ coordinate from. + */ + void setXYZ(Point3D pt) { + _touch(); + boolean bHasZ = hasAttribute(Semantics.Z); + if (!bHasZ && !VertexDescription.isDefaultValue(Semantics.Z, pt.z)) {// add + // Z + // only + // if + // pt.z + // is + // not + // a + // default + // value. + addAttribute(Semantics.Z); + bHasZ = true; + } + + if (m_attributes == null) + _setToDefault(); + + m_attributes[0] = pt.x; + m_attributes[1] = pt.y; + if (bHasZ) + m_attributes[2] = pt.z; + } + + /** + * Returns the X coordinate of the point. + */ + public final double getX() { + if (isEmptyImpl()) + throw new GeometryException( + "This operation should not be performed on an empty geometry."); + + return m_attributes[0]; + } + + /** + * Sets the X coordinate of the point. + * + * @param x + * The X coordinate to be set for this point. + */ + public void setX(double x) { + setAttribute(Semantics.POSITION, 0, x); + } + + /** + * Returns the Y coordinate of this point. + */ + public final double getY() { + if (isEmptyImpl()) + throw new GeometryException( + "This operation should not be performed on an empty geometry."); + + return m_attributes[1]; + } + + /** + * Sets the Y coordinate of this point. + * + * @param y + * The Y coordinate to be set for this point. + */ + public void setY(double y) { + setAttribute(Semantics.POSITION, 1, y); + } + + /** + * Returns the Z coordinate of this point. + */ + public double getZ() { + return getAttributeAsDbl(Semantics.Z, 0); + } + + /** + * Sets the Z coordinate of this point. + * + * @param z + * The Z coordinate to be set for this point. + */ + public void setZ(double z) { + setAttribute(Semantics.Z, 0, z); + } + + /** + * Returns the attribute M of this point. + */ + public double getM() { + return getAttributeAsDbl(Semantics.M, 0); + } + + /** + * Sets the M coordinate of this point. + * + * @param m + * The M coordinate to be set for this point. + */ + public void setM(double m) { + setAttribute(Semantics.M, 0, m); + } + + /** + * Returns the ID of this point. + */ + public int getID() { + return getAttributeAsInt(Semantics.ID, 0); + } + + /** + * Sets the ID of this point. + * + * @param id + * The ID of this point. + */ + public void setID(int id) { + setAttribute(Semantics.ID, 0, id); + } + + /** + * Returns value of the given vertex attribute's ordinate. + * + * @param semantics + * The attribute semantics. + * @param ordinate + * The attribute's ordinate. For example, the Y coordinate of the + * NORMAL has ordinate of 1. + * @return The ordinate as double value. + */ + public double getAttributeAsDbl(int semantics, int ordinate) { + if (isEmptyImpl()) + throw new GeometryException( + "This operation was performed on an Empty Geometry."); + + int ncomps = VertexDescription.getComponentCount(semantics); + if (ordinate >= ncomps) + throw new IndexOutOfBoundsException(); + + int attributeIndex = m_description.getAttributeIndex(semantics); + if (attributeIndex >= 0) + return m_attributes[m_description + ._getPointAttributeOffset(attributeIndex) + ordinate]; + else + return VertexDescription.getDefaultValue(semantics); + } + + /** + * Returns value of the given vertex attribute's ordinate. The ordinate is + * always 0 because integer attributes always have one component. + * + * @param semantics + * The attribute semantics. + * @param ordinate + * The attribute's ordinate. For example, the y coordinate of the + * NORMAL has ordinate of 1. + * @return The ordinate value truncated to a 32 bit integer value. + */ + public int getAttributeAsInt(int semantics, int ordinate) { + if (isEmptyImpl()) + throw new GeometryException( + "This operation was performed on an Empty Geometry."); + + int ncomps = VertexDescription.getComponentCount(semantics); + if (ordinate >= ncomps) + throw new IndexOutOfBoundsException(); + + int attributeIndex = m_description.getAttributeIndex(semantics); + if (attributeIndex >= 0) + return (int) m_attributes[m_description + ._getPointAttributeOffset(attributeIndex) + ordinate]; + else + return (int) VertexDescription.getDefaultValue(semantics); + } + + /** + * Sets the value of the attribute. + * + * @param semantics + * The attribute semantics. + * @param ordinate + * The ordinate of the attribute. + * @param value + * Is the array to write values to. The attribute type and the + * number of elements must match the persistence type, as well as + * the number of components of the attribute. + */ + public void setAttribute(int semantics, int ordinate, double value) { + _touch(); + int ncomps = VertexDescription.getComponentCount(semantics); + if (ncomps < ordinate) + throw new IndexOutOfBoundsException(); + + int attributeIndex = m_description.getAttributeIndex(semantics); + if (attributeIndex < 0) { + addAttribute(semantics); + attributeIndex = m_description.getAttributeIndex(semantics); + } + + if (m_attributes == null) + _setToDefault(); + + m_attributes[m_description._getPointAttributeOffset(attributeIndex) + + ordinate] = value; + } + + public void setAttribute(int semantics, int ordinate, int value) { + setAttribute(semantics, ordinate, (double) value); + } + + @Override + public Geometry.Type getType() { + return Type.Point; + } + + @Override + public int getDimension() { + return 0; + } + + @Override + public void setEmpty() { + _touch(); + if (m_attributes != null) { + m_attributes[0] = NumberUtils.NaN(); + m_attributes[1] = NumberUtils.NaN(); + } + } + + @Override + void _afterAddAttributeImpl(int semantics) { + _touch(); + if (m_attributes == null) + return; + + int attributeIndex = m_description.getAttributeIndex(semantics); + int offset = m_description._getPointAttributeOffset(attributeIndex); + int comps = VertexDescription.getComponentCount(semantics); + int totalComps = m_description._getTotalComponents(); + resizeAttributes(totalComps); + + for (int i = totalComps - 1; i >= offset + comps; i--) + m_attributes[i] = m_attributes[i - comps]; + + double dv = VertexDescription.getDefaultValue(semantics); + for (int i = 0; i < comps; i++) + m_attributes[offset + i] = dv; + } + + @Override + void _beforeDropAttributeImpl(int semantics) { + _touch(); + if (m_attributes == null) + return; + + // _ASSERT(semantics != enum_value2(VertexDescription, Semantics, + // POSITION)); + int attributeIndex = m_description.getAttributeIndex(semantics); + int offset = m_description._getPointAttributeOffset(attributeIndex); + int comps = VertexDescription.getComponentCount(semantics); + int totalCompsOld = m_description._getTotalComponents(); + if (totalCompsOld > comps) { + for (int i = offset + comps; i < totalCompsOld; i++) + m_attributes[i - comps] = m_attributes[i]; + } + } + + /** + * Sets the Point to a default, non-empty state. + */ + void _setToDefault() { + resizeAttributes(m_description._getTotalComponents()); + Point.attributeCopy(m_description._getDefaultPointAttributes(), + m_attributes, m_description._getTotalComponents()); + m_attributes[0] = NumberUtils.NaN(); + m_attributes[1] = NumberUtils.NaN(); + } + + @Override + public void applyTransformation(Transformation2D transform) { + if (isEmptyImpl()) + return; + + Point2D pt = getXY(); + transform.transform(pt, pt); + setXY(pt); + } + + @Override + void applyTransformation(Transformation3D transform) { + if (isEmptyImpl()) + return; + + addAttribute(Semantics.Z); + Point3D pt = getXYZ(); + setXYZ(transform.transform(pt)); + } + + @Override + public void copyTo(Geometry dst) { + if (dst.getType() != Type.Point) + throw new IllegalArgumentException(); + + Point pointDst = (Point) dst; + dst._touch(); + + if (m_attributes == null) { + pointDst.setEmpty(); + pointDst.m_attributes = null; + pointDst.assignVertexDescription(m_description); + } else { + pointDst.assignVertexDescription(m_description); + pointDst.resizeAttributes(m_description._getTotalComponents()); + attributeCopy(m_attributes, pointDst.m_attributes, + m_description._getTotalComponents()); + } + } + + @Override + public Geometry createInstance() { + Point point = new Point(m_description); + return point; + } + + @Override + public boolean isEmpty() { + return isEmptyImpl(); + } + + final boolean isEmptyImpl() { + return ((m_attributes == null) || NumberUtils.isNaN(m_attributes[0]) || NumberUtils + .isNaN(m_attributes[1])); + } + + @Override + public void queryEnvelope(Envelope env) { + env.setEmpty(); + if (m_description != env.m_description) + env.assignVertexDescription(m_description); + env.merge(this); + } + + @Override + void queryEnvelope2D(Envelope2D env) { + + if (isEmptyImpl()) { + env.setEmpty(); + return; + } + + env.xmin = m_attributes[0]; + env.ymin = m_attributes[1]; + env.xmax = m_attributes[0]; + env.ymax = m_attributes[1]; + } + + @Override + void queryEnvelope3D(Envelope3D env) { + if (isEmptyImpl()) { + env.setEmpty(); + return; + } + + Point3D pt = getXYZ(); + env.xmin = pt.x; + env.ymin = pt.y; + env.zmin = pt.z; + env.xmax = pt.x; + env.ymax = pt.y; + env.zmax = pt.z; + } + + @Override + public Envelope1D queryInterval(int semantics, int ordinate) { + Envelope1D env = new Envelope1D(); + if (isEmptyImpl()) { + env.setEmpty(); + return env; + } + + double s = getAttributeAsDbl(semantics, ordinate); + env.vmin = s; + env.vmax = s; + return env; + } + + private void resizeAttributes(int newSize) { + if (m_attributes == null) { + m_attributes = new double[newSize]; + } else if (m_attributes.length < newSize) { + double[] newbuffer = new double[newSize]; + System.arraycopy(m_attributes, 0, newbuffer, 0, m_attributes.length); + m_attributes = newbuffer; + } + } + + static void attributeCopy(double[] src, double[] dst, int count) { + for (int i = 0; i < count; i++) + dst[i] = src[i]; + } + + /** + * Set the X and Y coordinate of the point. + * + * @param x + * X coordinate of the point. + * @param y + * Y coordinate of the point. + */ + public void setXY(double x, double y) { + _touch(); + + if (m_attributes == null) + _setToDefault(); + + m_attributes[0] = x; + m_attributes[1] = y; + } + + /** + * Returns TRUE when this geometry has exactly same type, properties, and + * coordinates as the other geometry. + */ + @Override + public boolean equals(Object _other) { + if (_other == this) + return true; + + if (!(_other instanceof Point)) + return false; + + Point otherPt = (Point) _other; + + if (m_description != otherPt.m_description) + return false; + + if (isEmptyImpl()) + if (otherPt.isEmptyImpl()) + return true; + else + return false; + + for (int i = 0, n = m_description._getTotalComponents(); i < n; i++) + if (m_attributes[i] != otherPt.m_attributes[i]) + return false; + + return true; + } + + /** + * Returns the hash code for the point. + */ + + @Override + public int hashCode() { + int hashCode = m_description.hashCode(); + if (!isEmptyImpl()) { + for (int i = 0, n = m_description._getTotalComponents(); i < n; i++) { + long bits = Double.doubleToLongBits(m_attributes[i]); + int hc = (int) (bits ^ (bits >>> 32)); + hashCode = NumberUtils.hash(hashCode, hc); + } + } + return hashCode; + } +} diff --git a/src/com/esri/core/geometry/Point2D.java b/src/com/esri/core/geometry/Point2D.java new file mode 100644 index 00000000..35979c74 --- /dev/null +++ b/src/com/esri/core/geometry/Point2D.java @@ -0,0 +1,442 @@ +/* + Copyright 1995-2013 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ + + +package com.esri.core.geometry; + +import java.math.BigDecimal; +import java.util.Comparator; + +/** + * + * Basic 2D point class. Contains only two double fields. + * + */ +final class Point2D { + + public double x; + public double y; + + public Point2D() { + } + + public Point2D(double x, double y) { + this.x = x; + this.y = y; + } + + // Header definitions + public static Point2D construct(double x, double y) { + return new Point2D(x, y); + } + + public void setCoords(double x, double y) { + this.x = x; + this.y = y; + } + + public void setCoords(Point2D other) { + x = other.x; + y = other.y; + } + + public boolean isEqual(Point2D other) { + return x == other.x && y == other.y; + } + + boolean isEqual(Point2D other, double tol) { + return (Math.abs(x - other.x) <= tol) && (Math.abs(y - other.y) <= tol); + } + + public void sub(Point2D other) { + x -= other.x; + y -= other.y; + } + + void sub(Point2D p1, Point2D p2) { + x = p1.x - p2.x; + y = p1.y - p2.y; + } + + public void add(Point2D other) { + x += other.x; + y += other.y; + } + + public void add(Point2D p1, Point2D p2) { + x = p1.x + p2.x; + y = p1.y + p2.y; + } + + public void negate() { + x = -x; + y = -y; + } + + public void negate(Point2D other) { + x = -other.x; + y = -other.y; + } + + public void interpolate(Point2D other, double alpha) { + x = x * (1.0 - alpha) + other.x * alpha; + y = y * (1.0 - alpha) + other.y * alpha; + } + + public void interpolate(Point2D p1, Point2D p2, double alpha) { + x = p1.x * (1.0 - alpha) + p2.x * alpha; + y = p1.y * (1.0 - alpha) + p2.y * alpha; + } + + public void scaleAdd(double f, Point2D shift) { + x = x * f + shift.x; + y = y * f + shift.y; + } + + public void scaleAdd(double f, Point2D other, Point2D shift) { + x = other.x * f + shift.x; + y = other.y * f + shift.y; + } + + public void scale(double f, Point2D other) { + x = f * other.x; + y = f * other.y; + } + + public void scale(double f) { + x *= f; + y *= f; + } + + /** + * Compares two vertices lexicographicaly. + */ + int compare(Point2D other) { + return y < other.y ? -1 : (y > other.y ? 1 : (x < other.x ? -1 + : (x > other.x ? 1 : 0))); + } + + void normalize(Point2D other) { + double len = other.length(); + if (len == 0) { + x = 1.0; + y = 0.0; + } else { + x = other.x / len; + y = other.y / len; + } + } + + public void normalize() { + double len = length(); + if (len == 0)// (!len) + { + x = 1.0; + y = 0.0; + } + x /= len; + y /= len; + } + + public double length() { + return Math.sqrt(x * x + y * y); + } + + double sqrLength() { + return x * x + y * y; + } + + static double distance(Point2D pt1, Point2D pt2) { + return Math.sqrt(sqrDistance(pt1, pt2)); + } + + double dotProduct(Point2D other) { + return x * other.x + y * other.y; + } + + double _dotProductAbs(Point2D other) { + return Math.abs(x * other.x) + Math.abs(y * other.y); + } + + double crossProduct(Point2D other) { + return x * other.y - y * other.x; + } + + void rotateDirect(double Cos, double Sin) // corresponds to the + // Transformation2D.SetRotate(cos, + // sin).Transform(pt) + { + double xx = x * Cos - y * Sin; + double yy = x * Sin + y * Cos; + x = xx; + y = yy; + } + + void rotateReverse(double Cos, double Sin) { + double xx = x * Cos + y * Sin; + double yy = -x * Sin + y * Cos; + x = xx; + y = yy; + } + + /** + * 90 degree rotation, anticlockwise. Equivalent to RotateDirect(cos(pi/2), + * sin(pi/2)). + */ + void leftPerpendicular() { + double xx = x; + x = -y; + y = xx; + } + + /** + * 90 degree rotation, anticlockwise. Equivalent to RotateDirect(cos(pi/2), + * sin(pi/2)). + */ + void leftPerpendicular(Point2D pt) { + x = -pt.y; + y = pt.x; + } + + /** + * 270 degree rotation, anticlockwise. Equivalent to + * RotateDirect(-cos(pi/2), sin(-pi/2)). + */ + void rightPerpendicular() { + double xx = x; + x = y; + y = -xx; + } + + /** + * 270 degree rotation, anticlockwise. Equivalent to + * RotateDirect(-cos(pi/2), sin(-pi/2)). + */ + void rightPerpendicular(Point2D pt) { + x = pt.y; + y = -pt.x; + } + + void _setNan() { + x = NumberUtils.NaN(); + y = NumberUtils.NaN(); + } + + boolean _isNan() { + return NumberUtils.isNaN(x); + } + + // calculates which quarter of xy plane the vector lies in. First quater is + // between vectors (1,0) and (0, 1), second between (0, 1) and (-1, 0), etc. + // Angle intervals corresponding to quarters: 1 : [0 : 90); 2 : [90 : 180); + // 3 : [180 : 270); 4 : [270 : 360) + int _getQuarter() { + // _ASSERT(x != 0 || y != 0 || !NumberUtils.isNaN(x) || + // !NumberUtils.isNaN(y)); + if (x > 0) { + if (y >= 0) + return 1; // x > 0 && y <= 0 + else + return 4; // y < 0 && x > 0. Should be x >= 0 && y < 0. The x == + // 0 case is processed later. + } else { + if (y > 0) + return 2; // x <= 0 && y > 0 + else + return x == 0 ? 4 : 3; // 3: x < 0 && y <= 0. The case x == 0 && + // y <= 0 is attribute to the case 4. + // The point x==0 and y==0 is a bug, but + // will be assigned to 4. + } + } + + // Assume vector v1 and v2 have same origin. The function compares the + // vectors by angle from the x acis to the vector in the counter clockwise + // direction. + // > > + // \ / + // V3 \ / V1 + // ----------------/-------------------->X In this example, + // __compareVectors(V1, V2) == -1. + // \ _compareVectors(V1, V3) == -1 + // \ V2 _compareVectors(V2, V3) == 1 + // > + static int _compareVectors(Point2D v1, Point2D v2) { + int q1 = v1._getQuarter(); + int q2 = v2._getQuarter(); + + if (q2 == q1) { + double cross = v1.crossProduct(v2); + return cross < 0 ? 1 : (cross > 0 ? -1 : 0); + } else + return q1 < q2 ? -1 : 1; + } + + static class CompareVectors implements Comparator { + @Override + public int compare(Point2D v1, Point2D v2) { + return _compareVectors((Point2D) v1, (Point2D) v2); + } + } + + // Header definitions + + // public Point2D mul(double factor) { + // return new Point2D(x * factor, y * factor); + // } + + public static double sqrDistance(Point2D pt1, Point2D pt2) { + double dx = pt1.x - pt2.x; + double dy = pt1.y - pt2.y; + return dx * dx + dy * dy; + } + + // Header definitions + + // Cpp definitions + + @Override + public String toString() { + return "(" + x + " , " + y + ")"; + } + + public void setNaN() { + x = NumberUtils.NaN(); + y = NumberUtils.NaN(); + } + + public boolean isNaN() { + return NumberUtils.isNaN(x) || NumberUtils.isNaN(y); + } + + // metric = 1: Manhattan metric + // 2: Euclidian metric (default) + // 0: used for L-infinite (max(fabs(x), fabs(y)) + // for predefined metrics, use the DistanceMetricEnum defined in WKSPoint.h + double _norm(int metric) { + if (metric < 0 || _isNan()) + return NumberUtils.NaN(); + + switch (metric) { + case 0: // L-infinite + return Math.abs(x) >= Math.abs(y) ? Math.abs(x) : Math.abs(y); + + case 1: // L1 or Manhattan metric + return Math.abs(x) + Math.abs(y); + + case 2: // L2 or Euclidean metric + return Math.sqrt(x * x + y * y); + + default: + return Math + .pow(Math.pow(x, (double) metric) + + Math.pow(y, (double) metric), + 1.0 / (double) metric); + } + } + + /** + * returns signed distance of point from infinite line represented by + * pt_1...pt_2. The returned distance is positive if this point lies on the + * right-hand side of the line, negative otherwise. If the two input points + * are equal, the (positive) distance of this point to p_1 is returned. + */ + double offset(/* const */Point2D pt1, /* const */Point2D pt2) { + double newDistance = distance(pt1, pt2); + Point2D p = construct(x, y); + if (newDistance == 0.0) + return distance(p, pt1); + + // get vectors relative to pt_1 + Point2D p2 = new Point2D(); + p2.setCoords(pt2); + p2.sub(pt1); + p.sub(pt1); + + double cross = p.crossProduct(p2); + return cross / newDistance; + } + + /** + * Calculates the orientation of the triangle formed by p->q->r. Returns 1 + * for counter-clockwise, -1 for clockwise, and 0 for collinear. May use + * high precision arithmetics for some special degenerate cases. + */ + static int orientationRobust(Point2D p, Point2D q, Point2D r) { + ECoordinate det_ec = new ECoordinate(); + det_ec.set(q.x); + det_ec.sub(p.x); + + ECoordinate rp_y_ec = new ECoordinate(); + rp_y_ec.set(r.y); + rp_y_ec.sub(p.y); + + ECoordinate qp_y_ec = new ECoordinate(); + qp_y_ec.set(q.y); + qp_y_ec.sub(p.y); + + ECoordinate rp_x_ec = new ECoordinate(); + rp_x_ec.set(r.x); + rp_x_ec.sub(p.x); + + det_ec.mul(rp_y_ec); + qp_y_ec.mul(rp_x_ec); + det_ec.sub(qp_y_ec); + + if (!det_ec.isFuzzyZero()) { + double det_ec_value = det_ec.value(); + + if (det_ec_value < 0.0) + return -1; + + if (det_ec_value > 0.0) + return 1; + + return 0; + } + + // Need extended precision + + BigDecimal det_mp = new BigDecimal(q.x); + BigDecimal px_mp = new BigDecimal(p.x); + BigDecimal py_mp = new BigDecimal(p.y); + det_mp = det_mp.subtract(px_mp); + + BigDecimal rp_y_mp = new BigDecimal(r.y); + rp_y_mp = rp_y_mp.subtract(py_mp); + + BigDecimal qp_y_mp = new BigDecimal(q.y); + qp_y_mp = qp_y_mp.subtract(py_mp); + + BigDecimal rp_x_mp = new BigDecimal(r.x); + rp_x_mp = rp_x_mp.subtract(px_mp); + + det_mp = det_mp.multiply(rp_y_mp); + qp_y_mp = qp_y_mp.multiply(rp_x_mp); + det_mp = det_mp.subtract(qp_y_mp); + + return det_mp.signum(); + } + +} diff --git a/src/com/esri/core/geometry/Point3D.java b/src/com/esri/core/geometry/Point3D.java new file mode 100644 index 00000000..34a901fc --- /dev/null +++ b/src/com/esri/core/geometry/Point3D.java @@ -0,0 +1,97 @@ +/* + Copyright 1995-2013 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ + + +package com.esri.core.geometry; + +/** + * + * Basic 3D point class. + * + */ +final class Point3D { + public double x; + public double y; + public double z; + + public Point3D() { + } + + public static Point3D construct(double x, double y, double z) { + Point3D pt = new Point3D(); + pt.x = x; + pt.y = y; + pt.z = z; + return pt; + } + + public void setCoords(double x, double y, double z) { + this.x = x; + this.y = y; + this.z = z; + } + + public void setZero() { + x = 0.0; + y = 0.0; + z = 0.0; + } + + public void normalize() { + double len = length(); + if (len != 0) + return; + + x /= len; + y /= len; + z /= len; + } + + double length() { + return Math.sqrt(x * x + y * y + z * z); + } + + public Point3D(double x, double y, double z) { + this.x = x; + this.y = y; + this.z = z; + } + + Point3D sub(Point3D other) { + return new Point3D(x - other.x, y - other.y, z - other.z); + } + + Point3D mul(double factor) { + return new Point3D(x * factor, y * factor, z * factor); + } + + void _setNan() { + x = NumberUtils.NaN(); + } + + boolean _isNan() { + return NumberUtils.isNaN(x); + } + +} diff --git a/src/com/esri/core/geometry/PointInPolygonHelper.java b/src/com/esri/core/geometry/PointInPolygonHelper.java new file mode 100644 index 00000000..1d4e6986 --- /dev/null +++ b/src/com/esri/core/geometry/PointInPolygonHelper.java @@ -0,0 +1,359 @@ +/* + Copyright 1995-2013 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ + +package com.esri.core.geometry; + +class PointInPolygonHelper { + + private Point2D m_inputPoint; + private int m_windnum; + private SegmentBuffer[] m_monotoneParts = null; + private double[] m_xOrds = null; + private double m_tolerance; + private double m_toleranceSqr; + private double m_miny; + private double m_maxy; + private boolean m_bAlternate; + private boolean m_bTestBorder; + private boolean m_bBreak; + private boolean m_bPointInAnyOuterRingTest; + + private int result() { + return m_windnum != 0 ? 1 : 0; + } + + private boolean _testBorder(Segment seg) { + double t = seg.getClosestCoordinate(m_inputPoint, false); + Point2D pt = seg.getCoord2D(t); + if (Point2D.sqrDistance(pt, m_inputPoint) <= m_toleranceSqr) { + return true; + } + return false; + } + + private void doOne(Segment seg) { + if (!m_bTestBorder) { + // test if point is on the boundary + if (m_bAlternate && m_inputPoint.isEqual(seg.getStartXY()) + || m_inputPoint.isEqual(seg.getEndXY())) {// test if the + // point + // coincides + // with a vertex + m_bBreak = true; + return; + } + } + + if (seg.getStartY() == m_inputPoint.y + && seg.getStartY() == seg.getEndY()) {// skip horizontal + // segments. test if the + // point lies on a + // horizontal segment + if (m_bAlternate && !m_bTestBorder) { + double minx = Math.min(seg.getStartX(), seg.getEndX()); + double maxx = Math.max(seg.getStartX(), seg.getEndX()); + if (m_inputPoint.x > minx && m_inputPoint.x < maxx) + m_bBreak = true; + } + + return;// skip horizontal segments + } + + boolean bToTheRight = false; + double maxx = Math.max(seg.getStartX(), seg.getEndX()); + if (m_inputPoint.x > maxx) { + bToTheRight = true; + } else { + if (m_inputPoint.x >= Math.min(seg.getStartX(), seg.getEndX())) { + int n = seg.intersectionWithAxis2D(true, m_inputPoint.y, + m_xOrds, null); + bToTheRight = n > 0 && m_xOrds[0] <= m_inputPoint.x; + } + } + + if (bToTheRight) { + // to prevent double counting, when the ray crosses a vertex, count + // only the segments that are below the ray. + if (m_inputPoint.y == seg.getStartXY().y) { + if (m_inputPoint.y < seg.getEndXY().y) + return; + } else if (m_inputPoint.y == seg.getEndXY().y) { + if (m_inputPoint.y < seg.getStartXY().y) + return; + } + + if (m_bAlternate) + m_windnum ^= 1; + else + m_windnum += (seg.getStartXY().y > seg.getEndXY().y) ? 1 : -1; + } + } + + public PointInPolygonHelper(boolean bFillRule_Alternate, + Point2D inputPoint, double tolerance) { + // //_ASSERT(tolerance >= 0); + m_inputPoint = inputPoint; + m_miny = inputPoint.y - tolerance; + m_maxy = inputPoint.y + tolerance; + m_windnum = 0; + m_bAlternate = bFillRule_Alternate; + m_tolerance = tolerance; + m_toleranceSqr = tolerance * tolerance; + m_bTestBorder = tolerance != 0;// + m_bBreak = false; + } + + private boolean processSegment(Segment segment) { + Envelope1D yrange = segment.queryInterval( + (int) VertexDescription.Semantics.POSITION, 1); + if (yrange.vmin > m_maxy || yrange.vmax < m_miny) { + return false; + } + + if (m_bTestBorder && _testBorder(segment)) + return true; + + if (yrange.vmin > m_inputPoint.y || yrange.vmax < m_inputPoint.y) { + return false; + } + + if (m_monotoneParts == null) + m_monotoneParts = new SegmentBuffer[5]; + if (m_xOrds == null) + m_xOrds = new double[3]; + + int nparts = segment.getYMonotonicParts(m_monotoneParts); + if (nparts > 0) {// the segment is a curve and has been broken in + // ymonotone parts + for (int i = 0; i < nparts; i++) { + Segment part = m_monotoneParts[i].get(); + doOne(part); + if (m_bBreak) + return true; + } + } else {// the segment is a line or it is y monotone curve + doOne(segment); + if (m_bBreak) + return true; + } + + return false; + } + + private static int _isPointInPolygonInternal(Polygon inputPolygon, + Point2D inputPoint, double tolerance) { + + boolean bAltenate = true;// Path.get_FillMode() == fillmodeAlternate; + PointInPolygonHelper helper = new PointInPolygonHelper(bAltenate, + inputPoint, tolerance); + MultiPathImpl mpImpl = (MultiPathImpl) inputPolygon._getImpl(); + SegmentIteratorImpl iter = mpImpl.querySegmentIterator(); + while (iter.nextPath()) { + while (iter.hasNextSegment()) { + Segment segment = iter.nextSegment(); + if (helper.processSegment(segment)) + return -1; // point on boundary + } + } + + return helper.result(); + } + + private static int _isPointInPolygonInternalWithQuadTree( + Polygon inputPolygon, QuadTreeImpl quadTree, Point2D inputPoint, + double tolerance) { + Envelope2D envPoly = new Envelope2D(); + inputPolygon.queryLooseEnvelope(envPoly); + envPoly.inflate(tolerance, tolerance); + + boolean bAltenate = true;// Path.get_FillMode() == fillmodeAlternate; + PointInPolygonHelper helper = new PointInPolygonHelper(bAltenate, + inputPoint, tolerance); + + MultiPathImpl mpImpl = (MultiPathImpl) inputPolygon._getImpl(); + SegmentIteratorImpl iter = mpImpl.querySegmentIterator(); + Envelope2D queryEnv = new Envelope2D(); + queryEnv.setCoords(envPoly); + queryEnv.xmax = inputPoint.x + tolerance;// no need to query segments to + // the right of the point. + // Only segments to the left + // matter. + queryEnv.ymin = inputPoint.y - tolerance; + queryEnv.ymax = inputPoint.y + tolerance; + QuadTreeImpl.QuadTreeIteratorImpl qiter = quadTree.getIterator( + queryEnv, tolerance); + for (int qhandle = qiter.next(); qhandle != -1; qhandle = qiter.next()) { + iter.resetToVertex(quadTree.getElement(qhandle)); + if (iter.hasNextSegment()) { + Segment segment = iter.nextSegment(); + if (helper.processSegment(segment)) + return -1; // point on boundary + } + } + + return helper.result(); + } + + public static int isPointInPolygon(Polygon inputPolygon, + Point2D inputPoint, double tolerance) { + if (inputPolygon.isEmpty()) + return 0; + + Envelope2D env = new Envelope2D(); + inputPolygon.queryLooseEnvelope(env); + env.inflate(tolerance, tolerance); + if (!env.contains(inputPoint)) + return 0; + + MultiPathImpl mpImpl = (MultiPathImpl) inputPolygon._getImpl(); + GeometryAccelerators accel = mpImpl._getAccelerators(); + if (accel != null) { + //geometry has spatial indices built. Try using them. + RasterizedGeometry2D rgeom = accel.getRasterizedGeometry(); + if (rgeom != null) { + RasterizedGeometry2D.HitType hit = rgeom.queryPointInGeometry( + inputPoint.x, inputPoint.y); + if (hit == RasterizedGeometry2D.HitType.Inside) + return 1; + else if (hit == RasterizedGeometry2D.HitType.Outside) + return 0; + } + + QuadTreeImpl qtree = accel.getQuadTree(); + if (qtree != null) { + return _isPointInPolygonInternalWithQuadTree(inputPolygon, + qtree, inputPoint, tolerance); + } + } + + return _isPointInPolygonInternal(inputPolygon, inputPoint, tolerance); + } + + static int isPointInPolygon(Polygon inputPolygon, double inputPointXVal, + double inputPointYVal, double tolerance) { + if (inputPolygon.isEmpty()) + return 0; + + Envelope2D env = new Envelope2D(); + inputPolygon.queryLooseEnvelope(env); + env.inflate(tolerance, tolerance); + if (!env.contains(inputPointXVal, inputPointYVal)) + return 0; + + MultiPathImpl mpImpl = (MultiPathImpl) inputPolygon._getImpl(); + GeometryAccelerators accel = mpImpl._getAccelerators(); + if (accel != null) { + RasterizedGeometry2D rgeom = accel.getRasterizedGeometry(); + if (rgeom != null) { + RasterizedGeometry2D.HitType hit = rgeom.queryPointInGeometry( + inputPointXVal, inputPointYVal); + if (hit == RasterizedGeometry2D.HitType.Inside) + return 1; + else if (hit == RasterizedGeometry2D.HitType.Outside) + return 0; + } + } + + // FIXME, the rest of the point in polygon tests are set up to avoid + // Point2D. This is the last bit + return _isPointInPolygonInternal(inputPolygon, new Point2D( + inputPointXVal, inputPointYVal), tolerance); + } + + public static int isPointInRing(MultiPathImpl inputPolygonImpl, int iRing, + Point2D inputPoint, double tolerance) { + Envelope2D env = new Envelope2D(); + inputPolygonImpl.queryLooseEnvelope2D(env); + env.inflate(tolerance, tolerance); + if (!env.contains(inputPoint)) + return 0; + + boolean bAltenate = true;// Path.get_FillMode() == fillmodeAlternate; + PointInPolygonHelper helper = new PointInPolygonHelper(bAltenate, + inputPoint, tolerance); + SegmentIteratorImpl iter = inputPolygonImpl.querySegmentIterator(); + iter.resetToPath(iRing); + + if (iter.nextPath()) { + while (iter.hasNextSegment()) { + Segment segment = iter.nextSegment(); + if (helper.processSegment(segment)) + return -1; // point on boundary + } + } + + return helper.result(); + } + + public static int isPointInPolygon(Polygon inputPolygon, Point inputPoint, + double tolerance) { + if (inputPoint.isEmpty()) + return 0; + + return isPointInPolygon(inputPolygon, inputPoint.getXY(), tolerance); + } + + public static int isPointInAnyOuterRing(Polygon inputPolygon, + Point2D inputPoint, double tolerance) { + Envelope2D env = new Envelope2D(); + inputPolygon.queryLooseEnvelope(env); + env.inflate(tolerance, tolerance); + if (!env.contains(inputPoint)) + return 0; + + // Note: + // Wolfgang had noted that this could be optimized if the exterior rings + // have positive area: + // Only test the positive rings and bail out immediately when in a + // positive ring. + // The worst case complexity is still O(n), but on average for polygons + // with holes, that would be faster. + // However, that method would not work if polygon is reversed, while the + // one here works fine same as PointInPolygon. + + boolean bAltenate = false;// use winding in this test + PointInPolygonHelper helper = new PointInPolygonHelper(bAltenate, + inputPoint, tolerance); + MultiPathImpl mpImpl = (MultiPathImpl) inputPolygon._getImpl(); + SegmentIteratorImpl iter = mpImpl.querySegmentIterator(); + while (iter.nextPath()) { + double ringArea = mpImpl.calculateRingArea2D(iter.getPathIndex()); + boolean bIsHole = ringArea < 0; + if (!bIsHole) { + helper.m_windnum = 0; + while (iter.hasNextSegment()) { + Segment segment = iter.nextSegment(); + if (helper.processSegment(segment)) + return -1; // point on boundary + } + + if (helper.m_windnum != 0) + return 1; + } + } + + return helper.result(); + } + +} diff --git a/src/com/esri/core/geometry/Polygon.java b/src/com/esri/core/geometry/Polygon.java new file mode 100644 index 00000000..18e96249 --- /dev/null +++ b/src/com/esri/core/geometry/Polygon.java @@ -0,0 +1,147 @@ +/* + Copyright 1995-2013 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ + + +package com.esri.core.geometry; + +import java.io.Serializable; + +/** + * A polygon is a collection of one or many interior or exterior rings. + */ +public final class Polygon extends MultiPath implements Serializable { + + private static final long serialVersionUID = 2L;// TODO:remove as we use + // writeReplace and + // GeometrySerializer + + /** + * Creates a polygon. + */ + public Polygon() { + m_impl = new MultiPathImpl(true); + } + + Polygon(VertexDescription vd) { + m_impl = new MultiPathImpl(true, vd); + } + + @Override + public Geometry createInstance() { + return new Polygon(getDescription()); + } + + @Override + public int getDimension() { + return 2; + } + + @Override + public Geometry.Type getType() { + return Type.Polygon; + } + + /** + * Calculates the ring area for this ring. + * + * @param ringIndex + * The index of this ring. + * @return The ring area for this ring. + */ + public double calculateRingArea2D(int ringIndex) { + return m_impl.calculateRingArea2D(ringIndex); + } + + // FIXME are these a java requirement? + // int getWKBPolygonCount() { return m_impl.getWKBPolygonCount(); } + // void setKnownRingOrientation(boolean bYesNo) { + // m_impl.setKnownRingOrientation(bYesNo); } + + /** + * Returns TRUE if the ring is an exterior ring. Valid only for simple + * polygons. + */ + public boolean isExteriorRing(int partIndex) { + return m_impl.isExteriorRing(partIndex); + } + + /** + * Returns TRUE when this geometry has exactly same type, properties, and + * coordinates as the other geometry. + */ + @Override + public boolean equals(Object other) { + if (other == null) + return false; + + if (other == this) + return true; + + if (other.getClass() != getClass()) + return false; + + return m_impl.equals(((Polygon) other)._getImpl()); + } + + /** + * Returns a hash code value for this polygon. + */ + + @Override + public int hashCode() { + return m_impl.hashCode(); + } + + /** + * Sets a new vertex for the polygon. + * + * @param i + * The index of the new vertex. + * @param x + * The X coordinate for the new vertex. + * @param y + * The Y coordinate for the new vertex. + */ + public void setXY(int i, double x, double y) { + m_impl.setXY(i, x, y); + + } + + void interpolateAttributes(int path_index, int from_point_index, + int to_point_index) { + m_impl.interpolateAttributes(path_index, from_point_index, + to_point_index); + } + + void interpolateAttributes(int semantics, int path_index, + int from_point_index, int to_point_index) { + m_impl.interpolateAttributesForSemantics(semantics, path_index, + from_point_index, to_point_index); + } + + public int getExteriorRingCount() { + return m_impl.getOGCPolygonCount(); + } + +} diff --git a/src/com/esri/core/geometry/PolygonUtils.java b/src/com/esri/core/geometry/PolygonUtils.java new file mode 100644 index 00000000..3c5bb5ca --- /dev/null +++ b/src/com/esri/core/geometry/PolygonUtils.java @@ -0,0 +1,427 @@ +/* + Copyright 1995-2013 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ + +package com.esri.core.geometry; + +class PolygonUtils { + + enum PiPResult { + PiPOutside, PiPInside, PiPBoundary + }; + + // enum_class PiPResult { PiPOutside = 0, PiPInside = 1, PiPBoundary = 2}; + /** + * Tests if Point is inside the Polygon. Returns PiPOutside if not in + * polygon, PiPInside if in the polygon, PiPBoundary is if on the border. It + * tests border only if the tolerance is > 0, otherwise PiPBoundary cannot + * be returned. Note: If the tolerance is not 0, the test is more expensive + * because it calculates closest distance from a point to each segment. + * + * O(n) complexity, where n is the number of polygon segments. + */ + public static PiPResult isPointInPolygon2D(Polygon polygon, + Point inputPoint, double tolerance) { + int res = PointInPolygonHelper.isPointInPolygon(polygon, inputPoint, + tolerance); + if (res == 0) + return PiPResult.PiPOutside; + if (res == 1) + return PiPResult.PiPInside; + + return PiPResult.PiPBoundary; + } + + public static PiPResult isPointInPolygon2D(Polygon polygon, + Point2D inputPoint, double tolerance) { + int res = PointInPolygonHelper.isPointInPolygon(polygon, inputPoint, + tolerance); + if (res == 0) + return PiPResult.PiPOutside; + if (res == 1) + return PiPResult.PiPInside; + + return PiPResult.PiPBoundary; + } + + static PiPResult isPointInPolygon2D(Polygon polygon, double inputPointXVal, + double inputPointYVal, double tolerance) { + int res = PointInPolygonHelper.isPointInPolygon(polygon, + inputPointXVal, inputPointYVal, tolerance); + if (res == 0) + return PiPResult.PiPOutside; + if (res == 1) + return PiPResult.PiPInside; + + return PiPResult.PiPBoundary; + } + + /** + * Tests if Point is inside the Polygon's ring. Returns PiPOutside if not in + * ring, PiPInside if in the ring, PiPBoundary is if on the border. It tests + * border only if the tolerance is > 0, otherwise PiPBoundary cannot be + * returned. Note: If the tolerance is not 0, the test is more expensive + * because it calculates closest distance from a point to each segment. + * + * O(n) complexity, where n is the number of ring segments. + */ + public static PiPResult isPointInRing2D(Polygon polygon, int iRing, + Point2D inputPoint, double tolerance) { + MultiPathImpl polygonImpl = (MultiPathImpl) polygon._getImpl(); + int res = PointInPolygonHelper.isPointInRing(polygonImpl, iRing, + inputPoint, tolerance); + if (res == 0) + return PiPResult.PiPOutside; + if (res == 1) + return PiPResult.PiPInside; + + // return PiPResult.PiPBoundary; + return PiPResult.PiPInside; // we do not return PiPBoundary. Overwise, + // we would have to do more complex + // calculations to differentiat between + // internal and external boundaries. + } + + /** + * Tests if Point is inside of the any outer ring of a Polygon. Returns + * PiPOutside if not in any outer ring, PiPInside if in the any outer ring, + * or on the boundary. PiPBoundary is never returned. Note: If the tolerance + * is not 0, the test is more expensive because it calculates closest + * distance from a point to each segment. + * + * O(n) complexity, where n is the number of polygon segments. + */ + public static PiPResult isPointInAnyOuterRing(Polygon polygon, + Point2D inputPoint, double tolerance) { + int res = PointInPolygonHelper.isPointInAnyOuterRing(polygon, + inputPoint, tolerance); + if (res == 0) + return PiPResult.PiPOutside; + if (res == 1) + return PiPResult.PiPInside; + + // return PiPResult.PiPBoundary; + return PiPResult.PiPInside; // we do not return PiPBoundary. Overwise, + // we would have to do more complex + // calculations to differentiat between + // internal and external boundaries. + } + + // #ifndef DOTNET + // /** + // *Tests point is inside the Polygon for an array of points. + // *Returns PiPOutside if not in polygon, PiPInside if in the polygon, + // PiPBoundary is if on the border. + // *It tests border only if the tolerance is > 0, otherwise PiPBoundary + // cannot be returned. + // *Note: If the tolerance is not 0, the test is more expensive. + // * + // *O(n*m) complexity, where n is the number of polygon segments, m is the + // number of input points. + // */ + // static void TestPointsInPolygon2D(Polygon polygon, const Point2D* + // inputPoints, int count, double tolerance, PiPResult testResults) + // { + // LOCALREFCLASS2(Array, Point2D*, int, inputPointsArr, + // const_cast(inputPoints), count); + // LOCALREFCLASS2(Array, PolygonUtils::PiPResult*, + // int, testResultsArr, testResults, count); + // TestPointsInPolygon2D(polygon, inputPointsArr, count, tolerance, + // testResultsArr); + // } + // #endif + /** + * Tests point is inside the Polygon for an array of points. Returns + * PiPOutside if not in polygon, PiPInside if in the polygon, PiPBoundary is + * if on the border. It tests border only if the tolerance is > 0, otherwise + * PiPBoundary cannot be returned. Note: If the tolerance is not 0, the test + * is more expensive. + * + * O(n*m) complexity, where n is the number of polygon segments, m is the + * number of input points. + */ + public static void testPointsInPolygon2D(Polygon polygon, + Point2D[] inputPoints, int count, double tolerance, + PiPResult[] testResults) { + if (inputPoints.length < count || testResults.length < count) + throw new IllegalArgumentException();// GEOMTHROW(invalid_argument); + + for (int i = 0; i < count; i++) + testResults[i] = isPointInPolygon2D(polygon, inputPoints[i], + tolerance); + } + + static void testPointsInPolygon2D(Polygon polygon, double[] xyStreamBuffer, + int pointCount, double tolerance, PiPResult[] testResults) { + if (xyStreamBuffer.length / 2 < pointCount + || testResults.length < pointCount) + throw new IllegalArgumentException();// GEOMTHROW(invalid_argument); + + for (int i = 0; i < pointCount; i++) + testResults[i] = isPointInPolygon2D(polygon, xyStreamBuffer[i * 2], + xyStreamBuffer[i * 2 + 1], tolerance); + } + + // public static void testPointsInPolygon2D(Polygon polygon, Geometry geom, + // int count, double tolerance, PiPResult[] testResults) + // { + // if(geom.getType() == Type.Point) + // { + // + // } + // else if(Geometry.isMultiVertex(geom.getType())) + // { + // + // } + // + // + // if (inputPoints.length < count || testResults.length < count) + // throw new IllegalArgumentException();//GEOMTHROW(invalid_argument); + // + // for (int i = 0; i < count; i++) + // testResults[i] = isPointInPolygon2D(polygon, inputPoints[i], tolerance); + // } + + /** + * Tests point is inside an Area Geometry (Envelope, Polygon) for an array + * of points. Returns PiPOutside if not in area, PiPInside if in the area, + * PiPBoundary is if on the border. It tests border only if the tolerance is + * > 0, otherwise PiPBoundary cannot be returned. Note: If the tolerance is + * not 0, the test is more expensive. + * + * O(n*m) complexity, where n is the number of polygon segments, m is the + * number of input points. + */ + public static void testPointsInArea2D(Geometry polygon, + Point2D[] inputPoints, int count, double tolerance, + PiPResult[] testResults) { + if (polygon.getType() == Geometry.Type.Polygon) + testPointsInPolygon2D((Polygon) polygon, inputPoints, count, + tolerance, testResults); + else if (polygon.getType() == Geometry.Type.Envelope) { + Envelope2D env2D = new Envelope2D(); + ((Envelope) polygon).queryEnvelope2D(env2D); + _testPointsInEnvelope2D(env2D, inputPoints, count, tolerance, + testResults); + } else + throw new GeometryException("invalid_call");// GEOMTHROW(invalid_call); + } + + public static void testPointsInArea2D(Geometry polygon, + double[] xyStreamBuffer, int count, double tolerance, + PiPResult[] testResults) { + if (polygon.getType() == Geometry.Type.Polygon) + testPointsInPolygon2D((Polygon) polygon, xyStreamBuffer, count, + tolerance, testResults); + else if (polygon.getType() == Geometry.Type.Envelope) { + Envelope2D env2D = new Envelope2D(); + ((Envelope) polygon).queryEnvelope2D(env2D); + _testPointsInEnvelope2D(env2D, xyStreamBuffer, count, tolerance, + testResults); + } else + throw new GeometryException("invalid_call");// GEOMTHROW(invalid_call); + } + + // Tests if Ring1 is inside Ring2. + // We assume here that the Polygon is Weak Simple. That is if one point of + // Ring1 is found to be inside of Ring2, then + // we assume that all of Ring1 is inside Ring2. + static boolean _isRingInRing2D(MultiPathImpl polygonImpl, int iRing1, + int iRing2, double tolerance) { + SegmentIteratorImpl segIter = polygonImpl.querySegmentIterator(); + segIter.resetToPath(iRing1); + if (!segIter.nextPath() || !segIter.hasNextSegment()) + throw new GeometryException("corrupted geometry"); + + // FIXME java enums can't cast to ints? what the hell?! + // enum_class PiPResult { PiPOutside = 0, PiPInside = 1, PiPBoundary = + // 2}; + int res = 2;// 2(int)PiPResult.PiPBoundary; + + while (res == 2 /* (int)PiPResult.PiPBoundary */ + && segIter.hasNextSegment()) { + Segment segment = segIter.nextSegment(); + Point2D point = segment.getCoord2D(0.5); + res = PointInPolygonHelper.isPointInRing(polygonImpl, iRing2, + point, tolerance); + } + + if (res == 2 /* (int)PiPResult.PiPBoundary */) + throw new GeometryException("internal error"); + if (res == 1 /* (int)PiPResult.PiPInside */) + return true; + + return false; + } + + private static void _testPointsInEnvelope2D(Envelope2D env2D, + Point2D[] inputPoints, int count, double tolerance, + PiPResult[] testResults) { + if (inputPoints.length < count || testResults.length < count) + throw new IllegalArgumentException(); + + if (env2D.isEmpty()) { + for (int i = 0; i < count; i++) + testResults[i] = PiPResult.PiPOutside; + return; + } + + Envelope2D envIn = env2D; // note for java port - assignement by value + envIn.inflate(-tolerance * 0.5, -tolerance * 0.5); + Envelope2D envOut = env2D;// note for java port - assignement by value + envOut.inflate(tolerance * 0.5, tolerance * 0.5); + for (int i = 0; i < count; i++) { + if (envIn.contains(inputPoints[i])) + testResults[i] = PiPResult.PiPInside; + else if (!envOut.contains(inputPoints[i])) + testResults[i] = PiPResult.PiPOutside; + else + testResults[i] = PiPResult.PiPBoundary; + } + } + + private static void _testPointsInEnvelope2D(Envelope2D env2D, + double[] xyStreamBuffer, int pointCount, double tolerance, + PiPResult[] testResults) { + if (xyStreamBuffer.length / 2 < pointCount + || testResults.length < pointCount) + throw new IllegalArgumentException(); + + if (env2D.isEmpty()) { + for (int i = 0; i < pointCount; i++) + testResults[i] = PiPResult.PiPOutside; + return; + } + + Envelope2D envIn = env2D; // note for java port - assignement by value + envIn.inflate(-tolerance * 0.5, -tolerance * 0.5); + Envelope2D envOut = env2D;// note for java port - assignement by value + envOut.inflate(tolerance * 0.5, tolerance * 0.5); + for (int i = 0; i < pointCount; i++) { + if (envIn + .contains(xyStreamBuffer[i * 2], xyStreamBuffer[i * 2 + 1])) + testResults[i] = PiPResult.PiPInside; + else if (!envIn.contains(xyStreamBuffer[i * 2], + xyStreamBuffer[i * 2 + 1])) + testResults[i] = PiPResult.PiPOutside; + else + testResults[i] = PiPResult.PiPBoundary; + } + } + + static void testPointsOnSegment_(Segment seg, Point2D[] input_points, + int count, double tolerance, PolygonUtils.PiPResult[] test_results) { + for (int i = 0; i < count; i++) { + if (seg.isIntersecting(input_points[i], tolerance)) + test_results[i] = PiPResult.PiPBoundary; + else + test_results[i] = PiPResult.PiPOutside; + } + } + + static void testPointsOnPolyline2D_(Polyline poly, Point2D[] input_points, + int count, double tolerance, PolygonUtils.PiPResult[] test_results) { + MultiPathImpl mp_impl = (MultiPathImpl) poly._getImpl(); + GeometryAccelerators accel = mp_impl._getAccelerators(); + RasterizedGeometry2D rgeom = null; + if (accel != null) { + rgeom = accel.getRasterizedGeometry(); + } + + int pointsLeft = count; + for (int i = 0; i < count; i++) { + test_results[i] = PiPResult.PiPInside;// set to impossible value + + if (rgeom != null) { + Point2D input_point = input_points[i]; + RasterizedGeometry2D.HitType hit = rgeom.queryPointInGeometry( + input_point.x, input_point.y); + if (hit == RasterizedGeometry2D.HitType.Outside) { + test_results[i] = PiPResult.PiPOutside; + pointsLeft--; + } + } + } + + if (pointsLeft != 0) { + SegmentIteratorImpl iter = mp_impl.querySegmentIterator(); + while (iter.nextPath() && pointsLeft != 0) { + while (iter.hasNextSegment() && pointsLeft != 0) { + Segment segment = iter.nextSegment(); + for (int i = 0; i < count && pointsLeft != 0; i++) { + if (test_results[i] == PiPResult.PiPInside) { + if (segment.isIntersecting(input_points[i], + tolerance)) { + test_results[i] = PiPResult.PiPBoundary; + pointsLeft--; + } + } + } + } + } + } + + for (int i = 0; i < count; i++) { + if (test_results[i] == PiPResult.PiPInside) + test_results[i] = PiPResult.PiPOutside; + } + } + + static void testPointsOnLine2D(Geometry line, Point2D[] input_points, + int count, double tolerance, PolygonUtils.PiPResult[] test_results) { + Geometry.Type gt = line.getType(); + if (gt == Geometry.Type.Polyline) + testPointsOnPolyline2D_((Polyline) line, input_points, count, + tolerance, test_results); + else if (Geometry.isSegment(gt.value())) { + testPointsOnSegment_((Segment) line, input_points, count, + tolerance, test_results); + } else + throw new GeometryException("Invalid call."); + } + + /* + * // Tests if Ring1 is inside Ring2. // We assume here that the Polygon is + * Weak Simple. That is if one point of Ring1 is found to be inside of + * Ring2, then // we assume that all of Ring1 is inside Ring2. public static + * booleanean _isRingInRing2D(MultiPathImpl polygonImpl, int iRing1, int + * iRing2, double tolerance) { SegmentIteratorImpl segIter = + * polygonImpl.querySegmentIterator(); segIter.resetToPath(iRing1); if + * (!segIter.nextPath() || !segIter.hasNextSegment()) throw new + * GeometryException("corrupted geometry"); + * + * PiPResult res = PiPResult.PiPBoundary; + * + * while ((res == PiPResult.PiPBoundary) && segIter.hasNextSegment()) { + * Segment segment = segIter.nextSegment(); Point2D point = + * segment.getCoord2D(0.5); res = + * PointInPolygonHelper.isPointInRing(polygonImpl, iRing2, point, + * tolerance); } + * + * if (res == PiPResult. PiPBoundary) throw new + * GeometryException("internal error"); if (res == PiPResult.PiPInside) + * return true; + * + * return false; } + */ +} diff --git a/src/com/esri/core/geometry/Polyline.java b/src/com/esri/core/geometry/Polyline.java new file mode 100644 index 00000000..194bbfb6 --- /dev/null +++ b/src/com/esri/core/geometry/Polyline.java @@ -0,0 +1,109 @@ +/* + Copyright 1995-2013 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ + + +package com.esri.core.geometry; + +import java.io.Serializable; + +/** + * A polyline is a collection of one or many paths. + * + */ +public final class Polyline extends MultiPath implements Serializable { + + private static final long serialVersionUID = 2L;// TODO:remove as we use + // writeReplace and + // GeometrySerializer + + /** + * Creates an empty polyline. + */ + public Polyline() { + m_impl = new MultiPathImpl(false); + } + + Polyline(VertexDescription vd) { + m_impl = new MultiPathImpl(false, vd); + } + + @Override + public Geometry createInstance() { + return new Polyline(getDescription()); + } + + @Override + public int getDimension() { + return 1; + } + + @Override + public Geometry.Type getType() { + return Type.Polyline; + } + + /** + * Returns TRUE when this geometry has exactly same type, properties, and + * coordinates as the other geometry. + */ + @Override + public boolean equals(Object other) { + if (other == null) + return false; + + if (other == this) + return true; + + if (other.getClass() != getClass()) + return false; + + return m_impl.equals(((Polyline) other)._getImpl()); + } + + /** + * Returns the hash code for the polyline. + */ + + @Override + public int hashCode() { + return m_impl.hashCode(); + } + + @Override + public void addSegment(Segment segment, boolean bStartNewPath) { + m_impl.addSegment(segment, bStartNewPath); + } + + void interpolateAttributes(int from_path_index, int from_point_index, + int to_path_index, int to_point_index) { + m_impl.interpolateAttributes(from_path_index, from_point_index, + to_path_index, to_point_index); + } + + void interpolateAttributes(int semantics, int from_path_index, + int from_point_index, int to_path_index, int to_point_index) { + m_impl.interpolateAttributesForSemantics(semantics, from_path_index, + from_point_index, to_path_index, to_point_index); + } +} diff --git a/src/com/esri/core/geometry/PolylinePath.java b/src/com/esri/core/geometry/PolylinePath.java new file mode 100644 index 00000000..b3c06dc8 --- /dev/null +++ b/src/com/esri/core/geometry/PolylinePath.java @@ -0,0 +1,77 @@ +/* + Copyright 1995-2013 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ + +package com.esri.core.geometry; + +import java.util.Comparator; + +class PolylinePath { + Point2D m_fromPoint; + Point2D m_toPoint; + double m_fromDist; // from lower left corner; -1.0 if point is not on + // clipping bounday + double m_toDist; // from lower left corner; -1.0 if point is not on clipping + // bounday + int m_path; // from polyline + boolean m_used; + + public PolylinePath() { + } + + public PolylinePath(Point2D fromPoint, Point2D toPoint, double fromDist, + double toDist, int path) { + m_fromPoint = fromPoint; + m_toPoint = toPoint; + m_fromDist = fromDist; + m_toDist = toDist; + m_path = path; + m_used = false; + } + + void setValues(Point2D fromPoint, Point2D toPoint, double fromDist, + double toDist, int path) { + m_fromPoint = fromPoint; + m_toPoint = toPoint; + m_fromDist = fromDist; + m_toDist = toDist; + m_path = path; + m_used = false; + } + + // to be used in Use SORTARRAY + +} + +class PolylinePathComparator implements Comparator { + @Override + public int compare(PolylinePath v1, PolylinePath v2) { + if ((v1).m_fromDist < (v2).m_fromDist) + return -1; + else if ((v1).m_fromDist > (v2).m_fromDist) + return 1; + else + return 0; + } + +} diff --git a/src/com/esri/core/geometry/ProgressTracker.java b/src/com/esri/core/geometry/ProgressTracker.java new file mode 100644 index 00000000..d185b991 --- /dev/null +++ b/src/com/esri/core/geometry/ProgressTracker.java @@ -0,0 +1,37 @@ +/* + Copyright 1995-2013 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ +package com.esri.core.geometry; + +/** + *A callback to provide progress and cancel tracking mechanism for lengthy operation. + */ +public abstract class ProgressTracker { + /** + *Periodically called by a lengthy operation to check if the caller requested to cancel. + *@param step The current step of the operation. + *@param totalExpectedSteps is the number of steps the operation is expects to complete its task. + *@return true, if the operation can continue. Returns False, when the operation has to terminate due to a user cancelation. + */ + public abstract boolean progress(int step, int totalExpectedSteps); +} diff --git a/src/com/esri/core/geometry/ProjectionTransformation.java b/src/com/esri/core/geometry/ProjectionTransformation.java new file mode 100644 index 00000000..4f2aedde --- /dev/null +++ b/src/com/esri/core/geometry/ProjectionTransformation.java @@ -0,0 +1,29 @@ +/* + Copyright 1995-2013 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ + +package com.esri.core.geometry; + +//This is a stub +class ProjectionTransformation { +} diff --git a/src/com/esri/core/geometry/Proximity2DResult.java b/src/com/esri/core/geometry/Proximity2DResult.java new file mode 100644 index 00000000..01a1a2ae --- /dev/null +++ b/src/com/esri/core/geometry/Proximity2DResult.java @@ -0,0 +1,104 @@ +/* + Copyright 1995-2013 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ + +package com.esri.core.geometry; + +/** + * Proximity operators are used to find the distance between two geometries or + * the distance from a given point to the nearest point on another geometry. + */ +public class Proximity2DResult { + Point2D m_coordinate = new Point2D(); + int m_vertexIndex; + double m_distance; + + /** + * Returns TRUE if the Proximity2DResult is empty. This only happens if the + * Geometry passed to the Proximity operator is empty. + */ + public boolean isEmpty() { + return m_vertexIndex < 0; + } + + /** + * Returns the closest coordinate for + * OperatorProximity2D.getNearestCoordinate or the vertex coordinates for + * the OperatorProximity2D.getNearestVertex and + * OperatorProximity2D.getNearestVertices. + */ + public Point getCoordinate() { + if (isEmpty()) + throw new GeometryException("invalid call"); + + return new Point(m_coordinate.x, m_coordinate.y); + } + + /** + * Returns the vertex index. For OperatorProximity2D.getNearestCoordinate + * the behavior is: When the input is a polygon or an envelope and the + * bTestPolygonInterior is true, the value is zero. When the input is a + * polygon or an Envelope and the bTestPolygonInterior is false, the value + * is the start vertex index of a segment with the closest coordinate. When + * the input is a polyline, the value is the start vertex index of a segment + * with the closest coordinate. When the input is a point, the value is 0. + * When the input is a multipoint, the value is the closest vertex. + */ + public int getVertexIndex() { + if (isEmpty()) + throw new GeometryException("invalid call"); + + return m_vertexIndex; + } + + /** + * Returns the distance to the closest vertex or coordinate. + */ + public double getDistance() { + if (isEmpty()) + throw new GeometryException("invalid call"); + + return m_distance; + } + + void _setParams(double x, double y, int vertexIndex, double distance) { + m_coordinate.x = x; + m_coordinate.y = y; + m_vertexIndex = vertexIndex; + m_distance = distance; + } + + // static int _compare(Proximity2DResult v1, Proximity2DResult v2) + // { + // if (v1.m_distance < v2.m_distance) + // return -1; + // if (v1.m_distance == v2.m_distance) + // return 0; + // + // return 1; + // } + + Proximity2DResult() { + m_vertexIndex = -1; + } +} diff --git a/src/com/esri/core/geometry/Proximity2DResultComparator.java b/src/com/esri/core/geometry/Proximity2DResultComparator.java new file mode 100644 index 00000000..1be0cfbb --- /dev/null +++ b/src/com/esri/core/geometry/Proximity2DResultComparator.java @@ -0,0 +1,40 @@ +/* + Copyright 1995-2013 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ + +package com.esri.core.geometry; + +import java.util.Comparator; + +class Proximity2DResultComparator implements Comparator { + + public int compare(Proximity2DResult v1, Proximity2DResult v2) { + if (v1.m_distance < v2.m_distance) + return -1; + if (v1.m_distance == v2.m_distance) + return 0; + + return 1; + } + +} diff --git a/src/com/esri/core/geometry/QuadTree.java b/src/com/esri/core/geometry/QuadTree.java new file mode 100644 index 00000000..59f976d5 --- /dev/null +++ b/src/com/esri/core/geometry/QuadTree.java @@ -0,0 +1,206 @@ +/* + Copyright 1995-2013 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ + + +package com.esri.core.geometry; + +public class QuadTree { + public static final class QuadTreeIterator { + /** + * Resets the iterator to an starting state on the Quad_tree. If the + * input Geometry is a Line segment, then the query will be the segment. + * Otherwise the query will be the Envelope_2D bounding the Geometry. + * \param query The Geometry used for the query. \param tolerance The + * tolerance used for the intersection tests. + */ + public void resetIterator(Geometry query, double tolerance) { + m_impl.resetIterator(query, tolerance); + } + + /** + * Resets the iterator to a starting state on the Quad_tree using the + * input Envelope_2D as the query. \param query The Envelope_2D used for + * the query. \param tolerance The tolerance used for the intersection + * tests. + */ + public void resetIterator(Envelope2D query, double tolerance) { + m_impl.resetIterator(query, tolerance); + } + + /** + * Moves the iterator to the next Element_handle and returns the + * Element_handle. + */ + public int next() { + return m_impl.next(); + } + + /** + * Returns a void* to the impl class. + */ + Object getImpl_() { + return m_impl; + } + + // Creates an iterator on the input Quad_tree_impl. The query will be + // the Envelope_2D bounding the input Geometry. + private QuadTreeIterator(Object obj) { + m_impl = (QuadTreeImpl.QuadTreeIteratorImpl) obj; + } + + private QuadTreeImpl.QuadTreeIteratorImpl m_impl; + }; + + /** + * Creates a Quad_tree with the root having the extent of the input + * Envelope_2D, and height of the input height, where the root starts at + * height 0. Note that the height cannot be larger than 16 if on a 32 bit + * platform and 32 if on a 64 bit platform. \param extent The extent of the + * Quad_tree. \param height The max height of the Quad_tree. + */ + public QuadTree(Envelope2D extent, int height) { + m_impl = new QuadTreeImpl(extent, height); + } + + /** + * Inserts the element and bounding_box into the Quad_tree. Note that a copy + * will me made of the input bounding_box. Note that this will invalidate + * any active iterator on the Quad_tree. Returns an Element_handle + * corresponding to the element and bounding_box. \param element The element + * of the Geometry to be inserted. \param bounding_box The bounding_box of + * the Geometry to be inserted. + */ + public int insert(int element, Envelope2D bounding_box) { + return m_impl.insert(element, bounding_box); + } + + /** + * Inserts the element and bounding_box into the Quad_tree at the given + * quad_handle. Note that a copy will me made of the input bounding_box. + * Note that this will invalidate any active iterator on the Quad_tree. + * Returns an Element_handle corresponding to the element and bounding_box. + * \param element The element of the Geometry to be inserted. \param + * bounding_box The bounding_box of the Geometry to be inserted. \param + * hint_index A handle used as a hint where to place the element. This can + * be a handle obtained from a previous insertion and is useful on data + * having strong locality such as segments of a Polygon. + */ + public int insert(int element, Envelope2D bounding_box, int hint_index) { + return m_impl.insert(element, bounding_box, hint_index); + } + + /** + * Removes the element and bounding_box at the given element_handle. Note + * that this will invalidate any active iterator on the Quad_tree. \param + * element_handle The handle corresponding to the element and bounding_box + * to be removed. + */ + public void removeElement(int element_handle) { + m_impl.removeElement(element_handle); + } + + /** + * Returns the element at the given element_handle. \param element_handle + * The handle corresponding to the element to be retrieved. + */ + public int getElement(int element_handle) { + return m_impl.getElement(element_handle); + } + + /** + * Returns the height of the quad at the given quad_handle. \param + * quad_handle The handle corresponding to the quad. + */ + public int getHeight(int quad_handle) { + return m_impl.getHeight(quad_handle); + } + + /** + * Returns the extent of the quad at the given quad_handle. \param + * quad_handle The handle corresponding to the quad. + */ + public Envelope2D getExtent(int quad_handle) { + return m_impl.getExtent(quad_handle); + } + + /** + * Returns the Quad_handle of the quad containing the given element_handle. + * \param element_handle The handle corresponding to the element. + */ + public int getQuad(int element_handle) { + return m_impl.getQuad(element_handle); + } + + /** + * Returns the number of elements in the Quad_tree. + */ + public int getElementCount() { + return m_impl.getElementCount(); + } + + /** + * Gets an iterator on the Quad_tree. The query will be the Envelope_2D that + * bounds the input Geometry. To reuse the existing iterator on the same + * Quad_tree but with a new query, use the reset_iterator function on the + * Quad_tree_iterator. \param query The Geometry used for the query. If the + * Geometry is a Line segment, then the query will be the segment. Otherwise + * the query will be the Envelope_2D bounding the Geometry. \param tolerance + * The tolerance used for the intersection tests. + */ + public QuadTreeIterator getIterator(Geometry query, double tolerance) { + QuadTreeImpl.QuadTreeIteratorImpl iterator = m_impl.getIterator(query, + tolerance); + return new QuadTreeIterator(iterator); + } + + /** + * Gets an iterator on the Quad_tree using the input Envelope_2D as the + * query. To reuse the existing iterator on the same Quad_tree but with a + * new query, use the reset_iterator function on the Quad_tree_iterator. + * \param query The Envelope_2D used for the query. \param tolerance The + * tolerance used for the intersection tests. + */ + public QuadTreeIterator getIterator(Envelope2D query, double tolerance) { + QuadTreeImpl.QuadTreeIteratorImpl iterator = m_impl.getIterator(query, + tolerance); + return new QuadTreeIterator(iterator); + } + + /** + * Gets an iterator on the Quad_tree. + */ + public QuadTreeIterator getIterator() { + QuadTreeImpl.QuadTreeIteratorImpl iterator = m_impl.getIterator(); + return new QuadTreeIterator(iterator); + } + + /** + * Returns a void* to the impl class. + */ + Object getImpl_() { + return m_impl; + } + + private QuadTreeImpl m_impl; +} diff --git a/src/com/esri/core/geometry/QuadTreeImpl.java b/src/com/esri/core/geometry/QuadTreeImpl.java new file mode 100644 index 00000000..e1035b6b --- /dev/null +++ b/src/com/esri/core/geometry/QuadTreeImpl.java @@ -0,0 +1,888 @@ +/* + Copyright 1995-2013 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ +package com.esri.core.geometry; + +import java.util.ArrayList; + +class QuadTreeImpl { + static final class QuadTreeIteratorImpl { + /** + * Resets the iterator to an starting state on the Quad_tree_impl. If + * the input Geometry is a Line segment, then the query will be the + * segment. Otherwise the query will be the Envelope_2D bounding the + * Geometry. \param query The Geometry used for the query. \param + * tolerance The tolerance used for the intersection tests. \param + * tolerance The tolerance used for the intersection tests. + */ + void resetIterator(Geometry query, double tolerance) { + m_quads_stack.resize(0); + m_extents_stack.clear(); + m_current_element_handle = -1; + query.queryLooseEnvelope2D(m_query_box); + m_query_box.inflate(tolerance, tolerance); + + if (m_query_box.isIntersecting(m_quad_tree.m_extent)) { + int type = query.getType().value(); + m_b_linear = Geometry.isLinear(type); + + if (m_b_linear) { + Segment segment = (Segment) query; + m_query_start = segment.getStartXY(); + m_query_end = segment.getEndXY(); + m_tolerance = tolerance; + } else { + m_tolerance = NumberUtils.NaN(); // we don't need it + } + + m_quads_stack.add(m_quad_tree.m_root); + m_extents_stack.add(m_quad_tree.m_extent); + m_next_element_handle = m_quad_tree + .getFirstElement_(m_quad_tree.m_root); + } else + m_next_element_handle = -1; + } + + /** + * Resets the iterator to a starting state on the Quad_tree_impl using + * the input Envelope_2D as the query. \param query The Envelope_2D used + * for the query. \param tolerance The tolerance used for the + * intersection tests. + */ + void resetIterator(Envelope2D query, double tolerance) { + m_quads_stack.resize(0); + m_extents_stack.clear(); + m_current_element_handle = -1; + m_query_box.setCoords(query); + m_query_box.inflate(tolerance, tolerance); + m_tolerance = NumberUtils.NaN(); // we don't need it + + if (m_query_box.isIntersecting(m_quad_tree.m_extent)) { + m_quads_stack.add(m_quad_tree.m_root); + m_extents_stack.add(m_quad_tree.m_extent); + m_next_element_handle = m_quad_tree + .getFirstElement_(m_quad_tree.m_root); + m_b_linear = false; + } else + m_next_element_handle = -1; + } + + /** + * Moves the iterator to the next int and returns the int. + */ + int next() { + // If the node stack is empty, then we've exhausted our search + + if (m_quads_stack.size() == 0) + return -1; + + m_current_element_handle = m_next_element_handle; + + Point2D start = null; + Point2D end = null; + Envelope2D bounding_box; + Envelope2D extent_inf = null; + Envelope2D[] child_extents = null; + + if (m_b_linear) {// Should this memory be cached for reuse? + start = new Point2D(); + end = new Point2D(); + extent_inf = new Envelope2D(); + } + + boolean b_found_hit = false; + while (!b_found_hit) { + while (m_current_element_handle != -1) { + int current_box_handle = m_quad_tree + .getBoxHandle_(m_current_element_handle); + bounding_box = m_quad_tree + .getBoundingBox_(current_box_handle); + + if (bounding_box.isIntersecting(m_query_box)) { + if (m_b_linear) { + start.setCoords(m_query_start); + end.setCoords(m_query_end); + extent_inf.setCoords(bounding_box); + + extent_inf.inflate(m_tolerance, m_tolerance); + if (extent_inf.clipLine(start, end) > 0) { + b_found_hit = true; + break; + } + } else { + b_found_hit = true; + break; + } + } + + // get next element_handle + m_current_element_handle = m_quad_tree + .getNextElement_(m_current_element_handle); + } + + // If m_current_element_handle equals -1, then we've exhausted + // our search in the current quadtree node + if (m_current_element_handle == -1) { + // get the last node from the stack and add the children + // whose extent intersects m_query_box + int current_quad = m_quads_stack.getLast(); + Envelope2D current_extent = m_extents_stack + .get(m_extents_stack.size() - 1); + + double x_mid = 0.5 * (current_extent.xmin + current_extent.xmax); + double y_mid = 0.5 * (current_extent.ymin + current_extent.ymax); + + if (child_extents == null) { + child_extents = new Envelope2D[4]; + child_extents[0] = new Envelope2D(); + child_extents[1] = new Envelope2D(); + child_extents[2] = new Envelope2D(); + child_extents[3] = new Envelope2D(); + } + child_extents[0].setCoords(x_mid, y_mid, + current_extent.xmax, current_extent.ymax); // northeast + child_extents[1].setCoords(current_extent.xmin, y_mid, + x_mid, current_extent.ymax); // northwest + child_extents[2].setCoords(current_extent.xmin, + current_extent.ymin, x_mid, y_mid); // southwest + child_extents[3].setCoords(x_mid, current_extent.ymin, + current_extent.xmax, y_mid); // southeast + + m_quads_stack.removeLast(); + m_extents_stack.remove(m_extents_stack.size() - 1); + + for (int quadrant = 0; quadrant < 4; quadrant++) { + int child_handle = m_quad_tree.getChild_(current_quad, + quadrant); + + if (child_handle != -1 + && m_quad_tree + .getSubTreeElementCount(child_handle) > 0) { + if (child_extents[quadrant] + .isIntersecting(m_query_box)) { + if (m_b_linear) { + start.setCoords(m_query_start); + end.setCoords(m_query_end); + + extent_inf + .setCoords(child_extents[quadrant]); + extent_inf + .inflate(m_tolerance, m_tolerance); + if (extent_inf.clipLine(start, end) > 0) { + Envelope2D child_extent = new Envelope2D(); + child_extent + .setCoords(child_extents[quadrant]); + m_quads_stack.add(child_handle); + m_extents_stack.add(child_extent); + } + } else { + Envelope2D child_extent = new Envelope2D(); + child_extent + .setCoords(child_extents[quadrant]); + m_quads_stack.add(child_handle); + m_extents_stack.add(child_extent); + } + } + } + } + + assert (m_quads_stack.size() <= 4 * (m_quad_tree.m_height - 1)); + + if (m_quads_stack.size() == 0) + return -1; + + m_current_element_handle = m_quad_tree + .getFirstElement_(m_quads_stack.get(m_quads_stack + .size() - 1)); + } + } + + // We did not exhaust our search in the current node, so we return + // the element at m_current_element_handle in m_element_nodes + + m_next_element_handle = m_quad_tree + .getNextElement_(m_current_element_handle); + return m_current_element_handle; + } + + // Creates an iterator on the input Quad_tree_impl. The query will be + // the Envelope_2D bounding the input Geometry. + QuadTreeIteratorImpl(QuadTreeImpl quad_tree_impl, Geometry query, + double tolerance) { + m_quad_tree = quad_tree_impl; + m_query_box = new Envelope2D(); + m_quads_stack = new AttributeStreamOfInt32(0); + m_extents_stack = new ArrayList(0); + resetIterator(query, tolerance); + } + + // Creates an iterator on the input Quad_tree_impl using the input + // Envelope_2D as the query. + QuadTreeIteratorImpl(QuadTreeImpl quad_tree_impl, Envelope2D query, + double tolerance) { + m_quad_tree = quad_tree_impl; + m_query_box = new Envelope2D(); + m_quads_stack = new AttributeStreamOfInt32(0); + m_extents_stack = new ArrayList(0); + resetIterator(query, tolerance); + } + + // Creates an iterator on the input Quad_tree_impl. + QuadTreeIteratorImpl(QuadTreeImpl quad_tree_impl) { + m_quad_tree = quad_tree_impl; + m_query_box = new Envelope2D(); + m_quads_stack = new AttributeStreamOfInt32(0); + m_extents_stack = new ArrayList(0); + } + + private boolean m_b_linear; + private Point2D m_query_start; + private Point2D m_query_end; + private Envelope2D m_query_box; + private double m_tolerance; + private int m_current_element_handle; + private int m_next_element_handle; + private QuadTreeImpl m_quad_tree; + private AttributeStreamOfInt32 m_quads_stack; + private ArrayList m_extents_stack; // this won't grow bigger + // than 4 * + // (m_quad_tree->m_height + // - 1) + } + + /** + * Creates a Quad_tree_impl with the root having the extent of the input + * Envelope_2D, and height of the input height, where the root starts at + * height 0. Note that the height cannot be larger than 16 if on a 32 bit + * platform and 32 if on a 64 bit platform. \param extent The extent of the + * Quad_tree_impl. \param height The max height of the Quad_tree_impl. + */ + QuadTreeImpl(Envelope2D extent, int height) { + m_quad_tree_nodes = new StridedIndexTypeCollection(11); + m_element_nodes = new StridedIndexTypeCollection(5); + m_boxes = new ArrayList(0); + m_free_boxes = new AttributeStreamOfInt32(0); + m_extent = new Envelope2D(); + reset_(extent, height); + } + + /** + * Resets the Quad_tree_impl to the given extent and height. \param extent + * The extent of the Quad_tree_impl. \param height The max height of the + * Quad_tree_impl. + */ + void reset(Envelope2D extent, int height) { + m_quad_tree_nodes.deleteAll(false); + m_element_nodes.deleteAll(false); + m_boxes.clear(); + m_free_boxes.clear(false); + reset_(extent, height); + } + + /** + * Inserts the element and bounding_box into the Quad_tree_impl. Note that + * this will invalidate any active iterator on the Quad_tree_impl. Returns + * an int corresponding to the element and bounding_box. \param element The + * element of the Geometry to be inserted. \param bounding_box The + * bounding_box of the Geometry to be inserted. + */ + int insert(int element, Envelope2D bounding_box) { + return insert_(element, bounding_box, 0, m_extent, m_root, false, -1); + } + + /** + * Inserts the element and bounding_box into the Quad_tree_impl at the given + * quad_handle. Note that this will invalidate any active iterator on the + * Quad_tree_impl. Returns an int corresponding to the element and + * bounding_box. \param element The element of the Geometry to be inserted. + * \param bounding_box The bounding_box of the Geometry to be inserted. + * \param hint_index A handle used as a hint where to place the element. + * This can be a handle obtained from a previous insertion and is useful on + * data having strong locality such as segments of a Polygon. + */ + int insert(int element, Envelope2D bounding_box, int hint_index) { + int quad_handle; + + if (hint_index == -1) + quad_handle = m_root; + else + quad_handle = getQuad_(hint_index); + + int quad_height = getHeight(quad_handle); + Envelope2D quad_extent = getExtent(quad_handle); + return insert_(element, bounding_box, quad_height, quad_extent, + quad_handle, false, -1); + } + + /** + * Removes the element and bounding_box at the given element_handle. Note + * that this will invalidate any active iterator on the Quad_tree_impl. + * \param element_handle The handle corresponding to the element and + * bounding_box to be removed. + */ + void removeElement(int element_handle) { + int quad_handle = getQuad_(element_handle); + int nextElementHandle = disconnectElementHandle_(element_handle); + freeElementAndBoxNode_(element_handle); + + for (int q = quad_handle; q != -1; q = getParent_(q)) { + setSubTreeElementCount_(q, getSubTreeElementCount_(q) - 1); + assert (getSubTreeElementCount_(q) >= 0); + } + } + + /** + * Removes the quad and all its children corresponding to the input + * quad_handle. \param quad_handle The handle corresponding to the quad to + * be removed. + */ + void removeQuad(int quad_handle) { + removeQuad_(quad_handle); + } + + /** + * Returns the element at the given element_handle. \param element_handle + * The handle corresponding to the element to be retrieved. + */ + int getElement(int element_handle) { + return getElement_(element_handle); + } + + /** + * Returns the height of the quad at the given quad_handle. \param + * quad_handle The handle corresponding to the quad. + */ + int getHeight(int quad_handle) { + return getHeight_(quad_handle); + } + + /** + * Returns the extent of the quad at the given quad_handle. \param + * quad_handle The handle corresponding to the quad. + */ + Envelope2D getExtent(int quad_handle) { + Envelope2D quad_extent = new Envelope2D(); + quad_extent.setCoords(m_extent); + + int height = getHeight_(quad_handle); + int morten_number = getMortenNumber_(quad_handle); + int mask = 3; + + for (int i = 0; i < 2 * height; i += 2) { + int child = (int) (mask & (morten_number >> i)); + + if (child == 0) {// northeast + quad_extent.xmin = 0.5 * (quad_extent.xmin + quad_extent.xmax); + quad_extent.ymin = 0.5 * (quad_extent.ymin + quad_extent.ymax); + } else if (child == 1) {// northwest + quad_extent.xmax = 0.5 * (quad_extent.xmin + quad_extent.xmax); + quad_extent.ymin = 0.5 * (quad_extent.ymin + quad_extent.ymax); + } else if (child == 2) {// southwest + quad_extent.xmax = 0.5 * (quad_extent.xmin + quad_extent.xmax); + quad_extent.ymax = 0.5 * (quad_extent.ymin + quad_extent.ymax); + } else {// southeast + quad_extent.xmin = 0.5 * (quad_extent.xmin + quad_extent.xmax); + quad_extent.ymax = 0.5 * (quad_extent.ymin + quad_extent.ymax); + } + } + + return quad_extent; + } + + /** + * Returns the int of the quad containing the given element_handle. \param + * element_handle The handle corresponding to the element. + */ + int getQuad(int element_handle) { + return getQuad_(element_handle); + } + + /** + * Returns the number of elements in the Quad_tree_impl. + */ + int getElementCount() { + return getSubTreeElementCount_(m_root); + } + + /** + * Returns the number of elements in the subtree rooted at the given + * quad_handle. \param quad_handle The handle corresponding to the quad. + */ + int getSubTreeElementCount(int quad_handle) { + return getSubTreeElementCount_(quad_handle); + } + + /** + * Gets an iterator on the Quad_tree_impl. The query will be the Envelope_2D + * that bounds the input Geometry. To reuse the existing iterator on the + * same Quad_tree_impl but with a new query, use the reset_iterator function + * on the Quad_tree_iterator_impl. \param query The Geometry used for the + * query. If the Geometry is a Line segment, then the query will be the + * segment. Otherwise the query will be the Envelope_2D bounding the + * Geometry. \param tolerance The tolerance used for the intersection tests. + */ + QuadTreeIteratorImpl getIterator(Geometry query, double tolerance) { + return new QuadTreeIteratorImpl(this, query, tolerance); + } + + /** + * Gets an iterator on the Quad_tree_impl using the input Envelope_2D as the + * query. To reuse the existing iterator on the same Quad_tree_impl but with + * a new query, use the reset_iterator function on the + * Quad_tree_iterator_impl. \param query The Envelope_2D used for the query. + * \param tolerance The tolerance used for the intersection tests. + */ + QuadTreeIteratorImpl getIterator(Envelope2D query, double tolerance) { + return new QuadTreeIteratorImpl(this, query, tolerance); + } + + /** + * Gets an iterator on the Quad_tree. + */ + QuadTreeIteratorImpl getIterator() { + return new QuadTreeIteratorImpl(this); + } + + private void reset_(Envelope2D extent, int height) { + // We need 2 * height bits for the morten number, which is of type + // Index_type (more than enough). + if (height < 0 || 2 * height > 8 * 4) + throw new IllegalArgumentException("invalid height"); + + m_height = height; + m_extent.setCoords(extent); + m_root = m_quad_tree_nodes.newElement(); + setSubTreeElementCount_(m_root, 0); + setLocalElementCount_(m_root, 0); + setMortenNumber_(m_root, 0); + setHeight_(m_root, 0); + } + + private int insert_(int element, Envelope2D bounding_box, int height, + Envelope2D quad_extent, int quad_handle, boolean b_flushing, + int flushed_element_handle) { + Point2D bb_center = new Point2D(); + bounding_box.queryCenter(bb_center); + + Envelope2D current_extent = new Envelope2D(); + current_extent.setCoords(quad_extent); + + int current_quad_handle = quad_handle; + if (!current_extent.contains(bounding_box)) { + if (height == 0) + return -1; + + return insert_(element, bounding_box, 0, m_extent, m_root, + b_flushing, flushed_element_handle); + } + + // Should this memory be cached for reuse? + Point2D quad_center = new Point2D(); + Envelope2D[] child_extents = new Envelope2D[4]; + child_extents[0] = new Envelope2D(); + child_extents[1] = new Envelope2D(); + child_extents[2] = new Envelope2D(); + child_extents[3] = new Envelope2D(); + + int current_height; + for (current_height = height; current_height < m_height + && canPushDown_(current_quad_handle); current_height++) { + double x_mid = 0.5 * (current_extent.xmin + current_extent.xmax); + double y_mid = 0.5 * (current_extent.ymin + current_extent.ymax); + + child_extents[0].setCoords(x_mid, y_mid, current_extent.xmax, + current_extent.ymax); // northeast + child_extents[1].setCoords(current_extent.xmin, y_mid, x_mid, + current_extent.ymax); // northwest + child_extents[2].setCoords(current_extent.xmin, + current_extent.ymin, x_mid, y_mid); // southwest + child_extents[3].setCoords(x_mid, current_extent.ymin, + current_extent.xmax, y_mid); // southeast + + // Find the first child quadrant that contains the bounding box, and + // recursively insert into that child (greedy algorithm) + double mind = NumberUtils.doubleMax(); + int quadrant = -1; + for (int i = 0; i < 4; i++) { + child_extents[i].queryCenter(quad_center); + double d = Point2D.sqrDistance(quad_center, bb_center); + if (d < mind) { + mind = d; + quadrant = i; + } + } + + if (child_extents[quadrant].contains(bounding_box)) { + int child_handle = getChild_(current_quad_handle, quadrant); + if (child_handle == -1) + child_handle = createChild_(current_quad_handle, quadrant); + + current_quad_handle = child_handle; + current_extent.setCoords(child_extents[quadrant]); + setSubTreeElementCount_(current_quad_handle, + getSubTreeElementCount_(current_quad_handle) + 1); + continue; + } + + break; + } + + // If the bounding box is not contained in any of the current_node's + // children, or if the current_height is m_height, then insert the + // element and + // bounding box into the current_node + + int head_element_handle = getFirstElement_(current_quad_handle); + int tail_element_handle = getLastElement_(current_quad_handle); + int element_handle; + + if (b_flushing) { + assert (flushed_element_handle != -1); + + if (current_quad_handle == quad_handle) + return flushed_element_handle; + + disconnectElementHandle_(flushed_element_handle); // Take it out of + // the incoming + // quad_handle, + // and place in + // current_quad_handle + element_handle = flushed_element_handle; + } else { + element_handle = createElementAndBoxNode_(); + setElement_(element_handle, element); // insert element at the new + // tail of the list + // (next_element_handle). + setBoundingBox_(getBoxHandle_(element_handle), bounding_box); // insert + // bounding_box + setSubTreeElementCount_(quad_handle, + getSubTreeElementCount_(quad_handle) + 1); + } + + assert (!b_flushing || element_handle == flushed_element_handle); + + setQuad_(element_handle, current_quad_handle); // set parent quad + // (needed for removal + // of element) + + // assign the prev pointer of the new tail to point at the old tail + // (tail_element_handle) + // assign the next pointer of the old tail to point at the new tail + // (next_element_handle) + if (tail_element_handle != -1) { + setPrevElement_(element_handle, tail_element_handle); + setNextElement_(tail_element_handle, element_handle); + } else { + assert (head_element_handle == -1); + setFirstElement_(current_quad_handle, element_handle); + } + + // assign the new tail + setLastElement_(current_quad_handle, element_handle); + + setLocalElementCount_(current_quad_handle, + getLocalElementCount_(current_quad_handle) + 1); + + if (canFlush_(current_quad_handle)) + flush_(current_height, current_extent, current_quad_handle); + + return element_handle; + } + + private int disconnectElementHandle_(int element_handle) { + assert (element_handle != -1); + int quad_handle = getQuad_(element_handle); + int head_element_handle = getFirstElement_(quad_handle); + int tail_element_handle = getLastElement_(quad_handle); + int prev_element_handle = getPrevElement_(element_handle); + int next_element_handle = getNextElement_(element_handle); + assert (head_element_handle != -1 && tail_element_handle != -1); + + if (head_element_handle == element_handle) { + if (next_element_handle != -1) + setPrevElement_(next_element_handle, -1); + else { + assert (head_element_handle == tail_element_handle); + assert (getLocalElementCount_(quad_handle) == 1); + setLastElement_(quad_handle, -1); + } + + setFirstElement_(quad_handle, next_element_handle); + } else if (tail_element_handle == element_handle) { + assert (prev_element_handle != -1); + assert (getLocalElementCount_(quad_handle) >= 2); + setNextElement_(prev_element_handle, -1); + setLastElement_(quad_handle, prev_element_handle); + } else { + assert (next_element_handle != -1 && prev_element_handle != -1); + assert (getLocalElementCount_(quad_handle) >= 3); + setPrevElement_(next_element_handle, prev_element_handle); + setNextElement_(prev_element_handle, next_element_handle); + } + + setPrevElement_(element_handle, -1); + setNextElement_(element_handle, -1); + + setLocalElementCount_(quad_handle, + getLocalElementCount_(quad_handle) - 1); + assert (getLocalElementCount_(quad_handle) >= 0); + + return next_element_handle; + } + + private void removeQuad_(int quad_handle) { + int subTreeElementCount = getSubTreeElementCount_(quad_handle); + if (subTreeElementCount > 0) + for (int q = getParent_(quad_handle); q != -1; q = getParent_(q)) + setSubTreeElementCount_(q, getSubTreeElementCount_(q) + - subTreeElementCount); + + removeQuadHelper_(quad_handle); + + int parent = getParent_(quad_handle); + + if (parent != -1) { + for (int quadrant = 0; quadrant < 4; quadrant++) { + if (getChild_(parent, quadrant) == quad_handle) { + setChild_(parent, quadrant, -1); + break; + } + } + } + } + + private void removeQuadHelper_(int quad_handle) { + for (int element_handle = getFirstElement_(quad_handle); element_handle != -1; element_handle = getNextElement_(element_handle)) { + m_free_boxes.add(getBoxHandle_(element_handle)); + m_element_nodes.deleteElement(element_handle); + } + + for (int quadrant = 0; quadrant < 4; quadrant++) { + int child_handle = getChild_(quad_handle, quadrant); + if (child_handle != -1) { + removeQuadHelper_(child_handle); + setChild_(quad_handle, quadrant, -1); + } + } + + if (quad_handle != m_root) { + m_quad_tree_nodes.deleteElement(quad_handle); + } else { + setSubTreeElementCount_(m_root, 0); + setLocalElementCount_(m_root, 0); + setFirstElement_(m_root, -1); + setLastElement_(m_root, -1); + } + } + + private boolean canFlush_(int quad_handle) { + return getLocalElementCount_(quad_handle) == 8 + && !hasChildren_(quad_handle); + } + + private void flush_(int height, Envelope2D extent, int quad_handle) { + int element; + Envelope2D bounding_box; + + assert (quad_handle != -1); + + int element_handle = getFirstElement_(quad_handle), next_handle; + int box_handle; + assert (element_handle != -1); + + do { + box_handle = getBoxHandle_(element_handle); + element = m_element_nodes.getField(element_handle, 0); + bounding_box = getBoundingBox_(box_handle); + insert_(element, bounding_box, height, extent, quad_handle, true, + element_handle); + + next_handle = getNextElement_(element_handle); + element_handle = next_handle; + + } while (element_handle != -1); + } + + boolean canPushDown_(int quad_handle) { + return getLocalElementCount_(quad_handle) >= 8 + || hasChildren_(quad_handle); + } + + boolean hasChildren_(int parent) { + return getChild_(parent, 0) != -1 || getChild_(parent, 1) != -1 + || getChild_(parent, 2) != -1 || getChild_(parent, 3) != -1; + } + + private int createChild_(int parent, int quadrant) { + int child = m_quad_tree_nodes.newElement(); + setChild_(parent, quadrant, child); + setSubTreeElementCount_(child, 0); + setLocalElementCount_(child, 0); + setParent_(child, parent); + setHeight_(child, getHeight_(parent) + 1); + setMortenNumber_(child, (quadrant << (2 * getHeight_(parent))) + | getMortenNumber_(parent)); + return child; + } + + private int createElementAndBoxNode_() { + int element_handle = m_element_nodes.newElement(); + int box_handle; + + if (m_free_boxes.size() > 0) { + box_handle = m_free_boxes.getLast(); + m_free_boxes.removeLast(); + } else { + box_handle = m_boxes.size(); + m_boxes.add(new Envelope2D()); + } + + setBoxHandle_(element_handle, box_handle); + return element_handle; + } + + private void freeElementAndBoxNode_(int element_handle) { + m_free_boxes.add(getBoxHandle_(element_handle)); + m_element_nodes.deleteElement(element_handle); + } + + private int getChild_(int quad_handle, int quadrant) { + return m_quad_tree_nodes.getField(quad_handle, quadrant); + } + + private void setChild_(int parent, int quadrant, int child) { + m_quad_tree_nodes.setField(parent, quadrant, child); + } + + private int getFirstElement_(int quad_handle) { + return m_quad_tree_nodes.getField(quad_handle, 4); + } + + private void setFirstElement_(int quad_handle, int head) { + m_quad_tree_nodes.setField(quad_handle, 4, head); + } + + private int getLastElement_(int quad_handle) { + return m_quad_tree_nodes.getField(quad_handle, 5); + } + + private void setLastElement_(int quad_handle, int tail) { + m_quad_tree_nodes.setField(quad_handle, 5, tail); + } + + private int getMortenNumber_(int quad_handle) { + return m_quad_tree_nodes.getField(quad_handle, 6); + } + + private void setMortenNumber_(int quad_handle, int morten_number) { + m_quad_tree_nodes.setField(quad_handle, 6, morten_number); + } + + private int getLocalElementCount_(int quad_handle) { + return m_quad_tree_nodes.getField(quad_handle, 7); + } + + private int getSubTreeElementCount_(int quad_handle) { + return m_quad_tree_nodes.getField(quad_handle, 8); + } + + private void setLocalElementCount_(int quad_handle, int count) { + m_quad_tree_nodes.setField(quad_handle, 7, count); + } + + private void setSubTreeElementCount_(int quad_handle, int count) { + m_quad_tree_nodes.setField(quad_handle, 8, count); + } + + private int getParent_(int child) { + return m_quad_tree_nodes.getField(child, 9); + } + + private void setParent_(int child, int parent) { + m_quad_tree_nodes.setField(child, 9, parent); + } + + private int getHeight_(int quad_handle) { + return (int) m_quad_tree_nodes.getField(quad_handle, 10); + } + + private void setHeight_(int quad_handle, int height) { + m_quad_tree_nodes.setField(quad_handle, 10, height); + } + + private int getElement_(int element_handle) { + return m_element_nodes.getField(element_handle, 0); + } + + private void setElement_(int element_handle, int element) { + m_element_nodes.setField(element_handle, 0, element); + } + + private int getPrevElement_(int element_handle) { + return m_element_nodes.getField(element_handle, 1); + } + + private int getNextElement_(int element_handle) { + return m_element_nodes.getField(element_handle, 2); + } + + private void setPrevElement_(int element_handle, int prev_handle) { + m_element_nodes.setField(element_handle, 1, prev_handle); + } + + private void setNextElement_(int element_handle, int next_handle) { + m_element_nodes.setField(element_handle, 2, next_handle); + } + + private int getQuad_(int element_handle) { + return m_element_nodes.getField(element_handle, 3); + } + + private void setQuad_(int element_handle, int parent) { + m_element_nodes.setField(element_handle, 3, parent); + } + + private int getBoxHandle_(int element_handle) { + return m_element_nodes.getField(element_handle, 4); + } + + private void setBoxHandle_(int element_handle, int box_handle) { + m_element_nodes.setField(element_handle, 4, box_handle); + } + + private Envelope2D getBoundingBox_(int box_handle) { + return m_boxes.get(box_handle); + } + + private void setBoundingBox_(int box_handle, Envelope2D bounding_box) { + m_boxes.get(box_handle).setCoords(bounding_box); + } + + private int m_root; + private Envelope2D m_extent; + private int m_height; + private StridedIndexTypeCollection m_quad_tree_nodes; + private StridedIndexTypeCollection m_element_nodes; + private ArrayList m_boxes; + private AttributeStreamOfInt32 m_free_boxes; +} diff --git a/src/com/esri/core/geometry/RasterizedGeometry2D.java b/src/com/esri/core/geometry/RasterizedGeometry2D.java new file mode 100644 index 00000000..54ca904b --- /dev/null +++ b/src/com/esri/core/geometry/RasterizedGeometry2D.java @@ -0,0 +1,139 @@ +/* + Copyright 1995-2013 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ +package com.esri.core.geometry; + +import com.esri.core.geometry.Geometry.GeometryAccelerationDegree; + +abstract class RasterizedGeometry2D { + + public enum HitType { + Outside(0), // the test geometry is well outside the geometry bounds + Inside(1), // the test geometry is well inside the geomety bounds + Border(2); // the test geometry is close to the bounds or intersects the + // bounds + + private int enumVal; + + private HitType(int val) { + enumVal = val; + }; + } + + /** + * Test a point agains the RasterizedGeometry + */ + public abstract HitType queryPointInGeometry(double x, double y); + + /** + * Test an envelope against the RasterizedGeometry. + */ + public abstract HitType queryEnvelopeInGeometry(Envelope2D env); + + /** + * Creates a rasterized geometry from a given Geometry. + * + * @param geom + * The input geometry to rasterize. + * @param toleranceXY + * The tolerance of the rasterization. Raster pixels that are + * closer than given tolerance to the Geometry will be set. + * @param rasterSize + * The max size of the raster in bytes. The raster has size of + * rasterSize x rasterSize. Polygons are rasterized into 2 bpp + * (bits per pixel) rasters while other geometries are rasterized + * into 1 bpp rasters. 32x32 pixel raster for a polygon would + * take 256 bytes of memory + */ + public static RasterizedGeometry2D create(Geometry geom, + double toleranceXY, int rasterSizeBytes) { + if (!canUseAccelerator(geom)) + throw new IllegalArgumentException(); + + RasterizedGeometry2DImpl gc = RasterizedGeometry2DImpl.createImpl(geom, + toleranceXY, rasterSizeBytes); + return (RasterizedGeometry2D) gc; + } + + public static RasterizedGeometry2D create(MultiVertexGeometryImpl geom, + double toleranceXY, int rasterSizeBytes) { + if (!canUseAccelerator(geom)) + throw new IllegalArgumentException(); + + RasterizedGeometry2DImpl gc = RasterizedGeometry2DImpl.createImpl(geom, + toleranceXY, rasterSizeBytes); + return (RasterizedGeometry2D) gc; + + } + + public static int rasterSizeFromAccelerationDegree( + GeometryAccelerationDegree accelDegree) { + int value = 0; + switch (accelDegree) { + case enumMild: + value = 64 * 64 * 2 / 8;// 1k + break; + case enumMedium: + value = 256 * 256 * 2 / 8;// 16k + break; + case enumHot: + value = 1024 * 1024 * 2 / 8;// 256k + break; + default: + throw new GeometryException("internal error"); + } + + return value; + } + + /** + * Checks whether the RasterizedGeometry2D accelerator can be used with the + * given geometry. + */ + static boolean canUseAccelerator(Geometry geom) { + if (geom.isEmpty() + || !(geom.getType() == Geometry.Type.Polyline || geom.getType() == Geometry.Type.Polygon)) + return false; + + return true; + } + + /** + * Returns the tolerance for which the rasterized Geometry has been built. + */ + abstract double getToleranceXY(); + + /** + * Returns raster size in bytes + */ + abstract int getRasterSize(); + + /** + * Dumps the raster to file for debug purposes. + * + * @param fileName + * @returns true if success, false otherwise. + */ + abstract boolean dbgSaveToBitmap(String fileName); + +} diff --git a/src/com/esri/core/geometry/RasterizedGeometry2DImpl.java b/src/com/esri/core/geometry/RasterizedGeometry2DImpl.java new file mode 100644 index 00000000..c5252e20 --- /dev/null +++ b/src/com/esri/core/geometry/RasterizedGeometry2DImpl.java @@ -0,0 +1,37 @@ +/* + Copyright 1995-2013 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ + +package com.esri.core.geometry; + +abstract class RasterizedGeometry2DImpl extends RasterizedGeometry2D { + static RasterizedGeometry2DImpl createImpl(Geometry geom, + double toleranceXY, int rasterSizeBytes) { + return null; + } + + static RasterizedGeometry2DImpl createImpl(MultiVertexGeometryImpl geom, + double toleranceXY, int rasterSizeBytes) { + return null; + } +} diff --git a/src/com/esri/core/geometry/RelationalOperations.java b/src/com/esri/core/geometry/RelationalOperations.java new file mode 100644 index 00000000..9626b4ca --- /dev/null +++ b/src/com/esri/core/geometry/RelationalOperations.java @@ -0,0 +1,4993 @@ +/* + Copyright 1995-2013 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ +package com.esri.core.geometry; + +import java.util.ArrayList; + +class RelationalOperations { + interface Relation { + static final int contains = 1; + static final int within = 2; + static final int equals = 3; + static final int disjoint = 4; + static final int touches = 8; + static final int crosses = 16; + static final int overlaps = 32; + + static final int unknown = 0; + static final int intersects = 0x40000000; + } + + static boolean relate(Geometry geometry_a, Geometry geometry_b, + SpatialReference sr, int relation, ProgressTracker progress_tracker) { + int type_a = geometry_a.getType().value(); + int type_b = geometry_b.getType().value(); + + // Give preference to the Point vs Envelope, Envelope vs Envelope and + // Point vs Point realtions: + if (type_a == Geometry.GeometryType.Envelope) { + if (type_b == Geometry.GeometryType.Envelope) { + return relate((Envelope) geometry_a, (Envelope) geometry_b, sr, + relation, progress_tracker); + } else if (type_b == Geometry.GeometryType.Point) { + if (relation == Relation.within) + relation = Relation.contains; + else if (relation == Relation.contains) + relation = Relation.within; + + return relate((Point) geometry_b, (Envelope) geometry_a, sr, + relation, progress_tracker); + } else { + // proceed below + } + } else if (type_a == Geometry.GeometryType.Point) { + if (type_b == Geometry.GeometryType.Envelope) { + return relate((Point) geometry_a, (Envelope) geometry_b, sr, + relation, progress_tracker); + } else if (type_b == Geometry.GeometryType.Point) { + return relate((Point) geometry_a, (Point) geometry_b, sr, + relation, progress_tracker); + } else { + // proceed below + } + } else { + // proceed below + } + + if (geometry_a.isEmpty() || geometry_b.isEmpty()) { + if (relation == Relation.disjoint) + return true; // Always true + + return false; // Always false + } + + Envelope2D env1 = new Envelope2D(); + geometry_a.queryEnvelope2D(env1); + Envelope2D env2 = new Envelope2D(); + geometry_b.queryEnvelope2D(env2); + + Envelope2D envMerged = new Envelope2D(); + envMerged.setCoords(env1); + envMerged.merge(env2); + double tolerance = InternalUtils.calculateToleranceFromGeometry(sr, + envMerged, false); + + if (envelopeDisjointEnvelope_(env1, env2, tolerance, progress_tracker)) { + if (relation == Relation.disjoint) + return true; + + return false; + } + + boolean bRelation = false; + + Geometry _geometry_a; + Geometry _geometry_b; + Polyline polyline_a, polyline_b; + + if (MultiPath.isSegment(type_a)) { + polyline_a = new Polyline(geometry_a.getDescription()); + polyline_a.addSegment((Segment) geometry_a, true); + _geometry_a = polyline_a; + type_a = Geometry.GeometryType.Polyline; + } else { + _geometry_a = geometry_a; + } + + if (MultiPath.isSegment(type_b)) { + polyline_b = new Polyline(geometry_b.getDescription()); + polyline_b.addSegment((Segment) geometry_b, true); + _geometry_b = polyline_b; + type_b = Geometry.GeometryType.Polyline; + } else { + _geometry_b = geometry_b; + } + + if (type_a != Geometry.GeometryType.Envelope + && type_b != Geometry.GeometryType.Envelope) { + if (_geometry_a.getDimension() < _geometry_b.getDimension() + || (type_a == Geometry.GeometryType.Point && type_b == Geometry.GeometryType.MultiPoint)) {// we + // will + // switch + // the + // order + // of + // the + // geometries + // below. + if (relation == Relation.within) + relation = Relation.contains; + else if (relation == Relation.contains) + relation = Relation.within; + } + } else { + if (type_a != Geometry.GeometryType.Polygon + && type_b != Geometry.GeometryType.Envelope) { // we will + // switch + // the order + // of the + // geometries + // below. + if (relation == Relation.within) + relation = Relation.contains; + else if (relation == Relation.contains) + relation = Relation.within; + } + } + + switch (type_a) { + case Geometry.GeometryType.Polygon: + switch (type_b) { + case Geometry.GeometryType.Polygon: + bRelation = polygonRelatePolygon_((Polygon) (_geometry_a), + (Polygon) (_geometry_b), tolerance, relation, + progress_tracker); + break; + + case Geometry.GeometryType.Polyline: + bRelation = polygonRelatePolyline_((Polygon) (_geometry_a), + (Polyline) (_geometry_b), tolerance, relation, + progress_tracker); + break; + + case Geometry.GeometryType.Point: + bRelation = polygonRelatePoint_((Polygon) (_geometry_a), + (Point) (_geometry_b), tolerance, relation, + progress_tracker); + break; + + case Geometry.GeometryType.MultiPoint: + bRelation = polygonRelateMultiPoint_((Polygon) (_geometry_a), + (MultiPoint) (_geometry_b), tolerance, relation, + progress_tracker); + break; + + case Geometry.GeometryType.Envelope: + bRelation = polygonRelateEnvelope_((Polygon) (_geometry_a), + (Envelope) (_geometry_b), tolerance, relation, + progress_tracker); + break; + + default: + break; // warning fix + } + break; + + case Geometry.GeometryType.Polyline: + switch (type_b) { + case Geometry.GeometryType.Polygon: + bRelation = polygonRelatePolyline_((Polygon) (_geometry_b), + (Polyline) (_geometry_a), tolerance, relation, + progress_tracker); + break; + + case Geometry.GeometryType.Polyline: + bRelation = polylineRelatePolyline_((Polyline) (_geometry_a), + (Polyline) (_geometry_b), tolerance, relation, + progress_tracker); + break; + + case Geometry.GeometryType.Point: + bRelation = polylineRelatePoint_((Polyline) (_geometry_a), + (Point) (_geometry_b), tolerance, relation, + progress_tracker); + break; + + case Geometry.GeometryType.MultiPoint: + bRelation = polylineRelateMultiPoint_((Polyline) (_geometry_a), + (MultiPoint) (_geometry_b), tolerance, relation, + progress_tracker); + break; + + case Geometry.GeometryType.Envelope: + bRelation = polylineRelateEnvelope_((Polyline) (_geometry_a), + (Envelope) (_geometry_b), tolerance, relation, + progress_tracker); + break; + + default: + break; // warning fix + } + break; + + case Geometry.GeometryType.Point: + switch (type_b) { + case Geometry.GeometryType.Polygon: + bRelation = polygonRelatePoint_((Polygon) (_geometry_b), + (Point) (_geometry_a), tolerance, relation, + progress_tracker); + break; + + case Geometry.GeometryType.Polyline: + bRelation = polylineRelatePoint_((Polyline) (_geometry_b), + (Point) (_geometry_a), tolerance, relation, + progress_tracker); + break; + + case Geometry.GeometryType.MultiPoint: + bRelation = multiPointRelatePoint_((MultiPoint) (_geometry_b), + (Point) (_geometry_a), tolerance, relation, + progress_tracker); + break; + + default: + break; // warning fix + } + break; + + case Geometry.GeometryType.MultiPoint: + switch (type_b) { + case Geometry.GeometryType.Polygon: + bRelation = polygonRelateMultiPoint_((Polygon) (_geometry_b), + (MultiPoint) (_geometry_a), tolerance, relation, + progress_tracker); + break; + + case Geometry.GeometryType.Polyline: + bRelation = polylineRelateMultiPoint_((Polyline) (_geometry_b), + (MultiPoint) (_geometry_a), tolerance, relation, + progress_tracker); + break; + + case Geometry.GeometryType.MultiPoint: + bRelation = multiPointRelateMultiPoint_( + (MultiPoint) (_geometry_a), (MultiPoint) (_geometry_b), + tolerance, relation, progress_tracker); + break; + + case Geometry.GeometryType.Point: + bRelation = multiPointRelatePoint_((MultiPoint) (_geometry_a), + (Point) (_geometry_b), tolerance, relation, + progress_tracker); + break; + + case Geometry.GeometryType.Envelope: + bRelation = multiPointRelateEnvelope_( + (MultiPoint) (_geometry_a), (Envelope) (_geometry_b), + tolerance, relation, progress_tracker); + break; + + default: + break; // warning fix + } + break; + + case Geometry.GeometryType.Envelope: + switch (type_b) { + case Geometry.GeometryType.Polygon: + bRelation = polygonRelateEnvelope_((Polygon) (_geometry_b), + (Envelope) (_geometry_a), tolerance, relation, + progress_tracker); + break; + + case Geometry.GeometryType.Polyline: + bRelation = polylineRelateEnvelope_((Polyline) (_geometry_b), + (Envelope) (_geometry_a), tolerance, relation, + progress_tracker); + break; + + case Geometry.GeometryType.MultiPoint: + bRelation = multiPointRelateEnvelope_( + (MultiPoint) (_geometry_b), (Envelope) (_geometry_a), + tolerance, relation, progress_tracker); + break; + + default: + break; // warning fix + } + break; + + default: + break; // warning fix + } + + return bRelation; + } + + // Computes the necessary 9 intersection relationships of boundary, + // interior, and exterior of envelope_a vs envelope_b for the given + // relation. + private static boolean relate(Envelope envelope_a, Envelope envelope_b, + SpatialReference sr, int relation, ProgressTracker progress_tracker) { + if (envelope_a.isEmpty() || envelope_b.isEmpty()) { + if (relation == Relation.disjoint) + return true; // Always true + + return false; // Always false + } + + Envelope2D env_a = new Envelope2D(), env_b = new Envelope2D(), env_merged = new Envelope2D(); + envelope_a.queryEnvelope2D(env_a); + envelope_b.queryEnvelope2D(env_b); + env_merged.setCoords(env_a); + env_merged.merge(env_b); + + double tolerance = InternalUtils.calculateToleranceFromGeometry(sr, + env_merged, false); + + switch (relation) { + case Relation.disjoint: + return envelopeDisjointEnvelope_(env_a, env_b, tolerance, + progress_tracker); + + case Relation.within: + return envelopeContainsEnvelope_(env_b, env_a, tolerance, + progress_tracker); + + case Relation.contains: + return envelopeContainsEnvelope_(env_a, env_b, tolerance, + progress_tracker); + + case Relation.equals: + return envelopeEqualsEnvelope_(env_a, env_b, tolerance, + progress_tracker); + + case Relation.touches: + return envelopeTouchesEnvelope_(env_a, env_b, tolerance, + progress_tracker); + + case Relation.overlaps: + return envelopeOverlapsEnvelope_(env_a, env_b, tolerance, + progress_tracker); + + case Relation.crosses: + return envelopeCrossesEnvelope_(env_a, env_b, tolerance, + progress_tracker); + + default: + break; // warning fix + } + + return false; + } + + // Computes the necessary 9 intersection relationships of boundary, + // interior, and exterior of point_a vs envelope_b for the given relation. + private static boolean relate(Point point_a, Envelope envelope_b, + SpatialReference sr, int relation, ProgressTracker progress_tracker) { + if (point_a.isEmpty() || envelope_b.isEmpty()) { + if (relation == Relation.disjoint) + return true; // Always true + + return false; // Always false + } + + Point2D pt_a = point_a.getXY(); + Envelope2D env_b = new Envelope2D(), env_merged = new Envelope2D(); + envelope_b.queryEnvelope2D(env_b); + env_merged.setCoords(pt_a); + env_merged.merge(env_b); + + double tolerance = InternalUtils.calculateToleranceFromGeometry(sr, + env_merged, false); + + switch (relation) { + case Relation.disjoint: + return pointDisjointEnvelope_(pt_a, env_b, tolerance, + progress_tracker); + + case Relation.within: + return pointWithinEnvelope_(pt_a, env_b, tolerance, + progress_tracker); + + case Relation.contains: + return pointContainsEnvelope_(pt_a, env_b, tolerance, + progress_tracker); + + case Relation.equals: + return pointEqualsEnvelope_(pt_a, env_b, tolerance, + progress_tracker); + + case Relation.touches: + return pointTouchesEnvelope_(pt_a, env_b, tolerance, + progress_tracker); + + default: + break; // warning fix + } + + return false; + } + + // Computes the necessary 9 intersection relationships of boundary, + // interior, and exterior of point_a vs point_b for the given relation. + private static boolean relate(Point point_a, Point point_b, + SpatialReference sr, int relation, ProgressTracker progress_tracker) { + if (point_a.isEmpty() || point_b.isEmpty()) { + if (relation == Relation.disjoint) + return true; // Always true + + return false; // Always false + } + + Point2D pt_a = point_a.getXY(); + Point2D pt_b = point_b.getXY(); + Envelope2D env_merged = new Envelope2D(); + env_merged.setCoords(pt_a); + env_merged.merge(pt_b); + + double tolerance = InternalUtils.calculateToleranceFromGeometry(sr, + env_merged, false); + + switch (relation) { + case Relation.disjoint: + return pointDisjointPoint_(pt_a, pt_b, tolerance, progress_tracker); + + case Relation.within: + return pointContainsPoint_(pt_b, pt_a, tolerance, progress_tracker); + + case Relation.contains: + return pointContainsPoint_(pt_a, pt_b, tolerance, progress_tracker); + + case Relation.equals: + return pointEqualsPoint_(pt_a, pt_b, tolerance, progress_tracker); + + default: + break; // warning fix + } + + return false; + } + + // Returns true if the relation holds. + private static boolean polygonRelatePolygon_(Polygon polygon_a, + Polygon polygon_b, double tolerance, int relation, + ProgressTracker progress_tracker) { + switch (relation) { + case Relation.disjoint: + return polygonDisjointPolygon_(polygon_a, polygon_b, tolerance, + progress_tracker); + + case Relation.within: + return polygonContainsPolygon_(polygon_b, polygon_a, tolerance, + progress_tracker); + + case Relation.contains: + return polygonContainsPolygon_(polygon_a, polygon_b, tolerance, + progress_tracker); + + case Relation.equals: + return polygonEqualsPolygon_(polygon_a, polygon_b, tolerance, + progress_tracker); + + case Relation.touches: + return polygonTouchesPolygon_(polygon_a, polygon_b, tolerance, + progress_tracker); + + case Relation.overlaps: + return polygonOverlapsPolygon_(polygon_a, polygon_b, tolerance, + progress_tracker); + + default: + break; // warning fix + } + + return false; + } + + // Returns true if the relation holds. + private static boolean polygonRelatePolyline_(Polygon polygon_a, + Polyline polyline_b, double tolerance, int relation, + ProgressTracker progress_tracker) { + switch (relation) { + case Relation.disjoint: + return polygonDisjointPolyline_(polygon_a, polyline_b, tolerance, + progress_tracker); + + case Relation.contains: + return polygonContainsPolyline_(polygon_a, polyline_b, tolerance, + progress_tracker); + + case Relation.touches: + return polygonTouchesPolyline_(polygon_a, polyline_b, tolerance, + progress_tracker); + + case Relation.crosses: + return polygonCrossesPolyline_(polygon_a, polyline_b, tolerance, + progress_tracker); + + default: + break; // warning fix + } + + return false; + } + + // Returns true if the relation holds. + private static boolean polygonRelatePoint_(Polygon polygon_a, + Point point_b, double tolerance, int relation, + ProgressTracker progress_tracker) { + switch (relation) { + case Relation.disjoint: + return polygonDisjointPoint_(polygon_a, point_b, tolerance, + progress_tracker); + + case Relation.contains: + return polygonContainsPoint_(polygon_a, point_b, tolerance, + progress_tracker); + + case Relation.touches: + return polygonTouchesPoint_(polygon_a, point_b, tolerance, + progress_tracker); + + default: + break; // warning fix + } + + return false; + } + + // Returns true if the relation holds + private static boolean polygonRelateMultiPoint_(Polygon polygon_a, + MultiPoint multipoint_b, double tolerance, int relation, + ProgressTracker progress_tracker) { + switch (relation) { + case Relation.disjoint: + return polygonDisjointMultiPoint_(polygon_a, multipoint_b, + tolerance, true, progress_tracker); + + case Relation.contains: + return polygonContainsMultiPoint_(polygon_a, multipoint_b, + tolerance, progress_tracker); + + case Relation.touches: + return polygonTouchesMultiPoint_(polygon_a, multipoint_b, + tolerance, progress_tracker); + + case Relation.crosses: + return polygonCrossesMultiPoint_(polygon_a, multipoint_b, + tolerance, progress_tracker); + + default: + break; // warning fix + } + + return false; + } + + // Returns true if the relation holds + private static boolean polygonRelateEnvelope_(Polygon polygon_a, + Envelope envelope_b, double tolerance, int relation, + ProgressTracker progress_tracker) { + if (polygonDisjointEnvelope_(polygon_a, envelope_b, tolerance, + progress_tracker)) { + if (relation == Relation.disjoint) + return true; + + return false; + } else if (relation == Relation.disjoint) { + return false; + } + + switch (relation) { + case Relation.within: + return polygonWithinEnvelope_(polygon_a, envelope_b, tolerance, + progress_tracker); + + case Relation.contains: + return polygonContainsEnvelope_(polygon_a, envelope_b, tolerance, + progress_tracker); + + case Relation.equals: + return polygonEqualsEnvelope_(polygon_a, envelope_b, tolerance, + progress_tracker); + + case Relation.touches: + return polygonTouchesEnvelope_(polygon_a, envelope_b, tolerance, + progress_tracker); + + case Relation.overlaps: + return polygonOverlapsEnvelope_(polygon_a, envelope_b, tolerance, + progress_tracker); + + case Relation.crosses: + return polygonCrossesEnvelope_(polygon_a, envelope_b, tolerance, + progress_tracker); + + default: + break; // warning fix + } + + return false; + } + + // Returns true if the relation holds. + private static boolean polylineRelatePolyline_(Polyline polyline_a, + Polyline polyline_b, double tolerance, int relation, + ProgressTracker progress_tracker) { + switch (relation) { + case Relation.disjoint: + return polylineDisjointPolyline_(polyline_a, polyline_b, tolerance, + progress_tracker); + + case Relation.within: + return polylineContainsPolyline_(polyline_b, polyline_a, tolerance, + progress_tracker); + + case Relation.contains: + return polylineContainsPolyline_(polyline_a, polyline_b, tolerance, + progress_tracker); + + case Relation.equals: + return polylineEqualsPolyline_(polyline_a, polyline_b, tolerance, + progress_tracker); + + case Relation.touches: + return polylineTouchesPolyline_(polyline_a, polyline_b, tolerance, + progress_tracker); + + case Relation.overlaps: + return polylineOverlapsPolyline_(polyline_a, polyline_b, tolerance, + progress_tracker); + + case Relation.crosses: + return polylineCrossesPolyline_(polyline_a, polyline_b, tolerance, + progress_tracker); + + default: + break; // warning fix + } + + return false; + } + + // Returns true if the relation holds. + private static boolean polylineRelatePoint_(Polyline polyline_a, + Point point_b, double tolerance, int relation, + ProgressTracker progress_tracker) { + switch (relation) { + case Relation.disjoint: + return polylineDisjointPoint_(polyline_a, point_b, tolerance, + progress_tracker); + + case Relation.contains: + return polylineContainsPoint_(polyline_a, point_b, tolerance, + progress_tracker); + + case Relation.touches: + return polylineTouchesPoint_(polyline_a, point_b, tolerance, + progress_tracker); + + default: + break; // warning fix + } + + return false; + } + + // Returns true if the relation holds. + private static boolean polylineRelateMultiPoint_(Polyline polyline_a, + MultiPoint multipoint_b, double tolerance, int relation, + ProgressTracker progress_tracker) { + switch (relation) { + case Relation.disjoint: + return polylineDisjointMultiPoint_(polyline_a, multipoint_b, + tolerance, progress_tracker); + + case Relation.contains: + return polylineContainsMultiPoint_(polyline_a, multipoint_b, + tolerance, progress_tracker); + + case Relation.touches: + return polylineTouchesMultiPoint_(polyline_a, multipoint_b, + tolerance, progress_tracker); + + case Relation.crosses: + return polylineCrossesMultiPoint_(polyline_a, multipoint_b, + tolerance, progress_tracker); + + default: + break; // warning fix + } + + return false; + } + + // Returns true if the relation holds. + private static boolean polylineRelateEnvelope_(Polyline polyline_a, + Envelope envelope_b, double tolerance, int relation, + ProgressTracker progress_tracker) { + if (polylineDisjointEnvelope_(polyline_a, envelope_b, tolerance, + progress_tracker)) { + if (relation == Relation.disjoint) + return true; + + return false; + } else if (relation == Relation.disjoint) { + return false; + } + + switch (relation) { + case Relation.within: + return polylineWithinEnvelope_(polyline_a, envelope_b, tolerance, + progress_tracker); + + case Relation.contains: + return polylineContainsEnvelope_(polyline_a, envelope_b, tolerance, + progress_tracker); + + case Relation.equals: + return polylineEqualsEnvelope_(polyline_a, envelope_b, tolerance, + progress_tracker); + + case Relation.touches: + return polylineTouchesEnvelope_(polyline_a, envelope_b, tolerance, + progress_tracker); + + case Relation.overlaps: + return polylineOverlapsEnvelope_(polyline_a, envelope_b, tolerance, + progress_tracker); + + case Relation.crosses: + return polylineCrossesEnvelope_(polyline_a, envelope_b, tolerance, + progress_tracker); + + default: + break; // warning fix + } + + return false; + } + + // Returns true if the relation holds. + private static boolean multiPointRelateMultiPoint_(MultiPoint multipoint_a, + MultiPoint multipoint_b, double tolerance, int relation, + ProgressTracker progress_tracker) { + switch (relation) { + case Relation.disjoint: + return multiPointDisjointMultiPoint_(multipoint_a, multipoint_b, + tolerance, progress_tracker); + + case Relation.within: + return multiPointContainsMultiPoint_(multipoint_b, multipoint_a, + tolerance, progress_tracker); + + case Relation.contains: + return multiPointContainsMultiPoint_(multipoint_a, multipoint_b, + tolerance, progress_tracker); + + case Relation.equals: + return multiPointEqualsMultiPoint_(multipoint_a, multipoint_b, + tolerance, progress_tracker); + + case Relation.overlaps: + return multiPointOverlapsMultiPoint_(multipoint_a, multipoint_b, + tolerance, progress_tracker); + + default: + break; // warning fix + } + + return false; + } + + // Returns true if the relation holds. + private static boolean multiPointRelatePoint_(MultiPoint multipoint_a, + Point point_b, double tolerance, int relation, + ProgressTracker progress_tracker) { + switch (relation) { + case Relation.disjoint: + return multiPointDisjointPoint_(multipoint_a, point_b, tolerance, + progress_tracker); + + case Relation.within: + return multiPointWithinPoint_(multipoint_a, point_b, tolerance, + progress_tracker); + + case Relation.contains: + return multiPointContainsPoint_(multipoint_a, point_b, tolerance, + progress_tracker); + + case Relation.equals: + return multiPointEqualsPoint_(multipoint_a, point_b, tolerance, + progress_tracker); + + default: + break; // warning fix + } + + return false; + } + + // Returns true if the relation holds. + private static boolean multiPointRelateEnvelope_(MultiPoint multipoint_a, + Envelope envelope_b, double tolerance, int relation, + ProgressTracker progress_tracker) { + switch (relation) { + case Relation.disjoint: + return multiPointDisjointEnvelope_(multipoint_a, envelope_b, + tolerance, progress_tracker); + + case Relation.within: + return multiPointWithinEnvelope_(multipoint_a, envelope_b, + tolerance, progress_tracker); + + case Relation.contains: + return multiPointContainsEnvelope_(multipoint_a, envelope_b, + tolerance, progress_tracker); + + case Relation.equals: + return multiPointEqualsEnvelope_(multipoint_a, envelope_b, + tolerance, progress_tracker); + + case Relation.touches: + return multiPointTouchesEnvelope_(multipoint_a, envelope_b, + tolerance, progress_tracker); + + case Relation.crosses: + return multiPointCrossesEnvelope_(multipoint_a, envelope_b, + tolerance, progress_tracker); + + default: + break; // warning fix + } + + return false; + } + + // Returns true if polygon_a equals polygon_b. + private static boolean polygonEqualsPolygon_(Polygon polygon_a, + Polygon polygon_b, double tolerance, + ProgressTracker progress_tracker) { + Envelope2D env_a = new Envelope2D(), env_b = new Envelope2D(); + polygon_a.queryEnvelope2D(env_a); + polygon_b.queryEnvelope2D(env_b); + + // Quick envelope rejection test for false equality. + if (!envelopeEqualsEnvelope_(env_a, env_b, tolerance, progress_tracker)) + return false; + + // Quick rasterize test to see whether the the geometries are disjoint, + // or if one is contained in the other. + int relation = tryRasterizedContainsOrDisjoint_(polygon_a, polygon_b, + tolerance, false); + + if (relation == Relation.disjoint || relation == Relation.contains + || relation == Relation.within) + return false; + + // Quick point equality check for true equality. This just checks if all + // the points in each ring are the same (within a tolerance) and in the + // same order + if (multiPathExactlyEqualsMultiPath_(polygon_a, polygon_b, tolerance, + progress_tracker)) + return true; + + return linearPathEqualsLinearPath_(polygon_a, polygon_b, tolerance); + } + + // Returns true if polygon_a is disjoint from polygon_b. + private static boolean polygonDisjointPolygon_(Polygon polygon_a, + Polygon polygon_b, double tolerance, + ProgressTracker progress_tracker) { + // Quick rasterize test to see whether the the geometries are disjoint, + // or if one is contained in the other. + int relation = tryRasterizedContainsOrDisjoint_(polygon_a, polygon_b, + tolerance, true); + + if (relation == Relation.disjoint) + return true; + + if (relation == Relation.contains || relation == Relation.within + || relation == Relation.intersects) + return false; + + return polygonDisjointMultiPath_(polygon_a, polygon_b, tolerance, + progress_tracker); + } + + // Returns true if polygon_a touches polygon_b. + private static boolean polygonTouchesPolygon_(Polygon polygon_a, + Polygon polygon_b, double tolerance, + ProgressTracker progress_tracker) { + // Quick rasterize test to see whether the the geometries are disjoint, + // or if one is contained in the other. + int relation = tryRasterizedContainsOrDisjoint_(polygon_a, polygon_b, + tolerance, false); + + if (relation == Relation.disjoint || relation == Relation.contains + || relation == Relation.within) + return false; + + return polygonTouchesPolygonImpl_(polygon_a, polygon_b, tolerance, null); + } + + // Returns true if polygon_a overlaps polygon_b. + private static boolean polygonOverlapsPolygon_(Polygon polygon_a, + Polygon polygon_b, double tolerance, + ProgressTracker progress_tracker) { + // Quick rasterize test to see whether the the geometries are disjoint, + // or if one is contained in the other. + int relation = tryRasterizedContainsOrDisjoint_(polygon_a, polygon_b, + tolerance, false); + + if (relation == Relation.disjoint || relation == Relation.contains + || relation == Relation.within) + return false; + + return polygonOverlapsPolygonImpl_(polygon_a, polygon_b, tolerance, + progress_tracker); + } + + // Returns true if polygon_a contains polygon_b. + private static boolean polygonContainsPolygon_(Polygon polygon_a, + Polygon polygon_b, double tolerance, + ProgressTracker progress_tracker) { + Envelope2D env_a = new Envelope2D(), env_b = new Envelope2D(); + polygon_a.queryEnvelope2D(env_a); + polygon_b.queryEnvelope2D(env_b); + + // Quick envelope rejection test for false equality. + if (!envelopeInfContainsEnvelope_(env_a, env_b, tolerance)) + return false; + + // Quick rasterize test to see whether the the geometries are disjoint, + // or if one is contained in the other. + int relation = tryRasterizedContainsOrDisjoint_(polygon_a, polygon_b, + tolerance, false); + + if (relation == Relation.disjoint || relation == Relation.within) + return false; + + if (relation == Relation.contains) + return true; + + return polygonContainsPolygonImpl_(polygon_a, polygon_b, tolerance, + progress_tracker); + } + + // Returns true if polygon_a is disjoint from polyline_b. + private static boolean polygonDisjointPolyline_(Polygon polygon_a, + Polyline polyline_b, double tolerance, + ProgressTracker progress_tracker) { + // Quick rasterize test to see whether the the geometries are disjoint, + // or if one is contained in the other. + int relation = tryRasterizedContainsOrDisjoint_(polygon_a, polyline_b, + tolerance, true); + + if (relation == Relation.disjoint) + return true; + + if (relation == Relation.contains || relation == Relation.intersects) + return false; + + return polygonDisjointMultiPath_(polygon_a, polyline_b, tolerance, + progress_tracker); + } + + // Returns true if polygon_a touches polyline_b. + private static boolean polygonTouchesPolyline_(Polygon polygon_a, + Polyline polyline_b, double tolerance, + ProgressTracker progress_tracker) { + // Quick rasterize test to see whether the the geometries are disjoint, + // or if one is contained in the other. + int relation = tryRasterizedContainsOrDisjoint_(polygon_a, polyline_b, + tolerance, false); + + if (relation == Relation.disjoint || relation == Relation.contains) + return false; + + return polygonTouchesPolylineImpl_(polygon_a, polyline_b, tolerance, + progress_tracker); + } + + // Returns true if polygon_a crosses polyline_b. + private static boolean polygonCrossesPolyline_(Polygon polygon_a, + Polyline polyline_b, double tolerance, + ProgressTracker progress_tracker) { + // Quick rasterize test to see whether the the geometries are disjoint, + // or if one is contained in the other. + int relation = tryRasterizedContainsOrDisjoint_(polygon_a, polyline_b, + tolerance, false); + + if (relation == Relation.disjoint || relation == Relation.contains) + return false; + + return polygonCrossesPolylineImpl_(polygon_a, polyline_b, tolerance, + null); + } + + // Returns true if polygon_a contains polyline_b. + private static boolean polygonContainsPolyline_(Polygon polygon_a, + Polyline polyline_b, double tolerance, + ProgressTracker progress_tracker) { + Envelope2D env_a = new Envelope2D(), env_b = new Envelope2D(); + polygon_a.queryEnvelope2D(env_a); + polyline_b.queryEnvelope2D(env_b); + + // Quick envelope rejection test for false equality. + if (!envelopeInfContainsEnvelope_(env_a, env_b, tolerance)) + return false; + + // Quick rasterize test to see whether the the geometries are disjoint, + // or if one is contained in the other. + int relation = tryRasterizedContainsOrDisjoint_(polygon_a, polyline_b, + tolerance, false); + + if (relation == Relation.disjoint) + return false; + + if (relation == Relation.contains) + return true; + + return polygonContainsPolylineImpl_(polygon_a, polyline_b, tolerance, + progress_tracker); + } + + // Returns true if polygon_a is disjoint from point_b. + private static boolean polygonDisjointPoint_(Polygon polygon_a, + Point point_b, double tolerance, ProgressTracker progress_tracker) { + PolygonUtils.PiPResult result = PolygonUtils.isPointInPolygon2D( + polygon_a, point_b, tolerance); + + if (result == PolygonUtils.PiPResult.PiPOutside) + return true; + + return false; + } + + // Returns true of polygon_a touches point_b. + private static boolean polygonTouchesPoint_(Polygon polygon_a, + Point point_b, double tolerance, ProgressTracker progress_tracker) { + Point2D pt_b = point_b.getXY(); + return polygonTouchesPointImpl_(polygon_a, pt_b, tolerance, null); + } + + // Returns true if polygon_a contains point_b. + private static boolean polygonContainsPoint_(Polygon polygon_a, + Point point_b, double tolerance, ProgressTracker progress_tracker) { + Point2D pt_b = point_b.getXY(); + return polygonContainsPointImpl_(polygon_a, pt_b, tolerance, + progress_tracker); + } + + // Returns true if polygon_a is disjoint from multipoint_b. + private static boolean polygonDisjointMultiPoint_(Polygon polygon_a, + MultiPoint multipoint_b, double tolerance, + boolean bIncludeBoundaryA, ProgressTracker progress_tracker) { + // Quick rasterize test to see whether the the geometries are disjoint, + // or if one is contained in the other. + int relation = tryRasterizedContainsOrDisjoint_(polygon_a, + multipoint_b, tolerance, false); + + if (relation == Relation.disjoint) + return true; + + if (relation == Relation.contains) + return false; + + Envelope2D env_a_inflated = new Envelope2D(); + polygon_a.queryEnvelope2D(env_a_inflated); + env_a_inflated.inflate(tolerance, tolerance); + Point2D ptB = new Point2D(); + + for (int i = 0; i < multipoint_b.getPointCount(); i++) { + multipoint_b.getXY(i, ptB); + + if (!env_a_inflated.contains(ptB)) + continue; + + PolygonUtils.PiPResult result = PolygonUtils.isPointInPolygon2D( + polygon_a, ptB, tolerance); + + if (result == PolygonUtils.PiPResult.PiPInside + || (bIncludeBoundaryA && result == PolygonUtils.PiPResult.PiPBoundary)) + return false; + } + + return true; + } + + // Returns true if polygon_a touches multipoint_b. + private static boolean polygonTouchesMultiPoint_(Polygon polygon_a, + MultiPoint multipoint_b, double tolerance, + ProgressTracker progress_tracker) { + // Quick rasterize test to see whether the the geometries are disjoint, + // or if one is contained in the other. + int relation = tryRasterizedContainsOrDisjoint_(polygon_a, + multipoint_b, tolerance, false); + + if (relation == Relation.disjoint || relation == Relation.contains) + return false; + + Envelope2D env_a_inflated = new Envelope2D(); + polygon_a.queryEnvelope2D(env_a_inflated); + env_a_inflated.inflate(tolerance, tolerance); + + Point2D ptB = new Point2D(); + boolean b_boundary = false; + + for (int i = 0; i < multipoint_b.getPointCount(); i++) { + multipoint_b.getXY(i, ptB); + + if (!env_a_inflated.contains(ptB)) + continue; + + PolygonUtils.PiPResult result = PolygonUtils.isPointInPolygon2D( + polygon_a, ptB, tolerance); + + if (result == PolygonUtils.PiPResult.PiPBoundary) + b_boundary = true; + else if (result == PolygonUtils.PiPResult.PiPInside) + return false; + } + + if (b_boundary) + return true; + + return false; + } + + // Returns true if polygon_a crosses multipoint_b. + private static boolean polygonCrossesMultiPoint_(Polygon polygon_a, + MultiPoint multipoint_b, double tolerance, + ProgressTracker progress_tracker) { + // Quick rasterize test to see whether the the geometries are disjoint, + // or if one is contained in the other. + int relation = tryRasterizedContainsOrDisjoint_(polygon_a, + multipoint_b, tolerance, false); + + if (relation == Relation.disjoint || relation == Relation.contains) + return false; + + Envelope2D env_a = new Envelope2D(), env_a_inflated = new Envelope2D(), env_b = new Envelope2D(); + polygon_a.queryEnvelope2D(env_a); + multipoint_b.queryEnvelope2D(env_b); + env_a_inflated.setCoords(env_a); + env_a_inflated.inflate(tolerance, tolerance); + + boolean b_interior = false, b_exterior = false; + + Point2D pt_b = new Point2D(); + + for (int i = 0; i < multipoint_b.getPointCount(); i++) { + multipoint_b.getXY(i, pt_b); + + if (!env_a_inflated.contains(pt_b)) { + b_exterior = true; + } else { + PolygonUtils.PiPResult result = PolygonUtils + .isPointInPolygon2D(polygon_a, pt_b, tolerance); + + if (result == PolygonUtils.PiPResult.PiPOutside) + b_exterior = true; + else if (result == PolygonUtils.PiPResult.PiPInside) + b_interior = true; + } + + if (b_interior && b_exterior) + return true; + } + + return false; + } + + // Returns true if polygon_a contains multipoint_b. + private static boolean polygonContainsMultiPoint_(Polygon polygon_a, + MultiPoint multipoint_b, double tolerance, + ProgressTracker progress_tracker) { + Envelope2D env_a = new Envelope2D(), env_b = new Envelope2D(); + polygon_a.queryEnvelope2D(env_a); + multipoint_b.queryEnvelope2D(env_b); + + // Quick envelope rejection test for false equality. + if (!envelopeInfContainsEnvelope_(env_a, env_b, tolerance)) + return false; + + // Quick rasterize test to see whether the the geometries are disjoint, + // or if one is contained in the other. + int relation = tryRasterizedContainsOrDisjoint_(polygon_a, + multipoint_b, tolerance, false); + + if (relation == Relation.disjoint) + return false; + + if (relation == Relation.contains) + return true; + + boolean b_interior = false; + Point2D ptB = new Point2D(); + + for (int i = 0; i < multipoint_b.getPointCount(); i++) { + multipoint_b.getXY(i, ptB); + + if (!env_a.contains(ptB)) + return false; + + PolygonUtils.PiPResult result = PolygonUtils.isPointInPolygon2D( + polygon_a, ptB, tolerance); + + if (result == PolygonUtils.PiPResult.PiPInside) + b_interior = true; + else if (result == PolygonUtils.PiPResult.PiPOutside) + return false; + } + + return b_interior; + } + + // Returns true if polygon_a equals envelope_b. + private static boolean polygonEqualsEnvelope_(Polygon polygon_a, + Envelope envelope_b, double tolerance, + ProgressTracker progress_tracker) { + Envelope2D env_a = new Envelope2D(), env_b = new Envelope2D(); + polygon_a.queryEnvelope2D(env_a); + envelope_b.queryEnvelope2D(env_b); + + // Quick envelope rejection test for false equality. + // This check will correctly handle degenerate envelope cases (i.e. + // degenerate to point or line) + if (!envelopeEqualsEnvelope_(env_a, env_b, tolerance, progress_tracker)) + return false; + + Polygon polygon_b = new Polygon(); + polygon_b.addEnvelope(envelope_b, false); + + return linearPathEqualsLinearPath_(polygon_a, polygon_b, tolerance); + } + + // Returns true if polygon_a is disjoint from envelope_b. + private static boolean polygonDisjointEnvelope_(Polygon polygon_a, + Envelope envelope_b, double tolerance, + ProgressTracker progress_tracker) { + // Quick rasterize test to see whether the the geometries are disjoint, + // or if one is contained in the other. + int relation = tryRasterizedContainsOrDisjoint_(polygon_a, envelope_b, + tolerance, false); + + if (relation == Relation.disjoint) + return true; + + if (relation == Relation.contains || relation == Relation.within) + return false; + + Envelope2D env_a = new Envelope2D(), env_b = new Envelope2D(); + polygon_a.queryEnvelope2D(env_a); + envelope_b.queryEnvelope2D(env_b); + + if (envelopeInfContainsEnvelope_(env_b, env_a, tolerance)) + return false; + + PolygonUtils.PiPResult pipres; + Point2D pt_b = new Point2D(); + env_b.queryLowerLeft(pt_b); + pipres = PolygonUtils.isPointInPolygon2D(polygon_a, pt_b, tolerance); + if (pipres != PolygonUtils.PiPResult.PiPOutside) + return false; + + env_b.queryLowerRight(pt_b); + pipres = PolygonUtils.isPointInPolygon2D(polygon_a, pt_b, tolerance); + if (pipres != PolygonUtils.PiPResult.PiPOutside) + return false; + + env_b.queryUpperRight(pt_b); + pipres = PolygonUtils.isPointInPolygon2D(polygon_a, pt_b, tolerance); + if (pipres != PolygonUtils.PiPResult.PiPOutside) + return false; + + env_b.queryUpperLeft(pt_b); + pipres = PolygonUtils.isPointInPolygon2D(polygon_a, pt_b, tolerance); + if (pipres != PolygonUtils.PiPResult.PiPOutside) + return false; + + MultiPathImpl mimpl_a = (MultiPathImpl) polygon_a._getImpl(); + AttributeStreamOfDbl pos = (AttributeStreamOfDbl) (mimpl_a + .getAttributeStreamRef(VertexDescription.Semantics.POSITION)); + + Envelope2D env_b_inflated = new Envelope2D(); + env_b_inflated.setCoords(env_b); + env_b_inflated.inflate(tolerance, tolerance); + for (int ptIndex = 0, n = mimpl_a.getPointCount(); ptIndex < n; ptIndex++) { + double x = pos.read(2 * ptIndex); + double y = pos.read(2 * ptIndex + 1); + if (env_b_inflated.contains(x, y)) + return false; + } + + return !linearPathIntersectsEnvelope_(polygon_a, env_b, tolerance, + progress_tracker); + } + + // Returns true if polygon_a touches envelope_b. + private static boolean polygonTouchesEnvelope_(Polygon polygon_a, + Envelope envelope_b, double tolerance, + ProgressTracker progress_tracker) { + // Quick rasterize test to see whether the the geometries are disjoint, + // or if one is contained in the other. + int relation = tryRasterizedContainsOrDisjoint_(polygon_a, envelope_b, + tolerance, false); + + if (relation == Relation.disjoint || relation == Relation.contains + || relation == Relation.within) + return false; + + Envelope2D env_a = new Envelope2D(), env_b = new Envelope2D(); + polygon_a.queryEnvelope2D(env_a); + envelope_b.queryEnvelope2D(env_b); + + if (envelopeInfContainsEnvelope_(env_b, env_a, tolerance)) + return false; + + if (env_b.getWidth() <= tolerance && env_b.getHeight() <= tolerance) {// treat + // as + // point + Point2D pt_b = envelope_b.getCenterXY(); + return polygonTouchesPointImpl_(polygon_a, pt_b, tolerance, + progress_tracker); + } + + if (env_b.getWidth() <= tolerance || env_b.getHeight() <= tolerance) {// treat + // as + // polyline + Polyline polyline_b = new Polyline(); + Point p = new Point(); + envelope_b.queryCornerByVal(0, p); + polyline_b.startPath(p); + envelope_b.queryCornerByVal(2, p); + polyline_b.lineTo(p); + return polygonTouchesPolylineImpl_(polygon_a, polyline_b, + tolerance, progress_tracker); + } + + // treat as polygon + Polygon polygon_b = new Polygon(); + polygon_b.addEnvelope(envelope_b, false); + return polygonTouchesPolygonImpl_(polygon_a, polygon_b, tolerance, + progress_tracker); + } + + // Returns true if polygon_a overlaps envelope_b. + private static boolean polygonOverlapsEnvelope_(Polygon polygon_a, + Envelope envelope_b, double tolerance, + ProgressTracker progress_tracker) { + // Quick rasterize test to see whether the the geometries are disjoint, + // or if one is contained in the other. + int relation = tryRasterizedContainsOrDisjoint_(polygon_a, envelope_b, + tolerance, false); + + if (relation == Relation.disjoint || relation == Relation.contains + || relation == Relation.within) + return false; + + Envelope2D env_a = new Envelope2D(), env_b = new Envelope2D(); + polygon_a.queryEnvelope2D(env_a); + envelope_b.queryEnvelope2D(env_b); + + if (envelopeInfContainsEnvelope_(env_b, env_a, tolerance)) + return false; + + if (env_b.getWidth() <= tolerance || env_b.getHeight() <= tolerance) + return false; // has no interior + + Polygon polygon_b = new Polygon(); + polygon_b.addEnvelope(envelope_b, false); + return polygonOverlapsPolygonImpl_(polygon_a, polygon_b, tolerance, + progress_tracker); + } + + // Returns true if polygon_a is within envelope_b + private static boolean polygonWithinEnvelope_(Polygon polygon_a, + Envelope envelope_b, double tolerance, + ProgressTracker progress_tracker) { + Envelope2D env_a = new Envelope2D(), env_b = new Envelope2D(); + polygon_a.queryEnvelope2D(env_a); + envelope_b.queryEnvelope2D(env_b); + return envelopeInfContainsEnvelope_(env_b, env_a, tolerance); + } + + // Returns true if polygon_a contains envelope_b. + private static boolean polygonContainsEnvelope_(Polygon polygon_a, + Envelope envelope_b, double tolerance, + ProgressTracker progress_tracker) { + // Quick envelope rejection test + + Envelope2D env_a = new Envelope2D(), env_b = new Envelope2D(); + polygon_a.queryEnvelope2D(env_a); + envelope_b.queryEnvelope2D(env_b); + + if (!envelopeInfContainsEnvelope_(env_a, env_b, tolerance)) + return false; + + // Quick rasterize test to see whether the the geometries are disjoint, + // or if one is contained in the other. + int relation = tryRasterizedContainsOrDisjoint_(polygon_a, envelope_b, + tolerance, false); + + if (relation == Relation.disjoint || relation == Relation.within) + return false; + + if (relation == Relation.contains) + return true; + + if (env_b.getWidth() <= tolerance && env_b.getHeight() <= tolerance) {// treat + // as + // point + Point2D pt_b = envelope_b.getCenterXY(); + return polygonContainsPointImpl_(polygon_a, pt_b, tolerance, + progress_tracker); + } + + if (env_b.getWidth() <= tolerance || env_b.getHeight() <= tolerance) {// treat + // as + // polyline + Polyline polyline_b = new Polyline(); + Point p = new Point(); + envelope_b.queryCornerByVal(0, p); + polyline_b.startPath(p); + envelope_b.queryCornerByVal(2, p); + polyline_b.lineTo(p); + return polygonContainsPolylineImpl_(polygon_a, polyline_b, + tolerance, null); + } + + // treat as polygon + Polygon polygon_b = new Polygon(); + polygon_b.addEnvelope(envelope_b, false); + return polygonContainsPolygonImpl_(polygon_a, polygon_b, tolerance, + null); + } + + // Returns true if polygon_a crosses envelope_b. + private static boolean polygonCrossesEnvelope_(Polygon polygon_a, + Envelope envelope_b, double tolerance, + ProgressTracker progress_tracker) { + Envelope2D env_a = new Envelope2D(), env_b = new Envelope2D(); + polygon_a.queryEnvelope2D(env_a); + envelope_b.queryEnvelope2D(env_b); + + if (envelopeInfContainsEnvelope_(env_b, env_a, tolerance)) + return false; + + if (env_b.getHeight() > tolerance && env_b.getWidth() > tolerance) + return false; // when treated as an area, areas cannot cross areas. + + if (env_b.getHeight() <= tolerance && env_b.getWidth() <= tolerance) + return false; // when treated as a point, areas cannot cross points. + + // Treat as polyline + Polyline polyline_b = new Polyline(); + Point p = new Point(); + envelope_b.queryCornerByVal(0, p); + polyline_b.startPath(p); + envelope_b.queryCornerByVal(2, p); + polyline_b.lineTo(p); + return polygonCrossesPolylineImpl_(polygon_a, polyline_b, tolerance, + progress_tracker); + } + + // Returns true if polyline_a equals polyline_b. + private static boolean polylineEqualsPolyline_(Polyline polyline_a, + Polyline polyline_b, double tolerance, + ProgressTracker progress_tracker) { + Envelope2D env_a = new Envelope2D(), env_b = new Envelope2D(); + polyline_a.queryEnvelope2D(env_a); + polyline_b.queryEnvelope2D(env_b); + + // Quick envelope rejection test for false equality. + if (!envelopeEqualsEnvelope_(env_a, env_b, tolerance, progress_tracker)) + return false; + + // Quick rasterize test to see whether the the geometries are disjoint. + if (tryRasterizedContainsOrDisjoint_(polyline_a, polyline_b, tolerance, + false) == Relation.disjoint) + return false; + + // Quick point equality check for true equality. This just checks if all + // the points in each ring are the same (within a tolerance) and in the + // same order + if (multiPathExactlyEqualsMultiPath_(polyline_a, polyline_b, tolerance, + progress_tracker)) + return true; + + return linearPathEqualsLinearPath_(polyline_a, polyline_b, tolerance); + } + + // Returns true if polyline_a is disjoint from polyline_b. + private static boolean polylineDisjointPolyline_(Polyline polyline_a, + Polyline polyline_b, double tolerance, + ProgressTracker progress_tracker) { + // Quick rasterize test to see whether the the geometries are disjoint. + if (tryRasterizedContainsOrDisjoint_(polyline_a, polyline_b, tolerance, + false) == Relation.disjoint) + return true; + + return !linearPathIntersectsLinearPath_(polyline_a, polyline_b, + tolerance); + } + + // Returns true if polyline_a touches polyline_b. + private static boolean polylineTouchesPolyline_(Polyline polyline_a, + Polyline polyline_b, double tolerance, + ProgressTracker progress_tracker) { + // Quick rasterize test to see whether the the geometries are disjoint. + if (tryRasterizedContainsOrDisjoint_(polyline_a, polyline_b, tolerance, + false) == Relation.disjoint) + return false; + + AttributeStreamOfDbl intersections = new AttributeStreamOfDbl(0); + + int dim = linearPathIntersectsLinearPathMaxDim_(polyline_a, polyline_b, + tolerance, intersections); + + if (dim != 0) + return false; + + MultiPoint intersection = new MultiPoint(); + + for (int i = 0; i < intersections.size(); i += 2) { + double x = intersections.read(i); + double y = intersections.read(i + 1); + intersection.add(x, y); + } + + MultiPoint boundary_a_b = (MultiPoint) (polyline_a.getBoundary()); + MultiPoint boundary_b = (MultiPoint) (polyline_b.getBoundary()); + + boundary_a_b.add(boundary_b, 0, boundary_b.getPointCount()); + + return multiPointContainsMultiPointBrute_(boundary_a_b, intersection, + tolerance); + } + + // Returns true if polyline_a crosses polyline_b. + private static boolean polylineCrossesPolyline_(Polyline polyline_a, + Polyline polyline_b, double tolerance, + ProgressTracker progress_tracker) { + // Quick rasterize test to see whether the the geometries are disjoint. + if (tryRasterizedContainsOrDisjoint_(polyline_a, polyline_b, tolerance, + false) == Relation.disjoint) + return false; + + AttributeStreamOfDbl intersections = new AttributeStreamOfDbl(0); + + int dim = linearPathIntersectsLinearPathMaxDim_(polyline_a, polyline_b, + tolerance, intersections); + + if (dim != 0) + return false; + + MultiPoint intersection = new MultiPoint(); + + for (int i = 0; i < intersections.size(); i += 2) { + double x = intersections.read(i); + double y = intersections.read(i + 1); + intersection.add(x, y); + } + + MultiPoint boundary_a_b = (MultiPoint) (polyline_a.getBoundary()); + MultiPoint boundary_b = (MultiPoint) (polyline_b.getBoundary()); + + boundary_a_b.add(boundary_b, 0, boundary_b.getPointCount()); + + return !multiPointContainsMultiPointBrute_(boundary_a_b, intersection, + tolerance); + } + + // Returns true if polyline_a overlaps polyline_b. + private static boolean polylineOverlapsPolyline_(Polyline polyline_a, + Polyline polyline_b, double tolerance, + ProgressTracker progress_tracker) { + // Quick rasterize test to see whether the the geometries are disjoint. + if (tryRasterizedContainsOrDisjoint_(polyline_a, polyline_b, tolerance, + false) == Relation.disjoint) + return false; + + return linearPathOverlapsLinearPath_(polyline_a, polyline_b, tolerance); + } + + // Returns true if polyline_a contains polyline_b. + private static boolean polylineContainsPolyline_(Polyline polyline_a, + Polyline polyline_b, double tolerance, + ProgressTracker progress_tracker) { + Envelope2D env_a = new Envelope2D(), env_b = new Envelope2D(); + polyline_a.queryEnvelope2D(env_a); + polyline_b.queryEnvelope2D(env_b); + + // Quick envelope rejection test for false equality. + if (!envelopeInfContainsEnvelope_(env_a, env_b, tolerance)) + return false; + + // Quick rasterize test to see whether the the geometries are disjoint. + if (tryRasterizedContainsOrDisjoint_(polyline_a, polyline_b, tolerance, + false) == Relation.disjoint) + return false; + + return linearPathWithinLinearPath_(polyline_b, polyline_a, tolerance); + } + + // Returns true if polyline_a is disjoint from point_b. + private static boolean polylineDisjointPoint_(Polyline polyline_a, + Point point_b, double tolerance, ProgressTracker progress_tracker) { + // Quick rasterize test to see whether the the geometries are disjoint. + if (tryRasterizedContainsOrDisjoint_(polyline_a, point_b, tolerance, + false) == Relation.disjoint) + return true; + + Point2D pt_b = point_b.getXY(); + return !linearPathIntersectsPoint_(polyline_a, pt_b, tolerance); + } + + // Returns true if polyline_a touches point_b. + private static boolean polylineTouchesPoint_(Polyline polyline_a, + Point point_b, double tolerance, ProgressTracker progress_tracker) { + // Quick rasterize test to see whether the the geometries are disjoint. + if (tryRasterizedContainsOrDisjoint_(polyline_a, point_b, tolerance, + false) == Relation.disjoint) + return false; + + Point2D pt_b = point_b.getXY(); + return linearPathTouchesPointImpl_(polyline_a, pt_b, tolerance); + } + + // Returns true of polyline_a contains point_b. + private static boolean polylineContainsPoint_(Polyline polyline_a, + Point point_b, double tolerance, ProgressTracker progress_tracker) { + // Quick rasterize test to see whether the the geometries are disjoint. + if (tryRasterizedContainsOrDisjoint_(polyline_a, point_b, tolerance, + false) == Relation.disjoint) + return false; + + Point2D pt_b = point_b.getXY(); + return linearPathContainsPoint_(polyline_a, pt_b, tolerance); + } + + // Returns true if polyline_a is disjoint from multipoint_b. + private static boolean polylineDisjointMultiPoint_(Polyline polyline_a, + MultiPoint multipoint_b, double tolerance, + ProgressTracker progress_tracker) { + // Quick rasterize test to see whether the the geometries are disjoint. + if (tryRasterizedContainsOrDisjoint_(polyline_a, multipoint_b, + tolerance, false) == Relation.disjoint) + return true; + + return !linearPathIntersectsMultiPoint_(polyline_a, multipoint_b, + tolerance, false); + } + + // Returns true if polyline_a touches multipoint_b. + private static boolean polylineTouchesMultiPoint_(Polyline polyline_a, + MultiPoint multipoint_b, double tolerance, + ProgressTracker progress_tracker) { + // Quick rasterize test to see whether the the geometries are disjoint. + if (tryRasterizedContainsOrDisjoint_(polyline_a, multipoint_b, + tolerance, false) == Relation.disjoint) + return false; + + SegmentIteratorImpl segIterA = ((MultiPathImpl) polyline_a._getImpl()) + .querySegmentIterator(); + + Envelope2D env_a = new Envelope2D(); + Envelope2D env_b = new Envelope2D(); + Envelope2D envInter = new Envelope2D(); + polyline_a.queryEnvelope2D(env_a); + multipoint_b.queryEnvelope2D(env_b); + env_a.inflate(tolerance, tolerance); + env_b.inflate(tolerance, tolerance); + envInter.setCoords(env_a); + envInter.intersect(env_b); + + QuadTreeImpl quadTreeA = InternalUtils.buildQuadTree( + (MultiPathImpl) (polyline_a._getImpl()), envInter); + QuadTreeImpl.QuadTreeIteratorImpl qtIterA = quadTreeA.getIterator(); + + Point2D ptB = new Point2D(), closest = new Point2D(); + boolean b_intersects = false; + double toleranceSq = tolerance * tolerance; + + AttributeStreamOfInt8 intersects = new AttributeStreamOfInt8( + multipoint_b.getPointCount()); + for (int i = 0; i < multipoint_b.getPointCount(); i++) + intersects.write(i, (byte) 0); + + for (int i = 0; i < multipoint_b.getPointCount(); i++) { + multipoint_b.getXY(i, ptB); + + if (!envInter.contains(ptB)) + continue; + + env_b.setCoords(ptB.x, ptB.y, ptB.x, ptB.y); + qtIterA.resetIterator(env_b, tolerance); + + for (int elementHandleA = qtIterA.next(); elementHandleA != -1; elementHandleA = qtIterA + .next()) { + int vertex_a = quadTreeA.getElement(elementHandleA); + segIterA.resetToVertex(vertex_a); + + Segment segmentA = segIterA.nextSegment(); + double t = segmentA.getClosestCoordinate(ptB, false); + segmentA.getCoord2D(t, closest); + + if (Point2D.sqrDistance(ptB, closest) <= toleranceSq) { + intersects.write(i, (byte) 1); + b_intersects = true; + break; + } + } + } + + if (!b_intersects) + return false; + + MultiPoint boundary_a = (MultiPoint) (polyline_a.getBoundary()); + MultiPoint multipoint_b_inter = new MultiPoint(); + Point2D pt = new Point2D(); + + for (int i = 0; i < multipoint_b.getPointCount(); i++) { + if (intersects.read(i) == 0) + continue; + + multipoint_b.getXY(i, pt); + multipoint_b_inter.add(pt.x, pt.y); + } + + return multiPointContainsMultiPointBrute_(boundary_a, + multipoint_b_inter, tolerance); + } + + // Returns true if polyline_a crosses multipoint_b. + private static boolean polylineCrossesMultiPoint_(Polyline polyline_a, + MultiPoint multipoint_b, double tolerance, + ProgressTracker progress_tracker) { + // Quick rasterize test to see whether the the geometries are disjoint. + if (tryRasterizedContainsOrDisjoint_(polyline_a, multipoint_b, + tolerance, false) == Relation.disjoint) + return false; + + SegmentIteratorImpl segIterA = ((MultiPathImpl) polyline_a._getImpl()) + .querySegmentIterator(); + + Envelope2D env_a = new Envelope2D(); + Envelope2D env_b = new Envelope2D(); + Envelope2D envInter = new Envelope2D(); + polyline_a.queryEnvelope2D(env_a); + multipoint_b.queryEnvelope2D(env_b); + env_a.inflate(tolerance, tolerance); + env_b.inflate(tolerance, tolerance); + envInter.setCoords(env_a); + envInter.intersect(env_b); + + QuadTreeImpl quadTreeA = InternalUtils.buildQuadTree( + (MultiPathImpl) polyline_a._getImpl(), envInter); + QuadTreeImpl.QuadTreeIteratorImpl qtIterA = quadTreeA.getIterator(); + + Point2D ptB = new Point2D(), closest = new Point2D(); + boolean b_intersects = false; + boolean b_exterior_found = false; + double toleranceSq = tolerance * tolerance; + + AttributeStreamOfInt8 intersects = new AttributeStreamOfInt8( + multipoint_b.getPointCount()); + for (int i = 0; i < multipoint_b.getPointCount(); i++) + intersects.write(i, (byte) 0); + + for (int i = 0; i < multipoint_b.getPointCount(); i++) { + multipoint_b.getXY(i, ptB); + + if (!envInter.contains(ptB)) { + b_exterior_found = true; + continue; + } + + env_b.setCoords(ptB.x, ptB.y, ptB.x, ptB.y); + qtIterA.resetIterator(env_b, tolerance); + + boolean b_covered = false; + + for (int elementHandleA = qtIterA.next(); elementHandleA != -1; elementHandleA = qtIterA + .next()) { + int vertex_a = quadTreeA.getElement(elementHandleA); + segIterA.resetToVertex(vertex_a); + + Segment segmentA = segIterA.nextSegment(); + double t = segmentA.getClosestCoordinate(ptB, false); + segmentA.getCoord2D(t, closest); + + if (Point2D.sqrDistance(ptB, closest) <= toleranceSq) { + intersects.write(i, (byte) 1); + b_intersects = true; + b_covered = true; + break; + } + } + + if (!b_covered) + b_exterior_found = true; + } + + if (!b_intersects || !b_exterior_found) + return false; + + MultiPoint boundary_a = (MultiPoint) (polyline_a.getBoundary()); + MultiPoint multipoint_b_inter = new MultiPoint(); + Point2D pt = new Point2D(); + + for (int i = 0; i < multipoint_b.getPointCount(); i++) { + if (intersects.read(i) == 0) + continue; + + multipoint_b.getXY(i, pt); + multipoint_b_inter.add(pt.x, pt.y); + } + + return !multiPointContainsMultiPointBrute_(boundary_a, + multipoint_b_inter, tolerance); + } + + // Returns true if polyline_a contains multipoint_b. + private static boolean polylineContainsMultiPoint_(Polyline polyline_a, + MultiPoint multipoint_b, double tolerance, + ProgressTracker progress_tracker) { + Envelope2D env_a = new Envelope2D(), env_b = new Envelope2D(); + polyline_a.queryEnvelope2D(env_a); + multipoint_b.queryEnvelope2D(env_b); + + if (!envelopeInfContainsEnvelope_(env_a, env_b, tolerance)) + return false; + + // Quick rasterize test to see whether the the geometries are disjoint. + if (tryRasterizedContainsOrDisjoint_(polyline_a, multipoint_b, + tolerance, false) == Relation.disjoint) + return false; + + if (!linearPathIntersectsMultiPoint_(polyline_a, multipoint_b, + tolerance, true)) + return false; + + MultiPoint boundary_a = (MultiPoint) (polyline_a.getBoundary()); + return !multiPointIntersectsMultiPoint_(boundary_a, multipoint_b, + tolerance, progress_tracker); + } + + // Returns true if polyline_a equals envelope_b. + private static boolean polylineEqualsEnvelope_(Polyline polyline_a, + Envelope envelope_b, double tolerance, + ProgressTracker progress_tracker) { + Envelope2D env_a = new Envelope2D(), env_b = new Envelope2D(); + polyline_a.queryEnvelope2D(env_a); + envelope_b.queryEnvelope2D(env_b); + + if (env_b.getHeight() > tolerance && env_b.getWidth() > tolerance) + return false; // area cannot equal a line + + return envelopeEqualsEnvelope_(env_a, env_b, tolerance, + progress_tracker); + } + + // Returns true if polyline_a is disjoint from envelope_b. + private static boolean polylineDisjointEnvelope_(Polyline polyline_a, + Envelope envelope_b, double tolerance, + ProgressTracker progress_tracker) { + Envelope2D env_a = new Envelope2D(), env_b = new Envelope2D(); + polyline_a.queryEnvelope2D(env_a); + envelope_b.queryEnvelope2D(env_b); + + if (envelopeInfContainsEnvelope_(env_b, env_a, tolerance)) + return false; + + return !linearPathIntersectsEnvelope_(polyline_a, env_b, tolerance, + progress_tracker); + } + + // Returns true if polyline_a touches envelope_b. + private static boolean polylineTouchesEnvelope_(Polyline polyline_a, + Envelope envelope_b, double tolerance, + ProgressTracker progress_tracker) { + Envelope2D env_a = new Envelope2D(), env_b = new Envelope2D(); + polyline_a.queryEnvelope2D(env_a); + envelope_b.queryEnvelope2D(env_b); + + if (env_b.getHeight() <= tolerance && env_b.getWidth() <= tolerance) {// Treat + // as + // point + Point2D pt_b = envelope_b.getCenterXY(); + return linearPathTouchesPointImpl_(polyline_a, pt_b, tolerance); + } + + if (env_b.getHeight() <= tolerance || env_b.getWidth() <= tolerance) {// Treat + // as + // polyline + Polyline polyline_b = new Polyline(); + Point p = new Point(); + envelope_b.queryCornerByVal(0, p); + polyline_b.startPath(p); + envelope_b.queryCornerByVal(2, p); + polyline_b.lineTo(p); + return polylineTouchesPolyline_(polyline_a, polyline_b, tolerance, + progress_tracker); + } + + // Treat env_b as area + + SegmentIterator seg_iter_a = polyline_a.querySegmentIterator(); + Envelope2D env_b_deflated = new Envelope2D(), env_b_inflated = new Envelope2D(); + env_b_deflated.setCoords(env_b); + env_b_inflated.setCoords(env_b); + env_b_deflated.inflate(-tolerance, -tolerance); + env_b_inflated.inflate(tolerance, tolerance); + + boolean b_boundary = false; + Envelope2D env_segment_a = new Envelope2D(); + Envelope2D env_inter = new Envelope2D(); + + while (seg_iter_a.nextPath()) { + while (seg_iter_a.hasNextSegment()) { + Segment segment_a = seg_iter_a.nextSegment(); + segment_a.queryEnvelope2D(env_segment_a); + + env_inter.setCoords(env_b_deflated); + env_inter.intersect(env_segment_a); + + if (!env_inter.isEmpty() + && (env_inter.getHeight() > tolerance || env_inter + .getWidth() > tolerance)) + return false; // consider segment within + + env_inter.setCoords(env_b_inflated); + env_inter.intersect(env_segment_a); + + if (!env_inter.isEmpty()) + b_boundary = true; + } + } + + return b_boundary; + } + + // Returns true if polyline_a overlaps envelope_b. + private static boolean polylineOverlapsEnvelope_(Polyline polyline_a, + Envelope envelope_b, double tolerance, + ProgressTracker progress_tracker) { + Envelope2D env_a = new Envelope2D(), env_b = new Envelope2D(); + polyline_a.queryEnvelope2D(env_a); + envelope_b.queryEnvelope2D(env_b); + + if (envelopeInfContainsEnvelope_(env_a, env_b, tolerance) + || envelopeInfContainsEnvelope_(env_b, env_a, tolerance)) + return false; + + if (envelopeInfContainsEnvelope_(env_b, env_a, tolerance)) + return false; + + if (env_b.getHeight() > tolerance && env_b.getWidth() > tolerance) + return false; // lines cannot overlap areas + + if (env_b.getHeight() <= tolerance && env_b.getWidth() <= tolerance) + return false; // lines cannot overlap points + + // Treat as polyline + Polyline polyline_b = new Polyline(); + Point p = new Point(); + envelope_b.queryCornerByVal(0, p); + polyline_b.startPath(p); + envelope_b.queryCornerByVal(2, p); + polyline_b.lineTo(p); + return linearPathOverlapsLinearPath_(polyline_a, polyline_b, tolerance); + } + + // Returns true if polyline_a is within envelope_b. + private static boolean polylineWithinEnvelope_(Polyline polyline_a, + Envelope envelope_b, double tolerance, + ProgressTracker progress_tracker) { + Envelope2D env_a = new Envelope2D(), env_b = new Envelope2D(); + polyline_a.queryEnvelope2D(env_a); + envelope_b.queryEnvelope2D(env_b); + + if (!envelopeInfContainsEnvelope_(env_b, env_a, tolerance)) + return false; + + if (env_b.getHeight() <= tolerance && env_b.getWidth() <= tolerance) + return false; + + if (env_b.getHeight() <= tolerance || env_b.getWidth() <= tolerance) + return envelopeInfContainsEnvelope_(env_b, env_a, tolerance); + + SegmentIterator seg_iter_a = polyline_a.querySegmentIterator(); + Envelope2D env_b_deflated = new Envelope2D(); + env_b_deflated.setCoords(env_b); + env_b_deflated.inflate(-tolerance, -tolerance); + + boolean b_interior = false; + Envelope2D env_segment_a = new Envelope2D(); + Envelope2D env_inter = new Envelope2D(); + + while (seg_iter_a.nextPath()) { + while (seg_iter_a.hasNextSegment()) { + Segment segment_a = seg_iter_a.nextSegment(); + segment_a.queryEnvelope2D(env_segment_a); + + if (env_b_deflated.containsExclusive(env_segment_a)) { + b_interior = true; + continue; + } + + env_inter.setCoords(env_b_deflated); + env_inter.intersect(env_segment_a); + + if (!env_inter.isEmpty() + && (env_inter.getHeight() > tolerance || env_inter + .getWidth() > tolerance)) + b_interior = true; + } + } + + return b_interior; + } + + // Returns true if polyline_a contains envelope_b. + private static boolean polylineContainsEnvelope_(Polyline polyline_a, + Envelope envelope_b, double tolerance, + ProgressTracker progress_tracker) { + Envelope2D env_a = new Envelope2D(), env_b = new Envelope2D(); + envelope_b.queryEnvelope2D(env_b); + polyline_a.queryEnvelope2D(env_a); + + if (!envelopeInfContainsEnvelope_(env_a, env_b, tolerance)) + return false; + + if (env_b.getHeight() > tolerance && env_b.getWidth() > tolerance) + return false; // when treated as an area, lines cannot contain + // areas. + + if (env_b.getHeight() <= tolerance && env_b.getWidth() <= tolerance) {// Treat + // as + // point + Point2D pt_b = envelope_b.getCenterXY(); + return linearPathContainsPoint_(polyline_a, pt_b, tolerance); + } + + // Treat as polyline + Polyline polyline_b = new Polyline(); + Point p = new Point(); + envelope_b.queryCornerByVal(0, p); + polyline_b.startPath(p); + envelope_b.queryCornerByVal(2, p); + polyline_b.lineTo(p); + return linearPathWithinLinearPath_(polyline_b, polyline_a, tolerance); + } + + // Returns true if polyline_a crosses envelope_b. + private static boolean polylineCrossesEnvelope_(Polyline polyline_a, + Envelope envelope_b, double tolerance, + ProgressTracker progress_tracker) { + Envelope2D env_a = new Envelope2D(), env_b = new Envelope2D(); + polyline_a.queryEnvelope2D(env_a); + envelope_b.queryEnvelope2D(env_b); + + if (envelopeInfContainsEnvelope_(env_b, env_a, tolerance)) + return false; + + if (env_b.getHeight() <= tolerance && env_b.getWidth() <= tolerance) + return false; // when treated as a point, lines cannot cross points. + + if (env_b.getHeight() <= tolerance || env_b.getWidth() <= tolerance) {// Treat + // as + // polyline + Polyline polyline_b = new Polyline(); + Point p = new Point(); + envelope_b.queryCornerByVal(0, p); + polyline_b.startPath(p); + envelope_b.queryCornerByVal(2, p); + polyline_b.lineTo(p); + return polylineCrossesPolyline_(polyline_a, polyline_b, tolerance, + progress_tracker); + } + + // Treat env_b as area + + SegmentIterator seg_iter_a = polyline_a.querySegmentIterator(); + Envelope2D env_b_inflated = new Envelope2D(), env_b_deflated = new Envelope2D(); + env_b_deflated.setCoords(env_b); + env_b_inflated.setCoords(env_b); + env_b_deflated.inflate(-tolerance, -tolerance); + env_b_inflated.inflate(tolerance, tolerance); + + boolean b_interior = false, b_exterior = false; + Envelope2D env_segment_a = new Envelope2D(); + Envelope2D env_inter = new Envelope2D(); + + while (seg_iter_a.nextPath()) { + while (seg_iter_a.hasNextSegment()) { + Segment segment_a = seg_iter_a.nextSegment(); + segment_a.queryEnvelope2D(env_segment_a); + + if (!b_exterior) { + if (!env_b_inflated.contains(env_segment_a)) + b_exterior = true; + } + + if (!b_interior) { + env_inter.setCoords(env_b_deflated); + env_inter.intersect(env_segment_a); + + if (!env_inter.isEmpty() + && (env_inter.getHeight() > tolerance || env_inter + .getWidth() > tolerance)) + b_interior = true; + } + + if (b_interior && b_exterior) + return true; + } + } + + return false; + } + + // Returns true if multipoint_a equals multipoint_b. + private static boolean multiPointEqualsMultiPoint_(MultiPoint multipoint_a, + MultiPoint multipoint_b, double tolerance, + ProgressTracker progress_tracker) { + Envelope2D env_a = new Envelope2D(), env_b = new Envelope2D(); + multipoint_a.queryEnvelope2D(env_a); + multipoint_b.queryEnvelope2D(env_b); + + if (!envelopeEqualsEnvelope_(env_a, env_b, tolerance, progress_tracker)) + return false; + + if (multiPointExactlyEqualsMultiPoint_(multipoint_a, multipoint_b, + tolerance, progress_tracker)) + return true; + + return multiPointCoverageMultiPoint_(multipoint_a, multipoint_b, + tolerance, false, true, false, progress_tracker); + } + + // Returns true if multipoint_a is disjoint from multipoint_b. + private static boolean multiPointDisjointMultiPoint_( + MultiPoint multipoint_a, MultiPoint multipoint_b, double tolerance, + ProgressTracker progress_tracker) { + return !multiPointIntersectsMultiPoint_(multipoint_a, multipoint_b, + tolerance, progress_tracker); + } + + // Returns true if multipoint_a overlaps multipoint_b. + private static boolean multiPointOverlapsMultiPoint_( + MultiPoint multipoint_a, MultiPoint multipoint_b, double tolerance, + ProgressTracker progress_tracker) { + return multiPointCoverageMultiPoint_(multipoint_a, multipoint_b, + tolerance, false, false, true, progress_tracker); + } + + // Returns true if multipoint_a contains multipoint_b. + private static boolean multiPointContainsMultiPoint_( + MultiPoint multipoint_a, MultiPoint multipoint_b, double tolerance, + ProgressTracker progress_tracker) { + Envelope2D env_a = new Envelope2D(), env_b = new Envelope2D(); + multipoint_a.queryEnvelope2D(env_a); + multipoint_b.queryEnvelope2D(env_b); + + if (!envelopeInfContainsEnvelope_(env_a, env_b, tolerance)) + return false; + + return multiPointCoverageMultiPoint_(multipoint_b, multipoint_a, + tolerance, true, false, false, progress_tracker); + } + + private static boolean multiPointContainsMultiPointBrute_( + MultiPoint multipoint_a, MultiPoint multipoint_b, double tolerance) { + double tolerance_sq = tolerance * tolerance; + Point2D pt_a = new Point2D(); + Point2D pt_b = new Point2D(); + + for (int i = 0; i < multipoint_b.getPointCount(); i++) { + multipoint_b.getXY(i, pt_b); + boolean b_contained = false; + + for (int j = 0; j < multipoint_a.getPointCount(); j++) { + multipoint_a.getXY(j, pt_a); + + if (Point2D.sqrDistance(pt_a, pt_b) <= tolerance_sq) { + b_contained = true; + break; + } + } + + if (!b_contained) + return false; + } + + return true; + } + + // Returns true if multipoint_a equals point_b. + private static boolean multiPointEqualsPoint_(MultiPoint multipoint_a, + Point point_b, double tolerance, ProgressTracker progress_tracker) { + Envelope2D env_a = new Envelope2D(), env_b = new Envelope2D(); + multipoint_a.queryEnvelope2D(env_a); + point_b.queryEnvelope2D(env_b); + return envelopeEqualsEnvelope_(env_a, env_b, tolerance, + progress_tracker); + } + + // Returns true if multipoint_a is disjoint from point_b. + private static boolean multiPointDisjointPoint_(MultiPoint multipoint_a, + Point point_b, double tolerance, ProgressTracker progress_tracker) { + Point2D pt_b = point_b.getXY(); + return multiPointDisjointPointImpl_(multipoint_a, pt_b, tolerance, + progress_tracker); + } + + // Returns true if multipoint_a is within point_b. + private static boolean multiPointWithinPoint_(MultiPoint multipoint_a, + Point point_b, double tolerance, ProgressTracker progress_tracker) { + return multiPointEqualsPoint_(multipoint_a, point_b, tolerance, + progress_tracker); + } + + // Returns true if multipoint_a contains point_b. + private static boolean multiPointContainsPoint_(MultiPoint multipoint_a, + Point point_b, double tolerance, ProgressTracker progress_tracker) { + return !multiPointDisjointPoint_(multipoint_a, point_b, tolerance, + progress_tracker); + } + + // Returns true if multipoint_a equals envelope_b. + private static boolean multiPointEqualsEnvelope_(MultiPoint multipoint_a, + Envelope envelope_b, double tolerance, + ProgressTracker progress_tracker) { + Envelope2D env_a = new Envelope2D(), env_b = new Envelope2D(); + multipoint_a.queryEnvelope2D(env_a); + envelope_b.queryEnvelope2D(env_b); + + if (env_b.getHeight() > tolerance || env_b.getWidth() > tolerance) + return false; + + // only true if all the points of the multi_point degenerate to a point + // equal to the envelope + return envelopeEqualsEnvelope_(env_a, env_b, tolerance, + progress_tracker); + } + + // Returns true if multipoint_a is disjoint from envelope_b. + private static boolean multiPointDisjointEnvelope_(MultiPoint multipoint_a, + Envelope envelope_b, double tolerance, + ProgressTracker progress_tracker) { + Envelope2D env_a = new Envelope2D(), env_b = new Envelope2D(); + multipoint_a.queryEnvelope2D(env_a); + envelope_b.queryEnvelope2D(env_b); + + if (envelopeInfContainsEnvelope_(env_b, env_a, tolerance)) + return false; + + Envelope2D env_b_inflated = new Envelope2D(); + env_b_inflated.setCoords(env_b); + env_b_inflated.inflate(tolerance, tolerance); + Point2D pt_a = new Point2D(); + + for (int i = 0; i < multipoint_a.getPointCount(); i++) { + multipoint_a.getXY(i, pt_a); + + if (!env_b_inflated.contains(pt_a)) + continue; + + return false; + } + + return true; + } + + // Returns true if multipoint_a touches envelope_b. + private static boolean multiPointTouchesEnvelope_(MultiPoint multipoint_a, + Envelope envelope_b, double tolerance, + ProgressTracker progress_tracker) { + Envelope2D env_b = new Envelope2D(), env_b_inflated = new Envelope2D(), env_b_deflated = new Envelope2D(); + envelope_b.queryEnvelope2D(env_b); + + if (env_b.getHeight() <= tolerance && env_b.getWidth() <= tolerance) + return false; // there are no boundaries to intersect + + if (env_b.getHeight() <= tolerance || env_b.getWidth() <= tolerance) {// treat + // as + // line + + Point2D pt_a = new Point2D(); + boolean b_boundary = false; + + env_b_inflated.setCoords(env_b); + env_b_deflated.setCoords(env_b); + env_b_inflated.inflate(tolerance, tolerance); + if (env_b.getHeight() > tolerance) + env_b_deflated.inflate(0, -tolerance); + else + env_b_deflated.inflate(-tolerance, 0); + + for (int i = 0; i < multipoint_a.getPointCount(); i++) { + multipoint_a.getXY(i, pt_a); + + if (!env_b_inflated.contains(pt_a)) + continue; + + if (env_b.getHeight() > tolerance) { + if (pt_a.y > env_b_deflated.ymin + && pt_a.y < env_b_deflated.ymax) + return false; + + b_boundary = true; + } else { + if (pt_a.x > env_b_deflated.xmin + && pt_a.x < env_b_deflated.xmax) + return false; + + b_boundary = true; + } + } + + return b_boundary; + } + + // treat as area + env_b_inflated.setCoords(env_b); + env_b_deflated.setCoords(env_b); + env_b_inflated.inflate(tolerance, tolerance); + env_b_deflated.inflate(-tolerance, -tolerance); + + Point2D pt_a = new Point2D(); + boolean b_boundary = false; + + for (int i = 0; i < multipoint_a.getPointCount(); i++) { + multipoint_a.getXY(i, pt_a); + + if (!env_b_inflated.contains(pt_a)) + continue; + + if (env_b_deflated.containsExclusive(pt_a)) + return false; + + b_boundary = true; + } + + return b_boundary; + } + + // Returns true if multipoint_a is within envelope_b. + private static boolean multiPointWithinEnvelope_(MultiPoint multipoint_a, + Envelope envelope_b, double tolerance, + ProgressTracker progress_tracker) { + Envelope2D env_a = new Envelope2D(), env_b = new Envelope2D(); + multipoint_a.queryEnvelope2D(env_a); + envelope_b.queryEnvelope2D(env_b); + + if (!envelopeInfContainsEnvelope_(env_b, env_a, tolerance)) + return false; + + if (env_b.getHeight() <= tolerance && env_b.getWidth() <= tolerance) + return envelopeEqualsEnvelope_(env_a, env_b, tolerance, + progress_tracker); // treat as point + + if (env_b.getHeight() <= tolerance || env_b.getWidth() <= tolerance) {// treat + // as + // line + + boolean b_interior = false; + + Envelope2D env_b_deflated = new Envelope2D(), env_b_inflated = new Envelope2D(); + env_b_deflated.setCoords(env_b); + env_b_inflated.setCoords(env_b); + + if (env_b.getHeight() > tolerance) + env_b_deflated.inflate(0, -tolerance); + else + env_b_deflated.inflate(-tolerance, 0); + + env_b_inflated.inflate(tolerance, tolerance); + + Point2D pt_a = new Point2D(); + + for (int i = 0; i < multipoint_a.getPointCount(); i++) { + multipoint_a.getXY(i, pt_a); + + if (!env_b_inflated.contains(pt_a)) + return false; + + if (env_b.getHeight() > tolerance) { + if (pt_a.y > env_b_deflated.ymin + && pt_a.y < env_b_deflated.ymax) + b_interior = true; + } else { + if (pt_a.x > env_b_deflated.xmin + && pt_a.x < env_b_deflated.xmax) + b_interior = true; + } + } + + return b_interior; + } + + // treat as area + + boolean b_interior = false; + + Envelope2D env_b_deflated = new Envelope2D(), env_b_inflated = new Envelope2D(); + env_b_deflated.setCoords(env_b); + env_b_inflated.setCoords(env_b); + env_b_deflated.inflate(-tolerance, -tolerance); + env_b_inflated.inflate(tolerance, tolerance); + + Point2D pt_a = new Point2D(); + + // we loop to find a proper interior intersection (i.e. something inside + // instead of just on the boundary) + for (int i = 0; i < multipoint_a.getPointCount(); i++) { + multipoint_a.getXY(i, pt_a); + + if (!env_b_inflated.contains(pt_a)) + return false; + + if (env_b_deflated.containsExclusive(pt_a)) + b_interior = true; + } + + return b_interior; + } + + // Returns true if multipoint_a contains envelope_b. + private static boolean multiPointContainsEnvelope_(MultiPoint multipoint_a, + Envelope envelope_b, double tolerance, + ProgressTracker progress_tracker) { + Envelope2D env_a = new Envelope2D(), env_b = new Envelope2D(); + multipoint_a.queryEnvelope2D(env_a); + envelope_b.queryEnvelope2D(env_b); + + if (!envelopeInfContainsEnvelope_(env_a, env_b, tolerance)) + return false; + + if (env_b.getHeight() > tolerance || env_b.getWidth() > tolerance) + return false; + + Point2D pt_b = envelope_b.getCenterXY(); + return !multiPointDisjointPointImpl_(multipoint_a, pt_b, tolerance, + progress_tracker); + } + + // Returns true if multipoint_a crosses envelope_b. + static boolean multiPointCrossesEnvelope_(MultiPoint multipoint_a, + Envelope envelope_b, double tolerance, + ProgressTracker progress_tracker) { + Envelope2D env_a = new Envelope2D(), env_b = new Envelope2D(); + multipoint_a.queryEnvelope2D(env_a); + envelope_b.queryEnvelope2D(env_b); + + if (envelopeInfContainsEnvelope_(env_b, env_a, tolerance)) + return false; + + if (env_b.getHeight() <= tolerance && env_b.getWidth() <= tolerance) + return false; + + if (env_b.getHeight() <= tolerance || env_b.getWidth() <= tolerance) {// treat + // as + // line + Envelope2D env_b_deflated = new Envelope2D(); + Envelope2D env_b_inflated = new Envelope2D(); + env_b_deflated.setCoords(env_b); + + if (env_b.getHeight() > tolerance) + env_b_deflated.inflate(0, -tolerance); + else + env_b_deflated.inflate(-tolerance, 0); + + env_b_inflated.setCoords(env_b); + env_b_inflated.inflate(tolerance, tolerance); + + Point2D pt_a = new Point2D(); + boolean b_interior = false, b_exterior = false; + + for (int i = 0; i < multipoint_a.getPointCount(); i++) { + multipoint_a.getXY(i, pt_a); + + if (!b_interior) { + if (env_b.getHeight() > tolerance) { + if (pt_a.y > env_b_deflated.ymin + && pt_a.y < env_b_deflated.ymax) + b_interior = true; + } else { + if (pt_a.x > env_b_deflated.xmin + && pt_a.x < env_b_deflated.xmax) + b_interior = true; + } + } + + if (!b_exterior && !env_b_inflated.contains(pt_a)) + b_exterior = true; + + if (b_interior && b_exterior) + return true; + } + + return false; + } + + Envelope2D env_b_deflated = new Envelope2D(), env_b_inflated = new Envelope2D(); + env_b_deflated.setCoords(env_b); + env_b_deflated.inflate(-tolerance, -tolerance); + assert (!env_b_deflated.isEmpty()); + + env_b_inflated.setCoords(env_b); + env_b_inflated.inflate(tolerance, tolerance); + + Point2D pt_a = new Point2D(); + boolean b_interior = false, b_exterior = false; + + for (int i = 0; i < multipoint_a.getPointCount(); i++) { + multipoint_a.getXY(i, pt_a); + + if (!b_interior && env_b_deflated.containsExclusive(pt_a)) + b_interior = true; + + if (!b_exterior && !env_b_inflated.contains(pt_a)) + b_exterior = true; + + if (b_interior && b_exterior) + return true; + } + + return false; + } + + // Returns true if pt_a equals pt_b. + private static boolean pointEqualsPoint_(Point2D pt_a, Point2D pt_b, + double tolerance, ProgressTracker progress_tracker) { + if (Point2D.sqrDistance(pt_a, pt_b) <= tolerance * tolerance) + return true; + + return false; + } + + // Returns true if pt_a is disjoint from pt_b. + private static boolean pointDisjointPoint_(Point2D pt_a, Point2D pt_b, + double tolerance, ProgressTracker progress_tracker) { + if (Point2D.sqrDistance(pt_a, pt_b) > tolerance * tolerance) + return true; + + return false; + } + + // Returns true if pt_a contains pt_b. + private static boolean pointContainsPoint_(Point2D pt_a, Point2D pt_b, + double tolerance, ProgressTracker progress_tracker) { + return pointEqualsPoint_(pt_a, pt_b, tolerance, progress_tracker); + } + + // Returns true if pt_a equals enve_b. + private static boolean pointEqualsEnvelope_(Point2D pt_a, Envelope2D env_b, + double tolerance, ProgressTracker progress_tracker) { + Envelope2D env_a = new Envelope2D(); + env_a.setCoords(pt_a); + return envelopeEqualsEnvelope_(env_a, env_b, tolerance, + progress_tracker); + } + + // Returns true if pt_a is disjoint from env_b. + private static boolean pointDisjointEnvelope_(Point2D pt_a, + Envelope2D env_b, double tolerance, ProgressTracker progress_tracker) { + Envelope2D env_b_inflated = new Envelope2D(); + env_b_inflated.setCoords(env_b); + env_b_inflated.inflate(tolerance, tolerance); + return !env_b_inflated.contains(pt_a); + } + + // Returns true if pt_a touches env_b. + private static boolean pointTouchesEnvelope_(Point2D pt_a, + Envelope2D env_b, double tolerance, ProgressTracker progress_tracker) { + if (env_b.getHeight() <= tolerance && env_b.getWidth() <= tolerance) + return false; // when treates as a point, points cannot touch points + + Envelope2D env_b_inflated = new Envelope2D(), env_b_deflated = new Envelope2D(); + + env_b_inflated.setCoords(env_b); + env_b_inflated.inflate(tolerance, tolerance); + + if (!env_b_inflated.contains(pt_a)) + return false; + + if (env_b.getHeight() <= tolerance || env_b.getWidth() <= tolerance) { + env_b_deflated.setCoords(env_b); + + if (env_b.getHeight() > tolerance) + env_b_deflated.inflate(0, -tolerance); + else + env_b_deflated.inflate(-tolerance, 0); + + if (env_b.getHeight() > tolerance) { + if (pt_a.y > env_b_deflated.ymin + && pt_a.y < env_b_deflated.ymax) + return false; + } else { + if (pt_a.x > env_b_deflated.xmin + && pt_a.x < env_b_deflated.xmax) + return false; + } + + return true; + } + + env_b_deflated.setCoords(env_b); + env_b_deflated.inflate(-tolerance, -tolerance); + + if (env_b_deflated.containsExclusive(pt_a)) + return false; + + return true; + } + + // Returns true if pt_a is within env_b. + private static boolean pointWithinEnvelope_(Point2D pt_a, Envelope2D env_b, + double tolerance, ProgressTracker progress_tracker) { + if (env_b.getHeight() <= tolerance && env_b.getWidth() <= tolerance) { + // assert(env_b_inflated.contains(pt_a)); // should contain if we + // got to here (i.e. not disjoint) + return true; + } + + if (env_b.getHeight() <= tolerance || env_b.getWidth() <= tolerance) {// treat + // as + // line + Envelope2D env_b_deflated = new Envelope2D(); + env_b_deflated.setCoords(env_b); + + if (env_b.getHeight() > tolerance) + env_b_deflated.inflate(0, -tolerance); + else + env_b_deflated.inflate(-tolerance, 0); + + boolean b_interior = false; + + if (env_b.getHeight() > tolerance) { + if (pt_a.y > env_b_deflated.ymin + && pt_a.y < env_b_deflated.ymax) + b_interior = true; + } else { + if (pt_a.x > env_b_deflated.xmin + && pt_a.x < env_b_deflated.xmax) + b_interior = true; + } + + return b_interior; + } + + // treat as area + + Envelope2D env_b_deflated = new Envelope2D(); + env_b_deflated.setCoords(env_b); + env_b_deflated.inflate(-tolerance, -tolerance); + return env_b_deflated.containsExclusive(pt_a); + } + + // Returns true if pt_a contains env_b. + private static boolean pointContainsEnvelope_(Point2D pt_a, + Envelope2D env_b, double tolerance, ProgressTracker progress_tracker) { + return pointEqualsEnvelope_(pt_a, env_b, tolerance, progress_tracker); + } + + // Returns true if env_a equals env_b. + private static boolean envelopeEqualsEnvelope_(Envelope2D env_a, + Envelope2D env_b, double tolerance, ProgressTracker progress_tracker) { + return envelopeInfContainsEnvelope_(env_a, env_b, tolerance) + && envelopeInfContainsEnvelope_(env_b, env_a, tolerance); + } + + // Returns true if env_a is disjoint from env_b. + private static boolean envelopeDisjointEnvelope_(Envelope2D env_a, + Envelope2D env_b, double tolerance, ProgressTracker progress_tracker) { + Envelope2D env_b_inflated = new Envelope2D(); + env_b_inflated.setCoords(env_b); + env_b_inflated.inflate(tolerance, tolerance); + return !env_a.isIntersecting(env_b_inflated); + } + + // Returns true if env_a touches env_b. + private static boolean envelopeTouchesEnvelope_(Envelope2D env_a, + Envelope2D env_b, double tolerance, ProgressTracker progress_tracker) { + if (env_a.getHeight() <= tolerance && env_a.getWidth() <= tolerance) {// treat + // env_a + // as + // point + Point2D pt_a = env_a.getCenter(); + return pointTouchesEnvelope_(pt_a, env_b, tolerance, + progress_tracker); + } + + if (env_b.getHeight() <= tolerance && env_b.getWidth() <= tolerance) {// treat + // env_b + // as + // point + Point2D pt_b = env_b.getCenter(); + return pointTouchesEnvelope_(pt_b, env_a, tolerance, + progress_tracker); + } + + Envelope2D _env_a; + Envelope2D _env_b; + + if (env_a.getHeight() > tolerance + && env_a.getWidth() > tolerance + && (env_b.getHeight() <= tolerance || env_b.getWidth() <= tolerance)) { + // swap a and b + _env_a = env_b; + _env_b = env_a; + } else { + _env_a = env_a; + _env_b = env_b; + } + + if (_env_a.getHeight() <= tolerance || _env_a.getWidth() <= tolerance) {// treat + // env_a + // as + // line + + if (_env_b.getHeight() <= tolerance + || _env_b.getWidth() <= tolerance) {// treat env_b as line + + Line line_a = new Line(), line_b = new Line(); + double[] scalars_a = new double[2]; + double[] scalars_b = new double[2]; + Point2D pt = new Point2D(); + _env_a.queryLowerLeft(pt); + line_a.setStartXY(pt); + _env_a.queryUpperRight(pt); + line_a.setEndXY(pt); + _env_b.queryLowerLeft(pt); + line_b.setStartXY(pt); + _env_b.queryUpperRight(pt); + line_b.setEndXY(pt); + + line_a.intersect(line_b, null, scalars_a, scalars_b, tolerance); + int count = line_a.intersect(line_b, null, null, null, + tolerance); + + if (count != 1) + return false; + + return scalars_a[0] == 0.0 || scalars_a[1] == 1.0 + || scalars_b[0] == 0.0 || scalars_b[1] == 1.0; + } + + // treat env_b as area + + Envelope2D env_b_deflated = new Envelope2D(), env_inter = new Envelope2D(); + env_b_deflated.setCoords(_env_b); + env_b_deflated.inflate(-tolerance, -tolerance); + env_inter.setCoords(env_b_deflated); + env_inter.intersect(_env_a); + + if (!env_inter.isEmpty() + && (env_inter.getHeight() > tolerance || env_inter + .getWidth() > tolerance)) + return false; + + assert (!envelopeDisjointEnvelope_(_env_a, _env_b, tolerance, + progress_tracker)); + return true; // we already know they intersect within a tolerance + } + + Envelope2D env_inter = new Envelope2D(); + env_inter.setCoords(_env_a); + env_inter.intersect(_env_b); + + if (!env_inter.isEmpty() && env_inter.getHeight() > tolerance + && env_inter.getWidth() > tolerance) + return false; + + return true; // we already know they intersect within a tolerance + } + + // Returns true if env_a overlaps env_b. + private static boolean envelopeOverlapsEnvelope_(Envelope2D env_a, + Envelope2D env_b, double tolerance, ProgressTracker progress_tracker) { + if (envelopeInfContainsEnvelope_(env_a, env_b, tolerance) + || envelopeInfContainsEnvelope_(env_b, env_a, tolerance)) + return false; + + if (env_a.getHeight() <= tolerance && env_a.getWidth() <= tolerance) + return false; // points cannot overlap + + if (env_b.getHeight() <= tolerance && env_b.getWidth() <= tolerance) + return false; // points cannot overlap + + if (env_a.getHeight() <= tolerance || env_a.getWidth() <= tolerance) {// treat + // env_a + // as + // a + // line + + if (env_b.getHeight() > tolerance && env_b.getWidth() > tolerance) + return false; // lines cannot overlap areas + + // treat both as lines + + Line line_a = new Line(), line_b = new Line(); + double[] scalars_a = new double[2]; + double[] scalars_b = new double[2]; + Point2D pt = new Point2D(); + env_a.queryLowerLeft(pt); + line_a.setStartXY(pt); + env_a.queryUpperRight(pt); + line_a.setEndXY(pt); + env_b.queryLowerLeft(pt); + line_b.setStartXY(pt); + env_b.queryUpperRight(pt); + line_b.setEndXY(pt); + + line_a.intersect(line_b, null, scalars_a, scalars_b, tolerance); + int count = line_a.intersect(line_b, null, null, null, tolerance); + + if (count != 2) + return false; + + return (scalars_a[0] > 0.0 || scalars_a[1] < 1.0) + && (scalars_b[0] > 0.0 || scalars_b[1] < 1.0); + } + + if (env_b.getHeight() <= tolerance || env_b.getWidth() <= tolerance) + return false; // lines cannot overlap areas + + // treat both as areas + + Envelope2D env_inter = new Envelope2D(); + env_inter.setCoords(env_a); + env_inter.intersect(env_b); + + if (env_inter.isEmpty()) + return false; + + if (env_inter.getHeight() <= tolerance + || env_inter.getWidth() <= tolerance) + return false; // not an area + + assert (!envelopeInfContainsEnvelope_(env_inter, env_a, tolerance) && !envelopeInfContainsEnvelope_( + env_inter, env_b, tolerance)); + + return true; + } + + // Returns true if env_a contains env_b. + private static boolean envelopeContainsEnvelope_(Envelope2D env_a, + Envelope2D env_b, double tolerance, ProgressTracker progress_tracker) { + if (!envelopeInfContainsEnvelope_(env_a, env_b, tolerance)) + return false; + + if (env_a.getHeight() <= tolerance && env_a.getWidth() <= tolerance) { + Point2D pt_a = env_a.getCenter(); + return pointWithinEnvelope_(pt_a, env_b, tolerance, + progress_tracker); + } + + if (env_b.getHeight() <= tolerance && env_b.getWidth() <= tolerance) { + Point2D pt_b = env_b.getCenter(); + return pointWithinEnvelope_(pt_b, env_a, tolerance, + progress_tracker); + } + + if (env_a.getHeight() <= tolerance || env_a.getWidth() <= tolerance) + return envelopeInfContainsEnvelope_(env_a, env_b, tolerance); // treat + // env_b + // as + // line + + // treat env_a as area + + if (env_b.getHeight() <= tolerance || env_b.getWidth() <= tolerance) {// treat + // env_b + // as + // line + + Envelope2D env_a_deflated = new Envelope2D(); + env_a_deflated.setCoords(env_a); + env_a_deflated.inflate(-tolerance, -tolerance); + + if (env_a_deflated.containsExclusive(env_b)) + return true; + + Envelope2D env_inter = new Envelope2D(); + env_inter.setCoords(env_a_deflated); + env_inter.intersect(env_b); + + if (env_inter.isEmpty() + || (env_inter.getHeight() <= tolerance && env_inter + .getWidth() <= tolerance)) + return false; + + return true; + } + + return envelopeInfContainsEnvelope_(env_a, env_b, tolerance); + } + + // Returns true if env_a crosses env_b. + private static boolean envelopeCrossesEnvelope_(Envelope2D env_a, + Envelope2D env_b, double tolerance, ProgressTracker progress_tracker) { + if (envelopeInfContainsEnvelope_(env_a, env_b, tolerance) + || envelopeInfContainsEnvelope_(env_b, env_a, tolerance)) + return false; + + if (env_a.getHeight() <= tolerance && env_a.getWidth() <= tolerance) + return false; // points cannot cross + + if (env_b.getHeight() <= tolerance && env_b.getWidth() <= tolerance) + return false; // points cannot cross + + if (env_b.getHeight() > tolerance && env_b.getWidth() > tolerance) { + if (env_a.getHeight() > tolerance && env_a.getWidth() > tolerance) + return false; // areas cannot cross + } + + Envelope2D _env_a; + Envelope2D _env_b; + + if (env_a.getHeight() > tolerance && env_a.getWidth() > tolerance) { + // swap b and a + _env_a = env_b; + _env_b = env_a; + } else { + _env_a = env_a; + _env_b = env_b; + } + + if (_env_b.getHeight() > tolerance && _env_b.getWidth() > tolerance) {// treat + // env_b + // as + // an + // area + // (env_a + // as + // a + // line); + + Envelope2D env_inter = new Envelope2D(), env_b_deflated = new Envelope2D(); + env_b_deflated.setCoords(_env_b); + env_b_deflated.inflate(-tolerance, -tolerance); + env_inter.setCoords(env_b_deflated); + env_inter.intersect(_env_a); + + if (env_inter.isEmpty()) + return false; + + if (env_inter.getHeight() <= tolerance + && env_inter.getWidth() <= tolerance) + return false; // not a line + + assert (!envelopeInfContainsEnvelope_(env_inter, _env_a, tolerance)); + return true; + } + + // treat both as lines + + Line line_a = new Line(), line_b = new Line(); + double[] scalars_a = new double[2]; + double[] scalars_b = new double[2]; + Point2D pt = new Point2D(); + _env_a.queryLowerLeft(pt); + line_a.setStartXY(pt); + _env_a.queryUpperRight(pt); + line_a.setEndXY(pt); + _env_b.queryLowerLeft(pt); + line_b.setStartXY(pt); + _env_b.queryUpperRight(pt); + line_b.setEndXY(pt); + + line_a.intersect(line_b, null, scalars_a, scalars_b, tolerance); + int count = line_a.intersect(line_b, null, null, null, tolerance); + + if (count != 1) + return false; + + return scalars_a[0] > 0.0 && scalars_a[1] < 1.0 && scalars_b[0] > 0.0 + && scalars_b[1] < 1.0; + } + + // Returns true if polygon_a is disjoint from multipath_b. + private static boolean polygonDisjointMultiPath_(Polygon polygon_a, + MultiPath multipath_b, double tolerance, + ProgressTracker progress_tracker) { + Point2D pt_a = new Point2D(), pt_b = new Point2D(); + Envelope2D env_a_inf = new Envelope2D(), env_b_inf = new Envelope2D(); + + AttributeStreamOfInt32 parts_a = new AttributeStreamOfInt32(0); + AttributeStreamOfInt32 parts_b = new AttributeStreamOfInt32(0); + + Envelope2DIntersectorImpl intersector = InternalUtils + .getEnvelope2DIntersectorForOGCParts( + (MultiPathImpl) polygon_a._getImpl(), + (MultiPathImpl) multipath_b._getImpl(), tolerance, + parts_a, parts_b); + + if (intersector != null) { + while (intersector.next()) { + int index_a = intersector.getHandleA(); + int index_b = intersector.getHandleB(); + int path_a = parts_a.get(index_a); + int path_b = parts_b.get(index_b); + + multipath_b.getXY(multipath_b.getPathStart(path_b), pt_b); + env_a_inf.setCoords(intersector.getRedEnvelope(index_a)); + env_a_inf.inflate(tolerance, tolerance); + + if (env_a_inf.contains(pt_b)) { + PolygonUtils.PiPResult result = PolygonUtils + .isPointInPolygon2D(polygon_a, pt_b, 0.0); + + if (result != PolygonUtils.PiPResult.PiPOutside) + return false; + } + + if (multipath_b.getType().value() == Geometry.GeometryType.Polygon) { + polygon_a.getXY(polygon_a.getPathStart(path_a), pt_a); + env_b_inf.setCoords(intersector.getBlueEnvelope(index_b)); + env_b_inf.inflate(tolerance, tolerance); + + if (env_b_inf.contains(pt_a)) { + PolygonUtils.PiPResult result = PolygonUtils + .isPointInPolygon2D((Polygon) multipath_b, + pt_a, 0.0); + + if (result != PolygonUtils.PiPResult.PiPOutside) + return false; + } + } + } + } + + boolean b_intersects = linearPathIntersectsLinearPath_(polygon_a, + multipath_b, tolerance); + + if (b_intersects) + return false; + + return true; + } + + // Returns true if env_a inflated contains env_b. + private static boolean envelopeInfContainsEnvelope_(Envelope2D env_a, + Envelope2D env_b, double tolerance) { + Envelope2D env_a_inflated = new Envelope2D(); + env_a_inflated.setCoords(env_a); + env_a_inflated.inflate(tolerance, tolerance); + return env_a_inflated.contains(env_b); + } + + // Returns true if a coordinate of envelope A is outside of envelope B. + private static boolean interiorEnvExteriorEnv_(Envelope2D env_a, + Envelope2D env_b, double tolerance) { + Envelope2D envBInflated = new Envelope2D(); + envBInflated.setCoords(env_b); + envBInflated.inflate(tolerance, tolerance); + Point2D pt = new Point2D(); + + env_a.queryLowerLeft(pt); + if (!envBInflated.contains(pt)) + return true; + + env_a.queryLowerRight(pt); + if (!envBInflated.contains(pt)) + return true; + + env_a.queryUpperLeft(pt); + if (!envBInflated.contains(pt)) + return true; + + env_a.queryUpperRight(pt); + if (!envBInflated.contains(pt)) + return true; + + assert (envBInflated.contains(env_a)); + return false; + } + + // Returns true if the points in each path of multipathA are the same as + // those in multipathB, within a tolerance, and in the same order. + private static boolean multiPathExactlyEqualsMultiPath_( + MultiPath multipathA, MultiPath multipathB, double tolerance, + ProgressTracker progress_tracker) { + if (multipathA.getPathCount() != multipathB.getPathCount() + || multipathA.getPointCount() != multipathB.getPointCount()) + return false; + + Point2D ptA = new Point2D(), ptB = new Point2D(); + boolean bAllPointsEqual = true; + double tolerance_sq = tolerance * tolerance; + + for (int ipath = 0; ipath < multipathA.getPathCount(); ipath++) { + if (multipathA.getPathEnd(ipath) != multipathB.getPathEnd(ipath)) { + bAllPointsEqual = false; + break; + } + + for (int i = multipathA.getPathStart(ipath); i < multipathB + .getPathEnd(ipath); i++) { + multipathA.getXY(i, ptA); + multipathB.getXY(i, ptB); + + if (Point2D.sqrDistance(ptA, ptB) > tolerance_sq) { + bAllPointsEqual = false; + break; + } + } + + if (!bAllPointsEqual) + break; + } + + if (!bAllPointsEqual) + return false; + + return true; + } + + // Returns true if the points of multipoint_a are the same as those in + // multipoint_b, within a tolerance, and in the same order. + private static boolean multiPointExactlyEqualsMultiPoint_( + MultiPoint multipoint_a, MultiPoint multipoint_b, double tolerance, + ProgressTracker progress_tracker) { + if (multipoint_a.getPointCount() != multipoint_b.getPointCount()) + return false; + + Point2D ptA = new Point2D(), ptB = new Point2D(); + boolean bAllPointsEqual = true; + double tolerance_sq = tolerance * tolerance; + + for (int i = 0; i < multipoint_a.getPointCount(); i++) { + multipoint_a.getXY(i, ptA); + multipoint_b.getXY(i, ptB); + + if (Point2D.sqrDistance(ptA, ptB) > tolerance_sq) { + bAllPointsEqual = false; + break; + } + } + + if (!bAllPointsEqual) + return false; + + return true; + } + + // By default this will perform the within operation if bEquals is false. + // Otherwise it will do equals. + private static boolean multiPointCoverageMultiPoint_( + MultiPoint _multipointA, MultiPoint _multipointB, double tolerance, + boolean bPerformWithin, boolean bPerformEquals, + boolean bPerformOverlaps, ProgressTracker progress_tracker) { + boolean bPerformContains = false; + MultiPoint multipoint_a; + MultiPoint multipoint_b; + + if (_multipointA.getPointCount() > _multipointB.getPointCount()) { + if (bPerformWithin) { + bPerformWithin = false; + bPerformContains = true; + } + + multipoint_a = _multipointB; + multipoint_b = _multipointA; + } else { + multipoint_a = _multipointA; + multipoint_b = _multipointB; + } + + AttributeStreamOfInt8 contained = null; + + if (bPerformEquals || bPerformOverlaps || bPerformContains) { + contained = new AttributeStreamOfInt8(multipoint_b.getPointCount()); + + for (int i = 0; i < multipoint_b.getPointCount(); i++) + contained.write(i, (byte) 0); + } + + Envelope2D env_a = new Envelope2D(); + Envelope2D env_b = new Envelope2D(); + Envelope2D envInter = new Envelope2D(); + multipoint_a.queryEnvelope2D(env_a); + multipoint_b.queryEnvelope2D(env_b); + env_a.inflate(tolerance, tolerance); + env_b.inflate(tolerance, tolerance); + envInter.setCoords(env_a); + envInter.intersect(env_b); + + Point2D ptA = new Point2D(); + Point2D ptB = new Point2D(); + + boolean bWithin = true; // starts off true by default + + QuadTreeImpl quadTreeB = InternalUtils.buildQuadTree( + (MultiPointImpl) (multipoint_b._getImpl()), envInter); + QuadTreeImpl.QuadTreeIteratorImpl qtIterB = quadTreeB.getIterator(); + double tolerance_sq = tolerance * tolerance; + + for (int vertex_a = 0; vertex_a < multipoint_a.getPointCount(); vertex_a++) { + multipoint_a.getXY(vertex_a, ptA); + + if (!envInter.contains(ptA)) { + if (bPerformEquals || bPerformWithin) + return false; + else { + bWithin = false; + continue; + } + } + + boolean bPtACovered = false; + env_a.setCoords(ptA.x, ptA.y, ptA.x, ptA.y); + qtIterB.resetIterator(env_a, tolerance); + for (int elementHandleB = qtIterB.next(); elementHandleB != -1; elementHandleB = qtIterB + .next()) { + int vertex_b = quadTreeB.getElement(elementHandleB); + multipoint_b.getXY(vertex_b, ptB); + + if (Point2D.sqrDistance(ptA, ptB) <= tolerance_sq) { + if (bPerformEquals || bPerformOverlaps || bPerformContains) + contained.write(vertex_b, (byte) 1); + + bPtACovered = true; + + if (bPerformWithin) + break; + } + } + + if (!bPtACovered) { + bWithin = false; + + if (bPerformEquals || bPerformWithin) + return false; + } + } + + if (bPerformOverlaps && bWithin) + return false; + + if (bPerformWithin) + return true; + + for (int i = 0; i < multipoint_b.getPointCount(); i++) { + if (contained.read(i) == 1) { + if (bPerformOverlaps) + return true; + } else { + if (bPerformEquals || bPerformContains) + return false; + } + } + + if (bPerformOverlaps) + return false; + + return true; + } + + // Returns true if multipoint_a intersects multipoint_b. + private static boolean multiPointIntersectsMultiPoint_( + MultiPoint _multipointA, MultiPoint _multipointB, double tolerance, + ProgressTracker progress_tracker) { + MultiPoint multipoint_a; + MultiPoint multipoint_b; + + if (_multipointA.getPointCount() > _multipointB.getPointCount()) { + multipoint_a = _multipointB; + multipoint_b = _multipointA; + } else { + multipoint_a = _multipointA; + multipoint_b = _multipointB; + } + + Envelope2D env_a = new Envelope2D(); + Envelope2D env_b = new Envelope2D(); + Envelope2D envInter = new Envelope2D(); + multipoint_a.queryEnvelope2D(env_a); + multipoint_b.queryEnvelope2D(env_b); + env_a.inflate(tolerance, tolerance); + env_b.inflate(tolerance, tolerance); + envInter.setCoords(env_a); + envInter.intersect(env_b); + + Point2D ptA = new Point2D(); + Point2D ptB = new Point2D(); + double tolerance_sq = tolerance * tolerance; + + QuadTreeImpl quadTreeB = InternalUtils.buildQuadTree( + (MultiPointImpl) (multipoint_b._getImpl()), envInter); + QuadTreeImpl.QuadTreeIteratorImpl qtIterB = quadTreeB.getIterator(); + + for (int vertex_a = 0; vertex_a < multipoint_a.getPointCount(); vertex_a++) { + multipoint_a.getXY(vertex_a, ptA); + + if (!envInter.contains(ptA)) + continue; + + env_a.setCoords(ptA.x, ptA.y, ptA.x, ptA.y); + qtIterB.resetIterator(env_a, tolerance); + + for (int elementHandleB = qtIterB.next(); elementHandleB != -1; elementHandleB = qtIterB + .next()) { + int vertex_b = quadTreeB.getElement(elementHandleB); + multipoint_b.getXY(vertex_b, ptB); + + if (Point2D.sqrDistance(ptA, ptB) <= tolerance_sq) + return true; + } + } + + return false; + } + + // Returns true if multipathA equals multipathB. + private static boolean linearPathEqualsLinearPath_(MultiPath multipathA, + MultiPath multipathB, double tolerance) { + return linearPathWithinLinearPath_(multipathA, multipathB, tolerance) + && linearPathWithinLinearPath_(multipathB, multipathA, + tolerance); + } + + // Returns true if the segments of multipathA are within the segments of + // multipathB. + private static boolean linearPathWithinLinearPath_(MultiPath multipathA, + MultiPath multipathB, double tolerance) { + boolean bWithin = true; + double[] scalarsA = new double[2]; + double[] scalarsB = new double[2]; + + int ievent = 0; + AttributeStreamOfInt32 eventIndices = new AttributeStreamOfInt32(0); + RelationalOperations relOps = new RelationalOperations(); + OverlapComparer overlapComparer = new OverlapComparer(relOps); + OverlapEvent overlapEvent; + + Envelope2D env_a = new Envelope2D(); + Envelope2D env_b = new Envelope2D(); + Envelope2D envInter = new Envelope2D(); + multipathA.queryEnvelope2D(env_a); + multipathB.queryEnvelope2D(env_b); + env_a.inflate(tolerance, tolerance); + env_b.inflate(tolerance, tolerance); + envInter.setCoords(env_a); + envInter.intersect(env_b); + + SegmentIteratorImpl segIterA = ((MultiPathImpl) multipathA._getImpl()) + .querySegmentIterator(); + SegmentIteratorImpl segIterB = ((MultiPathImpl) multipathB._getImpl()) + .querySegmentIterator(); + + QuadTreeImpl quadTreeB = InternalUtils.buildQuadTree( + ((MultiPathImpl) multipathB._getImpl()), envInter); + QuadTreeImpl.QuadTreeIteratorImpl qtIterB = quadTreeB.getIterator(); + + while (segIterA.nextPath()) { + while (segIterA.hasNextSegment()) { + boolean bStringOfSegmentAsCovered = false; + + Segment segmentA = segIterA.nextSegment(); + segmentA.queryEnvelope2D(env_a); + + if (!env_a.isIntersecting(envInter)) { + return false; // bWithin = false + } + + double lengthA = segmentA.calculateLength2D(); + + qtIterB.resetIterator(segmentA, tolerance); + + for (int elementHandleB = qtIterB.next(); elementHandleB != -1; elementHandleB = qtIterB + .next()) { + int vertex_b = quadTreeB.getElement(elementHandleB); + segIterB.resetToVertex(vertex_b); + Segment segmentB = segIterB.nextSegment(); + + int result = segmentA.intersect(segmentB, null, scalarsA, + scalarsB, tolerance); + + if (result == 2) { + double scalar_a_0 = scalarsA[0]; + double scalar_a_1 = scalarsA[1]; + double scalar_b_0 = scalarsB[0]; + double scalar_b_1 = scalarsB[1]; + + // Performance enhancement for nice cases where + // localization occurs. Increment segIterA as far as we + // can while the current segmentA is covered. + if (scalar_a_0 * lengthA <= tolerance + && (1.0 - scalar_a_1) * lengthA <= tolerance) { + bStringOfSegmentAsCovered = true; + + ievent = 0; + eventIndices.resize(0); + relOps.m_overlap_events.clear(); + + int ivertex_a = segIterA.getStartPointIndex(); + boolean bSegmentACovered = true; + + while (bSegmentACovered) {// keep going while the + // current segmentA is + // covered. + if (segIterA.hasNextSegment()) { + segmentA = segIterA.nextSegment(); + lengthA = segmentA.calculateLength2D(); + + result = segmentA.intersect(segmentB, null, + scalarsA, scalarsB, tolerance); + + if (result == 2) { + scalar_a_0 = scalarsA[0]; + scalar_a_1 = scalarsA[1]; + + if (scalar_a_0 * lengthA <= tolerance + && (1.0 - scalar_a_1) * lengthA <= tolerance) { + ivertex_a = segIterA + .getStartPointIndex(); + continue; + } + } + + if (segIterB.hasNextSegment()) { + segmentB = segIterB.nextSegment(); + result = segmentA.intersect(segmentB, + null, scalarsA, scalarsB, + tolerance); + + if (result == 2) { + scalar_a_0 = scalarsA[0]; + scalar_a_1 = scalarsA[1]; + + if (scalar_a_0 * lengthA <= tolerance + && (1.0 - scalar_a_1) + * lengthA <= tolerance) { + ivertex_a = segIterA + .getStartPointIndex(); + continue; + } + } + } + } + + bSegmentACovered = false; + } + + if (ivertex_a != segIterA.getStartPointIndex()) { + segIterA.resetToVertex(ivertex_a); + segIterA.nextSegment(); + } + + break; + } else { + int ivertex_a = segIterA.getStartPointIndex(); + int ipath_a = segIterA.getPathIndex(); + int ivertex_b = segIterB.getStartPointIndex(); + int ipath_b = segIterB.getPathIndex(); + + overlapEvent = OverlapEvent.construct(ivertex_a, + ipath_a, scalar_a_0, scalar_a_1, ivertex_b, + ipath_b, scalar_b_0, scalar_b_1); + relOps.m_overlap_events.add(overlapEvent); + eventIndices.add(eventIndices.size()); + } + } + } + + if (bStringOfSegmentAsCovered) + continue; // no need to check that segmentA is covered + + if (ievent == relOps.m_overlap_events.size()) { + return false; // bWithin = false + } + + if (eventIndices.size() - ievent > 1) + eventIndices.Sort(ievent, eventIndices.size(), + overlapComparer); + + double lastScalar = 0.0; + + for (int i = ievent; i < relOps.m_overlap_events.size(); i++) { + overlapEvent = relOps.m_overlap_events.get(eventIndices + .get(i)); + + if (overlapEvent.m_scalar_a_0 < lastScalar + && overlapEvent.m_scalar_a_1 < lastScalar) + continue; + + if (lengthA * (overlapEvent.m_scalar_a_0 - lastScalar) > tolerance) { + return false; // bWithin = false + } else { + lastScalar = overlapEvent.m_scalar_a_1; + + if (lengthA * (1.0 - lastScalar) <= tolerance + || lastScalar == 1.0) + break; + } + } + + if (lengthA * (1.0 - lastScalar) > tolerance) { + return false; // bWithin = false + } + + ievent = 0; + eventIndices.resize(0); + relOps.m_overlap_events.clear(); + } + } + + return bWithin; + } + + // Returns true if the segments of multipathA overlap the segments of + // multipathB. + private static boolean linearPathOverlapsLinearPath_(MultiPath multipathA, + MultiPath multipathB, double tolerance) { + int dim = linearPathIntersectsLinearPathMaxDim_(multipathA, multipathB, + tolerance, null); + + if (dim < 1) + return false; + + Envelope2D env_a = new Envelope2D(), env_b = new Envelope2D(); + multipathA.queryEnvelope2D(env_a); + multipathB.queryEnvelope2D(env_b); + + boolean bIntAExtB = interiorEnvExteriorEnv_(env_a, env_b, tolerance); + boolean bIntBExtA = interiorEnvExteriorEnv_(env_b, env_a, tolerance); + + if (bIntAExtB && bIntBExtA) + return true; + + if (bIntAExtB && !bIntBExtA) + return !linearPathWithinLinearPath_(multipathB, multipathA, + tolerance); + + if (bIntBExtA && !bIntAExtB) + return !linearPathWithinLinearPath_(multipathA, multipathB, + tolerance); + + return !linearPathWithinLinearPath_(multipathA, multipathB, tolerance) + && !linearPathWithinLinearPath_(multipathB, multipathA, + tolerance); + } + + // Returns true if the segments of multipathA touches the segments of + // multipathB. + private static boolean linearPathTouchesLinearPath_(MultiPath _multipathA, + MultiPath _multipathB, double tolerance) { + MultiPath multipathA; + MultiPath multipathB; + + if (_multipathA.getSegmentCount() > _multipathB.getSegmentCount()) { + multipathA = _multipathB; + multipathB = _multipathA; + } else { + multipathA = _multipathA; + multipathB = _multipathB; + } + + SegmentIteratorImpl segIterA = ((MultiPathImpl) multipathA._getImpl()) + .querySegmentIterator(); + SegmentIteratorImpl segIterB = ((MultiPathImpl) multipathB._getImpl()) + .querySegmentIterator(); + double[] scalarsA = new double[2]; + double[] scalarsB = new double[2]; + + boolean bBoundaryIntersectionFound = false; + + Envelope2D env_a = new Envelope2D(); + Envelope2D env_b = new Envelope2D(); + Envelope2D envInter = new Envelope2D(); + multipathA.queryEnvelope2D(env_a); + multipathB.queryEnvelope2D(env_b); + env_a.inflate(tolerance, tolerance); + env_b.inflate(tolerance, tolerance); + envInter.setCoords(env_a); + envInter.intersect(env_b); + + QuadTreeImpl quadTreeB = InternalUtils.buildQuadTree( + (MultiPathImpl) multipathB._getImpl(), envInter); + QuadTreeImpl.QuadTreeIteratorImpl qtIterB = quadTreeB.getIterator(); + + while (segIterA.nextPath()) { + boolean bHasEndPointsA = !isClosedPath_(multipathA, + segIterA.getPathIndex()); + + while (segIterA.hasNextSegment()) { + Segment segmentA = segIterA.nextSegment(); + segmentA.queryEnvelope2D(env_a); + + if (!env_a.isIntersecting(envInter)) + continue; + + double lengthA = segmentA.calculateLength2D(); + + qtIterB.resetIterator(segmentA, tolerance); + + for (int elementHandleB = qtIterB.next(); elementHandleB != -1; elementHandleB = qtIterB + .next()) { + int vertex_b = quadTreeB.getElement(elementHandleB); + segIterB.resetToVertex(vertex_b); + boolean bHasEndPointsB = !isClosedPath_(multipathB, + segIterB.getPathIndex()); + + Segment segmentB = segIterB.nextSegment(); + double lengthB = segmentB.calculateLength2D(); + + int result = segmentA.intersect(segmentB, null, scalarsA, + scalarsB, tolerance); + + if (result > 0) { + if (result == 2 + && lengthA * (scalarsA[1] - scalarsA[0]) > tolerance) + return false; + + double scalar_a_0 = scalarsA[0]; + double scalar_b_0 = scalarsB[0]; + + boolean bTouchesStartA = false, bTouchesEndA = false, bTouchesStartB = false, bTouchesEndB = false; + + if (lengthA * scalar_a_0 <= tolerance + && segIterA.isFirstSegmentInPath()) + bTouchesStartA = true; + + if (lengthA * (1.0 - scalar_a_0) <= tolerance + && segIterA.isLastSegmentInPath()) + bTouchesEndA = true; + + if (lengthB * scalar_b_0 <= tolerance + && segIterB.isFirstSegmentInPath()) + bTouchesStartB = true; + + if (lengthB * (1.0 - scalar_b_0) <= tolerance + && segIterB.isLastSegmentInPath()) + bTouchesEndB = true; + + if (((!bTouchesStartA && !bTouchesEndA) || !bHasEndPointsA) + && ((!bTouchesStartB && !bTouchesEndB) || !bHasEndPointsB)) + return false; // return false if Interior-Interior + // intersection is found + + bBoundaryIntersectionFound = true; + } + } + } + } + + return bBoundaryIntersectionFound; // If we haven't already returned + // false, then no Interior-Interior + // intersection has been found. + // Thus, they touch. + } + + // Returns true the dimension of intersection of _multipathA and + // _multipathB. + static int linearPathIntersectsLinearPathMaxDim_(MultiPath _multipathA, + MultiPath _multipathB, double tolerance, + AttributeStreamOfDbl intersections) { + MultiPath multipathA; + MultiPath multipathB; + + if (_multipathA.getSegmentCount() > _multipathB.getSegmentCount()) { + multipathA = _multipathB; + multipathB = _multipathA; + } else { + multipathA = _multipathA; + multipathB = _multipathB; + } + + SegmentIteratorImpl segIterA = ((MultiPathImpl) multipathA._getImpl()) + .querySegmentIterator(); + SegmentIteratorImpl segIterB = ((MultiPathImpl) multipathB._getImpl()) + .querySegmentIterator(); + double[] scalarsA = new double[2]; + double[] scalarsB = new double[2]; + + int dim = -1; + + int ievent = 0; + double overlapLength; + AttributeStreamOfInt32 eventIndices = new AttributeStreamOfInt32(0); + RelationalOperations relOps = new RelationalOperations(); + OverlapComparer overlapComparer = new OverlapComparer(relOps); + OverlapEvent overlapEvent; + + Envelope2D env_a = new Envelope2D(); + Envelope2D env_b = new Envelope2D(); + Envelope2D envInter = new Envelope2D(); + multipathA.queryEnvelope2D(env_a); + multipathB.queryEnvelope2D(env_b); + env_a.inflate(tolerance, tolerance); + env_b.inflate(tolerance, tolerance); + envInter.setCoords(env_a); + envInter.intersect(env_b); + + Point2D int_point = null; + + if (intersections != null) + int_point = new Point2D(); + + QuadTreeImpl quadTreeB = InternalUtils.buildQuadTree( + (MultiPathImpl) multipathB._getImpl(), envInter); + QuadTreeImpl.QuadTreeIteratorImpl qtIterB = quadTreeB.getIterator(); + + while (segIterA.nextPath()) { + overlapLength = 0.0; + + while (segIterA.hasNextSegment()) { + Segment segmentA = segIterA.nextSegment(); + segmentA.queryEnvelope2D(env_a); + + if (!env_a.isIntersecting(envInter)) + continue; + + double lengthA = segmentA.calculateLength2D(); + + qtIterB.resetIterator(segmentA, tolerance); + + for (int elementHandleB = qtIterB.next(); elementHandleB != -1; elementHandleB = qtIterB + .next()) { + int vertex_b = quadTreeB.getElement(elementHandleB); + segIterB.resetToVertex(vertex_b); + + Segment segmentB = segIterB.nextSegment(); + double lengthB = segmentB.calculateLength2D(); + + int result = segmentA.intersect(segmentB, null, scalarsA, + scalarsB, tolerance); + + if (result > 0) { + double scalar_a_0 = scalarsA[0]; + double scalar_b_0 = scalarsB[0]; + double scalar_a_1 = (result == 2 ? scalarsA[1] + : NumberUtils.TheNaN); + double scalar_b_1 = (result == 2 ? scalarsB[1] + : NumberUtils.TheNaN); + + if (result == 2) { + if (lengthA * (scalar_a_1 - scalar_a_0) > tolerance) { + dim = 1; + return dim; + } + + // Quick neighbor check + double length = lengthA * (scalar_a_1 - scalar_a_0); + + if (segIterB.hasNextSegment()) { + segmentB = segIterB.nextSegment(); + result = segmentA.intersect(segmentB, null, + scalarsA, null, tolerance); + + if (result == 2) { + double nextScalarA0 = scalarsA[0]; + double nextScalarA1 = scalarsA[1]; + + double lengthNext = lengthA + * (nextScalarA1 - nextScalarA0); + + if (length + lengthNext > tolerance) { + dim = 1; + return dim; + } + } + + segIterB.resetToVertex(vertex_b); + segIterB.nextSegment(); + } + + if (!segIterB.isFirstSegmentInPath()) { + segIterB.previousSegment(); + segmentB = segIterB.previousSegment(); + result = segmentA.intersect(segmentB, null, + scalarsA, null, tolerance); + + if (result == 2) { + double nextScalarA0 = scalarsA[0]; + double nextScalarA1 = scalarsA[1]; + + double lengthPrevious = lengthA + * (nextScalarA1 - nextScalarA0); + + if (length + lengthPrevious > tolerance) { + dim = 1; + return dim; + } + } + + segIterB.resetToVertex(vertex_b); + segIterB.nextSegment(); + } + + if (segIterA.hasNextSegment()) { + int vertex_a = segIterA.getStartPointIndex(); + segmentA = segIterA.nextSegment(); + result = segmentA.intersect(segmentB, null, + scalarsA, null, tolerance); + + if (result == 2) { + double nextScalarA0 = scalarsA[0]; + double nextScalarA1 = scalarsA[1]; + + double lengthNext = lengthA + * (nextScalarA1 - nextScalarA0); + + if (length + lengthNext > tolerance) { + dim = 1; + return dim; + } + } + + segIterA.resetToVertex(vertex_a); + segIterA.nextSegment(); + } + + if (!segIterA.isFirstSegmentInPath()) { + int vertex_a = segIterA.getStartPointIndex(); + segIterA.previousSegment(); + segmentA = segIterA.previousSegment(); + result = segmentA.intersect(segmentB, null, + scalarsA, null, tolerance); + + if (result == 2) { + double nextScalarA0 = scalarsA[0]; + double nextScalarA1 = scalarsA[1]; + + double lengthPrevious = lengthB + * (nextScalarA1 - nextScalarA0); + + if (length + lengthPrevious > tolerance) { + dim = 1; + return dim; + } + } + + segIterA.resetToVertex(vertex_a); + segIterA.nextSegment(); + } + + int ivertex_a = segIterA.getStartPointIndex(); + int ipath_a = segIterA.getPathIndex(); + int ivertex_b = segIterB.getStartPointIndex(); + int ipath_b = segIterB.getPathIndex(); + + overlapEvent = OverlapEvent.construct(ivertex_a, + ipath_a, scalar_a_0, scalar_a_1, ivertex_b, + ipath_b, scalar_b_0, scalar_b_1); + relOps.m_overlap_events.add(overlapEvent); + eventIndices.add(eventIndices.size()); + } + + dim = 0; + + if (intersections != null) { + segmentA.getCoord2D(scalar_a_0, int_point); + intersections.add(int_point.x); + intersections.add(int_point.y); + } + } + } + + if (ievent < relOps.m_overlap_events.size()) { + eventIndices.Sort(ievent, eventIndices.size(), + overlapComparer); + + double lastScalar = 0.0; + int lastPath = relOps.m_overlap_events.get(eventIndices + .get(ievent)).m_ipath_a; + + for (int i = ievent; i < relOps.m_overlap_events.size(); i++) { + overlapEvent = relOps.m_overlap_events.get(eventIndices + .get(i)); + + if (overlapEvent.m_scalar_a_0 < lastScalar + && overlapEvent.m_scalar_a_1 < lastScalar) + continue; + + if (lengthA * (overlapEvent.m_scalar_a_0 - lastScalar) > tolerance) { + overlapLength = lengthA + * (overlapEvent.m_scalar_a_1 - overlapEvent.m_scalar_a_0); // reset + lastScalar = overlapEvent.m_scalar_a_1; + lastPath = overlapEvent.m_ipath_a; + } else { + if (overlapEvent.m_ipath_a != lastPath) { + overlapLength = lengthA + * (overlapEvent.m_scalar_a_1 - overlapEvent.m_scalar_a_0); // reset + lastPath = overlapEvent.m_ipath_a; + } else + overlapLength += lengthA + * (overlapEvent.m_scalar_a_1 - overlapEvent.m_scalar_a_0); // accumulate + + if (overlapLength > tolerance) { + dim = 1; + return dim; + } + + lastScalar = overlapEvent.m_scalar_a_1; + + if (lastScalar == 1.0) + break; + } + } + + if (lengthA * (1.0 - lastScalar) > tolerance) + overlapLength = 0.0; // reset + + ievent = 0; + eventIndices.resize(0); + relOps.m_overlap_events.clear(); + } + } + } + + return dim; + } + + // Returns true if the line segments of _multipathA intersect the line + // segments of _multipathB. + private static boolean linearPathIntersectsLinearPath_( + MultiPath multipathA, MultiPath multipathB, double tolerance) { + MultiPathImpl multi_path_impl_a = (MultiPathImpl) multipathA._getImpl(); + MultiPathImpl multi_path_impl_b = (MultiPathImpl) multipathB._getImpl(); + + SegmentIteratorImpl segIterA = multi_path_impl_a.querySegmentIterator(); + SegmentIteratorImpl segIterB = multi_path_impl_b.querySegmentIterator(); + AttributeStreamOfInt32 verticesA = new AttributeStreamOfInt32(0); + AttributeStreamOfInt32 verticesB = new AttributeStreamOfInt32(0); + + Envelope2DIntersectorImpl intersector = InternalUtils + .getEnvelope2DIntersector(multi_path_impl_a, multi_path_impl_b, + tolerance, verticesA, verticesB); + + if (intersector != null) { + while (intersector.next()) { + int index_a = intersector.getHandleA(); + int index_b = intersector.getHandleB(); + int vertex_a = verticesA.get(index_a); + int vertex_b = verticesB.get(index_b); + + segIterA.resetToVertex(vertex_a); + segIterB.resetToVertex(vertex_b); + Segment segmentA = segIterA.nextSegment(); + Segment segmentB = segIterB.nextSegment(); + + int result = segmentB.intersect(segmentA, null, null, null, + tolerance); + + if (result > 0) + return true; + } + } + + return false; + } + + // Returns true if the relation intersects, crosses, or contains holds + // between multipathA and multipoint_b. multipathA is put in the + // Quad_tree_impl. + private static boolean linearPathIntersectsMultiPoint_( + MultiPath multipathA, MultiPoint multipoint_b, double tolerance, + boolean b_intersects_all) { + SegmentIteratorImpl segIterA = ((MultiPathImpl) multipathA._getImpl()) + .querySegmentIterator(); + + boolean bContained = true; + boolean bInteriorHitFound = false; + + Envelope2D env_a = new Envelope2D(); + Envelope2D env_b = new Envelope2D(); + Envelope2D envInter = new Envelope2D(); + multipathA.queryEnvelope2D(env_a); + multipoint_b.queryEnvelope2D(env_b); + env_a.inflate(tolerance, tolerance); + + if (!env_a.contains(env_b)) + bContained = false; + + env_b.inflate(tolerance, tolerance); + envInter.setCoords(env_a); + envInter.intersect(env_b); + + QuadTreeImpl quadTreeA = InternalUtils.buildQuadTree( + (MultiPathImpl) multipathA._getImpl(), envInter); + QuadTreeImpl.QuadTreeIteratorImpl qtIterA = quadTreeA.getIterator(); + Point2D ptB = new Point2D(), closest = new Point2D(); + boolean b_intersects = false; + double toleranceSq = tolerance * tolerance; + + for (int i = 0; i < multipoint_b.getPointCount(); i++) { + multipoint_b.getXY(i, ptB); + + if (!envInter.contains(ptB)) + continue; + + boolean bPtBContained = false; + env_b.setCoords(ptB.x, ptB.y, ptB.x, ptB.y); + qtIterA.resetIterator(env_b, tolerance); + + boolean b_covered = false; + + for (int elementHandleA = qtIterA.next(); elementHandleA != -1; elementHandleA = qtIterA + .next()) { + int vertex_a = quadTreeA.getElement(elementHandleA); + segIterA.resetToVertex(vertex_a); + Segment segmentA = segIterA.nextSegment(); + + double t = segmentA.getClosestCoordinate(ptB, false); + segmentA.getCoord2D(t, closest); + + if (Point2D.sqrDistance(closest, ptB) <= toleranceSq) { + b_covered = true; + break; + } + } + + if (b_intersects_all) { + if (!b_covered) + return false; + } else { + if (b_covered) + return true; + } + } + + if (b_intersects_all) + return true; + + return false; + } + + // Returns true if a segment of multipathA intersects point_b. + private static boolean linearPathIntersectsPoint_(MultiPath multipathA, + Point2D ptB, double tolerance) { + SegmentIterator segIterA = multipathA.querySegmentIterator(); + Point2D closest = new Point2D(); + double toleranceSq = tolerance * tolerance; + + Envelope2D env_a = new Envelope2D(); + boolean b_intersects = false; + + while (segIterA.nextPath()) { + while (segIterA.hasNextSegment()) { + Segment segmentA = segIterA.nextSegment(); + boolean bHasEndPointsA = !isClosedPath_(multipathA, + segIterA.getPathIndex()); + + segmentA.queryEnvelope2D(env_a); + env_a.inflate(tolerance, tolerance); + + if (!env_a.contains(ptB)) + continue; + + double t = segmentA.getClosestCoordinate(ptB, false); + segmentA.getCoord2D(t, closest); + + if (Point2D.sqrDistance(ptB, closest) <= toleranceSq) { + b_intersects = true; + break; + } + } + } + + return b_intersects; + } + + private static boolean linearPathContainsPoint_(MultiPath multipathA, + Point2D pt_b, double tolerance) { + return linearPathIntersectsPoint_(multipathA, pt_b, tolerance) + && !linearPathTouchesPointImpl_(multipathA, pt_b, tolerance); + } + + private static boolean linearPathTouchesPointImpl_(MultiPath multipathA, + Point2D ptB, double tolerance) { + MultiPoint boundary = (MultiPoint) (multipathA.getBoundary()); + return !multiPointDisjointPointImpl_(boundary, ptB, tolerance, null); + } + + // Returns true if the segments of multipathA intersects env_b + private static boolean linearPathIntersectsEnvelope_(MultiPath multipath_a, + Envelope2D env_b, double tolerance, ProgressTracker progress_tracker) { + if (!multipath_a.hasNonLinearSegments()) { + Envelope2D env_b_inflated = new Envelope2D(); + env_b_inflated.setCoords(env_b); + env_b_inflated.inflate(tolerance, tolerance); + MultiPathImpl mimpl_a = (MultiPathImpl) multipath_a._getImpl(); + AttributeStreamOfDbl xy = (AttributeStreamOfDbl) (mimpl_a + .getAttributeStreamRef(VertexDescription.Semantics.POSITION)); + Point2D pt = new Point2D(); + Point2D pt_prev = new Point2D(); + Point2D pt_1 = new Point2D(); + Point2D pt_2 = new Point2D(); + for (int ipath = 0, npath = mimpl_a.getPathCount(); ipath < npath; ipath++) { + boolean b_first = true; + for (int i = mimpl_a.getPathStart(ipath), n = mimpl_a + .getPathEnd(ipath); i < n; i++) { + if (b_first) { + xy.read(2 * i, pt_prev); + b_first = false; + continue; + } + + xy.read(2 * i, pt); + pt_1.setCoords(pt_prev); + pt_2.setCoords(pt); + if (env_b_inflated.clipLine(pt_1, pt_2) != 0) + return true; + + pt_prev.setCoords(pt); + } + } + } else { + Line line_1 = new Line(env_b.xmin, env_b.ymin, env_b.xmin, + env_b.ymax); + Line line_2 = new Line(env_b.xmin, env_b.ymax, env_b.xmax, + env_b.ymax); + Line line3 = new Line(env_b.xmax, env_b.ymax, env_b.xmax, + env_b.ymin); + Line line4 = new Line(env_b.xmax, env_b.ymin, env_b.xmin, + env_b.ymin); + SegmentIterator iter = multipath_a.querySegmentIterator(); + while (iter.nextPath()) { + while (iter.hasNextSegment()) { + Segment polySeg = iter.nextSegment(); + if (polySeg.isIntersecting(line_1, tolerance)) + return true; + + if (polySeg.isIntersecting(line_2, tolerance)) + return true; + + if (polySeg.isIntersecting(line3, tolerance)) + return true; + + if (polySeg.isIntersecting(line4, tolerance)) + return true; + } + } + } + + return false; + } + + // Returns contains, disjoint, or within if the relationship can be + // determined from the rasterized tests. + // When bExtraTestForIntersects is true performs extra tests and can return + // "intersects". + static int tryRasterizedContainsOrDisjoint_(Geometry geom_a, + Geometry geom_b, double tolerance, boolean bExtraTestForIntersects) { + int gtA = geom_a.getType().value(); + int gtB = geom_b.getType().value(); + do { + if (Geometry.isMultiVertex(gtA)) { + MultiVertexGeometryImpl impl = (MultiVertexGeometryImpl) geom_a + ._getImpl(); + GeometryAccelerators accel = impl._getAccelerators(); + if (accel != null) { + RasterizedGeometry2D rgeom = accel.getRasterizedGeometry(); + if (rgeom != null) { + if (gtB == Geometry.GeometryType.Point) { + Point2D ptB = ((Point) geom_b).getXY(); + RasterizedGeometry2D.HitType hit = rgeom + .queryPointInGeometry(ptB.x, ptB.y); + if (hit == RasterizedGeometry2D.HitType.Inside) { + return Relation.contains; + } else if (hit == RasterizedGeometry2D.HitType.Outside) { + return Relation.disjoint; + } + break; + } + Envelope2D env_b = new Envelope2D(); + geom_b.queryEnvelope2D(env_b); + RasterizedGeometry2D.HitType hit = rgeom + .queryEnvelopeInGeometry(env_b); + if (hit == RasterizedGeometry2D.HitType.Inside) { + return Relation.contains; + } else if (hit == RasterizedGeometry2D.HitType.Outside) { + return Relation.disjoint; + } else if (bExtraTestForIntersects + && Geometry.isMultiVertex(gtB)) { + if (checkVerticesForIntersection_( + (MultiVertexGeometryImpl) geom_b._getImpl(), + rgeom)) { + return Relation.intersects; + } + } + + break; + } + } + } + } while (false); + + do { + if (Geometry.isMultiVertex(gtB)) { + MultiVertexGeometryImpl impl = (MultiVertexGeometryImpl) geom_b + ._getImpl(); + GeometryAccelerators accel = impl._getAccelerators(); + if (accel != null) { + RasterizedGeometry2D rgeom = accel.getRasterizedGeometry(); + if (rgeom != null) { + if (gtA == Geometry.GeometryType.Point) { + Point2D ptA = ((Point) geom_a).getXY(); + RasterizedGeometry2D.HitType hit = rgeom + .queryPointInGeometry(ptA.x, ptA.y); + if (hit == RasterizedGeometry2D.HitType.Inside) { + return Relation.within; + } else if (hit == RasterizedGeometry2D.HitType.Outside) { + return Relation.disjoint; + } + break; + } + + Envelope2D env_a = new Envelope2D(); + geom_a.queryEnvelope2D(env_a); + RasterizedGeometry2D.HitType hit = rgeom + .queryEnvelopeInGeometry(env_a); + if (hit == RasterizedGeometry2D.HitType.Inside) { + return Relation.within; + } else if (hit == RasterizedGeometry2D.HitType.Outside) { + return Relation.disjoint; + } else if (bExtraTestForIntersects + && Geometry.isMultiVertex(gtA)) { + if (checkVerticesForIntersection_( + (MultiVertexGeometryImpl) geom_a._getImpl(), + rgeom)) { + return Relation.intersects; + } + } + + break; + } + } + } + } while (false); + + return Relation.unknown; + } + + // Returns true if intersects and false if nothing can be determined. + private static boolean checkVerticesForIntersection_( + MultiVertexGeometryImpl geom, RasterizedGeometry2D rgeom) { + // Do a quick raster test for each point. If any point is inside, then + // there is an intersection. + int pointCount = geom.getPointCount(); + Point2D pt = new Point2D(); + for (int ipoint = 0; ipoint < pointCount; ipoint++) { + geom.getXY(ipoint, pt); + RasterizedGeometry2D.HitType hit = rgeom.queryPointInGeometry(pt.x, + pt.y); + if (hit == RasterizedGeometry2D.HitType.Inside) { + return true; + } + } + + return false; + } + + // Returns true if the MultiPath is Closed, or if the end points are within + // a tolerance. + private static boolean isClosedPath_(MultiPath multipathA, int pathA) { + if (multipathA.isClosedPath(pathA)) + return true; + + return false; + // Point2D startA = multipathA.get_xy(multipathA.getPathStart(pathA)); + // Point2D endA = multipathA.get_xy(multipathA.getPathEnd(pathA) - 1); + // return startA.equals(endA); + } + + private static boolean polygonTouchesPolygonImpl_(Polygon polygon_a, + Polygon polygon_b, double tolerance, ProgressTracker progressTracker) { + MultiPathImpl polygon_impl_a = (MultiPathImpl) polygon_a._getImpl(); + MultiPathImpl polygon_impl_b = (MultiPathImpl) polygon_b._getImpl(); + + // double geom_tolerance; + boolean b_geometries_simple = polygon_impl_a.getIsSimple(0.0) >= 1 + && polygon_impl_b.getIsSimple(0.0) >= 1; + + SegmentIteratorImpl segIterA = polygon_impl_a.querySegmentIterator(); + SegmentIteratorImpl segIterB = polygon_impl_b.querySegmentIterator(); + AttributeStreamOfInt32 verticesA = new AttributeStreamOfInt32(0); + AttributeStreamOfInt32 verticesB = new AttributeStreamOfInt32(0); + double[] scalarsA = new double[2]; + double[] scalarsB = new double[2]; + + Envelope2DIntersectorImpl intersector = InternalUtils + .getEnvelope2DIntersector(polygon_impl_a, polygon_impl_b, + tolerance, verticesA, verticesB); + + boolean b_boundaries_intersect = false; + + if (intersector != null) { + while (intersector.next()) { + int index_a = intersector.getHandleA(); + int index_b = intersector.getHandleB(); + int vertex_a = verticesA.get(index_a); + int vertex_b = verticesB.get(index_b); + + segIterA.resetToVertex(vertex_a); + segIterB.resetToVertex(vertex_b); + Segment segmentA = segIterA.nextSegment(); + Segment segmentB = segIterB.nextSegment(); + + int result = segmentB.intersect(segmentA, null, scalarsB, + scalarsA, tolerance); + + if (result == 2) { + double scalar_a_0 = scalarsA[0]; + double scalar_a_1 = scalarsA[1]; + double length_a = segmentA.calculateLength2D(); + + if (b_geometries_simple + && (scalar_a_1 - scalar_a_0) * length_a > tolerance) { + // If the line segments overlap along the same + // direction, then we have an Interior-Interior + // intersection + return false; + } + + b_boundaries_intersect = true; + } else if (result != 0) { + double scalar_a_0 = scalarsA[0]; + double scalar_b_0 = scalarsB[0]; + + if (scalar_a_0 > 0.0 && scalar_a_0 < 1.0 + && scalar_b_0 > 0.0 && scalar_b_0 < 1.0) + return false; + + b_boundaries_intersect = true; + } + } + } + + if (!b_boundaries_intersect) + return false; + + Envelope2D env_a = new Envelope2D(), env_b = new Envelope2D(), envInter = new Envelope2D(); + polygon_a.queryEnvelope2D(env_a); + polygon_b.queryEnvelope2D(env_b); + env_a.inflate(1000.0 * tolerance, 1000.0 * tolerance); + env_b.inflate(1000.0 * tolerance, 1000.0 * tolerance); + envInter.setCoords(env_a); + envInter.intersect(env_b); + + Polygon _polygonA; + Polygon _polygonB; + + if (polygon_a.getPointCount() > 10) { + _polygonA = (Polygon) (Clipper.clip(polygon_a, envInter, tolerance, + 0.0)); + if (_polygonA.isEmpty()) + return false; + } else { + _polygonA = polygon_a; + } + + if (polygon_b.getPointCount() > 10) { + _polygonB = (Polygon) (Clipper.clip(polygon_b, envInter, tolerance, + 0.0)); + if (_polygonB.isEmpty()) + return false; + } else { + _polygonB = polygon_b; + } + + // We just need to determine whether interior_interior is false + + String scl = "F********"; + boolean bRelation = RelationalOperationsMatrix.polygonRelatePolygon_( + _polygonA, _polygonB, tolerance, scl, progressTracker); + + return bRelation; + } + + private static boolean polygonOverlapsPolygonImpl_(Polygon polygon_a, + Polygon polygon_b, double tolerance, ProgressTracker progressTracker) { + MultiPathImpl polygon_impl_a = (MultiPathImpl) polygon_a._getImpl(); + MultiPathImpl polygon_impl_b = (MultiPathImpl) polygon_b._getImpl(); + + // double geom_tolerance; + boolean b_geometries_simple = polygon_impl_a.getIsSimple(0.0) >= 1 + && polygon_impl_b.getIsSimple(0.0) >= 1; + + Envelope2D env_a = new Envelope2D(), env_b = new Envelope2D(), envInter = new Envelope2D(); + polygon_a.queryEnvelope2D(env_a); + polygon_b.queryEnvelope2D(env_b); + + boolean bInteriorIntersectionKnown = false; + + boolean bIntAExtB = interiorEnvExteriorEnv_(env_a, env_b, tolerance); + boolean bExtAIntB = interiorEnvExteriorEnv_(env_b, env_a, tolerance); + + SegmentIteratorImpl segIterA = polygon_impl_a.querySegmentIterator(); + SegmentIteratorImpl segIterB = polygon_impl_b.querySegmentIterator(); + AttributeStreamOfInt32 verticesA = new AttributeStreamOfInt32(0); + AttributeStreamOfInt32 verticesB = new AttributeStreamOfInt32(0); + double[] scalarsA = new double[2]; + double[] scalarsB = new double[2]; + + Envelope2DIntersectorImpl intersector = InternalUtils + .getEnvelope2DIntersector(polygon_impl_a, polygon_impl_b, + tolerance, verticesA, verticesB); + + if (intersector != null) { + while (intersector.next()) { + int index_a = intersector.getHandleA(); + int index_b = intersector.getHandleB(); + int vertex_a = verticesA.get(index_a); + int vertex_b = verticesB.get(index_b); + + segIterA.resetToVertex(vertex_a); + segIterB.resetToVertex(vertex_b); + Segment segmentA = segIterA.nextSegment(); + Segment segmentB = segIterB.nextSegment(); + + int result = segmentB.intersect(segmentA, null, scalarsB, + scalarsA, tolerance); + + if (result == 2) { + double scalar_a_0 = scalarsA[0]; + double scalar_a_1 = scalarsA[1]; + double length_a = segmentA.calculateLength2D(); + + if (b_geometries_simple + && (scalar_a_1 - scalar_a_0) * length_a > tolerance) { + // When the line segments intersect along the same + // direction, then we have an interior-interior + // intersection + bInteriorIntersectionKnown = true; + + if (bIntAExtB && bExtAIntB) + return true; + } + } else if (result != 0) { + double scalar_a_0 = scalarsA[0]; + double scalar_b_0 = scalarsB[0]; + + if (scalar_a_0 > 0.0 && scalar_a_0 < 1.0 + && scalar_b_0 > 0.0 && scalar_b_0 < 1.0) + return true; + } + } + } + + Envelope2D envAInflated = new Envelope2D(), envBInflated = new Envelope2D(); + envAInflated.setCoords(env_a); + envAInflated.inflate(1000.0 * tolerance, 1000.0 * tolerance); + envBInflated.setCoords(env_b); + envBInflated.inflate(1000.0 * tolerance, 1000.0 * tolerance); + + envInter.setCoords(envAInflated); + envInter.intersect(envBInflated); + + Polygon _polygonA; + Polygon _polygonB; + StringBuilder scl = new StringBuilder(); + + if (!bInteriorIntersectionKnown) + scl.append("T*"); + else + scl.append("**"); + + if (bIntAExtB) { + if (polygon_b.getPointCount() > 10) { + _polygonB = (Polygon) (Clipper.clip(polygon_b, envInter, + tolerance, 0.0)); + if (_polygonB.isEmpty()) + return false; + } else { + _polygonB = polygon_b; + } + + scl.append("****"); + } else { + _polygonB = polygon_b; + scl.append("T***"); + } + + if (bExtAIntB) { + if (polygon_a.getPointCount() > 10) { + _polygonA = (Polygon) (Clipper.clip(polygon_a, envInter, + tolerance, 0.0)); + if (_polygonA.isEmpty()) + return false; + } else { + _polygonA = polygon_a; + } + + scl.append("***"); + } else { + _polygonA = polygon_a; + scl.append("T**"); + } + + boolean bRelation = RelationalOperationsMatrix.polygonRelatePolygon_( + _polygonA, _polygonB, tolerance, scl.toString(), + progressTracker); + return bRelation; + } + + private static boolean polygonContainsPolygonImpl_(Polygon polygon_a, + Polygon polygon_b, double tolerance, ProgressTracker progressTracker) { + MultiPathImpl polygon_impl_a = (MultiPathImpl) polygon_a._getImpl(); + MultiPathImpl polygon_impl_b = (MultiPathImpl) polygon_b._getImpl(); + + SegmentIteratorImpl segIterA = polygon_impl_a.querySegmentIterator(); + SegmentIteratorImpl segIterB = polygon_impl_b.querySegmentIterator(); + AttributeStreamOfInt32 verticesA = new AttributeStreamOfInt32(0); + AttributeStreamOfInt32 verticesB = new AttributeStreamOfInt32(0); + double[] scalarsA = new double[2]; + double[] scalarsB = new double[2]; + + Envelope2DIntersectorImpl intersector = InternalUtils + .getEnvelope2DIntersector(polygon_impl_a, polygon_impl_b, + tolerance, verticesA, verticesB); + + if (intersector != null) { + while (intersector.next()) { + int index_a = intersector.getHandleA(); + int index_b = intersector.getHandleB(); + int vertex_a = verticesA.get(index_a); + int vertex_b = verticesB.get(index_b); + + segIterA.resetToVertex(vertex_a); + segIterB.resetToVertex(vertex_b); + Segment segmentA = segIterA.nextSegment(); + Segment segmentB = segIterB.nextSegment(); + + int result = segmentB.intersect(segmentA, null, scalarsB, + scalarsA, tolerance); + + if (result == 1) { + double scalar_a_0 = scalarsA[0]; + double scalar_b_0 = scalarsB[0]; + + if (scalar_a_0 > 0.0 && scalar_a_0 < 1.0 + && scalar_b_0 > 0.0 && scalar_b_0 < 1.0) + return false; + } + } + } + + // We can clip polygon_a to the extent of polyline_b + + Envelope2D envBInflated = new Envelope2D(); + polygon_b.queryEnvelope2D(envBInflated); + envBInflated.inflate(1000.0 * tolerance, 1000.0 * tolerance); + + Polygon _polygonA; + + if (polygon_a.getPointCount() > 10) { + _polygonA = (Polygon) (Clipper.clip(polygon_a, envBInflated, + tolerance, 0.0)); + if (_polygonA.isEmpty()) + return false; + } else { + _polygonA = polygon_a; + } + + String scl = "T*****F**"; // If Exterior-Interior is false, then + // Exterior-Boundary is false + + boolean bRelation = RelationalOperationsMatrix.polygonRelatePolygon_( + _polygonA, polygon_b, tolerance, scl, progressTracker); + return bRelation; + } + + private static boolean polygonTouchesPolylineImpl_(Polygon polygon_a, + Polyline polyline_b, double tolerance, + ProgressTracker progressTracker) { + MultiPathImpl polygon_impl_a = (MultiPathImpl) polygon_a._getImpl(); + MultiPathImpl polyline_impl_b = (MultiPathImpl) polyline_b._getImpl(); + + SegmentIteratorImpl segIterA = polygon_impl_a.querySegmentIterator(); + SegmentIteratorImpl segIterB = polyline_impl_b.querySegmentIterator(); + AttributeStreamOfInt32 verticesA = new AttributeStreamOfInt32(0); + AttributeStreamOfInt32 verticesB = new AttributeStreamOfInt32(0); + double[] scalarsA = new double[2]; + double[] scalarsB = new double[2]; + + Envelope2DIntersectorImpl intersector = InternalUtils + .getEnvelope2DIntersector(polygon_impl_a, polyline_impl_b, + tolerance, verticesA, verticesB); + + boolean b_boundaries_intersect = false; + + if (intersector != null) { + while (intersector.next()) { + int index_a = intersector.getHandleA(); + int index_b = intersector.getHandleB(); + int vertex_a = verticesA.get(index_a); + int vertex_b = verticesB.get(index_b); + + segIterA.resetToVertex(vertex_a); + segIterB.resetToVertex(vertex_b); + Segment segmentA = segIterA.nextSegment(); + Segment segmentB = segIterB.nextSegment(); + + int result = segmentB.intersect(segmentA, null, scalarsB, + scalarsA, tolerance); + + if (result == 2) { + b_boundaries_intersect = true; + } else if (result != 0) { + double scalar_a_0 = scalarsA[0]; + double scalar_b_0 = scalarsB[0]; + + if (scalar_a_0 > 0.0 && scalar_a_0 < 1.0 + && scalar_b_0 > 0.0 && scalar_b_0 < 1.0) + return false; + + b_boundaries_intersect = true; + } + } + } + + if (!b_boundaries_intersect) + return false; + + Envelope2D env_a = new Envelope2D(), env_b = new Envelope2D(), envInter = new Envelope2D(); + polygon_a.queryEnvelope2D(env_a); + polyline_b.queryEnvelope2D(env_b); + env_a.inflate(1000.0 * tolerance, 1000.0 * tolerance); + env_b.inflate(1000.0 * tolerance, 1000.0 * tolerance); + envInter.setCoords(env_a); + envInter.intersect(env_b); + + Polygon _polygonA; + Polyline _polylineB; + + if (polygon_a.getPointCount() > 10) { + _polygonA = (Polygon) (Clipper.clip(polygon_a, envInter, tolerance, + 0.0)); + if (_polygonA.isEmpty()) + return false; + } else { + _polygonA = polygon_a; + } + + if (polyline_b.getPointCount() > 10) { + _polylineB = (Polyline) Clipper.clip(polyline_b, envInter, + tolerance, 0.0); + if (_polylineB.isEmpty()) + return false; + } else { + _polylineB = polyline_b; + } + + // We just need to determine that interior_interior is false + + String scl = "F********"; + boolean bRelation = RelationalOperationsMatrix.polygonRelatePolyline_( + _polygonA, _polylineB, tolerance, scl, progressTracker); + + return bRelation; + } + + private static boolean polygonCrossesPolylineImpl_(Polygon polygon_a, + Polyline polyline_b, double tolerance, + ProgressTracker progressTracker) { + MultiPathImpl polygon_impl_a = (MultiPathImpl) polygon_a._getImpl(); + MultiPathImpl polyline_impl_b = (MultiPathImpl) polyline_b._getImpl(); + + SegmentIteratorImpl segIterA = polygon_impl_a.querySegmentIterator(); + SegmentIteratorImpl segIterB = polyline_impl_b.querySegmentIterator(); + AttributeStreamOfInt32 verticesA = new AttributeStreamOfInt32(0); + AttributeStreamOfInt32 verticesB = new AttributeStreamOfInt32(0); + + double[] scalarsA = new double[2]; + double[] scalarsB = new double[2]; + + Envelope2DIntersectorImpl intersector = InternalUtils + .getEnvelope2DIntersector(polygon_impl_a, polyline_impl_b, + tolerance, verticesA, verticesB); + + boolean b_boundaries_intersect = false; + + if (intersector != null) { + while (intersector.next()) { + int index_a = intersector.getHandleA(); + int index_b = intersector.getHandleB(); + int vertex_a = verticesA.get(index_a); + int vertex_b = verticesB.get(index_b); + + segIterA.resetToVertex(vertex_a); + segIterB.resetToVertex(vertex_b); + Segment segmentA = segIterA.nextSegment(); + Segment segmentB = segIterB.nextSegment(); + + int result = segmentB.intersect(segmentA, null, scalarsB, + scalarsA, tolerance); + + if (result == 2) { + b_boundaries_intersect = true; + } else if (result != 0) { + double scalar_a_0 = scalarsA[0]; + double scalar_b_0 = scalarsB[0]; + + if (scalar_a_0 > 0.0 && scalar_a_0 < 1.0 + && scalar_b_0 > 0.0 && scalar_b_0 < 1.0) + return true; + + b_boundaries_intersect = true; + } + } + } + + if (!b_boundaries_intersect) + return false; + + Envelope2D env_a = new Envelope2D(), env_b = new Envelope2D(), envAInflated = new Envelope2D(), envBInflated = new Envelope2D(), envInter = new Envelope2D(); + polygon_a.queryEnvelope2D(env_a); + polyline_b.queryEnvelope2D(env_b); + + if (interiorEnvExteriorEnv_(env_b, env_a, tolerance)) { + envAInflated.setCoords(env_a); + envAInflated.inflate(1000.0 * tolerance, 1000.0 * tolerance); + envBInflated.setCoords(env_b); + envBInflated.inflate(1000.0 * tolerance, 1000.0 * tolerance); + envInter.setCoords(envAInflated); + envInter.intersect(envBInflated); + + Polygon _polygonA; + Polyline _polylineB; + + if (polygon_a.getPointCount() > 10) { + _polygonA = (Polygon) (Clipper.clip(polygon_a, envInter, + tolerance, 0.0)); + if (_polygonA.isEmpty()) + return false; + } else { + _polygonA = polygon_a; + } + + if (polyline_b.getPointCount() > 10) { + _polylineB = (Polyline) (Clipper.clip(polyline_b, envInter, + tolerance, 0.0)); + if (_polylineB.isEmpty()) + return false; + } else { + _polylineB = polyline_b; + } + + String scl = "T********"; + boolean bRelation = RelationalOperationsMatrix + .polygonRelatePolyline_(_polygonA, _polylineB, tolerance, + scl, progressTracker); + return bRelation; + } + + String scl = "T*****T**"; + boolean bRelation = RelationalOperationsMatrix.polygonRelatePolyline_( + polygon_a, polyline_b, tolerance, scl, progressTracker); + + return bRelation; + } + + private static boolean polygonContainsPolylineImpl_(Polygon polygon_a, + Polyline polyline_b, double tolerance, + ProgressTracker progress_tracker) { + MultiPathImpl polygon_impl_a = (MultiPathImpl) polygon_a._getImpl(); + MultiPathImpl polyline_impl_b = (MultiPathImpl) polyline_b._getImpl(); + + SegmentIteratorImpl segIterA = polygon_impl_a.querySegmentIterator(); + SegmentIteratorImpl segIterB = polyline_impl_b.querySegmentIterator(); + AttributeStreamOfInt32 verticesA = new AttributeStreamOfInt32(0); + AttributeStreamOfInt32 verticesB = new AttributeStreamOfInt32(0); + double[] scalarsA = new double[2]; + double[] scalarsB = new double[2]; + + Envelope2DIntersectorImpl intersector = InternalUtils + .getEnvelope2DIntersector(polygon_impl_a, polyline_impl_b, + tolerance, verticesA, verticesB); + + boolean b_boundaries_intersect = false; + + if (intersector != null) { + while (intersector.next()) { + int index_a = intersector.getHandleA(); + int index_b = intersector.getHandleB(); + int vertex_a = verticesA.get(index_a); + int vertex_b = verticesB.get(index_b); + + segIterA.resetToVertex(vertex_a); + segIterB.resetToVertex(vertex_b); + Segment segmentA = segIterA.nextSegment(); + Segment segmentB = segIterB.nextSegment(); + + int result = segmentB.intersect(segmentA, null, scalarsB, + scalarsA, tolerance); + + if (result == 2) { + b_boundaries_intersect = true; + // Keep going to see if we find a proper intersection of two + // segments (means contains is false) + } else if (result != 0) { + double scalar_a_0 = scalarsA[0]; + double scalar_b_0 = scalarsB[0]; + + if (scalar_a_0 > 0.0 && scalar_a_0 < 1.0 + && scalar_b_0 > 0.0 && scalar_b_0 < 1.0) + return false; + + b_boundaries_intersect = true; + // Keep going to see if we find a proper intersection of two + // segments (means contains is false) + } + } + } + + if (!b_boundaries_intersect) { + segIterB.resetToVertex(0); + Segment segmentB = segIterB.nextSegment(); + + Point2D ptB = new Point2D(); + segmentB.getCoord2D(0.5, ptB); + return (PolygonUtils.isPointInPolygon2D(polygon_a, ptB, 0.0) == PolygonUtils.PiPResult.PiPInside); + } + + // We can clip polygon_a to the extent of polyline_b + + Envelope2D envBInflated = new Envelope2D(); + polyline_b.queryEnvelope2D(envBInflated); + envBInflated.inflate(1000.0 * tolerance, 1000.0 * tolerance); + + Polygon _polygonA; + + if (polygon_a.getPointCount() > 10) { + _polygonA = (Polygon) (Clipper.clip(polygon_a, envBInflated, + tolerance, 0.0)); + if (_polygonA.isEmpty()) + return false; + } else { + _polygonA = polygon_a; + } + + String scl = "T*****F**"; // If Exterior-Interior is false, then + // Exterior-Boundary is false + boolean bRelation = RelationalOperationsMatrix.polygonRelatePolyline_( + _polygonA, polyline_b, tolerance, scl, progress_tracker); + + return bRelation; + } + + private static boolean polygonContainsPointImpl_(Polygon polygon_a, + Point2D pt_b, double tolerance, ProgressTracker progressTracker) { + PolygonUtils.PiPResult result = PolygonUtils.isPointInPolygon2D( + polygon_a, pt_b, tolerance); + + if (result == PolygonUtils.PiPResult.PiPInside) + return true; + + return false; + } + + private static boolean polygonTouchesPointImpl_(Polygon polygon_a, + Point2D pt_b, double tolerance, ProgressTracker progressTracker) { + PolygonUtils.PiPResult result = PolygonUtils.isPointInPolygon2D( + polygon_a, pt_b, tolerance); + + if (result == PolygonUtils.PiPResult.PiPBoundary) + return true; + + return false; + } + + private static boolean multiPointDisjointPointImpl_( + MultiPoint multipoint_a, Point2D pt_b, double tolerance, + ProgressTracker progressTracker) { + Point2D pt_a = new Point2D(); + double tolerance_sq = tolerance * tolerance; + + for (int i = 0; i < multipoint_a.getPointCount(); i++) { + multipoint_a.getXY(i, pt_a); + + if (Point2D.sqrDistance(pt_a, pt_b) <= tolerance_sq) + return false; + } + + return true; + } + + private static final class OverlapEvent { + int m_ivertex_a; + int m_ipath_a; + double m_scalar_a_0; + double m_scalar_a_1; + int m_ivertex_b; + int m_ipath_b; + double m_scalar_b_0; + double m_scalar_b_1; + + static OverlapEvent construct(int ivertex_a, int ipath_a, + double scalar_a_0, double scalar_a_1, int ivertex_b, + int ipath_b, double scalar_b_0, double scalar_b_1) { + OverlapEvent overlapEvent = new OverlapEvent(); + overlapEvent.m_ivertex_a = ivertex_a; + overlapEvent.m_ipath_a = ipath_a; + overlapEvent.m_scalar_a_0 = scalar_a_0; + overlapEvent.m_scalar_a_1 = scalar_a_1; + overlapEvent.m_ivertex_b = ivertex_b; + overlapEvent.m_ipath_b = ipath_b; + overlapEvent.m_scalar_b_0 = scalar_b_0; + overlapEvent.m_scalar_b_1 = scalar_b_1; + return overlapEvent; + } + } + + ArrayList m_overlap_events; + + private RelationalOperations() { + m_overlap_events = new ArrayList(); + } + + private static class OverlapComparer extends + AttributeStreamOfInt32.IntComparator { + OverlapComparer(RelationalOperations rel_ops) { + m_rel_ops = rel_ops; + } + + @Override + public int compare(int o_1, int o_2) { + return m_rel_ops.compareOverlapEvents_(o_1, o_2); + } + + private RelationalOperations m_rel_ops; + } + + int compareOverlapEvents_(int o_1, int o_2) { + OverlapEvent overlapEvent1 = m_overlap_events.get(o_1); + OverlapEvent overlapEvent2 = m_overlap_events.get(o_2); + + if (overlapEvent1.m_ipath_a < overlapEvent2.m_ipath_a) + return -1; + + if (overlapEvent1.m_ipath_a == overlapEvent2.m_ipath_a) { + if (overlapEvent1.m_ivertex_a < overlapEvent2.m_ivertex_a) + return -1; + + if (overlapEvent1.m_ivertex_a == overlapEvent2.m_ivertex_a) { + if (overlapEvent1.m_scalar_a_0 < overlapEvent2.m_scalar_a_0) + return -1; + + if (overlapEvent1.m_scalar_a_0 == overlapEvent2.m_scalar_a_0) { + if (overlapEvent1.m_scalar_a_1 < overlapEvent2.m_scalar_a_1) + return -1; + + if (overlapEvent1.m_scalar_a_1 == overlapEvent2.m_scalar_a_1) { + if (overlapEvent1.m_ivertex_b < overlapEvent2.m_ivertex_b) + return -1; + } + } + } + } + + return 1; + } +} diff --git a/src/com/esri/core/geometry/RelationalOperationsMatrix.java b/src/com/esri/core/geometry/RelationalOperationsMatrix.java new file mode 100644 index 00000000..7ed7a024 --- /dev/null +++ b/src/com/esri/core/geometry/RelationalOperationsMatrix.java @@ -0,0 +1,1920 @@ +/* + Copyright 1995-2013 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ +package com.esri.core.geometry; + +class RelationalOperationsMatrix { + private TopoGraph m_topo_graph; + private int[] m_matrix; + private boolean[] m_perform_predicates; + private String m_scl; + private int m_predicates; + private int m_predicate_count; + private int m_cluster_index_a; + private int m_cluster_index_b; + private int m_visited_index; + + private interface MatrixPredicate { + static final int InteriorInterior = 0; + static final int InteriorBoundary = 1; + static final int InteriorExterior = 2; + static final int BoundaryInterior = 3; + static final int BoundaryBoundary = 4; + static final int BoundaryExterior = 5; + static final int ExteriorInterior = 6; + static final int ExteriorBoundary = 7; + static final int ExteriorExterior = 8; + } + + private interface Predicates { + static final int AreaAreaPredicates = 0; + static final int AreaLinePredicates = 1; + static final int LineLinePredicates = 2; + static final int AreaPointPredicates = 3; + static final int LinePointPredicates = 4; + static final int PointPointPredicates = 5; + } + + // Computes the necessary 9 intersection relationships of boundary, + // interior, and exterior of geometry_a vs geometry_b for the given scl + // string. + static boolean relate(Geometry geometry_a, Geometry geometry_b, + SpatialReference sr, String scl, ProgressTracker progress_tracker) { + int relation = getPredefinedRelation_(scl, geometry_a.getDimension(), + geometry_b.getDimension()); + + if (relation != RelationalOperations.Relation.unknown) + return RelationalOperations.relate(geometry_a, geometry_b, sr, + relation, progress_tracker); + + Envelope2D env1 = new Envelope2D(); + geometry_a.queryEnvelope2D(env1); + Envelope2D env2 = new Envelope2D(); + geometry_b.queryEnvelope2D(env2); + + Envelope2D envMerged = new Envelope2D(); + envMerged.setCoords(env1); + envMerged.merge(env2); + double tolerance = InternalUtils.calculateToleranceFromGeometry(sr, + envMerged, false); + + Geometry _geometryA = convertGeometry_(geometry_a, tolerance); + Geometry _geometryB = convertGeometry_(geometry_b, tolerance); + + int typeA = _geometryA.getType().value(); + int typeB = _geometryB.getType().value(); + + boolean bRelation = false; + + switch (typeA) { + case Geometry.GeometryType.Polygon: + switch (typeB) { + case Geometry.GeometryType.Polygon: + bRelation = polygonRelatePolygon_((Polygon) (_geometryA), + (Polygon) (_geometryB), tolerance, scl, + progress_tracker); + break; + + case Geometry.GeometryType.Polyline: + bRelation = polygonRelatePolyline_((Polygon) (_geometryA), + (Polyline) (_geometryB), tolerance, scl, + progress_tracker); + break; + + case Geometry.GeometryType.Point: + bRelation = polygonRelatePoint_((Polygon) (_geometryA), + (Point) (_geometryB), tolerance, scl, progress_tracker); + break; + + case Geometry.GeometryType.MultiPoint: + bRelation = polygonRelateMultiPoint_((Polygon) (_geometryA), + (MultiPoint) (_geometryB), tolerance, scl, + progress_tracker); + break; + + default: + break; // warning fix + } + break; + + case Geometry.GeometryType.Polyline: + switch (typeB) { + case Geometry.GeometryType.Polygon: + bRelation = polygonRelatePolyline_((Polygon) (_geometryB), + (Polyline) (_geometryA), tolerance, + transposeMatrix_(scl), progress_tracker); + break; + + case Geometry.GeometryType.Polyline: + bRelation = polylineRelatePolyline_((Polyline) (_geometryA), + (Polyline) (_geometryB), tolerance, scl, + progress_tracker); + break; + + case Geometry.GeometryType.Point: + bRelation = polylineRelatePoint_((Polyline) (_geometryA), + (Point) (_geometryB), tolerance, scl, progress_tracker); + break; + + case Geometry.GeometryType.MultiPoint: + bRelation = polylineRelateMultiPoint_((Polyline) (_geometryA), + (MultiPoint) (_geometryB), tolerance, scl, + progress_tracker); + break; + + default: + break; // warning fix + } + break; + + case Geometry.GeometryType.Point: + switch (typeB) { + case Geometry.GeometryType.Polygon: + bRelation = polygonRelatePoint_((Polygon) (_geometryB), + (Point) (_geometryA), tolerance, transposeMatrix_(scl), + progress_tracker); + break; + + case Geometry.GeometryType.Polyline: + bRelation = polylineRelatePoint_((Polyline) (_geometryB), + (Point) (_geometryA), tolerance, transposeMatrix_(scl), + progress_tracker); + break; + + case Geometry.GeometryType.Point: + bRelation = pointRelatePoint_((Point) (_geometryA), + (Point) (_geometryB), tolerance, scl, progress_tracker); + break; + + case Geometry.GeometryType.MultiPoint: + bRelation = multiPointRelatePoint_((MultiPoint) (_geometryB), + (Point) (_geometryA), tolerance, transposeMatrix_(scl), + progress_tracker); + break; + + default: + break; // warning fix + } + break; + + case Geometry.GeometryType.MultiPoint: + switch (typeB) { + case Geometry.GeometryType.Polygon: + bRelation = polygonRelateMultiPoint_((Polygon) (_geometryB), + (MultiPoint) (_geometryA), tolerance, + transposeMatrix_(scl), progress_tracker); + break; + + case Geometry.GeometryType.Polyline: + bRelation = polylineRelateMultiPoint_((Polyline) (_geometryB), + (MultiPoint) (_geometryA), tolerance, + transposeMatrix_(scl), progress_tracker); + break; + + case Geometry.GeometryType.MultiPoint: + bRelation = multiPointRelateMultiPoint_( + (MultiPoint) (_geometryA), (MultiPoint) (_geometryB), + tolerance, scl, progress_tracker); + break; + + case Geometry.GeometryType.Point: + bRelation = multiPointRelatePoint_((MultiPoint) (_geometryA), + (Point) (_geometryB), tolerance, scl, progress_tracker); + break; + + default: + break; // warning fix + } + break; + default: + bRelation = false; + break; + } + + return bRelation; + } + + private RelationalOperationsMatrix() { + m_predicate_count = 0; + m_topo_graph = new TopoGraph(); + m_matrix = new int[9]; + m_perform_predicates = new boolean[9]; + } + + // Returns true if the relation holds. + static boolean polygonRelatePolygon_(Polygon polygon_a, Polygon polygon_b, + double tolerance, String scl, ProgressTracker progress_tracker) { + RelationalOperationsMatrix relOps = new RelationalOperationsMatrix(); + relOps.resetMatrix_(); + relOps.setPredicates_(scl); + relOps.setAreaAreaPredicates_(); + + EditShape edit_shape = new EditShape(); + int geom_a = edit_shape.addGeometry(polygon_a); + int geom_b = edit_shape.addGeometry(polygon_b); + relOps.setEditShapeCrackAndCluster_(edit_shape, tolerance, + progress_tracker); + relOps.computeMatrixTopoGraphHalfEdges_(geom_a, geom_b); + relOps.m_topo_graph.removeShape(); + + boolean bRelation = relationCompare_(relOps.m_matrix, relOps.m_scl); + return bRelation; + } + + // Returns true if the relation holds. + static boolean polygonRelatePolyline_(Polygon polygon_a, + Polyline polyline_b, double tolerance, String scl, + ProgressTracker progress_tracker) { + RelationalOperationsMatrix relOps = new RelationalOperationsMatrix(); + relOps.resetMatrix_(); + relOps.setPredicates_(scl); + relOps.setAreaLinePredicates_(); + + EditShape edit_shape = new EditShape(); + int geom_a = edit_shape.addGeometry(polygon_a); + int geom_b = edit_shape.addGeometry(polyline_b); + relOps.setEditShapeCrackAndCluster_(edit_shape, tolerance, + progress_tracker); + relOps.m_cluster_index_b = relOps.m_topo_graph + .createUserIndexForClusters(); + markClusters_(geom_b, relOps.m_topo_graph, relOps.m_cluster_index_b); + relOps.computeMatrixTopoGraphHalfEdges_(geom_a, geom_b); + relOps.m_topo_graph + .deleteUserIndexForClusters(relOps.m_cluster_index_b); + relOps.m_topo_graph.removeShape(); + + boolean bRelation = relationCompare_(relOps.m_matrix, relOps.m_scl); + return bRelation; + } + + // Returns true if the relation holds + static boolean polygonRelateMultiPoint_(Polygon polygon_a, + MultiPoint multipoint_b, double tolerance, String scl, + ProgressTracker progress_tracker) { + RelationalOperationsMatrix relOps = new RelationalOperationsMatrix(); + relOps.resetMatrix_(); + relOps.setPredicates_(scl); + relOps.setAreaPointPredicates_(); + + EditShape edit_shape = new EditShape(); + int geom_a = edit_shape.addGeometry(polygon_a); + int geom_b = edit_shape.addGeometry(multipoint_b); + relOps.setEditShapeCrackAndCluster_(edit_shape, tolerance, + progress_tracker); + relOps.computeMatrixTopoGraphClusters_(geom_a, geom_b); + relOps.m_topo_graph.removeShape(); + + boolean bRelation = relationCompare_(relOps.m_matrix, relOps.m_scl); + return bRelation; + } + + // Returns true if the relation holds. + static boolean polylineRelatePolyline_(Polyline polyline_a, + Polyline polyline_b, double tolerance, String scl, + ProgressTracker progress_tracker) { + RelationalOperationsMatrix relOps = new RelationalOperationsMatrix(); + relOps.resetMatrix_(); + relOps.setPredicates_(scl); + relOps.setLineLinePredicates_(); + + EditShape edit_shape = new EditShape(); + int geom_a = edit_shape.addGeometry(polyline_a); + int geom_b = edit_shape.addGeometry(polyline_b); + relOps.setEditShapeCrackAndCluster_(edit_shape, tolerance, + progress_tracker); + relOps.m_cluster_index_a = relOps.m_topo_graph + .createUserIndexForClusters(); + relOps.m_cluster_index_b = relOps.m_topo_graph + .createUserIndexForClusters(); + markClusters_(geom_a, relOps.m_topo_graph, relOps.m_cluster_index_a); + markClusters_(geom_b, relOps.m_topo_graph, relOps.m_cluster_index_b); + relOps.computeMatrixTopoGraphHalfEdges_(geom_a, geom_b); + relOps.m_topo_graph + .deleteUserIndexForClusters(relOps.m_cluster_index_a); + relOps.m_topo_graph + .deleteUserIndexForClusters(relOps.m_cluster_index_b); + relOps.m_topo_graph.removeShape(); + + boolean bRelation = relationCompare_(relOps.m_matrix, relOps.m_scl); + return bRelation; + } + + // Returns true if the relation holds. + static boolean polylineRelateMultiPoint_(Polyline polyline_a, + MultiPoint multipoint_b, double tolerance, String scl, + ProgressTracker progress_tracker) { + RelationalOperationsMatrix relOps = new RelationalOperationsMatrix(); + relOps.resetMatrix_(); + relOps.setPredicates_(scl); + relOps.setLinePointPredicates_(); + + EditShape edit_shape = new EditShape(); + int geom_a = edit_shape.addGeometry(polyline_a); + int geom_b = edit_shape.addGeometry(multipoint_b); + relOps.setEditShapeCrackAndCluster_(edit_shape, tolerance, + progress_tracker); + relOps.m_cluster_index_a = relOps.m_topo_graph + .createUserIndexForClusters(); + markClusters_(geom_a, relOps.m_topo_graph, relOps.m_cluster_index_a); + relOps.computeMatrixTopoGraphClusters_(geom_a, geom_b); + relOps.m_topo_graph + .deleteUserIndexForClusters(relOps.m_cluster_index_a); + relOps.m_topo_graph.removeShape(); + + boolean bRelation = relationCompare_(relOps.m_matrix, relOps.m_scl); + return bRelation; + } + + // Returns true if the relation holds. + static boolean multiPointRelateMultiPoint_(MultiPoint multipoint_a, + MultiPoint multipoint_b, double tolerance, String scl, + ProgressTracker progress_tracker) { + RelationalOperationsMatrix relOps = new RelationalOperationsMatrix(); + relOps.resetMatrix_(); + relOps.setPredicates_(scl); + relOps.setPointPointPredicates_(); + + EditShape edit_shape = new EditShape(); + int geom_a = edit_shape.addGeometry(multipoint_a); + int geom_b = edit_shape.addGeometry(multipoint_b); + relOps.setEditShapeCrackAndCluster_(edit_shape, tolerance, + progress_tracker); + relOps.computeMatrixTopoGraphClusters_(geom_a, geom_b); + relOps.m_topo_graph.removeShape(); + + boolean bRelation = relationCompare_(relOps.m_matrix, relOps.m_scl); + return bRelation; + } + + // Returns true if the relation holds. + static boolean polygonRelatePoint_(Polygon polygon_a, Point point_b, + double tolerance, String scl, ProgressTracker progress_tracker) { + Point2D pt_b = point_b.getXY(); + int[] matrix = new int[9]; + + for (int i = 0; i < 8; i++) + matrix[i] = -1; + + PolygonUtils.PiPResult res = PolygonUtils.isPointInPolygon2D(polygon_a, + pt_b, tolerance); + + if (res == PolygonUtils.PiPResult.PiPInside) + matrix[MatrixPredicate.InteriorInterior] = 0; + else if (res == PolygonUtils.PiPResult.PiPBoundary) + matrix[MatrixPredicate.BoundaryInterior] = 0; + else + matrix[MatrixPredicate.ExteriorInterior] = 0; + + matrix[MatrixPredicate.InteriorExterior] = 2; + matrix[MatrixPredicate.BoundaryExterior] = 1; + matrix[MatrixPredicate.ExteriorExterior] = 2; + + boolean bRelation = relationCompare_(matrix, scl); + return bRelation; + } + + // Returns true if the relation holds. + static boolean polylineRelatePoint_(Polyline polyline_a, Point point_b, + double tolerance, String scl, ProgressTracker progress_tracker) { + RelationalOperationsMatrix relOps = new RelationalOperationsMatrix(); + relOps.resetMatrix_(); + relOps.setPredicates_(scl); + relOps.setLinePointPredicates_(); + + MultiPoint multipoint_b = new MultiPoint(); + multipoint_b.add(point_b); + + EditShape edit_shape = new EditShape(); + int geom_a = edit_shape.addGeometry(polyline_a); + int geom_b = edit_shape.addGeometry(multipoint_b); + relOps.setEditShapeCrackAndCluster_(edit_shape, tolerance, + progress_tracker); + relOps.m_cluster_index_a = relOps.m_topo_graph + .createUserIndexForClusters(); + markClusters_(geom_a, relOps.m_topo_graph, relOps.m_cluster_index_a); + relOps.computeMatrixTopoGraphClusters_(geom_a, geom_b); + relOps.m_topo_graph + .deleteUserIndexForClusters(relOps.m_cluster_index_a); + relOps.m_topo_graph.removeShape(); + + boolean bRelation = relationCompare_(relOps.m_matrix, relOps.m_scl); + return bRelation; + } + + // Returns true if the relation holds. + static boolean multiPointRelatePoint_(MultiPoint multipoint_a, + Point point_b, double tolerance, String scl, + ProgressTracker progress_tracker) { + Point2D pt_b = point_b.getXY(); + int[] matrix = new int[9]; + + for (int i = 0; i < 8; i++) + matrix[i] = -1; + + boolean b_intersects = false; + boolean b_multipoint_contained = true; + double tolerance_sq = tolerance * tolerance; + Point2D pt_a = new Point2D(); + + for (int i = 0; i < multipoint_a.getPointCount(); i++) { + multipoint_a.getXY(i, pt_a); + + if (Point2D.sqrDistance(pt_a, pt_b) <= tolerance_sq) { + b_intersects = true; + } else { + b_multipoint_contained = false; + } + + if (b_intersects && !b_multipoint_contained) + break; + } + + if (b_intersects) { + matrix[MatrixPredicate.InteriorInterior] = 0; + + if (!b_multipoint_contained) + matrix[MatrixPredicate.InteriorExterior] = 0; + } else { + matrix[MatrixPredicate.InteriorExterior] = 0; + matrix[MatrixPredicate.ExteriorInterior] = 0; + } + + matrix[MatrixPredicate.ExteriorExterior] = 2; + + boolean bRelation = relationCompare_(matrix, scl); + return bRelation; + } + + // Returns true if the relation holds. + static boolean pointRelatePoint_(Point point_a, Point point_b, + double tolerance, String scl, ProgressTracker progress_tracker) { + Point2D pt_a = point_a.getXY(); + Point2D pt_b = point_b.getXY(); + int[] matrix = new int[9]; + + for (int i = 1; i < 8; i++) + matrix[i] = -1; + + if (Point2D.sqrDistance(pt_a, pt_b) <= tolerance * tolerance) { + matrix[MatrixPredicate.InteriorInterior] = 0; + } else { + matrix[MatrixPredicate.InteriorExterior] = 0; + matrix[MatrixPredicate.ExteriorInterior] = 0; + } + + matrix[MatrixPredicate.ExteriorExterior] = 2; + + boolean bRelation = relationCompare_(matrix, scl); + return bRelation; + } + + // Compares the DE-9I matrix against the scl string. + private static boolean relationCompare_(int[] matrix, String scl) { + for (int i = 0; i < 9; i++) { + switch (scl.charAt(i)) { + case 'T': + assert (matrix[i] != -2); + if (matrix[i] == -1) + return false; + break; + + case 'F': + assert (matrix[i] != -2); + if (matrix[i] != -1) + return false; + break; + + case '0': + assert (matrix[i] != -2); + if (matrix[i] != 0) + return false; + break; + + case '1': + assert (matrix[i] != -2); + if (matrix[i] != 1) + return false; + break; + + case '2': + assert (matrix[i] != -2); + if (matrix[i] != 2) + return false; + break; + } + } + + return true; + } + + // Checks whether scl string is a predefined relation. + private static int getPredefinedRelation_(String scl, int dim_a, int dim_b) { + if (equals_(scl)) + return RelationalOperations.Relation.equals; + + if (disjoint_(scl)) + return RelationalOperations.Relation.disjoint; + + if (touches_(scl, dim_a, dim_b)) + return RelationalOperations.Relation.touches; + + if (crosses_(scl, dim_a, dim_b)) + return RelationalOperations.Relation.crosses; + + if (contains_(scl)) + return RelationalOperations.Relation.contains; + + if (overlaps_(scl, dim_a, dim_b)) + return RelationalOperations.Relation.overlaps; + + return RelationalOperations.Relation.unknown; + } + + // Checks whether the scl string is the equals relation. + private static boolean equals_(String scl) { + // Valid for all + if (scl.charAt(0) == 'T' && scl.charAt(1) == '*' + && scl.charAt(2) == 'F' && scl.charAt(3) == '*' + && scl.charAt(4) == '*' && scl.charAt(5) == 'F' + && scl.charAt(6) == 'F' && scl.charAt(7) == 'F' + && scl.charAt(8) == '*') + return true; + + return false; + } + + // Checks whether the scl string is the disjoint relation. + private static boolean disjoint_(String scl) { + if (scl.charAt(0) == 'F' && scl.charAt(1) == 'F' + && scl.charAt(2) == '*' && scl.charAt(3) == 'F' + && scl.charAt(4) == 'F' && scl.charAt(5) == '*' + && scl.charAt(6) == '*' && scl.charAt(7) == '*' + && scl.charAt(8) == '*') + return true; + + return false; + } + + // Checks whether the scl string is the touches relation. + private static boolean touches_(String scl, int dim_a, int dim_b) { + // Points cant touch + if (dim_a == 0 && dim_b == 0) + return false; + + if (!(dim_a == 2 && dim_b == 2)) { + // Valid for area-Line, Line-Line, area-Point, and Line-Point + if (scl.charAt(0) == 'F' && scl.charAt(1) == '*' + && scl.charAt(2) == '*' && scl.charAt(3) == 'T' + && scl.charAt(4) == '*' && scl.charAt(5) == '*' + && scl.charAt(6) == '*' && scl.charAt(7) == '*' + && scl.charAt(8) == '*') + return true; + + if (dim_a == 1 && dim_b == 1) { + // Valid for Line-Line + if (scl.charAt(0) == 'F' && scl.charAt(1) == 'T' + && scl.charAt(2) == '*' && scl.charAt(3) == '*' + && scl.charAt(4) == '*' && scl.charAt(5) == '*' + && scl.charAt(6) == '*' && scl.charAt(7) == '*' + && scl.charAt(8) == '*') + return true; + } + } + + // Valid for area-area, area-Line, Line-Line + + if (dim_b != 0) { + if (scl.charAt(0) == 'F' && scl.charAt(1) == '*' + && scl.charAt(2) == '*' && scl.charAt(3) == '*' + && scl.charAt(4) == 'T' && scl.charAt(5) == '*' + && scl.charAt(6) == '*' && scl.charAt(7) == '*' + && scl.charAt(8) == '*') + return true; + } + + return false; + } + + // Checks whether the scl string is the crosses relation. + private static boolean crosses_(String scl, int dim_a, int dim_b) { + if (dim_a > dim_b) { + // Valid for area-Line, area-Point, Line-Point + if (scl.charAt(0) == 'T' && scl.charAt(1) == '*' + && scl.charAt(2) == '*' && scl.charAt(3) == '*' + && scl.charAt(4) == '*' && scl.charAt(5) == '*' + && scl.charAt(6) == 'T' && scl.charAt(7) == '*' + && scl.charAt(8) == '*') + return true; + + return false; + } + + if (dim_a == 1 && dim_b == 1) { + // Valid for Line-Line + if (scl.charAt(0) == '0' && scl.charAt(1) == '*' + && scl.charAt(2) == '*' && scl.charAt(3) == '*' + && scl.charAt(4) == '*' && scl.charAt(5) == '*' + && scl.charAt(6) == '*' && scl.charAt(7) == '*' + && scl.charAt(8) == '*') + return true; + } + + return false; + } + + // Checks whether the scl string is the contains relation. + private static boolean contains_(String scl) { + // Valid for all + if (scl.charAt(0) == 'T' && scl.charAt(1) == '*' + && scl.charAt(2) == '*' && scl.charAt(3) == '*' + && scl.charAt(4) == '*' && scl.charAt(5) == '*' + && scl.charAt(6) == 'F' && scl.charAt(7) == 'F' + && scl.charAt(8) == '*') + return true; + + return false; + } + + // Checks whether the scl string is the overlaps relation. + private static boolean overlaps_(String scl, int dim_a, int dim_b) { + if (dim_a == dim_b) { + if (dim_a != 1) { + // Valid for area-area, Point-Point + if (scl.charAt(0) == 'T' && scl.charAt(1) == '*' + && scl.charAt(2) == 'T' && scl.charAt(3) == '*' + && scl.charAt(4) == '*' && scl.charAt(5) == '*' + && scl.charAt(6) == 'T' && scl.charAt(7) == '*' + && scl.charAt(8) == '*') + return true; + + return false; + } + + // Valid for Line-Line + if (scl.charAt(0) == '1' && scl.charAt(1) == '*' + && scl.charAt(2) == 'T' && scl.charAt(3) == '*' + && scl.charAt(4) == '*' && scl.charAt(5) == '*' + && scl.charAt(6) == 'T' && scl.charAt(7) == '*' + && scl.charAt(8) == '*') + return true; + } + + return false; + } + + // Marks each cluster of the topoGraph as belonging to an interior vertex of + // the geometry and/or a boundary index of the geometry. + private static void markClusters_(int geometry, TopoGraph topoGraph, + int clusterIndex) { + EditShape edit_shape = topoGraph.getShape(); + + for (int path = edit_shape.getFirstPath(geometry); path != -1; path = edit_shape + .getNextPath(path)) { + int vertexFirst = edit_shape.getFirstVertex(path); + int vertexLast = edit_shape.getLastVertex(path); + boolean b_closed = (vertexFirst == vertexLast); + + int vertex = vertexFirst; + + do { + int cluster = topoGraph.getClusterFromVertex(vertex); + int index = topoGraph + .getClusterUserIndex(cluster, clusterIndex); + + if (!b_closed + && (vertex == vertexFirst || vertex == vertexLast)) { + if (index == -1) + topoGraph.setClusterUserIndex(cluster, clusterIndex, 1); + else + topoGraph.setClusterUserIndex(cluster, clusterIndex, + index + 1); + } else { + if (index == -1) + topoGraph.setClusterUserIndex(cluster, clusterIndex, 0); + } + + vertex = edit_shape.getNextVertex(vertex); + } while (vertex != vertexFirst && vertex != -1); + } + } + + private static String transposeMatrix_(String scl) { + String transpose = new String(); + transpose += scl.charAt(0); + transpose += scl.charAt(3); + transpose += scl.charAt(6); + transpose += scl.charAt(1); + transpose += scl.charAt(4); + transpose += scl.charAt(7); + transpose += scl.charAt(2); + transpose += scl.charAt(5); + transpose += scl.charAt(8); + return transpose; + } + + // Allocates the matrix array if need be, and sets all entries to -2. + // -2: Not Computed + // -1: No intersection + // 0: 0-dimension intersection + // 1: 1-dimension intersection + // 2: 2-dimension intersection + private void resetMatrix_() { + for (int i = 0; i < 9; i++) + m_matrix[i] = -2; + } + + // Sets the relation predicates from the scl string. + private void setPredicates_(String scl) { + m_scl = scl; + + for (int i = 0; i < 9; i++) { + if (m_scl.charAt(i) != '*') { + m_perform_predicates[i] = true; + m_predicate_count++; + } else + m_perform_predicates[i] = false; + } + } + + // Sets the remaining predicates to false + private void setRemainingPredicatesToFalse_() { + for (int i = 0; i < 9; i++) { + if (m_perform_predicates[i] && m_matrix[i] == -2) { + m_matrix[i] = -1; + m_perform_predicates[i] = false; + } + } + } + + // Checks whether the predicate is known. + private boolean isPredicateKnown_(int predicate, int dim) { + assert (m_scl.charAt(predicate) != '*'); + + if (m_matrix[predicate] == -1) { + m_perform_predicates[predicate] = false; + m_predicate_count--; + return true; + } + + if (m_scl.charAt(predicate) != 'T' && m_scl.charAt(predicate) != 'F') { + if (m_matrix[predicate] < dim) { + return false; + } else { + m_perform_predicates[predicate] = false; + m_predicate_count--; + return true; + } + } else { + if (m_matrix[predicate] == -2) { + return false; + } else { + m_perform_predicates[predicate] = false; + m_predicate_count--; + return true; + } + } + } + + // Sets the area-area predicates function. + private void setAreaAreaPredicates_() { + m_predicates = Predicates.AreaAreaPredicates; + + // set predicates that are always true/false + if (m_perform_predicates[MatrixPredicate.ExteriorExterior]) { + m_matrix[MatrixPredicate.ExteriorExterior] = 2; // Always true + m_perform_predicates[MatrixPredicate.ExteriorExterior] = false; + m_predicate_count--; + } + } + + // Sets the area-line predicate function. + private void setAreaLinePredicates_() { + m_predicates = Predicates.AreaLinePredicates; + + // set predicates that are always true/false + if (m_perform_predicates[MatrixPredicate.InteriorExterior]) { + m_matrix[MatrixPredicate.InteriorExterior] = 2; // Always true + m_perform_predicates[MatrixPredicate.InteriorExterior] = false; + m_predicate_count--; + } + + if (m_perform_predicates[MatrixPredicate.ExteriorExterior]) { + m_matrix[MatrixPredicate.ExteriorExterior] = 2; // Always true + m_perform_predicates[MatrixPredicate.ExteriorExterior] = false; + m_predicate_count--; + } + } + + // Sets the line-line predicates function. + private void setLineLinePredicates_() { + m_predicates = Predicates.LineLinePredicates; + + // set predicates that are always true/false + if (m_perform_predicates[MatrixPredicate.ExteriorExterior]) { + m_matrix[MatrixPredicate.ExteriorExterior] = 2; // Always true + m_perform_predicates[MatrixPredicate.ExteriorExterior] = false; + m_predicate_count--; + } + } + + // Sets the area-point predicate function. + private void setAreaPointPredicates_() { + m_predicates = Predicates.AreaPointPredicates; + + // set predicates that are always true/false + if (m_perform_predicates[MatrixPredicate.InteriorBoundary]) { + m_matrix[MatrixPredicate.InteriorBoundary] = -1; // Always false + m_perform_predicates[MatrixPredicate.InteriorBoundary] = false; + m_predicate_count--; + } + + if (m_perform_predicates[MatrixPredicate.InteriorExterior]) { + m_matrix[MatrixPredicate.InteriorExterior] = 2; // Always true + m_perform_predicates[MatrixPredicate.InteriorExterior] = false; + m_predicate_count--; + } + + if (m_perform_predicates[MatrixPredicate.BoundaryBoundary]) { + m_matrix[MatrixPredicate.BoundaryBoundary] = -1; // Always false + m_perform_predicates[MatrixPredicate.BoundaryBoundary] = false; + m_predicate_count--; + } + + if (m_perform_predicates[MatrixPredicate.BoundaryExterior]) { + m_matrix[MatrixPredicate.BoundaryExterior] = 1; // Always true + m_perform_predicates[MatrixPredicate.BoundaryExterior] = false; + m_predicate_count--; + } + + if (m_perform_predicates[MatrixPredicate.ExteriorBoundary]) { + m_matrix[MatrixPredicate.ExteriorBoundary] = -1; // Always false + m_perform_predicates[MatrixPredicate.ExteriorBoundary] = false; + m_predicate_count--; + } + + if (m_perform_predicates[MatrixPredicate.ExteriorExterior]) { + m_matrix[MatrixPredicate.ExteriorExterior] = 2; // Always true + m_perform_predicates[MatrixPredicate.ExteriorExterior] = false; + m_predicate_count--; + } + } + + // Sets the line-point predicates function. + private void setLinePointPredicates_() { + m_predicates = Predicates.LinePointPredicates; + + // set predicates that are always true/false + if (m_perform_predicates[MatrixPredicate.InteriorBoundary]) { + m_matrix[MatrixPredicate.InteriorBoundary] = -1; // Always false + m_perform_predicates[MatrixPredicate.InteriorBoundary] = false; + m_predicate_count--; + } + + if (m_perform_predicates[MatrixPredicate.InteriorExterior]) { + m_matrix[MatrixPredicate.InteriorExterior] = 1; // Always true + m_perform_predicates[MatrixPredicate.InteriorExterior] = false; + m_predicate_count--; + } + + if (m_perform_predicates[MatrixPredicate.BoundaryBoundary]) { + m_matrix[MatrixPredicate.BoundaryBoundary] = -1; // Always false + m_perform_predicates[MatrixPredicate.BoundaryBoundary] = false; + m_predicate_count--; + } + + if (m_perform_predicates[MatrixPredicate.ExteriorBoundary]) { + m_matrix[MatrixPredicate.ExteriorBoundary] = -1; // Always false + m_perform_predicates[MatrixPredicate.ExteriorBoundary] = false; + m_predicate_count--; + } + + if (m_perform_predicates[MatrixPredicate.ExteriorExterior]) { + m_matrix[MatrixPredicate.ExteriorExterior] = 2; // Always true + m_perform_predicates[MatrixPredicate.ExteriorExterior] = false; + m_predicate_count--; + } + } + + // Sets the point-point predicates function. + private void setPointPointPredicates_() { + m_predicates = Predicates.PointPointPredicates; + + // set predicates that are always true/false + if (m_perform_predicates[MatrixPredicate.InteriorBoundary]) { + m_matrix[MatrixPredicate.InteriorBoundary] = -1; // Always false + m_perform_predicates[MatrixPredicate.InteriorBoundary] = false; + m_predicate_count--; + } + + if (m_perform_predicates[MatrixPredicate.BoundaryInterior]) { + m_matrix[MatrixPredicate.BoundaryInterior] = -1; // Always false + m_perform_predicates[MatrixPredicate.BoundaryInterior] = false; + m_predicate_count--; + } + + if (m_perform_predicates[MatrixPredicate.BoundaryBoundary]) { + m_matrix[MatrixPredicate.BoundaryBoundary] = -1; // Always false + m_perform_predicates[MatrixPredicate.BoundaryBoundary] = false; + m_predicate_count--; + } + + if (m_perform_predicates[MatrixPredicate.BoundaryExterior]) { + m_matrix[MatrixPredicate.BoundaryExterior] = -1; // Always false + m_perform_predicates[MatrixPredicate.BoundaryExterior] = false; + m_predicate_count--; + } + + if (m_perform_predicates[MatrixPredicate.ExteriorBoundary]) { + m_matrix[MatrixPredicate.ExteriorBoundary] = -1; // Always false + m_perform_predicates[MatrixPredicate.ExteriorBoundary] = false; + m_predicate_count--; + } + + if (m_perform_predicates[MatrixPredicate.ExteriorExterior]) { + m_matrix[MatrixPredicate.ExteriorExterior] = 2; // Always true + m_perform_predicates[MatrixPredicate.ExteriorExterior] = false; + m_predicate_count--; + } + } + + // Invokes the 9 relational predicates of area vs area. + private boolean areaAreaPredicates_(int half_edge, int id_a, int id_b) { + boolean bRelationKnown = true; + + if (m_perform_predicates[MatrixPredicate.InteriorInterior]) { + interiorAreaInteriorArea_(half_edge, id_a, id_b); + bRelationKnown &= isPredicateKnown_( + MatrixPredicate.InteriorInterior, 2); + } + + if (m_perform_predicates[MatrixPredicate.InteriorBoundary]) { + interiorAreaBoundaryArea_(half_edge, id_a, + MatrixPredicate.InteriorBoundary); + bRelationKnown &= isPredicateKnown_( + MatrixPredicate.InteriorBoundary, 1); + } + + if (m_perform_predicates[MatrixPredicate.InteriorExterior]) { + interiorAreaExteriorArea_(half_edge, id_a, id_b, + MatrixPredicate.InteriorExterior); + bRelationKnown &= isPredicateKnown_( + MatrixPredicate.InteriorExterior, 2); + } + + if (m_perform_predicates[MatrixPredicate.BoundaryInterior]) { + interiorAreaBoundaryArea_(half_edge, id_b, + MatrixPredicate.BoundaryInterior); + bRelationKnown &= isPredicateKnown_( + MatrixPredicate.BoundaryInterior, 1); + } + + if (m_perform_predicates[MatrixPredicate.BoundaryBoundary]) { + boundaryAreaBoundaryArea_(half_edge, id_a, id_b); + bRelationKnown &= isPredicateKnown_( + MatrixPredicate.BoundaryBoundary, 1); + } + + if (m_perform_predicates[MatrixPredicate.BoundaryExterior]) { + boundaryAreaExteriorArea_(half_edge, id_a, id_b, + MatrixPredicate.BoundaryExterior); + bRelationKnown &= isPredicateKnown_( + MatrixPredicate.BoundaryExterior, 1); + } + + if (m_perform_predicates[MatrixPredicate.ExteriorInterior]) { + interiorAreaExteriorArea_(half_edge, id_b, id_a, + MatrixPredicate.ExteriorInterior); + bRelationKnown &= isPredicateKnown_( + MatrixPredicate.ExteriorInterior, 2); + } + + if (m_perform_predicates[MatrixPredicate.ExteriorBoundary]) { + boundaryAreaExteriorArea_(half_edge, id_b, id_a, + MatrixPredicate.ExteriorBoundary); + bRelationKnown &= isPredicateKnown_( + MatrixPredicate.ExteriorBoundary, 1); + } + + return bRelationKnown; + } + + // Invokes the 9 relational predicates of area vs Line. + private boolean areaLinePredicates_(int half_edge, int id_a, int id_b) { + boolean bRelationKnown = true; + + if (m_perform_predicates[MatrixPredicate.InteriorInterior]) { + interiorAreaInteriorLine_(half_edge, id_a, id_b); + bRelationKnown &= isPredicateKnown_( + MatrixPredicate.InteriorInterior, 1); + } + + if (m_perform_predicates[MatrixPredicate.InteriorBoundary]) { + interiorAreaBoundaryLine_(half_edge, id_a, id_b, m_cluster_index_b); + bRelationKnown &= isPredicateKnown_( + MatrixPredicate.InteriorBoundary, 0); + } + + if (m_perform_predicates[MatrixPredicate.BoundaryInterior]) { + boundaryAreaInteriorLine_(half_edge, id_a, id_b, m_cluster_index_b); + bRelationKnown &= isPredicateKnown_( + MatrixPredicate.BoundaryInterior, 1); + } + + if (m_perform_predicates[MatrixPredicate.BoundaryBoundary]) { + boundaryAreaBoundaryLine_(half_edge, id_a, id_b, m_cluster_index_b); + bRelationKnown &= isPredicateKnown_( + MatrixPredicate.BoundaryBoundary, 0); + } + + if (m_perform_predicates[MatrixPredicate.BoundaryExterior]) { + boundaryAreaExteriorLine_(half_edge, id_a, id_b); + bRelationKnown &= isPredicateKnown_( + MatrixPredicate.BoundaryExterior, 1); + } + + if (m_perform_predicates[MatrixPredicate.ExteriorInterior]) { + exteriorAreaInteriorLine_(half_edge, id_a); + bRelationKnown &= isPredicateKnown_( + MatrixPredicate.ExteriorInterior, 1); + } + + if (m_perform_predicates[MatrixPredicate.ExteriorBoundary]) { + exteriorAreaBoundaryLine_(half_edge, id_a, id_b, m_cluster_index_b); + bRelationKnown &= isPredicateKnown_( + MatrixPredicate.ExteriorBoundary, 0); + } + + return bRelationKnown; + } + + // Invokes the 9 relational predicates of Line vs Line. + private boolean lineLinePredicates_(int half_edge, int id_a, int id_b) { + boolean bRelationKnown = true; + + if (m_perform_predicates[MatrixPredicate.InteriorInterior]) { + interiorLineInteriorLine_(half_edge, id_a, id_b, m_cluster_index_a, + m_cluster_index_b); + bRelationKnown &= isPredicateKnown_( + MatrixPredicate.InteriorInterior, 1); + } + + if (m_perform_predicates[MatrixPredicate.InteriorBoundary]) { + interiorLineBoundaryLine_(half_edge, id_a, id_b, m_cluster_index_a, + m_cluster_index_b, MatrixPredicate.InteriorBoundary); + bRelationKnown &= isPredicateKnown_( + MatrixPredicate.InteriorBoundary, 0); + } + + if (m_perform_predicates[MatrixPredicate.InteriorExterior]) { + interiorLineExteriorLine_(half_edge, id_a, id_b, + MatrixPredicate.InteriorExterior); + bRelationKnown &= isPredicateKnown_( + MatrixPredicate.InteriorExterior, 1); + } + + if (m_perform_predicates[MatrixPredicate.BoundaryInterior]) { + interiorLineBoundaryLine_(half_edge, id_b, id_a, m_cluster_index_b, + m_cluster_index_a, MatrixPredicate.BoundaryInterior); + bRelationKnown &= isPredicateKnown_( + MatrixPredicate.BoundaryInterior, 0); + } + + if (m_perform_predicates[MatrixPredicate.BoundaryBoundary]) { + boundaryLineBoundaryLine_(half_edge, id_a, id_b, m_cluster_index_a, + m_cluster_index_b); + bRelationKnown &= isPredicateKnown_( + MatrixPredicate.BoundaryBoundary, 0); + } + + if (m_perform_predicates[MatrixPredicate.BoundaryExterior]) { + boundaryLineExteriorLine_(half_edge, id_a, id_b, m_cluster_index_a, + MatrixPredicate.BoundaryExterior); + bRelationKnown &= isPredicateKnown_( + MatrixPredicate.BoundaryExterior, 0); + } + + if (m_perform_predicates[MatrixPredicate.ExteriorInterior]) { + interiorLineExteriorLine_(half_edge, id_b, id_a, + MatrixPredicate.ExteriorInterior); + bRelationKnown &= isPredicateKnown_( + MatrixPredicate.ExteriorInterior, 1); + } + + if (m_perform_predicates[MatrixPredicate.ExteriorBoundary]) { + boundaryLineExteriorLine_(half_edge, id_b, id_a, m_cluster_index_b, + MatrixPredicate.ExteriorBoundary); + bRelationKnown &= isPredicateKnown_( + MatrixPredicate.ExteriorBoundary, 0); + } + + return bRelationKnown; + } + + // Invokes the 9 relational predicates of area vs Point. + private boolean areaPointPredicates_(int cluster, int id_a, int id_b) { + boolean bRelationKnown = true; + + if (m_perform_predicates[MatrixPredicate.InteriorInterior]) { + interiorAreaInteriorPoint_(cluster, id_a); + bRelationKnown &= isPredicateKnown_( + MatrixPredicate.InteriorInterior, 0); + } + + if (m_perform_predicates[MatrixPredicate.BoundaryInterior]) { + boundaryAreaInteriorPoint_(cluster, id_a, id_b); + bRelationKnown &= isPredicateKnown_( + MatrixPredicate.BoundaryInterior, 0); + } + + if (m_perform_predicates[MatrixPredicate.ExteriorInterior]) { + exteriorAreaInteriorPoint_(cluster, id_a); + bRelationKnown &= isPredicateKnown_( + MatrixPredicate.ExteriorInterior, 0); + } + + return bRelationKnown; + } + + // Invokes the 9 relational predicates of Line vs Point. + private boolean linePointPredicates_(int cluster, int id_a, int id_b) { + boolean bRelationKnown = true; + + if (m_perform_predicates[MatrixPredicate.InteriorInterior]) { + interiorLineInteriorPoint_(cluster, id_a, id_b, m_cluster_index_a); + bRelationKnown &= isPredicateKnown_( + MatrixPredicate.InteriorInterior, 0); + } + + if (m_perform_predicates[MatrixPredicate.BoundaryInterior]) { + boundaryLineInteriorPoint_(cluster, id_a, id_b, m_cluster_index_a); + bRelationKnown &= isPredicateKnown_( + MatrixPredicate.BoundaryInterior, 0); + } + + if (m_perform_predicates[MatrixPredicate.BoundaryExterior]) { + boundaryLineExteriorPoint_(cluster, id_a, id_b, m_cluster_index_a); + bRelationKnown &= isPredicateKnown_( + MatrixPredicate.BoundaryExterior, 0); + } + + if (m_perform_predicates[MatrixPredicate.ExteriorInterior]) { + exteriorLineInteriorPoint_(cluster, id_a, id_b); + bRelationKnown &= isPredicateKnown_( + MatrixPredicate.ExteriorInterior, 0); + } + + return bRelationKnown; + } + + // Invokes the 9 relational predicates of Point vs Point. + private boolean pointPointPredicates_(int cluster, int id_a, int id_b) { + boolean bRelationKnown = true; + + if (m_perform_predicates[MatrixPredicate.InteriorInterior]) { + interiorPointInteriorPoint_(cluster, id_a, id_b); + bRelationKnown &= isPredicateKnown_( + MatrixPredicate.InteriorInterior, 0); + } + + if (m_perform_predicates[MatrixPredicate.InteriorExterior]) { + interiorPointExteriorPoint_(cluster, id_a, id_b, + MatrixPredicate.InteriorExterior); + bRelationKnown &= isPredicateKnown_( + MatrixPredicate.InteriorExterior, 0); + } + + if (m_perform_predicates[MatrixPredicate.ExteriorInterior]) { + interiorPointExteriorPoint_(cluster, id_b, id_a, + MatrixPredicate.ExteriorInterior); + bRelationKnown &= isPredicateKnown_( + MatrixPredicate.ExteriorInterior, 0); + } + + return bRelationKnown; + } + + // Relational predicate to determine if the interior of area A intersects + // with the interior of area B. + private void interiorAreaInteriorArea_(int half_edge, int id_a, int id_b) { + if (m_matrix[MatrixPredicate.InteriorInterior] == 2) + return; + + int faceParentage = m_topo_graph.getHalfEdgeFaceParentage(half_edge); + + if ((faceParentage & id_a) != 0 && (faceParentage & id_b) != 0) + m_matrix[MatrixPredicate.InteriorInterior] = 2; + } + + // Relational predicate to determine if the interior of area A intersects + // with the boundary of area B. + private void interiorAreaBoundaryArea_(int half_edge, int id_a, + int predicate) { + if (m_matrix[predicate] == 1) + return; + + int faceParentage = m_topo_graph.getHalfEdgeFaceParentage(half_edge); + int twinFaceParentage = m_topo_graph + .getHalfEdgeFaceParentage(m_topo_graph + .getHalfEdgeTwin(half_edge)); + + if ((faceParentage & id_a) != 0 && (twinFaceParentage & id_a) != 0) + m_matrix[predicate] = 1; + } + + // Relational predicate to determine if the interior of area A intersects + // with the exterior of area B. + private void interiorAreaExteriorArea_(int half_edge, int id_a, int id_b, + int predicate) { + if (m_matrix[predicate] == 2) + return; + + int faceParentage = m_topo_graph.getHalfEdgeFaceParentage(half_edge); + + if ((faceParentage & id_a) != 0 && (faceParentage & id_b) == 0) + m_matrix[predicate] = 2; + + } + + // Relational predicate to determine if the boundary of area A intersects + // with the boundary of area B. + private void boundaryAreaBoundaryArea_(int half_edge, int id_a, int id_b) { + if (m_matrix[MatrixPredicate.BoundaryBoundary] == 1) + return; + + int parentage = m_topo_graph.getHalfEdgeParentage(half_edge); + + if ((parentage & id_a) != 0 && (parentage & id_b) != 0) { + m_matrix[MatrixPredicate.BoundaryBoundary] = 1; + return; + } + + if (m_matrix[MatrixPredicate.BoundaryBoundary] != 0) { + if (m_topo_graph.getHalfEdgeUserIndex(m_topo_graph + .getHalfEdgePrev(m_topo_graph.getHalfEdgeTwin(half_edge)), + m_visited_index) != 1) { + int cluster = m_topo_graph.getHalfEdgeTo(half_edge); + int clusterParentage = m_topo_graph + .getClusterParentage(cluster); + + if ((clusterParentage & id_a) != 0 + && (clusterParentage & id_b) != 0) { + m_matrix[MatrixPredicate.BoundaryBoundary] = 0; + } + } + } + } + + // Relational predicate to determine if the boundary of area A intersects + // with the exterior of area B. + private void boundaryAreaExteriorArea_(int half_edge, int id_a, int id_b, + int predicate) { + if (m_matrix[predicate] == 1) + return; + + int faceParentage = m_topo_graph.getHalfEdgeFaceParentage(half_edge); + int twinFaceParentage = m_topo_graph + .getHalfEdgeFaceParentage(m_topo_graph + .getHalfEdgeTwin(half_edge)); + + if ((faceParentage & id_b) == 0 && (twinFaceParentage & id_b) == 0) + m_matrix[predicate] = 1; + } + + // Relational predicate to determine if the interior of area A intersects + // with the interior of Line B. + private void interiorAreaInteriorLine_(int half_edge, int id_a, int id_b) { + if (m_matrix[MatrixPredicate.InteriorInterior] == 1) + return; + + int faceParentage = m_topo_graph.getHalfEdgeFaceParentage(half_edge); + int twinFaceParentage = m_topo_graph + .getHalfEdgeFaceParentage(m_topo_graph + .getHalfEdgeTwin(half_edge)); + + if ((faceParentage & id_a) != 0 && (twinFaceParentage & id_a) != 0) + m_matrix[MatrixPredicate.InteriorInterior] = 1; + } + + // Relational predicate to determine if the interior of area A intersects + // with the boundary of Line B. + private void interiorAreaBoundaryLine_(int half_edge, int id_a, int id_b, + int cluster_index_b) { + if (m_matrix[MatrixPredicate.InteriorBoundary] == 0) + return; + + if (m_topo_graph.getHalfEdgeUserIndex(m_topo_graph + .getHalfEdgePrev(m_topo_graph.getHalfEdgeTwin(half_edge)), + m_visited_index) != 1) { + int cluster = m_topo_graph.getHalfEdgeTo(half_edge); + int clusterParentage = m_topo_graph.getClusterParentage(cluster); + + if ((clusterParentage & id_a) == 0) { + int faceParentage = m_topo_graph + .getHalfEdgeFaceParentage(half_edge); + + if ((faceParentage & id_a) != 0) { + int index = m_topo_graph.getClusterUserIndex(cluster, + cluster_index_b); + + if ((clusterParentage & id_b) != 0 && (index % 2 != 0)) { + assert (index != -1); + m_matrix[MatrixPredicate.InteriorBoundary] = 0; + } + } + } + } + } + + // Relational predicate to determine if the boundary of area A intersects + // with the interior of Line B. + private void boundaryAreaInteriorLine_(int half_edge, int id_a, int id_b, + int cluster_index_b) { + if (m_matrix[MatrixPredicate.BoundaryInterior] == 1) + return; + + int parentage = m_topo_graph.getHalfEdgeParentage(half_edge); + + if ((parentage & id_a) != 0 && (parentage & id_b) != 0) { + m_matrix[MatrixPredicate.BoundaryInterior] = 1; + return; + } + + if (m_matrix[MatrixPredicate.BoundaryInterior] != 0) { + if (m_topo_graph.getHalfEdgeUserIndex(m_topo_graph + .getHalfEdgePrev(m_topo_graph.getHalfEdgeTwin(half_edge)), + m_visited_index) != 1) { + int cluster = m_topo_graph.getHalfEdgeTo(half_edge); + int clusterParentage = m_topo_graph + .getClusterParentage(cluster); + + if ((clusterParentage & id_a) != 0) { + int index = m_topo_graph.getClusterUserIndex(cluster, + cluster_index_b); + + if ((clusterParentage & id_b) != 0 && (index % 2 == 0)) { + assert (index != -1); + m_matrix[MatrixPredicate.BoundaryInterior] = 0; + } + } + } + } + } + + // Relational predicate to determine if the boundary of area A intersects + // with the boundary of Line B. + private void boundaryAreaBoundaryLine_(int half_edge, int id_a, int id_b, + int cluster_index_b) { + if (m_matrix[MatrixPredicate.BoundaryBoundary] == 0) + return; + + if (m_topo_graph.getHalfEdgeUserIndex(m_topo_graph + .getHalfEdgePrev(m_topo_graph.getHalfEdgeTwin(half_edge)), + m_visited_index) != 1) { + int cluster = m_topo_graph.getHalfEdgeTo(half_edge); + int clusterParentage = m_topo_graph.getClusterParentage(cluster); + + if ((clusterParentage & id_a) != 0) { + int index = m_topo_graph.getClusterUserIndex(cluster, + cluster_index_b); + + if ((clusterParentage & id_b) != 0 && (index % 2 != 0)) { + assert (index != -1); + m_matrix[MatrixPredicate.BoundaryBoundary] = 0; + } + } + } + } + + // Relational predicate to determine if the boundary of area A intersects + // with the exterior of Line B. + private void boundaryAreaExteriorLine_(int half_edge, int id_a, int id_b) { + if (m_matrix[MatrixPredicate.BoundaryExterior] == 1) + return; + + int parentage = m_topo_graph.getHalfEdgeParentage(half_edge); + + if ((parentage & id_a) != 0 && (parentage & id_b) == 0) + m_matrix[MatrixPredicate.BoundaryExterior] = 1; + + } + + // Relational predicate to determine if the exterior of area A intersects + // with the interior of Line B. + private void exteriorAreaInteriorLine_(int half_edge, int id_a) { + if (m_matrix[MatrixPredicate.ExteriorInterior] == 1) + return; + + int faceParentage = m_topo_graph.getHalfEdgeFaceParentage(half_edge); + int twinFaceParentage = m_topo_graph + .getHalfEdgeFaceParentage(m_topo_graph + .getHalfEdgeTwin(half_edge)); + + if ((faceParentage & id_a) == 0 && (twinFaceParentage & id_a) == 0) + m_matrix[MatrixPredicate.ExteriorInterior] = 1; + } + + // Relational predicate to determine if the exterior of area A intersects + // with the boundary of Line B. + private void exteriorAreaBoundaryLine_(int half_edge, int id_a, int id_b, + int cluster_index_b) { + if (m_matrix[MatrixPredicate.ExteriorBoundary] == 0) + return; + + if (m_topo_graph.getHalfEdgeUserIndex(m_topo_graph + .getHalfEdgePrev(m_topo_graph.getHalfEdgeTwin(half_edge)), + m_visited_index) != 1) { + int cluster = m_topo_graph.getHalfEdgeTo(half_edge); + int clusterParentage = m_topo_graph.getClusterParentage(cluster); + + if ((clusterParentage & id_a) == 0) { + int faceParentage = m_topo_graph + .getHalfEdgeFaceParentage(half_edge); + + if ((faceParentage & id_a) == 0) { + assert ((m_topo_graph.getHalfEdgeParentage(m_topo_graph + .getHalfEdgeTwin(half_edge)) & id_a) == 0); + + int index = m_topo_graph.getClusterUserIndex(cluster, + cluster_index_b); + + if ((clusterParentage & id_b) != 0 && (index % 2 != 0)) { + assert (index != -1); + m_matrix[MatrixPredicate.ExteriorBoundary] = 0; + } + } + } + } + } + + // Relational predicate to determine if the interior of Line A intersects + // with the interior of Line B. + private void interiorLineInteriorLine_(int half_edge, int id_a, int id_b, + int cluster_index_a, int cluster_index_b) { + if (m_matrix[MatrixPredicate.InteriorInterior] == 1) + return; + + int parentage = m_topo_graph.getHalfEdgeParentage(half_edge); + + if ((parentage & id_a) != 0 && (parentage & id_b) != 0) { + m_matrix[MatrixPredicate.InteriorInterior] = 1; + return; + } + + if (m_matrix[MatrixPredicate.InteriorInterior] != 0) { + if (m_topo_graph.getHalfEdgeUserIndex(m_topo_graph + .getHalfEdgePrev(m_topo_graph.getHalfEdgeTwin(half_edge)), + m_visited_index) != 1) { + int cluster = m_topo_graph.getHalfEdgeTo(half_edge); + int clusterParentage = m_topo_graph + .getClusterParentage(cluster); + + if ((clusterParentage & id_a) != 0 + && (clusterParentage & id_b) != 0) { + int index_a = m_topo_graph.getClusterUserIndex(cluster, + cluster_index_a); + int index_b = m_topo_graph.getClusterUserIndex(cluster, + cluster_index_b); + assert (index_a != -1); + assert (index_b != -1); + + if ((index_a % 2 == 0) && (index_b % 2 == 0)) { + assert ((m_topo_graph.getClusterParentage(cluster) & id_a) != 0 && (m_topo_graph + .getClusterParentage(cluster) & id_b) != 0); + m_matrix[MatrixPredicate.InteriorInterior] = 0; + } + } + } + } + } + + // Relational predicate to determine of the interior of LineA intersects + // with the boundary of Line B. + private void interiorLineBoundaryLine_(int half_edge, int id_a, int id_b, + int cluster_index_a, int cluster_index_b, int predicate) { + if (m_matrix[predicate] == 0) + return; + + if (m_topo_graph.getHalfEdgeUserIndex(m_topo_graph + .getHalfEdgePrev(m_topo_graph.getHalfEdgeTwin(half_edge)), + m_visited_index) != 1) { + int cluster = m_topo_graph.getHalfEdgeTo(half_edge); + int clusterParentage = m_topo_graph.getClusterParentage(cluster); + + if ((clusterParentage & id_a) != 0 + && (clusterParentage & id_b) != 0) { + int index_a = m_topo_graph.getClusterUserIndex(cluster, + cluster_index_a); + int index_b = m_topo_graph.getClusterUserIndex(cluster, + cluster_index_b); + assert (index_a != -1); + assert (index_b != -1); + + if ((index_a % 2 == 0) && (index_b % 2 != 0)) { + assert ((m_topo_graph.getClusterParentage(cluster) & id_a) != 0 && (m_topo_graph + .getClusterParentage(cluster) & id_b) != 0); + m_matrix[predicate] = 0; + } + } + } + } + + // Relational predicate to determine if the interior of Line A intersects + // with the exterior of Line B. + private void interiorLineExteriorLine_(int half_edge, int id_a, int id_b, + int predicate) { + if (m_matrix[predicate] == 1) + return; + + int parentage = m_topo_graph.getHalfEdgeParentage(half_edge); + + if ((parentage & id_a) != 0 && (parentage & id_b) == 0) + m_matrix[predicate] = 1; + } + + // Relational predicate to determine if the boundary of Line A intersects + // with the boundary of Line B. + private void boundaryLineBoundaryLine_(int half_edge, int id_a, int id_b, + int cluster_index_a, int cluster_index_b) { + if (m_matrix[MatrixPredicate.BoundaryBoundary] == 0) + return; + + if (m_topo_graph.getHalfEdgeUserIndex(m_topo_graph + .getHalfEdgePrev(m_topo_graph.getHalfEdgeTwin(half_edge)), + m_visited_index) != 1) { + int cluster = m_topo_graph.getHalfEdgeTo(half_edge); + int clusterParentage = m_topo_graph.getClusterParentage(cluster); + + if ((clusterParentage & id_a) != 0 + && (clusterParentage & id_b) != 0) { + int index_a = m_topo_graph.getClusterUserIndex(cluster, + cluster_index_a); + int index_b = m_topo_graph.getClusterUserIndex(cluster, + cluster_index_b); + assert (index_a != -1); + assert (index_b != -1); + + if ((index_a % 2 != 0) && (index_b % 2 != 0)) { + assert ((m_topo_graph.getClusterParentage(cluster) & id_a) != 0 && (m_topo_graph + .getClusterParentage(cluster) & id_b) != 0); + m_matrix[MatrixPredicate.BoundaryBoundary] = 0; + } + } + } + } + + // Relational predicate to determine if the boundary of Line A intersects + // with the exterior of Line B. + private void boundaryLineExteriorLine_(int half_edge, int id_a, int id_b, + int cluster_index_a, int predicate) { + if (m_matrix[predicate] == 0) + return; + + if (m_topo_graph.getHalfEdgeUserIndex(m_topo_graph + .getHalfEdgePrev(m_topo_graph.getHalfEdgeTwin(half_edge)), + m_visited_index) != 1) { + int cluster = m_topo_graph.getHalfEdgeTo(half_edge); + int clusterParentage = m_topo_graph.getClusterParentage(cluster); + + if ((clusterParentage & id_b) == 0) { + int index = m_topo_graph.getClusterUserIndex(cluster, + cluster_index_a); + assert (index != -1); + + if (index % 2 != 0) { + assert ((m_topo_graph.getClusterParentage(cluster) & id_a) != 0); + m_matrix[predicate] = 0; + } + } + } + } + + // Relational predicate to determine if the interior of area A intersects + // with the interior of Point B. + private void interiorAreaInteriorPoint_(int cluster, int id_a) { + if (m_matrix[MatrixPredicate.InteriorInterior] == 0) + return; + + int clusterParentage = m_topo_graph.getClusterParentage(cluster); + + if ((clusterParentage & id_a) == 0) { + int chain = m_topo_graph.getClusterChain(cluster); + int chainParentage = m_topo_graph.getChainParentage(chain); + + if ((chainParentage & id_a) != 0) { + m_matrix[MatrixPredicate.InteriorInterior] = 0; + } + } + } + + // Relational predicate to determine if the boundary of area A intersects + // with the interior of Point B. + private void boundaryAreaInteriorPoint_(int cluster, int id_a, int id_b) { + if (m_matrix[MatrixPredicate.BoundaryInterior] == 0) + return; + + int clusterParentage = m_topo_graph.getClusterParentage(cluster); + + if ((clusterParentage & id_a) != 0 && (clusterParentage & id_b) != 0) { + m_matrix[MatrixPredicate.BoundaryInterior] = 0; + } + } + + // Relational predicate to determine if the exterior of area A intersects + // with the interior of Point B. + private void exteriorAreaInteriorPoint_(int cluster, int id_a) { + if (m_matrix[MatrixPredicate.ExteriorInterior] == 0) + return; + + int clusterParentage = m_topo_graph.getClusterParentage(cluster); + + if ((clusterParentage & id_a) == 0) { + int chain = m_topo_graph.getClusterChain(cluster); + int chainParentage = m_topo_graph.getChainParentage(chain); + + if ((chainParentage & id_a) == 0) { + m_matrix[MatrixPredicate.ExteriorInterior] = 0; + } + } + } + + // Relational predicate to determine if the interior of Line A intersects + // with the interior of Point B. + private void interiorLineInteriorPoint_(int cluster, int id_a, int id_b, + int cluster_index_a) { + if (m_matrix[MatrixPredicate.InteriorInterior] == 0) + return; + + int clusterParentage = m_topo_graph.getClusterParentage(cluster); + + if ((clusterParentage & id_a) != 0 && (clusterParentage & id_b) != 0) { + int index = m_topo_graph.getClusterUserIndex(cluster, + cluster_index_a); + + if (index % 2 == 0) { + m_matrix[MatrixPredicate.InteriorInterior] = 0; + } + } + } + + // Relational predicate to determine if the boundary of Line A intersects + // with the interior of Point B. + private void boundaryLineInteriorPoint_(int cluster, int id_a, int id_b, + int cluster_index_a) { + if (m_matrix[MatrixPredicate.BoundaryInterior] == 0) + return; + + int clusterParentage = m_topo_graph.getClusterParentage(cluster); + + if ((clusterParentage & id_a) != 0 && (clusterParentage & id_b) != 0) { + int index = m_topo_graph.getClusterUserIndex(cluster, + cluster_index_a); + + if (index % 2 != 0) { + m_matrix[MatrixPredicate.BoundaryInterior] = 0; + } + } + } + + // Relational predicate to determine if the boundary of Line A intersects + // with the exterior of Point B. + private void boundaryLineExteriorPoint_(int cluster, int id_a, int id_b, + int cluster_index_a) { + if (m_matrix[MatrixPredicate.BoundaryExterior] == 0) + return; + + int clusterParentage = m_topo_graph.getClusterParentage(cluster); + + if ((clusterParentage & id_a) != 0 && (clusterParentage & id_b) == 0) { + int index = m_topo_graph.getClusterUserIndex(cluster, + cluster_index_a); + + if (index % 2 != 0) { + m_matrix[MatrixPredicate.BoundaryExterior] = 0; + } + } + } + + // Relational predicate to determine if the exterior of Line A intersects + // with the interior of Point B. + private void exteriorLineInteriorPoint_(int cluster, int id_a, int id_b) { + if (m_matrix[MatrixPredicate.ExteriorInterior] == 0) + return; + + int clusterParentage = m_topo_graph.getClusterParentage(cluster); + + if ((clusterParentage & id_a) == 0 && (clusterParentage & id_b) != 0) { + m_matrix[MatrixPredicate.ExteriorInterior] = 0; + } + } + + // Relational predicate to determine if the interior of Point A intersects + // with the interior of Point B. + private void interiorPointInteriorPoint_(int cluster, int id_a, int id_b) { + if (m_matrix[MatrixPredicate.InteriorInterior] == 0) + return; + + int clusterParentage = m_topo_graph.getClusterParentage(cluster); + + if ((clusterParentage & id_a) != 0 && (clusterParentage & id_b) != 0) { + m_matrix[MatrixPredicate.InteriorInterior] = 0; + } + } + + // Relational predicate to determine if the interior of Point A intersects + // with the exterior of Point B. + private void interiorPointExteriorPoint_(int cluster, int id_a, int id_b, + int predicate) { + if (m_matrix[predicate] == 0) + return; + + int clusterParentage = m_topo_graph.getClusterParentage(cluster); + + if ((clusterParentage & id_a) != 0 && (clusterParentage & id_b) == 0) { + m_matrix[predicate] = 0; + } + } + + // Computes the 9 intersection relationships of boundary, interior, and + // exterior of geometry_a vs geometry_b using the Topo_graph for area/area, + // area/Line, and Line/Line relations + private void computeMatrixTopoGraphHalfEdges_(int geometry_a, int geometry_b) { + boolean bRelationKnown = false; + + int id_a = m_topo_graph.getGeometryID(geometry_a); + int id_b = m_topo_graph.getGeometryID(geometry_b); + + m_visited_index = m_topo_graph.createUserIndexForHalfEdges(); + + for (int cluster = m_topo_graph.getFirstCluster(); cluster != -1; cluster = m_topo_graph + .getNextCluster(cluster)) { + int first_half_edge = m_topo_graph.getClusterHalfEdge(cluster); + int next_half_edge = first_half_edge; + + do { + int half_edge = next_half_edge; + int visited = m_topo_graph.getHalfEdgeUserIndex(half_edge, + m_visited_index); + + if (visited != 1) { + do { + // Invoke relational predicates + switch (m_predicates) { + case Predicates.AreaAreaPredicates: + bRelationKnown = areaAreaPredicates_(half_edge, + id_a, id_b); + break; + case Predicates.AreaLinePredicates: + bRelationKnown = areaLinePredicates_(half_edge, + id_a, id_b); + break; + case Predicates.LineLinePredicates: + bRelationKnown = lineLinePredicates_(half_edge, + id_a, id_b); + break; + default: + throw new GeometryException("internal error"); + } + + if (bRelationKnown) + break; + + m_topo_graph.setHalfEdgeUserIndex(half_edge, + m_visited_index, 1); + half_edge = m_topo_graph.getHalfEdgeNext(half_edge); + } while (half_edge != next_half_edge && !bRelationKnown); + } + + if (bRelationKnown) + break; + + next_half_edge = m_topo_graph.getHalfEdgeNext(m_topo_graph + .getHalfEdgeTwin(half_edge)); + } while (next_half_edge != first_half_edge); + + if (bRelationKnown) + break; + } + + if (!bRelationKnown) + setRemainingPredicatesToFalse_(); + + m_topo_graph.deleteUserIndexForHalfEdges(m_visited_index); + } + + // Computes the 9 intersection relationships of boundary, interior, and + // exterior of geometry_a vs geometry_b using the Topo_graph for area/Point, + // Line/Point, and Point/Point relations + private void computeMatrixTopoGraphClusters_(int geometry_a, int geometry_b) { + boolean bRelationKnown = false; + + int id_a = m_topo_graph.getGeometryID(geometry_a); + int id_b = m_topo_graph.getGeometryID(geometry_b); + + for (int cluster = m_topo_graph.getFirstCluster(); cluster != -1; cluster = m_topo_graph + .getNextCluster(cluster)) { + // Invoke relational predicates + switch (m_predicates) { + case Predicates.AreaPointPredicates: + bRelationKnown = areaPointPredicates_(cluster, id_a, id_b); + break; + case Predicates.LinePointPredicates: + bRelationKnown = linePointPredicates_(cluster, id_a, id_b); + break; + case Predicates.PointPointPredicates: + bRelationKnown = pointPointPredicates_(cluster, id_a, id_b); + break; + default: + throw new GeometryException("internal error"); + } + + if (bRelationKnown) + break; + } + + if (!bRelationKnown) + setRemainingPredicatesToFalse_(); + } + + // Call this method to set the edit shape, if the edit shape has been + // cracked and clustered already. + private void setEditShape_(EditShape shape) { + m_topo_graph.setEditShape(shape, null); + } + + // Call this method to set the edit shape, if the edit shape has not been + // cracked and clustered already. + private void setEditShapeCrackAndCluster_(EditShape shape, + double tolerance, ProgressTracker progress_tracker) { + CrackAndCluster.execute(shape, tolerance, progress_tracker); + for (int geometry = shape.getFirstGeometry(); geometry != -1; geometry = shape + .getNextGeometry(geometry)) { + if (shape.getGeometryType(geometry) == Geometry.GeometryType.Polygon) + Simplificator.execute(shape, geometry, -1); + } + setEditShape_(shape); + } + + // Upgrades the geometry to a feature geometry. + private static Geometry convertGeometry_(Geometry geometry, double tolerance) { + int gt = geometry.getType().value(); + + if (Geometry.isSegment(gt)) { + Polyline polyline = new Polyline(geometry.getDescription()); + polyline.addSegment((Segment) geometry, true); + return polyline; + } + + if (gt == Geometry.GeometryType.Envelope) { + Envelope envelope = (Envelope) (geometry); + Envelope2D env = new Envelope2D(); + geometry.queryEnvelope2D(env); + + if (env.getHeight() <= tolerance && env.getWidth() <= tolerance) {// treat + // as + // point + Point point = new Point(geometry.getDescription()); + envelope.getCenter(point); + return point; + } + + if (env.getHeight() <= tolerance || env.getWidth() <= tolerance) {// treat + // as + // line + Polyline polyline = new Polyline(geometry.getDescription()); + Point p = new Point(); + envelope.queryCornerByVal(0, p); + polyline.startPath(p); + envelope.queryCornerByVal(2, p); + polyline.lineTo(p); + return polyline; + } + + // treat as polygon + Polygon polygon = new Polygon(geometry.getDescription()); + polygon.addEnvelope(envelope, false); + return polygon; + } + + return geometry; + } +} diff --git a/src/com/esri/core/geometry/RingOrientationFixer.java b/src/com/esri/core/geometry/RingOrientationFixer.java new file mode 100644 index 00000000..21ed6606 --- /dev/null +++ b/src/com/esri/core/geometry/RingOrientationFixer.java @@ -0,0 +1,529 @@ +/* + Copyright 1995-2013 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ +package com.esri.core.geometry; + +class RingOrientationFixer { + EditShape m_shape; + Treap m_AET; + double m_y_scanline; + int m_geometry; + int m_unknown_ring_orientation_count; + IndexMultiDCList m_sorted_vertices; + AttributeStreamOfInt32 m_unknown_nodes; + int m_node_1_user_index; + int m_node_2_user_index; + int m_path_orientation_index; + int m_path_parentage_index; + + static final class Edges { + EditShape m_shape; + AttributeStreamOfInt32 m_end_1_nodes; + AttributeStreamOfInt32 m_end_2_nodes; + AttributeStreamOfInt8 m_directions; + Point2D pt_1 = new Point2D(); + Point2D pt_2 = new Point2D(); + int m_first_free; + + boolean getDirection_(int index) { + return m_shape.getNextVertex(getEnd1(index)) == getEnd2(index); + } + + int getEnd_(int index) { + int v_1 = getEnd1(index); + int v_2 = getEnd2(index); + if (m_shape.getNextVertex(v_1) == v_2) + return v_2; + else + return v_1; + } + + Edges(EditShape shape) { + m_shape = shape; + m_first_free = -1; + } + + Segment getSegment(int index) { + return m_shape.getSegment(getStart(index)); + } + + // True if the start vertex is the lower point of the edge. + boolean isBottomUp(int index) { + int v_1 = getEnd1(index); + int v_2 = getEnd2(index); + if (m_shape.getPrevVertex(v_1) == v_2) { + int temp = v_1; + v_1 = v_2; + v_2 = temp; + } + m_shape.getXY(v_1, pt_1); + m_shape.getXY(v_2, pt_2); + return pt_1.y < pt_2.y; + } + + int getStart(int index) { + int v_1 = getEnd1(index); + int v_2 = getEnd2(index); + return (m_shape.getNextVertex(v_1) == v_2) ? v_1 : v_2; + } + + int getEnd1(int index) { + return m_end_1_nodes.get(index); + } + + int getEnd2(int index) { + return m_end_2_nodes.get(index); + } + + void freeEdge(int edge) { + m_end_1_nodes.set(edge, m_first_free); + m_first_free = edge; + } + + int newEdge(int vertex) { + if (m_first_free != -1) { + int index = m_first_free; + m_first_free = m_end_1_nodes.get(index); + m_end_1_nodes.set(index, vertex); + m_end_2_nodes.set(index, m_shape.getNextVertex(vertex)); + return index; + } else if (m_end_1_nodes == null) { + m_end_1_nodes = new AttributeStreamOfInt32(0); + m_end_2_nodes = new AttributeStreamOfInt32(0); + } + + int index = m_end_1_nodes.size(); + m_end_1_nodes.add(vertex); + m_end_2_nodes.add(m_shape.getNextVertex(vertex)); + return index; + } + + EditShape getShape() { + return m_shape; + } + + int getPath(int index) { + return m_shape.getPathFromVertex(getEnd1(index)); + } + } + + Edges m_edges; + + class RingOrientationTestComparator extends Treap.Comparator { + RingOrientationFixer m_helper; + Line m_line_1; + Line m_line_2; + int m_left_elm; + double m_leftx; + Segment m_seg_1; + + RingOrientationTestComparator(RingOrientationFixer helper) { + m_helper = helper; + m_line_1 = new Line(); + m_line_2 = new Line(); + m_leftx = 0; + m_seg_1 = null; + m_left_elm = -1; + } + + @Override + int compare(Treap treap, int left, int node) { + int right = treap.getElement(node); + RingOrientationFixer.Edges edges = m_helper.m_edges; + double x_1; + if (m_left_elm == left) + x_1 = m_leftx; + else { + m_seg_1 = edges.getSegment(left); + if (m_seg_1 == null) { + EditShape shape = edges.getShape(); + shape.queryLineConnector(edges.getStart(left), m_line_1); + m_seg_1 = m_line_1; + x_1 = m_line_1.intersectionOfYMonotonicWithAxisX( + m_helper.m_y_scanline, 0); + } else + x_1 = m_seg_1.intersectionOfYMonotonicWithAxisX( + m_helper.m_y_scanline, 0); + + m_leftx = x_1; + m_left_elm = left; + } + + Segment seg_2 = edges.getSegment(right); + double x2; + if (seg_2 == null) { + EditShape shape = edges.getShape(); + shape.queryLineConnector(edges.getStart(right), m_line_2); + seg_2 = m_line_2; + x2 = m_line_2.intersectionOfYMonotonicWithAxisX( + m_helper.m_y_scanline, 0); + } else + x2 = seg_2.intersectionOfYMonotonicWithAxisX( + m_helper.m_y_scanline, 0); + + if (x_1 == x2) { + boolean bStartLower1 = edges.isBottomUp(left); + boolean bStartLower2 = edges.isBottomUp(right); + + // apparently these edges originate from same vertex and the + // scanline is on the vertex. move scanline a little. + double y1 = !bStartLower1 ? m_seg_1.getStartY() : m_seg_1 + .getEndY(); + double y2 = !bStartLower2 ? seg_2.getStartY() : seg_2.getEndY(); + double miny = Math.min(y1, y2); + double y = (miny + m_helper.m_y_scanline) * 0.5; + if (y == m_helper.m_y_scanline) { + // assert(0);//ST: not a bug. just curious to see this + // happens. + y = miny; // apparently, one of the segments is almost + // horizontal line. + } + x_1 = m_seg_1.intersectionOfYMonotonicWithAxisX(y, 0); + x2 = seg_2.intersectionOfYMonotonicWithAxisX(y, 0); + assert (x_1 != x2); + } + + return x_1 < x2 ? -1 : (x_1 > x2 ? 1 : 0); + } + + void reset() { + m_left_elm = -1; + } + } + + RingOrientationTestComparator m_sweep_comparator; + + RingOrientationFixer() { + m_AET = new Treap(); + m_AET.disableBalancing(); + m_sweep_comparator = new RingOrientationTestComparator(this); + m_AET.setComparator(m_sweep_comparator); + } + + boolean fixRingOrientation_() { + if (m_shape.getPathCount(m_geometry) == 1) { + int path = m_shape.getFirstPath(m_geometry); + double area = m_shape.getRingArea(path); + m_shape.setExterior(path, true); + if (area < 0) { + int first = m_shape.getFirstVertex(path); + m_shape.reverseRingInternal_(first); + m_shape.setLastVertex_(path, m_shape.getPrevVertex(first));// fix + // last + // after + // the + // reverse + return true; + } + + return false; + } + + m_path_orientation_index = m_shape.createPathUserIndex();// used to + // store + // discovered + // orientation + // (3 - + // extrior, + // 2 - + // interior) + m_path_parentage_index = m_shape.createPathUserIndex();// used to + // resolve OGC + // order + for (int path = m_shape.getFirstPath(m_geometry); path != -1; path = m_shape + .getNextPath(path)) { + m_shape.setPathUserIndex(path, m_path_orientation_index, 0); + m_shape.setPathUserIndex(path, m_path_parentage_index, -1); + } + + AttributeStreamOfInt32 bunch = new AttributeStreamOfInt32(0); + boolean bFound = false; + m_y_scanline = NumberUtils.TheNaN; + Point2D pt = new Point2D(); + m_unknown_ring_orientation_count = m_shape.getPathCount(m_geometry); + m_node_1_user_index = m_shape.createUserIndex(); + m_node_2_user_index = m_shape.createUserIndex(); + for (int ivertex = m_sorted_vertices.getFirst(m_sorted_vertices + .getFirstList()); ivertex != -1; ivertex = m_sorted_vertices + .getNext(ivertex)) { + int vertex = m_sorted_vertices.getData(ivertex); + m_shape.getXY(vertex, pt); + if (pt.y != m_y_scanline && bunch.size() != 0) { + bFound |= processBunchForRingOrientationTest_(bunch); + m_sweep_comparator.reset(); + bunch.clear(false); + } + + bunch.add(vertex);// all vertices that have same y are added to the + // bunch + m_y_scanline = pt.y; + if (m_unknown_ring_orientation_count == 0) + break; + } + + if (m_unknown_ring_orientation_count > 0) { + bFound |= processBunchForRingOrientationTest_(bunch); + bunch.clear(false); + } + + m_shape.removeUserIndex(m_node_1_user_index); + m_shape.removeUserIndex(m_node_2_user_index); + + // dbg_verify_ring_orientation_();//debug + + for (int path = m_shape.getFirstPath(m_geometry); path != -1;) { + if (m_shape.getPathUserIndex(path, m_path_orientation_index) == 3) {// exterior + m_shape.setExterior(path, true); + int afterPath = path; + for (int nextHole = m_shape.getPathUserIndex(path, + m_path_parentage_index); nextHole != -1;) { + int p = m_shape.getPathUserIndex(nextHole, + m_path_parentage_index); + m_shape.movePath(m_geometry, + m_shape.getNextPath(afterPath), nextHole); + afterPath = nextHole; + nextHole = p; + } + path = m_shape.getNextPath(afterPath); + } else { + m_shape.setExterior(path, false); + path = m_shape.getNextPath(path); + } + } + + m_shape.removePathUserIndex(m_path_orientation_index); + m_shape.removePathUserIndex(m_path_parentage_index); + + return bFound; + } + + boolean processBunchForRingOrientationTest_(AttributeStreamOfInt32 bunch) { + return processBunchForRingOrientationTestOddEven_(bunch); + } + + boolean processBunchForRingOrientationTestOddEven_( + AttributeStreamOfInt32 bunch) { + boolean bModified = false; + if (m_edges == null) + m_edges = new Edges(m_shape); + + if (m_unknown_nodes == null) { + m_unknown_nodes = new AttributeStreamOfInt32(0); + m_unknown_nodes.reserve(16); + } else { + m_unknown_nodes.clear(false); + } + + processBunchForRingOrientationRemoveEdges_(bunch); + + // add edges that come into scope + for (int i = 0, n = bunch.size(); i < n; i++) { + int vertex = bunch.get(i); + if (vertex == -1) + continue; + insertEdge_(vertex, -1); + } + + for (int i = 0; i < m_unknown_nodes.size() + && m_unknown_ring_orientation_count > 0; i++) { + int aetNode = m_unknown_nodes.get(i); + int edge = m_AET.getElement(aetNode); + int path = m_edges.getPath(edge); + int orientation = m_shape.getPathUserIndex(path, + m_path_orientation_index); + int prevPath = -1; + if (orientation == 0) { + int node = m_AET.getPrev(aetNode); + int prevNode = aetNode; + boolean odd_even = false; + // find the leftmost edge for which the ring orientation is + // known + while (node != Treap.nullNode()) { + int edge1 = m_AET.getElement(node); + int path1 = m_edges.getPath(edge1); + int orientation1 = m_shape.getPathUserIndex(path1, + m_path_orientation_index); + if (orientation1 != 0) { + prevPath = path1; + break; + } + prevNode = node; + node = m_AET.getPrev(node); + } + if (node == Treap.nullNode()) {// if no edges have ring + // orientation known, then start + // from the left most and it has + // to be exterior ring. + odd_even = true; + node = prevNode; + } else { + int edge1 = m_AET.getElement(node); + odd_even = m_edges.isBottomUp(edge1); + node = m_AET.getNext(node); + odd_even = !odd_even; + } + + do { + int edge1 = m_AET.getElement(node); + int path1 = m_edges.getPath(edge1); + int orientation1 = m_shape.getPathUserIndex(path1, + m_path_orientation_index); + if (orientation1 == 0) { + if (odd_even != m_edges.isBottomUp(edge1)) { + int first = m_shape.getFirstVertex(path1); + m_shape.reverseRingInternal_(first); + m_shape.setLastVertex_(path1, + m_shape.getPrevVertex(first)); + bModified = true; + } + + m_shape.setPathUserIndex(path1, + m_path_orientation_index, odd_even ? 3 : 2); + if (!odd_even) {// link the holes into the linked list + // to mantain the OGC order. + int lastHole = m_shape.getPathUserIndex(prevPath, + m_path_parentage_index); + m_shape.setPathUserIndex(prevPath, + m_path_parentage_index, path1); + m_shape.setPathUserIndex(path1, + m_path_parentage_index, lastHole); + } + + m_unknown_ring_orientation_count--; + if (m_unknown_ring_orientation_count == 0) + return bModified; + } + + prevPath = path1; + prevNode = node; + node = m_AET.getNext(node); + odd_even = !odd_even; + } while (prevNode != aetNode); + } + } + + return bModified; + } + + void processBunchForRingOrientationRemoveEdges_(AttributeStreamOfInt32 bunch) { + // remove all nodes that go out of scope + for (int i = 0, n = bunch.size(); i < n; i++) { + int vertex = bunch.get(i); + int node1 = m_shape.getUserIndex(vertex, m_node_1_user_index); + int node2 = m_shape.getUserIndex(vertex, m_node_2_user_index); + if (node1 != -1) { + int edge = m_AET.getElement(node1); + m_edges.freeEdge(edge); + m_shape.setUserIndex(vertex, m_node_1_user_index, -1); + } + if (node2 != -1) { + int edge = m_AET.getElement(node2); + m_edges.freeEdge(edge); + m_shape.setUserIndex(vertex, m_node_2_user_index, -1); + } + + int reused_node = -1; + if (node1 != -1 && node2 != -1) {// terminating vertex + m_AET.deleteNode(node1, -1); + m_AET.deleteNode(node2, -1); + bunch.set(i, -1); + } else + reused_node = node1 != -1 ? node1 : node2; + + if (reused_node != -1) {// this vertex is a part of vertical chain. + // Sorted order in AET did not change, so + // reuse the AET node. + if (!insertEdge_(vertex, reused_node)) + m_AET.deleteNode(reused_node, -1);// horizontal edge was not + // inserted + bunch.set(i, -1); + } + } + } + + boolean insertEdge_(int vertex, int reused_node) { + Point2D pt_1 = new Point2D(); + Point2D pt_2 = new Point2D(); + m_shape.getXY(vertex, pt_1); + int next = m_shape.getNextVertex(vertex); + m_shape.getXY(next, pt_2); + boolean b_res = false; + if (pt_1.y < pt_2.y) { + b_res = true; + int edge = m_edges.newEdge(vertex); + int aetNode; + if (reused_node == -1) + aetNode = m_AET.addElement(edge, -1); + else { + aetNode = reused_node; + m_AET.setElement(aetNode, edge); + } + int node = m_shape.getUserIndex(next, m_node_1_user_index); + if (node == -1) + m_shape.setUserIndex(next, m_node_1_user_index, aetNode); + else + m_shape.setUserIndex(next, m_node_2_user_index, aetNode); + + int path = m_shape.getPathFromVertex(vertex); + if (m_shape.getPathUserIndex(path, m_path_orientation_index) == 0) { + m_unknown_nodes.add(aetNode); + } + } + + int prev = m_shape.getPrevVertex(vertex); + m_shape.getXY(prev, pt_2); + if (pt_1.y < pt_2.y) { + b_res = true; + int edge = m_edges.newEdge(prev); + int aetNode; + if (reused_node == -1) + aetNode = m_AET.addElement(edge, -1); + else { + aetNode = reused_node; + m_AET.setElement(aetNode, edge); + } + int node = m_shape.getUserIndex(prev, m_node_1_user_index); + if (node == -1) + m_shape.setUserIndex(prev, m_node_1_user_index, aetNode); + else + m_shape.setUserIndex(prev, m_node_2_user_index, aetNode); + + int path = m_shape.getPathFromVertex(vertex); + if (m_shape.getPathUserIndex(path, m_path_orientation_index) == 0) { + m_unknown_nodes.add(aetNode); + } + } + + return b_res; + } + + static boolean execute(EditShape shape, int geometry, + IndexMultiDCList sorted_vertices) { + RingOrientationFixer fixer = new RingOrientationFixer(); + fixer.m_shape = shape; + fixer.m_geometry = geometry; + fixer.m_sorted_vertices = sorted_vertices; + return fixer.fixRingOrientation_(); + } + +} diff --git a/src/com/esri/core/geometry/Segment.java b/src/com/esri/core/geometry/Segment.java new file mode 100644 index 00000000..9e7a0575 --- /dev/null +++ b/src/com/esri/core/geometry/Segment.java @@ -0,0 +1,981 @@ +/* + Copyright 1995-2013 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ + +package com.esri.core.geometry; + +import com.esri.core.geometry.VertexDescription.Semantics; +import java.io.Serializable; + +/** + * A base class for segments. Presently only Line segments are supported. + */ +public abstract class Segment extends Geometry implements Serializable { + + // UPDATED PORT TO MATCH NATIVE AS OF JAN 30 2011 + private static final long serialVersionUID = 1L; + + double m_xStart; + + double m_yStart; + + double m_xEnd; + + double m_yEnd; + + double[] m_attributes; + + // Header Definitions + /** + * Returns XY coordinates of the start point. + */ + Point2D getStartXY() { + return Point2D.construct(m_xStart, m_yStart); + } + + void getStartXY(Point2D pt) { + pt.x = m_xStart; + pt.y = m_yStart; + } + + /** + * Sets the XY coordinates of the start point. + */ + void setStartXY(Point2D pt) { + _setXY(0, pt); + } + + void setStartXY(double x, double y) { + _setXY(0, Point2D.construct(x, y)); + } + + /** + * Returns XYZ coordinates of the start point. Z if 0 if Z is missing. + */ + Point3D getStartXYZ() { + return _getXYZ(0); + } + + /** + * Sets the XYZ coordinates of the start point. + */ + void setStartXYZ(Point3D pt) { + _setXYZ(0, pt); + } + + void setStartXYZ(double x, double y, double z) { + _setXYZ(0, Point3D.construct(x, y, z)); + } + + /** + * Returns coordinates of the start point in a Point class. + */ + public void queryStart(Point dstPoint) { + _get(0, dstPoint); + } + + /** + * Sets the coordinates of the start point in this segment. + * + * @param srcPoint + * The new start point of this segment. + */ + public void setStart(Point srcPoint) { + _set(0, srcPoint); + } + + /** + * Returns value of the start vertex attribute's ordinate. Throws if the + * Point is empty. + * + * @param semantics + * The attribute semantics. + * @param ordinate + * The attribute's ordinate. For example, the y coordinate of the + * NORMAL has ordinate of 1. + * @return Ordinate value as double. + */ + public double getStartAttributeAsDbl(int semantics, int ordinate) { + return _getAttributeAsDbl(0, semantics, ordinate); + } + + /** + * Returns the value of the start vertex attribute's ordinate. The ordinate + * is always 0 because integer attributes always have one component. + * + * @param semantics + * The attribute semantics. + * @param ordinate + * The attribute's ordinate. For example, the y coordinate of the + * NORMAL has ordinate of 1. + * @return Ordinate value truncated to 32 bit integer. + */ + public int getStartAttributeAsInt(int semantics, int ordinate) { + return _getAttributeAsInt(0, semantics, ordinate); + } + + /** + * Sets the value of the start vertex attribute. + * + * @param semantics + * The attribute semantics. + * @param valueIn + * is the array to write values to. The attribute type and the + * number of elements must match the persistence type, as well as + * the number of components of the attribute. + */ + public void setStartAttribute(int semantics, int ordinate, double value) { + _setAttribute(0, semantics, ordinate, value); + } + + public void setStartAttribute(int semantics, int ordinate, int value) { + _setAttribute(0, semantics, ordinate, value); + } + + /** + * Returns the X coordinate of starting point. + * + * @return The X coordinate of starting point. + */ + public double getStartX() { + return m_xStart; + } + + /** + * Returns the Y coordinate of starting point. + * + * @return The Y coordinate of starting point. + */ + public double getStartY() { + return m_yStart; + } + + /** + * Returns the X coordinate of ending point. + * + * @return The X coordinate of ending point. + */ + public double getEndX() { + return m_xEnd; + } + + /** + * Returns the Y coordinate of ending point. + * + * @return The Y coordinate of ending point. + */ + public double getEndY() { + return m_yEnd; + } + + /** + * Returns XY coordinates of the end point. + * + * @return The XY coordinates of the end point. + */ + Point2D getEndXY() { + return Point2D.construct(m_xEnd, m_yEnd); + } + + void getEndXY(Point2D pt) { + pt.x = m_xEnd; + pt.y = m_yEnd; + } + + /** + * Sets the XY coordinates of the end point. + * + * @param pt + * The end point of the segment. + */ + void setEndXY(Point2D pt) { + _setXY(1, pt); + } + + void setEndXY(double x, double y) { + _setXY(1, Point2D.construct(x, y)); + } + + /** + * Returns XYZ coordinates of the end point. Z if 0 if Z is missing. + * + * @return The XYZ coordinates of the end point. + */ + Point3D getEndXYZ() { + return _getXYZ(1); + } + + /** + * Sets the XYZ coordinates of the end point. + */ + void setEndXYZ(Point3D pt) { + _setXYZ(1, pt); + } + + void setEndXYZ(double x, double y, double z) { + _setXYZ(1, Point3D.construct(x, y, z)); + } + + /** + * Returns coordinates of the end point in this segment. + * + * @param dstPoint + * The end point of this segment. + */ + public void queryEnd(Point dstPoint) { + _get(1, dstPoint); + } + + /** + * Sets the coordinates of the end point in a Point class. + * + * @param srcPoint + * The new end point of this segment. + */ + public void setEnd(Point srcPoint) { + _set(1, srcPoint); + } + + /** + * Returns value of the end vertex attribute's ordinate. Throws if the Point + * is empty. + * + * @param semantics + * The attribute semantics. + * @param ordinate + * The attribute's ordinate. For example, the y coordinate of the + * NORMAL has ordinate of 1. + * @return Ordinate value as double. + */ + public double getEndAttributeAsDbl(int semantics, int ordinate) { + return _getAttributeAsDbl(1, semantics, ordinate); + } + + /** + * Returns the value of the end vertex attribute's ordinate. The ordinate is + * always 0 because integer attributes always have one component. + * + * @param semantics + * The attribute semantics. + * @param ordinate + * The attribute's ordinate. For example, the y coordinate of the + * NORMAL has ordinate of 1. + * @return The ordinate value truncated to 32 bit integer. + */ + public int getEndAttributeAsInt(int semantics, int ordinate) { + return _getAttributeAsInt(1, semantics, ordinate); + } + + /** + * Sets the value of end vertex attribute. + * + * @param semantics + * The attribute semantics. + * @param ordinate + * The attribute's ordinate. + * @param value + * Is the array to write values to. The attribute type and the + * number of elements must match the persistence type, as well as + * the number of components of the attribute. + */ + public void setEndAttribute(int semantics, int ordinate, double value) { + _setAttribute(1, semantics, ordinate, value); + } + + public void setEndAttribute(int semantics, int ordinate, int value) { + _setAttribute(1, semantics, ordinate, value); + } + + @Override + public final int getDimension() { + return 1; + } + + @Override + public final boolean isEmpty() { + return isEmptyImpl(); + } + + @Override + public final void setEmpty() { + + } + + @Override + public double calculateArea2D() { + return 0; + } + + /** + * Calculates intersections of this segment with another segment. + *

+ * Note: This is not a topological operation. It needs to be paired with the + * Segment.Overlap call. + * + * @param other + * The segment to calculate intersection with. + * @param intersectionPoints + * The intersection points. Can be NULL. + * @param paramThis + * The value of the parameter in the intersection points for this + * Segment (between 0 and 1). Can be NULL. + * @param paramOther + * The value of the parameter in the intersection points for the + * other Segment (between 0 and 1). Can be NULL. + * @param tolerance + * The tolerance value for the intersection calculation. Can be + * 0. + * @return The number of intersection points, 0 when no intersection points + * exist. + */ + int intersect(Segment other, Point2D[] intersectionPoints, + double[] paramThis, double[] paramOther, double tolerance) { + return _intersect(other, intersectionPoints, paramThis, paramOther, + tolerance); + } + + /** + * Returns TRUE if this segment intersects with the other segment with the + * given tolerance. + */ + boolean isIntersecting(Segment other, double tolerance) { + return _isIntersecting(other, tolerance, false) != 0; + } + + /** + * Returns TRUE if the point and segment intersect (not disjoint) for the + * given tolerance. + */ + boolean isIntersecting(Point2D pt, double tolerance) { + return _isIntersectingPoint(pt, tolerance, false); + } + + /** + * Non public abstract version of the function. + */ + public boolean isEmptyImpl() { + return false; + } + + // Header Definitions + + // Cpp definitions + /** + * Creates a segment with start and end points (0,0). + */ + public Segment() { + m_xStart = 0; + m_yStart = 0; + m_xEnd = 0; + m_yEnd = 0; + m_attributes = null; + } + + void _resizeAttributes(int newSize) { + _touch(); + if (m_attributes == null && newSize > 0) { + m_attributes = new double[newSize * 2]; + } else if (m_attributes != null && m_attributes.length < newSize * 2) { + double[] newbuffer = new double[newSize * 2]; + System.arraycopy(m_attributes, 0, newbuffer, 0, m_attributes.length); + m_attributes = newbuffer; + } + } + + static void _attributeCopy(double[] src, int srcStart, double[] dst, + int dstStart, int count) { + if (count > 0) + System.arraycopy(src, srcStart, dst, dstStart, count); + } + + private Point2D _getXY(int endPoint) { + Point2D pt = new Point2D(); + if (endPoint != 0) { + pt.setCoords(m_xEnd, m_yEnd); + } else { + pt.setCoords(m_xStart, m_yStart); + } + return pt; + } + + private void _setXY(int endPoint, Point2D pt) { + if (endPoint != 0) { + m_xEnd = pt.x; + m_yEnd = pt.y; + } else { + m_xStart = pt.x; + m_yStart = pt.y; + } + } + + private Point3D _getXYZ(int endPoint) { + Point3D pt = new Point3D(); + if (endPoint != 0) { + pt.x = m_xEnd; + pt.y = m_yEnd; + } else { + pt.x = m_xStart; + pt.y = m_yStart; + } + + if (m_description.hasZ()) + pt.z = m_attributes[_getEndPointOffset(endPoint)]; + else + pt.z = VertexDescription.getDefaultValue(Semantics.Z); + + return pt; + } + + private void _setXYZ(int endPoint, Point3D pt) { + _touch(); + boolean bHasZ = hasAttribute(Semantics.Z); + if (!bHasZ && !VertexDescription.isDefaultValue(Semantics.Z, pt.z)) {// add + // Z + // only + // if + // pt.z + // is + // not + // a + // default + // value. + addAttribute(Semantics.Z); + bHasZ = true; + } + + if (endPoint != 0) { + m_xEnd = pt.x; + m_yEnd = pt.y; + } else { + m_xStart = pt.x; + m_yStart = pt.y; + } + + if (bHasZ) + m_attributes[_getEndPointOffset(endPoint)] = pt.z; + + } + + @Override + void _beforeDropAttributeImpl(int semantics) { + _touch(); + if (isEmptyImpl()) + return; + + // _ASSERT(semantics != enum_value2(VertexDescription, Semantics, + // POSITION)); + int attributeIndex = m_description.getAttributeIndex(semantics); + int offset = m_description._getPointAttributeOffset(attributeIndex) - 2; + int comps = VertexDescription.getComponentCount(semantics); + int totalCompsOld = m_description._getTotalComponents() - 2; + if (totalCompsOld > comps) { + int offset0 = _getEndPointOffset(0); + for (int i = offset + comps; i < totalCompsOld * 2; i++) + m_attributes[offset0 + i - comps] = m_attributes[offset0 + i]; + + int offset1 = _getEndPointOffset(1) - comps; // -comp is for deleted + // attribute of + // start vertex + for (int i = offset + comps; i < totalCompsOld; i++) + m_attributes[offset1 + i - comps] = m_attributes[offset1 + i]; + } + } + + @Override + void _afterAddAttributeImpl(int semantics) { + _touch(); + int attributeIndex = m_description.getAttributeIndex(semantics); + int offset = m_description._getPointAttributeOffset(attributeIndex) - 2; + int comps = VertexDescription.getComponentCount(semantics); + int totalComps = m_description._getTotalComponents() - 2; + _resizeAttributes(totalComps); + int totalCompsOld = totalComps - comps; // the total number of + // components before resize. + + int offset0 = _getEndPointOffset(0); + int offset1 = _getEndPointOffset(1); + int offset1old = offset1 - comps; + for (int i = totalCompsOld - 1; i >= 0; i--) {// correct the position of + // the End attributes + m_attributes[offset1 + i] = m_attributes[offset1old + i]; + } + + // move attributes for start end end points that go after the insertion + // point + for (int i = totalComps - 1; i >= offset + comps; i--) { + m_attributes[offset0 + i] = m_attributes[offset0 + i - comps]; + m_attributes[offset1 + i] = m_attributes[offset1 + i - comps]; + } + + // initialize added attribute to the default value. + double dv = VertexDescription.getDefaultValue(semantics); + for (int i = 0; i < comps; i++) { + m_attributes[offset0 + offset + i] = dv; + m_attributes[offset1 + offset + i] = dv; + } + } + + private void _get(int endPoint, Point outPoint) { + if (isEmptyImpl()) + throw new GeometryException("empty geometry");// ._setToDefault(); + + // FIXME Native has this line repeated twice! Check up on this. + outPoint.assignVertexDescription(m_description); + // outPoint.assignVertexDescription(m_description); + + if (outPoint.isEmptyImpl()) + outPoint._setToDefault(); + + for (int attributeIndex = 0; attributeIndex < m_description + .getAttributeCount(); attributeIndex++) { + int semantics = m_description._getSemanticsImpl(attributeIndex); + for (int icomp = 0, ncomp = VertexDescription + .getComponentCount(semantics); icomp < ncomp; icomp++) { + double v = _getAttributeAsDbl(endPoint, semantics, icomp); + outPoint.setAttribute(semantics, icomp, v); + } + } + } + + private void _set(int endPoint, Point src) { + _touch(); + Point point = src; + + if (src.isEmptyImpl())// can not assign an empty point + throw new GeometryException("empty_Geometry"); + + VertexDescription vdin = point.getDescription(); + for (int attributeIndex = 0, nattrib = vdin.getAttributeCount(); attributeIndex < nattrib; attributeIndex++) { + int semantics = vdin._getSemanticsImpl(attributeIndex); + int ncomp = VertexDescription.getComponentCount(semantics); + for (int icomp = 0; icomp < ncomp; icomp++) { + double v = point.getAttributeAsDbl(semantics, icomp); + _setAttribute(endPoint, semantics, icomp, v); + } + } + } + + double _getAttributeAsDbl(int endPoint, int semantics, int ordinate) { + // FIXME review rohits impl. Only an assertion in native + if (isEmptyImpl()) + throw new GeometryException( + "This operation was performed on an Empty Geometry."); + + if (semantics == Semantics.POSITION) { + if (endPoint != 0) { + return (ordinate != 0) ? m_yEnd : m_xEnd; + } else { + return (ordinate != 0) ? m_yStart : m_xStart; + } + } + + int ncomps = VertexDescription.getComponentCount(semantics); + if (ordinate >= ncomps) + throw new IndexOutOfBoundsException(); + + int attributeIndex = m_description.getAttributeIndex(semantics); + if (attributeIndex >= 0) { + if (m_attributes != null) + _resizeAttributes(m_description._getTotalComponents() - 2); + + return m_attributes[_getEndPointOffset(endPoint) + + m_description._getPointAttributeOffset(attributeIndex) + - 2 + ordinate]; + } else + return VertexDescription.getDefaultValue(semantics); + } + + private int _getAttributeAsInt(int endPoint, int semantics, int ordinate) { + if (isEmptyImpl()) + throw new GeometryException("Empty_Geometry."); + + return (int) _getAttributeAsDbl(endPoint, semantics, ordinate); + } + + void _setAttribute(int endPoint, int semantics, int ordinate, double value) { + _touch(); + int ncomps = VertexDescription.getComponentCount(semantics); + if (ordinate >= ncomps) + throw new IndexOutOfBoundsException(); + + int attributeIndex = m_description.getAttributeIndex(semantics); + if (attributeIndex < 0) { + addAttribute(semantics); + attributeIndex = m_description.getAttributeIndex(semantics); + } + + if (semantics == Semantics.POSITION) { + if (endPoint != 0) + if (ordinate != 0) + m_yEnd = value; + else + m_xEnd = value; + else if (ordinate != 0) + m_yStart = value; + else + m_xStart = value; + return; + } + + if (m_attributes != null) + _resizeAttributes(m_description._getTotalComponents() - 2); + + m_attributes[_getEndPointOffset(endPoint) + + m_description._getPointAttributeOffset(attributeIndex) - 2 + + ordinate] = value; + + } + + void _setAttribute(int endPoint, int semantics, int ordinate, int value) { + _setAttribute(endPoint, semantics, ordinate, (double) value); + } + + @Override + public void copyTo(Geometry dst) { + if (dst.getType() != getType()) + throw new IllegalArgumentException(); + + Segment segDst = (Segment) dst; + segDst.m_description = m_description; + segDst._resizeAttributes(m_description._getTotalComponents() - 2); + _attributeCopy(m_attributes, 0, segDst.m_attributes, 0, + (m_description._getTotalComponents() - 2) * 2); + segDst.m_xStart = m_xStart; + segDst.m_yStart = m_yStart; + segDst.m_xEnd = m_xEnd; + segDst.m_yEnd = m_yEnd; + dst._touch(); + + _copyToImpl(segDst); + } + + @Override + public Envelope1D queryInterval(int semantics, int ordinate) { + Envelope1D env = new Envelope1D(); + if (isEmptyImpl()) { + env.setEmpty(); + return env; + } + + env.vmin = _getAttributeAsDbl(0, semantics, ordinate); + env.vmax = env.vmin; + env.mergeNE(_getAttributeAsDbl(1, semantics, ordinate)); + return env; + } + + void queryCoord(double t, Point point) { + point.assignVertexDescription(m_description); + point.setXY(getCoord2D(t)); + for (int iattrib = 1, nattrib = m_description.getAttributeCount(); iattrib < nattrib; iattrib++) { + int semantics = m_description._getSemanticsImpl(iattrib); + int ncomp = VertexDescription.getComponentCount(semantics); + for (int iord = 0; iord < ncomp; iord++) { + double value = getAttributeAsDbl(t, semantics, iord); + point.setAttribute(semantics, iord, value); + } + } + } + + boolean _equalsImpl(Segment other) { + if (m_description != other.m_description) + return false; + + if (m_xStart != other.m_xStart || m_xEnd != other.m_xEnd + || m_yStart != other.m_yStart || m_yEnd != other.m_yEnd) + return false; + for (int i = 0; i < (m_description._getTotalComponents() - 2) * 2; i++) + if (m_attributes[i] != other.m_attributes[i]) + return false; + + return true; + } + + /** + * Returns true, when this segment is a closed curve (start point is equal + * to end point exactly). + * + * Note, this will return true for lines, that are degenerate to a point + * too. + */ + boolean isClosed() { + return m_xStart == m_xEnd && m_yStart == m_yEnd; + } + + void reverse() { + _reverseImpl(); + // because java doesn't support passing value types + // by reference numberutils swap won't work + // NumberUtils.swap(m_xStart, m_xEnd); + // NumberUtils.swap(m_yStart, m_yEnd); + double origxStart = m_xStart; + double origxEnd = m_xEnd; + m_xStart = origxEnd; + m_xEnd = origxStart; + double origyStart = m_yStart; + double origyEnd = m_yEnd; + m_yStart = origyEnd; + m_yEnd = origyStart; + + for (int i = 1, n = m_description.getAttributeCount(); i < n; i++) { + // FIXME fix stupid semantics enum + int semantics = m_description.getSemantics(i);// VertexDescription.Semantics + // semantics = + // m_description.getSemantics(i); + for (int iord = 0, nord = VertexDescription + .getComponentCount(semantics); iord < nord; iord++) { + double v1 = _getAttributeAsDbl(0, semantics, iord); + double v2 = _getAttributeAsDbl(1, semantics, iord); + _setAttribute(0, semantics, iord, v2); + _setAttribute(1, semantics, iord, v1); + } + } + } + + int _isIntersecting(Segment other, double tolerance, + boolean bExcludeExactEndpoints) { + int gtThis = getType().value(); + int gtOther = other.getType().value(); + switch (gtThis) { + case Geometry.GeometryType.Line: + if (gtOther == Geometry.GeometryType.Line) + return Line._isIntersectingLineLine((Line) this, (Line) other, + tolerance, bExcludeExactEndpoints); + else + throw new GeometryException("internal error"); + default: + throw new GeometryException("internal error"); + } + } + + int _intersect(Segment other, Point2D[] intersectionPoints, + double[] paramThis, double[] paramOther, double tolerance) { + int gtThis = getType().value(); + int gtOther = other.getType().value(); + switch (gtThis) { + case Geometry.GeometryType.Line: + if (gtOther == Geometry.GeometryType.Line) + return Line._intersectLineLine((Line) this, (Line) other, + intersectionPoints, paramThis, paramOther, tolerance); + else + throw new GeometryException("internal error"); + default: + throw new GeometryException("internal error"); + } + } + + /** + * A helper function for area calculation. Calculates the Integral(y(t) * + * x'(t) * dt) for t = [0, 1]. The area of a ring is caluclated as a sum of + * the results of CalculateArea2DHelper. + */ + abstract double _calculateArea2DHelper(double xorg, double yorg); + + int _getEndPointOffset(int endPoint) { + return endPoint * (m_description._getTotalComponents() - 2); + } + + /** + * Returns the coordinate of the point on this segment for the given + * parameter value. + */ + Point2D getCoord2D(double t) { + Point2D pt = new Point2D(); + getCoord2D(t, pt); + return pt; + } + + /** + * Returns the coordinate of the point on this segment for the given + * parameter value (segments are parametric curves). + * + * @param t + * the parameter coordinate along the segment from 0.0 to 1.0. + * Value of 0 returns the start point, 1 returns end point. + * @param dst + * the coordinate where result will be placed. + */ + abstract void getCoord2D(double t, Point2D dst); + + /** + * Finds a closest coordinate on this segment. + * + * @param inputPoint + * The 2D point to find the closest coordinate on this segment. + * @param bExtrapolate + * TRUE if the segment is extrapolated at the end points along + * the end point tangents. Otherwise the result is limited to + * values between 0 and 1. + * @return The parametric coordinate t on the segment (0 corresponds to the + * start point, 1 corresponds to the end point). Use getCoord2D to + * obtain the 2D coordinate on the segment from t. To find the + * distance, call (inputPoint.sub(seg.getCoord2D(t))).length(); + */ + abstract double getClosestCoordinate(Point2D inputPoint, + boolean bExtrapolate); + + /** + * Splits this segment into Y monotonic parts and places them into the input + * array. + * + * @param monotonicSegments + * The in/out array of SegmentBuffer structures that will be + * filled with the monotonic parts. The monotonicSegments array + * must contain at least 3 elements. + * @return The number of monotonic parts if the split had happened. Returns + * 0 if the segment is already monotonic. + */ + abstract int getYMonotonicParts(SegmentBuffer[] monotonicSegments); + + /** + * Calculates intersection points of this segment with an infinite line, + * parallel to one of the axes. + * + * @param bAxisX + * TRUE if the function works with the line parallel to the axis + * X. + * @param ordinate + * The ordinate value of the line (x for axis Y, y for axis X). + * @param resultOrdinates + * The value of ordinate in the intersection points One ordinate + * is equal to the ordinate parameter. This parameter can be + * NULL. + * @param parameters + * The value of the parameter in the intersection points (between + * 0 and 1). This parameter can be NULL. + * @return The number of intersection points, 0 when no intersection points + * exist, -1 when the segment coincides with the line (infinite + * number of intersection points). + */ + public abstract int intersectionWithAxis2D(boolean bAxisX, double ordinate, + double[] resultOrdinates, double[] parameters); + + // FIXME Ask sergey what this method is for + void _reverseImpl() { + } + + /** + * Returns True if the segment is degenerate to a point with relation to the + * given tolerance. For Lines this means the line length is not longer than + * the tolerance. For the curves, the distance between the segment endpoints + * should not be longer than the tolerance and the distance from the line, + * connecting the endpoints to the furtherst point on the segment is not + * larger than the tolerance. + */ + abstract boolean isDegenerate(double tolerance); + + // Cpp definitions + + abstract boolean isCurve(); + + abstract Point2D _getTangent(double t); + + abstract boolean _isDegenerate(double tolerance); + + abstract double _calculateSubLength(double t); + + abstract void _copyToImpl(Segment dst); + + /** + * Returns subsegment between parameters t1 and t2. The attributes are + * interpolated along the length of the curve. + */ + abstract Segment cut(double t1, double t2); + + /** + * Calculates the subsegment between parameters t1 and t2, and stores the + * result in subSegmentBuffer. The attributes are interpolated along the + * length of the curve. + */ + abstract void cut(double t1, double t2, SegmentBuffer subSegmentBuffer); + + /** + * Returns the attribute on the segment for the given parameter value. The + * interpolation of attribute is given by the attribute interpolation type. + */ + public abstract double getAttributeAsDbl(double t, int semantics, + int ordinate); + + abstract boolean _isIntersectingPoint(Point2D pt, double tolerance, + boolean bExcludeExactEndpoints); + + /** + * Calculates intersection point of this segment with an infinite line, + * parallel to axis X. This segment must be to be y-monotonic (or + * horizontal). + * + * @param y + * The y coordinate of the line. + * @param xParallel + * For segments, that are horizontal, and have y coordinate, this + * value is returned. + * @return X coordinate of the intersection, or NaN, if no intersection. + */ + abstract double intersectionOfYMonotonicWithAxisX(double y, double xParallel); + + double distance(/* const */Segment otherSegment, + boolean bSegmentsKnownDisjoint) /* const */ + { + // if the segments are not known to be disjoint, and + // the segments are found to touch in any way, then return 0.0 + if (!bSegmentsKnownDisjoint + && _isIntersecting(otherSegment, 0, false) != 0) { + return 0.0; + } + + double minDistance = NumberUtils.doubleMax(); + + Point2D input_point; + double t; + double distance; + + input_point = getStartXY(); + t = otherSegment.getClosestCoordinate(input_point, false); + input_point.sub(otherSegment.getCoord2D(t)); + distance = input_point.length(); + if (distance < minDistance) + minDistance = distance; + + input_point = getEndXY(); + t = otherSegment.getClosestCoordinate(input_point, false); + input_point.sub(otherSegment.getCoord2D(t)); + distance = input_point.length(); + if (distance < minDistance) + minDistance = distance; + + input_point = otherSegment.getStartXY(); + t = getClosestCoordinate(input_point, false); + input_point.sub(getCoord2D(t)); + distance = input_point.length(); + if (distance < minDistance) + minDistance = distance; + + input_point = otherSegment.getEndXY(); + t = getClosestCoordinate(input_point, false); + input_point.sub(getCoord2D(t)); + distance = input_point.length(); + if (distance < minDistance) + minDistance = distance; + + return minDistance; + } +} diff --git a/src/com/esri/core/geometry/SegmentBuffer.java b/src/com/esri/core/geometry/SegmentBuffer.java new file mode 100644 index 00000000..10934a64 --- /dev/null +++ b/src/com/esri/core/geometry/SegmentBuffer.java @@ -0,0 +1,69 @@ +/* + Copyright 1995-2013 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ + +package com.esri.core.geometry; + +import com.esri.core.geometry.Geometry.Type; +import java.io.Serializable; + +/** + * A helper class to provide reusable segment, line, etc instances. + */ +class SegmentBuffer implements Serializable { + + private static final long serialVersionUID = 1L; + + Line m_line; + + // PointerOf(Bezier) m_bez; + Segment m_seg; + + public SegmentBuffer() { + m_line = null; + m_seg = null; + } + + public Segment get() { + return m_seg; + } + + public void set(Segment seg) { + m_seg = seg; + if (seg != null) { + if (seg.getType() == Type.Line) { + Line ln = (Line) seg; + m_line = ln; + } + throw new GeometryException("internal error"); + } + } + + public void createLine() { + if (null == m_line) { + m_line = new Line(); + + } + m_seg = m_line; + } +} diff --git a/src/com/esri/core/geometry/SegmentFlags.java b/src/com/esri/core/geometry/SegmentFlags.java new file mode 100644 index 00000000..ab3a37e9 --- /dev/null +++ b/src/com/esri/core/geometry/SegmentFlags.java @@ -0,0 +1,36 @@ +/* + Copyright 1995-2013 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ + +package com.esri.core.geometry; + +interface SegmentFlags { + public static final int enumLineSeg = 1; + public static final int enumBezierSeg = 2; + public static final int enumArcSeg = 4; + public static final int enumNonlinearSegmentMask = 6; + public static final int enumSegmentMask = 7; + public static final int enumDensified = 8; // set for segments that have + // been produced from a + // densified non-linear segment. +} diff --git a/src/com/esri/core/geometry/SegmentIntersector.java b/src/com/esri/core/geometry/SegmentIntersector.java new file mode 100644 index 00000000..38d2d4ba --- /dev/null +++ b/src/com/esri/core/geometry/SegmentIntersector.java @@ -0,0 +1,435 @@ +/* + Copyright 1995-2013 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ +/* + * To change this template, choose Tools | Templates + * and open the template in the editor. + */ +package com.esri.core.geometry; + +import java.util.ArrayList; + +class SegmentIntersector { + private static class IntersectionPart { + public Segment seg; + // Weight controls the snapping. When points of the same rank are + // snapped together, + // The new posistion is calculated as a weighted average. + public double weight_start; + public double weight_end; + // The rank controls the snapping. The point with lower rank will be + // snapped to the point with the higher rank. + public int rank_start; // the rank of the start point + public int rank_end; // the rank of the end point + public int rank_interior; // the rank of the interior point + + IntersectionPart(Segment _seg) { + seg = _seg; + weight_start = 1.0; + weight_end = 1.0; + rank_start = 0; + rank_end = 0; + rank_interior = 0; + } + } + + // typedef std::shared_ptr segment_buffer_sptr; + // typedef std::shared_ptr intersection_part_sptr; + // typedef Dynamic_array intersection_parts; + + private ArrayList m_input_segments; + private ArrayList m_result_segments_1; + private ArrayList m_result_segments_2; + private ArrayList m_recycled_intersection_parts; + private ArrayList m_recycled_segments; + private double[] m_param_1 = new double[15]; + private double[] m_param_2 = new double[15]; + private Point m_point = new Point(); + + private int m_used_recycled_segments; + + private void recycle_(ArrayList parts) { + if (parts == null) + return; + + for (int i = 0, n = (int) parts.size(); i < n; i++) { + recycle_(parts.get(i)); + } + + parts.clear(); + } + + private void recycle_(IntersectionPart part) { + part.seg = null; + m_recycled_intersection_parts.add(part); + } + + private IntersectionPart newIntersectionPart_(Segment _seg) { + if (m_recycled_intersection_parts.isEmpty()) { + IntersectionPart part = new IntersectionPart(_seg); + return part; + } else { + IntersectionPart part = m_recycled_intersection_parts + .get(m_recycled_intersection_parts.size() - 1); + part.seg = _seg; + m_recycled_intersection_parts.remove(m_recycled_intersection_parts + .size() - 1); + return part; + } + } + + private IntersectionPart getResultPart_(int input_segment_index, + int segment_index) { + if (input_segment_index == 0) { + return m_result_segments_1.get(segment_index); + } else { + assert (input_segment_index == 1); + return m_result_segments_2.get(segment_index); + } + } + + private SegmentBuffer newSegmentBuffer_() { + if (m_used_recycled_segments >= m_recycled_segments.size()) { + m_recycled_segments.add(new SegmentBuffer()); + } + + SegmentBuffer p = m_recycled_segments.get(m_used_recycled_segments); + m_used_recycled_segments++; + return p; + } + + private double m_tolerance; + + public SegmentIntersector() { + m_used_recycled_segments = 0; + m_tolerance = 0; + m_input_segments = new ArrayList(); + m_result_segments_1 = new ArrayList(); + m_result_segments_2 = new ArrayList(); + m_recycled_intersection_parts = new ArrayList(); + m_recycled_segments = new ArrayList(); + } + + // Clears the results and input segments + public void clear() { + recycle_(m_input_segments); + recycle_(m_result_segments_1); + recycle_(m_result_segments_2); + m_used_recycled_segments = 0; + } + + // Adds a segment to intersect and returns an index of the segment. + // Two segments has to be pushed for the intersect method to succeed. + public int pushSegment(Segment seg) { + assert (m_input_segments.size() < 2); + m_input_segments.add(newIntersectionPart_(seg)); + // m_param_1.resize(15); + // m_param_2.resize(15); + return (int) m_input_segments.size() - 1; + } + + public void setRankAndWeight(int input_segment_index, double start_weight, + int start_rank, double end_weight, int end_rank, int interior_rank) { + IntersectionPart part = m_input_segments.get(input_segment_index); + part.rank_end = end_rank; + part.weight_start = start_weight; + part.weight_end = end_weight; + part.rank_start = start_rank; + part.rank_end = end_rank; + part.rank_interior = interior_rank; + } + + // Returns the number of segments the input segment has been split to. + public int getResultSegmentCount(int input_segment_index) { + if (input_segment_index == 0) { + return (int) m_result_segments_1.size(); + } else { + assert (input_segment_index == 1); + return (int) m_result_segments_2.size(); + } + } + + // Returns a part of the input segment that is the result of the + // intersection with another segment. + // input_segment_index is the index of the input segment. + // segment_index is between 0 and + // get_result_segment_count(input_segment_index) - 1 + public Segment getResultSegment(int input_segment_index, int segment_index) { + return getResultPart_(input_segment_index, segment_index).seg; + } + + // double get_result_segment_start_point_weight(int input_segment_index, int + // segment_index); + // int get_result_segment_start_point_rank(int input_segment_index, int + // segment_index); + // double get_result_segment_end_point_weight(int input_segment_index, int + // segment_index); + // int get_result_segment_end_point_rank(int input_segment_index, int + // segment_index); + // int get_result_segment_interior_rank(int input_segment_index, int + // segment_index); + public Point getResultPoint() { + return m_point; + } + + // Performs the intersection + public void intersect(double tolerance, boolean b_intersecting) { + if (m_input_segments.size() != 2) + throw new GeometryException("internal error"); + + m_tolerance = tolerance; + + IntersectionPart part1 = m_input_segments.get(0); + IntersectionPart part2 = m_input_segments.get(1); + if (b_intersecting + || (part1.seg._isIntersecting(part2.seg, tolerance, true) & 5) != 0) { + if (part1.seg.getType().value() == Geometry.GeometryType.Line) { + Line line_1 = (Line) part1.seg; + if (part2.seg.getType().value() == Geometry.GeometryType.Line) { + Line line_2 = (Line) part2.seg; + int count = Line._intersectLineLine(line_1, line_2, null, + m_param_1, m_param_2, tolerance); + if (count == 0) { + assert (count > 0); + throw new GeometryException("internal error"); + } + Point2D[] points = new Point2D[9]; + for (int i = 0; i < count; i++) { + // For each point of intersection, we calculate a + // weighted point + // based on the ranks and weights of the endpoints and + // the interior. + double t1 = m_param_1[i]; + double t2 = m_param_2[i]; + int rank1 = part1.rank_interior; + double weight1 = 1.0; + + if (t1 == 0) { + rank1 = part1.rank_start; + weight1 = part1.weight_start; + } else if (t1 == 1.0) { + rank1 = part1.rank_end; + weight1 = part1.weight_end; + } + + int rank2 = part2.rank_interior; + double weight2 = 1.0; + if (t2 == 0) { + rank2 = part2.rank_start; + weight2 = part2.weight_start; + } else if (t2 == 1.0) { + rank2 = part2.rank_end; + weight2 = part2.weight_end; + } + + double ptWeight; + + Point2D pt; + if (rank1 == rank2) {// for equal ranks use weighted sum + Point2D pt_1 = new Point2D(); + line_1.getCoord2D(t1, pt_1); + pt_1.scale(weight1); + Point2D pt_2 = new Point2D(); + line_2.getCoord2D(t2, pt_2); + pt_2.scale(weight2); + pt = new Point2D(); + pt.add(pt_1, pt_2); + ptWeight = weight1 + weight2; + pt.scale(1 / ptWeight); + } else {// for non-equal ranks, the higher rank wins + if (rank1 > rank2) { + pt = new Point2D(); + line_1.getCoord2D(t1, pt); + ptWeight = weight1; + } else { + pt = new Point2D(); + line_2.getCoord2D(t2, pt); + ptWeight = weight2; + } + } + points[i] = pt; + } + + // Split the line_1, making sure the endpoints are adusted + // to the weighted + double t0 = 0; + int i0 = -1; + for (int i = 0; i <= count; i++) { + double t = i < count ? m_param_1[i] : 1.0; + if (t != t0) { + SegmentBuffer seg_buffer = newSegmentBuffer_(); + line_1.cut(t0, t, seg_buffer); + if (i0 != -1) + seg_buffer.get().setStartXY(points[i0]); + if (i != count) + seg_buffer.get().setEndXY(points[i]); + + t0 = t; + m_result_segments_1 + .add(newIntersectionPart_(seg_buffer.get())); + } + i0 = i; + } + + int[] indices = new int[9]; + for (int i = 0; i < count; i++) + indices[i] = i; + + if (count > 1) { + if (m_param_2[0] > m_param_2[1]) { + double t = m_param_2[0]; + m_param_2[0] = m_param_2[1]; + m_param_2[1] = t; + int i = indices[0]; + indices[0] = indices[1]; + indices[1] = i; + } + } + + // Split the line_2 + t0 = 0; + i0 = -1; + for (int i = 0; i <= count; i++) { + double t = i < count ? m_param_2[i] : 1.0; + if (t != t0) { + SegmentBuffer seg_buffer = newSegmentBuffer_(); + line_2.cut(t0, t, seg_buffer); + if (i0 != -1) { + int ind = indices[i0]; + seg_buffer.get().setStartXY(points[ind]); + } + if (i != count) { + int ind = indices[i]; + seg_buffer.get().setEndXY(points[ind]); + } + + t0 = t; + m_result_segments_2 + .add(newIntersectionPart_(seg_buffer.get())); + } + i0 = i; + } + + return; + } + + throw new GeometryException("internal error"); + } + + throw new GeometryException("internal error"); + } + } + + public void intersect(double tolerance, Point pt_intersector_point, + int point_rank, double point_weight, boolean b_intersecting) { + pt_intersector_point.copyTo(m_point); + if (m_input_segments.size() != 1) + throw new GeometryException("internal error"); + + m_tolerance = tolerance; + + IntersectionPart part1 = m_input_segments.get(0); + if (b_intersecting + || part1.seg._isIntersectingPoint(pt_intersector_point.getXY(), + tolerance, true)) { + if (part1.seg.getType().value() == Geometry.GeometryType.Line) { + Line line_1 = (Line) (part1.seg); + double t1 = line_1.getClosestCoordinate( + pt_intersector_point.getXY(), false); + m_param_1[0] = t1; + // For each point of intersection, we calculate a weighted point + // based on the ranks and weights of the endpoints and the + // interior. + int rank1 = part1.rank_interior; + double weight1 = 1.0; + + if (t1 == 0) { + rank1 = part1.rank_start; + weight1 = part1.weight_start; + } else if (t1 == 1.0) { + rank1 = part1.rank_end; + weight1 = part1.weight_end; + } + + int rank2 = point_rank; + double weight2 = point_weight; + + double ptWeight; + + Point2D pt; + if (rank1 == rank2) {// for equal ranks use weighted sum + Point2D pt_1 = new Point2D(); + line_1.getCoord2D(t1, pt_1); + pt_1.scale(weight1); + Point2D pt_2 = pt_intersector_point.getXY(); + pt_2.scale(weight2); + pt = new Point2D(); + pt.add(pt_1, pt_2); + ptWeight = weight1 + weight2; + pt.scale(1 / ptWeight); + } else {// for non-equal ranks, the higher rank wins + if (rank1 > rank2) { + pt = new Point2D(); + line_1.getCoord2D(t1, pt); + ptWeight = weight1; + } else { + pt = pt_intersector_point.getXY(); + ptWeight = weight2; + } + } + + // Split the line_1, making sure the endpoints are adusted to + // the weighted + double t0 = 0; + int i0 = -1; + int count = 1; + for (int i = 0; i <= count; i++) { + double t = i < count ? m_param_1[i] : 1.0; + if (t != t0) { + SegmentBuffer seg_buffer = newSegmentBuffer_(); + line_1.cut(t0, t, seg_buffer); + if (i0 != -1) + seg_buffer.get().setStartXY(pt); + if (i != count) + seg_buffer.get().setEndXY(pt); + + t0 = t; + m_result_segments_1.add(newIntersectionPart_(seg_buffer + .get())); + } + i0 = i; + } + + m_point.setXY(pt); + + return; + } + + throw new GeometryException("internal error"); + } + } + + public double get_tolerance_() { + return m_tolerance; + } +} diff --git a/src/com/esri/core/geometry/SegmentIterator.java b/src/com/esri/core/geometry/SegmentIterator.java new file mode 100644 index 00000000..0d5e1889 --- /dev/null +++ b/src/com/esri/core/geometry/SegmentIterator.java @@ -0,0 +1,205 @@ +/* + Copyright 1995-2013 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ + +package com.esri.core.geometry; + +/** + * This class provides functionality to iterate over multipath segments. + */ +public class SegmentIterator { + private SegmentIteratorImpl m_impl; + + SegmentIterator(Object obj) { + m_impl = (SegmentIteratorImpl) obj; + } + + /** + * Moves the iterator to the next path. Returns the TRUE if successful. + * + * @return TRUE if the next path exists. + */ + public boolean nextPath() { + return m_impl.nextPath(); + } + + /** + * Moves the iterator to the previous path. Returns the TRUE if successful. + * + * @return TRUE if the previous path exists. + */ + public boolean previousPath() { + return m_impl.previousPath(); + } + + /** + * Resets the iterator such that a subsequent call to NextPath will set the + * iterator to the first path. + */ + public void resetToFirstPath() { + m_impl.resetToFirstPath(); + } + + /** + * Resets the iterator such that a subsequent call to PreviousPath will set + * the iterator to the last path. + */ + public void resetToLastPath() { + m_impl.resetToLastPath(); + } + + /** + * Resets the iterator such that a subsequent call to NextPath will set the + * iterator to the given path index. A call to PreviousPath will set the + * iterator to the path at pathIndex - 1. + */ + public void resetToPath(int pathIndex) { + m_impl.resetToPath(pathIndex); + } + + /** + * Indicates whether the iterator points to the first segment in the current + * path. + * + * @return TRUE if the iterator points to the first segment in the current + * path. + */ + public boolean isFirstSegmentInPath() { + return m_impl.isFirstSegmentInPath(); + } + + /** + * Indicates whether the iterator points to the last segment in the current + * path. + * + * @return TRUE if the iterator points to the last segment in the current + * path. + */ + public boolean isLastSegmentInPath() { + return m_impl.isLastSegmentInPath(); + } + + /** + * Resets the iterator so that the call to NextSegment will return the first + * segment of the current path. + */ + public void resetToFirstSegment() { + m_impl.resetToFirstSegment(); + } + + /** + * Resets the iterator so that the call to PreviousSegment will return the + * last segment of the current path. + */ + public void resetToLastSegment() { + m_impl.resetToLastSegment(); + } + + /** + * Indicates whether a next segment exists for the path. + * + * @return TRUE is the next segment exists. + */ + public boolean hasNextSegment() { + return m_impl.hasNextSegment(); + } + + /** + * Indicates whether a previous segment exists in the path. + * + * @return TRUE if the previous segment exists. + */ + public boolean hasPreviousSegment() { + return m_impl.hasPreviousSegment(); + } + + /** + * Moves the iterator to the next segment and returns the segment. + * + * The Segment is returned by value and is owned by the iterator. + */ + public Segment nextSegment() { + return m_impl.nextSegment(); + } + + /** + * Moves the iterator to previous segment and returns the segment. + * + * The Segment is returned by value and is owned by the iterator. + */ + public Segment previousSegment() { + return m_impl.previousSegment(); + } + + /** + * Returns the index of the current path. + */ + public int getPathIndex() { + return m_impl.getPathIndex(); + } + + /** + * Returns the index of the start point of this segment. + */ + public int getStartPointIndex() { + return m_impl.getStartPointIndex(); + } + + /** + * Returns the index of the end point of the current segment. + */ + public int getEndPointIndex() { + return m_impl.getEndPointIndex(); + } + + /** + * Returns TRUE, if the segment is the closing segment of the closed path + */ + public boolean isClosingSegment() { + return m_impl.isClosingSegment(); + } + + /** + * Switches the iterator to navigation mode. + * + * @param bYesNo + * If TRUE, the iterator loops over the current path infinitely + * (unless the multipath is empty). + */ + public void setCirculator(boolean bYesNo) { + m_impl.setCirculator(bYesNo); + } + + /** + * Copies this SegmentIterator. + * + * @return SegmentIterator. + */ + public Object copy() { + return new SegmentIterator(m_impl.copy()); + } + + protected Object _getImpl() { + return (SegmentIteratorImpl) m_impl; + } +} diff --git a/src/com/esri/core/geometry/SegmentIteratorImpl.java b/src/com/esri/core/geometry/SegmentIteratorImpl.java new file mode 100644 index 00000000..644a6767 --- /dev/null +++ b/src/com/esri/core/geometry/SegmentIteratorImpl.java @@ -0,0 +1,456 @@ +/* + Copyright 1995-2013 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ + + +package com.esri.core.geometry; + +/** + * Provides functionality to iterate over MultiPath segments. + */ +final class SegmentIteratorImpl { + + protected Line m_line; + + // Bezier m_bezier: + // Arc m_arc; + protected Segment m_currentSegment; + protected Point2D m_dummyPoint; + + protected int m_currentPathIndex; + + protected int m_nextPathIndex; + + protected int m_prevPathIndex; + + protected int m_currentSegmentIndex; + + protected int m_nextSegmentIndex; + + protected int m_prevSegmentIndex; + + protected int m_segmentCount; + + protected MultiPathImpl m_parent; // parent of the iterator. + + protected boolean m_bCirculator; // If true, the iterator circulates around + // the current Path. + + protected boolean m_bNeedsUpdate; + + public SegmentIteratorImpl(MultiPathImpl parent) { + m_currentSegmentIndex = -1; + m_nextSegmentIndex = 0; + m_nextPathIndex = 0; + m_currentPathIndex = -1; + m_parent = parent; + m_segmentCount = _getSegmentCount(m_nextPathIndex); + m_bCirculator = false; + m_currentSegment = null; + m_dummyPoint = new Point2D(); + } + + public SegmentIteratorImpl(MultiPathImpl parent, int pointIndex) { + if (pointIndex < 0 || pointIndex >= parent.getPointCount()) + throw new IndexOutOfBoundsException(); + + m_currentSegmentIndex = -1; + int path = parent.getPathIndexFromPointIndex(pointIndex); + m_nextSegmentIndex = pointIndex - parent.getPathStart(path); + + m_nextPathIndex = path + 1; + m_currentPathIndex = path; + m_parent = parent; + m_segmentCount = _getSegmentCount(m_currentPathIndex); + m_bCirculator = false; + m_currentSegment = null; + m_dummyPoint = new Point2D(); + } + + public SegmentIteratorImpl(MultiPathImpl parent, int pathIndex, + int segmentIndex) { + if (pathIndex < 0 || pathIndex >= parent.getPathCount() + || segmentIndex < 0) + throw new IndexOutOfBoundsException(); + + int d = parent.isClosedPath(pathIndex) ? 0 : 1; + if (segmentIndex >= parent.getPathSize(pathIndex) - d) + throw new IndexOutOfBoundsException(); + + m_currentSegmentIndex = -1; + m_nextSegmentIndex = segmentIndex; + m_currentPathIndex = pathIndex; + m_nextPathIndex = m_nextSegmentIndex + 1; + m_parent = parent; + m_segmentCount = _getSegmentCount(m_nextPathIndex); + m_bCirculator = false; + m_currentSegment = null; + m_dummyPoint = new Point2D(); + } + + void resetTo(SegmentIteratorImpl src) { + if (m_parent != src.m_parent) + throw new GeometryException("invalid_call"); + + m_currentSegmentIndex = src.m_currentSegmentIndex; + m_nextSegmentIndex = src.m_nextSegmentIndex; + m_currentPathIndex = src.m_currentPathIndex; + m_nextPathIndex = src.m_nextPathIndex; + m_segmentCount = src.m_segmentCount; + m_bCirculator = src.m_bCirculator; + m_currentSegment = null; + } + + /** + * Moves the iterator to the next curve segment and returns the segment. + * + * The Segment is returned by value and is owned by the iterator. Note: The + * method can return null if there are no curves in the part. + */ + public Segment nextCurve() { + return null; + // TODO: Fix me. This method is supposed to go only through the curves + // and skip the Line classes!! + // It must be very efficient. + } + + /** + * Moves the iterator to next segment and returns the segment. + * + * The Segment is returned by value and is owned by the iterator. + */ + public Segment nextSegment() { + if (m_currentSegmentIndex != m_nextSegmentIndex) + _updateSegment(); + + if (m_bCirculator) { + m_nextSegmentIndex = (m_nextSegmentIndex + 1) % m_segmentCount; + } else { + if (m_nextSegmentIndex == m_segmentCount) + throw new IndexOutOfBoundsException(); + + m_nextSegmentIndex++; + } + + return m_currentSegment; + } + + /** + * Moves the iterator to previous segment and returns the segment. + * + * The Segment is returned by value and is owned by the iterator. + */ + public Segment previousSegment() { + if (m_bCirculator) { + m_nextSegmentIndex = (m_segmentCount + m_nextSegmentIndex - 1) + % m_segmentCount; + } else { + if (m_nextSegmentIndex == 0) + throw new IndexOutOfBoundsException(); + m_nextSegmentIndex--; + } + + if (m_nextSegmentIndex != m_currentSegmentIndex) + _updateSegment(); + + return m_currentSegment; + } + + /** + * Resets the iterator so that the call to NextSegment will return the first + * segment of the current path. + */ + public void resetToFirstSegment() { + m_currentSegmentIndex = -1; + m_nextSegmentIndex = 0; + } + + /** + * Resets the iterator so that the call to PreviousSegment will return the + * last segment of the current path. + */ + public void resetToLastSegment() { + m_nextSegmentIndex = m_segmentCount; + m_currentSegmentIndex = -1; + } + + public void resetToVertex(int vertexIndex) { + if (m_currentPathIndex >= 0 + && m_currentPathIndex < m_parent.getPathCount()) {// check if we + // are in + // the + // current + // path + int start = m_parent.getPathStart(m_currentPathIndex); + if (vertexIndex >= start + && vertexIndex < m_parent.getPathEnd(m_currentPathIndex)) { + m_currentSegmentIndex = -1; + m_nextSegmentIndex = vertexIndex - start; + return; + } + } + + int pathIndex = m_parent.getPathIndexFromPointIndex(vertexIndex); + m_nextPathIndex = pathIndex + 1; + m_currentPathIndex = pathIndex; + m_currentSegmentIndex = -1; + m_nextSegmentIndex = vertexIndex - m_parent.getPathStart(pathIndex); + m_segmentCount = _getSegmentCount(pathIndex); + } + + /** + * Moves the iterator to next path and returns true if successful. + * + */ + public boolean nextPath() { + // post-increment + m_currentPathIndex = m_nextPathIndex; + if (m_currentPathIndex >= m_parent.getPathCount()) + return false; + + m_currentSegmentIndex = -1; + m_nextSegmentIndex = 0; + m_segmentCount = _getSegmentCount(m_currentPathIndex); + m_nextPathIndex++; + return true; + } + + /** + * Moves the iterator to next path and returns true if successful. + * + */ + public boolean previousPath() { + // pre-decrement + if (m_nextPathIndex == 0) + return false; + + m_nextPathIndex--; + m_currentSegmentIndex = -1; + m_nextSegmentIndex = 0; + m_segmentCount = _getSegmentCount(m_nextPathIndex); + m_currentPathIndex = m_nextPathIndex; + resetToLastSegment(); + return true; + } + + /** + * Resets the iterator such that the subsequent call to the NextPath will + * set the iterator to the first segment of the first path. + */ + public void resetToFirstPath() { + + m_currentSegmentIndex = -1; + m_nextSegmentIndex = -1; + m_segmentCount = -1; + m_nextPathIndex = 0; + m_currentPathIndex = -1; + } + + /** + * Resets the iterator such that the subsequent call to the PreviousPath + * will set the iterator to the last segment of the last path. + */ + public void resetToLastPath() { + m_nextPathIndex = m_parent.getPathCount(); + m_currentPathIndex = -1; + m_currentSegmentIndex = -1; + m_nextSegmentIndex = -1; + m_segmentCount = -1; + } + + /** + * Resets the iterator such that the subsequent call to the NextPath will + * set the iterator to the first segment of the given path. The call to + * PreviousPath will reset the iterator to the last segment of path + * pathIndex - 1. + */ + public void resetToPath(int pathIndex) { + if (pathIndex < 0) + throw new IndexOutOfBoundsException(); + + m_nextPathIndex = pathIndex; + m_currentPathIndex = -1; + m_currentSegmentIndex = -1; + m_nextSegmentIndex = -1; + m_segmentCount = -1; + } + + public int _getSegmentCount(int pathIndex) { + if (m_parent.isEmptyImpl()) + return 0; + + int d = 1; + if (m_parent.isClosedPath(pathIndex)) + d = 0; + + return m_parent.getPathSize(pathIndex) - d; + } + + /** + * Returns True, if the segment is the closing segment of the closed path + */ + public boolean isClosingSegment() { + return m_currentSegmentIndex == m_segmentCount - 1 + && m_parent.isClosedPath(m_currentPathIndex); + } + + /** + * Switches the iterator navigation mode. + * + * @param bYesNo + * If True, the iterator loops over the current path infinitely + * (unless the MultiPath is empty). + */ + public void setCirculator(boolean bYesNo) { + m_bCirculator = bYesNo; + } + + /** + * Returns the index of the current path. + */ + public int getPathIndex() { + return m_currentPathIndex; + } + + /** + * Returns the index of the start Point of the current Segment. + */ + public int getStartPointIndex() { + return _getPathBegin() + m_currentSegmentIndex; + } + + public int _getPathBegin() { + return m_parent.getPathStart(m_currentPathIndex); + } + + /** + * Returns the index of the end Point of the current Segment. + */ + public int getEndPointIndex() { + if (isClosingSegment()) { + return m_parent.getPathStart(m_currentPathIndex); + } else { + return getStartPointIndex() + 1; + } + } + + /** + * Returns True if the segment is first one in the current Path. + */ + public boolean isFirstSegmentInPath() { + return m_currentSegmentIndex == 0; + } + + /** + * Returns True if the segment is last one in the current Path. + */ + public boolean isLastSegmentInPath() { + return m_currentSegmentIndex == m_segmentCount - 1; + } + + /** + * Returns True if the call to the NextSegment will succeed. + */ + public boolean hasNextSegment() { + return m_nextSegmentIndex < m_segmentCount; + } + + /** + * Returns True if the call to the NextSegment will succeed. + */ + public boolean hasPreviousSegment() { + return m_nextSegmentIndex > 0; + } + + public SegmentIteratorImpl copy() { + SegmentIteratorImpl clone = new SegmentIteratorImpl(m_parent); + clone.m_currentSegmentIndex = m_currentSegmentIndex; + clone.m_nextSegmentIndex = m_nextSegmentIndex; + clone.m_segmentCount = m_segmentCount; + clone.m_currentPathIndex = m_currentPathIndex; + clone.m_nextPathIndex = m_nextPathIndex; + clone.m_parent = m_parent; + clone.m_bCirculator = m_bCirculator; + return clone; + } + + public void _updateSegment() { + if (m_nextSegmentIndex < 0 || m_nextSegmentIndex >= m_segmentCount) + throw new IndexOutOfBoundsException(); + m_currentSegmentIndex = m_nextSegmentIndex; + + int startVertexIndex = getStartPointIndex(); + m_parent._verifyAllStreams(); + AttributeStreamOfInt8 segFlagStream = m_parent + .getSegmentFlagsStreamRef(); + + // FIXME Review this implementation of segment flags and the switch + // statement below. + int segFlag = SegmentFlags.enumLineSeg; + if (segFlagStream != null) + segFlag = (segFlagStream.read(startVertexIndex) & SegmentFlags.enumSegmentMask); + + VertexDescription vertexDescr = m_parent.getDescription(); + switch (segFlag) { + case SegmentFlags.enumLineSeg: + if (m_line == null) + m_line = new Line(); + m_currentSegment = (Line) m_line; + break; + case SegmentFlags.enumBezierSeg: + throw new GeometryException("internal error"); + // break; + case SegmentFlags.enumArcSeg: + throw new GeometryException("internal error"); + // break; + default: + throw new GeometryException("internal error"); + } + + m_currentSegment.assignVertexDescription(vertexDescr); + + int endVertexIndex = getEndPointIndex(); + m_parent.getXY(startVertexIndex, m_dummyPoint); + m_currentSegment.setStartXY(m_dummyPoint); + m_parent.getXY(endVertexIndex, m_dummyPoint); + m_currentSegment.setEndXY(m_dummyPoint); + + for (int i = 1, nattr = vertexDescr.getAttributeCount(); i < nattr; i++) { + int semantics = vertexDescr.getSemantics(i); + int ncomp = VertexDescription.getComponentCount(semantics); + for (int ord = 0; ord < ncomp; ord++) { + double vs = m_parent.getAttributeAsDbl(semantics, + startVertexIndex, ord); + m_currentSegment.setStartAttribute(semantics, ord, vs); + double ve = m_parent.getAttributeAsDbl(semantics, + endVertexIndex, ord); + m_currentSegment.setEndAttribute(semantics, ord, ve); + } + } + } + + boolean isLastPath() { + return m_currentPathIndex == m_parent.getPathCount() - 1; + } + +} diff --git a/src/com/esri/core/geometry/ShapeExportFlags.java b/src/com/esri/core/geometry/ShapeExportFlags.java new file mode 100644 index 00000000..2d195fb8 --- /dev/null +++ b/src/com/esri/core/geometry/ShapeExportFlags.java @@ -0,0 +1,42 @@ +/* + Copyright 1995-2013 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ + +package com.esri.core.geometry; + +interface ShapeExportFlags { + + public static final int ShapeExportDefaults = 0; + public static final int ShapeExportNoSwap = 1; + public static final int ShapeExportAngularDensify = 2; + public static final int ShapeExportDistanceDensify = 4; + public static final int ShapeExportTrueNaNs = 8; + public static final int ShapeExportStripZs = 16; + public static final int ShapeExportStripMs = 32; + public static final int ShapeExportStripIDs = 64; + public static final int ShapeExportStripTextures = 128; + public static final int ShapeExportStripNormals = 256; + public static final int ShapeExportStripMaterials = 512; + public static final int ShapeExportNewArcFormat = 1024; + public static final int ShapeExportNoCompress = 2048; +} diff --git a/src/com/esri/core/geometry/ShapeImportFlags.java b/src/com/esri/core/geometry/ShapeImportFlags.java new file mode 100644 index 00000000..36fc3c43 --- /dev/null +++ b/src/com/esri/core/geometry/ShapeImportFlags.java @@ -0,0 +1,34 @@ +/* + Copyright 1995-2013 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ + +package com.esri.core.geometry; + +interface ShapeImportFlags { + + public static final int ShapeImportDefaults = 0; + public static final int ShapeImportNoSwap = 1; + public static final int ShapeImportNonTrusted = 2; + public static final int ShapeImportAttach = 4; + +} diff --git a/src/com/esri/core/geometry/ShapeModifiers.java b/src/com/esri/core/geometry/ShapeModifiers.java new file mode 100644 index 00000000..2206c8e9 --- /dev/null +++ b/src/com/esri/core/geometry/ShapeModifiers.java @@ -0,0 +1,43 @@ +/* + Copyright 1995-2013 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ + +package com.esri.core.geometry; + +interface ShapeModifiers { + public static final int ShapeHasZs = 0x80000000; + public static final int ShapeHasMs = 0x40000000; + public static final int ShapeHasCurves = 0x20000000; + public static final int ShapeHasIDs = 0x10000000; + public static final int ShapeHasNormals = 0x08000000; + public static final int ShapeHasTextures = 0x04000000; + public static final int ShapeHasPartIDs = 0x02000000; + public static final int ShapeHasMaterials = 0x01000000; + public static final int ShapeIsCompressed = 0x00800000; + public static final int ShapeModifierMask = 0xFF000000; + public static final int ShapeMultiPatchModifierMask = 0x0F00000; + public static final int ShapeBasicTypeMask = 0x000000FF; + public static final int ShapeBasicModifierMask = 0xC0000000; + public static final int ShapeNonBasicModifierMask = 0x3F000000; + public static final int ShapeExtendedModifierMask = 0xDD000000; +} diff --git a/src/com/esri/core/geometry/ShapeType.java b/src/com/esri/core/geometry/ShapeType.java new file mode 100644 index 00000000..17efe606 --- /dev/null +++ b/src/com/esri/core/geometry/ShapeType.java @@ -0,0 +1,54 @@ +/* + Copyright 1995-2013 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ + +package com.esri.core.geometry; + +interface ShapeType { + public static final int ShapeNull = 0; + public static final int ShapePoint = 1; + public static final int ShapePointM = 21; + public static final int ShapePointZM = 11; + public static final int ShapePointZ = 9; + public static final int ShapeMultiPoint = 8; + public static final int ShapeMultiPointM = 28; + public static final int ShapeMultiPointZM = 18; + public static final int ShapeMultiPointZ = 20; + public static final int ShapePolyline = 3; + public static final int ShapePolylineM = 23; + public static final int ShapePolylineZM = 13; + public static final int ShapePolylineZ = 10; + public static final int ShapePolygon = 5; + public static final int ShapePolygonM = 25; + public static final int ShapePolygonZM = 15; + public static final int ShapePolygonZ = 19; + public static final int ShapeMultiPatchM = 31; + public static final int ShapeMultiPatch = 32; + public static final int ShapeGeneralPolyline = 50; + public static final int ShapeGeneralPolygon = 51; + public static final int ShapeGeneralPoint = 52; + public static final int ShapeGeneralMultiPoint = 53; + public static final int ShapeGeneralMultiPatch = 54; + public static final int ShapeTypeLast = 55; + +} diff --git a/src/com/esri/core/geometry/SimpleByteBufferCursor.java b/src/com/esri/core/geometry/SimpleByteBufferCursor.java new file mode 100644 index 00000000..5b884949 --- /dev/null +++ b/src/com/esri/core/geometry/SimpleByteBufferCursor.java @@ -0,0 +1,56 @@ +/* + Copyright 1995-2013 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ + +package com.esri.core.geometry; + +import java.nio.ByteBuffer; + +class SimpleByteBufferCursor extends ByteBufferCursor { + + ByteBuffer m_byteBuffer; + int m_index; + int m_count; + + public SimpleByteBufferCursor(ByteBuffer byteBuffer) { + m_byteBuffer = byteBuffer; + m_index = -1; + m_count = 1; + } + + @Override + public int getByteBufferID() { + return m_index; + } + + @Override + public ByteBuffer next() { + if (m_index < m_count - 1) { + m_index++; + return m_byteBuffer; + } + + return null; + } + +} diff --git a/src/com/esri/core/geometry/SimpleGeometryCursor.java b/src/com/esri/core/geometry/SimpleGeometryCursor.java new file mode 100644 index 00000000..5ec4034d --- /dev/null +++ b/src/com/esri/core/geometry/SimpleGeometryCursor.java @@ -0,0 +1,73 @@ +/* + Copyright 1995-2013 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ +package com.esri.core.geometry; + +import java.util.Arrays; +import java.util.List; + +/** + * A simple GeometryCursor implementation that wraps a single Geometry or + * an array of Geometry classes + */ +public class SimpleGeometryCursor extends GeometryCursor { + + Geometry m_geom; + List m_geomArray; + + int m_index; + int m_count; + + public SimpleGeometryCursor(Geometry geom) { + m_geom = geom; + m_index = -1; + m_count = 1; + } + + public SimpleGeometryCursor(Geometry[] geoms) { + m_geomArray = Arrays.asList(geoms); + m_index = -1; + m_count = geoms.length; + } + + public SimpleGeometryCursor(List geoms) { + m_geomArray = geoms; + m_index = -1; + m_count = geoms.size(); + } + + @Override + public int getGeometryID() { + return m_index; + } + + @Override + public Geometry next() { + if (m_index < m_count - 1) { + m_index++; + return m_geom != null ? m_geom : m_geomArray.get(m_index); + } + + return null; + } +} diff --git a/src/com/esri/core/geometry/SimpleJsonCursor.java b/src/com/esri/core/geometry/SimpleJsonCursor.java new file mode 100644 index 00000000..188d7d4d --- /dev/null +++ b/src/com/esri/core/geometry/SimpleJsonCursor.java @@ -0,0 +1,62 @@ +/* + Copyright 1995-2013 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ +package com.esri.core.geometry; + +class SimpleJsonCursor extends JsonCursor { + + String m_jsonString; + String[] m_jsonStringArray; + + int m_index; + int m_count; + + public SimpleJsonCursor(String jsonString) { + m_jsonString = jsonString; + m_index = -1; + m_count = 1; + } + + public SimpleJsonCursor(String[] jsonStringArray) { + m_jsonStringArray = jsonStringArray; + m_index = -1; + m_count = jsonStringArray.length; + } + + @Override + public int getID() { + return m_index; + } + + @Override + public String next() { + if (m_index < m_count - 1) { + m_index++; + return m_jsonString != null ? m_jsonString + : m_jsonStringArray[m_index]; + } + + return null; + } + +} diff --git a/src/com/esri/core/geometry/SimpleJsonParserCursor.java b/src/com/esri/core/geometry/SimpleJsonParserCursor.java new file mode 100644 index 00000000..1d30235c --- /dev/null +++ b/src/com/esri/core/geometry/SimpleJsonParserCursor.java @@ -0,0 +1,64 @@ +/* + Copyright 1995-2013 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ +package com.esri.core.geometry; + +import org.codehaus.jackson.JsonParser; + +class SimpleJsonParserCursor extends JsonParserCursor { + + JsonParser m_jsonParser; + JsonParser[] m_jsonParserArray; + + int m_index; + int m_count; + + public SimpleJsonParserCursor(JsonParser jsonString) { + m_jsonParser = jsonString; + m_index = -1; + m_count = 1; + } + + public SimpleJsonParserCursor(JsonParser[] jsonStringArray) { + m_jsonParserArray = jsonStringArray; + m_index = -1; + m_count = jsonStringArray.length; + } + + @Override + public int getID() { + return m_index; + } + + @Override + public JsonParser next() { + if (m_index < m_count - 1) { + m_index++; + return m_jsonParser != null ? m_jsonParser + : m_jsonParserArray[m_index]; + } + + return null; + } + +} diff --git a/src/com/esri/core/geometry/SimpleMapGeometryCursor.java b/src/com/esri/core/geometry/SimpleMapGeometryCursor.java new file mode 100644 index 00000000..6d6879ed --- /dev/null +++ b/src/com/esri/core/geometry/SimpleMapGeometryCursor.java @@ -0,0 +1,64 @@ +/* + Copyright 1995-2013 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ +package com.esri.core.geometry; + +/** + * A simple MapGeometryCursor implementation that wraps a single MapGeometry or + * an array of MapGeometry classes + */ +class SimpleMapGeometryCursor extends MapGeometryCursor { + + MapGeometry m_geom; + MapGeometry[] m_geomArray; + + int m_index; + int m_count; + + public SimpleMapGeometryCursor(MapGeometry geom) { + m_geom = geom; + m_index = -1; + m_count = 1; + } + + public SimpleMapGeometryCursor(MapGeometry[] geoms) { + m_geomArray = geoms; + m_index = -1; + m_count = geoms.length; + } + + @Override + public int getGeometryID() { + return m_index; + } + + @Override + public MapGeometry next() { + if (m_index < m_count - 1) { + m_index++; + return m_geom != null ? m_geom : m_geomArray[m_index]; + } + + return null; + } +} diff --git a/src/com/esri/core/geometry/Simplificator.java b/src/com/esri/core/geometry/Simplificator.java new file mode 100644 index 00000000..1c6db3ce --- /dev/null +++ b/src/com/esri/core/geometry/Simplificator.java @@ -0,0 +1,1031 @@ +/* + Copyright 1995-2013 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ +package com.esri.core.geometry; + +class Simplificator { + private EditShape m_shape; + private int m_geometry; + private IndexMultiDCList m_sortedVertices; + + private AttributeStreamOfInt32 m_bunchEdgeEndPoints; + private AttributeStreamOfInt32 m_bunchEdgeCenterPoints; + private AttributeStreamOfInt32 m_bunchEdgeIndices; + // private AttributeStreamOfInt32 m_orphanVertices; + + private int m_dbgCounter; + private int m_sortedVerticesListIndex; + private int m_userIndexSortedIndexToVertex; + private int m_userIndexSortedAngleIndexToVertex; + private int m_nextVertexToProcess; + private int m_firstCoincidentVertex; + private int m_knownSimpleResult; + private boolean m_bWinding; + + private void _beforeRemoveVertex(int vertex, boolean bChangePathFirst) { + int vertexlistIndex = m_shape.getUserIndex(vertex, + m_userIndexSortedIndexToVertex); + // _ASSERT(m_sortedVertices.getData(vertexlistIndex) != 0xdeadbeef); + if (m_nextVertexToProcess == vertexlistIndex) { + m_nextVertexToProcess = m_sortedVertices + .getNext(m_nextVertexToProcess); + } + + if (m_firstCoincidentVertex == vertexlistIndex) + m_firstCoincidentVertex = m_sortedVertices + .getNext(m_firstCoincidentVertex); + + m_sortedVertices.deleteElement(m_sortedVerticesListIndex, + vertexlistIndex); + _removeAngleSortInfo(vertex); + if (bChangePathFirst) { + int path = m_shape.getPathFromVertex(vertex); + if (path != -1) { + int first = m_shape.getFirstVertex(path); + if (first == vertex) { + int next = m_shape.getNextVertex(vertex); + if (next != vertex) + m_shape.setFirstVertex_(path, next); + else { + m_shape.setFirstVertex_(path, -1); + m_shape.setLastVertex_(path, -1); + } + } + } + } + } + + static class SimplificatorAngleComparer extends + AttributeStreamOfInt32.IntComparator { + Simplificator m_parent; + + public SimplificatorAngleComparer(Simplificator parent) { + m_parent = parent; + } + + @Override + public int compare(int v1, int v2) { + return m_parent._compareAngles(v1, v2); + } + + } + + private boolean _processBunch() { + boolean bModified = false; + int iter = 0; + Point2D ptCenter = new Point2D(); + while (true) { + m_dbgCounter++;// only for debugging + iter++; + // _ASSERT(iter < 10); + if (m_bunchEdgeEndPoints == null) { + m_bunchEdgeEndPoints = new AttributeStreamOfInt32(0); + m_bunchEdgeCenterPoints = new AttributeStreamOfInt32(0); + m_bunchEdgeIndices = new AttributeStreamOfInt32(0); + } else { + m_bunchEdgeEndPoints.clear(false); + m_bunchEdgeCenterPoints.clear(false); + m_bunchEdgeIndices.clear(false); + } + + int currentVertex = m_firstCoincidentVertex; + int index = 0; + boolean bFirst = true; + while (currentVertex != m_nextVertexToProcess) { + int v = m_sortedVertices.getData(currentVertex); + {// debug + Point2D pt = new Point2D(); + m_shape.getXY(v, pt); + double y = pt.x; + } + if (bFirst) { + m_shape.getXY(v, ptCenter); + bFirst = false; + } + int vertP = m_shape.getPrevVertex(v); + int vertN = m_shape.getNextVertex(v); + // _ASSERT(vertP != vertN || m_shape.getPrevVertex(vertN) == v + // && m_shape.getNextVertex(vertP) == v); + + int id = m_shape.getUserIndex(vertP, + m_userIndexSortedAngleIndexToVertex); + if (id != 0xdeadbeef)// avoid adding a point twice + { + // _ASSERT(id == -1); + m_bunchEdgeEndPoints.add(vertP); + m_shape.setUserIndex(vertP, + m_userIndexSortedAngleIndexToVertex, 0xdeadbeef);// mark + // that + // it + // has + // been + // already + // added + m_bunchEdgeCenterPoints.add(v); + m_bunchEdgeIndices.add(index++); + } + + int id2 = m_shape.getUserIndex(vertN, + m_userIndexSortedAngleIndexToVertex); + if (id2 != 0xdeadbeef) // avoid adding a point twice + { + // _ASSERT(id2 == -1); + m_bunchEdgeEndPoints.add(vertN); + m_shape.setUserIndex(vertN, + m_userIndexSortedAngleIndexToVertex, 0xdeadbeef);// mark + // that + // it + // has + // been + // already + // added + m_bunchEdgeCenterPoints.add(v); + m_bunchEdgeIndices.add(index++); + } + + currentVertex = m_sortedVertices.getNext(currentVertex); + } + + if (m_bunchEdgeEndPoints.size() < 2) + break; + + // Sort the bunch edpoints by angle (angle between the axis x and + // the edge, connecting the endpoint with the bunch center) + m_bunchEdgeIndices.Sort(0, m_bunchEdgeIndices.size(), + new SimplificatorAngleComparer(this)); + // SORTDYNAMICARRAYEX(m_bunchEdgeIndices, int, 0, + // m_bunchEdgeIndices.size(), SimplificatorAngleComparer, this); + + for (int i = 0, n = m_bunchEdgeIndices.size(); i < n; i++) { + int indexL = m_bunchEdgeIndices.get(i); + int vertex = m_bunchEdgeEndPoints.get(indexL); + m_shape.setUserIndex(vertex, + m_userIndexSortedAngleIndexToVertex, i);// rember the + // sort by angle + // order + {// debug + Point2D pt = new Point2D(); + m_shape.getXY(vertex, pt); + double y = pt.x; + } + } + + boolean bCrossOverResolved = _processCrossOvers(ptCenter);// see of + // there + // are + // crossing + // over + // edges. + for (int i = 0, n = m_bunchEdgeIndices.size(); i < n; i++) { + int indexL = m_bunchEdgeIndices.get(i); + if (indexL == -1) + continue; + int vertex = m_bunchEdgeEndPoints.get(indexL); + m_shape.setUserIndex(vertex, + m_userIndexSortedAngleIndexToVertex, -1);// remove + // mapping + } + + if (bCrossOverResolved) { + bModified = true; + continue; + } + + break; + } + + return bModified; + } + + private boolean _processCrossOvers(Point2D ptCenter) { + boolean bFound = false; + + // Resolve all overlaps + boolean bContinue = true; + while (bContinue) { + // The nearest pairts in the middle of the list + bContinue = false; + int index1 = 0; + if (m_bunchEdgeIndices.get(index1) == -1) + index1 = _getNextEdgeIndex(index1); + + int index2 = _getNextEdgeIndex(index1); + + for (int i = 0, n = m_bunchEdgeIndices.size(); i < n + && index1 != -1 && index2 != -1 && index1 != index2; i++) { + int edgeindex1 = m_bunchEdgeIndices.get(index1); + int edgeindex2 = m_bunchEdgeIndices.get(index2); + + int vertexB1 = m_bunchEdgeEndPoints.get(edgeindex1); + int vertexB2 = m_bunchEdgeEndPoints.get(edgeindex2); + // _ASSERT(vertexB2 != vertexB1); + + int vertexA1 = m_shape.getNextVertex(vertexB1); + if (!m_shape.isEqualXY(vertexA1, ptCenter)) + vertexA1 = m_shape.getPrevVertex(vertexB1); + int vertexA2 = m_shape.getNextVertex(vertexB2); + if (!m_shape.isEqualXY(vertexA2, ptCenter)) + vertexA2 = m_shape.getPrevVertex(vertexB2); + + // _ASSERT(m_shape.isEqualXY(vertexA1, vertexA2)); + // _ASSERT(m_shape.isEqualXY(vertexA1, ptCenter)); + + boolean bDirection1 = _getDirection(vertexA1, vertexB1); + boolean bDirection2 = _getDirection(vertexA2, vertexB2); + int vertexC1 = bDirection1 ? m_shape.getPrevVertex(vertexA1) + : m_shape.getNextVertex(vertexA1); + int vertexC2 = bDirection2 ? m_shape.getPrevVertex(vertexA2) + : m_shape.getNextVertex(vertexA2); + + boolean bOverlap = false; + if (_removeSpike(vertexA1)) + bOverlap = true; + else if (_removeSpike(vertexA2)) + bOverlap = true; + else if (_removeSpike(vertexB1)) + bOverlap = true; + else if (_removeSpike(vertexB2)) + bOverlap = true; + else if (_removeSpike(vertexC1)) + bOverlap = true; + else if (_removeSpike(vertexC2)) + bOverlap = true; + + if (!bOverlap && m_shape.isEqualXY(vertexB1, vertexB2)) { + bOverlap = true; + _resolveOverlap(bDirection1, bDirection2, vertexA1, + vertexB1, vertexA2, vertexB2); + } + + if (!bOverlap && m_shape.isEqualXY(vertexC1, vertexC2)) { + bOverlap = true; + _resolveOverlap(!bDirection1, !bDirection2, vertexA1, + vertexC1, vertexA2, vertexC2); + } + + if (bOverlap) + bFound = true; + + bContinue |= bOverlap; + + index1 = _getNextEdgeIndex(index1); + index2 = _getNextEdgeIndex(index1); + } + } + + if (!bFound) {// resolve all cross overs + int index1 = 0; + if (m_bunchEdgeIndices.get(index1) == -1) + index1 = _getNextEdgeIndex(index1); + + int index2 = _getNextEdgeIndex(index1); + + for (int i = 0, n = m_bunchEdgeIndices.size(); i < n + && index1 != -1 && index2 != -1 && index1 != index2; i++) { + int edgeindex1 = m_bunchEdgeIndices.get(index1); + int edgeindex2 = m_bunchEdgeIndices.get(index2); + + int vertexB1 = m_bunchEdgeEndPoints.get(edgeindex1); + int vertexB2 = m_bunchEdgeEndPoints.get(edgeindex2); + + int vertexA1 = m_shape.getNextVertex(vertexB1); + if (!m_shape.isEqualXY(vertexA1, ptCenter)) + vertexA1 = m_shape.getPrevVertex(vertexB1); + int vertexA2 = m_shape.getNextVertex(vertexB2); + if (!m_shape.isEqualXY(vertexA2, ptCenter)) + vertexA2 = m_shape.getPrevVertex(vertexB2); + + // _ASSERT(m_shape.isEqualXY(vertexA1, vertexA2)); + // _ASSERT(m_shape.isEqualXY(vertexA1, ptCenter)); + + boolean bDirection1 = _getDirection(vertexA1, vertexB1); + boolean bDirection2 = _getDirection(vertexA2, vertexB2); + int vertexC1 = bDirection1 ? m_shape.getPrevVertex(vertexA1) + : m_shape.getNextVertex(vertexA1); + int vertexC2 = bDirection2 ? m_shape.getPrevVertex(vertexA2) + : m_shape.getNextVertex(vertexA2); + + if (_detectAndResolveCrossOver(bDirection1, bDirection2, + vertexB1, vertexA1, vertexC1, vertexB2, vertexA2, + vertexC2)) { + bFound = true; + } + + index1 = _getNextEdgeIndex(index1); + index2 = _getNextEdgeIndex(index1); + } + } + + return bFound; + } + + static class SimplificatorVertexComparer extends + AttributeStreamOfInt32.IntComparator { + Simplificator m_parent; + + SimplificatorVertexComparer(Simplificator parent) { + m_parent = parent; + } + + @Override + public int compare(int v1, int v2) { + return m_parent._compareVerticesSimple(v1, v2); + } + + } + + private boolean _simplify() { + boolean bChanged = false; + boolean bNeedWindingRepeat = true; + boolean bWinding = false; + + m_userIndexSortedIndexToVertex = -1; + m_userIndexSortedAngleIndexToVertex = -1; + + int pointCount = m_shape.getPointCount(m_geometry); + + // Sort vertices lexicographically + // Firstly copy allvertices to an array. + AttributeStreamOfInt32 verticesSorter = new AttributeStreamOfInt32(0); + verticesSorter.reserve(pointCount); + + for (int path = m_shape.getFirstPath(m_geometry); path != -1; path = m_shape + .getNextPath(path)) { + int vertex = m_shape.getFirstVertex(path); + for (int index = 0, n = m_shape.getPathSize(path); index < n; index++) { + verticesSorter.add(vertex); + vertex = m_shape.getNextVertex(vertex); + } + } + + // Sort + verticesSorter.Sort(0, pointCount, + new SimplificatorVertexComparer(this)); + // SORTDYNAMICARRAYEX(verticesSorter, int, 0, pointCount, + // SimplificatorVertexComparer, this); + + // Copy sorted vertices to the m_sortedVertices list. Make a mapping + // from the edit shape vertices to the sorted vertices. + m_userIndexSortedIndexToVertex = m_shape.createUserIndex();// this index + // is used + // to map + // from edit + // shape + // vertex to + // the + // m_sortedVertices + // list + m_sortedVertices = new IndexMultiDCList(); + m_sortedVerticesListIndex = m_sortedVertices.createList(0); + for (int i = 0; i < pointCount; i++) { + int vertex = verticesSorter.get(i); + {// debug + Point2D pt = new Point2D(); + m_shape.getXY(vertex, pt);// for debugging + double y = pt.x; + } + int vertexlistIndex = m_sortedVertices.addElement( + m_sortedVerticesListIndex, vertex); + m_shape.setUserIndex(vertex, m_userIndexSortedIndexToVertex, + vertexlistIndex);// remember the sorted list element on the + // vertex. + // When we remove a vertex, we also remove associated sorted list + // element. + } + + m_userIndexSortedAngleIndexToVertex = m_shape.createUserIndex();// create + // additional + // list + // to + // store + // angular + // sort + // mapping. + + m_nextVertexToProcess = -1; + + if (_cleanupSpikes())// cleanup any spikes on the polygon. + bChanged = true; + + // External iteration loop for the simplificator. + // ST. I am not sure if it actually needs this loop. TODO: figure this + // out. + while (bNeedWindingRepeat) { + bNeedWindingRepeat = false; + + int max_iter = m_shape.getPointCount(m_geometry) + 10 > 30 ? 1000 + : (m_shape.getPointCount(m_geometry) + 10) + * (m_shape.getPointCount(m_geometry) + 10); + + // Simplify polygon + int iRepeatNum = 0; + boolean bNeedRepeat = false; + + // Internal iteration loop for the simplificator. + // ST. I am not sure if it actually needs this loop. TODO: figure + // this out. + do// while (bNeedRepeat); + { + bNeedRepeat = false; + + boolean bVertexRecheck = false; + m_firstCoincidentVertex = -1; + int coincidentCount = 0; + Point2D ptFirst = new Point2D(); + Point2D pt = new Point2D(); + // Main loop of the simplificator. Go through the vertices and + // for those that have same coordinates, + for (int vlistindex = m_sortedVertices + .getFirst(m_sortedVerticesListIndex); vlistindex != IndexMultiDCList + .nullNode();) { + int vertex = m_sortedVertices.getData(vlistindex); + {// debug + // Point2D pt = new Point2D(); + m_shape.getXY(vertex, pt); + double d = pt.x; + } + + if (m_firstCoincidentVertex != -1) { + // Point2D pt = new Point2D(); + m_shape.getXY(vertex, pt); + if (ptFirst.isEqual(pt)) { + coincidentCount++; + } else { + ptFirst.setCoords(pt); + m_nextVertexToProcess = vlistindex;// we remeber the + // next index in + // the member + // variable to + // allow it to + // be updated if + // a vertex is + // removed + // inside of the + // _ProcessBunch. + if (coincidentCount > 0) { + boolean result = _processBunch();// process a + // bunch of + // coinciding + // vertices + if (result) {// something has changed. + // Note that ProcessBunch may + // change m_nextVertexToProcess + // and m_firstCoincidentVertex. + bNeedRepeat = true; + if (m_nextVertexToProcess != IndexMultiDCList + .nullNode()) { + int v = m_sortedVertices + .getData(m_nextVertexToProcess); + m_shape.getXY(v, ptFirst); + } + } + } + + vlistindex = m_nextVertexToProcess; + m_firstCoincidentVertex = vlistindex; + coincidentCount = 0; + } + } else { + m_firstCoincidentVertex = vlistindex; + m_shape.getXY(m_sortedVertices.getData(vlistindex), + ptFirst); + coincidentCount = 0; + } + + vlistindex = m_sortedVertices.getNext(vlistindex); + } + + m_nextVertexToProcess = -1; + + if (coincidentCount > 0) { + boolean result = _processBunch(); + if (result) + bNeedRepeat = true; + } + + if (iRepeatNum++ > 10) { + throw new GeometryException("internal error."); + } + + if (bNeedRepeat) + _fixOrphanVertices();// fix broken structure of the shape + + if (_cleanupSpikes()) + bNeedRepeat = true; + + bNeedWindingRepeat |= bNeedRepeat && bWinding; + + bChanged |= bNeedRepeat; + + } while (bNeedRepeat); + + }// while (bNeedWindingRepeat) + + // Now process rings. Fix ring orientation and determine rings that need + // to be deleted. + + m_shape.removeUserIndex(m_userIndexSortedIndexToVertex); + m_shape.removeUserIndex(m_userIndexSortedAngleIndexToVertex); + + bChanged |= RingOrientationFixer.execute(m_shape, m_geometry, + m_sortedVertices); + + return bChanged; + } + + private boolean _getDirection(int vert1, int vert2) { + if (m_shape.getNextVertex(vert2) == vert1) { + // _ASSERT(m_shape.getPrevVertex(vert1) == vert2); + return false; + } else { + // _ASSERT(m_shape.getPrevVertex(vert2) == vert1); + // _ASSERT(m_shape.getNextVertex(vert1) == vert2); + return true; + } + } + + private boolean _detectAndResolveCrossOver(boolean bDirection1, + boolean bDirection2, int vertexB1, int vertexA1, int vertexC1, + int vertexB2, int vertexA2, int vertexC2) { + // _ASSERT(!m_shape.isEqualXY(vertexB1, vertexB2)); + // _ASSERT(!m_shape.isEqualXY(vertexC1, vertexC2)); + + if (vertexA1 == vertexA2) { + _removeAngleSortInfo(vertexB1); + _removeAngleSortInfo(vertexB2); + return false; + } + + // _ASSERT(!m_shape.isEqualXY(vertexB1, vertexC2)); + // _ASSERT(!m_shape.isEqualXY(vertexB1, vertexC1)); + // _ASSERT(!m_shape.isEqualXY(vertexB2, vertexC2)); + // _ASSERT(!m_shape.isEqualXY(vertexB2, vertexC1)); + // _ASSERT(!m_shape.isEqualXY(vertexA1, vertexB1)); + // _ASSERT(!m_shape.isEqualXY(vertexA1, vertexC1)); + // _ASSERT(!m_shape.isEqualXY(vertexA2, vertexB2)); + // _ASSERT(!m_shape.isEqualXY(vertexA2, vertexC2)); + + // _ASSERT(m_shape.isEqualXY(vertexA1, vertexA2)); + + // get indices of the vertices for the angle sort. + int iB1 = m_shape.getUserIndex(vertexB1, + m_userIndexSortedAngleIndexToVertex); + int iC1 = m_shape.getUserIndex(vertexC1, + m_userIndexSortedAngleIndexToVertex); + int iB2 = m_shape.getUserIndex(vertexB2, + m_userIndexSortedAngleIndexToVertex); + int iC2 = m_shape.getUserIndex(vertexC2, + m_userIndexSortedAngleIndexToVertex); + // _ASSERT(iB1 >= 0); + // _ASSERT(iC1 >= 0); + // _ASSERT(iB2 >= 0); + // _ASSERT(iC2 >= 0); + // Sort the indices to restore the angle-sort order + int[] ar = new int[8]; + int[] br = new int[4]; + + ar[0] = 0; + br[0] = iB1; + ar[1] = 0; + br[1] = iC1; + ar[2] = 1; + br[2] = iB2; + ar[3] = 1; + br[3] = iC2; + for (int j = 1; j < 4; j++)// insertion sort + { + int key = br[j]; + int data = ar[j]; + int i = j - 1; + while (i >= 0 && br[i] > key) { + br[i + 1] = br[i]; + ar[i + 1] = ar[i]; + i--; + } + br[i + 1] = key; + ar[i + 1] = data; + } + + int detector = 0; + if (ar[0] != 0) + detector |= 1; + if (ar[1] != 0) + detector |= 2; + if (ar[2] != 0) + detector |= 4; + if (ar[3] != 0) + detector |= 8; + if (detector != 5 && detector != 10)// not an overlap + return false; + + if (bDirection1 == bDirection2) { + if (bDirection1) { + m_shape.setNextVertex_(vertexC2, vertexA1); // B1< >B2 + m_shape.setPrevVertex_(vertexA1, vertexC2); // \ / + m_shape.setNextVertex_(vertexC1, vertexA2); // A1A2 + m_shape.setPrevVertex_(vertexA2, vertexC1); // / \ // + // C2> C1 + } + } else { + if (bDirection1) { + m_shape.setPrevVertex_(vertexA1, vertexB2); // B1< >B2 + m_shape.setPrevVertex_(vertexB2, vertexA1); // \ / + m_shape.setNextVertex_(vertexA2, vertexC1); // A1A2 + m_shape.setPrevVertex_(vertexC1, vertexA2); // / \ // + // C2> >C1 + + } + } + + return true; + } + + private void _resolveOverlap(boolean bDirection1, boolean bDirection2, + int vertexA1, int vertexB1, int vertexA2, int vertexB2) { + if (m_bWinding) { + _resolveOverlapWinding(bDirection1, bDirection2, vertexA1, + vertexB1, vertexA2, vertexB2); + } else { + _resolveOverlapOddEven(bDirection1, bDirection2, vertexA1, + vertexB1, vertexA2, vertexB2); + } + } + + private void _resolveOverlapWinding(boolean bDirection1, + boolean bDirection2, int vertexA1, int vertexB1, int vertexA2, + int vertexB2) { + throw new GeometryException("not implemented."); + } + + private void _resolveOverlapOddEven(boolean bDirection1, + boolean bDirection2, int vertexA1, int vertexB1, int vertexA2, + int vertexB2) { + if (bDirection1 != bDirection2) { + if (bDirection1) { + // _ASSERT(m_shape.getNextVertex(vertexA1) == vertexB1); + // _ASSERT(m_shape.getNextVertex(vertexB2) == vertexA2); + m_shape.setNextVertex_(vertexA1, vertexA2); // B1< B2 + m_shape.setPrevVertex_(vertexA2, vertexA1); // | | + m_shape.setNextVertex_(vertexB2, vertexB1); // | | + m_shape.setPrevVertex_(vertexB1, vertexB2); // A1 >A2 + + _transferVertexData(vertexA2, vertexA1); + _beforeRemoveVertex(vertexA2, true); + m_shape.removeVertexInternal_(vertexA2, true); + _removeAngleSortInfo(vertexA1); + _transferVertexData(vertexB2, vertexB1); + _beforeRemoveVertex(vertexB2, false); + m_shape.removeVertexInternal_(vertexB2, false); + _removeAngleSortInfo(vertexB1); + } else { + // _ASSERT(m_shape.getNextVertex(vertexB1) == vertexA1); + // _ASSERT(m_shape.getNextVertex(vertexA2) == vertexB2); + m_shape.setNextVertex_(vertexA2, vertexA1); // B1 B2< + m_shape.setPrevVertex_(vertexA1, vertexA2); // | | + m_shape.setNextVertex_(vertexB1, vertexB2); // | | + m_shape.setPrevVertex_(vertexB2, vertexB1); // A1< A2 + + _transferVertexData(vertexA2, vertexA1); + _beforeRemoveVertex(vertexA2, false); + m_shape.removeVertexInternal_(vertexA2, false); + _removeAngleSortInfo(vertexA1); + _transferVertexData(vertexB2, vertexB1); + _beforeRemoveVertex(vertexB2, true); + m_shape.removeVertexInternal_(vertexB2, true); + _removeAngleSortInfo(vertexB1); + } + } else// bDirection1 == bDirection2 + { + if (!bDirection1) { + // _ASSERT(m_shape.getNextVertex(vertexB1) == vertexA1); + // _ASSERT(m_shape.getNextVertex(vertexB2) == vertexA2); + } else { + // _ASSERT(m_shape.getNextVertex(vertexA1) == vertexB1); + // _ASSERT(m_shape.getNextVertex(vertexA2) == vertexB2); + } + + // if (m_shape._RingParentageCheckInternal(vertexA1, vertexA2)) + { + int a1 = bDirection1 ? vertexA1 : vertexB1; + int a2 = bDirection2 ? vertexA2 : vertexB2; + int b1 = bDirection1 ? vertexB1 : vertexA1; + int b2 = bDirection2 ? vertexB2 : vertexA2; + + // m_shape.dbgVerifyIntegrity(a1);//debug + // m_shape.dbgVerifyIntegrity(a2);//debug + + boolean bVisitedA1 = false; + m_shape.setNextVertex_(a1, a2); // ^ | <--- ^ \ --. + m_shape.setNextVertex_(a2, a1); // | | | ^ | | + m_shape.setPrevVertex_(b1, b2); // | | | | | | + m_shape.setPrevVertex_(b2, b1); // | | | | | | + int v = b2; // | | | | =>| | + while (v != a2) // | | . | | -. | + { // | <-- | | | ./ | | + int prev = m_shape.getPrevVertex(v); // | | | | | | | | + int next = m_shape.getNextVertex(v); // <-+---<--- | + // <-+---<--- | + m_shape.setPrevVertex_(v, next); // --------. <-------- + m_shape.setNextVertex_(v, prev); + bVisitedA1 |= v == a1; + v = next; + } + + if (!bVisitedA1) { + // a case of two rings being merged + int prev = m_shape.getPrevVertex(a2); + int next = m_shape.getNextVertex(a2); + m_shape.setPrevVertex_(a2, next); + m_shape.setNextVertex_(a2, prev); + } else { + // merge happend on the same ring. + } + + // m_shape.dbgVerifyIntegrity(b1);//debug + // m_shape.dbgVerifyIntegrity(a1);//debug + + _transferVertexData(a2, a1); + _beforeRemoveVertex(a2, true); + m_shape.removeVertexInternal_(a2, false); + _removeAngleSortInfo(a1); + _transferVertexData(b2, b1); + _beforeRemoveVertex(b2, true); + m_shape.removeVertexInternal_(b2, false); + _removeAngleSortInfo(b1); + + // m_shape.dbgVerifyIntegrity(b1);//debug + // m_shape.dbgVerifyIntegrity(a1);//debug + } + // else + // { + // m_shape._ReverseRingInternal(vertexA2); + // _ResolveOverlapOddEven(bDirection1, !bDirection2, vertexA1, + // vertexB1, vertexA2, vertexB2); + // } + } + } + + private boolean _cleanupSpikes() { + boolean bModified = false; + for (int path = m_shape.getFirstPath(m_geometry); path != -1;) { + int vertex = m_shape.getFirstVertex(path); + for (int vindex = 0, n = m_shape.getPathSize(path); vindex < n + && n > 1;) { + int prev = m_shape.getPrevVertex(vertex); + int next = m_shape.getNextVertex(vertex); + if (m_shape.isEqualXY(prev, next)) { + bModified = true; + _beforeRemoveVertex(vertex, false); + m_shape.removeVertex(vertex, true);// not internal, because + // path is valid at this + // point + _beforeRemoveVertex(next, false); + m_shape.removeVertex(next, true); + vertex = prev; + vindex = 0; + n = m_shape.getPathSize(path); + } else { + vertex = next; + vindex++; + } + } + + if (m_shape.getPathSize(path) < 2) { + int vertexL = m_shape.getFirstVertex(path); + for (int vindex = 0, n = m_shape.getPathSize(path); vindex < n; vindex++) { + _beforeRemoveVertex(vertexL, false); + vertexL = m_shape.getNextVertex(vertexL); + } + + path = m_shape.removePath(path); + bModified = true; + } else + path = m_shape.getNextPath(path); + } + + return bModified; + } + + private boolean _removeSpike(int vertexIn) { + // m_shape.dbgVerifyIntegrity(vertex);//debug + int vertex = vertexIn; + + // _ASSERT(m_shape.isEqualXY(m_shape.getNextVertex(vertex), + // m_shape.getPrevVertex(vertex))); + boolean bFound = false; + while (true) { + int next = m_shape.getNextVertex(vertex); + int prev = m_shape.getPrevVertex(vertex); + if (next == vertex) {// last vertex in a ring + _beforeRemoveVertex(vertex, true); + m_shape.removeVertexInternal_(vertex, false); + return true; + } + + if (!m_shape.isEqualXY(next, prev)) + break; + + bFound = true; + _removeAngleSortInfo(prev); + _removeAngleSortInfo(next); + _beforeRemoveVertex(vertex, true); + m_shape.removeVertexInternal_(vertex, false); + // m_shape.dbgVerifyIntegrity(prev);//debug + _transferVertexData(next, prev); + _beforeRemoveVertex(next, true); + m_shape.removeVertexInternal_(next, true); + if (next == prev) + break;// deleted the last vertex + + // m_shape.dbgVerifyIntegrity(prev);//debug + + vertex = prev; + } + return bFound; + } + + private void _fixOrphanVertices() { + int pathCount = 0; + // clean any path info + for (int node = m_sortedVertices.getFirst(m_sortedVertices + .getFirstList()); node != -1; node = m_sortedVertices + .getNext(node)) { + int vertex = m_sortedVertices.getData(node); + m_shape.setPathToVertex_(vertex, -1); + } + int geometrySize = 0; + for (int path = m_shape.getFirstPath(m_geometry); path != -1;) { + int first = m_shape.getFirstVertex(path); + if (first == -1 || m_shape.getPathFromVertex(first) != -1) { + int p = path; + path = m_shape.getNextPath(path); + m_shape.removePathOnly_(p); + continue; + } + + m_shape.setPathToVertex_(first, path); + int pathSize = 1; + for (int vertex = m_shape.getNextVertex(first); vertex != first; vertex = m_shape + .getNextVertex(vertex)) { + // _ASSERT(m_shape.getPathFromVertex(vertex) == -1); + m_shape.setPathToVertex_(vertex, path); + // _ASSERT(m_shape.getNextVertex(m_shape.getPrevVertex(vertex)) + // == vertex); + pathSize++; + } + m_shape.setPathSize_(path, pathSize); + m_shape.setLastVertex_(path, m_shape.getPrevVertex(first)); + geometrySize += pathSize; + pathCount++; + path = m_shape.getNextPath(path); + } + + // produce new paths for the orphan vertices. + for (int node = m_sortedVertices.getFirst(m_sortedVertices + .getFirstList()); node != -1; node = m_sortedVertices + .getNext(node)) { + int vertex = m_sortedVertices.getData(node); + if (m_shape.getPathFromVertex(vertex) != -1) + continue; + int path = m_shape.insertPath(m_geometry, -1); + int pathSize = 0; + int first = vertex; + while (true) { + m_shape.setPathToVertex_(vertex, path); + pathSize++; + int next = m_shape.getNextVertex(vertex); + // _ASSERT(m_shape.getNextVertex(m_shape.getPrevVertex(vertex)) + // == vertex); + if (next == first) + break; + vertex = next; + } + + m_shape.setClosedPath(path, true); + + m_shape.setPathSize_(path, pathSize); + m_shape.setFirstVertex_(path, first); + m_shape.setLastVertex_(path, m_shape.getPrevVertex(first)); + geometrySize += pathSize; + pathCount++; + } + m_shape.setGeometryPathCount_(m_geometry, pathCount); + int totalPointCount = m_shape.getTotalPointCount() + - m_shape.getPointCount(m_geometry); + m_shape.setGeometryVertexCount_(m_geometry, geometrySize); + m_shape.setTotalPointCount_(totalPointCount + geometrySize); + } + + private int _getNextEdgeIndex(int indexIn) { + int index = indexIn; + for (int i = 0, n = m_bunchEdgeIndices.size() - 1; i < n; i++) { + index = (index + 1) % m_bunchEdgeIndices.size(); + if (m_bunchEdgeIndices.get(index) != -1) + return index; + } + return -1; + } + + private void _transferVertexData(int vertexFrom, int vertexTo) { + int v1 = m_shape.getUserIndex(vertexTo, m_userIndexSortedIndexToVertex); + int v2 = m_shape.getUserIndex(vertexTo, + m_userIndexSortedAngleIndexToVertex); + m_shape.transferAllDataToTheVertex(vertexFrom, vertexTo); + m_shape.setUserIndex(vertexTo, m_userIndexSortedIndexToVertex, v1); + m_shape.setUserIndex(vertexTo, m_userIndexSortedAngleIndexToVertex, v2); + } + + private void _removeAngleSortInfo(int vertex) { + int angleIndex = m_shape.getUserIndex(vertex, + m_userIndexSortedAngleIndexToVertex); + if (angleIndex != -1) { + m_bunchEdgeIndices.set(angleIndex, -1); + m_shape.setUserIndex(vertex, m_userIndexSortedAngleIndexToVertex, + -1); + } + } + + protected Simplificator() { + m_dbgCounter = 0; + } + + public static boolean execute(EditShape shape, int geometry, + int knownSimpleResult) { + Simplificator simplificator = new Simplificator(); + simplificator.m_shape = shape; + // simplificator.m_bWinding = bWinding; + simplificator.m_geometry = geometry; + simplificator.m_knownSimpleResult = knownSimpleResult; + return simplificator._simplify(); + } + + int _compareVerticesSimple(int v1, int v2) { + Point2D pt1 = new Point2D(); + m_shape.getXY(v1, pt1); + Point2D pt2 = new Point2D(); + m_shape.getXY(v2, pt2); + int res = pt1.compare(pt2); + return res; + } + + int _compareAngles(int index1, int index2) { + int vert1 = m_bunchEdgeEndPoints.get(index1); + Point2D pt1 = new Point2D(); + m_shape.getXY(vert1, pt1); + Point2D pt2 = new Point2D(); + int vert2 = m_bunchEdgeEndPoints.get(index2); + m_shape.getXY(vert2, pt2); + + if (pt1.isEqual(pt2)) + return 0;// overlap case + + int vert10 = m_bunchEdgeCenterPoints.get(index1); + Point2D pt10 = new Point2D(); + m_shape.getXY(vert10, pt10); + + int vert20 = m_bunchEdgeCenterPoints.get(index2); + Point2D pt20 = new Point2D(); + m_shape.getXY(vert20, pt20); + // _ASSERT(pt10.isEqual(pt20)); + + Point2D v1 = new Point2D(); + v1.sub(pt1, pt10); + Point2D v2 = new Point2D(); + v2.sub(pt2, pt20); + int result = Point2D._compareVectors(v1, v2); + return result; + } +} diff --git a/src/com/esri/core/geometry/SpatialReference.java b/src/com/esri/core/geometry/SpatialReference.java new file mode 100644 index 00000000..cf1962e2 --- /dev/null +++ b/src/com/esri/core/geometry/SpatialReference.java @@ -0,0 +1,177 @@ +/* + Copyright 1995-2013 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ + +package com.esri.core.geometry; + +import java.io.ObjectStreamException; +import java.io.Serializable; +import org.codehaus.jackson.JsonParser; +import org.codehaus.jackson.JsonToken; + +import com.esri.core.geometry.SpatialReferenceSerializer; +import com.esri.core.geometry.VertexDescription; + +/** + * A class that represents the spatial reference for the geometry. + */ +public abstract class SpatialReference implements Serializable { + // Note: We use writeReplace with SpatialReferenceSerializer. This field is + // irrelevant. Needs to be removed after final. + private static final long serialVersionUID = 2L; + + /** + * Creates an instance of the spatial reference based on the provided well + * known ID for the horizontal coordinate system. + * + * @param wkid + * The well-known ID. + * @return SpatialReference The spatial reference. + * @throws IllegalArgumentException + * if wkid is not supported or does not exist. + */ + public static SpatialReference create(int wkid) { + SpatialReferenceImpl spatRef = SpatialReferenceImpl.createImpl(wkid); + return spatRef; + } + + /** + * Creates an instance of the spatial reference based on the provided well + * known text representation for the horizontal coordinate system. + * + * @param wktext + * The well-known text string representation of spatial + * reference. + * @return SpatialReference The spatial reference. + */ + public static SpatialReference create(String wktext) { + return SpatialReferenceImpl.createImpl(wktext); + } + + /** + * @return boolean Is spatial reference local? + */ + boolean isLocal() { + return false; + } + + /** + * Returns spatial reference from the JsonParser. + * + * @param parser + * The JSON parser. + * @return The spatial reference or null if there is no spatial reference + * information, or the parser does not point to an object start. + * @throws Exception + * if parsing has failed + */ + public static SpatialReference fromJson(JsonParser parser) throws Exception { + int wkid = 0; + String wkt = null; + if (!JSONUtils.isObjectStart(parser)) + return null; + + while (parser.nextToken() != JsonToken.END_OBJECT) { + String fieldName = parser.getCurrentName(); + parser.nextToken(); + if (parser.getCurrentToken() == JsonToken.VALUE_NULL) { + continue; + } + + if ("latestWkid".equals(fieldName)) {// get wkid + wkid = parser.getIntValue(); + } else if ("wkid".equals(fieldName)) {// get wkid + wkid = parser.getIntValue(); + } else if ("wkt".equals(fieldName)) { + wkt = parser.getText(); + } else { + parser.skipChildren(); + } + } + // END _OBJECT + + if (wkid > 0) // 1. Try to use wkid + { + try { + return SpatialReference.create(wkid); + } catch (IllegalArgumentException ex) { + // if (wkt == null || wkt.length() == 0) //Here this will be our + // default. + // throw ex; + } + } + + if (wkt != null && wkt.length() != 0) // try to use wkt. + { + return SpatialReference.create(wkt); + } + + return null; + } + + /** + * Returns the well known ID for the horizontal coordinate system of the + * spatial reference. + * + * @return wkid The well known ID. + */ + public abstract int getID(); + + public abstract String getText(); + + /** + * Returns the oldest value of the well known ID for the horizontal + * coordinate system of the spatial reference. This ID is used for JSON + * serialization. Not public. + */ + abstract int getOldID(); + + /** + * Returns the latest value of the well known ID for the horizontal + * coordinate system of the spatial reference. This ID is used for JSON + * serialization. Not public. + */ + abstract int getLatestID(); + + /** + * Get the XY tolerance of the spatial reference + * + * @return The XY tolerance of the spatial reference as double. + */ + public double getTolerance() { + return getTolerance(VertexDescription.Semantics.POSITION); + } + + /** + * Get the XY tolerance of the spatial reference + * + * @return The XY tolerance of the spatial reference as double. + */ + abstract double getTolerance(int semantics); + + Object writeReplace() throws ObjectStreamException { + SpatialReferenceSerializer srSerializer = new SpatialReferenceSerializer(); + srSerializer.setSpatialReferenceByValue(this); + return srSerializer; + } +} diff --git a/src/com/esri/core/geometry/SpatialReferenceImpl.java b/src/com/esri/core/geometry/SpatialReferenceImpl.java new file mode 100644 index 00000000..7da2b1ef --- /dev/null +++ b/src/com/esri/core/geometry/SpatialReferenceImpl.java @@ -0,0 +1,239 @@ +/* + Copyright 1995-2013 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ + +package com.esri.core.geometry; + +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.locks.ReentrantLock; + +import java.lang.ref.*; + +import com.esri.core.geometry.Envelope2D; +import com.esri.core.geometry.GeoDist; +import com.esri.core.geometry.GeometryException; +import com.esri.core.geometry.PeDouble; +import com.esri.core.geometry.Point; +import com.esri.core.geometry.Polyline; +import com.esri.core.geometry.SpatialReference; +import com.esri.core.geometry.SpatialReferenceImpl; +import com.esri.core.geometry.VertexDescription.Semantics; + +class SpatialReferenceImpl extends SpatialReference { + static boolean no_projection_engine = true; + public static int c_SULIMIT32 = 2147483645; + public static long c_SULIMIT64 = 9007199254740990L; + + enum Precision { + Integer32, Integer64, FloatingPoint + }; + + int m_userWkid;// this wkid is provided by user to the create method. + int m_userLatestWkid; + int m_userOldestWkid; + String m_userWkt;// a string, the well-known text. + + // public SgCoordRef m_sgCoordRef; + + private final static ReentrantLock m_lock = new ReentrantLock(); + + // TODO If one was going to create member object for locking it would be + // here. + SpatialReferenceImpl() { + m_userWkid = 0; + m_userLatestWkid = -1; + m_userOldestWkid = -1; + m_userWkt = null; + } + + @Override + public int getID() { + return m_userWkid; + } + + double getFalseX() { + return 0; + } + + double getFalseY() { + return 0; + } + + double getFalseZ() { + return 0; + } + + double getFalseM() { + return 0; + } + + double getGridUnitsXY() { + return 1 / (1.0e-9 * 0.0174532925199433/* getOneDegreeGCSUnit() */); + } + + double getGridUnitsZ() { + return 1 / 0.001; + } + + double getGridUnitsM() { + return 1 / 0.001; + } + + Precision getPrecision() { + return Precision.Integer64; + } + + @Override + double getTolerance(int semantics) { + double tolerance = 0.001; + if (m_userWkid != 0) { + tolerance = Wkid.find_tolerance_from_wkid(m_userWkid); + } else if (m_userWkt != null) { + tolerance = Wkt.find_tolerance_from_wkt(m_userWkt); + } + return tolerance; + } + + public void queryValidCoordinateRange(Envelope2D env2D) { + double delta = 0; + switch (getPrecision()) { + case Integer32: + delta = c_SULIMIT32 / getGridUnitsXY(); + break; + case Integer64: + delta = c_SULIMIT64 / getGridUnitsXY(); + break; + default: + // TODO + throw new GeometryException("internal error");// fixme + } + + env2D.setCoords(getFalseX(), getFalseY(), getFalseX() + delta, + getFalseY() + delta); + } + + public boolean requiresReSimplify(SpatialReference dst) { + return dst != this;// FIXME: needs to be smarter. + } + + @Override + public String getText() { + return m_userWkt; + } + + /** + * Returns the oldest value of the well known ID for the horizontal + * coordinate system of the spatial reference. This ID is used for JSON + * serialization. Not public. + */ + @Override + int getOldID() { + int ID_ = getID(); + + if (m_userOldestWkid != -1) + return m_userOldestWkid; + + m_userOldestWkid = Wkid.wkid_to_old(ID_); + + if (m_userOldestWkid != -1) + return m_userOldestWkid; + + return ID_; + } + + /** + * Returns the latest value of the well known ID for the horizontal + * coordinate system of the spatial reference. This ID is used for JSON + * serialization. Not public. + */ + @Override + int getLatestID() { + int ID_ = getID(); + + if (m_userLatestWkid != -1) + return m_userLatestWkid; + + m_userLatestWkid = Wkid.wkid_to_new(ID_); + + if (m_userLatestWkid != -1) + return m_userLatestWkid; + + return ID_; + } + + public static SpatialReferenceImpl createImpl(int wkid) { + if (wkid <= 0) + throw new IllegalArgumentException("Invalid or unsupported wkid: " + + wkid); + + SpatialReferenceImpl spatRef = new SpatialReferenceImpl(); + spatRef.m_userWkid = wkid; + + return spatRef; + } + + public static SpatialReferenceImpl createImpl(String wkt) { + if (wkt == null || wkt.length() == 0) + throw new IllegalArgumentException( + "Cannot create SpatialReference from null or empty text."); + + SpatialReferenceImpl spatRef = new SpatialReferenceImpl(); + spatRef.m_userWkt = wkt; + + return spatRef; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + + SpatialReferenceImpl sr = (SpatialReferenceImpl) obj; + if (m_userWkid != sr.m_userWkid) + return false; + + if (m_userWkid == 0) { + if (!m_userWkt.equals(m_userWkt))// m_userWkt cannot be null here! + return false; + } + + return true; + } + + static double geodesicDistanceOnWGS84Impl(Point ptFrom, Point ptTo) { + double a = 6378137.0; // radius of spheroid for WGS_1984 + double e2 = 0.0066943799901413165; // ellipticity for WGS_1984 + double rpu = Math.PI / 180.0; + PeDouble answer = new PeDouble(); + GeoDist.geodesic_distance_ngs(a, e2, ptFrom.getXY().x * rpu, + ptFrom.getXY().y * rpu, ptTo.getXY().x * rpu, ptTo.getXY().y + * rpu, answer, null, null); + return answer.val; + } + +} diff --git a/src/com/esri/core/geometry/SpatialReferenceSerializer.java b/src/com/esri/core/geometry/SpatialReferenceSerializer.java new file mode 100644 index 00000000..5df978f2 --- /dev/null +++ b/src/com/esri/core/geometry/SpatialReferenceSerializer.java @@ -0,0 +1,63 @@ +/* + Copyright 1995-2013 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ + +package com.esri.core.geometry; + +import java.io.InvalidObjectException; +import java.io.ObjectStreamException; +import java.io.Serializable; + +import com.esri.core.geometry.SpatialReference; + +final class SpatialReferenceSerializer implements Serializable { + private static final long serialVersionUID = 10000L; + String wkt = null; + int wkid = 0; + + Object readResolve() throws ObjectStreamException { + SpatialReference sr = null; + try { + if (wkid > 0) + sr = SpatialReference.create(wkid); + else + sr = SpatialReference.create(wkt); + } catch (Exception ex) { + throw new InvalidObjectException( + "Cannot read spatial reference from stream"); + } + return sr; + } + + public void setSpatialReferenceByValue(SpatialReference sr) + throws ObjectStreamException { + try { + if (sr.getID() > 0) + wkid = sr.getID(); + else + wkt = sr.getText(); + } catch (Exception ex) { + throw new InvalidObjectException("Cannot serialize this geometry"); + } + } +} diff --git a/src/com/esri/core/geometry/StridedIndexTypeCollection.java b/src/com/esri/core/geometry/StridedIndexTypeCollection.java new file mode 100644 index 00000000..c2451b34 --- /dev/null +++ b/src/com/esri/core/geometry/StridedIndexTypeCollection.java @@ -0,0 +1,247 @@ +/* + Copyright 1995-2013 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ +package com.esri.core.geometry; + +import java.util.ArrayList; + +/** + * A collection of strides of Index_type elements. To be used when one needs a + * collection of homogeneous elements that contain only integer fields (i.e. + * structs with Index_type members) Recycles the strides. Allows for constant + * time creation and deletion of an element. + */ +final class StridedIndexTypeCollection { + + private int m_firstFree; + private int m_last; + private int m_stride; + private int m_realStride; + private int m_realBlockSize; + private int m_blockSize; + private int m_blockMask; + private int m_blockPower; + private int m_size; + private int m_capacity; + private ArrayList m_buffer; + + StridedIndexTypeCollection(int stride) { + m_firstFree = -1; + m_last = 0; + m_size = 0; + m_stride = stride; + m_realStride = stride; + m_realBlockSize = 2048;//if you change this, change m_blockSize, m_blockPower, m_blockMask, and st_sizes + m_blockPower = 11; + m_blockMask = 0x7FF; + m_blockSize = 2048 / m_realStride; + m_capacity = 0; + m_buffer = null; + } + + void deleteElement(int element) { + assert dbgdelete_(element); + int totalStrides = (element >> m_blockPower) * m_blockSize + * m_realStride + (element & m_blockMask); + if (totalStrides < m_last * m_realStride) { + m_buffer.get(element >> m_blockPower)[element & m_blockMask] = m_firstFree; + m_firstFree = element; + } else { + assert (totalStrides == m_last * m_realStride); + m_last--; + } + m_size--; + } + + // Returns the given field of the element. + int getField(int element, int field) { + return m_buffer.get(element >> m_blockPower)[(element & m_blockMask) + + field]; + } + + // Sets the given field of the element. + void setField(int element, int field, int value) { + m_buffer.get(element >> m_blockPower)[(element & m_blockMask) + field] = value; + } + + // Returns the stride size + int getStride() { + return m_stride; + } + + // Creates the new element. This is a constant time operation. + // All fields are initialized to -1. + int newElement() { + int element = m_firstFree; + if (element == -1) { + if (m_last == m_capacity) { + grow_(m_capacity != 0 ? ((m_capacity + 1) * 3 / 2) : 1); + } + + element = ((m_last / m_blockSize) << m_blockPower) + + (m_last % m_blockSize) * m_realStride; + m_last++; + } else { + m_firstFree = m_buffer.get(element >> m_blockPower)[element + & m_blockMask]; + } + + m_size++; + int ar[] = m_buffer.get(element >> m_blockPower); + int ind = element & m_blockMask; + for (int i = 0; i < m_stride; i++) { + ar[ind + i] = -1; + } + return element; + } + + int elementToIndex(int element) { + return (element >> m_blockPower) * m_blockSize + + (element & m_blockMask) / m_realStride; + } + + // Deletes all elements and frees all the memory if b_free_memory is True. + void deleteAll(boolean b_free_memory) { + m_firstFree = -1; + m_last = 0; + m_size = 0; + if (b_free_memory) { + m_buffer = null; + m_capacity = 0; + } + } + + // Returns the count of existing elements + int size() { + return m_size; + } + + // Sets the capcity of the collection. Only applied if current capacity is + // smaller. + void setCapacity(int capacity) { + if (capacity > m_capacity) + grow_(capacity); + } + + // Returns the capacity of the collection + int capacity() { + return m_capacity; + } + + // Swaps content of two elements (each field of the stride) + void swap(int element1, int element2) { + int ar1[] = m_buffer.get(element1 >> m_blockPower); + int ar2[] = m_buffer.get(element2 >> m_blockPower); + int ind1 = element1 & m_blockMask; + int ind2 = element2 & m_blockMask; + for (int i = 0; i < m_stride; i++) { + int tmp = ar1[ind1 + i]; + ar1[ind1 + i] = ar2[ind2 + i]; + ar2[ind2 + i] = tmp; + } + } + + // Swaps content of two fields + void swapField(int element1, int element2, int field) { + int ar1[] = m_buffer.get(element1 >> m_blockPower); + int ar2[] = m_buffer.get(element2 >> m_blockPower); + int ind1 = (element1 & m_blockMask) + field; + int ind2 = (element2 & m_blockMask) + field; + int tmp = ar1[ind1]; + ar1[ind1] = ar2[ind2]; + ar2[ind2] = tmp; + } + + // Returns a value of the index, that never will be returned by new_element + // and is neither -1 nor impossible_index_3. + static int impossibleIndex2() { + return -2; + } + + // Returns a value of the index, that never will be returned by new_element + // and is neither -1 nor impossible_index_2. + static int impossibleIndex3() { + return -3; + } + + static boolean isValidElement(int element) { + return element >= 0; + } + + private boolean dbgdelete_(int element) { + setField(element, 1, 0x7eadbeed); + return true; + } + + static int[] st_sizes = {16, 32, 64, 128, 256, 512, 1024, 2048}; + + private void grow_(int newsize) { + if (m_buffer == null) { + m_buffer = new ArrayList(); + } + + assert (newsize > m_capacity); + + int nblocks = (newsize + m_blockSize - 1) / m_blockSize; + m_buffer.ensureCapacity(nblocks); + if (nblocks == 1) { + // When less than one block is needed we allocate smaller arrays + // than m_realBlockSize to avoid initialization cost. + int oldsz = m_capacity > 0 ? m_capacity : 0; + assert (oldsz < newsize); + int i = 0; + int realnewsize = newsize * m_realStride; + while (realnewsize > st_sizes[i]) + // get the size to allocate. Using fixed sizes to reduce + // fragmentation. + i++; + int[] b = new int[st_sizes[i]]; + if (m_buffer.size() == 1) { + System.arraycopy(m_buffer.get(0), 0, b, 0, m_size + * m_realStride); + m_buffer.set(0, b); + } else { + m_buffer.add(b); + } + m_capacity = b.length / m_realStride; + } else { + if (m_buffer.size() == 1) { + if (m_buffer.get(0).length < m_realBlockSize) { + // resize the first buffer to ensure it is equal the + // m_realBlockSize. + int[] b = new int[m_realBlockSize]; + System.arraycopy(m_buffer.get(0), 0, b, 0, m_size + * m_realStride); + m_buffer.set(0, b); + m_capacity = m_blockSize; + } + } + + while (m_buffer.size() < nblocks) { + m_buffer.add(new int[m_realBlockSize]); + m_capacity += m_blockSize; + } + } + } + +} diff --git a/src/com/esri/core/geometry/StringUtils.java b/src/com/esri/core/geometry/StringUtils.java new file mode 100644 index 00000000..c9267b5c --- /dev/null +++ b/src/com/esri/core/geometry/StringUtils.java @@ -0,0 +1,68 @@ +/* + Copyright 1995-2013 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ +package com.esri.core.geometry; + +class StringUtils { + static void appendDouble(double value, int precision, + StringBuilder stringBuilder) { + if (precision < 0) + precision = 0; + else if (precision > 17) + precision = 17; + + String format = "%." + precision + "g"; + + String str_dbl = String.format(format, value); + + boolean b_found_dot = false; + boolean b_found_exponent = false; + + for (int i = 0; i < str_dbl.length(); i++) { + char c = str_dbl.charAt(i); + + if (c == '.') + b_found_dot = true; + else if (c == 'e' || c == 'E') + b_found_exponent = true; + } + + if (b_found_dot && !b_found_exponent) { + StringBuilder buffer = new StringBuilder(str_dbl); + int non_zero = buffer.length() - 1; + + while (buffer.charAt(non_zero) == '0') + non_zero--; + + buffer.delete(non_zero + 1, buffer.length()); + + if (buffer.charAt(non_zero) == '.') + buffer.deleteCharAt(non_zero); + + stringBuilder.append(buffer); + } else { + stringBuilder.append(str_dbl); + } + } + +} diff --git a/src/com/esri/core/geometry/SweepComparator.java b/src/com/esri/core/geometry/SweepComparator.java new file mode 100644 index 00000000..9fb76798 --- /dev/null +++ b/src/com/esri/core/geometry/SweepComparator.java @@ -0,0 +1,677 @@ +/* + Copyright 1995-2013 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ +/* + * To change this template, choose Tools | Templates + * and open the template in the editor. + */ +package com.esri.core.geometry; + +import java.util.ArrayList; + +class SweepComparator extends Treap.Comparator { + static final class SimpleEdge { + int m_value; + Line m_line; + Segment m_segment; + Envelope1D m_env; + double m_dxdy; + boolean m_b_horizontal; + boolean m_b_curve; + + SimpleEdge() { + m_value = -1; + m_line = new Line(); + m_dxdy = 55555555; + m_b_horizontal = false; + m_b_curve = false; + + m_env = new Envelope1D(); + m_env.setCoordsNoNaN_(0, 0); + } + } + + private EditShape m_shape; + boolean m_b_intersection_detected; + NonSimpleResult m_non_simple_result; + // Index 1 corresponds to the left segments, index 2 - right, e.g. m_line_1, + // m_line_2 + SimpleEdge m_temp_simple_edge_1; + SimpleEdge m_temp_simple_edge_2; + + int m_prev_1; + int m_prev_2; + int m_vertex_1; + int m_vertex_2; + int m_current_node; + double m_prevx_1; + double m_prevx_2; + double m_prev_y; + double m_prev_x; + double m_sweep_y; + double m_sweep_x; + double m_tolerance; + double m_tolerance_10; + boolean m_b_is_simple; + + ArrayList m_simple_edges_cache; + ArrayList m_simple_edges_recycle; + ArrayList m_simple_edges_buffer; + + // Returns a cached edge for the given value. May return NULL. + SimpleEdge tryGetCachedEdge_(int value) { + SimpleEdge se = m_simple_edges_cache.get((value & NumberUtils.intMax()) + % m_simple_edges_cache.size()); + if (se != null) { + if (se.m_value == value) + return se; + else { + // int i = 0; + // cache collision + } + } + return null; + } + + // Removes cached edge from the cache for the given value. + void tryDeleteCachedEdge_(int value) { + int ind = (value & NumberUtils.intMax()) % m_simple_edges_cache.size(); + SimpleEdge se = m_simple_edges_cache.get(ind); + if (se != null && se.m_value == value) {// this value is cached + m_simple_edges_recycle.add(se); + m_simple_edges_cache.set(ind, null); + } else { + // The value has not been cached + } + } + + // Creates a cached edge. May fail and return NULL. + SimpleEdge tryCreateCachedEdge_(int value) { + int ind = (value & NumberUtils.intMax()) % m_simple_edges_cache.size(); + SimpleEdge se = m_simple_edges_cache.get(ind); + if (se == null) { + if (m_simple_edges_recycle.isEmpty()) { + // assert(m_simple_edges_buffer.size() < + // m_simple_edges_buffer.capacity());//should never happen + // assert(m_simple_edges_buffer.size() < + // m_simple_edges_cache.size());//should never happen + m_simple_edges_buffer.add(new SimpleEdge()); + se = m_simple_edges_buffer + .get(m_simple_edges_buffer.size() - 1); + } else { + se = m_simple_edges_recycle + .get(m_simple_edges_recycle.size() - 1); + m_simple_edges_recycle + .remove(m_simple_edges_recycle.size() - 1); + } + + se.m_value = value; + m_simple_edges_cache.set(ind, se); + return se; + } else { + assert (se.m_value != value);// do not call TryCreateCachedEdge + // twice. + } + + return null; + } + + void initSimpleEdge_(SweepComparator.SimpleEdge se, int vertex) { + se.m_segment = m_shape.getSegment(vertex); + se.m_b_curve = se.m_segment != null; + if (!se.m_b_curve) { + m_shape.queryLineConnector(vertex, se.m_line); + se.m_segment = se.m_line; + se.m_env.setCoordsNoNaN_(se.m_line.getStartX(), se.m_line.getEndX()); + se.m_env.vmax += m_tolerance; + se.m_line.orientBottomUp_(); + se.m_b_horizontal = se.m_line.getEndY() == se.m_line.getStartY(); + if (!se.m_b_horizontal) { + se.m_dxdy = (se.m_line.getEndX() - se.m_line.getStartX()) + / (se.m_line.getEndY() - se.m_line.getStartY()); + } + } else { + // se.m_segment = se.m_segment_sptr.get(); + } + } + + // Compares seg_1 and seg_2 x coordinates of intersection with the line + // parallel to axis x, passing through the coordinate y. + // If segments intersect not at the endpoint, the m_b_intersection_detected + // is set. + int compareTwoSegments_(Segment seg_1, Segment seg_2) { + int res = seg_1._isIntersecting(seg_2, m_tolerance, true); + if (res != 0) { + if (res == 2) + return errorCoincident(); + else + return errorCracking(); + } + + Point2D start_1 = seg_1.getStartXY(); + Point2D end1 = seg_1.getEndXY(); + Point2D start2 = seg_2.getStartXY(); + Point2D end2 = seg_2.getEndXY(); + Point2D ptSweep = new Point2D(); + ptSweep.setCoords(m_sweep_x, m_sweep_y); + if (start_1.isEqual(start2) && m_sweep_y == start_1.y) { + assert (start_1.compare(end1) < 0 && start2.compare(end2) < 0); + if (end1.compare(end2) < 0) + ptSweep.setCoords(end1); + else + ptSweep.setCoords(end2); + } else if (start_1.isEqual(end2) && m_sweep_y == start_1.y) { + assert (start_1.compare(end1) < 0 && start2.compare(end2) > 0); + if (end1.compare(start2) < 0) + ptSweep.setCoords(end1); + else + ptSweep.setCoords(start2); + } else if (start2.isEqual(end1) && m_sweep_y == start2.y) { + assert (end1.compare(start_1) < 0 && start2.compare(end2) < 0); + if (start_1.compare(end2) < 0) + ptSweep.setCoords(start_1); + else + ptSweep.setCoords(end2); + } else if (end1.isEqual(end2) && m_sweep_y == end1.y) { + assert (start_1.compare(end1) > 0 && start2.compare(end2) > 0); + if (start_1.compare(start2) < 0) + ptSweep.setCoords(start_1); + else + ptSweep.setCoords(start2); + } + + double xleft = seg_1.intersectionOfYMonotonicWithAxisX(ptSweep.y, + ptSweep.x); + double xright = seg_2.intersectionOfYMonotonicWithAxisX(ptSweep.y, + ptSweep.x); + assert (xleft != xright); + return xleft < xright ? -1 : 1; + } + + int compareNonHorizontal_(SimpleEdge line_1, SimpleEdge line_2) { + if (line_1.m_line.getStartY() == line_2.m_line.getStartY() + && line_1.m_line.getStartX() == line_2.m_line.getStartX()) {// connected + // at + // the + // start + // V + // shape + if (line_1.m_line.getEndY() == line_2.m_line.getEndY() + && line_1.m_line.getEndX() == line_2.m_line.getEndX()) {// connected + // at + // another + // end + // also + if (m_b_is_simple) + return errorCoincident(); + return 0; + } + + return compareNonHorizontalUpperEnd_(line_1, line_2); + } + + if (line_1.m_line.getEndY() == line_2.m_line.getEndY() + && line_1.m_line.getEndX() == line_2.m_line.getEndX()) { + // the case of upside-down V. + return compareNonHorizontalLowerEnd_(line_1, line_2); + } + + int lower = compareNonHorizontalLowerEnd_(line_1, line_2); + int upper = compareNonHorizontalUpperEnd_(line_1, line_2); + if (lower < 0 && upper < 0) + return -1; + if (lower > 0 && upper > 0) + return 1; + + return errorCracking(); + } + + int compareHorizontal1Case1_(Line line_1, Line line_2) { + // line_2 goes up and line_1 is horizontal connected at the start going + // to the right. + if (line_1.getEndX() > line_2.getEndX()) { + // / + // / + // +------------------ + if (line_2.getEndX() > line_2.getStartX() + && line_2.getEndY() - line_2.getStartY() < 2 * m_tolerance + && line_1._isIntersectingPoint(line_2.getEndXY(), + m_tolerance, true)) + return errorCracking(); + } else { + // / + // / + // / + // +-- + assert (line_2.getEndX() - line_2.getStartX() != 0); + // Note: line_2 cannot be vertical here + // Avoid expensive is_intersecting_ by providing a simple estimate. + double dydx = (line_2.getEndY() - line_2.getStartY()) + / (line_2.getEndX() - line_2.getStartX()); + double d = dydx * (line_1.getEndX() - line_1.getStartX()); + if (d < m_tolerance_10 + && line_2._isIntersectingPoint(line_1.getEndXY(), + m_tolerance, true)) + return errorCracking(); + } + + return 1; + } + + int compareHorizontal1Case2_(Line line_1, Line line_2) { + // -----------------+ + // / + // / + // / + // line_2 goes up and below line_1. line_1 is horizontal connected at + // the end to the line_2 end. + if (line_1.getStartX() < line_2.getStartX()) { + if (line_2.getEndX() > line_2.getStartX() + && line_2.getEndY() - line_2.getStartY() < 2 * m_tolerance + && line_1._isIntersectingPoint(line_2.getEndXY(), + m_tolerance, true)) + return errorCracking(); + } else { + // --+ + // / + // / + // / + // Avoid expensive is_intersecting_ by providing a simple estimate. + double dydx = (line_2.getEndY() - line_2.getStartY()) + / (line_2.getEndX() - line_2.getStartX()); + double d = dydx * (line_1.getStartX() - line_1.getEndX()); + if (d < m_tolerance_10 + && line_2._isIntersectingPoint(line_1.getStartXY(), + m_tolerance, true)) + return errorCracking(); + } + + return -1; + } + + int compareHorizontal1Case3_(Line line_1, Line line_2) { + Point2D v0 = new Point2D(); + v0.sub(line_2.getEndXY(), line_2.getStartXY()); + v0.rightPerpendicular(); + v0.normalize(); + Point2D v_1 = new Point2D(); + v_1.sub(line_1.getStartXY(), line_2.getStartXY()); + Point2D v_2 = new Point2D(); + v_2.sub(line_1.getEndXY(), line_2.getStartXY()); + double d_1 = v_1.dotProduct(v0); + double d_2 = v_2.dotProduct(v0); + + double ad1 = Math.abs(d_1); + double ad2 = Math.abs(d_2); + + if (ad1 < ad2) { + if (ad1 < m_tolerance_10 + && line_2._isIntersectingPoint(line_1.getStartXY(), + m_tolerance, true)) + return errorCracking(); + } else { + if (ad2 < m_tolerance_10 + && line_2._isIntersectingPoint(line_1.getEndXY(), + m_tolerance, true)) + return errorCracking(); + } + + if (d_1 < 0 && d_2 < 0) + return -1; + + if (d_1 > 0 && d_2 > 0) + return 1; + + return errorCracking(); + } + + int compareHorizontal1_(Line line_1, Line line_2) { + // Two most important cases of connecting edges + if (line_1.getStartY() == line_2.getStartY() + && line_1.getStartX() == line_2.getStartX()) { + return compareHorizontal1Case1_(line_1, line_2); + } + + if (line_1.getEndY() == line_2.getEndY() + && line_1.getEndX() == line_2.getEndX()) { + return compareHorizontal1Case2_(line_1, line_2); + } + + return compareHorizontal1Case3_(line_1, line_2); + } + + int compareHorizontal2_(Line line_1, Line line_2) { + if (line_1.getEndY() == line_2.getEndY() + && line_1.getEndX() == line_2.getEndX() + && line_1.getStartY() == line_2.getStartY() + && line_1.getStartX() == line_2.getStartX()) {// both lines + // coincide + if (m_b_is_simple) + return errorCoincident(); + return 0; + } else + return errorCracking(); + } + + int compareNonHorizontalLowerEnd_(SimpleEdge line_1, SimpleEdge line_2) { + int sign = 1; + if (line_1.m_line.getStartY() < line_2.m_line.getStartY()) { + sign = -1; + SimpleEdge tmp = line_1; + line_1 = line_2; + line_2 = tmp; + } + + Line l1 = line_1.m_line; + Line l2 = line_2.m_line; + // Now line_1 has Start point higher than line_2 startpoint. + double x_1 = l1.getStartX() - l2.getStartX(); + double x2 = line_2.m_dxdy * (l1.getStartY() - l2.getStartY()); + double tol = m_tolerance_10; + if (x_1 < x2 - tol) + return -sign; + else if (x_1 > x2 + tol) + return sign; + else // Possible problem + { + if (l2._isIntersectingPoint(l1.getStartXY(), m_tolerance, true)) + return errorCracking(); + return x_1 < x2 ? -sign : sign; + } + } + + int compareNonHorizontalUpperEnd_(SimpleEdge line_1, SimpleEdge line_2) { + int sign = 1; + if (line_2.m_line.getEndY() < line_1.m_line.getEndY()) { + sign = -1; + SimpleEdge tmp = line_1; + line_1 = line_2; + line_2 = tmp; + } + + Line l1 = line_1.m_line; + Line l2 = line_2.m_line; + // Now line_1 has End point lower than line_2 endpoint. + double x_1 = l1.getEndX() - l2.getStartX(); + double x2 = line_2.m_dxdy * (l1.getEndY() - l2.getStartY()); + double tol = m_tolerance_10; + if (x_1 < x2 - tol) + return -sign; + else if (x_1 > x2 + tol) + return sign; + else // Possible problem + { + if (l2._isIntersectingPoint(l1.getEndXY(), m_tolerance, true)) + return errorCracking(); + return x_1 < x2 ? -sign : sign; + } + } + + int errorCoincident() {// two segments coincide. + m_b_intersection_detected = true; + assert (m_b_is_simple); + NonSimpleResult.Reason reason = NonSimpleResult.Reason.CrossOver; + m_non_simple_result = new NonSimpleResult(reason, m_vertex_1, + m_vertex_2); + return -1; + } + + int errorCracking() {// cracking error + m_b_intersection_detected = true; + if (m_b_is_simple) {// only report the reason in IsSimple. Do not do + // that for regular cracking. + NonSimpleResult.Reason reason = NonSimpleResult.Reason.Cracking; + m_non_simple_result = new NonSimpleResult(reason, m_vertex_1, + m_vertex_2); + } else {// reset cached data after detected intersection + m_prev_1 = -1; + m_prev_2 = -1; + m_vertex_1 = -1; + m_vertex_2 = -1; + } + return -1; + } + + int compareSegments_(int left, int right, SimpleEdge segLeft, + SimpleEdge segRight) { + if (m_b_intersection_detected) + return -1; + + boolean sameY = m_prev_y == m_sweep_y && m_prev_x == m_sweep_x; + double xleft; + if (sameY && left == m_prev_1) + xleft = m_prevx_1; + else { + xleft = NumberUtils.NaN(); + m_prev_1 = -1; + } + double xright; + if (sameY && right == m_prev_2) + xright = m_prevx_2; + else { + xright = NumberUtils.NaN(); + m_prev_2 = -1; + } + + // Quickly compare x projections. + Envelope1D envLeft = segLeft.m_segment.queryInterval( + VertexDescription.Semantics.POSITION, 0); + Envelope1D envRight = segRight.m_segment.queryInterval( + VertexDescription.Semantics.POSITION, 0); + if (envLeft.vmax < envRight.vmin) + return -1; + if (envRight.vmax < envLeft.vmin) + return 1; + + m_prev_y = m_sweep_y; + m_prev_x = m_sweep_x; + + // Now do intersection with the sweep line (it is a line parallel to the + // axis x.) + if (NumberUtils.isNaN(xleft)) { + m_prev_1 = left; + double x = segLeft.m_segment.intersectionOfYMonotonicWithAxisX( + m_sweep_y, m_sweep_x); + xleft = x; + m_prevx_1 = x; + } + if (NumberUtils.isNaN(xright)) { + m_prev_2 = right; + double x = segRight.m_segment.intersectionOfYMonotonicWithAxisX( + m_sweep_y, m_sweep_x); + xright = x; + m_prevx_2 = x; + } + + if (Math.abs(xleft - xright) <= m_tolerance) { + // special processing as we cannot decide in a simple way. + return compareTwoSegments_(segLeft.m_segment, segRight.m_segment); + } else { + return xleft < xright ? -1 : xleft > xright ? 1 : 0; + } + } + + SweepComparator(EditShape shape, double tol, boolean bIsSimple) { + super(true); + m_shape = shape; + m_sweep_y = NumberUtils.TheNaN; + m_sweep_x = 0; + m_prev_x = 0; + m_prev_y = NumberUtils.TheNaN; + m_tolerance = tol; + m_tolerance_10 = 10 * tol; + m_prevx_2 = NumberUtils.TheNaN; + m_prevx_1 = NumberUtils.TheNaN; + m_b_intersection_detected = false; + m_prev_1 = -1; + m_prev_2 = -1; + m_vertex_1 = -1; + m_vertex_2 = -1; + m_current_node = -1; + m_b_is_simple = bIsSimple; + m_temp_simple_edge_1 = new SimpleEdge(); + m_temp_simple_edge_2 = new SimpleEdge(); + + int s = Math.min(shape.getTotalPointCount() * 3 / 2, + (int) (67 /* SIMPLEDGE_CACHESIZE */)); + int cache_size = Math.min((int) 7, s); + // m_simple_edges_buffer.reserve(cache_size);//must be reserved and + // never grow beyond reserved size + + m_simple_edges_buffer = new ArrayList(); + m_simple_edges_recycle = new ArrayList(); + m_simple_edges_cache = new ArrayList(); + + for (int i = 0; i < cache_size; i++) + m_simple_edges_cache.add(null); + } + + // Makes the comparator to forget about the last detected intersection. + // Need to be called after the intersection has been resolved. + void clearIntersectionDetectedFlag() { + m_b_intersection_detected = false; + } + + // Returns True if there has been intersection detected during compare call. + // Once intersection is detected subsequent calls to compare method do + // nothing until clear_intersection_detected_flag is called. + boolean intersectionDetected() { + return m_b_intersection_detected; + } + + // Returns the node at which the intersection has been detected + int getLastComparedNode() { + return m_current_node; + } + + // When used in IsSimple (see corresponding parameter in ctor), returns the + // reason of non-simplicity + NonSimpleResult getResult() { + return m_non_simple_result; + } + + // Sets new sweep line position. + void setSweepY(double y, double x) { + // _ASSERT(m_sweep_y != y || m_sweep_x != x); + m_sweep_y = y; + m_sweep_x = x; + m_prev_1 = -1; + m_prev_2 = -1; + m_vertex_1 = -1; + m_vertex_2 = -1; + } + + // The compare method. Compares x values of the edge given by its origin + // (elm) and the edge in the sweep structure and checks them for + // intersection at the same time. + @Override + int compare(Treap treap, int left, int node) { + // Compares two segments on a sweep line passing through m_sweep_y, + // m_sweep_x. + if (m_b_intersection_detected) + return -1; + + int right = treap.getElement(node); + m_current_node = node; + return compareSegments(left, left, right, right); + } + + int compareSegments(int leftElm, int left_vertex, int right_elm, + int right_vertex) { + SimpleEdge edgeLeft = tryGetCachedEdge_(leftElm); + if (edgeLeft == null) { + if (m_vertex_1 == left_vertex) + edgeLeft = m_temp_simple_edge_1; + else { + m_vertex_1 = left_vertex; + edgeLeft = tryCreateCachedEdge_(leftElm); + if (edgeLeft == null) { + edgeLeft = m_temp_simple_edge_1; + m_temp_simple_edge_1.m_value = leftElm; + } + initSimpleEdge_(edgeLeft, left_vertex); + } + } else + m_vertex_1 = left_vertex; + + SimpleEdge edgeRight = tryGetCachedEdge_(right_elm); + if (edgeRight == null) { + if (m_vertex_2 == right_vertex) + edgeRight = m_temp_simple_edge_2; + else { + m_vertex_2 = right_vertex; + edgeRight = tryCreateCachedEdge_(right_elm); + if (edgeRight == null) { + edgeRight = m_temp_simple_edge_2; + m_temp_simple_edge_2.m_value = right_elm; + } + initSimpleEdge_(edgeRight, right_vertex); + } + } else + m_vertex_2 = right_vertex; + + if (edgeLeft.m_b_curve || edgeRight.m_b_curve) + return compareSegments_(left_vertex, right_vertex, edgeLeft, + edgeRight); + + // Usually we work with lines, so process them in the fastest way. + // First check - assume segments are far apart. compare x intervals + if (edgeLeft.m_env.vmax < edgeRight.m_env.vmin) + return -1; + if (edgeRight.m_env.vmax < edgeLeft.m_env.vmin) + return 1; + + // compare case by case. + int kind = edgeLeft.m_b_horizontal ? 1 : 0; + kind |= edgeRight.m_b_horizontal ? 2 : 0; + if (kind == 0)// both segments are non-horizontal + return compareNonHorizontal_(edgeLeft, edgeRight); + else if (kind == 1) // line_1 horizontal, line_2 is not + return compareHorizontal1_(edgeLeft.m_line, edgeRight.m_line); + else if (kind == 2) // line_2 horizontal, line_1 is not + return compareHorizontal1_(edgeRight.m_line, edgeLeft.m_line) * -1; + else + // if (kind == 3) //both horizontal + return compareHorizontal2_(edgeLeft.m_line, edgeRight.m_line); + } + + @Override + void onDelete(int elm) { + tryDeleteCachedEdge_(elm); + } + + @Override + void onSet(int oldelm) { + tryDeleteCachedEdge_(oldelm); + } + + @Override + void onEndSearch(int elm) { + tryDeleteCachedEdge_(elm); + } + + @Override + void onAddUniqueElementFailed(int elm) { + tryDeleteCachedEdge_(elm); + } +} diff --git a/src/com/esri/core/geometry/SweepMonkierComparator.java b/src/com/esri/core/geometry/SweepMonkierComparator.java new file mode 100644 index 00000000..6589ef64 --- /dev/null +++ b/src/com/esri/core/geometry/SweepMonkierComparator.java @@ -0,0 +1,134 @@ +/* + Copyright 1995-2013 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ +/* + * To change this template, choose Tools | Templates + * and open the template in the editor. + */ +package com.esri.core.geometry; + +class SweepMonkierComparator extends Treap.MonikerComparator { + protected EditShape m_shape; + protected boolean m_b_intersection_detected; + protected Point2D m_point_of_interest; + protected Line m_line_1; + protected Envelope1D m_env; + protected int m_vertex_1; + protected int m_current_node; + protected double m_min_dist; + protected double m_tolerance; + + SweepMonkierComparator(EditShape shape, double tol) { + m_shape = shape; + m_tolerance = tol; + m_b_intersection_detected = false; + m_vertex_1 = -1; + m_env = new Envelope1D(); + m_point_of_interest = new Point2D(); + m_point_of_interest.setNaN(); + m_line_1 = new Line(); + m_current_node = -1; + m_min_dist = NumberUtils.doubleMax(); + } + + int getCurrentNode() { + return m_current_node; + } + + // Makes the comparator to forget about the last detected intersection. + // Need to be called after the intersection has been resolved. + void clearIntersectionDetectedFlag() { + m_b_intersection_detected = false; + m_min_dist = NumberUtils.doubleMax(); + } + + // Returns True if there has been intersection detected during compare call. + // Once intersection is detected subsequent calls to compare method do + // nothing until clear_intersection_detected_flag is called. + boolean intersectionDetected() { + return m_b_intersection_detected; + } + + void setPoint(Point2D pt) { + m_point_of_interest.setCoords(pt); + } + + // Compares the moniker, contained in the Moniker_comparator with the + // element contained in the given node. + @Override + int compare(Treap treap, int node) { + int vertex = treap.getElement(node); + return compareVertex_(treap, node, vertex); + } + + protected int compareVertex_(Treap treap, int node, int vertex) { + boolean bCurve = m_shape.getSegment(vertex) != null; + if (!bCurve) { + m_shape.queryLineConnector(vertex, m_line_1); + m_env.setCoordsNoNaN_(m_line_1.getStartX(), m_line_1.getEndX()); + } + + if (bCurve) { + throw new GeometryException("not implemented"); + } + + if (m_point_of_interest.x + m_tolerance < m_env.vmin) + return -1; + + if (m_point_of_interest.x - m_tolerance > m_env.vmax) + return 1; + + if (m_line_1.getStartY() == m_line_1.getEndY()) { + m_current_node = node; + m_b_intersection_detected = true; + return 0; + } + + m_line_1.orientBottomUp_(); + Point2D start = m_line_1.getStartXY(); + Point2D vector = new Point2D(); + vector.sub(m_line_1.getEndXY(), start); + vector.rightPerpendicular(); + Point2D v_2 = new Point2D(); + v_2.sub(m_point_of_interest, start); + double dot = vector.dotProduct(v_2); + dot /= vector.length(); + if (dot < -m_tolerance * 10) + return -1; + if (dot > m_tolerance * 10) + return 1; + + if (m_line_1.isIntersecting(m_point_of_interest, m_tolerance)) { + double absDot = Math.abs(dot); + if (absDot < m_min_dist) { + m_current_node = node; + m_min_dist = absDot; + } + m_b_intersection_detected = true; + if (absDot < 0.25 * m_tolerance) + return 0; + } + + return dot < 0 ? -1 : 1; + } +} diff --git a/src/com/esri/core/geometry/TopoGraph.java b/src/com/esri/core/geometry/TopoGraph.java new file mode 100644 index 00000000..67bb0de2 --- /dev/null +++ b/src/com/esri/core/geometry/TopoGraph.java @@ -0,0 +1,2346 @@ +/* + Copyright 1995-2013 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ +package com.esri.core.geometry; + +import java.util.ArrayList; + +import com.esri.core.geometry.AttributeStreamOfInt32.IntComparator; + +final class TopoGraph { + + static interface EnumInputMode { + + final static int enumInputModeBuildGraph = 0; + final static int enumInputModeSimplifyAlternate = 4 + 0; + final static int enumInputModeSimplifyWinding = 4 + 1; + final static int enumInputModeSimplifyForBuffer = 4 + 2; + } + + EditShape m_shape; + + // cluster data: index, parentage, halfEdge, globalPrev, globalNext + StridedIndexTypeCollection m_clusterData; + StridedIndexTypeCollection m_clusterVertices; + int m_firstCluster; + int m_lastCluster; + // edge data: index, origin, faceParentage, edgeParentage, twin, prev, next + StridedIndexTypeCollection m_halfEdgeData; + // chain data index, half_edge, parentage, parentChain, firstIsland, + // nextInParent, prev, next + StridedIndexTypeCollection m_chainData; + AttributeStreamOfDbl m_chainAreas; + AttributeStreamOfDbl m_chainPerimeters; + + final int c_edgeParentageMask; + final int c_edgeBitMask; + int m_universeChain; + ArrayList m_edgeIndices; + ArrayList m_clusterIndices; + ArrayList m_chainIndices; + + int m_geometryIDIndex; // index of geometryIDs in the m_shape + int m_clusterIndex; // vertex index of cluster handles in the m_shape + int m_halfEdgeIndex; // vertex index of half-edges in the m_shape + int m_tmpHalfEdgeParentageIndex; + int m_tmpHalfEdgeWindingNumberIndex; + + int m_pointCount;// point count processed in this Topo_graph. Used to + // reserve data. + + static final class PlaneSweepComparator extends Treap.Comparator { + TopoGraph m_helper; + SegmentBuffer m_buffer_left; + SegmentBuffer m_buffer_right; + Envelope1D interval_left; + Envelope1D interval_right; + double m_y_scanline; + + PlaneSweepComparator(TopoGraph helper) { + m_helper = helper; + m_y_scanline = NumberUtils.TheNaN; + m_buffer_left = new SegmentBuffer(); + m_buffer_right = new SegmentBuffer(); + interval_left = new Envelope1D(); + interval_right = new Envelope1D(); + } + + @Override + int compare(Treap treap, int left, int node) { + int right = treap.getElement(node); + // can be sped up a little, because left or right stay the same + // while an edge is inserted into the tree. + m_helper.querySegmentXY(left, m_buffer_left); + m_helper.querySegmentXY(right, m_buffer_right); + Segment segLeft = m_buffer_left.get(); + Segment segRight = m_buffer_right.get(); + + // Prerequisite: The segments have the start point lexicographically + // above the end point. + assert (segLeft.getStartXY().compare(segLeft.getEndXY()) < 0); + assert (segRight.getStartXY().compare(segRight.getEndXY()) < 0); + + // Simple test for faraway segments + interval_left.setCoords(segLeft.getStartX(), segLeft.getEndX()); + interval_right.setCoords(segRight.getStartX(), segRight.getEndX()); + if (interval_left.vmax < interval_right.vmin) + return -1; + if (interval_left.vmin > interval_right.vmax) + return 1; + + boolean bLeftHorz = segLeft.getStartY() == segLeft.getEndY(); + boolean bRightHorz = segRight.getStartY() == segRight.getEndY(); + if (bLeftHorz || bRightHorz) { + if (bLeftHorz && bRightHorz) { + assert (interval_left.equals(interval_right)); + return 0; + } + + // left segment is horizontal. The right one is not. + // Prerequisite of this algorithm is that this can only happen + // when: + // left + // |right -------------------- end == end + // | | + // | left | + // -------------------- right | + // start == start + // or: + // right segment is horizontal. The left one is not. + // Prerequisite of this algorithm is that his can only happen + // when: + // right + // |left -------------------- end == end + // | | + // | right | + // -------------------- left | + // start == start + + if (segLeft.getStartY() == segRight.getStartY() + && segLeft.getStartX() == segRight.getStartX()) + return bLeftHorz ? 1 : -1; + else if (segLeft.getEndY() == segRight.getEndY() + && segLeft.getEndX() == segRight.getEndX()) + return bLeftHorz ? -1 : 1; + } + + // Now do actual intersections + double xLeft = segLeft.intersectionOfYMonotonicWithAxisX( + m_y_scanline, interval_left.vmin); + double xRight = segRight.intersectionOfYMonotonicWithAxisX( + m_y_scanline, interval_right.vmin); + + if (xLeft == xRight) { + // apparently these edges originate from same vertex and the + // scanline is on the vertex. move scanline a little. + double yLeft = segLeft.getEndY(); + double yRight = segRight.getEndY(); + double miny = Math.min(yLeft, yRight); + double y = (miny + m_y_scanline) * 0.5; + if (y == m_y_scanline) { + // assert(0);//ST: not a bug. just curious to see this + // happens. + y = miny; // apparently, one of the segments is almost + // horizontal line. + } + xLeft = segLeft.intersectionOfYMonotonicWithAxisX(y, + interval_left.vmin); + xRight = segRight.intersectionOfYMonotonicWithAxisX(y, + interval_right.vmin); + } + + return xLeft < xRight ? -1 : (xLeft > xRight ? 1 : 0); + } + + void setY(double y) { + m_y_scanline = y; + } + // void operator=(const Plane_sweep_comparator&); // do not allow + // operator = + }; + + static final class TopoGraphAngleComparer extends IntComparator { + TopoGraph m_parent; + + TopoGraphAngleComparer(TopoGraph parent_) { + m_parent = parent_; + } + + @Override + public int compare(int v1, int v2) { + return m_parent.compareEdgeAngles_(v1, v2); + } + }; + + static final class ClusterSweepMonikerComparator extends + Treap.MonikerComparator { + TopoGraph m_parent; + SegmentBuffer m_segment_buffer; + Point2D m_point; + Envelope1D m_interval; + + ClusterSweepMonikerComparator(TopoGraph parent) { + m_parent = parent; + m_segment_buffer = new SegmentBuffer(); + m_point = new Point2D(); + m_interval = new Envelope1D(); + } + + void setPointXY(Point2D pt) { + m_point.setCoords(pt); + } + + @Override + int compare(Treap treap, int node) { + int half_edge = treap.getElement(node); + + // can be sped up a little, because left or right stay the same + // while an edge is inserted into the tree. + m_parent.querySegmentXY(half_edge, m_segment_buffer); + Segment seg = m_segment_buffer.get(); + + // Simple test for faraway segments + m_interval.setCoords(seg.getStartX(), seg.getEndX()); + if (m_point.x < m_interval.vmin) + return -1; + + if (m_point.x > m_interval.vmax) + return 1; + + // Now do actual intersections + double x = seg.intersectionOfYMonotonicWithAxisX(m_point.y, + m_point.x); + + assert (x != m_point.x); + + return m_point.x < x ? -1 : (m_point.x > x ? 1 : 0); + } + } + + int newCluster_() { + if (m_clusterData == null) + m_clusterData = new StridedIndexTypeCollection(8); + + int cluster = m_clusterData.newElement(); + // m_clusterData->add(-1);//first vertex + m_clusterData.setField(cluster, 1, 0);// parentage + // m_clusterData->add(-1);//first half edge + // m_clusterData->add(-1);//prev cluster + // m_clusterData->add(-1);//next cluster + return cluster; + } + + int newHalfEdgePair_() { + if (m_halfEdgeData == null) + m_halfEdgeData = new StridedIndexTypeCollection(8); + + int halfEdge = m_halfEdgeData.newElement(); + // m_halfEdgeData.add(-1);//origin cluster + m_halfEdgeData.setField(halfEdge, 2, 0);// chain parentage + m_halfEdgeData.setField(halfEdge, 3, 0);// edge parentage + // m_halfEdgeData.add(-1);//twin + // m_halfEdgeData.add(-1);//prev + // m_halfEdgeData.add(-1);//next + int twinHalfEdge = m_halfEdgeData.newElement(); + // m_halfEdgeData.add(-1);//origin cluster + m_halfEdgeData.setField(twinHalfEdge, 2, 0);// chain parentage + m_halfEdgeData.setField(twinHalfEdge, 3, 0);// edge parentage + // m_halfEdgeData.add(-1);//twin + // m_halfEdgeData.add(-1);//prev + // m_halfEdgeData.add(-1);//next + setHalfEdgeTwin_(halfEdge, twinHalfEdge); + setHalfEdgeTwin_(twinHalfEdge, halfEdge); + return halfEdge; + } + + int newChain_() { + if (m_chainData == null) + m_chainData = new StridedIndexTypeCollection(8); + + int chain = m_chainData.newElement(); + // m_chainData->write(chain, + 1, -1);//half_edge + m_chainData.setField(chain, 2, 0);// parentage (geometric) + // m_chainData->write(m_chainReserved + 3, -1);//parent chain + // m_chainData->write(m_chainReserved + 4, -1);//firstIsland + // m_chainData->write(m_chainReserved + 5, -1);//nextInParent + // m_chainData->write(m_chainReserved + 6, -1);//prev + // m_chainData->write(m_chainReserved + 7, -1);//next + // m_chainReserved += 8; + return chain; + } + + int deleteChain_(int chain) { + // Note: this method cannot be after _PlaneSweep + assert (m_universeChain != chain); + int n = getChainNext(chain); + m_chainData.deleteElement(chain); + // Note: no need to update the first chain, because one should never try + // deleting the first (the universe) chain. + return n; + } + + int getClusterIndex_(int cluster) { + return m_clusterData.elementToIndex(cluster); + } + + void setClusterVertexIterator_(int cluster, int verticeList) { + m_clusterData.setField(cluster, 7, verticeList); + } + + void setClusterHalfEdge_(int cluster, int half_edge) { + m_clusterData.setField(cluster, 2, half_edge); + } + + void setClusterParentage_(int cluster, int parentage) { + m_clusterData.setField(cluster, 1, parentage); + } + + void setPrevCluster_(int cluster, int nextCluster) { + m_clusterData.setField(cluster, 3, nextCluster); + } + + void setNextCluster_(int cluster, int nextCluster) { + m_clusterData.setField(cluster, 4, nextCluster); + } + + void setClusterVertexIndex_(int cluster, int index) { + m_clusterData.setField(cluster, 5, index); + } + + int getClusterVertexIndex_(int cluster) { + return m_clusterData.getField(cluster, 5); + } + + void setClusterChain_(int cluster, int chain) { + m_clusterData.setField(cluster, 6, chain); + } + + void addClusterToExteriorChain_(int chain, int cluster) { + assert (getClusterChain(cluster) == -1); + setClusterChain_(cluster, chain); + // There is no link from the chain to the cluster. Only vice versa. + // Consider for change? + } + + int getHalfEdgeIndex_(int he) { + return m_halfEdgeData.elementToIndex(he); + } + + void setHalfEdgeOrigin_(int half_edge, int cluster) { + m_halfEdgeData.setField(half_edge, 1, cluster); + } + + void setHalfEdgeTwin_(int half_edge, int twinHalfEdge) { + m_halfEdgeData.setField(half_edge, 4, twinHalfEdge); + } + + void setHalfEdgePrev_(int half_edge, int prevHalfEdge) { + m_halfEdgeData.setField(half_edge, 5, prevHalfEdge); + } + + void setHalfEdgeNext_(int half_edge, int nextHalfEdge) { + m_halfEdgeData.setField(half_edge, 6, nextHalfEdge); + } + + // void set_half_edge_chain_parentage_(int half_edge, int + // chainParentageMask) { m_halfEdgeData.setField(half_edge + 2, + // chainParentageMask); } + void setHalfEdgeChain_(int half_edge, int chain) { + m_halfEdgeData.setField(half_edge, 2, chain); + } + + void setHalfEdgeParentage_(int half_edge, int parentageMask) { + m_halfEdgeData.setField(half_edge, 3, parentageMask); + } + + int getHalfEdgeParentageMask_(int half_edge) { + return m_halfEdgeData.getField(half_edge, 3); + } + + void setHalfEdgeVertexIterator_(int half_edge, int vertexIterator) { + m_halfEdgeData.setField(half_edge, 7, vertexIterator); + } + + void updateVertexToHalfEdgeConnectionHelper_(int half_edge, boolean bClear) { + int viter = getHalfEdgeVertexIterator(half_edge); + if (viter != -1) { + int he = bClear ? -1 : half_edge; + for (int viter_ = getHalfEdgeVertexIterator(half_edge); viter_ != -1; viter_ = incrementVertexIterator(viter_)) { + int vertex = getVertexFromVertexIterator(viter_); + m_shape.setUserIndex(vertex, m_halfEdgeIndex, he); + } + } + } + + void updateVertexToHalfEdgeConnection_(int half_edge, boolean bClear) { + if (half_edge == -1) + return; + updateVertexToHalfEdgeConnectionHelper_(half_edge, bClear); + updateVertexToHalfEdgeConnectionHelper_(getHalfEdgeTwin(half_edge), + bClear); + } + + int getChainIndex_(int chain) { + return m_chainData.elementToIndex(chain); + } + + void setChainHalfEdge_(int chain, int half_edge) { + m_chainData.setField(chain, 1, half_edge); + } + + void setChainParentage_(int chain, int parentage) { + m_chainData.setField(chain, 2, parentage); + } + + void setChainParent_(int chain, int parentChain) { + assert (m_chainData.getField(chain, 3) != parentChain); + m_chainData.setField(chain, 3, parentChain); + int firstIsland = getChainFirstIsland(parentChain); + setChainNextInParent_(chain, firstIsland); + setChainFirstIsland_(parentChain, chain); + } + + void setChainFirstIsland_(int chain, int islandChain) { + m_chainData.setField(chain, 4, islandChain); + } + + void setChainNextInParent_(int chain, int nextInParent) { + m_chainData.setField(chain, 5, nextInParent); + } + + void setChainPrev_(int chain, int prev) { + m_chainData.setField(chain, 6, prev); + } + + void setChainNext_(int chain, int next) { + m_chainData.setField(chain, 7, next); + } + + void setChainArea_(int chain, double area) { + int chainIndex = getChainIndex_(chain); + m_chainAreas.write(chainIndex, area); + } + + void setChainPerimeter_(int chain, double perimeter) { + int chainIndex = getChainIndex_(chain); + m_chainPerimeters.write(chainIndex, perimeter); + } + + void updateChainAreaAndPerimeter_(int chain) { + double area = 0; + double perimeter = 0; + int firstHalfEdge = getChainHalfEdge(chain); + Point2D origin = new Point2D(), from = new Point2D(), to = new Point2D(); + getHalfEdgeFromXY(firstHalfEdge, origin); + from.setCoords(origin); + int half_edge = firstHalfEdge; + do { + getHalfEdgeToXY(half_edge, to); + perimeter += Point2D.distance(from, to); + int twinChain = getHalfEdgeChain(getHalfEdgeTwin(half_edge)); + if (twinChain != chain)// only count edges are not dangling segments + // of polylines + { + area += ((to.x - origin.x) - (from.x - origin.x)) + * ((to.y - origin.y) + (from.y - origin.y)) * 0.5; + } + + from.setCoords(to); + half_edge = getHalfEdgeNext(half_edge); + } while (half_edge != firstHalfEdge); + + int ind = getChainIndex_(chain); + m_chainAreas.write(ind, area); + m_chainPerimeters.write(ind, perimeter); + } + + int getChainTopMostEdge_(int chain) { + int firstHalfEdge = getChainHalfEdge(chain); + Point2D top = new Point2D(); + getHalfEdgeFromXY(firstHalfEdge, top); + int topEdge = firstHalfEdge; + Point2D v = new Point2D(); + int half_edge = firstHalfEdge; + do { + getHalfEdgeFromXY(half_edge, v); + if (v.compare(top) > 0) { + top.setCoords(v); + topEdge = half_edge; + } + half_edge = getHalfEdgeNext(half_edge); + } while (half_edge != firstHalfEdge); + return topEdge; + } + + void planeSweepParentage_(int inputMode, ProgressTracker progress_tracker) { + PlaneSweepComparator comparator = new PlaneSweepComparator(this); + Treap aet = new Treap(); + aet.setCapacity(m_pointCount / 2); + aet.setComparator(comparator); + + AttributeStreamOfInt32 new_edges = new AttributeStreamOfInt32(0); + int treeNodeIndex = createUserIndexForHalfEdges(); + + ClusterSweepMonikerComparator clusterMoniker = null; + int counter = 0; + // Clusters are sorted by the y, x coordinate in ascending order. + Point2D pt = new Point2D(); + // Each cluster is an event of the sweep-line algorithm. + for (int cluster = getFirstCluster(); cluster != -1; cluster = getNextCluster(cluster)) { + counter++; + if ((counter & 0xFF) == 0) { + if ((progress_tracker != null) + && !(progress_tracker.progress(-1, -1))) + throw new UserCancelException(); + } + + int firstHalfEdge = getClusterHalfEdge(cluster); + if (firstHalfEdge != -1) { + new_edges.resizePreserveCapacity(0); + if (!tryOptimizedInsertion_(aet, treeNodeIndex, new_edges, + cluster, firstHalfEdge))// optimized insertion is for a + // simple chain, in that case we + // simply replace an old edge + // with a new one in AET - O(1) + {// This is more complex than a simple chain of edges + getXY(cluster, pt); + comparator.setY(pt.y); + int clusterHalfEdge = firstHalfEdge; + // Delete all edges that end at the cluster. + do {// edges that end at the cluster have been assigned an + // AET node in the treeNodeIndex. + int attachedTreeNode = getHalfEdgeUserIndex( + clusterHalfEdge, treeNodeIndex); + if (attachedTreeNode != -1) { + assert (attachedTreeNode != StridedIndexTypeCollection + .impossibleIndex2()); + aet.deleteNode(attachedTreeNode, -1); + setHalfEdgeUserIndex(clusterHalfEdge, + treeNodeIndex, + StridedIndexTypeCollection + .impossibleIndex2());// set it to -2 + } + + clusterHalfEdge = getHalfEdgeNext(getHalfEdgeTwin(clusterHalfEdge)); + assert (getHalfEdgeOrigin(clusterHalfEdge) == cluster); + } while (firstHalfEdge != clusterHalfEdge); + + // insert edges that start at the cluster. + // We need to insert only the edges that have the from point + // below the to point. + // This is ensured by the logic of the algorithm. + clusterHalfEdge = firstHalfEdge; + do { + int attachedTreeNode = getHalfEdgeUserIndex( + clusterHalfEdge, treeNodeIndex); + if (attachedTreeNode == -1) { + // #ifdef DEBUG + // //Debug-checking that the "from" is below the + // "to" + // Point_2D pt_1; + // getHalfEdgeFromXY(clusterHalfEdge, pt_1); + // Point_2D pt_2; + // getHalfEdgeToXY(clusterHalfEdge, pt_2); + // assert(pt_1.compare(pt_2) < 0); + // #endif + int newTreeNode = aet.addElement(clusterHalfEdge, + -1); + new_edges.add(newTreeNode); + } + clusterHalfEdge = getHalfEdgeNext(getHalfEdgeTwin(clusterHalfEdge)); + assert (getHalfEdgeOrigin(clusterHalfEdge) == cluster); + } while (firstHalfEdge != clusterHalfEdge); + } + + // Analyze new edges. + // We go in the opposite order, because of the way how the half + // edges are sorted on a cluster. + // We want to go from the left to the right. + for (int i = new_edges.size() - 1; i >= 0; i--) { + int newTreeNode = new_edges.get(i); + int clusterHalfEdge = aet.getElement(newTreeNode); + int twinEdge = getHalfEdgeTwin(clusterHalfEdge); + assert (getHalfEdgeUserIndex(twinEdge, treeNodeIndex) == -1); + setHalfEdgeUserIndex(twinEdge, treeNodeIndex, newTreeNode); + + planeSweepParentagePropagateParentage_(aet, newTreeNode, + inputMode); + } + } else if (getClusterChain(cluster) == -1) { + // get the left half edge of a face. The point belongs to the + // face. + if (clusterMoniker == null) + clusterMoniker = new ClusterSweepMonikerComparator(this); + + getXY(cluster, pt); + clusterMoniker.setPointXY(pt); + int leftNode = aet.searchLowerBound(clusterMoniker, -1); + int chain = m_universeChain; + + if (leftNode != -1) { + int edge = aet.getElement(leftNode); + int leftChain = getHalfEdgeChain(edge); + if (leftChain == getHalfEdgeChain(getHalfEdgeTwin(edge))) { + edge = getLeftSkipPolylines_(aet, leftNode); + } + + if (edge != -1) + chain = getHalfEdgeChain(edge); + } + + addClusterToExteriorChain_(chain, cluster); + } + } + + deleteUserIndexForHalfEdges(treeNodeIndex); + } + + void planeSweepParentagePropagateParentage_(Treap aet, int treeNode, + int inputMode) { + int edge = aet.getElement(treeNode); + int edgeChain = getHalfEdgeChain(edge); + int edgeChainParent = getChainParent(edgeChain); + if (edgeChainParent != -1) + return;// this edge has been processed already. + + // get contributing left edge. + int leftEdge = getLeftSkipPolylines_(aet, treeNode); + + int twinEdge = getHalfEdgeTwin(edge); + int twinHalfEdgeChain = getHalfEdgeChain(twinEdge); + + double chainArea = getChainArea(edgeChain); + double twinChainArea = getChainArea(twinHalfEdgeChain); + + int parentChain = getChainParent(edgeChain); + int twinParentChain = getChainParent(twinHalfEdgeChain); + if (leftEdge == -1 && parentChain == -1) { + // This edge/twin pair does not have a neighbour edge to the left. + // twin parent is not yet been assigned. + if (twinHalfEdgeChain == edgeChain) {// set parentage of a polyline + // edge (any edge for which + // the edge ant its twin + // belong to the same chain) + setChainParent_(twinHalfEdgeChain, getFirstChain()); + twinParentChain = getFirstChain(); + parentChain = twinParentChain; + } else { + // We have two touching chains that do not have parent chain + // set. + // The edge is directed up, the twin edge is directed down. + // There is no edge to the left. THat means there is no other + // than the universe surrounding this edge. + // The edge must belong to a clockwise chain, and the twin edge + // must belong to a ccw chain that encloses this edge. This + // follows from the way how we connect edges around clusters. + assert (twinChainArea < 0 && chainArea > 0); + if (twinParentChain == -1) { + setChainParent_(twinHalfEdgeChain, m_universeChain); + twinParentChain = m_universeChain; + } else { + assert (getFirstChain() == twinParentChain); + } + + setChainParent_(edgeChain, twinHalfEdgeChain); + parentChain = twinHalfEdgeChain; + } + } + + if (leftEdge != -1) { + int leftEdgeChain = getHalfEdgeChain(leftEdge); + // the twin edge has not been processed yet + if (twinParentChain == -1) { + double leftArea = getChainArea(leftEdgeChain); + if (leftArea <= 0) {// if left Edge's chain area is negative, + // then it is a chain that ends at the left + // edge, so we need to get the parent of the + // left chain and it will be the parent of + // this one. + int leftChainParent = getChainParent(leftEdgeChain); + assert (leftChainParent != -1); + + setChainParent_(twinHalfEdgeChain, leftChainParent); + twinParentChain = leftChainParent; + } else // (leftArea > 0) + {// left edge is an edge of positive chain. It surrounds the + // twin chain. + setChainParent_(twinHalfEdgeChain, leftEdgeChain); + twinParentChain = leftEdgeChain; + } + + if (twinHalfEdgeChain == edgeChain) // if this is a polyline + // chain + parentChain = twinParentChain; + } + } + + if (parentChain == -1) { + trySetChainParentFromTwin_(edgeChain, twinHalfEdgeChain); + parentChain = getChainParent(edgeChain); + } + + assert (parentChain != -1); + + // Now do specific sweep calculations + if (inputMode == EnumInputMode.enumInputModeBuildGraph) { + int chainParentage = getChainParentage(edgeChain); + + if (leftEdge != -1) { + // borrow the parentage from the left edge also + int leftEdgeChain = getHalfEdgeChain(leftEdge); + // dbg_print_edge_(edge); + // dbg_print_edge_(leftEdge); + + // We take parentage from the left edge (that edge has been + // already processed), and move its face parentage accross this + // edge/twin pair. + // While the parentage is moved, accross, any bits of the + // parentage that is present in the twin are removed, because + // the twin is the right edge of the current face. + // The remaining bits are added to the face parentage of this + // edge, indicating that the face this edge borders, belongs to + // all the parents that are still active to the left. + int twinChainParentage = getChainParentage(twinHalfEdgeChain); + int leftChainParentage = getChainParentage(leftEdgeChain); + + int edgeParentage = getHalfEdgeParentage(edge); + int spikeParentage = chainParentage & twinChainParentage + & leftChainParentage; // parentage that needs to stay + leftChainParentage = leftChainParentage + ^ (leftChainParentage & edgeParentage); + leftChainParentage |= spikeParentage; + // leftChainParentage = leftChainParentage ^ (leftChainParentage + // & twinChainParentage);//only parentage that is abscent in the + // twin is propagated to the right + // leftChainParentage = leftChainParentage ^ (leftChainParentage + // & edgeParentage);//only parentage that is abscent in the twin + // is propagated to the right + // & (and) leaves the parentage that is common for left edge and + // the twin, while ^ (xor) leaves the parentage that is present + // in the + // left edge only. + + if (leftChainParentage != 0) { + // propagate left parentage to the current edge and its + // twin. + setChainParentage_(twinHalfEdgeChain, twinChainParentage + | leftChainParentage); + setChainParentage_(edgeChain, leftChainParentage + | chainParentage); + chainParentage |= leftChainParentage; + } + + // dbg_print_edge_(edge); + } + + for (int rightNode = aet.getNext(treeNode); rightNode != -1; rightNode = aet + .getNext(rightNode)) { + int rightEdge = aet.getElement(rightNode); + int rightTwin = getHalfEdgeTwin(rightEdge); + // dbg_print_edge_(rightEdge); + int rightTwinChain = getHalfEdgeChain(rightTwin); + int rightTwinChainParentage = getChainParentage(rightTwinChain); + int rightEdgeParentage = getHalfEdgeParentage(rightEdge); + int rightEdgeChain = getHalfEdgeChain(rightEdge); + int rightChainParentage = getChainParentage(rightEdgeChain); + + int spikeParentage = rightTwinChainParentage + & rightChainParentage & chainParentage; // parentage + // that needs to + // stay + chainParentage = chainParentage + ^ (chainParentage & rightEdgeParentage);// only + // parentage + // that is + // abscent in + // the twin is + // propagated to + // the right + chainParentage |= spikeParentage; + + if (chainParentage == 0) + break; + + setChainParentage_(rightTwinChain, rightTwinChainParentage + | chainParentage); + setChainParentage_(rightEdgeChain, rightChainParentage + | chainParentage); + // dbg_print_edge_(rightEdge); + } + } + + if (inputMode == EnumInputMode.enumInputModeSimplifyWinding) { + if (edgeChain == twinHalfEdgeChain) + return; + // starting from the left most edge, calculate winding. + int edgeWinding = getHalfEdgeUserIndex(edge, + m_tmpHalfEdgeWindingNumberIndex); + edgeWinding += getHalfEdgeUserIndex(twinEdge, + m_tmpHalfEdgeWindingNumberIndex); + int winding = 0; + AttributeStreamOfInt32 chainStack = new AttributeStreamOfInt32(0); + AttributeStreamOfInt32 windingStack = new AttributeStreamOfInt32(0); + windingStack.add(0); + for (int leftNode = aet.getFirst(-1); leftNode != treeNode; leftNode = aet + .getNext(leftNode)) { + int leftEdge1 = aet.getElement(leftNode); + int leftTwin = getHalfEdgeTwin(leftEdge1); + int l_chain = getHalfEdgeChain(leftEdge1); + int lt_chain = getHalfEdgeChain(leftTwin); + + if (l_chain != lt_chain) { + int leftWinding = getHalfEdgeUserIndex(leftEdge1, + m_tmpHalfEdgeWindingNumberIndex); + leftWinding += getHalfEdgeUserIndex(leftTwin, + m_tmpHalfEdgeWindingNumberIndex); + winding += leftWinding; + + boolean popped = false; + if (chainStack.size() != 0 + && chainStack.getLast() == lt_chain) { + windingStack + .resizePreserveCapacity(windingStack.size() - 1); + chainStack + .resizePreserveCapacity(chainStack.size() - 1); + popped = true; + } + + // geometry_release_assert(getChainParent(lt_chain) != -1); + + if (!popped || getChainParent(lt_chain) != l_chain) { + windingStack.add(winding); + chainStack.add(l_chain); + } + } + } + + winding += edgeWinding; + + if (chainStack.size() != 0 + && chainStack.getLast() == twinHalfEdgeChain) { + windingStack.resizePreserveCapacity(windingStack.size() - 1); + chainStack.resizePreserveCapacity(chainStack.size() - 1); + } + + if (winding != 0) { + if (windingStack.read(windingStack.size() - 1) == 0) { + int geometry = m_shape.getFirstGeometry(); + int geometryID = getGeometryID(geometry); + setChainParentage_(edgeChain, geometryID); + } + } else { + if (windingStack.read(windingStack.size() - 1) != 0) { + int geometry = m_shape.getFirstGeometry(); + int geometryID = getGeometryID(geometry); + setChainParentage_(edgeChain, geometryID); + } + } + } + } + + boolean tryOptimizedInsertion_(Treap aet, int treeNodeIndex, + AttributeStreamOfInt32 new_edges, int cluster, int firstHalfEdge) { + int clusterHalfEdge = firstHalfEdge; + int attachedTreeNode = -1; + int newEdge = -1; + // Delete all edges that end at the cluster. + int count = 0; + do { + if (count == 2) + return false; + int n = getHalfEdgeUserIndex(clusterHalfEdge, treeNodeIndex); + if (n != -1) { + if (attachedTreeNode != -1) + return false;// two edges end at the cluster + attachedTreeNode = n; + } else { + if (newEdge != -1) + return false; // two edges start from the cluster + newEdge = clusterHalfEdge; + } + assert (getHalfEdgeOrigin(clusterHalfEdge) == cluster); + count++; + clusterHalfEdge = getHalfEdgeNext(getHalfEdgeTwin(clusterHalfEdge)); + } while (firstHalfEdge != clusterHalfEdge); + + if (newEdge == -1 || attachedTreeNode == -1) + return false; + + setHalfEdgeUserIndex(aet.getElement(attachedTreeNode), treeNodeIndex, + StridedIndexTypeCollection.impossibleIndex2()); + aet.setElement(attachedTreeNode, newEdge); + new_edges.add(attachedTreeNode); + return true; + } + + boolean trySetChainParentFromTwin_(int chainToSet, int twinChain) { + assert (getChainParent(chainToSet) == -1); + double area = getChainArea(chainToSet); + if (area == 0) + return false; + double twinArea = getChainArea(twinChain); + assert (twinArea != 0); + if (area > 0 && twinArea < 0) { + // assert(twinArea + area <= area * Number_utils::double_eps() * + // 100); + setChainParent_(chainToSet, twinChain); + return true; + } + if (area < 0 && twinArea > 0) { + // assert(-area <= twinArea); + setChainParent_(chainToSet, twinChain); + return true; + } else { + int twinParent = getChainParent(twinChain); + if (twinParent != -1) { + setChainParent_(chainToSet, twinParent); + return true; + } + } + + return false; + } + + void createHalfEdges_(int inputMode, AttributeStreamOfInt32 sorted_vertices) { + // After this loop all halfedges will be created. + // This loop also sets the known parentage on the edges. + // The half edges are connected with each other in a random order + m_halfEdgeIndex = m_shape.createUserIndex(); + + for (int i = 0, nvert = sorted_vertices.size(); i < nvert; i++) { + int vertex = sorted_vertices.get(i); + int cluster = m_shape.getUserIndex(vertex, m_clusterIndex); + + int path = m_shape.getPathFromVertex(vertex); + int geometry = m_shape.getGeometryFromPath(path); + int gt = m_shape.getGeometryType(geometry); + if (Geometry.isMultiPath(gt)) { + int next = m_shape.getNextVertex(vertex); + if (next == -1) + continue; + + int clusterTo = m_shape.getUserIndex(next, m_clusterIndex); + assert (clusterTo != -1); + assert (cluster != clusterTo);// probably will assert for + // verticasl segments! Need to + // refactor a little + int half_edge = newHalfEdgePair_(); + int twinEdge = getHalfEdgeTwin(half_edge); + + // add vertex to the half edge. + int vertIndex = m_clusterVertices.newElement(); + m_clusterVertices.setField(vertIndex, 0, vertex); + m_clusterVertices.setField(vertIndex, 1, -1); + setHalfEdgeVertexIterator_(half_edge, vertIndex); + + setHalfEdgeOrigin_(half_edge, cluster); + int firstHalfEdge = getClusterHalfEdge(cluster); + if (firstHalfEdge == -1) { + setClusterHalfEdge_(cluster, half_edge); + setHalfEdgePrev_(half_edge, twinEdge); + setHalfEdgeNext_(twinEdge, half_edge); + } else { + // It does not matter what order we insert the new edges in. + // We fix the order later. + int firstPrev = getHalfEdgePrev(firstHalfEdge); + assert (getHalfEdgeNext(firstPrev) == firstHalfEdge); + setHalfEdgePrev_(firstHalfEdge, twinEdge); + setHalfEdgeNext_(twinEdge, firstHalfEdge); + assert (getHalfEdgePrev(firstHalfEdge) == twinEdge); + assert (getHalfEdgeNext(twinEdge) == firstHalfEdge); + setHalfEdgeNext_(firstPrev, half_edge); + setHalfEdgePrev_(half_edge, firstPrev); + assert (getHalfEdgePrev(half_edge) == firstPrev); + assert (getHalfEdgeNext(firstPrev) == half_edge); + } + + setHalfEdgeOrigin_(twinEdge, clusterTo); + int firstTo = getClusterHalfEdge(clusterTo); + if (firstTo == -1) { + setClusterHalfEdge_(clusterTo, twinEdge); + setHalfEdgeNext_(half_edge, twinEdge); + setHalfEdgePrev_(twinEdge, half_edge); + } else { + int firstToPrev = getHalfEdgePrev(firstTo); + assert (getHalfEdgeNext(firstToPrev) == firstTo); + setHalfEdgePrev_(firstTo, half_edge); + setHalfEdgeNext_(half_edge, firstTo); + assert (getHalfEdgePrev(firstTo) == half_edge); + assert (getHalfEdgeNext(half_edge) == firstTo); + setHalfEdgeNext_(firstToPrev, twinEdge); + setHalfEdgePrev_(twinEdge, firstToPrev); + assert (getHalfEdgePrev(twinEdge) == firstToPrev); + assert (getHalfEdgeNext(firstToPrev) == twinEdge); + } + + int geometryID = getGeometryID(geometry); + // No chains yet exists, so we use a temporary user index to + // store chain parentage. + // The input polygons has been already simplified so their edges + // directed such that the hole is to the left from the edge + // (each edge is directed from the "from" to "to" point). + if (inputMode == EnumInputMode.enumInputModeBuildGraph) { + setHalfEdgeUserIndex(twinEdge, m_tmpHalfEdgeParentageIndex, + 0); // Hole is always to the left. left side here is + // the twin. + setHalfEdgeUserIndex(half_edge, + m_tmpHalfEdgeParentageIndex, + gt == Geometry.GeometryType.Polygon ? geometryID + : 0); + } else if (inputMode == EnumInputMode.enumInputModeSimplifyWinding) { + Point2D pt_1 = new Point2D(); + m_shape.getXY(vertex, pt_1); + Point2D pt_2 = new Point2D(); + m_shape.getXY(next, pt_2); + int windingNumber = 0; + int windingNumberTwin = 0; + if (pt_1.compare(pt_2) < 0) { + // The edge is directed bottom-up. That means it has the + // winding number of +1. + // The half-edge direction coincides with the edge + // direction. THe twin is directed top-down. + // The half edge will have the winding number of 1 and + // its twin the winding number of 0. + // When crossing the half-edge/twin pair from left to + // right, the winding number is changed by +1 + windingNumber = 1; + } else { + // The edge is directed top-down. That means it has the + // winding number of -1. + // The half-edge direction coincides with the edge + // direction. The twin is directed bottom-up. + // The half edge will have the winding number of 0 and + // its twin the winding number of -1. + // When crossing the half-edge/twin pair from left to + // right, the winding number is changed by -1. + windingNumberTwin = -1; + } + + // When we get a half-edge/twin pair, we can determine the + // winding number of the underlying edge + // by summing up the half-edge and twin's + // winding numbers. + + setHalfEdgeUserIndex(twinEdge, m_tmpHalfEdgeParentageIndex, + 0); + setHalfEdgeUserIndex(half_edge, + m_tmpHalfEdgeParentageIndex, 0); + // We split the winding number between the half edge and its + // twin. + // This allows us to determine which half edge goes in the + // direction of the edge, and also it allows to calculate + // the + // winging number by summing up the winding number of half + // edge and its twin. + setHalfEdgeUserIndex(half_edge, + m_tmpHalfEdgeWindingNumberIndex, windingNumber); + setHalfEdgeUserIndex(twinEdge, + m_tmpHalfEdgeWindingNumberIndex, windingNumberTwin); + } + int edgeBit = gt == Geometry.GeometryType.Polygon ? c_edgeBitMask + : 0; + setHalfEdgeParentage_(half_edge, geometryID | edgeBit); + setHalfEdgeParentage_(twinEdge, geometryID | edgeBit); + } + } + } + + void mergeVertexListsOfEdges_(int eDst, int eSrc) { + assert (getHalfEdgeTo(eDst) == getHalfEdgeTo(eSrc)); + assert (getHalfEdgeOrigin(eDst) == getHalfEdgeOrigin(eSrc)); + + { + int vertFirst2 = getHalfEdgeVertexIterator(eSrc); + if (vertFirst2 != -1) { + int vertFirst1 = getHalfEdgeVertexIterator(eDst); + m_clusterVertices.setField(vertFirst2, 1, vertFirst1); + setHalfEdgeVertexIterator_(eDst, vertFirst2); + setHalfEdgeVertexIterator_(eSrc, -1); + } + } + + int eDstTwin = getHalfEdgeTwin(eDst); + int eSrcTwin = getHalfEdgeTwin(eSrc); + { + int vertFirst2 = getHalfEdgeVertexIterator(eSrcTwin); + if (vertFirst2 != -1) { + int vertFirst1 = getHalfEdgeVertexIterator(eDstTwin); + m_clusterVertices.setField(vertFirst2, 1, vertFirst1); + setHalfEdgeVertexIterator_(eDstTwin, vertFirst2); + setHalfEdgeVertexIterator_(eSrcTwin, -1); + } + } + } + + void sortHalfEdgesByAngle_(int inputMode) { + AttributeStreamOfInt32 angleSorter = new AttributeStreamOfInt32(0); + angleSorter.reserve(10); + // Now go through the clusters, sort edges in each cluster by angle, and + // reconnect the halfedges of sorted edges in the sorted order. + // Also share the parentage information between coinciding edges and + // remove duplicates. + for (int cluster = getFirstCluster(); cluster != -1; cluster = getNextCluster(cluster)) { + angleSorter.resizePreserveCapacity(0); + int first = getClusterHalfEdge(cluster); + if (first != -1) { + // 1. sort edges originating at the cluster by angle (counter - + // clockwise). + int edge = first; + do { + angleSorter.add(edge);// edges have the cluster in their + // origin and are directed away from + // it. The twin edges are directed + // towards the cluster. + edge = getHalfEdgeNext(getHalfEdgeTwin(edge)); + } while (edge != first); + + if (angleSorter.size() > 1) { + if (angleSorter.size() > 2) { + angleSorter.Sort(0, angleSorter.size(), + new TopoGraphAngleComparer(this)); // std::sort(angleSorter.get_ptr(), + // angleSorter.get_ptr() + // + + // angleSorter.size(), + // TopoGraphAngleComparer(this)); + angleSorter.add(angleSorter.get(0)); + } else { + if (compareEdgeAngles_(angleSorter.get(0), + angleSorter.get(1)) > 0) { + int tmp = angleSorter.get(0); + angleSorter.set(0, angleSorter.get(1)); + angleSorter.set(1, tmp); + } + } + // 2. get rid of duplicate edges by merging them (duplicate + // edges appear at this step because we converted all + // segments into the edges, including overlapping). + int e0 = angleSorter.get(0); + int ePrev = e0; + int ePrevTo = getHalfEdgeTo(ePrev); + int ePrevTwin = getHalfEdgeTwin(ePrev); + int prevMerged = -1; + for (int i = 1, n = angleSorter.size(); i < n; i++) { + int e = angleSorter.get(i); + int eTwin = getHalfEdgeTwin(e); + int eTo = getHalfEdgeOrigin(eTwin); + assert (getHalfEdgeOrigin(e) == getHalfEdgeOrigin(ePrev));// e + // origin + // and + // ePrev + // origin + // are + // equal + // by + // definition + // (e + // and + // ePrev + // emanate + // from + // the + // same + // cluster) + if (eTo == ePrevTo && e != ePrev)// e's To cluster and + // ePrev's To + // cluster are + // equal, means the + // edges coincide + // and need to be + // merged. + {// remove duplicate edge. Before removing, propagate + // the parentage to the remaning edge + if (inputMode == EnumInputMode.enumInputModeBuildGraph) { + int newEdgeParentage = getHalfEdgeParentageMask_(ePrev) + | getHalfEdgeParentageMask_(e); + setHalfEdgeParentage_(ePrev, newEdgeParentage); + setHalfEdgeParentage_(ePrevTwin, + newEdgeParentage); + assert (getHalfEdgeParentageMask_(ePrev) == getHalfEdgeParentageMask_(ePrevTwin)); + + setHalfEdgeUserIndex( + ePrev, + m_tmpHalfEdgeParentageIndex, + getHalfEdgeUserIndex(ePrev, + m_tmpHalfEdgeParentageIndex) + | getHalfEdgeUserIndex(e, + m_tmpHalfEdgeParentageIndex)); + setHalfEdgeUserIndex( + ePrevTwin, + m_tmpHalfEdgeParentageIndex, + getHalfEdgeUserIndex(ePrevTwin, + m_tmpHalfEdgeParentageIndex) + | getHalfEdgeUserIndex(eTwin, + m_tmpHalfEdgeParentageIndex)); + } else if (m_tmpHalfEdgeWindingNumberIndex != -1) { + // when doing simplify the + // m_tmpHalfEdgeWindingNumberIndex contains the + // winding number. + // When edges are merged their winding numbers + // are added. + int newHalfEdgeWinding = getHalfEdgeUserIndex( + ePrev, m_tmpHalfEdgeWindingNumberIndex) + + getHalfEdgeUserIndex(e, + m_tmpHalfEdgeWindingNumberIndex); + int newTwinEdgeWinding = getHalfEdgeUserIndex( + ePrevTwin, + m_tmpHalfEdgeWindingNumberIndex) + + getHalfEdgeUserIndex(eTwin, + m_tmpHalfEdgeWindingNumberIndex); + setHalfEdgeUserIndex(ePrev, + m_tmpHalfEdgeWindingNumberIndex, + newHalfEdgeWinding); + setHalfEdgeUserIndex(ePrevTwin, + m_tmpHalfEdgeWindingNumberIndex, + newTwinEdgeWinding); + // The winding number of an edge is a sum of the + // winding numbers of the half edge and its + // twin. + // To determine which half edge direction + // coincides with the edge direction, determine + // which half edge has larger abs value of + // winding number. If half edge and twin winding + // numbers cancel each other, the edge winding + // number is zero, meaning there are + // even number of edges coinciding there and + // half of them has opposite direction to + // another half. + } + + mergeVertexListsOfEdges_(ePrev, e); + deleteEdgeImpl_(e); + assert (n < 3 || e0 == angleSorter.read(angleSorter + .size() - 1)); + prevMerged = ePrev; + angleSorter.set(i, -1); + if (e == e0) { + angleSorter.set(0, -1); + e0 = -1; + } + + continue; + } + + updateVertexToHalfEdgeConnection_(prevMerged, false); + prevMerged = -1; + ePrev = e; + ePrevTo = eTo; + ePrevTwin = eTwin; + } + + updateVertexToHalfEdgeConnection_(prevMerged, false); + prevMerged = -1; + + // 3. Reconnect edges in the sorted order. + // This guarantees that the smallest faces are clockwise + // (when two opposite directed faces are exactly equal). + e0 = -1; + for (int i = 0, n = angleSorter.size(); i < n; i++) { + int e = angleSorter.get(i); + if (e == -1) + continue; + if (e0 == -1) { + e0 = e; + ePrev = e0; + ePrevTo = getHalfEdgeTo(ePrev); + ePrevTwin = getHalfEdgeTwin(ePrev); + continue; + } + int eTwin = getHalfEdgeTwin(e); + int eTo = getHalfEdgeOrigin(eTwin); + assert (getHalfEdgeOrigin(e) == getHalfEdgeOrigin(ePrev)); + assert (eTo != ePrevTo); + setHalfEdgeNext_(ePrevTwin, e); + setHalfEdgePrev_(e, ePrevTwin); + ePrev = e; + ePrevTo = eTo; + ePrevTwin = eTwin; + } + + setClusterHalfEdge_(cluster, e0);// smallest angle goes + // first. + } + } + } + } + + void buildChains_() { + // Creates chains and puts them in the list of chains. + // Does not set the chain parentage + // Does not connect chains + + int firstChain = -1; + int visitedHalfEdgeIndex = createUserIndexForHalfEdges(); + // Visit all the clusters + for (int cluster = getFirstCluster(); cluster != -1; cluster = getNextCluster(cluster)) { + // For each cluster visit all half edges on the cluster + int first = getClusterHalfEdge(cluster); + if (first != -1) { + int edge = first; + do { + if (getHalfEdgeUserIndex(edge, visitedHalfEdgeIndex) != 1)// check + // if + // we + // have + // visited + // this + // halfedge + // already + {// if we have not visited this halfedge yet, then we have + // not created a chain for it yet. + int chain = newChain_();// new chain's parentage is set + // to 0. + setChainHalfEdge_(chain, edge);// Note, the half-edge's + // Origin is the lowest + // point of the chain. + setChainNext_(chain, firstChain);// add the new chain to + // the list of + // chains. + if (firstChain != -1) { + setChainPrev_(firstChain, chain); + } + firstChain = chain; + // go thorough all halfedges until return back to the + // same one. Thus forming a chain. + int parentage = 0; + int e = edge; + do { + // accumulate chain parentage from all the chain + // edges m_tmpHalfEdgeParentageIndex. + parentage |= getHalfEdgeUserIndex(e, + m_tmpHalfEdgeParentageIndex); + assert (getHalfEdgeUserIndex(e, + visitedHalfEdgeIndex) != 1); + setHalfEdgeChain_(e, chain); + setHalfEdgeUserIndex(e, visitedHalfEdgeIndex, 1);// mark + // the + // edge + // visited. + e = getHalfEdgeNext(e); + } while (e != edge); + setChainParentage_(chain, parentage); + + // Polygon::SPtr poly = + // dbg_dump_chain_to_polygon_(chain); + // char buf[1024]; + // sprintf(buf, "c:\\temp\\chain%d.txt", + // m_chain_data->size()); + // Operator_factory_local::SaveGeometryToInternalTextFile(buf, + // poly); + } + + edge = getHalfEdgeNext(getHalfEdgeTwin(edge));// next + // halfedge + // on the + // cluster + } while (edge != first); + } + } + + // add the Universe chain. We want it to be the one that getFirstChain + // returns. + int chain = newChain_(); + setChainHalfEdge_(chain, -1); + setChainNext_(chain, firstChain); + if (firstChain != -1) + setChainPrev_(firstChain, chain); + + m_universeChain = chain; + + m_chainAreas = new AttributeStreamOfDbl(m_chainData.size(), + NumberUtils.TheNaN); + m_chainPerimeters = new AttributeStreamOfDbl(m_chainData.size(), + NumberUtils.TheNaN); + + setChainArea_(m_universeChain, NumberUtils.positiveInf());// the + // Universe + // is + // infinite + setChainPerimeter_(m_universeChain, NumberUtils.positiveInf());// the + // Universe + // is + // infinite + + deleteUserIndexForHalfEdges(visitedHalfEdgeIndex); + } + + void simplify_(int inputMode) { + assert (inputMode != EnumInputMode.enumInputModeBuildGraph); + if (inputMode == EnumInputMode.enumInputModeSimplifyAlternate) { + simplifyAlternate_(); + } else if (inputMode == EnumInputMode.enumInputModeSimplifyWinding) { + simplifyWinding_(); + } + } + + void simplifyAlternate_() { + int geometry = m_shape.getFirstGeometry(); + int geometryID = getGeometryID(geometry); + + int universe_chain = getFirstChain(); // winding = 0; + simplifyAlternateRecursion_(universe_chain, geometryID, false); + } + + void simplifyAlternateRecursion_(int parent_chain, int geometryID, + boolean bEven) { + boolean bEven_ = !bEven; + for (int chain = getChainFirstIsland(parent_chain); chain != -1; chain = getChainNextInParent(chain)) { + if (bEven_) + setChainParentage_(chain, geometryID); + else + setChainParentage_(chain, 0); + + simplifyAlternateRecursion_(chain, geometryID, bEven_); + } + } + + void simplifyWinding_() { + // there is nothing to do. + } + + boolean removeSpikes_() { + boolean bRemoved = false; + for (int chain = getFirstChain(); chain != -1;) { + int firstHalfEdge = getChainHalfEdge(chain); + assert (firstHalfEdge != -1); + + boolean bRemovedEdge = false; + + int prev = getHalfEdgePrev(firstHalfEdge); + int half_edge = firstHalfEdge; + while (true) { + int twin = getHalfEdgeTwin(half_edge); + if (prev == twin) { + int next2 = deleteEdgeInternal_(half_edge); + bRemovedEdge = true; + if (next2 == -1) { + // The chain has been deleted. + break; + } + + if (half_edge == firstHalfEdge) { + firstHalfEdge = next2; + } + + half_edge = next2; + prev = getHalfEdgePrev(half_edge); + } else { + prev = half_edge; + half_edge = getHalfEdgeNext(half_edge); + if (half_edge == firstHalfEdge) + break; + } + } + + bRemoved |= bRemovedEdge; + if (bRemovedEdge) { + chain = deleteChain_(chain); + } else + chain = getChainNext(chain); + } + + return bRemoved; + } + + void setEditShapeImpl_(EditShape shape, int inputMode, + AttributeStreamOfInt32 editShapeGeometries, + ProgressTracker progress_tracker) { + assert (editShapeGeometries == null || editShapeGeometries.size() > 0); + + removeShape(); + assert (m_shape == null); + m_shape = shape; + m_geometryIDIndex = m_shape.createGeometryUserIndex(); + // sort vertices lexicographically + // Firstly copy all vertices to an array. + AttributeStreamOfInt32 verticesSorter = new AttributeStreamOfInt32(0); + verticesSorter.reserve(editShapeGeometries != null ? m_shape + .getPointCount(editShapeGeometries.get(0)) : m_shape + .getTotalPointCount()); + int path_count = 0; + int geomID = 1; + {// scope + int geometry = editShapeGeometries != null ? editShapeGeometries + .get(0) : m_shape.getFirstGeometry(); + int ind = 1; + while (geometry != -1) { + m_shape.setGeometryUserIndex(geometry, m_geometryIDIndex, + geomID); + geomID = geomID << 1; + for (int path = m_shape.getFirstPath(geometry); path != -1; path = m_shape + .getNextPath(path)) { + int vertex = m_shape.getFirstVertex(path); + for (int index = 0, n = m_shape.getPathSize(path); index < n; index++) { + verticesSorter.add(vertex); + vertex = m_shape.getNextVertex(vertex); + } + } + + if (!Geometry.isPoint(m_shape.getGeometryType(geometry))) + path_count += m_shape.getPathCount(geometry); + + if (editShapeGeometries != null) { + geometry = ind < editShapeGeometries.size() ? editShapeGeometries + .get(ind) : -1; + ind++; + } else + geometry = m_shape.getNextGeometry(geometry); + } + } + + m_pointCount = verticesSorter.size(); + + // sort + m_shape.sortVerticesSimpleByY_(verticesSorter, 0, m_pointCount); + + if (m_clusterVertices == null) { + m_clusterVertices = new StridedIndexTypeCollection(2); + m_clusterData = new StridedIndexTypeCollection(8); + m_halfEdgeData = new StridedIndexTypeCollection(8); + m_chainData = new StridedIndexTypeCollection(8); + } + + m_clusterVertices.setCapacity(m_pointCount); + + if ((progress_tracker != null) && !(progress_tracker.progress(-1, -1))) + throw new UserCancelException(); + + m_clusterData.setCapacity(m_pointCount + 10);// 10 for some self + // intersections + m_halfEdgeData.setCapacity(2 * m_pointCount + 32); + m_chainData.setCapacity(Math.max((int) 32, path_count)); + + // create all clusters + assert (m_clusterIndex == -1);// cleanup was incorrect + m_clusterIndex = m_shape.createUserIndex(); + Point2D ptFirst = new Point2D(); + int ifirst = 0; + Point2D pt = new Point2D(); + ptFirst.setNaN(); + for (int i = 0; i <= m_pointCount; i++) { + if (i < m_pointCount) { + int vertex = verticesSorter.get(i); + m_shape.getXY(vertex, pt); + } else { + pt.setNaN();// makes it to go into the following "if" statement. + } + if (!ptFirst.isEqual(pt)) { + if (ifirst < i) { + int cluster = newCluster_(); + int vertFirst = -1; + int vert = -1; + for (int ind = ifirst; ind < i; ind++) { + vert = verticesSorter.get(ind); + m_shape.setUserIndex(vert, m_clusterIndex, cluster); + + // add vertex to the cluster's vertex list + int vertIndex = m_clusterVertices.newElement(); + m_clusterVertices.setField(vertIndex, 0, vert); + m_clusterVertices.setField(vertIndex, 1, vertFirst); + vertFirst = vertIndex; + + int path = m_shape.getPathFromVertex(vert); + int geometry = m_shape.getGeometryFromPath(path); + int geometryID = getGeometryID(geometry); + setClusterParentage_(cluster, + getClusterParentage(cluster) | geometryID); + } + setClusterVertexIterator_(cluster, vertFirst); + setClusterVertexIndex_(cluster, + m_shape.getVertexIndex(vert)); + + if (m_lastCluster != -1) + setNextCluster_(m_lastCluster, cluster); + + setPrevCluster_(cluster, m_lastCluster); + + m_lastCluster = cluster; + if (m_firstCluster == -1) + m_firstCluster = cluster; + } + ifirst = i; + ptFirst.setCoords(pt); + } + } + + if ((progress_tracker != null) && !(progress_tracker.progress(-1, -1))) + throw new UserCancelException(); + + m_tmpHalfEdgeParentageIndex = createUserIndexForHalfEdges(); + if (inputMode == EnumInputMode.enumInputModeSimplifyWinding) { + m_tmpHalfEdgeWindingNumberIndex = createUserIndexForHalfEdges(); + } + + createHalfEdges_(inputMode, verticesSorter);// For each geometry produce + // clusters and half edges + + // dbg_navigate_(); + + sortHalfEdgesByAngle_(inputMode); + + buildChains_(); + + deleteUserIndexForHalfEdges(m_tmpHalfEdgeParentageIndex); + m_tmpHalfEdgeParentageIndex = -1; + + planeSweepParentage_(inputMode, progress_tracker); + // dbg_chk_chain_parents_(); + + if (inputMode != EnumInputMode.enumInputModeBuildGraph) + simplify_(inputMode); + + // dbg_navigate_(); + // dbg_dump_chains_(); + } + + void deleteEdgeImpl_(int half_edge) { + int halfEdgeNext = getHalfEdgeNext(half_edge); + int halfEdgePrev = getHalfEdgePrev(half_edge); + int halfEdgeTwin = getHalfEdgeTwin(half_edge); + int halfEdgeTwinNext = getHalfEdgeNext(halfEdgeTwin); + int halfEdgeTwinPrev = getHalfEdgePrev(halfEdgeTwin); + + if (halfEdgeNext != halfEdgeTwin) { + setHalfEdgeNext_(halfEdgeTwinPrev, halfEdgeNext); + setHalfEdgePrev_(halfEdgeNext, halfEdgeTwinPrev); + } + + if (halfEdgePrev != halfEdgeTwin) { + setHalfEdgeNext_(halfEdgePrev, halfEdgeTwinNext); + setHalfEdgePrev_(halfEdgeTwinNext, halfEdgePrev); + } + + int cluster_1 = getHalfEdgeOrigin(half_edge); + int clusterFirstEdge1 = getClusterHalfEdge(cluster_1); + if (clusterFirstEdge1 == half_edge) { + if (halfEdgeTwinNext != half_edge) + setClusterHalfEdge_(cluster_1, halfEdgeTwinNext); + else + setClusterHalfEdge_(cluster_1, -1);// cluster has no more edges + } + + int cluster2 = getHalfEdgeOrigin(halfEdgeTwin); + int clusterFirstEdge2 = getClusterHalfEdge(cluster2); + if (clusterFirstEdge2 == halfEdgeTwin) { + if (halfEdgeNext != halfEdgeTwin) + setClusterHalfEdge_(cluster2, halfEdgeNext); + else + setClusterHalfEdge_(cluster2, -1);// cluster has no more edges + } + + m_halfEdgeData.deleteElement(half_edge); + m_halfEdgeData.deleteElement(halfEdgeTwin); + } + + int getLeftSkipPolylines_(Treap aet, int treeNode) { + int leftNode = treeNode; + + for (;;) { + leftNode = aet.getPrev(leftNode); + if (leftNode != -1) { + int e = aet.getElement(leftNode); + int leftChain = getHalfEdgeChain(e); + if (leftChain != getHalfEdgeChain(getHalfEdgeTwin(e))) { + return e; + } else { + // the left edge is a piece of polyline - does not + // contribute to the face parentage + } + } else { + return -1; + } + } + } + + TopoGraph() { + c_edgeParentageMask = ((int) -1) + ^ ((int) 1 << (NumberUtils.sizeOf((int) 0) * 8 - 1)); + c_edgeBitMask = (int) 1 << (NumberUtils.sizeOf((int) 0) * 8 - 1); + m_firstCluster = -1; + m_lastCluster = -1; + m_geometryIDIndex = -1; + m_clusterIndex = -1; + m_halfEdgeIndex = -1; + m_universeChain = -1; + m_tmpHalfEdgeParentageIndex = -1; + m_tmpHalfEdgeWindingNumberIndex = -1; + m_pointCount = 0; + } + + EditShape getShape() { + return m_shape; + } + + // Sets an edit shape. The geometry has to be cracked and clustered before + // calling this! + void setEditShape(EditShape shape, ProgressTracker progress_tracker) { + setEditShapeImpl_(shape, EnumInputMode.enumInputModeBuildGraph, null, + progress_tracker); + } + + // Sets an edit shape and simplifies a geometry in it using "odd-even" rule. + // This is the default simplify method for polygons. + // The result edit shape contains a simple polygon. + // The input shape could be a non-simple polygon or a polyline (polyline + // need to form some rings to produce non-empty result). + // The geometry has to be cracked and clustered before calling this! + void setAndSimplifyEditShapeAlternate(EditShape shape, int geometry) { + AttributeStreamOfInt32 geoms = new AttributeStreamOfInt32(0); + geoms.add(geometry); + setEditShapeImpl_(shape, EnumInputMode.enumInputModeSimplifyAlternate, + geoms, null); + } + + // Sets an edit shape and simplifies a geometry in it using "winding" rule. + // The result edit shape contains a simple polygon. + // The input shape could be a non-simple polygon or a polyline (polyline + // need to form some rings to produce non-empty result). + // The geometry has to be cracked and clustered before calling this! + void setAndSimplifyEditShapeWinding(EditShape shape, int geometry) { + AttributeStreamOfInt32 geoms = new AttributeStreamOfInt32(0); + geoms.add(geometry); + setEditShapeImpl_(shape, EnumInputMode.enumInputModeSimplifyWinding, + geoms, null); + } + + // Removes shape from the topograph and removes any user index created on + // the edit shape. + void removeShape() { + if (m_shape == null) + return; + + if (m_geometryIDIndex != -1) + m_shape.removeGeometryUserIndex(m_geometryIDIndex); + + m_geometryIDIndex = -1; + if (m_clusterIndex != -1) { + m_shape.removeUserIndex(m_clusterIndex); + m_clusterIndex = -1; + } + + if (m_halfEdgeIndex != -1) { + m_shape.removeUserIndex(m_halfEdgeIndex); + m_halfEdgeIndex = -1; + } + + if (m_tmpHalfEdgeParentageIndex != -1) { + deleteUserIndexForHalfEdges(m_tmpHalfEdgeParentageIndex); + m_tmpHalfEdgeParentageIndex = -1; + } + + if (m_tmpHalfEdgeWindingNumberIndex != -1) { + deleteUserIndexForHalfEdges(m_tmpHalfEdgeWindingNumberIndex); + m_tmpHalfEdgeWindingNumberIndex = -1; + } + + m_shape = null; + m_clusterData.deleteAll(true); + m_clusterVertices.deleteAll(true); + m_firstCluster = -1; + m_lastCluster = -1; + + if (m_halfEdgeData != null) + m_halfEdgeData.deleteAll(true); + if (m_edgeIndices != null) + m_edgeIndices.clear(); + if (m_clusterIndices != null) + m_clusterIndices.clear(); + if (m_chainIndices != null) + m_chainIndices.clear(); + if (m_chainData != null) + m_chainData.deleteAll(true); + m_universeChain = -1; + m_chainAreas = null; + } + + // Returns a half-edge emanating the cluster. All other half-edges can be + // visited with: + // incident_half_edge = getHalfEdgeTwin(half_edge);//get twin of the + // half_edge, it has the vertex as the end point. + // emanating_half_edge = getHalfEdgeTwin(incident_half_edge); //get next + // emanating half-edge + int getClusterHalfEdge(int cluster) { + return m_clusterData.getField(cluster, 2); + } + + // Returns the coordinates of the cluster + void getXY(int cluster, Point2D pt) { + int vindex = getClusterVertexIndex_(cluster); + m_shape.getXYWithIndex(vindex, pt); + } + + // Returns parentage mask of the cluster + int getClusterParentage(int cluster) { + return m_clusterData.getField(cluster, 1); + } + + // Returns first cluster in the Topo_graph (has lowest y, x coordinates). + int getFirstCluster() { + return m_firstCluster; + } + + // Returns previous cluster in the Topo_graph (in the sorted order of y,x + // coordinates). + int getPrevCluster(int cluster) { + return m_clusterData.getField(cluster, 3); + } + + // Returns next cluster in the Topo_graph (in the sorted order of y,x + // coordinates). + int getNextCluster(int cluster) { + return m_clusterData.getField(cluster, 4); + } + + // Returns an exterior chain of a face this cluster belongs to (belongs only + // to interior). set only for the clusters that are standalone clusters (do + // not have half-edges with them). + int getClusterChain(int cluster) { + return m_clusterData.getField(cluster, 6); + } + + // Returns iterator for cluster vertices + int getClusterVertexIterator(int cluster) { + return m_clusterData.getField(cluster, 7); + } + + // Increments iterator. Returns -1 if no more vertices in the cluster + int incrementVertexIterator(int vertexIterator) { + return m_clusterVertices.getField(vertexIterator, 1); + } + + // Dereference the iterator + int getVertexFromVertexIterator(int vertexIterator) { + return m_clusterVertices.getField(vertexIterator, 0); + } + + // Returns a user index value for the cluster. + int getClusterUserIndex(int cluster, int index) { + int i = getClusterIndex_(cluster); + AttributeStreamOfInt32 stream = m_clusterIndices.get(index); + if (stream.size() <= i) + return -1; + return stream.read(i); + } + + // Sets a user index value for the cluster. + void setClusterUserIndex(int cluster, int index, int value) { + int i = getClusterIndex_(cluster); + AttributeStreamOfInt32 stream = m_clusterIndices.get(index); + if (stream.size() <= i) + stream.resize(m_clusterData.size(), -1); + + stream.write(i, value); + } + + // Creates a new user index for the cluster. The index values are set to -1. + int createUserIndexForClusters() { + if (m_clusterIndices == null) { + m_clusterIndices = new ArrayList(3); + } + + AttributeStreamOfInt32 new_stream = new AttributeStreamOfInt32( + m_clusterData.capacity(), -1); + for (int i = 0, n = m_clusterIndices.size(); i < n; i++) { + if (m_clusterIndices.get(i) == null) { + m_clusterIndices.set(i, new_stream); + return i; + } + } + m_clusterIndices.add(new_stream); + return m_clusterIndices.size() - 1; + } + + // Deletes user index + void deleteUserIndexForClusters(int userIndex) { + assert (m_clusterIndices.get(userIndex) != null); + m_clusterIndices.set(userIndex, null); + } + + // Returns origin of this half edge. To get the other end: + // incident_half_edge = getHalfEdgeTwin(half_edge); + // edge_end_point = getHalfEdgeOrigin(incident_half_edge); + int getHalfEdgeOrigin(int half_edge) { + return m_halfEdgeData.getField(half_edge, 1); + } + + // Returns the to point of the half edge + int getHalfEdgeTo(int half_edge) { + return getHalfEdgeOrigin(getHalfEdgeTwin(half_edge)); + } + + // Twin of this halfedge, it has opposite direction and same endpoints + int getHalfEdgeTwin(int half_edge) { + return m_halfEdgeData.getField(half_edge, 4); + } + + // Returns previous halfedge. It ends, where this halfedge starts. + int getHalfEdgePrev(int half_edge) { + return m_halfEdgeData.getField(half_edge, 5); + } + + // Returns next halfedge. It starts, where this halfedge ends. + int getHalfEdgeNext(int half_edge) { + return m_halfEdgeData.getField(half_edge, 6); + } + + // Returns half edge chain. Chain is on the right from the halfedge + int getHalfEdgeChain(int half_edge) { + return m_halfEdgeData.getField(half_edge, 2); + } + + // Returns half edge chain parentage. The call is implemented as as + // getChainParentage(getHalfEdgeChain()); + int getHalfEdgeFaceParentage(int half_edge) { + return getChainParentage(m_halfEdgeData.getField(half_edge, 2)); + } + + // Returns iterator for cluster vertices + int getHalfEdgeVertexIterator(int half_edge) { + return m_halfEdgeData.getField(half_edge, 7); + } + + // Returns the coordinates of the origin of the half_edge + void getHalfEdgeFromXY(int half_edge, Point2D pt) { + getXY(getHalfEdgeOrigin(half_edge), pt); + } + + // Returns the coordinates of the end of the half_edge + void getHalfEdgeToXY(int half_edge, Point2D pt) { + getXY(getHalfEdgeTo(half_edge), pt); + } + + // Returns parentage mask of this halfedge. Parentage mask of halfedge and + // its twin are the same + int getHalfEdgeParentage(int half_edge) { + return m_halfEdgeData.getField(half_edge, 3) & c_edgeParentageMask; + } + + // Returns a user index value for the half edge + int getHalfEdgeUserIndex(int half_edge, int index) { + int i = getHalfEdgeIndex_(half_edge); + AttributeStreamOfInt32 stream = m_edgeIndices.get(index); + if (stream.size() <= i) + return -1; + + return stream.read(i); + } + + // Sets a user index value for a half edge + void setHalfEdgeUserIndex(int half_edge, int index, int value) { + int i = getHalfEdgeIndex_(half_edge); + AttributeStreamOfInt32 stream = m_edgeIndices.get(index); + if (stream.size() <= i) + stream.resize(m_halfEdgeData.size(), -1); + + stream.write(i, value); + } + + // create a new user index for half edges. The index values are set to -1. + int createUserIndexForHalfEdges() { + if (m_edgeIndices == null) + m_edgeIndices = new ArrayList(3); + + AttributeStreamOfInt32 new_stream = new AttributeStreamOfInt32( + m_halfEdgeData.capacity(), -1); + for (int i = 0, n = m_edgeIndices.size(); i < n; i++) { + if (m_edgeIndices.get(i) == null) { + m_edgeIndices.set(i, new_stream); + return i; + } + } + m_edgeIndices.add(new_stream); + return m_edgeIndices.size() - 1; + } + + // Deletes the given user index for half edges + void deleteUserIndexForHalfEdges(int userIndex) { + assert (m_edgeIndices.get(userIndex) != null); + m_edgeIndices.set(userIndex, null); + } + + // Deletes the half_edge and it's twin. It works presently when removing a + // spike only. + // Returns next valid half-edge, or -1 if no more half edges. + // Use with care. + int deleteEdgeInternal_(int half_edge) { + int chain = getHalfEdgeChain(half_edge); + int halfEdgeTwin = getHalfEdgeTwin(half_edge); + int chainTwin = getHalfEdgeChain(halfEdgeTwin); + // This function only works for spikes. These two asserts check for that + assert (chainTwin == chain); + assert (half_edge == getHalfEdgeNext(halfEdgeTwin) || halfEdgeTwin == getHalfEdgeNext(half_edge)); + + int n = getHalfEdgeNext(half_edge); + if (n == halfEdgeTwin) { + n = getHalfEdgeNext(n); + if (n == half_edge) + n = -1; + } + + if (getChainHalfEdge(chain) == half_edge) { + setChainHalfEdge_(chain, n); + } + + if (!NumberUtils.isNaN(getChainArea(chain))) { + setChainArea_(chain, NumberUtils.TheNaN); + setChainPerimeter_(chain, NumberUtils.TheNaN); + } + + updateVertexToHalfEdgeConnection_(half_edge, true); + + deleteEdgeImpl_(half_edge);// does not change chain information + return n; + } + + // Deletes the halfEdges and their twin. The chains are broken after this + // call. + // For every chain the halfedges belong to, it will set the first edge to + // -1. + // However, the halfedge will still reference the chain so one can get the + // parentage information still. + void deleteEdgesBreakFaces_(AttributeStreamOfInt32 edgesToDelete) { + for (int i = 0, n = edgesToDelete.size(); i < n; i++) { + int half_edge = edgesToDelete.get(i); + int chain = getHalfEdgeChain(half_edge); + int halfEdgeTwin = getHalfEdgeTwin(half_edge); + int chainTwin = getHalfEdgeChain(halfEdgeTwin); + setChainHalfEdge_(chain, -1); + setChainHalfEdge_(chainTwin, -1); + updateVertexToHalfEdgeConnection_(half_edge, true); + deleteEdgeImpl_(half_edge); + } + } + + boolean doesHalfEdgeBelongToAPolygonInterior(int half_edge, int polygonId) { + // Half edge belongs to polygon interior if both it and its twin belong + // to boundary of faces that have the polygon parentage (the poygon both + // to the left and to the right of the edge). + int p_1 = getHalfEdgeFaceParentage(half_edge); + int p_2 = getHalfEdgeFaceParentage(getHalfEdgeTwin(half_edge)); + return (p_1 & polygonId) != 0 && (p_2 & polygonId) != 0; + } + + boolean doesHalfEdgeBelongToAPolygonExterior(int half_edge, int polygonId) { + // Half edge belongs to polygon interior if both it and its twin belong + // to boundary of faces that have the polygon parentage (the poygon both + // to the left and to the right of the edge). + int p_1 = getHalfEdgeFaceParentage(half_edge); + int p_2 = getHalfEdgeFaceParentage(getHalfEdgeTwin(half_edge)); + return (p_1 & polygonId) == 0 && (p_2 & polygonId) == 0; + } + + boolean doesHalfEdgeBelongToAPolygonBoundary(int half_edge, int polygonId) { + // Half edge overlaps polygon boundary + int p_1 = getHalfEdgeParentage(half_edge); + return (p_1 & polygonId) != 0; + } + + boolean doesHalfEdgeBelongToAPolylineInterior(int half_edge, int polylineId) { + // Half-edge belongs to a polyline interioir if it has the polyline + // parentage (1D intersection (aka overlap)). + int p_1 = getHalfEdgeParentage(half_edge); + if ((p_1 & polylineId) != 0) { + return true; + } + + return false; + } + + boolean doesHalfEdgeBelongToAPolylineExterior(int half_edge, int polylineId) { + // Half-edge belongs to a polyline Exterioir if it does not have the + // polyline parentage and both its clusters also do not have polyline's + // parentage (to exclude touch at point). + int p_1 = getHalfEdgeParentage(half_edge); + if ((p_1 & polylineId) == 0) { + int c = getHalfEdgeOrigin(half_edge); + int pc = getClusterParentage(c); + if ((pc & polylineId) == 0) { + c = getHalfEdgeTo(half_edge); + pc = getClusterParentage(c); + if ((pc & polylineId) == 0) { + return true; + } + } + } + + return false; + } + + boolean doesClusterBelongToAPolygonInterior(int cluster, int polygonId) { + // cluster belongs to a polygon interior when + // 1) It is a standalone cluster that has face parentage of this polygon + // GetClusterFaceParentage() + // 2) or It is a cluster with half edges attached and + // a) It is not on the polygon boundrary (get_cluster_parentage) + // b) Any half edge associated with it has face parentage of the polygon + // (get_half_edge_face_parentage(getClusterHalfEdge())) + + int chain = getClusterChain(cluster); + if (chain != -1) { + if ((getChainParentage(chain) & polygonId) != 0) { + return true; + } + } else { + int p_1 = getClusterParentage(cluster); + if ((p_1 & polygonId) == 0)// not on the polygon boundary + { + int half_edge = getClusterHalfEdge(cluster); + assert (half_edge != -1); + + int p_2 = getHalfEdgeFaceParentage(half_edge); + if ((p_2 & polygonId) != 0) { + return true; + } + } + } + + return false; + } + + boolean doesClusterBelongToAPolygonExterior(int cluster, int polygonId) { + int p_1 = getClusterParentage(cluster); + if ((p_1 & polygonId) == 0) { + return doesClusterBelongToAPolygonInterior(cluster, polygonId); + } + + return false; + } + + boolean doesClusterBelongToAPolygonBoundary(int cluster, int polygonId) { + int p_1 = getClusterParentage(cluster); + if ((p_1 & polygonId) != 0) { + return true; + } + + return false; + } + + // bool DoesClusterBelongToAPolylineInterioir(int cluster, int polylineId); + // bool does_cluster_belong_to_a_polyline_exterior(int cluster, int + // polylineId); + // bool does_cluster_belong_to_a_polyline_boundary(int cluster, int + // polylineId); + + // Returns the first chain, which is always the Universe chain. + int getFirstChain() { + return m_universeChain; + } + + // Returns the chain half edge. + int getChainHalfEdge(int chain) { + return m_chainData.getField(chain, 1); + } + + // Returns the chain's face parentage. That is the parentage of a face this + // chain borders with. + int getChainParentage(int chain) { + return m_chainData.getField(chain, 2); + } + + // Returns the parent of the chain (the chain, this chain is inside of). + int getChainParent(int chain) { + return m_chainData.getField(chain, 3); + } + + // Returns the first island chain in that chain. Island chains are always + // counterclockwise. + // Each island chain will have its complement chain, which is a chain of a + // twin of any halfedge of that chain. + int getChainFirstIsland(int chain) { + return m_chainData.getField(chain, 4); + } + + // Returns the first island chain in that chain. Island chains are always + // counterclockwise. + int getChainNextInParent(int chain) { + return m_chainData.getField(chain, 5); + } + + // Returns the next chain in arbitrary order. + int getChainNext(int chain) { + return m_chainData.getField(chain, 7); + } + + // Returns the area of the chain. The area does not include any islands. + // +Inf is returned for the universe chain. + double getChainArea(int chain) { + int chainIndex = getChainIndex_(chain); + double v = m_chainAreas.read(chainIndex); + if (NumberUtils.isNaN(v)) { + updateChainAreaAndPerimeter_(chain); + v = m_chainAreas.read(chainIndex); + } + + return v; + } + + // Returns the perimeter of the chain (> 0). +Inf is returned for the + // universe chain. + double getChainPerimeter(int chain) { + int chainIndex = getChainIndex_(chain); + double v = m_chainPerimeters.read(chainIndex); + if (NumberUtils.isNaN(v)) { + updateChainAreaAndPerimeter_(chain); + v = m_chainPerimeters.read(chainIndex); + } + + return v; + } + + // Returns a user index value for the chain. + int getChainUserIndex(int chain, int index) { + int i = getChainIndex_(chain); + AttributeStreamOfInt32 stream = m_chainIndices.get(index); + if (stream.size() <= i) + return -1; + return stream.read(i); + } + + // Sets a user index value for the chain. + void setChainUserIndex(int chain, int index, int value) { + int i = getChainIndex_(chain); + AttributeStreamOfInt32 stream = m_chainIndices.get(index); + if (stream.size() <= i) + stream.resize(m_chainData.size(), -1); + + stream.write(i, value); + } + + // Creates a new user index for the chains. The index values are set to -1. + int createUserIndexForChains() { + if (m_chainIndices == null) { + m_chainIndices = new ArrayList(3); + } + + AttributeStreamOfInt32 new_stream = new AttributeStreamOfInt32( + m_chainData.capacity(), -1); + for (int i = 0, n = m_chainIndices.size(); i < n; i++) { + if (m_chainIndices.get(i) == null) { + m_chainIndices.set(i, new_stream); + return i; + } + } + m_chainIndices.add(new_stream); + return m_chainIndices.size() - 1; + } + + // Deletes user index + void deleteUserIndexForChains(int userIndex) { + assert (m_chainIndices.get(userIndex) != null); + m_chainIndices.set(userIndex, null); + } + + // Returns geometry ID mask from the geometry handle. + // Topo_graph creates a user index for geometries in the shape, which exists + // until the topo graph is destroyed. + int getGeometryID(int geometry) { + return m_shape.getGeometryUserIndex(geometry, m_geometryIDIndex); + } + + // Returns cluster from vertex handle. + // Topo_graph creates a user index for vertices in the shape to hold cluster + // handles. The index exists until the topo graph is destroyed. + int getClusterFromVertex(int vertex) { + return m_shape.getUserIndex(vertex, m_clusterIndex); + } + + int getHalfEdgeFromVertex(int vertex) { + return m_shape.getUserIndex(vertex, m_halfEdgeIndex); + } + + // Finds an edge connecting the two clusters. Returns -1 if not found. + // Could be a slow operation when valency of each cluster is high. + int getHalfEdgeConnector(int clusterFrom, int clusterTo) { + int first_edge = getClusterHalfEdge(clusterFrom); + if (first_edge == -1) + return -1; + int edge = first_edge; + int firstEdgeTo = -1; + int eTo = -1; + // Doing two loops in parallel - one on the half-edges attached to the + // clusterFrom, another - attached to clusterTo. + do { + if (getHalfEdgeTo(edge) == clusterTo) + return edge; + + if (firstEdgeTo == -1) { + firstEdgeTo = getClusterHalfEdge(clusterTo); + if (firstEdgeTo == -1) + return -1; + eTo = firstEdgeTo; + } + + if (getHalfEdgeTo(eTo) == clusterFrom) { + edge = getHalfEdgeTwin(eTo); + assert (getHalfEdgeTo(edge) == clusterTo && getHalfEdgeOrigin(edge) == clusterFrom); + return edge; + } + + edge = getHalfEdgeNext(getHalfEdgeTwin(edge)); + eTo = getHalfEdgeNext(getHalfEdgeTwin(eTo)); + } while (edge != first_edge && eTo != firstEdgeTo); + + return -1; + } + + // Queries segment for the edge (only xy coordinates, no attributes) + void querySegmentXY(int half_edge, SegmentBuffer outBuffer) { + outBuffer.createLine(); + Segment seg = outBuffer.get(); + Point2D pt = new Point2D(); + getHalfEdgeFromXY(half_edge, pt); + seg.setStartXY(pt); + getHalfEdgeToXY(half_edge, pt); + seg.setEndXY(pt); + } + + int compareEdgeAngles_(int edge1, int edge2) { + if (edge1 == edge2) + return 0; + + Point2D pt_1 = new Point2D(); + getHalfEdgeToXY(edge1, pt_1); + + Point2D pt_2 = new Point2D(); + getHalfEdgeToXY(edge2, pt_2); + + if (pt_1.isEqual(pt_2)) + return 0;// overlap case + + Point2D pt10 = new Point2D(); + getHalfEdgeFromXY(edge1, pt10); + // #ifdef DEBUG + // { + // Point_2D pt20; + // get_half_edge_from_xy(edge2, pt20); + // assert(pt10.is_equal(pt20)); + // } + // #endif + + Point2D v_1 = new Point2D(); + v_1.sub(pt_1, pt10); + Point2D v_2 = new Point2D(); + v_2.sub(pt_2, pt10); + int result = Point2D._compareVectors(v_1, v_2); + return result; + } +} diff --git a/src/com/esri/core/geometry/TopologicalOperations.java b/src/com/esri/core/geometry/TopologicalOperations.java new file mode 100644 index 00000000..fd29e918 --- /dev/null +++ b/src/com/esri/core/geometry/TopologicalOperations.java @@ -0,0 +1,2058 @@ +/* + Copyright 1995-2013 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ +package com.esri.core.geometry; + +import com.esri.core.geometry.AttributeStreamOfInt32.IntComparator; +import com.esri.core.geometry.MultiVertexGeometryImpl.GeometryXSimple; +import java.util.ArrayList; + +final class TopologicalOperations { + TopoGraph m_topo_graph = null; + Point2D m_dummy_pt_1 = new Point2D(); + Point2D m_dummy_pt_2 = new Point2D(); + int m_from_edge_for_polylines; + boolean m_mask_lookup[] = null; + + boolean isGoodParentage(int parentage) { + return parentage < m_mask_lookup.length ? m_mask_lookup[parentage] + : false; + } + + void cut(int sideIndex, int cuttee, int cutter, + AttributeStreamOfInt32 cutHandles) { + int gtCuttee = m_topo_graph.getShape().getGeometryType(cuttee); + int gtCutter = m_topo_graph.getShape().getGeometryType(cutter); + int dimCuttee = Geometry.getDimensionFromType(gtCuttee); + int dimCutter = Geometry.getDimensionFromType(gtCutter); + + if (dimCuttee == 2 && dimCutter == 1) { + cutPolygonPolyline_(sideIndex, cuttee, cutter, cutHandles); + return; + } + + throw new GeometryException("internal error"); + } + + static final class CompareCuts extends IntComparator { + private EditShape m_editShape; + + public CompareCuts(EditShape editShape) { + m_editShape = editShape; + } + + @Override + public int compare(int c1, int c2) { + int path1 = m_editShape.getFirstPath(c1); + double area1 = m_editShape.getRingArea(path1); + int path2 = m_editShape.getFirstPath(c2); + double area2 = m_editShape.getRingArea(path2); + if (area1 < area2) + return -1; + if (area1 == area2) + return 0; + return 1; + } + } + + public TopologicalOperations() { + m_from_edge_for_polylines = -1; + } + + void setEditShape(EditShape shape) { + if (m_topo_graph == null) + m_topo_graph = new TopoGraph(); + m_topo_graph.setEditShape(shape, null); + } + + void setEditShapeCrackAndCluster(EditShape shape, double tolerance, + ProgressTracker progressTracker) { + CrackAndCluster.execute(shape, tolerance, progressTracker); + for (int geometry = shape.getFirstGeometry(); geometry != -1; geometry = shape + .getNextGeometry(geometry)) { + if (shape.getGeometryType(geometry) == Geometry.Type.Polygon + .value()) + Simplificator.execute(shape, geometry, -1); + } + setEditShape(shape); + } + + private void collectPolygonPathsPreservingFrom_(int geometryFrom, + int newGeometry, int visitedEdges, int visitedClusters, + int geometry_dominant) { + // This function tries to create polygon paths using the paths that were + // in the input shape. + // This way we preserve original shape as much as possible. + EditShape shape = m_topo_graph.getShape(); + if (shape.getGeometryType(geometryFrom) != Geometry.Type.Polygon + .value()) + return; + + for (int path = shape.getFirstPath(geometryFrom); path != -1; path = shape + .getNextPath(path)) { + int first_vertex = shape.getFirstVertex(path); + int firstCluster = m_topo_graph.getClusterFromVertex(first_vertex); + assert (firstCluster != -1); + int secondVertex = shape.getNextVertex(first_vertex); + int secondCluster = m_topo_graph.getClusterFromVertex(secondVertex); + assert (secondCluster != -1); + + int firstHalfEdge = m_topo_graph + .getHalfEdgeFromVertex(first_vertex); + + if (firstHalfEdge == -1) + continue;// Usually there will be a half-edge that starts at + // first_vertex and goes to secondVertex, but it + // could happen that this half edge has been + // removed. + + assert (m_topo_graph.getHalfEdgeTo(firstHalfEdge) == secondCluster && m_topo_graph + .getHalfEdgeOrigin(firstHalfEdge) == firstCluster); + + int visited = m_topo_graph.getHalfEdgeUserIndex(firstHalfEdge, + visitedEdges); + if (visited == 1 || visited == 2) + continue; + + int parentage = m_topo_graph + .getHalfEdgeFaceParentage(firstHalfEdge); + if (!isGoodParentage(parentage)) { + m_topo_graph.setHalfEdgeUserIndex(firstHalfEdge, visitedEdges, + 2); + continue; + } + + m_topo_graph.setHalfEdgeUserIndex(firstHalfEdge, visitedEdges, 1); + + int newPath = shape.insertPath(newGeometry, -1);// add new path at + // the end + int half_edge = firstHalfEdge; + int vertex = first_vertex; + int cluster = m_topo_graph.getClusterFromVertex(vertex); + int dir = 1; + do { + int vertex_dominant = getVertexByID_(vertex, geometry_dominant); + shape.addVertex(newPath, vertex_dominant); + if (visitedClusters != -1) + m_topo_graph.setClusterUserIndex(cluster, visitedClusters, + 1); + + m_topo_graph.setHalfEdgeUserIndex(half_edge, visitedEdges, 1); + half_edge = m_topo_graph.getHalfEdgeNext(half_edge); + int v; + int cv; + do {// move in a loop through coincident vertices (probably + // vertical segments). + v = dir == 1 ? shape.getNextVertex(vertex) : shape + .getPrevVertex(vertex);// if we came to the polyline + // tail, the next may return + // -1. + cv = v != -1 ? m_topo_graph.getClusterFromVertex(v) : -1; + } while (cv == cluster); + + int originCluster = m_topo_graph.getHalfEdgeOrigin(half_edge); + if (originCluster != cv) { + // try going opposite way + do {// move in a loop through coincident vertices (probably + // vertical segments). + v = dir == 1 ? shape.getPrevVertex(vertex) : shape + .getNextVertex(vertex);// if we came to the + // polyline tail, the + // next may return -1. + cv = v != -1 ? m_topo_graph.getClusterFromVertex(v) + : -1; + } while (cv == cluster); + + if (originCluster != cv) {// pick any vertex. + cv = originCluster; + int iterator = m_topo_graph + .getClusterVertexIterator(cv); + v = m_topo_graph.getVertexFromVertexIterator(iterator); + } else { + dir = -dir;// remember direction we were going for + // performance + } + } + cluster = cv; + vertex = v; + } while (half_edge != firstHalfEdge); + + shape.setClosedPath(newPath, true); + } + } + + // processes Topo_graph and removes edges that border faces with good + // parentage + // If bAllowBrokenFaces is True the function will break face structure for + // dissolved faces. Only face parentage will be uasable. + void dissolveCommonEdges_() { + int visitedEdges = m_topo_graph.createUserIndexForHalfEdges(); + AttributeStreamOfInt32 edgesToDelete = new AttributeStreamOfInt32(0); + // Now extract paths that + for (int cluster = m_topo_graph.getFirstCluster(); cluster != -1; cluster = m_topo_graph + .getNextCluster(cluster)) { + int firstHalfEdge = m_topo_graph.getClusterHalfEdge(cluster); + int half_edge = firstHalfEdge; + if (firstHalfEdge == -1) + continue; + + do { + int visited = m_topo_graph.getHalfEdgeUserIndex(half_edge, + visitedEdges); + if (visited != 1) { + int halfEdgeTwin = m_topo_graph.getHalfEdgeTwin(half_edge); + m_topo_graph.setHalfEdgeUserIndex(halfEdgeTwin, + visitedEdges, 1); + m_topo_graph.setHalfEdgeUserIndex(half_edge, visitedEdges, + 1); + int parentage = m_topo_graph + .getHalfEdgeFaceParentage(half_edge); + if (isGoodParentage(parentage)) { + int twinParentage = m_topo_graph + .getHalfEdgeFaceParentage(halfEdgeTwin); + if (isGoodParentage(twinParentage)) { + // This half_edge pair is a border between two faces + // that share the parentage or it is a dangling edge + edgesToDelete.add(half_edge);// remember for + // subsequent delete + } + } + } + + half_edge = m_topo_graph.getHalfEdgeNext(m_topo_graph + .getHalfEdgeTwin(half_edge)); + } while (half_edge != firstHalfEdge); + } + + m_topo_graph.deleteUserIndexForHalfEdges(visitedEdges); + m_topo_graph.deleteEdgesBreakFaces_(edgesToDelete); + } + + int getVertexByID_(int vertex, int geometry_id) { + if (geometry_id == -1) + return vertex; + + return getVertexByIDImpl_(vertex, geometry_id); + } + + int getVertexByIDImpl_(int vertex, int geometry_id) { + EditShape shape = m_topo_graph.getShape(); + int v; + int geometry; + int vertex_iterator = m_topo_graph + .getClusterVertexIterator(m_topo_graph + .getClusterFromVertex(vertex)); + + do { + v = m_topo_graph.getVertexFromVertexIterator(vertex_iterator); + geometry = shape.getGeometryFromPath(shape.getPathFromVertex(v)); + + if (geometry == geometry_id) + return v; + + vertex_iterator = m_topo_graph + .incrementVertexIterator(vertex_iterator); + } while (vertex_iterator != -1); + + return vertex; + } + + private int topoOperationPolygonPolygon_(int geometry_a, int geometry_b, + int geometry_dominant) { + dissolveCommonEdges_();// faces are partially broken after this call. + // See help to this call. + + EditShape shape = m_topo_graph.getShape(); + int newGeometry = shape.createGeometry(Geometry.Type.Polygon); + int visitedEdges = m_topo_graph.createUserIndexForHalfEdges(); + + topoOperationPolygonPolygonHelper_(geometry_a, geometry_b, newGeometry, + geometry_dominant, visitedEdges, -1); + + m_topo_graph.deleteUserIndexForHalfEdges(visitedEdges); + Simplificator.execute(shape, newGeometry, + MultiVertexGeometryImpl.GeometryXSimple.Weak); + return newGeometry; + } + + private void topoOperationPolygonPolygonHelper_(int geometry_a, + int geometry_b, int newGeometryPolygon, int geometry_dominant, + int visitedEdges, int visitedClusters) { + collectPolygonPathsPreservingFrom_(geometry_a, newGeometryPolygon, + visitedEdges, visitedClusters, geometry_dominant); + if (geometry_b != -1) + collectPolygonPathsPreservingFrom_(geometry_b, newGeometryPolygon, + visitedEdges, visitedClusters, geometry_dominant); + + EditShape shape = m_topo_graph.getShape(); + // Now extract polygon paths that has not been extracted on the previous + // step. + for (int cluster = m_topo_graph.getFirstCluster(); cluster != -1; cluster = m_topo_graph + .getNextCluster(cluster)) { + int firstHalfEdge = m_topo_graph.getClusterHalfEdge(cluster); + if (firstHalfEdge == -1) + continue; + + int half_edge = firstHalfEdge; + do { + int visited = m_topo_graph.getHalfEdgeUserIndex(half_edge, + visitedEdges); + if (visited != 1 && visited != 2) { + int parentage = m_topo_graph + .getHalfEdgeFaceParentage(half_edge); + if (isGoodParentage(parentage)) {// Extract face. + int newPath = shape.insertPath(newGeometryPolygon, -1);// add + // new + // path + // at + // the + // end + int faceHalfEdge = half_edge; + do { + int viter = m_topo_graph + .getHalfEdgeVertexIterator(faceHalfEdge); + int v; + if (viter != -1) { + v = m_topo_graph + .getVertexFromVertexIterator(viter); + } else { + int viter1 = m_topo_graph + .getHalfEdgeVertexIterator(m_topo_graph + .getHalfEdgeTwin(faceHalfEdge)); + assert (viter1 != -1); + v = m_topo_graph + .getVertexFromVertexIterator(viter1); + v = m_topo_graph.getShape().getNextVertex(v); + } + + assert (v != -1); + int vertex_dominant = getVertexByID_(v, + geometry_dominant); + shape.addVertex(newPath, vertex_dominant); + assert (isGoodParentage(m_topo_graph + .getHalfEdgeFaceParentage(faceHalfEdge))); + m_topo_graph.setHalfEdgeUserIndex(faceHalfEdge, + visitedEdges, 1);// + + if (visitedClusters != -1) { + int c = m_topo_graph + .getClusterFromVertex(vertex_dominant); + m_topo_graph.setClusterUserIndex(c, + visitedClusters, 1); + } + + faceHalfEdge = m_topo_graph + .getHalfEdgeNext(faceHalfEdge); + } while (faceHalfEdge != half_edge); + + shape.setClosedPath(newPath, true); + } else { + // cannot extract a face + m_topo_graph.setHalfEdgeUserIndex(half_edge, + visitedEdges, 2); + } + + } + + half_edge = m_topo_graph.getHalfEdgeNext(m_topo_graph + .getHalfEdgeTwin(half_edge)); + } while (half_edge != firstHalfEdge); + } + } + + int[] topoOperationPolygonPolygonEx_(int geometry_a, int geometry_b, + int geometry_dominant) { + EditShape shape = m_topo_graph.getShape(); + int newGeometryPolygon = shape.createGeometry(Geometry.Type.Polygon); + int newGeometryPolyline = shape.createGeometry(Geometry.Type.Polyline); + int newGeometryMultipoint = shape + .createGeometry(Geometry.Type.MultiPoint); + + dissolveCommonEdges_();// faces are partially broken after this call. + // See help to this call. + + int multipointPath = -1; + int visitedEdges = m_topo_graph.createUserIndexForHalfEdges(); + int visitedClusters = m_topo_graph.createUserIndexForClusters(); + + topoOperationPolygonPolygonHelper_(geometry_a, geometry_b, + newGeometryPolygon, geometry_dominant, visitedEdges, + visitedClusters); + + for (int cluster = m_topo_graph.getFirstCluster(); cluster != -1; cluster = m_topo_graph + .getNextCluster(cluster)) { + int firstHalfEdge = m_topo_graph.getClusterHalfEdge(cluster); + if (firstHalfEdge == -1) + continue; + + int half_edge = firstHalfEdge; + do { + int visited1 = m_topo_graph.getHalfEdgeUserIndex(half_edge, + visitedEdges); + int visited2 = m_topo_graph.getHalfEdgeUserIndex( + m_topo_graph.getHalfEdgeTwin(half_edge), visitedEdges); + int visited = visited1 | visited2; + if (visited == 2) { + int parentage = m_topo_graph + .getHalfEdgeParentage(half_edge); + if (isGoodParentage(parentage)) {// Extract face. + int newPath = shape.insertPath(newGeometryPolyline, -1);// add + // new + // path + // at + // the + // end + int polyHalfEdge = half_edge; + int vert = selectVertex_(cluster, shape); + assert (vert != -1); + int vertex_dominant = getVertexByID_(vert, + geometry_dominant); + shape.addVertex(newPath, vertex_dominant); + m_topo_graph.setClusterUserIndex(cluster, + visitedClusters, 1); + + do { + int clusterTo = m_topo_graph + .getHalfEdgeTo(polyHalfEdge); + int vert1 = selectVertex_(clusterTo, shape); + assert (vert1 != -1); + int vertex_dominant1 = getVertexByID_(vert1, + geometry_dominant); + shape.addVertex(newPath, vertex_dominant1); + m_topo_graph.setHalfEdgeUserIndex(polyHalfEdge, + visitedEdges, 1);// + m_topo_graph.setHalfEdgeUserIndex( + m_topo_graph.getHalfEdgeTwin(polyHalfEdge), + visitedEdges, 1);// + m_topo_graph.setClusterUserIndex(clusterTo, + visitedClusters, 1); + + polyHalfEdge = m_topo_graph + .getHalfEdgeNext(polyHalfEdge); + visited1 = m_topo_graph.getHalfEdgeUserIndex( + polyHalfEdge, visitedEdges); + visited2 = m_topo_graph.getHalfEdgeUserIndex( + m_topo_graph.getHalfEdgeTwin(polyHalfEdge), + visitedEdges); + visited = visited1 | visited2; + if (visited != 2) + break; + + parentage = m_topo_graph + .getHalfEdgeParentage(polyHalfEdge); + if (!isGoodParentage(parentage)) { + m_topo_graph.setHalfEdgeUserIndex(polyHalfEdge, + visitedEdges, 1); + m_topo_graph.setHalfEdgeUserIndex(m_topo_graph + .getHalfEdgeTwin(polyHalfEdge), + visitedEdges, 1); + break; + } + + } while (polyHalfEdge != half_edge); + + } else { + m_topo_graph.setHalfEdgeUserIndex(half_edge, + visitedEdges, 1); + m_topo_graph.setHalfEdgeUserIndex( + m_topo_graph.getHalfEdgeTwin(half_edge), + visitedEdges, 1); + } + } + + half_edge = m_topo_graph.getHalfEdgeNext(m_topo_graph + .getHalfEdgeTwin(half_edge)); + } while (half_edge != firstHalfEdge); + } + + for (int cluster = m_topo_graph.getFirstCluster(); cluster != -1; cluster = m_topo_graph + .getNextCluster(cluster)) { + int visited = m_topo_graph.getClusterUserIndex(cluster, + visitedClusters); + if (visited == 1) + continue; + + int parentage = m_topo_graph.getClusterParentage(cluster); + if (isGoodParentage(parentage)) { + if (multipointPath == -1) + multipointPath = shape + .insertPath(newGeometryMultipoint, -1); + int viter = m_topo_graph.getClusterVertexIterator(cluster); + int v; + if (viter != -1) { + v = m_topo_graph.getVertexFromVertexIterator(viter); + int vertex_dominant = getVertexByID_(v, geometry_dominant); + shape.addVertex(multipointPath, vertex_dominant); + } + } + } + + m_topo_graph.deleteUserIndexForClusters(visitedClusters); + m_topo_graph.deleteUserIndexForHalfEdges(visitedEdges); + Simplificator.execute(shape, newGeometryPolygon, + MultiVertexGeometryImpl.GeometryXSimple.Weak); + int[] result = new int[3];// always returns size 3 result. + + result[0] = newGeometryMultipoint; + result[1] = newGeometryPolyline; + result[2] = newGeometryPolygon; + return result; + } + + int selectVertex_(int cluster, EditShape shape) { + int vert = -1; + for (int iterator = m_topo_graph.getClusterVertexIterator(cluster); iterator != -1; iterator = m_topo_graph + .incrementVertexIterator(iterator)) { + int vertex = m_topo_graph.getVertexFromVertexIterator(iterator); + if (vert == -1) + vert = vertex; + int geometry = shape.getGeometryFromPath(shape + .getPathFromVertex(vertex)); + int geomID = m_topo_graph.getGeometryID(geometry); + if (isGoodParentage(geomID)) { + vert = vertex; + break; + } + } + + return vert; + } + + private double prevailingDirection_(EditShape shape, int half_edge) { + int cluster = m_topo_graph.getHalfEdgeOrigin(half_edge); + int clusterTo = m_topo_graph.getHalfEdgeTo(half_edge); + int signTotal = 0; + int signCorrect = 0; + for (int iterator = m_topo_graph.getClusterVertexIterator(cluster); iterator != -1; iterator = m_topo_graph + .incrementVertexIterator(iterator)) { + int vertex = m_topo_graph.getVertexFromVertexIterator(iterator); + int path = shape.getPathFromVertex(vertex); + int geometry = shape.getGeometryFromPath(path); + int geomID = m_topo_graph.getGeometryID(geometry); + int nextVert = shape.getNextVertex(vertex); + int prevVert = shape.getPrevVertex(vertex); + + int firstVert = shape.getFirstVertex(path); + if (firstVert == vertex) {// remember the first half edge of the + // path. We use it to produce correct + // startpath for closed polyline loops + m_from_edge_for_polylines = half_edge; + } + + if (nextVert != -1 + && m_topo_graph.getClusterFromVertex(nextVert) == clusterTo) { + signTotal++; + if (isGoodParentage(geomID)) { + if (firstVert == nextVert) {// remember the first vertex of + // the path. We use it to + // produce correct startpath for + // closed polyline loops + m_from_edge_for_polylines = m_topo_graph + .getHalfEdgeNext(half_edge); + } + + // update the sign + signCorrect++; + } + } else if (prevVert != -1 + && m_topo_graph.getClusterFromVertex(prevVert) == clusterTo) { + signTotal--; + if (isGoodParentage(geomID)) { + if (firstVert == prevVert) {// remember the first vertex of + // the path. We use it to + // produce correct startpath for + // closed polyline loops + m_from_edge_for_polylines = m_topo_graph + .getHalfEdgeNext(half_edge); + } + + // update the sign + signCorrect--; + } + } + } + + m_topo_graph.getXY(cluster, m_dummy_pt_1); + m_topo_graph.getXY(clusterTo, m_dummy_pt_2); + double len = Point2D.distance(m_dummy_pt_1, m_dummy_pt_2); + return (signCorrect != 0 ? signCorrect : signTotal) * len; + } + + int getCombinedHalfEdgeParentage_(int e) { + return m_topo_graph.getHalfEdgeParentage(e) + | m_topo_graph.getHalfEdgeFaceParentage(e) + | m_topo_graph.getHalfEdgeFaceParentage(m_topo_graph + .getHalfEdgeTwin(e)); + } + + int tryMoveThroughCrossroadBackwards_(int half_edge) { + int e = m_topo_graph.getHalfEdgeTwin(m_topo_graph + .getHalfEdgePrev(half_edge)); + int goodEdge = -1; + while (e != half_edge) { + int parentage = getCombinedHalfEdgeParentage_(e); + if (isGoodParentage(parentage)) { + if (goodEdge != -1) + return -1; + goodEdge = e; + } + + e = m_topo_graph.getHalfEdgeTwin(m_topo_graph.getHalfEdgePrev(e)); + } + + return goodEdge != -1 ? m_topo_graph.getHalfEdgeTwin(goodEdge) : -1; + } + + int tryMoveThroughCrossroadForward_(int half_edge) { + int e = m_topo_graph.getHalfEdgeTwin(m_topo_graph + .getHalfEdgeNext(half_edge)); + int goodEdge = -1; + while (e != half_edge) { + int parentage = getCombinedHalfEdgeParentage_(e); + if (isGoodParentage(parentage)) { + if (goodEdge != -1) + return -1;// more than one way to move through the + // intersection + goodEdge = e; + } + + e = m_topo_graph.getHalfEdgeTwin(m_topo_graph.getHalfEdgeNext(e)); + } + + return goodEdge != -1 ? m_topo_graph.getHalfEdgeTwin(goodEdge) : -1; + } + + private void restorePolylineParts_(int first_edge, int newGeometry, + int visitedEdges, int visitedClusters, int geometry_dominant) { + assert (isGoodParentage(getCombinedHalfEdgeParentage_(first_edge))); + EditShape shape = m_topo_graph.getShape(); + int half_edge = first_edge; + int halfEdgeTwin = m_topo_graph.getHalfEdgeTwin(half_edge); + m_topo_graph.setHalfEdgeUserIndex(half_edge, visitedEdges, 1); + m_topo_graph.setHalfEdgeUserIndex(halfEdgeTwin, visitedEdges, 1); + double prevailingLength = prevailingDirection_(shape, half_edge);// prevailing + // direction + // is + // used + // to + // figure + // out + // the + // polyline + // direction. + // Prevailing length is the sum of the length of vectors that constitute + // the polyline. + // Vector length is positive, if the halfedge direction coincides with + // the direction of the original geometry + // and negative otherwise. + + m_from_edge_for_polylines = -1; + int fromEdge = half_edge; + int toEdge = -1; + while (true) { + int halfEdgePrev = m_topo_graph.getHalfEdgePrev(half_edge); + if (halfEdgePrev == halfEdgeTwin) + break;// the end of a polyline + int halfEdgeTwinNext = m_topo_graph.getHalfEdgeNext(halfEdgeTwin); + if (m_topo_graph.getHalfEdgeTwin(halfEdgePrev) != halfEdgeTwinNext) { + // Crossroads is here. We can move through the crossroad only if + // there is only a single way to pass through. + half_edge = tryMoveThroughCrossroadBackwards_(half_edge); + if (half_edge == -1) + break; + else + halfEdgeTwin = m_topo_graph.getHalfEdgeTwin(half_edge); + } else { + half_edge = halfEdgePrev; + halfEdgeTwin = halfEdgeTwinNext; + } + + if (half_edge == first_edge) {// we are in a loop. No need to search + // for the toEdge. Just remember the + // toEdge and skip the next while + // loop. + toEdge = first_edge; + break; + } + int parentage = getCombinedHalfEdgeParentage_(half_edge); + if (!isGoodParentage(parentage)) + break; + + m_topo_graph.setHalfEdgeUserIndex(half_edge, visitedEdges, 1); + m_topo_graph.setHalfEdgeUserIndex(halfEdgeTwin, visitedEdges, 1); + fromEdge = half_edge; + prevailingLength += prevailingDirection_(shape, half_edge); + } + + if (toEdge == -1) { + half_edge = first_edge; + halfEdgeTwin = m_topo_graph.getHalfEdgeTwin(half_edge); + toEdge = half_edge; + while (true) { + int halfEdgeNext = m_topo_graph.getHalfEdgeNext(half_edge); + if (halfEdgeNext == halfEdgeTwin) + break; + int halfEdgeTwinPrev = m_topo_graph + .getHalfEdgePrev(halfEdgeTwin); + if (m_topo_graph.getHalfEdgeTwin(halfEdgeNext) != halfEdgeTwinPrev) { + // Crossroads is here. We can move through the crossroad + // only if there is only a single way to pass through. + half_edge = tryMoveThroughCrossroadForward_(half_edge); + if (half_edge == -1) + break; + else + halfEdgeTwin = m_topo_graph.getHalfEdgeTwin(half_edge); + } else { + half_edge = halfEdgeNext; + halfEdgeTwin = halfEdgeTwinPrev; + } + + int parentage = getCombinedHalfEdgeParentage_(half_edge); + if (!isGoodParentage(parentage)) + break; + + m_topo_graph.setHalfEdgeUserIndex(half_edge, visitedEdges, 1); + m_topo_graph + .setHalfEdgeUserIndex(halfEdgeTwin, visitedEdges, 1); + toEdge = half_edge; + prevailingLength += prevailingDirection_(shape, half_edge); + } + } else { + // toEdge has been found in the first while loop. This happens when + // we go around a face. + // Closed loops need special processing as we do not know where the + // polyline started or ended. + // TODO: correctly process closed polylines (is_closed_path == + // true). + + if (m_from_edge_for_polylines != -1) { + fromEdge = m_from_edge_for_polylines; + toEdge = m_topo_graph + .getHalfEdgePrev(m_from_edge_for_polylines);// try + // simply + // getting + // prev + int fromEdgeTwin = m_topo_graph.getHalfEdgeTwin(fromEdge); + int fromEdgeTwinNext = m_topo_graph + .getHalfEdgeNext(fromEdgeTwin); + if (m_topo_graph.getHalfEdgeTwin(toEdge) != fromEdgeTwinNext) { + // Crossroads is here. Pass through the crossroad. + toEdge = tryMoveThroughCrossroadBackwards_(fromEdge); + if (toEdge == -1) + throw new GeometryException("internal error");// what? + } + + assert (isGoodParentage(getCombinedHalfEdgeParentage_(m_from_edge_for_polylines))); + assert (isGoodParentage(getCombinedHalfEdgeParentage_(toEdge))); + } + } + + boolean dir = prevailingLength >= 0; + if (!dir) { + int e = toEdge; + toEdge = fromEdge; + fromEdge = e; + toEdge = m_topo_graph.getHalfEdgeTwin(toEdge);// switch to twin so + // that we can use + // next instead of + // Prev + assert (isGoodParentage(getCombinedHalfEdgeParentage_(toEdge))); + fromEdge = m_topo_graph.getHalfEdgeTwin(fromEdge); + assert (isGoodParentage(getCombinedHalfEdgeParentage_(fromEdge))); + } + int newPath = shape.insertPath(newGeometry, -1);// add new path at the + // end + half_edge = fromEdge; + int cluster = m_topo_graph.getHalfEdgeOrigin(fromEdge); + int vert = selectVertex_(cluster, shape); + assert (vert != -1); + int vertex_dominant = getVertexByID_(vert, geometry_dominant); + shape.addVertex(newPath, vertex_dominant); + + if (visitedClusters != -1) { + m_topo_graph.setClusterUserIndex(cluster, visitedClusters, 1); + } + + while (true) { + int clusterTo = m_topo_graph.getHalfEdgeTo(half_edge); + int vert_1 = selectVertex_(clusterTo, shape); + vertex_dominant = getVertexByID_(vert_1, geometry_dominant); + shape.addVertex(newPath, vertex_dominant); + if (visitedClusters != -1) { + m_topo_graph.setClusterUserIndex(clusterTo, visitedClusters, 1); + } + + assert (isGoodParentage(getCombinedHalfEdgeParentage_(half_edge))); + if (half_edge == toEdge) + break; + int halfEdgeNext = m_topo_graph.getHalfEdgeNext(half_edge); + if (m_topo_graph.getHalfEdgePrev(m_topo_graph + .getHalfEdgeTwin(half_edge)) != m_topo_graph + .getHalfEdgeTwin(halfEdgeNext)) {// crossroads. + half_edge = tryMoveThroughCrossroadForward_(half_edge); + if (half_edge == -1) + throw new GeometryException("internal error");// a bug. This + // shoulf + // never + // happen + } else + half_edge = halfEdgeNext; + } + } + + private int topoOperationPolylinePolylineOrPolygon_(int geometry_dominant) { + EditShape shape = m_topo_graph.getShape(); + int newGeometry = shape.createGeometry(Geometry.Type.Polyline); + int visitedEdges = m_topo_graph.createUserIndexForHalfEdges(); + + for (int cluster = m_topo_graph.getFirstCluster(); cluster != -1; cluster = m_topo_graph + .getNextCluster(cluster)) { + int firstClusterHalfEdge = m_topo_graph.getClusterHalfEdge(cluster); + int clusterHalfEdge = firstClusterHalfEdge; + do { + int visited = m_topo_graph.getHalfEdgeUserIndex( + clusterHalfEdge, visitedEdges); + if (visited != 1) { + int parentage = getCombinedHalfEdgeParentage_(clusterHalfEdge); + if (isGoodParentage(parentage)) { + restorePolylineParts_(clusterHalfEdge, newGeometry, + visitedEdges, -1, geometry_dominant); + } else { + // + } + } + + clusterHalfEdge = m_topo_graph.getHalfEdgeNext(m_topo_graph + .getHalfEdgeTwin(clusterHalfEdge)); + } while (clusterHalfEdge != firstClusterHalfEdge); + } + + m_topo_graph.deleteUserIndexForHalfEdges(visitedEdges); + return newGeometry; + } + + int[] topoOperationPolylinePolylineOrPolygonEx_(int geometry_dominant) { + EditShape shape = m_topo_graph.getShape(); + int newPolyline = shape.createGeometry(Geometry.Type.Polyline); + int newMultipoint = shape.createGeometry(Geometry.Type.MultiPoint); + int visitedEdges = m_topo_graph.createUserIndexForHalfEdges(); + int visitedClusters = m_topo_graph.createUserIndexForClusters(); + int multipointPath = -1; + for (int cluster = m_topo_graph.getFirstCluster(); cluster != -1; cluster = m_topo_graph + .getNextCluster(cluster)) { + int firstClusterHalfEdge = m_topo_graph.getClusterHalfEdge(cluster); + int clusterHalfEdge = firstClusterHalfEdge; + do { + int visited = m_topo_graph.getHalfEdgeUserIndex( + clusterHalfEdge, visitedEdges); + if (visited != 1) { + int parentage = getCombinedHalfEdgeParentage_(clusterHalfEdge); + if (isGoodParentage(parentage)) { + restorePolylineParts_(clusterHalfEdge, newPolyline, + visitedEdges, visitedClusters, + geometry_dominant); + } else { + // + } + } + + clusterHalfEdge = m_topo_graph.getHalfEdgeNext(m_topo_graph + .getHalfEdgeTwin(clusterHalfEdge)); + } while (clusterHalfEdge != firstClusterHalfEdge); + } + + for (int cluster = m_topo_graph.getFirstCluster(); cluster != -1; cluster = m_topo_graph + .getNextCluster(cluster)) { + int visited = m_topo_graph.getClusterUserIndex(cluster, + visitedClusters); + if (visited != 1) { + int parentage = m_topo_graph.getClusterParentage(cluster); + if (isGoodParentage(parentage)) { + if (multipointPath == -1) + multipointPath = shape.insertPath(newMultipoint, -1); + + int viter = m_topo_graph.getClusterVertexIterator(cluster); + int v; + if (viter != -1) { + v = m_topo_graph.getVertexFromVertexIterator(viter); + int vertex_dominant = getVertexByID_(v, + geometry_dominant); + shape.addVertex(multipointPath, vertex_dominant); + } + } else { + // + } + } + } + + m_topo_graph.deleteUserIndexForHalfEdges(visitedEdges); + m_topo_graph.deleteUserIndexForClusters(visitedClusters); + int[] result = new int[2]; + result[0] = newMultipoint; + result[1] = newPolyline; + return result; + } + + private int topoOperationMultiPoint_() { + EditShape shape = m_topo_graph.getShape(); + int newGeometry = shape.createGeometry(Geometry.Type.MultiPoint); + int newPath = shape.insertPath(newGeometry, -1);// add new path at the + // end + + // Now extract paths that + for (int cluster = m_topo_graph.getFirstCluster(); cluster != -1; cluster = m_topo_graph + .getNextCluster(cluster)) { + int parentage = m_topo_graph.getClusterParentage(cluster); + if (isGoodParentage(parentage)) { + int vert = -1; + for (int iterator = m_topo_graph + .getClusterVertexIterator(cluster); iterator != -1; iterator = m_topo_graph + .incrementVertexIterator(iterator)) { + int vertex = m_topo_graph + .getVertexFromVertexIterator(iterator); + if (vert == -1) + vert = vertex; + int geometry = shape.getGeometryFromPath(shape + .getPathFromVertex(vertex)); + int geomID = m_topo_graph.getGeometryID(geometry); + if (isGoodParentage(geomID)) { + vert = vertex; + break; + } + } + assert (vert != -1); + shape.addVertex(newPath, vert); + } + } + + return newGeometry; + } + + void initMaskLookupArray_(int len) { + m_mask_lookup = new boolean[len]; + for (int i = 0; i < len; i++) { + m_mask_lookup[i] = false; + } + } + + static MultiPoint processMultiPointIntersectOrDiff_(MultiPoint multi_point, + Geometry intersector, double tolerance, boolean bClipIn) { + MultiPoint multi_point_out = ((MultiPoint) multi_point.createInstance()); + Point2D[] input_points = new Point2D[1000]; + PolygonUtils.PiPResult[] test_results = new PolygonUtils.PiPResult[1000]; + int npoints = multi_point.getPointCount(); + boolean bFirstOut = true; + boolean bArea = (intersector.getDimension() == 2); + if (intersector.getDimension() != 1 && intersector.getDimension() != 2) + throw new GeometryException("internal error"); + + for (int ipoints = 0; ipoints < npoints;) { + int num = multi_point.queryCoordinates(input_points, 1000, ipoints, + -1) - ipoints; + + if (bArea) + PolygonUtils.testPointsInArea2D(intersector, input_points, + (int) num, tolerance, test_results); + else + PolygonUtils.testPointsOnLine2D(intersector, input_points, + (int) num, tolerance, test_results); + int i0 = 0; + for (int i = 0; i < num; i++) { + boolean bTest = test_results[i] == PolygonUtils.PiPResult.PiPOutside; + if (!bClipIn) + bTest = !bTest; + + if (bTest) { + if (bFirstOut) { + bFirstOut = false; + multi_point_out.add(multi_point, 0, ipoints); + } + + if (i0 != i) + multi_point_out.add(multi_point, ipoints + i0, ipoints + + i); + + i0 = i + 1; + } + } + + if (!bFirstOut && i0 != num) + multi_point_out.add(multi_point, ipoints + i0, ipoints + num); + + ipoints += num; + } + + if (bFirstOut) + return multi_point; + + return multi_point_out; + } + + static MultiPoint intersection(MultiPoint multi_point, Geometry multi_path, + double tolerance) { + return processMultiPointIntersectOrDiff_(multi_point, multi_path, + tolerance, true); + } + + static MultiPoint difference(MultiPoint multi_point, Geometry multi_path, + double tolerance) { + return processMultiPointIntersectOrDiff_(multi_point, multi_path, + tolerance, false); + } + + static Point processPointIntersectOrDiff_(Point point, + Geometry intersector, double tolerance, boolean bClipIn) { + if (point.isEmpty()) + return ((Point) point.createInstance()); + if (intersector.isEmpty()) { + return bClipIn ? ((Point) point.createInstance()) : null; + } + + Point2D[] input_points = new Point2D[1]; + PolygonUtils.PiPResult[] test_results = new PolygonUtils.PiPResult[1]; + boolean bArea = intersector.getDimension() == 2; + if (intersector.getDimension() != 1 && intersector.getDimension() != 2) + throw new GeometryException("internal error"); + input_points[0] = point.getXY(); + if (bArea) + PolygonUtils.testPointsInArea2D(intersector, input_points, 1, + tolerance, test_results); + else + PolygonUtils.testPointsOnLine2D(intersector, input_points, 1, + tolerance, test_results); + + boolean bTest = test_results[0] == PolygonUtils.PiPResult.PiPOutside; + if (!bClipIn) + bTest = !bTest; + + if (!bTest) + return point; + else + return ((Point) point.createInstance()); + } + + static Point intersection(Point point, Geometry geom, double tolerance) { + return processPointIntersectOrDiff_(point, geom, tolerance, true); + } + + static Point difference(Point point, Geometry geom, double tolerance) { + return processPointIntersectOrDiff_(point, geom, tolerance, false); + } + + static Point intersection(Point point, Point point2, double tolerance) { + if (point.isEmpty() || point2.isEmpty()) + return (Point) point.createInstance(); + if (Point2D.distance(point.getXY(), point2.getXY()) < tolerance) { + return point; + } + + return (Point) point.createInstance(); + } + + static Point difference(Point point, Point point2, double tolerance) { + if (point.isEmpty()) + return (Point) point.createInstance(); + if (point2.isEmpty()) + return point; + if (Point2D.distance(point.getXY(), point2.getXY()) < tolerance) { + return (Point) point.createInstance(); + } + + return point; + } + + MultiVertexGeometry planarSimplifyImpl_(MultiVertexGeometry input_geom, + double tolerance, boolean b_use_winding_rule_for_polygons, + boolean dirty_result, ProgressTracker progress_tracker) { + if (input_geom.isEmpty()) + return input_geom; + + EditShape shape = new EditShape(); + int geom = shape.addGeometry(input_geom); + return planarSimplify(shape, geom, tolerance, + b_use_winding_rule_for_polygons, dirty_result, progress_tracker); + } + + MultiVertexGeometry planarSimplify(EditShape shape, int geom, + double tolerance, boolean b_use_winding_rule_for_polygons, + boolean dirty_result, ProgressTracker progress_tracker) { + // This method will produce a polygon from a polyline when + // b_use_winding_rule_for_polygons is true. This is used by buffer. + m_topo_graph = new TopoGraph(); + if (dirty_result + && shape.getGeometryType(geom) != Geometry.Type.MultiPoint + .value()) { + PlaneSweepCrackerHelper plane_sweeper = new PlaneSweepCrackerHelper(); + plane_sweeper.sweepVertical(shape, tolerance); + if (plane_sweeper.hadCompications())// shame. The one pass + // planesweep had some + // complications. Need to do + // full crack and cluster. + { + CrackAndCluster.execute(shape, tolerance, progress_tracker); + } + } else { + CrackAndCluster.execute(shape, tolerance, progress_tracker); + } + if (!b_use_winding_rule_for_polygons + || shape.getGeometryType(geom) == Geometry.Type.MultiPoint + .value()) + m_topo_graph.setAndSimplifyEditShapeAlternate(shape, geom); + else + m_topo_graph.setAndSimplifyEditShapeWinding(shape, geom); + + int ID_a = m_topo_graph.getGeometryID(geom); + initMaskLookupArray_((ID_a) + 1); + m_mask_lookup[ID_a] = true; // Works only when there is a single + // geometry in the edit shape. + // To make it work when many geometries are present, this need to be + // modified. + + if (shape.getGeometryType(geom) == Geometry.Type.Polygon.value() + || (b_use_winding_rule_for_polygons && shape + .getGeometryType(geom) != Geometry.Type.MultiPoint + .value())) { + // geom can be a polygon or a polyline. + // It can be a polyline only when the winding rule is true. + int resGeom = topoOperationPolygonPolygon_(geom, -1, -1); + + Polygon polygon = (Polygon) shape.getGeometry(resGeom); + if (!dirty_result) { + ((MultiVertexGeometryImpl) polygon._getImpl()).setIsSimple( + GeometryXSimple.Strong, tolerance, false); + ((MultiPathImpl) polygon._getImpl())._updateOGCFlags(); + } else + ((MultiVertexGeometryImpl) polygon._getImpl()).setIsSimple( + GeometryXSimple.Weak, 0.0, false);// dirty result means + // simple but with 0 + // tolerance. + + return polygon; + } else if (shape.getGeometryType(geom) == Geometry.Type.Polyline + .value()) { + int resGeom = topoOperationPolylinePolylineOrPolygon_(-1); + + Polyline polyline = (Polyline) shape.getGeometry(resGeom); + if (!dirty_result) + ((MultiVertexGeometryImpl) polyline._getImpl()).setIsSimple( + GeometryXSimple.Strong, tolerance, false); + + return polyline; + } else if (shape.getGeometryType(geom) == Geometry.Type.MultiPoint + .value()) { + int resGeom = topoOperationMultiPoint_(); + + MultiPoint mp = (MultiPoint) shape.getGeometry(resGeom); + if (!dirty_result) + ((MultiVertexGeometryImpl) mp._getImpl()).setIsSimple( + GeometryXSimple.Strong, tolerance, false); + + return mp; + } else { + throw new GeometryException("internal error"); + } + } + + // static + static MultiVertexGeometry planarSimplify(MultiVertexGeometry input_geom, + double tolerance, boolean use_winding_rule_for_polygons, + boolean dirty_result, ProgressTracker progress_tracker) { + TopologicalOperations topoOps = new TopologicalOperations(); + return topoOps.planarSimplifyImpl_(input_geom, tolerance, + use_winding_rule_for_polygons, dirty_result, progress_tracker); + } + + public int difference(int geometry_a, int geometry_b) { + int gtA = m_topo_graph.getShape().getGeometryType(geometry_a); + int gtB = m_topo_graph.getShape().getGeometryType(geometry_b); + int dim_a = Geometry.getDimensionFromType(gtA); + int dim_b = Geometry.getDimensionFromType(gtB); + if (dim_a > dim_b) { + return geometry_a; + } + + int ID_a = m_topo_graph.getGeometryID(geometry_a); + int ID_b = m_topo_graph.getGeometryID(geometry_b); + initMaskLookupArray_((ID_a | ID_b) + 1); + m_mask_lookup[m_topo_graph.getGeometryID(geometry_a)] = true; + + if (dim_a == 2 && dim_b == 2) + return topoOperationPolygonPolygon_(geometry_a, geometry_b, -1); + if (dim_a == 1 && dim_b == 2) + return topoOperationPolylinePolylineOrPolygon_(-1); + if (dim_a == 1 && dim_b == 1) + return topoOperationPolylinePolylineOrPolygon_(-1); + if (dim_a == 0) + return topoOperationMultiPoint_(); + + throw new GeometryException("internal error"); + } + + int dissolve(int geometry_a, int geometry_b) { + int gtA = m_topo_graph.getShape().getGeometryType(geometry_a); + int gtB = m_topo_graph.getShape().getGeometryType(geometry_b); + int dim_a = Geometry.getDimensionFromType(gtA); + int dim_b = Geometry.getDimensionFromType(gtB); + if (dim_a > dim_b) { + return geometry_a; + } + + if (dim_a < dim_b) { + return geometry_b; + } + + int ID_a = m_topo_graph.getGeometryID(geometry_a); + int ID_b = m_topo_graph.getGeometryID(geometry_b); + initMaskLookupArray_(((ID_a | ID_b) + 1)); + + m_mask_lookup[m_topo_graph.getGeometryID(geometry_a)] = true; + m_mask_lookup[m_topo_graph.getGeometryID(geometry_b)] = true; + m_mask_lookup[m_topo_graph.getGeometryID(geometry_a) + | m_topo_graph.getGeometryID(geometry_b)] = true; + + if (dim_a == 2 && dim_b == 2) + return topoOperationPolygonPolygon_(geometry_a, geometry_b, -1); + if (dim_a == 1 && dim_b == 1) + return topoOperationPolylinePolylineOrPolygon_(-1); + if (dim_a == 0 && dim_b == 0) + return topoOperationMultiPoint_(); + + throw new GeometryException("internal error"); + } + + public int intersection(int geometry_a, int geometry_b) { + int gtA = m_topo_graph.getShape().getGeometryType(geometry_a); + int gtB = m_topo_graph.getShape().getGeometryType(geometry_b); + int dim_a = Geometry.getDimensionFromType(gtA); + int dim_b = Geometry.getDimensionFromType(gtB); + + int ID_a = m_topo_graph.getGeometryID(geometry_a); + int ID_b = m_topo_graph.getGeometryID(geometry_b); + initMaskLookupArray_(((ID_a | ID_b) + 1)); + + m_mask_lookup[m_topo_graph.getGeometryID(geometry_a) + | m_topo_graph.getGeometryID(geometry_b)] = true; + + int geometry_dominant = -1; + boolean b_vertex_dominance = (m_topo_graph.getShape() + .getVertexDescription().getAttributeCount() > 1); + if (b_vertex_dominance) + geometry_dominant = geometry_a; + + if (dim_a == 2 && dim_b == 2)// intersect two polygons + return topoOperationPolygonPolygon_(geometry_a, geometry_b, + geometry_dominant); + if ((dim_a == 1 && dim_b > 0) || (dim_b == 1 && dim_a > 0))// intersect + // polyline + // with + // polyline + // or + // polygon + return topoOperationPolylinePolylineOrPolygon_(geometry_dominant); + if (dim_a == 0 || dim_b == 0)// intersect a multipoint with something + // else + return topoOperationMultiPoint_(); + + throw new GeometryException("internal error"); + } + + int[] intersectionEx(int geometry_a, int geometry_b) { + int gtA = m_topo_graph.getShape().getGeometryType(geometry_a); + int gtB = m_topo_graph.getShape().getGeometryType(geometry_b); + int dim_a = Geometry.getDimensionFromType(gtA); + int dim_b = Geometry.getDimensionFromType(gtB); + + int ID_a = m_topo_graph.getGeometryID(geometry_a); + int ID_b = m_topo_graph.getGeometryID(geometry_b); + initMaskLookupArray_(((ID_a | ID_b) + 1)); + + m_mask_lookup[m_topo_graph.getGeometryID(geometry_a) + | m_topo_graph.getGeometryID(geometry_b)] = true; + + int geometry_dominant = -1; + boolean b_vertex_dominance = (m_topo_graph.getShape() + .getVertexDescription().getAttributeCount() > 1); + if (b_vertex_dominance) + geometry_dominant = geometry_a; + + if (dim_a == 2 && dim_b == 2)// intersect two polygons + return topoOperationPolygonPolygonEx_(geometry_a, geometry_b, + geometry_dominant); + if ((dim_a == 1 && dim_b > 0) || (dim_b == 1 && dim_a > 0))// intersect + // polyline + // with + // polyline + // or + // polygon + return topoOperationPolylinePolylineOrPolygonEx_(geometry_dominant); + if (dim_a == 0 || dim_b == 0)// intersect a multipoint with something + // else + { + int[] res = new int[1]; + res[0] = topoOperationMultiPoint_(); + return res; + } + + throw new GeometryException("internal error"); + } + + public int symmetricDifference(int geometry_a, int geometry_b) { + int gtA = m_topo_graph.getShape().getGeometryType(geometry_a); + int gtB = m_topo_graph.getShape().getGeometryType(geometry_b); + int dim_a = Geometry.getDimensionFromType(gtA); + int dim_b = Geometry.getDimensionFromType(gtB); + + int ID_a = m_topo_graph.getGeometryID(geometry_a); + int ID_b = m_topo_graph.getGeometryID(geometry_b); + initMaskLookupArray_((ID_a | ID_b) + 1); + + m_mask_lookup[m_topo_graph.getGeometryID(geometry_a)] = true; + m_mask_lookup[m_topo_graph.getGeometryID(geometry_b)] = true; + + if (dim_a == 2 && dim_b == 2) + return topoOperationPolygonPolygon_(geometry_a, geometry_b, -1); + if (dim_a == 1 && dim_b == 1) + return topoOperationPolylinePolylineOrPolygon_(-1); + if (dim_a == 0 && dim_b == 0) + return topoOperationMultiPoint_(); + + throw new GeometryException("internal error"); + } + + int extractShape(int geometry_in) { + int gtA = m_topo_graph.getShape().getGeometryType(geometry_in); + int dim_a = Geometry.getDimensionFromType(gtA); + + int ID_a = m_topo_graph.getGeometryID(geometry_in); + initMaskLookupArray_((ID_a) + 1); + m_mask_lookup[m_topo_graph.getGeometryID(geometry_in)] = true; // Works + // only + // when + // there + // is a + // single + // geometry + // in + // the + // edit + // shape. + // To make it work when many geometries are present, this need to be + // modified. + + if (dim_a == 2) + return topoOperationPolygonPolygon_(geometry_in, -1, -1); + if (dim_a == 1) + return topoOperationPolylinePolylineOrPolygon_(-1); + if (dim_a == 0) + return topoOperationMultiPoint_(); + + throw new GeometryException("internal error"); + } + + static Geometry normalizeInputGeometry_(Geometry geom) { + Geometry.Type gt = geom.getType(); + if (gt == Geometry.Type.Envelope) { + Polygon poly = new Polygon(geom.getDescription()); + if (!geom.isEmpty()) + poly.addEnvelope((Envelope) geom, false); + return poly; + } + if (gt == Geometry.Type.Point) { + MultiPoint poly = new MultiPoint(geom.getDescription()); + if (!geom.isEmpty()) + poly.add((Point) geom); + return poly; + } + if (gt == Geometry.Type.Line) { + Polyline poly = new Polyline(geom.getDescription()); + if (!geom.isEmpty()) + poly.addSegment((Segment) geom, true); + return poly; + } + + return geom; + } + + static Geometry normalizeResult_(Geometry geomRes, Geometry geom_a, + Geometry dummy, char op) { + // assert(strchr("-&^|",op) != NULL); + Geometry.Type gtRes = geomRes.getType(); + if (gtRes == Geometry.Type.Envelope) { + Polygon poly = new Polygon(geomRes.getDescription()); + if (!geomRes.isEmpty()) + poly.addEnvelope((Envelope) geomRes, false); + return poly; + } + + if (gtRes == Geometry.Type.Point && (op == '|' || op == '^')) { + MultiPoint poly = new MultiPoint(geomRes.getDescription()); + if (!geomRes.isEmpty()) + poly.add((Point) geomRes); + return poly; + } + + if (gtRes == Geometry.Type.Line) { + Polyline poly = new Polyline(geomRes.getDescription()); + if (!geomRes.isEmpty()) + poly.addSegment((Segment) geomRes, true); + return poly; + } + + if (gtRes == Geometry.Type.Point && op == '-') { + if (geom_a.getType() == Geometry.Type.Point) { + Point pt = new Point(geomRes.getDescription()); + if (!geomRes.isEmpty()) { + assert (((MultiPoint) geomRes).getPointCount() == 1); + ((MultiPoint) geomRes).getPointByVal(0, pt); + } + return pt; + } + } + + if (gtRes == Geometry.Type.MultiPoint && op == '&') { + if (geom_a.getType() == Geometry.Type.Point) { + Point pt = new Point(geomRes.getDescription()); + if (!geomRes.isEmpty()) { + assert (((MultiPoint) geomRes).getPointCount() == 1); + ((MultiPoint) geomRes).getPointByVal(0, pt); + } + return pt; + } + } + + return geomRes; + } + + // static + public static Geometry difference(Geometry geometry_a, Geometry geometry_b, + SpatialReference sr, ProgressTracker progress_tracker) { + if (geometry_a.isEmpty() || geometry_b.isEmpty() + || geometry_a.getDimension() > geometry_b.getDimension()) + return normalizeResult_(normalizeInputGeometry_(geometry_a), + geometry_a, geometry_b, '-'); + + Envelope2D env2D_1 = new Envelope2D(); + geometry_a.queryEnvelope2D(env2D_1); + Envelope2D env2D_2 = new Envelope2D(); + geometry_b.queryEnvelope2D(env2D_2); + + if (!env2D_1.isIntersecting(env2D_2)) { + return normalizeResult_(normalizeInputGeometry_(geometry_a), + geometry_a, geometry_b, '-'); + } + + Envelope2D envMerged = new Envelope2D(); + envMerged.setCoords(env2D_1); + envMerged.merge(env2D_2); + double tolerance = InternalUtils.calculateToleranceFromGeometry(sr, + envMerged, true);// conservative to have same effect as simplify + + TopologicalOperations topoOps = new TopologicalOperations(); + EditShape edit_shape = new EditShape(); + int geom_a = edit_shape + .addGeometry(normalizeInputGeometry_(geometry_a)); + int geom_b = edit_shape + .addGeometry(normalizeInputGeometry_(geometry_b)); + topoOps.setEditShapeCrackAndCluster(edit_shape, tolerance, + progress_tracker); + int result = topoOps.difference(geom_a, geom_b); + Geometry resGeom = edit_shape.getGeometry(result); + + Geometry res_geom = normalizeResult_(resGeom, geometry_a, geometry_b, + '-'); + + if (Geometry.isMultiPath(res_geom.getType().value())) { + ((MultiVertexGeometryImpl) res_geom._getImpl()).setIsSimple( + GeometryXSimple.Strong, tolerance, false); + if (res_geom.getType() == Geometry.Type.Polygon) + ((MultiPathImpl) res_geom._getImpl())._updateOGCFlags(); + } + + return res_geom; + } + + public static Geometry dissolve(Geometry geometry_a, Geometry geometry_b, + SpatialReference sr, ProgressTracker progress_tracker) { + if (geometry_a.getDimension() > geometry_b.getDimension()) + return normalizeResult_(normalizeInputGeometry_(geometry_a), + geometry_a, geometry_b, '|'); + + if (geometry_a.getDimension() < geometry_b.getDimension()) + return normalizeResult_(normalizeInputGeometry_(geometry_b), + geometry_a, geometry_b, '|'); + + if (geometry_a.isEmpty()) + return normalizeResult_(normalizeInputGeometry_(geometry_b), + geometry_a, geometry_b, '|'); + + if (geometry_b.isEmpty()) + return normalizeResult_(normalizeInputGeometry_(geometry_a), + geometry_a, geometry_b, '|'); + + Envelope2D env2D_1 = new Envelope2D(); + geometry_a.queryEnvelope2D(env2D_1); + Envelope2D env2D_2 = new Envelope2D(); + geometry_b.queryEnvelope2D(env2D_2); + + Envelope2D envMerged = new Envelope2D(); + envMerged.setCoords(env2D_1); + envMerged.merge(env2D_2); + double tolerance = InternalUtils.calculateToleranceFromGeometry(sr, + envMerged, true);// conservative to have same effect as simplify + + if (!env2D_1.isIntersecting(env2D_2.getInflated(tolerance, tolerance))) { + // TODO: add optimization here to merge two geometries if the + // envelopes do not overlap. + Geometry geom1 = normalizeInputGeometry_(geometry_a); + assert (Geometry.isMultiVertex(geom1.getType().value())); + Geometry geom2 = normalizeInputGeometry_(geometry_b); + assert (Geometry.isMultiVertex(geom2.getType().value())); + assert (geom1.getType() == geom2.getType()); + switch (geom1.getType().value()) { + case Geometry.GeometryType.MultiPoint: { + Geometry res = Geometry._clone(geom1); + ((MultiPoint) res).add((MultiPoint) geom2, 0, -1); + return res; + } + // break; + case Geometry.GeometryType.Polyline: { + Geometry res = Geometry._clone(geom1); + ((Polyline) res).add((MultiPath) geom2, false); + return res; + } + // break; + case Geometry.GeometryType.Polygon: { + Geometry res = Geometry._clone(geom1); + ((Polygon) res).add((MultiPath) geom2, false); + return res; + } + // break; + default: + throw new GeometryException("internal error"); + } + } + + TopologicalOperations topoOps = new TopologicalOperations(); + EditShape edit_shape = new EditShape(); + int geom_a = edit_shape + .addGeometry(normalizeInputGeometry_(geometry_a)); + int geom_b = edit_shape + .addGeometry(normalizeInputGeometry_(geometry_b)); + topoOps.setEditShapeCrackAndCluster(edit_shape, tolerance, + progress_tracker); + int result = topoOps.dissolve(geom_a, geom_b); + + Geometry res_geom = normalizeResult_(edit_shape.getGeometry(result), + geometry_a, geometry_b, '|'); + + if (Geometry.isMultiPath(res_geom.getType().value())) { + ((MultiVertexGeometryImpl) res_geom._getImpl()).setIsSimple( + GeometryXSimple.Strong, tolerance, false); + if (res_geom.getType() == Geometry.Type.Polygon) + ((MultiPathImpl) res_geom._getImpl())._updateOGCFlags(); + } + + return res_geom; + } + + static Geometry dissolveDirty(ArrayList geometries, + SpatialReference sr, ProgressTracker progress_tracker) { + if (geometries.size() < 2) + throw new IllegalArgumentException( + "not enough geometries to dissolve"); + + int dim = 0; + for (int i = 0, n = geometries.size(); i < n; i++) { + dim = Math.max(geometries.get(i).getDimension(), dim); + } + + Envelope2D envMerged = new Envelope2D(); + envMerged.setEmpty(); + + EditShape shape = new EditShape(); + int geom = -1; + int count = 0; + int any_index = -1; + for (int i = 0, n = geometries.size(); i < n; i++) { + if (geometries.get(i).getDimension() == dim) { + if (!geometries.get(i).isEmpty()) { + any_index = i; + if (geom == -1) + geom = shape + .addGeometry(normalizeInputGeometry_(geometries + .get(i))); + else + shape.appendGeometry(geom, + normalizeInputGeometry_(geometries.get(i))); + + Envelope2D env = new Envelope2D(); + geometries.get(i).queryLooseEnvelope2D(env); + envMerged.merge(env); + count++; + } else if (any_index == -1) + any_index = i; + } + } + + if (count < 2) { + return normalizeInputGeometry_(geometries.get(any_index)); + } + + boolean winding = dim == 2; + + SpatialReference psr = dim == 0 ? sr : null;// if points, then use + // correct tolerance. + double tolerance = InternalUtils.calculateToleranceFromGeometry(psr, + envMerged, true); + TopologicalOperations topoOps = new TopologicalOperations(); + return topoOps.planarSimplify(shape, geom, tolerance, winding, true, + progress_tracker); + } + + // static + public static Geometry intersection(Geometry geometry_a, + Geometry geometry_b, SpatialReference sr, + ProgressTracker progress_tracker) { + Envelope2D env2D_1 = new Envelope2D(); + geometry_a.queryEnvelope2D(env2D_1); + Envelope2D env2D_2 = new Envelope2D(); + geometry_b.queryEnvelope2D(env2D_2); + if (!env2D_1.isIntersecting(env2D_2))// also includes the empty geometry + // cases + { + if (geometry_a.getDimension() <= geometry_b.getDimension()) + return normalizeResult_( + normalizeInputGeometry_(geometry_a.createInstance()), + geometry_a, geometry_b, '&'); + + if (geometry_a.getDimension() > geometry_b.getDimension()) + return normalizeResult_( + normalizeInputGeometry_(geometry_b.createInstance()), + geometry_a, geometry_b, '&'); + } + Envelope2D envMerged = new Envelope2D(); + envMerged.setCoords(env2D_1); + envMerged.merge(env2D_2); + double tolerance = InternalUtils.calculateToleranceFromGeometry(sr, + envMerged, true);// conservative to have same effect as simplify + + TopologicalOperations topoOps = new TopologicalOperations(); + EditShape edit_shape = new EditShape(); + int geom_a = edit_shape + .addGeometry(normalizeInputGeometry_(geometry_a)); + int geom_b = edit_shape + .addGeometry(normalizeInputGeometry_(geometry_b)); + + topoOps.setEditShapeCrackAndCluster(edit_shape, tolerance, + progress_tracker); + int result = topoOps.intersection(geom_a, geom_b); + Geometry res_geom = normalizeResult_(edit_shape.getGeometry(result), + geometry_a, geometry_b, '&'); + + if (Geometry.isMultiPath(res_geom.getType().value())) { + ((MultiVertexGeometryImpl) res_geom._getImpl()).setIsSimple( + GeometryXSimple.Strong, tolerance, false); + if (res_geom.getType() == Geometry.Type.Polygon) + ((MultiPathImpl) res_geom._getImpl())._updateOGCFlags(); + } + + return res_geom; + } + + static Geometry[] intersectionEx(Geometry geometry_a, Geometry geometry_b, + SpatialReference sr, ProgressTracker progress_tracker) { + Geometry[] res_vec = new Geometry[3]; + Envelope2D env2D_1 = new Envelope2D(); + geometry_a.queryEnvelope2D(env2D_1); + Envelope2D env2D_2 = new Envelope2D(); + geometry_b.queryEnvelope2D(env2D_2); + if (!env2D_1.isIntersecting(env2D_2))// also includes the empty geometry + // cases + { + if (geometry_a.getDimension() <= geometry_b.getDimension()) { + Geometry geom = normalizeResult_( + normalizeInputGeometry_(geometry_a.createInstance()), + geometry_a, geometry_b, '&'); + res_vec[geom.getDimension()] = geom; + return res_vec; + } + + if (geometry_a.getDimension() > geometry_b.getDimension()) { + Geometry geom = normalizeResult_( + normalizeInputGeometry_(geometry_b.createInstance()), + geometry_a, geometry_b, '&'); + res_vec[geom.getDimension()] = geom; + return res_vec; + } + + } + Envelope2D envMerged = new Envelope2D(); + envMerged.setCoords(env2D_1); + envMerged.merge(env2D_2); + double tolerance = InternalUtils.calculateToleranceFromGeometry(sr, + envMerged, true);// conservative to have same effect as simplify + + TopologicalOperations topoOps = new TopologicalOperations(); + EditShape edit_shape = new EditShape(); + int geom_a = edit_shape + .addGeometry(normalizeInputGeometry_(geometry_a)); + int geom_b = edit_shape + .addGeometry(normalizeInputGeometry_(geometry_b)); + + topoOps.setEditShapeCrackAndCluster(edit_shape, tolerance, + progress_tracker); + int[] result_geom_handles = topoOps.intersectionEx(geom_a, geom_b); + for (int i = 0; i < result_geom_handles.length; i++) { + Geometry res_geom = normalizeResult_( + edit_shape.getGeometry(result_geom_handles[i]), geometry_a, + geometry_b, '&'); + + if (Geometry.isMultiPath(res_geom.getType().value())) { + ((MultiVertexGeometryImpl) res_geom._getImpl()).setIsSimple( + MultiVertexGeometryImpl.GeometryXSimple.Strong, + tolerance, false); + if (res_geom.getType().value() == Geometry.GeometryType.Polygon) + ((MultiPathImpl) res_geom._getImpl())._updateOGCFlags(); + } + + res_vec[res_geom.getDimension()] = res_geom; + } + + return res_vec; + } + + // static + public static Geometry symmetricDifference(Geometry geometry_a, + Geometry geometry_b, SpatialReference sr, + ProgressTracker progress_tracker) { + if (geometry_a.getDimension() > geometry_b.getDimension()) + return normalizeResult_(normalizeInputGeometry_(geometry_a), + geometry_a, geometry_b, '^'); + + if (geometry_a.getDimension() < geometry_b.getDimension()) + return normalizeResult_(normalizeInputGeometry_(geometry_b), + geometry_a, geometry_b, '^'); + + if (geometry_a.isEmpty()) + return normalizeResult_(normalizeInputGeometry_(geometry_b), + geometry_a, geometry_b, '^'); + + if (geometry_b.isEmpty()) + return normalizeResult_(normalizeInputGeometry_(geometry_a), + geometry_a, geometry_b, '^'); + + Envelope2D env2D_1 = new Envelope2D(); + geometry_a.queryEnvelope2D(env2D_1); + Envelope2D env2D_2 = new Envelope2D(); + geometry_b.queryEnvelope2D(env2D_2); + // TODO: add optimization here to merge two geometries if the envelopes + // do not overlap. + + Envelope2D envMerged = new Envelope2D(); + envMerged.setCoords(env2D_1); + envMerged.merge(env2D_2); + double tolerance = InternalUtils.calculateToleranceFromGeometry(sr, + envMerged, true);// conservative to have same effect as simplify + + TopologicalOperations topoOps = new TopologicalOperations(); + EditShape edit_shape = new EditShape(); + int geom_a = edit_shape + .addGeometry(normalizeInputGeometry_(geometry_a)); + int geom_b = edit_shape + .addGeometry(normalizeInputGeometry_(geometry_b)); + topoOps.setEditShapeCrackAndCluster(edit_shape, tolerance, + progress_tracker); + int result = topoOps.symmetricDifference(geom_a, geom_b); + Geometry res_geom = normalizeResult_(edit_shape.getGeometry(result), + geometry_a, geometry_b, '^'); + + if (Geometry.isMultiPath(res_geom.getType().value())) { + ((MultiVertexGeometryImpl) res_geom._getImpl()).setIsSimple( + GeometryXSimple.Strong, tolerance, false); + if (res_geom.getType() == Geometry.Type.Polygon) + ((MultiPathImpl) res_geom._getImpl())._updateOGCFlags(); + } + + return res_geom; + } + + static Geometry _denormalizeGeometry(Geometry geom, Geometry geomA, + Geometry geomB) { + Geometry.Type gtA = geomA.getType(); + Geometry.Type gtB = geomB.getType(); + Geometry.Type gt = geom.getType(); + if (gt == Geometry.Type.MultiPoint) { + if (gtA == Geometry.Type.Point || gtB == Geometry.Type.Point) { + MultiPoint mp = (MultiPoint) geom; + if (mp.getPointCount() <= 1) { + Point pt = new Point(geom.getDescription()); + if (!mp.isEmpty()) + mp.getPointByVal(0, pt); + return (Geometry) pt; + } + } + } + return geom; + } + + private void flushVertices_(int geometry, AttributeStreamOfInt32 vertices) { + EditShape shape = m_topo_graph.getShape(); + int path = shape.insertPath(geometry, -1); + int size = vertices.size(); + // _ASSERT(size != 0); + for (int i = 0; i < size; i++) { + int vertex = vertices.get(i); + shape.addVertex(path, vertex); + } + shape.setClosedPath(path, true);// need to close polygon rings + } + + private void removeSpikes_(int cuttee, int cutter) { + int idCuttee = m_topo_graph.getGeometryID(cuttee); + int idCutter = m_topo_graph.getGeometryID(cutter); + int visitedIndex = m_topo_graph.createUserIndexForHalfEdges(); + for (int cluster = m_topo_graph.getFirstCluster(); cluster != -1; cluster = m_topo_graph + .getNextCluster(cluster)) { + int firstHalfEdge = m_topo_graph.getClusterHalfEdge(cluster); + if (firstHalfEdge == -1) + continue; + + int half_edge = firstHalfEdge; + + do { + int visited = m_topo_graph.getHalfEdgeUserIndex(half_edge, + visitedIndex); + if (visited != 1) { + int halfEdgeParentage = m_topo_graph + .getHalfEdgeParentage(half_edge); + int halfEdgeFaceParentage = m_topo_graph + .getHalfEdgeFaceParentage(half_edge); + if (halfEdgeParentage != (idCuttee | idCutter) + && halfEdgeFaceParentage != 0) { + int faceHalfEdge = half_edge; + do { + int faceHalfEdgeNext = m_topo_graph + .getHalfEdgeNext(faceHalfEdge); + if (m_topo_graph.getHalfEdgePrev(faceHalfEdge) == m_topo_graph + .getHalfEdgeTwin(faceHalfEdge)) + m_topo_graph.deleteEdgeInternal_(faceHalfEdge); + else + m_topo_graph.setHalfEdgeUserIndex(faceHalfEdge, + visitedIndex, 1); + + faceHalfEdge = faceHalfEdgeNext; + } while (faceHalfEdge != half_edge); + } else { + m_topo_graph.setHalfEdgeUserIndex(half_edge, + visitedIndex, 1); + } + } + + half_edge = m_topo_graph.getHalfEdgeNext(m_topo_graph + .getHalfEdgeTwin(half_edge)); + } while (half_edge != firstHalfEdge); + } + } + + private void setHalfEdgeOrientations_(int orientationIndex, int cutter) { + EditShape shape = m_topo_graph.getShape(); + + for (int igeometry = shape.getFirstGeometry(); igeometry != -1; igeometry = shape + .getNextGeometry(igeometry)) { + if (igeometry != cutter) + continue; + + for (int ipath = shape.getFirstPath(igeometry); ipath != -1; ipath = shape + .getNextPath(ipath)) { + int ivertex = shape.getFirstVertex(ipath); + if (ivertex == -1) + continue; + + int ivertexNext = shape.getNextVertex(ivertex); + assert (ivertexNext != -1); + + while (ivertexNext != -1) { + int clusterFrom = m_topo_graph + .getClusterFromVertex(ivertex); + int clusterTo = m_topo_graph + .getClusterFromVertex(ivertexNext); + int half_edge = m_topo_graph.getHalfEdgeConnector( + clusterFrom, clusterTo); + + if (half_edge != -1) { + int halfEdgeTwin = m_topo_graph + .getHalfEdgeTwin(half_edge); + m_topo_graph.setHalfEdgeUserIndex(half_edge, + orientationIndex, 1); + m_topo_graph.setHalfEdgeUserIndex(halfEdgeTwin, + orientationIndex, 2); + } + + ivertex = ivertexNext; + ivertexNext = shape.getNextVertex(ivertex); + } + } + } + } + + private void processPolygonCuts_(int orientationIndex, int sideIndex, + int cuttee, int cutter) { + int idCuttee = m_topo_graph.getGeometryID(cuttee); + int idCutter = m_topo_graph.getGeometryID(cutter); + AttributeStreamOfInt32 vertices = new AttributeStreamOfInt32(0); + vertices.reserve(256); + EditShape shape = m_topo_graph.getShape(); + + int visitedIndex = m_topo_graph.createUserIndexForHalfEdges(); + for (int cluster = m_topo_graph.getFirstCluster(); cluster != -1; cluster = m_topo_graph + .getNextCluster(cluster)) { + int firstHalfEdge = m_topo_graph.getClusterHalfEdge(cluster); + + if (firstHalfEdge == -1) + continue; + + int half_edge = firstHalfEdge; + + do { + int visited = m_topo_graph.getHalfEdgeUserIndex(half_edge, + visitedIndex); + if (visited != 1) { + int faceHalfEdge = half_edge; + int toHalfEdge = half_edge; + boolean bFoundCutter = false; + int side = 0; + do { + m_topo_graph.setHalfEdgeUserIndex(faceHalfEdge, + visitedIndex, 1); + if (!bFoundCutter) { + int edgeParentage = m_topo_graph + .getHalfEdgeParentage(faceHalfEdge); + if ((edgeParentage & idCutter) != 0) { + int faceParentage = m_topo_graph + .getHalfEdgeFaceParentage(faceHalfEdge); + if ((faceParentage & idCuttee) != 0) { + toHalfEdge = faceHalfEdge;// reset the loop + bFoundCutter = true; + } + } + } + + if (bFoundCutter) { + int clusterOrigin = m_topo_graph + .getHalfEdgeOrigin(faceHalfEdge); + int iterator = m_topo_graph + .getClusterVertexIterator(clusterOrigin); + assert (iterator != -1); + int vertex = m_topo_graph + .getVertexFromVertexIterator(iterator); + vertices.add(vertex); + + // get side + if (orientationIndex != -1) { + int edgeParentage = m_topo_graph + .getHalfEdgeParentage(faceHalfEdge); + if ((edgeParentage & idCutter) != 0) { + int orientation = m_topo_graph + .getHalfEdgeUserIndex(faceHalfEdge, + orientationIndex); + assert (orientation == 1 || orientation == 2); + side |= orientation; + } + } + } + + int next = m_topo_graph.getHalfEdgeNext(faceHalfEdge); + faceHalfEdge = next; + } while (faceHalfEdge != toHalfEdge); + + if (bFoundCutter + && m_topo_graph.getChainArea(m_topo_graph + .getHalfEdgeChain(toHalfEdge)) > 0.0) {// if + // we + // found + // a + // cutter + // face + // and + // its + // area + // is + // positive, + // then + // add + // the + // cutter + // face + // as + // new + // polygon. + int geometry = shape + .createGeometry(Geometry.Type.Polygon); + flushVertices_(geometry, vertices);// adds the cutter + // face vertices to + // the new polygon + + if (sideIndex != -1) + shape.setGeometryUserIndex(geometry, sideIndex, + side); // what is that? + } + + vertices.clear(false); + } + half_edge = m_topo_graph.getHalfEdgeNext(m_topo_graph + .getHalfEdgeTwin(half_edge)); + } while (half_edge != firstHalfEdge); + } + + m_topo_graph.deleteUserIndexForHalfEdges(visitedIndex); + } + + private void cutPolygonPolyline_(int sideIndex, int cuttee, int cutter, + AttributeStreamOfInt32 cutHandles) { + removeSpikes_(cuttee, cutter); + + int orientationIndex = -1; + if (sideIndex != -1) { + orientationIndex = m_topo_graph.createUserIndexForHalfEdges(); + setHalfEdgeOrientations_(orientationIndex, cutter); + } + + processPolygonCuts_(orientationIndex, sideIndex, cuttee, cutter); + + EditShape shape = m_topo_graph.getShape(); + + int cutCount = 0; + for (int geometry_handle = shape.getFirstGeometry(); geometry_handle != -1; geometry_handle = shape + .getNextGeometry(geometry_handle)) { + if (geometry_handle != cuttee && geometry_handle != cutter) { + cutHandles.add(geometry_handle); + cutCount++; + } + } + + // sort + CompareCuts compareCuts = new CompareCuts(shape); + cutHandles.Sort(0, cutCount, compareCuts); + } + +} diff --git a/src/com/esri/core/geometry/Transformation2D.java b/src/com/esri/core/geometry/Transformation2D.java new file mode 100644 index 00000000..1d90b17c --- /dev/null +++ b/src/com/esri/core/geometry/Transformation2D.java @@ -0,0 +1,922 @@ +/* + Copyright 1995-2013 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ + +package com.esri.core.geometry; + +/** + * The affine transformation class for 2D.
+ * Vector is a row: + *
|m11 m12 0| + *
| x y 1| * |m21 m22 0| = |m11 * x + m21 * y + m31 m12 * x + m22 * y + m32 1| + *
|m31 m32 1| + *
Then elements of the Transformation2D are as follows: + *
|xx yx 0| + *
| x y 1| * |xy yy 0| = |xx * x + xy * y + xd yx * x + yy * y + yd 1| + *
|xd yd 1| + *
+ *
Matrices are used for transformations of the vectors as rows (case + * 2). That means the math expressions on the Geometry matrix operations should + * be writen like this:
+ * v' = v * M1 * M2 * M3 = ( (v * M1) * M2 ) * M3, where v is a vector, Mn are + * the matrices.
+ * This is equivalent to the following line of code:
+ * ResultVector = (M1.mul(M2).mul(M3)).transform(Vector) + */ +public final class Transformation2D { + + /** + * Matrix coefficient XX of the transformation. + */ + public double xx; + /** + * Matrix coefficient XY of the transformation. + */ + public double xy; + /** + * X translation component of the transformation. + */ + public double xd; + /** + * Matrix coefficient YX of the transformation. + */ + + public double yx; + /** + * Matrix coefficient YY of the transformation. + */ + public double yy; + /** + * Y translation component of the transformation. + */ + + public double yd; + + /** + * Creates a 2D affine transformation with identity transformation. + */ + public Transformation2D() { + setIdentity(); + } + + /** + * Creates a 2D affine transformation with a specified scale. + * + * @param scale + * The scale to use for the transformation. + */ + public Transformation2D(double scale) { + setScale(scale); + } + + /** + * Initializes a zero transformation. Transforms any coordinate to (0, 0). + */ + public void setZero() { + xx = 0; + yy = 0; + xy = 0; + yx = 0; + xd = 0; + yd = 0; + } + + void transform(Point2D psrc, Point2D pdst) { + double x = xx * psrc.x + xy * psrc.y + xd; + double y = yx * psrc.x + yy * psrc.y + yd; + pdst.x = x; + pdst.y = y; + } + + /** + * Returns True when all members of this transformation are equal to the + * corresponding members of the other. + */ + + @Override + public boolean equals(Object other) { + if (this == other) + return true; + if (!(other instanceof Transformation2D)) + return false; + Transformation2D that = (Transformation2D) other; + + return (xx == that.xx && xy == that.xy && xd == that.xd + && yx == that.yx && yy == that.yy && yd == that.yd); + } + + /** + * Returns the hash code for the 2D transformation. + */ + + @Override + public int hashCode() { + int hash = NumberUtils.hash(xx); + hash = NumberUtils.hash(hash, xy); + hash = NumberUtils.hash(hash, xd); + hash = NumberUtils.hash(hash, yx); + hash = NumberUtils.hash(hash, yy); + hash = NumberUtils.hash(hash, yd); + return hash; + } + + void transform(Point2D[] points, int start, int count) { + int n = Math.min(points.length, start + count); + for (int i = count; i < n; i++) { + transform(points[i], points[i]); + } + } + + /** + * Transforms an array of points. + * + * @param pointsIn + * The points to be transformed. + * @param count + * The number of points to transform. + * @param pointsOut + * The transformed points are returned using this array. It + * should have the same or greater size as the input array. + */ + public void transform(Point[] pointsIn, int count, Point[] pointsOut) { + Point2D res = new Point2D(); + for (int i = 0; i < count; i++) { + Point2D p = pointsIn[i].getXY(); + res.x = xx * p.x + xy * p.y + xd; + res.y = yx * p.x + yy * p.y + yd; + pointsOut[i] = new Point(res.x, res.y); + } + } + + /** + * Transforms an array of points stored in an array of doubles as + * interleaved XY coordinates. + * + * @param pointsXYInterleaved + * The array of points with interleaved X, Y values to be + * transformed. + * @param start + * The start point index to transform from (the actual element + * index is 2 * start). + * @param count + * The number of points to transform (the actual element count is + * 2 * count). + */ + public void transform(double[] pointsXYInterleaved, int start, int count) { + int n = Math.min(pointsXYInterleaved.length, (start + count) * 2) / 2; + for (int i = count; i < n; i++) { + double px = pointsXYInterleaved[2 * i]; + double py = pointsXYInterleaved[2 * i + 1]; + pointsXYInterleaved[2 * i] = xx * px + xy * py + xd; + pointsXYInterleaved[2 * i + 1] = yx * px + yy * py + yd; + } + } + + /** + * Multiplies this matrix on the right with the "right" matrix. Stores the + * result into this matrix and returns a reference to it.
+ * Equivalent to this *= right. + * + * @param right + * The matrix to be multiplied with. + */ + public void multiply(Transformation2D right) { + multiply(this, right, this); + } + + /** + * Multiplies this matrix on the left with the "left" matrix. Stores the + * result into this matrix and returns a reference to it.
+ * Equivalent to this = left * this. + * + * @param left + * The matrix to be multiplied with. + */ + public void mulLeft(Transformation2D left) { + multiply(left, this, this); + } + + /** + * Performs multiplication of matrices a and b and places the result into + * this matrix. The a, b, and result could point to same objects.
+ * Equivalent to result = a * b. + * + * @param a + * The 2D transformation to be multiplied. + * @param b + * The 2D transformation to be multiplied. + * @param result + * The 2D transformation created by multiplication of matrices. + */ + public static void multiply(Transformation2D a, Transformation2D b, + Transformation2D result) { + double xx, xy, xd, yx, yy, yd; + + xx = a.xx * b.xx + a.yx * b.xy; + xy = a.xy * b.xx + a.yy * b.xy; + xd = a.xd * b.xx + a.yd * b.xy + b.xd; + yx = a.xx * b.yx + a.yx * b.yy; + yy = a.xy * b.yx + a.yy * b.yy; + yd = a.xd * b.yx + a.yd * b.yy + b.yd; + + result.xx = xx; + result.xy = xy; + result.xd = xd; + result.yx = yx; + result.yy = yy; + result.yd = yd; + } + + /** + * Returns a copy of the Transformation2D object. + * + * @return A copy of this object. + */ + public Transformation2D copy() { + Transformation2D result = new Transformation2D(); + result.xx = xx; + result.xy = xy; + result.xd = xd; + result.yx = yx; + result.yy = yy; + result.yd = yd; + return result; + } + + /** + * Writes the matrix coefficients in the order XX, XY, XD, YX, YY, YD into + * the given array. + * + * @param coefs + * The array into which the coefficients are returned. Should be + * of size 6 elements. + */ + public void getCoefficients(double[] coefs) { + if (coefs.length < 6) + throw new GeometryException( + "Buffer is too small. coefs needs 6 members"); + + coefs[0] = xx; + coefs[1] = xy; + coefs[2] = xd; + coefs[3] = yx; + coefs[4] = yy; + coefs[5] = yd; + } + + /** + * Transforms envelope + * + * @param env + * The envelope that is to be transformed + */ + void transform(Envelope2D env) { + + if (env.isEmpty()) + return; + + Point2D[] buf = new Point2D[4]; + env.queryCorners(buf); + transform(buf, buf); + env.setFromPoints(buf, 4); + } + + void transform(Point2D[] pointsIn, Point2D[] pointsOut) { + for (int i = 0; i < pointsIn.length; i++) { + Point2D res = new Point2D(); + Point2D p = pointsIn[i]; + res.x = xx * p.x + xy * p.y + xd; + res.y = yx * p.x + yy * p.y + yd; + pointsOut[i] = res; + } + } + + /** + * Initialize transformation from two rectangles. + */ + void initializeFromRect(Envelope2D src, Envelope2D dest) { + if (src.isEmpty() || dest.isEmpty() || 0 == src.getWidth() + || 0 == src.getHeight()) + setZero(); + else { + xy = yx = 0; + xx = dest.getWidth() / src.getWidth(); + yy = dest.getHeight() / src.getHeight(); + xd = dest.xmin - src.xmin * xx; + yd = dest.ymin - src.ymin * yy; + } + } + + /** + * Initializes an orhtonormal transformation from the Src and Dest + * rectangles. + * + * The result transformation proportionally fits the Src into the Dest. The + * center of the Src will be in the center of the Dest. + */ + void initializeFromRectIsotropic(Envelope2D src, Envelope2D dest) { + + if (src.isEmpty() || dest.isEmpty() || 0 == src.getWidth() + || 0 == src.getHeight()) + setZero(); + else { + yx = 0; + xy = 0; + xx = dest.getWidth() / src.getWidth(); + yy = dest.getHeight() / src.getHeight(); + if (xx > yy) + xx = yy; + else + yy = xx; + + Point2D destCenter = dest.getCenter(); + Point2D srcCenter = src.getCenter(); + xd = destCenter.x - srcCenter.x * xx; + yd = destCenter.y - srcCenter.y * yy; + } + } + + /** + * Initializes transformation from Position, Tangent vector and offset + * value. Tangent vector must have unity length + */ + void initializeFromCurveParameters(Point2D Position, Point2D Tangent, + double Offset) { + // TODO + } + + /** + * Transforms size. + * + * Creates an AABB with width of SizeSrc.x and height of SizeSrc.y. + * Transforms that AABB and gets a quadrangle in new coordinate system. The + * result x contains the length of the quadrangle edge, which were parallel + * to X in the original system, and y contains the length of the edge, that + * were parallel to the Y axis in the original system. + */ + Point2D transformSize(Point2D SizeSrc) { + Point2D pt = new Point2D(); + pt.x = Math.sqrt(xx * xx + yx * yx) * SizeSrc.x; + pt.y = Math.sqrt(xy * xy + yy * yy) * SizeSrc.y; + return pt; + } + + /** + * Transforms a tolerance value. + * + * @param tolerance + * The tolerance value. + */ + public double transform(double tolerance) { + // the function should be implemented as follows: find encompassing + // circle for the transformed circle of radius = Tolerance. + + // this is approximation. + Point2D pt1 = new Point2D(); + Point2D pt2 = new Point2D(); + /* + * pt[0].Set(0, 0); pt[1].Set(1, 0); pt[2].Set(0, 1); Transform(pt); + * pt[1] -= pt[0]; pt[2] -= pt[0]; + */ + + pt1.setCoords(xx, yx); + pt2.setCoords(xy, yy); + pt1.sub(pt1); + double d1 = pt1.sqrLength() * 0.5; + pt1.setCoords(xx, yx); + pt2.setCoords(xy, yy); + pt1.add(pt2); + double d2 = pt1.sqrLength() * 0.5; + return tolerance * ((d1 > d2) ? Math.sqrt(d1) : Math.sqrt(d2)); + } + + // Performs linear part of the transformation only. Same as if xd, yd would + // be zeroed. + void transformWithoutShift(Point2D[] pointsIn, int from, int count, + Point2D[] pointsOut) { + for (int i = from, n = from + count; i < n; i++) { + Point2D p = pointsIn[i]; + double new_x = xx * p.x + xy * p.y; + double new_y = yx * p.x + yy * p.y; + pointsOut[i].setCoords(new_x, new_y); + } + } + + Point2D transformWithoutShift(Point2D srcPoint) { + double new_x = xx * srcPoint.x + xy * srcPoint.y; + double new_y = yx * srcPoint.x + yy * srcPoint.y; + return Point2D.construct(new_x, new_y); + } + + /** + * Sets this matrix to be the identity matrix. + */ + public void setIdentity() { + xx = 1.0; + xy = 0; + xd = 0; + yx = 0; + yy = 1.0; + yd = 0; + } + + /** + * Returns TRUE if this matrix is the identity matrix. + */ + public boolean isIdentity() { + return xx == 1.0 && yy == 1.0 + && (0 == xy && 0 == xd && 0 == yx && 0 == yd); + } + + /** + * Returns TRUE if this matrix is an identity matrix within the given + * tolerance. + * + * @param tol + * The tolerance value. + */ + public boolean isIdentity(double tol) { + Point2D pt = Point2D.construct(0., 1.); + transform(pt, pt); + pt.sub(Point2D.construct(0., 1.)); + if (pt.sqrLength() > tol * tol) + return false; + + pt.setCoords(0, 0); + transform(pt, pt); + if (pt.sqrLength() > tol * tol) + return false; + + pt.setCoords(1., 0.); + transform(pt, pt); + pt.sub(Point2D.construct(1., 0)); + return pt.sqrLength() <= tol * tol; + } + + /** + * Returns TRUE for reflective transformations. It inverts the sign of + * vector cross product. + */ + public boolean isReflective() { + return xx * yy - yx * xy < 0; + } + + /** + * Returns TRUE if this transformation is a uniform transformation. + * + * The uniform transformation is a transformation, which transforms a square + * to a square. + */ + public boolean isUniform(double eps) { + double v1 = xx * xx + yx * yx; + double v2 = xy * xy + yy * yy; + double e = (v1 + v2) * eps; + return Math.abs(v1 - v2) <= e && Math.abs(xx * xy + yx * yy) <= e; + } + + /** + * Returns TRUE if this transformation is a shift transformation. The shift + * transformation performs shift only. + */ + public boolean isShift() { + return xx == 1.0 && yy == 1.0 && 0 == xy && 0 == yx; + } + + /** + * Returns TRUE if this transformation is a shift transformation within the + * given tolerance. + * + * @param tol + * The tolerance value. + */ + public boolean isShift(double tol) { + Point2D pt = transformWithoutShift(Point2D.construct(0., 1.)); + pt.y -= 1.0; + if (pt.sqrLength() > tol * tol) + return false; + + pt = transformWithoutShift(Point2D.construct(1., 0.)); + pt.x -= 1.0; + return pt.sqrLength() <= tol * tol; + } + + /** + * Returns TRUE if this is an orthonormal transformation with the given + * tolerance. The orthonormal: Rotation or rotoinversion and shift + * (preserves lengths of vectors and angles between vectors). + * + * @param tol + * The tolerance value. + */ + public boolean isOrthonormal(double tol) { + Transformation2D r = new Transformation2D(); + r.xx = xx * xx + xy * xy; + r.xy = xx * yx + xy * yy; + r.yx = yx * xx + yy * xy; + r.yy = yx * yx + yy * yy; + r.xd = 0; + r.yd = 0; + + return r.isIdentity(tol); + } + + /** + * Returns TRUE if this matrix is degenerated (does not have an inverse) + * within the given tolerance. + * + * @param tol + * The tolerance value. + */ + public boolean isDegenerate(double tol) { + return Math.abs(xx * yy - yx * xy) <= 2 * tol + * (Math.abs(xx * yy) + Math.abs(yx * xy)); + } + + /** + * Returns TRUE, if this transformation does not have rotation and shear + * within the given tolerance. + * + * @param tol + * The tolerance value. + */ + public boolean isScaleAndShift(double tol) { + return xy * xy + yx * yx < (xx * xx + yy * yy) * tol; + } + + /** + * Set this transformation to be a shift. + * + * @param x + * The X coordinate to shift to. + * @param y + * The Y coordinate to shift to. + */ + public void setShift(double x, double y) { + xx = 1; + xy = 0; + xd = x; + yx = 0; + yy = 1; + yd = y; + } + + /** + * Set this transformation to be a scale. + * + * @param x + * The X coordinate to scale to. + * @param y + * The Y coordinate to scale to. + */ + public void setScale(double x, double y) { + xx = x; + xy = 0; + xd = 0; + yx = 0; + yy = y; + yd = 0; + } + + /** + * Set transformation to be a uniform scale. + * + * @param _scale + * The scale of the transformation. + */ + public void setScale(double _scale) { + setScale(_scale, _scale); + } + + /** + * Sets the transformation to be a flip around the X axis. Flips the X + * coordinates so that the x0 becomes x1 and vice verse. + * + * @param x0 + * The X coordinate to flip. + * @param x1 + * The X coordinate to flip to. + */ + public void setFlipX(double x0, double x1) { + xx = -1; + xy = 0; + xd = x0 + x1; + yx = 0; + yy = 1; + yd = 0; + } + + /** + * Sets the transformation to be a flip around the Y axis. Flips the Y + * coordinates so that the y0 becomes y1 and vice verse. + * + * @param y0 + * The Y coordinate to flip. + * @param y1 + * The Y coordinate to flip to. + */ + public void setFlipY(double y0, double y1) { + xx = 1; + xy = 0; + xd = 0; + yx = 0; + yy = -1; + yd = y0 + y1; + } + + /** + * Set transformation to a shear. + * + * @param proportionX + * The proportion of shearing in x direction. + * @param proportionY + * The proportion of shearing in y direction. + */ + public void setShear(double proportionX, double proportionY) { + xx = 1; + xy = proportionX; + xd = 0; + yx = proportionY; + yy = 1; + yd = 0; + } + + /** + * Sets this transformation to be a rotation around point (0, 0). + * + * When the axis Y is directed up and X is directed to the right, the + * positive angle corresponds to the anti-clockwise rotation. When the axis + * Y is directed down and X is directed to the right, the positive angle + * corresponds to the clockwise rotation. + * + * @param angle_in_Radians + * The rotation angle in radian. + */ + public void setRotate(double angle_in_Radians) { + setRotate(Math.cos(angle_in_Radians), Math.sin(angle_in_Radians)); + } + + /** + * Produces a transformation that swaps x and y coordinate values. xx = 0.0; + * xy = 1.0; xd = 0; yx = 1.0; yy = 0.0; yd = 0; + */ + Transformation2D setSwapCoordinates() { + xx = 0.0; + xy = 1.0; + xd = 0; + yx = 1.0; + yy = 0.0; + yd = 0; + return this; + } + + /** + * Sets this transformation to be a rotation around point rotationCenter. + * + * When the axis Y is directed up and X is directed to the right, the + * positive angle corresponds to the anti-clockwise rotation. When the axis + * Y is directed down and X is directed to the right, the positive angle + * corresponds to the clockwise rotation. + * + * @param angle_in_Radians + * The rotation angle in radian. + * @param rotationCenter + * The center point of the rotation. + */ + void setRotate(double angle_in_Radians, Point2D rotationCenter) { + setRotate(Math.cos(angle_in_Radians), Math.sin(angle_in_Radians), + rotationCenter); + } + + /** + * Sets rotation for this transformation. + * + * When the axis Y is directed up and X is directed to the right, the + * positive angle corresponds to the anti-clockwise rotation. When the axis + * Y is directed down and X is directed to the right, the positive angle + * corresponds to the clockwise rotation. + * + * @param cosA + * The rotation angle. + * @param sinA + * The rotation angle. + */ + + public void setRotate(double cosA, double sinA) { + xx = cosA; + xy = -sinA; + xd = 0; + yx = sinA; + yy = cosA; + yd = 0; + } + + /** + * Sets this transformation to be a rotation around point rotationCenter. + * + * When the axis Y is directed up and X is directed to the right, the + * positive angle corresponds to the anti-clockwise rotation. When the axis + * Y is directed down and X is directed to the right, the positive angle + * corresponds to the clockwise rotation. + * + * @param cosA + * The cos of the rotation angle. + * @param sinA + * The sin of the rotation angle. + * @param rotationCenter + * The center point of the rotation. + */ + void setRotate(double cosA, double sinA, Point2D rotationCenter) { + setShift(-rotationCenter.x, -rotationCenter.y); + Transformation2D temp = new Transformation2D(); + temp.setRotate(cosA, sinA); + multiply(temp); + shift(rotationCenter.x, rotationCenter.y); + } + + /** + * Shifts the transformation. + * + * @param x + * The shift factor in X direction. + * @param y + * The shift factor in Y direction. + */ + public void shift(double x, double y) { + xd += x; + yd += y; + } + + /** + * Scales the transformation. + * + * @param x + * The scale factor in X direction. + * @param y + * The scale factor in Y direction. + */ + public void scale(double x, double y) { + xx *= x; + xy *= x; + xd *= x; + yx *= y; + yy *= y; + yd *= y; + } + + /** + * Flips the transformation around the X axis. + * + * @param x0 + * The X coordinate to flip. + * @param x1 + * The X coordinate to flip to. + */ + public void flipX(double x0, double x1) { + xx = -xx; + xy = -xy; + xd = x0 + x1 - xd; + } + + /** + * Flips the transformation around the Y axis. + * + * @param y0 + * The Y coordinate to flip. + * @param y1 + * The Y coordinate to flip to. + */ + public void flipY(double y0, double y1) { + yx = -yx; + yy = -yy; + yd = y0 + y1 - yd; + } + + /** + * Shears the transformation. + * + * @param proportionX + * The proportion of shearing in x direction. + * @param proportionY + * The proportion of shearing in y direction. + */ + public void shear(double proportionX, double proportionY) { + Transformation2D temp = new Transformation2D(); + temp.setShear(proportionX, proportionY); + multiply(temp); + } + + /** + * Rotates the transformation. + * + * @param angle_in_Radians + * The rotation angle in radian. + */ + public void rotate(double angle_in_Radians) { + Transformation2D temp = new Transformation2D(); + temp.setRotate(angle_in_Radians); + multiply(temp); + } + + /** + * Rotates the transformation. + * + * @param cos + * The cos angle of the rotation. + * @param sin + * The sin angle of the rotation. + */ + public void rotate(double cos, double sin) { + Transformation2D temp = new Transformation2D(); + temp.setRotate(cos, sin); + multiply(temp); + } + + /** + * Rotates the transformation aroung a center point. + * + * @param cos + * The cos angle of the rotation. + * @param sin + * sin angle of the rotation. + * @param rotationCenter + * The center point of the rotation. + */ + public void rotate(double cos, double sin, Point2D rotationCenter) { + Transformation2D temp = new Transformation2D(); + temp.setRotate(cos, sin, rotationCenter); + multiply(temp); + } + + /** + * Produces inverse matrix for this matrix and puts result into the inverse + * parameter. + * + * @param inverse + * The result inverse matrix. + */ + public void inverse(Transformation2D inverse) { + double det = xx * yy - xy * yx; + + if (det == 0) { + inverse.setZero(); + return; + } + + det = 1 / det; + + inverse.xd = (xy * yd - xd * yy) * det; + inverse.yd = (xd * yx - xx * yd) * det; + inverse.xx = yy * det; + inverse.xy = -xy * det; + inverse.yx = -yx * det; + inverse.yy = xx * det; + } + + /** + * Inverses the matrix. + * + */ + public void inverse() { + inverse(this); + } + + /** + * Extracts scaling part of the transformation. this == scale * + * rotateNshearNshift. + * + * @param scale + * The destination matrix where the scale part is copied. + * @param rotateNshearNshift + * The destination matrix where the part excluding rotation is + * copied. + */ + public void extractScaleTransform(Transformation2D scale, + Transformation2D rotateNshearNshift) { + + scale.setScale(Math.sqrt(xx * xx + xy * xy), + Math.sqrt(yx * yx + yy * yy)); + rotateNshearNshift.setScale(1.0 / scale.xx, 1.0 / scale.yy); + rotateNshearNshift.multiply(this); + } + +} diff --git a/src/com/esri/core/geometry/Transformation3D.java b/src/com/esri/core/geometry/Transformation3D.java new file mode 100644 index 00000000..5f914381 --- /dev/null +++ b/src/com/esri/core/geometry/Transformation3D.java @@ -0,0 +1,271 @@ +/* + Copyright 1995-2013 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ + +package com.esri.core.geometry; + +/** + * @brief The 3D affine transformation Vector is a row: |m11 m12 0| | x y 1| * + * |m21 m22 0| = |m11 * x + m21 * y + m31 m12 * x + m22 * y + m32 1| |m31 + * m32 1| Then elements of the Transformation2D are as follows: |xx yx 0| + * | x y 1| * |xy yy 0| = |xx * x + xy * y + xd yx * x + yy * y + yd 1| + * |xd yd 1| + * + * We use matrices for transformations of the vectors as rows (case 2). + * That means the math expressions on the Geometry matrix operations + * should be writen like this: v' = v * M1 * M2 * M3 = ( (v * M1) * M2 ) + * * M3, where v is a vector, Mn are the matrices. This is equivalent to + * the following line of code: ResultVector = + * (M1.Mul(M2).Mul(M3)).Transform(Vector) + */ +final class Transformation3D { + + public double xx, yx, zx, xd, xy, yy, zy, yd, xz, yz, zz, zd; + + public Transformation3D() { + + } + + /** + * Sets all elements to 0, thus producing and invalid transformation. + */ + public void setZero() { + xx = 0.0; + yx = 0.0; + zx = 0.0; + xy = 0.0; + yy = 0.0; + zy = 0.0; + xz = 0.0; + yz = 0.0; + zz = 0.0; + xd = 0.0; + yd = 0.0; + zd = 0.0; + } + + public void setScale(double scaleX, double scaleY, double scaleZ) { + xx = scaleX; + yx = 0.0; + zx = 0.0; + xy = 0.0; + yy = scaleY; + zy = 0.0; + xz = 0.0; + yz = 0.0; + zz = scaleZ; + xd = 0.0; + yd = 0.0; + zd = 0.0; + } + + public void setTranslate(double deltax, double deltay, double deltaz) { + xx = 1.0; + yx = 0.0; + zx = 0.0; + xy = 0.0; + yy = 1.0; + zy = 0.0; + xz = 0.0; + yz = 0.0; + zz = 1.0; + xd = deltax; + yd = deltay; + zd = deltaz; + } + + public void translate(double deltax, double deltay, double deltaz) { + xd += deltax; + yd += deltay; + zd += deltaz; + } + + /** + * Transforms an envelope. The result is the bounding box of the transformed + * envelope. + */ + public Envelope3D transform(Envelope3D env) { + + if (env.isEmpty()) + return env; + + Point3D[] buf = new Point3D[8]; + env.queryCorners(buf); + + transform(buf, 8, buf); + env.setFromPoints(buf); + return env; + } + + void transform(Point3D[] pointsIn, int count, Point3D[] pointsOut) { + for (int i = 0; i < count; i++) { + Point3D res = new Point3D(); + Point3D src = pointsIn[i]; + res.x = xx * src.x + xy * src.y + xz * src.z + xd; + res.y = yx * src.x + yy * src.y + yz * src.z + yd; + res.z = zx * src.x + zy * src.y + zz * src.z + zd; + pointsOut[i] = res; + } + } + + public Point3D transform(Point3D src) { + Point3D res = new Point3D(); + res.x = xx * src.x + xy * src.y + xz * src.z + xd; + res.y = yx * src.x + yy * src.y + yz * src.z + yd; + res.z = zx * src.x + zy * src.y + zz * src.z + zd; + return res; + } + + public void transform(Point3D[] points, int start, int count) { + int n = Math.min(points.length, start + count); + for (int i = start; i < n; i++) { + Point3D res = new Point3D(); + Point3D src = points[i]; + res.x = xx * src.x + xy * src.y + xz * src.z + xd; + res.y = yx * src.x + yy * src.y + yz * src.z + yd; + res.z = zx * src.x + zy * src.y + zz * src.z + zd; + points[i] = res; + } + } + + public void mul(Transformation3D right) { + multiply(this, right, this); + } + + public void mulLeft(Transformation3D left) { + multiply(left, this, this); + } + + /** + * Performs multiplication of matrices a and b and places result into + * result. The a, b, and result could point to same objects.
+ * Equivalent to result = a * b. + */ + // static + public static void multiply(Transformation3D a, Transformation3D b, + Transformation3D result) { + double xx, yx, zx; + double xy, yy, zy; + double xz, yz, zz; + double xd, yd, zd; + + xx = a.xx * b.xx + a.yx * b.xy + a.zx * b.xz; + yx = a.xx * b.yx + a.yx * b.yy + a.zx * b.yz; + zx = a.xx * b.zx + a.yx * b.zy + a.zx * b.zz; + xy = a.xy * b.xx + a.yy * b.xy + a.zy * b.xz; + yy = a.xy * b.yx + a.yy * b.yy + a.zy * b.yz; + zy = a.xy * b.zx + a.yy * b.zy + a.zy * b.zz; + xz = a.xz * b.xx + a.yz * b.xy + a.zz * b.xz; + yz = a.xz * b.yx + a.yz * b.yy + a.zz * b.yz; + zz = a.xz * b.zx + a.yz * b.zy + a.zz * b.zz; + xd = a.xd * b.xx + a.yd * b.xy + a.zd * b.xz + b.xd; + yd = a.xd * b.yx + a.yd * b.yy + a.zd * b.yz + b.yd; + zd = a.xd * b.zx + a.yd * b.zy + a.zd * b.zz + b.zd; + + result.xx = xx; + result.yx = yx; + result.zx = zx; + result.xy = xy; + result.yy = yy; + result.zy = zy; + result.xz = xz; + result.yz = yz; + result.zz = zz; + result.xd = xd; + result.yd = yd; + result.zd = zd; + } + + /** + * Calculates the Inverse transformation. + * + * @param src + * The input transformation. + * @param dst + * The inverse of the input transformation. + * @throws Throws + * the GeometryException("math_singularity") exception if the + * Inverse can not be calculated. + */ + // static + public static void inverse(Transformation3D src, Transformation3D result) { + double det = src.xx * (src.yy * src.zz - src.zy * src.yz) - src.yx + * (src.xy * src.zz - src.zy * src.xz) + src.zx + * (src.xy * src.yz - src.yy * src.xz); + if (det != 0) { + double xx, yx, zx; + double xy, yy, zy; + double xz, yz, zz; + double xd, yd, zd; + + double det_1 = 1.0 / det; + xx = (src.yy * src.zz - src.zy * src.yz) * det_1; + xy = -(src.xy * src.zz - src.zy * src.xz) * det_1; + xz = (src.xy * src.yz - src.yy * src.xz) * det_1; + + yx = -(src.yx * src.zz - src.yz * src.zx) * det_1; + yy = (src.xx * src.zz - src.zx * src.xz) * det_1; + yz = -(src.xx * src.yz - src.yx * src.xz) * det_1; + + zx = (src.yx * src.zy - src.zx * src.yy) * det_1; + zy = -(src.xx * src.zy - src.zx * src.xy) * det_1; + zz = (src.xx * src.yy - src.yx * src.xy) * det_1; + + xd = -(src.xd * xx + src.yd * xy + src.zd * xz); + yd = -(src.xd * yx + src.yd * yy + src.zd * yz); + zd = -(src.xd * zx + src.yd * zy + src.zd * zz); + + result.xx = xx; + result.yx = yx; + result.zx = zx; + result.xy = xy; + result.yy = yy; + result.zy = zy; + result.xz = xz; + result.yz = yz; + result.zz = zz; + result.xd = xd; + result.yd = yd; + result.zd = zd; + } else { + throw new GeometryException("math singularity"); + } + } + + public Transformation3D copy() { + Transformation3D result = new Transformation3D(); + result.xx = xx; + result.yx = yx; + result.zx = zx; + result.xy = xy; + result.yy = yy; + result.zy = zy; + result.xz = xz; + result.yz = yz; + result.zz = zz; + result.xd = xd; + result.yd = yd; + result.zd = zd; + return result; + } +} diff --git a/src/com/esri/core/geometry/Treap.java b/src/com/esri/core/geometry/Treap.java new file mode 100644 index 00000000..ba879718 --- /dev/null +++ b/src/com/esri/core/geometry/Treap.java @@ -0,0 +1,964 @@ +/* + Copyright 1995-2013 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ + + +package com.esri.core.geometry; + +class Treap { + static abstract class Comparator { + Comparator() { + m_b_notify_on_actions = false; + } + + Comparator(boolean bNotifyOnActions) { + m_b_notify_on_actions = bNotifyOnActions; + } + + // Compares the element elm to the element contained in the given node + abstract int compare(Treap treap, int elm, int node); + + // These virtual methods are called only when Comparator(true) ctro has + // been used. + void onDelete(int elm) { + } + + void onSet(int elm) { + } + + void onEndSearch(int elm) { + } + + void onAddUniqueElementFailed(int elm) { + } + + private boolean m_b_notify_on_actions; + + // void operator=(const Comparator&); // do not allow operator = + void onDeleteImpl_(Treap treap, int node) { + if (m_b_notify_on_actions) + onDelete(treap.getElement(node)); + } + + void onSetImpl_(Treap treap, int node) { + if (m_b_notify_on_actions) + onSet(treap.getElement(node)); + } + + void onAddUniqueElementFailedImpl_(int elm) { + if (m_b_notify_on_actions) + onAddUniqueElementFailed(elm); + } + + void onEndSearchImpl_(int elm) { + if (m_b_notify_on_actions) + onEndSearch(elm); + } + }; + + static abstract class MonikerComparator { + // Compares the moniker, contained in the MonikerComparator with the + // element contained in the given node. + abstract int compare(Treap treap, int node); + }; + + public Treap() { + m_random = 124234251; + m_b_balancing = true; + m_touchFlag = 0; + m_defaultTreap = nullNode(); + m_treapData = new StridedIndexTypeCollection(7); + m_comparator = null; + } + + // Sets the comparator + public void setComparator(Comparator comparator) { + m_comparator = comparator; + } + + // Returns the comparator + public Comparator getComparator() { + return m_comparator; + } + + // Stops auto-balancing + public void disableBalancing() { + m_b_balancing = false; + } + + // Reserves memory for nodes givne number of nodes + public void setCapacity(int capacity) { + m_treapData.setCapacity(capacity); + } + + // Create a new treap and returns the treap handle. + public int createTreap(int treap_data) { + int treap = m_treapData.newElement(); + setSize_(0, treap); + setTreapData_(treap_data, treap); + return treap; + } + + // Deletes the treap at the given treap handle. + public void deleteTreap(int treap) { + m_treapData.deleteElement(treap); + } + + // Adds new element to the treap. Allows duplicates to be added. + public int addElement(int element, int treap) { + int treap_; + if (treap == -1) { + if (m_defaultTreap == nullNode()) + m_defaultTreap = createTreap(-1); + treap_ = m_defaultTreap; + } else { + treap_ = treap; + } + + return addElement_(element, 0, treap_); + } + + // Adds new element to the treap if it is not equal to other elements. + // If the return value is -1, then get_duplicate_element reutrns the node of + // the already existing element equal to element. + public int addUniqueElement(int element, int treap) { + int treap_; + if (treap == -1) { + if (m_defaultTreap == nullNode()) + m_defaultTreap = createTreap(-1); + treap_ = m_defaultTreap; + } else { + treap_ = treap; + } + + return addElement_(element, 1, treap_); + } + + // Adds a new element to the treap that is known to be bigger or equal of + // all elements already in the treap. + // Use this method when adding elements from a sorted list for maximum + // performance (it does not call the treap comparator). + public int addBiggestElement(int element, int treap) { + int treap_; + if (treap == -1) { + if (m_defaultTreap == nullNode()) + m_defaultTreap = createTreap(-1); + treap_ = m_defaultTreap; + } else { + treap_ = treap; + } + + if (getRoot_(treap_) == nullNode()) { + int newNode = newNode_(element); + setRoot_(newNode, treap_); + addToList_(-1, newNode, treap_); + return newNode; + } + + int cur = getLast_(treap_); + int newNode = newNode_(element); + setRight_(cur, newNode); + setParent_(newNode, cur); + assert (m_b_balancing);// don't use this method for unbalanced tree, or + // the performance will be bad. + bubbleUp_(newNode); + if (getParent(newNode) == nullNode()) + setRoot_(newNode, treap_); + + addToList_(-1, newNode, treap_); + return newNode; + } + + // template void build_from_sorted(const Iterator& begin, + // const Iterator& end); + // Adds new element to the treap at the known position, thus avoiding a call + // to the comparator. + // If bCallCompare is True, the comparator will be called at most twice, + // once to compare with prevElement and once to compare with nextElement. + // When bUnique is true, if the return value is -1, then + // get_duplicate_element reutrns the node of the already existing element. + public int addElementAtPosition(int prevNode, int nextNode, int element, + boolean bUnique, boolean bCallCompare, int treap) { + int treap_; + if (treap == -1) { + if (m_defaultTreap == nullNode()) + m_defaultTreap = createTreap(-1); + treap_ = m_defaultTreap; + } else { + treap_ = treap; + } + + // dbg_check_(m_root); + if (getRoot_(treap_) == nullNode() + || (prevNode == nullNode() && nextNode == nullNode())) + throw new GeometryException("invald call"); + + int cmpNext; + int cmpPrev; + if (bCallCompare) { + cmpNext = nextNode != nullNode() ? m_comparator.compare(this, + element, nextNode) : -1; + assert (cmpNext <= 0); + cmpPrev = prevNode != nullNode() ? m_comparator.compare(this, + element, prevNode) : 1; + // cmpPrev can be negative in plane sweep when intersection is + // detected. + } else { + cmpNext = -1; + cmpPrev = 1; + } + + if (bUnique && (cmpNext == 0 || cmpPrev == 0)) { + m_comparator.onAddUniqueElementFailedImpl_(element); + int cur = cmpNext == 0 ? nextNode : prevNode; + setDuplicateElement_(cur, treap_); + return -1;// return negative value. + } + + int cur; + int cmp; + boolean bNext; + if (nextNode != nullNode() && prevNode != nullNode()) { + // randomize the the cost to insert a node. + bNext = m_random > NumberUtils.nextRand(m_random) >> 1; + } else + bNext = nextNode != nullNode(); + + if (bNext) { + cmp = cmpNext; + cur = nextNode; + } else { + cmp = cmpPrev; + cur = prevNode; + } + + int newNode = -1; + int before = -1; + boolean b_first = true; + for (;;) { + if (cmp < 0) { + int left = getLeft(cur); + if (left != nullNode()) + cur = left; + else { + before = cur; + newNode = newNode_(element); + setLeft_(cur, newNode); + setParent_(newNode, cur); + break; + } + } else { + int right = getRight(cur); + if (right != nullNode()) + cur = right; + else { + before = getNext(cur); + newNode = newNode_(element); + setRight_(cur, newNode); + setParent_(newNode, cur); + break; + } + } + + if (b_first) { + cmp *= -1; + b_first = false; + } + } + + bubbleUp_(newNode); + if (getParent(newNode) == nullNode()) + setRoot_(newNode, treap_); + + addToList_(before, newNode, treap_); + // dbg_check_(m_root); + return newNode; + } + + // Get duplicate element + public int getDuplicateElement(int treap) { + if (treap == -1) + return getDuplicateElement_(m_defaultTreap); + + return getDuplicateElement_(treap); + } + + // Removes a node from the treap. Throws if doesn't exist. + public void deleteNode(int treap_node_index, int treap) { + touch_(); + // assert(isValidNode(treap_node_index)); + if (m_comparator != null) + m_comparator.onDeleteImpl_(this, treap_node_index); + + int treap_; + if (treap == -1) + treap_ = m_defaultTreap; + else + treap_ = treap; + + if (!m_b_balancing) { + unbalancedDelete_(treap_node_index, treap_); + } else + deleteNode_(treap_node_index, treap_); + } + + // Finds an element in the treap and returns its node or -1. + public int search(int data, int treap) { + int cur = getRoot(treap); + while (cur != nullNode()) { + int res = m_comparator.compare(this, data, cur); + if (res == 0) + return cur; + else if (res < 0) + cur = getLeft(cur); + else + cur = getRight(cur); + } + + m_comparator.onEndSearchImpl_(data); + return nullNode(); + } + + // Find a first node in the treap which is less or equal the moniker. + // Returns closest smaller (Comparator::compare returns -1) or any equal. + public int searchLowerBound(MonikerComparator moniker, int treap) { + int cur = getRoot(treap); + int bound = -1; + while (cur != nullNode()) { + int res = moniker.compare(this, cur); + if (res == 0) + return cur; + else if (res < 0) + cur = getLeft(cur); + else { + bound = cur; + cur = getRight(cur); + } + } + + return bound; + } + + // Find a first node in the treap which is greater or equal the moniker. + // Returns closest greater (Comparator::compare returns 1) or any equal. + public int searchUpperBound(MonikerComparator moniker, int treap) { + int cur = getRoot(treap); + int bound = -1; + while (cur != nullNode()) { + int res = moniker.compare(this, cur); + if (res == 0) + return cur; + else if (res < 0) { + bound = cur; + cur = getLeft(cur); + } else { + cur = getRight(cur); + } + } + + return bound; + } + + // Returns treap node data (element) from the given node index. + public int getElement(int treap_node_index) { + return m_treapData.getField(treap_node_index, 3);// no error checking + // here + } + + // Returns treap node for the left node for the given treap node index + public int getLeft(int treap_node_index) { + return m_treapData.getField(treap_node_index, 0);// no error checking + // here + } + + // Returns treap index for the right node for the given treap node index + public int getRight(int treap_node_index) { + return m_treapData.getField(treap_node_index, 1);// no error checking + // here + } + + // Returns treap index for the parent node for the given treap node index + public int getParent(int treap_node_index) { + return m_treapData.getField(treap_node_index, 2);// no error checking + // here + } + + // Returns next treap index. Allows to navigate Treap in the sorted order + public int getNext(int treap_node_index) { + return m_treapData.getField(treap_node_index, 6); + } + + // Returns prev treap index. Allows to navigate Treap in the sorted order + // backwards + public int getPrev(int treap_node_index) { + return m_treapData.getField(treap_node_index, 5); + } + + // Returns the first element in the treap (least one). Used together with + // get_next to write a loop + public int getFirst(int treap) { + if (treap == -1) + return getFirst_(m_defaultTreap); + + return getFirst_(treap); + } + + // Returns the last element in the treap (greatest one). Used together with + // get_prev to write a loop + public int getLast(int treap) { + if (treap == -1) + return getLast_(m_defaultTreap); + + return getLast_(treap); + } + + // Gets the treap data associated with the treap. + public int getTreapData(int treap) { + if (treap == -1) + return getTreapData_(m_defaultTreap); + + return getTreapData_(treap); + } + + // Change the element value. Note: do not call this method if setting the + // element will change the sorted order. + public void setElement(int treap_node_index, int newElement) { + if (m_comparator != null) + m_comparator.onSetImpl_(this, treap_node_index); + setElement_(treap_node_index, newElement); + } + + // Returns the root of the treap. + public int getRoot(int treap) { + if (treap == -1) + return getRoot_(m_defaultTreap); + + return getRoot_(treap); + } + + // Check if the node is Null (does not exist). + public static int nullNode() { + return -1; + } + + // Clears all nodes + public void clear() { + m_treapData.deleteAll(false); + m_defaultTreap = nullNode(); + } + + // Total number of nodes + public int size(int treap) { + if (treap == -1) + return getSize_(m_defaultTreap); + + return getSize_(treap); + } + + // Returns the maximum depth of this Treap at given moment + public int getMaxDepth(int treap) { + return getMaxDepthHelper_(getRoot(treap)); + } + + public int getStateFlag() { + m_touchFlag &= 0x7FFFFFFF; + return m_touchFlag; + } + + private int m_defaultTreap; + private int m_random; + private Treap.Comparator m_comparator;// comparator used to arrange the + // nodes + private StridedIndexTypeCollection m_treapData; // m_left (0), m_right (1), + // m_parent (2), m_element + // (3), m_priority (4), + // m_prev (5), m_next (6) + // (optional: m_root (0), + // m_first (1), m_last (2), + // m_duplicate_element (3), + // m_treap_size (4), + // m_treapData (5)) + private int m_touchFlag; + private boolean m_b_balancing; + + private void touch_() { + if (m_touchFlag >= 0) { + m_touchFlag += 0x80000001; + } + } + + private int getPriority_(int treap_node_index) { + return m_treapData.getField(treap_node_index, 4);// no error checking + // here + } + + private void bubbleDown_(int treap_node_index) { + int left = getLeft(treap_node_index); + int right = getRight(treap_node_index); + int priority = getPriority_(treap_node_index); + while (left != nullNode() || right != nullNode()) { + int lcprior = left != nullNode() ? getPriority_(left) : NumberUtils + .intMax(); + int rcprior = right != nullNode() ? getPriority_(right) + : NumberUtils.intMax(); + int minprior = Math.min(lcprior, rcprior); + + if (priority <= minprior) + return; + + if (lcprior <= rcprior) + rotateRight_(left); + else + rotateLeft_(treap_node_index); + + left = getLeft(treap_node_index); + right = getRight(treap_node_index); + } + } + + private void bubbleUp_(int node) { + if (!m_b_balancing) + return; + int priority = getPriority_(node); + int parent = getParent(node); + while (parent != nullNode() && getPriority_(parent) > priority) { + if (getLeft(parent) == node) + rotateRight_(node); + else + rotateLeft_(parent); + + parent = getParent(node); + } + } + + private void rotateLeft_(int treap_node_index) { + int px = treap_node_index; + int py = getRight(treap_node_index); + int ptemp; + setParent_(py, getParent(px)); + setParent_(px, py); + + ptemp = getLeft(py); + setRight_(px, ptemp); + + if (ptemp != nullNode()) + setParent_(ptemp, px); + + setLeft_(py, px); + + ptemp = getParent(py); + if (ptemp != nullNode()) { + if (getLeft(ptemp) == px) + setLeft_(ptemp, py); + else { + assert (getRight(ptemp) == px); + setRight_(ptemp, py); + } + } + } + + private void rotateRight_(int treap_node_index) { + int py = getParent(treap_node_index); + int px = treap_node_index; + int ptemp; + + setParent_(px, getParent(py)); + setParent_(py, px); + + ptemp = getRight(px); + setLeft_(py, ptemp); + + if (ptemp != nullNode()) + setParent_(ptemp, py); + + setRight_(px, py); + + ptemp = getParent(px); + if (ptemp != nullNode()) { + if (getLeft(ptemp) == py) + setLeft_(ptemp, px); + else { + assert (getRight(ptemp) == py); + setRight_(ptemp, px); + } + } + } + + private void setParent_(int treap_node_index, int new_parent) { + m_treapData.setField(treap_node_index, 2, new_parent); // no error + // checking here + } + + private void setLeft_(int treap_node_index, int new_left) { + m_treapData.setField(treap_node_index, 0, new_left); // no error + // checking here + } + + private void setRight_(int treap_node_index, int new_right) { + m_treapData.setField(treap_node_index, 1, new_right); // no error + // checking here + } + + private void setPriority_(int treap_node_index, int new_priority) { + m_treapData.setField(treap_node_index, 4, new_priority); // no error + // checking + // here + } + + private void setPrev_(int treap_node_index, int prev) { + assert (prev != treap_node_index); + m_treapData.setField(treap_node_index, 5, prev); // no error checking + // here + } + + private void setNext_(int treap_node_index, int next) { + assert (next != treap_node_index); + m_treapData.setField(treap_node_index, 6, next); // no error checking + // here + } + + private void setRoot_(int root, int treap) { + m_treapData.setField(treap, 0, root); + } + + private void setFirst_(int first, int treap) { + m_treapData.setField(treap, 1, first); + } + + private void setLast_(int last, int treap) { + m_treapData.setField(treap, 2, last); + } + + private void setDuplicateElement_(int duplicate_element, int treap) { + m_treapData.setField(treap, 3, duplicate_element); + } + + private void setSize_(int size, int treap) { + m_treapData.setField(treap, 4, size); + } + + private void setTreapData_(int treap_data, int treap) { + m_treapData.setField(treap, 5, treap_data); + } + + private int getRoot_(int treap) { + if (treap == -1) + return nullNode(); + + return m_treapData.getField(treap, 0); + } + + private int getFirst_(int treap) { + if (treap == -1) + return nullNode(); + + return m_treapData.getField(treap, 1); + } + + private int getLast_(int treap) { + if (treap == -1) + return nullNode(); + + return m_treapData.getField(treap, 2); + } + + private int getDuplicateElement_(int treap) { + if (treap == -1) + return nullNode(); + + return m_treapData.getField(treap, 3); + } + + private int getSize_(int treap) { + if (treap == -1) + return 0; + + return m_treapData.getField(treap, 4); + } + + private int getTreapData_(int treap) { + return m_treapData.getField(treap, 5); + } + + private int newNode_(int element) { + touch_(); + int newNode = m_treapData.newElement(); + setPriority_(newNode, generatePriority_()); + setElement_(newNode, element); + return newNode; + } + + private void freeNode_(int treap_node_index, int treap) { + if (treap_node_index == nullNode()) + return; + + m_treapData.deleteElement(treap_node_index); + } + + private int generatePriority_() { + m_random = NumberUtils.nextRand(m_random); + return m_random & (NumberUtils.intMax() >> 1); + } + + private int getMaxDepthHelper_(int node) { + if (node == nullNode()) + return 0; + + return 1 + Math.max(getMaxDepthHelper_(getLeft(node)), + getMaxDepthHelper_(getRight(node))); + } + + private int addElement_(int element, int kind, int treap) { + // dbg_check_(m_root); + if (getRoot_(treap) == nullNode()) { + int newNode = newNode_(element); + setRoot_(newNode, treap); + addToList_(-1, newNode, treap); + return newNode; + } + + int cur = getRoot_(treap); + int newNode = -1; + int before = -1; + + for (;;) { + int cmp = kind == -1 ? 1 : m_comparator.compare(this, element, cur); + if (cmp < 0) { + int left = getLeft(cur); + if (left != nullNode()) + cur = left; + else { + before = cur; + newNode = newNode_(element); + setLeft_(cur, newNode); + setParent_(newNode, cur); + break; + } + } else { + if (kind == 1 && cmp == 0) { + m_comparator.onAddUniqueElementFailedImpl_(element); + setDuplicateElement_(cur, treap); + return -1;// return negative value. + } + + int right = getRight(cur); + if (right != nullNode()) + cur = right; + else { + before = getNext(cur); + newNode = newNode_(element); + setRight_(cur, newNode); + setParent_(newNode, cur); + break; + } + } + } + + bubbleUp_(newNode); + if (getParent(newNode) == nullNode()) + setRoot_(newNode, treap); + + addToList_(before, newNode, treap); + // dbg_check_(m_root); + return newNode; + } + + private void addToList_(int before, int node, int treap) { + assert (before != node); + int prev; + if (before != -1) { + prev = getPrev(before); + setPrev_(before, node); + } else + prev = getLast_(treap); + + setPrev_(node, prev); + if (prev != -1) + setNext_(prev, node); + setNext_(node, before); + + if (before == getFirst_(treap)) { + setFirst_(node, treap); + } + if (before == -1) { + setLast_(node, treap); + } + + setSize_(getSize_(treap) + 1, treap); + } + + private void removeFromList_(int node, int treap) { + int prev = getPrev(node); + int next = getNext(node); + if (prev != -1) + setNext_(prev, next); + else + setFirst_(next, treap); + + if (next != -1) + setPrev_(next, prev); + else + setLast_(prev, treap); + + setSize_(getSize_(treap) - 1, treap); + } + + private void unbalancedDelete_(int treap_node_index, int treap) { + assert (!m_b_balancing); + // dbg_check_(m_root); + removeFromList_(treap_node_index, treap); + int left = getLeft(treap_node_index); + int right = getRight(treap_node_index); + int parent = getParent(treap_node_index); + int x = treap_node_index; + if (left != -1 && right != -1) { + m_random = NumberUtils.nextRand(m_random); + int R; + if (m_random > (NumberUtils.intMax() >> 1)) + R = getNext(treap_node_index); + else + R = getPrev(treap_node_index); + + assert (R != -1);// cannot be NULL becaus the node has left and + // right + + boolean bFixMe = getParent(R) == treap_node_index; + + // swap left, right, and parent + m_treapData.swapField(treap_node_index, R, 0); + m_treapData.swapField(treap_node_index, R, 1); + m_treapData.swapField(treap_node_index, R, 2); + + if (parent != -1) { + // Connect ex-parent of int to R. + if (getLeft(parent) == treap_node_index) { + setLeft_(parent, R); + } else { + assert (getRight(parent) == treap_node_index); + setRight_(parent, R); + } + } else {// int was the root. Make R the Root. + setRoot_(R, treap); + } + + if (bFixMe) {// R was a child of int + if (left == R) { + setLeft_(R, treap_node_index); + setParent_(right, R); + } else if (right == R) { + setRight_(R, treap_node_index); + setParent_(left, R); + } + + setParent_(treap_node_index, R); + parent = R; + } else { + setParent_(left, R); + setParent_(right, R); + parent = getParent(treap_node_index); + x = R; + } + + assert (parent != -1); + left = getLeft(treap_node_index); + right = getRight(treap_node_index); + if (left != -1) + setParent_(left, treap_node_index); + if (right != -1) + setParent_(right, treap_node_index); + + assert (left == -1 || right == -1); + } + + // At most one child is not NULL. + int child = left != -1 ? left : right; + + if (parent == -1) { + setRoot_(child, treap); + } else { + if (getLeft(parent) == x) { + setLeft_(parent, child); + } else { + assert (getRight(parent) == x); + setRight_(parent, child); + } + } + + if (child != -1) + setParent_(child, parent); + + freeNode_(treap_node_index, treap); + // dbg_check_(m_root); + } + + private void deleteNode_(int treap_node_index, int treap) { + assert (m_b_balancing); + setPriority_(treap_node_index, NumberUtils.intMax()); // set the node + // priority high + int prl = nullNode(); + int prr = nullNode(); + int root = getRoot_(treap); + boolean isroot = (root == treap_node_index); + + if (isroot) { + // remember children of the root node, if the root node is to be + // deleted + prl = getLeft(root); + prr = getRight(root); + + if (prl == nullNode() && prr == nullNode()) { + removeFromList_(root, treap); + freeNode_(root, treap); + setRoot_(nullNode(), treap); + return; + } + } + + bubbleDown_(treap_node_index); // let the node to slide to the leaves of + // tree + + int p = getParent(treap_node_index); + + if (p != nullNode()) { + if (getLeft(p) == treap_node_index) + setLeft_(p, nullNode()); + else + setRight_(p, nullNode()); + } + + removeFromList_(treap_node_index, treap); + freeNode_(treap_node_index, treap); + + if (isroot) // if the root node is deleted, assign new root + setRoot_((prl == nullNode() || getParent(prl) != nullNode()) ? prr + : prl, treap); + + assert (getParent(getRoot(treap)) == nullNode()); + } + + private void setElement_(int treap_node_index, int newElement) { + touch_(); + m_treapData.setField(treap_node_index, 3, newElement);// no error + // checking here + } +} diff --git a/src/com/esri/core/geometry/UserCancelException.java b/src/com/esri/core/geometry/UserCancelException.java new file mode 100644 index 00000000..bf0b1f2b --- /dev/null +++ b/src/com/esri/core/geometry/UserCancelException.java @@ -0,0 +1,32 @@ +/* + Copyright 1995-2013 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ +package com.esri.core.geometry; + +class UserCancelException extends GeometryException { + private static final long serialVersionUID = 1L; + + public UserCancelException() { + super("user cancel"); + } +} diff --git a/src/com/esri/core/geometry/VertexDescription.java b/src/com/esri/core/geometry/VertexDescription.java new file mode 100644 index 00000000..c2f34be6 --- /dev/null +++ b/src/com/esri/core/geometry/VertexDescription.java @@ -0,0 +1,393 @@ +/* + Copyright 1995-2013 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ + + +package com.esri.core.geometry; + +/** + * Describes the vertex format of a Geometry. + * + * Geometry objects store vertices. The vertex is a multi attribute entity. It + * has mandatory X, Y coordinates. In addition it may have Z, M, ID, and other + * user specified attributes. Geometries point to VertexDescription instances. + * If the two Geometries have same set of attributes, they point to the same + * VertexDescription instance.
+ * To create a new VertexDescription use the VertexDescriptionDesigner class.
+ * The VertexDescription allows to add new attribute types easily (see ID2).
+ * The attributes are stored sorted by Semantics value.
+ * Note: You could also think of the VertexDescription as a schema of a database + * table. You may look the vertices of a Geometry as if they are stored in a + * database table, and the VertexDescription defines the fields of the table. + */ +public class VertexDescription { + + private double[] m_defaultPointAttributes; + private int[] m_pointAttributeOffsets; + int m_attributeCount; + int m_total_component_count; + + int[] m_semantics; + + int[] m_semanticsToIndexMap; + + int m_hash; + + static double[] _defaultValues = { 0, 0, NumberUtils.NaN(), 0, 0, 0, 0, 0, + 0 }; + + static int[] _interpolation = { Interpolation.LINEAR, Interpolation.LINEAR, + Interpolation.LINEAR, Interpolation.NONE, Interpolation.ANGULAR, + Interpolation.LINEAR, Interpolation.LINEAR, Interpolation.LINEAR, + Interpolation.NONE, // FIXME this last value doesnt exist in native + }; + + static int[] _persistence = { Persistence.enumDouble, + Persistence.enumDouble, Persistence.enumDouble, + Persistence.enumInt32, Persistence.enumFloat, + Persistence.enumFloat, Persistence.enumFloat, + Persistence.enumFloat, Persistence.enumInt32, // FIXME, this last + // Int32 doesn't + // exist in native + }; + + static int[] _persistencesize = { 4, 8, 4, 8, 1, 2 }; + + static int[] _components = { 2, 1, 1, 1, 3, 1, 2, 3, 2, }; + + VertexDescription() { + m_attributeCount = 0; + m_total_component_count = 0; + + } + + VertexDescription(int hashValue, VertexDescription other) { + m_attributeCount = other.m_attributeCount; + m_total_component_count = other.m_total_component_count; + m_semantics = other.m_semantics.clone(); + m_semanticsToIndexMap = other.m_semanticsToIndexMap.clone(); + m_hash = other.m_hash; + + // Prepare default values for the Point geometry. + m_pointAttributeOffsets = new int[getAttributeCount()]; + int offset = 0; + for (int i = 0; i < getAttributeCount(); i++) { + m_pointAttributeOffsets[i] = offset; + offset += getComponentCount(m_semantics[i]); + } + m_total_component_count = offset; + m_defaultPointAttributes = new double[offset]; + for (int i = 0; i < getAttributeCount(); i++) { + int components = getComponentCount(getSemantics(i)); + double dv = getDefaultValue(getSemantics(i)); + for (int icomp = 0; icomp < components; icomp++) + m_defaultPointAttributes[m_pointAttributeOffsets[i] + icomp] = dv; + } + } + + /** + * Specifies how the attribute is interpolated along the segments. are + * represented as int64 + */ + interface Interpolation { + public static final int NONE = 0; + + public static final int LINEAR = 1; + + public static final int ANGULAR = 2; + } + + /** + * Specifies the type of the attribute. + */ + interface Persistence { + public static final int enumFloat = 0; + + public static final int enumDouble = 1; + + public static final int enumInt32 = 2; + + public static final int enumInt64 = 3; + + public static final int enumInt8 = 4; // 8 bit integer. Can be signed or + // unsigned depending on + // platform. + + public static final int enumInt16 = 5; + }; + + /** + * Describes the attribute and, in case of predefined attributes, provides a + * hint of the attribute use. + */ + public interface Semantics { + static final int POSITION = 0; // xy coordinates of a point (2D + // vector of double, linear + // interpolation) + + static final int Z = 1; // z coordinates of a point (double, + // linear interpolation) + + static final int M = 2; // m attribute (double, linear + // interpolation) + + static final int ID = 3; // id (int, no interpolation) + + static final int NORMAL = 4; // xyz coordinates of normal vector + // (float, angular interpolation) + + static final int TEXTURE1D = 5; // u coordinates of texture + // (float, linear interpolation) + + static final int TEXTURE2D = 6; // uv coordinates of texture + // (float, linear interpolation) + + static final int TEXTURE3D = 7; // uvw coordinates of texture + // (float, linear interpolation) + + static final int ID2 = 8; // two component ID + + static final int MAXSEMANTICS = 10; // the max semantics value + } + + /** + * Returns the attribute count of this description. The value is always + * greater or equal to 1. The first attribute is always a POSITION. + */ + public final int getAttributeCount() { + return m_attributeCount; + } + + /** + * Returns the semantics of the given attribute. + * + * @param attributeIndex + * The index of the attribute in the description. Max value is + * GetAttributeCount() - 1. + */ + public final int getSemantics(int attrbuteIndex) { + if (attrbuteIndex < 0 || attrbuteIndex > m_attributeCount) + throw new IllegalArgumentException(); + + return m_semantics[attrbuteIndex]; + } + + /** + * Returns the index the given attribute in the vertex description. + * + * @param semantics + * @return Returns the attribute index or -1 of the attribute does not exist + */ + public final int getAttributeIndex(int semantics) { + return m_semanticsToIndexMap[semantics]; + } + + /** + * Returns the interpolation type for the attribute. + * + * @param semantics + * The semantics of the attribute. + */ + static int getInterpolation(int semantics) { + return _interpolation[semantics]; + } + + /** + * Returns the persistence type for the attribute. + * + * @param semantics + * The semantics of the attribute. + */ + static int getPersistence(int semantics) { + return _persistence[semantics]; + } + + /** + * Returns the size of the persistence type in bytes. + * + * @param persistence + * The persistence type to query. + */ + static int getPersistenceSize(int persistence) { + return _persistencesize[persistence]; + } + + /** + * Returns the size of the semantics in bytes. + */ + static int getPersistenceSizeSemantics(int semantics) { + return getPersistenceSize(getPersistence(semantics)) + * getComponentCount(semantics); + } + + /** + * Returns the number of the components of the given semantics. For example, + * it returns 2 for the POSITION. + * + * @param semantics + * The semantics of the attribute. + */ + public static int getComponentCount(int semantics) { + return _components[semantics]; + } + + /** + * Returns True for integer persistence type. + */ + static boolean isIntegerPersistence(int persistence) { + return persistence < Persistence.enumInt32; + } + + /** + * Returns True for integer semantics type. + */ + static boolean isIntegerSemantics(int semantics) { + return isIntegerPersistence(getPersistence(semantics)); + } + + /** + * Returns True if the attribute with the given name and given set exists. + * + * @param semantics + * The semantics of the attribute. + */ + public boolean hasAttribute(int semantics) { + return m_semanticsToIndexMap[semantics] >= 0; + } + + /** + * Returns True, if the vertex has Z attribute. + */ + public boolean hasZ() { + return hasAttribute(Semantics.Z); + } + + /** + * Returns True, if the vertex has M attribute. + */ + public boolean hasM() { + return hasAttribute(Semantics.M); + } + + /** + * Returns True, if the vertex has ID attribute. + */ + public boolean hasID() { + return hasAttribute(Semantics.ID); + } + + /** + * Returns default value for each ordinate of the vertex attribute with + * given semantics. + */ + public static double getDefaultValue(int semantics) { + return _defaultValues[semantics]; + } + + int getPointAttributeOffset_(int attribute_index) { + return m_pointAttributeOffsets[attribute_index]; + } + + /** + * Returns the total component count. + */ + public int getTotalComponentCount() { + return m_total_component_count; + } + + /** + * Checks if the given value is the default one. The simple equality test + * with GetDefaultValue does not work due to the use of NaNs as default + * value for some parameters. + */ + public static boolean isDefaultValue(int semantics, double v) { + return NumberUtils.doubleToInt64Bits(_defaultValues[semantics]) == NumberUtils + .doubleToInt64Bits(v); + } + + static int getPersistenceFromInt(int size) { + if (size == 4) + return Persistence.enumInt32; + else if (size == 8) + return Persistence.enumInt64; + else + throw new IllegalArgumentException(); + } + + @Override + public boolean equals(Object _other) { + return (Object) this == _other; + } + + int calculateHashImpl() { + int v = NumberUtils.hash(m_semantics[0]); + for (int i = 1; i < m_attributeCount; i++) + v = NumberUtils.hash(v, m_semantics[i]); + + return v; // if attribute size is 1, it returns 0 + } + + /** + * + * Returns a packed array of double representation of all ordinates of + * attributes of a point, i.e.: X, Y, Z, ID, TEXTURE2D.u, TEXTURE2D.v + */ + double[] _getDefaultPointAttributes() { + return m_defaultPointAttributes; + } + + double _getDefaultPointAttributeValue(int attributeIndex, int ordinate) { + return m_defaultPointAttributes[_getPointAttributeOffset(attributeIndex) + + ordinate]; + } + + /** + * + * Returns an offset to the first ordinate of the given attribute. This + * method is used for the cases when one wants to have a packed array of + * ordinates of all attributes, i.e.: X, Y, Z, ID, TEXTURE2D.u, TEXTURE2D.v + */ + int _getPointAttributeOffset(int attributeIndex) { + return m_pointAttributeOffsets[attributeIndex]; + } + + int _getPointAttributeOffsetFromSemantics(int semantics) { + return m_pointAttributeOffsets[getAttributeIndex(semantics)]; + } + + int _getTotalComponents() { + return m_defaultPointAttributes.length; + } + + @Override + public int hashCode() { + return m_hash; + } + + int _getSemanticsImpl(int attributeIndex) { + return m_semantics[attributeIndex]; + } + + // TODO: clone, equald, hashcode - whats really needed? + +} diff --git a/src/com/esri/core/geometry/VertexDescriptionDesignerImpl.java b/src/com/esri/core/geometry/VertexDescriptionDesignerImpl.java new file mode 100644 index 00000000..d7570b0c --- /dev/null +++ b/src/com/esri/core/geometry/VertexDescriptionDesignerImpl.java @@ -0,0 +1,213 @@ +/* + Copyright 1995-2013 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ + +package com.esri.core.geometry; + +/** + * This factory class allows to describe and create a VertexDescription + * instance. + */ +class VertexDescriptionDesignerImpl extends VertexDescription { + + /** + * Designer default constructor produces XY vertex description (POSITION + * semantics only). + */ + public VertexDescriptionDesignerImpl() { + super(); + m_semantics = new int[Semantics.MAXSEMANTICS]; + m_semantics[0] = Semantics.POSITION; + m_attributeCount = 1; + + m_semanticsToIndexMap = new int[Semantics.MAXSEMANTICS]; + + for (int i = 0; i < Semantics.MAXSEMANTICS; i++) + m_semanticsToIndexMap[i] = -1; + + m_semanticsToIndexMap[m_semantics[0]] = 0; + + m_bModified = true; + } + + /** + * Creates description designer and initializes it from the given + * description. Use this to add or remove attributes from the description. + */ + public VertexDescriptionDesignerImpl(VertexDescription other) { + super(other.hashCode(), other); + m_bModified = true; + } + + /** + * Adds a new attribute to the VertexDescription. + * + * @param semantics + * Attribute semantics. + */ + public void addAttribute(int semantics) { + if (hasAttribute(semantics)) + return; + + m_semanticsToIndexMap[semantics] = 0;// assign a value >= 0 to mark it + // as existing + _initMapping(); + } + + /** + * Removes given attribute. + * + * @param semantics + * Attribute semantics. + */ + void removeAttribute(int semantics) { + + if (semantics == Semantics.POSITION) + throw new IllegalArgumentException( + "Position attribue cannot be removed");// not allowed to + // remove the xy + + if (!hasAttribute(semantics)) + return; + + m_semanticsToIndexMap[semantics] = -1;// assign a value < 0 to mark it + // as removed + _initMapping(); + } + + /** + * Removes all attributes from the designer with exception of the POSITION + * attribute. + */ + public void reset() { + m_semantics[0] = Semantics.POSITION; + m_attributeCount = 1; + + for (int i : m_semanticsToIndexMap) + m_semanticsToIndexMap[i] = -1; + + m_semanticsToIndexMap[m_semantics[0]] = 0; + m_bModified = true; + } + + /** + * Returns a VertexDescription corresponding to the vertex design.
+ * Note: the same instance of VertexDescription will be returned each time + * for the same same set of attributes and attribute properties.
+ * The method searches for the VertexDescription in a global hash table. If + * found, it is returned. Else, a new instance of the VertexDescription is + * added to the has table and returned. + */ + public VertexDescription getDescription() { + VertexDescriptionHash vdhash = VertexDescriptionHash.getInstance(); + VertexDescriptionDesignerImpl vdd = this; + return vdhash.add(vdd); + } + + /** + * Returns a default VertexDescription that has X and Y coordinates only. + */ + static VertexDescription getDefaultDescriptor2D() { + VertexDescriptionHash vdhash = VertexDescriptionHash.getInstance(); + VertexDescription vd = vdhash.getVD2D(); + return vd; + } + + /** + * Returns a default VertexDescription that has X, Y, and Z coordinates only + */ + static VertexDescription getDefaultDescriptor3D() { + VertexDescriptionHash vdhash = VertexDescriptionHash.getInstance(); + VertexDescription vd = vdhash.getVD3D(); + return vd; + } + + VertexDescription _createInternal() { + int hash = hashCode(); + VertexDescription vd = new VertexDescription(hash, this); + return vd; + } + + protected boolean m_bModified; + + protected void _initMapping() { + m_attributeCount = 0; + // FIXME native has for loop for (int i = 0, j = 0; i < + // Semantics.MAXSEMANTICS + 1; i++) + for (int i = 0, j = 0; i < Semantics.MAXSEMANTICS; i++) { + if (m_semanticsToIndexMap[i] >= 0) { + m_semantics[j] = i; + m_semanticsToIndexMap[i] = j; + j++; + m_attributeCount++; + } + } + + m_bModified = true; + } + + @Override + public int hashCode() { + if (m_bModified) { + m_hash = calculateHashImpl(); + m_bModified = false; + } + + return m_hash; + } + + @Override + public boolean equals(Object _other) { + if (_other == null) + return false; + if (_other == this) + return true; + if (_other.getClass() != getClass()) + return false; + VertexDescriptionDesignerImpl other = (VertexDescriptionDesignerImpl) (_other); + if (other.getAttributeCount() != getAttributeCount()) + return false; + + for (int i = 0; i < m_attributeCount; i++) { + if (m_semantics[i] != other.m_semantics[i]) + return false; + } + if (m_bModified != other.m_bModified) + return false; + + return true; + } + + public boolean isDesignerFor(VertexDescription vd) { + if (vd.getAttributeCount() != getAttributeCount()) + return false; + + for (int i = 0; i < m_attributeCount; i++) { + if (m_semantics[i] != vd.m_semantics[i]) + return false; + } + + return true; + } + +} diff --git a/src/com/esri/core/geometry/VertexDescriptionHash.java b/src/com/esri/core/geometry/VertexDescriptionHash.java new file mode 100644 index 00000000..c9f9e137 --- /dev/null +++ b/src/com/esri/core/geometry/VertexDescriptionHash.java @@ -0,0 +1,109 @@ +/* + Copyright 1995-2013 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ + +package com.esri.core.geometry; + +import com.esri.core.geometry.VertexDescription.Semantics; +import java.lang.ref.WeakReference; +import java.util.HashMap; +import java.util.Map; + +/** + * A hash object singleton that stores all VertexDescription instances via + * WeakReference. The purpose of the class is to keep track of created + * VertexDescription instances to prevent duplicates. + */ +final class VertexDescriptionHash { + Map> map = new HashMap>(); + + private static VertexDescription m_vd2D; + + private static VertexDescription m_vd3D; + + private static final VertexDescriptionHash INSTANCE = new VertexDescriptionHash(); + + private VertexDescriptionHash() { + VertexDescriptionDesignerImpl vdd2D = new VertexDescriptionDesignerImpl(); + add(vdd2D); + VertexDescriptionDesignerImpl vdd3D = new VertexDescriptionDesignerImpl(); + vdd3D.addAttribute(Semantics.Z); + add(vdd3D); + } + + public static VertexDescriptionHash getInstance() { + return INSTANCE; + } + + public VertexDescription getVD2D() { + return m_vd2D; + } + + public VertexDescription getVD3D() { + return m_vd3D; + } + + synchronized public VertexDescription add(VertexDescriptionDesignerImpl vdd) { + // Firstly quick test for 2D/3D descriptors. + int h = vdd.hashCode(); + + if ((m_vd2D != null) && m_vd2D.hashCode() == h) { + if (vdd.isDesignerFor(m_vd2D)) + return m_vd2D; + } + + if ((m_vd3D != null) && (m_vd3D.hashCode() == h)) { + if (vdd.isDesignerFor(m_vd3D)) + return m_vd3D; + } + + // Now search in the hash. + + VertexDescription vd = null; + if (map.containsKey(h)) { + WeakReference vdweak = map.get(h); + vd = vdweak.get(); + if (vd == null) // GC'd VertexDescription + map.remove(h); + } + + if (vd == null) { // either not in map to begin with, or has been GC'd + vd = vdd._createInternal(); + + if (vd.getAttributeCount() == 1) { + m_vd2D = vd; + } else if ((vd.getAttributeCount() == 2) + && (vd.getSemantics(1) == Semantics.Z)) { + m_vd3D = vd; + } else { + WeakReference vdweak = new WeakReference( + vd); + + map.put(h, vdweak); + } + + } + + return vd; + } +} diff --git a/src/com/esri/core/geometry/WkbByteOrder.java b/src/com/esri/core/geometry/WkbByteOrder.java new file mode 100644 index 00000000..9f474f20 --- /dev/null +++ b/src/com/esri/core/geometry/WkbByteOrder.java @@ -0,0 +1,30 @@ +/* + Copyright 1995-2013 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ + +package com.esri.core.geometry; + +interface WkbByteOrder { + public static final int wkbXDR = 0; // Big Endian + public static final int wkbNDR = 1; // Little Endian +} diff --git a/src/com/esri/core/geometry/WkbExportFlags.java b/src/com/esri/core/geometry/WkbExportFlags.java new file mode 100644 index 00000000..77db5e00 --- /dev/null +++ b/src/com/esri/core/geometry/WkbExportFlags.java @@ -0,0 +1,38 @@ +/* + Copyright 1995-2013 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ + +package com.esri.core.geometry; + +public interface WkbExportFlags { + public static final int wkbExportDefaults = 0; + public static final int wkbExportPoint = 1; + public static final int wkbExportMultiPoint = 2; + public static final int wkbExportLineString = 4; + public static final int wkbExportMultiLineString = 8; + public static final int wkbExportPolygon = 16; + public static final int wkbExportMultiPolygon = 32; + public static final int wkbExportStripZs = 64; + public static final int wkbExportStripMs = 128; + public static final int wkbExportFailIfNotSimple = 4096; +} diff --git a/src/com/esri/core/geometry/WkbGeometryType.java b/src/com/esri/core/geometry/WkbGeometryType.java new file mode 100644 index 00000000..07d142d0 --- /dev/null +++ b/src/com/esri/core/geometry/WkbGeometryType.java @@ -0,0 +1,85 @@ +/* + Copyright 1995-2013 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ + +package com.esri.core.geometry; + +interface WkbGeometryType { + public static final int wkbPoint = 1; + + public static final int wkbPointZ = 1001; + + public static final int wkbPointM = 2001; + + public static final int wkbPointZM = 3001; + + public static final int wkbLineString = 2; + + public static final int wkbLineStringZ = 1002; + + public static final int wkbLineStringM = 2002; + + public static final int wkbLineStringZM = 3002; + + public static final int wkbPolygon = 3; + + public static final int wkbPolygonZ = 1003; + + public static final int wkbPolygonM = 2003; + + public static final int wkbPolygonZM = 3003; + + public static final int wkbMultiPoint = 4; + + public static final int wkbMultiPointZ = 1004; + + public static final int wkbMultiPointM = 2004; + + public static final int wkbMultiPointZM = 3004; + + public static final int wkbMultiLineString = 5; + + public static final int wkbMultiLineStringZ = 1005; + + public static final int wkbMultiLineStringM = 2005; + + public static final int wkbMultiLineStringZM = 3005; + + public static final int wkbMultiPolygon = 6; + + public static final int wkbMultiPolygonZ = 1006; + + public static final int wkbMultiPolygonM = 2006; + + public static final int wkbMultiPolygonZM = 3006; + + public static final int wkbGeometryCollection = 7; + + public static final int wkbGeometryCollectionZ = 1007; + + public static final int wkbGeometryCollectionM = 2007; + + public static final int wkbGeometryCollectionZM = 3007; + + public static final int wkbMultiPatch = 8; +} diff --git a/src/com/esri/core/geometry/WkbImportFlags.java b/src/com/esri/core/geometry/WkbImportFlags.java new file mode 100644 index 00000000..b6507bf0 --- /dev/null +++ b/src/com/esri/core/geometry/WkbImportFlags.java @@ -0,0 +1,30 @@ +/* + Copyright 1995-2013 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ + +package com.esri.core.geometry; + +interface WkbImportFlags { + public static final int wkbImportDefaults = 0; + public static final int wkbImportNonTrusted = 2; +} diff --git a/src/com/esri/core/geometry/Wkid.java b/src/com/esri/core/geometry/Wkid.java new file mode 100644 index 00000000..9ecf2bd7 --- /dev/null +++ b/src/com/esri/core/geometry/Wkid.java @@ -0,0 +1,182 @@ +/* + Copyright 1995-2013 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ + +package com.esri.core.geometry; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.util.ArrayList; +import java.util.HashMap; + +public final class Wkid { + static ArrayList readTolerances(String resourceName) { + try { + ArrayList tolerances = new ArrayList(); + InputStream input = Wkid.class.getResourceAsStream(resourceName); + BufferedReader reader = new BufferedReader(new InputStreamReader( + input)); + while (true) { + String s = reader.readLine(); + if (s == null) + break; + int sep = s.indexOf('\t', 0); + String id_s = s.substring(0, sep); + int tol_index = Integer.parseInt(id_s); + if (tol_index != tolerances.size()) + throw new IllegalArgumentException("Wkid.readTolerances"); + String tol_val = s.substring(sep + 1, s.length()); + tolerances.add(Double.parseDouble(tol_val)); + } + + return tolerances; + } catch (IOException ex) { + + } + return null; + } + + static HashMap readToleranceMap(String resourceName) { + try { + HashMap hashMap = new HashMap( + 600); + InputStream input = Wkid.class.getResourceAsStream(resourceName); + BufferedReader reader = new BufferedReader(new InputStreamReader( + input)); + while (true) { + String s = reader.readLine(); + if (s == null) + break; + int sep = s.indexOf('\t', 0); + String id_s = s.substring(0, sep); + int wkid = Integer.parseInt(id_s); + String id_t = s.substring(sep + 1, s.length()); + int tol = Integer.parseInt(id_t); + hashMap.put(wkid, tol); + } + return hashMap; + } catch (IOException ex) { + } + return null; + + } + + static ArrayList m_gcs_tolerances = readTolerances("gcs_tolerances.txt"); + static ArrayList m_pcs_tolerances = readTolerances("pcs_tolerances.txt"); + static HashMap m_gcsToTol = readToleranceMap("gcs_id_to_tolerance.txt"); + static HashMap m_pcsToTol = readToleranceMap("pcs_id_to_tolerance.txt"); + static HashMap m_wkid_to_new; + static HashMap m_wkid_to_old; + static { + try { + m_wkid_to_new = new HashMap(100); + m_wkid_to_old = new HashMap(100); + { + InputStream input = Wkid.class + .getResourceAsStream("new_to_old_wkid.txt"); + BufferedReader reader = new BufferedReader( + new InputStreamReader(input)); + while (true) { + String s = reader.readLine(); + if (s == null) + break; + s = s.trim(); + if (s.length() == 0) + continue; + int sep = s.indexOf('\t', 0); + String id_s = s.substring(0, sep); + int wkid_new = Integer.parseInt(id_s); + String id_t = s.substring(sep + 1, s.length()); + int wkid_old = Integer.parseInt(id_t); + m_wkid_to_new.put(wkid_old, wkid_new); + m_wkid_to_old.put(wkid_new, wkid_old); + } + } + { + InputStream input = Wkid.class + .getResourceAsStream("intermediate_to_old_wkid.txt"); + BufferedReader reader = new BufferedReader( + new InputStreamReader(input)); + while (true) { + String s = reader.readLine(); + if (s == null) + break; + s = s.trim(); + if (s.length() == 0) + continue; + int sep = s.indexOf('\t', 0); + String id_s = s.substring(0, sep); + int wkid = Integer.parseInt(id_s); + String id_t = s.substring(sep + 1, s.length()); + int wkid_old = Integer.parseInt(id_t); + m_wkid_to_old.put(wkid, wkid_old); + m_wkid_to_new.put(wkid, m_wkid_to_new.get(wkid_old)); + } + } + + } catch (IOException ex) { + + } + } + + public static double find_tolerance_from_wkid(int wkid) { + double tol = find_tolerance_from_wkid_helper(wkid); + if (tol == 1e38) { + int old = wkid_to_old(wkid); + if (old != wkid) + tol = find_tolerance_from_wkid_helper(wkid); + if (tol == 1e38) + return 1e-10; + } + + return tol; + } + + private static double find_tolerance_from_wkid_helper(int wkid) { + if (m_gcsToTol.containsKey(wkid)) { + return m_gcs_tolerances.get(m_gcsToTol.get(wkid)); + } + + if (m_pcsToTol.containsKey(wkid)) { + return m_pcs_tolerances.get(m_pcsToTol.get(wkid)); + } + + return 1e38; + } + + public static int wkid_to_new(int wkid) { + if (m_wkid_to_new.containsKey(wkid)) { + return m_wkid_to_new.get(wkid); + } + return wkid; + } + + public static int wkid_to_old(int wkid) { + if (m_wkid_to_old.containsKey(wkid)) { + return m_wkid_to_old.get(wkid); + } + return wkid; + } +} diff --git a/src/com/esri/core/geometry/Wkt.java b/src/com/esri/core/geometry/Wkt.java new file mode 100644 index 00000000..08db42f1 --- /dev/null +++ b/src/com/esri/core/geometry/Wkt.java @@ -0,0 +1,111 @@ +/* + Copyright 1995-2013 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ + +package com.esri.core.geometry; + +final class Wkt { + public static double find_tolerance_from_wkt(String buffer) { + double tolerance = -1.0; + + if (buffer != null && buffer.length() > 0) { + int n1, n2; + + n1 = buffer.indexOf("PROJCS"); + if (n1 >= 0) { + double factor = 0.0; + + n1 = buffer.lastIndexOf("UNIT"); + if (n1 >= 0) { + n1 = buffer.indexOf(',', n1 + 4); + if (n1 > 0) { + n1++; + n2 = buffer.indexOf(']', n1 + 1); + if (n2 > 0) { + try { + factor = Double.parseDouble(buffer.substring( + n1, n2)); + } catch (NumberFormatException e) { + factor = 0.0; + } + } + } + } + + if (factor > 0.0) + tolerance = (0.001 / factor); + } + + else { + n1 = buffer.indexOf("GEOGCS"); + if (n1 >= 0) { + double axis = 0.0; + double factor = 0.0; + + n1 = buffer.indexOf("SPHEROID", n1 + 6); + if (n1 > 0) { + n1 = buffer.indexOf(',', n1 + 8); + if (n1 > 0) { + n1++; + n2 = buffer.indexOf(',', n1 + 1); + if (n2 > 0) { + try { + axis = Double.parseDouble(buffer.substring( + n1, n2)); + } catch (NumberFormatException e) { + axis = 0.0; + } + } + + if (axis > 0.0) { + n1 = buffer.indexOf("UNIT", n2 + 1); + if (n1 >= 0) { + n1 = buffer.indexOf(',', n1 + 4); + if (n1 > 0) { + n1++; + n2 = buffer.indexOf(']', n1 + 1); + if (n2 > 0) { + try { + factor = Double + .parseDouble(buffer + .substring(n1, + n2)); + } catch (NumberFormatException e) { + factor = 0.0; + } + } + } + } + } + } + } + + if (axis > 0.0 && factor > 0.0) + tolerance = (0.001 / (axis * factor)); + } + } + } + + return tolerance; + } +} diff --git a/src/com/esri/core/geometry/WktExportFlags.java b/src/com/esri/core/geometry/WktExportFlags.java new file mode 100644 index 00000000..981020a9 --- /dev/null +++ b/src/com/esri/core/geometry/WktExportFlags.java @@ -0,0 +1,44 @@ +/* + Copyright 1995-2013 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ +package com.esri.core.geometry; + +public interface WktExportFlags { + public static final int wktExportDefaults = 0; + public static final int wktExportPoint = 1; + public static final int wktExportMultiPoint = 2; + public static final int wktExportLineString = 4; + public static final int wktExportMultiLineString = 8; + public static final int wktExportPolygon = 16; + public static final int wktExportMultiPolygon = 32; + public static final int wktExportStripZs = 64; + public static final int wktExportStripMs = 128; + public static final int wktExportFailIfNotSimple = 4096; + public static final int wktExportPrecision16 = 0x2000; + public static final int wktExportPrecision15 = 0x4000; + public static final int wktExportPrecision14 = 0x6000; + public static final int wktExportPrecision13 = 0x8000; + public static final int wktExportPrecision12 = 0xa000; + public static final int wktExportPrecision11 = 0xc000; + public static final int wktExportPrecision10 = 0xe000; +} diff --git a/src/com/esri/core/geometry/WktImportFlags.java b/src/com/esri/core/geometry/WktImportFlags.java new file mode 100644 index 00000000..f46548be --- /dev/null +++ b/src/com/esri/core/geometry/WktImportFlags.java @@ -0,0 +1,29 @@ +/* + Copyright 1995-2013 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ +package com.esri.core.geometry; + +public interface WktImportFlags { + static final int wktImportDefaults = 0; + static final int wktImportNonTrusted = 2; +} diff --git a/src/com/esri/core/geometry/WktParser.java b/src/com/esri/core/geometry/WktParser.java new file mode 100644 index 00000000..91f0198d --- /dev/null +++ b/src/com/esri/core/geometry/WktParser.java @@ -0,0 +1,705 @@ +/* + Copyright 1995-2013 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ +package com.esri.core.geometry; + +final class WktParser { + interface WktToken { + static final int not_available = 0; + static final int empty = 50; + static final int left_paren = 51; + static final int right_paren = 52; + static final int x_literal = 0x80000000; + static final int y_literal = 0x40000000; + static final int z_literal = 0x20000000; + static final int m_literal = 0x10000000; + static final int point = 1; + static final int linestring = 2; + static final int polygon = 3; + static final int multipoint = 4; + static final int multilinestring = 5; + static final int multipolygon = 6; + static final int geometrycollection = 7; + static final int attribute_z = 1000; + static final int attribute_m = 2000; + static final int attribute_zm = 3000; + } + + WktParser() { + } + + WktParser(String string) { + resetParser(string); + } + + void resetParser(String string) { + if (m_function_stack == null) + m_function_stack = new AttributeStreamOfInt32(0); + + reset_(); + m_wkt_string = string; + } + + int nextToken() { + switch (m_function_stack.getLast()) { + case State.xLiteral: + xLiteral_(); + break; + case State.yLiteral: + yLiteral_(); + break; + case State.zLiteral: + zLiteral_(); + break; + case State.mLiteral: + mLiteral_(); + break; + case State.pointStart: + pointStart_(); + break; + case State.pointStartAlt: + pointStartAlt_(); + break; + case State.pointEnd: + pointEnd_(); + break; + case State.lineStringStart: + lineStringStart_(); + break; + case State.lineStringEnd: + lineStringEnd_(); + break; + case State.multiPointStart: + multiPointStart_(); + break; + case State.multiPointEnd: + multiPointEnd_(); + break; + case State.polygonStart: + polygonStart_(); + break; + case State.polygonEnd: + polygonEnd_(); + break; + case State.multiLineStringStart: + multiLineStringStart_(); + break; + case State.multiLineStringEnd: + multiLineStringEnd_(); + break; + case State.multiPolygonStart: + multiPolygonStart_(); + break; + case State.multiPolygonEnd: + multiPolygonEnd_(); + break; + case State.geometryCollectionStart: + geometryCollectionStart_(); + break; + case State.geometryCollectionEnd: + geometryCollectionEnd_(); + break; + case State.accept: + accept_(); + break; + case State.geometry: + geometry_(); + break; + case State.attributes: + attributes_(); + break; + } + + return m_current_token_type; + } + + double currentNumericLiteral() { + if (((int) m_current_token_type & (int) Number.signed_numeric_literal) == 0) + throw new GeometryException("runtime error"); + + if (m_b_nan) + return NumberUtils.TheNaN; + + double value = Double.parseDouble(m_wkt_string.substring(m_start_token, + m_end_token)); + return value; + } + + int currentToken() { + return m_current_token_type; + } + + boolean hasZs() { + return m_b_has_zs; + } + + boolean hasMs() { + return m_b_has_ms; + } + + private String m_wkt_string; + private int m_start_token; + private int m_end_token; + private int m_current_token_type; + + private boolean m_b_has_zs; + private boolean m_b_has_ms; + private boolean m_b_check_consistent_attributes; + private boolean m_b_nan; + + private AttributeStreamOfInt32 m_function_stack; + + private interface State { + static final int xLiteral = 0; + static final int yLiteral = 1; + static final int zLiteral = 2; + static final int mLiteral = 3; + static final int pointStart = 4; + static final int pointStartAlt = 5; + static final int pointEnd = 6; + static final int lineStringStart = 7; + static final int lineStringEnd = 8; + static final int multiPointStart = 9; + static final int multiPointEnd = 10; + static final int polygonStart = 11; + static final int polygonEnd = 12; + static final int multiLineStringStart = 13; + static final int multiLineStringEnd = 14; + static final int multiPolygonStart = 15; + static final int multiPolygonEnd = 16; + static final int geometryCollectionStart = 17; + static final int geometryCollectionEnd = 18; + static final int accept = 19; + static final int geometry = 20; + static final int attributes = 21; + } + + private interface Number { + static final int signed_numeric_literal = WktToken.x_literal + | WktToken.y_literal | WktToken.z_literal | WktToken.m_literal; + } + + private void reset_() { + m_function_stack.add(State.accept); + m_function_stack.add(State.geometry); + m_start_token = -1; + m_end_token = 0; + m_current_token_type = WktToken.not_available; + m_b_has_zs = false; + m_b_has_ms = false; + m_b_check_consistent_attributes = false; + m_b_nan = false; + } + + private void accept_() { + m_start_token = m_end_token; + m_current_token_type = WktToken.not_available; + } + + private void geometry_() { + skipWhiteSpace_(); + m_start_token = m_end_token; + m_function_stack.removeLast(); + + if (m_start_token + 5 <= m_wkt_string.length() + && m_wkt_string.regionMatches(true, m_start_token, "points", 0, + 5)) { + m_end_token = m_start_token + 5; + m_current_token_type = WktToken.point; + m_function_stack.add(State.pointStart); + } else if (m_start_token + 10 <= m_wkt_string.length() + && m_wkt_string.regionMatches(true, m_start_token, + "linestring", 0, 10)) { + m_end_token = m_start_token + 10; + m_current_token_type = WktToken.linestring; + m_function_stack.add(State.lineStringStart); + } else if (m_start_token + 10 <= m_wkt_string.length() + && m_wkt_string.regionMatches(true, m_start_token, + "multipoint", 0, 10)) { + m_end_token = m_start_token + 10; + m_current_token_type = WktToken.multipoint; + m_function_stack.add(State.multiPointStart); + } else if (m_start_token + 7 <= m_wkt_string.length() + && m_wkt_string.regionMatches(true, m_start_token, "polygon", + 0, 7)) { + m_end_token = m_start_token + 7; + m_current_token_type = WktToken.polygon; + m_function_stack.add(State.polygonStart); + } else if (m_start_token + 15 <= m_wkt_string.length() + && m_wkt_string.regionMatches(true, m_start_token, + "multilinestring", 0, 15)) { + m_end_token = m_start_token + 15; + m_current_token_type = WktToken.multilinestring; + m_function_stack.add(State.multiLineStringStart); + } else if (m_start_token + 12 <= m_wkt_string.length() + && m_wkt_string.regionMatches(true, m_start_token, + "multipolygon", 0, 12)) { + m_end_token = m_start_token + 12; + m_current_token_type = WktToken.multipolygon; + m_function_stack.add(State.multiPolygonStart); + } else if (m_start_token + 18 <= m_wkt_string.length() + && m_wkt_string.regionMatches(true, m_start_token, + "geometrycollection", 0, 18)) { + m_end_token = m_start_token + 18; + m_current_token_type = WktToken.geometrycollection; + m_function_stack.add(State.geometryCollectionStart); + } else { + throw new IllegalArgumentException(); + } + + m_function_stack.add(State.attributes); + } + + private void attributes_() { + skipWhiteSpace_(); + m_start_token = m_end_token; + m_function_stack.removeLast(); + + // Z and M is not allowed to have a space between them + boolean b_has_zs = false, b_has_ms = false; + + if (m_wkt_string.charAt(m_end_token) == 'z' + || m_wkt_string.charAt(m_end_token) == 'Z') { + b_has_zs = true; + + if (++m_end_token >= m_wkt_string.length()) + throw new IllegalArgumentException(); + } + + if (m_wkt_string.charAt(m_end_token) == 'm' + || m_wkt_string.charAt(m_end_token) == 'M') { + b_has_ms = true; + + if (++m_end_token >= m_wkt_string.length()) + throw new IllegalArgumentException(); + } + + if (m_b_check_consistent_attributes) { + if (b_has_zs != m_b_has_zs || b_has_ms != m_b_has_ms) + throw new IllegalArgumentException(); + } + + m_b_has_zs = b_has_zs; + m_b_has_ms = b_has_ms; + + if (m_b_has_zs || m_b_has_ms) { + if (m_b_has_zs && !m_b_has_ms) + m_current_token_type = WktToken.attribute_z; + else if (m_b_has_ms && !m_b_has_zs) + m_current_token_type = WktToken.attribute_m; + else + m_current_token_type = WktToken.attribute_zm; + } else { + nextToken(); + } + } + + private void geometryCollectionStart_() { + skipWhiteSpace_(); + m_start_token = m_end_token; + m_b_check_consistent_attributes = true; + + if (empty_()) { + m_function_stack.removeLast(); + } else if (leftParen_()) { + m_function_stack.removeLast(); + m_function_stack.add(State.geometryCollectionEnd); + m_function_stack.add(State.geometry); + } else { + throw new IllegalArgumentException(); + } + } + + private void geometryCollectionEnd_() { + skipWhiteSpace_(); + m_start_token = m_end_token; + + if (comma_()) { + m_function_stack.add(State.geometry); + geometry_(); + } else if (rightParen_()) { + m_function_stack.removeLast(); + } else { + throw new IllegalArgumentException(); + } + } + + private void multiPolygonStart_() { + skipWhiteSpace_(); + m_start_token = m_end_token; + + if (empty_()) { + m_function_stack.removeLast(); + } else if (leftParen_()) { + m_function_stack.removeLast(); + m_function_stack.add(State.multiPolygonEnd); + m_function_stack.add(State.polygonStart); + } else { + throw new IllegalArgumentException(); + } + } + + private void multiPolygonEnd_() { + skipWhiteSpace_(); + m_start_token = m_end_token; + + if (comma_()) { + m_function_stack.add(State.polygonStart); + polygonStart_(); + } else if (rightParen_()) { + m_function_stack.removeLast(); + } else { + throw new IllegalArgumentException(); + } + } + + private void multiLineStringStart_() { + skipWhiteSpace_(); + m_start_token = m_end_token; + + if (empty_()) { + m_function_stack.removeLast(); + } else if (leftParen_()) { + m_function_stack.removeLast(); + m_function_stack.add(State.multiLineStringEnd); + m_function_stack.add(State.lineStringStart); + } else { + throw new IllegalArgumentException(); + } + } + + private void multiLineStringEnd_() { + skipWhiteSpace_(); + m_start_token = m_end_token; + + if (comma_()) { + m_function_stack.add(State.lineStringStart); + lineStringStart_(); + } else if (rightParen_()) { + m_function_stack.removeLast(); + } else { + throw new IllegalArgumentException(); + } + } + + private void multiPointStart_() { + skipWhiteSpace_(); + m_start_token = m_end_token; + + if (empty_()) { + m_function_stack.removeLast(); + } else if (leftParen_()) { + m_function_stack.removeLast(); + m_function_stack.add(State.multiPointEnd); + m_function_stack.add(State.pointStartAlt); + } else { + throw new IllegalArgumentException(); + } + } + + private void multiPointEnd_() { + skipWhiteSpace_(); + m_start_token = m_end_token; + + if (comma_()) { + m_function_stack.add(State.pointStart); + pointStart_(); + } else if (rightParen_()) { + m_function_stack.removeLast(); + } else { + throw new IllegalArgumentException(); + } + } + + private void polygonStart_() { + skipWhiteSpace_(); + m_start_token = m_end_token; + + if (empty_()) { + m_function_stack.removeLast(); + } else if (leftParen_()) { + m_function_stack.removeLast(); + m_function_stack.add(State.polygonEnd); + m_function_stack.add(State.lineStringStart); + } else { + throw new IllegalArgumentException(); + } + } + + private void polygonEnd_() { + skipWhiteSpace_(); + m_start_token = m_end_token; + + if (comma_()) { + m_function_stack.add(State.lineStringStart); + lineStringStart_(); + } else if (rightParen_()) { + m_function_stack.removeLast(); + } else { + throw new IllegalArgumentException(); + } + } + + private void lineStringStart_() { + skipWhiteSpace_(); + m_start_token = m_end_token; + + if (empty_()) { + m_function_stack.removeLast(); + } else if (leftParen_()) { + m_function_stack.removeLast(); + m_function_stack.add(State.lineStringEnd); + m_function_stack.add(State.xLiteral); + } else { + throw new IllegalArgumentException(); + } + } + + private void lineStringEnd_() { + skipWhiteSpace_(); + m_start_token = m_end_token; + + if (comma_()) { + m_function_stack.add(State.xLiteral); + xLiteral_(); + } else if (rightParen_()) { + m_function_stack.removeLast(); + } else { + throw new IllegalArgumentException(); + } + } + + private void pointStart_() { + skipWhiteSpace_(); + m_start_token = m_end_token; + + if (empty_()) { + m_function_stack.removeLast(); + } else if (leftParen_()) { + m_function_stack.removeLast(); + m_function_stack.add(State.pointEnd); + m_function_stack.add(State.xLiteral); + } else { + throw new IllegalArgumentException(); + } + } + + private void pointStartAlt_() { + skipWhiteSpace_(); + m_start_token = m_end_token; + + if (empty_()) {// ogc standard + m_function_stack.removeLast(); + } else if (leftParen_()) {// ogc standard + m_function_stack.removeLast(); + m_function_stack.add(State.pointEnd); + m_function_stack.add(State.xLiteral); + } else {// not ogc standard. treat as linestring + m_function_stack.removeLast(); + m_function_stack.removeLast(); + m_function_stack.add(State.lineStringEnd); + m_function_stack.add(State.xLiteral); + nextToken(); + } + } + + private void pointEnd_() { + skipWhiteSpace_(); + m_start_token = m_end_token; + + if (rightParen_()) { + m_function_stack.removeLast(); + } else { + throw new IllegalArgumentException(); + } + } + + private void xLiteral_() { + signedNumericLiteral_(); + m_current_token_type = WktToken.x_literal; + m_function_stack.removeLast(); + m_function_stack.add(State.yLiteral); + } + + private void yLiteral_() { + signedNumericLiteral_(); + m_current_token_type = WktToken.y_literal; + m_function_stack.removeLast(); + + if (m_b_has_zs) + m_function_stack.add(State.zLiteral); + else if (m_b_has_ms) + m_function_stack.add(State.mLiteral); + } + + private void zLiteral_() { + signedNumericLiteral_(); + m_current_token_type = WktToken.z_literal; + m_function_stack.removeLast(); + + if (m_b_has_ms) + m_function_stack.add(State.mLiteral); + } + + private void mLiteral_() { + signedNumericLiteral_(); + m_current_token_type = WktToken.m_literal; + m_function_stack.removeLast(); + } + + private boolean nan_() { + if (m_wkt_string.regionMatches(true, m_start_token, "nan", 0, 3)) { + m_end_token += 3; + m_b_nan = true; + return true; + } + + m_b_nan = false; + return false; + } + + private void sign_() { + // Optional - or + sign + if (m_wkt_string.charAt(m_end_token) == '-' + || m_wkt_string.charAt(m_end_token) == '+') { + if (++m_end_token >= m_wkt_string.length()) + throw new IllegalArgumentException(); + } + } + + private void signedNumericLiteral_() { + skipWhiteSpace_(); + m_start_token = m_end_token; + + if (nan_()) + return; + + sign_(); // Optional + unsignedNumericLiteral_(); + } + + private void unsignedNumericLiteral_() { + exactNumericLiteral_(); + exp_(); // Optional + } + + private void exactNumericLiteral_() { + if (Character.isDigit(m_wkt_string.charAt(m_end_token))) { + digits_(); + + // Optional + if (m_wkt_string.charAt(m_end_token) == '.') { + if (++m_end_token >= m_wkt_string.length()) + throw new IllegalArgumentException(); + + // Optional + if (Character.isDigit(m_wkt_string.charAt(m_end_token))) + digits_(); + } + } else if (m_wkt_string.charAt(m_end_token) == '.') { + if (++m_end_token >= m_wkt_string.length()) + throw new IllegalArgumentException(); + + if (!Character.isDigit(m_wkt_string.charAt(m_end_token))) + throw new IllegalArgumentException(); + + digits_(); + } else { + throw new IllegalArgumentException(); + } + } + + private void digits_() { + do { + if (++m_end_token >= m_wkt_string.length()) + throw new IllegalArgumentException(); + + } while (Character.isDigit(m_wkt_string.charAt(m_end_token))); + } + + private void exp_() { + // This is an optional state + if (m_wkt_string.charAt(m_end_token) == 'e' + || m_wkt_string.charAt(m_end_token) == 'E') { + if (++m_end_token >= m_wkt_string.length()) + throw new IllegalArgumentException(); + + sign_(); // optional + + if (!Character.isDigit(m_wkt_string.charAt(m_end_token))) + throw new IllegalArgumentException(); + + digits_(); + } + } + + private void skipWhiteSpace_() { + if (m_end_token >= m_wkt_string.length()) + throw new IllegalArgumentException(); + + while (Character.isWhitespace(m_wkt_string.charAt(m_end_token))) { + if (++m_end_token >= m_wkt_string.length()) + throw new IllegalArgumentException(); + } + } + + private boolean empty_() { + if (m_wkt_string.regionMatches(true, m_start_token, "empty", 0, 5)) { + m_end_token += 5; + m_current_token_type = WktToken.empty; + return true; + } + + return false; + } + + private boolean comma_() { + if (m_wkt_string.charAt(m_end_token) == ',') { + m_end_token++; + return true; + } + + return false; + } + + private boolean leftParen_() { + if (m_wkt_string.charAt(m_end_token) == '(') { + m_end_token++; + m_current_token_type = WktToken.left_paren; + return true; + } + + return false; + } + + private boolean rightParen_() { + if (m_wkt_string.charAt(m_end_token) == ')') { + m_end_token++; + m_current_token_type = WktToken.right_paren; + return true; + } + + return false; + } + +} diff --git a/src/com/esri/core/geometry/gcs_id_to_tolerance.txt b/src/com/esri/core/geometry/gcs_id_to_tolerance.txt new file mode 100644 index 00000000..b6c40819 --- /dev/null +++ b/src/com/esri/core/geometry/gcs_id_to_tolerance.txt @@ -0,0 +1,671 @@ +4001 0 +4002 0 +4003 0 +4004 0 +4005 0 +4006 0 +4007 0 +4008 0 +4009 0 +4010 0 +4011 0 +4012 0 +4013 0 +4014 0 +4015 0 +4016 0 +4018 0 +4019 0 +4020 0 +4021 0 +4022 0 +4023 0 +4024 0 +4025 0 +4027 0 +4028 0 +4029 0 +4031 0 +4032 0 +4033 0 +4034 0 +4035 0 +4036 0 +4042 0 +4044 0 +4045 0 +4046 0 +4047 0 +4052 0 +4053 0 +4054 0 +4075 0 +4081 0 +4120 0 +4121 0 +4122 0 +4123 0 +4124 0 +4125 0 +4126 0 +4127 0 +4128 0 +4129 0 +4130 0 +4131 0 +4132 0 +4133 0 +4134 0 +4135 0 +4136 0 +4137 0 +4138 0 +4139 0 +4140 0 +4141 0 +4142 0 +4143 0 +4144 0 +4145 0 +4146 0 +4147 0 +4148 0 +4149 0 +4150 0 +4151 0 +4152 0 +4153 0 +4154 0 +4155 0 +4156 0 +4157 0 +4158 0 +4159 0 +4160 0 +4161 0 +4162 0 +4163 0 +4164 0 +4165 0 +4166 0 +4168 0 +4170 0 +4172 0 +4173 0 +4174 0 +4175 0 +4176 0 +4178 0 +4179 0 +4180 0 +4181 0 +4182 0 +4183 0 +4184 0 +4185 0 +4188 0 +4190 0 +4191 0 +4192 0 +4193 0 +4194 0 +4195 0 +4196 0 +4198 0 +4199 0 +4200 0 +4201 0 +4202 0 +4203 0 +4204 0 +4205 0 +4206 0 +4207 0 +4208 0 +4209 0 +4210 0 +4211 0 +4212 0 +4213 0 +4214 0 +4215 0 +4216 0 +4218 0 +4219 0 +4220 0 +4221 0 +4222 0 +4224 0 +4225 0 +4226 0 +4227 0 +4228 0 +4229 0 +4230 0 +4231 0 +4232 0 +4234 0 +4235 0 +4236 0 +4237 0 +4238 0 +4239 0 +4240 0 +4241 0 +4242 0 +4243 0 +4244 0 +4245 0 +4246 0 +4247 0 +4248 0 +4249 0 +4250 0 +4251 0 +4252 0 +4253 0 +4254 0 +4255 0 +4256 0 +4257 0 +4258 0 +4259 0 +4260 0 +4261 1 +4262 0 +4263 0 +4265 0 +4266 0 +4267 0 +4269 0 +4270 0 +4271 0 +4272 0 +4273 0 +4274 0 +4275 0 +4276 0 +4277 0 +4278 0 +4279 0 +4280 0 +4281 0 +4282 0 +4283 0 +4284 0 +4285 0 +4286 0 +4288 0 +4289 0 +4291 0 +4292 0 +4293 0 +4294 0 +4295 0 +4296 0 +4297 0 +4298 0 +4299 0 +4300 0 +4301 0 +4302 0 +4303 0 +4305 1 +4306 0 +4307 0 +4308 0 +4309 0 +4310 0 +4311 0 +4312 0 +4313 0 +4314 0 +4315 0 +4316 0 +4317 0 +4318 0 +4319 0 +4322 0 +4324 0 +4326 0 +4466 0 +4469 0 +4475 0 +4483 0 +4490 0 +4555 0 +4558 0 +4600 0 +4601 0 +4602 0 +4603 0 +4604 0 +4605 0 +4606 0 +4607 0 +4608 0 +4609 0 +4610 0 +4614 0 +4619 0 +4621 0 +4622 0 +4623 0 +4624 0 +4625 0 +4627 0 +4628 0 +4629 0 +4630 0 +4631 0 +4632 0 +4633 0 +4636 0 +4637 0 +4638 0 +4639 0 +4641 0 +4642 0 +4643 0 +4644 0 +4645 0 +4646 0 +4657 0 +4659 0 +4660 0 +4661 0 +4662 0 +4663 0 +4664 0 +4665 0 +4666 0 +4667 0 +4670 0 +4671 0 +4674 0 +4676 0 +4677 0 +4678 0 +4679 0 +4680 0 +4682 0 +4683 0 +4686 0 +4687 0 +4688 0 +4689 0 +4690 0 +4691 0 +4692 0 +4693 0 +4694 0 +4695 0 +4696 0 +4697 0 +4699 0 +4700 0 +4701 0 +4702 0 +4703 0 +4704 0 +4705 0 +4706 0 +4718 0 +4720 0 +4721 0 +4723 0 +4726 0 +4737 0 +4738 0 +4740 0 +4741 0 +4742 0 +4743 0 +4744 0 +4745 0 +4746 0 +4747 0 +4748 0 +4749 0 +4750 0 +4751 0 +4752 0 +4753 0 +4754 0 +4755 0 +4756 0 +4757 0 +4759 0 +4761 0 +4763 0 +4764 0 +4765 0 +4801 0 +4802 0 +4803 0 +4804 0 +4805 0 +4806 0 +4807 1 +4808 0 +4809 0 +4810 1 +4811 1 +4812 1 +4813 0 +4814 0 +4815 0 +4816 1 +4817 0 +4818 0 +4819 1 +4820 0 +4821 1 +4823 0 +4824 0 +4901 1 +4902 1 +4903 0 +4904 0 +5228 0 +5229 0 +5233 0 +5252 0 +5264 0 +5340 0 +5354 0 +5360 0 +5371 0 +5373 0 +5381 0 +5393 0 +5464 0 +5467 0 +5489 0 +5524 0 +5527 0 +5546 0 +5561 0 +5593 0 +5681 0 +5886 0 +37001 0 +37002 0 +37003 0 +37004 0 +37005 0 +37006 0 +37007 0 +37008 0 +37201 0 +37202 0 +37203 0 +37204 0 +37205 0 +37206 0 +37207 0 +37208 0 +37211 0 +37212 0 +37213 0 +37214 0 +37215 0 +37216 0 +37217 0 +37218 0 +37219 0 +37220 0 +37221 0 +37222 0 +37223 0 +37224 0 +37225 1 +37226 0 +37227 0 +37228 0 +37229 0 +37230 0 +37231 0 +37232 0 +37233 0 +37235 0 +37237 0 +37238 0 +37239 0 +37240 0 +37241 0 +37242 0 +37243 0 +37245 0 +37246 0 +37247 0 +37249 0 +37250 0 +37251 0 +37252 0 +37253 0 +37254 0 +37255 0 +37257 0 +37259 0 +37260 0 +104020 0 +104100 0 +104101 0 +104102 0 +104103 0 +104104 0 +104105 0 +104106 0 +104107 0 +104108 0 +104109 0 +104110 0 +104111 0 +104112 0 +104113 0 +104114 0 +104115 0 +104116 0 +104117 0 +104118 0 +104119 0 +104120 0 +104121 0 +104122 0 +104123 0 +104124 0 +104125 0 +104126 0 +104127 0 +104128 0 +104129 0 +104130 0 +104131 0 +104132 0 +104133 0 +104134 0 +104135 0 +104136 0 +104137 0 +104138 0 +104139 1 +104140 1 +104141 0 +104142 0 +104143 0 +104144 0 +104145 0 +104223 0 +104256 0 +104257 0 +104258 0 +104259 0 +104260 0 +104261 0 +104286 0 +104287 0 +104304 0 +104305 0 +104700 0 +104701 0 +104702 0 +104703 0 +104704 0 +104705 0 +104706 0 +104707 0 +104708 0 +104709 0 +104710 0 +104711 0 +104712 0 +104713 0 +104714 0 +104715 0 +104716 0 +104717 0 +104718 0 +104719 0 +104720 0 +104721 0 +104722 0 +104723 0 +104724 0 +104725 0 +104726 0 +104727 0 +104728 0 +104729 0 +104730 0 +104731 0 +104732 0 +104733 0 +104734 0 +104735 0 +104736 0 +104737 0 +104738 0 +104739 0 +104740 0 +104741 0 +104742 0 +104743 0 +104744 0 +104745 0 +104746 0 +104747 0 +104748 0 +104749 0 +104750 0 +104751 0 +104752 0 +104753 0 +104754 0 +104755 0 +104756 0 +104757 0 +104758 0 +104759 0 +104760 0 +104761 0 +104762 0 +104763 0 +104764 0 +104765 0 +104766 0 +104767 0 +104768 0 +104769 0 +104770 0 +104771 0 +104772 0 +104773 0 +104774 0 +104775 0 +104776 0 +104777 0 +104778 0 +104779 0 +104780 0 +104781 0 +104782 0 +104783 0 +104784 0 +104785 0 +104786 0 +104896 0 +104900 5 +104901 0 +104902 0 +104903 9 +104904 2 +104905 2 +104906 59 +104907 54 +104908 0 +104909 58 +104910 31 +104911 56 +104912 6 +104913 50 +104914 41 +104915 10 +104916 3 +104917 30 +104918 8 +104919 60 +104920 53 +104921 44 +104922 48 +104923 51 +104924 38 +104925 0 +104926 49 +104927 57 +104928 21 +104929 23 +104930 35 +104931 49 +104932 27 +104933 17 +104934 13 +104935 7 +104936 56 +104937 40 +104938 28 +104939 37 +104940 15 +104941 55 +104942 22 +104943 4 +104944 0 +104945 20 +104946 42 +104947 47 +104948 52 +104949 43 +104950 46 +104951 39 +104952 24 +104953 16 +104954 50 +104955 36 +104956 33 +104957 46 +104958 14 +104959 19 +104960 0 +104961 34 +104962 32 +104963 29 +104964 45 +104965 26 +104966 25 +104967 41 +104968 11 +104969 12 +104970 18 +104990 0 +104991 0 +104992 0 diff --git a/src/com/esri/core/geometry/gcs_tolerances.txt b/src/com/esri/core/geometry/gcs_tolerances.txt new file mode 100644 index 00000000..e593f472 --- /dev/null +++ b/src/com/esri/core/geometry/gcs_tolerances.txt @@ -0,0 +1,61 @@ +0 1e-008 +1 1.11111e-008 +2 1.68845e-008 +3 2.17661e-008 +4 2.22508e-008 +5 2.34848e-008 +6 2.37811e-008 +7 2.88455e-008 +8 3.1456e-008 +9 3.29779e-008 +10 3.66789e-008 +11 4.23597e-008 +12 4.79463e-008 +13 6.45223e-008 +14 7.26274e-008 +15 7.49945e-008 +16 7.52506e-008 +17 7.97991e-008 +18 9.66202e-008 +19 9.79918e-008 +20 9.89735e-008 +21 1.02314e-007 +22 1.08146e-007 +23 2.29734e-007 +24 2.42985e-007 +25 2.7546e-007 +26 3.37034e-007 +27 4.30795e-007 +28 5.20871e-007 +29 5.50921e-007 +30 6.74068e-007 +31 6.86177e-007 +32 7.25263e-007 +33 7.44101e-007 +34 7.74267e-007 +35 9.62954e-007 +36 1.06103e-006 +37 1.14363e-006 +38 1.16219e-006 +39 1.36419e-006 +40 1.36744e-006 +41 1.43239e-006 +42 1.73624e-006 +43 1.84825e-006 +44 1.90986e-006 +45 1.97572e-006 +46 2.12207e-006 +47 2.72837e-006 +48 3.1831e-006 +49 3.58099e-006 +50 3.81972e-006 +51 4.09256e-006 +52 4.40737e-006 +53 4.77465e-006 +54 5.16178e-006 +55 5.20871e-006 +56 5.72958e-006 +57 6.03113e-006 +58 6.98729e-006 +59 9.24125e-006 +60 1.14592e-005 diff --git a/src/com/esri/core/geometry/intermediate_to_old_wkid.txt b/src/com/esri/core/geometry/intermediate_to_old_wkid.txt new file mode 100644 index 00000000..e69de29b diff --git a/src/com/esri/core/geometry/new_to_old_wkid.txt b/src/com/esri/core/geometry/new_to_old_wkid.txt new file mode 100644 index 00000000..7cb8d997 --- /dev/null +++ b/src/com/esri/core/geometry/new_to_old_wkid.txt @@ -0,0 +1,483 @@ +2154 102110 +2195 102200 +2204 32036 +2205 26979 +2225 102641 +2226 102642 +2227 102643 +2228 102644 +2229 102645 +2230 102646 +2231 102653 +2232 102654 +2233 102655 +2234 102656 +2235 102657 +2236 102658 +2237 102659 +2238 102660 +2239 102666 +2240 102667 +2241 102668 +2242 102669 +2243 102670 +2246 102679 +2247 102680 +2248 102685 +2249 102686 +2250 102687 +2254 102694 +2255 102695 +2257 102712 +2258 102713 +2259 102714 +2260 102715 +2261 102716 +2262 102717 +2263 102718 +2264 102719 +2267 102724 +2268 102725 +2271 102728 +2272 102729 +2274 102736 +2275 102737 +2276 102738 +2277 102739 +2278 102740 +2279 102741 +2283 102746 +2284 102747 +2285 102748 +2286 102749 +2287 102752 +2288 102753 +2289 102754 +2312 23433 +2326 102140 +2395 2091 +2396 2092 +2397 2166 +2398 2167 +2399 2168 +2759 102229 +2760 102230 +2761 102248 +2762 102249 +2763 102250 +2764 102251 +2765 102252 +2766 102241 +2767 102242 +2768 102243 +2769 102244 +2770 102245 +2771 102246 +2772 102253 +2773 102254 +2774 102255 +2775 102256 +2776 102257 +2777 102258 +2778 102259 +2779 102260 +2780 102266 +2781 102267 +2782 102261 +2783 102262 +2784 102263 +2785 102264 +2786 102265 +2787 102268 +2788 102269 +2789 102270 +2790 102271 +2791 102272 +2792 102273 +2793 102274 +2794 102275 +2795 102276 +2796 102277 +2797 102278 +2798 102279 +2799 102280 +2800 102281 +2801 102282 +2802 102283 +2803 102284 +2804 102285 +2805 102286 +2806 102287 +2807 102288 +2808 102289 +2809 102290 +2810 102291 +2811 102292 +2812 102293 +2813 102294 +2814 102295 +2815 102296 +2816 102297 +2817 102298 +2818 102300 +2819 102304 +2820 102307 +2821 102308 +2822 102309 +2823 102310 +2824 102311 +2825 102312 +2826 102313 +2827 102314 +2828 102315 +2829 102316 +2830 102317 +2831 102318 +2832 102320 +2833 102321 +2834 102322 +2835 102323 +2836 102324 +2837 102325 +2838 102326 +2839 102327 +2840 102330 +2841 102334 +2842 102335 +2843 102336 +2844 102337 +2845 102338 +2846 102339 +2847 102340 +2848 102341 +2849 102342 +2850 102343 +2851 102344 +2852 102345 +2853 102346 +2854 102347 +2855 102348 +2856 102349 +2857 102350 +2858 102351 +2859 102352 +2860 102353 +2861 102354 +2862 102355 +2863 102356 +2864 102357 +2865 102358 +2866 102361 +2942 102167 +2943 102169 +2944 2139 +2945 2140 +2946 2141 +2947 2142 +2948 2143 +2949 2144 +2950 2145 +2951 2146 +2952 2147 +2953 2036 +2954 2291 +2955 2153 +2956 2152 +2957 2151 +2958 2150 +2959 2149 +2960 2037 +2961 2038 +2962 2148 +2965 2244 +2966 2245 +3003 102091 +3004 102092 +3005 102190 +3060 2982 +3067 102139 +3072 102606 +3074 102608 +3075 102208 +3077 102210 +3078 102123 +3080 102119 +3081 102603 +3082 102602 +3083 102601 +3088 65163 +3089 102763 +3090 102363 +3092 102151 +3093 102152 +3094 102153 +3095 102154 +3096 102155 +3097 102145 +3098 102146 +3099 102147 +3100 102148 +3101 102149 +3102 2155 +3107 102172 +3110 102170 +3111 102171 +3119 2214 +3158 102234 +3159 102235 +3160 102236 +3336 2979 +3338 102006 +3346 2600 +3370 102126 +3371 102127 +3372 102130 +3373 102131 +3400 102184 +3401 102185 +3404 3359 +3407 3366 +3417 102675 +3418 102676 +3419 102677 +3420 102678 +3421 102707 +3422 102708 +3423 102709 +3424 102711 +3433 102651 +3434 102652 +3435 102671 +3436 102672 +3437 102710 +3438 102730 +3448 102095 +3451 102681 +3452 102682 +3455 102735 +3461 2063 +3462 2064 +3463 3073 +3464 3076 +3560 102742 +3566 102743 +3567 102744 +3734 102722 +3735 102723 +3736 102755 +3737 102756 +3738 102757 +3739 102758 +3741 102205 +3742 102206 +3743 102207 +3748 102211 +3750 102202 +3751 102203 +3759 102663 +3760 102463 +3764 102112 +3770 102090 +3771 102180 +3772 102181 +3773 102182 +3775 102186 +3776 102187 +3777 102188 +3800 102183 +3801 102189 +3812 102199 +3814 102609 +3815 102469 +3819 104990 +3821 104136 +3824 104137 +3825 102444 +3826 102443 +3827 102442 +3828 102441 +3857 102100 +3889 104991 +3906 104992 +4048 103201 +4049 103202 +4050 103203 +4051 103204 +4056 103205 +4057 103206 +4058 103207 +4059 103208 +4060 103209 +4061 103210 +4062 103211 +4063 103212 +4071 103213 +4082 103214 +4083 103215 +4093 103216 +4094 103217 +4095 103218 +4096 103219 +4167 104108 +4169 37252 +4171 104107 +4189 104110 +4197 4234 +4223 37223 +4304 104304 +4414 102201 +4415 102762 +4417 102764 +4434 102765 +4437 102647 +4455 32029 +4456 32018 +4457 3454 +4462 102439 +4463 4466 +4470 4469 +4484 103794 +4485 103795 +4486 103796 +4487 103797 +4488 103798 +4489 103799 +4611 104104 +4612 104111 +4613 37255 +4615 37247 +4616 37250 +4617 4140 +4618 4291 +4620 37211 +4626 37235 +4647 102362 +4658 37204 +4668 37201 +4669 4126 +4672 37217 +4673 104125 +4675 37220 +4684 37232 +4698 4631 +4707 37213 +4708 37231 +4709 37212 +4710 37238 +4711 37214 +4712 37237 +4713 37208 +4714 37215 +4715 37253 +4716 37216 +4717 37239 +4719 37219 +4722 37242 +4724 37233 +4725 37222 +4727 37224 +4728 37246 +4729 37226 +4730 37227 +4731 37228 +4732 37229 +4733 37230 +4734 37251 +4735 37259 +4736 37254 +4739 37205 +4758 104133 +4760 37001 +4762 104114 +4826 102214 +5013 104142 +5014 102331 +5015 102332 +5016 102333 +5173 102085 +5174 102086 +5175 102087 +5176 102088 +5177 102089 +5178 102040 +5179 102080 +5185 102081 +5186 102082 +5187 102083 +5188 102084 +5221 102066 +5246 104100 +5247 102490 +5324 104144 +5325 102420 +5329 2934 +5365 104143 +5367 102305 +5451 104132 +5513 102065 +5514 102067 +5519 102111 +5520 31461 +5646 102745 +5839 5388 +5858 5532 +5879 4474 +21896 21891 +21897 21892 +21898 21893 +21899 21894 +26701 102124 +26702 102125 +26799 26747 +26847 102683 +26848 102684 +26849 102691 +26850 102692 +26851 102693 +26852 102704 +26853 102750 +26854 102751 +26857 102466 +26858 102467 +26859 102468 +26901 102128 +26902 102129 +27493 27492 +29101 29100 +29168 29118 +29169 29119 +29170 29120 +29171 29121 +29172 29122 +29187 29177 +29188 29178 +29189 29179 +29190 29180 +29191 29181 +29192 29182 +29193 29183 +29194 29184 +29195 29185 +29902 29900 +31279 31278 +31281 31291 +31282 31292 +31283 31293 +31284 31294 +31285 31295 +31286 31296 +31287 31297 +31466 31462 +31467 31463 +31468 31464 +31469 31465 +31986 31917 +31987 31918 +31988 31919 +31989 31920 +31990 31921 +31991 31922 +32064 32074 +32065 32075 +32066 32076 +32067 32077 diff --git a/src/com/esri/core/geometry/ogc/OGCConcreteGeometryCollection.java b/src/com/esri/core/geometry/ogc/OGCConcreteGeometryCollection.java new file mode 100644 index 00000000..3ca58c1e --- /dev/null +++ b/src/com/esri/core/geometry/ogc/OGCConcreteGeometryCollection.java @@ -0,0 +1,310 @@ +package com.esri.core.geometry.ogc; + +import com.esri.core.geometry.Envelope; +import com.esri.core.geometry.Geometry; +import com.esri.core.geometry.GeometryCursor; +import com.esri.core.geometry.Polygon; +import com.esri.core.geometry.SpatialReference; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.util.ArrayList; +import java.util.List; + +public class OGCConcreteGeometryCollection extends OGCGeometryCollection { + public OGCConcreteGeometryCollection(List geoms, + SpatialReference sr) { + geometries = geoms; + esriSR = sr; + } + + public OGCConcreteGeometryCollection(OGCGeometry geom, SpatialReference sr) { + geometries = new ArrayList(1); + geometries.add(geom); + esriSR = sr; + } + + @Override + public int dimension() { + int maxD = 0; + for (int i = 0, n = numGeometries(); i < n; i++) + maxD = Math.max(geometryN(i).dimension(), maxD); + + return maxD; + } + + @Override + public int coordinateDimension() { + return isEmpty() ? 2 : geometryN(0).coordinateDimension(); + } + + @Override + public boolean is3D() { + return !isEmpty() && geometries.get(0).is3D(); + } + + @Override + public boolean isMeasured() { + return !isEmpty() && geometries.get(0).isMeasured(); + } + + @Override + public OGCGeometry envelope() { + GeometryCursor gc = getEsriGeometryCursor(); + Envelope env = new Envelope(); + for (Geometry g = gc.next(); g != null; g = gc.next()) { + Envelope e = new Envelope(); + g.queryEnvelope(e); + env.merge(e); + } + + Polygon polygon = new Polygon(); + polygon.addEnvelope(env, false); + return new OGCPolygon(polygon, esriSR); + } + + @Override + public int numGeometries() { + return geometries.size(); + } + + @Override + public OGCGeometry geometryN(int n) { + return geometries.get(n); + } + + @Override + public String geometryType() { + return "GeometryCollection"; + } + + @Override + public String asText() { + StringBuilder sb = new StringBuilder("GEOMETRYCOLLECTION "); + if (is3D()) { + sb.append('Z'); + } + if (isMeasured()) { + sb.append('M'); + } + if (is3D() || isMeasured()) + sb.append(' '); + + int n = numGeometries(); + + if (n == 0) { + sb.append("EMPTY"); + return sb.toString(); + } + + sb.append('('); + for (int i = 0; i < n; i++) { + if (i > 0) + sb.append(", "); + + sb.append(geometryN(i).asText()); + } + sb.append(')'); + + return sb.toString(); + } + + @Override + public ByteBuffer asBinary() { + + ArrayList buffers = new ArrayList(0); + + int size = 9; + int n = numGeometries(); + for (int i = 0; i < n; i++) { + ByteBuffer buffer = geometryN(i).asBinary(); + buffers.add(buffer); + size += buffer.capacity(); + } + + ByteBuffer wkbBuffer = ByteBuffer.allocate(size).order( + ByteOrder.nativeOrder()); + + byte byteOrder = (byte) (wkbBuffer.order() == ByteOrder.LITTLE_ENDIAN ? 1 + : 0); + int wkbType = 7; + + if (is3D()) + wkbType += 1000; + if (isMeasured()) + wkbType += 2000; + + wkbBuffer.put(0, byteOrder); + wkbBuffer.putInt(1, wkbType); + wkbBuffer.putInt(5, n); + + int offset = 9; + for (int i = 0; i < n; i++) { + byte[] arr = buffers.get(i).array(); + System.arraycopy(arr, 0, wkbBuffer.array(), offset, arr.length); + offset += arr.length; + } + + return wkbBuffer; + } + + @Override + public boolean isEmpty() { + return numGeometries() == 0; + } + + @Override + public double MinZ() { + double z = Double.NaN; + for (int i = 0, n = numGeometries(); i < n; i++) + z = i == 0 ? geometryN(i).MinZ() : Math.min(geometryN(i).MinZ(), z); + return z; + } + + @Override + public double MaxZ() { + double z = Double.NaN; + for (int i = 0, n = numGeometries(); i < n; i++) + z = i == 0 ? geometryN(i).MaxZ() : Math.min(geometryN(i).MaxZ(), z); + return z; + } + + @Override + public double MinMeasure() { + double z = Double.NaN; + for (int i = 0, n = numGeometries(); i < n; i++) + z = i == 0 ? geometryN(i).MinMeasure() : Math.min(geometryN(i) + .MinMeasure(), z); + return z; + } + + @Override + public double MaxMeasure() { + double z = Double.NaN; + for (int i = 0, n = numGeometries(); i < n; i++) + z = i == 0 ? geometryN(i).MaxMeasure() : Math.min(geometryN(i) + .MaxMeasure(), z); + return z; + } + + @Override + public boolean isSimple() { + for (int i = 0, n = numGeometries(); i < n; i++) + if (!geometryN(i).isSimple()) + return false; + return true; + } + + /** + * isSimpleRelaxed is not supported for the GeometryCollection instance. + * + * @return + */ + @Override + public boolean isSimpleRelaxed() { + throw new UnsupportedOperationException(); + } + + /** + * MakeSimpleRelaxed is not supported for the GeometryCollection instance. + * + * @return + */ + @Override + public OGCGeometry MakeSimpleRelaxed(boolean forceProcessing) { + throw new UnsupportedOperationException(); + } + + @Override + public OGCGeometry boundary() { + throw new UnsupportedOperationException(); + } + + @Override + public OGCGeometry locateAlong(double mValue) { + // TODO Auto-generated method stub + throw new UnsupportedOperationException(); + } + + @Override + public OGCGeometry locateBetween(double mStart, double mEnd) { + // TODO Auto-generated method stub + throw new UnsupportedOperationException(); + } + + @Override + public Geometry getEsriGeometry() { + return null; + } + + @Override + public GeometryCursor getEsriGeometryCursor() { + return new GeometryCursorOGC(geometries); + } + + @Override + protected boolean isConcreteGeometryCollection() { + return true; + } + + static class GeometryCursorOGC extends GeometryCursor { + private int m_index; + private int m_ind; + private List m_geoms; + GeometryCursor m_curs; + + GeometryCursorOGC(List geoms) { + m_geoms = geoms; + m_index = -1; + m_curs = null; + m_ind = 0; + } + + @Override + public Geometry next() { + while (true) { + if (m_curs != null) { + Geometry g = m_curs.next(); + if (g != null) { + m_index++; + return g; + } + m_curs = null; + } + if (m_ind >= m_geoms.size()) + return null; + + int i = m_ind; + m_ind++; + if (m_geoms.get(i) == null) + continue;// filter out nulls + if (!m_geoms.get(i).isConcreteGeometryCollection()) { + m_index++; + return m_geoms.get(i).getEsriGeometry(); + } else { + OGCConcreteGeometryCollection gc = (OGCConcreteGeometryCollection) m_geoms + .get(i); + m_curs = new GeometryCursorOGC(gc.geometries); + return next(); + } + } + } + + @Override + public int getGeometryID() { + return m_index; + } + + } + + List geometries; + + @Override + public void setSpatialReference(SpatialReference esriSR_) { + esriSR = esriSR_; + for (int i = 0, n = geometries.size(); i < n; i++) { + if (geometries.get(i) != null) + geometries.get(i).setSpatialReference(esriSR_); + } + } + +} diff --git a/src/com/esri/core/geometry/ogc/OGCCurve.java b/src/com/esri/core/geometry/ogc/OGCCurve.java new file mode 100644 index 00000000..2cad67f7 --- /dev/null +++ b/src/com/esri/core/geometry/ogc/OGCCurve.java @@ -0,0 +1,27 @@ +package com.esri.core.geometry.ogc; + +import com.esri.core.geometry.MultiPoint; +import com.esri.core.geometry.Point; + +public abstract class OGCCurve extends OGCGeometry { + public abstract double length(); + + public abstract OGCPoint startPoint(); + + public abstract OGCPoint endPoint(); + + public abstract boolean isClosed(); + + public boolean isRing() { + return isSimple() && isClosed(); + } + + @Override + public OGCGeometry boundary() { + if (isClosed()) + return new OGCMultiPoint(new MultiPoint(getEsriGeometry() + .getDescription()), esriSR);// return empty multipoint; + else + return new OGCMultiPoint(startPoint(), endPoint()); + } +} diff --git a/src/com/esri/core/geometry/ogc/OGCGeometry.java b/src/com/esri/core/geometry/ogc/OGCGeometry.java new file mode 100644 index 00000000..cadfb67c --- /dev/null +++ b/src/com/esri/core/geometry/ogc/OGCGeometry.java @@ -0,0 +1,618 @@ +package com.esri.core.geometry.ogc; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.Arrays; + +import org.codehaus.jackson.JsonFactory; +import org.codehaus.jackson.JsonParseException; +import org.codehaus.jackson.JsonParser; +import org.json.JSONException; + +import com.esri.core.geometry.Envelope; +import com.esri.core.geometry.Envelope1D; +import com.esri.core.geometry.Geometry; +import com.esri.core.geometry.GeometryCursor; +import com.esri.core.geometry.GeometryCursorAppend; +import com.esri.core.geometry.GeometryEngine; +import com.esri.core.geometry.MapGeometry; +import com.esri.core.geometry.MapOGCStructure; +import com.esri.core.geometry.MultiPoint; +import com.esri.core.geometry.OGCStructure; +import com.esri.core.geometry.Operator; +import com.esri.core.geometry.OperatorBuffer; +import com.esri.core.geometry.OperatorConvexHull; +import com.esri.core.geometry.OperatorExportToWkb; +import com.esri.core.geometry.OperatorFactoryLocal; +import com.esri.core.geometry.OperatorImportFromESRIShape; +import com.esri.core.geometry.OperatorImportFromGeoJson; +import com.esri.core.geometry.OperatorImportFromJson; +import com.esri.core.geometry.OperatorImportFromWkb; +import com.esri.core.geometry.OperatorImportFromWkt; +import com.esri.core.geometry.OperatorIntersection; +import com.esri.core.geometry.OperatorSimplify; +import com.esri.core.geometry.OperatorSimplifyOGC; +import com.esri.core.geometry.OperatorUnion; +import com.esri.core.geometry.Point; +import com.esri.core.geometry.Polygon; +import com.esri.core.geometry.Polyline; +import com.esri.core.geometry.SimpleGeometryCursor; +import com.esri.core.geometry.SpatialReference; +import com.esri.core.geometry.VertexDescription; + +/** + * OGC Simple Feature Access specification v.1.2.1 + * + */ +public abstract class OGCGeometry { + public int dimension() { + return getEsriGeometry().getDimension(); + } + + public int coordinateDimension() { + int d = 2; + if (getEsriGeometry().getDescription().hasAttribute( + VertexDescription.Semantics.M)) + d++; + if (getEsriGeometry().getDescription().hasAttribute( + VertexDescription.Semantics.Z)) + d++; + + return d; + } + + abstract public String geometryType(); + + public int SRID() { + if (esriSR == null) + return 0; + + return esriSR.getID(); + } + + public OGCGeometry envelope() { + com.esri.core.geometry.Envelope env = new com.esri.core.geometry.Envelope(); + getEsriGeometry().queryEnvelope(env); + com.esri.core.geometry.Polygon polygon = new com.esri.core.geometry.Polygon(); + polygon.addEnvelope(env, false); + return new OGCPolygon(polygon, esriSR); + } + + public String asText() { + return GeometryEngine.geometryToWkt(getEsriGeometry(), 0); + } + + public ByteBuffer asBinary() { + OperatorExportToWkb op = (OperatorExportToWkb) OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.ExportToWkb); + return op.execute(0, getEsriGeometry(), null); + } + + public boolean isEmpty() { + return getEsriGeometry().isEmpty(); + } + + public double MinZ() { + Envelope1D e = getEsriGeometry().queryInterval( + VertexDescription.Semantics.Z, 0); + return e.vmin; + } + + public double MaxZ() { + Envelope1D e = getEsriGeometry().queryInterval( + VertexDescription.Semantics.Z, 0); + return e.vmax; + } + + public double MinMeasure() { + Envelope1D e = getEsriGeometry().queryInterval( + VertexDescription.Semantics.M, 0); + return e.vmin; + } + + public double MaxMeasure() { + Envelope1D e = getEsriGeometry().queryInterval( + VertexDescription.Semantics.M, 0); + return e.vmax; + } + + public boolean isSimple() { + return OperatorSimplifyOGC.local().isSimpleOGC(getEsriGeometry(), + esriSR, true, null, null); + } + + /** + * Extension method - checks if geometry is simple for Geodatabase. + * + * @return + */ + public boolean isSimpleRelaxed() { + OperatorSimplify op = (OperatorSimplify) OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Simplify); + return op + .isSimpleAsFeature(getEsriGeometry(), esriSR, true, null, null); + } + + /** + * Makes a simple geometry for Geodatabase. + * + * @return + */ + public OGCGeometry MakeSimpleRelaxed(boolean forceProcessing) { + OperatorSimplify op = (OperatorSimplify) OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Simplify); + return OGCGeometry.createFromEsriGeometry( + op.execute(getEsriGeometry(), esriSR, forceProcessing, null), + esriSR); + } + + public boolean is3D() { + return getEsriGeometry().getDescription().hasAttribute( + VertexDescription.Semantics.Z); + } + + public boolean isMeasured() { + return getEsriGeometry().getDescription().hasAttribute( + VertexDescription.Semantics.M); + } + + abstract public OGCGeometry boundary(); + + // query + public boolean equals(OGCGeometry another) { + com.esri.core.geometry.Geometry geom1 = getEsriGeometry(); + com.esri.core.geometry.Geometry geom2 = another.getEsriGeometry(); + return com.esri.core.geometry.GeometryEngine.equals(geom1, geom2, + getEsriSpatialReference()); + } + + public boolean disjoint(OGCGeometry another) { + com.esri.core.geometry.Geometry geom1 = getEsriGeometry(); + com.esri.core.geometry.Geometry geom2 = another.getEsriGeometry(); + return com.esri.core.geometry.GeometryEngine.disjoint(geom1, geom2, + getEsriSpatialReference()); + } + + public boolean intersects(OGCGeometry another) { + return !disjoint(another); + } + + public boolean touches(OGCGeometry another) { + com.esri.core.geometry.Geometry geom1 = getEsriGeometry(); + com.esri.core.geometry.Geometry geom2 = another.getEsriGeometry(); + return com.esri.core.geometry.GeometryEngine.touches(geom1, geom2, + getEsriSpatialReference()); + } + + public boolean crosses(OGCGeometry another) { + com.esri.core.geometry.Geometry geom1 = getEsriGeometry(); + com.esri.core.geometry.Geometry geom2 = another.getEsriGeometry(); + return com.esri.core.geometry.GeometryEngine.crosses(geom1, geom2, + getEsriSpatialReference()); + } + + public boolean within(OGCGeometry another) { + com.esri.core.geometry.Geometry geom1 = getEsriGeometry(); + com.esri.core.geometry.Geometry geom2 = another.getEsriGeometry(); + return com.esri.core.geometry.GeometryEngine.within(geom1, geom2, + getEsriSpatialReference()); + } + + public boolean contains(OGCGeometry another) { + com.esri.core.geometry.Geometry geom1 = getEsriGeometry(); + com.esri.core.geometry.Geometry geom2 = another.getEsriGeometry(); + return com.esri.core.geometry.GeometryEngine.contains(geom1, geom2, + getEsriSpatialReference()); + } + + public boolean overlaps(OGCGeometry another) { + com.esri.core.geometry.Geometry geom1 = getEsriGeometry(); + com.esri.core.geometry.Geometry geom2 = another.getEsriGeometry(); + return com.esri.core.geometry.GeometryEngine.overlaps(geom1, geom2, + getEsriSpatialReference()); + } + + public boolean relate(OGCGeometry another, String matrix) { + com.esri.core.geometry.Geometry geom1 = getEsriGeometry(); + com.esri.core.geometry.Geometry geom2 = another.getEsriGeometry(); + return com.esri.core.geometry.GeometryEngine.relate(geom1, geom2, + getEsriSpatialReference(), matrix); + } + + abstract public OGCGeometry locateAlong(double mValue); + + abstract public OGCGeometry locateBetween(double mStart, double mEnd); + + // analysis + public double distance(OGCGeometry another) { + com.esri.core.geometry.Geometry geom1 = getEsriGeometry(); + com.esri.core.geometry.Geometry geom2 = another.getEsriGeometry(); + return com.esri.core.geometry.GeometryEngine.distance(geom1, geom2, + getEsriSpatialReference()); + } + + // This method firstly groups geometries by dimension (points, lines, + // areas), + // then simplifies each group such that each group is reduced to a single + // geometry. + // As a result there are at most three geometries, each geometry is Simple. + // Afterwards + // it produces a single OGCGeometry. + // + // Note: Not complete yet. We'll use this method to implement the OGC + // Simplify (or MakeValid method) + // At this moment, method removes self intersections, and clusters vertices, + // but may sometimes + // produce geometries with self-tangency or polygons with disconnected + // interior + // which are simple for ArcObjects, but non-simple for OGC. + private OGCGeometry simplifyBunch_(GeometryCursor gc) { + // Combines geometries into multipoint, polyline, and polygon types, + // simplifying them and unioning them, + // then produces OGCGeometry from the result. + // Can produce OGCConcreteGoemetryCollection + MultiPoint dstMultiPoint = null; + ArrayList dstPolylines = new ArrayList(); + ArrayList dstPolygons = new ArrayList(); + for (com.esri.core.geometry.Geometry g = gc.next(); g != null; g = gc + .next()) { + switch (g.getType()) { + case Point: + if (dstMultiPoint == null) + dstMultiPoint = new MultiPoint(); + dstMultiPoint.add((Point) g); + break; + case MultiPoint: + if (dstMultiPoint == null) + dstMultiPoint = new MultiPoint(); + dstMultiPoint.add((MultiPoint) g, 0, -1); + break; + case Polyline: + dstPolylines.add((Polyline) g.copy()); + break; + case Polygon: + dstPolygons.add((Polygon) g.copy()); + break; + default: + throw new UnsupportedOperationException(); + } + } + + ArrayList result = new ArrayList(3); + if (dstMultiPoint != null) { + Geometry resMP = OperatorSimplifyOGC.local().execute(dstMultiPoint, + esriSR, true, null); + result.add(resMP); + } + + if (dstPolylines.size() > 0) { + if (dstPolylines.size() == 1) { + Geometry resMP = OperatorSimplifyOGC.local().execute( + dstPolylines.get(0), esriSR, true, null); + result.add(resMP); + } else { + GeometryCursor res = OperatorUnion.local().execute( + new SimpleGeometryCursor(dstPolylines), esriSR, null); + Geometry resPolyline = res.next(); + Geometry resMP = OperatorSimplifyOGC.local().execute( + resPolyline, esriSR, true, null); + result.add(resMP); + } + } + + if (dstPolygons.size() > 0) { + if (dstPolygons.size() == 1) { + Geometry resMP = OperatorSimplifyOGC.local().execute( + dstPolygons.get(0), esriSR, true, null); + result.add(resMP); + } else { + GeometryCursor res = OperatorUnion.local().execute( + new SimpleGeometryCursor(dstPolygons), esriSR, null); + Geometry resPolygon = res.next(); + Geometry resMP = OperatorSimplifyOGC.local().execute( + resPolygon, esriSR, true, null); + result.add(resMP); + } + } + + return OGCGeometry.createFromEsriCursor( + new SimpleGeometryCursor(result), esriSR); + } + + public OGCGeometry buffer(double distance) { + OperatorBuffer op = (OperatorBuffer) OperatorFactoryLocal.getInstance() + .getOperator(Operator.Type.Buffer); + if (distance == 0) {// when distance is 0, return self (maybe we should + // create a copy instead). + return this; + } + + double d[] = { distance }; + com.esri.core.geometry.GeometryCursor cursor = op.execute( + getEsriGeometryCursor(), getEsriSpatialReference(), d, true, + null); + return OGCGeometry.createFromEsriGeometry(cursor.next(), esriSR); + } + + public OGCGeometry convexHull() { + com.esri.core.geometry.OperatorConvexHull op = (OperatorConvexHull) OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.ConvexHull); + com.esri.core.geometry.GeometryCursor cursor = op.execute( + getEsriGeometryCursor(), true, null); + return OGCGeometry.createFromEsriCursor(cursor, esriSR); + } + + public OGCGeometry intersection(OGCGeometry another) { + com.esri.core.geometry.OperatorIntersection op = (OperatorIntersection) OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Intersection); + com.esri.core.geometry.GeometryCursor cursor = op.execute( + getEsriGeometryCursor(), another.getEsriGeometryCursor(), + getEsriSpatialReference(), null, 7); + return OGCGeometry.createFromEsriCursor(cursor, esriSR, true); + } + + public OGCGeometry union(OGCGeometry another) { + OperatorUnion op = (OperatorUnion) OperatorFactoryLocal.getInstance() + .getOperator(Operator.Type.Union); + GeometryCursorAppend ap = new GeometryCursorAppend( + getEsriGeometryCursor(), another.getEsriGeometryCursor()); + com.esri.core.geometry.GeometryCursor cursor = op.execute(ap, + getEsriSpatialReference(), null); + return OGCGeometry.createFromEsriCursor(cursor, esriSR); + } + + public OGCGeometry difference(OGCGeometry another) { + com.esri.core.geometry.Geometry geom1 = getEsriGeometry(); + com.esri.core.geometry.Geometry geom2 = another.getEsriGeometry(); + return createFromEsriGeometry( + com.esri.core.geometry.GeometryEngine.difference(geom1, geom2, + getEsriSpatialReference()), esriSR); + } + + public OGCGeometry symDifference(OGCGeometry another) { + com.esri.core.geometry.Geometry geom1 = getEsriGeometry(); + com.esri.core.geometry.Geometry geom2 = another.getEsriGeometry(); + return createFromEsriGeometry( + com.esri.core.geometry.GeometryEngine.symmetricDifference( + geom1, geom2, getEsriSpatialReference()), esriSR); + } + + public abstract com.esri.core.geometry.Geometry getEsriGeometry(); + + public GeometryCursor getEsriGeometryCursor() { + return new SimpleGeometryCursor(getEsriGeometry()); + } + + public com.esri.core.geometry.SpatialReference getEsriSpatialReference() { + return esriSR; + } + + /** + * Create an OGCGeometry instance from the GeometryCursor. + * + * @param gc + * @param sr + * @return + */ + public static OGCGeometry createFromEsriCursor(GeometryCursor gc, + SpatialReference sr) { + return createFromEsriCursor(gc, sr, false); + } + + public static OGCGeometry createFromEsriCursor(GeometryCursor gc, + SpatialReference sr, boolean skipEmpty) { + ArrayList geoms = new ArrayList(10); + Geometry emptyGeom = null; + for (Geometry g = gc.next(); g != null; g = gc.next()) { + emptyGeom = g; + if (!skipEmpty || !g.isEmpty()) + geoms.add(createFromEsriGeometry(g, sr)); + } + + if (geoms.size() == 1) { + return geoms.get(0); + } else if (geoms.size() == 0) + return createFromEsriGeometry(emptyGeom, sr); + else + return new OGCConcreteGeometryCollection(geoms, sr); + } + + public static OGCGeometry fromText(String text) { + OperatorImportFromWkt op = (OperatorImportFromWkt) OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.ImportFromWkt); + OGCStructure ogcStructure = op.executeOGC(0, text, null); + return OGCGeometry.createFromOGCStructure(ogcStructure, + SpatialReference.create(4326)); + } + + public static OGCGeometry fromBinary(ByteBuffer binary) { + OperatorImportFromWkb op = (OperatorImportFromWkb) OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.ImportFromWkb); + OGCStructure ogcStructure = op.executeOGC(0, binary, null); + return OGCGeometry.createFromOGCStructure(ogcStructure, + SpatialReference.create(4326)); + } + + public static OGCGeometry fromEsriShape(ByteBuffer buffer) { + OperatorImportFromESRIShape op = (OperatorImportFromESRIShape) OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.ImportFromESRIShape); + Geometry g = op.execute(0, Geometry.Type.Unknown, buffer); + return OGCGeometry.createFromEsriGeometry(g, + SpatialReference.create(4326)); + } + + public static OGCGeometry fromJson(String string) + throws JsonParseException, IOException { + JsonFactory factory = new JsonFactory(); + JsonParser jsonParserPt = factory.createJsonParser(string); + jsonParserPt.nextToken(); + MapGeometry mapGeom = GeometryEngine.jsonToGeometry(jsonParserPt); + return OGCGeometry.createFromEsriGeometry(mapGeom.getGeometry(), + mapGeom.getSpatialReference()); + } + + public static OGCGeometry fromGeoJson(String string) throws JSONException { + OperatorImportFromGeoJson op = (OperatorImportFromGeoJson) OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.ImportFromGeoJson); + MapOGCStructure mapOGCStructure = op.executeOGC(0, string, null); + return OGCGeometry.createFromOGCStructure( + mapOGCStructure.m_ogcStructure, + mapOGCStructure.m_spatialReference); + } + + public static OGCGeometry createFromEsriGeometry(Geometry geom, + SpatialReference sr) { + return createFromEsriGeometry(geom, sr, false); + } + + public static OGCGeometry createFromEsriGeometry(Geometry geom, + SpatialReference sr, boolean multiType) { + if (geom == null) + return null; + Geometry.Type t = geom.getType(); + if (t == Geometry.Type.Polygon) { + if (!multiType && ((Polygon) geom).getExteriorRingCount() == 1) + return new OGCPolygon((Polygon) geom, sr); + else + return new OGCMultiPolygon((Polygon) geom, sr); + } + if (t == Geometry.Type.Polyline) { + if (!multiType && ((Polyline) geom).getPathCount() == 1) + return new OGCLineString((Polyline) geom, 0, sr); + else + return new OGCMultiLineString((Polyline) geom, sr); + } + if (t == Geometry.Type.MultiPoint) { + if (!multiType && ((MultiPoint) geom).getPointCount() <= 1) { + if (geom.isEmpty()) + return new OGCPoint(new Point(), sr); + else + return new OGCPoint(((MultiPoint) geom).getPoint(0), sr); + } else + return new OGCMultiPoint((MultiPoint) geom, sr); + } + if (t == Geometry.Type.Point) { + if (!multiType) { + return new OGCPoint((Point) geom, sr); + } else { + return new OGCMultiPoint((Point) geom, sr); + } + } + if (t == Geometry.Type.Envelope) { + Polygon p = new Polygon(); + p.addEnvelope((Envelope) geom, false); + return createFromEsriGeometry(p, sr, multiType); + } + + throw new UnsupportedOperationException(); + } + + public static OGCGeometry createFromOGCStructure(OGCStructure ogcStructure, + SpatialReference sr) { + ArrayList collectionStack = new ArrayList( + 0); + ArrayList structureStack = new ArrayList(0); + ArrayList indices = new ArrayList(0); + + OGCGeometry[] geometries = new OGCGeometry[1]; + OGCConcreteGeometryCollection root = new OGCConcreteGeometryCollection( + Arrays.asList(geometries), sr); + + structureStack.add(ogcStructure); + collectionStack.add(root); + indices.add(0); + + while (!structureStack.isEmpty()) { + OGCStructure lastStructure = structureStack.get(structureStack + .size() - 1); + if (indices.get(indices.size() - 1) == lastStructure.m_structures + .size()) { + structureStack.remove(structureStack.size() - 1); + collectionStack.remove(collectionStack.size() - 1); + indices.remove(indices.size() - 1); + continue; + } + + OGCConcreteGeometryCollection lastCollection = collectionStack + .get(collectionStack.size() - 1); + OGCGeometry g; + int i = indices.get(indices.size() - 1); + + int type = lastStructure.m_structures.get(i).m_type; + + switch (type) { + case 1: + g = new OGCPoint( + (Point) lastStructure.m_structures.get(i).m_geometry, + sr); + lastCollection.geometries.set(i, g); + indices.set(indices.size() - 1, i + 1); + break; + case 2: + g = new OGCLineString( + (Polyline) lastStructure.m_structures.get(i).m_geometry, + 0, sr); + lastCollection.geometries.set(i, g); + indices.set(indices.size() - 1, i + 1); + break; + case 3: + g = new OGCPolygon( + (Polygon) lastStructure.m_structures.get(i).m_geometry, + 0, sr); + lastCollection.geometries.set(i, g); + indices.set(indices.size() - 1, i + 1); + break; + case 4: + g = new OGCMultiPoint( + (MultiPoint) lastStructure.m_structures.get(i).m_geometry, + sr); + lastCollection.geometries.set(i, g); + indices.set(indices.size() - 1, i + 1); + break; + case 5: + g = new OGCMultiLineString( + (Polyline) lastStructure.m_structures.get(i).m_geometry, + sr); + lastCollection.geometries.set(i, g); + indices.set(indices.size() - 1, i + 1); + break; + case 6: + g = new OGCMultiPolygon( + (Polygon) lastStructure.m_structures.get(i).m_geometry, + sr); + lastCollection.geometries.set(i, g); + indices.set(indices.size() - 1, i + 1); + break; + case 7: + geometries = new OGCGeometry[lastStructure.m_structures.get(i).m_structures + .size()]; + g = new OGCConcreteGeometryCollection( + Arrays.asList(geometries), sr); + lastCollection.geometries.set(i, g); + indices.set(indices.size() - 1, i + 1); + structureStack.add(lastStructure.m_structures.get(i)); + collectionStack.add((OGCConcreteGeometryCollection) g); + indices.add(0); + break; + default: + throw new UnsupportedOperationException(); + } + } + + return root.geometries.get(0); + } + + protected boolean isConcreteGeometryCollection() { + return false; + } + + /** + * SpatialReference of the Geometry. + */ + public com.esri.core.geometry.SpatialReference esriSR; + + public void setSpatialReference(SpatialReference esriSR_) { + esriSR = esriSR_; + } +} diff --git a/src/com/esri/core/geometry/ogc/OGCGeometryCollection.java b/src/com/esri/core/geometry/ogc/OGCGeometryCollection.java new file mode 100644 index 00000000..354c1a50 --- /dev/null +++ b/src/com/esri/core/geometry/ogc/OGCGeometryCollection.java @@ -0,0 +1,14 @@ +package com.esri.core.geometry.ogc; + +abstract class OGCGeometryCollection extends OGCGeometry { + /** + * Returns the number of geometries in this GeometryCollection. + */ + public abstract int numGeometries(); + + /** + * Returns the Nth geometry in this GeometryCollection. + * @param n The 0 based index of the geometry. + */ + public abstract OGCGeometry geometryN(int n); +} diff --git a/src/com/esri/core/geometry/ogc/OGCLineString.java b/src/com/esri/core/geometry/ogc/OGCLineString.java new file mode 100644 index 00000000..6310a77f --- /dev/null +++ b/src/com/esri/core/geometry/ogc/OGCLineString.java @@ -0,0 +1,111 @@ +package com.esri.core.geometry.ogc; + +import com.esri.core.geometry.Geometry; +import com.esri.core.geometry.GeometryEngine; +import com.esri.core.geometry.MultiPath; +import com.esri.core.geometry.Operator; +import com.esri.core.geometry.OperatorExportToWkb; +import com.esri.core.geometry.OperatorFactoryLocal; +import com.esri.core.geometry.Polyline; +import com.esri.core.geometry.SpatialReference; +import com.esri.core.geometry.WkbExportFlags; +import com.esri.core.geometry.WktExportFlags; +import java.nio.ByteBuffer; + +public class OGCLineString extends OGCCurve { + /** + * The number of Points in this LineString. + */ + public int numPoints() { + if (multiPath.isEmpty()) + return 0; + int d = multiPath.isClosedPath(0) ? 1 : 0; + return multiPath.getPointCount() + d; + } + + @Override + public String asText() { + return GeometryEngine.geometryToWkt(getEsriGeometry(), + WktExportFlags.wktExportLineString); + } + + @Override + public ByteBuffer asBinary() { + OperatorExportToWkb op = (OperatorExportToWkb) OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.ExportToWkb); + return op.execute(WkbExportFlags.wkbExportLineString, + getEsriGeometry(), null); + } + + /** + * Returns the specified Point N in this LineString. + * @param n The 0 based index of the Point. + */ + public OGCPoint pointN(int n) { + int nn; + if (multiPath.isClosedPath(0) && n == multiPath.getPathSize(0)) { + nn = multiPath.getPathStart(0); + } else + nn = n + multiPath.getPathStart(0); + + return (OGCPoint) OGCGeometry.createFromEsriGeometry( + multiPath.getPoint(nn), esriSR); + } + + @Override + public boolean isClosed() { + return multiPath.isClosedPathInXYPlane(0); + } + + public OGCLineString(MultiPath mp, int pathIndex, SpatialReference sr) { + multiPath = new Polyline(); + if (!mp.isEmpty()) + multiPath.addPath(mp, pathIndex, true); + esriSR = sr; + } + + public OGCLineString(MultiPath mp, int pathIndex, SpatialReference sr, + boolean reversed) { + multiPath = new Polyline(); + if (!mp.isEmpty()) + multiPath.addPath(mp, pathIndex, !reversed); + esriSR = sr; + } + + @Override + public double length() { + return multiPath.calculateLength2D(); + } + + @Override + public OGCPoint startPoint() { + return pointN(0); + } + + @Override + public OGCPoint endPoint() { + return pointN(numPoints() - 1); + } + + @Override + public String geometryType() { + return "LineString"; + } + + @Override + public OGCGeometry locateAlong(double mValue) { + throw new UnsupportedOperationException(); + } + + @Override + public OGCGeometry locateBetween(double mStart, double mEnd) { + throw new UnsupportedOperationException(); + } + + @Override + public Geometry getEsriGeometry() { + return multiPath; + } + + MultiPath multiPath; +} diff --git a/src/com/esri/core/geometry/ogc/OGCLinearRing.java b/src/com/esri/core/geometry/ogc/OGCLinearRing.java new file mode 100644 index 00000000..e733f6a5 --- /dev/null +++ b/src/com/esri/core/geometry/ogc/OGCLinearRing.java @@ -0,0 +1,40 @@ +package com.esri.core.geometry.ogc; + +import com.esri.core.geometry.MultiPath; +import com.esri.core.geometry.SpatialReference; + +public class OGCLinearRing extends OGCLineString { + + public OGCLinearRing(MultiPath mp, int pathIndex, SpatialReference sr, + boolean reversed) { + super(mp, pathIndex, sr, reversed); + if (!mp.isClosedPath(0)) + throw new IllegalArgumentException("LinearRing path must be closed"); + } + + public int numPoints() { + if (multiPath.isEmpty()) + return 0; + return multiPath.getPointCount() + 1; + } + + public boolean isClosed() { + return true; + } + + public boolean isRing() { + return true; + } + + @Override + public OGCPoint pointN(int n) { + int nn; + if (n == multiPath.getPathSize(0)) { + nn = multiPath.getPathStart(0); + } else + nn = multiPath.getPathStart(0) + n; + + return (OGCPoint) OGCGeometry.createFromEsriGeometry( + multiPath.getPoint(nn), esriSR); + } +} diff --git a/src/com/esri/core/geometry/ogc/OGCMultiCurve.java b/src/com/esri/core/geometry/ogc/OGCMultiCurve.java new file mode 100644 index 00000000..1084d3e2 --- /dev/null +++ b/src/com/esri/core/geometry/ogc/OGCMultiCurve.java @@ -0,0 +1,24 @@ +package com.esri.core.geometry.ogc; + +import com.esri.core.geometry.MultiPath; + +public abstract class OGCMultiCurve extends OGCGeometryCollection { + public int numGeometries() { + MultiPath mp = (MultiPath) getEsriGeometry(); + return mp.getPathCount(); + } + + public boolean isClosed() { + MultiPath mp = (MultiPath) getEsriGeometry(); + for (int i = 0, n = mp.getPathCount(); i < n; i++) { + if (!mp.isClosedPathInXYPlane(i)) + return false; + } + + return true; + } + + public double length() { + return getEsriGeometry().calculateLength2D(); + } +} diff --git a/src/com/esri/core/geometry/ogc/OGCMultiLineString.java b/src/com/esri/core/geometry/ogc/OGCMultiLineString.java new file mode 100644 index 00000000..4e9e4a65 --- /dev/null +++ b/src/com/esri/core/geometry/ogc/OGCMultiLineString.java @@ -0,0 +1,73 @@ +package com.esri.core.geometry.ogc; + +import com.esri.core.geometry.Geometry; +import com.esri.core.geometry.GeometryEngine; +import com.esri.core.geometry.Operator; +import com.esri.core.geometry.OperatorBoundary; +import com.esri.core.geometry.OperatorExportToWkb; +import com.esri.core.geometry.OperatorFactoryLocal; +import com.esri.core.geometry.Polyline; +import com.esri.core.geometry.SpatialReference; +import com.esri.core.geometry.WkbExportFlags; +import com.esri.core.geometry.WktExportFlags; +import java.nio.ByteBuffer; + +public class OGCMultiLineString extends OGCMultiCurve { + + public OGCMultiLineString(Polyline poly, SpatialReference sr) { + polyline = poly; + esriSR = sr; + } + + @Override + public String asText() { + return GeometryEngine.geometryToWkt(getEsriGeometry(), + WktExportFlags.wktExportMultiLineString); + } + + @Override + public ByteBuffer asBinary() { + OperatorExportToWkb op = (OperatorExportToWkb) OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.ExportToWkb); + return op.execute(WkbExportFlags.wkbExportMultiLineString, + getEsriGeometry(), null); + } + + @Override + public OGCGeometry geometryN(int n) { + OGCLineString ls = new OGCLineString(polyline, n, esriSR); + return ls; + } + + @Override + public String geometryType() { + return "MultiLineString"; + } + + @Override + public OGCGeometry boundary() { + OperatorBoundary op = (OperatorBoundary) OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Boundary); + Geometry g = op.execute(polyline, null); + return OGCGeometry.createFromEsriGeometry(g, esriSR, true); + } + + @Override + public OGCGeometry locateAlong(double mValue) { + // TODO Auto-generated method stub + throw new UnsupportedOperationException(); + } + + @Override + public OGCGeometry locateBetween(double mStart, double mEnd) { + // TODO Auto-generated method stub + throw new UnsupportedOperationException(); + } + + @Override + public Geometry getEsriGeometry() { + return polyline; + } + + Polyline polyline; +} diff --git a/src/com/esri/core/geometry/ogc/OGCMultiPoint.java b/src/com/esri/core/geometry/ogc/OGCMultiPoint.java new file mode 100644 index 00000000..fa3f7c83 --- /dev/null +++ b/src/com/esri/core/geometry/ogc/OGCMultiPoint.java @@ -0,0 +1,99 @@ +package com.esri.core.geometry.ogc; + +import java.nio.ByteBuffer; + +import com.esri.core.geometry.Geometry; +import com.esri.core.geometry.GeometryCursor; +import com.esri.core.geometry.GeometryEngine; +import com.esri.core.geometry.MultiPoint; +import com.esri.core.geometry.Operator; +import com.esri.core.geometry.OperatorExportToWkb; +import com.esri.core.geometry.OperatorFactoryLocal; +import com.esri.core.geometry.OperatorUnion; +import com.esri.core.geometry.Point; +import com.esri.core.geometry.SpatialReference; +import com.esri.core.geometry.WkbExportFlags; +import com.esri.core.geometry.WktExportFlags; + +public class OGCMultiPoint extends OGCGeometryCollection { + public int numGeometries() { + return multiPoint.getPointCount(); + } + + @Override + public String asText() { + return GeometryEngine.geometryToWkt(getEsriGeometry(), + WktExportFlags.wktExportMultiPoint); + } + + @Override + public ByteBuffer asBinary() { + OperatorExportToWkb op = (OperatorExportToWkb) OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.ExportToWkb); + return op.execute(WkbExportFlags.wkbExportMultiPoint, + getEsriGeometry(), null); + } + + public OGCGeometry geometryN(int n) { + return OGCGeometry.createFromEsriGeometry(multiPoint.getPoint(n), + esriSR); + } + + @Override + public String geometryType() { + return "MultiPoint"; + } + + /** + * + * @param mp + * MultiPoint instance will be referenced by this OGC class + */ + public OGCMultiPoint(MultiPoint mp, SpatialReference sr) { + multiPoint = mp; + esriSR = sr; + } + + public OGCMultiPoint(Point startPoint, SpatialReference sr) { + multiPoint = new MultiPoint(); + multiPoint.add((Point) startPoint); + esriSR = sr; + } + + public OGCMultiPoint(OGCPoint startPoint, OGCPoint endPoint) { + multiPoint = new MultiPoint(); + multiPoint.add((Point) startPoint.getEsriGeometry()); + multiPoint.add((Point) endPoint.getEsriGeometry()); + esriSR = startPoint.esriSR; + } + + public OGCMultiPoint(SpatialReference sr) { + esriSR = sr; + multiPoint = new MultiPoint(); + } + + @Override + public OGCGeometry boundary() { + return new OGCMultiPoint((MultiPoint) multiPoint.createInstance(), + esriSR);// return empty multipoint + } + + @Override + public OGCGeometry locateAlong(double mValue) { + // TODO Auto-generated method stub + throw new UnsupportedOperationException(); + } + + @Override + public OGCGeometry locateBetween(double mStart, double mEnd) { + // TODO Auto-generated method stub + throw new UnsupportedOperationException(); + } + + @Override + public Geometry getEsriGeometry() { + return multiPoint; + } + + private com.esri.core.geometry.MultiPoint multiPoint; +} diff --git a/src/com/esri/core/geometry/ogc/OGCMultiPolygon.java b/src/com/esri/core/geometry/ogc/OGCMultiPolygon.java new file mode 100644 index 00000000..3df9c947 --- /dev/null +++ b/src/com/esri/core/geometry/ogc/OGCMultiPolygon.java @@ -0,0 +1,87 @@ +package com.esri.core.geometry.ogc; + +import com.esri.core.geometry.Geometry; +import com.esri.core.geometry.GeometryEngine; +import com.esri.core.geometry.Operator; +import com.esri.core.geometry.OperatorExportToWkb; +import com.esri.core.geometry.OperatorFactoryLocal; +import com.esri.core.geometry.Polygon; +import com.esri.core.geometry.Polyline; +import com.esri.core.geometry.SpatialReference; +import com.esri.core.geometry.WkbExportFlags; +import com.esri.core.geometry.WktExportFlags; +import java.nio.ByteBuffer; + +public class OGCMultiPolygon extends OGCMultiSurface { + + public OGCMultiPolygon(Polygon src, SpatialReference sr) { + polygon = src; + esriSR = sr; + } + + @Override + public String asText() { + return GeometryEngine.geometryToWkt(getEsriGeometry(), + WktExportFlags.wktExportMultiPolygon); + } + + @Override + public ByteBuffer asBinary() { + OperatorExportToWkb op = (OperatorExportToWkb) OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.ExportToWkb); + return op.execute(WkbExportFlags.wkbExportMultiPolygon, + getEsriGeometry(), null); + } + + @Override + public int numGeometries() { + return polygon.getExteriorRingCount(); + } + + @Override + public OGCGeometry geometryN(int n) { + int exterior = 0; + for (int i = 0; i < polygon.getPathCount(); i++) { + if (polygon.isExteriorRing(i)) + exterior++; + + if (exterior == i + 1) { + return new OGCPolygon(polygon, i, esriSR); + } + } + + throw new IllegalArgumentException("geometryN: n out of range"); + } + + @Override + public String geometryType() { + return "MultiPolygon"; + } + + @Override + public OGCGeometry boundary() { + Polyline polyline = new Polyline(); + polyline.add(polygon, true); // adds reversed path + return (OGCMultiCurve) OGCGeometry.createFromEsriGeometry(polyline, + esriSR, true); + } + + @Override + public OGCGeometry locateAlong(double mValue) { + // TODO Auto-generated method stub + throw new UnsupportedOperationException(); + } + + @Override + public OGCGeometry locateBetween(double mStart, double mEnd) { + // TODO Auto-generated method stub + throw new UnsupportedOperationException(); + } + + @Override + public Geometry getEsriGeometry() { + return polygon; + } + + Polygon polygon; +} diff --git a/src/com/esri/core/geometry/ogc/OGCMultiSurface.java b/src/com/esri/core/geometry/ogc/OGCMultiSurface.java new file mode 100644 index 00000000..fbb71977 --- /dev/null +++ b/src/com/esri/core/geometry/ogc/OGCMultiSurface.java @@ -0,0 +1,17 @@ +package com.esri.core.geometry.ogc; + +public abstract class OGCMultiSurface extends OGCGeometryCollection { + public double area() { + return getEsriGeometry().calculateArea2D(); + } + + public OGCPoint centroid() { + // TODO + throw new UnsupportedOperationException(); + } + + public OGCPoint pointOnSurface() { + // TODO + throw new UnsupportedOperationException(); + } +} diff --git a/src/com/esri/core/geometry/ogc/OGCPoint.java b/src/com/esri/core/geometry/ogc/OGCPoint.java new file mode 100644 index 00000000..d04bbc62 --- /dev/null +++ b/src/com/esri/core/geometry/ogc/OGCPoint.java @@ -0,0 +1,82 @@ +package com.esri.core.geometry.ogc; + +import java.nio.ByteBuffer; + +import com.esri.core.geometry.GeometryCursor; +import com.esri.core.geometry.GeometryEngine; +import com.esri.core.geometry.MultiPoint; +import com.esri.core.geometry.Operator; +import com.esri.core.geometry.OperatorExportToWkb; +import com.esri.core.geometry.OperatorFactoryLocal; +import com.esri.core.geometry.Point; +import com.esri.core.geometry.SpatialReference; +import com.esri.core.geometry.WkbExportFlags; +import com.esri.core.geometry.WktExportFlags; + +public final class OGCPoint extends OGCGeometry { + public OGCPoint(Point pt, SpatialReference sr) { + point = pt; + esriSR = sr; + } + + @Override + public String asText() { + return GeometryEngine.geometryToWkt(getEsriGeometry(), + WktExportFlags.wktExportPoint); + } + + @Override + public ByteBuffer asBinary() { + OperatorExportToWkb op = (OperatorExportToWkb) OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.ExportToWkb); + return op.execute(WkbExportFlags.wkbExportPoint, getEsriGeometry(), + null); + } + + public double X() { + return point.getX(); + } + + public double Y() { + return point.getY(); + } + + public double Z() { + return point.getZ(); + } + + public double M() { + return point.getM(); + } + + @Override + public String geometryType() { + return "Point"; + } + + @Override + public OGCGeometry boundary() { + return new OGCMultiPoint(new MultiPoint(getEsriGeometry() + .getDescription()), esriSR);// return empty point + } + + @Override + public OGCGeometry locateAlong(double mValue) { + // TODO Auto-generated method stub + throw new UnsupportedOperationException(); + } + + @Override + public OGCGeometry locateBetween(double mStart, double mEnd) { + // TODO Auto-generated method stub + throw new UnsupportedOperationException(); + } + + @Override + public com.esri.core.geometry.Geometry getEsriGeometry() { + return point; + } + + com.esri.core.geometry.Point point; + +} diff --git a/src/com/esri/core/geometry/ogc/OGCPolygon.java b/src/com/esri/core/geometry/ogc/OGCPolygon.java new file mode 100644 index 00000000..c94e6816 --- /dev/null +++ b/src/com/esri/core/geometry/ogc/OGCPolygon.java @@ -0,0 +1,106 @@ +package com.esri.core.geometry.ogc; + +import com.esri.core.geometry.Geometry; +import com.esri.core.geometry.GeometryEngine; +import com.esri.core.geometry.Operator; +import com.esri.core.geometry.OperatorExportToWkb; +import com.esri.core.geometry.OperatorFactoryLocal; +import com.esri.core.geometry.Polygon; +import com.esri.core.geometry.Polyline; +import com.esri.core.geometry.SpatialReference; +import com.esri.core.geometry.WkbExportFlags; +import com.esri.core.geometry.WktExportFlags; +import java.nio.ByteBuffer; + +public class OGCPolygon extends OGCSurface { + public OGCPolygon(Polygon src, int exteriorRing, SpatialReference sr) { + polygon = new Polygon(); + for (int i = exteriorRing, n = src.getPathCount(); i < n; i++) { + if (i > exteriorRing && src.isExteriorRing(i)) + break; + polygon.addPath(src, i, true); + } + esriSR = sr; + } + + public OGCPolygon(Polygon geom, SpatialReference sr) { + polygon = geom; + if (geom.getExteriorRingCount() > 1) + throw new IllegalArgumentException( + "Polygon has to have one exterior ring"); + esriSR = sr; + } + + @Override + public String asText() { + return GeometryEngine.geometryToWkt(getEsriGeometry(), + WktExportFlags.wktExportPolygon); + } + + @Override + public ByteBuffer asBinary() { + OperatorExportToWkb op = (OperatorExportToWkb) OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.ExportToWkb); + return op.execute(WkbExportFlags.wkbExportPolygon, getEsriGeometry(), + null); + } + + /** + * Returns the exterior ring of this Polygon. + * @return OGCLinearRing instance. + */ + public OGCLineString exterorRing() { + if (polygon.isEmpty()) + return new OGCLinearRing((Polygon) polygon.createInstance(), 0, + esriSR, true); + return new OGCLinearRing(polygon, 0, esriSR, true); + } + + /** + * Returns the number of interior rings in this Polygon. + */ + public int numInteriorRing() { + return polygon.getPathCount() - 1; + } + + /** + * Returns the Nth interior ring for this Polygon as a LineString. + * @param n The 0 based index of the interior ring. + * @return OGCLinearRing instance. + */ + public OGCLineString interiorRingN(int n) { + return new OGCLinearRing(polygon, n + 1, esriSR, true); + } + + @Override + public OGCMultiCurve boundary() { + Polyline polyline = new Polyline(); + polyline.add(polygon, true); // adds reversed path + return (OGCMultiCurve) OGCGeometry.createFromEsriGeometry(polyline, + esriSR, true); + } + + @Override + public String geometryType() { + return "Polygon"; + } + + @Override + public OGCGeometry locateAlong(double mValue) { + // TODO Auto-generated method stub + throw new UnsupportedOperationException(); + } + + @Override + public OGCGeometry locateBetween(double mStart, double mEnd) { + // TODO Auto-generated method stub + throw new UnsupportedOperationException(); + } + + @Override + public Geometry getEsriGeometry() { + return polygon; + } + + Polygon polygon; +} diff --git a/src/com/esri/core/geometry/ogc/OGCSurface.java b/src/com/esri/core/geometry/ogc/OGCSurface.java new file mode 100644 index 00000000..99886778 --- /dev/null +++ b/src/com/esri/core/geometry/ogc/OGCSurface.java @@ -0,0 +1,20 @@ +package com.esri.core.geometry.ogc; + +public abstract class OGCSurface extends OGCGeometry { + public double area() { + return getEsriGeometry().calculateArea2D(); + } + + public OGCPoint centroid() { + // TODO: implement me; + throw new UnsupportedOperationException(); + } + + public OGCPoint pointOnSurface() { + // TODO: support this (need to port OperatorLabelPoint) + throw new UnsupportedOperationException(); + } + + public abstract OGCMultiCurve boundary(); + +} diff --git a/src/com/esri/core/geometry/pcs_id_to_tolerance.txt b/src/com/esri/core/geometry/pcs_id_to_tolerance.txt new file mode 100644 index 00000000..f38609e5 --- /dev/null +++ b/src/com/esri/core/geometry/pcs_id_to_tolerance.txt @@ -0,0 +1,4549 @@ +2000 3 +2001 3 +2002 3 +2003 3 +2004 3 +2005 3 +2006 3 +2007 3 +2008 3 +2009 3 +2010 3 +2011 3 +2012 3 +2013 3 +2014 3 +2015 3 +2016 3 +2017 3 +2018 3 +2019 3 +2020 3 +2021 3 +2022 3 +2023 3 +2024 3 +2025 3 +2026 3 +2027 3 +2028 3 +2029 3 +2030 3 +2031 3 +2032 3 +2033 3 +2034 3 +2035 3 +2036 3 +2037 3 +2038 3 +2039 3 +2040 3 +2041 3 +2042 3 +2043 3 +2044 3 +2045 3 +2056 3 +2057 3 +2058 3 +2059 3 +2060 3 +2061 3 +2062 3 +2063 3 +2064 3 +2065 3 +2066 6 +2067 3 +2068 3 +2069 3 +2070 3 +2071 3 +2072 3 +2073 3 +2074 3 +2075 3 +2076 3 +2077 3 +2078 3 +2079 3 +2080 3 +2081 3 +2082 3 +2083 3 +2084 3 +2085 3 +2086 3 +2087 3 +2088 3 +2089 3 +2090 3 +2091 3 +2092 3 +2093 3 +2094 3 +2095 3 +2096 3 +2097 3 +2098 3 +2099 3 +2100 3 +2101 3 +2102 3 +2103 3 +2104 3 +2105 3 +2106 3 +2107 3 +2108 3 +2109 3 +2110 3 +2111 3 +2112 3 +2113 3 +2114 3 +2115 3 +2116 3 +2117 3 +2118 3 +2119 3 +2120 3 +2121 3 +2122 3 +2123 3 +2124 3 +2125 3 +2126 3 +2127 3 +2128 3 +2129 3 +2130 3 +2131 3 +2132 3 +2133 3 +2134 3 +2135 3 +2136 5 +2137 3 +2138 3 +2139 3 +2140 3 +2141 3 +2142 3 +2143 3 +2144 3 +2145 3 +2146 3 +2147 3 +2148 3 +2149 3 +2150 3 +2151 3 +2152 3 +2153 3 +2155 5 +2157 3 +2158 3 +2159 5 +2160 5 +2161 3 +2162 3 +2163 3 +2164 3 +2165 3 +2166 3 +2167 3 +2168 3 +2169 3 +2170 3 +2172 3 +2173 3 +2174 3 +2175 3 +2176 3 +2177 3 +2178 3 +2179 3 +2180 3 +2181 3 +2182 3 +2183 3 +2184 3 +2185 3 +2186 3 +2187 3 +2188 3 +2189 3 +2190 3 +2191 3 +2192 3 +2193 3 +2196 3 +2197 3 +2198 3 +2200 3 +2201 3 +2202 3 +2203 3 +2206 3 +2207 3 +2208 3 +2209 3 +2210 3 +2211 3 +2212 3 +2213 3 +2214 3 +2215 3 +2216 3 +2217 3 +2219 3 +2220 3 +2222 5 +2223 5 +2224 5 +2244 5 +2245 5 +2251 5 +2252 5 +2253 5 +2256 5 +2265 5 +2266 5 +2269 5 +2270 5 +2273 5 +2280 5 +2281 5 +2282 5 +2290 3 +2291 3 +2294 3 +2295 3 +2308 3 +2309 3 +2310 3 +2311 3 +2313 3 +2314 5 +2315 3 +2316 3 +2317 3 +2318 3 +2319 3 +2320 3 +2321 3 +2322 3 +2323 3 +2324 3 +2325 3 +2327 3 +2328 3 +2329 3 +2330 3 +2331 3 +2332 3 +2333 3 +2334 3 +2335 3 +2336 3 +2337 3 +2338 3 +2339 3 +2340 3 +2341 3 +2342 3 +2343 3 +2344 3 +2345 3 +2346 3 +2347 3 +2348 3 +2349 3 +2350 3 +2351 3 +2352 3 +2353 3 +2354 3 +2355 3 +2356 3 +2357 3 +2358 3 +2359 3 +2360 3 +2361 3 +2362 3 +2363 3 +2364 3 +2365 3 +2366 3 +2367 3 +2368 3 +2369 3 +2370 3 +2371 3 +2372 3 +2373 3 +2374 3 +2375 3 +2376 3 +2377 3 +2378 3 +2379 3 +2380 3 +2381 3 +2382 3 +2383 3 +2384 3 +2385 3 +2386 3 +2387 3 +2388 3 +2389 3 +2390 3 +2391 3 +2392 3 +2393 3 +2394 3 +2400 3 +2401 3 +2402 3 +2403 3 +2404 3 +2405 3 +2406 3 +2407 3 +2408 3 +2409 3 +2410 3 +2411 3 +2412 3 +2413 3 +2414 3 +2415 3 +2416 3 +2417 3 +2418 3 +2419 3 +2420 3 +2421 3 +2422 3 +2423 3 +2424 3 +2425 3 +2426 3 +2427 3 +2428 3 +2429 3 +2430 3 +2431 3 +2432 3 +2433 3 +2434 3 +2435 3 +2436 3 +2437 3 +2438 3 +2439 3 +2440 3 +2441 3 +2442 3 +2443 3 +2444 3 +2445 3 +2446 3 +2447 3 +2448 3 +2449 3 +2450 3 +2451 3 +2452 3 +2453 3 +2454 3 +2455 3 +2456 3 +2457 3 +2458 3 +2459 3 +2460 3 +2461 3 +2462 3 +2523 3 +2524 3 +2525 3 +2526 3 +2527 3 +2528 3 +2529 3 +2530 3 +2531 3 +2532 3 +2533 3 +2534 3 +2535 3 +2536 3 +2537 3 +2538 3 +2539 3 +2540 3 +2541 3 +2542 3 +2543 3 +2544 3 +2545 3 +2546 3 +2547 3 +2548 3 +2549 3 +2550 3 +2551 3 +2552 3 +2553 3 +2554 3 +2555 3 +2556 3 +2557 3 +2558 3 +2559 3 +2560 3 +2561 3 +2562 3 +2563 3 +2564 3 +2565 3 +2566 3 +2567 3 +2568 3 +2569 3 +2570 3 +2571 3 +2572 3 +2573 3 +2574 3 +2575 3 +2576 3 +2578 3 +2579 3 +2580 3 +2581 3 +2582 3 +2583 3 +2584 3 +2585 3 +2586 3 +2587 3 +2588 3 +2589 3 +2590 3 +2591 3 +2592 3 +2593 3 +2594 3 +2595 3 +2596 3 +2597 3 +2598 3 +2599 3 +2600 3 +2601 3 +2602 3 +2603 3 +2604 3 +2605 3 +2606 3 +2607 3 +2608 3 +2609 3 +2610 3 +2611 3 +2612 3 +2613 3 +2614 3 +2615 3 +2616 3 +2617 3 +2618 3 +2619 3 +2620 3 +2621 3 +2622 3 +2623 3 +2624 3 +2625 3 +2626 3 +2627 3 +2628 3 +2629 3 +2630 3 +2631 3 +2632 3 +2633 3 +2634 3 +2635 3 +2636 3 +2637 3 +2638 3 +2639 3 +2640 3 +2641 3 +2642 3 +2643 3 +2644 3 +2645 3 +2646 3 +2647 3 +2648 3 +2649 3 +2650 3 +2651 3 +2652 3 +2653 3 +2654 3 +2655 3 +2656 3 +2657 3 +2658 3 +2659 3 +2660 3 +2661 3 +2662 3 +2663 3 +2664 3 +2665 3 +2666 3 +2667 3 +2668 3 +2669 3 +2670 3 +2671 3 +2672 3 +2673 3 +2674 3 +2675 3 +2676 3 +2677 3 +2678 3 +2679 3 +2680 3 +2681 3 +2682 3 +2683 3 +2684 3 +2685 3 +2686 3 +2687 3 +2688 3 +2689 3 +2690 3 +2691 3 +2692 3 +2693 3 +2695 3 +2696 3 +2697 3 +2698 3 +2699 3 +2700 3 +2701 3 +2702 3 +2703 3 +2704 3 +2705 3 +2706 3 +2707 3 +2708 3 +2709 3 +2710 3 +2711 3 +2712 3 +2713 3 +2714 3 +2715 3 +2716 3 +2717 3 +2718 3 +2719 3 +2720 3 +2721 3 +2722 3 +2723 3 +2724 3 +2725 3 +2726 3 +2727 3 +2728 3 +2729 3 +2730 3 +2731 3 +2732 3 +2733 3 +2734 3 +2735 3 +2736 3 +2737 3 +2738 3 +2739 3 +2740 3 +2741 3 +2742 3 +2743 3 +2744 3 +2745 3 +2746 3 +2747 3 +2748 3 +2749 3 +2750 3 +2751 3 +2752 3 +2753 3 +2754 3 +2755 3 +2756 3 +2757 3 +2758 3 +2867 5 +2868 5 +2869 5 +2870 5 +2871 5 +2872 5 +2873 5 +2874 5 +2875 5 +2876 5 +2877 5 +2878 5 +2879 5 +2880 5 +2881 5 +2882 5 +2883 5 +2884 5 +2885 5 +2886 5 +2887 5 +2888 5 +2891 5 +2892 5 +2893 5 +2894 5 +2895 5 +2896 5 +2897 5 +2898 5 +2899 5 +2900 5 +2901 5 +2902 5 +2903 5 +2904 5 +2905 5 +2906 5 +2907 5 +2908 5 +2909 5 +2910 5 +2911 5 +2912 5 +2913 5 +2914 5 +2915 5 +2916 5 +2917 5 +2918 5 +2919 5 +2920 5 +2921 5 +2922 5 +2923 5 +2924 5 +2925 5 +2926 5 +2927 5 +2928 5 +2929 5 +2930 5 +2931 3 +2932 3 +2933 3 +2934 3 +2935 3 +2936 3 +2937 3 +2938 3 +2939 3 +2940 3 +2941 3 +2964 5 +2967 5 +2968 5 +2969 3 +2970 3 +2971 3 +2972 3 +2973 3 +2975 3 +2976 3 +2977 3 +2978 3 +2979 3 +2980 3 +2981 3 +2982 3 +2984 3 +2985 3 +2986 3 +2987 3 +2988 3 +2991 3 +2992 5 +2993 3 +2994 5 +2995 3 +2996 3 +2997 3 +2998 3 +2999 3 +3000 3 +3001 3 +3002 3 +3006 3 +3007 3 +3008 3 +3009 3 +3010 3 +3011 3 +3012 3 +3013 3 +3014 3 +3015 3 +3016 3 +3017 3 +3018 3 +3019 3 +3020 3 +3021 3 +3022 3 +3023 3 +3024 3 +3025 3 +3026 3 +3027 3 +3028 3 +3029 3 +3030 3 +3031 3 +3032 3 +3033 3 +3034 3 +3035 3 +3036 3 +3037 3 +3038 3 +3039 3 +3040 3 +3041 3 +3042 3 +3043 3 +3044 3 +3045 3 +3046 3 +3047 3 +3048 3 +3049 3 +3050 3 +3051 3 +3054 3 +3055 3 +3056 3 +3057 3 +3058 3 +3059 3 +3061 3 +3062 3 +3063 3 +3064 3 +3065 3 +3066 3 +3068 3 +3069 3 +3070 3 +3071 3 +3073 3 +3076 3 +3079 3 +3084 3 +3085 3 +3086 3 +3087 3 +3091 5 +3106 3 +3108 3 +3109 3 +3112 3 +3113 3 +3114 3 +3115 3 +3116 3 +3117 3 +3118 3 +3120 3 +3121 3 +3122 3 +3123 3 +3124 3 +3125 3 +3126 3 +3127 3 +3128 3 +3129 3 +3130 3 +3131 3 +3132 3 +3133 3 +3134 3 +3135 3 +3136 3 +3137 3 +3138 3 +3141 3 +3142 3 +3146 3 +3147 3 +3148 3 +3149 3 +3150 3 +3151 3 +3153 3 +3154 3 +3155 3 +3156 3 +3157 3 +3161 3 +3162 3 +3163 3 +3164 3 +3165 3 +3166 3 +3167 2 +3168 3 +3169 3 +3170 3 +3171 3 +3172 3 +3174 3 +3175 3 +3176 3 +3177 3 +3178 3 +3179 3 +3180 3 +3181 3 +3182 3 +3183 3 +3184 3 +3185 3 +3186 3 +3187 3 +3188 3 +3189 3 +3190 3 +3191 3 +3192 3 +3193 3 +3194 3 +3195 3 +3196 3 +3197 3 +3198 3 +3199 3 +3200 3 +3201 3 +3202 3 +3203 3 +3294 3 +3295 3 +3296 3 +3297 3 +3298 3 +3299 3 +3300 3 +3301 3 +3302 3 +3303 3 +3304 3 +3305 3 +3306 3 +3307 3 +3308 3 +3309 3 +3310 3 +3311 3 +3312 3 +3313 3 +3315 3 +3316 3 +3317 3 +3318 3 +3319 3 +3320 3 +3321 3 +3322 3 +3323 3 +3324 3 +3325 3 +3326 3 +3327 3 +3328 3 +3329 3 +3330 3 +3331 3 +3332 3 +3333 3 +3334 3 +3335 3 +3337 3 +3339 3 +3340 3 +3341 3 +3342 3 +3343 3 +3344 3 +3345 3 +3347 3 +3348 3 +3350 3 +3351 3 +3352 3 +3353 3 +3354 3 +3355 3 +3356 3 +3357 3 +3358 3 +3359 5 +3360 3 +3361 5 +3362 3 +3363 5 +3364 3 +3365 5 +3366 5 +3367 3 +3368 3 +3369 3 +3374 3 +3375 3 +3376 3 +3377 3 +3378 3 +3379 3 +3380 3 +3381 3 +3382 3 +3383 3 +3384 3 +3385 3 +3386 3 +3387 3 +3388 3 +3389 3 +3390 3 +3391 3 +3392 3 +3393 3 +3394 3 +3395 3 +3396 3 +3397 3 +3398 3 +3399 3 +3402 3 +3403 3 +3405 3 +3406 3 +3408 3 +3409 3 +3410 3 +3411 3 +3412 3 +3413 3 +3414 3 +3415 3 +3416 3 +3425 5 +3426 5 +3427 5 +3428 5 +3429 5 +3430 5 +3431 5 +3432 5 +3439 3 +3440 3 +3441 5 +3442 5 +3443 5 +3444 5 +3445 5 +3446 5 +3447 3 +3449 3 +3450 3 +3453 5 +3454 5 +3456 5 +3457 5 +3458 5 +3459 5 +3460 3 +3465 3 +3466 3 +3467 3 +3468 3 +3469 3 +3470 3 +3471 3 +3472 3 +3473 3 +3474 3 +3475 3 +3476 3 +3477 3 +3478 3 +3479 5 +3480 3 +3481 5 +3482 3 +3483 5 +3484 3 +3485 5 +3486 3 +3487 5 +3488 3 +3489 3 +3490 5 +3491 3 +3492 5 +3493 3 +3494 5 +3495 3 +3496 5 +3497 3 +3498 5 +3499 3 +3500 5 +3501 3 +3502 5 +3503 3 +3504 5 +3505 3 +3506 5 +3507 3 +3508 5 +3509 3 +3510 5 +3511 3 +3512 5 +3513 3 +3514 3 +3515 5 +3516 3 +3517 5 +3518 3 +3519 5 +3520 3 +3521 5 +3522 3 +3523 5 +3524 3 +3525 5 +3526 3 +3527 5 +3528 3 +3529 5 +3530 3 +3531 5 +3532 3 +3533 5 +3534 3 +3535 5 +3536 3 +3537 5 +3538 3 +3539 5 +3540 3 +3541 5 +3542 3 +3543 5 +3544 3 +3545 5 +3546 3 +3547 5 +3548 3 +3549 5 +3550 3 +3551 5 +3552 3 +3553 5 +3554 3 +3555 3 +3556 3 +3557 3 +3558 3 +3559 3 +3561 5 +3562 5 +3563 5 +3564 5 +3565 5 +3568 5 +3569 5 +3570 5 +3571 3 +3572 3 +3573 3 +3574 3 +3575 3 +3576 3 +3577 3 +3578 3 +3579 3 +3580 3 +3581 3 +3582 5 +3583 3 +3584 5 +3585 3 +3586 5 +3587 3 +3588 5 +3589 3 +3590 5 +3591 3 +3592 3 +3593 5 +3594 3 +3595 3 +3596 3 +3597 3 +3598 5 +3599 3 +3600 5 +3601 3 +3602 3 +3603 3 +3604 3 +3605 5 +3606 3 +3607 3 +3608 5 +3609 3 +3610 5 +3611 3 +3612 5 +3613 3 +3614 5 +3615 3 +3616 5 +3617 3 +3618 5 +3619 3 +3620 5 +3621 3 +3622 5 +3623 3 +3624 5 +3625 3 +3626 5 +3627 3 +3628 5 +3629 3 +3630 5 +3631 3 +3632 5 +3633 3 +3634 5 +3635 3 +3636 5 +3637 3 +3638 3 +3639 3 +3640 5 +3641 3 +3642 5 +3643 3 +3644 5 +3645 3 +3646 5 +3647 3 +3648 5 +3649 3 +3650 5 +3651 3 +3652 5 +3653 3 +3654 5 +3655 3 +3656 5 +3657 3 +3658 5 +3659 3 +3660 5 +3661 3 +3662 5 +3663 3 +3664 5 +3665 3 +3666 3 +3667 3 +3668 5 +3669 3 +3670 5 +3671 3 +3672 5 +3673 3 +3674 5 +3675 3 +3676 5 +3677 5 +3678 3 +3679 5 +3680 5 +3681 3 +3682 5 +3683 5 +3684 3 +3685 3 +3686 5 +3687 3 +3688 5 +3689 3 +3690 5 +3691 3 +3692 5 +3693 3 +3694 3 +3695 3 +3696 5 +3697 3 +3698 5 +3699 3 +3700 5 +3701 3 +3702 3 +3703 3 +3704 3 +3705 3 +3706 3 +3707 3 +3708 3 +3709 3 +3710 3 +3711 3 +3712 3 +3713 3 +3714 3 +3715 3 +3716 3 +3717 3 +3718 3 +3719 3 +3720 3 +3721 3 +3722 3 +3723 3 +3724 3 +3725 3 +3726 3 +3727 3 +3728 5 +3729 5 +3730 5 +3731 5 +3732 5 +3733 5 +3740 3 +3744 3 +3745 3 +3746 3 +3747 3 +3749 3 +3753 5 +3754 5 +3755 5 +3756 5 +3757 5 +3758 5 +3761 3 +3762 3 +3763 3 +3765 3 +3766 3 +3767 3 +3768 3 +3769 3 +3779 3 +3780 3 +3781 3 +3783 3 +3784 3 +3788 3 +3789 3 +3790 3 +3791 3 +3793 3 +3794 3 +3797 3 +3798 3 +3799 3 +3802 3 +3816 3 +3829 3 +3832 3 +3833 3 +3834 3 +3835 3 +3836 3 +3837 3 +3838 3 +3839 3 +3840 3 +3841 3 +3844 3 +3845 3 +3846 3 +3847 3 +3848 3 +3849 3 +3850 3 +3851 3 +3852 3 +3854 3 +3873 3 +3874 3 +3875 3 +3876 3 +3877 3 +3878 3 +3879 3 +3880 3 +3881 3 +3882 3 +3883 3 +3884 3 +3885 3 +3890 3 +3891 3 +3892 3 +3893 3 +3907 3 +3908 3 +3909 3 +3910 3 +3911 3 +3912 3 +3920 3 +3942 3 +3943 3 +3944 3 +3945 3 +3946 3 +3947 3 +3948 3 +3949 3 +3950 3 +3968 3 +3969 3 +3970 3 +3973 3 +3974 3 +3975 3 +3976 3 +3978 3 +3979 3 +3986 3 +3987 3 +3988 3 +3989 3 +3991 5 +3992 5 +3994 3 +3995 3 +3996 3 +3997 3 +4026 3 +4037 3 +4038 3 +4217 5 +4399 5 +4400 5 +4401 5 +4402 5 +4403 5 +4404 5 +4405 5 +4406 5 +4407 5 +4408 5 +4409 5 +4410 5 +4411 5 +4412 5 +4413 5 +4418 5 +4419 5 +4420 5 +4421 5 +4422 5 +4423 5 +4424 5 +4425 5 +4426 5 +4427 5 +4428 5 +4429 5 +4430 5 +4431 5 +4432 5 +4433 5 +4438 5 +4439 5 +4467 3 +4471 3 +4474 3 +4491 3 +4492 3 +4493 3 +4494 3 +4495 3 +4496 3 +4497 3 +4498 3 +4499 3 +4500 3 +4501 3 +4502 3 +4503 3 +4504 3 +4505 3 +4506 3 +4507 3 +4508 3 +4509 3 +4510 3 +4511 3 +4512 3 +4513 3 +4514 3 +4515 3 +4516 3 +4517 3 +4518 3 +4519 3 +4520 3 +4521 3 +4522 3 +4523 3 +4524 3 +4525 3 +4526 3 +4527 3 +4528 3 +4529 3 +4530 3 +4531 3 +4532 3 +4533 3 +4534 3 +4535 3 +4536 3 +4537 3 +4538 3 +4539 3 +4540 3 +4541 3 +4542 3 +4543 3 +4544 3 +4545 3 +4546 3 +4547 3 +4548 3 +4549 3 +4550 3 +4551 3 +4552 3 +4553 3 +4554 3 +4559 3 +4839 3 +5018 3 +5048 3 +5069 3 +5070 3 +5071 3 +5072 3 +5105 3 +5106 3 +5107 3 +5108 3 +5109 3 +5110 3 +5111 3 +5112 3 +5113 3 +5114 3 +5115 3 +5116 3 +5117 3 +5118 3 +5119 3 +5120 3 +5121 3 +5122 3 +5123 3 +5124 3 +5125 3 +5126 3 +5127 3 +5128 3 +5129 3 +5130 3 +5167 3 +5168 3 +5180 3 +5181 3 +5182 3 +5183 3 +5184 3 +5223 3 +5234 3 +5235 3 +5243 3 +5253 3 +5254 3 +5255 3 +5256 3 +5257 3 +5258 3 +5259 3 +5266 3 +5269 3 +5270 3 +5271 3 +5272 3 +5273 3 +5274 3 +5275 3 +5292 3 +5293 3 +5294 3 +5295 3 +5296 3 +5297 3 +5298 3 +5299 3 +5300 3 +5301 3 +5302 3 +5303 3 +5304 3 +5305 3 +5306 3 +5307 3 +5308 3 +5309 3 +5310 3 +5311 3 +5316 3 +5320 3 +5321 3 +5330 3 +5331 3 +5337 3 +5343 3 +5344 3 +5345 3 +5346 3 +5347 3 +5348 3 +5349 3 +5355 3 +5356 3 +5357 3 +5361 3 +5362 3 +5382 3 +5383 3 +5387 3 +5388 3 +5389 3 +5396 3 +5456 3 +5457 3 +5459 3 +5460 3 +5461 3 +5462 3 +5463 3 +5469 3 +5472 4 +5479 3 +5480 3 +5481 3 +5482 3 +5490 3 +5518 3 +5523 3 +5530 3 +5531 3 +5532 3 +5533 3 +5534 3 +5535 3 +5536 3 +5537 3 +5538 3 +5539 3 +5550 3 +5551 3 +5552 3 +5559 3 +5562 3 +5563 3 +5564 3 +5565 3 +5566 3 +5567 3 +5568 3 +5569 3 +5570 3 +5571 3 +5572 3 +5573 3 +5574 3 +5575 3 +5576 3 +5577 3 +5578 3 +5579 3 +5580 3 +5581 3 +5582 3 +5583 3 +5588 5 +5589 5 +5596 3 +5623 5 +5624 5 +5625 5 +5627 3 +5629 3 +5631 3 +5632 3 +5633 3 +5634 3 +5635 3 +5636 3 +5637 3 +5638 3 +5639 3 +5641 3 +5643 3 +5644 3 +5649 3 +5650 3 +5651 3 +5652 3 +5653 3 +5654 5 +5655 5 +5659 3 +5663 3 +5664 3 +5665 3 +5666 3 +5667 3 +5668 3 +5669 3 +5670 3 +5671 3 +5672 3 +5673 3 +5674 3 +5675 3 +5676 3 +5677 3 +5678 3 +5679 3 +5680 3 +5682 3 +5683 3 +5684 3 +5685 3 +5700 3 +5825 3 +5836 3 +5837 3 +5842 3 +5844 3 +5875 3 +5876 3 +5877 3 +5880 3 +5887 3 +5890 3 +20002 3 +20003 3 +20004 3 +20005 3 +20006 3 +20007 3 +20008 3 +20009 3 +20010 3 +20011 3 +20012 3 +20013 3 +20014 3 +20015 3 +20016 3 +20017 3 +20018 3 +20019 3 +20020 3 +20021 3 +20022 3 +20023 3 +20024 3 +20025 3 +20026 3 +20027 3 +20028 3 +20029 3 +20030 3 +20031 3 +20032 3 +20062 3 +20063 3 +20064 3 +20065 3 +20066 3 +20067 3 +20068 3 +20069 3 +20070 3 +20071 3 +20072 3 +20073 3 +20074 3 +20075 3 +20076 3 +20077 3 +20078 3 +20079 3 +20080 3 +20081 3 +20082 3 +20083 3 +20084 3 +20085 3 +20086 3 +20087 3 +20088 3 +20089 3 +20090 3 +20091 3 +20092 3 +20135 3 +20136 3 +20137 3 +20138 3 +20248 3 +20249 3 +20250 3 +20251 3 +20252 3 +20253 3 +20254 3 +20255 3 +20256 3 +20257 3 +20258 3 +20348 3 +20349 3 +20350 3 +20351 3 +20352 3 +20353 3 +20354 3 +20355 3 +20356 3 +20357 3 +20358 3 +20436 3 +20437 3 +20438 3 +20439 3 +20440 3 +20499 3 +20538 3 +20539 3 +20790 3 +20791 3 +20822 3 +20823 3 +20824 3 +20934 3 +20935 3 +20936 3 +21035 3 +21036 3 +21037 3 +21095 3 +21096 3 +21097 3 +21148 3 +21149 3 +21150 3 +21291 3 +21292 3 +21413 3 +21414 3 +21415 3 +21416 3 +21417 3 +21418 3 +21419 3 +21420 3 +21421 3 +21422 3 +21423 3 +21473 3 +21474 3 +21475 3 +21476 3 +21477 3 +21478 3 +21479 3 +21480 3 +21481 3 +21482 3 +21483 3 +21500 3 +21780 3 +21781 3 +21782 3 +21817 3 +21818 3 +21891 3 +21892 3 +21893 3 +21894 3 +22032 3 +22033 3 +22091 3 +22092 3 +22171 3 +22172 3 +22173 3 +22174 3 +22175 3 +22176 3 +22177 3 +22181 3 +22182 3 +22183 3 +22184 3 +22185 3 +22186 3 +22187 3 +22191 3 +22192 3 +22193 3 +22194 3 +22195 3 +22196 3 +22197 3 +22234 3 +22235 3 +22236 3 +22332 3 +22391 3 +22392 3 +22521 3 +22522 3 +22523 3 +22524 3 +22525 3 +22700 3 +22770 3 +22780 3 +22832 3 +22991 3 +22992 3 +22993 3 +22994 3 +23028 3 +23029 3 +23030 3 +23031 3 +23032 3 +23033 3 +23034 3 +23035 3 +23036 3 +23037 3 +23038 3 +23090 3 +23095 3 +23239 3 +23240 3 +23433 3 +23700 3 +23830 3 +23831 3 +23832 3 +23833 3 +23834 3 +23835 3 +23836 3 +23837 3 +23838 3 +23839 3 +23840 3 +23841 3 +23842 3 +23843 3 +23844 3 +23845 3 +23846 3 +23847 3 +23848 3 +23849 3 +23850 3 +23851 3 +23852 3 +23853 3 +23866 3 +23867 3 +23868 3 +23869 3 +23870 3 +23871 3 +23872 3 +23877 3 +23878 3 +23879 3 +23880 3 +23881 3 +23882 3 +23883 3 +23884 3 +23886 3 +23887 3 +23888 3 +23889 3 +23890 3 +23891 3 +23892 3 +23893 3 +23894 3 +23946 3 +23947 3 +23948 3 +24047 3 +24048 3 +24100 5 +24200 3 +24305 3 +24306 3 +24311 3 +24312 3 +24313 3 +24342 3 +24343 3 +24344 3 +24345 3 +24346 3 +24347 3 +24370 4 +24371 4 +24372 4 +24373 4 +24374 4 +24375 3 +24376 3 +24377 3 +24378 3 +24379 3 +24380 3 +24381 3 +24382 4 +24383 3 +24500 3 +24547 3 +24548 3 +24571 2 +24600 3 +24718 3 +24719 3 +24720 3 +24721 3 +24817 3 +24818 3 +24819 3 +24820 3 +24821 3 +24877 3 +24878 3 +24879 3 +24880 3 +24881 3 +24882 3 +24891 3 +24892 3 +24893 3 +25000 3 +25231 3 +25391 3 +25392 3 +25393 3 +25394 3 +25395 3 +25828 3 +25829 3 +25830 3 +25831 3 +25832 3 +25833 3 +25834 3 +25835 3 +25836 3 +25837 3 +25838 3 +25884 3 +25932 3 +26191 3 +26192 3 +26193 3 +26194 3 +26195 3 +26237 3 +26331 3 +26332 3 +26391 3 +26392 3 +26393 3 +26591 3 +26592 3 +26632 3 +26692 3 +26703 3 +26704 3 +26705 3 +26706 3 +26707 3 +26708 3 +26709 3 +26710 3 +26711 3 +26712 3 +26713 3 +26714 3 +26715 3 +26716 3 +26717 3 +26718 3 +26719 3 +26720 3 +26721 3 +26722 3 +26729 5 +26730 5 +26731 5 +26732 5 +26733 5 +26734 5 +26735 5 +26736 5 +26737 5 +26738 5 +26739 5 +26740 5 +26741 5 +26742 5 +26743 5 +26744 5 +26745 5 +26746 5 +26747 5 +26748 5 +26749 5 +26750 5 +26751 5 +26752 5 +26753 5 +26754 5 +26755 5 +26756 5 +26757 5 +26758 5 +26759 5 +26760 5 +26766 5 +26767 5 +26768 5 +26769 5 +26770 5 +26771 5 +26772 5 +26773 5 +26774 5 +26775 5 +26776 5 +26777 5 +26778 5 +26779 5 +26780 5 +26781 5 +26782 5 +26783 5 +26784 5 +26785 5 +26786 5 +26787 5 +26788 5 +26789 5 +26790 5 +26791 5 +26792 5 +26793 5 +26794 5 +26795 5 +26796 5 +26797 5 +26798 5 +26855 5 +26856 5 +26860 5 +26861 5 +26862 5 +26863 5 +26864 5 +26865 5 +26866 5 +26867 5 +26868 5 +26869 5 +26870 5 +26891 3 +26892 3 +26893 3 +26894 3 +26895 3 +26896 3 +26897 3 +26898 3 +26899 3 +26903 3 +26904 3 +26905 3 +26906 3 +26907 3 +26908 3 +26909 3 +26910 3 +26911 3 +26912 3 +26913 3 +26914 3 +26915 3 +26916 3 +26917 3 +26918 3 +26919 3 +26920 3 +26921 3 +26922 3 +26923 3 +26929 3 +26930 3 +26931 3 +26932 3 +26933 3 +26934 3 +26935 3 +26936 3 +26937 3 +26938 3 +26939 3 +26940 3 +26941 3 +26942 3 +26943 3 +26944 3 +26945 3 +26946 3 +26948 3 +26949 3 +26950 3 +26951 3 +26952 3 +26953 3 +26954 3 +26955 3 +26956 3 +26957 3 +26958 3 +26959 3 +26960 3 +26961 3 +26962 3 +26963 3 +26964 3 +26965 3 +26966 3 +26967 3 +26968 3 +26969 3 +26970 3 +26971 3 +26972 3 +26973 3 +26974 3 +26975 3 +26976 3 +26977 3 +26978 3 +26979 3 +26980 3 +26981 3 +26982 3 +26983 3 +26984 3 +26985 3 +26986 3 +26987 3 +26988 3 +26989 3 +26990 3 +26991 3 +26992 3 +26993 3 +26994 3 +26995 3 +26996 3 +26997 3 +26998 3 +27037 3 +27038 3 +27039 3 +27040 3 +27120 3 +27200 3 +27205 3 +27206 3 +27207 3 +27208 3 +27209 3 +27210 3 +27211 3 +27212 3 +27213 3 +27214 3 +27215 3 +27216 3 +27217 3 +27218 3 +27219 3 +27220 3 +27221 3 +27222 3 +27223 3 +27224 3 +27225 3 +27226 3 +27227 3 +27228 3 +27229 3 +27230 3 +27231 3 +27232 3 +27258 3 +27259 3 +27260 3 +27291 4 +27292 4 +27391 3 +27392 3 +27393 3 +27394 3 +27395 3 +27396 3 +27397 3 +27398 3 +27429 3 +27492 3 +27500 3 +27561 3 +27562 3 +27563 3 +27564 3 +27571 3 +27572 3 +27573 3 +27574 3 +27581 3 +27582 3 +27583 3 +27584 3 +27591 3 +27592 3 +27593 3 +27594 3 +27700 3 +28191 3 +28192 3 +28193 3 +28232 3 +28348 3 +28349 3 +28350 3 +28351 3 +28352 3 +28353 3 +28354 3 +28355 3 +28356 3 +28357 3 +28358 3 +28402 3 +28403 3 +28404 3 +28405 3 +28406 3 +28407 3 +28408 3 +28409 3 +28410 3 +28411 3 +28412 3 +28413 3 +28414 3 +28415 3 +28416 3 +28417 3 +28418 3 +28419 3 +28420 3 +28421 3 +28422 3 +28423 3 +28424 3 +28425 3 +28426 3 +28427 3 +28428 3 +28429 3 +28430 3 +28431 3 +28432 3 +28462 3 +28463 3 +28464 3 +28465 3 +28466 3 +28467 3 +28468 3 +28469 3 +28470 3 +28471 3 +28472 3 +28473 3 +28474 3 +28475 3 +28476 3 +28477 3 +28478 3 +28479 3 +28480 3 +28481 3 +28482 3 +28483 3 +28484 3 +28485 3 +28486 3 +28487 3 +28488 3 +28489 3 +28490 3 +28491 3 +28492 3 +28600 3 +28991 3 +28992 3 +29100 3 +29118 3 +29119 3 +29120 3 +29121 3 +29122 3 +29177 3 +29178 3 +29179 3 +29180 3 +29181 3 +29182 3 +29183 3 +29184 3 +29185 3 +29220 3 +29221 3 +29333 3 +29635 3 +29636 3 +29701 3 +29738 3 +29739 3 +29849 3 +29850 3 +29871 2 +29872 5 +29873 3 +29900 3 +29901 3 +29903 3 +30161 3 +30162 3 +30163 3 +30164 3 +30165 3 +30166 3 +30167 3 +30168 3 +30169 3 +30170 3 +30171 3 +30172 3 +30173 3 +30174 3 +30175 3 +30176 3 +30177 3 +30178 3 +30179 3 +30200 6 +30339 3 +30340 3 +30491 3 +30492 3 +30493 3 +30494 3 +30591 3 +30592 3 +30729 3 +30730 3 +30731 3 +30732 3 +30791 3 +30792 3 +31028 3 +31121 3 +31154 3 +31170 3 +31171 3 +31251 3 +31252 3 +31253 3 +31254 3 +31255 3 +31256 3 +31257 3 +31258 3 +31259 3 +31265 3 +31266 3 +31267 3 +31268 3 +31275 3 +31276 3 +31277 3 +31278 3 +31288 3 +31289 3 +31290 3 +31291 3 +31292 3 +31293 3 +31294 3 +31295 3 +31296 3 +31297 3 +31370 3 +31461 3 +31462 3 +31463 3 +31464 3 +31465 3 +31491 3 +31492 3 +31493 3 +31494 3 +31495 3 +31528 3 +31529 3 +31600 3 +31700 3 +31838 3 +31839 3 +31901 3 +31917 3 +31918 3 +31919 3 +31920 3 +31921 3 +31922 3 +31965 3 +31966 3 +31967 3 +31968 3 +31969 3 +31970 3 +31971 3 +31972 3 +31973 3 +31974 3 +31975 3 +31976 3 +31977 3 +31978 3 +31979 3 +31980 3 +31981 3 +31982 3 +31983 3 +31984 3 +31985 3 +31992 3 +31993 3 +31994 3 +31995 3 +31996 3 +31997 3 +31998 3 +31999 3 +32000 3 +32001 5 +32002 5 +32003 5 +32005 5 +32006 5 +32007 5 +32008 5 +32009 5 +32010 5 +32011 5 +32012 5 +32013 5 +32014 5 +32015 5 +32016 5 +32017 5 +32018 5 +32019 5 +32020 5 +32021 5 +32022 5 +32023 5 +32024 5 +32025 5 +32026 5 +32027 5 +32028 5 +32029 5 +32030 5 +32031 5 +32033 5 +32034 5 +32035 5 +32036 5 +32037 5 +32038 5 +32039 5 +32040 5 +32041 5 +32042 5 +32043 5 +32044 5 +32045 5 +32046 5 +32047 5 +32048 5 +32049 5 +32050 5 +32051 5 +32052 5 +32053 5 +32054 5 +32055 5 +32056 5 +32057 5 +32058 5 +32059 5 +32060 5 +32061 3 +32062 3 +32074 5 +32075 5 +32076 5 +32077 5 +32081 3 +32082 3 +32083 3 +32084 3 +32085 3 +32086 3 +32098 3 +32099 5 +32100 3 +32104 3 +32107 3 +32108 3 +32109 3 +32110 3 +32111 3 +32112 3 +32113 3 +32114 3 +32115 3 +32116 3 +32117 3 +32118 3 +32119 3 +32120 3 +32121 3 +32122 3 +32123 3 +32124 3 +32125 3 +32126 3 +32127 3 +32128 3 +32129 3 +32130 3 +32133 3 +32134 3 +32135 3 +32136 3 +32137 3 +32138 3 +32139 3 +32140 3 +32141 3 +32142 3 +32143 3 +32144 3 +32145 3 +32146 3 +32147 3 +32148 3 +32149 3 +32150 3 +32151 3 +32152 3 +32153 3 +32154 3 +32155 3 +32156 3 +32157 3 +32158 3 +32161 3 +32164 5 +32165 5 +32166 5 +32167 5 +32180 3 +32181 3 +32182 3 +32183 3 +32184 3 +32185 3 +32186 3 +32187 3 +32188 3 +32189 3 +32190 3 +32191 3 +32192 3 +32193 3 +32194 3 +32195 3 +32196 3 +32197 3 +32198 3 +32199 3 +32201 3 +32202 3 +32203 3 +32204 3 +32205 3 +32206 3 +32207 3 +32208 3 +32209 3 +32210 3 +32211 3 +32212 3 +32213 3 +32214 3 +32215 3 +32216 3 +32217 3 +32218 3 +32219 3 +32220 3 +32221 3 +32222 3 +32223 3 +32224 3 +32225 3 +32226 3 +32227 3 +32228 3 +32229 3 +32230 3 +32231 3 +32232 3 +32233 3 +32234 3 +32235 3 +32236 3 +32237 3 +32238 3 +32239 3 +32240 3 +32241 3 +32242 3 +32243 3 +32244 3 +32245 3 +32246 3 +32247 3 +32248 3 +32249 3 +32250 3 +32251 3 +32252 3 +32253 3 +32254 3 +32255 3 +32256 3 +32257 3 +32258 3 +32259 3 +32260 3 +32301 3 +32302 3 +32303 3 +32304 3 +32305 3 +32306 3 +32307 3 +32308 3 +32309 3 +32310 3 +32311 3 +32312 3 +32313 3 +32314 3 +32315 3 +32316 3 +32317 3 +32318 3 +32319 3 +32320 3 +32321 3 +32322 3 +32323 3 +32324 3 +32325 3 +32326 3 +32327 3 +32328 3 +32329 3 +32330 3 +32331 3 +32332 3 +32333 3 +32334 3 +32335 3 +32336 3 +32337 3 +32338 3 +32339 3 +32340 3 +32341 3 +32342 3 +32343 3 +32344 3 +32345 3 +32346 3 +32347 3 +32348 3 +32349 3 +32350 3 +32351 3 +32352 3 +32353 3 +32354 3 +32355 3 +32356 3 +32357 3 +32358 3 +32359 3 +32360 3 +32601 3 +32602 3 +32603 3 +32604 3 +32605 3 +32606 3 +32607 3 +32608 3 +32609 3 +32610 3 +32611 3 +32612 3 +32613 3 +32614 3 +32615 3 +32616 3 +32617 3 +32618 3 +32619 3 +32620 3 +32621 3 +32622 3 +32623 3 +32624 3 +32625 3 +32626 3 +32627 3 +32628 3 +32629 3 +32630 3 +32631 3 +32632 3 +32633 3 +32634 3 +32635 3 +32636 3 +32637 3 +32638 3 +32639 3 +32640 3 +32641 3 +32642 3 +32643 3 +32644 3 +32645 3 +32646 3 +32647 3 +32648 3 +32649 3 +32650 3 +32651 3 +32652 3 +32653 3 +32654 3 +32655 3 +32656 3 +32657 3 +32658 3 +32659 3 +32660 3 +32661 3 +32662 3 +32664 5 +32665 5 +32666 5 +32667 5 +32701 3 +32702 3 +32703 3 +32704 3 +32705 3 +32706 3 +32707 3 +32708 3 +32709 3 +32710 3 +32711 3 +32712 3 +32713 3 +32714 3 +32715 3 +32716 3 +32717 3 +32718 3 +32719 3 +32720 3 +32721 3 +32722 3 +32723 3 +32724 3 +32725 3 +32726 3 +32727 3 +32728 3 +32729 3 +32730 3 +32731 3 +32732 3 +32733 3 +32734 3 +32735 3 +32736 3 +32737 3 +32738 3 +32739 3 +32740 3 +32741 3 +32742 3 +32743 3 +32744 3 +32745 3 +32746 3 +32747 3 +32748 3 +32749 3 +32750 3 +32751 3 +32752 3 +32753 3 +32754 3 +32755 3 +32756 3 +32757 3 +32758 3 +32759 3 +32760 3 +32761 3 +32766 3 +53001 3 +53002 3 +53003 3 +53004 3 +53008 3 +53009 3 +53010 3 +53011 3 +53012 3 +53013 3 +53014 3 +53015 3 +53016 3 +53017 3 +53018 3 +53019 3 +53021 3 +53022 3 +53023 3 +53024 3 +53025 3 +53026 3 +53027 3 +53028 3 +53029 3 +53030 3 +53031 3 +53032 3 +53034 3 +53042 3 +53043 3 +53044 3 +53045 3 +53046 3 +53048 3 +53049 3 +54001 3 +54002 3 +54003 3 +54004 3 +54008 3 +54009 3 +54010 3 +54011 3 +54012 3 +54013 3 +54014 3 +54015 3 +54016 3 +54017 3 +54018 3 +54019 3 +54021 3 +54022 3 +54023 3 +54024 3 +54025 3 +54026 3 +54027 3 +54028 3 +54029 3 +54030 3 +54031 3 +54032 3 +54034 3 +54042 3 +54043 3 +54044 3 +54045 3 +54046 3 +54048 3 +54049 3 +54050 3 +54051 3 +54052 3 +54053 3 +65061 5 +65062 5 +65161 3 +65163 3 +102001 3 +102002 3 +102003 3 +102004 3 +102005 3 +102006 3 +102007 3 +102008 3 +102009 3 +102010 3 +102011 3 +102012 3 +102013 3 +102014 3 +102015 3 +102016 3 +102017 3 +102018 3 +102019 3 +102020 3 +102021 3 +102022 3 +102023 3 +102024 3 +102025 3 +102026 3 +102027 3 +102028 3 +102029 3 +102030 3 +102031 3 +102032 3 +102033 3 +102034 3 +102035 3 +102036 3 +102037 3 +102038 3 +102039 3 +102040 3 +102041 5 +102042 3 +102043 3 +102044 3 +102045 3 +102046 3 +102047 3 +102048 3 +102049 3 +102050 3 +102051 3 +102052 3 +102053 3 +102054 3 +102055 3 +102056 3 +102057 3 +102058 3 +102059 3 +102060 3 +102061 3 +102062 3 +102063 3 +102064 4 +102065 3 +102066 3 +102067 3 +102068 1 +102069 0 +102070 3 +102071 3 +102072 3 +102073 3 +102074 3 +102075 3 +102076 3 +102077 3 +102078 3 +102079 3 +102080 3 +102081 3 +102082 3 +102083 3 +102084 3 +102085 3 +102086 3 +102087 3 +102088 3 +102089 3 +102090 3 +102091 3 +102092 3 +102093 3 +102094 3 +102095 3 +102096 3 +102097 3 +102098 3 +102099 3 +102100 3 +102101 3 +102102 3 +102103 3 +102104 3 +102105 3 +102106 3 +102107 3 +102108 3 +102109 3 +102110 3 +102111 3 +102112 3 +102114 3 +102115 3 +102116 3 +102117 3 +102118 5 +102119 5 +102120 5 +102121 5 +102122 3 +102123 3 +102124 3 +102125 3 +102126 3 +102127 3 +102128 3 +102129 3 +102130 3 +102131 3 +102132 3 +102133 3 +102134 3 +102135 3 +102136 3 +102137 3 +102138 3 +102139 3 +102140 3 +102141 3 +102142 3 +102143 3 +102144 3 +102145 3 +102146 3 +102147 3 +102148 3 +102149 3 +102150 3 +102151 3 +102152 3 +102153 3 +102154 3 +102155 3 +102156 3 +102157 3 +102158 3 +102159 3 +102160 3 +102161 3 +102162 3 +102163 3 +102164 3 +102165 3 +102166 3 +102167 3 +102168 3 +102169 3 +102170 3 +102171 3 +102172 3 +102173 3 +102174 3 +102175 3 +102176 3 +102177 3 +102178 3 +102179 3 +102180 3 +102181 3 +102182 3 +102183 3 +102184 3 +102185 3 +102186 3 +102187 3 +102188 3 +102189 3 +102190 3 +102191 3 +102192 3 +102193 3 +102194 3 +102195 3 +102196 3 +102197 3 +102198 3 +102199 3 +102200 3 +102201 3 +102202 3 +102203 3 +102204 3 +102205 3 +102206 3 +102207 3 +102208 3 +102210 3 +102211 3 +102212 3 +102213 3 +102214 3 +102215 3 +102216 3 +102217 5 +102218 3 +102219 5 +102220 5 +102221 3 +102222 3 +102223 3 +102224 3 +102225 3 +102226 3 +102227 3 +102228 3 +102229 3 +102230 3 +102231 3 +102232 3 +102233 3 +102234 3 +102235 3 +102236 3 +102237 3 +102238 3 +102239 3 +102240 3 +102241 3 +102242 3 +102243 3 +102244 3 +102245 3 +102246 3 +102247 3 +102248 3 +102249 3 +102250 3 +102251 3 +102252 3 +102253 3 +102254 3 +102255 3 +102256 3 +102257 3 +102258 3 +102259 3 +102260 3 +102261 3 +102262 3 +102263 3 +102264 3 +102265 3 +102266 3 +102267 3 +102268 3 +102269 3 +102270 3 +102271 3 +102272 3 +102273 3 +102274 3 +102275 3 +102276 3 +102277 3 +102278 3 +102279 3 +102280 3 +102281 3 +102282 3 +102283 3 +102284 3 +102285 3 +102286 3 +102287 3 +102288 3 +102289 3 +102290 3 +102291 3 +102292 3 +102293 3 +102294 3 +102295 3 +102296 3 +102297 3 +102298 3 +102299 3 +102300 3 +102304 3 +102305 3 +102306 3 +102307 3 +102308 3 +102309 3 +102310 3 +102311 3 +102312 3 +102313 3 +102314 3 +102315 3 +102316 3 +102317 3 +102318 3 +102319 3 +102320 3 +102321 3 +102322 3 +102323 3 +102324 3 +102325 3 +102326 3 +102327 3 +102328 3 +102329 3 +102330 3 +102331 3 +102332 3 +102333 3 +102334 3 +102335 3 +102336 3 +102337 3 +102338 3 +102339 3 +102340 3 +102341 3 +102342 3 +102343 3 +102344 3 +102345 3 +102346 3 +102347 3 +102348 3 +102349 3 +102350 3 +102351 3 +102352 3 +102353 3 +102354 3 +102355 3 +102356 3 +102357 3 +102358 3 +102359 3 +102360 3 +102361 3 +102362 3 +102363 3 +102364 3 +102365 3 +102366 3 +102367 3 +102368 3 +102369 3 +102370 3 +102371 3 +102372 3 +102373 3 +102374 3 +102375 3 +102376 3 +102377 3 +102378 5 +102379 5 +102380 3 +102381 5 +102382 3 +102383 3 +102384 3 +102385 3 +102386 3 +102387 3 +102388 3 +102389 5 +102390 5 +102391 5 +102401 3 +102402 3 +102403 3 +102404 3 +102405 3 +102406 3 +102407 3 +102408 3 +102409 3 +102410 3 +102411 3 +102412 3 +102413 3 +102414 3 +102415 3 +102416 3 +102417 3 +102418 3 +102419 3 +102420 3 +102421 3 +102422 3 +102423 3 +102424 3 +102425 3 +102426 3 +102427 3 +102428 3 +102429 3 +102430 3 +102431 3 +102432 3 +102433 3 +102434 3 +102435 3 +102436 3 +102437 3 +102438 3 +102439 3 +102440 3 +102441 3 +102442 3 +102443 3 +102444 3 +102450 3 +102451 3 +102452 3 +102461 5 +102462 5 +102463 5 +102464 5 +102465 5 +102466 5 +102467 5 +102468 5 +102469 3 +102470 3 +102471 3 +102472 3 +102473 3 +102474 3 +102475 3 +102476 3 +102477 3 +102478 3 +102479 3 +102480 3 +102481 3 +102482 3 +102483 3 +102484 3 +102485 3 +102486 3 +102487 3 +102488 3 +102489 3 +102490 3 +102491 3 +102492 3 +102500 5 +102501 5 +102502 5 +102503 5 +102504 5 +102505 5 +102506 5 +102507 5 +102508 5 +102509 5 +102510 5 +102511 5 +102512 5 +102513 5 +102514 5 +102515 5 +102516 5 +102517 5 +102518 5 +102519 5 +102530 3 +102531 3 +102532 3 +102533 3 +102534 3 +102535 3 +102536 3 +102537 3 +102538 3 +102539 3 +102540 3 +102541 3 +102542 3 +102543 3 +102544 3 +102545 3 +102546 3 +102547 3 +102548 3 +102549 3 +102570 3 +102571 3 +102572 3 +102573 3 +102574 3 +102575 3 +102576 3 +102577 3 +102578 3 +102579 3 +102580 3 +102581 3 +102582 3 +102583 3 +102584 3 +102585 3 +102586 3 +102587 3 +102588 3 +102589 5 +102590 3 +102591 3 +102592 3 +102593 3 +102594 3 +102595 3 +102596 3 +102597 3 +102598 3 +102599 5 +102600 5 +102601 3 +102602 3 +102603 3 +102604 5 +102605 3 +102606 3 +102608 3 +102609 3 +102610 3 +102611 3 +102612 3 +102613 3 +102614 3 +102615 3 +102616 3 +102617 3 +102618 3 +102619 3 +102620 3 +102621 3 +102622 3 +102623 3 +102624 3 +102625 3 +102626 3 +102627 3 +102628 3 +102629 5 +102630 5 +102631 5 +102632 5 +102633 5 +102634 5 +102635 5 +102636 5 +102637 5 +102638 5 +102639 5 +102640 5 +102641 5 +102642 5 +102643 5 +102644 5 +102645 5 +102646 5 +102647 3 +102648 5 +102649 5 +102650 5 +102651 5 +102652 5 +102653 5 +102654 5 +102655 5 +102656 5 +102657 5 +102658 5 +102659 5 +102660 5 +102661 5 +102662 5 +102663 5 +102664 5 +102665 5 +102666 5 +102667 5 +102668 5 +102669 5 +102670 5 +102671 5 +102672 5 +102675 5 +102676 5 +102677 5 +102678 5 +102679 5 +102680 5 +102681 5 +102682 5 +102683 5 +102684 5 +102685 5 +102686 5 +102687 5 +102688 5 +102689 5 +102690 5 +102691 5 +102692 5 +102693 5 +102694 5 +102695 5 +102696 5 +102697 5 +102698 5 +102699 5 +102700 5 +102701 3 +102702 3 +102703 3 +102704 5 +102705 5 +102707 5 +102708 5 +102709 5 +102710 5 +102711 5 +102712 5 +102713 5 +102714 5 +102715 5 +102716 5 +102717 5 +102718 5 +102719 5 +102720 5 +102721 5 +102722 5 +102723 5 +102724 5 +102725 5 +102726 5 +102727 5 +102728 5 +102729 5 +102730 5 +102733 5 +102735 5 +102736 5 +102737 5 +102738 5 +102739 5 +102740 5 +102741 5 +102742 5 +102743 5 +102744 5 +102745 5 +102746 5 +102747 5 +102748 5 +102749 5 +102750 5 +102751 5 +102752 5 +102753 5 +102754 5 +102755 5 +102756 5 +102757 5 +102758 5 +102761 5 +102762 3 +102763 5 +102764 3 +102765 3 +102766 5 +102767 3 +102768 3 +102769 3 +102770 3 +102771 3 +102772 3 +102773 3 +102774 3 +102775 3 +102776 3 +102777 3 +102778 3 +102779 3 +102780 3 +102781 3 +102782 3 +102783 3 +102784 3 +102785 3 +102786 3 +102787 3 +102788 3 +102789 3 +102790 3 +102791 3 +102792 3 +102793 3 +102794 3 +102795 3 +102796 3 +102797 3 +102798 3 +102962 3 +102963 3 +102964 3 +102965 3 +102966 3 +102967 3 +102968 3 +102969 3 +102970 5 +102971 3 +102972 3 +102973 3 +102974 5 +102975 3 +102976 3 +102977 3 +102978 3 +102979 3 +102980 3 +102981 3 +102982 3 +102983 3 +102984 3 +102985 3 +102986 3 +102987 3 +102988 3 +102989 3 +102990 5 +102991 5 +102992 5 +102993 3 +102994 3 +102995 5 +102996 5 +102997 3 +102998 3 +102999 3 +103000 3 +103001 3 +103002 3 +103003 5 +103004 5 +103005 5 +103006 5 +103007 5 +103008 5 +103009 3 +103010 3 +103011 3 +103012 5 +103013 5 +103014 5 +103015 3 +103016 5 +103017 3 +103018 5 +103019 3 +103020 3 +103021 3 +103022 5 +103023 5 +103024 5 +103025 3 +103026 3 +103027 5 +103028 5 +103029 3 +103030 3 +103031 3 +103032 5 +103033 5 +103034 5 +103035 3 +103036 3 +103037 5 +103038 5 +103039 3 +103040 3 +103041 5 +103042 5 +103043 3 +103044 3 +103045 5 +103046 5 +103047 3 +103048 3 +103049 5 +103050 5 +103051 3 +103052 5 +103053 3 +103054 5 +103055 3 +103056 5 +103057 3 +103058 3 +103059 5 +103060 5 +103061 3 +103062 3 +103063 5 +103064 5 +103065 3 +103066 3 +103067 3 +103068 3 +103069 5 +103070 3 +103071 3 +103072 5 +103073 5 +103074 3 +103075 3 +103076 3 +103077 5 +103078 5 +103079 5 +103080 3 +103081 3 +103082 3 +103083 5 +103084 5 +103085 5 +103086 3 +103087 3 +103088 5 +103089 5 +103090 3 +103091 3 +103092 3 +103093 3 +103094 5 +103095 3 +103096 5 +103097 3 +103098 3 +103099 3 +103100 5 +103101 5 +103102 5 +103103 3 +103104 5 +103105 3 +103106 5 +103107 3 +103108 3 +103109 3 +103110 5 +103111 5 +103112 5 +103113 3 +103114 3 +103115 3 +103116 3 +103117 5 +103118 5 +103119 5 +103120 5 +103121 3 +103122 5 +103123 3 +103124 3 +103125 5 +103126 5 +103127 3 +103128 3 +103129 5 +103130 5 +103131 3 +103132 3 +103133 5 +103134 5 +103135 3 +103136 3 +103137 5 +103138 5 +103139 3 +103140 5 +103141 3 +103142 5 +103143 3 +103144 5 +103145 3 +103146 5 +103147 3 +103148 3 +103149 5 +103150 5 +103151 3 +103152 5 +103153 3 +103154 3 +103155 3 +103156 3 +103157 3 +103158 5 +103159 5 +103160 5 +103161 5 +103162 5 +103163 3 +103164 3 +103165 3 +103166 5 +103167 5 +103168 5 +103169 5 +103170 5 +103171 5 +103172 3 +103173 5 +103174 3 +103175 3 +103176 5 +103177 5 +103178 3 +103179 3 +103180 5 +103181 5 +103182 3 +103183 3 +103184 5 +103185 5 +103186 3 +103187 3 +103188 3 +103189 5 +103190 5 +103191 5 +103192 3 +103193 3 +103194 3 +103195 3 +103196 5 +103197 5 +103198 5 +103199 5 +103200 3 +103201 3 +103202 3 +103203 3 +103204 3 +103205 3 +103206 3 +103207 3 +103208 3 +103209 3 +103210 3 +103211 3 +103212 3 +103213 3 +103214 3 +103215 3 +103216 3 +103217 3 +103218 3 +103219 3 +103220 3 +103221 3 +103222 3 +103223 3 +103224 3 +103225 5 +103226 5 +103227 5 +103228 3 +103229 3 +103230 5 +103231 5 +103232 3 +103233 3 +103234 3 +103235 3 +103236 3 +103237 3 +103238 5 +103239 5 +103240 5 +103241 5 +103242 5 +103243 5 +103244 3 +103245 3 +103246 3 +103247 5 +103248 5 +103249 5 +103250 3 +103251 5 +103252 3 +103253 5 +103254 3 +103255 3 +103256 3 +103257 5 +103258 5 +103259 5 +103260 3 +103261 3 +103262 5 +103263 5 +103264 3 +103265 3 +103266 3 +103267 5 +103268 5 +103269 5 +103270 3 +103271 3 +103272 5 +103273 5 +103274 3 +103275 3 +103276 5 +103277 5 +103278 3 +103279 3 +103280 5 +103281 5 +103282 3 +103283 3 +103284 5 +103285 5 +103286 3 +103287 5 +103288 3 +103289 5 +103290 3 +103291 5 +103292 3 +103293 3 +103294 5 +103295 5 +103296 3 +103297 3 +103298 5 +103299 5 +103300 3 +103301 3 +103302 3 +103303 3 +103304 3 +103305 3 +103306 3 +103307 3 +103308 3 +103309 3 +103310 3 +103311 3 +103312 3 +103313 3 +103314 3 +103315 3 +103316 3 +103317 3 +103318 3 +103319 3 +103320 3 +103321 3 +103322 3 +103323 3 +103324 3 +103325 3 +103326 3 +103327 3 +103328 3 +103329 3 +103330 3 +103331 3 +103332 3 +103333 3 +103334 3 +103335 3 +103336 3 +103337 3 +103338 3 +103339 3 +103340 3 +103341 3 +103342 3 +103343 3 +103344 3 +103345 3 +103346 3 +103347 3 +103348 3 +103349 3 +103350 3 +103351 3 +103352 3 +103353 3 +103354 3 +103355 3 +103356 3 +103357 3 +103358 3 +103359 3 +103360 3 +103361 3 +103362 3 +103363 3 +103364 3 +103365 3 +103366 3 +103367 3 +103368 3 +103369 3 +103370 3 +103371 3 +103372 3 +103373 3 +103374 3 +103375 3 +103376 5 +103377 3 +103378 3 +103379 5 +103380 5 +103381 3 +103382 3 +103383 3 +103384 5 +103385 5 +103386 5 +103387 3 +103388 3 +103389 3 +103390 5 +103391 5 +103392 5 +103393 3 +103394 3 +103395 5 +103396 5 +103397 3 +103398 3 +103399 3 +103400 5 +103401 5 +103402 5 +103403 5 +103404 5 +103405 5 +103406 5 +103407 5 +103408 5 +103409 5 +103410 5 +103411 5 +103412 5 +103413 5 +103414 5 +103415 5 +103416 5 +103417 5 +103418 5 +103419 5 +103420 5 +103421 5 +103422 5 +103423 5 +103424 5 +103425 5 +103426 5 +103427 5 +103428 5 +103429 5 +103430 5 +103431 5 +103432 5 +103433 5 +103434 5 +103435 5 +103436 5 +103437 5 +103438 5 +103439 5 +103440 5 +103441 5 +103442 5 +103443 5 +103444 5 +103445 5 +103446 5 +103447 5 +103448 5 +103449 5 +103450 5 +103451 5 +103452 5 +103453 5 +103454 5 +103455 5 +103456 5 +103457 5 +103458 5 +103459 5 +103460 5 +103461 5 +103462 5 +103463 5 +103464 5 +103465 5 +103466 5 +103467 5 +103468 5 +103469 5 +103470 5 +103471 5 +103472 3 +103473 5 +103474 3 +103475 5 +103476 3 +103477 3 +103478 3 +103479 5 +103480 5 +103481 5 +103482 3 +103483 5 +103484 3 +103485 5 +103486 3 +103487 3 +103488 3 +103489 5 +103490 5 +103491 5 +103492 3 +103493 3 +103494 3 +103495 3 +103496 5 +103497 5 +103498 5 +103499 5 +103500 3 +103501 5 +103502 3 +103503 3 +103504 5 +103505 5 +103506 3 +103507 3 +103508 5 +103509 5 +103510 3 +103511 3 +103512 5 +103513 5 +103514 3 +103515 5 +103516 3 +103517 5 +103518 3 +103519 5 +103520 3 +103521 5 +103522 3 +103523 3 +103524 5 +103525 5 +103526 3 +103527 5 +103539 3 +103540 3 +103541 3 +103542 3 +103543 3 +103544 5 +103545 5 +103546 5 +103547 5 +103548 5 +103549 3 +103550 3 +103551 3 +103552 5 +103553 5 +103554 5 +103555 5 +103556 5 +103557 5 +103558 3 +103559 3 +103560 3 +103561 5 +103562 5 +103563 3 +103564 3 +103565 5 +103566 5 +103567 3 +103568 3 +103569 5 +103570 5 +103571 3 +103572 3 +103573 3 +103574 5 +103575 5 +103576 5 +103577 3 +103578 3 +103579 3 +103580 3 +103581 5 +103582 5 +103583 5 +103585 5 +103600 3 +103601 3 +103602 3 +103603 3 +103604 3 +103605 3 +103606 3 +103607 3 +103608 3 +103609 3 +103610 3 +103611 3 +103612 3 +103613 3 +103614 3 +103615 3 +103616 3 +103617 3 +103618 3 +103619 3 +103620 3 +103621 3 +103622 3 +103623 3 +103624 3 +103625 3 +103626 3 +103627 3 +103628 3 +103629 3 +103630 3 +103631 3 +103632 3 +103633 3 +103634 3 +103635 3 +103636 3 +103637 3 +103638 3 +103639 3 +103640 3 +103641 3 +103642 3 +103643 3 +103644 3 +103645 3 +103646 3 +103647 3 +103648 3 +103649 3 +103650 3 +103651 3 +103652 3 +103653 3 +103654 3 +103655 3 +103656 3 +103657 3 +103658 3 +103659 3 +103660 3 +103661 3 +103662 3 +103663 3 +103664 3 +103665 3 +103666 3 +103667 3 +103668 3 +103669 3 +103670 3 +103671 3 +103672 3 +103673 3 +103674 3 +103675 3 +103676 3 +103677 3 +103678 3 +103679 3 +103680 3 +103681 3 +103682 3 +103683 3 +103684 3 +103685 3 +103686 3 +103687 3 +103688 3 +103689 3 +103690 3 +103691 3 +103692 3 +103693 3 +103694 3 +103695 5 +103700 5 +103701 5 +103702 5 +103703 5 +103704 5 +103705 5 +103706 5 +103707 5 +103708 5 +103709 5 +103710 5 +103711 5 +103712 5 +103713 5 +103714 5 +103715 5 +103716 5 +103717 5 +103718 5 +103719 5 +103720 5 +103721 5 +103722 5 +103723 5 +103724 5 +103725 5 +103726 5 +103727 5 +103728 5 +103729 5 +103730 5 +103731 5 +103732 5 +103733 5 +103734 5 +103735 5 +103736 5 +103737 5 +103738 5 +103739 5 +103740 5 +103741 5 +103742 5 +103743 5 +103744 5 +103745 5 +103746 5 +103747 5 +103748 5 +103749 5 +103750 5 +103751 5 +103752 5 +103753 5 +103754 5 +103755 5 +103756 5 +103757 5 +103758 5 +103759 5 +103760 5 +103761 5 +103762 5 +103763 5 +103764 5 +103765 5 +103766 5 +103767 5 +103768 5 +103769 5 +103770 5 +103771 5 +103772 5 +103773 5 +103774 5 +103775 5 +103776 5 +103777 5 +103778 5 +103779 5 +103780 5 +103781 5 +103782 5 +103783 5 +103784 5 +103785 5 +103786 5 +103787 5 +103788 5 +103789 5 +103790 5 +103791 5 +103792 5 +103793 5 +103794 3 +103795 3 +103796 3 +103797 3 +103798 3 +103799 3 diff --git a/src/com/esri/core/geometry/pcs_tolerances.txt b/src/com/esri/core/geometry/pcs_tolerances.txt new file mode 100644 index 00000000..6271c3af --- /dev/null +++ b/src/com/esri/core/geometry/pcs_tolerances.txt @@ -0,0 +1,7 @@ +0 6.66667e-009 +1 2e-008 +2 4.97098e-005 +3 0.001 +4 0.00109362 +5 0.00328087 +6 0.00497101 diff --git a/unittest/com/esri/core/geometry/GeometryUtils.java b/unittest/com/esri/core/geometry/GeometryUtils.java new file mode 100644 index 00000000..cddf9957 --- /dev/null +++ b/unittest/com/esri/core/geometry/GeometryUtils.java @@ -0,0 +1,259 @@ +package com.esri.core.geometry; + +import java.io.File; +import java.io.FileNotFoundException; +import java.net.URI; +import java.util.Scanner; + +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.UriBuilder; + +import org.codehaus.jackson.JsonFactory; +import org.codehaus.jackson.JsonParser; + +import com.sun.jersey.api.client.Client; +import com.sun.jersey.api.client.ClientResponse; +import com.sun.jersey.api.client.WebResource; +import com.sun.jersey.api.client.config.ClientConfig; +import com.sun.jersey.api.client.config.DefaultClientConfig; +import com.sun.jersey.api.representation.Form; + +public class GeometryUtils { + static WebResource service4Proj; + static String url4Proj = "http://sampleserver1.arcgisonline.com/ArcGIS/rest/services/Geometry/GeometryServer/project"; + static WebResource service4Simplify; + static String url4Simplify = "http://sampleserver1.arcgisonline.com/ArcGIS/rest/services/Geometry/GeometryServer/simplify"; + static WebResource service4Relation; + static String url4Relation = "http://sampleserver1.arcgisonline.com/arcgis/rest/services/Geometry/GeometryServer/relation"; + + static { + ClientConfig config = new DefaultClientConfig(); + Client client = Client.create(config); + service4Proj = client.resource(getBaseURI4Proj()); + service4Simplify = client.resource(getBaseURI4Simplify()); + service4Relation = client.resource(url4Relation); + } + + private static URI getBaseURI4Proj() { + return UriBuilder.fromUri(url4Proj).build(); + } + + private static URI getBaseURI4Simplify() { + return UriBuilder.fromUri(url4Simplify).build(); + } + + public static Geometry getGeometryForProjectFromRestWS(int srIn, int srOut, + Geometry geomIn) { + String jsonStr4Geom = GeometryEngine.geometryToJson(srIn, geomIn); + String jsonStrNew = "{\"geometryType\":\"" + getGeometryType(geomIn) + + "\",\"geometries\":[" + jsonStr4Geom + "]}"; + + Form f = new Form(); + f.add("inSR", srIn); + f.add("outSR", srOut); + f.add("geometries", jsonStrNew); + f.add("f", "json"); + + ClientResponse response = service4Proj.type( + MediaType.APPLICATION_FORM_URLENCODED).post( + ClientResponse.class, f); + @SuppressWarnings("unused") + boolean isOK = response.getClientResponseStatus() == ClientResponse.Status.OK; + Object obj = response.getEntity(String.class); + String jsonStr = obj.toString(); + int idx2 = jsonStr.lastIndexOf("]"); + int idx1 = jsonStr.indexOf("["); + if (idx1 == -1 || idx2 == -1) + return null; + String jsonStrGeom = jsonStr.substring(idx1 + 1, idx2); + Geometry geometryObj = getGeometryFromJSon(jsonStrGeom); + return geometryObj; + } + + public static Geometry getGeometryForSimplifyFromRestWS(int sr, + Geometry geomIn) { + String jsonStr4Geom = GeometryEngine.geometryToJson( + SpatialReference.create(sr), geomIn); + String jsonStrNew = "{\"geometryType\":\"" + getGeometryType(geomIn) + + "\",\"geometries\":[" + jsonStr4Geom + "]}"; + + Form f = new Form(); + f.add("sr", sr); + f.add("geometries", jsonStrNew); + f.add("f", "json"); + + ClientResponse response = service4Simplify.type( + MediaType.APPLICATION_FORM_URLENCODED).post( + ClientResponse.class, f); + @SuppressWarnings("unused") + boolean isOK = response.getClientResponseStatus() == ClientResponse.Status.OK; + Object obj = response.getEntity(String.class); + String jsonStr = obj.toString(); + int idx2 = jsonStr.lastIndexOf("]"); + int idx1 = jsonStr.indexOf("["); + if (idx1 == -1 || idx2 == -1) + return null; + String jsonStrGeom = jsonStr.substring(idx1 + 1, idx2); + Geometry geometryObj = getGeometryFromJSon(jsonStrGeom); + return geometryObj; + } + + static String getGeometryType(Geometry geomIn) { + // there are five types: esriGeometryPoint + // esriGeometryMultipoint + // esriGeometryPolyline + // esriGeometryPolygon + // esriGeometryEnvelope + if (geomIn instanceof Point) + return "esriGeometryPoint"; + if (geomIn instanceof MultiPoint) + return "esriGeometryMultipoint"; + if (geomIn instanceof Polyline) + return "esriGeometryPolyline"; + if (geomIn instanceof Polygon) + return "esriGeometryPolygon"; + if (geomIn instanceof Envelope) + return "esriGeometryEnvelope"; + else + return null; + } + + static Geometry getGeometryFromJSon(String jsonStr) { + JsonFactory jf = new JsonFactory(); + + try { + JsonParser jp = jf.createJsonParser(jsonStr); + jp.nextToken(); + Geometry geom = GeometryEngine.jsonToGeometry(jp).getGeometry(); + return geom; + } catch (Exception ex) { + return null; + } + } + + public static void testMultiplePath(MultiPath mp1, MultiPath mp2) { + int count1 = mp1.getPointCount(); + int count2 = mp2.getPointCount(); + + System.out.println("From Rest vertices count: " + count1); + System.out.println("From Borg count: " + count2); + // Assert.assertTrue(count1==count2); + + int len = mp1.getPointCount(); + + for (int i = 0; i < len; i++) { + Point p = mp1.getPoint(i); + Point p2 = mp2.getPoint(i); + System.out.println("for rest: [" + p.getX() + "," + p.getY() + "]"); + System.out.println("for proj: [" + p2.getX() + "," + p2.getY() + + "]"); + @SuppressWarnings("unused") + double deltaX = p2.getX() - p.getX(); + @SuppressWarnings("unused") + double deltaY = p2.getY() - p.getY(); + + // Assert.assertTrue(deltaX<1e-7); + // Assert.assertTrue(deltaY<1e-7); + } + } + + public enum SpatialRelationType { + esriGeometryRelationCross, esriGeometryRelationDisjoint, esriGeometryRelationIn, esriGeometryRelationInteriorIntersection, esriGeometryRelationIntersection, esriGeometryRelationLineCoincidence, esriGeometryRelationLineTouch, esriGeometryRelationOverlap, esriGeometryRelationPointTouch, esriGeometryRelationTouch, esriGeometryRelationWithin, esriGeometryRelationRelation + } + + public static boolean isRelationTrue(Geometry geometry1, + Geometry geometry2, SpatialReference sr, + SpatialRelationType relation, String relationParam) { + String jsonStr4Geom1 = getJSonStringFromGeometry(geometry1, sr); + String jsonStr4Geom2 = getJSonStringFromGeometry(geometry2, sr); + + Form f = new Form(); + f.add("sr", sr.getID()); + f.add("geometries1", jsonStr4Geom1); + f.add("geometries2", jsonStr4Geom2); + + @SuppressWarnings("unused") + String enumName = relation.name(); + + f.add("relation", relation.name()); + f.add("f", "json"); + f.add("relationParam", relationParam); + + ClientResponse response = service4Relation.type( + MediaType.APPLICATION_FORM_URLENCODED).post( + ClientResponse.class, f); + @SuppressWarnings("unused") + boolean isOK = response.getClientResponseStatus() == ClientResponse.Status.OK; + Object obj = response.getEntity(String.class); + String jsonStr = obj.toString(); + int idx = jsonStr + .lastIndexOf("geometry1Index\":0,\"geometry2Index\":0"); + + if (idx == -1) { + return false; + } else { + return true; + } + } + + static String getJSonStringFromGeometry(Geometry geomIn, SpatialReference sr) { + String jsonStr4Geom = GeometryEngine.geometryToJson(sr, geomIn); + String jsonStrNew = "{\"geometryType\":\"" + getGeometryType(geomIn) + + "\",\"geometries\":[" + jsonStr4Geom + "]}"; + return jsonStrNew; + } + + public static Geometry loadFromTextFileDbg(String textFileName) + throws FileNotFoundException { + String fullPath = textFileName; + // string fullCSVPathName = System.IO.Path.Combine( directoryPath , + // CsvFileName); + File fileInfo = new File(fullPath); + + Scanner scanner = new Scanner(fileInfo); + + Geometry geom = null; + + // grab first line + String line = scanner.nextLine(); + String geomTypeString = line.substring(1); + if (geomTypeString.equalsIgnoreCase("polygon")) + geom = new Polygon(); + else if (geomTypeString.equalsIgnoreCase("polyline")) + geom = new Polyline(); + else if (geomTypeString.equalsIgnoreCase("multipoint")) + geom = new MultiPoint(); + else if (geomTypeString.equalsIgnoreCase("point")) + geom = new Point(); + + while (line.startsWith("*")) + if (scanner.hasNextLine()) + line = scanner.nextLine(); + + int j = 0; + Geometry.Type geomType = geom.getType(); + while (scanner.hasNextLine()) { + String[] parsedLine = line.split("\\s+"); + double xVal = Double.parseDouble(parsedLine[0]); + double yVal = Double.parseDouble(parsedLine[1]); + if (j == 0 + && (geomType == Geometry.Type.Polygon || geomType == Geometry.Type.Polyline)) + ((MultiPath) geom).startPath(xVal, yVal); + else { + if (geomType == Geometry.Type.Polygon + || geomType == Geometry.Type.Polyline) + ((MultiPath) geom).lineTo(xVal, yVal); + else if (geomType == Geometry.Type.MultiPoint) + ((MultiPoint) geom).add(xVal, yVal); + // else if(geomType == Geometry.Type.Point) + // Point geom = null;//new Point(xVal, yVal); + } + j++; + line = scanner.nextLine(); + } + + scanner.close(); + + return geom; + } +} diff --git a/unittest/com/esri/core/geometry/RandomCoordinateGenerator.java b/unittest/com/esri/core/geometry/RandomCoordinateGenerator.java new file mode 100644 index 00000000..f50f12d7 --- /dev/null +++ b/unittest/com/esri/core/geometry/RandomCoordinateGenerator.java @@ -0,0 +1,388 @@ +package com.esri.core.geometry; + +import java.util.Random; +import java.util.Vector; + +public class RandomCoordinateGenerator { + + // final Point openString[] = { new Point(1220, 1320), + // new Point(1220, 2320), new Point(3000, 2320), + // new Point(3520, 1720), new Point(3000, 1320), }; + // + // final Point3D openString3[] = { new Point3D(1220, 1320, 11), + // new Point3D(1220, 2320, 2), new Point3D(3000, 2320, 3), + // new Point3D(3520, 1720, 4), new Point3D(3000, 1320, 5), }; + + Vector points; + Envelope env; + int maxcount; + double tolerance; + double maxlen; + Random random = new Random(1973); + + Point _GenerateNewPoint() { + if (points.size() == maxcount) + return _RandomizeExisting(); + Point pt; + double f = random.nextDouble() - 0.5; + if (points.size() == 0) + pt = env.getCenter(); + else + pt = points.lastElement(); + // pt.x = pt.x + env.Width() * f; + pt.setX(pt.getX() + maxlen * f); + f = 1.0 * random.nextDouble() - 0.5; + pt.setY(pt.getY() + env.getHeight() * f); + pt.setY(pt.getY() + maxlen * f); + pt = _snapClip(pt, env); + points.add(pt); + return pt; + } + + Point _RandomizeExisting() { + if (points.size() == 0) { + return _GenerateNewPoint(); + } + + double f = random.nextDouble(); + int num = (int) (f * points.size()); + Point pt = points.get(num); + + f = random.nextDouble(); + + // if (f > 0.9) + if (f > 2) { + f = random.nextDouble(); + pt.setX(pt.getX() + (1 - 2 * f) * 2 * tolerance); + f = random.nextDouble(); + pt.setY(pt.getY() + (1 - 2 * f) * 2 * tolerance); + pt = _snapClip(pt, env); + } + return pt; + } + + public RandomCoordinateGenerator(int count, Envelope e, double tol) { + env = e; + maxlen = (env.getWidth() + env.getHeight()) / 2 * 0.1; + points = new Vector(); + points.ensureCapacity(count); + tolerance = tol; + maxcount = count; + } + + Point GetRandomCoord() { + double f = random.nextDouble(); + Point pt; + if (f > 0.9) + pt = _RandomizeExisting(); + else + pt = _GenerateNewPoint(); + + return pt; + } + + Point _snapClip(Point pt, Envelope env) { + double x = pt.getX(); + if (x < env.getXMin()) + x = env.getXMin(); + if (x > env.getXMax()) + x = env.getXMax(); + double y = pt.getY(); + if (y < env.getYMin()) + y = env.getYMin(); + if (y > env.getYMax()) + y = env.getYMax(); + return new Point(x, y); + } + + // void CompareGeometryContent(MultiVertexGeometry geom, Point buf[], int + // sz) { + // Assert.assertTrue(!geom.isEmpty()); + // Assert.assertTrue(geom.getPointCount() == sz); + // // Go through the geometry points + // for (int i = 0; i < geom.getPointCount(); i++) { + // Point point = new Point(); // not a right pattern the point has to + // // be created outside of the loop. + // geom.getPointByVal(i, point); + // Assert.assertTrue(point.getX() == buf[i].getX()); + // Assert.assertTrue(point.getY() == buf[i].getY()); + // Assert.assertTrue(point.getX() == buf[i].getX()); + // Assert.assertTrue(point.getY() == buf[i].getY()); + // } + // if (geom.getType() == Geometry.Type.Polygon + // || geom.getType() == Geometry.Type.Polyline) { + // CompareGeometryContent((MultiPath) geom, buf, sz); + // } + // } + // + // void CompareGeometryContent(MultiPath geom, Point buf[], int sz) { + // // Go through the geometry parts + // int j = 0; + // for (int ipart = 0; ipart < geom.getPathCount(); ipart++) { + // int start = geom.getPathStart(ipart); + // for (int i = 0; i < geom.getPathSize(ipart); i++, j++) { + // Point point = geom.getPoint(start + i); + // Assert.assertTrue(point.getX() == buf[j].getX()); + // Assert.assertTrue(point.getY() == buf[j].getY()); + // + // } + // } + // } + + // void CompareGeometryContent(MultiVertexGeometry geom, Point3D buf[], int + // sz) { + // Assert.assertTrue(!geom.isEmpty()); + // Assert.assertTrue(geom.getPointCount() == sz); + // // Go through the geometry points + // for (int i = 0; i < geom.getPointCount(); i++) { + // Point point = new Point(); // not a right pattern the point has to + // // be created outside of the loop. + // geom.getPointByVal(i, point); + // Assert.assertTrue(point.getX() == buf[i].x); + // Assert.assertTrue(point.getY() == buf[i].y); + // Assert.assertTrue(point.getZ() == buf[i].z); + // Point3D pt = point.getXYZ(); + // Assert.assertTrue(pt.x == buf[i].x); + // Assert.assertTrue(pt.y == buf[i].y); + // Assert.assertTrue(pt.z == buf[i].z); + // } + // + // { + // MultiVertexGeometryImpl mpGeom = (MultiVertexGeometryImpl) geom + // ._getImpl(); + // AttributeStreamOfDbl streamPos = (AttributeStreamOfDbl) mpGeom + // .getAttributeStreamRef(VertexDescription.Semantics.POSITION); + // AttributeStreamOfDbl streamZ = (AttributeStreamOfDbl) mpGeom + // .getAttributeStreamRef(VertexDescription.Semantics.Z); + // for (int i = 0; i < geom.getPointCount(); i++) { + // double x = streamPos.read(2 * i); + // double y = streamPos.read(2 * i + 1); + // double z = streamZ.read(i); + // + // Assert.assertTrue(x == buf[i].x); + // Assert.assertTrue(y == buf[i].y); + // Assert.assertTrue(z == buf[i].z); + // } + // } + // + // if (geom.getType() == Geometry.Type.Polygon + // || geom.getType() == Geometry.Type.Polyline) { + // CompareGeometryContent((MultiPath) geom, buf, sz); + // } + // } + + // void CompareGeometryContent(MultiPath geom, Point3D buf[], int sz) { + // Assert.assertTrue(!geom.isEmpty()); + // Assert.assertTrue(geom.getPointCount() == sz); + // + // // Go through the geometry parts + // int j = 0; + // for (int ipart = 0; ipart < geom.getPathCount(); ipart++) { + // int start = geom.getPathStart(ipart); + // for (int i = 0; i < geom.getPathSize(ipart); i++, j++) { + // double x = geom.getAttributeAsDbl( + // VertexDescription.Semantics.POSITION, i + start, 0); + // double y = geom.getAttributeAsDbl( + // VertexDescription.Semantics.POSITION, i + start, 1); + // double z = geom.getAttributeAsDbl( + // VertexDescription.Semantics.Z, i + start, 0); + // Assert.assertTrue(x == buf[j].x); + // Assert.assertTrue(y == buf[j].y); + // Assert.assertTrue(z == buf[j].z); + // + // Point point = new Point(); // not a right pattern the point has + // // to be created outside of the + // // loop. + // geom.getPointByVal(start + i, point); + // Assert.assertTrue(point.getX() == buf[j].x); + // Assert.assertTrue(point.getY() == buf[j].y); + // Assert.assertTrue(point.getZ() == buf[j].z); + // Point3D pt = point.getXYZ(); + // Assert.assertTrue(pt.x == buf[j].x); + // Assert.assertTrue(pt.y == buf[j].y); + // Assert.assertTrue(pt.z == buf[j].z); + // } + // } + // } + + // void CompareGeometryContent(MultiVertexGeometry geom1, + // MultiVertexGeometry geom2) { + // // Geometry types + // Assert.assertTrue(geom1.getType() == geom2.getType()); + // + // // Envelopes + // Envelope env1 = new Envelope(); + // geom1.queryEnvelope(env1); + // + // Envelope env2 = new Envelope(); + // geom2.queryEnvelope(env2); + // + // Assert.assertTrue(env1.getXMin() == env2.getXMin() + // && env1.getXMax() == env2.getXMax() + // && env1.getYMin() == env2.getYMin() + // && env1.getYMax() == env2.getYMax()); + // + // int type = geom1.getType(); + // if (type == Geometry.Type.Polyline || type == Geometry.Type.Polygon) { + // // Part Count + // int partCount1 = ((MultiPath) geom1).getPathCount(); + // int partCount2 = ((MultiPath) geom2).getPathCount(); + // Assert.assertTrue(partCount1 == partCount2); + // + // // Part indices + // for (int i = 0; i < partCount1; i++) { + // int start1 = ((MultiPath) geom1).getPathStart(i); + // int start2 = ((MultiPath) geom2).getPathStart(i); + // Assert.assertTrue(start1 == start2); + // int end1 = ((MultiPath) geom1).getPathEnd(i); + // int end2 = ((MultiPath) geom2).getPathEnd(i); + // Assert.assertTrue(end1 == end2); + // } + // } + // + // // Point count + // int pointCount1 = geom1.getPointCount(); + // int pointCount2 = geom2.getPointCount(); + // Assert.assertTrue(pointCount1 == pointCount2); + // + // if (type == Geometry.Type.MultiPoint || type == Geometry.Type.Polyline + // || type == Geometry.Type.Polygon) { + // MultiVertexGeometryImpl mpGeom1 = (MultiVertexGeometryImpl) geom1 + // ._getImpl(); + // MultiVertexGeometryImpl mpGeom2 = (MultiVertexGeometryImpl) geom2 + // ._getImpl(); + // // POSITION + // AttributeStreamBase positionStream1 = mpGeom1 + // .getAttributeStreamRef(VertexDescription.Semantics.POSITION); + // AttributeStreamOfDbl position1 = (AttributeStreamOfDbl) positionStream1; + // + // AttributeStreamBase positionStream2 = mpGeom2 + // .getAttributeStreamRef(VertexDescription.Semantics.POSITION); + // AttributeStreamOfDbl position2 = (AttributeStreamOfDbl) positionStream2; + // + // for (int i = 0; i < pointCount1; i++) { + // double x1 = position1.read(2 * i); + // double x2 = position2.read(2 * i); + // Assert.assertTrue(x1 == x2); + // + // double y1 = position1.read(2 * i + 1); + // double y2 = position2.read(2 * i + 1); + // Assert.assertTrue(y1 == y2); + // } + // + // // Zs + // boolean bHasZs1 = mpGeom1 + // .hasAttribute(VertexDescription.Semantics.Z); + // boolean bHasZs2 = mpGeom2 + // .hasAttribute(VertexDescription.Semantics.Z); + // Assert.assertTrue(bHasZs1 == bHasZs2); + // + // if (bHasZs1) { + // AttributeStreamBase zStream1 = mpGeom1 + // .getAttributeStreamRef(VertexDescription.Semantics.Z); + // AttributeStreamOfDbl zs1 = (AttributeStreamOfDbl) zStream1; + // + // AttributeStreamBase zStream2 = mpGeom2 + // .getAttributeStreamRef(VertexDescription.Semantics.Z); + // AttributeStreamOfDbl zs2 = (AttributeStreamOfDbl) zStream2; + // + // for (int i = 0; i < pointCount1; i++) { + // double z1 = zs1.read(i); + // double z2 = zs2.read(i); + // Assert.assertTrue(z1 == z2); + // } + // } + // + // // Ms + // boolean bHasMs1 = mpGeom1 + // .hasAttribute(VertexDescription.Semantics.M); + // boolean bHasMs2 = mpGeom2 + // .hasAttribute(VertexDescription.Semantics.M); + // Assert.assertTrue(bHasMs1 == bHasMs2); + // + // if (bHasMs1) { + // AttributeStreamBase mStream1 = mpGeom1 + // .getAttributeStreamRef(VertexDescription.Semantics.M); + // AttributeStreamOfDbl ms1 = (AttributeStreamOfDbl) mStream1; + // + // AttributeStreamBase mStream2 = mpGeom2 + // .getAttributeStreamRef(VertexDescription.Semantics.M); + // AttributeStreamOfDbl ms2 = (AttributeStreamOfDbl) mStream2; + // + // for (int i = 0; i < pointCount1; i++) { + // double m1 = ms1.read(i); + // double m2 = ms2.read(i); + // Assert.assertTrue(m1 == m2); + // } + // } + // + // // IDs + // boolean bHasIDs1 = mpGeom1 + // .hasAttribute(VertexDescription.Semantics.ID); + // boolean bHasIDs2 = mpGeom2 + // .hasAttribute(VertexDescription.Semantics.ID); + // Assert.assertTrue(bHasIDs1 == bHasIDs2); + // + // if (bHasIDs1) { + // AttributeStreamBase idStream1 = mpGeom1 + // .getAttributeStreamRef(VertexDescription.Semantics.ID); + // AttributeStreamOfInt32 ids1 = (AttributeStreamOfInt32) idStream1; + // + // AttributeStreamBase idStream2 = mpGeom2 + // .getAttributeStreamRef(VertexDescription.Semantics.ID); + // AttributeStreamOfInt32 ids2 = (AttributeStreamOfInt32) idStream2; + // + // for (int i = 0; i < pointCount1; i++) { + // int id1 = ids1.read(i); + // int id2 = ids2.read(i); + // Assert.assertTrue(id1 == id2); + // } + // } + // } + // } + // + // void SimpleTest(Geometry point) { + // Assert.assertTrue(point != null); + // point.addAttribute(VertexDescription.Semantics.Z); + // Assert.assertTrue(point + // .hasAttribute(VertexDescription.Semantics.POSITION)); + // Assert.assertTrue(point.hasAttribute(VertexDescription.Semantics.Z)); + // point.addAttribute(VertexDescription.Semantics.Z);// duplicate call + // Assert.assertTrue(point.getDescription().getAttributeCount() == 2); + // Assert + // .assertTrue(point.getDescription().getSemantics(1) == + // VertexDescription.Semantics.Z); + // point.dropAttribute(VertexDescription.Semantics.Z); + // Assert.assertTrue(!point.hasAttribute(VertexDescription.Semantics.Z)); + // point.dropAttribute(VertexDescription.Semantics.Z);// duplicate call + // Assert.assertTrue(!point.hasAttribute(VertexDescription.Semantics.Z)); + // Assert.assertTrue(point.getDescription().getAttributeCount() == 1); + // Assert + // .assertTrue(point.getDescription().getSemantics(0) == + // VertexDescription.Semantics.POSITION); + // + // point.addAttribute(VertexDescription.Semantics.M); + // Assert.assertTrue(point + // .hasAttribute(VertexDescription.Semantics.POSITION)); + // Assert.assertTrue(!point.hasAttribute(VertexDescription.Semantics.Z)); + // Assert.assertTrue(point.hasAttribute(VertexDescription.Semantics.M)); + // point.dropAttribute(VertexDescription.Semantics.M); + // Assert.assertTrue(!point.hasAttribute(VertexDescription.Semantics.M)); + // + // point.addAttribute(VertexDescription.Semantics.ID); + // Assert.assertTrue(point + // .hasAttribute(VertexDescription.Semantics.POSITION)); + // Assert.assertTrue(!point.hasAttribute(VertexDescription.Semantics.Z)); + // Assert.assertTrue(!point.hasAttribute(VertexDescription.Semantics.M)); + // point.dropAttribute(VertexDescription.Semantics.ID); + // Assert.assertTrue(!point.hasAttribute(VertexDescription.Semantics.ID)); + // + // // TEST_ASSERT(point->IsEmpty()); + // // TEST_ASSERT(point->GetPointCount() == 0); + // // TEST_ASSERT(point->GetPartCount() == 0); + // + // point = null; + // Assert.assertTrue(point == null); + // } + +} diff --git a/unittest/com/esri/core/geometry/TestBuffer.java b/unittest/com/esri/core/geometry/TestBuffer.java new file mode 100644 index 00000000..38902f83 --- /dev/null +++ b/unittest/com/esri/core/geometry/TestBuffer.java @@ -0,0 +1,357 @@ +package com.esri.core.geometry; + +import junit.framework.TestCase; +import org.junit.Test; + +public class TestBuffer extends TestCase { + @Override + protected void setUp() throws Exception { + super.setUp(); + } + + @Override + protected void tearDown() throws Exception { + super.tearDown(); + } + + @Test + public void testBufferPoint() { + SpatialReference sr = SpatialReference.create(4326); + Point inputGeom = new Point(12, 120); + OperatorBuffer buffer = (OperatorBuffer) OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Buffer); + OperatorSimplify simplify = (OperatorSimplify) OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Simplify); + Geometry result = buffer.execute(inputGeom, sr, 40.0, null); + assertTrue(result.getType().value() == Geometry.GeometryType.Polygon); + Polygon poly = (Polygon) result; + int pathCount = poly.getPathCount(); + assertTrue(pathCount == 1); + int pointCount = poly.getPointCount(); + assertTrue(Math.abs(pointCount - 100.0) < 10); + Envelope2D env2D = new Envelope2D(); + result.queryEnvelope2D(env2D); + assertTrue(Math.abs(env2D.getWidth() - 80) < 0.01 + && Math.abs(env2D.getHeight() - 80) < 0.01); + assertTrue(Math.abs(env2D.getCenterX() - 12) < 0.001 + && Math.abs(env2D.getCenterY() - 120) < 0.001); + NonSimpleResult nsr = new NonSimpleResult(); + boolean is_simple = simplify.isSimpleAsFeature(result, sr, true, nsr, + null); + assertTrue(is_simple); + + { + result = buffer.execute(inputGeom, sr, 0, null); + assertTrue(result.getType().value() == Geometry.GeometryType.Polygon); + assertTrue(result.isEmpty()); + } + + { + result = buffer.execute(inputGeom, sr, -1, null); + assertTrue(result.getType().value() == Geometry.GeometryType.Polygon); + assertTrue(result.isEmpty()); + } + } + + @Test + public void testBufferEnvelope() { + SpatialReference sr = SpatialReference.create(4326); + Envelope inputGeom = new Envelope(1, 0, 200, 400); + OperatorBuffer buffer = (OperatorBuffer) OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Buffer); + OperatorSimplify simplify = (OperatorSimplify) OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Simplify); + Geometry result = buffer.execute(inputGeom, sr, 40.0, null); + assertTrue(result.getType().value() == Geometry.GeometryType.Polygon); + Polygon poly = (Polygon) (result); + Envelope2D env2D = new Envelope2D(); + result.queryEnvelope2D(env2D); + assertTrue(Math.abs(env2D.getWidth() - (80 + 199)) < 0.001 + && Math.abs(env2D.getHeight() - (80 + 400)) < 0.001); + assertTrue(Math.abs(env2D.getCenterX() - 201.0 / 2) < 0.001 + && Math.abs(env2D.getCenterY() - 400 / 2.0) < 0.001); + int pathCount = poly.getPathCount(); + assertTrue(pathCount == 1); + int pointCount = poly.getPointCount(); + assertTrue(Math.abs(pointCount - 104.0) < 10); + NonSimpleResult nsr = new NonSimpleResult(); + assertTrue(simplify.isSimpleAsFeature(result, sr, true, nsr, null)); + + { + result = buffer.execute(inputGeom, sr, -200.0, null); + assertTrue(result.getType().value() == Geometry.GeometryType.Polygon); + assertTrue(result.isEmpty()); + } + + { + result = buffer.execute(inputGeom, sr, -200.0, null); + assertTrue(result.getType().value() == Geometry.GeometryType.Polygon); + assertTrue(result.isEmpty()); + } + + { + result = buffer.execute(inputGeom, sr, -199 / 2.0, null); + assertTrue(result.getType().value() == Geometry.GeometryType.Polygon); + assertTrue(result.isEmpty()); + } + + { + result = buffer.execute(inputGeom, sr, -50.0, null); + poly = (Polygon) (result); + result.queryEnvelope2D(env2D); + assertTrue(Math.abs(env2D.getWidth() - (199 - 100)) < 0.001 + && Math.abs(env2D.getHeight() - (400 - 100)) < 0.001); + assertTrue(Math.abs(env2D.getCenterX() - 201.0 / 2) < 0.001 + && Math.abs(env2D.getCenterY() - 400 / 2.0) < 0.001); + pathCount = poly.getPathCount(); + assertTrue(pathCount == 1); + pointCount = poly.getPointCount(); + assertTrue(Math.abs(pointCount - 4.0) < 10); + assertTrue(simplify.isSimpleAsFeature(result, sr, null)); + } + } + + @Test + public void testBufferMultiPoint() { + SpatialReference sr = SpatialReference.create(4326); + OperatorBuffer buffer = (OperatorBuffer) OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Buffer); + OperatorSimplify simplify = (OperatorSimplify) OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Simplify); + MultiPoint inputGeom = new MultiPoint(); + inputGeom.add(12, 120); + inputGeom.add(20, 120); + Geometry result = buffer.execute(inputGeom, sr, 40.0, null); + assertTrue(result.getType().value() == Geometry.GeometryType.Polygon); + Polygon poly = (Polygon) (result); + Envelope2D env2D = new Envelope2D(); + result.queryEnvelope2D(env2D); + assertTrue(Math.abs(env2D.getWidth() - 80 - 8) < 0.001 + && Math.abs(env2D.getHeight() - 80) < 0.001); + assertTrue(Math.abs(env2D.getCenterX() - 16) < 0.001 + && Math.abs(env2D.getCenterY() - 120) < 0.001); + int pathCount = poly.getPathCount(); + assertTrue(pathCount == 1); + int pointCount = poly.getPointCount(); + assertTrue(Math.abs(pointCount - 108.0) < 10); + assertTrue(simplify.isSimpleAsFeature(result, sr, null)); + + { + result = buffer.execute(inputGeom, sr, 0, null); + assertTrue(result.getType().value() == Geometry.GeometryType.Polygon); + assertTrue(result.isEmpty()); + } + + { + result = buffer.execute(inputGeom, sr, -1, null); + assertTrue(result.getType().value() == Geometry.GeometryType.Polygon); + assertTrue(result.isEmpty()); + } + } + + @Test + public void testBufferLine() { + SpatialReference sr = SpatialReference.create(4326); + Line inputGeom = new Line(12, 120, 20, 120); + OperatorBuffer buffer = (OperatorBuffer) OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Buffer); + OperatorSimplify simplify = (OperatorSimplify) OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Simplify); + Geometry result = buffer.execute(inputGeom, sr, 40.0, null); + assertTrue(result.getType().value() == Geometry.GeometryType.Polygon); + Polygon poly = (Polygon) (result); + Envelope2D env2D = new Envelope2D(); + result.queryEnvelope2D(env2D); + assertTrue(Math.abs(env2D.getWidth() - 80 - 8) < 0.001 + && Math.abs(env2D.getHeight() - 80) < 0.001); + assertTrue(Math.abs(env2D.getCenterX() - 16) < 0.001 + && Math.abs(env2D.getCenterY() - 120) < 0.001); + int pathCount = poly.getPathCount(); + assertTrue(pathCount == 1); + int pointCount = poly.getPointCount(); + assertTrue(Math.abs(pointCount - 100.0) < 10); + assertTrue(simplify.isSimpleAsFeature(result, sr, null)); + + { + result = buffer.execute(inputGeom, sr, 0, null); + assertTrue(result.getType().value() == Geometry.GeometryType.Polygon); + assertTrue(result.isEmpty()); + } + + { + result = buffer.execute(inputGeom, sr, -1, null); + assertTrue(result.getType().value() == Geometry.GeometryType.Polygon); + assertTrue(result.isEmpty()); + } + } + + @Test + public void testBufferPolyline() { + SpatialReference sr = SpatialReference.create(4326); + Polyline inputGeom = new Polyline(); + OperatorBuffer buffer = (OperatorBuffer) OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Buffer); + OperatorSimplify simplify = (OperatorSimplify) OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Simplify); + inputGeom.startPath(0, 0); + inputGeom.lineTo(50, 50); + inputGeom.lineTo(50, 0); + inputGeom.lineTo(0, 50); + + { + Geometry result = buffer.execute(inputGeom, sr, 0, null); + assertTrue(result.getType().value() == Geometry.GeometryType.Polygon); + assertTrue(result.isEmpty()); + } + + { + Geometry result = buffer.execute(inputGeom, sr, -1, null); + assertTrue(result.getType().value() == Geometry.GeometryType.Polygon); + assertTrue(result.isEmpty()); + } + + { + Geometry result = buffer.execute(inputGeom, sr, 40.0, null); + assertTrue(result.getType().value() == Geometry.GeometryType.Polygon); + Polygon poly = (Polygon) (result); + Envelope2D env2D = new Envelope2D(); + result.queryEnvelope2D(env2D); + assertTrue(Math.abs(env2D.getWidth() - 80 - 50) < 0.1 + && Math.abs(env2D.getHeight() - 80 - 50) < 0.1); + assertTrue(Math.abs(env2D.getCenterX() - 25) < 0.1 + && Math.abs(env2D.getCenterY() - 25) < 0.1); + int pathCount = poly.getPathCount(); + assertTrue(pathCount == 1); + int pointCount = poly.getPointCount(); + assertTrue(Math.abs(pointCount - 171.0) < 10); + assertTrue(simplify.isSimpleAsFeature(result, sr, null)); + } + + { + Geometry result = buffer.execute(inputGeom, sr, 4.0, null); + assertTrue(result.getType().value() == Geometry.GeometryType.Polygon); + Polygon poly = (Polygon) (result); + Envelope2D env2D = new Envelope2D(); + result.queryEnvelope2D(env2D); + assertTrue(Math.abs(env2D.getWidth() - 8 - 50) < 0.1 + && Math.abs(env2D.getHeight() - 8 - 50) < 0.1); + assertTrue(Math.abs(env2D.getCenterX() - 25) < 0.1 + && Math.abs(env2D.getCenterY() - 25) < 0.1); + int pathCount = poly.getPathCount(); + assertTrue(pathCount == 2); + int pointCount = poly.getPointCount(); + assertTrue(Math.abs(pointCount - 186.0) < 10); + assertTrue(simplify.isSimpleAsFeature(result, sr, null)); + } + + { + inputGeom = new Polyline(); + inputGeom.startPath(0, 0); + inputGeom.lineTo(50, 50); + inputGeom.startPath(50, 0); + inputGeom.lineTo(0, 50); + + Geometry result = buffer.execute(inputGeom, sr, 4.0, null); + assertTrue(result.getType().value() == Geometry.GeometryType.Polygon); + Polygon poly = (Polygon) (result); + Envelope2D env2D = new Envelope2D(); + result.queryEnvelope2D(env2D); + assertTrue(Math.abs(env2D.getWidth() - 8 - 50) < 0.1 + && Math.abs(env2D.getHeight() - 8 - 50) < 0.1); + assertTrue(Math.abs(env2D.getCenterX() - 25) < 0.1 + && Math.abs(env2D.getCenterY() - 25) < 0.1); + int pathCount = poly.getPathCount(); + assertTrue(pathCount == 1); + int pointCount = poly.getPointCount(); + assertTrue(Math.abs(pointCount - 208.0) < 10); + assertTrue(simplify.isSimpleAsFeature(result, sr, null)); + } + } + + @Test + public void testBufferPolygon() { + SpatialReference sr = SpatialReference.create(4326); + Polygon inputGeom = new Polygon(); + OperatorBuffer buffer = (OperatorBuffer) OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Buffer); + OperatorSimplify simplify = (OperatorSimplify) OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Simplify); + inputGeom.startPath(0, 0); + inputGeom.lineTo(50, 50); + inputGeom.lineTo(50, 0); + + { + Geometry result = buffer.execute(inputGeom, sr, 0, null); + assertTrue(result.getType().value() == Geometry.GeometryType.Polygon); + assertTrue(result == inputGeom); + } + + { + Geometry result = buffer.execute(inputGeom, sr, 10, null); + assertTrue(result.getType().value() == Geometry.GeometryType.Polygon); + Polygon poly = (Polygon) (result); + Envelope2D env2D = new Envelope2D(); + result.queryEnvelope2D(env2D); + assertTrue(Math.abs(env2D.getWidth() - 20 - 50) < 0.1 + && Math.abs(env2D.getHeight() - 20 - 50) < 0.1); + assertTrue(Math.abs(env2D.getCenterX() - 25) < 0.1 + && Math.abs(env2D.getCenterY() - 25) < 0.1); + int pathCount = poly.getPathCount(); + assertTrue(pathCount == 1); + int pointCount = poly.getPointCount(); + assertTrue(Math.abs(pointCount - 104.0) < 10); + assertTrue(simplify.isSimpleAsFeature(result, sr, null)); + } + + { + sr = SpatialReference.create(4326); + inputGeom = new Polygon(); + inputGeom.startPath(0, 0); + inputGeom.lineTo(50, 50); + inputGeom.lineTo(50, 0); + + Geometry result = buffer.execute(inputGeom, sr, -10, null); + assertTrue(result.getType().value() == Geometry.GeometryType.Polygon); + Polygon poly = (Polygon) (result); + Envelope2D env2D = new Envelope2D(); + result.queryEnvelope2D(env2D); + assertTrue(Math.abs(env2D.getWidth() - 15.85) < 0.1 + && Math.abs(env2D.getHeight() - 15.85) < 0.1); + assertTrue(Math.abs(env2D.getCenterX() - 32.07) < 0.1 + && Math.abs(env2D.getCenterY() - 17.93) < 0.1); + int pathCount = poly.getPathCount(); + assertTrue(pathCount == 1); + int pointCount = poly.getPointCount(); + assertTrue(pointCount == 3); + assertTrue(simplify.isSimpleAsFeature(result, sr, null)); + } + + { + sr = SpatialReference.create(4326); + inputGeom = new Polygon(); + inputGeom.startPath(0, 0); + inputGeom.lineTo(0, 50); + inputGeom.lineTo(50, 50); + inputGeom.lineTo(50, 0); + inputGeom.startPath(10, 10); + inputGeom.lineTo(40, 10); + inputGeom.lineTo(40, 40); + inputGeom.lineTo(10, 40); + + Geometry result = buffer.execute(inputGeom, sr, -2, null); + assertTrue(result.getType().value() == Geometry.GeometryType.Polygon); + Polygon poly = (Polygon) (result); + Envelope2D env2D = new Envelope2D(); + result.queryEnvelope2D(env2D); + assertTrue(Math.abs(env2D.getWidth() + 4 - 50) < 0.1 + && Math.abs(env2D.getHeight() + 4 - 50) < 0.1); + assertTrue(Math.abs(env2D.getCenterX() - 25) < 0.1 + && Math.abs(env2D.getCenterY() - 25) < 0.1); + int pathCount = poly.getPathCount(); + assertTrue(pathCount == 2); + int pointCount = poly.getPointCount(); + assertTrue(Math.abs(pointCount - 108) < 10); + assertTrue(simplify.isSimpleAsFeature(result, sr, null)); + } + } +} diff --git a/unittest/com/esri/core/geometry/TestClip.java b/unittest/com/esri/core/geometry/TestClip.java new file mode 100644 index 00000000..d96ee981 --- /dev/null +++ b/unittest/com/esri/core/geometry/TestClip.java @@ -0,0 +1,342 @@ +package com.esri.core.geometry; + +import junit.framework.TestCase; +import org.junit.Test; + +public class TestClip extends TestCase { + @Override + protected void setUp() throws Exception { + super.setUp(); + } + + @Override + protected void tearDown() throws Exception { + super.tearDown(); + } + + @Test + public static void testClipGeometries() { + // RandomTest(); + OperatorFactoryLocal engine = OperatorFactoryLocal.getInstance(); + OperatorClip clipOp = (OperatorClip) engine + .getOperator(Operator.Type.Clip); + + Polygon polygon = makePolygon(); + SimpleGeometryCursor polygonCurs = new SimpleGeometryCursor(polygon); + + Polyline polyline = makePolyline(); + SimpleGeometryCursor polylineCurs = new SimpleGeometryCursor(polyline); + + MultiPoint multipoint = makeMultiPoint(); + SimpleGeometryCursor multipointCurs = new SimpleGeometryCursor( + multipoint); + + Point point = makePoint(); + SimpleGeometryCursor pointCurs = new SimpleGeometryCursor(point); + + SpatialReference spatialRef = SpatialReference.create(3857); + + Envelope2D envelope = new Envelope2D(); + envelope.xmin = 0; + envelope.xmax = 20; + envelope.ymin = 5; + envelope.ymax = 15; + + // Cursor implementation + GeometryCursor clipPolygonCurs = clipOp.execute(polygonCurs, envelope, + spatialRef, null); + Polygon clippedPolygon = (Polygon) clipPolygonCurs.next(); + double area = clippedPolygon.calculateArea2D(); + assertTrue(Math.abs(area - 25) < 0.00001); + + // Single Geometry implementation + clippedPolygon = (Polygon) clipOp.execute(polygon, envelope, + spatialRef, null); + area = clippedPolygon.calculateArea2D(); + assertTrue(Math.abs(area - 25) < 0.00001); + + // Cursor implementation + GeometryCursor clipPolylineCurs = clipOp.execute(polylineCurs, + envelope, spatialRef, null); + Polyline clippedPolyline = (Polyline) clipPolylineCurs.next(); + double length = clippedPolyline.calculateLength2D(); + assertTrue(Math.abs(length - 10 * Math.sqrt(2.0)) < 1e-10); + + // Single Geometry implementation + clippedPolyline = (Polyline) clipOp.execute(polyline, envelope, + spatialRef, null); + length = clippedPolyline.calculateLength2D(); + assertTrue(Math.abs(length - 10 * Math.sqrt(2.0)) < 1e-10); + + // Cursor implementation + GeometryCursor clipMulti_pointCurs = clipOp.execute(multipointCurs, + envelope, spatialRef, null); + MultiPoint clipped_multi_point = (MultiPoint) clipMulti_pointCurs + .next(); + int pointCount = clipped_multi_point.getPointCount(); + assertTrue(pointCount == 2); + + // Cursor implementation + GeometryCursor clipPointCurs = clipOp.execute(pointCurs, envelope, + spatialRef, null); + Point clippedPoint = (Point) clipPointCurs.next(); + assertTrue(clippedPoint != null); + + // RandomTest(); + + Polyline _poly = new Polyline(); + _poly.startPath(2, 2); + _poly.lineTo(0, 0); + + Envelope2D _env = new Envelope2D(); + _env.setCoords(2, 1, 5, 3); + + Polyline _clippedPolyline = (Polyline) clipOp.execute(_poly, _env, + spatialRef, null); + assertTrue(_clippedPolyline.isEmpty()); + + { + Polygon poly = new Polygon(); + poly.addEnvelope(new Envelope2D(0, 0, 100, 100), false); + poly.addEnvelope(new Envelope2D(5, 5, 95, 95), true); + + Polygon clippedPoly = (Polygon) clipOp.execute(poly, + new Envelope2D(-10, -10, 110, 50), spatialRef, null); + assertTrue(clippedPoly.getPathCount() == 1); + assertTrue(clippedPoly.getPointCount() == 8); + } + } + + @Test + public static Polygon makePolygon() { + Polygon poly = new Polygon(); + poly.startPath(0, 0); + poly.lineTo(10, 10); + poly.lineTo(20, 0); + + return poly; + } + + @Test + public static Polyline makePolyline() { + Polyline poly = new Polyline(); + poly.startPath(0, 0); + poly.lineTo(10, 10); + poly.lineTo(20, 0); + + return poly; + } + + @Test + public static void testGetXCorrectCR185697() { + OperatorFactoryLocal engine = OperatorFactoryLocal.getInstance(); + OperatorClip clipOp = (OperatorClip) engine + .getOperator(Operator.Type.Clip); + + Polyline polylineCR = makePolylineCR(); + @SuppressWarnings("unused") + SimpleGeometryCursor polylineCursCR = new SimpleGeometryCursor( + polylineCR); + + SpatialReference gcsWGS84 = SpatialReference.create(4326); + + Envelope2D envelopeCR = new Envelope2D(); + envelopeCR.xmin = -180; + envelopeCR.xmax = 180; + envelopeCR.ymin = -90; + envelopeCR.ymax = 90; + // CR + Polyline clippedPolylineCR = (Polyline) clipOp.execute(polylineCR, + envelopeCR, gcsWGS84, null); + Point pointResult = new Point(); + clippedPolylineCR.getPointByVal(0, pointResult); + assertTrue(pointResult.getX() == -180); + clippedPolylineCR.getPointByVal(1, pointResult); + assertTrue(pointResult.getX() == -90); + clippedPolylineCR.getPointByVal(2, pointResult); + assertTrue(pointResult.getX() == 0); + clippedPolylineCR.getPointByVal(3, pointResult); + assertTrue(pointResult.getX() == 100); + clippedPolylineCR.getPointByVal(4, pointResult); + assertTrue(pointResult.getX() == 170); + clippedPolylineCR.getPointByVal(5, pointResult); + assertTrue(pointResult.getX() == 180); + } + + @Test + public static void testArcObjectsFailureCR196492() { + OperatorFactoryLocal engine = OperatorFactoryLocal.getInstance(); + OperatorClip clipOp = (OperatorClip) engine + .getOperator(Operator.Type.Clip); + + Polygon polygon = new Polygon(); + polygon.addEnvelope(new Envelope2D(0, 0, 600, 600), false); + polygon.startPath(30, 300); + polygon.lineTo(20, 310); + polygon.lineTo(10, 300); + + SpatialReference gcsWGS84 = SpatialReference.create(4326); + + Envelope2D envelopeCR = new Envelope2D(10, 10, 500, 500); + + Polygon clippedPolygon = (Polygon) clipOp.execute(polygon, envelopeCR, + gcsWGS84, null); + assertTrue(clippedPolygon.getPointCount() == 7); + // ((MultiPathImpl::SPtr)clippedPolygon._GetImpl()).SaveToTextFileDbg("c:\\temp\\test_ArcObjects_failure_CR196492.txt"); + } + + @Test + public static Polyline makePolylineCR() { + Polyline polyline = new Polyline(); + + polyline.startPath(-200, -90); + polyline.lineTo(-180, -85); + polyline.lineTo(-90, -70); + polyline.lineTo(0, 0); + polyline.lineTo(100, 25); + polyline.lineTo(170, 45); + polyline.lineTo(225, 65); + + return polyline; + } + + @Test + public static MultiPoint makeMultiPoint() { + MultiPoint mpoint = new MultiPoint(); + + Point2D pt1 = new Point2D(); + pt1.x = 10; + pt1.y = 10; + + Point2D pt2 = new Point2D(); + pt2.x = 15; + pt2.y = 10; + + Point2D pt3 = new Point2D(); + pt3.x = 10; + pt3.y = 20; + + mpoint.add(pt1.x, pt1.y); + mpoint.add(pt2.x, pt2.y); + mpoint.add(pt3.x, pt3.y); + + return mpoint; + } + + @Test + public static Point makePoint() { + Point point = new Point(); + + Point2D pt = new Point2D(); + pt.setCoords(10, 20); + point.setXY(pt); + + return point; + } + + @Test + public static void testClipOfCoinciding() { + OperatorFactoryLocal engine = OperatorFactoryLocal.getInstance(); + OperatorClip clipOp = (OperatorClip) engine + .getOperator(Operator.Type.Clip); + + Polygon polygon = new Polygon(); + Envelope2D envelopeCR = new Envelope2D(); + envelopeCR.xmin = -180; + envelopeCR.xmax = 180; + envelopeCR.ymin = -90; + envelopeCR.ymax = 90; + + polygon.addEnvelope(envelopeCR, false); + + SpatialReference gcsWGS84 = SpatialReference.create(4326); + // CR + Polygon clippedPolygon = (Polygon) clipOp.execute(polygon, envelopeCR, + gcsWGS84, null); + assertTrue(clippedPolygon.getPathCount() == 1); + assertTrue(clippedPolygon.getPointCount() == 4); + + OperatorDensifyByLength densifyOp = (OperatorDensifyByLength) engine + .getOperator(Operator.Type.DensifyByLength); + polygon.setEmpty(); + polygon.addEnvelope(envelopeCR, false); + polygon = (Polygon) densifyOp.execute(polygon, 1, null); + + int pc = polygon.getPointCount(); + int pathc = polygon.getPathCount(); + assertTrue(pc == 1080); + assertTrue(pathc == 1); + clippedPolygon = (Polygon) clipOp.execute(polygon, envelopeCR, + gcsWGS84, null); + int _pathc = clippedPolygon.getPathCount(); + int _pc = clippedPolygon.getPointCount(); + assertTrue(_pathc == 1); + assertTrue(_pc == pc); + } + + @Test + public static void testClipAttributes() { + OperatorFactoryLocal engine = OperatorFactoryLocal.getInstance(); + OperatorClip clipOp = (OperatorClip) engine + .getOperator(Operator.Type.Clip); + { + Polygon polygon = new Polygon(); + polygon.addAttribute(VertexDescription.Semantics.M); + + polygon.startPath(0, 0); + polygon.lineTo(30, 30); + polygon.lineTo(60, 0); + + polygon.setAttribute(VertexDescription.Semantics.M, 0, 0, 0); + polygon.setAttribute(VertexDescription.Semantics.M, 1, 0, 60); + polygon.setAttribute(VertexDescription.Semantics.M, 2, 0, 120); + + Envelope2D clipper = new Envelope2D(); + clipper.setCoords(10, 0, 50, 20); + Polygon clippedPolygon = (Polygon) clipOp.execute(polygon, clipper, + SpatialReference.create(4326), null); + + assertTrue(clippedPolygon.getAttributeAsDbl( + VertexDescription.Semantics.M, 0, 0) == 100); + assertTrue(clippedPolygon.getAttributeAsDbl( + VertexDescription.Semantics.M, 1, 0) == 19.999999999999996); // 20.0 + assertTrue(clippedPolygon.getAttributeAsDbl( + VertexDescription.Semantics.M, 2, 0) == 20); + assertTrue(clippedPolygon.getAttributeAsDbl( + VertexDescription.Semantics.M, 3, 0) == 40); + assertTrue(clippedPolygon.getAttributeAsDbl( + VertexDescription.Semantics.M, 4, 0) == 80); + assertTrue(clippedPolygon.getAttributeAsDbl( + VertexDescription.Semantics.M, 5, 0) == 100); + } + + { + Polygon polygon = new Polygon(); + polygon.addAttribute(VertexDescription.Semantics.M); + + polygon.startPath(0, 0); + polygon.lineTo(0, 40); + polygon.lineTo(20, 40); + polygon.lineTo(20, 0); + + polygon.setAttribute(VertexDescription.Semantics.M, 0, 0, 0); + polygon.setAttribute(VertexDescription.Semantics.M, 1, 0, 60); + polygon.setAttribute(VertexDescription.Semantics.M, 2, 0, 120); + polygon.setAttribute(VertexDescription.Semantics.M, 3, 0, 180); + + Envelope2D clipper = new Envelope2D(); + clipper.setCoords(0, 10, 20, 20); + Polygon clippedPolygon = (Polygon) clipOp.execute(polygon, clipper, + SpatialReference.create(4326), null); + + assertTrue(clippedPolygon.getAttributeAsDbl( + VertexDescription.Semantics.M, 0, 0) == 15); + assertTrue(clippedPolygon.getAttributeAsDbl( + VertexDescription.Semantics.M, 1, 0) == 30); + assertTrue(clippedPolygon.getAttributeAsDbl( + VertexDescription.Semantics.M, 2, 0) == 150); + assertTrue(clippedPolygon.getAttributeAsDbl( + VertexDescription.Semantics.M, 3, 0) == 165); + } + } +} diff --git a/unittest/com/esri/core/geometry/TestCommonMethods.java b/unittest/com/esri/core/geometry/TestCommonMethods.java new file mode 100644 index 00000000..74a113f3 --- /dev/null +++ b/unittest/com/esri/core/geometry/TestCommonMethods.java @@ -0,0 +1,254 @@ +package com.esri.core.geometry; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import org.codehaus.jackson.JsonFactory; +import org.codehaus.jackson.JsonParser; +import junit.framework.TestCase; +import org.junit.Test; + +public class TestCommonMethods extends TestCase { + public static boolean compareDouble(double a, double b, double tol) { + double diff = Math.abs(a - b); + return diff <= tol * Math.abs(a) + tol; + } + + public static Point[] pointsFromMultiPath(MultiPath geom) { + int numberOfPoints = geom.getPointCount(); + Point[] points = new Point[numberOfPoints]; + for (int i = 0; i < geom.getPointCount(); i++) { + points[i] = geom.getPoint(i); + } + return points; + } + + @Test + public static void compareGeometryContent(MultiVertexGeometry geom1, + MultiVertexGeometry geom2) { + // Geometry types + assertTrue(geom1.getType().value() == geom2.getType().value()); + + // Envelopes + Envelope2D env1 = new Envelope2D(); + geom1.queryEnvelope2D(env1); + + Envelope2D env2 = new Envelope2D(); + geom2.queryEnvelope2D(env2); + + assertTrue(env1.xmin == env2.xmin && env1.xmax == env2.xmax + && env1.ymin == env2.ymin && env1.ymax == env2.ymax); + + int type = geom1.getType().value(); + if (type == Geometry.GeometryType.Polyline + || type == Geometry.GeometryType.Polygon) { + // Part Count + int partCount1 = ((MultiPath) geom1).getPathCount(); + int partCount2 = ((MultiPath) geom2).getPathCount(); + assertTrue(partCount1 == partCount2); + + // Part indices + for (int i = 0; i < partCount1; i++) { + int start1 = ((MultiPath) geom1).getPathStart(i); + int start2 = ((MultiPath) geom2).getPathStart(i); + assertTrue(start1 == start2); + int end1 = ((MultiPath) geom1).getPathEnd(i); + int end2 = ((MultiPath) geom2).getPathEnd(i); + assertTrue(end1 == end2); + } + } + + // Point count + int pointCount1 = geom1.getPointCount(); + int pointCount2 = geom2.getPointCount(); + assertTrue(pointCount1 == pointCount2); + + if (type == Geometry.GeometryType.MultiPoint + || type == Geometry.GeometryType.Polyline + || type == Geometry.GeometryType.Polygon) { + // POSITION + AttributeStreamBase positionStream1 = ((MultiVertexGeometryImpl) geom1 + ._getImpl()) + .getAttributeStreamRef(VertexDescription.Semantics.POSITION); + AttributeStreamOfDbl position1 = (AttributeStreamOfDbl) (positionStream1); + + AttributeStreamBase positionStream2 = ((MultiVertexGeometryImpl) geom2 + ._getImpl()) + .getAttributeStreamRef(VertexDescription.Semantics.POSITION); + AttributeStreamOfDbl position2 = (AttributeStreamOfDbl) (positionStream2); + + for (int i = 0; i < pointCount1; i++) { + double x1 = position1.read(2 * i); + double x2 = position2.read(2 * i); + assertTrue(x1 == x2); + + double y1 = position1.read(2 * i + 1); + double y2 = position2.read(2 * i + 1); + assertTrue(y1 == y2); + } + + // Zs + boolean bHasZs1 = geom1.hasAttribute(VertexDescription.Semantics.Z); + boolean bHasZs2 = geom2.hasAttribute(VertexDescription.Semantics.Z); + assertTrue(bHasZs1 == bHasZs2); + + if (bHasZs1) { + AttributeStreamBase zStream1 = ((MultiVertexGeometryImpl) geom1 + ._getImpl()) + .getAttributeStreamRef(VertexDescription.Semantics.Z); + AttributeStreamOfDbl zs1 = (AttributeStreamOfDbl) (zStream1); + + AttributeStreamBase zStream2 = ((MultiVertexGeometryImpl) geom2 + ._getImpl()) + .getAttributeStreamRef(VertexDescription.Semantics.Z); + AttributeStreamOfDbl zs2 = (AttributeStreamOfDbl) (zStream2); + + for (int i = 0; i < pointCount1; i++) { + double z1 = zs1.read(i); + double z2 = zs2.read(i); + assertTrue(z1 == z2); + } + } + + // Ms + boolean bHasMs1 = geom1.hasAttribute(VertexDescription.Semantics.M); + boolean bHasMs2 = geom2.hasAttribute(VertexDescription.Semantics.M); + assertTrue(bHasMs1 == bHasMs2); + + if (bHasMs1) { + AttributeStreamBase mStream1 = ((MultiVertexGeometryImpl) geom1 + ._getImpl()) + .getAttributeStreamRef(VertexDescription.Semantics.M); + AttributeStreamOfDbl ms1 = (AttributeStreamOfDbl) (mStream1); + + AttributeStreamBase mStream2 = ((MultiVertexGeometryImpl) geom2 + ._getImpl()) + .getAttributeStreamRef(VertexDescription.Semantics.M); + AttributeStreamOfDbl ms2 = (AttributeStreamOfDbl) (mStream2); + + for (int i = 0; i < pointCount1; i++) { + double m1 = ms1.read(i); + double m2 = ms2.read(i); + assertTrue(m1 == m2); + } + } + + // IDs + boolean bHasIDs1 = geom1 + .hasAttribute(VertexDescription.Semantics.ID); + boolean bHasIDs2 = geom2 + .hasAttribute(VertexDescription.Semantics.ID); + assertTrue(bHasIDs1 == bHasIDs2); + + if (bHasIDs1) { + AttributeStreamBase idStream1 = ((MultiVertexGeometryImpl) geom1 + ._getImpl()) + .getAttributeStreamRef(VertexDescription.Semantics.ID); + AttributeStreamOfInt32 ids1 = (AttributeStreamOfInt32) (idStream1); + + AttributeStreamBase idStream2 = ((MultiVertexGeometryImpl) geom2 + ._getImpl()) + .getAttributeStreamRef(VertexDescription.Semantics.ID); + AttributeStreamOfInt32 ids2 = (AttributeStreamOfInt32) (idStream2); + + for (int i = 0; i < pointCount1; i++) { + int id1 = ids1.read(i); + int id2 = ids2.read(i); + assertTrue(id1 == id2); + } + } + } + } + + @Test + public static void compareGeometryContent(MultiPoint geom1, MultiPoint geom2) { + // Geometry types + assertTrue(geom1.getType().value() == geom2.getType().value()); + + // Envelopes + Envelope env1 = new Envelope(); + geom1.queryEnvelope(env1); + + Envelope env2 = new Envelope(); + geom2.queryEnvelope(env2); + + assertTrue(env1.getXMin() == env2.getXMin() + && env1.getXMax() == env2.getXMax() + && env1.getYMin() == env2.getYMin() + && env1.getYMax() == env2.getYMax()); + + // Point count + int pointCount1 = geom1.getPointCount(); + int pointCount2 = geom2.getPointCount(); + assertTrue(pointCount1 == pointCount2); + + Point point1; + Point point2; + + for (int i = 0; i < pointCount1; i++) { + point1 = geom1.getPoint(i); + point2 = geom2.getPoint(i); + + double x1 = point1.getX(); + double x2 = point2.getX(); + assertTrue(x1 == x2); + + double y1 = point1.getY(); + double y2 = point2.getY(); + assertTrue(y1 == y2); + } + } + + @Test + public static void testNothing() { + + } + + public static boolean writeObjectToFile(String fileName, Object obj) { + try { + File f = new File(fileName); + f.setWritable(true); + + FileOutputStream fout = new FileOutputStream(f); + ObjectOutputStream oo = new ObjectOutputStream(fout); + oo.writeObject(obj); + fout.close(); + return true; + } catch (Exception ex) { + return false; + } + } + + public static Object readObjectFromFile(String fileName) { + try { + File f = new File(fileName); + f.setReadable(true); + + FileInputStream streamIn = new FileInputStream(f); + ObjectInputStream ii = new ObjectInputStream(streamIn); + Object obj = ii.readObject(); + streamIn.close(); + return obj; + } catch (Exception ex) { + return null; + } + } + + public static MapGeometry fromJson(String jsonString) { + JsonFactory factory = new JsonFactory(); + try { + JsonParser jsonParser = factory.createJsonParser(jsonString); + jsonParser.nextToken(); + OperatorImportFromJson importer = (OperatorImportFromJson) OperatorFactoryLocal + .getInstance().getOperator( + Operator.Type.ImportMapGeometryFromJson); + + return importer.execute(Geometry.Type.Unknown, jsonParser); + } catch (Exception ex) { + } + + return null; + } +} diff --git a/unittest/com/esri/core/geometry/TestContains.java b/unittest/com/esri/core/geometry/TestContains.java new file mode 100644 index 00000000..71c811e9 --- /dev/null +++ b/unittest/com/esri/core/geometry/TestContains.java @@ -0,0 +1,32 @@ +package com.esri.core.geometry; + +import java.io.IOException; +import junit.framework.TestCase; +import org.codehaus.jackson.JsonFactory; +import org.codehaus.jackson.JsonParseException; +import org.codehaus.jackson.JsonParser; +import org.junit.Test; + +public class TestContains extends TestCase { + @Override + protected void setUp() throws Exception { + super.setUp(); + } + + @Override + protected void tearDown() throws Exception { + super.tearDown(); + } + + @Test + public static void testContainsFailureCR186456() throws JsonParseException, + IOException { + String str = "{\"rings\":[[[406944.399999999,287461.450000001],[406947.750000011,287462.299999997],[406946.44999999,287467.450000001],[406943.050000005,287466.550000005],[406927.799999992,287456.849999994],[406926.949999996,287456.599999995],[406924.800000005,287455.999999998],[406924.300000007,287455.849999999],[406924.200000008,287456.099999997],[406923.450000011,287458.449999987],[406922.999999987,287459.800000008],[406922.29999999,287462.099999998],[406921.949999991,287463.449999992],[406921.449999993,287465.050000011],[406920.749999996,287466.700000004],[406919.800000001,287468.599999996],[406919.050000004,287469.99999999],[406917.800000009,287471.800000008],[406916.04999999,287473.550000001],[406915.449999993,287473.999999999],[406913.700000001,287475.449999993],[406913.300000002,287475.899999991],[406912.050000008,287477.250000011],[406913.450000002,287478.150000007],[406915.199999994,287478.650000005],[406915.999999991,287478.800000005],[406918.300000007,287479.200000003],[406920.649999997,287479.450000002],[406923.100000013,287479.550000001],[406925.750000001,287479.450000002],[406928.39999999,287479.150000003],[406929.80000001,287478.950000004],[406932.449999998,287478.350000006],[406935.099999987,287477.60000001],[406938.699999998,287476.349999989],[406939.649999994,287473.949999999],[406939.799999993,287473.949999999],[406941.249999987,287473.75],[406942.700000007,287473.250000002],[406943.100000005,287473.100000003],[406943.950000001,287472.750000004],[406944.799999998,287472.300000006],[406944.999999997,287472.200000007],[406946.099999992,287471.200000011],[406946.299999991,287470.950000012],[406948.00000001,287468.599999996],[406948.10000001,287468.399999997],[406950.100000001,287465.050000011],[406951.949999993,287461.450000001],[406952.049999993,287461.300000001],[406952.69999999,287459.900000007],[406953.249999987,287458.549999987],[406953.349999987,287458.299999988],[406953.650000012,287457.299999992],[406953.900000011,287456.349999996],[406954.00000001,287455.300000001],[406954.00000001,287454.750000003],[406953.850000011,287453.750000008],[406953.550000012,287452.900000011],[406953.299999987,287452.299999988],[406954.500000008,287450.299999996],[406954.00000001,287449.000000002],[406953.399999987,287447.950000006],[406953.199999988,287447.550000008],[406952.69999999,287446.850000011],[406952.149999992,287446.099999988],[406951.499999995,287445.499999991],[406951.149999996,287445.249999992],[406950.449999999,287444.849999994],[406949.600000003,287444.599999995],[406949.350000004,287444.549999995],[406948.250000009,287444.499999995],[406947.149999987,287444.699999994],[406946.849999989,287444.749999994],[406945.899999993,287444.949999993],[406944.999999997,287445.349999991],[406944.499999999,287445.64999999],[406943.650000003,287446.349999987],[406942.900000006,287447.10000001],[406942.500000008,287447.800000007],[406942.00000001,287448.700000003],[406941.600000011,287449.599999999],[406941.350000013,287450.849999994],[406941.350000013,287451.84999999],[406941.450000012,287452.850000012],[406941.750000011,287453.850000007],[406941.800000011,287454.000000007],[406942.150000009,287454.850000003],[406942.650000007,287455.6],[406943.150000005,287456.299999997],[406944.499999999,287457.299999992],[406944.899999997,287457.599999991],[406945.299999995,287457.949999989],[406944.399999999,287461.450000001],[406941.750000011,287461.999999998],[406944.399999999,287461.450000001]],[[406944.399999999,287461.450000001],[406947.750000011,287462.299999997],[406946.44999999,287467.450000001],[406943.050000005,287466.550000005],[406927.799999992,287456.849999994],[406944.399999999,287461.450000001]]]}"; + JsonFactory jsonFactory = new JsonFactory(); + JsonParser jsonParser = jsonFactory.createJsonParser(str); + MapGeometry mg = GeometryEngine.jsonToGeometry(jsonParser); + boolean res = GeometryEngine.contains(mg.getGeometry(), + mg.getGeometry(), null); + assertTrue(res); + } +} diff --git a/unittest/com/esri/core/geometry/TestConvexHull.java b/unittest/com/esri/core/geometry/TestConvexHull.java new file mode 100644 index 00000000..6c2b668a --- /dev/null +++ b/unittest/com/esri/core/geometry/TestConvexHull.java @@ -0,0 +1,888 @@ +package com.esri.core.geometry; + +import junit.framework.TestCase; +import org.junit.Test; + +public class TestConvexHull extends TestCase { + @Override + protected void setUp() throws Exception { + super.setUp(); + } + + @Override + protected void tearDown() throws Exception { + super.tearDown(); + } + + @Test + public static void testDegenerate() { + OperatorConvexHull bounding = (OperatorConvexHull) OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.ConvexHull); + OperatorDensifyByLength densify = (OperatorDensifyByLength) OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.DensifyByLength); + + { + Polygon polygon = new Polygon(); + polygon.startPath(0, 0); + polygon.lineTo(1, 0); + polygon.lineTo(0, 0); + polygon.lineTo(2, 0); + polygon.lineTo(1, 0); + polygon.lineTo(3, 0); + + polygon.startPath(0, 0); + polygon.lineTo(1, 0); + polygon.lineTo(0, 0); + polygon.lineTo(2, 0); + polygon.lineTo(1, 0); + polygon.lineTo(3, 0); + + Polygon densified = (Polygon) (densify.execute(polygon, .5, null)); + Polygon convex_hull = (Polygon) (bounding.execute(densified, null)); + assertTrue(bounding.isConvex(convex_hull, null)); + assertTrue(convex_hull.calculateArea2D() == 0.0); + + Point2D p1 = convex_hull.getXY(0); + Point2D p2 = convex_hull.getXY(1); + assertTrue(p1.x == 0.0 && p1.y == 0.0); + assertTrue(p2.x == 3.0 && p2.y == 0.0); + } + + { + Polygon polygon = new Polygon(); + polygon.startPath(0, 0); + polygon.lineTo(0, 0); + polygon.lineTo(0, 0); + polygon.lineTo(NumberUtils.doubleEps(), 0); + polygon.lineTo(0, NumberUtils.doubleEps()); + polygon.lineTo(10, 0); + polygon.lineTo(10, 0); + polygon.lineTo(10, 5); + polygon.lineTo(10, 10); + polygon.lineTo(5, 10); + polygon.lineTo(10, 0); + polygon.lineTo(10, 10); + polygon.lineTo(5, 10); + polygon.lineTo(0, 10); + polygon.lineTo(0, 0); + + polygon.startPath(0, 0); + polygon.lineTo(0, 0); + polygon.lineTo(0, 0); + polygon.lineTo(10, 0); + polygon.lineTo(10, 0); + polygon.lineTo(10, 5); + polygon.lineTo(10, 10); + polygon.lineTo(5, 10); + polygon.lineTo(10, 0); + polygon.lineTo(10, 10); + polygon.lineTo(5, 10); + polygon.lineTo(0, 10); + polygon.lineTo(0, 0); + + polygon.startPath(0, 0); + polygon.lineTo(0, 0); + polygon.lineTo(0, 0); + polygon.lineTo(10, 0); + polygon.lineTo(5, 0); + polygon.lineTo(10, 0); + polygon.lineTo(10, 5); + polygon.lineTo(10, 0); + polygon.lineTo(10, 10); + polygon.lineTo(5, 10); + polygon.lineTo(10, 10); + polygon.lineTo(5, 10); + polygon.lineTo(0, 10); + polygon.lineTo(5, 10); + polygon.lineTo(0, 0); + + Polygon densified = (Polygon) (densify.execute(polygon, 1, null)); + Polygon convex_hull = (Polygon) (bounding.execute(densified, null)); + + double area = convex_hull.calculateArea2D(); + assertTrue(bounding.isConvex(convex_hull, null)); + assertTrue(area == 100.0); + + Point2D p1 = convex_hull.getXY(0); + Point2D p2 = convex_hull.getXY(1); + Point2D p3 = convex_hull.getXY(2); + Point2D p4 = convex_hull.getXY(3); + assertTrue(p1.x == 0.0 && p1.y == 0.0); + assertTrue(p2.x == 0.0 && p2.y == 10.0); + assertTrue(p3.x == 10.0 && p3.y == 10.0); + assertTrue(p4.x == 10.0 && p4.y == 0.0); + } + + { + Polygon polygon = new Polygon(); + polygon.startPath(0, 0); + polygon.lineTo(0, 10); + polygon.lineTo(5, 10); + polygon.lineTo(5, 5); + polygon.lineTo(5, 8); + polygon.lineTo(10, 10); + polygon.lineTo(10, 0); + + Polygon densified = (Polygon) (densify.execute(polygon, 1, null)); + Polygon convex_hull = (Polygon) (bounding.execute(densified, null)); + + double area = convex_hull.calculateArea2D(); + assertTrue(bounding.isConvex(convex_hull, null)); + assertTrue(area == 100.0); + + Point2D p1 = convex_hull.getXY(0); + Point2D p2 = convex_hull.getXY(1); + Point2D p3 = convex_hull.getXY(2); + Point2D p4 = convex_hull.getXY(3); + assertTrue(p1.x == 0.0 && p1.y == 0.0); + assertTrue(p2.x == 0.0 && p2.y == 10.0); + assertTrue(p3.x == 10.0 && p3.y == 10.0); + assertTrue(p4.x == 10.0 && p4.y == 0.0); + } + + { + Polygon polygon = new Polygon(); + polygon.startPath(0, 0); + polygon.lineTo(0, 0); + polygon.lineTo(0, 0); + polygon.lineTo(10, 0); + polygon.lineTo(5, 0); + polygon.lineTo(10, 0); + polygon.lineTo(10, 5); + polygon.lineTo(10, 0); + polygon.lineTo(10, 10); + polygon.lineTo(5, 10); + polygon.lineTo(10, 10); + polygon.lineTo(5, 10); + polygon.lineTo(0, 10); + polygon.lineTo(5, 10); + polygon.lineTo(0, 0); + Polygon densified = (Polygon) (densify.execute(polygon, 1, null)); + Polygon convex_hull = (Polygon) (bounding.execute(densified, null)); + double area = convex_hull.calculateArea2D(); + assertTrue(bounding.isConvex(convex_hull, null)); + assertTrue(area == 100.0); + + Point2D p1 = convex_hull.getXY(0); + Point2D p2 = convex_hull.getXY(1); + Point2D p3 = convex_hull.getXY(2); + Point2D p4 = convex_hull.getXY(3); + assertTrue(p1.x == 0.0 && p1.y == 0.0); + assertTrue(p2.x == 0.0 && p2.y == 10.0); + assertTrue(p3.x == 10.0 && p3.y == 10.0); + assertTrue(p4.x == 10.0 && p4.y == 0.0); + } + + { + Polygon polygon = new Polygon(); + polygon.startPath(0, 0); + polygon.lineTo(0, 0); + polygon.lineTo(0, 10); + polygon.lineTo(0, 0); + polygon.lineTo(10, 10); + polygon.lineTo(0, 0); + polygon.lineTo(10, 0); + polygon.lineTo(0, 0); + + polygon.startPath(0, 10); + polygon.lineTo(0, 10); + polygon.lineTo(10, 10); + polygon.lineTo(0, 10); + polygon.lineTo(10, 0); + polygon.lineTo(0, 10); + polygon.lineTo(0, 0); + polygon.lineTo(0, 10); + + polygon.startPath(10, 10); + polygon.lineTo(10, 10); + polygon.lineTo(10, 0); + polygon.lineTo(10, 10); + polygon.lineTo(0, 0); + polygon.lineTo(10, 10); + polygon.lineTo(0, 10); + polygon.lineTo(10, 10); + + polygon.startPath(10, 0); + polygon.lineTo(10, 0); + polygon.lineTo(0, 0); + polygon.lineTo(10, 0); + polygon.lineTo(0, 10); + polygon.lineTo(10, 0); + polygon.lineTo(10, 10); + polygon.lineTo(10, 0); + + Polygon densified = (Polygon) (densify.execute(polygon, 1, null)); + Polygon convex_hull = (Polygon) (bounding.execute(densified, null)); + assertTrue(bounding.isConvex(convex_hull, null)); + + Point2D p1 = convex_hull.getXY(0); + Point2D p2 = convex_hull.getXY(1); + Point2D p3 = convex_hull.getXY(2); + Point2D p4 = convex_hull.getXY(3); + assertTrue(p1.x == 0.0 && p1.y == 0.0); + assertTrue(p2.x == 0.0 && p2.y == 10.0); + assertTrue(p3.x == 10.0 && p3.y == 10.0); + assertTrue(p4.x == 10.0 && p4.y == 0.0); + } + + { + MultiPoint mpoint = new MultiPoint(); + mpoint.add(4, 4); + mpoint.add(4, 4); + mpoint.add(4, 4); + mpoint.add(4, 4); + + Polygon convex_hull = (Polygon) (bounding.execute(mpoint, null)); + assertTrue(convex_hull.getPointCount() == 2); + assertTrue(convex_hull.calculateArea2D() == 0.0); + assertTrue(bounding.isConvex(convex_hull, null)); + } + + { + MultiPoint mpoint = new MultiPoint(); + mpoint.add(4, 4); + + MultiPoint convex_hull = (MultiPoint) (bounding.execute(mpoint, + null)); + assertTrue(convex_hull.getPointCount() == 1); + assertTrue(bounding.isConvex(convex_hull, null)); + assertTrue(convex_hull == mpoint); + } + + { + MultiPoint mpoint = new MultiPoint(); + mpoint.add(4, 4); + mpoint.add(4, 5); + + Polyline convex_hull = (Polyline) (bounding.execute(mpoint, null)); + assertTrue(convex_hull.getPointCount() == 2); + assertTrue(bounding.isConvex(convex_hull, null)); + assertTrue(convex_hull.calculateLength2D() == 1.0); + } + + { + Line line = new Line(); + line.setStartXY(0, 0); + line.setEndXY(0, 1); + + Polyline convex_hull = (Polyline) (bounding.execute(line, null)); + assertTrue(convex_hull.getPointCount() == 2); + assertTrue(bounding.isConvex(convex_hull, null)); + assertTrue(convex_hull.calculateLength2D() == 1.0); + } + + { + Polyline polyline = new Polyline(); + polyline.startPath(0, 0); + polyline.lineTo(0, 1); + + Polyline convex_hull = (Polyline) (bounding.execute(polyline, null)); + assertTrue(convex_hull.getPointCount() == 2); + assertTrue(bounding.isConvex(convex_hull, null)); + assertTrue(polyline == convex_hull); + assertTrue(convex_hull.calculateLength2D() == 1.0); + } + + { + Envelope env = new Envelope(0, 0, 10, 10); + + Envelope convex_hull = (Envelope) (bounding.execute(env, null)); + assertTrue(bounding.isConvex(convex_hull, null)); + assertTrue(env == convex_hull); + } + } + + @Test + public static void testSquare() { + OperatorConvexHull bounding = (OperatorConvexHull) OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.ConvexHull); + OperatorDensifyByLength densify = (OperatorDensifyByLength) OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.DensifyByLength); + OperatorDifference difference = (OperatorDifference) OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Difference); + OperatorContains contains = (OperatorContains) OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Contains); + + Polygon square = new Polygon(); + square.startPath(0, 0); + square.lineTo(2, 3); + square.lineTo(1, 4); + square.lineTo(0, 5); + square.lineTo(0, 7); + square.lineTo(2, 7); + square.lineTo(0, 10); + square.lineTo(4, 7); + square.lineTo(6, 7); + square.lineTo(7, 10); + square.lineTo(8, 10); + square.lineTo(10, 10); + square.lineTo(8, 7); + square.lineTo(10, 5); + square.lineTo(8, 3); + square.lineTo(10, 1); + square.lineTo(10, 0); + square.lineTo(5, 5); + square.lineTo(8, 0); + square.lineTo(4, 3); + square.lineTo(5, 0); + square.lineTo(3, 1); + square.lineTo(3, 0); + square.lineTo(2, 1); + + Polygon densified = (Polygon) (densify.execute(square, 1.0, null)); + Polygon convex_hull = (Polygon) (bounding.execute(densified, null)); + Polygon differenced = (Polygon) (difference.execute(densified, + convex_hull, SpatialReference.create(4326), null)); + + assertTrue(differenced.isEmpty()); + assertTrue(bounding.isConvex(convex_hull, null)); + } + + @Test + public static void testPolygons() { + OperatorConvexHull bounding = (OperatorConvexHull) OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.ConvexHull); + OperatorDensifyByLength densify = (OperatorDensifyByLength) OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.DensifyByLength); + OperatorDifference difference = (OperatorDifference) OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Difference); + + { + Polygon shape = (Polygon) (TestCommonMethods + .fromJson("{\"rings\":[[[1.3734426370715553,-90],[-24.377532184629967,-63.428606327053856],[-25.684686546621553,90],[-24.260574484321914,80.526315789473699],[-25.414389575040037,90],[-23.851448513708718,90],[-23.100135788742072,87.435887853000679],[5.6085096351011448,-48.713222410606306],[1.3734426370715553,-90]]]}") + .getGeometry()); + Polygon convex_hull = (Polygon) (bounding.execute(shape, null)); + Polygon differenced = (Polygon) (difference.execute(shape, + convex_hull, SpatialReference.create(4326), null)); + assertTrue(differenced.isEmpty()); + assertTrue(bounding.isConvex(convex_hull, null)); + } + + { + Polygon polygon = (Polygon) (TestCommonMethods + .fromJson("{\"rings\":[[[-179.64843749999693,-43.3535476539993],[-179.99999999999696,-43.430006211999284],[-179.99999999999696,39.329644416999436],[-179.64843749999693,38.98862638799943],[-89.99999999999838,29.008084980999506],[-112.8515624999981,-16.20113770599962],[-115.66406249999804,-18.882554574999688],[-124.80468749999788,-23.7925511709996],[-138.86718749999767,-30.6635901109995],[-157.49999999999736,-38.468358112999354],[-162.42187499999724,-39.56498442199932],[-179.64843749999693,-43.3535476539993]],[[179.99999999999696,-43.430006211999284],[179.64843749999693,-43.50646476999926],[162.0703124999973,-42.36267115399919],[160.3124999999973,-42.24790485699929],[143.78906249999756,-41.1680427339993],[138.16406249999767,-39.64744846799925],[98.43749999999845,-28.523889212999524],[78.39843749999878,-5.1644422999998705],[75.9374999999988,19.738611663999766],[88.2421874999986,33.51651305599954],[108.63281249999815,44.160795160999356],[138.16406249999767,51.02062617799914],[140.9765624999976,51.68129673399923],[160.3124999999973,52.8064856429991],[162.0703124999973,52.908902047999206],[163.12499999999727,52.97036560499911],[165.93749999999716,52.97036560499911],[179.99999999999696,39.329644416999436],[179.99999999999696,-43.430006211999284]]]}") + .getGeometry()); + Polygon densified = (Polygon) (densify.execute(polygon, 10.0, null)); + Polygon convex_hull = (Polygon) (bounding.execute(densified, null)); + Polygon differenced = (Polygon) (difference.execute(densified, + convex_hull, SpatialReference.create(4326), null)); + assertTrue(differenced.isEmpty()); + assertTrue(bounding.isConvex(convex_hull, null)); + } + + { + Polygon polygon = new Polygon(); + polygon.startPath(1, 0); + polygon.lineTo(-1, 0); + polygon.lineTo(0, -1); + Polygon convex_hull = (Polygon) (bounding.execute(polygon, null)); + Polygon differenced = (Polygon) (difference.execute(polygon, + convex_hull, SpatialReference.create(4326), null)); + assertTrue(differenced.isEmpty()); + assertTrue(bounding.isConvex(convex_hull, null)); + } + + { + Polygon polygon = (Polygon) (TestCommonMethods + .fromJson("{\"rings\":[[[-38.554566833914528,21.902000764339238],[-30.516168471666138,90],[-38.554566833914528,21.902000764339238]],[[-43.013227444613932,28.423410187206883],[-43.632473335895916,90],[-42.342268693420237,62.208637129146894],[-37.218731802058755,63.685357222187029],[-32.522681335230686,47.080307818055296],[-40.537308829621097,-21.881392019745604],[-47.59510451722663,18.812521648505964],[-53.25344489340366,30.362745244224911],[-46.629060462410138,90],[-50.069277433245119,18.254168921734287],[-42.171214434397982,72.623347387008081],[-43.000844452530551,90],[-46.162281544954659,90],[-39.462049205071331,90],[-47.434856316742902,38.662565208814371],[-52.13115779642537,-19.952586632199857],[-56.025328966335081,90],[-60.056846215416158,-44.023645282268355],[-60.12338894192289,50.374596189881942],[-35.787508034048379,-7.8839007676038513],[-60.880218074135605,-46.447995750907815],[-67.782542852117956,-85.106300958016107],[-65.053131764313761,-0.96651520578494665],[-72.375821140304154,90],[-78.561502106749245,90],[-83.809168672565946,33.234498214085811],[-60.880218054506344,-46.447995733653201],[-75.637095425108981,59.886574792622838],[-71.364085965028096,31.976373491332097],[-67.89968380886117,90],[-67.544349171474749,8.8435794458927504],[-70.780047377934707,80.683454463576624],[-64.996733940204948,34.349882797035313],[-56.631753638680905,39.815838152456926],[-60.392350183516896,52.75446132093407],[-58.51633728692137,90],[-64.646972065627097,41.444197803942579],[-73.355591244695518,-0.15370205145035776],[-43.013227444613932,28.423410187206883]],[[-69.646471076946,-85.716191379686904],[-62.854465128320491,-45.739046580967972],[-71.377481570643141,-90],[-66.613495837251435,-90],[-66.9765142407159,-90],[-66.870099169607329,-90],[-67.23180828626819,-61.248439074609649],[-58.889775875438851,-90],[-53.391995883729322,-69.476385967096491],[-69.646471076946,-85.716191379686904]]]}") + .getGeometry()); + Polygon densified = (Polygon) (densify.execute(polygon, 10.0, null)); + Polygon convex_hull = (Polygon) (bounding.execute(densified, null)); + Polygon differenced = (Polygon) (difference.execute(densified, + convex_hull, SpatialReference.create(4326), null)); + assertTrue(differenced.isEmpty()); + // assertTrue(bounding.isConvex(*convex_hull, null)); + } + + { + Polygon polygon = (Polygon) (TestCommonMethods + .fromJson("{\"rings\":[[[-38.554566833914528,21.902000764339238],[-30.516168471666138,90],[-38.554566833914528,21.902000764339238]],[[-43.013227444613932,28.423410187206883],[-43.632473335895916,90],[-42.342268693420237,62.208637129146894],[-37.218731802058755,63.685357222187029],[-32.522681335230686,47.080307818055296],[-40.537308829621097,-21.881392019745604],[-47.59510451722663,18.812521648505964],[-53.25344489340366,30.362745244224911],[-46.629060462410138,90],[-50.069277433245119,18.254168921734287],[-42.171214434397982,72.623347387008081],[-43.000844452530551,90],[-46.162281544954659,90],[-39.462049205071331,90],[-47.434856316742902,38.662565208814371],[-52.13115779642537,-19.952586632199857],[-56.025328966335081,90],[-60.056846215416158,-44.023645282268355],[-60.12338894192289,50.374596189881942],[-35.787508034048379,-7.8839007676038513],[-60.880218074135605,-46.447995750907815],[-67.782542852117956,-85.106300958016107],[-65.053131764313761,-0.96651520578494665],[-72.375821140304154,90],[-78.561502106749245,90],[-83.809168672565946,33.234498214085811],[-60.880218054506344,-46.447995733653201],[-75.637095425108981,59.886574792622838],[-71.364085965028096,31.976373491332097],[-67.89968380886117,90],[-67.544349171474749,8.8435794458927504],[-70.780047377934707,80.683454463576624],[-64.996733940204948,34.349882797035313],[-56.631753638680905,39.815838152456926],[-60.392350183516896,52.75446132093407],[-58.51633728692137,90],[-64.646972065627097,41.444197803942579],[-73.355591244695518,-0.15370205145035776],[-43.013227444613932,28.423410187206883]],[[-69.646471076946,-85.716191379686904],[-62.854465128320491,-45.739046580967972],[-71.377481570643141,-90],[-66.613495837251435,-90],[-66.9765142407159,-90],[-66.870099169607329,-90],[-67.23180828626819,-61.248439074609649],[-58.889775875438851,-90],[-53.391995883729322,-69.476385967096491],[-69.646471076946,-85.716191379686904]]]}") + .getGeometry()); + Polygon convex_hull = (Polygon) (bounding.execute(polygon, null)); + Polygon differenced = (Polygon) (difference.execute(polygon, + convex_hull, SpatialReference.create(4326), null)); + assertTrue(differenced.isEmpty()); + assertTrue(bounding.isConvex(convex_hull, null)); + } + + { + Polygon polygon = (Polygon) (TestCommonMethods + .fromJson("{\"rings\":[[[-36.269498017702901,-26.37490682626369],[-49.146436641060951,-49.881862499696126],[-37.560006446488146,-45.592052597656789],[-39.13770692863632,-69.085816352131204],[-65.415587331361877,-90],[-51.462290812033373,-16.760787566546721],[-28.454456182408332,90],[-36.269498017702901,-26.37490682626369]],[[-40.542178258552283,-90],[-39.13770692863632,-69.085816352131204],[-16.295804332590937,-50.906277575066262],[-40.542178258552283,-90]],[[-16.295804332590937,-50.906277575066262],[-5.6790432913971927,-33.788307256548933],[14.686101893282586,-26.248228042967728],[-16.295804332590937,-50.906277575066262]],[[-37.560006446488146,-45.592052597656789],[-36.269498017702901,-26.37490682626369],[27.479825940672225,90],[71.095881152477034,90],[-5.6790432913971927,-33.788307256548933],[-37.560006446488146,-45.592052597656789]]]}") + .getGeometry()); + Polygon convex_hull = (Polygon) (bounding.execute(polygon, null)); + Polygon differenced = (Polygon) (difference.execute(polygon, + convex_hull, SpatialReference.create(4326), null)); + assertTrue(differenced.isEmpty()); + assertTrue(bounding.isConvex(convex_hull, null)); + } + + { + Polygon polygon = (Polygon) (TestCommonMethods + .fromJson("{\"rings\":[[[-77.020281185856106,-80.085419699581706],[-77.328568930885723,-83.404479889897416],[-80.495259564600545,-90],[-77.020281185856106,-80.085419699581706]],[[-77.941187535385211,-90],[-77.328568930885723,-83.404479889897416],[-39.252034383972621,-4.0994329574862469],[-39.29471328421063,-6.5269494453154593],[-77.941187535385211,-90]],[[-77.020281185856106,-80.085419699581706],[-62.688864277996522,74.208210509833052],[-38.108861278327581,80.371071656873013],[-37.597643844595929,90],[-38.663943358642484,29.350366647752089],[-77.020281185856106,-80.085419699581706]],[[-40.265125886194951,-61.722668598742551],[-39.29471328421063,-6.5269494453154593],[-15.554402498931253,44.750073899273843],[-8.4447006412989474,13.127318978368956],[-5.310206313296316,-4.5170390491918795],[-40.265125886194951,-61.722668598742551]],[[-39.252034383972621,-4.0994329574862469],[-38.663943358642484,29.350366647752089],[-22.476078360563164,75.536520897660651],[-15.632105532320049,45.095683888365997],[-39.252034383972621,-4.0994329574862469]],[[-15.554402498931253,44.750073899273843],[-15.632105532320049,45.095683888365997],[-8.9755856576261941,58.959750756602595],[-15.554402498931253,44.750073899273843]]]}") + .getGeometry()); + Polygon convex_hull = (Polygon) (bounding.execute(polygon, null)); + Polygon differenced = (Polygon) (difference.execute(polygon, + convex_hull, SpatialReference.create(4326), null)); + assertTrue(differenced.isEmpty()); + assertTrue(bounding.isConvex(convex_hull, null)); + } + + { + Polygon polygon = (Polygon) (TestCommonMethods + .fromJson("{\"rings\":[[[-68.840007952128175,37.060080998089632],[-68.145986924561413,31.114096694815196],[-69.187773850176768,30.693518246958952],[-68.840007952128175,37.060080998089632]],[[-75.780513355389928,-90],[-69.21567111077384,30.182802098042274],[-50.875629803516389,37.146119571446704],[-75.780513355389928,-90]],[[4.2911006174797457,-1.144569312564311],[-66.484019915251849,80.191238371060038],[-65.948228008382316,90],[4.2911006174797457,-1.144569312564311]],[[-90,22.291441435181515],[-69.187773850176768,30.693518246958952],[-69.21567111077384,30.182802098042274],[-90,22.291441435181515]],[[-68.840007952128175,37.060080998089632],[-75.019206401201359,90],[-66.484019915251849,80.191238371060038],[-68.840007952128175,37.060080998089632]]]}") + .getGeometry()); + Polygon convex_hull = (Polygon) (bounding.execute(polygon, null)); + Polygon differenced = (Polygon) (difference.execute(polygon, + convex_hull, SpatialReference.create(4326), null)); + assertTrue(differenced.isEmpty()); + assertTrue(bounding.isConvex(convex_hull, null)); + } + + { + Polygon polygon = (Polygon) (TestCommonMethods + .fromJson("{\"rings\":[[[27.570109889215438,22.850616190228489],[75.703105729477357,-90],[2.1548000876241362,-15.817792950796967],[27.570109889215438,22.850616190228489]],[[-0.069915984436478951,-90],[-46.602410662754053,-89.999999998014729],[-14.977190481820156,-41.883452819243004],[-0.069915984436478951,-90]],[[-14.977190481820156,-41.883452819243004],[-34.509989609682322,21.163004866431177],[2.1548000876241362,-15.817792950796967],[-14.977190481820156,-41.883452819243004]]]}") + .getGeometry()); + Polygon convex_hull = (Polygon) (bounding.execute(polygon, null)); + Polygon differenced = (Polygon) (difference.execute(polygon, + convex_hull, SpatialReference.create(4326), null)); + assertTrue(differenced.isEmpty()); + assertTrue(bounding.isConvex(convex_hull, null)); + } + + { + Polygon polygon = (Polygon) (TestCommonMethods + .fromJson("{\"rings\":[[[28.865673900286581,33.379551302126075],[39.505669183485338,-34.957993133630559],[7.152466542048213,-90],[28.865673900286581,33.379551302126075]],[[-64.597291313620858,2.4515644574812248],[20.050002923927103,90],[24.375150856531356,62.220853377417541],[-64.597291313620858,2.4515644574812248]],[[28.865673900286581,33.379551302126075],[24.375150856531356,62.220853377417541],[35.223952527956932,69.508785974507163],[28.865673900286581,33.379551302126075]]]}") + .getGeometry()); + Polygon convex_hull = (Polygon) (bounding.execute(polygon, null)); + Polygon differenced = (Polygon) (difference.execute(polygon, + convex_hull, SpatialReference.create(4326), null)); + assertTrue(differenced.isEmpty()); + assertTrue(bounding.isConvex(convex_hull, null)); + } + + { + Polygon polygon = (Polygon) (TestCommonMethods + .fromJson("{\"rings\":[[[-66.582505384413167,-51.305212522944061],[-60.169897093348865,-90],[-90,-90],[-66.582505384413167,-51.305212522944061]],[[20.858462934004656,-90],[-35.056287147954386,0.78833269359179781],[18.933251883215579,90],[20.858462934004656,-90]],[[-66.582505384413167,-51.305212522944061],[-90,90],[-35.056287147954386,0.78833269359179781],[-66.582505384413167,-51.305212522944061]]]}") + .getGeometry()); + Polygon convex_hull = (Polygon) (bounding.execute(polygon, null)); + Polygon differenced = (Polygon) (difference.execute(polygon, + convex_hull, SpatialReference.create(4326), null)); + assertTrue(differenced.isEmpty()); + assertTrue(bounding.isConvex(convex_hull, null)); + } + + { + Polygon polygon = (Polygon) (TestCommonMethods + .fromJson("{\"rings\":[[[36.692916710279974,-90],[-31.182443792600079,6.434474852744998],[-90,90],[52.245260790065387,57.329280208760991],[36.692916710279974,-90]]]}") + .getGeometry()); + Polygon convex_hull = (Polygon) (bounding.execute(polygon, null)); + Polygon differenced = (Polygon) (difference.execute(polygon, + convex_hull, SpatialReference.create(4326), null)); + assertTrue(differenced.isEmpty()); + assertTrue(bounding.isConvex(convex_hull, null)); + } + + { + Polygon polygon = (Polygon) (TestCommonMethods + .fromJson("{\"rings\":[[[-17.959089916602533,-4.3577640799248218],[-29.181784251472308,-90],[-65.493717350127127,15.053615507086979],[-17.959089916602533,-4.3577640799248218]],[[-21.884657435973146,-34.517617672142393],[-17.94005076020704,-4.3655389655558539],[9.3768748358343359,-15.520758655380195],[-21.884657435973146,-34.517617672142393]],[[-17.94005076020704,-4.3655389655558539],[-17.959089916602533,-4.3577640799248218],[-5.8963967801936494,87.694641571893939],[-17.94005076020704,-4.3655389655558539]]]}") + .getGeometry()); + Polygon convex_hull = (Polygon) (bounding.execute(polygon, null)); + Polygon differenced = (Polygon) (difference.execute(polygon, + convex_hull, SpatialReference.create(4326), null)); + assertTrue(differenced.isEmpty()); + assertTrue(bounding.isConvex(convex_hull, null)); + } + + { + Polygon polygon = (Polygon) (TestCommonMethods + .fromJson("{\"rings\":[[[17.198360589495572,-77.168667157542373],[-24.835678541343281,-83.717338556412017],[-30.259244993378722,61.914816728303791],[17.198360589495572,-77.168667157542373]],[[-8.3544985146710644,-90],[17.979891823366039,-79.459092168186686],[21.576625471325329,-90],[-8.3544985146710644,-90]],[[17.979891823366039,-79.459092168186686],[17.198360589495572,-77.168667157542373],[27.846596597209441,-75.509730732825361],[17.979891823366039,-79.459092168186686]]]}") + .getGeometry()); + Polygon convex_hull = (Polygon) (bounding.execute(polygon, null)); + Polygon differenced = (Polygon) (difference.execute(polygon, + convex_hull, SpatialReference.create(4326), null)); + assertTrue(differenced.isEmpty()); + assertTrue(bounding.isConvex(convex_hull, null)); + } + + { + Polygon polygon = (Polygon) (TestCommonMethods + .fromJson("{\"rings\":[[[-1.2588613620456419,13.607321860624268],[61.845346679259052,48.415944386573557],[15.226225965240992,-5.3702891526017318],[0.92681706095183469,1.6819284384951441],[3.8469417404317623,-14.250715301799051],[7.2615297628459139,-14.559458820527061],[4.4896578086498238,-17.757471781424698],[14.589138845678622,-72.861774161244625],[-10.508572009494033,-35.06149380752737],[-58.12642296329372,-90],[-15.260062192400673,90],[-1.2588613620456419,13.607321860624268]],[[0.92681706095183469,1.6819284384951441],[-1.2588613620456419,13.607321860624268],[-11.641308877525201,7.8803076458946304],[0.92681706095183469,1.6819284384951441]],[[-10.508572009494033,-35.06149380752737],[4.4896578086498238,-17.757471781424698],[3.8469417404317623,-14.250715301799051],[-26.125369947914503,-11.54064986657559],[-10.508572009494033,-35.06149380752737]],[[39.829571435268129,-17.504227477249202],[7.2615297628459139,-14.559458820527061],[15.226225965240992,-5.3702891526017318],[39.829571435268129,-17.504227477249202]]]}") + .getGeometry()); + Polygon convex_hull = (Polygon) (bounding.execute(polygon, null)); + Polygon differenced = (Polygon) (difference.execute(polygon, + convex_hull, SpatialReference.create(4326), null)); + assertTrue(differenced.isEmpty()); + assertTrue(bounding.isConvex(convex_hull, null)); + } + + { + Polygon polygon = (Polygon) (TestCommonMethods + .fromJson("{\"rings\":[[[-19.681975166855118,-34.10344217707847],[-90,89.999999998418275],[53.036316534501381,90],[-19.681975166855118,-34.10344217707847]],[[-52.434509065706855,-90],[-29.2339442498794,-50.405148598356135],[-2.8515119199232331,-90],[-52.434509065706855,-90]],[[18.310881874573923,-90],[-25.473718245381271,-43.987822508814972],[-19.681975166855118,-34.10344217707847],[-15.406194071963924,-41.649717163101563],[18.310881874573923,-90]],[[-29.2339442498794,-50.405148598356135],[-52.27954259799813,-15.81822990020261],[-25.473718245381271,-43.987822508814972],[-29.2339442498794,-50.405148598356135]]]}") + .getGeometry()); + Polygon convex_hull = (Polygon) (bounding.execute(polygon, null)); + Polygon differenced = (Polygon) (difference.execute(polygon, + convex_hull, SpatialReference.create(4326), null)); + assertTrue(differenced.isEmpty()); + assertTrue(bounding.isConvex(convex_hull, null)); + } + + { + Polygon polygon = (Polygon) (TestCommonMethods + .fromJson("{\"rings\":[[[49.939516827702498,-90],[-20.470128740962011,-68.102019032647391],[-20.124197553433845,-67.213968219799824],[34.438329237618149,-61.893901496061034],[49.939516827702498,-90]],[[-82.380918375714089,-73.284249936115529],[-4.7432060543229699,9.1484031048644194],[-11.790524932251525,21.926303986370414],[-3.4862200343039369,10.483021157965428],[19.753975453441285,35.158541777575607],[5.5896897290794696,-1.2030408273476854],[73.839023528563189,-58.052174675157325],[34.438329237618149,-61.893901496061034],[3.6757233436274213,-6.1164440290327313],[-20.124197553433845,-67.213968219799824],[-82.380918375714089,-73.284249936115529]],[[5.5896897290794696,-1.2030408273476854],[4.0842948437219349,0.050896618883412792],[-3.4862200343039369,10.483021157965428],[-4.7432060543229699,9.1484031048644194],[3.6757233436274213,-6.1164440290327313],[5.5896897290794696,-1.2030408273476854]]]}") + .getGeometry()); + Polygon convex_hull = (Polygon) (bounding.execute(polygon, null)); + Polygon differenced = (Polygon) (difference.execute(polygon, + convex_hull, SpatialReference.create(4326), null)); + assertTrue(differenced.isEmpty()); + assertTrue(bounding.isConvex(convex_hull, null)); + } + + { + Polygon polygon = (Polygon) (TestCommonMethods + .fromJson("{\"rings\":[[[4.7618727814345867,-14.245890151885444],[-7.1675180359486266,-90],[-83.232840716292529,40.620187389409224],[-29.219286930421923,6.9418934044755334],[-29.378277853968513,6.9629531745072839],[-28.933835455648254,6.7639099538036529],[4.7618727814345867,-14.245890151885444]],[[51.056303527367277,-43.111190419066219],[4.7618727814345867,-14.245890151885444],[5.632592229367642,-8.716640778187827],[-28.933835455648254,6.7639099538036529],[-29.219286930421923,6.9418934044755334],[2.700964609629902,2.7137705544807242],[12.385960896403816,0.48342578457646468],[51.056303527367277,-43.111190419066219]]]}") + .getGeometry()); + Polygon convex_hull = (Polygon) (bounding.execute(polygon, null)); + Polygon differenced = (Polygon) (difference.execute(polygon, + convex_hull, SpatialReference.create(4326), null)); + assertTrue(differenced.isEmpty()); + assertTrue(bounding.isConvex(convex_hull, null)); + } + + { + Polygon polygon = (Polygon) (TestCommonMethods + .fromJson("{\"rings\":[[[-21.426619830983732,-89.379667629404466],[-72.784461583687971,-88.999754827814016],[-81.94289434769162,25.456737039611831],[-38.382426191605546,-57.204127144336077],[-41.663734179022256,-78.439084394036513],[-29.749353943865881,-73.586348060869426],[-21.426619830983732,-89.379667629404466]],[[-21.09971823441461,-90],[-21.426619830983732,-89.379667629404466],[-21.080965849893449,-89.382224558742934],[-21.09971823441461,-90]],[[62.431917153693021,-90],[-21.080965849893449,-89.382224558742934],[-20.486971473666468,-69.813772479288062],[19.166418765782844,-53.662915804391695],[63.671046682728601,-90],[62.431917153693021,-90]],[[-29.749353943865881,-73.586348060869426],[-38.382426191605546,-57.204127144336077],[-31.449272112025476,-12.336278393150847],[-41.028899505665962,-4.5147159296945967],[-30.750049689226596,-7.8112663207986941],[-15.63587330244308,90],[-18.721998818789388,-11.66880646480822],[60.158611185675326,-36.966763960486929],[19.166418765782844,-53.662915804391695],[-19.049573405176112,-22.46036923493498],[-20.486971473666468,-69.813772479288062],[-29.749353943865881,-73.586348060869426]],[[-19.049573405176112,-22.46036923493498],[-18.721998818789388,-11.66880646480822],[-30.750049689226596,-7.8112663207986941],[-31.449272112025476,-12.336278393150847],[-19.049573405176112,-22.46036923493498]]]}") + .getGeometry()); + Polygon convex_hull = (Polygon) (bounding.execute(polygon, null)); + Polygon differenced = (Polygon) (difference.execute(polygon, + convex_hull, SpatialReference.create(4326), null)); + assertTrue(differenced.isEmpty()); + assertTrue(bounding.isConvex(convex_hull, null)); + } + + { + Polygon polygon = (Polygon) (TestCommonMethods + .fromJson("{\"rings\":[[[-17.906614911617162,-53.670186894017093],[-72.687715727164573,-90],[-77.889582483879749,90],[-47.149885004784061,16.372797801863811],[-58.874489264131405,8.3403055152440846],[-44.017112148517498,8.8692333739436133],[-43.760297522359615,8.2541153357643502],[-48.398890069305921,4.7201397602360009],[-38.665987052649818,-3.9476907252248874],[-17.906614911617162,-53.670186894017093]],[[-2.7387498969355368,-90],[-17.906614911617162,-53.670186894017093],[-6.8038688963847829,-46.30705103709559],[-2.7387498969355368,-90]],[[-6.8038688963847829,-46.30705103709559],[-8.2224486207861638,-31.0597897622158],[2.1962303277340673,-40.338351652092697],[-6.8038688963847829,-46.30705103709559]],[[-8.2224486207861638,-31.0597897622158],[-38.665987052649818,-3.9476907252248874],[-43.760297522359615,8.2541153357643502],[-42.90074612601282,8.9089763975751382],[-44.017112148517498,8.8692333739436133],[-47.149885004784061,16.372797801863811],[45.190674429223662,79.635046572817728],[40.490070954305672,72.441418146356597],[63.53694979672099,90],[75.056911135062407,13.108310545642606],[-0.027204347469059975,10.435289586728711],[-10.580480746811602,-5.715051428780245],[-8.2224486207861638,-31.0597897622158]],[[-42.90074612601282,8.9089763975751382],[-0.027204347469059975,10.435289586728711],[40.490070954305672,72.441418146356597],[-42.90074612601282,8.9089763975751382]]]}") + .getGeometry()); + Polygon convex_hull = (Polygon) (bounding.execute(polygon, null)); + Polygon differenced = (Polygon) (difference.execute(polygon, + convex_hull, SpatialReference.create(4326), null)); + assertTrue(differenced.isEmpty()); + assertTrue(bounding.isConvex(convex_hull, null)); + } + } + + @Test + public static void testPolylines() { + OperatorConvexHull bounding = (OperatorConvexHull) OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.ConvexHull); + OperatorDensifyByLength densify = (OperatorDensifyByLength) OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.DensifyByLength); + OperatorDifference difference = (OperatorDifference) OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Difference); + OperatorContains contains = (OperatorContains) OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Contains); + + { + Polyline poly = new Polyline(); + poly.startPath(0, 0); + poly.lineTo(0, 10); + poly.lineTo(0, 20); + poly.lineTo(0, 40); + poly.lineTo(0, 500); + + Polyline densified = (Polyline) (densify.execute(poly, 10.0, null)); + Polygon convex_hull = (Polygon) (bounding.execute(densified, null)); + Polyline differenced = (Polyline) (difference.execute(densified, + convex_hull, SpatialReference.create(4326), null)); + assertTrue(differenced.isEmpty()); + assertTrue(bounding.isConvex(convex_hull, null)); + } + + { + Polyline polyline = new Polyline(); + polyline.startPath(-200, -90); + polyline.lineTo(-180, -85); + polyline.lineTo(-90, -70); + polyline.lineTo(0, 0); + polyline.lineTo(100, 25); + polyline.lineTo(170, 45); + polyline.lineTo(225, 65); + + Polyline densified = (Polyline) (densify.execute(polyline, 10.0, + null)); + Polygon convex_hull = (Polygon) (bounding.execute(densified, null)); + boolean bcontains = contains.execute(convex_hull, densified, + SpatialReference.create(4326), null); + + assertTrue(bcontains); + assertTrue(bounding.isConvex(convex_hull, null)); + } + } + + @Test + public static void testNonSimpleShape() { + OperatorConvexHull bounding = (OperatorConvexHull) OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.ConvexHull); + OperatorDensifyByLength densify = (OperatorDensifyByLength) OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.DensifyByLength); + OperatorDifference difference = (OperatorDifference) OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Difference); + OperatorContains contains = (OperatorContains) OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Contains); + + { + Polygon shape = (Polygon) (TestCommonMethods + .fromJson("{\"rings\":[[[6.7260599916745036,-90],[15.304037095218971,-18.924146439950675],[6.3163297788539232,-90],[5.2105387036445823,-59.980719950158637],[5.1217504663506981,-60.571174400735508],[8.2945138368731044,-27.967042187979146],[15.76606600357545,28.594953216378414],[8.4365340991447919,66.880924521951329],[10.115022726199774,65.247385313781265],[12.721206966604395,-23.793016208456883],[12.051875868947576,-11.368909618476637],[11.867118776841318,44.968896646914239],[7.5326099218274614,35.095776924526533],[8.86765609460479,90],[3.2036592678446922,55.507964789691712],[0.23585282258761486,-42.620591380394039],[-1.2660432762142744,90],[5.5580612840503001,-9.4879902323389196],[12.258387597532487,-35.945231749575591],[-48.746716054894101,90],[7.2294405148356846,-15.719232058488402],[13.798313011339591,-10.467172541381753],[7.4430022048746718,6.3951685161785656],[6.4876332898327815,31.10016146737189],[9.3645424359058911,47.123308099298804],[13.398605254542668,-6.4398318586014325],[-90,90],[13.360786277212718,82.971274676174545],[7.9405631778693566,90],[10.512482079680538,90],[16.994982794293946,19.60673041736408],[16.723893839323615,22.728853852102926],[23.178783416627525,90],[6.7260599916745036,-90]],[[26.768777234301993,90],[20.949797955126346,90],[11.967758262201434,-0.45048849056049711],[17.535751576687339,52.767528591651441],[26.768777234301993,90]],[[18.677765775891793,12.559680067559942],[19.060218406331451,90],[17.123595624401705,90],[-2.3805299720687887,-90],[-11.882782057881979,-90],[21.640575461689693,90],[11.368255808198477,85.501555553904794],[17.390084032215348,90],[23.999392897519989,78.255909006554603],[-6.8860811786563101,69.49189433189926],[29.232578855788898,90],[25.951412073846683,90],[-5.5572284181160772,-16.763772082849457],[18.677765775891793,12.559680067559942]]]}") + .getGeometry()); + Polygon densified = (Polygon) (densify.execute(shape, 10.0, null)); + Polygon convex_hull = (Polygon) (bounding.execute(densified, null)); + Polygon differenced = (Polygon) (difference.execute(densified, + convex_hull, SpatialReference.create(4326), null)); + assertTrue(differenced.isEmpty()); + assertTrue(bounding.isConvex(convex_hull, null)); + } + + { + Polygon shape = (Polygon) (TestCommonMethods + .fromJson("{\"rings\":[[[-13.630596027421603,3.6796011190640709],[-10.617275202716886,-28.133054337834409],[-81.617315194491695,90],[-4.0763362539824293,-90],[2.8213537979804073,-90],[-5.1515857979973365,-11.605767592612787],[43.477754021411123,35.507543731267589],[-45.818261267516704,-90],[-4.8545715514870018,-64.204371906322223],[-1.744951154293144,-30.257848194381509],[-7.8524748309267149,15.513561279453089],[-10.657563385538953,-81.785061432086309],[-6.3487369893289411,-31.849779201388415],[-14.768278524737962,-12.004393281111058],[-27.001635582579841,90],[-14.967554248940855,-78.970629918591811],[-12.999635147475825,-38.584472796107939],[-13.630596027421603,3.6796011190640709]],[[-16.338143621861352,-37.415690513288375],[-21.553879270366266,-90],[-18.649338100909404,-90],[-24.880584966233631,1.3133858590648728],[-16.483464632078249,-53.979692212288882],[-24.836979215403964,-68.69859399640147],[-29.708282990385214,-90],[-27.469962102507036,-1.6392995673644872],[-20.405051753708271,61.943199597870034],[-18.242567838912599,24.405109362934219],[-66.334547696572528,-52.678390155566603],[-13.471083255903507,-33.782708412943229],[-7.092757068096085,33.673785662500464],[-2.7427100969018205,74.386868339212668],[-8.2174861339989675,90],[-15.699459164009667,90],[-9.5910045204059156,90],[-8.4504603287557369,90],[-1.5498862802092637,2.5144190340747681],[-6.5326327868410639,-17.428029961128306],[-10.947786354404593,31.516236387466538],[-7.4777936485986354,12.486727826508769],[-13.89052186883092,12.397126427870356],[-10.530672679779606,-55.463541447339118],[-8.7161833631330374,-90],[-4.7231067612639519,-90],[-3.9692500849117041,-32.204677519048822],[3.740804266163555,32.88191805391007],[6.2021313886056246,76.617541950091564],[6.1183997672398194,90],[0.59730820015390673,90],[7.3242950674530753,18.030401540676614],[1.8252371571535342,90],[-16.338143621861352,-37.415690513288375]]]}") + .getGeometry()); + Polygon densified = (Polygon) (densify.execute(shape, 10.0, null)); + Polygon convex_hull = (Polygon) (bounding.execute(densified, null)); + Polygon differenced = (Polygon) (difference.execute(densified, + convex_hull, SpatialReference.create(4326), null)); + assertTrue(differenced.isEmpty()); + assertTrue(bounding.isConvex(convex_hull, null)); + } + + { + Polygon shape = (Polygon) (TestCommonMethods + .fromJson("{\"rings\":[[[-11.752662474672046,-90],[-76.236530072126513,7.3247326417920817],[18.933251883215579,90],[51.538924439116798,90],[52.253017336758049,80.352482145105284],[41.767201918260639,51.890297432229289],[21.697252770910882,-1.3185641048567049],[45.112193442818935,60.758441021743636],[48.457184967377231,69.626584611257954],[49.531808284502759,70.202152706968036],[52.394797054144334,71.533541126234581],[ 52.9671102343993,70.704964290210626],[58.527850348069251,16.670036266565845],[62.310807912773328,-34.249918700039238],[62.775020703241523,-43.541598916699364],[64.631871865114277,-80.708319783339874],[65.096084655582459,-90],[-11.752662474672046,-90]]]}") + .getGeometry()); + Polygon convex_hull = (Polygon) (bounding.execute(shape, null)); + Polygon differenced = (Polygon) (difference.execute(shape, + convex_hull, SpatialReference.create(4326), null)); + assertTrue(differenced.isEmpty()); + assertTrue(bounding.isConvex(convex_hull, null)); + } + + { + Polygon polygon = new Polygon(); + polygon.startPath(0, 0); + polygon.lineTo(0, 10); + polygon.lineTo(4, 10); + polygon.lineTo(9, 1); + polygon.lineTo(1, 1); + polygon.lineTo(5, 10); + polygon.lineTo(10, 10); + polygon.lineTo(10, 0); + + Polygon densified = (Polygon) (densify.execute(polygon, 1, null)); + Polygon convex_hull = (Polygon) (bounding.execute(densified, null)); + + double area = convex_hull.calculateArea2D(); + assertTrue(bounding.isConvex(convex_hull, null)); + assertTrue(area == 100.0); + + Point2D p1 = convex_hull.getXY(0); + Point2D p2 = convex_hull.getXY(1); + Point2D p3 = convex_hull.getXY(2); + Point2D p4 = convex_hull.getXY(3); + assertTrue(p1.x == 0.0 && p1.y == 0.0); + assertTrue(p2.x == 0.0 && p2.y == 10.0); + assertTrue(p3.x == 10.0 && p3.y == 10.0); + assertTrue(p4.x == 10.0 && p4.y == 0.0); + } + + { + Polygon polygon = new Polygon(); + polygon.startPath(0, 0); + polygon.lineTo(0, 10); + polygon.lineTo(8, 10); + polygon.lineTo(10, 8); + polygon.lineTo(10, 0); + polygon.lineTo(0, 0); + polygon.lineTo(0, 10); + polygon.lineTo(10, 10); + polygon.lineTo(10, 0); + + // Polygon densified = (Polygon)(densify.execute(polygon, 1, null)); + Polygon convex_hull = (Polygon) (bounding.execute(polygon, null)); + + double area = convex_hull.calculateArea2D(); + assertTrue(bounding.isConvex(convex_hull, null)); + assertTrue(area == 100.0); + + Point2D p1 = convex_hull.getXY(0); + Point2D p2 = convex_hull.getXY(1); + Point2D p3 = convex_hull.getXY(2); + Point2D p4 = convex_hull.getXY(3); + assertTrue(p1.x == 0.0 && p1.y == 0.0); + assertTrue(p2.x == 0.0 && p2.y == 10.0); + assertTrue(p3.x == 10.0 && p3.y == 10.0); + assertTrue(p4.x == 10.0 && p4.y == 0.0); + } + } + + @Test + public static void testStar() { + OperatorConvexHull bounding = (OperatorConvexHull) OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.ConvexHull); + OperatorDensifyByLength densify = (OperatorDensifyByLength) OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.DensifyByLength); + + Polygon star = new Polygon(); + star.startPath(0, 0); + star.lineTo(0, 0); + star.lineTo(5, 10); + star.lineTo(5, 10); + star.lineTo(10, 0); + star.lineTo(10, 0); + star.startPath(0, 5); + star.lineTo(0, 5); + star.lineTo(10, 5); + star.lineTo(10, 5); + star.lineTo(5, -5); + star.lineTo(5, -5); + + star.reversePath(1); + + Polygon densified = (Polygon) (densify.execute(star, 1.0, null)); + Polygon convex_hull = (Polygon) (bounding.execute(densified, null)); + + assertTrue(bounding.isConvex(convex_hull, null)); + + Point2D p1 = convex_hull.getXY(0); + Point2D p2 = convex_hull.getXY(1); + Point2D p3 = convex_hull.getXY(2); + Point2D p4 = convex_hull.getXY(3); + Point2D p5 = convex_hull.getXY(4); + Point2D p6 = convex_hull.getXY(5); + assertTrue(p1.x == 0.0 && p1.y == 0.0); + assertTrue(p2.x == 0.0 && p2.y == 5.0); + assertTrue(p3.x == 5.0 && p3.y == 10.0); + assertTrue(p4.x == 10.0 && p4.y == 5.0); + assertTrue(p5.x == 10.0 && p5.y == 0.0); + assertTrue(p6.x == 5.0 && p6.y == -5.0); + } + + @Test + public static void testPointsArray() { + Point2D[] points = new Point2D[6]; + int[] convex_hull = new int[6]; + + for (int i = 0; i < 6; i++) + points[i] = new Point2D(); + + points[0].x = 0; + points[0].y = 0; + points[1].x = 5; + points[1].y = 10; + points[2].x = 10; + points[2].y = 0; + points[3].x = 0; + points[3].y = 5; + points[4].x = 10; + points[4].y = 5; + points[5].x = 5; + points[5].y = -5; + + ConvexHull.construct(points, 6, convex_hull); + assertTrue(convex_hull[0] == 0); + assertTrue(convex_hull[1] == 3); + assertTrue(convex_hull[2] == 1); + assertTrue(convex_hull[3] == 4); + assertTrue(convex_hull[4] == 2); + assertTrue(convex_hull[5] == 5); + } + + @Test + public static void testMergeCursor() { + OperatorConvexHull bounding = (OperatorConvexHull) OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.ConvexHull); + + Polygon geom1 = new Polygon(); + Polygon geom2 = new Polygon(); + Point geom3 = new Point(); + Line geom4 = new Line(); + Envelope geom5 = new Envelope(); + MultiPoint geom6 = new MultiPoint(); + + // polygon + geom1.startPath(0, 0); + geom1.lineTo(0, 0); + geom1.lineTo(5, 11); + geom1.lineTo(5, 11); + geom1.lineTo(10, 0); + geom1.lineTo(10, 0); + + // polygon + geom2.startPath(0, 5); + geom2.lineTo(0, 5); + geom2.lineTo(10, 5); + geom2.lineTo(10, 5); + geom2.lineTo(5, -5); + geom2.lineTo(5, -5); + + // point + geom3.setXY(15, 1.25); + + // segment + geom4.setEndXY(-5, 1.25); + geom4.setStartXY(0, 0); + + // envelope + geom5.setCoords(0, 0, 5, 10); + + // multi_point + geom6.add(10, 5); + geom6.add(10, 10); + + // create cursor + Geometry[] geoms = new Geometry[6]; + geoms[0] = geom1; + geoms[1] = geom2; + geoms[2] = geom3; + geoms[3] = geom4; + geoms[4] = geom5; + geoms[5] = geom6; + GeometryCursor cursor = new SimpleGeometryCursor(geoms); + + // create convex hull from the cursor with b_merge set to true + GeometryCursor convex_hull_curs = bounding.execute(cursor, true, null); + Polygon convex_hull = (Polygon) (convex_hull_curs.next()); + assertTrue(convex_hull_curs.next() == null); + + assertTrue(bounding.isConvex(convex_hull, null)); + + Point2D p1 = convex_hull.getXY(0); + Point2D p2 = convex_hull.getXY(1); + Point2D p3 = convex_hull.getXY(2); + Point2D p4 = convex_hull.getXY(3); + Point2D p5 = convex_hull.getXY(4); + Point2D p6 = convex_hull.getXY(5); + assertTrue(p1.x == 5.0 && p1.y == 11.0); + assertTrue(p2.x == 10.0 && p2.y == 10); + assertTrue(p3.x == 15.0 && p3.y == 1.25); + assertTrue(p4.x == 5.0 && p4.y == -5.0); + assertTrue(p5.x == -5.0 && p5.y == 1.25); + assertTrue(p6.x == 0.0 && p6.y == 10.0); + + // Test GeometryEngine + Geometry[] merged_hull = GeometryEngine.convexHull(geoms, true); + convex_hull = (Polygon) merged_hull[0]; + p1 = convex_hull.getXY(0); + p2 = convex_hull.getXY(1); + p3 = convex_hull.getXY(2); + p4 = convex_hull.getXY(3); + p5 = convex_hull.getXY(4); + p6 = convex_hull.getXY(5); + assertTrue(p1.x == 5.0 && p1.y == 11.0); + assertTrue(p2.x == 10.0 && p2.y == 10); + assertTrue(p3.x == 15.0 && p3.y == 1.25); + assertTrue(p4.x == 5.0 && p4.y == -5.0); + assertTrue(p5.x == -5.0 && p5.y == 1.25); + assertTrue(p6.x == 0.0 && p6.y == 10.0); + + } + +} diff --git a/unittest/com/esri/core/geometry/TestCut.java b/unittest/com/esri/core/geometry/TestCut.java new file mode 100644 index 00000000..8bb8f25c --- /dev/null +++ b/unittest/com/esri/core/geometry/TestCut.java @@ -0,0 +1,520 @@ +package com.esri.core.geometry; + +import junit.framework.TestCase; +import org.junit.Test; + +public class TestCut extends TestCase { + @Override + protected void setUp() throws Exception { + super.setUp(); + } + + @Override + protected void tearDown() throws Exception { + super.tearDown(); + } + + @Test + public static void testCut4326() { + SpatialReference sr = SpatialReference.create(4326); + testConsiderTouch1(sr); + testConsiderTouch2(sr); + testPolygon5(sr); + testPolygon7(sr); + testPolygon8(sr); + testPolygon9(sr); + testEngine(sr); + + } + + public static void testConsiderTouch1(SpatialReference spatialReference) { + OperatorFactoryLocal engine = OperatorFactoryLocal.getInstance(); + OperatorCut opCut = (OperatorCut) engine.getOperator(Operator.Type.Cut); + + Polyline polyline1 = makePolyline1(); + Polyline cutter1 = makePolylineCutter1(); + + GeometryCursor cursor = opCut.execute(true, polyline1, cutter1, + spatialReference, null); + Polyline cut; + int pathCount; + int segmentCount; + double length; + + cut = (Polyline) cursor.next(); + pathCount = cut.getPathCount(); + segmentCount = cut.getSegmentCount(); + length = cut.calculateLength2D(); + assertTrue(pathCount == 4); + assertTrue(segmentCount == 4); + assertTrue(length == 6); + + cut = (Polyline) cursor.next(); + pathCount = cut.getPathCount(); + segmentCount = cut.getSegmentCount(); + length = cut.calculateLength2D(); + assertTrue(pathCount == 6); + assertTrue(segmentCount == 8); + assertTrue(length == 12); + + cut = (Polyline) cursor.next(); + pathCount = cut.getPathCount(); + segmentCount = cut.getSegmentCount(); + length = cut.calculateLength2D(); + assertTrue(pathCount == 1); + assertTrue(segmentCount == 1); + assertTrue(length == 1); + + cut = (Polyline) cursor.next(); + pathCount = cut.getPathCount(); + segmentCount = cut.getSegmentCount(); + length = cut.calculateLength2D(); + assertTrue(pathCount == 1); + assertTrue(segmentCount == 1); + assertTrue(length == 1); + + cut = (Polyline) cursor.next(); + assertTrue(cut == null); + } + + public static void testConsiderTouch2(SpatialReference spatialReference) { + OperatorFactoryLocal engine = OperatorFactoryLocal.getInstance(); + OperatorCut opCut = (OperatorCut) engine.getOperator(Operator.Type.Cut); + + Polyline polyline2 = makePolyline2(); + Polyline cutter2 = makePolylineCutter2(); + + GeometryCursor cursor = opCut.execute(true, polyline2, cutter2, + spatialReference, null); + Polyline cut; + int pathCount; + int segmentCount; + double length; + + cut = (Polyline) cursor.next(); + pathCount = cut.getPathCount(); + segmentCount = cut.getSegmentCount(); + length = cut.calculateLength2D(); + assertTrue(pathCount == 4); + assertTrue(segmentCount == 4); + assertTrue(Math.abs(length - 5.74264068) <= 0.001); + + cut = (Polyline) cursor.next(); + pathCount = cut.getPathCount(); + segmentCount = cut.getSegmentCount(); + length = cut.calculateLength2D(); + assertTrue(pathCount == 6); + assertTrue(segmentCount == 8); + assertTrue(length == 6.75); + + cut = (Polyline) cursor.next(); + pathCount = cut.getPathCount(); + segmentCount = cut.getSegmentCount(); + length = cut.calculateLength2D(); + assertTrue(pathCount == 1); + assertTrue(segmentCount == 1); + assertTrue(Math.abs(length - 0.5) <= 0.001); + + cut = (Polyline) cursor.next(); + pathCount = cut.getPathCount(); + segmentCount = cut.getSegmentCount(); + length = cut.calculateLength2D(); + assertTrue(pathCount == 1); + assertTrue(segmentCount == 1); + assertTrue(Math.abs(length - 0.25) <= 0.001); + + cut = (Polyline) cursor.next(); + pathCount = cut.getPathCount(); + segmentCount = cut.getSegmentCount(); + length = cut.calculateLength2D(); + assertTrue(pathCount == 1); + assertTrue(segmentCount == 1); + assertTrue(Math.abs(length - 1) <= 0.001); + + cut = (Polyline) cursor.next(); + pathCount = cut.getPathCount(); + segmentCount = cut.getSegmentCount(); + length = cut.calculateLength2D(); + assertTrue(pathCount == 1); + assertTrue(segmentCount == 1); + assertTrue(Math.abs(length - 1.41421356) <= 0.001); + + cut = (Polyline) cursor.next(); + assertTrue(cut == null); + } + + public static void testPolygon5(SpatialReference spatialReference) { + OperatorFactoryLocal engine = OperatorFactoryLocal.getInstance(); + OperatorCut opCut = (OperatorCut) engine.getOperator(Operator.Type.Cut); + + Polygon polygon5 = makePolygon5(); + Polyline cutter5 = makePolygonCutter5(); + + GeometryCursor cursor = opCut.execute(true, polygon5, cutter5, + spatialReference, null); + Polygon cut; + int pathCount; + int pointCount; + double area; + + cut = (Polygon) cursor.next(); + pathCount = cut.getPathCount(); + pointCount = cut.getPointCount(); + area = cut.calculateArea2D(); + assertTrue(pathCount == 4); + assertTrue(pointCount == 12); + assertTrue(area == 450); + + cut = (Polygon) cursor.next(); + pathCount = cut.getPathCount(); + pointCount = cut.getPointCount(); + area = cut.calculateArea2D(); + assertTrue(pathCount == 1); + assertTrue(pointCount == 4); + assertTrue(area == 450); + + cut = (Polygon) cursor.next(); + assertTrue(cut == null); + } + + public static void testPolygon7(SpatialReference spatialReference) { + OperatorFactoryLocal engine = OperatorFactoryLocal.getInstance(); + OperatorCut opCut = (OperatorCut) engine.getOperator(Operator.Type.Cut); + + Polygon cut; + int path_count; + int point_count; + double area; + + Polygon polygon7 = makePolygon7(); + Polyline cutter7 = makePolygonCutter7(); + GeometryCursor cursor = opCut.execute(false, polygon7, cutter7, + spatialReference, null); + + cut = (Polygon) cursor.next(); + path_count = cut.getPathCount(); + point_count = cut.getPointCount(); + area = cut.calculateArea2D(); + assertTrue(path_count == 1); + assertTrue(point_count == 4); + assertTrue(area == 100); + + cut = (Polygon) cursor.next(); + assertTrue(cut.isEmpty()); + + cut = (Polygon) cursor.next(); + path_count = cut.getPathCount(); + point_count = cut.getPointCount(); + area = cut.calculateArea2D(); + assertTrue(path_count == 2); + assertTrue(point_count == 8); + assertTrue(area == 800); + + cut = (Polygon) cursor.next(); + assertTrue(cut == null); + } + + public static void testPolygon8(SpatialReference spatialReference) { + OperatorFactoryLocal engine = OperatorFactoryLocal.getInstance(); + OperatorCut opCut = (OperatorCut) engine.getOperator(Operator.Type.Cut); + + Polygon polygon8 = makePolygon8(); + Polyline cutter8 = makePolygonCutter8(); + + GeometryCursor cursor = opCut.execute(true, polygon8, cutter8, + spatialReference, null); + Polygon cut; + int pathCount; + int pointCount; + double area; + + cut = (Polygon) cursor.next(); + assertTrue(cut.isEmpty()); + + cut = (Polygon) cursor.next(); + pathCount = cut.getPathCount(); + pointCount = cut.getPointCount(); + area = cut.calculateArea2D(); + assertTrue(pathCount == 1); + assertTrue(pointCount == 4); + assertTrue(area == 100); + + cut = (Polygon) cursor.next(); + pathCount = cut.getPathCount(); + pointCount = cut.getPointCount(); + area = cut.calculateArea2D(); + assertTrue(pathCount == 2); + assertTrue(pointCount == 8); + assertTrue(area == 800); + + cut = (Polygon) cursor.next(); + assertTrue(cut == null); + } + + public static void testPolygon9(SpatialReference spatialReference) { + OperatorFactoryLocal engine = OperatorFactoryLocal.getInstance(); + OperatorCut opCut = (OperatorCut) engine.getOperator(Operator.Type.Cut); + + Polygon cut; + int path_count; + int point_count; + double area; + + Polygon polygon9 = makePolygon9(); + Polyline cutter9 = makePolygonCutter9(); + GeometryCursor cursor = opCut.execute(false, polygon9, cutter9, + spatialReference, null); + + cut = (Polygon) cursor.next(); + path_count = cut.getPathCount(); + point_count = cut.getPointCount(); + area = cut.calculateArea2D(); + assertTrue(path_count == 3); + assertTrue(point_count == 12); + assertTrue(area == 150); + + cut = (Polygon) cursor.next(); + path_count = cut.getPathCount(); + point_count = cut.getPointCount(); + area = cut.calculateArea2D(); + assertTrue(path_count == 3); + assertTrue(point_count == 12); + assertTrue(area == 150); + + cut = (Polygon) cursor.next(); + assertTrue(cut == null); + } + + public static void testEngine(SpatialReference spatialReference) { + Polygon polygon8 = makePolygon8(); + Polyline cutter8 = makePolygonCutter8(); + + Geometry[] cuts = GeometryEngine.cut(polygon8, cutter8, + spatialReference); + Polygon cut; + int pathCount; + int pointCount; + double area; + + cut = (Polygon) cuts[0]; + pathCount = cut.getPathCount(); + pointCount = cut.getPointCount(); + area = cut.calculateArea2D(); + assertTrue(pathCount == 1); + assertTrue(pointCount == 4); + assertTrue(area == 100); + + cut = (Polygon) cuts[1]; + pathCount = cut.getPathCount(); + pointCount = cut.getPointCount(); + area = cut.calculateArea2D(); + assertTrue(pathCount == 2); + assertTrue(pointCount == 8); + assertTrue(area == 800); + } + + public static Polyline makePolyline1() { + Polyline poly = new Polyline(); + + poly.startPath(0, 0); + poly.lineTo(2, 0); + poly.lineTo(4, 0); + poly.lineTo(6, 0); + poly.lineTo(8, 0); + poly.lineTo(10, 0); + poly.lineTo(12, 0); + poly.lineTo(14, 0); + poly.lineTo(16, 0); + poly.lineTo(18, 0); + poly.lineTo(20, 0); + + return poly; + } + + public static Polyline makePolylineCutter1() { + Polyline poly = new Polyline(); + + poly.startPath(1, 0); + poly.lineTo(4, 0); + + poly.startPath(6, -1); + poly.lineTo(6, 1); + + poly.startPath(6, 0); + poly.lineTo(8, 0); + + poly.startPath(9, -1); + poly.lineTo(9, 1); + + poly.startPath(10, 0); + poly.lineTo(12, 0); + + poly.startPath(12, 1); + poly.lineTo(12, -1); + + poly.startPath(12, 0); + poly.lineTo(15, 0); + + poly.startPath(15, 1); + poly.lineTo(15, -1); + + poly.startPath(16, 0); + poly.lineTo(16, -1); + poly.lineTo(17, -1); + poly.lineTo(17, 1); + poly.lineTo(17, 0); + poly.lineTo(18, 0); + + poly.startPath(18, 0); + poly.lineTo(18, -1); + + return poly; + } + + public static Polyline makePolyline2() { + Polyline poly = new Polyline(); + + poly.startPath(-2, 0); + poly.lineTo(-1, 0); + poly.lineTo(0, 0); + poly.lineTo(2, 0); + poly.lineTo(4, 2); + poly.lineTo(8, 2); + poly.lineTo(10, 4); + poly.lineTo(12, 4); + + return poly; + } + + public static Polyline makePolylineCutter2() { + Polyline poly = new Polyline(); + + poly.startPath(-1.5, 0); + poly.lineTo(-.75, 0); + + poly.startPath(-.5, 0); + poly.lineTo(1, 0); + poly.lineTo(1, 2); + poly.lineTo(3, -2); + poly.lineTo(4, 2); + poly.lineTo(5, -2); + poly.lineTo(5, 4); + poly.lineTo(8, 2); + poly.lineTo(6, 0); + poly.lineTo(6, 3); + + poly.startPath(9, 5); + poly.lineTo(9, 2); + poly.lineTo(10, 2); + poly.lineTo(10, 5); + poly.lineTo(10.5, 5); + poly.lineTo(10.5, 3); + + poly.startPath(11, 4); + poly.lineTo(11, 5); + + poly.startPath(12, 5); + poly.lineTo(12, 4); + + return poly; + } + + public static Polygon makePolygon5() { + Polygon poly = new Polygon(); + + poly.startPath(0, 0); + poly.lineTo(0, 30); + poly.lineTo(30, 30); + poly.lineTo(30, 0); + + return poly; + } + + public static Polyline makePolygonCutter5() { + Polyline poly = new Polyline(); + + poly.startPath(15, 0); + poly.lineTo(0, 15); + poly.lineTo(15, 30); + poly.lineTo(30, 15); + poly.lineTo(15, 0); + + return poly; + } + + public static Polygon makePolygon7() { + Polygon poly = new Polygon(); + + poly.startPath(0, 0); + poly.lineTo(0, 30); + poly.lineTo(30, 30); + poly.lineTo(30, 0); + + return poly; + } + + public static Polyline makePolygonCutter7() { + Polyline poly = new Polyline(); + + poly.startPath(10, 10); + poly.lineTo(20, 10); + poly.lineTo(20, 20); + poly.lineTo(10, 20); + poly.lineTo(10, 10); + + return poly; + } + + public static Polygon makePolygon8() { + Polygon poly = new Polygon(); + + poly.startPath(0, 0); + poly.lineTo(0, 30); + poly.lineTo(30, 30); + poly.lineTo(30, 0); + + return poly; + } + + public static Polyline makePolygonCutter8() { + Polyline poly = new Polyline(); + + poly.startPath(10, 10); + poly.lineTo(10, 20); + poly.lineTo(20, 20); + poly.lineTo(20, 10); + poly.lineTo(10, 10); + + return poly; + } + + public static Polygon makePolygon9() { + Polygon poly = new Polygon(); + + poly.startPath(0, 0); + poly.lineTo(0, 10); + poly.lineTo(10, 10); + poly.lineTo(10, 0); + + poly.startPath(0, 20); + poly.lineTo(0, 30); + poly.lineTo(10, 30); + poly.lineTo(10, 20); + + poly.startPath(0, 40); + poly.lineTo(0, 50); + poly.lineTo(10, 50); + poly.lineTo(10, 40); + + return poly; + } + + public static Polyline makePolygonCutter9() { + Polyline poly = new Polyline(); + + poly.startPath(5, -1); + poly.lineTo(5, 51); + + return poly; + } +} diff --git a/unittest/com/esri/core/geometry/TestDifference.java b/unittest/com/esri/core/geometry/TestDifference.java new file mode 100644 index 00000000..ca7bc3d4 --- /dev/null +++ b/unittest/com/esri/core/geometry/TestDifference.java @@ -0,0 +1,650 @@ +package com.esri.core.geometry; + +import junit.framework.TestCase; +import org.junit.Test; + +public class TestDifference extends TestCase { + @Override + protected void setUp() throws Exception { + super.setUp(); + } + + @Override + protected void tearDown() throws Exception { + super.tearDown(); + } + + @Test + public static void testDifferenceAndSymmetricDifference() { + OperatorFactoryLocal engine = OperatorFactoryLocal.getInstance(); + OperatorDifference differenceOp = (OperatorDifference) engine + .getOperator(Operator.Type.Difference); + + SpatialReference spatialRef = SpatialReference.create(102113); + Polygon polygon1 = makePolygon1(); + Polygon polygon2 = makePolygon2(); + Polyline polyline1 = makePolyline1(); + MultiPoint multipoint1 = makeMultiPoint1(); + MultiPoint multipoint2 = makeMultiPoint2(); + MultiPoint multipoint3 = makeMultiPoint3(); + Point point1 = makePoint1(); + Point point2 = makePoint2(); + Envelope envelope1 = makeEnvelope1(); + Envelope envelope2 = makeEnvelope2(); + Envelope envelope3 = makeEnvelope3(); + + Polygon outputPolygon = (Polygon) differenceOp.execute(polygon1, + polygon2, spatialRef, null); + double area = outputPolygon.calculateArea2D(); + assertTrue(Math.abs(area - 75) <= 0.001); + + { + Point point_1 = new Point(-130, 10); + Point point_2 = new Point(-130, 10); + Geometry baseGeom = new Point(point_1.getX(), point_1.getY()); + Geometry comparisonGeom = new Point(point_2.getX(), point2.getY()); + SpatialReference sr = SpatialReference.create(4326); + @SuppressWarnings("unused") + Geometry geom = differenceOp.execute(baseGeom, comparisonGeom, sr, + null); + } + + OperatorSymmetricDifference symDifferenceOp = (OperatorSymmetricDifference) engine + .getOperator(Operator.Type.SymmetricDifference); + outputPolygon = (Polygon) symDifferenceOp.execute(polygon1, polygon2, + spatialRef, null); + + area = outputPolygon.calculateArea2D(); + assertTrue(Math.abs(area - 150) <= 0.001); + + Polyline outputPolyline = (Polyline) differenceOp.execute(polyline1, + polygon1, spatialRef, null); + double length = outputPolyline.calculateLength2D(); + assertTrue(Math.abs(length * length - 50) < 0.001); + + MultiPoint outputMultiPoint = (MultiPoint) differenceOp.execute( + multipoint1, polygon1, spatialRef, null); + int pointCount = outputMultiPoint.getPointCount(); + assertTrue(pointCount == 1); + + outputMultiPoint = (MultiPoint) (symDifferenceOp.execute(multipoint1, + point1, spatialRef, null)); + pointCount = outputMultiPoint.getPointCount(); + assertTrue(pointCount == 2); + + outputMultiPoint = (MultiPoint) (symDifferenceOp.execute(multipoint1, + point2, spatialRef, null)); + pointCount = outputMultiPoint.getPointCount(); + assertTrue(pointCount == 4); + + outputMultiPoint = (MultiPoint) (differenceOp.execute(multipoint1, + point1, spatialRef, null)); + pointCount = outputMultiPoint.getPointCount(); + assertTrue(pointCount == 2); + + outputMultiPoint = (MultiPoint) (differenceOp.execute(multipoint1, + point2, spatialRef, null)); + pointCount = outputMultiPoint.getPointCount(); + assertTrue(pointCount == 3); + + outputPolygon = (Polygon) (differenceOp.execute(polygon1, envelope1, + spatialRef, null)); + area = outputPolygon.calculateArea2D(); + assertTrue(Math.abs(area - 75) <= 0.001); + + outputPolygon = (Polygon) (differenceOp.execute(polygon2, envelope2, + spatialRef, null)); + area = outputPolygon.calculateArea2D(); + assertTrue(Math.abs(area - 75) <= 0.001); + + outputPolyline = (Polyline) (differenceOp.execute(polyline1, envelope2, + spatialRef, null)); + length = outputPolyline.calculateLength2D(); + assertTrue(Math.abs(length * length - 50) <= 0.001); + + outputMultiPoint = (MultiPoint) (differenceOp.execute(multipoint1, + envelope2, spatialRef, null)); + pointCount = outputMultiPoint.getPointCount(); + assertTrue(pointCount == 1); + + outputMultiPoint = (MultiPoint) (differenceOp.execute(multipoint2, + envelope2, spatialRef, null)); + pointCount = outputMultiPoint.getPointCount(); + assertTrue(pointCount == 6); + + outputMultiPoint = (MultiPoint) (differenceOp.execute(multipoint3, + envelope2, spatialRef, null)); + pointCount = outputMultiPoint.getPointCount(); + assertTrue(pointCount == 0); + + Point outputPoint = (Point) (differenceOp.execute(point1, envelope2, + spatialRef, null)); + assertTrue(!outputPoint.isEmpty()); + + outputPoint = (Point) (differenceOp.execute(point2, envelope2, + spatialRef, null)); + assertTrue(outputPoint.isEmpty()); + + outputPolygon = (Polygon) (differenceOp.execute(envelope3, envelope2, + spatialRef, null)); + assertTrue(outputPolygon != null && outputPolygon.isEmpty()); + + outputPolygon = (Polygon) (symDifferenceOp.execute(envelope3, + envelope3, spatialRef, null)); + assertTrue(outputPolygon != null && outputPolygon.isEmpty()); + + outputPoint = (Point) (differenceOp.execute(point1, polygon1, + spatialRef, null)); + assertTrue(outputPoint != null); + } + + @Test + public static void testPointTypes() { + OperatorFactoryLocal engine = OperatorFactoryLocal.getInstance(); + OperatorDifference difference = (OperatorDifference) engine + .getOperator(Operator.Type.Difference); + OperatorSymmetricDifference sym_difference = (OperatorSymmetricDifference) engine + .getOperator(Operator.Type.SymmetricDifference); + + {// point/point + Point point_1 = new Point(); + Point point_2 = new Point(); + point_1.setXY(0, 0); + point_2.setXY(0.000000009, 0.000000009); + Point differenced = (Point) (difference.execute(point_1, point_2, + SpatialReference.create(4326), null)); + assertTrue(differenced.isEmpty()); + + MultiPoint sym_differenced = (MultiPoint) (sym_difference.execute( + point_1, point_2, SpatialReference.create(4326), null)); + assertTrue(sym_differenced.isEmpty()); + } + + {// point/point + Point point_1 = new Point(); + Point point_2 = new Point(); + point_1.setXY(0, 0); + point_2.setXY(0.000000009, 0.0); + Point differenced = (Point) (difference.execute(point_1, point_2, + SpatialReference.create(4326), null)); + assertTrue(differenced.isEmpty()); + + MultiPoint sym_differenced = (MultiPoint) (sym_difference.execute( + point_1, point_2, SpatialReference.create(4326), null)); + assertTrue(sym_differenced.isEmpty()); + } + + {// point/point + Point point_1 = new Point(); + Point point_2 = new Point(); + point_1.setXY(0, 0); + point_2.setXY(0.00000002, 0.00000002); + Point differenced_1 = (Point) (difference.execute(point_1, point_2, + SpatialReference.create(4326), null)); + assertTrue(!differenced_1.isEmpty()); + + Point differenced_2 = (Point) (difference.execute(point_2, point_1, + SpatialReference.create(4326), null)); + assertTrue(!differenced_2.isEmpty()); + + MultiPoint sym_differenced = (MultiPoint) (sym_difference.execute( + point_1, point_2, SpatialReference.create(4326), null)); + assertTrue(!sym_differenced.isEmpty()); + assertTrue(sym_differenced.getXY(0).x == 0 + && sym_differenced.getXY(0).y == 0); + assertTrue(sym_differenced.getXY(1).x == 0.00000002 + && sym_differenced.getXY(1).y == 0.00000002); + } + + {// multi_point/point + MultiPoint multi_point_1 = new MultiPoint(); + Point point_2 = new Point(); + multi_point_1.add(0, 0); + multi_point_1.add(1, 1); + point_2.setXY(0.000000009, 0.000000009); + MultiPoint differenced_1 = (MultiPoint) (difference + .execute(multi_point_1, point_2, + SpatialReference.create(4326), null)); + assertTrue(!differenced_1.isEmpty()); + assertTrue(differenced_1.getPointCount() == 1); + assertTrue(differenced_1.getXY(0).x == 1 + && differenced_1.getXY(0).y == 1); + + Point differenced_2 = (Point) (difference.execute(point_2, + multi_point_1, SpatialReference.create(4326), null)); + assertTrue(differenced_2.isEmpty()); + } + + {// multi_point/point + MultiPoint multi_point_1 = new MultiPoint(); + Point point_2 = new Point(); + multi_point_1.add(0, 0); + multi_point_1.add(1, 1); + point_2.setXY(0.000000009, 0.0); + MultiPoint differenced_1 = (MultiPoint) (difference + .execute(multi_point_1, point_2, + SpatialReference.create(4326), null)); + assertTrue(!differenced_1.isEmpty()); + assertTrue(differenced_1.getXY(0).x == 1.0 + && differenced_1.getXY(0).y == 1.0); + + Point differenced_2 = (Point) (difference.execute(point_2, + multi_point_1, SpatialReference.create(4326), null)); + assertTrue(differenced_2.isEmpty()); + + MultiPoint sym_differenced = (MultiPoint) (sym_difference + .execute(multi_point_1, point_2, + SpatialReference.create(4326), null)); + assertTrue(!sym_differenced.isEmpty()); + assertTrue(sym_differenced.getPointCount() == 1); + assertTrue(sym_differenced.getXY(0).x == 1 + && sym_differenced.getXY(0).y == 1); + } + + {// multi_point/point + MultiPoint multi_point_1 = new MultiPoint(); + Point point_2 = new Point(); + multi_point_1.add(0, 0); + multi_point_1.add(0, 0); + point_2.setXY(0.000000009, 0.0); + MultiPoint differenced_1 = (MultiPoint) (difference + .execute(multi_point_1, point_2, + SpatialReference.create(4326), null)); + assertTrue(differenced_1.isEmpty()); + + MultiPoint sym_differenced = (MultiPoint) (sym_difference + .execute(multi_point_1, point_2, + SpatialReference.create(4326), null)); + assertTrue(sym_differenced.isEmpty()); + } + + {// multi_point/polygon + MultiPoint multi_point_1 = new MultiPoint(); + Polygon polygon_2 = new Polygon(); + multi_point_1.add(0, 0); + multi_point_1.add(0, 0); + multi_point_1.add(2, 2); + + polygon_2.startPath(-1, -1); + polygon_2.lineTo(-1, 1); + polygon_2.lineTo(1, 1); + polygon_2.lineTo(1, -1); + MultiPoint differenced_1 = (MultiPoint) (difference.execute( + multi_point_1, polygon_2, SpatialReference.create(4326), + null)); + assertTrue(!differenced_1.isEmpty()); + assertTrue(differenced_1.getPointCount() == 1); + assertTrue(differenced_1.getXY(0).x == 2 + && differenced_1.getXY(0).y == 2); + } + + {// multi_point/polygon + MultiPoint multi_point_1 = new MultiPoint(); + Polygon polygon_2 = new Polygon(); + multi_point_1.add(0, 0); + multi_point_1.add(0, 0); + multi_point_1.add(1, 1); + + polygon_2.startPath(-1, -1); + polygon_2.lineTo(-1, 1); + polygon_2.lineTo(1, 1); + polygon_2.lineTo(1, -1); + MultiPoint differenced_1 = (MultiPoint) (difference.execute( + multi_point_1, polygon_2, SpatialReference.create(4326), + null)); + assertTrue(differenced_1.isEmpty()); + } + + {// multi_point/envelope + MultiPoint multi_point_1 = new MultiPoint(); + Envelope envelope_2 = new Envelope(); + multi_point_1.add(-2, 0); + multi_point_1.add(0, 2); + multi_point_1.add(2, 0); + multi_point_1.add(0, -2); + + envelope_2.setCoords(-1, -1, 1, 1); + MultiPoint differenced_1 = (MultiPoint) (difference.execute( + multi_point_1, envelope_2, SpatialReference.create(4326), + null)); + assertTrue(!differenced_1.isEmpty() + && differenced_1 == multi_point_1); + } + + {// multi_point/polygon + MultiPoint multi_point_1 = new MultiPoint(); + Polygon polygon_2 = new Polygon(); + multi_point_1.add(2, 2); + multi_point_1.add(2, 2); + multi_point_1.add(-2, -2); + + polygon_2.startPath(-1, -1); + polygon_2.lineTo(-1, 1); + polygon_2.lineTo(1, 1); + polygon_2.lineTo(1, -1); + MultiPoint differenced_1 = (MultiPoint) (difference.execute( + multi_point_1, polygon_2, SpatialReference.create(4326), + null)); + assertTrue(!differenced_1.isEmpty() + && differenced_1 == multi_point_1); + } + + {// point/polygon + Point point_1 = new Point(); + Polygon polygon_2 = new Polygon(); + point_1.setXY(0, 0); + + polygon_2.startPath(-1, -1); + polygon_2.lineTo(-1, 1); + polygon_2.lineTo(1, 1); + polygon_2.lineTo(1, -1); + Point differenced_1 = (Point) (difference.execute(point_1, + polygon_2, SpatialReference.create(4326), null)); + assertTrue(differenced_1.isEmpty()); + + polygon_2.setEmpty(); + polygon_2.startPath(1, 1); + polygon_2.lineTo(1, 2); + polygon_2.lineTo(2, 2); + polygon_2.lineTo(2, 1); + differenced_1 = (Point) (difference.execute(point_1, polygon_2, + SpatialReference.create(4326), null)); + assertTrue(!differenced_1.isEmpty()); + assertTrue(differenced_1 == point_1); + } + + {// point/polygon + Point point_1 = new Point(); + Polygon polygon_2 = new Polygon(); + point_1.setXY(0, 0); + + polygon_2.startPath(1, 0); + polygon_2.lineTo(0, 1); + polygon_2.lineTo(1, 1); + Point differenced_1 = (Point) (difference.execute(point_1, + polygon_2, SpatialReference.create(4326), null)); + assertTrue(!differenced_1.isEmpty()); + assertTrue(differenced_1 == point_1); + + point_1.setEmpty(); + point_1.setXY(0.5, 0.5); + + polygon_2.setEmpty(); + polygon_2.startPath(1, 0); + polygon_2.lineTo(0, 1); + polygon_2.lineTo(1, 1); + differenced_1 = (Point) (difference.execute(point_1, polygon_2, + SpatialReference.create(4326), null)); + assertTrue(differenced_1.isEmpty()); + } + + {// point/envelope + Point point_1 = new Point(); + Envelope envelope_2 = new Envelope(); + point_1.setXY(0, 0); + + envelope_2.setCoords(-1, -1, 1, 1); + Point differenced_1 = (Point) (difference.execute(point_1, + envelope_2, SpatialReference.create(4326), null)); + assertTrue(differenced_1.isEmpty()); + + envelope_2.setEmpty(); + envelope_2.setCoords(1, 1, 2, 2); + differenced_1 = (Point) (difference.execute(point_1, envelope_2, + SpatialReference.create(4326), null)); + assertTrue(!differenced_1.isEmpty()); + assertTrue(differenced_1 == point_1); + } + + {// point/polyline + Point point_1 = new Point(); + Polyline polyline_2 = new Polyline(); + point_1.setXY(0, 0); + + polyline_2.startPath(-1, 0); + polyline_2.lineTo(1, 0); + Point differenced_1 = (Point) (difference.execute(point_1, + polyline_2, SpatialReference.create(4326), null)); + assertTrue(differenced_1.isEmpty()); + + polyline_2.setEmpty(); + polyline_2.startPath(1, 0); + polyline_2.lineTo(2, 0); + differenced_1 = (Point) (difference.execute(point_1, polyline_2, + SpatialReference.create(4326), null)); + assertTrue(!differenced_1.isEmpty()); + assertTrue(differenced_1 == point_1); + + polyline_2.setEmpty(); + polyline_2.startPath(-1, -1); + polyline_2.lineTo(-1, 1); + polyline_2.lineTo(1, 1); + polyline_2.lineTo(1, -1); + differenced_1 = (Point) (difference.execute(point_1, polyline_2, + SpatialReference.create(4326), null)); + assertTrue(!differenced_1.isEmpty()); + assertTrue(differenced_1 == point_1); + } + } + + @Test + public static void testDifferenceOnPolyline() { + // # * * # + // # * @ + // # @ * + // # * + // + // /////////////////////////////// + // + // The polyline drawn in *s represents basePl + // The polyline drawn in #s represents compPl + // The @ represents their intersection points, so that + // the difference polyline will be basePl with two new vertices @ added. + + Polyline basePl = new Polyline(); + basePl.startPath(new Point(-117, 20)); + basePl.lineTo(new Point(-130, 10)); + basePl.lineTo(new Point(-120, 50)); + + Polyline compPl = new Polyline(); + compPl.startPath(new Point(-116, 20)); + compPl.lineTo(new Point(-131, 10)); + compPl.lineTo(new Point(-121, 50)); + + Geometry diffGeom = GeometryEngine.difference(basePl, compPl, + SpatialReference.create(4326)); + assertTrue(diffGeom instanceof Polyline); + Polyline diffPolyline = (Polyline) diffGeom; + int pointCountDiffPolyline = diffPolyline.getPointCount(); + + // first line in comp_pl is 3y = 2x + 292 + assertEquals(3 * 20, 2 * (-116) + 292); + assertEquals(3 * 10, 2 * (-131) + 292); + + // new points should also lie on this line + assertTrue(3.0 * diffPolyline.getCoordinates2D()[1].y - 2.0 + * diffPolyline.getCoordinates2D()[1].x - 292.0 == 0.0); + assertTrue(3.0 * diffPolyline.getCoordinates2D()[3].y - 2.0 + * diffPolyline.getCoordinates2D()[3].x - 292.0 == 0.0); + + for (int i = 0; i < 3; i++) { + assertTrue(basePl.getCoordinates2D()[i].x == diffPolyline + .getCoordinates2D()[2 * i].x); + assertTrue(basePl.getCoordinates2D()[i].y == diffPolyline + .getCoordinates2D()[2 * i].y); + } + + assertEquals(5, pointCountDiffPolyline); + } + + public static Polygon makePolygon1() { + Polygon poly = new Polygon(); + poly.startPath(0, 0); + poly.lineTo(0, 10); + poly.lineTo(10, 10); + poly.lineTo(10, 0); + + return poly; + } + + public static Polygon makePolygon2() { + Polygon poly = new Polygon(); + + poly.startPath(5, 5); + poly.lineTo(5, 15); + poly.lineTo(15, 15); + poly.lineTo(15, 5); + + return poly; + } + + public static Polyline makePolyline1() { + Polyline poly = new Polyline(); + poly.startPath(0, 0); + poly.lineTo(15, 15); + + return poly; + } + + public static MultiPoint makeMultiPoint1() { + MultiPoint mpoint = new MultiPoint(); + Point2D pt1 = new Point2D(); + pt1.x = 1.0; + pt1.y = 1.0; + + Point2D pt2 = new Point2D(); + pt2.x = 5.0; + pt2.y = 5.0; + + Point2D pt3 = new Point2D(); + pt3.x = 15.0; + pt3.y = 15.0; + + mpoint.add(pt1.x, pt1.y); + mpoint.add(pt2.x, pt2.y); + mpoint.add(pt3.x, pt3.y); + + return mpoint; + } + + public static MultiPoint makeMultiPoint2() { + MultiPoint mpoint = new MultiPoint(); + Point2D pt1 = new Point2D(); + pt1.x = 1.0; + pt1.y = 1.0; + + Point2D pt2 = new Point2D(); + pt2.x = 1.0; + pt2.y = 1.0; + + Point2D pt3 = new Point2D(); + pt3.x = 15.0; + pt3.y = 15.0; + + Point2D pt4 = new Point2D(); + pt4.x = 15.0; + pt4.y = 15.0; + + Point2D pt5 = new Point2D(); + pt5.x = 1.0; + pt5.y = 1.0; + + Point2D pt6 = new Point2D(); + pt6.x = 1.0; + pt6.y = 1.0; + + Point2D pt7 = new Point2D(); + pt7.x = 15.0; + pt7.y = 15.0; + + Point2D pt8 = new Point2D(); + pt8.x = 15.0; + pt8.y = 15.0; + + Point2D pt9 = new Point2D(); + pt9.x = 15.0; + pt9.y = 15.0; + + Point2D pt10 = new Point2D(); + pt10.x = 1.0; + pt10.y = 1.0; + + Point2D pt11 = new Point2D(); + pt11.x = 15.0; + pt11.y = 15.0; + + mpoint.add(pt1.x, pt1.y); + mpoint.add(pt2.x, pt2.y); + mpoint.add(pt3.x, pt3.y); + mpoint.add(pt4.x, pt4.y); + mpoint.add(pt5.x, pt5.y); + mpoint.add(pt6.x, pt6.y); + mpoint.add(pt7.x, pt7.y); + mpoint.add(pt8.x, pt8.y); + mpoint.add(pt9.x, pt9.y); + mpoint.add(pt10.x, pt10.y); + mpoint.add(pt11.x, pt11.y); + + return mpoint; + } + + public static MultiPoint makeMultiPoint3() { + MultiPoint mpoint = new MultiPoint(); + Point2D pt1 = new Point2D(); + pt1.x = 1.0; + pt1.y = 1.0; + + Point2D pt2 = new Point2D(); + pt2.x = 5.0; + pt2.y = 5.0; + + mpoint.add(pt1.x, pt1.y); + mpoint.add(pt2.x, pt2.y); + + return mpoint; + } + + public static Point makePoint1() { + Point point = new Point(); + + Point2D pt = new Point2D(); + pt.setCoords(15, 15); + point.setXY(pt); + + return point; + } + + public static Point makePoint2() { + Point point = new Point(); + + Point2D pt = new Point2D(); + pt.setCoords(7, 7); + point.setXY(pt); + + return point; + } + + public static Envelope makeEnvelope1() { + Envelope2D env = new Envelope2D(); + env.setCoords(5, 5, 15, 15); + Envelope envelope = new Envelope(env); + + return envelope; + } + + public static Envelope makeEnvelope2() { + Envelope2D env = new Envelope2D(); + env.setCoords(0, 0, 10, 10); + Envelope envelope = new Envelope(env); + + return envelope; + } + + public static Envelope makeEnvelope3() { + Envelope2D env = new Envelope2D(); + env.setCoords(5, 5, 6, 6); + Envelope envelope = new Envelope(env); + + return envelope; + } +} diff --git a/unittest/com/esri/core/geometry/TestDistance.java b/unittest/com/esri/core/geometry/TestDistance.java new file mode 100644 index 00000000..9fc159d2 --- /dev/null +++ b/unittest/com/esri/core/geometry/TestDistance.java @@ -0,0 +1,156 @@ +package com.esri.core.geometry; + +import java.io.IOException; +import junit.framework.TestCase; +import org.codehaus.jackson.JsonFactory; +import org.codehaus.jackson.JsonParseException; +import org.codehaus.jackson.JsonParser; +import org.junit.Test; + +public class TestDistance extends TestCase { + @Override + protected void setUp() throws Exception { + super.setUp(); + } + + @Override + protected void tearDown() throws Exception { + super.tearDown(); + } + + @Test + public static void testDistanceBetweenVariousGeometries() { + Polygon polygon = makePolygon(); + Polyline polyline = makePolyline(); + MultiPoint multipoint = makeMultiPoint(); + Point point = makePoint(); + // SpatialReference spatialRef = + // SpatialReference.create(3857);//PCS_WGS_1984_WEB_MERCATOR_AUXSPHERE + + double distance; + + distance = GeometryEngine.distance(polygon, polyline, null); + assertTrue(Math.abs(distance - 5.0) < 0.00001); + + distance = GeometryEngine.distance(polygon, multipoint, null); + assertTrue(Math.abs(distance - 5.0) < 0.00001); + + distance = GeometryEngine.distance(polygon, point, null); + assertTrue(Math.abs(distance - 5.0) < 0.00001); + } + + @Test + public static void testDistanceBetweenTriangles() { + double distance; + + Polygon poly = new Polygon(); + Polygon poly2 = new Polygon(); + + poly.startPath(0.0, 0.0); + poly.lineTo(1.0, 2.0); + poly.lineTo(0.0, 2.0); + + double xSeparation = 0.1; + double ySeparation = 0.1; + + poly2.startPath(xSeparation + 1.0, 2.0 - ySeparation); + poly2.lineTo(xSeparation + 2.0, 2.0 - ySeparation); + poly2.lineTo(xSeparation + 2.0, 4.0 - ySeparation); + + distance = GeometryEngine.distance(poly, poly2, null); + + assertTrue(0.0 < distance && distance < xSeparation + ySeparation); + } + + @Test + public static void testDistanceBetweenHugeGeometries() { + /* const */int N = 1000; // Should be even + /* const */double theoreticalDistance = 0.77; + + Polygon poly = new Polygon(); + Polygon poly2 = new Polygon(); + + double theta = 0.0; + double thetaPlusPi = Math.PI; + double dTheta = 2.0 * Math.PI / N; + double distance; + + poly.startPath(Math.cos(theta), Math.sin(theta)); + // Add something so that poly2's bounding box is in poly's. Deleting + // this should not affect answer. + poly.lineTo(1.0, 1.5 + theoreticalDistance); + poly.lineTo(3.5 + theoreticalDistance, 1.5 + theoreticalDistance); + poly.lineTo(3.5 + theoreticalDistance, 2.0 + theoreticalDistance); + poly.lineTo(0.95, 2.0 + theoreticalDistance); + // /////////////////////////////////////////////////////////// + poly2.startPath(2.0 + theoreticalDistance + Math.cos(thetaPlusPi), + Math.sin(thetaPlusPi)); + for (double i = 1; i < N; i++) { + theta += dTheta; + thetaPlusPi += dTheta; + poly.lineTo(Math.cos(theta), Math.sin(theta)); + poly2.lineTo(2.0 + theoreticalDistance + Math.cos(thetaPlusPi), + Math.sin(thetaPlusPi)); + } + + distance = GeometryEngine.distance(poly, poly2, null); + + assertTrue(Math.abs(distance - theoreticalDistance) < 1.0e-10); + } + + private static Polygon makePolygon() { + Polygon poly = new Polygon(); + + poly.startPath(0, 0); + poly.lineTo(0, 10); + poly.lineTo(10, 10); + poly.lineTo(10, 0); + + poly.startPath(3, 3); + poly.lineTo(7, 3); + poly.lineTo(7, 7); + poly.lineTo(3, 7); + + return poly; + } + + private static Polyline makePolyline() { + Polyline poly = new Polyline(); + poly.startPath(0, 15); + poly.lineTo(15, 15); + return poly; + } + + private static MultiPoint makeMultiPoint() { + MultiPoint mpoint = new MultiPoint(); + mpoint.add(0, 30); + mpoint.add(15, 15); + mpoint.add(0, 15); + return mpoint; + } + + private static Point makePoint() { + Point point = new Point(); + Point2D pt = new Point2D(); + pt.setCoords(0, 15); + point.setXY(pt); + return point; + } + + @Test + public static void testDistanceWithNullSpatialReference() + throws JsonParseException, IOException { + // There was a bug that distance op did not work with null Spatial + // Reference. + String str1 = "{\"paths\":[[[-117.138791850991,34.017492675023],[-117.138762336971,34.0174925550462]]]}"; + String str2 = "{\"paths\":[[[-117.138867827972,34.0174854109623],[-117.138850197027,34.0174929160126],[-117.138791850991,34.017492675023]]]}"; + JsonFactory jsonFactory = new JsonFactory(); + JsonParser jsonParser1 = jsonFactory.createJsonParser(str1); + JsonParser jsonParser2 = jsonFactory.createJsonParser(str2); + MapGeometry geom1 = GeometryEngine.jsonToGeometry(jsonParser1); + MapGeometry geom2 = GeometryEngine.jsonToGeometry(jsonParser2); + double distance = GeometryEngine.distance(geom1.getGeometry(), + geom2.getGeometry(), null); + assertTrue(distance == 0); + } +} diff --git a/unittest/com/esri/core/geometry/TestEditShape.java b/unittest/com/esri/core/geometry/TestEditShape.java new file mode 100644 index 00000000..94bd5fb2 --- /dev/null +++ b/unittest/com/esri/core/geometry/TestEditShape.java @@ -0,0 +1,397 @@ +package com.esri.core.geometry; + +import junit.framework.TestCase; +import org.junit.Test; + +public class TestEditShape extends TestCase { + @Override + protected void setUp() throws Exception { + super.setUp(); + } + + @Override + protected void tearDown() throws Exception { + super.tearDown(); + } + + @Test + public static void testEditShape() { + { + // std::shared_ptr poly_base_6 + // = std::make_shared(); + // Single part polygon + Polygon poly = new Polygon(); + poly.startPath(10, 10); + poly.lineTo(10, 12); + poly.lineTo(14, 15); + poly.lineTo(10, 11); + EditShape editShape = new EditShape(); + int geom = editShape.addGeometry(poly); + Polygon poly2 = (Polygon) editShape.getGeometry(geom); + assertTrue(poly.equals(poly2)); + } + + { + // Two part poly + Polygon poly = new Polygon(); + poly.startPath(10, 10); + poly.lineTo(10, 12); + poly.lineTo(14, 15); + poly.lineTo(10, 11); + + poly.startPath(100, 10); + poly.lineTo(100, 12); + poly.lineTo(14, 150); + poly.lineTo(10, 101); + poly.lineTo(100, 11); + + EditShape editShape = new EditShape(); + int geom = editShape.addGeometry(poly); + Polygon poly2 = (Polygon) editShape.getGeometry(geom); + assertTrue(poly.equals(poly2)); + } + + { + // Single part polyline + Polyline poly = new Polyline(); + poly.startPath(10, 10); + poly.lineTo(10, 12); + poly.lineTo(14, 15); + poly.lineTo(10, 11); + EditShape editShape = new EditShape(); + int geom = editShape.addGeometry(poly); + Polyline poly2 = (Polyline) editShape.getGeometry(geom); + assertTrue(poly.equals(poly2)); + } + + { + // Two part poly + Polyline poly = new Polyline(); + poly.startPath(10, 10); + poly.lineTo(10, 12); + poly.lineTo(14, 15); + poly.lineTo(10, 11); + + poly.startPath(100, 10); + poly.lineTo(100, 12); + poly.lineTo(14, 150); + poly.lineTo(10, 101); + poly.lineTo(100, 11); + + EditShape editShape = new EditShape(); + int geom = editShape.addGeometry(poly); + Polyline poly2 = (Polyline) editShape.getGeometry(geom); + assertTrue(poly.equals(poly2)); + } + + { + // Five part poly. Close one of parts to test if it works. + Polyline poly = new Polyline(); + poly.startPath(10, 10); + poly.lineTo(10, 12); + poly.lineTo(14, 15); + poly.lineTo(10, 11); + + poly.startPath(100, 10); + poly.lineTo(100, 12); + poly.lineTo(14, 150); + poly.lineTo(10, 101); + poly.lineTo(100, 11); + + poly.startPath(1100, 101); + poly.lineTo(1300, 132); + poly.lineTo(144, 150); + poly.lineTo(106, 1051); + poly.lineTo(1600, 161); + + poly.startPath(100, 190); + poly.lineTo(1800, 192); + poly.lineTo(184, 8150); + poly.lineTo(1080, 181); + + poly.startPath(1030, 10); + poly.lineTo(1300, 132); + poly.lineTo(314, 3150); + poly.lineTo(310, 1301); + poly.lineTo(3100, 311); + + EditShape editShape = new EditShape(); + int geom = editShape.addGeometry(poly); + editShape.setClosedPath( + editShape.getNextPath(editShape.getFirstPath(geom)), true); + ((MultiPathImpl) poly._getImpl()).closePathWithLine(1); + Polyline poly2 = (Polyline) editShape.getGeometry(geom); + assertTrue(poly.equals(poly2)); + } + + { + // Test erase + Polyline poly = new Polyline(); + poly.startPath(10, 10); + poly.lineTo(10, 12); + poly.lineTo(314, 3150); + poly.lineTo(310, 1301); + poly.lineTo(3100, 311); + + EditShape editShape = new EditShape(); + int geom = editShape.addGeometry(poly); + int vertex = editShape.getFirstVertex(editShape.getFirstPath(geom)); + vertex = editShape.removeVertex(vertex, true); + vertex = editShape.getNextVertex(vertex); + editShape.removeVertex(vertex, true); + Polyline poly2 = (Polyline) editShape.getGeometry(geom); + + poly.setEmpty(); + poly.startPath(10, 12); + poly.lineTo(310, 1301); + poly.lineTo(3100, 311); + + assertTrue(poly.equals(poly2)); + } + + { + // Test erase + Polygon poly = new Polygon(); + poly.startPath(10, 10); + poly.lineTo(10, 12); + poly.lineTo(314, 3150); + poly.lineTo(310, 1301); + poly.lineTo(3100, 311); + + EditShape editShape = new EditShape(); + int geom = editShape.addGeometry(poly); + int vertex = editShape.getFirstVertex(editShape.getFirstPath(geom)); + vertex = editShape.removeVertex(vertex, true); + vertex = editShape.getNextVertex(vertex); + editShape.removeVertex(vertex, true); + Polygon poly2 = (Polygon) editShape.getGeometry(geom); + + poly.setEmpty(); + poly.startPath(10, 12); + poly.lineTo(310, 1301); + poly.lineTo(3100, 311); + + assertTrue(poly.equals(poly2)); + } + + { + // Test Filter Close Points + Polygon poly = new Polygon(); + poly.startPath(10, 10); + poly.lineTo(10, 10.001); + poly.lineTo(10.001, 10); + + EditShape editShape = new EditShape(); + int geom = editShape.addGeometry(poly); + editShape.filterClosePoints(0.002, true); + Polygon poly2 = (Polygon) editShape.getGeometry(geom); + assertTrue(poly2.isEmpty()); + } + + { + // Test Filter Close Points + Polygon poly = new Polygon(); + poly.startPath(10, 10); + poly.lineTo(10, 10.0025); + poly.lineTo(11.0, 10); + + EditShape editShape = new EditShape(); + int geom = editShape.addGeometry(poly); + editShape.filterClosePoints(0.002, true); + Polygon poly2 = (Polygon) editShape.getGeometry(geom); + assertTrue(!poly2.isEmpty()); + } + + { + // Test Filter Close Points + Polygon poly = new Polygon(); + poly.startPath(10, 10); + poly.lineTo(10, 10.001); + poly.lineTo(11.0, 10); + + EditShape editShape = new EditShape(); + int geom = editShape.addGeometry(poly); + editShape.filterClosePoints(0.002, true); + Polygon poly2 = (Polygon) editShape.getGeometry(geom); + assertTrue(poly2.isEmpty()); + } + + { + // Test attribute splitting 1 + Polyline polyline = new Polyline(); + polyline.startPath(0, 0); + polyline.lineTo(1, 1); + polyline.lineTo(2, 2); + polyline.lineTo(3, 3); + polyline.lineTo(4, 4); + + polyline.startPath(5, 5); + polyline.lineTo(6, 6); + polyline.lineTo(7, 7); + polyline.lineTo(8, 8); + polyline.lineTo(9, 9); + + polyline.addAttribute(VertexDescription.Semantics.Z); + polyline.setAttribute(VertexDescription.Semantics.Z, 0, 0, 4); + polyline.setAttribute(VertexDescription.Semantics.Z, 1, 0, 8); + polyline.setAttribute(VertexDescription.Semantics.Z, 2, 0, 12); + polyline.setAttribute(VertexDescription.Semantics.Z, 3, 0, 16); + polyline.setAttribute(VertexDescription.Semantics.Z, 4, 0, 20); + + polyline.setAttribute(VertexDescription.Semantics.Z, 5, 0, 22); + polyline.setAttribute(VertexDescription.Semantics.Z, 6, 0, 26); + polyline.setAttribute(VertexDescription.Semantics.Z, 7, 0, 30); + polyline.setAttribute(VertexDescription.Semantics.Z, 8, 0, 34); + polyline.setAttribute(VertexDescription.Semantics.Z, 9, 0, 38); + + EditShape shape = new EditShape(); + int geometry = shape.addGeometry(polyline); + + AttributeStreamOfInt32 vertex_handles = new AttributeStreamOfInt32( + 0); + + for (int path = shape.getFirstPath(geometry); path != -1; path = shape + .getNextPath(path)) { + for (int vertex = shape.getFirstVertex(path); vertex != -1; vertex = shape + .getNextVertex(vertex)) { + if (vertex != shape.getLastVertex(path)) + vertex_handles.add(vertex); + } + } + + double[] t = new double[1]; + for (int i = 0; i < vertex_handles.size(); i++) { + int vertex = vertex_handles.read(i); + t[0] = 0.5; + shape.splitSegment(vertex, t, 1); + } + + Polyline chopped_polyline = (Polyline) shape.getGeometry(geometry); + assertTrue(chopped_polyline.getPointCount() == 18); + + double att_ = 4; + for (int i = 0; i < 18; i++) { + double att = chopped_polyline.getAttributeAsDbl( + VertexDescription.Semantics.Z, i, 0); + assertTrue(att == att_); + att_ += 2; + } + + } + + { // Test attribute splitting 2 + Polyline line1 = new Polyline(), line2 = new Polyline(); + line1.addAttribute(VertexDescription.Semantics.M); + line2.addAttribute(VertexDescription.Semantics.M); + line1.startPath(0, 0); + line1.lineTo(10, 10); + line2.startPath(10, 0); + line2.lineTo(0, 10); + line1.setAttribute(VertexDescription.Semantics.M, 0, 0, 7); + line1.setAttribute(VertexDescription.Semantics.M, 1, 0, 17); + line2.setAttribute(VertexDescription.Semantics.M, 0, 0, 5); + line2.setAttribute(VertexDescription.Semantics.M, 1, 0, 15); + + EditShape shape = new EditShape(); + int g1 = shape.addGeometry(line1); + int g2 = shape.addGeometry(line2); + CrackAndCluster.execute(shape, 0.001, null); + + Polyline chopped_line1 = (Polyline) shape.getGeometry(g1); + Polyline chopped_line2 = (Polyline) shape.getGeometry(g2); + + double att1 = chopped_line1.getAttributeAsDbl( + VertexDescription.Semantics.M, 1, 0); + double att2 = chopped_line2.getAttributeAsDbl( + VertexDescription.Semantics.M, 1, 0); + assertTrue(att1 == 12); + assertTrue(att2 == 10); + } + + { // Test attribute splitting 3 + Polygon polygon = new Polygon(); + polygon.addAttribute(VertexDescription.Semantics.M); + polygon.startPath(0, 0); + polygon.lineTo(0, 10); + polygon.lineTo(10, 10); + polygon.lineTo(10, 0); + + polygon.setAttribute(VertexDescription.Semantics.M, 0, 0, 7); + polygon.setAttribute(VertexDescription.Semantics.M, 1, 0, 17); + polygon.setAttribute(VertexDescription.Semantics.M, 2, 0, 23); + polygon.setAttribute(VertexDescription.Semantics.M, 3, 0, 43); + + EditShape shape = new EditShape(); + int geometry = shape.addGeometry(polygon); + + AttributeStreamOfInt32 vertex_handles = new AttributeStreamOfInt32( + 0); + + int start_v = shape.getFirstVertex(shape.getFirstPath(geometry)); + int v = start_v; + + do { + vertex_handles.add(v); + v = shape.getNextVertex(v); + } while (v != start_v); + + double[] t = new double[1]; + for (int i = 0; i < vertex_handles.size(); i++) { + int v1 = vertex_handles.read(i); + t[0] = 0.5; + shape.splitSegment(v1, t, 1); + } + + Polygon cut_polygon = (Polygon) shape.getGeometry(geometry); + assertTrue(cut_polygon.getPointCount() == 8); + + @SuppressWarnings("unused") + Point2D pt0 = cut_polygon.getXY(0); + double a0 = cut_polygon.getAttributeAsDbl( + VertexDescription.Semantics.M, 0, 0); + assertTrue(a0 == 25); + + @SuppressWarnings("unused") + Point2D pt1 = cut_polygon.getXY(1); + double a1 = cut_polygon.getAttributeAsDbl( + VertexDescription.Semantics.M, 1, 0); + assertTrue(a1 == 7); + + @SuppressWarnings("unused") + Point2D pt2 = cut_polygon.getXY(2); + double a2 = cut_polygon.getAttributeAsDbl( + VertexDescription.Semantics.M, 2, 0); + assertTrue(a2 == 12); + + @SuppressWarnings("unused") + Point2D pt3 = cut_polygon.getXY(3); + double a3 = cut_polygon.getAttributeAsDbl( + VertexDescription.Semantics.M, 3, 0); + assertTrue(a3 == 17); + + @SuppressWarnings("unused") + Point2D pt4 = cut_polygon.getXY(4); + double a4 = cut_polygon.getAttributeAsDbl( + VertexDescription.Semantics.M, 4, 0); + assertTrue(a4 == 20); + + @SuppressWarnings("unused") + Point2D pt5 = cut_polygon.getXY(5); + double a5 = cut_polygon.getAttributeAsDbl( + VertexDescription.Semantics.M, 5, 0); + assertTrue(a5 == 23); + + @SuppressWarnings("unused") + Point2D pt6 = cut_polygon.getXY(6); + double a6 = cut_polygon.getAttributeAsDbl( + VertexDescription.Semantics.M, 6, 0); + assertTrue(a6 == 33); + + @SuppressWarnings("unused") + Point2D pt7 = cut_polygon.getXY(7); + double a7 = cut_polygon.getAttributeAsDbl( + VertexDescription.Semantics.M, 7, 0); + assertTrue(a7 == 43); + } + } +} diff --git a/unittest/com/esri/core/geometry/TestEnvelope2DIntersector.java b/unittest/com/esri/core/geometry/TestEnvelope2DIntersector.java new file mode 100644 index 00000000..e8e867e6 --- /dev/null +++ b/unittest/com/esri/core/geometry/TestEnvelope2DIntersector.java @@ -0,0 +1,303 @@ +package com.esri.core.geometry; + +import java.util.ArrayList; +import java.util.Random; +import junit.framework.TestCase; +import org.junit.Test; + +public class TestEnvelope2DIntersector extends TestCase { + @Override + protected void setUp() throws Exception { + super.setUp(); + } + + @Override + protected void tearDown() throws Exception { + super.tearDown(); + } + + @Test + public static void testEnvelope2Dintersector() { + ArrayList envelopes = new ArrayList(0); + + Envelope2D env0 = new Envelope2D(2, 3, 4, 4); + Envelope2D env1 = new Envelope2D(5, 13, 9, 15); + Envelope2D env2 = new Envelope2D(6, 9, 11, 12); + Envelope2D env3 = new Envelope2D(8, 10, 9, 17); + Envelope2D env4 = new Envelope2D(11.001, 12, 14, 14); + Envelope2D env5 = new Envelope2D(1, 3, 3, 4); + Envelope2D env6 = new Envelope2D(0, 2, 5, 10); + Envelope2D env7 = new Envelope2D(4, 7, 5, 10); + Envelope2D env8 = new Envelope2D(3, 15, 15, 15); + Envelope2D env9 = new Envelope2D(0, 9, 14, 9); + Envelope2D env10 = new Envelope2D(0, 8.999, 14, 8.999); + + envelopes.add(env0); + envelopes.add(env1); + envelopes.add(env2); + envelopes.add(env3); + envelopes.add(env4); + envelopes.add(env5); + envelopes.add(env6); + envelopes.add(env7); + envelopes.add(env8); + envelopes.add(env9); + envelopes.add(env10); + + Envelope2DIntersectorImpl intersector = new Envelope2DIntersectorImpl( + envelopes, 0.001); + + int count = 0; + while (intersector.next()) { + int env_a = intersector.getHandleA(); + int env_b = intersector.getHandleB(); + count++; + Envelope2D env = new Envelope2D(); + env.setCoords(envelopes.get(env_a)); + env.inflate(0.001, 0.001); + assertTrue(env.isIntersecting(envelopes.get(env_b))); + } + + assert (count == 16); + + Envelope2DIntersectorImpl intersector2 = new Envelope2DIntersectorImpl( + envelopes, 0.0); + + count = 0; + while (intersector2.next()) { + int env_a = intersector2.getHandleA(); + int env_b = intersector2.getHandleB(); + count++; + Envelope2D env = new Envelope2D(); + env.setCoords(envelopes.get(env_a)); + assertTrue(env.isIntersecting(envelopes.get(env_b))); + } + + assert (count == 13); + + env0 = new Envelope2D(0, 0, 0, 10); + env1 = new Envelope2D(0, 10, 10, 10); + env2 = new Envelope2D(10, 0, 10, 10); + env3 = new Envelope2D(0, 0, 10, 0); + envelopes.clear(); + + envelopes.add(env0); + envelopes.add(env1); + envelopes.add(env2); + envelopes.add(env3); + + Envelope2DIntersectorImpl intersector3 = new Envelope2DIntersectorImpl( + envelopes, 0.001); + count = 0; + while (intersector3.next()) { + int env_a = intersector3.getHandleA(); + int env_b = intersector3.getHandleB(); + count++; + Envelope2D env = new Envelope2D(); + env.setCoords(envelopes.get(env_a)); + assertTrue(env.isIntersecting(envelopes.get(env_b))); + } + + assertTrue(count == 4); + + env0 = new Envelope2D(0, 0, 0, 10); + envelopes.clear(); + + envelopes.add(env0); + envelopes.add(env0); + envelopes.add(env0); + envelopes.add(env0); + + Envelope2DIntersectorImpl intersector4 = new Envelope2DIntersectorImpl( + envelopes, 0.001); + count = 0; + while (intersector4.next()) { + int env_a = intersector4.getHandleA(); + int env_b = intersector4.getHandleB(); + count++; + Envelope2D env = new Envelope2D(); + env.setCoords(envelopes.get(env_a)); + assertTrue(env.isIntersecting(envelopes.get(env_b))); + } + + assert (count == 6); + + env0 = new Envelope2D(0, 10, 10, 10); + envelopes.clear(); + + envelopes.add(env0); + envelopes.add(env0); + envelopes.add(env0); + envelopes.add(env0); + + Envelope2DIntersectorImpl intersector5 = new Envelope2DIntersectorImpl( + envelopes, 0.001); + count = 0; + while (intersector5.next()) { + int env_a = intersector5.getHandleA(); + int env_b = intersector5.getHandleB(); + count++; + Envelope2D env = new Envelope2D(); + env.setCoords(envelopes.get(env_a)); + assertTrue(env.isIntersecting(envelopes.get(env_b))); + } + + assertTrue(count == 6); + } + + @Test + public static void testRandom() { + int passcount = 10; + int figureSize = 100; + int figureSize2 = 100; + Envelope extent1 = new Envelope(), extent2 = new Envelope(), extent3 = new Envelope(), extent4 = new Envelope(); + extent1.setCoords(-10000, 5000, 10000, 25000);// red + extent2.setCoords(-10000, 2000, 10000, 8000);// blue + extent3.setCoords(-10000, -8000, 10000, -2000);// blue + extent4.setCoords(-10000, -25000, 10000, -5000);// red + + RandomCoordinateGenerator generator1 = new RandomCoordinateGenerator( + Math.max(figureSize, 10000), extent1, 0.001); + RandomCoordinateGenerator generator2 = new RandomCoordinateGenerator( + Math.max(figureSize, 10000), extent2, 0.001); + RandomCoordinateGenerator generator3 = new RandomCoordinateGenerator( + Math.max(figureSize, 10000), extent3, 0.001); + RandomCoordinateGenerator generator4 = new RandomCoordinateGenerator( + Math.max(figureSize, 10000), extent4, 0.001); + + Random random = new Random(1982); + int rand_max = 511; + + int qCount = 0; + int eCount = 0; + @SuppressWarnings("unused") + int bCount = 0; + for (int c = 0; c < passcount; c++) { + Polygon polyRed = new Polygon(); + Polygon polyBlue = new Polygon(); + + int r = figureSize; + if (r < 3) + continue; + + Point pt; + for (int j = 0; j < r; j++) { + int rand = random.nextInt(rand_max); + boolean bRandomNew = (r > 10) + && ((1.0 * rand) / rand_max > 0.95); + pt = generator1.GetRandomCoord(); + if (j == 0 || bRandomNew) + polyRed.startPath(pt); + else + polyRed.lineTo(pt); + } + + for (int j = 0; j < r; j++) { + int rand = random.nextInt(rand_max); + boolean bRandomNew = (r > 10) + && ((1.0 * rand) / rand_max > 0.95); + pt = generator4.GetRandomCoord(); + if (j == 0 || bRandomNew) + polyRed.startPath(pt); + else + polyRed.lineTo(pt); + } + + r = figureSize2; + if (r < 3) + continue; + + for (int j = 0; j < r; j++) { + int rand = random.nextInt(rand_max); + boolean bRandomNew = (r > 10) + && ((1.0 * rand) / rand_max > 0.95); + pt = generator2.GetRandomCoord(); + if (j == 0 || bRandomNew) + polyBlue.startPath(pt); + else + polyBlue.lineTo(pt); + } + + for (int j = 0; j < r; j++) { + int rand = random.nextInt(rand_max); + boolean bRandomNew = (r > 10) + && ((1.0 * rand) / rand_max > 0.95); + pt = generator3.GetRandomCoord(); + if (j == 0 || bRandomNew) + polyBlue.startPath(pt); + else + polyBlue.lineTo(pt); + } + + Envelope2D env = new Envelope2D(); + + // Quad_tree + QuadTree quadTree = buildQuadTree(polyBlue); + QuadTree.QuadTreeIterator iterator = quadTree.getIterator(); + + SegmentIteratorImpl _segIterRed = ((MultiPathImpl) polyRed + ._getImpl()).querySegmentIterator(); + + while (_segIterRed.nextPath()) { + while (_segIterRed.hasNextSegment()) { + Segment segmentRed = _segIterRed.nextSegment(); + segmentRed.queryEnvelope2D(env); + iterator.resetIterator(env, 0.001); + while (iterator.next() != -1) + qCount++; + } + } + + // Envelope_2D_intersector + + ArrayList envelopes_red = new ArrayList(); + ArrayList envelopes_blue = new ArrayList(); + + SegmentIterator segIterRed = polyRed.querySegmentIterator(); + while (segIterRed.nextPath()) { + while (segIterRed.hasNextSegment()) { + Segment segment = segIterRed.nextSegment(); + env = new Envelope2D(); + segment.queryEnvelope2D(env); + envelopes_red.add(env); + } + } + + SegmentIterator segIterBlue = polyBlue.querySegmentIterator(); + while (segIterBlue.nextPath()) { + while (segIterBlue.hasNextSegment()) { + Segment segment = segIterBlue.nextSegment(); + env = new Envelope2D(); + segment.queryEnvelope2D(env); + envelopes_blue.add(env); + } + } + + Envelope2DIntersectorImpl intersector = new Envelope2DIntersectorImpl( + envelopes_red, envelopes_blue, 0.001); + while (intersector.next()) + eCount++; + + assertTrue(qCount == eCount); + } + } + + public static QuadTree buildQuadTree(MultiPath multipath) { + Envelope2D extent = new Envelope2D(); + multipath.queryEnvelope2D(extent); + QuadTree quadTree = new QuadTree(extent, 8); + int hint_index = -1; + SegmentIterator seg_iter = multipath.querySegmentIterator(); + while (seg_iter.nextPath()) { + while (seg_iter.hasNextSegment()) { + Segment segment = seg_iter.nextSegment(); + int index = seg_iter.getStartPointIndex(); + Envelope2D boundingbox = new Envelope2D(); + segment.queryEnvelope2D(boundingbox); + hint_index = quadTree.insert(index, boundingbox, hint_index); + } + } + + return quadTree; + } +} diff --git a/unittest/com/esri/core/geometry/TestEquals.java b/unittest/com/esri/core/geometry/TestEquals.java new file mode 100644 index 00000000..a42abf30 --- /dev/null +++ b/unittest/com/esri/core/geometry/TestEquals.java @@ -0,0 +1,168 @@ +package com.esri.core.geometry; + +import junit.framework.TestCase; +import org.junit.Test; + +public class TestEquals extends TestCase { + @Override + protected void setUp() throws Exception { + super.setUp(); + } + + @Override + protected void tearDown() throws Exception { + super.tearDown(); + } + + @Test + public void testEqualsOnEnvelopes() { + SpatialReference sr = SpatialReference.create(4326); + + Point p = new Point(-130, 10); + Envelope env = new Envelope(p, 12, 12); + Envelope env2 = new Envelope(-136, 4, -124, 16); + + boolean isEqual; + try { + isEqual = GeometryEngine.equals(env, env2, sr); + + } catch (IllegalArgumentException ex) { + isEqual = false; + } + assertTrue(isEqual); + } + + @Test + public void testEqualsOnPoints() { + SpatialReference sr = SpatialReference.create(4326); + + Point p1 = new Point(-116, 40); + @SuppressWarnings("unused") + Point p2 = new Point(-120, 39); + @SuppressWarnings("unused") + Point p3 = new Point(-121, 10); + @SuppressWarnings("unused") + Point p4 = new Point(-130, 12); + @SuppressWarnings("unused") + Point p5 = new Point(-108, 25); + + Point p12 = new Point(-116, 40); + @SuppressWarnings("unused") + Point p22 = new Point(-120, 39); + @SuppressWarnings("unused") + Point p32 = new Point(-121, 10); + @SuppressWarnings("unused") + Point p42 = new Point(-130, 12); + @SuppressWarnings("unused") + Point p52 = new Point(-108, 25); + + boolean isEqual1 = false; + boolean isEqual2 = false; + boolean isEqual3 = false; + boolean isEqual4 = false; + boolean isEqual5 = false; + + try { + isEqual1 = GeometryEngine.equals(p1, p12, sr); + isEqual2 = GeometryEngine.equals(p1, p12, sr); + isEqual3 = GeometryEngine.equals(p1, p12, sr); + isEqual4 = GeometryEngine.equals(p1, p12, sr); + isEqual5 = GeometryEngine.equals(p1, p12, sr); + } catch (IllegalArgumentException ex) { + + } + + assertTrue(isEqual1 && isEqual2 && isEqual3 && isEqual4 && isEqual5); + } + + @Test + public void testEqualsOnPolygons() { + SpatialReference sr = SpatialReference.create(4326); + + Polygon baseMp = new Polygon(); + Polygon compMp = new Polygon(); + + baseMp.startPath(-116, 40); + baseMp.lineTo(-120, 39); + baseMp.lineTo(-121, 10); + baseMp.lineTo(-130, 12); + baseMp.lineTo(-108, 25); + + compMp.startPath(-116, 40); + compMp.lineTo(-120, 39); + compMp.lineTo(-121, 10); + compMp.lineTo(-130, 12); + compMp.lineTo(-108, 25); + + boolean isEqual; + + try { + isEqual = GeometryEngine.equals(baseMp, compMp, sr); + + } catch (IllegalArgumentException ex) { + isEqual = false; + } + + assertTrue(isEqual); + } + + @Test + public void testEqualsOnPolylines() { + SpatialReference sr = SpatialReference.create(4326); + + Polyline baseMp = new Polyline(); + Polyline compMp = new Polyline(); + + baseMp.startPath(-116, 40); + baseMp.lineTo(-120, 39); + baseMp.lineTo(-121, 10); + baseMp.lineTo(-130, 12); + baseMp.lineTo(-108, 25); + + compMp.startPath(-116, 40); + compMp.lineTo(-120, 39); + compMp.lineTo(-121, 10); + compMp.lineTo(-130, 12); + compMp.lineTo(-108, 25); + + boolean isEqual; + + try { + isEqual = GeometryEngine.equals(baseMp, compMp, sr); + } catch (IllegalArgumentException ex) { + isEqual = false; + } + + assertTrue(isEqual); + } + + @Test + public void testEqualsOnMultiPoints() { + SpatialReference sr = SpatialReference.create(4326); + + MultiPoint baseMp = new MultiPoint(); + MultiPoint compMp = new MultiPoint(); + + baseMp.add(new Point(-116, 40)); + baseMp.add(new Point(-120, 39)); + baseMp.add(new Point(-121, 10)); + baseMp.add(new Point(-130, 12)); + baseMp.add(new Point(-108, 25)); + + compMp.add(new Point(-116, 40)); + compMp.add(new Point(-120, 39)); + compMp.add(new Point(-121, 10)); + compMp.add(new Point(-130, 12)); + compMp.add(new Point(-108, 25)); + + boolean isEqual; + + try { + isEqual = GeometryEngine.equals(baseMp, compMp, sr); + } catch (IllegalArgumentException ex) { + isEqual = false; + } + + assertTrue(isEqual); + } +} diff --git a/unittest/com/esri/core/geometry/TestFailed.java b/unittest/com/esri/core/geometry/TestFailed.java new file mode 100644 index 00000000..da570fbf --- /dev/null +++ b/unittest/com/esri/core/geometry/TestFailed.java @@ -0,0 +1,65 @@ +package com.esri.core.geometry; + +import junit.framework.TestCase; +import org.junit.Test; + +public class TestFailed extends TestCase { + @Override + protected void setUp() throws Exception { + super.setUp(); + } + + @Override + protected void tearDown() throws Exception { + super.tearDown(); + } + + @Test + public void testCenterXY() { + Envelope env = new Envelope(-130, 30, -70, 50); + assertEquals(-100, env.getCenterX(), 0); + assertEquals(40, env.getCenterY(), 0); + } + + @Test + public void testGeometryOperationSupport() { + Geometry baseGeom = new Point(-130, 10); + Geometry comparisonGeom = new Point(-130, 10); + SpatialReference sr = SpatialReference.create(4326); + + @SuppressWarnings("unused") + Geometry diffGeom = null; + int noException = 1; // no exception + try { + diffGeom = GeometryEngine.difference(baseGeom, comparisonGeom, sr); + + } catch (IllegalArgumentException ex) { + noException = 0; + } catch (GeometryException ex) { + System.out.println(ex.internalCode); + noException = 0; + } + assertEquals(noException, 1); + } + + @Test + public void TestIntersection() { + OperatorIntersects op = (OperatorIntersects) OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Intersects); + Polygon polygon = new Polygon(); + // outer ring1 + polygon.startPath(0, 0); + polygon.lineTo(10, 10); + polygon.lineTo(20, 0); + + Point point1 = new Point(15, 10); + Point point2 = new Point(2, 10); + Point point3 = new Point(5, 5); + boolean res = op.execute(polygon, point1, null, null); + assertTrue(!res); + res = op.execute(polygon, point2, null, null); + assertTrue(!res); + res = op.execute(polygon, point3, null, null); + assertTrue(res); + } +} diff --git a/unittest/com/esri/core/geometry/TestGeneralize.java b/unittest/com/esri/core/geometry/TestGeneralize.java new file mode 100644 index 00000000..c7316e74 --- /dev/null +++ b/unittest/com/esri/core/geometry/TestGeneralize.java @@ -0,0 +1,93 @@ +package com.esri.core.geometry; + +import junit.framework.TestCase; +import org.junit.Test; + +public class TestGeneralize extends TestCase { + @Override + protected void setUp() throws Exception { + super.setUp(); + } + + @Override + protected void tearDown() throws Exception { + super.tearDown(); + } + + @Test + public static void test1() { + OperatorFactoryLocal engine = OperatorFactoryLocal.getInstance(); + OperatorGeneralize op = (OperatorGeneralize) engine + .getOperator(Operator.Type.Generalize); + + Polygon poly = new Polygon(); + poly.startPath(0, 0); + poly.lineTo(1, 1); + poly.lineTo(2, 0); + poly.lineTo(3, 2); + poly.lineTo(4, 1); + poly.lineTo(5, 0); + poly.lineTo(5, 10); + poly.lineTo(0, 10); + Geometry geom = op.execute(poly, 2, true, null); + Polygon p = (Polygon) geom; + Point2D[] points = p.getCoordinates2D(); + assertTrue(points.length == 4); + assertTrue(points[0].x == 0 && points[0].y == 0); + assertTrue(points[1].x == 5 && points[1].y == 0); + assertTrue(points[2].x == 5 && points[2].y == 10); + assertTrue(points[3].x == 0 && points[3].y == 10); + + Geometry geom1 = op.execute(geom, 5, false, null); + p = (Polygon) geom1; + points = p.getCoordinates2D(); + assertTrue(points.length == 3); + assertTrue(points[0].x == 0 && points[0].y == 0); + assertTrue(points[1].x == 5 && points[1].y == 10); + assertTrue(points[2].x == 5 && points[2].y == 10); + + geom1 = op.execute(geom, 5, true, null); + p = (Polygon) geom1; + points = p.getCoordinates2D(); + assertTrue(points.length == 0); + } + + @Test + public static void test2() { + OperatorFactoryLocal engine = OperatorFactoryLocal.getInstance(); + OperatorGeneralize op = (OperatorGeneralize) engine + .getOperator(Operator.Type.Generalize); + + Polyline polyline = new Polyline(); + polyline.startPath(0, 0); + polyline.lineTo(1, 1); + polyline.lineTo(2, 0); + polyline.lineTo(3, 2); + polyline.lineTo(4, 1); + polyline.lineTo(5, 0); + polyline.lineTo(5, 10); + polyline.lineTo(0, 10); + Geometry geom = op.execute(polyline, 2, true, null); + Polyline p = (Polyline) geom; + Point2D[] points = p.getCoordinates2D(); + assertTrue(points.length == 4); + assertTrue(points[0].x == 0 && points[0].y == 0); + assertTrue(points[1].x == 5 && points[1].y == 0); + assertTrue(points[2].x == 5 && points[2].y == 10); + assertTrue(points[3].x == 0 && points[3].y == 10); + + Geometry geom1 = op.execute(geom, 5, false, null); + p = (Polyline) geom1; + points = p.getCoordinates2D(); + assertTrue(points.length == 2); + assertTrue(points[0].x == 0 && points[0].y == 0); + assertTrue(points[1].x == 0 && points[1].y == 10); + + geom1 = op.execute(geom, 5, true, null); + p = (Polyline) geom1; + points = p.getCoordinates2D(); + assertTrue(points.length == 2); + assertTrue(points[0].x == 0 && points[0].y == 0); + assertTrue(points[1].x == 0 && points[1].y == 10); + } +} diff --git a/unittest/com/esri/core/geometry/TestGeodetic.java b/unittest/com/esri/core/geometry/TestGeodetic.java new file mode 100644 index 00000000..453f7e1b --- /dev/null +++ b/unittest/com/esri/core/geometry/TestGeodetic.java @@ -0,0 +1,72 @@ +package com.esri.core.geometry; + +import junit.framework.TestCase; +import org.junit.Test; + +public class TestGeodetic extends TestCase { + @Override + protected void setUp() throws Exception { + super.setUp(); + } + + @Override + protected void tearDown() throws Exception { + super.tearDown(); + } + + @Test + public static void testTriangleLength() { + Point pt_0 = new Point(10, 10); + Point pt_1 = new Point(20, 20); + Point pt_2 = new Point(20, 10); + double length = 0.0; + length += GeometryEngine.geodesicDistanceOnWGS84(pt_0, pt_1); + length += GeometryEngine.geodesicDistanceOnWGS84(pt_1, pt_2); + length += GeometryEngine.geodesicDistanceOnWGS84(pt_2, pt_0); + assertTrue(Math.abs(length - 3744719.4094597572) < 1e-13 * 3744719.4094597572); + } + + @Test + public static void testRotationInvariance() { + Point pt_0 = new Point(10, 40); + Point pt_1 = new Point(20, 60); + Point pt_2 = new Point(20, 40); + double length = 0.0; + length += GeometryEngine.geodesicDistanceOnWGS84(pt_0, pt_1); + length += GeometryEngine.geodesicDistanceOnWGS84(pt_1, pt_2); + length += GeometryEngine.geodesicDistanceOnWGS84(pt_2, pt_0); + assertTrue(Math.abs(length - 5409156.3896271614) < 1e-13 * 5409156.3896271614); + + for (int i = -540; i < 540; i += 5) { + pt_0.setXY(i + 10, 40); + pt_1.setXY(i + 20, 60); + pt_2.setXY(i + 20, 40); + length = 0.0; + length += GeometryEngine.geodesicDistanceOnWGS84(pt_0, pt_1); + length += GeometryEngine.geodesicDistanceOnWGS84(pt_1, pt_2); + length += GeometryEngine.geodesicDistanceOnWGS84(pt_2, pt_0); + assertTrue(Math.abs(length - 5409156.3896271614) < 1e-13 * 5409156.3896271614); + } + } + + @Test + public static void testLengthAccurateCR191313() { + /* + * // random_test(); OperatorFactoryLocal engine = + * OperatorFactoryLocal.getInstance(); //TODO: Make this: + * OperatorShapePreservingLength geoLengthOp = + * (OperatorShapePreservingLength) + * factory.getOperator(Operator.Type.ShapePreservingLength); + * SpatialReference spatialRef = SpatialReference.create(102631); + * //[6097817.59407673 + * ,17463475.2931517],[-1168053.34617516,11199801.3734424 + * ]]],"spatialReference":{"wkid":102631} + * + * Polyline polyline = new Polyline(); + * polyline.startPath(6097817.59407673, 17463475.2931517); + * polyline.lineTo(-1168053.34617516, 11199801.3734424); double length = + * geoLengthOp.execute(polyline, spatialRef, null); + * assertTrue(Math.abs(length - 2738362.3249366437) < 2e-9 * length); + */ + } +} diff --git a/unittest/com/esri/core/geometry/TestGeomToJSonExportSRFromWkiOrWkt_CR181369.java b/unittest/com/esri/core/geometry/TestGeomToJSonExportSRFromWkiOrWkt_CR181369.java new file mode 100644 index 00000000..17fc2ccc --- /dev/null +++ b/unittest/com/esri/core/geometry/TestGeomToJSonExportSRFromWkiOrWkt_CR181369.java @@ -0,0 +1,579 @@ +package com.esri.core.geometry; + +import java.io.IOException; +import org.codehaus.jackson.JsonFactory; +import org.codehaus.jackson.JsonParseException; +import org.codehaus.jackson.JsonParser; +import junit.framework.TestCase; +import org.junit.Test; + +public class TestGeomToJSonExportSRFromWkiOrWkt_CR181369 extends TestCase { + @Override + protected void setUp() throws Exception { + super.setUp(); + } + + @Override + protected void tearDown() throws Exception { + super.tearDown(); + } + + JsonFactory factory = new JsonFactory(); + SpatialReference spatialReferenceWebMerc1 = SpatialReference.create(102100); + SpatialReference spatialReferenceWebMerc2 = SpatialReference + .create(spatialReferenceWebMerc1.getLatestID()); + SpatialReference spatialReferenceWGS84 = SpatialReference.create(4326); + + @Test + public void testGeomToJSonExportSRFromWkiOrWkt_CR181369() + throws JsonParseException, IOException { + testPoint(); + testPolyline(); + testPolygon(); + testEnvelope(); + testMultiPoint(); + testCR181369(); + // These tests return the result of a method called + // checkResultSpatialRef. + // However, the tests pass or fail regardless of what that method + // returns. + } + + boolean testPoint() throws JsonParseException, IOException { + boolean bAnswer = true; + Point point1 = new Point(10.0, 20.0); + Point pointEmpty = new Point(); + { + JsonParser pointWebMerc1Parser = factory + .createJsonParser(GeometryEngine.geometryToJson( + spatialReferenceWebMerc1, point1)); + MapGeometry pointWebMerc1MP = GeometryEngine + .jsonToGeometry(pointWebMerc1Parser); + assertTrue(point1.getX() == ((Point) pointWebMerc1MP.getGeometry()) + .getX()); + assertTrue(point1.getY() == ((Point) pointWebMerc1MP.getGeometry()) + .getY()); + assertTrue(spatialReferenceWebMerc1.getID() == pointWebMerc1MP + .getSpatialReference().getID() + || pointWebMerc1MP.getSpatialReference().getID() == 3857); + + if (!checkResultSpatialRef(pointWebMerc1MP, 102100, 3857)) { + bAnswer = false; + } + + pointWebMerc1Parser = factory.createJsonParser(GeometryEngine + .geometryToJson(null, point1)); + pointWebMerc1MP = GeometryEngine + .jsonToGeometry(pointWebMerc1Parser); + assertTrue(null == pointWebMerc1MP.getSpatialReference()); + + if (pointWebMerc1MP.getSpatialReference() != null) { + if (!checkResultSpatialRef(pointWebMerc1MP, 102100, 3857)) { + bAnswer = false; + } + } + + String pointEmptyString = GeometryEngine.geometryToJson( + spatialReferenceWebMerc1, pointEmpty); + pointWebMerc1Parser = factory.createJsonParser(pointEmptyString); + // FIXME + // pointWebMerc1MP = + // GeometryEngine.jsonToGeometry(pointWebMerc1Parser); + // assertTrue(pointWebMerc1MP.getGeometry().isEmpty()); + // assertTrue(spatialReferenceWebMerc1.getID() == + // pointWebMerc1MP.getSpatialReference().getID()); + } + + JsonParser pointWebMerc2Parser = factory + .createJsonParser(GeometryEngine.geometryToJson( + spatialReferenceWebMerc2, point1)); + MapGeometry pointWebMerc2MP = GeometryEngine + .jsonToGeometry(pointWebMerc2Parser); + assertTrue(point1.getX() == ((Point) pointWebMerc2MP.getGeometry()) + .getX()); + assertTrue(point1.getY() == ((Point) pointWebMerc2MP.getGeometry()) + .getY()); + assertTrue(spatialReferenceWebMerc2.getLatestID() == pointWebMerc2MP + .getSpatialReference().getLatestID()); + if (!checkResultSpatialRef(pointWebMerc2MP, + spatialReferenceWebMerc2.getLatestID(), 0)) { + bAnswer = false; + } + + { + JsonParser pointWgs84Parser = factory + .createJsonParser(GeometryEngine.geometryToJson( + spatialReferenceWGS84, point1)); + MapGeometry pointWgs84MP = GeometryEngine + .jsonToGeometry(pointWgs84Parser); + assertTrue(point1.getX() == ((Point) pointWgs84MP.getGeometry()) + .getX()); + assertTrue(point1.getY() == ((Point) pointWgs84MP.getGeometry()) + .getY()); + assertTrue(spatialReferenceWGS84.getID() == pointWgs84MP + .getSpatialReference().getID()); + if (!checkResultSpatialRef(pointWgs84MP, 4326, 0)) { + bAnswer = false; + } + } + + { + Point p = new Point(); + String s = GeometryEngine.geometryToJson(spatialReferenceWebMerc1, + p); + assertTrue(s + .equals("{\"x\":null,\"y\":null,\"spatialReference\":{\"wkid\":102100,\"latestWkid\":3857}}")); + + p.addAttribute(VertexDescription.Semantics.Z); + p.addAttribute(VertexDescription.Semantics.M); + s = GeometryEngine.geometryToJson(spatialReferenceWebMerc1, p); + assertTrue(s + .equals("{\"x\":null,\"y\":null,\"z\":null,\"m\":null,\"spatialReference\":{\"wkid\":102100,\"latestWkid\":3857}}")); + + } + + { + Point p = new Point(10.0, 20.0, 30.0); + p.addAttribute(VertexDescription.Semantics.M); + String s = GeometryEngine.geometryToJson(spatialReferenceWebMerc1, + p); + assertTrue(s + .equals("{\"x\":10.0,\"y\":20.0,\"z\":30.0,\"m\":null,\"spatialReference\":{\"wkid\":102100,\"latestWkid\":3857}}")); + } + + {// import + String s = "{\"x\":0.0,\"y\":1.0,\"z\":5.0,\"m\":11.0,\"spatialReference\":{\"wkid\":102100,\"latestWkid\":3857}}"; + JsonParser parser = factory.createJsonParser(s); + MapGeometry map_pt = GeometryEngine.jsonToGeometry(parser); + Point pt = (Point) map_pt.getGeometry(); + assertTrue(pt.getX() == 0.0); + assertTrue(pt.getY() == 1.0); + assertTrue(pt.getZ() == 5.0); + assertTrue(pt.getM() == 11.0); + } + + { + String s = "{\"x\" : 5.0, \"y\" : null, \"spatialReference\" : {\"wkid\" : 4326}} "; + JsonParser parser = factory.createJsonParser(s); + MapGeometry map_pt = GeometryEngine.jsonToGeometry(parser); + Point pt = (Point) map_pt.getGeometry(); + assertTrue(pt.isEmpty()); + SpatialReference spatial_reference = map_pt.getSpatialReference(); + assertTrue(spatial_reference.getID() == 4326); + } + + return bAnswer; + } + + boolean testMultiPoint() throws JsonParseException, IOException { + boolean bAnswer = true; + + MultiPoint multiPoint1 = new MultiPoint(); + multiPoint1.add(-97.06138, 32.837); + multiPoint1.add(-97.06133, 32.836); + multiPoint1.add(-97.06124, 32.834); + multiPoint1.add(-97.06127, 32.832); + + { + String s = GeometryEngine.geometryToJson(spatialReferenceWGS84, + multiPoint1); + JsonParser mPointWgs84Parser = factory.createJsonParser(s); + MapGeometry mPointWgs84MP = GeometryEngine + .jsonToGeometry(mPointWgs84Parser); + assertTrue(multiPoint1.getPointCount() == ((MultiPoint) mPointWgs84MP + .getGeometry()).getPointCount()); + assertTrue(multiPoint1.getPoint(0).getX() == ((MultiPoint) mPointWgs84MP + .getGeometry()).getPoint(0).getX()); + assertTrue(multiPoint1.getPoint(0).getY() == ((MultiPoint) mPointWgs84MP + .getGeometry()).getPoint(0).getY()); + int lastIndex = multiPoint1.getPointCount() - 1; + assertTrue(multiPoint1.getPoint(lastIndex).getX() == ((MultiPoint) mPointWgs84MP + .getGeometry()).getPoint(lastIndex).getX()); + assertTrue(multiPoint1.getPoint(lastIndex).getY() == ((MultiPoint) mPointWgs84MP + .getGeometry()).getPoint(lastIndex).getY()); + + assertTrue(spatialReferenceWGS84.getID() == mPointWgs84MP + .getSpatialReference().getID()); + if (!checkResultSpatialRef(mPointWgs84MP, 4326, 0)) { + bAnswer = false; + } + + // FIXME + // MultiPoint mPointEmpty = new MultiPoint(); + // String mPointEmptyString = + // GeometryEngine.geometryToJson(spatialReferenceWGS84, + // mPointEmpty); + // mPointWgs84Parser = factory.createJsonParser(mPointEmptyString); + } + + { + MultiPoint p = new MultiPoint(); + p.addAttribute(VertexDescription.Semantics.Z); + p.addAttribute(VertexDescription.Semantics.M); + String s = GeometryEngine.geometryToJson(spatialReferenceWebMerc1, + p); + assertTrue(s + .equals("{\"hasZ\":true,\"hasM\":true,\"points\":[],\"spatialReference\":{\"wkid\":102100,\"latestWkid\":3857}}")); + + p.add(10.0, 20.0, 30.0); + p.add(20.0, 40.0, 60.0); + s = GeometryEngine.geometryToJson(spatialReferenceWebMerc1, p); + assertTrue(s + .equals("{\"hasZ\":true,\"hasM\":true,\"points\":[[10.0,20.0,30.0,null],[20.0,40.0,60.0,null]],\"spatialReference\":{\"wkid\":102100,\"latestWkid\":3857}}")); + } + { + String points = "{\"hasM\" : false, \"hasZ\" : true, \"uncle remus\" : null, \"points\" : [ [0,0,1], [0.0,10.0,1], [10.0,10.0,1], [10.0,0.0,1, 6666] ],\"spatialReference\" : {\"wkid\" : 4326}}"; + MapGeometry mp = GeometryEngine.jsonToGeometry(factory + .createJsonParser(points)); + MultiPoint multipoint = (MultiPoint) mp.getGeometry(); + assertTrue(multipoint.getPointCount() == 4); + Point2D point2d; + point2d = multipoint.getXY(0); + assertTrue(point2d.x == 0.0 && point2d.y == 0.0); + point2d = multipoint.getXY(1); + assertTrue(point2d.x == 0.0 && point2d.y == 10.0); + point2d = multipoint.getXY(2); + assertTrue(point2d.x == 10.0 && point2d.y == 10.0); + point2d = multipoint.getXY(3); + assertTrue(point2d.x == 10.0 && point2d.y == 0.0); + assertTrue(multipoint.hasAttribute(VertexDescription.Semantics.Z)); + assertTrue(!multipoint.hasAttribute(VertexDescription.Semantics.M)); + double z = multipoint.getAttributeAsDbl( + VertexDescription.Semantics.Z, 0, 0); + assertTrue(z == 1); + SpatialReference spatial_reference = mp.getSpatialReference(); + assertTrue(spatial_reference.getID() == 4326); + } + + return bAnswer; + } + + boolean testPolyline() throws JsonParseException, IOException { + boolean bAnswer = true; + + Polyline polyline = new Polyline(); + polyline.startPath(-97.06138, 32.837); + polyline.lineTo(-97.06133, 32.836); + polyline.lineTo(-97.06124, 32.834); + polyline.lineTo(-97.06127, 32.832); + + polyline.startPath(-97.06326, 32.759); + polyline.lineTo(-97.06298, 32.755); + + { + JsonParser polylinePathsWgs84Parser = factory + .createJsonParser(GeometryEngine.geometryToJson( + spatialReferenceWGS84, polyline)); + MapGeometry mPolylineWGS84MP = GeometryEngine + .jsonToGeometry(polylinePathsWgs84Parser); + + assertTrue(polyline.getPointCount() == ((Polyline) mPolylineWGS84MP + .getGeometry()).getPointCount()); + assertTrue(polyline.getPoint(0).getX() == ((Polyline) mPolylineWGS84MP + .getGeometry()).getPoint(0).getX()); + assertTrue(polyline.getPoint(0).getY() == ((Polyline) mPolylineWGS84MP + .getGeometry()).getPoint(0).getY()); + + assertTrue(polyline.getPathCount() == ((Polyline) mPolylineWGS84MP + .getGeometry()).getPathCount()); + assertTrue(polyline.getSegmentCount() == ((Polyline) mPolylineWGS84MP + .getGeometry()).getSegmentCount()); + assertTrue(polyline.getSegmentCount(0) == ((Polyline) mPolylineWGS84MP + .getGeometry()).getSegmentCount(0)); + assertTrue(polyline.getSegmentCount(1) == ((Polyline) mPolylineWGS84MP + .getGeometry()).getSegmentCount(1)); + + int lastIndex = polyline.getPointCount() - 1; + assertTrue(polyline.getPoint(lastIndex).getX() == ((Polyline) mPolylineWGS84MP + .getGeometry()).getPoint(lastIndex).getX()); + assertTrue(polyline.getPoint(lastIndex).getY() == ((Polyline) mPolylineWGS84MP + .getGeometry()).getPoint(lastIndex).getY()); + + assertTrue(spatialReferenceWGS84.getID() == mPolylineWGS84MP + .getSpatialReference().getID()); + + if (!checkResultSpatialRef(mPolylineWGS84MP, 4326, 0)) { + bAnswer = false; + } + } + + { + Polyline p = new Polyline(); + p.addAttribute(VertexDescription.Semantics.Z); + p.addAttribute(VertexDescription.Semantics.M); + String s = GeometryEngine.geometryToJson(spatialReferenceWebMerc1, + p); + assertTrue(s + .equals("{\"hasZ\":true,\"hasM\":true,\"paths\":[],\"spatialReference\":{\"wkid\":102100,\"latestWkid\":3857}}")); + + p.startPath(0, 0); + p.lineTo(0, 1); + p.startPath(2, 2); + p.lineTo(3, 3); + + p.setAttribute(VertexDescription.Semantics.Z, 0, 0, 3); + p.setAttribute(VertexDescription.Semantics.M, 1, 0, 5); + s = GeometryEngine.geometryToJson(spatialReferenceWebMerc1, p); + assertTrue(s + .equals("{\"hasZ\":true,\"hasM\":true,\"paths\":[[[0.0,0.0,3.0,null],[0.0,1.0,0.0,5.0]],[[2.0,2.0,0.0,null],[3.0,3.0,0.0,null]]],\"spatialReference\":{\"wkid\":102100,\"latestWkid\":3857}}")); + } + + { + String paths = "{\"hasZ\" : true, \"paths\" : [ [ [0.0, 0.0,3], [0, 10.0,3], [10.0, 10.0,3, 6666], [10.0, 0.0,3, 6666] ], [ [1.0, 1,3], [1.0, 9.0,3], [9.0, 9.0,3], [1.0, 9.0,3] ] ], \"spatialReference\" : {\"wkid\" : 4326}, \"hasM\" : false}"; + MapGeometry mapGeometry = GeometryEngine.jsonToGeometry(factory + .createJsonParser(paths)); + Polyline p = (Polyline) mapGeometry.getGeometry(); + assertTrue(p.getPathCount() == 2); + @SuppressWarnings("unused") + int count = p.getPathCount(); + assertTrue(p.getPointCount() == 8); + assertTrue(p.hasAttribute(VertexDescription.Semantics.Z)); + assertTrue(!p.hasAttribute(VertexDescription.Semantics.M)); + double z = p.getAttributeAsDbl(VertexDescription.Semantics.Z, 0, 0); + assertTrue(z == 3); + double length = p.calculateLength2D(); + assertTrue(Math.abs(length - 54.0) <= 0.001); + SpatialReference spatial_reference = mapGeometry + .getSpatialReference(); + assertTrue(spatial_reference.getID() == 4326); + } + + return bAnswer; + } + + boolean testPolygon() throws JsonParseException, IOException { + boolean bAnswer = true; + + Polygon polygon = new Polygon(); + polygon.startPath(-97.06138, 32.837); + polygon.lineTo(-97.06133, 32.836); + polygon.lineTo(-97.06124, 32.834); + polygon.lineTo(-97.06127, 32.832); + + polygon.startPath(-97.06326, 32.759); + polygon.lineTo(-97.06298, 32.755); + + { + JsonParser polygonPathsWgs84Parser = factory + .createJsonParser(GeometryEngine.geometryToJson( + spatialReferenceWGS84, polygon)); + MapGeometry mPolygonWGS84MP = GeometryEngine + .jsonToGeometry(polygonPathsWgs84Parser); + + assertTrue(polygon.getPointCount() + 1 == ((Polygon) mPolygonWGS84MP + .getGeometry()).getPointCount()); + assertTrue(polygon.getPoint(0).getX() == ((Polygon) mPolygonWGS84MP + .getGeometry()).getPoint(0).getX()); + assertTrue(polygon.getPoint(0).getY() == ((Polygon) mPolygonWGS84MP + .getGeometry()).getPoint(0).getY()); + + assertTrue(polygon.getPathCount() == ((Polygon) mPolygonWGS84MP + .getGeometry()).getPathCount()); + assertTrue(polygon.getSegmentCount() + 1 == ((Polygon) mPolygonWGS84MP + .getGeometry()).getSegmentCount()); + assertTrue(polygon.getSegmentCount(0) == ((Polygon) mPolygonWGS84MP + .getGeometry()).getSegmentCount(0)); + assertTrue(polygon.getSegmentCount(1) + 1 == ((Polygon) mPolygonWGS84MP + .getGeometry()).getSegmentCount(1)); + + int lastIndex = polygon.getPointCount() - 1; + assertTrue(polygon.getPoint(lastIndex).getX() == ((Polygon) mPolygonWGS84MP + .getGeometry()).getPoint(lastIndex).getX()); + assertTrue(polygon.getPoint(lastIndex).getY() == ((Polygon) mPolygonWGS84MP + .getGeometry()).getPoint(lastIndex).getY()); + + assertTrue(spatialReferenceWGS84.getID() == mPolygonWGS84MP + .getSpatialReference().getID()); + + if (!checkResultSpatialRef(mPolygonWGS84MP, 4326, 0)) { + bAnswer = false; + } + } + + { + Polygon p = new Polygon(); + p.addAttribute(VertexDescription.Semantics.Z); + p.addAttribute(VertexDescription.Semantics.M); + String s = GeometryEngine.geometryToJson(spatialReferenceWebMerc1, + p); + assertTrue(s + .equals("{\"hasZ\":true,\"hasM\":true,\"rings\":[],\"spatialReference\":{\"wkid\":102100,\"latestWkid\":3857}}")); + + p.startPath(0, 0); + p.lineTo(0, 1); + p.lineTo(4, 4); + p.startPath(2, 2); + p.lineTo(3, 3); + p.lineTo(7, 8); + + p.setAttribute(VertexDescription.Semantics.Z, 0, 0, 3); + p.setAttribute(VertexDescription.Semantics.M, 1, 0, 7); + p.setAttribute(VertexDescription.Semantics.M, 2, 0, 5); + p.setAttribute(VertexDescription.Semantics.M, 5, 0, 5); + s = GeometryEngine.geometryToJson(spatialReferenceWebMerc1, p); + assertTrue(s + .equals("{\"hasZ\":true,\"hasM\":true,\"rings\":[[[0.0,0.0,3.0,null],[0.0,1.0,0.0,7.0],[4.0,4.0,0.0,5.0],[0.0,0.0,3.0,null]],[[2.0,2.0,0.0,null],[3.0,3.0,0.0,null],[7.0,8.0,0.0,5.0],[2.0,2.0,0.0,null]]],\"spatialReference\":{\"wkid\":102100,\"latestWkid\":3857}}")); + } + + { + // Test Import Polygon from Polygon + String rings = "{\"hasZ\": true, \"rings\" : [ [ [0,0, 5], [0.0, 10.0, 5], [10.0,10.0, 5, 66666], [10.0,0.0, 5] ], [ [12, 12] ], [ [13 , 17], [13 , 17] ], [ [1.0, 1.0, 5, 66666], [9.0,1.0, 5], [9.0,9.0, 5], [1.0,9.0, 5], [1.0, 1.0, 5] ] ] }"; + MapGeometry mapGeometry = GeometryEngine.jsonToGeometry(factory + .createJsonParser(rings)); + Polygon p = (Polygon) mapGeometry.getGeometry(); + @SuppressWarnings("unused") + double area = p.calculateArea2D(); + @SuppressWarnings("unused") + double length = p.calculateLength2D(); + assertTrue(p.getPathCount() == 4); + int count = p.getPointCount(); + assertTrue(count == 15); + assertTrue(p.hasAttribute(VertexDescription.Semantics.Z)); + assertTrue(!p.hasAttribute(VertexDescription.Semantics.M)); + } + + return bAnswer; + } + + boolean testEnvelope() throws JsonParseException, IOException { + boolean bAnswer = true; + + Envelope envelope = new Envelope(); + envelope.setCoords(-109.55, 25.76, -86.39, 49.94); + + { + JsonParser envelopeWGS84Parser = factory + .createJsonParser(GeometryEngine.geometryToJson( + spatialReferenceWGS84, envelope)); + MapGeometry envelopeWGS84MP = GeometryEngine + .jsonToGeometry(envelopeWGS84Parser); + assertTrue(envelope.isEmpty() == envelopeWGS84MP.getGeometry() + .isEmpty()); + assertTrue(envelope.getXMax() == ((Envelope) envelopeWGS84MP + .getGeometry()).getXMax()); + assertTrue(envelope.getYMax() == ((Envelope) envelopeWGS84MP + .getGeometry()).getYMax()); + assertTrue(envelope.getXMin() == ((Envelope) envelopeWGS84MP + .getGeometry()).getXMin()); + assertTrue(envelope.getYMin() == ((Envelope) envelopeWGS84MP + .getGeometry()).getYMin()); + assertTrue(spatialReferenceWGS84.getID() == envelopeWGS84MP + .getSpatialReference().getID()); + if (!checkResultSpatialRef(envelopeWGS84MP, 4326, 0)) { + bAnswer = false; + } + } + + {// export + Envelope e = new Envelope(); + e.addAttribute(VertexDescription.Semantics.Z); + e.addAttribute(VertexDescription.Semantics.M); + String s = GeometryEngine.geometryToJson(spatialReferenceWebMerc1, + e); + assertTrue(s + .equals("{\"xmin\":null,\"ymin\":null,\"xmax\":null,\"ymax\":null,\"zmin\":null,\"zmax\":null,\"mmin\":null,\"mmax\":null,\"spatialReference\":{\"wkid\":102100,\"latestWkid\":3857}}")); + + e.setCoords(0, 1, 2, 3); + + Envelope1D z = new Envelope1D(); + Envelope1D m = new Envelope1D(); + z.setCoords(5, 7); + m.setCoords(11, 13); + + e.setInterval(VertexDescription.Semantics.Z, 0, z); + e.setInterval(VertexDescription.Semantics.M, 0, m); + s = GeometryEngine.geometryToJson(spatialReferenceWebMerc1, e); + assertTrue(s + .equals("{\"xmin\":0.0,\"ymin\":1.0,\"xmax\":2.0,\"ymax\":3.0,\"zmin\":5.0,\"zmax\":7.0,\"mmin\":11.0,\"mmax\":13.0,\"spatialReference\":{\"wkid\":102100,\"latestWkid\":3857}}")); + } + + {// import + String s = "{\"xmin\":0.0,\"ymin\":1.0,\"xmax\":2.0,\"ymax\":3.0,\"zmin\":5.0,\"zmax\":7.0,\"mmin\":11.0,\"mmax\":13.0,\"spatialReference\":{\"wkid\":102100,\"latestWkid\":3857}}"; + JsonParser parser = factory.createJsonParser(s); + MapGeometry map_env = GeometryEngine.jsonToGeometry(parser); + Envelope env = (Envelope) map_env.getGeometry(); + Envelope1D z = env.queryInterval(VertexDescription.Semantics.Z, 0); + Envelope1D m = env.queryInterval(VertexDescription.Semantics.M, 0); + assertTrue(z.vmin == 5.0); + assertTrue(z.vmax == 7.0); + assertTrue(m.vmin == 11.0); + assertTrue(m.vmax == 13.0); + } + + { + String s = "{ \"zmin\" : 33, \"xmin\" : -109.55, \"zmax\" : 53, \"ymin\" : 25.76, \"xmax\" : -86.39, \"ymax\" : 49.94, \"mmax\" : 13}"; + JsonParser parser = factory.createJsonParser(s); + MapGeometry map_env = GeometryEngine.jsonToGeometry(parser); + Envelope env = (Envelope) map_env.getGeometry(); + Envelope2D e = new Envelope2D(); + env.queryEnvelope2D(e); + assertTrue(e.xmin == -109.55 && e.ymin == 25.76 && e.xmax == -86.39 + && e.ymax == 49.94); + + Envelope1D e1D; + assertTrue(env.hasAttribute(VertexDescription.Semantics.Z)); + e1D = env.queryInterval(VertexDescription.Semantics.Z, 0); + assertTrue(e1D.vmin == 33 && e1D.vmax == 53); + + assertTrue(!env.hasAttribute(VertexDescription.Semantics.M)); + } + + return bAnswer; + } + + boolean testCR181369() throws JsonParseException, IOException { + // CR181369 + boolean bAnswer = true; + + String jsonStringPointAndWKT = "{\"x\":10.0,\"y\":20.0,\"spatialReference\":{\"wkt\" : \"PROJCS[\\\"NAD83_UTM_zone_15N\\\",GEOGCS[\\\"GCS_North_American_1983\\\",DATUM[\\\"D_North_American_1983\\\",SPHEROID[\\\"GRS_1980\\\",6378137.0,298.257222101]],PRIMEM[\\\"Greenwich\\\",0.0],UNIT[\\\"Degree\\\",0.0174532925199433]],PROJECTION[\\\"Transverse_Mercator\\\"],PARAMETER[\\\"false_easting\\\",500000.0],PARAMETER[\\\"false_northing\\\",0.0],PARAMETER[\\\"central_meridian\\\",-93.0],PARAMETER[\\\"scale_factor\\\",0.9996],PARAMETER[\\\"latitude_of_origin\\\",0.0],UNIT[\\\"Meter\\\",1.0]]\"} }"; + JsonParser jsonParserPointAndWKT = factory + .createJsonParser(jsonStringPointAndWKT); + MapGeometry mapGeom2 = GeometryEngine + .jsonToGeometry(jsonParserPointAndWKT); + String jsonStringPointAndWKT2 = GeometryEngine.geometryToJson( + mapGeom2.getSpatialReference(), mapGeom2.getGeometry()); + JsonParser jsonParserPointAndWKT2 = factory + .createJsonParser(jsonStringPointAndWKT2); + MapGeometry mapGeom3 = GeometryEngine + .jsonToGeometry(jsonParserPointAndWKT2); + assertTrue(((Point) mapGeom2.getGeometry()).getX() == ((Point) mapGeom3 + .getGeometry()).getX()); + assertTrue(((Point) mapGeom2.getGeometry()).getY() == ((Point) mapGeom3 + .getGeometry()).getY()); + + String s1 = mapGeom2.getSpatialReference().getText(); + String s2 = mapGeom3.getSpatialReference().getText(); + assertTrue(s1.equals(s2)); + + int id2 = mapGeom2.getSpatialReference().getID(); + int id3 = mapGeom3.getSpatialReference().getID(); + assertTrue(id2 == id3); + if (!checkResultSpatialRef(mapGeom3, mapGeom2.getSpatialReference() + .getID(), 0)) { + bAnswer = false; + } + return bAnswer; + } + + boolean checkResultSpatialRef(MapGeometry mapGeometry, int expectWki1, + int expectWki2) { + SpatialReference sr = mapGeometry.getSpatialReference(); + String Wkt = sr.getText(); + int wki1 = sr.getLatestID(); + if (!(wki1 == expectWki1 || wki1 == expectWki2)) + return false; + if (!(Wkt != null && Wkt.length() > 0)) + return false; + System.out.println("WKT1: " + Wkt); + SpatialReference sr2 = SpatialReference.create(Wkt); + int wki2 = sr2.getID(); + if (expectWki2 > 0) { + if (!(wki2 == expectWki1 || wki2 == expectWki2)) + return false; + } else { + if (!(wki2 == expectWki1)) + return false; + } + return true; + } +} diff --git a/unittest/com/esri/core/geometry/TestImportExport.java b/unittest/com/esri/core/geometry/TestImportExport.java new file mode 100644 index 00000000..695be0cb --- /dev/null +++ b/unittest/com/esri/core/geometry/TestImportExport.java @@ -0,0 +1,1929 @@ +package com.esri.core.geometry; + +import com.vividsolutions.jts.io.WKBReader; +import com.vividsolutions.jts.io.WKBWriter; +import com.vividsolutions.jts.io.WKTWriter; +import com.vividsolutions.jts.io.WKTReader; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import junit.framework.TestCase; +import org.json.JSONException; +import org.junit.Test; + +public class TestImportExport extends TestCase { + @Override + protected void setUp() throws Exception { + super.setUp(); + } + + @Override + protected void tearDown() throws Exception { + super.tearDown(); + } + + @Test + public static void testImportExportShapePolygon() { + OperatorExportToESRIShape exporterShape = (OperatorExportToESRIShape) OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.ExportToESRIShape); + OperatorImportFromESRIShape importerShape = (OperatorImportFromESRIShape) OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.ImportFromESRIShape); + + Polygon polygon = makePolygon(); + + byte[] esriShape = GeometryEngine.geometryToEsriShape(polygon); + Geometry imported = GeometryEngine.geometryFromEsriShape(esriShape, + Geometry.Type.Unknown); + TestCommonMethods.compareGeometryContent((MultiPath) imported, polygon); + + // Test Import Polygon from Polygon + ByteBuffer polygonShapeBuffer = exporterShape.execute(0, polygon); + Geometry polygonShapeGeometry = importerShape.execute(0, + Geometry.Type.Polygon, polygonShapeBuffer); + + TestCommonMethods.compareGeometryContent( + (MultiPath) polygonShapeGeometry, polygon); + + // Test Import Envelope from Polygon + Geometry envelopeShapeGeometry = importerShape.execute(0, + Geometry.Type.Envelope, polygonShapeBuffer); + Envelope envelope = (Envelope) envelopeShapeGeometry; + + @SuppressWarnings("unused") + Envelope env = new Envelope(), otherenv = new Envelope(); + polygon.queryEnvelope(otherenv); + assertTrue(envelope.getXMin() == otherenv.getXMin()); + assertTrue(envelope.getXMax() == otherenv.getXMax()); + assertTrue(envelope.getYMin() == otherenv.getYMin()); + assertTrue(envelope.getYMax() == otherenv.getYMax()); + + Envelope1D interval, otherinterval; + interval = envelope.queryInterval(VertexDescription.Semantics.Z, 0); + otherinterval = polygon.queryInterval(VertexDescription.Semantics.Z, 0); + assertTrue(interval.vmin == otherinterval.vmin); + assertTrue(interval.vmax == otherinterval.vmax); + } + + @Test + public static void testImportExportShapePolyline() { + OperatorExportToESRIShape exporterShape = (OperatorExportToESRIShape) OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.ExportToESRIShape); + OperatorImportFromESRIShape importerShape = (OperatorImportFromESRIShape) OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.ImportFromESRIShape); + + Polyline polyline = makePolyline(); + + // Test Import Polyline from Polyline + ByteBuffer polylineShapeBuffer = exporterShape.execute(0, polyline); + Geometry polylineShapeGeometry = importerShape.execute(0, + Geometry.Type.Polyline, polylineShapeBuffer); + + // TODO test this + TestCommonMethods.compareGeometryContent( + (MultiPath) polylineShapeGeometry, polyline); + + // Test Import Envelope from Polyline; + Geometry envelopeShapeGeometry = importerShape.execute(0, + Geometry.Type.Envelope, polylineShapeBuffer); + Envelope envelope = (Envelope) envelopeShapeGeometry; + + Envelope env = new Envelope(), otherenv = new Envelope(); + envelope.queryEnvelope(env); + polyline.queryEnvelope(otherenv); + assertTrue(env.getXMin() == otherenv.getXMin()); + assertTrue(env.getXMax() == otherenv.getXMax()); + assertTrue(env.getYMin() == otherenv.getYMin()); + assertTrue(env.getYMax() == otherenv.getYMax()); + + Envelope1D interval, otherinterval; + interval = envelope.queryInterval(VertexDescription.Semantics.Z, 0); + otherinterval = polyline + .queryInterval(VertexDescription.Semantics.Z, 0); + assertTrue(interval.vmin == otherinterval.vmin); + assertTrue(interval.vmax == otherinterval.vmax); + } + + @Test + public static void testImportExportShapeMultiPoint() { + OperatorExportToESRIShape exporterShape = (OperatorExportToESRIShape) OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.ExportToESRIShape); + OperatorImportFromESRIShape importerShape = (OperatorImportFromESRIShape) OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.ImportFromESRIShape); + + MultiPoint multipoint = makeMultiPoint(); + + // Test Import MultiPoint from MultiPoint + ByteBuffer multipointShapeBuffer = exporterShape.execute(0, multipoint); + MultiPoint multipointShapeGeometry = (MultiPoint) importerShape + .execute(0, Geometry.Type.MultiPoint, multipointShapeBuffer); + + TestCommonMethods.compareGeometryContent( + (MultiPoint) multipointShapeGeometry, multipoint); + + // Test Import Envelope from MultiPoint + Geometry envelopeShapeGeometry = importerShape.execute(0, + Geometry.Type.Envelope, multipointShapeBuffer); + Envelope envelope = (Envelope) envelopeShapeGeometry; + + Envelope env = new Envelope(), otherenv = new Envelope(); + envelope.queryEnvelope(env); + multipoint.queryEnvelope(otherenv); + assertTrue(env.getXMin() == otherenv.getXMin()); + assertTrue(env.getXMax() == otherenv.getXMax()); + assertTrue(env.getYMin() == otherenv.getYMin()); + assertTrue(env.getYMax() == otherenv.getYMax()); + + Envelope1D interval, otherinterval; + interval = envelope.queryInterval(VertexDescription.Semantics.Z, 0); + otherinterval = multipoint.queryInterval(VertexDescription.Semantics.Z, + 0); + assertTrue(interval.vmin == otherinterval.vmin); + assertTrue(interval.vmax == otherinterval.vmax); + + interval = envelope.queryInterval(VertexDescription.Semantics.ID, 0); + otherinterval = multipoint.queryInterval( + VertexDescription.Semantics.ID, 0); + assertTrue(interval.vmin == otherinterval.vmin); + assertTrue(interval.vmax == otherinterval.vmax); + } + + @Test + public static void testImportExportShapePoint() { + OperatorExportToESRIShape exporterShape = (OperatorExportToESRIShape) OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.ExportToESRIShape); + OperatorImportFromESRIShape importerShape = (OperatorImportFromESRIShape) OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.ImportFromESRIShape); + + // Point + Point point = makePoint(); + + // Test Import Point from Point + ByteBuffer pointShapeBuffer = exporterShape.execute(0, point); + Point pointShapeGeometry = (Point) importerShape.execute(0, + Geometry.Type.Point, pointShapeBuffer); + + double x1 = point.getX(); + double x2 = pointShapeGeometry.getX(); + assertTrue(x1 == x2); + + double y1 = point.getY(); + double y2 = pointShapeGeometry.getY(); + assertTrue(y1 == y2); + + double z1 = point.getZ(); + double z2 = pointShapeGeometry.getZ(); + assertTrue(z1 == z2); + + double m1 = point.getM(); + double m2 = pointShapeGeometry.getM(); + assertTrue(m1 == m2); + + int id1 = point.getID(); + int id2 = pointShapeGeometry.getID(); + assertTrue(id1 == id2); + + // Test Import Multipoint from Point + MultiPoint multipointShapeGeometry = (MultiPoint) importerShape + .execute(0, Geometry.Type.MultiPoint, pointShapeBuffer); + Point point2d = multipointShapeGeometry.getPoint(0); + assertTrue(x1 == point2d.getX() && y1 == point2d.getY()); + + int pointCount = multipointShapeGeometry.getPointCount(); + assertTrue(pointCount == 1); + + z2 = multipointShapeGeometry.getAttributeAsDbl( + VertexDescription.Semantics.Z, 0, 0); + assertTrue(z1 == z2); + + m2 = multipointShapeGeometry.getAttributeAsDbl( + VertexDescription.Semantics.M, 0, 0); + assertTrue(m1 == m2); + + id2 = multipointShapeGeometry.getAttributeAsInt( + VertexDescription.Semantics.ID, 0, 0); + assertTrue(id1 == id2); + + // Test Import Envelope from Point + Geometry envelopeShapeGeometry = importerShape.execute(0, + Geometry.Type.Envelope, pointShapeBuffer); + Envelope envelope = (Envelope) envelopeShapeGeometry; + + Envelope env = new Envelope(), otherenv = new Envelope(); + envelope.queryEnvelope(env); + point.queryEnvelope(otherenv); + assertTrue(env.getXMin() == otherenv.getXMin()); + assertTrue(env.getXMax() == otherenv.getXMax()); + assertTrue(env.getYMin() == otherenv.getYMin()); + assertTrue(env.getYMax() == otherenv.getYMax()); + + Envelope1D interval, otherinterval; + interval = envelope.queryInterval(VertexDescription.Semantics.Z, 0); + otherinterval = point.queryInterval(VertexDescription.Semantics.Z, 0); + assertTrue(interval.vmin == otherinterval.vmin); + assertTrue(interval.vmax == otherinterval.vmax); + + interval = envelope.queryInterval(VertexDescription.Semantics.ID, 0); + otherinterval = point.queryInterval(VertexDescription.Semantics.ID, 0); + assertTrue(interval.vmin == otherinterval.vmin); + assertTrue(interval.vmax == otherinterval.vmax); + } + + @Test + public static void testImportExportShapeEnvelope() { + OperatorExportToESRIShape exporterShape = (OperatorExportToESRIShape) OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.ExportToESRIShape); + OperatorImportFromESRIShape importerShape = (OperatorImportFromESRIShape) OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.ImportFromESRIShape); + + // Test Export Envelope to Polygon + Envelope envelope = makeEnvelope(); + + ByteBuffer polygonShapeBuffer = exporterShape.execute(0, envelope); + Polygon polygon = (Polygon) importerShape.execute(0, + Geometry.Type.Polygon, polygonShapeBuffer); + int pointCount = polygon.getPointCount(); + assertTrue(pointCount == 4); + + Envelope env = new Envelope(); + + envelope.queryEnvelope(env); + // interval = envelope.queryInterval(VertexDescription.Semantics.Z, 0); + Point point3d; + point3d = polygon.getPoint(0); + assertTrue(point3d.getX() == env.getXMin() + && point3d.getY() == env.getYMin());// && point3d.z == + // interval.vmin); + point3d = polygon.getPoint(1); + assertTrue(point3d.getX() == env.getXMin() + && point3d.getY() == env.getYMax());// && point3d.z == + // interval.vmax); + point3d = polygon.getPoint(2); + assertTrue(point3d.getX() == env.getXMax() + && point3d.getY() == env.getYMax());// && point3d.z == + // interval.vmin); + point3d = polygon.getPoint(3); + assertTrue(point3d.getX() == env.getXMax() + && point3d.getY() == env.getYMin());// && point3d.z == + // interval.vmax); + + Envelope1D interval; + interval = envelope.queryInterval(VertexDescription.Semantics.M, 0); + double m = polygon.getAttributeAsDbl(VertexDescription.Semantics.M, 0, + 0); + assertTrue(m == interval.vmin); + m = polygon.getAttributeAsDbl(VertexDescription.Semantics.M, 1, 0); + assertTrue(m == interval.vmax); + m = polygon.getAttributeAsDbl(VertexDescription.Semantics.M, 2, 0); + assertTrue(m == interval.vmin); + m = polygon.getAttributeAsDbl(VertexDescription.Semantics.M, 3, 0); + assertTrue(m == interval.vmax); + + interval = envelope.queryInterval(VertexDescription.Semantics.ID, 0); + double id = polygon.getAttributeAsDbl(VertexDescription.Semantics.ID, + 0, 0); + assertTrue(id == interval.vmin); + id = polygon.getAttributeAsDbl(VertexDescription.Semantics.ID, 1, 0); + assertTrue(id == interval.vmax); + id = polygon.getAttributeAsDbl(VertexDescription.Semantics.ID, 2, 0); + assertTrue(id == interval.vmin); + id = polygon.getAttributeAsDbl(VertexDescription.Semantics.ID, 3, 0); + assertTrue(id == interval.vmax); + } + + @Test + public static void testImportExportWkbGeometryCollection() { + OperatorImportFromWkb importerWKB = (OperatorImportFromWkb) OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.ImportFromWkb); + + int offset = 0; + ByteBuffer wkbBuffer = ByteBuffer.allocate(600).order( + ByteOrder.nativeOrder()); + wkbBuffer.put(offset, (byte) WkbByteOrder.wkbNDR); + offset += 1; // byte order + wkbBuffer.putInt(offset, WkbGeometryType.wkbGeometryCollection); + offset += 4; // type + wkbBuffer.putInt(offset, 3); // 3 geometries + offset += 4; + wkbBuffer.put(offset, (byte) WkbByteOrder.wkbNDR); + offset += 1; + wkbBuffer.putInt(offset, WkbGeometryType.wkbPoint); + offset += 4; + wkbBuffer.putDouble(offset, 0); + offset += 8; + wkbBuffer.putDouble(offset, 0); + offset += 8; + wkbBuffer.put(offset, (byte) WkbByteOrder.wkbNDR); + offset += 1; // byte order + wkbBuffer.putInt(offset, WkbGeometryType.wkbGeometryCollection); + offset += 4; // type + wkbBuffer.putInt(offset, 7); // 7 empty geometries + offset += 4; + wkbBuffer.put(offset, (byte) WkbByteOrder.wkbNDR); + offset += 1; + wkbBuffer.putInt(offset, WkbGeometryType.wkbLineString); + offset += 4; + wkbBuffer.putInt(offset, 0); // 0 points, for empty linestring + offset += 4; + wkbBuffer.put(offset, (byte) WkbByteOrder.wkbNDR); + offset += 1; + wkbBuffer.putInt(offset, WkbGeometryType.wkbPolygon); + offset += 4; + wkbBuffer.putInt(offset, 0); // 0 points, for empty polygon + offset += 4; + wkbBuffer.put(offset, (byte) WkbByteOrder.wkbNDR); + offset += 1; + wkbBuffer.putInt(offset, WkbGeometryType.wkbMultiPolygon); + offset += 4; + wkbBuffer.putInt(offset, 0); // 0 points, for empty multipolygon + offset += 4; + wkbBuffer.put(offset, (byte) WkbByteOrder.wkbNDR); + offset += 1; + wkbBuffer.putInt(offset, WkbGeometryType.wkbMultiLineString); + offset += 4; + wkbBuffer.putInt(offset, 0); // 0 points, for empty multilinestring + offset += 4; + wkbBuffer.put(offset, (byte) WkbByteOrder.wkbNDR); + offset += 1; + wkbBuffer.putInt(offset, WkbGeometryType.wkbGeometryCollection); + offset += 4; + wkbBuffer.putInt(offset, 0); // 0 geometries, for empty + // geometrycollection + offset += 4; + wkbBuffer.put(offset, (byte) WkbByteOrder.wkbNDR); + offset += 1; + wkbBuffer.putInt(offset, WkbGeometryType.wkbMultiPoint); + offset += 4; + wkbBuffer.putInt(offset, 0); // 0 points, for empty multipoint + offset += 4; + wkbBuffer.put(offset, (byte) WkbByteOrder.wkbNDR); + offset += 1; + wkbBuffer.putInt(offset, WkbGeometryType.wkbPoint); + offset += 4; + wkbBuffer.putDouble(offset, 66); + offset += 8; + wkbBuffer.putDouble(offset, 88); + offset += 8; + wkbBuffer.put(offset, (byte) WkbByteOrder.wkbNDR); + offset += 1; + wkbBuffer.putInt(offset, WkbGeometryType.wkbPoint); + offset += 4; + wkbBuffer.putDouble(offset, 13); + offset += 8; + wkbBuffer.putDouble(offset, 17); + offset += 8; + + // "GeometryCollection( Point (0 0), GeometryCollection( LineString empty, Polygon empty, MultiPolygon empty, MultiLineString empty, MultiPoint empty ), Point (13 17) )"; + OGCStructure structure = importerWKB.executeOGC(0, wkbBuffer, null).m_structures + .get(0); + + assertTrue(structure.m_type == 7); + assertTrue(structure.m_structures.get(0).m_type == 1); + assertTrue(structure.m_structures.get(1).m_type == 7); + assertTrue(structure.m_structures.get(2).m_type == 1); + + assertTrue(structure.m_structures.get(1).m_structures.get(0).m_type == 2); + assertTrue(structure.m_structures.get(1).m_structures.get(1).m_type == 3); + assertTrue(structure.m_structures.get(1).m_structures.get(2).m_type == 6); + assertTrue(structure.m_structures.get(1).m_structures.get(3).m_type == 5); + assertTrue(structure.m_structures.get(1).m_structures.get(4).m_type == 7); + assertTrue(structure.m_structures.get(1).m_structures.get(5).m_type == 4); + assertTrue(structure.m_structures.get(1).m_structures.get(6).m_type == 1); + + Point p = (Point) structure.m_structures.get(1).m_structures.get(6).m_geometry; + assertTrue(p.getX() == 66); + assertTrue(p.getY() == 88); + + p = (Point) structure.m_structures.get(2).m_geometry; + assertTrue(p.getX() == 13); + assertTrue(p.getY() == 17); + } + + @Test + public static void testImportExportWKBPolygon() { + OperatorExportToWkb exporterWKB = (OperatorExportToWkb) OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.ExportToWkb); + OperatorExportToWkt exporterWKT = (OperatorExportToWkt) OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.ExportToWkt); + OperatorImportFromWkb importerWKB = (OperatorImportFromWkb) OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.ImportFromWkb); + + // Test Import Polygon with bad rings + int offset = 0; + ByteBuffer wkbBuffer = ByteBuffer.allocate(500).order( + ByteOrder.nativeOrder()); + wkbBuffer.put(offset, (byte) WkbByteOrder.wkbNDR); + offset += 1; // byte order + wkbBuffer.putInt(offset, WkbGeometryType.wkbPolygon); + offset += 4; // type + wkbBuffer.putInt(offset, 8); + offset += 4; // num rings + wkbBuffer.putInt(offset, 4); + offset += 4; // num points + wkbBuffer.putDouble(offset, 0.0); + offset += 8; // x + wkbBuffer.putDouble(offset, 0.0); + offset += 8; // y + wkbBuffer.putDouble(offset, 0.0); + offset += 8; // x + wkbBuffer.putDouble(offset, 10.0); + offset += 8; // y + wkbBuffer.putDouble(offset, 10.0); + offset += 8; // x + wkbBuffer.putDouble(offset, 10.0); + offset += 8; // y + wkbBuffer.putDouble(offset, 0.0); + offset += 8; // x + wkbBuffer.putDouble(offset, 0.0); + offset += 8; // y + wkbBuffer.putInt(offset, 1); + offset += 4; // num points + wkbBuffer.putDouble(offset, 36.0); + offset += 8; // x + wkbBuffer.putDouble(offset, 17.0); + offset += 8; // y + wkbBuffer.putInt(offset, 2); + offset += 4; // num points + wkbBuffer.putDouble(offset, 19.0); + offset += 8; // x + wkbBuffer.putDouble(offset, 19.0); + offset += 8; // y + wkbBuffer.putDouble(offset, -19.0); + offset += 8; // x + wkbBuffer.putDouble(offset, -19.0); + offset += 8; // y + wkbBuffer.putInt(offset, 4); + offset += 4; // num points + wkbBuffer.putDouble(offset, 23.0); + offset += 8; // x + wkbBuffer.putDouble(offset, 88); + offset += 8; // y + wkbBuffer.putDouble(offset, 13.0); + offset += 8; // x + wkbBuffer.putDouble(offset, 43.0); + offset += 8; // y + wkbBuffer.putDouble(offset, 59.0); + offset += 8; // x + wkbBuffer.putDouble(offset, 79.0); + offset += 8; // y + wkbBuffer.putDouble(offset, 83.0); + offset += 8; // x + wkbBuffer.putDouble(offset, 87.0); + offset += 8; // y + wkbBuffer.putInt(offset, 3); + offset += 4; // num points + wkbBuffer.putDouble(offset, 23.0); + offset += 8; // x + wkbBuffer.putDouble(offset, 88); + offset += 8; // y + wkbBuffer.putDouble(offset, 88); + offset += 8; // x + wkbBuffer.putDouble(offset, 43.0); + offset += 8; // y + wkbBuffer.putDouble(offset, 67.0); + offset += 8; // x + wkbBuffer.putDouble(offset, 79.0); + offset += 8; // y + wkbBuffer.putInt(offset, 0); + offset += 4; // num points + wkbBuffer.putInt(offset, 3); + offset += 4; // num points + wkbBuffer.putDouble(offset, 23.0); + offset += 8; // x + wkbBuffer.putDouble(offset, 88); + offset += 8; // y + wkbBuffer.putDouble(offset, 88); + offset += 8; // x + wkbBuffer.putDouble(offset, 43.0); + offset += 8; // y + wkbBuffer.putDouble(offset, 67.0); + offset += 8; // x + wkbBuffer.putDouble(offset, 88); + offset += 8; // y + wkbBuffer.putInt(offset, 2); + offset += 4; // num points + wkbBuffer.putDouble(offset, 23.0); + offset += 8; // x + wkbBuffer.putDouble(offset, 67.0); + offset += 8; // y + wkbBuffer.putDouble(offset, 43.0); + offset += 8; // x + wkbBuffer.putDouble(offset, 67.0); + offset += 8; // y + + Geometry p = importerWKB.execute(0, Geometry.Type.Polygon, wkbBuffer, + null); + int pc = ((Polygon) p).getPathCount(); + String wktString = exporterWKT.execute(0, p, null); + assertTrue(wktString + .equals("MULTIPOLYGON (((0 0, 10 10, 0 10, 0 0), (36 17, 36 17, 36 17), (19 19, -19 -19, 19 19), (23 88, 83 87, 59 79, 13 43, 23 88), (23 88, 67 79, 88 43, 23 88), (23 88, 67 88, 88 43, 23 88), (23 67, 43 67, 23 67)))")); + + wktString = exporterWKT.execute(WktExportFlags.wktExportPolygon, p, + null); + assertTrue(wktString + .equals("POLYGON ((0 0, 10 10, 0 10, 0 0), (36 17, 36 17, 36 17), (19 19, -19 -19, 19 19), (23 88, 83 87, 59 79, 13 43, 23 88), (23 88, 67 79, 88 43, 23 88), (23 88, 67 88, 88 43, 23 88), (23 67, 43 67, 23 67))")); + + Polygon polygon = makePolygon(); + + // Test Import Polygon from Polygon8 + ByteBuffer polygonWKBBuffer = exporterWKB.execute(0, polygon, null); + int wkbType = polygonWKBBuffer.getInt(1); + assertTrue(wkbType == WkbGeometryType.wkbMultiPolygonZM); + Geometry polygonWKBGeometry = importerWKB.execute(0, + Geometry.Type.Polygon, polygonWKBBuffer, null); + TestCommonMethods.compareGeometryContent( + (MultiVertexGeometry) polygonWKBGeometry, polygon); + + // Test WKB_export_multi_polygon on nonempty single part polygon + Polygon polygon2 = makePolygon2(); + assertTrue(polygon2.getPathCount() == 1); + polygonWKBBuffer = exporterWKB.execute( + WkbExportFlags.wkbExportMultiPolygon, polygon2, null); + polygonWKBGeometry = importerWKB.execute(0, Geometry.Type.Polygon, + polygonWKBBuffer, null); + TestCommonMethods.compareGeometryContent( + (MultiVertexGeometry) polygonWKBGeometry, polygon2); + wkbType = polygonWKBBuffer.getInt(1); + assertTrue(wkbType == WkbGeometryType.wkbMultiPolygonZM); + + // Test WKB_export_polygon on nonempty single part polygon + assertTrue(polygon2.getPathCount() == 1); + polygonWKBBuffer = exporterWKB.execute(WkbExportFlags.wkbExportPolygon, + polygon2, null); + polygonWKBGeometry = importerWKB.execute(0, Geometry.Type.Polygon, + polygonWKBBuffer, null); + TestCommonMethods.compareGeometryContent( + (MultiVertexGeometry) polygonWKBGeometry, polygon2); + wkbType = polygonWKBBuffer.getInt(1); + assertTrue(wkbType == WkbGeometryType.wkbPolygonZM); + + // Test WKB_export_polygon on empty polygon + Polygon polygon3 = new Polygon(); + polygonWKBBuffer = exporterWKB.execute(WkbExportFlags.wkbExportPolygon, + polygon3, null); + polygonWKBGeometry = importerWKB.execute(0, Geometry.Type.Polygon, + polygonWKBBuffer, null); + assertTrue(polygonWKBGeometry.isEmpty() == true); + wkbType = polygonWKBBuffer.getInt(1); + assertTrue(wkbType == WkbGeometryType.wkbPolygon); + + // Test WKB_export_defaults on empty polygon + polygonWKBBuffer = exporterWKB.execute(0, polygon3, null); + polygonWKBGeometry = importerWKB.execute(0, Geometry.Type.Polygon, + polygonWKBBuffer, null); + assertTrue(polygonWKBGeometry.isEmpty() == true); + wkbType = polygonWKBBuffer.getInt(1); + assertTrue(wkbType == WkbGeometryType.wkbMultiPolygon); + } + + @Test + public static void testImportExportWKBPolyline() { + OperatorExportToWkb exporterWKB = (OperatorExportToWkb) OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.ExportToWkb); + OperatorExportToWkt exporterWKT = (OperatorExportToWkt) OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.ExportToWkt); + OperatorImportFromWkb importerWKB = (OperatorImportFromWkb) OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.ImportFromWkb); + + // Test Import Polyline with bad paths (i.e. paths with one point or + // zero points) + int offset = 0; + ByteBuffer wkbBuffer = ByteBuffer.allocate(500).order( + ByteOrder.nativeOrder()); + wkbBuffer.put(offset, (byte) WkbByteOrder.wkbNDR); + offset += 1; // byte order + wkbBuffer.putInt(offset, WkbGeometryType.wkbMultiLineString); + offset += 4; // type + wkbBuffer.putInt(offset, 4); + offset += 4; // num paths + wkbBuffer.put(offset, (byte) WkbByteOrder.wkbNDR); + offset += 1; // byte order + wkbBuffer.putInt(offset, WkbGeometryType.wkbLineString); + offset += 4; // type + wkbBuffer.putInt(offset, 1); + offset += 4; // num points + wkbBuffer.putDouble(offset, 36.0); + offset += 8; // x + wkbBuffer.putDouble(offset, 17.0); + offset += 8; // y + wkbBuffer.put(offset, (byte) WkbByteOrder.wkbNDR); + offset += 1; // byte order + wkbBuffer.putInt(offset, WkbGeometryType.wkbLineString); + offset += 4; // type + wkbBuffer.putInt(offset, 0); + offset += 4; // num points + wkbBuffer.put(offset, (byte) WkbByteOrder.wkbNDR); + offset += 1; // byte order + wkbBuffer.putInt(offset, WkbGeometryType.wkbLineString); + offset += 4; // type + wkbBuffer.putInt(offset, 1); + offset += 4; // num points + wkbBuffer.putDouble(offset, 19.0); + offset += 8; // x + wkbBuffer.putDouble(offset, 19.0); + offset += 8; // y + wkbBuffer.put(offset, (byte) WkbByteOrder.wkbNDR); + offset += 1; // byte order + wkbBuffer.putInt(offset, WkbGeometryType.wkbLineString); + offset += 4; // type + wkbBuffer.putInt(offset, 3); + offset += 4; // num points + wkbBuffer.putDouble(offset, 88); + offset += 8; // x + wkbBuffer.putDouble(offset, 29.0); + offset += 8; // y + wkbBuffer.putDouble(offset, 13.0); + offset += 8; // x + wkbBuffer.putDouble(offset, 43.0); + offset += 8; // y + wkbBuffer.putDouble(offset, 59.0); + offset += 8; // x + wkbBuffer.putDouble(offset, 88); + offset += 8; // y + + Polyline p = (Polyline) (importerWKB.execute(0, Geometry.Type.Polyline, + wkbBuffer, null)); + int pc = p.getPointCount(); + int pac = p.getPathCount(); + assertTrue(p.getPointCount() == 7); + assertTrue(p.getPathCount() == 3); + + String wktString = exporterWKT.execute(0, p, null); + assertTrue(wktString + .equals("MULTILINESTRING ((36 17, 36 17), (19 19, 19 19), (88 29, 13 43, 59 88))")); + + Polyline polyline = makePolyline(); + polyline.dropAttribute(VertexDescription.Semantics.ID); + + // Test Import Polyline from Polyline + ByteBuffer polylineWKBBuffer = exporterWKB.execute(0, polyline, null); + int wkbType = polylineWKBBuffer.getInt(1); + assertTrue(wkbType == WkbGeometryType.wkbMultiLineStringZM); + Geometry polylineWKBGeometry = importerWKB.execute(0, + Geometry.Type.Polyline, polylineWKBBuffer, null); + TestCommonMethods.compareGeometryContent( + (MultiVertexGeometry) polylineWKBGeometry, polyline); + + // Test wkbExportMultiPolyline on nonempty single part polyline + Polyline polyline2 = makePolyline2(); + assertTrue(polyline2.getPathCount() == 1); + polylineWKBBuffer = exporterWKB.execute( + WkbExportFlags.wkbExportMultiLineString, polyline2, null); + polylineWKBGeometry = importerWKB.execute(0, Geometry.Type.Polyline, + polylineWKBBuffer, null); + TestCommonMethods.compareGeometryContent( + (MultiVertexGeometry) polylineWKBGeometry, polyline2); + wkbType = polylineWKBBuffer.getInt(1); + assertTrue(wkbType == WkbGeometryType.wkbMultiLineStringZM); + + // Test wkbExportPolyline on nonempty single part polyline + assertTrue(polyline2.getPathCount() == 1); + polylineWKBBuffer = exporterWKB.execute( + WkbExportFlags.wkbExportLineString, polyline2, null); + polylineWKBGeometry = importerWKB.execute(0, Geometry.Type.Polyline, + polylineWKBBuffer, null); + TestCommonMethods.compareGeometryContent( + (MultiVertexGeometry) polylineWKBGeometry, polyline2); + wkbType = polylineWKBBuffer.getInt(1); + assertTrue(wkbType == WkbGeometryType.wkbLineStringZM); + + // Test wkbExportPolyline on empty polyline + Polyline polyline3 = new Polyline(); + polylineWKBBuffer = exporterWKB.execute( + WkbExportFlags.wkbExportLineString, polyline3, null); + polylineWKBGeometry = importerWKB.execute(0, Geometry.Type.Polyline, + polylineWKBBuffer, null); + assertTrue(polylineWKBGeometry.isEmpty() == true); + wkbType = polylineWKBBuffer.getInt(1); + assertTrue(wkbType == WkbGeometryType.wkbLineString); + + // Test WKB_export_defaults on empty polyline + polylineWKBBuffer = exporterWKB.execute(0, polyline3, null); + polylineWKBGeometry = importerWKB.execute(0, Geometry.Type.Polyline, + polylineWKBBuffer, null); + assertTrue(polylineWKBGeometry.isEmpty() == true); + wkbType = polylineWKBBuffer.getInt(1); + assertTrue(wkbType == WkbGeometryType.wkbMultiLineString); + } + + @Test + public static void testImportExportWKBMultiPoint() { + OperatorExportToWkb exporterWKB = (OperatorExportToWkb) OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.ExportToWkb); + OperatorImportFromWkb importerWKB = (OperatorImportFromWkb) OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.ImportFromWkb); + + MultiPoint multipoint = makeMultiPoint(); + multipoint.dropAttribute(VertexDescription.Semantics.ID); + + // Test Import Multi_point from Multi_point + ByteBuffer multipointWKBBuffer = exporterWKB.execute(0, multipoint, + null); + int wkbType = multipointWKBBuffer.getInt(1); + assertTrue(wkbType == WkbGeometryType.wkbMultiPointZ); + MultiPoint multipointWKBGeometry = (MultiPoint) (importerWKB.execute(0, + Geometry.Type.MultiPoint, multipointWKBBuffer, null)); + TestCommonMethods.compareGeometryContent( + (MultiVertexGeometry) multipointWKBGeometry, multipoint); + + // Test WKB_export_point on nonempty single point Multi_point + MultiPoint multipoint2 = makeMultiPoint2(); + assertTrue(multipoint2.getPointCount() == 1); + ByteBuffer pointWKBBuffer = exporterWKB.execute( + WkbExportFlags.wkbExportPoint, multipoint2, null); + Point pointWKBGeometry = (Point) (importerWKB.execute(0, + Geometry.Type.Point, pointWKBBuffer, null)); + Point3D point3d, mpoint3d; + point3d = pointWKBGeometry.getXYZ(); + mpoint3d = multipoint2.getXYZ(0); + assertTrue(point3d.x == mpoint3d.x && point3d.y == mpoint3d.y + && point3d.z == mpoint3d.z); + wkbType = pointWKBBuffer.getInt(1); + assertTrue(wkbType == WkbGeometryType.wkbPointZ); + + // Test WKB_export_point on empty Multi_point + MultiPoint multipoint3 = new MultiPoint(); + pointWKBBuffer = exporterWKB.execute(WkbExportFlags.wkbExportPoint, + multipoint3, null); + pointWKBGeometry = (Point) (importerWKB.execute(0, Geometry.Type.Point, + pointWKBBuffer, null)); + assertTrue(pointWKBGeometry.isEmpty() == true); + wkbType = pointWKBBuffer.getInt(1); + assertTrue(wkbType == WkbGeometryType.wkbPoint); + + // Test WKB_export_defaults on empty Multi_point + multipointWKBBuffer = exporterWKB.execute(0, multipoint3, null); + multipointWKBGeometry = (MultiPoint) (importerWKB.execute(0, + Geometry.Type.MultiPoint, multipointWKBBuffer, null)); + assertTrue(multipointWKBGeometry.isEmpty() == true); + wkbType = multipointWKBBuffer.getInt(1); + assertTrue(wkbType == WkbGeometryType.wkbMultiPoint); + } + + @Test + public static void testImportExportWKBPoint() { + OperatorExportToWkb exporterWKB = (OperatorExportToWkb) OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.ExportToWkb); + OperatorImportFromWkb importerWKB = (OperatorImportFromWkb) OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.ImportFromWkb); + + // Point + Point point = makePoint(); + + // Test Import Point from Point + ByteBuffer pointWKBBuffer = exporterWKB.execute(0, point, null); + int wkbType = pointWKBBuffer.getInt(1); + assertTrue(wkbType == WkbGeometryType.wkbPointZM); + Point pointWKBGeometry = (Point) (importerWKB.execute(0, + Geometry.Type.Point, pointWKBBuffer, null)); + + double x_1 = point.getX(); + double x2 = pointWKBGeometry.getX(); + assertTrue(x_1 == x2); + + double y1 = point.getY(); + double y2 = pointWKBGeometry.getY(); + assertTrue(y1 == y2); + + double z_1 = point.getZ(); + double z_2 = pointWKBGeometry.getZ(); + assertTrue(z_1 == z_2); + + double m1 = point.getM(); + double m2 = pointWKBGeometry.getM(); + assertTrue(m1 == m2); + + // Test WKB_export_defaults on empty point + Point point2 = new Point(); + pointWKBBuffer = exporterWKB.execute(0, point2, null); + pointWKBGeometry = (Point) (importerWKB.execute(0, Geometry.Type.Point, + pointWKBBuffer, null)); + assertTrue(pointWKBGeometry.isEmpty() == true); + wkbType = pointWKBBuffer.getInt(1); + assertTrue(wkbType == WkbGeometryType.wkbPoint); + + // Test WKB_export_point on empty point + pointWKBBuffer = exporterWKB.execute(WkbExportFlags.wkbExportPoint, + point2, null); + pointWKBGeometry = (Point) (importerWKB.execute(0, Geometry.Type.Point, + pointWKBBuffer, null)); + assertTrue(pointWKBGeometry.isEmpty() == true); + wkbType = pointWKBBuffer.getInt(1); + assertTrue(wkbType == WkbGeometryType.wkbPoint); + + // Test WKB_export_multi_point on empty point + MultiPoint multipoint = new MultiPoint(); + ByteBuffer multipointWKBBuffer = exporterWKB.execute( + WkbExportFlags.wkbExportMultiPoint, multipoint, null); + MultiPoint multipointWKBGeometry = (MultiPoint) (importerWKB.execute(0, + Geometry.Type.MultiPoint, multipointWKBBuffer, null)); + assertTrue(multipointWKBGeometry.isEmpty() == true); + wkbType = multipointWKBBuffer.getInt(1); + assertTrue(wkbType == WkbGeometryType.wkbMultiPoint); + + // Test WKB_export_point on nonempty single point Multi_point + MultiPoint multipoint2 = makeMultiPoint2(); + assertTrue(multipoint2.getPointCount() == 1); + pointWKBBuffer = exporterWKB.execute(WkbExportFlags.wkbExportPoint, + multipoint2, null); + pointWKBGeometry = (Point) (importerWKB.execute(0, Geometry.Type.Point, + pointWKBBuffer, null)); + Point3D point3d, mpoint3d; + point3d = pointWKBGeometry.getXYZ(); + mpoint3d = multipoint2.getXYZ(0); + assertTrue(point3d.x == mpoint3d.x && point3d.y == mpoint3d.y + && point3d.z == mpoint3d.z); + wkbType = pointWKBBuffer.getInt(1); + assertTrue(wkbType == WkbGeometryType.wkbPointZ); + } + + @Test + public static void testImportExportWKBEnvelope() { + OperatorExportToWkb exporterWKB = (OperatorExportToWkb) OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.ExportToWkb); + OperatorImportFromWkb importerWKB = (OperatorImportFromWkb) OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.ImportFromWkb); + + // Test Export Envelope to Polygon (WKB_export_defaults) + Envelope envelope = makeEnvelope(); + envelope.dropAttribute(VertexDescription.Semantics.ID); + + ByteBuffer polygonWKBBuffer = exporterWKB.execute(0, envelope, null); + int wkbType = polygonWKBBuffer.getInt(1); + assertTrue(wkbType == WkbGeometryType.wkbPolygonZM); + Polygon polygon = (Polygon) (importerWKB.execute(0, + Geometry.Type.Polygon, polygonWKBBuffer, null)); + int point_count = polygon.getPointCount(); + assertTrue(point_count == 4); + + Envelope2D env = new Envelope2D(); + Envelope1D interval; + + envelope.queryEnvelope2D(env); + interval = envelope.queryInterval(VertexDescription.Semantics.Z, 0); + Point3D point3d; + point3d = polygon.getXYZ(0); + assertTrue(point3d.x == env.xmin && point3d.y == env.ymin + && point3d.z == interval.vmin); + point3d = polygon.getXYZ(1); + assertTrue(point3d.x == env.xmin && point3d.y == env.ymax + && point3d.z == interval.vmax); + point3d = polygon.getXYZ(2); + assertTrue(point3d.x == env.xmax && point3d.y == env.ymax + && point3d.z == interval.vmin); + point3d = polygon.getXYZ(3); + assertTrue(point3d.x == env.xmax && point3d.y == env.ymin + && point3d.z == interval.vmax); + + interval = envelope.queryInterval(VertexDescription.Semantics.M, 0); + double m = polygon.getAttributeAsDbl(VertexDescription.Semantics.M, 0, + 0); + assertTrue(m == interval.vmin); + m = polygon.getAttributeAsDbl(VertexDescription.Semantics.M, 1, 0); + assertTrue(m == interval.vmax); + m = polygon.getAttributeAsDbl(VertexDescription.Semantics.M, 2, 0); + assertTrue(m == interval.vmin); + m = polygon.getAttributeAsDbl(VertexDescription.Semantics.M, 3, 0); + assertTrue(m == interval.vmax); + + // Test WKB_export_multi_polygon on nonempty Envelope + polygonWKBBuffer = exporterWKB.execute( + WkbExportFlags.wkbExportMultiPolygon, envelope, null); + wkbType = polygonWKBBuffer.getInt(1); + assertTrue(wkbType == WkbGeometryType.wkbMultiPolygonZM); + polygon = (Polygon) (importerWKB.execute(0, Geometry.Type.Polygon, + polygonWKBBuffer, null)); + point_count = polygon.getPointCount(); + assertTrue(point_count == 4); + + envelope.queryEnvelope2D(env); + interval = envelope.queryInterval(VertexDescription.Semantics.Z, 0); + point3d = polygon.getXYZ(0); + assertTrue(point3d.x == env.xmin && point3d.y == env.ymin + && point3d.z == interval.vmin); + point3d = polygon.getXYZ(1); + assertTrue(point3d.x == env.xmin && point3d.y == env.ymax + && point3d.z == interval.vmax); + point3d = polygon.getXYZ(2); + assertTrue(point3d.x == env.xmax && point3d.y == env.ymax + && point3d.z == interval.vmin); + point3d = polygon.getXYZ(3); + assertTrue(point3d.x == env.xmax && point3d.y == env.ymin + && point3d.z == interval.vmax); + + interval = envelope.queryInterval(VertexDescription.Semantics.M, 0); + m = polygon.getAttributeAsDbl(VertexDescription.Semantics.M, 0, 0); + assertTrue(m == interval.vmin); + m = polygon.getAttributeAsDbl(VertexDescription.Semantics.M, 1, 0); + assertTrue(m == interval.vmax); + m = polygon.getAttributeAsDbl(VertexDescription.Semantics.M, 2, 0); + assertTrue(m == interval.vmin); + m = polygon.getAttributeAsDbl(VertexDescription.Semantics.M, 3, 0); + assertTrue(m == interval.vmax); + + // Test WKB_export_defaults on empty Envelope + Envelope envelope2 = new Envelope(); + polygonWKBBuffer = exporterWKB.execute(0, envelope2, null); + wkbType = polygonWKBBuffer.getInt(1); + assertTrue(wkbType == WkbGeometryType.wkbPolygon); + polygon = (Polygon) (importerWKB.execute(0, Geometry.Type.Polygon, + polygonWKBBuffer, null)); + assertTrue(polygon.isEmpty()); + + // Test WKB_export_polygon on empty Envelope + polygonWKBBuffer = exporterWKB.execute(WkbExportFlags.wkbExportPolygon, + envelope2, null); + wkbType = polygonWKBBuffer.getInt(1); + assertTrue(wkbType == WkbGeometryType.wkbPolygon); + polygon = (Polygon) (importerWKB.execute(0, Geometry.Type.Polygon, + polygonWKBBuffer, null)); + assertTrue(polygon.isEmpty()); + } + + @Test + public static void testImportExportWktGeometryCollection() { + OperatorImportFromWkt importerWKT = (OperatorImportFromWkt) OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.ImportFromWkt); + OperatorExportToWkt exporterWKT = (OperatorExportToWkt) OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.ExportToWkt); + + String wktString; + Envelope2D envelope = new Envelope2D(); + WktParser wktParser = new WktParser(); + + wktString = "GeometryCollection( Point (0 0), GeometryCollection( Point (0 0) , Point (1 1) , Point (2 2), LineString empty ), Point (1 1), Point (2 2) )"; + OGCStructure structure = importerWKT.executeOGC(0, wktString, null).m_structures + .get(0); + + assertTrue(structure.m_type == 7); + assertTrue(structure.m_structures.get(0).m_type == 1); + assertTrue(structure.m_structures.get(0).m_type == 1); + assertTrue(structure.m_structures.get(1).m_type == 7); + assertTrue(structure.m_structures.get(2).m_type == 1); + assertTrue(structure.m_structures.get(3).m_type == 1); + + assertTrue(structure.m_structures.get(1).m_structures.get(0).m_type == 1); + assertTrue(structure.m_structures.get(1).m_structures.get(1).m_type == 1); + assertTrue(structure.m_structures.get(1).m_structures.get(2).m_type == 1); + assertTrue(structure.m_structures.get(1).m_structures.get(3).m_type == 2); + } + + @Test + public static void testImportExportWktMultiPolygon() { + OperatorImportFromWkt importerWKT = (OperatorImportFromWkt) OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.ImportFromWkt); + OperatorExportToWkt exporterWKT = (OperatorExportToWkt) OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.ExportToWkt); + + Polygon polygon; + String wktString; + Envelope2D envelope = new Envelope2D(); + WktParser wktParser = new WktParser(); + + // Test Import from MultiPolygon + + wktString = "Multipolygon M empty"; + polygon = (Polygon) importerWKT.execute(0, Geometry.Type.Polygon, + wktString, null); + assertTrue(polygon != null); + assertTrue(polygon.isEmpty()); + assertTrue(polygon.hasAttribute(VertexDescription.Semantics.M)); + + polygon = (Polygon) GeometryEngine.geometryFromWkt(wktString, 0, + Geometry.Type.Unknown); + assertTrue(polygon != null); + assertTrue(polygon.isEmpty()); + assertTrue(polygon.hasAttribute(VertexDescription.Semantics.M)); + + wktString = exporterWKT.execute(0, polygon, null); + assertTrue(wktString.equals("MULTIPOLYGON M EMPTY")); + + wktString = GeometryEngine.geometryToWkt(polygon, 0); + assertTrue(wktString.equals("MULTIPOLYGON M EMPTY")); + + wktString = "Multipolygon Z (empty, (empty, (10 10 5, 20 10 5, 20 20 5, 10 20 5, 10 10 5), (12 12 3), empty, (10 10 1, 12 12 1)), empty, ((90 90 88, 60 90 7, 60 60 7), empty, (70 70 7, 80 80 7, 70 80 7, 70 70 7)), empty)"; + polygon = (Polygon) (importerWKT.execute(0, Geometry.Type.Polygon, + wktString, null)); + assertTrue(polygon != null); + polygon.queryEnvelope2D(envelope); + assertTrue(envelope.xmin == 10 && envelope.xmax == 90 + && envelope.ymin == 10 && envelope.ymax == 90); + assertTrue(polygon.getPointCount() == 14); + assertTrue(polygon.getPathCount() == 5); + // assertTrue(polygon.calculate_area_2D() > 0.0); + assertTrue(polygon.hasAttribute(VertexDescription.Semantics.Z)); + + double z = polygon.getAttributeAsDbl(VertexDescription.Semantics.Z, 0, + 0); + assertTrue(z == 5); + + // Test Export to WKT MultiPolygon + wktString = exporterWKT.execute(0, polygon, null); + assertTrue(wktString + .equals("MULTIPOLYGON Z (((10 10 5, 20 10 5, 20 20 5, 10 20 5, 10 10 5), (12 12 3, 12 12 3, 12 12 3), (10 10 1, 12 12 1, 10 10 1)), ((90 90 88, 60 90 7, 60 60 7, 90 90 88), (70 70 7, 80 80 7, 70 80 7, 70 70 7)))")); + wktParser.resetParser(wktString); + while (wktParser.nextToken() != WktParser.WktToken.not_available) { + } + + // Test import Polygon + wktString = "POLYGON z (EMPTY, EMPTY, (10 10 5, 10 20 5, 20 20 5, 20 10 5), (12 12 3), EMPTY, (10 10 1, 12 12 1), EMPTY, (60 60 7, 60 90 7, 90 90 7, 60 60 7), EMPTY, (70 70 7, 70 80 7, 80 80 7), EMPTY)"; + polygon = (Polygon) (importerWKT.execute(0, Geometry.Type.Polygon, + wktString, null)); + assertTrue(polygon != null); + assertTrue(polygon.getPointCount() == 14); + assertTrue(polygon.getPathCount() == 5); + assertTrue(polygon.hasAttribute(VertexDescription.Semantics.Z)); + + // Test Export to WKT Polygon + wktString = exporterWKT.execute(WktExportFlags.wktExportPolygon, + polygon, null); + assertTrue(wktString + .equals("POLYGON Z ((10 10 5, 20 10 5, 20 20 5, 10 20 5, 10 10 5), (12 12 3, 12 12 3, 12 12 3), (10 10 1, 12 12 1, 10 10 1), (60 60 7, 90 90 7, 60 90 7, 60 60 7), (70 70 7, 80 80 7, 70 80 7, 70 70 7))")); + wktParser.resetParser(wktString); + while (wktParser.nextToken() != WktParser.WktToken.not_available) { + } + + Envelope env = new Envelope(); + env.addAttribute(VertexDescription.Semantics.Z); + polygon.queryEnvelope(env); + + wktString = exporterWKT.execute(0, env, null); + assertTrue(wktString + .equals("POLYGON Z ((10 10 1, 90 10 7, 90 90 1, 10 90 7, 10 10 1))")); + wktParser.resetParser(wktString); + while (wktParser.nextToken() != WktParser.WktToken.not_available) { + } + + wktString = exporterWKT.execute(WktExportFlags.wktExportMultiPolygon, + env, null); + assertTrue(wktString + .equals("MULTIPOLYGON Z (((10 10 1, 90 10 7, 90 90 1, 10 90 7, 10 10 1)))")); + wktParser.resetParser(wktString); + while (wktParser.nextToken() != WktParser.WktToken.not_available) { + } + + env.setEmpty(); + + wktString = exporterWKT.execute(0, env, null); + assertTrue(wktString.equals("POLYGON Z EMPTY")); + wktParser.resetParser(wktString); + while (wktParser.nextToken() != WktParser.WktToken.not_available) { + } + + wktString = exporterWKT.execute(WktExportFlags.wktExportMultiPolygon, + env, null); + assertTrue(wktString.equals("MULTIPOLYGON Z EMPTY")); + wktParser.resetParser(wktString); + while (wktParser.nextToken() != WktParser.WktToken.not_available) { + } + + wktString = "MULTIPOLYGON (((5 10, 8 10, 10 10, 10 0, 0 0, 0 10, 2 10, 5 10)))"; // ring + // is + // oriented + // clockwise + polygon = (Polygon) (importerWKT.execute(0, Geometry.Type.Polygon, + wktString, null)); + assertTrue(polygon != null); + assertTrue(polygon.calculateArea2D() > 0); + + wktString = "MULTIPOLYGON Z (((90 10 7, 10 10 1, 10 90 7, 90 90 1, 90 10 7)))"; // ring + // is + // oriented + // clockwise + polygon = (Polygon) (importerWKT.execute(0, Geometry.Type.Polygon, + wktString, null)); + assertTrue(polygon != null); + assertTrue(polygon.getPointCount() == 4); + assertTrue(polygon.getPathCount() == 1); + assertTrue(polygon.hasAttribute(VertexDescription.Semantics.Z)); + assertTrue(polygon.calculateArea2D() > 0); + + wktString = exporterWKT.execute(WktExportFlags.wktExportMultiPolygon, + polygon, null); + assertTrue(wktString + .equals("MULTIPOLYGON Z (((90 10 7, 90 90 1, 10 90 7, 10 10 1, 90 10 7)))")); + } + + @Test + public static void testImportExportWktPolygon() { + OperatorImportFromWkt importerWKT = (OperatorImportFromWkt) OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.ImportFromWkt); + // OperatorExportToWkt exporterWKT = + // (OperatorExportToWkt)OperatorFactoryLocal.getInstance().getOperator(Operator.Type.ExportToWkt); + + Polygon polygon; + String wktString; + Envelope2D envelope = new Envelope2D(); + + // Test Import from Polygon + + wktString = "Polygon ZM empty"; + polygon = (Polygon) (importerWKT.execute(0, Geometry.Type.Unknown, + wktString, null)); + assertTrue(polygon != null); + assertTrue(polygon.isEmpty()); + assertTrue(polygon.hasAttribute(VertexDescription.Semantics.Z)); + assertTrue(polygon.hasAttribute(VertexDescription.Semantics.M)); + + wktString = "Polygon z (empty, (10 10 5, 20 10 5, 20 20 5, 10 20 5, 10 10 5), (12 12 3), empty, (10 10 1, 12 12 1))"; + polygon = (Polygon) (importerWKT.execute(0, Geometry.Type.Unknown, + wktString, null)); + assertTrue(polygon != null); + polygon.queryEnvelope2D(envelope); + assertTrue(envelope.xmin == 10 && envelope.xmax == 20 + && envelope.ymin == 10 && envelope.ymax == 20); + assertTrue(polygon.getPointCount() == 8); + assertTrue(polygon.getPathCount() == 3); + assertTrue(polygon.hasAttribute(VertexDescription.Semantics.Z)); + + wktString = "polygon ((35 10, 10 20, 15 40, 45 45, 35 10), (20 30, 35 35, 30 20, 20 30))"; + Polygon polygon2 = (Polygon) (importerWKT.execute(0, + Geometry.Type.Unknown, wktString, null)); + assertTrue(polygon2 != null); + + // wktString = exporterWKT.execute(0, *polygon2, null); + } + + @Test + public static void testImportExportWktLineString() { + OperatorImportFromWkt importerWKT = (OperatorImportFromWkt) OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.ImportFromWkt); + // OperatorExportToWkt exporterWKT = + // (OperatorExportToWkt)OperatorFactoryLocal.getInstance().getOperator(Operator.Type.ExportToWkt); + + Polyline polyline; + String wktString; + Envelope2D envelope = new Envelope2D(); + + // Test Import from LineString + + wktString = "LineString ZM empty"; + polyline = (Polyline) (importerWKT.execute(0, Geometry.Type.Unknown, + wktString, null)); + assertTrue(polyline != null); + assertTrue(polyline.isEmpty()); + assertTrue(polyline.hasAttribute(VertexDescription.Semantics.Z)); + assertTrue(polyline.hasAttribute(VertexDescription.Semantics.M)); + + wktString = "LineString m (10 10 5, 10 20 5, 20 20 5, 20 10 5)"; + polyline = (Polyline) (importerWKT.execute(0, Geometry.Type.Unknown, + wktString, null)); + assertTrue(polyline != null); + polyline.queryEnvelope2D(envelope); + assertTrue(envelope.xmin == 10 && envelope.xmax == 20 + && envelope.ymin == 10 && envelope.ymax == 20); + assertTrue(polyline.getPointCount() == 4); + assertTrue(polyline.getPathCount() == 1); + assertTrue(polyline.hasAttribute(VertexDescription.Semantics.M)); + } + + @Test + public static void testImportExportWktMultiLineString() { + OperatorImportFromWkt importerWKT = (OperatorImportFromWkt) OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.ImportFromWkt); + OperatorExportToWkt exporterWKT = (OperatorExportToWkt) OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.ExportToWkt); + + Polyline polyline; + String wktString; + Envelope2D envelope = new Envelope2D(); + WktParser wktParser = new WktParser(); + + // Test Import from MultiLineString + + wktString = "MultiLineStringZMempty"; + polyline = (Polyline) (importerWKT.execute(0, Geometry.Type.Unknown, + wktString, null)); + assertTrue(polyline != null); + assertTrue(polyline.isEmpty()); + assertTrue(polyline.hasAttribute(VertexDescription.Semantics.Z)); + assertTrue(polyline.hasAttribute(VertexDescription.Semantics.M)); + + wktString = "MultiLineStringm(empty, empty, (10 10 5, 10 20 5, 20 88 5, 20 10 5), (12 88 3), empty, (10 10 1, 12 12 1), empty, (88 60 7, 60 90 7, 90 90 7), empty, (70 70 7, 70 80 7, 80 80 7), empty)"; + polyline = (Polyline) (importerWKT.execute(0, Geometry.Type.Unknown, + wktString, null)); + assertTrue(polyline != null); + polyline.queryEnvelope2D(envelope); + assertTrue(envelope.xmin == 10 && envelope.xmax == 90 + && envelope.ymin == 10 && envelope.ymax == 90); + assertTrue(polyline.getPointCount() == 14); + assertTrue(polyline.getPathCount() == 5); + assertTrue(polyline.hasAttribute(VertexDescription.Semantics.M)); + + wktString = exporterWKT.execute(0, polyline, null); + assertTrue(wktString + .equals("MULTILINESTRING M ((10 10 5, 10 20 5, 20 88 5, 20 10 5), (12 88 3, 12 88 3), (10 10 1, 12 12 1), (88 60 7, 60 90 7, 90 90 7), (70 70 7, 70 80 7, 80 80 7))")); + wktParser.resetParser(wktString); + while (wktParser.nextToken() != WktParser.WktToken.not_available) { + } + + // Test Import LineString + wktString = "Linestring Z(10 10 5, 10 20 5, 20 20 5, 20 10 5)"; + polyline = (Polyline) (importerWKT.execute(0, Geometry.Type.Unknown, + wktString, null)); + assertTrue(polyline.getPointCount() == 4); + wktString = exporterWKT.execute(WktExportFlags.wktExportLineString, + polyline, null); + assertTrue(wktString + .equals("LINESTRING Z (10 10 5, 10 20 5, 20 20 5, 20 10 5)")); + wktParser.resetParser(wktString); + while (wktParser.nextToken() != WktParser.WktToken.not_available) { + } + + wktString = exporterWKT.execute(0, polyline, null); + assertTrue(wktString + .equals("MULTILINESTRING Z ((10 10 5, 10 20 5, 20 20 5, 20 10 5))")); + wktParser.resetParser(wktString); + while (wktParser.nextToken() != WktParser.WktToken.not_available) { + } + } + + @Test + public static void testImportExportWktMultiPoint() { + OperatorImportFromWkt importerWKT = (OperatorImportFromWkt) OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.ImportFromWkt); + OperatorExportToWkt exporterWKT = (OperatorExportToWkt) OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.ExportToWkt); + + MultiPoint multipoint; + String wktString; + Envelope2D envelope = new Envelope2D(); + WktParser wktParser = new WktParser(); + + // Test Import from Multi_point + + wktString = " MultiPoint ZM empty"; + multipoint = (MultiPoint) (importerWKT.execute(0, + Geometry.Type.Unknown, wktString, null)); + assertTrue(multipoint != null); + assertTrue(multipoint.isEmpty()); + assertTrue(multipoint.hasAttribute(VertexDescription.Semantics.Z)); + assertTrue(multipoint.hasAttribute(VertexDescription.Semantics.M)); + + wktString = exporterWKT.execute(0, multipoint, null); + assertTrue(wktString.equals("MULTIPOINT ZM EMPTY")); + wktParser.resetParser(wktString); + while (wktParser.nextToken() != WktParser.WktToken.not_available) { + } + + wktString = exporterWKT.execute(WktExportFlags.wktExportPoint, + multipoint, null); + assertTrue(wktString.equals("POINT ZM EMPTY")); + wktParser.resetParser(wktString); + while (wktParser.nextToken() != WktParser.WktToken.not_available) { + } + + multipoint = new MultiPoint(); + multipoint.add(118.15114354234563, 33.82234433423462345); + multipoint.add(88, 88); + + wktString = exporterWKT.execute(WktExportFlags.wktExportPrecision10, + multipoint, null); + assertTrue(wktString + .equals("MULTIPOINT ((118.1511435 33.82234433), (88 88))")); + wktParser.resetParser(wktString); + while (wktParser.nextToken() != WktParser.WktToken.not_available) { + } + + multipoint = new MultiPoint(); + multipoint.add(88, 2); + multipoint.add(88, 88); + + wktString = exporterWKT.execute(0, multipoint, null); + assertTrue(wktString.equals("MULTIPOINT ((88 2), (88 88))")); + wktParser.resetParser(wktString); + while (wktParser.nextToken() != WktParser.WktToken.not_available) { + } + + wktString = "Multipoint zm (empty, empty, (10 88 88 33), (10 20 5 33), (20 20 5 33), (20 10 5 33), (12 12 3 33), empty, (10 10 1 33), (12 12 1 33), empty, (60 60 7 33), (60 90.1 7 33), (90 90 7 33), empty, (70 70 7 33), (70 80 7 33), (80 80 7 33), empty)"; + multipoint = (MultiPoint) (importerWKT.execute(0, + Geometry.Type.Unknown, wktString, null)); + assertTrue(multipoint != null); + multipoint.queryEnvelope2D(envelope); + // assertTrue(envelope.xmin == 10 && envelope.xmax == 90 && + // envelope.ymin == 10 && Math.abs(envelope.ymax - 90.1) <= 0.001); + assertTrue(multipoint.getPointCount() == 13); + assertTrue(multipoint.hasAttribute(VertexDescription.Semantics.Z)); + assertTrue(multipoint.hasAttribute(VertexDescription.Semantics.M)); + + wktString = "Multipoint zm (10 88 88 33, 10 20 5 33, 20 20 5 33, 20 10 5 33, 12 12 3 33, 10 10 1 33, 12 12 1 33, 60 60 7 33, 60 90.1 7 33, 90 90 7 33, 70 70 7 33, 70 80 7 33, 80 80 7 33)"; + multipoint = (MultiPoint) (importerWKT.execute(0, + Geometry.Type.Unknown, wktString, null)); + assertTrue(multipoint != null); + // assertTrue(envelope.xmin == 10 && envelope.xmax == 90 && + // envelope.ymin == 10 && ::fabs(envelope.ymax - 90.1) <= 0.001); + assertTrue(multipoint.getPointCount() == 13); + assertTrue(multipoint.hasAttribute(VertexDescription.Semantics.Z)); + assertTrue(multipoint.hasAttribute(VertexDescription.Semantics.M)); + + wktString = exporterWKT.execute(WktExportFlags.wktExportPrecision15, + multipoint, null); + assertTrue(wktString + .equals("MULTIPOINT ZM ((10 88 88 33), (10 20 5 33), (20 20 5 33), (20 10 5 33), (12 12 3 33), (10 10 1 33), (12 12 1 33), (60 60 7 33), (60 90.1 7 33), (90 90 7 33), (70 70 7 33), (70 80 7 33), (80 80 7 33))")); + wktParser.resetParser(wktString); + while (wktParser.nextToken() != WktParser.WktToken.not_available) { + } + + wktString = "Multipoint zm (empty, empty, (10 10 5 33))"; + multipoint = (MultiPoint) (importerWKT.execute(0, + Geometry.Type.Unknown, wktString, null)); + + wktString = exporterWKT.execute(WktExportFlags.wktExportPoint, + multipoint, null); + assertTrue(wktString.equals("POINT ZM (10 10 5 33)")); + wktParser.resetParser(wktString); + while (wktParser.nextToken() != WktParser.WktToken.not_available) { + } + + // test equivalence with JTS + WKTWriter wkt_writer = new WKTWriter(); + WKTReader wkt_reader = new WKTReader(); + + String wkt_string_jts = null; + + try { + wkt_string_jts = wkt_writer.write(wkt_reader + .read("MULTIPOINT (1 2, 3 4)")); + } catch (Exception ex) { + } + + String wkt_string_esri = exporterWKT.execute(0, importerWKT.execute(0, + Geometry.Type.Unknown, "MULTIPOINT (1 2, 3 4)", null), null); + + assertTrue(wkt_string_jts.equals(wkt_string_esri)); + + } + + @Test + public static void testImportExportWktPoint() { + OperatorImportFromWkt importerWKT = (OperatorImportFromWkt) OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.ImportFromWkt); + OperatorExportToWkt exporterWKT = (OperatorExportToWkt) OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.ExportToWkt); + + Point point; + String wktString; + WktParser wktParser = new WktParser(); + + // Test Import from Point + + wktString = "Point ZM empty"; + point = (Point) (importerWKT.execute(0, Geometry.Type.Unknown, + wktString, null)); + assertTrue(point != null); + assertTrue(point.isEmpty()); + assertTrue(point.hasAttribute(VertexDescription.Semantics.Z)); + assertTrue(point.hasAttribute(VertexDescription.Semantics.M)); + + wktString = exporterWKT.execute(0, point, null); + assertTrue(wktString.equals("POINT ZM EMPTY")); + wktParser.resetParser(wktString); + while (wktParser.nextToken() != WktParser.WktToken.not_available) { + } + + wktString = exporterWKT.execute(WktExportFlags.wktExportMultiPoint, + point, null); + assertTrue(wktString.equals("MULTIPOINT ZM EMPTY")); + wktParser.resetParser(wktString); + while (wktParser.nextToken() != WktParser.WktToken.not_available) { + } + + wktString = "Point zm (30.1 10.6 5.1 33.1)"; + point = (Point) (importerWKT.execute(0, Geometry.Type.Unknown, + wktString, null)); + assertTrue(point != null); + assertTrue(point.hasAttribute(VertexDescription.Semantics.Z)); + assertTrue(point.hasAttribute(VertexDescription.Semantics.M)); + double x = point.getX(); + double y = point.getY(); + double z = point.getZ(); + double m = point.getM(); + + assertTrue(x == 30.1); + assertTrue(y == 10.6); + assertTrue(z == 5.1); + assertTrue(m == 33.1); + + wktString = exporterWKT.execute(WktExportFlags.wktExportPrecision15, + point, null); + assertTrue(wktString.equals("POINT ZM (30.1 10.6 5.1 33.1)")); + wktParser.resetParser(wktString); + while (wktParser.nextToken() != WktParser.WktToken.not_available) { + } + + wktString = exporterWKT.execute(WktExportFlags.wktExportMultiPoint + | WktExportFlags.wktExportPrecision15, point, null); + assertTrue(wktString.equals("MULTIPOINT ZM ((30.1 10.6 5.1 33.1))")); + wktParser.resetParser(wktString); + while (wktParser.nextToken() != WktParser.WktToken.not_available) { + } + } + + @Test + public static void testImportGeoJsonGeometryCollection() + throws JSONException { + OperatorImportFromGeoJson importer = (OperatorImportFromGeoJson) OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.ImportFromGeoJson); + + String geoJsonString; + Envelope2D envelope = new Envelope2D(); + WktParser wktParser = new WktParser(); + + geoJsonString = "{\"type\" : \"GeometryCollection\", \"geometries\" : [{\"type\" : \"Point\", \"coordinates\": [0,0]}, {\"type\" : \"GeometryCollection\" , \"geometries\" : [ {\"type\" : \"Point\", \"coordinates\" : [0, 0]} , {\"type\" : \"Point\", \"coordinates\" : [1, 1]} ,{ \"type\" : \"Point\", \"coordinates\" : [2, 2]}, {\"type\" : \"LineString\", \"coordinates\" : []}]} , {\"type\" : \"Point\", \"coordinates\" : [1, 1]}, {\"type\" : \"Point\" , \"coordinates\" : [2, 2]} ] }"; + OGCStructure structure = importer.executeOGC(0, geoJsonString, null).m_ogcStructure.m_structures + .get(0); + + assertTrue(structure.m_type == 7); + assertTrue(structure.m_structures.get(0).m_type == 1); + assertTrue(structure.m_structures.get(0).m_type == 1); + assertTrue(structure.m_structures.get(1).m_type == 7); + assertTrue(structure.m_structures.get(2).m_type == 1); + assertTrue(structure.m_structures.get(3).m_type == 1); + + assertTrue(structure.m_structures.get(1).m_structures.get(0).m_type == 1); + assertTrue(structure.m_structures.get(1).m_structures.get(1).m_type == 1); + assertTrue(structure.m_structures.get(1).m_structures.get(2).m_type == 1); + assertTrue(structure.m_structures.get(1).m_structures.get(3).m_type == 2); + } + + @Test + public static void testImportGeoJsonMultiPolygon() throws JSONException { + OperatorImportFromGeoJson importerGeoJson = (OperatorImportFromGeoJson) OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.ImportFromGeoJson); + + Polygon polygon; + String geoJsonString; + Envelope2D envelope = new Envelope2D(); + + // Test Import from MultiPolygon + + geoJsonString = "{\"type\": \"Multipolygon\", \"coordinates\": []}"; + polygon = (Polygon) (importerGeoJson.execute(0, Geometry.Type.Polygon, + geoJsonString, null).getGeometry()); + assertTrue(polygon != null); + assertTrue(polygon.isEmpty()); + assertTrue(!polygon.hasAttribute(VertexDescription.Semantics.M)); + + polygon = (Polygon) (GeometryEngine.geometryFromGeoJson(geoJsonString, + 0, Geometry.Type.Unknown).getGeometry()); + assertTrue(polygon != null); + assertTrue(polygon.isEmpty()); + assertTrue(!polygon.hasAttribute(VertexDescription.Semantics.M)); + + geoJsonString = "{\"type\": \"Multipolygon\", \"coordinates\": [[], [[], [[10, 10, 5], [20, 10, 5], [20, 20, 5], [10, 20, 5], [10, 10, 5]], [[12, 12, 3]], [], [[10, 10, 1], [12, 12, 1]]], [], [[[90, 90, 88], [60, 90, 7], [60, 60, 7]], [], [[70, 70, 7], [80, 80, 7], [70, 80, 7], [70, 70, 7]]], []]}"; + polygon = (Polygon) (importerGeoJson.execute(0, Geometry.Type.Polygon, + geoJsonString, null).getGeometry()); + assertTrue(polygon != null); + polygon.queryEnvelope2D(envelope); + assertTrue(envelope.xmin == 10 && envelope.xmax == 90 + && envelope.ymin == 10 && envelope.ymax == 90); + assertTrue(polygon.getPointCount() == 14); + assertTrue(polygon.getPathCount() == 5); + // assertTrue(polygon.calculate_area_2D() > 0.0); + // assertTrue(polygon.hasAttribute(VertexDescription.Semantics.Z)); + + // double z = polygon.getAttributeAsDbl(VertexDescription.Semantics.Z, + // 0, 0); + // assertTrue(z == 5); + + // Test import Polygon + geoJsonString = "{\"type\": \"POLYGON\", \"coordinates\": [[], [], [[10, 10, 5], [10, 20, 5], [20, 20, 5], [20, 10, 5]], [[12, 12, 3]], [], [[10, 10, 1], [12, 12, 1]], [], [[60, 60, 7], [60, 90, 7], [90, 90, 7], [60, 60, 7]], [], [[70, 70, 7], [70, 80, 7], [80, 80, 7]], []] }"; + polygon = (Polygon) (importerGeoJson.execute(0, Geometry.Type.Polygon, + geoJsonString, null).getGeometry()); + assertTrue(polygon != null); + assertTrue(polygon.getPointCount() == 14); + assertTrue(polygon.getPathCount() == 5); + // assertTrue(polygon.hasAttribute(VertexDescription.Semantics.Z)); + + geoJsonString = "{\"type\": \"MULTIPOLYGON\", \"coordinates\": [[[[90, 10, 7], [10, 10, 1], [10, 90, 7], [90, 90, 1], [90, 10, 7]]]] }"; // ring + // is + // oriented + // clockwise + polygon = (Polygon) (importerGeoJson.execute(0, Geometry.Type.Polygon, + geoJsonString, null).getGeometry()); + assertTrue(polygon != null); + assertTrue(polygon.getPointCount() == 4); + assertTrue(polygon.getPathCount() == 1); + // assertTrue(polygon.hasAttribute(VertexDescription.Semantics.Z)); + assertTrue(polygon.calculateArea2D() > 0); + } + + @Test + public static void testImportGeoJsonMultiLineString() throws JSONException { + OperatorImportFromGeoJson importerGeoJson = (OperatorImportFromGeoJson) OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.ImportFromGeoJson); + + Polyline polyline; + String geoJsonString; + Envelope2D envelope = new Envelope2D(); + + // Test Import from MultiLineString + + geoJsonString = "{\"type\": \"MultiLineString\", \"coordinates\": []}"; + polyline = (Polyline) (importerGeoJson.execute(0, + Geometry.Type.Unknown, geoJsonString, null).getGeometry()); + assertTrue(polyline != null); + assertTrue(polyline.isEmpty()); + assertTrue(!polyline.hasAttribute(VertexDescription.Semantics.Z)); + assertTrue(!polyline.hasAttribute(VertexDescription.Semantics.M)); + + geoJsonString = "{\"type\": \"MultiLineString\", \"coordinates\": [[], [], [[10, 10, 5], [10, 20, 5], [20, 88, 5], [20, 10, 5]], [[12, 88, 3]], [], [[10, 10, 1], [12, 12, 1]], [], [[88, 60, 7], [60, 90, 7], [90, 90, 7]], [], [[70, 70, 7], [70, 80, 7], [80, 80, 7]], []]}"; + polyline = (Polyline) (importerGeoJson.execute(0, + Geometry.Type.Unknown, geoJsonString, null).getGeometry()); + assertTrue(polyline != null); + polyline.queryEnvelope2D(envelope); + assertTrue(envelope.xmin == 10 && envelope.xmax == 90 + && envelope.ymin == 10 && envelope.ymax == 90); + assertTrue(polyline.getPointCount() == 14); + assertTrue(polyline.getPathCount() == 5); + // assertTrue(!polyline.hasAttribute(VertexDescription.Semantics.M)); + + // Test Import LineString + geoJsonString = "{\"type\": \"Linestring\", \"coordinates\": [[10, 10, 5], [10, 20, 5], [20, 20, 5], [20, 10, 5]]}"; + polyline = (Polyline) (importerGeoJson.execute(0, + Geometry.Type.Unknown, geoJsonString, null).getGeometry()); + assertTrue(polyline.getPointCount() == 4); + // assertTrue(polyline.hasAttribute(VertexDescription.Semantics.Z)); + + geoJsonString = "{\"type\": \"Linestring\", \"coordinates\": [[10, 10, 5], [10, 20, 5, 3], [20, 20, 5], [20, 10, 5]]}"; + polyline = (Polyline) (importerGeoJson.execute(0, + Geometry.Type.Unknown, geoJsonString, null).getGeometry()); + assertTrue(polyline.getPointCount() == 4); + // assertTrue(polyline.hasAttribute(VertexDescription.Semantics.Z)); + // assertTrue(polyline.hasAttribute(VertexDescription.Semantics.M)); + } + + @Test + public static void testImportGeoJsonMultiPoint() throws JSONException { + OperatorImportFromGeoJson importerGeoJson = (OperatorImportFromGeoJson) OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.ImportFromGeoJson); + + MultiPoint multipoint; + String geoJsonString; + Envelope2D envelope = new Envelope2D(); + + // Test Import from Multi_point + + geoJsonString = "{\"type\": \"MultiPoint\", \"coordinates\": []}"; + multipoint = (MultiPoint) (importerGeoJson.execute(0, + Geometry.Type.Unknown, geoJsonString, null).getGeometry()); + assertTrue(multipoint != null); + assertTrue(multipoint.isEmpty()); + assertTrue(!multipoint.hasAttribute(VertexDescription.Semantics.Z)); + assertTrue(!multipoint.hasAttribute(VertexDescription.Semantics.M)); + + multipoint = new MultiPoint(); + multipoint.add(118.15114354234563, 33.82234433423462345); + multipoint.add(88, 88); + + multipoint = new MultiPoint(); + multipoint.add(88, 2); + multipoint.add(88, 88); + + geoJsonString = "{\"type\": \"Multipoint\", \"coordinates\": [[], [], [10, 88, 88, 33], [10, 20, 5, 33], [20, 20, 5, 33], [20, 10, 5, 33], [12, 12, 3, 33], [], [10, 10, 1, 33], [12, 12, 1, 33], [], [60, 60, 7, 33], [60, 90.1, 7, 33], [90, 90, 7, 33], [], [70, 70, 7, 33], [70, 80, 7, 33], [80, 80, 7, 33], []]}"; + multipoint = (MultiPoint) (importerGeoJson.execute(0, + Geometry.Type.Unknown, geoJsonString, null).getGeometry()); + assertTrue(multipoint != null); + multipoint.queryEnvelope2D(envelope); + // assertTrue(envelope.xmin == 10 && envelope.xmax == 90 && + // envelope.ymin == 10 && Math.abs(envelope.ymax - 90.1) <= 0.001); + assertTrue(multipoint.getPointCount() == 13); + // assertTrue(multipoint.hasAttribute(VertexDescription.Semantics.Z)); + // assertTrue(multipoint.hasAttribute(VertexDescription.Semantics.M)); + + geoJsonString = "{\"type\": \"Multipoint\", \"coordinates\": [[10, 88, 88, 33], [10, 20, 5, 33], [20, 20, 5, 33], [20, 10, 5, 33], [12, 12, 3, 33], [10, 10, 1, 33], [12, 12, 1, 33], [60, 60, 7, 33], [60, 90.1, 7, 33], [90, 90, 7, 33], [70, 70, 7, 33], [70, 80, 7, 33], [80, 80, 7, 33]]}"; + multipoint = (MultiPoint) (importerGeoJson.execute(0, + Geometry.Type.Unknown, geoJsonString, null).getGeometry()); + assertTrue(multipoint != null); + // assertTrue(envelope.xmin == 10 && envelope.xmax == 90 && + // envelope.ymin == 10 && ::fabs(envelope.ymax - 90.1) <= 0.001); + assertTrue(multipoint.getPointCount() == 13); + // assertTrue(multipoint.hasAttribute(VertexDescription.Semantics.Z)); + // assertTrue(multipoint.hasAttribute(VertexDescription.Semantics.M)); + + geoJsonString = "{\"type\": \"Multipoint\", \"coordinates\": [[], [], [10, 10, 5, 33]]}"; + multipoint = (MultiPoint) (importerGeoJson.execute(0, + Geometry.Type.Unknown, geoJsonString, null).getGeometry()); + } + + @Test + public static void testImportGeoJsonPolygon() throws JSONException { + OperatorImportFromGeoJson importerGeoJson = (OperatorImportFromGeoJson) OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.ImportFromGeoJson); + + Polygon polygon; + String geoJsonString; + Envelope2D envelope = new Envelope2D(); + + // Test Import from Polygon + + geoJsonString = "{\"type\": \"Polygon\", \"coordinates\": []}"; + polygon = (Polygon) (importerGeoJson.execute(0, Geometry.Type.Unknown, + geoJsonString, null).getGeometry()); + assertTrue(polygon != null); + assertTrue(polygon.isEmpty()); + assertTrue(!polygon.hasAttribute(VertexDescription.Semantics.Z)); + assertTrue(!polygon.hasAttribute(VertexDescription.Semantics.M)); + + geoJsonString = "{\"type\": \"Polygon\", \"coordinates\": [[], [[10, 10, 5], [20, 10, 5], [20, 20, 5], [10, 20, 5], [10, 10, 5]], [[12, 12, 3]], [], [[10, 10, 1], [12, 12, 1]]]}"; + polygon = (Polygon) (importerGeoJson.execute(0, Geometry.Type.Unknown, + geoJsonString, null).getGeometry()); + assertTrue(polygon != null); + polygon.queryEnvelope2D(envelope); + assertTrue(envelope.xmin == 10 && envelope.xmax == 20 + && envelope.ymin == 10 && envelope.ymax == 20); + assertTrue(polygon.getPointCount() == 8); + assertTrue(polygon.getPathCount() == 3); + // assertTrue(polygon.hasAttribute(VertexDescription.Semantics.Z)); + + geoJsonString = "{\"type\": \"polygon\", \"coordinates\": [[[35, 10], [10, 20], [15, 40], [45, 45], [35, 10]], [[20, 30], [35, 35], [30, 20], [20, 30]]]}"; + Polygon polygon2 = (Polygon) (importerGeoJson.execute(0, + Geometry.Type.Unknown, geoJsonString, null).getGeometry()); + assertTrue(polygon2 != null); + } + + @Test + public static void testImportGeoJsonLineString() throws JSONException { + OperatorImportFromGeoJson importerGeoJson = (OperatorImportFromGeoJson) OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.ImportFromGeoJson); + + Polyline polyline; + String geoJsonString; + Envelope2D envelope = new Envelope2D(); + + // Test Import from LineString + + geoJsonString = "{\"type\": \"LineString\", \"coordinates\": []}"; + polyline = (Polyline) (importerGeoJson.execute(0, + Geometry.Type.Unknown, geoJsonString, null).getGeometry()); + assertTrue(polyline != null); + assertTrue(polyline.isEmpty()); + assertTrue(!polyline.hasAttribute(VertexDescription.Semantics.Z)); + assertTrue(!polyline.hasAttribute(VertexDescription.Semantics.M)); + + geoJsonString = "{\"type\": \"LineString\", \"coordinates\": [[10, 10, 5], [10, 20, 5], [20, 20, 5], [20, 10, 5]]}"; + polyline = (Polyline) (importerGeoJson.execute(0, + Geometry.Type.Unknown, geoJsonString, null).getGeometry()); + assertTrue(polyline != null); + polyline.queryEnvelope2D(envelope); + assertTrue(envelope.xmin == 10 && envelope.xmax == 20 + && envelope.ymin == 10 && envelope.ymax == 20); + assertTrue(polyline.getPointCount() == 4); + assertTrue(polyline.getPathCount() == 1); + // assertTrue(!polyline.hasAttribute(VertexDescription.Semantics.M)); + } + + @Test + public static void testImportGeoJsonPoint() throws JSONException { + OperatorImportFromGeoJson importerGeoJson = (OperatorImportFromGeoJson) OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.ImportFromGeoJson); + + Point point; + String geoJsonString; + + // Test Import from Point + + geoJsonString = "{\"type\": \"Point\", \"coordinates\": []}"; + point = (Point) (importerGeoJson.execute(0, Geometry.Type.Unknown, + geoJsonString, null).getGeometry()); + assertTrue(point != null); + assertTrue(point.isEmpty()); + assertTrue(!point.hasAttribute(VertexDescription.Semantics.Z)); + assertTrue(!point.hasAttribute(VertexDescription.Semantics.M)); + + geoJsonString = "{\"type\": \"Point\", \"coordinates\": [30.1, 10.6, 5.1, 33.1]}"; + point = (Point) (importerGeoJson.execute(0, Geometry.Type.Unknown, + geoJsonString, null).getGeometry()); + assertTrue(point != null); + // assertTrue(point.hasAttribute(VertexDescription.Semantics.Z)); + // assertTrue(point.hasAttribute(VertexDescription.Semantics.M)); + double x = point.getX(); + double y = point.getY(); + // double z = point.getZ(); + // double m = point.getM(); + + assertTrue(x == 30.1); + assertTrue(y == 10.6); + // assertTrue(z == 5.1); + // assertTrue(m == 33.1); + } + + @Test + public static void testImportGeoJsonSpatialReference() throws JSONException { + OperatorImportFromGeoJson importerGeoJson = (OperatorImportFromGeoJson) OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.ImportFromGeoJson); + + String geoJsonString4326; + String geoJsonString3857; + + // Test Import from Point + + geoJsonString4326 = "{\"type\": \"Point\", \"coordinates\": [3.0, 5.0], \"crs\": \"EPSG:4326\"}"; + geoJsonString3857 = "{\"type\": \"Point\", \"coordinates\": [3.0, 5.0], \"crs\": \"EPSG:3857\"}"; + + MapGeometry mapGeometry4326 = importerGeoJson.execute(0, + Geometry.Type.Unknown, geoJsonString4326, null); + MapGeometry mapGeometry3857 = importerGeoJson.execute(0, + Geometry.Type.Unknown, geoJsonString3857, null); + + assertTrue(mapGeometry4326.equals(mapGeometry3857) == false); + assertTrue(mapGeometry4326.getGeometry().equals( + mapGeometry3857.getGeometry())); + } + + public static Polygon makePolygon() { + Polygon poly = new Polygon(); + poly.startPath(0, 0); + poly.lineTo(0, 10); + poly.lineTo(10, 10); + poly.lineTo(10, 0); + + poly.startPath(3, 3); + poly.lineTo(7, 3); + poly.lineTo(7, 7); + poly.lineTo(3, 7); + + poly.startPath(15, 0); + poly.lineTo(15, 15); + poly.lineTo(30, 15); + poly.lineTo(30, 0); + + poly.setAttribute(VertexDescription.Semantics.Z, 0, 0, 2); + poly.setAttribute(VertexDescription.Semantics.Z, 1, 0, 3); + poly.setAttribute(VertexDescription.Semantics.Z, 2, 0, 5); + poly.setAttribute(VertexDescription.Semantics.Z, 3, 0, 7); + poly.setAttribute(VertexDescription.Semantics.Z, 4, 0, 11); + poly.setAttribute(VertexDescription.Semantics.Z, 5, 0, 13); + poly.setAttribute(VertexDescription.Semantics.Z, 6, 0, 17); + poly.setAttribute(VertexDescription.Semantics.Z, 7, 0, 19); + poly.setAttribute(VertexDescription.Semantics.Z, 8, 0, 23); + poly.setAttribute(VertexDescription.Semantics.Z, 9, 0, 29); + poly.setAttribute(VertexDescription.Semantics.Z, 10, 0, 31); + poly.setAttribute(VertexDescription.Semantics.Z, 11, 0, 37); + + poly.setAttribute(VertexDescription.Semantics.M, 0, 0, 2); + poly.setAttribute(VertexDescription.Semantics.M, 1, 0, 4); + poly.setAttribute(VertexDescription.Semantics.M, 2, 0, 8); + poly.setAttribute(VertexDescription.Semantics.M, 3, 0, 16); + poly.setAttribute(VertexDescription.Semantics.M, 4, 0, 32); + poly.setAttribute(VertexDescription.Semantics.M, 5, 0, 64); + poly.setAttribute(VertexDescription.Semantics.M, 6, 0, 128); + poly.setAttribute(VertexDescription.Semantics.M, 7, 0, 256); + poly.setAttribute(VertexDescription.Semantics.M, 8, 0, 512); + poly.setAttribute(VertexDescription.Semantics.M, 9, 0, 1024); + poly.setAttribute(VertexDescription.Semantics.M, 10, 0, 2048); + poly.setAttribute(VertexDescription.Semantics.M, 11, 0, 4096); + + return poly; + } + + public static Polygon makePolygon2() { + Polygon poly = new Polygon(); + poly.startPath(0, 0); + poly.lineTo(0, 10); + poly.lineTo(10, 10); + poly.lineTo(10, 0); + + poly.setAttribute(VertexDescription.Semantics.Z, 0, 0, 2); + poly.setAttribute(VertexDescription.Semantics.Z, 1, 0, 3); + poly.setAttribute(VertexDescription.Semantics.Z, 2, 0, 5); + poly.setAttribute(VertexDescription.Semantics.Z, 3, 0, 7); + + poly.setAttribute(VertexDescription.Semantics.M, 0, 0, 2); + poly.setAttribute(VertexDescription.Semantics.M, 1, 0, 4); + poly.setAttribute(VertexDescription.Semantics.M, 2, 0, 8); + poly.setAttribute(VertexDescription.Semantics.M, 3, 0, 16); + + return poly; + } + + public static Polyline makePolyline() { + Polyline poly = new Polyline(); + poly.startPath(10, 1); + poly.lineTo(15, 20); + poly.lineTo(30, 14); + poly.lineTo(60, 144); + + poly.startPath(20, 13); + poly.lineTo(150, 120); + poly.lineTo(300, 414); + poly.lineTo(610, 14); + + poly.setAttribute(VertexDescription.Semantics.Z, 0, 0, 2); + poly.setAttribute(VertexDescription.Semantics.Z, 1, 0, 3); + poly.setAttribute(VertexDescription.Semantics.Z, 2, 0, 5); + poly.setAttribute(VertexDescription.Semantics.Z, 3, 0, 7); + poly.setAttribute(VertexDescription.Semantics.Z, 4, 0, 11); + poly.setAttribute(VertexDescription.Semantics.Z, 5, 0, 13); + poly.setAttribute(VertexDescription.Semantics.Z, 6, 0, 17); + poly.setAttribute(VertexDescription.Semantics.Z, 7, 0, 19); + + poly.setAttribute(VertexDescription.Semantics.M, 0, 0, 2); + poly.setAttribute(VertexDescription.Semantics.M, 1, 0, 4); + poly.setAttribute(VertexDescription.Semantics.M, 2, 0, 8); + poly.setAttribute(VertexDescription.Semantics.M, 3, 0, 16); + poly.setAttribute(VertexDescription.Semantics.M, 4, 0, 32); + poly.setAttribute(VertexDescription.Semantics.M, 5, 0, 64); + poly.setAttribute(VertexDescription.Semantics.M, 6, 0, 128); + poly.setAttribute(VertexDescription.Semantics.M, 7, 0, 256); + + poly.setAttribute(VertexDescription.Semantics.ID, 0, 0, 1); + poly.setAttribute(VertexDescription.Semantics.ID, 1, 0, 2); + poly.setAttribute(VertexDescription.Semantics.ID, 2, 0, 3); + poly.setAttribute(VertexDescription.Semantics.ID, 3, 0, 5); + poly.setAttribute(VertexDescription.Semantics.ID, 4, 0, 8); + poly.setAttribute(VertexDescription.Semantics.ID, 5, 0, 13); + poly.setAttribute(VertexDescription.Semantics.ID, 6, 0, 21); + poly.setAttribute(VertexDescription.Semantics.ID, 7, 0, 34); + + return poly; + } + + public static Polyline makePolyline2() { + Polyline poly = new Polyline(); + poly.startPath(10, 1); + poly.lineTo(15, 20); + poly.lineTo(30, 14); + poly.lineTo(60, 144); + + poly.setAttribute(VertexDescription.Semantics.Z, 0, 0, 2); + poly.setAttribute(VertexDescription.Semantics.Z, 1, 0, 3); + poly.setAttribute(VertexDescription.Semantics.Z, 2, 0, 5); + poly.setAttribute(VertexDescription.Semantics.Z, 3, 0, 7); + + poly.setAttribute(VertexDescription.Semantics.M, 0, 0, 2); + poly.setAttribute(VertexDescription.Semantics.M, 1, 0, 4); + poly.setAttribute(VertexDescription.Semantics.M, 2, 0, 8); + poly.setAttribute(VertexDescription.Semantics.M, 3, 0, 16); + + return poly; + } + + public static Point makePoint() { + Point point = new Point(); + point.setXY(11, 13); + + point.setZ(32); + point.setM(243); + point.setID(1024); + + return point; + } + + public static MultiPoint makeMultiPoint() { + MultiPoint mpoint = new MultiPoint(); + Point pt1 = new Point(); + pt1.setXY(0, 0); + pt1.setZ(-1); + + Point pt2 = new Point(); + pt2.setXY(0, 0); + pt2.setZ(1); + + Point pt3 = new Point(); + pt3.setXY(0, 1); + pt3.setZ(1); + + mpoint.add(pt1); + mpoint.add(pt2); + mpoint.add(pt3); + + mpoint.setAttribute(VertexDescription.Semantics.ID, 0, 0, 7); + mpoint.setAttribute(VertexDescription.Semantics.ID, 1, 0, 11); + mpoint.setAttribute(VertexDescription.Semantics.ID, 2, 0, 13); + + return mpoint; + } + + public static MultiPoint makeMultiPoint2() { + MultiPoint mpoint = new MultiPoint(); + Point pt1 = new Point(); + pt1.setX(0.0); + pt1.setY(0.0); + pt1.setZ(-1.0); + + mpoint.add(pt1); + + return mpoint; + } + + public static Envelope makeEnvelope() { + Envelope envelope; + + Envelope env = new Envelope(0.0, 0.0, 5.0, 5.0); + envelope = env; + + Envelope1D interval = new Envelope1D(); + interval.vmin = -3.0; + interval.vmax = -7.0; + envelope.setInterval(VertexDescription.Semantics.Z, 0, interval); + + interval.vmin = 16.0; + interval.vmax = 32.0; + envelope.setInterval(VertexDescription.Semantics.M, 0, interval); + + interval.vmin = 5.0; + interval.vmax = 11.0; + envelope.setInterval(VertexDescription.Semantics.ID, 0, interval); + + return envelope; + } +} diff --git a/unittest/com/esri/core/geometry/TestInterpolateAttributes.java b/unittest/com/esri/core/geometry/TestInterpolateAttributes.java new file mode 100644 index 00000000..de462a02 --- /dev/null +++ b/unittest/com/esri/core/geometry/TestInterpolateAttributes.java @@ -0,0 +1,197 @@ +package com.esri.core.geometry; + +import junit.framework.TestCase; +import org.junit.Test; + +public class TestInterpolateAttributes extends TestCase { + @Override + protected void setUp() throws Exception { + super.setUp(); + } + + @Override + protected void tearDown() throws Exception { + super.tearDown(); + } + + @Test + public static void test1() { + Polyline poly = new Polyline(); + + poly.startPath(0, 0); + poly.lineTo(0, 1.0 / 3.0); + poly.lineTo(0, 2.0 / 3.0); + poly.lineTo(0, 4.0 / 3.0); + poly.lineTo(0, Math.sqrt(6.0)); + poly.lineTo(0, Math.sqrt(7.0)); + + poly.setAttribute(VertexDescription.Semantics.M, 0, 0, 3); + poly.setAttribute(VertexDescription.Semantics.M, 1, 0, 5); + poly.setAttribute(VertexDescription.Semantics.M, 2, 0, 7); + poly.setAttribute(VertexDescription.Semantics.M, 5, 0, 11); + + poly.interpolateAttributes(VertexDescription.Semantics.M, 0, 1, 0, 1); + assertTrue(poly.getAttributeAsDbl(VertexDescription.Semantics.M, 0, 0) == 3); + assertTrue(poly.getAttributeAsDbl(VertexDescription.Semantics.M, 1, 0) == 5); + assertTrue(poly.getAttributeAsDbl(VertexDescription.Semantics.M, 2, 0) == 7); + assertTrue(NumberUtils.isNaN(poly.getAttributeAsDbl( + VertexDescription.Semantics.M, 3, 0))); + assertTrue(NumberUtils.isNaN(poly.getAttributeAsDbl( + VertexDescription.Semantics.M, 4, 0))); + assertTrue(poly.getAttributeAsDbl(VertexDescription.Semantics.M, 5, 0) == 11); + + poly.interpolateAttributes(VertexDescription.Semantics.M, 0, 1, 0, 2); + assertTrue(poly.getAttributeAsDbl(VertexDescription.Semantics.M, 0, 0) == 3); + assertTrue(poly.getAttributeAsDbl(VertexDescription.Semantics.M, 1, 0) == 5); + assertTrue(poly.getAttributeAsDbl(VertexDescription.Semantics.M, 2, 0) == 7); + assertTrue(NumberUtils.isNaN(poly.getAttributeAsDbl( + VertexDescription.Semantics.M, 3, 0))); + assertTrue(NumberUtils.isNaN(poly.getAttributeAsDbl( + VertexDescription.Semantics.M, 4, 0))); + assertTrue(poly.getAttributeAsDbl(VertexDescription.Semantics.M, 5, 0) == 11); + + poly.interpolateAttributes(VertexDescription.Semantics.M, 0, 2, 0, 5); + assertTrue(poly.getAttributeAsDbl(VertexDescription.Semantics.M, 0, 0) == 3); + assertTrue(poly.getAttributeAsDbl(VertexDescription.Semantics.M, 1, 0) == 5); + assertTrue(poly.getAttributeAsDbl(VertexDescription.Semantics.M, 2, 0) == 7); + double a3 = poly.getAttributeAsDbl(VertexDescription.Semantics.M, 3, 0); + assertTrue(a3 > 7 && a3 < 11); + double a4 = poly.getAttributeAsDbl(VertexDescription.Semantics.M, 4, 0); + assertTrue(a4 > a3 && a4 < 11); + assertTrue(poly.getAttributeAsDbl(VertexDescription.Semantics.M, 5, 0) == 11); + + poly.startPath(0, Math.sqrt(8.0)); + poly.lineTo(0, Math.sqrt(10.0)); + poly.lineTo(0, Math.sqrt(11.0)); + } + + @Test + public static void test2() { + Polyline poly = new Polyline(); + + poly.startPath(0, 0); + poly.lineTo(0, 1.0 / 3.0); + + poly.startPath(0, Math.sqrt(8.0)); + poly.lineTo(0, Math.sqrt(10.0)); + + poly.setAttribute(VertexDescription.Semantics.M, 1, 0, Math.sqrt(3.0)); + + poly.interpolateAttributes(VertexDescription.Semantics.M, 0, 1, 1, 0); + assertTrue(poly.getAttributeAsDbl(VertexDescription.Semantics.M, 1, 0) == Math + .sqrt(3.0)); + assertTrue(NumberUtils.isNaN(poly.getAttributeAsDbl( + VertexDescription.Semantics.M, 2, 0))); + + poly.setAttribute(VertexDescription.Semantics.M, 3, 0, Math.sqrt(5.0)); + poly.interpolateAttributes(VertexDescription.Semantics.M, 0, 1, 1, 1); + assertTrue(poly.getAttributeAsDbl(VertexDescription.Semantics.M, 1, 0) == Math + .sqrt(3.0)); + double a2 = poly.getAttributeAsDbl(VertexDescription.Semantics.M, 2, 0); + assertTrue(a2 == Math.sqrt(3.0)); + assertTrue(poly.getAttributeAsDbl(VertexDescription.Semantics.M, 3, 0) == Math + .sqrt(5.0)); + } + + @Test + public static void test3() { + Polyline poly = new Polyline(); + + poly.startPath(0, Math.sqrt(0.0)); + poly.lineTo(0, Math.sqrt(5.0)); + + poly.startPath(0, Math.sqrt(8.0)); + poly.lineTo(0, Math.sqrt(10.0)); + + poly.setAttribute(VertexDescription.Semantics.M, 0, 0, Math.sqrt(3.0)); + poly.setAttribute(VertexDescription.Semantics.M, 2, 0, Math.sqrt(5.0)); + + poly.interpolateAttributes(VertexDescription.Semantics.M, 0, 0, 1, 0); + assertTrue(poly.getAttributeAsDbl(VertexDescription.Semantics.M, 0, 0) == Math + .sqrt(3.0)); + assertTrue(poly.getAttributeAsDbl(VertexDescription.Semantics.M, 1, 0) == Math + .sqrt(5.0)); + assertTrue(poly.getAttributeAsDbl(VertexDescription.Semantics.M, 2, 0) == Math + .sqrt(5.0)); + } + + @Test + public static void test4() { + Polyline poly = new Polyline(); + + poly.startPath(0, Math.sqrt(0.0)); + poly.lineTo(0, Math.sqrt(1.0)); + + poly.startPath(0, Math.sqrt(1.0)); + poly.lineTo(0, Math.sqrt(2.0)); + + poly.startPath(0, Math.sqrt(2.0)); + poly.lineTo(0, Math.sqrt(3.0)); + + poly.startPath(0, Math.sqrt(3.0)); + poly.lineTo(0, Math.sqrt(4.0)); + + poly.startPath(0, Math.sqrt(4.0)); + poly.lineTo(0, Math.sqrt(5.0)); + + poly.setAttribute(VertexDescription.Semantics.M, 1, 0, Math.sqrt(1.0)); + poly.setAttribute(VertexDescription.Semantics.M, 8, 0, Math.sqrt(4.0)); + + poly.interpolateAttributes(VertexDescription.Semantics.M, 0, 1, 4, 0); + + assertTrue(NumberUtils.isNaN(poly.getAttributeAsDbl( + VertexDescription.Semantics.M, 0, 0))); + assertTrue(poly.getAttributeAsDbl(VertexDescription.Semantics.M, 1, 0) == Math + .sqrt(1.0)); + assertTrue(poly.getAttributeAsDbl(VertexDescription.Semantics.M, 2, 0) == Math + .sqrt(1.0)); + assertTrue(poly.getAttributeAsDbl(VertexDescription.Semantics.M, 3, 0) == Math + .sqrt(2.0)); + assertTrue(poly.getAttributeAsDbl(VertexDescription.Semantics.M, 4, 0) == Math + .sqrt(2.0)); + assertTrue(poly.getAttributeAsDbl(VertexDescription.Semantics.M, 5, 0) == Math + .sqrt(3.0)); + assertTrue(poly.getAttributeAsDbl(VertexDescription.Semantics.M, 6, 0) == Math + .sqrt(3.0)); + assertTrue(poly.getAttributeAsDbl(VertexDescription.Semantics.M, 7, 0) == Math + .sqrt(4.0)); + assertTrue(poly.getAttributeAsDbl(VertexDescription.Semantics.M, 8, 0) == Math + .sqrt(4.0)); + assertTrue(NumberUtils.isNaN(poly.getAttributeAsDbl( + VertexDescription.Semantics.M, 9, 0))); + } + + @Test + public static void test5() { + Polygon poly = new Polygon(); + + poly.startPath(0, 0); + poly.lineTo(0, 1); + poly.lineTo(1, 1); + poly.lineTo(1, 0); + + poly.startPath(2, 0); + poly.lineTo(2, 1); + poly.lineTo(3, 1); + poly.lineTo(3, 0); + + poly.setAttribute(VertexDescription.Semantics.M, 1, 0, 1); + poly.setAttribute(VertexDescription.Semantics.M, 3, 0, 3); + + poly.setAttribute(VertexDescription.Semantics.M, 6, 0, 1); + poly.setAttribute(VertexDescription.Semantics.M, 5, 0, 4); + + poly.interpolateAttributes(VertexDescription.Semantics.M, 0, 3, 1); + poly.interpolateAttributes(VertexDescription.Semantics.M, 0, 1, 3); + poly.interpolateAttributes(VertexDescription.Semantics.M, 1, 2, 1); + + assertTrue(poly.getAttributeAsDbl(VertexDescription.Semantics.M, 0, 0) == 2); + assertTrue(poly.getAttributeAsDbl(VertexDescription.Semantics.M, 1, 0) == 1); + assertTrue(poly.getAttributeAsDbl(VertexDescription.Semantics.M, 2, 0) == 2); + assertTrue(poly.getAttributeAsDbl(VertexDescription.Semantics.M, 3, 0) == 3); + assertTrue(poly.getAttributeAsDbl(VertexDescription.Semantics.M, 4, 0) == 3); + assertTrue(poly.getAttributeAsDbl(VertexDescription.Semantics.M, 5, 0) == 4); + assertTrue(poly.getAttributeAsDbl(VertexDescription.Semantics.M, 6, 0) == 1); + assertTrue(poly.getAttributeAsDbl(VertexDescription.Semantics.M, 7, 0) == 2); + } +} diff --git a/unittest/com/esri/core/geometry/TestIntersect2.java b/unittest/com/esri/core/geometry/TestIntersect2.java new file mode 100644 index 00000000..f5276d69 --- /dev/null +++ b/unittest/com/esri/core/geometry/TestIntersect2.java @@ -0,0 +1,366 @@ +package com.esri.core.geometry; + +import com.esri.core.geometry.Geometry.Type; +import junit.framework.TestCase; +import org.junit.Test; + +public class TestIntersect2 extends TestCase { + @Override + protected void setUp() throws Exception { + super.setUp(); + } + + @Override + protected void tearDown() throws Exception { + super.tearDown(); + } + + @Test + /** + * Intersect + * throw InvalidShapeException when applying between polyline and polygon + * + * */ + public void testIntersectBetweenPolylineAndPolygon() { + Polyline basePl = new Polyline(); + basePl.startPath(new Point(-117, 20)); + basePl.lineTo(new Point(-117, 10)); + basePl.lineTo(new Point(-130, 10)); + basePl.lineTo(new Point(-130, 20)); + basePl.lineTo(new Point(-117, 20)); + + Polygon compPl = new Polygon(); + compPl.startPath(-116, 20); + compPl.lineTo(-131, 10); + compPl.lineTo(-121, 50); + + Geometry intersectGeom = null; + + @SuppressWarnings("unused") + int noException = 1; // no exception + try { + intersectGeom = GeometryEngine.intersect(basePl, compPl, + SpatialReference.create(4326)); + + } catch (Exception ex) { + noException = 0; + } + assertNotNull(intersectGeom); + + // Geometry[] geometries = new Geometry[1]; + // geometries[0] = basePl; + // BorgGeometryUtils.getIntersectFromRestWS(geometries, compPl, 4326); + } + + @Test + public void testIntersectBetweenPolylines() { + Polyline basePl = new Polyline(); + basePl.startPath(new Point(-117, 20)); + basePl.lineTo(new Point(-130, 10)); + basePl.lineTo(new Point(-120, 50)); + + Polyline compPl = new Polyline(); + compPl.startPath(new Point(-116, 20)); + compPl.lineTo(new Point(-131, 10)); + compPl.lineTo(new Point(-121, 50)); + + int noException = 1; // no exception + try { + @SuppressWarnings("unused") + Geometry intersectGeom = GeometryEngine.intersect(basePl, compPl, + SpatialReference.create(4326)); + + } catch (Exception ex) { + noException = 0; + } + assertEquals(noException, 1); + } + + @Test + public void testPointAndPolyline1() { + Point basePl = new Point(-116, 20); + + Polyline compPl = new Polyline(); + compPl.startPath(new Point(-116, 20)); + compPl.lineTo(new Point(-131, 10)); + compPl.lineTo(new Point(-121, 50)); + + int noException = 1; // no exception + Geometry intersectGeom = null; + try { + intersectGeom = GeometryEngine.intersect(basePl, compPl, + SpatialReference.create(4326)); + } catch (Exception ex) { + noException = 0; + } + assertEquals(noException, 1); + assertNotNull(intersectGeom); + assertTrue(intersectGeom.getType() == Type.Point); + + Point ip = (Point) intersectGeom; + assertEquals(ip.getX(), -116, 0.1E7); + assertEquals(ip.getY(), 20, 0.1E7); + } + + @Test + public void testPointAndPolyline2() { + Point basePl = new Point(-115, 20); + Polyline compPl = new Polyline(); + compPl.startPath(new Point(-116, 20)); + compPl.lineTo(new Point(-131, 10)); + compPl.lineTo(new Point(-121, 50)); + + int noException = 1; // no exception + Geometry intersectGeom = null; + try { + intersectGeom = GeometryEngine.intersect(basePl, compPl, + SpatialReference.create(4326)); + } catch (Exception ex) { + noException = 0; + } + assertEquals(noException, 1); + assertTrue(intersectGeom.isEmpty()); + } + + @Test + public void testPointAndPolygon1() { + Point basePl = new Point(-116, 20); + Polygon compPl = new Polygon(); + compPl.startPath(new Point(-116, 20)); + compPl.lineTo(new Point(-131, 10)); + compPl.lineTo(new Point(-121, 50)); + + int noException = 1; // no exception + Geometry intersectGeom = null; + try { + intersectGeom = GeometryEngine.intersect(basePl, compPl, + SpatialReference.create(4326)); + + } catch (Exception ex) { + noException = 0; + } + assertEquals(noException, 1); + assertNotNull(intersectGeom); + assertTrue(intersectGeom.getType() == Type.Point); + + Point ip = (Point) intersectGeom; + assertEquals(ip.getX(), -116, 0.1E7); + assertEquals(ip.getY(), 20, 0.1E7); + } + + @Test + public void testPointAndPolygon2() { + Point basePl = new Point(-115, 20); + Polygon compPl = new Polygon(); + compPl.startPath(new Point(-116, 20)); + compPl.lineTo(new Point(-131, 10)); + compPl.lineTo(new Point(-121, 50)); + + int noException = 1; // no exception + Geometry intersectGeom = null; + try { + intersectGeom = GeometryEngine.intersect(basePl, compPl, + SpatialReference.create(4326)); + + } catch (Exception ex) { + noException = 0; + } + assertEquals(noException, 1); + assertTrue(intersectGeom.isEmpty()); + } + + @Test + public void testPointAndPolygon3() { + Point basePl = new Point(-121, 20); + Polygon compPl = new Polygon(); + compPl.startPath(new Point(-116, 20)); + compPl.lineTo(new Point(-131, 10)); + compPl.lineTo(new Point(-121, 50)); + + int noException = 1; // no exception + Geometry intersectGeom = null; + try { + intersectGeom = GeometryEngine.intersect(basePl, compPl, + SpatialReference.create(4326)); + + } catch (Exception ex) { + noException = 0; + } + assertEquals(noException, 1); + assertNotNull(intersectGeom); + assertTrue(intersectGeom.getType() == Type.Point); + + Point ip = (Point) intersectGeom; + assertEquals(ip.getX(), -121, 0.1E7); + assertEquals(ip.getY(), 20, 0.1E7); + } + + @Test + public void testPointAndPoint1() { + Point basePl = new Point(-116, 20); + Point compPl = new Point(-116, 20); + + int noException = 1; // no exception + Geometry intersectGeom = null; + try { + intersectGeom = GeometryEngine.intersect(basePl, compPl, + SpatialReference.create(4326)); + } catch (Exception ex) { + noException = 0; + } + assertEquals(noException, 1); + assertNotNull(intersectGeom); + assertTrue(intersectGeom.getType() == Type.Point); + + Point ip = (Point) intersectGeom; + assertEquals(ip.getX(), -116, 0.1E7); + assertEquals(ip.getY(), 20, 0.1E7); + } + + @Test + public void testPointAndPoint2() { + Point basePl = new Point(-115, 20); + Point compPl = new Point(-116, 20); + + int noException = 1; // no exception + Geometry intersectGeom = null; + try { + intersectGeom = GeometryEngine.intersect(basePl, compPl, + SpatialReference.create(4326)); + } catch (Exception ex) { + noException = 0; + } + assertEquals(noException, 1); + assertTrue(intersectGeom.isEmpty()); + } + + @Test + public void testPointAndMultiPoint1() { + Point basePl = new Point(-116, 20); + MultiPoint compPl = new MultiPoint(); + compPl.add(new Point(-116, 20)); + compPl.add(new Point(-118, 21)); + + int noException = 1; // no exception + Geometry intersectGeom = null; + try { + intersectGeom = GeometryEngine.intersect(basePl, compPl, + SpatialReference.create(4326)); + } catch (Exception ex) { + noException = 0; + } + assertEquals(noException, 1); + assertNotNull(intersectGeom); + assertTrue(intersectGeom.getType() == Type.Point); + + Point ip = (Point) intersectGeom; + assertEquals(ip.getX(), -116, 0.1E7); + assertEquals(ip.getY(), 20, 0.1E7); + } + + @Test + public void testPointAndMultiPoint2() { + Point basePl = new Point(-115, 20); + + MultiPoint compPl = new MultiPoint(); + compPl.add(new Point(-116, 20)); + compPl.add(new Point(-117, 21)); + compPl.add(new Point(-118, 20)); + compPl.add(new Point(-119, 21)); + + int noException = 1; // no exception + Geometry intersectGeom = null; + + try { + intersectGeom = GeometryEngine.intersect(basePl, compPl, + SpatialReference.create(4326)); + } catch (Exception ex) { + noException = 0; + } + assertEquals(noException, 1); + assertTrue(intersectGeom.isEmpty()); + } + + @Test + public void testMultiPointAndMultiPoint1() { + MultiPoint basePl = new MultiPoint(); + basePl.add(new Point(-116, 20)); + basePl.add(new Point(-117, 20)); + + MultiPoint compPl = new MultiPoint(); + compPl.add(new Point(-116, 20)); + compPl.add(new Point(-118, 21)); + + int noException = 1; // no exception + Geometry intersectGeom = null; + try { + intersectGeom = GeometryEngine.intersect(basePl, compPl, + SpatialReference.create(4326)); + } catch (Exception ex) { + noException = 0; + } + assertEquals(noException, 1); + assertNotNull(intersectGeom); + assertTrue(intersectGeom.getType() == Type.MultiPoint); + + MultiPoint imp = (MultiPoint) intersectGeom; + assertEquals(imp.getCoordinates2D().length, 1); + assertEquals(imp.getCoordinates2D()[0].x, -116, 0.0); + assertEquals(imp.getCoordinates2D()[0].y, 20, 0.0); + } + + @Test + public void testMultiPointAndMultiPoint2() { + MultiPoint basePl = new MultiPoint(); + basePl.add(new Point(-116, 20)); + basePl.add(new Point(-118, 21)); + + MultiPoint compPl = new MultiPoint(); + compPl.add(new Point(-116, 20)); + compPl.add(new Point(-118, 21)); + + int noException = 1; // no exception + Geometry intersectGeom = null; + try { + intersectGeom = GeometryEngine.intersect(basePl, compPl, + SpatialReference.create(4326)); + } catch (Exception ex) { + noException = 0; + } + + assertEquals(noException, 1); + assertNotNull(intersectGeom); + assertTrue(intersectGeom.getType() == Type.MultiPoint); + + MultiPoint ip = (MultiPoint) intersectGeom; + assertEquals(ip.getPoint(0).getX(), -116, 0.1E7); + assertEquals(ip.getPoint(0).getY(), 20, 0.1E7); + assertEquals(ip.getPoint(0).getX(), -118, 0.1E7); + assertEquals(ip.getPoint(0).getY(), 21, 0.1E7); + } + + @Test + public void testMultiPointAndMultiPoint3() { + MultiPoint basePl = new MultiPoint(); + basePl.add(new Point(-116, 21)); + basePl.add(new Point(-117, 20)); + + MultiPoint compPl = new MultiPoint(); + compPl.add(new Point(-116, 20)); + compPl.add(new Point(-117, 21)); + compPl.add(new Point(-118, 20)); + compPl.add(new Point(-119, 21)); + + int noException = 1; // no exception + Geometry intersectGeom = null; + try { + intersectGeom = GeometryEngine.intersect(basePl, compPl, + SpatialReference.create(4326)); + } catch (Exception ex) { + noException = 0; + System.out.println("err: " + ex.getMessage()); + } + assertEquals(noException, 1); + assertTrue(intersectGeom.isEmpty()); + } +} diff --git a/unittest/com/esri/core/geometry/TestIntersection.java b/unittest/com/esri/core/geometry/TestIntersection.java new file mode 100644 index 00000000..7f0ee8b6 --- /dev/null +++ b/unittest/com/esri/core/geometry/TestIntersection.java @@ -0,0 +1,919 @@ +package com.esri.core.geometry; + +import junit.framework.TestCase; +import org.junit.Test; + +//import java.util.Random; + +public class TestIntersection extends TestCase { + static OperatorFactoryLocal projEnv = OperatorFactoryLocal.getInstance(); + static int codeIn = 26910;// NAD_1983_UTM_Zone_10N : GCS 6269 + static int codeOut = 32610;// WGS_1984_UTM_Zone_10N; : GCS 4326 + static SpatialReference inputSR; + static SpatialReference outputSR; + + @Override + protected void setUp() throws Exception { + super.setUp(); + projEnv = OperatorFactoryLocal.getInstance(); + inputSR = SpatialReference.create(codeIn); + outputSR = SpatialReference.create(codeOut); + } + + @Override + protected void tearDown() throws Exception { + super.tearDown(); + } + + @Test + public void testIntersection1() { + // OperatorFactoryLocal projEnv = OperatorFactoryLocal.getInstance(); + // int codeIn = 26910;//NAD_1983_UTM_Zone_10N : GCS 6269 + // int codeOut = 32610;//WGS_1984_UTM_Zone_10N; : GCS 4326 + // int codeIn = SpatialReference::PCS_WGS_1984_UTM_10N; + // int codeOut = SpatialReference::PCS_WORLD_MOLLWEIDE; + // int codeOut = 102100; + inputSR = SpatialReference.create(codeIn); + assertTrue(inputSR.getID() == codeIn); + outputSR = SpatialReference.create(codeOut); + assertTrue(outputSR.getID() == codeOut); + + OperatorIntersection operatorIntersection = (OperatorIntersection) projEnv + .getOperator(Operator.Type.Intersection); + + Polygon poly1 = new Polygon(); + Envelope env1 = new Envelope(855277, 3892059, 855277 + 100, + 3892059 + 100); + // Envelope env1 = new Envelope(-1000000, -1000000, 1000000, 1000000); + // env1.SetCoords(-8552770, -3892059, 855277 + 100, 3892059 + 100); + poly1.addEnvelope(env1, false); + + Polygon poly2 = new Polygon(); + Envelope env2 = new Envelope(855277 + 1, 3892059 + 1, 855277 + 30, + 3892059 + 20); + poly2.addEnvelope(env2, false); + + GeometryCursor cursor1 = new SimpleGeometryCursor(poly1); + GeometryCursor cursor2 = new SimpleGeometryCursor(poly2); + + GeometryCursor outputGeoms = operatorIntersection.execute(cursor1, + cursor2, inputSR, null); + Geometry geomr = outputGeoms.next(); + assertNotNull(geomr); + assertTrue(geomr.getType() == Geometry.Type.Polygon); + Polygon geom = (Polygon) geomr; + assertTrue(geom.getPointCount() == 4); + Point[] points = TestCommonMethods.pointsFromMultiPath(geom);// SPtrOfArrayOf(Point2D) + // pts = + // geom.get.getCoordinates2D(); + assertTrue(Math.abs(points[0].getX() - 855278.000000000) < 1e-7); + assertTrue(Math.abs(points[0].getY() - 3892060.0000000000) < 1e-7); + assertTrue(Math.abs(points[2].getX() - 855307.00000000093) < 1e-7); + assertTrue(Math.abs(points[2].getY() - 3892079.0000000000) < 1e-7); + + geomr = operatorIntersection.execute(poly1, poly2, inputSR, null); + assertNotNull(geomr); + assertTrue(geomr.getType() == Geometry.Type.Polygon); + Polygon outputGeom = (Polygon) geomr; + + assertTrue(outputGeom.getPointCount() == 4); + points = TestCommonMethods.pointsFromMultiPath(outputGeom); + assertTrue(Math.abs(points[0].getX() - 855278.000000000) < 1e-7); + assertTrue(Math.abs(points[0].getY() - 3892060.0000000000) < 1e-7); + assertTrue(Math.abs(points[2].getX() - 855307.00000000093) < 1e-7); + assertTrue(Math.abs(points[2].getY() - 3892079.0000000000) < 1e-7); + } + + @Test + public void testSelfIntersecting() {// Test that we do not fail if there is + // self-intersection + // OperatorFactoryLocal projEnv = + // OperatorFactoryLocal.getInstance(); + OperatorIntersection operatorIntersection = (OperatorIntersection) projEnv + .getOperator(Operator.Type.Intersection); + SpatialReference sr = SpatialReference.create(4326); + Polygon poly1 = new Polygon(); + Envelope2D env1 = new Envelope2D(); + env1.setCoords(0, 0, 20, 30); + poly1.addEnvelope(env1, false); + Polygon poly2 = new Polygon(); + poly2.startPath(0, 0); + poly2.lineTo(10, 10); + poly2.lineTo(0, 10); + poly2.lineTo(10, 0); + @SuppressWarnings("unused") + Polygon res = (Polygon) (operatorIntersection.execute(poly1, poly2, sr, + null)); + // Operator_equals equals = + // (Operator_equals)projEnv.get_operator(Operator::equals); + // assertTrue(equals.execute(res, poly2, sr, NULL) == true); + } + + @Test + public void testMultipoint() { + Polygon poly1 = new Polygon(); + Envelope env1 = new Envelope(855277, 3892059, 855277 + 100, + 3892059 + 100); + + poly1.addEnvelope(env1, false); + MultiPoint multiPoint = new MultiPoint(); + multiPoint.add(855277 + 10, 3892059 + 10); + multiPoint.add(855277, 3892059); + multiPoint.add(855277 + 100, 3892059 + 100); + multiPoint.add(855277 + 100, 3892059 + 101); + multiPoint.add(855277 + 101, 3892059 + 100); + multiPoint.add(855277 + 101, 3892059 + 101); + OperatorIntersection operatorIntersection = (OperatorIntersection) projEnv + .getOperator(Operator.Type.Intersection); + + MultiPoint mpResult = (MultiPoint) operatorIntersection.execute(poly1, + multiPoint, inputSR, null); + assertTrue(mpResult.getPointCount() == 3); + assertTrue(mpResult.getPoint(0).getX() == 855277 + 10 + && mpResult.getPoint(0).getY() == 3892059 + 10); + assertTrue(mpResult.getPoint(1).getX() == 855277 + && mpResult.getPoint(1).getY() == 3892059); + assertTrue(mpResult.getPoint(2).getX() == 855277 + 100 + && mpResult.getPoint(2).getY() == 3892059 + 100); + + // Test intersection of Polygon with Envelope (calls Clip) + Polygon poly = new Polygon(); + poly.startPath(0, 0); + poly.lineTo(10, 10); + poly.lineTo(20, 0); + + env1.setXMin(0); + env1.setXMax(20); + env1.setYMin(5); + env1.setYMax(15); + + Envelope envelope1 = env1; + + Polygon clippedPoly = (Polygon) operatorIntersection.execute(poly, + envelope1, inputSR, null); + double area = clippedPoly.calculateArea2D(); + assertTrue(Math.abs(area - 25) < 0.00001); + + // Geometry res = GeometryEngine.difference(poly, envelope1, inputSR); + Envelope env2 = new Envelope(855277 + 1, 3892059 + 1, 855277 + 30, + 3892059 + 20); + env2.setXMin(5); + env2.setXMax(10); + env2.setYMin(0); + env2.setYMax(20); + + Envelope envelope2 = env2; + + Envelope clippedEnvelope = (Envelope) operatorIntersection.execute( + envelope1, envelope2, inputSR, null); + area = clippedEnvelope.calculateArea2D(); + assertTrue(Math.abs(area - 50) < 0.00001); + } + + @Test + public void testDifferenceOnPolyline() { + Polyline basePl = new Polyline(); + basePl.startPath(-117, 20); + basePl.lineTo(-130, 10); + basePl.lineTo(-120, 50); + + Polyline compPl = new Polyline(); + compPl.startPath(-116, 20); + compPl.lineTo(-131, 10); + compPl.lineTo(-121, 50); + + // OperatorFactoryLocal projEnv = OperatorFactoryLocal.getInstance(); + OperatorDifference op = (OperatorDifference) projEnv + .getOperator(Operator.Type.Difference); + Polyline diffGeom = (Polyline) (op.execute(basePl, compPl, + SpatialReference.create(4326), null)); + int pc = diffGeom.getPointCount(); + assertTrue(pc == 5); + } + + @Test + public void testDifferenceOnPolyline2() { + Polyline basePl = new Polyline(); + basePl.startPath(0, 0); + basePl.lineTo(10, 10); + basePl.lineTo(20, 20); + basePl.lineTo(10, 0); + basePl.lineTo(20, 10); + + Polyline compPl = new Polyline(); + compPl.startPath(5, 0); + compPl.lineTo(5, 10); + compPl.lineTo(0, 10); + compPl.lineTo(7.5, 2.5); + + // Operator_factory_local::SaveJSONToTextFileDbg("c:/temp/basePl.txt", + // *basePl, null); + // Operator_factory_local::SaveJSONToTextFileDbg("c:/temp/compPl.txt", + // *compPl, null); + // OperatorFactoryLocal projEnv = OperatorFactoryLocal.getInstance(); + OperatorDifference op = (OperatorDifference) projEnv + .getOperator(Operator.Type.Difference); + Polyline diffGeom = (Polyline) (op.execute(basePl, compPl, + SpatialReference.create(4326), null)); + // Operator_factory_local::SaveJSONToTextFileDbg("c:/temp/diffGeom.txt", + // *diffGeom, null); + int pathc = diffGeom.getPathCount(); + assertTrue(pathc == 1); + int pc = diffGeom.getPointCount(); + assertTrue(pc == 6); + + Polyline resPl = new Polyline(); + resPl.startPath(0, 0); + resPl.lineTo(5, 5); + resPl.lineTo(10, 10); + resPl.lineTo(20, 20); + resPl.lineTo(10, 0); + resPl.lineTo(20, 10); + // Operator_factory_local::SaveJSONToTextFileDbg("c:/temp/resPl.txt", + // *resPl, null); + assertTrue(resPl.equals(diffGeom)); + } + + @Test + public void testDifferencePointPolyline() { + Polyline basePl = new Polyline(); + basePl.startPath(0, 0); + basePl.lineTo(10, 10); + basePl.lineTo(20, 20); + basePl.lineTo(10, 0); + basePl.lineTo(20, 10); + + Point compPl = new Point(5, 5); + + // OperatorFactoryLocal projEnv = OperatorFactoryLocal.getInstance(); + OperatorDifference op = (OperatorDifference) projEnv + .getOperator(Operator.Type.Difference); + Polyline diffGeom = (Polyline) (op.execute(basePl, compPl, + SpatialReference.create(4326), null)); + int pathc = diffGeom.getPathCount(); + assertTrue(pathc == 1); + int pc = diffGeom.getPointCount(); + assertTrue(pc == 5); + + Polyline resPl = new Polyline(); + resPl.startPath(0, 0); + resPl.lineTo(10, 10); + resPl.lineTo(20, 20); + resPl.lineTo(10, 0); + resPl.lineTo(20, 10); + assertTrue(resPl.equals(diffGeom));// no change happens to the original + // polyline + } + + @Test + public void testIntersectionPolylinePolygon() { + { + Polygon polygon = new Polygon(); + polygon.startPath(0, 0); + polygon.lineTo(0, 10); + polygon.lineTo(20, 10); + polygon.lineTo(20, 0); + polygon.addAttribute(VertexDescription.Semantics.Z); + polygon.setAttribute(VertexDescription.Semantics.Z, 0, 0, 3); + polygon.setAttribute(VertexDescription.Semantics.Z, 3, 0, 3); + polygon.interpolateAttributes(0, 0, 3); + Polyline polyline = new Polyline(); + polyline.startPath(0, 10); + polyline.lineTo(5, 5); + polyline.lineTo(6, 4); + polyline.lineTo(7, -1); + polyline.addAttribute(VertexDescription.Semantics.Z); + polyline.setAttribute(VertexDescription.Semantics.Z, 0, 0, 5); + polyline.setAttribute(VertexDescription.Semantics.Z, 3, 0, 5); + polyline.interpolateAttributes(0, 0, 0, 3); + + // OperatorFactoryLocal projEnv = + // OperatorFactoryLocal.getInstance(); + OperatorIntersection operatorIntersection = (OperatorIntersection) projEnv + .getOperator(Operator.Type.Intersection); + Geometry geom = operatorIntersection.execute(polyline, polygon, + null, null); + assertTrue(!geom.isEmpty()); + Polyline poly = (Polyline) (geom); + for (int i = 0; i < poly.getPointCount(); i++) + assertTrue(poly.getAttributeAsDbl( + VertexDescription.Semantics.Z, i, 0) == 5); + + // std::shared_ptr jsonExport = + // (Operator_export_to_JSON>)Operator_factory_local::get_instance().get_operator(Operator::Operator_type::export_to_JSON); + // std::string str = jsonExport.execute(0, geom, null, null); + // OutputDebugStringA(str.c_str()); + // OutputDebugString(L"\n"); + // assertTrue(str=="{\"paths\":[[[0,10],[5,5],[6,4],[6.7999999999999998,4.4408922169635528e-016]]]}"); + } + + { + Polygon polygon = new Polygon(); + polygon.startPath(0, 0); + polygon.lineTo(0, 10); + polygon.lineTo(20, 10); + polygon.lineTo(20, 0); + polygon.addAttribute(VertexDescription.Semantics.Z); + polygon.setAttribute(VertexDescription.Semantics.Z, 0, 0, 3); + polygon.setAttribute(VertexDescription.Semantics.Z, 3, 0, 3); + polygon.interpolateAttributes(0, 0, 3); + Polyline polyline = new Polyline(); + polyline.startPath(0, 10); + polyline.lineTo(20, 0); + polyline.lineTo(5, 5); + polyline.addAttribute(VertexDescription.Semantics.Z); + polyline.setAttribute(VertexDescription.Semantics.Z, 0, 0, 5); + polyline.setAttribute(VertexDescription.Semantics.Z, 1, 0, 5); + polyline.setAttribute(VertexDescription.Semantics.Z, 2, 0, 5); + + // OperatorFactoryLocal projEnv = + // OperatorFactoryLocal.getInstance(); + OperatorIntersection operatorIntersection = (OperatorIntersection) projEnv + .getOperator(Operator.Type.Intersection); + Geometry geom = operatorIntersection.execute(polyline, polygon, + null, null); + assertTrue(!geom.isEmpty()); + Polyline poly = (Polyline) (geom); + for (int i = 0; i < poly.getPointCount(); i++) + assertTrue(poly.getAttributeAsDbl( + VertexDescription.Semantics.Z, i, 0) == 5); + + // Operator_export_to_JSON> jsonExport = + // (Operator_export_to_JSON>)Operator_factory_local::get_instance().get_operator(Operator::Operator_type::export_to_JSON); + // std::string str = jsonExport.execute(0, geom, null, null); + // OutputDebugStringA(str.c_str()); + // OutputDebugString(L"\n"); + // assertTrue(str=="{\"paths\":[[[0,10],[20,0],[5,5]]]}"); + } + + { + Polygon polygon = new Polygon(); + polygon.startPath(0, 0); + polygon.lineTo(0, 10); + polygon.lineTo(20, 10); + polygon.lineTo(20, 0); + polygon.addAttribute(VertexDescription.Semantics.Z); + polygon.setAttribute(VertexDescription.Semantics.Z, 0, 0, 3); + polygon.setAttribute(VertexDescription.Semantics.Z, 3, 0, 3); + polygon.interpolateAttributes(0, 0, 3); + Polyline polyline = new Polyline(); + polyline.startPath(0, 0); + polyline.lineTo(0, 10); + polyline.lineTo(20, 10); + polyline.addAttribute(VertexDescription.Semantics.Z); + polyline.setAttribute(VertexDescription.Semantics.Z, 0, 0, 5); + polyline.setAttribute(VertexDescription.Semantics.Z, 1, 0, 5); + polyline.setAttribute(VertexDescription.Semantics.Z, 2, 0, 5); + + // OperatorFactoryLocal projEnv = + // OperatorFactoryLocal.getInstance(); + OperatorIntersection operatorIntersection = (OperatorIntersection) projEnv + .getOperator(Operator.Type.Intersection); + Geometry geom = operatorIntersection.execute(polyline, polygon, + null, null); + assertTrue(!geom.isEmpty()); + Polyline poly = (Polyline) (geom); + for (int i = 0; i < poly.getPointCount(); i++) + assertTrue(poly.getAttributeAsDbl( + VertexDescription.Semantics.Z, i, 0) == 5); + + // Operator_export_to_JSON> jsonExport = + // (Operator_export_to_JSON>)Operator_factory_local::get_instance().get_operator(Operator::Operator_type::export_to_JSON); + // std::string str = jsonExport.execute(0, geom, null, null); + // OutputDebugStringA(str.c_str()); + // OutputDebugString(L"\n"); + // assertTrue(str=="{\"paths\":[[[0,0],[0,10],[20,10]]]}"); + } + + { + Polygon polygon = new Polygon(); + polygon.startPath(0, 0); + polygon.lineTo(0, 10); + polygon.lineTo(20, 10); + polygon.lineTo(20, 0); + Polyline polyline = new Polyline(); + polyline.startPath(3, -1); + polyline.lineTo(17, 1); + polyline.lineTo(10, 8); + polyline.lineTo(-1, 5); + polyline.addAttribute(VertexDescription.Semantics.Z); + polyline.setAttribute(VertexDescription.Semantics.Z, 0, 0, 5); + polyline.setAttribute(VertexDescription.Semantics.Z, 1, 0, 5); + polyline.setAttribute(VertexDescription.Semantics.Z, 2, 0, 5); + polyline.setAttribute(VertexDescription.Semantics.Z, 3, 0, 5); + + // OperatorFactoryLocal projEnv = + // OperatorFactoryLocal.getInstance(); + OperatorIntersection operatorIntersection = (OperatorIntersection) projEnv + .getOperator(Operator.Type.Intersection); + Geometry geom = operatorIntersection.execute(polyline, polygon, + null, null); + assertTrue(!geom.isEmpty()); + Polyline poly = (Polyline) geom; + for (int i = 0; i < poly.getPointCount(); i++) + assertTrue(poly.getAttributeAsDbl( + VertexDescription.Semantics.Z, i, 0) == 5); + + // Operator_export_to_JSON> jsonExport = + // (Operator_export_to_JSON>)Operator_factory_local::get_instance().get_operator(Operator::Operator_type::export_to_JSON); + // std::string str = jsonExport.execute(0, geom, null, null); + // OutputDebugStringA(str.c_str()); + // OutputDebugString(L"\n"); + // assertTrue(str=="{\"paths\":[[[10,0],[17,1],[10,8],[4.7377092701401439e-024,5.2727272727272734]]]}"); + } + + { + Polygon polygon = new Polygon(); + polygon.startPath(0, 0); + polygon.lineTo(0, 10); + polygon.lineTo(20, 10); + polygon.lineTo(20, 0); + Polyline polyline = new Polyline(); + polyline.startPath(0, 15); + polyline.lineTo(3, -1); + polyline.lineTo(17, 1); + polyline.lineTo(10, 8); + polyline.lineTo(-1, 5); + polyline.addAttribute(VertexDescription.Semantics.Z); + polyline.setAttribute(VertexDescription.Semantics.Z, 0, 0, 5); + polyline.setAttribute(VertexDescription.Semantics.Z, 4, 0, 5); + polyline.interpolateAttributes(0, 0, 0, 4); + OperatorIntersection operatorIntersection = (OperatorIntersection) projEnv + .getOperator(Operator.Type.Intersection); + Geometry geom = operatorIntersection.execute(polyline, polygon, + null, null); + assertTrue(!geom.isEmpty()); + Polyline poly = (Polyline) geom; + for (int i = 0; i < poly.getPointCount(); i++) + assertTrue(poly.getAttributeAsDbl( + VertexDescription.Semantics.Z, i, 0) == 5); + + // Operator_export_to_JSON> jsonExport = + // (Operator_export_to_JSON>)Operator_factory_local::get_instance().get_operator(Operator::Operator_type::export_to_JSON); + // std::string str = jsonExport.execute(0, geom, null, null); + // OutputDebugStringA(str.c_str()); + // OutputDebugString(L"\n"); + // assertTrue(str=="{\"paths\":[[[0.9375,10],[2.8125,9.476226333847234e-024]],[[10,0],[17,1],[10,8],[4.7377092701401439e-024,5.2727272727272734]]]}"); + } + + { + Polygon polygon = new Polygon(); + polygon.startPath(0, 0); + polygon.lineTo(0, 10); + polygon.lineTo(20, 10); + polygon.lineTo(20, 0); + Polyline polyline = new Polyline(); + polyline.startPath(5, 5); + polyline.lineTo(1, 1); + polyline.lineTo(-1, 1); + polyline.lineTo(-1, 10); + polyline.lineTo(0, 10); + polyline.lineTo(6, 6); + polyline.addAttribute(VertexDescription.Semantics.Z); + polyline.setAttribute(VertexDescription.Semantics.Z, 0, 0, 5); + polyline.setAttribute(VertexDescription.Semantics.Z, 5, 0, 5); + polyline.interpolateAttributes(0, 0, 0, 5); + + // OperatorFactoryLocal projEnv = + // OperatorFactoryLocal.getInstance(); + OperatorIntersection operatorIntersection = (OperatorIntersection) projEnv + .getOperator(Operator.Type.Intersection); + Geometry geom = operatorIntersection.execute(polyline, polygon, + null, null); + assertTrue(!geom.isEmpty()); + Polyline poly = (Polyline) geom; + for (int i = 0; i < poly.getPointCount(); i++) + assertTrue(poly.getAttributeAsDbl( + VertexDescription.Semantics.Z, i, 0) == 5); + + // Operator_export_to_JSON> jsonExport = + // (Operator_export_to_JSON>)Operator_factory_local::get_instance().get_operator(Operator::Operator_type::export_to_JSON); + // std::string str = jsonExport.execute(0, geom, null, null); + // OutputDebugStringA(str.c_str()); + // OutputDebugString(L"\n"); + // assertTrue(str=="{\"paths\":[[[5,5],[1,1],[4.738113166923617e-023,1]],[[0,10],[6,6]]]}"); + } + + { + Polygon polygon = new Polygon(); + polygon.startPath(0, 0); + polygon.lineTo(0, 10); + polygon.lineTo(20, 10); + polygon.lineTo(20, 0); + Polyline polyline = new Polyline(); + polyline.startPath(0, 15); + polyline.lineTo(3, -1); + polyline.lineTo(17, 1); + polyline.lineTo(10, 8); + polyline.lineTo(-1, 5); + polyline.startPath(19, 15); + polyline.lineTo(29, 9); + polyline.startPath(19, 15); + polyline.lineTo(29, 9); + polyline.startPath(5, 5); + polyline.lineTo(1, 1); + polyline.lineTo(-1, 1); + polyline.lineTo(-1, 10); + polyline.lineTo(0, 10); + polyline.lineTo(6, 6); + polyline.addAttribute(VertexDescription.Semantics.Z); + polyline.setAttribute(VertexDescription.Semantics.Z, 0, 0, 5); + polyline.setAttribute(VertexDescription.Semantics.Z, 14, 0, 5); + polyline.interpolateAttributes(0, 0, 3, 5); + + // OperatorFactoryLocal projEnv = + // OperatorFactoryLocal.getInstance(); + OperatorIntersection operatorIntersection = (OperatorIntersection) projEnv + .getOperator(Operator.Type.Intersection); + Geometry geom = operatorIntersection.execute(polyline, polygon, + null, null); + assertTrue(!geom.isEmpty()); + Polyline poly = (Polyline) geom; + for (int i = 0; i < poly.getPointCount(); i++) + assertTrue(poly.getAttributeAsDbl( + VertexDescription.Semantics.Z, i, 0) == 5); + + // Operator_export_to_JSON> jsonExport = + // (Operator_export_to_JSON>)Operator_factory_local::get_instance().get_operator(Operator::Operator_type::export_to_JSON); + // std::string str = jsonExport.execute(0, geom, null, null); + // OutputDebugStringA(str.c_str()); + // OutputDebugString(L"\n"); + // assertTrue(str=="{\"paths\":[[[0.9375,10],[2.8125,9.476226333847234e-024]],[[10,0],[17,1],[10,8],[4.7377092701401439e-024,5.2727272727272734]],[[5,5],[1,1],[4.738113166923617e-023,1]],[[0,10],[6,6]]]}"); + } + } + + @Test + public void testMultiPointPolyline() { + Polyline polyline = new Polyline(); + polyline.startPath(0, 0); + polyline.lineTo(0, 10); + polyline.lineTo(20, 10); + polyline.lineTo(20, 0); + MultiPoint mp = new MultiPoint(); + mp.add(0, 10, 7); + mp.add(0, 5, 7); + mp.add(1, 5, 7); + // OperatorFactoryLocal projEnv = OperatorFactoryLocal.getInstance(); + OperatorIntersection operatorIntersection = (OperatorIntersection) projEnv + .getOperator(Operator.Type.Intersection); + OperatorDifference operatorDifference = (OperatorDifference) projEnv + .getOperator(Operator.Type.Difference); + + {// intersect + Geometry geom = operatorIntersection.execute(polyline, mp, null, + null); + MultiPoint res = (MultiPoint) geom; + assertTrue(res.getPointCount() == 2); + Point2D pt_1 = res.getXY(0); + Point2D pt_2 = res.getXY(1); + assertTrue(Point2D.distance(pt_1, new Point2D(0, 10)) < 1e-10 + && Point2D.distance(pt_2, new Point2D(0, 5)) < 1e-10 + || Point2D.distance(pt_2, new Point2D(0, 10)) < 1e-10 + && Point2D.distance(pt_1, new Point2D(0, 5)) < 1e-10); + + assertTrue(res.getAttributeAsDbl(VertexDescription.Semantics.Z, 0, + 0) == 7); + assertTrue(res.getAttributeAsDbl(VertexDescription.Semantics.Z, 1, + 0) == 7); + } + + {// difference + Geometry geom = operatorDifference + .execute(polyline, mp, null, null); + // assertTrue(geom.getGeometryType() == + // Geometry.GeometryType.Polyline); + Polyline res = (Polyline) geom; + assertTrue(res.getPointCount() == 4); + } + {// difference + Geometry geom = operatorDifference + .execute(mp, polyline, null, null); + // assertTrue(geom.getType() == Geometry.GeometryType.MultiPoint); + MultiPoint res = (MultiPoint) geom; + assertTrue(res.getPointCount() == 1); + Point2D pt_1 = res.getXY(0); + assertTrue(Point2D.distance(pt_1, new Point2D(1, 5)) < 1e-10); + } + {// difference (subtract empty) + Geometry geom = operatorDifference.execute(mp, new Polyline(), + null, null); + // assertTrue(geom.getGeometryType() == + // Geometry.GeometryType.MultiPoint); + MultiPoint res = (MultiPoint) geom; + assertTrue(res.getPointCount() == 3); + Point2D pt_1 = res.getXY(0); + assertTrue(Point2D.distance(pt_1, new Point2D(0, 10)) < 1e-10); + } + + } + + @Test + public void testPointPolyline() { + Polyline polyline = new Polyline(); + polyline.startPath(0, 0); + polyline.lineTo(0, 10); + polyline.lineTo(20, 10); + polyline.lineTo(20, 0); + Point p_1 = new Point(0, 5, 7); + Point p_2 = new Point(0, 10, 7); + Point p3 = new Point(1, 5, 7); + // OperatorFactoryLocal projEnv = OperatorFactoryLocal.getInstance(); + OperatorIntersection operatorIntersection = (OperatorIntersection) projEnv + .getOperator(Operator.Type.Intersection); + OperatorDifference operatorDiff = (OperatorDifference) projEnv + .getOperator(Operator.Type.Difference); + OperatorUnion operatorUnion = (OperatorUnion) projEnv + .getOperator(Operator.Type.Union); + OperatorSymmetricDifference operatorSymDiff = (OperatorSymmetricDifference) projEnv + .getOperator(Operator.Type.SymmetricDifference); + + {// intersect case1 + Geometry geom = operatorIntersection.execute(polyline, p_1, null, + null); + // assertTrue(geom.getType() == Geometry::enum_point); + Point res = (Point) geom; + Point2D pt_1 = res.getXY(); + assertTrue(Point2D.distance(pt_1, new Point2D(0, 5)) < 1e-10); + assertTrue(res.getAttributeAsDbl(VertexDescription.Semantics.Z, 0) == 7); + } + {// intersect case2 + Geometry geom = operatorIntersection.execute(polyline, p_2, null, + null); + // assertTrue(geom.getType() == Geometry::enum_point); + Point res = (Point) geom; + Point2D pt_1 = res.getXY(); + assertTrue(Point2D.distance(pt_1, new Point2D(0, 10)) < 1e-10); + assertTrue(res.getAttributeAsDbl(VertexDescription.Semantics.Z, 0) == 7); + } + {// intersect case3 + Geometry geom = operatorIntersection.execute(polyline, p3, null, + null); + // assertTrue(geom.getType() == Geometry::enum_point); + assertTrue(geom.isEmpty()); + assertTrue(geom.hasAttribute(VertexDescription.Semantics.Z)); + } + + {// difference case1 + Geometry geom = operatorDiff.execute(polyline, p_1, null, null); + // assertTrue(geom.getType() == Geometry.GeometryType.Polyline); + Polyline res = (Polyline) geom; + assertTrue(res.getPointCount() == 4); + } + {// difference case2 + Geometry geom = operatorDiff.execute(p_1, polyline, null, null); + // assertTrue(geom.getType() == Geometry::enum_point); + Point res = (Point) geom; + assertTrue(res.isEmpty()); + } + {// difference case3 + Geometry geom = operatorDiff.execute(p_2, polyline, null, null); + // assertTrue(geom.getType() == Geometry::enum_point); + Point res = (Point) geom; + assertTrue(res.isEmpty()); + } + {// difference case4 + Geometry geom = operatorDiff.execute(p3, polyline, null, null); + // assertTrue(geom.getType() == Geometry::enum_point); + Point res = (Point) geom; + Point2D pt_1 = res.getXY(); + assertTrue(Point2D.distance(pt_1, new Point2D(1, 5)) < 1e-10); + } + + {// union case1 + Geometry geom = operatorUnion.execute(p_1, polyline, null, null); + // assertTrue(geom.getType() == Geometry.GeometryType.Polyline); + Polyline res = (Polyline) geom; + assertTrue(!res.isEmpty()); + } + {// union case2 + Geometry geom = operatorUnion.execute(polyline, p_1, null, null); + // assertTrue(geom.getType() == Geometry.GeometryType.Polyline); + Polyline res = (Polyline) geom; + assertTrue(!res.isEmpty()); + } + + {// symmetric difference case1 + Geometry geom = operatorSymDiff.execute(polyline, p_1, null, null); + assertTrue(geom.getType().value() == Geometry.GeometryType.Polyline); + Polyline res = (Polyline) (geom); + assertTrue(!res.isEmpty()); + } + {// symmetric difference case2 + Geometry geom = operatorSymDiff.execute(p_1, polyline, null, null); + assertTrue(geom.getType().value() == Geometry.GeometryType.Polyline); + Polyline res = (Polyline) (geom); + assertTrue(!res.isEmpty()); + } + } + + @Test + public void testPolylinePolylineIntersectionExtended() { + {// crossing intersection + Polyline basePl = new Polyline(); + basePl.startPath(0, 10); + basePl.lineTo(100, 10); + + Polyline compPl = new Polyline(); + compPl.startPath(50, 0); + compPl.lineTo(50, 100); + + OperatorIntersection op = (OperatorIntersection) projEnv + .getOperator(Operator.Type.Intersection); + GeometryCursor result_cursor = op.execute(new SimpleGeometryCursor( + basePl), new SimpleGeometryCursor(compPl), SpatialReference + .create(4326), null, 3); + + // dimension is 3, means it has to return a point and a polyline + Geometry geom1 = result_cursor.next(); + assertTrue(geom1 != null); + assertTrue(geom1.getDimension() == 0); + assertTrue(geom1.getType().value() == Geometry.GeometryType.MultiPoint); + assertTrue(((MultiPoint) geom1).getPointCount() == 1); + + Geometry geom2 = result_cursor.next(); + assertTrue(geom2 != null); + assertTrue(geom2.getDimension() == 1); + assertTrue(geom2.getType().value() == Geometry.GeometryType.Polyline); + assertTrue(((Polyline) geom2).getPointCount() == 0); + + Geometry geom3 = result_cursor.next(); + assertTrue(geom3 == null); + } + + {// crossing + overlapping intersection + Polyline basePl = new Polyline(); + basePl.startPath(0, 10); + basePl.lineTo(100, 10); + + Polyline compPl = new Polyline(); + compPl.startPath(50, 0); + compPl.lineTo(50, 100); + compPl.lineTo(70, 10); + compPl.lineTo(100, 10); + + OperatorIntersection op = (OperatorIntersection) projEnv + .getOperator(Operator.Type.Intersection); + GeometryCursor result_cursor = op.execute(new SimpleGeometryCursor( + basePl), new SimpleGeometryCursor(compPl), SpatialReference + .create(4326), null, 3); + + // dimension is 3, means it has to return a point and a polyline + Geometry geom1 = result_cursor.next(); + assertTrue(geom1 != null); + assertTrue(geom1.getDimension() == 0); + assertTrue(geom1.getType().value() == Geometry.GeometryType.MultiPoint); + assertTrue(((MultiPoint) geom1).getPointCount() == 1); + + Geometry geom2 = result_cursor.next(); + assertTrue(geom2 != null); + assertTrue(geom2.getDimension() == 1); + assertTrue(geom2.getType().value() == Geometry.GeometryType.Polyline); + assertTrue(((Polyline) geom2).getPathCount() == 1); + assertTrue(((Polyline) geom2).getPointCount() == 2); + + Geometry geom3 = result_cursor.next(); + assertTrue(geom3 == null); + } + } + + @Test + public void testPolygonPolygonIntersectionExtended() { + {// crossing intersection + Polygon basePl = new Polygon(); + basePl.startPath(0, 0); + basePl.lineTo(100, 0); + basePl.lineTo(100, 100); + basePl.lineTo(0, 100); + + Polygon compPl = new Polygon(); + compPl.startPath(100, 100); + compPl.lineTo(200, 100); + compPl.lineTo(200, 200); + compPl.lineTo(100, 200); + + OperatorIntersection op = (OperatorIntersection) projEnv + .getOperator(Operator.Type.Intersection); + GeometryCursor result_cursor = op.execute(new SimpleGeometryCursor( + basePl), new SimpleGeometryCursor(compPl), SpatialReference + .create(4326), null, 7); + + Geometry geom1 = result_cursor.next(); + assertTrue(geom1 != null); + assertTrue(geom1.getDimension() == 0); + assertTrue(geom1.getType().value() == Geometry.GeometryType.MultiPoint); + assertTrue(((MultiPoint) geom1).getPointCount() == 1); + + Geometry geom2 = result_cursor.next(); + assertTrue(geom2 != null); + assertTrue(geom2.getDimension() == 1); + assertTrue(geom2.getType().value() == Geometry.GeometryType.Polyline); + assertTrue(((Polyline) geom2).getPointCount() == 0); + + Geometry geom3 = result_cursor.next(); + assertTrue(geom3 != null); + assertTrue(geom3.getDimension() == 2); + assertTrue(geom3.getType().value() == Geometry.GeometryType.Polygon); + assertTrue(((Polygon) geom3).getPointCount() == 0); + + Geometry geom4 = result_cursor.next(); + assertTrue(geom4 == null); + } + + {// crossing + overlapping intersection + Polygon basePl = new Polygon(); + basePl.startPath(0, 0); + basePl.lineTo(100, 0); + basePl.lineTo(100, 100); + basePl.lineTo(0, 100); + + Polygon compPl = new Polygon(); + compPl.startPath(100, 100); + compPl.lineTo(200, 100); + compPl.lineTo(200, 200); + compPl.lineTo(100, 200); + + compPl.startPath(100, 20); + compPl.lineTo(200, 20); + compPl.lineTo(200, 40); + compPl.lineTo(100, 40); + + compPl.startPath(-10, -10); + compPl.lineTo(-10, 10); + compPl.lineTo(10, 10); + compPl.lineTo(10, -10); + + OperatorIntersection op = (OperatorIntersection) projEnv + .getOperator(Operator.Type.Intersection); + GeometryCursor result_cursor = op.execute(new SimpleGeometryCursor( + basePl), new SimpleGeometryCursor(compPl), SpatialReference + .create(4326), null, 7); + + // dimension is 3, means it has to return a point and a polyline + Geometry geom1 = result_cursor.next(); + assertTrue(geom1 != null); + assertTrue(geom1.getDimension() == 0); + assertTrue(geom1.getType().value() == Geometry.GeometryType.MultiPoint); + assertTrue(((MultiPoint) geom1).getPointCount() == 1); + + Geometry geom2 = result_cursor.next(); + assertTrue(geom2 != null); + assertTrue(geom2.getDimension() == 1); + assertTrue(geom2.getType().value() == Geometry.GeometryType.Polyline); + assertTrue(((Polyline) geom2).getPathCount() == 1); + assertTrue(((Polyline) geom2).getPointCount() == 2); + + Geometry geom3 = result_cursor.next(); + assertTrue(geom3 != null); + assertTrue(geom3.getDimension() == 2); + assertTrue(geom3.getType().value() == Geometry.GeometryType.Polygon); + assertTrue(((Polygon) geom3).getPathCount() == 1); + assertTrue(((Polygon) geom3).getPointCount() == 4); + + Geometry geom4 = result_cursor.next(); + assertTrue(geom4 == null); + } + } + + @Test + public void testFromProjection() { + MultiPoint multiPointInitial = new MultiPoint(); + multiPointInitial.add(-20037508.342789244, 3360107.7777777780); + multiPointInitial.add(-18924313.434856508, 3360107.7777777780); + multiPointInitial.add(-18924313.434856508, -3360107.7777777780); + multiPointInitial.add(-20037508.342789244, -3360107.7777777780); + Geometry geom1 = ((MultiPoint) multiPointInitial); + + SpatialReference sr = SpatialReference.create(102100); + + Envelope2D env = new Envelope2D(); + env.setCoords(/* xmin */-20037508.342788246, /* ymin */ + -30240971.958386172, /* xmax */20037508.342788246, /* ymax */ + 30240971.958386205); + // /*xmin*/ -20037508.342788246 + // /*ymin*/ -30240971.958386172 + // /*xmax*/ 20037508.342788246 + // /*ymax*/ 30240971.958386205 + + Polygon poly = new Polygon(); + poly.startPath(env.xmin, env.ymin); + poly.lineTo(env.xmin, env.ymax); + poly.lineTo(env.xmax, env.ymax); + poly.lineTo(env.xmax, env.ymin); + + Geometry geom2 = new Envelope(env); + // Geometry geom2 = poly; + + OperatorIntersection operatorIntersection = (OperatorIntersection) OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Intersection); + + MultiPoint multiPointOut = (MultiPoint) (operatorIntersection.execute( + geom1, geom2, sr, null)); + + assertTrue(multiPointOut.getCoordinates2D().length == 2); + assertTrue(multiPointOut.getCoordinates2D()[0].x == -18924313.434856508); + assertTrue(multiPointOut.getCoordinates2D()[0].y == 3360107.7777777780); + assertTrue(multiPointOut.getCoordinates2D()[1].x == -18924313.434856508); + assertTrue(multiPointOut.getCoordinates2D()[1].y == -3360107.7777777780); + } + +} diff --git a/unittest/com/esri/core/geometry/TestIntervalTree.java b/unittest/com/esri/core/geometry/TestIntervalTree.java new file mode 100644 index 00000000..59b85eb7 --- /dev/null +++ b/unittest/com/esri/core/geometry/TestIntervalTree.java @@ -0,0 +1,311 @@ +package com.esri.core.geometry; + +import java.util.ArrayList; +import java.util.Random; +import junit.framework.TestCase; +import org.junit.Test; + +public class TestIntervalTree extends TestCase { + @Override + protected void setUp() throws Exception { + super.setUp(); + } + + @Override + protected void tearDown() throws Exception { + super.tearDown(); + } + + @Test + public static void testIntervalTree() { + ArrayList intervals = new ArrayList(0); + + Envelope1D env0 = new Envelope1D(2, 3); + Envelope1D env1 = new Envelope1D(5, 13); + Envelope1D env2 = new Envelope1D(6, 9); + Envelope1D env3 = new Envelope1D(8, 10); + Envelope1D env4 = new Envelope1D(11, 12); + Envelope1D env5 = new Envelope1D(1, 3); + Envelope1D env6 = new Envelope1D(0, 2); + Envelope1D env7 = new Envelope1D(4, 7); + Envelope1D env8; + + intervals.add(env0); + intervals.add(env1); + intervals.add(env2); + intervals.add(env3); + intervals.add(env4); + intervals.add(env5); + intervals.add(env6); + intervals.add(env7); + + int counter; + IntervalTreeImpl intervalTree = new IntervalTreeImpl(); + intervalTree.reset(intervals, false); + IntervalTreeImpl.IntervalTreeIteratorImpl iterator = intervalTree + .getIterator(new Envelope1D(-1, 14), 0.0); + + counter = 0; + while (iterator.next() != -1) + counter++; + assertTrue(counter == 8); + + iterator.resetIterator(new Envelope1D(2.5, 10.5), 0.0); + counter = 0; + while (iterator.next() != -1) + counter++; + assertTrue(counter == 6); + + iterator.resetIterator(5.0, 0.0); + counter = 0; + while (iterator.next() != -1) + counter++; + assertTrue(counter == 2); + + iterator.resetIterator(7, 0.0); + counter = 0; + while (iterator.next() != -1) + counter++; + assertTrue(counter == 3); + + iterator.resetIterator(new Envelope1D(2.0, 10.5), 0.0); + counter = 0; + while (iterator.next() != -1) + counter++; + assertTrue(counter == 7); + + iterator.resetIterator(new Envelope1D(2.5, 11), 0.0); + counter = 0; + while (iterator.next() != -1) + counter++; + assertTrue(counter == 7); + + iterator.resetIterator(new Envelope1D(2.1, 2.5), 0.0); + counter = 0; + while (iterator.next() != -1) + counter++; + assertTrue(counter == 2); + + iterator.resetIterator(new Envelope1D(2.1, 5), 0.0); + counter = 0; + while (iterator.next() != -1) + counter++; + assertTrue(counter == 4); + + iterator.resetIterator(new Envelope1D(2.0, 5), 0.0); + counter = 0; + while (iterator.next() != -1) + counter++; + assertTrue(counter == 5); + + iterator.resetIterator(new Envelope1D(5.0, 11), 0.0); + counter = 0; + while (iterator.next() != -1) + counter++; + assertTrue(counter == 5); + + iterator.resetIterator(new Envelope1D(8, 10.5), 0.0); + counter = 0; + while (iterator.next() != -1) + counter++; + assertTrue(counter == 3); + + iterator.resetIterator(new Envelope1D(10, 11), 0.0); + counter = 0; + while (iterator.next() != -1) + counter++; + assertTrue(counter == 3); + + iterator.resetIterator(new Envelope1D(10, 10.9), 0.0); + counter = 0; + while (iterator.next() != -1) + counter++; + assertTrue(counter == 2); + + iterator.resetIterator(new Envelope1D(11.5, 12), 0.0); + counter = 0; + while (iterator.next() != -1) + counter++; + assertTrue(counter == 2); + + env0 = new Envelope1D(0, 4); + env1 = new Envelope1D(6, 7); + env2 = new Envelope1D(9, 10); + env3 = new Envelope1D(9, 11); + env4 = new Envelope1D(7, 12); + env5 = new Envelope1D(13, 15); + env6 = new Envelope1D(1, 6); + env7 = new Envelope1D(3, 3); + env8 = new Envelope1D(8, 8); + + intervals.clear(); + intervals.add(env0); + intervals.add(env1); + intervals.add(env2); + intervals.add(env3); + intervals.add(env4); + intervals.add(env5); + intervals.add(env6); + intervals.add(env7); + intervals.add(env8); + + intervalTree.reset(intervals, true); + intervalTree.insert(0); + intervalTree.insert(1); + intervalTree.insert(2); + intervalTree.insert(3); + intervalTree.insert(4); + intervalTree.insert(5); + intervalTree.insert(6); + intervalTree.insert(7); + intervalTree.insert(8); + + iterator = intervalTree.getIterator(new Envelope1D(8, 8), 0.0); + + counter = 0; + while (iterator.next() != -1) + counter++; + assertTrue(counter == 2); + + iterator.resetIterator(new Envelope1D(3, 7), 0.0); + counter = 0; + while (iterator.next() != -1) + counter++; + assertTrue(counter == 5); + + iterator.resetIterator(new Envelope1D(1, 3), 0.0); + counter = 0; + while (iterator.next() != -1) + counter++; + assertTrue(counter == 3); + + iterator.resetIterator(new Envelope1D(6, 9), 0.0); + counter = 0; + while (iterator.next() != -1) + counter++; + assertTrue(counter == 6); + + iterator.resetIterator(new Envelope1D(10, 14), 0.0); + counter = 0; + while (iterator.next() != -1) + counter++; + assertTrue(counter == 4); + + env0 = new Envelope1D(11, 14); + env1 = new Envelope1D(21, 36); + env2 = new Envelope1D(15, 19); + env3 = new Envelope1D(3, 8); + env4 = new Envelope1D(34, 38); + env5 = new Envelope1D(23, 27); + env6 = new Envelope1D(6, 36); + + intervals.clear(); + intervals.add(env0); + intervals.add(env1); + intervals.add(env2); + intervals.add(env3); + intervals.add(env4); + intervals.add(env5); + intervals.add(env6); + + intervalTree.reset(intervals, false); + iterator = intervalTree.getIterator(new Envelope1D(50, 50), 0.0); + assert (iterator.next() == -1); + } + + @Test + public static void testIntervalTreeRandomConstruction() { + @SuppressWarnings("unused") + int pointcount = 0; + int passcount = 1000; + int figureSize = 50; + Envelope env = new Envelope(); + env.setCoords(-10000, -10000, 10000, 10000); + RandomCoordinateGenerator generator = new RandomCoordinateGenerator( + Math.max(figureSize, 10000), env, 0.001); + Random random = new Random(2013); + int rand_max = 98765; + ArrayList intervals = new ArrayList(); + AttributeStreamOfInt8 intervalsFound = new AttributeStreamOfInt8(0); + + for (int i = 0; i < passcount; i++) { + int r = figureSize; + if (r < 3) + continue; + Polygon poly = new Polygon(); + Point pt; + for (int j = 0; j < r; j++) { + int rand = random.nextInt(rand_max); + boolean bRandomNew = (r > 10) + && ((1.0 * rand) / rand_max > 0.95); + pt = generator.GetRandomCoord(); + if (j == 0 || bRandomNew) + poly.startPath(pt); + else + poly.lineTo(pt); + } + + { + intervals.clear(); + SegmentIterator seg_iter = poly.querySegmentIterator(); + Envelope1D interval; + + Envelope1D range = poly.queryInterval( + VertexDescription.Semantics.POSITION, 0); + range.vmin -= 0.01; + range.vmax += 0.01; + + while (seg_iter.nextPath()) { + while (seg_iter.hasNextSegment()) { + Segment segment = seg_iter.nextSegment(); + interval = segment.queryInterval( + VertexDescription.Semantics.POSITION, 0); + intervals.add(interval); + } + } + + intervalsFound.resize(intervals.size(), 0); + + // Just test construction for assertions + IntervalTreeImpl intervalTree = new IntervalTreeImpl(intervals, + true); + + for (int j = 0; j < intervals.size(); j++) + intervalTree.insert(j); + + IntervalTreeImpl.IntervalTreeIteratorImpl iterator = intervalTree + .getIterator(range, 0.0); + + int count = 0; + int handle; + while ((handle = iterator.next()) != -1) { + count++; + intervalsFound.write(handle, (byte) 1); + } + + assertTrue(count == intervals.size()); + + for (int j = 0; j < intervalsFound.size(); j++) { + interval = intervals.get(j); + assertTrue(intervalsFound.read(j) == 1); + } + + for (int j = 0; j < intervals.size() >> 1; j++) + intervalTree.remove(j); + + iterator.resetIterator(range, 0.0); + + count = 0; + while ((handle = iterator.next()) != -1) { + count++; + intervalsFound.write(handle, (byte) 1); + } + + assertTrue(count == intervals.size() - (intervals.size() >> 1)); + + for (int j = (intervals.size() >> 1); j < intervals.size(); j++) + intervalTree.remove(j); + } + } + } +} diff --git a/unittest/com/esri/core/geometry/TestJSonGeometry.java b/unittest/com/esri/core/geometry/TestJSonGeometry.java new file mode 100644 index 00000000..d380a84b --- /dev/null +++ b/unittest/com/esri/core/geometry/TestJSonGeometry.java @@ -0,0 +1,47 @@ +package com.esri.core.geometry; + +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import java.util.Set; +import junit.framework.TestCase; +import org.junit.Test; + +public class TestJSonGeometry extends TestCase { + @Override + protected void setUp() throws Exception { + super.setUp(); + } + + @Override + protected void tearDown() throws Exception { + super.tearDown(); + } + + @Test + public void testGetSpatialReferenceFor4326() { + String completeStr = "GEOGCS[\"GCS_Sphere\",DATUM[\"D_Sphere\"," + + "SPHEROID[\"Sphere\",6371000.0,0.0]],PRIMEM[\"Greenwich\",0.0]," + + "UNIT[\"Degree\",0.0174532925199433]]"; + + // 4326 GCS_WGS_1984 + SpatialReference sr = SpatialReference.create(completeStr); + assertNotNull(sr); + } +} + +final class HashMapClassForTesting { + static Map SR_WKI_WKTs = new HashMap() { + /** + * added to get rid of warning + */ + private static final long serialVersionUID = 8630934425353750539L; + + { + put(4035, + "GEOGCS[\"GCS_Sphere\",DATUM[\"D_Sphere\"," + + "SPHEROID[\"Sphere\",6371000.0,0.0]],PRIMEM[\"Greenwich\",0.0]," + + "UNIT[\"Degree\",0.0174532925199433]]"); + } + }; +} diff --git a/unittest/com/esri/core/geometry/TestJSonToGeomFromWkiOrWkt_CR177613.java b/unittest/com/esri/core/geometry/TestJSonToGeomFromWkiOrWkt_CR177613.java new file mode 100644 index 00000000..a7307556 --- /dev/null +++ b/unittest/com/esri/core/geometry/TestJSonToGeomFromWkiOrWkt_CR177613.java @@ -0,0 +1,123 @@ +package com.esri.core.geometry; + +import java.io.IOException; +import org.codehaus.jackson.JsonFactory; +import org.codehaus.jackson.JsonParseException; +import org.codehaus.jackson.JsonParser; +import org.codehaus.jackson.JsonToken; +import junit.framework.TestCase; +import org.junit.Test; + +public class TestJSonToGeomFromWkiOrWkt_CR177613 extends TestCase { + JsonFactory factory = new JsonFactory(); + + @Override + protected void setUp() throws Exception { + super.setUp(); + } + + @Override + protected void tearDown() throws Exception { + super.tearDown(); + } + + @Test + public void testPolygonWithEmptyWKT_NoWKI() throws JsonParseException, + IOException { + String jsonStringPg = "{ \"rings\" :[ [ [-97.06138,32.837], [-97.06133,32.836], " + + "[-97.06124,32.834], [-97.06127,32.832], [-97.06138,32.837] ], " + + "[ [-97.06326,32.759], [-97.06298,32.755], [-97.06153,32.749], [-97.06326,32.759] ]], " + + "\"spatialReference\" : {\"wkt\" : \"\"}}"; + JsonParser jsonParserPg = factory.createJsonParser(jsonStringPg); + jsonParserPg.nextToken(); + + MapGeometry mapGeom = GeometryEngine.jsonToGeometry(jsonParserPg); + Utils.showProjectedGeometryInfo(mapGeom); + SpatialReference sr = mapGeom.getSpatialReference(); + assertTrue(sr == null); + } + + @Test + public void testOnlyWKI() throws JsonParseException, IOException { + String jsonStringSR = "{\"wkid\" : 4326}"; + JsonParser jsonParserSR = factory.createJsonParser(jsonStringSR); + jsonParserSR.nextToken(); + + MapGeometry mapGeom = GeometryEngine.jsonToGeometry(jsonParserSR); + Utils.showProjectedGeometryInfo(mapGeom); + SpatialReference sr = mapGeom.getSpatialReference(); + assertTrue(sr == null); + } + + @Test + public void testMP2onCR175871() throws Exception { + Polygon pg = new Polygon(); + pg.startPath(-50, 10); + pg.lineTo(-50, 12); + pg.lineTo(-45, 12); + pg.lineTo(-45, 10); + + Polygon pg1 = new Polygon(); + pg1.startPath(-45, 10); + pg1.lineTo(-40, 10); + pg1.lineTo(-40, 8); + pg.add(pg1, false); + + try { + String jSonStr = GeometryEngine.geometryToJson(4326, pg); + JsonFactory jf = new JsonFactory(); + + JsonParser jp = jf.createJsonParser(jSonStr); + jp.nextToken(); + MapGeometry mg = GeometryEngine.jsonToGeometry(jp); + Geometry gm = mg.getGeometry(); + assertEquals(Geometry.Type.Polygon, gm.getType()); + + Polygon pgNew = (Polygon) gm; + + assertEquals(pgNew.getPathCount(), pg.getPathCount()); + assertEquals(pgNew.getPointCount(), pg.getPointCount()); + assertEquals(pgNew.getSegmentCount(), pg.getSegmentCount()); + + assertEquals(pgNew.getPoint(0).getX(), pg.getPoint(0).getX(), + 0.000000001); + assertEquals(pgNew.getPoint(1).getX(), pg.getPoint(1).getX(), + 0.000000001); + assertEquals(pgNew.getPoint(2).getX(), pg.getPoint(2).getX(), + 0.000000001); + assertEquals(pgNew.getPoint(3).getX(), pg.getPoint(3).getX(), + 0.000000001); + + assertEquals(pgNew.getPoint(0).getY(), pg.getPoint(0).getY(), + 0.000000001); + assertEquals(pgNew.getPoint(1).getY(), pg.getPoint(1).getY(), + 0.000000001); + assertEquals(pgNew.getPoint(2).getY(), pg.getPoint(2).getY(), + 0.000000001); + assertEquals(pgNew.getPoint(3).getY(), pg.getPoint(3).getY(), + 0.000000001); + } catch (Exception ex) { + String err = ex.getMessage(); + System.out.print(err); + throw ex; + } + } + + public static int fromJsonToWkid(JsonParser parser) + throws JsonParseException, IOException { + int wkid = 0; + if (parser.getCurrentToken() != JsonToken.START_OBJECT) { + return 0; + } + + while (parser.nextToken() != JsonToken.END_OBJECT) { + String fieldName = parser.getCurrentName(); + + if ("wkid".equals(fieldName)) { + parser.nextToken(); + wkid = parser.getIntValue(); + } + } + return wkid; + } +} diff --git a/unittest/com/esri/core/geometry/TestJsonParser.java b/unittest/com/esri/core/geometry/TestJsonParser.java new file mode 100644 index 00000000..7e493dd1 --- /dev/null +++ b/unittest/com/esri/core/geometry/TestJsonParser.java @@ -0,0 +1,663 @@ +package com.esri.core.geometry; + +import java.io.IOException; +import junit.framework.TestCase; +import org.codehaus.jackson.JsonFactory; +import org.codehaus.jackson.JsonParseException; +import org.codehaus.jackson.JsonParser; +import org.codehaus.jackson.JsonToken; +import org.junit.Assert; +import org.junit.Test; + +public class TestJsonParser extends TestCase { + JsonFactory factory = new JsonFactory(); + SpatialReference spatialReferenceWebMerc1 = SpatialReference.create(102100); + SpatialReference spatialReferenceWebMerc2 = SpatialReference + .create(spatialReferenceWebMerc1.getLatestID()); + SpatialReference spatialReferenceWGS84 = SpatialReference.create(4326); + + @Override + protected void setUp() throws Exception { + super.setUp(); + } + + @Override + protected void tearDown() throws Exception { + super.tearDown(); + } + + @Test + public void test3DPoint() throws JsonParseException, IOException { + String jsonString3DPt = "{\"x\" : -118.15, \"y\" : 33.80, \"z\" : 10.0, \"spatialReference\" : {\"wkid\" : 4326}}"; + + JsonParser jsonParser3DPt = factory.createJsonParser(jsonString3DPt); + MapGeometry point3DMP = GeometryEngine.jsonToGeometry(jsonParser3DPt); + assertTrue(-118.15 == ((Point) point3DMP.getGeometry()).getX()); + assertTrue(33.80 == ((Point) point3DMP.getGeometry()).getY()); + // FIXME add 3D support + // assertTrue(10.0 == ((Point)point3DMP.getGeometry()).getZ()); + assertTrue(spatialReferenceWGS84.getID() == point3DMP + .getSpatialReference().getID()); + } + + @Test + public void test3DPoint1() throws JsonParseException, IOException { + Point point1 = new Point(10.0, 20.0); + Point pointEmpty = new Point(); + { + JsonParser pointWebMerc1Parser = factory + .createJsonParser(GeometryEngine.geometryToJson( + spatialReferenceWebMerc1, point1)); + MapGeometry pointWebMerc1MP = GeometryEngine + .jsonToGeometry(pointWebMerc1Parser); + assertTrue(point1.getX() == ((Point) pointWebMerc1MP.getGeometry()) + .getX()); + assertTrue(point1.getY() == ((Point) pointWebMerc1MP.getGeometry()) + .getY()); + int srIdOri = spatialReferenceWebMerc1.getID(); + int srIdAfter = pointWebMerc1MP.getSpatialReference().getID(); + assertTrue(srIdOri == srIdAfter || srIdAfter == 3857); + + pointWebMerc1Parser = factory.createJsonParser(GeometryEngine + .geometryToJson(null, point1)); + pointWebMerc1MP = GeometryEngine + .jsonToGeometry(pointWebMerc1Parser); + assertTrue(null == pointWebMerc1MP.getSpatialReference()); + + String pointEmptyString = GeometryEngine.geometryToJson( + spatialReferenceWebMerc1, pointEmpty); + pointWebMerc1Parser = factory.createJsonParser(pointEmptyString); + // FIXME + pointWebMerc1MP = GeometryEngine + .jsonToGeometry(pointWebMerc1Parser); + assertTrue(pointWebMerc1MP.getGeometry().isEmpty()); + int srIdOri2 = spatialReferenceWebMerc1.getID(); + int srIdAfter2 = pointWebMerc1MP.getSpatialReference().getID(); + assertTrue(srIdOri2 == srIdAfter2 || srIdAfter2 == 3857); + } + } + + @Test + public void test3DPoint2() throws JsonParseException, IOException { + { + Point point1 = new Point(10.0, 20.0); + JsonParser pointWebMerc2Parser = factory + .createJsonParser(GeometryEngine.geometryToJson( + spatialReferenceWebMerc2, point1)); + MapGeometry pointWebMerc2MP = GeometryEngine + .jsonToGeometry(pointWebMerc2Parser); + assertTrue(point1.getX() == ((Point) pointWebMerc2MP.getGeometry()) + .getX()); + assertTrue(point1.getY() == ((Point) pointWebMerc2MP.getGeometry()) + .getY()); + assertTrue(spatialReferenceWebMerc2.getLatestID() == pointWebMerc2MP + .getSpatialReference().getLatestID()); + } + } + + @Test + public void test3DPoint3() throws JsonParseException, IOException { + { + Point point1 = new Point(10.0, 20.0); + JsonParser pointWgs84Parser = factory + .createJsonParser(GeometryEngine.geometryToJson( + spatialReferenceWGS84, point1)); + MapGeometry pointWgs84MP = GeometryEngine + .jsonToGeometry(pointWgs84Parser); + assertTrue(point1.getX() == ((Point) pointWgs84MP.getGeometry()) + .getX()); + assertTrue(point1.getY() == ((Point) pointWgs84MP.getGeometry()) + .getY()); + assertTrue(spatialReferenceWGS84.getID() == pointWgs84MP + .getSpatialReference().getID()); + } + } + + @Test + public void testMultiPoint() throws JsonParseException, IOException { + MultiPoint multiPoint1 = new MultiPoint(); + multiPoint1.add(-97.06138, 32.837); + multiPoint1.add(-97.06133, 32.836); + multiPoint1.add(-97.06124, 32.834); + multiPoint1.add(-97.06127, 32.832); + + { + JsonParser mPointWgs84Parser = factory + .createJsonParser(GeometryEngine.geometryToJson( + spatialReferenceWGS84, multiPoint1)); + MapGeometry mPointWgs84MP = GeometryEngine + .jsonToGeometry(mPointWgs84Parser); + assertTrue(multiPoint1.getPointCount() == ((MultiPoint) mPointWgs84MP + .getGeometry()).getPointCount()); + assertTrue(multiPoint1.getPoint(0).getX() == ((MultiPoint) mPointWgs84MP + .getGeometry()).getPoint(0).getX()); + assertTrue(multiPoint1.getPoint(0).getY() == ((MultiPoint) mPointWgs84MP + .getGeometry()).getPoint(0).getY()); + int lastIndex = multiPoint1.getPointCount() - 1; + assertTrue(multiPoint1.getPoint(lastIndex).getX() == ((MultiPoint) mPointWgs84MP + .getGeometry()).getPoint(lastIndex).getX()); + assertTrue(multiPoint1.getPoint(lastIndex).getY() == ((MultiPoint) mPointWgs84MP + .getGeometry()).getPoint(lastIndex).getY()); + + assertTrue(spatialReferenceWGS84.getID() == mPointWgs84MP + .getSpatialReference().getID()); + + MultiPoint mPointEmpty = new MultiPoint(); + String mPointEmptyString = GeometryEngine.geometryToJson( + spatialReferenceWGS84, mPointEmpty); + mPointWgs84Parser = factory.createJsonParser(mPointEmptyString); + + mPointWgs84MP = GeometryEngine.jsonToGeometry(mPointWgs84Parser); + assertTrue(mPointWgs84MP.getGeometry().isEmpty()); + assertTrue(spatialReferenceWGS84.getID() == mPointWgs84MP + .getSpatialReference().getID()); + + } + } + + @Test + public void testPolyline() throws JsonParseException, IOException { + Polyline polyline = new Polyline(); + polyline.startPath(-97.06138, 32.837); + polyline.lineTo(-97.06133, 32.836); + polyline.lineTo(-97.06124, 32.834); + polyline.lineTo(-97.06127, 32.832); + + polyline.startPath(-97.06326, 32.759); + polyline.lineTo(-97.06298, 32.755); + + { + JsonParser polylinePathsWgs84Parser = factory + .createJsonParser(GeometryEngine.geometryToJson( + spatialReferenceWGS84, polyline)); + MapGeometry mPolylineWGS84MP = GeometryEngine + .jsonToGeometry(polylinePathsWgs84Parser); + + assertTrue(polyline.getPointCount() == ((Polyline) mPolylineWGS84MP + .getGeometry()).getPointCount()); + assertTrue(polyline.getPoint(0).getX() == ((Polyline) mPolylineWGS84MP + .getGeometry()).getPoint(0).getX()); + assertTrue(polyline.getPoint(0).getY() == ((Polyline) mPolylineWGS84MP + .getGeometry()).getPoint(0).getY()); + + assertTrue(polyline.getPathCount() == ((Polyline) mPolylineWGS84MP + .getGeometry()).getPathCount()); + assertTrue(polyline.getSegmentCount() == ((Polyline) mPolylineWGS84MP + .getGeometry()).getSegmentCount()); + assertTrue(polyline.getSegmentCount(0) == ((Polyline) mPolylineWGS84MP + .getGeometry()).getSegmentCount(0)); + assertTrue(polyline.getSegmentCount(1) == ((Polyline) mPolylineWGS84MP + .getGeometry()).getSegmentCount(1)); + + int lastIndex = polyline.getPointCount() - 1; + assertTrue(polyline.getPoint(lastIndex).getX() == ((Polyline) mPolylineWGS84MP + .getGeometry()).getPoint(lastIndex).getX()); + assertTrue(polyline.getPoint(lastIndex).getY() == ((Polyline) mPolylineWGS84MP + .getGeometry()).getPoint(lastIndex).getY()); + + assertTrue(spatialReferenceWGS84.getID() == mPolylineWGS84MP + .getSpatialReference().getID()); + + Polyline emptyPolyline = new Polyline(); + String emptyString = GeometryEngine.geometryToJson( + spatialReferenceWGS84, emptyPolyline); + mPolylineWGS84MP = GeometryEngine.jsonToGeometry(factory + .createJsonParser(emptyString)); + assertTrue(mPolylineWGS84MP.getGeometry().isEmpty()); + assertTrue(spatialReferenceWGS84.getID() == mPolylineWGS84MP + .getSpatialReference().getID()); + } + } + + @Test + public void testPolygon() throws JsonParseException, IOException { + Polygon polygon = new Polygon(); + polygon.startPath(-97.06138, 32.837); + polygon.lineTo(-97.06133, 32.836); + polygon.lineTo(-97.06124, 32.834); + polygon.lineTo(-97.06127, 32.832); + + polygon.startPath(-97.06326, 32.759); + polygon.lineTo(-97.06298, 32.755); + + { + JsonParser polygonPathsWgs84Parser = factory + .createJsonParser(GeometryEngine.geometryToJson( + spatialReferenceWGS84, polygon)); + MapGeometry mPolygonWGS84MP = GeometryEngine + .jsonToGeometry(polygonPathsWgs84Parser); + + assertTrue(polygon.getPointCount() + 1 == ((Polygon) mPolygonWGS84MP + .getGeometry()).getPointCount()); + assertTrue(polygon.getPoint(0).getX() == ((Polygon) mPolygonWGS84MP + .getGeometry()).getPoint(0).getX()); + assertTrue(polygon.getPoint(0).getY() == ((Polygon) mPolygonWGS84MP + .getGeometry()).getPoint(0).getY()); + + assertTrue(polygon.getPathCount() == ((Polygon) mPolygonWGS84MP + .getGeometry()).getPathCount()); + assertTrue(polygon.getSegmentCount() + 1 == ((Polygon) mPolygonWGS84MP + .getGeometry()).getSegmentCount()); + assertTrue(polygon.getSegmentCount(0) == ((Polygon) mPolygonWGS84MP + .getGeometry()).getSegmentCount(0)); + assertTrue(polygon.getSegmentCount(1) + 1 == ((Polygon) mPolygonWGS84MP + .getGeometry()).getSegmentCount(1)); + + int lastIndex = polygon.getPointCount() - 1; + assertTrue(polygon.getPoint(lastIndex).getX() == ((Polygon) mPolygonWGS84MP + .getGeometry()).getPoint(lastIndex).getX()); + assertTrue(polygon.getPoint(lastIndex).getY() == ((Polygon) mPolygonWGS84MP + .getGeometry()).getPoint(lastIndex).getY()); + + assertTrue(spatialReferenceWGS84.getID() == mPolygonWGS84MP + .getSpatialReference().getID()); + + Polygon emptyPolygon = new Polygon(); + String emptyPolygonString = GeometryEngine.geometryToJson( + spatialReferenceWGS84, emptyPolygon); + polygonPathsWgs84Parser = factory + .createJsonParser(emptyPolygonString); + mPolygonWGS84MP = GeometryEngine + .jsonToGeometry(polygonPathsWgs84Parser); + + assertTrue(mPolygonWGS84MP.getGeometry().isEmpty()); + assertTrue(spatialReferenceWGS84.getID() == mPolygonWGS84MP + .getSpatialReference().getID()); + } + } + + @Test + public void testEnvelope() throws JsonParseException, IOException { + Envelope envelope = new Envelope(); + envelope.setCoords(-109.55, 25.76, -86.39, 49.94); + + { + JsonParser envelopeWGS84Parser = factory + .createJsonParser(GeometryEngine.geometryToJson( + spatialReferenceWGS84, envelope)); + MapGeometry envelopeWGS84MP = GeometryEngine + .jsonToGeometry(envelopeWGS84Parser); + assertTrue(envelope.isEmpty() == envelopeWGS84MP.getGeometry() + .isEmpty()); + assertTrue(envelope.getXMax() == ((Envelope) envelopeWGS84MP + .getGeometry()).getXMax()); + assertTrue(envelope.getYMax() == ((Envelope) envelopeWGS84MP + .getGeometry()).getYMax()); + assertTrue(envelope.getXMin() == ((Envelope) envelopeWGS84MP + .getGeometry()).getXMin()); + assertTrue(envelope.getYMin() == ((Envelope) envelopeWGS84MP + .getGeometry()).getYMin()); + assertTrue(spatialReferenceWGS84.getID() == envelopeWGS84MP + .getSpatialReference().getID()); + + Envelope emptyEnvelope = new Envelope(); + String emptyEnvString = GeometryEngine.geometryToJson( + spatialReferenceWGS84, emptyEnvelope); + envelopeWGS84Parser = factory.createJsonParser(emptyEnvString); + envelopeWGS84MP = GeometryEngine + .jsonToGeometry(envelopeWGS84Parser); + + assertTrue(envelopeWGS84MP.getGeometry().isEmpty()); + assertTrue(spatialReferenceWGS84.getID() == envelopeWGS84MP + .getSpatialReference().getID()); + } + } + + @Test + public void testCR181369() throws JsonParseException, IOException { + // CR181369 + { + String jsonStringPointAndWKT = "{\"x\":10.0,\"y\":20.0,\"spatialReference\":{\"wkt\" : \"PROJCS[\\\"NAD83_UTM_zone_15N\\\",GEOGCS[\\\"GCS_North_American_1983\\\",DATUM[\\\"D_North_American_1983\\\",SPHEROID[\\\"GRS_1980\\\",6378137.0,298.257222101]],PRIMEM[\\\"Greenwich\\\",0.0],UNIT[\\\"Degree\\\",0.0174532925199433]],PROJECTION[\\\"Transverse_Mercator\\\"],PARAMETER[\\\"false_easting\\\",500000.0],PARAMETER[\\\"false_northing\\\",0.0],PARAMETER[\\\"central_meridian\\\",-93.0],PARAMETER[\\\"scale_factor\\\",0.9996],PARAMETER[\\\"latitude_of_origin\\\",0.0],UNIT[\\\"Meter\\\",1.0]]\"} }"; + JsonParser jsonParserPointAndWKT = factory + .createJsonParser(jsonStringPointAndWKT); + MapGeometry mapGeom2 = GeometryEngine + .jsonToGeometry(jsonParserPointAndWKT); + String jsonStringPointAndWKT2 = GeometryEngine.geometryToJson( + mapGeom2.getSpatialReference(), mapGeom2.getGeometry()); + JsonParser jsonParserPointAndWKT2 = factory + .createJsonParser(jsonStringPointAndWKT2); + MapGeometry mapGeom3 = GeometryEngine + .jsonToGeometry(jsonParserPointAndWKT2); + assertTrue(((Point) mapGeom2.getGeometry()).getX() == ((Point) mapGeom3 + .getGeometry()).getX()); + assertTrue(((Point) mapGeom2.getGeometry()).getY() == ((Point) mapGeom3 + .getGeometry()).getY()); + assertTrue(mapGeom2.getSpatialReference().getText() + .equals(mapGeom3.getSpatialReference().getText())); + assertTrue(mapGeom2.getSpatialReference().getID() == mapGeom3 + .getSpatialReference().getID()); + } + } + + @Test + public void testSpatialRef() throws JsonParseException, IOException { + // String jsonStringPt = + // "{\"x\":-20037508.342787,\"y\":20037508.342787},\"spatialReference\":{\"wkid\":102100}}"; + String jsonStringPt = "{\"x\":10.0,\"y\":20.0,\"spatialReference\":{\"wkid\": 102100}}";// 102100 + @SuppressWarnings("unused") + String jsonStringPt2 = "{\"x\":10.0,\"y\":20.0,\"spatialReference\":{\"wkid\":4326}}"; + String jsonStringMpt = "{ \"points\" : [ [-97.06138,32.837], [-97.06133,32.836], [-97.06124,32.834], [-97.06127,32.832] ], \"spatialReference\" : {\"wkid\" : 4326}}";// 4326 + String jsonStringMpt3D = "{\"hasZs\" : true,\"points\" : [ [-97.06138,32.837,35.0], [-97.06133,32.836,35.1], [-97.06124,32.834,35.2], [-97.06127,32.832,35.3] ],\"spatialReference\" : {\"wkid\" : 4326}}"; + String jsonStringPl = "{\"paths\" : [ [ [-97.06138,32.837], [-97.06133,32.836], [-97.06124,32.834], [-97.06127,32.832] ], [ [-97.06326,32.759], [-97.06298,32.755] ]],\"spatialReference\" : {\"wkid\" : 4326}}"; + String jsonStringPl3D = "{\"hasMs\" : true,\"paths\" : [[ [-97.06138,32.837,5], [-97.06133,32.836,6], [-97.06124,32.834,7], [-97.06127,32.832,8] ],[ [-97.06326,32.759], [-97.06298,32.755] ]],\"spatialReference\" : {\"wkid\" : 4326}}"; + String jsonStringPg = "{ \"rings\" :[ [ [-97.06138,32.837], [-97.06133,32.836], [-97.06124,32.834], [-97.06127,32.832], [-97.06138,32.837] ], [ [-97.06326,32.759], [-97.06298,32.755], [-97.06153,32.749], [-97.06326,32.759] ]], \"spatialReference\" : {\"wkt\" : \"\"}}"; + String jsonStringPg3D = "{\"hasZs\" : true,\"hasMs\" : true,\"rings\" : [ [ [-97.06138, 32.837, 35.1, 4], [-97.06133, 32.836, 35.2, 4.1], [-97.06124, 32.834, 35.3, 4.2], [-97.06127, 32.832, 35.2, 44.3], [-97.06138, 32.837, 35.1, 4] ],[ [-97.06326, 32.759, 35.4], [-97.06298, 32.755, 35.5], [-97.06153, 32.749, 35.6], [-97.06326, 32.759, 35.4] ]],\"spatialReference\" : {\"wkid\" : 4326}}"; + String jsonStringPg2 = "{ \"spatialReference\" : {\"wkid\" : 4326}, \"rings\" : [[[-118.35,32.81],[-118.42,32.806],[-118.511,32.892],[-118.35,32.81]]]}"; + String jsonStringPg3 = "{ \"spatialReference\": {\"layerName\":\"GAS_POINTS\",\"name\":null,\"sdesrid\":102100,\"wkid\":102100,\"wkt\":null}}"; + String jsonString2SpatialReferences = "{ \"spatialReference\": {\"layerName\":\"GAS_POINTS\",\"name\":null,\"sdesrid\":102100,\"wkid\":102100,\"wkt\":\"GEOGCS[\\\"GCS_WGS_1984\\\",DATUM[\\\"D_WGS_1984\\\",SPHEROID[\\\"WGS_1984\\\",6378137,298.257223563]],PRIMEM[\\\"Greenwich\\\",0],UNIT[\\\"Degree\\\",0.017453292519943295]]\"}}"; + String jsonString2SpatialReferences2 = "{ \"spatialReference\": {\"layerName\":\"GAS_POINTS\",\"name\":null,\"sdesrid\":10,\"wkid\":10,\"wkt\":\"GEOGCS[\\\"GCS_WGS_1984\\\",DATUM[\\\"D_WGS_1984\\\",SPHEROID[\\\"WGS_1984\\\",6378137,298.257223563]],PRIMEM[\\\"Greenwich\\\",0],UNIT[\\\"Degree\\\",0.017453292519943295]]\"}}"; + String jsonStringSR = "{\"wkid\" : 4326}"; + String jsonStringEnv = "{\"xmin\" : -109.55, \"ymin\" : 25.76, \"xmax\" : -86.39, \"ymax\" : 49.94,\"spatialReference\" : {\"wkid\" : 4326}}"; + String jsonStringHongKon = "{\"xmin\" : -122.55, \"ymin\" : 37.65, \"xmax\" : -122.28, \"ymax\" : 37.84,\"spatialReference\" : {\"wkid\" : 4326}}"; + @SuppressWarnings("unused") + String jsonStringWKT = " {\"wkt\" : \"GEOGCS[\\\"GCS_WGS_1984\\\",DATUM[\\\"D_WGS_1984\\\",SPHEROID[\\\"WGS_1984\\\",6378137,298.257223563]],PRIMEM[\\\"Greenwich\\\",0],UNIT[\\\"Degree\\\",0.017453292519943295]]\"}"; + String jsonStringInvalidWKID = "{\"x\":10.0,\"y\":20.0},\"spatialReference\":{\"wkid\":35253523}}"; + String jsonStringOregon = "{\"xmin\":7531831.219849482,\"ymin\":585702.9799639136,\"xmax\":7750143.589982405,\"ymax\":733289.6299999952,\"spatialReference\":{\"wkid\":102726}}"; + + JsonParser jsonParserPt = factory.createJsonParser(jsonStringPt); + JsonParser jsonParserMpt = factory.createJsonParser(jsonStringMpt); + JsonParser jsonParserMpt3D = factory.createJsonParser(jsonStringMpt3D); + JsonParser jsonParserPl = factory.createJsonParser(jsonStringPl); + JsonParser jsonParserPl3D = factory.createJsonParser(jsonStringPl3D); + JsonParser jsonParserPg = factory.createJsonParser(jsonStringPg); + JsonParser jsonParserPg3D = factory.createJsonParser(jsonStringPg3D); + JsonParser jsonParserPg2 = factory.createJsonParser(jsonStringPg2); + @SuppressWarnings("unused") + JsonParser jsonParserSR = factory.createJsonParser(jsonStringSR); + JsonParser jsonParserEnv = factory.createJsonParser(jsonStringEnv); + JsonParser jsonParserPg3 = factory.createJsonParser(jsonStringPg3); + @SuppressWarnings("unused") + JsonParser jsonParserCrazy1 = factory + .createJsonParser(jsonString2SpatialReferences); + @SuppressWarnings("unused") + JsonParser jsonParserCrazy2 = factory + .createJsonParser(jsonString2SpatialReferences2); + JsonParser jsonParserInvalidWKID = factory + .createJsonParser(jsonStringInvalidWKID); + @SuppressWarnings("unused") + JsonParser jsonParseHongKon = factory + .createJsonParser(jsonStringHongKon); + JsonParser jsonParseOregon = factory.createJsonParser(jsonStringOregon); + + MapGeometry mapGeom = GeometryEngine.jsonToGeometry(jsonParserPt); + // showProjectedGeometryInfo(mapGeom); + Assert.assertTrue(mapGeom.getSpatialReference().getID() == 102100); + + MapGeometry mapGeomOregon = GeometryEngine + .jsonToGeometry(jsonParseOregon); + Assert.assertTrue(mapGeomOregon.getSpatialReference().getID() == 102726); + + mapGeom = GeometryEngine.jsonToGeometry(jsonParserMpt); + Assert.assertTrue(mapGeom.getSpatialReference().getID() == 4326); + + mapGeom = GeometryEngine.jsonToGeometry(jsonParserMpt3D); + Assert.assertTrue(mapGeom.getSpatialReference().getID() == 4326); + { + Assert.assertTrue(((MultiPoint) mapGeom.getGeometry()).getPoint(0) + .getX() == -97.06138); + Assert.assertTrue(((MultiPoint) mapGeom.getGeometry()).getPoint(0) + .getY() == 32.837); + Assert.assertTrue(((MultiPoint) mapGeom.getGeometry()).getPoint(3) + .getX() == -97.06127); + Assert.assertTrue(((MultiPoint) mapGeom.getGeometry()).getPoint(3) + .getY() == 32.832); + } + // showProjectedGeometryInfo(mapGeom); + + mapGeom = GeometryEngine.jsonToGeometry(jsonParserPl); + Assert.assertTrue(mapGeom.getSpatialReference().getID() == 4326); + // showProjectedGeometryInfo(mapGeom); + + mapGeom = GeometryEngine.jsonToGeometry(jsonParserPl3D); + { + // [[ [-97.06138,32.837,5], [-97.06133,32.836,6], + // [-97.06124,32.834,7], [-97.06127,32.832,8] ], + // [ [-97.06326,32.759], [-97.06298,32.755] ]]"; + Assert.assertTrue(((Polyline) mapGeom.getGeometry()).getPoint(0) + .getX() == -97.06138); + Assert.assertTrue(((Polyline) mapGeom.getGeometry()).getPoint(0) + .getY() == 32.837); + int lastIndex = ((Polyline) mapGeom.getGeometry()).getPointCount() - 1; + Assert.assertTrue(((Polyline) mapGeom.getGeometry()).getPoint( + lastIndex).getX() == -97.06298);// -97.06153, 32.749 + Assert.assertTrue(((Polyline) mapGeom.getGeometry()).getPoint( + lastIndex).getY() == 32.755); + int lastIndexFirstLine = ((Polyline) mapGeom.getGeometry()) + .getPathEnd(0) - 1; + Assert.assertTrue(((Polyline) mapGeom.getGeometry()).getPoint( + lastIndexFirstLine).getX() == -97.06127);// -97.06153, + // 32.749 + Assert.assertTrue(((Polyline) mapGeom.getGeometry()).getPoint( + lastIndexFirstLine).getY() == 32.832); + } + + mapGeom = GeometryEngine.jsonToGeometry(jsonParserPg); + Assert.assertTrue(mapGeom.getSpatialReference() == null); + + mapGeom = GeometryEngine.jsonToGeometry(jsonParserPg3D); + { + Assert.assertTrue(((Polygon) mapGeom.getGeometry()).getPoint(0) + .getX() == -97.06138); + Assert.assertTrue(((Polygon) mapGeom.getGeometry()).getPoint(0) + .getY() == 32.837); + int lastIndex = ((Polygon) mapGeom.getGeometry()).getPointCount() - 1; + Assert.assertTrue(((Polygon) mapGeom.getGeometry()).getPoint( + lastIndex).getX() == -97.06153);// -97.06153, 32.749 + Assert.assertTrue(((Polygon) mapGeom.getGeometry()).getPoint( + lastIndex).getY() == 32.749); + } + + mapGeom = GeometryEngine.jsonToGeometry(jsonParserPg2); + Assert.assertTrue(mapGeom.getSpatialReference().getID() == 4326); + // showProjectedGeometryInfo(mapGeom); + + mapGeom = GeometryEngine.jsonToGeometry(jsonParserPg3); + Assert.assertTrue(mapGeom.getSpatialReference().getID() == 102100); + // showProjectedGeometryInfo(mapGeom); + + // mapGeom = GeometryEngine.jsonToGeometry(jsonParserCrazy1); + // Assert.assertTrue(mapGeom.getSpatialReference().getText().equals("")); + // showProjectedGeometryInfo(mapGeom); + + mapGeom = GeometryEngine.jsonToGeometry(jsonParserEnv); + Assert.assertTrue(mapGeom.getSpatialReference().getID() == 4326); + // showProjectedGeometryInfo(mapGeom); + // System.out.println("\n\nWKID: "+ + // SpatialReference.create(wkid).fromJson(jsonParserSR).getID()); + + try { + GeometryEngine.jsonToGeometry(jsonParserInvalidWKID); + } catch (Exception ex) { + System.out.print(ex.getMessage()); + } + } + + @Test + public void testMP2onCR175871() throws Exception { + Polygon pg = new Polygon(); + pg.startPath(-50, 10); + pg.lineTo(-50, 12); + pg.lineTo(-45, 12); + pg.lineTo(-45, 10); + + Polygon pg1 = new Polygon(); + pg1.startPath(-45, 10); + pg1.lineTo(-40, 10); + pg1.lineTo(-40, 8); + pg.add(pg1, false); + + SpatialReference spatialReference = SpatialReference.create(4326); + + try { + String jSonStr = GeometryEngine + .geometryToJson(spatialReference, pg); + JsonFactory jf = new JsonFactory(); + + JsonParser jp = jf.createJsonParser(jSonStr); + jp.nextToken(); + MapGeometry mg = GeometryEngine.jsonToGeometry(jp); + Geometry gm = mg.getGeometry(); + Assert.assertEquals(Geometry.Type.Polygon, gm.getType()); + Assert.assertTrue(mg.getSpatialReference().getID() == 4326); + + Polygon pgNew = (Polygon) gm; + + Assert.assertEquals(pgNew.getPathCount(), pg.getPathCount()); + Assert.assertEquals(pgNew.getPointCount(), pg.getPointCount()); + Assert.assertEquals(pgNew.getSegmentCount(), pg.getSegmentCount()); + + Assert.assertEquals(pgNew.getPoint(0).getX(), + pg.getPoint(0).getX(), 0.000000001); + Assert.assertEquals(pgNew.getPoint(1).getX(), + pg.getPoint(1).getX(), 0.000000001); + Assert.assertEquals(pgNew.getPoint(2).getX(), + pg.getPoint(2).getX(), 0.000000001); + Assert.assertEquals(pgNew.getPoint(3).getX(), + pg.getPoint(3).getX(), 0.000000001); + + Assert.assertEquals(pgNew.getPoint(0).getY(), + pg.getPoint(0).getY(), 0.000000001); + Assert.assertEquals(pgNew.getPoint(1).getY(), + pg.getPoint(1).getY(), 0.000000001); + Assert.assertEquals(pgNew.getPoint(2).getY(), + pg.getPoint(2).getY(), 0.000000001); + Assert.assertEquals(pgNew.getPoint(3).getY(), + pg.getPoint(3).getY(), 0.000000001); + } catch (Exception ex) { + String err = ex.getMessage(); + System.out.print(err); + throw ex; + } + } + + @Test + public static int fromJsonToWkid(JsonParser parser) + throws JsonParseException, IOException { + int wkid = 0; + if (parser.getCurrentToken() != JsonToken.START_OBJECT) { + return 0; + } + + while (parser.nextToken() != JsonToken.END_OBJECT) { + String fieldName = parser.getCurrentName(); + + if ("wkid".equals(fieldName)) { + parser.nextToken(); + wkid = parser.getIntValue(); + } + } + return wkid; + } + + @SuppressWarnings("unused") + private static void showProjectedGeometryInfo(MapGeometry mapGeom) { + System.out.println("\n"); + MapGeometry geom = mapGeom; + // while ((geom = geomCursor.next()) != null) { + + if (geom.getGeometry() instanceof Point) { + Point pnt = (Point) geom.getGeometry(); + System.out + .println("Point(" + pnt.getX() + " , " + pnt.getY() + ")"); + if (geom.getSpatialReference() == null) + System.out.println("No spatial reference"); + else + System.out.println("wkid: " + + geom.getSpatialReference().getID()); + + } else if (geom.getGeometry() instanceof MultiPoint) { + MultiPoint mp = (MultiPoint) geom.getGeometry(); + System.out.println("Multipoint has " + mp.getPointCount() + + " points."); + + System.out.println("wkid: " + geom.getSpatialReference().getID()); + + } else if (geom.getGeometry() instanceof Polygon) { + Polygon mp = (Polygon) geom.getGeometry(); + System.out.println("Polygon has " + mp.getPointCount() + + " points and " + mp.getPathCount() + " parts."); + if (mp.getPathCount() > 1) { + System.out.println("Part start of 2nd segment : " + + mp.getPathStart(1)); + System.out.println("Part end of 2nd segment : " + + mp.getPathEnd(1)); + System.out.println("Part size of 2nd segment : " + + mp.getPathSize(1)); + + int start = mp.getPathStart(1); + int end = mp.getPathEnd(1); + for (int i = start; i < end; i++) { + Point pp = mp.getPoint(i); + System.out.println("Point(" + i + ") = (" + pp.getX() + + ", " + pp.getY() + ")"); + } + } + System.out.println("wkid: " + geom.getSpatialReference().getID()); + + } else if (geom.getGeometry() instanceof Polyline) { + Polyline mp = (Polyline) geom.getGeometry(); + System.out.println("Polyline has " + mp.getPointCount() + + " points and " + mp.getPathCount() + " parts."); + System.out.println("Part start of 2nd segment : " + + mp.getPathStart(1)); + System.out.println("Part end of 2nd segment : " + + mp.getPathEnd(1)); + System.out.println("Part size of 2nd segment : " + + mp.getPathSize(1)); + int start = mp.getPathStart(1); + int end = mp.getPathEnd(1); + for (int i = start; i < end; i++) { + Point pp = mp.getPoint(i); + System.out.println("Point(" + i + ") = (" + pp.getX() + ", " + + pp.getY() + ")"); + } + + System.out.println("wkid: " + geom.getSpatialReference().getID()); + } + } + + @Test + public void testGeometryToJSON() { + Polygon geom = new Polygon(); + geom.startPath(new Point(-113, 34)); + geom.lineTo(new Point(-105, 34)); + geom.lineTo(new Point(-108, 40)); + + String outputPolygon1 = GeometryEngine.geometryToJson(-1, geom);// Test + // WKID + // == -1 + System.out.println("Geom JSON STRING is" + outputPolygon1); + String correctPolygon1 = "{\"rings\":[[[-113.0,34.0],[-105.0,34.0],[-108.0,40.0],[-113.0,34.0]]]}"; + + assertEquals(correctPolygon1, outputPolygon1); + + String outputPolygon2 = GeometryEngine.geometryToJson(4326, geom); + System.out.println("Geom JSON STRING is" + outputPolygon2); + + String correctPolygon2 = "{\"rings\":[[[-113.0,34.0],[-105.0,34.0],[-108.0,40.0],[-113.0,34.0]]],\"spatialReference\":{\"wkid\":4326}}"; + assertEquals(correctPolygon2, outputPolygon2); + } + + @Test + public void testGeometryToJSONOldID() throws Exception {// CR + Polygon geom = new Polygon(); + geom.startPath(new Point(-113, 34)); + geom.lineTo(new Point(-105, 34)); + geom.lineTo(new Point(-108, 40)); + String outputPolygon = GeometryEngine.geometryToJson( + SpatialReference.create(3857), geom);// Test WKID == -1 + String correctPolygon = "{\"rings\":[[[-113.0,34.0],[-105.0,34.0],[-108.0,40.0],[-113.0,34.0]]],\"spatialReference\":{\"wkid\":102100,\"latestWkid\":3857}}"; + assertTrue(outputPolygon.equals(correctPolygon)); + JsonFactory jf = new JsonFactory(); + JsonParser jp = jf.createJsonParser(outputPolygon); + jp.nextToken(); + MapGeometry mg = GeometryEngine.jsonToGeometry(jp); + @SuppressWarnings("unused") + int srId = mg.getSpatialReference().getID(); + @SuppressWarnings("unused") + int srOldId = mg.getSpatialReference().getOldID(); + Assert.assertTrue(mg.getSpatialReference().getID() == 3857); + Assert.assertTrue(mg.getSpatialReference().getLatestID() == 3857); + Assert.assertTrue(mg.getSpatialReference().getOldID() == 102100); + } +} diff --git a/unittest/com/esri/core/geometry/TestMathUtils.java b/unittest/com/esri/core/geometry/TestMathUtils.java new file mode 100644 index 00000000..0d41a739 --- /dev/null +++ b/unittest/com/esri/core/geometry/TestMathUtils.java @@ -0,0 +1,41 @@ +package com.esri.core.geometry; + +import junit.framework.TestCase; +import org.junit.Test; + +public class TestMathUtils extends TestCase { + @Override + protected void setUp() throws Exception { + super.setUp(); + } + + @Override + protected void tearDown() throws Exception { + super.tearDown(); + } + + @Test + public static void testKahanSummation() { + double s = 0.0; + for (int i = 0; i < 10000; i++) { + if (i == 0) { + s += 1e6; + } else + s += 1e-7; + } + + double trueAnswer = 1e6 + 9999 * 1e-7; + assertTrue(Math.abs(s - trueAnswer) > 1e-9); // precision loss + MathUtils.KahanSummator sum = new MathUtils.KahanSummator(0); + for (int i = 0; i < 10000; i++) { + if (i == 0) { + sum.add(1e6); + } else + sum.add(1e-7); + } + double kahanResult = sum.getResult(); + // 1000000.0009999000 //C++ + // 1000000.0009999 //Java + assertTrue(kahanResult == trueAnswer); // nice answer! + } +} diff --git a/unittest/com/esri/core/geometry/TestMultiPoint.java b/unittest/com/esri/core/geometry/TestMultiPoint.java new file mode 100644 index 00000000..44c927f6 --- /dev/null +++ b/unittest/com/esri/core/geometry/TestMultiPoint.java @@ -0,0 +1,338 @@ +package com.esri.core.geometry; + +import junit.framework.TestCase; +import org.junit.Test; + +public class TestMultiPoint extends TestCase { + @Override + protected void setUp() throws Exception { + super.setUp(); + } + + @Override + protected void tearDown() throws Exception { + super.tearDown(); + } + + static void simpleTest(Geometry point) { + assertTrue(point != null); + // point->AddAttribute(VertexDescription::Semantics::Z); + // assertTrue(point->HasAttribute(VertexDescription::Semantics::POSITION)); + // assertTrue(point.->HasAttribute(VertexDescription::Semantics::Z)); + // point->AddAttribute(VertexDescription::Semantics::Z);//duplicate call + // assertTrue(point->GetDescription()->GetAttributeCount() == 2); + // assertTrue(point->GetDescription()->GetSemantics(1) == + // VertexDescription::Semantics::Z); + // point->DropAttribute(VertexDescription::Semantics::Z); + // assertFalse(point->HasAttribute(VertexDescription::Semantics::Z)); + // point->DropAttribute(VertexDescription::Semantics::Z);//duplicate + // call + // assertFalse(point->HasAttribute(VertexDescription::Semantics::Z)); + // assertTrue(point->GetDescription()->GetAttributeCount() == 1); + // assertTrue(point->GetDescription()->GetSemantics(0) == + // VertexDescription::Semantics::POSITION); + + // point->AddAttribute(VertexDescription::Semantics::M); + // assertTrue(point->HasAttribute(VertexDescription::Semantics::POSITION)); + // assertFalse(point->HasAttribute(VertexDescription::Semantics::Z)); + // assertTrue(point->HasAttribute(VertexDescription::Semantics::M)); + // point->DropAttribute(VertexDescription::Semantics::M); + // assertFalse(point->HasAttribute(VertexDescription::Semantics::M)); + // + // point->AddAttribute(VertexDescription::Semantics::ID); + // assertTrue(point->HasAttribute(VertexDescription::Semantics::POSITION)); + // assertFalse(point->HasAttribute(VertexDescription::Semantics::Z)); + // assertFalse(point->HasAttribute(VertexDescription::Semantics::M)); + // point->DropAttribute(VertexDescription::Semantics::ID); + // assertFalse(point->HasAttribute(VertexDescription::Semantics::ID)); + // / + // assertTrue(point->IsEmpty()); + // assertTrue(point->GetPointCount() == 0); + // assertTrue(point->GetPartCount() == 0); + + point = null; + assertFalse(point != null); + } + + @Test + public static void testCreation() { + {// simple create + MultiPoint mpoint = new MultiPoint(); + assertTrue(mpoint.getClass() == MultiPoint.class); + // assertFalse(mpoint.getClass() == Polyline.class); + + assertTrue(mpoint != null); + assertTrue(mpoint.getType() == Geometry.Type.MultiPoint); + assertTrue(mpoint.isEmpty()); + assertTrue(mpoint.getPointCount() == 0); + mpoint = null; + assertFalse(mpoint != null); + } + {// play with default attributes + MultiPoint mpoint = new MultiPoint(); + simpleTest(mpoint); + } + + {// simple create 2D + MultiPoint mpoint = new MultiPoint(); + assertTrue(mpoint != null); + + // FIXME uncomment when assertions are fixed + // try + // { + // // OutputDebugString(L"Test an assert\n"); + // // GeometryException::m_assertOnException = false; + // + // Point pt2 = mpoint.getPoint(0);; + // } + // catch(GeometryException except) + // { + // assertTrue(except.index_out_of_bounds); + // GeometryException::m_assertOnException = true; + // } + + MultiPoint mpoint1 = new MultiPoint(); + assertTrue(mpoint1 != null); + // FIXME uncomment when assertions are fixed + // try + // { + // OutputDebugString(L"Test an assert\n"); + // GeometryException::m_assertOnException = false; + // Point ppp; + // mpoint.getPointByVal(0, ppp); + // } + // catch(GeometryException except) + // { + // assertTrue(except.index_out_of_bounds); + // GeometryException::m_assertOnException = true; + // } + + mpoint.setEmpty(); + Point pt = new Point(0, 0); + mpoint.add(pt); + Point pt3 = mpoint.getPoint(0); + assertTrue(pt3.getX() == 0 && pt3.getY() == 0/** && pt3.getZ() == 0 */ + ); + // assertFalse(mpoint->HasAttribute(VertexDescription::Semantics::Z)); + // pt3.setZ(115.0); + mpoint.setPoint(0, pt3); + pt3 = mpoint.getPoint(0); + assertTrue(pt3.getX() == 0 && pt3.getY() == 0/* && pt3.getZ() == 115 */); + // assertTrue(mpoint->HasAttribute(VertexDescription::Semantics::Z)); + // CompareGeometryContent(mpoint, &pt, 1); + } + + {// move 3d + MultiPoint mpoint = new MultiPoint(); + assertTrue(mpoint != null); + Point pt = new Point(0, 0); + mpoint.add(pt); + Point pt3 = mpoint.getPoint(0); + assertTrue(pt3.getX() == 0 && pt3.getY() == 0/* && pt3.getZ() == 0 */); + // assertFalse(mpoint->HasAttribute(VertexDescription::Semantics::Z)); + // FIXME once transform3D is public + // Transformation3D transform3D = GCNEW Transformation3D; + // transform3D.setTranslate(1, 1, 0); + // mpoint.applyTransformation(transform3D); + + // assertTrue(mpoint->HasAttribute(VertexDescription::Semantics::Z)); + // pt3 = mpoint.getPoint(0); + // assertTrue(pt3.x == 1 && pt3.y == 1 && pt3.z == 0); + // transform3D.setTranslate(56, 12, 333); + // mpoint.applyTransformation(transform3D); + // pt3 = mpoint.getXYZ(0); + // assertTrue(pt3.x == 57 && pt3.y == 13 && pt3.z == 333); + // CompareGeometryContent(mpoint, &pt3, 1); + } + + { // test QueryInterval + MultiPoint mpoint = new MultiPoint(); + + Point pt1 = new Point(0.0, 0.0); + // pt1.setZ(-1.0); + + Point pt2 = new Point(0.0, 0.0); + // pt2.setZ(1.0); + + mpoint.add(pt1); + mpoint.add(pt2); + + // Envelope1D e = + // mpoint->QueryInterval(enum_value2(VertexDescription, Semantics, + // Z), 0); + Envelope e = new Envelope(); + mpoint.queryEnvelope(e); + // assertTrue(e.get == -1.0 && e.vmax == 1.0); + } + + { + @SuppressWarnings("unused") + MultiPoint geom = new MultiPoint(); + // int sz = sizeof(openString) / sizeof(openString[0]); + // for (int i = 0; i < sz; i++) + // geom.add(openString[i]); + // CompareGeometryContent(geom, openString, sz); + } + + { + @SuppressWarnings("unused") + MultiPoint geom = new MultiPoint(); + // int sz = sizeof(openString) / sizeof(openString[0]); + // Point point = GCNEW Point; + // for (int i = 0; i < sz; i++) + // { + // point.setXY(openString[i]); + // geom.add(point); + // } + // CompareGeometryContent(geom, openString, sz); + } + + // Test AddPoints + { + @SuppressWarnings("unused") + MultiPoint geom = new MultiPoint(); + // int sz = sizeof(openString) / sizeof(openString[0]); + // geom.addPoints(openString, sz, 0, -1); + // CompareGeometryContent((MultiVertexGeometry)geom, openString, + // sz); + } + + // Test InsertPoint(Point2D) + { + MultiPoint mpoint = new MultiPoint(); + Point pt0 = new Point(0.0, 0.0); + // pt0.setZ(-1.0); + // pt0.setID(7); + + Point pt1 = new Point(0.0, 0.0); + // pt1.setZ(1.0); + // pt1.setID(11); + + Point pt2 = new Point(0.0, 1.0); + // pt2.setZ(1.0); + // pt2.setID(13); + + mpoint.add(pt0); + mpoint.add(pt1); + mpoint.add(pt2); + + Point pt3 = new Point(-11.0, -13.0); + + mpoint.add(pt3); + mpoint.insertPoint(1, pt3); + assertTrue(mpoint.getPointCount() == 5); + + Point pt; + pt = mpoint.getPoint(0); + assertTrue(pt.getX() == pt0.getX() && pt.getY() == pt0.getY()/* + * && + * pt. + * getZ + * () == + * pt0 + * .getZ + * () + */); + + pt = mpoint.getPoint(1); + assertTrue(pt.getX() == pt3.getX() && pt.getY() == pt3.getY()); + + pt = mpoint.getPoint(2); + assertTrue(pt.getX() == pt1.getX() && pt.getY() == pt1.getY()/* + * && + * pt. + * getZ + * () == + * pt1 + * .getZ + * () + */); + + pt = mpoint.getPoint(3); + assertTrue(pt.getX() == pt2.getX() && pt.getY() == pt2.getY()/* + * && + * pt. + * getZ + * () == + * pt2 + * .getZ + * () + */); + + Point point = new Point(); + point.setXY(17.0, 19.0); + // point.setID(12); + // point.setM(5); + + mpoint.insertPoint(2, point); + mpoint.add(point); + + assertTrue(mpoint.getPointCount() == 7); + + // double m; + // int id; + // pt = mpoint.getXYZ(2); + // assertTrue(pt.x == 17.0 && pt.y == 19.0 && pt.z == defaultZ); + // m = mpoint.getAttributeAsDbl(enum_value2(VertexDescription, + // Semantics, M), 2, 0); + // assertTrue(m == 5); + // id = mpoint.getAttributeAsInt(enum_value2(VertexDescription, + // Semantics, ID), 2, 0); + // assertTrue(id == 23); + // + // pt = mpoint.getXYZ(3); + // assertTrue(pt.x == pt1.x && pt.y == pt1.y && pt.z == pt1.z); + // m = mpoint.getAttributeAsDbl(enum_value2(VertexDescription, + // Semantics, M), 3, 0); + // assertTrue(NumberUtils::IsNaN(m)); + // id = mpoint.getAttributeAsInt(enum_value2(VertexDescription, + // Semantics, ID), 3, 0); + // assertTrue(id == 11); + } + + MultiPoint mpoint = new MultiPoint(); + Point pt0 = new Point(0.0, 0.0, -1.0); + + Point pt1 = new Point(0.0, 0.0, 1.0); + + Point pt2 = new Point(0.0, 1.0, 1.0); + + mpoint.add(pt0); + mpoint.add(pt1); + mpoint.add(pt2); + + mpoint.removePoint(1); + + Point pt; + pt = mpoint.getPoint(0); + assertTrue(pt.getX() == pt0.getX() && pt.getY() == pt0.getY()); + pt = mpoint.getPoint(1); + assertTrue(pt.getX() == pt2.getX() && pt.getY() == pt2.getY()); + + assertTrue(mpoint.getPointCount() == 2); + } + + @Test + public static void testCopy() { + MultiPoint mpoint = new MultiPoint(); + Point pt0 = new Point(0.0, 0.0, -1.0); + Point pt1 = new Point(0.0, 0.0, 1.0); + Point pt2 = new Point(0.0, 1.0, 1.0); + + mpoint.add(pt0); + mpoint.add(pt1); + mpoint.add(pt2); + mpoint.removePoint(1); + + MultiPoint mpCopy = (MultiPoint) mpoint.copy(); + assertTrue(mpCopy.equals(mpoint)); + + Point pt; + pt = mpCopy.getPoint(0); + assertTrue(pt.getX() == pt0.getX() && pt.getY() == pt0.getY()); + pt = mpCopy.getPoint(1); + assertTrue(pt.getX() == pt2.getX() && pt.getY() == pt2.getY()); + + assertTrue(mpCopy.getPointCount() == 2); + } +} diff --git a/unittest/com/esri/core/geometry/TestOGC.java b/unittest/com/esri/core/geometry/TestOGC.java new file mode 100644 index 00000000..5abda09a --- /dev/null +++ b/unittest/com/esri/core/geometry/TestOGC.java @@ -0,0 +1,561 @@ +package com.esri.core.geometry; + +import junit.framework.TestCase; + +import com.esri.core.geometry.ogc.OGCGeometry; +import com.esri.core.geometry.ogc.OGCLineString; +import com.esri.core.geometry.ogc.OGCMultiPoint; +import com.esri.core.geometry.ogc.OGCPoint; +import com.esri.core.geometry.ogc.OGCPolygon; +import com.esri.core.geometry.ogc.OGCConcreteGeometryCollection; +import org.json.JSONException; +import java.nio.ByteBuffer; + +public class TestOGC extends TestCase { + @Override + protected void setUp() throws Exception { + super.setUp(); + } + + @Override + protected void tearDown() throws Exception { + super.tearDown(); + } + + public void testPoint() { + OGCGeometry g = OGCGeometry.fromText("POINT(1 2)"); + assertTrue(g.geometryType().equals("Point")); + OGCPoint p = (OGCPoint) g; + assertTrue(p.X() == 1); + assertTrue(p.Y() == 2); + assertTrue(g.equals(OGCGeometry.fromText("POINT(1 2)"))); + assertTrue(!g.equals(OGCGeometry.fromText("POINT(1 3)"))); + OGCGeometry buf = g.buffer(10); + assertTrue(buf.geometryType().equals("Polygon")); + OGCPolygon poly = (OGCPolygon) buf.envelope(); + double a = poly.area(); + assertTrue(Math.abs(a - 400) < 1e-1); + } + + public void testPolygon() { + OGCGeometry g = OGCGeometry + .fromText("POLYGON((-10 -10, 10 -10, 10 10, -10 10, -10 -10), (-5 -5, -5 5, 5 5, 5 -5, -5 -5))"); + assertTrue(g.geometryType().equals("Polygon")); + OGCPolygon p = (OGCPolygon) g; + assertTrue(p.numInteriorRing() == 1); + OGCLineString ls = p.exterorRing(); + // assertTrue(ls.pointN(1).equals(OGCGeometry.fromText("POINT(10 -10)"))); + boolean b = ls + .equals(OGCGeometry + .fromText("LINESTRING(-10 -10, 10 -10, 10 10, -10 10, -10 -10)")); + assertTrue(b); + OGCLineString lsi = p.interiorRingN(0); + b = lsi.equals(OGCGeometry + .fromText("LINESTRING(-5 -5, -5 5, 5 5, 5 -5, -5 -5)")); + assertTrue(b); + assertTrue(!lsi.equals(ls)); + } + + public void testGeometryCollection() throws JSONException { + OGCGeometry g = OGCGeometry + .fromText("GEOMETRYCOLLECTION(POLYGON EMPTY, POINT(1 1), LINESTRING EMPTY, MULTIPOLYGON EMPTY, MULTILINESTRING EMPTY)"); + assertTrue(g.geometryType().equals("GeometryCollection")); + OGCConcreteGeometryCollection gc = (OGCConcreteGeometryCollection) g; + assertTrue(gc.numGeometries() == 5); + assertTrue(gc.geometryN(0).geometryType().equals("Polygon")); + assertTrue(gc.geometryN(1).geometryType().equals("Point")); + assertTrue(gc.geometryN(2).geometryType().equals("LineString")); + assertTrue(gc.geometryN(3).geometryType().equals("MultiPolygon")); + assertTrue(gc.geometryN(4).geometryType().equals("MultiLineString")); + + g = OGCGeometry + .fromText("GEOMETRYCOLLECTION(POLYGON EMPTY, POINT(1 1), GEOMETRYCOLLECTION EMPTY, LINESTRING EMPTY, GEOMETRYCOLLECTION(POLYGON EMPTY, POINT(1 1), LINESTRING EMPTY, MULTIPOLYGON EMPTY, MULTILINESTRING EMPTY, MULTIPOINT EMPTY), MULTIPOLYGON EMPTY, MULTILINESTRING EMPTY)"); + assertTrue(g.geometryType().equals("GeometryCollection")); + gc = (OGCConcreteGeometryCollection) g; + assertTrue(gc.numGeometries() == 7); + assertTrue(gc.geometryN(0).geometryType().equals("Polygon")); + assertTrue(gc.geometryN(1).geometryType().equals("Point")); + assertTrue(gc.geometryN(2).geometryType().equals("GeometryCollection")); + assertTrue(gc.geometryN(3).geometryType().equals("LineString")); + assertTrue(gc.geometryN(4).geometryType().equals("GeometryCollection")); + assertTrue(gc.geometryN(5).geometryType().equals("MultiPolygon")); + assertTrue(gc.geometryN(6).geometryType().equals("MultiLineString")); + + OGCConcreteGeometryCollection gc2 = (OGCConcreteGeometryCollection) gc + .geometryN(4); + assertTrue(gc2.numGeometries() == 6); + assertTrue(gc2.geometryN(0).geometryType().equals("Polygon")); + assertTrue(gc2.geometryN(1).geometryType().equals("Point")); + assertTrue(gc2.geometryN(2).geometryType().equals("LineString")); + assertTrue(gc2.geometryN(3).geometryType().equals("MultiPolygon")); + assertTrue(gc2.geometryN(4).geometryType().equals("MultiLineString")); + assertTrue(gc2.geometryN(5).geometryType().equals("MultiPoint")); + + ByteBuffer wkbBuffer = g.asBinary(); + g = OGCGeometry.fromBinary(wkbBuffer); + + assertTrue(g.geometryType().equals("GeometryCollection")); + gc = (OGCConcreteGeometryCollection) g; + assertTrue(gc.numGeometries() == 7); + assertTrue(gc.geometryN(0).geometryType().equals("Polygon")); + assertTrue(gc.geometryN(1).geometryType().equals("Point")); + assertTrue(gc.geometryN(2).geometryType().equals("GeometryCollection")); + assertTrue(gc.geometryN(3).geometryType().equals("LineString")); + assertTrue(gc.geometryN(4).geometryType().equals("GeometryCollection")); + assertTrue(gc.geometryN(5).geometryType().equals("MultiPolygon")); + assertTrue(gc.geometryN(6).geometryType().equals("MultiLineString")); + + gc2 = (OGCConcreteGeometryCollection) gc.geometryN(4); + assertTrue(gc2.numGeometries() == 6); + assertTrue(gc2.geometryN(0).geometryType().equals("Polygon")); + assertTrue(gc2.geometryN(1).geometryType().equals("Point")); + assertTrue(gc2.geometryN(2).geometryType().equals("LineString")); + assertTrue(gc2.geometryN(3).geometryType().equals("MultiPolygon")); + assertTrue(gc2.geometryN(4).geometryType().equals("MultiLineString")); + assertTrue(gc2.geometryN(5).geometryType().equals("MultiPoint")); + + String wktString = g.asText(); + assertTrue(wktString + .equals("GEOMETRYCOLLECTION (POLYGON EMPTY, POINT (1 1), GEOMETRYCOLLECTION EMPTY, LINESTRING EMPTY, GEOMETRYCOLLECTION (POLYGON EMPTY, POINT (1 1), LINESTRING EMPTY, MULTIPOLYGON EMPTY, MULTILINESTRING EMPTY, MULTIPOINT EMPTY), MULTIPOLYGON EMPTY, MULTILINESTRING EMPTY)")); + + g = OGCGeometry + .fromGeoJson("{\"type\" : \"GeometryCollection\", \"geometries\" : [{\"type\" : \"Polygon\", \"coordinates\" : []}, {\"type\" : \"Point\", \"coordinates\" : [1, 1]}, {\"type\" : \"GeometryCollection\", \"geometries\" : []}, {\"type\" : \"LineString\", \"coordinates\" : []}, {\"type\" : \"GeometryCollection\", \"geometries\" : [{\"type\": \"Polygon\", \"coordinates\" : []}, {\"type\" : \"Point\", \"coordinates\" : [1,1]}, {\"type\" : \"LineString\", \"coordinates\" : []}, {\"type\" : \"MultiPolygon\", \"coordinates\" : []}, {\"type\" : \"MultiLineString\", \"coordinates\" : []}, {\"type\" : \"MultiPoint\", \"coordinates\" : []}]}, {\"type\" : \"MultiPolygon\", \"coordinates\" : []}, {\"type\" : \"MultiLineString\", \"coordinates\" : []} ] }"); + + wktString = g.asText(); + assertTrue(wktString + .equals("GEOMETRYCOLLECTION (POLYGON EMPTY, POINT (1 1), GEOMETRYCOLLECTION EMPTY, LINESTRING EMPTY, GEOMETRYCOLLECTION (POLYGON EMPTY, POINT (1 1), LINESTRING EMPTY, MULTIPOLYGON EMPTY, MULTILINESTRING EMPTY, MULTIPOINT EMPTY), MULTIPOLYGON EMPTY, MULTILINESTRING EMPTY)")); + + } + + public void testFirstPointOfPolygon() { + OGCGeometry g = OGCGeometry + .fromText("POLYGON((-10 -10, 10 -10, 10 10, -10 10, -10 -10), (-5 -5, -5 5, 5 5, 5 -5, -5 -5))"); + assertTrue(g.geometryType().equals("Polygon")); + OGCPolygon p = (OGCPolygon) g; + assertTrue(p.numInteriorRing() == 1); + OGCLineString ls = p.exterorRing(); + OGCPoint p1 = ls.pointN(1); + assertTrue(ls.pointN(1).equals(OGCGeometry.fromText("POINT(10 -10)"))); + OGCPoint p2 = ls.pointN(3); + assertTrue(ls.pointN(3).equals(OGCGeometry.fromText("POINT(-10 10)"))); + OGCPoint p0 = ls.pointN(0); + assertTrue(ls.pointN(0).equals(OGCGeometry.fromText("POINT(-10 -10)"))); + + } + + public void testFirstPointOfLineString() { + OGCGeometry g = OGCGeometry + .fromText("LINESTRING(-10 -10, 10 -10, 10 10, -10 10, -10 -10)"); + assertTrue(g.geometryType().equals("LineString")); + OGCLineString p = (OGCLineString) g; + assertTrue(p.numPoints() == 5); + assertTrue(p.isClosed()); + assertTrue(p.pointN(1).equals(OGCGeometry.fromText("POINT(10 -10)"))); + } + + public void testPointInPolygon() { + OGCGeometry g = OGCGeometry + .fromText("POLYGON((-10 -10, 10 -10, 10 10, -10 10, -10 -10), (-5 -5, -5 5, 5 5, 5 -5, -5 -5))"); + assertTrue(g.geometryType().equals("Polygon")); + assertTrue(!g.contains(OGCGeometry.fromText("POINT(0 0)"))); + assertTrue(g.contains(OGCGeometry.fromText("POINT(9 9)"))); + assertTrue(!g.contains(OGCGeometry.fromText("POINT(-20 1)"))); + assertTrue(g.disjoint(OGCGeometry.fromText("POINT(0 0)"))); + assertTrue(!g.disjoint(OGCGeometry.fromText("POINT(9 9)"))); + assertTrue(g.disjoint(OGCGeometry.fromText("POINT(-20 1)"))); + + } + + public void testMultiPolygon() { + OGCGeometry g = OGCGeometry + .fromText("MULTIPOLYGON(((-10 -10, 10 -10, 10 10, -10 10, -10 -10), (-5 -5, -5 5, 5 5, 5 -5, -5 -5)))"); + assertTrue(g.geometryType().equals("MultiPolygon")); // the type is + // reduced + assertTrue(!g.contains(OGCGeometry.fromText("POINT(0 0)"))); + assertTrue(g.contains(OGCGeometry.fromText("POINT(9 9)"))); + assertTrue(!g.contains(OGCGeometry.fromText("POINT(-20 1)"))); + assertTrue(g.disjoint(OGCGeometry.fromText("POINT(0 0)"))); + assertTrue(!g.disjoint(OGCGeometry.fromText("POINT(9 9)"))); + assertTrue(g.disjoint(OGCGeometry.fromText("POINT(-20 1)"))); + } + + public void testMultiPolygonUnion() { + OGCGeometry g = OGCGeometry + .fromText("POLYGON((-10 -10, 10 -10, 10 10, -10 10, -10 -10), (-5 -5, -5 5, 5 5, 5 -5, -5 -5))"); + OGCGeometry g2 = OGCGeometry + .fromText("POLYGON((90 90, 110 90, 110 110, 90 110, 90 90))"); + OGCGeometry u = g.union(g2); + assertTrue(u.geometryType().equals("MultiPolygon")); + assertTrue(!u.contains(OGCGeometry.fromText("POINT(0 0)"))); + assertTrue(u.contains(OGCGeometry.fromText("POINT(9 9)"))); + assertTrue(!u.contains(OGCGeometry.fromText("POINT(-20 1)"))); + assertTrue(u.disjoint(OGCGeometry.fromText("POINT(0 0)"))); + assertTrue(!u.disjoint(OGCGeometry.fromText("POINT(9 9)"))); + assertTrue(u.disjoint(OGCGeometry.fromText("POINT(-20 1)"))); + assertTrue(u.contains(OGCGeometry.fromText("POINT(100 100)"))); + } + + public void testIntersection() { + OGCGeometry g = OGCGeometry.fromText("LINESTRING(0 0, 10 10)"); + OGCGeometry g2 = OGCGeometry.fromText("LINESTRING(10 0, 0 10)"); + OGCGeometry u = g.intersection(g2); + assertTrue(u.dimension() == 0); + String s = u.asText(); + assertTrue(u.equals(OGCGeometry.fromText("POINT(5 5)"))); + } + + public void testPointSymDif() { + OGCGeometry g1 = OGCGeometry.fromText("POINT(1 2)"); + OGCGeometry g2 = OGCGeometry.fromText("POINT(3 4)"); + OGCGeometry gg = g1.symDifference(g2); + assertTrue(gg.equals(OGCGeometry.fromText("MULTIPOINT(1 2, 3 4)"))); + + OGCGeometry g3 = OGCGeometry.fromText("POINT(1 2)"); + OGCGeometry gg1 = g1.symDifference(g3); + assertTrue(gg1 == null || gg1.isEmpty()); + + } + + public void testNullSr() { + String wkt = "point (0 0)"; + OGCGeometry g = OGCGeometry.fromText(wkt); + g.setSpatialReference(null); + assertTrue(g.SRID() < 1); + } + + public void testIsectPoint() { + String wkt = "point (0 0)"; + String wk2 = "point (0 0)"; + OGCGeometry g0 = OGCGeometry.fromText(wkt); + OGCGeometry g1 = OGCGeometry.fromText(wk2); + g0.setSpatialReference(null); + g1.setSpatialReference(null); + try { + OGCGeometry rslt = g0.intersection(g1); // ArrayIndexOutOfBoundsException + assertTrue(rslt != null); + } catch (Exception e) { + assertTrue(false); + } + } + + public void testIsectDisjoint() { + String wk3 = "linestring (0 0, 1 1)"; + String wk4 = "linestring (2 2, 4 4)"; + OGCGeometry g0 = OGCGeometry.fromText(wk3); + OGCGeometry g1 = OGCGeometry.fromText(wk4); + g0.setSpatialReference(null); + g1.setSpatialReference(null); + try { + OGCGeometry rslt = g0.intersection(g1); // null + assertTrue(rslt != null); + } catch (Exception e) { + assertTrue(false); + } + } + + public void test_polygon_is_simple_for_OGC() { + try { + { + String s = "{\"rings\":[[[0, 0], [0, 10], [10, 10], [10, 0], [0, 0]]]}"; + OGCGeometry g = OGCGeometry.fromJson(s); + boolean res = g.isSimple(); + assertTrue(res); + assertTrue(g.isSimpleRelaxed()); + } + + {// exterior ring is self-tangent + String s = "{\"rings\":[[[0, 0], [0, 10], [5, 5], [10, 10], [10, 0], [5, 5], [0, 0]]]}"; + OGCGeometry g = OGCGeometry.fromJson(s); + boolean res = g.isSimple(); + assertTrue(!res); + assertTrue(g.isSimpleRelaxed()); + } + + {// ring orientation (hole is cw) + String s = "{\"rings\":[[[-100, -100], [-100, 100], [100, 100], [100, -100], [-100, -100]], [[0, 0], [5, 5], [10, 0], [0, 0]]]}"; + OGCGeometry g = OGCGeometry.fromJson(s); + boolean res = g.isSimple(); + assertTrue(!res); + assertTrue(!g.isSimpleRelaxed()); + } + { + String s = "{\"rings\":[[[-100, -100], [-100, 100], [100, 100], [100, -100], [-100, -100]], [[0, 0], [10, 0], [5, 5], [0, 0]]]}"; + OGCGeometry g = OGCGeometry.fromJson(s); + boolean res = g.isSimple(); + assertTrue(res); + assertTrue(g.isSimpleRelaxed()); + } + + {// ring order + String s = "{\"rings\":[[[0, 0], [10, 0], [5, 5], [0, 0]], [[-100, -100], [-100, 100], [100, 100], [100, -100], [-100, -100]]]}"; + OGCGeometry g = OGCGeometry.fromJson(s); + boolean res = g.isSimple(); + assertTrue(!res); + assertTrue(g.isSimpleRelaxed()); + } + + { + // hole is self tangent + String s = "{\"rings\":[[[-100, -100], [-100, 100], [100, 100], [100, -100], [-100, -100]], [[0, 0], [5, 5], [10, 0], [10, 10], [5, 5], [0, 10], [0, 0]]]}"; + OGCGeometry g = OGCGeometry.fromJson(s); + boolean res = g.isSimple(); + assertTrue(!res); + assertTrue(g.isSimpleRelaxed()); + } + { + // two holes touch + String s = "{\"rings\":[[[-100, -100], [-100, 100], [100, 100], [100, -100], [-100, -100]], [[0, 0], [10, 0], [5, 5], [0, 0]], [[10, 10], [0, 10], [5, 5], [10, 10]]]}"; + OGCGeometry g = OGCGeometry.fromJson(s); + boolean res = g.isSimple(); + assertTrue(res); + assertTrue(g.isSimpleRelaxed()); + } + { + // two holes touch, bad orientation + String s = "{\"rings\":[[[-100, -100], [-100, 100], [100, 100], [100, -100], [-100, -100]], [[0, 0], [5, 5], [10, 0], [0, 0]], [[10, 10], [0, 10], [5, 5], [10, 10]]]}"; + OGCGeometry g = OGCGeometry.fromJson(s); + boolean res = g.isSimple(); + assertTrue(!res); + assertTrue(!g.isSimpleRelaxed()); + + } + + { + // hole touches exterior in two spots + String s = "{\"rings\":[[[-100, -100], [-100, 100], [0, 100], [100, 100], [100, -100], [0, -100], [-100, -100]], [[0, -100], [10, 0], [0, 100], [-10, 0], [0, -100]]]}"; + OGCGeometry g = OGCGeometry.fromJson(s); + boolean res = g.isSimple(); + assertTrue(!res); + assertTrue(g.isSimpleRelaxed()); + } + + { + // hole touches exterior in one spot + String s = "{\"rings\":[[[-100, -100], [-100, 100], [0, 100], [100, 100], [100, -100], [0, -100], [-100, -100]], [[0, -100], [10, 0], [0, 90], [-10, 0], [0, -100]]]}"; + OGCGeometry g = OGCGeometry.fromJson(s); + boolean res = g.isSimple(); + assertTrue(res); + assertTrue(g.isSimpleRelaxed()); + + } + + { + // exterior has inversion (planar simple) + String s = "{\"rings\":[[[-100, -100], [-100, 100], [0, 100], [100, 100], [100, -100], [0, -100], [10, 0], [0, 90], [-10, 0], [0, -100], [-100, -100]]]}"; + OGCGeometry g = OGCGeometry.fromJson(s); + boolean res = g.isSimple(); + assertTrue(!res); + assertTrue(g.isSimpleRelaxed()); + } + + { + // two holes touch in one spot, and they also touch exterior in + // two spots, producing disconnected interior + String s = "{\"rings\":[[[-100, -100], [-100, 100], [0, 100], [100, 100], [100, -100], [0, -100], [-100, -100]], [[0, -100], [10, -50], [0, 0], [-10, -50], [0, -100]], [[0, 0], [10, 50], [0, 100], [-10, 50], [0, 0]]]}"; + OGCGeometry g = OGCGeometry.fromJson(s); + boolean res = g.isSimple(); + assertTrue(!res); + assertTrue(g.isSimpleRelaxed()); + } + } catch (Exception ex) { + assertTrue(false); + } + } + + public void test_polyline_is_simple_for_OGC() { + try { + { + String s = "{\"paths\":[[[0, 10], [8, 5], [5, 2], [6, 0]]]}"; + OGCGeometry g = OGCGeometry.fromJson(s); + boolean res = g.isSimple(); + assertTrue(res); + assertTrue(g.isSimpleRelaxed()); + } + { + String s = "{\"paths\":[[[0, 10], [6, 0], [7, 5], [0, 3]]]}";// self + // intersection + OGCGeometry g = OGCGeometry.fromJson(s); + boolean res = g.isSimple(); + assertTrue(!res); + assertTrue(g.isSimpleRelaxed()); + } + + { + String s = "{\"paths\":[[[0, 10], [6, 0], [0, 3], [0, 10]]]}"; // closed + OGCGeometry g = OGCGeometry.fromJson(s); + boolean res = g.isSimple(); + assertTrue(res); + assertTrue(g.isSimpleRelaxed()); + } + + { + String s = "{\"paths\":[[[0, 10], [5, 5], [6, 0], [0, 3], [5, 5], [0, 9], [0, 10]]]}"; // closed + // with + // self + // tangent + OGCGeometry g = OGCGeometry.fromJson(s); + boolean res = g.isSimple(); + assertTrue(!res); + assertTrue(g.isSimpleRelaxed()); + } + + { + String s = "{\"paths\":[[[0, 10], [5, 2]], [[5, 2], [6, 0]]]}";// two + // paths + // connected + // at + // a + // point + OGCGeometry g = OGCGeometry.fromJson(s); + boolean res = g.isSimple(); + assertTrue(res); + assertTrue(g.isSimpleRelaxed()); + } + + { + String s = "{\"paths\":[[[0, 0], [3, 3], [5, 0], [0, 0]], [[0, 10], [3, 3], [10, 10], [0, 10]]]}";// two + // closed + // rings + // touch + // at + // one + // point + OGCGeometry g = OGCGeometry.fromJson(s); + boolean res = g.isSimple(); + assertTrue(!res); + assertTrue(g.isSimpleRelaxed()); + } + + { + String s = "{\"paths\":[[[3, 3], [0, 0], [5, 0], [3, 3]], [[3, 3], [0, 10], [10, 10], [3, 3]]]}"; + // two closed rings touch at one point. The touch happens at the + // endpoints of the paths. + OGCGeometry g = OGCGeometry.fromJson(s); + boolean res = g.isSimple(); + assertTrue(!res); + assertTrue(g.isSimpleRelaxed()); + } + + { + String s = "{\"paths\":[[[0, 0], [10, 10]], [[0, 10], [10, 0]]]}";// two + // lines + // intersect + OGCGeometry g = OGCGeometry.fromJson(s); + boolean res = g.isSimple(); + assertTrue(!res); + assertTrue(g.isSimpleRelaxed()); + } + + { + String s = "{\"paths\":[[[0, 0], [5, 5], [0, 10]], [[10, 10], [5, 5], [10, 0]]]}";// two + // paths + // share + // mid + // point. + OGCGeometry g = OGCGeometry.fromJson(s); + boolean res = g.isSimple(); + assertTrue(!res); + assertTrue(g.isSimpleRelaxed()); + } + } catch (Exception ex) { + assertTrue(false); + } + + } + + public void test_multipoint_is_simple_for_OGC() { + try { + + SpatialReference sr = SpatialReference.create(4326); + { + String s = "{\"points\":[[0, 0], [5, 5], [0, 10]]}"; + OGCGeometry g = OGCGeometry.fromJson(s); + boolean res = g.isSimple(); + assertTrue(res); + assertTrue(g.isSimpleRelaxed()); + } + { + String s = "{\"points\":[[0, 0], [5, 5], [0, 0], [0, 10]]}"; + OGCGeometry g = OGCGeometry.fromJson(s); + boolean res = g.isSimple(); + assertTrue(!res); + assertTrue(!g.isSimpleRelaxed()); + } + { + String s = "{\"points\":[[0, 0], [5, 5], [1e-10, -1e-10], [0, 10]]}"; + OGCGeometry g = OGCGeometry.fromJson(s); + g.setSpatialReference(sr); + boolean res = g.isSimple(); + assertTrue(!res); + assertTrue(g.isSimpleRelaxed()); + } + } catch (Exception ex) { + assertTrue(false); + } + + } + + public void testGeometryCollectionBuffer() { + OGCGeometry g = OGCGeometry + .fromText("GEOMETRYCOLLECTION(POINT(1 1), POINT(1 1), POINT(1 2), LINESTRING (0 0, 1 1, 1 0, 0 1), MULTIPOLYGON EMPTY, MULTILINESTRING EMPTY)"); + OGCGeometry simpleG = g.buffer(0); + String t = simpleG.geometryType(); + String rt = simpleG.asText(); + assertTrue(simpleG.geometryType().equals("GeometryCollection")); + } + + public void testIsectTria1() { + String wkt = "polygon((1 0, 3 0, 1 2, 1 0))"; + String wk2 = "polygon((0 1, 2 1, 0 3, 0 1))"; + OGCGeometry g0 = OGCGeometry.fromText(wkt); + OGCGeometry g1 = OGCGeometry.fromText(wk2); + g0.setSpatialReference(null); + g1.setSpatialReference(null); + OGCGeometry rslt = g0.intersection(g1); + assertTrue(rslt != null); + assertTrue(rslt.geometryType().equals("Polygon")); + String s = rslt.asText(); + } + + public void testIsectTria2() { + String wkt = "polygon((1 0, 3 0, 1 2, 1 0))"; + String wk2 = "polygon((0 3, 2 1, 3 1, 0 3))"; + OGCGeometry g0 = OGCGeometry.fromText(wkt); + OGCGeometry g1 = OGCGeometry.fromText(wk2); + g0.setSpatialReference(null); + g1.setSpatialReference(null); + OGCGeometry rslt = g0.intersection(g1); + assertTrue(rslt != null); + assertTrue(rslt.dimension() == 1); + assertTrue(rslt.geometryType().equals("LineString")); + String s = rslt.asText(); + } + + public void testIsectTria3() { + String wkt = "polygon((1 0, 3 0, 1 2, 1 0))"; + String wk2 = "polygon((2 2, 2 1, 3 1, 2 2))"; + OGCGeometry g0 = OGCGeometry.fromText(wkt); + OGCGeometry g1 = OGCGeometry.fromText(wk2); + g0.setSpatialReference(null); + g1.setSpatialReference(null); + OGCGeometry rslt = g0.intersection(g1); + assertTrue(rslt != null); + assertTrue(rslt.dimension() == 0); + assertTrue(rslt.geometryType().equals("Point")); + String s = rslt.asText(); + } + + public void testMultiPointSinglePoint() { + String wkt = "multipoint((1 0))"; + OGCGeometry g0 = OGCGeometry.fromText(wkt); + assertTrue(g0.dimension() == 0); + String gt = g0.geometryType(); + assertTrue(gt.equals("MultiPoint")); + OGCMultiPoint mp = (OGCMultiPoint)g0; + assertTrue(mp.numGeometries() == 1); + OGCGeometry p = mp.geometryN(0); + String s = p.asText(); + assertTrue(s.equals("POINT (1 0)")); + } + +} diff --git a/unittest/com/esri/core/geometry/TestOffset.java b/unittest/com/esri/core/geometry/TestOffset.java new file mode 100644 index 00000000..7ef61a01 --- /dev/null +++ b/unittest/com/esri/core/geometry/TestOffset.java @@ -0,0 +1,154 @@ +package com.esri.core.geometry; + +import com.esri.core.geometry.OperatorOffset.JoinType; +import junit.framework.TestCase; +import org.junit.Test; + +public class TestOffset extends TestCase { + @Override + protected void setUp() throws Exception { + super.setUp(); + } + + @Override + protected void tearDown() throws Exception { + super.tearDown(); + } + + @Test + public void testOffsetPoint() { + try { + Point point = new Point(); + point.setXY(0, 0); + + OperatorOffset offset = (OperatorOffset) OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Offset); + + Geometry outputGeom = offset.execute(point, null, 2, + JoinType.Round, 2, 0, null); + + assertNull(outputGeom); + } catch (Exception ex) { + } + + try { + MultiPoint mp = new MultiPoint(); + mp.add(0, 0); + mp.add(10, 10); + + OperatorOffset offset = (OperatorOffset) OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Offset); + + Geometry outputGeom = offset.execute(mp, null, 2, JoinType.Round, + 2, 0, null); + + assertNull(outputGeom); + } catch (Exception ex) { + } + } + + @Test + public void testOffsetPolyline() { + for (long i = -5; i <= 5; i++) { + try { + OffsetPolyline_(i, JoinType.Round); + } catch (Exception ex) { + fail("OffsetPolyline(Round) failure"); + } + + try { + OffsetPolyline_(i, JoinType.Miter); + } catch (Exception ex) { + fail("OffsetPolyline(Miter) failure"); + } + + try { + OffsetPolyline_(i, JoinType.Bevel); + } catch (Exception ex) { + fail("OffsetPolyline(Bevel) failure"); + } + + try { + OffsetPolyline_(i, JoinType.Square); + } catch (Exception ex) { + fail("OffsetPolyline(Square) failure"); + } + } + } + + public void OffsetPolyline_(double distance, JoinType joins) { + Polyline polyline = new Polyline(); + polyline.startPath(0, 0); + polyline.lineTo(6, 0); + polyline.lineTo(6, 1); + polyline.lineTo(4, 1); + polyline.lineTo(4, 2); + polyline.lineTo(10, 2); + + OperatorOffset offset = (OperatorOffset) OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Offset); + + Geometry outputGeom = offset.execute(polyline, null, distance, joins, + 2, 0, null); + + assertNotNull(outputGeom); + } + + @Test + public void testOffsetPolygon() { + for (long i = -5; i <= 5; i++) { + try { + OffsetPolygon_(i, JoinType.Round); + } catch (Exception ex) { + fail("OffsetPolyline(Round) failure"); + } + + try { + OffsetPolygon_(i, JoinType.Miter); + } catch (Exception ex) { + fail("OffsetPolyline(Miter) failure"); + } + + try { + OffsetPolygon_(i, JoinType.Bevel); + } catch (Exception ex) { + fail("OffsetPolyline(Bevel) failure"); + } + + try { + OffsetPolygon_(i, JoinType.Square); + } catch (Exception ex) { + fail("OffsetPolyline(Square) failure"); + } + } + } + + public void OffsetPolygon_(double distance, JoinType joins) { + Polygon polygon = new Polygon(); + polygon.startPath(0, 0); + polygon.lineTo(0, 16); + polygon.lineTo(16, 16); + polygon.lineTo(16, 11); + polygon.lineTo(10, 10); + polygon.lineTo(10, 12); + polygon.lineTo(3, 12); + polygon.lineTo(3, 4); + polygon.lineTo(10, 4); + polygon.lineTo(10, 6); + polygon.lineTo(16, 5); + polygon.lineTo(16, 0); + + OperatorOffset offset = (OperatorOffset) OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Offset); + + Geometry outputGeom = offset.execute(polygon, null, distance, joins, 2, + 0, null); + System.out.println(GeometryUtils.getJSonStringFromGeometry(outputGeom, + null)); + + assertNotNull(outputGeom); + if (distance > 2) { + assertTrue(outputGeom.isEmpty()); + } + } +} diff --git a/unittest/com/esri/core/geometry/TestPoint.java b/unittest/com/esri/core/geometry/TestPoint.java new file mode 100644 index 00000000..1a0bb15c --- /dev/null +++ b/unittest/com/esri/core/geometry/TestPoint.java @@ -0,0 +1,114 @@ +package com.esri.core.geometry; + +import java.util.Random; +import junit.framework.TestCase; +import org.junit.Test; + +public class TestPoint extends TestCase { + @Override + protected void setUp() throws Exception { + super.setUp(); + } + + @Override + protected void tearDown() throws Exception { + super.tearDown(); + } + + @Test + public void testPt() { + Point pt = new Point(); + assertTrue(pt.isEmpty()); + pt.setXY(10, 2); + assertFalse(pt.isEmpty()); + } + + @Test + public void testEnvelope2000() { + Point points[] = new Point[2000]; + Random random = new Random(69); + for (int i = 0; i < 2000; i++) { + points[i] = new Point(); + points[i].setX(random.nextDouble() * 100); + points[i].setY(random.nextDouble() * 100); + } + for (int iter = 0; iter < 2; iter++) { + final long startTime = System.nanoTime(); + Envelope geomExtent = new Envelope(); + Envelope fullExtent = new Envelope(); + for (int i = 0; i < 2000; i++) { + points[i].queryEnvelope(geomExtent); + fullExtent.merge(geomExtent); + } + long endTime = System.nanoTime(); + System.out.println((endTime - startTime) / 1.0e6); + } + } + + @Test + public void testBasic() { + assertTrue(Geometry.getDimensionFromType(Geometry.Type.Polygon.value()) == 2); + assertTrue(Geometry + .getDimensionFromType(Geometry.Type.Polyline.value()) == 1); + assertTrue(Geometry + .getDimensionFromType(Geometry.Type.Envelope.value()) == 2); + assertTrue(Geometry.getDimensionFromType(Geometry.Type.Line.value()) == 1); + assertTrue(Geometry.getDimensionFromType(Geometry.Type.Point.value()) == 0); + assertTrue(Geometry.getDimensionFromType(Geometry.Type.MultiPoint + .value()) == 0); + + assertTrue(Geometry.isLinear(Geometry.Type.Polygon.value())); + assertTrue(Geometry.isLinear(Geometry.Type.Polyline.value())); + assertTrue(Geometry.isLinear(Geometry.Type.Envelope.value())); + assertTrue(Geometry.isLinear(Geometry.Type.Line.value())); + assertTrue(!Geometry.isLinear(Geometry.Type.Point.value())); + assertTrue(!Geometry.isLinear(Geometry.Type.MultiPoint.value())); + + assertTrue(Geometry.isArea(Geometry.Type.Polygon.value())); + assertTrue(!Geometry.isArea(Geometry.Type.Polyline.value())); + assertTrue(Geometry.isArea(Geometry.Type.Envelope.value())); + assertTrue(!Geometry.isArea(Geometry.Type.Line.value())); + assertTrue(!Geometry.isArea(Geometry.Type.Point.value())); + assertTrue(!Geometry.isArea(Geometry.Type.MultiPoint.value())); + + assertTrue(!Geometry.isPoint(Geometry.Type.Polygon.value())); + assertTrue(!Geometry.isPoint(Geometry.Type.Polyline.value())); + assertTrue(!Geometry.isPoint(Geometry.Type.Envelope.value())); + assertTrue(!Geometry.isPoint(Geometry.Type.Line.value())); + assertTrue(Geometry.isPoint(Geometry.Type.Point.value())); + assertTrue(Geometry.isPoint(Geometry.Type.MultiPoint.value())); + + assertTrue(Geometry.isMultiVertex(Geometry.Type.Polygon.value())); + assertTrue(Geometry.isMultiVertex(Geometry.Type.Polyline.value())); + assertTrue(!Geometry.isMultiVertex(Geometry.Type.Envelope.value())); + assertTrue(!Geometry.isMultiVertex(Geometry.Type.Line.value())); + assertTrue(!Geometry.isMultiVertex(Geometry.Type.Point.value())); + assertTrue(Geometry.isMultiVertex(Geometry.Type.MultiPoint.value())); + + assertTrue(Geometry.isMultiPath(Geometry.Type.Polygon.value())); + assertTrue(Geometry.isMultiPath(Geometry.Type.Polyline.value())); + assertTrue(!Geometry.isMultiPath(Geometry.Type.Envelope.value())); + assertTrue(!Geometry.isMultiPath(Geometry.Type.Line.value())); + assertTrue(!Geometry.isMultiPath(Geometry.Type.Point.value())); + assertTrue(!Geometry.isMultiPath(Geometry.Type.MultiPoint.value())); + + assertTrue(!Geometry.isSegment(Geometry.Type.Polygon.value())); + assertTrue(!Geometry.isSegment(Geometry.Type.Polyline.value())); + assertTrue(!Geometry.isSegment(Geometry.Type.Envelope.value())); + assertTrue(Geometry.isSegment(Geometry.Type.Line.value())); + assertTrue(!Geometry.isSegment(Geometry.Type.Point.value())); + assertTrue(!Geometry.isSegment(Geometry.Type.MultiPoint.value())); + } + + @Test + public void testCopy() { + Point pt = new Point(); + Point copyPt = (Point) pt.copy(); + assertTrue(copyPt.equals(pt)); + + pt.setXY(11, 13); + copyPt = (Point) pt.copy(); + assertTrue(copyPt.equals(pt)); + assertTrue(copyPt.getXY().isEqual(new Point2D(11, 13))); + } +} diff --git a/unittest/com/esri/core/geometry/TestPolygon.java b/unittest/com/esri/core/geometry/TestPolygon.java new file mode 100644 index 00000000..d685923b --- /dev/null +++ b/unittest/com/esri/core/geometry/TestPolygon.java @@ -0,0 +1,1408 @@ +package com.esri.core.geometry; + +import junit.framework.TestCase; +import org.junit.Test; + +public class TestPolygon extends TestCase { + @Override + protected void setUp() throws Exception { + super.setUp(); + } + + @Override + protected void tearDown() throws Exception { + super.tearDown(); + } + + @Test + public void testCreation() { + // simple create + + Polygon poly = new Polygon(); + @SuppressWarnings("unused") + int number = poly.getStateFlag(); + + assertTrue(poly != null); + // assertTrue(poly.getClass() == Polygon.class); + // assertFalse(poly.getClass() == Envelope.class); + + assertTrue(poly.getType() == Geometry.Type.Polygon); + assertTrue(poly.isEmpty()); + assertTrue(poly.getPointCount() == 0); + assertTrue(poly.getPathCount() == 0); + number = poly.getStateFlag(); + poly = null; + assertFalse(poly != null); + + // play with default attributes + @SuppressWarnings("unused") + Polygon poly2 = new Polygon(); + // SimpleTest(poly2); + + // creation1(); + // creation2(); + // addpath(); + // addpath2(); + // removepath(); + // reversepath(); + // reverseallpaths(); + // openallpaths(); + // openpath(); + // insertpath(); + // insertpoints(); + // insertpoint(); + // removepoint(); + // insertpointsfromaray(); + // createWithStreams(); + // testBug1(); + } + + @Test + public void testCreation1() { + // Simple area and length calcul test + Polygon poly = new Polygon(); + @SuppressWarnings("unused") + int number = poly.getStateFlag(); + Envelope env = new Envelope(1000, 2000, 1010, 2010); + + poly.addEnvelope(env, false); + number = poly.getStateFlag(); + assertTrue(Math.abs(poly.calculateArea2D() - 100) < 1e-12); + assertTrue(Math.abs(poly.calculateLength2D() - 40) < 1e-12); + poly.setEmpty(); + number = poly.getStateFlag(); + poly.addEnvelope(env, true); + number = poly.getStateFlag(); + assertTrue(Math.abs(poly.calculateArea2D() + 100) < 1e-12); + number = poly.getStateFlag(); + + // FIXME + // env.queryCoornerByVal(index, ptDst) + // SPtrOfArrayOf(Point2D) corners = new ArrayOf(Point2D)(4); + // env.QueryCorners(corners); + // poly.setEmpty(); + // poly.addPath(corners, corners.length, true); + + // assertTrue(Math.abs(poly.calculateArea2D() - 100) < 1e-12); + + // { + // SegmentIterator segIter1 = poly.querySegmentIterator(); + // while (segIter1.nextPath()) + // { + // while (segIter1.hasNextSegment()) + // { + // Segment seg = segIter1.nextSegment(); + // double len = seg.calculateLength2D(); + // assertTrue(len != 0); + // } + // } + // } + + // env.QueryCornersReversed(corners); + // poly.setEmpty(); + // poly.addPath(corners, corners.length, true); + // assertTrue(Math.abs(poly.calculateArea2D() + 100) < 1e-12); + // + // poly.setEmpty(); + // env.SetCoords(-200, -200, 200, 200); + // poly.addEnvelope(env, false); + // env.SetCoords(-100, -100, 100, 100); + // poly.addEnvelope(env, true); + // assertTrue(Math.abs(poly.calculateArea2D() - (400 * 400 - 200 * 200)) + // < 1e-12); + // assertTrue(Math.abs(poly.calculateRingArea2D(1) - (- 200 * 200)) < + // 1e-12); + // assertTrue(Math.abs(poly.calculateRingArea2D(0) - (400 * 400)) < + // 1e-12); + // assertTrue(Math.abs(poly.calculateLength2D() - (400 * 4 + 200 * 4)) < + // 1e-12); + + // test CopyTo; + // Polygon polyCopy = new Polygon(); + // poly.copyTo(polyCopy); + // assertTrue(poly.calculateArea2D() == polyCopy.calculateArea2D()); + // assertTrue(poly.calculateRingArea2D(1) == + // polyCopy.calculateRingArea2D(1)); + // assertTrue(poly.calculateRingArea2D(1) == + // polyCopy.calculateRingArea2D(1)); + // assertTrue(poly.calculateLength2D() == polyCopy.calculateLength2D()); + // } + // + // Polygon poly = new Polygon(); + // poly.startPath(10, 1); + // poly.lineTo(15, 20); + // poly.lineTo(30, 14); + // poly.lineTo(60, 144); + // + // assertTrue(poly.getPointCount() == 4); + // assertTrue(poly.getPathCount() == 1); + // SPtrOfArrayOf(Point2D) xy = poly.getCoordinates2D(); + // assertTrue(xy[0].x == 10); assertTrue(xy[0].y == 1); + // assertTrue(xy[1].x == 15); assertTrue(xy[1].y == 20); + // assertTrue(xy[2].x == 30); assertTrue(xy[2].y == 14); + // assertTrue(xy[3].x == 60); assertTrue(xy[3].y == 144); + + // poly.startPath(20, 13); + // poly.lineTo(150, 120); + // poly.lineTo(300, 414); + // poly.lineTo(610, 14); + // poly.lineTo(6210, 140); + // + // assertTrue(poly.getPointCount() == 9); + // assertTrue(poly.getPathCount() == 2); + // assertTrue(poly.isClosedPath(0)); + // assertTrue(poly.isClosedPath(1)); + // assertFalse(poly.hasNonLinearSegments(0)); + // assertFalse(poly.hasNonLinearSegments(1)); + // + // { + // SegmentIterator segIter1 = poly.querySegmentIterator(); + // while (segIter1.nextPath()) + // { + // while (segIter1.hasNextSegment()) + // { + // Segment seg = segIter1.nextSegment(); + // double len = seg.calculateLength2D(); + // assertTrue(len != 0); + // } + // } + // } + + // { + // MultiPathImpl::Pointer mpImpl = + // (MultiPathImpl::Pointer)poly->_GetImpl(); + // AttributeStreamBase xy = + // mpImpl.getAttributeStreamRef(enum_value2(VertexDescription, + // Semantics, POSITION)); + // double x = xy.readAsDbl(2 * 0); + // double y = xy.readAsDbl(2 * 0 + 1); + // assertTrue(x == 10); assertTrue(y == 1); + // x = xy.readAsDbl(2 * 1); + // y = xy.readAsDbl(2 * 1 + 1); + // assertTrue(x == 15); assertTrue(y == 20); + // x = xy.readAsDbl(2 * 2); + // y = xy.readAsDbl(2 * 2 + 1); + // assertTrue(x == 30); assertTrue(y == 14); + // x = xy.readAsDbl(2 * 3); + // y = xy.readAsDbl(2 * 3 + 1); + // assertTrue(x == 60); assertTrue(y == 144); + // + // x = xy.readAsDbl(2 * 4); + // y = xy.readAsDbl(2 * 4 + 1); + // assertTrue(x == 20); assertTrue(y == 13); + // x = xy.readAsDbl(2 * 5); + // y = xy.readAsDbl(2 * 5 + 1); + // assertTrue(x == 150); assertTrue(y == 120); + // x = xy.readAsDbl(2 * 6); + // y = xy.readAsDbl(2 * 6 + 1); + // assertTrue(x == 300); assertTrue(y == 414); + // x = xy.readAsDbl(2 * 7); + // y = xy.readAsDbl(2 * 7 + 1); + // assertTrue(x == 610); assertTrue(y == 14); + // x = xy.readAsDbl(2 * 8); + // y = xy.readAsDbl(2 * 8 + 1); + // assertTrue(x == 6210); assertTrue(y == 140); + // + // assertTrue(Math.abs(mpImpl.calculateArea2D() - 71752.5) < 1e-6); + // assertTrue(Math.abs(mpImpl.calculateLength2D() - 13117.917692934170) + // < 1e-6); + // + // AttributeStreamOfIndexType parts = mpImpl.getPathStreamRef(); + // assertTrue(parts.size() == 3); + // assertTrue(parts.read(0) == 0); + // assertTrue(parts.read(1) == 4); + // assertTrue(parts.read(2) == 9); + // assertTrue(mpImpl.getSegmentIndexStreamRef() == NULLPTR); + // assertTrue(mpImpl.getSegmentFlagsStreamRef() == NULLPTR); + // assertTrue(mpImpl.getSegmentDataStreamRef() == NULLPTR); + // } + } + + @Test + public void testCreation2() { + Polygon poly = new Polygon(); + int state1 = poly.getStateFlag(); + poly.startPath(10, 1); + poly.lineTo(15, 20); + poly.lineTo(30, 14); + poly.lineTo(60, 144); + poly.closePathWithLine(); + int state2 = poly.getStateFlag(); + assertTrue(state2 == state1 + 1); + + // MultiPathImpl::Pointer mpImpl = + // (MultiPathImpl::Pointer)poly->_GetImpl(); + // + // assertTrue(mpImpl.getPointCount() == 4); + // assertTrue(mpImpl.getPathCount() == 1); + // AttributeStreamBase xy = + // mpImpl.getAttributeStreamRef(enum_value2(VertexDescription, + // Semantics, POSITION)); + // double x, y; + // x = xy.readAsDbl(2 * 2); + // y = xy.readAsDbl(2 * 2 + 1); + // assertTrue(x == 30); assertTrue(y == 14); + // + // AttributeStreamOfIndexType parts = mpImpl.getPathStreamRef(); + // assertTrue(parts.size() == 2); + // assertTrue(parts.read(0) == 0); + // assertTrue(parts.read(1) == 4); + // assertTrue(mpImpl.isClosedPath(0)); + // assertTrue(mpImpl.getSegmentFlagsStreamRef() == NULLPTR); + // assertTrue(mpImpl.getSegmentIndexStreamRef() == NULLPTR); + // assertTrue(mpImpl.getSegmentDataStreamRef() == NULLPTR); + + poly.startPath(20, 13); + poly.lineTo(150, 120); + poly.lineTo(300, 414); + poly.lineTo(610, 14); + poly.lineTo(6210, 140); + poly.closePathWithLine(); + + // assertTrue(mpImpl.getPointCount() == 9); + // assertTrue(mpImpl.getPathCount() == 2); + // assertTrue(mpImpl.isClosedPath(1)); + // xy = mpImpl.getAttributeStreamRef(enum_value2(VertexDescription, + // Semantics, POSITION)); + // x = xy.readAsDbl(2 * 3); + // y = xy.readAsDbl(2 * 3 + 1); + // assertTrue(x == 60); assertTrue(y == 144); + // + // x = xy.readAsDbl(2 * 6); + // y = xy.readAsDbl(2 * 6 + 1); + // assertTrue(x == 300); assertTrue(y == 414); + + // parts = mpImpl.getPathStreamRef(); + // assertTrue(parts.size() == 3); + // assertTrue(parts.read(0) == 0); + // assertTrue(parts.read(1) == 4); + // assertTrue(parts.read(2) == 9); + // assertTrue(mpImpl.getSegmentIndexStreamRef() == NULLPTR); + // assertTrue(mpImpl.getSegmentFlagsStreamRef() == NULLPTR); + // assertTrue(mpImpl.getSegmentDataStreamRef() == NULLPTR); + + poly.startPath(200, 1333); + poly.lineTo(1150, 1120); + poly.lineTo(300, 4114); + poly.lineTo(6110, 114); + poly.lineTo(61210, 1140); + + assertTrue(poly.isClosedPath(2) == true); + poly.closeAllPaths(); + assertTrue(poly.isClosedPath(2) == true); + + { + Polygon poly2 = new Polygon(); + poly2.startPath(10, 10); + poly2.lineTo(100, 10); + poly2.lineTo(100, 100); + poly2.lineTo(10, 100); + + // FIXME + // RasterizedGeometry2D rg = + // RasterizedGeometry2D.create((Geometry)poly2, 0, 1024); + // RasterizedGeometry2D.HitType res = null; + // res = rg.queryPointInGeometry(7, 10); + // assertTrue(res == RasterizedGeometry2D.HitType.Outside); + // res = rg.queryPointInGeometry(10, 10); + // assertTrue(res == RasterizedGeometry2D.HitType.Border); + // res = rg.queryPointInGeometry(50, 50); + // assertTrue(res == RasterizedGeometry2D.HitType.Inside); + } + + { + Polygon poly3 = new Polygon(); + // create a star (non-simple) + poly3.startPath(1, 0); + poly3.lineTo(5, 10); + poly3.lineTo(9, 0); + poly3.lineTo(0, 6); + poly3.lineTo(10, 6); + } + } + + @Test + public void testCreateWithStreams() { + // Polygon poly = new Polygon(); + // poly.addAttribute((int)Semantics.M); + // try + // { + // OutputDebugString(L"Test an assert\n"); + // GeometryException::m_assertOnException = false; + // ((MultiPathImpl::Pointer)poly->_GetImpl()).getPathStreamRef(); + // } + // catch(GeometryException except) + // { + // assertTrue(except->empty_geometry); + // GeometryException::m_assertOnException = true; + // } + // try + // { + // OutputDebugString(L"Test an assert\n"); + // GeometryException::m_assertOnException = false; + // ((MultiPathImpl::Pointer)poly->_GetImpl()).getAttributeStreamRef(enum_value2(VertexDescription, + // Semantics, POSITION)); + // } + // catch(GeometryException except) + // { + // assertTrue(except->empty_geometry); + // GeometryException::m_assertOnException = true; + // } + // + // MultiPathImpl::Pointer mpImpl = + // (MultiPathImpl::Pointer)poly->_GetImpl(); + // + // AttributeStreamOfIndexType parts = + // (AttributeStreamOfIndexType)AttributeStreamBase::CreateIndexStream(3); + // mpImpl.setPathStreamRef(parts); + // + // parts.write(0, 0); //first element is always 0 + // parts.write(1, 4); //second element is the index of the first vertex + // of the second part + // parts.write(2, 8); //the third element is the total point count. + // + // AttributeStreamOfInt8 flags = + // (AttributeStreamOfInt8)AttributeStreamBase::CreateByteStream(3); + // mpImpl.setPathFlagsStreamRef(flags); + // flags.write(0, enum_value1(PathFlags, enumClosed)); + // flags.write(1, enum_value1(PathFlags, enumClosed)); + // flags.write(2, 0); + // + // AttributeStreamOfDbl xy = + // (AttributeStreamOfDbl)AttributeStreamBase::CreateDoubleStream(16); + // //16 doubles means 8 points + // mpImpl.setAttributeStreamRef(enum_value2(VertexDescription, + // Semantics, POSITION), xy); + // + // Envelope2D env; + // env.SetCoords(-1000, -2000, 1000, 2000); + // Point2D buf[4]; + // env.QueryCorners(buf); + // xy.writeRange(0, 8, (double*)buf, 0, true); + // + // env.SetCoords(-100, -200, 100, 200); + // env.QueryCornersReversed(buf); //make a hole by quering reversed + // order + // xy.writeRange(8, 8, (double*)buf, 0, true); + // + // mpImpl.notifyModified(MultiVertexGeometryImpl::DirtyAll); //notify + // the path that the vertices had changed. + // + // assertTrue(poly.getPointCount() == 8); + // assertTrue(poly.getPathCount() == 2); + // assertTrue(poly.getPathSize(1) == 4); + // assertTrue(poly.isClosedPath(0)); + // assertTrue(poly.isClosedPath(1)); + } + + @Test + public void testCloneStuff() { + Polygon poly = new Polygon(); + poly.startPath(10, 1); + poly.lineTo(15, 20); + poly.lineTo(30, 14); + poly.lineTo(60, 144); + + poly.startPath(10, 1); + poly.lineTo(15, 20); + poly.lineTo(300, 14); + poly.lineTo(60, 144); + + poly.startPath(10, 1); + poly.lineTo(125, 20); + poly.lineTo(30, 14); + poly.lineTo(600, 144); + poly.closePathWithLine(); + + Polygon clone = (Polygon) poly.copy(); + assertTrue(clone.getPathCount() == 3); + assertTrue(clone.getPathStart(2) == 8); + assertTrue(clone.isClosedPath(0)); + assertTrue(clone.isClosedPath(1)); + assertTrue(clone.isClosedPath(2)); + assertTrue(clone.getXY(5).isEqual(new Point2D(15, 20))); + } + + @Test + public void testCloneStuffEnvelope() { + Envelope env = new Envelope(11, 12, 15, 24); + Envelope eCopy = (Envelope) env.copy(); + assertTrue(eCopy.equals(env)); + assertTrue(eCopy.getXMin() == 11); + assertTrue(eCopy.getYMin() == 12); + assertTrue(eCopy.getXMax() == 15); + assertTrue(eCopy.getYMax() == 24); + } + + @Test + public void testCloneStuffPolyline() { + Polyline poly = new Polyline(); + poly.startPath(10, 1); + poly.lineTo(15, 20); + poly.lineTo(30, 14); + poly.lineTo(60, 144); + + poly.startPath(10, 1); + poly.lineTo(15, 20); + poly.lineTo(300, 14); + poly.lineTo(60, 144); + + poly.startPath(10, 1); + poly.lineTo(125, 20); + poly.lineTo(30, 14); + poly.lineTo(600, 144); + poly.closePathWithLine(); + + Polyline clone = (Polyline) poly.copy(); + assertTrue(clone.getPathCount() == 3); + assertTrue(clone.getPathStart(2) == 8); + assertTrue(!clone.isClosedPath(0)); + assertTrue(!clone.isClosedPath(1)); + assertTrue(clone.isClosedPath(2)); + assertTrue(clone.getXY(5).isEqual(new Point2D(15, 20))); + } + + @Test + public void testAddpath() { + Polygon poly = new Polygon(); + poly.startPath(10, 1); + poly.lineTo(15, 20); + poly.lineTo(30, 14); + poly.lineTo(60, 144); + + poly.startPath(10, 1); + poly.lineTo(15, 20); + poly.lineTo(300, 14); + poly.lineTo(60, 144); + + poly.startPath(10, 1); + poly.lineTo(125, 20); + poly.lineTo(30, 14); + poly.lineTo(600, 144); + + Polygon poly1 = new Polygon(); + poly1.addPath(poly, 2, true); + poly1.addPath(poly, 0, true); + + assertTrue(poly1.getPathCount() == 2); + assertTrue(poly1.getPathStart(1) == 4); + assertTrue(poly1.isClosedPath(0)); + assertTrue(poly1.isClosedPath(1)); + Point ptOut = poly1.getPoint(6); + assertTrue(ptOut.getX() == 30 && ptOut.getY() == 14); + } + + @Test + public void testAddpath2() { + Polygon polygon = new Polygon(); + polygon.startPath(-179, 34); + polygon.lineTo(-154, 34); + polygon.lineTo(-179, 36); + polygon.lineTo(-180, 90); + polygon.lineTo(180, 90); + polygon.lineTo(180, 36); + polygon.lineTo(70, 46); + polygon.lineTo(-76, 80); + polygon.lineTo(12, 38); + polygon.lineTo(-69, 51); + polygon.lineTo(-95, 29); + polygon.lineTo(-105, 7); + polygon.lineTo(-112, -27); + polygon.lineTo(-149, -11); + polygon.lineTo(-149, -11); + polygon.lineTo(-166, -4); + polygon.lineTo(-179, 5); + + Polyline polyline = new Polyline(); + polyline.startPath(180, 5); + polyline.lineTo(140, 34); + polyline.lineTo(180, 34); + + polygon.addPath(polyline, 0, true); + + Point startpoint = polygon.getPoint(17); + assertTrue(startpoint.getX() == 180 && startpoint.getY() == 5); + } + + @Test + public void testRemovepath() { + Polygon poly = new Polygon(); + poly.startPath(10, 1); + poly.lineTo(15, 20); + poly.lineTo(30, 14); + poly.lineTo(60, 144); + + poly.startPath(10, 1); + poly.lineTo(15, 20); + poly.lineTo(300, 14); + poly.lineTo(60, 144); + + poly.startPath(10, 1); + poly.lineTo(125, 20); + poly.lineTo(30, 14); + poly.lineTo(600, 144); + + // poly->SetAttribute(enum_value2(VertexDescription, Semantics, Z), 0, + // 0, 2); + // poly->SetAttribute(enum_value2(VertexDescription, Semantics, Z), 1, + // 0, 3); + // poly->SetAttribute(enum_value2(VertexDescription, Semantics, Z), 2, + // 0, 5); + // poly->SetAttribute(enum_value2(VertexDescription, Semantics, Z), 3, + // 0, 7); + // poly->SetAttribute(enum_value2(VertexDescription, Semantics, Z), 4, + // 0, 11); + // poly->SetAttribute(enum_value2(VertexDescription, Semantics, Z), 5, + // 0, 13); + // poly->SetAttribute(enum_value2(VertexDescription, Semantics, Z), 6, + // 0, 17); + // poly->SetAttribute(enum_value2(VertexDescription, Semantics, Z), 7, + // 0, 19); + // poly->SetAttribute(enum_value2(VertexDescription, Semantics, Z), 8, + // 0, 23); + // poly->SetAttribute(enum_value2(VertexDescription, Semantics, Z), 9, + // 0, 29); + // poly->SetAttribute(enum_value2(VertexDescription, Semantics, Z), 10, + // 0, 31); + // poly->SetAttribute(enum_value2(VertexDescription, Semantics, Z), 11, + // 0, 37); + + poly.removePath(1); + + assertTrue(poly.getPathCount() == 2); + assertTrue(poly.getPathStart(1) == 4); + assertTrue(poly.isClosedPath(0)); + assertTrue(poly.isClosedPath(1)); + Point ptOut = poly.getPoint(4); + assertTrue(ptOut.getX() == 10 && ptOut.getY() == 1); + poly.removePath(0); + poly.removePath(0); + assertTrue(poly.getPathCount() == 0); + + Polygon poly2 = new Polygon(); + poly2.startPath(0, 0); + poly2.lineTo(0, 10); + poly2.lineTo(10, 10); + poly2.startPath(1, 1); + poly2.lineTo(2, 2); + poly2.removePath(0); + // poly2->StartPath(0, 0); + poly2.lineTo(0, 10); + poly2.lineTo(10, 10); + + // Polygon polygon2 = new Polygon(); + // polygon2.addPath(poly, -1, true); + // polygon2.addPath(poly, -1, true); + // polygon2.addPath(poly, -1, true); + // assertTrue(polygon2.getPathCount() == 3); + // polygon2.removePath(0); + // polygon2.removePath(0); + // polygon2.removePath(0); + // assertTrue(polygon2.getPathCount() == 0); + // polygon2.addPath(poly, -1, true); + + // Point point1 = new Point(); + // Point point2 = new Point(); + // point1.setX(0); + // point1.setY(0); + // point2.setX(0); + // point2.setY(0); + // polygon2.addPath(poly2, 0, true); + // polygon2.removePath(0); + // polygon2.insertPoint(0, 0, point1); + // polygon2.insertPoint(0, 0, point2); + // assertTrue(polygon2.getPathCount() == 1); + // assertTrue(polygon2.getPointCount() == 2); + + Polygon polygon3 = new Polygon(); + polygon3.startPath(0, 0); + polygon3.lineTo(0, 10); + polygon3.lineTo(10, 10); + double area = polygon3.calculateArea2D(); + polygon3.removePath(0); + + polygon3.startPath(0, 0); + polygon3.lineTo(0, 10); + polygon3.lineTo(10, 10); + area = polygon3.calculateArea2D(); + assertTrue(area > 0.0); + } + + @Test + public void testReversepath() { + Polygon poly = new Polygon(); + poly.startPath(10, 1); + poly.lineTo(15, 20); + poly.lineTo(30, 14); + poly.lineTo(60, 144); + + poly.startPath(10, 1); + poly.lineTo(15, 20); + poly.lineTo(300, 14); + poly.lineTo(60, 144); + + poly.startPath(10, 1); + poly.lineTo(125, 20); + poly.lineTo(30, 14); + poly.lineTo(600, 144); + + // poly.setAttribute(enum_value2(VertexDescription, Semantics, Z), 0, 0, + // 2); + // poly.setAttribute(enum_value2(VertexDescription, Semantics, Z), 1, 0, + // 3); + // poly.setAttribute(enum_value2(VertexDescription, Semantics, Z), 2, 0, + // 5); + // poly.setAttribute(enum_value2(VertexDescription, Semantics, Z), 3, 0, + // 7); + // poly.setAttribute(enum_value2(VertexDescription, Semantics, Z), 4, 0, + // 11); + // poly.setAttribute(enum_value2(VertexDescription, Semantics, Z), 5, 0, + // 13); + // poly.setAttribute(enum_value2(VertexDescription, Semantics, Z), 6, 0, + // 17); + // poly.setAttribute(enum_value2(VertexDescription, Semantics, Z), 7, 0, + // 19); + // poly.setAttribute(enum_value2(VertexDescription, Semantics, Z), 8, 0, + // 23); + // poly.setAttribute(enum_value2(VertexDescription, Semantics, Z), 9, 0, + // 29); + // poly.setAttribute(enum_value2(VertexDescription, Semantics, Z), 10, + // 0, 31); + // poly.setAttribute(enum_value2(VertexDescription, Semantics, Z), 11, + // 0, 37); + + poly.reversePath(1); + + assertTrue(poly.getPathCount() == 3); + assertTrue(poly.getPathStart(1) == 4); + assertTrue(poly.isClosedPath(0)); + assertTrue(poly.isClosedPath(1)); + Point ptOut = poly.getPoint(4); + assertTrue(ptOut.getX() == 10 && ptOut.getY() == 1); + } + + @Test + public void testReverseAllPaths() { + Polygon poly = new Polygon(); + poly.startPath(10, 1); + poly.lineTo(15, 20); + poly.lineTo(30, 14); + poly.lineTo(60, 144); + + poly.startPath(10, 1); + poly.lineTo(15, 20); + poly.lineTo(300, 14); + poly.lineTo(60, 144); + + poly.startPath(10, 1); + poly.lineTo(125, 20); + poly.lineTo(30, 14); + poly.lineTo(600, 144); + + // poly.setAttribute(enum_value2(VertexDescription, Semantics, Z), 0, 0, + // 2); + // poly.setAttribute(enum_value2(VertexDescription, Semantics, Z), 1, 0, + // 3); + // poly.setAttribute(enum_value2(VertexDescription, Semantics, Z), 2, 0, + // 5); + // poly.setAttribute(enum_value2(VertexDescription, Semantics, Z), 3, 0, + // 7); + // + // poly.setAttribute(enum_value2(VertexDescription, Semantics, Z), 4, 0, + // 11); + // poly.setAttribute(enum_value2(VertexDescription, Semantics, Z), 5, 0, + // 13); + // poly.setAttribute(enum_value2(VertexDescription, Semantics, Z), 6, 0, + // 17); + // poly.setAttribute(enum_value2(VertexDescription, Semantics, Z), 7, 0, + // 19); + // + // poly.setAttribute(enum_value2(VertexDescription, Semantics, Z), 8, 0, + // 23); + // poly.setAttribute(enum_value2(VertexDescription, Semantics, Z), 9, 0, + // 29); + // poly.setAttribute(enum_value2(VertexDescription, Semantics, Z), 10, + // 0, 31); + // poly.setAttribute(enum_value2(VertexDescription, Semantics, Z), 11, + // 0, 37); + + double area = poly.calculateArea2D(); + poly.reverseAllPaths(); + double areaReversed = poly.calculateArea2D(); + assertTrue(Math.abs(area + areaReversed) <= 0.001); + } + + @Test + public void testOpenAllPaths() { + Polyline poly = new Polyline(); + poly.startPath(10, 1); + poly.lineTo(15, 20); + poly.lineTo(30, 14); + poly.lineTo(60, 144); + poly.closePathWithLine(); + + poly.startPath(10, 1); + poly.lineTo(15, 20); + poly.lineTo(300, 14); + poly.lineTo(60, 144); + poly.closePathWithLine(); + + poly.startPath(10, 1); + poly.lineTo(125, 20); + poly.lineTo(30, 14); + poly.lineTo(600, 144); + poly.closePathWithLine(); + + // poly.setAttribute(enum_value2(VertexDescription, Semantics, Z), 0, 0, + // 2); + // poly.setAttribute(enum_value2(VertexDescription, Semantics, Z), 1, 0, + // 3); + // poly.setAttribute(enum_value2(VertexDescription, Semantics, Z), 2, 0, + // 5); + // poly.setAttribute(enum_value2(VertexDescription, Semantics, Z), 3, 0, + // 7); + // poly.setAttribute(enum_value2(VertexDescription, Semantics, Z), 4, 0, + // 11); + // poly.setAttribute(enum_value2(VertexDescription, Semantics, Z), 5, 0, + // 13); + // poly.setAttribute(enum_value2(VertexDescription, Semantics, Z), 6, 0, + // 17); + // poly.setAttribute(enum_value2(VertexDescription, Semantics, Z), 7, 0, + // 19); + // poly.setAttribute(enum_value2(VertexDescription, Semantics, Z), 8, 0, + // 23); + // poly.setAttribute(enum_value2(VertexDescription, Semantics, Z), 9, 0, + // 29); + // poly.setAttribute(enum_value2(VertexDescription, Semantics, Z), 10, + // 0, 31); + // poly.setAttribute(enum_value2(VertexDescription, Semantics, Z), 11, + // 0, 37); + + // MultiPathImpl::Pointer mpImpl = + // (MultiPathImpl::Pointer)poly->_GetImpl(); + // poly.openAllPathsAndDuplicateStartVertex(); + + // assertTrue(poly.getPathCount() == 3); + // assertTrue(poly.getPathStart(0) == 0); + // assertTrue(poly.getPathStart(1) == 5); + // assertTrue(poly.getPathStart(2) == 10); + // assertTrue(poly.getPointCount() == 15); + // Point ptstart = poly.getPoint(0); + // Point ptend = poly.getPoint(4); + // assertTrue(ptstart.getX() == ptend.getX() && ptstart.getY() == + // ptend.getY()); + // ptstart = poly.getPoint(5); + // ptend = poly.getPoint(9); + // assertTrue(ptstart.getX() == ptend.getX() && ptstart.getY() == + // ptend.getY()); + // ptstart = poly.getPoint(10); + // ptend = poly.getPoint(14); + // assertTrue(ptstart.getX() == ptend.getX() && ptstart.getY() == + // ptend.getY()); + } + + @Test + public void testOpenPath() { + Polyline poly = new Polyline(); + poly.startPath(10, 1); + poly.lineTo(15, 20); + poly.lineTo(30, 14); + poly.lineTo(60, 144); + poly.closePathWithLine(); + + poly.startPath(10, 1); + poly.lineTo(15, 20); + poly.lineTo(300, 14); + poly.lineTo(60, 144); + poly.closePathWithLine(); + + poly.startPath(10, 1); + poly.lineTo(125, 20); + poly.lineTo(30, 14); + poly.lineTo(600, 144); + poly.closePathWithLine(); + + // poly.setAttribute(enum_value2(VertexDescription, Semantics, Z), 0, 0, + // 2); + // poly.setAttribute(enum_value2(VertexDescription, Semantics, Z), 1, 0, + // 3); + // poly.setAttribute(enum_value2(VertexDescription, Semantics, Z), 2, 0, + // 5); + // poly.setAttribute(enum_value2(VertexDescription, Semantics, Z), 3, 0, + // 7); + // poly.setAttribute(enum_value2(VertexDescription, Semantics, Z), 4, 0, + // 11); + // poly.setAttribute(enum_value2(VertexDescription, Semantics, Z), 5, 0, + // 13); + // poly.setAttribute(enum_value2(VertexDescription, Semantics, Z), 6, 0, + // 17); + // poly.setAttribute(enum_value2(VertexDescription, Semantics, Z), 7, 0, + // 19); + // poly.setAttribute(enum_value2(VertexDescription, Semantics, Z), 8, 0, + // 23); + // poly.setAttribute(enum_value2(VertexDescription, Semantics, Z), 9, 0, + // 29); + // poly.setAttribute(enum_value2(VertexDescription, Semantics, Z), 10, + // 0, 31); + // poly.setAttribute(enum_value2(VertexDescription, Semantics, Z), 11, + // 0, 37); + // + // MultiPathImpl::Pointer mpImpl = + // (MultiPathImpl::Pointer)poly->_GetImpl(); + // poly.openPathAndDuplicateStartVertex(1); + + // assertTrue(poly.getPathCount() == 3); + // assertTrue(poly.getPathStart(0) == 0); + // assertTrue(poly.getPathStart(1) == 4); + // assertTrue(poly.getPathStart(2) == 9); + // assertTrue(poly.getPointCount() == 13); + // Point ptstart = poly.getPoint(4); + // Point ptend = poly.getPoint(8); + // assertTrue(ptstart.getX() == ptend.getX() && ptstart.getY() == + // ptend.getY()); + // ptstart = poly.getPoint(9); + // assertTrue(ptstart.getX() == 10 && ptstart.getY() == 1); + } + + @Test + public void testInsertPath() { + Polygon poly = new Polygon(); + poly.startPath(10, 1); + poly.lineTo(15, 20); + poly.lineTo(30, 14); + poly.lineTo(60, 144); + + poly.startPath(12, 2); + poly.lineTo(16, 21); + poly.lineTo(301, 15); + poly.lineTo(61, 145); + + poly.startPath(13, 3); + poly.lineTo(126, 22); + poly.lineTo(31, 16); + poly.lineTo(601, 146); + + // poly.setAttribute(enum_value2(VertexDescription, Semantics, Z), 0, 0, + // 2); + // poly.setAttribute(enum_value2(VertexDescription, Semantics, Z), 1, 0, + // 3); + // poly.setAttribute(enum_value2(VertexDescription, Semantics, Z), 2, 0, + // 5); + // poly.setAttribute(enum_value2(VertexDescription, Semantics, Z), 3, 0, + // 7); + // poly.setAttribute(enum_value2(VertexDescription, Semantics, Z), 4, 0, + // 11); + // poly.setAttribute(enum_value2(VertexDescription, Semantics, Z), 5, 0, + // 13); + // poly.setAttribute(enum_value2(VertexDescription, Semantics, Z), 6, 0, + // 17); + // poly.setAttribute(enum_value2(VertexDescription, Semantics, Z), 7, 0, + // 19); + // poly.setAttribute(enum_value2(VertexDescription, Semantics, Z), 8, 0, + // 23); + // poly.setAttribute(enum_value2(VertexDescription, Semantics, Z), 9, 0, + // 29); + // poly.setAttribute(enum_value2(VertexDescription, Semantics, Z), 10, + // 0, 31); + // poly.setAttribute(enum_value2(VertexDescription, Semantics, Z), 11, + // 0, 37); + + Polygon poly2 = new Polygon(); + poly2.startPath(12, 2); + poly2.lineTo(16, 21); + poly2.lineTo(301, 15); + poly2.lineTo(61, 145); + + poly.insertPath(0, poly2, 0, false); + + assertTrue(poly.getPathCount() == 4); + assertTrue(poly.getPathStart(0) == 0); + assertTrue(poly.getPathStart(1) == 4); + assertTrue(poly.getPathStart(2) == 8); + assertTrue(poly.getPathStart(3) == 12); + assertTrue(poly.getPointCount() == 16); + + Point2D pt0 = poly.getXY(0); + assertTrue(pt0.x == 12 && pt0.y == 2); + Point2D pt1 = poly.getXY(1); + assertTrue(pt1.x == 61 && pt1.y == 145); + Point2D pt2 = poly.getXY(2); + assertTrue(pt2.x == 301 && pt2.y == 15); + Point2D pt3 = poly.getXY(3); + assertTrue(pt3.x == 16 && pt3.y == 21); + + // SPtrOfArrayOf(Point2D) points = NULLPTR; + // FIXME + // poly.insertPath(1, points, 0, 0, true); + // assertTrue(poly.getPathCount() == 5); + // assertTrue(poly.getPathStart(0) == 0); + // assertTrue(poly.getPathStart(1) == 4); + // assertTrue(poly.getPathStart(2) == 4); + // assertTrue(poly.getPathStart(3) == 8); + // assertTrue(poly.getPathStart(4) == 12); + // assertTrue(poly.getPointCount() == 16); + + Point pt2d = new Point(-27, -27); + + poly.insertPoint(1, 0, pt2d); + assertTrue(poly.getPathCount() == 4); + assertTrue(poly.getPathStart(0) == 0); + assertTrue(poly.getPathStart(1) == 4); + assertTrue(poly.getPathStart(2) == 9); + assertTrue(poly.getPathStart(3) == 13); + assertTrue(poly.getPointCount() == 17); + + // points = new ArrayOf(Point2D)(3); + // points[0].x = 17; + // points[0].y = 17; + // points[1].x = 19; + // points[1].y = 19; + // points[2].x = 23; + // points[2].y = 23; + + // poly.insertPath(1, points, 0, 3, true); + // assertTrue(poly.getPathCount() == 6); + // assertTrue(poly.getPathStart(0) == 0); + // assertTrue(poly.getPathStart(1) == 4); + // assertTrue(poly.getPathStart(2) == 7); + // assertTrue(poly.getPathStart(3) == 8); + // assertTrue(poly.getPathStart(4) == 12); + // assertTrue(poly.getPathStart(5) == 16); + // assertTrue(poly.getPointCount() == 20); + + // Point2D *pointsNative = new Point2D[3]; + // pointsNative[0].x = 29; + // pointsNative[0].y = 29; + // pointsNative[1].x = 31; + // pointsNative[1].y = 31; + // pointsNative[2].x = 37; + // pointsNative[2].y = 37; + + // FIXME + // poly.insertPath(1, pointsNative, 0, 3, true); + // assertTrue(poly.getPathCount() == 7); + // assertTrue(poly.getPathStart(0) == 0); + // assertTrue(poly.getPathStart(1) == 4); + // assertTrue(poly.getPathStart(2) == 7); + // assertTrue(poly.getPathStart(3) == 10); + // assertTrue(poly.getPathStart(4) == 11); + // assertTrue(poly.getPathStart(5) == 15); + // assertTrue(poly.getPathStart(6) == 19); + // assertTrue(poly.getPointCount() == 23); + } + + @Test + public void testInsertPoints() { + {// forward insertion + Polygon poly = new Polygon(); + poly.startPath(10, 1); + poly.lineTo(15, 20); + poly.lineTo(30, 14); + poly.lineTo(60, 144); + + poly.startPath(10, 1); + poly.lineTo(15, 20); + poly.lineTo(300, 14); + poly.lineTo(314, 217); + poly.lineTo(60, 144); + + poly.startPath(10, 1); + poly.lineTo(125, 20); + poly.lineTo(30, 14); + poly.lineTo(600, 144); + + Polygon poly1 = new Polygon(); + poly1.startPath(1, 17); + poly1.lineTo(1, 207); + poly1.lineTo(3, 147); + poly1.lineTo(6, 1447); + + poly1.startPath(1000, 17); + poly1.lineTo(1250, 207); + poly1.lineTo(300, 147); + poly1.lineTo(6000, 1447); + + poly1.insertPoints(1, 2, poly, 1, 1, 3, true);// forward + + assertTrue(poly1.getPathCount() == 2); + assertTrue(poly1.getPathStart(1) == 4); + assertTrue(poly1.isClosedPath(0)); + assertTrue(poly1.isClosedPath(1)); + assertTrue(poly1.getPointCount() == 11); + assertTrue(poly1.getPathSize(1) == 7); + // Point2D ptOut; + // ptOut = poly1.getXY(5); + // assertTrue(ptOut.x == 1250 && ptOut.y == 207); + // ptOut = poly1.getXY(6); + // assertTrue(ptOut.x == 15 && ptOut.y == 20); + // ptOut = poly1.getXY(7); + // assertTrue(ptOut.x == 300 && ptOut.y == 14); + // ptOut = poly1.getXY(8); + // assertTrue(ptOut.x == 314 && ptOut.y == 217); + // ptOut = poly1.getXY(9); + // assertTrue(ptOut.x == 300 && ptOut.y == 147); + // ptOut = poly1.getXY(10); + // assertTrue(ptOut.x == 6000 && ptOut.y == 1447); + } + + {// reverse insertion + Polygon poly = new Polygon(); + poly.startPath(10, 1); + poly.lineTo(15, 20); + poly.lineTo(30, 14); + poly.lineTo(60, 144); + + poly.startPath(10, 1); + poly.lineTo(15, 20); + poly.lineTo(300, 14); + poly.lineTo(314, 217); + poly.lineTo(60, 144); + + poly.startPath(10, 1); + poly.lineTo(125, 20); + poly.lineTo(30, 14); + poly.lineTo(600, 144); + + Polygon poly1 = new Polygon(); + poly1.startPath(1, 17); + poly1.lineTo(1, 207); + poly1.lineTo(3, 147); + poly1.lineTo(6, 1447); + + poly1.startPath(1000, 17); + poly1.lineTo(1250, 207); + poly1.lineTo(300, 147); + poly1.lineTo(6000, 1447); + + poly1.insertPoints(1, 2, poly, 1, 1, 3, false);// reverse + + assertTrue(poly1.getPathCount() == 2); + assertTrue(poly1.getPathStart(1) == 4); + assertTrue(poly1.isClosedPath(0)); + assertTrue(poly1.isClosedPath(1)); + assertTrue(poly1.getPointCount() == 11); + assertTrue(poly1.getPathSize(1) == 7); + // Point2D ptOut; + // ptOut = poly1.getXY(5); + // assertTrue(ptOut.x == 1250 && ptOut.y == 207); + // ptOut = poly1.getXY(6); + // assertTrue(ptOut.x == 314 && ptOut.y == 217); + // ptOut = poly1.getXY(7); + // assertTrue(ptOut.x == 300 && ptOut.y == 14); + // ptOut = poly1.getXY(8); + // assertTrue(ptOut.x == 15 && ptOut.y == 20); + // ptOut = poly1.getXY(9); + // assertTrue(ptOut.x == 300 && ptOut.y == 147); + // ptOut = poly1.getXY(10); + // assertTrue(ptOut.x == 6000 && ptOut.y == 1447); + } + } + + @Test + public void testInsertPoint() { + Polygon poly = new Polygon(); + poly.startPath(10, 1); + poly.lineTo(15, 20); + poly.lineTo(30, 14); + poly.lineTo(60, 144); + + poly.startPath(10, 1); + poly.lineTo(15, 20); + poly.lineTo(300, 14); + poly.lineTo(314, 217); + poly.lineTo(60, 144); + + poly.startPath(10, 1); + poly.lineTo(125, 20); + poly.lineTo(30, 14); + poly.lineTo(600, 144); + + Point pt = new Point(-33, -34); + poly.insertPoint(1, 1, pt); + + pt = poly.getPoint(4); + assertTrue(pt.getX() == 10 && pt.getY() == 1); + pt = poly.getPoint(5); + assertTrue(pt.getX() == -33 && pt.getY() == -34); + pt = poly.getPoint(6); + assertTrue(pt.getX() == 15 && pt.getY() == 20); + + assertTrue(poly.getPointCount() == 14); + assertTrue(poly.getPathSize(1) == 6); + assertTrue(poly.getPathSize(2) == 4); + assertTrue(poly.getPathCount() == 3); + } + + @Test + public void testRemovePoint() { + Polygon poly = new Polygon(); + poly.startPath(10, 1); + poly.lineTo(15, 20); + poly.lineTo(30, 14); + poly.lineTo(60, 144); + + poly.startPath(10, 1); + poly.lineTo(15, 20); + poly.lineTo(300, 14); + poly.lineTo(314, 217); + poly.lineTo(60, 144); + + poly.startPath(10, 1); + poly.lineTo(125, 20); + poly.lineTo(30, 14); + poly.lineTo(600, 144); + + poly.removePoint(1, 1); + + Point pt; + + pt = poly.getPoint(4); + assertTrue(pt.getX() == 10 && pt.getY() == 1); + pt = poly.getPoint(5); + assertTrue(pt.getX() == 300 && pt.getY() == 14); + + assertTrue(poly.getPointCount() == 12); + assertTrue(poly.getPathSize(0) == 4); + assertTrue(poly.getPathSize(1) == 4); + assertTrue(poly.getPathSize(2) == 4); + } + + @Test + public static void testPolygonAreaAndLength() { + Polygon poly; + + /* const */double r = 1.0; + /* const */double epsilon = 1.0e-14; + /* const */int nMax = 40; + + // If r == 1.0 and nMax == 40 and epsilon == 1.0e-14, it will pass. + // But if r == 1.0 and nMax == 40 and epsilon == 1.0e-15, it will fail. + + for (int n = 3; n < nMax; n++) { + // regular polygon with n vertices and length from center to vertex + // = r + poly = new Polygon(); + double theta = 0.0; + poly.startPath(r, 0.0); + for (int k = 1; k <= n; k++) { + theta -= 2 * Math.PI / n; + poly.lineTo(r * Math.cos(theta), r * Math.sin(theta)); + } + double sinPiOverN = Math.sin(Math.PI / n); + double sinTwoPiOverN = Math.sin(2.0 * Math.PI / n); + double analyticalLength = 2.0 * n * r * sinPiOverN; + double analyticalArea = 0.5 * n * r * r * sinTwoPiOverN; + double calculatedLength = poly.calculateLength2D(); + double calculatedArea = poly.calculateArea2D(); + assertTrue(Math.abs(analyticalLength - calculatedLength) < epsilon); + assertTrue(Math.abs(analyticalArea - calculatedArea) < epsilon); + } + } + + @Test + public void testInsertPointsFromArray() { + {// Test forward insertion of an array of Point2D + // ArrayOf(Point2D) arr = new ArrayOf(Point2D)(5); + // arr[0].SetCoords(10, 1); + // arr[1].SetCoords(15, 20); + // arr[2].SetCoords(300, 14); + // arr[3].SetCoords(314, 217); + // arr[4].SetCoords(60, 144); + + Polygon poly1 = new Polygon(); + poly1.startPath(1, 17); + poly1.lineTo(1, 207); + poly1.lineTo(3, 147); + poly1.lineTo(6, 1447); + + poly1.startPath(1000, 17); + poly1.lineTo(1250, 207); + poly1.lineTo(300, 147); + poly1.lineTo(6000, 1447); + + // FIXME + // poly1.insertPoints(1, 2, arr, 1, 3, true); //forward + + assertTrue(poly1.getPathCount() == 2); + assertTrue(poly1.getPathStart(1) == 4); + assertTrue(poly1.isClosedPath(0)); + assertTrue(poly1.isClosedPath(1)); + // assertTrue(poly1.getPointCount() == 11); + // assertTrue(poly1.getPathSize(1) == 7); + // Point2D ptOut; + // ptOut = poly1.getXY(5); + // assertTrue(ptOut.x == 1250 && ptOut.y == 207); + // ptOut = poly1.getXY(6); + // assertTrue(ptOut.x == 15 && ptOut.y == 20); + // ptOut = poly1.getXY(7); + // assertTrue(ptOut.x == 300 && ptOut.y == 14); + // ptOut = poly1.getXY(8); + // assertTrue(ptOut.x == 314 && ptOut.y == 217); + // ptOut = poly1.getXY(9); + // assertTrue(ptOut.x == 300 && ptOut.y == 147); + // ptOut = poly1.getXY(10); + // assertTrue(ptOut.x == 6000 && ptOut.y == 1447); + + // Point2D *points = new Point2D[3]; + // points[0].x = 17; + // points[0].y = 17; + // points[1].x = 19; + // points[1].y = 19; + // points[2].x = 23; + // points[2].y = 23; + + // FIXME + // poly1.insertPoints(1, 2, points, 0, 3, true); + // assertTrue(poly1.getPathCount() == 2); + // assertTrue(poly1.getPathStart(1) == 4); + // assertTrue(poly1.getPointCount() == 14); + // assertTrue(poly1.getPathSize(1) == 10); + + // ptOut = poly1.getXY(5); + // assertTrue(ptOut.x == 1250 && ptOut.y == 207); + // ptOut = poly1.getXY(6); + // assertTrue(ptOut.x == 17 && ptOut.y == 17); + // ptOut = poly1.getXY(7); + // assertTrue(ptOut.x == 19 && ptOut.y == 19); + // ptOut = poly1.getXY(8); + // assertTrue(ptOut.x == 23 && ptOut.y == 23); + // ptOut = poly1.getXY(9); + // assertTrue(ptOut.x == 15 && ptOut.y == 20); + // ptOut = poly1.getXY(10); + // assertTrue(ptOut.x == 300 && ptOut.y == 14); + } + + {// Test reversed insertion of an array of Point2D + // ArrayOf(Point2D) arr = new ArrayOf(Point2D)(5); + // arr[0].SetCoords(10, 1); + // arr[1].SetCoords(15, 20); + // arr[2].SetCoords(300, 14); + // arr[3].SetCoords(314, 217); + // arr[4].SetCoords(60, 144); + + // Polygon poly1 = new Polygon(); + // poly1.startPath(1, 17); + // poly1.lineTo(1, 207); + // poly1.lineTo(3, 147); + // poly1.lineTo(6, 1447); + // + // poly1.startPath(1000, 17); + // poly1.lineTo(1250, 207); + // poly1.lineTo(300, 147); + // poly1.lineTo(6000, 1447); + // + // //FIXME + // //poly1.insertPoints(1, 2, arr, 1, 3, false); //reversed + // + // assertTrue(poly1.getPathCount() == 2); + // assertTrue(poly1.getPathStart(1) == 4); + // assertTrue(poly1.isClosedPath(0)); + // assertTrue(poly1.isClosedPath(1)); + // assertTrue(poly1.getPointCount() == 11); + // assertTrue(poly1.getPathSize(1) == 7); + // Point2D ptOut; + // ptOut = poly1.getXY(5); + // assertTrue(ptOut.x == 1250 && ptOut.y == 207); + // ptOut = poly1.getXY(6); + // assertTrue(ptOut.x == 314 && ptOut.y == 217); + // ptOut = poly1.getXY(7); + // assertTrue(ptOut.x == 300 && ptOut.y == 14); + // ptOut = poly1.getXY(8); + // assertTrue(ptOut.x == 15 && ptOut.y == 20); + // ptOut = poly1.getXY(9); + // assertTrue(ptOut.x == 300 && ptOut.y == 147); + // ptOut = poly1.getXY(10); + // assertTrue(ptOut.x == 6000 && ptOut.y == 1447); + } + } + + @Test + public void testCR177477() { + Polygon pg = new Polygon(); + pg.startPath(-130, 40); + pg.lineTo(-70, 40); + pg.lineTo(-70, 10); + pg.lineTo(-130, 10); + + Polygon pg2 = new Polygon(); + pg2.startPath(-60, 40); + pg2.lineTo(-50, 40); + pg2.lineTo(-50, 10); + pg2.lineTo(-60, 10); + + pg.add(pg2, false); + } + + @Test + public void testCR177477getPathEnd() { + Polygon pg = new Polygon(); + pg.startPath(-130, 40); + pg.lineTo(-70, 40); + pg.lineTo(-70, 10); + pg.lineTo(-130, 10); + + pg.startPath(-60, 40); + pg.lineTo(-50, 40); + pg.lineTo(-50, 10); + pg.lineTo(-60, 10); + + pg.startPath(-40, 40); + pg.lineTo(-30, 40); + pg.lineTo(-30, 10); + pg.lineTo(-40, 10); + + int pathCount = pg.getPathCount(); + assertTrue(pathCount == 3); + + // int startIndex = pg.getPathStart(pathCount - 1); + + // int endIndex = pg.getPathEnd(pathCount - 1); + + Line line = new Line(); + line.setStart(new Point(0, 0)); + line.setEnd(new Point(1, 0)); + + double geoLength = GeometryEngine.geodesicDistanceOnWGS84(new Point(0, + 0), new Point(1, 0)); + assertTrue(Math.abs(geoLength - 111319) < 1); + } + + @Test + public void testBug1() { + Polygon pg = new Polygon(); + pg.startPath(-130, 40); + for (int i = 0; i < 1000; i++) + pg.lineTo(-70, 40); + for (int i = 0; i < 999; i++) + pg.removePoint(0, pg.getPointCount() - 1); + + pg.lineTo(-70, 40); + } + + @Test + public void testGeometryCopy() { + boolean noException = true; + + Polyline polyline = new Polyline(); + + Point p1 = new Point(-85.59285621496956, 38.26004727491098); + Point p2 = new Point(-85.56417866635002, 38.28084064314639); + Point p3 = new Point(-85.56845156650877, 38.24659881865461); + Point p4 = new Point(-85.55341069949853, 38.26671513050464); + + polyline.startPath(p1); + try { + polyline.lineTo(p2); + polyline.copy(); + polyline.lineTo(p3); + polyline.copy(); + polyline.lineTo(p4); // exception thrown here!!! + + } catch (Exception e) { + e.printStackTrace(); + noException = false; + } + + assertTrue(noException); + }// end of method +} diff --git a/unittest/com/esri/core/geometry/TestPolygonUtils.java b/unittest/com/esri/core/geometry/TestPolygonUtils.java new file mode 100644 index 00000000..16bc5215 --- /dev/null +++ b/unittest/com/esri/core/geometry/TestPolygonUtils.java @@ -0,0 +1,132 @@ +package com.esri.core.geometry; + +import junit.framework.TestCase; +import org.junit.Test; + +public class TestPolygonUtils extends TestCase { + @Override + protected void setUp() throws Exception { + super.setUp(); + } + + @Override + protected void tearDown() throws Exception { + super.tearDown(); + } + + @Test + public static void testPointInAnyOuterRing() { + Polygon polygon = new Polygon(); + // outer ring1 + polygon.startPath(-200, -100); + polygon.lineTo(200, -100); + polygon.lineTo(200, 100); + polygon.lineTo(-190, 100); + polygon.lineTo(-190, 90); + polygon.lineTo(-200, 90); + + // hole + polygon.startPath(-100, 50); + polygon.lineTo(100, 50); + polygon.lineTo(100, -40); + polygon.lineTo(90, -40); + polygon.lineTo(90, -50); + polygon.lineTo(-100, -50); + + // island + polygon.startPath(-10, -10); + polygon.lineTo(10, -10); + polygon.lineTo(10, 10); + polygon.lineTo(-10, 10); + + // outer ring2 + polygon.startPath(300, 300); + polygon.lineTo(310, 300); + polygon.lineTo(310, 310); + polygon.lineTo(300, 310); + + polygon.reverseAllPaths(); + + Point2D testPointIn1 = new Point2D(1, 2); // inside the island + Point2D testPointIn2 = new Point2D(190, 90); // inside, betwen outer + // ring1 and the hole + Point2D testPointIn3 = new Point2D(305, 305); // inside the outer ring2 + Point2D testPointOut1 = new Point2D(300, 2); // outside any + Point2D testPointOut2 = new Point2D(-195, 95); // outside any (in the + // concave area of outer + // ring 2) + Point2D testPointOut3 = new Point2D(99, 49); // outside (in the hole) + + PolygonUtils.PiPResult res; + // is_point_in_polygon_2D + res = PolygonUtils.isPointInPolygon2D(polygon, testPointIn1, 0); + assertTrue(res == PolygonUtils.PiPResult.PiPInside); + res = PolygonUtils.isPointInPolygon2D(polygon, testPointIn2, 0); + assertTrue(res == PolygonUtils.PiPResult.PiPInside); + res = PolygonUtils.isPointInPolygon2D(polygon, testPointIn3, 0); + assertTrue(res == PolygonUtils.PiPResult.PiPInside); + + res = PolygonUtils.isPointInPolygon2D(polygon, testPointOut1, 0); + assertTrue(res == PolygonUtils.PiPResult.PiPOutside); + res = PolygonUtils.isPointInPolygon2D(polygon, testPointOut2, 0); + assertTrue(res == PolygonUtils.PiPResult.PiPOutside); + res = PolygonUtils.isPointInPolygon2D(polygon, testPointOut3, 0); + assertTrue(res == PolygonUtils.PiPResult.PiPOutside); + + // Ispoint_in_any_outer_ring + res = PolygonUtils.isPointInAnyOuterRing(polygon, testPointIn1, 0); + assertTrue(res == PolygonUtils.PiPResult.PiPInside); + res = PolygonUtils.isPointInAnyOuterRing(polygon, testPointIn2, 0); + assertTrue(res == PolygonUtils.PiPResult.PiPInside); + res = PolygonUtils.isPointInAnyOuterRing(polygon, testPointIn3, 0); + assertTrue(res == PolygonUtils.PiPResult.PiPInside); + + res = PolygonUtils.isPointInAnyOuterRing(polygon, testPointOut1, 0); + assertTrue(res == PolygonUtils.PiPResult.PiPOutside); + res = PolygonUtils.isPointInAnyOuterRing(polygon, testPointOut2, 0); + assertTrue(res == PolygonUtils.PiPResult.PiPOutside); + res = PolygonUtils.isPointInAnyOuterRing(polygon, testPointOut3, 0); + assertTrue(res == PolygonUtils.PiPResult.PiPInside);// inside of outer + // ring + } + + @Test + public static void testPointInPolygonBugCR181840() { + PolygonUtils.PiPResult res; + {// pointInPolygonBugCR181840 - point in polygon bug + Polygon polygon = new Polygon(); + // outer ring1 + polygon.startPath(0, 0); + polygon.lineTo(10, 10); + polygon.lineTo(20, 0); + + res = PolygonUtils.isPointInPolygon2D(polygon, + Point2D.construct(15, 10), 0); + assertTrue(res == PolygonUtils.PiPResult.PiPOutside); + res = PolygonUtils.isPointInPolygon2D(polygon, + Point2D.construct(2, 10), 0); + assertTrue(res == PolygonUtils.PiPResult.PiPOutside); + res = PolygonUtils.isPointInPolygon2D(polygon, + Point2D.construct(5, 5), 0); + assertTrue(res == PolygonUtils.PiPResult.PiPInside); + } + + {// CR181840 - point in polygon bug + Polygon polygon = new Polygon(); + // outer ring1 + polygon.startPath(10, 10); + polygon.lineTo(20, 0); + polygon.lineTo(0, 0); + + res = PolygonUtils.isPointInPolygon2D(polygon, + Point2D.construct(15, 10), 0); + assertTrue(res == PolygonUtils.PiPResult.PiPOutside); + res = PolygonUtils.isPointInPolygon2D(polygon, + Point2D.construct(2, 10), 0); + assertTrue(res == PolygonUtils.PiPResult.PiPOutside); + res = PolygonUtils.isPointInPolygon2D(polygon, + Point2D.construct(5, 5), 0); + assertTrue(res == PolygonUtils.PiPResult.PiPInside); + } + } +} diff --git a/unittest/com/esri/core/geometry/TestProximity2D.java b/unittest/com/esri/core/geometry/TestProximity2D.java new file mode 100644 index 00000000..a0766cd5 --- /dev/null +++ b/unittest/com/esri/core/geometry/TestProximity2D.java @@ -0,0 +1,239 @@ +package com.esri.core.geometry; + +import junit.framework.TestCase; +import org.junit.Test; + +public class TestProximity2D extends TestCase { + @Override + protected void setUp() throws Exception { + super.setUp(); + } + + @Override + protected void tearDown() throws Exception { + super.tearDown(); + } + + @Test + public void testProximity_2D_1() { + OperatorFactoryLocal engine = OperatorFactoryLocal.getInstance(); + + @SuppressWarnings("unused") + OperatorProximity2D proximityOp = (OperatorProximity2D) engine + .getOperator(Operator.Type.Proximity2D); + + Point inputPoint = new Point(3, 2); + + Point point0 = new Point(2.75, 2); + // Point point1 = new Point(3, 2.5); + // Point point2 = new Point(3.75, 2); + // Point point3 = new Point(2.25, 2.5); + // Point point4 = new Point(4, 2.25); + + // GetNearestVertices for Polygon (Native and DotNet) + Polygon polygon = MakePolygon(); + + Proximity2DResult[] resultArray = GeometryEngine.getNearestVertices( + polygon, inputPoint, 2.0, 8); + assertTrue(resultArray.length == 8); + + double lastdistance; + double distance; + + Proximity2DResult result0 = resultArray[0]; + lastdistance = result0.getDistance(); + assertTrue(lastdistance <= 2.0); + + Proximity2DResult result1 = resultArray[1]; + distance = result1.getDistance(); + assertTrue(distance <= 2.0 && distance >= lastdistance); + lastdistance = distance; + + Proximity2DResult result2 = resultArray[2]; + distance = result2.getDistance(); + assertTrue(distance <= 2.0 && distance >= lastdistance); + lastdistance = distance; + + Proximity2DResult result3 = resultArray[3]; + distance = result3.getDistance(); + assertTrue(distance <= 2.0 && distance >= lastdistance); + lastdistance = distance; + + Proximity2DResult result4 = resultArray[4]; + distance = result4.getDistance(); + assertTrue(distance <= 2.0 && distance >= lastdistance); + lastdistance = distance; + + Proximity2DResult result5 = resultArray[5]; + distance = result5.getDistance(); + assertTrue(distance <= 2.0 && distance >= lastdistance); + lastdistance = distance; + + Proximity2DResult result6 = resultArray[6]; + distance = result6.getDistance(); + assertTrue(distance <= 2.0 && distance >= lastdistance); + lastdistance = distance; + + Proximity2DResult result7 = resultArray[7]; + distance = result7.getDistance(); + assertTrue(distance <= 2.0 && distance >= lastdistance); + // lastdistance = distance; + + // Point[] coordinates = polygon.get.getCoordinates2D(); + // int pointCount = polygon.getPointCount(); + // + // int hits = 0; + // for (int i = 0; i < pointCount; i++) + // { + // Point ipoint = coordinates[i]; + // distance = Point::Distance(ipoint, inputPoint); + // + // if (distance < lastdistance) + // hits++; + // } + + // assertTrue(hits < 8); + + // GetNearestVertices for Point + Point point = MakePoint(); + resultArray = GeometryEngine.getNearestVertices(point, inputPoint, 1.0, + 1); + assertTrue(resultArray.length == 1); + result0 = resultArray[0]; + Point resultPoint0 = result0.getCoordinate(); + assertTrue(resultPoint0.getX() == point.getX() + && resultPoint0.getY() == point.getY()); + + // GetNearestVertex for Polygon + result0 = GeometryEngine.getNearestVertex(polygon, inputPoint); + resultPoint0 = result0.getCoordinate(); + assertTrue(resultPoint0.getX() == point0.getX() + && resultPoint0.getY() == point0.getY()); + + // GetNearestVertex for Point + result0 = GeometryEngine.getNearestVertex(point, inputPoint); + resultPoint0 = result0.getCoordinate(); + assertTrue(resultPoint0.getX() == point.getX() + && resultPoint0.getY() == point.getY()); + + // GetNearestCoordinate for Polygon + Polygon polygon2 = MakePolygon2(); + result0 = GeometryEngine.getNearestCoordinate(polygon2, inputPoint, + true); + resultPoint0 = result0.getCoordinate(); + assertTrue(resultPoint0.getX() == inputPoint.getX() + && resultPoint0.getY() == inputPoint.getY()); + + // GetNearestCoordinate for Polyline + Polyline polyline = MakePolyline(); + result0 = GeometryEngine.getNearestCoordinate(polyline, inputPoint, + true); + resultPoint0 = result0.getCoordinate(); + assertTrue(resultPoint0.getX() == 0.0 && resultPoint0.getY() == 2.0); + } + + Polygon MakePolygon() { + Polygon poly = new Polygon(); + poly.startPath(3, -2); + poly.lineTo(2, -1); + poly.lineTo(3, 0); + poly.lineTo(4, 0); + + poly.startPath(1.75, 1); + poly.lineTo(0.75, 2); + poly.lineTo(1.75, 3); + poly.lineTo(2.25, 2.5); + poly.lineTo(2.75, 2); + + poly.startPath(3, 2.5); + poly.lineTo(2.5, 3); + poly.lineTo(2, 3.5); + poly.lineTo(3, 4.5); + poly.lineTo(4, 3.5); + + poly.startPath(4.75, 1); + poly.lineTo(3.75, 2); + poly.lineTo(4, 2.25); + poly.lineTo(4.75, 3); + poly.lineTo(5.75, 2); + + return poly; + } + + Polygon MakePolygon2() { + Polygon poly = new Polygon(); + poly.startPath(0, 0); + poly.lineTo(0, 10); + poly.lineTo(10, 10); + poly.lineTo(10, 0); + + return poly; + } + + Polyline MakePolyline() { + Polyline poly = new Polyline(); + poly.startPath(0, 0); + poly.lineTo(0, 10); + poly.lineTo(10, 10); + poly.lineTo(10, 0); + + return poly; + } + + Point MakePoint() { + Point point = new Point(3, 2.5); + + return point; + } + + @Test + public void testProximity2D_2() { + Point point1 = new Point(3, 2); + Point point2 = new Point(2, 4); + Envelope envelope = new Envelope(); + envelope.setCoords(4, 3, 7, 6); + Polygon polygonToTest = new Polygon(); + polygonToTest.addEnvelope(envelope, false); + Proximity2DResult prxResult1 = GeometryEngine.getNearestVertex( + envelope, point1); + Proximity2DResult prxResult2 = GeometryEngine.getNearestVertex( + polygonToTest, point1); + Proximity2DResult prxResult3 = GeometryEngine.getNearestCoordinate( + envelope, point2, false); + Proximity2DResult prxResult4 = GeometryEngine.getNearestCoordinate( + polygonToTest, point2, false); + + Point result1 = prxResult1.getCoordinate(); + Point result2 = prxResult2.getCoordinate(); + assertTrue(result1.getX() == result2.getX()); + Point result3 = prxResult3.getCoordinate(); + Point result4 = prxResult4.getCoordinate(); + assertTrue(result3.getX() == result4.getX()); + } + + @Test + public static void testProximity2D_3() { + OperatorFactoryLocal factory = OperatorFactoryLocal.getInstance(); + OperatorProximity2D proximity = (OperatorProximity2D) factory + .getOperator(com.esri.core.geometry.Operator.Type.Proximity2D); + + Polygon polygon = new Polygon(); + polygon.startPath(new Point(-120, 22)); + polygon.lineTo(new Point(-120, 10)); + polygon.lineTo(new Point(-110, 10)); + polygon.lineTo(new Point(-110, 22)); + + Point point = new Point(); + point.setXY(-110, 20); + Proximity2DResult result = proximity.getNearestCoordinate(polygon, + point, false); + System.out.println("The closest coordinate is " + + result.getCoordinate()); + System.out.println("The closest point is " + result.getDistance()); + Point point2 = new Point(); + point2.setXY(-120, 12); + @SuppressWarnings("unused") + Proximity2DResult[] results = proximity.getNearestVertices(polygon, + point2, 10, 12); + } +} diff --git a/unittest/com/esri/core/geometry/TestQuadTree.java b/unittest/com/esri/core/geometry/TestQuadTree.java new file mode 100644 index 00000000..adca5ac6 --- /dev/null +++ b/unittest/com/esri/core/geometry/TestQuadTree.java @@ -0,0 +1,140 @@ +package com.esri.core.geometry; + +import junit.framework.TestCase; +import org.junit.Test; + +public class TestQuadTree extends TestCase { + @Override + protected void setUp() throws Exception { + super.setUp(); + } + + @Override + protected void tearDown() throws Exception { + super.tearDown(); + } + + @Test + public static void test1() { + Polyline polyline; + polyline = makePolyline(); + + MultiPathImpl polylineImpl = (MultiPathImpl) polyline._getImpl(); + QuadTree quadtree = buildQuadTree_(polylineImpl); + + Line queryline = new Line(34, 9, 66, 46); + QuadTree.QuadTreeIterator qtIter = quadtree.getIterator(); + assertTrue(qtIter.next() == -1); + + qtIter.resetIterator(queryline, 0.0); + + int element_handle = qtIter.next(); + while (element_handle > 0) { + int index = quadtree.getElement(element_handle); + assertTrue(index == 6 || index == 8 || index == 14); + element_handle = qtIter.next(); + } + } + + @Test + public static void test2() { + MultiPoint multipoint = new MultiPoint(); + + for (int i = 0; i < 100; i++) { + for (int j = 0; j < 100; j++) { + multipoint.add(i, j); + } + } + + Envelope2D extent = new Envelope2D(); + multipoint.queryEnvelope2D(extent); + + MultiPointImpl multipointImpl = (MultiPointImpl) multipoint._getImpl(); + QuadTree quadtree = buildQuadTree_(multipointImpl); + + QuadTree.QuadTreeIterator qtIter = quadtree.getIterator(); + assertTrue(qtIter.next() == -1); + + int count = 0; + qtIter.resetIterator(extent, 0.0); + + while (qtIter.next() != -1) { + count++; + } + + assertTrue(count == 10000); + } + + public static Polyline makePolyline() { + Polyline poly = new Polyline(); + + // 0 + poly.startPath(0, 40); + poly.lineTo(30, 0); + + // 1 + poly.startPath(20, 70); + poly.lineTo(45, 100); + + // 2 + poly.startPath(50, 100); + poly.lineTo(50, 60); + + // 3 + poly.startPath(35, 25); + poly.lineTo(65, 45); + + // 4 + poly.startPath(60, 10); + poly.lineTo(65, 35); + + // 5 + poly.startPath(60, 60); + poly.lineTo(100, 60); + + // 6 + poly.startPath(80, 10); + poly.lineTo(80, 99); + + // 7 + poly.startPath(60, 60); + poly.lineTo(65, 35); + + return poly; + } + + static QuadTree buildQuadTree_(MultiPathImpl multipathImpl) { + Envelope2D extent = new Envelope2D(); + multipathImpl.queryEnvelope2D(extent); + QuadTree quadTree = new QuadTree(extent, 8); + int hint_index = -1; + Envelope2D boundingbox = new Envelope2D(); + SegmentIteratorImpl seg_iter = multipathImpl.querySegmentIterator(); + while (seg_iter.nextPath()) { + while (seg_iter.hasNextSegment()) { + Segment segment = seg_iter.nextSegment(); + int index = seg_iter.getStartPointIndex(); + segment.queryEnvelope2D(boundingbox); + hint_index = quadTree.insert(index, boundingbox, hint_index); + } + } + + return quadTree; + } + + static QuadTree buildQuadTree_(MultiPointImpl multipointImpl) { + Envelope2D extent = new Envelope2D(); + multipointImpl.queryEnvelope2D(extent); + QuadTree quadTree = new QuadTree(extent, 8); + Envelope2D boundingbox = new Envelope2D(); + Point2D pt; + + for (int i = 0; i < multipointImpl.getPointCount(); i++) { + pt = multipointImpl.getXY(i); + boundingbox.setCoords(pt.x, pt.y, pt.x, pt.y); + quadTree.insert(i, boundingbox, -1); + } + + return quadTree; + } +} diff --git a/unittest/com/esri/core/geometry/TestRelation.java b/unittest/com/esri/core/geometry/TestRelation.java new file mode 100644 index 00000000..73ee533e --- /dev/null +++ b/unittest/com/esri/core/geometry/TestRelation.java @@ -0,0 +1,4700 @@ +package com.esri.core.geometry; + +import junit.framework.TestCase; +import org.codehaus.jackson.JsonFactory; +import org.codehaus.jackson.JsonParser; +import org.junit.Test; + +public class TestRelation extends TestCase { + @Override + protected void setUp() throws Exception { + super.setUp(); + } + + @Override + protected void tearDown() throws Exception { + super.tearDown(); + } + + @Test + public static void testCreation() { + { + OperatorFactoryLocal projEnv = OperatorFactoryLocal.getInstance(); + SpatialReference inputSR = SpatialReference.create(3857); + + Polygon poly1 = new Polygon(); + Envelope2D env1 = new Envelope2D(); + env1.setCoords(855277, 3892059, 855277 + 100, 3892059 + 100); + poly1.addEnvelope(env1, false); + + Polygon poly2 = new Polygon(); + Envelope2D env2 = new Envelope2D(); + env2.setCoords(855277, 3892059, 855277 + 300, 3892059 + 200); + poly2.addEnvelope(env2, false); + + { + OperatorEquals operatorEquals = (OperatorEquals) (projEnv + .getOperator(Operator.Type.Equals)); + boolean result = operatorEquals.execute(poly1, poly2, inputSR, + null); + assertTrue(!result); + Polygon poly11 = new Polygon(); + poly1.copyTo(poly11); + result = operatorEquals.execute(poly1, poly11, inputSR, null); + assertTrue(result); + } + { + OperatorCrosses operatorCrosses = (OperatorCrosses) (projEnv + .getOperator(Operator.Type.Crosses)); + boolean result = operatorCrosses.execute(poly1, poly2, inputSR, + null); + assertTrue(!result); + } + { + OperatorWithin operatorWithin = (OperatorWithin) (projEnv + .getOperator(Operator.Type.Within)); + boolean result = operatorWithin.execute(poly1, poly2, inputSR, + null); + assertTrue(result); + } + + { + OperatorDisjoint operatorDisjoint = (OperatorDisjoint) (projEnv + .getOperator(Operator.Type.Disjoint)); + OperatorIntersects operatorIntersects = (OperatorIntersects) (projEnv + .getOperator(Operator.Type.Intersects)); + boolean result = operatorDisjoint.execute(poly1, poly2, + inputSR, null); + assertTrue(!result); + { + result = operatorIntersects.execute(poly1, poly2, inputSR, + null); + assertTrue(result); + } + } + + { + OperatorDisjoint operatorDisjoint = (OperatorDisjoint) (projEnv + .getOperator(Operator.Type.Disjoint)); + OperatorIntersects operatorIntersects = (OperatorIntersects) (projEnv + .getOperator(Operator.Type.Intersects)); + Envelope2D env2D = new Envelope2D(); + poly2.queryEnvelope2D(env2D); + Envelope envelope = new Envelope(env2D); + boolean result = operatorDisjoint.execute(envelope, poly2, + inputSR, null); + assertTrue(!result); + { + result = operatorIntersects.execute(envelope, poly2, + inputSR, null); + assertTrue(result); + } + } + + { + OperatorDisjoint operatorDisjoint = (OperatorDisjoint) (projEnv + .getOperator(Operator.Type.Disjoint)); + OperatorIntersects operatorIntersects = (OperatorIntersects) (projEnv + .getOperator(Operator.Type.Intersects)); + Polygon poly = new Polygon(); + + Envelope2D env2D = new Envelope2D(); + env2D.setCoords(855277, 3892059, 855277 + 100, 3892059 + 100); + poly.addEnvelope(env2D, false); + env2D.setCoords(855277 + 10, 3892059 + 10, 855277 + 90, + 3892059 + 90); + poly.addEnvelope(env2D, true); + + env2D.setCoords(855277 + 20, 3892059 + 20, 855277 + 200, + 3892059 + 80); + Envelope envelope = new Envelope(env2D); + boolean result = operatorDisjoint.execute(envelope, poly, + inputSR, null); + assertTrue(!result); + { + result = operatorIntersects.execute(envelope, poly, + inputSR, null); + assertTrue(result); + } + } + + { + OperatorTouches operatorTouches = (OperatorTouches) (projEnv + .getOperator(Operator.Type.Touches)); + boolean result = operatorTouches.execute(poly1, poly2, inputSR, + null); + assertTrue(!result); + } + + } + } + + @Test + public static void testOperatorDisjoint() { + { + OperatorFactoryLocal projEnv = OperatorFactoryLocal.getInstance(); + SpatialReference inputSR = SpatialReference.create(3857); + + Polygon poly1 = new Polygon(); + Envelope2D env1 = new Envelope2D(); + env1.setCoords(855277, 3892059, 855277 + 100, 3892059 + 100); + poly1.addEnvelope(env1, false); + + Polygon poly2 = new Polygon(); + Envelope2D env2 = new Envelope2D(); + env2.setCoords(855277, 3892059, 855277 + 300, 3892059 + 200); + poly2.addEnvelope(env2, false); + + Polygon poly3 = new Polygon(); + Envelope2D env3 = new Envelope2D(); + env3.setCoords(855277 + 100, 3892059 + 100, 855277 + 100 + 100, + 3892059 + 100 + 100); + poly3.addEnvelope(env3, false); + + Polygon poly4 = new Polygon(); + Envelope2D env4 = new Envelope2D(); + env4.setCoords(855277 + 200, 3892059 + 200, 855277 + 200 + 100, + 3892059 + 200 + 100); + poly4.addEnvelope(env4, false); + + Point point1 = new Point(855277, 3892059); + Point point2 = new Point(855277 + 2, 3892059 + 3); + Point point3 = new Point(855277 - 2, 3892059 - 3); + + { + OperatorDisjoint operatorDisjoint = (OperatorDisjoint) (projEnv + .getOperator(Operator.Type.Disjoint)); + boolean result = operatorDisjoint.execute(poly1, poly2, + inputSR, null); + assertTrue(!result); + result = operatorDisjoint.execute(poly1, poly3, inputSR, null); + assertTrue(!result); + result = operatorDisjoint.execute(poly1, poly4, inputSR, null); + assertTrue(result); + + result = operatorDisjoint.execute(poly1, point1, inputSR, null); + assertTrue(!result); + result = operatorDisjoint.execute(point1, poly1, inputSR, null); + assertTrue(!result); + result = operatorDisjoint.execute(poly1, point2, inputSR, null); + assertTrue(!result); + result = operatorDisjoint.execute(point2, poly1, inputSR, null); + assertTrue(!result); + result = operatorDisjoint.execute(poly1, point3, inputSR, null); + assertTrue(result); + result = operatorDisjoint.execute(point3, poly1, inputSR, null); + assertTrue(result); + } + } + } + + @Test + public static void testTouchPointLineCR183227() {// Tests CR 183227 + OperatorTouches operatorTouches = (OperatorTouches) (OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Touches)); + + Geometry baseGeom = new Point(-130, 10); + Polyline pl = new Polyline(); + // pl.startPath(std::make_shared(-130, 10)); + pl.startPath(-130, 10); + pl.lineTo(-131, 15); + pl.lineTo(-140, 20); + + SpatialReference sr = SpatialReference.create(4326); + + boolean isTouched; + boolean isTouched2; + isTouched = operatorTouches.execute(baseGeom, pl, sr, null); + isTouched2 = operatorTouches.execute(pl, baseGeom, sr, null); + assertTrue(isTouched && isTouched2); + + { + baseGeom = new Point(-131, 15); + isTouched = operatorTouches.execute(baseGeom, pl, sr, null); + isTouched2 = operatorTouches.execute(pl, baseGeom, sr, null); + assertTrue(!isTouched && !isTouched2); + } + } + + @Test + public static void testTouchPointLineClosed() {// Tests CR 183227 + OperatorTouches operatorTouches = (OperatorTouches) (OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Touches)); + + Geometry baseGeom = new Point(-130, 10); + Polyline pl = new Polyline(); + pl.startPath(-130, 10); + pl.lineTo(-131, 15); + pl.lineTo(-140, 20); + pl.lineTo(-130, 10); + + SpatialReference sr = SpatialReference.create(4326); + + boolean isTouched; + boolean isTouched2; + isTouched = operatorTouches.execute(baseGeom, pl, sr, null); + isTouched2 = operatorTouches.execute(pl, baseGeom, sr, null); + assertTrue(!isTouched && !isTouched2);// this may change in future + + { + baseGeom = new Point(-131, 15); + isTouched = operatorTouches.execute(baseGeom, pl, sr, null); + isTouched2 = operatorTouches.execute(pl, baseGeom, sr, null); + assertTrue(!isTouched && !isTouched2); + } + } + + @Test + public static void testTouchPolygonPolygon() { + OperatorTouches operatorTouches = (OperatorTouches) (OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Touches)); + + Polygon pg = new Polygon(); + pg.startPath(-130, 10); + pg.lineTo(-131, 15); + pg.lineTo(-140, 20); + + Polygon pg2 = new Polygon(); + pg2.startPath(-130, 10); + pg2.lineTo(-131, 15); + pg2.lineTo(-120, 20); + SpatialReference sr = SpatialReference.create(4326); + + boolean isTouched; + boolean isTouched2; + isTouched = operatorTouches.execute(pg, pg2, sr, null); + isTouched2 = operatorTouches.execute(pg2, pg, sr, null); + assertTrue(isTouched && isTouched2); + } + + @Test + public static void testContainsFailureCR186456() { + { + OperatorContains op = (OperatorContains) (OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Contains)); + String str = "{\"rings\":[[[406944.399999999,287461.450000001],[406947.750000011,287462.299999997],[406946.44999999,287467.450000001],[406943.050000005,287466.550000005],[406927.799999992,287456.849999994],[406926.949999996,287456.599999995],[406924.800000005,287455.999999998],[406924.300000007,287455.849999999],[406924.200000008,287456.099999997],[406923.450000011,287458.449999987],[406922.999999987,287459.800000008],[406922.29999999,287462.099999998],[406921.949999991,287463.449999992],[406921.449999993,287465.050000011],[406920.749999996,287466.700000004],[406919.800000001,287468.599999996],[406919.050000004,287469.99999999],[406917.800000009,287471.800000008],[406916.04999999,287473.550000001],[406915.449999993,287473.999999999],[406913.700000001,287475.449999993],[406913.300000002,287475.899999991],[406912.050000008,287477.250000011],[406913.450000002,287478.150000007],[406915.199999994,287478.650000005],[406915.999999991,287478.800000005],[406918.300000007,287479.200000003],[406920.649999997,287479.450000002],[406923.100000013,287479.550000001],[406925.750000001,287479.450000002],[406928.39999999,287479.150000003],[406929.80000001,287478.950000004],[406932.449999998,287478.350000006],[406935.099999987,287477.60000001],[406938.699999998,287476.349999989],[406939.649999994,287473.949999999],[406939.799999993,287473.949999999],[406941.249999987,287473.75],[406942.700000007,287473.250000002],[406943.100000005,287473.100000003],[406943.950000001,287472.750000004],[406944.799999998,287472.300000006],[406944.999999997,287472.200000007],[406946.099999992,287471.200000011],[406946.299999991,287470.950000012],[406948.00000001,287468.599999996],[406948.10000001,287468.399999997],[406950.100000001,287465.050000011],[406951.949999993,287461.450000001],[406952.049999993,287461.300000001],[406952.69999999,287459.900000007],[406953.249999987,287458.549999987],[406953.349999987,287458.299999988],[406953.650000012,287457.299999992],[406953.900000011,287456.349999996],[406954.00000001,287455.300000001],[406954.00000001,287454.750000003],[406953.850000011,287453.750000008],[406953.550000012,287452.900000011],[406953.299999987,287452.299999988],[406954.500000008,287450.299999996],[406954.00000001,287449.000000002],[406953.399999987,287447.950000006],[406953.199999988,287447.550000008],[406952.69999999,287446.850000011],[406952.149999992,287446.099999988],[406951.499999995,287445.499999991],[406951.149999996,287445.249999992],[406950.449999999,287444.849999994],[406949.600000003,287444.599999995],[406949.350000004,287444.549999995],[406948.250000009,287444.499999995],[406947.149999987,287444.699999994],[406946.849999989,287444.749999994],[406945.899999993,287444.949999993],[406944.999999997,287445.349999991],[406944.499999999,287445.64999999],[406943.650000003,287446.349999987],[406942.900000006,287447.10000001],[406942.500000008,287447.800000007],[406942.00000001,287448.700000003],[406941.600000011,287449.599999999],[406941.350000013,287450.849999994],[406941.350000013,287451.84999999],[406941.450000012,287452.850000012],[406941.750000011,287453.850000007],[406941.800000011,287454.000000007],[406942.150000009,287454.850000003],[406942.650000007,287455.6],[406943.150000005,287456.299999997],[406944.499999999,287457.299999992],[406944.899999997,287457.599999991],[406945.299999995,287457.949999989],[406944.399999999,287461.450000001],[406941.750000011,287461.999999998],[406944.399999999,287461.450000001]],[[406944.399999999,287461.450000001],[406947.750000011,287462.299999997],[406946.44999999,287467.450000001],[406943.050000005,287466.550000005],[406927.799999992,287456.849999994],[406944.399999999,287461.450000001]]]}"; + MapGeometry mg = importFromJson(str); + boolean res = op.execute((mg.getGeometry()), (mg.getGeometry()), + null, null); + assertTrue(res); + } + } + + @Test + public static void testWithin() { + { + OperatorWithin op = (OperatorWithin) (OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Within)); + String str1 = "{\"rings\":[[[0,0],[0,200],[200,200],[200,0],[0,0],[0,0],[0,0]]]}"; + MapGeometry mg1 = importFromJson(str1); + String str2 = "{\"x\":100,\"y\":100}"; + MapGeometry mg2 = importFromJson(str2); + + boolean res = op.execute((mg2.getGeometry()), (mg1.getGeometry()), + null, null); + assertTrue(res); + res = op.execute((mg1.getGeometry()), (mg2.getGeometry()), null, + null); + assertTrue(!res); + + } + + {// polygon + OperatorWithin op = (OperatorWithin) (OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Within)); + String str1 = "{\"rings\":[[[0,0],[0,200],[200,200],[200,0],[100,0]]]}"; + MapGeometry mg1 = importFromJson(str1); + String str2 = "{\"rings\":[[[10,10],[10,100],[100,100],[100,10]]]}"; + MapGeometry mg2 = importFromJson(str2); + + boolean res = op.execute((mg2.getGeometry()), (mg1.getGeometry()), + null, null); + assertTrue(res); + res = op.execute((mg1.getGeometry()), (mg2.getGeometry()), null, + null); + assertTrue(!res); + } + + {// Multi_point + OperatorWithin op = (OperatorWithin) (OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Within)); + String str1 = "{\"points\":[[0,0],[0,200],[200,200]]}"; + MapGeometry mg1 = importFromJson(str1); + String str2 = "{\"points\":[[0,0],[0,200]]}"; + MapGeometry mg2 = importFromJson(str2); + boolean res = op.execute((mg2.getGeometry()), (mg1.getGeometry()), + null, null); + assertTrue(res); + res = op.execute((mg1.getGeometry()), (mg2.getGeometry()), null, + null); + assertTrue(!res); + } + + {// Multi_point + OperatorWithin op = (OperatorWithin) (OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Within)); + String str1 = "{\"points\":[[0,0],[0,200]]}"; + MapGeometry mg1 = importFromJson(str1); + String str2 = "{\"points\":[[0,0],[0,200]]}"; + MapGeometry mg2 = importFromJson(str2); + boolean res = op.execute((mg2.getGeometry()), (mg1.getGeometry()), + null, null); + assertTrue(res); + res = op.execute((mg1.getGeometry()), (mg2.getGeometry()), null, + null); + assertTrue(res); + } + + {// Multi_point + OperatorWithin op = (OperatorWithin) (OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Within)); + String str1 = "{\"points\":[[0,0],[0,200],[200,200]]}"; + MapGeometry mg1 = importFromJson(str1); + String str2 = "{\"points\":[[0,0],[0,200], [1, 1]]}"; + MapGeometry mg2 = importFromJson(str2); + boolean res = op.execute((mg2.getGeometry()), (mg1.getGeometry()), + null, null); + assertTrue(!res); + res = op.execute((mg1.getGeometry()), (mg2.getGeometry()), null, + null); + assertTrue(!res); + } + } + + @Test + public static void testContains() { + { + OperatorContains op = (OperatorContains) (OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Contains)); + String str1 = "{\"rings\":[[[0,0],[0,200],[200,200],[200,0],[0,0],[0,0],[0,0]]]}"; + MapGeometry mg1 = importFromJson(str1); + String str2 = "{\"x\":100,\"y\":100}"; + MapGeometry mg2 = importFromJson(str2); + + boolean res = op.execute((mg2.getGeometry()), (mg1.getGeometry()), + null, null); + assertTrue(!res); + res = op.execute((mg1.getGeometry()), (mg2.getGeometry()), null, + null); + assertTrue(res); + } + + {// polygon + OperatorContains op = (OperatorContains) (OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Contains)); + String str1 = "{\"rings\":[[[0,0],[0,200],[200,200],[200,0],[0,0]]]}"; + MapGeometry mg1 = importFromJson(str1); + String str2 = "{\"rings\":[[[10,10],[10,100],[100,100],[10,10]]]}"; + MapGeometry mg2 = importFromJson(str2); + + boolean res = op.execute((mg2.getGeometry()), (mg1.getGeometry()), + null, null); + assertTrue(!res); + res = op.execute((mg1.getGeometry()), (mg2.getGeometry()), null, + null); + assertTrue(res); + } + + {// Multi_point + OperatorContains op = (OperatorContains) (OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Contains)); + String str1 = "{\"points\":[[0,0],[0,200],[200,200]]}"; + MapGeometry mg1 = importFromJson(str1); + String str2 = "{\"points\":[[0,0],[0,200]]}"; + MapGeometry mg2 = importFromJson(str2); + boolean res = op.execute((mg2.getGeometry()), (mg1.getGeometry()), + null, null); + assertTrue(!res); + res = op.execute((mg1.getGeometry()), (mg2.getGeometry()), null, + null); + assertTrue(res); + } + + {// Multi_point + OperatorContains op = (OperatorContains) (OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Contains)); + String str1 = "{\"points\":[[0,0],[0,200]]}"; + MapGeometry mg1 = importFromJson(str1); + String str2 = "{\"points\":[[0,0],[0,200]]}"; + MapGeometry mg2 = importFromJson(str2); + boolean res = op.execute((mg2.getGeometry()), (mg1.getGeometry()), + null, null); + assertTrue(res); + res = op.execute((mg1.getGeometry()), (mg2.getGeometry()), null, + null); + assertTrue(res); + } + + {// Multi_point + OperatorContains op = (OperatorContains) (OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Contains)); + String str1 = "{\"points\":[[0,0],[0,200],[200,200]]}"; + MapGeometry mg1 = importFromJson(str1); + String str2 = "{\"points\":[[0,0],[0,200], [1, 1]]}"; + MapGeometry mg2 = importFromJson(str2); + boolean res = op.execute((mg2.getGeometry()), (mg1.getGeometry()), + null, null); + assertTrue(!res); + res = op.execute((mg1.getGeometry()), (mg2.getGeometry()), null, + null); + assertTrue(!res); + } + } + + @Test + public static void testOverlaps() { + {// empty polygon + OperatorOverlaps op = (OperatorOverlaps) (OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Overlaps)); + Polygon poly1 = new Polygon(); + Polygon poly2 = new Polygon(); + + boolean res = op.execute(poly1, poly2, null, null); + assertTrue(!res); + res = op.execute(poly1, poly2, null, null); + assertTrue(!res); + } + {// polygon + OperatorOverlaps op = (OperatorOverlaps) (OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Overlaps)); + String str1 = "{\"rings\":[[[0,0],[0,200],[200,200],[200,0],[0,0],[0,0],[0,0]]]}"; + MapGeometry mg1 = importFromJson(str1); + String str2 = "{\"x\":100,\"y\":100}"; + MapGeometry mg2 = importFromJson(str2); + + boolean res = op.execute((mg2.getGeometry()), (mg1.getGeometry()), + null, null); + assertTrue(!res); + res = op.execute((mg1.getGeometry()), (mg2.getGeometry()), null, + null); + assertTrue(!res); + } + {// polygon + OperatorOverlaps op = (OperatorOverlaps) (OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Overlaps)); + String str1 = "{\"rings\":[[[0,0],[0,200],[200,200],[200,0],[0,0]]]}"; + MapGeometry mg1 = importFromJson(str1); + MapGeometry mg2 = importFromJson(str1); + Transformation2D trans = new Transformation2D(); + trans.setShift(300, 0); + mg2.getGeometry().applyTransformation(trans); + boolean res = op.execute((mg2.getGeometry()), (mg1.getGeometry()), + null, null); + assertTrue(!res); + res = op.execute((mg1.getGeometry()), (mg2.getGeometry()), null, + null); + assertTrue(!res); + } + {// polygon + OperatorOverlaps op = (OperatorOverlaps) (OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Overlaps)); + String str1 = "{\"rings\":[[[0,0],[0,200],[200,200],[200,0],[0,0]]]}"; + MapGeometry mg1 = importFromJson(str1); + MapGeometry mg2 = importFromJson(str1); + Transformation2D trans = new Transformation2D(); + trans.setShift(30, 0); + mg2.getGeometry().applyTransformation(trans); + boolean res = op.execute((mg2.getGeometry()), (mg1.getGeometry()), + null, null); + assertTrue(res); + res = op.execute((mg1.getGeometry()), (mg2.getGeometry()), null, + null); + assertTrue(res); + } + {// polygon + OperatorOverlaps op = (OperatorOverlaps) (OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Overlaps)); + String str1 = "{\"rings\":[[[0,0],[0,200],[200,200],[200,0],[0,0]]]}"; + MapGeometry mg1 = importFromJson(str1); + MapGeometry mg2 = importFromJson(str1); + Transformation2D trans = new Transformation2D(); + trans.setShift(0, 0); + mg2.getGeometry().applyTransformation(trans); + boolean res = op.execute((mg2.getGeometry()), (mg1.getGeometry()), + null, null); + assertTrue(!res); + res = op.execute((mg1.getGeometry()), (mg2.getGeometry()), null, + null); + assertTrue(!res); + } + + {// polyline + OperatorOverlaps op = (OperatorOverlaps) (OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Overlaps)); + String str1 = "{\"paths\":[[[0,0],[100,0],[200,0]]]}"; + MapGeometry mg1 = importFromJson(str1); + MapGeometry mg2 = importFromJson(str1); + Transformation2D trans = new Transformation2D(); + trans.setShift(0, 0); + mg2.getGeometry().applyTransformation(trans); + boolean res = op.execute((mg2.getGeometry()), (mg1.getGeometry()), + null, null); + assertTrue(!res); + res = op.execute((mg1.getGeometry()), (mg2.getGeometry()), null, + null); + assertTrue(!res); + } + + {// polyline + OperatorOverlaps op = (OperatorOverlaps) (OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Overlaps)); + String str1 = "{\"paths\":[[[0,0],[100,0],[200,0]]]}"; + MapGeometry mg1 = importFromJson(str1); + MapGeometry mg2 = importFromJson(str1); + Transformation2D trans = new Transformation2D(); + trans.setShift(10, 0); + mg2.getGeometry().applyTransformation(trans); + boolean res = op.execute((mg2.getGeometry()), (mg1.getGeometry()), + null, null); + assertTrue(res); + res = op.execute((mg1.getGeometry()), (mg2.getGeometry()), null, + null); + assertTrue(res); + } + + {// polyline + OperatorOverlaps op = (OperatorOverlaps) (OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Overlaps)); + String str1 = "{\"paths\":[[[0,0],[100,0],[200,0]]]}"; + MapGeometry mg1 = importFromJson(str1); + MapGeometry mg2 = importFromJson(str1); + Transformation2D trans = new Transformation2D(); + trans.setShift(200, 0); + mg2.getGeometry().applyTransformation(trans); + boolean res = op.execute((mg2.getGeometry()), (mg1.getGeometry()), + null, null); + assertTrue(!res); + res = op.execute((mg1.getGeometry()), (mg2.getGeometry()), null, + null); + assertTrue(!res); + } + + {// Multi_point + OperatorOverlaps op = (OperatorOverlaps) (OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Overlaps)); + String str1 = "{\"points\":[[0,0],[0,200],[200,200],[200,0]]}"; + MapGeometry mg1 = importFromJson(str1); + MapGeometry mg2 = importFromJson(str1); + Transformation2D trans = new Transformation2D(); + trans.setShift(0, 0); + mg2.getGeometry().applyTransformation(trans); + boolean res = op.execute((mg2.getGeometry()), (mg1.getGeometry()), + null, null); + assertTrue(!res); + res = op.execute((mg1.getGeometry()), (mg2.getGeometry()), null, + null); + assertTrue(!res); + } + {// Multi_point + OperatorOverlaps op = (OperatorOverlaps) (OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Overlaps)); + String str1 = "{\"points\":[[0,0],[0,200],[200,200]]}"; + MapGeometry mg1 = importFromJson(str1); + String str2 = "{\"points\":[[0,0],[0,200]]}"; + MapGeometry mg2 = importFromJson(str2); + boolean res = op.execute((mg2.getGeometry()), (mg1.getGeometry()), + null, null); + assertTrue(!res); + res = op.execute((mg1.getGeometry()), (mg2.getGeometry()), null, + null); + assertTrue(!res); + } + {// Multi_point + OperatorOverlaps op = (OperatorOverlaps) (OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Overlaps)); + String str1 = "{\"points\":[[0,0],[0,200],[200,200]]}"; + MapGeometry mg1 = importFromJson(str1); + String str2 = "{\"points\":[[0,0],[0,200], [0,2]]}"; + MapGeometry mg2 = importFromJson(str2); + boolean res = op.execute((mg2.getGeometry()), (mg1.getGeometry()), + null, null); + assertTrue(res); + res = op.execute((mg1.getGeometry()), (mg2.getGeometry()), null, + null); + assertTrue(res); + } + } + + @Test + public static void testPolygonPolygonEquals() { + OperatorEquals equals = (OperatorEquals) (OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Equals)); + SpatialReference sr = SpatialReference.create(102100); + @SuppressWarnings("unused") + double tolerance = sr + .getTolerance(VertexDescription.Semantics.POSITION); + + // Polygon1 and Polygon2 are topologically equal, but have differing + // number of vertices + String str1 = "{\"rings\":[[[0,0],[0,5],[0,10],[0,5],[0,7],[0,10],[10,10],[10,0],[0,0]],[[1,1],[9,1],[9,9],[1,9],[1,1],[1,1]]]}"; + String str2 = "{\"rings\":[[[0,10],[10,10],[10,0],[0,0],[0,10]],[[9,1],[9,6],[9,9],[1,9],[1,1],[1,1],[9,1]]]}"; + + Polygon polygon1 = (Polygon) importFromJson(str1).getGeometry(); + Polygon polygon2 = (Polygon) importFromJson(str2).getGeometry(); + // wiggleGeometry(polygon1, tolerance, 1982); + // wiggleGeometry(polygon2, tolerance, 511); + + boolean res = equals.execute(polygon1, polygon2, sr, null); + assertTrue(res); + equals.execute(polygon2, polygon1, sr, null); + assertTrue(res); + + // The outer rings of Polygon1 and Polygon2 are equal, but Polygon1 has + // a hole. + str1 = "{\"rings\":[[[0,0],[0,5],[0,10],[10,10],[10,0],[0,0]],[[1,1],[9,1],[9,9],[1,9],[1,1],[1,1]]]}"; + str2 = "{\"rings\":[[[0,10],[10,10],[5,10],[10,10],[10,0],[0,0],[0,10]]]}"; + polygon1 = (Polygon) importFromJson(str1).getGeometry(); + polygon2 = (Polygon) importFromJson(str2).getGeometry(); + + res = equals.execute(polygon1, polygon2, sr, null); + assertTrue(!res); + res = equals.execute(polygon2, polygon1, sr, null); + assertTrue(!res); + + // The rings are equal but rotated + str1 = "{\"rings\":[[[0,0],[0,10],[10,10],[10,0],[0,0]]]}"; + str2 = "{\"rings\":[[[0,10],[10,10],[10,0],[0,0],[0,10]]]}"; + + polygon1 = (Polygon) importFromJson(str1).getGeometry(); + polygon2 = (Polygon) importFromJson(str2).getGeometry(); + + res = equals.execute(polygon1, polygon2, sr, null); + assertTrue(res); + res = equals.execute(polygon2, polygon1, sr, null); + assertTrue(res); + + // The rings are equal but opposite orientation + str1 = "{\"rings\":[[[0,0],[0,10],[10,10],[10,0],[0,0]]]}"; + str2 = "{\"rings\":[[[0,0],[10,0],[10,10],[0,10],[0,0]]]}"; + + polygon1 = (Polygon) importFromJson(str1).getGeometry(); + polygon2 = (Polygon) importFromJson(str2).getGeometry(); + + res = equals.execute(polygon1, polygon2, sr, null); + assertTrue(res); + res = equals.execute(polygon2, polygon1, sr, null); + assertTrue(res); + } + + @Test + public static void testMultiPointMultiPointEquals() { + OperatorEquals equals = (OperatorEquals) OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Equals); + SpatialReference sr = SpatialReference.create(102100); + @SuppressWarnings("unused") + double tolerance = sr + .getTolerance(VertexDescription.Semantics.POSITION); + + MultiPoint multipoint1 = new MultiPoint(); + MultiPoint multipoint2 = new MultiPoint(); + + multipoint1.add(0, 0); + multipoint1.add(1, 1); + multipoint1.add(2, 2); + multipoint1.add(3, 3); + multipoint1.add(4, 4); + multipoint1.add(1, 1); + multipoint1.add(0, 0); + + multipoint2.add(4, 4); + multipoint2.add(3, 3); + multipoint2.add(2, 2); + multipoint2.add(1, 1); + multipoint2.add(0, 0); + multipoint2.add(2, 2); + + wiggleGeometry(multipoint1, 0.001, 123); + wiggleGeometry(multipoint2, 0.001, 5937); + boolean res = equals.execute(multipoint1, multipoint2, sr, null); + assertTrue(res); + res = equals.execute(multipoint2, multipoint1, sr, null); + assertTrue(res); + + multipoint1.add(1, 2); + res = equals.execute(multipoint1, multipoint2, sr, null); + assertTrue(!res); + res = equals.execute(multipoint2, multipoint1, sr, null); + assertTrue(!res); + } + + @Test + public static void testMultiPointPointEquals() { + OperatorEquals equals = (OperatorEquals) (OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Equals)); + OperatorWithin within = (OperatorWithin) (OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Within)); + SpatialReference sr = SpatialReference.create(102100); + @SuppressWarnings("unused") + double tolerance = sr + .getTolerance(VertexDescription.Semantics.POSITION); + + MultiPoint multipoint1 = new MultiPoint(); + Point point2 = new Point(); + + multipoint1.add(2, 2); + multipoint1.add(2, 2); + + point2.setXY(2, 2); + + wiggleGeometry(multipoint1, 0.001, 123); + boolean res = equals.execute(multipoint1, point2, sr, null); + assertTrue(res); + res = equals.execute(point2, multipoint1, sr, null); + assertTrue(res); + + res = within.execute(multipoint1, point2, sr, null); + assertTrue(res); + res = within.execute(point2, multipoint1, sr, null); + assertTrue(res); + + multipoint1.add(4, 4); + res = equals.execute(multipoint1, point2, sr, null); + assertTrue(!res); + res = equals.execute(point2, multipoint1, sr, null); + assertTrue(!res); + + res = within.execute(multipoint1, point2, sr, null); + assertTrue(!res); + res = within.execute(point2, multipoint1, sr, null); + assertTrue(res); + } + + @Test + public static void testPointPointEquals() { + OperatorEquals equals = (OperatorEquals) (OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Equals)); + OperatorWithin within = (OperatorWithin) (OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Within)); + OperatorContains contains = (OperatorContains) (OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Contains)); + OperatorDisjoint disjoint = (OperatorDisjoint) (OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Disjoint)); + SpatialReference sr = SpatialReference.create(102100); + @SuppressWarnings("unused") + double tolerance = sr + .getTolerance(VertexDescription.Semantics.POSITION); + + Point point1 = new Point(); + Point point2 = new Point(); + + point1.setXY(2, 2); + point2.setXY(2, 2); + + boolean res = equals.execute(point1, point2, sr, null); + assertTrue(res); + res = equals.execute(point2, point1, sr, null); + assertTrue(res); + + res = within.execute(point1, point2, sr, null); + assertTrue(res); + res = within.execute(point2, point1, sr, null); + assertTrue(res); + + res = contains.execute(point1, point2, sr, null); + assertTrue(res); + res = contains.execute(point2, point1, sr, null); + assertTrue(res); + + res = disjoint.execute(point1, point2, sr, null); + assertTrue(!res); + res = disjoint.execute(point2, point1, sr, null); + assertTrue(!res); + + point2.setXY(2, 3); + res = equals.execute(point1, point2, sr, null); + assertTrue(!res); + res = equals.execute(point2, point1, sr, null); + assertTrue(!res); + + res = within.execute(point1, point2, sr, null); + assertTrue(!res); + res = within.execute(point2, point1, sr, null); + assertTrue(!res); + + res = contains.execute(point1, point2, sr, null); + assertTrue(!res); + res = contains.execute(point2, point1, sr, null); + assertTrue(!res); + + res = disjoint.execute(point1, point2, sr, null); + assertTrue(res); + res = disjoint.execute(point2, point1, sr, null); + assertTrue(res); + } + + @Test + public static void testPolygonPolygonDisjoint() { + OperatorDisjoint disjoint = (OperatorDisjoint) (OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Disjoint)); + SpatialReference sr = SpatialReference.create(102100); + double tolerance = sr + .getTolerance(VertexDescription.Semantics.POSITION); + + // Polygon1 and Polygon2 are topologically equal, but have differing + // number of vertices + String str1 = "{\"rings\":[[[0,0],[0,5],[0,10],[10,10],[10,0],[0,0]],[[1,1],[9,1],[9,9],[1,9],[1,1],[1,1]]]}"; + String str2 = "{\"rings\":[[[0,10],[10,10],[10,0],[0,0],[0,10]],[[9,1],[9,6],[9,9],[1,9],[1,1],[1,1],[9,1]]]}"; + + Polygon polygon1 = (Polygon) (importFromJson(str1).getGeometry()); + Polygon polygon2 = (Polygon) (importFromJson(str2).getGeometry()); + + boolean res = disjoint.execute(polygon1, polygon2, sr, null); + assertTrue(!res); + res = disjoint.execute(polygon2, polygon1, sr, null); + assertTrue(!res); + + // Polygon1 and Polygon2 touch at a point + str1 = "{\"rings\":[[[0,0],[0,5],[0,10],[10,10],[10,0],[0,0]],[[1,1],[9,1],[9,9],[1,9],[1,1],[1,1]]]}"; + str2 = "{\"rings\":[[[10,10],[10,15],[15,15],[15,10],[10,10]]]}"; + + polygon1 = (Polygon) (importFromJson(str1).getGeometry()); + polygon2 = (Polygon) (importFromJson(str2).getGeometry()); + wiggleGeometry(polygon1, tolerance, 1982); + wiggleGeometry(polygon2, tolerance, 511); + + res = disjoint.execute(polygon1, polygon2, sr, null); + assertTrue(!res); + res = disjoint.execute(polygon2, polygon1, sr, null); + assertTrue(!res); + + // Polygon1 and Polygon2 touch along the boundary + str1 = "{\"rings\":[[[0,0],[0,5],[0,10],[10,10],[10,0],[0,0]],[[1,1],[9,1],[9,9],[1,9],[1,1],[1,1]]]}"; + str2 = "{\"rings\":[[[10,0],[10,10],[15,10],[15,0],[10,0]]]}"; + + polygon1 = (Polygon) (importFromJson(str1).getGeometry()); + polygon2 = (Polygon) (importFromJson(str2).getGeometry()); + wiggleGeometry(polygon1, tolerance, 1982); + wiggleGeometry(polygon2, tolerance, 511); + + res = disjoint.execute(polygon1, polygon2, sr, null); + assertTrue(!res); + res = disjoint.execute(polygon2, polygon1, sr, null); + assertTrue(!res); + + // Polygon2 is inside of the hole of polygon1 + str1 = "{\"rings\":[[[0,0],[0,5],[0,10],[10,10],[10,0],[0,0]],[[1,1],[9,1],[9,9],[1,9],[1,1],[1,1]]]}"; + str2 = "{\"rings\":[[[2,2],[2,8],[8,8],[8,2],[2,2]]]}"; + + polygon1 = (Polygon) (importFromJson(str1).getGeometry()); + polygon2 = (Polygon) (importFromJson(str2).getGeometry()); + + res = disjoint.execute(polygon1, polygon2, sr, null); + assertTrue(res); + res = disjoint.execute(polygon2, polygon1, sr, null); + assertTrue(res); + + // Polygon2 is inside of the hole of polygon1 + str1 = "{\"rings\":[[[0,0],[0,5],[5,5],[5,0]],[[10,0],[10,10],[20,10],[20,0]]]}"; + str2 = "{\"rings\":[[[0,-10],[0,-5],[5,-5],[5,-10]],[[11,1],[11,9],[19,9],[19,1]]]}"; + + polygon1 = (Polygon) (importFromJson(str1).getGeometry()); + polygon2 = (Polygon) (importFromJson(str2).getGeometry()); + + res = disjoint.execute(polygon1, polygon2, sr, null); + assertTrue(!res); + res = disjoint.execute(polygon2, polygon1, sr, null); + assertTrue(!res); + } + + @Test + public static void testPolylinePolylineDisjoint() { + OperatorDisjoint disjoint = (OperatorDisjoint) (OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Disjoint)); + SpatialReference sr = SpatialReference.create(102100); + double tolerance = sr + .getTolerance(VertexDescription.Semantics.POSITION); + + // Polyline1 and Polyline2 touch at a point + String str1 = "{\"paths\":[[[0,0],[0,5],[0,10],[10,10],[10,0],[0,0]],[[1,1],[9,1],[9,9],[1,9],[1,1],[1,1]]]}"; + String str2 = "{\"paths\":[[[10,10],[10,15],[15,15],[15,10],[10,10]]]}"; + + Polyline polyline1 = (Polyline) (importFromJson(str1).getGeometry()); + Polyline polyline2 = (Polyline) (importFromJson(str2).getGeometry()); + wiggleGeometry(polyline1, tolerance, 1982); + wiggleGeometry(polyline2, tolerance, 511); + + boolean res = disjoint.execute(polyline1, polyline2, sr, null); + assertTrue(!res); + res = disjoint.execute(polyline2, polyline1, sr, null); + assertTrue(!res); + + // Polyline1 and Polyline2 touch along the boundary + str1 = "{\"paths\":[[[0,0],[0,5],[0,10],[10,10],[10,0],[0,0]],[[1,1],[9,1],[9,9],[1,9],[1,1],[1,1]]]}"; + str2 = "{\"paths\":[[[10,0],[10,10],[15,10],[15,0],[10,0]]]}"; + + polyline1 = (Polyline) (importFromJson(str1).getGeometry()); + polyline2 = (Polyline) (importFromJson(str2).getGeometry()); + wiggleGeometry(polyline1, tolerance, 1982); + wiggleGeometry(polyline2, tolerance, 511); + + res = disjoint.execute(polyline1, polyline2, sr, null); + assertTrue(!res); + res = disjoint.execute(polyline2, polyline1, sr, null); + assertTrue(!res); + + // Polyline2 does not intersect with Polyline1 + str1 = "{\"paths\":[[[0,0],[0,5],[0,10],[10,10],[10,0],[0,0]],[[1,1],[9,1],[9,9],[1,9],[1,1],[1,1]]]}"; + str2 = "{\"paths\":[[[2,2],[2,8],[8,8],[8,2],[2,2]]]}"; + + polyline1 = (Polyline) (importFromJson(str1).getGeometry()); + polyline2 = (Polyline) (importFromJson(str2).getGeometry()); + + res = disjoint.execute(polyline1, polyline2, sr, null); + assertTrue(res); + res = disjoint.execute(polyline2, polyline1, sr, null); + assertTrue(res); + } + + @Test + public static void testPolygonPolylineDisjoint() { + OperatorDisjoint disjoint = (OperatorDisjoint) (OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Disjoint)); + SpatialReference sr = SpatialReference.create(102100); + @SuppressWarnings("unused") + double tolerance = sr + .getTolerance(VertexDescription.Semantics.POSITION); + + Polygon polygon1 = new Polygon(); + Polyline polyline2 = new Polyline(); + + polygon1.startPath(0, 0); + polygon1.lineTo(0, 10); + polygon1.lineTo(10, 10); + polygon1.lineTo(10, 0); + + polygon1.startPath(1, 1); + polygon1.lineTo(9, 1); + polygon1.lineTo(9, 9); + polygon1.lineTo(1, 9); + + polyline2.startPath(3, 3); + polyline2.lineTo(6, 6); + + boolean res = disjoint.execute(polyline2, polygon1, sr, null); + assertTrue(res); + res = disjoint.execute(polygon1, polyline2, sr, null); + assertTrue(res); + + polyline2.startPath(0, 0); + polyline2.lineTo(0, 5); + + res = disjoint.execute(polyline2, polygon1, sr, null); + assertTrue(!res); + res = disjoint.execute(polygon1, polyline2, sr, null); + assertTrue(!res); + + polygon1.setEmpty(); + polyline2.setEmpty(); + + polygon1.startPath(0, 0); + polygon1.lineTo(0, 10); + polygon1.lineTo(10, 10); + polygon1.lineTo(10, 0); + + polyline2.startPath(2, 2); + polyline2.lineTo(4, 4); + + OperatorFactoryLocal factory = OperatorFactoryLocal.getInstance(); + OperatorSimplify simplify_op = (OperatorSimplify) factory + .getOperator(Operator.Type.Simplify); + simplify_op.isSimpleAsFeature(polygon1, sr, null); + simplify_op.isSimpleAsFeature(polyline2, sr, null); + + res = disjoint.execute(polyline2, polygon1, sr, null); + assertTrue(!res); + res = disjoint.execute(polygon1, polyline2, sr, null); + assertTrue(!res); + } + + @Test + public static void testPolylineMultiPointDisjoint() { + OperatorDisjoint disjoint = (OperatorDisjoint) (OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Disjoint)); + SpatialReference sr = SpatialReference.create(102100); + @SuppressWarnings("unused") + double tolerance = sr + .getTolerance(VertexDescription.Semantics.POSITION); + + Polyline polyline1 = new Polyline(); + MultiPoint multipoint2 = new MultiPoint(); + + polyline1.startPath(0, 0); + polyline1.lineTo(2, 0); + polyline1.lineTo(4, 2); + + multipoint2.add(1, 1); + multipoint2.add(2, 2); + multipoint2.add(3, 0); + + boolean res = disjoint.execute(polyline1, multipoint2, sr, null); + assertTrue(res); + res = disjoint.execute(multipoint2, polyline1, sr, null); + assertTrue(res); + + multipoint2.add(3, 1); + res = disjoint.execute(polyline1, multipoint2, sr, null); + assertTrue(!res); + res = disjoint.execute(multipoint2, polyline1, sr, null); + assertTrue(!res); + + polyline1.startPath(1, -4); + polyline1.lineTo(1, -3); + polyline1.lineTo(1, -2); + polyline1.lineTo(1, -1); + polyline1.lineTo(1, 0); + polyline1.lineTo(1, 1); + + res = disjoint.execute(polyline1, multipoint2, sr, null); + assertTrue(!res); + res = disjoint.execute(multipoint2, polyline1, sr, null); + assertTrue(!res); + } + + @Test + public static void testPolylinePointDisjoint() { + OperatorDisjoint disjoint = (OperatorDisjoint) (OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Disjoint)); + OperatorContains contains = (OperatorContains) (OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Contains)); + SpatialReference sr = SpatialReference.create(102100); + @SuppressWarnings("unused") + double tolerance = sr + .getTolerance(VertexDescription.Semantics.POSITION); + + Polyline polyline1 = new Polyline(); + Point point2 = new Point(); + + polyline1.startPath(0, 0); + polyline1.lineTo(2, 0); + polyline1.lineTo(4, 2); + + point2.setXY(1, 1); + + boolean res = disjoint.execute(polyline1, point2, sr, null); + assertTrue(res); + res = disjoint.execute(point2, polyline1, sr, null); + assertTrue(res); + + res = contains.execute(polyline1, point2, sr, null); + assertTrue(!res); + res = contains.execute(point2, polyline1, sr, null); + assertTrue(!res); + + point2.setXY(4, 2); + + res = disjoint.execute(polyline1, point2, sr, null); + assertTrue(!res); + res = disjoint.execute(point2, polyline1, sr, null); + assertTrue(!res); + + res = contains.execute(polyline1, point2, sr, null); + assertTrue(!res); + res = contains.execute(point2, polyline1, sr, null); + assertTrue(!res); + + polyline1.setEmpty(); + point2.setEmpty(); + + polyline1.startPath(659062.37370000035, 153070.85220000148); + polyline1.lineTo(660916.47940000147, 151481.10269999877); + point2.setXY(659927.85020000115, 152328.77430000156); + + res = contains.execute(polyline1, point2, + SpatialReference.create(54004), null); + assertTrue(res); + } + + @Test + public static void testMultiPointMultiPointDisjoint() { + OperatorDisjoint disjoint = (OperatorDisjoint) (OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Disjoint)); + SpatialReference sr = SpatialReference.create(102100); + @SuppressWarnings("unused") + double tolerance = sr + .getTolerance(VertexDescription.Semantics.POSITION); + + MultiPoint multipoint1 = new MultiPoint(); + MultiPoint multipoint2 = new MultiPoint(); + + multipoint1.add(2, 2); + multipoint1.add(2, 5); + multipoint1.add(4, 1); + multipoint1.add(4, 4); + multipoint1.add(4, 7); + multipoint1.add(6, 2); + multipoint1.add(6, 6); + multipoint1.add(4, 1); + multipoint1.add(6, 6); + + multipoint2.add(0, 1); + multipoint2.add(0, 7); + multipoint2.add(4, 2); + multipoint2.add(4, 6); + multipoint2.add(6, 4); + multipoint2.add(4, 2); + multipoint2.add(0, 1); + + boolean res = disjoint.execute(multipoint1, multipoint2, sr, null); + assertTrue(res); + res = disjoint.execute(multipoint2, multipoint1, sr, null); + assertTrue(res); + + multipoint2.add(2, 2); + res = disjoint.execute(multipoint1, multipoint2, sr, null); + assertTrue(!res); + res = disjoint.execute(multipoint2, multipoint1, sr, null); + assertTrue(!res); + } + + @Test + public static void testMultiPointPointDisjoint() { + OperatorDisjoint disjoint = (OperatorDisjoint) (OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Disjoint)); + OperatorContains contains = (OperatorContains) (OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Contains)); + SpatialReference sr = SpatialReference.create(102100); + @SuppressWarnings("unused") + double tolerance = sr + .getTolerance(VertexDescription.Semantics.POSITION); + + MultiPoint multipoint1 = new MultiPoint(); + Point point2 = new Point(); + + multipoint1.add(2, 2); + multipoint1.add(2, 5); + multipoint1.add(4, 1); + multipoint1.add(4, 4); + multipoint1.add(4, 7); + multipoint1.add(6, 2); + multipoint1.add(6, 6); + multipoint1.add(4, 1); + multipoint1.add(6, 6); + + point2.setXY(2, 6); + + boolean res = disjoint.execute(multipoint1, point2, sr, null); + assertTrue(res); + res = disjoint.execute(point2, multipoint1, sr, null); + assertTrue(res); + + res = contains.execute(multipoint1, point2, sr, null); + assertTrue(!res); + res = contains.execute(point2, multipoint1, sr, null); + assertTrue(!res); + + multipoint1.add(2, 6); + res = disjoint.execute(multipoint1, point2, sr, null); + assertTrue(!res); + res = disjoint.execute(point2, multipoint1, sr, null); + assertTrue(!res); + + res = contains.execute(multipoint1, point2, sr, null); + assertTrue(res); + res = contains.execute(point2, multipoint1, sr, null); + assertTrue(!res); + } + + @Test + public static void testPolygonMultiPointDisjoint() { + OperatorDisjoint disjoint = (OperatorDisjoint) (OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Disjoint)); + SpatialReference sr = SpatialReference.create(102100); + @SuppressWarnings("unused") + double tolerance = sr + .getTolerance(VertexDescription.Semantics.POSITION); + + Polygon polygon1 = new Polygon(); + MultiPoint multipoint2 = new MultiPoint(); + + polygon1.startPath(0, 0); + polygon1.lineTo(0, 10); + polygon1.lineTo(10, 10); + + multipoint2.add(-1, 5); + multipoint2.add(5, 11); + multipoint2.add(11, 5); + multipoint2.add(5, -1); + + boolean res = disjoint.execute(polygon1, multipoint2, sr, null); + assertTrue(res); + res = disjoint.execute(multipoint2, polygon1, sr, null); + assertTrue(res); + + polygon1.startPath(15, 0); + polygon1.lineTo(15, 10); + polygon1.lineTo(25, 10); + polygon1.lineTo(25, 0); + + multipoint2.add(14, 5); + multipoint2.add(20, 11); + multipoint2.add(26, 5); + multipoint2.add(20, -1); + + res = disjoint.execute(polygon1, multipoint2, sr, null); + assertTrue(res); + res = disjoint.execute(multipoint2, polygon1, sr, null); + assertTrue(res); + + multipoint2.add(20, 5); + + res = disjoint.execute(polygon1, multipoint2, sr, null); + assertTrue(!res); + res = disjoint.execute(multipoint2, polygon1, sr, null); + assertTrue(!res); + } + + @Test + public static void testPolygonMultiPointTouches() { + OperatorTouches touches = (OperatorTouches) (OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Touches)); + SpatialReference sr = SpatialReference.create(102100); + @SuppressWarnings("unused") + double tolerance = sr + .getTolerance(VertexDescription.Semantics.POSITION); + + Polygon polygon1 = new Polygon(); + MultiPoint multipoint2 = new MultiPoint(); + + polygon1.startPath(0, 0); + polygon1.lineTo(0, 10); + polygon1.lineTo(10, 10); + polygon1.lineTo(10, 0); + + multipoint2.add(-1, 5); + multipoint2.add(5, 11); + multipoint2.add(11, 5); + multipoint2.add(5, -1); + + boolean res = touches.execute(polygon1, multipoint2, sr, null); + assertTrue(!res); + res = touches.execute(multipoint2, polygon1, sr, null); + assertTrue(!res); + + multipoint2.add(5, 10); + + res = touches.execute(polygon1, multipoint2, sr, null); + assertTrue(res); + res = touches.execute(multipoint2, polygon1, sr, null); + assertTrue(res); + + multipoint2.add(5, 5); + res = touches.execute(polygon1, multipoint2, sr, null); + assertTrue(!res); + res = touches.execute(multipoint2, polygon1, sr, null); + assertTrue(!res); + } + + @Test + public static void testPolygonPointTouches() { + OperatorTouches touches = (OperatorTouches) (OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Touches)); + SpatialReference sr = SpatialReference.create(102100); + @SuppressWarnings("unused") + double tolerance = sr + .getTolerance(VertexDescription.Semantics.POSITION); + + Polygon polygon1 = new Polygon(); + Point point2 = new Point(); + + polygon1.startPath(0, 0); + polygon1.lineTo(0, 10); + polygon1.lineTo(10, 10); + polygon1.lineTo(10, 0); + + point2.setXY(5, 5); + + boolean res = touches.execute(polygon1, point2, sr, null); + assertTrue(!res); + res = touches.execute(point2, polygon1, sr, null); + assertTrue(!res); + + point2.setXY(5, 10); + + res = touches.execute(polygon1, point2, sr, null); + assertTrue(res); + res = touches.execute(point2, polygon1, sr, null); + assertTrue(res); + } + + @Test + public static void testPolygonPolygonTouches() { + OperatorTouches touches = (OperatorTouches) (OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Touches)); + SpatialReference sr = SpatialReference.create(102100); + double tolerance = sr + .getTolerance(VertexDescription.Semantics.POSITION); + + // Polygon1 and Polygon2 touch at a point + String str1 = "{\"rings\":[[[0,0],[0,5],[0,10],[10,10],[10,0],[0,0]],[[1,1],[9,1],[9,9],[1,9],[1,1],[1,1]]]}"; + String str2 = "{\"rings\":[[[10,10],[10,15],[15,15],[15,10],[10,10]]]}"; + + Polygon polygon1 = (Polygon) (importFromJson(str1).getGeometry()); + Polygon polygon2 = (Polygon) (importFromJson(str2).getGeometry()); + wiggleGeometry(polygon1, tolerance, 1982); + wiggleGeometry(polygon2, tolerance, 511); + + boolean res = touches.execute(polygon1, polygon2, sr, null); + assertTrue(res); + res = touches.execute(polygon2, polygon1, sr, null); + assertTrue(res); + + // Polygon1 and Polygon2 touch along the boundary + str1 = "{\"rings\":[[[0,0],[0,5],[0,10],[10,10],[10,0],[0,0]],[[1,1],[9,1],[9,9],[1,9],[1,1],[1,1]]]}"; + str2 = "{\"rings\":[[[10,0],[10,10],[15,10],[15,0],[10,0]]]}"; + + polygon1 = (Polygon) (importFromJson(str1).getGeometry()); + polygon2 = (Polygon) (importFromJson(str2).getGeometry()); + wiggleGeometry(polygon1, tolerance, 1982); + wiggleGeometry(polygon2, tolerance, 511); + + res = touches.execute(polygon1, polygon2, sr, null); + assertTrue(res); + res = touches.execute(polygon2, polygon1, sr, null); + assertTrue(res); + + // Polygon1 and Polygon2 touch at a corner of Polygon1 and a diagonal of + // Polygon2 + str1 = "{\"rings\":[[[0,0],[0,5],[0,10],[10,10],[10,0],[0,0]],[[1,1],[9,1],[9,9],[1,9],[1,1],[1,1]]]}"; + str2 = "{\"rings\":[[[15,5],[5,15],[15,15],[15,5]]]}"; + + polygon1 = (Polygon) (importFromJson(str1).getGeometry()); + polygon2 = (Polygon) (importFromJson(str2).getGeometry()); + wiggleGeometry(polygon1, tolerance, 1982); + wiggleGeometry(polygon2, tolerance, 511); + + res = touches.execute(polygon1, polygon2, sr, null); + assertTrue(res); + res = touches.execute(polygon2, polygon1, sr, null); + assertTrue(res); + + // Polygon1 and Polygon2 do not touch + str1 = "{\"rings\":[[[0,0],[0,5],[0,10],[10,10],[10,0],[0,0]],[[1,1],[9,1],[9,9],[1,9],[1,1],[1,1]]]}"; + str2 = "{\"rings\":[[[5,5],[5,15],[15,15],[15,5],[5,5]]]}"; + + polygon1 = (Polygon) (importFromJson(str1).getGeometry()); + polygon2 = (Polygon) (importFromJson(str2).getGeometry()); + + res = touches.execute(polygon1, polygon2, sr, null); + assertTrue(!res); + res = touches.execute(polygon2, polygon1, sr, null); + assertTrue(!res); + + polygon1.setEmpty(); + polygon2.setEmpty(); + + polygon1.startPath(0, 0); + polygon1.lineTo(0, 1); + polygon1.lineTo(-1, 0); + + polygon2.startPath(0, 0); + polygon2.lineTo(0, 1); + polygon2.lineTo(1, 0); + + res = touches.execute(polygon1, polygon2, sr, null); + assertTrue(res); + res = touches.execute(polygon2, polygon1, sr, null); + assertTrue(res); + } + + @Test + public static void testPolygonPolylineTouches() { + OperatorTouches touches = (OperatorTouches) (OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Touches)); + SpatialReference sr = SpatialReference.create(102100); + double tolerance = sr + .getTolerance(VertexDescription.Semantics.POSITION); + + // Polygon1 and Polyline2 touch at a point + String str1 = "{\"rings\":[[[0,0],[0,5],[0,10],[10,10],[10,0]],[[1,1],[9,1],[9,9],[1,9],[1,1],[1,1]]]}"; + String str2 = "{\"paths\":[[[10,10],[10,15],[15,15],[15,10]]]}"; + + Polygon polygon1 = (Polygon) (importFromJson(str1).getGeometry()); + Polyline polyline2 = (Polyline) (importFromJson(str2).getGeometry()); + wiggleGeometry(polygon1, tolerance, 1982); + wiggleGeometry(polyline2, tolerance, 511); + + boolean res = touches.execute(polygon1, polyline2, sr, null); + assertTrue(res); + res = touches.execute(polyline2, polygon1, sr, null); + assertTrue(res); + + // Polygon1 and Polyline2 overlap along the boundary + str1 = "{\"rings\":[[[0,0],[0,5],[0,10],[10,10],[10,0],[0,0]],[[1,1],[9,1],[9,9],[1,9],[1,1],[1,1]]]}"; + str2 = "{\"paths\":[[[10,0],[10,10],[15,10],[15,0]]]}"; + + polygon1 = (Polygon) (importFromJson(str1).getGeometry()); + polyline2 = (Polyline) (importFromJson(str2).getGeometry()); + wiggleGeometry(polygon1, tolerance, 1982); + wiggleGeometry(polyline2, tolerance, 511); + + res = touches.execute(polygon1, polyline2, sr, null); + assertTrue(res); + res = touches.execute(polyline2, polygon1, sr, null); + assertTrue(res); + + str1 = "{\"rings\":[[[0,0],[0,5],[0,10],[10,10],[10,0],[0,0]],[[1,1],[9,1],[9,9],[1,9],[1,1],[1,1]]]}"; + str2 = "{\"paths\":[[[15,5],[5,15],[15,15],[15,5]]]}"; + + polygon1 = (Polygon) (importFromJson(str1).getGeometry()); + polyline2 = (Polyline) (importFromJson(str2).getGeometry()); + wiggleGeometry(polygon1, tolerance, 1982); + wiggleGeometry(polyline2, tolerance, 511); + + res = touches.execute(polygon1, polyline2, sr, null); + assertTrue(res); + res = touches.execute(polyline2, polygon1, sr, null); + assertTrue(res); + + str1 = "{\"rings\":[[[10,10],[10,0],[0,0],[0,10],[10,10]],[[1,1],[9,1],[9,9],[1,9],[1,1],[1,1]]]}"; + str2 = "{\"paths\":[[[15,5],[5,15],[15,15]]]}"; + + polygon1 = (Polygon) (importFromJson(str1).getGeometry()); + polyline2 = (Polyline) (importFromJson(str2).getGeometry()); + wiggleGeometry(polygon1, tolerance, 1982); + wiggleGeometry(polyline2, tolerance, 511); + + res = touches.execute(polygon1, polyline2, sr, null); + assertTrue(res); + res = touches.execute(polyline2, polygon1, sr, null); + assertTrue(res); + } + + @Test + public static void testPolylinePolylineTouches() { + OperatorTouches touches = (OperatorTouches) (OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Touches)); + SpatialReference sr = SpatialReference.create(102100); + double tolerance = sr + .getTolerance(VertexDescription.Semantics.POSITION); + + // Polyline1 and Polyline2 touch at a point + String str1 = "{\"paths\":[[[0,0],[0,5],[0,10],[10,10],[10,0]],[[1,1],[9,1],[9,9],[1,9],[1,1],[1,1]]]}"; + String str2 = "{\"paths\":[[[10,10],[10,15],[15,15],[15,10]]]}"; + + Polyline polyline1 = (Polyline) (importFromJson(str1).getGeometry()); + Polyline polyline2 = (Polyline) (importFromJson(str2).getGeometry()); + + boolean res = touches.execute(polyline1, polyline2, sr, null); + assertTrue(res); + res = touches.execute(polyline2, polyline1, sr, null); + assertTrue(res); + + // Polyline1 and Polyline2 overlap along the boundary + str1 = "{\"paths\":[[[0,0],[0,5],[0,10],[10,10],[10,0],[0,0]],[[1,1],[9,1],[9,9],[1,9],[1,1],[1,1]]]}"; + str2 = "{\"paths\":[[[10,0],[10,10],[15,10],[15,0],[10,0]]]}"; + + polyline1 = (Polyline) (importFromJson(str1).getGeometry()); + polyline2 = (Polyline) (importFromJson(str2).getGeometry()); + + res = touches.execute(polyline1, polyline2, sr, null); + assertTrue(!res); + res = touches.execute(polyline2, polyline1, sr, null); + assertTrue(!res); + + // Polyline1 and Polyline2 intersect at interiors + str1 = "{\"paths\":[[[0,0],[0,5],[0,10],[10,10],[10,0],[0,0]],[[1,1],[9,1],[9,9],[1,9],[1,1],[1,1]]]}"; + str2 = "{\"paths\":[[[15,5],[5,15],[15,15],[15,5]]]}"; + + polyline1 = (Polyline) (importFromJson(str1).getGeometry()); + polyline2 = (Polyline) (importFromJson(str2).getGeometry()); + + res = touches.execute(polyline1, polyline2, sr, null); + assertTrue(!res); + res = touches.execute(polyline2, polyline1, sr, null); + assertTrue(!res); + + // Polyline1 and Polyline2 touch at an endpoint of Polyline1 and + // interior of Polyline2 (but Polyline1 is closed) + str1 = "{\"paths\":[[[10,10],[10,0],[0,0],[0,10],[10,10]],[[1,1],[9,1],[9,9],[1,9],[1,1],[1,1]]]}"; + str2 = "{\"paths\":[[[15,5],[5,15],[15,15]]]}"; + + polyline1 = (Polyline) (importFromJson(str1).getGeometry()); + polyline2 = (Polyline) (importFromJson(str2).getGeometry()); + + res = touches.execute(polyline1, polyline2, sr, null); + assertTrue(!res); + res = touches.execute(polyline2, polyline1, sr, null); + assertTrue(!res); + + // Polyline1 and Polyline2 touch at an endpoint of Polyline1 and + // interior of Polyline2 (same as previous case, but Polyline1 is not + // closed) + str1 = "{\"paths\":[[[10,10],[10,0],[0,0],[0,10]],[[1,1],[9,1],[9,9],[1,9],[1,1],[1,1]]]}"; + str2 = "{\"paths\":[[[15,5],[5,15],[15,15]]]}"; + + polyline1 = (Polyline) (importFromJson(str1).getGeometry()); + polyline2 = (Polyline) (importFromJson(str2).getGeometry()); + + res = touches.execute(polyline1, polyline2, sr, null); + assertTrue(res); + res = touches.execute(polyline2, polyline1, sr, null); + assertTrue(res); + + str1 = "{\"paths\":[[[10,10],[10,0],[0,0],[0,10]],[[1,1],[9,1],[9,9],[1,9],[6, 9]]]}"; + str2 = "{\"paths\":[[[15,5],[5,15],[15,15],[15,5]]]}"; + + polyline1 = (Polyline) (importFromJson(str1).getGeometry()); + polyline2 = (Polyline) (importFromJson(str2).getGeometry()); + + res = touches.execute(polyline1, polyline2, sr, null); + assertTrue(res); + res = touches.execute(polyline2, polyline1, sr, null); + assertTrue(res); + + polyline1.setEmpty(); + polyline2.setEmpty(); + + polyline1.startPath(-2, -2); + polyline1.lineTo(-1, -1); + polyline1.lineTo(1, 1); + polyline1.lineTo(2, 2); + + polyline2.startPath(-2, 2); + polyline2.lineTo(-1, 1); + polyline2.lineTo(1, -1); + polyline2.lineTo(2, -2); + + res = touches.execute(polyline2, polyline1, sr, null); + assertTrue(!res); + + polyline1.setEmpty(); + polyline2.setEmpty(); + + polyline1.startPath(-2, -2); + polyline1.lineTo(-1, -1); + polyline1.lineTo(1, 1); + polyline1.lineTo(2, 2); + + polyline2.startPath(-2, 2); + polyline2.lineTo(-1, 1); + polyline2.lineTo(1, -1); + + res = touches.execute(polyline1, polyline2, sr, null); + assertTrue(!res); + + polyline1.setEmpty(); + polyline2.setEmpty(); + + polyline1.startPath(-1, -1); + polyline1.lineTo(0, 0); + polyline1.lineTo(1, 1); + + polyline2.startPath(-1, 1); + polyline2.lineTo(0, 0); + polyline2.lineTo(1, -1); + + res = touches.execute(polyline2, polyline1, sr, null); + assertTrue(!res); + + polyline1.setEmpty(); + polyline2.setEmpty(); + polyline1.startPath(0, 0); + polyline1.lineTo(0, 1); + polyline1.lineTo(0, 0); + polyline2.startPath(0, 1); + polyline2.lineTo(0, 2); + polyline2.lineTo(0, 1); + + res = touches.execute(polyline2, polyline1, sr, null); + assertTrue(!res); + + res = touches.execute(polyline1, polyline2, sr, null); + assertTrue(!res); + } + + @Test + public static void testPolylineMultiPointTouches() { + OperatorTouches touches = (OperatorTouches) (OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Touches)); + SpatialReference sr = SpatialReference.create(102100); + @SuppressWarnings("unused") + double tolerance = sr + .getTolerance(VertexDescription.Semantics.POSITION); + + Polyline polyline1 = new Polyline(); + MultiPoint multipoint2 = new MultiPoint(); + + polyline1.startPath(0, 0); + polyline1.lineTo(2, 0); + polyline1.lineTo(4, 2); + + multipoint2.add(1, 1); + multipoint2.add(2, 2); + multipoint2.add(3, 0); + + boolean res = touches.execute(polyline1, multipoint2, sr, null); + assertTrue(!res); + res = touches.execute(multipoint2, polyline1, sr, null); + assertTrue(!res); + + polyline1.startPath(1, -4); + polyline1.lineTo(1, -3); + polyline1.lineTo(1, -2); + polyline1.lineTo(1, -1); + polyline1.lineTo(1, 0); + polyline1.lineTo(1, 1); + + res = touches.execute(polyline1, multipoint2, sr, null); + assertTrue(res); + res = touches.execute(multipoint2, polyline1, sr, null); + assertTrue(res); + + multipoint2.add(3, 1); + res = touches.execute(polyline1, multipoint2, sr, null); + assertTrue(!res); + res = touches.execute(multipoint2, polyline1, sr, null); + assertTrue(!res); + + polyline1.startPath(0, 0); + polyline1.lineTo(2, 0); + + polyline1.startPath(2, 1); + polyline1.lineTo(2, -1); + + multipoint2.add(2, 0); + + res = touches.execute(polyline1, multipoint2, sr, null); + assertTrue(!res); + res = touches.execute(multipoint2, polyline1, sr, null); + assertTrue(!res); + } + + @Test + public static void testPolylineMultiPointCrosses() { + OperatorCrosses crosses = (OperatorCrosses) (OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Crosses)); + SpatialReference sr = SpatialReference.create(102100); + double tolerance = sr + .getTolerance(VertexDescription.Semantics.POSITION); + + Polyline polyline1 = new Polyline(); + MultiPoint multipoint2 = new MultiPoint(); + + polyline1.startPath(0, 0); + polyline1.lineTo(2, 0); + polyline1.lineTo(4, 2); + + multipoint2.add(1, 1); + multipoint2.add(2, 2); + multipoint2.add(3, 0); + multipoint2.add(0, 0); + + boolean res = crosses.execute(polyline1, multipoint2, sr, null); + assertTrue(!res); + res = crosses.execute(multipoint2, polyline1, sr, null); + assertTrue(!res); + + polyline1.startPath(1, -4); + polyline1.lineTo(1, -3); + polyline1.lineTo(1, -2); + polyline1.lineTo(1, -1); + polyline1.lineTo(1, 0); + polyline1.lineTo(1, 1); + + res = crosses.execute(polyline1, multipoint2, sr, null); + assertTrue(!res); + res = crosses.execute(multipoint2, polyline1, sr, null); + assertTrue(!res); + + multipoint2.add(1, 0); + res = crosses.execute(polyline1, multipoint2, sr, null); + assertTrue(res); + res = crosses.execute(multipoint2, polyline1, sr, null); + assertTrue(res); + + multipoint2.add(3, 1); + res = crosses.execute(polyline1, multipoint2, sr, null); + assertTrue(res); + res = crosses.execute(multipoint2, polyline1, sr, null); + assertTrue(res); + } + + @Test + public static void testPolylinePointTouches() { + OperatorTouches touches = (OperatorTouches) (OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Touches)); + SpatialReference sr = SpatialReference.create(102100); + @SuppressWarnings("unused") + double tolerance = sr + .getTolerance(VertexDescription.Semantics.POSITION); + + Polyline polyline1 = new Polyline(); + Point point2 = new Point(); + + polyline1.startPath(0, 0); + polyline1.lineTo(2, 0); + + polyline1.startPath(2, 1); + polyline1.lineTo(2, -1); + + point2.setXY(2, 0); + + boolean res = touches.execute(polyline1, point2, sr, null); + assertTrue(res); + res = touches.execute(point2, polyline1, sr, null); + assertTrue(res); + } + + @Test + public static void testPolygonPolygonOverlaps() { + OperatorOverlaps overlaps = (OperatorOverlaps) (OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Overlaps)); + SpatialReference sr = SpatialReference.create(102100); + double tolerance = sr + .getTolerance(VertexDescription.Semantics.POSITION); + + // Polygon1 and Polygon2 touch at a point + String str1 = "{\"rings\":[[[0,0],[0,5],[0,10],[10,10],[10,0],[0,0]],[[1,1],[9,1],[9,9],[1,9],[1,1],[1,1]]]}"; + String str2 = "{\"rings\":[[[10,10],[10,15],[15,15],[15,10],[10,10]]]}"; + + Polygon polygon1 = (Polygon) (importFromJson(str1).getGeometry()); + Polygon polygon2 = (Polygon) (importFromJson(str2).getGeometry()); + wiggleGeometry(polygon1, tolerance, 1982); + wiggleGeometry(polygon2, tolerance, 511); + + boolean res = overlaps.execute(polygon1, polygon2, sr, null); + assertTrue(!res); + res = overlaps.execute(polygon2, polygon1, sr, null); + assertTrue(!res); + + // Polygon1 and Polygon2 touch along the boundary + str1 = "{\"rings\":[[[0,0],[0,5],[0,10],[10,10],[10,0],[0,0]],[[1,1],[9,1],[9,9],[1,9],[1,1],[1,1]]]}"; + str2 = "{\"rings\":[[[10,0],[10,10],[15,10],[15,0],[10,0]]]}"; + + polygon1 = (Polygon) (importFromJson(str1).getGeometry()); + polygon2 = (Polygon) (importFromJson(str2).getGeometry()); + wiggleGeometry(polygon1, tolerance, 1982); + wiggleGeometry(polygon2, tolerance, 511); + + res = overlaps.execute(polygon1, polygon2, sr, null); + assertTrue(!res); + res = overlaps.execute(polygon2, polygon1, sr, null); + assertTrue(!res); + + // Polygon1 and Polygon2 touch at a corner of Polygon1 and a diagonal of + // Polygon2 + str1 = "{\"rings\":[[[0,0],[0,5],[0,10],[10,10],[10,0],[0,0]],[[1,1],[9,1],[9,9],[1,9],[1,1],[1,1]]]}"; + str2 = "{\"rings\":[[[15,5],[5,15],[15,15],[15,5]]]}"; + + polygon1 = (Polygon) (importFromJson(str1).getGeometry()); + polygon2 = (Polygon) (importFromJson(str2).getGeometry()); + wiggleGeometry(polygon1, tolerance, 1982); + wiggleGeometry(polygon2, tolerance, 511); + + res = overlaps.execute(polygon1, polygon2, sr, null); + assertTrue(!res); + res = overlaps.execute(polygon2, polygon1, sr, null); + assertTrue(!res); + + // Polygon1 and Polygon2 overlap at the upper right corner + str1 = "{\"rings\":[[[0,0],[0,5],[0,10],[10,10],[10,0],[0,0]],[[1,1],[9,1],[9,9],[1,9],[1,1],[1,1]]]}"; + str2 = "{\"rings\":[[[5,5],[5,15],[15,15],[15,5],[5,5]]]}"; + + polygon1 = (Polygon) (importFromJson(str1).getGeometry()); + polygon2 = (Polygon) (importFromJson(str2).getGeometry()); + + res = overlaps.execute(polygon1, polygon2, sr, null); + assertTrue(res); + res = overlaps.execute(polygon2, polygon1, sr, null); + assertTrue(res); + + str1 = "{\"rings\":[[[0,0],[0,5],[0,10],[10,10],[10,0],[0,0]],[[4,4],[6,4],[6,6],[4,6],[4,4],[4,4]]]}"; + str2 = "{\"rings\":[[[1,1],[1,9],[9,9],[9,1],[1,1]]]}"; + + polygon1 = (Polygon) (importFromJson(str1).getGeometry()); + polygon2 = (Polygon) (importFromJson(str2).getGeometry()); + + res = overlaps.execute(polygon1, polygon2, sr, null); + assertTrue(res); + res = overlaps.execute(polygon2, polygon1, sr, null); + assertTrue(res); + } + + @Test + public static void testPolygonPolylineWithin() { + OperatorWithin within = (OperatorWithin) (OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Within)); + SpatialReference sr = SpatialReference.create(102100); + @SuppressWarnings("unused") + double tolerance = sr + .getTolerance(VertexDescription.Semantics.POSITION); + + Polygon polygon1 = new Polygon(); + Polyline polyline2 = new Polyline(); + + polygon1.startPath(0, 0); + polygon1.lineTo(0, 10); + polygon1.lineTo(10, 10); + polygon1.lineTo(10, 0); + + polyline2.startPath(5, 0); + polyline2.lineTo(5, 10); + + boolean res = within.execute(polygon1, polyline2, sr, null); + assertTrue(!res); + res = within.execute(polyline2, polygon1, sr, null); + assertTrue(res); + + polyline2.setEmpty(); + polyline2.startPath(0, 1); + polyline2.lineTo(0, 9); + + res = within.execute(polyline2, polygon1, sr, null); + assertTrue(!res); + } + + @Test + public static void testMultiPointMultiPointWithin() { + OperatorWithin within = (OperatorWithin) (OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Within)); + SpatialReference sr = SpatialReference.create(102100); + @SuppressWarnings("unused") + double tolerance = sr + .getTolerance(VertexDescription.Semantics.POSITION); + + MultiPoint multipoint1 = new MultiPoint(); + MultiPoint multipoint2 = new MultiPoint(); + + multipoint1.add(0, 0); + multipoint1.add(3, 3); + multipoint1.add(0, 0); + multipoint1.add(5, 5); + multipoint1.add(3, 3); + multipoint1.add(2, 4); + multipoint1.add(2, 8); + + multipoint2.add(0, 0); + multipoint2.add(3, 3); + multipoint2.add(2, 4); + multipoint2.add(2, 8); + multipoint2.add(5, 5); + + boolean res = within.execute(multipoint1, multipoint2, sr, null); + assertTrue(res); + res = within.execute(multipoint2, multipoint1, sr, null); + assertTrue(res); + + multipoint2.add(10, 10); + multipoint2.add(10, 10); + + res = within.execute(multipoint1, multipoint2, sr, null); + assertTrue(res); + res = within.execute(multipoint2, multipoint1, sr, null); + assertTrue(!res); + + multipoint1.add(10, 10); + res = within.execute(multipoint1, multipoint2, sr, null); + assertTrue(res); + res = within.execute(multipoint2, multipoint1, sr, null); + assertTrue(res); + + multipoint1.add(-10, -10); + res = within.execute(multipoint1, multipoint2, sr, null); + assertTrue(!res); + res = within.execute(multipoint2, multipoint1, sr, null); + assertTrue(res); + } + + @Test + public static void testPolylinePolylineOverlaps() { + OperatorOverlaps overlaps = (OperatorOverlaps) (OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Overlaps)); + SpatialReference sr = SpatialReference.create(102100); + double tolerance = sr + .getTolerance(VertexDescription.Semantics.POSITION); + + Polyline polyline1 = new Polyline(); + Polyline polyline2 = new Polyline(); + polyline1.startPath(0, 0); + polyline1.lineTo(2, 0); + polyline2.startPath(1, 0); + polyline2.lineTo(3, 0); + polyline2.lineTo(1, 1); + polyline2.lineTo(1, -1); + wiggleGeometry(polyline1, tolerance, 1982); + wiggleGeometry(polyline2, tolerance, 511); + + boolean res = overlaps.execute(polyline1, polyline2, sr, null); + assertTrue(res); + res = overlaps.execute(polyline2, polyline1, sr, null); + assertTrue(res); + + polyline1.setEmpty(); + polyline2.setEmpty(); + polyline1.startPath(0, 0); + polyline1.lineTo(2, 0); + polyline2.startPath(1.9989, 0); + polyline2.lineTo(2.0011, 0); + // wiggleGeometry(polyline1, tolerance, 1982); + // wiggleGeometry(polyline2, tolerance, 511); + + res = overlaps.execute(polyline1, polyline2, sr, null); + assertTrue(res); + res = overlaps.execute(polyline2, polyline1, sr, null); + assertTrue(res); + + polyline1.setEmpty(); + polyline2.setEmpty(); + polyline1.startPath(0, 0); + polyline1.lineTo(2, 0); + polyline2.startPath(1.9989, 0); + polyline2.lineTo(2.0009, 0); + wiggleGeometry(polyline1, tolerance, 1982); + wiggleGeometry(polyline2, tolerance, 511); + + res = overlaps.execute(polyline1, polyline2, sr, null); + assertTrue(!res); + res = overlaps.execute(polyline2, polyline1, sr, null); + assertTrue(!res); + + polyline1.setEmpty(); + polyline2.setEmpty(); + polyline1.startPath(0, 0); + polyline1.lineTo(2, 0); + polyline2.startPath(0, 0); + polyline2.lineTo(2, 0); + polyline2.startPath(0, -1); + polyline2.lineTo(2, -1); + + res = overlaps.execute(polyline1, polyline2, sr, null); + assertTrue(!res); + res = overlaps.execute(polyline2, polyline1, sr, null); + assertTrue(!res); + } + + @Test + public static void testMultiPointMultiPointOverlaps() { + OperatorOverlaps overlaps = (OperatorOverlaps) (OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Overlaps)); + SpatialReference sr = SpatialReference.create(102100); + @SuppressWarnings("unused") + double tolerance = sr + .getTolerance(VertexDescription.Semantics.POSITION); + + MultiPoint multipoint1 = new MultiPoint(); + MultiPoint multipoint2 = new MultiPoint(); + + multipoint1.add(4, 4); + multipoint1.add(6, 4); + + multipoint2.add(6, 2); + multipoint2.add(2, 6); + + boolean res = overlaps.execute(multipoint1, multipoint2, sr, null); + assertTrue(!res); + res = overlaps.execute(multipoint2, multipoint1, sr, null); + assertTrue(!res); + + multipoint1.add(10, 10); + multipoint2.add(6, 2); + + res = overlaps.execute(multipoint1, multipoint2, sr, null); + assertTrue(!res); + res = overlaps.execute(multipoint2, multipoint1, sr, null); + assertTrue(!res); + + multipoint1.add(6, 2); + res = overlaps.execute(multipoint1, multipoint2, sr, null); + assertTrue(res); + res = overlaps.execute(multipoint2, multipoint1, sr, null); + assertTrue(res); + + multipoint1.add(2, 6); + res = overlaps.execute(multipoint1, multipoint2, sr, null); + assertTrue(!res); + res = overlaps.execute(multipoint2, multipoint1, sr, null); + assertTrue(!res); + + multipoint2.add(1, 1); + res = overlaps.execute(multipoint1, multipoint2, sr, null); + assertTrue(res); + res = overlaps.execute(multipoint2, multipoint1, sr, null); + assertTrue(res); + + multipoint2.add(10, 10); + multipoint2.add(4, 4); + multipoint2.add(6, 4); + res = overlaps.execute(multipoint1, multipoint2, sr, null); + assertTrue(!res); + res = overlaps.execute(multipoint2, multipoint1, sr, null); + assertTrue(!res); + } + + @Test + public static void testPolygonPolygonWithin() { + OperatorWithin within = (OperatorWithin) (OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Within)); + SpatialReference sr = SpatialReference.create(102100); + double tolerance = sr + .getTolerance(VertexDescription.Semantics.POSITION); + + // Polygon1 is within Polygon2 + String str1 = "{\"rings\":[[[0,0],[0,5],[0,10],[10,10],[10,0],[0,0]],[[1,1],[9,1],[9,9],[1,9],[1,1],[1,1]]]}"; + String str2 = "{\"rings\":[[[-1,-1],[-1,11],[11,11],[11,-1],[-1,-1]]]}"; + + Polygon polygon1 = (Polygon) (importFromJson(str1).getGeometry()); + Polygon polygon2 = (Polygon) (importFromJson(str2).getGeometry()); + + boolean res = within.execute(polygon1, polygon2, sr, null); + assertTrue(res); + res = within.execute(polygon2, polygon1, sr, null); + assertTrue(!res); + + // Polygon1 is within Polygon2, and the boundaries intersect + str1 = "{\"rings\":[[[0,0],[0,5],[0,10],[10,10],[10,0],[0,0]],[[4,4],[6,4],[6,6],[4,6],[4,4],[4,4]]]}"; + str2 = "{\"rings\":[[[1,1],[1,9],[9,9],[9,1],[1,1]]]}"; + + polygon1 = (Polygon) (importFromJson(str1).getGeometry()); + polygon2 = (Polygon) (importFromJson(str2).getGeometry()); + wiggleGeometry(polygon1, tolerance, 1982); + wiggleGeometry(polygon2, tolerance, 511); + + res = within.execute(polygon2, polygon1, sr, null); + assertTrue(!res); + + // Polygon1 is within Polygon2, and the boundaries intersect + str1 = "{\"rings\":[[[0,0],[0,5],[0,10],[10,10],[10,0],[0,0]],[[1,1],[9,1],[9,9],[1,9],[1,1],[1,1]]]}"; + str2 = "{\"rings\":[[[-1,0],[-1,11],[11,11],[11,0],[-1,0]]]}"; + + polygon1 = (Polygon) (importFromJson(str1).getGeometry()); + polygon2 = (Polygon) (importFromJson(str2).getGeometry()); + wiggleGeometry(polygon1, tolerance, 1982); + wiggleGeometry(polygon2, tolerance, 511); + + res = within.execute(polygon1, polygon2, sr, null); + assertTrue(res); + + // Polygon2 is inside of the hole of polygon1 + str1 = "{\"rings\":[[[0,0],[0,5],[0,10],[10,10],[10,0],[0,0]],[[1,1],[9,1],[9,9],[1,9],[1,1],[1,1]]]}"; + str2 = "{\"rings\":[[[2,2],[2,8],[8,8],[8,2],[2,2]]]}"; + + polygon1 = (Polygon) (importFromJson(str1).getGeometry()); + polygon2 = (Polygon) (importFromJson(str2).getGeometry()); + + res = within.execute(polygon2, polygon1, sr, null); + assertTrue(!res); + + } + + @Test + public static void testPolylinePolylineWithin() { + OperatorWithin within = (OperatorWithin) (OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Within)); + OperatorContains contains = (OperatorContains) (OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Contains)); + SpatialReference sr = SpatialReference.create(102100); + @SuppressWarnings("unused") + double tolerance = sr + .getTolerance(VertexDescription.Semantics.POSITION); + + Polyline polyline1 = new Polyline(); + Polyline polyline2 = new Polyline(); + + polyline1.setEmpty(); + polyline2.setEmpty(); + polyline1.startPath(0, 0); + polyline1.lineTo(2, 0); + polyline2.startPath(1.9989, 0); + polyline2.lineTo(2.0011, 0); + + boolean res = within.execute(polyline2, polyline1, sr, null); + assertTrue(!res); + + res = contains.execute(polyline1, polyline2, sr, null); + assertTrue(!res); + + polyline1.setEmpty(); + polyline2.setEmpty(); + polyline1.startPath(0, 0); + polyline1.lineTo(2, 0); + polyline2.startPath(1.9989, 0); + polyline2.lineTo(2.001, 0); + + res = within.execute(polyline2, polyline1, sr, null); + assertTrue(res); + + res = contains.execute(polyline1, polyline2, sr, null); + assertTrue(res); + + polyline1.setEmpty(); + polyline2.setEmpty(); + polyline1.startPath(0, 0); + polyline1.lineTo(2, 0); + polyline1.lineTo(3, 0); + polyline1.lineTo(4, 0); + polyline1.lineTo(5, 0); + polyline1.lineTo(6, 0); + polyline1.lineTo(7, 0); + polyline1.lineTo(8, 0); + + polyline2.startPath(0, 0); + polyline2.lineTo(.1, 0); + polyline2.lineTo(.2, 0); + polyline2.lineTo(.4, 0); + polyline2.lineTo(1.1, 0); + polyline2.lineTo(2.5, 0); + + polyline2.startPath(2.7, 0); + polyline2.lineTo(4, 0); + + res = within.execute(polyline2, polyline1, sr, null); + assertTrue(res); + + res = contains.execute(polyline1, polyline2, sr, null); + assertTrue(res); + } + + @Test + public static void testPolylineMultiPointWithin() { + OperatorWithin within = (OperatorWithin) (OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Within)); + SpatialReference sr = SpatialReference.create(102100); + @SuppressWarnings("unused") + double tolerance = sr + .getTolerance(VertexDescription.Semantics.POSITION); + + Polyline polyline1 = new Polyline(); + MultiPoint multipoint2 = new MultiPoint(); + + polyline1.startPath(0, 0); + polyline1.lineTo(2, 0); + polyline1.lineTo(4, 2); + + multipoint2.add(1, 0); + multipoint2.add(2, 0); + multipoint2.add(3, 1); + multipoint2.add(2, 0); + + boolean res = within.execute(polyline1, multipoint2, sr, null); + assertTrue(!res); + res = within.execute(multipoint2, polyline1, sr, null); + assertTrue(res); + + polyline1.startPath(1, -2); + polyline1.lineTo(1, -1); + polyline1.lineTo(1, 0); + polyline1.lineTo(1, 1); + + res = within.execute(polyline1, multipoint2, sr, null); + assertTrue(!res); + res = within.execute(multipoint2, polyline1, sr, null); + assertTrue(res); + + multipoint2.add(1, 2); + res = within.execute(multipoint2, polyline1, sr, null); + assertTrue(!res); + + multipoint2.add(-1, -1); + multipoint2.add(4, 2); + multipoint2.add(0, 0); + + res = within.execute(multipoint2, polyline1, sr, null); + assertTrue(!res); + } + + @Test + public static void testPolygonMultiPointWithin() { + OperatorWithin within = (OperatorWithin) (OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Within)); + SpatialReference sr = SpatialReference.create(102100); + @SuppressWarnings("unused") + double tolerance = sr + .getTolerance(VertexDescription.Semantics.POSITION); + + Polygon polygon1 = new Polygon(); + MultiPoint multipoint2 = new MultiPoint(); + + polygon1.startPath(0, 0); + polygon1.lineTo(0, 10); + polygon1.lineTo(10, 10); + polygon1.lineTo(10, 0); + + multipoint2.add(5, 0); + multipoint2.add(5, 10); + multipoint2.add(5, 5); + + boolean res = within.execute(polygon1, multipoint2, sr, null); + assertTrue(!res); + res = within.execute(multipoint2, polygon1, sr, null); + assertTrue(res); + + multipoint2.add(5, 11); + res = within.execute(multipoint2, polygon1, sr, null); + assertTrue(!res); + } + + @Test + public static void testPolygonPolylineCrosses() { + OperatorCrosses crosses = (OperatorCrosses) (OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Crosses)); + SpatialReference sr = SpatialReference.create(102100); + @SuppressWarnings("unused") + double tolerance = sr + .getTolerance(VertexDescription.Semantics.POSITION); + + Polygon polygon1 = new Polygon(); + Polyline polyline2 = new Polyline(); + + polygon1.startPath(0, 0); + polygon1.lineTo(0, 10); + polygon1.lineTo(10, 10); + polygon1.lineTo(10, 0); + + polyline2.startPath(5, -5); + polyline2.lineTo(5, 15); + + boolean res = crosses.execute(polygon1, polyline2, sr, null); + assertTrue(res); + res = crosses.execute(polyline2, polygon1, sr, null); + assertTrue(res); + + polyline2.setEmpty(); + polyline2.startPath(5, 0); + polyline2.lineTo(5, 10); + + res = crosses.execute(polygon1, polyline2, sr, null); + assertTrue(!res); + res = crosses.execute(polyline2, polygon1, sr, null); + assertTrue(!res); + + polygon1.setEmpty(); + polyline2.setEmpty(); + + polygon1.startPath(0, 0); + polygon1.lineTo(0, 10); + polygon1.lineTo(10, 10); + polygon1.lineTo(10, 8); + polygon1.lineTo(15, 5); + polygon1.lineTo(10, 2); + polygon1.lineTo(10, 0); + + polyline2.startPath(10, 15); + polyline2.lineTo(10, -5); + + res = crosses.execute(polygon1, polyline2, sr, null); + assertTrue(res); + res = crosses.execute(polyline2, polygon1, sr, null); + assertTrue(res); + } + + @Test + public static void testPolylinePolylineCrosses() { + OperatorCrosses crosses = (OperatorCrosses) (OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Crosses)); + SpatialReference sr = SpatialReference.create(102100); + double tolerance = sr + .getTolerance(VertexDescription.Semantics.POSITION); + + // Polyline1 and Polyline2 touch at a point + String str1 = "{\"paths\":[[[0,0],[0,5],[0,10],[10,10],[10,0]],[[1,1],[9,1],[9,9],[1,9],[1,1],[1,1]]]}"; + String str2 = "{\"paths\":[[[10,10],[10,15],[15,15],[15,10]]]}"; + + Polyline polyline1 = (Polyline) (importFromJson(str1).getGeometry()); + Polyline polyline2 = (Polyline) (importFromJson(str2).getGeometry()); + + boolean res = crosses.execute(polyline1, polyline2, sr, null); + assertTrue(!res); + res = crosses.execute(polyline2, polyline1, sr, null); + assertTrue(!res); + + // Polyline1 and Polyline2 intersect at interiors + str1 = "{\"paths\":[[[0,0],[0,5],[0,10],[10,10],[10,0],[0,0]],[[1,1],[9,1],[9,9],[1,9],[1,1],[1,1]]]}"; + str2 = "{\"paths\":[[[15,5],[5,15],[15,15],[15,5]]]}"; + + polyline1 = (Polyline) (importFromJson(str1).getGeometry()); + polyline2 = (Polyline) (importFromJson(str2).getGeometry()); + + res = crosses.execute(polyline1, polyline2, sr, null); + assertTrue(res); + res = crosses.execute(polyline2, polyline1, sr, null); + assertTrue(res); + + // Polyline1 and Polyline2 touch at an endpoint of Polyline1 and + // interior of Polyline2 (but Polyline1 is closed) + str1 = "{\"paths\":[[[10,10],[10,0],[0,0],[0,10],[10,10]],[[1,1],[9,1],[9,9],[1,9],[1,1],[1,1]]]}"; + str2 = "{\"paths\":[[[15,5],[5,15],[15,15]]]}"; + + polyline1 = (Polyline) (importFromJson(str1).getGeometry()); + polyline2 = (Polyline) (importFromJson(str2).getGeometry()); + ; + + res = crosses.execute(polyline1, polyline2, sr, null); + assertTrue(res); + res = crosses.execute(polyline2, polyline1, sr, null); + assertTrue(res); + + // Polyline1 and Polyline2 touch at an endpoint of Polyline1 and + // interior of Polyline2 (same as previous case, but Polyline1 is not + // closed) + str1 = "{\"paths\":[[[10,10],[10,0],[0,0],[0,10]],[[1,1],[9,1],[9,9],[1,9],[1,1],[1,1]]]}"; + str2 = "{\"paths\":[[[15,5],[5,15],[15,15]]]}"; + + polyline1 = (Polyline) (importFromJson(str1).getGeometry()); + polyline2 = (Polyline) (importFromJson(str2).getGeometry()); + + res = crosses.execute(polyline1, polyline2, sr, null); + assertTrue(!res); + res = crosses.execute(polyline2, polyline1, sr, null); + assertTrue(!res); + + str1 = "{\"paths\":[[[10,11],[10,0],[0,0],[0,10]],[[1,1],[9,1],[9,9],[1,9],[6, 9]]]}"; + str2 = "{\"paths\":[[[15,5],[5,15],[15,15],[15,5]]]}"; + + polyline1 = (Polyline) (importFromJson(str1).getGeometry()); + polyline2 = (Polyline) (importFromJson(str2).getGeometry()); + + res = crosses.execute(polyline1, polyline2, sr, null); + assertTrue(res); + res = crosses.execute(polyline2, polyline1, sr, null); + assertTrue(res); + + polyline1.setEmpty(); + polyline2.setEmpty(); + + polyline1.startPath(-2, -2); + polyline1.lineTo(-1, -1); + polyline1.lineTo(1, 1); + polyline1.lineTo(2, 2); + + polyline2.startPath(-2, 2); + polyline2.lineTo(-1, 1); + polyline2.lineTo(1, -1); + polyline2.lineTo(2, -2); + + res = crosses.execute(polyline2, polyline1, sr, null); + assertTrue(res); + } + + @Test + public static void testPolygonEnvelope() { + OperatorEquals equals = (OperatorEquals) (OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Equals)); + OperatorContains contains = (OperatorContains) (OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Contains)); + OperatorDisjoint disjoint = (OperatorDisjoint) (OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Disjoint)); + OperatorCrosses crosses = (OperatorCrosses) (OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Crosses)); + @SuppressWarnings("unused") + OperatorWithin within = (OperatorWithin) (OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Within)); + OperatorOverlaps overlaps = (OperatorOverlaps) (OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Overlaps)); + OperatorTouches touches = (OperatorTouches) (OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Touches)); + OperatorDensifyByLength densify = (OperatorDensifyByLength) (OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.DensifyByLength)); + SpatialReference sr = SpatialReference.create(4326); + + { + Polygon polygon = (Polygon) (importFromJson("{\"rings\":[[[0,0],[0,5],[0,10],[10,10],[10,0],[0,0]]]}") + .getGeometry()); + Polygon densified = (Polygon) (densify.execute(polygon, 1.0, null)); + Envelope envelope = (Envelope) (importFromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":10}") + .getGeometry()); + wiggleGeometry(densified, 0.00000001, 1982); + wiggleGeometry(envelope, 0.00000001, 511); + assertTrue(equals.execute(envelope, densified, sr, null)); // they + // cover + // the + // same + // space + assertTrue(contains.execute(densified, envelope, sr, null)); + assertTrue(!disjoint.execute(envelope, densified, sr, null)); + assertTrue(!touches.execute(envelope, densified, sr, null)); + assertTrue(!overlaps.execute(envelope, densified, sr, null)); + assertTrue(!crosses.execute(envelope, densified, sr, null)); + } + + { + Polygon polygon = (Polygon) (importFromJson("{\"rings\":[[[0,0],[0,5],[0,10],[10,10],[10,0],[0,0]]]}") + .getGeometry()); + Polygon densified = (Polygon) (densify.execute(polygon, 1.0, null)); + Envelope envelope = (Envelope) (importFromJson("{\"xmin\":5,\"ymin\":0,\"xmax\":10,\"ymax\":10}") + .getGeometry()); + wiggleGeometry(densified, 0.00000001, 1982); + wiggleGeometry(envelope, 0.00000001, 511); + assertTrue(!equals.execute(envelope, densified, sr, null)); // the + // polygon + // contains + // the + // envelope, + // but + // they + // aren't + // equal + assertTrue(contains.execute(densified, envelope, sr, null)); + assertTrue(!disjoint.execute(envelope, densified, sr, null)); + assertTrue(!touches.execute(envelope, densified, sr, null)); + assertTrue(!overlaps.execute(envelope, densified, sr, null)); + assertTrue(!crosses.execute(envelope, densified, sr, null)); + } + + { + Polygon polygon = (Polygon) (importFromJson("{\"rings\":[[[0,0],[0,5],[0,10],[10,10],[10,0],[0,0]]]}") + .getGeometry()); + Polygon densified = (Polygon) (densify.execute(polygon, 1.0, null)); + Envelope envelope = (Envelope) (importFromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":15,\"ymax\":10}") + .getGeometry()); + wiggleGeometry(densified, 0.00000001, 1982); + wiggleGeometry(envelope, 0.00000001, 511); + assertTrue(!equals.execute(envelope, densified, sr, null)); // the + // envelope + // sticks + // outside + // of + // the + // polygon + // but + // they + // intersect + assertTrue(!contains.execute(densified, envelope, sr, null)); + assertTrue(!disjoint.execute(envelope, densified, sr, null)); + assertTrue(!touches.execute(envelope, densified, sr, null)); + assertTrue(!overlaps.execute(envelope, densified, sr, null)); + assertTrue(!crosses.execute(envelope, densified, sr, null)); + } + + { + Polygon polygon = (Polygon) (importFromJson("{\"rings\":[[[0,0],[0,5],[0,10],[10,10],[10,0],[0,0]]]}") + .getGeometry()); + Polygon densified = (Polygon) (densify.execute(polygon, 1.0, null)); + Envelope envelope = (Envelope) (importFromJson("{\"xmin\":5,\"ymin\":0,\"xmax\":15,\"ymax\":10}") + .getGeometry()); + wiggleGeometry(densified, 0.00000001, 1982); + wiggleGeometry(envelope, 0.00000001, 511); + assertTrue(!equals.execute(envelope, densified, sr, null)); // the + // envelope + // sticks + // outside + // of + // the + // polygon + // but + // they + // intersect + // and + // overlap + assertTrue(!contains.execute(densified, envelope, sr, null)); + assertTrue(!disjoint.execute(envelope, densified, sr, null)); + assertTrue(!touches.execute(envelope, densified, sr, null)); + assertTrue(overlaps.execute(envelope, densified, sr, null)); + assertTrue(!crosses.execute(envelope, densified, sr, null)); + } + + { + Polygon polygon = (Polygon) (importFromJson("{\"rings\":[[[0,0],[0,5],[0,10],[10,10],[10,0],[0,0]]]}") + .getGeometry()); + Polygon densified = (Polygon) (densify.execute(polygon, 1.0, null)); + Envelope envelope = (Envelope) (importFromJson("{\"xmin\":10,\"ymin\":0,\"xmax\":15,\"ymax\":5}") + .getGeometry()); + wiggleGeometry(densified, 0.00000001, 1982); + wiggleGeometry(envelope, 0.00000001, 511); + assertTrue(!equals.execute(envelope, densified, sr, null)); // the + // envelope + // rides + // the + // side + // of + // the + // polygon + // (they + // touch) + assertTrue(!contains.execute(densified, envelope, sr, null)); + assertTrue(!disjoint.execute(envelope, densified, sr, null)); + assertTrue(touches.execute(envelope, densified, sr, null)); + assertTrue(!overlaps.execute(envelope, densified, sr, null)); + assertTrue(!crosses.execute(envelope, densified, sr, null)); + } + + { + Polygon polygon = (Polygon) (importFromJson("{\"rings\":[[[0,0],[0,5],[0,10],[10,10],[10,0],[0,0]]]}") + .getGeometry()); + Polygon densified = (Polygon) (densify.execute(polygon, 1.0, null)); + Envelope envelope = (Envelope) (importFromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":10}") + .getGeometry()); + wiggleGeometry(densified, 0.00000001, 1982); + wiggleGeometry(envelope, 0.00000001, 511); + assertTrue(contains.execute(densified, envelope, sr, null)); // polygon + // and + // envelope + // cover + // the + // same + // space + assertTrue(!disjoint.execute(densified, envelope, sr, null)); + assertTrue(!touches.execute(envelope, densified, sr, null)); + assertTrue(!overlaps.execute(envelope, densified, sr, null)); + assertTrue(!crosses.execute(envelope, densified, sr, null)); + } + + { + Polygon polygon = (Polygon) (importFromJson("{\"rings\":[[[0,0],[0,5],[0,10],[10,0],[0,0]]]}") + .getGeometry()); + Polygon densified = (Polygon) (densify.execute(polygon, 1.0, null)); + Envelope envelope = (Envelope) (importFromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":10}") + .getGeometry()); + wiggleGeometry(densified, 0.00000001, 1982); + wiggleGeometry(envelope, 0.00000001, 511); + assertTrue(!equals.execute(envelope, densified, sr, null)); + assertTrue(!contains.execute(densified, envelope, sr, null)); // envelope + // sticks + // outside + // of + // polygon, + // but + // the + // envelopes + // are + // equal + assertTrue(!disjoint.execute(densified, envelope, sr, null)); + assertTrue(!touches.execute(envelope, densified, sr, null)); + assertTrue(!overlaps.execute(envelope, densified, sr, null)); + assertTrue(!crosses.execute(envelope, densified, sr, null)); + } + + { + Polygon polygon = (Polygon) (importFromJson("{\"rings\":[[[0,0],[0,5],[0,10],[10,0],[0,0]]]}") + .getGeometry()); + Polygon densified = (Polygon) (densify.execute(polygon, 1.0, null)); + Envelope envelope = (Envelope) (importFromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":15,\"ymax\":10}") + .getGeometry()); + wiggleGeometry(densified, 0.00000001, 1982); + wiggleGeometry(envelope, 0.00000001, 511); + assertTrue(!equals.execute(envelope, densified, sr, null)); + assertTrue(!contains.execute(densified, envelope, sr, null)); // the + // polygon + // envelope + // doesn't + // contain + // the + // envelope, + // but + // they + // intersect + assertTrue(!disjoint.execute(densified, envelope, sr, null)); + assertTrue(!touches.execute(envelope, densified, sr, null)); + assertTrue(!overlaps.execute(envelope, densified, sr, null)); + assertTrue(!crosses.execute(envelope, densified, sr, null)); + } + + { + Polygon polygon = (Polygon) (importFromJson("{\"rings\":[[[0,0],[0,5],[0,10],[10,10],[10,0],[0,0]]]}") + .getGeometry()); + Polygon densified = (Polygon) (densify.execute(polygon, 1.0, null)); + Envelope envelope = (Envelope) (importFromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":0,\"ymax\":0}") + .getGeometry()); + wiggleGeometry(densified, 0.00000001, 1982); + wiggleGeometry(envelope, 0.00000001, 511); + assertTrue(!equals.execute(envelope, densified, sr, null)); + assertTrue(!contains.execute(densified, envelope, sr, null)); // envelope + // degenerate + // to + // a + // point + // and + // is + // on + // border + // (i.e. + // touches) + assertTrue(!disjoint.execute(densified, envelope, sr, null)); + assertTrue(touches.execute(envelope, densified, sr, null)); + assertTrue(!overlaps.execute(envelope, densified, sr, null)); + assertTrue(!crosses.execute(envelope, densified, sr, null)); + } + + { + Polygon polygon = (Polygon) (importFromJson("{\"rings\":[[[0,0],[0,5],[0,10],[10,10],[10,0],[0,0]]]}") + .getGeometry()); + Polygon densified = (Polygon) (densify.execute(polygon, 1.0, null)); + Envelope envelope = (Envelope) (importFromJson("{\"xmin\":1,\"ymin\":1,\"xmax\":1,\"ymax\":1}") + .getGeometry()); + wiggleGeometry(densified, 0.00000001, 1982); + wiggleGeometry(envelope, 0.00000001, 511); + assertTrue(!equals.execute(envelope, densified, sr, null)); + assertTrue(contains.execute(densified, envelope, sr, null)); // envelope + // degenerate + // to + // a + // point + // and + // is + // properly + // inside + assertTrue(!disjoint.execute(densified, envelope, sr, null)); + assertTrue(!touches.execute(envelope, densified, sr, null)); + assertTrue(!overlaps.execute(envelope, densified, sr, null)); + assertTrue(!crosses.execute(envelope, densified, sr, null)); + } + + { + Polygon polygon = (Polygon) (importFromJson("{\"rings\":[[[0,0],[0,5],[0,10],[10,10],[10,0],[0,0]]]}") + .getGeometry()); + Polygon densified = (Polygon) (densify.execute(polygon, 1.0, null)); + Envelope envelope = (Envelope) (importFromJson("{\"xmin\":-1,\"ymin\":-1,\"xmax\":-1,\"ymax\":-1}") + .getGeometry()); + wiggleGeometry(densified, 0.00000001, 1982); + wiggleGeometry(envelope, 0.00000001, 511); + assertTrue(!equals.execute(envelope, densified, sr, null)); + assertTrue(!contains.execute(densified, envelope, sr, null)); // envelope + // degenerate + // to + // a + // point + // and + // is + // properly + // outside + assertTrue(disjoint.execute(densified, envelope, sr, null)); + assertTrue(!touches.execute(envelope, densified, sr, null)); + assertTrue(!overlaps.execute(envelope, densified, sr, null)); + assertTrue(!crosses.execute(envelope, densified, sr, null)); + } + + { + Polygon polygon = (Polygon) (importFromJson("{\"rings\":[[[0,0],[0,5],[0,10],[10,10],[10,0],[0,0]]]}") + .getGeometry()); + Polygon densified = (Polygon) (densify.execute(polygon, 1.0, null)); + Envelope envelope = (Envelope) (importFromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":1,\"ymax\":0}") + .getGeometry()); + wiggleGeometry(densified, 0.00000001, 1982); + wiggleGeometry(envelope, 0.00000001, 511); + assertTrue(!equals.execute(envelope, densified, sr, null)); + assertTrue(!contains.execute(densified, envelope, sr, null)); // envelope + // degenerate + // to + // a + // line + // and + // rides + // the + // bottom + // of + // the + // polygon + // (no + // interior + // intersection) + assertTrue(!disjoint.execute(densified, envelope, sr, null)); + assertTrue(touches.execute(envelope, densified, sr, null)); + assertTrue(!overlaps.execute(envelope, densified, sr, null)); + assertTrue(!crosses.execute(envelope, densified, sr, null)); + } + + { + Polygon polygon = (Polygon) (importFromJson("{\"rings\":[[[0,0],[0,5],[0,10],[10,10],[10,0],[0,0]]]}") + .getGeometry()); + Polygon densified = (Polygon) (densify.execute(polygon, 1.0, null)); + Envelope envelope = (Envelope) (importFromJson("{\"xmin\":0,\"ymin\":1,\"xmax\":1,\"ymax\":1}") + .getGeometry()); + wiggleGeometry(densified, 0.00000001, 1982); + wiggleGeometry(envelope, 0.00000001, 511); + assertTrue(!equals.execute(envelope, densified, sr, null)); + assertTrue(contains.execute(densified, envelope, sr, null)); // envelope + // degenerate + // to + // a + // line, + // touches + // the + // border + // on + // the + // inside + // yet + // has + // interior + // intersection + assertTrue(!disjoint.execute(densified, envelope, sr, null)); + assertTrue(!touches.execute(envelope, densified, sr, null)); + assertTrue(!overlaps.execute(envelope, densified, sr, null)); + assertTrue(!crosses.execute(envelope, densified, sr, null)); + } + + { + Polygon polygon = (Polygon) (importFromJson("{\"rings\":[[[0,0],[0,5],[0,10],[10,0],[0,0]]]}") + .getGeometry()); + Polygon densified = (Polygon) (densify.execute(polygon, 1.0, null)); + Envelope envelope = (Envelope) (importFromJson("{\"xmin\":5,\"ymin\":5,\"xmax\":6,\"ymax\":5}") + .getGeometry()); + wiggleGeometry(densified, 0.00000001, 1982); + wiggleGeometry(envelope, 0.00000001, 511); + assertTrue(!equals.execute(envelope, densified, sr, null)); + assertTrue(!contains.execute(densified, envelope, sr, null)); // envelope + // degenerate + // to + // a + // line, + // touches + // the + // boundary, + // and + // is + // outside + assertTrue(!disjoint.execute(densified, envelope, sr, null)); + assertTrue(touches.execute(envelope, densified, sr, null)); + assertTrue(!overlaps.execute(envelope, densified, sr, null)); + assertTrue(!crosses.execute(envelope, densified, sr, null)); + } + + { + Polygon polygon = (Polygon) (importFromJson("{\"rings\":[[[0,0],[0,5],[0,10],[10,0],[0,0]]]}") + .getGeometry()); + Polygon densified = (Polygon) (densify.execute(polygon, 1.0, null)); + Envelope envelope = (Envelope) (importFromJson("{\"xmin\":6,\"ymin\":5,\"xmax\":7,\"ymax\":5}") + .getGeometry()); + wiggleGeometry(densified, 0.00000001, 1982); + wiggleGeometry(envelope, 0.00000001, 511); + assertTrue(!equals.execute(envelope, densified, sr, null)); + assertTrue(!contains.execute(densified, envelope, sr, null)); // envelope + // degenerate + // to + // a + // line, + // and + // is + // outside + assertTrue(disjoint.execute(densified, envelope, sr, null)); + assertTrue(!touches.execute(envelope, densified, sr, null)); + assertTrue(!overlaps.execute(envelope, densified, sr, null)); + assertTrue(!crosses.execute(envelope, densified, sr, null)); + } + + { + Polygon polygon = (Polygon) (importFromJson("{\"rings\":[[[0,0],[0,5],[0,10],[10,0],[0,0]]]}") + .getGeometry()); + Polygon densified = (Polygon) (densify.execute(polygon, 1.0, null)); + Envelope envelope = (Envelope) (importFromJson("{\"xmin\":4,\"ymin\":5,\"xmax\":7,\"ymax\":5}") + .getGeometry()); + wiggleGeometry(densified, 0.00000001, 1982); + wiggleGeometry(envelope, 0.00000001, 511); + assertTrue(!equals.execute(envelope, densified, sr, null)); + assertTrue(!contains.execute(densified, envelope, sr, null)); // envelope + // degenerate + // to + // a + // line, + // and + // crosses + // polygon + assertTrue(!disjoint.execute(densified, envelope, sr, null)); + assertTrue(!touches.execute(envelope, densified, sr, null)); + assertTrue(!overlaps.execute(envelope, densified, sr, null)); + assertTrue(crosses.execute(envelope, densified, sr, null)); + } + } + + @Test + public static void testPolylineEnvelope() { + OperatorEquals equals = (OperatorEquals) (OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Equals)); + OperatorContains contains = (OperatorContains) (OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Contains)); + OperatorDisjoint disjoint = (OperatorDisjoint) (OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Disjoint)); + OperatorCrosses crosses = (OperatorCrosses) (OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Crosses)); + @SuppressWarnings("unused") + OperatorWithin within = (OperatorWithin) (OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Within)); + OperatorOverlaps overlaps = (OperatorOverlaps) (OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Overlaps)); + OperatorTouches touches = (OperatorTouches) (OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Touches)); + OperatorDensifyByLength densify = (OperatorDensifyByLength) (OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.DensifyByLength)); + + SpatialReference sr = SpatialReference.create(4326); + + { + Polyline polyline = (Polyline) (importFromJson("{\"paths\":[[[0,0],[0,5],[0,10],[10,10],[10,0]]]}") + .getGeometry()); + Polyline densified = (Polyline) (densify.execute(polyline, 1.0, + null)); + Envelope envelope = (Envelope) (importFromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":10}") + .getGeometry()); + wiggleGeometry(densified, 0.00000001, 1982); + wiggleGeometry(envelope, 0.00000001, 511); + assertTrue(!equals.execute(envelope, densified, sr, null)); // polyline + // straddles + // the + // envelope + // like + // a hat + assertTrue(!contains.execute(densified, envelope, sr, null)); + assertTrue(!disjoint.execute(envelope, densified, sr, null)); + assertTrue(touches.execute(envelope, densified, sr, null)); + assertTrue(!overlaps.execute(envelope, densified, sr, null)); + assertTrue(!crosses.execute(envelope, densified, sr, null)); + } + + { + Polyline polyline = (Polyline) (importFromJson("{\"paths\":[[[-10,0],[0,10]]]}") + .getGeometry()); + Polyline densified = (Polyline) (densify.execute(polyline, 1.0, + null)); + Envelope envelope = (Envelope) (importFromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":10}") + .getGeometry()); + wiggleGeometry(densified, 0.00000001, 1982); + wiggleGeometry(envelope, 0.00000001, 511); + assertTrue(!equals.execute(envelope, densified, sr, null)); + assertTrue(!contains.execute(densified, envelope, sr, null)); + assertTrue(!disjoint.execute(envelope, densified, sr, null)); + assertTrue(touches.execute(envelope, densified, sr, null)); + assertTrue(touches.execute(densified, envelope, sr, null)); + assertTrue(!overlaps.execute(envelope, densified, sr, null)); + assertTrue(!crosses.execute(envelope, densified, sr, null)); + } + + { + Polyline polyline = (Polyline) (importFromJson("{\"paths\":[[[-11,0],[1,12]]]}") + .getGeometry()); + Polyline densified = (Polyline) (densify.execute(polyline, 1.0, + null)); + Envelope envelope = (Envelope) (importFromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":10}") + .getGeometry()); + wiggleGeometry(densified, 0.00000001, 1982); + wiggleGeometry(envelope, 0.00000001, 511); + assertTrue(!equals.execute(envelope, densified, sr, null)); + assertTrue(!contains.execute(densified, envelope, sr, null)); + assertTrue(disjoint.execute(envelope, densified, sr, null)); + assertTrue(!touches.execute(envelope, densified, sr, null)); + assertTrue(!touches.execute(densified, envelope, sr, null)); + assertTrue(!overlaps.execute(envelope, densified, sr, null)); + assertTrue(!crosses.execute(envelope, densified, sr, null)); + } + + { + Polyline polyline = (Polyline) (importFromJson("{\"paths\":[[[5,5],[6,6]]]}") + .getGeometry()); + Polyline densified = (Polyline) (densify.execute(polyline, 1.0, + null)); + Envelope envelope = (Envelope) (importFromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":10}") + .getGeometry()); + wiggleGeometry(densified, 0.00000001, 1982); + wiggleGeometry(envelope, 0.00000001, 511); + assertTrue(!equals.execute(envelope, densified, sr, null)); // polyline + // properly + // inside + assertTrue(contains.execute(envelope, densified, sr, null)); + assertTrue(!disjoint.execute(envelope, densified, sr, null)); + assertTrue(!touches.execute(envelope, densified, sr, null)); + assertTrue(!overlaps.execute(envelope, densified, sr, null)); + assertTrue(!crosses.execute(envelope, densified, sr, null)); + } + + { + Polyline polyline = (Polyline) (importFromJson("{\"paths\":[[[5,5],[10,10]]]}") + .getGeometry()); + Polyline densified = (Polyline) (densify.execute(polyline, 1.0, + null)); + Envelope envelope = (Envelope) (importFromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":10}") + .getGeometry()); + wiggleGeometry(densified, 0.00000001, 1982); + wiggleGeometry(envelope, 0.00000001, 511); + assertTrue(!equals.execute(envelope, densified, sr, null)); + assertTrue(contains.execute(envelope, densified, sr, null)); + assertTrue(!disjoint.execute(envelope, densified, sr, null)); + assertTrue(!touches.execute(envelope, densified, sr, null)); + assertTrue(!overlaps.execute(envelope, densified, sr, null)); + assertTrue(!crosses.execute(envelope, densified, sr, null)); + } + + { + Polyline polyline = (Polyline) (importFromJson("{\"paths\":[[[-5,5],[15,5]]]}") + .getGeometry()); + Polyline densified = (Polyline) (densify.execute(polyline, 1.0, + null)); + Envelope envelope = (Envelope) (importFromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":10}") + .getGeometry()); + wiggleGeometry(densified, 0.00000001, 1982); + wiggleGeometry(envelope, 0.00000001, 511); + assertTrue(!equals.execute(envelope, densified, sr, null)); + assertTrue(!contains.execute(envelope, densified, sr, null)); + assertTrue(!disjoint.execute(envelope, densified, sr, null)); + assertTrue(!touches.execute(envelope, densified, sr, null)); + assertTrue(!overlaps.execute(envelope, densified, sr, null)); + assertTrue(crosses.execute(envelope, densified, sr, null)); + assertTrue(crosses.execute(densified, envelope, sr, null)); + } + + { + Polyline polyline = (Polyline) (importFromJson("{\"paths\":[[[5,5],[5,15]]]}") + .getGeometry()); + Polyline densified = (Polyline) (densify.execute(polyline, 1.0, + null)); + Envelope envelope = (Envelope) (importFromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":10}") + .getGeometry()); + wiggleGeometry(densified, 0.00000001, 1982); + wiggleGeometry(envelope, 0.00000001, 511); + assertTrue(!equals.execute(envelope, densified, sr, null)); // polyline + // slices + // through + // the + // envelope + // (interior + // and + // exterior + // intersection) + assertTrue(!contains.execute(densified, envelope, sr, null)); + assertTrue(!disjoint.execute(envelope, densified, sr, null)); + assertTrue(!touches.execute(envelope, densified, sr, null)); + assertTrue(!overlaps.execute(envelope, densified, sr, null)); + assertTrue(crosses.execute(envelope, densified, sr, null)); + } + + { + Polyline polyline = (Polyline) (importFromJson("{\"paths\":[[[5,11],[5,15]]]}") + .getGeometry()); + Polyline densified = (Polyline) (densify.execute(polyline, 1.0, + null)); + Envelope envelope = (Envelope) (importFromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":10}") + .getGeometry()); + wiggleGeometry(densified, 0.00000001, 1982); + wiggleGeometry(envelope, 0.00000001, 511); + assertTrue(!equals.execute(envelope, densified, sr, null)); // polyline + // outside + // of + // envelope + assertTrue(!contains.execute(densified, envelope, sr, null)); + assertTrue(disjoint.execute(envelope, densified, sr, null)); + assertTrue(!touches.execute(envelope, densified, sr, null)); + assertTrue(!overlaps.execute(envelope, densified, sr, null)); + assertTrue(!crosses.execute(envelope, densified, sr, null)); + } + + { + Polyline polyline = (Polyline) (importFromJson("{\"paths\":[[[0,0],[0,5],[0,10],[10,10],[10,0]]]}") + .getGeometry()); + Polyline densified = (Polyline) (densify.execute(polyline, 1.0, + null)); + Envelope envelope = (Envelope) (importFromJson("{\"xmin\":0,\"ymin\":10,\"xmax\":10,\"ymax\":10}") + .getGeometry()); + wiggleGeometry(densified, 0.00000001, 1982); + wiggleGeometry(envelope, 0.00000001, 511); + assertTrue(!equals.execute(envelope, densified, sr, null)); // polyline + // straddles + // the + // degenerate + // envelope + // like + // a hat + assertTrue(contains.execute(densified, envelope, sr, null)); + assertTrue(!disjoint.execute(envelope, densified, sr, null)); + assertTrue(!touches.execute(envelope, densified, sr, null)); + assertTrue(!overlaps.execute(envelope, densified, sr, null)); + assertTrue(!crosses.execute(envelope, densified, sr, null)); + } + + { + Polyline polyline = (Polyline) (importFromJson("{\"paths\":[[[0,0],[0,5],[0,10],[10,10],[10,0]]]}") + .getGeometry()); + Polyline densified = (Polyline) (densify.execute(polyline, 1.0, + null)); + Envelope envelope = (Envelope) (importFromJson("{\"xmin\":0,\"ymin\":-5,\"xmax\":0,\"ymax\":10}") + .getGeometry()); + wiggleGeometry(densified, 0.00000001, 1982); + wiggleGeometry(envelope, 0.00000001, 511); + assertTrue(!equals.execute(envelope, densified, sr, null)); + assertTrue(!contains.execute(densified, envelope, sr, null)); + assertTrue(!disjoint.execute(envelope, densified, sr, null)); + assertTrue(!touches.execute(envelope, densified, sr, null)); + assertTrue(overlaps.execute(envelope, densified, sr, null)); + assertTrue(!crosses.execute(envelope, densified, sr, null)); + } + + { + Polyline polyline = (Polyline) (importFromJson("{\"paths\":[[[0,0],[0,5],[0,10],[10,10],[10,0]]]}") + .getGeometry()); + Polyline densified = (Polyline) (densify.execute(polyline, 1.0, + null)); + Envelope envelope = (Envelope) (importFromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":0,\"ymax\":0}") + .getGeometry()); + wiggleGeometry(densified, 0.00000001, 511); + wiggleGeometry(envelope, 0.00000001, 1982); + assertTrue(!equals.execute(envelope, densified, sr, null)); // degenerate + // envelope + // is at + // the + // end + // point + // of + // polyline + assertTrue(!contains.execute(densified, envelope, sr, null)); + assertTrue(!disjoint.execute(envelope, densified, sr, null)); + assertTrue(touches.execute(envelope, densified, sr, null)); + assertTrue(!overlaps.execute(envelope, densified, sr, null)); + assertTrue(!crosses.execute(envelope, densified, sr, null)); + } + + { + Polyline polyline = (Polyline) (importFromJson("{\"paths\":[[[0,0],[0,5],[0,10],[10,10],[10,0]]]}") + .getGeometry()); + Polyline densified = (Polyline) (densify.execute(polyline, 1.0, + null)); + Envelope envelope = (Envelope) (importFromJson("{\"xmin\":0,\"ymin\":5,\"xmax\":0,\"ymax\":5}") + .getGeometry()); + wiggleGeometry(densified, 0.00000001, 1982); + wiggleGeometry(envelope, 0.00000001, 511); + assertTrue(!equals.execute(envelope, densified, sr, null)); // degenerate + // envelope + // is at + // the + // interior + // of + // polyline + assertTrue(contains.execute(densified, envelope, sr, null)); + assertTrue(!disjoint.execute(envelope, densified, sr, null)); + assertTrue(!touches.execute(envelope, densified, sr, null)); + assertTrue(!overlaps.execute(envelope, densified, sr, null)); + assertTrue(!crosses.execute(envelope, densified, sr, null)); + } + + { + Polyline polyline = (Polyline) (importFromJson("{\"paths\":[[[2,-2],[2,2]]]}") + .getGeometry()); + Polyline densified = (Polyline) (densify.execute(polyline, 1.0, + null)); + Envelope envelope = (Envelope) (importFromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":5,\"ymax\":0}") + .getGeometry()); + wiggleGeometry(densified, 0.00000001, 1982); + wiggleGeometry(envelope, 0.00000001, 511); + assertTrue(!equals.execute(envelope, densified, sr, null)); // degenerate + // envelope + // crosses + // polyline + assertTrue(!contains.execute(densified, envelope, sr, null)); + assertTrue(!disjoint.execute(envelope, densified, sr, null)); + assertTrue(!touches.execute(envelope, densified, sr, null)); + assertTrue(!overlaps.execute(envelope, densified, sr, null)); + assertTrue(crosses.execute(envelope, densified, sr, null)); + } + + { + Polyline polyline = (Polyline) (importFromJson("{\"paths\":[[[2,0],[2,2]]]}") + .getGeometry()); + Polyline densified = (Polyline) (densify.execute(polyline, 1.0, + null)); + Envelope envelope = (Envelope) (importFromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":5,\"ymax\":0}") + .getGeometry()); + wiggleGeometry(densified, 0.00000001, 1982); + wiggleGeometry(envelope, 0.00000001, 511); + assertTrue(!equals.execute(envelope, densified, sr, null)); // degenerate + // envelope + // crosses + // polyline + assertTrue(!contains.execute(densified, envelope, sr, null)); + assertTrue(!disjoint.execute(envelope, densified, sr, null)); + assertTrue(touches.execute(envelope, densified, sr, null)); + assertTrue(!overlaps.execute(envelope, densified, sr, null)); + assertTrue(!crosses.execute(envelope, densified, sr, null)); + } + + { + Polyline polyline = (Polyline) (importFromJson("{\"paths\":[[[2,0],[2,2]]]}") + .getGeometry()); + Polyline densified = (Polyline) (densify.execute(polyline, 1.0, + null)); + Envelope envelope = (Envelope) (importFromJson("{\"xmin\":2,\"ymin\":0,\"xmax\":2,\"ymax\":3}") + .getGeometry()); + wiggleGeometry(densified, 0.00000001, 1982); + wiggleGeometry(envelope, 0.00000001, 511); + assertTrue(!equals.execute(envelope, densified, sr, null)); // degenerate + // envelope + // contains + // polyline + assertTrue(!contains.execute(densified, envelope, sr, null)); + assertTrue(contains.execute(envelope, densified, sr, null)); + assertTrue(!disjoint.execute(envelope, densified, sr, null)); + assertTrue(!touches.execute(envelope, densified, sr, null)); + assertTrue(!overlaps.execute(envelope, densified, sr, null)); + assertTrue(!crosses.execute(envelope, densified, sr, null)); + } + + { + Polyline polyline = (Polyline) (importFromJson("{\"paths\":[[[5,5],[6,6]]]}") + .getGeometry()); + Polyline densified = (Polyline) (densify.execute(polyline, 1.0, + null)); + Envelope envelope = (Envelope) (importFromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":5,\"ymax\":5}") + .getGeometry()); + wiggleGeometry(densified, 0.00000001, 1982); + wiggleGeometry(envelope, 0.00000001, 511); + assertTrue(!equals.execute(envelope, densified, sr, null)); // polyline + // properly + // inside + assertTrue(!contains.execute(envelope, densified, sr, null)); + assertTrue(!disjoint.execute(envelope, densified, sr, null)); + assertTrue(touches.execute(envelope, densified, sr, null)); + assertTrue(!overlaps.execute(envelope, densified, sr, null)); + assertTrue(!crosses.execute(envelope, densified, sr, null)); + } + + { + Polyline polyline = (Polyline) (importFromJson("{\"paths\":[[[5,5],[5,10]]]}") + .getGeometry()); + Polyline densified = (Polyline) (densify.execute(polyline, 1.0, + null)); + Envelope envelope = (Envelope) (importFromJson("{\"xmin\":5,\"ymin\":5,\"xmax\":5,\"ymax\":10}") + .getGeometry()); + wiggleGeometry(densified, 0.00000001, 1982); + wiggleGeometry(envelope, 0.00000001, 511); + assertTrue(equals.execute(envelope, densified, sr, null)); // polyline + // properly + // inside + assertTrue(contains.execute(envelope, densified, sr, null)); + assertTrue(!disjoint.execute(envelope, densified, sr, null)); + assertTrue(!touches.execute(envelope, densified, sr, null)); + assertTrue(!overlaps.execute(envelope, densified, sr, null)); + assertTrue(!crosses.execute(envelope, densified, sr, null)); + } + } + + @Test + public static void testMultiPointEnvelope() { + OperatorEquals equals = (OperatorEquals) (OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Equals)); + OperatorContains contains = (OperatorContains) (OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Contains)); + OperatorDisjoint disjoint = (OperatorDisjoint) (OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Disjoint)); + OperatorCrosses crosses = (OperatorCrosses) (OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Crosses)); + @SuppressWarnings("unused") + OperatorWithin within = (OperatorWithin) (OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Within)); + OperatorOverlaps overlaps = (OperatorOverlaps) (OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Overlaps)); + OperatorTouches touches = (OperatorTouches) (OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Touches)); + @SuppressWarnings("unused") + OperatorDensifyByLength densify = (OperatorDensifyByLength) (OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.DensifyByLength)); + + SpatialReference sr = SpatialReference.create(4326); + + { + MultiPoint multi_point = (MultiPoint) (importFromJson("{\"points\":[[0,0],[0,10],[10,10],[10,0]]}") + .getGeometry()); + Envelope envelope = (Envelope) (importFromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":10}") + .getGeometry()); + wiggleGeometry(multi_point, 0.00000001, 1982); + wiggleGeometry(envelope, 0.00000001, 511); + assertTrue(!equals.execute(envelope, multi_point, sr, null)); // all + // points + // on + // boundary + assertTrue(!contains.execute(envelope, multi_point, sr, null)); + assertTrue(!disjoint.execute(envelope, multi_point, sr, null)); + assertTrue(touches.execute(envelope, multi_point, sr, null)); + assertTrue(!overlaps.execute(envelope, multi_point, sr, null)); + assertTrue(!crosses.execute(envelope, multi_point, sr, null)); + } + + { + MultiPoint multi_point = (MultiPoint) (importFromJson("{\"points\":[[0,0],[0,10],[10,10],[5,5]]}") + .getGeometry()); + Envelope envelope = (Envelope) (importFromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":10}") + .getGeometry()); + wiggleGeometry(multi_point, 0.00000001, 1982); + wiggleGeometry(envelope, 0.00000001, 511); + assertTrue(!equals.execute(envelope, multi_point, sr, null)); // points + // on + // boundary + // and + // one + // point + // in + // interior + assertTrue(contains.execute(envelope, multi_point, sr, null)); + assertTrue(!disjoint.execute(envelope, multi_point, sr, null)); + assertTrue(!touches.execute(envelope, multi_point, sr, null)); + assertTrue(!overlaps.execute(envelope, multi_point, sr, null)); + assertTrue(!crosses.execute(envelope, multi_point, sr, null)); + } + + { + MultiPoint multi_point = (MultiPoint) (importFromJson("{\"points\":[[0,0],[0,10],[10,10],[5,5],[15,15]]}") + .getGeometry()); + Envelope envelope = (Envelope) (importFromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":10}") + .getGeometry()); + wiggleGeometry(multi_point, 0.00000001, 1982); + wiggleGeometry(envelope, 0.00000001, 511); + assertTrue(!equals.execute(envelope, multi_point, sr, null)); // points + // on + // boundary, + // one + // interior, + // one + // exterior + assertTrue(!contains.execute(envelope, multi_point, sr, null)); + assertTrue(!disjoint.execute(envelope, multi_point, sr, null)); + assertTrue(!touches.execute(envelope, multi_point, sr, null)); + assertTrue(!overlaps.execute(envelope, multi_point, sr, null)); + assertTrue(crosses.execute(envelope, multi_point, sr, null)); + } + + { + MultiPoint multi_point = (MultiPoint) (importFromJson("{\"points\":[[0,0],[0,10],[10,10],[15,15]]}") + .getGeometry()); + Envelope envelope = (Envelope) (importFromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":10}") + .getGeometry()); + wiggleGeometry(multi_point, 0.00000001, 1982); + wiggleGeometry(envelope, 0.00000001, 511); + assertTrue(!equals.execute(envelope, multi_point, sr, null)); // points + // on + // boundary, + // one + // exterior + assertTrue(!contains.execute(envelope, multi_point, sr, null)); + assertTrue(!disjoint.execute(envelope, multi_point, sr, null)); + assertTrue(touches.execute(envelope, multi_point, sr, null)); + assertTrue(!overlaps.execute(envelope, multi_point, sr, null)); + assertTrue(!crosses.execute(envelope, multi_point, sr, null)); + } + + { + MultiPoint multi_point = (MultiPoint) (importFromJson("{\"points\":[[0,-1],[0,11],[11,11],[15,15]]}") + .getGeometry()); + Envelope envelope = (Envelope) (importFromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":10}") + .getGeometry()); + wiggleGeometry(multi_point, 0.00000001, 1982); + wiggleGeometry(envelope, 0.00000001, 511); + assertTrue(!equals.execute(envelope, multi_point, sr, null)); // all + // points + // exterior + assertTrue(!contains.execute(envelope, multi_point, sr, null)); + assertTrue(disjoint.execute(envelope, multi_point, sr, null)); + assertTrue(!touches.execute(envelope, multi_point, sr, null)); + assertTrue(!overlaps.execute(envelope, multi_point, sr, null)); + assertTrue(!crosses.execute(envelope, multi_point, sr, null)); + } + + { + MultiPoint multi_point = (MultiPoint) (importFromJson("{\"points\":[[0,0],[0,10],[10,10],[10,0]]}") + .getGeometry()); + Envelope envelope = (Envelope) (importFromJson("{\"xmin\":0,\"ymin\":10,\"xmax\":10,\"ymax\":10}") + .getGeometry()); + wiggleGeometry(multi_point, 0.00000001, 1982); + wiggleGeometry(envelope, 0.00000001, 511); + assertTrue(!equals.execute(envelope, multi_point, sr, null)); // degenerate + // envelope + // slices + // through + // some + // points, + // but + // some + // points + // are + // off + // the + // line + assertTrue(!contains.execute(envelope, multi_point, sr, null)); + assertTrue(!disjoint.execute(envelope, multi_point, sr, null)); + assertTrue(touches.execute(envelope, multi_point, sr, null)); + assertTrue(!overlaps.execute(envelope, multi_point, sr, null)); + assertTrue(!crosses.execute(envelope, multi_point, sr, null)); + } + + { + MultiPoint multi_point = (MultiPoint) (importFromJson("{\"points\":[[0,0],[1,10],[10,10],[10,0]]}") + .getGeometry()); + Envelope envelope = (Envelope) (importFromJson("{\"xmin\":0,\"ymin\":10,\"xmax\":10,\"ymax\":10}") + .getGeometry()); + wiggleGeometry(multi_point, 0.00000001, 1982); + wiggleGeometry(envelope, 0.00000001, 511); + assertTrue(!equals.execute(envelope, multi_point, sr, null)); // degenerate + // envelope + // slices + // through + // some + // points, + // but + // some + // points + // are + // off + // the + // line + assertTrue(!contains.execute(envelope, multi_point, sr, null)); + assertTrue(!disjoint.execute(envelope, multi_point, sr, null)); + assertTrue(!touches.execute(envelope, multi_point, sr, null)); + assertTrue(!overlaps.execute(envelope, multi_point, sr, null)); + assertTrue(crosses.execute(envelope, multi_point, sr, null)); + } + + { + MultiPoint multi_point = (MultiPoint) (importFromJson("{\"points\":[[0,10],[10,10]]}") + .getGeometry()); + Envelope envelope = (Envelope) (importFromJson("{\"xmin\":0,\"ymin\":10,\"xmax\":10,\"ymax\":10}") + .getGeometry()); + wiggleGeometry(multi_point, 0.00000001, 1982); + wiggleGeometry(envelope, 0.00000001, 511); + assertTrue(!equals.execute(envelope, multi_point, sr, null)); // degenerate + // envelopes + // slices + // through + // all + // the + // points, + // and + // they + // are + // at + // the + // end + // points + // of + // the + // line + assertTrue(!contains.execute(envelope, multi_point, sr, null)); + assertTrue(!disjoint.execute(envelope, multi_point, sr, null)); + assertTrue(touches.execute(envelope, multi_point, sr, null)); + assertTrue(!overlaps.execute(envelope, multi_point, sr, null)); + assertTrue(!crosses.execute(envelope, multi_point, sr, null)); + } + + { + MultiPoint multi_point = (MultiPoint) (importFromJson("{\"points\":[[1,10],[9,10]]}") + .getGeometry()); + Envelope envelope = (Envelope) (importFromJson("{\"xmin\":0,\"ymin\":10,\"xmax\":10,\"ymax\":10}") + .getGeometry()); + wiggleGeometry(multi_point, 0.00000001, 1982); + wiggleGeometry(envelope, 0.00000001, 511); + assertTrue(!equals.execute(envelope, multi_point, sr, null)); // degenerate + // envelopes + // slices + // through + // all + // the + // points, + // and + // they + // are + // in + // the + // interior + // of + // the + // line + assertTrue(contains.execute(envelope, multi_point, sr, null)); + assertTrue(!disjoint.execute(envelope, multi_point, sr, null)); + assertTrue(!touches.execute(envelope, multi_point, sr, null)); + assertTrue(!overlaps.execute(envelope, multi_point, sr, null)); + assertTrue(!crosses.execute(envelope, multi_point, sr, null)); + } + + { + MultiPoint multi_point = (MultiPoint) (importFromJson("{\"points\":[[0,-1],[0,11],[11,11],[15,15]]}") + .getGeometry()); + Envelope envelope = (Envelope) (importFromJson("{\"xmin\":0,\"ymin\":10,\"xmax\":10,\"ymax\":10}") + .getGeometry()); + wiggleGeometry(multi_point, 0.00000001, 1982); + wiggleGeometry(envelope, 0.00000001, 511); + assertTrue(!equals.execute(envelope, multi_point, sr, null)); // all + // points + // exterior + assertTrue(!contains.execute(envelope, multi_point, sr, null)); + assertTrue(disjoint.execute(envelope, multi_point, sr, null)); + assertTrue(!touches.execute(envelope, multi_point, sr, null)); + assertTrue(!overlaps.execute(envelope, multi_point, sr, null)); + assertTrue(!crosses.execute(envelope, multi_point, sr, null)); + } + + { + MultiPoint multi_point = (MultiPoint) (importFromJson("{\"points\":[[0,-1],[0,11],[11,11],[15,15]]}") + .getGeometry()); + Envelope envelope = (Envelope) (importFromJson("{\"xmin\":10,\"ymin\":10,\"xmax\":10,\"ymax\":10}") + .getGeometry()); + wiggleGeometry(multi_point, 0.00000001, 1982); + wiggleGeometry(envelope, 0.00000001, 511); + assertTrue(!equals.execute(envelope, multi_point, sr, null)); // all + // points + // exterior + assertTrue(!contains.execute(envelope, multi_point, sr, null)); + assertTrue(disjoint.execute(envelope, multi_point, sr, null)); + assertTrue(!touches.execute(envelope, multi_point, sr, null)); + assertTrue(!overlaps.execute(envelope, multi_point, sr, null)); + assertTrue(!crosses.execute(envelope, multi_point, sr, null)); + } + + { + MultiPoint multi_point = (MultiPoint) (importFromJson("{\"points\":[[0,-1],[0,11],[11,11],[15,15]]}") + .getGeometry()); + Envelope envelope = (Envelope) (importFromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":11,\"ymax\":11}") + .getGeometry()); + wiggleGeometry(multi_point, 0.00000001, 1982); + wiggleGeometry(envelope, 0.00000001, 511); + assertTrue(!equals.execute(envelope, multi_point, sr, null)); // all + // points + // exterior + assertTrue(!contains.execute(multi_point, envelope, sr, null)); + assertTrue(!contains.execute(envelope, multi_point, sr, null)); + assertTrue(!disjoint.execute(envelope, multi_point, sr, null)); + assertTrue(touches.execute(envelope, multi_point, sr, null)); + assertTrue(!overlaps.execute(envelope, multi_point, sr, null)); + assertTrue(!crosses.execute(envelope, multi_point, sr, null)); + } + + { + MultiPoint multi_point = (MultiPoint) (importFromJson("{\"points\":[[0,-1],[0,-1]]}") + .getGeometry()); + Envelope envelope = (Envelope) (importFromJson("{\"xmin\":0,\"ymin\":-1,\"xmax\":0,\"ymax\":-1}") + .getGeometry()); + wiggleGeometry(multi_point, 0.00000001, 1982); + wiggleGeometry(envelope, 0.00000001, 511); + assertTrue(equals.execute(envelope, multi_point, sr, null)); // all + // points + // exterior + assertTrue(contains.execute(multi_point, envelope, sr, null)); + assertTrue(contains.execute(envelope, multi_point, sr, null)); + assertTrue(!disjoint.execute(envelope, multi_point, sr, null)); + assertTrue(!touches.execute(envelope, multi_point, sr, null)); + assertTrue(!overlaps.execute(envelope, multi_point, sr, null)); + assertTrue(!crosses.execute(envelope, multi_point, sr, null)); + } + } + + @Test + public static void testPointEnvelope() { + OperatorEquals equals = (OperatorEquals) (OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Equals)); + OperatorContains contains = (OperatorContains) (OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Contains)); + OperatorDisjoint disjoint = (OperatorDisjoint) (OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Disjoint)); + OperatorCrosses crosses = (OperatorCrosses) (OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Crosses)); + @SuppressWarnings("unused") + OperatorWithin within = (OperatorWithin) (OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Within)); + OperatorOverlaps overlaps = (OperatorOverlaps) (OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Overlaps)); + OperatorTouches touches = (OperatorTouches) (OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Touches)); + SpatialReference sr = SpatialReference.create(4326); + + { + Point point = (Point) (importFromJson("{\"x\":5,\"y\":6}") + .getGeometry()); + Envelope envelope = (Envelope) (importFromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":10}") + .getGeometry()); + wiggleGeometry(point, 0.00000001, 1982); + wiggleGeometry(envelope, 0.00000001, 511); + assertTrue(!equals.execute(envelope, point, sr, null)); + assertTrue(contains.execute(envelope, point, sr, null)); + assertTrue(!contains.execute(point, envelope, sr, null)); + assertTrue(!disjoint.execute(envelope, point, sr, null)); + assertTrue(!touches.execute(envelope, point, sr, null)); + assertTrue(!overlaps.execute(envelope, point, sr, null)); + assertTrue(!crosses.execute(envelope, point, sr, null)); + } + + { + Point point = (Point) (importFromJson("{\"x\":0,\"y\":10}") + .getGeometry()); + Envelope envelope = (Envelope) (importFromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":10}") + .getGeometry()); + wiggleGeometry(point, 0.00000001, 1982); + wiggleGeometry(envelope, 0.00000001, 511); + assertTrue(!equals.execute(envelope, point, sr, null)); + assertTrue(!contains.execute(envelope, point, sr, null)); + assertTrue(!contains.execute(point, envelope, sr, null)); + assertTrue(!disjoint.execute(envelope, point, sr, null)); + assertTrue(touches.execute(envelope, point, sr, null)); + assertTrue(!overlaps.execute(envelope, point, sr, null)); + assertTrue(!crosses.execute(envelope, point, sr, null)); + } + + { + Point point = (Point) (importFromJson("{\"x\":0,\"y\":11}") + .getGeometry()); + Envelope envelope = (Envelope) (importFromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":10}") + .getGeometry()); + wiggleGeometry(point, 0.00000001, 1982); + wiggleGeometry(envelope, 0.00000001, 511); + assertTrue(!equals.execute(envelope, point, sr, null)); + assertTrue(!contains.execute(envelope, point, sr, null)); + assertTrue(!contains.execute(point, envelope, sr, null)); + assertTrue(disjoint.execute(envelope, point, sr, null)); + assertTrue(!touches.execute(envelope, point, sr, null)); + assertTrue(!overlaps.execute(envelope, point, sr, null)); + assertTrue(!crosses.execute(envelope, point, sr, null)); + } + + { + Point point = (Point) (importFromJson("{\"x\":0,\"y\":0}") + .getGeometry()); + Envelope envelope = (Envelope) (importFromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":0}") + .getGeometry()); + wiggleGeometry(point, 0.00000001, 1982); + wiggleGeometry(envelope, 0.00000001, 511); + assertTrue(!equals.execute(envelope, point, sr, null)); + assertTrue(!contains.execute(envelope, point, sr, null)); + assertTrue(!contains.execute(point, envelope, sr, null)); + assertTrue(!disjoint.execute(envelope, point, sr, null)); + assertTrue(touches.execute(envelope, point, sr, null)); + assertTrue(!overlaps.execute(envelope, point, sr, null)); + assertTrue(!crosses.execute(envelope, point, sr, null)); + } + + { + Point point = (Point) (importFromJson("{\"x\":5,\"y\":0}") + .getGeometry()); + Envelope envelope = (Envelope) (importFromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":0}") + .getGeometry()); + wiggleGeometry(point, 0.00000001, 1982); + wiggleGeometry(envelope, 0.00000001, 511); + assertTrue(!equals.execute(envelope, point, sr, null)); + assertTrue(contains.execute(envelope, point, sr, null)); + assertTrue(!contains.execute(point, envelope, sr, null)); + assertTrue(!disjoint.execute(envelope, point, sr, null)); + assertTrue(!touches.execute(envelope, point, sr, null)); + assertTrue(!overlaps.execute(envelope, point, sr, null)); + assertTrue(!crosses.execute(envelope, point, sr, null)); + } + + { + Point point = (Point) (importFromJson("{\"x\":11,\"y\":0}") + .getGeometry()); + Envelope envelope = (Envelope) (importFromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":0}") + .getGeometry()); + wiggleGeometry(point, 0.00000001, 1982); + wiggleGeometry(envelope, 0.00000001, 511); + assertTrue(!equals.execute(envelope, point, sr, null)); + assertTrue(!contains.execute(envelope, point, sr, null)); + assertTrue(!contains.execute(point, envelope, sr, null)); + assertTrue(disjoint.execute(envelope, point, sr, null)); + assertTrue(!touches.execute(envelope, point, sr, null)); + assertTrue(!overlaps.execute(envelope, point, sr, null)); + assertTrue(!crosses.execute(envelope, point, sr, null)); + } + + { + Point point = (Point) (importFromJson("{\"x\":0,\"y\":0}") + .getGeometry()); + Envelope envelope = (Envelope) (importFromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":0,\"ymax\":0}") + .getGeometry()); + wiggleGeometry(point, 0.00000001, 1982); + wiggleGeometry(envelope, 0.00000001, 511); + assertTrue(equals.execute(envelope, point, sr, null)); + assertTrue(contains.execute(envelope, point, sr, null)); + assertTrue(contains.execute(point, envelope, sr, null)); + assertTrue(!disjoint.execute(envelope, point, sr, null)); + assertTrue(!touches.execute(envelope, point, sr, null)); + assertTrue(!overlaps.execute(envelope, point, sr, null)); + assertTrue(!crosses.execute(envelope, point, sr, null)); + } + } + + @Test + public static void testEnvelopeEnvelope() { + OperatorEquals equals = (OperatorEquals) (OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Equals)); + OperatorContains contains = (OperatorContains) (OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Contains)); + OperatorDisjoint disjoint = (OperatorDisjoint) (OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Disjoint)); + OperatorCrosses crosses = (OperatorCrosses) (OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Crosses)); + @SuppressWarnings("unused") + OperatorWithin within = (OperatorWithin) (OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Within)); + OperatorOverlaps overlaps = (OperatorOverlaps) (OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Overlaps)); + OperatorTouches touches = (OperatorTouches) (OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Touches)); + SpatialReference sr = SpatialReference.create(4326); + + { + Envelope env1 = (Envelope) (importFromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":10}") + .getGeometry()); + Envelope env2 = (Envelope) (importFromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":10}") + .getGeometry()); + wiggleGeometry(env1, 0.00000001, 1982); + wiggleGeometry(env2, 0.00000001, 511); + assertTrue(equals.execute(env1, env2, sr, null)); + assertTrue(contains.execute(env1, env2, sr, null)); + assertTrue(contains.execute(env2, env1, sr, null)); + assertTrue(!disjoint.execute(env1, env2, sr, null)); + assertTrue(!touches.execute(env1, env2, sr, null)); + assertTrue(!touches.execute(env2, env1, sr, null)); + assertTrue(!overlaps.execute(env1, env2, sr, null)); + assertTrue(!overlaps.execute(env2, env1, sr, null)); + assertTrue(!crosses.execute(env1, env2, sr, null)); + assertTrue(!crosses.execute(env2, env1, sr, null)); + } + + { + Envelope env1 = (Envelope) (importFromJson("{\"xmin\":5,\"ymin\":5,\"xmax\":10,\"ymax\":10}") + .getGeometry()); + Envelope env2 = (Envelope) (importFromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":10}") + .getGeometry()); + wiggleGeometry(env1, 0.00000001, 1982); + wiggleGeometry(env2, 0.00000001, 511); + assertTrue(!equals.execute(env1, env2, sr, null)); + assertTrue(!contains.execute(env1, env2, sr, null)); + assertTrue(contains.execute(env2, env1, sr, null)); + assertTrue(!disjoint.execute(env1, env2, sr, null)); + assertTrue(!touches.execute(env1, env2, sr, null)); + assertTrue(!overlaps.execute(env1, env2, sr, null)); + assertTrue(!overlaps.execute(env2, env1, sr, null)); + assertTrue(!crosses.execute(env1, env2, sr, null)); + assertTrue(!crosses.execute(env2, env1, sr, null)); + } + + { + Envelope env1 = (Envelope) (importFromJson("{\"xmin\":5,\"ymin\":5,\"xmax\":15,\"ymax\":15}") + .getGeometry()); + Envelope env2 = (Envelope) (importFromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":10}") + .getGeometry()); + wiggleGeometry(env1, 0.00000001, 1982); + wiggleGeometry(env2, 0.00000001, 511); + assertTrue(!equals.execute(env1, env2, sr, null)); + assertTrue(!contains.execute(env1, env2, sr, null)); + assertTrue(!contains.execute(env2, env1, sr, null)); + assertTrue(!disjoint.execute(env1, env2, sr, null)); + assertTrue(!touches.execute(env1, env2, sr, null)); + assertTrue(overlaps.execute(env1, env2, sr, null)); + assertTrue(overlaps.execute(env2, env1, sr, null)); + assertTrue(!crosses.execute(env1, env2, sr, null)); + assertTrue(!crosses.execute(env2, env1, sr, null)); + } + + { + Envelope env1 = (Envelope) (importFromJson("{\"xmin\":10,\"ymin\":0,\"xmax\":20,\"ymax\":10}") + .getGeometry()); + Envelope env2 = (Envelope) (importFromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":10}") + .getGeometry()); + wiggleGeometry(env1, 0.00000001, 1982); + wiggleGeometry(env2, 0.00000001, 511); + assertTrue(!equals.execute(env1, env2, sr, null)); + assertTrue(!contains.execute(env1, env2, sr, null)); + assertTrue(!contains.execute(env2, env1, sr, null)); + assertTrue(!disjoint.execute(env1, env2, sr, null)); + assertTrue(touches.execute(env1, env2, sr, null)); + assertTrue(!overlaps.execute(env1, env2, sr, null)); + assertTrue(!overlaps.execute(env2, env1, sr, null)); + assertTrue(!crosses.execute(env1, env2, sr, null)); + assertTrue(!crosses.execute(env2, env1, sr, null)); + } + + { + Envelope env1 = (Envelope) (importFromJson("{\"xmin\":10,\"ymin\":0,\"xmax\":20,\"ymax\":10}") + .getGeometry()); + Envelope env2 = (Envelope) (importFromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":10}") + .getGeometry()); + wiggleGeometry(env1, 0.00000001, 1982); + wiggleGeometry(env2, 0.00000001, 511); + assertTrue(!equals.execute(env1, env2, sr, null)); + assertTrue(!contains.execute(env1, env2, sr, null)); + assertTrue(!contains.execute(env2, env1, sr, null)); + assertTrue(!disjoint.execute(env1, env2, sr, null)); + assertTrue(touches.execute(env1, env2, sr, null)); + assertTrue(!overlaps.execute(env1, env2, sr, null)); + assertTrue(!overlaps.execute(env2, env1, sr, null)); + assertTrue(!crosses.execute(env1, env2, sr, null)); + assertTrue(!crosses.execute(env2, env1, sr, null)); + } + + { + Envelope env1 = (Envelope) (importFromJson("{\"xmin\":10,\"ymin\":0,\"xmax\":20,\"ymax\":10}") + .getGeometry()); + Envelope env2 = (Envelope) (importFromJson("{\"xmin\":0,\"ymin\":10,\"xmax\":10,\"ymax\":20}") + .getGeometry()); + wiggleGeometry(env1, 0.00000001, 1982); + wiggleGeometry(env2, 0.00000001, 511); + assertTrue(!equals.execute(env1, env2, sr, null)); + assertTrue(!contains.execute(env1, env2, sr, null)); + assertTrue(!contains.execute(env2, env1, sr, null)); + assertTrue(!disjoint.execute(env1, env2, sr, null)); + assertTrue(touches.execute(env1, env2, sr, null)); + assertTrue(!overlaps.execute(env1, env2, sr, null)); + assertTrue(!overlaps.execute(env2, env1, sr, null)); + assertTrue(!crosses.execute(env1, env2, sr, null)); + assertTrue(!crosses.execute(env2, env1, sr, null)); + } + + { + Envelope env1 = (Envelope) (importFromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":10}") + .getGeometry()); + Envelope env2 = (Envelope) (importFromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":0}") + .getGeometry()); + wiggleGeometry(env1, 0.00000001, 1982); + wiggleGeometry(env2, 0.00000001, 511); + assertTrue(!equals.execute(env1, env2, sr, null)); + assertTrue(!contains.execute(env1, env2, sr, null)); + assertTrue(!contains.execute(env2, env1, sr, null)); + assertTrue(!disjoint.execute(env1, env2, sr, null)); + assertTrue(touches.execute(env1, env2, sr, null)); + assertTrue(!overlaps.execute(env1, env2, sr, null)); + assertTrue(!overlaps.execute(env2, env1, sr, null)); + assertTrue(!crosses.execute(env1, env2, sr, null)); + assertTrue(!crosses.execute(env2, env1, sr, null)); + } + + { + Envelope env1 = (Envelope) (importFromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":10}") + .getGeometry()); + Envelope env2 = (Envelope) (importFromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":15,\"ymax\":0}") + .getGeometry()); + wiggleGeometry(env1, 0.00000001, 1982); + wiggleGeometry(env2, 0.00000001, 511); + assertTrue(!equals.execute(env1, env2, sr, null)); + assertTrue(!contains.execute(env1, env2, sr, null)); + assertTrue(!contains.execute(env2, env1, sr, null)); + assertTrue(!disjoint.execute(env1, env2, sr, null)); + assertTrue(touches.execute(env1, env2, sr, null)); + assertTrue(!overlaps.execute(env1, env2, sr, null)); + assertTrue(!overlaps.execute(env2, env1, sr, null)); + assertTrue(!crosses.execute(env1, env2, sr, null)); + assertTrue(!crosses.execute(env2, env1, sr, null)); + } + + { + Envelope env1 = (Envelope) (importFromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":10}") + .getGeometry()); + Envelope env2 = (Envelope) (importFromJson("{\"xmin\":-5,\"ymin\":5,\"xmax\":0,\"ymax\":5}") + .getGeometry()); + wiggleGeometry(env1, 0.00000001, 1982); + wiggleGeometry(env2, 0.00000001, 511); + assertTrue(!equals.execute(env1, env2, sr, null)); + assertTrue(!contains.execute(env1, env2, sr, null)); + assertTrue(!contains.execute(env2, env1, sr, null)); + assertTrue(!disjoint.execute(env1, env2, sr, null)); + assertTrue(touches.execute(env1, env2, sr, null)); + assertTrue(!overlaps.execute(env1, env2, sr, null)); + assertTrue(!overlaps.execute(env2, env1, sr, null)); + assertTrue(!crosses.execute(env1, env2, sr, null)); + assertTrue(!crosses.execute(env2, env1, sr, null)); + } + + { + Envelope env1 = (Envelope) (importFromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":10}") + .getGeometry()); + Envelope env2 = (Envelope) (importFromJson("{\"xmin\":-5,\"ymin\":5,\"xmax\":5,\"ymax\":5}") + .getGeometry()); + wiggleGeometry(env1, 0.00000001, 1982); + wiggleGeometry(env2, 0.00000001, 511); + assertTrue(!equals.execute(env1, env2, sr, null)); + assertTrue(!contains.execute(env1, env2, sr, null)); + assertTrue(!contains.execute(env2, env1, sr, null)); + assertTrue(!disjoint.execute(env1, env2, sr, null)); + assertTrue(!touches.execute(env1, env2, sr, null)); + assertTrue(!overlaps.execute(env1, env2, sr, null)); + assertTrue(!overlaps.execute(env2, env1, sr, null)); + assertTrue(crosses.execute(env1, env2, sr, null)); + assertTrue(crosses.execute(env2, env1, sr, null)); + } + + { + Envelope env1 = (Envelope) (importFromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":10}") + .getGeometry()); + Envelope env2 = (Envelope) (importFromJson("{\"xmin\":3,\"ymin\":5,\"xmax\":5,\"ymax\":5}") + .getGeometry()); + wiggleGeometry(env1, 0.00000001, 1982); + wiggleGeometry(env2, 0.00000001, 511); + assertTrue(!equals.execute(env1, env2, sr, null)); + assertTrue(contains.execute(env1, env2, sr, null)); + assertTrue(!contains.execute(env2, env1, sr, null)); + assertTrue(!disjoint.execute(env1, env2, sr, null)); + assertTrue(!touches.execute(env1, env2, sr, null)); + assertTrue(!overlaps.execute(env1, env2, sr, null)); + assertTrue(!overlaps.execute(env2, env1, sr, null)); + assertTrue(!crosses.execute(env1, env2, sr, null)); + assertTrue(!crosses.execute(env2, env1, sr, null)); + } + + { + Envelope env1 = (Envelope) (importFromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":10}") + .getGeometry()); + Envelope env2 = (Envelope) (importFromJson("{\"xmin\":3,\"ymin\":5,\"xmax\":10,\"ymax\":5}") + .getGeometry()); + wiggleGeometry(env1, 0.00000001, 1982); + wiggleGeometry(env2, 0.00000001, 511); + assertTrue(!equals.execute(env1, env2, sr, null)); + assertTrue(contains.execute(env1, env2, sr, null)); + assertTrue(!contains.execute(env2, env1, sr, null)); + assertTrue(!disjoint.execute(env1, env2, sr, null)); + assertTrue(!touches.execute(env1, env2, sr, null)); + assertTrue(!overlaps.execute(env1, env2, sr, null)); + assertTrue(!overlaps.execute(env2, env1, sr, null)); + assertTrue(!crosses.execute(env1, env2, sr, null)); + assertTrue(!crosses.execute(env2, env1, sr, null)); + } + + { + Envelope env1 = (Envelope) (importFromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":10}") + .getGeometry()); + Envelope env2 = (Envelope) (importFromJson("{\"xmin\":-5,\"ymin\":0,\"xmax\":0,\"ymax\":0}") + .getGeometry()); + wiggleGeometry(env1, 0.00000001, 1982); + wiggleGeometry(env2, 0.00000001, 511); + assertTrue(!equals.execute(env1, env2, sr, null)); + assertTrue(!contains.execute(env1, env2, sr, null)); + assertTrue(!contains.execute(env2, env1, sr, null)); + assertTrue(!disjoint.execute(env1, env2, sr, null)); + assertTrue(touches.execute(env1, env2, sr, null)); + assertTrue(!overlaps.execute(env1, env2, sr, null)); + assertTrue(!overlaps.execute(env2, env1, sr, null)); + assertTrue(!crosses.execute(env1, env2, sr, null)); + assertTrue(!crosses.execute(env2, env1, sr, null)); + } + + { + Envelope env1 = (Envelope) (importFromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":0}") + .getGeometry()); + Envelope env2 = (Envelope) (importFromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":0}") + .getGeometry()); + wiggleGeometry(env1, 0.00000001, 1982); + wiggleGeometry(env2, 0.00000001, 511); + assertTrue(equals.execute(env1, env2, sr, null)); + assertTrue(contains.execute(env1, env2, sr, null)); + assertTrue(contains.execute(env2, env1, sr, null)); + assertTrue(!disjoint.execute(env1, env2, sr, null)); + assertTrue(!touches.execute(env1, env2, sr, null)); + assertTrue(!overlaps.execute(env1, env2, sr, null)); + assertTrue(!overlaps.execute(env2, env1, sr, null)); + assertTrue(!crosses.execute(env1, env2, sr, null)); + assertTrue(!crosses.execute(env2, env1, sr, null)); + } + + { + Envelope env1 = (Envelope) (importFromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":15,\"ymax\":0}") + .getGeometry()); + Envelope env2 = (Envelope) (importFromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":0}") + .getGeometry()); + wiggleGeometry(env1, 0.00000001, 1982); + wiggleGeometry(env2, 0.00000001, 511); + assertTrue(!equals.execute(env1, env2, sr, null)); + assertTrue(contains.execute(env1, env2, sr, null)); + assertTrue(!contains.execute(env2, env1, sr, null)); + assertTrue(!disjoint.execute(env1, env2, sr, null)); + assertTrue(!touches.execute(env1, env2, sr, null)); + assertTrue(!overlaps.execute(env1, env2, sr, null)); + assertTrue(!overlaps.execute(env2, env1, sr, null)); + assertTrue(!crosses.execute(env1, env2, sr, null)); + assertTrue(!crosses.execute(env2, env1, sr, null)); + } + + { + Envelope env1 = (Envelope) (importFromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":15,\"ymax\":0}") + .getGeometry()); + Envelope env2 = (Envelope) (importFromJson("{\"xmin\":-5,\"ymin\":0,\"xmax\":10,\"ymax\":0}") + .getGeometry()); + wiggleGeometry(env1, 0.00000001, 1982); + wiggleGeometry(env2, 0.00000001, 511); + assertTrue(!equals.execute(env1, env2, sr, null)); + assertTrue(!contains.execute(env1, env2, sr, null)); + assertTrue(!contains.execute(env2, env1, sr, null)); + assertTrue(!disjoint.execute(env1, env2, sr, null)); + assertTrue(!touches.execute(env1, env2, sr, null)); + assertTrue(overlaps.execute(env1, env2, sr, null)); + assertTrue(overlaps.execute(env2, env1, sr, null)); + assertTrue(!crosses.execute(env1, env2, sr, null)); + assertTrue(!crosses.execute(env2, env1, sr, null)); + } + + { + Envelope env1 = (Envelope) (importFromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":5,\"ymax\":0}") + .getGeometry()); + Envelope env2 = (Envelope) (importFromJson("{\"xmin\":-5,\"ymin\":0,\"xmax\":0,\"ymax\":0}") + .getGeometry()); + wiggleGeometry(env1, 0.00000001, 1982); + wiggleGeometry(env2, 0.00000001, 511); + assertTrue(!equals.execute(env1, env2, sr, null)); + assertTrue(!contains.execute(env1, env2, sr, null)); + assertTrue(!contains.execute(env2, env1, sr, null)); + assertTrue(!disjoint.execute(env1, env2, sr, null)); + assertTrue(touches.execute(env1, env2, sr, null)); + assertTrue(!overlaps.execute(env1, env2, sr, null)); + assertTrue(!overlaps.execute(env2, env1, sr, null)); + assertTrue(!crosses.execute(env1, env2, sr, null)); + assertTrue(!crosses.execute(env2, env1, sr, null)); + } + + { + Envelope env1 = (Envelope) (importFromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":0}") + .getGeometry()); + Envelope env2 = (Envelope) (importFromJson("{\"xmin\":5,\"ymin\":-5,\"xmax\":5,\"ymax\":5}") + .getGeometry()); + wiggleGeometry(env1, 0.00000001, 1982); + wiggleGeometry(env2, 0.00000001, 511); + assertTrue(!equals.execute(env1, env2, sr, null)); + assertTrue(!contains.execute(env1, env2, sr, null)); + assertTrue(!contains.execute(env2, env1, sr, null)); + assertTrue(!disjoint.execute(env1, env2, sr, null)); + assertTrue(!touches.execute(env1, env2, sr, null)); + assertTrue(!overlaps.execute(env1, env2, sr, null)); + assertTrue(!overlaps.execute(env2, env1, sr, null)); + assertTrue(crosses.execute(env1, env2, sr, null)); + assertTrue(crosses.execute(env2, env1, sr, null)); + } + + { + Envelope env1 = (Envelope) (importFromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":0}") + .getGeometry()); + Envelope env2 = (Envelope) (importFromJson("{\"xmin\":10,\"ymin\":0,\"xmax\":20,\"ymax\":0}") + .getGeometry()); + wiggleGeometry(env1, 0.00000001, 1982); + wiggleGeometry(env2, 0.00000001, 511); + assertTrue(!equals.execute(env1, env2, sr, null)); + assertTrue(!contains.execute(env1, env2, sr, null)); + assertTrue(!contains.execute(env2, env1, sr, null)); + assertTrue(!disjoint.execute(env1, env2, sr, null)); + assertTrue(touches.execute(env1, env2, sr, null)); + assertTrue(!overlaps.execute(env1, env2, sr, null)); + assertTrue(!overlaps.execute(env2, env1, sr, null)); + assertTrue(!crosses.execute(env1, env2, sr, null)); + assertTrue(!crosses.execute(env2, env1, sr, null)); + } + + { + Envelope env1 = (Envelope) (importFromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":0}") + .getGeometry()); + Envelope env2 = (Envelope) (importFromJson("{\"xmin\":5,\"ymin\":0,\"xmax\":5,\"ymax\":5}") + .getGeometry()); + wiggleGeometry(env1, 0.00000001, 1982); + wiggleGeometry(env2, 0.00000001, 511); + assertTrue(!equals.execute(env1, env2, sr, null)); + assertTrue(!contains.execute(env1, env2, sr, null)); + assertTrue(!contains.execute(env2, env1, sr, null)); + assertTrue(!disjoint.execute(env1, env2, sr, null)); + assertTrue(touches.execute(env1, env2, sr, null)); + assertTrue(touches.execute(env2, env1, sr, null)); + assertTrue(!overlaps.execute(env1, env2, sr, null)); + assertTrue(!overlaps.execute(env2, env1, sr, null)); + assertTrue(!crosses.execute(env1, env2, sr, null)); + assertTrue(!crosses.execute(env2, env1, sr, null)); + } + + { + Envelope env1 = (Envelope) (importFromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":0,\"ymax\":0}") + .getGeometry()); + Envelope env2 = (Envelope) (importFromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":10}") + .getGeometry()); + wiggleGeometry(env1, 0.00000001, 1982); + wiggleGeometry(env2, 0.00000001, 511); + assertTrue(!equals.execute(env1, env2, sr, null)); + assertTrue(!contains.execute(env1, env2, sr, null)); + assertTrue(!contains.execute(env2, env1, sr, null)); + assertTrue(!disjoint.execute(env1, env2, sr, null)); + assertTrue(touches.execute(env1, env2, sr, null)); + assertTrue(touches.execute(env2, env1, sr, null)); + assertTrue(!overlaps.execute(env1, env2, sr, null)); + assertTrue(!overlaps.execute(env2, env1, sr, null)); + assertTrue(!crosses.execute(env1, env2, sr, null)); + assertTrue(!crosses.execute(env2, env1, sr, null)); + } + + { + Envelope env1 = (Envelope) (importFromJson("{\"xmin\":5,\"ymin\":0,\"xmax\":5,\"ymax\":0}") + .getGeometry()); + Envelope env2 = (Envelope) (importFromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":10}") + .getGeometry()); + wiggleGeometry(env1, 0.00000001, 1982); + wiggleGeometry(env2, 0.00000001, 511); + assertTrue(!equals.execute(env1, env2, sr, null)); + assertTrue(!contains.execute(env1, env2, sr, null)); + assertTrue(!contains.execute(env2, env1, sr, null)); + assertTrue(!disjoint.execute(env1, env2, sr, null)); + assertTrue(touches.execute(env1, env2, sr, null)); + assertTrue(touches.execute(env2, env1, sr, null)); + assertTrue(!overlaps.execute(env1, env2, sr, null)); + assertTrue(!overlaps.execute(env2, env1, sr, null)); + assertTrue(!crosses.execute(env1, env2, sr, null)); + assertTrue(!crosses.execute(env2, env1, sr, null)); + } + + { + Envelope env1 = (Envelope) (importFromJson("{\"xmin\":5,\"ymin\":5,\"xmax\":5,\"ymax\":5}") + .getGeometry()); + Envelope env2 = (Envelope) (importFromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":10}") + .getGeometry()); + wiggleGeometry(env1, 0.00000001, 1982); + wiggleGeometry(env2, 0.00000001, 511); + assertTrue(!equals.execute(env1, env2, sr, null)); + assertTrue(!contains.execute(env1, env2, sr, null)); + assertTrue(contains.execute(env2, env1, sr, null)); + assertTrue(!disjoint.execute(env1, env2, sr, null)); + assertTrue(!touches.execute(env1, env2, sr, null)); + assertTrue(!touches.execute(env2, env1, sr, null)); + assertTrue(!overlaps.execute(env1, env2, sr, null)); + assertTrue(!overlaps.execute(env2, env1, sr, null)); + assertTrue(!crosses.execute(env1, env2, sr, null)); + assertTrue(!crosses.execute(env2, env1, sr, null)); + } + + { + Envelope env1 = (Envelope) (importFromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":0,\"ymax\":0}") + .getGeometry()); + Envelope env2 = (Envelope) (importFromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":0}") + .getGeometry()); + wiggleGeometry(env1, 0.00000001, 1982); + wiggleGeometry(env2, 0.00000001, 511); + assertTrue(!equals.execute(env1, env2, sr, null)); + assertTrue(!contains.execute(env1, env2, sr, null)); + assertTrue(!contains.execute(env2, env1, sr, null)); + assertTrue(!disjoint.execute(env1, env2, sr, null)); + assertTrue(touches.execute(env1, env2, sr, null)); + assertTrue(touches.execute(env2, env1, sr, null)); + assertTrue(!overlaps.execute(env1, env2, sr, null)); + assertTrue(!overlaps.execute(env2, env1, sr, null)); + assertTrue(!crosses.execute(env1, env2, sr, null)); + assertTrue(!crosses.execute(env2, env1, sr, null)); + } + + { + Envelope env1 = (Envelope) (importFromJson("{\"xmin\":5,\"ymin\":0,\"xmax\":5,\"ymax\":0}") + .getGeometry()); + Envelope env2 = (Envelope) (importFromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":0}") + .getGeometry()); + wiggleGeometry(env1, 0.00000001, 1982); + wiggleGeometry(env2, 0.00000001, 511); + assertTrue(!equals.execute(env1, env2, sr, null)); + assertTrue(!contains.execute(env1, env2, sr, null)); + assertTrue(contains.execute(env2, env1, sr, null)); + assertTrue(!disjoint.execute(env1, env2, sr, null)); + assertTrue(!touches.execute(env1, env2, sr, null)); + assertTrue(!touches.execute(env2, env1, sr, null)); + assertTrue(!overlaps.execute(env1, env2, sr, null)); + assertTrue(!overlaps.execute(env2, env1, sr, null)); + assertTrue(!crosses.execute(env1, env2, sr, null)); + assertTrue(!crosses.execute(env2, env1, sr, null)); + } + + { + Envelope env1 = (Envelope) (importFromJson("{\"xmin\":5,\"ymin\":0,\"xmax\":5,\"ymax\":0}") + .getGeometry()); + Envelope env2 = (Envelope) (importFromJson("{\"xmin\":5,\"ymin\":0,\"xmax\":5,\"ymax\":0}") + .getGeometry()); + wiggleGeometry(env1, 0.00000001, 1982); + wiggleGeometry(env2, 0.00000001, 511); + assertTrue(equals.execute(env1, env2, sr, null)); + assertTrue(contains.execute(env1, env2, sr, null)); + assertTrue(contains.execute(env2, env1, sr, null)); + assertTrue(!disjoint.execute(env1, env2, sr, null)); + assertTrue(!touches.execute(env1, env2, sr, null)); + assertTrue(!touches.execute(env2, env1, sr, null)); + assertTrue(!overlaps.execute(env1, env2, sr, null)); + assertTrue(!overlaps.execute(env2, env1, sr, null)); + assertTrue(!crosses.execute(env1, env2, sr, null)); + assertTrue(!crosses.execute(env2, env1, sr, null)); + } + } + + static void wiggleGeometry(Geometry geometry, double tolerance, int rand) { + int type = geometry.getType().value(); + + if (type == Geometry.GeometryType.Polygon + || type == Geometry.GeometryType.Polyline + || type == Geometry.GeometryType.MultiPoint) { + MultiVertexGeometry mvGeom = (MultiVertexGeometry) geometry; + for (int i = 0; i < mvGeom.getPointCount(); i++) { + Point2D pt = mvGeom.getXY(i); + + // create random vector and normalize it to 0.49 * tolerance + Point2D randomV = new Point2D(); + rand = NumberUtils.nextRand(rand); + randomV.x = 1.0 * rand / NumberUtils.intMax() - 0.5; + rand = NumberUtils.nextRand(rand); + randomV.y = 1.0 * rand / NumberUtils.intMax() - 0.5; + randomV.normalize(); + randomV.scale(0.45 * tolerance); + pt.add(randomV); + mvGeom.setXY(i, pt); + } + } else if (type == Geometry.GeometryType.Point) { + Point ptGeom = (Point) (geometry); + Point2D pt = ptGeom.getXY(); + // create random vector and normalize it to 0.49 * tolerance + Point2D randomV = new Point2D(); + rand = NumberUtils.nextRand(rand); + randomV.x = 1.0 * rand / NumberUtils.intMax() - 0.5; + rand = NumberUtils.nextRand(rand); + randomV.y = 1.0 * rand / NumberUtils.intMax() - 0.5; + randomV.normalize(); + randomV.scale(0.45 * tolerance); + pt.add(randomV); + ptGeom.setXY(pt); + } else if (type == Geometry.GeometryType.Envelope) { + Envelope envGeom = (Envelope) (geometry); + Envelope2D env = new Envelope2D(); + envGeom.queryEnvelope2D(env); + double xmin, xmax, ymin, ymax; + Point2D pt = new Point2D(); + env.queryLowerLeft(pt); + // create random vector and normalize it to 0.49 * tolerance + Point2D randomV = new Point2D(); + rand = NumberUtils.nextRand(rand); + randomV.x = 1.0 * rand / NumberUtils.intMax() - 0.5; + rand = NumberUtils.nextRand(rand); + randomV.y = 1.0 * rand / NumberUtils.intMax() - 0.5; + randomV.normalize(); + randomV.scale(0.45 * tolerance); + xmin = (pt.x + randomV.x); + ymin = (pt.y + randomV.y); + + env.queryUpperRight(pt); + // create random vector and normalize it to 0.49 * tolerance + rand = NumberUtils.nextRand(rand); + randomV.x = 1.0 * rand / NumberUtils.intMax() - 0.5; + rand = NumberUtils.nextRand(rand); + randomV.y = 1.0 * rand / NumberUtils.intMax() - 0.5; + randomV.normalize(); + randomV.scale(0.45 * tolerance); + xmax = (pt.x + randomV.x); + ymax = (pt.y + randomV.y); + + if (xmin > xmax) { + double swap = xmin; + xmin = xmax; + xmax = swap; + } + + if (ymin > ymax) { + double swap = ymin; + ymin = ymax; + ymax = swap; + } + + envGeom.setCoords(xmin, ymin, xmax, ymax); + } + + } + + @Test + public static void testDisjointRelationFalse() { + { + OperatorDisjoint op = (OperatorDisjoint) (OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Disjoint)); + Envelope env1 = new Envelope(50, 50, 150, 150); + Envelope env2 = new Envelope(25, 25, 175, 175); + boolean result = op.execute(env1, env2, + SpatialReference.create(4326), null); + assertTrue(!result); + } + { + OperatorIntersects op = (OperatorIntersects) (OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Intersects)); + Envelope env1 = new Envelope(50, 50, 150, 150); + Envelope env2 = new Envelope(25, 25, 175, 175); + boolean result = op.execute(env1, env2, + SpatialReference.create(4326), null); + assertTrue(result); + } + { + OperatorContains op = (OperatorContains) (OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Contains)); + Envelope env1 = new Envelope(100, 175, 200, 225); + Polyline polyline = new Polyline(); + polyline.startPath(200, 175); + polyline.lineTo(200, 225); + polyline.lineTo(125, 200); + boolean result = op.execute(env1, polyline, + SpatialReference.create(4326), null); + assertTrue(result); + } + { + OperatorTouches op = (OperatorTouches) (OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Touches)); + Envelope env1 = new Envelope(100, 200, 400, 400); + Polyline polyline = new Polyline(); + polyline.startPath(300, 60); + polyline.lineTo(300, 200); + polyline.lineTo(400, 50); + boolean result = op.execute(env1, polyline, + SpatialReference.create(4326), null); + assertTrue(result); + } + + { + OperatorTouches op = (OperatorTouches) (OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Touches)); + Envelope env1 = new Envelope(50, 50, 150, 150); + Polyline polyline = new Polyline(); + polyline.startPath(100, 20); + polyline.lineTo(100, 50); + polyline.lineTo(150, 10); + boolean result = op.execute(polyline, env1, + SpatialReference.create(4326), null); + assertTrue(result); + } + + { + OperatorDisjoint op = (OperatorDisjoint) (OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Disjoint)); + Polygon polygon = new Polygon(); + Polyline polyline = new Polyline(); + polygon.startPath(0, 0); + polygon.lineTo(0, 10); + polygon.lineTo(10, 10); + polygon.lineTo(10, 0); + polyline.startPath(-5, 4); + polyline.lineTo(5, -6); + boolean result = op.execute(polyline, polygon, + SpatialReference.create(4326), null); + assertTrue(result); + } + } + + @Test + public static void testPolylinePolylineRelate() { + OperatorRelate op = (OperatorRelate) (OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Relate)); + SpatialReference sr = SpatialReference.create(4326); + boolean res; + String scl; + + Polyline polyline1 = new Polyline(); + Polyline polyline2 = new Polyline(); + + polyline1.startPath(0, 0); + polyline1.lineTo(1, 1); + + polyline2.startPath(1, 1); + polyline2.lineTo(2, 0); + + scl = "FF1FT01T2"; + res = op.execute(polyline1, polyline2, sr, scl, null); + assertTrue(res); + + scl = "****TF*T*"; + res = op.execute(polyline1, polyline2, sr, scl, null); + assertTrue(!res); + + scl = "****F****"; + res = op.execute(polyline1, polyline2, sr, scl, null); + assertTrue(!res); + + scl = "**1*0*T**"; + res = op.execute(polyline1, polyline2, sr, scl, null); + assertTrue(res); + + scl = "****1****"; + res = op.execute(polyline1, polyline2, sr, scl, null); + assertTrue(!res); + + scl = "**T*001*T"; + res = op.execute(polyline1, polyline2, sr, scl, null); + assertTrue(res); + + scl = "T********"; + res = op.execute(polyline1, polyline2, sr, scl, null); + assertTrue(!res); + + scl = "F********"; + res = op.execute(polyline1, polyline2, sr, scl, null); + assertTrue(res); + + polyline1.setEmpty(); + polyline2.setEmpty(); + + polyline1.startPath(0, 0); + polyline1.lineTo(1, 0); + + polyline2.startPath(0, 0); + polyline2.lineTo(1, 0); + + scl = "1FFFTFFFT"; + res = op.execute(polyline1, polyline2, sr, scl, null); + assertTrue(res); + + scl = "1*T*T****"; + res = op.execute(polyline1, polyline2, sr, scl, null); + assertTrue(!res); + + scl = "1T**T****"; + res = op.execute(polyline1, polyline2, sr, scl, null); + assertTrue(!res); + + polyline1.setEmpty(); + polyline2.setEmpty(); + + polyline1.startPath(0, 0); + polyline1.lineTo(0.5, 0.5); + polyline1.lineTo(1, 1); + + polyline2.startPath(1, 0); + polyline2.lineTo(0.5, 0.5); + polyline2.lineTo(0, 1); + + scl = "0F1FFTT0T"; + res = op.execute(polyline1, polyline2, sr, scl, null); + assertTrue(res); + + scl = "*T*******"; + res = op.execute(polyline1, polyline2, sr, scl, null); + assertTrue(!res); + + scl = "*F*F*****"; + res = op.execute(polyline1, polyline2, sr, scl, null); + assertTrue(res); + + polyline1.setEmpty(); + polyline2.setEmpty(); + + polyline1.startPath(0, 0); + polyline1.lineTo(1, 0); + + polyline2.startPath(1, -1); + polyline2.lineTo(1, 1); + + scl = "FT1TF01TT"; + res = op.execute(polyline1, polyline2, sr, scl, null); + assertTrue(!res); + + scl = "***T*****"; + res = op.execute(polyline1, polyline2, sr, scl, null); + assertTrue(res); + } + + @Test + public static void testPolygonPolylineRelate() { + OperatorRelate op = (OperatorRelate) (OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Relate)); + SpatialReference sr = SpatialReference.create(4326); + boolean res; + String scl; + + Polygon polygon1 = new Polygon(); + Polyline polyline2 = new Polyline(); + + polygon1.startPath(0, 0); + polygon1.lineTo(0, 10); + polygon1.lineTo(10, 10); + polygon1.lineTo(10, 0); + + polyline2.startPath(-10, 0); + polyline2.lineTo(0, 0); + + scl = "FF2F01102"; + res = op.execute(polygon1, polyline2, sr, scl, null); + assertTrue(res); + + scl = "**1*0110*"; + res = op.execute(polygon1, polyline2, sr, scl, null); + assertTrue(!res); + + scl = "T***T****"; + res = op.execute(polygon1, polyline2, sr, scl, null); + assertTrue(!res); + + scl = "FF*FT****"; + res = op.execute(polygon1, polyline2, sr, scl, null); + assertTrue(res); + + polyline2.setEmpty(); + polyline2.startPath(0, 0); + polyline2.lineTo(10, 0); + + scl = "***1*1FF*"; + res = op.execute(polygon1, polyline2, sr, scl, null); + assertTrue(res); + + scl = "F**1*1FF*"; + res = op.execute(polygon1, polyline2, sr, scl, null); + assertTrue(res); + + scl = "0**1*1FF*"; + res = op.execute(polygon1, polyline2, sr, scl, null); + assertTrue(!res); + + scl = "F**1*1TF*"; + res = op.execute(polygon1, polyline2, sr, scl, null); + assertTrue(!res); + + polyline2.setEmpty(); + polyline2.startPath(1, 1); + polyline2.lineTo(5, 5); + + scl = "TT*******"; + res = op.execute(polygon1, polyline2, sr, scl, null); + assertTrue(res); + + scl = "1T2FF1FF*"; + res = op.execute(polygon1, polyline2, sr, scl, null); + assertTrue(res); + + scl = "1T1FF1FF*"; + res = op.execute(polygon1, polyline2, sr, scl, null); + assertTrue(!res); + + polyline2.setEmpty(); + polyline2.startPath(5, 5); + polyline2.lineTo(15, 5); + + scl = "1T*0F*T0T"; + res = op.execute(polygon1, polyline2, sr, scl, null); + assertTrue(res); + } + + @Test + public static void testPolygonPolygonRelate() { + OperatorRelate op = (OperatorRelate) (OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Relate)); + SpatialReference sr = SpatialReference.create(4326); + boolean res; + String scl; + + Polygon polygon1 = new Polygon(); + Polygon polygon2 = new Polygon(); + + polygon1.startPath(0, 0); + polygon1.lineTo(0, 10); + polygon1.lineTo(10, 10); + polygon1.lineTo(10, 0); + + polygon2.startPath(15, 0); + polygon2.lineTo(15, 10); + polygon2.lineTo(25, 10); + polygon2.lineTo(25, 0); + + scl = "FFTFFT21T"; + res = op.execute(polygon1, polygon2, sr, scl, null); + assertTrue(res); + + scl = "FFTFFT11T"; + res = op.execute(polygon1, polygon2, sr, scl, null); + assertTrue(!res); + + polygon2.setEmpty(); + polygon2.startPath(5, 0); + polygon2.lineTo(5, 10); + polygon2.lineTo(15, 10); + polygon2.lineTo(15, 0); + + scl = "21TT1121T"; + res = op.execute(polygon1, polygon2, sr, scl, null); + assertTrue(res); + + polygon2.setEmpty(); + polygon2.startPath(1, 1); + polygon2.lineTo(1, 9); + polygon2.lineTo(9, 9); + polygon2.lineTo(9, 1); + + scl = "212FF1FFT"; + res = op.execute(polygon1, polygon2, sr, scl, null); + assertTrue(res); + } + + @Test + public static void testMultiPointPointRelate() { + OperatorRelate op = (OperatorRelate) (OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Relate)); + SpatialReference sr = SpatialReference.create(4326); + boolean res; + String scl; + + MultiPoint m1 = new MultiPoint(); + Point p2 = new Point(); + + m1.add(0, 0); + p2.setXY(0, 0); + + scl = "T*F***F**"; + res = op.execute(m1, p2, sr, scl, null); + assertTrue(res); + + scl = "T*T***F**"; + res = op.execute(m1, p2, sr, scl, null); + assertTrue(!res); + + m1.add(1, 1); + res = op.execute(m1, p2, sr, scl, null); + assertTrue(res); + } + + @Test + public static void testPointPointRelate() { + OperatorRelate op = (OperatorRelate) (OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Relate)); + SpatialReference sr = SpatialReference.create(4326); + boolean res; + String scl; + + Point p1 = new Point(); + Point p2 = new Point(); + + p1.setXY(0, 0); + p2.setXY(0, 0); + + scl = "T********"; + res = op.execute(p1, p2, sr, scl, null); + assertTrue(res); + } + + @Test + public static void testPolygonMultiPointRelate() { + OperatorRelate op = (OperatorRelate) (OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Relate)); + SpatialReference sr = SpatialReference.create(4326); + boolean res; + String scl; + + Polygon polygon1 = new Polygon(); + MultiPoint multipoint2 = new MultiPoint(); + + polygon1.startPath(0, 0); + polygon1.lineTo(0, 10); + polygon1.lineTo(10, 10); + polygon1.lineTo(10, 0); + + multipoint2.add(0, 0); + multipoint2.add(5, 5); + + scl = "TFT0F1FFT"; + res = op.execute(polygon1, multipoint2, sr, scl, null); + assertTrue(res); + + scl = "T0FFFFT1T"; // transpose of above + res = op.execute(multipoint2, polygon1, sr, scl, null); + assertTrue(res); + + multipoint2.add(11, 11); + + scl = "TFT0F10FT"; + res = op.execute(polygon1, multipoint2, sr, scl, null); + assertTrue(res); + + multipoint2.add(0, 5); + + scl = "TFT0F10FT"; + res = op.execute(polygon1, multipoint2, sr, scl, null); + assertTrue(res); + + scl = "TFF0F10FT"; + res = op.execute(polygon1, multipoint2, sr, scl, null); + assertTrue(!res); + } + + @Test + public static void testPolygonPointRelate() { + OperatorRelate op = (OperatorRelate) (OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Relate)); + SpatialReference sr = SpatialReference.create(4326); + boolean res; + String scl; + + Polygon polygon = new Polygon(); + Point point = new Point(); + + polygon.startPath(0, 0); + polygon.lineTo(0, 10); + polygon.lineTo(10, 10); + polygon.lineTo(10, 0); + + point.setXY(0, 0); + + scl = "FF20FTFFT"; + res = op.execute(polygon, point, sr, scl, null); + assertTrue(res); + } + + @Test + public static void testPolylineMultiPointRelate() { + OperatorRelate op = (OperatorRelate) (OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Relate)); + SpatialReference sr = SpatialReference.create(4326); + boolean res; + String scl; + + Polyline polyline1 = new Polyline(); + MultiPoint multipoint2 = new MultiPoint(); + + polyline1.startPath(0, 0); + polyline1.lineTo(10, 0); + + multipoint2.add(0, 0); + multipoint2.add(5, 5); + + scl = "FF10F00F2"; + res = op.execute(polyline1, multipoint2, sr, scl, null); + assertTrue(res); + + multipoint2.add(5, 0); + + scl = "0F10F00F2"; + res = op.execute(polyline1, multipoint2, sr, scl, null); + assertTrue(res); + + scl = "0F11F00F2"; + res = op.execute(polyline1, multipoint2, sr, scl, null); + assertTrue(!res); + } + + @Test + public static void testMultiPointMultipointRelate() { + OperatorRelate op = (OperatorRelate) (OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Relate)); + SpatialReference sr = SpatialReference.create(4326); + boolean res; + String scl; + + MultiPoint multipoint1 = new MultiPoint(); + MultiPoint multipoint2 = new MultiPoint(); + + multipoint1.add(0, 0); + + multipoint2.add(0, 0); + + scl = "TFFFFFFF2"; + res = op.execute(multipoint1, multipoint2, sr, scl, null); + assertTrue(res); + + multipoint2.add(5, 5); + + scl = "TFFFFFTF2"; + res = op.execute(multipoint1, multipoint2, sr, scl, null); + assertTrue(res); + + multipoint1.add(-5, 0); + + scl = "0FTFFFTF2"; + res = op.execute(multipoint1, multipoint2, sr, scl, null); + assertTrue(res); + + res = GeometryEngine.relate(multipoint1, multipoint2, sr, scl); + assertTrue(res); + } + + @Test + public static void testPolylinePointRelate() { + OperatorRelate op = (OperatorRelate) (OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Relate)); + SpatialReference sr = SpatialReference.create(4326); + boolean res; + String scl; + + Polyline polyline = new Polyline(); + Point point = new Point(); + + polyline.startPath(0, 2); + polyline.lineTo(0, 4); + + point.setXY(0, 3); + + scl = "0F1FF0FF2'"; + res = op.execute(polyline, point, sr, scl, null); + assertTrue(res); + } + + static MapGeometry importFromJson(String jsonString) { + JsonFactory factory = new JsonFactory(); + try { + JsonParser jsonParser = factory.createJsonParser(jsonString); + jsonParser.nextToken(); + OperatorImportFromJson importer = (OperatorImportFromJson) OperatorFactoryLocal + .getInstance().getOperator( + Operator.Type.ImportMapGeometryFromJson); + + return importer.execute(Geometry.Type.Unknown, jsonParser); + } catch (Exception ex) { + } + + return null; + } +} diff --git a/unittest/com/esri/core/geometry/TestSerialization.java b/unittest/com/esri/core/geometry/TestSerialization.java new file mode 100644 index 00000000..03f62b07 --- /dev/null +++ b/unittest/com/esri/core/geometry/TestSerialization.java @@ -0,0 +1,294 @@ +package com.esri.core.geometry; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.InputStream; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import junit.framework.TestCase; +import org.junit.Test; + +public class TestSerialization extends TestCase { + @Override + protected void setUp() throws Exception { + super.setUp(); + } + + @Override + protected void tearDown() throws Exception { + super.tearDown(); + } + + @Test + public void testSerializePoint() { + try { + ByteArrayOutputStream streamOut = new ByteArrayOutputStream(); + ObjectOutputStream oo = new ObjectOutputStream(streamOut); + Point pt = new Point(10, 40); + oo.writeObject(pt); + ByteArrayInputStream streamIn = new ByteArrayInputStream( + streamOut.toByteArray()); + ObjectInputStream ii = new ObjectInputStream(streamIn); + Point ptRes = (Point) ii.readObject(); + assertTrue(ptRes.equals(pt)); + } catch (Exception ex) { + fail("Point serialization failure"); + + } + + // try + // { + // FileOutputStream streamOut = new FileOutputStream(m_thisDirectory + + // "savedPoint.txt"); + // ObjectOutputStream oo = new ObjectOutputStream(streamOut); + // Point pt = new Point(10, 40); + // oo.writeObject(pt); + // } + // catch(Exception ex) + // { + // fail("Point serialization failure"); + // } + + try { + InputStream s = TestSerialization.class + .getResourceAsStream("savedPoint.txt"); + ObjectInputStream ii = new ObjectInputStream(s); + Point ptRes = (Point) ii.readObject(); + assertTrue(ptRes.getX() == 10 && ptRes.getY() == 40); + } catch (Exception ex) { + fail("Point serialization failure"); + } + } + + @Test + public void testSerializePolygon() { + try { + ByteArrayOutputStream streamOut = new ByteArrayOutputStream(); + ObjectOutputStream oo = new ObjectOutputStream(streamOut); + Polygon pt = new Polygon(); + pt.startPath(10, 10); + pt.lineTo(100, 100); + pt.lineTo(200, 100); + oo.writeObject(pt); + ByteArrayInputStream streamIn = new ByteArrayInputStream( + streamOut.toByteArray()); + ObjectInputStream ii = new ObjectInputStream(streamIn); + Polygon ptRes = (Polygon) ii.readObject(); + assertTrue(ptRes.equals(pt)); + } catch (Exception ex) { + fail("Polygon serialization failure"); + } + + try { + ByteArrayOutputStream streamOut = new ByteArrayOutputStream(); + ObjectOutputStream oo = new ObjectOutputStream(streamOut); + Polygon pt = new Polygon(); + pt.startPath(10, 10); + pt.lineTo(100, 100); + pt.lineTo(200, 100); + pt = (Polygon) GeometryEngine.simplify(pt, null); + oo.writeObject(pt); + ByteArrayInputStream streamIn = new ByteArrayInputStream( + streamOut.toByteArray()); + ObjectInputStream ii = new ObjectInputStream(streamIn); + Polygon ptRes = (Polygon) ii.readObject(); + assertTrue(ptRes.equals(pt)); + } catch (Exception ex) { + fail("Polygon serialization failure"); + } + + // try + // { + // FileOutputStream streamOut = new FileOutputStream(m_thisDirectory + + // "savedPolygon.txt"); + // ObjectOutputStream oo = new ObjectOutputStream(streamOut); + // Polygon pt = new Polygon(); + // pt.startPath(10, 10); + // pt.lineTo(100, 100); + // pt.lineTo(200, 100); + // pt = (Polygon)GeometryEngine.simplify(pt, null); + // oo.writeObject(pt); + // } + // catch(Exception ex) + // { + // fail("Polygon serialization failure"); + // } + + try { + InputStream s = TestSerialization.class + .getResourceAsStream("savedPolygon.txt"); + ObjectInputStream ii = new ObjectInputStream(s); + Polygon ptRes = (Polygon) ii.readObject(); + assertTrue(ptRes != null); + } catch (Exception ex) { + fail("Polygon serialization failure"); + } + } + + @Test + public void testSerializePolyline() { + try { + ByteArrayOutputStream streamOut = new ByteArrayOutputStream(); + ObjectOutputStream oo = new ObjectOutputStream(streamOut); + Polyline pt = new Polyline(); + pt.startPath(10, 10); + pt.lineTo(100, 100); + pt.lineTo(200, 100); + oo.writeObject(pt); + ByteArrayInputStream streamIn = new ByteArrayInputStream( + streamOut.toByteArray()); + ObjectInputStream ii = new ObjectInputStream(streamIn); + Polyline ptRes = (Polyline) ii.readObject(); + assertTrue(ptRes.equals(pt)); + } catch (Exception ex) { + fail("Polyline serialization failure"); + } + + // try + // { + // FileOutputStream streamOut = new FileOutputStream(m_thisDirectory + + // "savedPolyline.txt"); + // ObjectOutputStream oo = new ObjectOutputStream(streamOut); + // Polyline pt = new Polyline(); + // pt.startPath(10, 10); + // pt.lineTo(100, 100); + // pt.lineTo(200, 100); + // oo.writeObject(pt); + // } + // catch(Exception ex) + // { + // fail("Polyline serialization failure"); + // } + + try { + InputStream s = TestSerialization.class + .getResourceAsStream("savedPolyline.txt"); + ObjectInputStream ii = new ObjectInputStream(s); + Polyline ptRes = (Polyline) ii.readObject(); + assertTrue(ptRes != null); + } catch (Exception ex) { + fail("Polyline serialization failure"); + } + } + + @Test + public void testSerializeEnvelope() { + try { + ByteArrayOutputStream streamOut = new ByteArrayOutputStream(); + ObjectOutputStream oo = new ObjectOutputStream(streamOut); + Envelope pt = new Envelope(10, 10, 400, 300); + oo.writeObject(pt); + ByteArrayInputStream streamIn = new ByteArrayInputStream( + streamOut.toByteArray()); + ObjectInputStream ii = new ObjectInputStream(streamIn); + Envelope ptRes = (Envelope) ii.readObject(); + assertTrue(ptRes.equals(pt)); + } catch (Exception ex) { + fail("Envelope serialization failure"); + } + + // try + // { + // FileOutputStream streamOut = new FileOutputStream(m_thisDirectory + + // "savedEnvelope.txt"); + // ObjectOutputStream oo = new ObjectOutputStream(streamOut); + // Envelope pt = new Envelope(10, 10, 400, 300); + // oo.writeObject(pt); + // } + // catch(Exception ex) + // { + // fail("Envelope serialization failure"); + // } + + try { + InputStream s = TestSerialization.class + .getResourceAsStream("savedEnvelope.txt"); + ObjectInputStream ii = new ObjectInputStream(s); + Envelope ptRes = (Envelope) ii.readObject(); + assertTrue(ptRes.getXMax() == 400); + } catch (Exception ex) { + fail("Envelope serialization failure"); + } + } + + @Test + public void testSerializeMultiPoint() { + try { + ByteArrayOutputStream streamOut = new ByteArrayOutputStream(); + ObjectOutputStream oo = new ObjectOutputStream(streamOut); + MultiPoint pt = new MultiPoint(); + pt.add(10, 30); + pt.add(120, 40); + oo.writeObject(pt); + ByteArrayInputStream streamIn = new ByteArrayInputStream( + streamOut.toByteArray()); + ObjectInputStream ii = new ObjectInputStream(streamIn); + MultiPoint ptRes = (MultiPoint) ii.readObject(); + assertTrue(ptRes.equals(pt)); + } catch (Exception ex) { + fail("MultiPoint serialization failure"); + } + + // try + // { + // FileOutputStream streamOut = new FileOutputStream(m_thisDirectory + + // "savedMultiPoint.txt"); + // ObjectOutputStream oo = new ObjectOutputStream(streamOut); + // MultiPoint pt = new MultiPoint(); + // pt.add(10, 30); + // pt.add(120, 40); + // oo.writeObject(pt); + // } + // catch(Exception ex) + // { + // fail("MultiPoint serialization failure"); + // } + + try { + InputStream s = TestSerialization.class + .getResourceAsStream("savedMultiPoint.txt"); + ObjectInputStream ii = new ObjectInputStream(s); + MultiPoint ptRes = (MultiPoint) ii.readObject(); + assertTrue(ptRes.getPoint(1).getY() == 40); + } catch (Exception ex) { + fail("MultiPoint serialization failure"); + } + } + + @Test + public void testSerializeLine() { + try { + ByteArrayOutputStream streamOut = new ByteArrayOutputStream(); + ObjectOutputStream oo = new ObjectOutputStream(streamOut); + Line pt = new Line(); + pt.setStart(new Point(10, 30)); + pt.setEnd(new Point(120, 40)); + oo.writeObject(pt); + ByteArrayInputStream streamIn = new ByteArrayInputStream( + streamOut.toByteArray()); + ObjectInputStream ii = new ObjectInputStream(streamIn); + Line ptRes = (Line) ii.readObject(); + assertTrue(ptRes.equals(pt)); + } catch (Exception ex) { + // fail("Line serialization failure"); + assertEquals(ex.getMessage(), "Cannot serialize this geometry"); + } + } + + @Test + public void testSerializeSR() { + try { + ByteArrayOutputStream streamOut = new ByteArrayOutputStream(); + ObjectOutputStream oo = new ObjectOutputStream(streamOut); + SpatialReference sr = SpatialReference.create(102100); + oo.writeObject(sr); + ByteArrayInputStream streamIn = new ByteArrayInputStream( + streamOut.toByteArray()); + ObjectInputStream ii = new ObjectInputStream(streamIn); + SpatialReference ptRes = (SpatialReference) ii.readObject(); + assertTrue(ptRes.equals(sr)); + } catch (Exception ex) { + fail("Spatial Reference serialization failure"); + } + } +} diff --git a/unittest/com/esri/core/geometry/TestShapePreserving.java b/unittest/com/esri/core/geometry/TestShapePreserving.java new file mode 100644 index 00000000..0d5524e5 --- /dev/null +++ b/unittest/com/esri/core/geometry/TestShapePreserving.java @@ -0,0 +1,151 @@ +package com.esri.core.geometry; + +import junit.framework.TestCase; +import org.junit.Test; + +public class TestShapePreserving extends TestCase { + @Override + protected void setUp() throws Exception { + super.setUp(); + } + + @Override + protected void tearDown() throws Exception { + super.tearDown(); + } + + @Test + public static void testBoxAreasWithinToleranceCR186615() { + /* + * //TODO: Implement these OperatorShapePreservingArea shapeAreaOp = + * OperatorShapePreservingArea.local(); OperatorShapePreservingLength + * shapeLengthOp = OperatorShapePreservingLength.local(); + * + * Polyline polyline2 = new Polyline(); polyline2.startPath(190, 0); + * polyline2.lineTo(200,0); SpatialReference spatialRefWGS = + * SpatialReference.create(4326); double lengthEquator10Degree = + * shapeLengthOp.execute(polyline2, spatialRefWGS, null); + * assertTrue(lengthEquator10Degree != 0.00); + * + * Polyline polylineEquator2 = new Polyline(); + * polylineEquator2.startPath(170, 0); polylineEquator2.lineTo(180,0); + * double lengthEquator10Degree2 = + * shapeLengthOp.execute(polylineEquator2, spatialRefWGS, null); + * assertTrue(GeomCommonMethods.compareDouble(lengthEquator10Degree2, + * lengthEquator10Degree, Math.pow(10.0,-10))); + * + * SpatialReference spatialRefWGSMerc = SpatialReference.create(102100); + * double PCS5 = 111319.49079327358 * 5; double PCS180 = + * 20037508.342789244; double CSYMax = 30240970.0; double CSYMin = + * -30240970.0; + * + * Polyline polylineEquator3 = new Polyline(); + * polylineEquator3.startPath(-PCS180 - 4*PCS5, 0); + * polylineEquator3.lineTo(-PCS180 - 2*PCS5, 0); double + * lengthEquatorMercDegree = shapeLengthOp.execute(*polylineEquator3, + * spatialRefWGSMerc, null); + * assertTrue(GeomCommonMethods.compareDouble(lengthEquatorMercDegree, + * lengthEquator10Degree, Math.pow(10.0,-10))); + * + * Polyline polylineBox = new Polyline(); polylineBox.startPath(PCS180 - + * 2*PCS5, 30240970.0 / 9); polylineBox.lineTo(PCS180 + 2*PCS5, + * 30240970.0 / 9); polylineBox.lineTo(PCS180 + 2*PCS5, -30240970.0 / + * 9); polylineBox.lineTo(PCS180 - 2*PCS5, -30240970.0 / 9); + * polylineBox.lineTo(PCS180 - 2*PCS5, 30240970.0 / 9); + * + * Polygon polygonBox = new Polygon(); polygonBox.startPath(PCS180 - + * 2*PCS5, 30240970.0 / 9); polygonBox.lineTo(PCS180 + 2*PCS5, + * 30240970.0 / 9); polygonBox.lineTo(PCS180 + 2*PCS5, -30240970.0 / 9); + * polygonBox.lineTo(PCS180 - 2*PCS5, -30240970.0 / 9); + * polygonBox.lineTo(PCS180 - 2*PCS5, 30240970.0 / 9); + * + * Envelope envelopeBox = new Envelope(); + * polygonBox.queryEnvelope(envelopeBox); + * + * double lengthBox1 = shapeLengthOp.execute(polylineBox, + * spatialRefWGSMerc, null); double lengthBox2 = + * shapeLengthOp.execute(polygonBox, spatialRefWGSMerc, null); double + * lengthBox3 = shapeLengthOp.execute(envelopeBox, spatialRefWGSMerc, + * null); assertTrue(GeomCommonMethods.compareDouble(lengthBox1, + * lengthBox2, Math.pow(10.0,-10))); + * assertTrue(GeomCommonMethods.compareDouble(lengthBox1, lengthBox3, + * Math.pow(10.0,-10))); + * + * // Repeated polygon area Polygon polygonBox1 = new Polygon(); + * polygonBox1.startPath(-PCS180 - 6 * PCS5, 30240970.0 / 9); + * polygonBox1.lineTo(-PCS180 - 4 * PCS5, 30240970.0 / 9); + * polygonBox1.lineTo(-PCS180 - 4 * PCS5, -30240970.0 / 9); + * polygonBox1.lineTo(-PCS180 - 6 * PCS5, -30240970.0 / 9); + * + * Polygon polygonBox2 = new Polygon(); polygonBox2.startPath(-PCS180 - + * 2 * PCS5, 30240970.0 / 9); polygonBox2.lineTo(-PCS180, 30240970.0 / + * 9); polygonBox2.lineTo(-PCS180, -30240970.0 / 9); + * polygonBox2.lineTo(-PCS180 - 2 * PCS5, -30240970.0 / 9); + * + * Polygon polygonBox3 = new Polygon(); polygonBox3.startPath(-PCS180 - + * PCS5, 30240970.0 / 9); polygonBox3.lineTo(-PCS180 + PCS5, 30240970.0 + * / 9); polygonBox3.lineTo(-PCS180 + PCS5, -30240970.0 / 9); + * polygonBox3.lineTo(-PCS180 - PCS5, -30240970.0 / 9); + * + * Polygon polygonBox4 = new Polygon(); polygonBox4.startPath(-PCS180, + * 30240970.0 / 9); polygonBox4.lineTo(-PCS180 + 2 * PCS5, 30240970.0 / + * 9); polygonBox4.lineTo(-PCS180 + 2 * PCS5, -30240970.0 / 9); + * polygonBox4.lineTo(-PCS180, -30240970.0 / 9); + * + * Polygon polygonBox5 = new Polygon(); polygonBox5.startPath(PCS180 - 6 + * * PCS5, 30240970.0 / 9); polygonBox5.lineTo(PCS180 - 4 * PCS5, + * 30240970.0 / 9); polygonBox5.lineTo(PCS180 - 4 * PCS5, -30240970.0 / + * 9); polygonBox5.lineTo(PCS180 - 6 * PCS5, -30240970.0 / 9); + * + * Polygon polygonBox6 = new Polygon(); polygonBox6.startPath(PCS180 - 2 + * * PCS5, 30240970.0 / 9); polygonBox6.lineTo(PCS180, 30240970.0 / 9); + * polygonBox6.lineTo(PCS180, -30240970.0 / 9); + * polygonBox6.lineTo(PCS180 - 2 * PCS5, -30240970.0 / 9); + * + * Polygon polygonBox7 = new Polygon(); polygonBox7.startPath(PCS180 - + * PCS5, 30240970.0 / 9); polygonBox7.lineTo(PCS180 + PCS5, 30240970.0 / + * 9); polygonBox7.lineTo(PCS180 + PCS5, -30240970.0 / 9); + * polygonBox7.lineTo(PCS180 - PCS5, -30240970.0 / 9); + * + * Polygon polygonBox8 = new Polygon(); polygonBox8.startPath(PCS180, + * 30240970.0 / 9); polygonBox8.lineTo(PCS180 + 2 * PCS5, 30240970.0 / + * 9); polygonBox8.lineTo(PCS180 + 2 * PCS5, -30240970.0 / 9); + * polygonBox8.lineTo(PCS180, -30240970.0 / 9); + * + * Polygon polygonBox9 = new Polygon(); polygonBox9.startPath(PCS180 + 2 + * * PCS5, 30240970.0 / 9); polygonBox9.lineTo(PCS180 + 4 * PCS5, + * 30240970.0 / 9); polygonBox9.lineTo(PCS180 + 4 * PCS5, -30240970.0 / + * 9); polygonBox9.lineTo(PCS180 + 2 * PCS5, -30240970.0 / 9); + * + * double area1 = shapeAreaOp.execute(polygonBox1, spatialRefWGSMerc, + * null); double area2 = shapeAreaOp.execute(polygonBox2, + * spatialRefWGSMerc, null); double area3 = + * shapeAreaOp.execute(polygonBox3, spatialRefWGSMerc, null); double + * area4 = shapeAreaOp.execute(polygonBox4, spatialRefWGSMerc, null); + * double area5 = shapeAreaOp.execute(polygonBox5, spatialRefWGSMerc, + * null); double area6 = shapeAreaOp.execute(polygonBox6, + * spatialRefWGSMerc, null); double area7 = + * shapeAreaOp.execute(polygonBox7, spatialRefWGSMerc, null); double + * area8 = shapeAreaOp.execute(polygonBox8, spatialRefWGSMerc, null); + * double area9 = shapeAreaOp.execute(polygonBox9, spatialRefWGSMerc, + * null); + * + * assertTrue(GeomCommonMethods.compareDouble(area1, area2, + * Math.pow(10.0,-10))); + * assertTrue(GeomCommonMethods.compareDouble(area1, area3, + * Math.pow(10.0,-10))); + * assertTrue(GeomCommonMethods.compareDouble(area1, area4, + * Math.pow(10.0,-10))); + * assertTrue(GeomCommonMethods.compareDouble(area1, area5, + * Math.pow(10.0,-10))); + * assertTrue(GeomCommonMethods.compareDouble(area1, area6, + * Math.pow(10.0,-10))); + * assertTrue(GeomCommonMethods.compareDouble(area1, area7, + * Math.pow(10.0,-10))); + * assertTrue(GeomCommonMethods.compareDouble(area1, area8, + * Math.pow(10.0,-10))); + * assertTrue(GeomCommonMethods.compareDouble(area1, area9, + * Math.pow(10.0,-10))); + */ + } +} diff --git a/unittest/com/esri/core/geometry/TestSimplify.java b/unittest/com/esri/core/geometry/TestSimplify.java new file mode 100644 index 00000000..58baf388 --- /dev/null +++ b/unittest/com/esri/core/geometry/TestSimplify.java @@ -0,0 +1,1328 @@ +package com.esri.core.geometry; + +//import java.io.FileOutputStream; +//import java.io.PrintStream; +//import java.util.ArrayList; +//import java.util.List; +//import java.util.Random; +import java.io.IOException; + +import junit.framework.TestCase; + +import org.codehaus.jackson.JsonFactory; +import org.junit.Test; + +public class TestSimplify extends TestCase { + OperatorFactoryLocal factory = null; + OperatorSimplify simplifyOp = null; + OperatorSimplifyOGC simplifyOpOGC = null; + SpatialReference sr102100 = null; + SpatialReference sr4326 = null; + SpatialReference sr3857 = null; + + @Override + protected void setUp() throws Exception { + super.setUp(); + factory = OperatorFactoryLocal.getInstance(); + simplifyOp = (OperatorSimplify) factory + .getOperator(Operator.Type.Simplify); + simplifyOpOGC = (OperatorSimplifyOGC) factory + .getOperator(Operator.Type.SimplifyOGC); + sr102100 = SpatialReference.create(102100); + sr3857 = SpatialReference.create(3857);// PE_PCS_WGS_1984_WEB_MERCATOR_AUXSPHERE); + sr4326 = SpatialReference.create(4326);// enum_value2(SpatialReference, + // Code, GCS_WGS_1984)); + } + + @Override + protected void tearDown() throws Exception { + super.tearDown(); + } + + public Polygon makeNonSimplePolygon2() { + Polygon poly = new Polygon(); + poly.startPath(0, 0); + poly.lineTo(0, 15); + poly.lineTo(15, 15); + poly.lineTo(15, 0); + + // This is an interior ring but it is clockwise + poly.startPath(5, 5); + poly.lineTo(5, 6); + poly.lineTo(6, 6); + poly.lineTo(6, 5); + + return poly; + }// done + + /* + * ------------>---------------->--------------- | | | (1) | | | | --->--- + * ------->------- | | | | | (5) | | | | | | --<-- | | | | (2) | | | | | | | + * | | | | (4) | | | | | | | -->-- | | --<-- | ---<--- | | | | | | + * -------<------- | | (3) | -------------<---------------<--------------- + * -->-- + */ + + // Bowtie case with vertices at intersection + + public Polygon makeNonSimplePolygon5() { + Polygon poly = new Polygon(); + poly.startPath(10, 0); + poly.lineTo(0, 0); + poly.lineTo(5, 5); + poly.lineTo(10, 10); + poly.lineTo(0, 10); + poly.lineTo(5, 5); + + return poly; + }// done + + @Test + public void test0() { + Polygon poly1 = new Polygon(); + poly1.addEnvelope(new Envelope(10, 10, 40, 20), false); + Polygon poly2 = (Polygon) simplifyOp.execute(poly1, null, false, null); + boolean res = simplifyOp.isSimpleAsFeature(poly2, null, true, null, + null); + assertTrue(res); + // assertTrue(poly1.equals(poly2)); + }// done + + @Test + public void test0Poly() {// simple + Polygon poly1 = new Polygon(); + poly1.addEnvelope(new Envelope(10, 10, 40, 20), false); + poly1.addEnvelope(new Envelope(50, 10, 100, 20), false); + Polygon poly2 = (Polygon) simplifyOp.execute(poly1, null, false, null); + boolean res = simplifyOp.isSimpleAsFeature(poly2, null, true, null, + null); + assertTrue(res); + // assertTrue(poly1.equals(poly2)); + }// done + + @Test + public void test0Polygon_Spike1() {// non-simple (spike) + Polygon poly1 = new Polygon(); + poly1.startPath(10, 10); + poly1.lineTo(10, 20); + poly1.lineTo(40, 20); + poly1.lineTo(40, 10); + poly1.lineTo(60, 10); + poly1.lineTo(70, 10); + + boolean res = simplifyOp.isSimpleAsFeature(poly1, null, true, null, + null); + assertTrue(!res); + Polygon poly2 = (Polygon) simplifyOp.execute(poly1, null, false, null); + res = simplifyOp.isSimpleAsFeature(poly2, null, true, null, null); + assertTrue(res); + assertTrue(poly2.getPointCount() == 4); + }// done + + @Test + public void test0Polygon_Spike2() {// non-simple (spikes) + Polygon poly1 = new Polygon(); + // rectangle with a spike + poly1.startPath(10, 10); + poly1.lineTo(10, 20); + poly1.lineTo(40, 20); + poly1.lineTo(40, 10); + poly1.lineTo(60, 10); + poly1.lineTo(70, 10); + + // degenerate + poly1.startPath(100, 100); + poly1.lineTo(100, 120); + poly1.lineTo(100, 130); + + boolean res = simplifyOp.isSimpleAsFeature(poly1, null, true, null, + null); + assertTrue(!res); + Polygon poly2 = (Polygon) simplifyOp.execute(poly1, null, false, null); + res = simplifyOp.isSimpleAsFeature(poly2, null, true, null, null); + assertTrue(res); + assertTrue(poly2.getPointCount() == 4); + }// done + + @Test + public void test0Polygon_Spike3() {// non-simple (spikes) + Polygon poly1 = new Polygon(); + // degenerate + poly1.startPath(100, 100); + poly1.lineTo(100, 120); + poly1.lineTo(100, 130); + + boolean res = simplifyOp.isSimpleAsFeature(poly1, null, true, null, + null); + assertTrue(!res); + Polygon poly2 = (Polygon) simplifyOp.execute(poly1, null, false, null); + res = simplifyOp.isSimpleAsFeature(poly2, null, true, null, null); + assertTrue(res); + assertTrue(poly2.isEmpty()); + }// done + + @Test + public void test0PolygonSelfIntersect1() {// non-simple (self-intersection) + Polygon poly1 = new Polygon(); + // touch uncracked + poly1.startPath(0, 0); + poly1.lineTo(0, 100); + poly1.lineTo(100, 100); + poly1.lineTo(0, 50); + poly1.lineTo(100, 0); + + boolean res = simplifyOp.isSimpleAsFeature(poly1, null, true, null, + null); + assertTrue(!res); + Polygon poly2 = (Polygon) simplifyOp.execute(poly1, null, false, null); + res = simplifyOp.isSimpleAsFeature(poly2, null, true, null, null); + assertTrue(res); + assertTrue(!poly2.isEmpty()); + }// done + + @Test + public void test0PolygonSelfIntersect2() {// non-simple (self-intersection) + Polygon poly1 = new Polygon(); + poly1.startPath(0, 0); + poly1.lineTo(0, 100); + poly1.lineTo(100, 100); + poly1.lineTo(-100, 0); + // poly1.lineTo(100, 0); + + boolean res = simplifyOp.isSimpleAsFeature(poly1, null, true, null, + null); + assertTrue(!res); + Polygon poly2 = (Polygon) simplifyOp.execute(poly1, null, false, null); + res = simplifyOp.isSimpleAsFeature(poly2, null, true, null, null); + assertTrue(res); + assertTrue(!poly2.isEmpty()); + }// done + + @Test + public void test0PolygonSelfIntersect3() { + Polygon poly = new Polygon(); + poly.startPath(0, 0); + poly.lineTo(0, 15); + poly.lineTo(15, 15); + poly.lineTo(15, 0); + + // This part intersects with the first part + poly.startPath(10, 10); + poly.lineTo(10, 20); + poly.lineTo(20, 20); + poly.lineTo(20, 10); + + boolean res = simplifyOp + .isSimpleAsFeature(poly, null, true, null, null); + assertTrue(!res); + Polygon poly2 = (Polygon) simplifyOp.execute(poly, null, false, null); + res = simplifyOp.isSimpleAsFeature(poly2, null, true, null, null); + assertTrue(res); + assertTrue(!poly2.isEmpty()); + }// done + + @Test + public void test0PolygonInteriorRing1() { + Polygon poly = new Polygon(); + poly.startPath(0, 0); + poly.lineTo(0, 15); + poly.lineTo(15, 15); + poly.lineTo(15, 0); + + // This is an interior ring but it is clockwise + poly.startPath(5, 5); + poly.lineTo(5, 6); + poly.lineTo(6, 6); + poly.lineTo(6, 5); + + boolean res = simplifyOp + .isSimpleAsFeature(poly, null, true, null, null); + assertTrue(!res); + Polygon poly2 = (Polygon) simplifyOp.execute(poly, null, false, null); + res = simplifyOp.isSimpleAsFeature(poly2, null, true, null, null); + assertTrue(res); + assertTrue(!poly2.isEmpty()); + }// done + + @Test + public void test0PolygonInteriorRing2() { + Polygon poly = new Polygon(); + poly.startPath(0, 0); + poly.lineTo(0, 15); + poly.lineTo(15, 15); + poly.lineTo(15, 0); + + // This is an interior ring but it is clockwise + poly.startPath(5, 5); + poly.lineTo(5, 6); + poly.lineTo(6, 6); + poly.lineTo(6, 5); + + // This part intersects with the first part + poly.startPath(10, 10); + poly.lineTo(10, 20); + poly.lineTo(20, 20); + poly.lineTo(20, 10); + + boolean res = simplifyOp + .isSimpleAsFeature(poly, null, true, null, null); + assertTrue(!res); + Polygon poly2 = (Polygon) simplifyOp.execute(poly, null, false, null); + res = simplifyOp.isSimpleAsFeature(poly2, null, true, null, null); + assertTrue(res); + assertTrue(!poly2.isEmpty()); + }// done + + @Test + public void test0PolygonInteriorRingWithCommonBoundary1() { + // Two rings have common boundary + Polygon poly = new Polygon(); + poly.startPath(0, 0); + poly.lineTo(0, 10); + poly.lineTo(10, 10); + poly.lineTo(10, 0); + + poly.startPath(10, 0); + poly.lineTo(10, 10); + poly.lineTo(20, 10); + poly.lineTo(20, 0); + + boolean res = simplifyOp + .isSimpleAsFeature(poly, null, true, null, null); + assertTrue(!res); + Polygon poly2 = (Polygon) simplifyOp.execute(poly, null, false, null); + res = simplifyOp.isSimpleAsFeature(poly2, null, true, null, null); + assertTrue(res); + assertTrue(!poly2.isEmpty()); + }// done + + @Test + public void test0PolygonInteriorRingWithCommonBoundary2() { + // Two rings have common boundary + Polygon poly = new Polygon(); + poly.startPath(0, 0); + poly.lineTo(0, 10); + poly.lineTo(10, 10); + poly.lineTo(10, 0); + + poly.startPath(10, 5); + poly.lineTo(10, 6); + poly.lineTo(20, 6); + poly.lineTo(20, 5); + + boolean res = simplifyOp + .isSimpleAsFeature(poly, null, true, null, null); + assertTrue(!res); + Polygon poly2 = (Polygon) simplifyOp.execute(poly, null, false, null); + res = simplifyOp.isSimpleAsFeature(poly2, null, true, null, null); + assertTrue(res); + assertTrue(!poly2.isEmpty()); + }// done + + @Test + public void testPolygon() { + Polygon nonSimplePolygon = makeNonSimplePolygon(); + Polygon simplePolygon = (Polygon) simplifyOp.execute(nonSimplePolygon, + sr3857, false, null); + + boolean res = simplifyOp.isSimpleAsFeature(simplePolygon, sr3857, true, + null, null); + assertTrue(res); + + @SuppressWarnings("unused") + int partCount = simplePolygon.getPathCount(); + // assertTrue(partCount == 2); + + double area = simplePolygon.calculateRingArea2D(0); + assertTrue(Math.abs(area - 300) <= 0.0001); + + area = simplePolygon.calculateRingArea2D(1); + assertTrue(Math.abs(area - (-25.0)) <= 0.0001); + }// done + + @Test + public void testPolygon2() { + Polygon nonSimplePolygon2 = makeNonSimplePolygon2(); + double area = nonSimplePolygon2.calculateRingArea2D(1); + assertTrue(Math.abs(area - 1.0) <= 0.0001); + + Polygon simplePolygon2 = (Polygon) simplifyOp.execute( + nonSimplePolygon2, sr3857, false, null); + + boolean res = simplifyOp.isSimpleAsFeature(simplePolygon2, sr3857, + true, null, null); + assertTrue(res); + + area = simplePolygon2.calculateRingArea2D(0); + assertTrue(Math.abs(area - 225) <= 0.0001); + + area = simplePolygon2.calculateRingArea2D(1); + assertTrue(Math.abs(area - (-1.0)) <= 0.0001); + }// done + + @Test + public void testPolygon3() { + Polygon nonSimplePolygon3 = makeNonSimplePolygon3(); + Polygon simplePolygon3 = (Polygon) simplifyOp.execute( + nonSimplePolygon3, sr3857, false, null); + + boolean res = simplifyOp.isSimpleAsFeature(simplePolygon3, sr3857, + true, null, null); + assertTrue(res); + + double area = simplePolygon3.calculateRingArea2D(0); + assertTrue(Math.abs(area - 875) <= 0.0001); + + area = simplePolygon3.calculateRingArea2D(1); + assertTrue(Math.abs(area - (-225)) <= 0.0001 + || Math.abs(area - (-50.0)) <= 0.0001); + + area = simplePolygon3.calculateRingArea2D(2); + assertTrue(Math.abs(area - (-225)) <= 0.0001 + || Math.abs(area - (-50.0)) <= 0.0001); + + area = simplePolygon3.calculateRingArea2D(3); + assertTrue(Math.abs(area - 25) <= 0.0001); + + area = simplePolygon3.calculateRingArea2D(4); + assertTrue(Math.abs(area - 25) <= 0.0001); + }// done + + @Test + public void testPolyline() { + Polyline nonSimplePolyline = makeNonSimplePolyline(); + Polyline simplePolyline = (Polyline) simplifyOp.execute( + nonSimplePolyline, sr3857, false, null); + + int segmentCount = simplePolyline.getSegmentCount(); + assertTrue(segmentCount == 4); + }// done + + @Test + public void testPolygon4() { + Polygon nonSimplePolygon4 = makeNonSimplePolygon4(); + Polygon simplePolygon4 = (Polygon) simplifyOp.execute( + nonSimplePolygon4, sr3857, false, null); + boolean res = simplifyOp.isSimpleAsFeature(simplePolygon4, sr3857, + true, null, null); + assertTrue(res); + + assertTrue(simplePolygon4.getPointCount() == 5); + Point point = nonSimplePolygon4.getPoint(0); + assertTrue(point.getX() == 0.0 && point.getY() == 0.0); + point = nonSimplePolygon4.getPoint(1); + assertTrue(point.getX() == 0.0 && point.getY() == 10.0); + point = nonSimplePolygon4.getPoint(2); + assertTrue(point.getX() == 10.0 && point.getY() == 10.0); + point = nonSimplePolygon4.getPoint(3); + assertTrue(point.getX() == 10.0 && point.getY() == 0.0); + point = nonSimplePolygon4.getPoint(4); + assertTrue(point.getX() == 5.0 && point.getY() == 0.0); + }// done + + @Test + public void testPolygon5() { + Polygon nonSimplePolygon5 = makeNonSimplePolygon5(); + Polygon simplePolygon5 = (Polygon) simplifyOp.execute( + nonSimplePolygon5, sr3857, false, null); + assertTrue(simplePolygon5 != null); + + boolean res = simplifyOp.isSimpleAsFeature(simplePolygon5, sr3857, + true, null, null); + assertTrue(res); + + // FIXME Bowtie. once simplify is fixed this should result in a + // simplified geom + + int pointCount = simplePolygon5.getPointCount(); + assertTrue(pointCount == 6); + + double area = simplePolygon5.calculateArea2D(); + assertTrue(Math.abs(area - 50.0) <= 0.001); + + }// done + + @Test + public void testPolygon6() { + Polygon nonSimplePolygon6 = makeNonSimplePolygon6(); + Polygon simplePolygon6 = (Polygon) simplifyOp.execute( + nonSimplePolygon6, sr3857, false, null); + + boolean res = simplifyOp.isSimpleAsFeature(simplePolygon6, sr3857, + true, null, null); + assertTrue(res); + } + + @Test + public void testPolygon7() { + Polygon nonSimplePolygon7 = makeNonSimplePolygon7(); + Polygon simplePolygon7 = (Polygon) simplifyOp.execute( + nonSimplePolygon7, sr3857, false, null); + + boolean res = simplifyOp.isSimpleAsFeature(simplePolygon7, sr3857, + true, null, null); + assertTrue(res); + } + + public Polygon makeNonSimplePolygon() { + Polygon poly = new Polygon(); + poly.startPath(0, 0); + poly.lineTo(0, 15); + poly.lineTo(15, 15); + poly.lineTo(15, 0); + + // This is an interior ring but it is clockwise + poly.startPath(5, 5); + poly.lineTo(5, 6); + poly.lineTo(6, 6); + poly.lineTo(6, 5); + + // This part intersects with the first part + poly.startPath(10, 10); + poly.lineTo(10, 20); + poly.lineTo(20, 20); + poly.lineTo(20, 10); + + return poly; + }// done + + /* + * ------------>---------------->--------------- | | | (1) | | | | --->--- + * ------->------- | | | | | (5) | | | | | | --<-- | | | | (2) | | | | | | | + * | | | | (4) | | | | | | | -->-- | | --<-- | ---<--- | | | | | | + * -------<------- | | (3) | -------------<---------------<--------------- + * -->-- + */ + + public Polygon makeNonSimplePolygon3() { + Polygon poly = new Polygon(); + poly.startPath(0, 0); + poly.lineTo(0, 25); + poly.lineTo(35, 25); + poly.lineTo(35, 0); + + poly.startPath(5, 5); + poly.lineTo(5, 15); + poly.lineTo(10, 15); + poly.lineTo(10, 5); + + poly.startPath(40, 0); + poly.lineTo(45, 0); + poly.lineTo(45, 5); + poly.lineTo(40, 5); + + poly.startPath(20, 10); + poly.lineTo(25, 10); + poly.lineTo(25, 15); + poly.lineTo(20, 15); + + poly.startPath(15, 5); + poly.lineTo(15, 20); + poly.lineTo(30, 20); + poly.lineTo(30, 5); + + return poly; + }// done + + public Polygon makeNonSimplePolygon4() { + Polygon poly = new Polygon(); + poly.startPath(0, 0); + poly.lineTo(0, 10); + poly.lineTo(10, 10); + poly.lineTo(10, 0); + poly.lineTo(5, 0); + poly.lineTo(5, 5); + poly.lineTo(5, 0); + + return poly; + }// done + + public Polygon makeNonSimplePolygon6() { + Polygon poly = new Polygon(); + poly.startPath(35.34407570857744, 54.00551247713412); + poly.lineTo(41.07663499357954, 20.0); + poly.lineTo(40.66372033705177, 26.217432321849017); + + poly.startPath(42.81936574509338, 20.0); + poly.lineTo(43.58226670584747, 20.0); + poly.lineTo(39.29611825817084, 22.64634933678729); + poly.lineTo(44.369873312241346, 25.81893670527215); + poly.lineTo(42.68845660737179, 20.0); + poly.lineTo(38.569549792944244, 56.47456192829393); + poly.lineTo(42.79274114188401, 45.45117792578003); + poly.lineTo(41.09512147544657, 70.0); + + return poly; + } + + public Polygon makeNonSimplePolygon7() { + Polygon poly = new Polygon(); + + poly.startPath(41.987895433319686, 53.75822619011542); + poly.lineTo(41.98789542535497, 53.75822618803151); + poly.lineTo(40.15120412113667, 68.12604154722113); + poly.lineTo(37.72272697311022, 67.92767094118877); + poly.lineTo(37.147347454283086, 49.497473094145505); + poly.lineTo(38.636627026664385, 51.036687142232736); + + poly.startPath(39.00920080789793, 62.063425518369016); + poly.lineTo(38.604912643136885, 70.0); + poly.lineTo(40.71826863485308, 43.60337143116787); + poly.lineTo(35.34407570857744, 54.005512477134126); + poly.lineTo(39.29611825817084, 22.64634933678729); + + return poly; + } + + public Polyline makeNonSimplePolyline() { + // This polyline has a short segment + Polyline poly = new Polyline(); + poly.startPath(0, 0); + poly.lineTo(10, 0); + poly.lineTo(10, 10); + poly.lineTo(10, 5); + poly.lineTo(-5, 5); + + return poly; + }// done + + @Test + public void testIsSimpleBasicsPoint() { + boolean result; + // point is always simple + Point pt = new Point(); + result = simplifyOp.isSimpleAsFeature(pt, sr4326, false, null, null); + assertTrue(result); + pt.setXY(0, 0); + result = simplifyOp.isSimpleAsFeature(pt, sr4326, false, null, null); + assertTrue(result); + pt.setXY(100000, 10000); + result = simplifyOp.isSimpleAsFeature(pt, sr4326, false, null, null); + assertTrue(result); + }// done + + @Test + public void testIsSimpleBasicsEnvelope() { + // Envelope is simple, when it's width and height are not degenerate + Envelope env = new Envelope(); + boolean result = simplifyOp.isSimpleAsFeature(env, sr4326, false, null, + null); // Empty is simple + assertTrue(result); + env.setCoords(0, 0, 10, 10); + result = simplifyOp.isSimpleAsFeature(env, sr4326, false, null, null); + assertTrue(result); + // sliver but still simple + env.setCoords(0, 0, 0 + sr4326.getTolerance() * 2, 10); + result = simplifyOp.isSimpleAsFeature(env, sr4326, false, null, null); + assertTrue(result); + // sliver and not simple + env.setCoords(0, 0, 0 + sr4326.getTolerance() * 0.5, 10); + result = simplifyOp.isSimpleAsFeature(env, sr4326, false, null, null); + assertTrue(!result); + }// done + + @Test + public void testIsSimpleBasicsLine() { + Line line = new Line(); + boolean result = simplifyOp.isSimpleAsFeature(line, sr4326, false, + null, null); + assertTrue(!result); + + line.setStart(new Point(0, 0)); + // line.setEndXY(0, 0); + result = simplifyOp.isSimpleAsFeature(line, sr4326, false, null, null); + assertTrue(!result); + line.setEnd(new Point(1, 0)); + result = simplifyOp.isSimpleAsFeature(line, sr4326, false, null, null); + assertTrue(result); + }// done + + @Test + public void testIsSimpleMultiPoint1() { + MultiPoint mp = new MultiPoint(); + boolean result = simplifyOp.isSimpleAsFeature(mp, sr4326, false, null, + null); + assertTrue(result);// empty is simple + result = simplifyOp.isSimpleAsFeature( + simplifyOp.execute(mp, sr4326, false, null), sr4326, false, + null, null); + assertTrue(result); + }// done + + @Test + public void testIsSimpleMultiPoint2FarApart() { + // Two point test: far apart + MultiPoint mp = new MultiPoint(); + mp.add(20, 10); + mp.add(100, 100); + boolean result = simplifyOp.isSimpleAsFeature(mp, sr4326, false, null, + null); + assertTrue(result); + result = simplifyOp.isSimpleAsFeature( + simplifyOp.execute(mp, sr4326, false, null), sr4326, false, + null, null); + assertTrue(result); + assertTrue(mp.getPointCount() == 2); + }// done + + @Test + public void testIsSimpleMultiPointCoincident() { + // Two point test: coincident + MultiPoint mp = new MultiPoint(); + mp.add(100, 100); + mp.add(100, 100); + boolean result = simplifyOp.isSimpleAsFeature(mp, sr4326, false, null, + null); + assertTrue(!result); + MultiPoint mpS; + result = simplifyOp.isSimpleAsFeature( + mpS = (MultiPoint) simplifyOp.execute(mp, sr4326, false, null), + sr4326, false, null, null); + assertTrue(result); + assertTrue(mpS.getPointCount() == 1); + }// done + + @Test + public void testMultiPointSR4326_CR184439() { + OperatorFactoryLocal engine = OperatorFactoryLocal.getInstance(); + OperatorSimplify simpOp = (OperatorSimplify) engine + .getOperator(Operator.Type.Simplify); + NonSimpleResult nonSimpResult = new NonSimpleResult(); + nonSimpResult.m_reason = NonSimpleResult.Reason.NotDetermined; + MultiPoint multiPoint = new MultiPoint(); + multiPoint.add(0, 0); + multiPoint.add(0, 1); + multiPoint.add(0, 0); + Boolean multiPointIsSimple = simpOp.isSimpleAsFeature(multiPoint, + SpatialReference.create(4326), true, nonSimpResult, null); + assertFalse(multiPointIsSimple); + assertTrue(nonSimpResult.m_reason == NonSimpleResult.Reason.Clustering); + assertTrue(nonSimpResult.m_vertexIndex1 == 0); + assertTrue(nonSimpResult.m_vertexIndex2 == 2); + } + + @Test + public void testIsSimpleMultiPointCloserThanTolerance() { + // Two point test: closer than tolerance + MultiPoint mp = new MultiPoint(); + MultiPoint mpS; + mp.add(100, 100); + mp.add(100, 100 + sr4326.getTolerance() * .5); + boolean result = simplifyOp.isSimpleAsFeature(mp, sr4326, false, null, + null); + assertTrue(result); + result = simplifyOp.isSimpleAsFeature( + mpS = (MultiPoint) simplifyOp.execute(mp, sr4326, false, null), + sr4326, false, null, null); + assertTrue(result); + assertTrue(mpS.getPointCount() == 2); + }// done + + @Test + public void testIsSimpleMultiPointFarApart2() { + // 5 point test: far apart + MultiPoint mp = new MultiPoint(); + mp.add(100, 100); + mp.add(100, 101); + mp.add(101, 101); + mp.add(11, 1); + mp.add(11, 14); + MultiPoint mpS; + boolean result = simplifyOp.isSimpleAsFeature(mp, sr4326, false, null, + null); + assertTrue(result); + result = simplifyOp.isSimpleAsFeature( + mpS = (MultiPoint) simplifyOp.execute(mp, sr4326, false, null), + sr4326, false, null, null); + assertTrue(result); + assertTrue(mpS.getPointCount() == 5); + }// done + + @Test + public void testIsSimpleMultiPoint_coincident2() { + // 5 point test: coincident + MultiPoint mp = new MultiPoint(); + mp.add(100, 100); + mp.add(100, 101); + mp.add(100, 100); + mp.add(11, 1); + mp.add(11, 14); + boolean result = simplifyOp.isSimpleAsFeature(mp, sr4326, false, null, + null); + assertTrue(!result); + MultiPoint mpS; + result = simplifyOp.isSimpleAsFeature( + mpS = (MultiPoint) simplifyOp.execute(mp, sr4326, false, null), + sr4326, false, null, null); + assertTrue(result); + assertTrue(mpS.getPointCount() == 4); + assertEquals(mpS.getPoint(0).getX(), 100, 1e-7); + assertEquals(mpS.getPoint(0).getY(), 100, 1e-7); + assertEquals(mpS.getPoint(1).getX(), 100, 1e-7); + assertEquals(mpS.getPoint(1).getY(), 101, 1e-7); + assertEquals(mpS.getPoint(2).getX(), 11, 1e-7); + assertEquals(mpS.getPoint(2).getY(), 1, 1e-7); + assertEquals(mpS.getPoint(3).getX(), 11, 1e-7); + assertEquals(mpS.getPoint(3).getY(), 14, 1e-7); + }// done + + @Test + public void testIsSimpleMultiPointCloserThanTolerance2() { + // 5 point test: closer than tolerance + MultiPoint mp = new MultiPoint(); + mp.add(100, 100); + mp.add(100, 101); + mp.add(100, 100 + sr4326.getTolerance() / 2); + mp.add(11, 1); + mp.add(11, 14); + MultiPoint mpS; + boolean result = simplifyOp.isSimpleAsFeature(mp, sr4326, false, null, + null); + assertTrue(result); + result = simplifyOp.isSimpleAsFeature( + mpS = (MultiPoint) simplifyOp.execute(mp, sr4326, false, null), + sr4326, false, null, null); + assertTrue(result); + assertTrue(mpS.getPointCount() == 5); + }// done + + @Test + public void testIsSimplePolyline() { + Polyline poly = new Polyline(); + boolean result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, + null, null); + assertTrue(result);// empty is simple + } + + @Test + public void testIsSimplePolylineFarApart() { + // Two point test: far apart + Polyline poly = new Polyline(); + poly.startPath(20, 10); + poly.lineTo(100, 100); + boolean result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, + null, null); + assertTrue(result); + } + + @Test + public void testIsSimplePolylineCoincident() { + // Two point test: coincident + Polyline poly = new Polyline(); + poly.startPath(100, 100); + poly.lineTo(100, 100); + boolean result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, + null, null); + assertTrue(!result); + @SuppressWarnings("unused") + Polyline polyS; + result = simplifyOp.isSimpleAsFeature( + polyS = (Polyline) simplifyOp + .execute(poly, sr4326, false, null), sr4326, false, + null, null); + assertTrue(result); + } + + @Test + public void testIsSimplePolylineCloserThanTolerance() { + // Two point test: closer than tolerance + Polyline poly = new Polyline(); + poly.startPath(100, 100); + poly.lineTo(100, 100 + sr4326.getTolerance() / 2); + boolean result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, + null, null); + assertTrue(!result); + @SuppressWarnings("unused") + Polyline polyS; + result = simplifyOp.isSimpleAsFeature( + polyS = (Polyline) simplifyOp + .execute(poly, sr4326, false, null), sr4326, false, + null, null); + assertTrue(result); + } + + @Test + public void testIsSimplePolylineFarApartSelfOverlap0() { + // 3 point test: far apart, self overlapping + Polyline poly = new Polyline(); + poly.startPath(0, 0); + poly.lineTo(100, 100); + poly.lineTo(0, 0); + boolean result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, + null, null); + assertTrue(result);// TO CONFIRM should be false + } + + @Test + public void testIsSimplePolylineSelfIntersect() { + // 4 point test: far apart, self intersecting + Polyline poly = new Polyline(); + poly.startPath(0, 0); + poly.lineTo(100, 100); + poly.lineTo(0, 100); + poly.lineTo(100, 0); + boolean result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, + null, null); + assertTrue(result);// TO CONFIRM should be false + } + + @Test + public void testIsSimplePolylineDegenerateSegment() { + // 4 point test: degenerate segment + Polyline poly = new Polyline(); + poly.startPath(0, 0); + poly.lineTo(100, 100); + poly.lineTo(100, 100 + sr4326.getTolerance() / 2); + poly.lineTo(100, 0); + boolean result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, + null, null); + assertTrue(!result); + @SuppressWarnings("unused") + Polyline polyS; + result = simplifyOp.isSimpleAsFeature( + polyS = (Polyline) simplifyOp + .execute(poly, sr4326, false, null), sr4326, false, + null, null); + assertTrue(result); + { + Polyline other = new Polyline(); + other.startPath(0, 0); + other.lineTo(100, 100); + other.lineTo(100, 0); + other.equals(poly); + } + } + + @Test + public void testIsSimplePolylineFarApartSelfOverlap() { + // 3 point test: far apart, self overlapping + Polyline poly = new Polyline(); + poly.startPath(0, 0); + poly.lineTo(100, 100); + poly.lineTo(0, 0); + boolean result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, + null, null); + assertTrue(result);// TO CONFIRM should be false + } + + @Test + public void testIsSimplePolylineFarApartIntersect() { + // 4 point 2 parts test: far apart, intersecting parts + Polyline poly = new Polyline(); + poly.startPath(0, 0); + poly.lineTo(100, 100); + poly.startPath(100, 0); + poly.lineTo(0, 100); + + boolean result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, + null, null); + assertTrue(result);// TO CONFIRM should be false + } + + @Test + public void testIsSimplePolylineFarApartOverlap2() { + // 4 point 2 parts test: far apart, overlapping parts. second part + // starts where first one ends + Polyline poly = new Polyline(); + poly.startPath(0, 0); + poly.lineTo(100, 100); + poly.startPath(100, 100); + poly.lineTo(0, 100); + + boolean result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, + null, null); + assertTrue(result);// TO CONFIRM should be false + } + + @Test + public void testIsSimplePolylineDegenerateVertical() { + // 3 point test: degenerate vertical line + Polyline poly = new Polyline(); + poly.startPath(0, 0); + poly.lineTo(new Point(100, 100)); + poly.lineTo(new Point(100, 100)); + boolean result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, + null, null); + assertTrue(!result); + Polyline polyS; + result = simplifyOp.isSimpleAsFeature( + polyS = (Polyline) simplifyOp + .execute(poly, sr4326, false, null), sr4326, false, + null, null); + assertTrue(result); + assertTrue(polyS.getPointCount() == 2); + } + + @Test + public void testIsSimplePolylineEmptyPath() { + // TODO: any way to test this? + // Empty path + // Polyline poly = new Polyline(); + // assertTrue(poly.isEmpty()); + // poly.addPath(new Polyline(), 0, true); + // assertTrue(poly.isEmpty()); + // boolean result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, + // null, null); + // assertTrue(result); + } + + @Test + public void testIsSimplePolylineSinglePointInPath() { + // Single point in path + Polyline poly = new Polyline(); + poly.startPath(0, 0); + poly.lineTo(100, 100); + poly.removePoint(0, 1); + boolean result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, + null, null); + assertTrue(!result); + Polyline polyS; + result = simplifyOp.isSimpleAsFeature( + polyS = (Polyline) simplifyOp + .execute(poly, sr4326, false, null), sr4326, false, + null, null); + assertTrue(result); + assertTrue(polyS.isEmpty()); + } + + @Test + public void testIsSimplePolygon() { + Polygon poly = new Polygon(); + boolean result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, + null, null); + assertTrue(result);// empty is simple + result = simplifyOp.isSimpleAsFeature( + simplifyOp.execute(poly, sr4326, false, null), sr4326, false, + null, null); + assertTrue(result);// empty is simple + } + + @Test + public void testIsSimplePolygonEmptyPath() { + // TODO: + // Empty path + // Polygon poly = new Polygon(); + // poly.addPath(new Polyline(), 0, true); + // assertTrue(poly.getPathCount() == 1); + // boolean result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, + // null, + // null); + // assertTrue(result); + // result = simplifyOp.isSimpleAsFeature(simplifyOp.execute(poly, + // sr4326, false, null), sr4326, false, null, null); + // assertTrue(result);// empty is simple + // assertTrue(poly.getPathCount() == 1); + } + + @Test + public void testIsSimplePolygonIncomplete1() { + // Incomplete polygon 1 + Polygon poly = new Polygon(); + poly.startPath(0, 0); + poly.lineTo(100, 100); + // poly.removePoint(0, 1);//TO CONFIRM no removePoint method in Java + boolean result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, + null, null); + assertTrue(!result); + } + + @Test + public void testIsSimplePolygonIncomplete2() { + // Incomplete polygon 2 + Polygon poly = new Polygon(); + poly.startPath(0, 0); + poly.lineTo(100, 100); + boolean result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, + null, null); + assertTrue(!result); + } + + @Test + public void testIsSimplePolygonDegenerateTriangle() { + // Degenerate triangle (self overlap) + Polygon poly = new Polygon(); + poly.startPath(0, 0); + poly.lineTo(100, 100); + poly.lineTo(0, 0); + boolean result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, + null, null); + assertTrue(!result); + } + + @Test + public void testIsSimplePolygonSelfIntersect() { + // Self intersection - cracking is needed + Polygon poly = new Polygon(); + poly.startPath(0, 0); + poly.lineTo(100, 100); + poly.lineTo(0, 100); + poly.lineTo(100, 0); + boolean result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, + null, null); + assertTrue(!result); + } + + @Test + public void testIsSimplePolygonRectangleHole() { + // Rectangle and rectangular hole that has one segment overlapping + // with the with the exterior ring. Cracking is needed. + Polygon poly = new Polygon(); + poly.addEnvelope(new Envelope(-200, -100, 200, 100), false); + poly.addEnvelope(new Envelope(-100, -100, 100, 50), true); + boolean result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, + null, null); + assertTrue(!result); + poly.reverseAllPaths(); + result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, null, null); + assertTrue(!result); + } + + @Test + public void testIsSimplePolygonRectangleHole2() { + // Rectangle and rectangular hole + Polygon poly = new Polygon(); + poly.addEnvelope(new Envelope(-200, -100, 200, 100), false); + poly.addEnvelope(new Envelope(-100, -50, 100, 50), true); + boolean result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, + null, null); + assertTrue(result); + poly.reverseAllPaths(); + result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, null, null); + assertTrue(!result); + } + + @Test + public void testIsSimplePolygonSelfIntersectAtVertex() { + // Self intersection at vertex + Polygon poly = new Polygon(); + poly.startPath(0, 0); + poly.lineTo(50, 50); + poly.lineTo(100, 100); + poly.lineTo(0, 100); + poly.lineTo(50, 50); + poly.lineTo(100, 0); + boolean result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, + null, null); + assertTrue(!result); + result = simplifyOp.isSimpleAsFeature( + simplifyOp.execute(poly, sr4326, false, null), sr4326, false, + null, null); + assertTrue(result); + } + + @Test + public void testIsSimplePolygon_2EdgesTouchAtVertex() { + // No self-intersection, but more than two edges touch at the same + // vertex. Simple for ArcGIS, not simple for OGC + Polygon poly = new Polygon(); + poly.startPath(0, 0); + poly.lineTo(50, 50); + poly.lineTo(0, 100); + poly.lineTo(100, 100); + poly.lineTo(50, 50); + poly.lineTo(100, 0); + boolean result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, + null, null); + assertTrue(result); + } + + @Test + public void testIsSimplePolygonTriangle() { + // Triangle + Polygon poly = new Polygon(); + poly.startPath(0, 0); + poly.lineTo(100, 100); + poly.lineTo(100, 0); + boolean result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, + null, null); + assertTrue(result); + poly.reverseAllPaths(); + result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, null, null); + assertTrue(!result); + } + + @Test + public void testIsSimplePolygonRectangle() { + // Rectangle + Polygon poly = new Polygon(); + poly.addEnvelope(new Envelope(-200, -100, 100, 200), false); + boolean result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, + null, null); + assertTrue(result); + poly.reverseAllPaths(); + result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, null, null); + assertTrue(!result); + } + + @Test + public void testIsSimplePolygonRectangleHoleWrongDirection() { + // Rectangle and rectangular hole that has wrong direction + Polygon poly = new Polygon(); + poly.addEnvelope(new Envelope(-200, -100, 200, 100), false); + poly.addEnvelope(new Envelope(-100, -50, 100, 50), false); + boolean result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, + null, null); + assertTrue(!result); + poly.reverseAllPaths(); + result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, null, null); + assertTrue(!result); + } + + @Test + public void testIsSimplePolygon_2RectanglesSideBySide() { + // Two rectangles side by side, simple + Polygon poly = new Polygon(); + poly.addEnvelope(new Envelope(-200, -100, 200, 100), false); + poly.addEnvelope(new Envelope(220, -50, 300, 50), false); + boolean result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, + null, null); + assertTrue(result); + poly.reverseAllPaths(); + result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, null, null); + assertTrue(!result); + } + + @Test + public void testIsSimplePolygonRectangleOneBelow() { + // Two rectangles one below another, simple + Polygon poly = new Polygon(); + poly.addEnvelope(new Envelope(50, 50, 100, 100), false); + poly.addEnvelope(new Envelope(50, 200, 100, 250), false); + boolean result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, + null, null); + assertTrue(result); + poly.reverseAllPaths(); + result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, null, null); + assertTrue(!result); + } + + @Test + public void testisSimpleOGC() { + Polyline poly = new Polyline(); + poly.startPath(0, 0); + poly.lineTo(10, 0); + boolean result = simplifyOpOGC.isSimpleOGC(poly, sr4326, true, null, + null); + assertTrue(result); + + poly = new Polyline(); + poly.startPath(0, 0); + poly.lineTo(10, 10); + poly.lineTo(0, 10); + poly.lineTo(10, 0); + NonSimpleResult nsr = new NonSimpleResult(); + result = simplifyOpOGC.isSimpleOGC(poly, sr4326, true, nsr, null); + assertTrue(!result); + assertTrue(nsr.m_reason == NonSimpleResult.Reason.Cracking); + + MultiPoint mp = new MultiPoint(); + mp.add(0, 0); + mp.add(10, 0); + result = simplifyOpOGC.isSimpleOGC(mp, sr4326, true, null, null); + assertTrue(result); + + mp = new MultiPoint(); + mp.add(10, 0); + mp.add(10, 0); + nsr = new NonSimpleResult(); + result = simplifyOpOGC.isSimpleOGC(mp, sr4326, true, nsr, null); + assertTrue(!result); + assertTrue(nsr.m_reason == NonSimpleResult.Reason.Clustering); + } + + @Test + public void testPolylineIsSimpleForOGC() throws IOException { + OperatorImportFromJson importerJson = (OperatorImportFromJson) factory + .getOperator(Operator.Type.ImportMapGeometryFromJson); + OperatorSimplify simplify = (OperatorSimplify) factory + .getOperator(Operator.Type.Simplify); + + JsonFactory f = new JsonFactory(); + + { + String s = "{\"paths\":[[[0, 10], [8, 5], [5, 2], [6, 0]]]}"; + Geometry g = importerJson.execute(Geometry.Type.Unknown, + f.createJsonParser(s)).getGeometry(); + boolean res = simplifyOpOGC.isSimpleOGC(g, null, true, null, null); + assertTrue(res); + } + { + String s = "{\"paths\":[[[0, 10], [6, 0], [7, 5], [0, 3]]]}";// self + // intersection + Geometry g = importerJson.execute(Geometry.Type.Unknown, + f.createJsonParser(s)).getGeometry(); + boolean res = simplifyOpOGC.isSimpleOGC(g, null, true, null, null); + assertTrue(!res); + } + + { + String s = "{\"paths\":[[[0, 10], [6, 0], [0, 3], [0, 10]]]}"; // closed + Geometry g = importerJson.execute(Geometry.Type.Unknown, + f.createJsonParser(s)).getGeometry(); + boolean res = simplifyOpOGC.isSimpleOGC(g, null, true, null, null); + assertTrue(res); + } + + { + String s = "{\"paths\":[[[0, 10], [5, 5], [6, 0], [0, 3], [5, 5], [0, 9], [0, 10]]]}"; // closed + // with + // self + // tangent + Geometry g = importerJson.execute(Geometry.Type.Unknown, + f.createJsonParser(s)).getGeometry(); + boolean res = simplifyOpOGC.isSimpleOGC(g, null, true, null, null); + assertTrue(!res); + } + + { + String s = "{\"paths\":[[[0, 10], [5, 2]], [[5, 2], [6, 0]]]}";// two + // paths + // connected + // at + // a + // point + Geometry g = importerJson.execute(Geometry.Type.Unknown, + f.createJsonParser(s)).getGeometry(); + boolean res = simplifyOpOGC.isSimpleOGC(g, null, true, null, null); + assertTrue(res); + } + + { + String s = "{\"paths\":[[[0, 0], [3, 3], [5, 0], [0, 0]], [[0, 10], [3, 3], [10, 10], [0, 10]]]}";// two + // closed + // rings + // touch + // at + // one + // point + Geometry g = importerJson.execute(Geometry.Type.Unknown, + f.createJsonParser(s)).getGeometry(); + boolean res = simplifyOpOGC.isSimpleOGC(g, null, true, null, null); + assertTrue(!res); + } + + { + String s = "{\"paths\":[[[0, 0], [10, 10]], [[0, 10], [10, 0]]]}";// two + // lines + // intersect + Geometry g = importerJson.execute(Geometry.Type.Unknown, + f.createJsonParser(s)).getGeometry(); + boolean res = simplifyOpOGC.isSimpleOGC(g, null, true, null, null); + assertTrue(!res); + } + + { + String s = "{\"paths\":[[[0, 0], [5, 5], [0, 10]], [[10, 10], [5, 5], [10, 0]]]}";// two + // paths + // share + // mid + // point. + Geometry g = importerJson.execute(Geometry.Type.Unknown, + f.createJsonParser(s)).getGeometry(); + boolean res = simplifyOpOGC.isSimpleOGC(g, null, true, null, null); + assertTrue(!res); + } + + } + +} diff --git a/unittest/com/esri/core/geometry/TestTouch.java b/unittest/com/esri/core/geometry/TestTouch.java new file mode 100644 index 00000000..c12d3999 --- /dev/null +++ b/unittest/com/esri/core/geometry/TestTouch.java @@ -0,0 +1,546 @@ +package com.esri.core.geometry; + +import junit.framework.TestCase; +import org.junit.Test; + +public class TestTouch extends TestCase { + @Override + protected void setUp() throws Exception { + super.setUp(); + } + + @Override + protected void tearDown() throws Exception { + super.tearDown(); + } + + @Test + public void testTouchOnPointAndPolyline() { + Geometry baseGeom = new Point(-130, 10); + Polyline pl = new Polyline(); + pl.startPath(new Point(-130, 10)); + pl.lineTo(-131, 15); + pl.lineTo(-140, 20); + + SpatialReference sr = SpatialReference.create(4326); + + boolean isTouched; + boolean isTouched2; + try { + isTouched = GeometryEngine.touches(baseGeom, pl, sr); + isTouched2 = GeometryEngine.touches(pl, baseGeom, sr); + + } catch (IllegalArgumentException ex) { + isTouched = false; + isTouched2 = false; + } + assertEquals(isTouched && isTouched2, true); + + boolean isTouchedFromRest = GeometryUtils + .isRelationTrue( + pl, + baseGeom, + sr, + GeometryUtils.SpatialRelationType.esriGeometryRelationTouch, + ""); + assertTrue(isTouchedFromRest == isTouched); + } + + @Test + public void testTouchOnPointAndPolygon() { + Geometry baseGeom = new Point(-130, 10); + Polygon pg = new Polygon(); + pg.startPath(new Point(-130, 10)); + pg.lineTo(-131, 15); + pg.lineTo(-140, 20); + + SpatialReference sr = SpatialReference.create(4326); + + boolean isTouched; + boolean isTouched2; + try { + isTouched = GeometryEngine.touches(baseGeom, pg, sr); + isTouched2 = GeometryEngine.touches(pg, baseGeom, sr); + + } catch (IllegalArgumentException ex) { + isTouched = false; + isTouched2 = false; + } + assertEquals(isTouched && isTouched2, true); + + boolean isTouchedFromRest = GeometryUtils + .isRelationTrue( + pg, + baseGeom, + sr, + GeometryUtils.SpatialRelationType.esriGeometryRelationTouch, + ""); + assertTrue(isTouchedFromRest == isTouched); + } + + @Test + public void testTouchOnPolygons() { + Polygon pg = new Polygon(); + pg.startPath(new Point(-130, 10)); + pg.lineTo(-131, 15); + pg.lineTo(-140, 20); + + Polygon pg2 = new Polygon(); + pg2.startPath(new Point(-130, 10)); + pg2.lineTo(-131, 15); + pg2.lineTo(-120, 20); + + SpatialReference sr = SpatialReference.create(4326); + + boolean isTouched; + boolean isTouched2; + try { + isTouched = GeometryEngine.touches(pg, pg2, sr); + isTouched2 = GeometryEngine.touches(pg2, pg, sr); + + } catch (IllegalArgumentException ex) { + isTouched = false; + isTouched2 = false; + } + assertEquals(isTouched && isTouched2, true); + + // boolean isTouchedFromRest = GeometryUtils.isRelationTrue(pg2, pg, sr, + // GeometryUtils.SpatialRelationType.esriGeometryRelationTouch, ""); + // assertTrue(isTouchedFromRest==isTouched); + } + + @Test + public void testTouchesOnPolylines() { + SpatialReference sr = SpatialReference.create(4326); + + Polyline basePl = new Polyline(); + basePl.startPath(new Point(-117, 20)); + basePl.lineTo(new Point(-100, 20)); + + basePl.lineTo(new Point(-100, 10)); + basePl.lineTo(new Point(-117, 10)); + basePl.lineTo(new Point(-117, 20)); + + Polyline compPl = new Polyline(); + compPl.startPath(new Point(-104, 20)); + + compPl.lineTo(new Point(-108, 25)); + + compPl.lineTo(new Point(-100, 20)); + // compPl.lineTo(new Point(-100, 30)); + // compPl.lineTo(new Point(-117, 30)); + // compPl.lineTo(new Point(-117, 20)); + + boolean isTouched; + try { + isTouched = GeometryEngine.touches(basePl, compPl, sr); + + } catch (IllegalArgumentException ex) { + isTouched = false; + } + assertEquals(isTouched, true); + boolean isTouchedFromRest = GeometryUtils + .isRelationTrue( + compPl, + basePl, + sr, + GeometryUtils.SpatialRelationType.esriGeometryRelationTouch, + ""); + assertTrue(isTouchedFromRest == isTouched); + } + + @Test + public void testTouchesOnPolylineAndPolygon() { + SpatialReference sr = SpatialReference.create(4326); + + Polygon basePl = new Polygon(); + basePl.startPath(new Point(-117, 20)); + basePl.lineTo(new Point(-100, 20)); + + basePl.lineTo(new Point(-100, 10)); + basePl.lineTo(new Point(-117, 10)); + + Polyline compPl = new Polyline(); + + compPl.startPath(new Point(-117, 20)); + compPl.lineTo(new Point(-108, 25)); + compPl.lineTo(new Point(-100, 20)); + compPl.lineTo(new Point(-100, 30)); + + boolean isTouched; + try { + isTouched = GeometryEngine.touches(basePl, compPl, sr); + } catch (IllegalArgumentException ex) { + isTouched = false; + } + assertEquals(isTouched, true); + + boolean isTouchedFromRest = GeometryUtils + .isRelationTrue( + compPl, + basePl, + sr, + GeometryUtils.SpatialRelationType.esriGeometryRelationTouch, + ""); + assertTrue(isTouchedFromRest == isTouched); + } + + @Test + public void testTouchOnEnvelopes() { + // case1, not touched + // Envelope env = new Envelope(new Point(-117,20), 12, 12); + // Envelope env2 = new Envelope(-100,20,-80,30); + + // case2 touched + Envelope env = new Envelope(new Point(-117, 20), 12, 12); + Envelope env2 = new Envelope(-117, 26, -80, 30); + + SpatialReference sr = SpatialReference.create(4326); + + boolean isTouched; + try { + isTouched = GeometryEngine.touches(env, env2, sr); + } catch (IllegalArgumentException ex) { + isTouched = false; + } + assertEquals(isTouched, true); + + boolean isTouchedFromRest = GeometryUtils + .isRelationTrue( + env, + env2, + sr, + GeometryUtils.SpatialRelationType.esriGeometryRelationTouch, + ""); + assertTrue(isTouchedFromRest == isTouched); + } + + @Test + public void testTouchesOnPolylineAndEnvelope() { + SpatialReference sr = SpatialReference.create(4326); + + Polyline basePl = new Polyline(); + basePl.startPath(new Point(-117, 20)); + basePl.lineTo(new Point(-100, 20)); + + basePl.lineTo(new Point(-100, 10)); + basePl.lineTo(new Point(-117, 10)); + basePl.lineTo(new Point(-117, 20)); + + // Envelope env = new Envelope(new Point(-117,20), 12, 12);//not touched + Envelope env = new Envelope(-100, 20, -80, 30);// touched + + boolean isTouched; + try { + isTouched = GeometryEngine.touches(basePl, env, sr); + } catch (IllegalArgumentException ex) { + isTouched = false; + } + assertEquals(isTouched, true); + + boolean isTouchedFromRest = GeometryUtils + .isRelationTrue( + env, + basePl, + sr, + GeometryUtils.SpatialRelationType.esriGeometryRelationTouch, + ""); + assertTrue(isTouchedFromRest == isTouched); + } + + @Test + public void testTouchesOnPolygonAndEnvelope() { + SpatialReference sr = SpatialReference.create(4326); + + Polygon basePl = new Polygon(); + basePl.startPath(new Point(-117, 20)); + basePl.lineTo(new Point(-100, 20)); + + basePl.lineTo(new Point(-100, 10)); + basePl.lineTo(new Point(-117, 10)); + + // Envelope env = new Envelope(new Point(-117,20), 12, 12);//not touched + Envelope env = new Envelope(-100, 20, -80, 30);// touched + + boolean isTouched; + try { + isTouched = GeometryEngine.touches(basePl, env, sr); + } catch (IllegalArgumentException ex) { + isTouched = false; + } + assertEquals(isTouched, true); + + boolean isTouchedFromRest = GeometryUtils + .isRelationTrue( + env, + basePl, + sr, + GeometryUtils.SpatialRelationType.esriGeometryRelationTouch, + ""); + assertTrue(isTouchedFromRest == isTouched); + } + + @Test + public void testTouchesOnPointAndEnvelope() { + SpatialReference sr = SpatialReference.create(4326); + + Point p = new Point(-130, 10); + + // Envelope env = new Envelope(p, 12, 12);//not touched + Envelope env = new Envelope(-130, 10, -110, 20);// touched + + boolean isTouched; + try { + isTouched = GeometryEngine.touches(p, env, sr); + } catch (IllegalArgumentException ex) { + isTouched = false; + } + assertEquals(isTouched, true); + + boolean isTouchedFromRest = GeometryUtils + .isRelationTrue( + p, + env, + sr, + GeometryUtils.SpatialRelationType.esriGeometryRelationTouch, + ""); + assertTrue(isTouchedFromRest == isTouched); + } + + @Test + public void testRelationTouch() { + SpatialReference sr = SpatialReference.create(4326); + Polyline basePl = new Polyline(); + basePl.startPath(2, 2); + basePl.lineTo(2, 10); + + Polyline compPl = new Polyline(); + compPl.startPath(2, 4); + compPl.lineTo(9, 4); + compPl.lineTo(9, 9); + compPl.lineTo(2, 9); + compPl.lineTo(2, 4); + + boolean isTouched = false;// GeometryEngine.relation(basePl, compPl, sr, + // "G1 TOUCH G2"); + assertEquals(isTouched, false); + + boolean isTouchedFromRest = GeometryUtils + .isRelationTrue( + compPl, + basePl, + sr, + GeometryUtils.SpatialRelationType.esriGeometryRelationTouch, + ""); + assertTrue(isTouchedFromRest == isTouched); + } + + @Test + /** + * test touches between point and polyline + * a point touches a polyline only if the point is + * coincident with one of the polyline end points + * */ + public void testTouchesBetweenPointAndLine() { + SpatialReference sr = SpatialReference.create(4326); + Point p = new Point(2, 4); + + Polyline compPl = new Polyline(); + compPl.startPath(2, 4); + + compPl.lineTo(9, 4); + compPl.lineTo(9, 9); + compPl.lineTo(2, 9); + compPl.lineTo(2, 4); + + boolean isTouched = GeometryEngine.touches(p, compPl, sr); + assertTrue(!isTouched); + + boolean isTouchedFromRest = GeometryUtils + .isRelationTrue( + compPl, + p, + sr, + GeometryUtils.SpatialRelationType.esriGeometryRelationTouch, + ""); + assertTrue(isTouchedFromRest == isTouched); + // We do not treat polyline that is not explicitly closed as closed. + // Keep the case to demonstrate the difference between ArcObjects and + // Borg here. + } + + @Test + /** + * test touches between polyline and polyline + * a polyline touches another polyline only if the end point(s) is + * coincident with the end points of another polyline + * In this test case, the end points of the first polyline are concident + * with two end points of the second polyline + * */ + public void testTouchesBetweenPolylines() { + SpatialReference sr = SpatialReference.create(4326); + Polyline pl = new Polyline(); + pl.startPath(2, 4); + pl.lineTo(9, 9); + + Polyline compPl = new Polyline(); + compPl.startPath(2, 4); + + compPl.lineTo(9, 4); + compPl.lineTo(9, 9); + compPl.lineTo(2, 9); + compPl.lineTo(2, 4); + + boolean isTouched = GeometryEngine.touches(pl, compPl, sr); + assertEquals(isTouched, true); + + boolean isTouchedFromRest = GeometryUtils + .isRelationTrue( + compPl, + pl, + sr, + GeometryUtils.SpatialRelationType.esriGeometryRelationTouch, + ""); + assertTrue(isTouchedFromRest == isTouched); + } + + @Test + /** + * test touches between polyline and polygon + * a polyline touches polygon only if the end point(s) is + * coincident with the vertices of polygon + * In this test case, the end points of the polyline are co-incident + * with two vertices of the polygon which consists of two parts + * */ + public void testTouchesBetweenPolylineAndPolygon() { + SpatialReference sr = SpatialReference.create(4326); + Polyline pl = new Polyline(); + pl.startPath(2, 4); + pl.lineTo(1, 10); + pl.lineTo(6, 12); + + Polygon compPg = new Polygon(); + compPg.startPath(2, 4); + + compPg.lineTo(2, 9); + compPg.lineTo(9, 9); + compPg.lineTo(9, 4); + + compPg.startPath(2, 9); + compPg.lineTo(6, 12); + compPg.lineTo(9, 10); + + boolean isTouched = GeometryEngine.touches(pl, compPg, sr); + assertEquals(isTouched, true); + + boolean isTouchedFromRest = GeometryUtils + .isRelationTrue( + compPg, + pl, + sr, + GeometryUtils.SpatialRelationType.esriGeometryRelationTouch, + ""); + assertTrue(isTouchedFromRest == isTouched); + } + + @Test + /** + * test touches between polylines who consists of two parts + * */ + public void testTouchesBetweenMultipartPolylines() { + SpatialReference sr = SpatialReference.create(4326); + Polyline pl = new Polyline(); + pl.startPath(2, 4); + pl.lineTo(1, 10); + pl.lineTo(6, 12); + + pl.startPath(6, 12); + pl.lineTo(12, 12); + pl.lineTo(9, 9); + + Polyline compPl = new Polyline(); + compPl.startPath(2, 4); + + compPl.lineTo(2, 9); + compPl.lineTo(9, 9); + compPl.lineTo(9, 4); + + compPl.startPath(2, 9); + compPl.lineTo(6, 12); + compPl.lineTo(9, 10); + + boolean isTouched = GeometryEngine.touches(pl, compPl, sr); + assertTrue(!isTouched); + + // boolean isTouchedFromRest = GeometryUtils.isRelationTrue(compPl, pl, + // sr, + // GeometryUtils.SpatialRelationType.esriGeometryRelationTouch, ""); + // assertTrue(isTouchedFromRest == isTouched); + } + + @Test + /** + * test touches between polygons who consists of two parts + * */ + public void testTouchesBetweenMultipartPolygons2() { + SpatialReference sr = SpatialReference.create(4326); + Polygon pl = new Polygon(); + pl.startPath(2, 4); + pl.lineTo(1, 9); + pl.lineTo(2, 6); + + pl.startPath(2, 9); + pl.lineTo(6, 14); + pl.lineTo(6, 12); + + Polygon compPl = new Polygon(); + compPl.startPath(2, 4); + + compPl.lineTo(2, 9); + compPl.lineTo(9, 9); + compPl.lineTo(9, 4); + + compPl.startPath(2, 9); + compPl.lineTo(6, 12); + compPl.lineTo(9, 10); + + boolean isTouched = GeometryEngine.touches(pl, compPl, sr); + assertEquals(isTouched, true); + + boolean isTouchedFromRest = GeometryUtils + .isRelationTrue( + compPl, + pl, + sr, + GeometryUtils.SpatialRelationType.esriGeometryRelationTouch, + ""); + assertTrue(isTouchedFromRest == isTouched); + } + + @Test + public void testTouchPointLineCR183227() { + // Tests CR 183227 + Geometry baseGeom = new Point(-130, 10); + Polyline pl = new Polyline(); + // pl.startPath(new Point(-130, 10)); + pl.startPath(-130, 10); + pl.lineTo(-131, 15); + pl.lineTo(-140, 20); + + SpatialReference sr = SpatialReference.create(4326); + + boolean isTouched; + boolean isTouched2; + isTouched = GeometryEngine.touches(baseGeom, pl, sr); + isTouched2 = GeometryEngine.touches(pl, baseGeom, sr); + assertTrue(isTouched && isTouched2); + { + Geometry baseGeom2 = (Geometry) new Point(-131, 15); + boolean bIsTouched; + boolean bIsTouched2; + bIsTouched = GeometryEngine.touches(baseGeom2, pl, sr); + bIsTouched2 = GeometryEngine.touches(pl, baseGeom2, sr); + assertTrue(!bIsTouched && !bIsTouched2); + } + } +} diff --git a/unittest/com/esri/core/geometry/TestTreap.java b/unittest/com/esri/core/geometry/TestTreap.java new file mode 100644 index 00000000..3bd20606 --- /dev/null +++ b/unittest/com/esri/core/geometry/TestTreap.java @@ -0,0 +1,81 @@ +package com.esri.core.geometry; + +import junit.framework.TestCase; +import org.junit.Test; + +public class TestTreap extends TestCase { + @Override + protected void setUp() throws Exception { + super.setUp(); + } + + @Override + protected void tearDown() throws Exception { + super.tearDown(); + } + + @Test + public static void test1() { + Point2D[] pts = new Point2D[10]; + + for (int i = 0; i < 10; i++) { + Point2D pt = new Point2D(); + pt.x = i; + pt.y = 0; + + pts[i] = pt; + } + + TreapComparatorForTesting c = new TreapComparatorForTesting(pts); + Treap treap = new Treap(); + treap.setComparator(c); + + int[] nodes = new int[10]; + for (int i = 0; i < 10; i++) + nodes[i] = treap.addElement(i, -1); + + for (int i = 1; i < 10; i++) { + assertTrue(treap.getPrev(nodes[i]) == nodes[i - 1]); + } + + for (int i = 0; i < 9; i++) { + assertTrue(treap.getNext(nodes[i]) == nodes[i + 1]); + } + + treap.deleteNode(nodes[0], -1); + treap.deleteNode(nodes[2], -1); + treap.deleteNode(nodes[4], -1); + treap.deleteNode(nodes[6], -1); + treap.deleteNode(nodes[8], -1); + + assertTrue(treap.getPrev(nodes[3]) == nodes[1]); + assertTrue(treap.getPrev(nodes[5]) == nodes[3]); + assertTrue(treap.getPrev(nodes[7]) == nodes[5]); + assertTrue(treap.getPrev(nodes[9]) == nodes[7]); + + assertTrue(treap.getNext(nodes[1]) == nodes[3]); + assertTrue(treap.getNext(nodes[3]) == nodes[5]); + assertTrue(treap.getNext(nodes[5]) == nodes[7]); + assertTrue(treap.getNext(nodes[7]) == nodes[9]); + } +} + +final class TreapComparatorForTesting extends Treap.Comparator { + Point2D[] m_pts; + + TreapComparatorForTesting(Point2D[] pts) { + m_pts = pts; + } + + @Override + int compare(Treap treap, int elm, int node) { + int elm2 = treap.getElement(node); + Point2D pt1 = m_pts[elm]; + Point2D pt2 = m_pts[elm2]; + + if (pt1.x < pt2.x) + return -1; + + return 1; + } +} diff --git a/unittest/com/esri/core/geometry/TestUnion.java b/unittest/com/esri/core/geometry/TestUnion.java new file mode 100644 index 00000000..94f3ef86 --- /dev/null +++ b/unittest/com/esri/core/geometry/TestUnion.java @@ -0,0 +1,39 @@ +package com.esri.core.geometry; + +import junit.framework.TestCase; +import org.junit.Test; + +public class TestUnion extends TestCase { + @Override + protected void setUp() throws Exception { + super.setUp(); + } + + @Override + protected void tearDown() throws Exception { + super.tearDown(); + } + + @Test + public static void testUnion() { + Point pt = new Point(10, 20); + System.out.println(pt.getX()); + + Point pt2 = new Point(); + pt2.setXY(10, 10); + + Envelope env1 = new Envelope(10, 10, 30, 50); + Envelope env2 = new Envelope(30, 10, 60, 50); + Geometry[] geomArray = new Geometry[] { env1, env2 }; + SimpleGeometryCursor inputGeometries = new SimpleGeometryCursor( + geomArray); + OperatorUnion union = (OperatorUnion) OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Union); + + SpatialReference sr = SpatialReference.create(4326); + + GeometryCursor outputCursor = union.execute(inputGeometries, sr, null); + Geometry result = outputCursor.next(); + System.out.println(result); + } +} diff --git a/unittest/com/esri/core/geometry/TestWKBSupport.java b/unittest/com/esri/core/geometry/TestWKBSupport.java new file mode 100644 index 00000000..622030ce --- /dev/null +++ b/unittest/com/esri/core/geometry/TestWKBSupport.java @@ -0,0 +1,101 @@ +package com.esri.core.geometry; + +import java.io.IOException; +import java.nio.ByteBuffer; +import org.codehaus.jackson.JsonFactory; +import org.codehaus.jackson.JsonParseException; +import org.codehaus.jackson.JsonParser; +import junit.framework.TestCase; +import org.junit.Test; + +//import com.vividsolutions.jts.io.WKBReader; + +public class TestWKBSupport extends TestCase { + @Override + protected void setUp() throws Exception { + super.setUp(); + } + + @Override + protected void tearDown() throws Exception { + super.tearDown(); + } + + @Test + public void testWKB() { + try { + // JSON -> GEOM -> WKB + + String strPolygon1 = "{\"xmin\":-1.1663479012889031E7,\"ymin\":4919777.494405342,\"xmax\":-1.1658587043078788E7,\"ymax\":4924669.464215587,\"spatialReference\":{\"wkid\":102100}}"; + // String strPolygon1 = + // "{\"rings\":[[[-119.152450421001,38.4118009590513],[-119.318825070203,38.5271086243914],[-119.575687062955,38.7029101298904],[-119.889341639399,38.9222515603984],[-119.995254694357,38.9941061536377],[-119.995150114198,39.0634913594691],[-119.994541258334,39.1061318056708],[-119.995527335641,39.1587132866355],[-119.995304181493,39.3115454332125],[-119.996011479298,39.4435009764511],[-119.996165311172,39.7206108077274],[-119.996324660047,41.1775662656441],[-119.993459369715,41.9892049531992],[-119.351692186077,41.9888529749781],[-119.3109421304,41.9891353872811],[-118.185316829038,41.9966370981387],[-117.018864363596,41.9947941808341],[-116.992313337997,41.9947945094663],[-115.947544658193,41.9945994628997],[-115.024862911148,41.996506455953],[-114.269471632824,41.9959242345073],[-114.039072662345,41.9953908974688],[-114.038151248682,40.9976868405942],[-114.038108189376,40.1110466529553],[-114.039844684228,39.9087788600023],[-114.040105338584,39.5386849268845],[-114.044267501155,38.6789958815881],[-114.045090206153,38.5710950539539],[-114.047272999176,38.1376524399918],[-114.047260595159,37.5984784866001],[-114.043939384154,36.9965379371421],[-114.043716435713,36.8418489458647],[-114.037392074194,36.2160228969702],[-114.045105557286,36.1939778840226],[-114.107775185788,36.1210907070504],[-114.12902308363,36.041730493896],[-114.206768869568,36.0172554164834],[-114.233472615347,36.0183310595897],[-114.307587598189,36.0622330993643],[-114.303857056018,36.0871084040611],[-114.316095374696,36.1114380366653],[-114.344233941709,36.1374802520568],[-114.380803116644,36.1509912717765],[-114.443945697733,36.1210532841897],[-114.466613475422,36.1247112590539],[-114.530573568745,36.1550902046725],[-114.598935242024,36.1383354528834],[-114.621610747198,36.1419666834504],[-114.712761724737,36.1051810523675],[-114.728150311069,36.0859627711604],[-114.728966012834,36.0587530361083],[-114.717673567756,36.0367580437018],[-114.736212493583,35.9876483502758],[-114.699275906446,35.9116119537412],[-114.661600122152,35.8804735854242],[-114.662462095522,35.8709599070091],[-114.689867343369,35.8474424944766],[-114.682739704595,35.7647034175617],[-114.688820027649,35.7325957399896],[-114.665091345861,35.6930994107107],[-114.668486064922,35.6563989882404],[-114.654065925137,35.6465840800053],[-114.6398667219,35.6113485698329],[-114.653134321223,35.5848331056108],[-114.649792053474,35.5466373866597],[-114.672215155693,35.5157541647721],[-114.645396168451,35.4507608261463],[-114.589584275424,35.3583787306827],[-114.587889840369,35.30476812919],[-114.559583045727,35.2201828714608],[-114.561039964054,35.1743461616313],[-114.572255261053,35.1400677445931],[-114.582616239058,35.1325604694085],[-114.626440825485,35.1339067529872],[-114.6359090842,35.1186557767895],[-114.595631971944,35.0760579746697],[-114.633779872695,35.0418633504303],[-114.621068606189,34.9989144286133],[-115.626197382816,35.7956983148418],[-115.88576934392,36.0012259572723],[-117.160423771838,36.9595941441767],[-117.838686423167,37.457298239715],[-118.417419755966,37.8866767486211],[-119.152450421001,38.4118009590513]]], \"spatialReference\":{\"wkid\":4326}}"; + + JsonFactory factory = new JsonFactory(); + JsonParser parser = factory.createJsonParser(strPolygon1); + parser.nextToken(); + MapGeometry mapGeom = GeometryEngine.jsonToGeometry(parser); + Geometry geom = mapGeom.getGeometry(); + OperatorExportToWkb operatorExport = (OperatorExportToWkb) OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.ExportToWkb); + ByteBuffer byteBuffer = operatorExport.execute(0, geom, null); + byte[] wkb = byteBuffer.array(); + + // WKB -> GEOM -> JSON + OperatorImportFromWkb operatorImport = (OperatorImportFromWkb) OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.ImportFromWkb); + geom = operatorImport.execute(0, Geometry.Type.Polygon, + ByteBuffer.wrap(wkb), null); + // geom = operatorImport.execute(0, Geometry.Type.Polygon, + // byteBuffer); + String outputPolygon1 = GeometryEngine.geometryToJson(-1, geom); + System.out.println(strPolygon1); + System.out.println(outputPolygon1); + } catch (JsonParseException ex) { + } catch (IOException ex) { + } + + } + + @Test + public void testWKB2() throws Exception { + // JSON -> GEOM -> WKB + + // String strPolygon1 = + // "{\"xmin\":-1.16605115291E7,\"ymin\":4925189.941699997,\"xmax\":-1.16567772126E7,\"ymax\":4928658.771399997,\"spatialReference\":{\"wkid\":102100}}"; + String strPolygon1 = "{\"rings\" : [ [ [-1.16605115291E7,4925189.941699997], [-1.16567772126E7,4925189.941699997], [-1.16567772126E7,4928658.771399997], [-1.16605115291E7,4928658.771399997], [-1.16605115291E7,4925189.941699997] ] ], \"spatialReference\" : {\"wkid\" : 102100}}"; + + JsonFactory factory = new JsonFactory(); + JsonParser parser = factory.createJsonParser(strPolygon1); + parser.nextToken(); + MapGeometry mapGeom = GeometryEngine.jsonToGeometry(parser); + Geometry geom = mapGeom.getGeometry(); + + // simplifying geom + OperatorSimplify operatorSimplify = (OperatorSimplify) OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Simplify); + SpatialReference sr = SpatialReference.create(102100); + geom = operatorSimplify.execute(geom, sr, true, null); + + OperatorExportToWkb operatorExport = (OperatorExportToWkb) OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.ExportToWkb); + ByteBuffer byteBuffer = operatorExport.execute(0, geom, null); + byte[] wkb = byteBuffer.array(); + + // // checking WKB correctness + // WKBReader jtsReader = new WKBReader(); + // com.vividsolutions.jts.geom.Geometry jtsGeom = jtsReader.read(wkb); + // System.out.println("jtsGeom = " + jtsGeom); + + // WKB -> GEOM -> JSON + OperatorImportFromWkb operatorImport = (OperatorImportFromWkb) OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.ImportFromWkb); + geom = operatorImport.execute(0, Geometry.Type.Polygon, + ByteBuffer.wrap(wkb), null); + assertTrue(!geom.isEmpty()); + // geom = operatorImport.execute(0, Geometry.Type.Polygon, byteBuffer); + // String outputPolygon1 = GeometryEngine.geometryToJson(-1, geom); + // System.out.println(strPolygon1); + // System.out.println(outputPolygon1); + + } +} diff --git a/unittest/com/esri/core/geometry/TestWkbImportOnPostgresST.java b/unittest/com/esri/core/geometry/TestWkbImportOnPostgresST.java new file mode 100644 index 00000000..ccac6c5f --- /dev/null +++ b/unittest/com/esri/core/geometry/TestWkbImportOnPostgresST.java @@ -0,0 +1,48 @@ +package com.esri.core.geometry; + +import java.nio.ByteBuffer; +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import junit.framework.TestCase; +import org.junit.Test; + +public class TestWkbImportOnPostgresST extends TestCase { + @Override + protected void setUp() throws Exception { + super.setUp(); + } + + @Override + protected void tearDown() throws Exception { + super.tearDown(); + } + + @Test + public static void testWkbImportOnPostgresST() throws Exception { + try { + Connection con = DriverManager.getConnection( + "jdbc:postgresql://tb.esri.com:5432/new_gdb", "tb", "tb"); + OperatorFactoryLocal factory = OperatorFactoryLocal.getInstance(); + OperatorImportFromWkb operatorImport = (OperatorImportFromWkb) factory + .getOperator(Operator.Type.ImportFromWkb); + String stmt = "SELECT objectid,sde.st_asbinary(shape) FROM new_gdb.tb.interstates a WHERE objectid IN (2) AND (a.shape IS NULL OR sde.st_geometrytype(shape)::text IN ('ST_MULTILINESTRING','ST_LINESTRING')) LIMIT 1000"; + PreparedStatement ps = con.prepareStatement(stmt); + ResultSet rs = ps.executeQuery(); + while (rs.next()) { + byte[] rsWkbGeom = rs.getBytes(2); + @SuppressWarnings("unused") + Geometry geomBorg = null; + if (rsWkbGeom != null) { + geomBorg = operatorImport.execute(0, Geometry.Type.Unknown, + ByteBuffer.wrap(rsWkbGeom), null); + } + } + + ps.close(); + con.close(); + } catch (Exception e) { + } + } +} diff --git a/unittest/com/esri/core/geometry/TestWkid.java b/unittest/com/esri/core/geometry/TestWkid.java new file mode 100644 index 00000000..fc09a66c --- /dev/null +++ b/unittest/com/esri/core/geometry/TestWkid.java @@ -0,0 +1,22 @@ +package com.esri.core.geometry; + +import static org.junit.Assert.*; +import junit.framework.TestCase; + +import org.junit.Test; + +public class TestWkid extends TestCase { + @Test + public void test() { + SpatialReference sr = SpatialReference.create(102100); + assertTrue(sr.getID() == 102100); + assertTrue(sr.getLatestID() == 3857); + assertTrue(sr.getOldID() == 102100); + assertTrue(sr.getTolerance() == 0.001); + + SpatialReference sr84 = SpatialReference.create(4326); + double tol84 = sr84.getTolerance(); + assertTrue(Math.abs(tol84 - 1e-8) < 1e-8 * 1e-8); + } + +} diff --git a/unittest/com/esri/core/geometry/TestWktParser.java b/unittest/com/esri/core/geometry/TestWktParser.java new file mode 100644 index 00000000..9949e5a3 --- /dev/null +++ b/unittest/com/esri/core/geometry/TestWktParser.java @@ -0,0 +1,1087 @@ +package com.esri.core.geometry; + +import static org.junit.Assert.*; +import junit.framework.TestCase; + +import org.junit.Test; + +public class TestWktParser extends TestCase { + + public void testGeometryCollection() { + String s = " geometrycollection emPty "; + WktParser wktParser = new WktParser(); + wktParser.resetParser(s); + + int currentToken; + double value; + + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.geometrycollection); + + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.empty); + + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.not_available); + + s = " geometrycollection zm ( geometrycollection zm ( POINT ZM ( 5. +1.e+0004 13 17) ), LineString zm emPty, MULTIpolyGON zM (((1 1 1 1))) ) "; + wktParser.resetParser(s); + + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.geometrycollection); + + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.attribute_zm); + + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.left_paren); + + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.geometrycollection); + + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.attribute_zm); + + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.left_paren); + + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.point); + + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.attribute_zm); + + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.left_paren); + + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.x_literal); + + value = wktParser.currentNumericLiteral(); + assertTrue(value == 5.0); + + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.y_literal); + + value = wktParser.currentNumericLiteral(); + assertTrue(value == 1.0e4); + + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.z_literal); + + value = wktParser.currentNumericLiteral(); + assertTrue(value == 13); + + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.m_literal); + + value = wktParser.currentNumericLiteral(); + assertTrue(value == 17); + + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.right_paren); + + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.right_paren); + + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.linestring); + + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.attribute_zm); + + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.empty); + + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.multipolygon); + + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.attribute_zm); + + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.left_paren); + + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.left_paren); + + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.left_paren); + + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.x_literal); + + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.y_literal); + + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.z_literal); + + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.m_literal); + + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.right_paren); + + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.right_paren); + + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.right_paren); + + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.right_paren); + + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.not_available); + } + + public void testMultiPolygon() { + String s = " MultIPolYgOn emPty "; + WktParser wktParser = new WktParser(); + wktParser.resetParser(s); + + int currentToken; + double value; + + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.multipolygon); + + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.empty); + + s = " MULTIpolyGON zM ( empty , ( empty, ( 5. +1.e+0004 13 17, -.1e07 .006 13 17 ) , empty , (4 003. 13 17, 02E-3 .3e2 13 17) ) , empty, ( ( 5. +1.e+0004 13 17, -.1e07 .006 13 17) , (4 003. 13 17 , 02E-3 .3e2 13 17) ) ) "; + wktParser.resetParser(s); + + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.multipolygon); + + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.attribute_zm); + + // Start first polygon + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.left_paren); + + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.empty); + + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.left_paren); + + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.empty); + + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.left_paren); + + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.x_literal); + + value = wktParser.currentNumericLiteral(); + assertTrue(value == 5.0); + + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.y_literal); + + value = wktParser.currentNumericLiteral(); + assertTrue(value == 1.0e4); + + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.z_literal); + + value = wktParser.currentNumericLiteral(); + assertTrue(value == 13); + + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.m_literal); + + value = wktParser.currentNumericLiteral(); + assertTrue(value == 17); + + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.x_literal); + + value = wktParser.currentNumericLiteral(); + assertTrue(value == -0.1e7); + + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.y_literal); + + value = wktParser.currentNumericLiteral(); + assertTrue(value == 0.006); + + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.z_literal); + + value = wktParser.currentNumericLiteral(); + assertTrue(value == 13); + + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.m_literal); + + value = wktParser.currentNumericLiteral(); + assertTrue(value == 17); + + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.right_paren); + + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.empty); + + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.left_paren); + + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.x_literal); + + value = wktParser.currentNumericLiteral(); + assertTrue(value == 4.0); + + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.y_literal); + + value = wktParser.currentNumericLiteral(); + assertTrue(value == 3.0); + + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.z_literal); + + value = wktParser.currentNumericLiteral(); + assertTrue(value == 13); + + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.m_literal); + + value = wktParser.currentNumericLiteral(); + assertTrue(value == 17); + + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.x_literal); + + value = wktParser.currentNumericLiteral(); + assertTrue(value == 2.0e-3); + + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.y_literal); + + value = wktParser.currentNumericLiteral(); + assertTrue(value == 0.3e2); + + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.z_literal); + + value = wktParser.currentNumericLiteral(); + assertTrue(value == 13); + + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.m_literal); + + value = wktParser.currentNumericLiteral(); + assertTrue(value == 17); + + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.right_paren); + + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.right_paren); + + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.empty); + + // End of First polygon + + // Start Second Polygon + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.left_paren); + + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.left_paren); + + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.x_literal); + + value = wktParser.currentNumericLiteral(); + assertTrue(value == 5.0); + + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.y_literal); + + value = wktParser.currentNumericLiteral(); + assertTrue(value == 1.0e4); + + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.z_literal); + + value = wktParser.currentNumericLiteral(); + assertTrue(value == 13); + + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.m_literal); + + value = wktParser.currentNumericLiteral(); + assertTrue(value == 17); + + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.x_literal); + + value = wktParser.currentNumericLiteral(); + assertTrue(value == -0.1e7); + + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.y_literal); + + value = wktParser.currentNumericLiteral(); + assertTrue(value == 0.006); + + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.z_literal); + + value = wktParser.currentNumericLiteral(); + assertTrue(value == 13); + + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.m_literal); + + value = wktParser.currentNumericLiteral(); + assertTrue(value == 17); + + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.right_paren); + + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.left_paren); + + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.x_literal); + + value = wktParser.currentNumericLiteral(); + assertTrue(value == 4.0); + + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.y_literal); + + value = wktParser.currentNumericLiteral(); + assertTrue(value == 3.0); + + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.z_literal); + + value = wktParser.currentNumericLiteral(); + assertTrue(value == 13); + + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.m_literal); + + value = wktParser.currentNumericLiteral(); + assertTrue(value == 17); + + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.x_literal); + + value = wktParser.currentNumericLiteral(); + assertTrue(value == 2.0e-3); + + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.y_literal); + + value = wktParser.currentNumericLiteral(); + assertTrue(value == 0.3e2); + + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.z_literal); + + value = wktParser.currentNumericLiteral(); + assertTrue(value == 13); + + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.m_literal); + + value = wktParser.currentNumericLiteral(); + assertTrue(value == 17); + + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.right_paren); + + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.right_paren); + + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.right_paren); + + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.not_available); + } + + public void testMultiLineString() { + String s = " MultiLineString emPty "; + WktParser wktParser = new WktParser(); + wktParser.resetParser(s); + + int currentToken; + double value; + + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.multilinestring); + + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.empty); + + s = " MultiLineString Z ( empty, ( 5. +1.e+0004 13, -.1e07 .006 13 ) , empty , (4 003. 13 , 02E-3 .3e2 13 ) , empty, ( 5. +1.e+0004 13 , -.1e07 .006 13) , (4 003. 13 , 02E-3 .3e2 13 ) ) "; + wktParser.resetParser(s); + + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.multilinestring); + + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.attribute_z); + + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.left_paren); + + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.empty); + + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.left_paren); + + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.x_literal); + + value = wktParser.currentNumericLiteral(); + assertTrue(value == 5.0); + + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.y_literal); + + value = wktParser.currentNumericLiteral(); + assertTrue(value == 1.0e4); + + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.z_literal); + + value = wktParser.currentNumericLiteral(); + assertTrue(value == 13); + + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.x_literal); + + value = wktParser.currentNumericLiteral(); + assertTrue(value == -0.1e7); + + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.y_literal); + + value = wktParser.currentNumericLiteral(); + assertTrue(value == 0.006); + + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.z_literal); + + value = wktParser.currentNumericLiteral(); + assertTrue(value == 13); + + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.right_paren); + + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.empty); + + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.left_paren); + + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.x_literal); + + value = wktParser.currentNumericLiteral(); + assertTrue(value == 4.0); + + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.y_literal); + + value = wktParser.currentNumericLiteral(); + assertTrue(value == 3.0); + + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.z_literal); + + value = wktParser.currentNumericLiteral(); + assertTrue(value == 13); + + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.x_literal); + + value = wktParser.currentNumericLiteral(); + assertTrue(value == 2.0e-3); + + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.y_literal); + + value = wktParser.currentNumericLiteral(); + assertTrue(value == 0.3e2); + + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.z_literal); + + value = wktParser.currentNumericLiteral(); + assertTrue(value == 13); + + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.right_paren); + + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.empty); + + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.left_paren); + + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.x_literal); + + value = wktParser.currentNumericLiteral(); + assertTrue(value == 5.0); + + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.y_literal); + + value = wktParser.currentNumericLiteral(); + assertTrue(value == 1.0e4); + + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.z_literal); + + value = wktParser.currentNumericLiteral(); + assertTrue(value == 13); + + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.x_literal); + + value = wktParser.currentNumericLiteral(); + assertTrue(value == -0.1e7); + + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.y_literal); + + value = wktParser.currentNumericLiteral(); + assertTrue(value == 0.006); + + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.z_literal); + + value = wktParser.currentNumericLiteral(); + assertTrue(value == 13); + + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.right_paren); + + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.left_paren); + + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.x_literal); + + value = wktParser.currentNumericLiteral(); + assertTrue(value == 4.0); + + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.y_literal); + + value = wktParser.currentNumericLiteral(); + assertTrue(value == 3.0); + + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.z_literal); + + value = wktParser.currentNumericLiteral(); + assertTrue(value == 13); + + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.x_literal); + + value = wktParser.currentNumericLiteral(); + assertTrue(value == 2.0e-3); + + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.y_literal); + + value = wktParser.currentNumericLiteral(); + assertTrue(value == 0.3e2); + + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.z_literal); + + value = wktParser.currentNumericLiteral(); + assertTrue(value == 13); + + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.right_paren); + + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.right_paren); + + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.not_available); + } + + public void testMultiPoint() { + String s = " MultipoInt emPty "; + WktParser wktParser = new WktParser(); + wktParser.resetParser(s); + + int currentToken; + double value; + + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.multipoint); + + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.empty); + + s = " Multipoint Z ( empty, ( 5. +1.e+0004 13 ), (-.1e07 .006 13 ) , empty , (4 003. 13 ), (02E-3 .3e2 13 ) ) "; + wktParser.resetParser(s); + + currentToken = wktParser.nextToken(); // bug here + assertTrue(currentToken == WktParser.WktToken.multipoint); + + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.attribute_z); + + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.left_paren); + + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.empty); + + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.left_paren); + + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.x_literal); + + value = wktParser.currentNumericLiteral(); + assertTrue(value == 5.0); + + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.y_literal); + + value = wktParser.currentNumericLiteral(); + assertTrue(value == 1.0e4); + + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.z_literal); + + value = wktParser.currentNumericLiteral(); + assertTrue(value == 13); + + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.right_paren); + + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.left_paren); + + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.x_literal); + + value = wktParser.currentNumericLiteral(); + assertTrue(value == -0.1e7); + + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.y_literal); + + value = wktParser.currentNumericLiteral(); + assertTrue(value == 0.006); + + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.z_literal); + + value = wktParser.currentNumericLiteral(); + assertTrue(value == 13); + + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.right_paren); + + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.empty); + + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.left_paren); + + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.x_literal); + + value = wktParser.currentNumericLiteral(); + assertTrue(value == 4.0); + + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.y_literal); + + value = wktParser.currentNumericLiteral(); + assertTrue(value == 3.0); + + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.z_literal); + + value = wktParser.currentNumericLiteral(); + assertTrue(value == 13); + + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.right_paren); + + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.left_paren); + + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.x_literal); + + value = wktParser.currentNumericLiteral(); + assertTrue(value == 2.0e-3); + + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.y_literal); + + value = wktParser.currentNumericLiteral(); + assertTrue(value == 0.3e2); + + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.z_literal); + + value = wktParser.currentNumericLiteral(); + assertTrue(value == 13); + + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.right_paren); + + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.right_paren); + + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.not_available); + } + + public void testPolygon() { + String s = " Polygon emPty "; + WktParser wktParser = new WktParser(); + wktParser.resetParser(s); + + int currentToken; + double value; + + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.polygon); + + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.empty); + + s = " polyGON M ( empty, ( 5. +1.e+0004 13, -.1e07 .006 13 ) , empty , (4 003. 13 , 02E-3 .3e2 13 ) , empty, ( 5. +1.e+0004 13 , -.1e07 .006 13) , (4 003. 13 , 02E-3 .3e2 13 ) ) "; + wktParser.resetParser(s); + + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.polygon); + + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.attribute_m); + + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.left_paren); + + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.empty); + + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.left_paren); + + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.x_literal); + + value = wktParser.currentNumericLiteral(); + assertTrue(value == 5.0); + + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.y_literal); + + value = wktParser.currentNumericLiteral(); + assertTrue(value == 1.0e4); + + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.m_literal); + + value = wktParser.currentNumericLiteral(); + assertTrue(value == 13); + + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.x_literal); + + value = wktParser.currentNumericLiteral(); + assertTrue(value == -0.1e7); + + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.y_literal); + + value = wktParser.currentNumericLiteral(); + assertTrue(value == 0.006); + + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.m_literal); + + value = wktParser.currentNumericLiteral(); + assertTrue(value == 13); + + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.right_paren); + + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.empty); + + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.left_paren); + + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.x_literal); + + value = wktParser.currentNumericLiteral(); + assertTrue(value == 4.0); + + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.y_literal); + + value = wktParser.currentNumericLiteral(); + assertTrue(value == 3.0); + + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.m_literal); + + value = wktParser.currentNumericLiteral(); + assertTrue(value == 13); + + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.x_literal); + + value = wktParser.currentNumericLiteral(); + assertTrue(value == 2.0e-3); + + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.y_literal); + + value = wktParser.currentNumericLiteral(); + assertTrue(value == 0.3e2); + + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.m_literal); + + value = wktParser.currentNumericLiteral(); + assertTrue(value == 13); + + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.right_paren); + + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.empty); + + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.left_paren); + + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.x_literal); + + value = wktParser.currentNumericLiteral(); + assertTrue(value == 5.0); + + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.y_literal); + + value = wktParser.currentNumericLiteral(); + assertTrue(value == 1.0e4); + + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.m_literal); + + value = wktParser.currentNumericLiteral(); + assertTrue(value == 13); + + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.x_literal); + + value = wktParser.currentNumericLiteral(); + assertTrue(value == -0.1e7); + + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.y_literal); + + value = wktParser.currentNumericLiteral(); + assertTrue(value == 0.006); + + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.m_literal); + + value = wktParser.currentNumericLiteral(); + assertTrue(value == 13); + + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.right_paren); + + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.left_paren); + + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.x_literal); + + value = wktParser.currentNumericLiteral(); + assertTrue(value == 4.0); + + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.y_literal); + + value = wktParser.currentNumericLiteral(); + assertTrue(value == 3.0); + + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.m_literal); + + value = wktParser.currentNumericLiteral(); + assertTrue(value == 13); + + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.x_literal); + + value = wktParser.currentNumericLiteral(); + assertTrue(value == 2.0e-3); + + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.y_literal); + + value = wktParser.currentNumericLiteral(); + assertTrue(value == 0.3e2); + + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.m_literal); + + value = wktParser.currentNumericLiteral(); + assertTrue(value == 13); + + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.right_paren); + + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.right_paren); + + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.not_available); + } + + public void testLineString() { + String s = " LineString emPty "; + WktParser wktParser = new WktParser(); + wktParser.resetParser(s); + + int currentToken; + double value; + + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.linestring); + + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.empty); + + s = " LineString ( 5. +1.e+0004 , -.1e07 .006 ) "; + wktParser.resetParser(s); + + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.linestring); + + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.left_paren); + + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.x_literal); + + value = wktParser.currentNumericLiteral(); + assertTrue(value == 5.0); + + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.y_literal); + + value = wktParser.currentNumericLiteral(); + assertTrue(value == 1.0e4); + + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.x_literal); + + value = wktParser.currentNumericLiteral(); + assertTrue(value == -0.1e7); + + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.y_literal); + + value = wktParser.currentNumericLiteral(); + assertTrue(value == 0.006); + + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.right_paren); + + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.not_available); + } + + public void testPoint() { + String s = " PoInT emPty "; + WktParser wktParser = new WktParser(); + wktParser.resetParser(s); + + int currentToken; + double value; + + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.point); + + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.empty); + + s = " POINT ZM ( 5. +1.e+0004 13 17) "; + wktParser.resetParser(s); + + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.point); + + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.attribute_zm); + + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.left_paren); + + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.x_literal); + + value = wktParser.currentNumericLiteral(); + assertTrue(value == 5.0); + + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.y_literal); + + value = wktParser.currentNumericLiteral(); + assertTrue(value == 1.0e4); + + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.z_literal); + + value = wktParser.currentNumericLiteral(); + assertTrue(value == 13); + + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.m_literal); + + value = wktParser.currentNumericLiteral(); + assertTrue(value == 17); + + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.right_paren); + + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.not_available); + + s = " PoInt "; + wktParser.resetParser(s); + + wktParser.nextToken(); + } + +} diff --git a/unittest/com/esri/core/geometry/Utils.java b/unittest/com/esri/core/geometry/Utils.java new file mode 100644 index 00000000..a984cfc5 --- /dev/null +++ b/unittest/com/esri/core/geometry/Utils.java @@ -0,0 +1,73 @@ +package com.esri.core.geometry; + +public class Utils { + static void showProjectedGeometryInfo(MapGeometry mapGeom) { + System.out.println("\n"); + MapGeometry geom = mapGeom; + int wkid = geom.getSpatialReference() != null ? geom + .getSpatialReference().getID() : -1; + // while ((geom = geomCursor.next()) != null) { + + if (geom.getGeometry() instanceof Point) { + Point pnt = (Point) geom.getGeometry(); + System.out + .println("Point(" + pnt.getX() + " , " + pnt.getY() + ")"); + if (geom.getSpatialReference() == null) + System.out.println("No spatial reference"); + else + System.out.println("wkid: " + + geom.getSpatialReference().getID()); + + } else if (geom.getGeometry() instanceof MultiPoint) { + MultiPoint mp = (MultiPoint) geom.getGeometry(); + System.out.println("Multipoint has " + mp.getPointCount() + + " points."); + + System.out.println("wkid: " + wkid); + + } else if (geom.getGeometry() instanceof Polygon) { + Polygon mp = (Polygon) geom.getGeometry(); + System.out.println("Polygon has " + mp.getPointCount() + + " points and " + mp.getPathCount() + " parts."); + if (mp.getPathCount() > 1) { + System.out.println("Part start of 2nd segment : " + + mp.getPathStart(1)); + System.out.println("Part end of 2nd segment : " + + mp.getPathEnd(1)); + System.out.println("Part size of 2nd segment : " + + mp.getPathSize(1)); + + int start = mp.getPathStart(1); + int end = mp.getPathEnd(1); + for (int i = start; i < end; i++) { + Point pp = mp.getPoint(i); + System.out.println("Point(" + i + ") = (" + pp.getX() + + ", " + pp.getY() + ")"); + } + } + System.out.println("wkid: " + wkid); + + } else if (geom.getGeometry() instanceof Polyline) { + Polyline mp = (Polyline) geom.getGeometry(); + System.out.println("Polyline has " + mp.getPointCount() + + " points and " + mp.getPathCount() + " parts."); + System.out.println("Part start of 2nd segment : " + + mp.getPathStart(1)); + System.out.println("Part end of 2nd segment : " + + mp.getPathEnd(1)); + System.out.println("Part size of 2nd segment : " + + mp.getPathSize(1)); + int start = mp.getPathStart(1); + int end = mp.getPathEnd(1); + for (int i = start; i < end; i++) { + Point pp = mp.getPoint(i); + System.out.println("Point(" + i + ") = (" + pp.getX() + ", " + + pp.getY() + ")"); + } + + System.out.println("wkid: " + wkid); + } + + } + +} diff --git a/unittest/com/esri/core/geometry/savedAngularUnit.txt b/unittest/com/esri/core/geometry/savedAngularUnit.txt new file mode 100644 index 0000000000000000000000000000000000000000..2b9bfe8dca643403f9ea8ca29e99d2bcda9f0d5b GIT binary patch literal 277 zcmaKmJqp4=5JpEq6blQFAkj*8S6Pf=Wnm#i8?_RVMV7FVY{Iya#KIGKHSb^sf3R|j zDdzi__wEmF>|CGqeG6gcfNs(qwa+#lK;RuFq_kMGBwKu~R$% literal 0 HcmV?d00001 diff --git a/unittest/com/esri/core/geometry/savedAreaUnit.txt b/unittest/com/esri/core/geometry/savedAreaUnit.txt new file mode 100644 index 0000000000000000000000000000000000000000..aa57dabf241fa3d849c1adee3beaae4c681f138f GIT binary patch literal 272 zcmZ4UmVvdnh(R?uKUXicxF}OEIlm}XFFiFsH?^dwQZF4nF%7WAq1~ng`1^OTh^g$Nr z!z|DTS)c+UKxvju@HiQ6BGknULJSNQMGSl(N9utbsppzknu{z6w3x-QC^ZoPlPpq3 literal 0 HcmV?d00001 diff --git a/unittest/com/esri/core/geometry/savedEnvelope.txt b/unittest/com/esri/core/geometry/savedEnvelope.txt new file mode 100644 index 0000000000000000000000000000000000000000..b162e5cb7a3b818b5daf56226a8c1d8606f3fc8f GIT binary patch literal 532 zcmZ4UmVvdnh(R+sKUXicxF}OEIlm}XFFiFsH?^dwQqLXA2u>}^Ow7rwN-bi507fPT zMjr+qm_nDtlEe}Q8y}!C`XFQULB{CAWc4wPQE^HvPK61BbX!*x0Nr6lv^y{in0|hb z&zT2xsZ0z^(F~j*M+9di7NnK{xlTwf4XFf)sS$Fi3P>H$O{GO3{cApp|6t>k}d!M literal 0 HcmV?d00001 diff --git a/unittest/com/esri/core/geometry/savedLinearUnit.txt b/unittest/com/esri/core/geometry/savedLinearUnit.txt new file mode 100644 index 0000000000000000000000000000000000000000..76b083bc3d2a231ee86bb105b92c4e319ac3c245 GIT binary patch literal 259 zcmZ4UmVvdnh(R?uKUXicxF}OEIlm}XFFiFsH?^dwQZF4nF%7WAq1~ng`1^OTh^g$Nr z!z|DTS)c+UK}^Ow7rwN-bi507fPT zMjr+qm_nDtlEe}Q8y}!C`XFQULB{CAWc4wPQE^HvPK61BbX!*x0Nr6pv^!LMOLIyx z!%~Y%QY%3Ef5o`TtYTTYh>3y8lYy%^Gq)fo)h#D6-Gza(BtIv$C^0WNwW5f@ig*jq zT{!*x9-lK0>QaF=L^E)Ld>NdPSddx*5{y5cc4JhBE_$iUWjc2c_enw2K2!F%;ldQ&+^GLcq&lm%vOC0y?0GfiEku iEKx5fF)v-uHLo-mS+bynfeRe;0r{DECI5i{CJO*RG_}^Ow7rwN-bi507fPT zMjr+qm_nDtlEe}Q8y}!C`XFQULB{CAWc4wPQE^HvPK61BbX!*x0Nr6lv^y{in0|hb z&zT2xsZ0z^(F~j*M+9di7NnK{xlTwf4XFf)sS$Fi3P>H$O{GO3{cApp|6tMlHWj8-2xwRl17B8RS)yJ}VqUtQYhGzCvSdLC L18YEjW?l&ZqMC=u literal 0 HcmV?d00001 diff --git a/unittest/com/esri/core/geometry/savedPolygon.txt b/unittest/com/esri/core/geometry/savedPolygon.txt new file mode 100644 index 0000000000000000000000000000000000000000..4444df6e56ca522ac78e5f033a5a95933295870f GIT binary patch literal 734 zcmZ4UmVvdnh(R+sKUXicxF}OEIlm}XFFiFsH?^dwQqLXA2u>}^Ow7rwN-bi507fPT zMjr+qm_nDtlEe}Q8y}!C`XFQULB{CAWc4wPQE^HvPK61BbX!*x0Nr6uv^!LMOLIyx z0}@LzK)TN_%Xi#+Mg9)ZRnZI_`RU1SIf?1TB@9f_Q58iDmL!@QmReMjS^+ZmSB#s? zDwdUtm>8Hm8Mulwa|?1(K}NeUaF*ofq!uOSC8q){up-_9bbn7jzsKjygSu3p4bcpo zps)+hNGwPNIo$~DjJE<-XzVdC zs5n5FFgg>;k91%JO0YmAp(IQVjE;n>g(yJih45kOR2=Gx7*q%Z4cNs{ix`A}4lQEf z%StRu)XPcCOV@MFE6qigEGS`M56I7{OwZ2)3NzX6j|9tty&DzYuV5*%BlZ!@yNrwg D$>i9_ literal 0 HcmV?d00001 diff --git a/unittest/com/esri/core/geometry/savedPolyline.txt b/unittest/com/esri/core/geometry/savedPolyline.txt new file mode 100644 index 0000000000000000000000000000000000000000..0f1cd6feb2aef5eed811818ad58d1ce54e89a3d2 GIT binary patch literal 719 zcmZ4UmVvdnh(R+sKUXicxF}OEIlm}XFFiFsH?^dwQqLXA2u>}^Ow7rwN-bi507fPT zMjr+qm_nDtlEe}Q8y}!C`XFQULB{CAWc4wPQE^HvPK61BbX!*x0Nr6uv^!LMOLIyx z0}@LzK)TN_%Xi#+Mg9)ZRnZI_`RU1SIf?1TB@9f_Q58iDmL!@QmReMjS^+ZmSB#s? zDwdUtm>8Hm8Mulwa|?1(K}NeUaF*ofq!uOSC8q){up-_9bbn7jzsKjygSu3p4bcpo zps)+hNGwPNIo$~DjJE`4XzVdC zs5n5FFgg>;k91%JN+8sMRKe81=t#I)kSqfOLT_CWg9?G50J{*PkAXo5=&&LNzO2Nu xM7^BEymUR+ywY4`$$}CFj)44}%ACx+)c-&LlLPxND!gC8Qe;Q$Bbfgf82}p3+Vubc literal 0 HcmV?d00001 diff --git a/unittest/com/esri/core/geometry/savedProjectionTransformation.txt b/unittest/com/esri/core/geometry/savedProjectionTransformation.txt new file mode 100644 index 0000000000000000000000000000000000000000..51ab119cd27cef68b79fb531a065e28a1b113385 GIT binary patch literal 1847 zcmd5+&2G~`5Z?SCsv@yOD4)wxh7}2pOb~dKO3&=-I!Sz=tf>IYe;bGz^Aq=-223~dfwj!cd zMAXVn)XL5rg`*Mm;#1;h!*6%NV-m&_W?KwwE(>GFoQoG&c!((ueaug-L=%=C`v9&$ zaT@c|UN0%_pCs~XEOfytyXi#2!1L~zckWdtj06>vQtz@$61JV@T^AlGJaF!re)_rb z<%jpR1PUEcPI&*OG4EgDqRiLQU+$s3q)~Ysi^pJvgy)zte9{G_i}#FywQHV%7mg}6 zqa+-0l7JO|wOa>m$1;0;=}p^qkqj@VI!y?WqaXtGcEds*Vh!s)?hEjCv7er>;5X&(in88Ra>-T<nXk70M0dSfi{>P^e!?v~Y;-n!)HE6CP*dftS`8|yT(82qA>N4i?>q`_ zLNe0S>R>SCVnR!le|K)=iHHxgGP{g11Mt4%=Y_-ZdoqSSa$A+yE#k5QaBRd*~&H+;ds=Xsxjg0V^j2E+xX)vW;>;Xtgy{*wn18VQCefMoxjd?2SI@=@<9(1eW)#;!4Tjj^9G_G`?S%;>L-Z=gaHDmJDD%3)nJX8 zH5m^x!-a*ZKb4$zv*o$I+j4xr>Fur2bDuync= Date: Thu, 21 Mar 2013 17:22:30 -0700 Subject: [PATCH 018/196] adding json.org java parser jar file --- DepFiles/public/java-json.jar | Bin 0 -> 42100 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 DepFiles/public/java-json.jar diff --git a/DepFiles/public/java-json.jar b/DepFiles/public/java-json.jar new file mode 100644 index 0000000000000000000000000000000000000000..35f1446b83520963ff832aa23ceb7c1b02ef3491 GIT binary patch literal 42100 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 Date: Fri, 22 Mar 2013 10:43:04 -0700 Subject: [PATCH 019/196] Fixing incorrect links in README.md --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index f8cafd2f..4ccc4ccd 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # geometry-api-java -This is the Esri Geometry API that can be used to enable spatial data processing in 3rd-party data-processing solutions. Developers of custom MapReduce-based applications for Hadoop can use this API for spatial processing of data in the Hadoop system. The API is also used by the [Hive UDF’s](https://github.com/esri/hive-spatial) and could be used by developers building geometry functions for 3rd-party applications such as [Cassandra]( https://cassandra.apache.org/), [HBase](http://hbase.apache.org/), [Storm](http://storm-project.net/) and many other Java-based “big data” applications. +This is the Esri Geometry API for Java that can be used to enable spatial data processing in 3rd-party data-processing solutions. Developers of custom MapReduce-based applications for Hadoop can use this API for spatial processing of data in the Hadoop system. The API is also used by the [Hive UDF’s](https://github.com/Esri/spatial-framework-for-hadoop) and could be used by developers building geometry functions for 3rd-party applications such as [Cassandra]( https://cassandra.apache.org/), [HBase](http://hbase.apache.org/), [Storm](http://storm-project.net/) and many other Java-based “big data” applications. ## Features * API methods to create simple geometries directly with API, or by importing from supported formats: JSON, WKT, Shape @@ -16,7 +16,7 @@ This is the Esri Geometry API that can be used to enable spatial data processing ## Requirements * Java JDK 1.6 or greater -* Experience developing MapReduce applications for [Hadoop](http://hadoop.apache.org/). +* Experience developing MapReduce applications for [Apache Hadoop](http://hadoop.apache.org/). * Familiarity with text formats of spatial data, such as JSON or WKT, would be useful. ## Resources @@ -50,6 +50,6 @@ limitations under the License. A copy of the license is available in the repository's [license.txt]( https://raw.github.com/Esri/geometry-api-java/master/license.txt) file. -[](Esri Tags: ArcGIS, Java, Geometry) +[](Esri Tags: ArcGIS, Java, Geometry, Relationship, Analysis, JSON, WKT, Shape) [](Esri Language: Java) From 33e332948bda164ebb98a86f0426d46634f76dbc Mon Sep 17 00:00:00 2001 From: Erik Hoel Date: Fri, 22 Mar 2013 11:36:36 -0700 Subject: [PATCH 020/196] Update README.md --- README.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 4ccc4ccd..357ada6f 100644 --- a/README.md +++ b/README.md @@ -1,23 +1,23 @@ # geometry-api-java -This is the Esri Geometry API for Java that can be used to enable spatial data processing in 3rd-party data-processing solutions. Developers of custom MapReduce-based applications for Hadoop can use this API for spatial processing of data in the Hadoop system. The API is also used by the [Hive UDF’s](https://github.com/Esri/spatial-framework-for-hadoop) and could be used by developers building geometry functions for 3rd-party applications such as [Cassandra]( https://cassandra.apache.org/), [HBase](http://hbase.apache.org/), [Storm](http://storm-project.net/) and many other Java-based “big data” applications. +The Esri Geometry API for Java can be used to enable spatial data processing in 3rd-party data-processing solutions. Developers of custom MapReduce-based applications for Hadoop can use this API for spatial processing of data in the Hadoop system. The API is also used by the [Hive UDF’s](https://github.com/Esri/spatial-framework-for-hadoop) and could be used by developers building geometry functions for 3rd-party applications such as [Cassandra]( https://cassandra.apache.org/), [HBase](http://hbase.apache.org/), [Storm](http://storm-project.net/) and many other Java-based “big data” applications. ## Features -* API methods to create simple geometries directly with API, or by importing from supported formats: JSON, WKT, Shape -* API methods for spatial operations: union, difference, intersect, clip, cut, buffer -* API methods for topological relationship tests: equals, within, contains, crosses, touches +* API methods to create simple geometries directly with the API, or by importing from supported formats: JSON, WKT, and Shape +* API methods for spatial operations: union, difference, intersect, clip, cut, and buffer +* API methods for topological relationship tests: equals, within, contains, crosses, and touches ## Instructions -1. Download and unzip the .zip file or clone the repository. -2. Deploy esri-geometry-api.jar to the target system, add a reference to it in a Java project. +1. Download and unzip the .zip file, or clone the repository. +2. Deploy the esri-geometry-api.jar to the target system, add a reference to it in a Java project. 3. To build the jar, Javadoc, and run the unit-tests, run the “ant” command-line command from within the cloned directory. The ant tool runs the “build.xml” script which creates the jar, runs the unit tests, then creates the Javadoc documentation files. ## Requirements -* Java JDK 1.6 or greater +* Java JDK 1.6 or greater. * Experience developing MapReduce applications for [Apache Hadoop](http://hadoop.apache.org/). -* Familiarity with text formats of spatial data, such as JSON or WKT, would be useful. +* Familiarity with text-based spatial data formats such as JSON or WKT would be useful. ## Resources From e278c25d112d1e7d24c7f4067eb224c6f75e7903 Mon Sep 17 00:00:00 2001 From: David Kaiser Date: Fri, 22 Mar 2013 12:56:19 -0700 Subject: [PATCH 021/196] adding exclusions for Eclipse project files --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index 96fc06a9..6c74d54d 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,6 @@ bin/ results/ javadoc/ esri-geometry-api.jar +.project +.classpath +description.jardesc From 10d812bc4f5b5b8d4a5a82b4e752057363e5fe2c Mon Sep 17 00:00:00 2001 From: Sergey Tolstov Date: Fri, 22 Mar 2013 15:19:24 -0700 Subject: [PATCH 022/196] added *.bak to ignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 6c74d54d..6d29d23b 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,4 @@ esri-geometry-api.jar .project .classpath description.jardesc +*.bak From e7e798108541a5339cc31fa9cd3a5f2080c71812 Mon Sep 17 00:00:00 2001 From: Sergey Tolstov Date: Fri, 22 Mar 2013 16:58:39 -0700 Subject: [PATCH 023/196] Fix a few javadoc warnings --- .../esri/core/geometry/GeometryEngine.java | 1587 +++++----- .../core/geometry/OperatorConvexHull.java | 6 +- .../core/geometry/OperatorImportFromWkb.java | 2 +- src/com/esri/core/geometry/Segment.java | 2 +- .../esri/core/geometry/VertexDescription.java | 6 +- unittest/com/esri/core/geometry/TestCut.java | 1040 +++---- .../com/esri/core/geometry/TestFailed.java | 130 +- .../com/esri/core/geometry/TestGeodetic.java | 144 +- ...omToJSonExportSRFromWkiOrWkt_CR181369.java | 1158 +++---- .../esri/core/geometry/TestJSonGeometry.java | 94 +- .../TestJSonToGeomFromWkiOrWkt_CR177613.java | 246 +- .../esri/core/geometry/TestJsonParser.java | 1326 ++++---- .../esri/core/geometry/TestSerialization.java | 588 ++-- .../com/esri/core/geometry/TestSimplify.java | 2656 ++++++++--------- .../esri/core/geometry/TestWKBSupport.java | 202 +- 15 files changed, 4591 insertions(+), 4596 deletions(-) diff --git a/src/com/esri/core/geometry/GeometryEngine.java b/src/com/esri/core/geometry/GeometryEngine.java index d6cf628f..c016f3d6 100644 --- a/src/com/esri/core/geometry/GeometryEngine.java +++ b/src/com/esri/core/geometry/GeometryEngine.java @@ -1,796 +1,791 @@ -/* - Copyright 1995-2013 Esri - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - - For additional information, contact: - Environmental Systems Research Institute, Inc. - Attn: Contracts Dept - 380 New York Street - Redlands, California, USA 92373 - - email: contracts@esri.com - */ - -package com.esri.core.geometry; - -import java.nio.ByteBuffer; -import java.nio.ByteOrder; -import java.util.ArrayList; -import org.codehaus.jackson.JsonParser; -import org.json.JSONException; - -/** - * Provides services that operate on geometry instances. - */ -public class GeometryEngine { - - private static OperatorFactoryLocal factory = OperatorFactoryLocal - .getInstance(); - - /** - * Imports the MapGeometry from its JSON representation. M and Z values are - * not imported from JSON representation. - * - * @param json - * The JSON representation of the geometry (with spatial - * reference). - * @return The MapGeometry instance containing the imported geometry and its - * spatial reference. - */ - public static MapGeometry jsonToGeometry(JsonParser json) { - OperatorImportFromJson importerJson = (OperatorImportFromJson) factory - .getOperator(Operator.Type.ImportMapGeometryFromJson); - - MapGeometry geom = importerJson.execute(Geometry.Type.Unknown, json); - return geom; - } - - /** - * Exports the specified geometry instance to it's JSON representation. - * - * @see GeometryEngine#geometryToJson(SpatialReference spatialiReference, - * Geometry geometry) - * @param wkid - * The spatial reference Well Known ID to be used for the JSON - * representation. - * @param geometry - * The geometry to be exported to JSON. - * @return The JSON representation of the specified Geometry. - */ - public static String geometryToJson(int wkid, Geometry geometry) { - return GeometryEngine.geometryToJson( - wkid > 0 ? SpatialReference.create(wkid) : null, geometry); - } - - /** - * Exports the specified geometry instance to it's JSON representation. M - * and Z values are not imported from JSON representation. - * - * @param spatialReference - * The spatial reference of associated object. - * @param geometry - * The geometry. - * @return The JSON representation of the specified geometry. - */ - public static String geometryToJson(SpatialReference spatialReference, - Geometry geometry) { - OperatorExportToJson exporter = (OperatorExportToJson) factory - .getOperator(Operator.Type.ExportToJson); - - return exporter.execute(spatialReference, geometry); - } - - /** - * Imports geometry from the ESRI shape file format. - * - * @param esriShapeBuffer - * The buffer containing geometry in the ESRI shape file format. - * @param geometryType - * The required type of the Geometry to be imported. Use - * Geometry.Type.Unknown if the geometry type needs to be - * determined from the buffer content. - * @return The geometry or null if the buffer contains null shape. - * @throws GeometryException - * when the geometryType is not Geometry.Type.Unknown and the - * buffer contains geometry that cannot be converted to the - * given geometryType. or the buffer is corrupt. Another - * exception possible is IllegalArgumentsException. - */ - public static Geometry geometryFromEsriShape(byte[] esriShapeBuffer, - Geometry.Type geometryType) { - OperatorImportFromESRIShape op = (OperatorImportFromESRIShape) factory - .getOperator(Operator.Type.ImportFromESRIShape); - return op - .execute( - ShapeImportFlags.ShapeImportNonTrusted, - geometryType, - ByteBuffer.wrap(esriShapeBuffer).order( - ByteOrder.LITTLE_ENDIAN)); - } - - /** - * Exports geometry to the ESRI shape file format. - * - * @param geometry - * The geometry to export. (null value is not allowed) - * @return Array containing the exported ESRI shape file. - */ - public static byte[] geometryToEsriShape(Geometry geometry) { - if (geometry == null) - throw new IllegalArgumentException(); - OperatorExportToESRIShape op = (OperatorExportToESRIShape) factory - .getOperator(Operator.Type.ExportToESRIShape); - return op.execute(0, geometry).array(); - } - - /** - * Imports a geometry from a WKT string. - * @param wkt The string containing the geometry in WKT format. - * @param importFlags Use the {@link WktImportFlags} interface. - * @param geometryType The required type of the Geometry to be imported. Use Geometry.Type.Unknown if the geometry type needs to be determined from the WKT context. - * @return The geometry. - * @throws GeometryException when the geometryType is not Geometry.Type.Unknown and the WKT contains a geometry that cannot be converted to the given geometryType. - * @throws IllegalArgument exception if an error is found while parsing the WKT string. - */ - public static Geometry geometryFromWkt(String wkt, int importFlags, - Geometry.Type geometryType) { - OperatorImportFromWkt op = (OperatorImportFromWkt) factory - .getOperator(Operator.Type.ImportFromWkt); - return op.execute(importFlags, geometryType, wkt, null); - } - - /** - * Imports a geometry from a geoJson string. - * @param geoJson The string containing the geometry in geoJson format. - * @param importFlags Use the {@link geoJsonImportFlags} interface. - * @param geometryType The required type of the Geometry to be imported. Use Geometry.Type.Unknown if the geometry type needs to be determined from the geoJson context. - * @return The geometry. - * @throws GeometryException when the geometryType is not Geometry.Type.Unknown and the geoJson contains a geometry that cannot be converted to the given geometryType. - * @throws IllegalArgument exception if an error is found while parsing the geoJson string. - */ - public static MapGeometry geometryFromGeoJson(String geoJson, - int importFlags, Geometry.Type geometryType) throws JSONException { - OperatorImportFromGeoJson op = (OperatorImportFromGeoJson) factory - .getOperator(Operator.Type.ImportFromGeoJson); - return op.execute(importFlags, geometryType, geoJson, null); - } - - /** - * Exports a geometry to a string in WKT format. - * @param geometry The geometry to export. (null value is not allowed) - * @param exportFlags Use the {@link WktExportFlags} interface. - * @return A String containing the exported geometry in WKT format. - */ - public static String geometryToWkt(Geometry geometry, int exportFlags) { - OperatorExportToWkt op = (OperatorExportToWkt) factory - .getOperator(Operator.Type.ExportToWkt); - return op.execute(exportFlags, geometry, null); - } - - /** - * Constructs a new geometry by union an array of geometries. All inputs - * must be of the same type of geometries and share one spatial reference. - * - * @param geometries - * The geometries to union. - * @param spatialReference - * The spatial reference of the geometries. - * @return The geometry object representing the resultant union. - */ - public static Geometry union(Geometry[] geometries, - SpatialReference spatialReference) { - OperatorUnion op = (OperatorUnion) factory - .getOperator(Operator.Type.Union); - - SimpleGeometryCursor inputGeometries = new SimpleGeometryCursor( - geometries); - GeometryCursor result = op.execute(inputGeometries, spatialReference, - null); - return result.next(); - } - - // TODO Remove this method from geometry engine - /** - * constructs the set-theoretic difference between an array of geometries - * and another geometry. The dimension of the input geometry has to be equal - * to or greater than that of any element in the array of "geometries". - * - * @param inputGeometries - * an array of geometry objects being subtracted - * @param subtractor - * geometry object to subtract from - * @return any array of geometry objects showing the difference - * */ - static Geometry[] difference(Geometry[] inputGeometries, - Geometry subtractor, SpatialReference spatialReference) { - OperatorDifference op = (OperatorDifference) factory - .getOperator(Operator.Type.Difference); - SimpleGeometryCursor inputGeometriesCursor = new SimpleGeometryCursor( - inputGeometries); - SimpleGeometryCursor subtractorCursor = new SimpleGeometryCursor( - subtractor); - GeometryCursor result = op.execute(inputGeometriesCursor, - subtractorCursor, spatialReference, null); - - ArrayList resultGeoms = new ArrayList(); - Geometry g; - while ((g = result.next()) != null) { - resultGeoms.add(g); - } - Geometry[] resultarr = resultGeoms.toArray(new Geometry[0]); - return resultarr; - } - - /** - * Creates the difference of two geometries. The dimension of geometry2 has - * to be equal to or greater than that of geometry1. - * - * @param geometry1 - * The geometry being subtracted. - * @param substractor - * The geometry object to subtract from. - * @param spatialReference - * The spatial reference of the geometries. - * @return The geometry of the differences. - */ - public static Geometry difference(Geometry geometry1, Geometry substractor, - SpatialReference spatialReference) { - OperatorDifference op = (OperatorDifference) factory - .getOperator(Operator.Type.Difference); - Geometry result = op.execute(geometry1, substractor, spatialReference, - null); - return result; - } - - /** - * Creates the symmetric difference of two geometries. - * - * @param leftGeometry - * is one of the Geometry instances in the XOR operation. - * @param rightGeometry - * is one of the Geometry instances in the XOR operation. - * @param spatialReference - * The spatial reference of the geometries. - * @return Returns the result of the symmetric difference. - */ - public static Geometry symmetricDifference(Geometry leftGeometry, - Geometry rightGeometry, SpatialReference spatialReference) { - OperatorSymmetricDifference op = (OperatorSymmetricDifference) factory - .getOperator(Operator.Type.SymmetricDifference); - Geometry result = op.execute(leftGeometry, rightGeometry, - spatialReference, null); - return result; - } - - /** - * Indicates if two geometries are equal. - * - * @param geometry1 - * Geometry. - * @param geometry2 - * Geometry. - * @param spatialReference - * The spatial reference of the geometries. - * @return TRUE if both geometry objects are equal. - */ - public static boolean equals(Geometry geometry1, Geometry geometry2, - SpatialReference spatialReference) { - OperatorEquals op = (OperatorEquals) factory - .getOperator(Operator.Type.Equals); - boolean result = op.execute(geometry1, geometry2, spatialReference, - null); - return result; - } - - public static boolean disjoint(Geometry geometry1, Geometry geometry2, - SpatialReference spatialReference) { - OperatorDisjoint op = (OperatorDisjoint) factory - .getOperator(Operator.Type.Disjoint); - boolean result = op.execute(geometry1, geometry2, spatialReference, - null); - return result; - } - - /** - * Constructs the set-theoretic intersection between an array of geometries - * and another geometry. - * - * @param inputGeometries - * An array of geometry objects. - * @param geometry - * The geometry object. - * @return Any array of geometry objects showing the intersection. - */ - static Geometry[] intersect(Geometry[] inputGeometries, Geometry geometry, - SpatialReference spatialReference) { - OperatorIntersection op = (OperatorIntersection) factory - .getOperator(Operator.Type.Intersection); - SimpleGeometryCursor inputGeometriesCursor = new SimpleGeometryCursor( - inputGeometries); - SimpleGeometryCursor intersectorCursor = new SimpleGeometryCursor( - geometry); - GeometryCursor result = op.execute(inputGeometriesCursor, - intersectorCursor, spatialReference, null); - - ArrayList resultGeoms = new ArrayList(); - Geometry g; - while ((g = result.next()) != null) { - resultGeoms.add(g); - } - - Geometry[] resultarr = resultGeoms.toArray(new Geometry[0]); - return resultarr; - } - - /** - * Creates a geometry through intersection between two geometries. - * - * @param geometry1 - * The first geometry. - * @param intersector - * The geometry to intersect the first geometry. - * @param spatialReference - * The spatial reference of the geometries. - * @return The geometry created through intersection. - */ - public static Geometry intersect(Geometry geometry1, Geometry intersector, - SpatialReference spatialReference) { - OperatorIntersection op = (OperatorIntersection) factory - .getOperator(Operator.Type.Intersection); - Geometry result = op.execute(geometry1, intersector, spatialReference, - null); - return result; - } - - /** - * Indicates if one geometry is within another geometry. - * - * @param geometry1 - * The base geometry that is tested for within relationship to - * the other geometry. - * @param geometry2 - * The comparison geometry that is tested for the contains - * relationship to the other geometry. - * @param spatialReference - * The spatial reference of the geometries. - * @return TRUE if the first geometry is within the other geometry. - */ - public static boolean within(Geometry geometry1, Geometry geometry2, - SpatialReference spatialReference) { - OperatorWithin op = (OperatorWithin) factory - .getOperator(Operator.Type.Within); - boolean result = op.execute(geometry1, geometry2, spatialReference, - null); - return result; - } - - /** - * Indicates if one geometry contains another geometry. - * - * @param geometry1 - * The geometry that is tested for the contains relationship to - * the other geometry.. - * @param geometry2 - * The geometry that is tested for within relationship to the - * other geometry. - * @param spatialReference - * The spatial reference of the geometries. - * @return TRUE if geometry1 contains geometry2. - */ - public static boolean contains(Geometry geometry1, Geometry geometry2, - SpatialReference spatialReference) { - OperatorContains op = (OperatorContains) factory - .getOperator(Operator.Type.Contains); - boolean result = op.execute(geometry1, geometry2, spatialReference, - null); - return result; - } - - /** - * Indicates if one geometry crosses another geometry. - * - * @param geometry1 - * The geometry to cross. - * @param geometry2 - * The geometry being crossed. - * @param spatialReference - * The spatial reference of the geometries. - * @return TRUE if geometry1 crosses geometry2. - */ - public static boolean crosses(Geometry geometry1, Geometry geometry2, - SpatialReference spatialReference) { - OperatorCrosses op = (OperatorCrosses) factory - .getOperator(Operator.Type.Crosses); - boolean result = op.execute(geometry1, geometry2, spatialReference, - null); - return result; - } - - /** - * Indicates if one geometry touches another geometry. - * - * @param geometry1 - * The geometry to touch. - * @param geometry2 - * The geometry to be touched. - * @param spatialReference - * The spatial reference of the geometries. - * @return TRUE if geometry1 touches geometry2. - */ - public static boolean touches(Geometry geometry1, Geometry geometry2, - SpatialReference spatialReference) { - OperatorTouches op = (OperatorTouches) factory - .getOperator(Operator.Type.Touches); - boolean result = op.execute(geometry1, geometry2, spatialReference, - null); - return result; - } - - /** - * Indicates if one geometry overlaps another geometry. - * - * @param geometry1 - * The geometry to overlap. - * @param geometry2 - * The geometry to be overlapped. - * @param spatialReference - * The spatial reference of the geometries. - * @return TRUE if geometry1 overlaps geometry2. - */ - public static boolean overlaps(Geometry geometry1, Geometry geometry2, - SpatialReference spatialReference) { - OperatorOverlaps op = (OperatorOverlaps) factory - .getOperator(Operator.Type.Overlaps); - boolean result = op.execute(geometry1, geometry2, spatialReference, - null); - return result; - } - - /** - * Indicates if the given relation holds for the two geometries. - * - * @param geometry1 - * The first geometry for the relation. - * @param geometry2 - * The second geometry for the relation. - * @param spatialReference - * The spatial reference of the geometries. - * @param relation - * The DE-9IM relation. - * @return TRUE if the given relation holds between geometry1 and geometry2. - */ - public static boolean relate(Geometry geometry1, Geometry geometry2, - SpatialReference spatialReference, String relation) { - OperatorRelate op = (OperatorRelate) factory - .getOperator(Operator.Type.Relate); - boolean result = op.execute(geometry1, geometry2, spatialReference, - relation, null); - return result; - } - - /** - * Calculates the 2D planar distance between two geometries. - * - * @param geometry1 - * Geometry. - * @param geometry2 - * Geometry. - * @param spatialReference - * The spatial reference of the geometries. This parameter is not - * used and can be null. - * @return The distance between the two geometries. - */ - public static double distance(Geometry geometry1, Geometry geometry2, - SpatialReference spatialReference) { - OperatorDistance op = (OperatorDistance) factory - .getOperator(Operator.Type.Distance); - double result = op.execute(geometry1, geometry2, null); - return result; - } - - /** - * Calculates the clipped geometry from a target geometry using an envelope. - * - * @param geometry - * The geometry to be clipped. - * @param envelope - * The envelope used to clip. - * @param spatialReference - * The spatial reference of the geometries. - * @return The geometry created by clipping. - */ - public static Geometry clip(Geometry geometry, Envelope envelope, - SpatialReference spatialReference) { - OperatorClip op = (OperatorClip) factory - .getOperator(Operator.Type.Clip); - Geometry result = op.execute(geometry, Envelope2D.construct( - envelope.getXMin(), envelope.getYMin(), envelope.getXMax(), - envelope.getYMax()), spatialReference, null); - return result; - } - - /** - * Calculates the cut geometry from a target geometry using a polyline. For - * Polylines, all left cuts will be grouped together in the first Geometry, - * Right cuts and coincident cuts are grouped in the second Geometry, and - * each undefined cut, along with any uncut parts, are output as separate - * Polylines. For Polygons, all left cuts are grouped in the first Polygon, - * all right cuts are in the second Polygon, and each undefined cut, along - * with any left-over parts after cutting, are output as a separate Polygon. - * If there were no cuts then the array will be empty. An undefined cut will - * only be produced if a left cut or right cut was produced, and there was a - * part left over after cutting or a cut is bounded to the left and right of - * the cutter. - * - * @param cuttee - * The geometry to be cut. - * @param cutter - * The polyline to cut the geometry. - * @param spatialReference - * The spatial reference of the geometries. - * @return An array of geometries created from cutting. - */ - public static Geometry[] cut(Geometry cuttee, Polyline cutter, - SpatialReference spatialReference) { - if (cuttee == null || cutter == null) - return null; - - OperatorCut op = (OperatorCut) factory.getOperator(Operator.Type.Cut); - GeometryCursor cursor = op.execute(true, cuttee, cutter, - spatialReference, null); - ArrayList cutsList = new ArrayList(); - - Geometry geometry; - while ((geometry = cursor.next()) != null) { - if (!geometry.isEmpty()) { - cutsList.add(geometry); - } - } - - return cutsList.toArray(new Geometry[0]); - } - - /** - * Calculates a buffer polygon for each geometry at each of the - * corresponding specified distances. It is assumed all geometries have - * the same spatial reference. The unit variable defines the unit of - * all the distances. If unit == null then the unit of distances is assumed - * to be that of the spatial reference. There is an option to union the - * returned geometries. - * @param geometries An array of geometries to be buffered. - * @param spatialReference The spatial reference of the geometries. - * @param distances The corresponding distances for the input geometries to be buffered. - * @param unit The unit of the values in the distances array. Must be of the same unit type as spatial reference. - * @param toUnionResults TRUE if all geometries buffered at a given distance are to be unioned into a single polygon. - * @return The buffer of the geometries. - * */ - public static Polygon[] buffer(Geometry[] geometries, - SpatialReference spatialReference, double[] distances, - boolean toUnionResults) { - // initially assume distances are in unit of spatial reference - double[] bufferDistances = distances; - - OperatorBuffer op = (OperatorBuffer) factory - .getOperator(Operator.Type.Buffer); - - if (toUnionResults) { - SimpleGeometryCursor inputGeometriesCursor = new SimpleGeometryCursor( - geometries); - GeometryCursor result = op.execute(inputGeometriesCursor, - spatialReference, bufferDistances, toUnionResults, null); - - ArrayList resultGeoms = new ArrayList(); - Geometry g; - while ((g = result.next()) != null) { - resultGeoms.add((Polygon) g); - } - Polygon[] buffers = resultGeoms.toArray(new Polygon[0]); - return buffers; - } else { - Polygon[] buffers = new Polygon[geometries.length]; - for (int i = 0; i < geometries.length; i++) { - buffers[i] = (Polygon) op.execute(geometries[i], - spatialReference, bufferDistances[i], null); - } - return buffers; - } - } - - /** - * Calculates a buffer polygon of the geometry as specified by the - * distance input. If unit == null, then the distance is assumed - * to be in the unit of the spatial reference. - * @param geometry Geometry to be buffered. - * @param spatialReference The spatial reference of the geometry. - * @param distance The specified distance for buffer. - * @param unit The unit of the values in the distances array. Must be of the same unit type as spatial reference. - * @return The buffer polygon at the specified distances. - * */ - public static Polygon buffer(Geometry geometry, - SpatialReference spatialReference, double distance) { - double bufferDistance = distance; - - OperatorBuffer op = (OperatorBuffer) factory - .getOperator(Operator.Type.Buffer); - Geometry result = op.execute(geometry, spatialReference, - bufferDistance, null); - return (Polygon) result; - } - - /** - * Calculates the convex hull geometry. - * - * @param geometry - * The input geometry. \return Returns the convex hull. - * - * For a Point - returns the same point. For an Envelope - - * returns the same envelope. For a MultiPoint - If the point - * count is one, returns the same multipoint. If the point count - * is two, returns a polyline of the points. Otherwise computes - * and returns the convex hull polygon. For a Segment - returns a - * polyline consisting of the segment. For a Polyline - If - * consists of only one segment, returns the same polyline. - * Otherwise computes and returns the convex hull polygon. For a - * Polygon - If more than one path, or if the path isn't already - * convex, computes and returns the convex hull polygon. - * Otherwise returns the same polygon. - */ - public static Geometry convexHull(Geometry geometry) { - OperatorConvexHull op = (OperatorConvexHull) factory - .getOperator(Operator.Type.ConvexHull); - return op.execute(geometry, null); - } - - /** - * Calculates the convex hull. - * - * @param geometries - * The input geometry array. - * @param b_merge - * Put true if you want the convex hull of all the geometries in - * the array combined. Put false if you want the convex hull of - * each geometry in the array individually. - * @return Returns an array of convex hulls. If b_merge is true, the result - * will be a one element array consisting of the merged convex hull. - */ - public static Geometry[] convexHull(Geometry[] geometries, boolean b_merge) { - OperatorConvexHull op = (OperatorConvexHull) factory - .getOperator(Operator.Type.ConvexHull); - SimpleGeometryCursor simple_cursor = new SimpleGeometryCursor( - geometries); - GeometryCursor cursor = op.execute(simple_cursor, b_merge, null); - - ArrayList resultGeoms = new ArrayList(); - Geometry g; - while ((g = cursor.next()) != null) { - resultGeoms.add(g); - } - - Geometry[] output = new Geometry[resultGeoms.size()]; - - for (int i = 0; i < resultGeoms.size(); i++) - output[i] = resultGeoms.get(i); - - return output; - } - - /** - * Finds the coordinate of the geometry which is closest to the specified - * point. - * - * @param inputPoint - * The point to find the nearest coordinate in the geometry for. - * @param geometry - * The geometry to consider. - * @return Proximity2DResult containing the nearest coordinate. - * */ - public static Proximity2DResult getNearestCoordinate(Geometry geometry, - Point inputPoint, boolean bTestPolygonInterior) { - - OperatorProximity2D proximity = (OperatorProximity2D) factory - .getOperator(com.esri.core.geometry.Operator.Type.Proximity2D); - Proximity2DResult result = proximity.getNearestCoordinate(geometry, - inputPoint, bTestPolygonInterior); - return result; - } - - /** - * Finds nearest vertex on the geometry which is closed to the specified - * point. - * - * @param inputPoint - * The point to find the nearest vertex of the geometry for. - * @param geometry - * The geometry to consider. - * @return Proximity2DResult containing the nearest vertex. - * */ - public static Proximity2DResult getNearestVertex(Geometry geometry, - Point inputPoint) { - OperatorProximity2D proximity = (OperatorProximity2D) factory - .getOperator(com.esri.core.geometry.Operator.Type.Proximity2D); - Proximity2DResult result = proximity.getNearestVertex(geometry, - inputPoint); - return result; - } - - /** - * Finds all vertices in the given distance from the specified point, sorted - * from the closest to the furthest. - * - * @param inputPoint - * The point to start from. - * @param geometry - * The geometry to consider. - * @param searchRadius - * The search radius. - * @param maxVertexCountToReturn - * The maximum number number of vertices to return. - * @return Proximity2DResult containing the array of nearest vertices. - * */ - public static Proximity2DResult[] getNearestVertices(Geometry geometry, - Point inputPoint, double searchRadius, int maxVertexCountToReturn) { - OperatorProximity2D proximity = (OperatorProximity2D) factory - .getOperator(com.esri.core.geometry.Operator.Type.Proximity2D); - - Proximity2DResult[] results = proximity.getNearestVertices(geometry, - inputPoint, searchRadius, maxVertexCountToReturn); - - return results; - } - - /** - * Performs the simplify operation on the geometry. - * - * @param geometry - * The geometry to be simplified. - * @param spatialReference - * The spatial reference of the geometry to be simplified. - * @return The simplified geometry. - */ - public static Geometry simplify(Geometry geometry, - SpatialReference spatialReference) { - OperatorSimplify op = (OperatorSimplify) factory - .getOperator(Operator.Type.Simplify); - Geometry result = op.execute(geometry, spatialReference, false, null); - return result; - } - - /** - * Checks if the Geometry is simple. - * - * @param geometry - * The geometry to be checked. - * @param spatialReference - * The spatial reference of the geometry. - * @return TRUE if the geometry is simple. - */ - static boolean isSimple(Geometry geometry, SpatialReference spatialReference) { - OperatorSimplify op = (OperatorSimplify) factory - .getOperator(Operator.Type.Simplify); - boolean result = op.isSimpleAsFeature(geometry, spatialReference, null); - return result; - } - - /** - * A geodesic distance is the shortest distance between any two points on the earth's surface when the earth's - * surface is approximated by a spheroid. The function returns the shortest distance between two points on the - * WGS84 spheroid. - * @param ptFrom The "from" point: long, lat in degrees. - * @param ptTo The "to" point: long, lat in degrees. - * @return The geodesic distance between two points in meters. - */ - public static double geodesicDistanceOnWGS84(Point ptFrom, Point ptTo) { - return SpatialReferenceImpl.geodesicDistanceOnWGS84Impl(ptFrom, ptTo); - } -} +/* + Copyright 1995-2013 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ + +package com.esri.core.geometry; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.util.ArrayList; +import org.codehaus.jackson.JsonParser; +import org.json.JSONException; + +/** + * Provides services that operate on geometry instances. + */ +public class GeometryEngine { + + private static OperatorFactoryLocal factory = OperatorFactoryLocal + .getInstance(); + + /** + * Imports the MapGeometry from its JSON representation. M and Z values are + * not imported from JSON representation. + * + * @param json + * The JSON representation of the geometry (with spatial + * reference). + * @return The MapGeometry instance containing the imported geometry and its + * spatial reference. + */ + public static MapGeometry jsonToGeometry(JsonParser json) { + OperatorImportFromJson importerJson = (OperatorImportFromJson) factory + .getOperator(Operator.Type.ImportMapGeometryFromJson); + + MapGeometry geom = importerJson.execute(Geometry.Type.Unknown, json); + return geom; + } + + /** + * Exports the specified geometry instance to it's JSON representation. + * + * @see GeometryEngine#geometryToJson(SpatialReference spatialiReference, + * Geometry geometry) + * @param wkid + * The spatial reference Well Known ID to be used for the JSON + * representation. + * @param geometry + * The geometry to be exported to JSON. + * @return The JSON representation of the specified Geometry. + */ + public static String geometryToJson(int wkid, Geometry geometry) { + return GeometryEngine.geometryToJson( + wkid > 0 ? SpatialReference.create(wkid) : null, geometry); + } + + /** + * Exports the specified geometry instance to it's JSON representation. M + * and Z values are not imported from JSON representation. + * + * @param spatialReference + * The spatial reference of associated object. + * @param geometry + * The geometry. + * @return The JSON representation of the specified geometry. + */ + public static String geometryToJson(SpatialReference spatialReference, + Geometry geometry) { + OperatorExportToJson exporter = (OperatorExportToJson) factory + .getOperator(Operator.Type.ExportToJson); + + return exporter.execute(spatialReference, geometry); + } + + /** + * Imports geometry from the ESRI shape file format. + * + * @param esriShapeBuffer + * The buffer containing geometry in the ESRI shape file format. + * @param geometryType + * The required type of the Geometry to be imported. Use + * Geometry.Type.Unknown if the geometry type needs to be + * determined from the buffer content. + * @return The geometry or null if the buffer contains null shape. + * @throws GeometryException + * when the geometryType is not Geometry.Type.Unknown and the + * buffer contains geometry that cannot be converted to the + * given geometryType. or the buffer is corrupt. Another + * exception possible is IllegalArgumentsException. + */ + public static Geometry geometryFromEsriShape(byte[] esriShapeBuffer, + Geometry.Type geometryType) { + OperatorImportFromESRIShape op = (OperatorImportFromESRIShape) factory + .getOperator(Operator.Type.ImportFromESRIShape); + return op + .execute( + ShapeImportFlags.ShapeImportNonTrusted, + geometryType, + ByteBuffer.wrap(esriShapeBuffer).order( + ByteOrder.LITTLE_ENDIAN)); + } + + /** + * Exports geometry to the ESRI shape file format. + * + * @param geometry + * The geometry to export. (null value is not allowed) + * @return Array containing the exported ESRI shape file. + */ + public static byte[] geometryToEsriShape(Geometry geometry) { + if (geometry == null) + throw new IllegalArgumentException(); + OperatorExportToESRIShape op = (OperatorExportToESRIShape) factory + .getOperator(Operator.Type.ExportToESRIShape); + return op.execute(0, geometry).array(); + } + + /** + * Imports a geometry from a WKT string. + * @param wkt The string containing the geometry in WKT format. + * @param importFlags Use the {@link WktImportFlags} interface. + * @param geometryType The required type of the Geometry to be imported. Use Geometry.Type.Unknown if the geometry type needs to be determined from the WKT context. + * @return The geometry. + * @throws GeometryException when the geometryType is not Geometry.Type.Unknown and the WKT contains a geometry that cannot be converted to the given geometryType. + * @throws IllegalArgument exception if an error is found while parsing the WKT string. + */ + public static Geometry geometryFromWkt(String wkt, int importFlags, + Geometry.Type geometryType) { + OperatorImportFromWkt op = (OperatorImportFromWkt) factory + .getOperator(Operator.Type.ImportFromWkt); + return op.execute(importFlags, geometryType, wkt, null); + } + + /** + * Imports a geometry from a geoJson string. + * @param geoJson The string containing the geometry in geoJson format. + * @param importFlags Use the {@link GeoJsonImportFlags} interface. + * @param geometryType The required type of the Geometry to be imported. Use Geometry.Type.Unknown if the geometry type needs to be determined from the geoJson context. + * @return The geometry. + * @throws GeometryException when the geometryType is not Geometry.Type.Unknown and the geoJson contains a geometry that cannot be converted to the given geometryType. + * @throws IllegalArgument exception if an error is found while parsing the geoJson string. + */ + public static MapGeometry geometryFromGeoJson(String geoJson, + int importFlags, Geometry.Type geometryType) throws JSONException { + OperatorImportFromGeoJson op = (OperatorImportFromGeoJson) factory + .getOperator(Operator.Type.ImportFromGeoJson); + return op.execute(importFlags, geometryType, geoJson, null); + } + + /** + * Exports a geometry to a string in WKT format. + * @param geometry The geometry to export. (null value is not allowed) + * @param exportFlags Use the {@link WktExportFlags} interface. + * @return A String containing the exported geometry in WKT format. + */ + public static String geometryToWkt(Geometry geometry, int exportFlags) { + OperatorExportToWkt op = (OperatorExportToWkt) factory + .getOperator(Operator.Type.ExportToWkt); + return op.execute(exportFlags, geometry, null); + } + + /** + * Constructs a new geometry by union an array of geometries. All inputs + * must be of the same type of geometries and share one spatial reference. + * + * @param geometries + * The geometries to union. + * @param spatialReference + * The spatial reference of the geometries. + * @return The geometry object representing the resultant union. + */ + public static Geometry union(Geometry[] geometries, + SpatialReference spatialReference) { + OperatorUnion op = (OperatorUnion) factory + .getOperator(Operator.Type.Union); + + SimpleGeometryCursor inputGeometries = new SimpleGeometryCursor( + geometries); + GeometryCursor result = op.execute(inputGeometries, spatialReference, + null); + return result.next(); + } + + // TODO Remove this method from geometry engine + /** + * constructs the set-theoretic difference between an array of geometries + * and another geometry. The dimension of the input geometry has to be equal + * to or greater than that of any element in the array of "geometries". + * + * @param inputGeometries + * an array of geometry objects being subtracted + * @param subtractor + * geometry object to subtract from + * @return any array of geometry objects showing the difference + * */ + static Geometry[] difference(Geometry[] inputGeometries, + Geometry subtractor, SpatialReference spatialReference) { + OperatorDifference op = (OperatorDifference) factory + .getOperator(Operator.Type.Difference); + SimpleGeometryCursor inputGeometriesCursor = new SimpleGeometryCursor( + inputGeometries); + SimpleGeometryCursor subtractorCursor = new SimpleGeometryCursor( + subtractor); + GeometryCursor result = op.execute(inputGeometriesCursor, + subtractorCursor, spatialReference, null); + + ArrayList resultGeoms = new ArrayList(); + Geometry g; + while ((g = result.next()) != null) { + resultGeoms.add(g); + } + Geometry[] resultarr = resultGeoms.toArray(new Geometry[0]); + return resultarr; + } + + /** + * Creates the difference of two geometries. The dimension of geometry2 has + * to be equal to or greater than that of geometry1. + * + * @param geometry1 + * The geometry being subtracted. + * @param substractor + * The geometry object to subtract from. + * @param spatialReference + * The spatial reference of the geometries. + * @return The geometry of the differences. + */ + public static Geometry difference(Geometry geometry1, Geometry substractor, + SpatialReference spatialReference) { + OperatorDifference op = (OperatorDifference) factory + .getOperator(Operator.Type.Difference); + Geometry result = op.execute(geometry1, substractor, spatialReference, + null); + return result; + } + + /** + * Creates the symmetric difference of two geometries. + * + * @param leftGeometry + * is one of the Geometry instances in the XOR operation. + * @param rightGeometry + * is one of the Geometry instances in the XOR operation. + * @param spatialReference + * The spatial reference of the geometries. + * @return Returns the result of the symmetric difference. + */ + public static Geometry symmetricDifference(Geometry leftGeometry, + Geometry rightGeometry, SpatialReference spatialReference) { + OperatorSymmetricDifference op = (OperatorSymmetricDifference) factory + .getOperator(Operator.Type.SymmetricDifference); + Geometry result = op.execute(leftGeometry, rightGeometry, + spatialReference, null); + return result; + } + + /** + * Indicates if two geometries are equal. + * + * @param geometry1 + * Geometry. + * @param geometry2 + * Geometry. + * @param spatialReference + * The spatial reference of the geometries. + * @return TRUE if both geometry objects are equal. + */ + public static boolean equals(Geometry geometry1, Geometry geometry2, + SpatialReference spatialReference) { + OperatorEquals op = (OperatorEquals) factory + .getOperator(Operator.Type.Equals); + boolean result = op.execute(geometry1, geometry2, spatialReference, + null); + return result; + } + + public static boolean disjoint(Geometry geometry1, Geometry geometry2, + SpatialReference spatialReference) { + OperatorDisjoint op = (OperatorDisjoint) factory + .getOperator(Operator.Type.Disjoint); + boolean result = op.execute(geometry1, geometry2, spatialReference, + null); + return result; + } + + /** + * Constructs the set-theoretic intersection between an array of geometries + * and another geometry. + * + * @param inputGeometries + * An array of geometry objects. + * @param geometry + * The geometry object. + * @return Any array of geometry objects showing the intersection. + */ + static Geometry[] intersect(Geometry[] inputGeometries, Geometry geometry, + SpatialReference spatialReference) { + OperatorIntersection op = (OperatorIntersection) factory + .getOperator(Operator.Type.Intersection); + SimpleGeometryCursor inputGeometriesCursor = new SimpleGeometryCursor( + inputGeometries); + SimpleGeometryCursor intersectorCursor = new SimpleGeometryCursor( + geometry); + GeometryCursor result = op.execute(inputGeometriesCursor, + intersectorCursor, spatialReference, null); + + ArrayList resultGeoms = new ArrayList(); + Geometry g; + while ((g = result.next()) != null) { + resultGeoms.add(g); + } + + Geometry[] resultarr = resultGeoms.toArray(new Geometry[0]); + return resultarr; + } + + /** + * Creates a geometry through intersection between two geometries. + * + * @param geometry1 + * The first geometry. + * @param intersector + * The geometry to intersect the first geometry. + * @param spatialReference + * The spatial reference of the geometries. + * @return The geometry created through intersection. + */ + public static Geometry intersect(Geometry geometry1, Geometry intersector, + SpatialReference spatialReference) { + OperatorIntersection op = (OperatorIntersection) factory + .getOperator(Operator.Type.Intersection); + Geometry result = op.execute(geometry1, intersector, spatialReference, + null); + return result; + } + + /** + * Indicates if one geometry is within another geometry. + * + * @param geometry1 + * The base geometry that is tested for within relationship to + * the other geometry. + * @param geometry2 + * The comparison geometry that is tested for the contains + * relationship to the other geometry. + * @param spatialReference + * The spatial reference of the geometries. + * @return TRUE if the first geometry is within the other geometry. + */ + public static boolean within(Geometry geometry1, Geometry geometry2, + SpatialReference spatialReference) { + OperatorWithin op = (OperatorWithin) factory + .getOperator(Operator.Type.Within); + boolean result = op.execute(geometry1, geometry2, spatialReference, + null); + return result; + } + + /** + * Indicates if one geometry contains another geometry. + * + * @param geometry1 + * The geometry that is tested for the contains relationship to + * the other geometry.. + * @param geometry2 + * The geometry that is tested for within relationship to the + * other geometry. + * @param spatialReference + * The spatial reference of the geometries. + * @return TRUE if geometry1 contains geometry2. + */ + public static boolean contains(Geometry geometry1, Geometry geometry2, + SpatialReference spatialReference) { + OperatorContains op = (OperatorContains) factory + .getOperator(Operator.Type.Contains); + boolean result = op.execute(geometry1, geometry2, spatialReference, + null); + return result; + } + + /** + * Indicates if one geometry crosses another geometry. + * + * @param geometry1 + * The geometry to cross. + * @param geometry2 + * The geometry being crossed. + * @param spatialReference + * The spatial reference of the geometries. + * @return TRUE if geometry1 crosses geometry2. + */ + public static boolean crosses(Geometry geometry1, Geometry geometry2, + SpatialReference spatialReference) { + OperatorCrosses op = (OperatorCrosses) factory + .getOperator(Operator.Type.Crosses); + boolean result = op.execute(geometry1, geometry2, spatialReference, + null); + return result; + } + + /** + * Indicates if one geometry touches another geometry. + * + * @param geometry1 + * The geometry to touch. + * @param geometry2 + * The geometry to be touched. + * @param spatialReference + * The spatial reference of the geometries. + * @return TRUE if geometry1 touches geometry2. + */ + public static boolean touches(Geometry geometry1, Geometry geometry2, + SpatialReference spatialReference) { + OperatorTouches op = (OperatorTouches) factory + .getOperator(Operator.Type.Touches); + boolean result = op.execute(geometry1, geometry2, spatialReference, + null); + return result; + } + + /** + * Indicates if one geometry overlaps another geometry. + * + * @param geometry1 + * The geometry to overlap. + * @param geometry2 + * The geometry to be overlapped. + * @param spatialReference + * The spatial reference of the geometries. + * @return TRUE if geometry1 overlaps geometry2. + */ + public static boolean overlaps(Geometry geometry1, Geometry geometry2, + SpatialReference spatialReference) { + OperatorOverlaps op = (OperatorOverlaps) factory + .getOperator(Operator.Type.Overlaps); + boolean result = op.execute(geometry1, geometry2, spatialReference, + null); + return result; + } + + /** + * Indicates if the given relation holds for the two geometries. + * + * @param geometry1 + * The first geometry for the relation. + * @param geometry2 + * The second geometry for the relation. + * @param spatialReference + * The spatial reference of the geometries. + * @param relation + * The DE-9IM relation. + * @return TRUE if the given relation holds between geometry1 and geometry2. + */ + public static boolean relate(Geometry geometry1, Geometry geometry2, + SpatialReference spatialReference, String relation) { + OperatorRelate op = (OperatorRelate) factory + .getOperator(Operator.Type.Relate); + boolean result = op.execute(geometry1, geometry2, spatialReference, + relation, null); + return result; + } + + /** + * Calculates the 2D planar distance between two geometries. + * + * @param geometry1 + * Geometry. + * @param geometry2 + * Geometry. + * @param spatialReference + * The spatial reference of the geometries. This parameter is not + * used and can be null. + * @return The distance between the two geometries. + */ + public static double distance(Geometry geometry1, Geometry geometry2, + SpatialReference spatialReference) { + OperatorDistance op = (OperatorDistance) factory + .getOperator(Operator.Type.Distance); + double result = op.execute(geometry1, geometry2, null); + return result; + } + + /** + * Calculates the clipped geometry from a target geometry using an envelope. + * + * @param geometry + * The geometry to be clipped. + * @param envelope + * The envelope used to clip. + * @param spatialReference + * The spatial reference of the geometries. + * @return The geometry created by clipping. + */ + public static Geometry clip(Geometry geometry, Envelope envelope, + SpatialReference spatialReference) { + OperatorClip op = (OperatorClip) factory + .getOperator(Operator.Type.Clip); + Geometry result = op.execute(geometry, Envelope2D.construct( + envelope.getXMin(), envelope.getYMin(), envelope.getXMax(), + envelope.getYMax()), spatialReference, null); + return result; + } + + /** + * Calculates the cut geometry from a target geometry using a polyline. For + * Polylines, all left cuts will be grouped together in the first Geometry, + * Right cuts and coincident cuts are grouped in the second Geometry, and + * each undefined cut, along with any uncut parts, are output as separate + * Polylines. For Polygons, all left cuts are grouped in the first Polygon, + * all right cuts are in the second Polygon, and each undefined cut, along + * with any left-over parts after cutting, are output as a separate Polygon. + * If there were no cuts then the array will be empty. An undefined cut will + * only be produced if a left cut or right cut was produced, and there was a + * part left over after cutting or a cut is bounded to the left and right of + * the cutter. + * + * @param cuttee + * The geometry to be cut. + * @param cutter + * The polyline to cut the geometry. + * @param spatialReference + * The spatial reference of the geometries. + * @return An array of geometries created from cutting. + */ + public static Geometry[] cut(Geometry cuttee, Polyline cutter, + SpatialReference spatialReference) { + if (cuttee == null || cutter == null) + return null; + + OperatorCut op = (OperatorCut) factory.getOperator(Operator.Type.Cut); + GeometryCursor cursor = op.execute(true, cuttee, cutter, + spatialReference, null); + ArrayList cutsList = new ArrayList(); + + Geometry geometry; + while ((geometry = cursor.next()) != null) { + if (!geometry.isEmpty()) { + cutsList.add(geometry); + } + } + + return cutsList.toArray(new Geometry[0]); + } + + /** + * Calculates a buffer polygon for each geometry at each of the + * corresponding specified distances. It is assumed that all geometries have + * the same spatial reference. There is an option to union the + * returned geometries. + * @param geometries An array of geometries to be buffered. + * @param spatialReference The spatial reference of the geometries. + * @param distances The corresponding distances for the input geometries to be buffered. + * @param toUnionResults TRUE if all geometries buffered at a given distance are to be unioned into a single polygon. + * @return The buffer of the geometries. + * */ + public static Polygon[] buffer(Geometry[] geometries, + SpatialReference spatialReference, double[] distances, + boolean toUnionResults) { + // initially assume distances are in unit of spatial reference + double[] bufferDistances = distances; + + OperatorBuffer op = (OperatorBuffer) factory + .getOperator(Operator.Type.Buffer); + + if (toUnionResults) { + SimpleGeometryCursor inputGeometriesCursor = new SimpleGeometryCursor( + geometries); + GeometryCursor result = op.execute(inputGeometriesCursor, + spatialReference, bufferDistances, toUnionResults, null); + + ArrayList resultGeoms = new ArrayList(); + Geometry g; + while ((g = result.next()) != null) { + resultGeoms.add((Polygon) g); + } + Polygon[] buffers = resultGeoms.toArray(new Polygon[0]); + return buffers; + } else { + Polygon[] buffers = new Polygon[geometries.length]; + for (int i = 0; i < geometries.length; i++) { + buffers[i] = (Polygon) op.execute(geometries[i], + spatialReference, bufferDistances[i], null); + } + return buffers; + } + } + + /** + * Calculates a buffer polygon of the geometry as specified by the + * distance input. The buffer is implemented in the xy-plane. + * @param geometry Geometry to be buffered. + * @param spatialReference The spatial reference of the geometry. + * @param distance The specified distance for buffer. Same units as the spatial reference. + * @return The buffer polygon at the specified distances. + * */ + public static Polygon buffer(Geometry geometry, + SpatialReference spatialReference, double distance) { + double bufferDistance = distance; + + OperatorBuffer op = (OperatorBuffer) factory + .getOperator(Operator.Type.Buffer); + Geometry result = op.execute(geometry, spatialReference, + bufferDistance, null); + return (Polygon) result; + } + + /** + * Calculates the convex hull geometry. + * + * @param geometry The input geometry. + * @return Returns the convex hull. + * + * For a Point - returns the same point. For an Envelope - + * returns the same envelope. For a MultiPoint - If the point + * count is one, returns the same multipoint. If the point count + * is two, returns a polyline of the points. Otherwise computes + * and returns the convex hull polygon. For a Segment - returns a + * polyline consisting of the segment. For a Polyline - If + * consists of only one segment, returns the same polyline. + * Otherwise computes and returns the convex hull polygon. For a + * Polygon - If more than one path, or if the path isn't already + * convex, computes and returns the convex hull polygon. + * Otherwise returns the same polygon. + */ + public static Geometry convexHull(Geometry geometry) { + OperatorConvexHull op = (OperatorConvexHull) factory + .getOperator(Operator.Type.ConvexHull); + return op.execute(geometry, null); + } + + /** + * Calculates the convex hull. + * + * @param geometries + * The input geometry array. + * @param b_merge + * Put true if you want the convex hull of all the geometries in + * the array combined. Put false if you want the convex hull of + * each geometry in the array individually. + * @return Returns an array of convex hulls. If b_merge is true, the result + * will be a one element array consisting of the merged convex hull. + */ + public static Geometry[] convexHull(Geometry[] geometries, boolean b_merge) { + OperatorConvexHull op = (OperatorConvexHull) factory + .getOperator(Operator.Type.ConvexHull); + SimpleGeometryCursor simple_cursor = new SimpleGeometryCursor( + geometries); + GeometryCursor cursor = op.execute(simple_cursor, b_merge, null); + + ArrayList resultGeoms = new ArrayList(); + Geometry g; + while ((g = cursor.next()) != null) { + resultGeoms.add(g); + } + + Geometry[] output = new Geometry[resultGeoms.size()]; + + for (int i = 0; i < resultGeoms.size(); i++) + output[i] = resultGeoms.get(i); + + return output; + } + + /** + * Finds the coordinate of the geometry which is closest to the specified + * point. + * + * @param inputPoint + * The point to find the nearest coordinate in the geometry for. + * @param geometry + * The geometry to consider. + * @return Proximity2DResult containing the nearest coordinate. + * */ + public static Proximity2DResult getNearestCoordinate(Geometry geometry, + Point inputPoint, boolean bTestPolygonInterior) { + + OperatorProximity2D proximity = (OperatorProximity2D) factory + .getOperator(com.esri.core.geometry.Operator.Type.Proximity2D); + Proximity2DResult result = proximity.getNearestCoordinate(geometry, + inputPoint, bTestPolygonInterior); + return result; + } + + /** + * Finds nearest vertex on the geometry which is closed to the specified + * point. + * + * @param inputPoint + * The point to find the nearest vertex of the geometry for. + * @param geometry + * The geometry to consider. + * @return Proximity2DResult containing the nearest vertex. + * */ + public static Proximity2DResult getNearestVertex(Geometry geometry, + Point inputPoint) { + OperatorProximity2D proximity = (OperatorProximity2D) factory + .getOperator(com.esri.core.geometry.Operator.Type.Proximity2D); + Proximity2DResult result = proximity.getNearestVertex(geometry, + inputPoint); + return result; + } + + /** + * Finds all vertices in the given distance from the specified point, sorted + * from the closest to the furthest. + * + * @param inputPoint + * The point to start from. + * @param geometry + * The geometry to consider. + * @param searchRadius + * The search radius. + * @param maxVertexCountToReturn + * The maximum number number of vertices to return. + * @return Proximity2DResult containing the array of nearest vertices. + * */ + public static Proximity2DResult[] getNearestVertices(Geometry geometry, + Point inputPoint, double searchRadius, int maxVertexCountToReturn) { + OperatorProximity2D proximity = (OperatorProximity2D) factory + .getOperator(com.esri.core.geometry.Operator.Type.Proximity2D); + + Proximity2DResult[] results = proximity.getNearestVertices(geometry, + inputPoint, searchRadius, maxVertexCountToReturn); + + return results; + } + + /** + * Performs the simplify operation on the geometry. + * + * @param geometry + * The geometry to be simplified. + * @param spatialReference + * The spatial reference of the geometry to be simplified. + * @return The simplified geometry. + */ + public static Geometry simplify(Geometry geometry, + SpatialReference spatialReference) { + OperatorSimplify op = (OperatorSimplify) factory + .getOperator(Operator.Type.Simplify); + Geometry result = op.execute(geometry, spatialReference, false, null); + return result; + } + + /** + * Checks if the Geometry is simple. + * + * @param geometry + * The geometry to be checked. + * @param spatialReference + * The spatial reference of the geometry. + * @return TRUE if the geometry is simple. + */ + static boolean isSimple(Geometry geometry, SpatialReference spatialReference) { + OperatorSimplify op = (OperatorSimplify) factory + .getOperator(Operator.Type.Simplify); + boolean result = op.isSimpleAsFeature(geometry, spatialReference, null); + return result; + } + + /** + * A geodesic distance is the shortest distance between any two points on the earth's surface when the earth's + * surface is approximated by a spheroid. The function returns the shortest distance between two points on the + * WGS84 spheroid. + * @param ptFrom The "from" point: long, lat in degrees. + * @param ptTo The "to" point: long, lat in degrees. + * @return The geodesic distance between two points in meters. + */ + public static double geodesicDistanceOnWGS84(Point ptFrom, Point ptTo) { + return SpatialReferenceImpl.geodesicDistanceOnWGS84Impl(ptFrom, ptTo); + } +} diff --git a/src/com/esri/core/geometry/OperatorConvexHull.java b/src/com/esri/core/geometry/OperatorConvexHull.java index ee5769cf..71824414 100644 --- a/src/com/esri/core/geometry/OperatorConvexHull.java +++ b/src/com/esri/core/geometry/OperatorConvexHull.java @@ -50,7 +50,7 @@ abstract public GeometryCursor execute(GeometryCursor geoms, *Calculates the convex hull geometry. *@param geom The input geometry. *@param progress_tracker The progress tracker, that allows to cancel the lengthy operation. - *\return Returns the convex hull. + *@return Returns the convex hull. * *For a Point - returns the same point. *For an Envelope - returns the same envelope. @@ -65,8 +65,8 @@ abstract public Geometry execute(Geometry geom, /** *Checks whether a Geometry is convex. *@param geom The input geometry to test for convex. - *@progress_tracker The progress tracker. - *\returns Returns true if the geometry is convex. + *@param progress_tracker The progress tracker. + *@return Returns true if the geometry is convex. */ abstract public boolean isConvex(Geometry geom, ProgressTracker progress_tracker); diff --git a/src/com/esri/core/geometry/OperatorImportFromWkb.java b/src/com/esri/core/geometry/OperatorImportFromWkb.java index 8f55cfa8..9550e4cf 100644 --- a/src/com/esri/core/geometry/OperatorImportFromWkb.java +++ b/src/com/esri/core/geometry/OperatorImportFromWkb.java @@ -55,7 +55,7 @@ public abstract Geometry execute(int importFlags, Geometry.Type type, * @return Returns the imported OGCStructure. */ public abstract OGCStructure executeOGC(int importFlags, - ByteBuffer wktBuffer, ProgressTracker progress_tracker); + ByteBuffer wkbBuffer, ProgressTracker progress_tracker); public static OperatorImportFromWkb local() { return (OperatorImportFromWkb) OperatorFactoryLocal.getInstance() diff --git a/src/com/esri/core/geometry/Segment.java b/src/com/esri/core/geometry/Segment.java index 9e7a0575..501f7dda 100644 --- a/src/com/esri/core/geometry/Segment.java +++ b/src/com/esri/core/geometry/Segment.java @@ -139,7 +139,7 @@ public int getStartAttributeAsInt(int semantics, int ordinate) { * * @param semantics * The attribute semantics. - * @param valueIn + * @param value * is the array to write values to. The attribute type and the * number of elements must match the persistence type, as well as * the number of components of the attribute. diff --git a/src/com/esri/core/geometry/VertexDescription.java b/src/com/esri/core/geometry/VertexDescription.java index c2f34be6..b10afe2e 100644 --- a/src/com/esri/core/geometry/VertexDescription.java +++ b/src/com/esri/core/geometry/VertexDescription.java @@ -185,11 +185,11 @@ public final int getAttributeCount() { * The index of the attribute in the description. Max value is * GetAttributeCount() - 1. */ - public final int getSemantics(int attrbuteIndex) { - if (attrbuteIndex < 0 || attrbuteIndex > m_attributeCount) + public final int getSemantics(int attributeIndex) { + if (attributeIndex < 0 || attributeIndex > m_attributeCount) throw new IllegalArgumentException(); - return m_semantics[attrbuteIndex]; + return m_semantics[attributeIndex]; } /** diff --git a/unittest/com/esri/core/geometry/TestCut.java b/unittest/com/esri/core/geometry/TestCut.java index 8bb8f25c..f67a4d1f 100644 --- a/unittest/com/esri/core/geometry/TestCut.java +++ b/unittest/com/esri/core/geometry/TestCut.java @@ -1,520 +1,520 @@ -package com.esri.core.geometry; - -import junit.framework.TestCase; -import org.junit.Test; - -public class TestCut extends TestCase { - @Override - protected void setUp() throws Exception { - super.setUp(); - } - - @Override - protected void tearDown() throws Exception { - super.tearDown(); - } - - @Test - public static void testCut4326() { - SpatialReference sr = SpatialReference.create(4326); - testConsiderTouch1(sr); - testConsiderTouch2(sr); - testPolygon5(sr); - testPolygon7(sr); - testPolygon8(sr); - testPolygon9(sr); - testEngine(sr); - - } - - public static void testConsiderTouch1(SpatialReference spatialReference) { - OperatorFactoryLocal engine = OperatorFactoryLocal.getInstance(); - OperatorCut opCut = (OperatorCut) engine.getOperator(Operator.Type.Cut); - - Polyline polyline1 = makePolyline1(); - Polyline cutter1 = makePolylineCutter1(); - - GeometryCursor cursor = opCut.execute(true, polyline1, cutter1, - spatialReference, null); - Polyline cut; - int pathCount; - int segmentCount; - double length; - - cut = (Polyline) cursor.next(); - pathCount = cut.getPathCount(); - segmentCount = cut.getSegmentCount(); - length = cut.calculateLength2D(); - assertTrue(pathCount == 4); - assertTrue(segmentCount == 4); - assertTrue(length == 6); - - cut = (Polyline) cursor.next(); - pathCount = cut.getPathCount(); - segmentCount = cut.getSegmentCount(); - length = cut.calculateLength2D(); - assertTrue(pathCount == 6); - assertTrue(segmentCount == 8); - assertTrue(length == 12); - - cut = (Polyline) cursor.next(); - pathCount = cut.getPathCount(); - segmentCount = cut.getSegmentCount(); - length = cut.calculateLength2D(); - assertTrue(pathCount == 1); - assertTrue(segmentCount == 1); - assertTrue(length == 1); - - cut = (Polyline) cursor.next(); - pathCount = cut.getPathCount(); - segmentCount = cut.getSegmentCount(); - length = cut.calculateLength2D(); - assertTrue(pathCount == 1); - assertTrue(segmentCount == 1); - assertTrue(length == 1); - - cut = (Polyline) cursor.next(); - assertTrue(cut == null); - } - - public static void testConsiderTouch2(SpatialReference spatialReference) { - OperatorFactoryLocal engine = OperatorFactoryLocal.getInstance(); - OperatorCut opCut = (OperatorCut) engine.getOperator(Operator.Type.Cut); - - Polyline polyline2 = makePolyline2(); - Polyline cutter2 = makePolylineCutter2(); - - GeometryCursor cursor = opCut.execute(true, polyline2, cutter2, - spatialReference, null); - Polyline cut; - int pathCount; - int segmentCount; - double length; - - cut = (Polyline) cursor.next(); - pathCount = cut.getPathCount(); - segmentCount = cut.getSegmentCount(); - length = cut.calculateLength2D(); - assertTrue(pathCount == 4); - assertTrue(segmentCount == 4); - assertTrue(Math.abs(length - 5.74264068) <= 0.001); - - cut = (Polyline) cursor.next(); - pathCount = cut.getPathCount(); - segmentCount = cut.getSegmentCount(); - length = cut.calculateLength2D(); - assertTrue(pathCount == 6); - assertTrue(segmentCount == 8); - assertTrue(length == 6.75); - - cut = (Polyline) cursor.next(); - pathCount = cut.getPathCount(); - segmentCount = cut.getSegmentCount(); - length = cut.calculateLength2D(); - assertTrue(pathCount == 1); - assertTrue(segmentCount == 1); - assertTrue(Math.abs(length - 0.5) <= 0.001); - - cut = (Polyline) cursor.next(); - pathCount = cut.getPathCount(); - segmentCount = cut.getSegmentCount(); - length = cut.calculateLength2D(); - assertTrue(pathCount == 1); - assertTrue(segmentCount == 1); - assertTrue(Math.abs(length - 0.25) <= 0.001); - - cut = (Polyline) cursor.next(); - pathCount = cut.getPathCount(); - segmentCount = cut.getSegmentCount(); - length = cut.calculateLength2D(); - assertTrue(pathCount == 1); - assertTrue(segmentCount == 1); - assertTrue(Math.abs(length - 1) <= 0.001); - - cut = (Polyline) cursor.next(); - pathCount = cut.getPathCount(); - segmentCount = cut.getSegmentCount(); - length = cut.calculateLength2D(); - assertTrue(pathCount == 1); - assertTrue(segmentCount == 1); - assertTrue(Math.abs(length - 1.41421356) <= 0.001); - - cut = (Polyline) cursor.next(); - assertTrue(cut == null); - } - - public static void testPolygon5(SpatialReference spatialReference) { - OperatorFactoryLocal engine = OperatorFactoryLocal.getInstance(); - OperatorCut opCut = (OperatorCut) engine.getOperator(Operator.Type.Cut); - - Polygon polygon5 = makePolygon5(); - Polyline cutter5 = makePolygonCutter5(); - - GeometryCursor cursor = opCut.execute(true, polygon5, cutter5, - spatialReference, null); - Polygon cut; - int pathCount; - int pointCount; - double area; - - cut = (Polygon) cursor.next(); - pathCount = cut.getPathCount(); - pointCount = cut.getPointCount(); - area = cut.calculateArea2D(); - assertTrue(pathCount == 4); - assertTrue(pointCount == 12); - assertTrue(area == 450); - - cut = (Polygon) cursor.next(); - pathCount = cut.getPathCount(); - pointCount = cut.getPointCount(); - area = cut.calculateArea2D(); - assertTrue(pathCount == 1); - assertTrue(pointCount == 4); - assertTrue(area == 450); - - cut = (Polygon) cursor.next(); - assertTrue(cut == null); - } - - public static void testPolygon7(SpatialReference spatialReference) { - OperatorFactoryLocal engine = OperatorFactoryLocal.getInstance(); - OperatorCut opCut = (OperatorCut) engine.getOperator(Operator.Type.Cut); - - Polygon cut; - int path_count; - int point_count; - double area; - - Polygon polygon7 = makePolygon7(); - Polyline cutter7 = makePolygonCutter7(); - GeometryCursor cursor = opCut.execute(false, polygon7, cutter7, - spatialReference, null); - - cut = (Polygon) cursor.next(); - path_count = cut.getPathCount(); - point_count = cut.getPointCount(); - area = cut.calculateArea2D(); - assertTrue(path_count == 1); - assertTrue(point_count == 4); - assertTrue(area == 100); - - cut = (Polygon) cursor.next(); - assertTrue(cut.isEmpty()); - - cut = (Polygon) cursor.next(); - path_count = cut.getPathCount(); - point_count = cut.getPointCount(); - area = cut.calculateArea2D(); - assertTrue(path_count == 2); - assertTrue(point_count == 8); - assertTrue(area == 800); - - cut = (Polygon) cursor.next(); - assertTrue(cut == null); - } - - public static void testPolygon8(SpatialReference spatialReference) { - OperatorFactoryLocal engine = OperatorFactoryLocal.getInstance(); - OperatorCut opCut = (OperatorCut) engine.getOperator(Operator.Type.Cut); - - Polygon polygon8 = makePolygon8(); - Polyline cutter8 = makePolygonCutter8(); - - GeometryCursor cursor = opCut.execute(true, polygon8, cutter8, - spatialReference, null); - Polygon cut; - int pathCount; - int pointCount; - double area; - - cut = (Polygon) cursor.next(); - assertTrue(cut.isEmpty()); - - cut = (Polygon) cursor.next(); - pathCount = cut.getPathCount(); - pointCount = cut.getPointCount(); - area = cut.calculateArea2D(); - assertTrue(pathCount == 1); - assertTrue(pointCount == 4); - assertTrue(area == 100); - - cut = (Polygon) cursor.next(); - pathCount = cut.getPathCount(); - pointCount = cut.getPointCount(); - area = cut.calculateArea2D(); - assertTrue(pathCount == 2); - assertTrue(pointCount == 8); - assertTrue(area == 800); - - cut = (Polygon) cursor.next(); - assertTrue(cut == null); - } - - public static void testPolygon9(SpatialReference spatialReference) { - OperatorFactoryLocal engine = OperatorFactoryLocal.getInstance(); - OperatorCut opCut = (OperatorCut) engine.getOperator(Operator.Type.Cut); - - Polygon cut; - int path_count; - int point_count; - double area; - - Polygon polygon9 = makePolygon9(); - Polyline cutter9 = makePolygonCutter9(); - GeometryCursor cursor = opCut.execute(false, polygon9, cutter9, - spatialReference, null); - - cut = (Polygon) cursor.next(); - path_count = cut.getPathCount(); - point_count = cut.getPointCount(); - area = cut.calculateArea2D(); - assertTrue(path_count == 3); - assertTrue(point_count == 12); - assertTrue(area == 150); - - cut = (Polygon) cursor.next(); - path_count = cut.getPathCount(); - point_count = cut.getPointCount(); - area = cut.calculateArea2D(); - assertTrue(path_count == 3); - assertTrue(point_count == 12); - assertTrue(area == 150); - - cut = (Polygon) cursor.next(); - assertTrue(cut == null); - } - - public static void testEngine(SpatialReference spatialReference) { - Polygon polygon8 = makePolygon8(); - Polyline cutter8 = makePolygonCutter8(); - - Geometry[] cuts = GeometryEngine.cut(polygon8, cutter8, - spatialReference); - Polygon cut; - int pathCount; - int pointCount; - double area; - - cut = (Polygon) cuts[0]; - pathCount = cut.getPathCount(); - pointCount = cut.getPointCount(); - area = cut.calculateArea2D(); - assertTrue(pathCount == 1); - assertTrue(pointCount == 4); - assertTrue(area == 100); - - cut = (Polygon) cuts[1]; - pathCount = cut.getPathCount(); - pointCount = cut.getPointCount(); - area = cut.calculateArea2D(); - assertTrue(pathCount == 2); - assertTrue(pointCount == 8); - assertTrue(area == 800); - } - - public static Polyline makePolyline1() { - Polyline poly = new Polyline(); - - poly.startPath(0, 0); - poly.lineTo(2, 0); - poly.lineTo(4, 0); - poly.lineTo(6, 0); - poly.lineTo(8, 0); - poly.lineTo(10, 0); - poly.lineTo(12, 0); - poly.lineTo(14, 0); - poly.lineTo(16, 0); - poly.lineTo(18, 0); - poly.lineTo(20, 0); - - return poly; - } - - public static Polyline makePolylineCutter1() { - Polyline poly = new Polyline(); - - poly.startPath(1, 0); - poly.lineTo(4, 0); - - poly.startPath(6, -1); - poly.lineTo(6, 1); - - poly.startPath(6, 0); - poly.lineTo(8, 0); - - poly.startPath(9, -1); - poly.lineTo(9, 1); - - poly.startPath(10, 0); - poly.lineTo(12, 0); - - poly.startPath(12, 1); - poly.lineTo(12, -1); - - poly.startPath(12, 0); - poly.lineTo(15, 0); - - poly.startPath(15, 1); - poly.lineTo(15, -1); - - poly.startPath(16, 0); - poly.lineTo(16, -1); - poly.lineTo(17, -1); - poly.lineTo(17, 1); - poly.lineTo(17, 0); - poly.lineTo(18, 0); - - poly.startPath(18, 0); - poly.lineTo(18, -1); - - return poly; - } - - public static Polyline makePolyline2() { - Polyline poly = new Polyline(); - - poly.startPath(-2, 0); - poly.lineTo(-1, 0); - poly.lineTo(0, 0); - poly.lineTo(2, 0); - poly.lineTo(4, 2); - poly.lineTo(8, 2); - poly.lineTo(10, 4); - poly.lineTo(12, 4); - - return poly; - } - - public static Polyline makePolylineCutter2() { - Polyline poly = new Polyline(); - - poly.startPath(-1.5, 0); - poly.lineTo(-.75, 0); - - poly.startPath(-.5, 0); - poly.lineTo(1, 0); - poly.lineTo(1, 2); - poly.lineTo(3, -2); - poly.lineTo(4, 2); - poly.lineTo(5, -2); - poly.lineTo(5, 4); - poly.lineTo(8, 2); - poly.lineTo(6, 0); - poly.lineTo(6, 3); - - poly.startPath(9, 5); - poly.lineTo(9, 2); - poly.lineTo(10, 2); - poly.lineTo(10, 5); - poly.lineTo(10.5, 5); - poly.lineTo(10.5, 3); - - poly.startPath(11, 4); - poly.lineTo(11, 5); - - poly.startPath(12, 5); - poly.lineTo(12, 4); - - return poly; - } - - public static Polygon makePolygon5() { - Polygon poly = new Polygon(); - - poly.startPath(0, 0); - poly.lineTo(0, 30); - poly.lineTo(30, 30); - poly.lineTo(30, 0); - - return poly; - } - - public static Polyline makePolygonCutter5() { - Polyline poly = new Polyline(); - - poly.startPath(15, 0); - poly.lineTo(0, 15); - poly.lineTo(15, 30); - poly.lineTo(30, 15); - poly.lineTo(15, 0); - - return poly; - } - - public static Polygon makePolygon7() { - Polygon poly = new Polygon(); - - poly.startPath(0, 0); - poly.lineTo(0, 30); - poly.lineTo(30, 30); - poly.lineTo(30, 0); - - return poly; - } - - public static Polyline makePolygonCutter7() { - Polyline poly = new Polyline(); - - poly.startPath(10, 10); - poly.lineTo(20, 10); - poly.lineTo(20, 20); - poly.lineTo(10, 20); - poly.lineTo(10, 10); - - return poly; - } - - public static Polygon makePolygon8() { - Polygon poly = new Polygon(); - - poly.startPath(0, 0); - poly.lineTo(0, 30); - poly.lineTo(30, 30); - poly.lineTo(30, 0); - - return poly; - } - - public static Polyline makePolygonCutter8() { - Polyline poly = new Polyline(); - - poly.startPath(10, 10); - poly.lineTo(10, 20); - poly.lineTo(20, 20); - poly.lineTo(20, 10); - poly.lineTo(10, 10); - - return poly; - } - - public static Polygon makePolygon9() { - Polygon poly = new Polygon(); - - poly.startPath(0, 0); - poly.lineTo(0, 10); - poly.lineTo(10, 10); - poly.lineTo(10, 0); - - poly.startPath(0, 20); - poly.lineTo(0, 30); - poly.lineTo(10, 30); - poly.lineTo(10, 20); - - poly.startPath(0, 40); - poly.lineTo(0, 50); - poly.lineTo(10, 50); - poly.lineTo(10, 40); - - return poly; - } - - public static Polyline makePolygonCutter9() { - Polyline poly = new Polyline(); - - poly.startPath(5, -1); - poly.lineTo(5, 51); - - return poly; - } -} +package com.esri.core.geometry; + +import junit.framework.TestCase; +import org.junit.Test; + +public class TestCut extends TestCase { + @Override + protected void setUp() throws Exception { + super.setUp(); + } + + @Override + protected void tearDown() throws Exception { + super.tearDown(); + } + + @Test + public static void testCut4326() { + SpatialReference sr = SpatialReference.create(4326); + testConsiderTouch1(sr); + testConsiderTouch2(sr); + testPolygon5(sr); + testPolygon7(sr); + testPolygon8(sr); + testPolygon9(sr); + testEngine(sr); + + } + + public static void testConsiderTouch1(SpatialReference spatialReference) { + OperatorFactoryLocal engine = OperatorFactoryLocal.getInstance(); + OperatorCut opCut = (OperatorCut) engine.getOperator(Operator.Type.Cut); + + Polyline polyline1 = makePolyline1(); + Polyline cutter1 = makePolylineCutter1(); + + GeometryCursor cursor = opCut.execute(true, polyline1, cutter1, + spatialReference, null); + Polyline cut; + int pathCount; + int segmentCount; + double length; + + cut = (Polyline) cursor.next(); + pathCount = cut.getPathCount(); + segmentCount = cut.getSegmentCount(); + length = cut.calculateLength2D(); + assertTrue(pathCount == 4); + assertTrue(segmentCount == 4); + assertTrue(length == 6); + + cut = (Polyline) cursor.next(); + pathCount = cut.getPathCount(); + segmentCount = cut.getSegmentCount(); + length = cut.calculateLength2D(); + assertTrue(pathCount == 6); + assertTrue(segmentCount == 8); + assertTrue(length == 12); + + cut = (Polyline) cursor.next(); + pathCount = cut.getPathCount(); + segmentCount = cut.getSegmentCount(); + length = cut.calculateLength2D(); + assertTrue(pathCount == 1); + assertTrue(segmentCount == 1); + assertTrue(length == 1); + + cut = (Polyline) cursor.next(); + pathCount = cut.getPathCount(); + segmentCount = cut.getSegmentCount(); + length = cut.calculateLength2D(); + assertTrue(pathCount == 1); + assertTrue(segmentCount == 1); + assertTrue(length == 1); + + cut = (Polyline) cursor.next(); + assertTrue(cut == null); + } + + public static void testConsiderTouch2(SpatialReference spatialReference) { + OperatorFactoryLocal engine = OperatorFactoryLocal.getInstance(); + OperatorCut opCut = (OperatorCut) engine.getOperator(Operator.Type.Cut); + + Polyline polyline2 = makePolyline2(); + Polyline cutter2 = makePolylineCutter2(); + + GeometryCursor cursor = opCut.execute(true, polyline2, cutter2, + spatialReference, null); + Polyline cut; + int pathCount; + int segmentCount; + double length; + + cut = (Polyline) cursor.next(); + pathCount = cut.getPathCount(); + segmentCount = cut.getSegmentCount(); + length = cut.calculateLength2D(); + assertTrue(pathCount == 4); + assertTrue(segmentCount == 4); + assertTrue(Math.abs(length - 5.74264068) <= 0.001); + + cut = (Polyline) cursor.next(); + pathCount = cut.getPathCount(); + segmentCount = cut.getSegmentCount(); + length = cut.calculateLength2D(); + assertTrue(pathCount == 6); + assertTrue(segmentCount == 8); + assertTrue(length == 6.75); + + cut = (Polyline) cursor.next(); + pathCount = cut.getPathCount(); + segmentCount = cut.getSegmentCount(); + length = cut.calculateLength2D(); + assertTrue(pathCount == 1); + assertTrue(segmentCount == 1); + assertTrue(Math.abs(length - 0.5) <= 0.001); + + cut = (Polyline) cursor.next(); + pathCount = cut.getPathCount(); + segmentCount = cut.getSegmentCount(); + length = cut.calculateLength2D(); + assertTrue(pathCount == 1); + assertTrue(segmentCount == 1); + assertTrue(Math.abs(length - 0.25) <= 0.001); + + cut = (Polyline) cursor.next(); + pathCount = cut.getPathCount(); + segmentCount = cut.getSegmentCount(); + length = cut.calculateLength2D(); + assertTrue(pathCount == 1); + assertTrue(segmentCount == 1); + assertTrue(Math.abs(length - 1) <= 0.001); + + cut = (Polyline) cursor.next(); + pathCount = cut.getPathCount(); + segmentCount = cut.getSegmentCount(); + length = cut.calculateLength2D(); + assertTrue(pathCount == 1); + assertTrue(segmentCount == 1); + assertTrue(Math.abs(length - 1.41421356) <= 0.001); + + cut = (Polyline) cursor.next(); + assertTrue(cut == null); + } + + public static void testPolygon5(SpatialReference spatialReference) { + OperatorFactoryLocal engine = OperatorFactoryLocal.getInstance(); + OperatorCut opCut = (OperatorCut) engine.getOperator(Operator.Type.Cut); + + Polygon polygon5 = makePolygon5(); + Polyline cutter5 = makePolygonCutter5(); + + GeometryCursor cursor = opCut.execute(true, polygon5, cutter5, + spatialReference, null); + Polygon cut; + int pathCount; + int pointCount; + double area; + + cut = (Polygon) cursor.next(); + pathCount = cut.getPathCount(); + pointCount = cut.getPointCount(); + area = cut.calculateArea2D(); + assertTrue(pathCount == 4); + assertTrue(pointCount == 12); + assertTrue(area == 450); + + cut = (Polygon) cursor.next(); + pathCount = cut.getPathCount(); + pointCount = cut.getPointCount(); + area = cut.calculateArea2D(); + assertTrue(pathCount == 1); + assertTrue(pointCount == 4); + assertTrue(area == 450); + + cut = (Polygon) cursor.next(); + assertTrue(cut == null); + } + + public static void testPolygon7(SpatialReference spatialReference) { + OperatorFactoryLocal engine = OperatorFactoryLocal.getInstance(); + OperatorCut opCut = (OperatorCut) engine.getOperator(Operator.Type.Cut); + + Polygon cut; + int path_count; + int point_count; + double area; + + Polygon polygon7 = makePolygon7(); + Polyline cutter7 = makePolygonCutter7(); + GeometryCursor cursor = opCut.execute(false, polygon7, cutter7, + spatialReference, null); + + cut = (Polygon) cursor.next(); + path_count = cut.getPathCount(); + point_count = cut.getPointCount(); + area = cut.calculateArea2D(); + assertTrue(path_count == 1); + assertTrue(point_count == 4); + assertTrue(area == 100); + + cut = (Polygon) cursor.next(); + assertTrue(cut.isEmpty()); + + cut = (Polygon) cursor.next(); + path_count = cut.getPathCount(); + point_count = cut.getPointCount(); + area = cut.calculateArea2D(); + assertTrue(path_count == 2); + assertTrue(point_count == 8); + assertTrue(area == 800); + + cut = (Polygon) cursor.next(); + assertTrue(cut == null); + } + + public static void testPolygon8(SpatialReference spatialReference) { + OperatorFactoryLocal engine = OperatorFactoryLocal.getInstance(); + OperatorCut opCut = (OperatorCut) engine.getOperator(Operator.Type.Cut); + + Polygon polygon8 = makePolygon8(); + Polyline cutter8 = makePolygonCutter8(); + + GeometryCursor cursor = opCut.execute(true, polygon8, cutter8, + spatialReference, null); + Polygon cut; + int pathCount; + int pointCount; + double area; + + cut = (Polygon) cursor.next(); + assertTrue(cut.isEmpty()); + + cut = (Polygon) cursor.next(); + pathCount = cut.getPathCount(); + pointCount = cut.getPointCount(); + area = cut.calculateArea2D(); + assertTrue(pathCount == 1); + assertTrue(pointCount == 4); + assertTrue(area == 100); + + cut = (Polygon) cursor.next(); + pathCount = cut.getPathCount(); + pointCount = cut.getPointCount(); + area = cut.calculateArea2D(); + assertTrue(pathCount == 2); + assertTrue(pointCount == 8); + assertTrue(area == 800); + + cut = (Polygon) cursor.next(); + assertTrue(cut == null); + } + + public static void testPolygon9(SpatialReference spatialReference) { + OperatorFactoryLocal engine = OperatorFactoryLocal.getInstance(); + OperatorCut opCut = (OperatorCut) engine.getOperator(Operator.Type.Cut); + + Polygon cut; + int path_count; + int point_count; + double area; + + Polygon polygon9 = makePolygon9(); + Polyline cutter9 = makePolygonCutter9(); + GeometryCursor cursor = opCut.execute(false, polygon9, cutter9, + spatialReference, null); + + cut = (Polygon) cursor.next(); + path_count = cut.getPathCount(); + point_count = cut.getPointCount(); + area = cut.calculateArea2D(); + assertTrue(path_count == 3); + assertTrue(point_count == 12); + assertTrue(area == 150); + + cut = (Polygon) cursor.next(); + path_count = cut.getPathCount(); + point_count = cut.getPointCount(); + area = cut.calculateArea2D(); + assertTrue(path_count == 3); + assertTrue(point_count == 12); + assertTrue(area == 150); + + cut = (Polygon) cursor.next(); + assertTrue(cut == null); + } + + public static void testEngine(SpatialReference spatialReference) { + Polygon polygon8 = makePolygon8(); + Polyline cutter8 = makePolygonCutter8(); + + Geometry[] cuts = GeometryEngine.cut(polygon8, cutter8, + spatialReference); + Polygon cut; + int pathCount; + int pointCount; + double area; + + cut = (Polygon) cuts[0]; + pathCount = cut.getPathCount(); + pointCount = cut.getPointCount(); + area = cut.calculateArea2D(); + assertTrue(pathCount == 1); + assertTrue(pointCount == 4); + assertTrue(area == 100); + + cut = (Polygon) cuts[1]; + pathCount = cut.getPathCount(); + pointCount = cut.getPointCount(); + area = cut.calculateArea2D(); + assertTrue(pathCount == 2); + assertTrue(pointCount == 8); + assertTrue(area == 800); + } + + public static Polyline makePolyline1() { + Polyline poly = new Polyline(); + + poly.startPath(0, 0); + poly.lineTo(2, 0); + poly.lineTo(4, 0); + poly.lineTo(6, 0); + poly.lineTo(8, 0); + poly.lineTo(10, 0); + poly.lineTo(12, 0); + poly.lineTo(14, 0); + poly.lineTo(16, 0); + poly.lineTo(18, 0); + poly.lineTo(20, 0); + + return poly; + } + + public static Polyline makePolylineCutter1() { + Polyline poly = new Polyline(); + + poly.startPath(1, 0); + poly.lineTo(4, 0); + + poly.startPath(6, -1); + poly.lineTo(6, 1); + + poly.startPath(6, 0); + poly.lineTo(8, 0); + + poly.startPath(9, -1); + poly.lineTo(9, 1); + + poly.startPath(10, 0); + poly.lineTo(12, 0); + + poly.startPath(12, 1); + poly.lineTo(12, -1); + + poly.startPath(12, 0); + poly.lineTo(15, 0); + + poly.startPath(15, 1); + poly.lineTo(15, -1); + + poly.startPath(16, 0); + poly.lineTo(16, -1); + poly.lineTo(17, -1); + poly.lineTo(17, 1); + poly.lineTo(17, 0); + poly.lineTo(18, 0); + + poly.startPath(18, 0); + poly.lineTo(18, -1); + + return poly; + } + + public static Polyline makePolyline2() { + Polyline poly = new Polyline(); + + poly.startPath(-2, 0); + poly.lineTo(-1, 0); + poly.lineTo(0, 0); + poly.lineTo(2, 0); + poly.lineTo(4, 2); + poly.lineTo(8, 2); + poly.lineTo(10, 4); + poly.lineTo(12, 4); + + return poly; + } + + public static Polyline makePolylineCutter2() { + Polyline poly = new Polyline(); + + poly.startPath(-1.5, 0); + poly.lineTo(-.75, 0); + + poly.startPath(-.5, 0); + poly.lineTo(1, 0); + poly.lineTo(1, 2); + poly.lineTo(3, -2); + poly.lineTo(4, 2); + poly.lineTo(5, -2); + poly.lineTo(5, 4); + poly.lineTo(8, 2); + poly.lineTo(6, 0); + poly.lineTo(6, 3); + + poly.startPath(9, 5); + poly.lineTo(9, 2); + poly.lineTo(10, 2); + poly.lineTo(10, 5); + poly.lineTo(10.5, 5); + poly.lineTo(10.5, 3); + + poly.startPath(11, 4); + poly.lineTo(11, 5); + + poly.startPath(12, 5); + poly.lineTo(12, 4); + + return poly; + } + + public static Polygon makePolygon5() { + Polygon poly = new Polygon(); + + poly.startPath(0, 0); + poly.lineTo(0, 30); + poly.lineTo(30, 30); + poly.lineTo(30, 0); + + return poly; + } + + public static Polyline makePolygonCutter5() { + Polyline poly = new Polyline(); + + poly.startPath(15, 0); + poly.lineTo(0, 15); + poly.lineTo(15, 30); + poly.lineTo(30, 15); + poly.lineTo(15, 0); + + return poly; + } + + public static Polygon makePolygon7() { + Polygon poly = new Polygon(); + + poly.startPath(0, 0); + poly.lineTo(0, 30); + poly.lineTo(30, 30); + poly.lineTo(30, 0); + + return poly; + } + + public static Polyline makePolygonCutter7() { + Polyline poly = new Polyline(); + + poly.startPath(10, 10); + poly.lineTo(20, 10); + poly.lineTo(20, 20); + poly.lineTo(10, 20); + poly.lineTo(10, 10); + + return poly; + } + + public static Polygon makePolygon8() { + Polygon poly = new Polygon(); + + poly.startPath(0, 0); + poly.lineTo(0, 30); + poly.lineTo(30, 30); + poly.lineTo(30, 0); + + return poly; + } + + public static Polyline makePolygonCutter8() { + Polyline poly = new Polyline(); + + poly.startPath(10, 10); + poly.lineTo(10, 20); + poly.lineTo(20, 20); + poly.lineTo(20, 10); + poly.lineTo(10, 10); + + return poly; + } + + public static Polygon makePolygon9() { + Polygon poly = new Polygon(); + + poly.startPath(0, 0); + poly.lineTo(0, 10); + poly.lineTo(10, 10); + poly.lineTo(10, 0); + + poly.startPath(0, 20); + poly.lineTo(0, 30); + poly.lineTo(10, 30); + poly.lineTo(10, 20); + + poly.startPath(0, 40); + poly.lineTo(0, 50); + poly.lineTo(10, 50); + poly.lineTo(10, 40); + + return poly; + } + + public static Polyline makePolygonCutter9() { + Polyline poly = new Polyline(); + + poly.startPath(5, -1); + poly.lineTo(5, 51); + + return poly; + } +} diff --git a/unittest/com/esri/core/geometry/TestFailed.java b/unittest/com/esri/core/geometry/TestFailed.java index da570fbf..aac65c3c 100644 --- a/unittest/com/esri/core/geometry/TestFailed.java +++ b/unittest/com/esri/core/geometry/TestFailed.java @@ -1,65 +1,65 @@ -package com.esri.core.geometry; - -import junit.framework.TestCase; -import org.junit.Test; - -public class TestFailed extends TestCase { - @Override - protected void setUp() throws Exception { - super.setUp(); - } - - @Override - protected void tearDown() throws Exception { - super.tearDown(); - } - - @Test - public void testCenterXY() { - Envelope env = new Envelope(-130, 30, -70, 50); - assertEquals(-100, env.getCenterX(), 0); - assertEquals(40, env.getCenterY(), 0); - } - - @Test - public void testGeometryOperationSupport() { - Geometry baseGeom = new Point(-130, 10); - Geometry comparisonGeom = new Point(-130, 10); - SpatialReference sr = SpatialReference.create(4326); - - @SuppressWarnings("unused") - Geometry diffGeom = null; - int noException = 1; // no exception - try { - diffGeom = GeometryEngine.difference(baseGeom, comparisonGeom, sr); - - } catch (IllegalArgumentException ex) { - noException = 0; - } catch (GeometryException ex) { - System.out.println(ex.internalCode); - noException = 0; - } - assertEquals(noException, 1); - } - - @Test - public void TestIntersection() { - OperatorIntersects op = (OperatorIntersects) OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.Intersects); - Polygon polygon = new Polygon(); - // outer ring1 - polygon.startPath(0, 0); - polygon.lineTo(10, 10); - polygon.lineTo(20, 0); - - Point point1 = new Point(15, 10); - Point point2 = new Point(2, 10); - Point point3 = new Point(5, 5); - boolean res = op.execute(polygon, point1, null, null); - assertTrue(!res); - res = op.execute(polygon, point2, null, null); - assertTrue(!res); - res = op.execute(polygon, point3, null, null); - assertTrue(res); - } -} +package com.esri.core.geometry; + +import junit.framework.TestCase; +import org.junit.Test; + +public class TestFailed extends TestCase { + @Override + protected void setUp() throws Exception { + super.setUp(); + } + + @Override + protected void tearDown() throws Exception { + super.tearDown(); + } + + @Test + public void testCenterXY() { + Envelope env = new Envelope(-130, 30, -70, 50); + assertEquals(-100, env.getCenterX(), 0); + assertEquals(40, env.getCenterY(), 0); + } + + @Test + public void testGeometryOperationSupport() { + Geometry baseGeom = new Point(-130, 10); + Geometry comparisonGeom = new Point(-130, 10); + SpatialReference sr = SpatialReference.create(4326); + + @SuppressWarnings("unused") + Geometry diffGeom = null; + int noException = 1; // no exception + try { + diffGeom = GeometryEngine.difference(baseGeom, comparisonGeom, sr); + + } catch (IllegalArgumentException ex) { + noException = 0; + } catch (GeometryException ex) { + System.out.println(ex.internalCode); + noException = 0; + } + assertEquals(noException, 1); + } + + @Test + public void TestIntersection() { + OperatorIntersects op = (OperatorIntersects) OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Intersects); + Polygon polygon = new Polygon(); + // outer ring1 + polygon.startPath(0, 0); + polygon.lineTo(10, 10); + polygon.lineTo(20, 0); + + Point point1 = new Point(15, 10); + Point point2 = new Point(2, 10); + Point point3 = new Point(5, 5); + boolean res = op.execute(polygon, point1, null, null); + assertTrue(!res); + res = op.execute(polygon, point2, null, null); + assertTrue(!res); + res = op.execute(polygon, point3, null, null); + assertTrue(res); + } +} diff --git a/unittest/com/esri/core/geometry/TestGeodetic.java b/unittest/com/esri/core/geometry/TestGeodetic.java index 453f7e1b..0c788cc2 100644 --- a/unittest/com/esri/core/geometry/TestGeodetic.java +++ b/unittest/com/esri/core/geometry/TestGeodetic.java @@ -1,72 +1,72 @@ -package com.esri.core.geometry; - -import junit.framework.TestCase; -import org.junit.Test; - -public class TestGeodetic extends TestCase { - @Override - protected void setUp() throws Exception { - super.setUp(); - } - - @Override - protected void tearDown() throws Exception { - super.tearDown(); - } - - @Test - public static void testTriangleLength() { - Point pt_0 = new Point(10, 10); - Point pt_1 = new Point(20, 20); - Point pt_2 = new Point(20, 10); - double length = 0.0; - length += GeometryEngine.geodesicDistanceOnWGS84(pt_0, pt_1); - length += GeometryEngine.geodesicDistanceOnWGS84(pt_1, pt_2); - length += GeometryEngine.geodesicDistanceOnWGS84(pt_2, pt_0); - assertTrue(Math.abs(length - 3744719.4094597572) < 1e-13 * 3744719.4094597572); - } - - @Test - public static void testRotationInvariance() { - Point pt_0 = new Point(10, 40); - Point pt_1 = new Point(20, 60); - Point pt_2 = new Point(20, 40); - double length = 0.0; - length += GeometryEngine.geodesicDistanceOnWGS84(pt_0, pt_1); - length += GeometryEngine.geodesicDistanceOnWGS84(pt_1, pt_2); - length += GeometryEngine.geodesicDistanceOnWGS84(pt_2, pt_0); - assertTrue(Math.abs(length - 5409156.3896271614) < 1e-13 * 5409156.3896271614); - - for (int i = -540; i < 540; i += 5) { - pt_0.setXY(i + 10, 40); - pt_1.setXY(i + 20, 60); - pt_2.setXY(i + 20, 40); - length = 0.0; - length += GeometryEngine.geodesicDistanceOnWGS84(pt_0, pt_1); - length += GeometryEngine.geodesicDistanceOnWGS84(pt_1, pt_2); - length += GeometryEngine.geodesicDistanceOnWGS84(pt_2, pt_0); - assertTrue(Math.abs(length - 5409156.3896271614) < 1e-13 * 5409156.3896271614); - } - } - - @Test - public static void testLengthAccurateCR191313() { - /* - * // random_test(); OperatorFactoryLocal engine = - * OperatorFactoryLocal.getInstance(); //TODO: Make this: - * OperatorShapePreservingLength geoLengthOp = - * (OperatorShapePreservingLength) - * factory.getOperator(Operator.Type.ShapePreservingLength); - * SpatialReference spatialRef = SpatialReference.create(102631); - * //[6097817.59407673 - * ,17463475.2931517],[-1168053.34617516,11199801.3734424 - * ]]],"spatialReference":{"wkid":102631} - * - * Polyline polyline = new Polyline(); - * polyline.startPath(6097817.59407673, 17463475.2931517); - * polyline.lineTo(-1168053.34617516, 11199801.3734424); double length = - * geoLengthOp.execute(polyline, spatialRef, null); - * assertTrue(Math.abs(length - 2738362.3249366437) < 2e-9 * length); - */ - } -} +package com.esri.core.geometry; + +import junit.framework.TestCase; +import org.junit.Test; + +public class TestGeodetic extends TestCase { + @Override + protected void setUp() throws Exception { + super.setUp(); + } + + @Override + protected void tearDown() throws Exception { + super.tearDown(); + } + + @Test + public static void testTriangleLength() { + Point pt_0 = new Point(10, 10); + Point pt_1 = new Point(20, 20); + Point pt_2 = new Point(20, 10); + double length = 0.0; + length += GeometryEngine.geodesicDistanceOnWGS84(pt_0, pt_1); + length += GeometryEngine.geodesicDistanceOnWGS84(pt_1, pt_2); + length += GeometryEngine.geodesicDistanceOnWGS84(pt_2, pt_0); + assertTrue(Math.abs(length - 3744719.4094597572) < 1e-13 * 3744719.4094597572); + } + + @Test + public static void testRotationInvariance() { + Point pt_0 = new Point(10, 40); + Point pt_1 = new Point(20, 60); + Point pt_2 = new Point(20, 40); + double length = 0.0; + length += GeometryEngine.geodesicDistanceOnWGS84(pt_0, pt_1); + length += GeometryEngine.geodesicDistanceOnWGS84(pt_1, pt_2); + length += GeometryEngine.geodesicDistanceOnWGS84(pt_2, pt_0); + assertTrue(Math.abs(length - 5409156.3896271614) < 1e-13 * 5409156.3896271614); + + for (int i = -540; i < 540; i += 5) { + pt_0.setXY(i + 10, 40); + pt_1.setXY(i + 20, 60); + pt_2.setXY(i + 20, 40); + length = 0.0; + length += GeometryEngine.geodesicDistanceOnWGS84(pt_0, pt_1); + length += GeometryEngine.geodesicDistanceOnWGS84(pt_1, pt_2); + length += GeometryEngine.geodesicDistanceOnWGS84(pt_2, pt_0); + assertTrue(Math.abs(length - 5409156.3896271614) < 1e-13 * 5409156.3896271614); + } + } + + @Test + public static void testLengthAccurateCR191313() { + /* + * // random_test(); OperatorFactoryLocal engine = + * OperatorFactoryLocal.getInstance(); //TODO: Make this: + * OperatorShapePreservingLength geoLengthOp = + * (OperatorShapePreservingLength) + * factory.getOperator(Operator.Type.ShapePreservingLength); + * SpatialReference spatialRef = SpatialReference.create(102631); + * //[6097817.59407673 + * ,17463475.2931517],[-1168053.34617516,11199801.3734424 + * ]]],"spatialReference":{"wkid":102631} + * + * Polyline polyline = new Polyline(); + * polyline.startPath(6097817.59407673, 17463475.2931517); + * polyline.lineTo(-1168053.34617516, 11199801.3734424); double length = + * geoLengthOp.execute(polyline, spatialRef, null); + * assertTrue(Math.abs(length - 2738362.3249366437) < 2e-9 * length); + */ + } + } diff --git a/unittest/com/esri/core/geometry/TestGeomToJSonExportSRFromWkiOrWkt_CR181369.java b/unittest/com/esri/core/geometry/TestGeomToJSonExportSRFromWkiOrWkt_CR181369.java index 17fc2ccc..90277da9 100644 --- a/unittest/com/esri/core/geometry/TestGeomToJSonExportSRFromWkiOrWkt_CR181369.java +++ b/unittest/com/esri/core/geometry/TestGeomToJSonExportSRFromWkiOrWkt_CR181369.java @@ -1,579 +1,579 @@ -package com.esri.core.geometry; - -import java.io.IOException; -import org.codehaus.jackson.JsonFactory; -import org.codehaus.jackson.JsonParseException; -import org.codehaus.jackson.JsonParser; -import junit.framework.TestCase; -import org.junit.Test; - -public class TestGeomToJSonExportSRFromWkiOrWkt_CR181369 extends TestCase { - @Override - protected void setUp() throws Exception { - super.setUp(); - } - - @Override - protected void tearDown() throws Exception { - super.tearDown(); - } - - JsonFactory factory = new JsonFactory(); - SpatialReference spatialReferenceWebMerc1 = SpatialReference.create(102100); - SpatialReference spatialReferenceWebMerc2 = SpatialReference - .create(spatialReferenceWebMerc1.getLatestID()); - SpatialReference spatialReferenceWGS84 = SpatialReference.create(4326); - - @Test - public void testGeomToJSonExportSRFromWkiOrWkt_CR181369() - throws JsonParseException, IOException { - testPoint(); - testPolyline(); - testPolygon(); - testEnvelope(); - testMultiPoint(); - testCR181369(); - // These tests return the result of a method called - // checkResultSpatialRef. - // However, the tests pass or fail regardless of what that method - // returns. - } - - boolean testPoint() throws JsonParseException, IOException { - boolean bAnswer = true; - Point point1 = new Point(10.0, 20.0); - Point pointEmpty = new Point(); - { - JsonParser pointWebMerc1Parser = factory - .createJsonParser(GeometryEngine.geometryToJson( - spatialReferenceWebMerc1, point1)); - MapGeometry pointWebMerc1MP = GeometryEngine - .jsonToGeometry(pointWebMerc1Parser); - assertTrue(point1.getX() == ((Point) pointWebMerc1MP.getGeometry()) - .getX()); - assertTrue(point1.getY() == ((Point) pointWebMerc1MP.getGeometry()) - .getY()); - assertTrue(spatialReferenceWebMerc1.getID() == pointWebMerc1MP - .getSpatialReference().getID() - || pointWebMerc1MP.getSpatialReference().getID() == 3857); - - if (!checkResultSpatialRef(pointWebMerc1MP, 102100, 3857)) { - bAnswer = false; - } - - pointWebMerc1Parser = factory.createJsonParser(GeometryEngine - .geometryToJson(null, point1)); - pointWebMerc1MP = GeometryEngine - .jsonToGeometry(pointWebMerc1Parser); - assertTrue(null == pointWebMerc1MP.getSpatialReference()); - - if (pointWebMerc1MP.getSpatialReference() != null) { - if (!checkResultSpatialRef(pointWebMerc1MP, 102100, 3857)) { - bAnswer = false; - } - } - - String pointEmptyString = GeometryEngine.geometryToJson( - spatialReferenceWebMerc1, pointEmpty); - pointWebMerc1Parser = factory.createJsonParser(pointEmptyString); - // FIXME - // pointWebMerc1MP = - // GeometryEngine.jsonToGeometry(pointWebMerc1Parser); - // assertTrue(pointWebMerc1MP.getGeometry().isEmpty()); - // assertTrue(spatialReferenceWebMerc1.getID() == - // pointWebMerc1MP.getSpatialReference().getID()); - } - - JsonParser pointWebMerc2Parser = factory - .createJsonParser(GeometryEngine.geometryToJson( - spatialReferenceWebMerc2, point1)); - MapGeometry pointWebMerc2MP = GeometryEngine - .jsonToGeometry(pointWebMerc2Parser); - assertTrue(point1.getX() == ((Point) pointWebMerc2MP.getGeometry()) - .getX()); - assertTrue(point1.getY() == ((Point) pointWebMerc2MP.getGeometry()) - .getY()); - assertTrue(spatialReferenceWebMerc2.getLatestID() == pointWebMerc2MP - .getSpatialReference().getLatestID()); - if (!checkResultSpatialRef(pointWebMerc2MP, - spatialReferenceWebMerc2.getLatestID(), 0)) { - bAnswer = false; - } - - { - JsonParser pointWgs84Parser = factory - .createJsonParser(GeometryEngine.geometryToJson( - spatialReferenceWGS84, point1)); - MapGeometry pointWgs84MP = GeometryEngine - .jsonToGeometry(pointWgs84Parser); - assertTrue(point1.getX() == ((Point) pointWgs84MP.getGeometry()) - .getX()); - assertTrue(point1.getY() == ((Point) pointWgs84MP.getGeometry()) - .getY()); - assertTrue(spatialReferenceWGS84.getID() == pointWgs84MP - .getSpatialReference().getID()); - if (!checkResultSpatialRef(pointWgs84MP, 4326, 0)) { - bAnswer = false; - } - } - - { - Point p = new Point(); - String s = GeometryEngine.geometryToJson(spatialReferenceWebMerc1, - p); - assertTrue(s - .equals("{\"x\":null,\"y\":null,\"spatialReference\":{\"wkid\":102100,\"latestWkid\":3857}}")); - - p.addAttribute(VertexDescription.Semantics.Z); - p.addAttribute(VertexDescription.Semantics.M); - s = GeometryEngine.geometryToJson(spatialReferenceWebMerc1, p); - assertTrue(s - .equals("{\"x\":null,\"y\":null,\"z\":null,\"m\":null,\"spatialReference\":{\"wkid\":102100,\"latestWkid\":3857}}")); - - } - - { - Point p = new Point(10.0, 20.0, 30.0); - p.addAttribute(VertexDescription.Semantics.M); - String s = GeometryEngine.geometryToJson(spatialReferenceWebMerc1, - p); - assertTrue(s - .equals("{\"x\":10.0,\"y\":20.0,\"z\":30.0,\"m\":null,\"spatialReference\":{\"wkid\":102100,\"latestWkid\":3857}}")); - } - - {// import - String s = "{\"x\":0.0,\"y\":1.0,\"z\":5.0,\"m\":11.0,\"spatialReference\":{\"wkid\":102100,\"latestWkid\":3857}}"; - JsonParser parser = factory.createJsonParser(s); - MapGeometry map_pt = GeometryEngine.jsonToGeometry(parser); - Point pt = (Point) map_pt.getGeometry(); - assertTrue(pt.getX() == 0.0); - assertTrue(pt.getY() == 1.0); - assertTrue(pt.getZ() == 5.0); - assertTrue(pt.getM() == 11.0); - } - - { - String s = "{\"x\" : 5.0, \"y\" : null, \"spatialReference\" : {\"wkid\" : 4326}} "; - JsonParser parser = factory.createJsonParser(s); - MapGeometry map_pt = GeometryEngine.jsonToGeometry(parser); - Point pt = (Point) map_pt.getGeometry(); - assertTrue(pt.isEmpty()); - SpatialReference spatial_reference = map_pt.getSpatialReference(); - assertTrue(spatial_reference.getID() == 4326); - } - - return bAnswer; - } - - boolean testMultiPoint() throws JsonParseException, IOException { - boolean bAnswer = true; - - MultiPoint multiPoint1 = new MultiPoint(); - multiPoint1.add(-97.06138, 32.837); - multiPoint1.add(-97.06133, 32.836); - multiPoint1.add(-97.06124, 32.834); - multiPoint1.add(-97.06127, 32.832); - - { - String s = GeometryEngine.geometryToJson(spatialReferenceWGS84, - multiPoint1); - JsonParser mPointWgs84Parser = factory.createJsonParser(s); - MapGeometry mPointWgs84MP = GeometryEngine - .jsonToGeometry(mPointWgs84Parser); - assertTrue(multiPoint1.getPointCount() == ((MultiPoint) mPointWgs84MP - .getGeometry()).getPointCount()); - assertTrue(multiPoint1.getPoint(0).getX() == ((MultiPoint) mPointWgs84MP - .getGeometry()).getPoint(0).getX()); - assertTrue(multiPoint1.getPoint(0).getY() == ((MultiPoint) mPointWgs84MP - .getGeometry()).getPoint(0).getY()); - int lastIndex = multiPoint1.getPointCount() - 1; - assertTrue(multiPoint1.getPoint(lastIndex).getX() == ((MultiPoint) mPointWgs84MP - .getGeometry()).getPoint(lastIndex).getX()); - assertTrue(multiPoint1.getPoint(lastIndex).getY() == ((MultiPoint) mPointWgs84MP - .getGeometry()).getPoint(lastIndex).getY()); - - assertTrue(spatialReferenceWGS84.getID() == mPointWgs84MP - .getSpatialReference().getID()); - if (!checkResultSpatialRef(mPointWgs84MP, 4326, 0)) { - bAnswer = false; - } - - // FIXME - // MultiPoint mPointEmpty = new MultiPoint(); - // String mPointEmptyString = - // GeometryEngine.geometryToJson(spatialReferenceWGS84, - // mPointEmpty); - // mPointWgs84Parser = factory.createJsonParser(mPointEmptyString); - } - - { - MultiPoint p = new MultiPoint(); - p.addAttribute(VertexDescription.Semantics.Z); - p.addAttribute(VertexDescription.Semantics.M); - String s = GeometryEngine.geometryToJson(spatialReferenceWebMerc1, - p); - assertTrue(s - .equals("{\"hasZ\":true,\"hasM\":true,\"points\":[],\"spatialReference\":{\"wkid\":102100,\"latestWkid\":3857}}")); - - p.add(10.0, 20.0, 30.0); - p.add(20.0, 40.0, 60.0); - s = GeometryEngine.geometryToJson(spatialReferenceWebMerc1, p); - assertTrue(s - .equals("{\"hasZ\":true,\"hasM\":true,\"points\":[[10.0,20.0,30.0,null],[20.0,40.0,60.0,null]],\"spatialReference\":{\"wkid\":102100,\"latestWkid\":3857}}")); - } - { - String points = "{\"hasM\" : false, \"hasZ\" : true, \"uncle remus\" : null, \"points\" : [ [0,0,1], [0.0,10.0,1], [10.0,10.0,1], [10.0,0.0,1, 6666] ],\"spatialReference\" : {\"wkid\" : 4326}}"; - MapGeometry mp = GeometryEngine.jsonToGeometry(factory - .createJsonParser(points)); - MultiPoint multipoint = (MultiPoint) mp.getGeometry(); - assertTrue(multipoint.getPointCount() == 4); - Point2D point2d; - point2d = multipoint.getXY(0); - assertTrue(point2d.x == 0.0 && point2d.y == 0.0); - point2d = multipoint.getXY(1); - assertTrue(point2d.x == 0.0 && point2d.y == 10.0); - point2d = multipoint.getXY(2); - assertTrue(point2d.x == 10.0 && point2d.y == 10.0); - point2d = multipoint.getXY(3); - assertTrue(point2d.x == 10.0 && point2d.y == 0.0); - assertTrue(multipoint.hasAttribute(VertexDescription.Semantics.Z)); - assertTrue(!multipoint.hasAttribute(VertexDescription.Semantics.M)); - double z = multipoint.getAttributeAsDbl( - VertexDescription.Semantics.Z, 0, 0); - assertTrue(z == 1); - SpatialReference spatial_reference = mp.getSpatialReference(); - assertTrue(spatial_reference.getID() == 4326); - } - - return bAnswer; - } - - boolean testPolyline() throws JsonParseException, IOException { - boolean bAnswer = true; - - Polyline polyline = new Polyline(); - polyline.startPath(-97.06138, 32.837); - polyline.lineTo(-97.06133, 32.836); - polyline.lineTo(-97.06124, 32.834); - polyline.lineTo(-97.06127, 32.832); - - polyline.startPath(-97.06326, 32.759); - polyline.lineTo(-97.06298, 32.755); - - { - JsonParser polylinePathsWgs84Parser = factory - .createJsonParser(GeometryEngine.geometryToJson( - spatialReferenceWGS84, polyline)); - MapGeometry mPolylineWGS84MP = GeometryEngine - .jsonToGeometry(polylinePathsWgs84Parser); - - assertTrue(polyline.getPointCount() == ((Polyline) mPolylineWGS84MP - .getGeometry()).getPointCount()); - assertTrue(polyline.getPoint(0).getX() == ((Polyline) mPolylineWGS84MP - .getGeometry()).getPoint(0).getX()); - assertTrue(polyline.getPoint(0).getY() == ((Polyline) mPolylineWGS84MP - .getGeometry()).getPoint(0).getY()); - - assertTrue(polyline.getPathCount() == ((Polyline) mPolylineWGS84MP - .getGeometry()).getPathCount()); - assertTrue(polyline.getSegmentCount() == ((Polyline) mPolylineWGS84MP - .getGeometry()).getSegmentCount()); - assertTrue(polyline.getSegmentCount(0) == ((Polyline) mPolylineWGS84MP - .getGeometry()).getSegmentCount(0)); - assertTrue(polyline.getSegmentCount(1) == ((Polyline) mPolylineWGS84MP - .getGeometry()).getSegmentCount(1)); - - int lastIndex = polyline.getPointCount() - 1; - assertTrue(polyline.getPoint(lastIndex).getX() == ((Polyline) mPolylineWGS84MP - .getGeometry()).getPoint(lastIndex).getX()); - assertTrue(polyline.getPoint(lastIndex).getY() == ((Polyline) mPolylineWGS84MP - .getGeometry()).getPoint(lastIndex).getY()); - - assertTrue(spatialReferenceWGS84.getID() == mPolylineWGS84MP - .getSpatialReference().getID()); - - if (!checkResultSpatialRef(mPolylineWGS84MP, 4326, 0)) { - bAnswer = false; - } - } - - { - Polyline p = new Polyline(); - p.addAttribute(VertexDescription.Semantics.Z); - p.addAttribute(VertexDescription.Semantics.M); - String s = GeometryEngine.geometryToJson(spatialReferenceWebMerc1, - p); - assertTrue(s - .equals("{\"hasZ\":true,\"hasM\":true,\"paths\":[],\"spatialReference\":{\"wkid\":102100,\"latestWkid\":3857}}")); - - p.startPath(0, 0); - p.lineTo(0, 1); - p.startPath(2, 2); - p.lineTo(3, 3); - - p.setAttribute(VertexDescription.Semantics.Z, 0, 0, 3); - p.setAttribute(VertexDescription.Semantics.M, 1, 0, 5); - s = GeometryEngine.geometryToJson(spatialReferenceWebMerc1, p); - assertTrue(s - .equals("{\"hasZ\":true,\"hasM\":true,\"paths\":[[[0.0,0.0,3.0,null],[0.0,1.0,0.0,5.0]],[[2.0,2.0,0.0,null],[3.0,3.0,0.0,null]]],\"spatialReference\":{\"wkid\":102100,\"latestWkid\":3857}}")); - } - - { - String paths = "{\"hasZ\" : true, \"paths\" : [ [ [0.0, 0.0,3], [0, 10.0,3], [10.0, 10.0,3, 6666], [10.0, 0.0,3, 6666] ], [ [1.0, 1,3], [1.0, 9.0,3], [9.0, 9.0,3], [1.0, 9.0,3] ] ], \"spatialReference\" : {\"wkid\" : 4326}, \"hasM\" : false}"; - MapGeometry mapGeometry = GeometryEngine.jsonToGeometry(factory - .createJsonParser(paths)); - Polyline p = (Polyline) mapGeometry.getGeometry(); - assertTrue(p.getPathCount() == 2); - @SuppressWarnings("unused") - int count = p.getPathCount(); - assertTrue(p.getPointCount() == 8); - assertTrue(p.hasAttribute(VertexDescription.Semantics.Z)); - assertTrue(!p.hasAttribute(VertexDescription.Semantics.M)); - double z = p.getAttributeAsDbl(VertexDescription.Semantics.Z, 0, 0); - assertTrue(z == 3); - double length = p.calculateLength2D(); - assertTrue(Math.abs(length - 54.0) <= 0.001); - SpatialReference spatial_reference = mapGeometry - .getSpatialReference(); - assertTrue(spatial_reference.getID() == 4326); - } - - return bAnswer; - } - - boolean testPolygon() throws JsonParseException, IOException { - boolean bAnswer = true; - - Polygon polygon = new Polygon(); - polygon.startPath(-97.06138, 32.837); - polygon.lineTo(-97.06133, 32.836); - polygon.lineTo(-97.06124, 32.834); - polygon.lineTo(-97.06127, 32.832); - - polygon.startPath(-97.06326, 32.759); - polygon.lineTo(-97.06298, 32.755); - - { - JsonParser polygonPathsWgs84Parser = factory - .createJsonParser(GeometryEngine.geometryToJson( - spatialReferenceWGS84, polygon)); - MapGeometry mPolygonWGS84MP = GeometryEngine - .jsonToGeometry(polygonPathsWgs84Parser); - - assertTrue(polygon.getPointCount() + 1 == ((Polygon) mPolygonWGS84MP - .getGeometry()).getPointCount()); - assertTrue(polygon.getPoint(0).getX() == ((Polygon) mPolygonWGS84MP - .getGeometry()).getPoint(0).getX()); - assertTrue(polygon.getPoint(0).getY() == ((Polygon) mPolygonWGS84MP - .getGeometry()).getPoint(0).getY()); - - assertTrue(polygon.getPathCount() == ((Polygon) mPolygonWGS84MP - .getGeometry()).getPathCount()); - assertTrue(polygon.getSegmentCount() + 1 == ((Polygon) mPolygonWGS84MP - .getGeometry()).getSegmentCount()); - assertTrue(polygon.getSegmentCount(0) == ((Polygon) mPolygonWGS84MP - .getGeometry()).getSegmentCount(0)); - assertTrue(polygon.getSegmentCount(1) + 1 == ((Polygon) mPolygonWGS84MP - .getGeometry()).getSegmentCount(1)); - - int lastIndex = polygon.getPointCount() - 1; - assertTrue(polygon.getPoint(lastIndex).getX() == ((Polygon) mPolygonWGS84MP - .getGeometry()).getPoint(lastIndex).getX()); - assertTrue(polygon.getPoint(lastIndex).getY() == ((Polygon) mPolygonWGS84MP - .getGeometry()).getPoint(lastIndex).getY()); - - assertTrue(spatialReferenceWGS84.getID() == mPolygonWGS84MP - .getSpatialReference().getID()); - - if (!checkResultSpatialRef(mPolygonWGS84MP, 4326, 0)) { - bAnswer = false; - } - } - - { - Polygon p = new Polygon(); - p.addAttribute(VertexDescription.Semantics.Z); - p.addAttribute(VertexDescription.Semantics.M); - String s = GeometryEngine.geometryToJson(spatialReferenceWebMerc1, - p); - assertTrue(s - .equals("{\"hasZ\":true,\"hasM\":true,\"rings\":[],\"spatialReference\":{\"wkid\":102100,\"latestWkid\":3857}}")); - - p.startPath(0, 0); - p.lineTo(0, 1); - p.lineTo(4, 4); - p.startPath(2, 2); - p.lineTo(3, 3); - p.lineTo(7, 8); - - p.setAttribute(VertexDescription.Semantics.Z, 0, 0, 3); - p.setAttribute(VertexDescription.Semantics.M, 1, 0, 7); - p.setAttribute(VertexDescription.Semantics.M, 2, 0, 5); - p.setAttribute(VertexDescription.Semantics.M, 5, 0, 5); - s = GeometryEngine.geometryToJson(spatialReferenceWebMerc1, p); - assertTrue(s - .equals("{\"hasZ\":true,\"hasM\":true,\"rings\":[[[0.0,0.0,3.0,null],[0.0,1.0,0.0,7.0],[4.0,4.0,0.0,5.0],[0.0,0.0,3.0,null]],[[2.0,2.0,0.0,null],[3.0,3.0,0.0,null],[7.0,8.0,0.0,5.0],[2.0,2.0,0.0,null]]],\"spatialReference\":{\"wkid\":102100,\"latestWkid\":3857}}")); - } - - { - // Test Import Polygon from Polygon - String rings = "{\"hasZ\": true, \"rings\" : [ [ [0,0, 5], [0.0, 10.0, 5], [10.0,10.0, 5, 66666], [10.0,0.0, 5] ], [ [12, 12] ], [ [13 , 17], [13 , 17] ], [ [1.0, 1.0, 5, 66666], [9.0,1.0, 5], [9.0,9.0, 5], [1.0,9.0, 5], [1.0, 1.0, 5] ] ] }"; - MapGeometry mapGeometry = GeometryEngine.jsonToGeometry(factory - .createJsonParser(rings)); - Polygon p = (Polygon) mapGeometry.getGeometry(); - @SuppressWarnings("unused") - double area = p.calculateArea2D(); - @SuppressWarnings("unused") - double length = p.calculateLength2D(); - assertTrue(p.getPathCount() == 4); - int count = p.getPointCount(); - assertTrue(count == 15); - assertTrue(p.hasAttribute(VertexDescription.Semantics.Z)); - assertTrue(!p.hasAttribute(VertexDescription.Semantics.M)); - } - - return bAnswer; - } - - boolean testEnvelope() throws JsonParseException, IOException { - boolean bAnswer = true; - - Envelope envelope = new Envelope(); - envelope.setCoords(-109.55, 25.76, -86.39, 49.94); - - { - JsonParser envelopeWGS84Parser = factory - .createJsonParser(GeometryEngine.geometryToJson( - spatialReferenceWGS84, envelope)); - MapGeometry envelopeWGS84MP = GeometryEngine - .jsonToGeometry(envelopeWGS84Parser); - assertTrue(envelope.isEmpty() == envelopeWGS84MP.getGeometry() - .isEmpty()); - assertTrue(envelope.getXMax() == ((Envelope) envelopeWGS84MP - .getGeometry()).getXMax()); - assertTrue(envelope.getYMax() == ((Envelope) envelopeWGS84MP - .getGeometry()).getYMax()); - assertTrue(envelope.getXMin() == ((Envelope) envelopeWGS84MP - .getGeometry()).getXMin()); - assertTrue(envelope.getYMin() == ((Envelope) envelopeWGS84MP - .getGeometry()).getYMin()); - assertTrue(spatialReferenceWGS84.getID() == envelopeWGS84MP - .getSpatialReference().getID()); - if (!checkResultSpatialRef(envelopeWGS84MP, 4326, 0)) { - bAnswer = false; - } - } - - {// export - Envelope e = new Envelope(); - e.addAttribute(VertexDescription.Semantics.Z); - e.addAttribute(VertexDescription.Semantics.M); - String s = GeometryEngine.geometryToJson(spatialReferenceWebMerc1, - e); - assertTrue(s - .equals("{\"xmin\":null,\"ymin\":null,\"xmax\":null,\"ymax\":null,\"zmin\":null,\"zmax\":null,\"mmin\":null,\"mmax\":null,\"spatialReference\":{\"wkid\":102100,\"latestWkid\":3857}}")); - - e.setCoords(0, 1, 2, 3); - - Envelope1D z = new Envelope1D(); - Envelope1D m = new Envelope1D(); - z.setCoords(5, 7); - m.setCoords(11, 13); - - e.setInterval(VertexDescription.Semantics.Z, 0, z); - e.setInterval(VertexDescription.Semantics.M, 0, m); - s = GeometryEngine.geometryToJson(spatialReferenceWebMerc1, e); - assertTrue(s - .equals("{\"xmin\":0.0,\"ymin\":1.0,\"xmax\":2.0,\"ymax\":3.0,\"zmin\":5.0,\"zmax\":7.0,\"mmin\":11.0,\"mmax\":13.0,\"spatialReference\":{\"wkid\":102100,\"latestWkid\":3857}}")); - } - - {// import - String s = "{\"xmin\":0.0,\"ymin\":1.0,\"xmax\":2.0,\"ymax\":3.0,\"zmin\":5.0,\"zmax\":7.0,\"mmin\":11.0,\"mmax\":13.0,\"spatialReference\":{\"wkid\":102100,\"latestWkid\":3857}}"; - JsonParser parser = factory.createJsonParser(s); - MapGeometry map_env = GeometryEngine.jsonToGeometry(parser); - Envelope env = (Envelope) map_env.getGeometry(); - Envelope1D z = env.queryInterval(VertexDescription.Semantics.Z, 0); - Envelope1D m = env.queryInterval(VertexDescription.Semantics.M, 0); - assertTrue(z.vmin == 5.0); - assertTrue(z.vmax == 7.0); - assertTrue(m.vmin == 11.0); - assertTrue(m.vmax == 13.0); - } - - { - String s = "{ \"zmin\" : 33, \"xmin\" : -109.55, \"zmax\" : 53, \"ymin\" : 25.76, \"xmax\" : -86.39, \"ymax\" : 49.94, \"mmax\" : 13}"; - JsonParser parser = factory.createJsonParser(s); - MapGeometry map_env = GeometryEngine.jsonToGeometry(parser); - Envelope env = (Envelope) map_env.getGeometry(); - Envelope2D e = new Envelope2D(); - env.queryEnvelope2D(e); - assertTrue(e.xmin == -109.55 && e.ymin == 25.76 && e.xmax == -86.39 - && e.ymax == 49.94); - - Envelope1D e1D; - assertTrue(env.hasAttribute(VertexDescription.Semantics.Z)); - e1D = env.queryInterval(VertexDescription.Semantics.Z, 0); - assertTrue(e1D.vmin == 33 && e1D.vmax == 53); - - assertTrue(!env.hasAttribute(VertexDescription.Semantics.M)); - } - - return bAnswer; - } - - boolean testCR181369() throws JsonParseException, IOException { - // CR181369 - boolean bAnswer = true; - - String jsonStringPointAndWKT = "{\"x\":10.0,\"y\":20.0,\"spatialReference\":{\"wkt\" : \"PROJCS[\\\"NAD83_UTM_zone_15N\\\",GEOGCS[\\\"GCS_North_American_1983\\\",DATUM[\\\"D_North_American_1983\\\",SPHEROID[\\\"GRS_1980\\\",6378137.0,298.257222101]],PRIMEM[\\\"Greenwich\\\",0.0],UNIT[\\\"Degree\\\",0.0174532925199433]],PROJECTION[\\\"Transverse_Mercator\\\"],PARAMETER[\\\"false_easting\\\",500000.0],PARAMETER[\\\"false_northing\\\",0.0],PARAMETER[\\\"central_meridian\\\",-93.0],PARAMETER[\\\"scale_factor\\\",0.9996],PARAMETER[\\\"latitude_of_origin\\\",0.0],UNIT[\\\"Meter\\\",1.0]]\"} }"; - JsonParser jsonParserPointAndWKT = factory - .createJsonParser(jsonStringPointAndWKT); - MapGeometry mapGeom2 = GeometryEngine - .jsonToGeometry(jsonParserPointAndWKT); - String jsonStringPointAndWKT2 = GeometryEngine.geometryToJson( - mapGeom2.getSpatialReference(), mapGeom2.getGeometry()); - JsonParser jsonParserPointAndWKT2 = factory - .createJsonParser(jsonStringPointAndWKT2); - MapGeometry mapGeom3 = GeometryEngine - .jsonToGeometry(jsonParserPointAndWKT2); - assertTrue(((Point) mapGeom2.getGeometry()).getX() == ((Point) mapGeom3 - .getGeometry()).getX()); - assertTrue(((Point) mapGeom2.getGeometry()).getY() == ((Point) mapGeom3 - .getGeometry()).getY()); - - String s1 = mapGeom2.getSpatialReference().getText(); - String s2 = mapGeom3.getSpatialReference().getText(); - assertTrue(s1.equals(s2)); - - int id2 = mapGeom2.getSpatialReference().getID(); - int id3 = mapGeom3.getSpatialReference().getID(); - assertTrue(id2 == id3); - if (!checkResultSpatialRef(mapGeom3, mapGeom2.getSpatialReference() - .getID(), 0)) { - bAnswer = false; - } - return bAnswer; - } - - boolean checkResultSpatialRef(MapGeometry mapGeometry, int expectWki1, - int expectWki2) { - SpatialReference sr = mapGeometry.getSpatialReference(); - String Wkt = sr.getText(); - int wki1 = sr.getLatestID(); - if (!(wki1 == expectWki1 || wki1 == expectWki2)) - return false; - if (!(Wkt != null && Wkt.length() > 0)) - return false; - System.out.println("WKT1: " + Wkt); - SpatialReference sr2 = SpatialReference.create(Wkt); - int wki2 = sr2.getID(); - if (expectWki2 > 0) { - if (!(wki2 == expectWki1 || wki2 == expectWki2)) - return false; - } else { - if (!(wki2 == expectWki1)) - return false; - } - return true; - } -} +package com.esri.core.geometry; + +import java.io.IOException; +import org.codehaus.jackson.JsonFactory; +import org.codehaus.jackson.JsonParseException; +import org.codehaus.jackson.JsonParser; +import junit.framework.TestCase; +import org.junit.Test; + +public class TestGeomToJSonExportSRFromWkiOrWkt_CR181369 extends TestCase { + @Override + protected void setUp() throws Exception { + super.setUp(); + } + + @Override + protected void tearDown() throws Exception { + super.tearDown(); + } + + JsonFactory factory = new JsonFactory(); + SpatialReference spatialReferenceWebMerc1 = SpatialReference.create(102100); + SpatialReference spatialReferenceWebMerc2 = SpatialReference + .create(spatialReferenceWebMerc1.getLatestID()); + SpatialReference spatialReferenceWGS84 = SpatialReference.create(4326); + + @Test + public void testGeomToJSonExportSRFromWkiOrWkt_CR181369() + throws JsonParseException, IOException { + testPoint(); + testPolyline(); + testPolygon(); + testEnvelope(); + testMultiPoint(); + testCR181369(); + // These tests return the result of a method called + // checkResultSpatialRef. + // However, the tests pass or fail regardless of what that method + // returns. + } + + boolean testPoint() throws JsonParseException, IOException { + boolean bAnswer = true; + Point point1 = new Point(10.0, 20.0); + Point pointEmpty = new Point(); + { + JsonParser pointWebMerc1Parser = factory + .createJsonParser(GeometryEngine.geometryToJson( + spatialReferenceWebMerc1, point1)); + MapGeometry pointWebMerc1MP = GeometryEngine + .jsonToGeometry(pointWebMerc1Parser); + assertTrue(point1.getX() == ((Point) pointWebMerc1MP.getGeometry()) + .getX()); + assertTrue(point1.getY() == ((Point) pointWebMerc1MP.getGeometry()) + .getY()); + assertTrue(spatialReferenceWebMerc1.getID() == pointWebMerc1MP + .getSpatialReference().getID() + || pointWebMerc1MP.getSpatialReference().getID() == 3857); + + if (!checkResultSpatialRef(pointWebMerc1MP, 102100, 3857)) { + bAnswer = false; + } + + pointWebMerc1Parser = factory.createJsonParser(GeometryEngine + .geometryToJson(null, point1)); + pointWebMerc1MP = GeometryEngine + .jsonToGeometry(pointWebMerc1Parser); + assertTrue(null == pointWebMerc1MP.getSpatialReference()); + + if (pointWebMerc1MP.getSpatialReference() != null) { + if (!checkResultSpatialRef(pointWebMerc1MP, 102100, 3857)) { + bAnswer = false; + } + } + + String pointEmptyString = GeometryEngine.geometryToJson( + spatialReferenceWebMerc1, pointEmpty); + pointWebMerc1Parser = factory.createJsonParser(pointEmptyString); + // FIXME + // pointWebMerc1MP = + // GeometryEngine.jsonToGeometry(pointWebMerc1Parser); + // assertTrue(pointWebMerc1MP.getGeometry().isEmpty()); + // assertTrue(spatialReferenceWebMerc1.getID() == + // pointWebMerc1MP.getSpatialReference().getID()); + } + + JsonParser pointWebMerc2Parser = factory + .createJsonParser(GeometryEngine.geometryToJson( + spatialReferenceWebMerc2, point1)); + MapGeometry pointWebMerc2MP = GeometryEngine + .jsonToGeometry(pointWebMerc2Parser); + assertTrue(point1.getX() == ((Point) pointWebMerc2MP.getGeometry()) + .getX()); + assertTrue(point1.getY() == ((Point) pointWebMerc2MP.getGeometry()) + .getY()); + assertTrue(spatialReferenceWebMerc2.getLatestID() == pointWebMerc2MP + .getSpatialReference().getLatestID()); + if (!checkResultSpatialRef(pointWebMerc2MP, + spatialReferenceWebMerc2.getLatestID(), 0)) { + bAnswer = false; + } + + { + JsonParser pointWgs84Parser = factory + .createJsonParser(GeometryEngine.geometryToJson( + spatialReferenceWGS84, point1)); + MapGeometry pointWgs84MP = GeometryEngine + .jsonToGeometry(pointWgs84Parser); + assertTrue(point1.getX() == ((Point) pointWgs84MP.getGeometry()) + .getX()); + assertTrue(point1.getY() == ((Point) pointWgs84MP.getGeometry()) + .getY()); + assertTrue(spatialReferenceWGS84.getID() == pointWgs84MP + .getSpatialReference().getID()); + if (!checkResultSpatialRef(pointWgs84MP, 4326, 0)) { + bAnswer = false; + } + } + + { + Point p = new Point(); + String s = GeometryEngine.geometryToJson(spatialReferenceWebMerc1, + p); + assertTrue(s + .equals("{\"x\":null,\"y\":null,\"spatialReference\":{\"wkid\":102100,\"latestWkid\":3857}}")); + + p.addAttribute(VertexDescription.Semantics.Z); + p.addAttribute(VertexDescription.Semantics.M); + s = GeometryEngine.geometryToJson(spatialReferenceWebMerc1, p); + assertTrue(s + .equals("{\"x\":null,\"y\":null,\"z\":null,\"m\":null,\"spatialReference\":{\"wkid\":102100,\"latestWkid\":3857}}")); + + } + + { + Point p = new Point(10.0, 20.0, 30.0); + p.addAttribute(VertexDescription.Semantics.M); + String s = GeometryEngine.geometryToJson(spatialReferenceWebMerc1, + p); + assertTrue(s + .equals("{\"x\":10.0,\"y\":20.0,\"z\":30.0,\"m\":null,\"spatialReference\":{\"wkid\":102100,\"latestWkid\":3857}}")); + } + + {// import + String s = "{\"x\":0.0,\"y\":1.0,\"z\":5.0,\"m\":11.0,\"spatialReference\":{\"wkid\":102100,\"latestWkid\":3857}}"; + JsonParser parser = factory.createJsonParser(s); + MapGeometry map_pt = GeometryEngine.jsonToGeometry(parser); + Point pt = (Point) map_pt.getGeometry(); + assertTrue(pt.getX() == 0.0); + assertTrue(pt.getY() == 1.0); + assertTrue(pt.getZ() == 5.0); + assertTrue(pt.getM() == 11.0); + } + + { + String s = "{\"x\" : 5.0, \"y\" : null, \"spatialReference\" : {\"wkid\" : 4326}} "; + JsonParser parser = factory.createJsonParser(s); + MapGeometry map_pt = GeometryEngine.jsonToGeometry(parser); + Point pt = (Point) map_pt.getGeometry(); + assertTrue(pt.isEmpty()); + SpatialReference spatial_reference = map_pt.getSpatialReference(); + assertTrue(spatial_reference.getID() == 4326); + } + + return bAnswer; + } + + boolean testMultiPoint() throws JsonParseException, IOException { + boolean bAnswer = true; + + MultiPoint multiPoint1 = new MultiPoint(); + multiPoint1.add(-97.06138, 32.837); + multiPoint1.add(-97.06133, 32.836); + multiPoint1.add(-97.06124, 32.834); + multiPoint1.add(-97.06127, 32.832); + + { + String s = GeometryEngine.geometryToJson(spatialReferenceWGS84, + multiPoint1); + JsonParser mPointWgs84Parser = factory.createJsonParser(s); + MapGeometry mPointWgs84MP = GeometryEngine + .jsonToGeometry(mPointWgs84Parser); + assertTrue(multiPoint1.getPointCount() == ((MultiPoint) mPointWgs84MP + .getGeometry()).getPointCount()); + assertTrue(multiPoint1.getPoint(0).getX() == ((MultiPoint) mPointWgs84MP + .getGeometry()).getPoint(0).getX()); + assertTrue(multiPoint1.getPoint(0).getY() == ((MultiPoint) mPointWgs84MP + .getGeometry()).getPoint(0).getY()); + int lastIndex = multiPoint1.getPointCount() - 1; + assertTrue(multiPoint1.getPoint(lastIndex).getX() == ((MultiPoint) mPointWgs84MP + .getGeometry()).getPoint(lastIndex).getX()); + assertTrue(multiPoint1.getPoint(lastIndex).getY() == ((MultiPoint) mPointWgs84MP + .getGeometry()).getPoint(lastIndex).getY()); + + assertTrue(spatialReferenceWGS84.getID() == mPointWgs84MP + .getSpatialReference().getID()); + if (!checkResultSpatialRef(mPointWgs84MP, 4326, 0)) { + bAnswer = false; + } + + // FIXME + // MultiPoint mPointEmpty = new MultiPoint(); + // String mPointEmptyString = + // GeometryEngine.geometryToJson(spatialReferenceWGS84, + // mPointEmpty); + // mPointWgs84Parser = factory.createJsonParser(mPointEmptyString); + } + + { + MultiPoint p = new MultiPoint(); + p.addAttribute(VertexDescription.Semantics.Z); + p.addAttribute(VertexDescription.Semantics.M); + String s = GeometryEngine.geometryToJson(spatialReferenceWebMerc1, + p); + assertTrue(s + .equals("{\"hasZ\":true,\"hasM\":true,\"points\":[],\"spatialReference\":{\"wkid\":102100,\"latestWkid\":3857}}")); + + p.add(10.0, 20.0, 30.0); + p.add(20.0, 40.0, 60.0); + s = GeometryEngine.geometryToJson(spatialReferenceWebMerc1, p); + assertTrue(s + .equals("{\"hasZ\":true,\"hasM\":true,\"points\":[[10.0,20.0,30.0,null],[20.0,40.0,60.0,null]],\"spatialReference\":{\"wkid\":102100,\"latestWkid\":3857}}")); + } + { + String points = "{\"hasM\" : false, \"hasZ\" : true, \"uncle remus\" : null, \"points\" : [ [0,0,1], [0.0,10.0,1], [10.0,10.0,1], [10.0,0.0,1, 6666] ],\"spatialReference\" : {\"wkid\" : 4326}}"; + MapGeometry mp = GeometryEngine.jsonToGeometry(factory + .createJsonParser(points)); + MultiPoint multipoint = (MultiPoint) mp.getGeometry(); + assertTrue(multipoint.getPointCount() == 4); + Point2D point2d; + point2d = multipoint.getXY(0); + assertTrue(point2d.x == 0.0 && point2d.y == 0.0); + point2d = multipoint.getXY(1); + assertTrue(point2d.x == 0.0 && point2d.y == 10.0); + point2d = multipoint.getXY(2); + assertTrue(point2d.x == 10.0 && point2d.y == 10.0); + point2d = multipoint.getXY(3); + assertTrue(point2d.x == 10.0 && point2d.y == 0.0); + assertTrue(multipoint.hasAttribute(VertexDescription.Semantics.Z)); + assertTrue(!multipoint.hasAttribute(VertexDescription.Semantics.M)); + double z = multipoint.getAttributeAsDbl( + VertexDescription.Semantics.Z, 0, 0); + assertTrue(z == 1); + SpatialReference spatial_reference = mp.getSpatialReference(); + assertTrue(spatial_reference.getID() == 4326); + } + + return bAnswer; + } + + boolean testPolyline() throws JsonParseException, IOException { + boolean bAnswer = true; + + Polyline polyline = new Polyline(); + polyline.startPath(-97.06138, 32.837); + polyline.lineTo(-97.06133, 32.836); + polyline.lineTo(-97.06124, 32.834); + polyline.lineTo(-97.06127, 32.832); + + polyline.startPath(-97.06326, 32.759); + polyline.lineTo(-97.06298, 32.755); + + { + JsonParser polylinePathsWgs84Parser = factory + .createJsonParser(GeometryEngine.geometryToJson( + spatialReferenceWGS84, polyline)); + MapGeometry mPolylineWGS84MP = GeometryEngine + .jsonToGeometry(polylinePathsWgs84Parser); + + assertTrue(polyline.getPointCount() == ((Polyline) mPolylineWGS84MP + .getGeometry()).getPointCount()); + assertTrue(polyline.getPoint(0).getX() == ((Polyline) mPolylineWGS84MP + .getGeometry()).getPoint(0).getX()); + assertTrue(polyline.getPoint(0).getY() == ((Polyline) mPolylineWGS84MP + .getGeometry()).getPoint(0).getY()); + + assertTrue(polyline.getPathCount() == ((Polyline) mPolylineWGS84MP + .getGeometry()).getPathCount()); + assertTrue(polyline.getSegmentCount() == ((Polyline) mPolylineWGS84MP + .getGeometry()).getSegmentCount()); + assertTrue(polyline.getSegmentCount(0) == ((Polyline) mPolylineWGS84MP + .getGeometry()).getSegmentCount(0)); + assertTrue(polyline.getSegmentCount(1) == ((Polyline) mPolylineWGS84MP + .getGeometry()).getSegmentCount(1)); + + int lastIndex = polyline.getPointCount() - 1; + assertTrue(polyline.getPoint(lastIndex).getX() == ((Polyline) mPolylineWGS84MP + .getGeometry()).getPoint(lastIndex).getX()); + assertTrue(polyline.getPoint(lastIndex).getY() == ((Polyline) mPolylineWGS84MP + .getGeometry()).getPoint(lastIndex).getY()); + + assertTrue(spatialReferenceWGS84.getID() == mPolylineWGS84MP + .getSpatialReference().getID()); + + if (!checkResultSpatialRef(mPolylineWGS84MP, 4326, 0)) { + bAnswer = false; + } + } + + { + Polyline p = new Polyline(); + p.addAttribute(VertexDescription.Semantics.Z); + p.addAttribute(VertexDescription.Semantics.M); + String s = GeometryEngine.geometryToJson(spatialReferenceWebMerc1, + p); + assertTrue(s + .equals("{\"hasZ\":true,\"hasM\":true,\"paths\":[],\"spatialReference\":{\"wkid\":102100,\"latestWkid\":3857}}")); + + p.startPath(0, 0); + p.lineTo(0, 1); + p.startPath(2, 2); + p.lineTo(3, 3); + + p.setAttribute(VertexDescription.Semantics.Z, 0, 0, 3); + p.setAttribute(VertexDescription.Semantics.M, 1, 0, 5); + s = GeometryEngine.geometryToJson(spatialReferenceWebMerc1, p); + assertTrue(s + .equals("{\"hasZ\":true,\"hasM\":true,\"paths\":[[[0.0,0.0,3.0,null],[0.0,1.0,0.0,5.0]],[[2.0,2.0,0.0,null],[3.0,3.0,0.0,null]]],\"spatialReference\":{\"wkid\":102100,\"latestWkid\":3857}}")); + } + + { + String paths = "{\"hasZ\" : true, \"paths\" : [ [ [0.0, 0.0,3], [0, 10.0,3], [10.0, 10.0,3, 6666], [10.0, 0.0,3, 6666] ], [ [1.0, 1,3], [1.0, 9.0,3], [9.0, 9.0,3], [1.0, 9.0,3] ] ], \"spatialReference\" : {\"wkid\" : 4326}, \"hasM\" : false}"; + MapGeometry mapGeometry = GeometryEngine.jsonToGeometry(factory + .createJsonParser(paths)); + Polyline p = (Polyline) mapGeometry.getGeometry(); + assertTrue(p.getPathCount() == 2); + @SuppressWarnings("unused") + int count = p.getPathCount(); + assertTrue(p.getPointCount() == 8); + assertTrue(p.hasAttribute(VertexDescription.Semantics.Z)); + assertTrue(!p.hasAttribute(VertexDescription.Semantics.M)); + double z = p.getAttributeAsDbl(VertexDescription.Semantics.Z, 0, 0); + assertTrue(z == 3); + double length = p.calculateLength2D(); + assertTrue(Math.abs(length - 54.0) <= 0.001); + SpatialReference spatial_reference = mapGeometry + .getSpatialReference(); + assertTrue(spatial_reference.getID() == 4326); + } + + return bAnswer; + } + + boolean testPolygon() throws JsonParseException, IOException { + boolean bAnswer = true; + + Polygon polygon = new Polygon(); + polygon.startPath(-97.06138, 32.837); + polygon.lineTo(-97.06133, 32.836); + polygon.lineTo(-97.06124, 32.834); + polygon.lineTo(-97.06127, 32.832); + + polygon.startPath(-97.06326, 32.759); + polygon.lineTo(-97.06298, 32.755); + + { + JsonParser polygonPathsWgs84Parser = factory + .createJsonParser(GeometryEngine.geometryToJson( + spatialReferenceWGS84, polygon)); + MapGeometry mPolygonWGS84MP = GeometryEngine + .jsonToGeometry(polygonPathsWgs84Parser); + + assertTrue(polygon.getPointCount() + 1 == ((Polygon) mPolygonWGS84MP + .getGeometry()).getPointCount()); + assertTrue(polygon.getPoint(0).getX() == ((Polygon) mPolygonWGS84MP + .getGeometry()).getPoint(0).getX()); + assertTrue(polygon.getPoint(0).getY() == ((Polygon) mPolygonWGS84MP + .getGeometry()).getPoint(0).getY()); + + assertTrue(polygon.getPathCount() == ((Polygon) mPolygonWGS84MP + .getGeometry()).getPathCount()); + assertTrue(polygon.getSegmentCount() + 1 == ((Polygon) mPolygonWGS84MP + .getGeometry()).getSegmentCount()); + assertTrue(polygon.getSegmentCount(0) == ((Polygon) mPolygonWGS84MP + .getGeometry()).getSegmentCount(0)); + assertTrue(polygon.getSegmentCount(1) + 1 == ((Polygon) mPolygonWGS84MP + .getGeometry()).getSegmentCount(1)); + + int lastIndex = polygon.getPointCount() - 1; + assertTrue(polygon.getPoint(lastIndex).getX() == ((Polygon) mPolygonWGS84MP + .getGeometry()).getPoint(lastIndex).getX()); + assertTrue(polygon.getPoint(lastIndex).getY() == ((Polygon) mPolygonWGS84MP + .getGeometry()).getPoint(lastIndex).getY()); + + assertTrue(spatialReferenceWGS84.getID() == mPolygonWGS84MP + .getSpatialReference().getID()); + + if (!checkResultSpatialRef(mPolygonWGS84MP, 4326, 0)) { + bAnswer = false; + } + } + + { + Polygon p = new Polygon(); + p.addAttribute(VertexDescription.Semantics.Z); + p.addAttribute(VertexDescription.Semantics.M); + String s = GeometryEngine.geometryToJson(spatialReferenceWebMerc1, + p); + assertTrue(s + .equals("{\"hasZ\":true,\"hasM\":true,\"rings\":[],\"spatialReference\":{\"wkid\":102100,\"latestWkid\":3857}}")); + + p.startPath(0, 0); + p.lineTo(0, 1); + p.lineTo(4, 4); + p.startPath(2, 2); + p.lineTo(3, 3); + p.lineTo(7, 8); + + p.setAttribute(VertexDescription.Semantics.Z, 0, 0, 3); + p.setAttribute(VertexDescription.Semantics.M, 1, 0, 7); + p.setAttribute(VertexDescription.Semantics.M, 2, 0, 5); + p.setAttribute(VertexDescription.Semantics.M, 5, 0, 5); + s = GeometryEngine.geometryToJson(spatialReferenceWebMerc1, p); + assertTrue(s + .equals("{\"hasZ\":true,\"hasM\":true,\"rings\":[[[0.0,0.0,3.0,null],[0.0,1.0,0.0,7.0],[4.0,4.0,0.0,5.0],[0.0,0.0,3.0,null]],[[2.0,2.0,0.0,null],[3.0,3.0,0.0,null],[7.0,8.0,0.0,5.0],[2.0,2.0,0.0,null]]],\"spatialReference\":{\"wkid\":102100,\"latestWkid\":3857}}")); + } + + { + // Test Import Polygon from Polygon + String rings = "{\"hasZ\": true, \"rings\" : [ [ [0,0, 5], [0.0, 10.0, 5], [10.0,10.0, 5, 66666], [10.0,0.0, 5] ], [ [12, 12] ], [ [13 , 17], [13 , 17] ], [ [1.0, 1.0, 5, 66666], [9.0,1.0, 5], [9.0,9.0, 5], [1.0,9.0, 5], [1.0, 1.0, 5] ] ] }"; + MapGeometry mapGeometry = GeometryEngine.jsonToGeometry(factory + .createJsonParser(rings)); + Polygon p = (Polygon) mapGeometry.getGeometry(); + @SuppressWarnings("unused") + double area = p.calculateArea2D(); + @SuppressWarnings("unused") + double length = p.calculateLength2D(); + assertTrue(p.getPathCount() == 4); + int count = p.getPointCount(); + assertTrue(count == 15); + assertTrue(p.hasAttribute(VertexDescription.Semantics.Z)); + assertTrue(!p.hasAttribute(VertexDescription.Semantics.M)); + } + + return bAnswer; + } + + boolean testEnvelope() throws JsonParseException, IOException { + boolean bAnswer = true; + + Envelope envelope = new Envelope(); + envelope.setCoords(-109.55, 25.76, -86.39, 49.94); + + { + JsonParser envelopeWGS84Parser = factory + .createJsonParser(GeometryEngine.geometryToJson( + spatialReferenceWGS84, envelope)); + MapGeometry envelopeWGS84MP = GeometryEngine + .jsonToGeometry(envelopeWGS84Parser); + assertTrue(envelope.isEmpty() == envelopeWGS84MP.getGeometry() + .isEmpty()); + assertTrue(envelope.getXMax() == ((Envelope) envelopeWGS84MP + .getGeometry()).getXMax()); + assertTrue(envelope.getYMax() == ((Envelope) envelopeWGS84MP + .getGeometry()).getYMax()); + assertTrue(envelope.getXMin() == ((Envelope) envelopeWGS84MP + .getGeometry()).getXMin()); + assertTrue(envelope.getYMin() == ((Envelope) envelopeWGS84MP + .getGeometry()).getYMin()); + assertTrue(spatialReferenceWGS84.getID() == envelopeWGS84MP + .getSpatialReference().getID()); + if (!checkResultSpatialRef(envelopeWGS84MP, 4326, 0)) { + bAnswer = false; + } + } + + {// export + Envelope e = new Envelope(); + e.addAttribute(VertexDescription.Semantics.Z); + e.addAttribute(VertexDescription.Semantics.M); + String s = GeometryEngine.geometryToJson(spatialReferenceWebMerc1, + e); + assertTrue(s + .equals("{\"xmin\":null,\"ymin\":null,\"xmax\":null,\"ymax\":null,\"zmin\":null,\"zmax\":null,\"mmin\":null,\"mmax\":null,\"spatialReference\":{\"wkid\":102100,\"latestWkid\":3857}}")); + + e.setCoords(0, 1, 2, 3); + + Envelope1D z = new Envelope1D(); + Envelope1D m = new Envelope1D(); + z.setCoords(5, 7); + m.setCoords(11, 13); + + e.setInterval(VertexDescription.Semantics.Z, 0, z); + e.setInterval(VertexDescription.Semantics.M, 0, m); + s = GeometryEngine.geometryToJson(spatialReferenceWebMerc1, e); + assertTrue(s + .equals("{\"xmin\":0.0,\"ymin\":1.0,\"xmax\":2.0,\"ymax\":3.0,\"zmin\":5.0,\"zmax\":7.0,\"mmin\":11.0,\"mmax\":13.0,\"spatialReference\":{\"wkid\":102100,\"latestWkid\":3857}}")); + } + + {// import + String s = "{\"xmin\":0.0,\"ymin\":1.0,\"xmax\":2.0,\"ymax\":3.0,\"zmin\":5.0,\"zmax\":7.0,\"mmin\":11.0,\"mmax\":13.0,\"spatialReference\":{\"wkid\":102100,\"latestWkid\":3857}}"; + JsonParser parser = factory.createJsonParser(s); + MapGeometry map_env = GeometryEngine.jsonToGeometry(parser); + Envelope env = (Envelope) map_env.getGeometry(); + Envelope1D z = env.queryInterval(VertexDescription.Semantics.Z, 0); + Envelope1D m = env.queryInterval(VertexDescription.Semantics.M, 0); + assertTrue(z.vmin == 5.0); + assertTrue(z.vmax == 7.0); + assertTrue(m.vmin == 11.0); + assertTrue(m.vmax == 13.0); + } + + { + String s = "{ \"zmin\" : 33, \"xmin\" : -109.55, \"zmax\" : 53, \"ymin\" : 25.76, \"xmax\" : -86.39, \"ymax\" : 49.94, \"mmax\" : 13}"; + JsonParser parser = factory.createJsonParser(s); + MapGeometry map_env = GeometryEngine.jsonToGeometry(parser); + Envelope env = (Envelope) map_env.getGeometry(); + Envelope2D e = new Envelope2D(); + env.queryEnvelope2D(e); + assertTrue(e.xmin == -109.55 && e.ymin == 25.76 && e.xmax == -86.39 + && e.ymax == 49.94); + + Envelope1D e1D; + assertTrue(env.hasAttribute(VertexDescription.Semantics.Z)); + e1D = env.queryInterval(VertexDescription.Semantics.Z, 0); + assertTrue(e1D.vmin == 33 && e1D.vmax == 53); + + assertTrue(!env.hasAttribute(VertexDescription.Semantics.M)); + } + + return bAnswer; + } + + boolean testCR181369() throws JsonParseException, IOException { + // CR181369 + boolean bAnswer = true; + + String jsonStringPointAndWKT = "{\"x\":10.0,\"y\":20.0,\"spatialReference\":{\"wkt\" : \"PROJCS[\\\"NAD83_UTM_zone_15N\\\",GEOGCS[\\\"GCS_North_American_1983\\\",DATUM[\\\"D_North_American_1983\\\",SPHEROID[\\\"GRS_1980\\\",6378137.0,298.257222101]],PRIMEM[\\\"Greenwich\\\",0.0],UNIT[\\\"Degree\\\",0.0174532925199433]],PROJECTION[\\\"Transverse_Mercator\\\"],PARAMETER[\\\"false_easting\\\",500000.0],PARAMETER[\\\"false_northing\\\",0.0],PARAMETER[\\\"central_meridian\\\",-93.0],PARAMETER[\\\"scale_factor\\\",0.9996],PARAMETER[\\\"latitude_of_origin\\\",0.0],UNIT[\\\"Meter\\\",1.0]]\"} }"; + JsonParser jsonParserPointAndWKT = factory + .createJsonParser(jsonStringPointAndWKT); + MapGeometry mapGeom2 = GeometryEngine + .jsonToGeometry(jsonParserPointAndWKT); + String jsonStringPointAndWKT2 = GeometryEngine.geometryToJson( + mapGeom2.getSpatialReference(), mapGeom2.getGeometry()); + JsonParser jsonParserPointAndWKT2 = factory + .createJsonParser(jsonStringPointAndWKT2); + MapGeometry mapGeom3 = GeometryEngine + .jsonToGeometry(jsonParserPointAndWKT2); + assertTrue(((Point) mapGeom2.getGeometry()).getX() == ((Point) mapGeom3 + .getGeometry()).getX()); + assertTrue(((Point) mapGeom2.getGeometry()).getY() == ((Point) mapGeom3 + .getGeometry()).getY()); + + String s1 = mapGeom2.getSpatialReference().getText(); + String s2 = mapGeom3.getSpatialReference().getText(); + assertTrue(s1.equals(s2)); + + int id2 = mapGeom2.getSpatialReference().getID(); + int id3 = mapGeom3.getSpatialReference().getID(); + assertTrue(id2 == id3); + if (!checkResultSpatialRef(mapGeom3, mapGeom2.getSpatialReference() + .getID(), 0)) { + bAnswer = false; + } + return bAnswer; + } + + boolean checkResultSpatialRef(MapGeometry mapGeometry, int expectWki1, + int expectWki2) { + SpatialReference sr = mapGeometry.getSpatialReference(); + String Wkt = sr.getText(); + int wki1 = sr.getLatestID(); + if (!(wki1 == expectWki1 || wki1 == expectWki2)) + return false; + if (!(Wkt != null && Wkt.length() > 0)) + return false; + System.out.println("WKT1: " + Wkt); + SpatialReference sr2 = SpatialReference.create(Wkt); + int wki2 = sr2.getID(); + if (expectWki2 > 0) { + if (!(wki2 == expectWki1 || wki2 == expectWki2)) + return false; + } else { + if (!(wki2 == expectWki1)) + return false; + } + return true; + } +} diff --git a/unittest/com/esri/core/geometry/TestJSonGeometry.java b/unittest/com/esri/core/geometry/TestJSonGeometry.java index d380a84b..571a103e 100644 --- a/unittest/com/esri/core/geometry/TestJSonGeometry.java +++ b/unittest/com/esri/core/geometry/TestJSonGeometry.java @@ -1,47 +1,47 @@ -package com.esri.core.geometry; - -import java.util.HashMap; -import java.util.Iterator; -import java.util.Map; -import java.util.Set; -import junit.framework.TestCase; -import org.junit.Test; - -public class TestJSonGeometry extends TestCase { - @Override - protected void setUp() throws Exception { - super.setUp(); - } - - @Override - protected void tearDown() throws Exception { - super.tearDown(); - } - - @Test - public void testGetSpatialReferenceFor4326() { - String completeStr = "GEOGCS[\"GCS_Sphere\",DATUM[\"D_Sphere\"," - + "SPHEROID[\"Sphere\",6371000.0,0.0]],PRIMEM[\"Greenwich\",0.0]," - + "UNIT[\"Degree\",0.0174532925199433]]"; - - // 4326 GCS_WGS_1984 - SpatialReference sr = SpatialReference.create(completeStr); - assertNotNull(sr); - } -} - -final class HashMapClassForTesting { - static Map SR_WKI_WKTs = new HashMap() { - /** - * added to get rid of warning - */ - private static final long serialVersionUID = 8630934425353750539L; - - { - put(4035, - "GEOGCS[\"GCS_Sphere\",DATUM[\"D_Sphere\"," - + "SPHEROID[\"Sphere\",6371000.0,0.0]],PRIMEM[\"Greenwich\",0.0]," - + "UNIT[\"Degree\",0.0174532925199433]]"); - } - }; -} +package com.esri.core.geometry; + +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import java.util.Set; +import junit.framework.TestCase; +import org.junit.Test; + +public class TestJSonGeometry extends TestCase { + @Override + protected void setUp() throws Exception { + super.setUp(); + } + + @Override + protected void tearDown() throws Exception { + super.tearDown(); + } + + @Test + public void testGetSpatialReferenceFor4326() { + String completeStr = "GEOGCS[\"GCS_Sphere\",DATUM[\"D_Sphere\"," + + "SPHEROID[\"Sphere\",6371000.0,0.0]],PRIMEM[\"Greenwich\",0.0]," + + "UNIT[\"Degree\",0.0174532925199433]]"; + + // 4326 GCS_WGS_1984 + SpatialReference sr = SpatialReference.create(completeStr); + assertNotNull(sr); + } + } + +final class HashMapClassForTesting { + static Map SR_WKI_WKTs = new HashMap() { + /** + * added to get rid of warning + */ + private static final long serialVersionUID = 8630934425353750539L; + + { + put(4035, + "GEOGCS[\"GCS_Sphere\",DATUM[\"D_Sphere\"," + + "SPHEROID[\"Sphere\",6371000.0,0.0]],PRIMEM[\"Greenwich\",0.0]," + + "UNIT[\"Degree\",0.0174532925199433]]"); + } + }; +} diff --git a/unittest/com/esri/core/geometry/TestJSonToGeomFromWkiOrWkt_CR177613.java b/unittest/com/esri/core/geometry/TestJSonToGeomFromWkiOrWkt_CR177613.java index a7307556..243f7c26 100644 --- a/unittest/com/esri/core/geometry/TestJSonToGeomFromWkiOrWkt_CR177613.java +++ b/unittest/com/esri/core/geometry/TestJSonToGeomFromWkiOrWkt_CR177613.java @@ -1,123 +1,123 @@ -package com.esri.core.geometry; - -import java.io.IOException; -import org.codehaus.jackson.JsonFactory; -import org.codehaus.jackson.JsonParseException; -import org.codehaus.jackson.JsonParser; -import org.codehaus.jackson.JsonToken; -import junit.framework.TestCase; -import org.junit.Test; - -public class TestJSonToGeomFromWkiOrWkt_CR177613 extends TestCase { - JsonFactory factory = new JsonFactory(); - - @Override - protected void setUp() throws Exception { - super.setUp(); - } - - @Override - protected void tearDown() throws Exception { - super.tearDown(); - } - - @Test - public void testPolygonWithEmptyWKT_NoWKI() throws JsonParseException, - IOException { - String jsonStringPg = "{ \"rings\" :[ [ [-97.06138,32.837], [-97.06133,32.836], " - + "[-97.06124,32.834], [-97.06127,32.832], [-97.06138,32.837] ], " - + "[ [-97.06326,32.759], [-97.06298,32.755], [-97.06153,32.749], [-97.06326,32.759] ]], " - + "\"spatialReference\" : {\"wkt\" : \"\"}}"; - JsonParser jsonParserPg = factory.createJsonParser(jsonStringPg); - jsonParserPg.nextToken(); - - MapGeometry mapGeom = GeometryEngine.jsonToGeometry(jsonParserPg); - Utils.showProjectedGeometryInfo(mapGeom); - SpatialReference sr = mapGeom.getSpatialReference(); - assertTrue(sr == null); - } - - @Test - public void testOnlyWKI() throws JsonParseException, IOException { - String jsonStringSR = "{\"wkid\" : 4326}"; - JsonParser jsonParserSR = factory.createJsonParser(jsonStringSR); - jsonParserSR.nextToken(); - - MapGeometry mapGeom = GeometryEngine.jsonToGeometry(jsonParserSR); - Utils.showProjectedGeometryInfo(mapGeom); - SpatialReference sr = mapGeom.getSpatialReference(); - assertTrue(sr == null); - } - - @Test - public void testMP2onCR175871() throws Exception { - Polygon pg = new Polygon(); - pg.startPath(-50, 10); - pg.lineTo(-50, 12); - pg.lineTo(-45, 12); - pg.lineTo(-45, 10); - - Polygon pg1 = new Polygon(); - pg1.startPath(-45, 10); - pg1.lineTo(-40, 10); - pg1.lineTo(-40, 8); - pg.add(pg1, false); - - try { - String jSonStr = GeometryEngine.geometryToJson(4326, pg); - JsonFactory jf = new JsonFactory(); - - JsonParser jp = jf.createJsonParser(jSonStr); - jp.nextToken(); - MapGeometry mg = GeometryEngine.jsonToGeometry(jp); - Geometry gm = mg.getGeometry(); - assertEquals(Geometry.Type.Polygon, gm.getType()); - - Polygon pgNew = (Polygon) gm; - - assertEquals(pgNew.getPathCount(), pg.getPathCount()); - assertEquals(pgNew.getPointCount(), pg.getPointCount()); - assertEquals(pgNew.getSegmentCount(), pg.getSegmentCount()); - - assertEquals(pgNew.getPoint(0).getX(), pg.getPoint(0).getX(), - 0.000000001); - assertEquals(pgNew.getPoint(1).getX(), pg.getPoint(1).getX(), - 0.000000001); - assertEquals(pgNew.getPoint(2).getX(), pg.getPoint(2).getX(), - 0.000000001); - assertEquals(pgNew.getPoint(3).getX(), pg.getPoint(3).getX(), - 0.000000001); - - assertEquals(pgNew.getPoint(0).getY(), pg.getPoint(0).getY(), - 0.000000001); - assertEquals(pgNew.getPoint(1).getY(), pg.getPoint(1).getY(), - 0.000000001); - assertEquals(pgNew.getPoint(2).getY(), pg.getPoint(2).getY(), - 0.000000001); - assertEquals(pgNew.getPoint(3).getY(), pg.getPoint(3).getY(), - 0.000000001); - } catch (Exception ex) { - String err = ex.getMessage(); - System.out.print(err); - throw ex; - } - } - - public static int fromJsonToWkid(JsonParser parser) - throws JsonParseException, IOException { - int wkid = 0; - if (parser.getCurrentToken() != JsonToken.START_OBJECT) { - return 0; - } - - while (parser.nextToken() != JsonToken.END_OBJECT) { - String fieldName = parser.getCurrentName(); - - if ("wkid".equals(fieldName)) { - parser.nextToken(); - wkid = parser.getIntValue(); - } - } - return wkid; - } -} +package com.esri.core.geometry; + +import java.io.IOException; +import org.codehaus.jackson.JsonFactory; +import org.codehaus.jackson.JsonParseException; +import org.codehaus.jackson.JsonParser; +import org.codehaus.jackson.JsonToken; +import junit.framework.TestCase; +import org.junit.Test; + +public class TestJSonToGeomFromWkiOrWkt_CR177613 extends TestCase { + JsonFactory factory = new JsonFactory(); + + @Override + protected void setUp() throws Exception { + super.setUp(); + } + + @Override + protected void tearDown() throws Exception { + super.tearDown(); + } + + @Test + public void testPolygonWithEmptyWKT_NoWKI() throws JsonParseException, + IOException { + String jsonStringPg = "{ \"rings\" :[ [ [-97.06138,32.837], [-97.06133,32.836], " + + "[-97.06124,32.834], [-97.06127,32.832], [-97.06138,32.837] ], " + + "[ [-97.06326,32.759], [-97.06298,32.755], [-97.06153,32.749], [-97.06326,32.759] ]], " + + "\"spatialReference\" : {\"wkt\" : \"\"}}"; + JsonParser jsonParserPg = factory.createJsonParser(jsonStringPg); + jsonParserPg.nextToken(); + + MapGeometry mapGeom = GeometryEngine.jsonToGeometry(jsonParserPg); + Utils.showProjectedGeometryInfo(mapGeom); + SpatialReference sr = mapGeom.getSpatialReference(); + assertTrue(sr == null); + } + + @Test + public void testOnlyWKI() throws JsonParseException, IOException { + String jsonStringSR = "{\"wkid\" : 4326}"; + JsonParser jsonParserSR = factory.createJsonParser(jsonStringSR); + jsonParserSR.nextToken(); + + MapGeometry mapGeom = GeometryEngine.jsonToGeometry(jsonParserSR); + Utils.showProjectedGeometryInfo(mapGeom); + SpatialReference sr = mapGeom.getSpatialReference(); + assertTrue(sr == null); + } + + @Test + public void testMP2onCR175871() throws Exception { + Polygon pg = new Polygon(); + pg.startPath(-50, 10); + pg.lineTo(-50, 12); + pg.lineTo(-45, 12); + pg.lineTo(-45, 10); + + Polygon pg1 = new Polygon(); + pg1.startPath(-45, 10); + pg1.lineTo(-40, 10); + pg1.lineTo(-40, 8); + pg.add(pg1, false); + + try { + String jSonStr = GeometryEngine.geometryToJson(4326, pg); + JsonFactory jf = new JsonFactory(); + + JsonParser jp = jf.createJsonParser(jSonStr); + jp.nextToken(); + MapGeometry mg = GeometryEngine.jsonToGeometry(jp); + Geometry gm = mg.getGeometry(); + assertEquals(Geometry.Type.Polygon, gm.getType()); + + Polygon pgNew = (Polygon) gm; + + assertEquals(pgNew.getPathCount(), pg.getPathCount()); + assertEquals(pgNew.getPointCount(), pg.getPointCount()); + assertEquals(pgNew.getSegmentCount(), pg.getSegmentCount()); + + assertEquals(pgNew.getPoint(0).getX(), pg.getPoint(0).getX(), + 0.000000001); + assertEquals(pgNew.getPoint(1).getX(), pg.getPoint(1).getX(), + 0.000000001); + assertEquals(pgNew.getPoint(2).getX(), pg.getPoint(2).getX(), + 0.000000001); + assertEquals(pgNew.getPoint(3).getX(), pg.getPoint(3).getX(), + 0.000000001); + + assertEquals(pgNew.getPoint(0).getY(), pg.getPoint(0).getY(), + 0.000000001); + assertEquals(pgNew.getPoint(1).getY(), pg.getPoint(1).getY(), + 0.000000001); + assertEquals(pgNew.getPoint(2).getY(), pg.getPoint(2).getY(), + 0.000000001); + assertEquals(pgNew.getPoint(3).getY(), pg.getPoint(3).getY(), + 0.000000001); + } catch (Exception ex) { + String err = ex.getMessage(); + System.out.print(err); + throw ex; + } + } + + public static int fromJsonToWkid(JsonParser parser) + throws JsonParseException, IOException { + int wkid = 0; + if (parser.getCurrentToken() != JsonToken.START_OBJECT) { + return 0; + } + + while (parser.nextToken() != JsonToken.END_OBJECT) { + String fieldName = parser.getCurrentName(); + + if ("wkid".equals(fieldName)) { + parser.nextToken(); + wkid = parser.getIntValue(); + } + } + return wkid; + } +} diff --git a/unittest/com/esri/core/geometry/TestJsonParser.java b/unittest/com/esri/core/geometry/TestJsonParser.java index 7e493dd1..9c22d49a 100644 --- a/unittest/com/esri/core/geometry/TestJsonParser.java +++ b/unittest/com/esri/core/geometry/TestJsonParser.java @@ -1,663 +1,663 @@ -package com.esri.core.geometry; - -import java.io.IOException; -import junit.framework.TestCase; -import org.codehaus.jackson.JsonFactory; -import org.codehaus.jackson.JsonParseException; -import org.codehaus.jackson.JsonParser; -import org.codehaus.jackson.JsonToken; -import org.junit.Assert; -import org.junit.Test; - -public class TestJsonParser extends TestCase { - JsonFactory factory = new JsonFactory(); - SpatialReference spatialReferenceWebMerc1 = SpatialReference.create(102100); - SpatialReference spatialReferenceWebMerc2 = SpatialReference - .create(spatialReferenceWebMerc1.getLatestID()); - SpatialReference spatialReferenceWGS84 = SpatialReference.create(4326); - - @Override - protected void setUp() throws Exception { - super.setUp(); - } - - @Override - protected void tearDown() throws Exception { - super.tearDown(); - } - - @Test - public void test3DPoint() throws JsonParseException, IOException { - String jsonString3DPt = "{\"x\" : -118.15, \"y\" : 33.80, \"z\" : 10.0, \"spatialReference\" : {\"wkid\" : 4326}}"; - - JsonParser jsonParser3DPt = factory.createJsonParser(jsonString3DPt); - MapGeometry point3DMP = GeometryEngine.jsonToGeometry(jsonParser3DPt); - assertTrue(-118.15 == ((Point) point3DMP.getGeometry()).getX()); - assertTrue(33.80 == ((Point) point3DMP.getGeometry()).getY()); - // FIXME add 3D support - // assertTrue(10.0 == ((Point)point3DMP.getGeometry()).getZ()); - assertTrue(spatialReferenceWGS84.getID() == point3DMP - .getSpatialReference().getID()); - } - - @Test - public void test3DPoint1() throws JsonParseException, IOException { - Point point1 = new Point(10.0, 20.0); - Point pointEmpty = new Point(); - { - JsonParser pointWebMerc1Parser = factory - .createJsonParser(GeometryEngine.geometryToJson( - spatialReferenceWebMerc1, point1)); - MapGeometry pointWebMerc1MP = GeometryEngine - .jsonToGeometry(pointWebMerc1Parser); - assertTrue(point1.getX() == ((Point) pointWebMerc1MP.getGeometry()) - .getX()); - assertTrue(point1.getY() == ((Point) pointWebMerc1MP.getGeometry()) - .getY()); - int srIdOri = spatialReferenceWebMerc1.getID(); - int srIdAfter = pointWebMerc1MP.getSpatialReference().getID(); - assertTrue(srIdOri == srIdAfter || srIdAfter == 3857); - - pointWebMerc1Parser = factory.createJsonParser(GeometryEngine - .geometryToJson(null, point1)); - pointWebMerc1MP = GeometryEngine - .jsonToGeometry(pointWebMerc1Parser); - assertTrue(null == pointWebMerc1MP.getSpatialReference()); - - String pointEmptyString = GeometryEngine.geometryToJson( - spatialReferenceWebMerc1, pointEmpty); - pointWebMerc1Parser = factory.createJsonParser(pointEmptyString); - // FIXME - pointWebMerc1MP = GeometryEngine - .jsonToGeometry(pointWebMerc1Parser); - assertTrue(pointWebMerc1MP.getGeometry().isEmpty()); - int srIdOri2 = spatialReferenceWebMerc1.getID(); - int srIdAfter2 = pointWebMerc1MP.getSpatialReference().getID(); - assertTrue(srIdOri2 == srIdAfter2 || srIdAfter2 == 3857); - } - } - - @Test - public void test3DPoint2() throws JsonParseException, IOException { - { - Point point1 = new Point(10.0, 20.0); - JsonParser pointWebMerc2Parser = factory - .createJsonParser(GeometryEngine.geometryToJson( - spatialReferenceWebMerc2, point1)); - MapGeometry pointWebMerc2MP = GeometryEngine - .jsonToGeometry(pointWebMerc2Parser); - assertTrue(point1.getX() == ((Point) pointWebMerc2MP.getGeometry()) - .getX()); - assertTrue(point1.getY() == ((Point) pointWebMerc2MP.getGeometry()) - .getY()); - assertTrue(spatialReferenceWebMerc2.getLatestID() == pointWebMerc2MP - .getSpatialReference().getLatestID()); - } - } - - @Test - public void test3DPoint3() throws JsonParseException, IOException { - { - Point point1 = new Point(10.0, 20.0); - JsonParser pointWgs84Parser = factory - .createJsonParser(GeometryEngine.geometryToJson( - spatialReferenceWGS84, point1)); - MapGeometry pointWgs84MP = GeometryEngine - .jsonToGeometry(pointWgs84Parser); - assertTrue(point1.getX() == ((Point) pointWgs84MP.getGeometry()) - .getX()); - assertTrue(point1.getY() == ((Point) pointWgs84MP.getGeometry()) - .getY()); - assertTrue(spatialReferenceWGS84.getID() == pointWgs84MP - .getSpatialReference().getID()); - } - } - - @Test - public void testMultiPoint() throws JsonParseException, IOException { - MultiPoint multiPoint1 = new MultiPoint(); - multiPoint1.add(-97.06138, 32.837); - multiPoint1.add(-97.06133, 32.836); - multiPoint1.add(-97.06124, 32.834); - multiPoint1.add(-97.06127, 32.832); - - { - JsonParser mPointWgs84Parser = factory - .createJsonParser(GeometryEngine.geometryToJson( - spatialReferenceWGS84, multiPoint1)); - MapGeometry mPointWgs84MP = GeometryEngine - .jsonToGeometry(mPointWgs84Parser); - assertTrue(multiPoint1.getPointCount() == ((MultiPoint) mPointWgs84MP - .getGeometry()).getPointCount()); - assertTrue(multiPoint1.getPoint(0).getX() == ((MultiPoint) mPointWgs84MP - .getGeometry()).getPoint(0).getX()); - assertTrue(multiPoint1.getPoint(0).getY() == ((MultiPoint) mPointWgs84MP - .getGeometry()).getPoint(0).getY()); - int lastIndex = multiPoint1.getPointCount() - 1; - assertTrue(multiPoint1.getPoint(lastIndex).getX() == ((MultiPoint) mPointWgs84MP - .getGeometry()).getPoint(lastIndex).getX()); - assertTrue(multiPoint1.getPoint(lastIndex).getY() == ((MultiPoint) mPointWgs84MP - .getGeometry()).getPoint(lastIndex).getY()); - - assertTrue(spatialReferenceWGS84.getID() == mPointWgs84MP - .getSpatialReference().getID()); - - MultiPoint mPointEmpty = new MultiPoint(); - String mPointEmptyString = GeometryEngine.geometryToJson( - spatialReferenceWGS84, mPointEmpty); - mPointWgs84Parser = factory.createJsonParser(mPointEmptyString); - - mPointWgs84MP = GeometryEngine.jsonToGeometry(mPointWgs84Parser); - assertTrue(mPointWgs84MP.getGeometry().isEmpty()); - assertTrue(spatialReferenceWGS84.getID() == mPointWgs84MP - .getSpatialReference().getID()); - - } - } - - @Test - public void testPolyline() throws JsonParseException, IOException { - Polyline polyline = new Polyline(); - polyline.startPath(-97.06138, 32.837); - polyline.lineTo(-97.06133, 32.836); - polyline.lineTo(-97.06124, 32.834); - polyline.lineTo(-97.06127, 32.832); - - polyline.startPath(-97.06326, 32.759); - polyline.lineTo(-97.06298, 32.755); - - { - JsonParser polylinePathsWgs84Parser = factory - .createJsonParser(GeometryEngine.geometryToJson( - spatialReferenceWGS84, polyline)); - MapGeometry mPolylineWGS84MP = GeometryEngine - .jsonToGeometry(polylinePathsWgs84Parser); - - assertTrue(polyline.getPointCount() == ((Polyline) mPolylineWGS84MP - .getGeometry()).getPointCount()); - assertTrue(polyline.getPoint(0).getX() == ((Polyline) mPolylineWGS84MP - .getGeometry()).getPoint(0).getX()); - assertTrue(polyline.getPoint(0).getY() == ((Polyline) mPolylineWGS84MP - .getGeometry()).getPoint(0).getY()); - - assertTrue(polyline.getPathCount() == ((Polyline) mPolylineWGS84MP - .getGeometry()).getPathCount()); - assertTrue(polyline.getSegmentCount() == ((Polyline) mPolylineWGS84MP - .getGeometry()).getSegmentCount()); - assertTrue(polyline.getSegmentCount(0) == ((Polyline) mPolylineWGS84MP - .getGeometry()).getSegmentCount(0)); - assertTrue(polyline.getSegmentCount(1) == ((Polyline) mPolylineWGS84MP - .getGeometry()).getSegmentCount(1)); - - int lastIndex = polyline.getPointCount() - 1; - assertTrue(polyline.getPoint(lastIndex).getX() == ((Polyline) mPolylineWGS84MP - .getGeometry()).getPoint(lastIndex).getX()); - assertTrue(polyline.getPoint(lastIndex).getY() == ((Polyline) mPolylineWGS84MP - .getGeometry()).getPoint(lastIndex).getY()); - - assertTrue(spatialReferenceWGS84.getID() == mPolylineWGS84MP - .getSpatialReference().getID()); - - Polyline emptyPolyline = new Polyline(); - String emptyString = GeometryEngine.geometryToJson( - spatialReferenceWGS84, emptyPolyline); - mPolylineWGS84MP = GeometryEngine.jsonToGeometry(factory - .createJsonParser(emptyString)); - assertTrue(mPolylineWGS84MP.getGeometry().isEmpty()); - assertTrue(spatialReferenceWGS84.getID() == mPolylineWGS84MP - .getSpatialReference().getID()); - } - } - - @Test - public void testPolygon() throws JsonParseException, IOException { - Polygon polygon = new Polygon(); - polygon.startPath(-97.06138, 32.837); - polygon.lineTo(-97.06133, 32.836); - polygon.lineTo(-97.06124, 32.834); - polygon.lineTo(-97.06127, 32.832); - - polygon.startPath(-97.06326, 32.759); - polygon.lineTo(-97.06298, 32.755); - - { - JsonParser polygonPathsWgs84Parser = factory - .createJsonParser(GeometryEngine.geometryToJson( - spatialReferenceWGS84, polygon)); - MapGeometry mPolygonWGS84MP = GeometryEngine - .jsonToGeometry(polygonPathsWgs84Parser); - - assertTrue(polygon.getPointCount() + 1 == ((Polygon) mPolygonWGS84MP - .getGeometry()).getPointCount()); - assertTrue(polygon.getPoint(0).getX() == ((Polygon) mPolygonWGS84MP - .getGeometry()).getPoint(0).getX()); - assertTrue(polygon.getPoint(0).getY() == ((Polygon) mPolygonWGS84MP - .getGeometry()).getPoint(0).getY()); - - assertTrue(polygon.getPathCount() == ((Polygon) mPolygonWGS84MP - .getGeometry()).getPathCount()); - assertTrue(polygon.getSegmentCount() + 1 == ((Polygon) mPolygonWGS84MP - .getGeometry()).getSegmentCount()); - assertTrue(polygon.getSegmentCount(0) == ((Polygon) mPolygonWGS84MP - .getGeometry()).getSegmentCount(0)); - assertTrue(polygon.getSegmentCount(1) + 1 == ((Polygon) mPolygonWGS84MP - .getGeometry()).getSegmentCount(1)); - - int lastIndex = polygon.getPointCount() - 1; - assertTrue(polygon.getPoint(lastIndex).getX() == ((Polygon) mPolygonWGS84MP - .getGeometry()).getPoint(lastIndex).getX()); - assertTrue(polygon.getPoint(lastIndex).getY() == ((Polygon) mPolygonWGS84MP - .getGeometry()).getPoint(lastIndex).getY()); - - assertTrue(spatialReferenceWGS84.getID() == mPolygonWGS84MP - .getSpatialReference().getID()); - - Polygon emptyPolygon = new Polygon(); - String emptyPolygonString = GeometryEngine.geometryToJson( - spatialReferenceWGS84, emptyPolygon); - polygonPathsWgs84Parser = factory - .createJsonParser(emptyPolygonString); - mPolygonWGS84MP = GeometryEngine - .jsonToGeometry(polygonPathsWgs84Parser); - - assertTrue(mPolygonWGS84MP.getGeometry().isEmpty()); - assertTrue(spatialReferenceWGS84.getID() == mPolygonWGS84MP - .getSpatialReference().getID()); - } - } - - @Test - public void testEnvelope() throws JsonParseException, IOException { - Envelope envelope = new Envelope(); - envelope.setCoords(-109.55, 25.76, -86.39, 49.94); - - { - JsonParser envelopeWGS84Parser = factory - .createJsonParser(GeometryEngine.geometryToJson( - spatialReferenceWGS84, envelope)); - MapGeometry envelopeWGS84MP = GeometryEngine - .jsonToGeometry(envelopeWGS84Parser); - assertTrue(envelope.isEmpty() == envelopeWGS84MP.getGeometry() - .isEmpty()); - assertTrue(envelope.getXMax() == ((Envelope) envelopeWGS84MP - .getGeometry()).getXMax()); - assertTrue(envelope.getYMax() == ((Envelope) envelopeWGS84MP - .getGeometry()).getYMax()); - assertTrue(envelope.getXMin() == ((Envelope) envelopeWGS84MP - .getGeometry()).getXMin()); - assertTrue(envelope.getYMin() == ((Envelope) envelopeWGS84MP - .getGeometry()).getYMin()); - assertTrue(spatialReferenceWGS84.getID() == envelopeWGS84MP - .getSpatialReference().getID()); - - Envelope emptyEnvelope = new Envelope(); - String emptyEnvString = GeometryEngine.geometryToJson( - spatialReferenceWGS84, emptyEnvelope); - envelopeWGS84Parser = factory.createJsonParser(emptyEnvString); - envelopeWGS84MP = GeometryEngine - .jsonToGeometry(envelopeWGS84Parser); - - assertTrue(envelopeWGS84MP.getGeometry().isEmpty()); - assertTrue(spatialReferenceWGS84.getID() == envelopeWGS84MP - .getSpatialReference().getID()); - } - } - - @Test - public void testCR181369() throws JsonParseException, IOException { - // CR181369 - { - String jsonStringPointAndWKT = "{\"x\":10.0,\"y\":20.0,\"spatialReference\":{\"wkt\" : \"PROJCS[\\\"NAD83_UTM_zone_15N\\\",GEOGCS[\\\"GCS_North_American_1983\\\",DATUM[\\\"D_North_American_1983\\\",SPHEROID[\\\"GRS_1980\\\",6378137.0,298.257222101]],PRIMEM[\\\"Greenwich\\\",0.0],UNIT[\\\"Degree\\\",0.0174532925199433]],PROJECTION[\\\"Transverse_Mercator\\\"],PARAMETER[\\\"false_easting\\\",500000.0],PARAMETER[\\\"false_northing\\\",0.0],PARAMETER[\\\"central_meridian\\\",-93.0],PARAMETER[\\\"scale_factor\\\",0.9996],PARAMETER[\\\"latitude_of_origin\\\",0.0],UNIT[\\\"Meter\\\",1.0]]\"} }"; - JsonParser jsonParserPointAndWKT = factory - .createJsonParser(jsonStringPointAndWKT); - MapGeometry mapGeom2 = GeometryEngine - .jsonToGeometry(jsonParserPointAndWKT); - String jsonStringPointAndWKT2 = GeometryEngine.geometryToJson( - mapGeom2.getSpatialReference(), mapGeom2.getGeometry()); - JsonParser jsonParserPointAndWKT2 = factory - .createJsonParser(jsonStringPointAndWKT2); - MapGeometry mapGeom3 = GeometryEngine - .jsonToGeometry(jsonParserPointAndWKT2); - assertTrue(((Point) mapGeom2.getGeometry()).getX() == ((Point) mapGeom3 - .getGeometry()).getX()); - assertTrue(((Point) mapGeom2.getGeometry()).getY() == ((Point) mapGeom3 - .getGeometry()).getY()); - assertTrue(mapGeom2.getSpatialReference().getText() - .equals(mapGeom3.getSpatialReference().getText())); - assertTrue(mapGeom2.getSpatialReference().getID() == mapGeom3 - .getSpatialReference().getID()); - } - } - - @Test - public void testSpatialRef() throws JsonParseException, IOException { - // String jsonStringPt = - // "{\"x\":-20037508.342787,\"y\":20037508.342787},\"spatialReference\":{\"wkid\":102100}}"; - String jsonStringPt = "{\"x\":10.0,\"y\":20.0,\"spatialReference\":{\"wkid\": 102100}}";// 102100 - @SuppressWarnings("unused") - String jsonStringPt2 = "{\"x\":10.0,\"y\":20.0,\"spatialReference\":{\"wkid\":4326}}"; - String jsonStringMpt = "{ \"points\" : [ [-97.06138,32.837], [-97.06133,32.836], [-97.06124,32.834], [-97.06127,32.832] ], \"spatialReference\" : {\"wkid\" : 4326}}";// 4326 - String jsonStringMpt3D = "{\"hasZs\" : true,\"points\" : [ [-97.06138,32.837,35.0], [-97.06133,32.836,35.1], [-97.06124,32.834,35.2], [-97.06127,32.832,35.3] ],\"spatialReference\" : {\"wkid\" : 4326}}"; - String jsonStringPl = "{\"paths\" : [ [ [-97.06138,32.837], [-97.06133,32.836], [-97.06124,32.834], [-97.06127,32.832] ], [ [-97.06326,32.759], [-97.06298,32.755] ]],\"spatialReference\" : {\"wkid\" : 4326}}"; - String jsonStringPl3D = "{\"hasMs\" : true,\"paths\" : [[ [-97.06138,32.837,5], [-97.06133,32.836,6], [-97.06124,32.834,7], [-97.06127,32.832,8] ],[ [-97.06326,32.759], [-97.06298,32.755] ]],\"spatialReference\" : {\"wkid\" : 4326}}"; - String jsonStringPg = "{ \"rings\" :[ [ [-97.06138,32.837], [-97.06133,32.836], [-97.06124,32.834], [-97.06127,32.832], [-97.06138,32.837] ], [ [-97.06326,32.759], [-97.06298,32.755], [-97.06153,32.749], [-97.06326,32.759] ]], \"spatialReference\" : {\"wkt\" : \"\"}}"; - String jsonStringPg3D = "{\"hasZs\" : true,\"hasMs\" : true,\"rings\" : [ [ [-97.06138, 32.837, 35.1, 4], [-97.06133, 32.836, 35.2, 4.1], [-97.06124, 32.834, 35.3, 4.2], [-97.06127, 32.832, 35.2, 44.3], [-97.06138, 32.837, 35.1, 4] ],[ [-97.06326, 32.759, 35.4], [-97.06298, 32.755, 35.5], [-97.06153, 32.749, 35.6], [-97.06326, 32.759, 35.4] ]],\"spatialReference\" : {\"wkid\" : 4326}}"; - String jsonStringPg2 = "{ \"spatialReference\" : {\"wkid\" : 4326}, \"rings\" : [[[-118.35,32.81],[-118.42,32.806],[-118.511,32.892],[-118.35,32.81]]]}"; - String jsonStringPg3 = "{ \"spatialReference\": {\"layerName\":\"GAS_POINTS\",\"name\":null,\"sdesrid\":102100,\"wkid\":102100,\"wkt\":null}}"; - String jsonString2SpatialReferences = "{ \"spatialReference\": {\"layerName\":\"GAS_POINTS\",\"name\":null,\"sdesrid\":102100,\"wkid\":102100,\"wkt\":\"GEOGCS[\\\"GCS_WGS_1984\\\",DATUM[\\\"D_WGS_1984\\\",SPHEROID[\\\"WGS_1984\\\",6378137,298.257223563]],PRIMEM[\\\"Greenwich\\\",0],UNIT[\\\"Degree\\\",0.017453292519943295]]\"}}"; - String jsonString2SpatialReferences2 = "{ \"spatialReference\": {\"layerName\":\"GAS_POINTS\",\"name\":null,\"sdesrid\":10,\"wkid\":10,\"wkt\":\"GEOGCS[\\\"GCS_WGS_1984\\\",DATUM[\\\"D_WGS_1984\\\",SPHEROID[\\\"WGS_1984\\\",6378137,298.257223563]],PRIMEM[\\\"Greenwich\\\",0],UNIT[\\\"Degree\\\",0.017453292519943295]]\"}}"; - String jsonStringSR = "{\"wkid\" : 4326}"; - String jsonStringEnv = "{\"xmin\" : -109.55, \"ymin\" : 25.76, \"xmax\" : -86.39, \"ymax\" : 49.94,\"spatialReference\" : {\"wkid\" : 4326}}"; - String jsonStringHongKon = "{\"xmin\" : -122.55, \"ymin\" : 37.65, \"xmax\" : -122.28, \"ymax\" : 37.84,\"spatialReference\" : {\"wkid\" : 4326}}"; - @SuppressWarnings("unused") - String jsonStringWKT = " {\"wkt\" : \"GEOGCS[\\\"GCS_WGS_1984\\\",DATUM[\\\"D_WGS_1984\\\",SPHEROID[\\\"WGS_1984\\\",6378137,298.257223563]],PRIMEM[\\\"Greenwich\\\",0],UNIT[\\\"Degree\\\",0.017453292519943295]]\"}"; - String jsonStringInvalidWKID = "{\"x\":10.0,\"y\":20.0},\"spatialReference\":{\"wkid\":35253523}}"; - String jsonStringOregon = "{\"xmin\":7531831.219849482,\"ymin\":585702.9799639136,\"xmax\":7750143.589982405,\"ymax\":733289.6299999952,\"spatialReference\":{\"wkid\":102726}}"; - - JsonParser jsonParserPt = factory.createJsonParser(jsonStringPt); - JsonParser jsonParserMpt = factory.createJsonParser(jsonStringMpt); - JsonParser jsonParserMpt3D = factory.createJsonParser(jsonStringMpt3D); - JsonParser jsonParserPl = factory.createJsonParser(jsonStringPl); - JsonParser jsonParserPl3D = factory.createJsonParser(jsonStringPl3D); - JsonParser jsonParserPg = factory.createJsonParser(jsonStringPg); - JsonParser jsonParserPg3D = factory.createJsonParser(jsonStringPg3D); - JsonParser jsonParserPg2 = factory.createJsonParser(jsonStringPg2); - @SuppressWarnings("unused") - JsonParser jsonParserSR = factory.createJsonParser(jsonStringSR); - JsonParser jsonParserEnv = factory.createJsonParser(jsonStringEnv); - JsonParser jsonParserPg3 = factory.createJsonParser(jsonStringPg3); - @SuppressWarnings("unused") - JsonParser jsonParserCrazy1 = factory - .createJsonParser(jsonString2SpatialReferences); - @SuppressWarnings("unused") - JsonParser jsonParserCrazy2 = factory - .createJsonParser(jsonString2SpatialReferences2); - JsonParser jsonParserInvalidWKID = factory - .createJsonParser(jsonStringInvalidWKID); - @SuppressWarnings("unused") - JsonParser jsonParseHongKon = factory - .createJsonParser(jsonStringHongKon); - JsonParser jsonParseOregon = factory.createJsonParser(jsonStringOregon); - - MapGeometry mapGeom = GeometryEngine.jsonToGeometry(jsonParserPt); - // showProjectedGeometryInfo(mapGeom); - Assert.assertTrue(mapGeom.getSpatialReference().getID() == 102100); - - MapGeometry mapGeomOregon = GeometryEngine - .jsonToGeometry(jsonParseOregon); - Assert.assertTrue(mapGeomOregon.getSpatialReference().getID() == 102726); - - mapGeom = GeometryEngine.jsonToGeometry(jsonParserMpt); - Assert.assertTrue(mapGeom.getSpatialReference().getID() == 4326); - - mapGeom = GeometryEngine.jsonToGeometry(jsonParserMpt3D); - Assert.assertTrue(mapGeom.getSpatialReference().getID() == 4326); - { - Assert.assertTrue(((MultiPoint) mapGeom.getGeometry()).getPoint(0) - .getX() == -97.06138); - Assert.assertTrue(((MultiPoint) mapGeom.getGeometry()).getPoint(0) - .getY() == 32.837); - Assert.assertTrue(((MultiPoint) mapGeom.getGeometry()).getPoint(3) - .getX() == -97.06127); - Assert.assertTrue(((MultiPoint) mapGeom.getGeometry()).getPoint(3) - .getY() == 32.832); - } - // showProjectedGeometryInfo(mapGeom); - - mapGeom = GeometryEngine.jsonToGeometry(jsonParserPl); - Assert.assertTrue(mapGeom.getSpatialReference().getID() == 4326); - // showProjectedGeometryInfo(mapGeom); - - mapGeom = GeometryEngine.jsonToGeometry(jsonParserPl3D); - { - // [[ [-97.06138,32.837,5], [-97.06133,32.836,6], - // [-97.06124,32.834,7], [-97.06127,32.832,8] ], - // [ [-97.06326,32.759], [-97.06298,32.755] ]]"; - Assert.assertTrue(((Polyline) mapGeom.getGeometry()).getPoint(0) - .getX() == -97.06138); - Assert.assertTrue(((Polyline) mapGeom.getGeometry()).getPoint(0) - .getY() == 32.837); - int lastIndex = ((Polyline) mapGeom.getGeometry()).getPointCount() - 1; - Assert.assertTrue(((Polyline) mapGeom.getGeometry()).getPoint( - lastIndex).getX() == -97.06298);// -97.06153, 32.749 - Assert.assertTrue(((Polyline) mapGeom.getGeometry()).getPoint( - lastIndex).getY() == 32.755); - int lastIndexFirstLine = ((Polyline) mapGeom.getGeometry()) - .getPathEnd(0) - 1; - Assert.assertTrue(((Polyline) mapGeom.getGeometry()).getPoint( - lastIndexFirstLine).getX() == -97.06127);// -97.06153, - // 32.749 - Assert.assertTrue(((Polyline) mapGeom.getGeometry()).getPoint( - lastIndexFirstLine).getY() == 32.832); - } - - mapGeom = GeometryEngine.jsonToGeometry(jsonParserPg); - Assert.assertTrue(mapGeom.getSpatialReference() == null); - - mapGeom = GeometryEngine.jsonToGeometry(jsonParserPg3D); - { - Assert.assertTrue(((Polygon) mapGeom.getGeometry()).getPoint(0) - .getX() == -97.06138); - Assert.assertTrue(((Polygon) mapGeom.getGeometry()).getPoint(0) - .getY() == 32.837); - int lastIndex = ((Polygon) mapGeom.getGeometry()).getPointCount() - 1; - Assert.assertTrue(((Polygon) mapGeom.getGeometry()).getPoint( - lastIndex).getX() == -97.06153);// -97.06153, 32.749 - Assert.assertTrue(((Polygon) mapGeom.getGeometry()).getPoint( - lastIndex).getY() == 32.749); - } - - mapGeom = GeometryEngine.jsonToGeometry(jsonParserPg2); - Assert.assertTrue(mapGeom.getSpatialReference().getID() == 4326); - // showProjectedGeometryInfo(mapGeom); - - mapGeom = GeometryEngine.jsonToGeometry(jsonParserPg3); - Assert.assertTrue(mapGeom.getSpatialReference().getID() == 102100); - // showProjectedGeometryInfo(mapGeom); - - // mapGeom = GeometryEngine.jsonToGeometry(jsonParserCrazy1); - // Assert.assertTrue(mapGeom.getSpatialReference().getText().equals("")); - // showProjectedGeometryInfo(mapGeom); - - mapGeom = GeometryEngine.jsonToGeometry(jsonParserEnv); - Assert.assertTrue(mapGeom.getSpatialReference().getID() == 4326); - // showProjectedGeometryInfo(mapGeom); - // System.out.println("\n\nWKID: "+ - // SpatialReference.create(wkid).fromJson(jsonParserSR).getID()); - - try { - GeometryEngine.jsonToGeometry(jsonParserInvalidWKID); - } catch (Exception ex) { - System.out.print(ex.getMessage()); - } - } - - @Test - public void testMP2onCR175871() throws Exception { - Polygon pg = new Polygon(); - pg.startPath(-50, 10); - pg.lineTo(-50, 12); - pg.lineTo(-45, 12); - pg.lineTo(-45, 10); - - Polygon pg1 = new Polygon(); - pg1.startPath(-45, 10); - pg1.lineTo(-40, 10); - pg1.lineTo(-40, 8); - pg.add(pg1, false); - - SpatialReference spatialReference = SpatialReference.create(4326); - - try { - String jSonStr = GeometryEngine - .geometryToJson(spatialReference, pg); - JsonFactory jf = new JsonFactory(); - - JsonParser jp = jf.createJsonParser(jSonStr); - jp.nextToken(); - MapGeometry mg = GeometryEngine.jsonToGeometry(jp); - Geometry gm = mg.getGeometry(); - Assert.assertEquals(Geometry.Type.Polygon, gm.getType()); - Assert.assertTrue(mg.getSpatialReference().getID() == 4326); - - Polygon pgNew = (Polygon) gm; - - Assert.assertEquals(pgNew.getPathCount(), pg.getPathCount()); - Assert.assertEquals(pgNew.getPointCount(), pg.getPointCount()); - Assert.assertEquals(pgNew.getSegmentCount(), pg.getSegmentCount()); - - Assert.assertEquals(pgNew.getPoint(0).getX(), - pg.getPoint(0).getX(), 0.000000001); - Assert.assertEquals(pgNew.getPoint(1).getX(), - pg.getPoint(1).getX(), 0.000000001); - Assert.assertEquals(pgNew.getPoint(2).getX(), - pg.getPoint(2).getX(), 0.000000001); - Assert.assertEquals(pgNew.getPoint(3).getX(), - pg.getPoint(3).getX(), 0.000000001); - - Assert.assertEquals(pgNew.getPoint(0).getY(), - pg.getPoint(0).getY(), 0.000000001); - Assert.assertEquals(pgNew.getPoint(1).getY(), - pg.getPoint(1).getY(), 0.000000001); - Assert.assertEquals(pgNew.getPoint(2).getY(), - pg.getPoint(2).getY(), 0.000000001); - Assert.assertEquals(pgNew.getPoint(3).getY(), - pg.getPoint(3).getY(), 0.000000001); - } catch (Exception ex) { - String err = ex.getMessage(); - System.out.print(err); - throw ex; - } - } - - @Test - public static int fromJsonToWkid(JsonParser parser) - throws JsonParseException, IOException { - int wkid = 0; - if (parser.getCurrentToken() != JsonToken.START_OBJECT) { - return 0; - } - - while (parser.nextToken() != JsonToken.END_OBJECT) { - String fieldName = parser.getCurrentName(); - - if ("wkid".equals(fieldName)) { - parser.nextToken(); - wkid = parser.getIntValue(); - } - } - return wkid; - } - - @SuppressWarnings("unused") - private static void showProjectedGeometryInfo(MapGeometry mapGeom) { - System.out.println("\n"); - MapGeometry geom = mapGeom; - // while ((geom = geomCursor.next()) != null) { - - if (geom.getGeometry() instanceof Point) { - Point pnt = (Point) geom.getGeometry(); - System.out - .println("Point(" + pnt.getX() + " , " + pnt.getY() + ")"); - if (geom.getSpatialReference() == null) - System.out.println("No spatial reference"); - else - System.out.println("wkid: " - + geom.getSpatialReference().getID()); - - } else if (geom.getGeometry() instanceof MultiPoint) { - MultiPoint mp = (MultiPoint) geom.getGeometry(); - System.out.println("Multipoint has " + mp.getPointCount() - + " points."); - - System.out.println("wkid: " + geom.getSpatialReference().getID()); - - } else if (geom.getGeometry() instanceof Polygon) { - Polygon mp = (Polygon) geom.getGeometry(); - System.out.println("Polygon has " + mp.getPointCount() - + " points and " + mp.getPathCount() + " parts."); - if (mp.getPathCount() > 1) { - System.out.println("Part start of 2nd segment : " - + mp.getPathStart(1)); - System.out.println("Part end of 2nd segment : " - + mp.getPathEnd(1)); - System.out.println("Part size of 2nd segment : " - + mp.getPathSize(1)); - - int start = mp.getPathStart(1); - int end = mp.getPathEnd(1); - for (int i = start; i < end; i++) { - Point pp = mp.getPoint(i); - System.out.println("Point(" + i + ") = (" + pp.getX() - + ", " + pp.getY() + ")"); - } - } - System.out.println("wkid: " + geom.getSpatialReference().getID()); - - } else if (geom.getGeometry() instanceof Polyline) { - Polyline mp = (Polyline) geom.getGeometry(); - System.out.println("Polyline has " + mp.getPointCount() - + " points and " + mp.getPathCount() + " parts."); - System.out.println("Part start of 2nd segment : " - + mp.getPathStart(1)); - System.out.println("Part end of 2nd segment : " - + mp.getPathEnd(1)); - System.out.println("Part size of 2nd segment : " - + mp.getPathSize(1)); - int start = mp.getPathStart(1); - int end = mp.getPathEnd(1); - for (int i = start; i < end; i++) { - Point pp = mp.getPoint(i); - System.out.println("Point(" + i + ") = (" + pp.getX() + ", " - + pp.getY() + ")"); - } - - System.out.println("wkid: " + geom.getSpatialReference().getID()); - } - } - - @Test - public void testGeometryToJSON() { - Polygon geom = new Polygon(); - geom.startPath(new Point(-113, 34)); - geom.lineTo(new Point(-105, 34)); - geom.lineTo(new Point(-108, 40)); - - String outputPolygon1 = GeometryEngine.geometryToJson(-1, geom);// Test - // WKID - // == -1 - System.out.println("Geom JSON STRING is" + outputPolygon1); - String correctPolygon1 = "{\"rings\":[[[-113.0,34.0],[-105.0,34.0],[-108.0,40.0],[-113.0,34.0]]]}"; - - assertEquals(correctPolygon1, outputPolygon1); - - String outputPolygon2 = GeometryEngine.geometryToJson(4326, geom); - System.out.println("Geom JSON STRING is" + outputPolygon2); - - String correctPolygon2 = "{\"rings\":[[[-113.0,34.0],[-105.0,34.0],[-108.0,40.0],[-113.0,34.0]]],\"spatialReference\":{\"wkid\":4326}}"; - assertEquals(correctPolygon2, outputPolygon2); - } - - @Test - public void testGeometryToJSONOldID() throws Exception {// CR - Polygon geom = new Polygon(); - geom.startPath(new Point(-113, 34)); - geom.lineTo(new Point(-105, 34)); - geom.lineTo(new Point(-108, 40)); - String outputPolygon = GeometryEngine.geometryToJson( - SpatialReference.create(3857), geom);// Test WKID == -1 - String correctPolygon = "{\"rings\":[[[-113.0,34.0],[-105.0,34.0],[-108.0,40.0],[-113.0,34.0]]],\"spatialReference\":{\"wkid\":102100,\"latestWkid\":3857}}"; - assertTrue(outputPolygon.equals(correctPolygon)); - JsonFactory jf = new JsonFactory(); - JsonParser jp = jf.createJsonParser(outputPolygon); - jp.nextToken(); - MapGeometry mg = GeometryEngine.jsonToGeometry(jp); - @SuppressWarnings("unused") - int srId = mg.getSpatialReference().getID(); - @SuppressWarnings("unused") - int srOldId = mg.getSpatialReference().getOldID(); - Assert.assertTrue(mg.getSpatialReference().getID() == 3857); - Assert.assertTrue(mg.getSpatialReference().getLatestID() == 3857); - Assert.assertTrue(mg.getSpatialReference().getOldID() == 102100); - } -} +package com.esri.core.geometry; + +import java.io.IOException; +import junit.framework.TestCase; +import org.codehaus.jackson.JsonFactory; +import org.codehaus.jackson.JsonParseException; +import org.codehaus.jackson.JsonParser; +import org.codehaus.jackson.JsonToken; +import org.junit.Assert; +import org.junit.Test; + +public class TestJsonParser extends TestCase { + JsonFactory factory = new JsonFactory(); + SpatialReference spatialReferenceWebMerc1 = SpatialReference.create(102100); + SpatialReference spatialReferenceWebMerc2 = SpatialReference + .create(spatialReferenceWebMerc1.getLatestID()); + SpatialReference spatialReferenceWGS84 = SpatialReference.create(4326); + + @Override + protected void setUp() throws Exception { + super.setUp(); + } + + @Override + protected void tearDown() throws Exception { + super.tearDown(); + } + + @Test + public void test3DPoint() throws JsonParseException, IOException { + String jsonString3DPt = "{\"x\" : -118.15, \"y\" : 33.80, \"z\" : 10.0, \"spatialReference\" : {\"wkid\" : 4326}}"; + + JsonParser jsonParser3DPt = factory.createJsonParser(jsonString3DPt); + MapGeometry point3DMP = GeometryEngine.jsonToGeometry(jsonParser3DPt); + assertTrue(-118.15 == ((Point) point3DMP.getGeometry()).getX()); + assertTrue(33.80 == ((Point) point3DMP.getGeometry()).getY()); + // FIXME add 3D support + // assertTrue(10.0 == ((Point)point3DMP.getGeometry()).getZ()); + assertTrue(spatialReferenceWGS84.getID() == point3DMP + .getSpatialReference().getID()); + } + + @Test + public void test3DPoint1() throws JsonParseException, IOException { + Point point1 = new Point(10.0, 20.0); + Point pointEmpty = new Point(); + { + JsonParser pointWebMerc1Parser = factory + .createJsonParser(GeometryEngine.geometryToJson( + spatialReferenceWebMerc1, point1)); + MapGeometry pointWebMerc1MP = GeometryEngine + .jsonToGeometry(pointWebMerc1Parser); + assertTrue(point1.getX() == ((Point) pointWebMerc1MP.getGeometry()) + .getX()); + assertTrue(point1.getY() == ((Point) pointWebMerc1MP.getGeometry()) + .getY()); + int srIdOri = spatialReferenceWebMerc1.getID(); + int srIdAfter = pointWebMerc1MP.getSpatialReference().getID(); + assertTrue(srIdOri == srIdAfter || srIdAfter == 3857); + + pointWebMerc1Parser = factory.createJsonParser(GeometryEngine + .geometryToJson(null, point1)); + pointWebMerc1MP = GeometryEngine + .jsonToGeometry(pointWebMerc1Parser); + assertTrue(null == pointWebMerc1MP.getSpatialReference()); + + String pointEmptyString = GeometryEngine.geometryToJson( + spatialReferenceWebMerc1, pointEmpty); + pointWebMerc1Parser = factory.createJsonParser(pointEmptyString); + // FIXME + pointWebMerc1MP = GeometryEngine + .jsonToGeometry(pointWebMerc1Parser); + assertTrue(pointWebMerc1MP.getGeometry().isEmpty()); + int srIdOri2 = spatialReferenceWebMerc1.getID(); + int srIdAfter2 = pointWebMerc1MP.getSpatialReference().getID(); + assertTrue(srIdOri2 == srIdAfter2 || srIdAfter2 == 3857); + } + } + + @Test + public void test3DPoint2() throws JsonParseException, IOException { + { + Point point1 = new Point(10.0, 20.0); + JsonParser pointWebMerc2Parser = factory + .createJsonParser(GeometryEngine.geometryToJson( + spatialReferenceWebMerc2, point1)); + MapGeometry pointWebMerc2MP = GeometryEngine + .jsonToGeometry(pointWebMerc2Parser); + assertTrue(point1.getX() == ((Point) pointWebMerc2MP.getGeometry()) + .getX()); + assertTrue(point1.getY() == ((Point) pointWebMerc2MP.getGeometry()) + .getY()); + assertTrue(spatialReferenceWebMerc2.getLatestID() == pointWebMerc2MP + .getSpatialReference().getLatestID()); + } + } + + @Test + public void test3DPoint3() throws JsonParseException, IOException { + { + Point point1 = new Point(10.0, 20.0); + JsonParser pointWgs84Parser = factory + .createJsonParser(GeometryEngine.geometryToJson( + spatialReferenceWGS84, point1)); + MapGeometry pointWgs84MP = GeometryEngine + .jsonToGeometry(pointWgs84Parser); + assertTrue(point1.getX() == ((Point) pointWgs84MP.getGeometry()) + .getX()); + assertTrue(point1.getY() == ((Point) pointWgs84MP.getGeometry()) + .getY()); + assertTrue(spatialReferenceWGS84.getID() == pointWgs84MP + .getSpatialReference().getID()); + } + } + + @Test + public void testMultiPoint() throws JsonParseException, IOException { + MultiPoint multiPoint1 = new MultiPoint(); + multiPoint1.add(-97.06138, 32.837); + multiPoint1.add(-97.06133, 32.836); + multiPoint1.add(-97.06124, 32.834); + multiPoint1.add(-97.06127, 32.832); + + { + JsonParser mPointWgs84Parser = factory + .createJsonParser(GeometryEngine.geometryToJson( + spatialReferenceWGS84, multiPoint1)); + MapGeometry mPointWgs84MP = GeometryEngine + .jsonToGeometry(mPointWgs84Parser); + assertTrue(multiPoint1.getPointCount() == ((MultiPoint) mPointWgs84MP + .getGeometry()).getPointCount()); + assertTrue(multiPoint1.getPoint(0).getX() == ((MultiPoint) mPointWgs84MP + .getGeometry()).getPoint(0).getX()); + assertTrue(multiPoint1.getPoint(0).getY() == ((MultiPoint) mPointWgs84MP + .getGeometry()).getPoint(0).getY()); + int lastIndex = multiPoint1.getPointCount() - 1; + assertTrue(multiPoint1.getPoint(lastIndex).getX() == ((MultiPoint) mPointWgs84MP + .getGeometry()).getPoint(lastIndex).getX()); + assertTrue(multiPoint1.getPoint(lastIndex).getY() == ((MultiPoint) mPointWgs84MP + .getGeometry()).getPoint(lastIndex).getY()); + + assertTrue(spatialReferenceWGS84.getID() == mPointWgs84MP + .getSpatialReference().getID()); + + MultiPoint mPointEmpty = new MultiPoint(); + String mPointEmptyString = GeometryEngine.geometryToJson( + spatialReferenceWGS84, mPointEmpty); + mPointWgs84Parser = factory.createJsonParser(mPointEmptyString); + + mPointWgs84MP = GeometryEngine.jsonToGeometry(mPointWgs84Parser); + assertTrue(mPointWgs84MP.getGeometry().isEmpty()); + assertTrue(spatialReferenceWGS84.getID() == mPointWgs84MP + .getSpatialReference().getID()); + + } + } + + @Test + public void testPolyline() throws JsonParseException, IOException { + Polyline polyline = new Polyline(); + polyline.startPath(-97.06138, 32.837); + polyline.lineTo(-97.06133, 32.836); + polyline.lineTo(-97.06124, 32.834); + polyline.lineTo(-97.06127, 32.832); + + polyline.startPath(-97.06326, 32.759); + polyline.lineTo(-97.06298, 32.755); + + { + JsonParser polylinePathsWgs84Parser = factory + .createJsonParser(GeometryEngine.geometryToJson( + spatialReferenceWGS84, polyline)); + MapGeometry mPolylineWGS84MP = GeometryEngine + .jsonToGeometry(polylinePathsWgs84Parser); + + assertTrue(polyline.getPointCount() == ((Polyline) mPolylineWGS84MP + .getGeometry()).getPointCount()); + assertTrue(polyline.getPoint(0).getX() == ((Polyline) mPolylineWGS84MP + .getGeometry()).getPoint(0).getX()); + assertTrue(polyline.getPoint(0).getY() == ((Polyline) mPolylineWGS84MP + .getGeometry()).getPoint(0).getY()); + + assertTrue(polyline.getPathCount() == ((Polyline) mPolylineWGS84MP + .getGeometry()).getPathCount()); + assertTrue(polyline.getSegmentCount() == ((Polyline) mPolylineWGS84MP + .getGeometry()).getSegmentCount()); + assertTrue(polyline.getSegmentCount(0) == ((Polyline) mPolylineWGS84MP + .getGeometry()).getSegmentCount(0)); + assertTrue(polyline.getSegmentCount(1) == ((Polyline) mPolylineWGS84MP + .getGeometry()).getSegmentCount(1)); + + int lastIndex = polyline.getPointCount() - 1; + assertTrue(polyline.getPoint(lastIndex).getX() == ((Polyline) mPolylineWGS84MP + .getGeometry()).getPoint(lastIndex).getX()); + assertTrue(polyline.getPoint(lastIndex).getY() == ((Polyline) mPolylineWGS84MP + .getGeometry()).getPoint(lastIndex).getY()); + + assertTrue(spatialReferenceWGS84.getID() == mPolylineWGS84MP + .getSpatialReference().getID()); + + Polyline emptyPolyline = new Polyline(); + String emptyString = GeometryEngine.geometryToJson( + spatialReferenceWGS84, emptyPolyline); + mPolylineWGS84MP = GeometryEngine.jsonToGeometry(factory + .createJsonParser(emptyString)); + assertTrue(mPolylineWGS84MP.getGeometry().isEmpty()); + assertTrue(spatialReferenceWGS84.getID() == mPolylineWGS84MP + .getSpatialReference().getID()); + } + } + + @Test + public void testPolygon() throws JsonParseException, IOException { + Polygon polygon = new Polygon(); + polygon.startPath(-97.06138, 32.837); + polygon.lineTo(-97.06133, 32.836); + polygon.lineTo(-97.06124, 32.834); + polygon.lineTo(-97.06127, 32.832); + + polygon.startPath(-97.06326, 32.759); + polygon.lineTo(-97.06298, 32.755); + + { + JsonParser polygonPathsWgs84Parser = factory + .createJsonParser(GeometryEngine.geometryToJson( + spatialReferenceWGS84, polygon)); + MapGeometry mPolygonWGS84MP = GeometryEngine + .jsonToGeometry(polygonPathsWgs84Parser); + + assertTrue(polygon.getPointCount() + 1 == ((Polygon) mPolygonWGS84MP + .getGeometry()).getPointCount()); + assertTrue(polygon.getPoint(0).getX() == ((Polygon) mPolygonWGS84MP + .getGeometry()).getPoint(0).getX()); + assertTrue(polygon.getPoint(0).getY() == ((Polygon) mPolygonWGS84MP + .getGeometry()).getPoint(0).getY()); + + assertTrue(polygon.getPathCount() == ((Polygon) mPolygonWGS84MP + .getGeometry()).getPathCount()); + assertTrue(polygon.getSegmentCount() + 1 == ((Polygon) mPolygonWGS84MP + .getGeometry()).getSegmentCount()); + assertTrue(polygon.getSegmentCount(0) == ((Polygon) mPolygonWGS84MP + .getGeometry()).getSegmentCount(0)); + assertTrue(polygon.getSegmentCount(1) + 1 == ((Polygon) mPolygonWGS84MP + .getGeometry()).getSegmentCount(1)); + + int lastIndex = polygon.getPointCount() - 1; + assertTrue(polygon.getPoint(lastIndex).getX() == ((Polygon) mPolygonWGS84MP + .getGeometry()).getPoint(lastIndex).getX()); + assertTrue(polygon.getPoint(lastIndex).getY() == ((Polygon) mPolygonWGS84MP + .getGeometry()).getPoint(lastIndex).getY()); + + assertTrue(spatialReferenceWGS84.getID() == mPolygonWGS84MP + .getSpatialReference().getID()); + + Polygon emptyPolygon = new Polygon(); + String emptyPolygonString = GeometryEngine.geometryToJson( + spatialReferenceWGS84, emptyPolygon); + polygonPathsWgs84Parser = factory + .createJsonParser(emptyPolygonString); + mPolygonWGS84MP = GeometryEngine + .jsonToGeometry(polygonPathsWgs84Parser); + + assertTrue(mPolygonWGS84MP.getGeometry().isEmpty()); + assertTrue(spatialReferenceWGS84.getID() == mPolygonWGS84MP + .getSpatialReference().getID()); + } + } + + @Test + public void testEnvelope() throws JsonParseException, IOException { + Envelope envelope = new Envelope(); + envelope.setCoords(-109.55, 25.76, -86.39, 49.94); + + { + JsonParser envelopeWGS84Parser = factory + .createJsonParser(GeometryEngine.geometryToJson( + spatialReferenceWGS84, envelope)); + MapGeometry envelopeWGS84MP = GeometryEngine + .jsonToGeometry(envelopeWGS84Parser); + assertTrue(envelope.isEmpty() == envelopeWGS84MP.getGeometry() + .isEmpty()); + assertTrue(envelope.getXMax() == ((Envelope) envelopeWGS84MP + .getGeometry()).getXMax()); + assertTrue(envelope.getYMax() == ((Envelope) envelopeWGS84MP + .getGeometry()).getYMax()); + assertTrue(envelope.getXMin() == ((Envelope) envelopeWGS84MP + .getGeometry()).getXMin()); + assertTrue(envelope.getYMin() == ((Envelope) envelopeWGS84MP + .getGeometry()).getYMin()); + assertTrue(spatialReferenceWGS84.getID() == envelopeWGS84MP + .getSpatialReference().getID()); + + Envelope emptyEnvelope = new Envelope(); + String emptyEnvString = GeometryEngine.geometryToJson( + spatialReferenceWGS84, emptyEnvelope); + envelopeWGS84Parser = factory.createJsonParser(emptyEnvString); + envelopeWGS84MP = GeometryEngine + .jsonToGeometry(envelopeWGS84Parser); + + assertTrue(envelopeWGS84MP.getGeometry().isEmpty()); + assertTrue(spatialReferenceWGS84.getID() == envelopeWGS84MP + .getSpatialReference().getID()); + } + } + + @Test + public void testCR181369() throws JsonParseException, IOException { + // CR181369 + { + String jsonStringPointAndWKT = "{\"x\":10.0,\"y\":20.0,\"spatialReference\":{\"wkt\" : \"PROJCS[\\\"NAD83_UTM_zone_15N\\\",GEOGCS[\\\"GCS_North_American_1983\\\",DATUM[\\\"D_North_American_1983\\\",SPHEROID[\\\"GRS_1980\\\",6378137.0,298.257222101]],PRIMEM[\\\"Greenwich\\\",0.0],UNIT[\\\"Degree\\\",0.0174532925199433]],PROJECTION[\\\"Transverse_Mercator\\\"],PARAMETER[\\\"false_easting\\\",500000.0],PARAMETER[\\\"false_northing\\\",0.0],PARAMETER[\\\"central_meridian\\\",-93.0],PARAMETER[\\\"scale_factor\\\",0.9996],PARAMETER[\\\"latitude_of_origin\\\",0.0],UNIT[\\\"Meter\\\",1.0]]\"} }"; + JsonParser jsonParserPointAndWKT = factory + .createJsonParser(jsonStringPointAndWKT); + MapGeometry mapGeom2 = GeometryEngine + .jsonToGeometry(jsonParserPointAndWKT); + String jsonStringPointAndWKT2 = GeometryEngine.geometryToJson( + mapGeom2.getSpatialReference(), mapGeom2.getGeometry()); + JsonParser jsonParserPointAndWKT2 = factory + .createJsonParser(jsonStringPointAndWKT2); + MapGeometry mapGeom3 = GeometryEngine + .jsonToGeometry(jsonParserPointAndWKT2); + assertTrue(((Point) mapGeom2.getGeometry()).getX() == ((Point) mapGeom3 + .getGeometry()).getX()); + assertTrue(((Point) mapGeom2.getGeometry()).getY() == ((Point) mapGeom3 + .getGeometry()).getY()); + assertTrue(mapGeom2.getSpatialReference().getText() + .equals(mapGeom3.getSpatialReference().getText())); + assertTrue(mapGeom2.getSpatialReference().getID() == mapGeom3 + .getSpatialReference().getID()); + } + } + + @Test + public void testSpatialRef() throws JsonParseException, IOException { + // String jsonStringPt = + // "{\"x\":-20037508.342787,\"y\":20037508.342787},\"spatialReference\":{\"wkid\":102100}}"; + String jsonStringPt = "{\"x\":10.0,\"y\":20.0,\"spatialReference\":{\"wkid\": 102100}}";// 102100 + @SuppressWarnings("unused") + String jsonStringPt2 = "{\"x\":10.0,\"y\":20.0,\"spatialReference\":{\"wkid\":4326}}"; + String jsonStringMpt = "{ \"points\" : [ [-97.06138,32.837], [-97.06133,32.836], [-97.06124,32.834], [-97.06127,32.832] ], \"spatialReference\" : {\"wkid\" : 4326}}";// 4326 + String jsonStringMpt3D = "{\"hasZs\" : true,\"points\" : [ [-97.06138,32.837,35.0], [-97.06133,32.836,35.1], [-97.06124,32.834,35.2], [-97.06127,32.832,35.3] ],\"spatialReference\" : {\"wkid\" : 4326}}"; + String jsonStringPl = "{\"paths\" : [ [ [-97.06138,32.837], [-97.06133,32.836], [-97.06124,32.834], [-97.06127,32.832] ], [ [-97.06326,32.759], [-97.06298,32.755] ]],\"spatialReference\" : {\"wkid\" : 4326}}"; + String jsonStringPl3D = "{\"hasMs\" : true,\"paths\" : [[ [-97.06138,32.837,5], [-97.06133,32.836,6], [-97.06124,32.834,7], [-97.06127,32.832,8] ],[ [-97.06326,32.759], [-97.06298,32.755] ]],\"spatialReference\" : {\"wkid\" : 4326}}"; + String jsonStringPg = "{ \"rings\" :[ [ [-97.06138,32.837], [-97.06133,32.836], [-97.06124,32.834], [-97.06127,32.832], [-97.06138,32.837] ], [ [-97.06326,32.759], [-97.06298,32.755], [-97.06153,32.749], [-97.06326,32.759] ]], \"spatialReference\" : {\"wkt\" : \"\"}}"; + String jsonStringPg3D = "{\"hasZs\" : true,\"hasMs\" : true,\"rings\" : [ [ [-97.06138, 32.837, 35.1, 4], [-97.06133, 32.836, 35.2, 4.1], [-97.06124, 32.834, 35.3, 4.2], [-97.06127, 32.832, 35.2, 44.3], [-97.06138, 32.837, 35.1, 4] ],[ [-97.06326, 32.759, 35.4], [-97.06298, 32.755, 35.5], [-97.06153, 32.749, 35.6], [-97.06326, 32.759, 35.4] ]],\"spatialReference\" : {\"wkid\" : 4326}}"; + String jsonStringPg2 = "{ \"spatialReference\" : {\"wkid\" : 4326}, \"rings\" : [[[-118.35,32.81],[-118.42,32.806],[-118.511,32.892],[-118.35,32.81]]]}"; + String jsonStringPg3 = "{ \"spatialReference\": {\"layerName\":\"GAS_POINTS\",\"name\":null,\"sdesrid\":102100,\"wkid\":102100,\"wkt\":null}}"; + String jsonString2SpatialReferences = "{ \"spatialReference\": {\"layerName\":\"GAS_POINTS\",\"name\":null,\"sdesrid\":102100,\"wkid\":102100,\"wkt\":\"GEOGCS[\\\"GCS_WGS_1984\\\",DATUM[\\\"D_WGS_1984\\\",SPHEROID[\\\"WGS_1984\\\",6378137,298.257223563]],PRIMEM[\\\"Greenwich\\\",0],UNIT[\\\"Degree\\\",0.017453292519943295]]\"}}"; + String jsonString2SpatialReferences2 = "{ \"spatialReference\": {\"layerName\":\"GAS_POINTS\",\"name\":null,\"sdesrid\":10,\"wkid\":10,\"wkt\":\"GEOGCS[\\\"GCS_WGS_1984\\\",DATUM[\\\"D_WGS_1984\\\",SPHEROID[\\\"WGS_1984\\\",6378137,298.257223563]],PRIMEM[\\\"Greenwich\\\",0],UNIT[\\\"Degree\\\",0.017453292519943295]]\"}}"; + String jsonStringSR = "{\"wkid\" : 4326}"; + String jsonStringEnv = "{\"xmin\" : -109.55, \"ymin\" : 25.76, \"xmax\" : -86.39, \"ymax\" : 49.94,\"spatialReference\" : {\"wkid\" : 4326}}"; + String jsonStringHongKon = "{\"xmin\" : -122.55, \"ymin\" : 37.65, \"xmax\" : -122.28, \"ymax\" : 37.84,\"spatialReference\" : {\"wkid\" : 4326}}"; + @SuppressWarnings("unused") + String jsonStringWKT = " {\"wkt\" : \"GEOGCS[\\\"GCS_WGS_1984\\\",DATUM[\\\"D_WGS_1984\\\",SPHEROID[\\\"WGS_1984\\\",6378137,298.257223563]],PRIMEM[\\\"Greenwich\\\",0],UNIT[\\\"Degree\\\",0.017453292519943295]]\"}"; + String jsonStringInvalidWKID = "{\"x\":10.0,\"y\":20.0},\"spatialReference\":{\"wkid\":35253523}}"; + String jsonStringOregon = "{\"xmin\":7531831.219849482,\"ymin\":585702.9799639136,\"xmax\":7750143.589982405,\"ymax\":733289.6299999952,\"spatialReference\":{\"wkid\":102726}}"; + + JsonParser jsonParserPt = factory.createJsonParser(jsonStringPt); + JsonParser jsonParserMpt = factory.createJsonParser(jsonStringMpt); + JsonParser jsonParserMpt3D = factory.createJsonParser(jsonStringMpt3D); + JsonParser jsonParserPl = factory.createJsonParser(jsonStringPl); + JsonParser jsonParserPl3D = factory.createJsonParser(jsonStringPl3D); + JsonParser jsonParserPg = factory.createJsonParser(jsonStringPg); + JsonParser jsonParserPg3D = factory.createJsonParser(jsonStringPg3D); + JsonParser jsonParserPg2 = factory.createJsonParser(jsonStringPg2); + @SuppressWarnings("unused") + JsonParser jsonParserSR = factory.createJsonParser(jsonStringSR); + JsonParser jsonParserEnv = factory.createJsonParser(jsonStringEnv); + JsonParser jsonParserPg3 = factory.createJsonParser(jsonStringPg3); + @SuppressWarnings("unused") + JsonParser jsonParserCrazy1 = factory + .createJsonParser(jsonString2SpatialReferences); + @SuppressWarnings("unused") + JsonParser jsonParserCrazy2 = factory + .createJsonParser(jsonString2SpatialReferences2); + JsonParser jsonParserInvalidWKID = factory + .createJsonParser(jsonStringInvalidWKID); + @SuppressWarnings("unused") + JsonParser jsonParseHongKon = factory + .createJsonParser(jsonStringHongKon); + JsonParser jsonParseOregon = factory.createJsonParser(jsonStringOregon); + + MapGeometry mapGeom = GeometryEngine.jsonToGeometry(jsonParserPt); + // showProjectedGeometryInfo(mapGeom); + Assert.assertTrue(mapGeom.getSpatialReference().getID() == 102100); + + MapGeometry mapGeomOregon = GeometryEngine + .jsonToGeometry(jsonParseOregon); + Assert.assertTrue(mapGeomOregon.getSpatialReference().getID() == 102726); + + mapGeom = GeometryEngine.jsonToGeometry(jsonParserMpt); + Assert.assertTrue(mapGeom.getSpatialReference().getID() == 4326); + + mapGeom = GeometryEngine.jsonToGeometry(jsonParserMpt3D); + Assert.assertTrue(mapGeom.getSpatialReference().getID() == 4326); + { + Assert.assertTrue(((MultiPoint) mapGeom.getGeometry()).getPoint(0) + .getX() == -97.06138); + Assert.assertTrue(((MultiPoint) mapGeom.getGeometry()).getPoint(0) + .getY() == 32.837); + Assert.assertTrue(((MultiPoint) mapGeom.getGeometry()).getPoint(3) + .getX() == -97.06127); + Assert.assertTrue(((MultiPoint) mapGeom.getGeometry()).getPoint(3) + .getY() == 32.832); + } + // showProjectedGeometryInfo(mapGeom); + + mapGeom = GeometryEngine.jsonToGeometry(jsonParserPl); + Assert.assertTrue(mapGeom.getSpatialReference().getID() == 4326); + // showProjectedGeometryInfo(mapGeom); + + mapGeom = GeometryEngine.jsonToGeometry(jsonParserPl3D); + { + // [[ [-97.06138,32.837,5], [-97.06133,32.836,6], + // [-97.06124,32.834,7], [-97.06127,32.832,8] ], + // [ [-97.06326,32.759], [-97.06298,32.755] ]]"; + Assert.assertTrue(((Polyline) mapGeom.getGeometry()).getPoint(0) + .getX() == -97.06138); + Assert.assertTrue(((Polyline) mapGeom.getGeometry()).getPoint(0) + .getY() == 32.837); + int lastIndex = ((Polyline) mapGeom.getGeometry()).getPointCount() - 1; + Assert.assertTrue(((Polyline) mapGeom.getGeometry()).getPoint( + lastIndex).getX() == -97.06298);// -97.06153, 32.749 + Assert.assertTrue(((Polyline) mapGeom.getGeometry()).getPoint( + lastIndex).getY() == 32.755); + int lastIndexFirstLine = ((Polyline) mapGeom.getGeometry()) + .getPathEnd(0) - 1; + Assert.assertTrue(((Polyline) mapGeom.getGeometry()).getPoint( + lastIndexFirstLine).getX() == -97.06127);// -97.06153, + // 32.749 + Assert.assertTrue(((Polyline) mapGeom.getGeometry()).getPoint( + lastIndexFirstLine).getY() == 32.832); + } + + mapGeom = GeometryEngine.jsonToGeometry(jsonParserPg); + Assert.assertTrue(mapGeom.getSpatialReference() == null); + + mapGeom = GeometryEngine.jsonToGeometry(jsonParserPg3D); + { + Assert.assertTrue(((Polygon) mapGeom.getGeometry()).getPoint(0) + .getX() == -97.06138); + Assert.assertTrue(((Polygon) mapGeom.getGeometry()).getPoint(0) + .getY() == 32.837); + int lastIndex = ((Polygon) mapGeom.getGeometry()).getPointCount() - 1; + Assert.assertTrue(((Polygon) mapGeom.getGeometry()).getPoint( + lastIndex).getX() == -97.06153);// -97.06153, 32.749 + Assert.assertTrue(((Polygon) mapGeom.getGeometry()).getPoint( + lastIndex).getY() == 32.749); + } + + mapGeom = GeometryEngine.jsonToGeometry(jsonParserPg2); + Assert.assertTrue(mapGeom.getSpatialReference().getID() == 4326); + // showProjectedGeometryInfo(mapGeom); + + mapGeom = GeometryEngine.jsonToGeometry(jsonParserPg3); + Assert.assertTrue(mapGeom.getSpatialReference().getID() == 102100); + // showProjectedGeometryInfo(mapGeom); + + // mapGeom = GeometryEngine.jsonToGeometry(jsonParserCrazy1); + // Assert.assertTrue(mapGeom.getSpatialReference().getText().equals("")); + // showProjectedGeometryInfo(mapGeom); + + mapGeom = GeometryEngine.jsonToGeometry(jsonParserEnv); + Assert.assertTrue(mapGeom.getSpatialReference().getID() == 4326); + // showProjectedGeometryInfo(mapGeom); + // System.out.println("\n\nWKID: "+ + // SpatialReference.create(wkid).fromJson(jsonParserSR).getID()); + + try { + GeometryEngine.jsonToGeometry(jsonParserInvalidWKID); + } catch (Exception ex) { + System.out.print(ex.getMessage()); + } + } + + @Test + public void testMP2onCR175871() throws Exception { + Polygon pg = new Polygon(); + pg.startPath(-50, 10); + pg.lineTo(-50, 12); + pg.lineTo(-45, 12); + pg.lineTo(-45, 10); + + Polygon pg1 = new Polygon(); + pg1.startPath(-45, 10); + pg1.lineTo(-40, 10); + pg1.lineTo(-40, 8); + pg.add(pg1, false); + + SpatialReference spatialReference = SpatialReference.create(4326); + + try { + String jSonStr = GeometryEngine + .geometryToJson(spatialReference, pg); + JsonFactory jf = new JsonFactory(); + + JsonParser jp = jf.createJsonParser(jSonStr); + jp.nextToken(); + MapGeometry mg = GeometryEngine.jsonToGeometry(jp); + Geometry gm = mg.getGeometry(); + Assert.assertEquals(Geometry.Type.Polygon, gm.getType()); + Assert.assertTrue(mg.getSpatialReference().getID() == 4326); + + Polygon pgNew = (Polygon) gm; + + Assert.assertEquals(pgNew.getPathCount(), pg.getPathCount()); + Assert.assertEquals(pgNew.getPointCount(), pg.getPointCount()); + Assert.assertEquals(pgNew.getSegmentCount(), pg.getSegmentCount()); + + Assert.assertEquals(pgNew.getPoint(0).getX(), + pg.getPoint(0).getX(), 0.000000001); + Assert.assertEquals(pgNew.getPoint(1).getX(), + pg.getPoint(1).getX(), 0.000000001); + Assert.assertEquals(pgNew.getPoint(2).getX(), + pg.getPoint(2).getX(), 0.000000001); + Assert.assertEquals(pgNew.getPoint(3).getX(), + pg.getPoint(3).getX(), 0.000000001); + + Assert.assertEquals(pgNew.getPoint(0).getY(), + pg.getPoint(0).getY(), 0.000000001); + Assert.assertEquals(pgNew.getPoint(1).getY(), + pg.getPoint(1).getY(), 0.000000001); + Assert.assertEquals(pgNew.getPoint(2).getY(), + pg.getPoint(2).getY(), 0.000000001); + Assert.assertEquals(pgNew.getPoint(3).getY(), + pg.getPoint(3).getY(), 0.000000001); + } catch (Exception ex) { + String err = ex.getMessage(); + System.out.print(err); + throw ex; + } + } + + @Test + public static int fromJsonToWkid(JsonParser parser) + throws JsonParseException, IOException { + int wkid = 0; + if (parser.getCurrentToken() != JsonToken.START_OBJECT) { + return 0; + } + + while (parser.nextToken() != JsonToken.END_OBJECT) { + String fieldName = parser.getCurrentName(); + + if ("wkid".equals(fieldName)) { + parser.nextToken(); + wkid = parser.getIntValue(); + } + } + return wkid; + } + + @SuppressWarnings("unused") + private static void showProjectedGeometryInfo(MapGeometry mapGeom) { + System.out.println("\n"); + MapGeometry geom = mapGeom; + // while ((geom = geomCursor.next()) != null) { + + if (geom.getGeometry() instanceof Point) { + Point pnt = (Point) geom.getGeometry(); + System.out + .println("Point(" + pnt.getX() + " , " + pnt.getY() + ")"); + if (geom.getSpatialReference() == null) + System.out.println("No spatial reference"); + else + System.out.println("wkid: " + + geom.getSpatialReference().getID()); + + } else if (geom.getGeometry() instanceof MultiPoint) { + MultiPoint mp = (MultiPoint) geom.getGeometry(); + System.out.println("Multipoint has " + mp.getPointCount() + + " points."); + + System.out.println("wkid: " + geom.getSpatialReference().getID()); + + } else if (geom.getGeometry() instanceof Polygon) { + Polygon mp = (Polygon) geom.getGeometry(); + System.out.println("Polygon has " + mp.getPointCount() + + " points and " + mp.getPathCount() + " parts."); + if (mp.getPathCount() > 1) { + System.out.println("Part start of 2nd segment : " + + mp.getPathStart(1)); + System.out.println("Part end of 2nd segment : " + + mp.getPathEnd(1)); + System.out.println("Part size of 2nd segment : " + + mp.getPathSize(1)); + + int start = mp.getPathStart(1); + int end = mp.getPathEnd(1); + for (int i = start; i < end; i++) { + Point pp = mp.getPoint(i); + System.out.println("Point(" + i + ") = (" + pp.getX() + + ", " + pp.getY() + ")"); + } + } + System.out.println("wkid: " + geom.getSpatialReference().getID()); + + } else if (geom.getGeometry() instanceof Polyline) { + Polyline mp = (Polyline) geom.getGeometry(); + System.out.println("Polyline has " + mp.getPointCount() + + " points and " + mp.getPathCount() + " parts."); + System.out.println("Part start of 2nd segment : " + + mp.getPathStart(1)); + System.out.println("Part end of 2nd segment : " + + mp.getPathEnd(1)); + System.out.println("Part size of 2nd segment : " + + mp.getPathSize(1)); + int start = mp.getPathStart(1); + int end = mp.getPathEnd(1); + for (int i = start; i < end; i++) { + Point pp = mp.getPoint(i); + System.out.println("Point(" + i + ") = (" + pp.getX() + ", " + + pp.getY() + ")"); + } + + System.out.println("wkid: " + geom.getSpatialReference().getID()); + } + } + + @Test + public void testGeometryToJSON() { + Polygon geom = new Polygon(); + geom.startPath(new Point(-113, 34)); + geom.lineTo(new Point(-105, 34)); + geom.lineTo(new Point(-108, 40)); + + String outputPolygon1 = GeometryEngine.geometryToJson(-1, geom);// Test + // WKID + // == -1 + System.out.println("Geom JSON STRING is" + outputPolygon1); + String correctPolygon1 = "{\"rings\":[[[-113.0,34.0],[-105.0,34.0],[-108.0,40.0],[-113.0,34.0]]]}"; + + assertEquals(correctPolygon1, outputPolygon1); + + String outputPolygon2 = GeometryEngine.geometryToJson(4326, geom); + System.out.println("Geom JSON STRING is" + outputPolygon2); + + String correctPolygon2 = "{\"rings\":[[[-113.0,34.0],[-105.0,34.0],[-108.0,40.0],[-113.0,34.0]]],\"spatialReference\":{\"wkid\":4326}}"; + assertEquals(correctPolygon2, outputPolygon2); + } + + @Test + public void testGeometryToJSONOldID() throws Exception {// CR + Polygon geom = new Polygon(); + geom.startPath(new Point(-113, 34)); + geom.lineTo(new Point(-105, 34)); + geom.lineTo(new Point(-108, 40)); + String outputPolygon = GeometryEngine.geometryToJson( + SpatialReference.create(3857), geom);// Test WKID == -1 + String correctPolygon = "{\"rings\":[[[-113.0,34.0],[-105.0,34.0],[-108.0,40.0],[-113.0,34.0]]],\"spatialReference\":{\"wkid\":102100,\"latestWkid\":3857}}"; + assertTrue(outputPolygon.equals(correctPolygon)); + JsonFactory jf = new JsonFactory(); + JsonParser jp = jf.createJsonParser(outputPolygon); + jp.nextToken(); + MapGeometry mg = GeometryEngine.jsonToGeometry(jp); + @SuppressWarnings("unused") + int srId = mg.getSpatialReference().getID(); + @SuppressWarnings("unused") + int srOldId = mg.getSpatialReference().getOldID(); + Assert.assertTrue(mg.getSpatialReference().getID() == 3857); + Assert.assertTrue(mg.getSpatialReference().getLatestID() == 3857); + Assert.assertTrue(mg.getSpatialReference().getOldID() == 102100); + } +} diff --git a/unittest/com/esri/core/geometry/TestSerialization.java b/unittest/com/esri/core/geometry/TestSerialization.java index 03f62b07..27587c34 100644 --- a/unittest/com/esri/core/geometry/TestSerialization.java +++ b/unittest/com/esri/core/geometry/TestSerialization.java @@ -1,294 +1,294 @@ -package com.esri.core.geometry; - -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.InputStream; -import java.io.ObjectInputStream; -import java.io.ObjectOutputStream; -import junit.framework.TestCase; -import org.junit.Test; - -public class TestSerialization extends TestCase { - @Override - protected void setUp() throws Exception { - super.setUp(); - } - - @Override - protected void tearDown() throws Exception { - super.tearDown(); - } - - @Test - public void testSerializePoint() { - try { - ByteArrayOutputStream streamOut = new ByteArrayOutputStream(); - ObjectOutputStream oo = new ObjectOutputStream(streamOut); - Point pt = new Point(10, 40); - oo.writeObject(pt); - ByteArrayInputStream streamIn = new ByteArrayInputStream( - streamOut.toByteArray()); - ObjectInputStream ii = new ObjectInputStream(streamIn); - Point ptRes = (Point) ii.readObject(); - assertTrue(ptRes.equals(pt)); - } catch (Exception ex) { - fail("Point serialization failure"); - - } - - // try - // { - // FileOutputStream streamOut = new FileOutputStream(m_thisDirectory + - // "savedPoint.txt"); - // ObjectOutputStream oo = new ObjectOutputStream(streamOut); - // Point pt = new Point(10, 40); - // oo.writeObject(pt); - // } - // catch(Exception ex) - // { - // fail("Point serialization failure"); - // } - - try { - InputStream s = TestSerialization.class - .getResourceAsStream("savedPoint.txt"); - ObjectInputStream ii = new ObjectInputStream(s); - Point ptRes = (Point) ii.readObject(); - assertTrue(ptRes.getX() == 10 && ptRes.getY() == 40); - } catch (Exception ex) { - fail("Point serialization failure"); - } - } - - @Test - public void testSerializePolygon() { - try { - ByteArrayOutputStream streamOut = new ByteArrayOutputStream(); - ObjectOutputStream oo = new ObjectOutputStream(streamOut); - Polygon pt = new Polygon(); - pt.startPath(10, 10); - pt.lineTo(100, 100); - pt.lineTo(200, 100); - oo.writeObject(pt); - ByteArrayInputStream streamIn = new ByteArrayInputStream( - streamOut.toByteArray()); - ObjectInputStream ii = new ObjectInputStream(streamIn); - Polygon ptRes = (Polygon) ii.readObject(); - assertTrue(ptRes.equals(pt)); - } catch (Exception ex) { - fail("Polygon serialization failure"); - } - - try { - ByteArrayOutputStream streamOut = new ByteArrayOutputStream(); - ObjectOutputStream oo = new ObjectOutputStream(streamOut); - Polygon pt = new Polygon(); - pt.startPath(10, 10); - pt.lineTo(100, 100); - pt.lineTo(200, 100); - pt = (Polygon) GeometryEngine.simplify(pt, null); - oo.writeObject(pt); - ByteArrayInputStream streamIn = new ByteArrayInputStream( - streamOut.toByteArray()); - ObjectInputStream ii = new ObjectInputStream(streamIn); - Polygon ptRes = (Polygon) ii.readObject(); - assertTrue(ptRes.equals(pt)); - } catch (Exception ex) { - fail("Polygon serialization failure"); - } - - // try - // { - // FileOutputStream streamOut = new FileOutputStream(m_thisDirectory + - // "savedPolygon.txt"); - // ObjectOutputStream oo = new ObjectOutputStream(streamOut); - // Polygon pt = new Polygon(); - // pt.startPath(10, 10); - // pt.lineTo(100, 100); - // pt.lineTo(200, 100); - // pt = (Polygon)GeometryEngine.simplify(pt, null); - // oo.writeObject(pt); - // } - // catch(Exception ex) - // { - // fail("Polygon serialization failure"); - // } - - try { - InputStream s = TestSerialization.class - .getResourceAsStream("savedPolygon.txt"); - ObjectInputStream ii = new ObjectInputStream(s); - Polygon ptRes = (Polygon) ii.readObject(); - assertTrue(ptRes != null); - } catch (Exception ex) { - fail("Polygon serialization failure"); - } - } - - @Test - public void testSerializePolyline() { - try { - ByteArrayOutputStream streamOut = new ByteArrayOutputStream(); - ObjectOutputStream oo = new ObjectOutputStream(streamOut); - Polyline pt = new Polyline(); - pt.startPath(10, 10); - pt.lineTo(100, 100); - pt.lineTo(200, 100); - oo.writeObject(pt); - ByteArrayInputStream streamIn = new ByteArrayInputStream( - streamOut.toByteArray()); - ObjectInputStream ii = new ObjectInputStream(streamIn); - Polyline ptRes = (Polyline) ii.readObject(); - assertTrue(ptRes.equals(pt)); - } catch (Exception ex) { - fail("Polyline serialization failure"); - } - - // try - // { - // FileOutputStream streamOut = new FileOutputStream(m_thisDirectory + - // "savedPolyline.txt"); - // ObjectOutputStream oo = new ObjectOutputStream(streamOut); - // Polyline pt = new Polyline(); - // pt.startPath(10, 10); - // pt.lineTo(100, 100); - // pt.lineTo(200, 100); - // oo.writeObject(pt); - // } - // catch(Exception ex) - // { - // fail("Polyline serialization failure"); - // } - - try { - InputStream s = TestSerialization.class - .getResourceAsStream("savedPolyline.txt"); - ObjectInputStream ii = new ObjectInputStream(s); - Polyline ptRes = (Polyline) ii.readObject(); - assertTrue(ptRes != null); - } catch (Exception ex) { - fail("Polyline serialization failure"); - } - } - - @Test - public void testSerializeEnvelope() { - try { - ByteArrayOutputStream streamOut = new ByteArrayOutputStream(); - ObjectOutputStream oo = new ObjectOutputStream(streamOut); - Envelope pt = new Envelope(10, 10, 400, 300); - oo.writeObject(pt); - ByteArrayInputStream streamIn = new ByteArrayInputStream( - streamOut.toByteArray()); - ObjectInputStream ii = new ObjectInputStream(streamIn); - Envelope ptRes = (Envelope) ii.readObject(); - assertTrue(ptRes.equals(pt)); - } catch (Exception ex) { - fail("Envelope serialization failure"); - } - - // try - // { - // FileOutputStream streamOut = new FileOutputStream(m_thisDirectory + - // "savedEnvelope.txt"); - // ObjectOutputStream oo = new ObjectOutputStream(streamOut); - // Envelope pt = new Envelope(10, 10, 400, 300); - // oo.writeObject(pt); - // } - // catch(Exception ex) - // { - // fail("Envelope serialization failure"); - // } - - try { - InputStream s = TestSerialization.class - .getResourceAsStream("savedEnvelope.txt"); - ObjectInputStream ii = new ObjectInputStream(s); - Envelope ptRes = (Envelope) ii.readObject(); - assertTrue(ptRes.getXMax() == 400); - } catch (Exception ex) { - fail("Envelope serialization failure"); - } - } - - @Test - public void testSerializeMultiPoint() { - try { - ByteArrayOutputStream streamOut = new ByteArrayOutputStream(); - ObjectOutputStream oo = new ObjectOutputStream(streamOut); - MultiPoint pt = new MultiPoint(); - pt.add(10, 30); - pt.add(120, 40); - oo.writeObject(pt); - ByteArrayInputStream streamIn = new ByteArrayInputStream( - streamOut.toByteArray()); - ObjectInputStream ii = new ObjectInputStream(streamIn); - MultiPoint ptRes = (MultiPoint) ii.readObject(); - assertTrue(ptRes.equals(pt)); - } catch (Exception ex) { - fail("MultiPoint serialization failure"); - } - - // try - // { - // FileOutputStream streamOut = new FileOutputStream(m_thisDirectory + - // "savedMultiPoint.txt"); - // ObjectOutputStream oo = new ObjectOutputStream(streamOut); - // MultiPoint pt = new MultiPoint(); - // pt.add(10, 30); - // pt.add(120, 40); - // oo.writeObject(pt); - // } - // catch(Exception ex) - // { - // fail("MultiPoint serialization failure"); - // } - - try { - InputStream s = TestSerialization.class - .getResourceAsStream("savedMultiPoint.txt"); - ObjectInputStream ii = new ObjectInputStream(s); - MultiPoint ptRes = (MultiPoint) ii.readObject(); - assertTrue(ptRes.getPoint(1).getY() == 40); - } catch (Exception ex) { - fail("MultiPoint serialization failure"); - } - } - - @Test - public void testSerializeLine() { - try { - ByteArrayOutputStream streamOut = new ByteArrayOutputStream(); - ObjectOutputStream oo = new ObjectOutputStream(streamOut); - Line pt = new Line(); - pt.setStart(new Point(10, 30)); - pt.setEnd(new Point(120, 40)); - oo.writeObject(pt); - ByteArrayInputStream streamIn = new ByteArrayInputStream( - streamOut.toByteArray()); - ObjectInputStream ii = new ObjectInputStream(streamIn); - Line ptRes = (Line) ii.readObject(); - assertTrue(ptRes.equals(pt)); - } catch (Exception ex) { - // fail("Line serialization failure"); - assertEquals(ex.getMessage(), "Cannot serialize this geometry"); - } - } - - @Test - public void testSerializeSR() { - try { - ByteArrayOutputStream streamOut = new ByteArrayOutputStream(); - ObjectOutputStream oo = new ObjectOutputStream(streamOut); - SpatialReference sr = SpatialReference.create(102100); - oo.writeObject(sr); - ByteArrayInputStream streamIn = new ByteArrayInputStream( - streamOut.toByteArray()); - ObjectInputStream ii = new ObjectInputStream(streamIn); - SpatialReference ptRes = (SpatialReference) ii.readObject(); - assertTrue(ptRes.equals(sr)); - } catch (Exception ex) { - fail("Spatial Reference serialization failure"); - } - } -} +package com.esri.core.geometry; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.InputStream; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import junit.framework.TestCase; +import org.junit.Test; + +public class TestSerialization extends TestCase { + @Override + protected void setUp() throws Exception { + super.setUp(); + } + + @Override + protected void tearDown() throws Exception { + super.tearDown(); + } + + @Test + public void testSerializePoint() { + try { + ByteArrayOutputStream streamOut = new ByteArrayOutputStream(); + ObjectOutputStream oo = new ObjectOutputStream(streamOut); + Point pt = new Point(10, 40); + oo.writeObject(pt); + ByteArrayInputStream streamIn = new ByteArrayInputStream( + streamOut.toByteArray()); + ObjectInputStream ii = new ObjectInputStream(streamIn); + Point ptRes = (Point) ii.readObject(); + assertTrue(ptRes.equals(pt)); + } catch (Exception ex) { + fail("Point serialization failure"); + + } + + // try + // { + // FileOutputStream streamOut = new FileOutputStream(m_thisDirectory + + // "savedPoint.txt"); + // ObjectOutputStream oo = new ObjectOutputStream(streamOut); + // Point pt = new Point(10, 40); + // oo.writeObject(pt); + // } + // catch(Exception ex) + // { + // fail("Point serialization failure"); + // } + + try { + InputStream s = TestSerialization.class + .getResourceAsStream("savedPoint.txt"); + ObjectInputStream ii = new ObjectInputStream(s); + Point ptRes = (Point) ii.readObject(); + assertTrue(ptRes.getX() == 10 && ptRes.getY() == 40); + } catch (Exception ex) { + fail("Point serialization failure"); + } + } + + @Test + public void testSerializePolygon() { + try { + ByteArrayOutputStream streamOut = new ByteArrayOutputStream(); + ObjectOutputStream oo = new ObjectOutputStream(streamOut); + Polygon pt = new Polygon(); + pt.startPath(10, 10); + pt.lineTo(100, 100); + pt.lineTo(200, 100); + oo.writeObject(pt); + ByteArrayInputStream streamIn = new ByteArrayInputStream( + streamOut.toByteArray()); + ObjectInputStream ii = new ObjectInputStream(streamIn); + Polygon ptRes = (Polygon) ii.readObject(); + assertTrue(ptRes.equals(pt)); + } catch (Exception ex) { + fail("Polygon serialization failure"); + } + + try { + ByteArrayOutputStream streamOut = new ByteArrayOutputStream(); + ObjectOutputStream oo = new ObjectOutputStream(streamOut); + Polygon pt = new Polygon(); + pt.startPath(10, 10); + pt.lineTo(100, 100); + pt.lineTo(200, 100); + pt = (Polygon) GeometryEngine.simplify(pt, null); + oo.writeObject(pt); + ByteArrayInputStream streamIn = new ByteArrayInputStream( + streamOut.toByteArray()); + ObjectInputStream ii = new ObjectInputStream(streamIn); + Polygon ptRes = (Polygon) ii.readObject(); + assertTrue(ptRes.equals(pt)); + } catch (Exception ex) { + fail("Polygon serialization failure"); + } + + // try + // { + // FileOutputStream streamOut = new FileOutputStream(m_thisDirectory + + // "savedPolygon.txt"); + // ObjectOutputStream oo = new ObjectOutputStream(streamOut); + // Polygon pt = new Polygon(); + // pt.startPath(10, 10); + // pt.lineTo(100, 100); + // pt.lineTo(200, 100); + // pt = (Polygon)GeometryEngine.simplify(pt, null); + // oo.writeObject(pt); + // } + // catch(Exception ex) + // { + // fail("Polygon serialization failure"); + // } + + try { + InputStream s = TestSerialization.class + .getResourceAsStream("savedPolygon.txt"); + ObjectInputStream ii = new ObjectInputStream(s); + Polygon ptRes = (Polygon) ii.readObject(); + assertTrue(ptRes != null); + } catch (Exception ex) { + fail("Polygon serialization failure"); + } + } + + @Test + public void testSerializePolyline() { + try { + ByteArrayOutputStream streamOut = new ByteArrayOutputStream(); + ObjectOutputStream oo = new ObjectOutputStream(streamOut); + Polyline pt = new Polyline(); + pt.startPath(10, 10); + pt.lineTo(100, 100); + pt.lineTo(200, 100); + oo.writeObject(pt); + ByteArrayInputStream streamIn = new ByteArrayInputStream( + streamOut.toByteArray()); + ObjectInputStream ii = new ObjectInputStream(streamIn); + Polyline ptRes = (Polyline) ii.readObject(); + assertTrue(ptRes.equals(pt)); + } catch (Exception ex) { + fail("Polyline serialization failure"); + } + + // try + // { + // FileOutputStream streamOut = new FileOutputStream(m_thisDirectory + + // "savedPolyline.txt"); + // ObjectOutputStream oo = new ObjectOutputStream(streamOut); + // Polyline pt = new Polyline(); + // pt.startPath(10, 10); + // pt.lineTo(100, 100); + // pt.lineTo(200, 100); + // oo.writeObject(pt); + // } + // catch(Exception ex) + // { + // fail("Polyline serialization failure"); + // } + + try { + InputStream s = TestSerialization.class + .getResourceAsStream("savedPolyline.txt"); + ObjectInputStream ii = new ObjectInputStream(s); + Polyline ptRes = (Polyline) ii.readObject(); + assertTrue(ptRes != null); + } catch (Exception ex) { + fail("Polyline serialization failure"); + } + } + + @Test + public void testSerializeEnvelope() { + try { + ByteArrayOutputStream streamOut = new ByteArrayOutputStream(); + ObjectOutputStream oo = new ObjectOutputStream(streamOut); + Envelope pt = new Envelope(10, 10, 400, 300); + oo.writeObject(pt); + ByteArrayInputStream streamIn = new ByteArrayInputStream( + streamOut.toByteArray()); + ObjectInputStream ii = new ObjectInputStream(streamIn); + Envelope ptRes = (Envelope) ii.readObject(); + assertTrue(ptRes.equals(pt)); + } catch (Exception ex) { + fail("Envelope serialization failure"); + } + + // try + // { + // FileOutputStream streamOut = new FileOutputStream(m_thisDirectory + + // "savedEnvelope.txt"); + // ObjectOutputStream oo = new ObjectOutputStream(streamOut); + // Envelope pt = new Envelope(10, 10, 400, 300); + // oo.writeObject(pt); + // } + // catch(Exception ex) + // { + // fail("Envelope serialization failure"); + // } + + try { + InputStream s = TestSerialization.class + .getResourceAsStream("savedEnvelope.txt"); + ObjectInputStream ii = new ObjectInputStream(s); + Envelope ptRes = (Envelope) ii.readObject(); + assertTrue(ptRes.getXMax() == 400); + } catch (Exception ex) { + fail("Envelope serialization failure"); + } + } + + @Test + public void testSerializeMultiPoint() { + try { + ByteArrayOutputStream streamOut = new ByteArrayOutputStream(); + ObjectOutputStream oo = new ObjectOutputStream(streamOut); + MultiPoint pt = new MultiPoint(); + pt.add(10, 30); + pt.add(120, 40); + oo.writeObject(pt); + ByteArrayInputStream streamIn = new ByteArrayInputStream( + streamOut.toByteArray()); + ObjectInputStream ii = new ObjectInputStream(streamIn); + MultiPoint ptRes = (MultiPoint) ii.readObject(); + assertTrue(ptRes.equals(pt)); + } catch (Exception ex) { + fail("MultiPoint serialization failure"); + } + + // try + // { + // FileOutputStream streamOut = new FileOutputStream(m_thisDirectory + + // "savedMultiPoint.txt"); + // ObjectOutputStream oo = new ObjectOutputStream(streamOut); + // MultiPoint pt = new MultiPoint(); + // pt.add(10, 30); + // pt.add(120, 40); + // oo.writeObject(pt); + // } + // catch(Exception ex) + // { + // fail("MultiPoint serialization failure"); + // } + + try { + InputStream s = TestSerialization.class + .getResourceAsStream("savedMultiPoint.txt"); + ObjectInputStream ii = new ObjectInputStream(s); + MultiPoint ptRes = (MultiPoint) ii.readObject(); + assertTrue(ptRes.getPoint(1).getY() == 40); + } catch (Exception ex) { + fail("MultiPoint serialization failure"); + } + } + + @Test + public void testSerializeLine() { + try { + ByteArrayOutputStream streamOut = new ByteArrayOutputStream(); + ObjectOutputStream oo = new ObjectOutputStream(streamOut); + Line pt = new Line(); + pt.setStart(new Point(10, 30)); + pt.setEnd(new Point(120, 40)); + oo.writeObject(pt); + ByteArrayInputStream streamIn = new ByteArrayInputStream( + streamOut.toByteArray()); + ObjectInputStream ii = new ObjectInputStream(streamIn); + Line ptRes = (Line) ii.readObject(); + assertTrue(ptRes.equals(pt)); + } catch (Exception ex) { + // fail("Line serialization failure"); + assertEquals(ex.getMessage(), "Cannot serialize this geometry"); + } + } + + @Test + public void testSerializeSR() { + try { + ByteArrayOutputStream streamOut = new ByteArrayOutputStream(); + ObjectOutputStream oo = new ObjectOutputStream(streamOut); + SpatialReference sr = SpatialReference.create(102100); + oo.writeObject(sr); + ByteArrayInputStream streamIn = new ByteArrayInputStream( + streamOut.toByteArray()); + ObjectInputStream ii = new ObjectInputStream(streamIn); + SpatialReference ptRes = (SpatialReference) ii.readObject(); + assertTrue(ptRes.equals(sr)); + } catch (Exception ex) { + fail("Spatial Reference serialization failure"); + } + } + } diff --git a/unittest/com/esri/core/geometry/TestSimplify.java b/unittest/com/esri/core/geometry/TestSimplify.java index 58baf388..774fc3dd 100644 --- a/unittest/com/esri/core/geometry/TestSimplify.java +++ b/unittest/com/esri/core/geometry/TestSimplify.java @@ -1,1328 +1,1328 @@ -package com.esri.core.geometry; - -//import java.io.FileOutputStream; -//import java.io.PrintStream; -//import java.util.ArrayList; -//import java.util.List; -//import java.util.Random; -import java.io.IOException; - -import junit.framework.TestCase; - -import org.codehaus.jackson.JsonFactory; -import org.junit.Test; - -public class TestSimplify extends TestCase { - OperatorFactoryLocal factory = null; - OperatorSimplify simplifyOp = null; - OperatorSimplifyOGC simplifyOpOGC = null; - SpatialReference sr102100 = null; - SpatialReference sr4326 = null; - SpatialReference sr3857 = null; - - @Override - protected void setUp() throws Exception { - super.setUp(); - factory = OperatorFactoryLocal.getInstance(); - simplifyOp = (OperatorSimplify) factory - .getOperator(Operator.Type.Simplify); - simplifyOpOGC = (OperatorSimplifyOGC) factory - .getOperator(Operator.Type.SimplifyOGC); - sr102100 = SpatialReference.create(102100); - sr3857 = SpatialReference.create(3857);// PE_PCS_WGS_1984_WEB_MERCATOR_AUXSPHERE); - sr4326 = SpatialReference.create(4326);// enum_value2(SpatialReference, - // Code, GCS_WGS_1984)); - } - - @Override - protected void tearDown() throws Exception { - super.tearDown(); - } - - public Polygon makeNonSimplePolygon2() { - Polygon poly = new Polygon(); - poly.startPath(0, 0); - poly.lineTo(0, 15); - poly.lineTo(15, 15); - poly.lineTo(15, 0); - - // This is an interior ring but it is clockwise - poly.startPath(5, 5); - poly.lineTo(5, 6); - poly.lineTo(6, 6); - poly.lineTo(6, 5); - - return poly; - }// done - - /* - * ------------>---------------->--------------- | | | (1) | | | | --->--- - * ------->------- | | | | | (5) | | | | | | --<-- | | | | (2) | | | | | | | - * | | | | (4) | | | | | | | -->-- | | --<-- | ---<--- | | | | | | - * -------<------- | | (3) | -------------<---------------<--------------- - * -->-- - */ - - // Bowtie case with vertices at intersection - - public Polygon makeNonSimplePolygon5() { - Polygon poly = new Polygon(); - poly.startPath(10, 0); - poly.lineTo(0, 0); - poly.lineTo(5, 5); - poly.lineTo(10, 10); - poly.lineTo(0, 10); - poly.lineTo(5, 5); - - return poly; - }// done - - @Test - public void test0() { - Polygon poly1 = new Polygon(); - poly1.addEnvelope(new Envelope(10, 10, 40, 20), false); - Polygon poly2 = (Polygon) simplifyOp.execute(poly1, null, false, null); - boolean res = simplifyOp.isSimpleAsFeature(poly2, null, true, null, - null); - assertTrue(res); - // assertTrue(poly1.equals(poly2)); - }// done - - @Test - public void test0Poly() {// simple - Polygon poly1 = new Polygon(); - poly1.addEnvelope(new Envelope(10, 10, 40, 20), false); - poly1.addEnvelope(new Envelope(50, 10, 100, 20), false); - Polygon poly2 = (Polygon) simplifyOp.execute(poly1, null, false, null); - boolean res = simplifyOp.isSimpleAsFeature(poly2, null, true, null, - null); - assertTrue(res); - // assertTrue(poly1.equals(poly2)); - }// done - - @Test - public void test0Polygon_Spike1() {// non-simple (spike) - Polygon poly1 = new Polygon(); - poly1.startPath(10, 10); - poly1.lineTo(10, 20); - poly1.lineTo(40, 20); - poly1.lineTo(40, 10); - poly1.lineTo(60, 10); - poly1.lineTo(70, 10); - - boolean res = simplifyOp.isSimpleAsFeature(poly1, null, true, null, - null); - assertTrue(!res); - Polygon poly2 = (Polygon) simplifyOp.execute(poly1, null, false, null); - res = simplifyOp.isSimpleAsFeature(poly2, null, true, null, null); - assertTrue(res); - assertTrue(poly2.getPointCount() == 4); - }// done - - @Test - public void test0Polygon_Spike2() {// non-simple (spikes) - Polygon poly1 = new Polygon(); - // rectangle with a spike - poly1.startPath(10, 10); - poly1.lineTo(10, 20); - poly1.lineTo(40, 20); - poly1.lineTo(40, 10); - poly1.lineTo(60, 10); - poly1.lineTo(70, 10); - - // degenerate - poly1.startPath(100, 100); - poly1.lineTo(100, 120); - poly1.lineTo(100, 130); - - boolean res = simplifyOp.isSimpleAsFeature(poly1, null, true, null, - null); - assertTrue(!res); - Polygon poly2 = (Polygon) simplifyOp.execute(poly1, null, false, null); - res = simplifyOp.isSimpleAsFeature(poly2, null, true, null, null); - assertTrue(res); - assertTrue(poly2.getPointCount() == 4); - }// done - - @Test - public void test0Polygon_Spike3() {// non-simple (spikes) - Polygon poly1 = new Polygon(); - // degenerate - poly1.startPath(100, 100); - poly1.lineTo(100, 120); - poly1.lineTo(100, 130); - - boolean res = simplifyOp.isSimpleAsFeature(poly1, null, true, null, - null); - assertTrue(!res); - Polygon poly2 = (Polygon) simplifyOp.execute(poly1, null, false, null); - res = simplifyOp.isSimpleAsFeature(poly2, null, true, null, null); - assertTrue(res); - assertTrue(poly2.isEmpty()); - }// done - - @Test - public void test0PolygonSelfIntersect1() {// non-simple (self-intersection) - Polygon poly1 = new Polygon(); - // touch uncracked - poly1.startPath(0, 0); - poly1.lineTo(0, 100); - poly1.lineTo(100, 100); - poly1.lineTo(0, 50); - poly1.lineTo(100, 0); - - boolean res = simplifyOp.isSimpleAsFeature(poly1, null, true, null, - null); - assertTrue(!res); - Polygon poly2 = (Polygon) simplifyOp.execute(poly1, null, false, null); - res = simplifyOp.isSimpleAsFeature(poly2, null, true, null, null); - assertTrue(res); - assertTrue(!poly2.isEmpty()); - }// done - - @Test - public void test0PolygonSelfIntersect2() {// non-simple (self-intersection) - Polygon poly1 = new Polygon(); - poly1.startPath(0, 0); - poly1.lineTo(0, 100); - poly1.lineTo(100, 100); - poly1.lineTo(-100, 0); - // poly1.lineTo(100, 0); - - boolean res = simplifyOp.isSimpleAsFeature(poly1, null, true, null, - null); - assertTrue(!res); - Polygon poly2 = (Polygon) simplifyOp.execute(poly1, null, false, null); - res = simplifyOp.isSimpleAsFeature(poly2, null, true, null, null); - assertTrue(res); - assertTrue(!poly2.isEmpty()); - }// done - - @Test - public void test0PolygonSelfIntersect3() { - Polygon poly = new Polygon(); - poly.startPath(0, 0); - poly.lineTo(0, 15); - poly.lineTo(15, 15); - poly.lineTo(15, 0); - - // This part intersects with the first part - poly.startPath(10, 10); - poly.lineTo(10, 20); - poly.lineTo(20, 20); - poly.lineTo(20, 10); - - boolean res = simplifyOp - .isSimpleAsFeature(poly, null, true, null, null); - assertTrue(!res); - Polygon poly2 = (Polygon) simplifyOp.execute(poly, null, false, null); - res = simplifyOp.isSimpleAsFeature(poly2, null, true, null, null); - assertTrue(res); - assertTrue(!poly2.isEmpty()); - }// done - - @Test - public void test0PolygonInteriorRing1() { - Polygon poly = new Polygon(); - poly.startPath(0, 0); - poly.lineTo(0, 15); - poly.lineTo(15, 15); - poly.lineTo(15, 0); - - // This is an interior ring but it is clockwise - poly.startPath(5, 5); - poly.lineTo(5, 6); - poly.lineTo(6, 6); - poly.lineTo(6, 5); - - boolean res = simplifyOp - .isSimpleAsFeature(poly, null, true, null, null); - assertTrue(!res); - Polygon poly2 = (Polygon) simplifyOp.execute(poly, null, false, null); - res = simplifyOp.isSimpleAsFeature(poly2, null, true, null, null); - assertTrue(res); - assertTrue(!poly2.isEmpty()); - }// done - - @Test - public void test0PolygonInteriorRing2() { - Polygon poly = new Polygon(); - poly.startPath(0, 0); - poly.lineTo(0, 15); - poly.lineTo(15, 15); - poly.lineTo(15, 0); - - // This is an interior ring but it is clockwise - poly.startPath(5, 5); - poly.lineTo(5, 6); - poly.lineTo(6, 6); - poly.lineTo(6, 5); - - // This part intersects with the first part - poly.startPath(10, 10); - poly.lineTo(10, 20); - poly.lineTo(20, 20); - poly.lineTo(20, 10); - - boolean res = simplifyOp - .isSimpleAsFeature(poly, null, true, null, null); - assertTrue(!res); - Polygon poly2 = (Polygon) simplifyOp.execute(poly, null, false, null); - res = simplifyOp.isSimpleAsFeature(poly2, null, true, null, null); - assertTrue(res); - assertTrue(!poly2.isEmpty()); - }// done - - @Test - public void test0PolygonInteriorRingWithCommonBoundary1() { - // Two rings have common boundary - Polygon poly = new Polygon(); - poly.startPath(0, 0); - poly.lineTo(0, 10); - poly.lineTo(10, 10); - poly.lineTo(10, 0); - - poly.startPath(10, 0); - poly.lineTo(10, 10); - poly.lineTo(20, 10); - poly.lineTo(20, 0); - - boolean res = simplifyOp - .isSimpleAsFeature(poly, null, true, null, null); - assertTrue(!res); - Polygon poly2 = (Polygon) simplifyOp.execute(poly, null, false, null); - res = simplifyOp.isSimpleAsFeature(poly2, null, true, null, null); - assertTrue(res); - assertTrue(!poly2.isEmpty()); - }// done - - @Test - public void test0PolygonInteriorRingWithCommonBoundary2() { - // Two rings have common boundary - Polygon poly = new Polygon(); - poly.startPath(0, 0); - poly.lineTo(0, 10); - poly.lineTo(10, 10); - poly.lineTo(10, 0); - - poly.startPath(10, 5); - poly.lineTo(10, 6); - poly.lineTo(20, 6); - poly.lineTo(20, 5); - - boolean res = simplifyOp - .isSimpleAsFeature(poly, null, true, null, null); - assertTrue(!res); - Polygon poly2 = (Polygon) simplifyOp.execute(poly, null, false, null); - res = simplifyOp.isSimpleAsFeature(poly2, null, true, null, null); - assertTrue(res); - assertTrue(!poly2.isEmpty()); - }// done - - @Test - public void testPolygon() { - Polygon nonSimplePolygon = makeNonSimplePolygon(); - Polygon simplePolygon = (Polygon) simplifyOp.execute(nonSimplePolygon, - sr3857, false, null); - - boolean res = simplifyOp.isSimpleAsFeature(simplePolygon, sr3857, true, - null, null); - assertTrue(res); - - @SuppressWarnings("unused") - int partCount = simplePolygon.getPathCount(); - // assertTrue(partCount == 2); - - double area = simplePolygon.calculateRingArea2D(0); - assertTrue(Math.abs(area - 300) <= 0.0001); - - area = simplePolygon.calculateRingArea2D(1); - assertTrue(Math.abs(area - (-25.0)) <= 0.0001); - }// done - - @Test - public void testPolygon2() { - Polygon nonSimplePolygon2 = makeNonSimplePolygon2(); - double area = nonSimplePolygon2.calculateRingArea2D(1); - assertTrue(Math.abs(area - 1.0) <= 0.0001); - - Polygon simplePolygon2 = (Polygon) simplifyOp.execute( - nonSimplePolygon2, sr3857, false, null); - - boolean res = simplifyOp.isSimpleAsFeature(simplePolygon2, sr3857, - true, null, null); - assertTrue(res); - - area = simplePolygon2.calculateRingArea2D(0); - assertTrue(Math.abs(area - 225) <= 0.0001); - - area = simplePolygon2.calculateRingArea2D(1); - assertTrue(Math.abs(area - (-1.0)) <= 0.0001); - }// done - - @Test - public void testPolygon3() { - Polygon nonSimplePolygon3 = makeNonSimplePolygon3(); - Polygon simplePolygon3 = (Polygon) simplifyOp.execute( - nonSimplePolygon3, sr3857, false, null); - - boolean res = simplifyOp.isSimpleAsFeature(simplePolygon3, sr3857, - true, null, null); - assertTrue(res); - - double area = simplePolygon3.calculateRingArea2D(0); - assertTrue(Math.abs(area - 875) <= 0.0001); - - area = simplePolygon3.calculateRingArea2D(1); - assertTrue(Math.abs(area - (-225)) <= 0.0001 - || Math.abs(area - (-50.0)) <= 0.0001); - - area = simplePolygon3.calculateRingArea2D(2); - assertTrue(Math.abs(area - (-225)) <= 0.0001 - || Math.abs(area - (-50.0)) <= 0.0001); - - area = simplePolygon3.calculateRingArea2D(3); - assertTrue(Math.abs(area - 25) <= 0.0001); - - area = simplePolygon3.calculateRingArea2D(4); - assertTrue(Math.abs(area - 25) <= 0.0001); - }// done - - @Test - public void testPolyline() { - Polyline nonSimplePolyline = makeNonSimplePolyline(); - Polyline simplePolyline = (Polyline) simplifyOp.execute( - nonSimplePolyline, sr3857, false, null); - - int segmentCount = simplePolyline.getSegmentCount(); - assertTrue(segmentCount == 4); - }// done - - @Test - public void testPolygon4() { - Polygon nonSimplePolygon4 = makeNonSimplePolygon4(); - Polygon simplePolygon4 = (Polygon) simplifyOp.execute( - nonSimplePolygon4, sr3857, false, null); - boolean res = simplifyOp.isSimpleAsFeature(simplePolygon4, sr3857, - true, null, null); - assertTrue(res); - - assertTrue(simplePolygon4.getPointCount() == 5); - Point point = nonSimplePolygon4.getPoint(0); - assertTrue(point.getX() == 0.0 && point.getY() == 0.0); - point = nonSimplePolygon4.getPoint(1); - assertTrue(point.getX() == 0.0 && point.getY() == 10.0); - point = nonSimplePolygon4.getPoint(2); - assertTrue(point.getX() == 10.0 && point.getY() == 10.0); - point = nonSimplePolygon4.getPoint(3); - assertTrue(point.getX() == 10.0 && point.getY() == 0.0); - point = nonSimplePolygon4.getPoint(4); - assertTrue(point.getX() == 5.0 && point.getY() == 0.0); - }// done - - @Test - public void testPolygon5() { - Polygon nonSimplePolygon5 = makeNonSimplePolygon5(); - Polygon simplePolygon5 = (Polygon) simplifyOp.execute( - nonSimplePolygon5, sr3857, false, null); - assertTrue(simplePolygon5 != null); - - boolean res = simplifyOp.isSimpleAsFeature(simplePolygon5, sr3857, - true, null, null); - assertTrue(res); - - // FIXME Bowtie. once simplify is fixed this should result in a - // simplified geom - - int pointCount = simplePolygon5.getPointCount(); - assertTrue(pointCount == 6); - - double area = simplePolygon5.calculateArea2D(); - assertTrue(Math.abs(area - 50.0) <= 0.001); - - }// done - - @Test - public void testPolygon6() { - Polygon nonSimplePolygon6 = makeNonSimplePolygon6(); - Polygon simplePolygon6 = (Polygon) simplifyOp.execute( - nonSimplePolygon6, sr3857, false, null); - - boolean res = simplifyOp.isSimpleAsFeature(simplePolygon6, sr3857, - true, null, null); - assertTrue(res); - } - - @Test - public void testPolygon7() { - Polygon nonSimplePolygon7 = makeNonSimplePolygon7(); - Polygon simplePolygon7 = (Polygon) simplifyOp.execute( - nonSimplePolygon7, sr3857, false, null); - - boolean res = simplifyOp.isSimpleAsFeature(simplePolygon7, sr3857, - true, null, null); - assertTrue(res); - } - - public Polygon makeNonSimplePolygon() { - Polygon poly = new Polygon(); - poly.startPath(0, 0); - poly.lineTo(0, 15); - poly.lineTo(15, 15); - poly.lineTo(15, 0); - - // This is an interior ring but it is clockwise - poly.startPath(5, 5); - poly.lineTo(5, 6); - poly.lineTo(6, 6); - poly.lineTo(6, 5); - - // This part intersects with the first part - poly.startPath(10, 10); - poly.lineTo(10, 20); - poly.lineTo(20, 20); - poly.lineTo(20, 10); - - return poly; - }// done - - /* - * ------------>---------------->--------------- | | | (1) | | | | --->--- - * ------->------- | | | | | (5) | | | | | | --<-- | | | | (2) | | | | | | | - * | | | | (4) | | | | | | | -->-- | | --<-- | ---<--- | | | | | | - * -------<------- | | (3) | -------------<---------------<--------------- - * -->-- - */ - - public Polygon makeNonSimplePolygon3() { - Polygon poly = new Polygon(); - poly.startPath(0, 0); - poly.lineTo(0, 25); - poly.lineTo(35, 25); - poly.lineTo(35, 0); - - poly.startPath(5, 5); - poly.lineTo(5, 15); - poly.lineTo(10, 15); - poly.lineTo(10, 5); - - poly.startPath(40, 0); - poly.lineTo(45, 0); - poly.lineTo(45, 5); - poly.lineTo(40, 5); - - poly.startPath(20, 10); - poly.lineTo(25, 10); - poly.lineTo(25, 15); - poly.lineTo(20, 15); - - poly.startPath(15, 5); - poly.lineTo(15, 20); - poly.lineTo(30, 20); - poly.lineTo(30, 5); - - return poly; - }// done - - public Polygon makeNonSimplePolygon4() { - Polygon poly = new Polygon(); - poly.startPath(0, 0); - poly.lineTo(0, 10); - poly.lineTo(10, 10); - poly.lineTo(10, 0); - poly.lineTo(5, 0); - poly.lineTo(5, 5); - poly.lineTo(5, 0); - - return poly; - }// done - - public Polygon makeNonSimplePolygon6() { - Polygon poly = new Polygon(); - poly.startPath(35.34407570857744, 54.00551247713412); - poly.lineTo(41.07663499357954, 20.0); - poly.lineTo(40.66372033705177, 26.217432321849017); - - poly.startPath(42.81936574509338, 20.0); - poly.lineTo(43.58226670584747, 20.0); - poly.lineTo(39.29611825817084, 22.64634933678729); - poly.lineTo(44.369873312241346, 25.81893670527215); - poly.lineTo(42.68845660737179, 20.0); - poly.lineTo(38.569549792944244, 56.47456192829393); - poly.lineTo(42.79274114188401, 45.45117792578003); - poly.lineTo(41.09512147544657, 70.0); - - return poly; - } - - public Polygon makeNonSimplePolygon7() { - Polygon poly = new Polygon(); - - poly.startPath(41.987895433319686, 53.75822619011542); - poly.lineTo(41.98789542535497, 53.75822618803151); - poly.lineTo(40.15120412113667, 68.12604154722113); - poly.lineTo(37.72272697311022, 67.92767094118877); - poly.lineTo(37.147347454283086, 49.497473094145505); - poly.lineTo(38.636627026664385, 51.036687142232736); - - poly.startPath(39.00920080789793, 62.063425518369016); - poly.lineTo(38.604912643136885, 70.0); - poly.lineTo(40.71826863485308, 43.60337143116787); - poly.lineTo(35.34407570857744, 54.005512477134126); - poly.lineTo(39.29611825817084, 22.64634933678729); - - return poly; - } - - public Polyline makeNonSimplePolyline() { - // This polyline has a short segment - Polyline poly = new Polyline(); - poly.startPath(0, 0); - poly.lineTo(10, 0); - poly.lineTo(10, 10); - poly.lineTo(10, 5); - poly.lineTo(-5, 5); - - return poly; - }// done - - @Test - public void testIsSimpleBasicsPoint() { - boolean result; - // point is always simple - Point pt = new Point(); - result = simplifyOp.isSimpleAsFeature(pt, sr4326, false, null, null); - assertTrue(result); - pt.setXY(0, 0); - result = simplifyOp.isSimpleAsFeature(pt, sr4326, false, null, null); - assertTrue(result); - pt.setXY(100000, 10000); - result = simplifyOp.isSimpleAsFeature(pt, sr4326, false, null, null); - assertTrue(result); - }// done - - @Test - public void testIsSimpleBasicsEnvelope() { - // Envelope is simple, when it's width and height are not degenerate - Envelope env = new Envelope(); - boolean result = simplifyOp.isSimpleAsFeature(env, sr4326, false, null, - null); // Empty is simple - assertTrue(result); - env.setCoords(0, 0, 10, 10); - result = simplifyOp.isSimpleAsFeature(env, sr4326, false, null, null); - assertTrue(result); - // sliver but still simple - env.setCoords(0, 0, 0 + sr4326.getTolerance() * 2, 10); - result = simplifyOp.isSimpleAsFeature(env, sr4326, false, null, null); - assertTrue(result); - // sliver and not simple - env.setCoords(0, 0, 0 + sr4326.getTolerance() * 0.5, 10); - result = simplifyOp.isSimpleAsFeature(env, sr4326, false, null, null); - assertTrue(!result); - }// done - - @Test - public void testIsSimpleBasicsLine() { - Line line = new Line(); - boolean result = simplifyOp.isSimpleAsFeature(line, sr4326, false, - null, null); - assertTrue(!result); - - line.setStart(new Point(0, 0)); - // line.setEndXY(0, 0); - result = simplifyOp.isSimpleAsFeature(line, sr4326, false, null, null); - assertTrue(!result); - line.setEnd(new Point(1, 0)); - result = simplifyOp.isSimpleAsFeature(line, sr4326, false, null, null); - assertTrue(result); - }// done - - @Test - public void testIsSimpleMultiPoint1() { - MultiPoint mp = new MultiPoint(); - boolean result = simplifyOp.isSimpleAsFeature(mp, sr4326, false, null, - null); - assertTrue(result);// empty is simple - result = simplifyOp.isSimpleAsFeature( - simplifyOp.execute(mp, sr4326, false, null), sr4326, false, - null, null); - assertTrue(result); - }// done - - @Test - public void testIsSimpleMultiPoint2FarApart() { - // Two point test: far apart - MultiPoint mp = new MultiPoint(); - mp.add(20, 10); - mp.add(100, 100); - boolean result = simplifyOp.isSimpleAsFeature(mp, sr4326, false, null, - null); - assertTrue(result); - result = simplifyOp.isSimpleAsFeature( - simplifyOp.execute(mp, sr4326, false, null), sr4326, false, - null, null); - assertTrue(result); - assertTrue(mp.getPointCount() == 2); - }// done - - @Test - public void testIsSimpleMultiPointCoincident() { - // Two point test: coincident - MultiPoint mp = new MultiPoint(); - mp.add(100, 100); - mp.add(100, 100); - boolean result = simplifyOp.isSimpleAsFeature(mp, sr4326, false, null, - null); - assertTrue(!result); - MultiPoint mpS; - result = simplifyOp.isSimpleAsFeature( - mpS = (MultiPoint) simplifyOp.execute(mp, sr4326, false, null), - sr4326, false, null, null); - assertTrue(result); - assertTrue(mpS.getPointCount() == 1); - }// done - - @Test - public void testMultiPointSR4326_CR184439() { - OperatorFactoryLocal engine = OperatorFactoryLocal.getInstance(); - OperatorSimplify simpOp = (OperatorSimplify) engine - .getOperator(Operator.Type.Simplify); - NonSimpleResult nonSimpResult = new NonSimpleResult(); - nonSimpResult.m_reason = NonSimpleResult.Reason.NotDetermined; - MultiPoint multiPoint = new MultiPoint(); - multiPoint.add(0, 0); - multiPoint.add(0, 1); - multiPoint.add(0, 0); - Boolean multiPointIsSimple = simpOp.isSimpleAsFeature(multiPoint, - SpatialReference.create(4326), true, nonSimpResult, null); - assertFalse(multiPointIsSimple); - assertTrue(nonSimpResult.m_reason == NonSimpleResult.Reason.Clustering); - assertTrue(nonSimpResult.m_vertexIndex1 == 0); - assertTrue(nonSimpResult.m_vertexIndex2 == 2); - } - - @Test - public void testIsSimpleMultiPointCloserThanTolerance() { - // Two point test: closer than tolerance - MultiPoint mp = new MultiPoint(); - MultiPoint mpS; - mp.add(100, 100); - mp.add(100, 100 + sr4326.getTolerance() * .5); - boolean result = simplifyOp.isSimpleAsFeature(mp, sr4326, false, null, - null); - assertTrue(result); - result = simplifyOp.isSimpleAsFeature( - mpS = (MultiPoint) simplifyOp.execute(mp, sr4326, false, null), - sr4326, false, null, null); - assertTrue(result); - assertTrue(mpS.getPointCount() == 2); - }// done - - @Test - public void testIsSimpleMultiPointFarApart2() { - // 5 point test: far apart - MultiPoint mp = new MultiPoint(); - mp.add(100, 100); - mp.add(100, 101); - mp.add(101, 101); - mp.add(11, 1); - mp.add(11, 14); - MultiPoint mpS; - boolean result = simplifyOp.isSimpleAsFeature(mp, sr4326, false, null, - null); - assertTrue(result); - result = simplifyOp.isSimpleAsFeature( - mpS = (MultiPoint) simplifyOp.execute(mp, sr4326, false, null), - sr4326, false, null, null); - assertTrue(result); - assertTrue(mpS.getPointCount() == 5); - }// done - - @Test - public void testIsSimpleMultiPoint_coincident2() { - // 5 point test: coincident - MultiPoint mp = new MultiPoint(); - mp.add(100, 100); - mp.add(100, 101); - mp.add(100, 100); - mp.add(11, 1); - mp.add(11, 14); - boolean result = simplifyOp.isSimpleAsFeature(mp, sr4326, false, null, - null); - assertTrue(!result); - MultiPoint mpS; - result = simplifyOp.isSimpleAsFeature( - mpS = (MultiPoint) simplifyOp.execute(mp, sr4326, false, null), - sr4326, false, null, null); - assertTrue(result); - assertTrue(mpS.getPointCount() == 4); - assertEquals(mpS.getPoint(0).getX(), 100, 1e-7); - assertEquals(mpS.getPoint(0).getY(), 100, 1e-7); - assertEquals(mpS.getPoint(1).getX(), 100, 1e-7); - assertEquals(mpS.getPoint(1).getY(), 101, 1e-7); - assertEquals(mpS.getPoint(2).getX(), 11, 1e-7); - assertEquals(mpS.getPoint(2).getY(), 1, 1e-7); - assertEquals(mpS.getPoint(3).getX(), 11, 1e-7); - assertEquals(mpS.getPoint(3).getY(), 14, 1e-7); - }// done - - @Test - public void testIsSimpleMultiPointCloserThanTolerance2() { - // 5 point test: closer than tolerance - MultiPoint mp = new MultiPoint(); - mp.add(100, 100); - mp.add(100, 101); - mp.add(100, 100 + sr4326.getTolerance() / 2); - mp.add(11, 1); - mp.add(11, 14); - MultiPoint mpS; - boolean result = simplifyOp.isSimpleAsFeature(mp, sr4326, false, null, - null); - assertTrue(result); - result = simplifyOp.isSimpleAsFeature( - mpS = (MultiPoint) simplifyOp.execute(mp, sr4326, false, null), - sr4326, false, null, null); - assertTrue(result); - assertTrue(mpS.getPointCount() == 5); - }// done - - @Test - public void testIsSimplePolyline() { - Polyline poly = new Polyline(); - boolean result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, - null, null); - assertTrue(result);// empty is simple - } - - @Test - public void testIsSimplePolylineFarApart() { - // Two point test: far apart - Polyline poly = new Polyline(); - poly.startPath(20, 10); - poly.lineTo(100, 100); - boolean result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, - null, null); - assertTrue(result); - } - - @Test - public void testIsSimplePolylineCoincident() { - // Two point test: coincident - Polyline poly = new Polyline(); - poly.startPath(100, 100); - poly.lineTo(100, 100); - boolean result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, - null, null); - assertTrue(!result); - @SuppressWarnings("unused") - Polyline polyS; - result = simplifyOp.isSimpleAsFeature( - polyS = (Polyline) simplifyOp - .execute(poly, sr4326, false, null), sr4326, false, - null, null); - assertTrue(result); - } - - @Test - public void testIsSimplePolylineCloserThanTolerance() { - // Two point test: closer than tolerance - Polyline poly = new Polyline(); - poly.startPath(100, 100); - poly.lineTo(100, 100 + sr4326.getTolerance() / 2); - boolean result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, - null, null); - assertTrue(!result); - @SuppressWarnings("unused") - Polyline polyS; - result = simplifyOp.isSimpleAsFeature( - polyS = (Polyline) simplifyOp - .execute(poly, sr4326, false, null), sr4326, false, - null, null); - assertTrue(result); - } - - @Test - public void testIsSimplePolylineFarApartSelfOverlap0() { - // 3 point test: far apart, self overlapping - Polyline poly = new Polyline(); - poly.startPath(0, 0); - poly.lineTo(100, 100); - poly.lineTo(0, 0); - boolean result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, - null, null); - assertTrue(result);// TO CONFIRM should be false - } - - @Test - public void testIsSimplePolylineSelfIntersect() { - // 4 point test: far apart, self intersecting - Polyline poly = new Polyline(); - poly.startPath(0, 0); - poly.lineTo(100, 100); - poly.lineTo(0, 100); - poly.lineTo(100, 0); - boolean result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, - null, null); - assertTrue(result);// TO CONFIRM should be false - } - - @Test - public void testIsSimplePolylineDegenerateSegment() { - // 4 point test: degenerate segment - Polyline poly = new Polyline(); - poly.startPath(0, 0); - poly.lineTo(100, 100); - poly.lineTo(100, 100 + sr4326.getTolerance() / 2); - poly.lineTo(100, 0); - boolean result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, - null, null); - assertTrue(!result); - @SuppressWarnings("unused") - Polyline polyS; - result = simplifyOp.isSimpleAsFeature( - polyS = (Polyline) simplifyOp - .execute(poly, sr4326, false, null), sr4326, false, - null, null); - assertTrue(result); - { - Polyline other = new Polyline(); - other.startPath(0, 0); - other.lineTo(100, 100); - other.lineTo(100, 0); - other.equals(poly); - } - } - - @Test - public void testIsSimplePolylineFarApartSelfOverlap() { - // 3 point test: far apart, self overlapping - Polyline poly = new Polyline(); - poly.startPath(0, 0); - poly.lineTo(100, 100); - poly.lineTo(0, 0); - boolean result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, - null, null); - assertTrue(result);// TO CONFIRM should be false - } - - @Test - public void testIsSimplePolylineFarApartIntersect() { - // 4 point 2 parts test: far apart, intersecting parts - Polyline poly = new Polyline(); - poly.startPath(0, 0); - poly.lineTo(100, 100); - poly.startPath(100, 0); - poly.lineTo(0, 100); - - boolean result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, - null, null); - assertTrue(result);// TO CONFIRM should be false - } - - @Test - public void testIsSimplePolylineFarApartOverlap2() { - // 4 point 2 parts test: far apart, overlapping parts. second part - // starts where first one ends - Polyline poly = new Polyline(); - poly.startPath(0, 0); - poly.lineTo(100, 100); - poly.startPath(100, 100); - poly.lineTo(0, 100); - - boolean result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, - null, null); - assertTrue(result);// TO CONFIRM should be false - } - - @Test - public void testIsSimplePolylineDegenerateVertical() { - // 3 point test: degenerate vertical line - Polyline poly = new Polyline(); - poly.startPath(0, 0); - poly.lineTo(new Point(100, 100)); - poly.lineTo(new Point(100, 100)); - boolean result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, - null, null); - assertTrue(!result); - Polyline polyS; - result = simplifyOp.isSimpleAsFeature( - polyS = (Polyline) simplifyOp - .execute(poly, sr4326, false, null), sr4326, false, - null, null); - assertTrue(result); - assertTrue(polyS.getPointCount() == 2); - } - - @Test - public void testIsSimplePolylineEmptyPath() { - // TODO: any way to test this? - // Empty path - // Polyline poly = new Polyline(); - // assertTrue(poly.isEmpty()); - // poly.addPath(new Polyline(), 0, true); - // assertTrue(poly.isEmpty()); - // boolean result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, - // null, null); - // assertTrue(result); - } - - @Test - public void testIsSimplePolylineSinglePointInPath() { - // Single point in path - Polyline poly = new Polyline(); - poly.startPath(0, 0); - poly.lineTo(100, 100); - poly.removePoint(0, 1); - boolean result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, - null, null); - assertTrue(!result); - Polyline polyS; - result = simplifyOp.isSimpleAsFeature( - polyS = (Polyline) simplifyOp - .execute(poly, sr4326, false, null), sr4326, false, - null, null); - assertTrue(result); - assertTrue(polyS.isEmpty()); - } - - @Test - public void testIsSimplePolygon() { - Polygon poly = new Polygon(); - boolean result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, - null, null); - assertTrue(result);// empty is simple - result = simplifyOp.isSimpleAsFeature( - simplifyOp.execute(poly, sr4326, false, null), sr4326, false, - null, null); - assertTrue(result);// empty is simple - } - - @Test - public void testIsSimplePolygonEmptyPath() { - // TODO: - // Empty path - // Polygon poly = new Polygon(); - // poly.addPath(new Polyline(), 0, true); - // assertTrue(poly.getPathCount() == 1); - // boolean result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, - // null, - // null); - // assertTrue(result); - // result = simplifyOp.isSimpleAsFeature(simplifyOp.execute(poly, - // sr4326, false, null), sr4326, false, null, null); - // assertTrue(result);// empty is simple - // assertTrue(poly.getPathCount() == 1); - } - - @Test - public void testIsSimplePolygonIncomplete1() { - // Incomplete polygon 1 - Polygon poly = new Polygon(); - poly.startPath(0, 0); - poly.lineTo(100, 100); - // poly.removePoint(0, 1);//TO CONFIRM no removePoint method in Java - boolean result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, - null, null); - assertTrue(!result); - } - - @Test - public void testIsSimplePolygonIncomplete2() { - // Incomplete polygon 2 - Polygon poly = new Polygon(); - poly.startPath(0, 0); - poly.lineTo(100, 100); - boolean result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, - null, null); - assertTrue(!result); - } - - @Test - public void testIsSimplePolygonDegenerateTriangle() { - // Degenerate triangle (self overlap) - Polygon poly = new Polygon(); - poly.startPath(0, 0); - poly.lineTo(100, 100); - poly.lineTo(0, 0); - boolean result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, - null, null); - assertTrue(!result); - } - - @Test - public void testIsSimplePolygonSelfIntersect() { - // Self intersection - cracking is needed - Polygon poly = new Polygon(); - poly.startPath(0, 0); - poly.lineTo(100, 100); - poly.lineTo(0, 100); - poly.lineTo(100, 0); - boolean result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, - null, null); - assertTrue(!result); - } - - @Test - public void testIsSimplePolygonRectangleHole() { - // Rectangle and rectangular hole that has one segment overlapping - // with the with the exterior ring. Cracking is needed. - Polygon poly = new Polygon(); - poly.addEnvelope(new Envelope(-200, -100, 200, 100), false); - poly.addEnvelope(new Envelope(-100, -100, 100, 50), true); - boolean result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, - null, null); - assertTrue(!result); - poly.reverseAllPaths(); - result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, null, null); - assertTrue(!result); - } - - @Test - public void testIsSimplePolygonRectangleHole2() { - // Rectangle and rectangular hole - Polygon poly = new Polygon(); - poly.addEnvelope(new Envelope(-200, -100, 200, 100), false); - poly.addEnvelope(new Envelope(-100, -50, 100, 50), true); - boolean result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, - null, null); - assertTrue(result); - poly.reverseAllPaths(); - result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, null, null); - assertTrue(!result); - } - - @Test - public void testIsSimplePolygonSelfIntersectAtVertex() { - // Self intersection at vertex - Polygon poly = new Polygon(); - poly.startPath(0, 0); - poly.lineTo(50, 50); - poly.lineTo(100, 100); - poly.lineTo(0, 100); - poly.lineTo(50, 50); - poly.lineTo(100, 0); - boolean result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, - null, null); - assertTrue(!result); - result = simplifyOp.isSimpleAsFeature( - simplifyOp.execute(poly, sr4326, false, null), sr4326, false, - null, null); - assertTrue(result); - } - - @Test - public void testIsSimplePolygon_2EdgesTouchAtVertex() { - // No self-intersection, but more than two edges touch at the same - // vertex. Simple for ArcGIS, not simple for OGC - Polygon poly = new Polygon(); - poly.startPath(0, 0); - poly.lineTo(50, 50); - poly.lineTo(0, 100); - poly.lineTo(100, 100); - poly.lineTo(50, 50); - poly.lineTo(100, 0); - boolean result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, - null, null); - assertTrue(result); - } - - @Test - public void testIsSimplePolygonTriangle() { - // Triangle - Polygon poly = new Polygon(); - poly.startPath(0, 0); - poly.lineTo(100, 100); - poly.lineTo(100, 0); - boolean result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, - null, null); - assertTrue(result); - poly.reverseAllPaths(); - result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, null, null); - assertTrue(!result); - } - - @Test - public void testIsSimplePolygonRectangle() { - // Rectangle - Polygon poly = new Polygon(); - poly.addEnvelope(new Envelope(-200, -100, 100, 200), false); - boolean result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, - null, null); - assertTrue(result); - poly.reverseAllPaths(); - result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, null, null); - assertTrue(!result); - } - - @Test - public void testIsSimplePolygonRectangleHoleWrongDirection() { - // Rectangle and rectangular hole that has wrong direction - Polygon poly = new Polygon(); - poly.addEnvelope(new Envelope(-200, -100, 200, 100), false); - poly.addEnvelope(new Envelope(-100, -50, 100, 50), false); - boolean result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, - null, null); - assertTrue(!result); - poly.reverseAllPaths(); - result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, null, null); - assertTrue(!result); - } - - @Test - public void testIsSimplePolygon_2RectanglesSideBySide() { - // Two rectangles side by side, simple - Polygon poly = new Polygon(); - poly.addEnvelope(new Envelope(-200, -100, 200, 100), false); - poly.addEnvelope(new Envelope(220, -50, 300, 50), false); - boolean result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, - null, null); - assertTrue(result); - poly.reverseAllPaths(); - result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, null, null); - assertTrue(!result); - } - - @Test - public void testIsSimplePolygonRectangleOneBelow() { - // Two rectangles one below another, simple - Polygon poly = new Polygon(); - poly.addEnvelope(new Envelope(50, 50, 100, 100), false); - poly.addEnvelope(new Envelope(50, 200, 100, 250), false); - boolean result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, - null, null); - assertTrue(result); - poly.reverseAllPaths(); - result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, null, null); - assertTrue(!result); - } - - @Test - public void testisSimpleOGC() { - Polyline poly = new Polyline(); - poly.startPath(0, 0); - poly.lineTo(10, 0); - boolean result = simplifyOpOGC.isSimpleOGC(poly, sr4326, true, null, - null); - assertTrue(result); - - poly = new Polyline(); - poly.startPath(0, 0); - poly.lineTo(10, 10); - poly.lineTo(0, 10); - poly.lineTo(10, 0); - NonSimpleResult nsr = new NonSimpleResult(); - result = simplifyOpOGC.isSimpleOGC(poly, sr4326, true, nsr, null); - assertTrue(!result); - assertTrue(nsr.m_reason == NonSimpleResult.Reason.Cracking); - - MultiPoint mp = new MultiPoint(); - mp.add(0, 0); - mp.add(10, 0); - result = simplifyOpOGC.isSimpleOGC(mp, sr4326, true, null, null); - assertTrue(result); - - mp = new MultiPoint(); - mp.add(10, 0); - mp.add(10, 0); - nsr = new NonSimpleResult(); - result = simplifyOpOGC.isSimpleOGC(mp, sr4326, true, nsr, null); - assertTrue(!result); - assertTrue(nsr.m_reason == NonSimpleResult.Reason.Clustering); - } - - @Test - public void testPolylineIsSimpleForOGC() throws IOException { - OperatorImportFromJson importerJson = (OperatorImportFromJson) factory - .getOperator(Operator.Type.ImportMapGeometryFromJson); - OperatorSimplify simplify = (OperatorSimplify) factory - .getOperator(Operator.Type.Simplify); - - JsonFactory f = new JsonFactory(); - - { - String s = "{\"paths\":[[[0, 10], [8, 5], [5, 2], [6, 0]]]}"; - Geometry g = importerJson.execute(Geometry.Type.Unknown, - f.createJsonParser(s)).getGeometry(); - boolean res = simplifyOpOGC.isSimpleOGC(g, null, true, null, null); - assertTrue(res); - } - { - String s = "{\"paths\":[[[0, 10], [6, 0], [7, 5], [0, 3]]]}";// self - // intersection - Geometry g = importerJson.execute(Geometry.Type.Unknown, - f.createJsonParser(s)).getGeometry(); - boolean res = simplifyOpOGC.isSimpleOGC(g, null, true, null, null); - assertTrue(!res); - } - - { - String s = "{\"paths\":[[[0, 10], [6, 0], [0, 3], [0, 10]]]}"; // closed - Geometry g = importerJson.execute(Geometry.Type.Unknown, - f.createJsonParser(s)).getGeometry(); - boolean res = simplifyOpOGC.isSimpleOGC(g, null, true, null, null); - assertTrue(res); - } - - { - String s = "{\"paths\":[[[0, 10], [5, 5], [6, 0], [0, 3], [5, 5], [0, 9], [0, 10]]]}"; // closed - // with - // self - // tangent - Geometry g = importerJson.execute(Geometry.Type.Unknown, - f.createJsonParser(s)).getGeometry(); - boolean res = simplifyOpOGC.isSimpleOGC(g, null, true, null, null); - assertTrue(!res); - } - - { - String s = "{\"paths\":[[[0, 10], [5, 2]], [[5, 2], [6, 0]]]}";// two - // paths - // connected - // at - // a - // point - Geometry g = importerJson.execute(Geometry.Type.Unknown, - f.createJsonParser(s)).getGeometry(); - boolean res = simplifyOpOGC.isSimpleOGC(g, null, true, null, null); - assertTrue(res); - } - - { - String s = "{\"paths\":[[[0, 0], [3, 3], [5, 0], [0, 0]], [[0, 10], [3, 3], [10, 10], [0, 10]]]}";// two - // closed - // rings - // touch - // at - // one - // point - Geometry g = importerJson.execute(Geometry.Type.Unknown, - f.createJsonParser(s)).getGeometry(); - boolean res = simplifyOpOGC.isSimpleOGC(g, null, true, null, null); - assertTrue(!res); - } - - { - String s = "{\"paths\":[[[0, 0], [10, 10]], [[0, 10], [10, 0]]]}";// two - // lines - // intersect - Geometry g = importerJson.execute(Geometry.Type.Unknown, - f.createJsonParser(s)).getGeometry(); - boolean res = simplifyOpOGC.isSimpleOGC(g, null, true, null, null); - assertTrue(!res); - } - - { - String s = "{\"paths\":[[[0, 0], [5, 5], [0, 10]], [[10, 10], [5, 5], [10, 0]]]}";// two - // paths - // share - // mid - // point. - Geometry g = importerJson.execute(Geometry.Type.Unknown, - f.createJsonParser(s)).getGeometry(); - boolean res = simplifyOpOGC.isSimpleOGC(g, null, true, null, null); - assertTrue(!res); - } - - } - -} +package com.esri.core.geometry; + +//import java.io.FileOutputStream; +//import java.io.PrintStream; +//import java.util.ArrayList; +//import java.util.List; +//import java.util.Random; +import java.io.IOException; + +import junit.framework.TestCase; + +import org.codehaus.jackson.JsonFactory; +import org.junit.Test; + +public class TestSimplify extends TestCase { + OperatorFactoryLocal factory = null; + OperatorSimplify simplifyOp = null; + OperatorSimplifyOGC simplifyOpOGC = null; + SpatialReference sr102100 = null; + SpatialReference sr4326 = null; + SpatialReference sr3857 = null; + + @Override + protected void setUp() throws Exception { + super.setUp(); + factory = OperatorFactoryLocal.getInstance(); + simplifyOp = (OperatorSimplify) factory + .getOperator(Operator.Type.Simplify); + simplifyOpOGC = (OperatorSimplifyOGC) factory + .getOperator(Operator.Type.SimplifyOGC); + sr102100 = SpatialReference.create(102100); + sr3857 = SpatialReference.create(3857);// PE_PCS_WGS_1984_WEB_MERCATOR_AUXSPHERE); + sr4326 = SpatialReference.create(4326);// enum_value2(SpatialReference, + // Code, GCS_WGS_1984)); + } + + @Override + protected void tearDown() throws Exception { + super.tearDown(); + } + + public Polygon makeNonSimplePolygon2() { + Polygon poly = new Polygon(); + poly.startPath(0, 0); + poly.lineTo(0, 15); + poly.lineTo(15, 15); + poly.lineTo(15, 0); + + // This is an interior ring but it is clockwise + poly.startPath(5, 5); + poly.lineTo(5, 6); + poly.lineTo(6, 6); + poly.lineTo(6, 5); + + return poly; + }// done + + /* + * ------------>---------------->--------------- | | | (1) | | | | --->--- + * ------->------- | | | | | (5) | | | | | | --<-- | | | | (2) | | | | | | | + * | | | | (4) | | | | | | | -->-- | | --<-- | ---<--- | | | | | | + * -------<------- | | (3) | -------------<---------------<--------------- + * -->-- + */ + + // Bowtie case with vertices at intersection + + public Polygon makeNonSimplePolygon5() { + Polygon poly = new Polygon(); + poly.startPath(10, 0); + poly.lineTo(0, 0); + poly.lineTo(5, 5); + poly.lineTo(10, 10); + poly.lineTo(0, 10); + poly.lineTo(5, 5); + + return poly; + }// done + + @Test + public void test0() { + Polygon poly1 = new Polygon(); + poly1.addEnvelope(new Envelope(10, 10, 40, 20), false); + Polygon poly2 = (Polygon) simplifyOp.execute(poly1, null, false, null); + boolean res = simplifyOp.isSimpleAsFeature(poly2, null, true, null, + null); + assertTrue(res); + // assertTrue(poly1.equals(poly2)); + }// done + + @Test + public void test0Poly() {// simple + Polygon poly1 = new Polygon(); + poly1.addEnvelope(new Envelope(10, 10, 40, 20), false); + poly1.addEnvelope(new Envelope(50, 10, 100, 20), false); + Polygon poly2 = (Polygon) simplifyOp.execute(poly1, null, false, null); + boolean res = simplifyOp.isSimpleAsFeature(poly2, null, true, null, + null); + assertTrue(res); + // assertTrue(poly1.equals(poly2)); + }// done + + @Test + public void test0Polygon_Spike1() {// non-simple (spike) + Polygon poly1 = new Polygon(); + poly1.startPath(10, 10); + poly1.lineTo(10, 20); + poly1.lineTo(40, 20); + poly1.lineTo(40, 10); + poly1.lineTo(60, 10); + poly1.lineTo(70, 10); + + boolean res = simplifyOp.isSimpleAsFeature(poly1, null, true, null, + null); + assertTrue(!res); + Polygon poly2 = (Polygon) simplifyOp.execute(poly1, null, false, null); + res = simplifyOp.isSimpleAsFeature(poly2, null, true, null, null); + assertTrue(res); + assertTrue(poly2.getPointCount() == 4); + }// done + + @Test + public void test0Polygon_Spike2() {// non-simple (spikes) + Polygon poly1 = new Polygon(); + // rectangle with a spike + poly1.startPath(10, 10); + poly1.lineTo(10, 20); + poly1.lineTo(40, 20); + poly1.lineTo(40, 10); + poly1.lineTo(60, 10); + poly1.lineTo(70, 10); + + // degenerate + poly1.startPath(100, 100); + poly1.lineTo(100, 120); + poly1.lineTo(100, 130); + + boolean res = simplifyOp.isSimpleAsFeature(poly1, null, true, null, + null); + assertTrue(!res); + Polygon poly2 = (Polygon) simplifyOp.execute(poly1, null, false, null); + res = simplifyOp.isSimpleAsFeature(poly2, null, true, null, null); + assertTrue(res); + assertTrue(poly2.getPointCount() == 4); + }// done + + @Test + public void test0Polygon_Spike3() {// non-simple (spikes) + Polygon poly1 = new Polygon(); + // degenerate + poly1.startPath(100, 100); + poly1.lineTo(100, 120); + poly1.lineTo(100, 130); + + boolean res = simplifyOp.isSimpleAsFeature(poly1, null, true, null, + null); + assertTrue(!res); + Polygon poly2 = (Polygon) simplifyOp.execute(poly1, null, false, null); + res = simplifyOp.isSimpleAsFeature(poly2, null, true, null, null); + assertTrue(res); + assertTrue(poly2.isEmpty()); + }// done + + @Test + public void test0PolygonSelfIntersect1() {// non-simple (self-intersection) + Polygon poly1 = new Polygon(); + // touch uncracked + poly1.startPath(0, 0); + poly1.lineTo(0, 100); + poly1.lineTo(100, 100); + poly1.lineTo(0, 50); + poly1.lineTo(100, 0); + + boolean res = simplifyOp.isSimpleAsFeature(poly1, null, true, null, + null); + assertTrue(!res); + Polygon poly2 = (Polygon) simplifyOp.execute(poly1, null, false, null); + res = simplifyOp.isSimpleAsFeature(poly2, null, true, null, null); + assertTrue(res); + assertTrue(!poly2.isEmpty()); + }// done + + @Test + public void test0PolygonSelfIntersect2() {// non-simple (self-intersection) + Polygon poly1 = new Polygon(); + poly1.startPath(0, 0); + poly1.lineTo(0, 100); + poly1.lineTo(100, 100); + poly1.lineTo(-100, 0); + // poly1.lineTo(100, 0); + + boolean res = simplifyOp.isSimpleAsFeature(poly1, null, true, null, + null); + assertTrue(!res); + Polygon poly2 = (Polygon) simplifyOp.execute(poly1, null, false, null); + res = simplifyOp.isSimpleAsFeature(poly2, null, true, null, null); + assertTrue(res); + assertTrue(!poly2.isEmpty()); + }// done + + @Test + public void test0PolygonSelfIntersect3() { + Polygon poly = new Polygon(); + poly.startPath(0, 0); + poly.lineTo(0, 15); + poly.lineTo(15, 15); + poly.lineTo(15, 0); + + // This part intersects with the first part + poly.startPath(10, 10); + poly.lineTo(10, 20); + poly.lineTo(20, 20); + poly.lineTo(20, 10); + + boolean res = simplifyOp + .isSimpleAsFeature(poly, null, true, null, null); + assertTrue(!res); + Polygon poly2 = (Polygon) simplifyOp.execute(poly, null, false, null); + res = simplifyOp.isSimpleAsFeature(poly2, null, true, null, null); + assertTrue(res); + assertTrue(!poly2.isEmpty()); + }// done + + @Test + public void test0PolygonInteriorRing1() { + Polygon poly = new Polygon(); + poly.startPath(0, 0); + poly.lineTo(0, 15); + poly.lineTo(15, 15); + poly.lineTo(15, 0); + + // This is an interior ring but it is clockwise + poly.startPath(5, 5); + poly.lineTo(5, 6); + poly.lineTo(6, 6); + poly.lineTo(6, 5); + + boolean res = simplifyOp + .isSimpleAsFeature(poly, null, true, null, null); + assertTrue(!res); + Polygon poly2 = (Polygon) simplifyOp.execute(poly, null, false, null); + res = simplifyOp.isSimpleAsFeature(poly2, null, true, null, null); + assertTrue(res); + assertTrue(!poly2.isEmpty()); + }// done + + @Test + public void test0PolygonInteriorRing2() { + Polygon poly = new Polygon(); + poly.startPath(0, 0); + poly.lineTo(0, 15); + poly.lineTo(15, 15); + poly.lineTo(15, 0); + + // This is an interior ring but it is clockwise + poly.startPath(5, 5); + poly.lineTo(5, 6); + poly.lineTo(6, 6); + poly.lineTo(6, 5); + + // This part intersects with the first part + poly.startPath(10, 10); + poly.lineTo(10, 20); + poly.lineTo(20, 20); + poly.lineTo(20, 10); + + boolean res = simplifyOp + .isSimpleAsFeature(poly, null, true, null, null); + assertTrue(!res); + Polygon poly2 = (Polygon) simplifyOp.execute(poly, null, false, null); + res = simplifyOp.isSimpleAsFeature(poly2, null, true, null, null); + assertTrue(res); + assertTrue(!poly2.isEmpty()); + }// done + + @Test + public void test0PolygonInteriorRingWithCommonBoundary1() { + // Two rings have common boundary + Polygon poly = new Polygon(); + poly.startPath(0, 0); + poly.lineTo(0, 10); + poly.lineTo(10, 10); + poly.lineTo(10, 0); + + poly.startPath(10, 0); + poly.lineTo(10, 10); + poly.lineTo(20, 10); + poly.lineTo(20, 0); + + boolean res = simplifyOp + .isSimpleAsFeature(poly, null, true, null, null); + assertTrue(!res); + Polygon poly2 = (Polygon) simplifyOp.execute(poly, null, false, null); + res = simplifyOp.isSimpleAsFeature(poly2, null, true, null, null); + assertTrue(res); + assertTrue(!poly2.isEmpty()); + }// done + + @Test + public void test0PolygonInteriorRingWithCommonBoundary2() { + // Two rings have common boundary + Polygon poly = new Polygon(); + poly.startPath(0, 0); + poly.lineTo(0, 10); + poly.lineTo(10, 10); + poly.lineTo(10, 0); + + poly.startPath(10, 5); + poly.lineTo(10, 6); + poly.lineTo(20, 6); + poly.lineTo(20, 5); + + boolean res = simplifyOp + .isSimpleAsFeature(poly, null, true, null, null); + assertTrue(!res); + Polygon poly2 = (Polygon) simplifyOp.execute(poly, null, false, null); + res = simplifyOp.isSimpleAsFeature(poly2, null, true, null, null); + assertTrue(res); + assertTrue(!poly2.isEmpty()); + }// done + + @Test + public void testPolygon() { + Polygon nonSimplePolygon = makeNonSimplePolygon(); + Polygon simplePolygon = (Polygon) simplifyOp.execute(nonSimplePolygon, + sr3857, false, null); + + boolean res = simplifyOp.isSimpleAsFeature(simplePolygon, sr3857, true, + null, null); + assertTrue(res); + + @SuppressWarnings("unused") + int partCount = simplePolygon.getPathCount(); + // assertTrue(partCount == 2); + + double area = simplePolygon.calculateRingArea2D(0); + assertTrue(Math.abs(area - 300) <= 0.0001); + + area = simplePolygon.calculateRingArea2D(1); + assertTrue(Math.abs(area - (-25.0)) <= 0.0001); + }// done + + @Test + public void testPolygon2() { + Polygon nonSimplePolygon2 = makeNonSimplePolygon2(); + double area = nonSimplePolygon2.calculateRingArea2D(1); + assertTrue(Math.abs(area - 1.0) <= 0.0001); + + Polygon simplePolygon2 = (Polygon) simplifyOp.execute( + nonSimplePolygon2, sr3857, false, null); + + boolean res = simplifyOp.isSimpleAsFeature(simplePolygon2, sr3857, + true, null, null); + assertTrue(res); + + area = simplePolygon2.calculateRingArea2D(0); + assertTrue(Math.abs(area - 225) <= 0.0001); + + area = simplePolygon2.calculateRingArea2D(1); + assertTrue(Math.abs(area - (-1.0)) <= 0.0001); + }// done + + @Test + public void testPolygon3() { + Polygon nonSimplePolygon3 = makeNonSimplePolygon3(); + Polygon simplePolygon3 = (Polygon) simplifyOp.execute( + nonSimplePolygon3, sr3857, false, null); + + boolean res = simplifyOp.isSimpleAsFeature(simplePolygon3, sr3857, + true, null, null); + assertTrue(res); + + double area = simplePolygon3.calculateRingArea2D(0); + assertTrue(Math.abs(area - 875) <= 0.0001); + + area = simplePolygon3.calculateRingArea2D(1); + assertTrue(Math.abs(area - (-225)) <= 0.0001 + || Math.abs(area - (-50.0)) <= 0.0001); + + area = simplePolygon3.calculateRingArea2D(2); + assertTrue(Math.abs(area - (-225)) <= 0.0001 + || Math.abs(area - (-50.0)) <= 0.0001); + + area = simplePolygon3.calculateRingArea2D(3); + assertTrue(Math.abs(area - 25) <= 0.0001); + + area = simplePolygon3.calculateRingArea2D(4); + assertTrue(Math.abs(area - 25) <= 0.0001); + }// done + + @Test + public void testPolyline() { + Polyline nonSimplePolyline = makeNonSimplePolyline(); + Polyline simplePolyline = (Polyline) simplifyOp.execute( + nonSimplePolyline, sr3857, false, null); + + int segmentCount = simplePolyline.getSegmentCount(); + assertTrue(segmentCount == 4); + }// done + + @Test + public void testPolygon4() { + Polygon nonSimplePolygon4 = makeNonSimplePolygon4(); + Polygon simplePolygon4 = (Polygon) simplifyOp.execute( + nonSimplePolygon4, sr3857, false, null); + boolean res = simplifyOp.isSimpleAsFeature(simplePolygon4, sr3857, + true, null, null); + assertTrue(res); + + assertTrue(simplePolygon4.getPointCount() == 5); + Point point = nonSimplePolygon4.getPoint(0); + assertTrue(point.getX() == 0.0 && point.getY() == 0.0); + point = nonSimplePolygon4.getPoint(1); + assertTrue(point.getX() == 0.0 && point.getY() == 10.0); + point = nonSimplePolygon4.getPoint(2); + assertTrue(point.getX() == 10.0 && point.getY() == 10.0); + point = nonSimplePolygon4.getPoint(3); + assertTrue(point.getX() == 10.0 && point.getY() == 0.0); + point = nonSimplePolygon4.getPoint(4); + assertTrue(point.getX() == 5.0 && point.getY() == 0.0); + }// done + + @Test + public void testPolygon5() { + Polygon nonSimplePolygon5 = makeNonSimplePolygon5(); + Polygon simplePolygon5 = (Polygon) simplifyOp.execute( + nonSimplePolygon5, sr3857, false, null); + assertTrue(simplePolygon5 != null); + + boolean res = simplifyOp.isSimpleAsFeature(simplePolygon5, sr3857, + true, null, null); + assertTrue(res); + + // FIXME Bowtie. once simplify is fixed this should result in a + // simplified geom + + int pointCount = simplePolygon5.getPointCount(); + assertTrue(pointCount == 6); + + double area = simplePolygon5.calculateArea2D(); + assertTrue(Math.abs(area - 50.0) <= 0.001); + + }// done + + @Test + public void testPolygon6() { + Polygon nonSimplePolygon6 = makeNonSimplePolygon6(); + Polygon simplePolygon6 = (Polygon) simplifyOp.execute( + nonSimplePolygon6, sr3857, false, null); + + boolean res = simplifyOp.isSimpleAsFeature(simplePolygon6, sr3857, + true, null, null); + assertTrue(res); + } + + @Test + public void testPolygon7() { + Polygon nonSimplePolygon7 = makeNonSimplePolygon7(); + Polygon simplePolygon7 = (Polygon) simplifyOp.execute( + nonSimplePolygon7, sr3857, false, null); + + boolean res = simplifyOp.isSimpleAsFeature(simplePolygon7, sr3857, + true, null, null); + assertTrue(res); + } + + public Polygon makeNonSimplePolygon() { + Polygon poly = new Polygon(); + poly.startPath(0, 0); + poly.lineTo(0, 15); + poly.lineTo(15, 15); + poly.lineTo(15, 0); + + // This is an interior ring but it is clockwise + poly.startPath(5, 5); + poly.lineTo(5, 6); + poly.lineTo(6, 6); + poly.lineTo(6, 5); + + // This part intersects with the first part + poly.startPath(10, 10); + poly.lineTo(10, 20); + poly.lineTo(20, 20); + poly.lineTo(20, 10); + + return poly; + }// done + + /* + * ------------>---------------->--------------- | | | (1) | | | | --->--- + * ------->------- | | | | | (5) | | | | | | --<-- | | | | (2) | | | | | | | + * | | | | (4) | | | | | | | -->-- | | --<-- | ---<--- | | | | | | + * -------<------- | | (3) | -------------<---------------<--------------- + * -->-- + */ + + public Polygon makeNonSimplePolygon3() { + Polygon poly = new Polygon(); + poly.startPath(0, 0); + poly.lineTo(0, 25); + poly.lineTo(35, 25); + poly.lineTo(35, 0); + + poly.startPath(5, 5); + poly.lineTo(5, 15); + poly.lineTo(10, 15); + poly.lineTo(10, 5); + + poly.startPath(40, 0); + poly.lineTo(45, 0); + poly.lineTo(45, 5); + poly.lineTo(40, 5); + + poly.startPath(20, 10); + poly.lineTo(25, 10); + poly.lineTo(25, 15); + poly.lineTo(20, 15); + + poly.startPath(15, 5); + poly.lineTo(15, 20); + poly.lineTo(30, 20); + poly.lineTo(30, 5); + + return poly; + }// done + + public Polygon makeNonSimplePolygon4() { + Polygon poly = new Polygon(); + poly.startPath(0, 0); + poly.lineTo(0, 10); + poly.lineTo(10, 10); + poly.lineTo(10, 0); + poly.lineTo(5, 0); + poly.lineTo(5, 5); + poly.lineTo(5, 0); + + return poly; + }// done + + public Polygon makeNonSimplePolygon6() { + Polygon poly = new Polygon(); + poly.startPath(35.34407570857744, 54.00551247713412); + poly.lineTo(41.07663499357954, 20.0); + poly.lineTo(40.66372033705177, 26.217432321849017); + + poly.startPath(42.81936574509338, 20.0); + poly.lineTo(43.58226670584747, 20.0); + poly.lineTo(39.29611825817084, 22.64634933678729); + poly.lineTo(44.369873312241346, 25.81893670527215); + poly.lineTo(42.68845660737179, 20.0); + poly.lineTo(38.569549792944244, 56.47456192829393); + poly.lineTo(42.79274114188401, 45.45117792578003); + poly.lineTo(41.09512147544657, 70.0); + + return poly; + } + + public Polygon makeNonSimplePolygon7() { + Polygon poly = new Polygon(); + + poly.startPath(41.987895433319686, 53.75822619011542); + poly.lineTo(41.98789542535497, 53.75822618803151); + poly.lineTo(40.15120412113667, 68.12604154722113); + poly.lineTo(37.72272697311022, 67.92767094118877); + poly.lineTo(37.147347454283086, 49.497473094145505); + poly.lineTo(38.636627026664385, 51.036687142232736); + + poly.startPath(39.00920080789793, 62.063425518369016); + poly.lineTo(38.604912643136885, 70.0); + poly.lineTo(40.71826863485308, 43.60337143116787); + poly.lineTo(35.34407570857744, 54.005512477134126); + poly.lineTo(39.29611825817084, 22.64634933678729); + + return poly; + } + + public Polyline makeNonSimplePolyline() { + // This polyline has a short segment + Polyline poly = new Polyline(); + poly.startPath(0, 0); + poly.lineTo(10, 0); + poly.lineTo(10, 10); + poly.lineTo(10, 5); + poly.lineTo(-5, 5); + + return poly; + }// done + + @Test + public void testIsSimpleBasicsPoint() { + boolean result; + // point is always simple + Point pt = new Point(); + result = simplifyOp.isSimpleAsFeature(pt, sr4326, false, null, null); + assertTrue(result); + pt.setXY(0, 0); + result = simplifyOp.isSimpleAsFeature(pt, sr4326, false, null, null); + assertTrue(result); + pt.setXY(100000, 10000); + result = simplifyOp.isSimpleAsFeature(pt, sr4326, false, null, null); + assertTrue(result); + }// done + + @Test + public void testIsSimpleBasicsEnvelope() { + // Envelope is simple, when it's width and height are not degenerate + Envelope env = new Envelope(); + boolean result = simplifyOp.isSimpleAsFeature(env, sr4326, false, null, + null); // Empty is simple + assertTrue(result); + env.setCoords(0, 0, 10, 10); + result = simplifyOp.isSimpleAsFeature(env, sr4326, false, null, null); + assertTrue(result); + // sliver but still simple + env.setCoords(0, 0, 0 + sr4326.getTolerance() * 2, 10); + result = simplifyOp.isSimpleAsFeature(env, sr4326, false, null, null); + assertTrue(result); + // sliver and not simple + env.setCoords(0, 0, 0 + sr4326.getTolerance() * 0.5, 10); + result = simplifyOp.isSimpleAsFeature(env, sr4326, false, null, null); + assertTrue(!result); + }// done + + @Test + public void testIsSimpleBasicsLine() { + Line line = new Line(); + boolean result = simplifyOp.isSimpleAsFeature(line, sr4326, false, + null, null); + assertTrue(!result); + + line.setStart(new Point(0, 0)); + // line.setEndXY(0, 0); + result = simplifyOp.isSimpleAsFeature(line, sr4326, false, null, null); + assertTrue(!result); + line.setEnd(new Point(1, 0)); + result = simplifyOp.isSimpleAsFeature(line, sr4326, false, null, null); + assertTrue(result); + }// done + + @Test + public void testIsSimpleMultiPoint1() { + MultiPoint mp = new MultiPoint(); + boolean result = simplifyOp.isSimpleAsFeature(mp, sr4326, false, null, + null); + assertTrue(result);// empty is simple + result = simplifyOp.isSimpleAsFeature( + simplifyOp.execute(mp, sr4326, false, null), sr4326, false, + null, null); + assertTrue(result); + }// done + + @Test + public void testIsSimpleMultiPoint2FarApart() { + // Two point test: far apart + MultiPoint mp = new MultiPoint(); + mp.add(20, 10); + mp.add(100, 100); + boolean result = simplifyOp.isSimpleAsFeature(mp, sr4326, false, null, + null); + assertTrue(result); + result = simplifyOp.isSimpleAsFeature( + simplifyOp.execute(mp, sr4326, false, null), sr4326, false, + null, null); + assertTrue(result); + assertTrue(mp.getPointCount() == 2); + }// done + + @Test + public void testIsSimpleMultiPointCoincident() { + // Two point test: coincident + MultiPoint mp = new MultiPoint(); + mp.add(100, 100); + mp.add(100, 100); + boolean result = simplifyOp.isSimpleAsFeature(mp, sr4326, false, null, + null); + assertTrue(!result); + MultiPoint mpS; + result = simplifyOp.isSimpleAsFeature( + mpS = (MultiPoint) simplifyOp.execute(mp, sr4326, false, null), + sr4326, false, null, null); + assertTrue(result); + assertTrue(mpS.getPointCount() == 1); + }// done + + @Test + public void testMultiPointSR4326_CR184439() { + OperatorFactoryLocal engine = OperatorFactoryLocal.getInstance(); + OperatorSimplify simpOp = (OperatorSimplify) engine + .getOperator(Operator.Type.Simplify); + NonSimpleResult nonSimpResult = new NonSimpleResult(); + nonSimpResult.m_reason = NonSimpleResult.Reason.NotDetermined; + MultiPoint multiPoint = new MultiPoint(); + multiPoint.add(0, 0); + multiPoint.add(0, 1); + multiPoint.add(0, 0); + Boolean multiPointIsSimple = simpOp.isSimpleAsFeature(multiPoint, + SpatialReference.create(4326), true, nonSimpResult, null); + assertFalse(multiPointIsSimple); + assertTrue(nonSimpResult.m_reason == NonSimpleResult.Reason.Clustering); + assertTrue(nonSimpResult.m_vertexIndex1 == 0); + assertTrue(nonSimpResult.m_vertexIndex2 == 2); + } + + @Test + public void testIsSimpleMultiPointCloserThanTolerance() { + // Two point test: closer than tolerance + MultiPoint mp = new MultiPoint(); + MultiPoint mpS; + mp.add(100, 100); + mp.add(100, 100 + sr4326.getTolerance() * .5); + boolean result = simplifyOp.isSimpleAsFeature(mp, sr4326, false, null, + null); + assertTrue(result); + result = simplifyOp.isSimpleAsFeature( + mpS = (MultiPoint) simplifyOp.execute(mp, sr4326, false, null), + sr4326, false, null, null); + assertTrue(result); + assertTrue(mpS.getPointCount() == 2); + }// done + + @Test + public void testIsSimpleMultiPointFarApart2() { + // 5 point test: far apart + MultiPoint mp = new MultiPoint(); + mp.add(100, 100); + mp.add(100, 101); + mp.add(101, 101); + mp.add(11, 1); + mp.add(11, 14); + MultiPoint mpS; + boolean result = simplifyOp.isSimpleAsFeature(mp, sr4326, false, null, + null); + assertTrue(result); + result = simplifyOp.isSimpleAsFeature( + mpS = (MultiPoint) simplifyOp.execute(mp, sr4326, false, null), + sr4326, false, null, null); + assertTrue(result); + assertTrue(mpS.getPointCount() == 5); + }// done + + @Test + public void testIsSimpleMultiPoint_coincident2() { + // 5 point test: coincident + MultiPoint mp = new MultiPoint(); + mp.add(100, 100); + mp.add(100, 101); + mp.add(100, 100); + mp.add(11, 1); + mp.add(11, 14); + boolean result = simplifyOp.isSimpleAsFeature(mp, sr4326, false, null, + null); + assertTrue(!result); + MultiPoint mpS; + result = simplifyOp.isSimpleAsFeature( + mpS = (MultiPoint) simplifyOp.execute(mp, sr4326, false, null), + sr4326, false, null, null); + assertTrue(result); + assertTrue(mpS.getPointCount() == 4); + assertEquals(mpS.getPoint(0).getX(), 100, 1e-7); + assertEquals(mpS.getPoint(0).getY(), 100, 1e-7); + assertEquals(mpS.getPoint(1).getX(), 100, 1e-7); + assertEquals(mpS.getPoint(1).getY(), 101, 1e-7); + assertEquals(mpS.getPoint(2).getX(), 11, 1e-7); + assertEquals(mpS.getPoint(2).getY(), 1, 1e-7); + assertEquals(mpS.getPoint(3).getX(), 11, 1e-7); + assertEquals(mpS.getPoint(3).getY(), 14, 1e-7); + }// done + + @Test + public void testIsSimpleMultiPointCloserThanTolerance2() { + // 5 point test: closer than tolerance + MultiPoint mp = new MultiPoint(); + mp.add(100, 100); + mp.add(100, 101); + mp.add(100, 100 + sr4326.getTolerance() / 2); + mp.add(11, 1); + mp.add(11, 14); + MultiPoint mpS; + boolean result = simplifyOp.isSimpleAsFeature(mp, sr4326, false, null, + null); + assertTrue(result); + result = simplifyOp.isSimpleAsFeature( + mpS = (MultiPoint) simplifyOp.execute(mp, sr4326, false, null), + sr4326, false, null, null); + assertTrue(result); + assertTrue(mpS.getPointCount() == 5); + }// done + + @Test + public void testIsSimplePolyline() { + Polyline poly = new Polyline(); + boolean result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, + null, null); + assertTrue(result);// empty is simple + } + + @Test + public void testIsSimplePolylineFarApart() { + // Two point test: far apart + Polyline poly = new Polyline(); + poly.startPath(20, 10); + poly.lineTo(100, 100); + boolean result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, + null, null); + assertTrue(result); + } + + @Test + public void testIsSimplePolylineCoincident() { + // Two point test: coincident + Polyline poly = new Polyline(); + poly.startPath(100, 100); + poly.lineTo(100, 100); + boolean result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, + null, null); + assertTrue(!result); + @SuppressWarnings("unused") + Polyline polyS; + result = simplifyOp.isSimpleAsFeature( + polyS = (Polyline) simplifyOp + .execute(poly, sr4326, false, null), sr4326, false, + null, null); + assertTrue(result); + } + + @Test + public void testIsSimplePolylineCloserThanTolerance() { + // Two point test: closer than tolerance + Polyline poly = new Polyline(); + poly.startPath(100, 100); + poly.lineTo(100, 100 + sr4326.getTolerance() / 2); + boolean result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, + null, null); + assertTrue(!result); + @SuppressWarnings("unused") + Polyline polyS; + result = simplifyOp.isSimpleAsFeature( + polyS = (Polyline) simplifyOp + .execute(poly, sr4326, false, null), sr4326, false, + null, null); + assertTrue(result); + } + + @Test + public void testIsSimplePolylineFarApartSelfOverlap0() { + // 3 point test: far apart, self overlapping + Polyline poly = new Polyline(); + poly.startPath(0, 0); + poly.lineTo(100, 100); + poly.lineTo(0, 0); + boolean result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, + null, null); + assertTrue(result);// TO CONFIRM should be false + } + + @Test + public void testIsSimplePolylineSelfIntersect() { + // 4 point test: far apart, self intersecting + Polyline poly = new Polyline(); + poly.startPath(0, 0); + poly.lineTo(100, 100); + poly.lineTo(0, 100); + poly.lineTo(100, 0); + boolean result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, + null, null); + assertTrue(result);// TO CONFIRM should be false + } + + @Test + public void testIsSimplePolylineDegenerateSegment() { + // 4 point test: degenerate segment + Polyline poly = new Polyline(); + poly.startPath(0, 0); + poly.lineTo(100, 100); + poly.lineTo(100, 100 + sr4326.getTolerance() / 2); + poly.lineTo(100, 0); + boolean result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, + null, null); + assertTrue(!result); + @SuppressWarnings("unused") + Polyline polyS; + result = simplifyOp.isSimpleAsFeature( + polyS = (Polyline) simplifyOp + .execute(poly, sr4326, false, null), sr4326, false, + null, null); + assertTrue(result); + { + Polyline other = new Polyline(); + other.startPath(0, 0); + other.lineTo(100, 100); + other.lineTo(100, 0); + other.equals(poly); + } + } + + @Test + public void testIsSimplePolylineFarApartSelfOverlap() { + // 3 point test: far apart, self overlapping + Polyline poly = new Polyline(); + poly.startPath(0, 0); + poly.lineTo(100, 100); + poly.lineTo(0, 0); + boolean result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, + null, null); + assertTrue(result);// TO CONFIRM should be false + } + + @Test + public void testIsSimplePolylineFarApartIntersect() { + // 4 point 2 parts test: far apart, intersecting parts + Polyline poly = new Polyline(); + poly.startPath(0, 0); + poly.lineTo(100, 100); + poly.startPath(100, 0); + poly.lineTo(0, 100); + + boolean result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, + null, null); + assertTrue(result);// TO CONFIRM should be false + } + + @Test + public void testIsSimplePolylineFarApartOverlap2() { + // 4 point 2 parts test: far apart, overlapping parts. second part + // starts where first one ends + Polyline poly = new Polyline(); + poly.startPath(0, 0); + poly.lineTo(100, 100); + poly.startPath(100, 100); + poly.lineTo(0, 100); + + boolean result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, + null, null); + assertTrue(result);// TO CONFIRM should be false + } + + @Test + public void testIsSimplePolylineDegenerateVertical() { + // 3 point test: degenerate vertical line + Polyline poly = new Polyline(); + poly.startPath(0, 0); + poly.lineTo(new Point(100, 100)); + poly.lineTo(new Point(100, 100)); + boolean result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, + null, null); + assertTrue(!result); + Polyline polyS; + result = simplifyOp.isSimpleAsFeature( + polyS = (Polyline) simplifyOp + .execute(poly, sr4326, false, null), sr4326, false, + null, null); + assertTrue(result); + assertTrue(polyS.getPointCount() == 2); + } + + @Test + public void testIsSimplePolylineEmptyPath() { + // TODO: any way to test this? + // Empty path + // Polyline poly = new Polyline(); + // assertTrue(poly.isEmpty()); + // poly.addPath(new Polyline(), 0, true); + // assertTrue(poly.isEmpty()); + // boolean result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, + // null, null); + // assertTrue(result); + } + + @Test + public void testIsSimplePolylineSinglePointInPath() { + // Single point in path + Polyline poly = new Polyline(); + poly.startPath(0, 0); + poly.lineTo(100, 100); + poly.removePoint(0, 1); + boolean result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, + null, null); + assertTrue(!result); + Polyline polyS; + result = simplifyOp.isSimpleAsFeature( + polyS = (Polyline) simplifyOp + .execute(poly, sr4326, false, null), sr4326, false, + null, null); + assertTrue(result); + assertTrue(polyS.isEmpty()); + } + + @Test + public void testIsSimplePolygon() { + Polygon poly = new Polygon(); + boolean result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, + null, null); + assertTrue(result);// empty is simple + result = simplifyOp.isSimpleAsFeature( + simplifyOp.execute(poly, sr4326, false, null), sr4326, false, + null, null); + assertTrue(result);// empty is simple + } + + @Test + public void testIsSimplePolygonEmptyPath() { + // TODO: + // Empty path + // Polygon poly = new Polygon(); + // poly.addPath(new Polyline(), 0, true); + // assertTrue(poly.getPathCount() == 1); + // boolean result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, + // null, + // null); + // assertTrue(result); + // result = simplifyOp.isSimpleAsFeature(simplifyOp.execute(poly, + // sr4326, false, null), sr4326, false, null, null); + // assertTrue(result);// empty is simple + // assertTrue(poly.getPathCount() == 1); + } + + @Test + public void testIsSimplePolygonIncomplete1() { + // Incomplete polygon 1 + Polygon poly = new Polygon(); + poly.startPath(0, 0); + poly.lineTo(100, 100); + // poly.removePoint(0, 1);//TO CONFIRM no removePoint method in Java + boolean result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, + null, null); + assertTrue(!result); + } + + @Test + public void testIsSimplePolygonIncomplete2() { + // Incomplete polygon 2 + Polygon poly = new Polygon(); + poly.startPath(0, 0); + poly.lineTo(100, 100); + boolean result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, + null, null); + assertTrue(!result); + } + + @Test + public void testIsSimplePolygonDegenerateTriangle() { + // Degenerate triangle (self overlap) + Polygon poly = new Polygon(); + poly.startPath(0, 0); + poly.lineTo(100, 100); + poly.lineTo(0, 0); + boolean result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, + null, null); + assertTrue(!result); + } + + @Test + public void testIsSimplePolygonSelfIntersect() { + // Self intersection - cracking is needed + Polygon poly = new Polygon(); + poly.startPath(0, 0); + poly.lineTo(100, 100); + poly.lineTo(0, 100); + poly.lineTo(100, 0); + boolean result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, + null, null); + assertTrue(!result); + } + + @Test + public void testIsSimplePolygonRectangleHole() { + // Rectangle and rectangular hole that has one segment overlapping + // with the with the exterior ring. Cracking is needed. + Polygon poly = new Polygon(); + poly.addEnvelope(new Envelope(-200, -100, 200, 100), false); + poly.addEnvelope(new Envelope(-100, -100, 100, 50), true); + boolean result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, + null, null); + assertTrue(!result); + poly.reverseAllPaths(); + result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, null, null); + assertTrue(!result); + } + + @Test + public void testIsSimplePolygonRectangleHole2() { + // Rectangle and rectangular hole + Polygon poly = new Polygon(); + poly.addEnvelope(new Envelope(-200, -100, 200, 100), false); + poly.addEnvelope(new Envelope(-100, -50, 100, 50), true); + boolean result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, + null, null); + assertTrue(result); + poly.reverseAllPaths(); + result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, null, null); + assertTrue(!result); + } + + @Test + public void testIsSimplePolygonSelfIntersectAtVertex() { + // Self intersection at vertex + Polygon poly = new Polygon(); + poly.startPath(0, 0); + poly.lineTo(50, 50); + poly.lineTo(100, 100); + poly.lineTo(0, 100); + poly.lineTo(50, 50); + poly.lineTo(100, 0); + boolean result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, + null, null); + assertTrue(!result); + result = simplifyOp.isSimpleAsFeature( + simplifyOp.execute(poly, sr4326, false, null), sr4326, false, + null, null); + assertTrue(result); + } + + @Test + public void testIsSimplePolygon_2EdgesTouchAtVertex() { + // No self-intersection, but more than two edges touch at the same + // vertex. Simple for ArcGIS, not simple for OGC + Polygon poly = new Polygon(); + poly.startPath(0, 0); + poly.lineTo(50, 50); + poly.lineTo(0, 100); + poly.lineTo(100, 100); + poly.lineTo(50, 50); + poly.lineTo(100, 0); + boolean result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, + null, null); + assertTrue(result); + } + + @Test + public void testIsSimplePolygonTriangle() { + // Triangle + Polygon poly = new Polygon(); + poly.startPath(0, 0); + poly.lineTo(100, 100); + poly.lineTo(100, 0); + boolean result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, + null, null); + assertTrue(result); + poly.reverseAllPaths(); + result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, null, null); + assertTrue(!result); + } + + @Test + public void testIsSimplePolygonRectangle() { + // Rectangle + Polygon poly = new Polygon(); + poly.addEnvelope(new Envelope(-200, -100, 100, 200), false); + boolean result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, + null, null); + assertTrue(result); + poly.reverseAllPaths(); + result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, null, null); + assertTrue(!result); + } + + @Test + public void testIsSimplePolygonRectangleHoleWrongDirection() { + // Rectangle and rectangular hole that has wrong direction + Polygon poly = new Polygon(); + poly.addEnvelope(new Envelope(-200, -100, 200, 100), false); + poly.addEnvelope(new Envelope(-100, -50, 100, 50), false); + boolean result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, + null, null); + assertTrue(!result); + poly.reverseAllPaths(); + result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, null, null); + assertTrue(!result); + } + + @Test + public void testIsSimplePolygon_2RectanglesSideBySide() { + // Two rectangles side by side, simple + Polygon poly = new Polygon(); + poly.addEnvelope(new Envelope(-200, -100, 200, 100), false); + poly.addEnvelope(new Envelope(220, -50, 300, 50), false); + boolean result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, + null, null); + assertTrue(result); + poly.reverseAllPaths(); + result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, null, null); + assertTrue(!result); + } + + @Test + public void testIsSimplePolygonRectangleOneBelow() { + // Two rectangles one below another, simple + Polygon poly = new Polygon(); + poly.addEnvelope(new Envelope(50, 50, 100, 100), false); + poly.addEnvelope(new Envelope(50, 200, 100, 250), false); + boolean result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, + null, null); + assertTrue(result); + poly.reverseAllPaths(); + result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, null, null); + assertTrue(!result); + } + + @Test + public void testisSimpleOGC() { + Polyline poly = new Polyline(); + poly.startPath(0, 0); + poly.lineTo(10, 0); + boolean result = simplifyOpOGC.isSimpleOGC(poly, sr4326, true, null, + null); + assertTrue(result); + + poly = new Polyline(); + poly.startPath(0, 0); + poly.lineTo(10, 10); + poly.lineTo(0, 10); + poly.lineTo(10, 0); + NonSimpleResult nsr = new NonSimpleResult(); + result = simplifyOpOGC.isSimpleOGC(poly, sr4326, true, nsr, null); + assertTrue(!result); + assertTrue(nsr.m_reason == NonSimpleResult.Reason.Cracking); + + MultiPoint mp = new MultiPoint(); + mp.add(0, 0); + mp.add(10, 0); + result = simplifyOpOGC.isSimpleOGC(mp, sr4326, true, null, null); + assertTrue(result); + + mp = new MultiPoint(); + mp.add(10, 0); + mp.add(10, 0); + nsr = new NonSimpleResult(); + result = simplifyOpOGC.isSimpleOGC(mp, sr4326, true, nsr, null); + assertTrue(!result); + assertTrue(nsr.m_reason == NonSimpleResult.Reason.Clustering); + } + + @Test + public void testPolylineIsSimpleForOGC() throws IOException { + OperatorImportFromJson importerJson = (OperatorImportFromJson) factory + .getOperator(Operator.Type.ImportMapGeometryFromJson); + OperatorSimplify simplify = (OperatorSimplify) factory + .getOperator(Operator.Type.Simplify); + + JsonFactory f = new JsonFactory(); + + { + String s = "{\"paths\":[[[0, 10], [8, 5], [5, 2], [6, 0]]]}"; + Geometry g = importerJson.execute(Geometry.Type.Unknown, + f.createJsonParser(s)).getGeometry(); + boolean res = simplifyOpOGC.isSimpleOGC(g, null, true, null, null); + assertTrue(res); + } + { + String s = "{\"paths\":[[[0, 10], [6, 0], [7, 5], [0, 3]]]}";// self + // intersection + Geometry g = importerJson.execute(Geometry.Type.Unknown, + f.createJsonParser(s)).getGeometry(); + boolean res = simplifyOpOGC.isSimpleOGC(g, null, true, null, null); + assertTrue(!res); + } + + { + String s = "{\"paths\":[[[0, 10], [6, 0], [0, 3], [0, 10]]]}"; // closed + Geometry g = importerJson.execute(Geometry.Type.Unknown, + f.createJsonParser(s)).getGeometry(); + boolean res = simplifyOpOGC.isSimpleOGC(g, null, true, null, null); + assertTrue(res); + } + + { + String s = "{\"paths\":[[[0, 10], [5, 5], [6, 0], [0, 3], [5, 5], [0, 9], [0, 10]]]}"; // closed + // with + // self + // tangent + Geometry g = importerJson.execute(Geometry.Type.Unknown, + f.createJsonParser(s)).getGeometry(); + boolean res = simplifyOpOGC.isSimpleOGC(g, null, true, null, null); + assertTrue(!res); + } + + { + String s = "{\"paths\":[[[0, 10], [5, 2]], [[5, 2], [6, 0]]]}";// two + // paths + // connected + // at + // a + // point + Geometry g = importerJson.execute(Geometry.Type.Unknown, + f.createJsonParser(s)).getGeometry(); + boolean res = simplifyOpOGC.isSimpleOGC(g, null, true, null, null); + assertTrue(res); + } + + { + String s = "{\"paths\":[[[0, 0], [3, 3], [5, 0], [0, 0]], [[0, 10], [3, 3], [10, 10], [0, 10]]]}";// two + // closed + // rings + // touch + // at + // one + // point + Geometry g = importerJson.execute(Geometry.Type.Unknown, + f.createJsonParser(s)).getGeometry(); + boolean res = simplifyOpOGC.isSimpleOGC(g, null, true, null, null); + assertTrue(!res); + } + + { + String s = "{\"paths\":[[[0, 0], [10, 10]], [[0, 10], [10, 0]]]}";// two + // lines + // intersect + Geometry g = importerJson.execute(Geometry.Type.Unknown, + f.createJsonParser(s)).getGeometry(); + boolean res = simplifyOpOGC.isSimpleOGC(g, null, true, null, null); + assertTrue(!res); + } + + { + String s = "{\"paths\":[[[0, 0], [5, 5], [0, 10]], [[10, 10], [5, 5], [10, 0]]]}";// two + // paths + // share + // mid + // point. + Geometry g = importerJson.execute(Geometry.Type.Unknown, + f.createJsonParser(s)).getGeometry(); + boolean res = simplifyOpOGC.isSimpleOGC(g, null, true, null, null); + assertTrue(!res); + } + + } + +} diff --git a/unittest/com/esri/core/geometry/TestWKBSupport.java b/unittest/com/esri/core/geometry/TestWKBSupport.java index 622030ce..656765d8 100644 --- a/unittest/com/esri/core/geometry/TestWKBSupport.java +++ b/unittest/com/esri/core/geometry/TestWKBSupport.java @@ -1,101 +1,101 @@ -package com.esri.core.geometry; - -import java.io.IOException; -import java.nio.ByteBuffer; -import org.codehaus.jackson.JsonFactory; -import org.codehaus.jackson.JsonParseException; -import org.codehaus.jackson.JsonParser; -import junit.framework.TestCase; -import org.junit.Test; - -//import com.vividsolutions.jts.io.WKBReader; - -public class TestWKBSupport extends TestCase { - @Override - protected void setUp() throws Exception { - super.setUp(); - } - - @Override - protected void tearDown() throws Exception { - super.tearDown(); - } - - @Test - public void testWKB() { - try { - // JSON -> GEOM -> WKB - - String strPolygon1 = "{\"xmin\":-1.1663479012889031E7,\"ymin\":4919777.494405342,\"xmax\":-1.1658587043078788E7,\"ymax\":4924669.464215587,\"spatialReference\":{\"wkid\":102100}}"; - // String strPolygon1 = - // "{\"rings\":[[[-119.152450421001,38.4118009590513],[-119.318825070203,38.5271086243914],[-119.575687062955,38.7029101298904],[-119.889341639399,38.9222515603984],[-119.995254694357,38.9941061536377],[-119.995150114198,39.0634913594691],[-119.994541258334,39.1061318056708],[-119.995527335641,39.1587132866355],[-119.995304181493,39.3115454332125],[-119.996011479298,39.4435009764511],[-119.996165311172,39.7206108077274],[-119.996324660047,41.1775662656441],[-119.993459369715,41.9892049531992],[-119.351692186077,41.9888529749781],[-119.3109421304,41.9891353872811],[-118.185316829038,41.9966370981387],[-117.018864363596,41.9947941808341],[-116.992313337997,41.9947945094663],[-115.947544658193,41.9945994628997],[-115.024862911148,41.996506455953],[-114.269471632824,41.9959242345073],[-114.039072662345,41.9953908974688],[-114.038151248682,40.9976868405942],[-114.038108189376,40.1110466529553],[-114.039844684228,39.9087788600023],[-114.040105338584,39.5386849268845],[-114.044267501155,38.6789958815881],[-114.045090206153,38.5710950539539],[-114.047272999176,38.1376524399918],[-114.047260595159,37.5984784866001],[-114.043939384154,36.9965379371421],[-114.043716435713,36.8418489458647],[-114.037392074194,36.2160228969702],[-114.045105557286,36.1939778840226],[-114.107775185788,36.1210907070504],[-114.12902308363,36.041730493896],[-114.206768869568,36.0172554164834],[-114.233472615347,36.0183310595897],[-114.307587598189,36.0622330993643],[-114.303857056018,36.0871084040611],[-114.316095374696,36.1114380366653],[-114.344233941709,36.1374802520568],[-114.380803116644,36.1509912717765],[-114.443945697733,36.1210532841897],[-114.466613475422,36.1247112590539],[-114.530573568745,36.1550902046725],[-114.598935242024,36.1383354528834],[-114.621610747198,36.1419666834504],[-114.712761724737,36.1051810523675],[-114.728150311069,36.0859627711604],[-114.728966012834,36.0587530361083],[-114.717673567756,36.0367580437018],[-114.736212493583,35.9876483502758],[-114.699275906446,35.9116119537412],[-114.661600122152,35.8804735854242],[-114.662462095522,35.8709599070091],[-114.689867343369,35.8474424944766],[-114.682739704595,35.7647034175617],[-114.688820027649,35.7325957399896],[-114.665091345861,35.6930994107107],[-114.668486064922,35.6563989882404],[-114.654065925137,35.6465840800053],[-114.6398667219,35.6113485698329],[-114.653134321223,35.5848331056108],[-114.649792053474,35.5466373866597],[-114.672215155693,35.5157541647721],[-114.645396168451,35.4507608261463],[-114.589584275424,35.3583787306827],[-114.587889840369,35.30476812919],[-114.559583045727,35.2201828714608],[-114.561039964054,35.1743461616313],[-114.572255261053,35.1400677445931],[-114.582616239058,35.1325604694085],[-114.626440825485,35.1339067529872],[-114.6359090842,35.1186557767895],[-114.595631971944,35.0760579746697],[-114.633779872695,35.0418633504303],[-114.621068606189,34.9989144286133],[-115.626197382816,35.7956983148418],[-115.88576934392,36.0012259572723],[-117.160423771838,36.9595941441767],[-117.838686423167,37.457298239715],[-118.417419755966,37.8866767486211],[-119.152450421001,38.4118009590513]]], \"spatialReference\":{\"wkid\":4326}}"; - - JsonFactory factory = new JsonFactory(); - JsonParser parser = factory.createJsonParser(strPolygon1); - parser.nextToken(); - MapGeometry mapGeom = GeometryEngine.jsonToGeometry(parser); - Geometry geom = mapGeom.getGeometry(); - OperatorExportToWkb operatorExport = (OperatorExportToWkb) OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.ExportToWkb); - ByteBuffer byteBuffer = operatorExport.execute(0, geom, null); - byte[] wkb = byteBuffer.array(); - - // WKB -> GEOM -> JSON - OperatorImportFromWkb operatorImport = (OperatorImportFromWkb) OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.ImportFromWkb); - geom = operatorImport.execute(0, Geometry.Type.Polygon, - ByteBuffer.wrap(wkb), null); - // geom = operatorImport.execute(0, Geometry.Type.Polygon, - // byteBuffer); - String outputPolygon1 = GeometryEngine.geometryToJson(-1, geom); - System.out.println(strPolygon1); - System.out.println(outputPolygon1); - } catch (JsonParseException ex) { - } catch (IOException ex) { - } - - } - - @Test - public void testWKB2() throws Exception { - // JSON -> GEOM -> WKB - - // String strPolygon1 = - // "{\"xmin\":-1.16605115291E7,\"ymin\":4925189.941699997,\"xmax\":-1.16567772126E7,\"ymax\":4928658.771399997,\"spatialReference\":{\"wkid\":102100}}"; - String strPolygon1 = "{\"rings\" : [ [ [-1.16605115291E7,4925189.941699997], [-1.16567772126E7,4925189.941699997], [-1.16567772126E7,4928658.771399997], [-1.16605115291E7,4928658.771399997], [-1.16605115291E7,4925189.941699997] ] ], \"spatialReference\" : {\"wkid\" : 102100}}"; - - JsonFactory factory = new JsonFactory(); - JsonParser parser = factory.createJsonParser(strPolygon1); - parser.nextToken(); - MapGeometry mapGeom = GeometryEngine.jsonToGeometry(parser); - Geometry geom = mapGeom.getGeometry(); - - // simplifying geom - OperatorSimplify operatorSimplify = (OperatorSimplify) OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.Simplify); - SpatialReference sr = SpatialReference.create(102100); - geom = operatorSimplify.execute(geom, sr, true, null); - - OperatorExportToWkb operatorExport = (OperatorExportToWkb) OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.ExportToWkb); - ByteBuffer byteBuffer = operatorExport.execute(0, geom, null); - byte[] wkb = byteBuffer.array(); - - // // checking WKB correctness - // WKBReader jtsReader = new WKBReader(); - // com.vividsolutions.jts.geom.Geometry jtsGeom = jtsReader.read(wkb); - // System.out.println("jtsGeom = " + jtsGeom); - - // WKB -> GEOM -> JSON - OperatorImportFromWkb operatorImport = (OperatorImportFromWkb) OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.ImportFromWkb); - geom = operatorImport.execute(0, Geometry.Type.Polygon, - ByteBuffer.wrap(wkb), null); - assertTrue(!geom.isEmpty()); - // geom = operatorImport.execute(0, Geometry.Type.Polygon, byteBuffer); - // String outputPolygon1 = GeometryEngine.geometryToJson(-1, geom); - // System.out.println(strPolygon1); - // System.out.println(outputPolygon1); - - } -} +package com.esri.core.geometry; + +import java.io.IOException; +import java.nio.ByteBuffer; +import org.codehaus.jackson.JsonFactory; +import org.codehaus.jackson.JsonParseException; +import org.codehaus.jackson.JsonParser; +import junit.framework.TestCase; +import org.junit.Test; + +//import com.vividsolutions.jts.io.WKBReader; + +public class TestWKBSupport extends TestCase { + @Override + protected void setUp() throws Exception { + super.setUp(); + } + + @Override + protected void tearDown() throws Exception { + super.tearDown(); + } + + @Test + public void testWKB() { + try { + // JSON -> GEOM -> WKB + + String strPolygon1 = "{\"xmin\":-1.1663479012889031E7,\"ymin\":4919777.494405342,\"xmax\":-1.1658587043078788E7,\"ymax\":4924669.464215587,\"spatialReference\":{\"wkid\":102100}}"; + // String strPolygon1 = + // "{\"rings\":[[[-119.152450421001,38.4118009590513],[-119.318825070203,38.5271086243914],[-119.575687062955,38.7029101298904],[-119.889341639399,38.9222515603984],[-119.995254694357,38.9941061536377],[-119.995150114198,39.0634913594691],[-119.994541258334,39.1061318056708],[-119.995527335641,39.1587132866355],[-119.995304181493,39.3115454332125],[-119.996011479298,39.4435009764511],[-119.996165311172,39.7206108077274],[-119.996324660047,41.1775662656441],[-119.993459369715,41.9892049531992],[-119.351692186077,41.9888529749781],[-119.3109421304,41.9891353872811],[-118.185316829038,41.9966370981387],[-117.018864363596,41.9947941808341],[-116.992313337997,41.9947945094663],[-115.947544658193,41.9945994628997],[-115.024862911148,41.996506455953],[-114.269471632824,41.9959242345073],[-114.039072662345,41.9953908974688],[-114.038151248682,40.9976868405942],[-114.038108189376,40.1110466529553],[-114.039844684228,39.9087788600023],[-114.040105338584,39.5386849268845],[-114.044267501155,38.6789958815881],[-114.045090206153,38.5710950539539],[-114.047272999176,38.1376524399918],[-114.047260595159,37.5984784866001],[-114.043939384154,36.9965379371421],[-114.043716435713,36.8418489458647],[-114.037392074194,36.2160228969702],[-114.045105557286,36.1939778840226],[-114.107775185788,36.1210907070504],[-114.12902308363,36.041730493896],[-114.206768869568,36.0172554164834],[-114.233472615347,36.0183310595897],[-114.307587598189,36.0622330993643],[-114.303857056018,36.0871084040611],[-114.316095374696,36.1114380366653],[-114.344233941709,36.1374802520568],[-114.380803116644,36.1509912717765],[-114.443945697733,36.1210532841897],[-114.466613475422,36.1247112590539],[-114.530573568745,36.1550902046725],[-114.598935242024,36.1383354528834],[-114.621610747198,36.1419666834504],[-114.712761724737,36.1051810523675],[-114.728150311069,36.0859627711604],[-114.728966012834,36.0587530361083],[-114.717673567756,36.0367580437018],[-114.736212493583,35.9876483502758],[-114.699275906446,35.9116119537412],[-114.661600122152,35.8804735854242],[-114.662462095522,35.8709599070091],[-114.689867343369,35.8474424944766],[-114.682739704595,35.7647034175617],[-114.688820027649,35.7325957399896],[-114.665091345861,35.6930994107107],[-114.668486064922,35.6563989882404],[-114.654065925137,35.6465840800053],[-114.6398667219,35.6113485698329],[-114.653134321223,35.5848331056108],[-114.649792053474,35.5466373866597],[-114.672215155693,35.5157541647721],[-114.645396168451,35.4507608261463],[-114.589584275424,35.3583787306827],[-114.587889840369,35.30476812919],[-114.559583045727,35.2201828714608],[-114.561039964054,35.1743461616313],[-114.572255261053,35.1400677445931],[-114.582616239058,35.1325604694085],[-114.626440825485,35.1339067529872],[-114.6359090842,35.1186557767895],[-114.595631971944,35.0760579746697],[-114.633779872695,35.0418633504303],[-114.621068606189,34.9989144286133],[-115.626197382816,35.7956983148418],[-115.88576934392,36.0012259572723],[-117.160423771838,36.9595941441767],[-117.838686423167,37.457298239715],[-118.417419755966,37.8866767486211],[-119.152450421001,38.4118009590513]]], \"spatialReference\":{\"wkid\":4326}}"; + + JsonFactory factory = new JsonFactory(); + JsonParser parser = factory.createJsonParser(strPolygon1); + parser.nextToken(); + MapGeometry mapGeom = GeometryEngine.jsonToGeometry(parser); + Geometry geom = mapGeom.getGeometry(); + OperatorExportToWkb operatorExport = (OperatorExportToWkb) OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.ExportToWkb); + ByteBuffer byteBuffer = operatorExport.execute(0, geom, null); + byte[] wkb = byteBuffer.array(); + + // WKB -> GEOM -> JSON + OperatorImportFromWkb operatorImport = (OperatorImportFromWkb) OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.ImportFromWkb); + geom = operatorImport.execute(0, Geometry.Type.Polygon, + ByteBuffer.wrap(wkb), null); + // geom = operatorImport.execute(0, Geometry.Type.Polygon, + // byteBuffer); + String outputPolygon1 = GeometryEngine.geometryToJson(-1, geom); + System.out.println(strPolygon1); + System.out.println(outputPolygon1); + } catch (JsonParseException ex) { + } catch (IOException ex) { + } + + } + + @Test + public void testWKB2() throws Exception { + // JSON -> GEOM -> WKB + + // String strPolygon1 = + // "{\"xmin\":-1.16605115291E7,\"ymin\":4925189.941699997,\"xmax\":-1.16567772126E7,\"ymax\":4928658.771399997,\"spatialReference\":{\"wkid\":102100}}"; + String strPolygon1 = "{\"rings\" : [ [ [-1.16605115291E7,4925189.941699997], [-1.16567772126E7,4925189.941699997], [-1.16567772126E7,4928658.771399997], [-1.16605115291E7,4928658.771399997], [-1.16605115291E7,4925189.941699997] ] ], \"spatialReference\" : {\"wkid\" : 102100}}"; + + JsonFactory factory = new JsonFactory(); + JsonParser parser = factory.createJsonParser(strPolygon1); + parser.nextToken(); + MapGeometry mapGeom = GeometryEngine.jsonToGeometry(parser); + Geometry geom = mapGeom.getGeometry(); + + // simplifying geom + OperatorSimplify operatorSimplify = (OperatorSimplify) OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Simplify); + SpatialReference sr = SpatialReference.create(102100); + geom = operatorSimplify.execute(geom, sr, true, null); + + OperatorExportToWkb operatorExport = (OperatorExportToWkb) OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.ExportToWkb); + ByteBuffer byteBuffer = operatorExport.execute(0, geom, null); + byte[] wkb = byteBuffer.array(); + + // // checking WKB correctness + // WKBReader jtsReader = new WKBReader(); + // com.vividsolutions.jts.geom.Geometry jtsGeom = jtsReader.read(wkb); + // System.out.println("jtsGeom = " + jtsGeom); + + // WKB -> GEOM -> JSON + OperatorImportFromWkb operatorImport = (OperatorImportFromWkb) OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.ImportFromWkb); + geom = operatorImport.execute(0, Geometry.Type.Polygon, + ByteBuffer.wrap(wkb), null); + assertTrue(!geom.isEmpty()); + // geom = operatorImport.execute(0, Geometry.Type.Polygon, byteBuffer); + // String outputPolygon1 = GeometryEngine.geometryToJson(-1, geom); + // System.out.println(strPolygon1); + // System.out.println(outputPolygon1); + + } + } From 1b8c92a2d659c8601db00bdb9a66ff23b71e995c Mon Sep 17 00:00:00 2001 From: Sergey Tolstov Date: Fri, 22 Mar 2013 18:11:11 -0700 Subject: [PATCH 024/196] Added ogc package to the javadoc and fixed a couple of javadoc warnings --- build.xml | 2 +- .../core/geometry/ogc/OGCConcreteGeometryCollection.java | 2 -- src/com/esri/core/geometry/ogc/OGCGeometry.java | 6 +++--- 3 files changed, 4 insertions(+), 6 deletions(-) diff --git a/build.xml b/build.xml index c085c8aa..9061de23 100644 --- a/build.xml +++ b/build.xml @@ -79,7 +79,7 @@ - + diff --git a/src/com/esri/core/geometry/ogc/OGCConcreteGeometryCollection.java b/src/com/esri/core/geometry/ogc/OGCConcreteGeometryCollection.java index 3ca58c1e..a3b6d989 100644 --- a/src/com/esri/core/geometry/ogc/OGCConcreteGeometryCollection.java +++ b/src/com/esri/core/geometry/ogc/OGCConcreteGeometryCollection.java @@ -197,7 +197,6 @@ public boolean isSimple() { /** * isSimpleRelaxed is not supported for the GeometryCollection instance. * - * @return */ @Override public boolean isSimpleRelaxed() { @@ -207,7 +206,6 @@ public boolean isSimpleRelaxed() { /** * MakeSimpleRelaxed is not supported for the GeometryCollection instance. * - * @return */ @Override public OGCGeometry MakeSimpleRelaxed(boolean forceProcessing) { diff --git a/src/com/esri/core/geometry/ogc/OGCGeometry.java b/src/com/esri/core/geometry/ogc/OGCGeometry.java index cadfb67c..1a303c0b 100644 --- a/src/com/esri/core/geometry/ogc/OGCGeometry.java +++ b/src/com/esri/core/geometry/ogc/OGCGeometry.java @@ -125,7 +125,7 @@ public boolean isSimple() { /** * Extension method - checks if geometry is simple for Geodatabase. * - * @return + * @return Returns true if geometry is simple, false otherwise. */ public boolean isSimpleRelaxed() { OperatorSimplify op = (OperatorSimplify) OperatorFactoryLocal @@ -137,7 +137,7 @@ public boolean isSimpleRelaxed() { /** * Makes a simple geometry for Geodatabase. * - * @return + * @return Returns simplified geometry. */ public OGCGeometry MakeSimpleRelaxed(boolean forceProcessing) { OperatorSimplify op = (OperatorSimplify) OperatorFactoryLocal @@ -393,7 +393,7 @@ public com.esri.core.geometry.SpatialReference getEsriSpatialReference() { * * @param gc * @param sr - * @return + * @return Geometry instance created from the geometry cursor. */ public static OGCGeometry createFromEsriCursor(GeometryCursor gc, SpatialReference sr) { From 08e517d589df28aa34abc090d1ec9f1bdf5a2550 Mon Sep 17 00:00:00 2001 From: David Kaiser Date: Sun, 24 Mar 2013 13:52:18 -0700 Subject: [PATCH 025/196] Update README.md to add Javadoc --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index 357ada6f..c5ba9064 100644 --- a/README.md +++ b/README.md @@ -19,6 +19,9 @@ The Esri Geometry API for Java can be used to enable spatial data processing in * Experience developing MapReduce applications for [Apache Hadoop](http://hadoop.apache.org/). * Familiarity with text-based spatial data formats such as JSON or WKT would be useful. +## Documentation +* [geometry-api-java/Javadoc](http://esri.github.com/geometry-api-java/javadoc/) + ## Resources * [ArcGIS Geodata Resource Center]( http://resources.arcgis.com/en/communities/geodata/) From a4d7efb5285104717d15d022a51e53d4d6bb4cfe Mon Sep 17 00:00:00 2001 From: Sergey Tolstov Date: Mon, 25 Mar 2013 10:08:31 -0700 Subject: [PATCH 026/196] OGCGeometryCollection has to be public --- src/com/esri/core/geometry/ogc/OGCGeometryCollection.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/com/esri/core/geometry/ogc/OGCGeometryCollection.java b/src/com/esri/core/geometry/ogc/OGCGeometryCollection.java index 354c1a50..ea0d84af 100644 --- a/src/com/esri/core/geometry/ogc/OGCGeometryCollection.java +++ b/src/com/esri/core/geometry/ogc/OGCGeometryCollection.java @@ -1,6 +1,6 @@ package com.esri.core.geometry.ogc; -abstract class OGCGeometryCollection extends OGCGeometry { +public abstract class OGCGeometryCollection extends OGCGeometry { /** * Returns the number of geometries in this GeometryCollection. */ From 492404ccbd44772960fe8928ac57e71eb5dd3590 Mon Sep 17 00:00:00 2001 From: Sergey Tolstov Date: Wed, 27 Mar 2013 11:24:00 -0700 Subject: [PATCH 027/196] added "public" to Envelope2D, added comments "in development" to OperatorSimplifyOGC.execute --- src/com/esri/core/geometry/Envelope2D.java | 38 ++-- .../core/geometry/OperatorSimplifyOGC.java | 10 +- .../core/geometry/TopologicalOperations.java | 2 + unittest/com/esri/core/geometry/TestOGC.java | 164 ++++++++++++++++++ 4 files changed, 188 insertions(+), 26 deletions(-) diff --git a/src/com/esri/core/geometry/Envelope2D.java b/src/com/esri/core/geometry/Envelope2D.java index 5d1f237a..35d66abb 100644 --- a/src/com/esri/core/geometry/Envelope2D.java +++ b/src/com/esri/core/geometry/Envelope2D.java @@ -27,7 +27,7 @@ /** * A class that represents axis parallel 2D rectangle. */ -final class Envelope2D { +public final class Envelope2D { private final int XLESSXMIN = 1; // private final int XGREATERXMAX = 2; @@ -36,13 +36,13 @@ final class Envelope2D { private final int XMASK = 3; private final int YMASK = 12; - public double xmin; + double xmin; - public double ymin; + double ymin; - public double xmax; + double xmax; - public double ymax; + double ymax; public static Envelope2D construct(double _xmin, double _ymin, double _xmax, double _ymax) { @@ -88,7 +88,7 @@ public void setCoords(Point2D center, double width, double height) { normalize(); } - void setCoords(Point2D pt) { + public void setCoords(Point2D pt) { xmin = pt.x; ymin = pt.y; xmax = pt.x; @@ -99,7 +99,7 @@ public void setCoords(Envelope2D envSrc) { setCoords(envSrc.xmin, envSrc.ymin, envSrc.xmax, envSrc.ymax); } - Envelope2D getInflated(double dx, double dy) { + public Envelope2D getInflated(double dx, double dy) { Envelope2D env = new Envelope2D(); env.setCoords(this.xmin, this.ymin, this.xmax, this.ymax); env.inflate(dx, dy); @@ -110,8 +110,6 @@ Envelope2D getInflated(double dx, double dy) { * Sets the envelope from the array of points. The envelope will be set to * empty if the array is null. */ - // TODO This should either call setFromPoints(points, int count) or vice - // versa public void setFromPoints(Point2D[] points) { if (points == null || points.length == 0) { setEmpty(); @@ -270,7 +268,6 @@ public boolean isIntersectingNE(Envelope2D other) { * Intersects this envelope with the other. Returns True if the result is * not empty. */ - public boolean intersect(Envelope2D other) { if (isEmpty() || other.isEmpty()) return false; @@ -416,11 +413,6 @@ public double getHeight() { return ymax - ymin; } - // public Envelope2D getCopy() { - // Envelope2D ret = new Envelope2D(xmin, ymin, xmax, ymax); - // return ret; - // } - /** * Moves the Envelope by given distance. */ @@ -437,7 +429,7 @@ public void centerAt(double x, double y) { move(x - getCenterX(), y - getCenterY()); } - public void centerAt(Point2D pt) { + void centerAt(Point2D pt) { centerAt(pt.x, pt.y); } @@ -462,19 +454,19 @@ public void normalize() { ymax = max; } - void queryLowerLeft(Point2D pt) { + public void queryLowerLeft(Point2D pt) { pt.setCoords(xmin, ymin); } - void queryLowerRight(Point2D pt) { + public void queryLowerRight(Point2D pt) { pt.setCoords(xmax, ymin); } - void queryUpperLeft(Point2D pt) { + public void queryUpperLeft(Point2D pt) { pt.setCoords(xmin, ymax); } - void queryUpperRight(Point2D pt) { + public void queryUpperRight(Point2D pt) { pt.setCoords(xmax, ymax); } @@ -664,7 +656,7 @@ else if (p.y > ymax) // boundary, // it is more efficient to perform ProjectToBoundary before using this // function). - public double _boundaryDistance(Point2D pt) { + double _boundaryDistance(Point2D pt) { if (isEmpty()) return NumberUtils.NaN(); @@ -687,7 +679,7 @@ public double _boundaryDistance(Point2D pt) { } // returns 0,..3 depending on which side pt lies. - public int _envelopeSide(Point2D pt) { + int _envelopeSide(Point2D pt) { if (isEmpty()) return -1; @@ -715,7 +707,7 @@ public int _envelopeSide(Point2D pt) { // 100.0; } - int clipLine(Point2D p1, Point2D p2) + public int clipLine(Point2D p1, Point2D p2) // Modified Cohen-Sutherland Line-Clipping Algorithm // returns: // 0 - the segment is outside of the clipping window diff --git a/src/com/esri/core/geometry/OperatorSimplifyOGC.java b/src/com/esri/core/geometry/OperatorSimplifyOGC.java index aaf89f50..e415368e 100644 --- a/src/com/esri/core/geometry/OperatorSimplifyOGC.java +++ b/src/com/esri/core/geometry/OperatorSimplifyOGC.java @@ -24,7 +24,7 @@ package com.esri.core.geometry; /** - * Simplifies geometry or determines if geometry is simple. Tries to follow OGC specification. + * Simplifies geometry or determines if geometry is simple. Tries to follow OGC specification 1.2.1. * Uses tolerance to determine equal vertices or points of intersection. * */ @@ -35,7 +35,7 @@ public Operator.Type getType() { } /** - *Tests if the Geometry is simple for OGC spec. + *Tests if the Geometry is simple for OGC spec 1.2.1. *@param geom The Geometry to be tested. *@param bForceTest When True, the Geometry will be tested regardless of the IsKnownSimple flag. * @@ -47,6 +47,8 @@ public abstract boolean isSimpleOGC(Geometry geom, NonSimpleResult result, ProgressTracker progressTracker); /** + * This method is still in development. Use Operator_simplify for now. + * *Performs the Simplify operation on the geometry set. *@return Returns a GeometryCursor of simplified geometries. */ @@ -55,7 +57,9 @@ public abstract GeometryCursor execute(GeometryCursor geoms, ProgressTracker progressTracker); /** - *Performs the Simplify operation on a Geometry + * This method is still in development. Use Operator_simplify for now. + * + *Performs the Simplify operation on a Geometry. *@return Returns a simple Geometry. */ public abstract Geometry execute(Geometry geom, SpatialReference sr, diff --git a/src/com/esri/core/geometry/TopologicalOperations.java b/src/com/esri/core/geometry/TopologicalOperations.java index fd29e918..96fae909 100644 --- a/src/com/esri/core/geometry/TopologicalOperations.java +++ b/src/com/esri/core/geometry/TopologicalOperations.java @@ -150,6 +150,8 @@ private void collectPolygonPathsPreservingFrom_(int geometryFrom, int vertex = first_vertex; int cluster = m_topo_graph.getClusterFromVertex(vertex); int dir = 1; + //Walk the chain of half edges, preferably selecting vertices that belong to the + //polygon path we have started from. do { int vertex_dominant = getVertexByID_(vertex, geometry_dominant); shape.addVertex(newPath, vertex_dominant); diff --git a/unittest/com/esri/core/geometry/TestOGC.java b/unittest/com/esri/core/geometry/TestOGC.java index 5abda09a..3b53cebc 100644 --- a/unittest/com/esri/core/geometry/TestOGC.java +++ b/unittest/com/esri/core/geometry/TestOGC.java @@ -3,8 +3,10 @@ import junit.framework.TestCase; import com.esri.core.geometry.ogc.OGCGeometry; +import com.esri.core.geometry.ogc.OGCGeometryCollection; import com.esri.core.geometry.ogc.OGCLineString; import com.esri.core.geometry.ogc.OGCMultiPoint; +import com.esri.core.geometry.ogc.OGCMultiPolygon; import com.esri.core.geometry.ogc.OGCPoint; import com.esri.core.geometry.ogc.OGCPolygon; import com.esri.core.geometry.ogc.OGCConcreteGeometryCollection; @@ -362,6 +364,168 @@ public void test_polygon_is_simple_for_OGC() { } } + /* + This will fail + public void test_polygon_simplify_for_OGC() { + try { + { + String s = "{\"rings\":[[[0, 0], [0, 10], [10, 10], [10, 0], [0, 0]]]}"; + OGCGeometry g = OGCGeometry.fromJson(s); + boolean res = g.isSimple(); + assertTrue(res); + assertTrue(g.isSimpleRelaxed()); + Geometry resg = OperatorSimplifyOGC.local().execute(g.getEsriGeometry(), null, true, null); + OGCGeometry og = OGCGeometry.createFromEsriGeometry(resg, null); + assertTrue(og.geometryType().equals("Polygon")); + assertTrue(((OGCPolygon)og).numInteriorRing() == 0); + } + + {// exterior ring is self-tangent + String s = "{\"rings\":[[[0, 0], [0, 10], [5, 5], [10, 10], [10, 0], [5, 5], [0, 0]]]}"; + OGCGeometry g = OGCGeometry.fromJson(s); + boolean res = g.isSimple(); + assertTrue(!res); + assertTrue(g.isSimpleRelaxed()); + Geometry resg = OperatorSimplifyOGC.local().execute(g.getEsriGeometry(), null, true, null); + OGCGeometry og = OGCGeometry.createFromEsriGeometry(resg, null); + res = og.isSimple(); + assertTrue(res); + assertTrue(og.geometryType().equals("MultiPolygon")); + assertTrue(((OGCGeometryCollection)og).numGeometries() == 2); + } + + {// ring orientation (hole is cw) + String s = "{\"rings\":[[[-100, -100], [-100, 100], [100, 100], [100, -100], [-100, -100]], [[0, 0], [5, 5], [10, 0], [0, 0]]]}"; + OGCGeometry g = OGCGeometry.fromJson(s); + boolean res = g.isSimple(); + assertTrue(!res); + assertTrue(!g.isSimpleRelaxed()); + Geometry resg = OperatorSimplifyOGC.local().execute(g.getEsriGeometry(), null, true, null); + OGCGeometry og = OGCGeometry.createFromEsriGeometry(resg, null); + res = og.isSimple(); + assertTrue(res); + assertTrue(og.geometryType().equals("Polygon")); + assertTrue(((OGCPolygon)og).numInteriorRing() == 1); + } + + {// ring order + String s = "{\"rings\":[[[0, 0], [10, 0], [5, 5], [0, 0]], [[-100, -100], [-100, 100], [100, 100], [100, -100], [-100, -100]]]}"; + OGCGeometry g = OGCGeometry.fromJson(s); + boolean res = g.isSimple(); + assertTrue(!res); + assertTrue(g.isSimpleRelaxed()); + Geometry resg = OperatorSimplifyOGC.local().execute(g.getEsriGeometry(), null, true, null); + OGCGeometry og = OGCGeometry.createFromEsriGeometry(resg, null); + res = og.isSimple(); + assertTrue(res); + assertTrue(og.geometryType().equals("Polygon")); + } + + { + // hole is self tangent + String s = "{\"rings\":[[[-100, -100], [-100, 100], [100, 100], [100, -100], [-100, -100]], [[0, 0], [5, 5], [10, 0], [10, 10], [5, 5], [0, 10], [0, 0]]]}"; + OGCGeometry g = OGCGeometry.fromJson(s); + boolean res = g.isSimple(); + assertTrue(!res); + assertTrue(g.isSimpleRelaxed()); + Geometry resg = OperatorSimplifyOGC.local().execute(g.getEsriGeometry(), null, true, null); + OGCGeometry og = OGCGeometry.createFromEsriGeometry(resg, null); + res = og.isSimple(); + assertTrue(res); + assertTrue(og.geometryType().equals("Polygon")); + assertTrue(((OGCPolygon)og).numInteriorRing() == 2); + } + { + // two holes touch + String s = "{\"rings\":[[[-100, -100], [-100, 100], [100, 100], [100, -100], [-100, -100]], [[0, 0], [10, 0], [5, 5], [0, 0]], [[10, 10], [0, 10], [5, 5], [10, 10]]]}"; + OGCGeometry g = OGCGeometry.fromJson(s); + boolean res = g.isSimple(); + assertTrue(res); + assertTrue(g.isSimpleRelaxed()); + Geometry resg = OperatorSimplifyOGC.local().execute(g.getEsriGeometry(), null, true, null); + OGCGeometry og = OGCGeometry.createFromEsriGeometry(resg, null); + assertTrue(og.geometryType().equals("Polygon")); + assertTrue(((OGCPolygon)og).numInteriorRing() == 2); + } + { + // two holes touch, bad orientation + String s = "{\"rings\":[[[-100, -100], [-100, 100], [100, 100], [100, -100], [-100, -100]], [[0, 0], [5, 5], [10, 0], [0, 0]], [[10, 10], [0, 10], [5, 5], [10, 10]]]}"; + OGCGeometry g = OGCGeometry.fromJson(s); + boolean res = g.isSimple(); + assertTrue(!res); + assertTrue(!g.isSimpleRelaxed()); + Geometry resg = OperatorSimplifyOGC.local().execute(g.getEsriGeometry(), null, true, null); + OGCGeometry og = OGCGeometry.createFromEsriGeometry(resg, null); + assertTrue(og.geometryType().equals("Polygon")); + assertTrue(((OGCPolygon)og).numInteriorRing() == 2); + } + + { + // hole touches exterior in two spots + //OperatorSimplifyOGC produces a multipolygon with two polygons without holes. + String s = "{\"rings\":[[[-100, -100], [-100, 100], [0, 100], [100, 100], [100, -100], [0, -100], [-100, -100]], [[0, -100], [10, 0], [0, 100], [-10, 0], [0, -100]]]}"; + OGCGeometry g = OGCGeometry.fromJson(s); + boolean res = g.isSimple(); + assertTrue(!res); + assertTrue(g.isSimpleRelaxed()); + Geometry resg = OperatorSimplifyOGC.local().execute(g.getEsriGeometry(), null, true, null); + OGCGeometry og = OGCGeometry.createFromEsriGeometry(resg, null); + assertTrue(og.geometryType().equals("MultiPolygon")); + assertTrue(((OGCMultiPolygon)og).numGeometries() == 2); + assertTrue(((OGCPolygon)((OGCMultiPolygon)og).geometryN(0)).numInteriorRing() == 0); + assertTrue(((OGCPolygon)((OGCMultiPolygon)og).geometryN(1)).numInteriorRing() == 0); + } + + { + // hole touches exterior in one spot + //OperatorSimplifyOGC produces a polygons with a hole. + String s = "{\"rings\":[[[-100, -100], [-100, 100], [0, 100], [100, 100], [100, -100], [0, -100], [-100, -100]], [[0, -100], [10, 0], [0, 90], [-10, 0], [0, -100]]]}"; + OGCGeometry g = OGCGeometry.fromJson(s); + boolean res = g.isSimple(); + assertTrue(res); + assertTrue(g.isSimpleRelaxed()); + Geometry resg = OperatorSimplifyOGC.local().execute(g.getEsriGeometry(), null, true, null); + OGCGeometry og = OGCGeometry.createFromEsriGeometry(resg, null); + assertTrue(og.geometryType().equals("Polygon")); + assertTrue(((OGCPolygon)og).numInteriorRing() == 1); + } + + { + // exterior has inversion (non simple for OGC) + //OperatorSimplifyOGC produces a polygons with a hole. + String s = "{\"rings\":[[[-100, -100], [-100, 100], [0, 100], [100, 100], [100, -100], [0, -100], [10, 0], [0, 90], [-10, 0], [0, -100], [-100, -100]]]}"; + OGCGeometry g = OGCGeometry.fromJson(s); + boolean res = g.isSimple(); + assertTrue(!res); + assertTrue(g.isSimpleRelaxed()); + Geometry resg = OperatorSimplifyOGC.local().execute(g.getEsriGeometry(), null, true, null); + OGCGeometry og = OGCGeometry.createFromEsriGeometry(resg, null); + assertTrue(og.geometryType().equals("Polygon")); + assertTrue(((OGCPolygon)og).numInteriorRing() == 1); + } + + { + // two holes touch in one spot, and they also touch exterior in + // two spots, producing disconnected interior + //OperatorSimplifyOGC produces two polygons with no holes. + String s = "{\"rings\":[[[-100, -100], [-100, 100], [0, 100], [100, 100], [100, -100], [0, -100], [-100, -100]], [[0, -100], [10, -50], [0, 0], [-10, -50], [0, -100]], [[0, 0], [10, 50], [0, 100], [-10, 50], [0, 0]]]}"; + OGCGeometry g = OGCGeometry.fromJson(s); + boolean res = g.isSimple(); + assertTrue(!res); + assertTrue(g.isSimpleRelaxed()); + Geometry resg = OperatorSimplifyOGC.local().execute(g.getEsriGeometry(), null, true, null); + OGCGeometry og = OGCGeometry.createFromEsriGeometry(resg, null); + assertTrue(og.geometryType().equals("MultiPolygon")); + assertTrue(((OGCMultiPolygon)og).numGeometries() == 2); + assertTrue(((OGCPolygon)((OGCMultiPolygon)og).geometryN(0)).numInteriorRing() == 0); + assertTrue(((OGCPolygon)((OGCMultiPolygon)og).geometryN(1)).numInteriorRing() == 0); + } + } catch (Exception ex) { + assertTrue(false); + } + } + */ + public void test_polyline_is_simple_for_OGC() { try { { From 89c91d560eaa975789831b8446f3065b6af4b2df Mon Sep 17 00:00:00 2001 From: Michael Park Date: Mon, 1 Apr 2013 09:51:20 -0700 Subject: [PATCH 028/196] Update README.md --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index c5ba9064..797a5f16 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,7 @@ The Esri Geometry API for Java can be used to enable spatial data processing in ## Requirements * Java JDK 1.6 or greater. +* [Apache Ant](http://ant.apache.org/) build system. * Experience developing MapReduce applications for [Apache Hadoop](http://hadoop.apache.org/). * Familiarity with text-based spatial data formats such as JSON or WKT would be useful. From f723933d85ab9c0aeb7b91a2022340457c2a978f Mon Sep 17 00:00:00 2001 From: Aaron Balog Date: Tue, 2 Apr 2013 17:08:44 -0700 Subject: [PATCH 029/196] Changed api for incremental construction of IntervalTree / Envelope2DIntersector --- .../geometry/Envelope2DIntersectorImpl.java | 265 +++++++------- src/com/esri/core/geometry/InternalUtils.java | 45 ++- .../esri/core/geometry/IntervalTreeImpl.java | 329 +++++++----------- .../geometry/TestEnvelope2DIntersector.java | 62 +++- .../esri/core/geometry/TestIntervalTree.java | 45 ++- 5 files changed, 385 insertions(+), 361 deletions(-) diff --git a/src/com/esri/core/geometry/Envelope2DIntersectorImpl.java b/src/com/esri/core/geometry/Envelope2DIntersectorImpl.java index c4f4e227..874e90c5 100644 --- a/src/com/esri/core/geometry/Envelope2DIntersectorImpl.java +++ b/src/com/esri/core/geometry/Envelope2DIntersectorImpl.java @@ -30,72 +30,109 @@ class Envelope2DIntersectorImpl { * Constructor for Envelope_2D_intersector. */ Envelope2DIntersectorImpl() { - m_tolerance = 0.0; m_function = -1; - m_sweep_index_red = -1; - m_sweep_index_blue = -1; - m_queued_list_red = -1; - m_queued_list_blue = -1; - m_b_done = true; + m_tolerance = 0.0; + reset_(); } - /* - * Constructor for Envelope_2D_intersector_impl to find all pairs of - * intersecting envelopes from the same set in O(nlogn + t) time, where n is - * the number of envelopes in the array and t is the number of intersecting - * pairs.\param envelopes A Dynamic_array of Envelope_2D objects to be - * iterated over for pair-wise intersection.\param tolerance The tolerance - * used to determine intersection. - */ - Envelope2DIntersectorImpl(ArrayList envelopes, double tolerance) { - m_envelopes_red = envelopes; - m_tolerance = tolerance; - m_sweep_index_red = -1; - m_sweep_index_blue = -1; - m_queued_list_red = -1; - m_queued_list_blue = -1; + void startConstruction() { + reset_(); + m_b_add_red_red = true; + + if (m_envelopes_red == null) + m_envelopes_red = new ArrayList(0); + else + m_envelopes_red.clear(); + } + + void addEnvelope(Envelope2D envelope) { + if (!m_b_add_red_red) + throw new GeometryException("invalid call"); + + m_envelopes_red.add(envelope); + } + + void endConstruction() { + if (!m_b_add_red_red) + throw new GeometryException("invalid call"); + + m_b_add_red_red = false; if (m_envelopes_red != null && m_envelopes_red.size() > 0) { m_function = State.initialize; m_b_done = false; - } else { - m_function = -1; - m_envelope_handle_a = -1; - m_envelope_handle_b = -1; - m_b_done = true; } } - /* - * Constructor for Envelope_2D_intersector_impl to find all pairs of - * intersecting envelopes from a set of red envelopes and blue envelopes in - * O(max(n,m)log(max(n,m)) + t) time, where n is the number of red - * envelopes, m is the number of blue envelopes, and t is the number of - * intersecting pairs.\param envelopes_red A Dynamic_array of Envelope_2D - * objects to be iterated over for pair-wise intersection with the blue - * envelopes.\param envelopes_blue A Dynamic_array of Envelope_2D objects to - * be iterated over for pair-wise intersection with the red envelopes.\param - * tolerance The tolerance used to determine intersection. - */ - Envelope2DIntersectorImpl(ArrayList envelopes_red, - ArrayList envelopes_blue, double tolerance) { - m_envelopes_red = envelopes_red; - m_envelopes_blue = envelopes_blue; - m_tolerance = tolerance; - m_sweep_index_red = -1; - m_sweep_index_blue = -1; - m_queued_list_red = -1; - m_queued_list_blue = -1; + void startRedConstruction() { + reset_(); + m_b_add_red = true; + + if (m_envelopes_red == null) + m_envelopes_red = new ArrayList(0); + else + m_envelopes_red.clear(); + } + + void addRedEnvelope(Envelope2D red_envelope) { + if (!m_b_add_red) + throw new GeometryException("invalid call"); + + m_envelopes_red.add(red_envelope); + } + + void endRedConstruction() { + if (!m_b_add_red) + throw new GeometryException("invalid call"); + + m_b_add_red = false; if (m_envelopes_red != null && m_envelopes_red.size() > 0 && m_envelopes_blue != null && m_envelopes_blue.size() > 0) { - m_function = State.initializeRedBlue; + if (m_function == -1) + m_function = State.initializeRedBlue; + else if (m_function == State.initializeBlue) + m_function = State.initializeRedBlue; + else if (m_function != State.initializeRedBlue) + m_function = State.initializeRed; + + m_b_done = false; + } + } + + void startBlueConstruction() { + reset_(); + m_b_add_blue = true; + + if (m_envelopes_blue == null) + m_envelopes_blue = new ArrayList(0); + else + m_envelopes_blue.clear(); + } + + void addBlueEnvelope(Envelope2D blue_envelope) { + if (!m_b_add_blue) + throw new GeometryException("invalid call"); + + m_envelopes_blue.add(blue_envelope); + } + + void endBlueConstruction() { + if (!m_b_add_blue) + throw new GeometryException("invalid call"); + + m_b_add_blue = false; + + if (m_envelopes_red != null && m_envelopes_red.size() > 0 + && m_envelopes_blue != null && m_envelopes_blue.size() > 0) { + if (m_function == -1) + m_function = State.initializeRedBlue; + else if (m_function == State.initializeRed) + m_function = State.initializeRedBlue; + else if (m_function != State.initializeRedBlue) + m_function = State.initializeBlue; + m_b_done = false; - } else { - m_function = -1; - m_envelope_handle_a = -1; - m_envelope_handle_b = -1; - m_b_done = true; } } @@ -191,56 +228,6 @@ int getHandleB() { return m_envelope_handle_b; } - /* - * Sets the intersector to do red on red intersection\param envelopes A - * Dynamic_array of Envelope_2D objects to be iterated over for pair-wise - * intersection. - */ - void setEnvelopes(ArrayList envelopes) { - m_envelopes_red = envelopes; - - if (m_envelopes_red != null && m_envelopes_red.size() > 0) { - m_function = State.initialize; - m_b_done = false; - } - } - - /* - * Sets the intersector to do red on blue intersection\param envelopes_red A - * Dynamic_array of Envelope_2D objects to be iterated over for pair-wise - * intersection with the blue envelopes. - */ - void setRedEnvelopes(ArrayList envelopes_red) { - m_envelopes_red = envelopes_red; - - if (m_envelopes_red != null && m_envelopes_red.size() > 0) { - if (m_function == State.initializeBlue) - m_function = State.initializeRedBlue; - else if (m_function != State.initializeRedBlue) - m_function = State.initializeRed; - - m_b_done = false; - } - } - - /* - * Sets the intersector to do red on blue intersection\param envelopes_blue - * A Dynamic_array of Envelope_2D objects to be iterated over for pair-wise - * intersection with the red envelopes. - */ - void setBlueEnvelopes(ArrayList envelopes_blue) { - m_envelopes_blue = envelopes_blue; - - if (m_envelopes_blue != null && m_envelopes_blue.size() > 0) { - if (m_function == State.initializeRed) - m_function = State.initializeRedBlue; - else if (m_function != State.initializeRedBlue) - m_function = State.initializeBlue; - - m_b_done = false; - } - } - /* * Sets the tolerance used for the intersection tests.\param tolerance The * tolerance used to determine intersection. @@ -284,6 +271,9 @@ Envelope2D getBlueEnvelope(int handle_b) { private IndexMultiDCList m_queued_envelopes; private AttributeStreamOfInt32 m_queued_indices_red; private AttributeStreamOfInt32 m_queued_indices_blue; + private boolean m_b_add_red; + private boolean m_b_add_blue; + private boolean m_b_add_red_red; boolean m_b_done; @@ -295,6 +285,17 @@ private static boolean isBottom_(int y_end_point_handle) { return (y_end_point_handle & 0x1) == 0; } + private void reset_() { + m_b_add_red = false; + m_b_add_blue = false; + m_b_add_red_red = false; + m_sweep_index_red = -1; + m_sweep_index_blue = -1; + m_queued_list_red = -1; + m_queued_list_blue = -1; + m_b_done = true; + } + private boolean initialize_() { m_envelope_handle_a = -1; m_envelope_handle_b = -1; @@ -306,14 +307,18 @@ private boolean initialize_() { } if (m_interval_tree_red == null) { - m_interval_tree_red = new IntervalTreeImpl(m_envelopes_red, true, - true); + m_interval_tree_red = new IntervalTreeImpl(true); m_iterator_red = m_interval_tree_red.getIterator(); m_sorted_end_indices_red = new AttributeStreamOfInt32(0); - } else { - m_interval_tree_red.reset(m_envelopes_red, true, true); } + m_interval_tree_red.startConstruction(); + for (int i = 0; i < m_envelopes_red.size(); i++) { + Envelope2D env = m_envelopes_red.get(i); + m_interval_tree_red.addInterval(env.xmin, env.xmax); + } + m_interval_tree_red.endConstruction(); + m_sorted_end_indices_red.reserve(2 * m_envelopes_red.size()); m_sorted_end_indices_red.resize(0); @@ -341,14 +346,18 @@ private boolean initializeRed_() { } if (m_interval_tree_red == null) { - m_interval_tree_red = new IntervalTreeImpl(m_envelopes_red, true, - true); + m_interval_tree_red = new IntervalTreeImpl(true); m_iterator_red = m_interval_tree_red.getIterator(); m_sorted_end_indices_red = new AttributeStreamOfInt32(0); - } else { - m_interval_tree_red.reset(m_envelopes_red, true, true); } + m_interval_tree_red.startConstruction(); + for (int i = 0; i < m_envelopes_red.size(); i++) { + Envelope2D env = m_envelopes_red.get(i); + m_interval_tree_red.addInterval(env.xmin, env.xmax); + } + m_interval_tree_red.endConstruction(); + m_sorted_end_indices_red.reserve(2 * m_envelopes_red.size()); m_sorted_end_indices_red.resize(0); @@ -381,14 +390,18 @@ private boolean initializeBlue_() { } if (m_interval_tree_blue == null) { - m_interval_tree_blue = new IntervalTreeImpl(m_envelopes_blue, true, - true); + m_interval_tree_blue = new IntervalTreeImpl(true); m_iterator_blue = m_interval_tree_blue.getIterator(); m_sorted_end_indices_blue = new AttributeStreamOfInt32(0); - } else { - m_interval_tree_blue.reset(m_envelopes_blue, true, true); } + m_interval_tree_blue.startConstruction(); + for (int i = 0; i < m_envelopes_blue.size(); i++) { + Envelope2D env = m_envelopes_blue.get(i); + m_interval_tree_blue.addInterval(env.xmin, env.xmax); + } + m_interval_tree_blue.endConstruction(); + m_sorted_end_indices_blue.reserve(2 * m_envelopes_blue.size()); m_sorted_end_indices_blue.resize(0); @@ -421,23 +434,31 @@ private boolean initializeRedBlue_() { } if (m_interval_tree_red == null) { - m_interval_tree_red = new IntervalTreeImpl(m_envelopes_red, true, - true); + m_interval_tree_red = new IntervalTreeImpl(true); m_iterator_red = m_interval_tree_red.getIterator(); m_sorted_end_indices_red = new AttributeStreamOfInt32(0); - } else { - m_interval_tree_red.reset(m_envelopes_red, true, true); } if (m_interval_tree_blue == null) { - m_interval_tree_blue = new IntervalTreeImpl(m_envelopes_blue, true, - true); + m_interval_tree_blue = new IntervalTreeImpl(true); m_iterator_blue = m_interval_tree_blue.getIterator(); m_sorted_end_indices_blue = new AttributeStreamOfInt32(0); - } else { - m_interval_tree_blue.reset(m_envelopes_blue, true, true); } + m_interval_tree_red.startConstruction(); + for (int i = 0; i < m_envelopes_red.size(); i++) { + Envelope2D env = m_envelopes_red.get(i); + m_interval_tree_red.addInterval(env.xmin, env.xmax); + } + m_interval_tree_red.endConstruction(); + + m_interval_tree_blue.startConstruction(); + for (int i = 0; i < m_envelopes_blue.size(); i++) { + Envelope2D env = m_envelopes_blue.get(i); + m_interval_tree_blue.addInterval(env.xmin, env.xmax); + } + m_interval_tree_blue.endConstruction(); + m_sorted_end_indices_red.reserve(2 * m_envelopes_red.size()); m_sorted_end_indices_blue.reserve(2 * m_envelopes_blue.size()); m_sorted_end_indices_red.resize(0); diff --git a/src/com/esri/core/geometry/InternalUtils.java b/src/com/esri/core/geometry/InternalUtils.java index 83ccbc0c..81ebd259 100644 --- a/src/com/esri/core/geometry/InternalUtils.java +++ b/src/com/esri/core/geometry/InternalUtils.java @@ -380,9 +380,12 @@ static Envelope2DIntersectorImpl getEnvelope2DIntersector( SegmentIteratorImpl segIterA = multipathImplA.querySegmentIterator(); SegmentIteratorImpl segIterB = multipathImplB.querySegmentIterator(); - ArrayList envelopes_a = new ArrayList(0); - ArrayList envelopes_b = new ArrayList(0); + Envelope2DIntersectorImpl intersector = new Envelope2DIntersectorImpl(); + intersector.setTolerance(tolerance); + + boolean b_found_red = false; + intersector.startRedConstruction(); while (segIterA.nextPath()) { while (segIterA.hasNextSegment()) { Segment segmentA = segIterA.nextSegment(); @@ -391,16 +394,20 @@ static Envelope2DIntersectorImpl getEnvelope2DIntersector( if (!env_a.isIntersecting(envInter)) continue; + b_found_red = true; Envelope2D env = new Envelope2D(); env.setCoords(env_a); - envelopes_a.add(env); + intersector.addRedEnvelope(env); verticesA.add(segIterA.getStartPointIndex()); } } + intersector.endRedConstruction(); - if (envelopes_a.isEmpty()) + if (!b_found_red) return null; + boolean b_found_blue = false; + intersector.startBlueConstruction(); while (segIterB.nextPath()) { while (segIterB.hasNextSegment()) { Segment segmentB = segIterB.nextSegment(); @@ -409,18 +416,18 @@ static Envelope2DIntersectorImpl getEnvelope2DIntersector( if (!env_b.isIntersecting(envInter)) continue; + b_found_blue = true; Envelope2D env = new Envelope2D(); env.setCoords(env_b); - envelopes_b.add(env); + intersector.addBlueEnvelope(env); verticesB.add(segIterB.getStartPointIndex()); } } + intersector.endBlueConstruction(); - if (envelopes_b.isEmpty()) + if (!b_found_blue) return null; - Envelope2DIntersectorImpl intersector = new Envelope2DIntersectorImpl( - envelopes_a, envelopes_b, tolerance); return intersector; } @@ -441,9 +448,11 @@ static Envelope2DIntersectorImpl getEnvelope2DIntersectorForOGCParts( envInter.setCoords(env_a); envInter.intersect(env_b); - ArrayList envelopes_a = new ArrayList(0); - ArrayList envelopes_b = new ArrayList(0); + Envelope2DIntersectorImpl intersector = new Envelope2DIntersectorImpl(); + intersector.setTolerance(tolerance); + boolean b_found_red = false; + intersector.startRedConstruction(); for (int ipath_a = 0; ipath_a < multipathImplA.getPathCount(); ipath_a++) { if (type_a == Geometry.GeometryType.Polygon && !multipathImplA.isExteriorRing(ipath_a)) @@ -454,15 +463,19 @@ static Envelope2DIntersectorImpl getEnvelope2DIntersectorForOGCParts( if (!env_a.isIntersecting(envInter)) continue; + b_found_red = true; Envelope2D env = new Envelope2D(); env.setCoords(env_a); - envelopes_a.add(env); + intersector.addRedEnvelope(env); parts_a.add(ipath_a); } + intersector.endRedConstruction(); - if (envelopes_a.isEmpty()) + if (!b_found_red) return null; + boolean b_found_blue = false; + intersector.startBlueConstruction(); for (int ipath_b = 0; ipath_b < multipathImplB.getPathCount(); ipath_b++) { if (type_b == Geometry.GeometryType.Polygon && !multipathImplB.isExteriorRing(ipath_b)) @@ -473,17 +486,17 @@ static Envelope2DIntersectorImpl getEnvelope2DIntersectorForOGCParts( if (!env_b.isIntersecting(envInter)) continue; + b_found_blue = true; Envelope2D env = new Envelope2D(); env.setCoords(env_b); - envelopes_b.add(env); + intersector.addBlueEnvelope(env); parts_b.add(ipath_b); } + intersector.endBlueConstruction(); - if (envelopes_b.isEmpty()) + if (!b_found_blue) return null; - Envelope2DIntersectorImpl intersector = new Envelope2DIntersectorImpl( - envelopes_a, envelopes_b, tolerance); return intersector; } diff --git a/src/com/esri/core/geometry/IntervalTreeImpl.java b/src/com/esri/core/geometry/IntervalTreeImpl.java index e8d0584d..4dd5594c 100644 --- a/src/com/esri/core/geometry/IntervalTreeImpl.java +++ b/src/com/esri/core/geometry/IntervalTreeImpl.java @@ -77,6 +77,9 @@ void resetIterator(double query, double tolerance) { * Returns an index to an interval that intersects the query. */ int next() { + if (!m_interval_tree.m_b_construction_ended) + throw new GeometryException("invalid call"); + if (m_function_index < 0) return -1; @@ -436,57 +439,41 @@ private int getCurrentEndIndex_() { } } - /** - * Default constructor for an Interval_tree_impl. You may call the reset() - * function with an array of intervals to populate or reuse the - * Interval_tree_impl. - */ - IntervalTreeImpl() { - m_root = -1; - m_c_count = 0; + IntervalTreeImpl(boolean b_offline_dynamic) { + m_b_offline_dynamic = b_offline_dynamic; + m_b_constructing = false; + m_b_construction_ended = false; } - /** - * Constructor for a static Interval_tree_impl. \param intervals A - * Dynamic_array of Envelope_1D intervals to be constructed into a static - * Interval_tree_impl. The Interval_tree_impl holds a reference to the input - * intervals. - */ - IntervalTreeImpl(ArrayList intervals) { - reset(intervals, false); + void startConstruction() { + reset_(true); } - /** - * Constructor for a static Interval_tree_impl with the option for offline - * dynamic insertion. \param intervals A Dynamic_array of Envelope_1D - * intervals to be constructed into a static Interval_tree_impl. The - * Interval_tree_impl holds a reference to the input intervals. \param - * b_offline_dynamic If set to true, then this gives the option to insert - * dynamically at anytime and in and order, only if the inserted intervals - * are from the same set of intervals as the input array. Otherwise if set - * to false, then the Interval_tree_impl is constructed statically. - */ - IntervalTreeImpl(ArrayList intervals, boolean b_offline_dynamic) { - reset(intervals, b_offline_dynamic); + void addInterval(Envelope1D interval) { + if (!m_b_constructing) + throw new GeometryException("invalid call"); + + m_intervals.add(interval); } - /** - * Constructor for a static Interval_tree_impl with the option for offline - * dynamic insertion. \param envelopes A Dynamic_array of Envelope_2D - * objects to be constructed into a static Interval_tree_impl along the - * x-axis. The Interval_tree_impl holds a reference to the input envelopes. - * \param b_offline_dynamic If set to true, then this gives the option to - * insert dynamically at anytime and in and order, only if the inserted - * envelopes are from the same set of envelopes as the input array. - * Otherwise if set to false, then the Interval_tree_impl is constructed - * statically. - */ - IntervalTreeImpl(ArrayList envelopes, - boolean b_offline_dynamic, boolean b_along_x/* - * currently ignored and - * assumed tru - */) { - reset(envelopes, b_offline_dynamic, b_along_x); + void addInterval(double min, double max) { + if (!m_b_constructing) + throw new GeometryException("invald call"); + + m_intervals.add(new Envelope1D(min, max)); + } + + void endConstruction() { + if (!m_b_constructing) + throw new GeometryException("invalid call"); + + m_b_constructing = false; + m_b_construction_ended = true; + + if (!m_b_offline_dynamic) { + insertIntervalsStatic_(); + m_c_count = m_intervals.size(); + } } /** @@ -495,24 +482,12 @@ private int getCurrentEndIndex_() { * index The index containing the interval to be inserted. */ void insert(int index) { - if (!m_b_offline_dynamic) + if (!m_b_offline_dynamic || !m_b_construction_ended) throw new IllegalArgumentException("invalid call"); if (m_root == -1) { - if (m_primary_nodes == null) { - m_primary_nodes = new StridedIndexTypeCollection(8); - m_interval_nodes = new StridedIndexTypeCollection(3); - m_end_indices_unique = new AttributeStreamOfInt32(0); - m_interval_handles = new AttributeStreamOfInt32(0); - } - - if (m_secondary_treaps == null) { - m_secondary_treaps = new Treap(); - m_secondary_treaps.setComparator(new SecondaryComparator(this)); - } - int size = (m_intervals != null ? m_intervals.size() : m_envelopes - .size()); + int size = m_intervals.size(); if (m_b_sort_intervals) { // sort @@ -528,6 +503,8 @@ void insert(int index) { m_interval_handles.resize(size, -1); m_interval_handles.setRange(-1, 0, size); m_b_sort_intervals = false; + } else { + m_interval_handles.setRange(-1, 0, size); } m_root = createPrimaryNode_(); @@ -548,6 +525,9 @@ void insert(int index) { * containing the interval to be deleted from the Interval_tree_impl. */ void remove(int index) { + if (!m_b_offline_dynamic || !m_b_construction_ended) + throw new GeometryException("invalid call"); + int interval_handle = m_interval_handles.get(index); if (interval_handle == -1) @@ -566,30 +546,16 @@ void remove(int index) { int secondary_handle = getSecondaryFromInterval_(interval_handle); int primary_handle; - if (m_b_offline_dynamic) { - primary_handle = m_secondary_treaps.getTreapData(secondary_handle); - m_secondary_treaps.deleteNode(getLeftEnd_(interval_handle), - secondary_handle); - m_secondary_treaps.deleteNode(getRightEnd_(interval_handle), - secondary_handle); - size = m_secondary_treaps.size(secondary_handle); + primary_handle = m_secondary_treaps.getTreapData(secondary_handle); + m_secondary_treaps.deleteNode(getLeftEnd_(interval_handle), + secondary_handle); + m_secondary_treaps.deleteNode(getRightEnd_(interval_handle), + secondary_handle); + size = m_secondary_treaps.size(secondary_handle); - if (size == 0) { - m_secondary_treaps.deleteTreap(secondary_handle); - setSecondaryToPrimary_(primary_handle, -1); - } - } else { - primary_handle = m_secondary_lists.getListData(secondary_handle); - m_secondary_lists.deleteElement(secondary_handle, - getLeftEnd_(interval_handle)); - m_secondary_lists.deleteElement(secondary_handle, - getRightEnd_(interval_handle)); - size = m_secondary_lists.getListSize(secondary_handle); - - if (size == 0) { - m_secondary_lists.deleteList(secondary_handle); - setSecondaryToPrimary_(primary_handle, -1); - } + if (size == 0) { + m_secondary_treaps.deleteTreap(secondary_handle); + setSecondaryToPrimary_(primary_handle, -1); } m_interval_nodes.deleteElement(interval_handle); @@ -654,46 +620,12 @@ void remove(int index) { * on the current intervals. */ void reset() { - if (!m_b_offline_dynamic) + if (!m_b_offline_dynamic || !m_b_construction_ended) throw new IllegalArgumentException("invalid call"); reset_(false); } - /* - * Resets the Interval_tree_impl to an empty state and resets the intervals. - */ - void reset(ArrayList intervals, boolean b_offline_dynamic) { - m_b_offline_dynamic = b_offline_dynamic; - - reset_(true); - m_intervals = intervals; - m_envelopes = null; - - if (!m_b_offline_dynamic) { - insertIntervalsStatic_(); - m_c_count = m_intervals.size(); - // assert(check_validation_()); - } - } - - /* - * Resets the Interval_tree_impl to an empty state and resets the intervals. - */ - void reset(ArrayList envelopes, boolean b_offline_dynamic, - boolean b_along_x /* currently ignored and assumed true */) { - m_b_offline_dynamic = b_offline_dynamic; - - reset_(true); - m_envelopes = envelopes; - m_intervals = null; - - if (!m_b_offline_dynamic) { - insertIntervalsStatic_(); - // assert(check_validation_()); - } - } - /** * Returns the number of intervals stored in the Interval_tree_impl */ @@ -762,20 +694,26 @@ public int compare(Treap treap, int e_1, int node) { private boolean m_b_offline_dynamic; private ArrayList m_intervals; - private ArrayList m_envelopes; - private StridedIndexTypeCollection m_primary_nodes; - private StridedIndexTypeCollection m_interval_nodes; - private AttributeStreamOfInt32 m_interval_handles; + private StridedIndexTypeCollection m_primary_nodes; // 8 elements for + // offline dynamic case, + // 7 elements for static + // case + private StridedIndexTypeCollection m_interval_nodes; // 3 elements + private AttributeStreamOfInt32 m_interval_handles; // for offline dynamic + // case private IndexMultiDCList m_secondary_lists; // for static case private Treap m_secondary_treaps; // for off-line dynamic case - private AttributeStreamOfInt32 m_end_indices_unique; + private AttributeStreamOfInt32 m_end_indices_unique; // for both offline + // dynamic and + // static cases private int m_c_count; private int m_root; private boolean m_b_sort_intervals; + private boolean m_b_constructing; + private boolean m_b_construction_ended; private void querySortedEndPointIndices_(AttributeStreamOfInt32 end_indices) { - int size = (m_intervals != null ? m_intervals.size() : m_envelopes - .size()); + int size = m_intervals.size(); for (int i = 0; i < 2 * size; i++) end_indices.add(i); @@ -800,21 +738,10 @@ private void querySortedDuplicatesRemoved_( } private void insertIntervalsStatic_() { - int size = (m_intervals != null ? m_intervals.size() : m_envelopes - .size()); + int size = m_intervals.size(); assert (m_b_sort_intervals); - if (m_primary_nodes == null) { - m_primary_nodes = new StridedIndexTypeCollection(8); - m_interval_nodes = new StridedIndexTypeCollection(3); - m_end_indices_unique = new AttributeStreamOfInt32(0); - m_interval_handles = new AttributeStreamOfInt32(0); - } - - if (m_secondary_lists == null) - m_secondary_lists = new IndexMultiDCList(); - // sort AttributeStreamOfInt32 end_indices_sorted = new AttributeStreamOfInt32( 0); @@ -831,20 +758,19 @@ private void insertIntervalsStatic_() { // inserted. each element contains a // primary node, a left secondary // node, and a right secondary node. - m_interval_handles.resize(size, -1); // one for each interval being - // inserted. contains a handle - // for each interval to the - // above collection. - m_interval_handles.setRange(-1, 0, size); m_secondary_lists.reserveNodes(2 * size); // one for each end point of // the original interval set // (not the unique set) + AttributeStreamOfInt32 interval_handles = (AttributeStreamOfInt32) AttributeStreamBase + .createIndexStream(size); + interval_handles.setRange(-1, 0, size); + m_root = createPrimaryNode_(); for (int i = 0; i < end_indices_sorted.size(); i++) { int e = end_indices_sorted.get(i); - int interval_handle = m_interval_handles.get(e >> 1); + int interval_handle = interval_handles.get(e >> 1); if (interval_handle != -1) {// insert the right end point assert (isRight_(e)); @@ -854,7 +780,7 @@ private void insertIntervalsStatic_() { } else {// insert the left end point assert (isLeft_(e)); interval_handle = insertIntervalEnd_(e, m_root); - m_interval_handles.set(e >> 1, interval_handle); + interval_handles.set(e >> 1, interval_handle); } } @@ -874,6 +800,9 @@ private int insertIntervalEnd_(int end_index, int root) { double discriminant_ptr = NumberUtils.TheNaN; boolean bSearching = true; + double min = getMin_(index); + double max = getMax_(index); + while (bSearching) { if (il < ir) { im = il + (ir - il) / 2; @@ -884,7 +813,7 @@ private int insertIntervalEnd_(int end_index, int root) { m_end_indices_unique.get(im + 1)); } else { assert (il == ir); - assert (getMin_(index) == getMax_(index)); + assert (min == max); if (getDiscriminantIndex1_(primary_handle) == -1) setDiscriminantIndices_(primary_handle, @@ -895,7 +824,7 @@ private int insertIntervalEnd_(int end_index, int root) { double discriminant = getDiscriminant_(primary_handle); assert (!NumberUtils.isNaN(discriminant)); - if (getMax_(index) < discriminant) { + if (max < discriminant) { if (ptr != -1) { if (ptr == primary_handle) { tertiary_handle = primary_handle; @@ -912,9 +841,12 @@ private int insertIntervalEnd_(int end_index, int root) { else setRPTR_(tertiary_handle, primary_handle); - setPPTR_(primary_handle, tertiary_handle); setRPTR_(primary_handle, ptr); - setPPTR_(ptr, primary_handle); + + if (m_b_offline_dynamic) { + setPPTR_(primary_handle, tertiary_handle); + setPPTR_(ptr, primary_handle); + } tertiary_handle = primary_handle; discriminant_tertiary = discriminant; @@ -939,7 +871,7 @@ private int insertIntervalEnd_(int end_index, int root) { continue; } - if (getMin_(index) > discriminant) { + if (min > discriminant) { if (ptr != -1) { if (ptr == primary_handle) { tertiary_handle = primary_handle; @@ -956,9 +888,12 @@ private int insertIntervalEnd_(int end_index, int root) { else setRPTR_(tertiary_handle, primary_handle); - setPPTR_(primary_handle, tertiary_handle); setLPTR_(primary_handle, ptr); - setPPTR_(ptr, primary_handle); + + if (m_b_offline_dynamic) { + setPPTR_(primary_handle, tertiary_handle); + setPPTR_(ptr, primary_handle); + } tertiary_handle = primary_handle; discriminant_tertiary = discriminant; @@ -998,14 +933,15 @@ private int insertIntervalEnd_(int end_index, int root) { if (primary_handle != ptr) { assert (primary_handle != -1); assert (getLPTR_(primary_handle) == -1 - && getRPTR_(primary_handle) == -1 && getPPTR_(primary_handle) == -1); + && getRPTR_(primary_handle) == -1 && (!m_b_offline_dynamic || getPPTR_(primary_handle) == -1)); if (discriminant < discriminant_tertiary) setLPTR_(tertiary_handle, primary_handle); else setRPTR_(tertiary_handle, primary_handle); - setPPTR_(primary_handle, tertiary_handle); + if (m_b_offline_dynamic) + setPPTR_(primary_handle, tertiary_handle); if (ptr != -1) { if (discriminant_ptr < discriminant) @@ -1013,7 +949,8 @@ private int insertIntervalEnd_(int end_index, int root) { else setRPTR_(primary_handle, ptr); - setPPTR_(ptr, primary_handle); + if (m_b_offline_dynamic) + setPPTR_(ptr, primary_handle); } } @@ -1039,26 +976,53 @@ private int createIntervalNode_() { } private void reset_(boolean b_new_intervals) { - if (m_secondary_lists != null) - m_secondary_lists.clear(); + if (b_new_intervals) { + m_b_sort_intervals = true; + m_b_constructing = true; + m_b_construction_ended = false; + + if (m_end_indices_unique == null) + m_end_indices_unique = (AttributeStreamOfInt32) (AttributeStreamBase + .createIndexStream(0)); + else + m_end_indices_unique.resize(0); + + if (m_intervals == null) + m_intervals = new ArrayList(0); + else + m_intervals.clear(); + } else { + assert (m_b_offline_dynamic && m_b_construction_ended); + m_b_sort_intervals = false; + } - if (m_secondary_treaps != null) - m_secondary_treaps.clear(); + if (m_b_offline_dynamic) { + if (m_interval_handles == null) { + m_interval_handles = (AttributeStreamOfInt32) (AttributeStreamBase + .createIndexStream(0)); + m_secondary_treaps = new Treap(); + m_secondary_treaps.setComparator(new SecondaryComparator(this)); + } else { + m_secondary_treaps.clear(); + } + } else { + if (m_secondary_lists == null) + m_secondary_lists = new IndexMultiDCList(); + else + m_secondary_lists.clear(); + } - if (m_primary_nodes != null) { + if (m_primary_nodes == null) { + m_interval_nodes = new StridedIndexTypeCollection(3); + m_primary_nodes = new StridedIndexTypeCollection( + m_b_offline_dynamic ? 8 : 7); + } else { m_interval_nodes.deleteAll(false); m_primary_nodes.deleteAll(false); } m_root = -1; m_c_count = 0; - - if (b_new_intervals) { - m_b_sort_intervals = true; - } else { - assert (m_b_offline_dynamic); - m_b_sort_intervals = false; - } } private void setDiscriminantIndices_(int primary_handle, int e_1, int e_2) { @@ -1226,30 +1190,14 @@ private int getRightEnd_(int interval_handle) { return m_interval_nodes.getField(interval_handle, 2); } - private Envelope1D getInterval_(int end_index) { - return m_intervals.get(end_index >> 1); - } - private double getMin_(int i) { - if (m_intervals != null) { - Envelope1D interval = m_intervals.get(i); - return interval.vmin; - } - - assert (m_envelopes != null); - Envelope2D envelope = m_envelopes.get(i); - return envelope.xmin; + Envelope1D interval = m_intervals.get(i); + return interval.vmin; } private double getMax_(int i) { - if (m_intervals != null) { - Envelope1D interval = m_intervals.get(i); - return interval.vmax; - } - - assert (m_envelopes != null); - Envelope2D envelope = m_envelopes.get(i); - return envelope.xmax; + Envelope1D interval = m_intervals.get(i); + return interval.vmax; } // *********** Helpers for Bucket sort************** @@ -1271,15 +1219,8 @@ private void sortEndIndicesHelper_(AttributeStreamOfInt32 end_indices, } private double getValue_(int e) { - if (m_intervals != null) { - Envelope1D interval = m_intervals.get(e >> 1); - double v = (isLeft_(e) ? interval.vmin : interval.vmax); - return v; - } - - assert (m_envelopes != null); - Envelope2D envelope = m_envelopes.get(e >> 1); - double v = (isLeft_(e) ? envelope.xmin : envelope.xmax); + Envelope1D interval = m_intervals.get(e >> 1); + double v = (isLeft_(e) ? interval.vmin : interval.vmax); return v; } diff --git a/unittest/com/esri/core/geometry/TestEnvelope2DIntersector.java b/unittest/com/esri/core/geometry/TestEnvelope2DIntersector.java index e8e867e6..4ffd03cf 100644 --- a/unittest/com/esri/core/geometry/TestEnvelope2DIntersector.java +++ b/unittest/com/esri/core/geometry/TestEnvelope2DIntersector.java @@ -44,8 +44,13 @@ public static void testEnvelope2Dintersector() { envelopes.add(env9); envelopes.add(env10); - Envelope2DIntersectorImpl intersector = new Envelope2DIntersectorImpl( - envelopes, 0.001); + Envelope2DIntersectorImpl intersector = new Envelope2DIntersectorImpl(); + intersector.setTolerance(0.001); + + intersector.startConstruction(); + for (int i = 0; i < envelopes.size(); i++) + intersector.addEnvelope(envelopes.get(i)); + intersector.endConstruction(); int count = 0; while (intersector.next()) { @@ -60,8 +65,12 @@ public static void testEnvelope2Dintersector() { assert (count == 16); - Envelope2DIntersectorImpl intersector2 = new Envelope2DIntersectorImpl( - envelopes, 0.0); + Envelope2DIntersectorImpl intersector2 = new Envelope2DIntersectorImpl(); + intersector2.setTolerance(0.0); + intersector2.startConstruction(); + for (int i = 0; i < envelopes.size(); i++) + intersector2.addEnvelope(envelopes.get(i)); + intersector2.endConstruction(); count = 0; while (intersector2.next()) { @@ -86,8 +95,14 @@ public static void testEnvelope2Dintersector() { envelopes.add(env2); envelopes.add(env3); - Envelope2DIntersectorImpl intersector3 = new Envelope2DIntersectorImpl( - envelopes, 0.001); + Envelope2DIntersectorImpl intersector3 = new Envelope2DIntersectorImpl(); + intersector3.setTolerance(0.001); + + intersector3.startConstruction(); + for (int i = 0; i < envelopes.size(); i++) + intersector3.addEnvelope(envelopes.get(i)); + intersector3.endConstruction(); + ; count = 0; while (intersector3.next()) { int env_a = intersector3.getHandleA(); @@ -108,8 +123,14 @@ public static void testEnvelope2Dintersector() { envelopes.add(env0); envelopes.add(env0); - Envelope2DIntersectorImpl intersector4 = new Envelope2DIntersectorImpl( - envelopes, 0.001); + Envelope2DIntersectorImpl intersector4 = new Envelope2DIntersectorImpl(); + intersector4.setTolerance(0.001); + + intersector4.startConstruction(); + for (int i = 0; i < envelopes.size(); i++) + intersector4.addEnvelope(envelopes.get(i)); + intersector4.endConstruction(); + count = 0; while (intersector4.next()) { int env_a = intersector4.getHandleA(); @@ -130,8 +151,14 @@ public static void testEnvelope2Dintersector() { envelopes.add(env0); envelopes.add(env0); - Envelope2DIntersectorImpl intersector5 = new Envelope2DIntersectorImpl( - envelopes, 0.001); + Envelope2DIntersectorImpl intersector5 = new Envelope2DIntersectorImpl(); + intersector5.setTolerance(0.001); + + intersector5.startConstruction(); + for (int i = 0; i < envelopes.size(); i++) + intersector5.addEnvelope(envelopes.get(i)); + intersector5.endConstruction(); + count = 0; while (intersector5.next()) { int env_a = intersector5.getHandleA(); @@ -273,8 +300,19 @@ public static void testRandom() { } } - Envelope2DIntersectorImpl intersector = new Envelope2DIntersectorImpl( - envelopes_red, envelopes_blue, 0.001); + Envelope2DIntersectorImpl intersector = new Envelope2DIntersectorImpl(); + intersector.setTolerance(0.001); + + intersector.startRedConstruction(); + for (int i = 0; i < envelopes_red.size(); i++) + intersector.addRedEnvelope(envelopes_red.get(i)); + intersector.endRedConstruction(); + + intersector.startBlueConstruction(); + for (int i = 0; i < envelopes_blue.size(); i++) + intersector.addBlueEnvelope(envelopes_blue.get(i)); + intersector.endBlueConstruction(); + while (intersector.next()) eCount++; diff --git a/unittest/com/esri/core/geometry/TestIntervalTree.java b/unittest/com/esri/core/geometry/TestIntervalTree.java index 59b85eb7..0b12d678 100644 --- a/unittest/com/esri/core/geometry/TestIntervalTree.java +++ b/unittest/com/esri/core/geometry/TestIntervalTree.java @@ -16,6 +16,14 @@ protected void tearDown() throws Exception { super.tearDown(); } + static void construct(IntervalTreeImpl interval_tree, + ArrayList intervals) { + interval_tree.startConstruction(); + for (int i = 0; i < intervals.size(); i++) + interval_tree.addInterval(intervals.get(i)); + interval_tree.endConstruction(); + } + @Test public static void testIntervalTree() { ArrayList intervals = new ArrayList(0); @@ -40,8 +48,8 @@ public static void testIntervalTree() { intervals.add(env7); int counter; - IntervalTreeImpl intervalTree = new IntervalTreeImpl(); - intervalTree.reset(intervals, false); + IntervalTreeImpl intervalTree = new IntervalTreeImpl(false); + construct(intervalTree, intervals); IntervalTreeImpl.IntervalTreeIteratorImpl iterator = intervalTree .getIterator(new Envelope1D(-1, 14), 0.0); @@ -149,18 +157,20 @@ public static void testIntervalTree() { intervals.add(env7); intervals.add(env8); - intervalTree.reset(intervals, true); - intervalTree.insert(0); - intervalTree.insert(1); - intervalTree.insert(2); - intervalTree.insert(3); - intervalTree.insert(4); - intervalTree.insert(5); - intervalTree.insert(6); - intervalTree.insert(7); - intervalTree.insert(8); + IntervalTreeImpl intervalTree2 = new IntervalTreeImpl(true); + construct(intervalTree2, intervals); + + intervalTree2.insert(0); + intervalTree2.insert(1); + intervalTree2.insert(2); + intervalTree2.insert(3); + intervalTree2.insert(4); + intervalTree2.insert(5); + intervalTree2.insert(6); + intervalTree2.insert(7); + intervalTree2.insert(8); - iterator = intervalTree.getIterator(new Envelope1D(8, 8), 0.0); + iterator = intervalTree2.getIterator(new Envelope1D(8, 8), 0.0); counter = 0; while (iterator.next() != -1) @@ -208,8 +218,9 @@ public static void testIntervalTree() { intervals.add(env5); intervals.add(env6); - intervalTree.reset(intervals, false); - iterator = intervalTree.getIterator(new Envelope1D(50, 50), 0.0); + IntervalTreeImpl intervalTree3 = new IntervalTreeImpl(false); + construct(intervalTree3, intervals); + iterator = intervalTree3.getIterator(new Envelope1D(50, 50), 0.0); assert (iterator.next() == -1); } @@ -267,8 +278,8 @@ public static void testIntervalTreeRandomConstruction() { intervalsFound.resize(intervals.size(), 0); // Just test construction for assertions - IntervalTreeImpl intervalTree = new IntervalTreeImpl(intervals, - true); + IntervalTreeImpl intervalTree = new IntervalTreeImpl(true); + construct(intervalTree, intervals); for (int j = 0; j < intervals.size(); j++) intervalTree.insert(j); From 1eb564fe8c027c7cdf4f4396a5b178d5f474be7b Mon Sep 17 00:00:00 2001 From: Sergey Tolstov Date: Fri, 12 Apr 2013 17:57:42 -0700 Subject: [PATCH 030/196] Fixes for the issues #2, #3, #4 --- src/com/esri/core/geometry/Boundary.java | 5 +- src/com/esri/core/geometry/Clipper.java | 6 +- src/com/esri/core/geometry/MultiPath.java | 3 + .../geometry/MultiVertexGeometryImpl.java | 17 ++---- .../geometry/OperatorIntersectionCursor.java | 15 ++++- .../esri/core/geometry/SpatialReference.java | 27 ++++++++- .../esri/core/geometry/ogc/OGCGeometry.java | 12 ++++ unittest/com/esri/core/geometry/TestClip.java | 56 +++++++++++++++++++ .../esri/core/geometry/TestIntersection.java | 24 ++++++++ unittest/com/esri/core/geometry/TestOGC.java | 4 ++ .../com/esri/core/geometry/TestPolygon.java | 20 +++++++ 11 files changed, 170 insertions(+), 19 deletions(-) diff --git a/src/com/esri/core/geometry/Boundary.java b/src/com/esri/core/geometry/Boundary.java index 28d0916f..f9b99f9a 100644 --- a/src/com/esri/core/geometry/Boundary.java +++ b/src/com/esri/core/geometry/Boundary.java @@ -28,8 +28,9 @@ static Geometry calculate(Geometry geom, ProgressTracker progress_tracker) { int gt = geom.getType().value(); if (gt == Geometry.GeometryType.Polygon) { Polyline dst = new Polyline(geom.getDescription()); - if (!geom.isEmpty()) - geom.copyTo(dst); + if (!geom.isEmpty()) { + ((MultiPathImpl)geom._getImpl())._copyToUnsafe((MultiPathImpl)dst._getImpl()); + } return dst; } else if (gt == Geometry.GeometryType.Polyline) { diff --git a/src/com/esri/core/geometry/Clipper.java b/src/com/esri/core/geometry/Clipper.java index 0fe2e9e6..baad9e41 100644 --- a/src/com/esri/core/geometry/Clipper.java +++ b/src/com/esri/core/geometry/Clipper.java @@ -1079,7 +1079,11 @@ void fixPaths_() { int nv = m_shape.removeVertex(first, false); if (path_size == 2) { ind = m_shape.getUserIndex(nv, m_vertices_on_extent_index); - m_vertices_on_extent.set(ind, -1); + if (ind >= 0) + m_vertices_on_extent.set(ind, -1); + else { + // this vertex is not on the extent. + } m_shape.removeVertex(nv, false); } diff --git a/src/com/esri/core/geometry/MultiPath.java b/src/com/esri/core/geometry/MultiPath.java index 5dfd1349..fe5cd675 100644 --- a/src/com/esri/core/geometry/MultiPath.java +++ b/src/com/esri/core/geometry/MultiPath.java @@ -173,6 +173,9 @@ public Envelope1D queryInterval(int semantics, int ordinate) { @Override public void copyTo(Geometry dst) { + if (getType() != dst.getType()) + throw new IllegalArgumentException(); + m_impl.copyTo((Geometry) dst._getImpl()); } diff --git a/src/com/esri/core/geometry/MultiVertexGeometryImpl.java b/src/com/esri/core/geometry/MultiVertexGeometryImpl.java index e3ffebad..9b5ce354 100644 --- a/src/com/esri/core/geometry/MultiVertexGeometryImpl.java +++ b/src/com/esri/core/geometry/MultiVertexGeometryImpl.java @@ -650,22 +650,17 @@ public void setEnvelope(Envelope env) { _setDirtyFlag(DirtyFlags.DirtyIntervals, false); } - // Checked vs. Jan 11, 2011 @Override public void copyTo(Geometry dstGeom) { - // Consider this: - // Imagine if we produce 100 copies, each will have dirty envelope to - // calculate - not good. - // However, if the copied geometry is changed immediately, we do not - // want to call _UpdateDirtyParams twice. - // That is why this is commented out so far. - // _UpdateDirtyParams(); - MultiVertexGeometryImpl dst = (MultiVertexGeometryImpl) dstGeom; if (dst.getType() != getType()) - // FIXME exc throw new IllegalArgumentException(); - + + _copyToUnsafe(dst); + } + + //Does not check geometry type. Used to copy Polygon to Polyline + void _copyToUnsafe(MultiVertexGeometryImpl dst) { _verifyAllStreams(); dst.m_description = m_description; dst.m_vertexAttributes = null; diff --git a/src/com/esri/core/geometry/OperatorIntersectionCursor.java b/src/com/esri/core/geometry/OperatorIntersectionCursor.java index 4f835010..3d07cc5a 100644 --- a/src/com/esri/core/geometry/OperatorIntersectionCursor.java +++ b/src/com/esri/core/geometry/OperatorIntersectionCursor.java @@ -143,7 +143,7 @@ GeometryCursor prepareVector_(VertexDescription descr, int dimensionMask, int inext = 0; if ((dimensionMask & 1) != 0) { if (res_vec[0] == null) - res_vec[0] = new Point(descr); + res_vec[0] = new MultiPoint(descr); inext++; } else { for (int i = 0; i < res_vec.length - 1; i++) @@ -162,12 +162,21 @@ GeometryCursor prepareVector_(VertexDescription descr, int dimensionMask, if ((dimensionMask & 4) != 0) { if (res_vec[inext] == null) res_vec[inext] = new Polygon(descr); + inext++; } else { for (int i = inext; i < res_vec.length - 1; i++) res_vec[i] = res_vec[i + 1]; } + + if (inext != 3) { + Geometry[] r = new Geometry[inext]; + for (int i = 0; i < inext; i++) + r[i] = res_vec[i]; - return new SimpleGeometryCursor(res_vec); + return new SimpleGeometryCursor(r); + } else { + return new SimpleGeometryCursor(res_vec); + } } GeometryCursor intersectEx(Geometry input_geom) { @@ -175,7 +184,7 @@ GeometryCursor intersectEx(Geometry input_geom) { Geometry dst_geom = tryNativeImplementation_(input_geom); if (dst_geom != null) { Geometry[] res_vec = new Geometry[3]; - res_vec[1 << dst_geom.getDimension()] = dst_geom; + res_vec[dst_geom.getDimension()] = dst_geom; return prepareVector_(input_geom.getDescription(), m_dimensionMask, res_vec); } diff --git a/src/com/esri/core/geometry/SpatialReference.java b/src/com/esri/core/geometry/SpatialReference.java index cf1962e2..c2621096 100644 --- a/src/com/esri/core/geometry/SpatialReference.java +++ b/src/com/esri/core/geometry/SpatialReference.java @@ -34,6 +34,7 @@ /** * A class that represents the spatial reference for the geometry. + * This class provide tolerance value for the topological and relational operations. */ public abstract class SpatialReference implements Serializable { // Note: We use writeReplace with SpatialReferenceSerializer. This field is @@ -154,9 +155,20 @@ public static SpatialReference fromJson(JsonParser parser) throws Exception { abstract int getLatestID(); /** - * Get the XY tolerance of the spatial reference + * Returns the XY tolerance of the spatial reference. * - * @return The XY tolerance of the spatial reference as double. + * The tolerance value defines the precision of topological operations, and + * "thickness" of boundaries of geometries for relational operations. + * + * When two points have xy coordinates closer than the tolerance value, they + * are considered equal. As well as when a point is within tolerance from + * the line, the point is assumed to be on the line. + * + * During topological operations the tolerance is increased by a factor of + * about 1.41 and any two points within that distance are snapped + * together. + * + * @return The XY tolerance of the spatial reference. */ public double getTolerance() { return getTolerance(VertexDescription.Semantics.POSITION); @@ -174,4 +186,15 @@ Object writeReplace() throws ObjectStreamException { srSerializer.setSpatialReferenceByValue(this); return srSerializer; } + + /** + * Returns string representation of the class for debugging purposes. The + * format and content of the returned string is not part of the contract of + * the method and is subject to change in any future release or patch + * without further notice. + */ + public String toString() { + return "[ tol: " + getTolerance() + "; wkid: " + getID() + "; wkt: " + + getText() + "]"; + } } diff --git a/src/com/esri/core/geometry/ogc/OGCGeometry.java b/src/com/esri/core/geometry/ogc/OGCGeometry.java index 1a303c0b..7f2fbc1a 100644 --- a/src/com/esri/core/geometry/ogc/OGCGeometry.java +++ b/src/com/esri/core/geometry/ogc/OGCGeometry.java @@ -117,6 +117,18 @@ public double MaxMeasure() { return e.vmax; } + /** + * Returns true if this geometric object has no anomalous geometric points, + * such as self intersection or self tangency. See the + * "Simple feature access - Part 1" document (OGC 06-103r4) for meaning of + * "simple" for each geometry type. + * + * The method has O(n log n) complexity when the input geometry is simple. + * For non-simple geometries, it terminates immediately when the first issue is + * encountered. + * + * @return True if geometry is simple and false otherwise. + */ public boolean isSimple() { return OperatorSimplifyOGC.local().isSimpleOGC(getEsriGeometry(), esriSR, true, null, null); diff --git a/unittest/com/esri/core/geometry/TestClip.java b/unittest/com/esri/core/geometry/TestClip.java index d96ee981..ee8d675f 100644 --- a/unittest/com/esri/core/geometry/TestClip.java +++ b/unittest/com/esri/core/geometry/TestClip.java @@ -339,4 +339,60 @@ public static void testClipAttributes() { VertexDescription.Semantics.M, 3, 0) == 165); } } + + @Test + public static void testClipIssue258243() { + Polygon poly1 = new Polygon(); + poly1.startPath(21.476191371901479, 41.267022001907215); + poly1.lineTo(59.669186665158051, 36.62700518555863); + poly1.lineTo(20.498578117352313, 30.363180148246094); + poly1.lineTo(18.342565836615044, 46.303295352085627); + poly1.lineTo(17.869569458621626, 23.886816966894159); + poly1.lineTo(19.835465558090434, 20); + poly1.lineTo(18.83911285048551, 43.515995498114791); + poly1.lineTo(20.864485260298004, 20.235921201027757); + poly1.lineTo(18.976127544787012, 20); + poly1.lineTo(34.290201277718218, 61.801369014954794); + poly1.lineTo(20.734727419368866, 20); + poly1.lineTo(18.545865698148113, 20); + poly1.lineTo(19.730260558565515, 20); + poly1.lineTo(19.924806216827005, 23.780315893949187); + poly1.lineTo(21.675168105421452, 36.699924873001258); + poly1.lineTo(22.500527828912158, 43.703424859922983); + poly1.lineTo(42.009527116514818, 36.995486982256089); + poly1.lineTo(24.469729873835782, 58.365871758247039); + poly1.lineTo(24.573736036545878, 36.268390409195824); + poly1.lineTo(22.726502169802746, 20); + poly1.lineTo(23.925834885228145, 20); + poly1.lineTo(25.495346880936729, 20); + poly1.lineTo(23.320941499288317, 20); + poly1.lineTo(24.05655665646276, 28.659578774758632); + poly1.lineTo(23.205940789341135, 38.491506888710504); + poly1.lineTo(21.472847203385509, 53.057228182018044); + poly1.lineTo(25.04257681654104, 20); + poly1.lineTo(25.880572351149542, 25.16102863979474); + poly1.lineTo(26.756283333879658, 20); + poly1.lineTo(21.476191371901479, 41.267022001907215); + Envelope2D env = new Envelope2D(); + env.setCoords(24.269517325186033, 19.999998900000001, + 57.305574253225409, 61.801370114954793); + + try { + Geometry output_geom = OperatorClip.local().execute(poly1, env, + SpatialReference.create(4326), null); + Envelope envPoly = new Envelope(); + poly1.queryEnvelope(envPoly); + Envelope e = new Envelope(env); + e.intersect(envPoly); + Envelope clippedEnv = new Envelope(); + output_geom.queryEnvelope(clippedEnv); + assertTrue(Math.abs(clippedEnv.getXMin() - e.getXMin()) < 1e-10 && + Math.abs(clippedEnv.getYMin() - e.getYMin()) < 1e-10 && + Math.abs(clippedEnv.getXMax() - e.getXMax()) < 1e-10 && + Math.abs(clippedEnv.getYMax() - e.getYMax()) < 1e-10); + } catch (Exception e) { + assertTrue(false); + } + + } } diff --git a/unittest/com/esri/core/geometry/TestIntersection.java b/unittest/com/esri/core/geometry/TestIntersection.java index 7f0ee8b6..990ffe9d 100644 --- a/unittest/com/esri/core/geometry/TestIntersection.java +++ b/unittest/com/esri/core/geometry/TestIntersection.java @@ -916,4 +916,28 @@ public void testFromProjection() { assertTrue(multiPointOut.getCoordinates2D()[1].y == -3360107.7777777780); } + @Test + public void testIssue258128() { + Polygon poly1 = new Polygon(); + poly1.startPath(0, 0); + poly1.lineTo(0, 10); + poly1.lineTo(10, 10); + poly1.lineTo(10, 0); + + Polygon poly2 = new Polygon(); + poly2.startPath(10.5, 4); + poly2.lineTo(10.5, 8); + poly2.lineTo(14, 10); + + try { + GeometryCursor result_cursor = OperatorIntersection.local().execute(new SimpleGeometryCursor( + poly1), new SimpleGeometryCursor(poly2), SpatialReference + .create(4326), null, 1); + while (result_cursor.next() != null) { + + } + } catch (Exception e) { + assertTrue(false); + } + } } diff --git a/unittest/com/esri/core/geometry/TestOGC.java b/unittest/com/esri/core/geometry/TestOGC.java index 3b53cebc..e3e4c524 100644 --- a/unittest/com/esri/core/geometry/TestOGC.java +++ b/unittest/com/esri/core/geometry/TestOGC.java @@ -5,6 +5,7 @@ import com.esri.core.geometry.ogc.OGCGeometry; import com.esri.core.geometry.ogc.OGCGeometryCollection; import com.esri.core.geometry.ogc.OGCLineString; +import com.esri.core.geometry.ogc.OGCMultiCurve; import com.esri.core.geometry.ogc.OGCMultiPoint; import com.esri.core.geometry.ogc.OGCMultiPolygon; import com.esri.core.geometry.ogc.OGCPoint; @@ -56,6 +57,9 @@ public void testPolygon() { .fromText("LINESTRING(-5 -5, -5 5, 5 5, 5 -5, -5 -5)")); assertTrue(b); assertTrue(!lsi.equals(ls)); + OGCMultiCurve boundary = p.boundary(); + String s = boundary.asText(); + assertTrue(s.equals("MULTILINESTRING ((-10 -10, 10 -10, 10 10, -10 10, -10 -10), (-5 -5, -5 5, 5 5, 5 -5, -5 -5))")); } public void testGeometryCollection() throws JSONException { diff --git a/unittest/com/esri/core/geometry/TestPolygon.java b/unittest/com/esri/core/geometry/TestPolygon.java index d685923b..450ac083 100644 --- a/unittest/com/esri/core/geometry/TestPolygon.java +++ b/unittest/com/esri/core/geometry/TestPolygon.java @@ -3,6 +3,8 @@ import junit.framework.TestCase; import org.junit.Test; +import com.esri.core.geometry.ogc.OGCGeometry; + public class TestPolygon extends TestCase { @Override protected void setUp() throws Exception { @@ -1405,4 +1407,22 @@ public void testGeometryCopy() { assertTrue(noException); }// end of method + + @Test + public void testBoundary() { + Geometry g = OperatorImportFromWkt + .local() + .execute( + 0, + Geometry.Type.Unknown, + "POLYGON((-10 -10, 10 -10, 10 10, -10 10, -10 -10), (-5 -5, -5 5, 5 5, 5 -5, -5 -5))", + null); + + Geometry boundary = OperatorBoundary.local().execute(g, null); + Polyline polyline = (Polyline) boundary; + polyline.reverseAllPaths(); + String s = OperatorExportToWkt.local().execute(0, boundary, null); + assertTrue(s + .equals("MULTILINESTRING ((-10 -10, 10 -10, 10 10, -10 10, -10 -10), (-5 -5, -5 5, 5 5, 5 -5, -5 -5))")); + } } From 7747adf5d9aae0efe2c7a27e9c7850557fbba710 Mon Sep 17 00:00:00 2001 From: Michael Park Date: Tue, 16 Apr 2013 08:52:26 -0700 Subject: [PATCH 031/196] Update README.md Added Maven dependency details --- README.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/README.md b/README.md index 797a5f16..c09c878c 100644 --- a/README.md +++ b/README.md @@ -9,10 +9,22 @@ The Esri Geometry API for Java can be used to enable spatial data processing in ## Instructions +Building the source: + 1. Download and unzip the .zip file, or clone the repository. 2. Deploy the esri-geometry-api.jar to the target system, add a reference to it in a Java project. 3. To build the jar, Javadoc, and run the unit-tests, run the “ant” command-line command from within the cloned directory. The ant tool runs the “build.xml” script which creates the jar, runs the unit tests, then creates the Javadoc documentation files. +The project is also available as a [Maven](http://maven.apache.org/) dependency: + +```xml + + com.esri.geometry + esri-geometry-api + 1.0 + +``` + ## Requirements * Java JDK 1.6 or greater. From 1429d239e715a8d9b09144f041e27a9e83af5c98 Mon Sep 17 00:00:00 2001 From: Mike Park Date: Tue, 16 Apr 2013 14:51:53 -0700 Subject: [PATCH 032/196] add pom for Maven builds --- pom.xml | 147 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 147 insertions(+) create mode 100644 pom.xml diff --git a/pom.xml b/pom.xml new file mode 100644 index 00000000..7f217ecc --- /dev/null +++ b/pom.xml @@ -0,0 +1,147 @@ + + 4.0.0 + + com.esri.geometry + esri-geometry-api + 1.1-SNAPSHOT + jar + + Esri Geometry API for Java + The Esri Geometry API for Java enables developers to write custom applications for analysis of spatial data. + + https://github.com/Esri/geometry-api-java + + + + The Apache Software License, Version 2.0 + http://www.apache.org/licenses/LICENSE-2.0.txt + repo + + + + + scm:git:git@github.com:Esri/geometry-api-java.git + scm:git:git@github.com:Esri/geometry-api-java.git + git@github.com:Esri/geometry-api-java.git + + + + UTF-8 + + 1.6 + 1.6 + + + 20090211 + 1.9.12 + 4.11 + 1.5 + 1.11 + + + 2.3.1 + 2.2.1 + 2.9 + + + + + org.sonatype.oss + oss-parent + 7 + + + + + org.json + json + ${json.version} + + + org.codehaus.jackson + jackson-core-asl + ${jackson.version} + + + junit + junit + ${junit.version} + test + + + com.sun.jersey + jersey-client + ${jersey.version} + test + + + com.vividsolutions + jts + ${jts.version} + test + + + + + src + unittest + + + com/esri/core/geometry + src/com/esri/core/geometry + + *.txt + + + + + + com/esri/core/geometry + unittest/com/esri/core/geometry + + *.txt + + + + + + maven-compiler-plugin + ${compiler.plugin.version} + + ${java.source.version} + ${java.target.version} + + + + org.apache.maven.plugins + maven-source-plugin + ${source.plugin.version} + + + attach-sources + + jar + + + + + + org.apache.maven.plugins + maven-javadoc-plugin + ${javadoc.plugin.version} + + http://help.arcgis.com/EN/sdk/10.0/Java_AO_ADF/api/arcobjects + http://docs.oracle.com/javase/6/docs/api/ + + + + attach-javadocs + + jar + + + + + + + From 34d2574ab44b9140e48a3a3af7e58ec030070732 Mon Sep 17 00:00:00 2001 From: Sergey Tolstov Date: Mon, 6 May 2013 17:21:36 -0700 Subject: [PATCH 033/196] Fixes for issues #5 and #6 Changed visibility of Geometry.GeometryAccelerationDegree. Added OperatorImportFromJson.execute(String) to import from string. Deprecated ImportMapGeometryFromJson. ImportFromJson should be used instead. Removed unused operator and fixed a rare bug in TopoGraph. --- src/com/esri/core/geometry/Geometry.java | 2 +- .../esri/core/geometry/GeometryEngine.java | 5 +- src/com/esri/core/geometry/Operator.java | 4 +- .../geometry/OperatorExportToESRIShape.java | 7 + .../core/geometry/OperatorFactoryLocal.java | 50 +- .../core/geometry/OperatorImportFromJson.java | 13 +- .../geometry/OperatorImportFromJsonLocal.java | 14 + ...eratorImportMapGeometryFromJsonParser.java | 51 -- ...ImportMapGeometryFromJsonParserCursor.java | 513 ------------------ ...rImportMapGeometryFromJsonParserLocal.java | 47 -- src/com/esri/core/geometry/TopoGraph.java | 16 +- .../esri/core/geometry/ogc/OGCGeometry.java | 2 +- .../esri/core/geometry/TestCommonMethods.java | 2 +- .../com/esri/core/geometry/TestRelation.java | 2 +- .../com/esri/core/geometry/TestSimplify.java | 2 +- 15 files changed, 99 insertions(+), 631 deletions(-) delete mode 100644 src/com/esri/core/geometry/OperatorImportMapGeometryFromJsonParser.java delete mode 100644 src/com/esri/core/geometry/OperatorImportMapGeometryFromJsonParserCursor.java delete mode 100644 src/com/esri/core/geometry/OperatorImportMapGeometryFromJsonParserLocal.java diff --git a/src/com/esri/core/geometry/Geometry.java b/src/com/esri/core/geometry/Geometry.java index c4dea92f..bc8b64a2 100644 --- a/src/com/esri/core/geometry/Geometry.java +++ b/src/com/esri/core/geometry/Geometry.java @@ -582,7 +582,7 @@ synchronized void _touch() { /** * Describes the degree of acceleration of the geometry. */ - enum GeometryAccelerationDegree { + static public enum GeometryAccelerationDegree { enumMild, // collectionStack = new ArrayList( 0); ArrayList structureStack = new ArrayList(0); - ArrayList indices = new ArrayList(0); + ArrayList indices = new ArrayList(0); OGCGeometry[] geometries = new OGCGeometry[1]; OGCConcreteGeometryCollection root = new OGCConcreteGeometryCollection( diff --git a/unittest/com/esri/core/geometry/TestCommonMethods.java b/unittest/com/esri/core/geometry/TestCommonMethods.java index 74a113f3..420718d4 100644 --- a/unittest/com/esri/core/geometry/TestCommonMethods.java +++ b/unittest/com/esri/core/geometry/TestCommonMethods.java @@ -243,7 +243,7 @@ public static MapGeometry fromJson(String jsonString) { jsonParser.nextToken(); OperatorImportFromJson importer = (OperatorImportFromJson) OperatorFactoryLocal .getInstance().getOperator( - Operator.Type.ImportMapGeometryFromJson); + Operator.Type.ImportFromJson); return importer.execute(Geometry.Type.Unknown, jsonParser); } catch (Exception ex) { diff --git a/unittest/com/esri/core/geometry/TestRelation.java b/unittest/com/esri/core/geometry/TestRelation.java index 73ee533e..e02ed168 100644 --- a/unittest/com/esri/core/geometry/TestRelation.java +++ b/unittest/com/esri/core/geometry/TestRelation.java @@ -4689,7 +4689,7 @@ static MapGeometry importFromJson(String jsonString) { jsonParser.nextToken(); OperatorImportFromJson importer = (OperatorImportFromJson) OperatorFactoryLocal .getInstance().getOperator( - Operator.Type.ImportMapGeometryFromJson); + Operator.Type.ImportFromJson); return importer.execute(Geometry.Type.Unknown, jsonParser); } catch (Exception ex) { diff --git a/unittest/com/esri/core/geometry/TestSimplify.java b/unittest/com/esri/core/geometry/TestSimplify.java index 774fc3dd..f39ae4de 100644 --- a/unittest/com/esri/core/geometry/TestSimplify.java +++ b/unittest/com/esri/core/geometry/TestSimplify.java @@ -1233,7 +1233,7 @@ public void testisSimpleOGC() { @Test public void testPolylineIsSimpleForOGC() throws IOException { OperatorImportFromJson importerJson = (OperatorImportFromJson) factory - .getOperator(Operator.Type.ImportMapGeometryFromJson); + .getOperator(Operator.Type.ImportFromJson); OperatorSimplify simplify = (OperatorSimplify) factory .getOperator(Operator.Type.Simplify); From e337f621896bf53a75bc6f894949cee38fe0b76a Mon Sep 17 00:00:00 2001 From: Sergey Tolstov Date: Wed, 8 May 2013 17:27:45 -0700 Subject: [PATCH 034/196] Import/Export flags need to be public. Removed a unit test with dependency on jts. --- DepFiles/unittest/jts/jts-1.11.jar | Bin 679870 -> 0 bytes build.xml | 1 - pom.xml | 7 ----- .../esri/core/geometry/ShapeExportFlags.java | 26 ++++++++++-------- .../esri/core/geometry/ShapeImportFlags.java | 15 +++++----- .../esri/core/geometry/WkbExportFlags.java | 5 +++- .../esri/core/geometry/WkbImportFlags.java | 9 ++++-- .../esri/core/geometry/WktExportFlags.java | 3 ++ .../esri/core/geometry/WktImportFlags.java | 7 +++-- .../esri/core/geometry/TestImportExport.java | 22 --------------- 10 files changed, 40 insertions(+), 55 deletions(-) delete mode 100644 DepFiles/unittest/jts/jts-1.11.jar diff --git a/DepFiles/unittest/jts/jts-1.11.jar b/DepFiles/unittest/jts/jts-1.11.jar deleted file mode 100644 index c17b15fd518ee6e4e880d7cc02df5914b732671b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 679870 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
ZYUHZa*MomJVvtFVJ7-HC49d<8 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%0A*VNYhV_u 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

Dv(Pi7&wzl(jR1epxe-cjumX}! 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%0A~;flF(JTeM=F278@94*w}~X(=06tnrx5K= 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

u!2%bTthtgo*~4fNymk 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 z%0A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

Q{o4+=N`ULivzE;8)YdXB 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 R0e%5~6Te0|zzmpy>HpM5O#%P_ diff --git a/build.xml b/build.xml index 9061de23..5bc1149d 100644 --- a/build.xml +++ b/build.xml @@ -15,7 +15,6 @@ - diff --git a/pom.xml b/pom.xml index 7f217ecc..2cda9fb4 100644 --- a/pom.xml +++ b/pom.xml @@ -36,7 +36,6 @@ 1.9.12 4.11 1.5 - 1.11 2.3.1 @@ -74,12 +73,6 @@ ${jersey.version} test - - com.vividsolutions - jts - ${jts.version} - test - diff --git a/src/com/esri/core/geometry/ShapeExportFlags.java b/src/com/esri/core/geometry/ShapeExportFlags.java index 2d195fb8..03cc9b3f 100644 --- a/src/com/esri/core/geometry/ShapeExportFlags.java +++ b/src/com/esri/core/geometry/ShapeExportFlags.java @@ -24,19 +24,21 @@ package com.esri.core.geometry; -interface ShapeExportFlags { - - public static final int ShapeExportDefaults = 0; - public static final int ShapeExportNoSwap = 1; - public static final int ShapeExportAngularDensify = 2; - public static final int ShapeExportDistanceDensify = 4; - public static final int ShapeExportTrueNaNs = 8; +/** +*Flags used by the OperatorExportToEsriShape. +*/ +public interface ShapeExportFlags { + public static final int ShapeExportDefaults = 0;//! Date: Tue, 14 May 2013 13:27:44 -0700 Subject: [PATCH 035/196] Fix descriptions. --- .../esri/core/geometry/OperatorBoundary.java | 65 +++++++++---------- 1 file changed, 32 insertions(+), 33 deletions(-) diff --git a/src/com/esri/core/geometry/OperatorBoundary.java b/src/com/esri/core/geometry/OperatorBoundary.java index 9b067bed..fd34bea1 100644 --- a/src/com/esri/core/geometry/OperatorBoundary.java +++ b/src/com/esri/core/geometry/OperatorBoundary.java @@ -24,37 +24,36 @@ package com.esri.core.geometry; public abstract class OperatorBoundary extends Operator { - @Override - public Type getType() { - return Type.Boundary; - } - - /** - *Calculates the boundary geometry. - *\param geoms The input geometry cursor. - *\param progress_tracker The progress tracker, that allows to cancel the lengthy operation. - *\return Returns a cursor over boundries for each geometry. - */ - abstract public GeometryCursor execute(GeometryCursor geoms, - ProgressTracker progress_tracker); - - /** - *Calculates the boundary. - *\param geom The input geometry. - *\param progress_tracker The progress tracker, that allows to cancel the lengthy operation. - *\return Returns the boundary. - * - *For Point - returns an empty point. - *For Multi_point - returns an empty point. - *For Envelope - returns a polyline, that bounds the envelope. - *For Polyline - returns a multipoint, using OGC specification (includes path endpoints, usinng mod 2 rule). - *For Polygon - returns a polyline that bounds the polygon (adds all rings of the polygon to a polyline). - */ - abstract public Geometry execute(Geometry geom, - ProgressTracker progress_tracker); - - public static OperatorBoundary local() { - return (OperatorBoundary) OperatorFactoryLocal.getInstance() - .getOperator(Type.Boundary); - } + @Override + public Type getType() { + return Type.Boundary; + } + + /** + * Calculates the boundary geometry. + * @param geoms The input geometry cursor. + * @param progress_tracker The progress tracker, that allows to cancel the lengthy operation. + * @return Returns a cursor over boundaries for each geometry. + */ + abstract public GeometryCursor execute(GeometryCursor geoms, + ProgressTracker progress_tracker); + + /** + * Calculates the boundary. + * @param geom The input geometry. + * @param progress_tracker The progress tracker, that allows to cancel the lengthy operation. + * @return Returns the boundary. + * + * For Point - returns an empty point. + * For Multi_point - returns an empty point. + * For Envelope - returns a polyline, that bounds the envelope. + * For Polyline - returns a multipoint, using OGC specification (includes path endpoints, using mod 2 rule). + * For Polygon - returns a polyline that bounds the polygon (adds all rings of the polygon to a polyline). + */ + abstract public Geometry execute(Geometry geom, + ProgressTracker progress_tracker); + + public static OperatorBoundary local() { + return (OperatorBoundary) OperatorFactoryLocal.getInstance().getOperator(Type.Boundary); + } } From 7cdd692209dd274a9f304935d52219b48196848e Mon Sep 17 00:00:00 2001 From: Annette Locke Date: Tue, 14 May 2013 13:37:59 -0700 Subject: [PATCH 036/196] Fix description. --- .../core/geometry/OperatorConvexHull.java | 88 +++++++++---------- src/com/esri/core/geometry/OperatorCut.java | 68 +++++++------- 2 files changed, 76 insertions(+), 80 deletions(-) diff --git a/src/com/esri/core/geometry/OperatorConvexHull.java b/src/com/esri/core/geometry/OperatorConvexHull.java index 71824414..3892abd3 100644 --- a/src/com/esri/core/geometry/OperatorConvexHull.java +++ b/src/com/esri/core/geometry/OperatorConvexHull.java @@ -22,57 +22,55 @@ email: contracts@esri.com */ -package com.esri.core.geometry; -import com.esri.core.geometry.Operator.Type; +package com.esri.core.geometry; -/** - *Creates buffer polygons around geometries. +/** + * Creates the convex hull of the input geometry. */ public abstract class OperatorConvexHull extends Operator { - @Override - public Operator.Type getType() { - return Operator.Type.ConvexHull; - } + @Override + public Operator.Type getType() { + return Operator.Type.ConvexHull; + } - /** - *Calculates the convex hull. - *@param geoms The input geometry cursor. - *@param progress_tracker The progress tracker, that allows to cancel the lengthy operation. - *@param b_merge Put true if you want the convex hull of all the geometries in the cursor combined. - *Put false if you want the convex hull of each geometry in the cursor individually. - *\return Returns a cursor over result convex hulls. - */ - abstract public GeometryCursor execute(GeometryCursor geoms, - boolean b_merge, ProgressTracker progress_tracker); + /** + * Calculates the convex hull. + * @param geoms The input geometry cursor. + * @param progress_tracker The progress tracker, that allows to cancel the lengthy operation. + * @param b_merge Put true if you want the convex hull of all the geometries in the cursor combined. + * Put false if you want the convex hull of each geometry in the cursor individually. + * @return Returns a cursor over result convex hulls. + */ + abstract public GeometryCursor execute(GeometryCursor geoms, boolean b_merge, + ProgressTracker progress_tracker); - /** - *Calculates the convex hull geometry. - *@param geom The input geometry. - *@param progress_tracker The progress tracker, that allows to cancel the lengthy operation. - *@return Returns the convex hull. - * - *For a Point - returns the same point. - *For an Envelope - returns the same envelope. - *For a MultiPoint - If the point count is one, returns the same multipoint. If the point count is two, returns a polyline of the points. Otherwise computes and returns the convex hull polygon. - *For a Segment - returns a polyline consisting of the segment. - *For a Polyline - If consists of only one segment, returns the same polyline. Otherwise computes and returns the convex hull polygon. - *For a Polygon - If more than one path, or if the path isn't already convex, computes and returns the convex hull polygon. Otherwise returns the same polygon. - */ - abstract public Geometry execute(Geometry geom, - ProgressTracker progress_tracker); + /** + * Calculates the convex hull geometry. + * @param geom The input geometry. + * @param progress_tracker The progress tracker, that allows to cancel the lengthy operation. + * @return Returns the convex hull. + * + * For a Point - returns the same point. + * For an Envelope - returns the same envelope. + * For a MultiPoint - If the point count is one, returns the same multipoint. If the point count is two, returns a polyline of the points. Otherwise computes and returns the convex hull polygon. + * For a Segment - returns a polyline consisting of the segment. + * For a Polyline - If consists of only one segment, returns the same polyline. Otherwise computes and returns the convex hull polygon. + * For a Polygon - If more than one path, or if the path isn't already convex, computes and returns the convex hull polygon. Otherwise returns the same polygon. + */ + abstract public Geometry execute(Geometry geom, + ProgressTracker progress_tracker); - /** - *Checks whether a Geometry is convex. - *@param geom The input geometry to test for convex. - *@param progress_tracker The progress tracker. - *@return Returns true if the geometry is convex. - */ - abstract public boolean isConvex(Geometry geom, - ProgressTracker progress_tracker); + /** + * Checks whether a Geometry is convex. + * @param geom The input geometry to test for convex. + * @param progress_tracker The progress tracker. + * @return Returns true if the geometry is convex. + */ + abstract public boolean isConvex(Geometry geom, + ProgressTracker progress_tracker); - public static OperatorConvexHull local() { - return (OperatorConvexHull) OperatorFactoryLocal.getInstance() - .getOperator(Type.ConvexHull); - } + public static OperatorConvexHull local() { + return (OperatorConvexHull) OperatorFactoryLocal.getInstance().getOperator(Type.ConvexHull); + } } diff --git a/src/com/esri/core/geometry/OperatorCut.java b/src/com/esri/core/geometry/OperatorCut.java index fb6d5eb7..d14b34cf 100644 --- a/src/com/esri/core/geometry/OperatorCut.java +++ b/src/com/esri/core/geometry/OperatorCut.java @@ -24,42 +24,40 @@ package com.esri.core.geometry; -import com.esri.core.geometry.Operator.Type; - /** - * Calculates distance between geometries. + * Splits the target polyline or polygon where it is crossed by the cutter polyline. */ public abstract class OperatorCut extends Operator { - @Override - public Type getType() { - return Type.Cut; - } - - /** - * Performs the Cut operation on a geometry. \param bConsiderTouch Indicates - * whether we consider a touch event a cut. This only applies to Polylines, - * but it's recommended to set this variable to True. \param cuttee The - * input geometry to be cut. \param cutter The polyline that will be used to - * divide the cuttee into pieces where they cross the cutter. \return - * Returns a GeometryCursor of cut geometries. For Polylines, all left cuts - * will be grouped together in the first Geometry, Right cuts and coincident - * cuts are grouped in the second Geometry, and each undefined cut, along - * with any uncut parts, are output as separate Polylines. For Polygons, all - * left cuts are grouped in the first Polygon, all right cuts are in the - * second Polygon, and each undefined cut, along with any left-over parts - * after cutting, are output as a separate Polygon. If there were no cuts - * the cursor will return no geometry. If the left or right cut does not - * exist, the returned geometry will be empty for this type of cut. An - * undefined cut will only be produced if a left cut or right cut was - * produced, and there was a part left over after cutting or a cut is - * bounded to the left and right of the cutter. - */ - public abstract GeometryCursor execute(boolean bConsiderTouch, - Geometry cuttee, Polyline cutter, - SpatialReference spatialReference, ProgressTracker progressTracker); - - public static OperatorCut local() { - return (OperatorCut) OperatorFactoryLocal.getInstance().getOperator( - Type.Cut); - } + @Override + public Type getType() { + return Type.Cut; + } + + /** + * Performs the Cut operation on a geometry. + * @param bConsiderTouch Indicates whether we consider a touch event a cut. + * This only applies to Polylines, but it's recommended to set this variable to True. + * @param cuttee The input geometry to be cut. + * @param cutter The polyline that will be used to divide the cuttee into + * pieces where they cross the cutter. + * @return Returns a GeometryCursor of cut geometries. For Polylines, all left cuts + * will be grouped together in the first Geometry, Right cuts and coincident + * cuts are grouped in the second Geometry, and each undefined cut, along + * with any uncut parts, are output as separate Polylines. For Polygons, all + * left cuts are grouped in the first Polygon, all right cuts are in the + * second Polygon, and each undefined cut, along with any left-over parts + * after cutting, are output as a separate Polygon. If there were no cuts + * the cursor will return no geometry. If the left or right cut does not + * exist, the returned geometry will be empty for this type of cut. An + * undefined cut will only be produced if a left cut or right cut was + * produced, and there was a part left over after cutting or a cut is + * bounded to the left and right of the cutter. + */ + public abstract GeometryCursor execute(boolean bConsiderTouch, + Geometry cuttee, Polyline cutter, SpatialReference spatialReference, + ProgressTracker progressTracker); + + public static OperatorCut local() { + return (OperatorCut) OperatorFactoryLocal.getInstance().getOperator(Type.Cut); + } } From 318e85d661416ef8a38265c78259afa7dbbf33f2 Mon Sep 17 00:00:00 2001 From: Annette Locke Date: Tue, 14 May 2013 13:38:16 -0700 Subject: [PATCH 037/196] Fix typo in description. --- .../core/geometry/OperatorGeneralize.java | 57 +++++++++---------- 1 file changed, 27 insertions(+), 30 deletions(-) diff --git a/src/com/esri/core/geometry/OperatorGeneralize.java b/src/com/esri/core/geometry/OperatorGeneralize.java index bf78aaab..8fbc640a 100644 --- a/src/com/esri/core/geometry/OperatorGeneralize.java +++ b/src/com/esri/core/geometry/OperatorGeneralize.java @@ -23,38 +23,35 @@ */ package com.esri.core.geometry; -import com.esri.core.geometry.Operator.Type; - /** - * Generalizes geometries using Douglas-Poiker algorithm. + * Generalizes geometries using Douglas-Peucker algorithm. */ public abstract class OperatorGeneralize extends Operator { - @Override - public Type getType() { - return Type.Generalize; - } - - /** - * Performs the Generalize operation on the geometry set. Point and - * MultiPoint geometries are left unchanged. Envelope is converted to a - * Polygon. - * - */ - public abstract GeometryCursor execute(GeometryCursor geoms, - double maxDeviation, boolean bRemoveDegenerateParts, - ProgressTracker progressTracker); - - /** - * Performs the Generalize operation on single geometry. Point and - * MultiPoint geometries are left unchanged. Envelope is converted to a - * Polygon. - */ - public abstract Geometry execute(Geometry geom, double maxDeviation, - boolean bRemoveDegenerateParts, ProgressTracker progressTracker); - - public static OperatorGeneralize local() { - return (OperatorGeneralize) OperatorFactoryLocal.getInstance() - .getOperator(Type.Generalize); - } + @Override + public Type getType() { + return Type.Generalize; + } + + /** + * Performs the Generalize operation on the geometry set. Point and + * MultiPoint geometries are left unchanged. Envelope is converted to a + * Polygon. + * + */ + public abstract GeometryCursor execute(GeometryCursor geoms, + double maxDeviation, boolean bRemoveDegenerateParts, + ProgressTracker progressTracker); + + /** + * Performs the Generalize operation on single geometry. Point and + * MultiPoint geometries are left unchanged. Envelope is converted to a + * Polygon. + */ + public abstract Geometry execute(Geometry geom, double maxDeviation, + boolean bRemoveDegenerateParts, ProgressTracker progressTracker); + + public static OperatorGeneralize local() { + return (OperatorGeneralize) OperatorFactoryLocal.getInstance().getOperator(Type.Generalize); + } } From 0438d3c39e7832ef11013161b310a097a64418fe Mon Sep 17 00:00:00 2001 From: Scooter Wadsworth Date: Sat, 25 May 2013 22:35:32 -0600 Subject: [PATCH 038/196] add support for empty geometries --- src/com/esri/core/geometry/Operator.java | 2 +- .../geometry/OperatorExportToGeoJson.java | 21 ++ .../OperatorExportToGeoJsonCursor.java | 202 ++++++++++++++++++ .../OperatorExportToGeoJsonLocal.java | 22 ++ .../core/geometry/OperatorFactoryLocal.java | 2 + .../esri/core/geometry/TestGeomToGeoJson.java | 66 ++++++ 6 files changed, 314 insertions(+), 1 deletion(-) create mode 100644 src/com/esri/core/geometry/OperatorExportToGeoJson.java create mode 100644 src/com/esri/core/geometry/OperatorExportToGeoJsonCursor.java create mode 100644 src/com/esri/core/geometry/OperatorExportToGeoJsonLocal.java create mode 100644 unittest/com/esri/core/geometry/TestGeomToGeoJson.java diff --git a/src/com/esri/core/geometry/Operator.java b/src/com/esri/core/geometry/Operator.java index b8e614e2..691c47f3 100644 --- a/src/com/esri/core/geometry/Operator.java +++ b/src/com/esri/core/geometry/Operator.java @@ -52,7 +52,7 @@ public enum Type { Simplify, SimplifyOGC, Offset, Generalize, - ExportToWkb, ImportFromWkb, ExportToWkt, ImportFromWkt, ImportFromGeoJson, SymmetricDifference, ConvexHull, Boundary + ExportToWkb, ImportFromWkb, ExportToWkt, ImportFromWkt, ImportFromGeoJson, ExportToGeoJson, SymmetricDifference, ConvexHull, Boundary } diff --git a/src/com/esri/core/geometry/OperatorExportToGeoJson.java b/src/com/esri/core/geometry/OperatorExportToGeoJson.java new file mode 100644 index 00000000..0cc0601a --- /dev/null +++ b/src/com/esri/core/geometry/OperatorExportToGeoJson.java @@ -0,0 +1,21 @@ +package com.esri.core.geometry; + +import com.esri.core.geometry.Operator.Type; + +public abstract class OperatorExportToGeoJson extends Operator { + @Override + public Type getType() { + return Type.ExportToGeoJson; + } + + abstract JsonCursor execute(SpatialReference spatialReference, GeometryCursor geometryCursor); + + public abstract String execute(SpatialReference spatialReference, Geometry geometry); + + public abstract String execute(Geometry geometry); + + public static OperatorExportToGeoJson local() { + return (OperatorExportToGeoJson) OperatorFactoryLocal.getInstance() + .getOperator(Type.ExportToGeoJson); + } +} diff --git a/src/com/esri/core/geometry/OperatorExportToGeoJsonCursor.java b/src/com/esri/core/geometry/OperatorExportToGeoJsonCursor.java new file mode 100644 index 00000000..767b2905 --- /dev/null +++ b/src/com/esri/core/geometry/OperatorExportToGeoJsonCursor.java @@ -0,0 +1,202 @@ +package com.esri.core.geometry; + +import com.esri.core.geometry.VertexDescription.Semantics; +import java.io.IOException; +import java.io.StringWriter; +import org.codehaus.jackson.JsonFactory; +import org.codehaus.jackson.JsonGenerationException; +import org.codehaus.jackson.JsonGenerator; + +class OperatorExportToGeoJsonCursor extends JsonCursor { + GeometryCursor m_inputGeometryCursor; + int m_index; + int m_wkid = -1; + int m_latest_wkid = -1; + String m_wkt = null; + + private static JsonFactory factory = new JsonFactory(); + + public OperatorExportToGeoJsonCursor(SpatialReference spatialReference, GeometryCursor geometryCursor) { + m_index = -1; + if (geometryCursor == null) + throw new IllegalArgumentException(); + if (spatialReference != null && !spatialReference.isLocal()) { + m_wkid = spatialReference.getOldID(); + m_wkt = spatialReference.getText(); + m_latest_wkid = spatialReference.getLatestID(); + } + m_inputGeometryCursor = geometryCursor; + } + + public OperatorExportToGeoJsonCursor(GeometryCursor geometryCursor) { + m_index = -1; + + if (geometryCursor == null) + throw new IllegalArgumentException(); + + m_inputGeometryCursor = geometryCursor; + } + + @Override + public int getID() { + return m_index; + } + + @Override + public String next() { + Geometry geometry; + if ((geometry = m_inputGeometryCursor.next()) != null) { + m_index = m_inputGeometryCursor.getGeometryID(); + return exportToGeoJson(geometry); + } + return null; + } + + private String exportToGeoJson(Geometry geometry) { + StringWriter sw = new StringWriter(); + + try { + JsonGenerator g = factory.createJsonGenerator(sw); + + int type = geometry.getType().value(); + + switch (type) { + case Geometry.GeometryType.Point: + exportPointToGeoJson(g, (Point) geometry); + break; + case Geometry.GeometryType.MultiPoint: + exportMultiPointToGeoJson(g, (MultiPoint) geometry); + break; + case Geometry.GeometryType.Polyline: + exportPolylineToGeoJson(g, (Polyline) geometry); + break; + case Geometry.GeometryType.Polygon: + exportPolygonToGeoJson(g, (Polygon) geometry); + break; + case Geometry.GeometryType.Envelope: + exportEnvelopeToGeoJson(g, (Envelope) geometry); + break; + default: + throw new RuntimeException("not implemented for this geometry type"); + } + + return sw.getBuffer().toString(); + } catch (IOException e) { + e.printStackTrace(); + return null; + } + } + + private void exportPointToGeoJson(JsonGenerator g, Point p) throws JsonGenerationException, IOException { + g.writeStartObject(); + + g.writeFieldName("type"); + g.writeString("Point"); + + g.writeFieldName("coordinates"); + + if (p.isEmpty()) { + g.writeStartArray(); + g.writeEndArray(); + } else { + g.writeStartArray(); + writeDouble(p.getX(), g); + writeDouble(p.getY(), g); + + if (p.hasAttribute(Semantics.Z)) + writeDouble(p.getZ(), g); + + g.writeEndArray(); + } + + g.writeEndObject(); + g.close(); + } + + private void exportMultiPointToGeoJson(JsonGenerator g, MultiPoint mp) throws JsonGenerationException, IOException { + g.writeStartObject(); + + g.writeFieldName("type"); + g.writeString("MultiPoint"); + + g.writeFieldName("coordinates"); + + if (mp.isEmpty()) { + g.writeStartArray(); + g.writeEndArray(); + } + + g.writeEndObject(); + g.close(); + } + + private void exportPolylineToGeoJson(JsonGenerator g, Polyline p) throws JsonGenerationException, IOException { + g.writeStartObject(); + + g.writeFieldName("type"); + g.writeString("LineString"); + + g.writeFieldName("coordinates"); + + if (p.isEmpty()) { + g.writeStartArray(); + g.writeEndArray(); + } + + g.writeEndObject(); + g.close(); + } + + private void exportPolygonToGeoJson(JsonGenerator g, Polygon p) throws JsonGenerationException, IOException { + g.writeStartObject(); + + g.writeFieldName("type"); + g.writeString("Polygon"); + + g.writeFieldName("coordinates"); + + if (p.isEmpty()) { + g.writeStartArray(); + g.writeEndArray(); + } + + g.writeEndObject(); + g.close(); + } + + private void exportEnvelopeToGeoJson(JsonGenerator g, Envelope e) throws JsonGenerationException, IOException { + boolean empty = e.isEmpty(); + + g.writeStartObject(); + + g.writeFieldName("bbox"); + g.writeStartArray(); + + if (!empty) {} + + g.writeEndArray(); + + g.writeFieldName("type"); + g.writeString("Polygon"); + + g.writeFieldName("coordinates"); + g.writeStartArray(); + + if (!empty) {} + + g.writeEndArray(); + + g.writeEndObject(); + g.close(); + } + + private void writeDouble(double d, JsonGenerator g) throws IOException, JsonGenerationException { + if (NumberUtils.isNaN(d)) { + g.writeNull(); + } else { + g.writeNumber(d); + } + + return; + } +} diff --git a/src/com/esri/core/geometry/OperatorExportToGeoJsonLocal.java b/src/com/esri/core/geometry/OperatorExportToGeoJsonLocal.java new file mode 100644 index 00000000..68a14292 --- /dev/null +++ b/src/com/esri/core/geometry/OperatorExportToGeoJsonLocal.java @@ -0,0 +1,22 @@ +package com.esri.core.geometry; + +class OperatorExportToGeoJsonLocal extends OperatorExportToGeoJson { + @Override + JsonCursor execute(SpatialReference spatialReference, GeometryCursor geometryCursor) { + return new OperatorExportToGeoJsonCursor(spatialReference, geometryCursor); + } + + @Override + public String execute(SpatialReference spatialReference, Geometry geometry) { + SimpleGeometryCursor gc = new SimpleGeometryCursor(geometry); + JsonCursor cursor = new OperatorExportToGeoJsonCursor(spatialReference, gc); + return cursor.next(); + } + + @Override + public String execute(Geometry geometry) { + SimpleGeometryCursor gc = new SimpleGeometryCursor(geometry); + JsonCursor cursor = new OperatorExportToGeoJsonCursor(gc); + return cursor.next(); + } +} diff --git a/src/com/esri/core/geometry/OperatorFactoryLocal.java b/src/com/esri/core/geometry/OperatorFactoryLocal.java index d1966b9e..14709bda 100644 --- a/src/com/esri/core/geometry/OperatorFactoryLocal.java +++ b/src/com/esri/core/geometry/OperatorFactoryLocal.java @@ -108,6 +108,8 @@ public class OperatorFactoryLocal extends OperatorFactory { new OperatorImportFromWktLocal()); st_supportedOperators.put(Type.ImportFromGeoJson, new OperatorImportFromGeoJsonLocal()); + st_supportedOperators.put(Type.ExportToGeoJson, + new OperatorExportToGeoJsonLocal()); st_supportedOperators.put(Type.Union, new OperatorUnionLocal()); st_supportedOperators.put(Type.Generalize, diff --git a/unittest/com/esri/core/geometry/TestGeomToGeoJson.java b/unittest/com/esri/core/geometry/TestGeomToGeoJson.java new file mode 100644 index 00000000..7f038842 --- /dev/null +++ b/unittest/com/esri/core/geometry/TestGeomToGeoJson.java @@ -0,0 +1,66 @@ +package com.esri.core.geometry; + +import junit.framework.TestCase; +import org.junit.Test; + +public class TestGeomToGeoJson extends TestCase { + OperatorFactoryLocal factory = OperatorFactoryLocal.getInstance(); + + @Override + protected void setUp() throws Exception { + super.setUp(); + } + + @Override + protected void tearDown() throws Exception { + super.tearDown(); + } + + @Test + public void testPoint() { + Point p = new Point(10.0, 20.0); + OperatorExportToGeoJson exporter = (OperatorExportToGeoJson) factory.getOperator(Operator.Type.ExportToGeoJson); + String result = exporter.execute(p); + assertEquals("{\"type\":\"Point\",\"coordinates\":[10.0,20.0]}", result); + } + + @Test + public void testEmptyPoint() { + Point p = new Point(); + OperatorExportToGeoJson exporter = (OperatorExportToGeoJson) factory.getOperator(Operator.Type.ExportToGeoJson); + String result = exporter.execute(p); + assertEquals("{\"type\":\"Point\",\"coordinates\":[]}", result); + } + + @Test + public void testEmptyMultiPoint() { + MultiPoint mp = new MultiPoint(); + OperatorExportToGeoJson exporter = (OperatorExportToGeoJson) factory.getOperator(Operator.Type.ExportToGeoJson); + String result = exporter.execute(mp); + assertEquals("{\"type\":\"MultiPoint\",\"coordinates\":[]}", result); + } + + @Test + public void testEmptyPolyline() { + Polyline p = new Polyline(); + OperatorExportToGeoJson exporter = (OperatorExportToGeoJson) factory.getOperator(Operator.Type.ExportToGeoJson); + String result = exporter.execute(p); + assertEquals("{\"type\":\"LineString\",\"coordinates\":[]}", result); + } + + @Test + public void testEmptyPolygon() { + Polygon p = new Polygon(); + OperatorExportToGeoJson exporter = (OperatorExportToGeoJson) factory.getOperator(Operator.Type.ExportToGeoJson); + String result = exporter.execute(p); + assertEquals("{\"type\":\"Polygon\",\"coordinates\":[]}", result); + } + + @Test + public void testEmptyEnvelope() { + Envelope e = new Envelope(); + OperatorExportToGeoJson exporter = (OperatorExportToGeoJson) factory.getOperator(Operator.Type.ExportToGeoJson); + String result = exporter.execute(e); + assertEquals("{\"bbox\":[],\"type\":\"Polygon\",\"coordinates\":[]}", result); + } +} From 1571672b9255dc155fcd68bb74caaf9ecc077a90 Mon Sep 17 00:00:00 2001 From: Scooter Wadsworth Date: Sat, 25 May 2013 22:58:26 -0600 Subject: [PATCH 039/196] add support for multipoint geometries --- .../OperatorExportToGeoJsonCursor.java | 27 ++++++++++++++++--- .../esri/core/geometry/TestGeomToGeoJson.java | 10 +++++++ 2 files changed, 34 insertions(+), 3 deletions(-) diff --git a/src/com/esri/core/geometry/OperatorExportToGeoJsonCursor.java b/src/com/esri/core/geometry/OperatorExportToGeoJsonCursor.java index 767b2905..2d60d144 100644 --- a/src/com/esri/core/geometry/OperatorExportToGeoJsonCursor.java +++ b/src/com/esri/core/geometry/OperatorExportToGeoJsonCursor.java @@ -120,12 +120,33 @@ private void exportMultiPointToGeoJson(JsonGenerator g, MultiPoint mp) throws Js g.writeString("MultiPoint"); g.writeFieldName("coordinates"); + g.writeStartArray(); - if (mp.isEmpty()) { - g.writeStartArray(); - g.writeEndArray(); + if (!mp.isEmpty()) { + MultiPointImpl mpImpl = (MultiPointImpl) mp._getImpl(); + AttributeStreamOfDbl zs = mp.hasAttribute(Semantics.Z) ? (AttributeStreamOfDbl) mpImpl.getAttributeStreamRef(Semantics.Z) : null; + + Point2D p = new Point2D(); + + int n = mp.getPointCount(); + + for(int i = 0; i < n; i++) { + mp.getXY(i, p); + + g.writeStartArray(); + + writeDouble(p.x, g); + writeDouble(p.y, g); + + if (zs != null) + writeDouble(zs.get(i), g); + + g.writeEndArray(); + } } + g.writeEndArray(); + g.writeEndObject(); g.close(); } diff --git a/unittest/com/esri/core/geometry/TestGeomToGeoJson.java b/unittest/com/esri/core/geometry/TestGeomToGeoJson.java index 7f038842..a1f9e0bc 100644 --- a/unittest/com/esri/core/geometry/TestGeomToGeoJson.java +++ b/unittest/com/esri/core/geometry/TestGeomToGeoJson.java @@ -32,6 +32,16 @@ public void testEmptyPoint() { assertEquals("{\"type\":\"Point\",\"coordinates\":[]}", result); } + @Test + public void testMultiPoint() { + MultiPoint mp = new MultiPoint(); + mp.add(10.0, 20.0); + mp.add(20.0, 30.0); + OperatorExportToGeoJson exporter = (OperatorExportToGeoJson) factory.getOperator(Operator.Type.ExportToGeoJson); + String result = exporter.execute(mp); + assertEquals("{\"type\":\"MultiPoint\",\"coordinates\":[[10.0,20.0],[20.0,30.0]]}", result); + } + @Test public void testEmptyMultiPoint() { MultiPoint mp = new MultiPoint(); From 1ca31a256bc61ff86c558a4a51c123db744f68ca Mon Sep 17 00:00:00 2001 From: Scooter Wadsworth Date: Sat, 25 May 2013 23:01:47 -0600 Subject: [PATCH 040/196] clean up support for point geometries --- .../core/geometry/OperatorExportToGeoJsonCursor.java | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/src/com/esri/core/geometry/OperatorExportToGeoJsonCursor.java b/src/com/esri/core/geometry/OperatorExportToGeoJsonCursor.java index 2d60d144..e36aa3d1 100644 --- a/src/com/esri/core/geometry/OperatorExportToGeoJsonCursor.java +++ b/src/com/esri/core/geometry/OperatorExportToGeoJsonCursor.java @@ -94,21 +94,18 @@ private void exportPointToGeoJson(JsonGenerator g, Point p) throws JsonGeneratio g.writeString("Point"); g.writeFieldName("coordinates"); + g.writeStartArray(); - if (p.isEmpty()) { - g.writeStartArray(); - g.writeEndArray(); - } else { - g.writeStartArray(); + if (!p.isEmpty()) { writeDouble(p.getX(), g); writeDouble(p.getY(), g); if (p.hasAttribute(Semantics.Z)) writeDouble(p.getZ(), g); - - g.writeEndArray(); } + g.writeEndArray(); + g.writeEndObject(); g.close(); } From dcc923c87f2b02e88816fbc3b9c919d48205e13a Mon Sep 17 00:00:00 2001 From: Scooter Wadsworth Date: Sun, 26 May 2013 00:01:45 -0600 Subject: [PATCH 041/196] add support for polyline and polygon geometries --- .../OperatorExportToGeoJsonCursor.java | 70 ++++++++++++++++--- .../esri/core/geometry/TestGeomToGeoJson.java | 46 ++++++++++++ 2 files changed, 108 insertions(+), 8 deletions(-) diff --git a/src/com/esri/core/geometry/OperatorExportToGeoJsonCursor.java b/src/com/esri/core/geometry/OperatorExportToGeoJsonCursor.java index e36aa3d1..55d03d5d 100644 --- a/src/com/esri/core/geometry/OperatorExportToGeoJsonCursor.java +++ b/src/com/esri/core/geometry/OperatorExportToGeoJsonCursor.java @@ -156,10 +156,12 @@ private void exportPolylineToGeoJson(JsonGenerator g, Polyline p) throws JsonGen g.writeFieldName("coordinates"); - if (p.isEmpty()) { - g.writeStartArray(); - g.writeEndArray(); - } + g.writeStartArray(); + + if (!p.isEmpty()) + exportPathToGeoJson(g, p); + + g.writeEndArray(); g.writeEndObject(); g.close(); @@ -173,15 +175,67 @@ private void exportPolygonToGeoJson(JsonGenerator g, Polygon p) throws JsonGener g.writeFieldName("coordinates"); - if (p.isEmpty()) { - g.writeStartArray(); - g.writeEndArray(); - } + g.writeStartArray(); + + if (!p.isEmpty()) + exportPathToGeoJson(g, p); + + g.writeEndArray(); g.writeEndObject(); g.close(); } + private void exportPathToGeoJson(JsonGenerator g, MultiPath mp) throws JsonGenerationException, IOException { + boolean isPoly = mp instanceof Polygon; + + MultiPathImpl mpImpl = (MultiPathImpl) mp._getImpl(); + AttributeStreamOfDbl zs = mp.hasAttribute(Semantics.Z) ? (AttributeStreamOfDbl) mpImpl.getAttributeStreamRef(Semantics.Z) : null; + + Point2D p = new Point2D(); + + int n = mp.getPathCount(); + + for (int i = 0; i < n; i++) { + if (isPoly) + g.writeStartArray(); + + int startIndex = mp.getPathStart(i); + int vertices = mp.getPathSize(i); + + for (int j = startIndex; j < startIndex + vertices; j++) { + mp.getXY(j, p); + + g.writeStartArray(); + + writeDouble(p.x, g); + writeDouble(p.y, g); + + if (zs != null) + writeDouble(zs.get(j), g); + + g.writeEndArray(); + } + + if (isPoly) { + mp.getXY(startIndex, p); + + g.writeStartArray(); + + writeDouble(p.x, g); + writeDouble(p.y, g); + + if (zs != null) + writeDouble(zs.get(startIndex), g); + + g.writeEndArray(); + } + + if (isPoly) + g.writeEndArray(); + } + } + private void exportEnvelopeToGeoJson(JsonGenerator g, Envelope e) throws JsonGenerationException, IOException { boolean empty = e.isEmpty(); diff --git a/unittest/com/esri/core/geometry/TestGeomToGeoJson.java b/unittest/com/esri/core/geometry/TestGeomToGeoJson.java index a1f9e0bc..c6963d95 100644 --- a/unittest/com/esri/core/geometry/TestGeomToGeoJson.java +++ b/unittest/com/esri/core/geometry/TestGeomToGeoJson.java @@ -50,6 +50,18 @@ public void testEmptyMultiPoint() { assertEquals("{\"type\":\"MultiPoint\",\"coordinates\":[]}", result); } + @Test + public void testPolyline() { + Polyline p = new Polyline(); + p.startPath(100.0, 0.0); + p.lineTo(101.0, 0.0); + p.lineTo(101.0, 1.0); + p.lineTo(100.0, 1.0); + OperatorExportToGeoJson exporter = (OperatorExportToGeoJson) factory.getOperator(Operator.Type.ExportToGeoJson); + String result = exporter.execute(p); + assertEquals("{\"type\":\"LineString\",\"coordinates\":[[100.0,0.0],[101.0,0.0],[101.0,1.0],[100.0,1.0]]}", result); + } + @Test public void testEmptyPolyline() { Polyline p = new Polyline(); @@ -58,6 +70,40 @@ public void testEmptyPolyline() { assertEquals("{\"type\":\"LineString\",\"coordinates\":[]}", result); } + @Test + public void testPolygon() { + Polygon p = new Polygon(); + p.startPath(100.0, 0.0); + p.lineTo(101.0, 0.0); + p.lineTo(101.0, 1.0); + p.lineTo(100.0, 1.0); + p.closePathWithLine(); + OperatorExportToGeoJson exporter = (OperatorExportToGeoJson) factory.getOperator(Operator.Type.ExportToGeoJson); + String result = exporter.execute(p); + assertEquals("{\"type\":\"Polygon\",\"coordinates\":[[[100.0,0.0],[101.0,0.0],[101.0,1.0],[100.0,1.0],[100.0,0.0]]]}", result); + } + + @Test + public void testPolygonWithHole() { + Polygon p = new Polygon(); + + p.startPath(100.0, 0.0); + p.lineTo(101.0, 0.0); + p.lineTo(101.0, 1.0); + p.lineTo(100.0, 1.0); + p.closePathWithLine(); + + p.startPath(100.2, 0.2); + p.lineTo(100.8, 0.2); + p.lineTo(100.8, 0.8); + p.lineTo(100.2, 0.8); + p.closePathWithLine(); + + OperatorExportToGeoJson exporter = (OperatorExportToGeoJson) factory.getOperator(Operator.Type.ExportToGeoJson); + String result = exporter.execute(p); + assertEquals("{\"type\":\"Polygon\",\"coordinates\":[[[100.0,0.0],[101.0,0.0],[101.0,1.0],[100.0,1.0],[100.0,0.0]],[[100.2,0.2],[100.8,0.2],[100.8,0.8],[100.2,0.8],[100.2,0.2]]]}", result); + } + @Test public void testEmptyPolygon() { Polygon p = new Polygon(); From 1d25a71667d8255f167c08e143a5a75310c9e83b Mon Sep 17 00:00:00 2001 From: Scooter Wadsworth Date: Sun, 26 May 2013 00:16:06 -0600 Subject: [PATCH 042/196] add support for envelope geometries (as polygon with bounding box) --- .../OperatorExportToGeoJsonCursor.java | 43 ++++++++++++++++++- .../esri/core/geometry/TestGeomToGeoJson.java | 9 ++++ 2 files changed, 50 insertions(+), 2 deletions(-) diff --git a/src/com/esri/core/geometry/OperatorExportToGeoJsonCursor.java b/src/com/esri/core/geometry/OperatorExportToGeoJsonCursor.java index 55d03d5d..07c090a1 100644 --- a/src/com/esri/core/geometry/OperatorExportToGeoJsonCursor.java +++ b/src/com/esri/core/geometry/OperatorExportToGeoJsonCursor.java @@ -244,7 +244,12 @@ private void exportEnvelopeToGeoJson(JsonGenerator g, Envelope e) throws JsonGen g.writeFieldName("bbox"); g.writeStartArray(); - if (!empty) {} + if (!empty) { + writeDouble(e.getXMin(), g); + writeDouble(e.getYMin(), g); + writeDouble(e.getXMax(), g); + writeDouble(e.getYMax(), g); + } g.writeEndArray(); @@ -254,7 +259,41 @@ private void exportEnvelopeToGeoJson(JsonGenerator g, Envelope e) throws JsonGen g.writeFieldName("coordinates"); g.writeStartArray(); - if (!empty) {} + if (!empty) { + double xmin = e.getXMin(); + double ymin = e.getYMin(); + double xmax = e.getXMax(); + double ymax = e.getYMax(); + + g.writeStartArray(); + + g.writeStartArray(); + writeDouble(xmin, g); + writeDouble(ymin, g); + g.writeEndArray(); + + g.writeStartArray(); + writeDouble(xmin, g); + writeDouble(ymax, g); + g.writeEndArray(); + + g.writeStartArray(); + writeDouble(xmax, g); + writeDouble(ymax, g); + g.writeEndArray(); + + g.writeStartArray(); + writeDouble(xmax, g); + writeDouble(ymin, g); + g.writeEndArray(); + + g.writeStartArray(); + writeDouble(xmin, g); + writeDouble(ymin, g); + g.writeEndArray(); + + g.writeEndArray(); + } g.writeEndArray(); diff --git a/unittest/com/esri/core/geometry/TestGeomToGeoJson.java b/unittest/com/esri/core/geometry/TestGeomToGeoJson.java index c6963d95..a1c88501 100644 --- a/unittest/com/esri/core/geometry/TestGeomToGeoJson.java +++ b/unittest/com/esri/core/geometry/TestGeomToGeoJson.java @@ -112,6 +112,15 @@ public void testEmptyPolygon() { assertEquals("{\"type\":\"Polygon\",\"coordinates\":[]}", result); } + @Test + public void testEnvelope() { + Envelope e = new Envelope(); + e.setCoords(-180.0, -90.0, 180.0, 90.0); + OperatorExportToGeoJson exporter = (OperatorExportToGeoJson) factory.getOperator(Operator.Type.ExportToGeoJson); + String result = exporter.execute(e); + assertEquals("{\"bbox\":[-180.0,-90.0,180.0,90.0],\"type\":\"Polygon\",\"coordinates\":[[[-180.0,-90.0],[-180.0,90.0],[180.0,90.0],[180.0,-90.0],[-180.0,-90.0]]]}", result); + } + @Test public void testEmptyEnvelope() { Envelope e = new Envelope(); From c651c1da488a0f629a0b6cb1e3a407b2c9edc1ae Mon Sep 17 00:00:00 2001 From: Scooter Wadsworth Date: Sun, 26 May 2013 15:19:19 -0600 Subject: [PATCH 043/196] add .travis.yml to repo and Travis CI status badge to README.md --- .travis.yml | 7 +++++++ README.md | 2 ++ 2 files changed, 9 insertions(+) create mode 100644 .travis.yml diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 00000000..7406d2b7 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,7 @@ +language: java +jdk: + - openjdk6 + - openjdk7 + - oraclejdk7 +notifications: + email: false \ No newline at end of file diff --git a/README.md b/README.md index c09c878c..30a1b565 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,5 @@ +[![Build Status](https://travis-ci.org/Esri/geometry-api-java.png?branch=master)](https://travis-ci.org/Esri/geometry-api-java) + # geometry-api-java The Esri Geometry API for Java can be used to enable spatial data processing in 3rd-party data-processing solutions. Developers of custom MapReduce-based applications for Hadoop can use this API for spatial processing of data in the Hadoop system. The API is also used by the [Hive UDF’s](https://github.com/Esri/spatial-framework-for-hadoop) and could be used by developers building geometry functions for 3rd-party applications such as [Cassandra]( https://cassandra.apache.org/), [HBase](http://hbase.apache.org/), [Storm](http://storm-project.net/) and many other Java-based “big data” applications. From 5c3deab52e432cc0c565783a137a73ad824a0f18 Mon Sep 17 00:00:00 2001 From: Scooter Wadsworth Date: Sun, 26 May 2013 21:14:14 -0600 Subject: [PATCH 044/196] return null coordinates for empty geometries --- .../OperatorExportToGeoJsonCursor.java | 96 ++++++------------- .../esri/core/geometry/TestGeomToGeoJson.java | 12 +-- 2 files changed, 36 insertions(+), 72 deletions(-) diff --git a/src/com/esri/core/geometry/OperatorExportToGeoJsonCursor.java b/src/com/esri/core/geometry/OperatorExportToGeoJsonCursor.java index 07c090a1..e080fbdb 100644 --- a/src/com/esri/core/geometry/OperatorExportToGeoJsonCursor.java +++ b/src/com/esri/core/geometry/OperatorExportToGeoJsonCursor.java @@ -94,17 +94,20 @@ private void exportPointToGeoJson(JsonGenerator g, Point p) throws JsonGeneratio g.writeString("Point"); g.writeFieldName("coordinates"); - g.writeStartArray(); - if (!p.isEmpty()) { + if (p.isEmpty()) { + g.writeNull(); + } else { + g.writeStartArray(); + writeDouble(p.getX(), g); writeDouble(p.getY(), g); if (p.hasAttribute(Semantics.Z)) writeDouble(p.getZ(), g); - } - g.writeEndArray(); + g.writeEndArray(); + } g.writeEndObject(); g.close(); @@ -117,9 +120,12 @@ private void exportMultiPointToGeoJson(JsonGenerator g, MultiPoint mp) throws Js g.writeString("MultiPoint"); g.writeFieldName("coordinates"); - g.writeStartArray(); - if (!mp.isEmpty()) { + if (mp.isEmpty()) { + g.writeNull(); + } else { + g.writeStartArray(); + MultiPointImpl mpImpl = (MultiPointImpl) mp._getImpl(); AttributeStreamOfDbl zs = mp.hasAttribute(Semantics.Z) ? (AttributeStreamOfDbl) mpImpl.getAttributeStreamRef(Semantics.Z) : null; @@ -140,9 +146,9 @@ private void exportMultiPointToGeoJson(JsonGenerator g, MultiPoint mp) throws Js g.writeEndArray(); } - } - g.writeEndArray(); + g.writeEndArray(); + } g.writeEndObject(); g.close(); @@ -156,12 +162,13 @@ private void exportPolylineToGeoJson(JsonGenerator g, Polyline p) throws JsonGen g.writeFieldName("coordinates"); - g.writeStartArray(); - - if (!p.isEmpty()) + if (p.isEmpty()) { + g.writeNull(); + } else { + g.writeStartArray(); exportPathToGeoJson(g, p); - - g.writeEndArray(); + g.writeEndArray(); + } g.writeEndObject(); g.close(); @@ -175,12 +182,13 @@ private void exportPolygonToGeoJson(JsonGenerator g, Polygon p) throws JsonGener g.writeFieldName("coordinates"); - g.writeStartArray(); - - if (!p.isEmpty()) + if (p.isEmpty()) { + g.writeNull(); + } else { + g.writeStartArray(); exportPathToGeoJson(g, p); - - g.writeEndArray(); + g.writeEndArray(); + } g.writeEndObject(); g.close(); @@ -240,63 +248,19 @@ private void exportEnvelopeToGeoJson(JsonGenerator g, Envelope e) throws JsonGen boolean empty = e.isEmpty(); g.writeStartObject(); - g.writeFieldName("bbox"); - g.writeStartArray(); - if (!empty) { + if (empty) { + g.writeNull(); + } else { + g.writeStartArray(); writeDouble(e.getXMin(), g); writeDouble(e.getYMin(), g); writeDouble(e.getXMax(), g); writeDouble(e.getYMax(), g); - } - - g.writeEndArray(); - - g.writeFieldName("type"); - g.writeString("Polygon"); - - g.writeFieldName("coordinates"); - g.writeStartArray(); - - if (!empty) { - double xmin = e.getXMin(); - double ymin = e.getYMin(); - double xmax = e.getXMax(); - double ymax = e.getYMax(); - - g.writeStartArray(); - - g.writeStartArray(); - writeDouble(xmin, g); - writeDouble(ymin, g); - g.writeEndArray(); - - g.writeStartArray(); - writeDouble(xmin, g); - writeDouble(ymax, g); - g.writeEndArray(); - - g.writeStartArray(); - writeDouble(xmax, g); - writeDouble(ymax, g); - g.writeEndArray(); - - g.writeStartArray(); - writeDouble(xmax, g); - writeDouble(ymin, g); - g.writeEndArray(); - - g.writeStartArray(); - writeDouble(xmin, g); - writeDouble(ymin, g); - g.writeEndArray(); - g.writeEndArray(); } - g.writeEndArray(); - g.writeEndObject(); g.close(); } diff --git a/unittest/com/esri/core/geometry/TestGeomToGeoJson.java b/unittest/com/esri/core/geometry/TestGeomToGeoJson.java index a1c88501..85801924 100644 --- a/unittest/com/esri/core/geometry/TestGeomToGeoJson.java +++ b/unittest/com/esri/core/geometry/TestGeomToGeoJson.java @@ -29,7 +29,7 @@ public void testEmptyPoint() { Point p = new Point(); OperatorExportToGeoJson exporter = (OperatorExportToGeoJson) factory.getOperator(Operator.Type.ExportToGeoJson); String result = exporter.execute(p); - assertEquals("{\"type\":\"Point\",\"coordinates\":[]}", result); + assertEquals("{\"type\":\"Point\",\"coordinates\":null}", result); } @Test @@ -47,7 +47,7 @@ public void testEmptyMultiPoint() { MultiPoint mp = new MultiPoint(); OperatorExportToGeoJson exporter = (OperatorExportToGeoJson) factory.getOperator(Operator.Type.ExportToGeoJson); String result = exporter.execute(mp); - assertEquals("{\"type\":\"MultiPoint\",\"coordinates\":[]}", result); + assertEquals("{\"type\":\"MultiPoint\",\"coordinates\":null}", result); } @Test @@ -67,7 +67,7 @@ public void testEmptyPolyline() { Polyline p = new Polyline(); OperatorExportToGeoJson exporter = (OperatorExportToGeoJson) factory.getOperator(Operator.Type.ExportToGeoJson); String result = exporter.execute(p); - assertEquals("{\"type\":\"LineString\",\"coordinates\":[]}", result); + assertEquals("{\"type\":\"LineString\",\"coordinates\":null}", result); } @Test @@ -109,7 +109,7 @@ public void testEmptyPolygon() { Polygon p = new Polygon(); OperatorExportToGeoJson exporter = (OperatorExportToGeoJson) factory.getOperator(Operator.Type.ExportToGeoJson); String result = exporter.execute(p); - assertEquals("{\"type\":\"Polygon\",\"coordinates\":[]}", result); + assertEquals("{\"type\":\"Polygon\",\"coordinates\":null}", result); } @Test @@ -118,7 +118,7 @@ public void testEnvelope() { e.setCoords(-180.0, -90.0, 180.0, 90.0); OperatorExportToGeoJson exporter = (OperatorExportToGeoJson) factory.getOperator(Operator.Type.ExportToGeoJson); String result = exporter.execute(e); - assertEquals("{\"bbox\":[-180.0,-90.0,180.0,90.0],\"type\":\"Polygon\",\"coordinates\":[[[-180.0,-90.0],[-180.0,90.0],[180.0,90.0],[180.0,-90.0],[-180.0,-90.0]]]}", result); + assertEquals("{\"bbox\":[-180.0,-90.0,180.0,90.0]}", result); } @Test @@ -126,6 +126,6 @@ public void testEmptyEnvelope() { Envelope e = new Envelope(); OperatorExportToGeoJson exporter = (OperatorExportToGeoJson) factory.getOperator(Operator.Type.ExportToGeoJson); String result = exporter.execute(e); - assertEquals("{\"bbox\":[],\"type\":\"Polygon\",\"coordinates\":[]}", result); + assertEquals("{\"bbox\":null}", result); } } From ce22b85a7d8a5d0812178f7485b48d7655f69365 Mon Sep 17 00:00:00 2001 From: Sergey Tolstov Date: Tue, 28 May 2013 10:23:47 -0700 Subject: [PATCH 045/196] Fix a bug in clipper --- src/com/esri/core/geometry/Clipper.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/com/esri/core/geometry/Clipper.java b/src/com/esri/core/geometry/Clipper.java index baad9e41..f0eea524 100644 --- a/src/com/esri/core/geometry/Clipper.java +++ b/src/com/esri/core/geometry/Clipper.java @@ -626,7 +626,7 @@ void splitSegments_(boolean b_axis_x, double clip_value) { } m_shape.removeUserIndex(usage_index); - if (sorted_vertices.size() < 4) { + if (sorted_vertices.size() < 3) { return; } From e222830a8d5772f2e9fcc8d7e2a9478197a7746b Mon Sep 17 00:00:00 2001 From: Travis L Pinney Date: Thu, 6 Jun 2013 20:55:09 -0400 Subject: [PATCH 046/196] Ignore eclipse settings file and target output --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index 6d29d23b..dbebc0c7 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,5 @@ esri-geometry-api.jar .classpath description.jardesc *.bak +.settings +target/ From acb4700ca6c565588d6a38c32fc1c8db660f0345 Mon Sep 17 00:00:00 2001 From: Sergey Tolstov Date: Fri, 7 Jun 2013 16:53:28 -0700 Subject: [PATCH 047/196] Issues #11 and #14 #14 Implement Listening geometry cursor #11 Union operator is returning inner ring as outer ring for certain polygons. Made Geometry.queryEnvelope2D public --- build.xml | 4 +- src/com/esri/core/geometry/Envelope.java | 2 +- src/com/esri/core/geometry/Geometry.java | 4 +- .../esri/core/geometry/GeometryCursor.java | 9 + src/com/esri/core/geometry/Line.java | 2 +- .../geometry/ListeningGeometryCursor.java | 72 +++ src/com/esri/core/geometry/MathUtils.java | 3 + src/com/esri/core/geometry/MultiPath.java | 4 +- .../geometry/OperatorConvexHullCursor.java | 30 +- .../esri/core/geometry/OperatorRelate.java | 20 +- .../core/geometry/OperatorUnionCursor.java | 432 +++++++++--------- .../geometry/PlaneSweepCrackerHelper.java | 24 +- src/com/esri/core/geometry/Point.java | 2 +- .../core/geometry/SegmentIntersector.java | 22 +- .../core/geometry/SpatialReferenceImpl.java | 2 +- .../esri/core/geometry/TestConvexHull.java | 75 +++ .../esri/core/geometry/TestIntersection.java | 52 +++ 17 files changed, 520 insertions(+), 239 deletions(-) create mode 100644 src/com/esri/core/geometry/ListeningGeometryCursor.java diff --git a/build.xml b/build.xml index 5bc1149d..d8302270 100644 --- a/build.xml +++ b/build.xml @@ -23,7 +23,7 @@ - + @@ -37,7 +37,7 @@ - + diff --git a/src/com/esri/core/geometry/Envelope.java b/src/com/esri/core/geometry/Envelope.java index 2005a29c..8b4ea150 100644 --- a/src/com/esri/core/geometry/Envelope.java +++ b/src/com/esri/core/geometry/Envelope.java @@ -443,7 +443,7 @@ public void queryEnvelope(Envelope env) { } @Override - void queryEnvelope2D(Envelope2D env) { + public void queryEnvelope2D(Envelope2D env) { env.xmin = m_envelope.xmin; env.ymin = m_envelope.ymin; env.xmax = m_envelope.xmax; diff --git a/src/com/esri/core/geometry/Geometry.java b/src/com/esri/core/geometry/Geometry.java index bc8b64a2..5684368d 100644 --- a/src/com/esri/core/geometry/Geometry.java +++ b/src/com/esri/core/geometry/Geometry.java @@ -289,7 +289,7 @@ public void dropAllAttributes() { /** * Returns tight bbox of the Geometry in X, Y plane. */ - abstract void queryEnvelope2D(Envelope2D env); + public abstract void queryEnvelope2D(Envelope2D env); /** * Returns tight bbox of the Geometry in 3D. @@ -301,7 +301,7 @@ public void dropAllAttributes() { * faster method than QueryEnvelope2D. However, the bbox could be larger * than the tight box. */ - void queryLooseEnvelope2D(Envelope2D env) { + public void queryLooseEnvelope2D(Envelope2D env) { queryEnvelope2D(env); } diff --git a/src/com/esri/core/geometry/GeometryCursor.java b/src/com/esri/core/geometry/GeometryCursor.java index f1584858..77a2f880 100644 --- a/src/com/esri/core/geometry/GeometryCursor.java +++ b/src/com/esri/core/geometry/GeometryCursor.java @@ -29,6 +29,7 @@ public abstract class GeometryCursor { /** *Moves the cursor to the next Geometry. Returns null when reached the end. + *The behavior of the cursor is undefined after the method returns null. */ public abstract Geometry next(); @@ -39,4 +40,12 @@ public abstract class GeometryCursor { *It is not always possible to preserve an ID during an operation. */ public abstract int getGeometryID(); + /** + *Executes a unit of work on the cursor. + *@return Returns true, if there is a geometry ready to be pulled using next(). + * + *This method is to be used together with the tick() method on the ListeningGeometryCursor. + *Call tock() for each tick() on the ListeningGeometryCursor. + */ + protected boolean tock() { return true; } } diff --git a/src/com/esri/core/geometry/Line.java b/src/com/esri/core/geometry/Line.java index c076476c..6063caf5 100644 --- a/src/com/esri/core/geometry/Line.java +++ b/src/com/esri/core/geometry/Line.java @@ -126,7 +126,7 @@ public void queryEnvelope(Envelope env) { } @Override - void queryEnvelope2D(Envelope2D env) { + public void queryEnvelope2D(Envelope2D env) { env.setCoords(m_xStart, m_yStart, m_xEnd, m_yEnd); env.normalize(); } diff --git a/src/com/esri/core/geometry/ListeningGeometryCursor.java b/src/com/esri/core/geometry/ListeningGeometryCursor.java new file mode 100644 index 00000000..04de301e --- /dev/null +++ b/src/com/esri/core/geometry/ListeningGeometryCursor.java @@ -0,0 +1,72 @@ +/* + Copyright 1995-2013 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ +package com.esri.core.geometry; + +import java.util.LinkedList; + +/** + * + * A GeometryCursor implementation that allows pushing geometries into it. + * + * To be used with aggregating operations, OperatorUnion and OperatorConvexHull, + * when the geometries are not available at the time of the execute method call, + * but are coming in a stream. + */ +public final class ListeningGeometryCursor extends GeometryCursor { + + LinkedList m_geomList = new LinkedList(); + int m_index = -1; + + public ListeningGeometryCursor() { + } + + @Override + public int getGeometryID() { + return m_index; + } + + @Override + public Geometry next() { + if (m_geomList != null && !m_geomList.isEmpty()) { + m_index++; + return m_geomList.pollFirst(); + } + + m_geomList = null;//prevent the class from being used again + return null; + } + + /** + * Call this method to add geometry to the cursor. After this method is + * called, call immediately the tock() method on the GeometryCursor returned + * by the OperatorUnion (or OperatorConvexHull with b_merge == true). Call + * next() on the GeometryCursor returned by the OperatorUnion when done + * listening to incoming geometries to finish the union operation. + * + * @param geom The geometry to be pushed into the cursor. + */ + public void tick(Geometry geom) { + m_geomList.add(geom); + } +} diff --git a/src/com/esri/core/geometry/MathUtils.java b/src/com/esri/core/geometry/MathUtils.java index 84e9ec07..7fca28eb 100644 --- a/src/com/esri/core/geometry/MathUtils.java +++ b/src/com/esri/core/geometry/MathUtils.java @@ -144,4 +144,7 @@ static double FMod(double x, double y) { static double round(double v) { return Math.floor(v + 0.5); } + static double sqr(double v) { + return v * v; + } } diff --git a/src/com/esri/core/geometry/MultiPath.java b/src/com/esri/core/geometry/MultiPath.java index fe5cd675..d0749652 100644 --- a/src/com/esri/core/geometry/MultiPath.java +++ b/src/com/esri/core/geometry/MultiPath.java @@ -149,7 +149,7 @@ public void queryEnvelope(Envelope env) { } @Override - void queryEnvelope2D(Envelope2D env) { + public void queryEnvelope2D(Envelope2D env) { m_impl.queryEnvelope2D(env); } @@ -158,7 +158,7 @@ void queryEnvelope3D(Envelope3D env) { m_impl.queryEnvelope3D(env); } - void queryLooseEnvelope(Envelope2D env) { + public void queryLooseEnvelope(Envelope2D env) { m_impl.queryLooseEnvelope2D(env); } diff --git a/src/com/esri/core/geometry/OperatorConvexHullCursor.java b/src/com/esri/core/geometry/OperatorConvexHullCursor.java index 6ee5a7bc..8c304a62 100644 --- a/src/com/esri/core/geometry/OperatorConvexHullCursor.java +++ b/src/com/esri/core/geometry/OperatorConvexHullCursor.java @@ -29,7 +29,8 @@ class OperatorConvexHullCursor extends GeometryCursor { private boolean m_b_done; private GeometryCursor m_inputGeometryCursor; private int m_index; - + ConvexHull m_hull = new ConvexHull(); + OperatorConvexHullCursor(boolean b_merge, GeometryCursor geoms, ProgressTracker progress_tracker) { m_index = -1; @@ -75,13 +76,34 @@ public int getGeometryID() { private Geometry calculateConvexHullMerging_(GeometryCursor geoms, ProgressTracker progress_tracker) { - ConvexHull hull = new ConvexHull(); Geometry geometry; while ((geometry = geoms.next()) != null) - hull.addGeometry(geometry); + m_hull.addGeometry(geometry); - return hull.getBoundingGeometry(); + return m_hull.getBoundingGeometry(); + } + + @Override + public boolean tock() { + if (m_b_done) + return true; + + if (!m_b_merge) + { + //Do not use tick/tock with the non-merging convex hull. + //Call tick/next instead, + //because tick pushes geometry into the cursor, and next performs a single convex hull on it. + throw new GeometryException("Invalid call for non merging convex hull."); + } + + Geometry geometry = m_inputGeometryCursor.next(); + if (geometry != null) { + m_hull.addGeometry(geometry); + return true; + } else { + throw new GeometryException("Expects a non-null geometry."); + } } static Geometry calculateConvexHull_(Geometry geom, diff --git a/src/com/esri/core/geometry/OperatorRelate.java b/src/com/esri/core/geometry/OperatorRelate.java index d82cb2a6..258a71bf 100644 --- a/src/com/esri/core/geometry/OperatorRelate.java +++ b/src/com/esri/core/geometry/OperatorRelate.java @@ -26,20 +26,26 @@ import com.esri.core.geometry.Operator.Type; +/** + *Performs the Relation operation between two geometries using the DE-9IM matrix encoded as a string. + * + */ public abstract class OperatorRelate extends Operator { @Override public Type getType() { return Type.Relate; } - /** - * Performs the Relation operation between two geometries using the Shape - * Comparison Language string. - * - * @return Returns True if the relation holds, False otherwise. - */ + /** + *Performs the Relation operation between two geometries using the DE-9IM matrix encoded as a string. + *@param inputGeom1 The first geometry in the relation. + *@param inputGeom2 The second geometry in the relation. + *@param sr The spatial reference of the geometries. + *@param de_9im_string The DE-9IM matrix relation encoded as a string. + *@return Returns True if the relation holds, False otherwise. + */ public abstract boolean execute(Geometry inputGeom1, Geometry inputGeom2, - SpatialReference sr, String scl, ProgressTracker progressTracker); + SpatialReference sr, String de_9im_string, ProgressTracker progressTracker); public static OperatorRelate local() { return (OperatorRelate) OperatorFactoryLocal.getInstance().getOperator( diff --git a/src/com/esri/core/geometry/OperatorUnionCursor.java b/src/com/esri/core/geometry/OperatorUnionCursor.java index 3ef0323f..cfc46871 100644 --- a/src/com/esri/core/geometry/OperatorUnionCursor.java +++ b/src/com/esri/core/geometry/OperatorUnionCursor.java @@ -41,258 +41,268 @@ class OperatorUnionCursor extends GeometryCursor { m_inputGeoms = inputGeoms1; m_spatial_reference = (SpatialReferenceImpl) (sr); m_progress_tracker = progress_tracker; - } + // Otherwise, unsupported use patternes could be produced. - @Override - public Geometry next() { - return dissolve_(); + startDissolve(); } @Override - public int getGeometryID() { - return m_index; - } - - private Geometry dissolve_() { + public Geometry next() { if (m_b_done) return null; m_b_done = true;// m_b_done is added to avoid calling // m_inputGeoms->next() second time after it returned // NULL. - // Otherwise, unsupported use patternes could be produced. - - m_index = m_inputGeoms.getGeometryID(); - - // Geometries are placed into the unionBins. - // Each bin stores geometries of certain size range. - // The bin number is calculated as log(N), where N is the number of - // vertices in geoemtry and the log is to a - // certain base (now it is 4). - ArrayList> unionBins = new ArrayList>( - 0); - AttributeStreamOfInt32 binSizes = new AttributeStreamOfInt32(0); - unionBins.ensureCapacity(128); - binSizes.reserve(128); - - for (int i = 0; i < 16; i++) - unionBins.add(null); - - int dimension = -1; - boolean bFinished = false; - int totalToUnion = 0; - int totalVertexCount = 0; - int binVertexThreshold = 10000; - boolean dissolved_something = false; - - ArrayList batchToUnion = new ArrayList(0);// a batch - // of - // geometries - // to - // union - batchToUnion.ensureCapacity(32); + + return dissolve_(); + } - boolean bLocalDone = false; - while (!bLocalDone) { - if (!bFinished) { - Geometry geom = m_inputGeoms.next(); + @Override + public int getGeometryID() { + return m_index; + } + + private void step(){ + if (!bFinished) { + Geometry geom = m_inputGeoms.next(); + + if ((m_progress_tracker != null) + && !(m_progress_tracker.progress(-1, -1))) + throw new RuntimeException("user_canceled"); + + if (geom != null) { + if (geom.getDimension() > dimension) + { + GeomPair pair = new GeomPair(); + pair.init(); + pair.geom = geom; + int sz = getVertexCount_(geom); + pair.vertex_count = sz; + int level = getLevel_(sz); - if ((m_progress_tracker != null) - && !(m_progress_tracker.progress(-1, -1))) - throw new RuntimeException("user_canceled"); + { + unionBins.clear(); + int resize = Math.max(16, level + 1); + for (int i = 0; i < resize; i++) + unionBins.add(null); + binSizes.resize(resize, 0); + unionBins.set(level, new ArrayList(0)); + unionBins.get(level).add(pair); + binSizes.set(level, sz); + totalToUnion = 1; + totalVertexCount = sz; + dimension = geom.getDimension(); + } + } else if (!geom.isEmpty() + && geom.getDimension() == dimension) { + GeomPair pair = new GeomPair(); + pair.init(); + pair.geom = geom; + int sz = getVertexCount_(geom); + pair.vertex_count = sz; + int level = getLevel_(sz); - if (geom != null) { - if (geom.getDimension() > dimension)// only master can - // change dimension var. { - GeomPair pair = new GeomPair(); - pair.init(); - pair.geom = geom; - pair.dim = geom.getDimension(); - int sz = getVertexCount_(geom); - pair.vertex_count = sz; - int level = getLevel_(sz); - - { - unionBins.clear(); - int resize = Math.max(16, level + 1); - for (int i = 0; i < resize; i++) + int resize = Math.max(unionBins.size(), level + 1); + if (resize > unionBins.size()) { + int grow = resize - unionBins.size(); + for (int i = 0; i < grow; i++) unionBins.add(null); - binSizes.resize(resize, 0); - unionBins.set(level, new ArrayList(0)); - unionBins.get(level).add(pair); - binSizes.set(level, sz); - totalToUnion = 1; - totalVertexCount = sz; - dimension = geom.getDimension(); } - } else if (!geom.isEmpty() - && geom.getDimension() == dimension) { - GeomPair pair = new GeomPair(); - pair.init(); - pair.geom = geom; - pair.dim = geom.getDimension(); - int sz = getVertexCount_(geom); - pair.vertex_count = sz; - int level = getLevel_(sz); - - { - int resize = Math.max(unionBins.size(), level + 1); - if (resize > unionBins.size()) { - int grow = resize - unionBins.size(); - for (int i = 0; i < grow; i++) - unionBins.add(null); - } - binSizes.resize(resize, 0); - if (unionBins.get(level) == null) - unionBins - .set(level, new ArrayList(0)); - - unionBins.get(level).add(pair); - binSizes.write(level, binSizes.read(level) + sz); - - totalToUnion++; - totalVertexCount += sz; - } - } else { - // skip empty or geometries of lower dimension + binSizes.resize(resize, 0); + if (unionBins.get(level) == null) + unionBins + .set(level, new ArrayList(0)); + + unionBins.get(level).add(pair); + binSizes.write(level, binSizes.read(level) + sz); + + totalToUnion++; + totalVertexCount += sz; } } else { - bFinished = true; + // skip empty or geometries of lower dimension } + } else { + bFinished = true; } + } - while (true)// union features that are in the unionBins - { - if (!bFinished) {// when we are still loading geometries, union - // geometries of the same level, starting - // with the biggest level. - int imax = -1; - int maxSz = 0; - // Find a bin that contains more than one geometry and has - // the max vertex count. - for (int i = 0, n = unionBins.size(); i < n; i++) { - if (unionBins.get(i) != null - && unionBins.get(i).size() > 1 - && binSizes.read(i) > binVertexThreshold) { - if (maxSz < binSizes.read(i)) { - maxSz = binSizes.read(i); - imax = i; - } + while (true)// union features that are in the unionBins + { + if (!bFinished) {// when we are still loading geometries, union + // geometries of the same level, starting + // with the biggest level. + int imax = -1; + int maxSz = 0; + // Find a bin that contains more than one geometry and has + // the max vertex count. + for (int i = 0, n = unionBins.size(); i < n; i++) { + if (unionBins.get(i) != null + && unionBins.get(i).size() > 1 + && binSizes.read(i) > binVertexThreshold) { + if (maxSz < binSizes.read(i)) { + maxSz = binSizes.read(i); + imax = i; } } + } - if (maxSz > 0) { - // load the found bin into the batchToUnion. - while (unionBins.get(imax).size() > 0) { - ArrayList bin = unionBins.get(imax); + if (maxSz > 0) { + // load the found bin into the batchToUnion. + while (unionBins.get(imax).size() > 0) { + ArrayList bin = unionBins.get(imax); + batchToUnion.add(bin.get(bin.size() - 1)); + bin.remove(bin.size() - 1); + totalVertexCount -= batchToUnion.get(batchToUnion + .size() - 1).vertex_count; + binSizes.write( + imax, + binSizes.read(imax) + - batchToUnion.get(batchToUnion + .size() - 1).vertex_count); + } + } + } else if (totalToUnion > 1) {// bFinished_shared == true - we + // loaded all geometries + int level = 0; + int vertexCount = 0; + for (int i = 0, n = unionBins.size(); i < n + && (batchToUnion.size() < 2 || vertexCount < binVertexThreshold); i++) { + if (unionBins.get(i) != null) { + while (!unionBins.get(i).isEmpty() + && (batchToUnion.size() < 2 || vertexCount < binVertexThreshold)) { + ArrayList bin = unionBins.get(i); batchToUnion.add(bin.get(bin.size() - 1)); bin.remove(bin.size() - 1); - totalVertexCount -= batchToUnion.get(batchToUnion + level = i; + totalVertexCount -= batchToUnion + .get(batchToUnion.size() - 1).vertex_count; + vertexCount += batchToUnion.get(batchToUnion .size() - 1).vertex_count; binSizes.write( - imax, - binSizes.read(imax) + i, + binSizes.read(i) - batchToUnion.get(batchToUnion .size() - 1).vertex_count); + continue; } } - } else if (totalToUnion > 1) {// bFinished_shared == true - we - // loaded all geometries - int level = 0; - int vertexCount = 0; - for (int i = 0, n = unionBins.size(); i < n - && (batchToUnion.size() < 2 || vertexCount < binVertexThreshold); i++) { - if (unionBins.get(i) != null) { - while (!unionBins.get(i).isEmpty() - && (batchToUnion.size() < 2 || vertexCount < binVertexThreshold)) { - ArrayList bin = unionBins.get(i); - batchToUnion.add(bin.get(bin.size() - 1)); - bin.remove(bin.size() - 1); - level = i; - totalVertexCount -= batchToUnion - .get(batchToUnion.size() - 1).vertex_count; - vertexCount += batchToUnion.get(batchToUnion - .size() - 1).vertex_count; - binSizes.write( - i, - binSizes.read(i) - - batchToUnion.get(batchToUnion - .size() - 1).vertex_count); - continue; - } - } - } - - if (batchToUnion.size() == 1)// never happens? - {// only one element. Put it back. - unionBins.get(level).add( - batchToUnion.get(batchToUnion.size() - 1)); - totalVertexCount += batchToUnion.get(batchToUnion - .size() - 1).vertex_count; - binSizes.write( - level, - binSizes.read(level) - + batchToUnion.get(batchToUnion.size() - 1).vertex_count); - batchToUnion.remove(batchToUnion.size() - 1); - } } - if (!batchToUnion.isEmpty()) { - Geometry resGeom; - int resDim; - ArrayList geoms = new ArrayList(0); - geoms.ensureCapacity(batchToUnion.size()); - for (int i = 0, n = batchToUnion.size(); i < n; i++) { - geoms.add(batchToUnion.get(i).geom); - } + if (batchToUnion.size() == 1)// never happens? + {// only one element. Put it back. + unionBins.get(level).add( + batchToUnion.get(batchToUnion.size() - 1)); + totalVertexCount += batchToUnion.get(batchToUnion + .size() - 1).vertex_count; + binSizes.write( + level, + binSizes.read(level) + + batchToUnion.get(batchToUnion.size() - 1).vertex_count); + batchToUnion.remove(batchToUnion.size() - 1); + } + } - resGeom = TopologicalOperations.dissolveDirty(geoms, - m_spatial_reference, m_progress_tracker); - // assert(Operator_factory_local::get_instance()->CanDoNewTopo(pair1.geom->get_geometry_type(), - // pair2.geom->get_geometry_type())); - // resGeom = - // Topological_operations::dissolve(batchToUnion[0].geom, - // batchToUnion[1].geom, m_spatial_reference, - // m_progress_tracker); - // Operator_factory_local::SaveJSONToTextFileDbg("c:/temp/buffer_dissolve.txt", - // *resGeom, nullptr); - resDim = resGeom.getDimension(); - - dissolved_something = true; - GeomPair pair = new GeomPair(); - pair.init(); - pair.geom = resGeom; - pair.dim = resDim; - int sz = getVertexCount_(resGeom); - pair.vertex_count = sz; - int level = getLevel_(sz); + if (!batchToUnion.isEmpty()) { + Geometry resGeom; + int resDim; + ArrayList geoms = new ArrayList(0); + geoms.ensureCapacity(batchToUnion.size()); + for (int i = 0, n = batchToUnion.size(); i < n; i++) { + geoms.add(batchToUnion.get(i).geom); + } - int resize = Math.max(unionBins.size() + 1, level); - if (resize > unionBins.size()) { - int grow = resize - unionBins.size(); - for (int i = 0; i < grow; i++) - unionBins.add(null); - } - binSizes.resize(resize, 0); + resGeom = TopologicalOperations.dissolveDirty(geoms, + m_spatial_reference, m_progress_tracker); + // assert(Operator_factory_local::get_instance()->CanDoNewTopo(pair1.geom->get_geometry_type(), + // pair2.geom->get_geometry_type())); + // resGeom = + // Topological_operations::dissolve(batchToUnion[0].geom, + // batchToUnion[1].geom, m_spatial_reference, + // m_progress_tracker); + // Operator_factory_local::SaveJSONToTextFileDbg("c:/temp/buffer_dissolve.txt", + // *resGeom, nullptr); + resDim = resGeom.getDimension(); + + dissolved_something = true; + GeomPair pair = new GeomPair(); + pair.init(); + pair.geom = resGeom; + int sz = getVertexCount_(resGeom); + pair.vertex_count = sz; + int level = getLevel_(sz); + + int resize = Math.max(unionBins.size() + 1, level); + if (resize > unionBins.size()) { + int grow = resize - unionBins.size(); + for (int i = 0; i < grow; i++) + unionBins.add(null); + } + binSizes.resize(resize, 0); - if (unionBins.get(level) == null) - unionBins.set(level, new ArrayList(0)); + if (unionBins.get(level) == null) + unionBins.set(level, new ArrayList(0)); - unionBins.get(level).add(pair); - binSizes.write(level, binSizes.read(level) + sz); - totalToUnion -= (batchToUnion.size() - 1); + unionBins.get(level).add(pair); + binSizes.write(level, binSizes.read(level) + sz); + totalToUnion -= (batchToUnion.size() - 1); - batchToUnion.clear(); - } else { - boolean bCanGo = totalToUnion == 1; - if (bFinished) - bLocalDone = true; + batchToUnion.clear(); + } else { + boolean bCanGo = totalToUnion == 1; + if (bFinished) + bLocalDone = true; - break; - } + break; } } + } + + boolean bLocalDone = false; + int dimension = -1; + boolean bFinished = false; + int totalToUnion = 0; + int totalVertexCount = 0; + int binVertexThreshold = 10000; + boolean dissolved_something = false; + + ArrayList batchToUnion = new ArrayList(0); + ArrayList> unionBins = new ArrayList>( + 0); + AttributeStreamOfInt32 binSizes = new AttributeStreamOfInt32(0); + + private void startDissolve() { + m_index = m_inputGeoms.getGeometryID(); + + // Geometries are placed into the unionBins. + // Each bin stores geometries of certain size range. + // The bin number is calculated as log(N), where N is the number of + // vertices in geoemtry and the log is to a + // certain base (now it is 4). + unionBins.ensureCapacity(128); + binSizes.reserve(128); + + for (int i = 0; i < 16; i++) + unionBins.add(null); + + batchToUnion.ensureCapacity(32); + } + + @Override + public boolean tock() { + if (!m_b_done) + { + step(); + } + return bFinished; + } + + private Geometry dissolve_() { + while (!bLocalDone) { + step(); + } Geometry resGeom = null; for (int i = 0; i < unionBins.size(); i++) { @@ -328,13 +338,11 @@ private Geometry dissolve_() { private static final class GeomPair { void init() { geom = null; - dim = -1; vertex_count = -1; } Geometry geom; int vertex_count; - int dim; } private static int getVertexCount_(Geometry geom) { diff --git a/src/com/esri/core/geometry/PlaneSweepCrackerHelper.java b/src/com/esri/core/geometry/PlaneSweepCrackerHelper.java index 902d2fb4..75e01a50 100644 --- a/src/com/esri/core/geometry/PlaneSweepCrackerHelper.java +++ b/src/com/esri/core/geometry/PlaneSweepCrackerHelper.java @@ -780,6 +780,15 @@ void processSplitHelper1_(int index, int edge, int clusterStart = getEdgeCluster(edge, 0); Point2D pt = getClusterXY(clusterStart); if (!pt.isEqual(newStart)) { + if (pt.compare(m_sweep_point) > 0 + && newStart.compare(m_sweep_point) < 0) { + m_complications = true;// point is not yet have been processed + // but moved before the sweep point, + // this will require + // repeating the cracking step and the sweep_vertical cannot + // help here + } + // This cluster's position needs to be changed getAffectedEdges(clusterStart, m_temp_edge_buffer); m_modified_clusters.add(clusterStart); @@ -790,6 +799,12 @@ void processSplitHelper1_(int index, int edge, int clusterEnd = getEdgeCluster(edge, 1); pt = getClusterXY(clusterEnd); if (!pt.isEqual(newEnd)) { + if (pt.compare(m_sweep_point) > 0 + && newEnd.compare(m_sweep_point) < 0) { + m_complications = true;// point is not yet have been processed + // but moved before the sweep point. + } + // This cluster's position needs to be changed getAffectedEdges(clusterEnd, m_temp_edge_buffer); m_modified_clusters.add(clusterEnd); @@ -874,7 +889,10 @@ void fixIntersection_(int left, int right) { m_segment_intersector.pushSegment(seg_1); m_segment_intersector.pushSegment(seg_2); - m_segment_intersector.intersect(m_tolerance, true); + if (m_segment_intersector.intersect(m_tolerance, true)) + m_complications = true; + + // #ifdef _DEBUG_CRACKING_REPORT // { // for (int resi = 0; resi < 2; resi++) @@ -1396,9 +1414,9 @@ boolean sweepImpl_() { // st_counter_insertions_peaks += edgesToDelete.size() == 0 && // m_edges_to_insert_in_sweep_structure.size() > 0; - // First step is to detelete the edges that terminate in the + // First step is to delete the edges that terminate in the // cluster. - // During that step we also determine the left and right neighbours + // During that step we also determine the left and right neighbors // of the deleted bunch and then check if those left and right // intersect or not. if (edgesToDelete.size() > 0) { diff --git a/src/com/esri/core/geometry/Point.java b/src/com/esri/core/geometry/Point.java index 29d0263f..ea4c02c7 100644 --- a/src/com/esri/core/geometry/Point.java +++ b/src/com/esri/core/geometry/Point.java @@ -477,7 +477,7 @@ public void queryEnvelope(Envelope env) { } @Override - void queryEnvelope2D(Envelope2D env) { + public void queryEnvelope2D(Envelope2D env) { if (isEmptyImpl()) { env.setEmpty(); diff --git a/src/com/esri/core/geometry/SegmentIntersector.java b/src/com/esri/core/geometry/SegmentIntersector.java index 38d2d4ba..794529fd 100644 --- a/src/com/esri/core/geometry/SegmentIntersector.java +++ b/src/com/esri/core/geometry/SegmentIntersector.java @@ -193,12 +193,14 @@ public Point getResultPoint() { } // Performs the intersection - public void intersect(double tolerance, boolean b_intersecting) { + public boolean intersect(double tolerance, boolean b_intersecting) { if (m_input_segments.size() != 2) throw new GeometryException("internal error"); m_tolerance = tolerance; - + double small_tolerance_sqr = MathUtils.sqr(tolerance * 0.01); + boolean bigmove = false; + IntersectionPart part1 = m_input_segments.get(0); IntersectionPart part2 = m_input_segments.get(1); if (b_intersecting @@ -256,15 +258,27 @@ public void intersect(double tolerance, boolean b_intersecting) { pt.add(pt_1, pt_2); ptWeight = weight1 + weight2; pt.scale(1 / ptWeight); + if (Point2D.sqrDistance(pt, pt_1) + + Point2D.sqrDistance(pt, pt_2) > small_tolerance_sqr) + bigmove = true; + } else {// for non-equal ranks, the higher rank wins if (rank1 > rank2) { pt = new Point2D(); line_1.getCoord2D(t1, pt); ptWeight = weight1; + Point2D pt_2 = new Point2D(); + line_2.getCoord2D(t2, pt_2); + if (Point2D.sqrDistance(pt, pt_2) > small_tolerance_sqr) + bigmove = true; } else { pt = new Point2D(); line_2.getCoord2D(t2, pt); ptWeight = weight2; + Point2D pt_1 = new Point2D(); + line_1.getCoord2D(t1, pt_1); + if (Point2D.sqrDistance(pt, pt_1) > small_tolerance_sqr) + bigmove = true; } } points[i] = pt; @@ -330,7 +344,7 @@ public void intersect(double tolerance, boolean b_intersecting) { i0 = i; } - return; + return bigmove; } throw new GeometryException("internal error"); @@ -338,6 +352,8 @@ public void intersect(double tolerance, boolean b_intersecting) { throw new GeometryException("internal error"); } + + return false; } public void intersect(double tolerance, Point pt_intersector_point, diff --git a/src/com/esri/core/geometry/SpatialReferenceImpl.java b/src/com/esri/core/geometry/SpatialReferenceImpl.java index 7da2b1ef..6a7540a5 100644 --- a/src/com/esri/core/geometry/SpatialReferenceImpl.java +++ b/src/com/esri/core/geometry/SpatialReferenceImpl.java @@ -134,7 +134,7 @@ public void queryValidCoordinateRange(Envelope2D env2D) { } public boolean requiresReSimplify(SpatialReference dst) { - return dst != this;// FIXME: needs to be smarter. + return dst != this; } @Override diff --git a/unittest/com/esri/core/geometry/TestConvexHull.java b/unittest/com/esri/core/geometry/TestConvexHull.java index 6c2b668a..72b6198a 100644 --- a/unittest/com/esri/core/geometry/TestConvexHull.java +++ b/unittest/com/esri/core/geometry/TestConvexHull.java @@ -885,4 +885,79 @@ public static void testMergeCursor() { } + @Test + public void testHullTickTock() { + Polygon geom1 = new Polygon(); + Polygon geom2 = new Polygon(); + Point geom3 = new Point(); + Line geom4= new Line(); + Envelope geom5 = new Envelope(); + MultiPoint geom6 = new MultiPoint(); + + // polygon + geom1.startPath(0, 0); + geom1.lineTo(0, 0); + geom1.lineTo(5, 11); + geom1.lineTo(5, 11); + geom1.lineTo(10, 0); + geom1.lineTo(10, 0); + + // polygon + geom2.startPath(0, 5); + geom2.lineTo(0, 5); + geom2.lineTo(10, 5); + geom2.lineTo(10, 5); + geom2.lineTo(5, -5); + geom2.lineTo(5, -5); + + // point + geom3.setXY(15, 1.25); + + // segment + geom4.setEndXY(-5, 1.25); + geom4.setStartXY(0, 0); + + // envelope + geom5.setCoords(0, 0, 5, 10); + + // multi_point + geom6.add(10, 5); + geom6.add(10, 10); + + // Create + ListeningGeometryCursor gc = new ListeningGeometryCursor(); + GeometryCursor ticktock = OperatorConvexHull.local().execute(gc, true, null); + + // Use tick-tock to push a geometry and do a piece of work. + gc.tick(geom1); + ticktock.tock(); + gc.tick(geom2); + ticktock.tock(); + gc.tick(geom3);// skiped one tock just for testing. + ticktock.tock(); + gc.tick(geom4); + ticktock.tock(); + gc.tick(geom5); + ticktock.tock(); + gc.tick(geom6); + ticktock.tock(); + // Get the result + Geometry result = ticktock.next(); + Polygon convex_hull = (Polygon)result; + assertTrue(OperatorConvexHull.local().isConvex(convex_hull, null)); + + Point2D p1 = convex_hull.getXY(0); + Point2D p2 = convex_hull.getXY(1); + Point2D p3 = convex_hull.getXY(2); + Point2D p4 = convex_hull.getXY(3); + Point2D p5 = convex_hull.getXY(4); + Point2D p6 = convex_hull.getXY(5); + assertTrue(p1.x == 5.0 && p1.y == 11.0); + assertTrue(p2.x == 10.0 && p2.y == 10); + assertTrue(p3.x == 15.0 && p3.y == 1.25); + assertTrue(p4.x == 5.0 && p4.y == -5.0); + assertTrue(p5.x == -5.0 && p5.y == 1.25); + assertTrue(p6.x == 0.0 && p6.y == 10.0); + } + } diff --git a/unittest/com/esri/core/geometry/TestIntersection.java b/unittest/com/esri/core/geometry/TestIntersection.java index 990ffe9d..68b0766b 100644 --- a/unittest/com/esri/core/geometry/TestIntersection.java +++ b/unittest/com/esri/core/geometry/TestIntersection.java @@ -940,4 +940,56 @@ public void testIssue258128() { assertTrue(false); } } + + @Test + public void testUnionTickTock() { + Polygon poly1 = new Polygon(); + poly1.startPath(0, 0); + poly1.lineTo(0, 10); + poly1.lineTo(10, 10); + poly1.lineTo(10, 0); + + Polygon poly2 = new Polygon(); + poly2.startPath(10.5, 4); + poly2.lineTo(10.5, 8); + poly2.lineTo(14, 10); + + Transformation2D trans = new Transformation2D(); + + Polygon poly3 = (Polygon) poly1.copy(); + trans.setShift(2, 3); + poly3.applyTransformation(trans); + + Polygon poly4 = (Polygon) poly1.copy(); + trans.setShift(-2, -3); + poly4.applyTransformation(trans); + + // Create + ListeningGeometryCursor gc = new ListeningGeometryCursor(); + GeometryCursor ticktock = OperatorUnion.local().execute(gc, null, null); + + // Use tick-tock to push a geometry and do a piece of work. + gc.tick(poly1); + ticktock.tock(); + gc.tick(poly2); + gc.tick(poly3);// skiped one tock just for testing. + ticktock.tock(); + gc.tick(poly4); + ticktock.tock(); + // Get the result + Geometry result = ticktock.next(); + + // Use ListeningGeometryCursor to put all geometries in. + ListeningGeometryCursor gc2 = new ListeningGeometryCursor(); + gc2.tick(poly1); + gc2.tick(poly2); + gc2.tick(poly3); + gc2.tick(poly4); + + GeometryCursor res = OperatorUnion.local().execute(gc2, null, null); + // Calling next will process all geometries at once. + Geometry result2 = res.next(); + assertTrue(result.equals(result2)); + } + } From 243515ac2db6c2b8d5ce7720fc1745a609ecd926 Mon Sep 17 00:00:00 2001 From: Sergey Tolstov Date: Fri, 7 Jun 2013 17:11:29 -0700 Subject: [PATCH 048/196] attempt to trigger travis build this commit adds nothing just to trigger travis build --- build.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/build.xml b/build.xml index d8302270..35cc55e1 100644 --- a/build.xml +++ b/build.xml @@ -85,3 +85,4 @@ + From 67b08c47a292aa1db4a8d3653425fbe1a614f44f Mon Sep 17 00:00:00 2001 From: Scooter Wadsworth Date: Thu, 13 Jun 2013 01:15:22 -0400 Subject: [PATCH 049/196] add GeometryEngine#geometryToGeoJson and OGCGeometry#asGeoJson (with tests) --- .../esri/core/geometry/GeometryEngine.java | 54 ++++++- .../esri/core/geometry/ogc/OGCGeometry.java | 7 + .../esri/core/geometry/TestGeomToGeoJson.java | 136 ++++++++++++++++++ 3 files changed, 193 insertions(+), 4 deletions(-) diff --git a/src/com/esri/core/geometry/GeometryEngine.java b/src/com/esri/core/geometry/GeometryEngine.java index 8323726b..faeb34a4 100644 --- a/src/com/esri/core/geometry/GeometryEngine.java +++ b/src/com/esri/core/geometry/GeometryEngine.java @@ -37,7 +37,9 @@ public class GeometryEngine { private static OperatorFactoryLocal factory = OperatorFactoryLocal .getInstance(); - + + + /** * Imports the MapGeometry from its JSON representation. M and Z values are * not imported from JSON representation. @@ -88,6 +90,47 @@ public static String geometryToJson(SpatialReference spatialReference, return exporter.execute(spatialReference, geometry); } + public static String geometryToGeoJson(Geometry geometry) { + OperatorExportToGeoJson exporter = (OperatorExportToGeoJson) factory + .getOperator(Operator.Type.ExportToGeoJson); + + return exporter.execute(geometry); + } + + /** + * Exports the specified geometry instance to its GeoJSON representation. + * + * @see GeometryEngine#geometryToGeoJson(SpatialReference spatialReference, + * Geometry geometry) + * + * @param wkid + * The spatial reference Well Known ID to be used for the GeoJSON representation. + * @param geometry + * The geometry to be exported to GeoJSON. + * @return The GeoJSON representation of the specified geometry. + */ + public static String geometryToGeoJson(int wkid, Geometry geometry) { + return GeometryEngine.geometryToGeoJson( + wkid > 0 ? SpatialReference.create(wkid) : null, geometry); + } + + /** + * Exports the specified geometry instance to it's JSON representation. + * + * @param spatialReference + * The spatial reference of associated object. + * @param geometry + * The geometry. + * @return The GeoJSON representation of the specified geometry. + */ + public static String geometryToGeoJson(SpatialReference spatialReference, + Geometry geometry) { + OperatorExportToGeoJson exporter = (OperatorExportToGeoJson) factory + .getOperator(Operator.Type.ExportToGeoJson); + + return exporter.execute(spatialReference, geometry); + } + /** * Imports geometry from the ESRI shape file format. * @@ -557,7 +600,8 @@ public static Geometry[] cut(Geometry cuttee, Polyline cutter, return cutsList.toArray(new Geometry[0]); } - + + /** * Calculates a buffer polygon for each geometry at each of the * corresponding specified distances. It is assumed that all geometries have @@ -600,7 +644,8 @@ public static Polygon[] buffer(Geometry[] geometries, return buffers; } } - + + /** * Calculates a buffer polygon of the geometry as specified by the * distance input. The buffer is implemented in the xy-plane. @@ -773,7 +818,8 @@ static boolean isSimple(Geometry geometry, SpatialReference spatialReference) { boolean result = op.isSimpleAsFeature(geometry, spatialReference, null); return result; } - + + /** * A geodesic distance is the shortest distance between any two points on the earth's surface when the earth's * surface is approximated by a spheroid. The function returns the shortest distance between two points on the diff --git a/src/com/esri/core/geometry/ogc/OGCGeometry.java b/src/com/esri/core/geometry/ogc/OGCGeometry.java index 49328ab1..47c5c304 100644 --- a/src/com/esri/core/geometry/ogc/OGCGeometry.java +++ b/src/com/esri/core/geometry/ogc/OGCGeometry.java @@ -24,6 +24,7 @@ import com.esri.core.geometry.OperatorBuffer; import com.esri.core.geometry.OperatorConvexHull; import com.esri.core.geometry.OperatorExportToWkb; +import com.esri.core.geometry.OperatorExportToGeoJson; import com.esri.core.geometry.OperatorFactoryLocal; import com.esri.core.geometry.OperatorImportFromESRIShape; import com.esri.core.geometry.OperatorImportFromGeoJson; @@ -89,6 +90,12 @@ public ByteBuffer asBinary() { return op.execute(0, getEsriGeometry(), null); } + public String asGeoJson() { + OperatorExportToGeoJson op = (OperatorExportToGeoJson) OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.ExportToGeoJson); + return op.execute(getEsriGeometry()); + } + public boolean isEmpty() { return getEsriGeometry().isEmpty(); } diff --git a/unittest/com/esri/core/geometry/TestGeomToGeoJson.java b/unittest/com/esri/core/geometry/TestGeomToGeoJson.java index 85801924..f47b83ea 100644 --- a/unittest/com/esri/core/geometry/TestGeomToGeoJson.java +++ b/unittest/com/esri/core/geometry/TestGeomToGeoJson.java @@ -1,5 +1,10 @@ package com.esri.core.geometry; +import com.esri.core.geometry.ogc.OGCGeometry; +import com.esri.core.geometry.ogc.OGCPoint; +import com.esri.core.geometry.ogc.OGCMultiPoint; +import com.esri.core.geometry.ogc.OGCLineString; +import com.esri.core.geometry.ogc.OGCPolygon; import junit.framework.TestCase; import org.junit.Test; @@ -32,6 +37,21 @@ public void testEmptyPoint() { assertEquals("{\"type\":\"Point\",\"coordinates\":null}", result); } + @Test + public void testPointGeometryEngine() { + Point p = new Point(10.0, 20.0); + String result = GeometryEngine.geometryToGeoJson(p); + assertEquals("{\"type\":\"Point\",\"coordinates\":[10.0,20.0]}", result); + } + + @Test + public void testOGCPoint() { + Point p = new Point(10.0, 20.0); + OGCGeometry ogcPoint = new OGCPoint(p, null); + String result = ogcPoint.asGeoJson(); + assertEquals("{\"type\":\"Point\",\"coordinates\":[10.0,20.0]}", result); + } + @Test public void testMultiPoint() { MultiPoint mp = new MultiPoint(); @@ -50,6 +70,25 @@ public void testEmptyMultiPoint() { assertEquals("{\"type\":\"MultiPoint\",\"coordinates\":null}", result); } + @Test + public void testMultiPointGeometryEngine() { + MultiPoint mp = new MultiPoint(); + mp.add(10.0, 20.0); + mp.add(20.0, 30.0); + String result = GeometryEngine.geometryToGeoJson(mp); + assertEquals("{\"type\":\"MultiPoint\",\"coordinates\":[[10.0,20.0],[20.0,30.0]]}", result); + } + + @Test + public void testOGCMultiPoint() { + MultiPoint mp = new MultiPoint(); + mp.add(10.0, 20.0); + mp.add(20.0, 30.0); + OGCMultiPoint ogcMultiPoint = new OGCMultiPoint(mp, null); + String result = ogcMultiPoint.asGeoJson(); + assertEquals("{\"type\":\"MultiPoint\",\"coordinates\":[[10.0,20.0],[20.0,30.0]]}", result); + } + @Test public void testPolyline() { Polyline p = new Polyline(); @@ -70,6 +109,29 @@ public void testEmptyPolyline() { assertEquals("{\"type\":\"LineString\",\"coordinates\":null}", result); } + @Test + public void testPolylineGeometryEngine() { + Polyline p = new Polyline(); + p.startPath(100.0, 0.0); + p.lineTo(101.0, 0.0); + p.lineTo(101.0, 1.0); + p.lineTo(100.0, 1.0); + String result = GeometryEngine.geometryToGeoJson(p); + assertEquals("{\"type\":\"LineString\",\"coordinates\":[[100.0,0.0],[101.0,0.0],[101.0,1.0],[100.0,1.0]]}", result); + } + + @Test + public void testOGCLineString() { + Polyline p = new Polyline(); + p.startPath(100.0, 0.0); + p.lineTo(101.0, 0.0); + p.lineTo(101.0, 1.0); + p.lineTo(100.0, 1.0); + OGCLineString ogcLineString = new OGCLineString(p, 0, null); + String result = ogcLineString.asGeoJson(); + assertEquals("{\"type\":\"LineString\",\"coordinates\":[[100.0,0.0],[101.0,0.0],[101.0,1.0],[100.0,1.0]]}", result); + } + @Test public void testPolygon() { Polygon p = new Polygon(); @@ -112,6 +174,72 @@ public void testEmptyPolygon() { assertEquals("{\"type\":\"Polygon\",\"coordinates\":null}", result); } + @Test + public void testPolygonGeometryEngine() { + Polygon p = new Polygon(); + p.startPath(100.0, 0.0); + p.lineTo(101.0, 0.0); + p.lineTo(101.0, 1.0); + p.lineTo(100.0, 1.0); + p.closePathWithLine(); + String result = GeometryEngine.geometryToGeoJson(p); + assertEquals("{\"type\":\"Polygon\",\"coordinates\":[[[100.0,0.0],[101.0,0.0],[101.0,1.0],[100.0,1.0],[100.0,0.0]]]}", result); + } + + @Test + public void testOGCPolygon() { + Polygon p = new Polygon(); + p.startPath(100.0, 0.0); + p.lineTo(101.0, 0.0); + p.lineTo(101.0, 1.0); + p.lineTo(100.0, 1.0); + p.closePathWithLine(); + OGCPolygon ogcPolygon = new OGCPolygon(p, null); + String result = ogcPolygon.asGeoJson(); + assertEquals("{\"type\":\"Polygon\",\"coordinates\":[[[100.0,0.0],[101.0,0.0],[101.0,1.0],[100.0,1.0],[100.0,0.0]]]}", result); + } + + @Test + public void testPolygonWithHoleGeometryEngine() { + Polygon p = new Polygon(); + + p.startPath(100.0, 0.0); + p.lineTo(101.0, 0.0); + p.lineTo(101.0, 1.0); + p.lineTo(100.0, 1.0); + p.closePathWithLine(); + + p.startPath(100.2, 0.2); + p.lineTo(100.8, 0.2); + p.lineTo(100.8, 0.8); + p.lineTo(100.2, 0.8); + p.closePathWithLine(); + + String result = GeometryEngine.geometryToGeoJson(p); + assertEquals("{\"type\":\"Polygon\",\"coordinates\":[[[100.0,0.0],[101.0,0.0],[101.0,1.0],[100.0,1.0],[100.0,0.0]],[[100.2,0.2],[100.8,0.2],[100.8,0.8],[100.2,0.8],[100.2,0.2]]]}", result); + } + + @Test + public void testOGCPolygonWithHole() { + Polygon p = new Polygon(); + + p.startPath(100.0, 0.0); + p.lineTo(101.0, 0.0); + p.lineTo(101.0, 1.0); + p.lineTo(100.0, 1.0); + p.closePathWithLine(); + + p.startPath(100.2, 0.2); + p.lineTo(100.8, 0.2); + p.lineTo(100.8, 0.8); + p.lineTo(100.2, 0.8); + p.closePathWithLine(); + + OGCPolygon ogcPolygon = new OGCPolygon(p, null); + String result = ogcPolygon.asGeoJson(); + assertEquals("{\"type\":\"Polygon\",\"coordinates\":[[[100.0,0.0],[101.0,0.0],[101.0,1.0],[100.0,1.0],[100.0,0.0]],[[100.2,0.2],[100.8,0.2],[100.8,0.8],[100.2,0.8],[100.2,0.2]]]}", result); + } + @Test public void testEnvelope() { Envelope e = new Envelope(); @@ -128,4 +256,12 @@ public void testEmptyEnvelope() { String result = exporter.execute(e); assertEquals("{\"bbox\":null}", result); } + + @Test + public void testEnvelopeGeometryEngine() { + Envelope e = new Envelope(); + e.setCoords(-180.0, -90.0, 180.0, 90.0); + String result = GeometryEngine.geometryToGeoJson(e); + assertEquals("{\"bbox\":[-180.0,-90.0,180.0,90.0]}", result); + } } From d3298473a1a2203a0d5f3f7af7b4403eecb0def2 Mon Sep 17 00:00:00 2001 From: Scooter Wadsworth Date: Sat, 15 Jun 2013 11:52:44 -0500 Subject: [PATCH 050/196] add proper multipolygon support --- .../OperatorExportToGeoJsonCursor.java | 120 +++++++++++++++++- .../esri/core/geometry/TestGeomToGeoJson.java | 25 ++++ 2 files changed, 143 insertions(+), 2 deletions(-) diff --git a/src/com/esri/core/geometry/OperatorExportToGeoJsonCursor.java b/src/com/esri/core/geometry/OperatorExportToGeoJsonCursor.java index e080fbdb..a01004cf 100644 --- a/src/com/esri/core/geometry/OperatorExportToGeoJsonCursor.java +++ b/src/com/esri/core/geometry/OperatorExportToGeoJsonCursor.java @@ -175,10 +175,22 @@ private void exportPolylineToGeoJson(JsonGenerator g, Polyline p) throws JsonGen } private void exportPolygonToGeoJson(JsonGenerator g, Polygon p) throws JsonGenerationException, IOException { + MultiPathImpl pImpl = (MultiPathImpl) p._getImpl(); + + //int pointCount = pImpl.getPointCount(); + int polyCount = pImpl.getOGCPolygonCount(); + + // check yo' polys playa + g.writeStartObject(); g.writeFieldName("type"); - g.writeString("Polygon"); + + if (polyCount >= 2) { // single polys seem to have a polyCount of 0, multi polys seem to be >= 2 + g.writeString("MultiPolygon"); + } else { + g.writeString("Polygon"); + } g.writeFieldName("coordinates"); @@ -186,7 +198,15 @@ private void exportPolygonToGeoJson(JsonGenerator g, Polygon p) throws JsonGener g.writeNull(); } else { g.writeStartArray(); - exportPathToGeoJson(g, p); + + if (polyCount >= 2) { + g.writeStartArray(); + exportMultiPolygonToGeoJson(g, p, pImpl); + g.writeEndArray(); + } else { + exportPathToGeoJson(g, p); + } + g.writeEndArray(); } @@ -194,6 +214,102 @@ private void exportPolygonToGeoJson(JsonGenerator g, Polygon p) throws JsonGener g.close(); } + private void exportMultiPolygonToGeoJson(JsonGenerator g, Polygon p, MultiPathImpl pImpl) throws IOException { + int startIndex; + int vertices; + + //AttributeStreamOfDbl position = (AttributeStreamOfDbl) pImpl.getAttributeStreamRef(VertexDescription.Semantics.POSITION); + //AttributeStreamOfInt8 pathFlags = pImpl.getPathFlagsStreamRef(); + //AttributeStreamOfInt32 paths = pImpl.getPathStreamRef(); + int pathCount = pImpl.getPathCount(); + + AttributeStreamOfDbl zs = p.hasAttribute(Semantics.Z) ? (AttributeStreamOfDbl) pImpl.getAttributeStreamRef(Semantics.Z) : null; + + Point2D pt = new Point2D(); + + g.writeStartArray(); + + startIndex = p.getPathStart(0); + vertices = p.getPathSize(0); + + for (int i = startIndex; i < startIndex + vertices; i++) { + p.getXY(i, pt); + g.writeStartArray(); + writeDouble(pt.x, g); + writeDouble(pt.y, g); + + if (zs != null) + writeDouble(zs.get(i), g); + + g.writeEndArray(); + } + + p.getXY(startIndex, pt); + g.writeStartArray(); + writeDouble(pt.x, g); + writeDouble(pt.y, g); + + if (zs != null) + writeDouble(zs.get(startIndex), g); + + g.writeEndArray(); + + g.writeEndArray(); + + for (int path = 1; path < pathCount; path++) { + boolean isExtRing = p.isExteriorRing(path); + startIndex = p.getPathStart(path); + vertices = p.getPathSize(path); + + writePath(p, g, startIndex, vertices, zs, isExtRing); + } + } + + private void closePolygon(MultiPath mp, JsonGenerator g, int startIndex, AttributeStreamOfDbl zs) throws IOException { + Point2D pt = new Point2D(); + + // close ring + mp.getXY(startIndex, pt); + g.writeStartArray(); + writeDouble(pt.x, g); + writeDouble(pt.y, g); + + if (zs != null) + writeDouble(zs.get(startIndex), g); + + g.writeEndArray(); + } + + private void writePath(MultiPath mp, JsonGenerator g, int startIndex, int vertices, AttributeStreamOfDbl zs, boolean isExtRing) throws IOException { + Point2D pt = new Point2D(); + + boolean isPoly = mp instanceof Polygon; + + if (isPoly && isExtRing) { + g.writeEndArray(); + g.writeStartArray(); + } + + g.writeStartArray(); + + for (int i = startIndex; i < startIndex + vertices; i++) { + mp.getXY(i, pt); + g.writeStartArray(); + writeDouble(pt.x, g); + writeDouble(pt.y, g); + + if (zs != null) + writeDouble(zs.get(i), g); + + g.writeEndArray(); + } + + if (isPoly) + closePolygon(mp, g, startIndex, zs); + + g.writeEndArray(); + } + private void exportPathToGeoJson(JsonGenerator g, MultiPath mp) throws JsonGenerationException, IOException { boolean isPoly = mp instanceof Polygon; diff --git a/unittest/com/esri/core/geometry/TestGeomToGeoJson.java b/unittest/com/esri/core/geometry/TestGeomToGeoJson.java index f47b83ea..23d54821 100644 --- a/unittest/com/esri/core/geometry/TestGeomToGeoJson.java +++ b/unittest/com/esri/core/geometry/TestGeomToGeoJson.java @@ -6,8 +6,12 @@ import com.esri.core.geometry.ogc.OGCLineString; import com.esri.core.geometry.ogc.OGCPolygon; import junit.framework.TestCase; +import org.codehaus.jackson.JsonFactory; +import org.codehaus.jackson.JsonParser; import org.junit.Test; +import java.io.IOException; + public class TestGeomToGeoJson extends TestCase { OperatorFactoryLocal factory = OperatorFactoryLocal.getInstance(); @@ -166,6 +170,27 @@ public void testPolygonWithHole() { assertEquals("{\"type\":\"Polygon\",\"coordinates\":[[[100.0,0.0],[101.0,0.0],[101.0,1.0],[100.0,1.0],[100.0,0.0]],[[100.2,0.2],[100.8,0.2],[100.8,0.8],[100.2,0.8],[100.2,0.2]]]}", result); } + @Test + public void testMultiPolygon() throws IOException { + JsonFactory jsonFactory = new JsonFactory(); + + String geoJsonPolygon = "{\"type\":\"MultiPolygon\",\"coordinates\":[[[[-100.0,-100.0],[-100.0,100.0],[100.0,100.0],[100.0,-100.0],[-100.0,-100.0]],[[-90.0,-90.0],[90.0,90.0],[-90.0,90.0],[90.0,-90.0],[-90.0,-90.0]]],[[[-10.0,-10.0],[-10.0,10.0],[10.0,10.0],[10.0,-10.0],[-10.0,-10.0]]]]}"; + String esriJsonPolygon = "{\"rings\": [[[-100, -100], [-100, 100], [100, 100], [100, -100], [-100, -100]], [[-90, -90], [90, 90], [-90, 90], [90, -90], [-90, -90]], [[-10, -10], [-10, 10], [10, 10], [10, -10], [-10, -10]]]}"; + + JsonParser parser = jsonFactory.createJsonParser(esriJsonPolygon); + MapGeometry parsedPoly = GeometryEngine.jsonToGeometry(parser); + //MapGeometry parsedPoly = GeometryEngine.geometryFromGeoJson(jsonPolygon, 0, Geometry.Type.Polygon); + + Polygon poly = (Polygon) parsedPoly.getGeometry(); + + OperatorExportToGeoJson exporter = (OperatorExportToGeoJson) factory.getOperator(Operator.Type.ExportToGeoJson); + //String result = exporter.execute(parsedPoly.getGeometry()); + String result = exporter.execute(poly); + assertEquals(geoJsonPolygon, result); + } + + + @Test public void testEmptyPolygon() { Polygon p = new Polygon(); From 8c4b4d9a1ebd06302849e2076a7403ca983bb35d Mon Sep 17 00:00:00 2001 From: Annette Locke Date: Mon, 17 Jun 2013 14:08:27 -0700 Subject: [PATCH 051/196] Clean up javadoc. --- src/com/esri/core/geometry/Envelope.java | 80 +++++++++---------- src/com/esri/core/geometry/Envelope1D.java | 2 +- src/com/esri/core/geometry/Envelope2D.java | 31 ++++--- .../core/geometry/OperatorConvexHull.java | 16 ++-- src/com/esri/core/geometry/OperatorCut.java | 17 ++-- .../core/geometry/OperatorGeneralize.java | 12 +-- 6 files changed, 79 insertions(+), 79 deletions(-) diff --git a/src/com/esri/core/geometry/Envelope.java b/src/com/esri/core/geometry/Envelope.java index 8b4ea150..b9227c4c 100644 --- a/src/com/esri/core/geometry/Envelope.java +++ b/src/com/esri/core/geometry/Envelope.java @@ -30,7 +30,7 @@ import com.esri.core.geometry.VertexDescription.Semantics; /** - * Envelopes are the rectangular window that contain a specific element. + * An envelope is a rectangle. */ public final class Envelope extends Geometry implements Serializable { private static final long serialVersionUID = 2L; @@ -89,10 +89,9 @@ public Envelope() { /** * Constructs an envelope that covers the given point. The coordinates of - * the point are used to set the envelope's extent. + * the point are used to set the extent of the envelope. * - * @param point - * - The point that the envelope covers. + * @param point The point that the envelope covers. */ public Envelope(Point point) { m_description = VertexDescriptionDesignerImpl.getDefaultDescriptor2D(); @@ -107,13 +106,13 @@ public Envelope(Point point) { * Constructs an envelope with the specified X and Y extents. * * @param xmin - * The minimum X coordinate of the envelope. + * The minimum x-coordinate of the envelope. * @param ymin - * The minimum Y coordinate of the envelope. + * The minimum y-coordinate of the envelope. * @param xmax - * The maximum X coordinate of the envelope. + * The maximum x-coordinate of the envelope. * @param ymax - * The maximum Y coordinate of the envelope. + * The maximum y-coordinate of the envelope. */ public Envelope(double xmin, double ymin, double xmax, double ymax) { m_description = VertexDescriptionDesignerImpl.getDefaultDescriptor2D(); @@ -121,30 +120,22 @@ public Envelope(double xmin, double ymin, double xmax, double ymax) { } /** - * Sets the 2 dimensional extents of the envelope. + * Sets the 2-dimensional extents of the envelope. * * @param xmin - * The minimum X coordinate of the envelope. + * The minimum x-coordinate of the envelope. * @param ymin - * The minimum Y coordinate of the envelope. + * The minimum y-coordinate of the envelope. * @param xmax - * The maximum X coordinate of the envelope. + * The maximum x-coordinate of the envelope. * @param ymax - * The maximum Y coordinate of the envelope. + * The maximum y-coordinate of the envelope. */ public void setCoords(double xmin, double ymin, double xmax, double ymax) { _touch(); m_envelope.setCoords(xmin, ymin, xmax, ymax); } - /** - * Sets the envelope from the array of points. The result envelope is a - * bounding box of all the points in the array. If the array has zero - * length, the envelope will be empty. - * - * @param points - * The point array. - */ void setCoords(Point[] points) { _touch(); setEmpty(); @@ -199,23 +190,28 @@ public double getHeight() { } /** - * The X coordinate of center of the envelope. + * The x-coordinate of the center of the envelope. * - * @return The center's X coordinate of the envelope. + * @return The x-coordinate of the center of the envelope. */ public double getCenterX() { return m_envelope.getCenterX(); } /** - * The Y coordinate fo the center of the envelope. + * The y-coordinate of center of the envelope. * - * @return The center's Y coordinate of the envelope. + * @return The y-coordinate of center of the envelope. */ public double getCenterY() { return m_envelope.getCenterY(); } + /** + * The x and y-coordinates of the center of the envelope. + * + * @return A point whose x and y-coordinates are that of the center of the envelope. + */ Point2D getCenterXY() { return m_envelope.getCenter(); } @@ -246,13 +242,12 @@ void merge(Point2D pt) { } /** - * Merges this envelope with the extent of the given envelope. If the - * original envelope is empty, the coordinates of the envelope to merge with - * are assigned. If the envelope to merge with is empty, the original - * envelope stays unchanged. + * Merges this envelope with the extent of the given envelope. If this + * envelope is empty, the coordinates of the given envelope + * are assigned. If the given envelope is empty, this envelope is unchanged. * * @param other - * The envelope to merge with. + * The envelope to merge. */ public void merge(Envelope other) { _touch(); @@ -279,10 +274,10 @@ public void merge(Envelope other) { * Merges this envelope with the point. The boundary of the envelope is * increased to include the point. If the envelope is empty, the coordinates * of the point to merge are assigned. If the point is empty, the original - * envelope stays unchanged. + * envelope is unchanged. * * @param point - * The point to be merged with. + * The point to be merged. */ public void merge(Point point) { _touch(); @@ -362,14 +357,14 @@ public void reaspect(double arWidth, double arHeight) { } /** - * Changes the dimensions of the envelope, preserving the center. New width + * Changes the dimensions of the envelope while preserving the center. New width * is Width + 2 * dx, new height is Height + 2 * dy. If the result envelope * width or height becomes negative, the envelope is set to be empty. * * @param dx - * The inflation along the X axis. + * The inflation along the x-axis. * @param dy - * The inflation along the Y axis. + * The inflation along the y-axis. */ public void inflate(double dx, double dy) { _touch(); @@ -498,7 +493,7 @@ void queryCoordinates(Point2D[] dst) { * given corner. * * @param index - * The index of the envlope's corners from 0 to 3. + * The index of the envelope's corners from 0 to 3. *

* 0 = lower left corner *

@@ -797,7 +792,7 @@ boolean isIntersecting(Envelope2D other) { * envelope. * * @param other - * The envelope to intersect with. + * The envelope to intersect. * @return Returns true if the result is not empty. */ public boolean intersect(Envelope other) { @@ -811,8 +806,8 @@ public boolean intersect(Envelope other) { * Returns true if the envelope and the other given envelope intersect. * * @param other - * The envelope to test intersection with. - * @return Returns true if the two envelopes are intersecting. + * The envelope to with which to test intersection. + * @return Returns true if the two envelopes intersect. */ public boolean isIntersecting(Envelope other) {// TODO: attributes. return m_envelope.isIntersecting(other.m_envelope); @@ -820,7 +815,7 @@ public boolean isIntersecting(Envelope other) {// TODO: attributes. /** * Sets the envelope's corners to be centered around the specified point, - * using it's center, width, and height. + * using its center, width, and height. * * @param c * The point around which to center the envelope. @@ -840,8 +835,7 @@ public void centerAt(Point c, double w, double h) { } /** - * Offsets the envelope by the specified distances along x and y - * coordinates. + * Offsets the envelope by the specified distances along x and y-coordinates. * * @param dx * The X offset to be applied. @@ -854,7 +848,7 @@ public void offset(double dx, double dy) { } /** - * Normalizes envelopes if the minimum dimension is larger then then the + * Normalizes envelopes if the minimum dimension is larger than the * maximum dimension. */ public void normalize() {// TODO: attributes diff --git a/src/com/esri/core/geometry/Envelope1D.java b/src/com/esri/core/geometry/Envelope1D.java index 762531b1..e0d19408 100644 --- a/src/com/esri/core/geometry/Envelope1D.java +++ b/src/com/esri/core/geometry/Envelope1D.java @@ -26,7 +26,7 @@ package com.esri.core.geometry; /** - * A One-dimensional interval + * A 1-dimensional interval. */ public final class Envelope1D { diff --git a/src/com/esri/core/geometry/Envelope2D.java b/src/com/esri/core/geometry/Envelope2D.java index 35d66abb..2ea01b86 100644 --- a/src/com/esri/core/geometry/Envelope2D.java +++ b/src/com/esri/core/geometry/Envelope2D.java @@ -25,7 +25,7 @@ package com.esri.core.geometry; /** - * A class that represents axis parallel 2D rectangle. + * An axis parallel 2-dimensional rectangle. */ public final class Envelope2D { @@ -174,7 +174,7 @@ else if (ymax < y) } /** - * Merges a point with this envelope without checkin if the envelope is + * Merges a point with this envelope without checking if the envelope is * empty. Use with care. */ public void mergeNE(double x, double y) { @@ -235,7 +235,8 @@ public void zoom(double factorX, double factorY) { } /** - * Returns True if this envelope intersects the other. + * Checks if this envelope intersects the other. + * @return True if this envelope intersects the other. */ public boolean isIntersecting(Envelope2D other) { return !isEmpty() @@ -251,7 +252,8 @@ public boolean isIntersecting(Envelope2D other) { } /** - * Returns True if this envelope intersects the other. Assumes this and + * Checks if this envelope intersects the other assuming neither one is empty. + * @return True if this envelope intersects the other. Assumes this and * other envelopes are not empty. */ public boolean isIntersectingNE(Envelope2D other) { @@ -265,8 +267,8 @@ public boolean isIntersectingNE(Envelope2D other) { } /** - * Intersects this envelope with the other. Returns True if the result is - * not empty. + * Checks if this envelope intersects the other. + * @return True if this envelope intersects the other. */ public boolean intersect(Envelope2D other) { if (isEmpty() || other.isEmpty()) @@ -293,8 +295,15 @@ public boolean intersect(Envelope2D other) { } /** - * Returns a corner of the envelope. Starts from the lowerleft corner and - * goes clockwise. + * Queries a corner of the envelope. + * + * @param index Indicates a corner of the envelope. + *

0 => lower left or (xmin, ymin) + *

1 => upper left or (xmin, ymax) + *

2 => upper right or (xmax, ymax) + *

3 => lower right or (xmax, ymin) + * @return Point at a corner of the envelope. + * */ public Point2D queryCorner(int index) { switch (index) { @@ -313,8 +322,8 @@ public Point2D queryCorner(int index) { } /** - * Queries corners into a given array. The array length must have at least - * 4. Starts from the lowerleft corner and goes clockwise. + * Queries corners into a given array. The array length must be at least + * 4. Starts from the lower left corner and goes clockwise. */ public void queryCorners(Point2D[] corners) { if ((corners == null) || (corners.length < 4)) @@ -327,7 +336,7 @@ public void queryCorners(Point2D[] corners) { /** * Queries corners into a given array in reversed order. The array length - * must have at least 4. Starts from the lowerleft corner and goes + * must be at least 4. Starts from the lower left corner and goes * counterclockwise. */ public void queryCornersReversed(Point2D[] corners) { diff --git a/src/com/esri/core/geometry/OperatorConvexHull.java b/src/com/esri/core/geometry/OperatorConvexHull.java index 3892abd3..bd9ea418 100644 --- a/src/com/esri/core/geometry/OperatorConvexHull.java +++ b/src/com/esri/core/geometry/OperatorConvexHull.java @@ -37,7 +37,7 @@ public Operator.Type getType() { /** * Calculates the convex hull. * @param geoms The input geometry cursor. - * @param progress_tracker The progress tracker, that allows to cancel the lengthy operation. + * @param progress_tracker The progress tracker. Allows cancellation of a lengthy operation. * @param b_merge Put true if you want the convex hull of all the geometries in the cursor combined. * Put false if you want the convex hull of each geometry in the cursor individually. * @return Returns a cursor over result convex hulls. @@ -48,15 +48,15 @@ abstract public GeometryCursor execute(GeometryCursor geoms, boolean b_merge, /** * Calculates the convex hull geometry. * @param geom The input geometry. - * @param progress_tracker The progress tracker, that allows to cancel the lengthy operation. + * @param progress_tracker The progress tracker. Allows cancellation of a lengthy operation. * @return Returns the convex hull. * - * For a Point - returns the same point. - * For an Envelope - returns the same envelope. - * For a MultiPoint - If the point count is one, returns the same multipoint. If the point count is two, returns a polyline of the points. Otherwise computes and returns the convex hull polygon. - * For a Segment - returns a polyline consisting of the segment. - * For a Polyline - If consists of only one segment, returns the same polyline. Otherwise computes and returns the convex hull polygon. - * For a Polygon - If more than one path, or if the path isn't already convex, computes and returns the convex hull polygon. Otherwise returns the same polygon. + * Point - Returns the same point. + * Envelope - returns the same envelope. + * MultiPoint - If the point count is one, returns the same multipoint. If the point count is two, returns a polyline of the points. Otherwise, computes and returns the convex hull polygon. + * Segment - Returns a polyline consisting of the segment. + * Polyline - If consists of only one segment, returns the same polyline. Otherwise, computes and returns the convex hull polygon. + * Polygon - If more than one path or if the path isn't already convex, computes and returns the convex hull polygon. Otherwise, returns the same polygon. */ abstract public Geometry execute(Geometry geom, ProgressTracker progress_tracker); diff --git a/src/com/esri/core/geometry/OperatorCut.java b/src/com/esri/core/geometry/OperatorCut.java index d14b34cf..59c5b589 100644 --- a/src/com/esri/core/geometry/OperatorCut.java +++ b/src/com/esri/core/geometry/OperatorCut.java @@ -36,21 +36,18 @@ public Type getType() { /** * Performs the Cut operation on a geometry. * @param bConsiderTouch Indicates whether we consider a touch event a cut. - * This only applies to Polylines, but it's recommended to set this variable to True. + * This only applies to polylines, but it's recommended to set this variable to True. * @param cuttee The input geometry to be cut. * @param cutter The polyline that will be used to divide the cuttee into - * pieces where they cross the cutter. - * @return Returns a GeometryCursor of cut geometries. For Polylines, all left cuts - * will be grouped together in the first Geometry, Right cuts and coincident - * cuts are grouped in the second Geometry, and each undefined cut, along - * with any uncut parts, are output as separate Polylines. For Polygons, all - * left cuts are grouped in the first Polygon, all right cuts are in the - * second Polygon, and each undefined cut, along with any left-over parts - * after cutting, are output as a separate Polygon. If there were no cuts + * pieces where it crosses the cutter. + * @return Returns a GeometryCursor of cut geometries. + * All left cuts will be grouped together in the first geometry. Right cuts and + * coincident cuts are grouped in the second geometry, and each undefined cut along + * with any uncut parts are output as separate geometries. If there were no cuts * the cursor will return no geometry. If the left or right cut does not * exist, the returned geometry will be empty for this type of cut. An * undefined cut will only be produced if a left cut or right cut was - * produced, and there was a part left over after cutting or a cut is + * produced and there was a part left over after cutting or a cut is * bounded to the left and right of the cutter. */ public abstract GeometryCursor execute(boolean bConsiderTouch, diff --git a/src/com/esri/core/geometry/OperatorGeneralize.java b/src/com/esri/core/geometry/OperatorGeneralize.java index 8fbc640a..af6223bd 100644 --- a/src/com/esri/core/geometry/OperatorGeneralize.java +++ b/src/com/esri/core/geometry/OperatorGeneralize.java @@ -33,9 +33,9 @@ public Type getType() { } /** - * Performs the Generalize operation on the geometry set. Point and - * MultiPoint geometries are left unchanged. Envelope is converted to a - * Polygon. + * Performs the Generalize operation on a geometry set. Point and + * multipoint geometries are left unchanged. An envelope is converted to a + * polygon. * */ public abstract GeometryCursor execute(GeometryCursor geoms, @@ -43,9 +43,9 @@ public abstract GeometryCursor execute(GeometryCursor geoms, ProgressTracker progressTracker); /** - * Performs the Generalize operation on single geometry. Point and - * MultiPoint geometries are left unchanged. Envelope is converted to a - * Polygon. + * Performs the Generalize operation on a single geometry. Point and + * multipoint geometries are left unchanged. An envelope is converted to a + * polygon. */ public abstract Geometry execute(Geometry geom, double maxDeviation, boolean bRemoveDegenerateParts, ProgressTracker progressTracker); From e60977f09b7aef00441dd0d4682ddec70a7227ea Mon Sep 17 00:00:00 2001 From: Scooter Wadsworth Date: Thu, 27 Jun 2013 15:15:37 -0500 Subject: [PATCH 052/196] add Apache license headers --- .../geometry/OperatorExportToGeoJson.java | 22 +++++++++++++++++++ .../OperatorExportToGeoJsonCursor.java | 22 +++++++++++++++++++ .../OperatorExportToGeoJsonLocal.java | 22 +++++++++++++++++++ .../esri/core/geometry/TestGeomToGeoJson.java | 22 +++++++++++++++++++ 4 files changed, 88 insertions(+) diff --git a/src/com/esri/core/geometry/OperatorExportToGeoJson.java b/src/com/esri/core/geometry/OperatorExportToGeoJson.java index 0cc0601a..f6c21d97 100644 --- a/src/com/esri/core/geometry/OperatorExportToGeoJson.java +++ b/src/com/esri/core/geometry/OperatorExportToGeoJson.java @@ -1,3 +1,25 @@ +/* + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ + package com.esri.core.geometry; import com.esri.core.geometry.Operator.Type; diff --git a/src/com/esri/core/geometry/OperatorExportToGeoJsonCursor.java b/src/com/esri/core/geometry/OperatorExportToGeoJsonCursor.java index a01004cf..85ea4b1d 100644 --- a/src/com/esri/core/geometry/OperatorExportToGeoJsonCursor.java +++ b/src/com/esri/core/geometry/OperatorExportToGeoJsonCursor.java @@ -1,3 +1,25 @@ +/* + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ + package com.esri.core.geometry; import com.esri.core.geometry.VertexDescription.Semantics; diff --git a/src/com/esri/core/geometry/OperatorExportToGeoJsonLocal.java b/src/com/esri/core/geometry/OperatorExportToGeoJsonLocal.java index 68a14292..2e7c8c5b 100644 --- a/src/com/esri/core/geometry/OperatorExportToGeoJsonLocal.java +++ b/src/com/esri/core/geometry/OperatorExportToGeoJsonLocal.java @@ -1,3 +1,25 @@ +/* + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ + package com.esri.core.geometry; class OperatorExportToGeoJsonLocal extends OperatorExportToGeoJson { diff --git a/unittest/com/esri/core/geometry/TestGeomToGeoJson.java b/unittest/com/esri/core/geometry/TestGeomToGeoJson.java index 23d54821..3c7f5832 100644 --- a/unittest/com/esri/core/geometry/TestGeomToGeoJson.java +++ b/unittest/com/esri/core/geometry/TestGeomToGeoJson.java @@ -1,3 +1,25 @@ +/* + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ + package com.esri.core.geometry; import com.esri.core.geometry.ogc.OGCGeometry; From e53e42a477d08481fc6e244161b444997a9b004a Mon Sep 17 00:00:00 2001 From: Sergey Tolstov Date: Mon, 1 Jul 2013 14:11:51 -0700 Subject: [PATCH 053/196] Fix export to multilinestring Fixed export to MultiLineString. Proper ring orientation handling in polygon export. Fix asGeoJson for OGCMultiPolygon and OGCMultiLineString --- .../core/geometry/GeoJsonExportFlags.java | 29 +++ .../esri/core/geometry/GeometryEngine.java | 13 +- src/com/esri/core/geometry/MultiPathImpl.java | 4 +- .../geometry/OperatorExportToGeoJson.java | 5 + .../OperatorExportToGeoJsonCursor.java | 203 ++++++------------ .../OperatorExportToGeoJsonLocal.java | 11 +- .../geometry/StridedIndexTypeCollection.java | 2 +- src/com/esri/core/geometry/Treap.java | 2 +- .../core/geometry/ogc/OGCMultiLineString.java | 9 +- .../core/geometry/ogc/OGCMultiPolygon.java | 9 +- .../esri/core/geometry/ogc/OGCPolygon.java | 2 +- .../esri/core/geometry/TestGeomToGeoJson.java | 67 ++++-- unittest/com/esri/core/geometry/TestOGC.java | 20 ++ 13 files changed, 204 insertions(+), 172 deletions(-) create mode 100644 src/com/esri/core/geometry/GeoJsonExportFlags.java diff --git a/src/com/esri/core/geometry/GeoJsonExportFlags.java b/src/com/esri/core/geometry/GeoJsonExportFlags.java new file mode 100644 index 00000000..04da23ce --- /dev/null +++ b/src/com/esri/core/geometry/GeoJsonExportFlags.java @@ -0,0 +1,29 @@ +/* + Copyright 1995-2013 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ +package com.esri.core.geometry; + +public interface GeoJsonExportFlags { + static final int geoJsonExportDefaults = 0; + static final int geoJsonExportPreferMultiGeometry = 1; +} diff --git a/src/com/esri/core/geometry/GeometryEngine.java b/src/com/esri/core/geometry/GeometryEngine.java index faeb34a4..c8fec93c 100644 --- a/src/com/esri/core/geometry/GeometryEngine.java +++ b/src/com/esri/core/geometry/GeometryEngine.java @@ -37,9 +37,7 @@ public class GeometryEngine { private static OperatorFactoryLocal factory = OperatorFactoryLocal .getInstance(); - - - + /** * Imports the MapGeometry from its JSON representation. M and Z values are * not imported from JSON representation. @@ -600,8 +598,7 @@ public static Geometry[] cut(Geometry cuttee, Polyline cutter, return cutsList.toArray(new Geometry[0]); } - - + /** * Calculates a buffer polygon for each geometry at each of the * corresponding specified distances. It is assumed that all geometries have @@ -644,8 +641,7 @@ public static Polygon[] buffer(Geometry[] geometries, return buffers; } } - - + /** * Calculates a buffer polygon of the geometry as specified by the * distance input. The buffer is implemented in the xy-plane. @@ -818,8 +814,7 @@ static boolean isSimple(Geometry geometry, SpatialReference spatialReference) { boolean result = op.isSimpleAsFeature(geometry, spatialReference, null); return result; } - - + /** * A geodesic distance is the shortest distance between any two points on the earth's surface when the earth's * surface is approximated by a spheroid. The function returns the shortest distance between two points on the diff --git a/src/com/esri/core/geometry/MultiPathImpl.java b/src/com/esri/core/geometry/MultiPathImpl.java index beca8173..e8062f3a 100644 --- a/src/com/esri/core/geometry/MultiPathImpl.java +++ b/src/com/esri/core/geometry/MultiPathImpl.java @@ -2186,9 +2186,11 @@ protected void _updateOGCFlags() { m_pathFlags = (AttributeStreamOfInt8) AttributeStreamBase .createByteStream(pathCount + 1); + int firstSign = 1; for (int ipath = 0; ipath < pathCount; ipath++) { double area = m_cachedRingAreas2D.read(ipath); - if (area > 0.0) + if (ipath == 0) firstSign = area > 0 ? 1 : -1; + if (area * firstSign > 0.0) m_pathFlags.setBits(ipath, (byte) PathFlags.enumOGCStartPolygon); else diff --git a/src/com/esri/core/geometry/OperatorExportToGeoJson.java b/src/com/esri/core/geometry/OperatorExportToGeoJson.java index f6c21d97..2eeba10f 100644 --- a/src/com/esri/core/geometry/OperatorExportToGeoJson.java +++ b/src/com/esri/core/geometry/OperatorExportToGeoJson.java @@ -24,6 +24,9 @@ import com.esri.core.geometry.Operator.Type; +/** + *Export to GeoJson format. + */ public abstract class OperatorExportToGeoJson extends Operator { @Override public Type getType() { @@ -34,6 +37,8 @@ public Type getType() { public abstract String execute(SpatialReference spatialReference, Geometry geometry); + public abstract String execute(int exportFlags, SpatialReference spatialReference, Geometry geometry); + public abstract String execute(Geometry geometry); public static OperatorExportToGeoJson local() { diff --git a/src/com/esri/core/geometry/OperatorExportToGeoJsonCursor.java b/src/com/esri/core/geometry/OperatorExportToGeoJsonCursor.java index 85ea4b1d..ddad1662 100644 --- a/src/com/esri/core/geometry/OperatorExportToGeoJsonCursor.java +++ b/src/com/esri/core/geometry/OperatorExportToGeoJsonCursor.java @@ -35,10 +35,11 @@ class OperatorExportToGeoJsonCursor extends JsonCursor { int m_wkid = -1; int m_latest_wkid = -1; String m_wkt = null; + boolean m_preferMulti = false; private static JsonFactory factory = new JsonFactory(); - public OperatorExportToGeoJsonCursor(SpatialReference spatialReference, GeometryCursor geometryCursor) { + public OperatorExportToGeoJsonCursor(boolean preferMulti, SpatialReference spatialReference, GeometryCursor geometryCursor) { m_index = -1; if (geometryCursor == null) throw new IllegalArgumentException(); @@ -48,6 +49,7 @@ public OperatorExportToGeoJsonCursor(SpatialReference spatialReference, Geometry m_latest_wkid = spatialReference.getLatestID(); } m_inputGeometryCursor = geometryCursor; + m_preferMulti = preferMulti; } public OperatorExportToGeoJsonCursor(GeometryCursor geometryCursor) { @@ -90,10 +92,10 @@ private String exportToGeoJson(Geometry geometry) { exportMultiPointToGeoJson(g, (MultiPoint) geometry); break; case Geometry.GeometryType.Polyline: - exportPolylineToGeoJson(g, (Polyline) geometry); + exportMultiPathToGeoJson(g, (Polyline) geometry); break; case Geometry.GeometryType.Polygon: - exportPolygonToGeoJson(g, (Polygon) geometry); + exportMultiPathToGeoJson(g, (Polygon) geometry); break; case Geometry.GeometryType.Envelope: exportEnvelopeToGeoJson(g, (Envelope) geometry); @@ -110,6 +112,13 @@ private String exportToGeoJson(Geometry geometry) { } private void exportPointToGeoJson(JsonGenerator g, Point p) throws JsonGenerationException, IOException { + if (m_preferMulti) { + MultiPoint mp = new MultiPoint(); + mp.add(p); + exportMultiPointToGeoJson(g, mp); + return; + } + g.writeStartObject(); g.writeFieldName("type"); @@ -176,31 +185,11 @@ private void exportMultiPointToGeoJson(JsonGenerator g, MultiPoint mp) throws Js g.close(); } - private void exportPolylineToGeoJson(JsonGenerator g, Polyline p) throws JsonGenerationException, IOException { - g.writeStartObject(); - - g.writeFieldName("type"); - g.writeString("LineString"); - - g.writeFieldName("coordinates"); - - if (p.isEmpty()) { - g.writeNull(); - } else { - g.writeStartArray(); - exportPathToGeoJson(g, p); - g.writeEndArray(); - } - - g.writeEndObject(); - g.close(); - } - - private void exportPolygonToGeoJson(JsonGenerator g, Polygon p) throws JsonGenerationException, IOException { + private void exportMultiPathToGeoJson(JsonGenerator g, MultiPath p) throws JsonGenerationException, IOException { MultiPathImpl pImpl = (MultiPathImpl) p._getImpl(); - //int pointCount = pImpl.getPointCount(); - int polyCount = pImpl.getOGCPolygonCount(); + boolean isPolygon = pImpl.m_bPolygon; + int polyCount = isPolygon ? pImpl.getOGCPolygonCount() : 0; // check yo' polys playa @@ -208,10 +197,22 @@ private void exportPolygonToGeoJson(JsonGenerator g, Polygon p) throws JsonGener g.writeFieldName("type"); - if (polyCount >= 2) { // single polys seem to have a polyCount of 0, multi polys seem to be >= 2 - g.writeString("MultiPolygon"); - } else { - g.writeString("Polygon"); + boolean bCollection = false; + if (isPolygon) { + if (polyCount >= 2 || m_preferMulti) { // single polys seem to have a polyCount of 0, multi polys seem to be >= 2 + g.writeString("MultiPolygon"); + bCollection = true; + } else { + g.writeString("Polygon"); + } + } + else { + if (p.getPathCount() > 1 || m_preferMulti) { // single polys seem to have a polyCount of 0, multi polys seem to be >= 2 + g.writeString("MultiLineString"); + bCollection = true; + } else { + g.writeString("LineString"); + } } g.writeFieldName("coordinates"); @@ -219,75 +220,50 @@ private void exportPolygonToGeoJson(JsonGenerator g, Polygon p) throws JsonGener if (p.isEmpty()) { g.writeNull(); } else { - g.writeStartArray(); - - if (polyCount >= 2) { - g.writeStartArray(); - exportMultiPolygonToGeoJson(g, p, pImpl); - g.writeEndArray(); - } else { - exportPathToGeoJson(g, p); - } - - g.writeEndArray(); + exportMultiPathToGeoJson(g, pImpl, bCollection); } g.writeEndObject(); g.close(); } - private void exportMultiPolygonToGeoJson(JsonGenerator g, Polygon p, MultiPathImpl pImpl) throws IOException { + private void exportMultiPathToGeoJson(JsonGenerator g, MultiPathImpl pImpl, boolean bCollection) throws IOException { int startIndex; int vertices; + if (bCollection) + g.writeStartArray(); + //AttributeStreamOfDbl position = (AttributeStreamOfDbl) pImpl.getAttributeStreamRef(VertexDescription.Semantics.POSITION); //AttributeStreamOfInt8 pathFlags = pImpl.getPathFlagsStreamRef(); //AttributeStreamOfInt32 paths = pImpl.getPathStreamRef(); int pathCount = pImpl.getPathCount(); + boolean isPolygon = pImpl.m_bPolygon; + AttributeStreamOfDbl zs = pImpl.hasAttribute(Semantics.Z) ? (AttributeStreamOfDbl) pImpl.getAttributeStreamRef(Semantics.Z) : null; + + for (int path = 0; path < pathCount; path++) { + startIndex = pImpl.getPathStart(path); + vertices = pImpl.getPathSize(path); + + boolean isExtRing = isPolygon && pImpl.isExteriorRing(path); + if (isExtRing) {//only for polygons + if (path > 0) + g.writeEndArray();//end of OGC polygon + + g.writeStartArray();//start of next OGC polygon + } - AttributeStreamOfDbl zs = p.hasAttribute(Semantics.Z) ? (AttributeStreamOfDbl) pImpl.getAttributeStreamRef(Semantics.Z) : null; - - Point2D pt = new Point2D(); - - g.writeStartArray(); - - startIndex = p.getPathStart(0); - vertices = p.getPathSize(0); - - for (int i = startIndex; i < startIndex + vertices; i++) { - p.getXY(i, pt); - g.writeStartArray(); - writeDouble(pt.x, g); - writeDouble(pt.y, g); - - if (zs != null) - writeDouble(zs.get(i), g); - - g.writeEndArray(); - } - - p.getXY(startIndex, pt); - g.writeStartArray(); - writeDouble(pt.x, g); - writeDouble(pt.y, g); - - if (zs != null) - writeDouble(zs.get(startIndex), g); - - g.writeEndArray(); - - g.writeEndArray(); - - for (int path = 1; path < pathCount; path++) { - boolean isExtRing = p.isExteriorRing(path); - startIndex = p.getPathStart(path); - vertices = p.getPathSize(path); - - writePath(p, g, startIndex, vertices, zs, isExtRing); + writePath(pImpl, g, path, startIndex, vertices, zs); } + + if (isPolygon) + g.writeEndArray();//end of last OGC polygon + + if (bCollection) + g.writeEndArray(); } - private void closePolygon(MultiPath mp, JsonGenerator g, int startIndex, AttributeStreamOfDbl zs) throws IOException { + private void closePath(MultiPathImpl mp, JsonGenerator g, int startIndex, AttributeStreamOfDbl zs) throws IOException { Point2D pt = new Point2D(); // close ring @@ -302,16 +278,9 @@ private void closePolygon(MultiPath mp, JsonGenerator g, int startIndex, Attribu g.writeEndArray(); } - private void writePath(MultiPath mp, JsonGenerator g, int startIndex, int vertices, AttributeStreamOfDbl zs, boolean isExtRing) throws IOException { + private void writePath(MultiPathImpl mp, JsonGenerator g, int pathIndex, int startIndex, int vertices, AttributeStreamOfDbl zs) throws IOException { Point2D pt = new Point2D(); - boolean isPoly = mp instanceof Polygon; - - if (isPoly && isExtRing) { - g.writeEndArray(); - g.writeStartArray(); - } - g.writeStartArray(); for (int i = startIndex; i < startIndex + vertices; i++) { @@ -326,62 +295,12 @@ private void writePath(MultiPath mp, JsonGenerator g, int startIndex, int vertic g.writeEndArray(); } - if (isPoly) - closePolygon(mp, g, startIndex, zs); + if (mp.isClosedPath(pathIndex)) + closePath(mp, g, startIndex, zs); g.writeEndArray(); } - private void exportPathToGeoJson(JsonGenerator g, MultiPath mp) throws JsonGenerationException, IOException { - boolean isPoly = mp instanceof Polygon; - - MultiPathImpl mpImpl = (MultiPathImpl) mp._getImpl(); - AttributeStreamOfDbl zs = mp.hasAttribute(Semantics.Z) ? (AttributeStreamOfDbl) mpImpl.getAttributeStreamRef(Semantics.Z) : null; - - Point2D p = new Point2D(); - - int n = mp.getPathCount(); - - for (int i = 0; i < n; i++) { - if (isPoly) - g.writeStartArray(); - - int startIndex = mp.getPathStart(i); - int vertices = mp.getPathSize(i); - - for (int j = startIndex; j < startIndex + vertices; j++) { - mp.getXY(j, p); - - g.writeStartArray(); - - writeDouble(p.x, g); - writeDouble(p.y, g); - - if (zs != null) - writeDouble(zs.get(j), g); - - g.writeEndArray(); - } - - if (isPoly) { - mp.getXY(startIndex, p); - - g.writeStartArray(); - - writeDouble(p.x, g); - writeDouble(p.y, g); - - if (zs != null) - writeDouble(zs.get(startIndex), g); - - g.writeEndArray(); - } - - if (isPoly) - g.writeEndArray(); - } - } - private void exportEnvelopeToGeoJson(JsonGenerator g, Envelope e) throws JsonGenerationException, IOException { boolean empty = e.isEmpty(); diff --git a/src/com/esri/core/geometry/OperatorExportToGeoJsonLocal.java b/src/com/esri/core/geometry/OperatorExportToGeoJsonLocal.java index 2e7c8c5b..3e0ccb17 100644 --- a/src/com/esri/core/geometry/OperatorExportToGeoJsonLocal.java +++ b/src/com/esri/core/geometry/OperatorExportToGeoJsonLocal.java @@ -25,16 +25,23 @@ class OperatorExportToGeoJsonLocal extends OperatorExportToGeoJson { @Override JsonCursor execute(SpatialReference spatialReference, GeometryCursor geometryCursor) { - return new OperatorExportToGeoJsonCursor(spatialReference, geometryCursor); + return new OperatorExportToGeoJsonCursor(false, spatialReference, geometryCursor); } @Override public String execute(SpatialReference spatialReference, Geometry geometry) { SimpleGeometryCursor gc = new SimpleGeometryCursor(geometry); - JsonCursor cursor = new OperatorExportToGeoJsonCursor(spatialReference, gc); + JsonCursor cursor = new OperatorExportToGeoJsonCursor(false, spatialReference, gc); return cursor.next(); } + @Override + public String execute(int exportFlags, SpatialReference spatialReference, Geometry geometry) { + SimpleGeometryCursor gc = new SimpleGeometryCursor(geometry); + JsonCursor cursor = new OperatorExportToGeoJsonCursor(exportFlags == GeoJsonExportFlags.geoJsonExportPreferMultiGeometry, spatialReference, gc); + return cursor.next(); + } + @Override public String execute(Geometry geometry) { SimpleGeometryCursor gc = new SimpleGeometryCursor(geometry); diff --git a/src/com/esri/core/geometry/StridedIndexTypeCollection.java b/src/com/esri/core/geometry/StridedIndexTypeCollection.java index c2451b34..90bfc9d5 100644 --- a/src/com/esri/core/geometry/StridedIndexTypeCollection.java +++ b/src/com/esri/core/geometry/StridedIndexTypeCollection.java @@ -193,7 +193,7 @@ private boolean dbgdelete_(int element) { return true; } - static int[] st_sizes = {16, 32, 64, 128, 256, 512, 1024, 2048}; + final static int[] st_sizes = {16, 32, 64, 128, 256, 512, 1024, 2048}; private void grow_(int newsize) { if (m_buffer == null) { diff --git a/src/com/esri/core/geometry/Treap.java b/src/com/esri/core/geometry/Treap.java index ba879718..e97840a6 100644 --- a/src/com/esri/core/geometry/Treap.java +++ b/src/com/esri/core/geometry/Treap.java @@ -25,7 +25,7 @@ package com.esri.core.geometry; -class Treap { +final class Treap { static abstract class Comparator { Comparator() { m_b_notify_on_actions = false; diff --git a/src/com/esri/core/geometry/ogc/OGCMultiLineString.java b/src/com/esri/core/geometry/ogc/OGCMultiLineString.java index 4e9e4a65..2183020d 100644 --- a/src/com/esri/core/geometry/ogc/OGCMultiLineString.java +++ b/src/com/esri/core/geometry/ogc/OGCMultiLineString.java @@ -1,9 +1,11 @@ package com.esri.core.geometry.ogc; +import com.esri.core.geometry.GeoJsonExportFlags; import com.esri.core.geometry.Geometry; import com.esri.core.geometry.GeometryEngine; import com.esri.core.geometry.Operator; import com.esri.core.geometry.OperatorBoundary; +import com.esri.core.geometry.OperatorExportToGeoJson; import com.esri.core.geometry.OperatorExportToWkb; import com.esri.core.geometry.OperatorFactoryLocal; import com.esri.core.geometry.Polyline; @@ -24,7 +26,12 @@ public String asText() { return GeometryEngine.geometryToWkt(getEsriGeometry(), WktExportFlags.wktExportMultiLineString); } - + @Override + public String asGeoJson() { + OperatorExportToGeoJson op = (OperatorExportToGeoJson) OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.ExportToGeoJson); + return op.execute(GeoJsonExportFlags.geoJsonExportPreferMultiGeometry, null, getEsriGeometry()); + } @Override public ByteBuffer asBinary() { OperatorExportToWkb op = (OperatorExportToWkb) OperatorFactoryLocal diff --git a/src/com/esri/core/geometry/ogc/OGCMultiPolygon.java b/src/com/esri/core/geometry/ogc/OGCMultiPolygon.java index 3df9c947..ac72df56 100644 --- a/src/com/esri/core/geometry/ogc/OGCMultiPolygon.java +++ b/src/com/esri/core/geometry/ogc/OGCMultiPolygon.java @@ -1,8 +1,10 @@ package com.esri.core.geometry.ogc; +import com.esri.core.geometry.GeoJsonExportFlags; import com.esri.core.geometry.Geometry; import com.esri.core.geometry.GeometryEngine; import com.esri.core.geometry.Operator; +import com.esri.core.geometry.OperatorExportToGeoJson; import com.esri.core.geometry.OperatorExportToWkb; import com.esri.core.geometry.OperatorFactoryLocal; import com.esri.core.geometry.Polygon; @@ -32,7 +34,12 @@ public ByteBuffer asBinary() { return op.execute(WkbExportFlags.wkbExportMultiPolygon, getEsriGeometry(), null); } - + @Override + public String asGeoJson() { + OperatorExportToGeoJson op = (OperatorExportToGeoJson) OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.ExportToGeoJson); + return op.execute(GeoJsonExportFlags.geoJsonExportPreferMultiGeometry, null, getEsriGeometry()); + } @Override public int numGeometries() { return polygon.getExteriorRingCount(); diff --git a/src/com/esri/core/geometry/ogc/OGCPolygon.java b/src/com/esri/core/geometry/ogc/OGCPolygon.java index c94e6816..e3613e3a 100644 --- a/src/com/esri/core/geometry/ogc/OGCPolygon.java +++ b/src/com/esri/core/geometry/ogc/OGCPolygon.java @@ -27,7 +27,7 @@ public OGCPolygon(Polygon geom, SpatialReference sr) { polygon = geom; if (geom.getExteriorRingCount() > 1) throw new IllegalArgumentException( - "Polygon has to have one exterior ring"); + "Polygon has to have one exterior ring. Simplify geom with OperatorSimplify."); esriSR = sr; } diff --git a/unittest/com/esri/core/geometry/TestGeomToGeoJson.java b/unittest/com/esri/core/geometry/TestGeomToGeoJson.java index 3c7f5832..0cbb0dc4 100644 --- a/unittest/com/esri/core/geometry/TestGeomToGeoJson.java +++ b/unittest/com/esri/core/geometry/TestGeomToGeoJson.java @@ -174,24 +174,51 @@ public void testPolygon() { @Test public void testPolygonWithHole() { Polygon p = new Polygon(); - + + //exterior ring - has to be clockwise for Esri p.startPath(100.0, 0.0); + p.lineTo(100.0, 1.0); + p.lineTo(101.0, 1.0); p.lineTo(101.0, 0.0); - p.lineTo(101.0, 1.0); - p.lineTo(100.0, 1.0); p.closePathWithLine(); + //hole - counterclockwise for Esri p.startPath(100.2, 0.2); p.lineTo(100.8, 0.2); p.lineTo(100.8, 0.8); p.lineTo(100.2, 0.8); p.closePathWithLine(); - + OperatorExportToGeoJson exporter = (OperatorExportToGeoJson) factory.getOperator(Operator.Type.ExportToGeoJson); String result = exporter.execute(p); - assertEquals("{\"type\":\"Polygon\",\"coordinates\":[[[100.0,0.0],[101.0,0.0],[101.0,1.0],[100.0,1.0],[100.0,0.0]],[[100.2,0.2],[100.8,0.2],[100.8,0.8],[100.2,0.8],[100.2,0.2]]]}", result); + assertEquals("{\"type\":\"Polygon\",\"coordinates\":[[[100.0,0.0],[100.0,1.0],[101.0,1.0],[101.0,0.0],[100.0,0.0]],[[100.2,0.2],[100.8,0.2],[100.8,0.8],[100.2,0.8],[100.2,0.2]]]}", result); } + @Test + public void testPolygonWithHoleReversed() { + Polygon p = new Polygon(); + + //exterior ring - has to be clockwise for Esri + p.startPath(100.0, 0.0); + p.lineTo(100.0, 1.0); + p.lineTo(101.0, 1.0); + p.lineTo(101.0, 0.0); + p.closePathWithLine(); + + //hole - counterclockwise for Esri + p.startPath(100.2, 0.2); + p.lineTo(100.8, 0.2); + p.lineTo(100.8, 0.8); + p.lineTo(100.2, 0.8); + p.closePathWithLine(); + + p.reverseAllPaths();//make it reversed. Exterior ring - ccw, hole - cw. + + OperatorExportToGeoJson exporter = (OperatorExportToGeoJson) factory.getOperator(Operator.Type.ExportToGeoJson); + String result = exporter.execute(p); + assertEquals("{\"type\":\"Polygon\",\"coordinates\":[[[100.0,0.0],[101.0,0.0],[101.0,1.0],[100.0,1.0],[100.0,0.0]],[[100.2,0.2],[100.2,0.8],[100.8,0.8],[100.8,0.2],[100.2,0.2]]]}", result); + } + @Test public void testMultiPolygon() throws IOException { JsonFactory jsonFactory = new JsonFactory(); @@ -250,30 +277,44 @@ public void testOGCPolygon() { public void testPolygonWithHoleGeometryEngine() { Polygon p = new Polygon(); - p.startPath(100.0, 0.0); - p.lineTo(101.0, 0.0); - p.lineTo(101.0, 1.0); + p.startPath(100.0, 0.0);//clockwise exterior p.lineTo(100.0, 1.0); + p.lineTo(101.0, 1.0); + p.lineTo(101.0, 0.0); p.closePathWithLine(); - p.startPath(100.2, 0.2); + p.startPath(100.2, 0.2);//counterclockwise hole p.lineTo(100.8, 0.2); p.lineTo(100.8, 0.8); p.lineTo(100.2, 0.8); p.closePathWithLine(); String result = GeometryEngine.geometryToGeoJson(p); - assertEquals("{\"type\":\"Polygon\",\"coordinates\":[[[100.0,0.0],[101.0,0.0],[101.0,1.0],[100.0,1.0],[100.0,0.0]],[[100.2,0.2],[100.8,0.2],[100.8,0.8],[100.2,0.8],[100.2,0.2]]]}", result); + assertEquals("{\"type\":\"Polygon\",\"coordinates\":[[[100.0,0.0],[100.0,1.0],[101.0,1.0],[101.0,0.0],[100.0,0.0]],[[100.2,0.2],[100.8,0.2],[100.8,0.8],[100.2,0.8],[100.2,0.2]]]}", result); } + @Test + public void testPolylineWithTwoPaths() { + Polyline p = new Polyline(); + + p.startPath(100.0, 0.0); + p.lineTo(100.0, 1.0); + + p.startPath(100.2, 0.2); + p.lineTo(100.8, 0.2); + + String result = GeometryEngine.geometryToGeoJson(p); + assertEquals("{\"type\":\"MultiLineString\",\"coordinates\":[[[100.0,0.0],[100.0,1.0]],[[100.2,0.2],[100.8,0.2]]]}", result); + } + @Test public void testOGCPolygonWithHole() { Polygon p = new Polygon(); p.startPath(100.0, 0.0); - p.lineTo(101.0, 0.0); - p.lineTo(101.0, 1.0); p.lineTo(100.0, 1.0); + p.lineTo(101.0, 1.0); + p.lineTo(101.0, 0.0); p.closePathWithLine(); p.startPath(100.2, 0.2); @@ -284,7 +325,7 @@ public void testOGCPolygonWithHole() { OGCPolygon ogcPolygon = new OGCPolygon(p, null); String result = ogcPolygon.asGeoJson(); - assertEquals("{\"type\":\"Polygon\",\"coordinates\":[[[100.0,0.0],[101.0,0.0],[101.0,1.0],[100.0,1.0],[100.0,0.0]],[[100.2,0.2],[100.8,0.2],[100.8,0.8],[100.2,0.8],[100.2,0.2]]]}", result); + assertEquals("{\"type\":\"Polygon\",\"coordinates\":[[[100.0,0.0],[100.0,1.0],[101.0,1.0],[101.0,0.0],[100.0,0.0]],[[100.2,0.2],[100.8,0.2],[100.8,0.8],[100.2,0.8],[100.2,0.2]]]}", result); } @Test diff --git a/unittest/com/esri/core/geometry/TestOGC.java b/unittest/com/esri/core/geometry/TestOGC.java index e3e4c524..218ebdc3 100644 --- a/unittest/com/esri/core/geometry/TestOGC.java +++ b/unittest/com/esri/core/geometry/TestOGC.java @@ -11,7 +11,11 @@ import com.esri.core.geometry.ogc.OGCPoint; import com.esri.core.geometry.ogc.OGCPolygon; import com.esri.core.geometry.ogc.OGCConcreteGeometryCollection; + +import org.codehaus.jackson.JsonParseException; import org.json.JSONException; + +import java.io.IOException; import java.nio.ByteBuffer; public class TestOGC extends TestCase { @@ -726,4 +730,20 @@ public void testMultiPointSinglePoint() { assertTrue(s.equals("POINT (1 0)")); } + public void testWktMultiPolygon() { + String restJson = "{\"rings\": [[[-100, -100], [-100, 100], [100, 100], [100, -100], [-100, -100]], [[-90, -90], [90, 90], [-90, 90], [90, -90], [-90, -90]], [[-10, -10], [-10, 10], [10, 10], [10, -10], [-10, -10]]]}"; + MapGeometry g = null; + try { + g = OperatorImportFromJson.local().execute(Geometry.Type.Unknown, restJson); + } catch (JsonParseException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } catch (IOException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + String wkt = OperatorExportToWkt.local().execute(0, g.getGeometry(), null); + assertTrue(wkt.equals("MULTIPOLYGON (((-100 -100, 100 -100, 100 100, -100 100, -100 -100), (-90 -90, 90 -90, -90 90, 90 90, -90 -90)), ((-10 -10, 10 -10, 10 10, -10 10, -10 -10)))")); + } + } From 332cf20d806d81ce18826dc5c9d6aac965a8d387 Mon Sep 17 00:00:00 2001 From: Annette Locke Date: Mon, 1 Jul 2013 14:22:09 -0700 Subject: [PATCH 054/196] Fix envelope description. --- src/com/esri/core/geometry/Envelope.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/com/esri/core/geometry/Envelope.java b/src/com/esri/core/geometry/Envelope.java index b9227c4c..e51c4ee8 100644 --- a/src/com/esri/core/geometry/Envelope.java +++ b/src/com/esri/core/geometry/Envelope.java @@ -30,7 +30,7 @@ import com.esri.core.geometry.VertexDescription.Semantics; /** - * An envelope is a rectangle. + * An envelope is an axis-aligned rectangle. */ public final class Envelope extends Geometry implements Serializable { private static final long serialVersionUID = 2L; From a842bef10146bc6466534d1bdcd68bb13cdc8691 Mon Sep 17 00:00:00 2001 From: Annette Locke Date: Fri, 12 Jul 2013 17:02:04 -0700 Subject: [PATCH 055/196] Add link to Wiki --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 30a1b565..3bbdaa10 100644 --- a/README.md +++ b/README.md @@ -35,6 +35,7 @@ The project is also available as a [Maven](http://maven.apache.org/) dependency: * Familiarity with text-based spatial data formats such as JSON or WKT would be useful. ## Documentation +* [geometry-api-java/Wiki](http://esri.github.com/geometry-api-java/wiki/) * [geometry-api-java/Javadoc](http://esri.github.com/geometry-api-java/javadoc/) ## Resources From 6d9cc3148a772e33ed073d86006b6e8afcde7324 Mon Sep 17 00:00:00 2001 From: Annette Locke Date: Fri, 12 Jul 2013 17:04:25 -0700 Subject: [PATCH 056/196] Add CORRECT link to wiki. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 3bbdaa10..60ce44ef 100644 --- a/README.md +++ b/README.md @@ -35,7 +35,7 @@ The project is also available as a [Maven](http://maven.apache.org/) dependency: * Familiarity with text-based spatial data formats such as JSON or WKT would be useful. ## Documentation -* [geometry-api-java/Wiki](http://esri.github.com/geometry-api-java/wiki/) +* [geometry-api-java/Wiki](https://github.com/Esri/geometry-api-java/wiki/) * [geometry-api-java/Javadoc](http://esri.github.com/geometry-api-java/javadoc/) ## Resources From 68ebd50f5035f0da030ece37a726bea09b46e120 Mon Sep 17 00:00:00 2001 From: Mike Park Date: Mon, 15 Jul 2013 12:46:07 -0700 Subject: [PATCH 057/196] [maven-release-plugin] prepare release v1.1 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 2cda9fb4..24379687 100644 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ com.esri.geometry esri-geometry-api - 1.1-SNAPSHOT + 1.1 jar Esri Geometry API for Java From 13113f85813e8994f1401a5c3f9af588fee62126 Mon Sep 17 00:00:00 2001 From: Mike Park Date: Mon, 15 Jul 2013 12:46:12 -0700 Subject: [PATCH 058/196] [maven-release-plugin] prepare for next development iteration --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 24379687..0d0d59ae 100644 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ com.esri.geometry esri-geometry-api - 1.1 + 1.2-SNAPSHOT jar Esri Geometry API for Java From 8ddb7d83b5a1539899ccf73249468eb7d80f0698 Mon Sep 17 00:00:00 2001 From: Michael Park Date: Mon, 15 Jul 2013 14:27:03 -0700 Subject: [PATCH 059/196] Update README.md Updated Maven dependency version --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 60ce44ef..ca143e5f 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,7 @@ The project is also available as a [Maven](http://maven.apache.org/) dependency: com.esri.geometry esri-geometry-api - 1.0 + 1.1 ``` From 1cf356daea5acd6fc9d42fe8e9df966b3a7d2a04 Mon Sep 17 00:00:00 2001 From: Randall Whitman Date: Mon, 15 Jul 2013 16:14:12 -0700 Subject: [PATCH 060/196] GeometryCursor: make tock method public --- src/com/esri/core/geometry/GeometryCursor.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/com/esri/core/geometry/GeometryCursor.java b/src/com/esri/core/geometry/GeometryCursor.java index 77a2f880..503dfe4c 100644 --- a/src/com/esri/core/geometry/GeometryCursor.java +++ b/src/com/esri/core/geometry/GeometryCursor.java @@ -47,5 +47,5 @@ public abstract class GeometryCursor { *This method is to be used together with the tick() method on the ListeningGeometryCursor. *Call tock() for each tick() on the ListeningGeometryCursor. */ - protected boolean tock() { return true; } + public boolean tock() { return true; } } From 07b3c6130b1543023353f346319f051db966b76e Mon Sep 17 00:00:00 2001 From: Sergey Tolstov Date: Fri, 9 Aug 2013 16:17:24 -0700 Subject: [PATCH 061/196] Fix for the null "coordinates" element --- .../geometry/OperatorImportFromGeoJsonLocal.java | 12 +++++++++--- .../com/esri/core/geometry/TestGeomToGeoJson.java | 5 +++++ 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/src/com/esri/core/geometry/OperatorImportFromGeoJsonLocal.java b/src/com/esri/core/geometry/OperatorImportFromGeoJsonLocal.java index 00d44a7f..7708a4a7 100644 --- a/src/com/esri/core/geometry/OperatorImportFromGeoJsonLocal.java +++ b/src/com/esri/core/geometry/OperatorImportFromGeoJsonLocal.java @@ -41,6 +41,13 @@ public MapGeometry execute(int importFlags, Geometry.Type type, MapGeometry mapGeometry = new MapGeometry(geometry, spatialReference); return mapGeometry; } + + static JSONArray getJSONArray(JSONObject obj, String name) { + if (obj.get(name) == JSONObject.NULL) + return new JSONArray(); + else + return obj.getJSONArray(name); + } @Override public MapOGCStructure executeOGC(int import_flags, String geoJsonString, @@ -80,7 +87,7 @@ public MapOGCStructure executeOGC(int import_flags, String geoJsonString, lastStructure.m_structures.add(next); structureStack.add(next); - JSONArray geometries = lastObject.getJSONArray("geometries"); + JSONArray geometries = getJSONArray(lastObject, "geometries"); indices.add(0); numGeometries.add(geometries.length()); @@ -149,8 +156,7 @@ private static Geometry importGeometryFromGeoJson_(int importFlags, Geometry.Type type, JSONObject geometryJSONObject) throws JSONException { String typeString = geometryJSONObject.getString("type"); - JSONArray coordinateArray = geometryJSONObject - .getJSONArray("coordinates"); + JSONArray coordinateArray = getJSONArray(geometryJSONObject, "coordinates"); if (typeString.equalsIgnoreCase("MultiPolygon")) { if (type != Geometry.Type.Polygon && type != Geometry.Type.Unknown) diff --git a/unittest/com/esri/core/geometry/TestGeomToGeoJson.java b/unittest/com/esri/core/geometry/TestGeomToGeoJson.java index 0cbb0dc4..d3a60b13 100644 --- a/unittest/com/esri/core/geometry/TestGeomToGeoJson.java +++ b/unittest/com/esri/core/geometry/TestGeomToGeoJson.java @@ -246,6 +246,10 @@ public void testEmptyPolygon() { OperatorExportToGeoJson exporter = (OperatorExportToGeoJson) factory.getOperator(Operator.Type.ExportToGeoJson); String result = exporter.execute(p); assertEquals("{\"type\":\"Polygon\",\"coordinates\":null}", result); + + MapGeometry imported = OperatorImportFromGeoJson.local().execute(0, Geometry.Type.Unknown, result, null); + assertTrue(imported.getGeometry().isEmpty()); + assertTrue(imported.getGeometry().getType() == Geometry.Type.Polygon); } @Test @@ -352,4 +356,5 @@ public void testEnvelopeGeometryEngine() { String result = GeometryEngine.geometryToGeoJson(e); assertEquals("{\"bbox\":[-180.0,-90.0,180.0,90.0]}", result); } + } From 213b478a04d0502ae770e2f5c70bb1da5137eb5f Mon Sep 17 00:00:00 2001 From: Sergey Tolstov Date: Fri, 9 Aug 2013 16:32:50 -0700 Subject: [PATCH 062/196] add missing "throws" --- src/com/esri/core/geometry/OperatorImportFromGeoJsonLocal.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/com/esri/core/geometry/OperatorImportFromGeoJsonLocal.java b/src/com/esri/core/geometry/OperatorImportFromGeoJsonLocal.java index 7708a4a7..89cd6632 100644 --- a/src/com/esri/core/geometry/OperatorImportFromGeoJsonLocal.java +++ b/src/com/esri/core/geometry/OperatorImportFromGeoJsonLocal.java @@ -42,7 +42,7 @@ public MapGeometry execute(int importFlags, Geometry.Type type, return mapGeometry; } - static JSONArray getJSONArray(JSONObject obj, String name) { + static JSONArray getJSONArray(JSONObject obj, String name) throws JSONException { if (obj.get(name) == JSONObject.NULL) return new JSONArray(); else From 406392062de9f08d946fa56ea22361663336ab12 Mon Sep 17 00:00:00 2001 From: Sergey Tolstov Date: Fri, 9 Aug 2013 16:43:12 -0700 Subject: [PATCH 063/196] added another missing "throws" --- unittest/com/esri/core/geometry/TestGeomToGeoJson.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/unittest/com/esri/core/geometry/TestGeomToGeoJson.java b/unittest/com/esri/core/geometry/TestGeomToGeoJson.java index d3a60b13..80099069 100644 --- a/unittest/com/esri/core/geometry/TestGeomToGeoJson.java +++ b/unittest/com/esri/core/geometry/TestGeomToGeoJson.java @@ -30,6 +30,7 @@ import junit.framework.TestCase; import org.codehaus.jackson.JsonFactory; import org.codehaus.jackson.JsonParser; +import org.json.JSONException; import org.junit.Test; import java.io.IOException; @@ -241,7 +242,7 @@ public void testMultiPolygon() throws IOException { @Test - public void testEmptyPolygon() { + public void testEmptyPolygon() throws JSONException { Polygon p = new Polygon(); OperatorExportToGeoJson exporter = (OperatorExportToGeoJson) factory.getOperator(Operator.Type.ExportToGeoJson); String result = exporter.execute(p); From d2b35eec4739f527d6e62b3fee209f8a324ed677 Mon Sep 17 00:00:00 2001 From: Mike Park Date: Wed, 14 Aug 2013 08:54:30 -0700 Subject: [PATCH 064/196] modify pom to version 1.1.1 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 0d0d59ae..419d224c 100644 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ com.esri.geometry esri-geometry-api - 1.2-SNAPSHOT + 1.1.1-SNAPSHOT jar Esri Geometry API for Java From ba6c2c54062cfa90ed579ef3684348ab68ecd43d Mon Sep 17 00:00:00 2001 From: Mike Park Date: Wed, 14 Aug 2013 08:57:07 -0700 Subject: [PATCH 065/196] [maven-release-plugin] prepare release v1.1.1 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 419d224c..586cda04 100644 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ com.esri.geometry esri-geometry-api - 1.1.1-SNAPSHOT + 1.1.1 jar Esri Geometry API for Java From 134214d5e5cacdc111a7fd6021ed6c0beb1b7834 Mon Sep 17 00:00:00 2001 From: Mike Park Date: Wed, 14 Aug 2013 08:57:12 -0700 Subject: [PATCH 066/196] [maven-release-plugin] prepare for next development iteration --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 586cda04..bf7853a2 100644 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ com.esri.geometry esri-geometry-api - 1.1.1 + 1.1.2-SNAPSHOT jar Esri Geometry API for Java From 6022af1c175d125065cfaf6fa13369495bce797b Mon Sep 17 00:00:00 2001 From: Sergey Tolstov Date: Mon, 9 Sep 2013 10:32:49 -0700 Subject: [PATCH 067/196] Remove fixme comments (#7) --- src/com/esri/core/geometry/Envelope.java | 14 +- src/com/esri/core/geometry/InternalUtils.java | 3 +- src/com/esri/core/geometry/Line.java | 5 +- src/com/esri/core/geometry/MultiPath.java | 2 +- src/com/esri/core/geometry/MultiPathImpl.java | 96 +----- .../geometry/MultiVertexGeometryImpl.java | 57 ---- .../geometry/OperatorSimplifyLocalHelper.java | 21 -- .../geometry/PlaneSweepCrackerHelper.java | 12 +- .../core/geometry/PointInPolygonHelper.java | 2 - src/com/esri/core/geometry/Polygon.java | 5 - src/com/esri/core/geometry/PolygonUtils.java | 3 - src/com/esri/core/geometry/Segment.java | 5 - .../core/geometry/SegmentIteratorImpl.java | 2 - .../esri/core/geometry/VertexDescription.java | 6 +- .../VertexDescriptionDesignerImpl.java | 2 - .../esri/core/geometry/TestMultiPoint.java | 40 --- .../com/esri/core/geometry/TestPolygon.java | 287 ------------------ .../com/esri/core/geometry/TestSimplify.java | 3 - 18 files changed, 28 insertions(+), 537 deletions(-) diff --git a/src/com/esri/core/geometry/Envelope.java b/src/com/esri/core/geometry/Envelope.java index e51c4ee8..d9336182 100644 --- a/src/com/esri/core/geometry/Envelope.java +++ b/src/com/esri/core/geometry/Envelope.java @@ -136,6 +136,14 @@ public void setCoords(double xmin, double ymin, double xmax, double ymax) { m_envelope.setCoords(xmin, ymin, xmax, ymax); } + /** + * Sets the envelope from the array of points. The result envelope is a + * bounding box of all the points in the array. If the array has zero + * length, the envelope will be empty. + * + * @param points + * The point array. + */ void setCoords(Point[] points) { _touch(); setEmpty(); @@ -700,10 +708,8 @@ void _afterAddAttributeImpl(int semantics) {// copied from static void _attributeCopy(double[] src, int srcStart, double[] dst, int dstStart, int count) { - // FIXME performance!!!! - // System.arraycopy(src, srcStart, dst, dstStart, count); - for (int i = 0; i < count; i++) - dst[dstStart + i] = src[i + srcStart]; + if (count > 0) + System.arraycopy(src, srcStart, dst, dstStart, count); } double _getAttributeAsDbl(int endPoint, int semantics, int ordinate) { diff --git a/src/com/esri/core/geometry/InternalUtils.java b/src/com/esri/core/geometry/InternalUtils.java index 81ebd259..8ba01659 100644 --- a/src/com/esri/core/geometry/InternalUtils.java +++ b/src/com/esri/core/geometry/InternalUtils.java @@ -108,9 +108,8 @@ void shiftPath(MultiPath inputGeom, int iPath, double shift) { int i1 = inputGeom.getPathStart(iPath); int i2 = inputGeom.getPathEnd(iPath); - Point2D pt = new Point2D();// = null; + Point2D pt = new Point2D(); - // FIXME test to see if Point2D should be null while (i1 < i2) { xyStream.read(i1, pt); pt.x += shift; diff --git a/src/com/esri/core/geometry/Line.java b/src/com/esri/core/geometry/Line.java index 6063caf5..f4f2db08 100644 --- a/src/com/esri/core/geometry/Line.java +++ b/src/com/esri/core/geometry/Line.java @@ -560,9 +560,7 @@ boolean equals(Line other) { if (other == this) return true; - // FIXME review use of Java's instance of to see if it complies with - // Borgs - if (other instanceof Line)// (!IS_INSTANCE_OF(other, Line)) + if (other instanceof Line) return false; return _equalsImpl((Segment) other); @@ -992,7 +990,6 @@ int getYMonotonicParts(SegmentBuffer[] monotonicSegments) { return 0; } - // FIXME In Native borg this has no implementation @Override void _copyToImpl(Segment dst) { // TODO Auto-generated method stub diff --git a/src/com/esri/core/geometry/MultiPath.java b/src/com/esri/core/geometry/MultiPath.java index d0749652..eca929d6 100644 --- a/src/com/esri/core/geometry/MultiPath.java +++ b/src/com/esri/core/geometry/MultiPath.java @@ -599,7 +599,7 @@ void closePathWithBezier(Point2D controlPoint1, Point2D controlPoint2) { * Closes last path of the MultiPath with the Arc Segment. */ void closePathWithArc() { - throw new RuntimeException("not implemented"); /* FIXME */ + throw new RuntimeException("not implemented"); } /** diff --git a/src/com/esri/core/geometry/MultiPathImpl.java b/src/com/esri/core/geometry/MultiPathImpl.java index e8062f3a..a8058804 100644 --- a/src/com/esri/core/geometry/MultiPathImpl.java +++ b/src/com/esri/core/geometry/MultiPathImpl.java @@ -131,7 +131,6 @@ public void startPath(Point3D point) { // Reviewed vs. Native Jan 11, 2011 public void startPath(Point point) { if (point.isEmpty()) - // FIXME exc throw new IllegalArgumentException();// throw new // IllegalArgumentException(); @@ -292,18 +291,15 @@ public void bezierTo(Point2D controlPoint1, Point2D controlPoint2, public void openPath(int pathIndex) { _touch(); if (m_bPolygon) - // FIXME exc throw new GeometryException("internal error");// do not call this // method on a // polygon int pathCount = getPathCount(); if (pathIndex > getPathCount()) - // FIXME exc throw new IllegalArgumentException(); if (m_pathFlags == null) - // FIXME exc throw new GeometryException("internal error"); m_pathFlags.clearBits(pathIndex, (byte) PathFlags.enumClosed); @@ -359,7 +355,6 @@ public void openPathAndDuplicateStartVertex(int pathIndex) { public void openAllPathsAndDuplicateStartVertex() { _touch(); if (m_bPolygon) - // FIXME throw new GeometryException("internal error");// do not call this // method on a // polygon @@ -579,15 +574,13 @@ public void addSegment(Segment segment, boolean bStartNewPath) { if (segment.getType() == Type.Line) { Point point = new Point(); if (bStartNewPath || isEmpty()) { - // FIXME change getStart to queryStart!!!!!!! segment.queryStart(point); startPath(point); } - // FIXME change getStart to queryEnd + segment.queryEnd(point); lineTo(point); } else { - // FIXME throw new GeometryException("internal error"); } } @@ -665,9 +658,6 @@ public void add(MultiPathImpl src, boolean bReversePaths) { addPath(src, i, !bReversePaths); } - // Reviewed vs. Native Jan 11, 2011 - // FIXME THERE IS POTENTIALLY A BUG WITH the use of AttributeStream - // InsertRange public void addPath(MultiPathImpl src, int srcPathIndex, boolean bForward) { insertPath(-1, src, srcPathIndex, bForward); } @@ -677,56 +667,6 @@ public void addPath(Point2D[] _points, int count, boolean bForward) { insertPath(-1, _points, 0, count, bForward); } - // FIXME add add to attributestream base - // public void addPath(double[][] _points, int count, boolean bForward) - // { - // m_bPathStarted = false; - // - // int oldPointCount = m_pointCount; - // _verifyAllStreams(); - // - // int newPointCount = oldPointCount + count; - // if (oldPointCount > 0) - // m_paths.add(newPointCount); - // else - // { - // // _ASSERT(m_paths.size() == 2); - // m_paths.write(1, newPointCount); - // } - // - // _resizeImpl(newPointCount); - // - // _verifyAllStreams(); - // - // if (m_segmentParamIndex != null) - // { - // m_segmentParamIndex.resize(m_pointCount, -1); - // m_segmentFlags.resize(m_pointCount, (byte)SegmentFlags.enumLineSeg); - // } - // - // if (oldPointCount > 0) - // m_pathFlags.add(0); - // - // // _ASSERT(m_pathFlags.size() == m_paths.size()); - // - // if (m_bPolygon) - // { - // //Marc the path as closed - // m_pathFlags.write(m_pathFlags.size() - 2, (byte)PathFlags.enumClosed); - // } - // - // int j = oldPointCount; - // AttributeStreamOfDbl points = - // (AttributeStreamOfDbl)m_vertexAttributes[0]; - // for (int i = 0; i < count; i++, j++) - // { - // int index = (bForward ? i : count - i - 1); - // points.write(2 * j, _points[index][0]); - // points.write(2 * j + 1, _points[index][1]); - // } - // } - // - public void addSegmentsFromPath(MultiPathImpl src, int src_path_index, int src_segment_from, int src_segment_count, boolean b_start_new_path) { @@ -866,7 +806,6 @@ public void reversePath(int pathIndex) { _verifyAllStreams(); int pathCount = getPathCount(); if (pathIndex >= pathCount) - // FIXME exc throw new IllegalArgumentException(); int reversedPathStart = getPathStart(pathIndex); @@ -1797,29 +1736,24 @@ public void applyTransformation(Transformation2D transform, int pathIndex) { case SegmentFlags.enumBezierSeg: { ptControl.x = m_segmentParams.read(segIndex); ptControl.y = m_segmentParams.read(segIndex + 1); - // FIXME rohit has transform returning the object rather - // than transforming the input transform.transform(ptControl, ptControl); m_segmentParams.write(segIndex, ptControl.x); m_segmentParams.write(segIndex + 1, ptControl.y); ptControl.x = m_segmentParams.read(segIndex + 3); ptControl.y = m_segmentParams.read(segIndex + 4); - // FIXME rohit has transform returning the object rather - // than transforming the input transform.transform(ptControl, ptControl); m_segmentParams.write(segIndex + 3, ptControl.x); m_segmentParams.write(segIndex + 4, ptControl.y); } break; case SegmentFlags.enumArcSeg: - throw new GeometryException("internal error");// FIXME + throw new GeometryException("internal error"); } } } - // FIXME rohit has transform returning the object rather than - // transforming the input + transform.transform(ptStart, ptStart); points.write(ipoint * 2, ptStart.x); points.write(ipoint * 2 + 1, ptStart.y); @@ -1872,11 +1806,12 @@ public void applyTransformation(Transformation3D transform) { } break; case SegmentFlags.enumArcSeg: - throw new GeometryException("internal error");// FIXME + throw new GeometryException("internal error"); } } } + ptStart = transform.transform(ptStart); points.write(ipoint * 2, ptStart.x); points.write(ipoint * 2 + 1, ptStart.y); @@ -1911,7 +1846,6 @@ void _copyToImpl(MultiVertexGeometryImpl dst) { dstPoly.m_bPathStarted = false; dstPoly.m_curveParamwritePoint = m_curveParamwritePoint; - // FIXME there is no cloning in here. Is this necessary? if (m_paths != null) dstPoly.m_paths = new AttributeStreamOfInt32(m_paths); else @@ -1968,25 +1902,6 @@ public double calculateLength2D() { return len.getResult(); } - // FIXME figure out hascode - // int getHashCode() - // { - // int hashCode = MultiVertexGeometryImpl.getHashCode(); - // - // if (!isEmptyImpl()) - // { - // int pathCount = getPathCount(); - // - // if (m_paths != null) - // m_paths.calculateHashImpl(hashCode, 0, pathCount + 1); - // - // if (m_pathFlags != null) - // m_pathFlags.calculateHashImpl(hashCode, 0, pathCount); - // } - // - // return hashCode; - // } - @Override public boolean equals(Object other) { if (other == this) @@ -2026,7 +1941,6 @@ public boolean equals(Object other) { */ public SegmentIteratorImpl querySegmentIteratorAtVertex(int startVertexIndex) { if (startVertexIndex < 0 || startVertexIndex >= getPointCount()) - // FIXME throw new IndexOutOfBoundsException(); SegmentIteratorImpl iter = new SegmentIteratorImpl(this, diff --git a/src/com/esri/core/geometry/MultiVertexGeometryImpl.java b/src/com/esri/core/geometry/MultiVertexGeometryImpl.java index 9b5ce354..80c69315 100644 --- a/src/com/esri/core/geometry/MultiVertexGeometryImpl.java +++ b/src/com/esri/core/geometry/MultiVertexGeometryImpl.java @@ -292,12 +292,9 @@ public void setXY(int index, double x, double y) { @Override public Point3D getXYZ(int index) { if (index < 0 || index >= getPointCount()) - // FIXME exc throw new IndexOutOfBoundsException(); _verifyAllStreams(); - // AttributeStreamOfDbl v = (AttributeStreamOfDbl) - // m_vertexAttributes[0]; AttributeStreamOfDbl v = (AttributeStreamOfDbl) m_vertexAttributes[0]; Point3D pt = new Point3D(); pt.x = v.read(index * 2); @@ -316,15 +313,12 @@ public Point3D getXYZ(int index) { @Override public void setXYZ(int index, Point3D pt) { if (index < 0 || index >= getPointCount()) - // FIXME exc throw new IndexOutOfBoundsException(); addAttribute(Semantics.Z); _verifyAllStreams(); notifyModified(DirtyFlags.DirtyCoordinates); - // AttributeStreamOfDbl v = (AttributeStreamOfDbl) - // m_vertexAttributes[0]; AttributeStreamOfDbl v = (AttributeStreamOfDbl) m_vertexAttributes[0]; v.write(index * 2, pt.x); v.write(index * 2 + 1, pt.y); @@ -335,12 +329,10 @@ public void setXYZ(int index, Point3D pt) { @Override public double getAttributeAsDbl(int semantics, int offset, int ordinate) { if (offset < 0 || offset >= m_pointCount) - // FIXME exc throw new IndexOutOfBoundsException(); int ncomps = VertexDescription.getComponentCount(semantics); if (ordinate >= ncomps) - // FIXME exc throw new IndexOutOfBoundsException(); _verifyAllStreams(); @@ -366,12 +358,10 @@ public int getAttributeAsInt(int semantics, int offset, int ordinate) { public void setAttribute(int semantics, int offset, int ordinate, double value) { if (offset < 0 || offset >= m_pointCount) - // FIXME exc throw new IndexOutOfBoundsException(); int ncomps = VertexDescription.getComponentCount(semantics); if (ordinate >= ncomps) - // FIXME exc throw new IndexOutOfBoundsException(); addAttribute(semantics); @@ -388,8 +378,6 @@ public void setAttribute(int semantics, int offset, int ordinate, int value) { setAttribute(semantics, offset, ordinate, (double) value); } - // FIXME change semantics to an enum - // Checked vs. Jan 11, 2011 public AttributeStreamBase getAttributeStreamRef(int semantics) { throwIfEmpty(); @@ -400,8 +388,6 @@ public AttributeStreamBase getAttributeStreamRef(int semantics) { return m_vertexAttributes[attributeIndex]; } - // FIXME change semantics to an enum - // Checked vs. Jan 11, 2011 /** * Sets a reference to the given AttributeStream of the Geometry. Once the * buffer has been obtained, the vertices of the Geometry can be manipulated @@ -424,7 +410,6 @@ public void setAttributeStreamRef(int semantics, AttributeStreamBase stream) { if ((stream != null) && VertexDescription.getPersistence(semantics) != stream .getPersistence())// input stream has wrong persistence - // FIXME exc throw new IllegalArgumentException(); // Do not check for the stream size here to allow several streams to be @@ -641,7 +626,6 @@ public boolean equals(Object other) { */ public void setEnvelope(Envelope env) { if (!m_description.equals(env.getDescription())) - // FIXME exc throw new IllegalArgumentException(); // m_envelope = (Envelope) env.clone(); @@ -690,15 +674,10 @@ void _copyToUnsafe(MultiVertexGeometryImpl dst) { dst.m_flagsMask = m_flagsMask; dst.m_vertexAttributes = cloneAttributes; - // FIXME accelerators - // if(m_accelerators != null) - // dst.m_accelerators = m_accelerators; - try { _copyToImpl(dst); // copy child props } catch (Exception ex) { dst.setEmpty(); - // TODO fix exception throw new RuntimeException(ex); } } @@ -735,7 +714,6 @@ public void notifyModified(int flags) { } m_flagsMask |= flags; - // FIXME acceler _clearAccelerators(); _touch(); } @@ -853,10 +831,6 @@ protected void _verifyAllStreamsImpl() { m_vertexAttributes[attributeIndex] = AttributeStreamBase .createAttributeStreamWithSemantics(semantics, m_pointCount); - // FIXME when attribute stream is updated, update this code. - // m_vertexAttributes[attributeIndex] = - // AttributeStreamBase.createAttributeStream(semantics, - // m_pointCount); m_reservedPointCount = m_pointCount; } } @@ -1034,41 +1008,12 @@ void _interpolateTwoVertices(int vertex1, int vertex2, double f, return pt.length(); } - // FIXME Remove this method. It is not in the MultiVertexGeometryImpl... - - // /** - // * Returns a reference to the given AttributeStream of the Geometry. Once - // * the stream has been obtained, the vertices of the Geometry can be - // * manipulated directly. Call notifyModified, when finished. The method - // * allocates the stream if not present. - // * - // * @param semantics - // * Semantics of the attribute to return stream for. - // * @throws Throws - // * empty_geometry for the empty geometry. - // */ - // public AttributeStreamBase getAttributeStream(int semantics) { - // if (isEmpty()) - // throw new GeometryException( - // "This operation was performed on an Empty Geometry."); - // - // addAttribute(semantics); - // _verifyAllStreams(); - // - // int attributeIndex = m_description.getAttributeIndex(semantics); - // return m_vertexAttributes[attributeIndex]; - // } - - // FIXME // ////////////////// METHODS To REMOVE /////////////////////// @Override public Point getPoint(int index) { if (index < 0 || index >= m_pointCount) throw new IndexOutOfBoundsException(); - // _ASSERT(!IsEmpty()); - // _ASSERT(m_vertexAttributes != null); - _verifyAllStreams(); Point outPoint = new Point(); @@ -1149,8 +1094,6 @@ public void queryCoordinates(Point3D[] dst) { } } - // FIXME - public abstract boolean _buildRasterizedGeometryAccelerator( double toleranceXY, GeometryAccelerationDegree accelDegree); diff --git a/src/com/esri/core/geometry/OperatorSimplifyLocalHelper.java b/src/com/esri/core/geometry/OperatorSimplifyLocalHelper.java index 2f726e53..305b2d9d 100644 --- a/src/com/esri/core/geometry/OperatorSimplifyLocalHelper.java +++ b/src/com/esri/core/geometry/OperatorSimplifyLocalHelper.java @@ -1652,27 +1652,6 @@ MultiVertexGeometry simplifyPlanar_() { m_progressTracker); } - // if (false)// FIXME:do not forget to change this to if(false)!!! - // { - // OperatorSimplify simplify = (OperatorSimplify) - // (OperatorFactoryLocal.getInstance().getOperator(Operator.Type.Simplify)); - // NonSimpleResult nsres = new NonSimpleResult(); - // Geometry geometry = - // m_editShape.getGeometry(m_editShape.getFirstGeometry());// extract - // the result of simplify - // boolean res = simplify.isSimpleAsFeature(geometry, m_sr, true, nsres, - // null); - // if (false) - // { - // ((MultiPathImpl)geometry._getImpl()).saveToTextFileDbg("c:/temp/_simplifyDbg.txt"); - // } - // if (!res) - // { - // assert (nsres.m_reason.compareTo(NonSimpleResult.Reason.CrossOver) >= - // 0); - // } - // } - if (m_geometry.getType().equals(Geometry.Type.Polygon)) { Simplificator.execute(m_editShape, m_editShape.getFirstGeometry(), m_knownSimpleResult); diff --git a/src/com/esri/core/geometry/PlaneSweepCrackerHelper.java b/src/com/esri/core/geometry/PlaneSweepCrackerHelper.java index 75e01a50..175f485a 100644 --- a/src/com/esri/core/geometry/PlaneSweepCrackerHelper.java +++ b/src/com/esri/core/geometry/PlaneSweepCrackerHelper.java @@ -780,8 +780,10 @@ void processSplitHelper1_(int index, int edge, int clusterStart = getEdgeCluster(edge, 0); Point2D pt = getClusterXY(clusterStart); if (!pt.isEqual(newStart)) { - if (pt.compare(m_sweep_point) > 0 - && newStart.compare(m_sweep_point) < 0) { + int res1 = pt.compare(m_sweep_point); + int res2 = newStart.compare(m_sweep_point); + //if (pt.compare(m_sweep_point) > 0 && newStart.compare(m_sweep_point) < 0) + if (res1 * res2 < 0) { m_complications = true;// point is not yet have been processed // but moved before the sweep point, // this will require @@ -799,8 +801,10 @@ void processSplitHelper1_(int index, int edge, int clusterEnd = getEdgeCluster(edge, 1); pt = getClusterXY(clusterEnd); if (!pt.isEqual(newEnd)) { - if (pt.compare(m_sweep_point) > 0 - && newEnd.compare(m_sweep_point) < 0) { + int res1 = pt.compare(m_sweep_point); + int res2 = newEnd.compare(m_sweep_point); + //if (pt.compare(m_sweep_point) > 0 && newEnd.compare(m_sweep_point) < 0) + if (res1 * res2 < 0) { m_complications = true;// point is not yet have been processed // but moved before the sweep point. } diff --git a/src/com/esri/core/geometry/PointInPolygonHelper.java b/src/com/esri/core/geometry/PointInPolygonHelper.java index 1d4e6986..0cda4401 100644 --- a/src/com/esri/core/geometry/PointInPolygonHelper.java +++ b/src/com/esri/core/geometry/PointInPolygonHelper.java @@ -275,8 +275,6 @@ else if (hit == RasterizedGeometry2D.HitType.Outside) } } - // FIXME, the rest of the point in polygon tests are set up to avoid - // Point2D. This is the last bit return _isPointInPolygonInternal(inputPolygon, new Point2D( inputPointXVal, inputPointYVal), tolerance); } diff --git a/src/com/esri/core/geometry/Polygon.java b/src/com/esri/core/geometry/Polygon.java index 18e96249..2e173acd 100644 --- a/src/com/esri/core/geometry/Polygon.java +++ b/src/com/esri/core/geometry/Polygon.java @@ -73,11 +73,6 @@ public double calculateRingArea2D(int ringIndex) { return m_impl.calculateRingArea2D(ringIndex); } - // FIXME are these a java requirement? - // int getWKBPolygonCount() { return m_impl.getWKBPolygonCount(); } - // void setKnownRingOrientation(boolean bYesNo) { - // m_impl.setKnownRingOrientation(bYesNo); } - /** * Returns TRUE if the ring is an exterior ring. Valid only for simple * polygons. diff --git a/src/com/esri/core/geometry/PolygonUtils.java b/src/com/esri/core/geometry/PolygonUtils.java index 3c5bb5ca..e8f700ab 100644 --- a/src/com/esri/core/geometry/PolygonUtils.java +++ b/src/com/esri/core/geometry/PolygonUtils.java @@ -253,9 +253,6 @@ static boolean _isRingInRing2D(MultiPathImpl polygonImpl, int iRing1, if (!segIter.nextPath() || !segIter.hasNextSegment()) throw new GeometryException("corrupted geometry"); - // FIXME java enums can't cast to ints? what the hell?! - // enum_class PiPResult { PiPOutside = 0, PiPInside = 1, PiPBoundary = - // 2}; int res = 2;// 2(int)PiPResult.PiPBoundary; while (res == 2 /* (int)PiPResult.PiPBoundary */ diff --git a/src/com/esri/core/geometry/Segment.java b/src/com/esri/core/geometry/Segment.java index 501f7dda..4f9b131d 100644 --- a/src/com/esri/core/geometry/Segment.java +++ b/src/com/esri/core/geometry/Segment.java @@ -539,9 +539,7 @@ private void _get(int endPoint, Point outPoint) { if (isEmptyImpl()) throw new GeometryException("empty geometry");// ._setToDefault(); - // FIXME Native has this line repeated twice! Check up on this. outPoint.assignVertexDescription(m_description); - // outPoint.assignVertexDescription(m_description); if (outPoint.isEmptyImpl()) outPoint._setToDefault(); @@ -576,7 +574,6 @@ private void _set(int endPoint, Point src) { } double _getAttributeAsDbl(int endPoint, int semantics, int ordinate) { - // FIXME review rohits impl. Only an assertion in native if (isEmptyImpl()) throw new GeometryException( "This operation was performed on an Empty Geometry."); @@ -737,7 +734,6 @@ void reverse() { m_yEnd = origyStart; for (int i = 1, n = m_description.getAttributeCount(); i < n; i++) { - // FIXME fix stupid semantics enum int semantics = m_description.getSemantics(i);// VertexDescription.Semantics // semantics = // m_description.getSemantics(i); @@ -869,7 +865,6 @@ abstract double getClosestCoordinate(Point2D inputPoint, public abstract int intersectionWithAxis2D(boolean bAxisX, double ordinate, double[] resultOrdinates, double[] parameters); - // FIXME Ask sergey what this method is for void _reverseImpl() { } diff --git a/src/com/esri/core/geometry/SegmentIteratorImpl.java b/src/com/esri/core/geometry/SegmentIteratorImpl.java index 644a6767..af1e62b5 100644 --- a/src/com/esri/core/geometry/SegmentIteratorImpl.java +++ b/src/com/esri/core/geometry/SegmentIteratorImpl.java @@ -404,8 +404,6 @@ public void _updateSegment() { AttributeStreamOfInt8 segFlagStream = m_parent .getSegmentFlagsStreamRef(); - // FIXME Review this implementation of segment flags and the switch - // statement below. int segFlag = SegmentFlags.enumLineSeg; if (segFlagStream != null) segFlag = (segFlagStream.read(startVertexIndex) & SegmentFlags.enumSegmentMask); diff --git a/src/com/esri/core/geometry/VertexDescription.java b/src/com/esri/core/geometry/VertexDescription.java index b10afe2e..296e2af3 100644 --- a/src/com/esri/core/geometry/VertexDescription.java +++ b/src/com/esri/core/geometry/VertexDescription.java @@ -59,16 +59,14 @@ public class VertexDescription { static int[] _interpolation = { Interpolation.LINEAR, Interpolation.LINEAR, Interpolation.LINEAR, Interpolation.NONE, Interpolation.ANGULAR, Interpolation.LINEAR, Interpolation.LINEAR, Interpolation.LINEAR, - Interpolation.NONE, // FIXME this last value doesnt exist in native + Interpolation.NONE, }; static int[] _persistence = { Persistence.enumDouble, Persistence.enumDouble, Persistence.enumDouble, Persistence.enumInt32, Persistence.enumFloat, Persistence.enumFloat, Persistence.enumFloat, - Persistence.enumFloat, Persistence.enumInt32, // FIXME, this last - // Int32 doesn't - // exist in native + Persistence.enumFloat, Persistence.enumInt32, }; static int[] _persistencesize = { 4, 8, 4, 8, 1, 2 }; diff --git a/src/com/esri/core/geometry/VertexDescriptionDesignerImpl.java b/src/com/esri/core/geometry/VertexDescriptionDesignerImpl.java index d7570b0c..3ead4792 100644 --- a/src/com/esri/core/geometry/VertexDescriptionDesignerImpl.java +++ b/src/com/esri/core/geometry/VertexDescriptionDesignerImpl.java @@ -152,8 +152,6 @@ VertexDescription _createInternal() { protected void _initMapping() { m_attributeCount = 0; - // FIXME native has for loop for (int i = 0, j = 0; i < - // Semantics.MAXSEMANTICS + 1; i++) for (int i = 0, j = 0; i < Semantics.MAXSEMANTICS; i++) { if (m_semanticsToIndexMap[i] >= 0) { m_semantics[j] = i; diff --git a/unittest/com/esri/core/geometry/TestMultiPoint.java b/unittest/com/esri/core/geometry/TestMultiPoint.java index 44c927f6..b5b7d534 100644 --- a/unittest/com/esri/core/geometry/TestMultiPoint.java +++ b/unittest/com/esri/core/geometry/TestMultiPoint.java @@ -77,35 +77,9 @@ public static void testCreation() { MultiPoint mpoint = new MultiPoint(); assertTrue(mpoint != null); - // FIXME uncomment when assertions are fixed - // try - // { - // // OutputDebugString(L"Test an assert\n"); - // // GeometryException::m_assertOnException = false; - // - // Point pt2 = mpoint.getPoint(0);; - // } - // catch(GeometryException except) - // { - // assertTrue(except.index_out_of_bounds); - // GeometryException::m_assertOnException = true; - // } MultiPoint mpoint1 = new MultiPoint(); assertTrue(mpoint1 != null); - // FIXME uncomment when assertions are fixed - // try - // { - // OutputDebugString(L"Test an assert\n"); - // GeometryException::m_assertOnException = false; - // Point ppp; - // mpoint.getPointByVal(0, ppp); - // } - // catch(GeometryException except) - // { - // assertTrue(except.index_out_of_bounds); - // GeometryException::m_assertOnException = true; - // } mpoint.setEmpty(); Point pt = new Point(0, 0); @@ -129,20 +103,6 @@ public static void testCreation() { mpoint.add(pt); Point pt3 = mpoint.getPoint(0); assertTrue(pt3.getX() == 0 && pt3.getY() == 0/* && pt3.getZ() == 0 */); - // assertFalse(mpoint->HasAttribute(VertexDescription::Semantics::Z)); - // FIXME once transform3D is public - // Transformation3D transform3D = GCNEW Transformation3D; - // transform3D.setTranslate(1, 1, 0); - // mpoint.applyTransformation(transform3D); - - // assertTrue(mpoint->HasAttribute(VertexDescription::Semantics::Z)); - // pt3 = mpoint.getPoint(0); - // assertTrue(pt3.x == 1 && pt3.y == 1 && pt3.z == 0); - // transform3D.setTranslate(56, 12, 333); - // mpoint.applyTransformation(transform3D); - // pt3 = mpoint.getXYZ(0); - // assertTrue(pt3.x == 57 && pt3.y == 13 && pt3.z == 333); - // CompareGeometryContent(mpoint, &pt3, 1); } { // test QueryInterval diff --git a/unittest/com/esri/core/geometry/TestPolygon.java b/unittest/com/esri/core/geometry/TestPolygon.java index 450ac083..22d9617c 100644 --- a/unittest/com/esri/core/geometry/TestPolygon.java +++ b/unittest/com/esri/core/geometry/TestPolygon.java @@ -77,147 +77,6 @@ public void testCreation1() { number = poly.getStateFlag(); assertTrue(Math.abs(poly.calculateArea2D() + 100) < 1e-12); number = poly.getStateFlag(); - - // FIXME - // env.queryCoornerByVal(index, ptDst) - // SPtrOfArrayOf(Point2D) corners = new ArrayOf(Point2D)(4); - // env.QueryCorners(corners); - // poly.setEmpty(); - // poly.addPath(corners, corners.length, true); - - // assertTrue(Math.abs(poly.calculateArea2D() - 100) < 1e-12); - - // { - // SegmentIterator segIter1 = poly.querySegmentIterator(); - // while (segIter1.nextPath()) - // { - // while (segIter1.hasNextSegment()) - // { - // Segment seg = segIter1.nextSegment(); - // double len = seg.calculateLength2D(); - // assertTrue(len != 0); - // } - // } - // } - - // env.QueryCornersReversed(corners); - // poly.setEmpty(); - // poly.addPath(corners, corners.length, true); - // assertTrue(Math.abs(poly.calculateArea2D() + 100) < 1e-12); - // - // poly.setEmpty(); - // env.SetCoords(-200, -200, 200, 200); - // poly.addEnvelope(env, false); - // env.SetCoords(-100, -100, 100, 100); - // poly.addEnvelope(env, true); - // assertTrue(Math.abs(poly.calculateArea2D() - (400 * 400 - 200 * 200)) - // < 1e-12); - // assertTrue(Math.abs(poly.calculateRingArea2D(1) - (- 200 * 200)) < - // 1e-12); - // assertTrue(Math.abs(poly.calculateRingArea2D(0) - (400 * 400)) < - // 1e-12); - // assertTrue(Math.abs(poly.calculateLength2D() - (400 * 4 + 200 * 4)) < - // 1e-12); - - // test CopyTo; - // Polygon polyCopy = new Polygon(); - // poly.copyTo(polyCopy); - // assertTrue(poly.calculateArea2D() == polyCopy.calculateArea2D()); - // assertTrue(poly.calculateRingArea2D(1) == - // polyCopy.calculateRingArea2D(1)); - // assertTrue(poly.calculateRingArea2D(1) == - // polyCopy.calculateRingArea2D(1)); - // assertTrue(poly.calculateLength2D() == polyCopy.calculateLength2D()); - // } - // - // Polygon poly = new Polygon(); - // poly.startPath(10, 1); - // poly.lineTo(15, 20); - // poly.lineTo(30, 14); - // poly.lineTo(60, 144); - // - // assertTrue(poly.getPointCount() == 4); - // assertTrue(poly.getPathCount() == 1); - // SPtrOfArrayOf(Point2D) xy = poly.getCoordinates2D(); - // assertTrue(xy[0].x == 10); assertTrue(xy[0].y == 1); - // assertTrue(xy[1].x == 15); assertTrue(xy[1].y == 20); - // assertTrue(xy[2].x == 30); assertTrue(xy[2].y == 14); - // assertTrue(xy[3].x == 60); assertTrue(xy[3].y == 144); - - // poly.startPath(20, 13); - // poly.lineTo(150, 120); - // poly.lineTo(300, 414); - // poly.lineTo(610, 14); - // poly.lineTo(6210, 140); - // - // assertTrue(poly.getPointCount() == 9); - // assertTrue(poly.getPathCount() == 2); - // assertTrue(poly.isClosedPath(0)); - // assertTrue(poly.isClosedPath(1)); - // assertFalse(poly.hasNonLinearSegments(0)); - // assertFalse(poly.hasNonLinearSegments(1)); - // - // { - // SegmentIterator segIter1 = poly.querySegmentIterator(); - // while (segIter1.nextPath()) - // { - // while (segIter1.hasNextSegment()) - // { - // Segment seg = segIter1.nextSegment(); - // double len = seg.calculateLength2D(); - // assertTrue(len != 0); - // } - // } - // } - - // { - // MultiPathImpl::Pointer mpImpl = - // (MultiPathImpl::Pointer)poly->_GetImpl(); - // AttributeStreamBase xy = - // mpImpl.getAttributeStreamRef(enum_value2(VertexDescription, - // Semantics, POSITION)); - // double x = xy.readAsDbl(2 * 0); - // double y = xy.readAsDbl(2 * 0 + 1); - // assertTrue(x == 10); assertTrue(y == 1); - // x = xy.readAsDbl(2 * 1); - // y = xy.readAsDbl(2 * 1 + 1); - // assertTrue(x == 15); assertTrue(y == 20); - // x = xy.readAsDbl(2 * 2); - // y = xy.readAsDbl(2 * 2 + 1); - // assertTrue(x == 30); assertTrue(y == 14); - // x = xy.readAsDbl(2 * 3); - // y = xy.readAsDbl(2 * 3 + 1); - // assertTrue(x == 60); assertTrue(y == 144); - // - // x = xy.readAsDbl(2 * 4); - // y = xy.readAsDbl(2 * 4 + 1); - // assertTrue(x == 20); assertTrue(y == 13); - // x = xy.readAsDbl(2 * 5); - // y = xy.readAsDbl(2 * 5 + 1); - // assertTrue(x == 150); assertTrue(y == 120); - // x = xy.readAsDbl(2 * 6); - // y = xy.readAsDbl(2 * 6 + 1); - // assertTrue(x == 300); assertTrue(y == 414); - // x = xy.readAsDbl(2 * 7); - // y = xy.readAsDbl(2 * 7 + 1); - // assertTrue(x == 610); assertTrue(y == 14); - // x = xy.readAsDbl(2 * 8); - // y = xy.readAsDbl(2 * 8 + 1); - // assertTrue(x == 6210); assertTrue(y == 140); - // - // assertTrue(Math.abs(mpImpl.calculateArea2D() - 71752.5) < 1e-6); - // assertTrue(Math.abs(mpImpl.calculateLength2D() - 13117.917692934170) - // < 1e-6); - // - // AttributeStreamOfIndexType parts = mpImpl.getPathStreamRef(); - // assertTrue(parts.size() == 3); - // assertTrue(parts.read(0) == 0); - // assertTrue(parts.read(1) == 4); - // assertTrue(parts.read(2) == 9); - // assertTrue(mpImpl.getSegmentIndexStreamRef() == NULLPTR); - // assertTrue(mpImpl.getSegmentFlagsStreamRef() == NULLPTR); - // assertTrue(mpImpl.getSegmentDataStreamRef() == NULLPTR); - // } } @Test @@ -299,17 +158,6 @@ public void testCreation2() { poly2.lineTo(100, 10); poly2.lineTo(100, 100); poly2.lineTo(10, 100); - - // FIXME - // RasterizedGeometry2D rg = - // RasterizedGeometry2D.create((Geometry)poly2, 0, 1024); - // RasterizedGeometry2D.HitType res = null; - // res = rg.queryPointInGeometry(7, 10); - // assertTrue(res == RasterizedGeometry2D.HitType.Outside); - // res = rg.queryPointInGeometry(10, 10); - // assertTrue(res == RasterizedGeometry2D.HitType.Border); - // res = rg.queryPointInGeometry(50, 50); - // assertTrue(res == RasterizedGeometry2D.HitType.Inside); } { @@ -933,17 +781,6 @@ public void testInsertPath() { Point2D pt3 = poly.getXY(3); assertTrue(pt3.x == 16 && pt3.y == 21); - // SPtrOfArrayOf(Point2D) points = NULLPTR; - // FIXME - // poly.insertPath(1, points, 0, 0, true); - // assertTrue(poly.getPathCount() == 5); - // assertTrue(poly.getPathStart(0) == 0); - // assertTrue(poly.getPathStart(1) == 4); - // assertTrue(poly.getPathStart(2) == 4); - // assertTrue(poly.getPathStart(3) == 8); - // assertTrue(poly.getPathStart(4) == 12); - // assertTrue(poly.getPointCount() == 16); - Point pt2d = new Point(-27, -27); poly.insertPoint(1, 0, pt2d); @@ -953,44 +790,6 @@ public void testInsertPath() { assertTrue(poly.getPathStart(2) == 9); assertTrue(poly.getPathStart(3) == 13); assertTrue(poly.getPointCount() == 17); - - // points = new ArrayOf(Point2D)(3); - // points[0].x = 17; - // points[0].y = 17; - // points[1].x = 19; - // points[1].y = 19; - // points[2].x = 23; - // points[2].y = 23; - - // poly.insertPath(1, points, 0, 3, true); - // assertTrue(poly.getPathCount() == 6); - // assertTrue(poly.getPathStart(0) == 0); - // assertTrue(poly.getPathStart(1) == 4); - // assertTrue(poly.getPathStart(2) == 7); - // assertTrue(poly.getPathStart(3) == 8); - // assertTrue(poly.getPathStart(4) == 12); - // assertTrue(poly.getPathStart(5) == 16); - // assertTrue(poly.getPointCount() == 20); - - // Point2D *pointsNative = new Point2D[3]; - // pointsNative[0].x = 29; - // pointsNative[0].y = 29; - // pointsNative[1].x = 31; - // pointsNative[1].y = 31; - // pointsNative[2].x = 37; - // pointsNative[2].y = 37; - - // FIXME - // poly.insertPath(1, pointsNative, 0, 3, true); - // assertTrue(poly.getPathCount() == 7); - // assertTrue(poly.getPathStart(0) == 0); - // assertTrue(poly.getPathStart(1) == 4); - // assertTrue(poly.getPathStart(2) == 7); - // assertTrue(poly.getPathStart(3) == 10); - // assertTrue(poly.getPathStart(4) == 11); - // assertTrue(poly.getPathStart(5) == 15); - // assertTrue(poly.getPathStart(6) == 19); - // assertTrue(poly.getPointCount() == 23); } @Test @@ -1222,99 +1021,13 @@ public void testInsertPointsFromArray() { poly1.lineTo(300, 147); poly1.lineTo(6000, 1447); - // FIXME - // poly1.insertPoints(1, 2, arr, 1, 3, true); //forward - assertTrue(poly1.getPathCount() == 2); assertTrue(poly1.getPathStart(1) == 4); assertTrue(poly1.isClosedPath(0)); assertTrue(poly1.isClosedPath(1)); - // assertTrue(poly1.getPointCount() == 11); - // assertTrue(poly1.getPathSize(1) == 7); - // Point2D ptOut; - // ptOut = poly1.getXY(5); - // assertTrue(ptOut.x == 1250 && ptOut.y == 207); - // ptOut = poly1.getXY(6); - // assertTrue(ptOut.x == 15 && ptOut.y == 20); - // ptOut = poly1.getXY(7); - // assertTrue(ptOut.x == 300 && ptOut.y == 14); - // ptOut = poly1.getXY(8); - // assertTrue(ptOut.x == 314 && ptOut.y == 217); - // ptOut = poly1.getXY(9); - // assertTrue(ptOut.x == 300 && ptOut.y == 147); - // ptOut = poly1.getXY(10); - // assertTrue(ptOut.x == 6000 && ptOut.y == 1447); - - // Point2D *points = new Point2D[3]; - // points[0].x = 17; - // points[0].y = 17; - // points[1].x = 19; - // points[1].y = 19; - // points[2].x = 23; - // points[2].y = 23; - - // FIXME - // poly1.insertPoints(1, 2, points, 0, 3, true); - // assertTrue(poly1.getPathCount() == 2); - // assertTrue(poly1.getPathStart(1) == 4); - // assertTrue(poly1.getPointCount() == 14); - // assertTrue(poly1.getPathSize(1) == 10); - - // ptOut = poly1.getXY(5); - // assertTrue(ptOut.x == 1250 && ptOut.y == 207); - // ptOut = poly1.getXY(6); - // assertTrue(ptOut.x == 17 && ptOut.y == 17); - // ptOut = poly1.getXY(7); - // assertTrue(ptOut.x == 19 && ptOut.y == 19); - // ptOut = poly1.getXY(8); - // assertTrue(ptOut.x == 23 && ptOut.y == 23); - // ptOut = poly1.getXY(9); - // assertTrue(ptOut.x == 15 && ptOut.y == 20); - // ptOut = poly1.getXY(10); - // assertTrue(ptOut.x == 300 && ptOut.y == 14); } {// Test reversed insertion of an array of Point2D - // ArrayOf(Point2D) arr = new ArrayOf(Point2D)(5); - // arr[0].SetCoords(10, 1); - // arr[1].SetCoords(15, 20); - // arr[2].SetCoords(300, 14); - // arr[3].SetCoords(314, 217); - // arr[4].SetCoords(60, 144); - - // Polygon poly1 = new Polygon(); - // poly1.startPath(1, 17); - // poly1.lineTo(1, 207); - // poly1.lineTo(3, 147); - // poly1.lineTo(6, 1447); - // - // poly1.startPath(1000, 17); - // poly1.lineTo(1250, 207); - // poly1.lineTo(300, 147); - // poly1.lineTo(6000, 1447); - // - // //FIXME - // //poly1.insertPoints(1, 2, arr, 1, 3, false); //reversed - // - // assertTrue(poly1.getPathCount() == 2); - // assertTrue(poly1.getPathStart(1) == 4); - // assertTrue(poly1.isClosedPath(0)); - // assertTrue(poly1.isClosedPath(1)); - // assertTrue(poly1.getPointCount() == 11); - // assertTrue(poly1.getPathSize(1) == 7); - // Point2D ptOut; - // ptOut = poly1.getXY(5); - // assertTrue(ptOut.x == 1250 && ptOut.y == 207); - // ptOut = poly1.getXY(6); - // assertTrue(ptOut.x == 314 && ptOut.y == 217); - // ptOut = poly1.getXY(7); - // assertTrue(ptOut.x == 300 && ptOut.y == 14); - // ptOut = poly1.getXY(8); - // assertTrue(ptOut.x == 15 && ptOut.y == 20); - // ptOut = poly1.getXY(9); - // assertTrue(ptOut.x == 300 && ptOut.y == 147); - // ptOut = poly1.getXY(10); - // assertTrue(ptOut.x == 6000 && ptOut.y == 1447); } } diff --git a/unittest/com/esri/core/geometry/TestSimplify.java b/unittest/com/esri/core/geometry/TestSimplify.java index f39ae4de..8d76da1b 100644 --- a/unittest/com/esri/core/geometry/TestSimplify.java +++ b/unittest/com/esri/core/geometry/TestSimplify.java @@ -431,9 +431,6 @@ public void testPolygon5() { true, null, null); assertTrue(res); - // FIXME Bowtie. once simplify is fixed this should result in a - // simplified geom - int pointCount = simplePolygon5.getPointCount(); assertTrue(pointCount == 6); From 96c5c608ff8f61c6a366d48f1022c7d0f7b42b3e Mon Sep 17 00:00:00 2001 From: David Kaiser Date: Thu, 12 Sep 2013 14:20:16 -0700 Subject: [PATCH 068/196] Updates to contribution statements; link contribution site --- CONTRIBUTING.md | 1 + README.md | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) create mode 100644 CONTRIBUTING.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 00000000..4180c704 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1 @@ +Esri welcomes contributions from anyone and everyone. Please see our [guidelines for contributing](https://github.com/esri/contributing). diff --git a/README.md b/README.md index ca143e5f..618f1572 100644 --- a/README.md +++ b/README.md @@ -50,7 +50,7 @@ Find a bug or want to request a new feature? Please let us know by submitting a ## Contributing -Anyone and everyone is welcome to contribute. +Esri welcomes contributions from anyone and everyone. Please see our [guidelines for contributing](https://github.com/esri/contributing) ## Licensing Copyright 2013 Esri From 100dadfbbbfbaf5aa5a953c5294322852e47c89e Mon Sep 17 00:00:00 2001 From: Sergey Tolstov Date: Thu, 19 Sep 2013 15:40:08 -0700 Subject: [PATCH 069/196] Fixed OGCMultiPolygon.geometryN, added OGCGeometry.convertToMulti --- .../ogc/OGCConcreteGeometryCollection.java | 6 +++ .../esri/core/geometry/ogc/OGCGeometry.java | 6 +++ .../esri/core/geometry/ogc/OGCLineString.java | 6 +++ .../core/geometry/ogc/OGCMultiLineString.java | 6 +++ .../esri/core/geometry/ogc/OGCMultiPoint.java | 6 +++ .../core/geometry/ogc/OGCMultiPolygon.java | 8 ++- src/com/esri/core/geometry/ogc/OGCPoint.java | 6 +++ .../esri/core/geometry/ogc/OGCPolygon.java | 6 +++ unittest/com/esri/core/geometry/TestOGC.java | 49 ++++++++++++++----- 9 files changed, 87 insertions(+), 12 deletions(-) diff --git a/src/com/esri/core/geometry/ogc/OGCConcreteGeometryCollection.java b/src/com/esri/core/geometry/ogc/OGCConcreteGeometryCollection.java index a3b6d989..a111cf23 100644 --- a/src/com/esri/core/geometry/ogc/OGCConcreteGeometryCollection.java +++ b/src/com/esri/core/geometry/ogc/OGCConcreteGeometryCollection.java @@ -305,4 +305,10 @@ public void setSpatialReference(SpatialReference esriSR_) { } } + @Override + public OGCGeometry convertToMulti() + { + return this; + } + } diff --git a/src/com/esri/core/geometry/ogc/OGCGeometry.java b/src/com/esri/core/geometry/ogc/OGCGeometry.java index 47c5c304..ef6e1452 100644 --- a/src/com/esri/core/geometry/ogc/OGCGeometry.java +++ b/src/com/esri/core/geometry/ogc/OGCGeometry.java @@ -634,4 +634,10 @@ protected boolean isConcreteGeometryCollection() { public void setSpatialReference(SpatialReference esriSR_) { esriSR = esriSR_; } + + /** + *Converts this Geometry to the OGCMulti* if it is not OGCMulti* or OGCGeometryCollection already. + * @return OGCMulti* or OGCGeometryCollection instance. + */ + public abstract OGCGeometry convertToMulti(); } diff --git a/src/com/esri/core/geometry/ogc/OGCLineString.java b/src/com/esri/core/geometry/ogc/OGCLineString.java index 6310a77f..eb3ee7bb 100644 --- a/src/com/esri/core/geometry/ogc/OGCLineString.java +++ b/src/com/esri/core/geometry/ogc/OGCLineString.java @@ -107,5 +107,11 @@ public Geometry getEsriGeometry() { return multiPath; } + @Override + public OGCGeometry convertToMulti() + { + return new OGCMultiLineString((Polyline)multiPath, esriSR); + } + MultiPath multiPath; } diff --git a/src/com/esri/core/geometry/ogc/OGCMultiLineString.java b/src/com/esri/core/geometry/ogc/OGCMultiLineString.java index 2183020d..2ea784a3 100644 --- a/src/com/esri/core/geometry/ogc/OGCMultiLineString.java +++ b/src/com/esri/core/geometry/ogc/OGCMultiLineString.java @@ -76,5 +76,11 @@ public Geometry getEsriGeometry() { return polyline; } + @Override + public OGCGeometry convertToMulti() + { + return this; + } + Polyline polyline; } diff --git a/src/com/esri/core/geometry/ogc/OGCMultiPoint.java b/src/com/esri/core/geometry/ogc/OGCMultiPoint.java index fa3f7c83..a57c3b4e 100644 --- a/src/com/esri/core/geometry/ogc/OGCMultiPoint.java +++ b/src/com/esri/core/geometry/ogc/OGCMultiPoint.java @@ -95,5 +95,11 @@ public Geometry getEsriGeometry() { return multiPoint; } + @Override + public OGCGeometry convertToMulti() + { + return this; + } + private com.esri.core.geometry.MultiPoint multiPoint; } diff --git a/src/com/esri/core/geometry/ogc/OGCMultiPolygon.java b/src/com/esri/core/geometry/ogc/OGCMultiPolygon.java index ac72df56..878ea168 100644 --- a/src/com/esri/core/geometry/ogc/OGCMultiPolygon.java +++ b/src/com/esri/core/geometry/ogc/OGCMultiPolygon.java @@ -52,7 +52,7 @@ public OGCGeometry geometryN(int n) { if (polygon.isExteriorRing(i)) exterior++; - if (exterior == i + 1) { + if (exterior == n + 1) { return new OGCPolygon(polygon, i, esriSR); } } @@ -90,5 +90,11 @@ public Geometry getEsriGeometry() { return polygon; } + @Override + public OGCGeometry convertToMulti() + { + return this; + } + Polygon polygon; } diff --git a/src/com/esri/core/geometry/ogc/OGCPoint.java b/src/com/esri/core/geometry/ogc/OGCPoint.java index d04bbc62..b6a8f9e0 100644 --- a/src/com/esri/core/geometry/ogc/OGCPoint.java +++ b/src/com/esri/core/geometry/ogc/OGCPoint.java @@ -76,6 +76,12 @@ public OGCGeometry locateBetween(double mStart, double mEnd) { public com.esri.core.geometry.Geometry getEsriGeometry() { return point; } + + @Override + public OGCGeometry convertToMulti() + { + return new OGCMultiPoint(point, esriSR); + } com.esri.core.geometry.Point point; diff --git a/src/com/esri/core/geometry/ogc/OGCPolygon.java b/src/com/esri/core/geometry/ogc/OGCPolygon.java index e3613e3a..c9f9fcff 100644 --- a/src/com/esri/core/geometry/ogc/OGCPolygon.java +++ b/src/com/esri/core/geometry/ogc/OGCPolygon.java @@ -102,5 +102,11 @@ public Geometry getEsriGeometry() { return polygon; } + @Override + public OGCGeometry convertToMulti() + { + return new OGCMultiPolygon(polygon, esriSR); + } + Polygon polygon; } diff --git a/unittest/com/esri/core/geometry/TestOGC.java b/unittest/com/esri/core/geometry/TestOGC.java index 218ebdc3..3099767a 100644 --- a/unittest/com/esri/core/geometry/TestOGC.java +++ b/unittest/com/esri/core/geometry/TestOGC.java @@ -150,6 +150,8 @@ public void testFirstPointOfPolygon() { assertTrue(ls.pointN(3).equals(OGCGeometry.fromText("POINT(-10 10)"))); OGCPoint p0 = ls.pointN(0); assertTrue(ls.pointN(0).equals(OGCGeometry.fromText("POINT(-10 -10)"))); + String ms = g.convertToMulti().asText(); + assertTrue(ms.equals("MULTIPOLYGON (((-10 -10, 10 -10, 10 10, -10 10, -10 -10), (-5 -5, -5 5, 5 5, 5 -5, -5 -5)))")); } @@ -161,6 +163,8 @@ public void testFirstPointOfLineString() { assertTrue(p.numPoints() == 5); assertTrue(p.isClosed()); assertTrue(p.pointN(1).equals(OGCGeometry.fromText("POINT(10 -10)"))); + String ms = g.convertToMulti().asText(); + assertTrue(ms.equals("MULTILINESTRING ((-10 -10, 10 -10, 10 10, -10 10, -10 -10))")); } public void testPointInPolygon() { @@ -173,20 +177,39 @@ public void testPointInPolygon() { assertTrue(g.disjoint(OGCGeometry.fromText("POINT(0 0)"))); assertTrue(!g.disjoint(OGCGeometry.fromText("POINT(9 9)"))); assertTrue(g.disjoint(OGCGeometry.fromText("POINT(-20 1)"))); - } public void testMultiPolygon() { - OGCGeometry g = OGCGeometry - .fromText("MULTIPOLYGON(((-10 -10, 10 -10, 10 10, -10 10, -10 -10), (-5 -5, -5 5, 5 5, 5 -5, -5 -5)))"); - assertTrue(g.geometryType().equals("MultiPolygon")); // the type is - // reduced - assertTrue(!g.contains(OGCGeometry.fromText("POINT(0 0)"))); - assertTrue(g.contains(OGCGeometry.fromText("POINT(9 9)"))); - assertTrue(!g.contains(OGCGeometry.fromText("POINT(-20 1)"))); - assertTrue(g.disjoint(OGCGeometry.fromText("POINT(0 0)"))); - assertTrue(!g.disjoint(OGCGeometry.fromText("POINT(9 9)"))); - assertTrue(g.disjoint(OGCGeometry.fromText("POINT(-20 1)"))); + { + OGCGeometry g = OGCGeometry + .fromText("MULTIPOLYGON(((-10 -10, 10 -10, 10 10, -10 10, -10 -10), (-5 -5, -5 5, 5 5, 5 -5, -5 -5)))"); + assertTrue(g.geometryType().equals("MultiPolygon")); // the type is + // reduced + assertTrue(!g.contains(OGCGeometry.fromText("POINT(0 0)"))); + assertTrue(g.contains(OGCGeometry.fromText("POINT(9 9)"))); + assertTrue(!g.contains(OGCGeometry.fromText("POINT(-20 1)"))); + assertTrue(g.disjoint(OGCGeometry.fromText("POINT(0 0)"))); + assertTrue(!g.disjoint(OGCGeometry.fromText("POINT(9 9)"))); + assertTrue(g.disjoint(OGCGeometry.fromText("POINT(-20 1)"))); + assertTrue(g.convertToMulti() == g); + } + + { + OGCGeometry g = OGCGeometry + .fromText("MULTIPOLYGON(((-10 -10, 10 -10, 10 10, -10 10, -10 -10), (-5 -5, -5 5, 5 5, 5 -5, -5 -5)), ((90 90, 110 90, 110 110, 90 110, 90 90), (95 95, 95 105, 105 105, 105 95, 95 95)))"); + assertTrue(g.geometryType().equals("MultiPolygon")); // the type is + + OGCMultiPolygon mp = (OGCMultiPolygon)g; + assertTrue(mp.numGeometries() == 2); + OGCGeometry p1 = mp.geometryN(0); + assertTrue(p1.geometryType().equals("Polygon")); // the type is + assertTrue(p1.contains(OGCGeometry.fromText("POINT(9 9)"))); + assertTrue(!p1.contains(OGCGeometry.fromText("POINT(109 109)"))); + OGCGeometry p2 = mp.geometryN(1); + assertTrue(p2.geometryType().equals("Polygon")); // the type is + assertTrue(!p2.contains(OGCGeometry.fromText("POINT(9 9)"))); + assertTrue(p2.contains(OGCGeometry.fromText("POINT(109 109)"))); + } } public void testMultiPolygonUnion() { @@ -728,6 +751,10 @@ public void testMultiPointSinglePoint() { OGCGeometry p = mp.geometryN(0); String s = p.asText(); assertTrue(s.equals("POINT (1 0)")); + + String ms = p.convertToMulti().asText(); + assertTrue(ms.equals("MULTIPOINT ((1 0))")); + } public void testWktMultiPolygon() { From 8b3be68d801a46b285ec3b18ad348a0cdcb51a23 Mon Sep 17 00:00:00 2001 From: Aaron Balog Date: Fri, 18 Oct 2013 11:58:30 -0700 Subject: [PATCH 070/196] Added import from JSONObject and export to JsonWriter --- src/com/esri/core/geometry/JSONUtils.java | 23 +- src/com/esri/core/geometry/JsonReader.java | 368 +++++ src/com/esri/core/geometry/JsonWriter.java | 462 ++++++ .../core/geometry/OperatorExportToJson.java | 61 +- .../geometry/OperatorExportToJsonCursor.java | 835 ++++++----- .../geometry/OperatorExportToJsonLocal.java | 33 +- .../core/geometry/OperatorImportFromJson.java | 9 + .../OperatorImportFromJsonCursor.java | 57 +- .../geometry/OperatorImportFromJsonLocal.java | 10 + src/com/esri/core/geometry/QuadTreeImpl.java | 2 +- src/com/esri/core/geometry/StringUtils.java | 121 +- ...omToJSonExportSRFromWkiOrWkt_CR181369.java | 22 +- .../esri/core/geometry/TestJsonParser.java | 1301 +++++++++-------- .../com/esri/core/geometry/TestQuadTree.java | 13 + 14 files changed, 2130 insertions(+), 1187 deletions(-) create mode 100644 src/com/esri/core/geometry/JsonReader.java create mode 100644 src/com/esri/core/geometry/JsonWriter.java diff --git a/src/com/esri/core/geometry/JSONUtils.java b/src/com/esri/core/geometry/JSONUtils.java index e401df70..1ec51185 100644 --- a/src/com/esri/core/geometry/JSONUtils.java +++ b/src/com/esri/core/geometry/JSONUtils.java @@ -25,26 +25,25 @@ import java.io.IOException; import org.codehaus.jackson.JsonParseException; -import org.codehaus.jackson.JsonParser; import org.codehaus.jackson.JsonToken; final class JSONUtils { - static boolean isObjectStart(JsonParser parser) throws Exception { - return parser.getCurrentToken() == null ? parser.nextToken() == JsonToken.START_OBJECT - : parser.getCurrentToken() == JsonToken.START_OBJECT; + static boolean isObjectStart(JsonReader parser) throws Exception { + return parser.currentToken() == null ? parser.nextToken() == JsonToken.START_OBJECT + : parser.currentToken() == JsonToken.START_OBJECT; } - static double readDouble(JsonParser parser) throws JsonParseException, + static double readDouble(JsonReader parser) throws JsonParseException, IOException, Exception { - if (parser.getCurrentToken() == JsonToken.VALUE_NUMBER_FLOAT) - return parser.getDoubleValue(); - else if (parser.getCurrentToken() == JsonToken.VALUE_NUMBER_INT) - return parser.getIntValue(); - else if (parser.getCurrentToken() == JsonToken.VALUE_NULL) + if (parser.currentToken() == JsonToken.VALUE_NUMBER_FLOAT) + return parser.currentDoubleValue(); + else if (parser.currentToken() == JsonToken.VALUE_NUMBER_INT) + return parser.currentIntValue(); + else if (parser.currentToken() == JsonToken.VALUE_NULL) return NumberUtils.NaN(); - else if (parser.getCurrentToken() == JsonToken.VALUE_STRING) - if (parser.getText().equals("NaN")) + else if (parser.currentToken() == JsonToken.VALUE_STRING) + if (parser.currentString().equals("NaN")) return NumberUtils.NaN(); throw new GeometryException("invalid parameter"); diff --git a/src/com/esri/core/geometry/JsonReader.java b/src/com/esri/core/geometry/JsonReader.java new file mode 100644 index 00000000..065ecafe --- /dev/null +++ b/src/com/esri/core/geometry/JsonReader.java @@ -0,0 +1,368 @@ +/* + Copyright 1995-2013 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ +package com.esri.core.geometry; + +import java.util.ArrayList; +import org.codehaus.jackson.JsonParser; +import org.codehaus.jackson.JsonToken; +import org.json.JSONArray; +import org.json.JSONObject; + + +abstract class JsonReader { + + abstract JsonToken nextToken() throws Exception; + + abstract JsonToken currentToken() throws Exception; + + abstract void skipChildren() throws Exception; + + abstract String currentString() throws Exception; + + abstract double currentDoubleValue() throws Exception; + + abstract int currentIntValue() throws Exception; +} +final class JsonParserReader extends JsonReader { + + private JsonParser m_jsonParser; + + JsonParserReader(JsonParser jsonParser) { + m_jsonParser = jsonParser; + } + + @Override + JsonToken nextToken() throws Exception { + JsonToken token = m_jsonParser.nextToken(); + return token; + } + + @Override + JsonToken currentToken() throws Exception { + return m_jsonParser.getCurrentToken(); + } + + @Override + void skipChildren() throws Exception { + m_jsonParser.skipChildren(); + } + + @Override + String currentString() throws Exception { + return m_jsonParser.getText(); + } + + @Override + double currentDoubleValue() throws Exception { + return m_jsonParser.getValueAsDouble(); + } + + @Override + int currentIntValue() throws Exception { + return m_jsonParser.getValueAsInt(); + } +} + +final class JsonValueReader extends JsonReader { + + private Object m_object; + private JsonToken m_currentToken; + private ArrayList m_parentStack; + private ArrayList m_objIters; + private ArrayList m_arrIters; + + JsonValueReader(Object object) { + m_object = object; + + boolean bJSONObject = (m_object instanceof JSONObject); + boolean bJSONArray = (m_object instanceof JSONArray); + + if (!bJSONObject && !bJSONArray) { + throw new IllegalArgumentException(); + } + + m_parentStack = new ArrayList(0); + m_objIters = new ArrayList(0); + m_arrIters = new ArrayList(0); + + m_parentStack.ensureCapacity(4); + m_objIters.ensureCapacity(4); + m_arrIters.ensureCapacity(4); + + if (bJSONObject) { + JSONObjectEnumerator objIter = new JSONObjectEnumerator((JSONObject) m_object); + m_parentStack.add(JsonToken.START_OBJECT); + m_objIters.add(objIter); + m_currentToken = JsonToken.START_OBJECT; + } else { + JSONArrayEnumerator arrIter = new JSONArrayEnumerator((JSONArray) m_object); + m_parentStack.add(JsonToken.START_ARRAY); + m_arrIters.add(arrIter); + m_currentToken = JsonToken.START_ARRAY; + } + } + + private void setCurrentToken_(Object obj) { + if (obj instanceof String) { + m_currentToken = JsonToken.VALUE_STRING; + } else if (obj instanceof Double || obj instanceof Float) { + m_currentToken = JsonToken.VALUE_NUMBER_FLOAT; + } else if (obj instanceof Integer || obj instanceof Long || obj instanceof Short) { + m_currentToken = JsonToken.VALUE_NUMBER_INT; + } else if (obj instanceof Boolean) { + Boolean bObj = (Boolean) obj; + boolean b = bObj.booleanValue(); + if (b) { + m_currentToken = JsonToken.VALUE_TRUE; + } else { + m_currentToken = JsonToken.VALUE_FALSE; + } + } else if (obj instanceof JSONObject) { + m_currentToken = JsonToken.START_OBJECT; + } else if (obj instanceof JSONArray) { + m_currentToken = JsonToken.START_ARRAY; + } else { + m_currentToken = JsonToken.VALUE_NULL; + } + } + + Object currentObject_() { + assert (!m_parentStack.isEmpty()); + + JsonToken parentType = m_parentStack.get(m_parentStack.size() - 1); + + if (parentType == JsonToken.START_OBJECT) { + JSONObjectEnumerator objIter = m_objIters.get(m_objIters.size() - 1); + return objIter.getCurrentObject(); + } + + JSONArrayEnumerator arrIter = m_arrIters.get(m_arrIters.size() - 1); + return arrIter.getCurrentObject(); + } + + @Override + JsonToken nextToken() throws Exception { + if (m_parentStack.isEmpty()) { + m_currentToken = JsonToken.NOT_AVAILABLE; + return m_currentToken; + } + + JsonToken parentType = m_parentStack.get(m_parentStack.size() - 1); + + if (parentType == JsonToken.START_OBJECT) { + JSONObjectEnumerator iterator = m_objIters.get(m_objIters.size() - 1); + + if (m_currentToken == JsonToken.FIELD_NAME) { + Object nextJSONValue = iterator.getCurrentObject(); + + if (nextJSONValue instanceof JSONObject) { + m_parentStack.add(JsonToken.START_OBJECT); + m_objIters.add(new JSONObjectEnumerator((JSONObject) nextJSONValue)); + m_currentToken = JsonToken.START_OBJECT; + } else if (nextJSONValue instanceof JSONArray) { + m_parentStack.add(JsonToken.START_ARRAY); + m_arrIters.add(new JSONArrayEnumerator((JSONArray) nextJSONValue)); + m_currentToken = JsonToken.START_ARRAY; + } else { + setCurrentToken_(nextJSONValue); + } + } else { + if (iterator.next()) { + m_currentToken = JsonToken.FIELD_NAME; + } else { + m_objIters.remove(m_objIters.size() - 1); + m_parentStack.remove(m_parentStack.size() - 1); + m_currentToken = JsonToken.END_OBJECT; + } + } + } else { + assert (parentType == JsonToken.START_ARRAY); + JSONArrayEnumerator iterator = m_arrIters.get(m_arrIters.size() - 1); + if (iterator.next()) { + Object nextJSONValue = iterator.getCurrentObject(); + + if (nextJSONValue instanceof JSONObject) { + m_parentStack.add(JsonToken.START_OBJECT); + m_objIters.add(new JSONObjectEnumerator((JSONObject) nextJSONValue)); + m_currentToken = JsonToken.START_OBJECT; + } else if (nextJSONValue instanceof JSONArray) { + m_parentStack.add(JsonToken.START_ARRAY); + m_arrIters.add(new JSONArrayEnumerator((JSONArray) nextJSONValue)); + m_currentToken = JsonToken.START_ARRAY; + } else { + setCurrentToken_(nextJSONValue); + } + } else { + m_arrIters.remove(m_arrIters.size() - 1); + m_parentStack.remove(m_parentStack.size() - 1); + m_currentToken = JsonToken.END_ARRAY; + } + } + + return m_currentToken; + } + + @Override + JsonToken currentToken() throws Exception { + return m_currentToken; + } + + @Override + void skipChildren() throws Exception { + assert (!m_parentStack.isEmpty()); + + if (m_currentToken != JsonToken.START_OBJECT && m_currentToken != JsonToken.START_ARRAY) { + return; + } + + JsonToken parentType = m_parentStack.get(m_parentStack.size() - 1); + + if (parentType == JsonToken.START_OBJECT) { + m_objIters.remove(m_objIters.size() - 1); + m_parentStack.remove(m_parentStack.size() - 1); + m_currentToken = JsonToken.END_OBJECT; + } else { + m_arrIters.remove(m_arrIters.size() - 1); + m_parentStack.remove(m_parentStack.size() - 1); + m_currentToken = JsonToken.END_ARRAY; + } + } + + @Override + String currentString() throws Exception { + if (m_currentToken == JsonToken.FIELD_NAME) { + return m_objIters.get(m_objIters.size() - 1).getCurrentKey(); + } + + if (m_currentToken != JsonToken.VALUE_STRING) { + throw new GeometryException("invalid call"); + } + + return ((String) currentObject_()).toString(); + } + + @Override + double currentDoubleValue() throws Exception { + if (m_currentToken != JsonToken.VALUE_NUMBER_FLOAT && m_currentToken != JsonToken.VALUE_NUMBER_INT) { + throw new GeometryException("invalid call"); + } + + return ((Number) currentObject_()).doubleValue(); + } + + @Override + int currentIntValue() throws Exception { + if (m_currentToken != JsonToken.VALUE_NUMBER_INT) { + throw new GeometryException("invalid call"); + } + + return ((Number) currentObject_()).intValue(); + } +} + +final class JSONObjectEnumerator { + + private JSONObject m_jsonObject; + private boolean m_bStarted; + private int m_currentIndex; + private String[] m_keys; + + JSONObjectEnumerator(JSONObject jsonObject) { + m_bStarted = false; + m_currentIndex = -1; + m_jsonObject = jsonObject; + } + + String getCurrentKey() { + if (!m_bStarted) { + throw new GeometryException("invalid call"); + } + + if (m_currentIndex == m_jsonObject.length()) { + throw new GeometryException("invalid call"); + } + + return m_keys[m_currentIndex]; + } + + Object getCurrentObject() { + if (!m_bStarted) { + throw new GeometryException("invalid call"); + } + + if (m_currentIndex == m_jsonObject.length()) { + throw new GeometryException("invalid call"); + } + + return m_jsonObject.get(m_keys[m_currentIndex]); + } + + boolean next() { + if (!m_bStarted) { + m_currentIndex = 0; + m_keys = JSONObject.getNames(m_jsonObject); + m_bStarted = true; + } else if (m_currentIndex != m_jsonObject.length()) { + m_currentIndex++; + } + + return m_currentIndex != m_jsonObject.length(); + } +} + +final class JSONArrayEnumerator { + + private JSONArray m_jsonArray; + private boolean m_bStarted; + private int m_currentIndex; + + JSONArrayEnumerator(JSONArray jsonArray) { + m_bStarted = false; + m_currentIndex = -1; + m_jsonArray = jsonArray; + } + + Object getCurrentObject() { + if (!m_bStarted) { + throw new GeometryException("invalid call"); + } + + if (m_currentIndex == m_jsonArray.length()) { + throw new GeometryException("invalid call"); + } + + return m_jsonArray.get(m_currentIndex); + } + + boolean next() { + if (!m_bStarted) { + m_currentIndex = 0; + m_bStarted = true; + } else if (m_currentIndex != m_jsonArray.length()) { + m_currentIndex++; + } + + return m_currentIndex != m_jsonArray.length(); + } +} diff --git a/src/com/esri/core/geometry/JsonWriter.java b/src/com/esri/core/geometry/JsonWriter.java new file mode 100644 index 00000000..710a34d6 --- /dev/null +++ b/src/com/esri/core/geometry/JsonWriter.java @@ -0,0 +1,462 @@ +/* + Copyright 1995-2013 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ +package com.esri.core.geometry; + +abstract class JsonWriter { + + abstract Object getJson(); + + abstract void startObject(); + + abstract void startArray(); + + abstract void endObject(); + + abstract void endArray(); + + abstract void addPairObject(String fieldName); + + abstract void addPairArray(String fieldName); + + abstract void addPairString(String fieldName, String v); + + abstract void addPairDouble(String fieldName, double v); + + abstract void addPairDoubleF(String fieldName, double v, int decimals); + + abstract void addPairInt(String fieldName, int v); + + abstract void addPairBoolean(String fieldName, boolean v); + + abstract void addPairNull(String fieldName); + + abstract void addValueObject(); + + abstract void addValueArray(); + + abstract void addValueString(String v); + + abstract void addValueDouble(double v); + + abstract void addValueDoubleF(double v, int decimals); + + abstract void addValueInt(int v); + + abstract void addValueBoolean(boolean v); + + abstract void addValueNull(); + + protected interface Action { + + static final int accept = 0; + static final int addContainer = 1; + static final int popObject = 4; + static final int popArray = 8; + static final int addPair = 16; + static final int addValue = 32; + } + + protected interface State { + + static final int accept = 0; + static final int start = 1; + static final int objectStart = 2; + static final int arrayStart = 3; + static final int pairEnd = 4; + static final int elementEnd = 6; + } +} + +final class JsonStringWriter extends JsonWriter { + + @Override + Object getJson() { + next_(Action.accept); + return m_jsonString.toString(); + } + + @Override + void startObject() { + next_(Action.addContainer); + m_jsonString.append('{'); + m_functionStack.add(State.objectStart); + } + + @Override + void startArray() { + next_(Action.addContainer); + m_jsonString.append('['); + m_functionStack.add(State.arrayStart); + } + + @Override + void endObject() { + next_(Action.popObject); + m_jsonString.append('}'); + } + + @Override + void endArray() { + next_(Action.popArray); + m_jsonString.append(']'); + } + + @Override + void addPairObject(String fieldName) { + next_(Action.addPair); + appendQuote_(fieldName); + m_jsonString.append(":"); + addValueObject_(); + } + + @Override + void addPairArray(String fieldName) { + next_(Action.addPair); + appendQuote_(fieldName); + m_jsonString.append(":"); + addValueArray_(); + } + + @Override + void addPairString(String fieldName, String v) { + next_(Action.addPair); + appendQuote_(fieldName); + m_jsonString.append(":"); + addValueString_(v); + } + + @Override + void addPairDouble(String fieldName, double v) { + next_(Action.addPair); + appendQuote_(fieldName); + m_jsonString.append(":"); + addValueDouble_(v); + } + + @Override + void addPairDoubleF(String fieldName, double v, int decimals) { + next_(Action.addPair); + appendQuote_(fieldName); + m_jsonString.append(":"); + addValueDoubleF_(v, decimals); + } + + @Override + void addPairInt(String fieldName, int v) { + next_(Action.addPair); + appendQuote_(fieldName); + m_jsonString.append(":"); + addValueInt_(v); + } + + @Override + void addPairBoolean(String fieldName, boolean v) { + next_(Action.addPair); + appendQuote_(fieldName); + m_jsonString.append(":"); + addValueBoolean_(v); + } + + @Override + void addPairNull(String fieldName) { + next_(Action.addPair); + appendQuote_(fieldName); + m_jsonString.append(":"); + addValueNull_(); + } + + @Override + void addValueObject() { + next_(Action.addValue); + addValueObject_(); + } + + @Override + void addValueArray() { + next_(Action.addValue); + addValueArray_(); + } + + @Override + void addValueString(String v) { + next_(Action.addValue); + addValueString_(v); + } + + @Override + void addValueDouble(double v) { + next_(Action.addValue); + addValueDouble_(v); + } + + @Override + void addValueDoubleF(double v, int decimals) { + next_(Action.addValue); + addValueDoubleF_(v, decimals); + } + + @Override + void addValueInt(int v) { + next_(Action.addValue); + addValueInt_(v); + } + + @Override + void addValueBoolean(boolean v) { + next_(Action.addValue); + addValueBoolean_(v); + } + + @Override + void addValueNull() { + next_(Action.addValue); + addValueNull_(); + } + + JsonStringWriter() { + m_jsonString = new StringBuilder(); + m_functionStack = new AttributeStreamOfInt32(0); + m_functionStack.add(State.accept); + m_functionStack.add(State.start); + } + private StringBuilder m_jsonString; + private AttributeStreamOfInt32 m_functionStack; + + private void addValueObject_() { + m_jsonString.append('{'); + m_functionStack.add(State.objectStart); + } + + private void addValueArray_() { + m_jsonString.append('['); + m_functionStack.add(State.arrayStart); + } + + private void addValueString_(String v) { + appendQuote_(v); + } + + private void addValueDouble_(double v) { + if (NumberUtils.isNaN(v)) { + addValueNull_(); + return; + } + + StringUtils.appendDouble(v, 17, m_jsonString); + } + + private void addValueDoubleF_(double v, int decimals) { + if (NumberUtils.isNaN(v)) { + addValueNull_(); + return; + } + + StringUtils.appendDoubleF(v, decimals, m_jsonString); + } + + private void addValueInt_(int v) { + m_jsonString.append(v); + } + + private void addValueBoolean_(boolean v) { + if (v) { + m_jsonString.append("true"); + } else { + m_jsonString.append("false"); + } + } + + private void addValueNull_() { + m_jsonString.append("null"); + } + + private void next_(int action) { + switch (m_functionStack.getLast()) { + case State.accept: + accept_(action); + break; + case State.start: + start_(action); + break; + case State.objectStart: + objectStart_(action); + break; + case State.arrayStart: + arrayStart_(action); + break; + case State.pairEnd: + pairEnd_(action); + break; + case State.elementEnd: + elementEnd_(action); + break; + default: + throw new GeometryException("internal error"); + } + } + + private void accept_(int action) { + if (action != Action.accept) { + throw new GeometryException("invalid call"); + } + } + + private void start_(int action) { + if (action == Action.addContainer) { + m_functionStack.removeLast(); + } else { + throw new GeometryException("invalid call"); + } + } + + private void objectStart_(int action) { + m_functionStack.removeLast(); + + if (action == Action.addPair) { + m_functionStack.add(State.pairEnd); + } else if (action != Action.popObject) { + throw new GeometryException("invalid call"); + } + } + + private void pairEnd_(int action) { + if (action == Action.addPair) { + m_jsonString.append(','); + } else if (action == Action.popObject) { + m_functionStack.removeLast(); + } else { + throw new GeometryException("invalid call"); + } + } + + private void arrayStart_(int action) { + m_functionStack.removeLast(); + + if (action == Action.addValue) { + m_functionStack.add(State.elementEnd); + } else if (action != Action.popArray) { + throw new GeometryException("invalid call"); + } + } + + private void elementEnd_(int action) { + if (action == Action.addValue) { + m_jsonString.append(','); + } else if (action == Action.popArray) { + m_functionStack.removeLast(); + } else { + throw new GeometryException("invalid call"); + } + } + + private void appendQuote_(String string) { + int count = 0; + int start = 0; + int end = string.length(); + + m_jsonString.append('"'); + + for (int i = 0; i < end; i++) { + switch (string.charAt(i)) { + case '"': + if (count > 0) { + m_jsonString.append(string, start, start + count); + count = 0; + } + m_jsonString.append("\\\""); + start = i + 1; + break; + case '\\': + if (count > 0) { + m_jsonString.append(string, start, start + count); + count = 0; + } + m_jsonString.append("\\\\"); + start = i + 1; + break; + case '/': + if (i > 0 && string.charAt(i - 1) == '<') { + if (count > 0) { + m_jsonString.append(string, start, start + count); + count = 0; + } + m_jsonString.append("\\/"); + start = i + 1; + } else { + count++; + } + break; + case '\b': + if (count > 0) { + m_jsonString.append(string, start, start + count); + count = 0; + } + m_jsonString.append("\\b"); + start = i + 1; + break; + case '\f': + if (count > 0) { + m_jsonString.append(string, start, start + count); + count = 0; + } + m_jsonString.append("\\f"); + start = i + 1; + break; + case '\n': + if (count > 0) { + m_jsonString.append(string, start, start + count); + count = 0; + } + m_jsonString.append("\\n"); + start = i + 1; + break; + case '\r': + if (count > 0) { + m_jsonString.append(string, start, start + count); + count = 0; + } + m_jsonString.append("\\r"); + start = i + 1; + break; + case '\t': + if (count > 0) { + m_jsonString.append(string, start, start + count); + count = 0; + } + m_jsonString.append("\\t"); + start = i + 1; + break; + default: + count++; + break; + } + } + + if (count > 0) { + m_jsonString.append(string, start, start + count); + } + + m_jsonString.append('"'); + } +} diff --git a/src/com/esri/core/geometry/OperatorExportToJson.java b/src/com/esri/core/geometry/OperatorExportToJson.java index 8bf1240e..60729155 100644 --- a/src/com/esri/core/geometry/OperatorExportToJson.java +++ b/src/com/esri/core/geometry/OperatorExportToJson.java @@ -23,35 +23,46 @@ */ package com.esri.core.geometry; +import java.util.Map; + import com.esri.core.geometry.Operator.Type; /** - *Export to JSON format. + * Export to JSON format. */ public abstract class OperatorExportToJson extends Operator { - @Override - public Type getType() { - return Type.ExportToJson; - } - - /** - * Performs the ExportToJson operation - * - * @return Returns a JsonCursor. - */ - abstract JsonCursor execute(SpatialReference spatialReference, - GeometryCursor geometryCursor); - - /** - *Performs the ExportToJson operation - *@return Returns a String. - */ - public abstract String execute(SpatialReference spatialReference, - Geometry geometry); - - public static OperatorExportToJson local() { - return (OperatorExportToJson) OperatorFactoryLocal.getInstance() - .getOperator(Type.ExportToJson); - } + @Override + public Type getType() { + return Type.ExportToJson; + } + + /** + * Performs the ExportToJson operation + * + * @return Returns a JsonCursor. + */ + abstract JsonCursor execute(SpatialReference spatialReference, + GeometryCursor geometryCursor); + + /** + * Performs the ExportToJson operation + * + * @return Returns a String. + */ + public abstract String execute(SpatialReference spatialReference, + Geometry geometry); + + /** + * Performs the ExportToJson operation + * + * @return Returns a String. + */ + public abstract String execute(SpatialReference spatialReference, + Geometry geometry, Map exportProperties); + + public static OperatorExportToJson local() { + return (OperatorExportToJson) OperatorFactoryLocal.getInstance() + .getOperator(Type.ExportToJson); + } } diff --git a/src/com/esri/core/geometry/OperatorExportToJsonCursor.java b/src/com/esri/core/geometry/OperatorExportToJsonCursor.java index 6de353f1..85547549 100644 --- a/src/com/esri/core/geometry/OperatorExportToJsonCursor.java +++ b/src/com/esri/core/geometry/OperatorExportToJsonCursor.java @@ -25,408 +25,441 @@ import com.esri.core.geometry.VertexDescription.Semantics; import java.io.IOException; -import java.io.StringWriter; -import org.codehaus.jackson.JsonFactory; -import org.codehaus.jackson.JsonGenerationException; -import org.codehaus.jackson.JsonGenerator; +import java.util.Map; class OperatorExportToJsonCursor extends JsonCursor { - GeometryCursor m_inputGeometryCursor; - int m_index; - int m_wkid = -1; - int m_latest_wkid = -1; - String m_wkt = null; - - private static JsonFactory factory = new JsonFactory(); - - public OperatorExportToJsonCursor(SpatialReference spatialReference, - GeometryCursor geometryCursor) { - m_index = -1; - if (geometryCursor == null) - throw new IllegalArgumentException(); - if (spatialReference != null && !spatialReference.isLocal()) { - m_wkid = spatialReference.getOldID(); - m_wkt = spatialReference.getText(); - m_latest_wkid = spatialReference.getLatestID(); - } - m_inputGeometryCursor = geometryCursor; - } - - @Override - public int getID() { - return m_index; - } - - @Override - public String next() { - Geometry geometry; - if ((geometry = m_inputGeometryCursor.next()) != null) { - m_index = m_inputGeometryCursor.getGeometryID(); - return exportToJson(geometry); - } - return null; - } - - private String exportToJson(Geometry geometry) { - StringWriter sw = new StringWriter(); - try { - JsonGenerator gen = factory.createJsonGenerator(sw); - int type = geometry.getType().value(); - switch (type) { - case Geometry.GeometryType.Point: - exportPointToJson(gen, (Point) geometry); - break; - - case Geometry.GeometryType.MultiPoint: - exportMultiPointToJson(gen, (MultiPoint) geometry); - break; - - case Geometry.GeometryType.Polyline: - exportPolylineToJson(gen, (Polyline) geometry); - break; - - case Geometry.GeometryType.Polygon: - exportPolygonToJson(gen, (Polygon) geometry); - break; - - case Geometry.GeometryType.Envelope: - exportEnvelopeToJson(gen, (Envelope) geometry); - break; - - default: - throw new RuntimeException( - "not implemented for this geometry type"); - } - - return sw.getBuffer().toString(); - } catch (IOException e) { - e.printStackTrace(); - return null; - } - - } - - private void exportPolygonToJson(JsonGenerator g, Polygon pp) - throws JsonGenerationException, IOException { - exportPolypathToJson(g, pp, "rings"); - } - - private void exportPolylineToJson(JsonGenerator g, Polyline pp) - throws JsonGenerationException, IOException { - exportPolypathToJson(g, pp, "paths"); - } - - private void exportPolypathToJson(JsonGenerator g, MultiPath pp, String name) - throws JsonGenerationException, IOException { - boolean bExportZs = pp.hasAttribute(Semantics.Z); - boolean bExportMs = pp.hasAttribute(Semantics.M); - - g.writeStartObject(); - - if (bExportZs) { - g.writeFieldName("hasZ"); - g.writeBoolean(true); - } - - if (bExportMs) { - g.writeFieldName("hasM"); - g.writeBoolean(true); - } - - g.writeFieldName(name); - - g.writeStartArray(); - - if (!pp.isEmpty()) { - int n = pp.getPathCount(); // rings or paths - - MultiPathImpl mpImpl = (MultiPathImpl) pp._getImpl();// get impl for - // faster - // access - AttributeStreamOfDbl zs = null; - AttributeStreamOfDbl ms = null; - - if (bExportZs) - zs = (AttributeStreamOfDbl) mpImpl - .getAttributeStreamRef(Semantics.Z); - - if (bExportMs) - ms = (AttributeStreamOfDbl) mpImpl - .getAttributeStreamRef(Semantics.M); - - boolean bPolygon = pp instanceof Polygon; - Point2D pt = new Point2D(); - - for (int i = 0; i < n; i++) { - g.writeStartArray(); - int startindex = pp.getPathStart(i); - int numVertices = pp.getPathSize(i); - for (int j = startindex; j < startindex + numVertices; j++) { - pp.getXY(j, pt); - - g.writeStartArray(); - - writeDouble(pt.x, g); - writeDouble(pt.y, g); - - if (bExportZs) { - double z = zs.get(j); - writeDouble(z, g); - } - - if (bExportMs) { - double m = ms.get(j); - writeDouble(m, g); - } - - g.writeEndArray(); - } - - // Close the Path/Ring by writing the Point at the start index - if (bPolygon) { - pp.getXY(startindex, pt); - // getPoint(startindex); - g.writeStartArray(); - - g.writeNumber(pt.x); - g.writeNumber(pt.y); - - if (bExportZs) { - double z = zs.get(startindex); - writeDouble(z, g); - } - - if (bExportMs) { - double m = ms.get(startindex); - writeDouble(m, g); - } - - g.writeEndArray(); - } - - g.writeEndArray(); - } - } - - g.writeEndArray(); - - writeSR(g); - - g.writeEndObject(); - g.close(); - } - - private void exportMultiPointToJson(JsonGenerator g, MultiPoint mpt) - throws JsonGenerationException, IOException { - boolean bExportZs = mpt.hasAttribute(Semantics.Z); - boolean bExportMs = mpt.hasAttribute(Semantics.M); - - g.writeStartObject(); - - if (bExportZs) { - g.writeFieldName("hasZ"); - g.writeBoolean(true); - } - - if (bExportMs) { - g.writeFieldName("hasM"); - g.writeBoolean(true); - } - - g.writeFieldName("points"); - - g.writeStartArray(); - - if (!mpt.isEmpty()) { - MultiPointImpl mpImpl = (MultiPointImpl) mpt._getImpl();// get impl - // for - // faster - // access - AttributeStreamOfDbl zs = null; - AttributeStreamOfDbl ms = null; - - if (bExportZs) - zs = (AttributeStreamOfDbl) mpImpl - .getAttributeStreamRef(Semantics.Z); - - if (bExportMs) - ms = (AttributeStreamOfDbl) mpImpl - .getAttributeStreamRef(Semantics.M); - - Point2D pt = new Point2D(); - int n = mpt.getPointCount(); - for (int i = 0; i < n; i++) { - mpt.getXY(i, pt); - - g.writeStartArray(); - - writeDouble(pt.x, g); - writeDouble(pt.y, g); - - if (bExportZs) { - double z = zs.get(i); - writeDouble(z, g); - } - - if (bExportMs) { - double m = ms.get(i); - writeDouble(m, g); - } - - g.writeEndArray(); - } - } - - g.writeEndArray(); - - writeSR(g); - - g.writeEndObject(); - g.close(); - } - - private void exportPointToJson(JsonGenerator g, Point pt) - throws JsonGenerationException, IOException { - boolean bExportZs = pt.hasAttribute(Semantics.Z); - boolean bExportMs = pt.hasAttribute(Semantics.M); - - g.writeStartObject(); - - if (pt.isEmpty()) { - g.writeFieldName("x"); - g.writeNull(); - g.writeFieldName("y"); - g.writeNull(); - - if (bExportZs) { - g.writeFieldName("z"); - g.writeNull(); - } - - if (bExportMs) { - g.writeFieldName("m"); - g.writeNull(); - } - } else { - g.writeFieldName("x"); - writeDouble(pt.getX(), g); - g.writeFieldName("y"); - writeDouble(pt.getY(), g); - - if (bExportZs) { - g.writeFieldName("z"); - writeDouble(pt.getZ(), g); - } - - if (bExportMs) { - g.writeFieldName("m"); - writeDouble(pt.getM(), g); - } - } - - writeSR(g); - - g.writeEndObject(); - g.close(); - } - - private void exportEnvelopeToJson(JsonGenerator g, Envelope env) - throws JsonGenerationException, IOException { - boolean bExportZs = env.hasAttribute(Semantics.Z); - boolean bExportMs = env.hasAttribute(Semantics.M); - - g.writeStartObject(); - - if (env.isEmpty()) { - g.writeFieldName("xmin"); - g.writeNull(); - g.writeFieldName("ymin"); - g.writeNull(); - g.writeFieldName("xmax"); - g.writeNull(); - g.writeFieldName("ymax"); - g.writeNull(); - - if (bExportZs) { - g.writeFieldName("zmin"); - g.writeNull(); - g.writeFieldName("zmax"); - g.writeNull(); - } - - if (bExportMs) { - g.writeFieldName("mmin"); - g.writeNull(); - g.writeFieldName("mmax"); - g.writeNull(); - } - } else { - g.writeFieldName("xmin"); - writeDouble(env.getXMin(), g); - g.writeFieldName("ymin"); - writeDouble(env.getYMin(), g); - g.writeFieldName("xmax"); - writeDouble(env.getXMax(), g); - g.writeFieldName("ymax"); - writeDouble(env.getYMax(), g); - - if (bExportZs) { - Envelope1D z = env.queryInterval(Semantics.Z, 0); - g.writeFieldName("zmin"); - writeDouble(z.vmin, g); - g.writeFieldName("zmax"); - writeDouble(z.vmax, g); - } - - if (bExportMs) { - Envelope1D m = env.queryInterval(Semantics.M, 0); - g.writeFieldName("mmin"); - writeDouble(m.vmin, g); - g.writeFieldName("mmax"); - writeDouble(m.vmax, g); - } - } - - writeSR(g); - - g.writeEndObject(); - g.close(); - } - - private void writeDouble(double d, JsonGenerator g) throws IOException, - JsonGenerationException { - if (NumberUtils.isNaN(d)) { - g.writeNull(); - } else { - g.writeNumber(d); - } - - return; - } - - private void writeSR(JsonGenerator g) throws IOException, - JsonGenerationException { - if (m_wkid > 0) { - g.writeFieldName("spatialReference"); - g.writeStartObject(); - - g.writeFieldName("wkid"); - g.writeNumber(m_wkid); - - if (m_latest_wkid > 0 && m_latest_wkid != m_wkid) { - g.writeFieldName("latestWkid"); - g.writeNumber(m_latest_wkid); - } - - g.writeEndObject(); - } else if (m_wkt != null) { - g.writeFieldName("spatialReference"); - g.writeStartObject(); - g.writeFieldName("wkt"); - g.writeString(m_wkt); - g.writeEndObject(); - } else - return; - } + GeometryCursor m_inputGeometryCursor; + SpatialReference m_spatialReference; + int m_index; + + public OperatorExportToJsonCursor(SpatialReference spatialReference, + GeometryCursor geometryCursor) { + m_index = -1; + if (geometryCursor == null) { + throw new IllegalArgumentException(); + } + + m_inputGeometryCursor = geometryCursor; + m_spatialReference = spatialReference; + } + + @Override + public int getID() { + return m_index; + } + + @Override + public String next() { + Geometry geometry; + if ((geometry = m_inputGeometryCursor.next()) != null) { + m_index = m_inputGeometryCursor.getGeometryID(); + return exportToString(geometry, m_spatialReference, null); + } + return null; + } + + static String exportToString(Geometry geometry, SpatialReference spatialReference, Map exportProperties) { + JsonWriter jsonWriter = new JsonStringWriter(); + exportToJson_(geometry, spatialReference, jsonWriter, exportProperties); + return (String) jsonWriter.getJson(); + } + + private static void exportToJson_(Geometry geometry, SpatialReference spatialReference, JsonWriter jsonWriter, Map exportProperties) { + try { + int type = geometry.getType().value(); + switch (type) { + case Geometry.GeometryType.Point: + exportPointToJson((Point) geometry, spatialReference, jsonWriter, exportProperties); + break; + + case Geometry.GeometryType.MultiPoint: + exportMultiPointToJson((MultiPoint) geometry, spatialReference, jsonWriter, exportProperties); + break; + + case Geometry.GeometryType.Polyline: + exportPolylineToJson((Polyline) geometry, spatialReference, jsonWriter, exportProperties); + break; + + case Geometry.GeometryType.Polygon: + exportPolygonToJson((Polygon) geometry, spatialReference, jsonWriter, exportProperties); + break; + + case Geometry.GeometryType.Envelope: + exportEnvelopeToJson((Envelope) geometry, spatialReference, jsonWriter, exportProperties); + break; + + default: + throw new RuntimeException( + "not implemented for this geometry type"); + } + + } catch (Exception e) { + e.printStackTrace(); + } + + } + + private static void exportPolygonToJson(Polygon pp, SpatialReference spatialReference, JsonWriter jsonWriter, Map exportProperties) { + exportPolypathToJson(pp, "rings", spatialReference, jsonWriter, exportProperties); + } + + private static void exportPolylineToJson(Polyline pp, SpatialReference spatialReference, JsonWriter jsonWriter, Map exportProperties) { + exportPolypathToJson(pp, "paths", spatialReference, jsonWriter, exportProperties); + } + + private static void exportPolypathToJson(MultiPath pp, String name, SpatialReference spatialReference, JsonWriter jsonWriter, Map exportProperties) { + boolean bExportZs = pp.hasAttribute(Semantics.Z); + boolean bExportMs = pp.hasAttribute(Semantics.M); + + boolean bPositionAsF = false; + int decimals = 17; + + if (exportProperties != null) { + Object numberOfDecimalsXY = exportProperties.get("numberOfDecimalsXY"); + if (numberOfDecimalsXY != null && numberOfDecimalsXY instanceof Number) { + bPositionAsF = true; + decimals = ((Number) numberOfDecimalsXY).intValue(); + } + } + + jsonWriter.startObject(); + + if (bExportZs) { + jsonWriter.addPairBoolean("hasZ", true); + } + + if (bExportMs) { + jsonWriter.addPairBoolean("hasM", true); + } + + jsonWriter.addPairArray(name); + + if (!pp.isEmpty()) { + int n = pp.getPathCount(); // rings or paths + + MultiPathImpl mpImpl = (MultiPathImpl) pp._getImpl();// get impl for + // faster + // access + AttributeStreamOfDbl zs = null; + AttributeStreamOfDbl ms = null; + + if (bExportZs) { + zs = (AttributeStreamOfDbl) mpImpl + .getAttributeStreamRef(Semantics.Z); + } + + if (bExportMs) { + ms = (AttributeStreamOfDbl) mpImpl + .getAttributeStreamRef(Semantics.M); + } + + boolean bPolygon = pp instanceof Polygon; + Point2D pt = new Point2D(); + + for (int i = 0; i < n; i++) { + jsonWriter.addValueArray(); + int startindex = pp.getPathStart(i); + int numVertices = pp.getPathSize(i); + double startx = 0.0, starty = 0.0, startz = NumberUtils.NaN(), startm = NumberUtils.NaN(); + double z = NumberUtils.NaN(), m = NumberUtils.NaN(); + boolean bClosed = pp.isClosedPath(i); + for (int j = startindex; j < startindex + numVertices; j++) { + pp.getXY(j, pt); + + jsonWriter.addValueArray(); + + if (bPositionAsF) { + jsonWriter.addValueDoubleF(pt.x, decimals); + jsonWriter.addValueDoubleF(pt.y, decimals); + } else { + jsonWriter.addValueDouble(pt.x); + jsonWriter.addValueDouble(pt.y); + } + + if (bExportZs) { + z = zs.get(j); + jsonWriter.addValueDouble(z); + } + + if (bExportMs) { + m = ms.get(j); + jsonWriter.addValueDouble(m); + } + + if (j == startindex && bClosed) { + startx = pt.x; + starty = pt.y; + startz = z; + startm = m; + } + + jsonWriter.endArray(); + } + + // Close the Path/Ring by writing the Point at the start index + if (bClosed && (startx != pt.x || starty != pt.y || (bExportZs && !(NumberUtils.isNaN(startz) && NumberUtils.isNaN(z)) && startz != z) || (bExportMs && !(NumberUtils.isNaN(startm) && NumberUtils.isNaN(m)) && startm != m))) { + pp.getXY(startindex, pt); + // getPoint(startindex); + jsonWriter.addValueArray(); + + if (bPositionAsF) { + jsonWriter.addValueDoubleF(pt.x, decimals); + jsonWriter.addValueDoubleF(pt.y, decimals); + } else { + jsonWriter.addValueDouble(pt.x); + jsonWriter.addValueDouble(pt.y); + } + + if (bExportZs) { + z = zs.get(startindex); + jsonWriter.addValueDouble(z); + } + + if (bExportMs) { + m = ms.get(startindex); + jsonWriter.addValueDouble(m); + } + + jsonWriter.endArray(); + } + + jsonWriter.endArray(); + } + } + + jsonWriter.endArray(); + + if (spatialReference != null) { + writeSR(spatialReference, jsonWriter); + } + + jsonWriter.endObject(); + } + + private static void exportMultiPointToJson(MultiPoint mpt, SpatialReference spatialReference, JsonWriter jsonWriter, Map exportProperties) { + boolean bExportZs = mpt.hasAttribute(Semantics.Z); + boolean bExportMs = mpt.hasAttribute(Semantics.M); + + boolean bPositionAsF = false; + int decimals = 17; + + if (exportProperties != null) { + Object numberOfDecimalsXY = exportProperties.get("numberOfDecimalsXY"); + if (numberOfDecimalsXY != null && numberOfDecimalsXY instanceof Number) { + bPositionAsF = true; + decimals = ((Number) numberOfDecimalsXY).intValue(); + } + } + + jsonWriter.startObject(); + + if (bExportZs) { + jsonWriter.addPairBoolean("hasZ", true); + } + + if (bExportMs) { + jsonWriter.addPairBoolean("hasM", true); + } + + jsonWriter.addPairArray("points"); + + if (!mpt.isEmpty()) { + MultiPointImpl mpImpl = (MultiPointImpl) mpt._getImpl();// get impl + // for + // faster + // access + AttributeStreamOfDbl zs = null; + AttributeStreamOfDbl ms = null; + + if (bExportZs) { + zs = (AttributeStreamOfDbl) mpImpl + .getAttributeStreamRef(Semantics.Z); + } + + if (bExportMs) { + ms = (AttributeStreamOfDbl) mpImpl + .getAttributeStreamRef(Semantics.M); + } + + Point2D pt = new Point2D(); + int n = mpt.getPointCount(); + for (int i = 0; i < n; i++) { + mpt.getXY(i, pt); + + jsonWriter.addValueArray(); + + if (bPositionAsF) { + jsonWriter.addValueDoubleF(pt.x, decimals); + jsonWriter.addValueDoubleF(pt.y, decimals); + } else { + jsonWriter.addValueDouble(pt.x); + jsonWriter.addValueDouble(pt.y); + } + + if (bExportZs) { + double z = zs.get(i); + jsonWriter.addValueDouble(z); + } + + if (bExportMs) { + double m = ms.get(i); + jsonWriter.addValueDouble(m); + } + + jsonWriter.endArray(); + } + } + + jsonWriter.endArray(); + + if (spatialReference != null) { + writeSR(spatialReference, jsonWriter); + } + + jsonWriter.endObject(); + } + + private static void exportPointToJson(Point pt, SpatialReference spatialReference, JsonWriter jsonWriter, Map exportProperties) { + boolean bExportZs = pt.hasAttribute(Semantics.Z); + boolean bExportMs = pt.hasAttribute(Semantics.M); + + boolean bPositionAsF = false; + int decimals = 17; + + if (exportProperties != null) { + Object numberOfDecimalsXY = exportProperties.get("numberOfDecimalsXY"); + if (numberOfDecimalsXY != null && numberOfDecimalsXY instanceof Number) { + bPositionAsF = true; + decimals = ((Number) numberOfDecimalsXY).intValue(); + } + } + + jsonWriter.startObject(); + + if (pt.isEmpty()) { + jsonWriter.addPairNull("x"); + jsonWriter.addPairNull("y"); + + if (bExportZs) { + jsonWriter.addPairNull("z"); + } + + if (bExportMs) { + jsonWriter.addPairNull("m"); + } + } else { + + if (bPositionAsF) { + jsonWriter.addPairDoubleF("x", pt.getX(), decimals); + jsonWriter.addPairDoubleF("y", pt.getY(), decimals); + } else { + jsonWriter.addPairDouble("x", pt.getX()); + jsonWriter.addPairDouble("y", pt.getY()); + } + + if (bExportZs) { + jsonWriter.addPairDouble("z", pt.getZ()); + } + + if (bExportMs) { + jsonWriter.addPairDouble("m", pt.getM()); + } + } + + if (spatialReference != null) { + writeSR(spatialReference, jsonWriter); + } + + jsonWriter.endObject(); + } + + private static void exportEnvelopeToJson(Envelope env, SpatialReference spatialReference, JsonWriter jsonWriter, Map exportProperties) { + boolean bExportZs = env.hasAttribute(Semantics.Z); + boolean bExportMs = env.hasAttribute(Semantics.M); + + boolean bPositionAsF = false; + int decimals = 17; + + if (exportProperties != null) { + Object numberOfDecimalsXY = exportProperties.get("numberOfDecimalsXY"); + if (numberOfDecimalsXY != null && numberOfDecimalsXY instanceof Number) { + bPositionAsF = true; + decimals = ((Number) numberOfDecimalsXY).intValue(); + } + } + + jsonWriter.startObject(); + + if (env.isEmpty()) { + jsonWriter.addPairNull("xmin"); + jsonWriter.addPairNull("ymin"); + jsonWriter.addPairNull("xmax"); + jsonWriter.addPairNull("ymax"); + + if (bExportZs) { + jsonWriter.addPairNull("zmin"); + jsonWriter.addPairNull("zmax"); + } + + if (bExportMs) { + jsonWriter.addPairNull("mmin"); + jsonWriter.addPairNull("mmax"); + } + } else { + + if (bPositionAsF) { + jsonWriter.addPairDoubleF("xmin", env.getXMin(), decimals); + jsonWriter.addPairDoubleF("ymin", env.getYMin(), decimals); + jsonWriter.addPairDoubleF("xmax", env.getXMax(), decimals); + jsonWriter.addPairDoubleF("ymax", env.getYMax(), decimals); + } else { + jsonWriter.addPairDouble("xmin", env.getXMin()); + jsonWriter.addPairDouble("ymin", env.getYMin()); + jsonWriter.addPairDouble("xmax", env.getXMax()); + jsonWriter.addPairDouble("ymax", env.getYMax()); + } + + if (bExportZs) { + Envelope1D z = env.queryInterval(Semantics.Z, 0); + jsonWriter.addPairDouble("zmin", z.vmin); + jsonWriter.addPairDouble("zmax", z.vmax); + } + + if (bExportMs) { + Envelope1D m = env.queryInterval(Semantics.M, 0); + jsonWriter.addPairDouble("mmin", m.vmin); + jsonWriter.addPairDouble("mmax", m.vmax); + } + } + + if (spatialReference != null) { + writeSR(spatialReference, jsonWriter); + } + + jsonWriter.endObject(); + } + + private static void writeSR(SpatialReference spatialReference, JsonWriter jsonWriter) { + int wkid = spatialReference.getOldID(); + if (wkid > 0) { + jsonWriter.addPairObject("spatialReference"); + + jsonWriter.addPairInt("wkid", wkid); + + int latest_wkid = spatialReference.getLatestID(); + if (latest_wkid > 0 && latest_wkid != wkid) { + jsonWriter.addPairInt("latestWkid", latest_wkid); + } + + jsonWriter.endObject(); + } else { + String wkt = spatialReference.getText(); + if (wkt != null) { + jsonWriter.addPairObject("spatialReference"); + jsonWriter.addPairString("wkt", wkt); + jsonWriter.endObject(); + } + } + } } diff --git a/src/com/esri/core/geometry/OperatorExportToJsonLocal.java b/src/com/esri/core/geometry/OperatorExportToJsonLocal.java index 32586c5f..8b93cb1b 100644 --- a/src/com/esri/core/geometry/OperatorExportToJsonLocal.java +++ b/src/com/esri/core/geometry/OperatorExportToJsonLocal.java @@ -23,19 +23,26 @@ */ package com.esri.core.geometry; -class OperatorExportToJsonLocal extends OperatorExportToJson { - - @Override - JsonCursor execute(SpatialReference spatialReference, - GeometryCursor geometryCursor) { - return new OperatorExportToJsonCursor(spatialReference, geometryCursor); - } +import java.util.Map; - @Override - public String execute(SpatialReference spatialReference, Geometry geometry) { - SimpleGeometryCursor gc = new SimpleGeometryCursor(geometry); - JsonCursor cursor = new OperatorExportToJsonCursor(spatialReference, gc); - return cursor.next(); - } +class OperatorExportToJsonLocal extends OperatorExportToJson { + @Override + JsonCursor execute(SpatialReference spatialReference, + GeometryCursor geometryCursor) { + return new OperatorExportToJsonCursor(spatialReference, geometryCursor); + } + + @Override + public String execute(SpatialReference spatialReference, Geometry geometry) { + SimpleGeometryCursor gc = new SimpleGeometryCursor(geometry); + JsonCursor cursor = new OperatorExportToJsonCursor(spatialReference, gc); + return cursor.next(); + } + + @Override + public String execute(SpatialReference spatialReference, + Geometry geometry, Map exportProperties) { + return OperatorExportToJsonCursor.exportToString(geometry, spatialReference, exportProperties); + } } diff --git a/src/com/esri/core/geometry/OperatorImportFromJson.java b/src/com/esri/core/geometry/OperatorImportFromJson.java index 66e59b9b..c05438eb 100644 --- a/src/com/esri/core/geometry/OperatorImportFromJson.java +++ b/src/com/esri/core/geometry/OperatorImportFromJson.java @@ -28,6 +28,8 @@ import org.codehaus.jackson.JsonParseException; import org.codehaus.jackson.JsonParser; +import org.json.JSONObject; +import org.json.JSONException; import com.esri.core.geometry.Operator.Type; @@ -62,6 +64,13 @@ public abstract MapGeometry execute(Geometry.Type type, public abstract MapGeometry execute(Geometry.Type type, String string) throws JsonParseException, IOException; + /** + *Performs the ImportFromJson operation on a JSONObject + *@return Returns a MapGeometry. + */ + public abstract MapGeometry execute(Geometry.Type type, JSONObject jsonObject) + throws JSONException, IOException; + public static OperatorImportFromJson local() { return (OperatorImportFromJson) OperatorFactoryLocal.getInstance() diff --git a/src/com/esri/core/geometry/OperatorImportFromJsonCursor.java b/src/com/esri/core/geometry/OperatorImportFromJsonCursor.java index ac982316..cc56169b 100644 --- a/src/com/esri/core/geometry/OperatorImportFromJsonCursor.java +++ b/src/com/esri/core/geometry/OperatorImportFromJsonCursor.java @@ -26,7 +26,6 @@ import com.esri.core.geometry.MultiVertexGeometryImpl.DirtyFlags; import com.esri.core.geometry.VertexDescription.Semantics; -import java.io.IOException; import org.codehaus.jackson.JsonParseException; import org.codehaus.jackson.JsonParser; import org.codehaus.jackson.JsonToken; @@ -57,12 +56,12 @@ public MapGeometry next() { JsonParser jsonParser; if ((jsonParser = m_inputJsonParsers.next()) != null) { m_index = m_inputJsonParsers.getID(); - return importFromJsonParser(m_type, jsonParser); + return importFromJsonParser(m_type, new JsonParserReader(jsonParser)); } return null; } - private static MapGeometry importFromJsonParser(int gt, JsonParser parser) { + static MapGeometry importFromJsonParser(int gt, JsonReader parser) { MapGeometry mp; try { @@ -110,25 +109,25 @@ private static MapGeometry importFromJsonParser(int gt, JsonParser parser) { SpatialReference spatial_reference = null; while (parser.nextToken() != JsonToken.END_OBJECT) { - String name = parser.getCurrentName(); + String name = parser.currentString(); parser.nextToken(); if (!bFoundSpatial_reference && name.equals("spatialReference")) { bFoundSpatial_reference = true; - if (parser.getCurrentToken() == JsonToken.START_OBJECT) { + if (parser.currentToken() == JsonToken.START_OBJECT) { spatial_reference = SpatialReference.fromJson(parser); } else { - if (parser.getCurrentToken() != JsonToken.VALUE_NULL) + if (parser.currentToken() != JsonToken.VALUE_NULL) throw new GeometryException( "failed to parse spatial reference: object or null is expected"); } } else if (!bFoundHasZ && name.equals("hasZ")) { bFoundHasZ = true; - bHasZ = (parser.getCurrentToken() == JsonToken.VALUE_TRUE); + bHasZ = (parser.currentToken() == JsonToken.VALUE_TRUE); } else if (!bFoundHasM && name.equals("hasM")) { bFoundHasM = true; - bHasM = (parser.getCurrentToken() == JsonToken.VALUE_TRUE); + bHasM = (parser.currentToken() == JsonToken.VALUE_TRUE); } else if (!bFoundPolygon && name.equals("rings") && (gt == Geometry.GeometryType.Unknown || gt == Geometry.GeometryType.Polygon)) { @@ -279,55 +278,55 @@ private static MapGeometry importFromJsonParser(int gt, JsonParser parser) { return mp; } - public static MapGeometry fromJsonToUnknown(JsonParser parser) + public static MapGeometry fromJsonToUnknown(JsonReader parser) throws Exception { return importFromJsonParser(Geometry.GeometryType.Unknown, parser); } - public static MapGeometry fromJsonToEnvelope(JsonParser parser) + public static MapGeometry fromJsonToEnvelope(JsonReader parser) throws Exception { return importFromJsonParser(Geometry.GeometryType.Envelope, parser); } - public static MapGeometry fromJsonToPoint(JsonParser parser) + public static MapGeometry fromJsonToPoint(JsonReader parser) throws Exception { return importFromJsonParser(Geometry.GeometryType.Point, parser); } - public static MapGeometry fromJsonToPolygon(JsonParser parser) + public static MapGeometry fromJsonToPolygon(JsonReader parser) throws Exception { return importFromJsonParser(Geometry.GeometryType.Polygon, parser); } - public static MapGeometry fromJsonToPolyline(JsonParser parser) + public static MapGeometry fromJsonToPolyline(JsonReader parser) throws Exception { return importFromJsonParser(Geometry.GeometryType.Polyline, parser); } - public static MapGeometry fromJsonToMultiPoint(JsonParser parser) + public static MapGeometry fromJsonToMultiPoint(JsonReader parser) throws Exception { return importFromJsonParser(Geometry.GeometryType.MultiPoint, parser); } - private static void windup(JsonParser parser) throws IOException, + private static void windup(JsonReader parser) throws Exception, JsonParseException { parser.skipChildren(); } - private static double readDouble(JsonParser parser) throws IOException, + private static double readDouble(JsonReader parser) throws Exception, JsonParseException { - if (parser.getCurrentToken() == JsonToken.VALUE_NULL - || parser.getCurrentToken() == JsonToken.VALUE_STRING - && parser.getCurrentName().equals("NaN")) + if (parser.currentToken() == JsonToken.VALUE_NULL + || parser.currentToken() == JsonToken.VALUE_STRING + && parser.currentString().equals("NaN")) return NumberUtils.NaN(); else - return parser.getValueAsDouble(); + return parser.currentDoubleValue(); } - private static Geometry importFromJsonMultiPoint(JsonParser parser, + private static Geometry importFromJsonMultiPoint(JsonReader parser, AttributeStreamOfDbl as, AttributeStreamOfDbl bs) throws Exception { - if (parser.getCurrentToken() != JsonToken.START_ARRAY) + if (parser.currentToken() != JsonToken.START_ARRAY) throw new GeometryException( "failed to parse multipoint: array of vertices is expected"); @@ -343,7 +342,7 @@ private static Geometry importFromJsonMultiPoint(JsonParser parser, int sz; double[] buf = new double[4]; while (parser.nextToken() != JsonToken.END_ARRAY) { - if (parser.getCurrentToken() != JsonToken.START_ARRAY) + if (parser.currentToken() != JsonToken.START_ARRAY) throw new GeometryException( "failed to parse multipoint: array is expected, multipoint vertices consist of arrays of cooridinates"); @@ -408,9 +407,9 @@ else if (c < 16) } private static Geometry importFromJsonMultiPath(boolean b_polygon, - JsonParser parser, AttributeStreamOfDbl as, AttributeStreamOfDbl bs) + JsonReader parser, AttributeStreamOfDbl as, AttributeStreamOfDbl bs) throws Exception { - if (parser.getCurrentToken() != JsonToken.START_ARRAY) + if (parser.currentToken() != JsonToken.START_ARRAY) throw new GeometryException( "failed to parse multipath: array of array of vertices is expected"); @@ -439,7 +438,7 @@ private static Geometry importFromJsonMultiPath(boolean b_polygon, // At start of rings while (parser.nextToken() != JsonToken.END_ARRAY) { - if (parser.getCurrentToken() != JsonToken.START_ARRAY) + if (parser.currentToken() != JsonToken.START_ARRAY) throw new GeometryException( "failed to parse multipath: ring/path array is expected"); @@ -449,8 +448,8 @@ private static Geometry importFromJsonMultiPath(boolean b_polygon, int szstart = 0; parser.nextToken(); - while (parser.getCurrentToken() != JsonToken.END_ARRAY) { - if (parser.getCurrentToken() != JsonToken.START_ARRAY) + while (parser.currentToken() != JsonToken.END_ARRAY) { + if (parser.currentToken() != JsonToken.START_ARRAY) throw new GeometryException( "failed to parse multipath: array is expected, rings/paths vertices consist of arrays of cooridinates"); @@ -524,7 +523,7 @@ else if (c < 16) point_count++; pathPointCount++; } while (pathPointCount < requiredSize - && parser.getCurrentToken() == JsonToken.END_ARRAY); + && parser.currentToken() == JsonToken.END_ARRAY); } if (b_polygon && pathPointCount > requiredSize && sz == szstart diff --git a/src/com/esri/core/geometry/OperatorImportFromJsonLocal.java b/src/com/esri/core/geometry/OperatorImportFromJsonLocal.java index da614dfb..f92842fb 100644 --- a/src/com/esri/core/geometry/OperatorImportFromJsonLocal.java +++ b/src/com/esri/core/geometry/OperatorImportFromJsonLocal.java @@ -28,6 +28,8 @@ import org.codehaus.jackson.JsonFactory; import org.codehaus.jackson.JsonParseException; import org.codehaus.jackson.JsonParser; +import org.json.JSONObject; +import org.json.JSONException; import com.esri.core.geometry.ogc.OGCGeometry; @@ -55,4 +57,12 @@ public MapGeometry execute(Geometry.Type type, String string) jsonParserPt.nextToken(); return execute(type, jsonParserPt); } + @Override + public MapGeometry execute(Geometry.Type type, JSONObject jsonObject) + throws JSONException, IOException { + if (jsonObject == null) + return null; + + return OperatorImportFromJsonCursor.importFromJsonParser(type.value(), new JsonValueReader(jsonObject)); + } } diff --git a/src/com/esri/core/geometry/QuadTreeImpl.java b/src/com/esri/core/geometry/QuadTreeImpl.java index e1035b6b..8a927447 100644 --- a/src/com/esri/core/geometry/QuadTreeImpl.java +++ b/src/com/esri/core/geometry/QuadTreeImpl.java @@ -44,7 +44,7 @@ void resetIterator(Geometry query, double tolerance) { if (m_query_box.isIntersecting(m_quad_tree.m_extent)) { int type = query.getType().value(); - m_b_linear = Geometry.isLinear(type); + m_b_linear = Geometry.isSegment(type); if (m_b_linear) { Segment segment = (Segment) query; diff --git a/src/com/esri/core/geometry/StringUtils.java b/src/com/esri/core/geometry/StringUtils.java index c9267b5c..6797d94e 100644 --- a/src/com/esri/core/geometry/StringUtils.java +++ b/src/com/esri/core/geometry/StringUtils.java @@ -24,45 +24,86 @@ package com.esri.core.geometry; class StringUtils { - static void appendDouble(double value, int precision, - StringBuilder stringBuilder) { - if (precision < 0) - precision = 0; - else if (precision > 17) - precision = 17; - - String format = "%." + precision + "g"; - - String str_dbl = String.format(format, value); - - boolean b_found_dot = false; - boolean b_found_exponent = false; - - for (int i = 0; i < str_dbl.length(); i++) { - char c = str_dbl.charAt(i); - - if (c == '.') - b_found_dot = true; - else if (c == 'e' || c == 'E') - b_found_exponent = true; - } - - if (b_found_dot && !b_found_exponent) { - StringBuilder buffer = new StringBuilder(str_dbl); - int non_zero = buffer.length() - 1; - - while (buffer.charAt(non_zero) == '0') - non_zero--; - - buffer.delete(non_zero + 1, buffer.length()); - - if (buffer.charAt(non_zero) == '.') - buffer.deleteCharAt(non_zero); - - stringBuilder.append(buffer); - } else { - stringBuilder.append(str_dbl); - } - } + static void appendDouble(double value, int precision, + StringBuilder stringBuilder) { + if (precision < 0) { + precision = 0; + } else if (precision > 17) { + precision = 17; + } + + String format = "%." + precision + "g"; + + String str_dbl = String.format(format, value); + + boolean b_found_dot = false; + boolean b_found_exponent = false; + + for (int i = 0; i < str_dbl.length(); i++) { + char c = str_dbl.charAt(i); + + if (c == '.') { + b_found_dot = true; + } else if (c == 'e' || c == 'E') { + b_found_exponent = true; + break; + } + } + + if (b_found_dot && !b_found_exponent) { + StringBuilder buffer = removeTrailingZeros_(str_dbl); + stringBuilder.append(buffer); + } else { + stringBuilder.append(str_dbl); + } + } + + static void appendDoubleF(double value, int decimals, + StringBuilder stringBuilder) { + if (decimals < 0) { + decimals = 0; + } else if (decimals > 17) { + decimals = 17; + } + + String format = "%." + decimals + "f"; + + String str_dbl = String.format(format, value); + + boolean b_found_dot = false; + + for (int i = 0; i < str_dbl.length(); i++) { + char c = str_dbl.charAt(i); + + if (c == '.') { + b_found_dot = true; + break; + } + } + + if (b_found_dot) { + StringBuilder buffer = removeTrailingZeros_(str_dbl); + stringBuilder.append(buffer); + } else { + stringBuilder.append(str_dbl); + } + } + + static private StringBuilder removeTrailingZeros_(String str_dbl) { + StringBuilder buffer = new StringBuilder(str_dbl); + int non_zero = buffer.length() - 1; + + while (buffer.charAt(non_zero) == '0') { + non_zero--; + } + + buffer.delete(non_zero + 1, buffer.length()); + + if (buffer.charAt(non_zero) == '.') { + buffer.deleteCharAt(non_zero); + } + + return buffer; + } } diff --git a/unittest/com/esri/core/geometry/TestGeomToJSonExportSRFromWkiOrWkt_CR181369.java b/unittest/com/esri/core/geometry/TestGeomToJSonExportSRFromWkiOrWkt_CR181369.java index 90277da9..8d32bbe2 100644 --- a/unittest/com/esri/core/geometry/TestGeomToJSonExportSRFromWkiOrWkt_CR181369.java +++ b/unittest/com/esri/core/geometry/TestGeomToJSonExportSRFromWkiOrWkt_CR181369.java @@ -76,12 +76,6 @@ boolean testPoint() throws JsonParseException, IOException { String pointEmptyString = GeometryEngine.geometryToJson( spatialReferenceWebMerc1, pointEmpty); pointWebMerc1Parser = factory.createJsonParser(pointEmptyString); - // FIXME - // pointWebMerc1MP = - // GeometryEngine.jsonToGeometry(pointWebMerc1Parser); - // assertTrue(pointWebMerc1MP.getGeometry().isEmpty()); - // assertTrue(spatialReferenceWebMerc1.getID() == - // pointWebMerc1MP.getSpatialReference().getID()); } JsonParser pointWebMerc2Parser = factory @@ -138,7 +132,7 @@ boolean testPoint() throws JsonParseException, IOException { String s = GeometryEngine.geometryToJson(spatialReferenceWebMerc1, p); assertTrue(s - .equals("{\"x\":10.0,\"y\":20.0,\"z\":30.0,\"m\":null,\"spatialReference\":{\"wkid\":102100,\"latestWkid\":3857}}")); + .equals("{\"x\":10,\"y\":20,\"z\":30,\"m\":null,\"spatialReference\":{\"wkid\":102100,\"latestWkid\":3857}}")); } {// import @@ -198,12 +192,6 @@ boolean testMultiPoint() throws JsonParseException, IOException { bAnswer = false; } - // FIXME - // MultiPoint mPointEmpty = new MultiPoint(); - // String mPointEmptyString = - // GeometryEngine.geometryToJson(spatialReferenceWGS84, - // mPointEmpty); - // mPointWgs84Parser = factory.createJsonParser(mPointEmptyString); } { @@ -219,7 +207,7 @@ boolean testMultiPoint() throws JsonParseException, IOException { p.add(20.0, 40.0, 60.0); s = GeometryEngine.geometryToJson(spatialReferenceWebMerc1, p); assertTrue(s - .equals("{\"hasZ\":true,\"hasM\":true,\"points\":[[10.0,20.0,30.0,null],[20.0,40.0,60.0,null]],\"spatialReference\":{\"wkid\":102100,\"latestWkid\":3857}}")); + .equals("{\"hasZ\":true,\"hasM\":true,\"points\":[[10,20,30,null],[20,40,60,null]],\"spatialReference\":{\"wkid\":102100,\"latestWkid\":3857}}")); } { String points = "{\"hasM\" : false, \"hasZ\" : true, \"uncle remus\" : null, \"points\" : [ [0,0,1], [0.0,10.0,1], [10.0,10.0,1], [10.0,0.0,1, 6666] ],\"spatialReference\" : {\"wkid\" : 4326}}"; @@ -315,7 +303,7 @@ boolean testPolyline() throws JsonParseException, IOException { p.setAttribute(VertexDescription.Semantics.M, 1, 0, 5); s = GeometryEngine.geometryToJson(spatialReferenceWebMerc1, p); assertTrue(s - .equals("{\"hasZ\":true,\"hasM\":true,\"paths\":[[[0.0,0.0,3.0,null],[0.0,1.0,0.0,5.0]],[[2.0,2.0,0.0,null],[3.0,3.0,0.0,null]]],\"spatialReference\":{\"wkid\":102100,\"latestWkid\":3857}}")); + .equals("{\"hasZ\":true,\"hasM\":true,\"paths\":[[[0,0,3,null],[0,1,0,5]],[[2,2,0,null],[3,3,0,null]]],\"spatialReference\":{\"wkid\":102100,\"latestWkid\":3857}}")); } { @@ -412,7 +400,7 @@ boolean testPolygon() throws JsonParseException, IOException { p.setAttribute(VertexDescription.Semantics.M, 5, 0, 5); s = GeometryEngine.geometryToJson(spatialReferenceWebMerc1, p); assertTrue(s - .equals("{\"hasZ\":true,\"hasM\":true,\"rings\":[[[0.0,0.0,3.0,null],[0.0,1.0,0.0,7.0],[4.0,4.0,0.0,5.0],[0.0,0.0,3.0,null]],[[2.0,2.0,0.0,null],[3.0,3.0,0.0,null],[7.0,8.0,0.0,5.0],[2.0,2.0,0.0,null]]],\"spatialReference\":{\"wkid\":102100,\"latestWkid\":3857}}")); + .equals("{\"hasZ\":true,\"hasM\":true,\"rings\":[[[0,0,3,null],[0,1,0,7],[4,4,0,5],[0,0,3,null]],[[2,2,0,null],[3,3,0,null],[7,8,0,5],[2,2,0,null]]],\"spatialReference\":{\"wkid\":102100,\"latestWkid\":3857}}")); } { @@ -484,7 +472,7 @@ boolean testEnvelope() throws JsonParseException, IOException { e.setInterval(VertexDescription.Semantics.M, 0, m); s = GeometryEngine.geometryToJson(spatialReferenceWebMerc1, e); assertTrue(s - .equals("{\"xmin\":0.0,\"ymin\":1.0,\"xmax\":2.0,\"ymax\":3.0,\"zmin\":5.0,\"zmax\":7.0,\"mmin\":11.0,\"mmax\":13.0,\"spatialReference\":{\"wkid\":102100,\"latestWkid\":3857}}")); + .equals("{\"xmin\":0,\"ymin\":1,\"xmax\":2,\"ymax\":3,\"zmin\":5,\"zmax\":7,\"mmin\":11,\"mmax\":13,\"spatialReference\":{\"wkid\":102100,\"latestWkid\":3857}}")); } {// import diff --git a/unittest/com/esri/core/geometry/TestJsonParser.java b/unittest/com/esri/core/geometry/TestJsonParser.java index 9c22d49a..de188f3e 100644 --- a/unittest/com/esri/core/geometry/TestJsonParser.java +++ b/unittest/com/esri/core/geometry/TestJsonParser.java @@ -1,663 +1,666 @@ package com.esri.core.geometry; +import java.util.Hashtable; import java.io.IOException; +import java.util.Map; import junit.framework.TestCase; import org.codehaus.jackson.JsonFactory; import org.codehaus.jackson.JsonParseException; import org.codehaus.jackson.JsonParser; import org.codehaus.jackson.JsonToken; +import org.json.JSONObject; import org.junit.Assert; import org.junit.Test; public class TestJsonParser extends TestCase { - JsonFactory factory = new JsonFactory(); - SpatialReference spatialReferenceWebMerc1 = SpatialReference.create(102100); - SpatialReference spatialReferenceWebMerc2 = SpatialReference - .create(spatialReferenceWebMerc1.getLatestID()); - SpatialReference spatialReferenceWGS84 = SpatialReference.create(4326); - - @Override - protected void setUp() throws Exception { - super.setUp(); - } - - @Override - protected void tearDown() throws Exception { - super.tearDown(); - } - - @Test - public void test3DPoint() throws JsonParseException, IOException { - String jsonString3DPt = "{\"x\" : -118.15, \"y\" : 33.80, \"z\" : 10.0, \"spatialReference\" : {\"wkid\" : 4326}}"; - - JsonParser jsonParser3DPt = factory.createJsonParser(jsonString3DPt); - MapGeometry point3DMP = GeometryEngine.jsonToGeometry(jsonParser3DPt); - assertTrue(-118.15 == ((Point) point3DMP.getGeometry()).getX()); - assertTrue(33.80 == ((Point) point3DMP.getGeometry()).getY()); - // FIXME add 3D support - // assertTrue(10.0 == ((Point)point3DMP.getGeometry()).getZ()); - assertTrue(spatialReferenceWGS84.getID() == point3DMP - .getSpatialReference().getID()); - } + + JsonFactory factory = new JsonFactory(); + SpatialReference spatialReferenceWebMerc1 = SpatialReference.create(102100); + SpatialReference spatialReferenceWebMerc2 = SpatialReference + .create(spatialReferenceWebMerc1.getLatestID()); + SpatialReference spatialReferenceWGS84 = SpatialReference.create(4326); + + @Override + protected void setUp() throws Exception { + super.setUp(); + } + + @Override + protected void tearDown() throws Exception { + super.tearDown(); + } + + @Test + public void test3DPoint() throws JsonParseException, IOException { + String jsonString3DPt = "{\"x\" : -118.15, \"y\" : 33.80, \"z\" : 10.0, \"spatialReference\" : {\"wkid\" : 4326}}"; + + JsonParser jsonParser3DPt = factory.createJsonParser(jsonString3DPt); + MapGeometry point3DMP = GeometryEngine.jsonToGeometry(jsonParser3DPt); + assertTrue(-118.15 == ((Point) point3DMP.getGeometry()).getX()); + assertTrue(33.80 == ((Point) point3DMP.getGeometry()).getY()); + assertTrue(spatialReferenceWGS84.getID() == point3DMP + .getSpatialReference().getID()); + } - @Test - public void test3DPoint1() throws JsonParseException, IOException { - Point point1 = new Point(10.0, 20.0); - Point pointEmpty = new Point(); - { - JsonParser pointWebMerc1Parser = factory - .createJsonParser(GeometryEngine.geometryToJson( - spatialReferenceWebMerc1, point1)); - MapGeometry pointWebMerc1MP = GeometryEngine - .jsonToGeometry(pointWebMerc1Parser); - assertTrue(point1.getX() == ((Point) pointWebMerc1MP.getGeometry()) - .getX()); - assertTrue(point1.getY() == ((Point) pointWebMerc1MP.getGeometry()) - .getY()); - int srIdOri = spatialReferenceWebMerc1.getID(); - int srIdAfter = pointWebMerc1MP.getSpatialReference().getID(); - assertTrue(srIdOri == srIdAfter || srIdAfter == 3857); - - pointWebMerc1Parser = factory.createJsonParser(GeometryEngine - .geometryToJson(null, point1)); - pointWebMerc1MP = GeometryEngine - .jsonToGeometry(pointWebMerc1Parser); - assertTrue(null == pointWebMerc1MP.getSpatialReference()); - - String pointEmptyString = GeometryEngine.geometryToJson( - spatialReferenceWebMerc1, pointEmpty); - pointWebMerc1Parser = factory.createJsonParser(pointEmptyString); - // FIXME - pointWebMerc1MP = GeometryEngine - .jsonToGeometry(pointWebMerc1Parser); - assertTrue(pointWebMerc1MP.getGeometry().isEmpty()); - int srIdOri2 = spatialReferenceWebMerc1.getID(); - int srIdAfter2 = pointWebMerc1MP.getSpatialReference().getID(); - assertTrue(srIdOri2 == srIdAfter2 || srIdAfter2 == 3857); - } - } - - @Test - public void test3DPoint2() throws JsonParseException, IOException { - { - Point point1 = new Point(10.0, 20.0); - JsonParser pointWebMerc2Parser = factory - .createJsonParser(GeometryEngine.geometryToJson( - spatialReferenceWebMerc2, point1)); - MapGeometry pointWebMerc2MP = GeometryEngine - .jsonToGeometry(pointWebMerc2Parser); - assertTrue(point1.getX() == ((Point) pointWebMerc2MP.getGeometry()) - .getX()); - assertTrue(point1.getY() == ((Point) pointWebMerc2MP.getGeometry()) - .getY()); - assertTrue(spatialReferenceWebMerc2.getLatestID() == pointWebMerc2MP - .getSpatialReference().getLatestID()); - } - } - - @Test - public void test3DPoint3() throws JsonParseException, IOException { - { - Point point1 = new Point(10.0, 20.0); - JsonParser pointWgs84Parser = factory - .createJsonParser(GeometryEngine.geometryToJson( - spatialReferenceWGS84, point1)); - MapGeometry pointWgs84MP = GeometryEngine - .jsonToGeometry(pointWgs84Parser); - assertTrue(point1.getX() == ((Point) pointWgs84MP.getGeometry()) - .getX()); - assertTrue(point1.getY() == ((Point) pointWgs84MP.getGeometry()) - .getY()); - assertTrue(spatialReferenceWGS84.getID() == pointWgs84MP - .getSpatialReference().getID()); - } - } - - @Test - public void testMultiPoint() throws JsonParseException, IOException { - MultiPoint multiPoint1 = new MultiPoint(); - multiPoint1.add(-97.06138, 32.837); - multiPoint1.add(-97.06133, 32.836); - multiPoint1.add(-97.06124, 32.834); - multiPoint1.add(-97.06127, 32.832); - - { - JsonParser mPointWgs84Parser = factory - .createJsonParser(GeometryEngine.geometryToJson( - spatialReferenceWGS84, multiPoint1)); - MapGeometry mPointWgs84MP = GeometryEngine - .jsonToGeometry(mPointWgs84Parser); - assertTrue(multiPoint1.getPointCount() == ((MultiPoint) mPointWgs84MP - .getGeometry()).getPointCount()); - assertTrue(multiPoint1.getPoint(0).getX() == ((MultiPoint) mPointWgs84MP - .getGeometry()).getPoint(0).getX()); - assertTrue(multiPoint1.getPoint(0).getY() == ((MultiPoint) mPointWgs84MP - .getGeometry()).getPoint(0).getY()); - int lastIndex = multiPoint1.getPointCount() - 1; - assertTrue(multiPoint1.getPoint(lastIndex).getX() == ((MultiPoint) mPointWgs84MP - .getGeometry()).getPoint(lastIndex).getX()); - assertTrue(multiPoint1.getPoint(lastIndex).getY() == ((MultiPoint) mPointWgs84MP - .getGeometry()).getPoint(lastIndex).getY()); - - assertTrue(spatialReferenceWGS84.getID() == mPointWgs84MP - .getSpatialReference().getID()); - - MultiPoint mPointEmpty = new MultiPoint(); - String mPointEmptyString = GeometryEngine.geometryToJson( - spatialReferenceWGS84, mPointEmpty); - mPointWgs84Parser = factory.createJsonParser(mPointEmptyString); - - mPointWgs84MP = GeometryEngine.jsonToGeometry(mPointWgs84Parser); - assertTrue(mPointWgs84MP.getGeometry().isEmpty()); - assertTrue(spatialReferenceWGS84.getID() == mPointWgs84MP - .getSpatialReference().getID()); - - } - } - - @Test - public void testPolyline() throws JsonParseException, IOException { - Polyline polyline = new Polyline(); - polyline.startPath(-97.06138, 32.837); - polyline.lineTo(-97.06133, 32.836); - polyline.lineTo(-97.06124, 32.834); - polyline.lineTo(-97.06127, 32.832); - - polyline.startPath(-97.06326, 32.759); - polyline.lineTo(-97.06298, 32.755); - - { - JsonParser polylinePathsWgs84Parser = factory - .createJsonParser(GeometryEngine.geometryToJson( - spatialReferenceWGS84, polyline)); - MapGeometry mPolylineWGS84MP = GeometryEngine - .jsonToGeometry(polylinePathsWgs84Parser); - - assertTrue(polyline.getPointCount() == ((Polyline) mPolylineWGS84MP - .getGeometry()).getPointCount()); - assertTrue(polyline.getPoint(0).getX() == ((Polyline) mPolylineWGS84MP - .getGeometry()).getPoint(0).getX()); - assertTrue(polyline.getPoint(0).getY() == ((Polyline) mPolylineWGS84MP - .getGeometry()).getPoint(0).getY()); - - assertTrue(polyline.getPathCount() == ((Polyline) mPolylineWGS84MP - .getGeometry()).getPathCount()); - assertTrue(polyline.getSegmentCount() == ((Polyline) mPolylineWGS84MP - .getGeometry()).getSegmentCount()); - assertTrue(polyline.getSegmentCount(0) == ((Polyline) mPolylineWGS84MP - .getGeometry()).getSegmentCount(0)); - assertTrue(polyline.getSegmentCount(1) == ((Polyline) mPolylineWGS84MP - .getGeometry()).getSegmentCount(1)); - - int lastIndex = polyline.getPointCount() - 1; - assertTrue(polyline.getPoint(lastIndex).getX() == ((Polyline) mPolylineWGS84MP - .getGeometry()).getPoint(lastIndex).getX()); - assertTrue(polyline.getPoint(lastIndex).getY() == ((Polyline) mPolylineWGS84MP - .getGeometry()).getPoint(lastIndex).getY()); - - assertTrue(spatialReferenceWGS84.getID() == mPolylineWGS84MP - .getSpatialReference().getID()); - - Polyline emptyPolyline = new Polyline(); - String emptyString = GeometryEngine.geometryToJson( - spatialReferenceWGS84, emptyPolyline); - mPolylineWGS84MP = GeometryEngine.jsonToGeometry(factory - .createJsonParser(emptyString)); - assertTrue(mPolylineWGS84MP.getGeometry().isEmpty()); - assertTrue(spatialReferenceWGS84.getID() == mPolylineWGS84MP - .getSpatialReference().getID()); - } - } - - @Test - public void testPolygon() throws JsonParseException, IOException { - Polygon polygon = new Polygon(); - polygon.startPath(-97.06138, 32.837); - polygon.lineTo(-97.06133, 32.836); - polygon.lineTo(-97.06124, 32.834); - polygon.lineTo(-97.06127, 32.832); - - polygon.startPath(-97.06326, 32.759); - polygon.lineTo(-97.06298, 32.755); - - { - JsonParser polygonPathsWgs84Parser = factory - .createJsonParser(GeometryEngine.geometryToJson( - spatialReferenceWGS84, polygon)); - MapGeometry mPolygonWGS84MP = GeometryEngine - .jsonToGeometry(polygonPathsWgs84Parser); - - assertTrue(polygon.getPointCount() + 1 == ((Polygon) mPolygonWGS84MP - .getGeometry()).getPointCount()); - assertTrue(polygon.getPoint(0).getX() == ((Polygon) mPolygonWGS84MP - .getGeometry()).getPoint(0).getX()); - assertTrue(polygon.getPoint(0).getY() == ((Polygon) mPolygonWGS84MP - .getGeometry()).getPoint(0).getY()); - - assertTrue(polygon.getPathCount() == ((Polygon) mPolygonWGS84MP - .getGeometry()).getPathCount()); - assertTrue(polygon.getSegmentCount() + 1 == ((Polygon) mPolygonWGS84MP - .getGeometry()).getSegmentCount()); - assertTrue(polygon.getSegmentCount(0) == ((Polygon) mPolygonWGS84MP - .getGeometry()).getSegmentCount(0)); - assertTrue(polygon.getSegmentCount(1) + 1 == ((Polygon) mPolygonWGS84MP - .getGeometry()).getSegmentCount(1)); - - int lastIndex = polygon.getPointCount() - 1; - assertTrue(polygon.getPoint(lastIndex).getX() == ((Polygon) mPolygonWGS84MP - .getGeometry()).getPoint(lastIndex).getX()); - assertTrue(polygon.getPoint(lastIndex).getY() == ((Polygon) mPolygonWGS84MP - .getGeometry()).getPoint(lastIndex).getY()); - - assertTrue(spatialReferenceWGS84.getID() == mPolygonWGS84MP - .getSpatialReference().getID()); - - Polygon emptyPolygon = new Polygon(); - String emptyPolygonString = GeometryEngine.geometryToJson( - spatialReferenceWGS84, emptyPolygon); - polygonPathsWgs84Parser = factory - .createJsonParser(emptyPolygonString); - mPolygonWGS84MP = GeometryEngine - .jsonToGeometry(polygonPathsWgs84Parser); - - assertTrue(mPolygonWGS84MP.getGeometry().isEmpty()); - assertTrue(spatialReferenceWGS84.getID() == mPolygonWGS84MP - .getSpatialReference().getID()); - } - } - - @Test - public void testEnvelope() throws JsonParseException, IOException { - Envelope envelope = new Envelope(); - envelope.setCoords(-109.55, 25.76, -86.39, 49.94); - - { - JsonParser envelopeWGS84Parser = factory - .createJsonParser(GeometryEngine.geometryToJson( - spatialReferenceWGS84, envelope)); - MapGeometry envelopeWGS84MP = GeometryEngine - .jsonToGeometry(envelopeWGS84Parser); - assertTrue(envelope.isEmpty() == envelopeWGS84MP.getGeometry() - .isEmpty()); - assertTrue(envelope.getXMax() == ((Envelope) envelopeWGS84MP - .getGeometry()).getXMax()); - assertTrue(envelope.getYMax() == ((Envelope) envelopeWGS84MP - .getGeometry()).getYMax()); - assertTrue(envelope.getXMin() == ((Envelope) envelopeWGS84MP - .getGeometry()).getXMin()); - assertTrue(envelope.getYMin() == ((Envelope) envelopeWGS84MP - .getGeometry()).getYMin()); - assertTrue(spatialReferenceWGS84.getID() == envelopeWGS84MP - .getSpatialReference().getID()); - - Envelope emptyEnvelope = new Envelope(); - String emptyEnvString = GeometryEngine.geometryToJson( - spatialReferenceWGS84, emptyEnvelope); - envelopeWGS84Parser = factory.createJsonParser(emptyEnvString); - envelopeWGS84MP = GeometryEngine - .jsonToGeometry(envelopeWGS84Parser); - - assertTrue(envelopeWGS84MP.getGeometry().isEmpty()); - assertTrue(spatialReferenceWGS84.getID() == envelopeWGS84MP - .getSpatialReference().getID()); - } - } - - @Test - public void testCR181369() throws JsonParseException, IOException { - // CR181369 - { - String jsonStringPointAndWKT = "{\"x\":10.0,\"y\":20.0,\"spatialReference\":{\"wkt\" : \"PROJCS[\\\"NAD83_UTM_zone_15N\\\",GEOGCS[\\\"GCS_North_American_1983\\\",DATUM[\\\"D_North_American_1983\\\",SPHEROID[\\\"GRS_1980\\\",6378137.0,298.257222101]],PRIMEM[\\\"Greenwich\\\",0.0],UNIT[\\\"Degree\\\",0.0174532925199433]],PROJECTION[\\\"Transverse_Mercator\\\"],PARAMETER[\\\"false_easting\\\",500000.0],PARAMETER[\\\"false_northing\\\",0.0],PARAMETER[\\\"central_meridian\\\",-93.0],PARAMETER[\\\"scale_factor\\\",0.9996],PARAMETER[\\\"latitude_of_origin\\\",0.0],UNIT[\\\"Meter\\\",1.0]]\"} }"; - JsonParser jsonParserPointAndWKT = factory - .createJsonParser(jsonStringPointAndWKT); - MapGeometry mapGeom2 = GeometryEngine - .jsonToGeometry(jsonParserPointAndWKT); - String jsonStringPointAndWKT2 = GeometryEngine.geometryToJson( - mapGeom2.getSpatialReference(), mapGeom2.getGeometry()); - JsonParser jsonParserPointAndWKT2 = factory - .createJsonParser(jsonStringPointAndWKT2); - MapGeometry mapGeom3 = GeometryEngine - .jsonToGeometry(jsonParserPointAndWKT2); - assertTrue(((Point) mapGeom2.getGeometry()).getX() == ((Point) mapGeom3 - .getGeometry()).getX()); - assertTrue(((Point) mapGeom2.getGeometry()).getY() == ((Point) mapGeom3 - .getGeometry()).getY()); - assertTrue(mapGeom2.getSpatialReference().getText() - .equals(mapGeom3.getSpatialReference().getText())); - assertTrue(mapGeom2.getSpatialReference().getID() == mapGeom3 - .getSpatialReference().getID()); - } - } - - @Test - public void testSpatialRef() throws JsonParseException, IOException { - // String jsonStringPt = - // "{\"x\":-20037508.342787,\"y\":20037508.342787},\"spatialReference\":{\"wkid\":102100}}"; - String jsonStringPt = "{\"x\":10.0,\"y\":20.0,\"spatialReference\":{\"wkid\": 102100}}";// 102100 - @SuppressWarnings("unused") - String jsonStringPt2 = "{\"x\":10.0,\"y\":20.0,\"spatialReference\":{\"wkid\":4326}}"; - String jsonStringMpt = "{ \"points\" : [ [-97.06138,32.837], [-97.06133,32.836], [-97.06124,32.834], [-97.06127,32.832] ], \"spatialReference\" : {\"wkid\" : 4326}}";// 4326 - String jsonStringMpt3D = "{\"hasZs\" : true,\"points\" : [ [-97.06138,32.837,35.0], [-97.06133,32.836,35.1], [-97.06124,32.834,35.2], [-97.06127,32.832,35.3] ],\"spatialReference\" : {\"wkid\" : 4326}}"; - String jsonStringPl = "{\"paths\" : [ [ [-97.06138,32.837], [-97.06133,32.836], [-97.06124,32.834], [-97.06127,32.832] ], [ [-97.06326,32.759], [-97.06298,32.755] ]],\"spatialReference\" : {\"wkid\" : 4326}}"; - String jsonStringPl3D = "{\"hasMs\" : true,\"paths\" : [[ [-97.06138,32.837,5], [-97.06133,32.836,6], [-97.06124,32.834,7], [-97.06127,32.832,8] ],[ [-97.06326,32.759], [-97.06298,32.755] ]],\"spatialReference\" : {\"wkid\" : 4326}}"; - String jsonStringPg = "{ \"rings\" :[ [ [-97.06138,32.837], [-97.06133,32.836], [-97.06124,32.834], [-97.06127,32.832], [-97.06138,32.837] ], [ [-97.06326,32.759], [-97.06298,32.755], [-97.06153,32.749], [-97.06326,32.759] ]], \"spatialReference\" : {\"wkt\" : \"\"}}"; - String jsonStringPg3D = "{\"hasZs\" : true,\"hasMs\" : true,\"rings\" : [ [ [-97.06138, 32.837, 35.1, 4], [-97.06133, 32.836, 35.2, 4.1], [-97.06124, 32.834, 35.3, 4.2], [-97.06127, 32.832, 35.2, 44.3], [-97.06138, 32.837, 35.1, 4] ],[ [-97.06326, 32.759, 35.4], [-97.06298, 32.755, 35.5], [-97.06153, 32.749, 35.6], [-97.06326, 32.759, 35.4] ]],\"spatialReference\" : {\"wkid\" : 4326}}"; - String jsonStringPg2 = "{ \"spatialReference\" : {\"wkid\" : 4326}, \"rings\" : [[[-118.35,32.81],[-118.42,32.806],[-118.511,32.892],[-118.35,32.81]]]}"; - String jsonStringPg3 = "{ \"spatialReference\": {\"layerName\":\"GAS_POINTS\",\"name\":null,\"sdesrid\":102100,\"wkid\":102100,\"wkt\":null}}"; - String jsonString2SpatialReferences = "{ \"spatialReference\": {\"layerName\":\"GAS_POINTS\",\"name\":null,\"sdesrid\":102100,\"wkid\":102100,\"wkt\":\"GEOGCS[\\\"GCS_WGS_1984\\\",DATUM[\\\"D_WGS_1984\\\",SPHEROID[\\\"WGS_1984\\\",6378137,298.257223563]],PRIMEM[\\\"Greenwich\\\",0],UNIT[\\\"Degree\\\",0.017453292519943295]]\"}}"; - String jsonString2SpatialReferences2 = "{ \"spatialReference\": {\"layerName\":\"GAS_POINTS\",\"name\":null,\"sdesrid\":10,\"wkid\":10,\"wkt\":\"GEOGCS[\\\"GCS_WGS_1984\\\",DATUM[\\\"D_WGS_1984\\\",SPHEROID[\\\"WGS_1984\\\",6378137,298.257223563]],PRIMEM[\\\"Greenwich\\\",0],UNIT[\\\"Degree\\\",0.017453292519943295]]\"}}"; - String jsonStringSR = "{\"wkid\" : 4326}"; - String jsonStringEnv = "{\"xmin\" : -109.55, \"ymin\" : 25.76, \"xmax\" : -86.39, \"ymax\" : 49.94,\"spatialReference\" : {\"wkid\" : 4326}}"; - String jsonStringHongKon = "{\"xmin\" : -122.55, \"ymin\" : 37.65, \"xmax\" : -122.28, \"ymax\" : 37.84,\"spatialReference\" : {\"wkid\" : 4326}}"; - @SuppressWarnings("unused") - String jsonStringWKT = " {\"wkt\" : \"GEOGCS[\\\"GCS_WGS_1984\\\",DATUM[\\\"D_WGS_1984\\\",SPHEROID[\\\"WGS_1984\\\",6378137,298.257223563]],PRIMEM[\\\"Greenwich\\\",0],UNIT[\\\"Degree\\\",0.017453292519943295]]\"}"; - String jsonStringInvalidWKID = "{\"x\":10.0,\"y\":20.0},\"spatialReference\":{\"wkid\":35253523}}"; - String jsonStringOregon = "{\"xmin\":7531831.219849482,\"ymin\":585702.9799639136,\"xmax\":7750143.589982405,\"ymax\":733289.6299999952,\"spatialReference\":{\"wkid\":102726}}"; - - JsonParser jsonParserPt = factory.createJsonParser(jsonStringPt); - JsonParser jsonParserMpt = factory.createJsonParser(jsonStringMpt); - JsonParser jsonParserMpt3D = factory.createJsonParser(jsonStringMpt3D); - JsonParser jsonParserPl = factory.createJsonParser(jsonStringPl); - JsonParser jsonParserPl3D = factory.createJsonParser(jsonStringPl3D); - JsonParser jsonParserPg = factory.createJsonParser(jsonStringPg); - JsonParser jsonParserPg3D = factory.createJsonParser(jsonStringPg3D); - JsonParser jsonParserPg2 = factory.createJsonParser(jsonStringPg2); - @SuppressWarnings("unused") - JsonParser jsonParserSR = factory.createJsonParser(jsonStringSR); - JsonParser jsonParserEnv = factory.createJsonParser(jsonStringEnv); - JsonParser jsonParserPg3 = factory.createJsonParser(jsonStringPg3); - @SuppressWarnings("unused") - JsonParser jsonParserCrazy1 = factory - .createJsonParser(jsonString2SpatialReferences); - @SuppressWarnings("unused") - JsonParser jsonParserCrazy2 = factory - .createJsonParser(jsonString2SpatialReferences2); - JsonParser jsonParserInvalidWKID = factory - .createJsonParser(jsonStringInvalidWKID); - @SuppressWarnings("unused") - JsonParser jsonParseHongKon = factory - .createJsonParser(jsonStringHongKon); - JsonParser jsonParseOregon = factory.createJsonParser(jsonStringOregon); - - MapGeometry mapGeom = GeometryEngine.jsonToGeometry(jsonParserPt); - // showProjectedGeometryInfo(mapGeom); - Assert.assertTrue(mapGeom.getSpatialReference().getID() == 102100); - - MapGeometry mapGeomOregon = GeometryEngine - .jsonToGeometry(jsonParseOregon); - Assert.assertTrue(mapGeomOregon.getSpatialReference().getID() == 102726); - - mapGeom = GeometryEngine.jsonToGeometry(jsonParserMpt); - Assert.assertTrue(mapGeom.getSpatialReference().getID() == 4326); - - mapGeom = GeometryEngine.jsonToGeometry(jsonParserMpt3D); - Assert.assertTrue(mapGeom.getSpatialReference().getID() == 4326); - { - Assert.assertTrue(((MultiPoint) mapGeom.getGeometry()).getPoint(0) - .getX() == -97.06138); - Assert.assertTrue(((MultiPoint) mapGeom.getGeometry()).getPoint(0) - .getY() == 32.837); - Assert.assertTrue(((MultiPoint) mapGeom.getGeometry()).getPoint(3) - .getX() == -97.06127); - Assert.assertTrue(((MultiPoint) mapGeom.getGeometry()).getPoint(3) - .getY() == 32.832); - } - // showProjectedGeometryInfo(mapGeom); - - mapGeom = GeometryEngine.jsonToGeometry(jsonParserPl); - Assert.assertTrue(mapGeom.getSpatialReference().getID() == 4326); - // showProjectedGeometryInfo(mapGeom); - - mapGeom = GeometryEngine.jsonToGeometry(jsonParserPl3D); - { - // [[ [-97.06138,32.837,5], [-97.06133,32.836,6], - // [-97.06124,32.834,7], [-97.06127,32.832,8] ], - // [ [-97.06326,32.759], [-97.06298,32.755] ]]"; - Assert.assertTrue(((Polyline) mapGeom.getGeometry()).getPoint(0) - .getX() == -97.06138); - Assert.assertTrue(((Polyline) mapGeom.getGeometry()).getPoint(0) - .getY() == 32.837); - int lastIndex = ((Polyline) mapGeom.getGeometry()).getPointCount() - 1; - Assert.assertTrue(((Polyline) mapGeom.getGeometry()).getPoint( - lastIndex).getX() == -97.06298);// -97.06153, 32.749 - Assert.assertTrue(((Polyline) mapGeom.getGeometry()).getPoint( - lastIndex).getY() == 32.755); - int lastIndexFirstLine = ((Polyline) mapGeom.getGeometry()) - .getPathEnd(0) - 1; - Assert.assertTrue(((Polyline) mapGeom.getGeometry()).getPoint( - lastIndexFirstLine).getX() == -97.06127);// -97.06153, - // 32.749 - Assert.assertTrue(((Polyline) mapGeom.getGeometry()).getPoint( - lastIndexFirstLine).getY() == 32.832); - } - - mapGeom = GeometryEngine.jsonToGeometry(jsonParserPg); - Assert.assertTrue(mapGeom.getSpatialReference() == null); - - mapGeom = GeometryEngine.jsonToGeometry(jsonParserPg3D); - { - Assert.assertTrue(((Polygon) mapGeom.getGeometry()).getPoint(0) - .getX() == -97.06138); - Assert.assertTrue(((Polygon) mapGeom.getGeometry()).getPoint(0) - .getY() == 32.837); - int lastIndex = ((Polygon) mapGeom.getGeometry()).getPointCount() - 1; - Assert.assertTrue(((Polygon) mapGeom.getGeometry()).getPoint( - lastIndex).getX() == -97.06153);// -97.06153, 32.749 - Assert.assertTrue(((Polygon) mapGeom.getGeometry()).getPoint( - lastIndex).getY() == 32.749); - } - - mapGeom = GeometryEngine.jsonToGeometry(jsonParserPg2); - Assert.assertTrue(mapGeom.getSpatialReference().getID() == 4326); - // showProjectedGeometryInfo(mapGeom); - - mapGeom = GeometryEngine.jsonToGeometry(jsonParserPg3); - Assert.assertTrue(mapGeom.getSpatialReference().getID() == 102100); - // showProjectedGeometryInfo(mapGeom); - - // mapGeom = GeometryEngine.jsonToGeometry(jsonParserCrazy1); - // Assert.assertTrue(mapGeom.getSpatialReference().getText().equals("")); - // showProjectedGeometryInfo(mapGeom); - - mapGeom = GeometryEngine.jsonToGeometry(jsonParserEnv); - Assert.assertTrue(mapGeom.getSpatialReference().getID() == 4326); - // showProjectedGeometryInfo(mapGeom); - // System.out.println("\n\nWKID: "+ - // SpatialReference.create(wkid).fromJson(jsonParserSR).getID()); - - try { - GeometryEngine.jsonToGeometry(jsonParserInvalidWKID); - } catch (Exception ex) { - System.out.print(ex.getMessage()); - } - } - - @Test - public void testMP2onCR175871() throws Exception { - Polygon pg = new Polygon(); - pg.startPath(-50, 10); - pg.lineTo(-50, 12); - pg.lineTo(-45, 12); - pg.lineTo(-45, 10); - - Polygon pg1 = new Polygon(); - pg1.startPath(-45, 10); - pg1.lineTo(-40, 10); - pg1.lineTo(-40, 8); - pg.add(pg1, false); - - SpatialReference spatialReference = SpatialReference.create(4326); - - try { - String jSonStr = GeometryEngine - .geometryToJson(spatialReference, pg); - JsonFactory jf = new JsonFactory(); - - JsonParser jp = jf.createJsonParser(jSonStr); - jp.nextToken(); - MapGeometry mg = GeometryEngine.jsonToGeometry(jp); - Geometry gm = mg.getGeometry(); - Assert.assertEquals(Geometry.Type.Polygon, gm.getType()); - Assert.assertTrue(mg.getSpatialReference().getID() == 4326); - - Polygon pgNew = (Polygon) gm; - - Assert.assertEquals(pgNew.getPathCount(), pg.getPathCount()); - Assert.assertEquals(pgNew.getPointCount(), pg.getPointCount()); - Assert.assertEquals(pgNew.getSegmentCount(), pg.getSegmentCount()); - - Assert.assertEquals(pgNew.getPoint(0).getX(), - pg.getPoint(0).getX(), 0.000000001); - Assert.assertEquals(pgNew.getPoint(1).getX(), - pg.getPoint(1).getX(), 0.000000001); - Assert.assertEquals(pgNew.getPoint(2).getX(), - pg.getPoint(2).getX(), 0.000000001); - Assert.assertEquals(pgNew.getPoint(3).getX(), - pg.getPoint(3).getX(), 0.000000001); - - Assert.assertEquals(pgNew.getPoint(0).getY(), - pg.getPoint(0).getY(), 0.000000001); - Assert.assertEquals(pgNew.getPoint(1).getY(), - pg.getPoint(1).getY(), 0.000000001); - Assert.assertEquals(pgNew.getPoint(2).getY(), - pg.getPoint(2).getY(), 0.000000001); - Assert.assertEquals(pgNew.getPoint(3).getY(), - pg.getPoint(3).getY(), 0.000000001); - } catch (Exception ex) { - String err = ex.getMessage(); - System.out.print(err); - throw ex; - } - } - - @Test - public static int fromJsonToWkid(JsonParser parser) - throws JsonParseException, IOException { - int wkid = 0; - if (parser.getCurrentToken() != JsonToken.START_OBJECT) { - return 0; - } - - while (parser.nextToken() != JsonToken.END_OBJECT) { - String fieldName = parser.getCurrentName(); - - if ("wkid".equals(fieldName)) { - parser.nextToken(); - wkid = parser.getIntValue(); - } - } - return wkid; - } - - @SuppressWarnings("unused") - private static void showProjectedGeometryInfo(MapGeometry mapGeom) { - System.out.println("\n"); - MapGeometry geom = mapGeom; - // while ((geom = geomCursor.next()) != null) { - - if (geom.getGeometry() instanceof Point) { - Point pnt = (Point) geom.getGeometry(); - System.out - .println("Point(" + pnt.getX() + " , " + pnt.getY() + ")"); - if (geom.getSpatialReference() == null) - System.out.println("No spatial reference"); - else - System.out.println("wkid: " - + geom.getSpatialReference().getID()); - - } else if (geom.getGeometry() instanceof MultiPoint) { - MultiPoint mp = (MultiPoint) geom.getGeometry(); - System.out.println("Multipoint has " + mp.getPointCount() - + " points."); - - System.out.println("wkid: " + geom.getSpatialReference().getID()); - - } else if (geom.getGeometry() instanceof Polygon) { - Polygon mp = (Polygon) geom.getGeometry(); - System.out.println("Polygon has " + mp.getPointCount() - + " points and " + mp.getPathCount() + " parts."); - if (mp.getPathCount() > 1) { - System.out.println("Part start of 2nd segment : " - + mp.getPathStart(1)); - System.out.println("Part end of 2nd segment : " - + mp.getPathEnd(1)); - System.out.println("Part size of 2nd segment : " - + mp.getPathSize(1)); - - int start = mp.getPathStart(1); - int end = mp.getPathEnd(1); - for (int i = start; i < end; i++) { - Point pp = mp.getPoint(i); - System.out.println("Point(" + i + ") = (" + pp.getX() - + ", " + pp.getY() + ")"); - } - } - System.out.println("wkid: " + geom.getSpatialReference().getID()); - - } else if (geom.getGeometry() instanceof Polyline) { - Polyline mp = (Polyline) geom.getGeometry(); - System.out.println("Polyline has " + mp.getPointCount() - + " points and " + mp.getPathCount() + " parts."); - System.out.println("Part start of 2nd segment : " - + mp.getPathStart(1)); - System.out.println("Part end of 2nd segment : " - + mp.getPathEnd(1)); - System.out.println("Part size of 2nd segment : " - + mp.getPathSize(1)); - int start = mp.getPathStart(1); - int end = mp.getPathEnd(1); - for (int i = start; i < end; i++) { - Point pp = mp.getPoint(i); - System.out.println("Point(" + i + ") = (" + pp.getX() + ", " - + pp.getY() + ")"); - } - - System.out.println("wkid: " + geom.getSpatialReference().getID()); - } - } - - @Test - public void testGeometryToJSON() { - Polygon geom = new Polygon(); - geom.startPath(new Point(-113, 34)); - geom.lineTo(new Point(-105, 34)); - geom.lineTo(new Point(-108, 40)); - - String outputPolygon1 = GeometryEngine.geometryToJson(-1, geom);// Test - // WKID - // == -1 - System.out.println("Geom JSON STRING is" + outputPolygon1); - String correctPolygon1 = "{\"rings\":[[[-113.0,34.0],[-105.0,34.0],[-108.0,40.0],[-113.0,34.0]]]}"; - - assertEquals(correctPolygon1, outputPolygon1); - - String outputPolygon2 = GeometryEngine.geometryToJson(4326, geom); - System.out.println("Geom JSON STRING is" + outputPolygon2); - - String correctPolygon2 = "{\"rings\":[[[-113.0,34.0],[-105.0,34.0],[-108.0,40.0],[-113.0,34.0]]],\"spatialReference\":{\"wkid\":4326}}"; - assertEquals(correctPolygon2, outputPolygon2); - } - - @Test - public void testGeometryToJSONOldID() throws Exception {// CR - Polygon geom = new Polygon(); - geom.startPath(new Point(-113, 34)); - geom.lineTo(new Point(-105, 34)); - geom.lineTo(new Point(-108, 40)); - String outputPolygon = GeometryEngine.geometryToJson( - SpatialReference.create(3857), geom);// Test WKID == -1 - String correctPolygon = "{\"rings\":[[[-113.0,34.0],[-105.0,34.0],[-108.0,40.0],[-113.0,34.0]]],\"spatialReference\":{\"wkid\":102100,\"latestWkid\":3857}}"; - assertTrue(outputPolygon.equals(correctPolygon)); - JsonFactory jf = new JsonFactory(); - JsonParser jp = jf.createJsonParser(outputPolygon); - jp.nextToken(); - MapGeometry mg = GeometryEngine.jsonToGeometry(jp); - @SuppressWarnings("unused") - int srId = mg.getSpatialReference().getID(); - @SuppressWarnings("unused") - int srOldId = mg.getSpatialReference().getOldID(); - Assert.assertTrue(mg.getSpatialReference().getID() == 3857); - Assert.assertTrue(mg.getSpatialReference().getLatestID() == 3857); - Assert.assertTrue(mg.getSpatialReference().getOldID() == 102100); - } + @Test + public void test3DPoint1() throws JsonParseException, IOException { + Point point1 = new Point(10.0, 20.0); + Point pointEmpty = new Point(); + { + JsonParser pointWebMerc1Parser = factory + .createJsonParser(GeometryEngine.geometryToJson( + spatialReferenceWebMerc1, point1)); + MapGeometry pointWebMerc1MP = GeometryEngine + .jsonToGeometry(pointWebMerc1Parser); + assertTrue(point1.getX() == ((Point) pointWebMerc1MP.getGeometry()) + .getX()); + assertTrue(point1.getY() == ((Point) pointWebMerc1MP.getGeometry()) + .getY()); + int srIdOri = spatialReferenceWebMerc1.getID(); + int srIdAfter = pointWebMerc1MP.getSpatialReference().getID(); + assertTrue(srIdOri == srIdAfter || srIdAfter == 3857); + + pointWebMerc1Parser = factory.createJsonParser(GeometryEngine + .geometryToJson(null, point1)); + pointWebMerc1MP = GeometryEngine + .jsonToGeometry(pointWebMerc1Parser); + assertTrue(null == pointWebMerc1MP.getSpatialReference()); + + String pointEmptyString = GeometryEngine.geometryToJson( + spatialReferenceWebMerc1, pointEmpty); + pointWebMerc1Parser = factory.createJsonParser(pointEmptyString); + + pointWebMerc1MP = GeometryEngine + .jsonToGeometry(pointWebMerc1Parser); + assertTrue(pointWebMerc1MP.getGeometry().isEmpty()); + int srIdOri2 = spatialReferenceWebMerc1.getID(); + int srIdAfter2 = pointWebMerc1MP.getSpatialReference().getID(); + assertTrue(srIdOri2 == srIdAfter2 || srIdAfter2 == 3857); + } + } + + @Test + public void test3DPoint2() throws JsonParseException, IOException { + { + Point point1 = new Point(10.0, 20.0); + JsonParser pointWebMerc2Parser = factory + .createJsonParser(GeometryEngine.geometryToJson( + spatialReferenceWebMerc2, point1)); + MapGeometry pointWebMerc2MP = GeometryEngine + .jsonToGeometry(pointWebMerc2Parser); + assertTrue(point1.getX() == ((Point) pointWebMerc2MP.getGeometry()) + .getX()); + assertTrue(point1.getY() == ((Point) pointWebMerc2MP.getGeometry()) + .getY()); + assertTrue(spatialReferenceWebMerc2.getLatestID() == pointWebMerc2MP + .getSpatialReference().getLatestID()); + } + } + + @Test + public void test3DPoint3() throws JsonParseException, IOException { + { + Point point1 = new Point(10.0, 20.0); + JsonParser pointWgs84Parser = factory + .createJsonParser(GeometryEngine.geometryToJson( + spatialReferenceWGS84, point1)); + MapGeometry pointWgs84MP = GeometryEngine + .jsonToGeometry(pointWgs84Parser); + assertTrue(point1.getX() == ((Point) pointWgs84MP.getGeometry()) + .getX()); + assertTrue(point1.getY() == ((Point) pointWgs84MP.getGeometry()) + .getY()); + assertTrue(spatialReferenceWGS84.getID() == pointWgs84MP + .getSpatialReference().getID()); + } + } + + @Test + public void testMultiPoint() throws JsonParseException, IOException { + MultiPoint multiPoint1 = new MultiPoint(); + multiPoint1.add(-97.06138, 32.837); + multiPoint1.add(-97.06133, 32.836); + multiPoint1.add(-97.06124, 32.834); + multiPoint1.add(-97.06127, 32.832); + + { + JsonParser mPointWgs84Parser = factory + .createJsonParser(GeometryEngine.geometryToJson( + spatialReferenceWGS84, multiPoint1)); + MapGeometry mPointWgs84MP = GeometryEngine + .jsonToGeometry(mPointWgs84Parser); + assertTrue(multiPoint1.getPointCount() == ((MultiPoint) mPointWgs84MP + .getGeometry()).getPointCount()); + assertTrue(multiPoint1.getPoint(0).getX() == ((MultiPoint) mPointWgs84MP + .getGeometry()).getPoint(0).getX()); + assertTrue(multiPoint1.getPoint(0).getY() == ((MultiPoint) mPointWgs84MP + .getGeometry()).getPoint(0).getY()); + int lastIndex = multiPoint1.getPointCount() - 1; + assertTrue(multiPoint1.getPoint(lastIndex).getX() == ((MultiPoint) mPointWgs84MP + .getGeometry()).getPoint(lastIndex).getX()); + assertTrue(multiPoint1.getPoint(lastIndex).getY() == ((MultiPoint) mPointWgs84MP + .getGeometry()).getPoint(lastIndex).getY()); + + assertTrue(spatialReferenceWGS84.getID() == mPointWgs84MP + .getSpatialReference().getID()); + + MultiPoint mPointEmpty = new MultiPoint(); + String mPointEmptyString = GeometryEngine.geometryToJson( + spatialReferenceWGS84, mPointEmpty); + mPointWgs84Parser = factory.createJsonParser(mPointEmptyString); + + mPointWgs84MP = GeometryEngine.jsonToGeometry(mPointWgs84Parser); + assertTrue(mPointWgs84MP.getGeometry().isEmpty()); + assertTrue(spatialReferenceWGS84.getID() == mPointWgs84MP + .getSpatialReference().getID()); + + } + } + + @Test + public void testPolyline() throws JsonParseException, IOException { + Polyline polyline = new Polyline(); + polyline.startPath(-97.06138, 32.837); + polyline.lineTo(-97.06133, 32.836); + polyline.lineTo(-97.06124, 32.834); + polyline.lineTo(-97.06127, 32.832); + + polyline.startPath(-97.06326, 32.759); + polyline.lineTo(-97.06298, 32.755); + + { + JsonParser polylinePathsWgs84Parser = factory + .createJsonParser(GeometryEngine.geometryToJson( + spatialReferenceWGS84, polyline)); + MapGeometry mPolylineWGS84MP = GeometryEngine + .jsonToGeometry(polylinePathsWgs84Parser); + + assertTrue(polyline.getPointCount() == ((Polyline) mPolylineWGS84MP + .getGeometry()).getPointCount()); + assertTrue(polyline.getPoint(0).getX() == ((Polyline) mPolylineWGS84MP + .getGeometry()).getPoint(0).getX()); + assertTrue(polyline.getPoint(0).getY() == ((Polyline) mPolylineWGS84MP + .getGeometry()).getPoint(0).getY()); + + assertTrue(polyline.getPathCount() == ((Polyline) mPolylineWGS84MP + .getGeometry()).getPathCount()); + assertTrue(polyline.getSegmentCount() == ((Polyline) mPolylineWGS84MP + .getGeometry()).getSegmentCount()); + assertTrue(polyline.getSegmentCount(0) == ((Polyline) mPolylineWGS84MP + .getGeometry()).getSegmentCount(0)); + assertTrue(polyline.getSegmentCount(1) == ((Polyline) mPolylineWGS84MP + .getGeometry()).getSegmentCount(1)); + + int lastIndex = polyline.getPointCount() - 1; + assertTrue(polyline.getPoint(lastIndex).getX() == ((Polyline) mPolylineWGS84MP + .getGeometry()).getPoint(lastIndex).getX()); + assertTrue(polyline.getPoint(lastIndex).getY() == ((Polyline) mPolylineWGS84MP + .getGeometry()).getPoint(lastIndex).getY()); + + assertTrue(spatialReferenceWGS84.getID() == mPolylineWGS84MP + .getSpatialReference().getID()); + + Polyline emptyPolyline = new Polyline(); + String emptyString = GeometryEngine.geometryToJson( + spatialReferenceWGS84, emptyPolyline); + mPolylineWGS84MP = GeometryEngine.jsonToGeometry(factory + .createJsonParser(emptyString)); + assertTrue(mPolylineWGS84MP.getGeometry().isEmpty()); + assertTrue(spatialReferenceWGS84.getID() == mPolylineWGS84MP + .getSpatialReference().getID()); + } + } + + @Test + public void testPolygon() throws JsonParseException, IOException { + Polygon polygon = new Polygon(); + polygon.startPath(-97.06138, 32.837); + polygon.lineTo(-97.06133, 32.836); + polygon.lineTo(-97.06124, 32.834); + polygon.lineTo(-97.06127, 32.832); + + polygon.startPath(-97.06326, 32.759); + polygon.lineTo(-97.06298, 32.755); + + { + JsonParser polygonPathsWgs84Parser = factory + .createJsonParser(GeometryEngine.geometryToJson( + spatialReferenceWGS84, polygon)); + MapGeometry mPolygonWGS84MP = GeometryEngine + .jsonToGeometry(polygonPathsWgs84Parser); + + assertTrue(polygon.getPointCount() + 1 == ((Polygon) mPolygonWGS84MP + .getGeometry()).getPointCount()); + assertTrue(polygon.getPoint(0).getX() == ((Polygon) mPolygonWGS84MP + .getGeometry()).getPoint(0).getX()); + assertTrue(polygon.getPoint(0).getY() == ((Polygon) mPolygonWGS84MP + .getGeometry()).getPoint(0).getY()); + + assertTrue(polygon.getPathCount() == ((Polygon) mPolygonWGS84MP + .getGeometry()).getPathCount()); + assertTrue(polygon.getSegmentCount() + 1 == ((Polygon) mPolygonWGS84MP + .getGeometry()).getSegmentCount()); + assertTrue(polygon.getSegmentCount(0) == ((Polygon) mPolygonWGS84MP + .getGeometry()).getSegmentCount(0)); + assertTrue(polygon.getSegmentCount(1) + 1 == ((Polygon) mPolygonWGS84MP + .getGeometry()).getSegmentCount(1)); + + int lastIndex = polygon.getPointCount() - 1; + assertTrue(polygon.getPoint(lastIndex).getX() == ((Polygon) mPolygonWGS84MP + .getGeometry()).getPoint(lastIndex).getX()); + assertTrue(polygon.getPoint(lastIndex).getY() == ((Polygon) mPolygonWGS84MP + .getGeometry()).getPoint(lastIndex).getY()); + + assertTrue(spatialReferenceWGS84.getID() == mPolygonWGS84MP + .getSpatialReference().getID()); + + Polygon emptyPolygon = new Polygon(); + String emptyPolygonString = GeometryEngine.geometryToJson( + spatialReferenceWGS84, emptyPolygon); + polygonPathsWgs84Parser = factory + .createJsonParser(emptyPolygonString); + mPolygonWGS84MP = GeometryEngine + .jsonToGeometry(polygonPathsWgs84Parser); + + assertTrue(mPolygonWGS84MP.getGeometry().isEmpty()); + assertTrue(spatialReferenceWGS84.getID() == mPolygonWGS84MP + .getSpatialReference().getID()); + } + } + + @Test + public void testEnvelope() throws JsonParseException, IOException { + Envelope envelope = new Envelope(); + envelope.setCoords(-109.55, 25.76, -86.39, 49.94); + + { + JsonParser envelopeWGS84Parser = factory + .createJsonParser(GeometryEngine.geometryToJson( + spatialReferenceWGS84, envelope)); + MapGeometry envelopeWGS84MP = GeometryEngine + .jsonToGeometry(envelopeWGS84Parser); + assertTrue(envelope.isEmpty() == envelopeWGS84MP.getGeometry() + .isEmpty()); + assertTrue(envelope.getXMax() == ((Envelope) envelopeWGS84MP + .getGeometry()).getXMax()); + assertTrue(envelope.getYMax() == ((Envelope) envelopeWGS84MP + .getGeometry()).getYMax()); + assertTrue(envelope.getXMin() == ((Envelope) envelopeWGS84MP + .getGeometry()).getXMin()); + assertTrue(envelope.getYMin() == ((Envelope) envelopeWGS84MP + .getGeometry()).getYMin()); + assertTrue(spatialReferenceWGS84.getID() == envelopeWGS84MP + .getSpatialReference().getID()); + + Envelope emptyEnvelope = new Envelope(); + String emptyEnvString = GeometryEngine.geometryToJson( + spatialReferenceWGS84, emptyEnvelope); + envelopeWGS84Parser = factory.createJsonParser(emptyEnvString); + envelopeWGS84MP = GeometryEngine + .jsonToGeometry(envelopeWGS84Parser); + + assertTrue(envelopeWGS84MP.getGeometry().isEmpty()); + assertTrue(spatialReferenceWGS84.getID() == envelopeWGS84MP + .getSpatialReference().getID()); + } + } + + @Test + public void testCR181369() throws JsonParseException, IOException { + // CR181369 + { + String jsonStringPointAndWKT = "{\"x\":10.0,\"y\":20.0,\"spatialReference\":{\"wkt\" : \"PROJCS[\\\"NAD83_UTM_zone_15N\\\",GEOGCS[\\\"GCS_North_American_1983\\\",DATUM[\\\"D_North_American_1983\\\",SPHEROID[\\\"GRS_1980\\\",6378137.0,298.257222101]],PRIMEM[\\\"Greenwich\\\",0.0],UNIT[\\\"Degree\\\",0.0174532925199433]],PROJECTION[\\\"Transverse_Mercator\\\"],PARAMETER[\\\"false_easting\\\",500000.0],PARAMETER[\\\"false_northing\\\",0.0],PARAMETER[\\\"central_meridian\\\",-93.0],PARAMETER[\\\"scale_factor\\\",0.9996],PARAMETER[\\\"latitude_of_origin\\\",0.0],UNIT[\\\"Meter\\\",1.0]]\"} }"; + JsonParser jsonParserPointAndWKT = factory + .createJsonParser(jsonStringPointAndWKT); + MapGeometry mapGeom2 = GeometryEngine + .jsonToGeometry(jsonParserPointAndWKT); + String jsonStringPointAndWKT2 = GeometryEngine.geometryToJson( + mapGeom2.getSpatialReference(), mapGeom2.getGeometry()); + JsonParser jsonParserPointAndWKT2 = factory + .createJsonParser(jsonStringPointAndWKT2); + MapGeometry mapGeom3 = GeometryEngine + .jsonToGeometry(jsonParserPointAndWKT2); + assertTrue(((Point) mapGeom2.getGeometry()).getX() == ((Point) mapGeom3 + .getGeometry()).getX()); + assertTrue(((Point) mapGeom2.getGeometry()).getY() == ((Point) mapGeom3 + .getGeometry()).getY()); + assertTrue(mapGeom2.getSpatialReference().getText() + .equals(mapGeom3.getSpatialReference().getText())); + assertTrue(mapGeom2.getSpatialReference().getID() == mapGeom3 + .getSpatialReference().getID()); + } + } + + @Test + public void testSpatialRef() throws JsonParseException, IOException { + // String jsonStringPt = + // "{\"x\":-20037508.342787,\"y\":20037508.342787},\"spatialReference\":{\"wkid\":102100}}"; + String jsonStringPt = "{\"x\":10.0,\"y\":20.0,\"spatialReference\":{\"wkid\": 102100}}";// 102100 + @SuppressWarnings("unused") + String jsonStringPt2 = "{\"x\":10.0,\"y\":20.0,\"spatialReference\":{\"wkid\":4326}}"; + String jsonStringMpt = "{ \"points\" : [ [-97.06138,32.837], [-97.06133,32.836], [-97.06124,32.834], [-97.06127,32.832] ], \"spatialReference\" : {\"wkid\" : 4326}}";// 4326 + String jsonStringMpt3D = "{\"hasZs\" : true,\"points\" : [ [-97.06138,32.837,35.0], [-97.06133,32.836,35.1], [-97.06124,32.834,35.2], [-97.06127,32.832,35.3] ],\"spatialReference\" : {\"wkid\" : 4326}}"; + String jsonStringPl = "{\"paths\" : [ [ [-97.06138,32.837], [-97.06133,32.836], [-97.06124,32.834], [-97.06127,32.832] ], [ [-97.06326,32.759], [-97.06298,32.755] ]],\"spatialReference\" : {\"wkid\" : 4326}}"; + String jsonStringPl3D = "{\"hasMs\" : true,\"paths\" : [[ [-97.06138,32.837,5], [-97.06133,32.836,6], [-97.06124,32.834,7], [-97.06127,32.832,8] ],[ [-97.06326,32.759], [-97.06298,32.755] ]],\"spatialReference\" : {\"wkid\" : 4326}}"; + String jsonStringPg = "{ \"rings\" :[ [ [-97.06138,32.837], [-97.06133,32.836], [-97.06124,32.834], [-97.06127,32.832], [-97.06138,32.837] ], [ [-97.06326,32.759], [-97.06298,32.755], [-97.06153,32.749], [-97.06326,32.759] ]], \"spatialReference\" : {\"wkt\" : \"\"}}"; + String jsonStringPg3D = "{\"hasZs\" : true,\"hasMs\" : true,\"rings\" : [ [ [-97.06138, 32.837, 35.1, 4], [-97.06133, 32.836, 35.2, 4.1], [-97.06124, 32.834, 35.3, 4.2], [-97.06127, 32.832, 35.2, 44.3], [-97.06138, 32.837, 35.1, 4] ],[ [-97.06326, 32.759, 35.4], [-97.06298, 32.755, 35.5], [-97.06153, 32.749, 35.6], [-97.06326, 32.759, 35.4] ]],\"spatialReference\" : {\"wkid\" : 4326}}"; + String jsonStringPg2 = "{ \"spatialReference\" : {\"wkid\" : 4326}, \"rings\" : [[[-118.35,32.81],[-118.42,32.806],[-118.511,32.892],[-118.35,32.81]]]}"; + String jsonStringPg3 = "{ \"spatialReference\": {\"layerName\":\"GAS_POINTS\",\"name\":null,\"sdesrid\":102100,\"wkid\":102100,\"wkt\":null}}"; + String jsonString2SpatialReferences = "{ \"spatialReference\": {\"layerName\":\"GAS_POINTS\",\"name\":null,\"sdesrid\":102100,\"wkid\":102100,\"wkt\":\"GEOGCS[\\\"GCS_WGS_1984\\\",DATUM[\\\"D_WGS_1984\\\",SPHEROID[\\\"WGS_1984\\\",6378137,298.257223563]],PRIMEM[\\\"Greenwich\\\",0],UNIT[\\\"Degree\\\",0.017453292519943295]]\"}}"; + String jsonString2SpatialReferences2 = "{ \"spatialReference\": {\"layerName\":\"GAS_POINTS\",\"name\":null,\"sdesrid\":10,\"wkid\":10,\"wkt\":\"GEOGCS[\\\"GCS_WGS_1984\\\",DATUM[\\\"D_WGS_1984\\\",SPHEROID[\\\"WGS_1984\\\",6378137,298.257223563]],PRIMEM[\\\"Greenwich\\\",0],UNIT[\\\"Degree\\\",0.017453292519943295]]\"}}"; + String jsonStringSR = "{\"wkid\" : 4326}"; + String jsonStringEnv = "{\"xmin\" : -109.55, \"ymin\" : 25.76, \"xmax\" : -86.39, \"ymax\" : 49.94,\"spatialReference\" : {\"wkid\" : 4326}}"; + String jsonStringHongKon = "{\"xmin\" : -122.55, \"ymin\" : 37.65, \"xmax\" : -122.28, \"ymax\" : 37.84,\"spatialReference\" : {\"wkid\" : 4326}}"; + @SuppressWarnings("unused") + String jsonStringWKT = " {\"wkt\" : \"GEOGCS[\\\"GCS_WGS_1984\\\",DATUM[\\\"D_WGS_1984\\\",SPHEROID[\\\"WGS_1984\\\",6378137,298.257223563]],PRIMEM[\\\"Greenwich\\\",0],UNIT[\\\"Degree\\\",0.017453292519943295]]\"}"; + String jsonStringInvalidWKID = "{\"x\":10.0,\"y\":20.0},\"spatialReference\":{\"wkid\":35253523}}"; + String jsonStringOregon = "{\"xmin\":7531831.219849482,\"ymin\":585702.9799639136,\"xmax\":7750143.589982405,\"ymax\":733289.6299999952,\"spatialReference\":{\"wkid\":102726}}"; + + JsonParser jsonParserPt = factory.createJsonParser(jsonStringPt); + JsonParser jsonParserMpt = factory.createJsonParser(jsonStringMpt); + JsonParser jsonParserMpt3D = factory.createJsonParser(jsonStringMpt3D); + JsonParser jsonParserPl = factory.createJsonParser(jsonStringPl); + JsonParser jsonParserPl3D = factory.createJsonParser(jsonStringPl3D); + JsonParser jsonParserPg = factory.createJsonParser(jsonStringPg); + JsonParser jsonParserPg3D = factory.createJsonParser(jsonStringPg3D); + JsonParser jsonParserPg2 = factory.createJsonParser(jsonStringPg2); + @SuppressWarnings("unused") + JsonParser jsonParserSR = factory.createJsonParser(jsonStringSR); + JsonParser jsonParserEnv = factory.createJsonParser(jsonStringEnv); + JsonParser jsonParserPg3 = factory.createJsonParser(jsonStringPg3); + @SuppressWarnings("unused") + JsonParser jsonParserCrazy1 = factory + .createJsonParser(jsonString2SpatialReferences); + @SuppressWarnings("unused") + JsonParser jsonParserCrazy2 = factory + .createJsonParser(jsonString2SpatialReferences2); + JsonParser jsonParserInvalidWKID = factory + .createJsonParser(jsonStringInvalidWKID); + @SuppressWarnings("unused") + JsonParser jsonParseHongKon = factory + .createJsonParser(jsonStringHongKon); + JsonParser jsonParseOregon = factory.createJsonParser(jsonStringOregon); + + MapGeometry mapGeom = GeometryEngine.jsonToGeometry(jsonParserPt); + // showProjectedGeometryInfo(mapGeom); + Assert.assertTrue(mapGeom.getSpatialReference().getID() == 102100); + + MapGeometry mapGeomOregon = GeometryEngine + .jsonToGeometry(jsonParseOregon); + Assert.assertTrue(mapGeomOregon.getSpatialReference().getID() == 102726); + + mapGeom = GeometryEngine.jsonToGeometry(jsonParserMpt); + Assert.assertTrue(mapGeom.getSpatialReference().getID() == 4326); + + mapGeom = GeometryEngine.jsonToGeometry(jsonParserMpt3D); + Assert.assertTrue(mapGeom.getSpatialReference().getID() == 4326); + { + Assert.assertTrue(((MultiPoint) mapGeom.getGeometry()).getPoint(0) + .getX() == -97.06138); + Assert.assertTrue(((MultiPoint) mapGeom.getGeometry()).getPoint(0) + .getY() == 32.837); + Assert.assertTrue(((MultiPoint) mapGeom.getGeometry()).getPoint(3) + .getX() == -97.06127); + Assert.assertTrue(((MultiPoint) mapGeom.getGeometry()).getPoint(3) + .getY() == 32.832); + } + // showProjectedGeometryInfo(mapGeom); + + mapGeom = GeometryEngine.jsonToGeometry(jsonParserPl); + Assert.assertTrue(mapGeom.getSpatialReference().getID() == 4326); + // showProjectedGeometryInfo(mapGeom); + + mapGeom = GeometryEngine.jsonToGeometry(jsonParserPl3D); + { + // [[ [-97.06138,32.837,5], [-97.06133,32.836,6], + // [-97.06124,32.834,7], [-97.06127,32.832,8] ], + // [ [-97.06326,32.759], [-97.06298,32.755] ]]"; + Assert.assertTrue(((Polyline) mapGeom.getGeometry()).getPoint(0) + .getX() == -97.06138); + Assert.assertTrue(((Polyline) mapGeom.getGeometry()).getPoint(0) + .getY() == 32.837); + int lastIndex = ((Polyline) mapGeom.getGeometry()).getPointCount() - 1; + Assert.assertTrue(((Polyline) mapGeom.getGeometry()).getPoint( + lastIndex).getX() == -97.06298);// -97.06153, 32.749 + Assert.assertTrue(((Polyline) mapGeom.getGeometry()).getPoint( + lastIndex).getY() == 32.755); + int lastIndexFirstLine = ((Polyline) mapGeom.getGeometry()) + .getPathEnd(0) - 1; + Assert.assertTrue(((Polyline) mapGeom.getGeometry()).getPoint( + lastIndexFirstLine).getX() == -97.06127);// -97.06153, + // 32.749 + Assert.assertTrue(((Polyline) mapGeom.getGeometry()).getPoint( + lastIndexFirstLine).getY() == 32.832); + } + + mapGeom = GeometryEngine.jsonToGeometry(jsonParserPg); + Assert.assertTrue(mapGeom.getSpatialReference() == null); + + mapGeom = GeometryEngine.jsonToGeometry(jsonParserPg3D); + { + Assert.assertTrue(((Polygon) mapGeom.getGeometry()).getPoint(0) + .getX() == -97.06138); + Assert.assertTrue(((Polygon) mapGeom.getGeometry()).getPoint(0) + .getY() == 32.837); + int lastIndex = ((Polygon) mapGeom.getGeometry()).getPointCount() - 1; + Assert.assertTrue(((Polygon) mapGeom.getGeometry()).getPoint( + lastIndex).getX() == -97.06153);// -97.06153, 32.749 + Assert.assertTrue(((Polygon) mapGeom.getGeometry()).getPoint( + lastIndex).getY() == 32.749); + } + + mapGeom = GeometryEngine.jsonToGeometry(jsonParserPg2); + Assert.assertTrue(mapGeom.getSpatialReference().getID() == 4326); + // showProjectedGeometryInfo(mapGeom); + + mapGeom = GeometryEngine.jsonToGeometry(jsonParserPg3); + Assert.assertTrue(mapGeom.getSpatialReference().getID() == 102100); + // showProjectedGeometryInfo(mapGeom); + + // mapGeom = GeometryEngine.jsonToGeometry(jsonParserCrazy1); + // Assert.assertTrue(mapGeom.getSpatialReference().getText().equals("")); + // showProjectedGeometryInfo(mapGeom); + + mapGeom = GeometryEngine.jsonToGeometry(jsonParserEnv); + Assert.assertTrue(mapGeom.getSpatialReference().getID() == 4326); + // showProjectedGeometryInfo(mapGeom); + // System.out.println("\n\nWKID: "+ + // SpatialReference.create(wkid).fromJson(jsonParserSR).getID()); + + try { + GeometryEngine.jsonToGeometry(jsonParserInvalidWKID); + } catch (Exception ex) { + System.out.print(ex.getMessage()); + } + } + + @Test + public void testMP2onCR175871() throws Exception { + Polygon pg = new Polygon(); + pg.startPath(-50, 10); + pg.lineTo(-50, 12); + pg.lineTo(-45, 12); + pg.lineTo(-45, 10); + + Polygon pg1 = new Polygon(); + pg1.startPath(-45, 10); + pg1.lineTo(-40, 10); + pg1.lineTo(-40, 8); + pg.add(pg1, false); + + SpatialReference spatialReference = SpatialReference.create(4326); + + try { + String jSonStr = GeometryEngine + .geometryToJson(spatialReference, pg); + JsonFactory jf = new JsonFactory(); + + JsonParser jp = jf.createJsonParser(jSonStr); + jp.nextToken(); + MapGeometry mg = GeometryEngine.jsonToGeometry(jp); + Geometry gm = mg.getGeometry(); + Assert.assertEquals(Geometry.Type.Polygon, gm.getType()); + Assert.assertTrue(mg.getSpatialReference().getID() == 4326); + + Polygon pgNew = (Polygon) gm; + + Assert.assertEquals(pgNew.getPathCount(), pg.getPathCount()); + Assert.assertEquals(pgNew.getPointCount(), pg.getPointCount()); + Assert.assertEquals(pgNew.getSegmentCount(), pg.getSegmentCount()); + + Assert.assertEquals(pgNew.getPoint(0).getX(), + pg.getPoint(0).getX(), 0.000000001); + Assert.assertEquals(pgNew.getPoint(1).getX(), + pg.getPoint(1).getX(), 0.000000001); + Assert.assertEquals(pgNew.getPoint(2).getX(), + pg.getPoint(2).getX(), 0.000000001); + Assert.assertEquals(pgNew.getPoint(3).getX(), + pg.getPoint(3).getX(), 0.000000001); + + Assert.assertEquals(pgNew.getPoint(0).getY(), + pg.getPoint(0).getY(), 0.000000001); + Assert.assertEquals(pgNew.getPoint(1).getY(), + pg.getPoint(1).getY(), 0.000000001); + Assert.assertEquals(pgNew.getPoint(2).getY(), + pg.getPoint(2).getY(), 0.000000001); + Assert.assertEquals(pgNew.getPoint(3).getY(), + pg.getPoint(3).getY(), 0.000000001); + } catch (Exception ex) { + String err = ex.getMessage(); + System.out.print(err); + throw ex; + } + } + + @Test + public static int fromJsonToWkid(JsonParser parser) + throws JsonParseException, IOException { + int wkid = 0; + if (parser.getCurrentToken() != JsonToken.START_OBJECT) { + return 0; + } + + while (parser.nextToken() != JsonToken.END_OBJECT) { + String fieldName = parser.getCurrentName(); + + if ("wkid".equals(fieldName)) { + parser.nextToken(); + wkid = parser.getIntValue(); + } + } + return wkid; + } + + @SuppressWarnings("unused") + private static void showProjectedGeometryInfo(MapGeometry mapGeom) { + System.out.println("\n"); + MapGeometry geom = mapGeom; + // while ((geom = geomCursor.next()) != null) { + + if (geom.getGeometry() instanceof Point) { + Point pnt = (Point) geom.getGeometry(); + System.out + .println("Point(" + pnt.getX() + " , " + pnt.getY() + ")"); + if (geom.getSpatialReference() == null) { + System.out.println("No spatial reference"); + } else { + System.out.println("wkid: " + + geom.getSpatialReference().getID()); + } + + } else if (geom.getGeometry() instanceof MultiPoint) { + MultiPoint mp = (MultiPoint) geom.getGeometry(); + System.out.println("Multipoint has " + mp.getPointCount() + + " points."); + + System.out.println("wkid: " + geom.getSpatialReference().getID()); + + } else if (geom.getGeometry() instanceof Polygon) { + Polygon mp = (Polygon) geom.getGeometry(); + System.out.println("Polygon has " + mp.getPointCount() + + " points and " + mp.getPathCount() + " parts."); + if (mp.getPathCount() > 1) { + System.out.println("Part start of 2nd segment : " + + mp.getPathStart(1)); + System.out.println("Part end of 2nd segment : " + + mp.getPathEnd(1)); + System.out.println("Part size of 2nd segment : " + + mp.getPathSize(1)); + + int start = mp.getPathStart(1); + int end = mp.getPathEnd(1); + for (int i = start; i < end; i++) { + Point pp = mp.getPoint(i); + System.out.println("Point(" + i + ") = (" + pp.getX() + + ", " + pp.getY() + ")"); + } + } + System.out.println("wkid: " + geom.getSpatialReference().getID()); + + } else if (geom.getGeometry() instanceof Polyline) { + Polyline mp = (Polyline) geom.getGeometry(); + System.out.println("Polyline has " + mp.getPointCount() + + " points and " + mp.getPathCount() + " parts."); + System.out.println("Part start of 2nd segment : " + + mp.getPathStart(1)); + System.out.println("Part end of 2nd segment : " + + mp.getPathEnd(1)); + System.out.println("Part size of 2nd segment : " + + mp.getPathSize(1)); + int start = mp.getPathStart(1); + int end = mp.getPathEnd(1); + for (int i = start; i < end; i++) { + Point pp = mp.getPoint(i); + System.out.println("Point(" + i + ") = (" + pp.getX() + ", " + + pp.getY() + ")"); + } + + System.out.println("wkid: " + geom.getSpatialReference().getID()); + } + } + + @Test + public void testGeometryToJSON() { + Polygon geom = new Polygon(); + geom.startPath(new Point(-113, 34)); + geom.lineTo(new Point(-105, 34)); + geom.lineTo(new Point(-108, 40)); + + String outputPolygon1 = GeometryEngine.geometryToJson(-1, geom);// Test + // WKID + // == -1 + System.out.println("Geom JSON STRING is" + outputPolygon1); + String correctPolygon1 = "{\"rings\":[[[-113,34],[-105,34],[-108,40],[-113,34]]]}"; + + assertEquals(correctPolygon1, outputPolygon1); + + String outputPolygon2 = GeometryEngine.geometryToJson(4326, geom); + System.out.println("Geom JSON STRING is" + outputPolygon2); + + String correctPolygon2 = "{\"rings\":[[[-113,34],[-105,34],[-108,40],[-113,34]]],\"spatialReference\":{\"wkid\":4326}}"; + assertEquals(correctPolygon2, outputPolygon2); + } + + @Test + public void testGeometryToJSONOldID() throws Exception {// CR + Polygon geom = new Polygon(); + geom.startPath(new Point(-113, 34)); + geom.lineTo(new Point(-105, 34)); + geom.lineTo(new Point(-108, 40)); + String outputPolygon = GeometryEngine.geometryToJson( + SpatialReference.create(3857), geom);// Test WKID == -1 + String correctPolygon = "{\"rings\":[[[-113,34],[-105,34],[-108,40],[-113,34]]],\"spatialReference\":{\"wkid\":102100,\"latestWkid\":3857}}"; + assertTrue(outputPolygon.equals(correctPolygon)); + JsonFactory jf = new JsonFactory(); + JsonParser jp = jf.createJsonParser(outputPolygon); + jp.nextToken(); + MapGeometry mg = GeometryEngine.jsonToGeometry(jp); + @SuppressWarnings("unused") + int srId = mg.getSpatialReference().getID(); + @SuppressWarnings("unused") + int srOldId = mg.getSpatialReference().getOldID(); + Assert.assertTrue(mg.getSpatialReference().getID() == 3857); + Assert.assertTrue(mg.getSpatialReference().getLatestID() == 3857); + Assert.assertTrue(mg.getSpatialReference().getOldID() == 102100); + } } diff --git a/unittest/com/esri/core/geometry/TestQuadTree.java b/unittest/com/esri/core/geometry/TestQuadTree.java index adca5ac6..3836ec13 100644 --- a/unittest/com/esri/core/geometry/TestQuadTree.java +++ b/unittest/com/esri/core/geometry/TestQuadTree.java @@ -34,6 +34,19 @@ public static void test1() { assertTrue(index == 6 || index == 8 || index == 14); element_handle = qtIter.next(); } + + Envelope2D envelope = new Envelope2D(34, 9, 66, 46); + Polygon queryPolygon = new Polygon(); + queryPolygon.addEnvelope(envelope, true); + + qtIter.resetIterator(queryline, 0.0); + + element_handle = qtIter.next(); + while (element_handle > 0) { + int index = quadtree.getElement(element_handle); + assertTrue(index == 6 || index == 8 || index == 14); + element_handle = qtIter.next(); + } } @Test From 7fd76dcca8c318096ef0d6c22b82821eb420726b Mon Sep 17 00:00:00 2001 From: Sergey Tolstov Date: Fri, 18 Oct 2013 12:16:21 -0700 Subject: [PATCH 071/196] Made some methods public (Point2D, JsonCursor). Bug fixes for internal tests. Added a new unit test. Need Point2D public. Need JsonCursor public. Found bugs with handling of the attributes (Zs and Ms). Added proper unit test. Found a bug in Clipper. --- src/com/esri/core/geometry/Clipper.java | 216 ++++++------ src/com/esri/core/geometry/Envelope.java | 167 ++++----- src/com/esri/core/geometry/Envelope2D.java | 24 +- src/com/esri/core/geometry/Geometry.java | 78 +---- src/com/esri/core/geometry/JsonCursor.java | 2 +- .../core/geometry/MultiVertexGeometry.java | 13 +- .../geometry/MultiVertexGeometryImpl.java | 62 +--- .../geometry/OperatorExportToGeoJson.java | 2 +- .../OperatorExportToGeoJsonLocal.java | 2 +- .../core/geometry/OperatorExportToJson.java | 2 +- .../geometry/OperatorExportToJsonLocal.java | 2 +- .../geometry/OperatorIntersectionCursor.java | 13 +- src/com/esri/core/geometry/Point.java | 73 ++-- src/com/esri/core/geometry/Point2D.java | 45 +-- src/com/esri/core/geometry/Segment.java | 113 +++--- .../esri/core/geometry/SpatialReference.java | 96 ++++-- .../VertexDescriptionDesignerImpl.java | 51 +++ .../esri/core/geometry/TestAttributes.java | 324 ++++++++++++++++++ 18 files changed, 796 insertions(+), 489 deletions(-) create mode 100644 unittest/com/esri/core/geometry/TestAttributes.java diff --git a/src/com/esri/core/geometry/Clipper.java b/src/com/esri/core/geometry/Clipper.java index f0eea524..fd064c95 100644 --- a/src/com/esri/core/geometry/Clipper.java +++ b/src/com/esri/core/geometry/Clipper.java @@ -592,7 +592,7 @@ void densifyAlongClipExtent_(double densify_dist) { } while (vertex != first_vertex); } } - + void splitSegments_(boolean b_axis_x, double clip_value) { // After the clipping, we could have produced unwanted segment overlaps // along the clipping envelope boundary. @@ -630,12 +630,8 @@ void splitSegments_(boolean b_axis_x, double clip_value) { return; } - // SORTDYNAMICARRAYEX(&sorted_vertices, int, 0, sorted_vertices.size(), - // Clipper_vertex_comparer, this); sorted_vertices.Sort(0, sorted_vertices.size(), new ClipperVertexComparer(this)); - // std::sort(sorted_vertices.get_ptr(), sorted_vertices.get_ptr() + - // sorted_vertices.size(), Clipper_vertex_comparer(this)); Point2D pt_tmp = new Point2D(); // forward declare for java port // optimization @@ -653,120 +649,124 @@ void splitSegments_(boolean b_axis_x, double clip_value) { int vert = sorted_vertices.get(index); m_shape.getXY(vert, pt); if (!pt.isEqual(pt_0)) { - if (index_0 != -1) { - // add new intervals, that started at pt_0 - for (int i = index_0; i < index; i++) { - int v = sorted_vertices.get(i); - int nextv = m_shape.getNextVertex(v); - int prevv = m_shape.getPrevVertex(v); - boolean bAdded = false; - if (compareVertices_(v, nextv) < 0) { - m_shape.getXY(nextv, pt_tmp); - if (b_axis_x ? pt_tmp.y == clip_value - : pt_tmp.x == clip_value) { - active_intervals.add(v); - bAdded = true; - m_shape.setUserIndex(v, node2, 1); - } + if (index_0 == -1) { + index_0 = index; + pt_0.setCoords(pt); + continue; + } + + // add new intervals, that started at pt_0 + for (int i = index_0; i < index; i++) { + int v = sorted_vertices.get(i); + int nextv = m_shape.getNextVertex(v); + int prevv = m_shape.getPrevVertex(v); + boolean bAdded = false; + if (compareVertices_(v, nextv) < 0) { + m_shape.getXY(nextv, pt_tmp); + if (b_axis_x ? pt_tmp.y == clip_value + : pt_tmp.x == clip_value) { + active_intervals.add(v); + bAdded = true; + m_shape.setUserIndex(v, node2, 1); } - if (compareVertices_(v, prevv) < 0) { - m_shape.getXY(prevv, pt_tmp); - if (b_axis_x ? pt_tmp.y == clip_value - : pt_tmp.x == clip_value) { - if (!bAdded) - active_intervals.add(v); - m_shape.setUserIndex(v, node1, 1); - } + } + if (compareVertices_(v, prevv) < 0) { + m_shape.getXY(prevv, pt_tmp); + if (b_axis_x ? pt_tmp.y == clip_value + : pt_tmp.x == clip_value) { + if (!bAdded) + active_intervals.add(v); + m_shape.setUserIndex(v, node1, 1); } } + } - // Split all active intervals at new point - for (int ia = 0, na = active_intervals.size(); ia < na; ia++) { - int v = active_intervals.get(ia); - int n_1 = m_shape.getUserIndex(v, node1); - int n_2 = m_shape.getUserIndex(v, node2); - if (n_1 == 1) { - int prevv = m_shape.getPrevVertex(v); - m_shape.getXY(prevv, pt_1); - double[] t = new double[1]; - t[0] = 0; - if (!pt_1.isEqual(pt)) {// Split the active segment - double active_segment_length = Point2D - .distance(pt_0, pt_1); - t[0] = Point2D.distance(pt_1, pt) - / active_segment_length; - assert (t[0] >= 0 && t[0] <= 1.0); - if (t[0] == 0) - t[0] = NumberUtils.doubleEps();// some - // roundoff - // issue. - // split - // anyway. - else if (t[0] == 1.0) { - t[0] = 1.0 - NumberUtils.doubleEps();// some - // roundoff - // issue. - // split - // anyway. - assert (t[0] != 1.0); - } - - int split_count = m_shape.splitSegment(prevv, - t, 1); - assert (split_count > 0); - int v_1 = m_shape.getPrevVertex(v); - m_shape.setXY(v_1, pt); - new_active_intervals.add(v_1); - m_shape.setUserIndex(v_1, node1, 1); - m_shape.setUserIndex(v_1, node2, -1); - } else { - // The active segment ends at the current point. - // We skip it, and it goes away. + // Split all active intervals at new point + for (int ia = 0, na = active_intervals.size(); ia < na; ia++) { + int v = active_intervals.get(ia); + int n_1 = m_shape.getUserIndex(v, node1); + int n_2 = m_shape.getUserIndex(v, node2); + if (n_1 == 1) { + int prevv = m_shape.getPrevVertex(v); + m_shape.getXY(prevv, pt_1); + double[] t = new double[1]; + t[0] = 0; + if (!pt_1.isEqual(pt)) {// Split the active segment + double active_segment_length = Point2D + .distance(pt_0, pt_1); + t[0] = Point2D.distance(pt_1, pt) + / active_segment_length; + assert (t[0] >= 0 && t[0] <= 1.0); + if (t[0] == 0) + t[0] = NumberUtils.doubleEps();// some + // roundoff + // issue. + // split + // anyway. + else if (t[0] == 1.0) { + t[0] = 1.0 - NumberUtils.doubleEps();// some + // roundoff + // issue. + // split + // anyway. + assert (t[0] != 1.0); } - } - if (n_2 == 1) { - int nextv = m_shape.getNextVertex(v); - m_shape.getXY(nextv, pt_1); - double[] t = new double[1]; - t[0] = 0; - if (!pt_1.isEqual(pt)) { - double active_segment_length = Point2D - .distance(pt_0, pt_1); - t[0] = Point2D.distance(pt_0, pt) - / active_segment_length; - assert (t[0] >= 0 && t[0] <= 1.0); - if (t[0] == 0) - t[0] = NumberUtils.doubleEps();// some - // roundoff - // issue. - // split - // anyway. - else if (t[0] == 1.0) { - t[0] = 1.0 - NumberUtils.doubleEps();// some - // roundoff - // issue. - // split - // anyway. - assert (t[0] != 1.0); - } - int split_count = m_shape.splitSegment(v, t, 1); - assert (split_count > 0); - int v_1 = m_shape.getNextVertex(v); - m_shape.setXY(v_1, pt); - new_active_intervals.add(v_1); - m_shape.setUserIndex(v_1, node1, -1); - m_shape.setUserIndex(v_1, node2, 1); - } + int split_count = m_shape.splitSegment(prevv, + t, 1); + assert (split_count > 0); + int v_1 = m_shape.getPrevVertex(v); + m_shape.setXY(v_1, pt); + new_active_intervals.add(v_1); + m_shape.setUserIndex(v_1, node1, 1); + m_shape.setUserIndex(v_1, node2, -1); + } else { + // The active segment ends at the current point. + // We skip it, and it goes away. } } + if (n_2 == 1) { + int nextv = m_shape.getNextVertex(v); + m_shape.getXY(nextv, pt_1); + double[] t = new double[1]; + t[0] = 0; + if (!pt_1.isEqual(pt)) { + double active_segment_length = Point2D + .distance(pt_0, pt_1); + t[0] = Point2D.distance(pt_0, pt) + / active_segment_length; + assert (t[0] >= 0 && t[0] <= 1.0); + if (t[0] == 0) + t[0] = NumberUtils.doubleEps();// some + // roundoff + // issue. + // split + // anyway. + else if (t[0] == 1.0) { + t[0] = 1.0 - NumberUtils.doubleEps();// some + // roundoff + // issue. + // split + // anyway. + assert (t[0] != 1.0); + } - AttributeStreamOfInt32 tmp = active_intervals; - active_intervals = new_active_intervals; - new_active_intervals = tmp; - new_active_intervals.clear(false); + int split_count = m_shape.splitSegment(v, t, 1); + assert (split_count > 0); + int v_1 = m_shape.getNextVertex(v); + m_shape.setXY(v_1, pt); + new_active_intervals.add(v_1); + m_shape.setUserIndex(v_1, node1, -1); + m_shape.setUserIndex(v_1, node2, 1); + } + } } + AttributeStreamOfInt32 tmp = active_intervals; + active_intervals = new_active_intervals; + new_active_intervals = tmp; + new_active_intervals.clear(false); + index_0 = index; pt_0.setCoords(pt); } diff --git a/src/com/esri/core/geometry/Envelope.java b/src/com/esri/core/geometry/Envelope.java index d9336182..b684cc23 100644 --- a/src/com/esri/core/geometry/Envelope.java +++ b/src/com/esri/core/geometry/Envelope.java @@ -37,7 +37,7 @@ public final class Envelope extends Geometry implements Serializable { Envelope2D m_envelope = new Envelope2D(); - double[] m_attributes;// use doubles to store everything (int64 are bitcast) + double[] m_attributes;// use doubles to store everything /** * Creates an envelope by defining its center, width, and height. @@ -410,9 +410,13 @@ public void copyTo(Geometry dst) { dst._touch(); envDst.m_description = m_description; envDst.m_envelope.setCoords(m_envelope); - envDst._resizeAttributes(m_description._getTotalComponents() - 2); - _attributeCopy(m_attributes, 0, envDst.m_attributes, 0, - (m_description._getTotalComponents() - 2) * 2); + envDst.m_attributes = null; + if (m_attributes != null) + { + envDst._ensureAttributes(); + System.arraycopy(m_attributes, 0, envDst.m_attributes, 0, + (m_description._getTotalComponents() - 2) * 2); + } } @Override @@ -597,6 +601,7 @@ int getEndPointOffset(VertexDescription descr, int end_point) { throw new IllegalArgumentException(); int attribute_index = m_description.getAttributeIndex(semantics); + _ensureAttributes(); if (attribute_index >= 0) { return m_attributes[getEndPointOffset(m_description, end_point) + m_description.getPointAttributeOffset_(attribute_index) @@ -622,6 +627,8 @@ void setAttributeAsDblImpl_(int end_point, int semantics, int ordinate, else m_envelope.xmin = value; } + + return; } int ncomps = VertexDescription.getComponentCount(semantics); @@ -629,87 +636,88 @@ void setAttributeAsDblImpl_(int end_point, int semantics, int ordinate, throw new IllegalArgumentException(); addAttribute(semantics); + _ensureAttributes(); int attribute_index = m_description.getAttributeIndex(semantics); m_attributes[getEndPointOffset(m_description, end_point) + m_description.getPointAttributeOffset_(attribute_index) - 2 + ordinate] = value; } - void _resizeAttributes(int newSize) {// copied from - // Segment::_ResizeAttributes + void _ensureAttributes() { _touch(); - if (m_attributes == null) { - m_attributes = new double[newSize * 2]; - } else if (m_attributes.length < newSize * 2) { - double[] newBuffer = new double[newSize * 2]; - System.arraycopy(m_attributes, 0, newBuffer, 0, m_attributes.length); - m_attributes = newBuffer; + if (m_attributes == null && m_description._getTotalComponents() > 2) { + m_attributes = new double[(m_description._getTotalComponents() - 2) * 2]; + int offset0 = _getEndPointOffset(m_description, 0); + int offset1 = _getEndPointOffset(m_description, 1); + + int j = 0; + for (int i = 1, n = m_description.getAttributeCount(); i < n; i++) { + int semantics = m_description.getSemantics(i); + int nords = VertexDescription.getComponentCount(semantics); + double d = VertexDescription.getDefaultValue(semantics); + for (int ord = 0; ord < nords; ord++) + { + m_attributes[offset0 + j] = d; + m_attributes[offset1 + j] = d; + j++; + } + } } } @Override - void _beforeDropAttributeImpl(int semantics) {// copied from - // Segment::_BeforeDropAttributeImpl - if (m_envelope.isEmpty()) + protected void _assignVertexDescriptionImpl(VertexDescription newDescription) { + if (m_attributes == null) { + m_description = newDescription; return; - - // _ASSERT(semantics != enum_value2(VertexDescription, Semantics, - // POSITION)); - int attributeIndex = m_description.getAttributeIndex(semantics); - int offset = m_description._getPointAttributeOffset(attributeIndex) - 2; - int comps = VertexDescription.getComponentCount(semantics); - int totalCompsOld = m_description._getTotalComponents() - 2; - if (totalCompsOld > comps) { - int offset0 = _getEndPointOffset(0); - for (int i = offset + comps; i < totalCompsOld * 2; i++) - m_attributes[offset0 + i - comps] = m_attributes[offset0 + i]; - - int offset1 = _getEndPointOffset(1) - comps; // -comp is for deleted - // attribute of - // start vertex - for (int i = offset + comps; i < totalCompsOld; i++) - m_attributes[offset1 + i - comps] = m_attributes[offset1 + i]; - } - } - - @Override - void _afterAddAttributeImpl(int semantics) {// copied from - // Segment::_AfterAddAttributeImpl - int attributeIndex = m_description.getAttributeIndex(semantics); - int offset = m_description._getPointAttributeOffset(attributeIndex) - 2; - int comps = VertexDescription.getComponentCount(semantics); - int totalComps = m_description._getTotalComponents() - 2; - _resizeAttributes(totalComps); - int totalCompsOld = totalComps - comps; // the total number of - // components before resize. - - int offset0 = _getEndPointOffset(0); - int offset1 = _getEndPointOffset(1); - int offset1old = offset1 - comps; - for (int i = totalCompsOld - 1; i >= 0; i--) {// correct the position of - // the End attributes - m_attributes[offset1 + i] = m_attributes[offset1old + i]; } - - // move attributes for start end end points that go after the insertion - // point - for (int i = totalComps - 1; i >= offset + comps; i--) { - m_attributes[offset0 + i] = m_attributes[offset0 + i - comps]; - m_attributes[offset1 + i] = m_attributes[offset1 + i - comps]; + + if (newDescription._getTotalComponents() > 2) { + int[] mapping = VertexDescriptionDesignerImpl.mapAttributes(newDescription, m_description); + + double[] newAttributes = new double[(newDescription._getTotalComponents() - 2) * 2]; + + int old_offset0 = _getEndPointOffset(m_description, 0); + int old_offset1 = _getEndPointOffset(m_description, 1); + + int new_offset0 = _getEndPointOffset(newDescription, 0); + int new_offset1 = _getEndPointOffset(newDescription, 1); + + int j = 0; + for (int i = 1, n = newDescription.getAttributeCount(); i < n; i++) { + int semantics = newDescription.getSemantics(i); + int nords = VertexDescription.getComponentCount(semantics); + if (mapping[i] == -1) + { + double d = VertexDescription.getDefaultValue(semantics); + for (int ord = 0; ord < nords; ord++) + { + newAttributes[new_offset0 + j] = d; + newAttributes[new_offset1 + j] = d; + j++; + } + } + else { + int m = mapping[i]; + int offset = m_description._getPointAttributeOffset(m) - 2; + for (int ord = 0; ord < nords; ord++) + { + newAttributes[new_offset0 + j] = m_attributes[old_offset0 + offset]; + newAttributes[new_offset1 + j] = m_attributes[old_offset1 + offset]; + j++; + offset++; + } + } + + } + + m_attributes = newAttributes; } - - // initialize added attribute to the default value. - double dv = VertexDescription.getDefaultValue(semantics); - for (int i = 0; i < comps; i++) { - m_attributes[offset0 + offset + i] = dv; - m_attributes[offset1 + offset + i] = dv; + else { + m_attributes = null; } - } - - static void _attributeCopy(double[] src, int srcStart, double[] dst, - int dstStart, int count) { - if (count > 0) - System.arraycopy(src, srcStart, dst, dstStart, count); + + m_description = newDescription; } double _getAttributeAsDbl(int endPoint, int semantics, int ordinate) { @@ -733,10 +741,8 @@ static void _attributeCopy(double[] src, int srcStart, double[] dst, int attributeIndex = m_description.getAttributeIndex(semantics); if (attributeIndex >= 0) { - if (null != m_attributes) - _resizeAttributes(m_description._getTotalComponents() - 2); - - return m_attributes[_getEndPointOffset(endPoint) + _ensureAttributes(); + return m_attributes[_getEndPointOffset(m_description, endPoint) + m_description._getPointAttributeOffset(attributeIndex) - 2 + ordinate]; } else @@ -760,6 +766,8 @@ void _setAttributeAsDbl(int endPoint, int semantics, int ordinate, else m_envelope.xmin = value; } + + return; } int ncomps = VertexDescription.getComponentCount(semantics); @@ -769,14 +777,13 @@ void _setAttributeAsDbl(int endPoint, int semantics, int ordinate, if (!hasAttribute(semantics)) { if (VertexDescription.isDefaultValue(semantics, value)) return; + addAttribute(semantics); } int attributeIndex = m_description.getAttributeIndex(semantics); - if (null == m_attributes) - _resizeAttributes(m_description._getTotalComponents() - 2); - - m_attributes[_getEndPointOffset(endPoint) + _ensureAttributes(); + m_attributes[_getEndPointOffset(m_description, endPoint) + m_description._getPointAttributeOffset(attributeIndex) - 2 + ordinate] = value; } @@ -785,8 +792,8 @@ int _getAttributeAsInt(int endPoint, int semantics, int ordinate) { return (int) _getAttributeAsDbl(endPoint, semantics, ordinate); } - int _getEndPointOffset(int endPoint) { - return endPoint * (m_description._getTotalComponents() - 2); + static int _getEndPointOffset(VertexDescription vd, int endPoint) { + return endPoint * (vd._getTotalComponents() - 2); } boolean isIntersecting(Envelope2D other) { diff --git a/src/com/esri/core/geometry/Envelope2D.java b/src/com/esri/core/geometry/Envelope2D.java index 2ea01b86..a3f09cf5 100644 --- a/src/com/esri/core/geometry/Envelope2D.java +++ b/src/com/esri/core/geometry/Envelope2D.java @@ -36,13 +36,13 @@ public final class Envelope2D { private final int XMASK = 3; private final int YMASK = 12; - double xmin; + public double xmin; - double ymin; + public double ymin; - double xmax; + public double xmax; - double ymax; + public double ymax; public static Envelope2D construct(double _xmin, double _ymin, double _xmax, double _ymax) { @@ -987,26 +987,25 @@ Point2D _snapClip(Point2D pt)// clips the point if it is outside, then snaps return new Point2D(x, y); } - boolean isPointOnBoundary(Point2D pt, double tolerance) { + public boolean isPointOnBoundary(Point2D pt, double tolerance) { return Math.abs(pt.x - xmin) <= tolerance || Math.abs(pt.x - xmax) <= tolerance || Math.abs(pt.y - ymin) <= tolerance || Math.abs(pt.y - ymax) <= tolerance; } - double distance(/* const */Envelope2D other) /* const */ + public double distance(/* const */Envelope2D other) { return Math.sqrt(sqrDistance(other)); } - double distance(/* const */Point2D pt2D) /* const */ + public double distance(Point2D pt2D) { return Math.sqrt(sqrDistance(pt2D)); } - double sqrDistance(/* const */Envelope2D other) /* const */ + public double sqrDistance(Envelope2D other) { - // code from SG's windist double dx = 0; double dy = 0; double nn; @@ -1030,9 +1029,8 @@ boolean isPointOnBoundary(Point2D pt, double tolerance) { return dx * dx + dy * dy; } - double sqrDistance(/* const */Point2D pt2D) /* const */ + public double sqrDistance(Point2D pt2D) { - // code from SG's windist double dx = 0; double dy = 0; double nn; @@ -1056,7 +1054,7 @@ boolean isPointOnBoundary(Point2D pt, double tolerance) { return dx * dx + dy * dy; } - void queryIntervalX(Envelope1D env1D) /* const */ + public void queryIntervalX(Envelope1D env1D) { if (isEmpty()) { env1D.setEmpty(); @@ -1065,7 +1063,7 @@ void queryIntervalX(Envelope1D env1D) /* const */ } } - void queryIntervalY(Envelope1D env1D) /* const */ + public void queryIntervalY(Envelope1D env1D) { if (isEmpty()) { env1D.setEmpty(); diff --git a/src/com/esri/core/geometry/Geometry.java b/src/com/esri/core/geometry/Geometry.java index 5684368d..0257579c 100644 --- a/src/com/esri/core/geometry/Geometry.java +++ b/src/com/esri/core/geometry/Geometry.java @@ -158,19 +158,10 @@ void assignVertexDescription(VertexDescription src) { if (src == m_description) return; - for (int i = Semantics.POSITION; i < Semantics.MAXSEMANTICS; i++) { - if (m_description.hasAttribute(i) && (!src.hasAttribute(i))) - _beforeDropAttributeImpl(i); - } - - VertexDescription olddescription = m_description; - m_description = src; - - for (int i = Semantics.POSITION; i < Semantics.MAXSEMANTICS; i++) { - if (!olddescription.hasAttribute(i) && src.hasAttribute(i)) - _afterAddAttributeImpl(i); - } + _assignVertexDescriptionImpl(src); } + + protected abstract void _assignVertexDescriptionImpl(VertexDescription src); /** * Merges the new VertexDescription by adding missing attributes from the @@ -183,30 +174,11 @@ void mergeVertexDescription(VertexDescription src) { return; // check if we need to do anything (if the src has same attributes) - boolean bNeedAction = false; - VertexDescriptionDesignerImpl vdd = null; - for (int i = Semantics.POSITION; i < Semantics.MAXSEMANTICS; i++) { - if (!m_description.hasAttribute(i) && src.hasAttribute(i)) { - if (!bNeedAction) { - bNeedAction = true; - vdd = new VertexDescriptionDesignerImpl(m_description); - } - - vdd.addAttribute(i); - } - } - - if (!bNeedAction) + VertexDescription newdescription = VertexDescriptionDesignerImpl.getMergedVertexDescription(m_description, src); + if (newdescription == m_description) return; - - VertexDescription olddescription = m_description; - m_description = vdd.getDescription(); - for (int i = Semantics.POSITION; i < Semantics.MAXSEMANTICS; i++) { - if (!olddescription.hasAttribute(i) - && m_description.hasAttribute(i)) { - _afterAddAttributeImpl(i); - } - } + + _assignVertexDescriptionImpl(newdescription); } /** @@ -225,16 +197,9 @@ public void addAttribute(int semantics) { _touch(); if (m_description.hasAttribute(semantics)) return; - - // Create a designer instance out of existing description. - VertexDescriptionDesignerImpl vdd = new VertexDescriptionDesignerImpl( - m_description); - // Add the attribute to the description designer. - vdd.addAttribute(semantics); - // Obtain new description. - m_description = vdd.getDescription(); - // Let it be known we have added the attribute. - _afterAddAttributeImpl(semantics); + + VertexDescription newvd = VertexDescriptionDesignerImpl.getMergedVertexDescription(m_description, semantics); + _assignVertexDescriptionImpl(newvd); } /** @@ -248,15 +213,8 @@ public void dropAttribute(int semantics) { if (!m_description.hasAttribute(semantics)) return; - // Create a designer instance out of existing description. - VertexDescriptionDesignerImpl vdd = new VertexDescriptionDesignerImpl( - m_description); - // Remove the attribute from the description designer. - vdd.removeAttribute(semantics); - // Let know we are dropping the attribute. - _beforeDropAttributeImpl(semantics); - // Obtain new description. - m_description = vdd.getDescription(); + VertexDescription newvd = VertexDescriptionDesignerImpl.removeSemanticsFromVertexDescription(m_description, semantics); + _assignVertexDescriptionImpl(newvd); } /** @@ -383,18 +341,6 @@ public double calculateLength2D() { return 0; } - /* - * Called before the new description pointer is set. Caller is supposed to - * get rid of the Geometry attribute value. - */ - abstract void _beforeDropAttributeImpl(int semantics); - - /* - * Called after the new description pointer is set. Caller is supposed to - * add the Geometry attribute value. - */ - abstract void _afterAddAttributeImpl(int semantics); - protected Object _getImpl() { throw new RuntimeException("invalid call"); } diff --git a/src/com/esri/core/geometry/JsonCursor.java b/src/com/esri/core/geometry/JsonCursor.java index eba7987d..30e1beed 100644 --- a/src/com/esri/core/geometry/JsonCursor.java +++ b/src/com/esri/core/geometry/JsonCursor.java @@ -26,7 +26,7 @@ /** * An abstract Json String Cursor class. */ -abstract class JsonCursor { +public abstract class JsonCursor { /** * Moves the cursor to the next string. Returns null when reached the end. diff --git a/src/com/esri/core/geometry/MultiVertexGeometry.java b/src/com/esri/core/geometry/MultiVertexGeometry.java index cbbf9b9d..b8102c0c 100644 --- a/src/com/esri/core/geometry/MultiVertexGeometry.java +++ b/src/com/esri/core/geometry/MultiVertexGeometry.java @@ -36,17 +36,10 @@ abstract class MultiVertexGeometry extends Geometry implements Serializable { @Override - void _afterAddAttributeImpl(int semantics) { - // TODO Auto-generated method stub - - } - - @Override - void _beforeDropAttributeImpl(int semantics) { - // TODO Auto-generated method stub - + protected void _assignVertexDescriptionImpl(VertexDescription newDescription) { + throw new GeometryException("invalid call"); } - + // Multipart methods: /** * Returns the total vertex count in this Geometry. diff --git a/src/com/esri/core/geometry/MultiVertexGeometryImpl.java b/src/com/esri/core/geometry/MultiVertexGeometryImpl.java index 80c69315..60836fa2 100644 --- a/src/com/esri/core/geometry/MultiVertexGeometryImpl.java +++ b/src/com/esri/core/geometry/MultiVertexGeometryImpl.java @@ -424,59 +424,35 @@ public void setAttributeStreamRef(int semantics, AttributeStreamBase stream) { notifyModified(DirtyFlags.DirtyAll); } - // Checked vs. Jan 11, 2011 @Override - void _beforeDropAttributeImpl(int semantics) { - _touch(); - int attributeIndex = m_description.getAttributeIndex(semantics); - // _ASSERT(attributeIndex >= 0); - - AttributeStreamBase[] newAttributes = null; - if (m_vertexAttributes != null) { - newAttributes = new AttributeStreamBase[m_description - .getAttributeCount() - 1]; - - for (int i = 0; i < attributeIndex; i++) - newAttributes[i] = m_vertexAttributes[i]; - for (int i = attributeIndex + 1; i < m_description - .getAttributeCount(); i++) - newAttributes[i - 1] = m_vertexAttributes[i]; - } - - m_vertexAttributes = newAttributes; // late assignment to try to stay - // valid if out-of memory happens. - notifyModified(DirtyFlags.DirtyAll); - } - - // Checked vs. Jan 11, 2011 - @Override - void _afterAddAttributeImpl(int semantics) { - _touch(); - int attributeIndex = m_description.getAttributeIndex(semantics); - // _ASSERT(attributeIndex >= 0); - + protected void _assignVertexDescriptionImpl(VertexDescription newDescription) { AttributeStreamBase[] newAttributes = null; + if (m_vertexAttributes != null) { - newAttributes = new AttributeStreamBase[m_description - .getAttributeCount()]; + int[] mapping = VertexDescriptionDesignerImpl.mapAttributes( + newDescription, m_description); + + newAttributes = new AttributeStreamBase[newDescription + .getAttributeCount()]; + + for (int i = 0, n = newDescription.getAttributeCount(); i < n; i++) { + if (mapping[i] != -1) { + int m = mapping[i]; + newAttributes[i] = m_vertexAttributes[m]; + } - if (m_vertexAttributes != null) { - // Do not create new stream The stream will be created when one - // queries or sets a point to it. - for (int i = 0; i < attributeIndex; i++) - newAttributes[i] = m_vertexAttributes[i]; - for (int i = attributeIndex + 1; i < m_description - .getAttributeCount(); i++) - newAttributes[i] = m_vertexAttributes[i - 1]; } } - + else { + //if there are no streams we do not create them + } + + m_description = newDescription; m_vertexAttributes = newAttributes; // late assignment to try to stay - // valid if out-of memory happens. m_reservedPointCount = -1;// we need to recreate the new attribute then notifyModified(DirtyFlags.DirtyAll); } - + // Checked vs. Jan 11, 2011 protected void _updateEnvelope(Envelope2D env) { _updateAllDirtyIntervals(true); diff --git a/src/com/esri/core/geometry/OperatorExportToGeoJson.java b/src/com/esri/core/geometry/OperatorExportToGeoJson.java index 2eeba10f..9a1ac765 100644 --- a/src/com/esri/core/geometry/OperatorExportToGeoJson.java +++ b/src/com/esri/core/geometry/OperatorExportToGeoJson.java @@ -33,7 +33,7 @@ public Type getType() { return Type.ExportToGeoJson; } - abstract JsonCursor execute(SpatialReference spatialReference, GeometryCursor geometryCursor); + public abstract JsonCursor execute(SpatialReference spatialReference, GeometryCursor geometryCursor); public abstract String execute(SpatialReference spatialReference, Geometry geometry); diff --git a/src/com/esri/core/geometry/OperatorExportToGeoJsonLocal.java b/src/com/esri/core/geometry/OperatorExportToGeoJsonLocal.java index 3e0ccb17..0abbadb5 100644 --- a/src/com/esri/core/geometry/OperatorExportToGeoJsonLocal.java +++ b/src/com/esri/core/geometry/OperatorExportToGeoJsonLocal.java @@ -24,7 +24,7 @@ class OperatorExportToGeoJsonLocal extends OperatorExportToGeoJson { @Override - JsonCursor execute(SpatialReference spatialReference, GeometryCursor geometryCursor) { + public JsonCursor execute(SpatialReference spatialReference, GeometryCursor geometryCursor) { return new OperatorExportToGeoJsonCursor(false, spatialReference, geometryCursor); } diff --git a/src/com/esri/core/geometry/OperatorExportToJson.java b/src/com/esri/core/geometry/OperatorExportToJson.java index 60729155..9b548d4f 100644 --- a/src/com/esri/core/geometry/OperatorExportToJson.java +++ b/src/com/esri/core/geometry/OperatorExportToJson.java @@ -42,7 +42,7 @@ public Type getType() { * * @return Returns a JsonCursor. */ - abstract JsonCursor execute(SpatialReference spatialReference, + public abstract JsonCursor execute(SpatialReference spatialReference, GeometryCursor geometryCursor); /** diff --git a/src/com/esri/core/geometry/OperatorExportToJsonLocal.java b/src/com/esri/core/geometry/OperatorExportToJsonLocal.java index 8b93cb1b..5c5f819d 100644 --- a/src/com/esri/core/geometry/OperatorExportToJsonLocal.java +++ b/src/com/esri/core/geometry/OperatorExportToJsonLocal.java @@ -28,7 +28,7 @@ class OperatorExportToJsonLocal extends OperatorExportToJson { @Override - JsonCursor execute(SpatialReference spatialReference, + public JsonCursor execute(SpatialReference spatialReference, GeometryCursor geometryCursor) { return new OperatorExportToJsonCursor(spatialReference, geometryCursor); } diff --git a/src/com/esri/core/geometry/OperatorIntersectionCursor.java b/src/com/esri/core/geometry/OperatorIntersectionCursor.java index 3d07cc5a..e23c3e8a 100644 --- a/src/com/esri/core/geometry/OperatorIntersectionCursor.java +++ b/src/com/esri/core/geometry/OperatorIntersectionCursor.java @@ -483,18 +483,7 @@ Geometry tryFastIntersectPolylinePolygon_(Polyline polyline, Polygon polygon) { polygonQuadTree = accel.getQuadTree(); if (polygonQuadTree == null && polygonImpl.getPointCount() > 20) { - Envelope2D env = new Envelope2D(); - polygon.queryEnvelope2D(env); - QuadTreeImpl polygonQuadTreeNew = new QuadTreeImpl(env, 8); - while (polygonIter.nextPath()) { - while (polygonIter.hasNextSegment()) { - Segment seg = polygonIter.nextSegment(); - seg.queryEnvelope2D(env); - polygonQuadTreeNew.insert(polygonIter.getStartPointIndex(), - env); - } - } - polygonQuadTree = polygonQuadTreeNew; + polygonQuadTree = InternalUtils.buildQuadTree(polygonImpl); } Polyline result_polyline = (Polyline) polyline.createInstance(); diff --git a/src/com/esri/core/geometry/Point.java b/src/com/esri/core/geometry/Point.java index ea4c02c7..30db22e7 100644 --- a/src/com/esri/core/geometry/Point.java +++ b/src/com/esri/core/geometry/Point.java @@ -364,41 +364,44 @@ public void setEmpty() { } @Override - void _afterAddAttributeImpl(int semantics) { - _touch(); - if (m_attributes == null) - return; - - int attributeIndex = m_description.getAttributeIndex(semantics); - int offset = m_description._getPointAttributeOffset(attributeIndex); - int comps = VertexDescription.getComponentCount(semantics); - int totalComps = m_description._getTotalComponents(); - resizeAttributes(totalComps); - - for (int i = totalComps - 1; i >= offset + comps; i--) - m_attributes[i] = m_attributes[i - comps]; - - double dv = VertexDescription.getDefaultValue(semantics); - for (int i = 0; i < comps; i++) - m_attributes[offset + i] = dv; - } - - @Override - void _beforeDropAttributeImpl(int semantics) { - _touch(); - if (m_attributes == null) + protected void _assignVertexDescriptionImpl(VertexDescription newDescription) { + if (m_attributes == null) { + m_description = newDescription; return; - - // _ASSERT(semantics != enum_value2(VertexDescription, Semantics, - // POSITION)); - int attributeIndex = m_description.getAttributeIndex(semantics); - int offset = m_description._getPointAttributeOffset(attributeIndex); - int comps = VertexDescription.getComponentCount(semantics); - int totalCompsOld = m_description._getTotalComponents(); - if (totalCompsOld > comps) { - for (int i = offset + comps; i < totalCompsOld; i++) - m_attributes[i - comps] = m_attributes[i]; } + + int[] mapping = VertexDescriptionDesignerImpl.mapAttributes(newDescription, m_description); + + double[] newAttributes = new double[newDescription._getTotalComponents()]; + + int j = 0; + for (int i = 0, n = newDescription.getAttributeCount(); i < n; i++) { + int semantics = newDescription.getSemantics(i); + int nords = VertexDescription.getComponentCount(semantics); + if (mapping[i] == -1) + { + double d = VertexDescription.getDefaultValue(semantics); + for (int ord = 0; ord < nords; ord++) + { + newAttributes[j] = d; + j++; + } + } + else { + int m = mapping[i]; + int offset = m_description._getPointAttributeOffset(m); + for (int ord = 0; ord < nords; ord++) + { + newAttributes[j] = m_attributes[offset]; + j++; + offset++; + } + } + + } + + m_attributes = newAttributes; + m_description = newDescription; } /** @@ -531,8 +534,8 @@ private void resizeAttributes(int newSize) { } static void attributeCopy(double[] src, double[] dst, int count) { - for (int i = 0; i < count; i++) - dst[i] = src[i]; + if (count > 0) + System.arraycopy(src, 0, dst, 0, count); } /** diff --git a/src/com/esri/core/geometry/Point2D.java b/src/com/esri/core/geometry/Point2D.java index 35979c74..661cb244 100644 --- a/src/com/esri/core/geometry/Point2D.java +++ b/src/com/esri/core/geometry/Point2D.java @@ -33,10 +33,10 @@ * Basic 2D point class. Contains only two double fields. * */ -final class Point2D { +public final class Point2D { public double x; - public double y; + double y; public Point2D() { } @@ -46,7 +46,6 @@ public Point2D(double x, double y) { this.y = y; } - // Header definitions public static Point2D construct(double x, double y) { return new Point2D(x, y); } @@ -65,7 +64,7 @@ public boolean isEqual(Point2D other) { return x == other.x && y == other.y; } - boolean isEqual(Point2D other, double tol) { + public boolean isEqual(Point2D other, double tol) { return (Math.abs(x - other.x) <= tol) && (Math.abs(y - other.y) <= tol); } @@ -74,7 +73,7 @@ public void sub(Point2D other) { y -= other.y; } - void sub(Point2D p1, Point2D p2) { + public void sub(Point2D p1, Point2D p2) { x = p1.x - p2.x; y = p1.y - p2.y; } @@ -132,12 +131,12 @@ public void scale(double f) { /** * Compares two vertices lexicographicaly. */ - int compare(Point2D other) { + public int compare(Point2D other) { return y < other.y ? -1 : (y > other.y ? 1 : (x < other.x ? -1 : (x > other.x ? 1 : 0))); } - void normalize(Point2D other) { + public void normalize(Point2D other) { double len = other.length(); if (len == 0) { x = 1.0; @@ -163,15 +162,15 @@ public double length() { return Math.sqrt(x * x + y * y); } - double sqrLength() { + public double sqrLength() { return x * x + y * y; } - static double distance(Point2D pt1, Point2D pt2) { + public static double distance(Point2D pt1, Point2D pt2) { return Math.sqrt(sqrDistance(pt1, pt2)); } - double dotProduct(Point2D other) { + public double dotProduct(Point2D other) { return x * other.x + y * other.y; } @@ -179,11 +178,11 @@ static double distance(Point2D pt1, Point2D pt2) { return Math.abs(x * other.x) + Math.abs(y * other.y); } - double crossProduct(Point2D other) { + public double crossProduct(Point2D other) { return x * other.y - y * other.x; } - void rotateDirect(double Cos, double Sin) // corresponds to the + public void rotateDirect(double Cos, double Sin) // corresponds to the // Transformation2D.SetRotate(cos, // sin).Transform(pt) { @@ -193,7 +192,7 @@ void rotateDirect(double Cos, double Sin) // corresponds to the y = yy; } - void rotateReverse(double Cos, double Sin) { + public void rotateReverse(double Cos, double Sin) { double xx = x * Cos + y * Sin; double yy = -x * Sin + y * Cos; x = xx; @@ -204,7 +203,7 @@ void rotateReverse(double Cos, double Sin) { * 90 degree rotation, anticlockwise. Equivalent to RotateDirect(cos(pi/2), * sin(pi/2)). */ - void leftPerpendicular() { + public void leftPerpendicular() { double xx = x; x = -y; y = xx; @@ -214,7 +213,7 @@ void leftPerpendicular() { * 90 degree rotation, anticlockwise. Equivalent to RotateDirect(cos(pi/2), * sin(pi/2)). */ - void leftPerpendicular(Point2D pt) { + public void leftPerpendicular(Point2D pt) { x = -pt.y; y = pt.x; } @@ -223,7 +222,7 @@ void leftPerpendicular(Point2D pt) { * 270 degree rotation, anticlockwise. Equivalent to * RotateDirect(-cos(pi/2), sin(-pi/2)). */ - void rightPerpendicular() { + public void rightPerpendicular() { double xx = x; x = y; y = -xx; @@ -233,7 +232,7 @@ void rightPerpendicular() { * 270 degree rotation, anticlockwise. Equivalent to * RotateDirect(-cos(pi/2), sin(-pi/2)). */ - void rightPerpendicular(Point2D pt) { + public void rightPerpendicular(Point2D pt) { x = pt.y; y = -pt.x; } @@ -300,22 +299,12 @@ public int compare(Point2D v1, Point2D v2) { } } - // Header definitions - - // public Point2D mul(double factor) { - // return new Point2D(x * factor, y * factor); - // } - public static double sqrDistance(Point2D pt1, Point2D pt2) { double dx = pt1.x - pt2.x; double dy = pt1.y - pt2.y; return dx * dx + dy * dy; } - // Header definitions - - // Cpp definitions - @Override public String toString() { return "(" + x + " , " + y + ")"; @@ -383,7 +372,7 @@ public boolean isNaN() { * for counter-clockwise, -1 for clockwise, and 0 for collinear. May use * high precision arithmetics for some special degenerate cases. */ - static int orientationRobust(Point2D p, Point2D q, Point2D r) { + public static int orientationRobust(Point2D p, Point2D q, Point2D r) { ECoordinate det_ec = new ECoordinate(); det_ec.set(q.x); det_ec.sub(p.x); diff --git a/src/com/esri/core/geometry/Segment.java b/src/com/esri/core/geometry/Segment.java index 4f9b131d..a36ab086 100644 --- a/src/com/esri/core/geometry/Segment.java +++ b/src/com/esri/core/geometry/Segment.java @@ -439,7 +439,7 @@ private Point3D _getXYZ(int endPoint) { } if (m_description.hasZ()) - pt.z = m_attributes[_getEndPointOffset(endPoint)]; + pt.z = m_attributes[_getEndPointOffset(m_description, endPoint)]; else pt.z = VertexDescription.getDefaultValue(Semantics.Z); @@ -472,67 +472,57 @@ private void _setXYZ(int endPoint, Point3D pt) { } if (bHasZ) - m_attributes[_getEndPointOffset(endPoint)] = pt.z; + m_attributes[_getEndPointOffset(m_description, endPoint)] = pt.z; } @Override - void _beforeDropAttributeImpl(int semantics) { - _touch(); - if (isEmptyImpl()) + protected void _assignVertexDescriptionImpl(VertexDescription newDescription) { + if (m_attributes == null) { + m_description = newDescription; return; - - // _ASSERT(semantics != enum_value2(VertexDescription, Semantics, - // POSITION)); - int attributeIndex = m_description.getAttributeIndex(semantics); - int offset = m_description._getPointAttributeOffset(attributeIndex) - 2; - int comps = VertexDescription.getComponentCount(semantics); - int totalCompsOld = m_description._getTotalComponents() - 2; - if (totalCompsOld > comps) { - int offset0 = _getEndPointOffset(0); - for (int i = offset + comps; i < totalCompsOld * 2; i++) - m_attributes[offset0 + i - comps] = m_attributes[offset0 + i]; - - int offset1 = _getEndPointOffset(1) - comps; // -comp is for deleted - // attribute of - // start vertex - for (int i = offset + comps; i < totalCompsOld; i++) - m_attributes[offset1 + i - comps] = m_attributes[offset1 + i]; } - } - - @Override - void _afterAddAttributeImpl(int semantics) { - _touch(); - int attributeIndex = m_description.getAttributeIndex(semantics); - int offset = m_description._getPointAttributeOffset(attributeIndex) - 2; - int comps = VertexDescription.getComponentCount(semantics); - int totalComps = m_description._getTotalComponents() - 2; - _resizeAttributes(totalComps); - int totalCompsOld = totalComps - comps; // the total number of - // components before resize. - - int offset0 = _getEndPointOffset(0); - int offset1 = _getEndPointOffset(1); - int offset1old = offset1 - comps; - for (int i = totalCompsOld - 1; i >= 0; i--) {// correct the position of - // the End attributes - m_attributes[offset1 + i] = m_attributes[offset1old + i]; - } - - // move attributes for start end end points that go after the insertion - // point - for (int i = totalComps - 1; i >= offset + comps; i--) { - m_attributes[offset0 + i] = m_attributes[offset0 + i - comps]; - m_attributes[offset1 + i] = m_attributes[offset1 + i - comps]; - } - - // initialize added attribute to the default value. - double dv = VertexDescription.getDefaultValue(semantics); - for (int i = 0; i < comps; i++) { - m_attributes[offset0 + offset + i] = dv; - m_attributes[offset1 + offset + i] = dv; + + int[] mapping = VertexDescriptionDesignerImpl.mapAttributes(newDescription, m_description); + + double[] newAttributes = new double[(newDescription._getTotalComponents() - 2) * 2]; + + int old_offset0 = _getEndPointOffset(m_description, 0); + int old_offset1 = _getEndPointOffset(m_description, 1); + + int new_offset0 = _getEndPointOffset(newDescription, 0); + int new_offset1 = _getEndPointOffset(newDescription, 1); + + int j = 0; + for (int i = 1, n = newDescription.getAttributeCount(); i < n; i++) { + int semantics = newDescription.getSemantics(i); + int nords = VertexDescription.getComponentCount(semantics); + if (mapping[i] == -1) + { + double d = VertexDescription.getDefaultValue(semantics); + for (int ord = 0; ord < nords; ord++) + { + newAttributes[new_offset0 + j] = d; + newAttributes[new_offset1 + j] = d; + j++; + } + } + else { + int m = mapping[i]; + int offset = m_description._getPointAttributeOffset(m) - 2; + for (int ord = 0; ord < nords; ord++) + { + newAttributes[new_offset0 + j] = m_attributes[old_offset0 + offset]; + newAttributes[new_offset1 + j] = m_attributes[old_offset1 + offset]; + j++; + offset++; + } + } + } + + m_attributes = newAttributes; + m_description = newDescription; } private void _get(int endPoint, Point outPoint) { @@ -595,7 +585,7 @@ private void _set(int endPoint, Point src) { if (m_attributes != null) _resizeAttributes(m_description._getTotalComponents() - 2); - return m_attributes[_getEndPointOffset(endPoint) + return m_attributes[_getEndPointOffset(m_description, endPoint) + m_description._getPointAttributeOffset(attributeIndex) - 2 + ordinate]; } else @@ -622,11 +612,12 @@ void _setAttribute(int endPoint, int semantics, int ordinate, double value) { } if (semantics == Semantics.POSITION) { - if (endPoint != 0) + if (endPoint != 0) { if (ordinate != 0) m_yEnd = value; else m_xEnd = value; + } else if (ordinate != 0) m_yStart = value; else @@ -634,10 +625,10 @@ else if (ordinate != 0) return; } - if (m_attributes != null) + if (m_attributes == null) _resizeAttributes(m_description._getTotalComponents() - 2); - m_attributes[_getEndPointOffset(endPoint) + m_attributes[_getEndPointOffset(m_description, endPoint) + m_description._getPointAttributeOffset(attributeIndex) - 2 + ordinate] = value; @@ -786,8 +777,8 @@ int _intersect(Segment other, Point2D[] intersectionPoints, */ abstract double _calculateArea2DHelper(double xorg, double yorg); - int _getEndPointOffset(int endPoint) { - return endPoint * (m_description._getTotalComponents() - 2); + static int _getEndPointOffset(VertexDescription vd, int endPoint) { + return endPoint * (vd._getTotalComponents() - 2); } /** diff --git a/src/com/esri/core/geometry/SpatialReference.java b/src/com/esri/core/geometry/SpatialReference.java index c2621096..a416c241 100644 --- a/src/com/esri/core/geometry/SpatialReference.java +++ b/src/com/esri/core/geometry/SpatialReference.java @@ -29,6 +29,7 @@ import org.codehaus.jackson.JsonParser; import org.codehaus.jackson.JsonToken; +import com.esri.core.geometry.SpatialReference; import com.esri.core.geometry.SpatialReferenceSerializer; import com.esri.core.geometry.VertexDescription; @@ -87,48 +88,87 @@ boolean isLocal() { * if parsing has failed */ public static SpatialReference fromJson(JsonParser parser) throws Exception { - int wkid = 0; + return fromJson(new JsonParserReader(parser)); + } + + static SpatialReference fromJson(JsonReader parser) throws Exception { + // Note this class is processed specially: it is expected that the + // iterator points to the first element of the SR object. + boolean bFoundWkid = false; + boolean bFoundLatestWkid = false; + boolean bFoundVcsWkid = false; + boolean bFoundLatestVcsWkid = false; + boolean bFoundWkt = false; + + int wkid = -1; + int latestWkid = -1; + int vcs_wkid = -1; + int latestVcsWkid = -1; String wkt = null; - if (!JSONUtils.isObjectStart(parser)) - return null; - while (parser.nextToken() != JsonToken.END_OBJECT) { - String fieldName = parser.getCurrentName(); + String name = parser.currentString(); parser.nextToken(); - if (parser.getCurrentToken() == JsonToken.VALUE_NULL) { - continue; + + if (!bFoundWkid && name.equals("wkid")) { + bFoundWkid = true; + + if (parser.currentToken() == JsonToken.VALUE_NUMBER_INT) + wkid = parser.currentIntValue(); + } else if (!bFoundLatestWkid && name.equals("latestWkid")) { + bFoundLatestWkid = true; + + if (parser.currentToken() == JsonToken.VALUE_NUMBER_INT) + latestWkid = parser.currentIntValue(); + } else if (!bFoundWkt && name.equals("wkt")) { + bFoundWkt = true; + + if (parser.currentToken() == JsonToken.VALUE_STRING) + wkt = parser.currentString(); + } else if (!bFoundVcsWkid && name.equals("vcsWkid")) { + bFoundVcsWkid = true; + + if (parser.currentToken() == JsonToken.VALUE_NUMBER_INT) + vcs_wkid = parser.currentIntValue(); + } else if (!bFoundLatestVcsWkid && name.equals("latestVcsWkid")) { + bFoundLatestVcsWkid = true; + + if (parser.currentToken() == JsonToken.VALUE_NUMBER_INT) + latestVcsWkid = parser.currentIntValue(); } + } + + if (latestVcsWkid <= 0 && vcs_wkid > 0) { + latestVcsWkid = vcs_wkid; + } + + // iter.step_out_after(); do not step out for the spatial reference, + // because this method is used standalone + + SpatialReference spatial_reference = null; - if ("latestWkid".equals(fieldName)) {// get wkid - wkid = parser.getIntValue(); - } else if ("wkid".equals(fieldName)) {// get wkid - wkid = parser.getIntValue(); - } else if ("wkt".equals(fieldName)) { - wkt = parser.getText(); - } else { - parser.skipChildren(); + if (wkt != null && wkt.length() != 0) { + try { + spatial_reference = SpatialReference.create(wkt); + } catch (Exception e) { } } - // END _OBJECT - if (wkid > 0) // 1. Try to use wkid - { + if (spatial_reference == null && latestWkid > 0) { try { - return SpatialReference.create(wkid); - } catch (IllegalArgumentException ex) { - // if (wkt == null || wkt.length() == 0) //Here this will be our - // default. - // throw ex; + spatial_reference = SpatialReference.create(latestWkid); + } catch (Exception e) { } } - if (wkt != null && wkt.length() != 0) // try to use wkt. - { - return SpatialReference.create(wkt); + if (spatial_reference == null && wkid > 0) { + try { + spatial_reference = SpatialReference.create(wkid); + } catch (Exception e) { + } } - return null; - } + return spatial_reference; + } /** * Returns the well known ID for the horizontal coordinate system of the diff --git a/src/com/esri/core/geometry/VertexDescriptionDesignerImpl.java b/src/com/esri/core/geometry/VertexDescriptionDesignerImpl.java index 3ead4792..9bec3a33 100644 --- a/src/com/esri/core/geometry/VertexDescriptionDesignerImpl.java +++ b/src/com/esri/core/geometry/VertexDescriptionDesignerImpl.java @@ -24,6 +24,10 @@ package com.esri.core.geometry; +import java.util.Arrays; + +import com.esri.core.geometry.VertexDescription.Semantics; + /** * This factory class allows to describe and create a VertexDescription * instance. @@ -207,5 +211,52 @@ public boolean isDesignerFor(VertexDescription vd) { return true; } + + // returns a mapping from the source attribute indices to the destination + // attribute indices. + static int[] mapAttributes(VertexDescription src, VertexDescription dest) { + int[] srcToDst = new int[src.getAttributeCount()]; + Arrays.fill(srcToDst, -1); + for (int i = 0, nsrc = src.getAttributeCount(); i < nsrc; i++) { + srcToDst[i] = dest.getAttributeIndex(src.getSemantics(i)); + } + return srcToDst; + } + static VertexDescription getMergedVertexDescription(VertexDescription src, + int semanticsToAdd) { + VertexDescriptionDesignerImpl vdd = new VertexDescriptionDesignerImpl( + src); + vdd.addAttribute(semanticsToAdd); + return vdd.getDescription(); + } + + static VertexDescription getMergedVertexDescription(VertexDescription d1, VertexDescription d2) { + VertexDescriptionDesignerImpl vdd = null; + for (int semantics = Semantics.POSITION; semantics < Semantics.MAXSEMANTICS; semantics++) { + if (!d1.hasAttribute(semantics) && d2.hasAttribute(semantics)) { + if (vdd == null) { + vdd = new VertexDescriptionDesignerImpl(d1); + } + + vdd.addAttribute(semantics); + } + } + + if (vdd != null) { + return vdd.getDescription(); + } + + return d1; + } + + static VertexDescription removeSemanticsFromVertexDescription( + VertexDescription src, int semanticsToRemove) { + VertexDescriptionDesignerImpl vdd = new VertexDescriptionDesignerImpl( + src); + vdd.removeAttribute(semanticsToRemove); + return vdd.getDescription(); + } + } + diff --git a/unittest/com/esri/core/geometry/TestAttributes.java b/unittest/com/esri/core/geometry/TestAttributes.java new file mode 100644 index 00000000..fe3f6a67 --- /dev/null +++ b/unittest/com/esri/core/geometry/TestAttributes.java @@ -0,0 +1,324 @@ +package com.esri.core.geometry; + +import static org.junit.Assert.*; + +import org.junit.Test; + +public class TestAttributes { + + @Test + public void testPoint() { + Point pt = new Point(); + pt.setXY(100, 200); + assertFalse(pt.hasAttribute(VertexDescription.Semantics.M)); + pt.addAttribute(VertexDescription.Semantics.M); + assertTrue(pt.hasAttribute(VertexDescription.Semantics.M)); + assertTrue(Double.isNaN(pt.getM())); + pt.setAttribute(VertexDescription.Semantics.M, 0, 13); + assertTrue(pt.getM() == 13); + pt.addAttribute(VertexDescription.Semantics.Z); + assertTrue(pt.getZ() == 0); + assertTrue(pt.getM() == 13); + pt.setAttribute(VertexDescription.Semantics.Z, 0, 11); + assertTrue(pt.getZ() == 11); + assertTrue(pt.getM() == 13); + pt.addAttribute(VertexDescription.Semantics.ID); + assertTrue(pt.getID() == 0); + assertTrue(pt.getZ() == 11); + assertTrue(pt.getM() == 13); + pt.setAttribute(VertexDescription.Semantics.ID, 0, 1); + assertTrue(pt.getID() == 1); + assertTrue(pt.getZ() == 11); + assertTrue(pt.getM() == 13); + pt.dropAttribute(VertexDescription.Semantics.M); + assertTrue(pt.getID() == 1); + assertTrue(pt.getZ() == 11); + assertFalse(pt.hasAttribute(VertexDescription.Semantics.M)); + + Point pt1 = new Point(); + assertFalse(pt1.hasAttribute(VertexDescription.Semantics.M)); + assertFalse(pt1.hasAttribute(VertexDescription.Semantics.Z)); + assertFalse(pt1.hasAttribute(VertexDescription.Semantics.ID)); + pt1.mergeVertexDescription(pt.getDescription()); + assertFalse(pt1.hasAttribute(VertexDescription.Semantics.M)); + assertTrue(pt1.hasAttribute(VertexDescription.Semantics.Z)); + assertTrue(pt1.hasAttribute(VertexDescription.Semantics.ID)); + } + + @Test + public void testEnvelope() { + Envelope env = new Envelope(); + env.setCoords(100, 200, 250, 300); + assertFalse(env.hasAttribute(VertexDescription.Semantics.M)); + env.addAttribute(VertexDescription.Semantics.M); + assertTrue(env.hasAttribute(VertexDescription.Semantics.M)); + assertTrue(env.queryInterval(VertexDescription.Semantics.M, 0).isEmpty()); + env.setInterval(VertexDescription.Semantics.M, 0, 1, 2); + assertTrue(env.queryInterval(VertexDescription.Semantics.M, 0).vmin == 1); + assertTrue(env.queryInterval(VertexDescription.Semantics.M, 0).vmax == 2); + + assertFalse(env.hasAttribute(VertexDescription.Semantics.Z)); + env.addAttribute(VertexDescription.Semantics.Z); + assertTrue(env.hasAttribute(VertexDescription.Semantics.Z)); + assertTrue(env.queryInterval(VertexDescription.Semantics.Z, 0).vmin == 0); + assertTrue(env.queryInterval(VertexDescription.Semantics.Z, 0).vmax == 0); + env.setInterval(VertexDescription.Semantics.Z, 0, 3, 4); + assertTrue(env.queryInterval(VertexDescription.Semantics.Z, 0).vmin == 3); + assertTrue(env.queryInterval(VertexDescription.Semantics.Z, 0).vmax == 4); + + + assertFalse(env.hasAttribute(VertexDescription.Semantics.ID)); + env.addAttribute(VertexDescription.Semantics.ID); + assertTrue(env.hasAttribute(VertexDescription.Semantics.ID)); + assertTrue(env.queryInterval(VertexDescription.Semantics.ID, 0).vmin == 0); + assertTrue(env.queryInterval(VertexDescription.Semantics.ID, 0).vmax == 0); + env.setInterval(VertexDescription.Semantics.ID, 0, 5, 6); + assertTrue(env.queryInterval(VertexDescription.Semantics.ID, 0).vmin == 5); + assertTrue(env.queryInterval(VertexDescription.Semantics.ID, 0).vmax == 6); + assertTrue(env.queryInterval(VertexDescription.Semantics.Z, 0).vmin == 3); + assertTrue(env.queryInterval(VertexDescription.Semantics.Z, 0).vmax == 4); + assertTrue(env.queryInterval(VertexDescription.Semantics.M, 0).vmin == 1); + assertTrue(env.queryInterval(VertexDescription.Semantics.M, 0).vmax == 2); + + env.dropAttribute(VertexDescription.Semantics.M); + assertFalse(env.hasAttribute(VertexDescription.Semantics.M)); + assertTrue(env.queryInterval(VertexDescription.Semantics.ID, 0).vmin == 5); + assertTrue(env.queryInterval(VertexDescription.Semantics.ID, 0).vmax == 6); + assertTrue(env.queryInterval(VertexDescription.Semantics.Z, 0).vmin == 3); + assertTrue(env.queryInterval(VertexDescription.Semantics.Z, 0).vmax == 4); + + Envelope env1 = new Envelope(); + env.copyTo(env1); + assertFalse(env1.hasAttribute(VertexDescription.Semantics.M)); + assertTrue(env1.queryInterval(VertexDescription.Semantics.ID, 0).vmin == 5); + assertTrue(env1.queryInterval(VertexDescription.Semantics.ID, 0).vmax == 6); + assertTrue(env1.queryInterval(VertexDescription.Semantics.Z, 0).vmin == 3); + assertTrue(env1.queryInterval(VertexDescription.Semantics.Z, 0).vmax == 4); + } + + @Test + public void testLine() { + Line env = new Line(); + env.setStartXY(100, 200); + env.setEndXY(250, 300); + assertFalse(env.hasAttribute(VertexDescription.Semantics.M)); + env.addAttribute(VertexDescription.Semantics.M); + assertTrue(env.hasAttribute(VertexDescription.Semantics.M)); + env.setStartAttribute(VertexDescription.Semantics.M, 0, 1); + env.setEndAttribute(VertexDescription.Semantics.M, 0, 2); + assertTrue(env.getStartAttributeAsDbl(VertexDescription.Semantics.M, 0) == 1); + assertTrue(env.getEndAttributeAsDbl(VertexDescription.Semantics.M, 0) == 2); + + assertFalse(env.hasAttribute(VertexDescription.Semantics.Z)); + env.addAttribute(VertexDescription.Semantics.Z); + assertTrue(env.hasAttribute(VertexDescription.Semantics.Z)); + env.setStartAttribute(VertexDescription.Semantics.Z, 0, 3); + env.setEndAttribute(VertexDescription.Semantics.Z, 0, 4); + assertTrue(env.getStartAttributeAsDbl(VertexDescription.Semantics.Z, 0) == 3); + assertTrue(env.getEndAttributeAsDbl(VertexDescription.Semantics.Z, 0) == 4); + + + assertFalse(env.hasAttribute(VertexDescription.Semantics.ID)); + env.addAttribute(VertexDescription.Semantics.ID); + assertTrue(env.hasAttribute(VertexDescription.Semantics.ID)); + env.setStartAttribute(VertexDescription.Semantics.ID, 0, 5); + env.setEndAttribute(VertexDescription.Semantics.ID, 0, 6); + assertTrue(env.getStartAttributeAsDbl(VertexDescription.Semantics.ID, 0) == 5); + assertTrue(env.getEndAttributeAsDbl(VertexDescription.Semantics.ID, 0) == 6); + + assertTrue(env.getStartAttributeAsDbl(VertexDescription.Semantics.M, 0) == 1); + assertTrue(env.getEndAttributeAsDbl(VertexDescription.Semantics.M, 0) == 2); + assertTrue(env.getStartAttributeAsDbl(VertexDescription.Semantics.Z, 0) == 3); + assertTrue(env.getEndAttributeAsDbl(VertexDescription.Semantics.Z, 0) == 4); + + env.dropAttribute(VertexDescription.Semantics.M); + assertFalse(env.hasAttribute(VertexDescription.Semantics.M)); + assertTrue(env.getStartAttributeAsDbl(VertexDescription.Semantics.ID, 0) == 5); + assertTrue(env.getEndAttributeAsDbl(VertexDescription.Semantics.ID, 0) == 6); + assertTrue(env.getStartAttributeAsDbl(VertexDescription.Semantics.Z, 0) == 3); + assertTrue(env.getEndAttributeAsDbl(VertexDescription.Semantics.Z, 0) == 4); + + Line env1 = new Line(); + env.copyTo(env1); + assertFalse(env1.hasAttribute(VertexDescription.Semantics.M)); + assertTrue(env.getStartAttributeAsDbl(VertexDescription.Semantics.ID, 0) == 5); + assertTrue(env.getEndAttributeAsDbl(VertexDescription.Semantics.ID, 0) == 6); + assertTrue(env.getStartAttributeAsDbl(VertexDescription.Semantics.Z, 0) == 3); + assertTrue(env.getEndAttributeAsDbl(VertexDescription.Semantics.Z, 0) == 4); + } + + @Test + public void testMultiPoint() { + MultiPoint mp = new MultiPoint(); + mp.add(new Point(100, 200)); + mp.add(new Point(101, 201)); + mp.add(new Point(102, 202)); + assertFalse(mp.hasAttribute(VertexDescription.Semantics.M)); + mp.addAttribute(VertexDescription.Semantics.M); + assertTrue(mp.hasAttribute(VertexDescription.Semantics.M)); + assertTrue(Double.isNaN(mp.getAttributeAsDbl(VertexDescription.Semantics.M, 0, 0))); + assertTrue(Double.isNaN(mp.getAttributeAsDbl(VertexDescription.Semantics.M, 1, 0))); + assertTrue(Double.isNaN(mp.getAttributeAsDbl(VertexDescription.Semantics.M, 2, 0))); + mp.setAttribute(VertexDescription.Semantics.M, 0, 0, 1); + mp.setAttribute(VertexDescription.Semantics.M, 1, 0, 2); + mp.setAttribute(VertexDescription.Semantics.M, 2, 0, 3); + assertTrue(mp.getAttributeAsDbl(VertexDescription.Semantics.M, 0, 0)==1); + assertTrue(mp.getAttributeAsDbl(VertexDescription.Semantics.M, 1, 0)==2); + assertTrue(mp.getAttributeAsDbl(VertexDescription.Semantics.M, 2, 0)==3); + + assertFalse(mp.hasAttribute(VertexDescription.Semantics.Z)); + mp.addAttribute(VertexDescription.Semantics.Z); + assertTrue(mp.hasAttribute(VertexDescription.Semantics.Z)); + assertTrue(mp.getAttributeAsDbl(VertexDescription.Semantics.Z, 0, 0)==0); + assertTrue(mp.getAttributeAsDbl(VertexDescription.Semantics.Z, 1, 0)==0); + assertTrue(mp.getAttributeAsDbl(VertexDescription.Semantics.Z, 2, 0)==0); + mp.setAttribute(VertexDescription.Semantics.Z, 0, 0, 11); + mp.setAttribute(VertexDescription.Semantics.Z, 1, 0, 21); + mp.setAttribute(VertexDescription.Semantics.Z, 2, 0, 31); + assertTrue(mp.getAttributeAsDbl(VertexDescription.Semantics.M, 0, 0)==1); + assertTrue(mp.getAttributeAsDbl(VertexDescription.Semantics.M, 1, 0)==2); + assertTrue(mp.getAttributeAsDbl(VertexDescription.Semantics.M, 2, 0)==3); + assertTrue(mp.getAttributeAsDbl(VertexDescription.Semantics.Z, 0, 0)==11); + assertTrue(mp.getAttributeAsDbl(VertexDescription.Semantics.Z, 1, 0)==21); + assertTrue(mp.getAttributeAsDbl(VertexDescription.Semantics.Z, 2, 0)==31); + + assertFalse(mp.hasAttribute(VertexDescription.Semantics.ID)); + mp.addAttribute(VertexDescription.Semantics.ID); + assertTrue(mp.hasAttribute(VertexDescription.Semantics.ID)); + assertTrue(mp.getAttributeAsDbl(VertexDescription.Semantics.ID, 0, 0)==0); + assertTrue(mp.getAttributeAsDbl(VertexDescription.Semantics.ID, 1, 0)==0); + assertTrue(mp.getAttributeAsDbl(VertexDescription.Semantics.ID, 2, 0)==0); + mp.setAttribute(VertexDescription.Semantics.ID, 0, 0, -11); + mp.setAttribute(VertexDescription.Semantics.ID, 1, 0, -21); + mp.setAttribute(VertexDescription.Semantics.ID, 2, 0, -31); + assertTrue(mp.getAttributeAsDbl(VertexDescription.Semantics.M, 0, 0)==1); + assertTrue(mp.getAttributeAsDbl(VertexDescription.Semantics.M, 1, 0)==2); + assertTrue(mp.getAttributeAsDbl(VertexDescription.Semantics.M, 2, 0)==3); + assertTrue(mp.getAttributeAsDbl(VertexDescription.Semantics.Z, 0, 0)==11); + assertTrue(mp.getAttributeAsDbl(VertexDescription.Semantics.Z, 1, 0)==21); + assertTrue(mp.getAttributeAsDbl(VertexDescription.Semantics.Z, 2, 0)==31); + assertTrue(mp.getAttributeAsDbl(VertexDescription.Semantics.ID, 0, 0)==-11); + assertTrue(mp.getAttributeAsDbl(VertexDescription.Semantics.ID, 1, 0)==-21); + assertTrue(mp.getAttributeAsDbl(VertexDescription.Semantics.ID, 2, 0)==-31); + + mp.dropAttribute(VertexDescription.Semantics.M); + assertFalse(mp.hasAttribute(VertexDescription.Semantics.M)); + assertTrue(mp.getAttributeAsDbl(VertexDescription.Semantics.Z, 0, 0)==11); + assertTrue(mp.getAttributeAsDbl(VertexDescription.Semantics.Z, 1, 0)==21); + assertTrue(mp.getAttributeAsDbl(VertexDescription.Semantics.Z, 2, 0)==31); + assertTrue(mp.getAttributeAsDbl(VertexDescription.Semantics.ID, 0, 0)==-11); + assertTrue(mp.getAttributeAsDbl(VertexDescription.Semantics.ID, 1, 0)==-21); + assertTrue(mp.getAttributeAsDbl(VertexDescription.Semantics.ID, 2, 0)==-31); + + MultiPoint mp1 = new MultiPoint(); + mp.copyTo(mp1); + assertFalse(mp1.hasAttribute(VertexDescription.Semantics.M)); + assertTrue(mp1.getAttributeAsDbl(VertexDescription.Semantics.Z, 0, 0)==11); + assertTrue(mp1.getAttributeAsDbl(VertexDescription.Semantics.Z, 1, 0)==21); + assertTrue(mp1.getAttributeAsDbl(VertexDescription.Semantics.Z, 2, 0)==31); + assertTrue(mp1.getAttributeAsDbl(VertexDescription.Semantics.ID, 0, 0)==-11); + assertTrue(mp1.getAttributeAsDbl(VertexDescription.Semantics.ID, 1, 0)==-21); + assertTrue(mp1.getAttributeAsDbl(VertexDescription.Semantics.ID, 2, 0)==-31); + + mp1.dropAllAttributes(); + mp1.mergeVertexDescription(mp.getDescription()); + assertTrue(mp1.getAttributeAsDbl(VertexDescription.Semantics.Z, 0, 0)==0); + assertTrue(mp1.getAttributeAsDbl(VertexDescription.Semantics.Z, 1, 0)==0); + assertTrue(mp1.getAttributeAsDbl(VertexDescription.Semantics.Z, 2, 0)==0); + assertTrue(Double.isNaN(mp1.getAttributeAsDbl(VertexDescription.Semantics.M, 0, 0))); + assertTrue(Double.isNaN(mp1.getAttributeAsDbl(VertexDescription.Semantics.M, 1, 0))); + assertTrue(Double.isNaN(mp1.getAttributeAsDbl(VertexDescription.Semantics.M, 2, 0))); + assertTrue(mp1.getAttributeAsDbl(VertexDescription.Semantics.ID, 0, 0)==0); + assertTrue(mp1.getAttributeAsDbl(VertexDescription.Semantics.ID, 1, 0)==0); + assertTrue(mp1.getAttributeAsDbl(VertexDescription.Semantics.ID, 2, 0)==0); + + } + + @Test + public void testPolygon() { + Polygon mp = new Polygon(); + mp.startPath(new Point(100, 200)); + mp.lineTo(new Point(101, 201)); + mp.lineTo(new Point(102, 202)); + assertFalse(mp.hasAttribute(VertexDescription.Semantics.M)); + mp.addAttribute(VertexDescription.Semantics.M); + assertTrue(mp.hasAttribute(VertexDescription.Semantics.M)); + assertTrue(Double.isNaN(mp.getAttributeAsDbl(VertexDescription.Semantics.M, 0, 0))); + assertTrue(Double.isNaN(mp.getAttributeAsDbl(VertexDescription.Semantics.M, 1, 0))); + assertTrue(Double.isNaN(mp.getAttributeAsDbl(VertexDescription.Semantics.M, 2, 0))); + mp.setAttribute(VertexDescription.Semantics.M, 0, 0, 1); + mp.setAttribute(VertexDescription.Semantics.M, 1, 0, 2); + mp.setAttribute(VertexDescription.Semantics.M, 2, 0, 3); + assertTrue(mp.getAttributeAsDbl(VertexDescription.Semantics.M, 0, 0)==1); + assertTrue(mp.getAttributeAsDbl(VertexDescription.Semantics.M, 1, 0)==2); + assertTrue(mp.getAttributeAsDbl(VertexDescription.Semantics.M, 2, 0)==3); + + assertFalse(mp.hasAttribute(VertexDescription.Semantics.Z)); + mp.addAttribute(VertexDescription.Semantics.Z); + assertTrue(mp.hasAttribute(VertexDescription.Semantics.Z)); + assertTrue(mp.getAttributeAsDbl(VertexDescription.Semantics.Z, 0, 0)==0); + assertTrue(mp.getAttributeAsDbl(VertexDescription.Semantics.Z, 1, 0)==0); + assertTrue(mp.getAttributeAsDbl(VertexDescription.Semantics.Z, 2, 0)==0); + mp.setAttribute(VertexDescription.Semantics.Z, 0, 0, 11); + mp.setAttribute(VertexDescription.Semantics.Z, 1, 0, 21); + mp.setAttribute(VertexDescription.Semantics.Z, 2, 0, 31); + assertTrue(mp.getAttributeAsDbl(VertexDescription.Semantics.M, 0, 0)==1); + assertTrue(mp.getAttributeAsDbl(VertexDescription.Semantics.M, 1, 0)==2); + assertTrue(mp.getAttributeAsDbl(VertexDescription.Semantics.M, 2, 0)==3); + assertTrue(mp.getAttributeAsDbl(VertexDescription.Semantics.Z, 0, 0)==11); + assertTrue(mp.getAttributeAsDbl(VertexDescription.Semantics.Z, 1, 0)==21); + assertTrue(mp.getAttributeAsDbl(VertexDescription.Semantics.Z, 2, 0)==31); + + assertFalse(mp.hasAttribute(VertexDescription.Semantics.ID)); + mp.addAttribute(VertexDescription.Semantics.ID); + assertTrue(mp.hasAttribute(VertexDescription.Semantics.ID)); + assertTrue(mp.getAttributeAsDbl(VertexDescription.Semantics.ID, 0, 0)==0); + assertTrue(mp.getAttributeAsDbl(VertexDescription.Semantics.ID, 1, 0)==0); + assertTrue(mp.getAttributeAsDbl(VertexDescription.Semantics.ID, 2, 0)==0); + mp.setAttribute(VertexDescription.Semantics.ID, 0, 0, -11); + mp.setAttribute(VertexDescription.Semantics.ID, 1, 0, -21); + mp.setAttribute(VertexDescription.Semantics.ID, 2, 0, -31); + assertTrue(mp.getAttributeAsDbl(VertexDescription.Semantics.M, 0, 0)==1); + assertTrue(mp.getAttributeAsDbl(VertexDescription.Semantics.M, 1, 0)==2); + assertTrue(mp.getAttributeAsDbl(VertexDescription.Semantics.M, 2, 0)==3); + assertTrue(mp.getAttributeAsDbl(VertexDescription.Semantics.Z, 0, 0)==11); + assertTrue(mp.getAttributeAsDbl(VertexDescription.Semantics.Z, 1, 0)==21); + assertTrue(mp.getAttributeAsDbl(VertexDescription.Semantics.Z, 2, 0)==31); + assertTrue(mp.getAttributeAsDbl(VertexDescription.Semantics.ID, 0, 0)==-11); + assertTrue(mp.getAttributeAsDbl(VertexDescription.Semantics.ID, 1, 0)==-21); + assertTrue(mp.getAttributeAsDbl(VertexDescription.Semantics.ID, 2, 0)==-31); + + mp.dropAttribute(VertexDescription.Semantics.M); + assertFalse(mp.hasAttribute(VertexDescription.Semantics.M)); + assertTrue(mp.getAttributeAsDbl(VertexDescription.Semantics.Z, 0, 0)==11); + assertTrue(mp.getAttributeAsDbl(VertexDescription.Semantics.Z, 1, 0)==21); + assertTrue(mp.getAttributeAsDbl(VertexDescription.Semantics.Z, 2, 0)==31); + assertTrue(mp.getAttributeAsDbl(VertexDescription.Semantics.ID, 0, 0)==-11); + assertTrue(mp.getAttributeAsDbl(VertexDescription.Semantics.ID, 1, 0)==-21); + assertTrue(mp.getAttributeAsDbl(VertexDescription.Semantics.ID, 2, 0)==-31); + + Polygon mp1 = new Polygon(); + mp.copyTo(mp1); + assertFalse(mp1.hasAttribute(VertexDescription.Semantics.M)); + assertTrue(mp1.getAttributeAsDbl(VertexDescription.Semantics.Z, 0, 0)==11); + assertTrue(mp1.getAttributeAsDbl(VertexDescription.Semantics.Z, 1, 0)==21); + assertTrue(mp1.getAttributeAsDbl(VertexDescription.Semantics.Z, 2, 0)==31); + assertTrue(mp1.getAttributeAsDbl(VertexDescription.Semantics.ID, 0, 0)==-11); + assertTrue(mp1.getAttributeAsDbl(VertexDescription.Semantics.ID, 1, 0)==-21); + assertTrue(mp1.getAttributeAsDbl(VertexDescription.Semantics.ID, 2, 0)==-31); + + mp1.dropAllAttributes(); + mp1.mergeVertexDescription(mp.getDescription()); + assertTrue(mp1.getAttributeAsDbl(VertexDescription.Semantics.Z, 0, 0)==0); + assertTrue(mp1.getAttributeAsDbl(VertexDescription.Semantics.Z, 1, 0)==0); + assertTrue(mp1.getAttributeAsDbl(VertexDescription.Semantics.Z, 2, 0)==0); + assertTrue(Double.isNaN(mp1.getAttributeAsDbl(VertexDescription.Semantics.M, 0, 0))); + assertTrue(Double.isNaN(mp1.getAttributeAsDbl(VertexDescription.Semantics.M, 1, 0))); + assertTrue(Double.isNaN(mp1.getAttributeAsDbl(VertexDescription.Semantics.M, 2, 0))); + assertTrue(mp1.getAttributeAsDbl(VertexDescription.Semantics.ID, 0, 0)==0); + assertTrue(mp1.getAttributeAsDbl(VertexDescription.Semantics.ID, 1, 0)==0); + assertTrue(mp1.getAttributeAsDbl(VertexDescription.Semantics.ID, 2, 0)==0); + + } + +} From 7729b1a738276cb401b477692688a1cb725ad92e Mon Sep 17 00:00:00 2001 From: Sergey Tolstov Date: Sun, 20 Oct 2013 14:13:24 -0700 Subject: [PATCH 072/196] Point2D.y has to be public Made Point2D.y public. Some cleanup in the unit tests. --- src/com/esri/core/geometry/Point2D.java | 2 +- .../com/esri/core/geometry/GeometryUtils.java | 131 +----------------- ...omToJSonExportSRFromWkiOrWkt_CR181369.java | 2 +- .../com/esri/core/geometry/TestTouch.java | 109 --------------- 4 files changed, 6 insertions(+), 238 deletions(-) diff --git a/src/com/esri/core/geometry/Point2D.java b/src/com/esri/core/geometry/Point2D.java index 661cb244..fe1a13b9 100644 --- a/src/com/esri/core/geometry/Point2D.java +++ b/src/com/esri/core/geometry/Point2D.java @@ -36,7 +36,7 @@ public final class Point2D { public double x; - double y; + public double y; public Point2D() { } diff --git a/unittest/com/esri/core/geometry/GeometryUtils.java b/unittest/com/esri/core/geometry/GeometryUtils.java index cddf9957..de3bcf04 100644 --- a/unittest/com/esri/core/geometry/GeometryUtils.java +++ b/unittest/com/esri/core/geometry/GeometryUtils.java @@ -2,103 +2,13 @@ import java.io.File; import java.io.FileNotFoundException; -import java.net.URI; import java.util.Scanner; -import javax.ws.rs.core.MediaType; -import javax.ws.rs.core.UriBuilder; - import org.codehaus.jackson.JsonFactory; import org.codehaus.jackson.JsonParser; -import com.sun.jersey.api.client.Client; -import com.sun.jersey.api.client.ClientResponse; -import com.sun.jersey.api.client.WebResource; -import com.sun.jersey.api.client.config.ClientConfig; -import com.sun.jersey.api.client.config.DefaultClientConfig; -import com.sun.jersey.api.representation.Form; - public class GeometryUtils { - static WebResource service4Proj; - static String url4Proj = "http://sampleserver1.arcgisonline.com/ArcGIS/rest/services/Geometry/GeometryServer/project"; - static WebResource service4Simplify; - static String url4Simplify = "http://sampleserver1.arcgisonline.com/ArcGIS/rest/services/Geometry/GeometryServer/simplify"; - static WebResource service4Relation; - static String url4Relation = "http://sampleserver1.arcgisonline.com/arcgis/rest/services/Geometry/GeometryServer/relation"; - - static { - ClientConfig config = new DefaultClientConfig(); - Client client = Client.create(config); - service4Proj = client.resource(getBaseURI4Proj()); - service4Simplify = client.resource(getBaseURI4Simplify()); - service4Relation = client.resource(url4Relation); - } - - private static URI getBaseURI4Proj() { - return UriBuilder.fromUri(url4Proj).build(); - } - - private static URI getBaseURI4Simplify() { - return UriBuilder.fromUri(url4Simplify).build(); - } - - public static Geometry getGeometryForProjectFromRestWS(int srIn, int srOut, - Geometry geomIn) { - String jsonStr4Geom = GeometryEngine.geometryToJson(srIn, geomIn); - String jsonStrNew = "{\"geometryType\":\"" + getGeometryType(geomIn) - + "\",\"geometries\":[" + jsonStr4Geom + "]}"; - - Form f = new Form(); - f.add("inSR", srIn); - f.add("outSR", srOut); - f.add("geometries", jsonStrNew); - f.add("f", "json"); - - ClientResponse response = service4Proj.type( - MediaType.APPLICATION_FORM_URLENCODED).post( - ClientResponse.class, f); - @SuppressWarnings("unused") - boolean isOK = response.getClientResponseStatus() == ClientResponse.Status.OK; - Object obj = response.getEntity(String.class); - String jsonStr = obj.toString(); - int idx2 = jsonStr.lastIndexOf("]"); - int idx1 = jsonStr.indexOf("["); - if (idx1 == -1 || idx2 == -1) - return null; - String jsonStrGeom = jsonStr.substring(idx1 + 1, idx2); - Geometry geometryObj = getGeometryFromJSon(jsonStrGeom); - return geometryObj; - } - - public static Geometry getGeometryForSimplifyFromRestWS(int sr, - Geometry geomIn) { - String jsonStr4Geom = GeometryEngine.geometryToJson( - SpatialReference.create(sr), geomIn); - String jsonStrNew = "{\"geometryType\":\"" + getGeometryType(geomIn) - + "\",\"geometries\":[" + jsonStr4Geom + "]}"; - - Form f = new Form(); - f.add("sr", sr); - f.add("geometries", jsonStrNew); - f.add("f", "json"); - - ClientResponse response = service4Simplify.type( - MediaType.APPLICATION_FORM_URLENCODED).post( - ClientResponse.class, f); - @SuppressWarnings("unused") - boolean isOK = response.getClientResponseStatus() == ClientResponse.Status.OK; - Object obj = response.getEntity(String.class); - String jsonStr = obj.toString(); - int idx2 = jsonStr.lastIndexOf("]"); - int idx1 = jsonStr.indexOf("["); - if (idx1 == -1 || idx2 == -1) - return null; - String jsonStrGeom = jsonStr.substring(idx1 + 1, idx2); - Geometry geometryObj = getGeometryFromJSon(jsonStrGeom); - return geometryObj; - } - - static String getGeometryType(Geometry geomIn) { + public static String getGeometryType(Geometry geomIn) { // there are five types: esriGeometryPoint // esriGeometryMultipoint // esriGeometryPolyline @@ -132,7 +42,8 @@ static Geometry getGeometryFromJSon(String jsonStr) { } public static void testMultiplePath(MultiPath mp1, MultiPath mp2) { - int count1 = mp1.getPointCount(); + return; + /*int count1 = mp1.getPointCount(); int count2 = mp2.getPointCount(); System.out.println("From Rest vertices count: " + count1); @@ -155,47 +66,13 @@ public static void testMultiplePath(MultiPath mp1, MultiPath mp2) { // Assert.assertTrue(deltaX<1e-7); // Assert.assertTrue(deltaY<1e-7); } + */ } public enum SpatialRelationType { esriGeometryRelationCross, esriGeometryRelationDisjoint, esriGeometryRelationIn, esriGeometryRelationInteriorIntersection, esriGeometryRelationIntersection, esriGeometryRelationLineCoincidence, esriGeometryRelationLineTouch, esriGeometryRelationOverlap, esriGeometryRelationPointTouch, esriGeometryRelationTouch, esriGeometryRelationWithin, esriGeometryRelationRelation } - public static boolean isRelationTrue(Geometry geometry1, - Geometry geometry2, SpatialReference sr, - SpatialRelationType relation, String relationParam) { - String jsonStr4Geom1 = getJSonStringFromGeometry(geometry1, sr); - String jsonStr4Geom2 = getJSonStringFromGeometry(geometry2, sr); - - Form f = new Form(); - f.add("sr", sr.getID()); - f.add("geometries1", jsonStr4Geom1); - f.add("geometries2", jsonStr4Geom2); - - @SuppressWarnings("unused") - String enumName = relation.name(); - - f.add("relation", relation.name()); - f.add("f", "json"); - f.add("relationParam", relationParam); - - ClientResponse response = service4Relation.type( - MediaType.APPLICATION_FORM_URLENCODED).post( - ClientResponse.class, f); - @SuppressWarnings("unused") - boolean isOK = response.getClientResponseStatus() == ClientResponse.Status.OK; - Object obj = response.getEntity(String.class); - String jsonStr = obj.toString(); - int idx = jsonStr - .lastIndexOf("geometry1Index\":0,\"geometry2Index\":0"); - - if (idx == -1) { - return false; - } else { - return true; - } - } - static String getJSonStringFromGeometry(Geometry geomIn, SpatialReference sr) { String jsonStr4Geom = GeometryEngine.geometryToJson(sr, geomIn); String jsonStrNew = "{\"geometryType\":\"" + getGeometryType(geomIn) diff --git a/unittest/com/esri/core/geometry/TestGeomToJSonExportSRFromWkiOrWkt_CR181369.java b/unittest/com/esri/core/geometry/TestGeomToJSonExportSRFromWkiOrWkt_CR181369.java index 8d32bbe2..9a12ac11 100644 --- a/unittest/com/esri/core/geometry/TestGeomToJSonExportSRFromWkiOrWkt_CR181369.java +++ b/unittest/com/esri/core/geometry/TestGeomToJSonExportSRFromWkiOrWkt_CR181369.java @@ -552,7 +552,7 @@ boolean checkResultSpatialRef(MapGeometry mapGeometry, int expectWki1, return false; if (!(Wkt != null && Wkt.length() > 0)) return false; - System.out.println("WKT1: " + Wkt); + //System.out.println("WKT1: " + Wkt); SpatialReference sr2 = SpatialReference.create(Wkt); int wki2 = sr2.getID(); if (expectWki2 > 0) { diff --git a/unittest/com/esri/core/geometry/TestTouch.java b/unittest/com/esri/core/geometry/TestTouch.java index c12d3999..b677ba7d 100644 --- a/unittest/com/esri/core/geometry/TestTouch.java +++ b/unittest/com/esri/core/geometry/TestTouch.java @@ -35,15 +35,6 @@ public void testTouchOnPointAndPolyline() { isTouched2 = false; } assertEquals(isTouched && isTouched2, true); - - boolean isTouchedFromRest = GeometryUtils - .isRelationTrue( - pl, - baseGeom, - sr, - GeometryUtils.SpatialRelationType.esriGeometryRelationTouch, - ""); - assertTrue(isTouchedFromRest == isTouched); } @Test @@ -67,15 +58,6 @@ public void testTouchOnPointAndPolygon() { isTouched2 = false; } assertEquals(isTouched && isTouched2, true); - - boolean isTouchedFromRest = GeometryUtils - .isRelationTrue( - pg, - baseGeom, - sr, - GeometryUtils.SpatialRelationType.esriGeometryRelationTouch, - ""); - assertTrue(isTouchedFromRest == isTouched); } @Test @@ -139,14 +121,6 @@ public void testTouchesOnPolylines() { isTouched = false; } assertEquals(isTouched, true); - boolean isTouchedFromRest = GeometryUtils - .isRelationTrue( - compPl, - basePl, - sr, - GeometryUtils.SpatialRelationType.esriGeometryRelationTouch, - ""); - assertTrue(isTouchedFromRest == isTouched); } @Test @@ -175,14 +149,6 @@ public void testTouchesOnPolylineAndPolygon() { } assertEquals(isTouched, true); - boolean isTouchedFromRest = GeometryUtils - .isRelationTrue( - compPl, - basePl, - sr, - GeometryUtils.SpatialRelationType.esriGeometryRelationTouch, - ""); - assertTrue(isTouchedFromRest == isTouched); } @Test @@ -205,14 +171,6 @@ public void testTouchOnEnvelopes() { } assertEquals(isTouched, true); - boolean isTouchedFromRest = GeometryUtils - .isRelationTrue( - env, - env2, - sr, - GeometryUtils.SpatialRelationType.esriGeometryRelationTouch, - ""); - assertTrue(isTouchedFromRest == isTouched); } @Test @@ -238,14 +196,6 @@ public void testTouchesOnPolylineAndEnvelope() { } assertEquals(isTouched, true); - boolean isTouchedFromRest = GeometryUtils - .isRelationTrue( - env, - basePl, - sr, - GeometryUtils.SpatialRelationType.esriGeometryRelationTouch, - ""); - assertTrue(isTouchedFromRest == isTouched); } @Test @@ -270,14 +220,6 @@ public void testTouchesOnPolygonAndEnvelope() { } assertEquals(isTouched, true); - boolean isTouchedFromRest = GeometryUtils - .isRelationTrue( - env, - basePl, - sr, - GeometryUtils.SpatialRelationType.esriGeometryRelationTouch, - ""); - assertTrue(isTouchedFromRest == isTouched); } @Test @@ -297,14 +239,6 @@ public void testTouchesOnPointAndEnvelope() { } assertEquals(isTouched, true); - boolean isTouchedFromRest = GeometryUtils - .isRelationTrue( - p, - env, - sr, - GeometryUtils.SpatialRelationType.esriGeometryRelationTouch, - ""); - assertTrue(isTouchedFromRest == isTouched); } @Test @@ -325,14 +259,6 @@ public void testRelationTouch() { // "G1 TOUCH G2"); assertEquals(isTouched, false); - boolean isTouchedFromRest = GeometryUtils - .isRelationTrue( - compPl, - basePl, - sr, - GeometryUtils.SpatialRelationType.esriGeometryRelationTouch, - ""); - assertTrue(isTouchedFromRest == isTouched); } @Test @@ -356,17 +282,6 @@ public void testTouchesBetweenPointAndLine() { boolean isTouched = GeometryEngine.touches(p, compPl, sr); assertTrue(!isTouched); - boolean isTouchedFromRest = GeometryUtils - .isRelationTrue( - compPl, - p, - sr, - GeometryUtils.SpatialRelationType.esriGeometryRelationTouch, - ""); - assertTrue(isTouchedFromRest == isTouched); - // We do not treat polyline that is not explicitly closed as closed. - // Keep the case to demonstrate the difference between ArcObjects and - // Borg here. } @Test @@ -394,14 +309,6 @@ public void testTouchesBetweenPolylines() { boolean isTouched = GeometryEngine.touches(pl, compPl, sr); assertEquals(isTouched, true); - boolean isTouchedFromRest = GeometryUtils - .isRelationTrue( - compPl, - pl, - sr, - GeometryUtils.SpatialRelationType.esriGeometryRelationTouch, - ""); - assertTrue(isTouchedFromRest == isTouched); } @Test @@ -433,14 +340,6 @@ public void testTouchesBetweenPolylineAndPolygon() { boolean isTouched = GeometryEngine.touches(pl, compPg, sr); assertEquals(isTouched, true); - boolean isTouchedFromRest = GeometryUtils - .isRelationTrue( - compPg, - pl, - sr, - GeometryUtils.SpatialRelationType.esriGeometryRelationTouch, - ""); - assertTrue(isTouchedFromRest == isTouched); } @Test @@ -507,14 +406,6 @@ public void testTouchesBetweenMultipartPolygons2() { boolean isTouched = GeometryEngine.touches(pl, compPl, sr); assertEquals(isTouched, true); - boolean isTouchedFromRest = GeometryUtils - .isRelationTrue( - compPl, - pl, - sr, - GeometryUtils.SpatialRelationType.esriGeometryRelationTouch, - ""); - assertTrue(isTouchedFromRest == isTouched); } @Test From 0b4d55d8e23ed7b7b848158fb04c33f1fd3fa27c Mon Sep 17 00:00:00 2001 From: Sergey Tolstov Date: Wed, 8 Jan 2014 17:50:07 -0800 Subject: [PATCH 073/196] Envelope2D serialize; OperatorDistance optimized for Point to Envelope;A bug in Polyline vs Polygon intersection;SimpleRasterizer for acceleration --- src/com/esri/core/geometry/Envelope2D.java | 43 +- src/com/esri/core/geometry/JsonReader.java | 2 +- .../core/geometry/OperatorDistanceLocal.java | 33 +- .../geometry/OperatorIntersectionCursor.java | 1 + .../geometry/RasterizedGeometry2DImpl.java | 552 +++++++++++++++++- .../esri/core/geometry/SimpleRasterizer.java | 377 ++++++++++++ src/com/esri/core/geometry/Simplificator.java | 3 +- .../geometry/StridedIndexTypeCollection.java | 23 +- .../com/esri/core/geometry/TestDistance.java | 8 + .../esri/core/geometry/TestIntersection.java | 12 +- unittest/com/esri/core/geometry/TestOGC.java | 22 +- .../geometry/TestRasterizedGeometry2D.java | 134 +++++ .../esri/core/geometry/TestSerialization.java | 46 +- .../esri/core/geometry/savedEnvelope2D.txt | Bin 0 -> 115 bytes 14 files changed, 1228 insertions(+), 28 deletions(-) create mode 100644 src/com/esri/core/geometry/SimpleRasterizer.java create mode 100644 unittest/com/esri/core/geometry/TestRasterizedGeometry2D.java create mode 100644 unittest/com/esri/core/geometry/savedEnvelope2D.txt diff --git a/src/com/esri/core/geometry/Envelope2D.java b/src/com/esri/core/geometry/Envelope2D.java index a3f09cf5..30aa184b 100644 --- a/src/com/esri/core/geometry/Envelope2D.java +++ b/src/com/esri/core/geometry/Envelope2D.java @@ -24,17 +24,21 @@ package com.esri.core.geometry; +import java.io.IOException; +import java.io.ObjectStreamException; +import java.io.Serializable; + /** * An axis parallel 2-dimensional rectangle. */ -public final class Envelope2D { - - private final int XLESSXMIN = 1; +public final class Envelope2D implements Serializable { + private static final long serialVersionUID = 1L; + private final static int XLESSXMIN = 1; // private final int XGREATERXMAX = 2; - private final int YLESSYMIN = 4; + private final static int YLESSYMIN = 4; // private final int YGREATERYMAX = 8; - private final int XMASK = 3; - private final int YMASK = 12; + private final static int XMASK = 3; + private final static int YMASK = 12; public double xmin; @@ -994,16 +998,28 @@ public boolean isPointOnBoundary(Point2D pt, double tolerance) { || Math.abs(pt.y - ymax) <= tolerance; } + /** + * Calculates minimum distance from this envelope to the other. + * Returns 0 for empty envelopes. + */ public double distance(/* const */Envelope2D other) { return Math.sqrt(sqrDistance(other)); } + /** + * Calculates minimum distance from this envelope to the point. + * Returns 0 for empty envelopes. + */ public double distance(Point2D pt2D) { return Math.sqrt(sqrDistance(pt2D)); } + /** + * Calculates minimum squared distance from this envelope to the other. + * Returns 0 for empty envelopes. + */ public double sqrDistance(Envelope2D other) { double dx = 0; @@ -1029,6 +1045,10 @@ public double sqrDistance(Envelope2D other) return dx * dx + dy * dy; } + /** + * Calculates minimum squared distance from this envelope to the point. + * Returns 0 for empty envelopes. + */ public double sqrDistance(Point2D pt2D) { double dx = 0; @@ -1071,4 +1091,15 @@ public void queryIntervalY(Envelope1D env1D) env1D.setCoords(ymin, ymax); } } + + private void writeObject(java.io.ObjectOutputStream out) throws IOException { + out.defaultWriteObject(); + } + private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException { + in.defaultReadObject(); + } + private void readObjectNoData() throws ObjectStreamException { + setEmpty(); + } + } diff --git a/src/com/esri/core/geometry/JsonReader.java b/src/com/esri/core/geometry/JsonReader.java index 065ecafe..10e4d0c7 100644 --- a/src/com/esri/core/geometry/JsonReader.java +++ b/src/com/esri/core/geometry/JsonReader.java @@ -315,7 +315,7 @@ Object getCurrentObject() { throw new GeometryException("invalid call"); } - return m_jsonObject.get(m_keys[m_currentIndex]); + return m_jsonObject.opt(m_keys[m_currentIndex]); } boolean next() { diff --git a/src/com/esri/core/geometry/OperatorDistanceLocal.java b/src/com/esri/core/geometry/OperatorDistanceLocal.java index 12bc7c48..76ac6a35 100644 --- a/src/com/esri/core/geometry/OperatorDistanceLocal.java +++ b/src/com/esri/core/geometry/OperatorDistanceLocal.java @@ -41,6 +41,9 @@ public double execute(Geometry geom1, Geometry geom2, Geometry geometryA = geom1; Geometry geometryB = geom2; + if (geometryA.isEmpty() || geometryB.isEmpty()) + return NumberUtils.TheNaN; + Polygon polygonA; Polygon polygonB; MultiPoint multiPointA; @@ -49,23 +52,41 @@ public double execute(Geometry geom1, Geometry geom2, // if geometryA is an envelope use a polygon instead (if geom1 was // folded, then geometryA will already be a polygon) // if geometryA is a point use a multipoint instead - if (geometryA.getType().equals(Geometry.Type.Point)) { + Geometry.Type gtA = geometryA.getType(); + Geometry.Type gtB = geometryB.getType(); + if (gtA == Geometry.Type.Point) { + if (gtB == Geometry.Type.Point) { + return Point2D.distance(((Point)geometryA).getXY(), ((Point)geometryB).getXY()); + } + else if (gtB == Geometry.Type.Envelope) { + Envelope2D envB = new Envelope2D(); + geometryB.queryEnvelope2D(envB); + return envB.distance(((Point)geometryA).getXY()); + } + multiPointA = new MultiPoint(); multiPointA.add((Point) geometryA); geometryA = multiPointA; - } else if (geometryA.getType().equals(Geometry.Type.Envelope)) { + } else if (gtA == Geometry.Type.Envelope) { + if (gtB == Geometry.Type.Envelope) { + Envelope2D envA = new Envelope2D(); + geometryA.queryEnvelope2D(envA); + Envelope2D envB = new Envelope2D(); + geometryB.queryEnvelope2D(envB); + return envB.distance(envA); + } polygonA = new Polygon(); polygonA.addEnvelope((Envelope) geometryA, false); - geometryB = polygonA; + geometryA = polygonA; } // if geom_2 is an envelope use a polygon instead // if geom_2 is a point use a multipoint instead - if (geometryB.getType().equals(Geometry.Type.Point)) { + if (gtB == Geometry.Type.Point) { multiPointB = new MultiPoint(); multiPointB.add((Point) geometryB); geometryB = multiPointB; - } else if (geometryB.getType().equals(Geometry.Type.Envelope)) { + } else if (gtB == Geometry.Type.Envelope) { polygonB = new Polygon(); polygonB.addEnvelope((Envelope) geometryB, false); geometryB = polygonB; @@ -404,7 +425,7 @@ private boolean weakIntersectionTest_(/* const */Geometry geometryA, /* const */ double calculate(/* const */Geometry geometryA, /* const */ Geometry geometryB) { if (geometryA.isEmpty() || geometryB.isEmpty()) - return 0.0; + return NumberUtils.TheNaN; geometryA.queryEnvelope2D(m_env2DgeometryA); geometryB.queryEnvelope2D(m_env2DgeometryB); diff --git a/src/com/esri/core/geometry/OperatorIntersectionCursor.java b/src/com/esri/core/geometry/OperatorIntersectionCursor.java index e23c3e8a..9d5437dc 100644 --- a/src/com/esri/core/geometry/OperatorIntersectionCursor.java +++ b/src/com/esri/core/geometry/OperatorIntersectionCursor.java @@ -607,6 +607,7 @@ Geometry tryFastIntersectPolylinePolygon_(Polyline polyline, Polygon polygon) { resultPolylineImpl.addSegment(resSeg, false); state = stateAddSegment; + inCount = 0; } else { status = analyseClipSegment_(polygon, resSeg, tolerance); diff --git a/src/com/esri/core/geometry/RasterizedGeometry2DImpl.java b/src/com/esri/core/geometry/RasterizedGeometry2DImpl.java index c5252e20..cd9f445f 100644 --- a/src/com/esri/core/geometry/RasterizedGeometry2DImpl.java +++ b/src/com/esri/core/geometry/RasterizedGeometry2DImpl.java @@ -24,14 +24,560 @@ package com.esri.core.geometry; -abstract class RasterizedGeometry2DImpl extends RasterizedGeometry2D { +import java.io.*; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + +import com.esri.core.geometry.Envelope2D; +import com.esri.core.geometry.Geometry; +import com.esri.core.geometry.GeometryException; +import com.esri.core.geometry.NumberUtils; +import com.esri.core.geometry.Point2D; +import com.esri.core.geometry.Segment; +import com.esri.core.geometry.SegmentIteratorImpl; +import com.esri.core.geometry.SimpleRasterizer; + +final class RasterizedGeometry2DImpl extends RasterizedGeometry2D { + int[] m_bitmap; + int m_scanLineSize; + int m_width; + double m_dx; + double m_dy; + double m_x0; + double m_y0; + double m_toleranceXY; + double m_stroke_half_widthX_pix; + double m_stroke_half_widthY_pix; + double m_stroke_half_width; + + Envelope2D m_geomEnv;// envelope of the raster in world coordinates + Transformation2D m_transform; + int m_dbgTestCount; + SimpleRasterizer m_rasterizer; + ScanCallbackImpl m_callback; + + class ScanCallbackImpl implements SimpleRasterizer.ScanCallback { + int[] m_bitmap; + int m_scanlineWidth; + int m_color; + + public ScanCallbackImpl(int[] bitmap, int scanlineWidth) { + m_scanlineWidth = scanlineWidth; + m_bitmap = bitmap; + } + + public void setColor(SimpleRasterizer rasterizer, int color) { + m_color = color;// set new color + } + + @Override + public void drawScan(int y, int x, int numPxls) { + int x0 = x; + int x1 = x + numPxls; + if (x1 > m_width) + x1 = m_width; + + int scanlineStart = y * m_scanlineWidth; + for (int xx = x0; xx < x1; xx++) { + m_bitmap[scanlineStart + (xx >> 4)] |= m_color << ((xx & 15) * 2);// 2 + // bit + // per + // color + } + } + } + + void fillMultiPath(SimpleRasterizer rasterizer, Transformation2D trans, MultiPathImpl polygon, boolean isWinding) { + SegmentIteratorImpl segIter = polygon.querySegmentIterator(); + Point2D p1 = new Point2D(); + Point2D p2 = new Point2D(); + while (segIter.nextPath()) { + while (segIter.hasNextSegment()) { + Segment seg = segIter.nextSegment(); + if (seg.getType() != Geometry.Type.Line) + throw new GeometryException("internal error"); // TODO: + // densify + // the + // segment + // here + trans.transform(seg.getStartXY(), p1); + trans.transform(seg.getEndXY(), p2); + m_rasterizer.addEdge(p1.x, p1.y, p2.x, p2.y); + } + } + + m_rasterizer.renderEdges(isWinding ? SimpleRasterizer.WINDING : SimpleRasterizer.EVEN_ODD); + } + + void fillPoints(SimpleRasterizer rasterizer, MultiPointImpl geom, double stroke_half_width) { + throw new GeometryException("internal error"); + } + + void fillConvexPolygon(SimpleRasterizer rasterizer, Point2D[] fan, int len) { + for (int i = 1, n = len; i < n; i++) { + rasterizer.addEdge(fan[i-1].x, fan[i-1].y, fan[i].x, fan[i].y); + } + rasterizer.addEdge(fan[len-1].x, fan[len-1].y, fan[0].x, fan[0].y); + m_rasterizer.renderEdges(SimpleRasterizer.EVEN_ODD); + } + + void fillEnvelope(SimpleRasterizer rasterizer, Envelope2D envIn) { + /*if (!m_identity) { + Point2D fan[] = new Point2D[4]; + envIn.queryCorners(fan); + fillConvexPolygon(rasterizer, fan, 4); + return; + }*/ + + Envelope2D env = new Envelope2D(0, 0, m_width, m_width); + if (!env.intersect(envIn)) + return; + + int x0 = (int) Math.round(env.xmin); + int x = (int) Math.round(env.xmax); + + int xn = NumberUtils.snap(x0, 0, m_width); + int xm = NumberUtils.snap(x, 0, m_width); + if (x0 < m_width && xn < xm) { + int y0 = (int) Math.round(env.ymin); + int y1 = (int) Math.round(env.ymax); + y0 = NumberUtils.snap(y0, 0, m_width); + y1 = NumberUtils.snap(y1, 0, m_width); + if (y0 < m_width) { + for (int y = y0; y < y1; y++) { + m_rasterizer.callback_.drawScan(y, xn, xm - xn); + } + } + } + } + + void strokeDrawPolyPath(SimpleRasterizer rasterizer, + MultiPathImpl polyPath, double tol) { + + Point2D[] fan = new Point2D[4]; + for (int i = 0; i < fan.length; i++) + fan[i] = new Point2D(); + + SegmentIteratorImpl segIter = polyPath.querySegmentIterator(); + double strokeHalfWidth = m_transform.transform(tol) + 1.5; + double shortSegment = 0.5; + Point2D vec = new Point2D(); + Point2D vecA = new Point2D(); + Point2D vecB = new Point2D(); + + // TODO check this Java workaroung + Point2D ptStart = new Point2D(); + Point2D ptEnd = new Point2D(); + Envelope2D segEnv = new Envelope2D(); + Point2D ptOld = new Point2D(); + while (segIter.nextPath()) { + boolean hasFan = false; + boolean first = true; + ptOld.setCoords(0, 0); + while (segIter.hasNextSegment()) { + Segment seg = segIter.nextSegment(); + ptStart.x = seg.getStartX(); + ptStart.y = seg.getStartY();// Point2D ptStart = + // seg.getStartXY(); + ptEnd.x = seg.getEndX(); + ptEnd.y = seg.getEndY();// Point2D ptEnd = seg.getEndXY(); + segEnv.setEmpty(); + segEnv.merge(ptStart.x, ptStart.y); + segEnv.mergeNE(ptEnd.x, ptEnd.y); + if (!m_geomEnv.isIntersectingNE(segEnv)) { + if (hasFan) { + fillConvexPolygon(rasterizer, fan, 4); + hasFan = false; + } + first = true; + continue; + } + + m_transform.transform(ptEnd, ptEnd); + + if (first) { + m_transform.transform(ptStart, ptStart); + ptOld.setCoords(ptStart); + first = false; + } else { + ptStart.setCoords(ptOld); + } + + vec.sub(ptEnd, ptStart); + double len = vec.length(); + boolean bShort = len < shortSegment; + if (len == 0) { + vec.setCoords(1.0, 0); + len = 1.0; + continue; + } + + if (!bShort) + ptOld.setCoords(ptEnd); + + vec.scale(strokeHalfWidth / len); + vecA.setCoords(-vec.y, vec.x); + vecB.setCoords(vec.y, -vec.x); + ptStart.sub(vec); + ptEnd.add(vec); + fan[0].add(ptStart, vecA); + fan[1].add(ptStart, vecB); + fan[2].add(ptEnd, vecB); + fan[3].add(ptEnd, vecA); + if (!bShort) + fillConvexPolygon(rasterizer, fan, 4); + else + hasFan = true; + } + if (hasFan) + fillConvexPolygon(rasterizer, fan, 4); + } + } + + int worldToPixX(double x) { + return (int) Math.round(x * m_dx + m_x0); + } + + int worldToPixY(double y) { + return (int) Math.round(y * m_dy + m_y0); + } + + RasterizedGeometry2DImpl(Geometry geom, double toleranceXY, + int rasterSizeBytes) { + // //_ASSERT(CanUseAccelerator(geom)); + init((MultiVertexGeometryImpl) geom._getImpl(), toleranceXY, + rasterSizeBytes); + } + static RasterizedGeometry2DImpl createImpl(Geometry geom, double toleranceXY, int rasterSizeBytes) { - return null; + RasterizedGeometry2DImpl rgImpl = new RasterizedGeometry2DImpl(geom, + toleranceXY, rasterSizeBytes); + rgImpl.init((MultiVertexGeometryImpl) geom._getImpl(), toleranceXY, + rasterSizeBytes); + return rgImpl; + } + + private RasterizedGeometry2DImpl(MultiVertexGeometryImpl geom, + double toleranceXY, int rasterSizeBytes) { + init(geom, toleranceXY, rasterSizeBytes); } static RasterizedGeometry2DImpl createImpl(MultiVertexGeometryImpl geom, double toleranceXY, int rasterSizeBytes) { - return null; + RasterizedGeometry2DImpl rgImpl = new RasterizedGeometry2DImpl(geom, + toleranceXY, rasterSizeBytes); + rgImpl.init(geom, toleranceXY, rasterSizeBytes); + return rgImpl; + } + + void init(MultiVertexGeometryImpl geom, double toleranceXY, + int rasterSizeBytes) { + // _ASSERT(CanUseAccelerator(geom)); + m_width = Math.max((int) (Math.sqrt(rasterSizeBytes) * 2 + 0.5), 64); + m_scanLineSize = (m_width * 2 + 31) / 32; // 2 bits per pixel + m_geomEnv = new Envelope2D(); + + m_toleranceXY = toleranceXY; + + // calculate bitmap size + int size = 0; + int width = m_width; + int scanLineSize = m_scanLineSize; + while (width >= 8) { + size += width * scanLineSize; + width /= 2; + scanLineSize = (width * 2 + 31) / 32; + } + + // allocate the bitmap, that contains the base and the mip-levels + m_bitmap = new int[size]; + for (int i = 0; i < size; i++) + m_bitmap[i] = 0; + + m_rasterizer = new SimpleRasterizer(); + ScanCallbackImpl callback = new ScanCallbackImpl(m_bitmap, + m_scanLineSize); + m_callback = callback; + m_rasterizer.setup(m_width, m_width, callback); + geom.queryEnvelope2D(m_geomEnv); + if (m_geomEnv.getWidth() > m_width * m_geomEnv.getHeight() + || m_geomEnv.getHeight() > m_geomEnv.getWidth() * m_width) { + // the geometry is thin and the rasterizer is not needed. + } + m_geomEnv.inflate(toleranceXY, toleranceXY); + Envelope2D worldEnv = new Envelope2D(); + + Envelope2D pixEnv = Envelope2D + .construct(1, 1, m_width - 2, m_width - 2); + + double minWidth = toleranceXY * pixEnv.getWidth(); // min width is such + // that the size of + // one pixel is + // equal to the + // tolerance + double minHeight = toleranceXY * pixEnv.getHeight(); + + worldEnv.setCoords(m_geomEnv.getCenter(), + Math.max(minWidth, m_geomEnv.getWidth()), + Math.max(minHeight, m_geomEnv.getHeight())); + + m_stroke_half_widthX_pix = worldEnv.getWidth() / pixEnv.getWidth(); + m_stroke_half_widthY_pix = worldEnv.getHeight() / pixEnv.getHeight(); + + // The stroke half width. Later it will be inflated to account for + // pixels size. + m_stroke_half_width = m_toleranceXY; + + m_transform = new Transformation2D(); + m_transform.initializeFromRect(worldEnv, pixEnv);// geom to pixels + + Transformation2D identityTransform = new Transformation2D(); + + switch (geom.getType().value()) { + case Geometry.GeometryType.MultiPoint: + callback.setColor(m_rasterizer, 2); + fillPoints(m_rasterizer, (MultiPointImpl) geom, m_stroke_half_width); + break; + case Geometry.GeometryType.Polyline: + callback.setColor(m_rasterizer, 2); + strokeDrawPolyPath(m_rasterizer, (MultiPathImpl) geom._getImpl(), + m_stroke_half_width); + break; + case Geometry.GeometryType.Polygon: { + boolean isWinding = false;// NOTE: change when winding is supported + callback.setColor(m_rasterizer, 1); + fillMultiPath(m_rasterizer, m_transform, (MultiPathImpl) geom, isWinding); + callback.setColor(m_rasterizer, 2); + strokeDrawPolyPath(m_rasterizer, (MultiPathImpl) geom._getImpl(), + m_stroke_half_width); + } + break; + } + + m_dx = m_transform.xx; + m_dy = m_transform.yy; + m_x0 = m_transform.xd; + m_y0 = m_transform.yd; + buildLevels(); + } + + boolean tryRenderAsSmallEnvelope_(Envelope2D env) { + if (!env.isIntersecting(m_geomEnv)) + return true; + + Envelope2D envPix = new Envelope2D(); + envPix.setCoords(env); + m_transform.transform(env); + double strokeHalfWidthPixX = m_stroke_half_widthX_pix; + double strokeHalfWidthPixY = m_stroke_half_widthY_pix; + if (envPix.getWidth() > 2 * strokeHalfWidthPixX + 1 + || envPix.getHeight() > 2 * strokeHalfWidthPixY + 1) + return false; + + // This envelope is too narrow/small, so that it can be just drawn as a + // rectangle using only boundary color. + + envPix.inflate(strokeHalfWidthPixX, strokeHalfWidthPixY); + envPix.xmax += 1.0; + envPix.ymax += 1.0;// take into account that it does not draw right and + // bottom edges. + + m_callback.setColor(m_rasterizer, 2); + fillEnvelope(m_rasterizer, envPix); + return true; + } + + void buildLevels() { + int iStart = 0; + int iStartNext = m_width * m_scanLineSize; + int width = m_width; + int widthNext = m_width / 2; + int scanLineSize = m_scanLineSize; + int scanLineSizeNext = (widthNext * 2 + 31) / 32; + while (width > 8) { + for (int iy = 0; iy < widthNext; iy++) { + int iysrc1 = iy * 2; + int iysrc2 = iy * 2 + 1; + for (int ix = 0; ix < widthNext; ix++) { + int ixsrc1 = ix * 2; + int ixsrc2 = ix * 2 + 1; + int divix1 = ixsrc1 >> 4; + int modix1 = (ixsrc1 & 15) * 2; + int divix2 = ixsrc2 >> 4; + int modix2 = (ixsrc2 & 15) * 2; + int res = (m_bitmap[iStart + scanLineSize * iysrc1 + divix1] >> modix1) & 3; + res |= (m_bitmap[iStart + scanLineSize * iysrc1 + divix2] >> modix2) & 3; + res |= (m_bitmap[iStart + scanLineSize * iysrc2 + divix1] >> modix1) & 3; + res |= (m_bitmap[iStart + scanLineSize * iysrc2 + divix2] >> modix2) & 3; + int divixDst = ix >> 4; + int modixDst = (ix & 15) * 2; + m_bitmap[iStartNext + scanLineSizeNext * iy + divixDst] |= res << modixDst; + } + } + + width = widthNext; + scanLineSize = scanLineSizeNext; + iStart = iStartNext; + widthNext = width / 2; + scanLineSizeNext = (widthNext * 2 + 31) / 32; + iStartNext = iStart + scanLineSize * width; + } + } + + @Override + public HitType queryPointInGeometry(double x, double y) { + int ix = worldToPixX(x); + int iy = worldToPixY(y); + if (ix < 0 || ix >= m_width || iy < 0 || iy >= m_width) + return HitType.Outside; + int divix = ix >> 4; + int modix = (ix & 15) * 2; + int res = (m_bitmap[m_scanLineSize * iy + divix] >> modix) & 3; + if (res == 0) + return HitType.Outside; + else if (res == 1) + return HitType.Inside; + else + return HitType.Border; + } + + @Override + public HitType queryEnvelopeInGeometry(Envelope2D env) { + if (!env.intersect(m_geomEnv)) + return com.esri.core.geometry.RasterizedGeometry2D.HitType.Outside; + int ixmin = worldToPixX(env.xmin); + int ixmax = worldToPixX(env.xmax); + int iymin = worldToPixY(env.ymin); + int iymax = worldToPixY(env.ymax); + if (ixmin < 0) + ixmin = 0; + if (iymin < 0) + iymin = 0; + if (ixmax >= m_width) + ixmax = m_width - 1; + if (iymax >= m_width) + iymax = m_width - 1; + + if (ixmin > ixmax || iymin > iymax) + return HitType.Outside; + + int area = Math.max(ixmax - ixmin, 1) * Math.max(iymax - iymin, 1); + int iStart = 0; + int scanLineSize = m_scanLineSize; + int width = m_width; + int res = 0; + while (true) { + if (area < 32 || width < 16) { + for (int iy = iymin; iy <= iymax; iy++) { + for (int ix = ixmin; ix <= ixmax; ix++) { + int divix = ix >> 4; + int modix = (ix & 15) * 2; + res = (m_bitmap[iStart + scanLineSize * iy + divix] >> modix) & 3; // read + // two + // bit + // color. + if (res > 1) + return HitType.Border; + } + } + + if (res == 0) + return HitType.Outside; + else if (res == 1) + return HitType.Inside; + } + + iStart += scanLineSize * width; + width /= 2; + scanLineSize = (width * 2 + 31) / 32; + ixmin /= 2; + iymin /= 2; + ixmax /= 2; + iymax /= 2; + area = Math.max(ixmax - ixmin, 1) * Math.max(iymax - iymin, 1); + } + } + + @Override + double getToleranceXY() { + return m_toleranceXY; + } + + @Override + int getRasterSize() { + return m_width * m_scanLineSize; + } + + @Override + boolean dbgSaveToBitmap(String fileName) { + try { + FileOutputStream outfile = new FileOutputStream(fileName); + + int height = m_width; + int width = m_width; + int sz = 14 + 40 + 4 * m_width * height; + // Write the BITMAPFILEHEADER + ByteBuffer byteBuffer = ByteBuffer.allocate(sz); + byteBuffer.order(ByteOrder.LITTLE_ENDIAN); + // byteBuffer.put((byte) 'M'); + byteBuffer.put((byte) 66); + byteBuffer.put((byte) 77); + // fwrite("BM", 1, 2, f); //bfType + byteBuffer.putInt(sz); + // fwrite(&sz, 1, 4, f);//bfSize + short zero16 = 0; + byteBuffer.putShort(zero16); + // fwrite(&zero16, 1, 2, f);//bfReserved1 + byteBuffer.putShort(zero16); + // fwrite(&zero16, 1, 2, f);//bfReserved2 + int offset = 14 + 40; + byteBuffer.putInt(offset); + // fwrite(&offset, 1, 4, f);//bfOffBits + + // Write the BITMAPINFOHEADER + int biSize = 40; + int biWidth = width; + int biHeight = -height; + short biPlanes = 1; + short biBitCount = 32; + int biCompression = 0; + int biSizeImage = 4 * width * height; + int biXPelsPerMeter = 0; + int biYPelsPerMeter = 0; + int biClrUsed = 0; + int biClrImportant = 0; + byteBuffer.putInt(biSize); + byteBuffer.putInt(biWidth); + byteBuffer.putInt(biHeight); + byteBuffer.putShort(biPlanes); + byteBuffer.putShort(biBitCount); + byteBuffer.putInt(biCompression); + byteBuffer.putInt(biSizeImage); + byteBuffer.putInt(biXPelsPerMeter); + byteBuffer.putInt(biYPelsPerMeter); + byteBuffer.putInt(biClrUsed); + byteBuffer.putInt(biClrImportant); + + int colors[] = { 0xFFFFFFFF, 0xFF000000, 0xFFFF0000, 0xFF00FF00 }; + // int32_t* rgb4 = (int32_t*)malloc(biSizeImage); + for (int y = 0; y < height; y++) { + int scanlineIn = y * ((width * 2 + 31) / 32); + int scanlineOut = offset + width * y; + + for (int x = 0; x < width; x++) { + int res = (m_bitmap[scanlineIn + (x >> 4)] >> ((x & 15) * 2)) & 3; + byteBuffer.putInt(colors[res]); + } + } + + byte[] b = byteBuffer.array(); + outfile.write(b); + outfile.close(); + return true; + } catch (IOException ex) { + return false; + + } } } diff --git a/src/com/esri/core/geometry/SimpleRasterizer.java b/src/com/esri/core/geometry/SimpleRasterizer.java new file mode 100644 index 00000000..3ca4d304 --- /dev/null +++ b/src/com/esri/core/geometry/SimpleRasterizer.java @@ -0,0 +1,377 @@ +/* + Copyright 2013 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ + +package com.esri.core.geometry; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; + +/** + * Simple scanline rasterizer. Caller provides a callback to draw pixels to actual surface. + * + */ +public class SimpleRasterizer { + + /** + * Even odd fill rule + */ + public final static int EVEN_ODD = 0; + /** + * Winding fill rule + */ + public final static int WINDING = 1; + + public static interface ScanCallback { + /** + * Rasterizer calls this method for each scan it produced + * @param y the Y coordinate for the scan + * @param x the X coordinate for the scan + * @param numPxls the number of pixels in the scan + */ + public abstract void drawScan(int y, int x, int numPxls); + } + + public SimpleRasterizer() { + width_ = -1; + height_ = -1; + } + + /** + * Sets up the rasterizer. + */ + public void setup(int width, int height, ScanCallback callback) + { + width_ = width; height_ = height; + ySortedEdges_ = null; + activeEdgesTable_ = null; + numEdges_ = 0; + callback_ = callback; + startAddingEdges(); + } + + public int getWidth() { + return width_; + } + + public int getHeight() { + return height_; + } + + /** + * Adds edges of a triangle. + */ + public void addTriangle(double x1, double y1, double x2, double y2, double x3, double y3) { + addEdge(x1, y1, x2, y2); + addEdge(x2, y2, x3, y3); + addEdge(x1, y1, x3, y3); + } + + /** + * Adds edges of the ring to the rasterizer. + * @param xy interleaved coordinates x1, y1, x2, y2,... + */ + public void addRing(double xy[]) { + for (int i = 2; i < xy.length; i += 2) { + addEdge(xy[i-2], xy[i - 1], xy[i], xy[i + 1]); + } + } + + /** + * Call before starting the edges. + * For example to render two polygons that consist of a single ring: + * startAddingEdges(); + * addRing(...); + * renderEdges(Rasterizer.EVEN_ODD); + * addRing(...); + * renderEdges(Rasterizer.EVEN_ODD); + */ + public void startAddingEdges() { + if (numEdges_ > 0) { + ySortedEdges_ = null; + activeEdgesTable_ = null; + } + + minY_ = height_; + maxY_ = -1; + numEdges_ = 0; + } + + /** + * Renders all edges added so far, and removes them. + * @param fillMode + */ + public void renderEdges(int fillMode) { + evenOdd_ = fillMode == EVEN_ODD; + for (int line = minY_; line <= maxY_; line++) { + advanceAET_(); + addNewEdgesToAET_(line); + emitScans_(); + } + + numEdges_ = 0; + if (activeEdgesTable_ != null) + activeEdgesTable_.clear(); + + startAddingEdges();//reset for new edges + } + + /** + * Add a single edge. + * @param x1 + * @param y1 + * @param x2 + * @param y2 + */ + public void addEdge(double x1, double y1, double x2, double y2) { + if (y1 == y2) + return; + int dir = 1; + if (y1 > y2) { + double temp; + temp = x1; x1 = x2; x2 = temp; + temp = y1; y1 = y2; y2 = temp; + dir = -1; + } + + if (y2 < 0 || y1 >= height_) + return; + + if (x1 < 0 && x2 < 0) + { + x1 = -1; x2 = -1; + } + else if (x1 >= width_ && x2 >= width_) + { + x1 = width_; x2 = width_; + } + + //clip to extent + double dxdy = (x2 - x1) / (y2 - y1); + + if (y2 > height_) { + y2 = height_; + x2 = dxdy * (y2 - y1) + x1; + } + + if (y1 < 0) { + x1 = dxdy * (0 - y1) + x1; + y1 = 0; + } + + //We know that dxdy != 0, otherwise it would return earlier + //do not clip x unless it is too small or too big + int bigX = Math.max(width_ + 1, 0x7fffff); + if (x1 < -0x7fffff) { + + y1 = (0 - x1) / dxdy + y1; + x1 = 0; + } + else if (x1 > bigX) { + //we know that dx != 0, otherwise it would return earlier + y1 = (width_ - x1) / dxdy + y1; + x1 = width_; + } + + if (x2 < -0x7fffff) { + //we know that dx != 0, otherwise it would return earlier + y2 = (0 - x1) / dxdy + y1; + x2 = 0; + } + else if (x2 > bigX) { + //we know that dx != 0, otherwise it would return earlier + y2 = (width_ - x1) / dxdy + y1; + x2 = width_; + } + + int ystart = (int)y1; + int yend = (int)y2; + if (ystart == yend) + return; + + Edge e; + if (recycledEdges_ != null && recycledEdges_.size() > 0) + e = recycledEdges_.remove(recycledEdges_.size() - 1); + else + e = new Edge(); + + e.x = (long)(x1 * 4294967296.0); + e.y = ystart; + e.ymax = yend; + e.dxdy = (long)(dxdy * 4294967296.0); + e.dir = dir; + + if (ySortedEdges_ == null) { + ySortedEdges_ = new ArrayList>(); + ySortedEdges_.ensureCapacity(height_); + for (int i = 0; i < height_; i++) { + ySortedEdges_.add(null); + } + } + + if (ySortedEdges_.get(e.y) == null) { + ySortedEdges_.set(e.y, new ArrayList()); + } + + ySortedEdges_.get(e.y).add(e); + if (e.y < minY_) + minY_ = e.y; + + if (e.ymax > maxY_) + maxY_ = e.ymax; + } + + class Edge { + long x; + long dxdy; + int y; + int ymax; + int dir; + } + + private void advanceAET_() { + if (activeEdgesTable_ != null && activeEdgesTable_.size() > 0) { + for (int i = 0, n = activeEdgesTable_.size(); i < n; i++) { + Edge e = activeEdgesTable_.get(i); + e.y++; + if (e.y == e.ymax) { + if (recycledEdges_ == null) { + recycledEdges_ = new ArrayList(); + } + + recycledEdges_.add(e); + activeEdgesTable_.set(i, null); + continue; + } + + e.x += e.dxdy; + } + } + } + + private void addNewEdgesToAET_(int y) { + if (y >= ySortedEdges_.size()) + return; + + if (activeEdgesTable_ == null) + activeEdgesTable_ = new ArrayList(); + + ArrayList edgesOnLine = ySortedEdges_.get(y); + if (edgesOnLine != null) { + for (int i = 0, n = edgesOnLine.size(); i < n; i++) { + activeEdgesTable_.add(edgesOnLine.get(i)); + } + + edgesOnLine.clear(); + } + } + + static int snap_(int x, int mi, int ma) { + return x < mi ? mi : x > ma ? ma : x; + } + private void emitScans_() { + sortAET_(); + + if (activeEdgesTable_ == null || activeEdgesTable_.size() == 0) + return; + + int w = 0; + Edge e0 = activeEdgesTable_.get(0); + int x0 = (int)(e0.x >> 32); + for (int i = 1; i < activeEdgesTable_.size(); i++) { + Edge e = activeEdgesTable_.get(i); + if (evenOdd_) + w ^= 1; + else + w += e.dir; + + if (e.x > e0.x) { + int x = (int)(e.x >> 32); + if (w == 1) { + int xx0 = snap_(x0, 0, width_); + int xx = snap_(x, 0, width_); + if (xx > xx0 && xx0 < width_) { + callback_.drawScan(e.y, xx0, xx - xx0); + } + } + + e0 = e; + x0 = x; + } + } + } + + static class EdgeComparator implements Comparator { + @Override + public int compare(Edge o1, Edge o2) { + if (o1 == null) + return o2 == null ? 0 : 1; + else if (o2 == null) + return -1; + + return o1.x < o2.x ? -1 : o1.x > o2.x ? 1 : 0; + } + } + + private static EdgeComparator edgeCompare_ = new EdgeComparator(); + + private void sortAET_() { + if (!checkAETIsSorted_()) + { + Collections.sort(activeEdgesTable_, edgeCompare_); + while (activeEdgesTable_.size() > 0 && activeEdgesTable_.get(activeEdgesTable_.size() - 1) == null) + activeEdgesTable_.remove(activeEdgesTable_.size() - 1); + } + } + + private boolean checkAETIsSorted_() { + if (activeEdgesTable_ == null || activeEdgesTable_.size() == 0) + return true; + + Edge e0 = activeEdgesTable_.get(0); + if (e0 == null) + return false; + + for (int i = 1; i < activeEdgesTable_.size(); i++) { + Edge e = activeEdgesTable_.get(i); + if (e == null || e.x < e0.x) { + return false; + } + e0 = e; + } + + return true; + } + + private ArrayList recycledEdges_; + private ArrayList activeEdgesTable_; + private ArrayList> ySortedEdges_; + public ScanCallback callback_; + private int width_; + private int height_; + private int minY_; + private int maxY_; + private int numEdges_; + private boolean evenOdd_; +} diff --git a/src/com/esri/core/geometry/Simplificator.java b/src/com/esri/core/geometry/Simplificator.java index 1c6db3ce..88760528 100644 --- a/src/com/esri/core/geometry/Simplificator.java +++ b/src/com/esri/core/geometry/Simplificator.java @@ -512,7 +512,8 @@ private boolean _simplify() { coincidentCount = 0; } - vlistindex = m_sortedVertices.getNext(vlistindex); + if (vlistindex != -1)//vlistindex can be set to -1 after ProcessBunch call above + vlistindex = m_sortedVertices.getNext(vlistindex); } m_nextVertexToProcess = -1; diff --git a/src/com/esri/core/geometry/StridedIndexTypeCollection.java b/src/com/esri/core/geometry/StridedIndexTypeCollection.java index 90bfc9d5..ca631d98 100644 --- a/src/com/esri/core/geometry/StridedIndexTypeCollection.java +++ b/src/com/esri/core/geometry/StridedIndexTypeCollection.java @@ -95,7 +95,14 @@ int newElement() { int element = m_firstFree; if (element == -1) { if (m_last == m_capacity) { - grow_(m_capacity != 0 ? ((m_capacity + 1) * 3 / 2) : 1); + long newcap = m_capacity != 0 ? (((long)m_capacity + 1) * 3 / 2) : (long)1; + if (newcap > Integer.MAX_VALUE) + newcap = Integer.MAX_VALUE;//cannot grow past 2gb elements presently + + if (newcap == m_capacity) + throw new IndexOutOfBoundsException(); + + grow_(newcap); } element = ((m_last / m_blockSize) << m_blockPower) @@ -195,22 +202,25 @@ private boolean dbgdelete_(int element) { final static int[] st_sizes = {16, 32, 64, 128, 256, 512, 1024, 2048}; - private void grow_(int newsize) { + private void grow_(long newsize) { if (m_buffer == null) { m_buffer = new ArrayList(); } assert (newsize > m_capacity); - int nblocks = (newsize + m_blockSize - 1) / m_blockSize; - m_buffer.ensureCapacity(nblocks); + long nblocks = (newsize + m_blockSize - 1) / m_blockSize; + if (nblocks > Integer.MAX_VALUE) + throw new IndexOutOfBoundsException(); + + m_buffer.ensureCapacity((int)nblocks); if (nblocks == 1) { // When less than one block is needed we allocate smaller arrays // than m_realBlockSize to avoid initialization cost. int oldsz = m_capacity > 0 ? m_capacity : 0; assert (oldsz < newsize); int i = 0; - int realnewsize = newsize * m_realStride; + int realnewsize = (int)newsize * m_realStride; while (realnewsize > st_sizes[i]) // get the size to allocate. Using fixed sizes to reduce // fragmentation. @@ -225,6 +235,9 @@ private void grow_(int newsize) { } m_capacity = b.length / m_realStride; } else { + if (nblocks * m_blockSize > Integer.MAX_VALUE) + throw new IndexOutOfBoundsException(); + if (m_buffer.size() == 1) { if (m_buffer.get(0).length < m_realBlockSize) { // resize the first buffer to ensure it is equal the diff --git a/unittest/com/esri/core/geometry/TestDistance.java b/unittest/com/esri/core/geometry/TestDistance.java index 9fc159d2..b531bb17 100644 --- a/unittest/com/esri/core/geometry/TestDistance.java +++ b/unittest/com/esri/core/geometry/TestDistance.java @@ -62,6 +62,14 @@ public static void testDistanceBetweenTriangles() { assertTrue(0.0 < distance && distance < xSeparation + ySeparation); } + @Test + public static void testDistanceBetweenPointAndEnvelope() { + Envelope env = new Envelope(23,23, 23,23); + Point pt = new Point(30, 30); + double dist = GeometryEngine.distance(env, pt, null); // expect just under 10. + assertTrue(Math.abs(dist - 9.8994949) < 0.0001); + } + @Test public static void testDistanceBetweenHugeGeometries() { /* const */int N = 1000; // Should be even diff --git a/unittest/com/esri/core/geometry/TestIntersection.java b/unittest/com/esri/core/geometry/TestIntersection.java index 68b0766b..66e0bc49 100644 --- a/unittest/com/esri/core/geometry/TestIntersection.java +++ b/unittest/com/esri/core/geometry/TestIntersection.java @@ -991,5 +991,15 @@ public void testUnionTickTock() { Geometry result2 = res.next(); assertTrue(result.equals(result2)); } - + + @Test + public void testIntersectionIssueLinePoly1() { + String wkt1 = new String("polygon((0 0, 10 0, 10 10, 0 10, 0 0))"); + String wkt2 = new String("linestring(9 5, 10 5, 9 4, 8 3)"); + Geometry g1 = OperatorImportFromWkt.local().execute(0, Geometry.Type.Unknown, wkt1, null); + Geometry g2 = OperatorImportFromWkt.local().execute(0, Geometry.Type.Unknown, wkt2, null); + Geometry res = OperatorIntersection.local().execute(g1, g2, null, null); + assertTrue(((Polyline)res).getPathCount() == 1); + assertTrue(((Polyline)res).getPointCount() == 4); + } } diff --git a/unittest/com/esri/core/geometry/TestOGC.java b/unittest/com/esri/core/geometry/TestOGC.java index 3099767a..816813bd 100644 --- a/unittest/com/esri/core/geometry/TestOGC.java +++ b/unittest/com/esri/core/geometry/TestOGC.java @@ -704,14 +704,27 @@ public void testIsectTria1() { String wk2 = "polygon((0 1, 2 1, 0 3, 0 1))"; OGCGeometry g0 = OGCGeometry.fromText(wkt); OGCGeometry g1 = OGCGeometry.fromText(wk2); - g0.setSpatialReference(null); - g1.setSpatialReference(null); + g0.setSpatialReference(SpatialReference.create(4326)); + g1.setSpatialReference(SpatialReference.create(4326)); OGCGeometry rslt = g0.intersection(g1); assertTrue(rslt != null); assertTrue(rslt.geometryType().equals("Polygon")); + assertTrue(rslt.esriSR.getID() == 4326); String s = rslt.asText(); } + public void testIsectTriaJson1() throws JsonParseException, IOException { + String json1 = "{\"rings\":[[[1, 0], [3, 0], [1, 2], [1, 0]]], \"spatialReference\":{\"wkid\":4326}}"; + String json2 = "{\"rings\":[[[0, 1], [2, 1], [0, 3], [0, 1]]], \"spatialReference\":{\"wkid\":4326}}"; + OGCGeometry g0 = OGCGeometry.fromJson(json1); + OGCGeometry g1 = OGCGeometry.fromJson(json2); + OGCGeometry rslt = g0.intersection(g1); + assertTrue(rslt != null); + assertTrue(rslt.geometryType().equals("Polygon")); + assertTrue(rslt.esriSR.getID() == 4326); + String s = GeometryEngine.geometryToJson(rslt.getEsriSpatialReference().getID(), rslt.getEsriGeometry()); + } + public void testIsectTria2() { String wkt = "polygon((1 0, 3 0, 1 2, 1 0))"; String wk2 = "polygon((0 3, 2 1, 3 1, 0 3))"; @@ -731,12 +744,13 @@ public void testIsectTria3() { String wk2 = "polygon((2 2, 2 1, 3 1, 2 2))"; OGCGeometry g0 = OGCGeometry.fromText(wkt); OGCGeometry g1 = OGCGeometry.fromText(wk2); - g0.setSpatialReference(null); - g1.setSpatialReference(null); + g0.setSpatialReference(SpatialReference.create(4326)); + g1.setSpatialReference(SpatialReference.create(4326)); OGCGeometry rslt = g0.intersection(g1); assertTrue(rslt != null); assertTrue(rslt.dimension() == 0); assertTrue(rslt.geometryType().equals("Point")); + assertTrue(rslt.esriSR.getID() == 4326); String s = rslt.asText(); } diff --git a/unittest/com/esri/core/geometry/TestRasterizedGeometry2D.java b/unittest/com/esri/core/geometry/TestRasterizedGeometry2D.java new file mode 100644 index 00000000..a96528ca --- /dev/null +++ b/unittest/com/esri/core/geometry/TestRasterizedGeometry2D.java @@ -0,0 +1,134 @@ +package com.esri.core.geometry; + +import junit.framework.TestCase; +import org.junit.Test; + +import com.esri.core.geometry.Geometry.GeometryAccelerationDegree; + +public class TestRasterizedGeometry2D extends TestCase { + boolean rgHelper(RasterizedGeometry2D rg, MultiPath mp) { + SegmentIterator iter = mp.querySegmentIterator(); + while (iter.nextPath()) { + while (iter.hasNextSegment()) { + Segment seg = iter.nextSegment(); + int count = 20; + + for (int i = 0; i < count; i++) { + double t = (1.0 * i / count); + Point2D pt = seg.getCoord2D(t); + RasterizedGeometry2D.HitType hit = rg.queryPointInGeometry( + pt.x, pt.y); + if (hit != RasterizedGeometry2D.HitType.Border) + return false; + } + } + } + + if (mp.getType() != Geometry.Type.Polygon) + return true; + + Polygon poly = (Polygon) mp; + Envelope2D env = new Envelope2D(); + poly.queryEnvelope2D(env); + int count = 100; + for (int iy = 0; iy < count; iy++) { + double ty = 1.0 * iy / count; + double y = env.ymin * (1.0 - ty) + ty * env.ymax; + for (int ix = 0; ix < count; ix++) { + double tx = 1.0 * ix / count; + double x = env.xmin * (1.0 - tx) + tx * env.xmax; + + RasterizedGeometry2D.HitType hit = rg + .queryPointInGeometry(x, y); + PolygonUtils.PiPResult res = PolygonUtils.isPointInPolygon2D( + poly, new Point2D(x, y), 0); + if (res == PolygonUtils.PiPResult.PiPInside) { + boolean bgood = (hit == RasterizedGeometry2D.HitType.Border || hit == RasterizedGeometry2D.HitType.Inside); + if (!bgood) + return false; + } else if (res == PolygonUtils.PiPResult.PiPOutside) { + boolean bgood = (hit == RasterizedGeometry2D.HitType.Border || hit == RasterizedGeometry2D.HitType.Outside); + if (!bgood) + return false; + } else { + boolean bgood = (hit == RasterizedGeometry2D.HitType.Border); + if (!bgood) + return false; + } + } + } + + return true; + } + + @Test + public void test() { + { + Polygon poly = new Polygon(); + poly.startPath(10, 10); + poly.lineTo(100, 10); + poly.lineTo(100, 100); + poly.lineTo(10, 100); + + // create using move semantics. Usually we do not use this + // approach. + RasterizedGeometry2D rg = RasterizedGeometry2D + .create(poly, 0, 1024); + //rg.dbgSaveToBitmap("c:/temp/_dbg.bmp"); + RasterizedGeometry2D.HitType res; + res = rg.queryPointInGeometry(7, 10); + assertTrue(res == RasterizedGeometry2D.HitType.Outside); + res = rg.queryPointInGeometry(10, 10); + assertTrue(res == RasterizedGeometry2D.HitType.Border); + res = rg.queryPointInGeometry(50, 50); + assertTrue(res == RasterizedGeometry2D.HitType.Inside); + + assertTrue(rgHelper(rg, poly)); + } + + { + Polygon poly = new Polygon(); + // create a star (non-simple) + poly.startPath(1, 0); + poly.lineTo(5, 10); + poly.lineTo(9, 0); + poly.lineTo(0, 6); + poly.lineTo(10, 6); + + RasterizedGeometry2D rg = RasterizedGeometry2D + .create(poly, 0, 1024); + //rg.dbgSaveToBitmap("c:/temp/_dbg.bmp"); + RasterizedGeometry2D.HitType res; + res = rg.queryPointInGeometry(5, 5.5); + assertTrue(res == RasterizedGeometry2D.HitType.Outside); + res = rg.queryPointInGeometry(5, 8); + assertTrue(res == RasterizedGeometry2D.HitType.Inside); + res = rg.queryPointInGeometry(1.63, 0.77); + assertTrue(res == RasterizedGeometry2D.HitType.Inside); + res = rg.queryPointInGeometry(1, 3); + assertTrue(res == RasterizedGeometry2D.HitType.Outside); + res = rg.queryPointInGeometry(1.6, 0.1); + assertTrue(res == RasterizedGeometry2D.HitType.Outside); + assertTrue(rgHelper(rg, poly)); + } + + { + Polygon poly = new Polygon(); + // create a star (non-simple) + poly.startPath(1, 0); + poly.lineTo(5, 10); + poly.lineTo(9, 0); + poly.lineTo(0, 6); + poly.lineTo(10, 6); + + SpatialReference sr = SpatialReference.create(4326); + poly = (Polygon)OperatorSimplify.local().execute(poly, sr, true, null); + OperatorContains.local().accelerateGeometry(poly, sr, GeometryAccelerationDegree.enumMedium); + assertFalse(OperatorContains.local().execute(poly, new Point(5, 5.5), sr, null)); + assertTrue(OperatorContains.local().execute(poly, new Point(5, 8), sr, null)); + assertTrue(OperatorContains.local().execute(poly, new Point(1.63, 0.77), sr, null)); + assertFalse(OperatorContains.local().execute(poly, new Point(1, 3), sr, null)); + assertFalse(OperatorContains.local().execute(poly, new Point(1.6, 0.1), sr, null)); + } + } +} diff --git a/unittest/com/esri/core/geometry/TestSerialization.java b/unittest/com/esri/core/geometry/TestSerialization.java index 27587c34..289546da 100644 --- a/unittest/com/esri/core/geometry/TestSerialization.java +++ b/unittest/com/esri/core/geometry/TestSerialization.java @@ -2,6 +2,7 @@ import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; +import java.io.FileOutputStream; import java.io.InputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; @@ -291,4 +292,47 @@ public void testSerializeSR() { fail("Spatial Reference serialization failure"); } } - } + + @Test + public void testSerializeEnvelope2D() { + try { + ByteArrayOutputStream streamOut = new ByteArrayOutputStream(); + ObjectOutputStream oo = new ObjectOutputStream(streamOut); + Envelope2D env = new Envelope2D(1.213948734, 2.213948734, 11.213948734, 12.213948734); + oo.writeObject(env); + ByteArrayInputStream streamIn = new ByteArrayInputStream( + streamOut.toByteArray()); + ObjectInputStream ii = new ObjectInputStream(streamIn); + Envelope2D envRes = (Envelope2D)ii.readObject(); + assertTrue(envRes.equals(env)); + } catch (Exception ex) { + fail("Envelope2D serialization failure"); + } + +// try +// { +// FileOutputStream streamOut = new FileOutputStream( +// "c:/temp/savedEnvelope2D.txt"); +// ObjectOutputStream oo = new ObjectOutputStream(streamOut); +// Envelope2D e = new Envelope2D(177.123, 188.234, 999.122, 888.999); +// oo.writeObject(e); +// } +// catch(Exception ex) +// { +// fail("Envelope2D serialization failure"); +// } + + try { + InputStream s = TestSerialization.class + .getResourceAsStream("savedEnvelope2D.txt"); + ObjectInputStream ii = new ObjectInputStream(s); + Envelope2D e = (Envelope2D) ii + .readObject(); + assertTrue(e != null); + assertTrue(e.equals(new Envelope2D(177.123, 188.234, 999.122, 888.999))); + } catch (Exception ex) { + fail("Envelope2D serialization failure"); + } + } + +} diff --git a/unittest/com/esri/core/geometry/savedEnvelope2D.txt b/unittest/com/esri/core/geometry/savedEnvelope2D.txt new file mode 100644 index 0000000000000000000000000000000000000000..354682bc0804cf4992b71e4e7b8fecc74e164727 GIT binary patch literal 115 zcmZ4UmVvdnh(R$qKUXicxF}OEIlm}XFFiFsH?^dwQqMK7EHx*;Al1l)0RkAA8CYBx zSSoT8E5KA{9+0Yp@G5gN^C}7)`YnFmR=V)iAx-)H+)cWC4&BH9e%|J~$RWMG=Iz9c G;tBw4Rw=9i literal 0 HcmV?d00001 From 6686af168ade721ac14bfac42fc11e379c2cb9c0 Mon Sep 17 00:00:00 2001 From: Sergey Tolstov Date: Thu, 9 Jan 2014 10:14:51 -0800 Subject: [PATCH 074/196] Update JsonReader.java Inline fix for JSONArrayEnumerator.getCurrentObject() - use JSONArray.opt instead of JSONArray.get --- src/com/esri/core/geometry/JsonReader.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/com/esri/core/geometry/JsonReader.java b/src/com/esri/core/geometry/JsonReader.java index 10e4d0c7..342c6435 100644 --- a/src/com/esri/core/geometry/JsonReader.java +++ b/src/com/esri/core/geometry/JsonReader.java @@ -352,7 +352,7 @@ Object getCurrentObject() { throw new GeometryException("invalid call"); } - return m_jsonArray.get(m_currentIndex); + return m_jsonArray.opt(m_currentIndex); } boolean next() { From 38c70f7afd8180374908315b5736d228243fd8ac Mon Sep 17 00:00:00 2001 From: Sergey Tolstov Date: Thu, 23 Jan 2014 09:34:29 -0800 Subject: [PATCH 075/196] marked setXY public as well as some other methods --- src/com/esri/core/geometry/Envelope1D.java | 4 ++-- src/com/esri/core/geometry/Envelope2D.java | 6 +++--- src/com/esri/core/geometry/MultiPath.java | 8 ++++---- src/com/esri/core/geometry/MultiPoint.java | 8 ++++---- .../core/geometry/MultiVertexGeometry.java | 18 +++++++++--------- 5 files changed, 22 insertions(+), 22 deletions(-) diff --git a/src/com/esri/core/geometry/Envelope1D.java b/src/com/esri/core/geometry/Envelope1D.java index e0d19408..5d982bc1 100644 --- a/src/com/esri/core/geometry/Envelope1D.java +++ b/src/com/esri/core/geometry/Envelope1D.java @@ -48,7 +48,7 @@ public void setCoords(double _vmin, double _vmax) { normalize(); } - void normalize() { + public void normalize() { if (NumberUtils.isNaN(vmin)) return; if (vmin > vmax) { @@ -175,7 +175,7 @@ void setCoordsNoNaN_(double vmin_, double vmax_) { normalizeNoNaN_(); } - double snapClip(double v) /* const */ + public double snapClip(double v) /* const */ { return NumberUtils.snap(v, vmin, vmax); } diff --git a/src/com/esri/core/geometry/Envelope2D.java b/src/com/esri/core/geometry/Envelope2D.java index 30aa184b..89dded08 100644 --- a/src/com/esri/core/geometry/Envelope2D.java +++ b/src/com/esri/core/geometry/Envelope2D.java @@ -557,7 +557,7 @@ public boolean contains(Envelope2D other) {// Note: Will return False, if /** * Returns True if the envelope contains the point (boundary exclusive). */ - boolean containsExclusive(double x, double y) { + public boolean containsExclusive(double x, double y) { // Note: This will return False, if envelope is empty, thus no need to // call is_empty(). return x > xmin && x < xmax && y > ymin && y < ymax; @@ -566,7 +566,7 @@ boolean containsExclusive(double x, double y) { /** * Returns True if the envelope contains the point (boundary exclusive). */ - boolean containsExclusive(Point2D pt) { + public boolean containsExclusive(Point2D pt) { return contains(pt.x, pt.y); } @@ -978,7 +978,7 @@ boolean clipLineAuxiliary(double denominator, double numerator, * Returns True, envelope is degenerate (Width or Height are less than * tolerance). Note: this returns False for Empty envelope. */ - boolean isDegenerate(double tolerance) { + public boolean isDegenerate(double tolerance) { return !isEmpty() && (getWidth() <= tolerance || getHeight() <= tolerance); } diff --git a/src/com/esri/core/geometry/MultiPath.java b/src/com/esri/core/geometry/MultiPath.java index eca929d6..34cae2b0 100644 --- a/src/com/esri/core/geometry/MultiPath.java +++ b/src/com/esri/core/geometry/MultiPath.java @@ -124,12 +124,12 @@ public Point2D getXY(int index) { } @Override - void getXY(int index, Point2D pt) { + public void getXY(int index, Point2D pt) { m_impl.getXY(index, pt); } @Override - void setXY(int index, Point2D pt) { + public void setXY(int index, Point2D pt) { m_impl.setXY(index, pt); } @@ -184,7 +184,7 @@ Geometry getBoundary() { } @Override - void queryCoordinates(Point2D[] dst) { + public void queryCoordinates(Point2D[] dst) { m_impl.queryCoordinates(dst); } @@ -194,7 +194,7 @@ void queryCoordinates(Point3D[] dst) { } @Override - void queryCoordinates(Point[] dst) { + public void queryCoordinates(Point[] dst) { m_impl.queryCoordinates(dst); } diff --git a/src/com/esri/core/geometry/MultiPoint.java b/src/com/esri/core/geometry/MultiPoint.java index 0a7baf04..008401fd 100644 --- a/src/com/esri/core/geometry/MultiPoint.java +++ b/src/com/esri/core/geometry/MultiPoint.java @@ -76,7 +76,7 @@ public Point2D getXY(int index) { } @Override - void getXY(int index, Point2D pt) { + public void getXY(int index, Point2D pt) { m_impl.getXY(index, pt); } @@ -86,12 +86,12 @@ Point3D getXYZ(int index) { } @Override - void queryCoordinates(Point2D[] dst) { + public void queryCoordinates(Point2D[] dst) { m_impl.queryCoordinates(dst); } @Override - void queryCoordinates(Point[] dst) { + public void queryCoordinates(Point[] dst) { m_impl.queryCoordinates(dst); } @@ -215,7 +215,7 @@ public void setPoint(int index, Point pointSrc) { } @Override - void setXY(int index, Point2D pt) { + public void setXY(int index, Point2D pt) { m_impl.setXY(index, pt); } diff --git a/src/com/esri/core/geometry/MultiVertexGeometry.java b/src/com/esri/core/geometry/MultiVertexGeometry.java index b8102c0c..07730838 100644 --- a/src/com/esri/core/geometry/MultiVertexGeometry.java +++ b/src/com/esri/core/geometry/MultiVertexGeometry.java @@ -32,7 +32,7 @@ * The vertex attributes are stored in separate arrays of corresponding type. * There are as many arrays as there are attributes in the vertex. */ -abstract class MultiVertexGeometry extends Geometry implements +public abstract class MultiVertexGeometry extends Geometry implements Serializable { @Override @@ -80,13 +80,13 @@ public void getPoint(int index, Point ptOut) { */ public abstract Point2D getXY(int index); - abstract void getXY(int index, Point2D pt); + public abstract void getXY(int index, Point2D pt); /** * Sets XY coordinates of the given vertex of the Geometry. All other * attributes are unchanged. */ - abstract void setXY(int index, Point2D pt); + public abstract void setXY(int index, Point2D pt); /** * Returns XYZ coordinates of the given vertex of the Geometry. If the @@ -119,13 +119,13 @@ Point3D[] getCoordinates3D() { return arr; } - abstract void queryCoordinates(Point[] dst); + public abstract void queryCoordinates(Point[] dst); /** * Queries XY coordinates as an array. The array must be larg enough (See * GetPointCount()). */ - abstract void queryCoordinates(Point2D[] dst); + public abstract void queryCoordinates(Point2D[] dst); /** * Queries XYZ coordinates as an array. The array must be larg enough (See @@ -147,7 +147,7 @@ Point3D[] getCoordinates3D() { * If attribute is not present, the default value is returned. * See VertexDescription::GetDefaultValue() method. */ - public abstract double getAttributeAsDbl(int semantics, int index, + abstract double getAttributeAsDbl(int semantics, int index, int ordinate); /** @@ -165,7 +165,7 @@ public abstract double getAttributeAsDbl(int semantics, int index, * See VertexDescription::GetDefaultValue() method. Avoid using * this method on non-integer atributes. */ - public abstract int getAttributeAsInt(int semantics, int index, int ordinate); + abstract int getAttributeAsInt(int semantics, int index, int ordinate); /** * Sets the value of given attribute at given posisiotnsis. @@ -183,7 +183,7 @@ public abstract double getAttributeAsDbl(int semantics, int index, * * If the attribute is not present in this Geometry, it is added. */ - public abstract void setAttribute(int semantics, int index, int ordinate, + abstract void setAttribute(int semantics, int index, int ordinate, double value); /** @@ -191,7 +191,7 @@ public abstract void setAttribute(int semantics, int index, int ordinate, * non-integer atributes because some double attributes may have NaN default * values (e.g. Ms) */ - public abstract void setAttribute(int semantics, int index, int ordinate, + abstract void setAttribute(int semantics, int index, int ordinate, int value); /** From 81d8ad0257e1db4d7094af79fa8010f9d059c88d Mon Sep 17 00:00:00 2001 From: Sergey Tolstov Date: Thu, 23 Jan 2014 09:39:17 -0800 Subject: [PATCH 076/196] remove comments --- src/com/esri/core/geometry/MultiVertexGeometry.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/com/esri/core/geometry/MultiVertexGeometry.java b/src/com/esri/core/geometry/MultiVertexGeometry.java index 07730838..51948f74 100644 --- a/src/com/esri/core/geometry/MultiVertexGeometry.java +++ b/src/com/esri/core/geometry/MultiVertexGeometry.java @@ -40,7 +40,6 @@ protected void _assignVertexDescriptionImpl(VertexDescription newDescription) { throw new GeometryException("invalid call"); } - // Multipart methods: /** * Returns the total vertex count in this Geometry. */ @@ -56,7 +55,7 @@ protected void _assignVertexDescriptionImpl(VertexDescription newDescription) { */ public void getPoint(int index, Point ptOut) { getPointByVal(index, ptOut); - }// Java only + } /** * Sets the vertex at given index of the Geometry. From f78dbed42f1b231ae83f6de37e68f19ecdc044ee Mon Sep 17 00:00:00 2001 From: Sergey Tolstov Date: Thu, 23 Jan 2014 10:03:33 -0800 Subject: [PATCH 077/196] marked two methods on Point2D public --- src/com/esri/core/geometry/Point2D.java | 48 ++++++++++++++++++------- 1 file changed, 36 insertions(+), 12 deletions(-) diff --git a/src/com/esri/core/geometry/Point2D.java b/src/com/esri/core/geometry/Point2D.java index fe1a13b9..319cd5dd 100644 --- a/src/com/esri/core/geometry/Point2D.java +++ b/src/com/esri/core/geometry/Point2D.java @@ -250,9 +250,7 @@ boolean _isNan() { // between vectors (1,0) and (0, 1), second between (0, 1) and (-1, 0), etc. // Angle intervals corresponding to quarters: 1 : [0 : 90); 2 : [90 : 180); // 3 : [180 : 270); 4 : [270 : 360) - int _getQuarter() { - // _ASSERT(x != 0 || y != 0 || !NumberUtils.isNaN(x) || - // !NumberUtils.isNaN(y)); + final int _getQuarter() { if (x > 0) { if (y >= 0) return 1; // x > 0 && y <= 0 @@ -270,18 +268,29 @@ int _getQuarter() { } } + /** + * Calculates which quarter of XY plane the vector lies in. First quarter is + * between vectors (1,0) and (0, 1), second between (0, 1) and (-1, 0), etc. + * The quarters are numbered counterclockwise. + * Angle intervals corresponding to quarters: 1 : [0 : 90); 2 : [90 : 180); + * 3 : [180 : 270); 4 : [270 : 360) + */ + public int getQuarter() { return _getQuarter(); } + // Assume vector v1 and v2 have same origin. The function compares the - // vectors by angle from the x acis to the vector in the counter clockwise + // vectors by angle from the x axis to the vector in the counter clockwise // direction. - // > > - // \ / + // > > + // \ / // V3 \ / V1 - // ----------------/-------------------->X In this example, - // __compareVectors(V1, V2) == -1. - // \ _compareVectors(V1, V3) == -1 - // \ V2 _compareVectors(V2, V3) == 1 - // > - static int _compareVectors(Point2D v1, Point2D v2) { + // \ + // \ + // >V2 + // _compareVectors(V1, V2) == -1. + // _compareVectors(V1, V3) == -1 + // _compareVectors(V2, V3) == 1 + // + final static int _compareVectors(Point2D v1, Point2D v2) { int q1 = v1._getQuarter(); int q2 = v2._getQuarter(); @@ -292,6 +301,21 @@ static int _compareVectors(Point2D v1, Point2D v2) { return q1 < q2 ? -1 : 1; } + /** + * Assume vector v1 and v2 have same origin. The function compares the + * vectors by angle in the counter clockwise direction from the axis X. + * + * For example, V1 makes 30 degree angle counterclockwise from horizontal x axis + * V2, makes 270, V3 makes 90, then + * compareVectors(V1, V2) == -1. + * compareVectors(V1, V3) == -1. + * compareVectors(V2, V3) == 1. + * @return Returns 1 if v1 is less than v2, 0 if equal, and 1 if greater. + */ + public static int compareVectors(Point2D v1, Point2D v2) { + return _compareVectors(v1, v2); + } + static class CompareVectors implements Comparator { @Override public int compare(Point2D v1, Point2D v2) { From 3b807a1d9c0d9072694835d2cc13f3c57fd4961c Mon Sep 17 00:00:00 2001 From: Sergey Tolstov Date: Mon, 10 Feb 2014 12:40:06 -0800 Subject: [PATCH 078/196] make RasterizedGeometry2D public --- .../core/geometry/RasterizedGeometry2D.java | 20 +++++++++---------- .../geometry/RasterizedGeometry2DImpl.java | 6 +++--- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/com/esri/core/geometry/RasterizedGeometry2D.java b/src/com/esri/core/geometry/RasterizedGeometry2D.java index 54ca904b..0e420658 100644 --- a/src/com/esri/core/geometry/RasterizedGeometry2D.java +++ b/src/com/esri/core/geometry/RasterizedGeometry2D.java @@ -25,7 +25,7 @@ import com.esri.core.geometry.Geometry.GeometryAccelerationDegree; -abstract class RasterizedGeometry2D { +public abstract class RasterizedGeometry2D { public enum HitType { Outside(0), // the test geometry is well outside the geometry bounds @@ -33,7 +33,7 @@ public enum HitType { Border(2); // the test geometry is close to the bounds or intersects the // bounds - private int enumVal; + int enumVal; private HitType(int val) { enumVal = val; @@ -41,7 +41,7 @@ private HitType(int val) { } /** - * Test a point agains the RasterizedGeometry + * Test a point against the RasterizedGeometry */ public abstract HitType queryPointInGeometry(double x, double y); @@ -54,11 +54,11 @@ private HitType(int val) { * Creates a rasterized geometry from a given Geometry. * * @param geom - * The input geometry to rasterize. + * The input geometry to rasterize. It has to be a MultiVertexGeometry instance. * @param toleranceXY * The tolerance of the rasterization. Raster pixels that are * closer than given tolerance to the Geometry will be set. - * @param rasterSize + * @param rasterSizeBytes * The max size of the raster in bytes. The raster has size of * rasterSize x rasterSize. Polygons are rasterized into 2 bpp * (bits per pixel) rasters while other geometries are rasterized @@ -75,7 +75,7 @@ public static RasterizedGeometry2D create(Geometry geom, return (RasterizedGeometry2D) gc; } - public static RasterizedGeometry2D create(MultiVertexGeometryImpl geom, + static RasterizedGeometry2D create(MultiVertexGeometryImpl geom, double toleranceXY, int rasterSizeBytes) { if (!canUseAccelerator(geom)) throw new IllegalArgumentException(); @@ -121,19 +121,19 @@ static boolean canUseAccelerator(Geometry geom) { /** * Returns the tolerance for which the rasterized Geometry has been built. */ - abstract double getToleranceXY(); + public abstract double getToleranceXY(); /** * Returns raster size in bytes */ - abstract int getRasterSize(); + public abstract int getRasterSize(); /** - * Dumps the raster to file for debug purposes. + * Dumps the raster to a bmp file for debug purposes. * * @param fileName * @returns true if success, false otherwise. */ - abstract boolean dbgSaveToBitmap(String fileName); + public abstract boolean dbgSaveToBitmap(String fileName); } diff --git a/src/com/esri/core/geometry/RasterizedGeometry2DImpl.java b/src/com/esri/core/geometry/RasterizedGeometry2DImpl.java index cd9f445f..b2ebf125 100644 --- a/src/com/esri/core/geometry/RasterizedGeometry2DImpl.java +++ b/src/com/esri/core/geometry/RasterizedGeometry2DImpl.java @@ -500,17 +500,17 @@ else if (res == 1) } @Override - double getToleranceXY() { + public double getToleranceXY() { return m_toleranceXY; } @Override - int getRasterSize() { + public int getRasterSize() { return m_width * m_scanLineSize; } @Override - boolean dbgSaveToBitmap(String fileName) { + public boolean dbgSaveToBitmap(String fileName) { try { FileOutputStream outfile = new FileOutputStream(fileName); From b7e6972e7636408ec15a41adbae0e2b9e65386f4 Mon Sep 17 00:00:00 2001 From: Sergey Tolstov Date: Mon, 10 Feb 2014 16:24:01 -0800 Subject: [PATCH 079/196] local build cannot handle several classes in one java file --- .../core/geometry/JSONArrayEnumerator.java | 67 +++ .../core/geometry/JSONObjectEnumerator.java | 80 ++++ .../esri/core/geometry/JsonParserReader.java | 71 ++++ src/com/esri/core/geometry/JsonReader.java | 321 -------------- .../esri/core/geometry/JsonStringWriter.java | 398 ++++++++++++++++++ .../esri/core/geometry/JsonValueReader.java | 228 ++++++++++ src/com/esri/core/geometry/JsonWriter.java | 372 ---------------- .../core/geometry/RasterizedGeometry2D.java | 2 +- 8 files changed, 845 insertions(+), 694 deletions(-) create mode 100644 src/com/esri/core/geometry/JSONArrayEnumerator.java create mode 100644 src/com/esri/core/geometry/JSONObjectEnumerator.java create mode 100644 src/com/esri/core/geometry/JsonParserReader.java create mode 100644 src/com/esri/core/geometry/JsonStringWriter.java create mode 100644 src/com/esri/core/geometry/JsonValueReader.java diff --git a/src/com/esri/core/geometry/JSONArrayEnumerator.java b/src/com/esri/core/geometry/JSONArrayEnumerator.java new file mode 100644 index 00000000..5b2d93d7 --- /dev/null +++ b/src/com/esri/core/geometry/JSONArrayEnumerator.java @@ -0,0 +1,67 @@ +/* + Copyright 1995-2013 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ +package com.esri.core.geometry; + +import java.util.ArrayList; +import org.codehaus.jackson.JsonParser; +import org.codehaus.jackson.JsonToken; +import org.json.JSONArray; +import org.json.JSONObject; + +final class JSONArrayEnumerator { + + private JSONArray m_jsonArray; + private boolean m_bStarted; + private int m_currentIndex; + + JSONArrayEnumerator(JSONArray jsonArray) { + m_bStarted = false; + m_currentIndex = -1; + m_jsonArray = jsonArray; + } + + Object getCurrentObject() { + if (!m_bStarted) { + throw new GeometryException("invalid call"); + } + + if (m_currentIndex == m_jsonArray.length()) { + throw new GeometryException("invalid call"); + } + + return m_jsonArray.opt(m_currentIndex); + } + + boolean next() { + if (!m_bStarted) { + m_currentIndex = 0; + m_bStarted = true; + } else if (m_currentIndex != m_jsonArray.length()) { + m_currentIndex++; + } + + return m_currentIndex != m_jsonArray.length(); + } +} + diff --git a/src/com/esri/core/geometry/JSONObjectEnumerator.java b/src/com/esri/core/geometry/JSONObjectEnumerator.java new file mode 100644 index 00000000..f474a16c --- /dev/null +++ b/src/com/esri/core/geometry/JSONObjectEnumerator.java @@ -0,0 +1,80 @@ +/* + Copyright 1995-2013 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ +package com.esri.core.geometry; + +import java.util.ArrayList; +import org.codehaus.jackson.JsonParser; +import org.codehaus.jackson.JsonToken; +import org.json.JSONArray; +import org.json.JSONObject; + +final class JSONObjectEnumerator { + + private JSONObject m_jsonObject; + private boolean m_bStarted; + private int m_currentIndex; + private String[] m_keys; + + JSONObjectEnumerator(JSONObject jsonObject) { + m_bStarted = false; + m_currentIndex = -1; + m_jsonObject = jsonObject; + } + + String getCurrentKey() { + if (!m_bStarted) { + throw new GeometryException("invalid call"); + } + + if (m_currentIndex == m_jsonObject.length()) { + throw new GeometryException("invalid call"); + } + + return m_keys[m_currentIndex]; + } + + Object getCurrentObject() { + if (!m_bStarted) { + throw new GeometryException("invalid call"); + } + + if (m_currentIndex == m_jsonObject.length()) { + throw new GeometryException("invalid call"); + } + + return m_jsonObject.opt(m_keys[m_currentIndex]); + } + + boolean next() { + if (!m_bStarted) { + m_currentIndex = 0; + m_keys = JSONObject.getNames(m_jsonObject); + m_bStarted = true; + } else if (m_currentIndex != m_jsonObject.length()) { + m_currentIndex++; + } + + return m_currentIndex != m_jsonObject.length(); + } +} diff --git a/src/com/esri/core/geometry/JsonParserReader.java b/src/com/esri/core/geometry/JsonParserReader.java new file mode 100644 index 00000000..efa6f55d --- /dev/null +++ b/src/com/esri/core/geometry/JsonParserReader.java @@ -0,0 +1,71 @@ +/* + Copyright 1995-2013 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ +package com.esri.core.geometry; + +import java.util.ArrayList; +import org.codehaus.jackson.JsonParser; +import org.codehaus.jackson.JsonToken; +import org.json.JSONArray; +import org.json.JSONObject; + +final class JsonParserReader extends JsonReader { + + private JsonParser m_jsonParser; + + JsonParserReader(JsonParser jsonParser) { + m_jsonParser = jsonParser; + } + + @Override + JsonToken nextToken() throws Exception { + JsonToken token = m_jsonParser.nextToken(); + return token; + } + + @Override + JsonToken currentToken() throws Exception { + return m_jsonParser.getCurrentToken(); + } + + @Override + void skipChildren() throws Exception { + m_jsonParser.skipChildren(); + } + + @Override + String currentString() throws Exception { + return m_jsonParser.getText(); + } + + @Override + double currentDoubleValue() throws Exception { + return m_jsonParser.getValueAsDouble(); + } + + @Override + int currentIntValue() throws Exception { + return m_jsonParser.getValueAsInt(); + } +} + diff --git a/src/com/esri/core/geometry/JsonReader.java b/src/com/esri/core/geometry/JsonReader.java index 342c6435..1b3a4f03 100644 --- a/src/com/esri/core/geometry/JsonReader.java +++ b/src/com/esri/core/geometry/JsonReader.java @@ -44,325 +44,4 @@ abstract class JsonReader { abstract int currentIntValue() throws Exception; } -final class JsonParserReader extends JsonReader { - private JsonParser m_jsonParser; - - JsonParserReader(JsonParser jsonParser) { - m_jsonParser = jsonParser; - } - - @Override - JsonToken nextToken() throws Exception { - JsonToken token = m_jsonParser.nextToken(); - return token; - } - - @Override - JsonToken currentToken() throws Exception { - return m_jsonParser.getCurrentToken(); - } - - @Override - void skipChildren() throws Exception { - m_jsonParser.skipChildren(); - } - - @Override - String currentString() throws Exception { - return m_jsonParser.getText(); - } - - @Override - double currentDoubleValue() throws Exception { - return m_jsonParser.getValueAsDouble(); - } - - @Override - int currentIntValue() throws Exception { - return m_jsonParser.getValueAsInt(); - } -} - -final class JsonValueReader extends JsonReader { - - private Object m_object; - private JsonToken m_currentToken; - private ArrayList m_parentStack; - private ArrayList m_objIters; - private ArrayList m_arrIters; - - JsonValueReader(Object object) { - m_object = object; - - boolean bJSONObject = (m_object instanceof JSONObject); - boolean bJSONArray = (m_object instanceof JSONArray); - - if (!bJSONObject && !bJSONArray) { - throw new IllegalArgumentException(); - } - - m_parentStack = new ArrayList(0); - m_objIters = new ArrayList(0); - m_arrIters = new ArrayList(0); - - m_parentStack.ensureCapacity(4); - m_objIters.ensureCapacity(4); - m_arrIters.ensureCapacity(4); - - if (bJSONObject) { - JSONObjectEnumerator objIter = new JSONObjectEnumerator((JSONObject) m_object); - m_parentStack.add(JsonToken.START_OBJECT); - m_objIters.add(objIter); - m_currentToken = JsonToken.START_OBJECT; - } else { - JSONArrayEnumerator arrIter = new JSONArrayEnumerator((JSONArray) m_object); - m_parentStack.add(JsonToken.START_ARRAY); - m_arrIters.add(arrIter); - m_currentToken = JsonToken.START_ARRAY; - } - } - - private void setCurrentToken_(Object obj) { - if (obj instanceof String) { - m_currentToken = JsonToken.VALUE_STRING; - } else if (obj instanceof Double || obj instanceof Float) { - m_currentToken = JsonToken.VALUE_NUMBER_FLOAT; - } else if (obj instanceof Integer || obj instanceof Long || obj instanceof Short) { - m_currentToken = JsonToken.VALUE_NUMBER_INT; - } else if (obj instanceof Boolean) { - Boolean bObj = (Boolean) obj; - boolean b = bObj.booleanValue(); - if (b) { - m_currentToken = JsonToken.VALUE_TRUE; - } else { - m_currentToken = JsonToken.VALUE_FALSE; - } - } else if (obj instanceof JSONObject) { - m_currentToken = JsonToken.START_OBJECT; - } else if (obj instanceof JSONArray) { - m_currentToken = JsonToken.START_ARRAY; - } else { - m_currentToken = JsonToken.VALUE_NULL; - } - } - - Object currentObject_() { - assert (!m_parentStack.isEmpty()); - - JsonToken parentType = m_parentStack.get(m_parentStack.size() - 1); - - if (parentType == JsonToken.START_OBJECT) { - JSONObjectEnumerator objIter = m_objIters.get(m_objIters.size() - 1); - return objIter.getCurrentObject(); - } - - JSONArrayEnumerator arrIter = m_arrIters.get(m_arrIters.size() - 1); - return arrIter.getCurrentObject(); - } - - @Override - JsonToken nextToken() throws Exception { - if (m_parentStack.isEmpty()) { - m_currentToken = JsonToken.NOT_AVAILABLE; - return m_currentToken; - } - - JsonToken parentType = m_parentStack.get(m_parentStack.size() - 1); - - if (parentType == JsonToken.START_OBJECT) { - JSONObjectEnumerator iterator = m_objIters.get(m_objIters.size() - 1); - - if (m_currentToken == JsonToken.FIELD_NAME) { - Object nextJSONValue = iterator.getCurrentObject(); - - if (nextJSONValue instanceof JSONObject) { - m_parentStack.add(JsonToken.START_OBJECT); - m_objIters.add(new JSONObjectEnumerator((JSONObject) nextJSONValue)); - m_currentToken = JsonToken.START_OBJECT; - } else if (nextJSONValue instanceof JSONArray) { - m_parentStack.add(JsonToken.START_ARRAY); - m_arrIters.add(new JSONArrayEnumerator((JSONArray) nextJSONValue)); - m_currentToken = JsonToken.START_ARRAY; - } else { - setCurrentToken_(nextJSONValue); - } - } else { - if (iterator.next()) { - m_currentToken = JsonToken.FIELD_NAME; - } else { - m_objIters.remove(m_objIters.size() - 1); - m_parentStack.remove(m_parentStack.size() - 1); - m_currentToken = JsonToken.END_OBJECT; - } - } - } else { - assert (parentType == JsonToken.START_ARRAY); - JSONArrayEnumerator iterator = m_arrIters.get(m_arrIters.size() - 1); - if (iterator.next()) { - Object nextJSONValue = iterator.getCurrentObject(); - - if (nextJSONValue instanceof JSONObject) { - m_parentStack.add(JsonToken.START_OBJECT); - m_objIters.add(new JSONObjectEnumerator((JSONObject) nextJSONValue)); - m_currentToken = JsonToken.START_OBJECT; - } else if (nextJSONValue instanceof JSONArray) { - m_parentStack.add(JsonToken.START_ARRAY); - m_arrIters.add(new JSONArrayEnumerator((JSONArray) nextJSONValue)); - m_currentToken = JsonToken.START_ARRAY; - } else { - setCurrentToken_(nextJSONValue); - } - } else { - m_arrIters.remove(m_arrIters.size() - 1); - m_parentStack.remove(m_parentStack.size() - 1); - m_currentToken = JsonToken.END_ARRAY; - } - } - - return m_currentToken; - } - - @Override - JsonToken currentToken() throws Exception { - return m_currentToken; - } - - @Override - void skipChildren() throws Exception { - assert (!m_parentStack.isEmpty()); - - if (m_currentToken != JsonToken.START_OBJECT && m_currentToken != JsonToken.START_ARRAY) { - return; - } - - JsonToken parentType = m_parentStack.get(m_parentStack.size() - 1); - - if (parentType == JsonToken.START_OBJECT) { - m_objIters.remove(m_objIters.size() - 1); - m_parentStack.remove(m_parentStack.size() - 1); - m_currentToken = JsonToken.END_OBJECT; - } else { - m_arrIters.remove(m_arrIters.size() - 1); - m_parentStack.remove(m_parentStack.size() - 1); - m_currentToken = JsonToken.END_ARRAY; - } - } - - @Override - String currentString() throws Exception { - if (m_currentToken == JsonToken.FIELD_NAME) { - return m_objIters.get(m_objIters.size() - 1).getCurrentKey(); - } - - if (m_currentToken != JsonToken.VALUE_STRING) { - throw new GeometryException("invalid call"); - } - - return ((String) currentObject_()).toString(); - } - - @Override - double currentDoubleValue() throws Exception { - if (m_currentToken != JsonToken.VALUE_NUMBER_FLOAT && m_currentToken != JsonToken.VALUE_NUMBER_INT) { - throw new GeometryException("invalid call"); - } - - return ((Number) currentObject_()).doubleValue(); - } - - @Override - int currentIntValue() throws Exception { - if (m_currentToken != JsonToken.VALUE_NUMBER_INT) { - throw new GeometryException("invalid call"); - } - - return ((Number) currentObject_()).intValue(); - } -} - -final class JSONObjectEnumerator { - - private JSONObject m_jsonObject; - private boolean m_bStarted; - private int m_currentIndex; - private String[] m_keys; - - JSONObjectEnumerator(JSONObject jsonObject) { - m_bStarted = false; - m_currentIndex = -1; - m_jsonObject = jsonObject; - } - - String getCurrentKey() { - if (!m_bStarted) { - throw new GeometryException("invalid call"); - } - - if (m_currentIndex == m_jsonObject.length()) { - throw new GeometryException("invalid call"); - } - - return m_keys[m_currentIndex]; - } - - Object getCurrentObject() { - if (!m_bStarted) { - throw new GeometryException("invalid call"); - } - - if (m_currentIndex == m_jsonObject.length()) { - throw new GeometryException("invalid call"); - } - - return m_jsonObject.opt(m_keys[m_currentIndex]); - } - - boolean next() { - if (!m_bStarted) { - m_currentIndex = 0; - m_keys = JSONObject.getNames(m_jsonObject); - m_bStarted = true; - } else if (m_currentIndex != m_jsonObject.length()) { - m_currentIndex++; - } - - return m_currentIndex != m_jsonObject.length(); - } -} - -final class JSONArrayEnumerator { - - private JSONArray m_jsonArray; - private boolean m_bStarted; - private int m_currentIndex; - - JSONArrayEnumerator(JSONArray jsonArray) { - m_bStarted = false; - m_currentIndex = -1; - m_jsonArray = jsonArray; - } - - Object getCurrentObject() { - if (!m_bStarted) { - throw new GeometryException("invalid call"); - } - - if (m_currentIndex == m_jsonArray.length()) { - throw new GeometryException("invalid call"); - } - - return m_jsonArray.opt(m_currentIndex); - } - - boolean next() { - if (!m_bStarted) { - m_currentIndex = 0; - m_bStarted = true; - } else if (m_currentIndex != m_jsonArray.length()) { - m_currentIndex++; - } - - return m_currentIndex != m_jsonArray.length(); - } -} diff --git a/src/com/esri/core/geometry/JsonStringWriter.java b/src/com/esri/core/geometry/JsonStringWriter.java new file mode 100644 index 00000000..dd4cf251 --- /dev/null +++ b/src/com/esri/core/geometry/JsonStringWriter.java @@ -0,0 +1,398 @@ +/* + Copyright 1995-2013 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ +package com.esri.core.geometry; + +final class JsonStringWriter extends JsonWriter { + + @Override + Object getJson() { + next_(Action.accept); + return m_jsonString.toString(); + } + + @Override + void startObject() { + next_(Action.addContainer); + m_jsonString.append('{'); + m_functionStack.add(State.objectStart); + } + + @Override + void startArray() { + next_(Action.addContainer); + m_jsonString.append('['); + m_functionStack.add(State.arrayStart); + } + + @Override + void endObject() { + next_(Action.popObject); + m_jsonString.append('}'); + } + + @Override + void endArray() { + next_(Action.popArray); + m_jsonString.append(']'); + } + + @Override + void addPairObject(String fieldName) { + next_(Action.addPair); + appendQuote_(fieldName); + m_jsonString.append(":"); + addValueObject_(); + } + + @Override + void addPairArray(String fieldName) { + next_(Action.addPair); + appendQuote_(fieldName); + m_jsonString.append(":"); + addValueArray_(); + } + + @Override + void addPairString(String fieldName, String v) { + next_(Action.addPair); + appendQuote_(fieldName); + m_jsonString.append(":"); + addValueString_(v); + } + + @Override + void addPairDouble(String fieldName, double v) { + next_(Action.addPair); + appendQuote_(fieldName); + m_jsonString.append(":"); + addValueDouble_(v); + } + + @Override + void addPairDoubleF(String fieldName, double v, int decimals) { + next_(Action.addPair); + appendQuote_(fieldName); + m_jsonString.append(":"); + addValueDoubleF_(v, decimals); + } + + @Override + void addPairInt(String fieldName, int v) { + next_(Action.addPair); + appendQuote_(fieldName); + m_jsonString.append(":"); + addValueInt_(v); + } + + @Override + void addPairBoolean(String fieldName, boolean v) { + next_(Action.addPair); + appendQuote_(fieldName); + m_jsonString.append(":"); + addValueBoolean_(v); + } + + @Override + void addPairNull(String fieldName) { + next_(Action.addPair); + appendQuote_(fieldName); + m_jsonString.append(":"); + addValueNull_(); + } + + @Override + void addValueObject() { + next_(Action.addValue); + addValueObject_(); + } + + @Override + void addValueArray() { + next_(Action.addValue); + addValueArray_(); + } + + @Override + void addValueString(String v) { + next_(Action.addValue); + addValueString_(v); + } + + @Override + void addValueDouble(double v) { + next_(Action.addValue); + addValueDouble_(v); + } + + @Override + void addValueDoubleF(double v, int decimals) { + next_(Action.addValue); + addValueDoubleF_(v, decimals); + } + + @Override + void addValueInt(int v) { + next_(Action.addValue); + addValueInt_(v); + } + + @Override + void addValueBoolean(boolean v) { + next_(Action.addValue); + addValueBoolean_(v); + } + + @Override + void addValueNull() { + next_(Action.addValue); + addValueNull_(); + } + + JsonStringWriter() { + m_jsonString = new StringBuilder(); + m_functionStack = new AttributeStreamOfInt32(0); + m_functionStack.add(State.accept); + m_functionStack.add(State.start); + } + private StringBuilder m_jsonString; + private AttributeStreamOfInt32 m_functionStack; + + private void addValueObject_() { + m_jsonString.append('{'); + m_functionStack.add(State.objectStart); + } + + private void addValueArray_() { + m_jsonString.append('['); + m_functionStack.add(State.arrayStart); + } + + private void addValueString_(String v) { + appendQuote_(v); + } + + private void addValueDouble_(double v) { + if (NumberUtils.isNaN(v)) { + addValueNull_(); + return; + } + + StringUtils.appendDouble(v, 17, m_jsonString); + } + + private void addValueDoubleF_(double v, int decimals) { + if (NumberUtils.isNaN(v)) { + addValueNull_(); + return; + } + + StringUtils.appendDoubleF(v, decimals, m_jsonString); + } + + private void addValueInt_(int v) { + m_jsonString.append(v); + } + + private void addValueBoolean_(boolean v) { + if (v) { + m_jsonString.append("true"); + } else { + m_jsonString.append("false"); + } + } + + private void addValueNull_() { + m_jsonString.append("null"); + } + + private void next_(int action) { + switch (m_functionStack.getLast()) { + case State.accept: + accept_(action); + break; + case State.start: + start_(action); + break; + case State.objectStart: + objectStart_(action); + break; + case State.arrayStart: + arrayStart_(action); + break; + case State.pairEnd: + pairEnd_(action); + break; + case State.elementEnd: + elementEnd_(action); + break; + default: + throw new GeometryException("internal error"); + } + } + + private void accept_(int action) { + if (action != Action.accept) { + throw new GeometryException("invalid call"); + } + } + + private void start_(int action) { + if (action == Action.addContainer) { + m_functionStack.removeLast(); + } else { + throw new GeometryException("invalid call"); + } + } + + private void objectStart_(int action) { + m_functionStack.removeLast(); + + if (action == Action.addPair) { + m_functionStack.add(State.pairEnd); + } else if (action != Action.popObject) { + throw new GeometryException("invalid call"); + } + } + + private void pairEnd_(int action) { + if (action == Action.addPair) { + m_jsonString.append(','); + } else if (action == Action.popObject) { + m_functionStack.removeLast(); + } else { + throw new GeometryException("invalid call"); + } + } + + private void arrayStart_(int action) { + m_functionStack.removeLast(); + + if (action == Action.addValue) { + m_functionStack.add(State.elementEnd); + } else if (action != Action.popArray) { + throw new GeometryException("invalid call"); + } + } + + private void elementEnd_(int action) { + if (action == Action.addValue) { + m_jsonString.append(','); + } else if (action == Action.popArray) { + m_functionStack.removeLast(); + } else { + throw new GeometryException("invalid call"); + } + } + + private void appendQuote_(String string) { + int count = 0; + int start = 0; + int end = string.length(); + + m_jsonString.append('"'); + + for (int i = 0; i < end; i++) { + switch (string.charAt(i)) { + case '"': + if (count > 0) { + m_jsonString.append(string, start, start + count); + count = 0; + } + m_jsonString.append("\\\""); + start = i + 1; + break; + case '\\': + if (count > 0) { + m_jsonString.append(string, start, start + count); + count = 0; + } + m_jsonString.append("\\\\"); + start = i + 1; + break; + case '/': + if (i > 0 && string.charAt(i - 1) == '<') { + if (count > 0) { + m_jsonString.append(string, start, start + count); + count = 0; + } + m_jsonString.append("\\/"); + start = i + 1; + } else { + count++; + } + break; + case '\b': + if (count > 0) { + m_jsonString.append(string, start, start + count); + count = 0; + } + m_jsonString.append("\\b"); + start = i + 1; + break; + case '\f': + if (count > 0) { + m_jsonString.append(string, start, start + count); + count = 0; + } + m_jsonString.append("\\f"); + start = i + 1; + break; + case '\n': + if (count > 0) { + m_jsonString.append(string, start, start + count); + count = 0; + } + m_jsonString.append("\\n"); + start = i + 1; + break; + case '\r': + if (count > 0) { + m_jsonString.append(string, start, start + count); + count = 0; + } + m_jsonString.append("\\r"); + start = i + 1; + break; + case '\t': + if (count > 0) { + m_jsonString.append(string, start, start + count); + count = 0; + } + m_jsonString.append("\\t"); + start = i + 1; + break; + default: + count++; + break; + } + } + + if (count > 0) { + m_jsonString.append(string, start, start + count); + } + + m_jsonString.append('"'); + } +} + diff --git a/src/com/esri/core/geometry/JsonValueReader.java b/src/com/esri/core/geometry/JsonValueReader.java new file mode 100644 index 00000000..9f320029 --- /dev/null +++ b/src/com/esri/core/geometry/JsonValueReader.java @@ -0,0 +1,228 @@ +/* + Copyright 1995-2013 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ +package com.esri.core.geometry; + +import java.util.ArrayList; +import org.codehaus.jackson.JsonParser; +import org.codehaus.jackson.JsonToken; +import org.json.JSONArray; +import org.json.JSONObject; + + +final class JsonValueReader extends JsonReader { + + private Object m_object; + private JsonToken m_currentToken; + private ArrayList m_parentStack; + private ArrayList m_objIters; + private ArrayList m_arrIters; + + JsonValueReader(Object object) { + m_object = object; + + boolean bJSONObject = (m_object instanceof JSONObject); + boolean bJSONArray = (m_object instanceof JSONArray); + + if (!bJSONObject && !bJSONArray) { + throw new IllegalArgumentException(); + } + + m_parentStack = new ArrayList(0); + m_objIters = new ArrayList(0); + m_arrIters = new ArrayList(0); + + m_parentStack.ensureCapacity(4); + m_objIters.ensureCapacity(4); + m_arrIters.ensureCapacity(4); + + if (bJSONObject) { + JSONObjectEnumerator objIter = new JSONObjectEnumerator((JSONObject) m_object); + m_parentStack.add(JsonToken.START_OBJECT); + m_objIters.add(objIter); + m_currentToken = JsonToken.START_OBJECT; + } else { + JSONArrayEnumerator arrIter = new JSONArrayEnumerator((JSONArray) m_object); + m_parentStack.add(JsonToken.START_ARRAY); + m_arrIters.add(arrIter); + m_currentToken = JsonToken.START_ARRAY; + } + } + + private void setCurrentToken_(Object obj) { + if (obj instanceof String) { + m_currentToken = JsonToken.VALUE_STRING; + } else if (obj instanceof Double || obj instanceof Float) { + m_currentToken = JsonToken.VALUE_NUMBER_FLOAT; + } else if (obj instanceof Integer || obj instanceof Long || obj instanceof Short) { + m_currentToken = JsonToken.VALUE_NUMBER_INT; + } else if (obj instanceof Boolean) { + Boolean bObj = (Boolean) obj; + boolean b = bObj.booleanValue(); + if (b) { + m_currentToken = JsonToken.VALUE_TRUE; + } else { + m_currentToken = JsonToken.VALUE_FALSE; + } + } else if (obj instanceof JSONObject) { + m_currentToken = JsonToken.START_OBJECT; + } else if (obj instanceof JSONArray) { + m_currentToken = JsonToken.START_ARRAY; + } else { + m_currentToken = JsonToken.VALUE_NULL; + } + } + + Object currentObject_() { + assert (!m_parentStack.isEmpty()); + + JsonToken parentType = m_parentStack.get(m_parentStack.size() - 1); + + if (parentType == JsonToken.START_OBJECT) { + JSONObjectEnumerator objIter = m_objIters.get(m_objIters.size() - 1); + return objIter.getCurrentObject(); + } + + JSONArrayEnumerator arrIter = m_arrIters.get(m_arrIters.size() - 1); + return arrIter.getCurrentObject(); + } + + @Override + JsonToken nextToken() throws Exception { + if (m_parentStack.isEmpty()) { + m_currentToken = JsonToken.NOT_AVAILABLE; + return m_currentToken; + } + + JsonToken parentType = m_parentStack.get(m_parentStack.size() - 1); + + if (parentType == JsonToken.START_OBJECT) { + JSONObjectEnumerator iterator = m_objIters.get(m_objIters.size() - 1); + + if (m_currentToken == JsonToken.FIELD_NAME) { + Object nextJSONValue = iterator.getCurrentObject(); + + if (nextJSONValue instanceof JSONObject) { + m_parentStack.add(JsonToken.START_OBJECT); + m_objIters.add(new JSONObjectEnumerator((JSONObject) nextJSONValue)); + m_currentToken = JsonToken.START_OBJECT; + } else if (nextJSONValue instanceof JSONArray) { + m_parentStack.add(JsonToken.START_ARRAY); + m_arrIters.add(new JSONArrayEnumerator((JSONArray) nextJSONValue)); + m_currentToken = JsonToken.START_ARRAY; + } else { + setCurrentToken_(nextJSONValue); + } + } else { + if (iterator.next()) { + m_currentToken = JsonToken.FIELD_NAME; + } else { + m_objIters.remove(m_objIters.size() - 1); + m_parentStack.remove(m_parentStack.size() - 1); + m_currentToken = JsonToken.END_OBJECT; + } + } + } else { + assert (parentType == JsonToken.START_ARRAY); + JSONArrayEnumerator iterator = m_arrIters.get(m_arrIters.size() - 1); + if (iterator.next()) { + Object nextJSONValue = iterator.getCurrentObject(); + + if (nextJSONValue instanceof JSONObject) { + m_parentStack.add(JsonToken.START_OBJECT); + m_objIters.add(new JSONObjectEnumerator((JSONObject) nextJSONValue)); + m_currentToken = JsonToken.START_OBJECT; + } else if (nextJSONValue instanceof JSONArray) { + m_parentStack.add(JsonToken.START_ARRAY); + m_arrIters.add(new JSONArrayEnumerator((JSONArray) nextJSONValue)); + m_currentToken = JsonToken.START_ARRAY; + } else { + setCurrentToken_(nextJSONValue); + } + } else { + m_arrIters.remove(m_arrIters.size() - 1); + m_parentStack.remove(m_parentStack.size() - 1); + m_currentToken = JsonToken.END_ARRAY; + } + } + + return m_currentToken; + } + + @Override + JsonToken currentToken() throws Exception { + return m_currentToken; + } + + @Override + void skipChildren() throws Exception { + assert (!m_parentStack.isEmpty()); + + if (m_currentToken != JsonToken.START_OBJECT && m_currentToken != JsonToken.START_ARRAY) { + return; + } + + JsonToken parentType = m_parentStack.get(m_parentStack.size() - 1); + + if (parentType == JsonToken.START_OBJECT) { + m_objIters.remove(m_objIters.size() - 1); + m_parentStack.remove(m_parentStack.size() - 1); + m_currentToken = JsonToken.END_OBJECT; + } else { + m_arrIters.remove(m_arrIters.size() - 1); + m_parentStack.remove(m_parentStack.size() - 1); + m_currentToken = JsonToken.END_ARRAY; + } + } + + @Override + String currentString() throws Exception { + if (m_currentToken == JsonToken.FIELD_NAME) { + return m_objIters.get(m_objIters.size() - 1).getCurrentKey(); + } + + if (m_currentToken != JsonToken.VALUE_STRING) { + throw new GeometryException("invalid call"); + } + + return ((String) currentObject_()).toString(); + } + + @Override + double currentDoubleValue() throws Exception { + if (m_currentToken != JsonToken.VALUE_NUMBER_FLOAT && m_currentToken != JsonToken.VALUE_NUMBER_INT) { + throw new GeometryException("invalid call"); + } + + return ((Number) currentObject_()).doubleValue(); + } + + @Override + int currentIntValue() throws Exception { + if (m_currentToken != JsonToken.VALUE_NUMBER_INT) { + throw new GeometryException("invalid call"); + } + + return ((Number) currentObject_()).intValue(); + } +} diff --git a/src/com/esri/core/geometry/JsonWriter.java b/src/com/esri/core/geometry/JsonWriter.java index 710a34d6..28e15874 100644 --- a/src/com/esri/core/geometry/JsonWriter.java +++ b/src/com/esri/core/geometry/JsonWriter.java @@ -88,375 +88,3 @@ protected interface State { } } -final class JsonStringWriter extends JsonWriter { - - @Override - Object getJson() { - next_(Action.accept); - return m_jsonString.toString(); - } - - @Override - void startObject() { - next_(Action.addContainer); - m_jsonString.append('{'); - m_functionStack.add(State.objectStart); - } - - @Override - void startArray() { - next_(Action.addContainer); - m_jsonString.append('['); - m_functionStack.add(State.arrayStart); - } - - @Override - void endObject() { - next_(Action.popObject); - m_jsonString.append('}'); - } - - @Override - void endArray() { - next_(Action.popArray); - m_jsonString.append(']'); - } - - @Override - void addPairObject(String fieldName) { - next_(Action.addPair); - appendQuote_(fieldName); - m_jsonString.append(":"); - addValueObject_(); - } - - @Override - void addPairArray(String fieldName) { - next_(Action.addPair); - appendQuote_(fieldName); - m_jsonString.append(":"); - addValueArray_(); - } - - @Override - void addPairString(String fieldName, String v) { - next_(Action.addPair); - appendQuote_(fieldName); - m_jsonString.append(":"); - addValueString_(v); - } - - @Override - void addPairDouble(String fieldName, double v) { - next_(Action.addPair); - appendQuote_(fieldName); - m_jsonString.append(":"); - addValueDouble_(v); - } - - @Override - void addPairDoubleF(String fieldName, double v, int decimals) { - next_(Action.addPair); - appendQuote_(fieldName); - m_jsonString.append(":"); - addValueDoubleF_(v, decimals); - } - - @Override - void addPairInt(String fieldName, int v) { - next_(Action.addPair); - appendQuote_(fieldName); - m_jsonString.append(":"); - addValueInt_(v); - } - - @Override - void addPairBoolean(String fieldName, boolean v) { - next_(Action.addPair); - appendQuote_(fieldName); - m_jsonString.append(":"); - addValueBoolean_(v); - } - - @Override - void addPairNull(String fieldName) { - next_(Action.addPair); - appendQuote_(fieldName); - m_jsonString.append(":"); - addValueNull_(); - } - - @Override - void addValueObject() { - next_(Action.addValue); - addValueObject_(); - } - - @Override - void addValueArray() { - next_(Action.addValue); - addValueArray_(); - } - - @Override - void addValueString(String v) { - next_(Action.addValue); - addValueString_(v); - } - - @Override - void addValueDouble(double v) { - next_(Action.addValue); - addValueDouble_(v); - } - - @Override - void addValueDoubleF(double v, int decimals) { - next_(Action.addValue); - addValueDoubleF_(v, decimals); - } - - @Override - void addValueInt(int v) { - next_(Action.addValue); - addValueInt_(v); - } - - @Override - void addValueBoolean(boolean v) { - next_(Action.addValue); - addValueBoolean_(v); - } - - @Override - void addValueNull() { - next_(Action.addValue); - addValueNull_(); - } - - JsonStringWriter() { - m_jsonString = new StringBuilder(); - m_functionStack = new AttributeStreamOfInt32(0); - m_functionStack.add(State.accept); - m_functionStack.add(State.start); - } - private StringBuilder m_jsonString; - private AttributeStreamOfInt32 m_functionStack; - - private void addValueObject_() { - m_jsonString.append('{'); - m_functionStack.add(State.objectStart); - } - - private void addValueArray_() { - m_jsonString.append('['); - m_functionStack.add(State.arrayStart); - } - - private void addValueString_(String v) { - appendQuote_(v); - } - - private void addValueDouble_(double v) { - if (NumberUtils.isNaN(v)) { - addValueNull_(); - return; - } - - StringUtils.appendDouble(v, 17, m_jsonString); - } - - private void addValueDoubleF_(double v, int decimals) { - if (NumberUtils.isNaN(v)) { - addValueNull_(); - return; - } - - StringUtils.appendDoubleF(v, decimals, m_jsonString); - } - - private void addValueInt_(int v) { - m_jsonString.append(v); - } - - private void addValueBoolean_(boolean v) { - if (v) { - m_jsonString.append("true"); - } else { - m_jsonString.append("false"); - } - } - - private void addValueNull_() { - m_jsonString.append("null"); - } - - private void next_(int action) { - switch (m_functionStack.getLast()) { - case State.accept: - accept_(action); - break; - case State.start: - start_(action); - break; - case State.objectStart: - objectStart_(action); - break; - case State.arrayStart: - arrayStart_(action); - break; - case State.pairEnd: - pairEnd_(action); - break; - case State.elementEnd: - elementEnd_(action); - break; - default: - throw new GeometryException("internal error"); - } - } - - private void accept_(int action) { - if (action != Action.accept) { - throw new GeometryException("invalid call"); - } - } - - private void start_(int action) { - if (action == Action.addContainer) { - m_functionStack.removeLast(); - } else { - throw new GeometryException("invalid call"); - } - } - - private void objectStart_(int action) { - m_functionStack.removeLast(); - - if (action == Action.addPair) { - m_functionStack.add(State.pairEnd); - } else if (action != Action.popObject) { - throw new GeometryException("invalid call"); - } - } - - private void pairEnd_(int action) { - if (action == Action.addPair) { - m_jsonString.append(','); - } else if (action == Action.popObject) { - m_functionStack.removeLast(); - } else { - throw new GeometryException("invalid call"); - } - } - - private void arrayStart_(int action) { - m_functionStack.removeLast(); - - if (action == Action.addValue) { - m_functionStack.add(State.elementEnd); - } else if (action != Action.popArray) { - throw new GeometryException("invalid call"); - } - } - - private void elementEnd_(int action) { - if (action == Action.addValue) { - m_jsonString.append(','); - } else if (action == Action.popArray) { - m_functionStack.removeLast(); - } else { - throw new GeometryException("invalid call"); - } - } - - private void appendQuote_(String string) { - int count = 0; - int start = 0; - int end = string.length(); - - m_jsonString.append('"'); - - for (int i = 0; i < end; i++) { - switch (string.charAt(i)) { - case '"': - if (count > 0) { - m_jsonString.append(string, start, start + count); - count = 0; - } - m_jsonString.append("\\\""); - start = i + 1; - break; - case '\\': - if (count > 0) { - m_jsonString.append(string, start, start + count); - count = 0; - } - m_jsonString.append("\\\\"); - start = i + 1; - break; - case '/': - if (i > 0 && string.charAt(i - 1) == '<') { - if (count > 0) { - m_jsonString.append(string, start, start + count); - count = 0; - } - m_jsonString.append("\\/"); - start = i + 1; - } else { - count++; - } - break; - case '\b': - if (count > 0) { - m_jsonString.append(string, start, start + count); - count = 0; - } - m_jsonString.append("\\b"); - start = i + 1; - break; - case '\f': - if (count > 0) { - m_jsonString.append(string, start, start + count); - count = 0; - } - m_jsonString.append("\\f"); - start = i + 1; - break; - case '\n': - if (count > 0) { - m_jsonString.append(string, start, start + count); - count = 0; - } - m_jsonString.append("\\n"); - start = i + 1; - break; - case '\r': - if (count > 0) { - m_jsonString.append(string, start, start + count); - count = 0; - } - m_jsonString.append("\\r"); - start = i + 1; - break; - case '\t': - if (count > 0) { - m_jsonString.append(string, start, start + count); - count = 0; - } - m_jsonString.append("\\t"); - start = i + 1; - break; - default: - count++; - break; - } - } - - if (count > 0) { - m_jsonString.append(string, start, start + count); - } - - m_jsonString.append('"'); - } -} diff --git a/src/com/esri/core/geometry/RasterizedGeometry2D.java b/src/com/esri/core/geometry/RasterizedGeometry2D.java index 0e420658..558c3b16 100644 --- a/src/com/esri/core/geometry/RasterizedGeometry2D.java +++ b/src/com/esri/core/geometry/RasterizedGeometry2D.java @@ -132,7 +132,7 @@ static boolean canUseAccelerator(Geometry geom) { * Dumps the raster to a bmp file for debug purposes. * * @param fileName - * @returns true if success, false otherwise. + * @return true if success, false otherwise. */ public abstract boolean dbgSaveToBitmap(String fileName); From 7d1f9c8b859a6b939d5e7f3889289d3ca7380650 Mon Sep 17 00:00:00 2001 From: Dan O'Neill Date: Thu, 6 Mar 2014 11:40:17 -0900 Subject: [PATCH 080/196] added some ide project setting files to ignore --- .gitignore | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/.gitignore b/.gitignore index 6d29d23b..82b5005e 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,28 @@ esri-geometry-api.jar .classpath description.jardesc *.bak + +# Intellij project files +*.iml +*.ipr +*.iws +.idea/ + +# Eclipse project files +.settings/ +libs/ + +# SublimeText +/*.sublime-project +*.sublime-workspace + +# OS generated files # +###################### +.DS_Store +.DS_Store? +._* +.Spotlight-V100 +.Trashes +Icon? +ehthumbs.db +Thumbs.db From 55ac0a734ae59dbc416f9157b2713907c96f8d89 Mon Sep 17 00:00:00 2001 From: Sergey Tolstov Date: Thu, 15 May 2014 10:58:56 -0700 Subject: [PATCH 081/196] changed the folder structure --- .gitignore | 1 + DepFiles/unittest/jersey-client-1.5.jar | Bin 128096 -> 0 bytes DepFiles/unittest/jersey-core-1.5.jar | Bin 455665 -> 0 bytes build.xml | 33 ++++++++++++------ pom.xml | 28 +++++---------- .../core/geometry/AttributeStreamBase.java | 0 .../core/geometry/AttributeStreamOfDbl.java | 0 .../core/geometry/AttributeStreamOfFloat.java | 0 .../core/geometry/AttributeStreamOfInt16.java | 0 .../core/geometry/AttributeStreamOfInt32.java | 0 .../core/geometry/AttributeStreamOfInt64.java | 0 .../core/geometry/AttributeStreamOfInt8.java | 0 .../com/esri/core/geometry/Boundary.java | 0 .../com/esri/core/geometry/BucketSort.java | 0 .../com/esri/core/geometry/Bufferer.java | 0 .../esri/core/geometry/ByteBufferCursor.java | 0 .../com/esri/core/geometry/ClassicSort.java | 0 .../java}/com/esri/core/geometry/Clipper.java | 0 .../com/esri/core/geometry/Clusterer.java | 0 .../esri/core/geometry/ConstructOffset.java | 0 .../com/esri/core/geometry/ConvexHull.java | 0 .../esri/core/geometry/CrackAndCluster.java | 0 .../java}/com/esri/core/geometry/Cracker.java | 0 .../java}/com/esri/core/geometry/Cutter.java | 0 .../com/esri/core/geometry/DirtyFlags.java | 0 .../com/esri/core/geometry/ECoordinate.java | 0 .../com/esri/core/geometry/EditShape.java | 0 .../com/esri/core/geometry/Envelope.java | 0 .../com/esri/core/geometry/Envelope1D.java | 0 .../com/esri/core/geometry/Envelope2D.java | 0 .../geometry/Envelope2DIntersectorImpl.java | 0 .../com/esri/core/geometry/Envelope3D.java | 0 .../java}/com/esri/core/geometry/GeoDist.java | 0 .../core/geometry/GeoJsonExportFlags.java | 0 .../core/geometry/GeoJsonImportFlags.java | 0 .../esri/core/geometry/GeodeticCurveType.java | 0 .../com/esri/core/geometry/Geometry.java | 0 .../core/geometry/GeometryAccelerators.java | 0 .../esri/core/geometry/GeometryCursor.java | 0 .../core/geometry/GeometryCursorAppend.java | 0 .../esri/core/geometry/GeometryEngine.java | 0 .../esri/core/geometry/GeometryException.java | 0 .../core/geometry/GeometrySerializer.java | 0 .../esri/core/geometry/IndexHashTable.java | 0 .../esri/core/geometry/IndexMultiDCList.java | 0 .../esri/core/geometry/IndexMultiList.java | 0 .../com/esri/core/geometry/InternalUtils.java | 0 .../java}/com/esri/core/geometry/Interop.java | 0 .../esri/core/geometry/IntervalTreeImpl.java | 0 .../core/geometry/JSONArrayEnumerator.java | 0 .../core/geometry/JSONObjectEnumerator.java | 0 .../com/esri/core/geometry/JSONUtils.java | 0 .../com/esri/core/geometry/JsonCursor.java | 0 .../esri/core/geometry/JsonParserCursor.java | 0 .../esri/core/geometry/JsonParserReader.java | 0 .../com/esri/core/geometry/JsonReader.java | 0 .../esri/core/geometry/JsonStringWriter.java | 0 .../esri/core/geometry/JsonValueReader.java | 0 .../com/esri/core/geometry/JsonWriter.java | 0 .../java}/com/esri/core/geometry/Line.java | 0 .../geometry/ListeningGeometryCursor.java | 0 .../com/esri/core/geometry/MapGeometry.java | 0 .../esri/core/geometry/MapGeometryCursor.java | 0 .../esri/core/geometry/MapOGCStructure.java | 0 .../com/esri/core/geometry/MathUtils.java | 0 .../core/geometry/MgrsConversionMode.java | 0 .../com/esri/core/geometry/MultiPath.java | 0 .../com/esri/core/geometry/MultiPathImpl.java | 0 .../com/esri/core/geometry/MultiPoint.java | 0 .../esri/core/geometry/MultiPointImpl.java | 0 .../core/geometry/MultiVertexGeometry.java | 0 .../geometry/MultiVertexGeometryImpl.java | 0 .../esri/core/geometry/NonSimpleResult.java | 0 .../com/esri/core/geometry/NumberUtils.java | 0 .../com/esri/core/geometry/OGCStructure.java | 0 .../esri/core/geometry/ObjectCacheTable.java | 0 .../com/esri/core/geometry/Operator.java | 0 .../esri/core/geometry/OperatorBoundary.java | 0 .../core/geometry/OperatorBoundaryLocal.java | 0 .../geometry/OperatorBoundaryLocalCursor.java | 0 .../esri/core/geometry/OperatorBuffer.java | 0 .../core/geometry/OperatorBufferCursor.java | 0 .../core/geometry/OperatorBufferLocal.java | 0 .../com/esri/core/geometry/OperatorClip.java | 0 .../core/geometry/OperatorClipCursor.java | 0 .../esri/core/geometry/OperatorClipLocal.java | 0 .../esri/core/geometry/OperatorContains.java | 0 .../core/geometry/OperatorContainsLocal.java | 0 .../core/geometry/OperatorConvexHull.java | 0 .../geometry/OperatorConvexHullCursor.java | 0 .../geometry/OperatorConvexHullLocal.java | 0 .../esri/core/geometry/OperatorCrosses.java | 0 .../core/geometry/OperatorCrossesLocal.java | 0 .../com/esri/core/geometry/OperatorCut.java | 0 .../esri/core/geometry/OperatorCutCursor.java | 0 .../esri/core/geometry/OperatorCutLocal.java | 0 .../geometry/OperatorDensifyByLength.java | 0 .../OperatorDensifyByLengthCursor.java | 0 .../OperatorDensifyByLengthLocal.java | 0 .../core/geometry/OperatorDifference.java | 0 .../geometry/OperatorDifferenceCursor.java | 0 .../geometry/OperatorDifferenceLocal.java | 0 .../esri/core/geometry/OperatorDisjoint.java | 0 .../core/geometry/OperatorDisjointLocal.java | 0 .../esri/core/geometry/OperatorDistance.java | 0 .../core/geometry/OperatorDistanceLocal.java | 0 .../esri/core/geometry/OperatorEquals.java | 0 .../core/geometry/OperatorEqualsLocal.java | 0 .../geometry/OperatorExportToESRIShape.java | 0 .../OperatorExportToESRIShapeCursor.java | 0 .../OperatorExportToESRIShapeLocal.java | 0 .../geometry/OperatorExportToGeoJson.java | 0 .../OperatorExportToGeoJsonCursor.java | 0 .../OperatorExportToGeoJsonLocal.java | 0 .../core/geometry/OperatorExportToJson.java | 0 .../geometry/OperatorExportToJsonCursor.java | 0 .../geometry/OperatorExportToJsonLocal.java | 0 .../core/geometry/OperatorExportToWkb.java | 0 .../geometry/OperatorExportToWkbLocal.java | 0 .../core/geometry/OperatorExportToWkt.java | 0 .../geometry/OperatorExportToWktLocal.java | 0 .../esri/core/geometry/OperatorFactory.java | 0 .../core/geometry/OperatorFactoryLocal.java | 0 .../core/geometry/OperatorGeneralize.java | 0 .../geometry/OperatorGeneralizeCursor.java | 0 .../geometry/OperatorGeneralizeLocal.java | 0 .../core/geometry/OperatorGeodeticArea.java | 0 .../geometry/OperatorGeodeticAreaLocal.java | 0 .../core/geometry/OperatorGeodeticLength.java | 0 .../geometry/OperatorGeodeticLengthLocal.java | 0 .../geometry/OperatorImportFromESRIShape.java | 0 .../OperatorImportFromESRIShapeCursor.java | 0 .../OperatorImportFromESRIShapeLocal.java | 0 .../geometry/OperatorImportFromGeoJson.java | 0 .../OperatorImportFromGeoJsonLocal.java | 0 .../core/geometry/OperatorImportFromJson.java | 0 .../OperatorImportFromJsonCursor.java | 0 .../geometry/OperatorImportFromJsonLocal.java | 0 .../core/geometry/OperatorImportFromWkb.java | 0 .../geometry/OperatorImportFromWkbLocal.java | 0 .../core/geometry/OperatorImportFromWkt.java | 0 .../geometry/OperatorImportFromWktLocal.java | 0 .../OperatorInternalRelationUtils.java | 0 .../core/geometry/OperatorIntersection.java | 0 .../geometry/OperatorIntersectionCursor.java | 0 .../geometry/OperatorIntersectionLocal.java | 0 .../core/geometry/OperatorIntersects.java | 0 .../geometry/OperatorIntersectsLocal.java | 0 .../esri/core/geometry/OperatorOffset.java | 0 .../core/geometry/OperatorOffsetCursor.java | 0 .../core/geometry/OperatorOffsetLocal.java | 0 .../esri/core/geometry/OperatorOverlaps.java | 0 .../core/geometry/OperatorOverlapsLocal.java | 0 .../esri/core/geometry/OperatorProject.java | 0 .../core/geometry/OperatorProjectLocal.java | 0 .../core/geometry/OperatorProximity2D.java | 0 .../geometry/OperatorProximity2DLocal.java | 0 .../esri/core/geometry/OperatorRelate.java | 0 .../core/geometry/OperatorRelateLocal.java | 0 .../core/geometry/OperatorSimpleRelation.java | 0 .../esri/core/geometry/OperatorSimplify.java | 0 .../core/geometry/OperatorSimplifyCursor.java | 0 .../geometry/OperatorSimplifyCursorOGC.java | 0 .../core/geometry/OperatorSimplifyLocal.java | 0 .../geometry/OperatorSimplifyLocalHelper.java | 0 .../geometry/OperatorSimplifyLocalOGC.java | 0 .../core/geometry/OperatorSimplifyOGC.java | 0 .../geometry/OperatorSymmetricDifference.java | 0 .../OperatorSymmetricDifferenceCursor.java | 0 .../OperatorSymmetricDifferenceLocal.java | 0 .../esri/core/geometry/OperatorTouches.java | 0 .../core/geometry/OperatorTouchesLocal.java | 0 .../com/esri/core/geometry/OperatorUnion.java | 0 .../core/geometry/OperatorUnionCursor.java | 0 .../core/geometry/OperatorUnionLocal.java | 0 .../esri/core/geometry/OperatorWithin.java | 0 .../core/geometry/OperatorWithinLocal.java | 0 .../com/esri/core/geometry/PathFlags.java | 0 .../com/esri/core/geometry/PeDouble.java | 0 .../geometry/PlaneSweepCrackerHelper.java | 0 .../java}/com/esri/core/geometry/Point.java | 0 .../java}/com/esri/core/geometry/Point2D.java | 0 .../java}/com/esri/core/geometry/Point3D.java | 0 .../core/geometry/PointInPolygonHelper.java | 0 .../java}/com/esri/core/geometry/Polygon.java | 0 .../com/esri/core/geometry/PolygonUtils.java | 0 .../com/esri/core/geometry/Polyline.java | 0 .../com/esri/core/geometry/PolylinePath.java | 0 .../esri/core/geometry/ProgressTracker.java | 0 .../geometry/ProjectionTransformation.java | 0 .../esri/core/geometry/Proximity2DResult.java | 0 .../geometry/Proximity2DResultComparator.java | 0 .../com/esri/core/geometry/QuadTree.java | 0 .../com/esri/core/geometry/QuadTreeImpl.java | 0 .../core/geometry/RasterizedGeometry2D.java | 0 .../geometry/RasterizedGeometry2DImpl.java | 0 .../core/geometry/RelationalOperations.java | 0 .../geometry/RelationalOperationsMatrix.java | 0 .../core/geometry/RingOrientationFixer.java | 0 .../java}/com/esri/core/geometry/Segment.java | 0 .../com/esri/core/geometry/SegmentBuffer.java | 0 .../com/esri/core/geometry/SegmentFlags.java | 0 .../core/geometry/SegmentIntersector.java | 0 .../esri/core/geometry/SegmentIterator.java | 0 .../core/geometry/SegmentIteratorImpl.java | 0 .../esri/core/geometry/ShapeExportFlags.java | 0 .../esri/core/geometry/ShapeImportFlags.java | 0 .../esri/core/geometry/ShapeModifiers.java | 0 .../com/esri/core/geometry/ShapeType.java | 0 .../core/geometry/SimpleByteBufferCursor.java | 0 .../core/geometry/SimpleGeometryCursor.java | 0 .../esri/core/geometry/SimpleJsonCursor.java | 0 .../core/geometry/SimpleJsonParserCursor.java | 0 .../geometry/SimpleMapGeometryCursor.java | 0 .../esri/core/geometry/SimpleRasterizer.java | 0 .../com/esri/core/geometry/Simplificator.java | 0 .../esri/core/geometry/SpatialReference.java | 0 .../core/geometry/SpatialReferenceImpl.java | 0 .../geometry/SpatialReferenceSerializer.java | 0 .../geometry/StridedIndexTypeCollection.java | 0 .../com/esri/core/geometry/StringUtils.java | 0 .../esri/core/geometry/SweepComparator.java | 0 .../core/geometry/SweepMonkierComparator.java | 0 .../com/esri/core/geometry/TopoGraph.java | 0 .../core/geometry/TopologicalOperations.java | 0 .../esri/core/geometry/Transformation2D.java | 0 .../esri/core/geometry/Transformation3D.java | 0 .../java}/com/esri/core/geometry/Treap.java | 0 .../core/geometry/UserCancelException.java | 0 .../esri/core/geometry/VertexDescription.java | 0 .../VertexDescriptionDesignerImpl.java | 0 .../core/geometry/VertexDescriptionHash.java | 0 .../com/esri/core/geometry/WkbByteOrder.java | 0 .../esri/core/geometry/WkbExportFlags.java | 0 .../esri/core/geometry/WkbGeometryType.java | 0 .../esri/core/geometry/WkbImportFlags.java | 0 .../java}/com/esri/core/geometry/Wkid.java | 0 .../java}/com/esri/core/geometry/Wkt.java | 0 .../esri/core/geometry/WktExportFlags.java | 0 .../esri/core/geometry/WktImportFlags.java | 0 .../com/esri/core/geometry/WktParser.java | 0 .../ogc/OGCConcreteGeometryCollection.java | 0 .../com/esri/core/geometry/ogc/OGCCurve.java | 0 .../esri/core/geometry/ogc/OGCGeometry.java | 0 .../geometry/ogc/OGCGeometryCollection.java | 0 .../esri/core/geometry/ogc/OGCLineString.java | 0 .../esri/core/geometry/ogc/OGCLinearRing.java | 0 .../esri/core/geometry/ogc/OGCMultiCurve.java | 0 .../core/geometry/ogc/OGCMultiLineString.java | 0 .../esri/core/geometry/ogc/OGCMultiPoint.java | 0 .../core/geometry/ogc/OGCMultiPolygon.java | 0 .../core/geometry/ogc/OGCMultiSurface.java | 0 .../com/esri/core/geometry/ogc/OGCPoint.java | 0 .../esri/core/geometry/ogc/OGCPolygon.java | 0 .../esri/core/geometry/ogc/OGCSurface.java | 0 .../core/geometry/gcs_id_to_tolerance.txt | 0 .../com/esri/core/geometry/gcs_tolerances.txt | 0 .../geometry/intermediate_to_old_wkid.txt | 0 .../esri/core/geometry/new_to_old_wkid.txt | 0 .../core/geometry/pcs_id_to_tolerance.txt | 0 .../com/esri/core/geometry/pcs_tolerances.txt | 0 .../com/esri/core/geometry/GeometryUtils.java | 0 .../geometry/RandomCoordinateGenerator.java | 0 .../esri/core/geometry/TestAttributes.java | 0 .../com/esri/core/geometry/TestBuffer.java | 0 .../com/esri/core/geometry/TestClip.java | 0 .../esri/core/geometry/TestCommonMethods.java | 0 .../com/esri/core/geometry/TestContains.java | 0 .../esri/core/geometry/TestConvexHull.java | 0 .../java}/com/esri/core/geometry/TestCut.java | 0 .../esri/core/geometry/TestDifference.java | 0 .../com/esri/core/geometry/TestDistance.java | 0 .../com/esri/core/geometry/TestEditShape.java | 0 .../geometry/TestEnvelope2DIntersector.java | 0 .../com/esri/core/geometry/TestEquals.java | 0 .../com/esri/core/geometry/TestFailed.java | 0 .../esri/core/geometry/TestGeneralize.java | 0 .../com/esri/core/geometry/TestGeodetic.java | 0 .../esri/core/geometry/TestGeomToGeoJson.java | 0 ...omToJSonExportSRFromWkiOrWkt_CR181369.java | 0 .../esri/core/geometry/TestImportExport.java | 0 .../geometry/TestInterpolateAttributes.java | 0 .../esri/core/geometry/TestIntersect2.java | 0 .../esri/core/geometry/TestIntersection.java | 0 .../esri/core/geometry/TestIntervalTree.java | 0 .../esri/core/geometry/TestJSonGeometry.java | 0 .../TestJSonToGeomFromWkiOrWkt_CR177613.java | 0 .../esri/core/geometry/TestJsonParser.java | 0 .../com/esri/core/geometry/TestMathUtils.java | 0 .../esri/core/geometry/TestMultiPoint.java | 0 .../java}/com/esri/core/geometry/TestOGC.java | 0 .../com/esri/core/geometry/TestOffset.java | 0 .../com/esri/core/geometry/TestPoint.java | 0 .../com/esri/core/geometry/TestPolygon.java | 0 .../esri/core/geometry/TestPolygonUtils.java | 0 .../esri/core/geometry/TestProximity2D.java | 0 .../com/esri/core/geometry/TestQuadTree.java | 0 .../geometry/TestRasterizedGeometry2D.java | 0 .../com/esri/core/geometry/TestRelation.java | 0 .../esri/core/geometry/TestSerialization.java | 0 .../core/geometry/TestShapePreserving.java | 0 .../com/esri/core/geometry/TestSimplify.java | 0 .../com/esri/core/geometry/TestTouch.java | 0 .../com/esri/core/geometry/TestTreap.java | 0 .../com/esri/core/geometry/TestUnion.java | 0 .../esri/core/geometry/TestWKBSupport.java | 0 .../geometry/TestWkbImportOnPostgresST.java | 0 .../com/esri/core/geometry/TestWkid.java | 0 .../com/esri/core/geometry/TestWktParser.java | 0 .../java}/com/esri/core/geometry/Utils.java | 0 .../esri/core/geometry/savedAngularUnit.txt | Bin .../com/esri/core/geometry/savedAreaUnit.txt | Bin .../com/esri/core/geometry/savedEnvelope.txt | Bin .../esri/core/geometry/savedEnvelope2D.txt | Bin .../esri/core/geometry/savedLinearUnit.txt | Bin .../esri/core/geometry/savedMultiPoint.txt | Bin .../com/esri/core/geometry/savedPoint.txt | Bin .../com/esri/core/geometry/savedPolygon.txt | Bin .../com/esri/core/geometry/savedPolyline.txt | Bin .../savedProjectionTransformation.txt | Bin .../core/geometry/savedSpatialReference.txt | Bin 321 files changed, 32 insertions(+), 30 deletions(-) delete mode 100644 DepFiles/unittest/jersey-client-1.5.jar delete mode 100644 DepFiles/unittest/jersey-core-1.5.jar rename src/{ => main/java}/com/esri/core/geometry/AttributeStreamBase.java (100%) rename src/{ => main/java}/com/esri/core/geometry/AttributeStreamOfDbl.java (100%) rename src/{ => main/java}/com/esri/core/geometry/AttributeStreamOfFloat.java (100%) rename src/{ => main/java}/com/esri/core/geometry/AttributeStreamOfInt16.java (100%) rename src/{ => main/java}/com/esri/core/geometry/AttributeStreamOfInt32.java (100%) rename src/{ => main/java}/com/esri/core/geometry/AttributeStreamOfInt64.java (100%) rename src/{ => main/java}/com/esri/core/geometry/AttributeStreamOfInt8.java (100%) rename src/{ => main/java}/com/esri/core/geometry/Boundary.java (100%) rename src/{ => main/java}/com/esri/core/geometry/BucketSort.java (100%) rename src/{ => main/java}/com/esri/core/geometry/Bufferer.java (100%) rename src/{ => main/java}/com/esri/core/geometry/ByteBufferCursor.java (100%) rename src/{ => main/java}/com/esri/core/geometry/ClassicSort.java (100%) rename src/{ => main/java}/com/esri/core/geometry/Clipper.java (100%) rename src/{ => main/java}/com/esri/core/geometry/Clusterer.java (100%) rename src/{ => main/java}/com/esri/core/geometry/ConstructOffset.java (100%) rename src/{ => main/java}/com/esri/core/geometry/ConvexHull.java (100%) rename src/{ => main/java}/com/esri/core/geometry/CrackAndCluster.java (100%) rename src/{ => main/java}/com/esri/core/geometry/Cracker.java (100%) rename src/{ => main/java}/com/esri/core/geometry/Cutter.java (100%) rename src/{ => main/java}/com/esri/core/geometry/DirtyFlags.java (100%) rename src/{ => main/java}/com/esri/core/geometry/ECoordinate.java (100%) rename src/{ => main/java}/com/esri/core/geometry/EditShape.java (100%) rename src/{ => main/java}/com/esri/core/geometry/Envelope.java (100%) rename src/{ => main/java}/com/esri/core/geometry/Envelope1D.java (100%) rename src/{ => main/java}/com/esri/core/geometry/Envelope2D.java (100%) rename src/{ => main/java}/com/esri/core/geometry/Envelope2DIntersectorImpl.java (100%) rename src/{ => main/java}/com/esri/core/geometry/Envelope3D.java (100%) rename src/{ => main/java}/com/esri/core/geometry/GeoDist.java (100%) rename src/{ => main/java}/com/esri/core/geometry/GeoJsonExportFlags.java (100%) rename src/{ => main/java}/com/esri/core/geometry/GeoJsonImportFlags.java (100%) rename src/{ => main/java}/com/esri/core/geometry/GeodeticCurveType.java (100%) rename src/{ => main/java}/com/esri/core/geometry/Geometry.java (100%) rename src/{ => main/java}/com/esri/core/geometry/GeometryAccelerators.java (100%) rename src/{ => main/java}/com/esri/core/geometry/GeometryCursor.java (100%) rename src/{ => main/java}/com/esri/core/geometry/GeometryCursorAppend.java (100%) rename src/{ => main/java}/com/esri/core/geometry/GeometryEngine.java (100%) rename src/{ => main/java}/com/esri/core/geometry/GeometryException.java (100%) rename src/{ => main/java}/com/esri/core/geometry/GeometrySerializer.java (100%) rename src/{ => main/java}/com/esri/core/geometry/IndexHashTable.java (100%) rename src/{ => main/java}/com/esri/core/geometry/IndexMultiDCList.java (100%) rename src/{ => main/java}/com/esri/core/geometry/IndexMultiList.java (100%) rename src/{ => main/java}/com/esri/core/geometry/InternalUtils.java (100%) rename src/{ => main/java}/com/esri/core/geometry/Interop.java (100%) rename src/{ => main/java}/com/esri/core/geometry/IntervalTreeImpl.java (100%) rename src/{ => main/java}/com/esri/core/geometry/JSONArrayEnumerator.java (100%) rename src/{ => main/java}/com/esri/core/geometry/JSONObjectEnumerator.java (100%) rename src/{ => main/java}/com/esri/core/geometry/JSONUtils.java (100%) rename src/{ => main/java}/com/esri/core/geometry/JsonCursor.java (100%) rename src/{ => main/java}/com/esri/core/geometry/JsonParserCursor.java (100%) rename src/{ => main/java}/com/esri/core/geometry/JsonParserReader.java (100%) rename src/{ => main/java}/com/esri/core/geometry/JsonReader.java (100%) rename src/{ => main/java}/com/esri/core/geometry/JsonStringWriter.java (100%) rename src/{ => main/java}/com/esri/core/geometry/JsonValueReader.java (100%) rename src/{ => main/java}/com/esri/core/geometry/JsonWriter.java (100%) rename src/{ => main/java}/com/esri/core/geometry/Line.java (100%) rename src/{ => main/java}/com/esri/core/geometry/ListeningGeometryCursor.java (100%) rename src/{ => main/java}/com/esri/core/geometry/MapGeometry.java (100%) rename src/{ => main/java}/com/esri/core/geometry/MapGeometryCursor.java (100%) rename src/{ => main/java}/com/esri/core/geometry/MapOGCStructure.java (100%) rename src/{ => main/java}/com/esri/core/geometry/MathUtils.java (100%) rename src/{ => main/java}/com/esri/core/geometry/MgrsConversionMode.java (100%) rename src/{ => main/java}/com/esri/core/geometry/MultiPath.java (100%) rename src/{ => main/java}/com/esri/core/geometry/MultiPathImpl.java (100%) rename src/{ => main/java}/com/esri/core/geometry/MultiPoint.java (100%) rename src/{ => main/java}/com/esri/core/geometry/MultiPointImpl.java (100%) rename src/{ => main/java}/com/esri/core/geometry/MultiVertexGeometry.java (100%) rename src/{ => main/java}/com/esri/core/geometry/MultiVertexGeometryImpl.java (100%) rename src/{ => main/java}/com/esri/core/geometry/NonSimpleResult.java (100%) rename src/{ => main/java}/com/esri/core/geometry/NumberUtils.java (100%) rename src/{ => main/java}/com/esri/core/geometry/OGCStructure.java (100%) rename src/{ => main/java}/com/esri/core/geometry/ObjectCacheTable.java (100%) rename src/{ => main/java}/com/esri/core/geometry/Operator.java (100%) rename src/{ => main/java}/com/esri/core/geometry/OperatorBoundary.java (100%) rename src/{ => main/java}/com/esri/core/geometry/OperatorBoundaryLocal.java (100%) rename src/{ => main/java}/com/esri/core/geometry/OperatorBoundaryLocalCursor.java (100%) rename src/{ => main/java}/com/esri/core/geometry/OperatorBuffer.java (100%) rename src/{ => main/java}/com/esri/core/geometry/OperatorBufferCursor.java (100%) rename src/{ => main/java}/com/esri/core/geometry/OperatorBufferLocal.java (100%) rename src/{ => main/java}/com/esri/core/geometry/OperatorClip.java (100%) rename src/{ => main/java}/com/esri/core/geometry/OperatorClipCursor.java (100%) rename src/{ => main/java}/com/esri/core/geometry/OperatorClipLocal.java (100%) rename src/{ => main/java}/com/esri/core/geometry/OperatorContains.java (100%) rename src/{ => main/java}/com/esri/core/geometry/OperatorContainsLocal.java (100%) rename src/{ => main/java}/com/esri/core/geometry/OperatorConvexHull.java (100%) rename src/{ => main/java}/com/esri/core/geometry/OperatorConvexHullCursor.java (100%) rename src/{ => main/java}/com/esri/core/geometry/OperatorConvexHullLocal.java (100%) rename src/{ => main/java}/com/esri/core/geometry/OperatorCrosses.java (100%) rename src/{ => main/java}/com/esri/core/geometry/OperatorCrossesLocal.java (100%) rename src/{ => main/java}/com/esri/core/geometry/OperatorCut.java (100%) rename src/{ => main/java}/com/esri/core/geometry/OperatorCutCursor.java (100%) rename src/{ => main/java}/com/esri/core/geometry/OperatorCutLocal.java (100%) rename src/{ => main/java}/com/esri/core/geometry/OperatorDensifyByLength.java (100%) rename src/{ => main/java}/com/esri/core/geometry/OperatorDensifyByLengthCursor.java (100%) rename src/{ => main/java}/com/esri/core/geometry/OperatorDensifyByLengthLocal.java (100%) rename src/{ => main/java}/com/esri/core/geometry/OperatorDifference.java (100%) rename src/{ => main/java}/com/esri/core/geometry/OperatorDifferenceCursor.java (100%) rename src/{ => main/java}/com/esri/core/geometry/OperatorDifferenceLocal.java (100%) rename src/{ => main/java}/com/esri/core/geometry/OperatorDisjoint.java (100%) rename src/{ => main/java}/com/esri/core/geometry/OperatorDisjointLocal.java (100%) rename src/{ => main/java}/com/esri/core/geometry/OperatorDistance.java (100%) rename src/{ => main/java}/com/esri/core/geometry/OperatorDistanceLocal.java (100%) rename src/{ => main/java}/com/esri/core/geometry/OperatorEquals.java (100%) rename src/{ => main/java}/com/esri/core/geometry/OperatorEqualsLocal.java (100%) rename src/{ => main/java}/com/esri/core/geometry/OperatorExportToESRIShape.java (100%) rename src/{ => main/java}/com/esri/core/geometry/OperatorExportToESRIShapeCursor.java (100%) rename src/{ => main/java}/com/esri/core/geometry/OperatorExportToESRIShapeLocal.java (100%) rename src/{ => main/java}/com/esri/core/geometry/OperatorExportToGeoJson.java (100%) rename src/{ => main/java}/com/esri/core/geometry/OperatorExportToGeoJsonCursor.java (100%) rename src/{ => main/java}/com/esri/core/geometry/OperatorExportToGeoJsonLocal.java (100%) rename src/{ => main/java}/com/esri/core/geometry/OperatorExportToJson.java (100%) rename src/{ => main/java}/com/esri/core/geometry/OperatorExportToJsonCursor.java (100%) rename src/{ => main/java}/com/esri/core/geometry/OperatorExportToJsonLocal.java (100%) rename src/{ => main/java}/com/esri/core/geometry/OperatorExportToWkb.java (100%) rename src/{ => main/java}/com/esri/core/geometry/OperatorExportToWkbLocal.java (100%) rename src/{ => main/java}/com/esri/core/geometry/OperatorExportToWkt.java (100%) rename src/{ => main/java}/com/esri/core/geometry/OperatorExportToWktLocal.java (100%) rename src/{ => main/java}/com/esri/core/geometry/OperatorFactory.java (100%) rename src/{ => main/java}/com/esri/core/geometry/OperatorFactoryLocal.java (100%) rename src/{ => main/java}/com/esri/core/geometry/OperatorGeneralize.java (100%) rename src/{ => main/java}/com/esri/core/geometry/OperatorGeneralizeCursor.java (100%) rename src/{ => main/java}/com/esri/core/geometry/OperatorGeneralizeLocal.java (100%) rename src/{ => main/java}/com/esri/core/geometry/OperatorGeodeticArea.java (100%) rename src/{ => main/java}/com/esri/core/geometry/OperatorGeodeticAreaLocal.java (100%) rename src/{ => main/java}/com/esri/core/geometry/OperatorGeodeticLength.java (100%) rename src/{ => main/java}/com/esri/core/geometry/OperatorGeodeticLengthLocal.java (100%) rename src/{ => main/java}/com/esri/core/geometry/OperatorImportFromESRIShape.java (100%) rename src/{ => main/java}/com/esri/core/geometry/OperatorImportFromESRIShapeCursor.java (100%) rename src/{ => main/java}/com/esri/core/geometry/OperatorImportFromESRIShapeLocal.java (100%) rename src/{ => main/java}/com/esri/core/geometry/OperatorImportFromGeoJson.java (100%) rename src/{ => main/java}/com/esri/core/geometry/OperatorImportFromGeoJsonLocal.java (100%) rename src/{ => main/java}/com/esri/core/geometry/OperatorImportFromJson.java (100%) rename src/{ => main/java}/com/esri/core/geometry/OperatorImportFromJsonCursor.java (100%) rename src/{ => main/java}/com/esri/core/geometry/OperatorImportFromJsonLocal.java (100%) rename src/{ => main/java}/com/esri/core/geometry/OperatorImportFromWkb.java (100%) rename src/{ => main/java}/com/esri/core/geometry/OperatorImportFromWkbLocal.java (100%) rename src/{ => main/java}/com/esri/core/geometry/OperatorImportFromWkt.java (100%) rename src/{ => main/java}/com/esri/core/geometry/OperatorImportFromWktLocal.java (100%) rename src/{ => main/java}/com/esri/core/geometry/OperatorInternalRelationUtils.java (100%) rename src/{ => main/java}/com/esri/core/geometry/OperatorIntersection.java (100%) rename src/{ => main/java}/com/esri/core/geometry/OperatorIntersectionCursor.java (100%) rename src/{ => main/java}/com/esri/core/geometry/OperatorIntersectionLocal.java (100%) rename src/{ => main/java}/com/esri/core/geometry/OperatorIntersects.java (100%) rename src/{ => main/java}/com/esri/core/geometry/OperatorIntersectsLocal.java (100%) rename src/{ => main/java}/com/esri/core/geometry/OperatorOffset.java (100%) rename src/{ => main/java}/com/esri/core/geometry/OperatorOffsetCursor.java (100%) rename src/{ => main/java}/com/esri/core/geometry/OperatorOffsetLocal.java (100%) rename src/{ => main/java}/com/esri/core/geometry/OperatorOverlaps.java (100%) rename src/{ => main/java}/com/esri/core/geometry/OperatorOverlapsLocal.java (100%) rename src/{ => main/java}/com/esri/core/geometry/OperatorProject.java (100%) rename src/{ => main/java}/com/esri/core/geometry/OperatorProjectLocal.java (100%) rename src/{ => main/java}/com/esri/core/geometry/OperatorProximity2D.java (100%) rename src/{ => main/java}/com/esri/core/geometry/OperatorProximity2DLocal.java (100%) rename src/{ => main/java}/com/esri/core/geometry/OperatorRelate.java (100%) rename src/{ => main/java}/com/esri/core/geometry/OperatorRelateLocal.java (100%) rename src/{ => main/java}/com/esri/core/geometry/OperatorSimpleRelation.java (100%) rename src/{ => main/java}/com/esri/core/geometry/OperatorSimplify.java (100%) rename src/{ => main/java}/com/esri/core/geometry/OperatorSimplifyCursor.java (100%) rename src/{ => main/java}/com/esri/core/geometry/OperatorSimplifyCursorOGC.java (100%) rename src/{ => main/java}/com/esri/core/geometry/OperatorSimplifyLocal.java (100%) rename src/{ => main/java}/com/esri/core/geometry/OperatorSimplifyLocalHelper.java (100%) rename src/{ => main/java}/com/esri/core/geometry/OperatorSimplifyLocalOGC.java (100%) rename src/{ => main/java}/com/esri/core/geometry/OperatorSimplifyOGC.java (100%) rename src/{ => main/java}/com/esri/core/geometry/OperatorSymmetricDifference.java (100%) rename src/{ => main/java}/com/esri/core/geometry/OperatorSymmetricDifferenceCursor.java (100%) rename src/{ => main/java}/com/esri/core/geometry/OperatorSymmetricDifferenceLocal.java (100%) rename src/{ => main/java}/com/esri/core/geometry/OperatorTouches.java (100%) rename src/{ => main/java}/com/esri/core/geometry/OperatorTouchesLocal.java (100%) rename src/{ => main/java}/com/esri/core/geometry/OperatorUnion.java (100%) rename src/{ => main/java}/com/esri/core/geometry/OperatorUnionCursor.java (100%) rename src/{ => main/java}/com/esri/core/geometry/OperatorUnionLocal.java (100%) rename src/{ => main/java}/com/esri/core/geometry/OperatorWithin.java (100%) rename src/{ => main/java}/com/esri/core/geometry/OperatorWithinLocal.java (100%) rename src/{ => main/java}/com/esri/core/geometry/PathFlags.java (100%) rename src/{ => main/java}/com/esri/core/geometry/PeDouble.java (100%) rename src/{ => main/java}/com/esri/core/geometry/PlaneSweepCrackerHelper.java (100%) rename src/{ => main/java}/com/esri/core/geometry/Point.java (100%) rename src/{ => main/java}/com/esri/core/geometry/Point2D.java (100%) rename src/{ => main/java}/com/esri/core/geometry/Point3D.java (100%) rename src/{ => main/java}/com/esri/core/geometry/PointInPolygonHelper.java (100%) rename src/{ => main/java}/com/esri/core/geometry/Polygon.java (100%) rename src/{ => main/java}/com/esri/core/geometry/PolygonUtils.java (100%) rename src/{ => main/java}/com/esri/core/geometry/Polyline.java (100%) rename src/{ => main/java}/com/esri/core/geometry/PolylinePath.java (100%) rename src/{ => main/java}/com/esri/core/geometry/ProgressTracker.java (100%) rename src/{ => main/java}/com/esri/core/geometry/ProjectionTransformation.java (100%) rename src/{ => main/java}/com/esri/core/geometry/Proximity2DResult.java (100%) rename src/{ => main/java}/com/esri/core/geometry/Proximity2DResultComparator.java (100%) rename src/{ => main/java}/com/esri/core/geometry/QuadTree.java (100%) rename src/{ => main/java}/com/esri/core/geometry/QuadTreeImpl.java (100%) rename src/{ => main/java}/com/esri/core/geometry/RasterizedGeometry2D.java (100%) rename src/{ => main/java}/com/esri/core/geometry/RasterizedGeometry2DImpl.java (100%) rename src/{ => main/java}/com/esri/core/geometry/RelationalOperations.java (100%) rename src/{ => main/java}/com/esri/core/geometry/RelationalOperationsMatrix.java (100%) rename src/{ => main/java}/com/esri/core/geometry/RingOrientationFixer.java (100%) rename src/{ => main/java}/com/esri/core/geometry/Segment.java (100%) rename src/{ => main/java}/com/esri/core/geometry/SegmentBuffer.java (100%) rename src/{ => main/java}/com/esri/core/geometry/SegmentFlags.java (100%) rename src/{ => main/java}/com/esri/core/geometry/SegmentIntersector.java (100%) rename src/{ => main/java}/com/esri/core/geometry/SegmentIterator.java (100%) rename src/{ => main/java}/com/esri/core/geometry/SegmentIteratorImpl.java (100%) rename src/{ => main/java}/com/esri/core/geometry/ShapeExportFlags.java (100%) rename src/{ => main/java}/com/esri/core/geometry/ShapeImportFlags.java (100%) rename src/{ => main/java}/com/esri/core/geometry/ShapeModifiers.java (100%) rename src/{ => main/java}/com/esri/core/geometry/ShapeType.java (100%) rename src/{ => main/java}/com/esri/core/geometry/SimpleByteBufferCursor.java (100%) rename src/{ => main/java}/com/esri/core/geometry/SimpleGeometryCursor.java (100%) rename src/{ => main/java}/com/esri/core/geometry/SimpleJsonCursor.java (100%) rename src/{ => main/java}/com/esri/core/geometry/SimpleJsonParserCursor.java (100%) rename src/{ => main/java}/com/esri/core/geometry/SimpleMapGeometryCursor.java (100%) rename src/{ => main/java}/com/esri/core/geometry/SimpleRasterizer.java (100%) rename src/{ => main/java}/com/esri/core/geometry/Simplificator.java (100%) rename src/{ => main/java}/com/esri/core/geometry/SpatialReference.java (100%) rename src/{ => main/java}/com/esri/core/geometry/SpatialReferenceImpl.java (100%) rename src/{ => main/java}/com/esri/core/geometry/SpatialReferenceSerializer.java (100%) rename src/{ => main/java}/com/esri/core/geometry/StridedIndexTypeCollection.java (100%) rename src/{ => main/java}/com/esri/core/geometry/StringUtils.java (100%) rename src/{ => main/java}/com/esri/core/geometry/SweepComparator.java (100%) rename src/{ => main/java}/com/esri/core/geometry/SweepMonkierComparator.java (100%) rename src/{ => main/java}/com/esri/core/geometry/TopoGraph.java (100%) rename src/{ => main/java}/com/esri/core/geometry/TopologicalOperations.java (100%) rename src/{ => main/java}/com/esri/core/geometry/Transformation2D.java (100%) rename src/{ => main/java}/com/esri/core/geometry/Transformation3D.java (100%) rename src/{ => main/java}/com/esri/core/geometry/Treap.java (100%) rename src/{ => main/java}/com/esri/core/geometry/UserCancelException.java (100%) rename src/{ => main/java}/com/esri/core/geometry/VertexDescription.java (100%) rename src/{ => main/java}/com/esri/core/geometry/VertexDescriptionDesignerImpl.java (100%) rename src/{ => main/java}/com/esri/core/geometry/VertexDescriptionHash.java (100%) rename src/{ => main/java}/com/esri/core/geometry/WkbByteOrder.java (100%) rename src/{ => main/java}/com/esri/core/geometry/WkbExportFlags.java (100%) rename src/{ => main/java}/com/esri/core/geometry/WkbGeometryType.java (100%) rename src/{ => main/java}/com/esri/core/geometry/WkbImportFlags.java (100%) rename src/{ => main/java}/com/esri/core/geometry/Wkid.java (100%) rename src/{ => main/java}/com/esri/core/geometry/Wkt.java (100%) rename src/{ => main/java}/com/esri/core/geometry/WktExportFlags.java (100%) rename src/{ => main/java}/com/esri/core/geometry/WktImportFlags.java (100%) rename src/{ => main/java}/com/esri/core/geometry/WktParser.java (100%) rename src/{ => main/java}/com/esri/core/geometry/ogc/OGCConcreteGeometryCollection.java (100%) rename src/{ => main/java}/com/esri/core/geometry/ogc/OGCCurve.java (100%) rename src/{ => main/java}/com/esri/core/geometry/ogc/OGCGeometry.java (100%) rename src/{ => main/java}/com/esri/core/geometry/ogc/OGCGeometryCollection.java (100%) rename src/{ => main/java}/com/esri/core/geometry/ogc/OGCLineString.java (100%) rename src/{ => main/java}/com/esri/core/geometry/ogc/OGCLinearRing.java (100%) rename src/{ => main/java}/com/esri/core/geometry/ogc/OGCMultiCurve.java (100%) rename src/{ => main/java}/com/esri/core/geometry/ogc/OGCMultiLineString.java (100%) rename src/{ => main/java}/com/esri/core/geometry/ogc/OGCMultiPoint.java (100%) rename src/{ => main/java}/com/esri/core/geometry/ogc/OGCMultiPolygon.java (100%) rename src/{ => main/java}/com/esri/core/geometry/ogc/OGCMultiSurface.java (100%) rename src/{ => main/java}/com/esri/core/geometry/ogc/OGCPoint.java (100%) rename src/{ => main/java}/com/esri/core/geometry/ogc/OGCPolygon.java (100%) rename src/{ => main/java}/com/esri/core/geometry/ogc/OGCSurface.java (100%) rename src/{ => main/resources}/com/esri/core/geometry/gcs_id_to_tolerance.txt (100%) rename src/{ => main/resources}/com/esri/core/geometry/gcs_tolerances.txt (100%) rename src/{ => main/resources}/com/esri/core/geometry/intermediate_to_old_wkid.txt (100%) rename src/{ => main/resources}/com/esri/core/geometry/new_to_old_wkid.txt (100%) rename src/{ => main/resources}/com/esri/core/geometry/pcs_id_to_tolerance.txt (100%) rename src/{ => main/resources}/com/esri/core/geometry/pcs_tolerances.txt (100%) rename {unittest => src/test/java}/com/esri/core/geometry/GeometryUtils.java (100%) rename {unittest => src/test/java}/com/esri/core/geometry/RandomCoordinateGenerator.java (100%) rename {unittest => src/test/java}/com/esri/core/geometry/TestAttributes.java (100%) rename {unittest => src/test/java}/com/esri/core/geometry/TestBuffer.java (100%) rename {unittest => src/test/java}/com/esri/core/geometry/TestClip.java (100%) rename {unittest => src/test/java}/com/esri/core/geometry/TestCommonMethods.java (100%) rename {unittest => src/test/java}/com/esri/core/geometry/TestContains.java (100%) rename {unittest => src/test/java}/com/esri/core/geometry/TestConvexHull.java (100%) rename {unittest => src/test/java}/com/esri/core/geometry/TestCut.java (100%) rename {unittest => src/test/java}/com/esri/core/geometry/TestDifference.java (100%) rename {unittest => src/test/java}/com/esri/core/geometry/TestDistance.java (100%) rename {unittest => src/test/java}/com/esri/core/geometry/TestEditShape.java (100%) rename {unittest => src/test/java}/com/esri/core/geometry/TestEnvelope2DIntersector.java (100%) rename {unittest => src/test/java}/com/esri/core/geometry/TestEquals.java (100%) rename {unittest => src/test/java}/com/esri/core/geometry/TestFailed.java (100%) rename {unittest => src/test/java}/com/esri/core/geometry/TestGeneralize.java (100%) rename {unittest => src/test/java}/com/esri/core/geometry/TestGeodetic.java (100%) rename {unittest => src/test/java}/com/esri/core/geometry/TestGeomToGeoJson.java (100%) rename {unittest => src/test/java}/com/esri/core/geometry/TestGeomToJSonExportSRFromWkiOrWkt_CR181369.java (100%) rename {unittest => src/test/java}/com/esri/core/geometry/TestImportExport.java (100%) rename {unittest => src/test/java}/com/esri/core/geometry/TestInterpolateAttributes.java (100%) rename {unittest => src/test/java}/com/esri/core/geometry/TestIntersect2.java (100%) rename {unittest => src/test/java}/com/esri/core/geometry/TestIntersection.java (100%) rename {unittest => src/test/java}/com/esri/core/geometry/TestIntervalTree.java (100%) rename {unittest => src/test/java}/com/esri/core/geometry/TestJSonGeometry.java (100%) rename {unittest => src/test/java}/com/esri/core/geometry/TestJSonToGeomFromWkiOrWkt_CR177613.java (100%) rename {unittest => src/test/java}/com/esri/core/geometry/TestJsonParser.java (100%) rename {unittest => src/test/java}/com/esri/core/geometry/TestMathUtils.java (100%) rename {unittest => src/test/java}/com/esri/core/geometry/TestMultiPoint.java (100%) rename {unittest => src/test/java}/com/esri/core/geometry/TestOGC.java (100%) rename {unittest => src/test/java}/com/esri/core/geometry/TestOffset.java (100%) rename {unittest => src/test/java}/com/esri/core/geometry/TestPoint.java (100%) rename {unittest => src/test/java}/com/esri/core/geometry/TestPolygon.java (100%) rename {unittest => src/test/java}/com/esri/core/geometry/TestPolygonUtils.java (100%) rename {unittest => src/test/java}/com/esri/core/geometry/TestProximity2D.java (100%) rename {unittest => src/test/java}/com/esri/core/geometry/TestQuadTree.java (100%) rename {unittest => src/test/java}/com/esri/core/geometry/TestRasterizedGeometry2D.java (100%) rename {unittest => src/test/java}/com/esri/core/geometry/TestRelation.java (100%) rename {unittest => src/test/java}/com/esri/core/geometry/TestSerialization.java (100%) rename {unittest => src/test/java}/com/esri/core/geometry/TestShapePreserving.java (100%) rename {unittest => src/test/java}/com/esri/core/geometry/TestSimplify.java (100%) rename {unittest => src/test/java}/com/esri/core/geometry/TestTouch.java (100%) rename {unittest => src/test/java}/com/esri/core/geometry/TestTreap.java (100%) rename {unittest => src/test/java}/com/esri/core/geometry/TestUnion.java (100%) rename {unittest => src/test/java}/com/esri/core/geometry/TestWKBSupport.java (100%) rename {unittest => src/test/java}/com/esri/core/geometry/TestWkbImportOnPostgresST.java (100%) rename {unittest => src/test/java}/com/esri/core/geometry/TestWkid.java (100%) rename {unittest => src/test/java}/com/esri/core/geometry/TestWktParser.java (100%) rename {unittest => src/test/java}/com/esri/core/geometry/Utils.java (100%) rename {unittest => src/test/resources}/com/esri/core/geometry/savedAngularUnit.txt (100%) rename {unittest => src/test/resources}/com/esri/core/geometry/savedAreaUnit.txt (100%) rename {unittest => src/test/resources}/com/esri/core/geometry/savedEnvelope.txt (100%) rename {unittest => src/test/resources}/com/esri/core/geometry/savedEnvelope2D.txt (100%) rename {unittest => src/test/resources}/com/esri/core/geometry/savedLinearUnit.txt (100%) rename {unittest => src/test/resources}/com/esri/core/geometry/savedMultiPoint.txt (100%) rename {unittest => src/test/resources}/com/esri/core/geometry/savedPoint.txt (100%) rename {unittest => src/test/resources}/com/esri/core/geometry/savedPolygon.txt (100%) rename {unittest => src/test/resources}/com/esri/core/geometry/savedPolyline.txt (100%) rename {unittest => src/test/resources}/com/esri/core/geometry/savedProjectionTransformation.txt (100%) rename {unittest => src/test/resources}/com/esri/core/geometry/savedSpatialReference.txt (100%) diff --git a/.gitignore b/.gitignore index 82b5005e..03c080fb 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,7 @@ esri-geometry-api.jar .classpath description.jardesc *.bak +*.properties # Intellij project files *.iml diff --git a/DepFiles/unittest/jersey-client-1.5.jar b/DepFiles/unittest/jersey-client-1.5.jar deleted file mode 100644 index 62f790fa6fba8d2783a059b4a493fb28bb563373..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 128096 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 sc^g^m7M-Jkk0+RkQFO>YU`8qwzbXzaVr6 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 z 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 z zbYX^8dn9;UQ@_1_Bowa`&rO5F_ULKNA7dWJp4D;r17Z_Q%A_Rl*<8P{q0mlWQFnEM z3=rR)OND=S0k(>Dev;jz$r&@#C%m#VQbN6-79_6l{1PM0u#3XU&KRjtT51^o>F|jz zUv6z|M%2Od1Z@Qqszaxs*tMw|0*$|-v_lzeoIAx|9}wzX(|PFvgCn`ARfZXnb!6*o zbG6w(B$lY?ICli2-Rc5BE)xIOu;GjW`wYv)SI1YdiJ%3-@>tvvrLQF|05e$b9aCum#3|R>gQmo&wW+*hxyVn~?kL9tb zIcz+1i|t{V4$=T!4Fdpa-Ohzs<2pJcUEmJW(~8@_oG5iw$Y)VOL3adUVGhA;3;$BM>YYHp-~Y-jL{G(1GpI9NMxI^Fu(bJ+L{5(`2bWF$;1AtIY*KnE7nN`B0; zi#_B>BX`~TMUfP^yAsa=MyJ5MjbcA{P`@!xt&7(zzngbfrJ%HAUYjV50Ap@G*?mABh&to- zDj(vx4<~H^k6j3tm;*QeS1)IrhJSEkTBhNulJ06ABO$aHx}ig5j*DWXGkJ25$$tC9 zzG!7T7q*`tvlC!suEtM4GKexjvC0vXd} ze91+1bVR;2paj|YBFFFHbkb{FUyTw^7>WJK8N4f)G7&XRM!gYaA)VH?hr!-63nY5?Ab5WMPE;;!cFr;c#aNS_1)YToa7Z*iU^jU= zu711tY^zR?FU@Sx%yGDhMyCE0$D4A`kG7jA&~~i}fnyFu`7ms~1yxS;Gco~|u(kY= z1lN>2EAgoWtg(rw!yUzBG=obU)=&m-hLXrSdW?Jd(_O$L6W3Y?;d1TMMIZZ*$A%5U zrNXZr*mCR@xe(r{2xQLh6knktNnn_<`Bw%6&9q*HBv+2;H~hzpIs{AE7Py4aomLDR z`+0(qUTPOigR9!QEX>4WRSX&0iPiaiBQ7R#nHz>I?X3D@(lIx2GMPyu&Q1msnRnrv zr$62wz{~M6)^^cSlfPlKy(AVyNNeZ^>U&L-Hu?GAhKZOTSb|fhZ_O;RN85+@8R|et zQXjT=W?i5r-*eW7ys);Y3aT18^WpaU8!8M}Z8N6(shIb?&dLtiVq~FGgUrYZOP&iV@ms&(hYU$}D6kj)(XZNBu1*c>&$BRlha4Yw`Xa+NS`>Q9K)8|x~`mJb({iha;EdT!}L;3#@w~HEC+8EjW=M%Wr zcdk1!S3R|sdb2z^9)TZO2{oC;4FW$uX2eh>ri4qj^{~?!!}^4^nCs2ea~Q4ZU++W7 z5qp`1n2`L2@tGX%Oq z7{3_`#1FNC9GEO2gXm=_ZpxaM;oX8+0QrYHXO4SmmggeCblI)PLja(spb7)3Ksm#J z@xOCVH|+DX>^S&E_Ie%!s5Pm#K>DJMN;aCk+oy$QP~1xEq=`?m^i#+aJ7Kqvt$UQl zrN}yCSOSZnCGIhfq@|Vvx91tSS1#Z3EKVfj@aQCUMq;anH@tQBHw6Xt2MHO1^z=>l zmm^_OL|<0hEXi@77$Lk|xp#p>K^#FS4+%A>!lN)CKTdP z6V10+uqU6B8XCDZsgYTz-H}Zf>%kjQ_>)1j!*1QP;mY7x^9&>;z%tZ9TN4#?%T1uA z!A7)_$Aw*Bwl*U*Yqvw1J3PYR#ypT>qRijT%SIxt8+R0HSreO@*~e(RSs;h7L@%et zY;3s!SWh3`hq`F@mJvf3f&CdA!*~=|;kqHV2Y5+x!bO$G%e5~HdW7`>^A|YfZVohA zzppK7|MV*VzX9ieJd^(&oC3)-n>4Z-<_I9&Wlcg_Lfb?{0iulL<|U^ysX zC#;b^!@BvA1_!{reR#%POiIJxC07iewp#6{I-M^wK5p*Toqt%k$o4QMaUIm|?Xp|1 zOiQ6PYi>BnnWW&}m2m5;n!~-MfFY>(^M*Ueg2(HM5WU%cVF-9>QKVYZQL#(~Gqpoc zDQ}=b-&vIolshaSy;5HZA(J=n$B1ZE6>7=&N!QWB0HMDpi$ipx*ct!}Wi|A>Ag3sT z2;@exLieeLZ$B7T_-*oBLLuOQYw6_E*t07^Lq#5i;_5sF1>{ezO850YalGrwx6i}^ z4aO_RcY7w;VAB&wHcG4ik~I}p%5rTU2#n|Ma;oSd zW0isq``gFp!xr^&t0u6)2a2XBQVQ=ew@ zGq@008#6jR8u3fqt%V$`a3ip?ifc;KgV@`#TM~i^Pwn(0u7j=G!=y3toT&zTn|KHO z3t|szou;4PH@@=!!;b&Iihuq)hpvCP_5Hv2Pry>o-d@qw#z~xKlc|lj zhm8iAX}IVg%yNwMer8Bw69uA^V+zp_1g zs{t<^FY3L5=N;|V;XW>D1iJ1GTrM*Bw!>-I1cq(|w67Y|H8WZ#eKH_OZ#Q-}P`Wtb zq&nUAhoQ}wXt!<7U;TQ^0`bwt4ji-`J4&pEJ7=tI5$?J!g#5Ze?(%22U6a8$Pi$&m z14T-gy@Niigjxe!F&_ekpFC4-&p129E3mVvm8iV}MbcVNf zQ}H#+62kKI#O1ydG6zB%T<7vRYKFN&X_k%6V2otd@C z-%ucC{oN@s)-(7AC{!wpThGhFbM3d(B4reqnYkvYsq%@qnScbu)?BbWS=^nQ< z_r_X?u)yYJ4-$k8AnRX7ifP19fXEiuA4?x)q_Q=2e?2>Z>*7Qb+^W*_^*sQJ zupn2EwV3TT1!CqR6WRV0qF^TM-X>Ml(E94U67t%(aX917?V$7tDm$D<+m6sknrnm^4h*P*T^U(uRIq z=eK^l2>E4SNpzi$gDbaLUy#}b<+@ZjOd6CO@jeDe+`Ag)iu4h^vv*wibM@)ff8`J4 zDjJw}jOdL;3|(mFa{P5^h#P-ZM&4ApN)$%n9I7(J&->5b&{7dg^ScN*XB0-13d)tA zmhXY6xz8jRBfOjtiQ`b+$s2M89Avo_J&wBhJ2@pg(AZFpef3|a4BZhT+?|%rsUoJ* zov8RK3N)??47C$@@q^pyuu6^OB1(-F+^#y@FrT$p?+cV3;m^jmu1h$*Hh%;THiqDf z+L;F?M6|fZTS73rq+s>tOps5Y#_mz>?jxB1oF|5{QGei+b`=mVuFlL7JWbUqLZH=6 z{Z1V&tJ!ojumWa3OH#4KG<$I0PYZ2}3{P!~ER@_FA}*}TA8+Yv1FbS|wAjFyU{bw9 zJ!<{jP(395i+t{wK>7i`u^0DG>)Zc^eE#B{zd`RG$VW~~0#FW~E5AIw=}1Iv4eWl# zKeb`OqSO~2L|%^IzQwv(UCsIM67#v+2;hxHH+o8XC8k)awMEn8rDW zc*Aq2hXU6CFQx=&+zgB)exX6C>{l`k{?m=Xa3{A4;vUL_#1UR-?I5Mpb(XwIqD`5=qFma3w|)2npa~t{f1#xqi?iUH|i$1Xea6 zysLN+feggp^Mo*!JrzvS!9{KW_Sp1af9c}#otyqQrTG6-N@4n+Q_A0aiIeIOx{8af zpPwA@t?{G8SkUlJeMypt`hWyMLNQEnAP{w>J9Mc5vqVU75Ppi~q9_~7Yr2&n8m`ri zp0$lJNnx*R#np}Jn=Cr(%gax%ZRRQxwf7vyTd5P0B$kEMGLxOhS%*B^-C18e`^R6m zrTah14$1M9_XQ}%B0!ARM3Hn=C^sJy~NxFi>k zAH?h3Rfq{yEM6aq_C~)1?*Q%qY)X8?zDntu~jqO=l z#*Is<>5s`TY8J64mxE9$S`?ZumMx(aRovPtWs*N|SIGHUB!NDdD%vZT4=1wK38yU> zRFbQ_h+1d5Rj3$XNv@V`rF*2%s!$p(mkcgbsOeaZ8TZx@TlHREHny|(m;1-k_$sZ> zB3#a8?ppV*6z5imwN>+wQ`(j;=4hhs!zn-GpM4y=%!(TO)33F;tCg5* zs?t(NxiNY5MZ-|b;+;|)t zCzz-SI!T;yS?M?HpQO1pKbMz^w~8BO70$fu6wY9o>09pB=>aZ_@VR(f ztwm>i(zxJZQbV~SU5lp8C7S2phU5;+wh+gV5~RyP%v5POGY|nEA&Z5Q!6(&qBIxaT7>-fUU_74-XL8&rs4sJDh0T5eUJ>I3SZFk&GJ2qiSXo{%lv(Jo z7&A%mbcDk|Oi(n(5S~4_OV`Djm09toKhM7>xE)k+#Mjh2Wq!AJZ zu|iHH#Y7BJEBQu6Q6W1&B)}SPgg1VWqWBnnE!1LqiTw?wublJq@6YZ+>c+zc7((J_*rr74xz5s z?=`8|y3Ls_jVuJr{j5CHnsrK-rD#RKu6IAd+^nt2lBHTpEUL(L7E>2+B8gEBv%BwW zk&~tBjk9NNoX76J69)^{%<9QiE#9fK-y$Q%k(EX<3^v?Y9 zw`(j@oOIaB+_|{}U@Ew#WmIP|SX1m;>%;NDKzNW{a@g^Z{nGKp-9ovHYPtZY+`Exf zvw3UflNLrNwZj#i_xGE{mM7T2PjM)k1cuZ4G4kjlLp7&7s`Osv(`EGCrMliA7YH|;8ujniGgX5-=7A8H*iYcd=drutI~Ct z92x!&gJ#t4j7Fz9W%Db2Z4QkHL?ROHjZ8b^NBf(mR--~Dv@6T0{fpyq3{OdDIj2r@I-&3UqPH&g&1DR}FRz zo?D2U$8i-N)o*E9358~X%n6G)QfV%0pxF4l+6vX`WF9r_lF#c7tc@7Ek_2;&N4e~( ziJx|VN|etBGe}$xb4%dv0BsLBQ*j1*lDDrm>yj5`Ne=hnV;6Mpk0)`GHLJV8=rkVS zr}1cPBUZHekY^!6PPXX&RIY}0`fbpmJfou$ovnTg9!M2E)}BhNdYjTQ9H-4@gG&;J zn6x+*T|Vqi?aKcMMUsUtsD8`sIv4je&L;s&Ui*&l8-ijQ0a#`s!9KK!;d#g`OG{}b z*`83GVGG>NFEeu&7PRyBm*!TXRzeiDnq8XQ6?52jSB+C*!qO0vfjbS#avAGLsE|sK z^AN4-2XP|?fSW-m$BN&9-x8bDh;mWbW`u*q)7;zL^~my*!$sySEGSg_1cS!jQxX(I zP1h+l4UUz_8Emjy8$qqs)MQf|%6S-rcXcU~3Xp$zW0ZoE z*oKzIK`!?iwpr*&gxQ2{b9l78dF> zK_iAE8@uaX&qtfS{mxUejs3f@l^iGpEc4rZk#j>xkt$sg75^qx6+}J%SxxJBBW*1h zVul*Ai3sOI8O?Spt(2hgC33f&Eo}eBQ3GC*Ff=_l{+Fk{2>Qc=E^1fN`I>P`Cg9%om(uD3&IdW%j@%L z6VIkdOd&VdWAQQgM3=#}HD}m4TFXo!OU94*=|hhyUfkFyLF%`v9>xlOkh6GR%?KKY zJDhnRP1n}efq8!%*eqxG=ss->7mzG|Jm;}pX=N?79q=F=zDtCILt1t}4=>dF{Fv-N z<59XUI)J01@*qupk?^yONK)tk4d%dvR&y-~94}M-MCW}{8>~et;bKJXp*_O4%TD;PYdY@0Vv@5sEfS$?Y%j9*2SX3s2h3 z*xV1Bx9{?eRh)szKTsexAZe}pEtj~H_h6}dGffA$EqlBycb(CnY%V?1x>tcY@d{fo znq$2AomrJCo#S8yaIHjcEMt~5 zCaFg_qF3?JWJlLFC%3WsU~@+Ccc6K3`rf$29o>6P4Df083A*8LOWXideg1^Gs6p-F zcaaU;jrvh{3g9fS$Qj34m+c8kF1ZM3V+&)0cDW?HDvEz;A5n*%)f{WEr%mpH)lZJd z=G&)0B=Xjx!gZ;D>%fC+b6&7^^YeEO-u^3n7wv8!?&eo0(f-pOA1AQ;NB`6-Sm@y3 zQ%GI%scu!Bm*hz~X{DIw!<>k?b;(zu5drx@zRzzBc>__LocO(8*dJ6PUzz#Y`3+q; zPf#`kdv6Gw;+TPT*yVQf83@yEOydfS3I}b< zSLu9N_L(p_V1Q;H5QyB;iwl#AnKnEM3=m4gvbf@1tf9_yYL)>r2J%BJBzF2yo#rQu zosH}8ebOiVrrPHnl$-F#_4p}#GD3-x!GW|YKgv}eL%WEMUr(oYL^L2==aA*kekLvJ zNOp5DucX>%=N?FkHY8^BjK>Pm&G682KL&B@@3IE)QUX+W9`Q3e*UmugMNTsT)`@(X zig4u+xIIqS0^$+U&q@?fBk2bR7l6~bn(ssDI)aE*vu#rzbLz3mA^=Dtsj8CJDybZG z2`$g1m;6Iwd)fRHoSJ}(MJ%OCFYO;XZDu0A^mE?v!u!i!9(zij#y5PA6<_1tk)_yu z3?w}KjA;2@<_x^sO0o+447E;&QZ#xAoXUFl&}*Ryx8+Ze>f?cGk>Fe$?LljN?r#ye?T)4(=JEwL?nwU>+ndg!Ow1+;7gR?Wz;w?d7mxs znr@#(Hj_O8>^2(+uqiPE2L5X6=jE5PEixmc4L+0IM-O(dd?w;c22CWOUC|rrl-L1W zqIn2-3w?V)c>fVif>{4vZ~>*jv~GI!WX5?#M1)5GlW6k@n$>@}#)K$t1##M+Hea^OkH9su>zOHGY*~(cdq;VD z0PzL&<9fYCzRN$y0}bH?*J=F8mgks@f~UvemXNwA(a?=0`eNV3SHEV6^gS?xWYaD}d;xP@_iVBD!fz7@{~F&9j3MrFe;{J-<*U1d0$M1?29XB{|B4 zl>DrN%be0ZLS^0!^3weZ>|!S#?Z)OB`hB4EaD=7vu#E+D*N9sDMaIZ+#|!Nn`;^9w?9> zf{#&xUW1&kem-L1kWdL@nXG}LK`TAhp9l#=%*q44f^$uyTP(zPYtid={N5{2YNq6T zxu=u!_!NG7o?Zb$jgP6l8ja#hs07iH76aA(h0anui}{PUv;s-$Wj0~OKkZXZt!nmDqD<0FU(T)e5(A2*=OZ(H2psEFW0{DJltvEal&A2{vikTM9m7w43dg zfd+R?rG3gl(Cswp?nK*~TT*4vo8p7zQ7N zfZ>%eG2swU^r9Y(ngP>6aRV&n+Ar~Zm_LzDmh^e1^z5@%mc!QhP|oP|)Zv}T=EOs0 zMur9uQt}|Act(mrUpYISmen&yX@i?I%SUIq!+L}b#%hMedP$m^YdcFiZK;`Sb&P&>X)rwmZlSzX3i+J9(F%>EwisqK6@8bdzoL+F;pg6G7ryrReiBsE2+?5 zlGEHQW>1}0DT`WVFO-p_nu|YMa8Q=AUpP;*%pJp-r&ug%q{x{Ruf8sC^=(P5-RUTj znJlg>IcZcTR-HauIFTPVcGBfEs)y1xVClb{L^s;tH|az{8M0C}J?HDR_N)AY5v{v#`r2N7jqDw`d#9w7+OnWtUM*!Ek4a zikyFJiPS8u^&<}E(#y6N!pL^s%=Th}O-p_#UmJV<4Q2KV^EYeh1Q}`KDz+?|_gU8! zc-2LDG1hOh5?1cvi?sN|>DzWVJ-tAla&@*&E~^0q&E;_Nvw3e#YK~8@=NH(rEM1oEp59wlW^P?wnNzu zjjjII$|T%RtU2^RqqY7`Q*nL zZk8Lc!@@i1((~=hz_QcNkmih-&h6IQ)LnHt8eWzRWONlR=Bih4ez2HvP@FiW)<3D8 zpsgRdZ>bIbPj?NwD)fENfJ0b11A<;MzqBO=oc%?vCST>Fu}%n)+8#)oEEl+2r2ArQ zNgZ76_$_XxBTUYC$NNYae}PR6nRjWtR2{fu!QJRf4UexBY;Kgk|HB}KoQuQ1iTUF;|MF%CoF1y$Y$G8jC;ysBLe2ozE42a0L(bzZRL<2zM!~Pbc7}+G z`<~eY--0A66W6@{!HKNDWSgNYuV-jx%$fD_I8ovK`T7aeL&6Mg+M=fm%YrsiAzoi( zpfya5qZA}00?uPcFpY-a|Jv>}{c^J6DdKQ3iq@VQ6paBHH7%P-0sZ{O?M&Ey+Qtdp zDV^PYc2CCFxRylPVVcfYYaR@3KF+fTUD!nW{An|@+iMX#hB0Qf?f4ojy)*hmR4R5`UrwHtNN42{DojI`(PGpsXI6}?NZfMI5x4p zmEtZ_9olsvL;Isn(onS_**Y#$daKF&rAg=97`9Zsk(0#|`?3vc+jz5K0T znv?$XSqXJuGGa?a!4cpf70G^}X!5;&;qiJzIHl$+#|b=PKN;HOqn+YAXEZ+9t^36JYlWJ- zeZ8B$e?QtkRnE)*?^oy_R_m`7)U(t3UbnyR;znf+g?V1&&n7fLrbIsh zp2v0WaXQ{!*Z?WLci?SwD13q)L}Un(gwzeG;Fo(@0c?6UI(;1hV2G&b>~(Fq9Be_l zoV8Q%W9h_GGg>n+UGn9YHWpHwWb=~0dlwdN+sse(&t-x#tTv5LrqhEiRGB8}D?8Lm zzjKE%{W~ntH^!2c){hLEmDgodA}y6*+Wla3ZMqIj3CVy!dtQR4 zR9JzjF;)(vmtdA*EAeEet~naSR2!GXiwF%jo$2QweD4={_uJ$-LrcT-p4M5}he?E% z(ov;KN)5G`M4r(iX(;)=&N&`St|&l)!BEi1@J5zjdWeuSsWyZZ@s=TLFExNTXi?dn ze4il*U45+!UH^+DGq+l83FBy5!&bb!%xt4+xVZS#G-+)H7|D^eFlT+Q=4n|(jwZhG z+sX>TQvF>Z5^Ie{fI{2U*l}o(J&J%juVx)BaNOcXYBy5eM2au+ec311B%GsMu?Eul zZgM_{ z`S$+I4c>^YIujq<``B|Hre8m1Q%)j~N_^gg-vo%_O9g;e@%iJss1Aj@esb1{>|X=0 zN6B|lU9{3bOldotgc6Z%NI~nbAIi9eaCgrH*$zJZ3@tAJF&+LXY!lfM=xjj5_2!H8 zHjDHoSBG~;@E*w*^rryKhOkLgZsR`wHVPhCm@)Mg!YnQe$l;PnDoO;E%a$h0cEfHt zzeP$thel6sx-bQu)T0cI2*~SG?d~1U1(Xz7FHdyN&4Ad?i;-Ni?G~0M7Q5($WeFx`vhTkT}}olE$iHUGa>R^@-mZWXgO|290<)3^Kwe^EtN4pC`rI7I6 z0Y1t0Gu0r!bwCiFt-tJ_Hr%ILIZ}1Hz2AXlg_wNV4)&2D2}~gdWEF1|2O$Lp9B_gT zm6x9_PnvemPjJ6X?SIXrSM7soZ$oQV55aHOIAIf>d?y|}IXiT2Rxyu z+jz1+v`f|F;CX9Bk=Eugs^%o90@wfA3z_anrf>kITlsSLElyOA1yXsWAW+0mF8)0b zQ{AjG?9hD)e$_ZMiY(>Uf8kEztryy!##Ol=RwBN~ATL&uBD04Wx2LMir@w6SVR1s8 zmLT?fwxK^K`4=tdn`IkliwLc4M3xB%hF+Gnpk@}LLlGPFG*pOu0fwBU!Vy#@g1W*Q zexCZxk1%C+-UF;cf);*y0gxV!u&dHRFpAAQ>=4MJU0qR!*!P5rFiHt{_*lNU|1p|h+g#|J}KQ>VB6+OV5WyqC33(*fwKXX=o zcJ@`5p-2Sl=F>+cTi_d-Y849SI-^=+xN9fJLh7gJKCjO@I7lwD>aI9^1&VG6oixN~SW%GHc z<)V3sD)YPiVp0V<0~Wt73K|}G)-*KSS11c+Q<~B?q-*(tpZAikJcq_v?&eVEP@3N=$b&5!x3?r{?f zr$X>{5zHT{o@0PkT$G-19k%VbcDm!9Ykw7x5@c1ML)L%kYzM|ig|yKng4n_hh<{@~;fNE^YXgr)cXcc57zdoeVL|NqbwNs9 z?>XX{u$LqgLESvqRm3s4*Tnaid3FL!s=Dd(L*V4&Z?|f3V8q%pK%I%Dau65{QL)yYb9Pe#9Z2jFI(;U}EYK)B9V%}7&QkM#YR7rQ~eAxs_OoR6z zqd?>=$i}5gCFAG8pU2SNK76WC7d7{qSs=k%Vj?*E0;}3t%G316EF+!bN+-eX9Fg&C zu3pjU>rCK+e=2&yHV;-Ga(C&R(QOoO)0t|3d=as&fHz2$y>y&!XvyyKtCl0hiAyzs zX+%7LLLK!j-s-A(C;3m04jxdCzN*?hp5o+ciS-OYMv?;u1nWH^Gr$~G-GPFW!rBEP zIPmJfR_hVcQ?^uQpp2=fDG^o8@T)Nk%%fs6d)_Wu%DfgLSlMr?+F}s+{fy_6Ff+LF zfIPyFpkwcG(f}@j$UdN%TdCKJM&LBQur>2+?cz(HLp@yQM~@@+A}$j{H}frp4FS6} zBIy@Io=%L6ex~0hlai{P=_L(k}9bd<)U0@D93!6SvMq#v|5Eg9|ua=v{*8}P}XgdU6qJ?SED0ua*nxf$3Gr34CE&$ONrxp$!gtCVd{D|7LPZFW zfqqsAGH4?zn?a}!E$BfmigHUB+x~F?^)4M$Uh0Gs3IYQmqI6pXb}(pQeygb8uOhru zxisg1GK0T?GDF<7L`n1klDW9vmkY8&>BLH_YML?uvqZdLfhnr`)+i_%yF4XNrGOc* zbTsB-O5MUgbYl0_K~B5hxO@6GAkH?O+I}DihVj-wxQ7ohOV(!2BdkM*8(9KBg(04J zd2wY@g9IK0ZsM`wQ(CKi)IJV%pOeXlgG@Y@B%Jp!q6h7!yL>Xi@OCWR1}+LY@n8tz zNx9|{qqR!V6rxnMTpbCV#q6SeqP`!no*tX&{TC4B^k-9|rCE+!C3$owKh!{#VQ;Px z$VJF}Jf+R>RAj=8LfN>YyTLn#-Oa=gKS?W1#j(-T;M_7tFX~sKM@eP_^wBd~VX&mZ z&|?D;D$O^o%~2cJ+2y$p5RcBIR#U&1Q&%%`v2?hrk_U!#nQ zTmeJ09rk-clF-$PBG-|7y9@>`{QB@e@%n`fBM)WcoE5h{8W-A$+r@Da#j}KACxGR9 z##1~h8ev_Ry>3_AZO!PvMLlRWt1?|)`tJ|%qXTwojPE&l%f2zDzviSR7jW;0>-LFr zWUq*aya$V&h=^^q3Z0KXRW`0R1d>y!iIG+;1|~{8>P;q=Mgo&}Fe5EcEo{tj zTXBtOfEhb0O;cflo?z;Gxe2)|deIbq9}3|XRJ?`AHNTG17^z@JZuUd!jkbI?(Mou^nXef51wGt-buD%i;{b;uyXjzXGhHTP5JQl)a zApk-n1WTib)vQib4JCBnVSJ|6dlX`2aL>&cI1k}b@rZnjd&bJ)rIpy~PJL2jzOxWI zo9ZK|lHC+{^hzvPesNc6ob{V;-33yc4XRHhT;pLol?-j$sdp0e1UT6kJ{B<%G^ecv z=Zv1pkoOmj(KdEiYmJ%n)4FR5dhCy6`vKX6cE{f}EI`XoLo?u^6uif(sL5Y$!Qr+XI9L?lG5(xBhV-yxhX`Pa z0A+~?i4`A?RiCRf0I<;p!`cLBeUPcCZs|%-abv`!1?u9N$F~^^O`nyfkGmQ4Db*z0 z<&Qh^&ywXpSgr0y@LD8a@EQydBG*FpCQ*7sb*Q`aWJV~8pcv6SMG;x9aB;{yd!#82 zwIwKw1Wh#qU|!wrt}sKDPr^SSL)sqK=73q_ySP$U1JJ!PY($SY=P~T3!Z`u5Di+$w zczGwiz$Zsfxx>Ci+xz@>UT_kP8fHr|Nd`t%ZEk0|!`1Ql(P6vQd~T%hW2NWToSJP$ z?aq~A2fHWS|LhPvpZc%y5pgmch%%&Fy(n3rqqnl-7YZ#$>aM7yN< zy+T#0@M=prH(sHzPO*{P)Wy&s#G$-6%A}e~)c}QM%(j)FB7YFQ33$RTxjt!4olm)N zQ7++JbX7H8tzy#3ppBtTZeP^KKvhc2M}1kYLroapT(=$xW~)IfBg>!A${c5R-L7d@ z)}dzAlUiZU)mhUFC#kVcVI7d5yBcO(t9KNzFwjwqN=+TY*_NUW%>_rM!^fdxWZLC# z?vjFY4>{RimcFdJ2mHxF*3Y`#mu5B7guZE0C%B~_)HO!u8nknbE4cleE5dL5g+Z~y z25<+r@eEdGUv<|KnKnkIo)0*R<<>DgF?F&RU>h>98k1huQs z4gIY>YVxhN7tENn`OpLGmSG8DkUvxY?_G{Cx>%84e|KyAf>!cufd&BB`R-%%&jG>z z488x?Ez*CYckReFNI-gc;j3p2Jbc1uFv3+fJxdbb1U-Fols@vd@(`=s<0|#%A71gv z%JTFR(9V-7BNtvRfap00IjA`@W;O$|^{8l7it6P^Gnvz8aEBorlrjk`_M~Y{{qOx= z&Gb1bYlCW49##=Vj&FVoQKdYLp9Vc;v=;<(3}K1{j)hK5O(Nl08hZ!(K`(&4Vrw{U zemg=8WTuNONKS8TOg5Qc(gJ7h?+;^;K5~4YkR$>KB>3|TlmrAqu92sI4-)K^DN5Hj zBfKz2_wZ&Gambk?j@&xG{ z7ViMVmV~?|x20wzXI7;KpK{LN4pe;k^-)r(-l{#Lb<>X+ScT%>br4^K@p_rucrr!T zkBDP%mw4$qH^Lk^+^&cU-RC-oI`(i{Xe;?zC`>lfZg{+ydYNkf#}@sjNh)f`WYe}Z zRkf+%*N&TkEF-q6na%`pp@xH%{@bcY7?kZI%$6MlEF-!|n>Ss!vVYg?V@XJ+eks^t z6Z=>x^pcwaC;Ye5M_R+Lp(Rgw>#~4xN#v};omLwSjc+U1P2h4Rppy2J7w8k`6`J}0 z@!!vM;y;`MdvdF=I6*p4{p0osP=JOYfoPjzF|Hh6b`DuEU~l`eQ(`N1f$7>TgHzi# z_MX!X6Y|80go`dQ4QAri(~e`p!kG$ofh8y>_k2Uy1~FvD<~G7Np%GTbu}K5=pinGb z`lH{cGdgbIn2_~?AI#Xt5=Ndqf_6?ua z>@2I)0EO)gewC_7Y+DY9VjNc@1l|Q1u#X|4Y@{+n@3>y{(`lWf+EG1FZY&YRUsj%{ zf5f-SebSuJG>8Q`vJs34xQlkHtet!6F97E#!<>xf?$Mknhdv)nx>3DWnyY>;7*>Er zI5+JE0@F1YxYu3S>nEQq)QX;9CP2^PYs_eONFYoR+l$WOakP6)(3=MfNt{uwkWMS_ zENQpZs}c>pO*jD#A&qQKOp&10oPo`3;ww5;hmgc}aM*bx&Zla{g*3sI(#t+i5Yn5& z*T+v1Zm#^czZsYJcpG!KcL@brSX73SVM$)}8&^JyhNBPHXz#X7P3 z9|M6Z(snDHc=~$@JhS8Y+lC&)hX6Sv^gt`E_nEIou%gg~F!FV046rj44W~HhJ<`sy ziJU1g)kYb<+)IvsgmykC?Sr`NShV_l+5!Rb@|kbD0G-~>PoWz<;1&qdtJa)x0WV9{wuNQMp5tWK)+i3j*Ip$i9^@;@x$T+W~TrYL*z)>RPqT> zb8EkH$oco2UCSj32!y8a&|zcMQDgzTW~#jwg9;~s)s8F!@JT6kda9)HegbEQD=5hk zGI`9PtNvY4&myIBQiL+q_y#=;l})q2XA%oa3N28vf!MXoH2JP0D$ z$2n@0r?Rm<;T{8@H4ES>dHvE@y+kPR2Jz=*Y7U0w6DE25G&RDSchXE zdFY@*(!$PN#EZZB;Iu+*9_ta^*P*p`u1>=$mr;$<(F&!_9dZ>6W8eD|d!5oy7|w)@ zB@{xe%?4S14k10h0zR>(`-Z+@lHTwMLeW^732F6_FqxYr68lh~e0!o17VRLIFpclh z3m&#~RDd8mq4`8S%S5-(UC{zdj+17(F3O)Kk65 zF$5LANEOM~BUO;#Y%|9SHOR^ALUIJ_a@Nd2yfA2qs*Vido=8@$jH3y`%DurpC@8>Y zOUO`3gn7|c%ie9lyU#cWzvAVz&^YG7N*wcu{{XN#X8$7z(Lo00?)dtH!UI}RB03H^ zr!7Qc!%En8MZuQUtR$G}XKAzepnGp={tVVF^lLEHG1;?ntif%9;PVIQ-|#P%+qKNa zH~#el{a0DV{_ic@e`VD_7VQdES1sfvoG%`y6@61vG-Oom2(ZLs`xcQH;<5+wTtoy4 zc<>r&>?8z{By9%HGlV>PdBfRM7H7)~6boN^=`t0xD513FLTNYF4!E`D!Y9vE-rSE4 zRw(Lf^5+vT9?q-RjitAaCE5%QAU&4N#GP&yGV5-q&lc5QSPUntINPl7x0*Owr;+6d zuxG0%D7amM&UbGlynY=t+`%BNXFA*%h!=U8Gy2Y_=5VrWVzTRa|ETMmJcjmVvK>Q@ zb=sXhk9XiNkJ%8}ojH$3N7UBmXN*tw?OsNg#6PVh*zxkiB;@@L?+(EZ?wNPb<^E_! z$a~e{L~N9MP;8_-&Hn!30B*$TdetWx1c5Eg-xq@LQKNttEk&f(5QBf8{@||FflBeQ zpf9%U)5t*BC~mS4F=jHbNSknl4!0eeFIQzP^{K`*hf)e$Pz!&|fN|3lV%`fJjOKmz z@G{(M2Lawst`c>0_#xQvmAVYKqdDKNR~6ofICqZ8ox3o|xo9CmG-Qmfi3C%!L%J~i z!qH;Ry7r)k`5-7Sh$JxWWy+K*JkDo)HI7JbF}_y{ligBKmt=jd0sgpJ@c3m28;0E! z(${Nz{i^7#QgM(5=zRh`C)ywXre#bnmXjw!D6h!;U`AZsY$IXov_J=mQCNWsVWk!Z zPhDm*JZ&DE6RSGtcwCc=Yp$95+agBgC*rCAeAC zB!y*q#UUP)@QbI#*+_CUdd(qd6{?vZ9Io6cYqxsI-6r2eLlI@YbR;=8e8V1lBobZvZ@kW56W}T3MlIoeV<0;|4GS9GRqWs|!mdspw#y;cPw z-PIcDn(45GQEtBm1*Icjhyh#MpeV6R*LU{-KG9H#j(pBnwbw1le zP`sLcg=NnOgLbIor*1_A+kk`K3ltX*q)??;fes*Q`C0r3QxQ4>fUe;&lINA_VYtfV z5P}L>_TABZpFqCq4o$nH^hkl68a9Bbl{UBnse$4A4Sa*ldt8YWI zofxLOZS&7>rk?7rVFm6b-T1<&UHhMNHWk_m4fU;! z79$xj3k_oyrI@M<3b2J*CL@-Hw&M}`BspO|`?4@WeV+spk%fv9bEo;NH?~W%T_VCn zznp8u0`@)QSi7G+7cvBi87Xz69wgnU5 zkuE5?u5b~}uqehMewF(3a4hEbN@5Xux)0=TD5;Ht5q!L8dCwwll1Y~9WHt|gVN$Nn zx`u&TNI9p6Iqw^(kVf6nwfHbNzC9_odm2KVQ!1L-B--D*azrD4U%6j?^D)4WE>5(o zMZ&PyfA1x;7&3A~NG{%(>dwxy55kZOHbbt_e*bI9s0M3Jz)q00iCB_Z){O`g*3wA2 zfLX)z7^@`8VozN;Qz;)V@5|P1xU}H}(dpfGHLPfM@E9~SG;Quav20E(Bhib}+OQ2_ zgV;h`9trZ;_!1!6himg>hB}JXeV$e+cmqqn=ta6Y<&b|hWX>#)zO5Ooym_CEAyhJ| z&&j{hDp_=^#vg#X$c8z+83eby+haAwB4=kq4Imi9bf`a^alBg~^=QtF|EGjNLL4}T zxIQYS)DX&7pIhCDjj`kF7 zw(UB1oH{>+5?KlSus`WNSftca(z&ZgwHEx2`gTX*po*8_6-gByx!84v2=omcfELdRrv* zHA^S|a`FK}#C5C~h^e(}TH>h4Z*xc{sm$ z?1)1#XPI(cr%Cms7l$WliJS8nfzl;5)ZJr}r=retMZi+sEF)unbt|G589B?rd-Rru zwdA>8brVd#?__fviF`FdJZ;SNd0F_`1D53e7ayv10G2dz#6)ty8aaRlIk`32QPrSA zJDlaUmsQ@?hg=Pop5bB*it6P;7z z8dq)8HTgFwt@=>my`ZVkwm>YF_|yr$5_7{cWwd>#75ivLOceu{A`^yjDKEssz; zqd7Hs0Wf7v7qDnpMLB^_?V+_1*hLY7+0BYO-Pt(;zX9~Y)VPt!0aU-K`4#)>Md1TK zD1?5wPf4B&QrWUZC`jxd6${zP~ zdo>J^d+^rR-}MLZR36lr-^!bXZ`JKT-`4$`1*n|8jgz_2UuL|V4>n6YprD{cpfXON zPEMe(BA}P|^N3UJyRrT4`${692r$xF<^8onAL;$=j-Frn<@5W9mLi~)+wZfv`$|pY z*x8u$NAK&t@9p#LvEI4wwd3*ggxt}nBA|Qw?^++bvs@qL^SQh85+D2Txk?|qzEb!= zrTln;R78FNt)5ol(BaTh(D>cMy~Ew(-GB!4)qvuppe%nMet8+9O!9Yi-TVFg^WpRT zd-eBU!xz#2o1W73A7%J|Mgi9el*S3vS_CxaI|~2TxI{qVVE#)qj?(*g4J@Uz{`o+4 z@%o6rqcyobe&607JDd9*P;qF(U-jqZ%!27Yryf@673IUVeXt{P|A=h1omS6Ff$&I) zbqYJ5yD>de3khk`KAQ5Wk0S$vT8#eBd0-LHAC7A)=osi2$>@CEF#$0?DLx>>rXaV; z=*!;dy#}V+0CVYokDud%blL28bmIRdcKlXz1ANO=XdQlA(^?qYI~cout5)odY5$3Q zMbXg4*7!e&qmsMqx0U{v_F{$Ri4g;?FBdXDi`=N72aFvXs7#U%vKaWEXa>kc$DdB$ zQk@+#pBV5Nv*16kIpIf6f`de))1*gsQ)ii5El;1HcNb*d@qPc2G!LUQ^?U$rK4U>HL-YRvCUTHKL2uxwd5^ zOD5JkakMVT#EX&J`FS@rVpnrc%NW?)hko3+m^>#B&E0W7+z_YJ33_6<%$61bBg-%|Z2sLT z3HoOGuSmm&Yp6vbY7N=Jt@0e5I8lFHl23WS_33=s;Vy7E!*2CBhSJj{4bOGfL zhW-QmxGu9lx1bpJV1*$QHlaPH{1ry8PJhZvz@b>+NPXB@oJyf!}`P5EERES`+SDpyGB5hJ>iBvJYhOUYp|H zxolnnm>fgYgR}zM8D(*5BBtiuWWrxq?J!lvhC}#O)Gh4~TNgN5YqN_lC`0u7w=|lb zKoKcAWlEM*7@p9I8&9HHF7Mx(Iyb+2&*yZmf28g<(4V8G+-7p;=o%Uoj*DBvQkgIm$R&`FJN1w zD7WarpFnsNZf#%?9_tZJ-;EHniVx;+*9V$8lId)#u;QM}v9k8O&^nKMkPBguVaCyu znXe0cVtv?uAb{tOr9DRea&6RYKwV-jK#GDQPKWW~G$vcF%}urWHA)xTmWbp(8``MN zpt~wLv0Z449K=ipEAZipF{r?G*i55;{Wj`FL!rbC(2aiNnyYEX7vOs{(PRath>=g+YT1+>#T3V-{LE=51`1$CBO~SF^rXz_uV0}$ni)>s)q1;x zG%3yD{76T3J>9t>a5YARVI)4|XA*|nR7hjR=xX#g@a31=g1QH>A%0SMlPPi{99TfQ zY0sdRz6aOzOoG7mr&C~12r<4#>Wmv^Nu z#iAY)b}j5$$ZcB^I%acT3g)ue-;lwjjAa|FB^nTe;#nnCGW~mKPa%_e2cKi}Ujozn z062#2te@ZW7l_SA^$DeK+kBe{u3^8SEbMH~x@7P=o>+OrW!@f1to!l!!Bg~T#AT0* zOayRXvko8G5@N;S@zIZ%vHp~yws@0MvHUBbR7Ko?LnUg6+rH=QofSf5(~Ooay?3`` z{DJ^U%;|C}CIFB>085yI&dlh{X6Xt5Z^s;v3f%eKkZ$a+qHKo*GLP^_Hl(?ZWWh7) zLnmH=HMdaro!%)taa%(xro-vWPk3v*OXg3z)E|@_{yfr9$44QdMcp{ngAeo#xHs>b zIz5J&ombJ^7=y6$^!&H9%o9skVb^}p9Ne@zJg ztrV6B8p`%UVLL3%RqXZ%hq!WjSkPF852R0C|_OCUU#ETQXwM}Nta7F5F zw6GjdxUeV#Sz86+xBR8gCPZiq~}WV((RJH zt8U$4Dy``2?O7gR?j{khu12~q!^@?*0^s2}->MN!4}*s!t?fO1Q#)98j}eoHiYzNG zZfa}>jcI*P6rr}d;k`Plceko>KesExo_#cxWCbZ3;(U^O+M0Q ze=p7cw!-6lbjZ)Xc`b_DMXaySODoc5EG}w)kMqSvX|juowW~?5M9?VXR zgl@7agzk5r!>3@>sw6AshKP1=P4930 zwz@dzv&33fLkEz+fN(jqlCU@U22A-&o>zI#4xLkN^lr@sUng|jxG%{=Zt6O zRdxM)DfA~qhHDHpA)K{}PN2a>^i&q&308B$!|Tq=cEo*l%neS+d@9rfTLEsJ7#Dv9 zj6df?lRDJN>BKjIaLq12(Q5Os2jsH*9un$9+c<|$OG6tTC=^2ZkSMDoa4V63{Kifh zkk0ndron?yD#z06L#hQWF=P<1GCqaZAIBZn2Sth#?d7Jm!uUFBP;1nwO$?v-3FyPa z`Kxi(Hl`(KMMj7t)qs8gpBC|S8DH+(_{hW>oD8TX4Q;IEd>E2QWFxK7f4n$dg6biM zw`&r6!l>*sbNbrwP(^{XYM^%5*OsjU7-(ISgtmc|hA98nb(7<4_lt^!jr6YZSsb(` zHlvAHoVkkx-h$<{_@@MHNESW1YZ))W8d!XF%#F4WR z65Ih#tI03)+q-?6CZw9^6)W++_y$-{KwZ zV_B^tilt)toDx}d!j&RkEl0+|XToH-8}$-i}6EUL9IA3*rOIXd%KuP6!|C;rj!p ztH5b&lr@P55+WxPMG-%U>DW~{CLZ+M^B_bQ zhE^aRcG2shE9xg{ij&W6T!UO-N+@FiAeBKf-Qq?|?JYbu2IhxX-W|GZ^6Wi4QZ`JI z@n|E)R#P=mO?jFzm~Nqlf=8hTB}%Kn;zDTpbTL|@z$8mb+;$GMZHy!7IO>?3R_+Zm ziO^per<~9C5wqTXcxW)(wBU;?Fy%V1F-JoA7W+g9Wt#ICwkwZASJF0ShnF_%Yt3De zXc0@^jFo)`dxpm;_yKQzW9|5tH&80l)s_|4YMssZNa&u1hWDNL#w-{lA=oJ5d6la# zjmD}=dyJfIuM(tyNid#AzvfSiO`BNdW-`u*unf0n>S2zrY~LMXkWQu(qR< zZ5aZP)Nb#r0!DJH4YOO~MMAYK`kOR{vAg5Rx-2KD;sq982AEFhyTB1txt$U~uN6ho z$dBJHi!3{3qqB^{3qz?=#s=BpYyuU>Aoogz6qx-Mo-bjiD}TigypXQDFB$k58j(qC zyAp1s{;2bGP1Jv6yv6i@@4=a)TDRmD-IX+cyZ;Oh$;Afx$MTv28tPK{El8fch=d|_ z8{t@|98zLOhvc2X%!+sN(J8!}1@l@)E94?Mu&uaf15yc}f~`UmDKRgjnp6)_W!e_E ze7_WL6TwtIM}I*L#G?VqqYa9yn9nHZyvLTEMx|}#nbHh;TL*cEXVndCP>-zC?hl?; z4^w3}wJ}9Qutvs^GQgC2Jc0^)hLa%XBDS?;(o1R4ti(sh4If_7>vxNwN`R>H-*F!O zSis~2nJ24R~jSgf-Nee}3jLtw{0FpSQw`A1W_ z)2yN;aa$(&g3;y#br|*R_YYo4D}QaT-a#+Hb0o-4<$JmfKAcFP$hqpnXh(vcQKvFn zaRrTBn>C9Q@#VzCZ7(!!fUgedkou)CI7aD};y>bFaT>OZ&?y07R|}~2cwrZCW`jlL zD1i!YWI7Z*76^Jk??JxXrixV*ZL7~K+?@z%MxiBqcZg9K`y%J*M?!Lyj_c8uvZe1= zZl82~P%p6$exeR#W<%N{O^ALd4|8b>b9n>^CNsg$Yy=V(;;N`P1g}I~cDOY9Gfs;z z0b$^k@+>pETcwApbK$r0-F!Sxl`FA(v|JY|Lj7ju;_ncnpUtFv&)Yww_b&;z=Pk4!p03uILS>J3+ zkZ(6l@k=4LaoeTEjD!3-UbJXXUK!^9zMfcYF0LOyvqK6R4E`ODw`MwaGmT$t#1Z98Q*;Zs35=~9f z4#Fw=l`z>GQTBKwj{+VEwkwGFeNl=sPcrk=9W?M1zkBE0y5VW4p9yxs*`&2c34)pZ zfEXo^4ol7^F>2;P$u(}_h47Erbu&y3MXzLA0;AgprQ1)*>4v5nmxIPg+?t2c8xH(r ziYyY0$Efsn@7Ki-f|#cI8-ut=di_ZM2Bdl4n~Hx9nZ^JAz6U9D2S;P;znFW1jI?AQ zAH1jHT7Q3O$331)FdCYh5<)&cG9)UZn=Y&1O7vyo2JyD;-8S5{Xh>RU(&4_=CfL=) z_|nr5nKy_DJ&l1D0_h@G5{Z6h1=WhUyks3xl?_VS;f|A}n0*IhtvL=-ZaI0R$)nmj z+@7ppSoC4z?@(Pw9Mo~vNvBq$q`I?ugFj2G23kmoqn80T5yi`{UTC8nsGr$DD|qGl zbHQ`B6InHjo%(%|bXXd52@uWY1yARKyZ55KyI(~_fcpMkW>Rc@Y{%kqL_+)@wdhBp zQnna$!dv3>5wUoG3+px;dc*d2&;tJ@d;Xv4g8#~@ztZd8y$sNzS;7r`m|$!OgZ)*Hy*O@Xj_BxGLve4G&vl zdM-&K9oI8eN#1cse2z%Q9oJL8d0~7oeuOc7QC9IrAHu9W37Suu-P_#0$0S*tY!y5i zMtBiUeMKMgj+rrjB;9+J&ZOz2l4yB}@Fp#Ek>`CCvImpYI}`wA6$%laJ%l?I@VH>!eN^7nI)t!zCH=%JzFz zUf&W|#utwE#TiRC1X*%+ycz?Y8mXf%*r8O zaQCE36YBM10&*eYKt5qoj8Bf>s>c-ZXKRNrQ%D8t>N07v9dsk%Vt-?cy>ifCsZeQ3b{p^lm|cE7ekMSl{@<&F1Eaaz(= zYoi~)r2F>oRizA>a2P=18Hnizm`jk;r-XZW!urr;D5@c~?6#{8wGq$$y^)(7IUj+P zMWwW|fQJF_wk_;kF$G1DUMm}cXn%*R?-!CgY*jcC`EaHv!T=n|z}n82ib}qEYX-44-FBKN!~o#W4uf%(Rq>GNEdTg9BE?@nVIDw0$4+B;r&$*hoJD6%*3`(ZD1 zh@+SzUC-omh6cbKdz-j-xcY>S`f69eby9Dn^UybXzY-6~n;|$gG6tVLa+rgYiq~(S zS>}S%-~;29vyy!9fL^M|{a`G!IOBau2PIdkWDM}R)SPg0FBn`g*Gd&-J7T05 zp^v!A^mf%B-(9#-b4uW2r2e9|+#W)%AgXs43Mk`` z?av6xJ5_pf{W`t@yWy!O)IJJVtO|+VvMYwnEvK?N0OP@ef~m|;O!hjPKJ7D$cl!?{_?_$Y^IRl<;V;0**j z9m7BBvo1DspByI~2<8f%0f_HGA z6J+|XX!kSTP@A(#pwayCY3J1nCf9FZx*Sif;!qjW`M5ohlmE(K0WcKc4;v5Sg{syQ z`sa_7#b=I;C402q_Q^Bg_lEU~JVI{}mg@%&ZrLK>ZQ33Tm%PC=$IG1wsoxzU+)>VF zfjg+rAeAtDQ3hFBJ4wfoM4nVhK@G_h(Ixoj*@9`k+SyzNi==o=V(FN2VFpwx)JQm% z1K$J1bP$PiYT?&V(E228OPJEv=!&K2;N+^vCKQH`clf2BZ25VZ~uqH}{ zAA5;cbsmUQc(sYj2gSJ|ENdOF?5ztu3>~%&@&s>-y&pDhfV9uAEIWD147 z@HD;2-!<3GU|3`|xW;37#Jb&SYK8xU->_ZF3!j`|@#E-_Xr86&;RG}J zq+6MyWr!?!$1!(v|P2S;mIPYOVGZc&)ga#2~cI!9jNE_4oCPz!!Ej z^Dp*Km2L8aS510wI_9Ma@!IOQd6Z#*MPm}PlO>dp?~BbaLi_mpTL5cLsMKswu`y-3 zl$DLYiQ{sBPF;{&yDPgm5|>LXOp`l^0RivO<&OXGF!_y|5g{bVRm5u?D@$xhO0nkZ3awp7BC18?K{gIK@8E#YzcQl>vWL}Z zUb8?;&~S|bD)+-gNDmbsL$ssL+2lMJAow(+2?-p7j2F}g^$L}em8(7YtH+!|q#2y5 zmWP(NXg{X=uq)>OAQ{$?jF#O~_ci^sjgA6_85v78?Gi5mC@ngIbKMddn;27;xNg=L zU6>`oUxQs=$~PkFCow5ZN}@Tb0Y!Kc(l1=vBsfCB9kX0O{HRIP0T5fE+;*-Es*+L* zXTT!xCbiOn@y>;g2M+x0k=aLoTi=>r#w^h!1RAAC5tqs>vq`v0Q+GmNF@w#?Z_IhE zvFq?65gD_2%EOQ3gq?S*GEwxhp2t)(QWT*e3>svDFX2eFN?si~(|{ZlVVxJy{8+CT z$}$IDzX6A_1+-$Lx@^Tv(olm@y)NX|_7e-%+XR_wH2W>Ow=l5L`%~t)C8hF55*@WI zlC7*>MqxCgry<`?LZOLws<>g2bRPf=Qs3Hj-@|xsHJWUV#W}FF?`#4$Kq=%w$+wP6 zCN2ebhWbR=usDB4)jr)K^q7A(E#Doco73ZSVX(%T33+qoo{g;)Igt^C#yPhm%w%Sx z`_Xe=0*v5{JGl$Z&1-tl22~ogm1sI2(CrGWbr51QN~e!$A}%xC6GJCsOdhDSw}K|Rq`Ul7 zU^SxE5RjUvUA2C~e{5J>!^KCowsgm4oP2cma24426seh)85c-L{Ahkd9xIx8puoCk zQb#p2QvhWXZ(u{>4Yb{*GacWlj#8r_)}A>g85Z(-$n`QVg~e)*~^QRV{{4h zn2t}~ER(<6bfST@R0y4l*WbI<^y8%lL`)SE{>P5Cp*P(I;Ld88A05t6=rKX?y@R#L|Kd4DyTC)e*@Q(w zT>;$R41q6DPTrf*Ity0`V*3JC*(N*lf(cZM?Fh0`iyCI#Q5q{E)K+<jP9!VOHE=FI;Lo~WTy(*KX@O&GzjW9N zlrGG^7Jc-rY{cN38@OUlb~;GO%u-BDQq6pD`jZ~$(wJtAEwRh?f zoH%C2zPOyfzvI|`$9;H(=H=9$3s+>EjnV!$o zU3H!>NlJs)G^qw}Q;2kpHb3w+KJb-AZt(ELU8uxetAzSU$#7_xk|ns#;5#x^lI0Zz zx`PH;V2!i)F00*uZN5iq_BX8RSFzIqe#Mn%qp7vGYPLUX{PS1XGYr>PH|1B57)!cA zB#|uxE$aAYdUkIvRHA5)mLCHU!b250G~WJA7j|8p5V8*Lk% zDY-n9^zg3dmM}YBlj7a!3T@CYunrOV6nN9c%pACCSe_r`)Z9V~!1L-Zk;!-pWvlyQ zQ?wikj!|+ZT0Se%4Qd6(XL&X>4K?}=2s)z5y_JDEg@U_ankleOlM?-wN5`4ORE~JH zhxPGxMQMSWnc}Tt@4Nw*vK-=Ip@GUXpZyk}NUNP9)llPU~= znaOFgtD-Ps04eI#M6CTG(?B7rNo)B@n)z)6eAQfOJhc4MQ!)t)P0C^QP zpiQ?SOgiOmy7?Dh%7wNR)p<|(ITslTu2+3Zd%APw{A?W>cE@$N5Jj0*hUhZSRQ)+# z*=VnmSu!bsbvTsFaH$MHTOGPkpPeChn^&PT@6(Etuf?C0#J^Pl92&#rQd+G7tX6OQ zs%woWz+Z?@*hxg^K#U>s8O54q=fj=4warcTU6PDcnOn1qmkJi=77{b?li83#GBz&HBW=(EK^V8$!8~>iJUUBHl|lhxDC^kJ&M&2 zbD!QB9?&>>sI%%|tg2t~yH0E?GnWaK`(LGGIwXsjlNQhjOS*`d@i(XE2&Hn0hSInm zQD3u56q749EsZlUV3ilSB~E1(T7G2!er3R(6eNIf72^CTU}-h^d0fEIJ-5@sXa#Co zIyx-&#C+FMnj+o9+=Dv0E~qkfk|Y$UIS*a2fQQ!uW&4H+uw+D>mgfv5Rg%Y;d}q@B za4$#3YS^HjcxC!sd~9XE7|@&XjR;9aK}&$>n%shdP6mz%1mc%?M2XZEhxgfLqM-cs+-(8~kjk8h}iY+4GrA-k!(h%|kb3T8LjAy*n zNK}6w{o?cguVG2C|DAFE6Y-%amJKlqteGRt)0oQ_4?rtdp^F4`Tg0Sr43NSRZ1M=@L(wT59xX znQA=}jue`fZb&5W-P0r0VJvMcUOj5bbT0#!GK7t~Av3D<8J!~diE=_^yLf?6+m5Kr zx&$D?IQM@7x_h3IxPzZ|$@)v=QStv5%Zb~VJDKZSnS1;L+AWf)`*bvviHas=GG0M` zE<0xwfPX%IYtD#Zb>ri&YSg<0?AI^65?0`SV?!_mfw8}y+j+lC&dSCWA0Zl?S_!K~ zFN;W^T9i0E_|=SunxalOBkrqXcSxwyTaZKyjjXuSWBWfzWk|d_^Y9>meJ|_UnM9LH zeGRWkH?DyGZpb@zFO1Cc;qwF_fmzc~Hgi>2Gn_7#fCz-3CMR`5Xo1=YCL*@|1E8&3 zc_1$SGy?8lnu+g!YNmgpmt=@Y2ksth0|?$BpNbYO;t z-_9EZp?n+KV~+Xo;LZM`f=)pmM8L02qO{2zxcZuU+;O0_hLsa9CDo*^1+mvq(G};F z4z2HDx3}jb9l>$+noxc+HQ=G7TNVU|mF>g{oU=`X;mypuoA*7cWOC-v`~Uc}kT%3WIDM&H!f=$~`nKi$5l*gtPy(AHp% znN??CZWOdZQ4sHYrc1UdOS+F#r+u!UFl^Z2Yb#RT!xE0{eW$XI6DB2Uo6UGZ9 zkbhFSP^vF*->F%V^qX;&@ObmD3c2BW@`yox9rUSoqzw43n&m)zh9+ff(lke@W)tLP zb(alEYC|5MmY8L&c*Zq%0Bt-%^EVQaFcvKR^(P@xXDYaoL}g*MxAXVwdq|KO=@IVm z_COjB*u2a6fWI;Q(vVdHnm&yo_?H3d-!hl@=N@f+o6m^t^q1!P+%^-m_4LgL6Duay z%&=EMl3xu0g^QnsQ!JS|&I+Y7JqY|t&H$~dan=AgMt^g}@FM0Ewikxm?~?KIPc zbTzoj)ij|lM+Kx`q9cGxR^{MKvRmw&YvRT%-yOWrUgJF<@g*k;;F1J*MC?mMz1UisICEt9{~ka7NKDv z-dH;MKdgn)h}4uiqB`jbf8fEX`z3M~(G02)u{-|MX##+?tX9(eS$;)i2)@RPIm`7N9cjT48Te3^ z6=ZN+lDAPDRz+I@Fn4!P?9S-Crt@=#XJ>j51s}gE!$O4D&i_@-Tz*DGmXQ*T1G1S$kYXf8+Il1V(OMi z4ZD=u4-+c_nbeHX@Qcpqh6#LlqGeReXUD<(nq)nO<|M|YcVy5lw<+KQei}jVAZeGa#UU` z!FmA{_&|VrdD;}~);kB=&bGYmy@V`#>@0hZJ#fywj=ifTj${qay`yhg*St3o_h+BF z6oohFJjCB0&e*?uAG$wY`uM!T_R@PI2z{lo8yuI9iG%9&Z!M&8hy;@lr*FeJbYl*6 zkQ&_!EF`7e9!Nw<-A#qtMbe=hfuTv+xyRq^&A_%5KR}Ic*)MmTbU?>;#8$;f9gxG( zolUu`Em|A9S~>Hl>*Nm(XODZ(G8N8g0ul6Cw?i8^j>OSHu+gXtgdIWMaT+H!pxj&c~Vp&wRSg6w+>_7DOjG-EQA$ZCai(& znnmI$(~3CqFj&vp>a{}8d4Ak%dXk;`J&?p_Lytd@FgaTWzFC^_iD(*o$i!#8amdWX zUZpi*)l7K=N3f;X`A6b!>;=&-1et zv1gqpHYbe4Vj!`RU+D3`x*{rK|hyO_xuSu#dCG_heaNqoEWG@TF! zjy8+XIHPi`{y}(!3QMS@5{H7Gf-B^HJJ&snGXpcdAl;6UmLN<< zedP%Y*>!2Bsvu*jt%vJE3ubm-0}3;209(-x9VW$nhlPIFZh9~&*NC0xw<6kmYS@Tc zW;QW%HrVol;;Q)G>u9iV=5VrtV3IH)#a74a35QJkH$Q)cH-;09IKxil3bEQ|vBF;Z zb5t6Fmn~dnD`9M`WgC_LRBPmfw%>#r`3+OjqOu-pQF`@T?s~?ju8iF*8_Pwa2t1Iik z&THp{TQO>Lrct~F0J}t4dbyp@)IeMo;NF{bSO-ObGoLbeEG`F?K!xO!8jzeUlt zuqEVM`ZbqX@7GF1Z;(*Owl`e0?}vC-8v?bru1_jErmphyo9;Of>%zHRZ>EW_L0<53 z91G6yT$FOj`z$pbxD>4W2TX?$vj~ELQ4H| z3Q`JU3Uc;9&_{X&IXp8@iD*V5RNoJ;3Sp4EIO_8V1EkX6#4f#w`%5|j%9Asw4%C#f z1CqB8rVisDv1CZr0>h`Wi=!l1MG|-__Q7Ttx4&g+oI+=`uS zVUu-2+0sYfG6d!&Px%SyM#=7iMjJ=@7S18MSerjh8G_6nVhmM|P+CypZ4_x3>Mb9& zj;qq=fq_#FR)V1Cosq+8I!6MlFXnCvdfP zXsw5eu|R^(a0NR{Ei(1X35k7{H)|X|ik5RF<+In;9li2$I;P#8G(cb$WVMf%-K|we zJrY7B7}Sg@#OsImQLcY&2|wN?Y0)aZ??~{nIMb18;0{cSZ4k`~xKRg>?41xrr7upx zG$uH`WTWRhLd;=Uh_=p`rg6Y?PU1FsOi#mU7T;A{{Kln)mZLZRYyN4 zD0^uAm~>sj#lD;kBjW@`n6HJ2_wZQ^+s1sGRLCvLw^W%)fA;{>PGA$wrl{W}j0VYC zU58w8MXy<2mPPoEFtaa@U0-v4pr=Ho`g% zAZ?_8@}T*kZzyqbpPSast)>g zcK^VZs+2Wtv4m0Hhd14_K9fXdAOd-nUo)G`Pm-?Lh>Yg4HI#sb;P%2oE!NXMtM*m( zbDQ#CyjS3#96~6hPZ(!Q>$G((^E574QA| zK=X^}Ra`KCrh@#*(3fc=y1+4*>D|oC>=a8yOq$%v-TZJ#Or}f;LydK!$zs=t~Z~q=de`CJyrZ}d7`AhbL&=9Hy%~~Hnmo~lWH^2+Fm% zbJBhh=!LHBTtWrZgwui4R`TsiX<*LE;F29orAY^DNJxLzPDb{KSFmMFk``9l$6i9K zqSed|`naqgTX3M0v!^Hs16@R(4_1uPH<%8KJoAlo`mWFlu|V562xYB^VUl@b@!GzD zaIC;$OT?^zpg9{2dBY_JV(5Ai4QZ<1XgSTskijjAu4*r{b>KB+rSwufak0)^0f;!F z_K{boZg4-0Z#$z~=tbL(TK!znAFTVU+2Cwb;f(QaBq`Az1vFa&9UVof@5)NQC0m!! zFf*X2lF{Y|-X;^%*BIim8sE|k_;up^(ndTm@3Uz|Qh7JjrE<=8gTI_;4SB<#W2I43?YrV`Z zw!~)%|LuG5kt9A@4Z_=+!_BT-PN6Nl&X)llUsyhVc=gO(Of~ygPGX(_7H_;T+T2=B zvFIAbm<0)YDXk;J?SRKZgo5%7w1x;!LzL0=06Jb`5$DSw>dI2D)VfJTzcO(% z(fDtKP%1?a?nq#f5O0)>UbzKITUZ7m(mLO-cPu#4nN(VG<5aa&*w>i^VOsaWq8m4gq!KNCb;%I?G-`uuZ`_ zuV38fPmMnq7NR}ciwGnNde;#fWLU;FJyP<#m!`xuF>I>=4A*OCThC)M`{Xr{^NhVhB0a#4l!5-|VJa#L5a5qkI-K8r=V z!7>=hgaTFY*^3fM36IdBzX{6!eNqQA_lNWTZ zh&ZR9Le-|Q3e7`B`aJf@snXfS7fTuW9hH@^i}8_Kmvh0vr%xA zJ%%)9Y5m-kT*sFK#tC~=FBLIg*>jD!$0E5&AAwj0`z%`k;FVQsK-Lmi3hHG zd>Mf^5w1v$rx7`1(Pch|oALavU&zJJ_lBnlVn$?I)bz$IS^}C4AI#!R&8*%Q*$G8( zo9Ymw*fqFZ2nK;Ax8>wbM=L#LQ?ND!U}(gXQ6~VEm6b-Lv@~I(OS!lZv&K933rC?? zhuSmlBf908DJ?MEwyGK$qd4kxVf? z9K9EukY^Ay*!s5G6%lX85OJ+mTZmEP0GM4{C-e#A8>lB>m(g>27X6Z1j_tk}?HaWE z7f;(@t!F-$reLD1m&As=I=xkr?s^+?gmts{T1&`cZh<;84P)iXMu8iZIx9-d=6T&Q zNoBa`AgbpOp6){5uVZNS<`TtgFei(6;+S7LSVn4`as1zzD2>qD#m8IFUtRK1B1W60t}S|N z-+Nj_&db7jcFQy)y3H`k_tE92&SC~O=K&VPWtej>3=a=z%C5z53ZpF z_m7@77bUcCcE=w@e|PlMMzS^gWG@+tDG%U|ra4uLHsTA!7%?LQGvf@A{62D+&rGSb ze;0EO!|pJ)2&*wp&vO?i;kSN$GFXx0Dp~Gd6^2N1+}QJ6gH*Lq=3=-NKQ%WTtflxF zug>z@^y9Kdgz=GTZqC~ECMcWYRcx*u9YE-)cPDX^BosGnL$QVOK6a0gz`L|8Dz2E| zD21kMKm{u%+=7g1lT#_RgP9_!RZ^gZEy|0giibAPLC!}n@nzO;_&X8ij|ENOa{Sp& zOsu1}IC5{|d2qX=)4)ZJ^zQ zQBCt-qYP1*TiO~dk%jX&IkA&@$T&yB)AI#lB9zuP5x%Y=I~)3+8utRgxP@)##Y%|A zi=`oTJ|`=1=Y2_q-8LyaViOkDM~Il)gVksTLR<8 zFTW3CZq^E3+G&ZRxENsU=_+p>3TGW@$G%58o#Q`V2!cK&jLEXS*y5Mr8s#Qy#R+*- z4-J$$T9w1EDaq!5%`^HfyX3|VX9-OS+-Qda<05YBI)X!9Z~#d+icB4pN}sXmJ7!cT zegxkES<&`{IoT`_23x9mLQ0Cn&dGBGlAsoP9&AG-yF}tw7^1JHu?vn;WA)cha;3xQ zKhvqK<0rlX{X&rNzJ*Y6L73-;8Bk*uqs*Q|g|9*hq*uL7V*aWoO=+2q^UWT7Q-p!Y zJO~H%*QEZsw7U8=skp}!J7en|d8TrDW6f$9rJ`_Xc z0r~vd&?DR9YA{b$gkbJs&(Ola4;y{uot+({$hA57S7hrLWp0@QT5&wLKWRWe_-oPkw|hv@6baii({;gr^!$-N zqvfZ#7d}_3w4(ggtMcDMx&K~w|H~E%KU6;-jId9mMimqdmoH@*7P!`l1jr}Q&Bc!s zAZ2bk-iX$f{%2DOQL8L8BK8&Uo#>ZKj}}nm+%@8p*|Qn41~^SJ2Q49W+AMTP1si}U zPUr=Hxg*t>o@9~1QpU6&|8?CP7DUsrG5#CCe)fHSk`*r3jhEHjgz2>ZSmAs%I$OVA zDqZQ9D78T|drlBjvT!y9YbUS(28FjT#Kt2hL_$v;UMGj2oV~I$ZG{^MJag|ohvy*z zrYznR_#aktn1n#?`+S=y|0lfrzj8S6|Kjyx^0$py{}y|L`@iu$euK|N_MhKYaQR*r-$-OHAAAK41;nZ zO-Yk6@d&=irn>muU9*oD3&^RJh>Qc=iC1{u;T;9MgNGyatTCpIQK1Zm9Ctsi-)7xr zInHpm96tK^w8L#9bYB&KCIE_Guk`A%!R=&*8u3WV#v;SX-rS&MCq|y3w0aqD@>KQE z2v8lpPeP%zgw%wEg7OS>hWx`z!&9LYh1X*|%z1Z4Xr8-&i3WIT8rQz9d=>Ri0H75K zbC3_jha00(LUqOkR_UG2c6OJ7CqyH{Mnjfp@;|2{vN)5OLxOU*Rd5UDxlx&sO>KA9 zTGb&nB8%AZ9Xa-MkmM6qa_&~5>N2riUKCN-O3D6eM_)!xR6Q_2F$*ZBsO&DaS@SO> zbwD&nliz>=*Url3vu&Z&griyIpMVQDCSyxy!XYAEj$%1{v3pK@Kri08i&wK;D+Jcn z^^_6w4yF)^uT$5LI&F}bC=cEx;N~dP49+YGf)X^}5&}kXi!=dc#xlTg3M}HZZ#!@# z1S8_kwxvU93ADV=$IU;GxEicHSHnIG5=GV=#^!G9eQgEVu=Y~n_ZmggavjT&431S(#~*T0GtV}Hwq6AL`<4w%ki7i-=1nrxt}m~ zQyY8sGcb@opfScN0!!td<0ilGrkK}FDf1W(+gkaz30wpNM&xm3S+TGM4&z6u41n=cVlhq)YvlXj` zZ7G|gIaV9sUmwXduJGY&A+jRGybz|`F#1*;*p2^9eH`=WTwd;Zbxc}#3R7pKDpP0p z54SH&ozbjJonb9XomD$HofUej{KuK3x~9fj@RsO9x#5pYoiXs?=5P`0f_SbiJ#~6@ zvhcz}y6aArZjI<}LR9xdd(dEgoKStos8iB`6q(zDNBG|jS!hevJ8dT}E>aCKOw{_~ zIGLmR1v$|rV|RBrfo^3Cp`7vIi;!a*BVobmiduLloag{86dwIhWQP4tGPEH={q(z} z4dgQliUKWnsd}7NP{+j>uIVPi32Bic#!H^1J4Bw9xt5Oi6+6hD<=aOeR;5-9u2&-$ z>MP^Qp2L><$Qz17hn1Hd7a>p@5t*QxY7ru0S28YJZ^6gqFEkFhxwS-2GmWSnZQ<$q z(BzEsb9Hz{oVI80 z`!mFl94kXz5IyN1*`pOoN;SQK+y>&MBp3jC8E^3=*iCfzZxM2}{NZ(~Uy;>&#FpK!aQnXRt%PHw7t*b2OjeS4IxvA?)upp5lk0zSspA+syVv6+o{x@p82ZY&gf+Ig6B|M=iwnjTXeO-9UsCYzP72+I`^8>dFH0Sp9 zVjFKGR1Di|gmb!81)ZU$@ld)eb%SW-$CmvjQ#e19GcuH@Ey%slbsOFx9_y_9(YNk54({X^5a${vqKuO-#kCOaXxy#&`(QU2>E}o)9CY_N!ge)p36q# z?#WzR`bh=1isD8Zsf{%;Vvkv+3@ExIF9D{DE8ZaAMHr2GcGgB*Qz}=P*9-Joi9Usl zaZW+G=f!uYaK|M$YP7oVOX7>P_VZI_LyfcRP_xSqwc3HkvBXUyi?)Mu^hXu0#AMSD z+UE(3!d$KKtNXqqd#j9QV+|U&#dH)vp7?{F6z!uqB`5Fcdw373M)P#~Dt6!ejf6<; zecS~0nf9sR{wnSB{WlWM-<91Jj2&&QT#Oz5(XOQ@rvH1+QJk_trbl?sq*8C+m`515 zeWp-=6X#bG#zvsfA_OQy;qxbTG&n1YYI4?R&2xSAH}(4}u-p6fN-p&J0#`zbM%|p7 zack0(5qGWUta6Q_dY}h2S0|wToQtLa}FgF-o;cpGewV5EtdHPOt zql3VXv;$=Gtjyam_FjC}aIW^QXsL1|kbeEKBbtWHosyDe`jD+h)PTxoIC?nkeBJV{*_25HbOA|r~IP9ZD#lz4MZ-5|?W!R`x^rE@*uiL=b}*>O0r zM!)A=YtQjYy(kT;XMg6RZzC_8OG9(>Bda3fN!r zw(>IwL}sWFR0*p^-c+RQ(*9BJFlxru_I=hm?SIKN`d_&V!#~_*!{QHJ43mbjX8pCn z=J(|sm6li`d$XEFG+X~50j1e^N&SYbVytFHeG}x26okQV^0g2;PhWTv-Ar6*e-;rM z=^tjYA98MG=A>=;c)tVJ22=^Rsr> z(M&6BDl?Eg%b+Jo1#Obpc_3xztqqWVMSuND5p+SCN8eA^c_e(2m=X4iC@(7YTFks} zitS)TKM}x9$#`*iptq)QIm95N%iw7?^(mr&^wSHhr6L+cu;82IwTS}(NI6B}MfcYM zQor6jST5^eBo_-Klycs^kVkn`6vQ}p1Ss8TxBRHK0%0bH)a}LBR*$8rzFP88adR;FWt$2CA9U zAi}@Gplp0e+w27_>uoJNualdxiJw-ubPqghK)pKY$T*ZJ1 z{PvY`^Q^Kn3VlhqP9y}Qh=CFyip}inIRpKstTMB%N~^+)Y>QNcgDfIY(toO8krNl1 z)30bWT6kGnF1=LOyx6|I__xa48F zw#o;*(X5Ac5U-#6;XI19rsO=fnLm3&=C}+5&4{sW2ZO`6*1_l8rx=VC=%5nHki4=5sa?*zlkexD6NMv`e4pJ7Cj5oeo{dm(`lVovFekx#M& z{DT#YNc-I^=3*-kQ{K6T7oA?or&06PR?**NaOG4NYfjoZW;I`gXh*xejz&aBoI}OD z-J}{(?RY{({FoHz_fIh~)KS|-r>3@&3PI+diP3cO#mpl95^f0wX0NzpKQUUrfcyf} zR*h=XC&5$>Q%E5Rj>qJXuqm-<1|!;N>{HM^ZcPu}#FA0o(Y!hI!lHBF3$xr zTMT+^x9Ur%9%N)11uw&gFfe&d2gTOzPFTzKbwXrFu3bryxlwaTE_2I93tQk+#Q0dqd_N!?l_ ztoLq(l)D)1fi;#I1xi}y6w1*gvbw3<>O3=-|IVM-qX4aGy_PH~i^{+}@0DC;iu5OE zwtQ0AiK;f-Ip(~}Myj={v>8~kg^aNCsy1m+?MXe|yqzL@@>OLZ0pyH4K{Sb#zu#E{ zO~nfN)b>|nViQC9f+LSXB`NCs-(SNhMp_Nfe?13jk`7?0IPapu-u;;Bmf|@TAwoKV zET2nK@$$)r>H&e`RIOMpqcn-(=Yf0Eg=5t>^-mDhg5}3Q#8n;8;<)S{9B#l%F!m^slDTmH?7V603g%?1(8XaAqhu zekUg0sf@JMR~vR#0~lgVi$66-e!I%=%@83vXc>NWF_p^u4Niife>|&Oj{0Hz3f7Z!&6(Zh#{wW?_|%UWeIinP59C$UNv>Pb zHF{0jf)Y&vhY?(zI375)9e={o`DQjgk8U{X7dvl#Go_O0?$`3lxdejo(aU?BJf_5A z^a^b|ch0SYkZ?08PqnuJAqSpTrF#s9mEi`Asv7SO>|TZnAYm9SNW4CNEay8?DkvSSKAau0d zD!7cibgOsIUuO&$?81Od;gU=S`p&H`2zwk!WS>)RFnn$Sw(8+1-7_s%cR;;Cb|LTd z(}au%Kc=imzsLxr9xY0H#}KC|>nyBLCb%;1)A-hu+Pzzg?;#YiJ2YOd-v*>Yp) zS-VcI6|nx=3+>LgncC@hnt_ zvn|ss9=ai?fS2^S-GrO)h%qGQFsGTn8ubDA~-4m zAKbNItb@!-a|u7r$l?}QMIIiA)xQfQUo^FzX$mDBwRPWMpM~rocqI+g{#fmL;P>Ds z$Q(%L^M+;=|hdwKSDp2zN2awH~CfE-BI$!?!MLhuf&v%?d^WmtcC?rvd%qu^#F%A$3{@#~SzUSxNW~ z=C2zbY$)31lmxP$Y&j*%pDlm_ zHDI(sAN_eM>F{~dc>j$&RNVjox{5fdHw;z7ZHlmvT33s=aJ(*jPA1Rgi{T7sk|iF6 zpJsxf<0r`0+o0~Bc1m2c7_%Bew{*%D#vYJrIdGt3CY9w1A=w#CfYN-s#=LLOLHF|H)EfP3Cr4@Gs`R+Gx_MoL3lm4y&U~xX^Z( z(Dq!U;DiXGTnP0O0_Y}}d>DK}AWn-KflZ9%TKen*;yl`(D$*;?;18p>wIxR&Mq&_J zy}H0z;b>Xq{$^O-%=yc+5g~=8Z)7l;H8t}I2}VI?06d{IKF9sl z{fEr`XhPArH}V5jG1fv>9^nK|ryK~pjO-Wm&O#UG=m+1x+Zc(V55v|N2 zgO^h42yfHEfK z`vKnO;h_^l^+-k_0{HKHLqsw8MkUHzH=&O#{OyyB z1K#SV1!SL>(9?|6d)`0lf6f%VE#A*PU!vdss#N={F`)lPsrFwgIDR92JE#9k)tFJl z{-kPnxVnIwkRgQT`uY+NQgx*I!R7-SAPnV7(h(38We4jI_8F(7rwwh71(1D!$R>ZQ zQ7aNQkQi5l%kHW`tx}i>;2j4{nC<@HhVGXLrv@}NE!60``dohB`p$j3pEc9`SOrD z^Ub*bsc!u@s5_=Z$!P7=7BAUpN*QX;0ca47ida&#^QGB)(8*(ixqC8b7lhVz)(lq( zN)Pp4HG2D9_)6k(x&FO# zCnQK48G|n;E`+A#579W}%WOL*hn+?FILTgwFsh7YpaSvC$?LWCgcB%vC}+FzMNU}f zYx`;1YA!fwp>{^X59*=piCrbZ)3a7vm-}*d5!i!FF&<2j@dEMl5KM0|k-c$kvo4#N zb*hx(Bwns{XR~Z^7Zn7RI~|lo%*`r(sF>wN6crVvd!T-lA#CcJ(ot;+tFG%5vZO!i ze0Z~y`AEUy80RA4p4mh-NQB+H)8DPvdZx|e#G$oDwc2qOGPL7uF<1rB_aZt`q(SpTY06 z2ukD1fL4MX?yiNZs0s456w7y<^xi`;Em4MG+FYM-4h;NiU?w{d8~y>;z*le8*yt>L z95?hVn0etfrM|+FjAf#5Gk9tpe+d@Wqd9gs6CVBzsgGqhPc_Rq4qhQypNM2E@kWx- zQl8*ABh4GqrxiCWc;YLfJ|6)*YiM3dheL=>L2eIwB=HNg;R3dy zH%Oz{Vl?{dtlcn}I)(M5q9Sn)&SRK-ywB}S$K-FEV#l*VN3V*)O=`8|ai9icMjb|k znwNq-V1|OLKf&n=5ofH8%GE#mpL!IEw~l^iF}&reiPk$SvyF~o8ki7mCeY?eY;+UK z#P7|*7V3;yS@%zxx}5%KtJ;!IU1dqW6@yVtbZM!_f|9Q{Q$_cwbF^XH+@hM`oqS|* z01h8x`_Yl=T9`gYuTT}oNwk!%zHGe|{Ku!N0lywyz2|?(Y2!I5X?M2QbnSSCQuTk1ZOMEN3jJlk zQk1s+guJ{qJ+0J%t-n^~7BTTjD#AJM`1vwG4iW2u2-Bvi#DlV>bkvCC0y?Z$|YWS1f(dcShgBh<-uznGD{~hWF=@=}N5V#lm}iu^ z_rb2~OIA!6e%+t1@%jy$n4yM!hH-c94|ayXBM);qWAm)p?)MVo)n1{`4HJN1W=5l2ENZgh*X?g8TYY`?Ph|nN$c$2oMO9}wg$mGEWxs~V z6U#W|c-O|4We}U{8abo)s$-~wRX@5!VW1aq^weCd5kJgYSuzcZ%`tGNG&qBc8Wd;i zI3!-w6>+CU!n9w+cwmRH-ztE)RBh{N;)l#j-1(0;Av9^#>xu`dX?)1{6ggA%R2(skgp6@hc8S#-u+31AisRqNxe1xjAa3 zy=kejQN>O?v@CfUu-sIXQB}3^sB&(TRP^C>%aBHl3_)=>@z~Y&xZbeQu;F+!IVXw- zUMn?ep9h?ikTcUEANd`1{V*7|3+V>5jqq25%~@Yc{gv^0PEgG@(DN!~-6MtDY*ffq z?dOQsjCA}v)i1psf1u~zW@sk6-k zP*;WiCF`j%I&JLa7(7+mnl@B}4K`dMhZ}A{wBs`^sGg$r97bVJ)mX+)*%20=y7QN= zM0mWhIe6m%DZdUy9ex{<=_L2?+F#!)hP{OQ=d|sN%%Hvs;P(KKqL0@MVLh0TewJe; zS!<|Dv4Sk7+o`dZ3urKvi-MZ`7!l07w#y0zMNC!cP+u#5-mTLcRV{H&f^NkOIM=5} zV|DVgCX%H@T8l-qdBk`rj>QdqPH*qHyKypG^IFU2Av%E;g4s|lX#s80U(90!9AXt2 zJxjbpu6)(V4ZvJ?xWNP=+~HfNyQ>{r)2Lp~$0-qO>U_CxYNA%QJk2PlwX`rVwb9b- z$ZO}%tz$trz7yWl-!j<pT zY?%d-#17A*A7tS$qVjVjH8jwmSP{_h8AtcICuUI)HLPI-gw3LDS71k#V?P8%x^)}eMLFw$=c<|oe=0d= zb4yD*} zBa1UbV@Uh8>Ns%xqBi$}x7p}f5iu6>uT{c2>Cr7J<|A2nFxTQGzxV3F)Je2}xXJdF zELsbcBb`g1bjAE=5wjHoi;yXl6;@2Z=sy1_2@ZXu>j(%Z^$Gt}XBtl__S`?Ndzyva z(C~%VYqT4SCjn;Odn6+>vkxC;>cv<~fpOF&ZL4`i%SzsyR!4+T-^l8qM`8tj8RCB~ zoED$4s#{bV8fq093My5q!(^uVHt?d!lLX$QS_hQ`d@G|TjmC)T(~GCRq4WyrV-lu# z{jIZnm$|cgx8@0pQWF5r0?)8k0QFwIi}zIG?^E`~x+!-})2TV+b46--2$4|k1|7m4 z7kTOAS4Om;i*Jf9XO6DX5?IdWUjT@vk5QOOfY0Vr=b%j!v%I?ixX7oke${YM2$T~J z9xdz<y*woCe8I`&rsgFy+X|GkW0>pI4{!MWM0(N1!UQjBu-&{f>$-8tTB^97a(% zg#lpVm3Y4hUa+i>Nm)uX0bnwFNvO%;(=@tsE>sP;5#l-5rs8K*rkBijFXW`<-iIU* zo z&9l(yVOnpV<9Ty&FELL1csBo@shvGoN5)>?G2xUNQ}b~wP9t?bwS!Em1n;hMa8Xey9 zg;kswP43Q16&3)^^zS*uJPg&wZ>Mt#bN7;YpFBRtQJa`=hrg9z=CZ=T^pp7 zwPx#xzCrjB&nC*Habr9tUvi}zXv?Z=Y)bzRZSNRlX|!eyCMs>)wr%UAQEA(@ZQHhO z+s;bcHYzJ?^4{({Gu<|*9rf#DGBO;}}$t^~C6NYeo>!J|rzs3!@iNM^kTHrk`meW&kurg&$8jm;3*#%>?l;SEbic5e?7#@PI~0M<{GpyNf5ZDTeO z>SRAl!@d1d$Bri&+ozwZ@Sh1y;SA9`+}jT`BwBuzk(L*2}dUVGwH!dAWCIk<|8vedG4@0Z*BbO@Us2I{=ax+ud z+tCEd!2{Jpv5_*hk9=j?7nL0I8Q-h-HC>lGJY?qSx{yDcY}=e=yFFt8sB>KE?_9qp z?EttqfF;Kb>~HNbTRXInb`0jvHsK*Y^AR7&J>X`=ZCQ8ZYj0G=rBb9YbnZ z2Xwm}_LlL|=M@^ladz`N^{(do41UIf|KnRC-vBA-^pKjC~m8 zFn$A~e7G1VX?M1j4Cf*Jad!`ZbJ%gIis!70j&hK`3I58MtUZJV%;M2>woHuq} z1M@Ek1My}Z@tvI`o@>T{wBqg=*DaztzyXg#nwYkJ?tH@2mG0xVT#3uUjd7NO`3b)V z+~6+lhvFCFaIYe280Bw#<)Ask5)GUyN+Ykvz|x1uSCB9CdV1|^`+d)De;o7p5b~d& z82{knTzaIAetdfX2MGS%>#flL;B7<7*3!ht`5*0f|IP96FYQK6vW^|H5NhZy@MP+> zCDg`;j!l$wL1S_{rihGixNPR0MBw?KM!-4Ybo_eAFTK56riDy*gYFjztT|ehBqe(D z?U~uxT&~9}ui5Mjy}rJ`KnmhqA*Ad!>f;;gV@5FY){VR^@lEw61^w}`abodwOe)yD z7hMAiojSFkz6T%DP8^t!uj7v*CGuOK>&L!lEuNn_Y9tFMo_@SaMANnL#MR6yPCxgo z@t8?3Drc}#afBZ@hJ2$gUq@4jmK1u|c%GUWcTtQ6$IXC(oCwL*o^vsNWYc=3r6p_^ z_@gIRw`{`lDBoj-hM(yAhYN>C0E;;Edce|jh_B&z*v=*4z1mE~LI+=UpX@hSYW;G(gB63I`URi5$4zMvV&H{O1aF(kl|qvYX7cVW zP8Z+uMbJ2T_pmrwj_geqCrVKnjM3z_XU(TrGUB9onbFBGH`hhsa_4}J6))bNvvsd@$CT}Z23LiqrX5_ z3%`kufsA#=z?qk);^`vpTf%v7D3txasTK&xCvYVSx^Gus&cAi_{qJG(KZPRyQ*0t~ zi=8 zN@sk)>fghaoN(zm1p}s-+Br5co=Tn%Ol)z@GBBei7(!gwfI(D*W?C?#oNbFWAX_{0 z6q&pBXgut-$p}ZfHku$6I5v`G6O-`V7gQ!%L{Sp?K~yHq=$)y{(nM)|OEY0|CdVmT z8#Z_<^up`z);nBDx@=n+@bccvsMX5ebSGAn*7}w5=%1Lc??v=Cr14s*1=yO*CWiuO99dN6~#SFL({5uu|><^F22$0KO<ly-~}fqqSAKS?$qjkSgV*coPH8N(+nQT*4Wihr&#Zgu~{GJeCe^xwks z-0DI<%vk5?%imj)UA;Tg zr4q~rly;HHZe_-I9LdO|6)@awUb_y}G4`!}kDM!|D?_iIVHwvsf!Io4OX^kkn_K57 zI8WtM7cNq*aU+qond;Rtm&OMJjp#P*aYEB?Ad&nZiql4^2B&^)Q3cMIZe5lZ zq0`xHpT~Jl1`)Z+vG|(cWLiH85^F$V=b*gqEVlj3_yi$UoFeWJ3kX=vEbyUb`vc$E zyY&lV0Cx>BSMp^%0HcgXpxrZL(Tl9&#t@8Or38@N_fI__xQ+bjBC8Rn@z3!CR%ORJ zPG4{wg4$!dlz)uiW0s^iPkqt{WupK5GcKq^D}U_QHwmcxZ%IIb|D9|3KR|S?3FEFZ zg7#(B^xfX2Jsw0_Pe}N{IAJRp4oxaV7$IH`r4UF+y)|&IaJ~W4Z zl9!mEx6B|7=J3v^!m#aXr)XahA&81uA!mZ01r_O7O$hu1W0GRONM8_29_ z6Wdu-iv(;yO^<}x2mxrXzE#H}%T&_xx8_`gNpBh!Y|5&}bml6P6fef~PPnLKA$ru5 z?L)-oc;Q9=9h(H1Z8hb$2vQL*Hv81i_s<3eV;zr57`Xdy1_AE~&}7kTl#0b_)O4=r zFrftOUzP0}dZOB%n6`S2S=#s^86pMv|kVTHV)0;|(uG4RDcib33_ zDJo7dj#XDd>g0St-8e_k;8p>kw4FU;Du+xdiJeWrLd;)Tu1KUSCj9i`tkb@^IqOU< z0L_HIiu?G^P&_e-1ZTIPswWfG-;6qZY+bsTWXcTYD4vCduI~M0lhROdQW3-wLHZ=7 zDJA^cA=NGstqS}e7_A!W|8~;K~64Ue$SeNg=Be2S{kQ&5c9SH zmr3!6SXlgnY$EYom*jzDP}2kvO=!n%k#Y^9R6-AzA0*9%m63Dt_-^Lg2&PsHvp~p( z51oxW*!3$s6vXPV7KWDUMxGn&`D>AKTa^8~mQzoiA*J|A`-r6jIXx+rJtB?l8C`3} zL!8iBl>AwVkge@4R>L*SAc zye>uGuZ5Qs({#Jj(nRw6#TC}q%0oTr0`5tP<0{$TePEGbi zIOb=*JU&pA^emD&uP33D%i*+nWyypq9S{4}$#R-s`F<(zka~`1HikQQOjVGAHUac# zbd(WW*fWuo{=PKVpZGKGcxKr_S*-H>nI{BmspJF3%LIA-L4!Z3S_!CY9ke=1RkhD- z!p75;HMRWJg&!9xd`)5Fr9^KsUKUqO(d(-Sap69_(4g$==z9{!&A@B(zD$NOs38PI z$hcr(5YY~bb)GHL(KTw+Kg6G&Ds$g9PBY>#qdNQeBO)kD93qd>-~Ak6k+|D4MXr(h z7W60Mw%dKv7?exaxi1{BIm-Cxrys?Bem`E^C;GLaedljHUj_Sl++QJWY~3Y?-v*FF zzAF-frR>%-cRl#yC#Zl}bWHA-sfNJZwF{Ae;YRJTJ_o@uzq^1~LSc}o*q2T~r>DZ$ zv-t{9T;U!b>=6i?2#0wl(9x5brSRDxIdhz(RtY^Wu`)*H_yw5h9K9xn-2TBqpPlU) zan_^J0g!85CnkC5C9%XAH-8L`OF|UFcQ(@hbl?9n-~Ui5qebWP8jB2L8HD%Fxb z{OpQY0Oj&MSEw**M8X!@Sv&gLxnc6rYFda^zG1!8@ar8~086YAK9ja#v*1en5UKql zB4B@m>Ul3IFZih)Cfif=yLElP#|i^us>e~2a4KG7+uEM8j??3DxHePwK$?8&s-Yvz zeUYfPbpt~u-+P8K_0q{pfe#fmtyqhl`;Covi7;Ekn)VrN)_XE{o@xS4AJo?o`ianLkpBLlJk zwoGj@$WJ)qA)hnHtWHbJr-n2@;_$`8(ZSONywh^BmAF-r;yZ(Nb2$;RWl~1=i=+rk zQCVAv5#SgOlxs%-n`Rlfu44{Qoep!-HoijH57iM7MPVqfpZ4qF_#*mM6Nr7;fOAuO z{N9t>%akODjS`*7jJ9IJ00SCOY;LrZ4bk)(A=0`#Iwps}6Tq zTx~iteA%nOG7ccA3sJ|GUrMn#{tM>AAh9R;ATSM*>zo`0N!C8ubV5;wFQdm-Bhov7t>bMcMGQB@gYOZG#24<&@9P#BLA|ncZGY9uyC0UfozJtgBXaD&%qbpqmPW zHn*bt%og^c%HUSmSd-lqij{QzAJU zxyMpJ*|r4Zk3B3Q?KdI4JBHmwOAn-3nI0Q+(PY7xLL)W9m#bJ|I9cs?UDa#K)FLyNsxS@4!&FW zV0*gLtvlhZyYiJfb!f-oNWSAtTxW&P#ymNI-gjYLQn~SE^a8i-qm!42vI6`t3~_Js zjI(i=lpE%y_+Y-fhMUSgzzog9Mm5$9=<=>aIYXn~#L2h1>5t}wXljQ?a8r;r%*D)& zh=ti_+hggnQqu1mWPT+&XaHRUS!46N#80xpr8QQ3<<9j5E+l$*w{IQf*WRs}n#0z~0m{VhqYCn{L7xK`0O%^ zf?B~$4V+{2ca1fsN;f&rFbZIo0H#;5N&%5=tu>|59SI${;q^ac)ovmv^dH(nBg4!d z=@L&k$+=^k>Rx$KLaS ze`wIKZAgimaSSW-wkCJ5D~^-%)vzU&y^f^4FHU=~qJ5uJ=BIaIDIq228h=9&H*XIa z>9G_DJA{1$(OVB^Ag^2`={6g!^2YrpTA(CH(=@_=nY^K38gJZQa=tl^`lMdIQ>%C} zqQ@xvEbkWtGs!}m+Ykw3XR z)0Ef_r6J?joLVmy4l2d9SwqNO3{C`UY0vBcdnK;JmwZrN8VzI6HbK)hLDxv4k`pf& z=@*#j8zcsNI!C8(_Vnvlj&HjJMsyAatW+)^wmxQE%&MH^3?`n%cSqL27}EssR8d8o z(IrK&Z^LWIy*476+FWzKHJ{1!nnip$l@C4w$}u?qMrp5h7jN;zHK{Sx48BpdP@A_s!lgdK0gg6Ps^9>}JStb%>Ar5%?bxV<8B)R4^*{*^S#sKRa(h>nZs5eg}Qv7Pp8^L^$b&7=Yoddr;j4A{vgs{HQAdv`e9Zn>Yk zgk-zMroAQs2Q7kFdEs@VI~C}Zs{d`OF>#RdlgT|hg(?~aE2RCA({A&<87$LY`kfed zKgoM@{3!&0<#nie(ZYf$%qS{TyPCE(;8jq{%9QLqvmm=gcz4L;ZX39-4=eBgkY+#7 zW{+xc&eYpiHvQL;8K{^Y{Li7slcRXvb0+ZHRjj#9{1RlNn?CLQtMWl%<(MS$MKFb- zfon`XeULU5lMwzF3$HH%mY3=Z{?zZE`VJz8y5F{NKtKTMf5*7~yQJlR(FU^LG5X)} z!oqgO9^(ID0RI=&TB54;b4d{KtFmo^}GgVO5 zGLiZZ)fb4K_dVF$g442@U-Ha!vLsj=F?-p?iP_eax7POmnM*=+ZK(@_qI#tmgcmLRG>lG|Vn@aNrX3388 zr1Q>uft79zrBttN=5z~MyUrpt`1Y}~{ZcLSaepDB0(Sw40jFCWvSZgM`_Fxp)$|ja|C?izSckrj9AvIVOiH{t-M#oNBd$ z;wy^AU}TGmsuT-c1h;84r?R8DlfIg;`B!!_#@!fC=p!C4w?1zQrg9g1S^Pw!j*I(z z{5jvv-txI8PJj*zhA2zj#o)P|S?4jo%;$6C<{d4Q-P8raM-v5ku|UgVw$Y0Au4^S3 zQgsI!+$-$Wn12A*v{k#^9xyaGzTr-PAgWk0<)&UU0he!)u3L|wO6o;m`+6-)n?*#Y zN_lIPZM>~UaTalU{96?45D$euVN4QusKauqn7!^S8!W}zca~$ z9M4mBe{vd_Z=t&Os}GRERq%iJnXsO-dY)BUxcRdtsbO>eVtE0+;-g~1C0#Fqm%${p z2nNR-L>gZZ#X#kpv431Osdn0pD&#KZySyp4op<&?(z;-(JPB zzfH@vbXr|%@!@)us?u6m<>7`<5o!v}`B30>Wt$DO7H(PFbOao|p$z*mQ2GZU_Zwq` z{9*UgtqY+CA+lN1(-}^tzFsHO8A}9xy}qCd{YtQ>3CcSBv6wd_h$8@8@b=hs&WclK zROhWVra8oIv}fm)#e09E=jj*Fef7p8_RK+)fJ-J!t5}jAC7rk4C#>8wy0(EosY7nX zr&FM|9l1xoODDG>VmR<`olicBN%Gql1D0ATH^=DbQvzXdD*$g~Mb51`!7~xf$pVIj`SYfbUv#N< z1~eevrPlp;CFp4vEZg?dke6OO7yOJ?(ImBG1*_>4yMMfw9;vRMKAG$`T)XG8zx;MN z8QUCaSV!)x@29p6h?CVAZ~0ZK<0Lw-v;g<@y}5PG`8yL>GJt26@fmK{NjH~R)heCV zuv5b_2{GA*Hdfiyd%e7BQh%-HLW}7hE&&eEW@U^n4nReR;}u`%=Oo9}g`JfD{$nEF zmiNPyQ9dli;HbKtxf=E;g&FlLZ-}uP8t{AiN&l2t$E}Y(bGt&JRNi%t!vs++v;gpn zgW3x=slX@i_I>NIV++Au-uSy6`2n|R5i!g(^8y+e`<%a`5kXOAA6Y{}QZpFcGI4zi zNuAyA2icm^4FLj7I#t@oc9pf{d8OK*HD1O zVxmCj_DA@e&_h&aR&mA{TKafaxgSBMQ00B?FAUO;RpEdCS`EG;sb`IZTb3juVP1rmtM%dFy%Ud~zUgq-q0c0b)g9 z{CANURSm0ubE}Hzx~<$$sxb-&j_HNin4;M-jEnPU_JT|pj%_v)CHJBG3e4YtzhDBn zO;RMR3keys&8LTdeDhxUZco|-{64_?5hD$p4YbG389^{_1v$u<9m%InlTaLz#j!$Q~nOJ@C?g1e4A4^;qvPDuXRel#N20mLwvbr8hjr zo~!Cm)jP5Hxx2En2p7y|>)Zpz5;dA=>ubo>p09)>e>}wT1F&o#9)S!d?~Qdkd$+&M z$qhY70eJW1DZ|}r!z$Mdd79hMG?olPf(0m*016U;D9rB+p5IprQ}SI{1Z&-!E1 zBaP>ox)W%OrMMEEy(Ur_EK6z?>6I!u%Z<)7j$|e&+8LF}*UO0(iUwpUDJhW|X=TzK zDG}HfeofoagI5KTYLhq%0K1r|80525`NDlr1Va~J2MtZ#(u~A(WXR-L%JNM2NS0wT ziB<(iuAM;VVb^e{!#t(iX&E~b!hjug+mz(Hw>*?H3q>Qx?}q9hFVQkpg3;OCdLSci zP(j`L8?Ldj<) z*+$LuG4NUDgYdwW+(-MZ+6xa|K<)M}LO+A-aTpuQhm-=jK#^s@AKJ<5eVmr3SNvmW zz*8M`B!u>!QdwyT9BDXz%-R|Ra3QdxrOx2|JS@zC)1r79MxmP|@Gu7`#z2FB)^K;N zR9Up)+0D|bl-$PaBzg`v|Jbp{cb-YIi^?0~H}wcep_E>{ZZn=u<32yB5<7SQF%@lB zF(z7-R<^6qpQCp$94$|#IU5Oim|d~hsmfs4F?oUe*YB`awI>w6r($7F-Wr3G|Lr)Y zvTe+XL0}U2>L>@H(IJj#+=t^OzA>$g_ZIl3XQB&nA`eNuJ!;R!d?Kg~=_4ST@S|P4 zz`-Z%a`RWH+xgx%(Pd&Lixqb$)i1D;EilqF&@;L@r?Y1PtDwpCC)!I=Ll(a~s5Rk; zaLq`wZZ)lIx_# z(+&|U#zjqlmIP-hSeUIa%DI9)T!J-ibF4eXbta}s<%*< z2d(No9)dQcgF#yC_L_t6I4zFI=6BjAyneF6-@K1_!(zB&P@dEJuoh}8MYscaL?S%a4Xz^`bQZTS^{LiJIl9oJ*5GLOe zOo>Sg8d!JG0t9WSl=~gnU=XS3jHi_Ooku6$F0cTz(*DxzZ^c`a`;3`)HXTKYdi4r`o_q-@#NA;h1@ zTRUb>GU$@9`snVQpAo#ZQT@6Z6gDRyRBXq{m+GP%^fq%Z%BrWwJPH?mOl02#XjVTV zhA?5$+IOjqzzQ)mO0VTAvtcasMy2>uYsZfywaO@#e%$nrzf#|KCqA}shl*6$l=dU+V2^jHOsE}9fw1`r7cA49U3i>8Y;1)v3-{-t3ZD{;=L zw5HsBlHk72?p)EgJ-lVBAKb5TDx^94F(jI^hZU`?7iiCqZjvYLoYc0mjLr0&^XqF@m4ub5uk|cNSZiC8#Hnc zxsx78RLV`fD`z4Plp=&ff|PV%#?sekMlmDTucvNp;g8b11TC&&gfA1ZzMc?4m@mW*`6KU_)=4boji)z^*l3XUbF;x%;W z#%3ofuqNkHq&J*ndG95pxbNdYHRW4#i&Ocnxvj7(x30FPu!;}s4w}#yTCB0hTCuW;|KPcW zWM}2v4E(yI#y~qd40IhHzgE*3V^GZ|JYfc@dut1kl{%aYj22E(6J&*K(eqPm@*sPm zJT-K-@Hu=t)XFe0yT$|#Hhn6VJvBwjK_(7x05LeFoO(7#rnQaV-ofHKfn8)Ju1QMG zHR{ein4qs^)W|XY^*k}^F#1X%Wv6ul^F~aQo?0CCK-j*{>4m4Prdq#3qkjk1UB$DZ z*H7Dd44;UW~b**A;#FwwJ~fwq2wgIM}!^GByyL^~j3q|v^13)2*6%)pAoc23TH zSG&BP6Dr4{&8jD%MEVI55%>-v7^4(+7q;}X{k%$FlBL?FYwHU>g2#&$HKP;(cmk4& zHd_ZTJeYgGQ&iEc+;;v=6-N2wwHIzMJ!q2ptz4@567nTg@#qw$&@H=YGca;~dNS(>F7~bD|2Z*U8 zQ7;n{hY@@FN7UIf1$k)`OnPwlmjjTIEH8(DL#oKc=+n*mpj0;3?8&MNwJZ9MW1B~D zl?b(CTer5i&t6ESlaTX*GdGCVlp2fm{pWQ3t1;fepRc0t@1#MeC60pYtLQm_u3oTt z7&)OdnVEnilfsnrPoqPCS1Oxoab)v>v!H%$v_R1MC~%gRmX*c5{}GjHKHb&=2!a)? zxR=1}R#Ov6IK3M5@5k|^c>8H#?k!Yp9H@7Y&3KZe0bZWjd+_g&>w`-h3%*8xE}NuA2TKb5L-S)xF83 z9j|H5p>s?KBixw=t?U+kIjcl(LH>~%!1vo3QdKf`9=pg+jjyC?soEwg#~%ETKqR+e z4A^8<&6xn@@px%{t?}e2#nxD9q!CxwfmH5);C0669d!wsJ@>g%CL#gtD zsN>#G=c4)G*r;Uoms9TFx{Al;Jti^ymGP#U@Vb#(HUh$QGrMOSlY%;Oj8pjIs0BCo&B+BRCMbGiJ=6rW6y@w?8*DU58}Say!O+Ipf;I zJ^`=UMU2vFmh(SRk|3J7luxnd+#S&K_~M;8k6M9I;oHUzZQ?NPbRUd3n6%Ba%kF7Q zhfbMQ;ns(P&dl{r?z0$(@T`LHh~nBtDKcYaLVjr*6f39Ku2!UUbK8{IaQlec-WFA5 zNQb>>`jqf=;~7*hp^vQKt1drL~)E($>yFK9N z)Sb*R(qUr1U3drN?5tA$c*h0`*(u(=SeB7bXP5NV#bR?l2$D_!x$A-_EYjVndVK!W zAlh7g^~0Yqx=_ini%(#X48|?Xn>fbc-W}&S_hvEJL+@2BTRZ>h=pk~tX+Nch z8sQ5O;hyLJ=GrTA%5&~NtCxu7Hqh~D(u*@N^m^vauv_-%%WrQvnmxw)_fXylJ0trr z-IV+u3H=@jGSsy)Y}tcpTzvO{1x^MMh0Hcm!r z=kD;W#b}f-pD(vEy{!>$D47Du&_)L6ou&oqBugU%MdiH}z9kpO8X+;kzH8NNo&= z;&(#7USh*P^r^&tu^yC(*qyN7?^Qc*l#;A@?_adKdYtlZWCw~`V=EUo1>_*pGO1U) zF|oUOb#VawP9@8??7}(Un}mPwrSG(z%%ZH(vHcT3x|?2Y;s6&_=h`;WCaU!ZZ~q#S z?+Nl??e}Mms;!XL2%@gXi&pNjj#5u&XG5c{%oU0xs%3Bevaq;R=V3M<#wO)%5hew> z(fNLRP$s@x`3mK##0dAQYcr;vTg(+?Gnkf-)`|pRl*>v}`m-dW6p$Ta&s%P7UaqC8 zy54L#b3tcSnfcwIRBdBfU0cl|n}EmE7zIb9ay}+BrlFli|CaJ(v=qkm(7}MqgLP2M zSwGeVVzt?We-^@Rv%Lqgr|ckrwkW@`rJr5s{<4wkj=+Q@BomsdFGd*djX$#ii;xJ2i(F~WJ$3K zCx?;lX~;A%YgP8w!FOKfoI0{y3oI+?p$T%tE68l>Eusmq&9DBe>ZVj*lj`uP6N3yq zt^M{RXP4@GIL2jRs7~92>=|a`>%+!RkY~o$P7_RxYy{*rVOJ9U>|e8r52PkHW%p!n zG}qA!$(ypC2ymB$#OKou++js53D!S6McV;nTd0fn*MaeI^Q4~1rAtQN%Q=~}MIz2e z`|SEGS%bO(hA8G#z)wTHD4O9Dk+UYFqrGfh?1|>i<}`7p>oPX;BPKqP4m6F~YFli+ z%!e+EX_jQac?3 zeQzC*EocXhnvSqif&wfkP!=Q5CRj;1CFdD`;|N2Ak7JbH&(ef8mP=32NThAHEp!+=%zINE{kDB(3epQ5J??l`5j?1W$JUM8EgMA48&^$S(@b zjnsyhs>z)~{jO*Z%pE7x1xwav`#jE1*SHZOuTUVq%LMjCPYDl4Zf_9SAClV7vIGUo zbL@yN%#sDYE>6EHC#CNv03rY%IrJ1vWsmn2`P-VT7Qb^ZKBfFSNc^-iWegj2b&}mv zsY9SMccE#BS|c`pg>M*5{HaM;eiE9GIx-q9_FxT1CxCB{((Ad`sQHIGL!E#)+D!e` zqOa=;&4JsB(uCra%Isz7@pd!uZPK>0fpwki&RkjMZ;ueQ%%4z>Wue0xvKR))tw*&< z5HejAmU0|AxQ`dC39%AMIgbNIuyjWa&Qx(lKyhU_?!?>ENy%u~LKyjqzuE|dPd1Ei zn%IMPEuC(20(+P`UvHYKTjFA=tk#8|SzUj_-nE1CeCcI+)e(;E{bqc_-pW-hAYvh7 z1fxizs){Q$f2tN25G;HTPqlc$#lBslwQech_k%n*JqKHAVG!b!a)>%GbQF%TlB-~m z@$@X$WDmj^Kbht?cHx=6N%tX#M9G!no-2&d7FF&@FLi>NIpvz9ry+V4HGd_xL{9Kb zD0wITKw?)=t0^Ot3$^Tto?%sPAr)?{A27{yGICGM5MW}H_{gxmcvb$4U77>@*%=sA zl3Qg2ihl-~VNdS=dxc+#q|hyCROlmdp4fh~ApUBgY2iW+Y6xudAnb35gxHG7+2eoW z2u8P8qB1f?H`35qVjJ^+PDO2VFPNZlQ5$s2FdXA8Kh3x$U<9rbFCQ7@NMRHzGwOkQ zmvvZ=M&vWbBK)VUNb(q$XJUnW>%L`4Vf%(FrEPygVb}f@Hs^2T%xM}6?7G4XZJDT! z9q7P`(k3viG%%4D!yu0m_>{lUw^3A143+*ZjU!izHrs+f2$N z-C}EgQntk6A6-kfrNLN>%m`pyv$!Qb3^kLZ@iQ4t<(EfQxJ!C?3UAEVvzO+qb!AN5 zz)%HP{JWA>?oEvYYP5Zb5M1{<@TTo5^D>cDE!B!v8bzy(;x#5Qnp4;f-KbO<(XM8kXXefC0Iq(TJe`6fxL097+1k8hS#=)(@(RWU&b2*ygj3@A!-#)Ng7t|b)ambc)7Gayt zXoW0#5@$k^D{=95XrN0`@n1pF_HfLDErMe^Ea^z_u<=NrQds*|3(oz=jwpslro-cO;R#v>A)AOKZlojQ0ZphRe4wM@yM^CJgwYYlTaQ#98`Bf? zstUh@i}D}e3aHcMm-!3&P9Qw1mnHS4q8|!O666wH+WjgA`imk!yvm!LT(h65q@jW67EDmWOD3F$4ZWI=``7)=M195rU<5|L2r; zbH}K?(|!F=(wEZJm$p?9lP9c{6u)$;I+ zm*c8~yd}R_w%0tgY(D5ssszHl_M();Ezn0A2PKylc9b*~JLyy?BE`um%Js>aa%Z49 zE3d%kRx8TB>O_*dO&IvmgF%5~{NuF*&IeU!^>0V2^J*L2ExNGtt;{ZH3^C@gRQFVPglF@9Vs)`KCF-~s`G}!K1}&~!*wU)(<;#+B0UCwmdYyMX zjgf=}pW`Zw4h2E9$2~6Xs*9tsb|!5C?6iPjynqD8142beUYXUqSZ9o0>Db$tbGDzb z-Ms_%Kpmyot^|qx8W!i)?Z`AYDT9MGypCj5a?|zRFGGF~F587?P3Jfb=T}WZS0 z^UTaHRZf)3$sm08?c1$$_jw+Hptq8$l1YoB&(rzBB$)RhrvhZw8^6a-yuV!a%g;XkAAcfAEv-mR+w+78&Yd1G|~YSNeNaD z8mdA`qn`X%zI7mY1sn(MowIJQg5CKrFp%v0$^t6)K53=t;Xa$yLzmk_Xdnt&*U&sp zywIp2!<;%pwhS4Tmfpl8%I*q!)*p(bfDi%L3dbBvZcBVwFX zM8Ys2?{q)124e`t+aZ1+Zvh6pjiyu|v)qqIB}B{?br5TFIc6|l2ARWJ5%FYC(Xnvl zCA@ek!6Fo{44V$iTcopKQv!>3U>=nWneH?sT9QMwQ7IG6G<6PLYB7cK)ZHO6t8jM|flVVY^+++yJc zk4xEJO9d<$PsEIISrCGT$$1T;zPTNHIH3i+ZBC)g>owfsfh<&ZKtZ_>(`E&m#X8Bk zx!nMgMe`_?jPkGS^Y2^N=J8^&-I@A@XJ%WNBov}WJTpv7Q@7s>ZD!WFJQAVsNYJ`! zjQM+L;3b4t(O=E>@T#eN`=ksJNKM+2rzVm23Qee5Pu{9rxoMS)!3K8qifqfI#FgMf zOlOuSns}3FRw3x)$uy>N(Ps~Lt!#DCe-=YRhd2l4cdOfRXGcernzc2L;LD7*GAZa$ zyDRtEy6IzfofChnn@scfbl`6Hec&X%y)X#zM zN~J1T$+Eu-cRAG3_!E&QjtU9ewwHpN$#=@_-R31g_r}z&26db%cIoOgugJf9o^FY{ z*Fu86!;QCO+UG)Ek#5}f{{#=wdjs(`T0rie?ui6f_?g$lM~5Qt5q$F-e&o0JJ68H> zcq;KYqV45p=vn70U-f8x(H6{!%`Y4Cn@>P2vr9g%J^GK z0iE8NAcDh*PXtG(M8PFmF1^S!87nRmlPO?9!0=}*ZGma0EASb%FP}xIiar$0T0i|) zTVPD|$(pXFwc#7w_C)U&nsw?Foq!=tjn`2p%i|vbzvqeh9=Wq^Q40ISd5h{HEbfUW z%o2U$@*dLbG1Fm2ap0$U?pO&bP6nyU{W_8!2X5RG(nZxEO_JN6lR(9egv>Q|!$7=fj@D=l=dP4!*9^=cMo3Lpcxg?=A|i|G{SL zzg-kkHul#4aZ8k_LwTdFVEyguF?D4jH6S7dMg~So+zu8K6fIN?FCPFxQ-I#sAj3)= zG2yrY7S*cTsO+h-L9_1BEYY-CkS12L8?dy}y429Jsj_LQS+&%P^PK;im65Z3h1B>2 z@p~uB?X&OEXZQ8tmxS9cjDD>8)sWKFvmpWgeIc?y%1lwC2Vh{%`)=>_;n0C=qiPR> z-}iG7g20zdLZ0%iEnq*tTtSY&#R%wr$(CZQD*J z$v^M=?e4F3tM>hWd#dYH*Xin~tDke9+xK-XEK{mvCNfSvUsGi&++e=kw8K3-S75zM z_dr2v$MCs01jNSTFzQIMd9xHJ+ysI?a=ED7;Obc<*ne@aXKhj z#?eV0FFC}I`BDLwTr_?#9qM3a%ZYu)HnKk9hr^W^nh*Np4`_)j6V@3_g=&}Fm9*`G zab;@z!oj)dF=p;MLhZ17WMn@3vlHl&f z&avr^prDsVT&1Fpe>kpO{EeYEQ9b^8Mkpz$#SCf@p`VX5`4U`bXC|CHSalJ8DuSS)dj}sSsl2f6+J*-%$fEB?|bEheWY%9q{g-W4vQO zYt^*J&@Mdn;X|aqMj?n;YM6fJ6$?ef+JzIVvLlBCwi1lkodiz+M-Dsq+_14`kaNKw zz8|?ca&u-du5VO473HsomB*+Kg;`5G%kiDqR9Tsib5i6bF$pF(lC({*HxVsjU*#y* zY1A^(jE?bY=;->EV<83(iKuUc#!$NgV`-_^Tv&#pRRv=RN$OG<)*@b4hTadlj3EfB zr&XQ8=ZrE6tOcv+^_%lAOj%GUI_^kbPHM8y*fB1OQ9eDL(F^y}-gZ z=7^8e&E^|#YlKv(WQ5G(i{xWm#ut^z3XAxQD$l79i|wBE*fpIw6ZKb5YaxnTFWe^; zo10vU)%A^@ZCYaTMT};`#fx1>f6u0OIM#S0gitGXZQZ-6Be;()kSapO(WtaOp;cEaWwD%eNgLQ#H$HD2fmsz#@lyIk97+zF1FEq2!;KV{uSS zR!P}3Up+mf!7T2D-)2|_J=l&SCNw{q%aJMVa7HS)pVCV3utNoryTep2CzwG(5-Nrv zJqQUyStH&zWE0A(dk;YO={JNav!xJtF)33T2@J$JzIE^mj8G+{d%`o*A|E9EQ3!aL zMUOb`*g4J>w01)j2&}-8VhR;tvfvLFiX+~hrCRj9%7`5?QhIGqR(JRykmfcu_%HEYRr_D1mreIZuu<2Kl z9EKQ4Vc{P#j+pI=*8DyikgHV_jbU6J%`ILDPS>JNA0xnTQRFpVWb+(-l>8p5@x_OLz8AOV2d%yuLvLZBUQxn z;YVRqVJ+?<ysp=|d3{YFsyswP82 zchtwFK*A8T^MG>lT(!pIA@-om)e-jD%al<(oB_wIwx88I{wmkbX3Ql%_B%E5RD=B8 zuuQ3*x1H>Zc*FTGE7fUclC^L!kiE}P7~ndQC#w<9Qxn0d5Tb~I%*>WI%4Qz-gL5jn zc8jF6+9eB@(&}Y`wV3@_srOqMf=LoJes=c*%x03jl=l8yVGGi9x^R=pGl3H*(xfvD z1qMcpj3D$DmIY(k?KH&%VNoCV2md&?&BgRpXFCOv zoZl<)E9$dk15c>L0;so-MG7hFUcY-e3BhF8`nbZCGW|QrJ5#`n<2@3OPTD&2op)^% z^~t}XVlgP$??;EM9;Y-!WTGd(D}tx2tazpCT%@C(H+26L+=gX!>TzwWai?ArXI;sZ zoQGAc)**FHeY0hsqu(6#c z%#at34cRl&rvw*-|BR(=XDn$(Ad2rk{WL!cFvneeHHww|4ZPNDs{Un@DSAUp0eXos z*k_Gf%}vIjy5(;I9$#^RKlm;M!|5xgVdk;yh_NC(=;IXaiv*5^H7d_+EkGS8AM7sB zE0|B+O^LzM>e;>>VLXoD>Zn~qKkkT6g|Ksz$3scWI)uX{vEko-;&%!xYNKQ*P+6Bu zs4mf^MzbR(u1EqS7E^6ojK<)7iiHb>+Mf{If~UApKer@)P20(~BvPpW*dEcKzO#MP z#{7+(24WMKTENqBdAM1TGrqjj%@^V zo^i)WiIUYZSwGtSpT!Lj^Xgt?DV0HcNEo_Z`{UPd`R9m`miUH1<}*5yst~(S&*q{o zy{TABPw137KA}~I(RVBR9{nr&!*PSkP*p>*#umSoW&2^tdfH?;f?Lap3PUT0eXz_0 zB8lgDm3^fJfQyKG*b;vn+4U8f3dHRyp$z;&umBH!lanzs-`Cy6BeA6m9*L*mJ;4Y^ zhQ$E04W>`lr#Tc#r#!(?(SvS`>AGkqRJk+K^h;@mt+VCIn{>8!v95EqCR8{AXcflv z-JNK`juiNT7IglG8#ze+?M=~=D%{(%!KxS?nI>>#CF#8Fg(WPU#I|PNg^Ajlez(!W zsDR>@eus>cg%*gtbnEH=yq)d2&uM$avKMXWvcGqs7&HU{Ph1I zuVr5K3WL8}JMF4c&Mba>H{NaPr|*goVCaMLDLa6r%rOU}SOxP>UEt5%?7@pH%Lt^; z9jt{izhrxuQnFI4N#=oo@DB5cWM-cV#nC5M80$jkE%B?4h+m)Z5ABE(&EKJ}4Ne_U z8Q1XayS1Q^+gxid3qc;Yf!4!@Y;ZI4^J%ZokU9NXuIV<1wEKR&kE0sj&}fQ=xuD17 zHG^jr5YByT?BLFKr74Ut+C}$>j6BC%D7tpl62J z0mr|xK5-2Cc?R2laR1%f748zwCf?V2S7Y#@E0%|wq=M3^H39z}T1KfSk#Bs>s-?fe zwdr*x{m*d|^FQAOEK5}l-|eNdc}iWfy?wO*=!fun zSKwFlw^_}*(86CG#JVJ*yDaSm^0x;J+-T!(mJwf98cQa+Y*7X-DPvtR?{;{7YsU;b zxjNo;u|~M6KyYDEe{Bn)uljbC{B;HKD&y-4y;Z6U`p0YeYa2r~QTMlG=^e&39C;5QovmXBDZmvtO5T`Rl z`&u#=(dMN7A4jC2JA^3}VC$lut$^l?mErz1DNeXI*Ko{<9su=IBgavjnI;TNc4fgJ zvvCf`Hc#g!b=<}PO^v~48Pj)T|BQD@Ldkfv+*oF9_>I1iu}3+7Dnvzf$x_Dd5AD1sulqCOCF-{j+ZA$?&;d|#)f8CE z9lpV5pU0B8cE)ioD%G7!P5irC66%{^LNQO}hxcD?l~YmOQj1^HbpAyD6g)EhAA-lf zqQ?K6qf3Zev-+B&3;dkOkq%$W&%xqn^Vc_tEwsgKrsNkzP9O;IOQm?ibQZ&6bJ}-= zbj2dFiv9@<@nhE)Y&+-Y-uDV*h=PQJjg^%bBO_-c@6VSPC|)SIyJ3BS0fftvV&18o z;_XgfFvwD9O49Ykf~ohCb>}FM@8*nDT#Z;`-o4Q!jpkgbxfT3FcKu-|&9v==n@wU0 zr_Cfy?(22Fj^*Re0ul_r`OG;YuOBUG3h*YRha83i)7b|_UF651=N|m=NfU2U(>l*4 z!Ugb2D+z_%Icpv^xB?G+uG>P+XBRLIl1MLra5Ww!`7&lBsE{mTuujk$OB*JGv#y&4 zF-=_}SP0WVztM~$P^uXGnC&IKq1#YM^lHOzJJVB^IJ!kNv zj1b}XGgK8(=%Le&+BpITg(ms9cG^Z1&F1x>&XAHX0|u@FU~{MCM3^S`w#%mvK2znD zYVSqSkgE0Ri&40;px-HIKsqF63GPMJ=6WRNVm_)JwRCWo00okzJ2)IAF+K1_)cZqo;9Q2tUP5Kb&Ph_-Oj!u;7f+Xy}qY2e={lB1Y${B2XZ4}ev`HOZrq zn#H@3(2ztSpdnO&%|QY|79sLM7NIn$qv6qP3D^A9&>p(GnSot>e0k-viK=@dBCIUr zrxnRk2jua&f0?z=4<4x=sQHu{*C}si(L;fhW$SvGbhPxW?{BDHA3|*^(pbCZDeEyM zQLAQrwo)r>Ya6EsC}+~x!#P7FF$~;;GAh&t_8yW|aMj$~ByPGIX*x2!9W?r|7#Ib0 z)aY|(U~4Ng8$ee!$qxW1(Hdq(QRak{=k1=|6HnS5N7K~+F-?m`CcD%sv(20tQm86q z%d^Ziy8xV^2>?J%FB?(yk;|{w+(Hlik7`haU75bt&Q>Z@sw&Mrl9fM zlI?wUSGY6N_hV(mOGBA}PG8f{9BotMhU7Bsv`ZS#-Y|uaZ>$r{DOK_+ahfe*Xe=g? z`HTj)s}bfcq>*))OSb3j26K``IzlWjSTdI2{yL-6+T!AXZ4tV!CPF6TnU2>l8L=-50>l;OpczN!=U3PvGm6 zHd)u$0E+%E8Yz>{Pq>&?$SA{3zC8-JYgZQrA9Dj z@sIGLjy|8u)zk53%mHe_Wui9iS236SW!N7~+9%8lce=ZVu+-}*JdcVkD%|iJXW>uy z%=#JB_06t7%3fS*@H4{Ag0P|vAx>sC{Kdj|AnM+lA5a(};73Kn%lHRpFs6p0(ml~M zKw4$t;Ku52Xj{QQ?whRyWBIahVt+Ft#Hi**5vfWtNFsC^dSpi(&N(h3MLhG5B%@p` z<%&^bFst&Hah`fni|M~Z@8;XzfRC)6!XrSx5e3lp$9YH&QSg1#sM7Hb9eOYN`80!n zL{BdK3G#i_5r%o()u-{!aky0phoZK7W;Khs18bxna#S5Q#u0MQEQ(nbSR-cjLy>r? zPxu8hf5G*Zb5FG#)S5Z>nA2&z!`VZ4XcjRQeAfb+1eDI611UD71dI|Qg>s~R`bNRbMfFj@p+lKwrd1g&>V2B%LU zpX`G_9;hT#oX21MeXM$&WL^&c?-xn_wnsS`;;np`r@&NM@tT6zx`|BWvY6sfcDi5> zh^ks&i(dO(VCp3IePTvZVn!O#-I)!gi>;>rgc5uz1^gfIX)}-4*PD~3MbRsV@WB>5 z<%QxNX71q$W?z?m_bXP)Cwr}f4crUb<+D$yf`nV{1JhBkcw=&0`TkvlQHgsE(Rav( z`U|hqxe_?JEWUS?2d7i#^XG2&c{q7*!4AJy#8a*LEIA(jcaR6G)2d?JypOh$ z2ki8jqY?XXanSHZ1Vtld*|IdhK~i63qz;m4O>%}a4&0tcEo+0^hsdd*ApoRA~QS;yojAO+iBm zX|O9U-3AY9RUCqPfM25~kSJQ-oR*iI8YK**lvl%j4yTtK3mhVos6M>YVUT6Z!#@3{ z2UXjnnvq@dLUM@3_z#D}y^IZ3(Q}3UNPx`)Aj|W8(YmVPQQXV@;ZEXP(G6=jilj+0 zG~w?McC5j8CYFL0;t5MrZpgY8g{Zb>(g{y=?&!^-hvy$~1@ugkV-Dhd;T}>jQB323 z@IC40^-SaDsc~jDCBR6F(1;`)<6c@T@1 zN;>T^o<8GHGS6V9g=}auKQ*ha*w%|FK}+%wGG)JAEEf4mzlE6cWmxo}?lJTVPp?(J zdX}+}tF}3wMBGF@Fh0sjStLh~!8#vhFr0Ww(|Mr~TOXduNU&|S71)y~j{YL2K4vVW zDlYThNFICP2qbdV0FYBjIjGfhwickq*L^BGt>}~x({76CpqLfNG*ir}F7Eeu>|};e zug+siB&o!<3#m;V&~B{JV+J(jpT!`Acpx@VUIZ~E1sV8glC3T)sU5Pp<`0OHlZRO= ztb?;67#OHe*~w+d)oUKt1{ub=z+M*==yL?g%Y0SVThGhLTPrn1GZrPO1K|L`YTP=g zvv~V#5ajMjr4zZ02qamqsm4^WuTGclB{PxcpF|%9Ga1{+_vJ=HOP>zwi;2*_I(R^) ziIo$QGP)umVXa^&4t=Ak`DPs+5lM-5do-q5vVE;=6)ORK!WsYoG5*wHlmEFT@bmd+ zDQ>l|X97qlGR>EUr7=;yJl&P|8YroEe#tH0O*ss0Ri*;vWHjC%oFG+{W1K7+**al8 z7`$&!iRDKRQJ!+z9I*ZP)_DEI+>fg?2u&r41!98~fv~jOoUvyB*xSR(BE#(Ju@xC$ z9p{bIegyX6FbDtm(J&8_(8^p;`8XaER%=N@WCKg_T8)bu;vND@ zlNVRlN*Rf^`{x!6U5k@Akd(78NS`P7QRSRcY-w~HP7{*-_U##@(ibknxkZ`8eK_vI z>5(F06FE+kGoYyh%Ijq1WUGG?f-jqodZU3qI_WrA``Zrz zx{(I*&0CVKnG1&QkBHwuu5LKXDp(^EZdx1@|n?<_v6sI~zl#onwC+K)_YA?Ew9X9NN{YHGiLq@P~WRnamC_4Wbi% zpDWHuqp@qq*g@>&P1(yQ{S!KcaigZu5(P4B@SZ6i>+ffEjkOr4%E`CvS0>tA=Z}4L ztE87@TRV~(){rztQ`mBB}iEZdxgHr+V=y^(5~#>x~-4$4K%FX52xyWUmGZ^ zeTCF|4@)9n#OnHE_aHd`2ukp>AMsx6&K?VL+vy}C0J~|Nu^UQg=|*u9;`m-2>k1atygBub@RiL9PwV*yKik%{iUo(bHF4K*|+@VM?b2 zY|l5xR2TXh5}{H*8HePppAysV9)*1Dr4g{Lhta}e`Es++?m(8j+sJ0j;bfy* zxb=a+R11nhdJa2-7Tq?{QM=8zegS8H834oZrmm!~vTNsmGXO4zG=Fg<|JQ;4_0GWB zlGfhQiq`C_>jvOTYhY~$ptZL#rL{NEv$8U^GN%3h_kw?Y=PUU6`}p523iH?J|Gx_g zn_2+u75-WtVE13KxT573eE3fEjhWUy!&rk4UbS2ViPN=y07o7jewhG z!l&8eDF$+k_ieaq5p-iX{x#LPbz&Aq4vxF=vB}FT9v*KXlN~8oNQzj_=zTpLdzbj2 zz*A0#d8BgkVoEx+c*3!gWiVKQvHFf2c)?2gZLjVww8kTQLNo}%YU(BA%bb}Q;3p|~ z|MX|`K+W?_ROu+a9xPRnM10P@3L92SqrOgX3K-BmeUlRe$(w?+KpUavk!^Db&o~v^ zUl*s`0)-b3tbMuUnw^+1ZR?*Vj@YwEQd5oTo@1zXTuMfUDbTvI&O|nAHNL;&3R9>a z#N!~1!Jx}lsS2>2kpeQNj~3}BsfaZL6YQJNZ`ZOcxgi6NKp|7>FI{4+?a}@8P8^CI zA$UQo+8Xyj;)mUF9y>C96GMkwa`l12C~lKga&~J&fio#w3U}GG$y`cyk;&3ys@Vhi z)0+%t7PXzbFJyZ_{hV;5ksyamWMa!iab6Vt8R8O+sh87~CS+ye5q8Ah6Y)waGO+#j z4Tz#hE5u4Og>&(OIM*8ji4y8dU1x#WS$cDXf5i^wx*xURdQ2SxMWj@|L%nVRQRvKD z#Gmve=jl!2Ymp73e+!;N>rV&u(@sWu2ce2lipiH!Hx5p^UqzGD!6YnL8WKI*ih04% zphcXBWu6uZDaIHhF4slA-PP=Ktc_K@ijJHKd!^68B7BEQGUJJs+WSF{tKpIREFJCy z*rgYWn}t~?n%5vQ4&EXe=csW;cQ)AIS|3_iW)NDC?!;Q089w~@8*Ud&xj$R=OLhgn zo`?TkbQ%AzqWhmMYUT=vDqkQL%Z37~G{S_1xq={=z-R;F$W%%M^)ukAKf|7gEez=m z_ekVwo)&qgX)|)(XK$czIixk@0mAR=A8{YRMRPTtlO%jr75YwgkD8lfckNg1lPg;~ zoL<1$P*0xAVXDlJ+woBozv29zc^|psJDO38JX!txQH?v$ZuQUzDJDyvMF)!E9D`ly zNw$svj5KLb-1EBzC7g18Va@qpGC@gE`TDb;8P{pM6)vK=lK)8$HP(*qARvdJU^g(- z6v%m05|hp6RU!oay%C4G;uz1^4TN)OB0S{^@j7U!ydeULjNW`STfKK2O(0-JtPf|7 zbT0jdv^+l(XUS3_ax6~VYc)U4gurw{*gingVX)eMP?O1YMVKjxIjq<1+ ziK&XzI&+~8RtvcTxaQid$vSWgu#OB)r4b4Tw=RXT(ryZHQ^No*o#zIujvLsmO3K6} zSgN@q7yh=Mr&tyfa6?_LWvB$p=Yn~o&&vU+z+krxr@*ixsMz7cnxlgf*2>AQ=$(xo zpk`|~HUi1+O{5qxxf#s%EO6h=sEn%ufb7(nwk19HZS0n4eYPj482)dNSOpzTB z4xZ+(brQh1B^y3$4CELV)plkc-^mP>Ve+7e3HrsaIGlE3LS=biBBg5_AnuKyz+hoO zkvW2L+pm9IhwO5oS*aQ`$sIRwM0F{(;53F0L`~yfQ=d78&SrW51taG{J$;Xxj@=2I zk#O2c0}K8@{BoT{Pc50*C=wu|kjQm3u4PESNp~yoN(E>PSVJ&J zTy29CF_rE%H!QS69p{wE<#p+ZWO_So&Rnz%u|&UlX>hmB*kDM`^^CJc`p#>t^y_v4;sA zK`JfX+#m0^&DtKI=;_#!m*H5CHLYhGskvJncwHuW7u z5l#-@q-E3(9{S@Rc~=gj=I^dIFkV0^>#${BOIs6u*Pa12I{%2T#BnA%wxZ2F?Ib|? zcu{CAvs-^T0S~^rHeW9OcTElHpfLtMZ>bKr(V zx?KgI1m#$i-9uQ>Cufp$LtLqeV;hzX?pndPdt+e_!VPP*Att-1|3!mMfq*OOJD*Iz ziDp!5VrhK5re7)1+720auM_H_tr0{-{6>$r4Z$(_ZUuo8%1ph#7Gex6Ta7#X1k`fV zs+mc~!kd-y@UTEFdnh6mKTOj#Hnt-0YLHu_O7eRd)xA0NoTIq3{9?8P!uMqTwA@9e zhuZn^Z7@|A!Hl9|ww})MYnbXy!)m2UWjDS=Tih>>ya6sJiptQ~FzEqu!RtB&TPyUf zgL}@tx37~%Or}vWZ8lrO6N^(BtF2Fo8BO&I)Wno&Z$R8+<{7C&3ykI;P@4Bn!yWH~ zl8?W$v1VmNrVd{UJ@p^^%2@s#r7o^#_m@lZKRl9(xUDZNj-NwaYl%2qa2h3eAN=#B z1wts-Bf1`@6hLL1*_M`dq=nN$x)~l|z~LxnbJ^Wa`R!inyUHuHN&h?Q+AlAx6Ku^yQegex>eUL`rz?! z+dj?V%|XU7^|2dLedoS#cD(0v{629I)CmczWaz@fgc6I02*umI22iAwwJjgH-Mu|` zr*Ja>=Az1N6nhVz0+#4gmLzk&z>_M*taw$kWHSIkA!$9ST+{L35|uPD04uBK0iPRQ zO6SjhwXSs(mW{JK1JjcnXrCJqbJ@?{$q8YGt0Qx@v7PV>IO{ ziPze#fav>uj1X{1fVGGR3_(Qahs=7XYwYVL8|Zq@n$ z)X~46XU7KAXb-}y|LA%Jp_(NhtcU2`ZZX;mr!yXY=hcI@Wjw+G+BC`@2}b|8M!f z|6ShxZH`vbFK^yPB25;bb+&yAc| zDn5|j!2X=0qK!$uBZv1&!s_Ftt{w=w~ zI%)na*%yG6`^*^tPQt7yiSIKe{=(nq04XL(aRY$V*$G9^DWdaRaT?z#MYuD2NJW6c zs5E~T#)G~mz}bewB5Q!$h~nN7`Fz~4No{)Pr8t5NdY0Q(tuxoJNX>Kr204tN1BUM+ptHTV0WN6) zODqR%JbT8t1%86%rDk1F@BTTv?&Z8Z?BTtkMqN!QymF7!WiP$WrO0Y=>y~@)RkmAi z?|x9QUS+X9Cj%{X^qwgXEHQF4Rh^gJPI3venyAaySIRD8YcNjG!6u!iv?)Yxr`wkt zcz}?}zOo60&ER!amEQ#&I}2W5W}^893ulh8=;#r}jLq{KoMpPKPo6Gt|t zmxagaKD!Zn@5(2G*9I&3h1soEw1ruZ$Bp>>Kq(-pgZjf8q!tSa(jY{h`DHAeJbMXH zkQY2wUS|f5Kaa0gP48-Jgr=3NqvmI@ew3((zx;5$0jullT2)guq3_-Iz@LrS@yoQ- z#j=r8*a@&czm{6`OQ0c%w2lIg)Mt-~*=9q}n*bz$7~BfiE+S=-A*Y;+?2GOqH*%o6`&zVDE`=rlSSBo7;?DS+UO6;(chEXl{}GxoH)Nid zW)y|=98FI`E!jDKiGCqu3wFIuC`^B*o*Q1{_jgq2vKj*iupt~h8!E-jTt;7!<8t$s zqT^8Cz?0USy{^w;E&<$x%c0{|q@rAM0N%$NqHyFki3ZM}iXa1~eg`1zc;}YZK&Q%l zSM2-SU&;OmS9RMnEjjte+-&0PEldF~KRi^hMkx+ii6}rkVS2M^l44``T)E8&geMq^wE1_x z4{mo9|M;p>0A2?#l`EGQIDps{-F%Ia1Jd0eJj!5#6@E6YR#8_3Epp9XZpj|m3f1m+jiS05BvNg-Ck5gB)B zmj3XnlWyJhtB!2s?9#xiQ*!OE5x7zY+DU}xpSjDf9%MR@G=(^i7u7uQ`qjM;w?t67#pv* zgCrIxe56GH*AWS28?-H_`o$?Y0VsWtVZWLknSj{`Y&;AD*RcAIkf zsMj-bQ60#OH)F9Fn)mzBPSq|_q1bqwj;zCi2#@BA?`RRvGA`6(1;cLgMFmtc`5)$S z{YW&_5m@k)p#pwAAGELlM|2fPo!@1OAY`56V511pxAL-|Ab%MvmQL%n=Px)a_kT22 zlK+l#^l!=Bf7mcZa~Vt#BrXHm@(y~RWAXXA?|2>{z8Qw zN^FKc>)o@fA(of`0YaTaRuQK`ujJ5HZ2)jpvouG0%suk=SgPGwp0x07gPA|(o^9AJ zJ^N}L)Ry~e8p5LC7$2u!f9FXx3^ukOgC2BnNn>>2@ajfAT~MO2YAHjG&Ci&&ryQ%i zUT7g_%5}^-#!}mx(ZnNUxfXRHOydxA^Br>|)<32Y`9a>oe4&2iFxszVN3)hOWehjg zG%74Pj|Q3!^HgcBqUj6yVH)5b(=o4#+EV-~|EkBUh#2ecxFeDVZM=$iP*}eYy00kv zQiNWf;*L`#^dp|6QqGgyj~WtMrS1ZhfUkULji?naeR_cj^VV%&KVlOpayL0dEpVleoOohv2W>Q*p}T*GTyqGkZ@_!zYnK5X%6T-2 z3hW$nXX|Z*O4%&Mi1NunebaT;Cx@q?=(@)ns2l32JH`<{9Pd${LQ>Axl?wp($3N4V zZ7U?$Z?qvVm|M4n;VucAV5i&P<6moAmL!4uLR9YaQ`@X$03}>uo>CJX=yhwtv`rTG za{_BZGISl>rS2w6V(w-nuQ)|b-e+yaa)!n5XXYvkJ!_5D5y~StNAvB%BzL0(QM%Z?D2hQq8g_j_OlRePte5&i?SfL^Wh*Mm%E619zF(z{5XDK-Kkl6^ zxlgUTU#flDU%9%TPy4}rSEVw0e@5cI5W(c$6X9q?jcpZxb*6YO$K9MCJwjSP5W#Ph z7;HoK*n~q~L-y#2(r5FYiNb!4#kwT#O}as5^JoAscvgqM9N>cNycA(`LfPS7zZ!D$ z5cu%mm2V5B^VefLSFRQb^_GQoN9N4mPUGf07eU66yDo;?iv6?oxX1BaiFD6Jp5^iO zr+B~#x0Ch=_xW0Yjpwj$kNeMX5boPGBCZ$3Qf73n*WnNrC%MueP4U-&KQ9ud6IDbH z=4_nAK}84Gm>^vb2FzZhLB<{~i$O<_H}0HQLr^YqG#`8)fAZjHM{s4xmaDd9zNR6s zjeSZm9o8h(gAdSUZeD%r#&zCyf? zHjmud#o`s*nz)-`l+ZaaLbbz z0IwQZ5DU~K!ZlOMMr-{{@Y%)42hGOme5<}DM186pwNs)I`mM9u%-%ivy~)YvFH=v7 z$P4u@t0+|>SyXoZ?nvE??Ao}e z=Q7^T#SQA|pX}lTv)8;d=ld%TLxgZp>8ubZral#c7srIYclSU(E957(8zC0!)3fww z&haI){&%d1$X3u=q1>F;q~R1?y^KKs&!O@6Nqzr>4H#u z@E0KvR3D!(B$u^vOvz|`vwzkbsd3NLDCx)3#Cs?&5AMj~OwXhv3Ahaqdd7i4y)YbyPiFHmcC0N5TD&7MmStaO zt)M3&GCBkYbZ}N|TT^^QdKa^zYT3C+34yUIS$tIm9g#%MncJd>D|Ri?39BvS58PXg zEPo-WA(s z9Cr(n5Y?SAyx}~QbgToUDBEac;lK$`JS%V54WrHb&SN=9CYj)WGx=3`(A)z)ljz9j zsVMr&3>SF}Z8bxsB-xV?N~SCXk%3sCPhmY8g2$|X2_rkZQY2AHsW_DK4^*Ig>XoY*O2dta^9J3F{GQ|4ZncfTWRz zaD4BJmDx@bL?P<7&<1n}Nth_9B5@NjZ_Y5-VmR9TLA95;RK+=UyjcLv5M2pg-KM{i zg@*{Y;0E78AXh^pqF8!v-8BN(~ zZgvm|vRMquf;0n})fXOn?KJqJL)x6-Bk4HnoHjKNRxut|h4s0n1X1|VE4+-n!~&5E z`nbtyr-WI}q09U`opu(Kb)0f~orO_=UAzJb4v4Xp#b!0cP!7z62f-3PF3`#aaQe!1 zE90BSr6oZ|(8P5NH|misXBK4yXUrG%VmoH85t{2$iv_C9eH$hh=X4m9SV94sEj&|q zK|aAw=$Uw!d&Vv#t8+k53~uEHh)lB)DrB$w&IS`S0M&yCO6)aAPEekhjaUdJjtZX* zrl5`95!~mol}q=|fn!vx+Q1E|04CEWAc&I#wu|Uu-1!1}PQKeeD8dMzhL7Gtv73)M zHdvPugRc7<^z7+Vi&r`?)RYgZoWtF?X);WP_Mt=>W2a+F4dGR+MV|sEyG8~xK zL9GLv;fy5U4ukq)xs}=JjnVjg51X-rbSz{abIla8TlvukZ=%k-=RvF5=(>6-Q7i9* zv4fhnMC_AtK`N5N?DTIu#`CCAn*+A0PHRQr2+|@T~O5 zt8!_ZTKU+b$gPjm!o*ZuYBNJ*7+O$U%{9al7(Lg}YJoq(;2{}OxY2&}yQ2>h1j>>U zBN_pEP2ef6jO)31)eq8R-N_3^x|Idk{0Rzmf}7wKZBl&4XccV+fF#u_w*AsKvn%;M zz*Xdv{QD7_B)j>SBc`7r-9tySoHFDTBpt5$(yI==;Z!Q|QF(G^kKNMEXP2!YvQqJ> z@O&Tf(n-hiI;?O*&&pibZmG9hs>dR|aw2{!`?+4e>JmU%#eiSc^6N*-VZlBo z#CP<$*l=dz9}`k1q1XIOj4~<1lf<-L+_L~%=T@tyx!(r@tPA!tCHczd0s-mGHeliu zP_y?!mxrUC$@1jJEd;2_7lctF2AKI=wn{}Axb7k}Daw_74>Y9g2PkZ+>Ql~}vjKW0Bt^GgMosDAG z2I7IO5hB6ppse+{H%T_(^w0^rByU0} zzNebH{%rZo%9SAht!=k)F2WY#kUL4r8X4DN!v9>PoWeEE-{Tmhm*8h^qL&k=N;!M4 z8`=A(2|u17xDdUb|MF3d3?FGH;uWFf-x+;^cyIHe&+XDWr3etb4a z>lXl%*Zw-CkEFobDZ%lt1b50bZ1s^}SQ1W``DHI`>fvve7Z8e=iF3>y-M?_-&q`3| z>@)#xT$XN$^_G@RQVh|4ce zLqEwpi<)6AbetD#5YI%X;r1wDdq`uYXA}}jvr~)j@wH1*cte?lke3PXxC=`p;3O4| ziy3r(0nd+&Y#IXUqC({OGmKyb`1Q6!!PfzFs5sKJ*fo>zd6;w*X&RpW!g@ta4k*2g zppE$B!Mf{!8eXfx!$G0&D0y1)-346ulmYm=9m$=)W7@LsBddO2_Za3MvDtt9A0+*+ zW>~;i^U}X=v!x85_;q(!;QV;K{IHvYIS_S3Tw~*v$4i{g z-*AZ^e+Gm9ViH1KSXj{6!p-`2X`{Vc-;b_`w!g_{rQW+>p|qxA?xfsUXqK0Bq5mO? z2I3mpV=L}|=FJ2)2zEy%wL~%E)Rk-vzIUME^vEY45;CcDoVy3sWzY*aCFg;g>zkQNOh9BU!c%;6Rv!`0zOKF=BnkyP ziX+uYH9A93qzWuop!(c{yAzs3+n8UpcdZuBwv!9q`Lts`lHHaSxgVGmym0!xeISG* zyHT>0Q-;7$B4_PZm*uoD$!T!4CP0+~^B1iDJ42#7j&%$6=AIuDBtT0P-jdGa*H91} zPt}|uiWB;*qjwbJI8(sB^{#dr*j0MtSI&CQ8QXdZ!e1RO-Bc(O2wGSGXo-syn$2}_ z$Vg5*$Eg|)nz&9UZ zHr%{0>?4w73dpTp&^QxfpFtmz4YML-YF#aR^|PLhkZ#)+gnP}I4r27&RU;$lovkzZ*GjwkjAgph|gV2Kqr;Gjwh-m= zy)+@zOS~igf1JHjd?i}9FIcf{+jc6pZQHg}v2EM7Z5tKaX2nTo@9&)MKDW=={oT7> z)_PhG<2UEP{9^zy8*2|Z3v}_jq(4aacP3?Kk6aGAg1OrN43k1w|4bNSEKj?@`|H`P zJ>n6~U)0P0UvlJM7v;Y>)Bj2}fBB(de#^vQ382$t(i7z}bd(mAAjCPO zEi7T;()IGmA<*L~tCT}_iKRFuceJeDcHnN&o-K6wj8|!OKjdkjFnR&?X%cSiXx}a- zT)P{da6dnfW6*x6?2NVsb0YG@+OdVh8CMe05CyVe;1M{&KHby>IzwO?0iM;Rn?g$NWe86lziVbqUJ*9~zy`>>^a`ebTrxeq+H_L8wf z+?GJ2ab`nnPNA^ooOSMEGz-V2&e;8;hg5GR9T-0?V311YlJ@DWVgU?3WeCgt>PO&r z82f{hWsBkexrz|E1uuu(>Bkl}rK;pz+{Th0q3@AOC*dctBhs(>O{rE#=Z(FQ>tk~x z-qi%z-t`O~EL{igZYaJ4jjsMfzIFo#+d5G3vtrj1IoQ$}1d|fc2}IcrVbAyOsM4IN zW%36o2^p)pqNDZ|WKIaAh1(=O(_R&Nvg|9aH)~4p>DmoU!#GZGn-)PI&WH=5ti^`J zgBLM}5vh|OWnQn4Hgqx|Rt)ek=)h*fmgmk3WD-&JLc|tNT#NSz`YEMbpYzCEw!Dil zv+6;9X+y}%uGs14Jd#CXbxMMFeWi7JA=#NgV${4YGBo0nyZpTc^sNrUlPb-?)Iu_p z;9~Q#^R0?f&e%TBF&=%S=bukn9b*RdDN<3~ zP^_;+E>9vZ?*??M@AVd(u{S^rG?0vDDUt;MkE)kisz2@E=P(EPS?IZ@j~L`zQ0E0- z15Ozgc+Xb5t`aa2%4O?S9WTi8(LjGBkafXs^((&?GIq*r|7J)pBbZOk&5~ehf{QHC z(3>$wAT-8D1XsOfjgk-rxYmQspVJ!YC?ttw((ZD@%5!*@jZ7Lb*qygUw4(Gdi%^=kAAOvUms1+m|;5G`x6B&VCY0HQL(Sb>pM!<|k!;)avV5*%_ z*i#G|(s}nxC)Z>0Mtiz{Wt+*REDykMWbff05#@h7+x(0B^pEw^vk_ZPlHW_T%7a@6 zw%kDYK(Y_m(O(?qSGNvY`+`1l=@z!_MfbCr?tB7)l0AX5qAY_AZ~HSw^5bTrxrb;KmsIGV|0Q zymZrDsXGSs19!PBW_CLWW#28-Wja{;6MeG|T`-;PxwA&txDA=JU_g5s+FIW$*MrRw z6}Yziz|lG3=|?;q4lYzbelvuPd~nt|jaX*GWQ3)k!4&zEa3{mOWvC#=W}+l59?7G+ z=Tbx<+A~32JQDzVI2hRG*#_G&T^%=8&^S(_&jr1B@|O)(->-D$gwPJ{Bm)s(Xe2aR zCgf#aPcL2sefV>=LVG4DqL>Lj=!E0FDM{q~4qUfi=!zQG?(-1q92TJ2yKR2*kr?E9 zE53dRK*F7~bw)cRXB;{hYM?`vaN|jNrE1T#FIG)|T2oHc^y%pUrI2>lqY|j86iN`} z=iipydhj(rFzKmblCcItoz##J4v|GdpkeN<8nn7r4_3{K6Nh`m2K7_OGHg;R>(!lQ zs02bJ*p+sIDu(mlFhTM1XZIOc3XHSCK5Sj3byYm276h4uXmUbSsK>W{h$(uKOT!4# zDF%NMRwt;2RVrheil(5Xj(KofAx0w%#CE>@YGF-zJ)Tvv^|0 zEo}>N3&i?%ld_WS<~pj2o%k1GT}xQTYxZ41z5cO+{*z$s{|27^s-XX$GyR|Sb6XMX zyMEgIqESa(6YwiuVuf6ajR&n21{RY1A*_%KTnbP{I{~BKz8aMwP0_5PnHA@gSJQoC zp&L3b<^4qGfL-EoMal=x4mf#mwdpt$bM1AUJ@9#Z){^qWS- z=qUuliPvo;+hZsiu+b!^d2IAm1||mngt%wKtivw=a5W}xrP@`*kXA#a%MpLp>gR^g z9W_G&=th=Rrk*gEGio#t226>Ks~v|&pO@amFu4)oKtuO}unc0#6h9-)p*%`%<1V4a zA~u)MNVlLaZKmC#639tdf`a}KpBf*65@zZiz69J3wM?Gc59-9FGb=YgJF@6BJHNHx z+@YYEaBPfET@Qf1qjVi-ymO?TPt#JwE4|Gxz`8|t6(dNUSCw9(SVO+DxO|a&S(2j= z7_Xz4-JxH77E%iqD)h2)AmicXWO)7}r)rJ&=-UO%(F;Vuz=Y!vIiPDe zmTl3J5nG9MP`doIX1PX`{Ou>yKT>MrI8}EM^4!!^JocVqO%|sl%P)f8!T77u)a1f>7d7gnTTK8Wy zc#O6n;(J}TQzZlWl0NZk~9eF9pvjxwSSEs6*)dxpS;{4Q1FdBa;qzmz8Xlr z@9uo{d7J_1fOR*c{!s+%CQ9`Y`dT@VYF6mVxdB1_tE_7arw2M;nRIAMU>+)DCq9=A z`Qk?mMtTf6fZ&XMgog2g$~q8{a5-U1SWeg}eT(%3Y>jCf#?~W(j^br)DWBSR?I%pO zMe$NCey0%JK4mmv?}#z-aF3#;9hfqFNpE??RHb?I&aQI(%t4)RuCigJm;6_(*FmF2Or@Dq0v@e1vB5_P{Y zBVU?YmzQ_R16%aU&Y-H^)u)6Fqoko%EAY9A$1c-7Rx?HrdDs z477J0VxK*D0ols2tmG}NhgGU9sGeqtcP3EryFu3h1ku{?qw}`L`a}d&tg{xIL5Lh~ zmncPQ?W{f~RdtN)j4nvyF%(F3DiE5$rIa7MYR^btaV`ikpnL#Wh;SX9fr(%C`-qAS z%y1G&=#C7G639o=OOOLDF`#?%+T!tv=oxxKS~jHl-16fi-`TSW4F&{UL7mqYApQ#X zfXg;?0F&?lgL$x&3IQa^q}a8BIChy$jCO(Gzh|Ac&GY74HK_y`_=q7a|G4@hE1Sz6Y^2LVsJbgRo?kIvwiAP0Dslpib|qYH}Am zQT~O34AFvq>z3w5ErI?5rTR&135RHtBnlBxni}%!u%|{ilJDm4zTkb|Fk)-8~7_WP^@!eYVvBDi*06dnhp_XG4_HAIT5>9z9bp6MHMOOH?{psxI{DTvhU>41`;9A-<$4T1 zF{kYuEItHiNKnNTUugE8SzDEo)U(=0*~zpKQ;ml#jAKcMG*$AGxpB9>>yvMmBsd{l?Ud)z)QEW4~Vx&y{~7 zw8v*X)v;yN(l$B<8p}DPb|U0EZb2p>CO}}lQAr}DC2k+gPK`ykRwIPT}pS1 zO723WhoN#u3HlN-pOH4ESTBcG*&kKFCwHUn&_ZM=W=v5mXlYy#gk-|`=8{=ePF{x|3KKdb(^=7I|P7swi@fm~&- zQi+PNYB;*{J?L~M^NffyXTR06d)$l_F6+uO%0-9^Qf;5k#a_4bpfwWq8? z;Uaq+kN5N;_x0hE&UD886K~HK#18$hxY;NuLfY`rCcRRS6k=#YAps;>NpxWy0jA++ z`sok|2v0lVK`g{IJ81zx+i*9@!F9r|IQWquRITHN@epwyGep*Wa{sW@_*#?&uhiZ6 z_E2>MHVyq;;=VuULI!I?Jol85uej>C)M z0pz!tMP{ZlAy{R zm0RxDpsDJ(c=e`zf0(Dc14J=fOFbT#EM=4{{BaT-V~Ro7xPNUd^Dqv9zOv@x#jt`& zvO3@nk%Gx2Oao`5#!rBZmOi4`FIe?6-LKpoKdl~N=fO=NWu?nynl03naCzS79JZ2e zk*X(71gX?D4>Bl|#sGxn?fHr5teqkH2GIgM+EC7Ph2EFVXj%cw#0_BWF1us{UDlj~ z!rM{}9upolF5+8?k$f(REzMolOom*DwV49q)_1Np zDno!4qm5x4jBf{So{glqK1-!c%c`j>K|ntFnLFuWIqj6TjV&<7tKDv2X#x;}ommS< z9kPa^xupi)LNXH6IOG3*hCct@s5DcVyp1^jqMo?$YtXQg4&jW_S-Oohivr&s5P zRt-n33n&cTJ*w_kjo#=l*+)c;yNqL2%QfQ|KBgw(JuHmxrvU`s(Pso+2=?yf*_zSM zn*-{FQvB#jiS^b3w$gG}HmaSSd!2C-`d{!f?9uIfhd7Gbzot3oo3x0Q{a#6jR!<;F z6r39)jnM~3Y+-HbwHE%A*-s_thLb>E3>D4%i`R*Mqy-MVGp)1N8 zW&XloKt#<@&VZ^~5aG?yfL{@=DYdcK@jQh=%}eWMW-c}>nU(6onR7zv7t~kf-}3Eu_htt1fnES#@2^75hr$-N)h4_nZzj{)x$zmLQ3(k)EJ5L<5wqMO6NpxaPY?2Nq)nWPQrgbd0) zbKpl6Vc`?H%sgedYN#Vw`X`Bi)F1^yO*lKvp43eU{@w*FV(zsI&C1i59@EsRF!5+p zRnQh?v+|7q$~qo2NMlye)^@B{ptkbGkrAE@c1CT%(UFJlfOY-`KNvT3tE70ovmR5M zQ#5tl=Ll_)_pCo$(p?IgeYOCyk{stP>}%S@9_5s#xFnYUc1YrSjKO=B_1^kI*uOO9 zR;}gjO5eHr{U0S*od52tr?P|je`z-UmAs>3rX+wEzCpAq!;$5l_8Wvi7ehHamOT;V zKtF+y_LyBplT;IlUqZT5L2x^QVPQm&n`ZbMUQY)H*_}R}-2m((#3R3=f%;obbjw6_ z>WHwVO^u0i3c+<+@(pz1MUtwc9{Lz}jTCz>>XM34lUmjmhm;?TWW)tqv;WlV(Dx+2IX>JY+ zjYw9!J#Q~BFvr>59+&DhtuP~z_I#!BGFNi#Ui6gtIKV;#0?cC?1=1MBwIJ!E{~VDy zkUv<&8|iv0y7c~AwY+udZ1MOVyX^l0zPSD`3Vz;yH@Ye&-;(?P^2qa_LV>J!X96yz16nA_On}Zj+h=&9d<`DVI)wJ&zT@=d;m0wI( z5ygPvqq_M1L3b9~3;9!_Nr0%KpV4oe*xs{QH4oKE(gWTy@!`0pyah@uD%*S?Oeupn zzyM?C#78BWY)Gfi3NbxlQ(C)X3%YX4t1PqhAQ+FU|&JSZ~D)V6ymz68+BQ5)!b%?AuNyGQK1X4ns>fFOmZp2nJkKftG z@3B^>4Oun@Nty|}gNIbLss9DhH4l!ywSAwExPJsUx&O~k$-i!4m9n)emNNV=8?k!* zj>b3v%hENoeFo}Tkj7rZl7 zQ&zuZc+D|}pPa5PR~x1#*^?XZA2WD9fT_V3Bk(3_ll86RdzcaD9KrU2Bhnl5cABD) zjvQBdIs@7;ITp`SBR^~k<#wF?hK{MjIiRU6zE{px?q>Ifk=3WKCx6CQQuy`lC3_K8 zl{>LjDV%U#9aZv$Yo9Szf)lI5lmjjZuUDw^4>UC=;+5jjtluZ0EaFhr7M*i=WuqLT zW#KC|BFg#8vH{C9=eJTe;_{HsiZE8P^V=xw+@0%VbsJXkz)H9XFDY??3TioPk#*AE zD0YiR78JSGVRxJj6h&7<4jy;H+Q6aLjwvYQ*|Y!W$fuLI@+!_mxlljyc8%$^W?YMEW&?ed(J#rp~wj}L|9JjxvBJ3 zu?a9zsz;PZmX!d18hE;YQh+&erRBFFm0%B3xkrl9BX%+fh6wbLsD%82fu;h<{ItbWU|?z+G38@qWAp-vfZ@?Qm-^~ zGt)!Sh40{w&&V*yrDI9_iGAzV4h00L_|+7n!{OMR@V*XH(w^t#=NNQYdFMSdW22gw zu{kn2uVxD46~uz7i}eh;?clOKm-(oc&G#ydI&rQ!UW}T~btMO`bwX1Bl6=Sjvq3juTyMvRNLFy&l0Y47B7wdWlc;a$7@V zeTtl73n~U+wikMK252Q;9wKfWL*qNZNi#nqg@kJo9vN0~)>er%K~!r|4viHC5%SLo zD?@A30=|_X81<}S6yx^kPB3-#!EiJCL>^%atYGcPN$(7_qRq14??CgQW2}M8WJ?;^ zK!1%`%(d)Sjo+yO5aORWF8_9k{$Gp8e^@`DJ@{Vr^K!^w{&VIq40=CJREu<6-Bx-~`#DIaq zPPFl-@fJWR&zDol7#JV|qi35%D z?d%9@YFQq_L=AopYC8ix*y=78K@zqHRkEj1FUjOwM+cwfl4HZb9JF5sur!Wt!OKR& zlRvCTs#?`ptzkb!?^K{VmD;|RR7-fv!_!OTKR$&D+Lp5Jj#;LTUJ3DJ8IYb{_gILz z%gW$J= zXUI}u>fA`H4~xBGM>^Bctdj^D;@G5>WS0iSb{}WFfxc)*yOV7oEn1B?W*u^C1Pn5L zE`LWblLWTO^ZM#l&NBm3)*^X`1_W={5F}?uks(v{27`yJ2&>J6@)__L*((eRT0)+s zACh*D3Sp82M0Xzxbk?}Fq@%XlPbp-u+<6`cC56O`%B#D-wiNmY2IdtqurQ;f>m71TfZS4PR~Oa=HX*m@G5ooM@+F zRB$m)~)zL^^Y1RZ^3bc61QI z0><~paLFaA^HJk$+WP(CT7`21QMeVFXj!XS2O>3Uvq)lO@nZ|(G&15ViWkzBIVK{d z0vnXdOR{I0Fs9b zc2z}xq^H?#1f6?OtFbPB4RG(qYDy}acoy&SVhtH%>PbR;N$n&b*3cC6ggTVYNWp}a zBzN{(EH0!72S+_j)%lxvh?!gc1>17t*eyi|Xcq(pbZY=!VcTQj=F5*U?dV(ixfx;3 z326zmHua*V`VyVB`-PhY&0vIacvp`)w~i2esO8(4#`W{@?=EA{5D684F4hzK&Q3}^SgeC!rhr?zo=&5Dj_ zRoC4mllN(;*t&Mg{`Q({;M^%!Xi!@tpGza2<^qVZRY&!^XVe##_fh^R{M zs~bl+RU+0Ry(aw*5y1e>NEV!iS72lwNHb!-9C+-&!W-aD+!Bx9l=aIg(g4o|9cRrJ%U`3{7G*$dyMEHUrJ*4r7L`yC$gm6V1fdwIVEp�Ua>bx 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

0?OHVz9VA0VGpf&kpun1 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

Cfl2*u&`AhC0dFC#e}=Y^q;R3iIc7UbE}x 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 - + + + + + + + - - - - - + + + @@ -23,9 +27,9 @@ - + - + @@ -37,9 +41,9 @@ - + - + @@ -78,11 +82,18 @@ - + + + + + + + + diff --git a/pom.xml b/pom.xml index bf7853a2..24547bbc 100644 --- a/pom.xml +++ b/pom.xml @@ -35,7 +35,6 @@ 20090211 1.9.12 4.11 - 1.5 2.3.1 @@ -67,33 +66,24 @@ ${junit.version} test - - com.sun.jersey - jersey-client - ${jersey.version} - test - - src - unittest + ${project.basedir}/target + ${project.build.directory}/classes + ${project.artifactId}-${project.version} + ${project.build.directory}/test-classes + ${project.basedir}/src/main/java + src/main/scripts + ${project.basedir}/src/test/java - com/esri/core/geometry - src/com/esri/core/geometry - - *.txt - + ${project.basedir}/src/main/resources - com/esri/core/geometry - unittest/com/esri/core/geometry - - *.txt - + ${project.basedir}/src/test/resources diff --git a/src/com/esri/core/geometry/AttributeStreamBase.java b/src/main/java/com/esri/core/geometry/AttributeStreamBase.java similarity index 100% rename from src/com/esri/core/geometry/AttributeStreamBase.java rename to src/main/java/com/esri/core/geometry/AttributeStreamBase.java diff --git a/src/com/esri/core/geometry/AttributeStreamOfDbl.java b/src/main/java/com/esri/core/geometry/AttributeStreamOfDbl.java similarity index 100% rename from src/com/esri/core/geometry/AttributeStreamOfDbl.java rename to src/main/java/com/esri/core/geometry/AttributeStreamOfDbl.java diff --git a/src/com/esri/core/geometry/AttributeStreamOfFloat.java b/src/main/java/com/esri/core/geometry/AttributeStreamOfFloat.java similarity index 100% rename from src/com/esri/core/geometry/AttributeStreamOfFloat.java rename to src/main/java/com/esri/core/geometry/AttributeStreamOfFloat.java diff --git a/src/com/esri/core/geometry/AttributeStreamOfInt16.java b/src/main/java/com/esri/core/geometry/AttributeStreamOfInt16.java similarity index 100% rename from src/com/esri/core/geometry/AttributeStreamOfInt16.java rename to src/main/java/com/esri/core/geometry/AttributeStreamOfInt16.java diff --git a/src/com/esri/core/geometry/AttributeStreamOfInt32.java b/src/main/java/com/esri/core/geometry/AttributeStreamOfInt32.java similarity index 100% rename from src/com/esri/core/geometry/AttributeStreamOfInt32.java rename to src/main/java/com/esri/core/geometry/AttributeStreamOfInt32.java diff --git a/src/com/esri/core/geometry/AttributeStreamOfInt64.java b/src/main/java/com/esri/core/geometry/AttributeStreamOfInt64.java similarity index 100% rename from src/com/esri/core/geometry/AttributeStreamOfInt64.java rename to src/main/java/com/esri/core/geometry/AttributeStreamOfInt64.java diff --git a/src/com/esri/core/geometry/AttributeStreamOfInt8.java b/src/main/java/com/esri/core/geometry/AttributeStreamOfInt8.java similarity index 100% rename from src/com/esri/core/geometry/AttributeStreamOfInt8.java rename to src/main/java/com/esri/core/geometry/AttributeStreamOfInt8.java diff --git a/src/com/esri/core/geometry/Boundary.java b/src/main/java/com/esri/core/geometry/Boundary.java similarity index 100% rename from src/com/esri/core/geometry/Boundary.java rename to src/main/java/com/esri/core/geometry/Boundary.java diff --git a/src/com/esri/core/geometry/BucketSort.java b/src/main/java/com/esri/core/geometry/BucketSort.java similarity index 100% rename from src/com/esri/core/geometry/BucketSort.java rename to src/main/java/com/esri/core/geometry/BucketSort.java diff --git a/src/com/esri/core/geometry/Bufferer.java b/src/main/java/com/esri/core/geometry/Bufferer.java similarity index 100% rename from src/com/esri/core/geometry/Bufferer.java rename to src/main/java/com/esri/core/geometry/Bufferer.java diff --git a/src/com/esri/core/geometry/ByteBufferCursor.java b/src/main/java/com/esri/core/geometry/ByteBufferCursor.java similarity index 100% rename from src/com/esri/core/geometry/ByteBufferCursor.java rename to src/main/java/com/esri/core/geometry/ByteBufferCursor.java diff --git a/src/com/esri/core/geometry/ClassicSort.java b/src/main/java/com/esri/core/geometry/ClassicSort.java similarity index 100% rename from src/com/esri/core/geometry/ClassicSort.java rename to src/main/java/com/esri/core/geometry/ClassicSort.java diff --git a/src/com/esri/core/geometry/Clipper.java b/src/main/java/com/esri/core/geometry/Clipper.java similarity index 100% rename from src/com/esri/core/geometry/Clipper.java rename to src/main/java/com/esri/core/geometry/Clipper.java diff --git a/src/com/esri/core/geometry/Clusterer.java b/src/main/java/com/esri/core/geometry/Clusterer.java similarity index 100% rename from src/com/esri/core/geometry/Clusterer.java rename to src/main/java/com/esri/core/geometry/Clusterer.java diff --git a/src/com/esri/core/geometry/ConstructOffset.java b/src/main/java/com/esri/core/geometry/ConstructOffset.java similarity index 100% rename from src/com/esri/core/geometry/ConstructOffset.java rename to src/main/java/com/esri/core/geometry/ConstructOffset.java diff --git a/src/com/esri/core/geometry/ConvexHull.java b/src/main/java/com/esri/core/geometry/ConvexHull.java similarity index 100% rename from src/com/esri/core/geometry/ConvexHull.java rename to src/main/java/com/esri/core/geometry/ConvexHull.java diff --git a/src/com/esri/core/geometry/CrackAndCluster.java b/src/main/java/com/esri/core/geometry/CrackAndCluster.java similarity index 100% rename from src/com/esri/core/geometry/CrackAndCluster.java rename to src/main/java/com/esri/core/geometry/CrackAndCluster.java diff --git a/src/com/esri/core/geometry/Cracker.java b/src/main/java/com/esri/core/geometry/Cracker.java similarity index 100% rename from src/com/esri/core/geometry/Cracker.java rename to src/main/java/com/esri/core/geometry/Cracker.java diff --git a/src/com/esri/core/geometry/Cutter.java b/src/main/java/com/esri/core/geometry/Cutter.java similarity index 100% rename from src/com/esri/core/geometry/Cutter.java rename to src/main/java/com/esri/core/geometry/Cutter.java diff --git a/src/com/esri/core/geometry/DirtyFlags.java b/src/main/java/com/esri/core/geometry/DirtyFlags.java similarity index 100% rename from src/com/esri/core/geometry/DirtyFlags.java rename to src/main/java/com/esri/core/geometry/DirtyFlags.java diff --git a/src/com/esri/core/geometry/ECoordinate.java b/src/main/java/com/esri/core/geometry/ECoordinate.java similarity index 100% rename from src/com/esri/core/geometry/ECoordinate.java rename to src/main/java/com/esri/core/geometry/ECoordinate.java diff --git a/src/com/esri/core/geometry/EditShape.java b/src/main/java/com/esri/core/geometry/EditShape.java similarity index 100% rename from src/com/esri/core/geometry/EditShape.java rename to src/main/java/com/esri/core/geometry/EditShape.java diff --git a/src/com/esri/core/geometry/Envelope.java b/src/main/java/com/esri/core/geometry/Envelope.java similarity index 100% rename from src/com/esri/core/geometry/Envelope.java rename to src/main/java/com/esri/core/geometry/Envelope.java diff --git a/src/com/esri/core/geometry/Envelope1D.java b/src/main/java/com/esri/core/geometry/Envelope1D.java similarity index 100% rename from src/com/esri/core/geometry/Envelope1D.java rename to src/main/java/com/esri/core/geometry/Envelope1D.java diff --git a/src/com/esri/core/geometry/Envelope2D.java b/src/main/java/com/esri/core/geometry/Envelope2D.java similarity index 100% rename from src/com/esri/core/geometry/Envelope2D.java rename to src/main/java/com/esri/core/geometry/Envelope2D.java diff --git a/src/com/esri/core/geometry/Envelope2DIntersectorImpl.java b/src/main/java/com/esri/core/geometry/Envelope2DIntersectorImpl.java similarity index 100% rename from src/com/esri/core/geometry/Envelope2DIntersectorImpl.java rename to src/main/java/com/esri/core/geometry/Envelope2DIntersectorImpl.java diff --git a/src/com/esri/core/geometry/Envelope3D.java b/src/main/java/com/esri/core/geometry/Envelope3D.java similarity index 100% rename from src/com/esri/core/geometry/Envelope3D.java rename to src/main/java/com/esri/core/geometry/Envelope3D.java diff --git a/src/com/esri/core/geometry/GeoDist.java b/src/main/java/com/esri/core/geometry/GeoDist.java similarity index 100% rename from src/com/esri/core/geometry/GeoDist.java rename to src/main/java/com/esri/core/geometry/GeoDist.java diff --git a/src/com/esri/core/geometry/GeoJsonExportFlags.java b/src/main/java/com/esri/core/geometry/GeoJsonExportFlags.java similarity index 100% rename from src/com/esri/core/geometry/GeoJsonExportFlags.java rename to src/main/java/com/esri/core/geometry/GeoJsonExportFlags.java diff --git a/src/com/esri/core/geometry/GeoJsonImportFlags.java b/src/main/java/com/esri/core/geometry/GeoJsonImportFlags.java similarity index 100% rename from src/com/esri/core/geometry/GeoJsonImportFlags.java rename to src/main/java/com/esri/core/geometry/GeoJsonImportFlags.java diff --git a/src/com/esri/core/geometry/GeodeticCurveType.java b/src/main/java/com/esri/core/geometry/GeodeticCurveType.java similarity index 100% rename from src/com/esri/core/geometry/GeodeticCurveType.java rename to src/main/java/com/esri/core/geometry/GeodeticCurveType.java diff --git a/src/com/esri/core/geometry/Geometry.java b/src/main/java/com/esri/core/geometry/Geometry.java similarity index 100% rename from src/com/esri/core/geometry/Geometry.java rename to src/main/java/com/esri/core/geometry/Geometry.java diff --git a/src/com/esri/core/geometry/GeometryAccelerators.java b/src/main/java/com/esri/core/geometry/GeometryAccelerators.java similarity index 100% rename from src/com/esri/core/geometry/GeometryAccelerators.java rename to src/main/java/com/esri/core/geometry/GeometryAccelerators.java diff --git a/src/com/esri/core/geometry/GeometryCursor.java b/src/main/java/com/esri/core/geometry/GeometryCursor.java similarity index 100% rename from src/com/esri/core/geometry/GeometryCursor.java rename to src/main/java/com/esri/core/geometry/GeometryCursor.java diff --git a/src/com/esri/core/geometry/GeometryCursorAppend.java b/src/main/java/com/esri/core/geometry/GeometryCursorAppend.java similarity index 100% rename from src/com/esri/core/geometry/GeometryCursorAppend.java rename to src/main/java/com/esri/core/geometry/GeometryCursorAppend.java diff --git a/src/com/esri/core/geometry/GeometryEngine.java b/src/main/java/com/esri/core/geometry/GeometryEngine.java similarity index 100% rename from src/com/esri/core/geometry/GeometryEngine.java rename to src/main/java/com/esri/core/geometry/GeometryEngine.java diff --git a/src/com/esri/core/geometry/GeometryException.java b/src/main/java/com/esri/core/geometry/GeometryException.java similarity index 100% rename from src/com/esri/core/geometry/GeometryException.java rename to src/main/java/com/esri/core/geometry/GeometryException.java diff --git a/src/com/esri/core/geometry/GeometrySerializer.java b/src/main/java/com/esri/core/geometry/GeometrySerializer.java similarity index 100% rename from src/com/esri/core/geometry/GeometrySerializer.java rename to src/main/java/com/esri/core/geometry/GeometrySerializer.java diff --git a/src/com/esri/core/geometry/IndexHashTable.java b/src/main/java/com/esri/core/geometry/IndexHashTable.java similarity index 100% rename from src/com/esri/core/geometry/IndexHashTable.java rename to src/main/java/com/esri/core/geometry/IndexHashTable.java diff --git a/src/com/esri/core/geometry/IndexMultiDCList.java b/src/main/java/com/esri/core/geometry/IndexMultiDCList.java similarity index 100% rename from src/com/esri/core/geometry/IndexMultiDCList.java rename to src/main/java/com/esri/core/geometry/IndexMultiDCList.java diff --git a/src/com/esri/core/geometry/IndexMultiList.java b/src/main/java/com/esri/core/geometry/IndexMultiList.java similarity index 100% rename from src/com/esri/core/geometry/IndexMultiList.java rename to src/main/java/com/esri/core/geometry/IndexMultiList.java diff --git a/src/com/esri/core/geometry/InternalUtils.java b/src/main/java/com/esri/core/geometry/InternalUtils.java similarity index 100% rename from src/com/esri/core/geometry/InternalUtils.java rename to src/main/java/com/esri/core/geometry/InternalUtils.java diff --git a/src/com/esri/core/geometry/Interop.java b/src/main/java/com/esri/core/geometry/Interop.java similarity index 100% rename from src/com/esri/core/geometry/Interop.java rename to src/main/java/com/esri/core/geometry/Interop.java diff --git a/src/com/esri/core/geometry/IntervalTreeImpl.java b/src/main/java/com/esri/core/geometry/IntervalTreeImpl.java similarity index 100% rename from src/com/esri/core/geometry/IntervalTreeImpl.java rename to src/main/java/com/esri/core/geometry/IntervalTreeImpl.java diff --git a/src/com/esri/core/geometry/JSONArrayEnumerator.java b/src/main/java/com/esri/core/geometry/JSONArrayEnumerator.java similarity index 100% rename from src/com/esri/core/geometry/JSONArrayEnumerator.java rename to src/main/java/com/esri/core/geometry/JSONArrayEnumerator.java diff --git a/src/com/esri/core/geometry/JSONObjectEnumerator.java b/src/main/java/com/esri/core/geometry/JSONObjectEnumerator.java similarity index 100% rename from src/com/esri/core/geometry/JSONObjectEnumerator.java rename to src/main/java/com/esri/core/geometry/JSONObjectEnumerator.java diff --git a/src/com/esri/core/geometry/JSONUtils.java b/src/main/java/com/esri/core/geometry/JSONUtils.java similarity index 100% rename from src/com/esri/core/geometry/JSONUtils.java rename to src/main/java/com/esri/core/geometry/JSONUtils.java diff --git a/src/com/esri/core/geometry/JsonCursor.java b/src/main/java/com/esri/core/geometry/JsonCursor.java similarity index 100% rename from src/com/esri/core/geometry/JsonCursor.java rename to src/main/java/com/esri/core/geometry/JsonCursor.java diff --git a/src/com/esri/core/geometry/JsonParserCursor.java b/src/main/java/com/esri/core/geometry/JsonParserCursor.java similarity index 100% rename from src/com/esri/core/geometry/JsonParserCursor.java rename to src/main/java/com/esri/core/geometry/JsonParserCursor.java diff --git a/src/com/esri/core/geometry/JsonParserReader.java b/src/main/java/com/esri/core/geometry/JsonParserReader.java similarity index 100% rename from src/com/esri/core/geometry/JsonParserReader.java rename to src/main/java/com/esri/core/geometry/JsonParserReader.java diff --git a/src/com/esri/core/geometry/JsonReader.java b/src/main/java/com/esri/core/geometry/JsonReader.java similarity index 100% rename from src/com/esri/core/geometry/JsonReader.java rename to src/main/java/com/esri/core/geometry/JsonReader.java diff --git a/src/com/esri/core/geometry/JsonStringWriter.java b/src/main/java/com/esri/core/geometry/JsonStringWriter.java similarity index 100% rename from src/com/esri/core/geometry/JsonStringWriter.java rename to src/main/java/com/esri/core/geometry/JsonStringWriter.java diff --git a/src/com/esri/core/geometry/JsonValueReader.java b/src/main/java/com/esri/core/geometry/JsonValueReader.java similarity index 100% rename from src/com/esri/core/geometry/JsonValueReader.java rename to src/main/java/com/esri/core/geometry/JsonValueReader.java diff --git a/src/com/esri/core/geometry/JsonWriter.java b/src/main/java/com/esri/core/geometry/JsonWriter.java similarity index 100% rename from src/com/esri/core/geometry/JsonWriter.java rename to src/main/java/com/esri/core/geometry/JsonWriter.java diff --git a/src/com/esri/core/geometry/Line.java b/src/main/java/com/esri/core/geometry/Line.java similarity index 100% rename from src/com/esri/core/geometry/Line.java rename to src/main/java/com/esri/core/geometry/Line.java diff --git a/src/com/esri/core/geometry/ListeningGeometryCursor.java b/src/main/java/com/esri/core/geometry/ListeningGeometryCursor.java similarity index 100% rename from src/com/esri/core/geometry/ListeningGeometryCursor.java rename to src/main/java/com/esri/core/geometry/ListeningGeometryCursor.java diff --git a/src/com/esri/core/geometry/MapGeometry.java b/src/main/java/com/esri/core/geometry/MapGeometry.java similarity index 100% rename from src/com/esri/core/geometry/MapGeometry.java rename to src/main/java/com/esri/core/geometry/MapGeometry.java diff --git a/src/com/esri/core/geometry/MapGeometryCursor.java b/src/main/java/com/esri/core/geometry/MapGeometryCursor.java similarity index 100% rename from src/com/esri/core/geometry/MapGeometryCursor.java rename to src/main/java/com/esri/core/geometry/MapGeometryCursor.java diff --git a/src/com/esri/core/geometry/MapOGCStructure.java b/src/main/java/com/esri/core/geometry/MapOGCStructure.java similarity index 100% rename from src/com/esri/core/geometry/MapOGCStructure.java rename to src/main/java/com/esri/core/geometry/MapOGCStructure.java diff --git a/src/com/esri/core/geometry/MathUtils.java b/src/main/java/com/esri/core/geometry/MathUtils.java similarity index 100% rename from src/com/esri/core/geometry/MathUtils.java rename to src/main/java/com/esri/core/geometry/MathUtils.java diff --git a/src/com/esri/core/geometry/MgrsConversionMode.java b/src/main/java/com/esri/core/geometry/MgrsConversionMode.java similarity index 100% rename from src/com/esri/core/geometry/MgrsConversionMode.java rename to src/main/java/com/esri/core/geometry/MgrsConversionMode.java diff --git a/src/com/esri/core/geometry/MultiPath.java b/src/main/java/com/esri/core/geometry/MultiPath.java similarity index 100% rename from src/com/esri/core/geometry/MultiPath.java rename to src/main/java/com/esri/core/geometry/MultiPath.java diff --git a/src/com/esri/core/geometry/MultiPathImpl.java b/src/main/java/com/esri/core/geometry/MultiPathImpl.java similarity index 100% rename from src/com/esri/core/geometry/MultiPathImpl.java rename to src/main/java/com/esri/core/geometry/MultiPathImpl.java diff --git a/src/com/esri/core/geometry/MultiPoint.java b/src/main/java/com/esri/core/geometry/MultiPoint.java similarity index 100% rename from src/com/esri/core/geometry/MultiPoint.java rename to src/main/java/com/esri/core/geometry/MultiPoint.java diff --git a/src/com/esri/core/geometry/MultiPointImpl.java b/src/main/java/com/esri/core/geometry/MultiPointImpl.java similarity index 100% rename from src/com/esri/core/geometry/MultiPointImpl.java rename to src/main/java/com/esri/core/geometry/MultiPointImpl.java diff --git a/src/com/esri/core/geometry/MultiVertexGeometry.java b/src/main/java/com/esri/core/geometry/MultiVertexGeometry.java similarity index 100% rename from src/com/esri/core/geometry/MultiVertexGeometry.java rename to src/main/java/com/esri/core/geometry/MultiVertexGeometry.java diff --git a/src/com/esri/core/geometry/MultiVertexGeometryImpl.java b/src/main/java/com/esri/core/geometry/MultiVertexGeometryImpl.java similarity index 100% rename from src/com/esri/core/geometry/MultiVertexGeometryImpl.java rename to src/main/java/com/esri/core/geometry/MultiVertexGeometryImpl.java diff --git a/src/com/esri/core/geometry/NonSimpleResult.java b/src/main/java/com/esri/core/geometry/NonSimpleResult.java similarity index 100% rename from src/com/esri/core/geometry/NonSimpleResult.java rename to src/main/java/com/esri/core/geometry/NonSimpleResult.java diff --git a/src/com/esri/core/geometry/NumberUtils.java b/src/main/java/com/esri/core/geometry/NumberUtils.java similarity index 100% rename from src/com/esri/core/geometry/NumberUtils.java rename to src/main/java/com/esri/core/geometry/NumberUtils.java diff --git a/src/com/esri/core/geometry/OGCStructure.java b/src/main/java/com/esri/core/geometry/OGCStructure.java similarity index 100% rename from src/com/esri/core/geometry/OGCStructure.java rename to src/main/java/com/esri/core/geometry/OGCStructure.java diff --git a/src/com/esri/core/geometry/ObjectCacheTable.java b/src/main/java/com/esri/core/geometry/ObjectCacheTable.java similarity index 100% rename from src/com/esri/core/geometry/ObjectCacheTable.java rename to src/main/java/com/esri/core/geometry/ObjectCacheTable.java diff --git a/src/com/esri/core/geometry/Operator.java b/src/main/java/com/esri/core/geometry/Operator.java similarity index 100% rename from src/com/esri/core/geometry/Operator.java rename to src/main/java/com/esri/core/geometry/Operator.java diff --git a/src/com/esri/core/geometry/OperatorBoundary.java b/src/main/java/com/esri/core/geometry/OperatorBoundary.java similarity index 100% rename from src/com/esri/core/geometry/OperatorBoundary.java rename to src/main/java/com/esri/core/geometry/OperatorBoundary.java diff --git a/src/com/esri/core/geometry/OperatorBoundaryLocal.java b/src/main/java/com/esri/core/geometry/OperatorBoundaryLocal.java similarity index 100% rename from src/com/esri/core/geometry/OperatorBoundaryLocal.java rename to src/main/java/com/esri/core/geometry/OperatorBoundaryLocal.java diff --git a/src/com/esri/core/geometry/OperatorBoundaryLocalCursor.java b/src/main/java/com/esri/core/geometry/OperatorBoundaryLocalCursor.java similarity index 100% rename from src/com/esri/core/geometry/OperatorBoundaryLocalCursor.java rename to src/main/java/com/esri/core/geometry/OperatorBoundaryLocalCursor.java diff --git a/src/com/esri/core/geometry/OperatorBuffer.java b/src/main/java/com/esri/core/geometry/OperatorBuffer.java similarity index 100% rename from src/com/esri/core/geometry/OperatorBuffer.java rename to src/main/java/com/esri/core/geometry/OperatorBuffer.java diff --git a/src/com/esri/core/geometry/OperatorBufferCursor.java b/src/main/java/com/esri/core/geometry/OperatorBufferCursor.java similarity index 100% rename from src/com/esri/core/geometry/OperatorBufferCursor.java rename to src/main/java/com/esri/core/geometry/OperatorBufferCursor.java diff --git a/src/com/esri/core/geometry/OperatorBufferLocal.java b/src/main/java/com/esri/core/geometry/OperatorBufferLocal.java similarity index 100% rename from src/com/esri/core/geometry/OperatorBufferLocal.java rename to src/main/java/com/esri/core/geometry/OperatorBufferLocal.java diff --git a/src/com/esri/core/geometry/OperatorClip.java b/src/main/java/com/esri/core/geometry/OperatorClip.java similarity index 100% rename from src/com/esri/core/geometry/OperatorClip.java rename to src/main/java/com/esri/core/geometry/OperatorClip.java diff --git a/src/com/esri/core/geometry/OperatorClipCursor.java b/src/main/java/com/esri/core/geometry/OperatorClipCursor.java similarity index 100% rename from src/com/esri/core/geometry/OperatorClipCursor.java rename to src/main/java/com/esri/core/geometry/OperatorClipCursor.java diff --git a/src/com/esri/core/geometry/OperatorClipLocal.java b/src/main/java/com/esri/core/geometry/OperatorClipLocal.java similarity index 100% rename from src/com/esri/core/geometry/OperatorClipLocal.java rename to src/main/java/com/esri/core/geometry/OperatorClipLocal.java diff --git a/src/com/esri/core/geometry/OperatorContains.java b/src/main/java/com/esri/core/geometry/OperatorContains.java similarity index 100% rename from src/com/esri/core/geometry/OperatorContains.java rename to src/main/java/com/esri/core/geometry/OperatorContains.java diff --git a/src/com/esri/core/geometry/OperatorContainsLocal.java b/src/main/java/com/esri/core/geometry/OperatorContainsLocal.java similarity index 100% rename from src/com/esri/core/geometry/OperatorContainsLocal.java rename to src/main/java/com/esri/core/geometry/OperatorContainsLocal.java diff --git a/src/com/esri/core/geometry/OperatorConvexHull.java b/src/main/java/com/esri/core/geometry/OperatorConvexHull.java similarity index 100% rename from src/com/esri/core/geometry/OperatorConvexHull.java rename to src/main/java/com/esri/core/geometry/OperatorConvexHull.java diff --git a/src/com/esri/core/geometry/OperatorConvexHullCursor.java b/src/main/java/com/esri/core/geometry/OperatorConvexHullCursor.java similarity index 100% rename from src/com/esri/core/geometry/OperatorConvexHullCursor.java rename to src/main/java/com/esri/core/geometry/OperatorConvexHullCursor.java diff --git a/src/com/esri/core/geometry/OperatorConvexHullLocal.java b/src/main/java/com/esri/core/geometry/OperatorConvexHullLocal.java similarity index 100% rename from src/com/esri/core/geometry/OperatorConvexHullLocal.java rename to src/main/java/com/esri/core/geometry/OperatorConvexHullLocal.java diff --git a/src/com/esri/core/geometry/OperatorCrosses.java b/src/main/java/com/esri/core/geometry/OperatorCrosses.java similarity index 100% rename from src/com/esri/core/geometry/OperatorCrosses.java rename to src/main/java/com/esri/core/geometry/OperatorCrosses.java diff --git a/src/com/esri/core/geometry/OperatorCrossesLocal.java b/src/main/java/com/esri/core/geometry/OperatorCrossesLocal.java similarity index 100% rename from src/com/esri/core/geometry/OperatorCrossesLocal.java rename to src/main/java/com/esri/core/geometry/OperatorCrossesLocal.java diff --git a/src/com/esri/core/geometry/OperatorCut.java b/src/main/java/com/esri/core/geometry/OperatorCut.java similarity index 100% rename from src/com/esri/core/geometry/OperatorCut.java rename to src/main/java/com/esri/core/geometry/OperatorCut.java diff --git a/src/com/esri/core/geometry/OperatorCutCursor.java b/src/main/java/com/esri/core/geometry/OperatorCutCursor.java similarity index 100% rename from src/com/esri/core/geometry/OperatorCutCursor.java rename to src/main/java/com/esri/core/geometry/OperatorCutCursor.java diff --git a/src/com/esri/core/geometry/OperatorCutLocal.java b/src/main/java/com/esri/core/geometry/OperatorCutLocal.java similarity index 100% rename from src/com/esri/core/geometry/OperatorCutLocal.java rename to src/main/java/com/esri/core/geometry/OperatorCutLocal.java diff --git a/src/com/esri/core/geometry/OperatorDensifyByLength.java b/src/main/java/com/esri/core/geometry/OperatorDensifyByLength.java similarity index 100% rename from src/com/esri/core/geometry/OperatorDensifyByLength.java rename to src/main/java/com/esri/core/geometry/OperatorDensifyByLength.java diff --git a/src/com/esri/core/geometry/OperatorDensifyByLengthCursor.java b/src/main/java/com/esri/core/geometry/OperatorDensifyByLengthCursor.java similarity index 100% rename from src/com/esri/core/geometry/OperatorDensifyByLengthCursor.java rename to src/main/java/com/esri/core/geometry/OperatorDensifyByLengthCursor.java diff --git a/src/com/esri/core/geometry/OperatorDensifyByLengthLocal.java b/src/main/java/com/esri/core/geometry/OperatorDensifyByLengthLocal.java similarity index 100% rename from src/com/esri/core/geometry/OperatorDensifyByLengthLocal.java rename to src/main/java/com/esri/core/geometry/OperatorDensifyByLengthLocal.java diff --git a/src/com/esri/core/geometry/OperatorDifference.java b/src/main/java/com/esri/core/geometry/OperatorDifference.java similarity index 100% rename from src/com/esri/core/geometry/OperatorDifference.java rename to src/main/java/com/esri/core/geometry/OperatorDifference.java diff --git a/src/com/esri/core/geometry/OperatorDifferenceCursor.java b/src/main/java/com/esri/core/geometry/OperatorDifferenceCursor.java similarity index 100% rename from src/com/esri/core/geometry/OperatorDifferenceCursor.java rename to src/main/java/com/esri/core/geometry/OperatorDifferenceCursor.java diff --git a/src/com/esri/core/geometry/OperatorDifferenceLocal.java b/src/main/java/com/esri/core/geometry/OperatorDifferenceLocal.java similarity index 100% rename from src/com/esri/core/geometry/OperatorDifferenceLocal.java rename to src/main/java/com/esri/core/geometry/OperatorDifferenceLocal.java diff --git a/src/com/esri/core/geometry/OperatorDisjoint.java b/src/main/java/com/esri/core/geometry/OperatorDisjoint.java similarity index 100% rename from src/com/esri/core/geometry/OperatorDisjoint.java rename to src/main/java/com/esri/core/geometry/OperatorDisjoint.java diff --git a/src/com/esri/core/geometry/OperatorDisjointLocal.java b/src/main/java/com/esri/core/geometry/OperatorDisjointLocal.java similarity index 100% rename from src/com/esri/core/geometry/OperatorDisjointLocal.java rename to src/main/java/com/esri/core/geometry/OperatorDisjointLocal.java diff --git a/src/com/esri/core/geometry/OperatorDistance.java b/src/main/java/com/esri/core/geometry/OperatorDistance.java similarity index 100% rename from src/com/esri/core/geometry/OperatorDistance.java rename to src/main/java/com/esri/core/geometry/OperatorDistance.java diff --git a/src/com/esri/core/geometry/OperatorDistanceLocal.java b/src/main/java/com/esri/core/geometry/OperatorDistanceLocal.java similarity index 100% rename from src/com/esri/core/geometry/OperatorDistanceLocal.java rename to src/main/java/com/esri/core/geometry/OperatorDistanceLocal.java diff --git a/src/com/esri/core/geometry/OperatorEquals.java b/src/main/java/com/esri/core/geometry/OperatorEquals.java similarity index 100% rename from src/com/esri/core/geometry/OperatorEquals.java rename to src/main/java/com/esri/core/geometry/OperatorEquals.java diff --git a/src/com/esri/core/geometry/OperatorEqualsLocal.java b/src/main/java/com/esri/core/geometry/OperatorEqualsLocal.java similarity index 100% rename from src/com/esri/core/geometry/OperatorEqualsLocal.java rename to src/main/java/com/esri/core/geometry/OperatorEqualsLocal.java diff --git a/src/com/esri/core/geometry/OperatorExportToESRIShape.java b/src/main/java/com/esri/core/geometry/OperatorExportToESRIShape.java similarity index 100% rename from src/com/esri/core/geometry/OperatorExportToESRIShape.java rename to src/main/java/com/esri/core/geometry/OperatorExportToESRIShape.java diff --git a/src/com/esri/core/geometry/OperatorExportToESRIShapeCursor.java b/src/main/java/com/esri/core/geometry/OperatorExportToESRIShapeCursor.java similarity index 100% rename from src/com/esri/core/geometry/OperatorExportToESRIShapeCursor.java rename to src/main/java/com/esri/core/geometry/OperatorExportToESRIShapeCursor.java diff --git a/src/com/esri/core/geometry/OperatorExportToESRIShapeLocal.java b/src/main/java/com/esri/core/geometry/OperatorExportToESRIShapeLocal.java similarity index 100% rename from src/com/esri/core/geometry/OperatorExportToESRIShapeLocal.java rename to src/main/java/com/esri/core/geometry/OperatorExportToESRIShapeLocal.java diff --git a/src/com/esri/core/geometry/OperatorExportToGeoJson.java b/src/main/java/com/esri/core/geometry/OperatorExportToGeoJson.java similarity index 100% rename from src/com/esri/core/geometry/OperatorExportToGeoJson.java rename to src/main/java/com/esri/core/geometry/OperatorExportToGeoJson.java diff --git a/src/com/esri/core/geometry/OperatorExportToGeoJsonCursor.java b/src/main/java/com/esri/core/geometry/OperatorExportToGeoJsonCursor.java similarity index 100% rename from src/com/esri/core/geometry/OperatorExportToGeoJsonCursor.java rename to src/main/java/com/esri/core/geometry/OperatorExportToGeoJsonCursor.java diff --git a/src/com/esri/core/geometry/OperatorExportToGeoJsonLocal.java b/src/main/java/com/esri/core/geometry/OperatorExportToGeoJsonLocal.java similarity index 100% rename from src/com/esri/core/geometry/OperatorExportToGeoJsonLocal.java rename to src/main/java/com/esri/core/geometry/OperatorExportToGeoJsonLocal.java diff --git a/src/com/esri/core/geometry/OperatorExportToJson.java b/src/main/java/com/esri/core/geometry/OperatorExportToJson.java similarity index 100% rename from src/com/esri/core/geometry/OperatorExportToJson.java rename to src/main/java/com/esri/core/geometry/OperatorExportToJson.java diff --git a/src/com/esri/core/geometry/OperatorExportToJsonCursor.java b/src/main/java/com/esri/core/geometry/OperatorExportToJsonCursor.java similarity index 100% rename from src/com/esri/core/geometry/OperatorExportToJsonCursor.java rename to src/main/java/com/esri/core/geometry/OperatorExportToJsonCursor.java diff --git a/src/com/esri/core/geometry/OperatorExportToJsonLocal.java b/src/main/java/com/esri/core/geometry/OperatorExportToJsonLocal.java similarity index 100% rename from src/com/esri/core/geometry/OperatorExportToJsonLocal.java rename to src/main/java/com/esri/core/geometry/OperatorExportToJsonLocal.java diff --git a/src/com/esri/core/geometry/OperatorExportToWkb.java b/src/main/java/com/esri/core/geometry/OperatorExportToWkb.java similarity index 100% rename from src/com/esri/core/geometry/OperatorExportToWkb.java rename to src/main/java/com/esri/core/geometry/OperatorExportToWkb.java diff --git a/src/com/esri/core/geometry/OperatorExportToWkbLocal.java b/src/main/java/com/esri/core/geometry/OperatorExportToWkbLocal.java similarity index 100% rename from src/com/esri/core/geometry/OperatorExportToWkbLocal.java rename to src/main/java/com/esri/core/geometry/OperatorExportToWkbLocal.java diff --git a/src/com/esri/core/geometry/OperatorExportToWkt.java b/src/main/java/com/esri/core/geometry/OperatorExportToWkt.java similarity index 100% rename from src/com/esri/core/geometry/OperatorExportToWkt.java rename to src/main/java/com/esri/core/geometry/OperatorExportToWkt.java diff --git a/src/com/esri/core/geometry/OperatorExportToWktLocal.java b/src/main/java/com/esri/core/geometry/OperatorExportToWktLocal.java similarity index 100% rename from src/com/esri/core/geometry/OperatorExportToWktLocal.java rename to src/main/java/com/esri/core/geometry/OperatorExportToWktLocal.java diff --git a/src/com/esri/core/geometry/OperatorFactory.java b/src/main/java/com/esri/core/geometry/OperatorFactory.java similarity index 100% rename from src/com/esri/core/geometry/OperatorFactory.java rename to src/main/java/com/esri/core/geometry/OperatorFactory.java diff --git a/src/com/esri/core/geometry/OperatorFactoryLocal.java b/src/main/java/com/esri/core/geometry/OperatorFactoryLocal.java similarity index 100% rename from src/com/esri/core/geometry/OperatorFactoryLocal.java rename to src/main/java/com/esri/core/geometry/OperatorFactoryLocal.java diff --git a/src/com/esri/core/geometry/OperatorGeneralize.java b/src/main/java/com/esri/core/geometry/OperatorGeneralize.java similarity index 100% rename from src/com/esri/core/geometry/OperatorGeneralize.java rename to src/main/java/com/esri/core/geometry/OperatorGeneralize.java diff --git a/src/com/esri/core/geometry/OperatorGeneralizeCursor.java b/src/main/java/com/esri/core/geometry/OperatorGeneralizeCursor.java similarity index 100% rename from src/com/esri/core/geometry/OperatorGeneralizeCursor.java rename to src/main/java/com/esri/core/geometry/OperatorGeneralizeCursor.java diff --git a/src/com/esri/core/geometry/OperatorGeneralizeLocal.java b/src/main/java/com/esri/core/geometry/OperatorGeneralizeLocal.java similarity index 100% rename from src/com/esri/core/geometry/OperatorGeneralizeLocal.java rename to src/main/java/com/esri/core/geometry/OperatorGeneralizeLocal.java diff --git a/src/com/esri/core/geometry/OperatorGeodeticArea.java b/src/main/java/com/esri/core/geometry/OperatorGeodeticArea.java similarity index 100% rename from src/com/esri/core/geometry/OperatorGeodeticArea.java rename to src/main/java/com/esri/core/geometry/OperatorGeodeticArea.java diff --git a/src/com/esri/core/geometry/OperatorGeodeticAreaLocal.java b/src/main/java/com/esri/core/geometry/OperatorGeodeticAreaLocal.java similarity index 100% rename from src/com/esri/core/geometry/OperatorGeodeticAreaLocal.java rename to src/main/java/com/esri/core/geometry/OperatorGeodeticAreaLocal.java diff --git a/src/com/esri/core/geometry/OperatorGeodeticLength.java b/src/main/java/com/esri/core/geometry/OperatorGeodeticLength.java similarity index 100% rename from src/com/esri/core/geometry/OperatorGeodeticLength.java rename to src/main/java/com/esri/core/geometry/OperatorGeodeticLength.java diff --git a/src/com/esri/core/geometry/OperatorGeodeticLengthLocal.java b/src/main/java/com/esri/core/geometry/OperatorGeodeticLengthLocal.java similarity index 100% rename from src/com/esri/core/geometry/OperatorGeodeticLengthLocal.java rename to src/main/java/com/esri/core/geometry/OperatorGeodeticLengthLocal.java diff --git a/src/com/esri/core/geometry/OperatorImportFromESRIShape.java b/src/main/java/com/esri/core/geometry/OperatorImportFromESRIShape.java similarity index 100% rename from src/com/esri/core/geometry/OperatorImportFromESRIShape.java rename to src/main/java/com/esri/core/geometry/OperatorImportFromESRIShape.java diff --git a/src/com/esri/core/geometry/OperatorImportFromESRIShapeCursor.java b/src/main/java/com/esri/core/geometry/OperatorImportFromESRIShapeCursor.java similarity index 100% rename from src/com/esri/core/geometry/OperatorImportFromESRIShapeCursor.java rename to src/main/java/com/esri/core/geometry/OperatorImportFromESRIShapeCursor.java diff --git a/src/com/esri/core/geometry/OperatorImportFromESRIShapeLocal.java b/src/main/java/com/esri/core/geometry/OperatorImportFromESRIShapeLocal.java similarity index 100% rename from src/com/esri/core/geometry/OperatorImportFromESRIShapeLocal.java rename to src/main/java/com/esri/core/geometry/OperatorImportFromESRIShapeLocal.java diff --git a/src/com/esri/core/geometry/OperatorImportFromGeoJson.java b/src/main/java/com/esri/core/geometry/OperatorImportFromGeoJson.java similarity index 100% rename from src/com/esri/core/geometry/OperatorImportFromGeoJson.java rename to src/main/java/com/esri/core/geometry/OperatorImportFromGeoJson.java diff --git a/src/com/esri/core/geometry/OperatorImportFromGeoJsonLocal.java b/src/main/java/com/esri/core/geometry/OperatorImportFromGeoJsonLocal.java similarity index 100% rename from src/com/esri/core/geometry/OperatorImportFromGeoJsonLocal.java rename to src/main/java/com/esri/core/geometry/OperatorImportFromGeoJsonLocal.java diff --git a/src/com/esri/core/geometry/OperatorImportFromJson.java b/src/main/java/com/esri/core/geometry/OperatorImportFromJson.java similarity index 100% rename from src/com/esri/core/geometry/OperatorImportFromJson.java rename to src/main/java/com/esri/core/geometry/OperatorImportFromJson.java diff --git a/src/com/esri/core/geometry/OperatorImportFromJsonCursor.java b/src/main/java/com/esri/core/geometry/OperatorImportFromJsonCursor.java similarity index 100% rename from src/com/esri/core/geometry/OperatorImportFromJsonCursor.java rename to src/main/java/com/esri/core/geometry/OperatorImportFromJsonCursor.java diff --git a/src/com/esri/core/geometry/OperatorImportFromJsonLocal.java b/src/main/java/com/esri/core/geometry/OperatorImportFromJsonLocal.java similarity index 100% rename from src/com/esri/core/geometry/OperatorImportFromJsonLocal.java rename to src/main/java/com/esri/core/geometry/OperatorImportFromJsonLocal.java diff --git a/src/com/esri/core/geometry/OperatorImportFromWkb.java b/src/main/java/com/esri/core/geometry/OperatorImportFromWkb.java similarity index 100% rename from src/com/esri/core/geometry/OperatorImportFromWkb.java rename to src/main/java/com/esri/core/geometry/OperatorImportFromWkb.java diff --git a/src/com/esri/core/geometry/OperatorImportFromWkbLocal.java b/src/main/java/com/esri/core/geometry/OperatorImportFromWkbLocal.java similarity index 100% rename from src/com/esri/core/geometry/OperatorImportFromWkbLocal.java rename to src/main/java/com/esri/core/geometry/OperatorImportFromWkbLocal.java diff --git a/src/com/esri/core/geometry/OperatorImportFromWkt.java b/src/main/java/com/esri/core/geometry/OperatorImportFromWkt.java similarity index 100% rename from src/com/esri/core/geometry/OperatorImportFromWkt.java rename to src/main/java/com/esri/core/geometry/OperatorImportFromWkt.java diff --git a/src/com/esri/core/geometry/OperatorImportFromWktLocal.java b/src/main/java/com/esri/core/geometry/OperatorImportFromWktLocal.java similarity index 100% rename from src/com/esri/core/geometry/OperatorImportFromWktLocal.java rename to src/main/java/com/esri/core/geometry/OperatorImportFromWktLocal.java diff --git a/src/com/esri/core/geometry/OperatorInternalRelationUtils.java b/src/main/java/com/esri/core/geometry/OperatorInternalRelationUtils.java similarity index 100% rename from src/com/esri/core/geometry/OperatorInternalRelationUtils.java rename to src/main/java/com/esri/core/geometry/OperatorInternalRelationUtils.java diff --git a/src/com/esri/core/geometry/OperatorIntersection.java b/src/main/java/com/esri/core/geometry/OperatorIntersection.java similarity index 100% rename from src/com/esri/core/geometry/OperatorIntersection.java rename to src/main/java/com/esri/core/geometry/OperatorIntersection.java diff --git a/src/com/esri/core/geometry/OperatorIntersectionCursor.java b/src/main/java/com/esri/core/geometry/OperatorIntersectionCursor.java similarity index 100% rename from src/com/esri/core/geometry/OperatorIntersectionCursor.java rename to src/main/java/com/esri/core/geometry/OperatorIntersectionCursor.java diff --git a/src/com/esri/core/geometry/OperatorIntersectionLocal.java b/src/main/java/com/esri/core/geometry/OperatorIntersectionLocal.java similarity index 100% rename from src/com/esri/core/geometry/OperatorIntersectionLocal.java rename to src/main/java/com/esri/core/geometry/OperatorIntersectionLocal.java diff --git a/src/com/esri/core/geometry/OperatorIntersects.java b/src/main/java/com/esri/core/geometry/OperatorIntersects.java similarity index 100% rename from src/com/esri/core/geometry/OperatorIntersects.java rename to src/main/java/com/esri/core/geometry/OperatorIntersects.java diff --git a/src/com/esri/core/geometry/OperatorIntersectsLocal.java b/src/main/java/com/esri/core/geometry/OperatorIntersectsLocal.java similarity index 100% rename from src/com/esri/core/geometry/OperatorIntersectsLocal.java rename to src/main/java/com/esri/core/geometry/OperatorIntersectsLocal.java diff --git a/src/com/esri/core/geometry/OperatorOffset.java b/src/main/java/com/esri/core/geometry/OperatorOffset.java similarity index 100% rename from src/com/esri/core/geometry/OperatorOffset.java rename to src/main/java/com/esri/core/geometry/OperatorOffset.java diff --git a/src/com/esri/core/geometry/OperatorOffsetCursor.java b/src/main/java/com/esri/core/geometry/OperatorOffsetCursor.java similarity index 100% rename from src/com/esri/core/geometry/OperatorOffsetCursor.java rename to src/main/java/com/esri/core/geometry/OperatorOffsetCursor.java diff --git a/src/com/esri/core/geometry/OperatorOffsetLocal.java b/src/main/java/com/esri/core/geometry/OperatorOffsetLocal.java similarity index 100% rename from src/com/esri/core/geometry/OperatorOffsetLocal.java rename to src/main/java/com/esri/core/geometry/OperatorOffsetLocal.java diff --git a/src/com/esri/core/geometry/OperatorOverlaps.java b/src/main/java/com/esri/core/geometry/OperatorOverlaps.java similarity index 100% rename from src/com/esri/core/geometry/OperatorOverlaps.java rename to src/main/java/com/esri/core/geometry/OperatorOverlaps.java diff --git a/src/com/esri/core/geometry/OperatorOverlapsLocal.java b/src/main/java/com/esri/core/geometry/OperatorOverlapsLocal.java similarity index 100% rename from src/com/esri/core/geometry/OperatorOverlapsLocal.java rename to src/main/java/com/esri/core/geometry/OperatorOverlapsLocal.java diff --git a/src/com/esri/core/geometry/OperatorProject.java b/src/main/java/com/esri/core/geometry/OperatorProject.java similarity index 100% rename from src/com/esri/core/geometry/OperatorProject.java rename to src/main/java/com/esri/core/geometry/OperatorProject.java diff --git a/src/com/esri/core/geometry/OperatorProjectLocal.java b/src/main/java/com/esri/core/geometry/OperatorProjectLocal.java similarity index 100% rename from src/com/esri/core/geometry/OperatorProjectLocal.java rename to src/main/java/com/esri/core/geometry/OperatorProjectLocal.java diff --git a/src/com/esri/core/geometry/OperatorProximity2D.java b/src/main/java/com/esri/core/geometry/OperatorProximity2D.java similarity index 100% rename from src/com/esri/core/geometry/OperatorProximity2D.java rename to src/main/java/com/esri/core/geometry/OperatorProximity2D.java diff --git a/src/com/esri/core/geometry/OperatorProximity2DLocal.java b/src/main/java/com/esri/core/geometry/OperatorProximity2DLocal.java similarity index 100% rename from src/com/esri/core/geometry/OperatorProximity2DLocal.java rename to src/main/java/com/esri/core/geometry/OperatorProximity2DLocal.java diff --git a/src/com/esri/core/geometry/OperatorRelate.java b/src/main/java/com/esri/core/geometry/OperatorRelate.java similarity index 100% rename from src/com/esri/core/geometry/OperatorRelate.java rename to src/main/java/com/esri/core/geometry/OperatorRelate.java diff --git a/src/com/esri/core/geometry/OperatorRelateLocal.java b/src/main/java/com/esri/core/geometry/OperatorRelateLocal.java similarity index 100% rename from src/com/esri/core/geometry/OperatorRelateLocal.java rename to src/main/java/com/esri/core/geometry/OperatorRelateLocal.java diff --git a/src/com/esri/core/geometry/OperatorSimpleRelation.java b/src/main/java/com/esri/core/geometry/OperatorSimpleRelation.java similarity index 100% rename from src/com/esri/core/geometry/OperatorSimpleRelation.java rename to src/main/java/com/esri/core/geometry/OperatorSimpleRelation.java diff --git a/src/com/esri/core/geometry/OperatorSimplify.java b/src/main/java/com/esri/core/geometry/OperatorSimplify.java similarity index 100% rename from src/com/esri/core/geometry/OperatorSimplify.java rename to src/main/java/com/esri/core/geometry/OperatorSimplify.java diff --git a/src/com/esri/core/geometry/OperatorSimplifyCursor.java b/src/main/java/com/esri/core/geometry/OperatorSimplifyCursor.java similarity index 100% rename from src/com/esri/core/geometry/OperatorSimplifyCursor.java rename to src/main/java/com/esri/core/geometry/OperatorSimplifyCursor.java diff --git a/src/com/esri/core/geometry/OperatorSimplifyCursorOGC.java b/src/main/java/com/esri/core/geometry/OperatorSimplifyCursorOGC.java similarity index 100% rename from src/com/esri/core/geometry/OperatorSimplifyCursorOGC.java rename to src/main/java/com/esri/core/geometry/OperatorSimplifyCursorOGC.java diff --git a/src/com/esri/core/geometry/OperatorSimplifyLocal.java b/src/main/java/com/esri/core/geometry/OperatorSimplifyLocal.java similarity index 100% rename from src/com/esri/core/geometry/OperatorSimplifyLocal.java rename to src/main/java/com/esri/core/geometry/OperatorSimplifyLocal.java diff --git a/src/com/esri/core/geometry/OperatorSimplifyLocalHelper.java b/src/main/java/com/esri/core/geometry/OperatorSimplifyLocalHelper.java similarity index 100% rename from src/com/esri/core/geometry/OperatorSimplifyLocalHelper.java rename to src/main/java/com/esri/core/geometry/OperatorSimplifyLocalHelper.java diff --git a/src/com/esri/core/geometry/OperatorSimplifyLocalOGC.java b/src/main/java/com/esri/core/geometry/OperatorSimplifyLocalOGC.java similarity index 100% rename from src/com/esri/core/geometry/OperatorSimplifyLocalOGC.java rename to src/main/java/com/esri/core/geometry/OperatorSimplifyLocalOGC.java diff --git a/src/com/esri/core/geometry/OperatorSimplifyOGC.java b/src/main/java/com/esri/core/geometry/OperatorSimplifyOGC.java similarity index 100% rename from src/com/esri/core/geometry/OperatorSimplifyOGC.java rename to src/main/java/com/esri/core/geometry/OperatorSimplifyOGC.java diff --git a/src/com/esri/core/geometry/OperatorSymmetricDifference.java b/src/main/java/com/esri/core/geometry/OperatorSymmetricDifference.java similarity index 100% rename from src/com/esri/core/geometry/OperatorSymmetricDifference.java rename to src/main/java/com/esri/core/geometry/OperatorSymmetricDifference.java diff --git a/src/com/esri/core/geometry/OperatorSymmetricDifferenceCursor.java b/src/main/java/com/esri/core/geometry/OperatorSymmetricDifferenceCursor.java similarity index 100% rename from src/com/esri/core/geometry/OperatorSymmetricDifferenceCursor.java rename to src/main/java/com/esri/core/geometry/OperatorSymmetricDifferenceCursor.java diff --git a/src/com/esri/core/geometry/OperatorSymmetricDifferenceLocal.java b/src/main/java/com/esri/core/geometry/OperatorSymmetricDifferenceLocal.java similarity index 100% rename from src/com/esri/core/geometry/OperatorSymmetricDifferenceLocal.java rename to src/main/java/com/esri/core/geometry/OperatorSymmetricDifferenceLocal.java diff --git a/src/com/esri/core/geometry/OperatorTouches.java b/src/main/java/com/esri/core/geometry/OperatorTouches.java similarity index 100% rename from src/com/esri/core/geometry/OperatorTouches.java rename to src/main/java/com/esri/core/geometry/OperatorTouches.java diff --git a/src/com/esri/core/geometry/OperatorTouchesLocal.java b/src/main/java/com/esri/core/geometry/OperatorTouchesLocal.java similarity index 100% rename from src/com/esri/core/geometry/OperatorTouchesLocal.java rename to src/main/java/com/esri/core/geometry/OperatorTouchesLocal.java diff --git a/src/com/esri/core/geometry/OperatorUnion.java b/src/main/java/com/esri/core/geometry/OperatorUnion.java similarity index 100% rename from src/com/esri/core/geometry/OperatorUnion.java rename to src/main/java/com/esri/core/geometry/OperatorUnion.java diff --git a/src/com/esri/core/geometry/OperatorUnionCursor.java b/src/main/java/com/esri/core/geometry/OperatorUnionCursor.java similarity index 100% rename from src/com/esri/core/geometry/OperatorUnionCursor.java rename to src/main/java/com/esri/core/geometry/OperatorUnionCursor.java diff --git a/src/com/esri/core/geometry/OperatorUnionLocal.java b/src/main/java/com/esri/core/geometry/OperatorUnionLocal.java similarity index 100% rename from src/com/esri/core/geometry/OperatorUnionLocal.java rename to src/main/java/com/esri/core/geometry/OperatorUnionLocal.java diff --git a/src/com/esri/core/geometry/OperatorWithin.java b/src/main/java/com/esri/core/geometry/OperatorWithin.java similarity index 100% rename from src/com/esri/core/geometry/OperatorWithin.java rename to src/main/java/com/esri/core/geometry/OperatorWithin.java diff --git a/src/com/esri/core/geometry/OperatorWithinLocal.java b/src/main/java/com/esri/core/geometry/OperatorWithinLocal.java similarity index 100% rename from src/com/esri/core/geometry/OperatorWithinLocal.java rename to src/main/java/com/esri/core/geometry/OperatorWithinLocal.java diff --git a/src/com/esri/core/geometry/PathFlags.java b/src/main/java/com/esri/core/geometry/PathFlags.java similarity index 100% rename from src/com/esri/core/geometry/PathFlags.java rename to src/main/java/com/esri/core/geometry/PathFlags.java diff --git a/src/com/esri/core/geometry/PeDouble.java b/src/main/java/com/esri/core/geometry/PeDouble.java similarity index 100% rename from src/com/esri/core/geometry/PeDouble.java rename to src/main/java/com/esri/core/geometry/PeDouble.java diff --git a/src/com/esri/core/geometry/PlaneSweepCrackerHelper.java b/src/main/java/com/esri/core/geometry/PlaneSweepCrackerHelper.java similarity index 100% rename from src/com/esri/core/geometry/PlaneSweepCrackerHelper.java rename to src/main/java/com/esri/core/geometry/PlaneSweepCrackerHelper.java diff --git a/src/com/esri/core/geometry/Point.java b/src/main/java/com/esri/core/geometry/Point.java similarity index 100% rename from src/com/esri/core/geometry/Point.java rename to src/main/java/com/esri/core/geometry/Point.java diff --git a/src/com/esri/core/geometry/Point2D.java b/src/main/java/com/esri/core/geometry/Point2D.java similarity index 100% rename from src/com/esri/core/geometry/Point2D.java rename to src/main/java/com/esri/core/geometry/Point2D.java diff --git a/src/com/esri/core/geometry/Point3D.java b/src/main/java/com/esri/core/geometry/Point3D.java similarity index 100% rename from src/com/esri/core/geometry/Point3D.java rename to src/main/java/com/esri/core/geometry/Point3D.java diff --git a/src/com/esri/core/geometry/PointInPolygonHelper.java b/src/main/java/com/esri/core/geometry/PointInPolygonHelper.java similarity index 100% rename from src/com/esri/core/geometry/PointInPolygonHelper.java rename to src/main/java/com/esri/core/geometry/PointInPolygonHelper.java diff --git a/src/com/esri/core/geometry/Polygon.java b/src/main/java/com/esri/core/geometry/Polygon.java similarity index 100% rename from src/com/esri/core/geometry/Polygon.java rename to src/main/java/com/esri/core/geometry/Polygon.java diff --git a/src/com/esri/core/geometry/PolygonUtils.java b/src/main/java/com/esri/core/geometry/PolygonUtils.java similarity index 100% rename from src/com/esri/core/geometry/PolygonUtils.java rename to src/main/java/com/esri/core/geometry/PolygonUtils.java diff --git a/src/com/esri/core/geometry/Polyline.java b/src/main/java/com/esri/core/geometry/Polyline.java similarity index 100% rename from src/com/esri/core/geometry/Polyline.java rename to src/main/java/com/esri/core/geometry/Polyline.java diff --git a/src/com/esri/core/geometry/PolylinePath.java b/src/main/java/com/esri/core/geometry/PolylinePath.java similarity index 100% rename from src/com/esri/core/geometry/PolylinePath.java rename to src/main/java/com/esri/core/geometry/PolylinePath.java diff --git a/src/com/esri/core/geometry/ProgressTracker.java b/src/main/java/com/esri/core/geometry/ProgressTracker.java similarity index 100% rename from src/com/esri/core/geometry/ProgressTracker.java rename to src/main/java/com/esri/core/geometry/ProgressTracker.java diff --git a/src/com/esri/core/geometry/ProjectionTransformation.java b/src/main/java/com/esri/core/geometry/ProjectionTransformation.java similarity index 100% rename from src/com/esri/core/geometry/ProjectionTransformation.java rename to src/main/java/com/esri/core/geometry/ProjectionTransformation.java diff --git a/src/com/esri/core/geometry/Proximity2DResult.java b/src/main/java/com/esri/core/geometry/Proximity2DResult.java similarity index 100% rename from src/com/esri/core/geometry/Proximity2DResult.java rename to src/main/java/com/esri/core/geometry/Proximity2DResult.java diff --git a/src/com/esri/core/geometry/Proximity2DResultComparator.java b/src/main/java/com/esri/core/geometry/Proximity2DResultComparator.java similarity index 100% rename from src/com/esri/core/geometry/Proximity2DResultComparator.java rename to src/main/java/com/esri/core/geometry/Proximity2DResultComparator.java diff --git a/src/com/esri/core/geometry/QuadTree.java b/src/main/java/com/esri/core/geometry/QuadTree.java similarity index 100% rename from src/com/esri/core/geometry/QuadTree.java rename to src/main/java/com/esri/core/geometry/QuadTree.java diff --git a/src/com/esri/core/geometry/QuadTreeImpl.java b/src/main/java/com/esri/core/geometry/QuadTreeImpl.java similarity index 100% rename from src/com/esri/core/geometry/QuadTreeImpl.java rename to src/main/java/com/esri/core/geometry/QuadTreeImpl.java diff --git a/src/com/esri/core/geometry/RasterizedGeometry2D.java b/src/main/java/com/esri/core/geometry/RasterizedGeometry2D.java similarity index 100% rename from src/com/esri/core/geometry/RasterizedGeometry2D.java rename to src/main/java/com/esri/core/geometry/RasterizedGeometry2D.java diff --git a/src/com/esri/core/geometry/RasterizedGeometry2DImpl.java b/src/main/java/com/esri/core/geometry/RasterizedGeometry2DImpl.java similarity index 100% rename from src/com/esri/core/geometry/RasterizedGeometry2DImpl.java rename to src/main/java/com/esri/core/geometry/RasterizedGeometry2DImpl.java diff --git a/src/com/esri/core/geometry/RelationalOperations.java b/src/main/java/com/esri/core/geometry/RelationalOperations.java similarity index 100% rename from src/com/esri/core/geometry/RelationalOperations.java rename to src/main/java/com/esri/core/geometry/RelationalOperations.java diff --git a/src/com/esri/core/geometry/RelationalOperationsMatrix.java b/src/main/java/com/esri/core/geometry/RelationalOperationsMatrix.java similarity index 100% rename from src/com/esri/core/geometry/RelationalOperationsMatrix.java rename to src/main/java/com/esri/core/geometry/RelationalOperationsMatrix.java diff --git a/src/com/esri/core/geometry/RingOrientationFixer.java b/src/main/java/com/esri/core/geometry/RingOrientationFixer.java similarity index 100% rename from src/com/esri/core/geometry/RingOrientationFixer.java rename to src/main/java/com/esri/core/geometry/RingOrientationFixer.java diff --git a/src/com/esri/core/geometry/Segment.java b/src/main/java/com/esri/core/geometry/Segment.java similarity index 100% rename from src/com/esri/core/geometry/Segment.java rename to src/main/java/com/esri/core/geometry/Segment.java diff --git a/src/com/esri/core/geometry/SegmentBuffer.java b/src/main/java/com/esri/core/geometry/SegmentBuffer.java similarity index 100% rename from src/com/esri/core/geometry/SegmentBuffer.java rename to src/main/java/com/esri/core/geometry/SegmentBuffer.java diff --git a/src/com/esri/core/geometry/SegmentFlags.java b/src/main/java/com/esri/core/geometry/SegmentFlags.java similarity index 100% rename from src/com/esri/core/geometry/SegmentFlags.java rename to src/main/java/com/esri/core/geometry/SegmentFlags.java diff --git a/src/com/esri/core/geometry/SegmentIntersector.java b/src/main/java/com/esri/core/geometry/SegmentIntersector.java similarity index 100% rename from src/com/esri/core/geometry/SegmentIntersector.java rename to src/main/java/com/esri/core/geometry/SegmentIntersector.java diff --git a/src/com/esri/core/geometry/SegmentIterator.java b/src/main/java/com/esri/core/geometry/SegmentIterator.java similarity index 100% rename from src/com/esri/core/geometry/SegmentIterator.java rename to src/main/java/com/esri/core/geometry/SegmentIterator.java diff --git a/src/com/esri/core/geometry/SegmentIteratorImpl.java b/src/main/java/com/esri/core/geometry/SegmentIteratorImpl.java similarity index 100% rename from src/com/esri/core/geometry/SegmentIteratorImpl.java rename to src/main/java/com/esri/core/geometry/SegmentIteratorImpl.java diff --git a/src/com/esri/core/geometry/ShapeExportFlags.java b/src/main/java/com/esri/core/geometry/ShapeExportFlags.java similarity index 100% rename from src/com/esri/core/geometry/ShapeExportFlags.java rename to src/main/java/com/esri/core/geometry/ShapeExportFlags.java diff --git a/src/com/esri/core/geometry/ShapeImportFlags.java b/src/main/java/com/esri/core/geometry/ShapeImportFlags.java similarity index 100% rename from src/com/esri/core/geometry/ShapeImportFlags.java rename to src/main/java/com/esri/core/geometry/ShapeImportFlags.java diff --git a/src/com/esri/core/geometry/ShapeModifiers.java b/src/main/java/com/esri/core/geometry/ShapeModifiers.java similarity index 100% rename from src/com/esri/core/geometry/ShapeModifiers.java rename to src/main/java/com/esri/core/geometry/ShapeModifiers.java diff --git a/src/com/esri/core/geometry/ShapeType.java b/src/main/java/com/esri/core/geometry/ShapeType.java similarity index 100% rename from src/com/esri/core/geometry/ShapeType.java rename to src/main/java/com/esri/core/geometry/ShapeType.java diff --git a/src/com/esri/core/geometry/SimpleByteBufferCursor.java b/src/main/java/com/esri/core/geometry/SimpleByteBufferCursor.java similarity index 100% rename from src/com/esri/core/geometry/SimpleByteBufferCursor.java rename to src/main/java/com/esri/core/geometry/SimpleByteBufferCursor.java diff --git a/src/com/esri/core/geometry/SimpleGeometryCursor.java b/src/main/java/com/esri/core/geometry/SimpleGeometryCursor.java similarity index 100% rename from src/com/esri/core/geometry/SimpleGeometryCursor.java rename to src/main/java/com/esri/core/geometry/SimpleGeometryCursor.java diff --git a/src/com/esri/core/geometry/SimpleJsonCursor.java b/src/main/java/com/esri/core/geometry/SimpleJsonCursor.java similarity index 100% rename from src/com/esri/core/geometry/SimpleJsonCursor.java rename to src/main/java/com/esri/core/geometry/SimpleJsonCursor.java diff --git a/src/com/esri/core/geometry/SimpleJsonParserCursor.java b/src/main/java/com/esri/core/geometry/SimpleJsonParserCursor.java similarity index 100% rename from src/com/esri/core/geometry/SimpleJsonParserCursor.java rename to src/main/java/com/esri/core/geometry/SimpleJsonParserCursor.java diff --git a/src/com/esri/core/geometry/SimpleMapGeometryCursor.java b/src/main/java/com/esri/core/geometry/SimpleMapGeometryCursor.java similarity index 100% rename from src/com/esri/core/geometry/SimpleMapGeometryCursor.java rename to src/main/java/com/esri/core/geometry/SimpleMapGeometryCursor.java diff --git a/src/com/esri/core/geometry/SimpleRasterizer.java b/src/main/java/com/esri/core/geometry/SimpleRasterizer.java similarity index 100% rename from src/com/esri/core/geometry/SimpleRasterizer.java rename to src/main/java/com/esri/core/geometry/SimpleRasterizer.java diff --git a/src/com/esri/core/geometry/Simplificator.java b/src/main/java/com/esri/core/geometry/Simplificator.java similarity index 100% rename from src/com/esri/core/geometry/Simplificator.java rename to src/main/java/com/esri/core/geometry/Simplificator.java diff --git a/src/com/esri/core/geometry/SpatialReference.java b/src/main/java/com/esri/core/geometry/SpatialReference.java similarity index 100% rename from src/com/esri/core/geometry/SpatialReference.java rename to src/main/java/com/esri/core/geometry/SpatialReference.java diff --git a/src/com/esri/core/geometry/SpatialReferenceImpl.java b/src/main/java/com/esri/core/geometry/SpatialReferenceImpl.java similarity index 100% rename from src/com/esri/core/geometry/SpatialReferenceImpl.java rename to src/main/java/com/esri/core/geometry/SpatialReferenceImpl.java diff --git a/src/com/esri/core/geometry/SpatialReferenceSerializer.java b/src/main/java/com/esri/core/geometry/SpatialReferenceSerializer.java similarity index 100% rename from src/com/esri/core/geometry/SpatialReferenceSerializer.java rename to src/main/java/com/esri/core/geometry/SpatialReferenceSerializer.java diff --git a/src/com/esri/core/geometry/StridedIndexTypeCollection.java b/src/main/java/com/esri/core/geometry/StridedIndexTypeCollection.java similarity index 100% rename from src/com/esri/core/geometry/StridedIndexTypeCollection.java rename to src/main/java/com/esri/core/geometry/StridedIndexTypeCollection.java diff --git a/src/com/esri/core/geometry/StringUtils.java b/src/main/java/com/esri/core/geometry/StringUtils.java similarity index 100% rename from src/com/esri/core/geometry/StringUtils.java rename to src/main/java/com/esri/core/geometry/StringUtils.java diff --git a/src/com/esri/core/geometry/SweepComparator.java b/src/main/java/com/esri/core/geometry/SweepComparator.java similarity index 100% rename from src/com/esri/core/geometry/SweepComparator.java rename to src/main/java/com/esri/core/geometry/SweepComparator.java diff --git a/src/com/esri/core/geometry/SweepMonkierComparator.java b/src/main/java/com/esri/core/geometry/SweepMonkierComparator.java similarity index 100% rename from src/com/esri/core/geometry/SweepMonkierComparator.java rename to src/main/java/com/esri/core/geometry/SweepMonkierComparator.java diff --git a/src/com/esri/core/geometry/TopoGraph.java b/src/main/java/com/esri/core/geometry/TopoGraph.java similarity index 100% rename from src/com/esri/core/geometry/TopoGraph.java rename to src/main/java/com/esri/core/geometry/TopoGraph.java diff --git a/src/com/esri/core/geometry/TopologicalOperations.java b/src/main/java/com/esri/core/geometry/TopologicalOperations.java similarity index 100% rename from src/com/esri/core/geometry/TopologicalOperations.java rename to src/main/java/com/esri/core/geometry/TopologicalOperations.java diff --git a/src/com/esri/core/geometry/Transformation2D.java b/src/main/java/com/esri/core/geometry/Transformation2D.java similarity index 100% rename from src/com/esri/core/geometry/Transformation2D.java rename to src/main/java/com/esri/core/geometry/Transformation2D.java diff --git a/src/com/esri/core/geometry/Transformation3D.java b/src/main/java/com/esri/core/geometry/Transformation3D.java similarity index 100% rename from src/com/esri/core/geometry/Transformation3D.java rename to src/main/java/com/esri/core/geometry/Transformation3D.java diff --git a/src/com/esri/core/geometry/Treap.java b/src/main/java/com/esri/core/geometry/Treap.java similarity index 100% rename from src/com/esri/core/geometry/Treap.java rename to src/main/java/com/esri/core/geometry/Treap.java diff --git a/src/com/esri/core/geometry/UserCancelException.java b/src/main/java/com/esri/core/geometry/UserCancelException.java similarity index 100% rename from src/com/esri/core/geometry/UserCancelException.java rename to src/main/java/com/esri/core/geometry/UserCancelException.java diff --git a/src/com/esri/core/geometry/VertexDescription.java b/src/main/java/com/esri/core/geometry/VertexDescription.java similarity index 100% rename from src/com/esri/core/geometry/VertexDescription.java rename to src/main/java/com/esri/core/geometry/VertexDescription.java diff --git a/src/com/esri/core/geometry/VertexDescriptionDesignerImpl.java b/src/main/java/com/esri/core/geometry/VertexDescriptionDesignerImpl.java similarity index 100% rename from src/com/esri/core/geometry/VertexDescriptionDesignerImpl.java rename to src/main/java/com/esri/core/geometry/VertexDescriptionDesignerImpl.java diff --git a/src/com/esri/core/geometry/VertexDescriptionHash.java b/src/main/java/com/esri/core/geometry/VertexDescriptionHash.java similarity index 100% rename from src/com/esri/core/geometry/VertexDescriptionHash.java rename to src/main/java/com/esri/core/geometry/VertexDescriptionHash.java diff --git a/src/com/esri/core/geometry/WkbByteOrder.java b/src/main/java/com/esri/core/geometry/WkbByteOrder.java similarity index 100% rename from src/com/esri/core/geometry/WkbByteOrder.java rename to src/main/java/com/esri/core/geometry/WkbByteOrder.java diff --git a/src/com/esri/core/geometry/WkbExportFlags.java b/src/main/java/com/esri/core/geometry/WkbExportFlags.java similarity index 100% rename from src/com/esri/core/geometry/WkbExportFlags.java rename to src/main/java/com/esri/core/geometry/WkbExportFlags.java diff --git a/src/com/esri/core/geometry/WkbGeometryType.java b/src/main/java/com/esri/core/geometry/WkbGeometryType.java similarity index 100% rename from src/com/esri/core/geometry/WkbGeometryType.java rename to src/main/java/com/esri/core/geometry/WkbGeometryType.java diff --git a/src/com/esri/core/geometry/WkbImportFlags.java b/src/main/java/com/esri/core/geometry/WkbImportFlags.java similarity index 100% rename from src/com/esri/core/geometry/WkbImportFlags.java rename to src/main/java/com/esri/core/geometry/WkbImportFlags.java diff --git a/src/com/esri/core/geometry/Wkid.java b/src/main/java/com/esri/core/geometry/Wkid.java similarity index 100% rename from src/com/esri/core/geometry/Wkid.java rename to src/main/java/com/esri/core/geometry/Wkid.java diff --git a/src/com/esri/core/geometry/Wkt.java b/src/main/java/com/esri/core/geometry/Wkt.java similarity index 100% rename from src/com/esri/core/geometry/Wkt.java rename to src/main/java/com/esri/core/geometry/Wkt.java diff --git a/src/com/esri/core/geometry/WktExportFlags.java b/src/main/java/com/esri/core/geometry/WktExportFlags.java similarity index 100% rename from src/com/esri/core/geometry/WktExportFlags.java rename to src/main/java/com/esri/core/geometry/WktExportFlags.java diff --git a/src/com/esri/core/geometry/WktImportFlags.java b/src/main/java/com/esri/core/geometry/WktImportFlags.java similarity index 100% rename from src/com/esri/core/geometry/WktImportFlags.java rename to src/main/java/com/esri/core/geometry/WktImportFlags.java diff --git a/src/com/esri/core/geometry/WktParser.java b/src/main/java/com/esri/core/geometry/WktParser.java similarity index 100% rename from src/com/esri/core/geometry/WktParser.java rename to src/main/java/com/esri/core/geometry/WktParser.java diff --git a/src/com/esri/core/geometry/ogc/OGCConcreteGeometryCollection.java b/src/main/java/com/esri/core/geometry/ogc/OGCConcreteGeometryCollection.java similarity index 100% rename from src/com/esri/core/geometry/ogc/OGCConcreteGeometryCollection.java rename to src/main/java/com/esri/core/geometry/ogc/OGCConcreteGeometryCollection.java diff --git a/src/com/esri/core/geometry/ogc/OGCCurve.java b/src/main/java/com/esri/core/geometry/ogc/OGCCurve.java similarity index 100% rename from src/com/esri/core/geometry/ogc/OGCCurve.java rename to src/main/java/com/esri/core/geometry/ogc/OGCCurve.java diff --git a/src/com/esri/core/geometry/ogc/OGCGeometry.java b/src/main/java/com/esri/core/geometry/ogc/OGCGeometry.java similarity index 100% rename from src/com/esri/core/geometry/ogc/OGCGeometry.java rename to src/main/java/com/esri/core/geometry/ogc/OGCGeometry.java diff --git a/src/com/esri/core/geometry/ogc/OGCGeometryCollection.java b/src/main/java/com/esri/core/geometry/ogc/OGCGeometryCollection.java similarity index 100% rename from src/com/esri/core/geometry/ogc/OGCGeometryCollection.java rename to src/main/java/com/esri/core/geometry/ogc/OGCGeometryCollection.java diff --git a/src/com/esri/core/geometry/ogc/OGCLineString.java b/src/main/java/com/esri/core/geometry/ogc/OGCLineString.java similarity index 100% rename from src/com/esri/core/geometry/ogc/OGCLineString.java rename to src/main/java/com/esri/core/geometry/ogc/OGCLineString.java diff --git a/src/com/esri/core/geometry/ogc/OGCLinearRing.java b/src/main/java/com/esri/core/geometry/ogc/OGCLinearRing.java similarity index 100% rename from src/com/esri/core/geometry/ogc/OGCLinearRing.java rename to src/main/java/com/esri/core/geometry/ogc/OGCLinearRing.java diff --git a/src/com/esri/core/geometry/ogc/OGCMultiCurve.java b/src/main/java/com/esri/core/geometry/ogc/OGCMultiCurve.java similarity index 100% rename from src/com/esri/core/geometry/ogc/OGCMultiCurve.java rename to src/main/java/com/esri/core/geometry/ogc/OGCMultiCurve.java diff --git a/src/com/esri/core/geometry/ogc/OGCMultiLineString.java b/src/main/java/com/esri/core/geometry/ogc/OGCMultiLineString.java similarity index 100% rename from src/com/esri/core/geometry/ogc/OGCMultiLineString.java rename to src/main/java/com/esri/core/geometry/ogc/OGCMultiLineString.java diff --git a/src/com/esri/core/geometry/ogc/OGCMultiPoint.java b/src/main/java/com/esri/core/geometry/ogc/OGCMultiPoint.java similarity index 100% rename from src/com/esri/core/geometry/ogc/OGCMultiPoint.java rename to src/main/java/com/esri/core/geometry/ogc/OGCMultiPoint.java diff --git a/src/com/esri/core/geometry/ogc/OGCMultiPolygon.java b/src/main/java/com/esri/core/geometry/ogc/OGCMultiPolygon.java similarity index 100% rename from src/com/esri/core/geometry/ogc/OGCMultiPolygon.java rename to src/main/java/com/esri/core/geometry/ogc/OGCMultiPolygon.java diff --git a/src/com/esri/core/geometry/ogc/OGCMultiSurface.java b/src/main/java/com/esri/core/geometry/ogc/OGCMultiSurface.java similarity index 100% rename from src/com/esri/core/geometry/ogc/OGCMultiSurface.java rename to src/main/java/com/esri/core/geometry/ogc/OGCMultiSurface.java diff --git a/src/com/esri/core/geometry/ogc/OGCPoint.java b/src/main/java/com/esri/core/geometry/ogc/OGCPoint.java similarity index 100% rename from src/com/esri/core/geometry/ogc/OGCPoint.java rename to src/main/java/com/esri/core/geometry/ogc/OGCPoint.java diff --git a/src/com/esri/core/geometry/ogc/OGCPolygon.java b/src/main/java/com/esri/core/geometry/ogc/OGCPolygon.java similarity index 100% rename from src/com/esri/core/geometry/ogc/OGCPolygon.java rename to src/main/java/com/esri/core/geometry/ogc/OGCPolygon.java diff --git a/src/com/esri/core/geometry/ogc/OGCSurface.java b/src/main/java/com/esri/core/geometry/ogc/OGCSurface.java similarity index 100% rename from src/com/esri/core/geometry/ogc/OGCSurface.java rename to src/main/java/com/esri/core/geometry/ogc/OGCSurface.java diff --git a/src/com/esri/core/geometry/gcs_id_to_tolerance.txt b/src/main/resources/com/esri/core/geometry/gcs_id_to_tolerance.txt similarity index 100% rename from src/com/esri/core/geometry/gcs_id_to_tolerance.txt rename to src/main/resources/com/esri/core/geometry/gcs_id_to_tolerance.txt diff --git a/src/com/esri/core/geometry/gcs_tolerances.txt b/src/main/resources/com/esri/core/geometry/gcs_tolerances.txt similarity index 100% rename from src/com/esri/core/geometry/gcs_tolerances.txt rename to src/main/resources/com/esri/core/geometry/gcs_tolerances.txt diff --git a/src/com/esri/core/geometry/intermediate_to_old_wkid.txt b/src/main/resources/com/esri/core/geometry/intermediate_to_old_wkid.txt similarity index 100% rename from src/com/esri/core/geometry/intermediate_to_old_wkid.txt rename to src/main/resources/com/esri/core/geometry/intermediate_to_old_wkid.txt diff --git a/src/com/esri/core/geometry/new_to_old_wkid.txt b/src/main/resources/com/esri/core/geometry/new_to_old_wkid.txt similarity index 100% rename from src/com/esri/core/geometry/new_to_old_wkid.txt rename to src/main/resources/com/esri/core/geometry/new_to_old_wkid.txt diff --git a/src/com/esri/core/geometry/pcs_id_to_tolerance.txt b/src/main/resources/com/esri/core/geometry/pcs_id_to_tolerance.txt similarity index 100% rename from src/com/esri/core/geometry/pcs_id_to_tolerance.txt rename to src/main/resources/com/esri/core/geometry/pcs_id_to_tolerance.txt diff --git a/src/com/esri/core/geometry/pcs_tolerances.txt b/src/main/resources/com/esri/core/geometry/pcs_tolerances.txt similarity index 100% rename from src/com/esri/core/geometry/pcs_tolerances.txt rename to src/main/resources/com/esri/core/geometry/pcs_tolerances.txt diff --git a/unittest/com/esri/core/geometry/GeometryUtils.java b/src/test/java/com/esri/core/geometry/GeometryUtils.java similarity index 100% rename from unittest/com/esri/core/geometry/GeometryUtils.java rename to src/test/java/com/esri/core/geometry/GeometryUtils.java diff --git a/unittest/com/esri/core/geometry/RandomCoordinateGenerator.java b/src/test/java/com/esri/core/geometry/RandomCoordinateGenerator.java similarity index 100% rename from unittest/com/esri/core/geometry/RandomCoordinateGenerator.java rename to src/test/java/com/esri/core/geometry/RandomCoordinateGenerator.java diff --git a/unittest/com/esri/core/geometry/TestAttributes.java b/src/test/java/com/esri/core/geometry/TestAttributes.java similarity index 100% rename from unittest/com/esri/core/geometry/TestAttributes.java rename to src/test/java/com/esri/core/geometry/TestAttributes.java diff --git a/unittest/com/esri/core/geometry/TestBuffer.java b/src/test/java/com/esri/core/geometry/TestBuffer.java similarity index 100% rename from unittest/com/esri/core/geometry/TestBuffer.java rename to src/test/java/com/esri/core/geometry/TestBuffer.java diff --git a/unittest/com/esri/core/geometry/TestClip.java b/src/test/java/com/esri/core/geometry/TestClip.java similarity index 100% rename from unittest/com/esri/core/geometry/TestClip.java rename to src/test/java/com/esri/core/geometry/TestClip.java diff --git a/unittest/com/esri/core/geometry/TestCommonMethods.java b/src/test/java/com/esri/core/geometry/TestCommonMethods.java similarity index 100% rename from unittest/com/esri/core/geometry/TestCommonMethods.java rename to src/test/java/com/esri/core/geometry/TestCommonMethods.java diff --git a/unittest/com/esri/core/geometry/TestContains.java b/src/test/java/com/esri/core/geometry/TestContains.java similarity index 100% rename from unittest/com/esri/core/geometry/TestContains.java rename to src/test/java/com/esri/core/geometry/TestContains.java diff --git a/unittest/com/esri/core/geometry/TestConvexHull.java b/src/test/java/com/esri/core/geometry/TestConvexHull.java similarity index 100% rename from unittest/com/esri/core/geometry/TestConvexHull.java rename to src/test/java/com/esri/core/geometry/TestConvexHull.java diff --git a/unittest/com/esri/core/geometry/TestCut.java b/src/test/java/com/esri/core/geometry/TestCut.java similarity index 100% rename from unittest/com/esri/core/geometry/TestCut.java rename to src/test/java/com/esri/core/geometry/TestCut.java diff --git a/unittest/com/esri/core/geometry/TestDifference.java b/src/test/java/com/esri/core/geometry/TestDifference.java similarity index 100% rename from unittest/com/esri/core/geometry/TestDifference.java rename to src/test/java/com/esri/core/geometry/TestDifference.java diff --git a/unittest/com/esri/core/geometry/TestDistance.java b/src/test/java/com/esri/core/geometry/TestDistance.java similarity index 100% rename from unittest/com/esri/core/geometry/TestDistance.java rename to src/test/java/com/esri/core/geometry/TestDistance.java diff --git a/unittest/com/esri/core/geometry/TestEditShape.java b/src/test/java/com/esri/core/geometry/TestEditShape.java similarity index 100% rename from unittest/com/esri/core/geometry/TestEditShape.java rename to src/test/java/com/esri/core/geometry/TestEditShape.java diff --git a/unittest/com/esri/core/geometry/TestEnvelope2DIntersector.java b/src/test/java/com/esri/core/geometry/TestEnvelope2DIntersector.java similarity index 100% rename from unittest/com/esri/core/geometry/TestEnvelope2DIntersector.java rename to src/test/java/com/esri/core/geometry/TestEnvelope2DIntersector.java diff --git a/unittest/com/esri/core/geometry/TestEquals.java b/src/test/java/com/esri/core/geometry/TestEquals.java similarity index 100% rename from unittest/com/esri/core/geometry/TestEquals.java rename to src/test/java/com/esri/core/geometry/TestEquals.java diff --git a/unittest/com/esri/core/geometry/TestFailed.java b/src/test/java/com/esri/core/geometry/TestFailed.java similarity index 100% rename from unittest/com/esri/core/geometry/TestFailed.java rename to src/test/java/com/esri/core/geometry/TestFailed.java diff --git a/unittest/com/esri/core/geometry/TestGeneralize.java b/src/test/java/com/esri/core/geometry/TestGeneralize.java similarity index 100% rename from unittest/com/esri/core/geometry/TestGeneralize.java rename to src/test/java/com/esri/core/geometry/TestGeneralize.java diff --git a/unittest/com/esri/core/geometry/TestGeodetic.java b/src/test/java/com/esri/core/geometry/TestGeodetic.java similarity index 100% rename from unittest/com/esri/core/geometry/TestGeodetic.java rename to src/test/java/com/esri/core/geometry/TestGeodetic.java diff --git a/unittest/com/esri/core/geometry/TestGeomToGeoJson.java b/src/test/java/com/esri/core/geometry/TestGeomToGeoJson.java similarity index 100% rename from unittest/com/esri/core/geometry/TestGeomToGeoJson.java rename to src/test/java/com/esri/core/geometry/TestGeomToGeoJson.java diff --git a/unittest/com/esri/core/geometry/TestGeomToJSonExportSRFromWkiOrWkt_CR181369.java b/src/test/java/com/esri/core/geometry/TestGeomToJSonExportSRFromWkiOrWkt_CR181369.java similarity index 100% rename from unittest/com/esri/core/geometry/TestGeomToJSonExportSRFromWkiOrWkt_CR181369.java rename to src/test/java/com/esri/core/geometry/TestGeomToJSonExportSRFromWkiOrWkt_CR181369.java diff --git a/unittest/com/esri/core/geometry/TestImportExport.java b/src/test/java/com/esri/core/geometry/TestImportExport.java similarity index 100% rename from unittest/com/esri/core/geometry/TestImportExport.java rename to src/test/java/com/esri/core/geometry/TestImportExport.java diff --git a/unittest/com/esri/core/geometry/TestInterpolateAttributes.java b/src/test/java/com/esri/core/geometry/TestInterpolateAttributes.java similarity index 100% rename from unittest/com/esri/core/geometry/TestInterpolateAttributes.java rename to src/test/java/com/esri/core/geometry/TestInterpolateAttributes.java diff --git a/unittest/com/esri/core/geometry/TestIntersect2.java b/src/test/java/com/esri/core/geometry/TestIntersect2.java similarity index 100% rename from unittest/com/esri/core/geometry/TestIntersect2.java rename to src/test/java/com/esri/core/geometry/TestIntersect2.java diff --git a/unittest/com/esri/core/geometry/TestIntersection.java b/src/test/java/com/esri/core/geometry/TestIntersection.java similarity index 100% rename from unittest/com/esri/core/geometry/TestIntersection.java rename to src/test/java/com/esri/core/geometry/TestIntersection.java diff --git a/unittest/com/esri/core/geometry/TestIntervalTree.java b/src/test/java/com/esri/core/geometry/TestIntervalTree.java similarity index 100% rename from unittest/com/esri/core/geometry/TestIntervalTree.java rename to src/test/java/com/esri/core/geometry/TestIntervalTree.java diff --git a/unittest/com/esri/core/geometry/TestJSonGeometry.java b/src/test/java/com/esri/core/geometry/TestJSonGeometry.java similarity index 100% rename from unittest/com/esri/core/geometry/TestJSonGeometry.java rename to src/test/java/com/esri/core/geometry/TestJSonGeometry.java diff --git a/unittest/com/esri/core/geometry/TestJSonToGeomFromWkiOrWkt_CR177613.java b/src/test/java/com/esri/core/geometry/TestJSonToGeomFromWkiOrWkt_CR177613.java similarity index 100% rename from unittest/com/esri/core/geometry/TestJSonToGeomFromWkiOrWkt_CR177613.java rename to src/test/java/com/esri/core/geometry/TestJSonToGeomFromWkiOrWkt_CR177613.java diff --git a/unittest/com/esri/core/geometry/TestJsonParser.java b/src/test/java/com/esri/core/geometry/TestJsonParser.java similarity index 100% rename from unittest/com/esri/core/geometry/TestJsonParser.java rename to src/test/java/com/esri/core/geometry/TestJsonParser.java diff --git a/unittest/com/esri/core/geometry/TestMathUtils.java b/src/test/java/com/esri/core/geometry/TestMathUtils.java similarity index 100% rename from unittest/com/esri/core/geometry/TestMathUtils.java rename to src/test/java/com/esri/core/geometry/TestMathUtils.java diff --git a/unittest/com/esri/core/geometry/TestMultiPoint.java b/src/test/java/com/esri/core/geometry/TestMultiPoint.java similarity index 100% rename from unittest/com/esri/core/geometry/TestMultiPoint.java rename to src/test/java/com/esri/core/geometry/TestMultiPoint.java diff --git a/unittest/com/esri/core/geometry/TestOGC.java b/src/test/java/com/esri/core/geometry/TestOGC.java similarity index 100% rename from unittest/com/esri/core/geometry/TestOGC.java rename to src/test/java/com/esri/core/geometry/TestOGC.java diff --git a/unittest/com/esri/core/geometry/TestOffset.java b/src/test/java/com/esri/core/geometry/TestOffset.java similarity index 100% rename from unittest/com/esri/core/geometry/TestOffset.java rename to src/test/java/com/esri/core/geometry/TestOffset.java diff --git a/unittest/com/esri/core/geometry/TestPoint.java b/src/test/java/com/esri/core/geometry/TestPoint.java similarity index 100% rename from unittest/com/esri/core/geometry/TestPoint.java rename to src/test/java/com/esri/core/geometry/TestPoint.java diff --git a/unittest/com/esri/core/geometry/TestPolygon.java b/src/test/java/com/esri/core/geometry/TestPolygon.java similarity index 100% rename from unittest/com/esri/core/geometry/TestPolygon.java rename to src/test/java/com/esri/core/geometry/TestPolygon.java diff --git a/unittest/com/esri/core/geometry/TestPolygonUtils.java b/src/test/java/com/esri/core/geometry/TestPolygonUtils.java similarity index 100% rename from unittest/com/esri/core/geometry/TestPolygonUtils.java rename to src/test/java/com/esri/core/geometry/TestPolygonUtils.java diff --git a/unittest/com/esri/core/geometry/TestProximity2D.java b/src/test/java/com/esri/core/geometry/TestProximity2D.java similarity index 100% rename from unittest/com/esri/core/geometry/TestProximity2D.java rename to src/test/java/com/esri/core/geometry/TestProximity2D.java diff --git a/unittest/com/esri/core/geometry/TestQuadTree.java b/src/test/java/com/esri/core/geometry/TestQuadTree.java similarity index 100% rename from unittest/com/esri/core/geometry/TestQuadTree.java rename to src/test/java/com/esri/core/geometry/TestQuadTree.java diff --git a/unittest/com/esri/core/geometry/TestRasterizedGeometry2D.java b/src/test/java/com/esri/core/geometry/TestRasterizedGeometry2D.java similarity index 100% rename from unittest/com/esri/core/geometry/TestRasterizedGeometry2D.java rename to src/test/java/com/esri/core/geometry/TestRasterizedGeometry2D.java diff --git a/unittest/com/esri/core/geometry/TestRelation.java b/src/test/java/com/esri/core/geometry/TestRelation.java similarity index 100% rename from unittest/com/esri/core/geometry/TestRelation.java rename to src/test/java/com/esri/core/geometry/TestRelation.java diff --git a/unittest/com/esri/core/geometry/TestSerialization.java b/src/test/java/com/esri/core/geometry/TestSerialization.java similarity index 100% rename from unittest/com/esri/core/geometry/TestSerialization.java rename to src/test/java/com/esri/core/geometry/TestSerialization.java diff --git a/unittest/com/esri/core/geometry/TestShapePreserving.java b/src/test/java/com/esri/core/geometry/TestShapePreserving.java similarity index 100% rename from unittest/com/esri/core/geometry/TestShapePreserving.java rename to src/test/java/com/esri/core/geometry/TestShapePreserving.java diff --git a/unittest/com/esri/core/geometry/TestSimplify.java b/src/test/java/com/esri/core/geometry/TestSimplify.java similarity index 100% rename from unittest/com/esri/core/geometry/TestSimplify.java rename to src/test/java/com/esri/core/geometry/TestSimplify.java diff --git a/unittest/com/esri/core/geometry/TestTouch.java b/src/test/java/com/esri/core/geometry/TestTouch.java similarity index 100% rename from unittest/com/esri/core/geometry/TestTouch.java rename to src/test/java/com/esri/core/geometry/TestTouch.java diff --git a/unittest/com/esri/core/geometry/TestTreap.java b/src/test/java/com/esri/core/geometry/TestTreap.java similarity index 100% rename from unittest/com/esri/core/geometry/TestTreap.java rename to src/test/java/com/esri/core/geometry/TestTreap.java diff --git a/unittest/com/esri/core/geometry/TestUnion.java b/src/test/java/com/esri/core/geometry/TestUnion.java similarity index 100% rename from unittest/com/esri/core/geometry/TestUnion.java rename to src/test/java/com/esri/core/geometry/TestUnion.java diff --git a/unittest/com/esri/core/geometry/TestWKBSupport.java b/src/test/java/com/esri/core/geometry/TestWKBSupport.java similarity index 100% rename from unittest/com/esri/core/geometry/TestWKBSupport.java rename to src/test/java/com/esri/core/geometry/TestWKBSupport.java diff --git a/unittest/com/esri/core/geometry/TestWkbImportOnPostgresST.java b/src/test/java/com/esri/core/geometry/TestWkbImportOnPostgresST.java similarity index 100% rename from unittest/com/esri/core/geometry/TestWkbImportOnPostgresST.java rename to src/test/java/com/esri/core/geometry/TestWkbImportOnPostgresST.java diff --git a/unittest/com/esri/core/geometry/TestWkid.java b/src/test/java/com/esri/core/geometry/TestWkid.java similarity index 100% rename from unittest/com/esri/core/geometry/TestWkid.java rename to src/test/java/com/esri/core/geometry/TestWkid.java diff --git a/unittest/com/esri/core/geometry/TestWktParser.java b/src/test/java/com/esri/core/geometry/TestWktParser.java similarity index 100% rename from unittest/com/esri/core/geometry/TestWktParser.java rename to src/test/java/com/esri/core/geometry/TestWktParser.java diff --git a/unittest/com/esri/core/geometry/Utils.java b/src/test/java/com/esri/core/geometry/Utils.java similarity index 100% rename from unittest/com/esri/core/geometry/Utils.java rename to src/test/java/com/esri/core/geometry/Utils.java diff --git a/unittest/com/esri/core/geometry/savedAngularUnit.txt b/src/test/resources/com/esri/core/geometry/savedAngularUnit.txt similarity index 100% rename from unittest/com/esri/core/geometry/savedAngularUnit.txt rename to src/test/resources/com/esri/core/geometry/savedAngularUnit.txt diff --git a/unittest/com/esri/core/geometry/savedAreaUnit.txt b/src/test/resources/com/esri/core/geometry/savedAreaUnit.txt similarity index 100% rename from unittest/com/esri/core/geometry/savedAreaUnit.txt rename to src/test/resources/com/esri/core/geometry/savedAreaUnit.txt diff --git a/unittest/com/esri/core/geometry/savedEnvelope.txt b/src/test/resources/com/esri/core/geometry/savedEnvelope.txt similarity index 100% rename from unittest/com/esri/core/geometry/savedEnvelope.txt rename to src/test/resources/com/esri/core/geometry/savedEnvelope.txt diff --git a/unittest/com/esri/core/geometry/savedEnvelope2D.txt b/src/test/resources/com/esri/core/geometry/savedEnvelope2D.txt similarity index 100% rename from unittest/com/esri/core/geometry/savedEnvelope2D.txt rename to src/test/resources/com/esri/core/geometry/savedEnvelope2D.txt diff --git a/unittest/com/esri/core/geometry/savedLinearUnit.txt b/src/test/resources/com/esri/core/geometry/savedLinearUnit.txt similarity index 100% rename from unittest/com/esri/core/geometry/savedLinearUnit.txt rename to src/test/resources/com/esri/core/geometry/savedLinearUnit.txt diff --git a/unittest/com/esri/core/geometry/savedMultiPoint.txt b/src/test/resources/com/esri/core/geometry/savedMultiPoint.txt similarity index 100% rename from unittest/com/esri/core/geometry/savedMultiPoint.txt rename to src/test/resources/com/esri/core/geometry/savedMultiPoint.txt diff --git a/unittest/com/esri/core/geometry/savedPoint.txt b/src/test/resources/com/esri/core/geometry/savedPoint.txt similarity index 100% rename from unittest/com/esri/core/geometry/savedPoint.txt rename to src/test/resources/com/esri/core/geometry/savedPoint.txt diff --git a/unittest/com/esri/core/geometry/savedPolygon.txt b/src/test/resources/com/esri/core/geometry/savedPolygon.txt similarity index 100% rename from unittest/com/esri/core/geometry/savedPolygon.txt rename to src/test/resources/com/esri/core/geometry/savedPolygon.txt diff --git a/unittest/com/esri/core/geometry/savedPolyline.txt b/src/test/resources/com/esri/core/geometry/savedPolyline.txt similarity index 100% rename from unittest/com/esri/core/geometry/savedPolyline.txt rename to src/test/resources/com/esri/core/geometry/savedPolyline.txt diff --git a/unittest/com/esri/core/geometry/savedProjectionTransformation.txt b/src/test/resources/com/esri/core/geometry/savedProjectionTransformation.txt similarity index 100% rename from unittest/com/esri/core/geometry/savedProjectionTransformation.txt rename to src/test/resources/com/esri/core/geometry/savedProjectionTransformation.txt diff --git a/unittest/com/esri/core/geometry/savedSpatialReference.txt b/src/test/resources/com/esri/core/geometry/savedSpatialReference.txt similarity index 100% rename from unittest/com/esri/core/geometry/savedSpatialReference.txt rename to src/test/resources/com/esri/core/geometry/savedSpatialReference.txt From 4f92c50cbc8b28466857be256079aa9406407f48 Mon Sep 17 00:00:00 2001 From: "Philip (flip) Kromer" Date: Fri, 4 Jul 2014 19:14:04 -0500 Subject: [PATCH 082/196] Adding Interfaces to families of operators; permits much cleaner UDFs * New interface IOperatorAWithB for (geom, geom) -> geom * Applied it to Intersection, Difference, SymmetricDifference, and Union More to come. --- .../esri/core/geometry/IOperatorAWithB.java | 45 +++++++++++++++++++ .../core/geometry/OperatorDifference.java | 3 +- .../core/geometry/OperatorIntersection.java | 3 +- .../geometry/OperatorSymmetricDifference.java | 3 +- .../com/esri/core/geometry/OperatorUnion.java | 3 +- 5 files changed, 53 insertions(+), 4 deletions(-) create mode 100644 src/main/java/com/esri/core/geometry/IOperatorAWithB.java diff --git a/src/main/java/com/esri/core/geometry/IOperatorAWithB.java b/src/main/java/com/esri/core/geometry/IOperatorAWithB.java new file mode 100644 index 00000000..b01b7573 --- /dev/null +++ b/src/main/java/com/esri/core/geometry/IOperatorAWithB.java @@ -0,0 +1,45 @@ +/* + Copyright 1995-2013 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ +package com.esri.core.geometry; + +import com.esri.core.geometry.Geometry; +import com.esri.core.geometry.SpatialReference; +import com.esri.core.geometry.ProgressTracker; + +/** + * Interface for operators that act on two geometries to produce a new geometry as result. + */ +public interface IOperatorAWithB { + + /** + * Operation on two geometries, returning a third. Examples include + * Intersection, Difference, and so forth. + * + * @param geom1 and geom2 are the geometry instances to be operated on. + * + */ + public Geometry execute(Geometry geom1, Geometry geom2, + SpatialReference sr, ProgressTracker progressTracker); + +} diff --git a/src/main/java/com/esri/core/geometry/OperatorDifference.java b/src/main/java/com/esri/core/geometry/OperatorDifference.java index 1dbfc9ec..15e2845f 100644 --- a/src/main/java/com/esri/core/geometry/OperatorDifference.java +++ b/src/main/java/com/esri/core/geometry/OperatorDifference.java @@ -25,11 +25,12 @@ package com.esri.core.geometry; import com.esri.core.geometry.Operator.Type; +import com.esri.core.geometry.IOperatorAWithB; /** * Difference of geometries. */ -public abstract class OperatorDifference extends Operator { +public abstract class OperatorDifference extends Operator implements IOperatorAWithB { @Override public Type getType() { diff --git a/src/main/java/com/esri/core/geometry/OperatorIntersection.java b/src/main/java/com/esri/core/geometry/OperatorIntersection.java index f69c86ec..ab3ae72f 100644 --- a/src/main/java/com/esri/core/geometry/OperatorIntersection.java +++ b/src/main/java/com/esri/core/geometry/OperatorIntersection.java @@ -25,11 +25,12 @@ package com.esri.core.geometry; import com.esri.core.geometry.Operator.Type; +import com.esri.core.geometry.IOperatorAWithB; /** *Intersection of geometries by a given geometry. */ -public abstract class OperatorIntersection extends Operator { +public abstract class OperatorIntersection extends Operator implements IOperatorAWithB { @Override public Type getType() { return Type.Intersection; diff --git a/src/main/java/com/esri/core/geometry/OperatorSymmetricDifference.java b/src/main/java/com/esri/core/geometry/OperatorSymmetricDifference.java index b21f8745..b3a5bd28 100644 --- a/src/main/java/com/esri/core/geometry/OperatorSymmetricDifference.java +++ b/src/main/java/com/esri/core/geometry/OperatorSymmetricDifference.java @@ -24,12 +24,13 @@ package com.esri.core.geometry; import com.esri.core.geometry.Operator.Type; +import com.esri.core.geometry.IOperatorAWithB; /** * Symmetric difference (XOR) operation between geometries. * */ -public abstract class OperatorSymmetricDifference extends Operator { +public abstract class OperatorSymmetricDifference extends Operator implements IOperatorAWithB { @Override public Type getType() { return Type.Difference; diff --git a/src/main/java/com/esri/core/geometry/OperatorUnion.java b/src/main/java/com/esri/core/geometry/OperatorUnion.java index c7cf49b4..b66f4403 100644 --- a/src/main/java/com/esri/core/geometry/OperatorUnion.java +++ b/src/main/java/com/esri/core/geometry/OperatorUnion.java @@ -25,13 +25,14 @@ package com.esri.core.geometry; import com.esri.core.geometry.Operator.Type; +import com.esri.core.geometry.IOperatorAWithB; /** * * Union of geometries. * */ -public abstract class OperatorUnion extends Operator { +public abstract class OperatorUnion extends Operator implements IOperatorAWithB { @Override public Type getType() { return Type.Union; From f1b1db07e699e41d666b010ee7465e88d871a0f1 Mon Sep 17 00:00:00 2001 From: "Philip (flip) Kromer" Date: Fri, 4 Jul 2014 19:15:36 -0500 Subject: [PATCH 083/196] Minor fixes: error messages on import; gitignore artifacts; make NumberUtils available to UDFs --- .gitignore | 1 + .../java/com/esri/core/geometry/NumberUtils.java | 6 +++--- .../core/geometry/OperatorExportToWktLocal.java | 16 ++++++++-------- .../java/com/esri/core/geometry/WktParser.java | 4 +++- 4 files changed, 15 insertions(+), 12 deletions(-) diff --git a/.gitignore b/.gitignore index 03c080fb..85898c5a 100644 --- a/.gitignore +++ b/.gitignore @@ -32,3 +32,4 @@ libs/ Icon? ehthumbs.db Thumbs.db +target/* diff --git a/src/main/java/com/esri/core/geometry/NumberUtils.java b/src/main/java/com/esri/core/geometry/NumberUtils.java index 7e540f0d..383aa7af 100644 --- a/src/main/java/com/esri/core/geometry/NumberUtils.java +++ b/src/main/java/com/esri/core/geometry/NumberUtils.java @@ -24,13 +24,13 @@ package com.esri.core.geometry; -class NumberUtils { +public class NumberUtils { - static int snap(int v, int minv, int maxv) { + public static int snap(int v, int minv, int maxv) { return v < minv ? minv : v > maxv ? maxv : v; } - static long snap(long v, long minv, long maxv) { + public static long snap(long v, long minv, long maxv) { return v < minv ? minv : v > maxv ? maxv : v; } diff --git a/src/main/java/com/esri/core/geometry/OperatorExportToWktLocal.java b/src/main/java/com/esri/core/geometry/OperatorExportToWktLocal.java index cdcefd84..a48b1f5c 100644 --- a/src/main/java/com/esri/core/geometry/OperatorExportToWktLocal.java +++ b/src/main/java/com/esri/core/geometry/OperatorExportToWktLocal.java @@ -42,7 +42,7 @@ static void exportToWkt(int export_flags, Geometry geometry, || (export_flags & WktExportFlags.wktExportMultiLineString) != 0 || (export_flags & WktExportFlags.wktExportPoint) != 0 || (export_flags & WktExportFlags.wktExportMultiPoint) != 0) - throw new IllegalArgumentException(); + throw new IllegalArgumentException("Cannot export a Polygon as Line/Point : "+export_flags); exportPolygonToWkt(export_flags, (Polygon) geometry, string); return; @@ -52,7 +52,7 @@ static void exportToWkt(int export_flags, Geometry geometry, || (export_flags & WktExportFlags.wktExportMultiPolygon) != 0 || (export_flags & WktExportFlags.wktExportPoint) != 0 || (export_flags & WktExportFlags.wktExportMultiPoint) != 0) - throw new IllegalArgumentException(); + throw new IllegalArgumentException("Cannot export a Polyline as (Multi)Polygon/(Multi)Point : "+export_flags); exportPolylineToWkt(export_flags, (Polyline) geometry, string); return; @@ -62,7 +62,7 @@ static void exportToWkt(int export_flags, Geometry geometry, || (export_flags & WktExportFlags.wktExportMultiLineString) != 0 || (export_flags & WktExportFlags.wktExportPolygon) != 0 || (export_flags & WktExportFlags.wktExportMultiPolygon) != 0) - throw new IllegalArgumentException(); + throw new IllegalArgumentException("Cannot export a MultiPoint as (Multi)LineString/(Multi)Polygon: "+export_flags); exportMultiPointToWkt(export_flags, (MultiPoint) geometry, string); return; @@ -72,7 +72,7 @@ static void exportToWkt(int export_flags, Geometry geometry, || (export_flags & WktExportFlags.wktExportMultiLineString) != 0 || (export_flags & WktExportFlags.wktExportPolygon) != 0 || (export_flags & WktExportFlags.wktExportMultiPolygon) != 0) - throw new IllegalArgumentException(); + throw new IllegalArgumentException("Cannot export a Point as (Multi)LineString/(Multi)Polygon: "+export_flags); exportPointToWkt(export_flags, (Point) geometry, string); return; @@ -82,7 +82,7 @@ static void exportToWkt(int export_flags, Geometry geometry, || (export_flags & WktExportFlags.wktExportMultiLineString) != 0 || (export_flags & WktExportFlags.wktExportPoint) != 0 || (export_flags & WktExportFlags.wktExportMultiPoint) != 0) - throw new IllegalArgumentException(); + throw new IllegalArgumentException("Cannot export an Envelop as (Multi)LineString/(Multi)Point: "+export_flags); exportEnvelopeToWkt(export_flags, (Envelope) geometry, string); return; @@ -149,7 +149,7 @@ static void exportPolygonToWkt(int export_flags, Polygon polygon, if ((export_flags & WktExportFlags.wktExportPolygon) != 0) { if (polygon_count > 1) - throw new IllegalArgumentException(); + throw new IllegalArgumentException("Cannot export a Polygon with specified export flags: "+export_flags); polygonTaggedText_(precision, b_export_zs, b_export_ms, zs, ms, position, path_flags, paths, path_count, string); @@ -207,7 +207,7 @@ static void exportPolylineToWkt(int export_flags, Polyline polyline, if ((export_flags & WktExportFlags.wktExportLineString) != 0) { if (path_count > 1) - throw new IllegalArgumentException(); + throw new IllegalArgumentException("Cannot export a LineString with specified export flags: "+export_flags); lineStringTaggedText_(precision, b_export_zs, b_export_ms, zs, ms, position, path_flags, paths, string); @@ -256,7 +256,7 @@ static void exportMultiPointToWkt(int export_flags, MultiPoint multipoint, if ((export_flags & WktExportFlags.wktExportPoint) != 0) { if (point_count > 1) - throw new IllegalArgumentException(); + throw new IllegalArgumentException("Cannot export a Point with specified export flags: "+export_flags); pointTaggedTextFromMultiPoint_(precision, b_export_zs, b_export_ms, zs, ms, position, string); diff --git a/src/main/java/com/esri/core/geometry/WktParser.java b/src/main/java/com/esri/core/geometry/WktParser.java index 91f0198d..02fbc058 100644 --- a/src/main/java/com/esri/core/geometry/WktParser.java +++ b/src/main/java/com/esri/core/geometry/WktParser.java @@ -264,7 +264,9 @@ private void geometry_() { m_current_token_type = WktToken.geometrycollection; m_function_stack.add(State.geometryCollectionStart); } else { - throw new IllegalArgumentException(); + String snippet = (m_wkt_string.length() > 200 ? + m_wkt_string.substring(0,200)+"..." : m_wkt_string); + throw new IllegalArgumentException("Could not parse Well-Known Text: "+snippet); } m_function_stack.add(State.attributes); From 36cc1ee81037c04b85efe90dee52d17f6c0157db Mon Sep 17 00:00:00 2001 From: "Philip (flip) Kromer" Date: Tue, 8 Jul 2014 04:04:58 -0500 Subject: [PATCH 084/196] toString() on Geometry and OGCGeometry --- src/main/java/com/esri/core/geometry/Geometry.java | 8 ++++++++ src/main/java/com/esri/core/geometry/ogc/OGCGeometry.java | 6 ++++++ 2 files changed, 14 insertions(+) diff --git a/src/main/java/com/esri/core/geometry/Geometry.java b/src/main/java/com/esri/core/geometry/Geometry.java index 0257579c..fbbee9f3 100644 --- a/src/main/java/com/esri/core/geometry/Geometry.java +++ b/src/main/java/com/esri/core/geometry/Geometry.java @@ -26,6 +26,8 @@ package com.esri.core.geometry; import com.esri.core.geometry.VertexDescription.Semantics; +import com.esri.core.geometry.GeometryEngine; +import com.esri.core.geometry.WktExportFlags; import java.io.ObjectStreamException; import java.io.Serializable; @@ -507,6 +509,12 @@ static Geometry _clone(Geometry src) { return geom; } + public String toString() { + String snippet = GeometryEngine.geometryToWkt(this, WktExportFlags.wktExportDefaults); + if (snippet.length() > 200) { snippet = snippet.substring(0, 197)+"..."; } + return String.format("%s: %s", this.getClass().getSimpleName(), snippet); + } + /** * The stateFlag value changes with changes applied to this geometry. This * allows the user to keep track of the geometry's state. diff --git a/src/main/java/com/esri/core/geometry/ogc/OGCGeometry.java b/src/main/java/com/esri/core/geometry/ogc/OGCGeometry.java index ef6e1452..1a6e8b09 100644 --- a/src/main/java/com/esri/core/geometry/ogc/OGCGeometry.java +++ b/src/main/java/com/esri/core/geometry/ogc/OGCGeometry.java @@ -84,6 +84,12 @@ public String asText() { return GeometryEngine.geometryToWkt(getEsriGeometry(), 0); } + public String toString() { + String snippet = asText(); + if (snippet.length() > 200) { snippet = snippet.substring(0, 197)+"..."; } + return String.format("%s: %s", this.getClass().getSimpleName(), snippet); + } + public ByteBuffer asBinary() { OperatorExportToWkb op = (OperatorExportToWkb) OperatorFactoryLocal .getInstance().getOperator(Operator.Type.ExportToWkb); From 6ab844c1983864dd10fd039ffab20bbe4b2b15fd Mon Sep 17 00:00:00 2001 From: "Philip (flip) Kromer" Date: Tue, 8 Jul 2014 04:10:27 -0500 Subject: [PATCH 085/196] CombineOperator replaces IOperatorAWithB.java --- .../geometry/{IOperatorAWithB.java => CombineOperator.java} | 2 +- src/main/java/com/esri/core/geometry/OperatorDifference.java | 4 ++-- .../java/com/esri/core/geometry/OperatorIntersection.java | 4 ++-- .../com/esri/core/geometry/OperatorSymmetricDifference.java | 4 ++-- src/main/java/com/esri/core/geometry/OperatorUnion.java | 4 ++-- 5 files changed, 9 insertions(+), 9 deletions(-) rename src/main/java/com/esri/core/geometry/{IOperatorAWithB.java => CombineOperator.java} (97%) diff --git a/src/main/java/com/esri/core/geometry/IOperatorAWithB.java b/src/main/java/com/esri/core/geometry/CombineOperator.java similarity index 97% rename from src/main/java/com/esri/core/geometry/IOperatorAWithB.java rename to src/main/java/com/esri/core/geometry/CombineOperator.java index b01b7573..e929ca39 100644 --- a/src/main/java/com/esri/core/geometry/IOperatorAWithB.java +++ b/src/main/java/com/esri/core/geometry/CombineOperator.java @@ -30,7 +30,7 @@ /** * Interface for operators that act on two geometries to produce a new geometry as result. */ -public interface IOperatorAWithB { +public interface CombineOperator { /** * Operation on two geometries, returning a third. Examples include diff --git a/src/main/java/com/esri/core/geometry/OperatorDifference.java b/src/main/java/com/esri/core/geometry/OperatorDifference.java index 15e2845f..607285a6 100644 --- a/src/main/java/com/esri/core/geometry/OperatorDifference.java +++ b/src/main/java/com/esri/core/geometry/OperatorDifference.java @@ -25,12 +25,12 @@ package com.esri.core.geometry; import com.esri.core.geometry.Operator.Type; -import com.esri.core.geometry.IOperatorAWithB; +import com.esri.core.geometry.CombineOperator; /** * Difference of geometries. */ -public abstract class OperatorDifference extends Operator implements IOperatorAWithB { +public abstract class OperatorDifference extends Operator implements CombineOperator { @Override public Type getType() { diff --git a/src/main/java/com/esri/core/geometry/OperatorIntersection.java b/src/main/java/com/esri/core/geometry/OperatorIntersection.java index ab3ae72f..2416fb06 100644 --- a/src/main/java/com/esri/core/geometry/OperatorIntersection.java +++ b/src/main/java/com/esri/core/geometry/OperatorIntersection.java @@ -25,12 +25,12 @@ package com.esri.core.geometry; import com.esri.core.geometry.Operator.Type; -import com.esri.core.geometry.IOperatorAWithB; +import com.esri.core.geometry.CombineOperator; /** *Intersection of geometries by a given geometry. */ -public abstract class OperatorIntersection extends Operator implements IOperatorAWithB { +public abstract class OperatorIntersection extends Operator implements CombineOperator { @Override public Type getType() { return Type.Intersection; diff --git a/src/main/java/com/esri/core/geometry/OperatorSymmetricDifference.java b/src/main/java/com/esri/core/geometry/OperatorSymmetricDifference.java index b3a5bd28..17a2f5bf 100644 --- a/src/main/java/com/esri/core/geometry/OperatorSymmetricDifference.java +++ b/src/main/java/com/esri/core/geometry/OperatorSymmetricDifference.java @@ -24,13 +24,13 @@ package com.esri.core.geometry; import com.esri.core.geometry.Operator.Type; -import com.esri.core.geometry.IOperatorAWithB; +import com.esri.core.geometry.CombineOperator; /** * Symmetric difference (XOR) operation between geometries. * */ -public abstract class OperatorSymmetricDifference extends Operator implements IOperatorAWithB { +public abstract class OperatorSymmetricDifference extends Operator implements CombineOperator { @Override public Type getType() { return Type.Difference; diff --git a/src/main/java/com/esri/core/geometry/OperatorUnion.java b/src/main/java/com/esri/core/geometry/OperatorUnion.java index b66f4403..95cd8f93 100644 --- a/src/main/java/com/esri/core/geometry/OperatorUnion.java +++ b/src/main/java/com/esri/core/geometry/OperatorUnion.java @@ -25,14 +25,14 @@ package com.esri.core.geometry; import com.esri.core.geometry.Operator.Type; -import com.esri.core.geometry.IOperatorAWithB; +import com.esri.core.geometry.CombineOperator; /** * * Union of geometries. * */ -public abstract class OperatorUnion extends Operator implements IOperatorAWithB { +public abstract class OperatorUnion extends Operator implements CombineOperator { @Override public Type getType() { return Type.Union; From c885e7955c415861fa4d1ad064e2eb27fc2b5237 Mon Sep 17 00:00:00 2001 From: serg4066 Date: Wed, 23 Jul 2014 17:30:46 -0700 Subject: [PATCH 086/196] Fixes for issues 52, 51, 48, 46, 45, 41, 40, 36 --- .../core/geometry/AttributeStreamOfDbl.java | 6 +- .../core/geometry/AttributeStreamOfInt32.java | 4 + .../java/com/esri/core/geometry/Boundary.java | 81 +- .../java/com/esri/core/geometry/Bufferer.java | 8 +- .../java/com/esri/core/geometry/Clipper.java | 4 +- .../com/esri/core/geometry/Clusterer.java | 610 +++++----- .../esri/core/geometry/CombineOperator.java | 45 +- .../esri/core/geometry/ConstructOffset.java | 6 +- .../esri/core/geometry/CrackAndCluster.java | 119 +- .../java/com/esri/core/geometry/Cracker.java | 71 +- .../java/com/esri/core/geometry/Cutter.java | 8 +- .../com/esri/core/geometry/EditShape.java | 60 +- .../java/com/esri/core/geometry/Envelope.java | 13 +- .../com/esri/core/geometry/Envelope1D.java | 26 + .../com/esri/core/geometry/Envelope2D.java | 68 +- .../geometry/Envelope2DIntersectorImpl.java | 66 +- .../com/esri/core/geometry/Envelope3D.java | 4 +- .../esri/core/geometry/GeodeticCurveType.java | 5 + .../java/com/esri/core/geometry/Geometry.java | 35 +- .../core/geometry/GeometryAccelerators.java | 42 + .../esri/core/geometry/GeometryEngine.java | 52 +- .../esri/core/geometry/GeometryException.java | 3 + .../esri/core/geometry/IndexHashTable.java | 111 +- .../com/esri/core/geometry/InternalUtils.java | 110 +- .../esri/core/geometry/IntervalTreeImpl.java | 2 +- .../esri/core/geometry/JsonStringWriter.java | 2 +- .../java/com/esri/core/geometry/Line.java | 36 +- .../com/esri/core/geometry/MapGeometry.java | 57 + .../com/esri/core/geometry/MathUtils.java | 7 + .../com/esri/core/geometry/MultiPath.java | 52 +- .../com/esri/core/geometry/MultiPathImpl.java | 71 +- .../com/esri/core/geometry/MultiPoint.java | 5 + .../esri/core/geometry/MultiPointImpl.java | 5 + .../geometry/MultiVertexGeometryImpl.java | 7 +- .../com/esri/core/geometry/NumberUtils.java | 48 +- .../java/com/esri/core/geometry/Operator.java | 2 +- .../esri/core/geometry/OperatorCutCursor.java | 182 +-- .../OperatorDensifyByLengthCursor.java | 2 +- .../core/geometry/OperatorDifference.java | 1 - .../OperatorExportToESRIShapeCursor.java | 2 +- .../geometry/OperatorExportToWkbLocal.java | 2 +- .../geometry/OperatorExportToWktLocal.java | 6 +- .../core/geometry/OperatorFactoryLocal.java | 20 +- .../geometry/OperatorGeneralizeCursor.java | 2 +- .../core/geometry/OperatorGeodesicBuffer.java | 67 ++ .../geometry/OperatorGeodesicBufferLocal.java | 44 + .../OperatorGeodeticDensifyByLength.java | 60 + .../OperatorGeodeticDensifyLocal.java | 43 + .../core/geometry/OperatorGeodeticLength.java | 17 - .../geometry/OperatorGeodeticLengthLocal.java | 6 - .../OperatorImportFromGeoJsonLocal.java | 26 +- .../geometry/OperatorImportFromWkbLocal.java | 18 +- .../geometry/OperatorImportFromWktLocal.java | 19 +- .../OperatorInternalRelationUtils.java | 14 +- .../core/geometry/OperatorIntersection.java | 4 +- .../geometry/OperatorIntersectionCursor.java | 24 +- .../geometry/OperatorIntersectionLocal.java | 3 +- .../esri/core/geometry/OperatorProject.java | 32 +- .../core/geometry/OperatorProjectLocal.java | 11 + .../core/geometry/OperatorProximity2D.java | 26 +- .../geometry/OperatorProximity2DLocal.java | 276 ++++- .../esri/core/geometry/OperatorRelate.java | 15 + .../OperatorShapePreservingDensify.java | 71 ++ .../OperatorShapePreservingDensifyLocal.java | 44 + .../core/geometry/OperatorSimpleRelation.java | 17 +- .../geometry/OperatorSimplifyLocalHelper.java | 50 +- .../geometry/OperatorSimplifyLocalOGC.java | 2 - .../geometry/OperatorSymmetricDifference.java | 1 - .../com/esri/core/geometry/OperatorUnion.java | 3 +- .../core/geometry/OperatorUnionCursor.java | 497 ++++---- .../geometry/PlaneSweepCrackerHelper.java | 238 +--- .../java/com/esri/core/geometry/Point.java | 25 +- .../java/com/esri/core/geometry/Point2D.java | 26 + .../java/com/esri/core/geometry/Point3D.java | 8 +- .../java/com/esri/core/geometry/Polygon.java | 4 +- .../com/esri/core/geometry/PolygonUtils.java | 29 +- .../java/com/esri/core/geometry/Polyline.java | 6 +- .../esri/core/geometry/ProgressTracker.java | 9 + .../esri/core/geometry/Proximity2DResult.java | 29 + .../com/esri/core/geometry/QuadTreeImpl.java | 157 +-- .../core/geometry/RasterizedGeometry2D.java | 2 +- .../geometry/RasterizedGeometry2DImpl.java | 4 +- .../core/geometry/RelationalOperations.java | 1061 ++++++++++------- .../geometry/RelationalOperationsMatrix.java | 725 ++++++++--- .../core/geometry/RingOrientationFixer.java | 160 ++- .../java/com/esri/core/geometry/Segment.java | 27 +- .../com/esri/core/geometry/SegmentBuffer.java | 10 +- .../core/geometry/SegmentIntersector.java | 12 +- .../esri/core/geometry/SegmentIterator.java | 12 + .../core/geometry/SegmentIteratorImpl.java | 42 +- .../com/esri/core/geometry/Simplificator.java | 13 +- .../core/geometry/SpatialReferenceImpl.java | 8 +- .../geometry/StridedIndexTypeCollection.java | 118 +- .../com/esri/core/geometry/TopoGraph.java | 576 +++++---- .../core/geometry/TopologicalOperations.java | 342 +++--- .../java/com/esri/core/geometry/Treap.java | 16 +- .../com/esri/core/geometry/WktParser.java | 9 +- .../ogc/OGCConcreteGeometryCollection.java | 22 +- .../esri/core/geometry/ogc/OGCGeometry.java | 91 +- .../esri/core/geometry/ogc/OGCPolygon.java | 2 +- .../com/esri/core/geometry/GeometryUtils.java | 28 - .../esri/core/geometry/TestAttributes.java | 3 +- .../geometry/TestEnvelope2DIntersector.java | 14 +- .../com/esri/core/geometry/TestFailed.java | 1 - .../com/esri/core/geometry/TestGeodetic.java | 2 +- ...omToJSonExportSRFromWkiOrWkt_CR181369.java | 1 - .../esri/core/geometry/TestImportExport.java | 4 +- .../esri/core/geometry/TestIntersect2.java | 1 - .../esri/core/geometry/TestIntersection.java | 36 + .../esri/core/geometry/TestJsonParser.java | 8 +- .../java/com/esri/core/geometry/TestOGC.java | 108 +- .../com/esri/core/geometry/TestOffset.java | 2 - .../com/esri/core/geometry/TestPoint.java | 57 +- .../esri/core/geometry/TestProximity2D.java | 29 +- .../com/esri/core/geometry/TestRelation.java | 905 +++++++++----- .../core/geometry/TestShapePreserving.java | 151 --- .../com/esri/core/geometry/TestSimplify.java | 4 + .../com/esri/core/geometry/TestUnion.java | 2 - .../esri/core/geometry/TestWKBSupport.java | 2 - .../java/com/esri/core/geometry/Utils.java | 4 +- 120 files changed, 5412 insertions(+), 3191 deletions(-) create mode 100644 src/main/java/com/esri/core/geometry/OperatorGeodesicBuffer.java create mode 100644 src/main/java/com/esri/core/geometry/OperatorGeodesicBufferLocal.java create mode 100644 src/main/java/com/esri/core/geometry/OperatorGeodeticDensifyByLength.java create mode 100644 src/main/java/com/esri/core/geometry/OperatorGeodeticDensifyLocal.java create mode 100644 src/main/java/com/esri/core/geometry/OperatorShapePreservingDensify.java create mode 100644 src/main/java/com/esri/core/geometry/OperatorShapePreservingDensifyLocal.java delete mode 100644 src/test/java/com/esri/core/geometry/TestShapePreserving.java diff --git a/src/main/java/com/esri/core/geometry/AttributeStreamOfDbl.java b/src/main/java/com/esri/core/geometry/AttributeStreamOfDbl.java index ec56a58b..09f87ff4 100644 --- a/src/main/java/com/esri/core/geometry/AttributeStreamOfDbl.java +++ b/src/main/java/com/esri/core/geometry/AttributeStreamOfDbl.java @@ -571,8 +571,10 @@ public void eraseRange(int index, int count, int validSize) { if (index + count > m_size) throw new GeometryException("invalid_call"); - System.arraycopy(m_buffer, index + count, m_buffer, index, validSize - - (index + count)); + if (validSize - (index + count) > 0) { + System.arraycopy(m_buffer, index + count, m_buffer, index, validSize + - (index + count)); + } m_size -= count; } diff --git a/src/main/java/com/esri/core/geometry/AttributeStreamOfInt32.java b/src/main/java/com/esri/core/geometry/AttributeStreamOfInt32.java index 0f7488ec..609a19fd 100644 --- a/src/main/java/com/esri/core/geometry/AttributeStreamOfInt32.java +++ b/src/main/java/com/esri/core/geometry/AttributeStreamOfInt32.java @@ -303,6 +303,10 @@ public int getLast() { return m_buffer[m_size - 1]; } + public void setLast(int v) { + m_buffer[m_size - 1] = v; + } + public void removeLast() { resize(m_size - 1); } diff --git a/src/main/java/com/esri/core/geometry/Boundary.java b/src/main/java/com/esri/core/geometry/Boundary.java index f9b99f9a..13958cb7 100644 --- a/src/main/java/com/esri/core/geometry/Boundary.java +++ b/src/main/java/com/esri/core/geometry/Boundary.java @@ -24,17 +24,52 @@ package com.esri.core.geometry; class Boundary { + + static boolean hasNonEmptyBoundary(Geometry geom, + ProgressTracker progress_tracker) { + if (geom.isEmpty()) + return false; + + Geometry.Type gt = geom.getType(); + if (gt == Geometry.Type.Polygon) { + if (geom.calculateArea2D() == 0) + return false; + + return true; + } else if (gt == Geometry.Type.Polyline) { + boolean[] b = new boolean[1]; + b[0] = false; + calculatePolylineBoundary_(geom._getImpl(), progress_tracker, true, + b); + return b[0]; + } else if (gt == Geometry.Type.Envelope) { + return true; + } else if (Geometry.isSegment(gt.value())) { + if (!((Segment) geom).isClosed()) { + return true; + } + + return false; + } else if (Geometry.isPoint(gt.value())) { + return false; + } + + return false; + } + static Geometry calculate(Geometry geom, ProgressTracker progress_tracker) { int gt = geom.getType().value(); if (gt == Geometry.GeometryType.Polygon) { Polyline dst = new Polyline(geom.getDescription()); - if (!geom.isEmpty()) { - ((MultiPathImpl)geom._getImpl())._copyToUnsafe((MultiPathImpl)dst._getImpl()); + if (!geom.isEmpty()) { + ((MultiPathImpl) geom._getImpl()) + ._copyToUnsafe((MultiPathImpl) dst._getImpl()); } return dst; } else if (gt == Geometry.GeometryType.Polyline) { - return calculatePolylineBoundary_(geom._getImpl(), progress_tracker); + return calculatePolylineBoundary_(geom._getImpl(), + progress_tracker, false, null); } else if (gt == Geometry.GeometryType.Envelope) { Polyline dst = new Polyline(geom.getDescription()); if (!geom.isEmpty()) @@ -98,9 +133,15 @@ public double getValue(int index) { } static MultiPoint calculatePolylineBoundary_(Object impl, - ProgressTracker progress_tracker) { + ProgressTracker progress_tracker, + boolean only_check_non_empty_boundary, boolean[] not_empty) { + if (not_empty != null) + not_empty[0] = false; MultiPathImpl mpImpl = (MultiPathImpl) impl; - MultiPoint dst = new MultiPoint(mpImpl.getDescription()); + MultiPoint dst = null; + if (!only_check_non_empty_boundary) + dst = new MultiPoint(mpImpl.getDescription()); + if (!mpImpl.isEmpty()) { AttributeStreamOfInt32 indices = new AttributeStreamOfInt32(0); indices.reserve(mpImpl.getPathCount() * 2); @@ -151,6 +192,12 @@ static MultiPoint calculatePolylineBoundary_(Object impl, } else { if ((counter & 1) == 0) {// remove boundary point indices.set(ind, NumberUtils.intMax()); + } else { + if (only_check_non_empty_boundary) { + if (not_empty != null) + not_empty[0] = true; + return null; + } } ptPrev.setCoords(pt); @@ -161,20 +208,30 @@ static MultiPoint calculatePolylineBoundary_(Object impl, if ((counter & 1) == 0) {// remove the point indices.set(ind, NumberUtils.intMax()); + } else { + if (only_check_non_empty_boundary) { + if (not_empty != null) + not_empty[0] = true; + return null; + } } + if (!only_check_non_empty_boundary) { + indices.sort(0, indices.size()); - indices.sort(0, indices.size()); - - for (int i = 0, n = indices.size(); i < n; i++) { - if (indices.get(i) == NumberUtils.intMax()) - break; + for (int i = 0, n = indices.size(); i < n; i++) { + if (indices.get(i) == NumberUtils.intMax()) + break; - mpImpl.getPointByVal(indices.get(i), point); - dst.add(point); + mpImpl.getPointByVal(indices.get(i), point); + dst.add(point); + } } } } + if (only_check_non_empty_boundary) + return null; + return dst; } } diff --git a/src/main/java/com/esri/core/geometry/Bufferer.java b/src/main/java/com/esri/core/geometry/Bufferer.java index db4318ad..a8517844 100644 --- a/src/main/java/com/esri/core/geometry/Bufferer.java +++ b/src/main/java/com/esri/core/geometry/Bufferer.java @@ -470,7 +470,7 @@ private Geometry buffer_() { case Geometry.GeometryType.Envelope: return bufferEnvelope_(); default: - throw new GeometryException("internal error"); + throw GeometryException.GeometryInternalError(); } } @@ -796,7 +796,7 @@ private Polygon bufferConvexPath_(MultiPath src, int ipath) { src_mp.getXY(path_start + (i + 2) % path_size, pt_3); v_1.sub(pt_2, pt_1); if (v_1.length() == 0) - throw new GeometryException("internal error"); + throw GeometryException.GeometryInternalError(); v_1.leftPerpendicular(); v_1.normalize(); @@ -813,7 +813,7 @@ private Polygon bufferConvexPath_(MultiPath src, int ipath) { v_2.sub(pt_3, pt_2); if (v_2.length() == 0) - throw new GeometryException("internal error"); + throw GeometryException.GeometryInternalError(); v_2.leftPerpendicular(); v_2.normalize(); @@ -1085,7 +1085,7 @@ private int bufferClosedPath_(Geometry input_geom, int ipath, pt_current, BufferCommand.Flags.enum_arc, m_buffer_commands.size() + 1, m_buffer_commands.size() - 1)); - } else if (!pt_left_prev.equals(pt)) { + } else if (!pt_left_prev.isEqual(pt)) { m_buffer_commands.add(new BufferCommand(pt_left_prev, pt_current, m_buffer_commands.size() + 1, m_buffer_commands.size() - 1, "dummy")); diff --git a/src/main/java/com/esri/core/geometry/Clipper.java b/src/main/java/com/esri/core/geometry/Clipper.java index fd064c95..2af2e0a2 100644 --- a/src/main/java/com/esri/core/geometry/Clipper.java +++ b/src/main/java/com/esri/core/geometry/Clipper.java @@ -1180,7 +1180,7 @@ static Geometry clip(Geometry geometry, Envelope2D extent, .queryEnvelopeInGeometry(extent); if (hit == RasterizedGeometry2D.HitType.Inside) { if (geomtype != Geometry.Type.Polygon.value()) - throw new GeometryException("internal error"); + throw GeometryException.GeometryInternalError(); Polygon poly = new Polygon(geometry.getDescription()); poly.addEnvelope(extent, false); @@ -1242,7 +1242,7 @@ static Geometry clip(Geometry geometry, Envelope2D extent, densify_dist); default: assert (false); - throw new GeometryException("internal error"); + throw GeometryException.GeometryInternalError(); } } diff --git a/src/main/java/com/esri/core/geometry/Clusterer.java b/src/main/java/com/esri/core/geometry/Clusterer.java index 9fb3cb46..8ff5efd8 100644 --- a/src/main/java/com/esri/core/geometry/Clusterer.java +++ b/src/main/java/com/esri/core/geometry/Clusterer.java @@ -33,13 +33,14 @@ final class Clusterer { // (clustered). // Uses reciprocal clustering (cluster vertices that are mutual nearest // neighbours) - static boolean executeReciprocal(EditShape shape, double tolerance) { - Clusterer clusterer = new Clusterer(); - clusterer.m_shape = shape; - clusterer.m_tolerance = tolerance; - clusterer.m_cell_size = 2 * tolerance; - return clusterer.clusterReciprocal_(); - } + /* + * static boolean executeReciprocal(EditShape shape, double tolerance) { + * Clusterer clusterer = new Clusterer(); clusterer.m_shape = shape; + * clusterer.m_tolerance = tolerance; clusterer.m_sqr_tolerance = tolerance + * * tolerance; clusterer.m_cell_size = 2 * tolerance; + * clusterer.m_inv_cell_size = 1.0 / clusterer.m_cell_size; return + * clusterer.clusterReciprocal_(); } + */ // Clusters vertices of the shape. Returns True, if some vertices were moved // (clustered). @@ -49,88 +50,102 @@ static boolean executeNonReciprocal(EditShape shape, double tolerance) { Clusterer clusterer = new Clusterer(); clusterer.m_shape = shape; clusterer.m_tolerance = tolerance; - clusterer.m_cell_size = 2 * tolerance;// revisit this value. Probably - // should be m_tolerance? + clusterer.m_sqr_tolerance = tolerance * tolerance; + clusterer.m_cell_size = 2 * tolerance; + clusterer.m_inv_cell_size = 1.0 / clusterer.m_cell_size; return clusterer.clusterNonReciprocal_(); } // Use b_conservative == True for simplify, and False for IsSimple. This // makes sure Simplified shape is more robust to transformations. - static boolean isClusterCandidate(double x_1, double y1, double x2, - double y2, double tolerance) { + static boolean isClusterCandidate_(double x_1, double y1, double x2, + double y2, double sqr_tolerance) { double dx = x_1 - x2; double dy = y1 - y2; - return Math.sqrt(dx * dx + dy * dy) <= tolerance; + return dx * dx + dy * dy <= sqr_tolerance; } Point2D m_origin = new Point2D(); double m_tolerance; + double m_sqr_tolerance; double m_cell_size; + double m_inv_cell_size; int[] m_bucket_array = new int[4];// temporary 4 element array - int m_dbg_candidate_check_count; - int m_hash_values; + int[] m_bucket_hash = new int[4];// temporary 4 element array + int m_dbg_candidate_check_count = 0; + int m_hash_values = -1; + int m_new_clusters = -1; - static int hashFunction_(double xi, double yi) { + static int hashFunction_(int xi, int yi) { int h = NumberUtils.hash(xi); return NumberUtils.hash(h, yi); } - class ClusterHashFunction extends IndexHashTable.HashFunction { - IndexMultiList m_clusters; + final class ClusterHashFunction extends IndexHashTable.HashFunction { EditShape m_shape; - double m_tolerance; - double m_cell_size; + double m_sqr_tolerance; + double m_inv_cell_size; Point2D m_origin = new Point2D(); Point2D m_pt = new Point2D(); Point2D m_pt_2 = new Point2D(); int m_hash_values; - public ClusterHashFunction(IndexMultiList clusters, EditShape shape, - Point2D origin, double tolerance, double cell_size, - int hash_values) { - m_clusters = clusters; + public ClusterHashFunction(EditShape shape, Point2D origin, + double sqr_tolerance, double inv_cell_size, int hash_values) { m_shape = shape; - m_tolerance = tolerance; - m_cell_size = cell_size; + m_sqr_tolerance = sqr_tolerance; + m_inv_cell_size = inv_cell_size; m_origin = origin; m_hash_values = hash_values; m_pt.setNaN(); m_pt_2.setNaN(); } - @Override - public int getHash(int element) { - int vertex = m_clusters.getFirstElement(element); - return m_shape.getUserIndex(vertex, m_hash_values); + int calculate_hash(int element) { + return calculate_hash_from_vertex(element); } - int calculateHash(int element) { - int vertex = m_clusters.getFirstElement(element); + int dbg_calculate_hash_from_xy(double x, double y) { + double dx = x - m_origin.x; + int xi = (int) (dx * m_inv_cell_size + 0.5); + double dy = y - m_origin.y; + int yi = (int) (dy * m_inv_cell_size + 0.5); + return hashFunction_(xi, yi); + } + + int calculate_hash_from_vertex(int vertex) { m_shape.getXY(vertex, m_pt); double dx = m_pt.x - m_origin.x; - double xi = Math.round(dx / m_cell_size); + int xi = (int) (dx * m_inv_cell_size + 0.5); double dy = m_pt.y - m_origin.y; - double yi = Math.round(dy / m_cell_size); + int yi = (int) (dy * m_inv_cell_size + 0.5); return hashFunction_(xi, yi); } + @Override + public int getHash(int element) { + return m_shape.getUserIndex(element, m_hash_values); + } + @Override public boolean equal(int element_1, int element_2) { - int xyindex_1 = m_clusters.getFirstElement(element_1); + int xyindex_1 = element_1; + int xyindex_2 = element_2; m_shape.getXY(xyindex_1, m_pt); - int xyindex_2 = m_clusters.getFirstElement(element_2); m_shape.getXY(xyindex_2, m_pt_2); - return isClusterCandidate(m_pt.x, m_pt.y, m_pt_2.x, m_pt_2.y, - m_tolerance); + return isClusterCandidate_(m_pt.x, m_pt.y, m_pt_2.x, m_pt_2.y, + m_sqr_tolerance); } @Override public int getHash(Object element_descriptor) { + // UNUSED return 0; } @Override public boolean equal(Object element_descriptor, int element) { + // UNUSED return false; } }; @@ -141,30 +156,30 @@ public boolean equal(Object element_descriptor, int element) { IndexHashTable m_hash_table; static class ClusterCandidate { - public int cluster; + public int vertex; double distance; }; void getNearestNeighbourCandidate_(int xyindex, Point2D pointOfInterest, int bucket_ptr, ClusterCandidate candidate) { - candidate.cluster = IndexMultiList.nullNode(); + candidate.vertex = -1; candidate.distance = NumberUtils.doubleMax(); Point2D pt = new Point2D(); - for (int node = bucket_ptr; node != IndexHashTable.nullNode(); node = m_hash_table + for (int node = bucket_ptr; node != -1; node = m_hash_table .getNextInBucket(node)) { - int cluster = m_hash_table.getElement(node); - int xyind = m_clusters.getFirstElement(cluster); + int xyind = m_hash_table.getElement(node); if (xyindex == xyind) continue; + m_shape.getXY(xyind, pt); - if (isClusterCandidate(pointOfInterest.x, pointOfInterest.y, pt.x, - pt.y, m_tolerance)) { + if (isClusterCandidate_(pointOfInterest.x, pointOfInterest.y, pt.x, + pt.y, m_sqr_tolerance)) { pt.sub(pointOfInterest); double l = pt.length(); if (l < candidate.distance) { candidate.distance = l; - candidate.cluster = cluster; + candidate.vertex = xyind; } } } @@ -174,26 +189,26 @@ void findClusterCandidate_(int xyindex, ClusterCandidate candidate) { Point2D pointOfInterest = new Point2D(); m_shape.getXY(xyindex, pointOfInterest); double x_0 = pointOfInterest.x - m_origin.x; - double x = x_0 / m_cell_size; + double x = x_0 * m_inv_cell_size; double y0 = pointOfInterest.y - m_origin.y; - double y = y0 / m_cell_size; + double y = y0 * m_inv_cell_size; - double xi = Math.round(x - 0.5); - double yi = Math.round(y - 0.5); + int xi = (int) x; + int yi = (int) y; // find the nearest neighbour in the 4 neigbouring cells. - candidate.cluster = IndexHashTable.nullNode(); + candidate.vertex = -1; candidate.distance = NumberUtils.doubleMax(); ClusterCandidate c = new ClusterCandidate(); - for (double dx = 0; dx <= 1.0; dx += 1.0) { - for (double dy = 0; dy <= 1.0; dy += 1.0) { + for (int dx = 0; dx <= 1; dx += 1) { + for (int dy = 0; dy <= 1; dy += 1) { int bucket_ptr = m_hash_table.getFirstInBucket(hashFunction_(xi + dx, yi + dy)); if (bucket_ptr != IndexHashTable.nullNode()) { getNearestNeighbourCandidate_(xyindex, pointOfInterest, bucket_ptr, c); - if (c.cluster != IndexHashTable.nullNode() + if (c.vertex != IndexHashTable.nullNode() && c.distance < candidate.distance) { candidate = c; } @@ -207,81 +222,192 @@ void collectClusterCandidates_(int xyindex, Point2D pointOfInterest = new Point2D(); m_shape.getXY(xyindex, pointOfInterest); double x_0 = pointOfInterest.x - m_origin.x; - double x = x_0 / m_cell_size; + double x = x_0 * m_inv_cell_size; double y0 = pointOfInterest.y - m_origin.y; - double y = y0 / m_cell_size; + double y = y0 * m_inv_cell_size; + + int xi = (int) x; + int yi = (int) y; - double xi = Math.round(x - 0.5); - double yi = Math.round(y - 0.5); - for (int i = 0; i < 4; i++) - m_bucket_array[i] = -1; int bucket_count = 0; // find all nearest neighbours in the 4 neigbouring cells. // Note, because we check four neighbours, there should be 4 times more // bins in the hash table to reduce collision probability in this loop. - for (double dx = 0; dx <= 1.0; dx += 1.0) { - for (double dy = 0; dy <= 1.0; dy += 1.0) { - int bucket_ptr = m_hash_table.getFirstInBucket(hashFunction_(xi - + dx, yi + dy)); - if (bucket_ptr != IndexHashTable.nullNode()) { + for (int dx = 0; dx <= 1; dx += 1) { + for (int dy = 0; dy <= 1; dy += 1) { + int hash = hashFunction_(xi + dx, yi + dy); + int bucket_ptr = m_hash_table.getFirstInBucket(hash); + if (bucket_ptr != -1) { // Check if we already have this bucket. // There could be a hash collision for neighbouring buckets. - for (int j = 0; j < bucket_count; j++) { - if (m_bucket_array[j] == bucket_ptr) { - bucket_ptr = -1;// hash values for two neighbouring - // cells have collided. - break; - } - } + m_bucket_array[bucket_count] = bucket_ptr; + m_bucket_hash[bucket_count] = hash; - if (bucket_ptr != -1) { - m_bucket_array[bucket_count] = bucket_ptr; - bucket_count++; - } + bucket_count++; } } } - for (int i = 0; i < 4; i++) { - int bucket_ptr = m_bucket_array[i]; - if (bucket_ptr == -1) - break; + // Clear duplicate buckets + // There could be a hash collision for neighboring buckets. + for (int j = bucket_count - 1; j >= 1; j--) { + int bucket_ptr = m_bucket_array[j]; + for (int i = j - 1; i >= 0; i--) { + if (bucket_ptr == m_bucket_array[i])// hash values for two + // neighbouring cells have + // collided. + { + m_bucket_hash[i] = -1; // forget collided hash + bucket_count--; + if (j != bucket_count) { + m_bucket_hash[j] = m_bucket_hash[bucket_count]; + m_bucket_array[j] = m_bucket_array[bucket_count]; + } + break;// duplicate + } + } + } - collectNearestNeighbourCandidates_(xyindex, pointOfInterest, - bucket_ptr, candidates); + for (int i = 0; i < bucket_count; i++) { + collectNearestNeighbourCandidates_(xyindex, m_bucket_hash[i], + pointOfInterest, m_bucket_array[i], candidates); } } - void collectNearestNeighbourCandidates_(int xyindex, + void collectNearestNeighbourCandidates_(int xyindex, int hash, Point2D pointOfInterest, int bucket_ptr, AttributeStreamOfInt32 candidates) { Point2D pt = new Point2D(); - for (int node = bucket_ptr; node != IndexHashTable.nullNode(); node = m_hash_table + for (int node = bucket_ptr; node != -1; node = m_hash_table .getNextInBucket(node)) { - int cluster = m_hash_table.getElement(node); - int xyind = m_clusters.getFirstElement(cluster); - if (xyindex == xyind) - continue; + int xyind = m_hash_table.getElement(node); + if (xyindex == xyind || hash != -1 + && m_shape.getUserIndex(xyind, m_hash_values) != hash) + continue;// processing same vertex, or the bucket hash modulo + // bin count collides. + m_shape.getXY(xyind, pt); m_dbg_candidate_check_count++; - if (isClusterCandidate(pointOfInterest.x, pointOfInterest.y, pt.x, - pt.y, m_tolerance)) { + if (isClusterCandidate_(pointOfInterest.x, pointOfInterest.y, pt.x, + pt.y, m_sqr_tolerance)) { candidates.add(node);// note that we add the cluster node // instead of the cluster. } } } - boolean mergeClusters_(int cluster_1, int cluster_2) { - int xyindex_1 = m_clusters.getFirstElement(cluster_1); - int xyindex_2 = m_clusters.getFirstElement(cluster_2); - boolean res = mergeVertices_(xyindex_1, xyindex_2); - m_clusters.concatenateLists(cluster_1, cluster_2); - int hash = m_hash_function.calculateHash(cluster_1); - m_shape.setUserIndex(xyindex_1, m_hash_values, hash); + boolean mergeClusters_(int vertex1, int vertex2, boolean update_hash) { + int cluster_1 = m_shape.getUserIndex(vertex1, m_new_clusters); + int cluster_2 = m_shape.getUserIndex(vertex2, m_new_clusters); + assert (cluster_1 != StridedIndexTypeCollection.impossibleIndex2()); + assert (cluster_2 != StridedIndexTypeCollection.impossibleIndex2()); + + if (cluster_1 == -1) { + cluster_1 = m_clusters.createList(); + m_clusters.addElement(cluster_1, vertex1); + m_shape.setUserIndex(vertex1, m_new_clusters, cluster_1); + } + + if (cluster_2 == -1) { + m_clusters.addElement(cluster_1, vertex2); + } else { + m_clusters.concatenateLists(cluster_1, cluster_2); + } + + // ensure only single vertex refers to the cluster. + m_shape.setUserIndex(vertex2, m_new_clusters, + StridedIndexTypeCollection.impossibleIndex2()); + + // merge cordinates + boolean res = mergeVertices_(vertex1, vertex2); + + if (update_hash) { + int hash = m_hash_function.calculate_hash_from_vertex(vertex1); + m_shape.setUserIndex(vertex1, m_hash_values, hash); + } else { + + } + + return res; + } + + // recalculate coordinates of the vertices by averaging them using weights. + // return true if the coordinates has changed. + static boolean mergeVertices(Point pt_1, Point pt_2, double w_1, + int rank_1, double w_2, int rank_2, Point pt_res, double[] w_res, + int[] rank_res) { + assert (!pt_1.isEmpty() && !pt_2.isEmpty()); + boolean res = pt_1.equals(pt_2); + + if (rank_1 > rank_2) { + pt_res = pt_1; + if (w_res != null) { + rank_res[0] = rank_1; + w_res[0] = w_1; + } + return res; + } else if (rank_2 > rank_1) { + pt_res = pt_2; + if (w_res != null) { + rank_res[0] = rank_1; + w_res[0] = w_1; + } + return res; + } + + pt_res = pt_1; + Point2D pt2d = new Point2D(); + mergeVertices2D(pt_1.getXY(), pt_2.getXY(), w_1, rank_1, w_2, rank_2, + pt2d, w_res, rank_res); + pt_res.setXY(pt2d); return res; } + static boolean mergeVertices2D(Point2D pt_1, Point2D pt_2, double w_1, + int rank_1, double w_2, int rank_2, Point2D pt_res, double[] w_res, + int[] rank_res) { + double w = w_1 + w_2; + boolean r = false; + double x = pt_1.x; + if (pt_1.x != pt_2.x) { + if (rank_1 == rank_2) + x = (pt_1.x * w_1 + pt_2.x * w_2) / w; + + r = true; + } + double y = pt_1.y; + if (pt_1.y != pt_2.y) { + if (rank_1 == rank_2) + y = (pt_1.y * w_1 + pt_2.y * w_2) / w; + + r = true; + } + + if (rank_1 != rank_2) { + if (rank_1 > rank_2) { + if (w_res != null) { + rank_res[0] = rank_1; + w_res[0] = w_1; + } + pt_res = pt_1; + } else { + if (w_res != null) { + rank_res[0] = rank_2; + w_res[0] = w_2; + } + pt_res = pt_2; + } + } else { + pt_res.setCoords(x, y); + if (w_res != null) { + w_res[0] = w; + rank_res[0] = rank_1; + } + } + + return r; + } + boolean mergeVertices_(int vert_1, int vert_2) { Point2D pt_1 = new Point2D(); m_shape.getXY(vert_1, pt_1); @@ -305,197 +431,39 @@ boolean mergeVertices_(int vert_1, int vert_2) { if (r > 0) m_shape.setXY(vert_1, x, y); + m_shape.setWeight(vert_1, w); return r != 0; } - boolean clusterReciprocal_() { - m_hash_values = m_shape.createUserIndex(); - int point_count = m_shape.getTotalPointCount(); - - m_shape.getXY(m_shape.getFirstVertex(m_shape.getFirstPath(m_shape - .getFirstGeometry())), m_origin); - - // This holds clusters. - m_clusters.clear(); - m_clusters.reserveLists(m_shape.getTotalPointCount()); - m_clusters.reserveNodes(m_shape.getTotalPointCount()); - - // Make the hash table. It serves a purpose of fine grain grid. - // Make it 25% larger than the point count to reduce the chance of - // collision. - m_hash_table = new IndexHashTable((point_count * 5) / 4, - new ClusterHashFunction(m_clusters, m_shape, m_origin, - m_tolerance, m_cell_size, m_hash_values)); - m_hash_table.reserveElements(m_shape.getTotalPointCount()); - - boolean b_clustered = false; - - // Go through all vertices stored in the m_shape and put the handles of - // the vertices into the clusters and the hash table. - for (int geometry = m_shape.getFirstGeometry(); geometry != -1; geometry = m_shape - .getNextGeometry(geometry)) { - for (int path = m_shape.getFirstPath(geometry); path != -1; path = m_shape - .getNextPath(path)) { - int vertex = m_shape.getFirstVertex(path); - for (int index = 0, nindex = m_shape.getPathSize(path); index < nindex; index++) { - assert (vertex != -1); - int cluster = m_clusters.createList(); - m_clusters.addElement(cluster, vertex); // initially each - // cluster consist - // of a single - // vertex - int hash = m_hash_function.calculateHash(cluster); - m_shape.setUserIndex(vertex, m_hash_values, hash); - m_hash_table.addElement(cluster); // add cluster to the hash - // table - vertex = m_shape.getNextVertex(vertex); - } - } - } - - AttributeStreamOfInt32 nn_chain = new AttributeStreamOfInt32(0); - AttributeStreamOfDbl nn_chain_distances = new AttributeStreamOfDbl(0);// array - // of - // distances - // between - // neighbour - // elements - // on - // nn_chain. - // nn_chain_distances->size() - // + - // 1 - // == - // nn_chain->size() - - ClusterCandidate candidate = new ClusterCandidate(); - - // Reciprocal nearest neighbour clustering, using a hash table. - while (m_hash_table.size() != 0 || nn_chain.size() != 0) { - if (nn_chain.size() == 0) { - int cluster = m_hash_table.getAnyElement(); - nn_chain.add(cluster); - continue; - } - - int cluster_1 = nn_chain.getLast(); - int xyindex = m_clusters.getFirstElement(cluster_1); - findClusterCandidate_(xyindex, candidate); - if (candidate.cluster == IndexHashTable.nullNode()) {// no candidate - // for - // clustering - // has been - // found for - // the - // cluster_1. - assert (nn_chain.size() == 1); - nn_chain.removeLast(); - continue; - } - - if (nn_chain.size() == 1) { - m_hash_table.deleteElement(candidate.cluster); - - if (candidate.distance == 0) {// coincident points. Cluster them - // at once. - // cluster xyNearestNeighbour - // with xyindex. The coordinates - // do not need to be changed, - // but weight need to be doubled - m_clusters.concatenateLists(cluster_1, candidate.cluster); - int cluster_weight_1 = m_clusters - .getFirstElement(cluster_1); - int cluster_weight_2 = m_clusters - .getFirstElement(candidate.cluster); - m_shape.setWeight( - cluster_weight_1, - m_shape.getWeight(cluster_weight_1) - + m_shape.getWeight(cluster_weight_2)); - } else - nn_chain.add(candidate.cluster); - - continue; - } - - assert (nn_chain.size() > 1); - if (nn_chain.get(nn_chain.size() - 2) == candidate.cluster) {// reciprocal - // NN - nn_chain.clear(false); - b_clustered |= mergeClusters_(cluster_1, candidate.cluster); - m_hash_table.addElement(cluster_1); - } else { - if (nn_chain_distances.get(nn_chain_distances.size()) <= candidate.distance) {// this - // neighbour - // is - // not - // better - // than - // the - // previous - // one - // (can - // happen - // when - // there - // are - // equidistant - // points). - nn_chain.clear(false); - b_clustered |= mergeClusters_(cluster_1, candidate.cluster); - m_hash_table.addElement(cluster_1); - } else { - nn_chain.add(candidate.cluster); - nn_chain_distances.add(candidate.distance); - } - } - }// while (hashTable->size() != 0 || nn_chain->size() != 0) - - if (b_clustered) { - applyClusterPositions_(); - } - - m_shape.removeUserIndex(m_hash_values); - return b_clustered; - } - boolean clusterNonReciprocal_() { int point_count = m_shape.getTotalPointCount(); - - { - int geometry = m_shape.getFirstGeometry(); - int path = m_shape.getFirstPath(geometry); - int vertex = m_shape.getFirstVertex(path); - m_shape.getXY(vertex, m_origin); + Envelope2D env = m_shape.getEnvelope2D(); + m_origin = env.getLowerLeft(); + double dim = Math.max(env.getHeight(), env.getWidth()); + double mincell = dim / (NumberUtils.intMax() - 1); + if (m_cell_size < mincell) { + m_cell_size = mincell; + m_inv_cell_size = 1.0 / m_cell_size; } // This holds clusters. - if (m_clusters == null) - m_clusters = new IndexMultiList(); - m_clusters.clear(); - m_clusters.reserveLists(m_shape.getTotalPointCount()); - m_clusters.reserveNodes(m_shape.getTotalPointCount()); + m_clusters = new IndexMultiList(); + m_clusters.reserveLists(m_shape.getTotalPointCount() / 3 + 1); + m_clusters.reserveNodes(m_shape.getTotalPointCount() / 3 + 1); m_hash_values = m_shape.createUserIndex(); + m_new_clusters = m_shape.createUserIndex(); // Make the hash table. It serves a purpose of fine grain grid. // Make it 25% larger than the 4 times point count to reduce the chance // of collision. // The 4 times comes from the fact that we check four neighbouring cells // in the grid for each point. - m_hash_function = new ClusterHashFunction(m_clusters, m_shape, - m_origin, m_tolerance, m_cell_size, m_hash_values); - m_hash_table = new IndexHashTable(point_count * 5, m_hash_function); // N - // * - // 4 - // * - // 1.25 - // = - // N - // * - // 5. + m_hash_function = new ClusterHashFunction(m_shape, m_origin, + m_sqr_tolerance, m_inv_cell_size, m_hash_values); + m_hash_table = new IndexHashTable(4 * point_count / 3, m_hash_function); m_hash_table.reserveElements(m_shape.getTotalPointCount()); - boolean b_clustered = false; // Go through all vertices stored in the m_shape and put the handles of @@ -507,44 +475,75 @@ boolean clusterNonReciprocal_() { int vertex = m_shape.getFirstVertex(path); for (int index = 0, nindex = m_shape.getPathSize(path); index < nindex; index++) { assert (vertex != -1); - int cluster = m_clusters.createList(); - m_clusters.addElement(cluster, vertex); // initially each - // cluster consist - // of a single - // vertex - int hash = m_hash_function.calculateHash(cluster); + int hash = m_hash_function + .calculate_hash_from_vertex(vertex); m_shape.setUserIndex(vertex, m_hash_values, hash); - m_hash_table.addElement(cluster); // add cluster to the hash - // table + m_hash_table.addElement(vertex, hash); // add cluster to the + // hash table + assert (m_shape.getUserIndex(vertex, m_new_clusters) == -1); vertex = m_shape.getNextVertex(vertex); } } } - // m_hash_table.dbg_print_bucket_histogram_(); + // m_hash_table->dbg_print_bucket_histogram_(); {// scope for candidates array AttributeStreamOfInt32 candidates = new AttributeStreamOfInt32(0); - while (m_hash_table.size() != 0) { - int node = m_hash_table.getAnyNode(); - assert (node != IndexHashTable.nullNode()); - int cluster_1 = m_hash_table.getElement(node); - m_hash_table.deleteNode(node); - int xyindex = m_clusters.getFirstElement(cluster_1); - collectClusterCandidates_(xyindex, candidates); - if (candidates.size() == 0) {// no candidate for clustering has - // been found for the cluster_1. - continue; - } + candidates.reserve(10); + + for (int geometry = m_shape.getFirstGeometry(); geometry != -1; geometry = m_shape + .getNextGeometry(geometry)) { + for (int path = m_shape.getFirstPath(geometry); path != -1; path = m_shape + .getNextPath(path)) { + int vertex = m_shape.getFirstVertex(path); + for (int index = 0, nindex = m_shape.getPathSize(path); index < nindex; index++) { + if (m_shape.getUserIndex(vertex, m_new_clusters) == StridedIndexTypeCollection + .impossibleIndex2()) { + vertex = m_shape.getNextVertex(vertex); + continue;// this vertex was merged with another + // cluster. It also was removed from the + // hash table. + } - for (int candidate_index = 0, ncandidates = candidates.size(); candidate_index < ncandidates; candidate_index++) { - int cluster_node = candidates.get(candidate_index); - int cluster = m_hash_table.getElement(cluster_node); - m_hash_table.deleteNode(cluster_node); - b_clustered |= mergeClusters_(cluster_1, cluster); + int hash = m_shape.getUserIndex(vertex, m_hash_values); + m_hash_table.deleteElement(vertex, hash); + + while (true) { + collectClusterCandidates_(vertex, candidates); + if (candidates.size() == 0) {// no candidate for + // clustering has + // been found for + // the cluster_1. + break; + } + + boolean clustered = false; + for (int candidate_index = 0, ncandidates = candidates + .size(); candidate_index < ncandidates; candidate_index++) { + int cluster_node = candidates + .get(candidate_index); + int other_vertex = m_hash_table + .getElement(cluster_node); + m_hash_table.deleteNode(cluster_node); + clustered |= mergeClusters_(vertex, + other_vertex, + candidate_index + 1 == ncandidates); + } + + b_clustered |= clustered; + candidates.clear(false); + // repeat search for the cluster candidates for + // cluster_1 + if (!clustered) + break;// positions did not change + } + + // m_shape->set_user_index(vertex, m_new_clusters, + // Strided_index_type_collection::impossible_index_2()); + vertex = m_shape.getNextVertex(vertex); + } } - m_hash_table.addElement(cluster_1); - candidates.clear(false); } } @@ -552,24 +551,27 @@ boolean clusterNonReciprocal_() { applyClusterPositions_(); } - // m_hash_table.reset(); - // m_hash_function.reset(); + m_hash_table = null; + m_hash_function = null; m_shape.removeUserIndex(m_hash_values); + m_shape.removeUserIndex(m_new_clusters); + // output_debug_printf("total: %d\n",m_shape->get_total_point_count()); + // output_debug_printf("clustered: %d\n",m_dbg_candidate_check_count); return b_clustered; } void applyClusterPositions_() { Point2D cluster_pt = new Point2D(); // move vertices to the clustered positions. - for (int list = m_clusters.getFirstList(); list != IndexMultiList - .nullNode(); list = m_clusters.getNextList(list)) { + for (int list = m_clusters.getFirstList(); list != -1; list = m_clusters + .getNextList(list)) { int node = m_clusters.getFirst(list); - assert (node != IndexMultiList.nullNode()); + assert (node != -1); int vertex = m_clusters.getElement(node); m_shape.getXY(vertex, cluster_pt); - for (node = m_clusters.getNext(node); node != IndexMultiList - .nullNode(); node = m_clusters.getNext(node)) { + for (node = m_clusters.getNext(node); node != -1; node = m_clusters + .getNext(node)) { int vertex_1 = m_clusters.getElement(node); m_shape.setXY(vertex_1, cluster_pt); } @@ -577,8 +579,6 @@ void applyClusterPositions_() { } Clusterer() { - m_hash_values = -1; - m_dbg_candidate_check_count = 0; } } diff --git a/src/main/java/com/esri/core/geometry/CombineOperator.java b/src/main/java/com/esri/core/geometry/CombineOperator.java index e929ca39..43e5ac2c 100644 --- a/src/main/java/com/esri/core/geometry/CombineOperator.java +++ b/src/main/java/com/esri/core/geometry/CombineOperator.java @@ -1,26 +1,26 @@ /* - Copyright 1995-2013 Esri +Copyright 1995-2013 Esri - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 +http://www.apache.org/licenses/LICENSE-2.0 - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. - For additional information, contact: - Environmental Systems Research Institute, Inc. - Attn: Contracts Dept - 380 New York Street - Redlands, California, USA 92373 +For additional information, contact: +Environmental Systems Research Institute, Inc. +Attn: Contracts Dept +380 New York Street +Redlands, California, USA 92373 - email: contracts@esri.com - */ +email: contracts@esri.com +*/ package com.esri.core.geometry; import com.esri.core.geometry.Geometry; @@ -35,11 +35,16 @@ public interface CombineOperator { /** * Operation on two geometries, returning a third. Examples include * Intersection, Difference, and so forth. - * - * @param geom1 and geom2 are the geometry instances to be operated on. * + * @param geom1 and geom2 are the geometry instances to be operated on. + * @param sr The spatial reference to get the tolerance value from. + * When sr is null, the tolerance is calculated from the input geometries. + * @param progressTracker ProgressTracker instance that is used to cancel the lengthy operation. Can be null. + * @return Returns the result geoemtry. In some cases the returned value can point to geom1 or geom2 + * instance. For example, the OperatorIntersection may return geom2 when it is completely + * inside of the geom1. */ public Geometry execute(Geometry geom1, Geometry geom2, - SpatialReference sr, ProgressTracker progressTracker); + SpatialReference sr, ProgressTracker progressTracker); } diff --git a/src/main/java/com/esri/core/geometry/ConstructOffset.java b/src/main/java/com/esri/core/geometry/ConstructOffset.java index 6565e114..89db1b0b 100644 --- a/src/main/java/com/esri/core/geometry/ConstructOffset.java +++ b/src/main/java/com/esri/core/geometry/ConstructOffset.java @@ -660,7 +660,7 @@ void _OffsetPath(MultiPath multiPath, int pathIndex, MultiPath resultingPath) { if (multiPath.isClosedPath(pathIndex)) { // check if last point is a duplicate of first Point2D ptStart = multiPath.getXY(startVertex); - while (multiPath.getXY(endVertex - 1).equals(ptStart)) + while (multiPath.getXY(endVertex - 1).isEqual(ptStart)) endVertex--; // we need at least three points for a polygon @@ -679,11 +679,11 @@ void _OffsetPath(MultiPath multiPath, int pathIndex, MultiPath resultingPath) { // remove duplicate points at extremities Point2D ptStart = multiPath.getXY(startVertex); while ((startVertex < endVertex) - && multiPath.getXY(startVertex + 1).equals(ptStart)) + && multiPath.getXY(startVertex + 1).isEqual(ptStart)) startVertex++; Point2D ptEnd = multiPath.getXY(endVertex - 1); while ((startVertex < endVertex) - && multiPath.getXY(endVertex - 2).equals(ptEnd)) + && multiPath.getXY(endVertex - 2).isEqual(ptEnd)) endVertex--; // we need at least two points for a polyline diff --git a/src/main/java/com/esri/core/geometry/CrackAndCluster.java b/src/main/java/com/esri/core/geometry/CrackAndCluster.java index cb8b63ee..2a5c6286 100644 --- a/src/main/java/com/esri/core/geometry/CrackAndCluster.java +++ b/src/main/java/com/esri/core/geometry/CrackAndCluster.java @@ -35,6 +35,30 @@ private CrackAndCluster(ProgressTracker progressTracker) { m_progressTracker = progressTracker; } + static boolean non_empty_points_need_to_cluster(double tolerance, Point pt1, Point pt2) + { + double tolerance_for_clustering = InternalUtils.adjust_tolerance_for_TE_clustering(tolerance); + return Clusterer.isClusterCandidate_(pt1.getX(), pt1.getY(), pt2.getX(), pt2.getY(), MathUtils.sqr(tolerance_for_clustering)); + } + + static Point cluster_non_empty_points(Point pt1, Point pt2, double w1, int rank1, double w2, int rank2) + { + if (rank1 > rank2) + { + return pt1; + } + else if (rank2 < rank1) + { + return pt2; + } + + int [] rank = null; + double [] w = null; + Point pt = new Point(); + Clusterer.mergeVertices(pt1, pt2, w1, rank1, w2, rank2, pt, w, rank); + return pt; + } + public static boolean execute(EditShape shape, double tolerance, ProgressTracker progressTracker) { CrackAndCluster cracker = new CrackAndCluster(progressTracker); @@ -45,41 +69,45 @@ public static boolean execute(EditShape shape, double tolerance, private boolean _cluster(double toleranceCluster) { boolean res = Clusterer.executeNonReciprocal(m_shape, toleranceCluster); - // if (false) - // { - // Geometry geometry = - // m_shape.getGeometry(m_shape.getFirstGeometry());//extract the result - // of simplify - // ((MultiPathImpl)geometry._GetImpl()).SaveToTextFileDbg("c:/temp/_simplifyDbg.txt"); - // } - return res; } - private boolean _crack() { - boolean res = Cracker.execute(m_shape, m_tolerance, m_progressTracker); - // if (false) - // { - // for (int geom = m_shape.getFirstGeometry(); geom != -1; geom = - // m_shape.getNextGeometry(geom)) - // { - // Geometry geometry = m_shape.getGeometry(geom);//extract the result of - // simplify - // ((MultiPathImpl)geometry._getImpl()).SaveToTextFileDbg("c:/temp/_simplifyDbg.txt");//NOTE: - // It ovewrites the previous one! - // } - // } - + private boolean _crack(double tolerance_for_cracking) { + boolean res = Cracker.execute(m_shape, tolerance_for_cracking, m_progressTracker); return res; } private boolean _do() { - double toleranceCluster = m_tolerance * Math.sqrt(2.0) * 1.00001; + double tol = m_tolerance; + + // Use same tolerances as ArcObjects (2 * sqrt(2) * tolerance for + // clustering) + // sqrt(2) * tolerance for cracking. + // Also, inflate the tolerances slightly to insure the simplified result + // would not change after small rounding issues. + + final double c_factor = 1e-5; + final double c_factor_for_needs_cracking = 1e-6; + double tolerance_for_clustering = InternalUtils + .adjust_tolerance_for_TE_clustering(tol); + double tolerance_for_needs_cracking = InternalUtils + .adjust_tolerance_for_TE_cracking(tol); + double tolerance_for_cracking = tolerance_for_needs_cracking + * (1.0 + c_factor); + tolerance_for_needs_cracking *= (1.0 + c_factor_for_needs_cracking); + + // Require tolerance_for_clustering > tolerance_for_cracking > + // tolerance_for_needs_cracking + assert (tolerance_for_clustering > tolerance_for_cracking); + assert (tolerance_for_cracking > tolerance_for_needs_cracking); + + // double toleranceCluster = m_tolerance * Math.sqrt(2.0) * 1.00001; boolean bChanged = false; int max_iter = m_shape.getTotalPointCount() + 10 > 30 ? 1000 : (m_shape .getTotalPointCount() + 10) * (m_shape.getTotalPointCount() + 10); int iter = 0; + boolean has_point_features = m_shape.hasPointFeatures(); for (;; iter++) { if (iter > max_iter) throw new GeometryException( @@ -87,29 +115,44 @@ private boolean _do() { // many // iterations - boolean bClustered = _cluster(toleranceCluster); // find close - // vertices and - // clamp them - // together. + boolean bClustered = _cluster(tolerance_for_clustering); // find + // close + // vertices and + // clamp them + // together. bChanged |= bClustered; - boolean bFiltered = (m_shape.filterClosePoints(toleranceCluster, - true) != 0); // remove all degenerate segments. + boolean bFiltered = (m_shape.filterClosePoints( + tolerance_for_clustering, true) != 0); // remove all + // degenerate + // segments. bChanged |= bFiltered; - // _ASSERT(!m_shape.hasDegenerateSegments(toleranceCluster)); - boolean bCracked = _crack(); // crack all segments at intersection - // points and touch points. - bChanged |= bCracked; - if (!bCracked) - break; + boolean b_cracked = false; + if (iter == 0 + || has_point_features + || Cracker.needsCracking(true, m_shape, + tolerance_for_needs_cracking, null, + m_progressTracker)) { + // Cracks only if shape contains segments. + b_cracked = _crack(tolerance_for_cracking); // crack all + // segments at + // intersection + // points and touch + // points. If + // Cracked, then the + // iteration will be + // repeated. + bChanged |= b_cracked; + } + + if (!b_cracked) + break;// was not cracked, so we can bail out. else { // Loop while cracking happens. } - if (m_progressTracker != null - && !m_progressTracker.progress(-1, -1)) - throw new UserCancelException(); + ProgressTracker.checkAndThrow(m_progressTracker); } return bChanged; diff --git a/src/main/java/com/esri/core/geometry/Cracker.java b/src/main/java/com/esri/core/geometry/Cracker.java index 822f8bbb..6f320b04 100644 --- a/src/main/java/com/esri/core/geometry/Cracker.java +++ b/src/main/java/com/esri/core/geometry/Cracker.java @@ -40,6 +40,7 @@ class Cracker { private double m_tolerance_cluster; private Treap m_sweep_structure; private SweepComparator m_sweep_comparator; + private boolean m_bAllowCoincident; boolean crackBruteForce_() { EditShape.VertexIterator iter_1 = m_shape.queryVertexIterator(false); @@ -57,9 +58,7 @@ boolean crackBruteForce_() { for (int vertex_1 = iter_1.next(); vertex_1 != -1; vertex_1 = iter_1 .next()) { - if ((m_progress_tracker != null) - && !(m_progress_tracker.progress(-1, -1))) - throw new RuntimeException("user_canceled"); + ProgressTracker.checkAndThrow(m_progress_tracker); int GT_1 = m_shape.getGeometryType(iter_1.currentGeometry()); @@ -218,11 +217,11 @@ boolean planeSweep_() { } boolean needsCrackingImpl_() { + boolean b_needs_cracking = false; + if (m_sweep_structure == null) m_sweep_structure = new Treap(); - boolean b_needs_cracking = false; - AttributeStreamOfInt32 event_q = new AttributeStreamOfInt32(0); event_q.reserve(m_shape.getTotalPointCount() + 1); @@ -237,20 +236,21 @@ boolean needsCrackingImpl_() { // create user indices to store edges that end at vertices. int edge_index_1 = m_shape.createUserIndex(); int edge_index_2 = m_shape.createUserIndex(); - m_sweep_comparator = new SweepComparator(m_shape, m_tolerance, true); + m_sweep_comparator = new SweepComparator(m_shape, m_tolerance, !m_bAllowCoincident); m_sweep_structure.setComparator(m_sweep_comparator); - AttributeStreamOfInt32 new_edges = new AttributeStreamOfInt32(0); AttributeStreamOfInt32 swept_edges_to_delete = new AttributeStreamOfInt32( 0); AttributeStreamOfInt32 edges_to_insert = new AttributeStreamOfInt32(0); // Go throught the sorted vertices int event_q_index = 0; + Point2D cluster_pt = new Point2D(); // sweep-line algorithm: for (int vertex = event_q.get(event_q_index++); vertex != -1;) { - Point2D cluster_pt = m_shape.getXY(vertex); + m_shape.getXY(vertex, cluster_pt); + do { int next_vertex = m_shape.getNextVertex(vertex); int prev_vertex = m_shape.getPrevVertex(vertex); @@ -310,16 +310,6 @@ boolean needsCrackingImpl_() { // search. { assert (new_left == -1); - // if (false) - // { - // dbg_print_sweep_structure_(); - // } - // if (false) - // { - // dbg_print_sweep_edge_(edge); - // dbg_print_sweep_edge_(left); - // dbg_print_sweep_edge_(new_left); - // } new_left = left; } @@ -328,19 +318,14 @@ boolean needsCrackingImpl_() { assert (new_right == -1); new_right = right; } - // #ifndef DEBUG - // if (new_left != -1 && new_right != -1) - // break; - // #endif +//#ifdef NDEBUG + if (new_left != -1 && new_right != -1) + break; +//#endif } assert (new_left == -1 || new_left != new_right); - // if (false) - // { - // dbg_print_sweep_structure_(); - // } - m_sweep_comparator.setSweepY(cluster_pt.y, cluster_pt.x); // Delete the edges that terminate at the cluster. @@ -348,9 +333,9 @@ boolean needsCrackingImpl_() { int edge = swept_edges_to_delete.get(i); m_sweep_structure.deleteNode(edge, -1); } - swept_edges_to_delete.resizePreserveCapacity(0); + swept_edges_to_delete.clear(false); - if (new_left != -1 && new_right != -1) { + if (!b_continuing_segment_chain_optimization && new_left != -1 && new_right != -1) { if (checkForIntersections_(new_left, new_right)) { b_needs_cracking = true; m_non_simple_result = m_sweep_comparator.getResult(); @@ -361,9 +346,8 @@ boolean needsCrackingImpl_() { for (int i = 0, n = edges_to_insert.size(); i < n; i += 2) { int v = edges_to_insert.get(i); int otherv = edges_to_insert.get(i + 1); - ; - int new_edge_1; + int new_edge_1 = -1; if (b_continuing_segment_chain_optimization) { new_edge_1 = m_sweep_structure.addElementAtPosition( new_left, new_right, v, true, true, -1); @@ -392,7 +376,6 @@ boolean needsCrackingImpl_() { // vertex. } - // DbgCheckSweepStructure(); if (m_sweep_comparator.intersectionDetected()) { m_non_simple_result = m_sweep_comparator.getResult(); b_needs_cracking = true; @@ -407,16 +390,6 @@ boolean needsCrackingImpl_() { m_shape.setUserIndex(otherv, edge_index_2, new_edge_1); } } - /* - * for (int i = 0, n = new_edges.size(); i < n; i++) { int edge = - * new_edges.get(i); int left = m_sweep_structure.get_prev(edge); - * int right = m_sweep_structure.get_next(edge); if (left != -1) { - * if (check_for_intersections_(left, edge)) { b_needs_cracking = - * true; m_non_simple_result = m_sweep_comparator->get_result(); - * break; } } if (right != -1) { if (check_for_intersections_(right, - * edge)) { b_needs_cracking = true; m_non_simple_result = - * m_sweep_comparator->get_result(); break; } } } - */ if (b_needs_cracking) break; @@ -427,9 +400,6 @@ boolean needsCrackingImpl_() { m_shape.removeUserIndex(edge_index_1); m_shape.removeUserIndex(edge_index_2); - // DEBUGPRINTF("number of compare calls: %d\n", g_dbg); - // DEBUGPRINTF("total point count: %d\n", - // m_shape->get_total_point_count()); return b_needs_cracking; } @@ -450,6 +420,7 @@ boolean checkForIntersections_(int sweep_edge_1, int sweep_edge_2) { // void dbg_check_sweep_structure_(); Cracker(ProgressTracker progress_tracker) { m_progress_tracker = progress_tracker; + m_bAllowCoincident = true; } static boolean canBeCracked(EditShape shape) { @@ -480,13 +451,7 @@ static boolean execute(EditShape shape, Envelope2D extent, { b_cracked = cracker.crackBruteForce_(); } else { - // bool b_cracked_1 = cracker.crack_quad_tree_(); - // bool b_cracked_2 = cracker.crack_quad_tree_(); - // assert(!b_cracked_2); - boolean b_cracked_1 = cracker.crackerPlaneSweep_(); - // bool b_cracked_1 = cracker.crack_quad_tree_(); - // bool b_cracked_1 = cracker.crack_envelope_2D_intersector_(); return b_cracked_1; } return b_cracked; @@ -499,7 +464,7 @@ static boolean execute(EditShape shape, double tolerance, } // Used for IsSimple. - static boolean needsCracking(EditShape shape, double tolerance, + static boolean needsCracking(boolean allowCoincident, EditShape shape, double tolerance, NonSimpleResult result, ProgressTracker progress_tracker) { if (!canBeCracked(shape)) return false; @@ -509,6 +474,7 @@ static boolean needsCracking(EditShape shape, double tolerance, cracker.m_shape = shape; cracker.m_tolerance = tolerance; cracker.m_extent = shape_env; + cracker.m_bAllowCoincident = allowCoincident; if (cracker.needsCrackingImpl_()) { if (result != null) result.Assign(cracker.m_non_simple_result); @@ -525,6 +491,7 @@ static boolean needsCracking(EditShape shape, double tolerance, cracker.m_shape = shape; cracker.m_tolerance = tolerance; cracker.m_extent = shape_env; + cracker.m_bAllowCoincident = allowCoincident; boolean b_res = cracker.needsCrackingImpl_(); transform.setSwapCoordinates(); diff --git a/src/main/java/com/esri/core/geometry/Cutter.java b/src/main/java/com/esri/core/geometry/Cutter.java index 66710733..f3f17efe 100644 --- a/src/main/java/com/esri/core/geometry/Cutter.java +++ b/src/main/java/com/esri/core/geometry/Cutter.java @@ -102,7 +102,7 @@ static class CutEvent { static EditShape CutPolyline(boolean bConsiderTouch, Polyline cuttee, Polyline cutter, double tolerance, ArrayList cutPairs, - AttributeStreamOfInt32 segmentCounts) { + AttributeStreamOfInt32 segmentCounts, ProgressTracker progressTracker) { if (cuttee.isEmpty()) { OperatorCutLocal.CutPair cutPair; cutPair = new OperatorCutLocal.CutPair(cuttee, @@ -116,7 +116,7 @@ static EditShape CutPolyline(boolean bConsiderTouch, Polyline cuttee, EditShape editShape = new EditShape(); int cutteeHandle = editShape.addGeometry(cuttee); int cutterHandle = editShape.addGeometry(cutter); - CrackAndCluster.execute(editShape, tolerance, null); + CrackAndCluster.execute(editShape, tolerance, progressTracker); int order = 0; int orderIndex = editShape.createUserIndex(); @@ -1150,7 +1150,7 @@ static boolean _cutterTangents(boolean bConsiderTouch, EditShape shape, return _cutterStartTangents(bConsiderTouch, shape, cutEvents, icutEvent, tangent0, tangent1); - throw new GeometryException("internal error"); + throw GeometryException.GeometryInternalError(); } static boolean _cutterEndTangents(boolean bConsiderTouch, EditShape shape, @@ -1423,6 +1423,6 @@ static boolean _cutteeTangents(EditShape shape, return false; } - throw new GeometryException("internal error"); + throw GeometryException.GeometryInternalError(); } } diff --git a/src/main/java/com/esri/core/geometry/EditShape.java b/src/main/java/com/esri/core/geometry/EditShape.java index 565dd98f..23f70df5 100644 --- a/src/main/java/com/esri/core/geometry/EditShape.java +++ b/src/main/java/com/esri/core/geometry/EditShape.java @@ -30,7 +30,7 @@ * geometries in linked lists. It allows constant time manipulation of geometry * vertices. */ -class EditShape { +final class EditShape { interface PathFlags_ { static final int closedPath = 1; static final int exteriorPath = 2; @@ -419,7 +419,7 @@ void splitSegmentForward_(int origin_vertex, SegmentIntersector intersector, int intersector_index) { int last_vertex = getNextVertex(origin_vertex); if (last_vertex == -1) - throw new IllegalArgumentException("internal error"); + throw GeometryException.GeometryInternalError(); Point point = getHelperPoint_(); int path = getPathFromVertex(origin_vertex); int vertex = origin_vertex; @@ -466,7 +466,8 @@ void splitSegmentBackward_(int origin_vertex, SegmentIntersector intersector, int intersector_index) { int last_vertex = getNextVertex(origin_vertex); if (last_vertex == -1) - throw new IllegalArgumentException("internal error"); + throw GeometryException.GeometryInternalError(); + Point point = getHelperPoint_(); int path = getPathFromVertex(origin_vertex); int vertex = origin_vertex; @@ -561,7 +562,7 @@ int addGeometry(Geometry geometry) { if (gt == Geometry.Type.MultiPoint) return addMultiPoint_((MultiPoint) geometry); - throw new IllegalArgumentException("internal error"); + throw GeometryException.GeometryInternalError(); } // Append a Geometry to the given geometry of the Edit_shape @@ -575,7 +576,7 @@ void appendGeometry(int dstGeometry, Geometry srcGeometry) { return; } - throw new IllegalArgumentException("internal error"); + throw GeometryException.GeometryInternalError(); } // Adds a path @@ -1075,7 +1076,7 @@ int splitSegment(int origin_vertex, double[] split_scalars, int split_count) { int actual_splits = 0; int next_vertex = getNextVertex(origin_vertex); if (next_vertex == -1) - throw new IllegalArgumentException("internal error"); + throw GeometryException.GeometryInternalError(); int vindex = getVertexIndex(origin_vertex); int vindex_next = getVertexIndex(next_vertex); @@ -1445,7 +1446,7 @@ int insertPath(int geometry, int before_path) { if (before_path != -1) { if (geometry != getGeometryFromPath(before_path)) - throw new IllegalArgumentException("internal error"); + throw GeometryException.GeometryInternalError(); prev = getPrevPath(before_path); } else @@ -1468,6 +1469,39 @@ int insertPath(int geometry, int before_path) { setGeometryPathCount_(geometry, getPathCount(geometry) + 1); return newpath; } + + int insertClosedPath_(int geometry, int before_path, int first_vertex, int checked_vertex, boolean[] contains_checked_vertex) + { + int path = insertPath(geometry, -1); + int path_size = 0; + int vertex = first_vertex; + contains_checked_vertex[0] = false; + while(true) + { + if (vertex == checked_vertex) + contains_checked_vertex[0] = true; + + setPathToVertex_(vertex, path); + path_size++; + int next = getNextVertex(vertex); + assert(getNextVertex(getPrevVertex(vertex)) == vertex); + if (next == first_vertex) + break; + + vertex = next; + } + + setClosedPath(path, true); + setPathSize_(path, path_size); + if (contains_checked_vertex[0]) + first_vertex = checked_vertex; + + setFirstVertex_(path, first_vertex); + setLastVertex_(path, getPrevVertex(first_vertex)); + setRingAreaValid_(path, false); + return path; + } + // Removes a path, gets rid of all its vertices, and returns the next one int removePath(int path) { @@ -1566,7 +1600,7 @@ void closeAllPaths(int geometry) { if (getGeometryType(geometry) == Geometry.GeometryType.Polygon) return; if (!Geometry.isLinear(getGeometryType(geometry))) - throw new IllegalArgumentException("internal error"); + throw GeometryException.GeometryInternalError(); for (int path = getFirstPath(geometry); path != -1; path = getNextPath(path)) { setClosedPath(path, true); @@ -2231,4 +2265,14 @@ void sortVerticesSimpleByX_(AttributeStreamOfInt32 points, int begin_, // The estimated size can be very slightly less than the actual size. // int estimate_memory_size() const; + boolean hasPointFeatures() + { + for (int geometry = getFirstGeometry(); geometry != -1; geometry = getNextGeometry(geometry)) + { + if (!Geometry.isMultiPath(getGeometryType(geometry))) + return true; + } + return false; + } + } diff --git a/src/main/java/com/esri/core/geometry/Envelope.java b/src/main/java/com/esri/core/geometry/Envelope.java index b684cc23..0675a1e8 100644 --- a/src/main/java/com/esri/core/geometry/Envelope.java +++ b/src/main/java/com/esri/core/geometry/Envelope.java @@ -925,7 +925,7 @@ public void centerAt(Point c) { * @return Returns the lower left corner point. */ public Point getLowerLeft() { - return m_envelope.getLowerLeft(); + return new Point(m_envelope.getLowerLeft()); } /** @@ -934,7 +934,7 @@ public Point getLowerLeft() { * @return Returns the upper right corner point. */ public Point getUpperRight() { - return m_envelope.getUpperRight(); + return new Point(m_envelope.getUpperRight()); } /** @@ -943,7 +943,7 @@ public Point getUpperRight() { * @return Returns the lower right corner point. */ public Point getLowerRight() { - return m_envelope.getLowerRight(); + return new Point(m_envelope.getLowerRight()); } /** @@ -952,7 +952,7 @@ public Point getLowerRight() { * @return Returns the upper left corner point. */ public Point getUpperLeft() { - return m_envelope.getUpperLeft(); + return new Point(m_envelope.getUpperLeft()); } /** @@ -1108,4 +1108,9 @@ public void setYMax(double y) { _touch(); m_envelope.ymax = y; } + + @Override + public Geometry getBoundary() { + return Boundary.calculate(this, null); + } } diff --git a/src/main/java/com/esri/core/geometry/Envelope1D.java b/src/main/java/com/esri/core/geometry/Envelope1D.java index 5d982bc1..6f3da52d 100644 --- a/src/main/java/com/esri/core/geometry/Envelope1D.java +++ b/src/main/java/com/esri/core/geometry/Envelope1D.java @@ -64,6 +64,7 @@ public void normalize() { public void setEmpty() { vmin = NumberUtils.NaN(); + vmax = NumberUtils.NaN(); } public boolean isEmpty() { @@ -189,4 +190,29 @@ public double getCenter() /* const */ { return 0.5 * (vmin + vmax); } + + @Override + public boolean equals(Object _other) + { + if (_other == this) + return true; + + if (!(_other instanceof Envelope1D)) + return false; + + Envelope1D other = (Envelope1D) _other; + if (isEmpty() && other.isEmpty()) + return true; + + if (vmin != other.vmin || vmax != other.vmax) + return false; + + return true; + } + + @Override + public int hashCode() { + return NumberUtils.hash(NumberUtils.hash(vmin), vmax); + } + } diff --git a/src/main/java/com/esri/core/geometry/Envelope2D.java b/src/main/java/com/esri/core/geometry/Envelope2D.java index 89dded08..a6641de9 100644 --- a/src/main/java/com/esri/core/geometry/Envelope2D.java +++ b/src/main/java/com/esri/core/geometry/Envelope2D.java @@ -332,10 +332,25 @@ public Point2D queryCorner(int index) { public void queryCorners(Point2D[] corners) { if ((corners == null) || (corners.length < 4)) throw new IllegalArgumentException(); - corners[0] = new Point2D(xmin, ymin); - corners[1] = new Point2D(xmin, ymax); - corners[2] = new Point2D(xmax, ymax); - corners[3] = new Point2D(xmax, ymin); + if (corners[0] != null) + corners[0].setCoords(xmin, ymin); + else + corners[0] = new Point2D(xmin, ymin); + + if (corners[1] != null) + corners[1].setCoords(xmin, ymax); + else + corners[1] = new Point2D(xmin, ymax); + + if (corners[2] != null) + corners[2].setCoords(xmax, ymax); + else + corners[2] = new Point2D(xmax, ymax); + + if (corners[3] != null) + corners[3].setCoords(xmax, ymin); + else + corners[3] = new Point2D(xmax, ymin); } /** @@ -346,10 +361,25 @@ public void queryCorners(Point2D[] corners) { public void queryCornersReversed(Point2D[] corners) { if (corners == null || ((corners != null) && (corners.length < 4))) throw new IllegalArgumentException(); - corners[0] = new Point2D(xmin, ymin); - corners[1] = new Point2D(xmax, ymin); - corners[2] = new Point2D(xmax, ymax); - corners[3] = new Point2D(xmin, ymax); + if (corners[0] != null) + corners[0].setCoords(xmin, ymin); + else + corners[0] = new Point2D(xmin, ymin); + + if (corners[1] != null) + corners[1].setCoords(xmax, ymin); + else + corners[1] = new Point2D(xmax, ymin); + + if (corners[2] != null) + corners[2].setCoords(xmax, ymax); + else + corners[2] = new Point2D(xmax, ymax); + + if (corners[3] != null) + corners[3].setCoords(xmin, ymax); + else + corners[3] = new Point2D(xmin, ymax); } public double getArea() { @@ -516,20 +546,20 @@ public void centerAt(Point c) { ymax = c.getY() + cy; } - public Point getLowerLeft() { - return new Point(xmin, ymin); + public Point2D getLowerLeft() { + return new Point2D(xmin, ymin); } - public Point getUpperLeft() { - return new Point(xmin, ymax); + public Point2D getUpperLeft() { + return new Point2D(xmin, ymax); } - public Point getLowerRight() { - return new Point(xmax, ymin); + public Point2D getLowerRight() { + return new Point2D(xmax, ymin); } - public Point getUpperRight() { - return new Point(xmax, ymax); + public Point2D getUpperRight() { + return new Point2D(xmax, ymax); } public boolean contains(Point p) { @@ -541,7 +571,9 @@ public boolean contains(Point2D p) { } public boolean contains(double x, double y) { - return (!isEmpty() && (x >= xmin && x <= xmax && y >= ymin && y <= ymax)); + // Note: This will return False, if envelope is empty, thus no need to + // call is_empty(). + return x >= xmin && x <= xmax && y >= ymin && y <= ymax; } /** @@ -567,7 +599,7 @@ public boolean containsExclusive(double x, double y) { * Returns True if the envelope contains the point (boundary exclusive). */ public boolean containsExclusive(Point2D pt) { - return contains(pt.x, pt.y); + return containsExclusive(pt.x, pt.y); } /** diff --git a/src/main/java/com/esri/core/geometry/Envelope2DIntersectorImpl.java b/src/main/java/com/esri/core/geometry/Envelope2DIntersectorImpl.java index 874e90c5..5fdb9fc3 100644 --- a/src/main/java/com/esri/core/geometry/Envelope2DIntersectorImpl.java +++ b/src/main/java/com/esri/core/geometry/Envelope2DIntersectorImpl.java @@ -39,17 +39,23 @@ void startConstruction() { reset_(); m_b_add_red_red = true; - if (m_envelopes_red == null) + if (m_envelopes_red == null) { + m_elements_red = new AttributeStreamOfInt32(0); m_envelopes_red = new ArrayList(0); - else + } else { + m_elements_red.resizePreserveCapacity(0); m_envelopes_red.clear(); + } } - void addEnvelope(Envelope2D envelope) { + void addEnvelope(int element, Envelope2D envelope) { if (!m_b_add_red_red) throw new GeometryException("invalid call"); - m_envelopes_red.add(envelope); + Envelope2D e = new Envelope2D(); + e.setCoords(envelope); + m_elements_red.add(element); + m_envelopes_red.add(e); } void endConstruction() { @@ -68,17 +74,23 @@ void startRedConstruction() { reset_(); m_b_add_red = true; - if (m_envelopes_red == null) + if (m_envelopes_red == null) { + m_elements_red = new AttributeStreamOfInt32(0); m_envelopes_red = new ArrayList(0); - else + } else { + m_elements_red.resizePreserveCapacity(0); m_envelopes_red.clear(); + } } - void addRedEnvelope(Envelope2D red_envelope) { + void addRedEnvelope(int element, Envelope2D red_envelope) { if (!m_b_add_red) throw new GeometryException("invalid call"); - m_envelopes_red.add(red_envelope); + Envelope2D e = new Envelope2D(); + e.setCoords(red_envelope); + m_elements_red.add(element); + m_envelopes_red.add(e); } void endRedConstruction() { @@ -104,17 +116,23 @@ void startBlueConstruction() { reset_(); m_b_add_blue = true; - if (m_envelopes_blue == null) + if (m_envelopes_blue == null) { + m_elements_blue = new AttributeStreamOfInt32(0); m_envelopes_blue = new ArrayList(0); - else + } else { + m_elements_blue.resizePreserveCapacity(0); m_envelopes_blue.clear(); + } } - void addBlueEnvelope(Envelope2D blue_envelope) { + void addBlueEnvelope(int element, Envelope2D blue_envelope) { if (!m_b_add_blue) throw new GeometryException("invalid call"); - m_envelopes_blue.add(blue_envelope); + Envelope2D e = new Envelope2D(); + e.setCoords(blue_envelope); + m_elements_blue.add(element); + m_envelopes_blue.add(e); } void endBlueConstruction() { @@ -202,7 +220,7 @@ boolean next() { b_searching = resetBlue_(); break; default: - throw new GeometryException("internal error"); + throw GeometryException.GeometryInternalError(); } } @@ -250,9 +268,22 @@ Envelope2D getBlueEnvelope(int handle_b) { return m_envelopes_blue.get(handle_b); } + /* + * Returns the user element associated with handle_a. + */ + int getRedElement(int handle_a) { + return m_elements_red.read(handle_a); + } + + /* + * Returns the user element associated with handle_b. + */ + + int getBlueElement(int handle_b) { + return m_elements_blue.read(handle_b); + } + private double m_tolerance; - private ArrayList m_envelopes_red; - private ArrayList m_envelopes_blue; private int m_sweep_index_red; private int m_sweep_index_blue; private int m_envelope_handle_a; @@ -263,6 +294,11 @@ Envelope2D getBlueEnvelope(int handle_b) { private IntervalTreeImpl.IntervalTreeIteratorImpl m_iterator_blue; private Envelope2D m_envelope_helper = new Envelope2D(); + private ArrayList m_envelopes_red; + private ArrayList m_envelopes_blue; + private AttributeStreamOfInt32 m_elements_red; + private AttributeStreamOfInt32 m_elements_blue; + private AttributeStreamOfInt32 m_sorted_end_indices_red; private AttributeStreamOfInt32 m_sorted_end_indices_blue; diff --git a/src/main/java/com/esri/core/geometry/Envelope3D.java b/src/main/java/com/esri/core/geometry/Envelope3D.java index 37edf611..18fd6515 100644 --- a/src/main/java/com/esri/core/geometry/Envelope3D.java +++ b/src/main/java/com/esri/core/geometry/Envelope3D.java @@ -28,7 +28,7 @@ /** * A class that represents axis parallel 3D rectangle. */ -final class Envelope3D { +public final class Envelope3D { public double xmin; public double ymin; @@ -170,7 +170,7 @@ public void merge(double x, double y, double z) { public void merge(Point3D pt) { merge(pt.x, pt.y, pt.z); } - + public void merge(Envelope3D other) { if (other.isEmpty()) return; diff --git a/src/main/java/com/esri/core/geometry/GeodeticCurveType.java b/src/main/java/com/esri/core/geometry/GeodeticCurveType.java index b39d57fd..b07a73e1 100644 --- a/src/main/java/com/esri/core/geometry/GeodeticCurveType.java +++ b/src/main/java/com/esri/core/geometry/GeodeticCurveType.java @@ -43,4 +43,9 @@ interface GeodeticCurveType { */ public final static int GreatElliptic = 2; public final static int NormalSection = 3; + /*The ShapePreserving type means the segments shapes are preserved in the spatial reference where they are defined. + *The behavior of the ShapePreserving type can be emulated by densifying the geometry with a small step, and then calling a geodetic method + *using Geodesic or GreatElliptic curve types. + */ + public final static int ShapePreserving = 4; } diff --git a/src/main/java/com/esri/core/geometry/Geometry.java b/src/main/java/com/esri/core/geometry/Geometry.java index fbbee9f3..08e15e40 100644 --- a/src/main/java/com/esri/core/geometry/Geometry.java +++ b/src/main/java/com/esri/core/geometry/Geometry.java @@ -26,8 +26,7 @@ package com.esri.core.geometry; import com.esri.core.geometry.VertexDescription.Semantics; -import com.esri.core.geometry.GeometryEngine; -import com.esri.core.geometry.WktExportFlags; + import java.io.ObjectStreamException; import java.io.Serializable; @@ -127,7 +126,7 @@ public int value() { * @return Returns the geometry type. */ public abstract Geometry.Type getType(); - + /** * Returns the topological dimension of the geometry object based on the * geometry's type. @@ -503,18 +502,21 @@ public Geometry copy() { return geom; } + /** + * Returns boundary of this geometry. + * + * Polygon and Envelope boundary is a Polyline. For Polyline and Line, the + * boundary is a Multi_point consisting of path endpoints. For Multi_point + * and Point NULL is returned. + */ + public abstract Geometry getBoundary(); + static Geometry _clone(Geometry src) { Geometry geom = src.createInstance(); src.copyTo(geom); return geom; } - public String toString() { - String snippet = GeometryEngine.geometryToWkt(this, WktExportFlags.wktExportDefaults); - if (snippet.length() > 200) { snippet = snippet.substring(0, 197)+"..."; } - return String.format("%s: %s", this.getClass().getSimpleName(), snippet); - } - /** * The stateFlag value changes with changes applied to this geometry. This * allows the user to keep track of the geometry's state. @@ -551,4 +553,19 @@ Object writeReplace() throws ObjectStreamException { geomSerializer.setGeometryByValue(this); return geomSerializer; } + + /** + * The output of this method can be only used for debugging. It is subject to change without notice. + */ + @Override + public String toString() { + String snippet = OperatorExportToJson.local().execute(null, this); + if (snippet.length() > 200) { + return snippet.substring(0, 197) + "... ("+snippet.length()+" characters)"; + } + else { + return snippet; + } + } + } diff --git a/src/main/java/com/esri/core/geometry/GeometryAccelerators.java b/src/main/java/com/esri/core/geometry/GeometryAccelerators.java index 8208e1a2..756a764f 100644 --- a/src/main/java/com/esri/core/geometry/GeometryAccelerators.java +++ b/src/main/java/com/esri/core/geometry/GeometryAccelerators.java @@ -23,6 +23,8 @@ */ package com.esri.core.geometry; +import java.util.ArrayList; + class GeometryAccelerators { // /** @@ -39,6 +41,7 @@ class GeometryAccelerators { private RasterizedGeometry2D m_rasterizedGeometry; private QuadTreeImpl m_quad_tree; + private ArrayList m_path_envelopes; public RasterizedGeometry2D getRasterizedGeometry() { return m_rasterizedGeometry; @@ -48,6 +51,10 @@ public QuadTreeImpl getQuadTree() { return m_quad_tree; } + public ArrayList getPathEnvelopes() { + return m_path_envelopes; + } + void _setRasterizedGeometry(RasterizedGeometry2D rg) { m_rasterizedGeometry = rg; } @@ -55,4 +62,39 @@ void _setRasterizedGeometry(RasterizedGeometry2D rg) { void _setQuadTree(QuadTreeImpl quad_tree) { m_quad_tree = quad_tree; } + + void _setPathEnvelopes(ArrayList pe) { + m_path_envelopes = pe; + } + + static boolean canUseRasterizedGeometry(Geometry geom) { + if (geom.isEmpty() + || !(geom.getType() == Geometry.Type.Polyline || geom.getType() == Geometry.Type.Polygon)) { + return false; + } + + return true; + } + + static boolean canUseQuadTree(Geometry geom) { + if (geom.isEmpty() + || !(geom.getType() == Geometry.Type.Polyline || geom.getType() == Geometry.Type.Polygon)) { + return false; + } + + if (((MultiVertexGeometry) geom).getPointCount() < 20) { + return false; + } + + return true; + } + + static boolean canUsePathEnvelopes(Geometry geom) { + if (geom.isEmpty() + || !(geom.getType() == Geometry.Type.Polyline || geom.getType() == Geometry.Type.Polygon)) { + return false; + } + + return true; + } } diff --git a/src/main/java/com/esri/core/geometry/GeometryEngine.java b/src/main/java/com/esri/core/geometry/GeometryEngine.java index c8fec93c..66406448 100644 --- a/src/main/java/com/esri/core/geometry/GeometryEngine.java +++ b/src/main/java/com/esri/core/geometry/GeometryEngine.java @@ -24,9 +24,12 @@ package com.esri.core.geometry; +import java.io.IOException; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.util.ArrayList; + +import org.codehaus.jackson.JsonParseException; import org.codehaus.jackson.JsonParser; import org.json.JSONException; @@ -53,6 +56,23 @@ public static MapGeometry jsonToGeometry(JsonParser json) { return geom; } + /** + * Imports the MapGeometry from its JSON representation. M and Z values are + * not imported from JSON representation. + * + * @param json + * The JSON representation of the geometry (with spatial + * reference). + * @return The MapGeometry instance containing the imported geometry and its + * spatial reference. + * @throws IOException + * @throws JsonParseException + */ + public static MapGeometry jsonToGeometry(String json) throws JsonParseException, IOException { + MapGeometry geom = OperatorImportFromJson.local().execute(Geometry.Type.Unknown, json); + return geom; + } + /** * Exports the specified geometry instance to it's JSON representation. * @@ -238,38 +258,6 @@ public static Geometry union(Geometry[] geometries, return result.next(); } - // TODO Remove this method from geometry engine - /** - * constructs the set-theoretic difference between an array of geometries - * and another geometry. The dimension of the input geometry has to be equal - * to or greater than that of any element in the array of "geometries". - * - * @param inputGeometries - * an array of geometry objects being subtracted - * @param subtractor - * geometry object to subtract from - * @return any array of geometry objects showing the difference - * */ - static Geometry[] difference(Geometry[] inputGeometries, - Geometry subtractor, SpatialReference spatialReference) { - OperatorDifference op = (OperatorDifference) factory - .getOperator(Operator.Type.Difference); - SimpleGeometryCursor inputGeometriesCursor = new SimpleGeometryCursor( - inputGeometries); - SimpleGeometryCursor subtractorCursor = new SimpleGeometryCursor( - subtractor); - GeometryCursor result = op.execute(inputGeometriesCursor, - subtractorCursor, spatialReference, null); - - ArrayList resultGeoms = new ArrayList(); - Geometry g; - while ((g = result.next()) != null) { - resultGeoms.add(g); - } - Geometry[] resultarr = resultGeoms.toArray(new Geometry[0]); - return resultarr; - } - /** * Creates the difference of two geometries. The dimension of geometry2 has * to be equal to or greater than that of geometry1. diff --git a/src/main/java/com/esri/core/geometry/GeometryException.java b/src/main/java/com/esri/core/geometry/GeometryException.java index 23e86829..07b11ffd 100644 --- a/src/main/java/com/esri/core/geometry/GeometryException.java +++ b/src/main/java/com/esri/core/geometry/GeometryException.java @@ -52,4 +52,7 @@ public class GeometryException extends RuntimeException { internalCode = sgCode; } + static GeometryException GeometryInternalError() { + return new GeometryException("internal error"); + } } diff --git a/src/main/java/com/esri/core/geometry/IndexHashTable.java b/src/main/java/com/esri/core/geometry/IndexHashTable.java index c9189770..4e60b59e 100644 --- a/src/main/java/com/esri/core/geometry/IndexHashTable.java +++ b/src/main/java/com/esri/core/geometry/IndexHashTable.java @@ -23,7 +23,9 @@ */ package com.esri.core.geometry; -class IndexHashTable { +import java.util.Arrays; + +final class IndexHashTable { // The hash function abstract class that user need to define to use the // IndexHashTable. public static abstract class HashFunction { @@ -38,6 +40,8 @@ public static abstract class HashFunction { int m_random; AttributeStreamOfInt32 m_hashBuckets; + int [] m_bit_filter; //this is aimed to speedup the find + //operation and allows to have less buckets. IndexMultiList m_lists; HashFunction m_hash; @@ -47,6 +51,7 @@ public IndexHashTable(int size, HashFunction hashFunction) { m_hashBuckets = new AttributeStreamOfInt32(size, nullNode()); m_lists = new IndexMultiList(); m_hash = hashFunction; + m_bit_filter = new int[(size*10+31) >> 5]; //10 times more bits than buckets } public void reserveElements(int capacity) { @@ -55,35 +60,76 @@ public void reserveElements(int capacity) { } // Adds new element to the hash table. - public void addElement(int element) { + public int addElement(int element, int hash) { + int bit_bucket = hash % (m_bit_filter.length << 5); + m_bit_filter[(bit_bucket >> 5)] |= (1 << (bit_bucket & 0x1F)); + int bucket = hash % m_hashBuckets.size(); + int list = m_hashBuckets.get(bucket); + if (list == -1) { + list = m_lists.createList(); + m_hashBuckets.set(bucket, list); + } + int node = m_lists.addElement(list, element); + return node; + } + + public int addElement(int element) { int hash = m_hash.getHash(element); + int bit_bucket = hash % (m_bit_filter.length << 5); + m_bit_filter[(bit_bucket >> 5)] |= (1 << (bit_bucket & 0x1F)); int bucket = hash % m_hashBuckets.size(); int list = m_hashBuckets.get(bucket); - if (list == IndexMultiList.nullNode()) { + if (list == -1) { list = m_lists.createList(); m_hashBuckets.set(bucket, list); } - m_lists.addElement(list, element); + int node = m_lists.addElement(list, element); + return node; } + public void deleteElement(int element, int hash) { + int bucket = hash % m_hashBuckets.size(); + int list = m_hashBuckets.get(bucket); + if (list == -1) + throw new IllegalArgumentException(); + + int ptr = m_lists.getFirst(list); + int prev = -1; + while (ptr != -1) { + int e = m_lists.getElement(ptr); + int nextptr = m_lists.getNext(ptr); + if (e == element) { + m_lists.deleteElement(list, prev, ptr); + if (m_lists.getFirst(list) == -1) { + m_lists.deleteList(list);// do not keep empty lists + m_hashBuckets.set(bucket, -1); + } + } else { + prev = ptr; + } + ptr = nextptr; + } + + } + // Removes element from the hash table. public void deleteElement(int element) { int hash = m_hash.getHash(element); int bucket = hash % m_hashBuckets.size(); int list = m_hashBuckets.get(bucket); - if (list == IndexMultiList.nullNode()) + if (list == -1) throw new IllegalArgumentException(); int ptr = m_lists.getFirst(list); - int prev = IndexMultiList.nullNode(); - while (ptr != IndexMultiList.nullNode()) { + int prev = -1; + while (ptr != -1) { int e = m_lists.getElement(ptr); int nextptr = m_lists.getNext(ptr); if (e == element) { m_lists.deleteElement(list, prev, ptr); - if (m_lists.getFirst(list) == IndexMultiList.nullNode()) { + if (m_lists.getFirst(list) == -1) { m_lists.deleteList(list);// do not keep empty lists - m_hashBuckets.set(bucket, IndexMultiList.nullNode()); + m_hashBuckets.set(bucket, -1); } } else { prev = ptr; @@ -96,10 +142,14 @@ public void deleteElement(int element) { // Returns the first node in the hash table bucket defined by the given // hashValue. public int getFirstInBucket(int hashValue) { + int bit_bucket = hashValue % (m_bit_filter.length << 5); + if ((m_bit_filter[(bit_bucket >> 5)] & (1 << (bit_bucket & 0x1F))) == 0) + return -1; + int bucket = hashValue % m_hashBuckets.size(); int list = m_hashBuckets.get(bucket); - if (list == IndexMultiList.nullNode()) - return IndexMultiList.nullNode(); + if (list == -1) + return -1; return m_lists.getFirst(list); @@ -115,13 +165,8 @@ public int getNextInBucket(int elementHandle) { // the given one. public int findNode(int element) { int hash = m_hash.getHash(element); - int bucket = hash % m_hashBuckets.size(); - int list = m_hashBuckets.get(bucket); - if (list == IndexMultiList.nullNode()) - return IndexMultiList.nullNode(); - - int ptr = m_lists.getFirst(list); - while (ptr != IndexMultiList.nullNode()) { + int ptr = getFirstInBucket(hash); + while (ptr != -1) { int e = m_lists.getElement(ptr); if (m_hash.equal(e, element)) { return ptr; @@ -129,7 +174,7 @@ public int findNode(int element) { ptr = m_lists.getNext(ptr); } - return IndexMultiList.nullNode(); + return -1; } @@ -137,13 +182,8 @@ public int findNode(int element) { // the given element descriptor. public int findNode(Object elementDescriptor) { int hash = m_hash.getHash(elementDescriptor); - int bucket = hash % m_hashBuckets.size(); - int list = m_hashBuckets.get(bucket); - if (list == IndexMultiList.nullNode()) - return IndexMultiList.nullNode(); - - int ptr = m_lists.getFirst(list); - while (ptr != IndexMultiList.nullNode()) { + int ptr = getFirstInBucket(hash);; + while (ptr != -1) { int e = m_lists.getElement(ptr); if (m_hash.equal(elementDescriptor, e)) { return ptr; @@ -151,7 +191,7 @@ public int findNode(Object elementDescriptor) { ptr = m_lists.getNext(ptr); } - return IndexMultiList.nullNode(); + return -1; } @@ -159,7 +199,7 @@ public int findNode(Object elementDescriptor) { public int getNextNode(int elementHandle) { int element = m_lists.getElement(elementHandle); int ptr = m_lists.getNext(elementHandle); - while (ptr != IndexMultiList.nullNode()) { + while (ptr != -1) { int e = m_lists.getElement(ptr); if (m_hash.equal(e, element)) { return ptr; @@ -167,7 +207,7 @@ public int getNextNode(int elementHandle) { ptr = m_lists.getNext(ptr); } - return IndexMultiList.nullNode(); + return -1; } @@ -177,17 +217,17 @@ public void deleteNode(int node) { int hash = m_hash.getHash(element); int bucket = hash % m_hashBuckets.size(); int list = m_hashBuckets.get(bucket); - if (list == IndexMultiList.nullNode()) + if (list == -1) throw new IllegalArgumentException(); int ptr = m_lists.getFirst(list); - int prev = IndexMultiList.nullNode(); - while (ptr != IndexMultiList.nullNode()) { + int prev = -1; + while (ptr != -1) { if (ptr == node) { m_lists.deleteElement(list, prev, ptr); - if (m_lists.getFirst(list) == IndexMultiList.nullNode()) { + if (m_lists.getFirst(list) == -1) { m_lists.deleteList(list);// do not keep empty lists - m_hashBuckets.set(bucket, IndexMultiList.nullNode()); + m_hashBuckets.set(bucket, -1); } return; } @@ -217,11 +257,12 @@ public int getAnyNode() { } public static int nullNode() { - return IndexMultiList.nullNode(); + return -1; } // Removes all elements from the hash table. public void clear() { + Arrays.fill(m_bit_filter, 0); m_hashBuckets = new AttributeStreamOfInt32(m_hashBuckets.size(), nullNode()); m_lists.clear(); diff --git a/src/main/java/com/esri/core/geometry/InternalUtils.java b/src/main/java/com/esri/core/geometry/InternalUtils.java index 8ba01659..90c84f48 100644 --- a/src/main/java/com/esri/core/geometry/InternalUtils.java +++ b/src/main/java/com/esri/core/geometry/InternalUtils.java @@ -130,6 +130,10 @@ static double calculateToleranceFromGeometry(SpatialReference sr, return Math.max(stolerance, gtolerance); } + static double adjust_tolerance_for_TE_clustering(double tol) { return 2.0 * Math.sqrt(2.0) * tol; } + + static double adjust_tolerance_for_TE_cracking(double tol) { return Math.sqrt(2.0) * tol; } + static double calculateToleranceFromGeometry(SpatialReference sr, Geometry geometry, boolean bConservative) { Envelope2D env2D = new Envelope2D(); @@ -249,7 +253,7 @@ static QuadTreeImpl buildQuadTree(MultiPathImpl multipathImpl) { if (hint_index == -1) { if (resized_extent) - throw new GeometryException("internal error"); + throw GeometryException.GeometryInternalError(); // resize extent multipathImpl.calculateEnvelope2D(extent, false); @@ -286,7 +290,7 @@ static QuadTreeImpl buildQuadTree(MultiPathImpl multipathImpl, if (hint_index == -1) { if (resized_extent) - throw new GeometryException("internal error"); + throw GeometryException.GeometryInternalError(); // resize extent multipathImpl.calculateEnvelope2D(extent, false); @@ -317,7 +321,7 @@ static QuadTreeImpl buildQuadTree(MultiPointImpl multipointImpl) { if (element_handle == -1) { if (resized_extent) - throw new GeometryException("internal error"); + throw GeometryException.GeometryInternalError(); // resize extent multipointImpl.calculateEnvelope2D(extent, false); @@ -348,7 +352,7 @@ static QuadTreeImpl buildQuadTree(MultiPointImpl multipointImpl, if (element_handle == -1) { if (resized_extent) - throw new GeometryException("internal error"); + throw GeometryException.GeometryInternalError(); // resize extent resized_extent = true; @@ -365,8 +369,7 @@ static QuadTreeImpl buildQuadTree(MultiPointImpl multipointImpl, static Envelope2DIntersectorImpl getEnvelope2DIntersector( MultiPathImpl multipathImplA, MultiPathImpl multipathImplB, - double tolerance, AttributeStreamOfInt32 verticesA, - AttributeStreamOfInt32 verticesB) { + double tolerance) { Envelope2D env_a = new Envelope2D(), env_b = new Envelope2D(); multipathImplA.queryLooseEnvelope2D(env_a); multipathImplB.queryLooseEnvelope2D(env_b); @@ -396,8 +399,7 @@ static Envelope2DIntersectorImpl getEnvelope2DIntersector( b_found_red = true; Envelope2D env = new Envelope2D(); env.setCoords(env_a); - intersector.addRedEnvelope(env); - verticesA.add(segIterA.getStartPointIndex()); + intersector.addRedEnvelope(segIterA.getStartPointIndex(), env); } } intersector.endRedConstruction(); @@ -418,8 +420,7 @@ static Envelope2DIntersectorImpl getEnvelope2DIntersector( b_found_blue = true; Envelope2D env = new Envelope2D(); env.setCoords(env_b); - intersector.addBlueEnvelope(env); - verticesB.add(segIterB.getStartPointIndex()); + intersector.addBlueEnvelope(segIterB.getStartPointIndex(), env); } } intersector.endBlueConstruction(); @@ -430,10 +431,9 @@ static Envelope2DIntersectorImpl getEnvelope2DIntersector( return intersector; } - static Envelope2DIntersectorImpl getEnvelope2DIntersectorForOGCParts( + static Envelope2DIntersectorImpl getEnvelope2DIntersectorForParts( MultiPathImpl multipathImplA, MultiPathImpl multipathImplB, - double tolerance, AttributeStreamOfInt32 parts_a, - AttributeStreamOfInt32 parts_b) { + double tolerance, boolean bExteriorOnlyA, boolean bExteriorOnlyB) { int type_a = multipathImplA.getType().value(); int type_b = multipathImplB.getType().value(); @@ -447,54 +447,94 @@ static Envelope2DIntersectorImpl getEnvelope2DIntersectorForOGCParts( envInter.setCoords(env_a); envInter.intersect(env_b); + GeometryAccelerators accel_a = multipathImplA._getAccelerators(); + ArrayList path_envelopes_a = null; + + if (accel_a != null) { + path_envelopes_a = accel_a.getPathEnvelopes(); + } + Envelope2DIntersectorImpl intersector = new Envelope2DIntersectorImpl(); intersector.setTolerance(tolerance); boolean b_found_red = false; intersector.startRedConstruction(); for (int ipath_a = 0; ipath_a < multipathImplA.getPathCount(); ipath_a++) { - if (type_a == Geometry.GeometryType.Polygon - && !multipathImplA.isExteriorRing(ipath_a)) + if (bExteriorOnlyA && type_a == Geometry.GeometryType.Polygon + && !multipathImplA.isExteriorRing(ipath_a)) { continue; + } - multipathImplA.queryPathEnvelope2D(ipath_a, env_a); + if (path_envelopes_a != null) { + Envelope2D env = path_envelopes_a.get(ipath_a); - if (!env_a.isIntersecting(envInter)) - continue; + if (!env.isIntersecting(envInter)) { + continue; + } + + b_found_red = true; + intersector.addRedEnvelope(ipath_a, env); + } else { + multipathImplA.queryPathEnvelope2D(ipath_a, env_a); - b_found_red = true; - Envelope2D env = new Envelope2D(); - env.setCoords(env_a); - intersector.addRedEnvelope(env); - parts_a.add(ipath_a); + if (!env_a.isIntersecting(envInter)) { + continue; + } + + b_found_red = true; + Envelope2D env = new Envelope2D(); + env.setCoords(env_a); + intersector.addRedEnvelope(ipath_a, env); + } } intersector.endRedConstruction(); - if (!b_found_red) + if (!b_found_red) { return null; + } + + GeometryAccelerators accel_b = multipathImplB._getAccelerators(); + ArrayList path_envelopes_b = null; + + if (accel_b != null) { + path_envelopes_b = accel_b.getPathEnvelopes(); + } boolean b_found_blue = false; intersector.startBlueConstruction(); for (int ipath_b = 0; ipath_b < multipathImplB.getPathCount(); ipath_b++) { - if (type_b == Geometry.GeometryType.Polygon - && !multipathImplB.isExteriorRing(ipath_b)) + if (bExteriorOnlyB && type_b == Geometry.GeometryType.Polygon + && !multipathImplB.isExteriorRing(ipath_b)) { continue; + } - multipathImplB.queryPathEnvelope2D(ipath_b, env_b); + if (path_envelopes_b != null) { + Envelope2D env = path_envelopes_b.get(ipath_b); - if (!env_b.isIntersecting(envInter)) - continue; + if (!env.isIntersecting(envInter)) { + continue; + } + + b_found_blue = true; + intersector.addBlueEnvelope(ipath_b, env); + } else { + multipathImplB.queryPathEnvelope2D(ipath_b, env_b); - b_found_blue = true; - Envelope2D env = new Envelope2D(); - env.setCoords(env_b); - intersector.addBlueEnvelope(env); - parts_b.add(ipath_b); + if (!env_b.isIntersecting(envInter)) { + continue; + } + + b_found_blue = true; + Envelope2D env = new Envelope2D(); + env.setCoords(env_b); + intersector.addBlueEnvelope(ipath_b, env); + } } intersector.endBlueConstruction(); - if (!b_found_blue) + if (!b_found_blue) { return null; + } return intersector; } diff --git a/src/main/java/com/esri/core/geometry/IntervalTreeImpl.java b/src/main/java/com/esri/core/geometry/IntervalTreeImpl.java index 4dd5594c..1237e798 100644 --- a/src/main/java/com/esri/core/geometry/IntervalTreeImpl.java +++ b/src/main/java/com/esri/core/geometry/IntervalTreeImpl.java @@ -112,7 +112,7 @@ int next() { b_searching = initialize_(); break; default: - throw new GeometryException("internal error"); + throw GeometryException.GeometryInternalError(); } } diff --git a/src/main/java/com/esri/core/geometry/JsonStringWriter.java b/src/main/java/com/esri/core/geometry/JsonStringWriter.java index dd4cf251..135b8463 100644 --- a/src/main/java/com/esri/core/geometry/JsonStringWriter.java +++ b/src/main/java/com/esri/core/geometry/JsonStringWriter.java @@ -247,7 +247,7 @@ private void next_(int action) { elementEnd_(action); break; default: - throw new GeometryException("internal error"); + throw GeometryException.GeometryInternalError(); } } diff --git a/src/main/java/com/esri/core/geometry/Line.java b/src/main/java/com/esri/core/geometry/Line.java index f4f2db08..881cdcef 100644 --- a/src/main/java/com/esri/core/geometry/Line.java +++ b/src/main/java/com/esri/core/geometry/Line.java @@ -26,6 +26,7 @@ package com.esri.core.geometry; import com.esri.core.geometry.VertexDescription.Semantics; + import java.io.Serializable; /** @@ -80,13 +81,6 @@ boolean _isDegenerate(double tolerance) { return calculateLength2D() <= tolerance; } - @Override - double _calculateSubLength(double t) { - Point2D pt = getCoord2D(t); - pt.sub(getStartXY()); - return pt.length(); - } - // HEADER DEF // Cpp @@ -185,6 +179,16 @@ public Geometry createInstance() { * ((m_yEnd - yorg) + (m_yStart - yorg)) * 0.5; } + @Override + double tToLength(double t) { + return t * calculateLength2D(); + } + + @Override + double lengthToT(double len) { + return len / calculateLength2D(); + } + double getCoordX_(double t) { // double x = m_x_end * t + (1.0 - t) * m_x_start; < 200) { + return snippet.substring(0, 197) + "... ("+snippet.length()+" characters)"; + } + else { + return snippet; + } + } + + @Override + public boolean equals(Object other) { + if (other == null) + return false; + + if (other == this) + return true; + + if (other.getClass() != getClass()) + return false; + + MapGeometry omg = (MapGeometry)other; + SpatialReference sr = getSpatialReference(); + Geometry g = getGeometry(); + SpatialReference osr = omg.getSpatialReference(); + Geometry og = omg.getGeometry(); + + if (sr != osr) { + if (sr == null || !sr.equals(osr)) + return false; + } + + if (g != og) { + if (g == null || !g.equals(og)) + return false; + } + + return true; + } + + @Override + public int hashCode() { + SpatialReference sr = getSpatialReference(); + Geometry g = getGeometry(); + int hc = 0x2937912; + if (sr != null) + hc ^= sr.hashCode(); + if (g != null) + hc ^= g.hashCode(); + + return hc; + } } diff --git a/src/main/java/com/esri/core/geometry/MathUtils.java b/src/main/java/com/esri/core/geometry/MathUtils.java index 7fca28eb..f6c9311a 100644 --- a/src/main/java/com/esri/core/geometry/MathUtils.java +++ b/src/main/java/com/esri/core/geometry/MathUtils.java @@ -128,6 +128,13 @@ static double copySign(double x, double y) { return y >= 0.0 ? Math.abs(x) : -Math.abs(x); } + /** + * Calculates sign of the given value. Returns 0 if the value is equal to 0. + */ + static int sign(double value) { + return value < 0 ? -1 : (value > 0) ? 1 : 0; + } + /** * C fmod function. */ diff --git a/src/main/java/com/esri/core/geometry/MultiPath.java b/src/main/java/com/esri/core/geometry/MultiPath.java index 34cae2b0..3dbda88d 100644 --- a/src/main/java/com/esri/core/geometry/MultiPath.java +++ b/src/main/java/com/esri/core/geometry/MultiPath.java @@ -152,6 +152,10 @@ public void queryEnvelope(Envelope env) { public void queryEnvelope2D(Envelope2D env) { m_impl.queryEnvelope2D(env); } + + public void queryPathEnvelope2D(int pathIndex, Envelope2D env) { + m_impl.queryPathEnvelope2D(pathIndex, env); + } @Override void queryEnvelope3D(Envelope3D env) { @@ -179,7 +183,8 @@ public void copyTo(Geometry dst) { m_impl.copyTo((Geometry) dst._getImpl()); } - Geometry getBoundary() { + @Override + public Geometry getBoundary() { return m_impl.getBoundary(); } @@ -187,6 +192,10 @@ Geometry getBoundary() { public void queryCoordinates(Point2D[] dst) { m_impl.queryCoordinates(dst); } + + public void queryCoordinates(Point2D[] dst, int dstSize, int beginIndex, int endIndex) { + m_impl.queryCoordinates(dst, dstSize, beginIndex, endIndex); + } @Override void queryCoordinates(Point3D[] dst) { @@ -636,31 +645,6 @@ boolean hasNonLinearSegments(int pathIndex) { return m_impl.hasNonLinearSegments(pathIndex); } - // //Elliptic Arc - // //returns true, if parameters of the arc are correct (conform with - // equation of elliptic arc) - // //returns false, if parameters are incorrect. - // //In the latter case the command will be executed anyway, however, the - // EllipseAxes will be automatically corrected - // //to conform with equation of the elliptic arc. - // //Elliptic arcs are stored as rational bezier curves. Use - // CalcEllipticArcParams function - // //to calculate geometric parameters of arc - // //bCW - clockwise (true) or anti-clockwise (false). - // //The type parameter helps to resolve situation, when start and end point - // of arc are too close. - // //It gives a hint to the function how to deal with the situation, when - // start point equal or almost equal to the end point. - // //If type == unknownArc and EndPoint == start point, the arc is a point. - // //If type == minorArc, and abs(SweepAngle - 2 * PI) < 0.001 the arc is - // replaced by the line from start point to EndPoint. - // //If type == majorArc, and abs(SweepAngle) < 0.001 the SweepAngle is - // replaced by SweepAngle + (bCW ? -1. : 1.) * 2. * PI. - // //Here SweepAngle is calculated sweep angle of the arc. - // boolean ArcTo(Point2D endPoint, Point2D ellipseAxes, Point2D center, - // double axisXRotationRad, boolean bCW, enum enumArcType type = - // unknownArc); - /** * Adds a rectangular closed Path to the MultiPathImpl. * @@ -685,22 +669,6 @@ public void addEnvelope(Envelope envSrc, boolean bReverse) { m_impl.addEnvelope(envSrc, bReverse); } - // void AddRoundRect(Envelope2D envSrc, double EllipseWidth, double - // EllipseHeight, boolean bReverse); - - // //Adds ellipse with center point Center and rotation angle between axis X - // and ellipse axes Axes.X equal to RotationAngle. - // boolean AddEllipse(Point2D center, Point2D axes, double rotationAngle, - // boolean bReverse); - - // //adds a pie - a closed figure, consisting of two lines and a segment of - // an ellipse - // //pie is drawn always ccw (when axis X is left-right, axis Y is - // bottom-up). negative sweep angle is converted to positive. - // //angles greater than 2 * pi are converted back into the 2 * pi range. - // boolean AddPie(const DRect & rect, double startAngle, double sweepAngle, - // boolean bReverse); - /** * Returns a SegmentIterator that is set right before the beginning of the * multipath. Calling nextPath() will set the iterator to the first path of diff --git a/src/main/java/com/esri/core/geometry/MultiPathImpl.java b/src/main/java/com/esri/core/geometry/MultiPathImpl.java index a8058804..33e530b6 100644 --- a/src/main/java/com/esri/core/geometry/MultiPathImpl.java +++ b/src/main/java/com/esri/core/geometry/MultiPathImpl.java @@ -25,6 +25,8 @@ package com.esri.core.geometry; +import java.util.ArrayList; + final class MultiPathImpl extends MultiVertexGeometryImpl { protected boolean m_bPolygon; @@ -291,7 +293,7 @@ public void bezierTo(Point2D controlPoint1, Point2D controlPoint2, public void openPath(int pathIndex) { _touch(); if (m_bPolygon) - throw new GeometryException("internal error");// do not call this + throw GeometryException.GeometryInternalError();// do not call this // method on a // polygon @@ -300,29 +302,27 @@ public void openPath(int pathIndex) { throw new IllegalArgumentException(); if (m_pathFlags == null) - throw new GeometryException("internal error"); + throw GeometryException.GeometryInternalError(); m_pathFlags.clearBits(pathIndex, (byte) PathFlags.enumClosed); } - // Reviewed vs. Native Jan 11, 2011 - // Major Changes on 16th of January public void openPathAndDuplicateStartVertex(int pathIndex) { _touch(); if (m_bPolygon) - throw new GeometryException("internal error");// do not call this + throw GeometryException.GeometryInternalError();// do not call this // method on a // polygon int pathCount = getPathCount(); if (pathIndex > pathCount) - throw new GeometryException("internal error"); + throw GeometryException.GeometryInternalError(); if (!isClosedPath(pathIndex)) return;// do not open if open if (m_pathFlags == null)// if (!m_pathFlags) - throw new GeometryException("nternal_error"); + throw GeometryException.GeometryInternalError(); int oldPointCount = m_pointCount; int pathIndexStart = getPathStart(pathIndex); @@ -355,12 +355,12 @@ public void openPathAndDuplicateStartVertex(int pathIndex) { public void openAllPathsAndDuplicateStartVertex() { _touch(); if (m_bPolygon) - throw new GeometryException("internal error");// do not call this + throw GeometryException.GeometryInternalError();// do not call this // method on a // polygon if (m_pathFlags == null)// if (!m_pathFlags) - throw new GeometryException("nternal_error"); + throw GeometryException.GeometryInternalError(); _verifyAllStreams(); @@ -581,7 +581,7 @@ public void addSegment(Segment segment, boolean bStartNewPath) { segment.queryEnd(point); lineTo(point); } else { - throw new GeometryException("internal error"); + throw GeometryException.GeometryInternalError(); } } @@ -764,7 +764,7 @@ public void addSegmentsFromPath(MultiPathImpl src, int src_path_index, if (hasNonLinearSegments()) { // TODO: implement me. For example as a while loop over all curves. // Replace, calling ReplaceSegment - throw new GeometryException("internal error"); + throw GeometryException.GeometryInternalError(); // m_segment_flags->write_range((get_path_start(path_index) + // before_point_index + src_point_count), (oldPointCount - // get_path_start(path_index) - before_point_index), @@ -788,7 +788,7 @@ public void addSegmentsFromPath(MultiPathImpl src, int src_path_index, if (src.hasNonLinearSegments(src_path_index)) { // TODO: implement me. For example as a while loop over all curves. // Replace, calling ReplaceSegment - throw new GeometryException("internal error"); + throw GeometryException.GeometryInternalError(); } notifyModified(DirtyFlags.DirtyCoordinates); @@ -1058,8 +1058,8 @@ public void insertPoints(int pathIndex, int beforePointIndex, if (srcPathIndex < 0) srcPathIndex = src.getPathCount() - 1; - if (pathIndex > getPathCount() - || beforePointIndex > getPathSize(pathIndex) + if (pathIndex > getPathCount() || beforePointIndex >= 0 + && beforePointIndex > getPathSize(pathIndex) || srcPathIndex >= src.getPathCount() || srcPointCount > src.getPathSize(srcPathIndex)) throw new GeometryException("index out of bounds"); @@ -1148,7 +1148,7 @@ public void insertPoints(int pathIndex, int beforePointIndex, if (src.hasNonLinearSegments(srcPathIndex)) { // TODO: implement me. For example as a while loop over all curves. // Replace, calling ReplaceSegment - throw new GeometryException("internal error"); + throw GeometryException.GeometryInternalError(); } for (int ipath = pathIndex + 1, npaths = getPathCount(); ipath <= npaths; ipath++) { @@ -1461,7 +1461,8 @@ public double calculatePathLength2D(int pathIndex) /* const */ return sub_length; } - Geometry getBoundary() { + @Override + public Geometry getBoundary() { return Boundary.calculate(this, null); } @@ -1748,7 +1749,7 @@ public void applyTransformation(Transformation2D transform, int pathIndex) { } break; case SegmentFlags.enumArcSeg: - throw new GeometryException("internal error"); + throw GeometryException.GeometryInternalError(); } } @@ -1806,12 +1807,12 @@ public void applyTransformation(Transformation3D transform) { } break; case SegmentFlags.enumArcSeg: - throw new GeometryException("internal error"); + throw GeometryException.GeometryInternalError(); } } } - + ptStart = transform.transform(ptStart); points.write(ipoint * 2, ptStart.x); points.write(ipoint * 2 + 1, ptStart.y); @@ -2103,7 +2104,8 @@ protected void _updateOGCFlags() { int firstSign = 1; for (int ipath = 0; ipath < pathCount; ipath++) { double area = m_cachedRingAreas2D.read(ipath); - if (ipath == 0) firstSign = area > 0 ? 1 : -1; + if (ipath == 0) + firstSign = area > 0 ? 1 : -1; if (area * firstSign > 0.0) m_pathFlags.setBits(ipath, (byte) PathFlags.enumOGCStartPolygon); @@ -2393,6 +2395,7 @@ public boolean _buildRasterizedGeometryAccelerator(double toleranceXY, rgeom = RasterizedGeometry2D.create(this, toleranceXY, rasterSize); m_accelerators._setRasterizedGeometry(rgeom); + //rgeom.dbgSaveToBitmap("c:/temp/ddd.bmp"); return true; } @@ -2438,11 +2441,11 @@ public void getSegment(int startVertexIndex, SegmentBuffer segBuffer, segBuffer.createLine(); break; case SegmentFlags.enumBezierSeg: - throw new GeometryException("internal error"); + throw GeometryException.GeometryInternalError(); case SegmentFlags.enumArcSeg: - throw new GeometryException("internal error"); + throw GeometryException.GeometryInternalError(); default: - throw new GeometryException("internal error"); + throw GeometryException.GeometryInternalError(); } Segment currentSegment = segBuffer.get(); @@ -2504,7 +2507,7 @@ void queryPathEnvelope2D(int path_index, Envelope2D envelope) { envelope.setCoords(env); } } - + @Override public boolean _buildQuadTreeAccelerator(GeometryAccelerationDegree d) { if (m_accelerators == null)// (!m_accelerators) @@ -2520,5 +2523,23 @@ public boolean _buildQuadTreeAccelerator(GeometryAccelerationDegree d) { return true; } - + + boolean _buildPathEnvelopesAccelerator(GeometryAccelerationDegree d) { + if (m_accelerators == null) { + m_accelerators = new GeometryAccelerators(); + } + + ArrayList path_envelopes = new ArrayList(0); + + for (int ipath = 0; ipath < getPathCount(); ipath++) { + Envelope2D env = new Envelope2D(); + queryPathEnvelope2D(ipath, env); + path_envelopes.add(env); + } + + m_accelerators._setPathEnvelopes(path_envelopes); + + return true; + } + } diff --git a/src/main/java/com/esri/core/geometry/MultiPoint.java b/src/main/java/com/esri/core/geometry/MultiPoint.java index 008401fd..e890cb8a 100644 --- a/src/main/java/com/esri/core/geometry/MultiPoint.java +++ b/src/main/java/com/esri/core/geometry/MultiPoint.java @@ -359,4 +359,9 @@ void setPointByVal(int index, Point pointSrc) { public int getStateFlag() { return m_impl.getStateFlag(); } + + @Override + public Geometry getBoundary() { + return m_impl.getBoundary(); + } } diff --git a/src/main/java/com/esri/core/geometry/MultiPointImpl.java b/src/main/java/com/esri/core/geometry/MultiPointImpl.java index 65d7ca48..b29e35c3 100644 --- a/src/main/java/com/esri/core/geometry/MultiPointImpl.java +++ b/src/main/java/com/esri/core/geometry/MultiPointImpl.java @@ -343,4 +343,9 @@ public boolean _buildQuadTreeAccelerator(GeometryAccelerationDegree accelDegree) // // TODO Auto-generated method stub // // } + + @Override + public Geometry getBoundary() { + return null; + } } diff --git a/src/main/java/com/esri/core/geometry/MultiVertexGeometryImpl.java b/src/main/java/com/esri/core/geometry/MultiVertexGeometryImpl.java index 60836fa2..fc614cfb 100644 --- a/src/main/java/com/esri/core/geometry/MultiVertexGeometryImpl.java +++ b/src/main/java/com/esri/core/geometry/MultiVertexGeometryImpl.java @@ -561,7 +561,6 @@ public boolean equals(Object other) { if (!(other instanceof MultiVertexGeometryImpl)) return false; - // Borg Implementation MultiVertexGeometryImpl otherMulti = (MultiVertexGeometryImpl) other; if (!(m_description.equals(otherMulti.m_description))) @@ -829,7 +828,7 @@ void _resizeImpl(int pointCount) { } // Checked vs. Jan 11, 2011 - int QueryCoordinates(Point2D[] dst, int dstSize, int beginIndex, + int queryCoordinates(Point2D[] dst, int dstSize, int beginIndex, int endIndex) { int endIndexC = endIndex < 0 ? m_pointCount : endIndex; endIndexC = Math.min(endIndexC, beginIndex + dstSize); @@ -841,7 +840,7 @@ int QueryCoordinates(Point2D[] dst, int dstSize, int beginIndex, AttributeStreamOfDbl xy = (AttributeStreamOfDbl) getAttributeStreamRef(VertexDescription.Semantics.POSITION); int j = 0; double[] dstArray = new double[dst.length * 2]; - xy.readRange(2 * beginIndex, endIndexC - beginIndex, dstArray, j, true); + xy.readRange(2 * beginIndex, (endIndexC - beginIndex) * 2, dstArray, j, true); for (int i = 0; i < dst.length; i++) { dst[i] = new Point2D(dstArray[i * 2], dstArray[i * 2 + 1]); @@ -932,7 +931,7 @@ void setIsSimple(int isSimpleRes, double tolerance, boolean ogc_known) { _setDirtyFlag(DirtyFlags.IsWeakSimple, true); _setDirtyFlag(DirtyFlags.IsStrongSimple, true); } else - throw new GeometryException("internal error");// what? + throw GeometryException.GeometryInternalError();// what? } double _getSimpleTolerance() { diff --git a/src/main/java/com/esri/core/geometry/NumberUtils.java b/src/main/java/com/esri/core/geometry/NumberUtils.java index 383aa7af..7de7558d 100644 --- a/src/main/java/com/esri/core/geometry/NumberUtils.java +++ b/src/main/java/com/esri/core/geometry/NumberUtils.java @@ -38,47 +38,37 @@ public static double snap(double v, double minv, double maxv) { return v < minv ? minv : v > maxv ? maxv : v; } - public static int sizeOf(double v) { + static int sizeOf(double v) { return 8; } - public static int sizeOfDouble() { + static int sizeOfDouble() { return 8; } - public static int sizeOf(int v) { + static int sizeOf(int v) { return 4; } - public static int sizeOf(long v) { + static int sizeOf(long v) { return 8; } - public static int sizeOf(byte v) { + static int sizeOf(byte v) { return 1; } - public static boolean isNaN(double d) { + static boolean isNaN(double d) { return Double.isNaN(d); } - public final static double TheNaN = Double.NaN; + final static double TheNaN = Double.NaN; - public static double NaN() { + static double NaN() { return Double.NaN; } - // public static void main(String[] args) - // { - // System.out.println(Long.toHexString(Double.doubleToLongBits(Double.NaN))); - // System.out.println(Double.longBitsToDouble(0x7FF8000000000001L)); - // - // System.out.println(Long.toHexString(Double.doubleToLongBits(Double.NEGATIVE_INFINITY))); - // System.out.println(Double.longBitsToDouble(0xFFF8000000000000L)); - // System.out.println(Double.NEGATIVE_INFINITY); - // } - - public static int hash(int n) { + static int hash(int n) { int hash = 5381; hash = ((hash << 5) + hash) + (n & 0xFF); /* hash * 33 + c */ hash = ((hash << 5) + hash) + ((n >> 8) & 0xFF); @@ -88,13 +78,13 @@ public static int hash(int n) { return hash; } - public static int hash(double d) { + static int hash(double d) { long bits = Double.doubleToLongBits(d); int hc = (int) (bits ^ (bits >>> 32)); return hash(hc); } - public static int hash(int hashIn, int n) { + static int hash(int hashIn, int n) { int hash = ((hashIn << 5) + hashIn) + (n & 0xFF); /* hash * 33 + c */ hash = ((hash << 5) + hash) + ((n >> 8) & 0xFF); hash = ((hash << 5) + hash) + ((n >> 16) & 0xFF); @@ -103,37 +93,37 @@ public static int hash(int hashIn, int n) { return hash; } - public static int hash(int hash, double d) { + static int hash(int hash, double d) { long bits = Double.doubleToLongBits(d); int hc = (int) (bits ^ (bits >>> 32)); return hash(hash, hc); } - public static long doubleToInt64Bits(double d) { + static long doubleToInt64Bits(double d) { return Double.doubleToLongBits(d); } - public static double negativeInf() { + static double negativeInf() { return Double.NEGATIVE_INFINITY; } - public static double positiveInf() { + static double positiveInf() { return Double.POSITIVE_INFINITY; } - public static int intMax() { + static int intMax() { return Integer.MAX_VALUE; } - public static double doubleEps() { + static double doubleEps() { return 2.2204460492503131e-016; } - public static double doubleMax() { + static double doubleMax() { return Double.MAX_VALUE; } - public static int nextRand(int prevRand) { + static int nextRand(int prevRand) { return (1103515245 * prevRand + 12345) & intMax(); // according to Wiki, // this is gcc's } diff --git a/src/main/java/com/esri/core/geometry/Operator.java b/src/main/java/com/esri/core/geometry/Operator.java index 691c47f3..20c00787 100644 --- a/src/main/java/com/esri/core/geometry/Operator.java +++ b/src/main/java/com/esri/core/geometry/Operator.java @@ -48,7 +48,7 @@ public enum Type { Buffer, Distance, Intersection, Clip, Cut, DensifyByLength, // m_cuts; + ArrayList m_cuts = null; OperatorCutCursor(boolean bConsiderTouch, Geometry cuttee, Polyline cutter, SpatialReference spatialReference, ProgressTracker progressTracker) { - if (cuttee == null) + if (cuttee == null || cutter == null) throw new GeometryException("invalid argument"); m_bConsiderTouch = bConsiderTouch; m_cuttee = cuttee; m_cutter = cutter; - m_tolerance = spatialReference != null ? spatialReference - .getTolerance(Semantics.POSITION) : InternalUtils - .calculateToleranceFromGeometry(null, cuttee, false); - if (m_tolerance > 0.001) - m_tolerance = 0.001; + Envelope2D e = InternalUtils.getMergedExtent(cuttee, cutter); + m_tolerance = InternalUtils.calculateToleranceFromGeometry(spatialReference, e, true); m_cutIndex = -1; - m_cuts = null; + m_progressTracker = progressTracker; } @Override @@ -61,38 +59,47 @@ public int getGeometryID() { @Override public Geometry next() { - if (m_cuts == null) { - int type = m_cuttee.getType().value(); - switch (type) { - case Geometry.GeometryType.Polyline: - m_cuts = _cutPolyline(); - break; - - case Geometry.GeometryType.Polygon: - m_cuts = _cutPolygon(); - break; - } + generateCuts_(); + if (++m_cutIndex < m_cuts.size()) { + return (Geometry)m_cuts.get(m_cutIndex); } - - if (++m_cutIndex < m_cuts.size()) - return (Geometry) m_cuts.get(m_cutIndex); - + return null; } - private ArrayList _cutPolyline() { + private void generateCuts_() { + if (m_cuts != null) + return; + + m_cuts = new ArrayList(); + + Geometry.Type type = m_cuttee.getType(); + switch (type.value()) { + case Geometry.GeometryType.Polyline: + generate_polyline_cuts_(); + break; + + case Geometry.GeometryType.Polygon: + generate_polygon_cuts_(); + break; + + default: + break; // warning fix + } + } + + private void generate_polyline_cuts_() { MultiPath left = new Polyline(); MultiPath right = new Polyline(); MultiPath uncut = new Polyline(); - ArrayList cuts = new ArrayList(2); - cuts.add(left); - cuts.add(right); + m_cuts.add(left); + m_cuts.add(right); ArrayList cutPairs = new ArrayList( 0); Cutter.CutPolyline(m_bConsiderTouch, (Polyline) m_cuttee, m_cutter, - m_tolerance, cutPairs, null); + m_tolerance, cutPairs, null, m_progressTracker); for (int icut = 0; icut < cutPairs.size(); icut++) { OperatorCutLocal.CutPair cutPair = cutPairs.get(icut); @@ -102,78 +109,89 @@ private ArrayList _cutPolyline() { || cutPair.m_side == Side.Coincident) { right.add((MultiPath) cutPair.m_geometry, false); } else if (cutPair.m_side == Side.Undefined) { - cuts.add((MultiPath) cutPair.m_geometry); + m_cuts.add((MultiPath) cutPair.m_geometry); } else { uncut.add((MultiPath) cutPair.m_geometry, false); } } if (!uncut.isEmpty() - && (!left.isEmpty() || !right.isEmpty() || cuts.size() >= 3)) - cuts.add(uncut); + && (!left.isEmpty() || !right.isEmpty() || m_cuts.size() >= 3)) + m_cuts.add(uncut); - return cuts; + if (left.isEmpty() && right.isEmpty() && m_cuts.size() < 3) + m_cuts.clear(); // no cuts } - ArrayList _cutPolygon() { + private void generate_polygon_cuts_() { AttributeStreamOfInt32 cutHandles = new AttributeStreamOfInt32(0); EditShape shape = new EditShape(); int sideIndex = shape.createGeometryUserIndex(); int cutteeHandle = shape.addGeometry(m_cuttee); int cutterHandle = shape.addGeometry(m_cutter); TopologicalOperations topoOp = new TopologicalOperations(); - topoOp.setEditShapeCrackAndCluster(shape, m_tolerance, null); - topoOp.cut(sideIndex, cutteeHandle, cutterHandle, cutHandles); - Polygon cutteeRemainder = (Polygon) shape.getGeometry(cutteeHandle); - - MultiPath left = new Polygon(); - MultiPath right = new Polygon(); - - ArrayList cuts = new ArrayList(2); - cuts.add(left); - cuts.add(right); - - for (int icutIndex = 0; icutIndex < cutHandles.size(); icutIndex++) { - Geometry cutGeometry; - { - // intersection - EditShape shapeIntersect = new EditShape(); - int geometryA = shapeIntersect.addGeometry(cutteeRemainder); - int geometryB = shapeIntersect.addGeometry(shape - .getGeometry(cutHandles.get(icutIndex))); - topoOp.setEditShape(shapeIntersect); - int intersectHandle = topoOp.intersection(geometryA, geometryB); - cutGeometry = shapeIntersect.getGeometry(intersectHandle); - - if (cutGeometry.isEmpty()) - continue; - - int side = shape.getGeometryUserIndex( - cutHandles.get(icutIndex), sideIndex); - if (side == 2) - left.add((MultiPath) cutGeometry, false); - else if (side == 1) - right.add((MultiPath) cutGeometry, false); - else - cuts.add((MultiPath) cutGeometry); // Undefined + try { + topoOp.setEditShapeCrackAndCluster(shape, m_tolerance, + m_progressTracker); + topoOp.cut(sideIndex, cutteeHandle, cutterHandle, cutHandles); + Polygon cutteeRemainder = (Polygon) shape.getGeometry(cutteeHandle); + + MultiPath left = new Polygon(); + MultiPath right = new Polygon(); + + m_cuts.clear(); + m_cuts.add(left); + m_cuts.add(right); + + for (int icutIndex = 0; icutIndex < cutHandles.size(); icutIndex++) { + Geometry cutGeometry; + { + // intersection + EditShape shapeIntersect = new EditShape(); + int geometryA = shapeIntersect.addGeometry(cutteeRemainder); + int geometryB = shapeIntersect.addGeometry(shape + .getGeometry(cutHandles.get(icutIndex))); + topoOp.setEditShape(shapeIntersect, m_progressTracker); + int intersectHandle = topoOp.intersection(geometryA, + geometryB); + cutGeometry = shapeIntersect.getGeometry(intersectHandle); + + if (cutGeometry.isEmpty()) + continue; + + int side = shape.getGeometryUserIndex( + cutHandles.get(icutIndex), sideIndex); + if (side == 2) + left.add((MultiPath) cutGeometry, false); + else if (side == 1) + right.add((MultiPath) cutGeometry, false); + else + m_cuts.add((MultiPath) cutGeometry); // Undefined + } + + { + // difference + EditShape shapeDifference = new EditShape(); + int geometryA = shapeDifference + .addGeometry(cutteeRemainder); + int geometryB = shapeDifference.addGeometry(shape + .getGeometry(cutHandles.get(icutIndex))); + topoOp.setEditShape(shapeDifference, m_progressTracker); + cutteeRemainder = (Polygon) shapeDifference + .getGeometry(topoOp + .difference(geometryA, geometryB)); + } } - { - // difference - EditShape shapeDifference = new EditShape(); - int geometryA = shapeDifference.addGeometry(cutteeRemainder); - int geometryB = shapeDifference.addGeometry(shape - .getGeometry(cutHandles.get(icutIndex))); - topoOp.setEditShape(shapeDifference); - cutteeRemainder = (Polygon) shapeDifference.getGeometry(topoOp - .difference(geometryA, geometryB)); - } - } + if (!cutteeRemainder.isEmpty() && cutHandles.size() > 0) + m_cuts.add((MultiPath) cutteeRemainder); - if (!cutteeRemainder.isEmpty() && cutHandles.size() > 0) - cuts.add((MultiPath) cutteeRemainder); + if (left.isEmpty() && right.isEmpty()) + m_cuts.clear(); // no cuts - return cuts; + } finally { + topoOp.removeShape(); + } } - } + diff --git a/src/main/java/com/esri/core/geometry/OperatorDensifyByLengthCursor.java b/src/main/java/com/esri/core/geometry/OperatorDensifyByLengthCursor.java index d876a60c..b634ff59 100644 --- a/src/main/java/com/esri/core/geometry/OperatorDensifyByLengthCursor.java +++ b/src/main/java/com/esri/core/geometry/OperatorDensifyByLengthCursor.java @@ -72,7 +72,7 @@ else if (geometryType == Geometry.GeometryType.Envelope) return densifyEnvelope((Envelope) geom); else // TODO fix geometry exception to match native implementation - throw new GeometryException("internal error");// GEOMTHROW(internal_error); + throw GeometryException.GeometryInternalError();// GEOMTHROW(internal_error); // unreachable in java // return null; diff --git a/src/main/java/com/esri/core/geometry/OperatorDifference.java b/src/main/java/com/esri/core/geometry/OperatorDifference.java index 607285a6..11c1295a 100644 --- a/src/main/java/com/esri/core/geometry/OperatorDifference.java +++ b/src/main/java/com/esri/core/geometry/OperatorDifference.java @@ -25,7 +25,6 @@ package com.esri.core.geometry; import com.esri.core.geometry.Operator.Type; -import com.esri.core.geometry.CombineOperator; /** * Difference of geometries. diff --git a/src/main/java/com/esri/core/geometry/OperatorExportToESRIShapeCursor.java b/src/main/java/com/esri/core/geometry/OperatorExportToESRIShapeCursor.java index f460633a..13d821ed 100644 --- a/src/main/java/com/esri/core/geometry/OperatorExportToESRIShapeCursor.java +++ b/src/main/java/com/esri/core/geometry/OperatorExportToESRIShapeCursor.java @@ -93,7 +93,7 @@ static int exportToESRIShape(int exportFlags, Geometry geometry, return exportEnvelopeToESRIShape(exportFlags, (Envelope) geometry, shapeBuffer); default: { - throw new GeometryException("internal error"); + throw GeometryException.GeometryInternalError(); // return -1; } } diff --git a/src/main/java/com/esri/core/geometry/OperatorExportToWkbLocal.java b/src/main/java/com/esri/core/geometry/OperatorExportToWkbLocal.java index 3b4fedfe..929e9168 100644 --- a/src/main/java/com/esri/core/geometry/OperatorExportToWkbLocal.java +++ b/src/main/java/com/esri/core/geometry/OperatorExportToWkbLocal.java @@ -97,7 +97,7 @@ private static int exportToWKB(int exportFlags, Geometry geometry, wkbBuffer); default: { - throw new GeometryException("internal error"); + throw GeometryException.GeometryInternalError(); // return -1; } } diff --git a/src/main/java/com/esri/core/geometry/OperatorExportToWktLocal.java b/src/main/java/com/esri/core/geometry/OperatorExportToWktLocal.java index a48b1f5c..5fe4398f 100644 --- a/src/main/java/com/esri/core/geometry/OperatorExportToWktLocal.java +++ b/src/main/java/com/esri/core/geometry/OperatorExportToWktLocal.java @@ -42,7 +42,7 @@ static void exportToWkt(int export_flags, Geometry geometry, || (export_flags & WktExportFlags.wktExportMultiLineString) != 0 || (export_flags & WktExportFlags.wktExportPoint) != 0 || (export_flags & WktExportFlags.wktExportMultiPoint) != 0) - throw new IllegalArgumentException("Cannot export a Polygon as Line/Point : "+export_flags); + throw new IllegalArgumentException("Cannot export a Polygon as (Multi)LineString/(Multi)Point : "+export_flags); exportPolygonToWkt(export_flags, (Polygon) geometry, string); return; @@ -82,13 +82,13 @@ static void exportToWkt(int export_flags, Geometry geometry, || (export_flags & WktExportFlags.wktExportMultiLineString) != 0 || (export_flags & WktExportFlags.wktExportPoint) != 0 || (export_flags & WktExportFlags.wktExportMultiPoint) != 0) - throw new IllegalArgumentException("Cannot export an Envelop as (Multi)LineString/(Multi)Point: "+export_flags); + throw new IllegalArgumentException("Cannot export an Envelope as (Multi)LineString/(Multi)Point: "+export_flags); exportEnvelopeToWkt(export_flags, (Envelope) geometry, string); return; default: { - throw new GeometryException("internal error"); + throw GeometryException.GeometryInternalError(); } } } diff --git a/src/main/java/com/esri/core/geometry/OperatorFactoryLocal.java b/src/main/java/com/esri/core/geometry/OperatorFactoryLocal.java index 14709bda..a77628d3 100644 --- a/src/main/java/com/esri/core/geometry/OperatorFactoryLocal.java +++ b/src/main/java/com/esri/core/geometry/OperatorFactoryLocal.java @@ -83,6 +83,15 @@ public class OperatorFactoryLocal extends OperatorFactory { st_supportedOperators.put(Type.Simplify, new OperatorSimplifyLocal()); st_supportedOperators.put(Type.Offset, new OperatorOffsetLocal()); + st_supportedOperators.put(Type.GeodeticDensifyByLength, + new OperatorGeodeticDensifyLocal()); + + st_supportedOperators.put(Type.ShapePreservingDensify, + new OperatorShapePreservingDensifyLocal()); + + st_supportedOperators.put(Type.GeodesicBuffer, + new OperatorGeodesicBufferLocal()); + st_supportedOperators.put(Type.GeodeticLength, new OperatorGeodeticLengthLocal()); st_supportedOperators.put(Type.GeodeticArea, @@ -118,23 +127,14 @@ public class OperatorFactoryLocal extends OperatorFactory { new OperatorConvexHullLocal()); st_supportedOperators.put(Type.Boundary, new OperatorBoundaryLocal()); - // LabelPoint, - // Simplify, - // + // LabelPoint, - not ported } private OperatorFactoryLocal() { - m_bNewTopo = false;// use sg by default - // m_bNewTopo = true;//use sg by default } - /** - * A temporary way to switch to new topo engine from SG. Set it to true, to - * switch. Need to be changed once at the startup of the program. - */ - boolean m_bNewTopo; /** *Returns a reference to the singleton. diff --git a/src/main/java/com/esri/core/geometry/OperatorGeneralizeCursor.java b/src/main/java/com/esri/core/geometry/OperatorGeneralizeCursor.java index 8e04928a..eb860616 100644 --- a/src/main/java/com/esri/core/geometry/OperatorGeneralizeCursor.java +++ b/src/main/java/com/esri/core/geometry/OperatorGeneralizeCursor.java @@ -65,7 +65,7 @@ private Geometry Generalize(Geometry geom) { return geom; MultiPath mp = (MultiPath) geom; if (mp == null) - throw new GeometryException("internal error"); + throw GeometryException.GeometryInternalError(); MultiPath dstmp = (MultiPath) geom.createInstance(); Line line = new Line(); for (int ipath = 0, npath = mp.getPathCount(); ipath < npath; ipath++) { diff --git a/src/main/java/com/esri/core/geometry/OperatorGeodesicBuffer.java b/src/main/java/com/esri/core/geometry/OperatorGeodesicBuffer.java new file mode 100644 index 00000000..fd0c9bdf --- /dev/null +++ b/src/main/java/com/esri/core/geometry/OperatorGeodesicBuffer.java @@ -0,0 +1,67 @@ +/* + Copyright 1995-2013 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ +package com.esri.core.geometry; + +abstract class OperatorGeodesicBuffer extends Operator { + + @Override + public Operator.Type getType() { + return Operator.Type.GeodesicBuffer; + } + + /** + * Creates a geodesic buffer around the input geometries + * + * @param input_geometries The geometries to buffer. + * @param sr The Spatial_reference of the Geometries. + * @param curveType The geodetic curve type of the segments. If the curve_type is Geodetic_curve::shape_preserving, then the segments are densified in the projection where they are defined before + * buffering. + * @param distancesMeters The buffer distances in meters for the Geometries. If the size of the distances array is less than the number of geometries in the input_geometries, the last distance value + * is used for the rest of geometries. + * @param maxDeviationMeters The deviation offset to use for convergence. The geodesic arcs of the resulting buffer will be closer than the max deviation of the true buffer. Pass in NaN to use the + * default deviation. + * @param bReserved Must be false. Reserved for future development. Will throw an exception if not false. + * @param bUnion If True, the buffered geometries will be unioned, otherwise they wont be unioned. + */ + abstract public GeometryCursor execute(GeometryCursor inputGeometries, SpatialReference sr, int curveType, double[] distancesMeters, double maxDeviationMeters, boolean bReserved, boolean bUnion, ProgressTracker progressTracker); + + /** + * Creates a geodesic buffer around the input geometry + * + * @param input_geometry The geometry to buffer. + * @param sr The Spatial_reference of the Geometry. + * @param curveType The geodetic curve type of the segments. If the curve_type is Geodetic_curve::shape_preserving, then the segments are densified in the projection where they are defined before + * buffering. + * @param distanceMeters The buffer distance in meters for the Geometry. + * @param maxDeviationMeters The deviation offset to use for convergence. The geodesic arcs of the resulting buffer will be closer than the max deviation of the true buffer. Pass in NaN to use the + * default deviation. + * @param bReserved Must be false. Reserved for future development. Will throw an exception if not false. + */ + abstract public Geometry execute(Geometry inputGeometry, SpatialReference sr, int curveType, double distanceMeters, double maxDeviationMeters, boolean bReserved, ProgressTracker progressTracker); + + public static OperatorGeodesicBuffer local() { + return (OperatorGeodesicBuffer) OperatorFactoryLocal.getInstance() + .getOperator(Type.GeodesicBuffer); + } +} diff --git a/src/main/java/com/esri/core/geometry/OperatorGeodesicBufferLocal.java b/src/main/java/com/esri/core/geometry/OperatorGeodesicBufferLocal.java new file mode 100644 index 00000000..b3475eb9 --- /dev/null +++ b/src/main/java/com/esri/core/geometry/OperatorGeodesicBufferLocal.java @@ -0,0 +1,44 @@ +/* + Copyright 1995-2013 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ + +package com.esri.core.geometry; + +//This is a stub +class OperatorGeodesicBufferLocal extends OperatorGeodesicBuffer { + + @Override + public GeometryCursor execute(GeometryCursor inputGeometries, + SpatialReference sr, int curveType, double[] distancesMeters, + double maxDeviationMeters, boolean bReserved, boolean bUnion, + ProgressTracker progressTracker) { + throw new GeometryException("not implemented"); + } + + @Override + public Geometry execute(Geometry inputGeometry, SpatialReference sr, + int curveType, double distanceMeters, double maxDeviationMeters, + boolean bReserved, ProgressTracker progressTracker) { + throw new GeometryException("not implemented"); + } +} diff --git a/src/main/java/com/esri/core/geometry/OperatorGeodeticDensifyByLength.java b/src/main/java/com/esri/core/geometry/OperatorGeodeticDensifyByLength.java new file mode 100644 index 00000000..d9a660e6 --- /dev/null +++ b/src/main/java/com/esri/core/geometry/OperatorGeodeticDensifyByLength.java @@ -0,0 +1,60 @@ +/* + Copyright 1995-2013 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ +package com.esri.core.geometry; + +/** + * Densifies the line segments by length, making them run along specified geodetic curves. + * +* Use this operator to construct geodetic curves. + */ +abstract class OperatorGeodeticDensifyByLength extends Operator { + + @Override + public Type getType() { + return Type.GeodeticDensifyByLength; + } + + /** + * Densifies input geometries. Attributes are interpolated along the scalar t-values of the input segments obtained from the length ratios along the densified segments. + * + * @param geoms The geometries to be densified. + * @param maxSegmentLengthMeters The maximum segment length (in meters) allowed. Must be a positive value. + * @param sr The SpatialReference of the Geometry. + * @param curveType The interpretation of a line connecting two points. + * @return Returns the densified geometries (It does nothing to geometries with dim < 1, but simply passes them along). + * + * Note the behavior is not determined for any geodetic curve segments that connect two poles, or for loxodrome segments that connect to any pole. + */ + public abstract GeometryCursor execute(GeometryCursor geoms, double maxSegmentLengthMeters, SpatialReference sr, int curveType, ProgressTracker progressTracker); + + /** + * Same as above, but works with a single geometry. + */ + public abstract Geometry execute(Geometry geom, double maxSegmentLengthMeters, SpatialReference sr, int curveType, ProgressTracker progressTracker); + + public static OperatorGeodeticDensifyByLength local() { + return (OperatorGeodeticDensifyByLength) OperatorFactoryLocal.getInstance() + .getOperator(Type.GeodeticDensifyByLength); + } +} diff --git a/src/main/java/com/esri/core/geometry/OperatorGeodeticDensifyLocal.java b/src/main/java/com/esri/core/geometry/OperatorGeodeticDensifyLocal.java new file mode 100644 index 00000000..5b81f8cf --- /dev/null +++ b/src/main/java/com/esri/core/geometry/OperatorGeodeticDensifyLocal.java @@ -0,0 +1,43 @@ +/* + Copyright 1995-2013 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ + +package com.esri.core.geometry; + +//This is a stub +public class OperatorGeodeticDensifyLocal extends + OperatorGeodeticDensifyByLength { + + @Override + public GeometryCursor execute(GeometryCursor geoms, + double maxSegmentLengthMeters, SpatialReference sr, int curveType, + ProgressTracker progressTracker) { + throw new GeometryException("not implemented"); + } + + @Override + public Geometry execute(Geometry geom, double maxSegmentLengthMeters, + SpatialReference sr, int curveType, ProgressTracker progressTracker) { + throw new GeometryException("not implemented"); + } +} diff --git a/src/main/java/com/esri/core/geometry/OperatorGeodeticLength.java b/src/main/java/com/esri/core/geometry/OperatorGeodeticLength.java index d43fe141..47f79482 100644 --- a/src/main/java/com/esri/core/geometry/OperatorGeodeticLength.java +++ b/src/main/java/com/esri/core/geometry/OperatorGeodeticLength.java @@ -38,23 +38,6 @@ public Type getType() { return Operator.Type.GeodeticLength; } - /** - * Calculates the geodetic length of each geometry in the geometry cursor. - * - * @param geoms - * The geometry cursor to be iterated over to perform the - * Geodetic Length calculation. - * @param sr - * The SpatialReference of the geometries. - * @param geodeticCurveType - * Use the {@link GeodeticCurveType} interface to choose the - * interpretation of a line connecting two points. - * @param progressTracker - * @return Returns an array of the geoetic lengths of the geometries. - */ - public abstract double[] execute(GeometryCursor geoms, SpatialReference sr, - int geodeticCurveType, ProgressTracker progressTracker); - /** * Calculates the geodetic length of the input Geometry. * diff --git a/src/main/java/com/esri/core/geometry/OperatorGeodeticLengthLocal.java b/src/main/java/com/esri/core/geometry/OperatorGeodeticLengthLocal.java index 4c57ca28..e878027d 100644 --- a/src/main/java/com/esri/core/geometry/OperatorGeodeticLengthLocal.java +++ b/src/main/java/com/esri/core/geometry/OperatorGeodeticLengthLocal.java @@ -26,12 +26,6 @@ //This is a stub class OperatorGeodeticLengthLocal extends OperatorGeodeticLength { - @Override - public double[] execute(GeometryCursor geoms, SpatialReference sr, - int geodeticCurveType, ProgressTracker progressTracker) { - throw new GeometryException("not implemented"); - } - @Override public double execute(Geometry geom, SpatialReference sr, int geodeticCurveType, ProgressTracker progressTracker) { diff --git a/src/main/java/com/esri/core/geometry/OperatorImportFromGeoJsonLocal.java b/src/main/java/com/esri/core/geometry/OperatorImportFromGeoJsonLocal.java index 89cd6632..b5d0c788 100644 --- a/src/main/java/com/esri/core/geometry/OperatorImportFromGeoJsonLocal.java +++ b/src/main/java/com/esri/core/geometry/OperatorImportFromGeoJsonLocal.java @@ -41,8 +41,9 @@ public MapGeometry execute(int importFlags, Geometry.Type type, MapGeometry mapGeometry = new MapGeometry(geometry, spatialReference); return mapGeometry; } - - static JSONArray getJSONArray(JSONObject obj, String name) throws JSONException { + + static JSONArray getJSONArray(JSONObject obj, String name) + throws JSONException { if (obj.get(name) == JSONObject.NULL) return new JSONArray(); else @@ -156,7 +157,8 @@ private static Geometry importGeometryFromGeoJson_(int importFlags, Geometry.Type type, JSONObject geometryJSONObject) throws JSONException { String typeString = geometryJSONObject.getString("type"); - JSONArray coordinateArray = getJSONArray(geometryJSONObject, "coordinates"); + JSONArray coordinateArray = getJSONArray(geometryJSONObject, + "coordinates"); if (typeString.equalsIgnoreCase("MultiPolygon")) { if (type != Geometry.Type.Polygon && type != Geometry.Type.Unknown) @@ -237,9 +239,23 @@ private static Geometry polygonTaggedText_(boolean bMultiPolygon, multiPathImpl.notifyModified(MultiPathImpl.DirtyFlags.DirtyAll); - if (!InternalUtils.isClockwiseRing(multiPathImpl, 0)) { - multiPathImpl.reverseAllPaths(); + AttributeStreamOfInt8 path_flags_clone = new AttributeStreamOfInt8( + path_flags); + + for (int i = 0; i < path_flags_clone.size() - 1; i++) { + if (((int) path_flags_clone.read(i) & (int) PathFlags.enumOGCStartPolygon) != 0) {// Should + // be + // clockwise + if (!InternalUtils.isClockwiseRing(multiPathImpl, i)) + multiPathImpl.reversePath(i); // make clockwise + } else {// Should be counter-clockwise + if (InternalUtils.isClockwiseRing(multiPathImpl, i)) + multiPathImpl.reversePath(i); // make counter-clockwise + } } + + multiPathImpl.setPathFlagsStreamRef(path_flags_clone); + } if ((importFlags & (int) GeoJsonImportFlags.geoJsonImportNonTrusted) == 0) { diff --git a/src/main/java/com/esri/core/geometry/OperatorImportFromWkbLocal.java b/src/main/java/com/esri/core/geometry/OperatorImportFromWkbLocal.java index c5ee10aa..6fa9c01a 100644 --- a/src/main/java/com/esri/core/geometry/OperatorImportFromWkbLocal.java +++ b/src/main/java/com/esri/core/geometry/OperatorImportFromWkbLocal.java @@ -692,8 +692,22 @@ private static Geometry importFromWkbPolygon(boolean bMultiPolygon, polygon.notifyModified(MultiPathImpl.DirtyFlags.DirtyAll); - if (!InternalUtils.isClockwiseRing(polygon, 0)) - polygon.reverseAllPaths(); + AttributeStreamOfInt8 path_flags_clone = new AttributeStreamOfInt8( + pathFlags); + + for (int i = 0; i < path_flags_clone.size() - 1; i++) { + if (((int) path_flags_clone.read(i) & (int) PathFlags.enumOGCStartPolygon) != 0) {// Should + // be + // clockwise + if (!InternalUtils.isClockwiseRing(polygon, i)) + polygon.reversePath(i); // make clockwise + } else {// Should be counter-clockwise + if (InternalUtils.isClockwiseRing(polygon, i)) + polygon.reversePath(i); // make counter-clockwise + } + } + + polygon.setPathFlagsStreamRef(path_flags_clone); } if ((importFlags & (int) WkbImportFlags.wkbImportNonTrusted) == 0) diff --git a/src/main/java/com/esri/core/geometry/OperatorImportFromWktLocal.java b/src/main/java/com/esri/core/geometry/OperatorImportFromWktLocal.java index d742caa9..3a9bb232 100644 --- a/src/main/java/com/esri/core/geometry/OperatorImportFromWktLocal.java +++ b/src/main/java/com/esri/core/geometry/OperatorImportFromWktLocal.java @@ -196,8 +196,23 @@ static Geometry polygonTaggedText(boolean b_multi_polygon, multi_path_impl.notifyModified(MultiPathImpl.DirtyFlags.DirtyAll); - if (!InternalUtils.isClockwiseRing(multi_path_impl, 0)) - multi_path_impl.reverseAllPaths(); + AttributeStreamOfInt8 path_flags_clone = new AttributeStreamOfInt8( + path_flags); + + for (int i = 0; i < path_flags_clone.size() - 1; i++) { + if (((int) path_flags_clone.read(i) & (int) PathFlags.enumOGCStartPolygon) != 0) {// Should + // be + // clockwise + if (!InternalUtils.isClockwiseRing(multi_path_impl, i)) + multi_path_impl.reversePath(i); // make clockwise + } else {// Should be counter-clockwise + if (InternalUtils.isClockwiseRing(multi_path_impl, i)) + multi_path_impl.reversePath(i); // make + // counter-clockwise + } + } + + multi_path_impl.setPathFlagsStreamRef(path_flags_clone); } if ((import_flags & (int) WktImportFlags.wktImportNonTrusted) == 0) diff --git a/src/main/java/com/esri/core/geometry/OperatorInternalRelationUtils.java b/src/main/java/com/esri/core/geometry/OperatorInternalRelationUtils.java index cc7fa41d..7f78b59d 100644 --- a/src/main/java/com/esri/core/geometry/OperatorInternalRelationUtils.java +++ b/src/main/java/com/esri/core/geometry/OperatorInternalRelationUtils.java @@ -88,7 +88,7 @@ public static int quickTest2D(Geometry geomA, Geometry geomB, return reverseResult(quickTest2DPolygonPoint((Polygon) geomB, (Point) geomA, tolerance)); } - throw new GeometryException("internal error");// GEOMTHROW(internal_error);//what + throw GeometryException.GeometryInternalError();// GEOMTHROW(internal_error);//what // else? } case Geometry.GeometryType.Envelope: { @@ -110,7 +110,7 @@ public static int quickTest2D(Geometry geomA, Geometry geomB, return reverseResult(quickTest2DPolygonEnvelope( (Polygon) geomB, (Envelope) geomA, tolerance)); } - throw new GeometryException("internal error");// GEOMTHROW(internal_error);//what + throw GeometryException.GeometryInternalError();// GEOMTHROW(internal_error);//what // else? } case Geometry.GeometryType.MultiPoint: { @@ -131,7 +131,7 @@ public static int quickTest2D(Geometry geomA, Geometry geomB, return reverseResult(quickTest2DPolygonMultiPoint( (Polygon) geomB, (MultiPoint) geomA, tolerance)); } - throw new GeometryException("internal error");// GEOMTHROW(internal_error);//what + throw GeometryException.GeometryInternalError();// GEOMTHROW(internal_error);//what // else? } case Geometry.GeometryType.Polyline: { @@ -152,7 +152,7 @@ public static int quickTest2D(Geometry geomA, Geometry geomB, return reverseResult(quickTest2DPolygonPolyline( (Polygon) geomB, (Polyline) geomA, tolerance)); } - throw new GeometryException("internal error");// GEOMTHROW(internal_error);//what + throw GeometryException.GeometryInternalError();// GEOMTHROW(internal_error);//what // else? } case Geometry.GeometryType.Polygon: { @@ -173,12 +173,12 @@ public static int quickTest2D(Geometry geomA, Geometry geomB, return quickTest2DPolygonPolygon((Polygon) geomA, (Polygon) geomB, tolerance); } - throw new GeometryException("internal error");// GEOMTHROW(internal_error);//what + throw GeometryException.GeometryInternalError();// GEOMTHROW(internal_error);//what // else? } default: - throw new GeometryException("internal error");// GEOMTHROW(internal_error);//what + throw GeometryException.GeometryInternalError();// GEOMTHROW(internal_error);//what // else? // return 0; } @@ -581,7 +581,7 @@ private static int quickTest2DPolygonPoint(Polygon geomA, Point2D ptB, if (pipres == PolygonUtils.PiPResult.PiPBoundary) return (int) Relation.Touches;// clementini's touches - throw new GeometryException("internal error");// GEOMTHROW(internal_error); + throw GeometryException.GeometryInternalError();// GEOMTHROW(internal_error); // //what else // return 0; } diff --git a/src/main/java/com/esri/core/geometry/OperatorIntersection.java b/src/main/java/com/esri/core/geometry/OperatorIntersection.java index 2416fb06..c1230446 100644 --- a/src/main/java/com/esri/core/geometry/OperatorIntersection.java +++ b/src/main/java/com/esri/core/geometry/OperatorIntersection.java @@ -25,7 +25,6 @@ package com.esri.core.geometry; import com.esri.core.geometry.Operator.Type; -import com.esri.core.geometry.CombineOperator; /** *Intersection of geometries by a given geometry. @@ -60,7 +59,7 @@ public abstract GeometryCursor execute(GeometryCursor inputGeometries, *The bitmask of values (1 << dim), where dim is the desired dimension value, is used to indicate *what dimensions of geometry one wants to be returned. For example, to return *multipoints and lines only, pass (1 << 0) | (1 << 1), which is equivalen to 1 | 2, or 3. - *\return Returns the cursor of the intersection result. The cursors' get_geometry_ID method returns the current ID of the input geometry + *@return Returns the cursor of the intersection result. The cursors' get_geometry_ID method returns the current ID of the input geometry *being processed. Wh dimensionMask is a bitmask, there will be n result geometries per one input geometry returned, where n is the number *of bits set in the bitmask. For example, if the dimensionMask is 5, there will be two geometries per one input geometry. * @@ -85,6 +84,7 @@ public abstract GeometryCursor execute(GeometryCursor input_geometries, *where mask can be either -1 or min(1 << input_geometry.getDimension(), 1 << intersector.getDimension()); *@param inputGeometry is the Geometry instance to be intersected by the intersector. *@param intersector is the intersector Geometry. + *@param sr The spatial reference to get the tolerance value from. Can be null, then the tolerance is calculated from the input geometries. *@return Returns the intersected Geometry. */ public abstract Geometry execute(Geometry inputGeometry, diff --git a/src/main/java/com/esri/core/geometry/OperatorIntersectionCursor.java b/src/main/java/com/esri/core/geometry/OperatorIntersectionCursor.java index 9d5437dc..e06670dc 100644 --- a/src/main/java/com/esri/core/geometry/OperatorIntersectionCursor.java +++ b/src/main/java/com/esri/core/geometry/OperatorIntersectionCursor.java @@ -118,16 +118,16 @@ Geometry intersect(Geometry input_geom) { // m_geomIntersector, m_spatial_reference, m_progress_tracker); // Preprocess geometries to be clipped to the extent of intersection to // get rid of extra segments. - double tol = 0; + double t = InternalUtils.calculateToleranceFromGeometry(m_spatial_reference, commonExtent, true); Envelope2D env = new Envelope2D(); m_geomIntersector.queryEnvelope2D(env); Envelope2D env1 = new Envelope2D(); input_geom.queryEnvelope2D(env1); + env.inflate(2.0 * t, 2.0 * t); env.intersect(env1); assert (!env.isEmpty()); - double t = InternalUtils.calculateToleranceFromGeometry( - m_spatial_reference, commonExtent, true) * 10; - env.inflate(10 * t, 10 * t); + env.inflate(100 * t, 100 * t); + double tol = 0; Geometry clippedIntersector = Clipper.clip(m_geomIntersector, env, tol, 0.0); Geometry clippedInputGeom = Clipper.clip(input_geom, env, tol, 0.0); @@ -191,18 +191,21 @@ GeometryCursor intersectEx(Geometry input_geom) { Envelope2D commonExtent = InternalUtils.getMergedExtent( m_geomIntersector, input_geom); + double t = InternalUtils.calculateToleranceFromGeometry( + m_spatial_reference, commonExtent, true); + // Preprocess geometries to be clipped to the extent of intersection to // get rid of extra segments. - double tol = 0; + Envelope2D env = new Envelope2D(); m_geomIntersector.queryEnvelope2D(env); + env.inflate(2 * t, 2 * t); Envelope2D env1 = new Envelope2D(); input_geom.queryEnvelope2D(env1); env.intersect(env1); assert (!env.isEmpty()); - double t = InternalUtils.calculateToleranceFromGeometry( - m_spatial_reference, commonExtent, true) * 10; - env.inflate(10 * t, 10 * t); + env.inflate(100 * t, 100 * t); + double tol = 0; Geometry clippedIntersector = Clipper.clip(m_geomIntersector, env, tol, 0.0); Geometry clippedInputGeom = Clipper.clip(input_geom, env, tol, 0.0); @@ -252,6 +255,7 @@ Geometry tryNativeImplementation_(Geometry input_geom) { input_geom.queryEnvelope2D(env2D1); Envelope2D env2D2 = new Envelope2D(); m_geomIntersector.queryEnvelope2D(env2D2); + env2D2.inflate(2.0 * tolerance, 2.0 * tolerance); bResultIsEmpty = !env2D1.isIntersecting(env2D2); } @@ -369,7 +373,7 @@ else if (dim1 == 0) { if (m_geomIntersectorType == Geometry.GeometryType.Point) return TopologicalOperations.intersection( (Point) m_geomIntersector, input_geom, tolerance1); - throw new GeometryException("internal error"); + throw GeometryException.GeometryInternalError(); } // Try Polyline vs Polygon @@ -402,6 +406,7 @@ Geometry tryFastIntersectPolylinePolygon_(Polyline polyline, Polygon polygon) { polygonImpl.queryEnvelope2D(clipEnvelope); Envelope2D env1 = new Envelope2D(); polylineImpl.queryEnvelope2D(env1); + env1.inflate(2.0 * tolerance, 2.0 * tolerance); clipEnvelope.intersect(env1); assert (!clipEnvelope.isEmpty()); } @@ -453,6 +458,7 @@ Geometry tryFastIntersectPolylinePolygon_(Polyline polyline, Polygon polygon) { polygon = (Polygon) clippedPolygon; polygonImpl = (MultiPathImpl) polygon._getImpl(); + accel = polygonImpl._getAccelerators();//update accelerators } if (unresolvedSegments < 0) { diff --git a/src/main/java/com/esri/core/geometry/OperatorIntersectionLocal.java b/src/main/java/com/esri/core/geometry/OperatorIntersectionLocal.java index f9adfbca..bb13dcf3 100644 --- a/src/main/java/com/esri/core/geometry/OperatorIntersectionLocal.java +++ b/src/main/java/com/esri/core/geometry/OperatorIntersectionLocal.java @@ -65,8 +65,7 @@ public boolean accelerateGeometry(Geometry geometry, if (!canAccelerateGeometry(geometry)) return false; - double tol = spatialReference != null ? spatialReference - .getTolerance(VertexDescription.Semantics.POSITION) : 0; + double tol = InternalUtils.calculateToleranceFromGeometry(spatialReference, geometry, false); boolean accelerated = ((MultiVertexGeometryImpl) geometry._getImpl()) ._buildQuadTreeAccelerator(accelDegree); accelerated |= ((MultiVertexGeometryImpl) geometry._getImpl()) diff --git a/src/main/java/com/esri/core/geometry/OperatorProject.java b/src/main/java/com/esri/core/geometry/OperatorProject.java index cfd1bec1..da2a1712 100644 --- a/src/main/java/com/esri/core/geometry/OperatorProject.java +++ b/src/main/java/com/esri/core/geometry/OperatorProject.java @@ -71,11 +71,39 @@ public abstract int transform(ProjectionTransformation transform, * @return projected coordinates in the interleaved form. */ public abstract double[] transform(ProjectionTransformation transform, - double[] coordsSrc, int pointCount); + double[] coordsSrc, int pointCount); + + /** + * Folds a geometry into the 360 degree range of the associated spatial reference. If the spatial reference be a 'pannable' PCS or GCS. For other spatial types, the function throws an invalid + * argument exception. A pannable PCS it a Rectangular PCS where the x coordinate range is equivalent to a 360 degree range on the defining geographic Coordinate System(GCS). If the spatial + * reference is a GCS then it is always pannable(default 360 range for spatial reference in GCS coordinates is -180 to 180) + * + * If the geometry is an Envelope fold_into_360_range returns a polygon, unless the Envelope is empty, in which case the empty envelope is returned. The result geometry will be completely inside of + * the coordinate system extent. The folding happens where geometry intersects the min or max meridian of the spatial reference and when geometry is completely outside of the min-max meridian range. + * Folding does not preserve geodetic area or length. Folding does not preserve perimeter of a polygon. + * + * @param geom The geometry to be folded. + * @param pannableSR The pannable Spatial Reference. + * @return Folded geometry. + */ + public abstract Geometry foldInto360Range(Geometry geom, SpatialReference pannableSR); + + /** + * Same as fold_into_360_range. The difference is that this function preserves geodetic area of polygons and geodetic length of polylines. It does not preserve regular area and length or perimeter + * of polygons. Also, this function might change tangent of the lines at the points of folding. + * + * If the geometry is an Envelope fold_into_360_range returns a polygon, unless the Envelope is empty, in which case the empty envelope is returned. The result geometry will be completely inside of + * the coordinate system extent. The folding happens where geometry intersects the min or max meridian of the spatial reference and when geometry is completely outside of the min-max meridian range. + * + * @param geom The geometry to be folded. + * @param pannableSR The pannable Spatial Reference. + * @param curveType The type of geodetic curve to use to produce vertices at the points of folding. \return Folded geometry. + */ + public abstract Geometry foldInto360RangeGeodetic(Geometry geom, SpatialReference pannableSR, int curveType); public static OperatorProject local() { return (OperatorProject) OperatorFactoryLocal.getInstance() - .getOperator(Type.Project); + .getOperator(Type.Project); } } diff --git a/src/main/java/com/esri/core/geometry/OperatorProjectLocal.java b/src/main/java/com/esri/core/geometry/OperatorProjectLocal.java index c44c7e1d..78598ca6 100644 --- a/src/main/java/com/esri/core/geometry/OperatorProjectLocal.java +++ b/src/main/java/com/esri/core/geometry/OperatorProjectLocal.java @@ -49,4 +49,15 @@ public double[] transform(ProjectionTransformation transform, throw new GeometryException("not implemented"); } + @Override + public Geometry foldInto360RangeGeodetic(/* const */Geometry _geom, /* const */ + SpatialReference pannableSR, /* GeodeticCurveType */int curveType) { + throw new GeometryException("not implemented"); + } + + @Override + public Geometry foldInto360Range(/* const */Geometry geom, /* const */ + SpatialReference pannableSR) { + throw new GeometryException("not implemented"); + } } diff --git a/src/main/java/com/esri/core/geometry/OperatorProximity2D.java b/src/main/java/com/esri/core/geometry/OperatorProximity2D.java index d8b5b33b..80327e18 100644 --- a/src/main/java/com/esri/core/geometry/OperatorProximity2D.java +++ b/src/main/java/com/esri/core/geometry/OperatorProximity2D.java @@ -36,7 +36,27 @@ public Type getType() { } /** - * Returns the nearest coordinate on the Geometry to the given input point. + *Returns the nearest coordinate on the Geometry to the given input point. + *@param geom The input Geometry. + *@param inputPoint The query point. + *@param bTestPolygonInterior When true and geom is a polygon, the function will test if the input_point is inside of the polygon. Points that are + *inside of the polygon have zero distance to the polygon. When false, the function will not check if the point is inside of the polygon, + *but only determine proximity to the boundary. + *@param bCalculateLeftRightSide The function will calculate left/right side of polylines or polygons when the parameter is True. + *\return Returns the result of proximity calculation. See Proximity_2D_result. + */ + public abstract Proximity2DResult getNearestCoordinate(Geometry geom, + Point inputPoint, boolean bTestPolygonInterior, + boolean bCalculateLeftRightSide); + + /** + *Returns the nearest coordinate on the Geometry to the given input point. + *@param geom The input Geometry. + *@param inputPoint The query point. + *@param bTestPolygonInterior When true and geom is a polygon, the function will test if the input_point is inside of the polygon. Points that are + *inside of the polygon have zero distance to the polygon. When false, the function will not check if the point is inside of the polygon, + *but only determine proximity to the boundary. + *\return Returns the result of proximity calculation. See Proximity_2D_result. */ public abstract Proximity2DResult getNearestCoordinate(Geometry geom, Point inputPoint, boolean bTestPolygonInterior); @@ -74,4 +94,8 @@ public static OperatorProximity2D local() { .getOperator(Type.Proximity2D); } + interface ProxResultInfo { + static final int rightSide = 0x1; + } + } diff --git a/src/main/java/com/esri/core/geometry/OperatorProximity2DLocal.java b/src/main/java/com/esri/core/geometry/OperatorProximity2DLocal.java index 89171166..363c29a4 100644 --- a/src/main/java/com/esri/core/geometry/OperatorProximity2DLocal.java +++ b/src/main/java/com/esri/core/geometry/OperatorProximity2DLocal.java @@ -30,9 +30,199 @@ class OperatorProximity2DLocal extends OperatorProximity2D { + class Side_helper { + int m_i1; + int m_i2; + boolean m_bRight1; + boolean m_bRight2; + + void reset() { + m_i1 = -1; + m_i2 = -1; + m_bRight1 = false; + m_bRight2 = false; + } + + int find_non_degenerate(SegmentIterator segIter, int vertexIndex, + int pathIndex) { + segIter.resetToVertex(vertexIndex, pathIndex); + + while (segIter.hasNextSegment()) { + Segment segment = segIter.nextSegment(); + double length = segment.calculateLength2D(); + + if (length != 0.0) + return segIter.getStartPointIndex(); + } + + segIter.resetToVertex(vertexIndex, pathIndex); + + while (segIter.hasPreviousSegment()) { + Segment segment = segIter.previousSegment(); + double length = segment.calculateLength2D(); + + if (length != 0) + return segIter.getStartPointIndex(); + } + + return -1; + } + + int find_prev_non_degenerate(SegmentIterator segIter, int index) { + segIter.resetToVertex(index, -1); + + while (segIter.hasPreviousSegment()) { + Segment segment = segIter.previousSegment(); + double length = segment.calculateLength2D(); + + if (length != 0) + return segIter.getStartPointIndex(); + } + + return -1; + } + + int find_next_non_degenerate(SegmentIterator segIter, int index) { + segIter.resetToVertex(index, -1); + segIter.nextSegment(); + + while (segIter.hasNextSegment()) { + Segment segment = segIter.nextSegment(); + double length = segment.calculateLength2D(); + + if (length != 0) + return segIter.getStartPointIndex(); + } + + return -1; + } + + void find_analysis_pair_from_index(Point2D inputPoint, + SegmentIterator segIter, int vertexIndex, int pathIndex) { + m_i1 = find_non_degenerate(segIter, vertexIndex, pathIndex); + + if (m_i1 != -1) { + segIter.resetToVertex(m_i1, -1); + Segment segment1 = segIter.nextSegment(); + double t1 = segment1.getClosestCoordinate(inputPoint, false); + Point2D p1 = segment1.getCoord2D(t1); + double d1 = Point2D.sqrDistance(p1, inputPoint); + Point2D pq = new Point2D(); + pq.setCoords(p1); + pq.sub(segment1.getStartXY()); + Point2D pr = new Point2D(); + pr.setCoords(inputPoint); + pr.sub(segment1.getStartXY()); + m_bRight1 = (pq.crossProduct(pr) < 0); + + m_i2 = find_next_non_degenerate(segIter, m_i1); + if (m_i2 != -1) { + segIter.resetToVertex(m_i2, -1); + Segment segment2 = segIter.nextSegment(); + double t2 = segment2 + .getClosestCoordinate(inputPoint, false); + Point2D p2 = segment2.getCoord2D(t2); + double d2 = Point2D.sqrDistance(p2, inputPoint); + + if (d2 > d1) { + m_i2 = -1; + } else { + pq.setCoords(p2); + pq.sub(segment2.getStartXY()); + pr.setCoords(inputPoint); + pr.sub(segment2.getStartXY()); + m_bRight2 = (pq.crossProduct(pr) < 0); + } + } + + if (m_i2 == -1) { + m_i2 = find_prev_non_degenerate(segIter, m_i1); + if (m_i2 != -1) { + segIter.resetToVertex(m_i2, -1); + Segment segment2 = segIter.nextSegment(); + double t2 = segment2.getClosestCoordinate(inputPoint, + false); + Point2D p2 = segment2.getCoord2D(t2); + double d2 = Point2D.sqrDistance(p2, inputPoint); + + if (d2 > d1) + m_i2 = -1; + else { + pq.setCoords(p2); + pq.sub(segment2.getStartXY()); + pr.setCoords(inputPoint); + pr.sub(segment2.getStartXY()); + m_bRight2 = (pq.crossProduct(pr) < 0); + + int itemp = m_i1; + m_i1 = m_i2; + m_i2 = itemp; + + boolean btemp = m_bRight1; + m_bRight1 = m_bRight2; + m_bRight2 = btemp; + } + } + } + } + } + + // Try to find two segements that are not degenerate + boolean calc_side(Point2D inputPoint, boolean bRight, + MultiPath multipath, int vertexIndex, int pathIndex) { + SegmentIterator segIter = multipath.querySegmentIterator(); + + find_analysis_pair_from_index(inputPoint, segIter, vertexIndex, + pathIndex); + + if (m_i1 != -1 && m_i2 == -1) {// could not find a pair of segments + return m_bRight1; + } + + if (m_i1 != -1 && m_i2 != -1) { + if (m_bRight1 == m_bRight2) + return m_bRight1;// no conflicting result for the side + else { + // the conflicting result, that we are trying to resolve, + // happens in the obtuse (outer) side of the turn only. + segIter.resetToVertex(m_i1, -1); + Segment segment1 = segIter.nextSegment(); + Point2D tang1 = segment1._getTangent(1.0); + + segIter.resetToVertex(m_i2, -1); + Segment segment2 = segIter.nextSegment(); + Point2D tang2 = segment2._getTangent(0.0); + + double cross = tang1.crossProduct(tang2); + + if (cross >= 0) // the obtuse angle is on the right side + { + return true; + } else // the obtuse angle is on the right side + { + return false; + } + } + } else { + assert (m_i1 == -1 && m_i2 == -1); + return bRight;// could not resolve the side. So just return the + // old value. + } + } + } + @Override public Proximity2DResult getNearestCoordinate(Geometry geom, Point inputPoint, boolean bTestPolygonInterior) { + + return getNearestCoordinate(geom, inputPoint, bTestPolygonInterior, + false); + } + + @Override + public Proximity2DResult getNearestCoordinate(Geometry geom, + Point inputPoint, boolean bTestPolygonInterior, + boolean bCalculateLeftRightSide) { if (geom.isEmpty()) return new Proximity2DResult(); @@ -55,8 +245,8 @@ public Proximity2DResult getNearestCoordinate(Geometry geom, (MultiVertexGeometry) proxmityTestGeom, inputPoint2D); case Geometry.GeometryType.Polyline: case Geometry.GeometryType.Polygon: - return polyPathGetNearestCoordinate((MultiPath) proxmityTestGeom, - inputPoint2D, bTestPolygonInterior); + return multiPathGetNearestCoordinate((MultiPath) proxmityTestGeom, + inputPoint2D, bTestPolygonInterior, bCalculateLeftRightSide); default: { throw new GeometryException("not implemented"); } @@ -129,64 +319,92 @@ public Proximity2DResult[] getNearestVertices(Geometry geom, } } - Proximity2DResult polyPathGetNearestCoordinate(MultiPath geom, - Point2D inputPoint, boolean bTestPolygonInterior) { - Proximity2DResult result = new Proximity2DResult(); + Proximity2DResult multiPathGetNearestCoordinate(MultiPath geom, + Point2D inputPoint, boolean bTestPolygonInterior, + boolean bCalculateLeftRightSide) { + if (geom.getType() == Geometry.Type.Polygon && bTestPolygonInterior) { + Envelope2D env = new Envelope2D(); + geom.queryEnvelope2D(env); + double tolerance = InternalUtils.calculateToleranceFromGeometry( + null, env, false); + + PolygonUtils.PiPResult pipResult; - if (geom.getType() == (Geometry.Type.Polygon) && bTestPolygonInterior) { - OperatorFactoryLocal factory = OperatorFactoryLocal.getInstance(); - OperatorDisjoint operatorDisjoint = (OperatorDisjoint) factory - .getOperator(Type.Disjoint); + if (bCalculateLeftRightSide) + pipResult = PolygonUtils.isPointInPolygon2D((Polygon) geom, + inputPoint, 0.0); + else + pipResult = PolygonUtils.isPointInPolygon2D((Polygon) geom, + inputPoint, tolerance); - Point point = new Point(geom.getDescription()); - point.setXY(inputPoint.x, inputPoint.y); + if (pipResult != PolygonUtils.PiPResult.PiPOutside) { + Proximity2DResult result = new Proximity2DResult(inputPoint, 0, + 0.0); + + if (bCalculateLeftRightSide) + result.setRightSide(true); - boolean disjoint = operatorDisjoint - .execute(geom, point, null, null); - if (!disjoint) { - result._setParams(inputPoint.x, inputPoint.y, 0, 0.0); return result; } } - MultiPathImpl mpImpl = (MultiPathImpl) geom._getImpl(); - SegmentIteratorImpl segIter = mpImpl.querySegmentIterator(); + SegmentIterator segIter = geom.querySegmentIterator(); - Point2D closest = null;// new Point2D(); - int closestIndex = 0; + Point2D closest = new Point2D(); + int closestVertexIndex = -1; + int closestPathIndex = -1; double closestDistanceSq = NumberUtils.doubleMax(); + boolean bRight = false; + int num_candidates = 0; while (segIter.nextPath()) { while (segIter.hasNextSegment()) { Segment segment = segIter.nextSegment(); double t = segment.getClosestCoordinate(inputPoint, false); + Point2D point = segment.getCoord2D(t); double distanceSq = Point2D.sqrDistance(point, inputPoint); if (distanceSq < closestDistanceSq) { + num_candidates = 1; closest = point; - closestIndex = segIter.getStartPointIndex(); + closestVertexIndex = segIter.getStartPointIndex(); + closestPathIndex = segIter.getPathIndex(); closestDistanceSq = distanceSq; + } else if (distanceSq == closestDistanceSq) { + num_candidates++; } } } - result._setParams(closest.x, closest.y, closestIndex, - Math.sqrt(closestDistanceSq)); + Proximity2DResult result = new Proximity2DResult(closest, + closestVertexIndex, Math.sqrt(closestDistanceSq)); - return result; - } + if (bCalculateLeftRightSide) { + segIter.resetToVertex(closestVertexIndex, closestPathIndex); + Segment segment = segIter.nextSegment(); + bRight = (Point2D.orientationRobust(inputPoint, + segment.getStartXY(), segment.getEndXY()) < 0); - Proximity2DResult pointGetNearestVertex(Point geom, Point2D inputPoint) { - Proximity2DResult result = new Proximity2DResult(); + if (num_candidates > 1) { + Side_helper sideHelper = new Side_helper(); + sideHelper.reset(); + bRight = sideHelper.calc_side(inputPoint, bRight, geom, + closestVertexIndex, closestPathIndex); + } - Point2D pt = geom.getXY(); - double distance = Point2D.distance(pt, inputPoint); - result._setParams(pt.x, pt.y, 0, distance); + result.setRightSide(bRight); + } return result; } + Proximity2DResult pointGetNearestVertex(Point geom, Point2D input_point) { + Point2D pt = geom.getXY(); + double distance = Point2D.distance(pt, input_point); + return new Proximity2DResult(pt, 0, distance); + } + Proximity2DResult multiVertexGetNearestVertex(MultiVertexGeometry geom, Point2D inputPoint) { MultiVertexGeometryImpl mpImpl = (MultiVertexGeometryImpl) geom diff --git a/src/main/java/com/esri/core/geometry/OperatorRelate.java b/src/main/java/com/esri/core/geometry/OperatorRelate.java index 258a71bf..b1e435bf 100644 --- a/src/main/java/com/esri/core/geometry/OperatorRelate.java +++ b/src/main/java/com/esri/core/geometry/OperatorRelate.java @@ -24,6 +24,7 @@ package com.esri.core.geometry; +import com.esri.core.geometry.Geometry.GeometryAccelerationDegree; import com.esri.core.geometry.Operator.Type; /** @@ -51,5 +52,19 @@ public static OperatorRelate local() { return (OperatorRelate) OperatorFactoryLocal.getInstance().getOperator( Type.Relate); } + + @Override + public boolean canAccelerateGeometry(Geometry geometry) { + return RelationalOperations.Accelerate_helper + .can_accelerate_geometry(geometry); + } + + @Override + public boolean accelerateGeometry(Geometry geometry, + SpatialReference spatialReference, + GeometryAccelerationDegree accelDegree) { + return RelationalOperations.Accelerate_helper.accelerate_geometry( + geometry, spatialReference, accelDegree); + } } diff --git a/src/main/java/com/esri/core/geometry/OperatorShapePreservingDensify.java b/src/main/java/com/esri/core/geometry/OperatorShapePreservingDensify.java new file mode 100644 index 00000000..3aa5ed8e --- /dev/null +++ b/src/main/java/com/esri/core/geometry/OperatorShapePreservingDensify.java @@ -0,0 +1,71 @@ +/* + Copyright 1995-2013 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ +package com.esri.core.geometry; + +/** + * Densifies geometries preserving the shape of the segments in a given spatial reference by length and/or deviation. The elliptic arc lengths of the resulting line segments are no longer than the + * given max length, and the line segments will be closer than the given max deviation to both the original segment curve and the joining elliptic arcs. + */ +abstract class OperatorShapePreservingDensify extends Operator { + + @Override + public Type getType() { + return Type.ShapePreservingDensify; + } + + /** + * Performs the Shape Preserving Densify operation on the geometry set. Attributes are interpolated along the scalar t-values of the input segments obtained from the length ratios along the + * densified segments. + * + * @param geoms The geometries to be densified. + * @param sr The spatial reference of the geometries. + * @param maxLengthMeters The maximum segment length allowed. Must be a positive value to be used. Pass zero or NaN to disable densification by length. + * @param maxDeviationMeters The maximum deviation. Must be a positive value to be used. Pass zero or NaN to disable densification by deviation. + * @param reserved Must be 0 or NaN. Reserved for future use. Throws and exception if not NaN or 0. + * @return Returns the densified geometries (It does nothing to geometries with dim < 1, but simply passes them along). + * + * The operation always starts from the lowest point on the segment, thus guaranteeing that topologically equal segments are always densified exactly the same. + */ + public abstract GeometryCursor execute(GeometryCursor geoms, SpatialReference sr, double maxLengthMeters, double maxDeviationMeters, double reserved, ProgressTracker progressTracker); + + /** + * Performs the Shape Preserving Densify operation on the geometry. Attributes are interpolated along the scalar t-values of the input segments obtained from the length ratios along the densified + * segments. + * + * @param geom The geometry to be densified. + * @param sr The spatial reference of the geometry. + * @param maxLengthMeters The maximum segment length allowed. Must be a positive value to be used. Pass zero or NaN to disable densification by length. + * @param maxDeviationMeters The maximum deviation. Must be a positive value to be used. Pass zero or NaN to disable densification by deviation. + * @param reserved Must be 0 or NaN. Reserved for future use. Throws and exception if not NaN or 0. + * @return Returns the densified geometries (It does nothing to geometries with dim < 1, but simply passes them along). + * + * The operation always starts from the lowest point on the segment, thus guaranteeing that topologically equal segments are always densified exactly the same. + */ + public abstract Geometry execute(Geometry geom, SpatialReference sr, double maxLengthMeters, double maxDeviationMeters, double reserved, ProgressTracker progressTracker); + + public static OperatorShapePreservingDensify local() { + return (OperatorShapePreservingDensify) OperatorFactoryLocal.getInstance() + .getOperator(Type.ShapePreservingDensify); + } +} diff --git a/src/main/java/com/esri/core/geometry/OperatorShapePreservingDensifyLocal.java b/src/main/java/com/esri/core/geometry/OperatorShapePreservingDensifyLocal.java new file mode 100644 index 00000000..01bcf3a7 --- /dev/null +++ b/src/main/java/com/esri/core/geometry/OperatorShapePreservingDensifyLocal.java @@ -0,0 +1,44 @@ +/* + Copyright 1995-2013 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ + +package com.esri.core.geometry; + +//This is a stub +class OperatorShapePreservingDensifyLocal extends + OperatorShapePreservingDensify { + + @Override + public GeometryCursor execute(GeometryCursor geoms, SpatialReference sr, + double maxLengthMeters, double maxDeviationMeters, double reserved, + ProgressTracker progressTracker) { + throw new GeometryException("not implemented"); + } + + @Override + public Geometry execute(Geometry geom, SpatialReference sr, + double maxLengthMeters, double maxDeviationMeters, double reserved, + ProgressTracker progressTracker) { + throw new GeometryException("not implemented"); + } +} diff --git a/src/main/java/com/esri/core/geometry/OperatorSimpleRelation.java b/src/main/java/com/esri/core/geometry/OperatorSimpleRelation.java index 2cb90a96..f4afeff7 100644 --- a/src/main/java/com/esri/core/geometry/OperatorSimpleRelation.java +++ b/src/main/java/com/esri/core/geometry/OperatorSimpleRelation.java @@ -41,22 +41,15 @@ public abstract boolean execute(Geometry inputGeom1, Geometry inputGeom2, @Override public boolean canAccelerateGeometry(Geometry geometry) { - return RasterizedGeometry2D.canUseAccelerator(geometry); + return RelationalOperations.Accelerate_helper + .can_accelerate_geometry(geometry); } - + @Override public boolean accelerateGeometry(Geometry geometry, SpatialReference spatialReference, GeometryAccelerationDegree accelDegree) { - if (!canAccelerateGeometry(geometry)) - return false; - - double tol = spatialReference != null ? spatialReference - .getTolerance(VertexDescription.Semantics.POSITION) : 0; - boolean accelerated = ((MultiVertexGeometryImpl) geometry._getImpl()) - ._buildQuadTreeAccelerator(accelDegree); - accelerated |= ((MultiVertexGeometryImpl) geometry._getImpl()) - ._buildRasterizedGeometryAccelerator(tol, accelDegree); - return accelerated; + return RelationalOperations.Accelerate_helper.accelerate_geometry( + geometry, spatialReference, accelDegree); } } diff --git a/src/main/java/com/esri/core/geometry/OperatorSimplifyLocalHelper.java b/src/main/java/com/esri/core/geometry/OperatorSimplifyLocalHelper.java index 305b2d9d..791f394a 100644 --- a/src/main/java/com/esri/core/geometry/OperatorSimplifyLocalHelper.java +++ b/src/main/java/com/esri/core/geometry/OperatorSimplifyLocalHelper.java @@ -152,8 +152,8 @@ private boolean testToleranceDistance_(int xyindex1, int xyindex2) { double y1 = m_xy.read(2 * xyindex1 + 1); double x2 = m_xy.read(2 * xyindex2); double y2 = m_xy.read(2 * xyindex2 + 1); - boolean b = !Clusterer.isClusterCandidate(x1, y1, x2, y2, - m_toleranceIsSimple); + boolean b = !Clusterer.isClusterCandidate_(x1, y1, x2, y2, + m_toleranceIsSimple * m_toleranceIsSimple); if (!b) { if (m_geometry.getDimension() == 0) return false; @@ -332,7 +332,7 @@ private boolean checkCrackingPlanesweep_() // cracker,that uses planesweep EditShape editShape = new EditShape(); editShape.addGeometry(m_geometry); NonSimpleResult result = new NonSimpleResult(); - boolean bNonSimple = Cracker.needsCracking(editShape, + boolean bNonSimple = Cracker.needsCracking(false, editShape, m_toleranceIsSimple, result, m_progressTracker); if (bNonSimple) { result.m_vertexIndex1 = editShape @@ -1258,7 +1258,7 @@ private Edge createEdge_(/* const */Segment seg, int xyindex, int pathIndex, if (gt == Geometry.Type.Line) { edge = createEdgeLine_(seg); } else { - throw new GeometryException("internal error"); // implement + throw GeometryException.GeometryInternalError(); // implement // recycling for // curves } @@ -1646,17 +1646,19 @@ MultiVertexGeometry simplifyPlanar_() { m_editShape = new EditShape(); m_editShape.addGeometry(m_geometry); - assert (m_knownSimpleResult != GeometryXSimple.Strong); - if (m_knownSimpleResult != GeometryXSimple.Weak) { - CrackAndCluster.execute(m_editShape, m_toleranceSimplify, - m_progressTracker); - } - - if (m_geometry.getType().equals(Geometry.Type.Polygon)) { - Simplificator.execute(m_editShape, m_editShape.getFirstGeometry(), - m_knownSimpleResult); + if (m_editShape.getTotalPointCount() != 0) { + assert (m_knownSimpleResult != GeometryXSimple.Strong); + if (m_knownSimpleResult != GeometryXSimple.Weak) { + CrackAndCluster.execute(m_editShape, m_toleranceSimplify, + m_progressTracker); + } + + if (m_geometry.getType().equals(Geometry.Type.Polygon)) { + Simplificator.execute(m_editShape, m_editShape.getFirstGeometry(), + m_knownSimpleResult, false); + } } - + m_geometry = m_editShape.getGeometry(m_editShape.getFirstGeometry()); // extract // the // result @@ -1726,10 +1728,10 @@ else if (gt == Geometry.Type.Envelope) { false)); return bReturnValue ? 1 : 0; } else if (Geometry.isSegment(gt.value())) { - throw new GeometryException("internal error"); + throw GeometryException.GeometryInternalError(); // return seg.IsSimple(m_tolerance); } else if (!Geometry.isMultiVertex(gt.value())) { - throw new GeometryException("internal error");// What else? + throw GeometryException.GeometryInternalError();// What else? } double tolerance = InternalUtils.calculateToleranceFromGeometry( @@ -1824,7 +1826,7 @@ static protected int isSimpleAsFeature(/* const */Geometry geometry, /* const */ } else if (gt == Geometry.Type.Polygon) { knownSimpleResult = helper.polygonIsSimpleAsFeature_(); } else { - throw new GeometryException("internal error");// what else? + throw GeometryException.GeometryInternalError();// what else? } ((MultiVertexGeometryImpl) (geometry._getImpl())).setIsSimple( @@ -1881,7 +1883,7 @@ static int isSimpleOGC(/* const */Geometry geometry, /* const */ || gt == Geometry.Type.Polygon) { knownSimpleResult = helper.isSimplePlanarImpl_(); } else { - throw new GeometryException("internal error");// what else? + throw GeometryException.GeometryInternalError();// what else? } if (result != null) @@ -1954,7 +1956,7 @@ static protected Geometry simplifyAsFeature(/* const */Geometry geometry, /* con } else if (gt == Geometry.Type.Polygon) { result = (Geometry) (helper.polygonSimplifyAsFeature_()); } else { - throw new GeometryException("internal error"); // what else? + throw GeometryException.GeometryInternalError(); // what else? } return result; @@ -1999,12 +2001,11 @@ static Geometry simplifyOGC(/* const */Geometry geometry, /* const */ } if (!Geometry.isMultiVertex(gt.value())) { - throw new GeometryException("internal error"); // what else? + throw new GeometryException("OGC simplify is not implemented for this geometry type" + gt); } - MultiVertexGeometry result = TopologicalOperations.planarSimplify( - (MultiVertexGeometry) geometry, tolerance, false, false, - progressTracker); + MultiVertexGeometry result = TopologicalOperations.simplifyOGC( + (MultiVertexGeometry) geometry, tolerance, false, progressTracker); return result; } @@ -2134,9 +2135,6 @@ private static final class EdgeComparerForSelfIntersection implements @Override public int compare(Edge e1, Edge e2) { - // C++ style with bool operator() would be - // return parent->edgeAngleCompare_(*e1,*e2) < 0; - return parent.edgeAngleCompare_(e1, e2); } } diff --git a/src/main/java/com/esri/core/geometry/OperatorSimplifyLocalOGC.java b/src/main/java/com/esri/core/geometry/OperatorSimplifyLocalOGC.java index 8f6bce0f..aa9c5a9f 100644 --- a/src/main/java/com/esri/core/geometry/OperatorSimplifyLocalOGC.java +++ b/src/main/java/com/esri/core/geometry/OperatorSimplifyLocalOGC.java @@ -25,7 +25,6 @@ class OperatorSimplifyLocalOGC extends OperatorSimplifyOGC { - // Reviewed vs. Feb 8 2011 @Override public GeometryCursor execute(GeometryCursor geoms, SpatialReference spatialRef, boolean bForceSimplify, @@ -43,7 +42,6 @@ public boolean isSimpleOGC(Geometry geom, SpatialReference spatialRef, return res > 0; } - // Reviewed vs. Feb 8 2011 @Override public Geometry execute(Geometry geom, SpatialReference spatialRef, boolean bForceSimplify, ProgressTracker progressTracker) { diff --git a/src/main/java/com/esri/core/geometry/OperatorSymmetricDifference.java b/src/main/java/com/esri/core/geometry/OperatorSymmetricDifference.java index 17a2f5bf..bb914e77 100644 --- a/src/main/java/com/esri/core/geometry/OperatorSymmetricDifference.java +++ b/src/main/java/com/esri/core/geometry/OperatorSymmetricDifference.java @@ -24,7 +24,6 @@ package com.esri.core.geometry; import com.esri.core.geometry.Operator.Type; -import com.esri.core.geometry.CombineOperator; /** * Symmetric difference (XOR) operation between geometries. diff --git a/src/main/java/com/esri/core/geometry/OperatorUnion.java b/src/main/java/com/esri/core/geometry/OperatorUnion.java index 95cd8f93..69340bd2 100644 --- a/src/main/java/com/esri/core/geometry/OperatorUnion.java +++ b/src/main/java/com/esri/core/geometry/OperatorUnion.java @@ -25,14 +25,13 @@ package com.esri.core.geometry; import com.esri.core.geometry.Operator.Type; -import com.esri.core.geometry.CombineOperator; /** * * Union of geometries. * */ -public abstract class OperatorUnion extends Operator implements CombineOperator { +public abstract class OperatorUnion extends Operator implements CombineOperator{ @Override public Type getType() { return Type.Union; diff --git a/src/main/java/com/esri/core/geometry/OperatorUnionCursor.java b/src/main/java/com/esri/core/geometry/OperatorUnionCursor.java index cfc46871..53164ef2 100644 --- a/src/main/java/com/esri/core/geometry/OperatorUnionCursor.java +++ b/src/main/java/com/esri/core/geometry/OperatorUnionCursor.java @@ -25,37 +25,119 @@ package com.esri.core.geometry; import java.util.ArrayList; +import java.util.Map; +import java.util.Set; +import java.util.TreeMap; -class OperatorUnionCursor extends GeometryCursor { +final class OperatorUnionCursor extends GeometryCursor { private GeometryCursor m_inputGeoms; private ProgressTracker m_progress_tracker; private SpatialReferenceImpl m_spatial_reference; - private int m_index; - private boolean m_b_done; + private int m_index = -1; + private boolean m_b_done = false; + private boolean [] m_had_geometry = new boolean[4]; + private int [] m_dim_geom_counts = new int [4]; + private boolean m_b_union_all_dimensions = false; + private int m_max_dimension = -1; + private int m_added_geoms = 0; + private int m_current_dim = -1; + + private final static class Geom_pair + { + void init() { geom = null; vertex_count = -1; unioned = false; } + Geometry geom; + int vertex_count; + boolean unioned;//true if geometry is a result of union operation + } + + final static class Bin_type //bin array and the total vertex count in the bin + { + int bin_vertex_count = 0; + ArrayList geometries = new ArrayList(); + + void add_pair(Geom_pair geom) + { + bin_vertex_count += geom.vertex_count; + geometries.add(geom); + } + void pop_pair() + { + bin_vertex_count -= geometries.get(geometries.size() - 1).vertex_count; + geometries.remove(geometries.size() - 1); + } + Geom_pair back_pair() { return geometries.get(geometries.size() - 1); } + int geom_count() { return geometries.size(); } + } + + ArrayList< TreeMap > m_union_bins = new ArrayList< TreeMap >();//for each dimension there is a list of bins sorted by level OperatorUnionCursor(GeometryCursor inputGeoms1, SpatialReference sr, ProgressTracker progress_tracker) { - m_index = -1; - m_b_done = false; m_inputGeoms = inputGeoms1; m_spatial_reference = (SpatialReferenceImpl) (sr); m_progress_tracker = progress_tracker; - // Otherwise, unsupported use patternes could be produced. + } + + private Geometry get_result_geometry(int dim) { + assert (m_dim_geom_counts[dim] > 0); + java.util.TreeMap map = m_union_bins.get(dim); + Map.Entry e = map.firstEntry(); + Bin_type bin = e.getValue(); + + Geometry resG; + resG = bin.back_pair().geom; + boolean unioned = bin.back_pair().unioned; + map.remove(e.getKey()); + + if (unioned) { + resG = OperatorSimplify.local().execute(resG, m_spatial_reference, + false, m_progress_tracker); + if (dim == 0 && resG.getType() == Geometry.Type.Point) {// must + // return + // multipoint + // for + // points + MultiPoint mp = new MultiPoint(resG.getDescription()); + if (!resG.isEmpty()) + mp.add((Point) resG); + + resG = mp; + } + } - startDissolve(); + return resG; } @Override public Geometry next() { - if (m_b_done) + if (m_b_done && m_current_dim == m_max_dimension) return null; - m_b_done = true;// m_b_done is added to avoid calling - // m_inputGeoms->next() second time after it returned - // NULL. - - return dissolve_(); + while (!step_()) { + } + + if (m_max_dimension == -1) + return null;// empty input cursor + + if (m_b_union_all_dimensions) { + m_current_dim++; + while (true) { + if (m_current_dim > m_max_dimension || m_current_dim < 0) + throw GeometryException.GeometryInternalError(); + + if (m_had_geometry[m_current_dim]) + break; + } + + m_index++; + return get_result_geometry(m_current_dim); + } else { + m_index = 0; + assert (m_max_dimension >= 0); + m_current_dim = m_max_dimension; + return get_result_geometry(m_max_dimension); + } } @Override @@ -63,289 +145,143 @@ public int getGeometryID() { return m_index; } - private void step(){ - if (!bFinished) { - Geometry geom = m_inputGeoms.next(); - - if ((m_progress_tracker != null) - && !(m_progress_tracker.progress(-1, -1))) - throw new RuntimeException("user_canceled"); - - if (geom != null) { - if (geom.getDimension() > dimension) - { - GeomPair pair = new GeomPair(); - pair.init(); - pair.geom = geom; - int sz = getVertexCount_(geom); - pair.vertex_count = sz; - int level = getLevel_(sz); - - { - unionBins.clear(); - int resize = Math.max(16, level + 1); - for (int i = 0; i < resize; i++) - unionBins.add(null); - binSizes.resize(resize, 0); - unionBins.set(level, new ArrayList(0)); - unionBins.get(level).add(pair); - binSizes.set(level, sz); - totalToUnion = 1; - totalVertexCount = sz; - dimension = geom.getDimension(); - } - } else if (!geom.isEmpty() - && geom.getDimension() == dimension) { - GeomPair pair = new GeomPair(); - pair.init(); - pair.geom = geom; - int sz = getVertexCount_(geom); - pair.vertex_count = sz; - int level = getLevel_(sz); - - { - int resize = Math.max(unionBins.size(), level + 1); - if (resize > unionBins.size()) { - int grow = resize - unionBins.size(); - for (int i = 0; i < grow; i++) - unionBins.add(null); - } - binSizes.resize(resize, 0); - if (unionBins.get(level) == null) - unionBins - .set(level, new ArrayList(0)); - - unionBins.get(level).add(pair); - binSizes.write(level, binSizes.read(level) + sz); - - totalToUnion++; - totalVertexCount += sz; - } - } else { - // skip empty or geometries of lower dimension - } - } else { - bFinished = true; - } - } + private boolean step_(){ + if (m_b_done) + return true; - while (true)// union features that are in the unionBins + Geometry geom = null; + if (m_inputGeoms != null) { - if (!bFinished) {// when we are still loading geometries, union - // geometries of the same level, starting - // with the biggest level. - int imax = -1; - int maxSz = 0; - // Find a bin that contains more than one geometry and has - // the max vertex count. - for (int i = 0, n = unionBins.size(); i < n; i++) { - if (unionBins.get(i) != null - && unionBins.get(i).size() > 1 - && binSizes.read(i) > binVertexThreshold) { - if (maxSz < binSizes.read(i)) { - maxSz = binSizes.read(i); - imax = i; - } - } - } - - if (maxSz > 0) { - // load the found bin into the batchToUnion. - while (unionBins.get(imax).size() > 0) { - ArrayList bin = unionBins.get(imax); - batchToUnion.add(bin.get(bin.size() - 1)); - bin.remove(bin.size() - 1); - totalVertexCount -= batchToUnion.get(batchToUnion - .size() - 1).vertex_count; - binSizes.write( - imax, - binSizes.read(imax) - - batchToUnion.get(batchToUnion - .size() - 1).vertex_count); - } - } - } else if (totalToUnion > 1) {// bFinished_shared == true - we - // loaded all geometries - int level = 0; - int vertexCount = 0; - for (int i = 0, n = unionBins.size(); i < n - && (batchToUnion.size() < 2 || vertexCount < binVertexThreshold); i++) { - if (unionBins.get(i) != null) { - while (!unionBins.get(i).isEmpty() - && (batchToUnion.size() < 2 || vertexCount < binVertexThreshold)) { - ArrayList bin = unionBins.get(i); - batchToUnion.add(bin.get(bin.size() - 1)); - bin.remove(bin.size() - 1); - level = i; - totalVertexCount -= batchToUnion - .get(batchToUnion.size() - 1).vertex_count; - vertexCount += batchToUnion.get(batchToUnion - .size() - 1).vertex_count; - binSizes.write( - i, - binSizes.read(i) - - batchToUnion.get(batchToUnion - .size() - 1).vertex_count); - continue; - } - } - } - - if (batchToUnion.size() == 1)// never happens? - {// only one element. Put it back. - unionBins.get(level).add( - batchToUnion.get(batchToUnion.size() - 1)); - totalVertexCount += batchToUnion.get(batchToUnion - .size() - 1).vertex_count; - binSizes.write( - level, - binSizes.read(level) - + batchToUnion.get(batchToUnion.size() - 1).vertex_count); - batchToUnion.remove(batchToUnion.size() - 1); - } + geom = m_inputGeoms.next(); + if (geom == null) { + m_b_done = true; + m_inputGeoms = null; } - - if (!batchToUnion.isEmpty()) { - Geometry resGeom; - int resDim; - ArrayList geoms = new ArrayList(0); - geoms.ensureCapacity(batchToUnion.size()); - for (int i = 0, n = batchToUnion.size(); i < n; i++) { - geoms.add(batchToUnion.get(i).geom); - } - - resGeom = TopologicalOperations.dissolveDirty(geoms, - m_spatial_reference, m_progress_tracker); - // assert(Operator_factory_local::get_instance()->CanDoNewTopo(pair1.geom->get_geometry_type(), - // pair2.geom->get_geometry_type())); - // resGeom = - // Topological_operations::dissolve(batchToUnion[0].geom, - // batchToUnion[1].geom, m_spatial_reference, - // m_progress_tracker); - // Operator_factory_local::SaveJSONToTextFileDbg("c:/temp/buffer_dissolve.txt", - // *resGeom, nullptr); - resDim = resGeom.getDimension(); - - dissolved_something = true; - GeomPair pair = new GeomPair(); - pair.init(); - pair.geom = resGeom; - int sz = getVertexCount_(resGeom); - pair.vertex_count = sz; - int level = getLevel_(sz); - - int resize = Math.max(unionBins.size() + 1, level); - if (resize > unionBins.size()) { - int grow = resize - unionBins.size(); - for (int i = 0; i < grow; i++) - unionBins.add(null); - } - binSizes.resize(resize, 0); - - if (unionBins.get(level) == null) - unionBins.set(level, new ArrayList(0)); - - unionBins.get(level).add(pair); - binSizes.write(level, binSizes.read(level) + sz); - totalToUnion -= (batchToUnion.size() - 1); - - batchToUnion.clear(); - } else { - boolean bCanGo = totalToUnion == 1; - if (bFinished) - bLocalDone = true; - - break; + } + + ProgressTracker.checkAndThrow(m_progress_tracker); + + if (geom != null) { + int dim = geom.getDimension(); + m_had_geometry[dim] = true; + if (dim >= m_max_dimension && !m_b_union_all_dimensions) + { + add_geom(dim, false, geom); + if (dim > m_max_dimension && !m_b_union_all_dimensions) + { + //this geometry has higher dimension than the previously processed one + //Therefore we delete all lower dimensions (unless m_b_union_all_dimensions is true). + remove_all_bins_with_lower_dimension(dim); + } } + else + { + //this geometry is skipped + } + } else { + //geom is null. do nothing } + + if (m_added_geoms > 0) { + for (int dim = 0; dim <= m_max_dimension; dim++) + { + while (m_dim_geom_counts[dim] > 1) + { + ArrayList batch_to_union = collect_geometries_to_union(dim); + boolean serial_execution = true; + if (serial_execution) + { + if (batch_to_union.size() != 0) + { + Geometry geomRes = TopologicalOperations.dissolveDirty(batch_to_union, m_spatial_reference, m_progress_tracker); + add_geom(dim, true, geomRes); + } + else + { + break; + } + } + } + } + } + + return m_b_done; } - boolean bLocalDone = false; - int dimension = -1; - boolean bFinished = false; - int totalToUnion = 0; - int totalVertexCount = 0; - int binVertexThreshold = 10000; - boolean dissolved_something = false; - - ArrayList batchToUnion = new ArrayList(0); - ArrayList> unionBins = new ArrayList>( - 0); - AttributeStreamOfInt32 binSizes = new AttributeStreamOfInt32(0); - - private void startDissolve() { - m_index = m_inputGeoms.getGeometryID(); - - // Geometries are placed into the unionBins. - // Each bin stores geometries of certain size range. - // The bin number is calculated as log(N), where N is the number of - // vertices in geoemtry and the log is to a - // certain base (now it is 4). - unionBins.ensureCapacity(128); - binSizes.reserve(128); - - for (int i = 0; i < 16; i++) - unionBins.add(null); - - batchToUnion.ensureCapacity(32); + ArrayList collect_geometries_to_union(int dim) { + ArrayList batch_to_union = new ArrayList(); + ArrayList> entriesToRemove = new ArrayList>(); + Set> set = m_union_bins.get(dim) + .entrySet(); + for (Map.Entry e : set) { + //int level = e.getKey(); + Bin_type bin = e.getValue(); + + final int binVertexThreshold = 10000; + + if (m_b_done + || (bin.bin_vertex_count > binVertexThreshold && bin + .geom_count() > 1)) { + m_dim_geom_counts[dim] -= bin.geom_count(); + m_added_geoms -= bin.geom_count(); + while (bin.geometries.size() > 0) { + // empty geometries will be unioned too. + batch_to_union.add(bin.back_pair().geom); + bin.pop_pair(); + } + + entriesToRemove.add(e); + } + } + + set.removeAll(entriesToRemove); + return batch_to_union; } - @Override - public boolean tock() { - if (!m_b_done) - { - step(); + private void remove_all_bins_with_lower_dimension(int dim) { + // this geometry has higher dimension than the previously processed one + for (int i = 0; i < dim; i++) { + m_union_bins.get(i).clear(); + m_added_geoms -= m_dim_geom_counts[i]; + m_dim_geom_counts[i] = 0; } - return bFinished; } - private Geometry dissolve_() { - while (!bLocalDone) { - step(); - } - - Geometry resGeom = null; - for (int i = 0; i < unionBins.size(); i++) { - if (unionBins.get(i) != null && unionBins.get(i).size() > 0) - resGeom = unionBins.get(i).get(0).geom; + private void add_geom(int dimension, boolean unioned, Geometry geom) { + Geom_pair pair = new Geom_pair(); + pair.init(); + pair.geom = geom; + int sz = get_vertex_count_(geom); + pair.vertex_count = sz; + int level = get_level_(sz); + if (dimension + 1 > (int) m_union_bins.size()) { + for (int i = 0, n = Math.max(2, dimension + 1); i < n; i++) { + m_union_bins.add(new TreeMap()); + } } - if (resGeom == null) - return resGeom; - - if (dissolved_something) { - OperatorFactoryLocal engine = OperatorFactoryLocal.getInstance(); - OperatorSimplify simplify = (OperatorSimplify) engine - .getOperator(Operator.Type.Simplify); - resGeom = simplify.execute(resGeom, m_spatial_reference, false, - m_progress_tracker); + Bin_type bin = m_union_bins.get(dimension).get(level);//return null if level is abscent + if (bin == null) { + bin = new Bin_type(); + m_union_bins.get(dimension).put(level, bin); } - if (resGeom.getType().value() == Geometry.GeometryType.Point) {// must - // return - // multipoint - // for - // points - MultiPoint mp = new MultiPoint(resGeom.getDescription()); - if (!resGeom.isEmpty()) - mp.add((Point) resGeom); - resGeom = mp; - } + pair.unioned = unioned; + bin.add_pair(pair); - return resGeom; + // Update global cursor state + m_dim_geom_counts[dimension]++; + m_added_geoms++; + m_max_dimension = Math.max(m_max_dimension, dimension); } - private static final class GeomPair { - void init() { - geom = null; - vertex_count = -1; - } - - Geometry geom; - int vertex_count; + private static int get_level_(int sz) {// calculates logarithm of sz to base + // 4. + return sz > 0 ? (int) (Math.log((double) sz) / Math.log(4.0) + 0.5) + : (int) 0; } - private static int getVertexCount_(Geometry geom) { + private static int get_vertex_count_(Geometry geom) { int gt = geom.getType().value(); if (Geometry.isMultiVertex(gt)) { return ((MultiVertexGeometry) geom).getPointCount(); @@ -356,13 +292,12 @@ private static int getVertexCount_(Geometry geom) { } else if (Geometry.isSegment(gt)) { return 2; } else { - throw new GeometryException("internal error"); + throw GeometryException.GeometryInternalError(); } } - - private static int getLevel_(int sz) {// calculates logarithm of sz to base - // 4. - return sz > 0 ? (int) (Math.log((double) sz) / Math.log(4.0) + 0.5) - : (int) 0; + + @Override + public boolean tock() { + return step_(); } } diff --git a/src/main/java/com/esri/core/geometry/PlaneSweepCrackerHelper.java b/src/main/java/com/esri/core/geometry/PlaneSweepCrackerHelper.java index 175f485a..ab23f7c1 100644 --- a/src/main/java/com/esri/core/geometry/PlaneSweepCrackerHelper.java +++ b/src/main/java/com/esri/core/geometry/PlaneSweepCrackerHelper.java @@ -60,21 +60,14 @@ boolean sweep(EditShape shape, double tolerance) { m_b_cracked = false; m_tolerance = tolerance; m_tolerance_sqr = tolerance * tolerance; - // #ifdef _DEBUG_CRACKING_REPORT - // { - // DEBUGPRINTF(L"PlaneSweep along x\n"); - // } - // #endif boolean b_cracked = sweepImpl_(); - // #ifdef _DEBUG_CRACKING_REPORT - // { - // DEBUGPRINTF(L"PlaneSweep along y\n"); - // } - // #endif shape.applyTransformation(transform); - fillEventQueuePass2(); - b_cracked |= sweepImpl_(); + if (!b_cracked) { + fillEventQueuePass2(); + b_cracked |= sweepImpl_(); + } + m_shape.removeUserIndex(m_vertex_cluster_index); m_shape = null; return m_b_cracked; @@ -372,7 +365,7 @@ void addEdgeToCluster(int edge, int cluster) { assert (getEdgeCluster(edge, 0) != cluster); setEdgeCluster_(edge, 1, cluster); } else - throw new GeometryException("internal error"); + throw GeometryException.GeometryInternalError(); addEdgeToClusterImpl_(edge, cluster);// simply adds the edge to the list // of cluster edges. @@ -527,16 +520,9 @@ void mergeEdges_(int edge1, int edge2) { // Merged edges have different clusters (clusters have not yet been // merged) // merge clusters before merging the edges - Point2D pt11 = getClusterXY(cluster_1); - Point2D pt21 = getClusterXY(cluster21); - // #ifdef _DEBUG_TOPO - // Point_2D pt12, pt22; - // pt12 = get_cluster_xy(cluster2); - // pt22 = get_cluster_xy(cluster22); - // assert((pt11.is_equal(pt21) && pt12.is_equal(pt22)) || - // (pt12.is_equal(pt21) && pt11.is_equal(pt22))); - // #endif - if (pt11.isEqual(pt21)) { + getClusterXY(cluster_1, pt_1); + getClusterXY(cluster21, pt_2); + if (pt_1.isEqual(pt_2)) { if (cluster_1 != cluster21) { mergeClusters_(cluster_1, cluster21); assert (!m_modified_clusters.hasElement(cluster21)); @@ -776,13 +762,12 @@ void processSplitHelper1_(int index, int edge, // sweep structure. int count = intersector.getResultSegmentCount(index); Segment seg = intersector.getResultSegment(index, 0); - Point2D newStart = seg.getStartXY(); + seg.getStartXY(pt_2); int clusterStart = getEdgeCluster(edge, 0); - Point2D pt = getClusterXY(clusterStart); - if (!pt.isEqual(newStart)) { - int res1 = pt.compare(m_sweep_point); - int res2 = newStart.compare(m_sweep_point); - //if (pt.compare(m_sweep_point) > 0 && newStart.compare(m_sweep_point) < 0) + getClusterXY(clusterStart, pt_1); + if (!pt_1.isEqual(pt_2)) { + int res1 = pt_1.compare(m_sweep_point); + int res2 = pt_2.compare(m_sweep_point); if (res1 * res2 < 0) { m_complications = true;// point is not yet have been processed // but moved before the sweep point, @@ -797,13 +782,12 @@ void processSplitHelper1_(int index, int edge, } seg = intersector.getResultSegment(index, count - 1); - Point2D newEnd = seg.getEndXY(); + seg.getEndXY(pt_2); int clusterEnd = getEdgeCluster(edge, 1); - pt = getClusterXY(clusterEnd); - if (!pt.isEqual(newEnd)) { - int res1 = pt.compare(m_sweep_point); - int res2 = newEnd.compare(m_sweep_point); - //if (pt.compare(m_sweep_point) > 0 && newEnd.compare(m_sweep_point) < 0) + getClusterXY(clusterEnd, pt_1); + if (!pt_1.isEqual(pt_2)) { + int res1 = pt_1.compare(m_sweep_point); + int res2 = pt_2.compare(m_sweep_point); if (res1 * res2 < 0) { m_complications = true;// point is not yet have been processed // but moved before the sweep point. @@ -850,8 +834,6 @@ boolean checkAndFixIntersection_(int leftSweepNode, int rightSweepNode) { } void fixIntersection_(int left, int right) { - // static int dbg = 0; - // dbg++; m_b_cracked = true; int edge1 = m_sweep_structure.getElement(left); int edge2 = m_sweep_structure.getElement(right); @@ -897,24 +879,6 @@ void fixIntersection_(int left, int right) { m_complications = true; - // #ifdef _DEBUG_CRACKING_REPORT - // { - // for (int resi = 0; resi < 2; resi++) - // { - // DEBUGPRINTF(L"intersection result %d:\n", resi); - // for (int i = 0; i < - // m_segment_intersector.get_result_segment_count(resi); i++) - // { - // Point_2D pt_1 = m_segment_intersector.get_result_segment(resi, - // i)->get_start_xy(); - // Point_2D pt_2 = m_segment_intersector.get_result_segment(resi, - // i)->get_end_xy(); - // DEBUGPRINTF(L"(%0.17f, %0.17f --- %0.17f, %0.17f)\n", pt_1.x, - // pt_1.y, pt_2.x, pt_2.y); - // } - // } - // } - // #endif splitEdge_(edge1, edge2, -1, m_segment_intersector); m_segment_intersector.clear(); } @@ -933,38 +897,13 @@ void fixIntersectionPointSegment_(int cluster, int node) { seg_1 = m_line_1; } - // #ifdef _DEBUG_CRACKING_REPORT - // { - // Point_2D pt11, pt12, pt21; - // pt11 = seg_1->get_start_xy(); - // pt12 = seg_1->get_end_xy(); - // get_cluster_xy(cluster, pt21); - // DEBUGPRINTF(L"Intersecting edge %d (%0.4f, %0.4f - %0.4f, %0.4f) and cluster %d (%0.4f, %0.4f)\n", - // edge1, pt11.x, pt11.y, pt12.x, pt12.y, cluster, pt21.x, pt21.y); - // } - // #endif - int clusterVertex = getClusterFirstVertex(cluster); m_segment_intersector.pushSegment(seg_1); m_shape.queryPoint(clusterVertex, m_helper_point); m_segment_intersector.intersect(m_tolerance, m_helper_point, 0, 1.0, true); - // #ifdef _DEBUG_CRACKING_REPORT - // { - // DEBUGPRINTF(L"intersection result\n"); - // for (int i = 0; i < - // m_segment_intersector.get_result_segment_count(0); i++) - // { - // Point_2D pt_1 = m_segment_intersector.get_result_segment(0, - // i)->get_start_xy(); - // Point_2D pt_2 = m_segment_intersector.get_result_segment(0, - // i)->get_end_xy(); - // DEBUGPRINTF(L"(%0.17f, %0.17f --- %0.17f, %0.17f)\n", pt_1.x, - // pt_1.y, pt_2.x, pt_2.y); - // } - // } - // #endif + splitEdge_(edge1, -1, cluster, m_segment_intersector); m_segment_intersector.clear(); @@ -974,8 +913,6 @@ void insertNewEdges_() { if (m_edges_to_insert_in_sweep_structure.size() == 0) return; - // dbg_check_new_edges_array_(); - while (m_edges_to_insert_in_sweep_structure.size() != 0) { if (m_edges_to_insert_in_sweep_structure.size() > Math.max( (int) 100, m_shape.getTotalPointCount())) { @@ -987,10 +924,8 @@ void insertNewEdges_() { // iterate on the data one more time. } - int edge = m_edges_to_insert_in_sweep_structure - .get(m_edges_to_insert_in_sweep_structure.size() - 1); - m_edges_to_insert_in_sweep_structure - .resize(m_edges_to_insert_in_sweep_structure.size() - 1); + int edge = m_edges_to_insert_in_sweep_structure.getLast(); + m_edges_to_insert_in_sweep_structure.removeLast(); assert (getEdgeSweepNode(edge) == StridedIndexTypeCollection .impossibleIndex3()); @@ -1005,37 +940,18 @@ void insertNewEdges_() { boolean insertNewEdgeToSweepStructure_(int edge, int terminatingCluster) { assert (getEdgeSweepNode(edge) == -1); - // #ifdef _DEBUG_CRACKING_REPORT - // { - // Point_2D pt11, pt12; - // int c_1 = get_edge_cluster(edge, 0); - // int c_2 = get_edge_cluster(edge, 1); - // get_cluster_xy(c_1, pt11); - // get_cluster_xy(c_2, pt12); - // DEBUGPRINTF(L"Inserting edge %d (%0.4f, %0.4f - %0.4f, %0.4f) to sweep structure\n", - // edge, pt11.x, pt11.y, pt12.x, pt12.y); - // } - // #endif int newEdgeNode; if (m_b_continuing_segment_chain_optimization) { - // st_counter_insertions++; - // st_counter_insertions_optimized++; newEdgeNode = m_sweep_structure.addElementAtPosition( m_prev_neighbour, m_next_neighbour, edge, true, true, -1); m_b_continuing_segment_chain_optimization = false; } else { - // st_counter_insertions++; - // st_counter_insertions_unique++; newEdgeNode = m_sweep_structure.addUniqueElement(edge, -1); } if (newEdgeNode == -1) {// a coinciding edge. int existingNode = m_sweep_structure.getDuplicateElement(-1); int existingEdge = m_sweep_structure.getElement(existingNode); - // #ifdef _DEBUG_CRACKING_REPORT - // DEBUGPRINTF(L"Edge %d is a duplicate of %d. Merged\n", edge, - // existingEdge); - // #endif mergeEdges_(existingEdge, edge); return false; } @@ -1044,10 +960,6 @@ boolean insertNewEdgeToSweepStructure_(int edge, int terminatingCluster) { setEdgeSweepNode_(edge, newEdgeNode); if (m_sweep_comparator.intersectionDetected()) { - // #ifdef _DEBUG_CRACKING_REPORT - // DEBUGPRINTF(L"intersection detected\n"); - // #endif - // The edge has been inserted into the sweep structure and an // intersection has beebn found. The edge will be split and removed. m_sweep_comparator.clearIntersectionDetectedFlag(); @@ -1062,11 +974,13 @@ boolean insertNewEdgeToSweepStructure_(int edge, int terminatingCluster) { return false; } + Point2D pt_1 = new Point2D(); + Point2D pt_2 = new Point2D(); int isEdgeOnSweepLine_(int edge) { int cluster_1 = getEdgeCluster(edge, 0); int cluster2 = getEdgeCluster(edge, 1); - Point2D pt_1 = getClusterXY(cluster_1); - Point2D pt_2 = getClusterXY(cluster2); + getClusterXY(cluster_1, pt_1); + getClusterXY(cluster2, pt_2); if (Point2D.sqrDistance(pt_1, pt_2) <= m_tolerance_sqr) {// avoid // degenerate // segments @@ -1095,18 +1009,14 @@ void fillEventQueue() { // clusters EditShape.VertexIterator iter = m_shape.queryVertexIterator(); for (int vert = iter.next(); vert != -1; vert = iter.next()) { - event_q.add(vert); + if (m_shape.getUserIndex(vert, m_vertex_cluster_index) != -1) + event_q.add(vert); } - assert (m_shape.getTotalPointCount() == event_q.size()); - // Now we can merge coincident clusters and form the envent structure. // sort vertices lexicographically. m_shape.sortVerticesSimpleByY_(event_q, 0, event_q.size()); - // int perPoint = m_shape->estimate_memory_size() / - // m_shape->get_total_point_count(); - // perPoint = 0; // The m_event_q is the event structure for the planesweep algorithm. // We could use any data structure that allows log(n) insertion and @@ -1127,9 +1037,10 @@ void fillEventQueue() { Point2D cluster_pt = new Point2D(); cluster_pt.setNaN(); int cluster = -1; + Point2D pt = new Point2D(); for (int index = 0, nvertex = event_q.size(); index < nvertex; index++) { int vertex = event_q.get(index); - Point2D pt = m_shape.getXY(vertex); + m_shape.getXY(vertex, pt); if (pt.isEqual(cluster_pt)) { int vertexCluster = m_shape.getUserIndex(vertex, m_vertex_cluster_index); @@ -1139,7 +1050,7 @@ void fillEventQueue() { cluster = getClusterFromVertex(vertex); // add a vertex to the event queue - cluster_pt = m_shape.getXY(vertex); + m_shape.getXY(vertex, cluster_pt); int eventQnode = m_event_q.addBiggestElement(vertex, -1); // this // method // does @@ -1222,18 +1133,6 @@ void updateClusterXY(int cluster, Point2D pt) { // m_edges_to_insert_in_sweep_structure. void splitEdge_(int edge1, int edge2, int intersectionCluster, SegmentIntersector intersector) { - // #ifdef _DEBUG_CRACKING_REPORT - // { - // if (edge2 != -1) - // DEBUGPRINTF(L"Splitting edge1 (%d) and edge2 (%d)\n", edge1, edge2); - // else - // DEBUGPRINTF(L"Splitting edge (%d)\n", edge1); - // } - // #endif - // dbg_check_edge_(edge1); - // - // if (edge2 != -1) - // dbg_check_edge_(edge2); disconnectEdge_(edge1);// disconnects the edge from the clusters. The // edge still remembers the clusters. @@ -1250,9 +1149,9 @@ void splitEdge_(int edge1, int edge2, int intersectionCluster, processSplitHelper1_(1, edge2, intersector); if (intersectionCluster != -1) { - Point2D pt = intersector.getResultPoint().getXY(); - Point2D ptCluster = getClusterXY(intersectionCluster); - if (!ptCluster.isEqual(pt)) + intersector.getResultPoint().getXY(pt_1); + getClusterXY(intersectionCluster, pt_2); + if (!pt_2.isEqual(pt_1)) m_modified_clusters.add(intersectionCluster); } @@ -1302,17 +1201,6 @@ void splitEdge_(int edge1, int edge2, int intersectionCluster, int vertex = getClusterFirstVertex(cluster); assert (getClusterFromVertex(vertex) == cluster); - // #ifdef _DEBUG_CRACKING_REPORT - // { - // Point_2D pt; - // m_shape->get_xy(vertex, pt); - // DEBUGPRINTF(L"Inserting vertex %d, cluster %d (%0.3f, %0.3f)\n", - // vertex, cluster, pt.x, pt.y); - // } - // #endif - // st_counter_insertions++; - // st_counter_insertions_unique++; - eventQnode = m_event_q.addUniqueElement(vertex, -1);// O(logN) // operation if (eventQnode == -1) {// the cluster is coinciding with another @@ -1321,17 +1209,10 @@ void splitEdge_(int edge1, int edge2, int intersectionCluster, int v = m_event_q.getElement(existingNode); assert (m_shape.isEqualXY(vertex, v)); int existingCluster = getClusterFromVertex(v); - // #ifdef _DEBUG_CRACKING_REPORT - // { - // Point_2D pt; - // m_shape->get_xy(v, pt); - // DEBUGPRINTF(L"Already in the queue %d, cluster %d (%0.3f, %0.3f)\n", - // v, existingCluster, pt.x, pt.y); - // } - // #endif mergeClusters_(existingCluster, cluster); - } else + } else { setClusterEventQNode_(cluster, eventQnode); + } } else { // if already inserted (probably impossible) case } @@ -1341,11 +1222,9 @@ void splitEdge_(int edge1, int edge2, int intersectionCluster, } // Returns a cluster's xy. - Point2D getClusterXY(int cluster) { - Point2D p = new Point2D(); + void getClusterXY(int cluster, Point2D ptOut) { int vindex = getClusterVertexIndex(cluster); - m_shape.getXYWithIndex(vindex, p); - return p; + m_shape.getXYWithIndex(vindex, ptOut); } int getClusterFirstVertex(int cluster) { @@ -1383,12 +1262,6 @@ boolean sweepImpl_() { int vertex = m_event_q.getElement(eventQnode); m_sweep_point_cluster = getClusterFromVertex(vertex); m_shape.getXY(vertex, m_sweep_point); - // #ifdef _DEBUG_CRACKING_REPORT - // { - // DEBUGPRINTF(L"next event node. Cluster %d, Vertex %d, (%0.3f, %0.3f)\n", - // m_sweep_point_cluster, vertex, m_sweep_point.x, m_sweep_point.y); - // } - // #endif m_sweep_comparator.setSweepY(m_sweep_point.y, m_sweep_point.x);// move // the @@ -1408,7 +1281,7 @@ boolean sweepImpl_() { setEdgeSweepNode_(edge, c_3);// mark that its in // m_edges_to_insert_in_sweep_structure } else if (sweepNode != c_3) { - // assert(StridedIndexTypeCollection.isValidElement(sweepNode)); + assert(StridedIndexTypeCollection.isValidElement(sweepNode)); edgesToDelete.add(sweepNode); } edge = getNextEdge(edge, m_sweep_point_cluster); @@ -1427,8 +1300,6 @@ boolean sweepImpl_() { m_b_continuing_segment_chain_optimization = (edgesToDelete .size() == 1 && m_edges_to_insert_in_sweep_structure .size() == 1); - // st_counter_insertions_all_potential += - // m_edges_to_insert_in_sweep_structure.size() > 0; // Mark nodes that need to be deleted by setting c_2 to the // edge's sweep node member. @@ -1486,11 +1357,9 @@ boolean sweepImpl_() { // Now check if the left and right we found intersect or not. if (left != -1 && right != -1) { - boolean bIntersected = checkAndFixIntersection_(left, right); - if (bIntersected) { - // We'll insert the results of intersection below in - // insert_new_edges_ - m_b_continuing_segment_chain_optimization = false; + if (!m_b_continuing_segment_chain_optimization) { + boolean bIntersected = checkAndFixIntersection_(left, + right); } } else { if ((left == -1) && (right == -1)) @@ -1572,12 +1441,6 @@ void setEditShape_(EditShape shape) { int path_size = m_shape.getPathSize(path); assert (path_size > 1); int first_vertex = m_shape.getFirstVertex(path); - // #ifdef _DEBUG_TOPO - // LOCALREFCLASS(Line, line); - // m_shape.query_line_connector(first_vertex, line); - // assert(line.calculate_length_2D() > m_tolerance);//no - // degenerate lines at the input - // #endif // first------------------ int firstCluster = newCluster_(first_vertex); @@ -1586,24 +1449,31 @@ void setEditShape_(EditShape shape) { int prevEdge = first_edge; int vertex = m_shape.getNextVertex(first_vertex); for (int index = 0, n = path_size - 2; index < n; index++) { + int nextvertex = m_shape.getNextVertex(vertex); // ------------x------------ int cluster = newCluster_(vertex); addEdgeToCluster(prevEdge, cluster); int newEdge = newEdge_(vertex); addEdgeToCluster(newEdge, cluster); prevEdge = newEdge; - vertex = m_shape.getNextVertex(vertex); + vertex = nextvertex; } // ------------------lastx - int cluster = newCluster_(vertex); - addEdgeToCluster(prevEdge, cluster); - if (m_shape.isClosedPath(path)) {// close the path - // lastx------------------firstx + if (m_shape.isClosedPath(path)) { + int cluster = newCluster_(vertex); + addEdgeToCluster(prevEdge, cluster); + // close the path + // lastx------------------firstx int newEdge = newEdge_(vertex); addEdgeToCluster(newEdge, cluster); addEdgeToCluster(newEdge, firstCluster); + } else { + // ------------------lastx + int cluster = newCluster_(vertex); + addEdgeToCluster(prevEdge, cluster); } + } } diff --git a/src/main/java/com/esri/core/geometry/Point.java b/src/main/java/com/esri/core/geometry/Point.java index 30db22e7..11459be5 100644 --- a/src/main/java/com/esri/core/geometry/Point.java +++ b/src/main/java/com/esri/core/geometry/Point.java @@ -25,6 +25,7 @@ package com.esri.core.geometry; import com.esri.core.geometry.VertexDescription.Semantics; + import java.io.Serializable; /** @@ -66,6 +67,10 @@ public Point(double x, double y) { m_description = VertexDescriptionDesignerImpl.getDefaultDescriptor2D(); setXY(x, y); } + public Point(Point2D pt) { + m_description = VertexDescriptionDesignerImpl.getDefaultDescriptor2D(); + setXY(pt); + } /** * Creates a 3D point with specified X, Y and Z coordinates. In case of @@ -91,7 +96,7 @@ public Point(double x, double y, double z) { /** * Returns XY coordinates of this point. */ - Point2D getXY() { + public final Point2D getXY() { if (isEmptyImpl()) throw new GeometryException( "This operation should not be performed on an empty geometry."); @@ -101,11 +106,22 @@ Point2D getXY() { return pt; } + /** + * Returns XY coordinates of this point. + */ + public final void getXY(Point2D pt) { + if (isEmptyImpl()) + throw new GeometryException( + "This operation should not be performed on an empty geometry."); + + pt.setCoords(m_attributes[0], m_attributes[1]); + } + /** * Sets the XY coordinates of this point. param pt The point to create the X * and Y coordinate from. */ - void setXY(Point2D pt) { + public final void setXY(Point2D pt) { _touch(); setXY(pt.x, pt.y); } @@ -602,4 +618,9 @@ public int hashCode() { } return hashCode; } + + @Override + public Geometry getBoundary() { + return null; + } } diff --git a/src/main/java/com/esri/core/geometry/Point2D.java b/src/main/java/com/esri/core/geometry/Point2D.java index 319cd5dd..cb673ff5 100644 --- a/src/main/java/com/esri/core/geometry/Point2D.java +++ b/src/main/java/com/esri/core/geometry/Point2D.java @@ -63,11 +63,32 @@ public void setCoords(Point2D other) { public boolean isEqual(Point2D other) { return x == other.x && y == other.y; } + + public boolean isEqual(double x_, double y_) { + return x == x_ && y == y_; + } public boolean isEqual(Point2D other, double tol) { return (Math.abs(x - other.x) <= tol) && (Math.abs(y - other.y) <= tol); } + public boolean equals(Point2D other) { + return x == other.x && y == other.y; + } + + @Override + public boolean equals(Object other) { + if (other == this) + return true; + + if (!(other instanceof Point2D)) + return false; + + Point2D v = (Point2D)other; + + return x == v.x && y == v.y; + } + public void sub(Point2D other) { x -= other.x; y -= other.y; @@ -451,5 +472,10 @@ public static int orientationRobust(Point2D p, Point2D q, Point2D r) { return det_mp.signum(); } + + @Override + public int hashCode() { + return NumberUtils.hash(NumberUtils.hash(x), y); + } } diff --git a/src/main/java/com/esri/core/geometry/Point3D.java b/src/main/java/com/esri/core/geometry/Point3D.java index 34a901fc..f5d343ff 100644 --- a/src/main/java/com/esri/core/geometry/Point3D.java +++ b/src/main/java/com/esri/core/geometry/Point3D.java @@ -30,7 +30,7 @@ * Basic 3D point class. * */ -final class Point3D { +public final class Point3D { public double x; public double y; public double z; @@ -68,7 +68,7 @@ public void normalize() { z /= len; } - double length() { + public double length() { return Math.sqrt(x * x + y * y + z * z); } @@ -78,11 +78,11 @@ public Point3D(double x, double y, double z) { this.z = z; } - Point3D sub(Point3D other) { + public Point3D sub(Point3D other) { return new Point3D(x - other.x, y - other.y, z - other.z); } - Point3D mul(double factor) { + public Point3D mul(double factor) { return new Point3D(x * factor, y * factor, z * factor); } diff --git a/src/main/java/com/esri/core/geometry/Polygon.java b/src/main/java/com/esri/core/geometry/Polygon.java index 2e173acd..8faea700 100644 --- a/src/main/java/com/esri/core/geometry/Polygon.java +++ b/src/main/java/com/esri/core/geometry/Polygon.java @@ -123,13 +123,13 @@ public void setXY(int i, double x, double y) { } - void interpolateAttributes(int path_index, int from_point_index, + public void interpolateAttributes(int path_index, int from_point_index, int to_point_index) { m_impl.interpolateAttributes(path_index, from_point_index, to_point_index); } - void interpolateAttributes(int semantics, int path_index, + public void interpolateAttributes(int semantics, int path_index, int from_point_index, int to_point_index) { m_impl.interpolateAttributesForSemantics(semantics, path_index, from_point_index, to_point_index); diff --git a/src/main/java/com/esri/core/geometry/PolygonUtils.java b/src/main/java/com/esri/core/geometry/PolygonUtils.java index e8f700ab..005446d0 100644 --- a/src/main/java/com/esri/core/geometry/PolygonUtils.java +++ b/src/main/java/com/esri/core/geometry/PolygonUtils.java @@ -246,8 +246,9 @@ else if (polygon.getType() == Geometry.Type.Envelope) { // We assume here that the Polygon is Weak Simple. That is if one point of // Ring1 is found to be inside of Ring2, then // we assume that all of Ring1 is inside Ring2. - static boolean _isRingInRing2D(MultiPathImpl polygonImpl, int iRing1, + static boolean _isRingInRing2D(MultiPath polygon, int iRing1, int iRing2, double tolerance) { + MultiPathImpl polygonImpl = (MultiPathImpl)polygon._getImpl(); SegmentIteratorImpl segIter = polygonImpl.querySegmentIterator(); segIter.resetToPath(iRing1); if (!segIter.nextPath() || !segIter.hasNextSegment()) @@ -264,7 +265,7 @@ static boolean _isRingInRing2D(MultiPathImpl polygonImpl, int iRing1, } if (res == 2 /* (int)PiPResult.PiPBoundary */) - throw new GeometryException("internal error"); + throw GeometryException.GeometryInternalError(); if (res == 1 /* (int)PiPResult.PiPInside */) return true; @@ -397,28 +398,4 @@ else if (Geometry.isSegment(gt.value())) { throw new GeometryException("Invalid call."); } - /* - * // Tests if Ring1 is inside Ring2. // We assume here that the Polygon is - * Weak Simple. That is if one point of Ring1 is found to be inside of - * Ring2, then // we assume that all of Ring1 is inside Ring2. public static - * booleanean _isRingInRing2D(MultiPathImpl polygonImpl, int iRing1, int - * iRing2, double tolerance) { SegmentIteratorImpl segIter = - * polygonImpl.querySegmentIterator(); segIter.resetToPath(iRing1); if - * (!segIter.nextPath() || !segIter.hasNextSegment()) throw new - * GeometryException("corrupted geometry"); - * - * PiPResult res = PiPResult.PiPBoundary; - * - * while ((res == PiPResult.PiPBoundary) && segIter.hasNextSegment()) { - * Segment segment = segIter.nextSegment(); Point2D point = - * segment.getCoord2D(0.5); res = - * PointInPolygonHelper.isPointInRing(polygonImpl, iRing2, point, - * tolerance); } - * - * if (res == PiPResult. PiPBoundary) throw new - * GeometryException("internal error"); if (res == PiPResult.PiPInside) - * return true; - * - * return false; } - */ } diff --git a/src/main/java/com/esri/core/geometry/Polyline.java b/src/main/java/com/esri/core/geometry/Polyline.java index 194bbfb6..2124a0f4 100644 --- a/src/main/java/com/esri/core/geometry/Polyline.java +++ b/src/main/java/com/esri/core/geometry/Polyline.java @@ -95,13 +95,13 @@ public void addSegment(Segment segment, boolean bStartNewPath) { m_impl.addSegment(segment, bStartNewPath); } - void interpolateAttributes(int from_path_index, int from_point_index, - int to_path_index, int to_point_index) { + public void interpolateAttributes(int from_path_index, + int from_point_index, int to_path_index, int to_point_index) { m_impl.interpolateAttributes(from_path_index, from_point_index, to_path_index, to_point_index); } - void interpolateAttributes(int semantics, int from_path_index, + public void interpolateAttributes(int semantics, int from_path_index, int from_point_index, int to_path_index, int to_point_index) { m_impl.interpolateAttributesForSemantics(semantics, from_path_index, from_point_index, to_path_index, to_point_index); diff --git a/src/main/java/com/esri/core/geometry/ProgressTracker.java b/src/main/java/com/esri/core/geometry/ProgressTracker.java index d185b991..d4153375 100644 --- a/src/main/java/com/esri/core/geometry/ProgressTracker.java +++ b/src/main/java/com/esri/core/geometry/ProgressTracker.java @@ -34,4 +34,13 @@ public abstract class ProgressTracker { *@return true, if the operation can continue. Returns False, when the operation has to terminate due to a user cancelation. */ public abstract boolean progress(int step, int totalExpectedSteps); + + /** + * Checks the tracker and throws UserCancelException if tracker is not null and progress returns false + * @param tracker can be null, then the method does nothing. + */ + public static void checkAndThrow(ProgressTracker tracker) { + if (tracker != null && !tracker.progress(-1, -1)) + throw new UserCancelException(); + } } diff --git a/src/main/java/com/esri/core/geometry/Proximity2DResult.java b/src/main/java/com/esri/core/geometry/Proximity2DResult.java index 01a1a2ae..04ac528d 100644 --- a/src/main/java/com/esri/core/geometry/Proximity2DResult.java +++ b/src/main/java/com/esri/core/geometry/Proximity2DResult.java @@ -32,6 +32,21 @@ public class Proximity2DResult { Point2D m_coordinate = new Point2D(); int m_vertexIndex; double m_distance; + int m_info; + + /** + * Sets the right_side info to true or false. + * + * @param bRight + * Whether the nearest coordinate is to the right or left of the + * geometry. + */ + public void setRightSide(boolean bRight) { + if (bRight) + m_info |= (int) OperatorProximity2D.ProxResultInfo.rightSide; + else + m_info &= ~(int) OperatorProximity2D.ProxResultInfo.rightSide; + } /** * Returns TRUE if the Proximity2DResult is empty. This only happens if the @@ -81,6 +96,13 @@ public double getDistance() { return m_distance; } + /** + *Returns true if the closest coordinate is to the right of the MultiPath. + */ + public boolean isRightSide() { + return (m_info & (int) OperatorProximity2D.ProxResultInfo.rightSide) != 0; + } + void _setParams(double x, double y, int vertexIndex, double distance) { m_coordinate.x = x; m_coordinate.y = y; @@ -101,4 +123,11 @@ void _setParams(double x, double y, int vertexIndex, double distance) { Proximity2DResult() { m_vertexIndex = -1; } + + Proximity2DResult(Point2D coordinate, int vertexIndex, double distance) { + m_coordinate.setCoords(coordinate); + m_vertexIndex = vertexIndex; + m_distance = distance; + m_info = 0; + } } diff --git a/src/main/java/com/esri/core/geometry/QuadTreeImpl.java b/src/main/java/com/esri/core/geometry/QuadTreeImpl.java index 8a927447..84c16f67 100644 --- a/src/main/java/com/esri/core/geometry/QuadTreeImpl.java +++ b/src/main/java/com/esri/core/geometry/QuadTreeImpl.java @@ -159,15 +159,8 @@ int next() { child_extents[2] = new Envelope2D(); child_extents[3] = new Envelope2D(); } - child_extents[0].setCoords(x_mid, y_mid, - current_extent.xmax, current_extent.ymax); // northeast - child_extents[1].setCoords(current_extent.xmin, y_mid, - x_mid, current_extent.ymax); // northwest - child_extents[2].setCoords(current_extent.xmin, - current_extent.ymin, x_mid, y_mid); // southwest - child_extents[3].setCoords(x_mid, current_extent.ymin, - current_extent.xmax, y_mid); // southeast + setChildExtents_(current_extent, child_extents); m_quads_stack.removeLast(); m_extents_stack.remove(m_extents_stack.size() - 1); @@ -351,15 +344,6 @@ void removeElement(int element_handle) { } } - /** - * Removes the quad and all its children corresponding to the input - * quad_handle. \param quad_handle The handle corresponding to the quad to - * be removed. - */ - void removeQuad(int quad_handle) { - removeQuad_(quad_handle); - } - /** * Returns the element at the given element_handle. \param element_handle * The handle corresponding to the element to be retrieved. @@ -481,14 +465,9 @@ private void reset_(Envelope2D extent, int height) { private int insert_(int element, Envelope2D bounding_box, int height, Envelope2D quad_extent, int quad_handle, boolean b_flushing, int flushed_element_handle) { - Point2D bb_center = new Point2D(); - bounding_box.queryCenter(bb_center); - - Envelope2D current_extent = new Envelope2D(); - current_extent.setCoords(quad_extent); + if (!quad_extent.contains(bounding_box)) { + assert (!b_flushing); - int current_quad_handle = quad_handle; - if (!current_extent.contains(bounding_box)) { if (height == 0) return -1; @@ -496,8 +475,16 @@ private int insert_(int element, Envelope2D bounding_box, int height, b_flushing, flushed_element_handle); } - // Should this memory be cached for reuse? - Point2D quad_center = new Point2D(); + if (!b_flushing) { + for (int q = quad_handle; q != -1; q = getParent_(q)) + setSubTreeElementCount_(q, getSubTreeElementCount_(q) + 1); + } + + Envelope2D current_extent = new Envelope2D(); + current_extent.setCoords(quad_extent); + + int current_quad_handle = quad_handle; + Envelope2D[] child_extents = new Envelope2D[4]; child_extents[0] = new Envelope2D(); child_extents[1] = new Envelope2D(); @@ -507,46 +494,40 @@ private int insert_(int element, Envelope2D bounding_box, int height, int current_height; for (current_height = height; current_height < m_height && canPushDown_(current_quad_handle); current_height++) { - double x_mid = 0.5 * (current_extent.xmin + current_extent.xmax); - double y_mid = 0.5 * (current_extent.ymin + current_extent.ymax); - - child_extents[0].setCoords(x_mid, y_mid, current_extent.xmax, - current_extent.ymax); // northeast - child_extents[1].setCoords(current_extent.xmin, y_mid, x_mid, - current_extent.ymax); // northwest - child_extents[2].setCoords(current_extent.xmin, - current_extent.ymin, x_mid, y_mid); // southwest - child_extents[3].setCoords(x_mid, current_extent.ymin, - current_extent.xmax, y_mid); // southeast - - // Find the first child quadrant that contains the bounding box, and - // recursively insert into that child (greedy algorithm) - double mind = NumberUtils.doubleMax(); - int quadrant = -1; + setChildExtents_(current_extent, child_extents); + + boolean b_contains = false; + for (int i = 0; i < 4; i++) { - child_extents[i].queryCenter(quad_center); - double d = Point2D.sqrDistance(quad_center, bb_center); - if (d < mind) { - mind = d; - quadrant = i; - } - } + if (child_extents[i].contains(bounding_box)) { + b_contains = true; + + int child_handle = getChild_(current_quad_handle, i); + if (child_handle == -1) + child_handle = createChild_(current_quad_handle, i); - if (child_extents[quadrant].contains(bounding_box)) { - int child_handle = getChild_(current_quad_handle, quadrant); - if (child_handle == -1) - child_handle = createChild_(current_quad_handle, quadrant); + setSubTreeElementCount_(child_handle, + getSubTreeElementCount_(child_handle) + 1); - current_quad_handle = child_handle; - current_extent.setCoords(child_extents[quadrant]); - setSubTreeElementCount_(current_quad_handle, - getSubTreeElementCount_(current_quad_handle) + 1); - continue; + current_quad_handle = child_handle; + current_extent.setCoords(child_extents[i]); + break; + } } - break; + if (!b_contains) + break; } + return insertAtQuad_(element, bounding_box, current_height, + current_extent, current_quad_handle, b_flushing, quad_handle, + flushed_element_handle); + } + + private int insertAtQuad_(int element, Envelope2D bounding_box, + int current_height, Envelope2D current_extent, + int current_quad_handle, boolean b_flushing, int quad_handle, + int flushed_element_handle) { // If the bounding box is not contained in any of the current_node's // children, or if the current_height is m_height, then insert the // element and @@ -554,7 +535,7 @@ private int insert_(int element, Envelope2D bounding_box, int height, int head_element_handle = getFirstElement_(current_quad_handle); int tail_element_handle = getLastElement_(current_quad_handle); - int element_handle; + int element_handle = -1; if (b_flushing) { assert (flushed_element_handle != -1); @@ -575,8 +556,6 @@ private int insert_(int element, Envelope2D bounding_box, int height, // (next_element_handle). setBoundingBox_(getBoxHandle_(element_handle), bounding_box); // insert // bounding_box - setSubTreeElementCount_(quad_handle, - getSubTreeElementCount_(quad_handle) + 1); } assert (!b_flushing || element_handle == flushed_element_handle); @@ -650,49 +629,19 @@ private int disconnectElementHandle_(int element_handle) { return next_element_handle; } - private void removeQuad_(int quad_handle) { - int subTreeElementCount = getSubTreeElementCount_(quad_handle); - if (subTreeElementCount > 0) - for (int q = getParent_(quad_handle); q != -1; q = getParent_(q)) - setSubTreeElementCount_(q, getSubTreeElementCount_(q) - - subTreeElementCount); - - removeQuadHelper_(quad_handle); - - int parent = getParent_(quad_handle); - - if (parent != -1) { - for (int quadrant = 0; quadrant < 4; quadrant++) { - if (getChild_(parent, quadrant) == quad_handle) { - setChild_(parent, quadrant, -1); - break; - } - } - } - } - - private void removeQuadHelper_(int quad_handle) { - for (int element_handle = getFirstElement_(quad_handle); element_handle != -1; element_handle = getNextElement_(element_handle)) { - m_free_boxes.add(getBoxHandle_(element_handle)); - m_element_nodes.deleteElement(element_handle); - } - - for (int quadrant = 0; quadrant < 4; quadrant++) { - int child_handle = getChild_(quad_handle, quadrant); - if (child_handle != -1) { - removeQuadHelper_(child_handle); - setChild_(quad_handle, quadrant, -1); - } - } + private static void setChildExtents_(Envelope2D current_extent, + Envelope2D[] child_extents) { + double x_mid = 0.5 * (current_extent.xmin + current_extent.xmax); + double y_mid = 0.5 * (current_extent.ymin + current_extent.ymax); - if (quad_handle != m_root) { - m_quad_tree_nodes.deleteElement(quad_handle); - } else { - setSubTreeElementCount_(m_root, 0); - setLocalElementCount_(m_root, 0); - setFirstElement_(m_root, -1); - setLastElement_(m_root, -1); - } + child_extents[0].setCoords(x_mid, y_mid, current_extent.xmax, + current_extent.ymax); // northeast + child_extents[1].setCoords(current_extent.xmin, y_mid, x_mid, + current_extent.ymax); // northwest + child_extents[2].setCoords(current_extent.xmin, current_extent.ymin, + x_mid, y_mid); // southwest + child_extents[3].setCoords(x_mid, current_extent.ymin, + current_extent.xmax, y_mid); // southeast } private boolean canFlush_(int quad_handle) { diff --git a/src/main/java/com/esri/core/geometry/RasterizedGeometry2D.java b/src/main/java/com/esri/core/geometry/RasterizedGeometry2D.java index 558c3b16..94f187d9 100644 --- a/src/main/java/com/esri/core/geometry/RasterizedGeometry2D.java +++ b/src/main/java/com/esri/core/geometry/RasterizedGeometry2D.java @@ -100,7 +100,7 @@ public static int rasterSizeFromAccelerationDegree( value = 1024 * 1024 * 2 / 8;// 256k break; default: - throw new GeometryException("internal error"); + throw GeometryException.GeometryInternalError(); } return value; diff --git a/src/main/java/com/esri/core/geometry/RasterizedGeometry2DImpl.java b/src/main/java/com/esri/core/geometry/RasterizedGeometry2DImpl.java index b2ebf125..16e3dd1c 100644 --- a/src/main/java/com/esri/core/geometry/RasterizedGeometry2DImpl.java +++ b/src/main/java/com/esri/core/geometry/RasterizedGeometry2DImpl.java @@ -95,7 +95,7 @@ void fillMultiPath(SimpleRasterizer rasterizer, Transformation2D trans, MultiPat while (segIter.hasNextSegment()) { Segment seg = segIter.nextSegment(); if (seg.getType() != Geometry.Type.Line) - throw new GeometryException("internal error"); // TODO: + throw GeometryException.GeometryInternalError(); // TODO: // densify // the // segment @@ -110,7 +110,7 @@ void fillMultiPath(SimpleRasterizer rasterizer, Transformation2D trans, MultiPat } void fillPoints(SimpleRasterizer rasterizer, MultiPointImpl geom, double stroke_half_width) { - throw new GeometryException("internal error"); + throw GeometryException.GeometryInternalError(); } void fillConvexPolygon(SimpleRasterizer rasterizer, Point2D[] fan, int len) { diff --git a/src/main/java/com/esri/core/geometry/RelationalOperations.java b/src/main/java/com/esri/core/geometry/RelationalOperations.java index 9626b4ca..e88409ff 100644 --- a/src/main/java/com/esri/core/geometry/RelationalOperations.java +++ b/src/main/java/com/esri/core/geometry/RelationalOperations.java @@ -1691,8 +1691,9 @@ private static boolean polylineTouchesMultiPoint_(Polyline polyline_a, ProgressTracker progress_tracker) { // Quick rasterize test to see whether the the geometries are disjoint. if (tryRasterizedContainsOrDisjoint_(polyline_a, multipoint_b, - tolerance, false) == Relation.disjoint) + tolerance, false) == Relation.disjoint) { return false; + } SegmentIteratorImpl segIterA = ((MultiPathImpl) polyline_a._getImpl()) .querySegmentIterator(); @@ -1707,8 +1708,25 @@ private static boolean polylineTouchesMultiPoint_(Polyline polyline_a, envInter.setCoords(env_a); envInter.intersect(env_b); - QuadTreeImpl quadTreeA = InternalUtils.buildQuadTree( - (MultiPathImpl) (polyline_a._getImpl()), envInter); + QuadTreeImpl qtA; + QuadTreeImpl quadTreeA; + + GeometryAccelerators accel = ((MultiPathImpl) (polyline_a._getImpl())) + ._getAccelerators(); + + if (accel != null) { + quadTreeA = accel.getQuadTree(); + if (quadTreeA == null) { + qtA = InternalUtils.buildQuadTree( + (MultiPathImpl) polyline_a._getImpl(), envInter); + quadTreeA = qtA; + } + } else { + qtA = InternalUtils.buildQuadTree( + (MultiPathImpl) polyline_a._getImpl(), envInter); + quadTreeA = qtA; + } + QuadTreeImpl.QuadTreeIteratorImpl qtIterA = quadTreeA.getIterator(); Point2D ptB = new Point2D(), closest = new Point2D(); @@ -1717,14 +1735,16 @@ private static boolean polylineTouchesMultiPoint_(Polyline polyline_a, AttributeStreamOfInt8 intersects = new AttributeStreamOfInt8( multipoint_b.getPointCount()); - for (int i = 0; i < multipoint_b.getPointCount(); i++) + for (int i = 0; i < multipoint_b.getPointCount(); i++) { intersects.write(i, (byte) 0); + } for (int i = 0; i < multipoint_b.getPointCount(); i++) { multipoint_b.getXY(i, ptB); - if (!envInter.contains(ptB)) + if (!envInter.contains(ptB)) { continue; + } env_b.setCoords(ptB.x, ptB.y, ptB.x, ptB.y); qtIterA.resetIterator(env_b, tolerance); @@ -1746,16 +1766,18 @@ private static boolean polylineTouchesMultiPoint_(Polyline polyline_a, } } - if (!b_intersects) + if (!b_intersects) { return false; + } MultiPoint boundary_a = (MultiPoint) (polyline_a.getBoundary()); MultiPoint multipoint_b_inter = new MultiPoint(); Point2D pt = new Point2D(); for (int i = 0; i < multipoint_b.getPointCount(); i++) { - if (intersects.read(i) == 0) + if (intersects.read(i) == 0) { continue; + } multipoint_b.getXY(i, pt); multipoint_b_inter.add(pt.x, pt.y); @@ -1771,8 +1793,9 @@ private static boolean polylineCrossesMultiPoint_(Polyline polyline_a, ProgressTracker progress_tracker) { // Quick rasterize test to see whether the the geometries are disjoint. if (tryRasterizedContainsOrDisjoint_(polyline_a, multipoint_b, - tolerance, false) == Relation.disjoint) + tolerance, false) == Relation.disjoint) { return false; + } SegmentIteratorImpl segIterA = ((MultiPathImpl) polyline_a._getImpl()) .querySegmentIterator(); @@ -1787,8 +1810,25 @@ private static boolean polylineCrossesMultiPoint_(Polyline polyline_a, envInter.setCoords(env_a); envInter.intersect(env_b); - QuadTreeImpl quadTreeA = InternalUtils.buildQuadTree( - (MultiPathImpl) polyline_a._getImpl(), envInter); + QuadTreeImpl qtA; + QuadTreeImpl quadTreeA; + + GeometryAccelerators accel = ((MultiPathImpl) (polyline_a._getImpl())) + ._getAccelerators(); + + if (accel != null) { + quadTreeA = accel.getQuadTree(); + if (quadTreeA == null) { + qtA = InternalUtils.buildQuadTree( + (MultiPathImpl) polyline_a._getImpl(), envInter); + quadTreeA = qtA; + } + } else { + qtA = InternalUtils.buildQuadTree( + (MultiPathImpl) polyline_a._getImpl(), envInter); + quadTreeA = qtA; + } + QuadTreeImpl.QuadTreeIteratorImpl qtIterA = quadTreeA.getIterator(); Point2D ptB = new Point2D(), closest = new Point2D(); @@ -1798,8 +1838,9 @@ private static boolean polylineCrossesMultiPoint_(Polyline polyline_a, AttributeStreamOfInt8 intersects = new AttributeStreamOfInt8( multipoint_b.getPointCount()); - for (int i = 0; i < multipoint_b.getPointCount(); i++) + for (int i = 0; i < multipoint_b.getPointCount(); i++) { intersects.write(i, (byte) 0); + } for (int i = 0; i < multipoint_b.getPointCount(); i++) { multipoint_b.getXY(i, ptB); @@ -1831,20 +1872,23 @@ private static boolean polylineCrossesMultiPoint_(Polyline polyline_a, } } - if (!b_covered) + if (!b_covered) { b_exterior_found = true; + } } - if (!b_intersects || !b_exterior_found) + if (!b_intersects || !b_exterior_found) { return false; + } MultiPoint boundary_a = (MultiPoint) (polyline_a.getBoundary()); MultiPoint multipoint_b_inter = new MultiPoint(); Point2D pt = new Point2D(); for (int i = 0; i < multipoint_b.getPointCount(); i++) { - if (intersects.read(i) == 0) + if (intersects.read(i) == 0) { continue; + } multipoint_b.getXY(i, pt); multipoint_b_inter.add(pt.x, pt.y); @@ -2231,7 +2275,7 @@ private static boolean multiPointContainsMultiPointBrute_( } // Returns true if multipoint_a equals point_b. - private static boolean multiPointEqualsPoint_(MultiPoint multipoint_a, + static boolean multiPointEqualsPoint_(MultiPoint multipoint_a, Point point_b, double tolerance, ProgressTracker progress_tracker) { Envelope2D env_a = new Envelope2D(), env_b = new Envelope2D(); multipoint_a.queryEnvelope2D(env_a); @@ -2598,8 +2642,8 @@ private static boolean pointEqualsEnvelope_(Point2D pt_a, Envelope2D env_b, } // Returns true if pt_a is disjoint from env_b. - private static boolean pointDisjointEnvelope_(Point2D pt_a, - Envelope2D env_b, double tolerance, ProgressTracker progress_tracker) { + static boolean pointDisjointEnvelope_(Point2D pt_a, Envelope2D env_b, + double tolerance, ProgressTracker progress_tracker) { Envelope2D env_b_inflated = new Envelope2D(); env_b_inflated.setCoords(env_b); env_b_inflated.inflate(tolerance, tolerance); @@ -2707,7 +2751,7 @@ private static boolean envelopeEqualsEnvelope_(Envelope2D env_a, } // Returns true if env_a is disjoint from env_b. - private static boolean envelopeDisjointEnvelope_(Envelope2D env_a, + static boolean envelopeDisjointEnvelope_(Envelope2D env_a, Envelope2D env_b, double tolerance, ProgressTracker progress_tracker) { Envelope2D env_b_inflated = new Envelope2D(); env_b_inflated.setCoords(env_b); @@ -3019,59 +3063,69 @@ private static boolean envelopeCrossesEnvelope_(Envelope2D env_a, private static boolean polygonDisjointMultiPath_(Polygon polygon_a, MultiPath multipath_b, double tolerance, ProgressTracker progress_tracker) { - Point2D pt_a = new Point2D(), pt_b = new Point2D(); + Point2D pt_a, pt_b; Envelope2D env_a_inf = new Envelope2D(), env_b_inf = new Envelope2D(); - AttributeStreamOfInt32 parts_a = new AttributeStreamOfInt32(0); - AttributeStreamOfInt32 parts_b = new AttributeStreamOfInt32(0); + MultiPathImpl multi_path_impl_a = (MultiPathImpl) polygon_a._getImpl(); + MultiPathImpl multi_path_impl_b = (MultiPathImpl) multipath_b + ._getImpl(); + boolean b_simple_a = multi_path_impl_a.getIsSimple(0.0) >= 1; + boolean b_simple_b = multi_path_impl_b.getIsSimple(0.0) >= 1; Envelope2DIntersectorImpl intersector = InternalUtils - .getEnvelope2DIntersectorForOGCParts( - (MultiPathImpl) polygon_a._getImpl(), - (MultiPathImpl) multipath_b._getImpl(), tolerance, - parts_a, parts_b); + .getEnvelope2DIntersectorForParts(multi_path_impl_a, + multi_path_impl_b, tolerance, b_simple_a, b_simple_b); if (intersector != null) { - while (intersector.next()) { - int index_a = intersector.getHandleA(); - int index_b = intersector.getHandleB(); - int path_a = parts_a.get(index_a); - int path_b = parts_b.get(index_b); + if (!intersector.next()) { + return true; // no rings intersect + } + } else { + return true; // no rings intersect + } - multipath_b.getXY(multipath_b.getPathStart(path_b), pt_b); - env_a_inf.setCoords(intersector.getRedEnvelope(index_a)); - env_a_inf.inflate(tolerance, tolerance); + boolean b_intersects = linearPathIntersectsLinearPath_(polygon_a, + multipath_b, tolerance); - if (env_a_inf.contains(pt_b)) { - PolygonUtils.PiPResult result = PolygonUtils - .isPointInPolygon2D(polygon_a, pt_b, 0.0); + if (b_intersects) { + return false; + } - if (result != PolygonUtils.PiPResult.PiPOutside) - return false; - } + do { + int index_a = intersector.getHandleA(); + int index_b = intersector.getHandleB(); + int path_a = intersector.getRedElement(index_a); + int path_b = intersector.getBlueElement(index_b); - if (multipath_b.getType().value() == Geometry.GeometryType.Polygon) { - polygon_a.getXY(polygon_a.getPathStart(path_a), pt_a); - env_b_inf.setCoords(intersector.getBlueEnvelope(index_b)); - env_b_inf.inflate(tolerance, tolerance); + pt_b = multipath_b.getXY(multipath_b.getPathStart(path_b)); + env_a_inf.setCoords(intersector.getRedEnvelope(index_a)); + env_a_inf.inflate(tolerance, tolerance); - if (env_b_inf.contains(pt_a)) { - PolygonUtils.PiPResult result = PolygonUtils - .isPointInPolygon2D((Polygon) multipath_b, - pt_a, 0.0); + if (env_a_inf.contains(pt_b)) { + PolygonUtils.PiPResult result = PolygonUtils + .isPointInPolygon2D(polygon_a, pt_b, 0.0); - if (result != PolygonUtils.PiPResult.PiPOutside) - return false; - } + if (result != PolygonUtils.PiPResult.PiPOutside) { + return false; } } - } - boolean b_intersects = linearPathIntersectsLinearPath_(polygon_a, - multipath_b, tolerance); + if (multipath_b.getType() == Geometry.Type.Polygon) { + pt_a = polygon_a.getXY(polygon_a.getPathStart(path_a)); + env_b_inf.setCoords(intersector.getBlueEnvelope(index_b)); + env_b_inf.inflate(tolerance, tolerance); - if (b_intersects) - return false; + if (env_b_inf.contains(pt_a)) { + PolygonUtils.PiPResult result = PolygonUtils + .isPointInPolygon2D((Polygon) multipath_b, pt_a, + 0.0); + + if (result != PolygonUtils.PiPResult.PiPOutside) { + return false; + } + } + } + } while (intersector.next()); return true; } @@ -3386,8 +3440,25 @@ private static boolean linearPathWithinLinearPath_(MultiPath multipathA, SegmentIteratorImpl segIterB = ((MultiPathImpl) multipathB._getImpl()) .querySegmentIterator(); - QuadTreeImpl quadTreeB = InternalUtils.buildQuadTree( - ((MultiPathImpl) multipathB._getImpl()), envInter); + QuadTreeImpl qtB; + QuadTreeImpl quadTreeB; + + GeometryAccelerators accel = ((MultiPathImpl) multipathB._getImpl()) + ._getAccelerators(); + + if (accel != null) { + quadTreeB = accel.getQuadTree(); + if (quadTreeB == null) { + qtB = InternalUtils.buildQuadTree( + (MultiPathImpl) multipathB._getImpl(), envInter); + quadTreeB = qtB; + } + } else { + qtB = InternalUtils.buildQuadTree( + (MultiPathImpl) multipathB._getImpl(), envInter); + quadTreeB = qtB; + } + QuadTreeImpl.QuadTreeIteratorImpl qtIterB = quadTreeB.getIterator(); while (segIterA.nextPath()) { @@ -3435,8 +3506,8 @@ private static boolean linearPathWithinLinearPath_(MultiPath multipathA, boolean bSegmentACovered = true; while (bSegmentACovered) {// keep going while the - // current segmentA is - // covered. + // current segmentA is + // covered. if (segIterA.hasNextSegment()) { segmentA = segIterA.nextSegment(); lengthA = segmentA.calculateLength2D(); @@ -3501,16 +3572,17 @@ private static boolean linearPathWithinLinearPath_(MultiPath multipathA, } } - if (bStringOfSegmentAsCovered) + if (bStringOfSegmentAsCovered) { continue; // no need to check that segmentA is covered - + } if (ievent == relOps.m_overlap_events.size()) { return false; // bWithin = false } - if (eventIndices.size() - ievent > 1) + if (eventIndices.size() - ievent > 1) { eventIndices.Sort(ievent, eventIndices.size(), overlapComparer); + } double lastScalar = 0.0; @@ -3519,8 +3591,9 @@ private static boolean linearPathWithinLinearPath_(MultiPath multipathA, .get(i)); if (overlapEvent.m_scalar_a_0 < lastScalar - && overlapEvent.m_scalar_a_1 < lastScalar) + && overlapEvent.m_scalar_a_1 < lastScalar) { continue; + } if (lengthA * (overlapEvent.m_scalar_a_0 - lastScalar) > tolerance) { return false; // bWithin = false @@ -3528,8 +3601,9 @@ private static boolean linearPathWithinLinearPath_(MultiPath multipathA, lastScalar = overlapEvent.m_scalar_a_1; if (lengthA * (1.0 - lastScalar) <= tolerance - || lastScalar == 1.0) + || lastScalar == 1.0) { break; + } } } @@ -3579,115 +3653,6 @@ private static boolean linearPathOverlapsLinearPath_(MultiPath multipathA, tolerance); } - // Returns true if the segments of multipathA touches the segments of - // multipathB. - private static boolean linearPathTouchesLinearPath_(MultiPath _multipathA, - MultiPath _multipathB, double tolerance) { - MultiPath multipathA; - MultiPath multipathB; - - if (_multipathA.getSegmentCount() > _multipathB.getSegmentCount()) { - multipathA = _multipathB; - multipathB = _multipathA; - } else { - multipathA = _multipathA; - multipathB = _multipathB; - } - - SegmentIteratorImpl segIterA = ((MultiPathImpl) multipathA._getImpl()) - .querySegmentIterator(); - SegmentIteratorImpl segIterB = ((MultiPathImpl) multipathB._getImpl()) - .querySegmentIterator(); - double[] scalarsA = new double[2]; - double[] scalarsB = new double[2]; - - boolean bBoundaryIntersectionFound = false; - - Envelope2D env_a = new Envelope2D(); - Envelope2D env_b = new Envelope2D(); - Envelope2D envInter = new Envelope2D(); - multipathA.queryEnvelope2D(env_a); - multipathB.queryEnvelope2D(env_b); - env_a.inflate(tolerance, tolerance); - env_b.inflate(tolerance, tolerance); - envInter.setCoords(env_a); - envInter.intersect(env_b); - - QuadTreeImpl quadTreeB = InternalUtils.buildQuadTree( - (MultiPathImpl) multipathB._getImpl(), envInter); - QuadTreeImpl.QuadTreeIteratorImpl qtIterB = quadTreeB.getIterator(); - - while (segIterA.nextPath()) { - boolean bHasEndPointsA = !isClosedPath_(multipathA, - segIterA.getPathIndex()); - - while (segIterA.hasNextSegment()) { - Segment segmentA = segIterA.nextSegment(); - segmentA.queryEnvelope2D(env_a); - - if (!env_a.isIntersecting(envInter)) - continue; - - double lengthA = segmentA.calculateLength2D(); - - qtIterB.resetIterator(segmentA, tolerance); - - for (int elementHandleB = qtIterB.next(); elementHandleB != -1; elementHandleB = qtIterB - .next()) { - int vertex_b = quadTreeB.getElement(elementHandleB); - segIterB.resetToVertex(vertex_b); - boolean bHasEndPointsB = !isClosedPath_(multipathB, - segIterB.getPathIndex()); - - Segment segmentB = segIterB.nextSegment(); - double lengthB = segmentB.calculateLength2D(); - - int result = segmentA.intersect(segmentB, null, scalarsA, - scalarsB, tolerance); - - if (result > 0) { - if (result == 2 - && lengthA * (scalarsA[1] - scalarsA[0]) > tolerance) - return false; - - double scalar_a_0 = scalarsA[0]; - double scalar_b_0 = scalarsB[0]; - - boolean bTouchesStartA = false, bTouchesEndA = false, bTouchesStartB = false, bTouchesEndB = false; - - if (lengthA * scalar_a_0 <= tolerance - && segIterA.isFirstSegmentInPath()) - bTouchesStartA = true; - - if (lengthA * (1.0 - scalar_a_0) <= tolerance - && segIterA.isLastSegmentInPath()) - bTouchesEndA = true; - - if (lengthB * scalar_b_0 <= tolerance - && segIterB.isFirstSegmentInPath()) - bTouchesStartB = true; - - if (lengthB * (1.0 - scalar_b_0) <= tolerance - && segIterB.isLastSegmentInPath()) - bTouchesEndB = true; - - if (((!bTouchesStartA && !bTouchesEndA) || !bHasEndPointsA) - && ((!bTouchesStartB && !bTouchesEndB) || !bHasEndPointsB)) - return false; // return false if Interior-Interior - // intersection is found - - bBoundaryIntersectionFound = true; - } - } - } - } - - return bBoundaryIntersectionFound; // If we haven't already returned - // false, then no Interior-Interior - // intersection has been found. - // Thus, they touch. - } - // Returns true the dimension of intersection of _multipathA and // _multipathB. static int linearPathIntersectsLinearPathMaxDim_(MultiPath _multipathA, @@ -3732,11 +3697,29 @@ static int linearPathIntersectsLinearPathMaxDim_(MultiPath _multipathA, Point2D int_point = null; - if (intersections != null) + if (intersections != null) { int_point = new Point2D(); + } + + QuadTreeImpl qtB; + QuadTreeImpl quadTreeB; + + GeometryAccelerators accel = ((MultiPathImpl) multipathB._getImpl()) + ._getAccelerators(); + + if (accel != null) { + quadTreeB = accel.getQuadTree(); + if (quadTreeB == null) { + qtB = InternalUtils.buildQuadTree( + (MultiPathImpl) multipathB._getImpl(), envInter); + quadTreeB = qtB; + } + } else { + qtB = InternalUtils.buildQuadTree( + (MultiPathImpl) multipathB._getImpl(), envInter); + quadTreeB = qtB; + } - QuadTreeImpl quadTreeB = InternalUtils.buildQuadTree( - (MultiPathImpl) multipathB._getImpl(), envInter); QuadTreeImpl.QuadTreeIteratorImpl qtIterB = quadTreeB.getIterator(); while (segIterA.nextPath()) { @@ -3746,8 +3729,9 @@ static int linearPathIntersectsLinearPathMaxDim_(MultiPath _multipathA, Segment segmentA = segIterA.nextSegment(); segmentA.queryEnvelope2D(env_a); - if (!env_a.isIntersecting(envInter)) + if (!env_a.isIntersecting(envInter)) { continue; + } double lengthA = segmentA.calculateLength2D(); @@ -3908,8 +3892,9 @@ static int linearPathIntersectsLinearPathMaxDim_(MultiPath _multipathA, .get(i)); if (overlapEvent.m_scalar_a_0 < lastScalar - && overlapEvent.m_scalar_a_1 < lastScalar) + && overlapEvent.m_scalar_a_1 < lastScalar) { continue; + } if (lengthA * (overlapEvent.m_scalar_a_0 - lastScalar) > tolerance) { overlapLength = lengthA @@ -3921,10 +3906,10 @@ static int linearPathIntersectsLinearPathMaxDim_(MultiPath _multipathA, overlapLength = lengthA * (overlapEvent.m_scalar_a_1 - overlapEvent.m_scalar_a_0); // reset lastPath = overlapEvent.m_ipath_a; - } else + } else { overlapLength += lengthA * (overlapEvent.m_scalar_a_1 - overlapEvent.m_scalar_a_0); // accumulate - + } if (overlapLength > tolerance) { dim = 1; return dim; @@ -3932,14 +3917,15 @@ static int linearPathIntersectsLinearPathMaxDim_(MultiPath _multipathA, lastScalar = overlapEvent.m_scalar_a_1; - if (lastScalar == 1.0) + if (lastScalar == 1.0) { break; + } } } - if (lengthA * (1.0 - lastScalar) > tolerance) + if (lengthA * (1.0 - lastScalar) > tolerance) { overlapLength = 0.0; // reset - + } ievent = 0; eventIndices.resize(0); relOps.m_overlap_events.clear(); @@ -3959,30 +3945,24 @@ private static boolean linearPathIntersectsLinearPath_( SegmentIteratorImpl segIterA = multi_path_impl_a.querySegmentIterator(); SegmentIteratorImpl segIterB = multi_path_impl_b.querySegmentIterator(); - AttributeStreamOfInt32 verticesA = new AttributeStreamOfInt32(0); - AttributeStreamOfInt32 verticesB = new AttributeStreamOfInt32(0); - Envelope2DIntersectorImpl intersector = InternalUtils - .getEnvelope2DIntersector(multi_path_impl_a, multi_path_impl_b, - tolerance, verticesA, verticesB); + Pair_wise_intersector intersector = new Pair_wise_intersector( + multi_path_impl_a, multi_path_impl_b, tolerance); - if (intersector != null) { - while (intersector.next()) { - int index_a = intersector.getHandleA(); - int index_b = intersector.getHandleB(); - int vertex_a = verticesA.get(index_a); - int vertex_b = verticesB.get(index_b); + while (intersector.next()) { + int vertex_a = intersector.get_red_element(); + int vertex_b = intersector.get_blue_element(); - segIterA.resetToVertex(vertex_a); - segIterB.resetToVertex(vertex_b); - Segment segmentA = segIterA.nextSegment(); - Segment segmentB = segIterB.nextSegment(); + segIterA.resetToVertex(vertex_a); + segIterB.resetToVertex(vertex_b); + Segment segmentA = segIterA.nextSegment(); + Segment segmentB = segIterB.nextSegment(); - int result = segmentB.intersect(segmentA, null, null, null, - tolerance); + int result = segmentB.intersect(segmentA, null, null, null, + tolerance); - if (result > 0) - return true; + if (result > 0) { + return true; } } @@ -4008,15 +3988,33 @@ private static boolean linearPathIntersectsMultiPoint_( multipoint_b.queryEnvelope2D(env_b); env_a.inflate(tolerance, tolerance); - if (!env_a.contains(env_b)) + if (!env_a.contains(env_b)) { bContained = false; + } env_b.inflate(tolerance, tolerance); envInter.setCoords(env_a); envInter.intersect(env_b); - QuadTreeImpl quadTreeA = InternalUtils.buildQuadTree( - (MultiPathImpl) multipathA._getImpl(), envInter); + QuadTreeImpl qtA = null; + QuadTreeImpl quadTreeA = null; + + GeometryAccelerators accel = ((MultiPathImpl) multipathA._getImpl()) + ._getAccelerators(); + + if (accel != null) { + quadTreeA = accel.getQuadTree(); + if (quadTreeA == null) { + qtA = InternalUtils.buildQuadTree( + (MultiPathImpl) multipathA._getImpl(), envInter); + quadTreeA = qtA; + } + } else { + qtA = InternalUtils.buildQuadTree( + (MultiPathImpl) multipathA._getImpl(), envInter); + quadTreeA = qtA; + } + QuadTreeImpl.QuadTreeIteratorImpl qtIterA = quadTreeA.getIterator(); Point2D ptB = new Point2D(), closest = new Point2D(); boolean b_intersects = false; @@ -4025,8 +4023,9 @@ private static boolean linearPathIntersectsMultiPoint_( for (int i = 0; i < multipoint_b.getPointCount(); i++) { multipoint_b.getXY(i, ptB); - if (!envInter.contains(ptB)) + if (!envInter.contains(ptB)) { continue; + } boolean bPtBContained = false; env_b.setCoords(ptB.x, ptB.y, ptB.x, ptB.y); @@ -4050,53 +4049,83 @@ private static boolean linearPathIntersectsMultiPoint_( } if (b_intersects_all) { - if (!b_covered) + if (!b_covered) { return false; + } } else { - if (b_covered) + if (b_covered) { return true; + } } } - if (b_intersects_all) + if (b_intersects_all) { return true; + } return false; } // Returns true if a segment of multipathA intersects point_b. - private static boolean linearPathIntersectsPoint_(MultiPath multipathA, + static boolean linearPathIntersectsPoint_(MultiPath multipathA, Point2D ptB, double tolerance) { - SegmentIterator segIterA = multipathA.querySegmentIterator(); Point2D closest = new Point2D(); double toleranceSq = tolerance * tolerance; + SegmentIteratorImpl segIterA = ((MultiPathImpl) multipathA._getImpl()) + .querySegmentIterator(); + + GeometryAccelerators accel = ((MultiPathImpl) multipathA._getImpl()) + ._getAccelerators(); + + if (accel != null) { + QuadTreeImpl quadTreeA = accel.getQuadTree(); + if (quadTreeA != null) { + Envelope2D env_b = new Envelope2D(); + env_b.setCoords(ptB); + QuadTreeImpl.QuadTreeIteratorImpl qt_iter = quadTreeA + .getIterator(env_b, tolerance); + + for (int e = qt_iter.next(); e != -1; e = qt_iter.next()) { + segIterA.resetToVertex(quadTreeA.getElement(e)); + + if (segIterA.hasNextSegment()) { + Segment segmentA = segIterA.nextSegment(); + + double t = segmentA.getClosestCoordinate(ptB, false); + segmentA.getCoord2D(t, closest); + + if (Point2D.sqrDistance(ptB, closest) <= toleranceSq) { + return true; + } + } + } + + return false; + } + } Envelope2D env_a = new Envelope2D(); - boolean b_intersects = false; while (segIterA.nextPath()) { while (segIterA.hasNextSegment()) { Segment segmentA = segIterA.nextSegment(); - boolean bHasEndPointsA = !isClosedPath_(multipathA, - segIterA.getPathIndex()); - segmentA.queryEnvelope2D(env_a); env_a.inflate(tolerance, tolerance); - if (!env_a.contains(ptB)) + if (!env_a.contains(ptB)) { continue; + } double t = segmentA.getClosestCoordinate(ptB, false); segmentA.getCoord2D(t, closest); if (Point2D.sqrDistance(ptB, closest) <= toleranceSq) { - b_intersects = true; - break; + return true; } } } - return b_intersects; + return false; } private static boolean linearPathContainsPoint_(MultiPath multipathA, @@ -4290,18 +4319,6 @@ private static boolean checkVerticesForIntersection_( return false; } - // Returns true if the MultiPath is Closed, or if the end points are within - // a tolerance. - private static boolean isClosedPath_(MultiPath multipathA, int pathA) { - if (multipathA.isClosedPath(pathA)) - return true; - - return false; - // Point2D startA = multipathA.get_xy(multipathA.getPathStart(pathA)); - // Point2D endA = multipathA.get_xy(multipathA.getPathEnd(pathA) - 1); - // return startA.equals(endA); - } - private static boolean polygonTouchesPolygonImpl_(Polygon polygon_a, Polygon polygon_b, double tolerance, ProgressTracker progressTracker) { MultiPathImpl polygon_impl_a = (MultiPathImpl) polygon_a._getImpl(); @@ -4313,61 +4330,55 @@ private static boolean polygonTouchesPolygonImpl_(Polygon polygon_a, SegmentIteratorImpl segIterA = polygon_impl_a.querySegmentIterator(); SegmentIteratorImpl segIterB = polygon_impl_b.querySegmentIterator(); - AttributeStreamOfInt32 verticesA = new AttributeStreamOfInt32(0); - AttributeStreamOfInt32 verticesB = new AttributeStreamOfInt32(0); double[] scalarsA = new double[2]; double[] scalarsB = new double[2]; - Envelope2DIntersectorImpl intersector = InternalUtils - .getEnvelope2DIntersector(polygon_impl_a, polygon_impl_b, - tolerance, verticesA, verticesB); + Pair_wise_intersector intersector = new Pair_wise_intersector( + polygon_impl_a, polygon_impl_b, tolerance); boolean b_boundaries_intersect = false; - if (intersector != null) { - while (intersector.next()) { - int index_a = intersector.getHandleA(); - int index_b = intersector.getHandleB(); - int vertex_a = verticesA.get(index_a); - int vertex_b = verticesB.get(index_b); - - segIterA.resetToVertex(vertex_a); - segIterB.resetToVertex(vertex_b); - Segment segmentA = segIterA.nextSegment(); - Segment segmentB = segIterB.nextSegment(); + while (intersector.next()) { + int vertex_a = intersector.get_red_element(); + int vertex_b = intersector.get_blue_element(); - int result = segmentB.intersect(segmentA, null, scalarsB, - scalarsA, tolerance); + segIterA.resetToVertex(vertex_a); + segIterB.resetToVertex(vertex_b); + Segment segmentA = segIterA.nextSegment(); + Segment segmentB = segIterB.nextSegment(); - if (result == 2) { - double scalar_a_0 = scalarsA[0]; - double scalar_a_1 = scalarsA[1]; - double length_a = segmentA.calculateLength2D(); + int result = segmentB.intersect(segmentA, null, scalarsB, scalarsA, + tolerance); - if (b_geometries_simple - && (scalar_a_1 - scalar_a_0) * length_a > tolerance) { - // If the line segments overlap along the same - // direction, then we have an Interior-Interior - // intersection - return false; - } + if (result == 2) { + double scalar_a_0 = scalarsA[0]; + double scalar_a_1 = scalarsA[1]; + double length_a = segmentA.calculateLength2D(); - b_boundaries_intersect = true; - } else if (result != 0) { - double scalar_a_0 = scalarsA[0]; - double scalar_b_0 = scalarsB[0]; + if (b_geometries_simple + && (scalar_a_1 - scalar_a_0) * length_a > tolerance) { + // If the line segments overlap along the same direction, + // then we have an Interior-Interior intersection + return false; + } - if (scalar_a_0 > 0.0 && scalar_a_0 < 1.0 - && scalar_b_0 > 0.0 && scalar_b_0 < 1.0) - return false; + b_boundaries_intersect = true; + } else if (result != 0) { + double scalar_a_0 = scalarsA[0]; + double scalar_b_0 = scalarsB[0]; - b_boundaries_intersect = true; + if (scalar_a_0 > 0.0 && scalar_a_0 < 1.0 && scalar_b_0 > 0.0 + && scalar_b_0 < 1.0) { + return false; } + + b_boundaries_intersect = true; } } - if (!b_boundaries_intersect) + if (!b_boundaries_intersect) { return false; + } Envelope2D env_a = new Envelope2D(), env_b = new Envelope2D(), envInter = new Envelope2D(); polygon_a.queryEnvelope2D(env_a); @@ -4383,8 +4394,9 @@ private static boolean polygonTouchesPolygonImpl_(Polygon polygon_a, if (polygon_a.getPointCount() > 10) { _polygonA = (Polygon) (Clipper.clip(polygon_a, envInter, tolerance, 0.0)); - if (_polygonA.isEmpty()) + if (_polygonA.isEmpty()) { return false; + } } else { _polygonA = polygon_a; } @@ -4392,14 +4404,14 @@ private static boolean polygonTouchesPolygonImpl_(Polygon polygon_a, if (polygon_b.getPointCount() > 10) { _polygonB = (Polygon) (Clipper.clip(polygon_b, envInter, tolerance, 0.0)); - if (_polygonB.isEmpty()) + if (_polygonB.isEmpty()) { return false; + } } else { _polygonB = polygon_b; } // We just need to determine whether interior_interior is false - String scl = "F********"; boolean bRelation = RelationalOperationsMatrix.polygonRelatePolygon_( _polygonA, _polygonB, tolerance, scl, progressTracker); @@ -4427,52 +4439,46 @@ private static boolean polygonOverlapsPolygonImpl_(Polygon polygon_a, SegmentIteratorImpl segIterA = polygon_impl_a.querySegmentIterator(); SegmentIteratorImpl segIterB = polygon_impl_b.querySegmentIterator(); - AttributeStreamOfInt32 verticesA = new AttributeStreamOfInt32(0); - AttributeStreamOfInt32 verticesB = new AttributeStreamOfInt32(0); double[] scalarsA = new double[2]; double[] scalarsB = new double[2]; - Envelope2DIntersectorImpl intersector = InternalUtils - .getEnvelope2DIntersector(polygon_impl_a, polygon_impl_b, - tolerance, verticesA, verticesB); + Pair_wise_intersector intersector = new Pair_wise_intersector( + polygon_impl_a, polygon_impl_b, tolerance); - if (intersector != null) { - while (intersector.next()) { - int index_a = intersector.getHandleA(); - int index_b = intersector.getHandleB(); - int vertex_a = verticesA.get(index_a); - int vertex_b = verticesB.get(index_b); + while (intersector.next()) { + int vertex_a = intersector.get_red_element(); + int vertex_b = intersector.get_blue_element(); - segIterA.resetToVertex(vertex_a); - segIterB.resetToVertex(vertex_b); - Segment segmentA = segIterA.nextSegment(); - Segment segmentB = segIterB.nextSegment(); + segIterA.resetToVertex(vertex_a); + segIterB.resetToVertex(vertex_b); + Segment segmentA = segIterA.nextSegment(); + Segment segmentB = segIterB.nextSegment(); - int result = segmentB.intersect(segmentA, null, scalarsB, - scalarsA, tolerance); + int result = segmentB.intersect(segmentA, null, scalarsB, scalarsA, + tolerance); - if (result == 2) { - double scalar_a_0 = scalarsA[0]; - double scalar_a_1 = scalarsA[1]; - double length_a = segmentA.calculateLength2D(); + if (result == 2) { + double scalar_a_0 = scalarsA[0]; + double scalar_a_1 = scalarsA[1]; + double length_a = segmentA.calculateLength2D(); - if (b_geometries_simple - && (scalar_a_1 - scalar_a_0) * length_a > tolerance) { - // When the line segments intersect along the same - // direction, then we have an interior-interior - // intersection - bInteriorIntersectionKnown = true; + if (b_geometries_simple + && (scalar_a_1 - scalar_a_0) * length_a > tolerance) { + // When the line segments intersect along the same + // direction, then we have an interior-interior intersection + bInteriorIntersectionKnown = true; - if (bIntAExtB && bExtAIntB) - return true; + if (bIntAExtB && bExtAIntB) { + return true; } - } else if (result != 0) { - double scalar_a_0 = scalarsA[0]; - double scalar_b_0 = scalarsB[0]; + } + } else if (result != 0) { + double scalar_a_0 = scalarsA[0]; + double scalar_b_0 = scalarsB[0]; - if (scalar_a_0 > 0.0 && scalar_a_0 < 1.0 - && scalar_b_0 > 0.0 && scalar_b_0 < 1.0) - return true; + if (scalar_a_0 > 0.0 && scalar_a_0 < 1.0 && scalar_b_0 > 0.0 + && scalar_b_0 < 1.0) { + return true; } } } @@ -4490,17 +4496,19 @@ private static boolean polygonOverlapsPolygonImpl_(Polygon polygon_a, Polygon _polygonB; StringBuilder scl = new StringBuilder(); - if (!bInteriorIntersectionKnown) + if (!bInteriorIntersectionKnown) { scl.append("T*"); - else + } else { scl.append("**"); + } if (bIntAExtB) { if (polygon_b.getPointCount() > 10) { _polygonB = (Polygon) (Clipper.clip(polygon_b, envInter, tolerance, 0.0)); - if (_polygonB.isEmpty()) + if (_polygonB.isEmpty()) { return false; + } } else { _polygonB = polygon_b; } @@ -4515,8 +4523,9 @@ private static boolean polygonOverlapsPolygonImpl_(Polygon polygon_a, if (polygon_a.getPointCount() > 10) { _polygonA = (Polygon) (Clipper.clip(polygon_a, envInter, tolerance, 0.0)); - if (_polygonA.isEmpty()) + if (_polygonA.isEmpty()) { return false; + } } else { _polygonA = polygon_a; } @@ -4540,43 +4549,36 @@ private static boolean polygonContainsPolygonImpl_(Polygon polygon_a, SegmentIteratorImpl segIterA = polygon_impl_a.querySegmentIterator(); SegmentIteratorImpl segIterB = polygon_impl_b.querySegmentIterator(); - AttributeStreamOfInt32 verticesA = new AttributeStreamOfInt32(0); - AttributeStreamOfInt32 verticesB = new AttributeStreamOfInt32(0); double[] scalarsA = new double[2]; double[] scalarsB = new double[2]; - Envelope2DIntersectorImpl intersector = InternalUtils - .getEnvelope2DIntersector(polygon_impl_a, polygon_impl_b, - tolerance, verticesA, verticesB); + Pair_wise_intersector intersector = new Pair_wise_intersector( + polygon_impl_a, polygon_impl_b, tolerance); - if (intersector != null) { - while (intersector.next()) { - int index_a = intersector.getHandleA(); - int index_b = intersector.getHandleB(); - int vertex_a = verticesA.get(index_a); - int vertex_b = verticesB.get(index_b); + while (intersector.next()) { + int vertex_a = intersector.get_red_element(); + int vertex_b = intersector.get_blue_element(); - segIterA.resetToVertex(vertex_a); - segIterB.resetToVertex(vertex_b); - Segment segmentA = segIterA.nextSegment(); - Segment segmentB = segIterB.nextSegment(); + segIterA.resetToVertex(vertex_a); + segIterB.resetToVertex(vertex_b); + Segment segmentA = segIterA.nextSegment(); + Segment segmentB = segIterB.nextSegment(); - int result = segmentB.intersect(segmentA, null, scalarsB, - scalarsA, tolerance); + int result = segmentB.intersect(segmentA, null, scalarsB, scalarsA, + tolerance); - if (result == 1) { - double scalar_a_0 = scalarsA[0]; - double scalar_b_0 = scalarsB[0]; + if (result == 1) { + double scalar_a_0 = scalarsA[0]; + double scalar_b_0 = scalarsB[0]; - if (scalar_a_0 > 0.0 && scalar_a_0 < 1.0 - && scalar_b_0 > 0.0 && scalar_b_0 < 1.0) - return false; + if (scalar_a_0 > 0.0 && scalar_a_0 < 1.0 && scalar_b_0 > 0.0 + && scalar_b_0 < 1.0) { + return false; } } } // We can clip polygon_a to the extent of polyline_b - Envelope2D envBInflated = new Envelope2D(); polygon_b.queryEnvelope2D(envBInflated); envBInflated.inflate(1000.0 * tolerance, 1000.0 * tolerance); @@ -4586,14 +4588,15 @@ private static boolean polygonContainsPolygonImpl_(Polygon polygon_a, if (polygon_a.getPointCount() > 10) { _polygonA = (Polygon) (Clipper.clip(polygon_a, envBInflated, tolerance, 0.0)); - if (_polygonA.isEmpty()) + if (_polygonA.isEmpty()) { return false; + } } else { _polygonA = polygon_a; } String scl = "T*****F**"; // If Exterior-Interior is false, then - // Exterior-Boundary is false + // Exterior-Boundary is false boolean bRelation = RelationalOperationsMatrix.polygonRelatePolygon_( _polygonA, polygon_b, tolerance, scl, progressTracker); @@ -4608,49 +4611,44 @@ private static boolean polygonTouchesPolylineImpl_(Polygon polygon_a, SegmentIteratorImpl segIterA = polygon_impl_a.querySegmentIterator(); SegmentIteratorImpl segIterB = polyline_impl_b.querySegmentIterator(); - AttributeStreamOfInt32 verticesA = new AttributeStreamOfInt32(0); - AttributeStreamOfInt32 verticesB = new AttributeStreamOfInt32(0); double[] scalarsA = new double[2]; double[] scalarsB = new double[2]; - Envelope2DIntersectorImpl intersector = InternalUtils - .getEnvelope2DIntersector(polygon_impl_a, polyline_impl_b, - tolerance, verticesA, verticesB); + Pair_wise_intersector intersector = new Pair_wise_intersector( + polygon_impl_a, polyline_impl_b, tolerance); boolean b_boundaries_intersect = false; - if (intersector != null) { - while (intersector.next()) { - int index_a = intersector.getHandleA(); - int index_b = intersector.getHandleB(); - int vertex_a = verticesA.get(index_a); - int vertex_b = verticesB.get(index_b); + while (intersector.next()) { + int vertex_a = intersector.get_red_element(); + int vertex_b = intersector.get_blue_element(); - segIterA.resetToVertex(vertex_a); - segIterB.resetToVertex(vertex_b); - Segment segmentA = segIterA.nextSegment(); - Segment segmentB = segIterB.nextSegment(); - - int result = segmentB.intersect(segmentA, null, scalarsB, - scalarsA, tolerance); + segIterA.resetToVertex(vertex_a); + segIterB.resetToVertex(vertex_b); + Segment segmentA = segIterA.nextSegment(); + Segment segmentB = segIterB.nextSegment(); - if (result == 2) { - b_boundaries_intersect = true; - } else if (result != 0) { - double scalar_a_0 = scalarsA[0]; - double scalar_b_0 = scalarsB[0]; + int result = segmentB.intersect(segmentA, null, scalarsB, scalarsA, + tolerance); - if (scalar_a_0 > 0.0 && scalar_a_0 < 1.0 - && scalar_b_0 > 0.0 && scalar_b_0 < 1.0) - return false; + if (result == 2) { + b_boundaries_intersect = true; + } else if (result != 0) { + double scalar_a_0 = scalarsA[0]; + double scalar_b_0 = scalarsB[0]; - b_boundaries_intersect = true; + if (scalar_a_0 > 0.0 && scalar_a_0 < 1.0 && scalar_b_0 > 0.0 + && scalar_b_0 < 1.0) { + return false; } + + b_boundaries_intersect = true; } } - if (!b_boundaries_intersect) + if (!b_boundaries_intersect) { return false; + } Envelope2D env_a = new Envelope2D(), env_b = new Envelope2D(), envInter = new Envelope2D(); polygon_a.queryEnvelope2D(env_a); @@ -4666,8 +4664,9 @@ private static boolean polygonTouchesPolylineImpl_(Polygon polygon_a, if (polygon_a.getPointCount() > 10) { _polygonA = (Polygon) (Clipper.clip(polygon_a, envInter, tolerance, 0.0)); - if (_polygonA.isEmpty()) + if (_polygonA.isEmpty()) { return false; + } } else { _polygonA = polygon_a; } @@ -4675,14 +4674,14 @@ private static boolean polygonTouchesPolylineImpl_(Polygon polygon_a, if (polyline_b.getPointCount() > 10) { _polylineB = (Polyline) Clipper.clip(polyline_b, envInter, tolerance, 0.0); - if (_polylineB.isEmpty()) + if (_polylineB.isEmpty()) { return false; + } } else { _polylineB = polyline_b; } // We just need to determine that interior_interior is false - String scl = "F********"; boolean bRelation = RelationalOperationsMatrix.polygonRelatePolyline_( _polygonA, _polylineB, tolerance, scl, progressTracker); @@ -4698,50 +4697,44 @@ private static boolean polygonCrossesPolylineImpl_(Polygon polygon_a, SegmentIteratorImpl segIterA = polygon_impl_a.querySegmentIterator(); SegmentIteratorImpl segIterB = polyline_impl_b.querySegmentIterator(); - AttributeStreamOfInt32 verticesA = new AttributeStreamOfInt32(0); - AttributeStreamOfInt32 verticesB = new AttributeStreamOfInt32(0); - double[] scalarsA = new double[2]; double[] scalarsB = new double[2]; - Envelope2DIntersectorImpl intersector = InternalUtils - .getEnvelope2DIntersector(polygon_impl_a, polyline_impl_b, - tolerance, verticesA, verticesB); + Pair_wise_intersector intersector = new Pair_wise_intersector( + polygon_impl_a, polyline_impl_b, tolerance); boolean b_boundaries_intersect = false; - if (intersector != null) { - while (intersector.next()) { - int index_a = intersector.getHandleA(); - int index_b = intersector.getHandleB(); - int vertex_a = verticesA.get(index_a); - int vertex_b = verticesB.get(index_b); + while (intersector.next()) { + int vertex_a = intersector.get_red_element(); + int vertex_b = intersector.get_blue_element(); - segIterA.resetToVertex(vertex_a); - segIterB.resetToVertex(vertex_b); - Segment segmentA = segIterA.nextSegment(); - Segment segmentB = segIterB.nextSegment(); - - int result = segmentB.intersect(segmentA, null, scalarsB, - scalarsA, tolerance); + segIterA.resetToVertex(vertex_a); + segIterB.resetToVertex(vertex_b); + Segment segmentA = segIterA.nextSegment(); + Segment segmentB = segIterB.nextSegment(); - if (result == 2) { - b_boundaries_intersect = true; - } else if (result != 0) { - double scalar_a_0 = scalarsA[0]; - double scalar_b_0 = scalarsB[0]; + int result = segmentB.intersect(segmentA, null, scalarsB, scalarsA, + tolerance); - if (scalar_a_0 > 0.0 && scalar_a_0 < 1.0 - && scalar_b_0 > 0.0 && scalar_b_0 < 1.0) - return true; + if (result == 2) { + b_boundaries_intersect = true; + } else if (result != 0) { + double scalar_a_0 = scalarsA[0]; + double scalar_b_0 = scalarsB[0]; - b_boundaries_intersect = true; + if (scalar_a_0 > 0.0 && scalar_a_0 < 1.0 && scalar_b_0 > 0.0 + && scalar_b_0 < 1.0) { + return true; } + + b_boundaries_intersect = true; } } - if (!b_boundaries_intersect) + if (!b_boundaries_intersect) { return false; + } Envelope2D env_a = new Envelope2D(), env_b = new Envelope2D(), envAInflated = new Envelope2D(), envBInflated = new Envelope2D(), envInter = new Envelope2D(); polygon_a.queryEnvelope2D(env_a); @@ -4761,8 +4754,9 @@ private static boolean polygonCrossesPolylineImpl_(Polygon polygon_a, if (polygon_a.getPointCount() > 10) { _polygonA = (Polygon) (Clipper.clip(polygon_a, envInter, tolerance, 0.0)); - if (_polygonA.isEmpty()) + if (_polygonA.isEmpty()) { return false; + } } else { _polygonA = polygon_a; } @@ -4770,8 +4764,9 @@ private static boolean polygonCrossesPolylineImpl_(Polygon polygon_a, if (polyline_b.getPointCount() > 10) { _polylineB = (Polyline) (Clipper.clip(polyline_b, envInter, tolerance, 0.0)); - if (_polylineB.isEmpty()) + if (_polylineB.isEmpty()) { return false; + } } else { _polylineB = polyline_b; } @@ -4798,48 +4793,42 @@ private static boolean polygonContainsPolylineImpl_(Polygon polygon_a, SegmentIteratorImpl segIterA = polygon_impl_a.querySegmentIterator(); SegmentIteratorImpl segIterB = polyline_impl_b.querySegmentIterator(); - AttributeStreamOfInt32 verticesA = new AttributeStreamOfInt32(0); - AttributeStreamOfInt32 verticesB = new AttributeStreamOfInt32(0); double[] scalarsA = new double[2]; double[] scalarsB = new double[2]; - Envelope2DIntersectorImpl intersector = InternalUtils - .getEnvelope2DIntersector(polygon_impl_a, polyline_impl_b, - tolerance, verticesA, verticesB); + Pair_wise_intersector intersector = new Pair_wise_intersector( + polygon_impl_a, polyline_impl_b, tolerance); boolean b_boundaries_intersect = false; - if (intersector != null) { - while (intersector.next()) { - int index_a = intersector.getHandleA(); - int index_b = intersector.getHandleB(); - int vertex_a = verticesA.get(index_a); - int vertex_b = verticesB.get(index_b); - - segIterA.resetToVertex(vertex_a); - segIterB.resetToVertex(vertex_b); - Segment segmentA = segIterA.nextSegment(); - Segment segmentB = segIterB.nextSegment(); + while (intersector.next()) { + int vertex_a = intersector.get_red_element(); + int vertex_b = intersector.get_blue_element(); - int result = segmentB.intersect(segmentA, null, scalarsB, - scalarsA, tolerance); + segIterA.resetToVertex(vertex_a); + segIterB.resetToVertex(vertex_b); + Segment segmentA = segIterA.nextSegment(); + Segment segmentB = segIterB.nextSegment(); - if (result == 2) { - b_boundaries_intersect = true; - // Keep going to see if we find a proper intersection of two - // segments (means contains is false) - } else if (result != 0) { - double scalar_a_0 = scalarsA[0]; - double scalar_b_0 = scalarsB[0]; + int result = segmentB.intersect(segmentA, null, scalarsB, scalarsA, + tolerance); - if (scalar_a_0 > 0.0 && scalar_a_0 < 1.0 - && scalar_b_0 > 0.0 && scalar_b_0 < 1.0) - return false; + if (result == 2) { + b_boundaries_intersect = true; + // Keep going to see if we find a proper intersection of two + // segments (means contains is false) + } else if (result != 0) { + double scalar_a_0 = scalarsA[0]; + double scalar_b_0 = scalarsB[0]; - b_boundaries_intersect = true; - // Keep going to see if we find a proper intersection of two - // segments (means contains is false) + if (scalar_a_0 > 0.0 && scalar_a_0 < 1.0 && scalar_b_0 > 0.0 + && scalar_b_0 < 1.0) { + return false; } + + b_boundaries_intersect = true; + // Keep going to see if we find a proper intersection of two + // segments (means contains is false) } } @@ -4853,7 +4842,6 @@ private static boolean polygonContainsPolylineImpl_(Polygon polygon_a, } // We can clip polygon_a to the extent of polyline_b - Envelope2D envBInflated = new Envelope2D(); polyline_b.queryEnvelope2D(envBInflated); envBInflated.inflate(1000.0 * tolerance, 1000.0 * tolerance); @@ -4863,14 +4851,15 @@ private static boolean polygonContainsPolylineImpl_(Polygon polygon_a, if (polygon_a.getPointCount() > 10) { _polygonA = (Polygon) (Clipper.clip(polygon_a, envBInflated, tolerance, 0.0)); - if (_polygonA.isEmpty()) + if (_polygonA.isEmpty()) { return false; + } } else { _polygonA = polygon_a; } String scl = "T*****F**"; // If Exterior-Interior is false, then - // Exterior-Boundary is false + // Exterior-Boundary is false boolean bRelation = RelationalOperationsMatrix.polygonRelatePolyline_( _polygonA, polyline_b, tolerance, scl, progress_tracker); @@ -4899,9 +4888,8 @@ private static boolean polygonTouchesPointImpl_(Polygon polygon_a, return false; } - private static boolean multiPointDisjointPointImpl_( - MultiPoint multipoint_a, Point2D pt_b, double tolerance, - ProgressTracker progressTracker) { + static boolean multiPointDisjointPointImpl_(MultiPoint multipoint_a, + Point2D pt_b, double tolerance, ProgressTracker progressTracker) { Point2D pt_a = new Point2D(); double tolerance_sq = tolerance * tolerance; @@ -4990,4 +4978,205 @@ int compareOverlapEvents_(int o_1, int o_2) { return 1; } + + static final class Pair_wise_intersector { + + Pair_wise_intersector(MultiPathImpl multi_path_impl_a, + MultiPathImpl multi_path_impl_b, double tolerance) { + m_b_quad_tree = false; + + GeometryAccelerators geometry_accelerators_a = multi_path_impl_a + ._getAccelerators(); + + if (geometry_accelerators_a != null) { + QuadTreeImpl qtree_a = geometry_accelerators_a.getQuadTree(); + + if (qtree_a != null) { + m_b_done = false; + m_tolerance = tolerance; + m_quad_tree = qtree_a; + m_qt_iter = m_quad_tree.getIterator(); + m_b_quad_tree = true; + m_b_swap_elements = true; + m_seg_iter = multi_path_impl_b.querySegmentIterator(); + m_function = State.next_path; + } + } + + if (!m_b_quad_tree) { + GeometryAccelerators geometry_accelerators_b = multi_path_impl_b + ._getAccelerators(); + + if (geometry_accelerators_b != null) { + QuadTreeImpl qtree_b = geometry_accelerators_b + .getQuadTree(); + + if (qtree_b != null) { + m_b_done = false; + m_tolerance = tolerance; + m_quad_tree = qtree_b; + m_qt_iter = m_quad_tree.getIterator(); + m_b_quad_tree = true; + m_b_swap_elements = false; + m_seg_iter = multi_path_impl_a.querySegmentIterator(); + m_function = State.next_path; + } + } + } + + if (!m_b_quad_tree) { + m_intersector = InternalUtils.getEnvelope2DIntersector( + multi_path_impl_a, multi_path_impl_b, tolerance); + } + } + + boolean next() { + if (m_b_quad_tree) { + if (m_b_done) { + return false; + } + + boolean b_searching = true; + while (b_searching) { + switch (m_function) { + case State.next_path: + b_searching = next_path_(); + break; + case State.next_segment: + b_searching = next_segment_(); + break; + case State.iterate: + b_searching = iterate_(); + break; + } + + } + + if (m_b_done) { + return false; + } + + return true; + } + + if (m_intersector == null) { + return false; + } + + return m_intersector.next(); + } + + int get_red_element() { + if (m_b_quad_tree) { + if (!m_b_swap_elements) { + return m_seg_iter.getStartPointIndex(); + } + + return m_quad_tree.getElement(m_element_handle); + } + + return m_intersector.getRedElement(m_intersector.getHandleA()); + } + + int get_blue_element() { + if (m_b_quad_tree) { + if (m_b_swap_elements) { + return m_seg_iter.getStartPointIndex(); + } + + return m_quad_tree.getElement(m_element_handle); + } + + return m_intersector.getBlueElement(m_intersector.getHandleB()); + } + + private boolean next_path_() { + if (!m_seg_iter.nextPath()) { + m_b_done = true; + return false; + } + + m_function = State.next_segment; + return true; + } + + private boolean next_segment_() { + if (!m_seg_iter.hasNextSegment()) { + m_function = State.next_path; + return true; + } + + Segment segment = m_seg_iter.nextSegment(); + m_qt_iter.resetIterator(segment, m_tolerance); + m_function = State.iterate; + return true; + } + + boolean iterate_() { + m_element_handle = m_qt_iter.next(); + if (m_element_handle == -1) { + m_function = State.next_segment; + return true; + } + + return false; + } + + private interface State { + + static final int next_path = 0; + static final int next_segment = 1; + static final int iterate = 2; + } + + // Quad_tree + private boolean m_b_quad_tree; + private boolean m_b_done; + private boolean m_b_swap_elements; + private double m_tolerance; + private int m_element_handle; + private QuadTreeImpl m_quad_tree; + private QuadTreeImpl.QuadTreeIteratorImpl m_qt_iter; + private SegmentIteratorImpl m_seg_iter; + private int m_function; + + // Envelope_2D_intersector + private Envelope2DIntersectorImpl m_intersector; + } + + static final class Accelerate_helper { + static boolean accelerate_geometry(Geometry geometry, + SpatialReference sr, + Geometry.GeometryAccelerationDegree accel_degree) { + if (!can_accelerate_geometry(geometry)) + return false; + + double tol = InternalUtils.calculateToleranceFromGeometry(sr, + geometry, false); + boolean bAccelerated = false; + if (GeometryAccelerators.canUseRasterizedGeometry(geometry)) + bAccelerated |= ((MultiVertexGeometryImpl) geometry._getImpl()) + ._buildRasterizedGeometryAccelerator(tol, accel_degree); + + Geometry.Type type = geometry.getType(); + if ((type == Geometry.Type.Polygon || type == Geometry.Type.Polyline) + && GeometryAccelerators.canUseQuadTree(geometry) + && accel_degree != Geometry.GeometryAccelerationDegree.enumMild) + bAccelerated |= ((MultiVertexGeometryImpl) geometry._getImpl()) + ._buildQuadTreeAccelerator(accel_degree); + + if (type == Geometry.Type.Polygon + && GeometryAccelerators.canUsePathEnvelopes(geometry) + && accel_degree != Geometry.GeometryAccelerationDegree.enumMild) + bAccelerated |= ((MultiPathImpl) geometry._getImpl()) + ._buildPathEnvelopesAccelerator(accel_degree); + + return bAccelerated; + } + + static boolean can_accelerate_geometry(Geometry geometry) { + return GeometryAccelerators.canUseRasterizedGeometry(geometry) + || GeometryAccelerators.canUseQuadTree(geometry); + } + } } diff --git a/src/main/java/com/esri/core/geometry/RelationalOperationsMatrix.java b/src/main/java/com/esri/core/geometry/RelationalOperationsMatrix.java index 7ed7a024..a3b3f02d 100644 --- a/src/main/java/com/esri/core/geometry/RelationalOperationsMatrix.java +++ b/src/main/java/com/esri/core/geometry/RelationalOperationsMatrix.java @@ -122,7 +122,7 @@ static boolean relate(Geometry geometry_a, Geometry geometry_b, case Geometry.GeometryType.Polygon: bRelation = polygonRelatePolyline_((Polygon) (_geometryB), (Polyline) (_geometryA), tolerance, - transposeMatrix_(scl), progress_tracker); + getTransposeMatrix_(scl), progress_tracker); break; case Geometry.GeometryType.Polyline: @@ -151,14 +151,14 @@ static boolean relate(Geometry geometry_a, Geometry geometry_b, switch (typeB) { case Geometry.GeometryType.Polygon: bRelation = polygonRelatePoint_((Polygon) (_geometryB), - (Point) (_geometryA), tolerance, transposeMatrix_(scl), - progress_tracker); + (Point) (_geometryA), tolerance, + getTransposeMatrix_(scl), progress_tracker); break; case Geometry.GeometryType.Polyline: bRelation = polylineRelatePoint_((Polyline) (_geometryB), - (Point) (_geometryA), tolerance, transposeMatrix_(scl), - progress_tracker); + (Point) (_geometryA), tolerance, + getTransposeMatrix_(scl), progress_tracker); break; case Geometry.GeometryType.Point: @@ -168,8 +168,8 @@ static boolean relate(Geometry geometry_a, Geometry geometry_b, case Geometry.GeometryType.MultiPoint: bRelation = multiPointRelatePoint_((MultiPoint) (_geometryB), - (Point) (_geometryA), tolerance, transposeMatrix_(scl), - progress_tracker); + (Point) (_geometryA), tolerance, + getTransposeMatrix_(scl), progress_tracker); break; default: @@ -182,13 +182,13 @@ static boolean relate(Geometry geometry_a, Geometry geometry_b, case Geometry.GeometryType.Polygon: bRelation = polygonRelateMultiPoint_((Polygon) (_geometryB), (MultiPoint) (_geometryA), tolerance, - transposeMatrix_(scl), progress_tracker); + getTransposeMatrix_(scl), progress_tracker); break; case Geometry.GeometryType.Polyline: bRelation = polylineRelateMultiPoint_((Polyline) (_geometryB), (MultiPoint) (_geometryA), tolerance, - transposeMatrix_(scl), progress_tracker); + getTransposeMatrix_(scl), progress_tracker); break; case Geometry.GeometryType.MultiPoint: @@ -229,13 +229,47 @@ static boolean polygonRelatePolygon_(Polygon polygon_a, Polygon polygon_b, relOps.setPredicates_(scl); relOps.setAreaAreaPredicates_(); - EditShape edit_shape = new EditShape(); - int geom_a = edit_shape.addGeometry(polygon_a); - int geom_b = edit_shape.addGeometry(polygon_b); - relOps.setEditShapeCrackAndCluster_(edit_shape, tolerance, - progress_tracker); - relOps.computeMatrixTopoGraphHalfEdges_(geom_a, geom_b); - relOps.m_topo_graph.removeShape(); + Envelope2D env_a = new Envelope2D(), env_b = new Envelope2D(); + polygon_a.queryEnvelope2D(env_a); + polygon_b.queryEnvelope2D(env_b); + + boolean bRelationKnown = false; + boolean b_disjoint = RelationalOperations.envelopeDisjointEnvelope_( + env_a, env_b, tolerance, progress_tracker); + + if (b_disjoint) { + relOps.areaAreaDisjointPredicates_(); + bRelationKnown = true; + } + + if (!bRelationKnown) { + // Quick rasterize test to see whether the the geometries are + // disjoint, or if one is contained in the other. + int relation = RelationalOperations + .tryRasterizedContainsOrDisjoint_(polygon_a, polygon_b, + tolerance, false); + + if (relation == RelationalOperations.Relation.disjoint) { + relOps.areaAreaDisjointPredicates_(); + bRelationKnown = true; + } else if (relation == RelationalOperations.Relation.contains) { + relOps.areaAreaContainsPredicates_(); + bRelationKnown = true; + } else if (relation == RelationalOperations.Relation.within) { + relOps.areaAreaWithinPredicates_(); + bRelationKnown = true; + } + } + + if (!bRelationKnown) { + EditShape edit_shape = new EditShape(); + int geom_a = edit_shape.addGeometry(polygon_a); + int geom_b = edit_shape.addGeometry(polygon_b); + relOps.setEditShapeCrackAndCluster_(edit_shape, tolerance, + progress_tracker); + relOps.computeMatrixTopoGraphHalfEdges_(geom_a, geom_b); + relOps.m_topo_graph.removeShape(); + } boolean bRelation = relationCompare_(relOps.m_matrix, relOps.m_scl); return bRelation; @@ -250,18 +284,58 @@ static boolean polygonRelatePolyline_(Polygon polygon_a, relOps.setPredicates_(scl); relOps.setAreaLinePredicates_(); - EditShape edit_shape = new EditShape(); - int geom_a = edit_shape.addGeometry(polygon_a); - int geom_b = edit_shape.addGeometry(polyline_b); - relOps.setEditShapeCrackAndCluster_(edit_shape, tolerance, - progress_tracker); - relOps.m_cluster_index_b = relOps.m_topo_graph - .createUserIndexForClusters(); - markClusters_(geom_b, relOps.m_topo_graph, relOps.m_cluster_index_b); - relOps.computeMatrixTopoGraphHalfEdges_(geom_a, geom_b); - relOps.m_topo_graph - .deleteUserIndexForClusters(relOps.m_cluster_index_b); - relOps.m_topo_graph.removeShape(); + Envelope2D env_a = new Envelope2D(), env_b = new Envelope2D(); + polygon_a.queryEnvelope2D(env_a); + polyline_b.queryEnvelope2D(env_b); + + boolean bRelationKnown = false; + boolean b_disjoint = RelationalOperations.envelopeDisjointEnvelope_( + env_a, env_b, tolerance, progress_tracker); + + if (b_disjoint) { + relOps.areaLineDisjointPredicates_(polyline_b); // passing polyline + // to get boundary + // information + bRelationKnown = true; + } + + if (!bRelationKnown) { + // Quick rasterize test to see whether the the geometries are + // disjoint, or if one is contained in the other. + int relation = RelationalOperations + .tryRasterizedContainsOrDisjoint_(polygon_a, polyline_b, + tolerance, false); + + if (relation == RelationalOperations.Relation.disjoint) { + relOps.areaLineDisjointPredicates_(polyline_b); // passing + // polyline to + // get boundary + // information + bRelationKnown = true; + } else if (relation == RelationalOperations.Relation.contains) { + relOps.areaLineContainsPredicates_(polyline_b); // passing + // polyline to + // get boundary + // information + bRelationKnown = true; + } + } + + if (!bRelationKnown) { + EditShape edit_shape = new EditShape(); + int geom_a = edit_shape.addGeometry(polygon_a); + int geom_b = edit_shape.addGeometry(polyline_b); + relOps.setEditShapeCrackAndCluster_(edit_shape, tolerance, + progress_tracker); + relOps.m_cluster_index_b = relOps.m_topo_graph + .createUserIndexForClusters(); + markClusterEndPoints_(geom_b, relOps.m_topo_graph, + relOps.m_cluster_index_b); + relOps.computeMatrixTopoGraphHalfEdges_(geom_a, geom_b); + relOps.m_topo_graph + .deleteUserIndexForClusters(relOps.m_cluster_index_b); + relOps.m_topo_graph.removeShape(); + } boolean bRelation = relationCompare_(relOps.m_matrix, relOps.m_scl); return bRelation; @@ -276,13 +350,44 @@ static boolean polygonRelateMultiPoint_(Polygon polygon_a, relOps.setPredicates_(scl); relOps.setAreaPointPredicates_(); - EditShape edit_shape = new EditShape(); - int geom_a = edit_shape.addGeometry(polygon_a); - int geom_b = edit_shape.addGeometry(multipoint_b); - relOps.setEditShapeCrackAndCluster_(edit_shape, tolerance, - progress_tracker); - relOps.computeMatrixTopoGraphClusters_(geom_a, geom_b); - relOps.m_topo_graph.removeShape(); + Envelope2D env_a = new Envelope2D(), env_b = new Envelope2D(); + polygon_a.queryEnvelope2D(env_a); + multipoint_b.queryEnvelope2D(env_b); + + boolean bRelationKnown = false; + boolean b_disjoint = RelationalOperations.envelopeDisjointEnvelope_( + env_a, env_b, tolerance, progress_tracker); + + if (b_disjoint) { + relOps.areaPointDisjointPredicates_(); + bRelationKnown = true; + } + + if (!bRelationKnown) { + // Quick rasterize test to see whether the the geometries are + // disjoint, or if one is contained in the other. + int relation = RelationalOperations + .tryRasterizedContainsOrDisjoint_(polygon_a, multipoint_b, + tolerance, false); + + if (relation == RelationalOperations.Relation.disjoint) { + relOps.areaPointDisjointPredicates_(); + bRelationKnown = true; + } else if (relation == RelationalOperations.Relation.contains) { + relOps.areaPointContainsPredicates_(); + bRelationKnown = true; + } + } + + if (!bRelationKnown) { + EditShape edit_shape = new EditShape(); + int geom_a = edit_shape.addGeometry(polygon_a); + int geom_b = edit_shape.addGeometry(multipoint_b); + relOps.setEditShapeCrackAndCluster_(edit_shape, tolerance, + progress_tracker); + relOps.computeMatrixTopoGraphClusters_(geom_a, geom_b); + relOps.m_topo_graph.removeShape(); + } boolean bRelation = relationCompare_(relOps.m_matrix, relOps.m_scl); return bRelation; @@ -297,23 +402,53 @@ static boolean polylineRelatePolyline_(Polyline polyline_a, relOps.setPredicates_(scl); relOps.setLineLinePredicates_(); - EditShape edit_shape = new EditShape(); - int geom_a = edit_shape.addGeometry(polyline_a); - int geom_b = edit_shape.addGeometry(polyline_b); - relOps.setEditShapeCrackAndCluster_(edit_shape, tolerance, - progress_tracker); - relOps.m_cluster_index_a = relOps.m_topo_graph - .createUserIndexForClusters(); - relOps.m_cluster_index_b = relOps.m_topo_graph - .createUserIndexForClusters(); - markClusters_(geom_a, relOps.m_topo_graph, relOps.m_cluster_index_a); - markClusters_(geom_b, relOps.m_topo_graph, relOps.m_cluster_index_b); - relOps.computeMatrixTopoGraphHalfEdges_(geom_a, geom_b); - relOps.m_topo_graph - .deleteUserIndexForClusters(relOps.m_cluster_index_a); - relOps.m_topo_graph - .deleteUserIndexForClusters(relOps.m_cluster_index_b); - relOps.m_topo_graph.removeShape(); + Envelope2D env_a = new Envelope2D(), env_b = new Envelope2D(); + polyline_a.queryEnvelope2D(env_a); + polyline_b.queryEnvelope2D(env_b); + + boolean bRelationKnown = false; + boolean b_disjoint = RelationalOperations.envelopeDisjointEnvelope_( + env_a, env_b, tolerance, progress_tracker); + + if (b_disjoint) { + relOps.lineLineDisjointPredicates_(polyline_a, polyline_b); + bRelationKnown = true; + } + + if (!bRelationKnown) { + // Quick rasterize test to see whether the the geometries are + // disjoint, or if one is contained in the other. + int relation = RelationalOperations + .tryRasterizedContainsOrDisjoint_(polyline_a, polyline_b, + tolerance, false); + + if (relation == RelationalOperations.Relation.disjoint) { + relOps.lineLineDisjointPredicates_(polyline_a, polyline_b); + bRelationKnown = true; + } + } + + if (!bRelationKnown) { + EditShape edit_shape = new EditShape(); + int geom_a = edit_shape.addGeometry(polyline_a); + int geom_b = edit_shape.addGeometry(polyline_b); + relOps.setEditShapeCrackAndCluster_(edit_shape, tolerance, + progress_tracker); + relOps.m_cluster_index_a = relOps.m_topo_graph + .createUserIndexForClusters(); + relOps.m_cluster_index_b = relOps.m_topo_graph + .createUserIndexForClusters(); + markClusterEndPoints_(geom_a, relOps.m_topo_graph, + relOps.m_cluster_index_a); + markClusterEndPoints_(geom_b, relOps.m_topo_graph, + relOps.m_cluster_index_b); + relOps.computeMatrixTopoGraphHalfEdges_(geom_a, geom_b); + relOps.m_topo_graph + .deleteUserIndexForClusters(relOps.m_cluster_index_a); + relOps.m_topo_graph + .deleteUserIndexForClusters(relOps.m_cluster_index_b); + relOps.m_topo_graph.removeShape(); + } boolean bRelation = relationCompare_(relOps.m_matrix, relOps.m_scl); return bRelation; @@ -328,18 +463,47 @@ static boolean polylineRelateMultiPoint_(Polyline polyline_a, relOps.setPredicates_(scl); relOps.setLinePointPredicates_(); - EditShape edit_shape = new EditShape(); - int geom_a = edit_shape.addGeometry(polyline_a); - int geom_b = edit_shape.addGeometry(multipoint_b); - relOps.setEditShapeCrackAndCluster_(edit_shape, tolerance, - progress_tracker); - relOps.m_cluster_index_a = relOps.m_topo_graph - .createUserIndexForClusters(); - markClusters_(geom_a, relOps.m_topo_graph, relOps.m_cluster_index_a); - relOps.computeMatrixTopoGraphClusters_(geom_a, geom_b); - relOps.m_topo_graph - .deleteUserIndexForClusters(relOps.m_cluster_index_a); - relOps.m_topo_graph.removeShape(); + Envelope2D env_a = new Envelope2D(), env_b = new Envelope2D(); + polyline_a.queryEnvelope2D(env_a); + multipoint_b.queryEnvelope2D(env_b); + + boolean bRelationKnown = false; + boolean b_disjoint = RelationalOperations.envelopeDisjointEnvelope_( + env_a, env_b, tolerance, progress_tracker); + + if (b_disjoint) { + relOps.linePointDisjointPredicates_(polyline_a); + bRelationKnown = true; + } + + if (!bRelationKnown) { + // Quick rasterize test to see whether the the geometries are + // disjoint, or if one is contained in the other. + int relation = RelationalOperations + .tryRasterizedContainsOrDisjoint_(polyline_a, multipoint_b, + tolerance, false); + + if (relation == RelationalOperations.Relation.disjoint) { + relOps.linePointDisjointPredicates_(polyline_a); + bRelationKnown = true; + } + } + + if (!bRelationKnown) { + EditShape edit_shape = new EditShape(); + int geom_a = edit_shape.addGeometry(polyline_a); + int geom_b = edit_shape.addGeometry(multipoint_b); + relOps.setEditShapeCrackAndCluster_(edit_shape, tolerance, + progress_tracker); + relOps.m_cluster_index_a = relOps.m_topo_graph + .createUserIndexForClusters(); + markClusterEndPoints_(geom_a, relOps.m_topo_graph, + relOps.m_cluster_index_a); + relOps.computeMatrixTopoGraphClusters_(geom_a, geom_b); + relOps.m_topo_graph + .deleteUserIndexForClusters(relOps.m_cluster_index_a); + relOps.m_topo_graph.removeShape(); + } boolean bRelation = relationCompare_(relOps.m_matrix, relOps.m_scl); return bRelation; @@ -354,13 +518,28 @@ static boolean multiPointRelateMultiPoint_(MultiPoint multipoint_a, relOps.setPredicates_(scl); relOps.setPointPointPredicates_(); - EditShape edit_shape = new EditShape(); - int geom_a = edit_shape.addGeometry(multipoint_a); - int geom_b = edit_shape.addGeometry(multipoint_b); - relOps.setEditShapeCrackAndCluster_(edit_shape, tolerance, - progress_tracker); - relOps.computeMatrixTopoGraphClusters_(geom_a, geom_b); - relOps.m_topo_graph.removeShape(); + Envelope2D env_a = new Envelope2D(), env_b = new Envelope2D(); + multipoint_a.queryEnvelope2D(env_a); + multipoint_b.queryEnvelope2D(env_b); + + boolean bRelationKnown = false; + boolean b_disjoint = RelationalOperations.envelopeDisjointEnvelope_( + env_a, env_b, tolerance, progress_tracker); + + if (b_disjoint) { + relOps.pointPointDisjointPredicates_(); + bRelationKnown = true; + } + + if (!bRelationKnown) { + EditShape edit_shape = new EditShape(); + int geom_a = edit_shape.addGeometry(multipoint_a); + int geom_b = edit_shape.addGeometry(multipoint_b); + relOps.setEditShapeCrackAndCluster_(edit_shape, tolerance, + progress_tracker); + relOps.computeMatrixTopoGraphClusters_(geom_a, geom_b); + relOps.m_topo_graph.removeShape(); + } boolean bRelation = relationCompare_(relOps.m_matrix, relOps.m_scl); return bRelation; @@ -369,27 +548,44 @@ static boolean multiPointRelateMultiPoint_(MultiPoint multipoint_a, // Returns true if the relation holds. static boolean polygonRelatePoint_(Polygon polygon_a, Point point_b, double tolerance, String scl, ProgressTracker progress_tracker) { - Point2D pt_b = point_b.getXY(); - int[] matrix = new int[9]; - - for (int i = 0; i < 8; i++) - matrix[i] = -1; - - PolygonUtils.PiPResult res = PolygonUtils.isPointInPolygon2D(polygon_a, - pt_b, tolerance); + RelationalOperationsMatrix relOps = new RelationalOperationsMatrix(); + relOps.resetMatrix_(); + relOps.setPredicates_(scl); + relOps.setAreaPointPredicates_(); - if (res == PolygonUtils.PiPResult.PiPInside) - matrix[MatrixPredicate.InteriorInterior] = 0; - else if (res == PolygonUtils.PiPResult.PiPBoundary) - matrix[MatrixPredicate.BoundaryInterior] = 0; - else - matrix[MatrixPredicate.ExteriorInterior] = 0; + Envelope2D env_a = new Envelope2D(); + polygon_a.queryEnvelope2D(env_a); + Point2D pt_b = point_b.getXY(); - matrix[MatrixPredicate.InteriorExterior] = 2; - matrix[MatrixPredicate.BoundaryExterior] = 1; - matrix[MatrixPredicate.ExteriorExterior] = 2; + boolean bRelationKnown = false; + boolean b_disjoint = RelationalOperations.pointDisjointEnvelope_(pt_b, + env_a, tolerance, progress_tracker); + + if (b_disjoint) { + relOps.areaPointDisjointPredicates_(); + bRelationKnown = true; + } + + if (!bRelationKnown) { + PolygonUtils.PiPResult res = PolygonUtils.isPointInPolygon2D( + polygon_a, pt_b, tolerance); // uses accelerator + + if (res == PolygonUtils.PiPResult.PiPInside) { + relOps.m_matrix[MatrixPredicate.InteriorInterior] = 0; + relOps.m_matrix[MatrixPredicate.BoundaryInterior] = -1; + relOps.m_matrix[MatrixPredicate.ExteriorInterior] = -1; + } else if (res == PolygonUtils.PiPResult.PiPBoundary) { + relOps.m_matrix[MatrixPredicate.InteriorInterior] = -1; + relOps.m_matrix[MatrixPredicate.BoundaryInterior] = 0; + relOps.m_matrix[MatrixPredicate.ExteriorInterior] = -1; + } else { + relOps.m_matrix[MatrixPredicate.InteriorInterior] = -1; + relOps.m_matrix[MatrixPredicate.ExteriorInterior] = 0; + relOps.m_matrix[MatrixPredicate.ExteriorInterior] = -1; + } + } - boolean bRelation = relationCompare_(matrix, scl); + boolean bRelation = relationCompare_(relOps.m_matrix, scl); return bRelation; } @@ -401,21 +597,92 @@ static boolean polylineRelatePoint_(Polyline polyline_a, Point point_b, relOps.setPredicates_(scl); relOps.setLinePointPredicates_(); - MultiPoint multipoint_b = new MultiPoint(); - multipoint_b.add(point_b); - - EditShape edit_shape = new EditShape(); - int geom_a = edit_shape.addGeometry(polyline_a); - int geom_b = edit_shape.addGeometry(multipoint_b); - relOps.setEditShapeCrackAndCluster_(edit_shape, tolerance, - progress_tracker); - relOps.m_cluster_index_a = relOps.m_topo_graph - .createUserIndexForClusters(); - markClusters_(geom_a, relOps.m_topo_graph, relOps.m_cluster_index_a); - relOps.computeMatrixTopoGraphClusters_(geom_a, geom_b); - relOps.m_topo_graph - .deleteUserIndexForClusters(relOps.m_cluster_index_a); - relOps.m_topo_graph.removeShape(); + Envelope2D env_a = new Envelope2D(); + polyline_a.queryEnvelope2D(env_a); + Point2D pt_b = point_b.getXY(); + + boolean bRelationKnown = false; + boolean b_disjoint = RelationalOperations.pointDisjointEnvelope_(pt_b, + env_a, tolerance, progress_tracker); + + if (b_disjoint) { + relOps.linePointDisjointPredicates_(polyline_a); + bRelationKnown = true; + } + + if (!bRelationKnown) { + MultiPoint boundary_a = null; + boolean b_boundary_contains_point_known = false; + boolean b_boundary_contains_point = false; + + if (relOps.m_perform_predicates[MatrixPredicate.InteriorInterior] + || relOps.m_perform_predicates[MatrixPredicate.ExteriorInterior]) { + boolean b_intersects = RelationalOperations + .linearPathIntersectsPoint_(polyline_a, pt_b, tolerance); + + if (b_intersects) { + if (relOps.m_perform_predicates[MatrixPredicate.InteriorInterior]) { + boundary_a = (MultiPoint) Boundary.calculate( + polyline_a, progress_tracker); + b_boundary_contains_point = !RelationalOperations + .multiPointDisjointPointImpl_(boundary_a, pt_b, + tolerance, progress_tracker); + b_boundary_contains_point_known = true; + + if (b_boundary_contains_point) + relOps.m_matrix[MatrixPredicate.InteriorInterior] = -1; + else + relOps.m_matrix[MatrixPredicate.InteriorInterior] = 0; + } + + relOps.m_matrix[MatrixPredicate.ExteriorInterior] = -1; + } else { + relOps.m_matrix[MatrixPredicate.InteriorInterior] = -1; + relOps.m_matrix[MatrixPredicate.ExteriorInterior] = 0; + } + } + + if (relOps.m_perform_predicates[MatrixPredicate.BoundaryInterior]) { + if (boundary_a != null && boundary_a.isEmpty()) { + relOps.m_matrix[MatrixPredicate.BoundaryInterior] = -1; + } else { + if (!b_boundary_contains_point_known) { + if (boundary_a == null) + boundary_a = (MultiPoint) Boundary.calculate( + polyline_a, progress_tracker); + + b_boundary_contains_point = !RelationalOperations + .multiPointDisjointPointImpl_(boundary_a, pt_b, + tolerance, progress_tracker); + b_boundary_contains_point_known = true; + } + + relOps.m_matrix[MatrixPredicate.BoundaryInterior] = (b_boundary_contains_point ? 0 + : -1); + } + } + + if (relOps.m_perform_predicates[MatrixPredicate.BoundaryExterior]) { + if (boundary_a != null && boundary_a.isEmpty()) { + relOps.m_matrix[MatrixPredicate.BoundaryExterior] = -1; + } else { + if (b_boundary_contains_point_known + && !b_boundary_contains_point) { + relOps.m_matrix[MatrixPredicate.BoundaryExterior] = 0; + } else { + if (boundary_a == null) + boundary_a = (MultiPoint) Boundary.calculate( + polyline_a, progress_tracker); + + boolean b_boundary_equals_point = RelationalOperations + .multiPointEqualsPoint_(boundary_a, point_b, + tolerance, progress_tracker); + relOps.m_matrix[MatrixPredicate.BoundaryExterior] = (b_boundary_equals_point ? -1 + : 0); + } + } + } + } boolean bRelation = relationCompare_(relOps.m_matrix, relOps.m_scl); return bRelation; @@ -425,43 +692,58 @@ static boolean polylineRelatePoint_(Polyline polyline_a, Point point_b, static boolean multiPointRelatePoint_(MultiPoint multipoint_a, Point point_b, double tolerance, String scl, ProgressTracker progress_tracker) { + RelationalOperationsMatrix relOps = new RelationalOperationsMatrix(); + relOps.resetMatrix_(); + relOps.setPredicates_(scl); + relOps.setPointPointPredicates_(); + + Envelope2D env_a = new Envelope2D(); + multipoint_a.queryEnvelope2D(env_a); Point2D pt_b = point_b.getXY(); - int[] matrix = new int[9]; - for (int i = 0; i < 8; i++) - matrix[i] = -1; + boolean bRelationKnown = false; + boolean b_disjoint = RelationalOperations.pointDisjointEnvelope_(pt_b, + env_a, tolerance, progress_tracker); - boolean b_intersects = false; - boolean b_multipoint_contained = true; - double tolerance_sq = tolerance * tolerance; - Point2D pt_a = new Point2D(); + if (b_disjoint) { + relOps.pointPointDisjointPredicates_(); + bRelationKnown = true; + } - for (int i = 0; i < multipoint_a.getPointCount(); i++) { - multipoint_a.getXY(i, pt_a); + if (!bRelationKnown) { + boolean b_intersects = false; + boolean b_multipoint_contained = true; + double tolerance_sq = tolerance * tolerance; - if (Point2D.sqrDistance(pt_a, pt_b) <= tolerance_sq) { - b_intersects = true; - } else { - b_multipoint_contained = false; + for (int i = 0; i < multipoint_a.getPointCount(); i++) { + Point2D pt_a = multipoint_a.getXY(i); + + if (Point2D.sqrDistance(pt_a, pt_b) <= tolerance_sq) + b_intersects = true; + else + b_multipoint_contained = false; + + if (b_intersects && !b_multipoint_contained) + break; } - if (b_intersects && !b_multipoint_contained) - break; - } + if (b_intersects) { + relOps.m_matrix[MatrixPredicate.InteriorInterior] = 0; - if (b_intersects) { - matrix[MatrixPredicate.InteriorInterior] = 0; + if (!b_multipoint_contained) + relOps.m_matrix[MatrixPredicate.InteriorExterior] = 0; + else + relOps.m_matrix[MatrixPredicate.InteriorExterior] = -1; - if (!b_multipoint_contained) - matrix[MatrixPredicate.InteriorExterior] = 0; - } else { - matrix[MatrixPredicate.InteriorExterior] = 0; - matrix[MatrixPredicate.ExteriorInterior] = 0; + relOps.m_matrix[MatrixPredicate.ExteriorInterior] = -1; + } else { + relOps.m_matrix[MatrixPredicate.InteriorInterior] = -1; + relOps.m_matrix[MatrixPredicate.InteriorExterior] = 0; + relOps.m_matrix[MatrixPredicate.ExteriorInterior] = 0; + } } - matrix[MatrixPredicate.ExteriorExterior] = 2; - - boolean bRelation = relationCompare_(matrix, scl); + boolean bRelation = relationCompare_(relOps.m_matrix, scl); return bRelation; } @@ -472,7 +754,7 @@ static boolean pointRelatePoint_(Point point_a, Point point_b, Point2D pt_b = point_b.getXY(); int[] matrix = new int[9]; - for (int i = 1; i < 8; i++) + for (int i = 0; i < 9; i++) matrix[i] = -1; if (Point2D.sqrDistance(pt_a, pt_b) <= tolerance * tolerance) { @@ -521,6 +803,9 @@ private static boolean relationCompare_(int[] matrix, String scl) { if (matrix[i] != 2) return false; break; + + default: + break; } } @@ -684,8 +969,8 @@ private static boolean overlaps_(String scl, int dim_a, int dim_b) { // Marks each cluster of the topoGraph as belonging to an interior vertex of // the geometry and/or a boundary index of the geometry. - private static void markClusters_(int geometry, TopoGraph topoGraph, - int clusterIndex) { + private static void markClusterEndPoints_(int geometry, + TopoGraph topoGraph, int clusterIndex) { EditShape edit_shape = topoGraph.getShape(); for (int path = edit_shape.getFirstPath(geometry); path != -1; path = edit_shape @@ -718,7 +1003,7 @@ private static void markClusters_(int geometry, TopoGraph topoGraph, } } - private static String transposeMatrix_(String scl) { + private static String getTransposeMatrix_(String scl) { String transpose = new String(); transpose += scl.charAt(0); transpose += scl.charAt(3); @@ -743,6 +1028,20 @@ private void resetMatrix_() { m_matrix[i] = -2; } + private void transposeMatrix_() { + int temp1 = m_matrix[1]; + int temp2 = m_matrix[2]; + int temp5 = m_matrix[5]; + + m_matrix[1] = m_matrix[3]; + m_matrix[2] = m_matrix[6]; + m_matrix[5] = m_matrix[7]; + + m_matrix[3] = temp1; + m_matrix[6] = temp2; + m_matrix[7] = temp5; + } + // Sets the relation predicates from the scl string. private void setPredicates_(String scl) { m_scl = scl; @@ -770,6 +1069,9 @@ private void setRemainingPredicatesToFalse_() { private boolean isPredicateKnown_(int predicate, int dim) { assert (m_scl.charAt(predicate) != '*'); + if (m_matrix[predicate] == -2) + return false; + if (m_matrix[predicate] == -1) { m_perform_predicates[predicate] = false; m_predicate_count--; @@ -785,13 +1087,9 @@ private boolean isPredicateKnown_(int predicate, int dim) { return true; } } else { - if (m_matrix[predicate] == -2) { - return false; - } else { - m_perform_predicates[predicate] = false; - m_predicate_count--; - return true; - } + m_perform_predicates[predicate] = false; + m_predicate_count--; + return true; } } @@ -1018,6 +1316,129 @@ private boolean areaAreaPredicates_(int half_edge, int id_a, int id_b) { return bRelationKnown; } + private void areaAreaDisjointPredicates_() { + m_matrix[MatrixPredicate.InteriorInterior] = -1; + m_matrix[MatrixPredicate.InteriorBoundary] = -1; + m_matrix[MatrixPredicate.InteriorExterior] = 2; + m_matrix[MatrixPredicate.BoundaryInterior] = -1; + m_matrix[MatrixPredicate.BoundaryBoundary] = -1; + m_matrix[MatrixPredicate.BoundaryExterior] = 1; + m_matrix[MatrixPredicate.ExteriorInterior] = 2; + m_matrix[MatrixPredicate.ExteriorBoundary] = 1; + + // all other predicates should already be set by + // set_area_area_predicates + } + + private void areaAreaContainsPredicates_() { + m_matrix[MatrixPredicate.InteriorInterior] = 2; + m_matrix[MatrixPredicate.InteriorBoundary] = 1; + m_matrix[MatrixPredicate.InteriorExterior] = 2; + m_matrix[MatrixPredicate.BoundaryInterior] = -1; + m_matrix[MatrixPredicate.BoundaryBoundary] = -1; + m_matrix[MatrixPredicate.BoundaryExterior] = 1; + m_matrix[MatrixPredicate.ExteriorInterior] = -1; + m_matrix[MatrixPredicate.ExteriorBoundary] = -1; + + // all other predicates should already be set by + // set_area_area_predicates + } + + private void areaAreaWithinPredicates_() { + areaAreaContainsPredicates_(); + transposeMatrix_(); + } + + private void areaLineDisjointPredicates_(Polyline polyline) { + m_matrix[MatrixPredicate.InteriorInterior] = -1; + m_matrix[MatrixPredicate.InteriorBoundary] = -1; + m_matrix[MatrixPredicate.BoundaryInterior] = -1; + m_matrix[MatrixPredicate.BoundaryBoundary] = -1; + m_matrix[MatrixPredicate.BoundaryExterior] = 1; + m_matrix[MatrixPredicate.ExteriorInterior] = 1; + + if (m_perform_predicates[MatrixPredicate.ExteriorBoundary]) { + boolean has_non_empty_boundary = Boundary.hasNonEmptyBoundary( + polyline, null); + m_matrix[MatrixPredicate.ExteriorBoundary] = (has_non_empty_boundary ? 0 + : -1); + } + } + + private void areaLineContainsPredicates_(Polyline polyline) { + m_matrix[MatrixPredicate.InteriorInterior] = 1; + + if (m_perform_predicates[MatrixPredicate.InteriorBoundary]) { + boolean has_non_empty_boundary = Boundary.hasNonEmptyBoundary( + polyline, null); + m_matrix[MatrixPredicate.InteriorBoundary] = (has_non_empty_boundary ? 0 + : -1); + } + + m_matrix[MatrixPredicate.BoundaryInterior] = -1; + m_matrix[MatrixPredicate.BoundaryBoundary] = -1; + m_matrix[MatrixPredicate.BoundaryExterior] = 1; + m_matrix[MatrixPredicate.ExteriorInterior] = -1; + m_matrix[MatrixPredicate.ExteriorBoundary] = -1; + } + + private void areaPointDisjointPredicates_() { + m_matrix[MatrixPredicate.InteriorInterior] = -1; + m_matrix[MatrixPredicate.BoundaryInterior] = -1; + m_matrix[MatrixPredicate.ExteriorInterior] = 0; + } + + private void areaPointContainsPredicates_() { + m_matrix[MatrixPredicate.InteriorInterior] = 0; + m_matrix[MatrixPredicate.BoundaryInterior] = -1; + m_matrix[MatrixPredicate.ExteriorInterior] = -1; + } + + private void lineLineDisjointPredicates_(Polyline polyline_a, + Polyline polyline_b) { + m_matrix[MatrixPredicate.InteriorInterior] = -1; + m_matrix[MatrixPredicate.InteriorBoundary] = -1; + m_matrix[MatrixPredicate.InteriorExterior] = 1; + m_matrix[MatrixPredicate.BoundaryInterior] = -1; + m_matrix[MatrixPredicate.BoundaryBoundary] = -1; + + if (m_perform_predicates[MatrixPredicate.BoundaryExterior]) { + boolean has_non_empty_boundary_a = Boundary.hasNonEmptyBoundary( + polyline_a, null); + m_matrix[MatrixPredicate.BoundaryExterior] = (has_non_empty_boundary_a ? 0 + : -1); + } + + m_matrix[MatrixPredicate.ExteriorInterior] = 1; + + if (m_perform_predicates[MatrixPredicate.ExteriorBoundary]) { + boolean has_non_empty_boundary_b = Boundary.hasNonEmptyBoundary( + polyline_b, null); + m_matrix[MatrixPredicate.ExteriorBoundary] = (has_non_empty_boundary_b ? 0 + : -1); + } + } + + private void linePointDisjointPredicates_(Polyline polyline) { + m_matrix[MatrixPredicate.InteriorInterior] = -1; + m_matrix[MatrixPredicate.BoundaryInterior] = -1; + + if (m_perform_predicates[MatrixPredicate.BoundaryExterior]) { + boolean has_non_empty_boundary = Boundary.hasNonEmptyBoundary( + polyline, null); + m_matrix[MatrixPredicate.BoundaryExterior] = (has_non_empty_boundary ? 0 + : -1); + } + + m_matrix[MatrixPredicate.ExteriorInterior] = 0; + } + + private void pointPointDisjointPredicates_() { + m_matrix[MatrixPredicate.InteriorInterior] = -1; + m_matrix[MatrixPredicate.InteriorExterior] = 0; + m_matrix[MatrixPredicate.ExteriorInterior] = 0; + } + // Invokes the 9 relational predicates of area vs Line. private boolean areaLinePredicates_(int half_edge, int id_a, int id_b) { boolean bRelationKnown = true; @@ -1792,7 +2213,7 @@ private void computeMatrixTopoGraphHalfEdges_(int geometry_a, int geometry_b) { id_a, id_b); break; default: - throw new GeometryException("internal error"); + throw GeometryException.GeometryInternalError(); } if (bRelationKnown) @@ -1844,7 +2265,7 @@ private void computeMatrixTopoGraphClusters_(int geometry_a, int geometry_b) { bRelationKnown = pointPointPredicates_(cluster, id_a, id_b); break; default: - throw new GeometryException("internal error"); + throw GeometryException.GeometryInternalError(); } if (bRelationKnown) @@ -1857,21 +2278,25 @@ private void computeMatrixTopoGraphClusters_(int geometry_a, int geometry_b) { // Call this method to set the edit shape, if the edit shape has been // cracked and clustered already. - private void setEditShape_(EditShape shape) { - m_topo_graph.setEditShape(shape, null); + private void setEditShape_(EditShape shape, ProgressTracker progressTracker) { + m_topo_graph.setEditShape(shape, progressTracker); } - // Call this method to set the edit shape, if the edit shape has not been - // cracked and clustered already. private void setEditShapeCrackAndCluster_(EditShape shape, double tolerance, ProgressTracker progress_tracker) { + editShapeCrackAndCluster_(shape, tolerance, progress_tracker); + setEditShape_(shape, progress_tracker); + } + + private void editShapeCrackAndCluster_(EditShape shape, double tolerance, + ProgressTracker progress_tracker) { CrackAndCluster.execute(shape, tolerance, progress_tracker); for (int geometry = shape.getFirstGeometry(); geometry != -1; geometry = shape .getNextGeometry(geometry)) { - if (shape.getGeometryType(geometry) == Geometry.GeometryType.Polygon) - Simplificator.execute(shape, geometry, -1); + if (shape.getGeometryType(geometry) == Geometry.Type.Polygon + .value()) + Simplificator.execute(shape, geometry, -1, false); } - setEditShape_(shape); } // Upgrades the geometry to a feature geometry. diff --git a/src/main/java/com/esri/core/geometry/RingOrientationFixer.java b/src/main/java/com/esri/core/geometry/RingOrientationFixer.java index 21ed6606..970ebe3e 100644 --- a/src/main/java/com/esri/core/geometry/RingOrientationFixer.java +++ b/src/main/java/com/esri/core/geometry/RingOrientationFixer.java @@ -35,6 +35,7 @@ class RingOrientationFixer { int m_node_2_user_index; int m_path_orientation_index; int m_path_parentage_index; + boolean m_fixSelfTangency; static final class Edges { EditShape m_shape; @@ -221,6 +222,11 @@ void reset() { } boolean fixRingOrientation_() { + boolean bFound = false; + + if (m_fixSelfTangency) + bFound = fixRingSelfTangency_(); + if (m_shape.getPathCount(m_geometry) == 1) { int path = m_shape.getFirstPath(m_geometry); double area = m_shape.getRingArea(path); @@ -257,7 +263,6 @@ boolean fixRingOrientation_() { } AttributeStreamOfInt32 bunch = new AttributeStreamOfInt32(0); - boolean bFound = false; m_y_scanline = NumberUtils.TheNaN; Point2D pt = new Point2D(); m_unknown_ring_orientation_count = m_shape.getPathCount(m_geometry); @@ -518,12 +523,163 @@ boolean insertEdge_(int vertex, int reused_node) { } static boolean execute(EditShape shape, int geometry, - IndexMultiDCList sorted_vertices) { + IndexMultiDCList sorted_vertices, boolean fixSelfTangency) { RingOrientationFixer fixer = new RingOrientationFixer(); fixer.m_shape = shape; fixer.m_geometry = geometry; fixer.m_sorted_vertices = sorted_vertices; + fixer.m_fixSelfTangency = fixSelfTangency; return fixer.fixRingOrientation_(); } + boolean fixRingSelfTangency_() { + AttributeStreamOfInt32 self_tangent_paths = new AttributeStreamOfInt32( + 0); + AttributeStreamOfInt32 self_tangency_clusters = new AttributeStreamOfInt32( + 0); + int tangent_path_first_vertex_index = -1; + int tangent_vertex_cluster_index = -1; + Point2D pt_prev = new Point2D(); + pt_prev.setNaN(); + int prev_vertex = -1; + int old_path = -1; + int current_cluster = -1; + Point2D pt = new Point2D(); + for (int ivertex = m_sorted_vertices.getFirst(m_sorted_vertices + .getFirstList()); ivertex != -1; ivertex = m_sorted_vertices + .getNext(ivertex)) { + int vertex = m_sorted_vertices.getData(ivertex); + m_shape.getXY(vertex, pt); + int path = m_shape.getPathFromVertex(vertex); + if (pt_prev.isEqual(pt) && old_path == path) { + if (tangent_vertex_cluster_index == -1) { + tangent_path_first_vertex_index = m_shape + .createPathUserIndex(); + tangent_vertex_cluster_index = m_shape.createUserIndex(); + } + + if (current_cluster == -1) { + current_cluster = self_tangency_clusters.size(); + m_shape.setUserIndex(prev_vertex, + tangent_vertex_cluster_index, current_cluster); + self_tangency_clusters.add(1); + int p = m_shape.getPathUserIndex(path, + tangent_path_first_vertex_index); + if (p == -1) { + m_shape.setPathUserIndex(path, + tangent_path_first_vertex_index, prev_vertex); + self_tangent_paths.add(path); + } + } + + m_shape.setUserIndex(vertex, tangent_vertex_cluster_index, + current_cluster); + self_tangency_clusters + .setLast(self_tangency_clusters.getLast() + 1); + } else { + current_cluster = -1; + pt_prev.setCoords(pt); + } + + prev_vertex = vertex; + old_path = path; + } + + if (self_tangent_paths.size() == 0) + return false; + + // Now self_tangent_paths contains list of clusters of tangency for each + // path. + // The clusters contains list of clusters and for each cluster it + // contains a list of vertices. + AttributeStreamOfInt32 vertex_stack = new AttributeStreamOfInt32(0); + AttributeStreamOfInt32 cluster_stack = new AttributeStreamOfInt32(0); + + for (int ipath = 0, npath = self_tangent_paths.size(); ipath < npath; ipath++) { + int path = self_tangent_paths.get(ipath); + int first_vertex = m_shape.getPathUserIndex(path, + tangent_path_first_vertex_index); + int cluster = m_shape.getUserIndex(first_vertex, + tangent_vertex_cluster_index); + vertex_stack.clear(false); + cluster_stack.clear(false); + vertex_stack.add(first_vertex); + cluster_stack.add(cluster); + + for (int vertex = m_shape.getNextVertex(first_vertex); vertex != first_vertex; vertex = m_shape + .getNextVertex(vertex)) { + int vertex_to = vertex; + int cluster_to = m_shape.getUserIndex(vertex_to, + tangent_vertex_cluster_index); + if (cluster_to != -1) { + if (cluster_stack.size() == 0) { + cluster_stack.add(cluster_to); + vertex_stack.add(vertex_to); + continue; + } + + if (cluster_stack.getLast() == cluster_to) { + int vertex_from = vertex_stack.getLast(); + + // peel the loop from path + int from_next = m_shape.getNextVertex(vertex_from); + int from_prev = m_shape.getPrevVertex(vertex_from); + int to_next = m_shape.getNextVertex(vertex_to); + int to_prev = m_shape.getPrevVertex(vertex_to); + + m_shape.setNextVertex_(vertex_from, to_next); + m_shape.setPrevVertex_(to_next, vertex_from); + + m_shape.setNextVertex_(vertex_to, from_next); + m_shape.setPrevVertex_(from_next, vertex_to); + + // vertex_from is left in the path we are processing, + // while the vertex_to is in the loop being teared off. + boolean[] first_vertex_correction_requied = new boolean[] { false }; + int new_path = m_shape.insertClosedPath_(m_geometry, + -1, from_next, m_shape.getFirstVertex(path), + first_vertex_correction_requied); + + m_shape.setUserIndex(vertex, + tangent_vertex_cluster_index, -1); + + // Fix the path after peeling if the peeled loop had the + // first path vertex in it + + if (first_vertex_correction_requied[0]) { + m_shape.setFirstVertex_(path, to_next); + } + + int path_size = m_shape.getPathSize(path); + int new_path_size = m_shape.getPathSize(new_path); + path_size -= new_path_size; + assert (path_size >= 3); + m_shape.setPathSize_(path, path_size); + + self_tangency_clusters.set(cluster_to, + self_tangency_clusters.get(cluster_to) - 1); + if (self_tangency_clusters.get(cluster_to) == 1) { + self_tangency_clusters.set(cluster_to, 0); + cluster_stack.removeLast(); + vertex_stack.removeLast(); + } else { + // this cluster has more than two vertices in it. + } + + first_vertex = vertex_from;// reset the counter to + // ensure we find all loops. + vertex = vertex_from; + } else { + vertex_stack.add(vertex); + cluster_stack.add(cluster_to); + } + } + } + } + + m_shape.removePathUserIndex(tangent_path_first_vertex_index); + m_shape.removeUserIndex(tangent_vertex_cluster_index); + return true; + } + } diff --git a/src/main/java/com/esri/core/geometry/Segment.java b/src/main/java/com/esri/core/geometry/Segment.java index a36ab086..b80a4236 100644 --- a/src/main/java/com/esri/core/geometry/Segment.java +++ b/src/main/java/com/esri/core/geometry/Segment.java @@ -748,9 +748,9 @@ int _isIntersecting(Segment other, double tolerance, return Line._isIntersectingLineLine((Line) this, (Line) other, tolerance, bExcludeExactEndpoints); else - throw new GeometryException("internal error"); + throw GeometryException.GeometryInternalError(); default: - throw new GeometryException("internal error"); + throw GeometryException.GeometryInternalError(); } } @@ -764,9 +764,9 @@ int _intersect(Segment other, Point2D[] intersectionPoints, return Line._intersectLineLine((Line) this, (Line) other, intersectionPoints, paramThis, paramOther, tolerance); else - throw new GeometryException("internal error"); + throw GeometryException.GeometryInternalError(); default: - throw new GeometryException("internal error"); + throw GeometryException.GeometryInternalError(); } } @@ -877,7 +877,9 @@ void _reverseImpl() { abstract boolean _isDegenerate(double tolerance); - abstract double _calculateSubLength(double t); + double _calculateSubLength(double t) { return tToLength(t); } + + double _calculateSubLength(double t1, double t2) { return tToLength(t2) - tToLength(t1); } abstract void _copyToImpl(Segment dst); @@ -917,6 +919,13 @@ abstract boolean _isIntersectingPoint(Point2D pt, double tolerance, * @return X coordinate of the intersection, or NaN, if no intersection. */ abstract double intersectionOfYMonotonicWithAxisX(double y, double xParallel); + + /** + * Converts curves parameter t to the curve length. Can be expensive for curves. + */ + abstract double tToLength(double t); + + abstract double lengthToT(double len); double distance(/* const */Segment otherSegment, boolean bSegmentsKnownDisjoint) /* const */ @@ -963,5 +972,11 @@ && _isIntersecting(otherSegment, 0, false) != 0) { minDistance = distance; return minDistance; - } + } + + public Geometry getBoundary() { + return Boundary.calculate(this, null); + } + + } diff --git a/src/main/java/com/esri/core/geometry/SegmentBuffer.java b/src/main/java/com/esri/core/geometry/SegmentBuffer.java index 10934a64..17df913d 100644 --- a/src/main/java/com/esri/core/geometry/SegmentBuffer.java +++ b/src/main/java/com/esri/core/geometry/SegmentBuffer.java @@ -55,9 +55,17 @@ public void set(Segment seg) { Line ln = (Line) seg; m_line = ln; } - throw new GeometryException("internal error"); + throw GeometryException.GeometryInternalError(); } } + + public void create(Geometry.Type type) + { + if (type == Geometry.Type.Line) + createLine(); + else + throw new GeometryException("not implemented"); + } public void createLine() { if (null == m_line) { diff --git a/src/main/java/com/esri/core/geometry/SegmentIntersector.java b/src/main/java/com/esri/core/geometry/SegmentIntersector.java index 794529fd..ba82fd2f 100644 --- a/src/main/java/com/esri/core/geometry/SegmentIntersector.java +++ b/src/main/java/com/esri/core/geometry/SegmentIntersector.java @@ -195,7 +195,7 @@ public Point getResultPoint() { // Performs the intersection public boolean intersect(double tolerance, boolean b_intersecting) { if (m_input_segments.size() != 2) - throw new GeometryException("internal error"); + throw GeometryException.GeometryInternalError(); m_tolerance = tolerance; double small_tolerance_sqr = MathUtils.sqr(tolerance * 0.01); @@ -213,7 +213,7 @@ public boolean intersect(double tolerance, boolean b_intersecting) { m_param_1, m_param_2, tolerance); if (count == 0) { assert (count > 0); - throw new GeometryException("internal error"); + throw GeometryException.GeometryInternalError(); } Point2D[] points = new Point2D[9]; for (int i = 0; i < count; i++) { @@ -347,10 +347,10 @@ public boolean intersect(double tolerance, boolean b_intersecting) { return bigmove; } - throw new GeometryException("internal error"); + throw GeometryException.GeometryInternalError(); } - throw new GeometryException("internal error"); + throw GeometryException.GeometryInternalError(); } return false; @@ -360,7 +360,7 @@ public void intersect(double tolerance, Point pt_intersector_point, int point_rank, double point_weight, boolean b_intersecting) { pt_intersector_point.copyTo(m_point); if (m_input_segments.size() != 1) - throw new GeometryException("internal error"); + throw GeometryException.GeometryInternalError(); m_tolerance = tolerance; @@ -441,7 +441,7 @@ public void intersect(double tolerance, Point pt_intersector_point, return; } - throw new GeometryException("internal error"); + throw GeometryException.GeometryInternalError(); } } diff --git a/src/main/java/com/esri/core/geometry/SegmentIterator.java b/src/main/java/com/esri/core/geometry/SegmentIterator.java index 0d5e1889..b563ccdd 100644 --- a/src/main/java/com/esri/core/geometry/SegmentIterator.java +++ b/src/main/java/com/esri/core/geometry/SegmentIterator.java @@ -115,6 +115,18 @@ public void resetToLastSegment() { m_impl.resetToLastSegment(); } + /** + *Resets the iterator to a specific vertex. + *The call to next_segment will return the segment that starts at the vertex. + *Call to previous_segment will return the segment that starts at the previous vertex. + *@param vertexIndex The vertex index to reset the iterator to. + *@param pathIndex The path index to reset the iterator to. Used as a hint. If the path_index is wrong or -1, then the path_index is automatically calculated. + * + */ + public void resetToVertex(int vertexIndex, int pathIndex) { + m_impl.resetToVertex(vertexIndex, pathIndex); + } + /** * Indicates whether a next segment exists for the path. * diff --git a/src/main/java/com/esri/core/geometry/SegmentIteratorImpl.java b/src/main/java/com/esri/core/geometry/SegmentIteratorImpl.java index af1e62b5..c5f97d19 100644 --- a/src/main/java/com/esri/core/geometry/SegmentIteratorImpl.java +++ b/src/main/java/com/esri/core/geometry/SegmentIteratorImpl.java @@ -51,6 +51,8 @@ final class SegmentIteratorImpl { protected int m_segmentCount; + protected int m_pathBegin; + protected MultiPathImpl m_parent; // parent of the iterator. protected boolean m_bCirculator; // If true, the iterator circulates around @@ -67,6 +69,7 @@ public SegmentIteratorImpl(MultiPathImpl parent) { m_segmentCount = _getSegmentCount(m_nextPathIndex); m_bCirculator = false; m_currentSegment = null; + m_pathBegin = -1; m_dummyPoint = new Point2D(); } @@ -84,6 +87,7 @@ public SegmentIteratorImpl(MultiPathImpl parent, int pointIndex) { m_segmentCount = _getSegmentCount(m_currentPathIndex); m_bCirculator = false; m_currentSegment = null; + m_pathBegin = m_parent.getPathStart(m_currentPathIndex); m_dummyPoint = new Point2D(); } @@ -105,6 +109,7 @@ public SegmentIteratorImpl(MultiPathImpl parent, int pathIndex, m_segmentCount = _getSegmentCount(m_nextPathIndex); m_bCirculator = false; m_currentSegment = null; + m_pathBegin = m_parent.getPathStart(m_currentPathIndex); m_dummyPoint = new Point2D(); } @@ -118,6 +123,7 @@ void resetTo(SegmentIteratorImpl src) { m_nextPathIndex = src.m_nextPathIndex; m_segmentCount = src.m_segmentCount; m_bCirculator = src.m_bCirculator; + m_pathBegin = src.m_pathBegin; m_currentSegment = null; } @@ -195,13 +201,17 @@ public void resetToLastSegment() { } public void resetToVertex(int vertexIndex) { + resetToVertex(vertexIndex, -1); + } + + public void resetToVertex(int vertexIndex, int _pathIndex) { if (m_currentPathIndex >= 0 && m_currentPathIndex < m_parent.getPathCount()) {// check if we // are in // the // current // path - int start = m_parent.getPathStart(m_currentPathIndex); + int start = _getPathBegin(); if (vertexIndex >= start && vertexIndex < m_parent.getPathEnd(m_currentPathIndex)) { m_currentSegmentIndex = -1; @@ -210,12 +220,21 @@ public void resetToVertex(int vertexIndex) { } } - int pathIndex = m_parent.getPathIndexFromPointIndex(vertexIndex); - m_nextPathIndex = pathIndex + 1; - m_currentPathIndex = pathIndex; + int path_index; + if (_pathIndex >= 0 && _pathIndex < m_parent.getPathCount() + && vertexIndex >= m_parent.getPathStart(_pathIndex) + && vertexIndex < m_parent.getPathEnd(_pathIndex)) { + path_index = _pathIndex; + } else { + path_index = m_parent.getPathIndexFromPointIndex(vertexIndex); + } + + m_nextPathIndex = path_index + 1; + m_currentPathIndex = path_index; m_currentSegmentIndex = -1; - m_nextSegmentIndex = vertexIndex - m_parent.getPathStart(pathIndex); - m_segmentCount = _getSegmentCount(pathIndex); + m_nextSegmentIndex = vertexIndex - m_parent.getPathStart(path_index); + m_segmentCount = _getSegmentCount(path_index); + m_pathBegin = m_parent.getPathStart(m_currentPathIndex); } /** @@ -231,6 +250,7 @@ public boolean nextPath() { m_currentSegmentIndex = -1; m_nextSegmentIndex = 0; m_segmentCount = _getSegmentCount(m_currentPathIndex); + m_pathBegin = m_parent.getPathStart(m_currentPathIndex); m_nextPathIndex++; return true; } @@ -249,6 +269,7 @@ public boolean previousPath() { m_nextSegmentIndex = 0; m_segmentCount = _getSegmentCount(m_nextPathIndex); m_currentPathIndex = m_nextPathIndex; + m_pathBegin = m_parent.getPathStart(m_currentPathIndex); resetToLastSegment(); return true; } @@ -264,6 +285,7 @@ public void resetToFirstPath() { m_segmentCount = -1; m_nextPathIndex = 0; m_currentPathIndex = -1; + m_pathBegin = -1; } /** @@ -276,6 +298,7 @@ public void resetToLastPath() { m_currentSegmentIndex = -1; m_nextSegmentIndex = -1; m_segmentCount = -1; + m_pathBegin = -1; } /** @@ -293,6 +316,7 @@ public void resetToPath(int pathIndex) { m_currentSegmentIndex = -1; m_nextSegmentIndex = -1; m_segmentCount = -1; + m_pathBegin = -1; } public int _getSegmentCount(int pathIndex) { @@ -416,13 +440,13 @@ public void _updateSegment() { m_currentSegment = (Line) m_line; break; case SegmentFlags.enumBezierSeg: - throw new GeometryException("internal error"); + throw GeometryException.GeometryInternalError(); // break; case SegmentFlags.enumArcSeg: - throw new GeometryException("internal error"); + throw GeometryException.GeometryInternalError(); // break; default: - throw new GeometryException("internal error"); + throw GeometryException.GeometryInternalError(); } m_currentSegment.assignVertexDescription(vertexDescr); diff --git a/src/main/java/com/esri/core/geometry/Simplificator.java b/src/main/java/com/esri/core/geometry/Simplificator.java index 88760528..5bfaf014 100644 --- a/src/main/java/com/esri/core/geometry/Simplificator.java +++ b/src/main/java/com/esri/core/geometry/Simplificator.java @@ -41,6 +41,7 @@ class Simplificator { private int m_firstCoincidentVertex; private int m_knownSimpleResult; private boolean m_bWinding; + private boolean m_fixSelfTangency; private void _beforeRemoveVertex(int vertex, boolean bChangePathFirst) { int vertexlistIndex = m_shape.getUserIndex(vertex, @@ -525,7 +526,7 @@ private boolean _simplify() { } if (iRepeatNum++ > 10) { - throw new GeometryException("internal error."); + throw GeometryException.GeometryInternalError(); } if (bNeedRepeat) @@ -549,7 +550,7 @@ private boolean _simplify() { m_shape.removeUserIndex(m_userIndexSortedAngleIndexToVertex); bChanged |= RingOrientationFixer.execute(m_shape, m_geometry, - m_sortedVertices); + m_sortedVertices, m_fixSelfTangency); return bChanged; } @@ -984,12 +985,13 @@ protected Simplificator() { } public static boolean execute(EditShape shape, int geometry, - int knownSimpleResult) { + int knownSimpleResult, boolean fixSelfTangency) { Simplificator simplificator = new Simplificator(); simplificator.m_shape = shape; // simplificator.m_bWinding = bWinding; simplificator.m_geometry = geometry; simplificator.m_knownSimpleResult = knownSimpleResult; + simplificator.m_fixSelfTangency = fixSelfTangency; return simplificator._simplify(); } @@ -999,6 +1001,11 @@ int _compareVerticesSimple(int v1, int v2) { Point2D pt2 = new Point2D(); m_shape.getXY(v2, pt2); int res = pt1.compare(pt2); + if (res == 0) {// sort equal vertices by the path ID + res = Integer.compare(m_shape.getPathFromVertex(v1), + m_shape.getPathFromVertex(v2)); + } + return res; } diff --git a/src/main/java/com/esri/core/geometry/SpatialReferenceImpl.java b/src/main/java/com/esri/core/geometry/SpatialReferenceImpl.java index 6a7540a5..081b2b64 100644 --- a/src/main/java/com/esri/core/geometry/SpatialReferenceImpl.java +++ b/src/main/java/com/esri/core/geometry/SpatialReferenceImpl.java @@ -41,9 +41,9 @@ import com.esri.core.geometry.VertexDescription.Semantics; class SpatialReferenceImpl extends SpatialReference { - static boolean no_projection_engine = true; - public static int c_SULIMIT32 = 2147483645; - public static long c_SULIMIT64 = 9007199254740990L; + static final boolean no_projection_engine = true; + public final static int c_SULIMIT32 = 2147483645; + public final static long c_SULIMIT64 = 9007199254740990L; enum Precision { Integer32, Integer64, FloatingPoint @@ -126,7 +126,7 @@ public void queryValidCoordinateRange(Envelope2D env2D) { break; default: // TODO - throw new GeometryException("internal error");// fixme + throw GeometryException.GeometryInternalError(); } env2D.setCoords(getFalseX(), getFalseY(), getFalseX() + delta, diff --git a/src/main/java/com/esri/core/geometry/StridedIndexTypeCollection.java b/src/main/java/com/esri/core/geometry/StridedIndexTypeCollection.java index ca631d98..93de656f 100644 --- a/src/main/java/com/esri/core/geometry/StridedIndexTypeCollection.java +++ b/src/main/java/com/esri/core/geometry/StridedIndexTypeCollection.java @@ -23,8 +23,6 @@ */ package com.esri.core.geometry; -import java.util.ArrayList; - /** * A collection of strides of Index_type elements. To be used when one needs a * collection of homogeneous elements that contain only integer fields (i.e. @@ -32,31 +30,37 @@ * time creation and deletion of an element. */ final class StridedIndexTypeCollection { - - private int m_firstFree; - private int m_last; + private int[][] m_buffer = null; + private int m_firstFree = -1; + private int m_last = 0; + private int m_size = 0; + private int m_capacity = 0; + private int m_bufferSize = 0; private int m_stride; private int m_realStride; - private int m_realBlockSize; private int m_blockSize; - private int m_blockMask; - private int m_blockPower; - private int m_size; - private int m_capacity; - private ArrayList m_buffer; + + /* + private final static int m_realBlockSize = 2048;//if you change this, change m_blockSize, m_blockPower, m_blockMask, and st_sizes + private final static int m_blockMask = 0x7FF; + private final static int m_blockPower = 11; + private final static int[] st_sizes = {16, 32, 64, 128, 256, 512, 1024, 2048}; + */ + + private final static int m_realBlockSize = 16384;// if you change this, + // change m_blockSize, + // m_blockPower, + // m_blockMask, and + // st_sizes + private final static int m_blockMask = 0x3FFF; + private final static int m_blockPower = 14; + private final static int[] st_sizes = { 16, 32, 64, 128, 256, 512, 1024, + 2048, 4096, 8192, 16384 }; StridedIndexTypeCollection(int stride) { - m_firstFree = -1; - m_last = 0; - m_size = 0; m_stride = stride; m_realStride = stride; - m_realBlockSize = 2048;//if you change this, change m_blockSize, m_blockPower, m_blockMask, and st_sizes - m_blockPower = 11; - m_blockMask = 0x7FF; - m_blockSize = 2048 / m_realStride; - m_capacity = 0; - m_buffer = null; + m_blockSize = m_realBlockSize / m_realStride; } void deleteElement(int element) { @@ -64,7 +68,7 @@ void deleteElement(int element) { int totalStrides = (element >> m_blockPower) * m_blockSize * m_realStride + (element & m_blockMask); if (totalStrides < m_last * m_realStride) { - m_buffer.get(element >> m_blockPower)[element & m_blockMask] = m_firstFree; + m_buffer[element >> m_blockPower][element & m_blockMask] = m_firstFree; m_firstFree = element; } else { assert (totalStrides == m_last * m_realStride); @@ -75,13 +79,13 @@ void deleteElement(int element) { // Returns the given field of the element. int getField(int element, int field) { - return m_buffer.get(element >> m_blockPower)[(element & m_blockMask) + return m_buffer[element >> m_blockPower][(element & m_blockMask) + field]; } // Sets the given field of the element. void setField(int element, int field, int value) { - m_buffer.get(element >> m_blockPower)[(element & m_blockMask) + field] = value; + m_buffer[element >> m_blockPower][(element & m_blockMask) + field] = value; } // Returns the stride size @@ -95,13 +99,15 @@ int newElement() { int element = m_firstFree; if (element == -1) { if (m_last == m_capacity) { - long newcap = m_capacity != 0 ? (((long)m_capacity + 1) * 3 / 2) : (long)1; + long newcap = m_capacity != 0 ? (((long) m_capacity + 1) * 3 / 2) + : (long) 1; if (newcap > Integer.MAX_VALUE) - newcap = Integer.MAX_VALUE;//cannot grow past 2gb elements presently - + newcap = Integer.MAX_VALUE;// cannot grow past 2gb elements + // presently + if (newcap == m_capacity) throw new IndexOutOfBoundsException(); - + grow_(newcap); } @@ -109,12 +115,12 @@ int newElement() { + (m_last % m_blockSize) * m_realStride; m_last++; } else { - m_firstFree = m_buffer.get(element >> m_blockPower)[element + m_firstFree = m_buffer[element >> m_blockPower][element & m_blockMask]; } m_size++; - int ar[] = m_buffer.get(element >> m_blockPower); + int ar[] = m_buffer[element >> m_blockPower]; int ind = element & m_blockMask; for (int i = 0; i < m_stride; i++) { ar[ind + i] = -1; @@ -157,8 +163,8 @@ int capacity() { // Swaps content of two elements (each field of the stride) void swap(int element1, int element2) { - int ar1[] = m_buffer.get(element1 >> m_blockPower); - int ar2[] = m_buffer.get(element2 >> m_blockPower); + int ar1[] = m_buffer[element1 >> m_blockPower]; + int ar2[] = m_buffer[element2 >> m_blockPower]; int ind1 = element1 & m_blockMask; int ind2 = element2 & m_blockMask; for (int i = 0; i < m_stride; i++) { @@ -170,8 +176,8 @@ void swap(int element1, int element2) { // Swaps content of two fields void swapField(int element1, int element2, int field) { - int ar1[] = m_buffer.get(element1 >> m_blockPower); - int ar2[] = m_buffer.get(element2 >> m_blockPower); + int ar1[] = m_buffer[element1 >> m_blockPower]; + int ar2[] = m_buffer[element2 >> m_blockPower]; int ind1 = (element1 & m_blockMask) + field; int ind2 = (element2 & m_blockMask) + field; int tmp = ar1[ind1]; @@ -200,11 +206,21 @@ private boolean dbgdelete_(int element) { return true; } - final static int[] st_sizes = {16, 32, 64, 128, 256, 512, 1024, 2048}; + private void ensureBufferBlocksCapacity(int blocks) { + if (m_buffer.length < blocks) { + int[][] newBuffer = new int[blocks][]; + for (int i = 0; i < m_buffer.length; i++) { + newBuffer[i] = m_buffer[i]; + } + + m_buffer = newBuffer; + } + } private void grow_(long newsize) { if (m_buffer == null) { - m_buffer = new ArrayList(); + m_bufferSize = 0; + m_buffer = new int[8][]; } assert (newsize > m_capacity); @@ -212,49 +228,47 @@ private void grow_(long newsize) { long nblocks = (newsize + m_blockSize - 1) / m_blockSize; if (nblocks > Integer.MAX_VALUE) throw new IndexOutOfBoundsException(); - - m_buffer.ensureCapacity((int)nblocks); + + ensureBufferBlocksCapacity((int) nblocks); if (nblocks == 1) { // When less than one block is needed we allocate smaller arrays // than m_realBlockSize to avoid initialization cost. int oldsz = m_capacity > 0 ? m_capacity : 0; assert (oldsz < newsize); int i = 0; - int realnewsize = (int)newsize * m_realStride; + int realnewsize = (int) newsize * m_realStride; while (realnewsize > st_sizes[i]) // get the size to allocate. Using fixed sizes to reduce // fragmentation. i++; int[] b = new int[st_sizes[i]]; - if (m_buffer.size() == 1) { - System.arraycopy(m_buffer.get(0), 0, b, 0, m_size - * m_realStride); - m_buffer.set(0, b); + if (m_bufferSize == 1) { + System.arraycopy(m_buffer[0], 0, b, 0, m_buffer[0].length); + m_buffer[0] = b; } else { - m_buffer.add(b); + m_buffer[m_bufferSize] = b; + m_bufferSize++; } m_capacity = b.length / m_realStride; } else { if (nblocks * m_blockSize > Integer.MAX_VALUE) throw new IndexOutOfBoundsException(); - - if (m_buffer.size() == 1) { - if (m_buffer.get(0).length < m_realBlockSize) { + + if (m_bufferSize == 1) { + if (m_buffer[0].length < m_realBlockSize) { // resize the first buffer to ensure it is equal the // m_realBlockSize. int[] b = new int[m_realBlockSize]; - System.arraycopy(m_buffer.get(0), 0, b, 0, m_size - * m_realStride); - m_buffer.set(0, b); + System.arraycopy(m_buffer[0], 0, b, 0, m_buffer[0].length); + m_buffer[0] = b; m_capacity = m_blockSize; } } - while (m_buffer.size() < nblocks) { - m_buffer.add(new int[m_realBlockSize]); + while (m_bufferSize < nblocks) { + m_buffer[m_bufferSize++] = new int[m_realBlockSize]; m_capacity += m_blockSize; } } } - } diff --git a/src/main/java/com/esri/core/geometry/TopoGraph.java b/src/main/java/com/esri/core/geometry/TopoGraph.java index e7a40cfa..affcbc60 100644 --- a/src/main/java/com/esri/core/geometry/TopoGraph.java +++ b/src/main/java/com/esri/core/geometry/TopoGraph.java @@ -34,7 +34,7 @@ static interface EnumInputMode { final static int enumInputModeBuildGraph = 0; final static int enumInputModeSimplifyAlternate = 4 + 0; final static int enumInputModeSimplifyWinding = 4 + 1; - final static int enumInputModeSimplifyForBuffer = 4 + 2; + final static int enumInputModeIsSimplePolygon = 4 + 3; } EditShape m_shape; @@ -64,6 +64,13 @@ static interface EnumInputMode { int m_halfEdgeIndex; // vertex index of half-edges in the m_shape int m_tmpHalfEdgeParentageIndex; int m_tmpHalfEdgeWindingNumberIndex; + int m_tmpHalfEdgeOddEvenNumberIndex = -1; + + int m_universe_geomID = -1; + + boolean m_buildChains = true; + + NonSimpleResult m_non_simple_result = new NonSimpleResult(); int m_pointCount;// point count processed in this Topo_graph. Used to // reserve data. @@ -554,15 +561,6 @@ void planeSweepParentage_(int inputMode, ProgressTracker progress_tracker) { int attachedTreeNode = getHalfEdgeUserIndex( clusterHalfEdge, treeNodeIndex); if (attachedTreeNode == -1) { - // #ifdef DEBUG - // //Debug-checking that the "from" is below the - // "to" - // Point_2D pt_1; - // getHalfEdgeFromXY(clusterHalfEdge, pt_1); - // Point_2D pt_2; - // getHalfEdgeToXY(clusterHalfEdge, pt_2); - // assert(pt_1.compare(pt_2) < 0); - // #endif int newTreeNode = aet.addElement(clusterHalfEdge, -1); new_edges.add(newTreeNode); @@ -701,160 +699,214 @@ void planeSweepParentagePropagateParentage_(Treap aet, int treeNode, assert (parentChain != -1); - // Now do specific sweep calculations if (inputMode == EnumInputMode.enumInputModeBuildGraph) { - int chainParentage = getChainParentage(edgeChain); + propagate_parentage_build_graph_(aet, treeNode, edge, leftEdge, edgeChain, edgeChainParent, twinHalfEdgeChain); + } + else if (inputMode == EnumInputMode.enumInputModeSimplifyWinding) { + propagate_parentage_winding_(aet, treeNode, edge, leftEdge, twinEdge, edgeChain, edgeChainParent, twinHalfEdgeChain); + } + else if (inputMode == EnumInputMode.enumInputModeSimplifyAlternate) { + propagate_parentage_alternate_(aet, treeNode, edge, leftEdge, twinEdge, edgeChain, edgeChainParent, twinHalfEdgeChain); + } + + } + + void propagate_parentage_build_graph_(Treap aet, int treeNode, int edge, int leftEdge, + int edgeChain, int edgeChainParent, int twinHalfEdgeChain) { + // Now do specific sweep calculations + int chainParentage = getChainParentage(edgeChain); - if (leftEdge != -1) { - // borrow the parentage from the left edge also - int leftEdgeChain = getHalfEdgeChain(leftEdge); - // dbg_print_edge_(edge); - // dbg_print_edge_(leftEdge); - - // We take parentage from the left edge (that edge has been - // already processed), and move its face parentage accross this - // edge/twin pair. - // While the parentage is moved, accross, any bits of the - // parentage that is present in the twin are removed, because - // the twin is the right edge of the current face. - // The remaining bits are added to the face parentage of this - // edge, indicating that the face this edge borders, belongs to - // all the parents that are still active to the left. - int twinChainParentage = getChainParentage(twinHalfEdgeChain); - int leftChainParentage = getChainParentage(leftEdgeChain); - - int edgeParentage = getHalfEdgeParentage(edge); - int spikeParentage = chainParentage & twinChainParentage - & leftChainParentage; // parentage that needs to stay - leftChainParentage = leftChainParentage + if (leftEdge != -1) { + // borrow the parentage from the left edge also + int leftEdgeChain = getHalfEdgeChain(leftEdge); + + // We take parentage from the left edge (that edge has been + // already processed), and move its face parentage accross this + // edge/twin pair. + // While the parentage is moved, accross, any bits of the + // parentage that is present in the twin are removed, because + // the twin is the right edge of the current face. + // The remaining bits are added to the face parentage of this + // edge, indicating that the face this edge borders, belongs to + // all the parents that are still active to the left. + int twinChainParentage = getChainParentage(twinHalfEdgeChain); + int leftChainParentage = getChainParentage(leftEdgeChain); + + int edgeParentage = getHalfEdgeParentage(edge); + int spikeParentage = chainParentage & twinChainParentage + & leftChainParentage; // parentage that needs to stay + leftChainParentage = leftChainParentage ^ (leftChainParentage & edgeParentage); - leftChainParentage |= spikeParentage; - // leftChainParentage = leftChainParentage ^ (leftChainParentage - // & twinChainParentage);//only parentage that is abscent in the - // twin is propagated to the right - // leftChainParentage = leftChainParentage ^ (leftChainParentage - // & edgeParentage);//only parentage that is abscent in the twin - // is propagated to the right - // & (and) leaves the parentage that is common for left edge and - // the twin, while ^ (xor) leaves the parentage that is present - // in the - // left edge only. - - if (leftChainParentage != 0) { - // propagate left parentage to the current edge and its - // twin. - setChainParentage_(twinHalfEdgeChain, twinChainParentage + leftChainParentage |= spikeParentage; + + if (leftChainParentage != 0) { + // propagate left parentage to the current edge and its + // twin. + setChainParentage_(twinHalfEdgeChain, twinChainParentage | leftChainParentage); - setChainParentage_(edgeChain, leftChainParentage + setChainParentage_(edgeChain, leftChainParentage | chainParentage); - chainParentage |= leftChainParentage; - } + chainParentage |= leftChainParentage; + } // dbg_print_edge_(edge); - } + } - for (int rightNode = aet.getNext(treeNode); rightNode != -1; rightNode = aet + for (int rightNode = aet.getNext(treeNode); rightNode != -1; rightNode = aet .getNext(rightNode)) { - int rightEdge = aet.getElement(rightNode); - int rightTwin = getHalfEdgeTwin(rightEdge); - // dbg_print_edge_(rightEdge); - int rightTwinChain = getHalfEdgeChain(rightTwin); - int rightTwinChainParentage = getChainParentage(rightTwinChain); - int rightEdgeParentage = getHalfEdgeParentage(rightEdge); - int rightEdgeChain = getHalfEdgeChain(rightEdge); - int rightChainParentage = getChainParentage(rightEdgeChain); - - int spikeParentage = rightTwinChainParentage - & rightChainParentage & chainParentage; // parentage - // that needs to - // stay - chainParentage = chainParentage - ^ (chainParentage & rightEdgeParentage);// only - // parentage - // that is - // abscent in - // the twin is - // propagated to - // the right - chainParentage |= spikeParentage; - - if (chainParentage == 0) - break; - - setChainParentage_(rightTwinChain, rightTwinChainParentage + int rightEdge = aet.getElement(rightNode); + int rightTwin = getHalfEdgeTwin(rightEdge); + + int rightTwinChain = getHalfEdgeChain(rightTwin); + int rightTwinChainParentage = getChainParentage(rightTwinChain); + int rightEdgeParentage = getHalfEdgeParentage(rightEdge); + int rightEdgeChain = getHalfEdgeChain(rightEdge); + int rightChainParentage = getChainParentage(rightEdgeChain); + + int spikeParentage = rightTwinChainParentage + & rightChainParentage & chainParentage; // parentage + // that needs to + // stay + chainParentage = chainParentage + ^ (chainParentage & rightEdgeParentage);// only + // parentage + // that is + // abscent in + // the twin is + // propagated to + // the right + chainParentage |= spikeParentage; + + if (chainParentage == 0) + break; + + setChainParentage_(rightTwinChain, rightTwinChainParentage | chainParentage); - setChainParentage_(rightEdgeChain, rightChainParentage + setChainParentage_(rightEdgeChain, rightChainParentage | chainParentage); - // dbg_print_edge_(rightEdge); - } } + } - if (inputMode == EnumInputMode.enumInputModeSimplifyWinding) { - if (edgeChain == twinHalfEdgeChain) - return; - // starting from the left most edge, calculate winding. - int edgeWinding = getHalfEdgeUserIndex(edge, - m_tmpHalfEdgeWindingNumberIndex); - edgeWinding += getHalfEdgeUserIndex(twinEdge, - m_tmpHalfEdgeWindingNumberIndex); - int winding = 0; - AttributeStreamOfInt32 chainStack = new AttributeStreamOfInt32(0); - AttributeStreamOfInt32 windingStack = new AttributeStreamOfInt32(0); - windingStack.add(0); - for (int leftNode = aet.getFirst(-1); leftNode != treeNode; leftNode = aet + void propagate_parentage_winding_(Treap aet, int treeNode, int edge, int leftEdge, int twinEdge, + int edgeChain, int edgeChainParent, int twinHalfEdgeChain) { + + if (edgeChain == twinHalfEdgeChain) + return; + // starting from the left most edge, calculate winding. + int edgeWinding = getHalfEdgeUserIndex(edge, + m_tmpHalfEdgeWindingNumberIndex); + edgeWinding += getHalfEdgeUserIndex(twinEdge, + m_tmpHalfEdgeWindingNumberIndex); + int winding = 0; + AttributeStreamOfInt32 chainStack = new AttributeStreamOfInt32(0); + AttributeStreamOfInt32 windingStack = new AttributeStreamOfInt32(0); + windingStack.add(0); + for (int leftNode = aet.getFirst(-1); leftNode != treeNode; leftNode = aet .getNext(leftNode)) { - int leftEdge1 = aet.getElement(leftNode); - int leftTwin = getHalfEdgeTwin(leftEdge1); - int l_chain = getHalfEdgeChain(leftEdge1); - int lt_chain = getHalfEdgeChain(leftTwin); - - if (l_chain != lt_chain) { - int leftWinding = getHalfEdgeUserIndex(leftEdge1, - m_tmpHalfEdgeWindingNumberIndex); - leftWinding += getHalfEdgeUserIndex(leftTwin, - m_tmpHalfEdgeWindingNumberIndex); - winding += leftWinding; - - boolean popped = false; - if (chainStack.size() != 0 - && chainStack.getLast() == lt_chain) { - windingStack - .resizePreserveCapacity(windingStack.size() - 1); - chainStack - .resizePreserveCapacity(chainStack.size() - 1); - popped = true; - } + int leftEdge1 = aet.getElement(leftNode); + int leftTwin = getHalfEdgeTwin(leftEdge1); + int l_chain = getHalfEdgeChain(leftEdge1); + int lt_chain = getHalfEdgeChain(leftTwin); + + if (l_chain != lt_chain) { + int leftWinding = getHalfEdgeUserIndex(leftEdge1, + m_tmpHalfEdgeWindingNumberIndex); + leftWinding += getHalfEdgeUserIndex(leftTwin, + m_tmpHalfEdgeWindingNumberIndex); + winding += leftWinding; + + boolean popped = false; + if (chainStack.size() != 0 + && chainStack.getLast() == lt_chain) { + windingStack.removeLast(); + chainStack.removeLast(); + popped = true; + } - // geometry_release_assert(getChainParent(lt_chain) != -1); + if (getChainParent(lt_chain) == -1) + throw GeometryException.GeometryInternalError(); - if (!popped || getChainParent(lt_chain) != l_chain) { - windingStack.add(winding); - chainStack.add(l_chain); - } + if (!popped || getChainParent(lt_chain) != l_chain) { + windingStack.add(winding); + chainStack.add(l_chain); } } + } - winding += edgeWinding; + winding += edgeWinding; - if (chainStack.size() != 0 + if (chainStack.size() != 0 && chainStack.getLast() == twinHalfEdgeChain) { - windingStack.resizePreserveCapacity(windingStack.size() - 1); - chainStack.resizePreserveCapacity(chainStack.size() - 1); + windingStack.removeLast(); + chainStack.removeLast(); + } + + if (winding != 0) { + if (windingStack.getLast() == 0) { + int geometry = m_shape.getFirstGeometry(); + int geometryID = getGeometryID(geometry); + setChainParentage_(edgeChain, geometryID); } + } else { + if (windingStack.getLast() != 0) { + int geometry = m_shape.getFirstGeometry(); + int geometryID = getGeometryID(geometry); + setChainParentage_(edgeChain, geometryID); + } + } + } + + void propagate_parentage_alternate_(Treap aet, int treeNode, int edge, + int leftEdge, int twinEdge, int edgeChain, int edgeChainParent, + int twinHalfEdgeChain) { + // Now do specific sweep calculations + // This one is done when we are doing a topological operation. + int geometry = m_shape.getFirstGeometry(); + int geometryID = getGeometryID(geometry); + + if (leftEdge == -1) { + // no left edge neighbour means the twin chain is surrounded by the + // universe + assert (getChainParent(twinHalfEdgeChain) == m_universeChain); + assert (getChainParentage(twinHalfEdgeChain) == 0 || getChainParentage(twinHalfEdgeChain) == m_universe_geomID); + assert (getChainParentage(edgeChain) == 0); + setChainParentage_(twinHalfEdgeChain, m_universe_geomID); + int parity = getHalfEdgeUserIndex(edge, + m_tmpHalfEdgeOddEvenNumberIndex); + if ((parity & 1) != 0) + setChainParentage_(edgeChain, geometryID);// set the parenentage + // from the parity + else + setChainParentage_(edgeChain, m_universe_geomID);// this chain + // does not + // belong to + // geometry + } else { + int twin_parentage = getChainParentage(twinHalfEdgeChain); + if (twin_parentage == 0) { + int leftEdgeChain = getHalfEdgeChain(leftEdge); + int left_parentage = getChainParentage(leftEdgeChain); + setChainParentage_(twinHalfEdgeChain, left_parentage); + int parity = getHalfEdgeUserIndex(edge, + m_tmpHalfEdgeOddEvenNumberIndex); + if ((parity & 1) != 0) + setChainParentage_(edgeChain, + (left_parentage == geometryID) ? m_universe_geomID + : geometryID); + else + setChainParentage_(edgeChain, left_parentage); - if (winding != 0) { - if (windingStack.read(windingStack.size() - 1) == 0) { - int geometry = m_shape.getFirstGeometry(); - int geometryID = getGeometryID(geometry); - setChainParentage_(edgeChain, geometryID); - } } else { - if (windingStack.read(windingStack.size() - 1) != 0) { - int geometry = m_shape.getFirstGeometry(); - int geometryID = getGeometryID(geometry); - setChainParentage_(edgeChain, geometryID); - } + int parity = getHalfEdgeUserIndex(edge, + m_tmpHalfEdgeOddEvenNumberIndex); + if ((parity & 1) != 0) + setChainParentage_(edgeChain, + (twin_parentage == geometryID) ? m_universe_geomID + : geometryID); + else + setChainParentage_(edgeChain, twin_parentage); } + } } @@ -901,13 +953,10 @@ boolean trySetChainParentFromTwin_(int chainToSet, int twinChain) { double twinArea = getChainArea(twinChain); assert (twinArea != 0); if (area > 0 && twinArea < 0) { - // assert(twinArea + area <= area * Number_utils::double_eps() * - // 100); setChainParent_(chainToSet, twinChain); return true; } if (area < 0 && twinArea > 0) { - // assert(-area <= twinArea); setChainParent_(chainToSet, twinChain); return true; } else { @@ -1056,7 +1105,24 @@ void createHalfEdges_(int inputMode, AttributeStreamOfInt32 sorted_vertices) { m_tmpHalfEdgeWindingNumberIndex, windingNumber); setHalfEdgeUserIndex(twinEdge, m_tmpHalfEdgeWindingNumberIndex, windingNumberTwin); + } else if (inputMode == EnumInputMode.enumInputModeIsSimplePolygon) { + setHalfEdgeUserIndex(twinEdge, m_tmpHalfEdgeParentageIndex, + m_universe_geomID); + setHalfEdgeUserIndex(half_edge, + m_tmpHalfEdgeParentageIndex, + gt == Geometry.GeometryType.Polygon ? geometryID + : 0); + } else if (inputMode == EnumInputMode.enumInputModeSimplifyAlternate) { + setHalfEdgeUserIndex(twinEdge, m_tmpHalfEdgeParentageIndex, + 0); + setHalfEdgeUserIndex(half_edge, + m_tmpHalfEdgeParentageIndex, 0); + setHalfEdgeUserIndex(half_edge, + m_tmpHalfEdgeOddEvenNumberIndex, 1); + setHalfEdgeUserIndex(twinEdge, + m_tmpHalfEdgeOddEvenNumberIndex, 1); } + int edgeBit = gt == Geometry.GeometryType.Polygon ? c_edgeBitMask : 0; setHalfEdgeParentage_(half_edge, geometryID | edgeBit); @@ -1095,12 +1161,13 @@ void mergeVertexListsOfEdges_(int eDst, int eSrc) { void sortHalfEdgesByAngle_(int inputMode) { AttributeStreamOfInt32 angleSorter = new AttributeStreamOfInt32(0); angleSorter.reserve(10); + TopoGraphAngleComparer tgac = new TopoGraphAngleComparer(this); // Now go through the clusters, sort edges in each cluster by angle, and // reconnect the halfedges of sorted edges in the sorted order. // Also share the parentage information between coinciding edges and // remove duplicates. for (int cluster = getFirstCluster(); cluster != -1; cluster = getNextCluster(cluster)) { - angleSorter.resizePreserveCapacity(0); + angleSorter.clear(false); int first = getClusterHalfEdge(cluster); if (first != -1) { // 1. sort edges originating at the cluster by angle (counter - @@ -1117,7 +1184,7 @@ void sortHalfEdgesByAngle_(int inputMode) { if (angleSorter.size() > 1) { if (angleSorter.size() > 2) { angleSorter.Sort(0, angleSorter.size(), - new TopoGraphAngleComparer(this)); // std::sort(angleSorter.get_ptr(), + tgac); // std::sort(angleSorter.get_ptr(), // angleSorter.get_ptr() // + // angleSorter.size(), @@ -1224,12 +1291,31 @@ void sortHalfEdgesByAngle_(int inputMode) { // even number of edges coinciding there and // half of them has opposite direction to // another half. + } else if (inputMode == EnumInputMode.enumInputModeIsSimplePolygon) { + m_non_simple_result = new NonSimpleResult(NonSimpleResult.Reason.CrossOver, cluster, -1); + return; + } + else if (m_tmpHalfEdgeOddEvenNumberIndex != -1) { + int newHalfEdgeWinding = getHalfEdgeUserIndex( + ePrev, m_tmpHalfEdgeOddEvenNumberIndex) + + getHalfEdgeUserIndex(e, + m_tmpHalfEdgeOddEvenNumberIndex); + int newTwinEdgeWinding = getHalfEdgeUserIndex( + ePrevTwin, + m_tmpHalfEdgeOddEvenNumberIndex) + + getHalfEdgeUserIndex(eTwin, + m_tmpHalfEdgeOddEvenNumberIndex); + setHalfEdgeUserIndex(ePrev, + m_tmpHalfEdgeOddEvenNumberIndex, + newHalfEdgeWinding); + setHalfEdgeUserIndex(ePrevTwin, + m_tmpHalfEdgeOddEvenNumberIndex, + newTwinEdgeWinding); } mergeVertexListsOfEdges_(ePrev, e); deleteEdgeImpl_(e); - assert (n < 3 || e0 == angleSorter.read(angleSorter - .size() - 1)); + assert (n < 3 || e0 == angleSorter.getLast()); prevMerged = ePrev; angleSorter.set(i, -1); if (e == e0) { @@ -1284,6 +1370,18 @@ void sortHalfEdgesByAngle_(int inputMode) { ePrev = e; ePrevTo = eTo; ePrevTwin = eTwin; + if (inputMode == EnumInputMode.enumInputModeIsSimplePolygon) + { + int par1 = getHalfEdgeUserIndex(e, m_tmpHalfEdgeParentageIndex) | + getHalfEdgeUserIndex(getHalfEdgePrev(e), m_tmpHalfEdgeParentageIndex); + if (par1 == (m_universe_geomID | 1)) + { + //violation of face parentage + m_non_simple_result = new NonSimpleResult(NonSimpleResult.Reason.CrossOver, cluster, -1); + return; + } + } + } setClusterHalfEdge_(cluster, e0);// smallest angle goes @@ -1293,7 +1391,7 @@ void sortHalfEdgesByAngle_(int inputMode) { } } - void buildChains_() { + void buildChains_(int inputMode) { // Creates chains and puts them in the list of chains. // Does not set the chain parentage // Does not connect chains @@ -1347,15 +1445,10 @@ void buildChains_() { // visited. e = getHalfEdgeNext(e); } while (e != edge); + + assert(inputMode != EnumInputMode.enumInputModeIsSimplePolygon || parentage != (1 | m_universe_geomID)); + setChainParentage_(chain, parentage); - - // Polygon::SPtr poly = - // dbg_dump_chain_to_polygon_(chain); - // char buf[1024]; - // sprintf(buf, "c:\\temp\\chain%d.txt", - // m_chain_data->size()); - // Operator_factory_local::SaveGeometryToInternalTextFile(buf, - // poly); } edge = getHalfEdgeNext(getHalfEdgeTwin(edge));// next @@ -1394,7 +1487,6 @@ void buildChains_() { } void simplify_(int inputMode) { - assert (inputMode != EnumInputMode.enumInputModeBuildGraph); if (inputMode == EnumInputMode.enumInputModeSimplifyAlternate) { simplifyAlternate_(); } else if (inputMode == EnumInputMode.enumInputModeSimplifyWinding) { @@ -1403,80 +1495,77 @@ void simplify_(int inputMode) { } void simplifyAlternate_() { - int geometry = m_shape.getFirstGeometry(); - int geometryID = getGeometryID(geometry); - - int universe_chain = getFirstChain(); // winding = 0; - simplifyAlternateRecursion_(universe_chain, geometryID, false); - } - - void simplifyAlternateRecursion_(int parent_chain, int geometryID, - boolean bEven) { - boolean bEven_ = !bEven; - for (int chain = getChainFirstIsland(parent_chain); chain != -1; chain = getChainNextInParent(chain)) { - if (bEven_) - setChainParentage_(chain, geometryID); - else - setChainParentage_(chain, 0); - - simplifyAlternateRecursion_(chain, geometryID, bEven_); - } + //there is nothing to do } void simplifyWinding_() { - // there is nothing to do. + //there is nothing to do } boolean removeSpikes_() { - boolean bRemoved = false; - for (int chain = getFirstChain(); chain != -1;) { - int firstHalfEdge = getChainHalfEdge(chain); - assert (firstHalfEdge != -1); - - boolean bRemovedEdge = false; + boolean removed = false; + int visitedIndex = createUserIndexForHalfEdges(); + for (int cluster = getFirstCluster(); cluster != -1; cluster = getNextCluster(cluster)) { + int firstHalfEdge = getClusterHalfEdge(cluster); + if (firstHalfEdge == -1) + continue; - int prev = getHalfEdgePrev(firstHalfEdge); int half_edge = firstHalfEdge; - while (true) { - int twin = getHalfEdgeTwin(half_edge); - if (prev == twin) { - int next2 = deleteEdgeInternal_(half_edge); - bRemovedEdge = true; - if (next2 == -1) { - // The chain has been deleted. - break; - } - if (half_edge == firstHalfEdge) { - firstHalfEdge = next2; - } + do { + int visited = getHalfEdgeUserIndex(half_edge, visitedIndex); + int halfEdgeNext = getHalfEdgeNext(getHalfEdgeTwin(half_edge)); + if (visited != 1) { + int faceHalfEdge = half_edge; + do { + int faceHalfEdgeNext = getHalfEdgeNext(faceHalfEdge); + int faceHalfEdgePrev = getHalfEdgePrev(faceHalfEdge); + int faceHalfEdgeTwin = getHalfEdgeTwin(faceHalfEdge); + // We first check whether faceHalfEdge corresponds to + // the starting segment with + // "faceHalfEdge != half_edge && faceHalfEdgePrev != half_edge" + // Otherwise if we deleted the first half edge, then + // there could be an infinite loop since the terminal + // condition wouldn't occur. + // if (faceHalfEdge != half_edge && faceHalfEdgeNext != + // half_edge && faceHalfEdgePrev == faceHalfEdgeTwin) + if (faceHalfEdge != half_edge + && faceHalfEdgePrev != half_edge + && faceHalfEdgePrev == faceHalfEdgeTwin) { + deleteEdgeInternal_(faceHalfEdge); + removed = true; + assert (faceHalfEdgeNext != faceHalfEdge); + assert (faceHalfEdgeNext != faceHalfEdgeTwin); + assert (faceHalfEdgeNext != faceHalfEdgePrev); + } else { + setHalfEdgeUserIndex(faceHalfEdge, visitedIndex, 1); + } - half_edge = next2; - prev = getHalfEdgePrev(half_edge); - } else { - prev = half_edge; - half_edge = getHalfEdgeNext(half_edge); - if (half_edge == firstHalfEdge) - break; + faceHalfEdge = faceHalfEdgeNext; + } while (faceHalfEdge != half_edge); + + int faceHalfEdgePrev = getHalfEdgePrev(faceHalfEdge); + int faceHalfEdgeTwin = getHalfEdgeTwin(faceHalfEdge); + if (faceHalfEdgePrev == faceHalfEdgeTwin) { + deleteEdgeInternal_(faceHalfEdge); + removed = true; + } } - } - bRemoved |= bRemovedEdge; - if (bRemovedEdge) { - chain = deleteChain_(chain); - } else - chain = getChainNext(chain); + half_edge = halfEdgeNext; + } while (half_edge != firstHalfEdge); } - return bRemoved; + return removed; } - + void setEditShapeImpl_(EditShape shape, int inputMode, AttributeStreamOfInt32 editShapeGeometries, - ProgressTracker progress_tracker) { + ProgressTracker progress_tracker, boolean bBuildChains) { assert (editShapeGeometries == null || editShapeGeometries.size() > 0); removeShape(); + m_buildChains = bBuildChains; assert (m_shape == null); m_shape = shape; m_geometryIDIndex = m_shape.createGeometryUserIndex(); @@ -1516,6 +1605,8 @@ void setEditShapeImpl_(EditShape shape, int inputMode, geometry = m_shape.getNextGeometry(geometry); } } + + m_universe_geomID = geomID; m_pointCount = verticesSorter.size(); @@ -1531,8 +1622,7 @@ void setEditShapeImpl_(EditShape shape, int inputMode, m_clusterVertices.setCapacity(m_pointCount); - if ((progress_tracker != null) && !(progress_tracker.progress(-1, -1))) - throw new UserCancelException(); + ProgressTracker.checkAndThrow(progress_tracker); m_clusterData.setCapacity(m_pointCount + 10);// 10 for some self // intersections @@ -1591,35 +1681,40 @@ void setEditShapeImpl_(EditShape shape, int inputMode, ptFirst.setCoords(pt); } } - - if ((progress_tracker != null) && !(progress_tracker.progress(-1, -1))) - throw new UserCancelException(); + + ProgressTracker.checkAndThrow(progress_tracker); m_tmpHalfEdgeParentageIndex = createUserIndexForHalfEdges(); if (inputMode == EnumInputMode.enumInputModeSimplifyWinding) { m_tmpHalfEdgeWindingNumberIndex = createUserIndexForHalfEdges(); } + if (inputMode == EnumInputMode.enumInputModeSimplifyAlternate) { + m_tmpHalfEdgeOddEvenNumberIndex = createUserIndexForHalfEdges(); + } + createHalfEdges_(inputMode, verticesSorter);// For each geometry produce // clusters and half edges - // dbg_navigate_(); - + if (m_non_simple_result.m_reason != NonSimpleResult.Reason.NotDetermined) + return; + sortHalfEdgesByAngle_(inputMode); + if (m_non_simple_result.m_reason != NonSimpleResult.Reason.NotDetermined) + return; - buildChains_(); + buildChains_(inputMode); + if (m_non_simple_result.m_reason != NonSimpleResult.Reason.NotDetermined) + return; deleteUserIndexForHalfEdges(m_tmpHalfEdgeParentageIndex); m_tmpHalfEdgeParentageIndex = -1; - planeSweepParentage_(inputMode, progress_tracker); - // dbg_chk_chain_parents_(); - - if (inputMode != EnumInputMode.enumInputModeBuildGraph) - simplify_(inputMode); + if (m_buildChains) + planeSweepParentage_(inputMode, progress_tracker); + - // dbg_navigate_(); - // dbg_dump_chains_(); + simplify_(inputMode); } void deleteEdgeImpl_(int half_edge) { @@ -1704,32 +1799,26 @@ EditShape getShape() { // calling this! void setEditShape(EditShape shape, ProgressTracker progress_tracker) { setEditShapeImpl_(shape, EnumInputMode.enumInputModeBuildGraph, null, - progress_tracker); + progress_tracker, true); } - // Sets an edit shape and simplifies a geometry in it using "odd-even" rule. - // This is the default simplify method for polygons. - // The result edit shape contains a simple polygon. - // The input shape could be a non-simple polygon or a polyline (polyline - // need to form some rings to produce non-empty result). - // The geometry has to be cracked and clustered before calling this! - void setAndSimplifyEditShapeAlternate(EditShape shape, int geometry) { + void setEditShape(EditShape shape, ProgressTracker progress_tracker, boolean bBuildChains) { + setEditShapeImpl_(shape, EnumInputMode.enumInputModeBuildGraph, null, + progress_tracker, bBuildChains); + } + + void setAndSimplifyEditShapeAlternate(EditShape shape, int geometry, ProgressTracker progressTracker) { AttributeStreamOfInt32 geoms = new AttributeStreamOfInt32(0); geoms.add(geometry); setEditShapeImpl_(shape, EnumInputMode.enumInputModeSimplifyAlternate, - geoms, null); + geoms, progressTracker, shape.getGeometryType(geometry) == Geometry.Type.Polygon.value()); } - // Sets an edit shape and simplifies a geometry in it using "winding" rule. - // The result edit shape contains a simple polygon. - // The input shape could be a non-simple polygon or a polyline (polyline - // need to form some rings to produce non-empty result). - // The geometry has to be cracked and clustered before calling this! - void setAndSimplifyEditShapeWinding(EditShape shape, int geometry) { + void setAndSimplifyEditShapeWinding(EditShape shape, int geometry, ProgressTracker progressTracker) { AttributeStreamOfInt32 geoms = new AttributeStreamOfInt32(0); geoms.add(geometry); setEditShapeImpl_(shape, EnumInputMode.enumInputModeSimplifyWinding, - geoms, null); + geoms, progressTracker, true); } // Removes shape from the topograph and removes any user index created on @@ -1762,6 +1851,11 @@ void removeShape() { m_tmpHalfEdgeWindingNumberIndex = -1; } + if (m_tmpHalfEdgeOddEvenNumberIndex != -1) { + deleteUserIndexForHalfEdges(m_tmpHalfEdgeOddEvenNumberIndex); + m_tmpHalfEdgeOddEvenNumberIndex = -1; + } + m_shape = null; m_clusterData.deleteAll(true); m_clusterVertices.deleteAll(true); diff --git a/src/main/java/com/esri/core/geometry/TopologicalOperations.java b/src/main/java/com/esri/core/geometry/TopologicalOperations.java index 96fae909..4e2b02b7 100644 --- a/src/main/java/com/esri/core/geometry/TopologicalOperations.java +++ b/src/main/java/com/esri/core/geometry/TopologicalOperations.java @@ -33,6 +33,7 @@ final class TopologicalOperations { Point2D m_dummy_pt_2 = new Point2D(); int m_from_edge_for_polylines; boolean m_mask_lookup[] = null; + boolean m_bOGCOutput = false; boolean isGoodParentage(int parentage) { return parentage < m_mask_lookup.length ? m_mask_lookup[parentage] @@ -51,7 +52,7 @@ void cut(int sideIndex, int cuttee, int cutter, return; } - throw new GeometryException("internal error"); + throw GeometryException.GeometryInternalError(); } static final class CompareCuts extends IntComparator { @@ -79,10 +80,10 @@ public TopologicalOperations() { m_from_edge_for_polylines = -1; } - void setEditShape(EditShape shape) { + void setEditShape(EditShape shape, ProgressTracker progressTracker) { if (m_topo_graph == null) m_topo_graph = new TopoGraph(); - m_topo_graph.setEditShape(shape, null); + m_topo_graph.setEditShape(shape, progressTracker); } void setEditShapeCrackAndCluster(EditShape shape, double tolerance, @@ -92,9 +93,9 @@ void setEditShapeCrackAndCluster(EditShape shape, double tolerance, .getNextGeometry(geometry)) { if (shape.getGeometryType(geometry) == Geometry.Type.Polygon .value()) - Simplificator.execute(shape, geometry, -1); + Simplificator.execute(shape, geometry, -1, m_bOGCOutput); } - setEditShape(shape); + setEditShape(shape, progressTracker); } private void collectPolygonPathsPreservingFrom_(int geometryFrom, @@ -293,7 +294,7 @@ private int topoOperationPolygonPolygon_(int geometry_a, int geometry_b, m_topo_graph.deleteUserIndexForHalfEdges(visitedEdges); Simplificator.execute(shape, newGeometry, - MultiVertexGeometryImpl.GeometryXSimple.Weak); + MultiVertexGeometryImpl.GeometryXSimple.Weak, m_bOGCOutput); return newGeometry; } @@ -512,7 +513,7 @@ int[] topoOperationPolygonPolygonEx_(int geometry_a, int geometry_b, m_topo_graph.deleteUserIndexForClusters(visitedClusters); m_topo_graph.deleteUserIndexForHalfEdges(visitedEdges); Simplificator.execute(shape, newGeometryPolygon, - MultiVertexGeometryImpl.GeometryXSimple.Weak); + MultiVertexGeometryImpl.GeometryXSimple.Weak, m_bOGCOutput); int[] result = new int[3];// always returns size 3 result. result[0] = newGeometryMultipoint; @@ -616,6 +617,7 @@ int tryMoveThroughCrossroadBackwards_(int half_edge) { if (isGoodParentage(parentage)) { if (goodEdge != -1) return -1; + goodEdge = e; } @@ -671,19 +673,25 @@ private void restorePolylineParts_(int first_edge, int newGeometry, m_from_edge_for_polylines = -1; int fromEdge = half_edge; int toEdge = -1; + boolean b_found_impassable_crossroad = false; + int edgeCount = 1; while (true) { int halfEdgePrev = m_topo_graph.getHalfEdgePrev(half_edge); if (halfEdgePrev == halfEdgeTwin) break;// the end of a polyline + int halfEdgeTwinNext = m_topo_graph.getHalfEdgeNext(halfEdgeTwin); if (m_topo_graph.getHalfEdgeTwin(halfEdgePrev) != halfEdgeTwinNext) { // Crossroads is here. We can move through the crossroad only if // there is only a single way to pass through. + //When doing planar_simplify we'll never go through the crossroad. half_edge = tryMoveThroughCrossroadBackwards_(half_edge); if (half_edge == -1) break; - else + else { + b_found_impassable_crossroad = true; halfEdgeTwin = m_topo_graph.getHalfEdgeTwin(half_edge); + } } else { half_edge = halfEdgePrev; halfEdgeTwin = halfEdgeTwinNext; @@ -704,6 +712,7 @@ private void restorePolylineParts_(int first_edge, int newGeometry, m_topo_graph.setHalfEdgeUserIndex(halfEdgeTwin, visitedEdges, 1); fromEdge = half_edge; prevailingLength += prevailingDirection_(shape, half_edge); + edgeCount++; } if (toEdge == -1) { @@ -714,14 +723,17 @@ private void restorePolylineParts_(int first_edge, int newGeometry, int halfEdgeNext = m_topo_graph.getHalfEdgeNext(half_edge); if (halfEdgeNext == halfEdgeTwin) break; + int halfEdgeTwinPrev = m_topo_graph .getHalfEdgePrev(halfEdgeTwin); if (m_topo_graph.getHalfEdgeTwin(halfEdgeNext) != halfEdgeTwinPrev) { // Crossroads is here. We can move through the crossroad // only if there is only a single way to pass through. half_edge = tryMoveThroughCrossroadForward_(half_edge); - if (half_edge == -1) + if (half_edge == -1) { + b_found_impassable_crossroad = true; break; + } else halfEdgeTwin = m_topo_graph.getHalfEdgeTwin(half_edge); } else { @@ -738,14 +750,13 @@ private void restorePolylineParts_(int first_edge, int newGeometry, .setHalfEdgeUserIndex(halfEdgeTwin, visitedEdges, 1); toEdge = half_edge; prevailingLength += prevailingDirection_(shape, half_edge); + edgeCount++; } } else { // toEdge has been found in the first while loop. This happens when // we go around a face. // Closed loops need special processing as we do not know where the // polyline started or ended. - // TODO: correctly process closed polylines (is_closed_path == - // true). if (m_from_edge_for_polylines != -1) { fromEdge = m_from_edge_for_polylines; @@ -761,7 +772,7 @@ private void restorePolylineParts_(int first_edge, int newGeometry, // Crossroads is here. Pass through the crossroad. toEdge = tryMoveThroughCrossroadBackwards_(fromEdge); if (toEdge == -1) - throw new GeometryException("internal error");// what? + throw GeometryException.GeometryInternalError();// what? } assert (isGoodParentage(getCombinedHalfEdgeParentage_(m_from_edge_for_polylines))); @@ -782,12 +793,22 @@ private void restorePolylineParts_(int first_edge, int newGeometry, fromEdge = m_topo_graph.getHalfEdgeTwin(fromEdge); assert (isGoodParentage(getCombinedHalfEdgeParentage_(fromEdge))); } + int newPath = shape.insertPath(newGeometry, -1);// add new path at the // end half_edge = fromEdge; int cluster = m_topo_graph.getHalfEdgeOrigin(fromEdge); + int clusterLast = m_topo_graph.getHalfEdgeTo(toEdge); + boolean b_closed = clusterLast == cluster; + // The linestrings can touch at boundary points only, while closed path + // has no boundary, therefore no other path can touch it. + // Therefore, if a closed path touches another path, we need to split + // the closed path in two to make the result OGC simple. + boolean b_closed_linestring_touches_other_linestring = b_closed + && b_found_impassable_crossroad; + int vert = selectVertex_(cluster, shape); - assert (vert != -1); + assert(vert != -1); int vertex_dominant = getVertexByID_(vert, geometry_dominant); shape.addVertex(newPath, vertex_dominant); @@ -795,25 +816,36 @@ private void restorePolylineParts_(int first_edge, int newGeometry, m_topo_graph.setClusterUserIndex(cluster, visitedClusters, 1); } + int counter = 0; + int splitAt = b_closed_linestring_touches_other_linestring ? (edgeCount + 1) / 2 : -1; while (true) { int clusterTo = m_topo_graph.getHalfEdgeTo(half_edge); int vert_1 = selectVertex_(clusterTo, shape); vertex_dominant = getVertexByID_(vert_1, geometry_dominant); shape.addVertex(newPath, vertex_dominant); + counter++; if (visitedClusters != -1) { m_topo_graph.setClusterUserIndex(clusterTo, visitedClusters, 1); } + if (b_closed_linestring_touches_other_linestring + && counter == splitAt) { + newPath = shape.insertPath(newGeometry, -1);// add new path at + // the end + shape.addVertex(newPath, vertex_dominant); + } + assert (isGoodParentage(getCombinedHalfEdgeParentage_(half_edge))); if (half_edge == toEdge) break; + int halfEdgeNext = m_topo_graph.getHalfEdgeNext(half_edge); if (m_topo_graph.getHalfEdgePrev(m_topo_graph .getHalfEdgeTwin(half_edge)) != m_topo_graph .getHalfEdgeTwin(halfEdgeNext)) {// crossroads. half_edge = tryMoveThroughCrossroadForward_(half_edge); if (half_edge == -1) - throw new GeometryException("internal error");// a bug. This + throw GeometryException.GeometryInternalError();// a bug. This // shoulf // never // happen @@ -966,7 +998,7 @@ static MultiPoint processMultiPointIntersectOrDiff_(MultiPoint multi_point, boolean bFirstOut = true; boolean bArea = (intersector.getDimension() == 2); if (intersector.getDimension() != 1 && intersector.getDimension() != 2) - throw new GeometryException("internal error"); + throw GeometryException.GeometryInternalError(); for (int ipoints = 0; ipoints < npoints;) { int num = multi_point.queryCoordinates(input_points, 1000, ipoints, @@ -1034,7 +1066,7 @@ static Point processPointIntersectOrDiff_(Point point, PolygonUtils.PiPResult[] test_results = new PolygonUtils.PiPResult[1]; boolean bArea = intersector.getDimension() == 2; if (intersector.getDimension() != 1 && intersector.getDimension() != 2) - throw new GeometryException("internal error"); + throw GeometryException.GeometryInternalError(); input_points[0] = point.getXY(); if (bArea) PolygonUtils.testPointsInArea2D(intersector, input_points, 1, @@ -1064,8 +1096,11 @@ static Point difference(Point point, Geometry geom, double tolerance) { static Point intersection(Point point, Point point2, double tolerance) { if (point.isEmpty() || point2.isEmpty()) return (Point) point.createInstance(); - if (Point2D.distance(point.getXY(), point2.getXY()) < tolerance) { - return point; + + if (CrackAndCluster.non_empty_points_need_to_cluster(tolerance, point, + point2)) { + return CrackAndCluster.cluster_non_empty_points(point, point2, 1, + 1, 1, 1); } return (Point) point.createInstance(); @@ -1076,7 +1111,9 @@ static Point difference(Point point, Point point2, double tolerance) { return (Point) point.createInstance(); if (point2.isEmpty()) return point; - if (Point2D.distance(point.getXY(), point2.getXY()) < tolerance) { + + if (CrackAndCluster.non_empty_points_need_to_cluster(tolerance, point, + point2)) { return (Point) point.createInstance(); } @@ -1101,77 +1138,83 @@ MultiVertexGeometry planarSimplify(EditShape shape, int geom, // This method will produce a polygon from a polyline when // b_use_winding_rule_for_polygons is true. This is used by buffer. m_topo_graph = new TopoGraph(); - if (dirty_result - && shape.getGeometryType(geom) != Geometry.Type.MultiPoint - .value()) { - PlaneSweepCrackerHelper plane_sweeper = new PlaneSweepCrackerHelper(); - plane_sweeper.sweepVertical(shape, tolerance); - if (plane_sweeper.hadCompications())// shame. The one pass - // planesweep had some - // complications. Need to do - // full crack and cluster. - { + try + { + if (dirty_result + && shape.getGeometryType(geom) != Geometry.Type.MultiPoint + .value()) { + PlaneSweepCrackerHelper plane_sweeper = new PlaneSweepCrackerHelper(); + plane_sweeper.sweepVertical(shape, tolerance); + if (plane_sweeper.hadCompications())// shame. The one pass + // planesweep had some + // complications. Need to do + // full crack and cluster. + { + CrackAndCluster.execute(shape, tolerance, progress_tracker); + } + } else { CrackAndCluster.execute(shape, tolerance, progress_tracker); } - } else { - CrackAndCluster.execute(shape, tolerance, progress_tracker); + if (!b_use_winding_rule_for_polygons + || shape.getGeometryType(geom) == Geometry.Type.MultiPoint + .value()) + m_topo_graph.setAndSimplifyEditShapeAlternate(shape, geom, progress_tracker); + else + m_topo_graph.setAndSimplifyEditShapeWinding(shape, geom, progress_tracker); + + int ID_a = m_topo_graph.getGeometryID(geom); + initMaskLookupArray_((ID_a) + 1); + m_mask_lookup[ID_a] = true; // Works only when there is a single + // geometry in the edit shape. + // To make it work when many geometries are present, this need to be + // modified. + + if (shape.getGeometryType(geom) == Geometry.Type.Polygon.value() + || (b_use_winding_rule_for_polygons && shape + .getGeometryType(geom) != Geometry.Type.MultiPoint + .value())) { + // geom can be a polygon or a polyline. + // It can be a polyline only when the winding rule is true. + int resGeom = topoOperationPolygonPolygon_(geom, -1, -1); + + Polygon polygon = (Polygon) shape.getGeometry(resGeom); + if (!dirty_result) { + ((MultiVertexGeometryImpl) polygon._getImpl()).setIsSimple( + GeometryXSimple.Strong, tolerance, false); + ((MultiPathImpl) polygon._getImpl())._updateOGCFlags(); + } else + ((MultiVertexGeometryImpl) polygon._getImpl()).setIsSimple( + GeometryXSimple.Weak, 0.0, false);// dirty result means + // simple but with 0 + // tolerance. + + return polygon; + } else if (shape.getGeometryType(geom) == Geometry.Type.Polyline + .value()) { + int resGeom = topoOperationPolylinePolylineOrPolygon_(-1); + + Polyline polyline = (Polyline) shape.getGeometry(resGeom); + if (!dirty_result) + ((MultiVertexGeometryImpl) polyline._getImpl()).setIsSimple( + GeometryXSimple.Strong, tolerance, false); + + return polyline; + } else if (shape.getGeometryType(geom) == Geometry.Type.MultiPoint + .value()) { + int resGeom = topoOperationMultiPoint_(); + + MultiPoint mp = (MultiPoint) shape.getGeometry(resGeom); + if (!dirty_result) + ((MultiVertexGeometryImpl) mp._getImpl()).setIsSimple( + GeometryXSimple.Strong, tolerance, false); + + return mp; + } else { + throw GeometryException.GeometryInternalError(); + } } - if (!b_use_winding_rule_for_polygons - || shape.getGeometryType(geom) == Geometry.Type.MultiPoint - .value()) - m_topo_graph.setAndSimplifyEditShapeAlternate(shape, geom); - else - m_topo_graph.setAndSimplifyEditShapeWinding(shape, geom); - - int ID_a = m_topo_graph.getGeometryID(geom); - initMaskLookupArray_((ID_a) + 1); - m_mask_lookup[ID_a] = true; // Works only when there is a single - // geometry in the edit shape. - // To make it work when many geometries are present, this need to be - // modified. - - if (shape.getGeometryType(geom) == Geometry.Type.Polygon.value() - || (b_use_winding_rule_for_polygons && shape - .getGeometryType(geom) != Geometry.Type.MultiPoint - .value())) { - // geom can be a polygon or a polyline. - // It can be a polyline only when the winding rule is true. - int resGeom = topoOperationPolygonPolygon_(geom, -1, -1); - - Polygon polygon = (Polygon) shape.getGeometry(resGeom); - if (!dirty_result) { - ((MultiVertexGeometryImpl) polygon._getImpl()).setIsSimple( - GeometryXSimple.Strong, tolerance, false); - ((MultiPathImpl) polygon._getImpl())._updateOGCFlags(); - } else - ((MultiVertexGeometryImpl) polygon._getImpl()).setIsSimple( - GeometryXSimple.Weak, 0.0, false);// dirty result means - // simple but with 0 - // tolerance. - - return polygon; - } else if (shape.getGeometryType(geom) == Geometry.Type.Polyline - .value()) { - int resGeom = topoOperationPolylinePolylineOrPolygon_(-1); - - Polyline polyline = (Polyline) shape.getGeometry(resGeom); - if (!dirty_result) - ((MultiVertexGeometryImpl) polyline._getImpl()).setIsSimple( - GeometryXSimple.Strong, tolerance, false); - - return polyline; - } else if (shape.getGeometryType(geom) == Geometry.Type.MultiPoint - .value()) { - int resGeom = topoOperationMultiPoint_(); - - MultiPoint mp = (MultiPoint) shape.getGeometry(resGeom); - if (!dirty_result) - ((MultiVertexGeometryImpl) mp._getImpl()).setIsSimple( - GeometryXSimple.Strong, tolerance, false); - - return mp; - } else { - throw new GeometryException("internal error"); + finally { + m_topo_graph.removeShape(); } } @@ -1184,6 +1227,13 @@ static MultiVertexGeometry planarSimplify(MultiVertexGeometry input_geom, use_winding_rule_for_polygons, dirty_result, progress_tracker); } + static MultiVertexGeometry simplifyOGC(MultiVertexGeometry input_geom, double tolerance, boolean dirty_result, ProgressTracker progress_tracker) + { + TopologicalOperations topoOps = new TopologicalOperations(); + topoOps.m_bOGCOutput = true; + return topoOps.planarSimplifyImpl_(input_geom, tolerance, false, dirty_result, progress_tracker); + } + public int difference(int geometry_a, int geometry_b) { int gtA = m_topo_graph.getShape().getGeometryType(geometry_a); int gtB = m_topo_graph.getShape().getGeometryType(geometry_b); @@ -1207,7 +1257,7 @@ public int difference(int geometry_a, int geometry_b) { if (dim_a == 0) return topoOperationMultiPoint_(); - throw new GeometryException("internal error"); + throw GeometryException.GeometryInternalError(); } int dissolve(int geometry_a, int geometry_b) { @@ -1239,7 +1289,7 @@ int dissolve(int geometry_a, int geometry_b) { if (dim_a == 0 && dim_b == 0) return topoOperationMultiPoint_(); - throw new GeometryException("internal error"); + throw GeometryException.GeometryInternalError(); } public int intersection(int geometry_a, int geometry_b) { @@ -1275,7 +1325,7 @@ public int intersection(int geometry_a, int geometry_b) { // else return topoOperationMultiPoint_(); - throw new GeometryException("internal error"); + throw GeometryException.GeometryInternalError(); } int[] intersectionEx(int geometry_a, int geometry_b) { @@ -1315,7 +1365,7 @@ int[] intersectionEx(int geometry_a, int geometry_b) { return res; } - throw new GeometryException("internal error"); + throw GeometryException.GeometryInternalError(); } public int symmetricDifference(int geometry_a, int geometry_b) { @@ -1338,7 +1388,7 @@ public int symmetricDifference(int geometry_a, int geometry_b) { if (dim_a == 0 && dim_b == 0) return topoOperationMultiPoint_(); - throw new GeometryException("internal error"); + throw GeometryException.GeometryInternalError(); } int extractShape(int geometry_in) { @@ -1368,7 +1418,7 @@ int extractShape(int geometry_in) { if (dim_a == 0) return topoOperationMultiPoint_(); - throw new GeometryException("internal error"); + throw GeometryException.GeometryInternalError(); } static Geometry normalizeInputGeometry_(Geometry geom) { @@ -1550,7 +1600,7 @@ public static Geometry dissolve(Geometry geometry_a, Geometry geometry_b, } // break; default: - throw new GeometryException("internal error"); + throw GeometryException.GeometryInternalError(); } } @@ -1635,12 +1685,26 @@ static Geometry dissolveDirty(ArrayList geometries, public static Geometry intersection(Geometry geometry_a, Geometry geometry_b, SpatialReference sr, ProgressTracker progress_tracker) { + Envelope2D env2D_1 = new Envelope2D(); geometry_a.queryEnvelope2D(env2D_1); Envelope2D env2D_2 = new Envelope2D(); geometry_b.queryEnvelope2D(env2D_2); - if (!env2D_1.isIntersecting(env2D_2))// also includes the empty geometry - // cases + + Envelope2D envMerged = new Envelope2D(); + envMerged.setCoords(env2D_1); + envMerged.merge(env2D_2); + double tolerance = InternalUtils.calculateToleranceFromGeometry(sr, + envMerged, true);// conservative to have same effect as simplify + + Envelope2D e = new Envelope2D(); + e.setCoords(env2D_2); + double tol_cluster = InternalUtils + .adjust_tolerance_for_TE_clustering(tolerance); + e.inflate(tol_cluster, tol_cluster); + + if (!env2D_1.isIntersecting(e))// also includes the empty geometry + // cases { if (geometry_a.getDimension() <= geometry_b.getDimension()) return normalizeResult_( @@ -1652,11 +1716,6 @@ public static Geometry intersection(Geometry geometry_a, normalizeInputGeometry_(geometry_b.createInstance()), geometry_a, geometry_b, '&'); } - Envelope2D envMerged = new Envelope2D(); - envMerged.setCoords(env2D_1); - envMerged.merge(env2D_2); - double tolerance = InternalUtils.calculateToleranceFromGeometry(sr, - envMerged, true);// conservative to have same effect as simplify TopologicalOperations topoOps = new TopologicalOperations(); EditShape edit_shape = new EditShape(); @@ -1684,12 +1743,26 @@ public static Geometry intersection(Geometry geometry_a, static Geometry[] intersectionEx(Geometry geometry_a, Geometry geometry_b, SpatialReference sr, ProgressTracker progress_tracker) { Geometry[] res_vec = new Geometry[3]; + Envelope2D env2D_1 = new Envelope2D(); geometry_a.queryEnvelope2D(env2D_1); Envelope2D env2D_2 = new Envelope2D(); geometry_b.queryEnvelope2D(env2D_2); - if (!env2D_1.isIntersecting(env2D_2))// also includes the empty geometry - // cases + + Envelope2D envMerged = new Envelope2D(); + envMerged.setCoords(env2D_1); + envMerged.merge(env2D_2); + double tolerance = InternalUtils.calculateToleranceFromGeometry(sr, + envMerged, true);// conservative to have same effect as simplify + + Envelope2D e = new Envelope2D(); + e.setCoords(env2D_2); + double tol_cluster = InternalUtils + .adjust_tolerance_for_TE_clustering(tolerance); + e.inflate(tol_cluster, tol_cluster); + + if (!env2D_1.isIntersecting(e))// also includes the empty geometry + // cases { if (geometry_a.getDimension() <= geometry_b.getDimension()) { Geometry geom = normalizeResult_( @@ -1708,11 +1781,6 @@ static Geometry[] intersectionEx(Geometry geometry_a, Geometry geometry_b, } } - Envelope2D envMerged = new Envelope2D(); - envMerged.setCoords(env2D_1); - envMerged.merge(env2D_2); - double tolerance = InternalUtils.calculateToleranceFromGeometry(sr, - envMerged, true);// conservative to have same effect as simplify TopologicalOperations topoOps = new TopologicalOperations(); EditShape edit_shape = new EditShape(); @@ -1829,53 +1897,6 @@ private void flushVertices_(int geometry, AttributeStreamOfInt32 vertices) { shape.setClosedPath(path, true);// need to close polygon rings } - private void removeSpikes_(int cuttee, int cutter) { - int idCuttee = m_topo_graph.getGeometryID(cuttee); - int idCutter = m_topo_graph.getGeometryID(cutter); - int visitedIndex = m_topo_graph.createUserIndexForHalfEdges(); - for (int cluster = m_topo_graph.getFirstCluster(); cluster != -1; cluster = m_topo_graph - .getNextCluster(cluster)) { - int firstHalfEdge = m_topo_graph.getClusterHalfEdge(cluster); - if (firstHalfEdge == -1) - continue; - - int half_edge = firstHalfEdge; - - do { - int visited = m_topo_graph.getHalfEdgeUserIndex(half_edge, - visitedIndex); - if (visited != 1) { - int halfEdgeParentage = m_topo_graph - .getHalfEdgeParentage(half_edge); - int halfEdgeFaceParentage = m_topo_graph - .getHalfEdgeFaceParentage(half_edge); - if (halfEdgeParentage != (idCuttee | idCutter) - && halfEdgeFaceParentage != 0) { - int faceHalfEdge = half_edge; - do { - int faceHalfEdgeNext = m_topo_graph - .getHalfEdgeNext(faceHalfEdge); - if (m_topo_graph.getHalfEdgePrev(faceHalfEdge) == m_topo_graph - .getHalfEdgeTwin(faceHalfEdge)) - m_topo_graph.deleteEdgeInternal_(faceHalfEdge); - else - m_topo_graph.setHalfEdgeUserIndex(faceHalfEdge, - visitedIndex, 1); - - faceHalfEdge = faceHalfEdgeNext; - } while (faceHalfEdge != half_edge); - } else { - m_topo_graph.setHalfEdgeUserIndex(half_edge, - visitedIndex, 1); - } - } - - half_edge = m_topo_graph.getHalfEdgeNext(m_topo_graph - .getHalfEdgeTwin(half_edge)); - } while (half_edge != firstHalfEdge); - } - } - private void setHalfEdgeOrientations_(int orientationIndex, int cutter) { EditShape shape = m_topo_graph.getShape(); @@ -2031,7 +2052,7 @@ private void processPolygonCuts_(int orientationIndex, int sideIndex, private void cutPolygonPolyline_(int sideIndex, int cuttee, int cutter, AttributeStreamOfInt32 cutHandles) { - removeSpikes_(cuttee, cutter); + m_topo_graph.removeSpikes_(); int orientationIndex = -1; if (sideIndex != -1) { @@ -2056,5 +2077,14 @@ private void cutPolygonPolyline_(int sideIndex, int cuttee, int cutter, CompareCuts compareCuts = new CompareCuts(shape); cutHandles.Sort(0, cutCount, compareCuts); } + + //call this if EditShape instance has to survive the TopologicalOperations life. + void removeShape() { + if (m_topo_graph != null) { + m_topo_graph.removeShape(); + m_topo_graph = null; + } + + } } diff --git a/src/main/java/com/esri/core/geometry/Treap.java b/src/main/java/com/esri/core/geometry/Treap.java index e97840a6..d654fa3d 100644 --- a/src/main/java/com/esri/core/geometry/Treap.java +++ b/src/main/java/com/esri/core/geometry/Treap.java @@ -199,19 +199,21 @@ public int addBiggestElement(int element, int treap) { // get_duplicate_element reutrns the node of the already existing element. public int addElementAtPosition(int prevNode, int nextNode, int element, boolean bUnique, boolean bCallCompare, int treap) { - int treap_; - if (treap == -1) { + int treap_ = treap; + if (treap_ == -1) { if (m_defaultTreap == nullNode()) m_defaultTreap = createTreap(-1); treap_ = m_defaultTreap; - } else { - treap_ = treap; } // dbg_check_(m_root); - if (getRoot_(treap_) == nullNode() - || (prevNode == nullNode() && nextNode == nullNode())) - throw new GeometryException("invald call"); + if (getRoot_(treap_) == nullNode()) { + assert (nextNode == nullNode() && prevNode == nullNode()); + int root = newNode_(element); + setRoot_(root, treap_); + addToList_(-1, root, treap_); + return root; + } int cmpNext; int cmpPrev; diff --git a/src/main/java/com/esri/core/geometry/WktParser.java b/src/main/java/com/esri/core/geometry/WktParser.java index 02fbc058..7e5afcb6 100644 --- a/src/main/java/com/esri/core/geometry/WktParser.java +++ b/src/main/java/com/esri/core/geometry/WktParser.java @@ -264,9 +264,12 @@ private void geometry_() { m_current_token_type = WktToken.geometrycollection; m_function_stack.add(State.geometryCollectionStart); } else { - String snippet = (m_wkt_string.length() > 200 ? - m_wkt_string.substring(0,200)+"..." : m_wkt_string); - throw new IllegalArgumentException("Could not parse Well-Known Text: "+snippet); + //String snippet = (m_wkt_string.length() > 200 ? m_wkt_string + // .substring(0, 200) + "..." : m_wkt_string); + //throw new IllegalArgumentException( + // "Could not parse Well-Known Text: " + snippet); + throw new IllegalArgumentException( + "Could not parse Well-Known Text around position: " + m_end_token); } m_function_stack.add(State.attributes); diff --git a/src/main/java/com/esri/core/geometry/ogc/OGCConcreteGeometryCollection.java b/src/main/java/com/esri/core/geometry/ogc/OGCConcreteGeometryCollection.java index a111cf23..4d91d7e2 100644 --- a/src/main/java/com/esri/core/geometry/ogc/OGCConcreteGeometryCollection.java +++ b/src/main/java/com/esri/core/geometry/ogc/OGCConcreteGeometryCollection.java @@ -195,20 +195,28 @@ public boolean isSimple() { } /** - * isSimpleRelaxed is not supported for the GeometryCollection instance. + * makeSimpleRelaxed is not supported for the GeometryCollection instance. * */ @Override - public boolean isSimpleRelaxed() { + public OGCGeometry makeSimple() { throw new UnsupportedOperationException(); } + @Override + public boolean isSimpleRelaxed() { + for (int i = 0, n = numGeometries(); i < n; i++) + if (!geometryN(i).isSimpleRelaxed()) + return false; + return true; + } + /** - * MakeSimpleRelaxed is not supported for the GeometryCollection instance. + * makeSimpleRelaxed is not supported for the GeometryCollection instance. * */ @Override - public OGCGeometry MakeSimpleRelaxed(boolean forceProcessing) { + public OGCGeometry makeSimpleRelaxed(boolean forceProcessing) { throw new UnsupportedOperationException(); } @@ -310,5 +318,9 @@ public OGCGeometry convertToMulti() { return this; } - + + @Override + public String asJson() { + throw new UnsupportedOperationException(); + } } diff --git a/src/main/java/com/esri/core/geometry/ogc/OGCGeometry.java b/src/main/java/com/esri/core/geometry/ogc/OGCGeometry.java index 1a6e8b09..9783bb94 100644 --- a/src/main/java/com/esri/core/geometry/ogc/OGCGeometry.java +++ b/src/main/java/com/esri/core/geometry/ogc/OGCGeometry.java @@ -84,24 +84,27 @@ public String asText() { return GeometryEngine.geometryToWkt(getEsriGeometry(), 0); } - public String toString() { - String snippet = asText(); - if (snippet.length() > 200) { snippet = snippet.substring(0, 197)+"..."; } - return String.format("%s: %s", this.getClass().getSimpleName(), snippet); - } - public ByteBuffer asBinary() { OperatorExportToWkb op = (OperatorExportToWkb) OperatorFactoryLocal .getInstance().getOperator(Operator.Type.ExportToWkb); return op.execute(0, getEsriGeometry(), null); } - public String asGeoJson() { - OperatorExportToGeoJson op = (OperatorExportToGeoJson) OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.ExportToGeoJson); - return op.execute(getEsriGeometry()); - } + public String asGeoJson() { + OperatorExportToGeoJson op = (OperatorExportToGeoJson) OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.ExportToGeoJson); + return op.execute(getEsriGeometry()); + } + + /** + * + * @return Convert to REST JSON. + */ + public String asJson() { + return GeometryEngine.geometryToJson(esriSR, getEsriGeometry()); + } + public boolean isEmpty() { return getEsriGeometry().isEmpty(); } @@ -137,10 +140,12 @@ public double MaxMeasure() { * "simple" for each geometry type. * * The method has O(n log n) complexity when the input geometry is simple. - * For non-simple geometries, it terminates immediately when the first issue is - * encountered. + * For non-simple geometries, it terminates immediately when the first issue + * is encountered. * * @return True if geometry is simple and false otherwise. + * + * Note: If isSimple is true, then isSimpleRelaxed is true too. */ public boolean isSimple() { return OperatorSimplifyOGC.local().isSimpleOGC(getEsriGeometry(), @@ -151,26 +156,47 @@ public boolean isSimple() { * Extension method - checks if geometry is simple for Geodatabase. * * @return Returns true if geometry is simple, false otherwise. + * + * Note: If isSimpleRelaxed is true, then isSimple is either true or false. Geodatabase has more relaxed requirements for simple geometries than OGC. */ public boolean isSimpleRelaxed() { OperatorSimplify op = (OperatorSimplify) OperatorFactoryLocal .getInstance().getOperator(Operator.Type.Simplify); - return op - .isSimpleAsFeature(getEsriGeometry(), esriSR, true, null, null); + return op.isSimpleAsFeature(getEsriGeometry(), esriSR, true, null, null); } + @Deprecated + /** + * Use makeSimpleRelaxed instead. + */ + public OGCGeometry MakeSimpleRelaxed(boolean forceProcessing) { + return makeSimpleRelaxed(forceProcessing); + } /** * Makes a simple geometry for Geodatabase. * * @return Returns simplified geometry. + * + * Note: isSimpleRelaxed should return true after this operation. */ - public OGCGeometry MakeSimpleRelaxed(boolean forceProcessing) { + public OGCGeometry makeSimpleRelaxed(boolean forceProcessing) { OperatorSimplify op = (OperatorSimplify) OperatorFactoryLocal .getInstance().getOperator(Operator.Type.Simplify); return OGCGeometry.createFromEsriGeometry( op.execute(getEsriGeometry(), esriSR, forceProcessing, null), esriSR); } + + /** + * Resolves topological issues in this geometry and makes it Simple according to OGC specification. + * + * @return Returns simplified geometry. + * + * Note: isSimple and isSimpleRelaxed should return true after this operation. + */ + public OGCGeometry makeSimple() { + return simplifyBunch_(getEsriGeometryCursor()); + } public boolean is3D() { return getEsriGeometry().getDescription().hasAttribute( @@ -184,14 +210,17 @@ public boolean isMeasured() { abstract public OGCGeometry boundary(); - // query + /** + * OGC equals + * + */ public boolean equals(OGCGeometry another) { com.esri.core.geometry.Geometry geom1 = getEsriGeometry(); com.esri.core.geometry.Geometry geom2 = another.getEsriGeometry(); return com.esri.core.geometry.GeometryEngine.equals(geom1, geom2, getEsriSpatialReference()); } - + public boolean disjoint(OGCGeometry another) { com.esri.core.geometry.Geometry geom1 = getEsriGeometry(); com.esri.core.geometry.Geometry geom2 = another.getEsriGeometry(); @@ -264,14 +293,6 @@ public double distance(OGCGeometry another) { // As a result there are at most three geometries, each geometry is Simple. // Afterwards // it produces a single OGCGeometry. - // - // Note: Not complete yet. We'll use this method to implement the OGC - // Simplify (or MakeValid method) - // At this moment, method removes self intersections, and clusters vertices, - // but may sometimes - // produce geometries with self-tangency or polygons with disconnected - // interior - // which are simple for ArcObjects, but non-simple for OGC. private OGCGeometry simplifyBunch_(GeometryCursor gc) { // Combines geometries into multipoint, polyline, and polygon types, // simplifying them and unioning them, @@ -640,10 +661,22 @@ protected boolean isConcreteGeometryCollection() { public void setSpatialReference(SpatialReference esriSR_) { esriSR = esriSR_; } - + /** - *Converts this Geometry to the OGCMulti* if it is not OGCMulti* or OGCGeometryCollection already. + * Converts this Geometry to the OGCMulti* if it is not OGCMulti* or + * OGCGeometryCollection already. + * * @return OGCMulti* or OGCGeometryCollection instance. */ public abstract OGCGeometry convertToMulti(); -} + + @Override + public String toString() { + String snippet = asText(); + if (snippet.length() > 200) { + snippet = snippet.substring(0, 197) + "..."; + } + return String + .format("%s: %s", this.getClass().getSimpleName(), snippet); + } +} \ No newline at end of file diff --git a/src/main/java/com/esri/core/geometry/ogc/OGCPolygon.java b/src/main/java/com/esri/core/geometry/ogc/OGCPolygon.java index c9f9fcff..5a038d2a 100644 --- a/src/main/java/com/esri/core/geometry/ogc/OGCPolygon.java +++ b/src/main/java/com/esri/core/geometry/ogc/OGCPolygon.java @@ -49,7 +49,7 @@ public ByteBuffer asBinary() { * Returns the exterior ring of this Polygon. * @return OGCLinearRing instance. */ - public OGCLineString exterorRing() { + public OGCLineString exteriorRing() { if (polygon.isEmpty()) return new OGCLinearRing((Polygon) polygon.createInstance(), 0, esriSR, true); diff --git a/src/test/java/com/esri/core/geometry/GeometryUtils.java b/src/test/java/com/esri/core/geometry/GeometryUtils.java index de3bcf04..c1af6866 100644 --- a/src/test/java/com/esri/core/geometry/GeometryUtils.java +++ b/src/test/java/com/esri/core/geometry/GeometryUtils.java @@ -41,34 +41,6 @@ static Geometry getGeometryFromJSon(String jsonStr) { } } - public static void testMultiplePath(MultiPath mp1, MultiPath mp2) { - return; - /*int count1 = mp1.getPointCount(); - int count2 = mp2.getPointCount(); - - System.out.println("From Rest vertices count: " + count1); - System.out.println("From Borg count: " + count2); - // Assert.assertTrue(count1==count2); - - int len = mp1.getPointCount(); - - for (int i = 0; i < len; i++) { - Point p = mp1.getPoint(i); - Point p2 = mp2.getPoint(i); - System.out.println("for rest: [" + p.getX() + "," + p.getY() + "]"); - System.out.println("for proj: [" + p2.getX() + "," + p2.getY() - + "]"); - @SuppressWarnings("unused") - double deltaX = p2.getX() - p.getX(); - @SuppressWarnings("unused") - double deltaY = p2.getY() - p.getY(); - - // Assert.assertTrue(deltaX<1e-7); - // Assert.assertTrue(deltaY<1e-7); - } - */ - } - public enum SpatialRelationType { esriGeometryRelationCross, esriGeometryRelationDisjoint, esriGeometryRelationIn, esriGeometryRelationInteriorIntersection, esriGeometryRelationIntersection, esriGeometryRelationLineCoincidence, esriGeometryRelationLineTouch, esriGeometryRelationOverlap, esriGeometryRelationPointTouch, esriGeometryRelationTouch, esriGeometryRelationWithin, esriGeometryRelationRelation } diff --git a/src/test/java/com/esri/core/geometry/TestAttributes.java b/src/test/java/com/esri/core/geometry/TestAttributes.java index fe3f6a67..bd59cc9b 100644 --- a/src/test/java/com/esri/core/geometry/TestAttributes.java +++ b/src/test/java/com/esri/core/geometry/TestAttributes.java @@ -1,10 +1,11 @@ package com.esri.core.geometry; import static org.junit.Assert.*; +import junit.framework.TestCase; import org.junit.Test; -public class TestAttributes { +public class TestAttributes extends TestCase{ @Test public void testPoint() { diff --git a/src/test/java/com/esri/core/geometry/TestEnvelope2DIntersector.java b/src/test/java/com/esri/core/geometry/TestEnvelope2DIntersector.java index 4ffd03cf..b7bc7d2e 100644 --- a/src/test/java/com/esri/core/geometry/TestEnvelope2DIntersector.java +++ b/src/test/java/com/esri/core/geometry/TestEnvelope2DIntersector.java @@ -49,7 +49,7 @@ public static void testEnvelope2Dintersector() { intersector.startConstruction(); for (int i = 0; i < envelopes.size(); i++) - intersector.addEnvelope(envelopes.get(i)); + intersector.addEnvelope(i, envelopes.get(i)); intersector.endConstruction(); int count = 0; @@ -69,7 +69,7 @@ public static void testEnvelope2Dintersector() { intersector2.setTolerance(0.0); intersector2.startConstruction(); for (int i = 0; i < envelopes.size(); i++) - intersector2.addEnvelope(envelopes.get(i)); + intersector2.addEnvelope(i, envelopes.get(i)); intersector2.endConstruction(); count = 0; @@ -100,7 +100,7 @@ public static void testEnvelope2Dintersector() { intersector3.startConstruction(); for (int i = 0; i < envelopes.size(); i++) - intersector3.addEnvelope(envelopes.get(i)); + intersector3.addEnvelope(i, envelopes.get(i)); intersector3.endConstruction(); ; count = 0; @@ -128,7 +128,7 @@ public static void testEnvelope2Dintersector() { intersector4.startConstruction(); for (int i = 0; i < envelopes.size(); i++) - intersector4.addEnvelope(envelopes.get(i)); + intersector4.addEnvelope(i, envelopes.get(i)); intersector4.endConstruction(); count = 0; @@ -156,7 +156,7 @@ public static void testEnvelope2Dintersector() { intersector5.startConstruction(); for (int i = 0; i < envelopes.size(); i++) - intersector5.addEnvelope(envelopes.get(i)); + intersector5.addEnvelope(i, envelopes.get(i)); intersector5.endConstruction(); count = 0; @@ -305,12 +305,12 @@ public static void testRandom() { intersector.startRedConstruction(); for (int i = 0; i < envelopes_red.size(); i++) - intersector.addRedEnvelope(envelopes_red.get(i)); + intersector.addRedEnvelope(i, envelopes_red.get(i)); intersector.endRedConstruction(); intersector.startBlueConstruction(); for (int i = 0; i < envelopes_blue.size(); i++) - intersector.addBlueEnvelope(envelopes_blue.get(i)); + intersector.addBlueEnvelope(i, envelopes_blue.get(i)); intersector.endBlueConstruction(); while (intersector.next()) diff --git a/src/test/java/com/esri/core/geometry/TestFailed.java b/src/test/java/com/esri/core/geometry/TestFailed.java index aac65c3c..9c7a915d 100644 --- a/src/test/java/com/esri/core/geometry/TestFailed.java +++ b/src/test/java/com/esri/core/geometry/TestFailed.java @@ -36,7 +36,6 @@ public void testGeometryOperationSupport() { } catch (IllegalArgumentException ex) { noException = 0; } catch (GeometryException ex) { - System.out.println(ex.internalCode); noException = 0; } assertEquals(noException, 1); diff --git a/src/test/java/com/esri/core/geometry/TestGeodetic.java b/src/test/java/com/esri/core/geometry/TestGeodetic.java index 0c788cc2..d377e6f6 100644 --- a/src/test/java/com/esri/core/geometry/TestGeodetic.java +++ b/src/test/java/com/esri/core/geometry/TestGeodetic.java @@ -69,4 +69,4 @@ public static void testLengthAccurateCR191313() { * assertTrue(Math.abs(length - 2738362.3249366437) < 2e-9 * length); */ } - } + } diff --git a/src/test/java/com/esri/core/geometry/TestGeomToJSonExportSRFromWkiOrWkt_CR181369.java b/src/test/java/com/esri/core/geometry/TestGeomToJSonExportSRFromWkiOrWkt_CR181369.java index 9a12ac11..2de57b43 100644 --- a/src/test/java/com/esri/core/geometry/TestGeomToJSonExportSRFromWkiOrWkt_CR181369.java +++ b/src/test/java/com/esri/core/geometry/TestGeomToJSonExportSRFromWkiOrWkt_CR181369.java @@ -552,7 +552,6 @@ boolean checkResultSpatialRef(MapGeometry mapGeometry, int expectWki1, return false; if (!(Wkt != null && Wkt.length() > 0)) return false; - //System.out.println("WKT1: " + Wkt); SpatialReference sr2 = SpatialReference.create(Wkt); int wki2 = sr2.getID(); if (expectWki2 > 0) { diff --git a/src/test/java/com/esri/core/geometry/TestImportExport.java b/src/test/java/com/esri/core/geometry/TestImportExport.java index 9b4f19a3..99adee7a 100644 --- a/src/test/java/com/esri/core/geometry/TestImportExport.java +++ b/src/test/java/com/esri/core/geometry/TestImportExport.java @@ -1014,7 +1014,7 @@ public static void testImportExportWktMultiPolygon() { // Test Export to WKT MultiPolygon wktString = exporterWKT.execute(0, polygon, null); assertTrue(wktString - .equals("MULTIPOLYGON Z (((10 10 5, 20 10 5, 20 20 5, 10 20 5, 10 10 5), (12 12 3, 12 12 3, 12 12 3), (10 10 1, 12 12 1, 10 10 1)), ((90 90 88, 60 90 7, 60 60 7, 90 90 88), (70 70 7, 80 80 7, 70 80 7, 70 70 7)))")); + .equals("MULTIPOLYGON Z (((10 10 5, 20 10 5, 20 20 5, 10 20 5, 10 10 5), (12 12 3, 12 12 3, 12 12 3), (10 10 1, 12 12 1, 10 10 1)), ((90 90 88, 60 90 7, 60 60 7, 90 90 88), (70 70 7, 70 80 7, 80 80 7, 70 70 7)))")); wktParser.resetParser(wktString); while (wktParser.nextToken() != WktParser.WktToken.not_available) { } @@ -1032,7 +1032,7 @@ public static void testImportExportWktMultiPolygon() { wktString = exporterWKT.execute(WktExportFlags.wktExportPolygon, polygon, null); assertTrue(wktString - .equals("POLYGON Z ((10 10 5, 20 10 5, 20 20 5, 10 20 5, 10 10 5), (12 12 3, 12 12 3, 12 12 3), (10 10 1, 12 12 1, 10 10 1), (60 60 7, 90 90 7, 60 90 7, 60 60 7), (70 70 7, 80 80 7, 70 80 7, 70 70 7))")); + .equals("POLYGON Z ((10 10 5, 20 10 5, 20 20 5, 10 20 5, 10 10 5), (12 12 3, 12 12 3, 12 12 3), (10 10 1, 12 12 1, 10 10 1), (60 60 7, 60 90 7, 90 90 7, 60 60 7), (70 70 7, 70 80 7, 80 80 7, 70 70 7))")); wktParser.resetParser(wktString); while (wktParser.nextToken() != WktParser.WktToken.not_available) { } diff --git a/src/test/java/com/esri/core/geometry/TestIntersect2.java b/src/test/java/com/esri/core/geometry/TestIntersect2.java index f5276d69..8bdcd3c8 100644 --- a/src/test/java/com/esri/core/geometry/TestIntersect2.java +++ b/src/test/java/com/esri/core/geometry/TestIntersect2.java @@ -358,7 +358,6 @@ public void testMultiPointAndMultiPoint3() { SpatialReference.create(4326)); } catch (Exception ex) { noException = 0; - System.out.println("err: " + ex.getMessage()); } assertEquals(noException, 1); assertTrue(intersectGeom.isEmpty()); diff --git a/src/test/java/com/esri/core/geometry/TestIntersection.java b/src/test/java/com/esri/core/geometry/TestIntersection.java index 66e0bc49..ab7dd9f0 100644 --- a/src/test/java/com/esri/core/geometry/TestIntersection.java +++ b/src/test/java/com/esri/core/geometry/TestIntersection.java @@ -1002,4 +1002,40 @@ public void testIntersectionIssueLinePoly1() { assertTrue(((Polyline)res).getPathCount() == 1); assertTrue(((Polyline)res).getPointCount() == 4); } + + @Test + public void testSharedEdgeIntersection_13() + { + String s1 = "{\"rings\":[[[0.099604024000029767,0.2107958250000479],[0.14626826900007472,0.2107958250000479],[0.14626826900007472,0.18285316400005058],[0.099604024000029767,0.18285316400005058],[0.099604024000029767,0.2107958250000479]]]}"; + String s2 = "{\"paths\":[[[0.095692051000071388,0.15910190100004229],[0.10324853600002371,0.18285316400004228],[0.12359292700006108,0.18285316400004228],[0.12782611200003657,0.1705583920000322],[0.13537063000007138,0.18285316400004228]]]}"; + + Polygon polygon = (Polygon)TestCommonMethods.fromJson(s1).getGeometry(); + Polyline polyline = (Polyline)TestCommonMethods.fromJson(s2).getGeometry(); + SpatialReference sr = SpatialReference.create(4326); + + Geometry g = OperatorIntersection.local().execute(polygon, polyline, sr, null); + assertTrue(!g.isEmpty()); + } + + @Test + public void testIntersectionIssue2() { + String s1 = "{\"rings\":[[[-97.174860352323378,48.717174479818425],[-97.020624513410553,58.210155436624177],[-94.087641114245969,58.210155436624177],[-94.087641114245969,48.639781902013226],[-97.174860352323378,48.717174479818425]]]}"; + String s2 = "{\"rings\":[[[-94.08764111399995,52.68342763000004],[-94.08764111399995,56.835188018000053],[-90.285921520999977,62.345706350000057],[-94.08764111399995,52.68342763000004]]]}"; + + Polygon polygon1 = (Polygon)TestCommonMethods.fromJson(s1).getGeometry(); + Polygon polygon2 = (Polygon)TestCommonMethods.fromJson(s2).getGeometry(); + SpatialReference sr = SpatialReference.create(4326); + + GeometryCursor res = OperatorIntersection.local().execute(new SimpleGeometryCursor(polygon1), new SimpleGeometryCursor(polygon2), sr, null, 2); + Geometry g = res.next(); + assertTrue(g != null); + assertTrue(!g.isEmpty()); + Geometry g2 = res.next(); + assertTrue(g2 == null); + + String ss = "{\"paths\":[[[-94.08764111412296,52.68342763000004],[-94.08764111410767,56.83518801800005]]]}"; + Polyline polyline = (Polyline)TestCommonMethods.fromJson(ss).getGeometry(); + boolean eq = OperatorEquals.local().execute(g, polyline, sr, null); + assertTrue(eq); + } } diff --git a/src/test/java/com/esri/core/geometry/TestJsonParser.java b/src/test/java/com/esri/core/geometry/TestJsonParser.java index de188f3e..a2214e5e 100644 --- a/src/test/java/com/esri/core/geometry/TestJsonParser.java +++ b/src/test/java/com/esri/core/geometry/TestJsonParser.java @@ -464,13 +464,11 @@ public void testSpatialRef() throws JsonParseException, IOException { mapGeom = GeometryEngine.jsonToGeometry(jsonParserEnv); Assert.assertTrue(mapGeom.getSpatialReference().getID() == 4326); // showProjectedGeometryInfo(mapGeom); - // System.out.println("\n\nWKID: "+ - // SpatialReference.create(wkid).fromJson(jsonParserSR).getID()); try { GeometryEngine.jsonToGeometry(jsonParserInvalidWKID); } catch (Exception ex) { - System.out.print(ex.getMessage()); + Assert.assertTrue("Should not throw for invalid wkid", false); } } @@ -629,13 +627,13 @@ public void testGeometryToJSON() { String outputPolygon1 = GeometryEngine.geometryToJson(-1, geom);// Test // WKID // == -1 - System.out.println("Geom JSON STRING is" + outputPolygon1); + //System.out.println("Geom JSON STRING is" + outputPolygon1); String correctPolygon1 = "{\"rings\":[[[-113,34],[-105,34],[-108,40],[-113,34]]]}"; assertEquals(correctPolygon1, outputPolygon1); String outputPolygon2 = GeometryEngine.geometryToJson(4326, geom); - System.out.println("Geom JSON STRING is" + outputPolygon2); + //System.out.println("Geom JSON STRING is" + outputPolygon2); String correctPolygon2 = "{\"rings\":[[[-113,34],[-105,34],[-108,40],[-113,34]]],\"spatialReference\":{\"wkid\":4326}}"; assertEquals(correctPolygon2, outputPolygon2); diff --git a/src/test/java/com/esri/core/geometry/TestOGC.java b/src/test/java/com/esri/core/geometry/TestOGC.java index 816813bd..dcca0c3a 100644 --- a/src/test/java/com/esri/core/geometry/TestOGC.java +++ b/src/test/java/com/esri/core/geometry/TestOGC.java @@ -6,6 +6,7 @@ import com.esri.core.geometry.ogc.OGCGeometryCollection; import com.esri.core.geometry.ogc.OGCLineString; import com.esri.core.geometry.ogc.OGCMultiCurve; +import com.esri.core.geometry.ogc.OGCMultiLineString; import com.esri.core.geometry.ogc.OGCMultiPoint; import com.esri.core.geometry.ogc.OGCMultiPolygon; import com.esri.core.geometry.ogc.OGCPoint; @@ -14,6 +15,7 @@ import org.codehaus.jackson.JsonParseException; import org.json.JSONException; +import org.junit.Test; import java.io.IOException; import java.nio.ByteBuffer; @@ -29,6 +31,7 @@ protected void tearDown() throws Exception { super.tearDown(); } + @Test public void testPoint() { OGCGeometry g = OGCGeometry.fromText("POINT(1 2)"); assertTrue(g.geometryType().equals("Point")); @@ -44,13 +47,14 @@ public void testPoint() { assertTrue(Math.abs(a - 400) < 1e-1); } + @Test public void testPolygon() { OGCGeometry g = OGCGeometry .fromText("POLYGON((-10 -10, 10 -10, 10 10, -10 10, -10 -10), (-5 -5, -5 5, 5 5, 5 -5, -5 -5))"); assertTrue(g.geometryType().equals("Polygon")); OGCPolygon p = (OGCPolygon) g; assertTrue(p.numInteriorRing() == 1); - OGCLineString ls = p.exterorRing(); + OGCLineString ls = p.exteriorRing(); // assertTrue(ls.pointN(1).equals(OGCGeometry.fromText("POINT(10 -10)"))); boolean b = ls .equals(OGCGeometry @@ -66,6 +70,7 @@ public void testPolygon() { assertTrue(s.equals("MULTILINESTRING ((-10 -10, 10 -10, 10 10, -10 10, -10 -10), (-5 -5, -5 5, 5 5, 5 -5, -5 -5))")); } + @Test public void testGeometryCollection() throws JSONException { OGCGeometry g = OGCGeometry .fromText("GEOMETRYCOLLECTION(POLYGON EMPTY, POINT(1 1), LINESTRING EMPTY, MULTIPOLYGON EMPTY, MULTILINESTRING EMPTY)"); @@ -137,13 +142,14 @@ public void testGeometryCollection() throws JSONException { } + @Test public void testFirstPointOfPolygon() { OGCGeometry g = OGCGeometry .fromText("POLYGON((-10 -10, 10 -10, 10 10, -10 10, -10 -10), (-5 -5, -5 5, 5 5, 5 -5, -5 -5))"); assertTrue(g.geometryType().equals("Polygon")); OGCPolygon p = (OGCPolygon) g; assertTrue(p.numInteriorRing() == 1); - OGCLineString ls = p.exterorRing(); + OGCLineString ls = p.exteriorRing(); OGCPoint p1 = ls.pointN(1); assertTrue(ls.pointN(1).equals(OGCGeometry.fromText("POINT(10 -10)"))); OGCPoint p2 = ls.pointN(3); @@ -155,6 +161,7 @@ public void testFirstPointOfPolygon() { } + @Test public void testFirstPointOfLineString() { OGCGeometry g = OGCGeometry .fromText("LINESTRING(-10 -10, 10 -10, 10 10, -10 10, -10 -10)"); @@ -167,6 +174,7 @@ public void testFirstPointOfLineString() { assertTrue(ms.equals("MULTILINESTRING ((-10 -10, 10 -10, 10 10, -10 10, -10 -10))")); } + @Test public void testPointInPolygon() { OGCGeometry g = OGCGeometry .fromText("POLYGON((-10 -10, 10 -10, 10 10, -10 10, -10 -10), (-5 -5, -5 5, 5 5, 5 -5, -5 -5))"); @@ -179,6 +187,7 @@ public void testPointInPolygon() { assertTrue(g.disjoint(OGCGeometry.fromText("POINT(-20 1)"))); } + @Test public void testMultiPolygon() { { OGCGeometry g = OGCGeometry @@ -212,6 +221,7 @@ public void testMultiPolygon() { } } + @Test public void testMultiPolygonUnion() { OGCGeometry g = OGCGeometry .fromText("POLYGON((-10 -10, 10 -10, 10 10, -10 10, -10 -10), (-5 -5, -5 5, 5 5, 5 -5, -5 -5))"); @@ -228,6 +238,7 @@ public void testMultiPolygonUnion() { assertTrue(u.contains(OGCGeometry.fromText("POINT(100 100)"))); } + @Test public void testIntersection() { OGCGeometry g = OGCGeometry.fromText("LINESTRING(0 0, 10 10)"); OGCGeometry g2 = OGCGeometry.fromText("LINESTRING(10 0, 0 10)"); @@ -237,6 +248,7 @@ public void testIntersection() { assertTrue(u.equals(OGCGeometry.fromText("POINT(5 5)"))); } + @Test public void testPointSymDif() { OGCGeometry g1 = OGCGeometry.fromText("POINT(1 2)"); OGCGeometry g2 = OGCGeometry.fromText("POINT(3 4)"); @@ -249,6 +261,7 @@ public void testPointSymDif() { } + @Test public void testNullSr() { String wkt = "point (0 0)"; OGCGeometry g = OGCGeometry.fromText(wkt); @@ -256,6 +269,7 @@ public void testNullSr() { assertTrue(g.SRID() < 1); } + @Test public void testIsectPoint() { String wkt = "point (0 0)"; String wk2 = "point (0 0)"; @@ -271,6 +285,7 @@ public void testIsectPoint() { } } + @Test public void testIsectDisjoint() { String wk3 = "linestring (0 0, 1 1)"; String wk4 = "linestring (2 2, 4 4)"; @@ -286,6 +301,7 @@ public void testIsectDisjoint() { } } + @Test public void test_polygon_is_simple_for_OGC() { try { { @@ -395,10 +411,20 @@ public void test_polygon_is_simple_for_OGC() { } } - /* - This will fail + @Test public void test_polygon_simplify_for_OGC() { try { + { + //degenerate + String s = "{\"rings\":[[[0, 0], [0, 10], [10, 10], [10, 0], [20, 0], [10, 0], [0, 0]]]}"; + OGCGeometry g = OGCGeometry.fromJson(s); + boolean res = g.isSimple(); + assertTrue(!res); + Geometry resg = OperatorSimplifyOGC.local().execute(g.getEsriGeometry(), null, true, null); + OGCGeometry og = OGCGeometry.createFromEsriGeometry(resg, null); + String res_str = og.asText(); + assertTrue(og.isSimple()); + } { String s = "{\"rings\":[[[0, 0], [0, 10], [10, 10], [10, 0], [0, 0]]]}"; OGCGeometry g = OGCGeometry.fromJson(s); @@ -407,8 +433,10 @@ public void test_polygon_simplify_for_OGC() { assertTrue(g.isSimpleRelaxed()); Geometry resg = OperatorSimplifyOGC.local().execute(g.getEsriGeometry(), null, true, null); OGCGeometry og = OGCGeometry.createFromEsriGeometry(resg, null); + String res_str = og.asText(); assertTrue(og.geometryType().equals("Polygon")); assertTrue(((OGCPolygon)og).numInteriorRing() == 0); + assertTrue(og.isSimple()); } {// exterior ring is self-tangent @@ -461,6 +489,7 @@ public void test_polygon_simplify_for_OGC() { assertTrue(g.isSimpleRelaxed()); Geometry resg = OperatorSimplifyOGC.local().execute(g.getEsriGeometry(), null, true, null); OGCGeometry og = OGCGeometry.createFromEsriGeometry(resg, null); + String res_str = og.asText(); res = og.isSimple(); assertTrue(res); assertTrue(og.geometryType().equals("Polygon")); @@ -551,12 +580,34 @@ public void test_polygon_simplify_for_OGC() { assertTrue(((OGCPolygon)((OGCMultiPolygon)og).geometryN(0)).numInteriorRing() == 0); assertTrue(((OGCPolygon)((OGCMultiPolygon)og).geometryN(1)).numInteriorRing() == 0); } + + + { + OGCGeometry g = OGCGeometry.fromJson("{\"rings\":[[[-3,4],[6,4],[6,-3],[-3,-3],[-3,4]],[[0,2],[2,2],[0,0],[4,0],[4,2],[2,0],[2,2],[4,2],[3,3],[2,2],[1,3],[0,2]]], \"spatialReference\":{\"wkid\":4326}}"); + assertTrue(g.geometryType().equals("Polygon")); + boolean res = g.isSimple(); + assertTrue(!res); + assertTrue(!g.isSimpleRelaxed()); + OGCGeometry simpleG = g.makeSimple(); + assertTrue(simpleG.geometryType().equals("MultiPolygon")); + assertTrue(simpleG.isSimple()); + OGCMultiPolygon mp = (OGCMultiPolygon)simpleG; + assertTrue(mp.numGeometries() == 2); + OGCPolygon g1 = (OGCPolygon)mp.geometryN(0); + OGCPolygon g2 = (OGCPolygon)mp.geometryN(1); + assertTrue((g1.numInteriorRing() == 0 && g1.numInteriorRing() == 2) || + (g1.numInteriorRing() == 2 && g2.numInteriorRing() == 0)); + + OGCGeometry oldOutput = OGCGeometry.fromJson("{\"rings\":[[[-3,-3],[-3,4],[6,4],[6,-3],[-3,-3]],[[0,0],[2,0],[4,0],[4,2],[3,3],[2,2],[1,3],[0,2],[2,2],[0,0]],[[2,0],[2,2],[4,2],[2,0]]],\"spatialReference\":{\"wkid\":4326}}"); + assertTrue(oldOutput.isSimpleRelaxed()); + assertFalse(oldOutput.isSimple()); + } } catch (Exception ex) { assertTrue(false); } } - */ + @Test public void test_polyline_is_simple_for_OGC() { try { { @@ -658,6 +709,7 @@ public void test_polyline_is_simple_for_OGC() { } + @Test public void test_multipoint_is_simple_for_OGC() { try { @@ -690,6 +742,7 @@ public void test_multipoint_is_simple_for_OGC() { } + @Test public void testGeometryCollectionBuffer() { OGCGeometry g = OGCGeometry .fromText("GEOMETRYCOLLECTION(POINT(1 1), POINT(1 1), POINT(1 2), LINESTRING (0 0, 1 1, 1 0, 0 1), MULTIPOLYGON EMPTY, MULTILINESTRING EMPTY)"); @@ -699,6 +752,7 @@ public void testGeometryCollectionBuffer() { assertTrue(simpleG.geometryType().equals("GeometryCollection")); } + @Test public void testIsectTria1() { String wkt = "polygon((1 0, 3 0, 1 2, 1 0))"; String wk2 = "polygon((0 1, 2 1, 0 3, 0 1))"; @@ -713,6 +767,7 @@ public void testIsectTria1() { String s = rslt.asText(); } + @Test public void testIsectTriaJson1() throws JsonParseException, IOException { String json1 = "{\"rings\":[[[1, 0], [3, 0], [1, 2], [1, 0]]], \"spatialReference\":{\"wkid\":4326}}"; String json2 = "{\"rings\":[[[0, 1], [2, 1], [0, 3], [0, 1]]], \"spatialReference\":{\"wkid\":4326}}"; @@ -725,6 +780,7 @@ public void testIsectTriaJson1() throws JsonParseException, IOException { String s = GeometryEngine.geometryToJson(rslt.getEsriSpatialReference().getID(), rslt.getEsriGeometry()); } + @Test public void testIsectTria2() { String wkt = "polygon((1 0, 3 0, 1 2, 1 0))"; String wk2 = "polygon((0 3, 2 1, 3 1, 0 3))"; @@ -739,6 +795,7 @@ public void testIsectTria2() { String s = rslt.asText(); } + @Test public void testIsectTria3() { String wkt = "polygon((1 0, 3 0, 1 2, 1 0))"; String wk2 = "polygon((2 2, 2 1, 3 1, 2 2))"; @@ -754,6 +811,7 @@ public void testIsectTria3() { String s = rslt.asText(); } + @Test public void testMultiPointSinglePoint() { String wkt = "multipoint((1 0))"; OGCGeometry g0 = OGCGeometry.fromText(wkt); @@ -771,6 +829,7 @@ public void testMultiPointSinglePoint() { } + @Test public void testWktMultiPolygon() { String restJson = "{\"rings\": [[[-100, -100], [-100, 100], [100, 100], [100, -100], [-100, -100]], [[-90, -90], [90, 90], [-90, 90], [90, -90], [-90, -90]], [[-10, -10], [-10, 10], [10, 10], [10, -10], [-10, -10]]]}"; MapGeometry g = null; @@ -786,5 +845,44 @@ public void testWktMultiPolygon() { String wkt = OperatorExportToWkt.local().execute(0, g.getGeometry(), null); assertTrue(wkt.equals("MULTIPOLYGON (((-100 -100, 100 -100, 100 100, -100 100, -100 -100), (-90 -90, 90 -90, -90 90, 90 90, -90 -90)), ((-10 -10, 10 -10, 10 10, -10 10, -10 -10)))")); } + + @Test + public void testMultiPolygonArea() { + //MultiPolygon Area #36 + String wkt = "MULTIPOLYGON (((1001200 2432900, 1001420 2432691, 1001250 2432388, 1001498 2432325, 1001100 2432100, 1001500 2431900, 1002044 2431764, 1002059 2432120, 1002182 2432003, 1002400 2432300, 1002650 2432150, 1002610 2432323, 1002772 2432434, 1002410 2432821, 1002700 2433000, 1001824 2432866, 1001600 2433150, 1001200 2432900)), ((1000393 2433983, 1000914 2434018, 1000933 2433817, 1000568 2433834, 1000580 2433584, 1000700 2433750, 1000800 2433650, 1000700 2433450, 1000600 2433550, 1000200 2433350, 1000100 2433900, 1000393 2433983)), ((1001200 2432900, 1000878 2432891, 1000900 2433300, 1001659 2433509, 1001600 2433150, 1001200 2432900)), ((1002450 2431650, 1002300 2431650, 1002300 2431900, 1002500 2432100, 1002600 2431800, 1002450 2431800, 1002450 2431650)), ((999750 2433550, 999850 2433600, 999900 2433350, 999780 2433433, 999750 2433550)), ((1002950 2432050, 1003005 2431932, 1002850 2432250, 1002928 2432210, 1002950 2432050)), ((1002600 2431750, 1002642 2431882, 1002750 2431900, 1002750 2431750, 1002600 2431750)), ((1002950 2431750, 1003050 2431650, 1002968 2431609, 1002950 2431750)))"; + { + OGCGeometry ogcg = OGCGeometry.fromText(wkt); + assertTrue(ogcg.geometryType().equals("MultiPolygon")); + OGCMultiPolygon mp = (OGCMultiPolygon)ogcg; + double a = mp.area(); + assertTrue(Math.abs(mp.area() - 2037634.5) < a*1e-14); + } + + { + OGCGeometry ogcg = OGCGeometry.fromText(wkt); + assertTrue(ogcg.geometryType().equals("MultiPolygon")); + Geometry g = ogcg.getEsriGeometry(); + double a = g.calculateArea2D(); + assertTrue(Math.abs(a - 2037634.5) < a*1e-14); + } + } + + @Test + public void testPolylineSimplifyIssueGithub52() throws JsonParseException, IOException { + String json = "{\"paths\":[[[2,0],[4,3],[5,1],[3.25,1.875],[1,3]]],\"spatialReference\":{\"wkid\":4326}}"; + { + OGCGeometry g = OGCGeometry.fromJson(json); + assertTrue(g.geometryType().equals("LineString")); + OGCGeometry simpleG = g.makeSimple();//make ogc simple + assertTrue(simpleG.geometryType().equals("MultiLineString")); + assertTrue(simpleG.isSimpleRelaxed());//geodatabase simple + assertTrue(simpleG.isSimple());//ogc simple + OGCMultiLineString mls =(OGCMultiLineString)simpleG; + assertTrue(mls.numGeometries() == 4); + OGCGeometry baseGeom = OGCGeometry.fromJson("{\"paths\":[[[2,0],[3.25,1.875]],[[3.25,1.875],[4,3],[5,1]],[[5,1],[3.25,1.875]],[[3.25,1.875],[1,3]]],\"spatialReference\":{\"wkid\":4326}}"); + assertTrue(simpleG.equals(baseGeom)); + + } + } } diff --git a/src/test/java/com/esri/core/geometry/TestOffset.java b/src/test/java/com/esri/core/geometry/TestOffset.java index 7ef61a01..1bd18b9a 100644 --- a/src/test/java/com/esri/core/geometry/TestOffset.java +++ b/src/test/java/com/esri/core/geometry/TestOffset.java @@ -143,8 +143,6 @@ public void OffsetPolygon_(double distance, JoinType joins) { Geometry outputGeom = offset.execute(polygon, null, distance, joins, 2, 0, null); - System.out.println(GeometryUtils.getJSonStringFromGeometry(outputGeom, - null)); assertNotNull(outputGeom); if (distance > 2) { diff --git a/src/test/java/com/esri/core/geometry/TestPoint.java b/src/test/java/com/esri/core/geometry/TestPoint.java index 1a0bb15c..df53b170 100644 --- a/src/test/java/com/esri/core/geometry/TestPoint.java +++ b/src/test/java/com/esri/core/geometry/TestPoint.java @@ -1,7 +1,9 @@ package com.esri.core.geometry; import java.util.Random; + import junit.framework.TestCase; + import org.junit.Test; public class TestPoint extends TestCase { @@ -41,7 +43,6 @@ public void testEnvelope2000() { fullExtent.merge(geomExtent); } long endTime = System.nanoTime(); - System.out.println((endTime - startTime) / 1.0e6); } } @@ -110,5 +111,59 @@ public void testCopy() { copyPt = (Point) pt.copy(); assertTrue(copyPt.equals(pt)); assertTrue(copyPt.getXY().isEqual(new Point2D(11, 13))); + + assertTrue(copyPt.getXY().equals((Object)new Point2D(11, 13))); } + + @Test + public void testEnvelope2D_corners() { + Envelope2D env = new Envelope2D(0, 1, 2, 3); + assertFalse(env.equals(null)); + assertTrue(env.equals((Object)new Envelope2D(0, 1, 2, 3))); + + Point2D pt2D = env.getLowerLeft(); + assertTrue(pt2D.equals(Point2D.construct(0, 1))); + pt2D = env.getUpperLeft(); + assertTrue(pt2D.equals(Point2D.construct(0, 3))); + pt2D = env.getUpperRight(); + assertTrue(pt2D.equals(Point2D.construct(2, 3))); + pt2D = env.getLowerRight(); + assertTrue(pt2D.equals(Point2D.construct(2, 1))); + + { + Point2D[] corners = new Point2D[4]; + env.queryCorners(corners); + assertTrue(corners[0].equals(Point2D.construct(0, 1))); + assertTrue(corners[1].equals(Point2D.construct(0, 3))); + assertTrue(corners[2].equals(Point2D.construct(2, 3))); + assertTrue(corners[3].equals(Point2D.construct(2, 1))); + + env.queryCorners(corners); + assertTrue(corners[0].equals(env.queryCorner(0))); + assertTrue(corners[1].equals(env.queryCorner(1))); + assertTrue(corners[2].equals(env.queryCorner(2))); + assertTrue(corners[3].equals(env.queryCorner(3))); + } + + { + Point2D[] corners = new Point2D[4]; + env.queryCornersReversed(corners); + assertTrue(corners[0].equals(Point2D.construct(0, 1))); + assertTrue(corners[1].equals(Point2D.construct(2, 1))); + assertTrue(corners[2].equals(Point2D.construct(2, 3))); + assertTrue(corners[3].equals(Point2D.construct(0, 3))); + + env.queryCornersReversed(corners); + assertTrue(corners[0].equals(env.queryCorner(0))); + assertTrue(corners[1].equals(env.queryCorner(3))); + assertTrue(corners[2].equals(env.queryCorner(2))); + assertTrue(corners[3].equals(env.queryCorner(1))); + } + + assertTrue(env.getCenter().equals(Point2D.construct(1, 2))); + + assertFalse(env.containsExclusive(env.getUpperLeft())); + assertTrue(env.contains(env.getUpperLeft())); + assertTrue(env.containsExclusive(env.getCenter())); + } } diff --git a/src/test/java/com/esri/core/geometry/TestProximity2D.java b/src/test/java/com/esri/core/geometry/TestProximity2D.java index a0766cd5..f06c8ce9 100644 --- a/src/test/java/com/esri/core/geometry/TestProximity2D.java +++ b/src/test/java/com/esri/core/geometry/TestProximity2D.java @@ -130,6 +130,18 @@ public void testProximity_2D_1() { true); resultPoint0 = result0.getCoordinate(); assertTrue(resultPoint0.getX() == 0.0 && resultPoint0.getY() == 2.0); + + Polygon pp = new Polygon(); + pp.startPath(0, 0); + pp.lineTo(0, 10); + pp.lineTo(10, 10); + pp.lineTo(10, 0); + + inputPoint.setXY(15, -5); + + result0 = proximityOp.getNearestCoordinate(pp, inputPoint, true, true); + boolean is_right = result0.isRightSide(); + assertTrue(!is_right); } Polygon MakePolygon() { @@ -227,13 +239,24 @@ public static void testProximity2D_3() { point.setXY(-110, 20); Proximity2DResult result = proximity.getNearestCoordinate(polygon, point, false); - System.out.println("The closest coordinate is " - + result.getCoordinate()); - System.out.println("The closest point is " + result.getDistance()); Point point2 = new Point(); point2.setXY(-120, 12); @SuppressWarnings("unused") Proximity2DResult[] results = proximity.getNearestVertices(polygon, point2, 10, 12); } + + @Test + public static void testCR254240() { + OperatorProximity2D proximityOp = OperatorProximity2D.local(); + + Point inputPoint = new Point(-12, 12); + Polyline line = new Polyline(); + line.startPath(-10, 0); + line.lineTo(0, 0); + + Proximity2DResult result = proximityOp.getNearestCoordinate(line, + inputPoint, false, true); + assertTrue(result.isRightSide() == false); + } } diff --git a/src/test/java/com/esri/core/geometry/TestRelation.java b/src/test/java/com/esri/core/geometry/TestRelation.java index e02ed168..e12c1d4a 100644 --- a/src/test/java/com/esri/core/geometry/TestRelation.java +++ b/src/test/java/com/esri/core/geometry/TestRelation.java @@ -1,10 +1,13 @@ package com.esri.core.geometry; import junit.framework.TestCase; + import org.codehaus.jackson.JsonFactory; import org.codehaus.jackson.JsonParser; import org.junit.Test; +import com.esri.core.geometry.Geometry.GeometryAccelerationDegree; + public class TestRelation extends TestCase { @Override protected void setUp() throws Exception { @@ -273,7 +276,7 @@ public static void testContainsFailureCR186456() { OperatorContains op = (OperatorContains) (OperatorFactoryLocal .getInstance().getOperator(Operator.Type.Contains)); String str = "{\"rings\":[[[406944.399999999,287461.450000001],[406947.750000011,287462.299999997],[406946.44999999,287467.450000001],[406943.050000005,287466.550000005],[406927.799999992,287456.849999994],[406926.949999996,287456.599999995],[406924.800000005,287455.999999998],[406924.300000007,287455.849999999],[406924.200000008,287456.099999997],[406923.450000011,287458.449999987],[406922.999999987,287459.800000008],[406922.29999999,287462.099999998],[406921.949999991,287463.449999992],[406921.449999993,287465.050000011],[406920.749999996,287466.700000004],[406919.800000001,287468.599999996],[406919.050000004,287469.99999999],[406917.800000009,287471.800000008],[406916.04999999,287473.550000001],[406915.449999993,287473.999999999],[406913.700000001,287475.449999993],[406913.300000002,287475.899999991],[406912.050000008,287477.250000011],[406913.450000002,287478.150000007],[406915.199999994,287478.650000005],[406915.999999991,287478.800000005],[406918.300000007,287479.200000003],[406920.649999997,287479.450000002],[406923.100000013,287479.550000001],[406925.750000001,287479.450000002],[406928.39999999,287479.150000003],[406929.80000001,287478.950000004],[406932.449999998,287478.350000006],[406935.099999987,287477.60000001],[406938.699999998,287476.349999989],[406939.649999994,287473.949999999],[406939.799999993,287473.949999999],[406941.249999987,287473.75],[406942.700000007,287473.250000002],[406943.100000005,287473.100000003],[406943.950000001,287472.750000004],[406944.799999998,287472.300000006],[406944.999999997,287472.200000007],[406946.099999992,287471.200000011],[406946.299999991,287470.950000012],[406948.00000001,287468.599999996],[406948.10000001,287468.399999997],[406950.100000001,287465.050000011],[406951.949999993,287461.450000001],[406952.049999993,287461.300000001],[406952.69999999,287459.900000007],[406953.249999987,287458.549999987],[406953.349999987,287458.299999988],[406953.650000012,287457.299999992],[406953.900000011,287456.349999996],[406954.00000001,287455.300000001],[406954.00000001,287454.750000003],[406953.850000011,287453.750000008],[406953.550000012,287452.900000011],[406953.299999987,287452.299999988],[406954.500000008,287450.299999996],[406954.00000001,287449.000000002],[406953.399999987,287447.950000006],[406953.199999988,287447.550000008],[406952.69999999,287446.850000011],[406952.149999992,287446.099999988],[406951.499999995,287445.499999991],[406951.149999996,287445.249999992],[406950.449999999,287444.849999994],[406949.600000003,287444.599999995],[406949.350000004,287444.549999995],[406948.250000009,287444.499999995],[406947.149999987,287444.699999994],[406946.849999989,287444.749999994],[406945.899999993,287444.949999993],[406944.999999997,287445.349999991],[406944.499999999,287445.64999999],[406943.650000003,287446.349999987],[406942.900000006,287447.10000001],[406942.500000008,287447.800000007],[406942.00000001,287448.700000003],[406941.600000011,287449.599999999],[406941.350000013,287450.849999994],[406941.350000013,287451.84999999],[406941.450000012,287452.850000012],[406941.750000011,287453.850000007],[406941.800000011,287454.000000007],[406942.150000009,287454.850000003],[406942.650000007,287455.6],[406943.150000005,287456.299999997],[406944.499999999,287457.299999992],[406944.899999997,287457.599999991],[406945.299999995,287457.949999989],[406944.399999999,287461.450000001],[406941.750000011,287461.999999998],[406944.399999999,287461.450000001]],[[406944.399999999,287461.450000001],[406947.750000011,287462.299999997],[406946.44999999,287467.450000001],[406943.050000005,287466.550000005],[406927.799999992,287456.849999994],[406944.399999999,287461.450000001]]]}"; - MapGeometry mg = importFromJson(str); + MapGeometry mg = TestCommonMethods.fromJson(str); boolean res = op.execute((mg.getGeometry()), (mg.getGeometry()), null, null); assertTrue(res); @@ -286,9 +289,9 @@ public static void testWithin() { OperatorWithin op = (OperatorWithin) (OperatorFactoryLocal .getInstance().getOperator(Operator.Type.Within)); String str1 = "{\"rings\":[[[0,0],[0,200],[200,200],[200,0],[0,0],[0,0],[0,0]]]}"; - MapGeometry mg1 = importFromJson(str1); + MapGeometry mg1 = TestCommonMethods.fromJson(str1); String str2 = "{\"x\":100,\"y\":100}"; - MapGeometry mg2 = importFromJson(str2); + MapGeometry mg2 = TestCommonMethods.fromJson(str2); boolean res = op.execute((mg2.getGeometry()), (mg1.getGeometry()), null, null); @@ -303,9 +306,9 @@ public static void testWithin() { OperatorWithin op = (OperatorWithin) (OperatorFactoryLocal .getInstance().getOperator(Operator.Type.Within)); String str1 = "{\"rings\":[[[0,0],[0,200],[200,200],[200,0],[100,0]]]}"; - MapGeometry mg1 = importFromJson(str1); + MapGeometry mg1 = TestCommonMethods.fromJson(str1); String str2 = "{\"rings\":[[[10,10],[10,100],[100,100],[100,10]]]}"; - MapGeometry mg2 = importFromJson(str2); + MapGeometry mg2 = TestCommonMethods.fromJson(str2); boolean res = op.execute((mg2.getGeometry()), (mg1.getGeometry()), null, null); @@ -319,9 +322,9 @@ public static void testWithin() { OperatorWithin op = (OperatorWithin) (OperatorFactoryLocal .getInstance().getOperator(Operator.Type.Within)); String str1 = "{\"points\":[[0,0],[0,200],[200,200]]}"; - MapGeometry mg1 = importFromJson(str1); + MapGeometry mg1 = TestCommonMethods.fromJson(str1); String str2 = "{\"points\":[[0,0],[0,200]]}"; - MapGeometry mg2 = importFromJson(str2); + MapGeometry mg2 = TestCommonMethods.fromJson(str2); boolean res = op.execute((mg2.getGeometry()), (mg1.getGeometry()), null, null); assertTrue(res); @@ -334,9 +337,9 @@ public static void testWithin() { OperatorWithin op = (OperatorWithin) (OperatorFactoryLocal .getInstance().getOperator(Operator.Type.Within)); String str1 = "{\"points\":[[0,0],[0,200]]}"; - MapGeometry mg1 = importFromJson(str1); + MapGeometry mg1 = TestCommonMethods.fromJson(str1); String str2 = "{\"points\":[[0,0],[0,200]]}"; - MapGeometry mg2 = importFromJson(str2); + MapGeometry mg2 = TestCommonMethods.fromJson(str2); boolean res = op.execute((mg2.getGeometry()), (mg1.getGeometry()), null, null); assertTrue(res); @@ -349,9 +352,9 @@ public static void testWithin() { OperatorWithin op = (OperatorWithin) (OperatorFactoryLocal .getInstance().getOperator(Operator.Type.Within)); String str1 = "{\"points\":[[0,0],[0,200],[200,200]]}"; - MapGeometry mg1 = importFromJson(str1); + MapGeometry mg1 = TestCommonMethods.fromJson(str1); String str2 = "{\"points\":[[0,0],[0,200], [1, 1]]}"; - MapGeometry mg2 = importFromJson(str2); + MapGeometry mg2 = TestCommonMethods.fromJson(str2); boolean res = op.execute((mg2.getGeometry()), (mg1.getGeometry()), null, null); assertTrue(!res); @@ -367,9 +370,9 @@ public static void testContains() { OperatorContains op = (OperatorContains) (OperatorFactoryLocal .getInstance().getOperator(Operator.Type.Contains)); String str1 = "{\"rings\":[[[0,0],[0,200],[200,200],[200,0],[0,0],[0,0],[0,0]]]}"; - MapGeometry mg1 = importFromJson(str1); + MapGeometry mg1 = TestCommonMethods.fromJson(str1); String str2 = "{\"x\":100,\"y\":100}"; - MapGeometry mg2 = importFromJson(str2); + MapGeometry mg2 = TestCommonMethods.fromJson(str2); boolean res = op.execute((mg2.getGeometry()), (mg1.getGeometry()), null, null); @@ -383,9 +386,9 @@ public static void testContains() { OperatorContains op = (OperatorContains) (OperatorFactoryLocal .getInstance().getOperator(Operator.Type.Contains)); String str1 = "{\"rings\":[[[0,0],[0,200],[200,200],[200,0],[0,0]]]}"; - MapGeometry mg1 = importFromJson(str1); + MapGeometry mg1 = TestCommonMethods.fromJson(str1); String str2 = "{\"rings\":[[[10,10],[10,100],[100,100],[10,10]]]}"; - MapGeometry mg2 = importFromJson(str2); + MapGeometry mg2 = TestCommonMethods.fromJson(str2); boolean res = op.execute((mg2.getGeometry()), (mg1.getGeometry()), null, null); @@ -399,9 +402,9 @@ public static void testContains() { OperatorContains op = (OperatorContains) (OperatorFactoryLocal .getInstance().getOperator(Operator.Type.Contains)); String str1 = "{\"points\":[[0,0],[0,200],[200,200]]}"; - MapGeometry mg1 = importFromJson(str1); + MapGeometry mg1 = TestCommonMethods.fromJson(str1); String str2 = "{\"points\":[[0,0],[0,200]]}"; - MapGeometry mg2 = importFromJson(str2); + MapGeometry mg2 = TestCommonMethods.fromJson(str2); boolean res = op.execute((mg2.getGeometry()), (mg1.getGeometry()), null, null); assertTrue(!res); @@ -414,9 +417,9 @@ public static void testContains() { OperatorContains op = (OperatorContains) (OperatorFactoryLocal .getInstance().getOperator(Operator.Type.Contains)); String str1 = "{\"points\":[[0,0],[0,200]]}"; - MapGeometry mg1 = importFromJson(str1); + MapGeometry mg1 = TestCommonMethods.fromJson(str1); String str2 = "{\"points\":[[0,0],[0,200]]}"; - MapGeometry mg2 = importFromJson(str2); + MapGeometry mg2 = TestCommonMethods.fromJson(str2); boolean res = op.execute((mg2.getGeometry()), (mg1.getGeometry()), null, null); assertTrue(res); @@ -429,9 +432,9 @@ public static void testContains() { OperatorContains op = (OperatorContains) (OperatorFactoryLocal .getInstance().getOperator(Operator.Type.Contains)); String str1 = "{\"points\":[[0,0],[0,200],[200,200]]}"; - MapGeometry mg1 = importFromJson(str1); + MapGeometry mg1 = TestCommonMethods.fromJson(str1); String str2 = "{\"points\":[[0,0],[0,200], [1, 1]]}"; - MapGeometry mg2 = importFromJson(str2); + MapGeometry mg2 = TestCommonMethods.fromJson(str2); boolean res = op.execute((mg2.getGeometry()), (mg1.getGeometry()), null, null); assertTrue(!res); @@ -458,9 +461,9 @@ public static void testOverlaps() { OperatorOverlaps op = (OperatorOverlaps) (OperatorFactoryLocal .getInstance().getOperator(Operator.Type.Overlaps)); String str1 = "{\"rings\":[[[0,0],[0,200],[200,200],[200,0],[0,0],[0,0],[0,0]]]}"; - MapGeometry mg1 = importFromJson(str1); + MapGeometry mg1 = TestCommonMethods.fromJson(str1); String str2 = "{\"x\":100,\"y\":100}"; - MapGeometry mg2 = importFromJson(str2); + MapGeometry mg2 = TestCommonMethods.fromJson(str2); boolean res = op.execute((mg2.getGeometry()), (mg1.getGeometry()), null, null); @@ -473,8 +476,8 @@ public static void testOverlaps() { OperatorOverlaps op = (OperatorOverlaps) (OperatorFactoryLocal .getInstance().getOperator(Operator.Type.Overlaps)); String str1 = "{\"rings\":[[[0,0],[0,200],[200,200],[200,0],[0,0]]]}"; - MapGeometry mg1 = importFromJson(str1); - MapGeometry mg2 = importFromJson(str1); + MapGeometry mg1 = TestCommonMethods.fromJson(str1); + MapGeometry mg2 = TestCommonMethods.fromJson(str1); Transformation2D trans = new Transformation2D(); trans.setShift(300, 0); mg2.getGeometry().applyTransformation(trans); @@ -489,8 +492,8 @@ public static void testOverlaps() { OperatorOverlaps op = (OperatorOverlaps) (OperatorFactoryLocal .getInstance().getOperator(Operator.Type.Overlaps)); String str1 = "{\"rings\":[[[0,0],[0,200],[200,200],[200,0],[0,0]]]}"; - MapGeometry mg1 = importFromJson(str1); - MapGeometry mg2 = importFromJson(str1); + MapGeometry mg1 = TestCommonMethods.fromJson(str1); + MapGeometry mg2 = TestCommonMethods.fromJson(str1); Transformation2D trans = new Transformation2D(); trans.setShift(30, 0); mg2.getGeometry().applyTransformation(trans); @@ -505,8 +508,8 @@ public static void testOverlaps() { OperatorOverlaps op = (OperatorOverlaps) (OperatorFactoryLocal .getInstance().getOperator(Operator.Type.Overlaps)); String str1 = "{\"rings\":[[[0,0],[0,200],[200,200],[200,0],[0,0]]]}"; - MapGeometry mg1 = importFromJson(str1); - MapGeometry mg2 = importFromJson(str1); + MapGeometry mg1 = TestCommonMethods.fromJson(str1); + MapGeometry mg2 = TestCommonMethods.fromJson(str1); Transformation2D trans = new Transformation2D(); trans.setShift(0, 0); mg2.getGeometry().applyTransformation(trans); @@ -522,8 +525,8 @@ public static void testOverlaps() { OperatorOverlaps op = (OperatorOverlaps) (OperatorFactoryLocal .getInstance().getOperator(Operator.Type.Overlaps)); String str1 = "{\"paths\":[[[0,0],[100,0],[200,0]]]}"; - MapGeometry mg1 = importFromJson(str1); - MapGeometry mg2 = importFromJson(str1); + MapGeometry mg1 = TestCommonMethods.fromJson(str1); + MapGeometry mg2 = TestCommonMethods.fromJson(str1); Transformation2D trans = new Transformation2D(); trans.setShift(0, 0); mg2.getGeometry().applyTransformation(trans); @@ -539,8 +542,8 @@ public static void testOverlaps() { OperatorOverlaps op = (OperatorOverlaps) (OperatorFactoryLocal .getInstance().getOperator(Operator.Type.Overlaps)); String str1 = "{\"paths\":[[[0,0],[100,0],[200,0]]]}"; - MapGeometry mg1 = importFromJson(str1); - MapGeometry mg2 = importFromJson(str1); + MapGeometry mg1 = TestCommonMethods.fromJson(str1); + MapGeometry mg2 = TestCommonMethods.fromJson(str1); Transformation2D trans = new Transformation2D(); trans.setShift(10, 0); mg2.getGeometry().applyTransformation(trans); @@ -556,8 +559,8 @@ public static void testOverlaps() { OperatorOverlaps op = (OperatorOverlaps) (OperatorFactoryLocal .getInstance().getOperator(Operator.Type.Overlaps)); String str1 = "{\"paths\":[[[0,0],[100,0],[200,0]]]}"; - MapGeometry mg1 = importFromJson(str1); - MapGeometry mg2 = importFromJson(str1); + MapGeometry mg1 = TestCommonMethods.fromJson(str1); + MapGeometry mg2 = TestCommonMethods.fromJson(str1); Transformation2D trans = new Transformation2D(); trans.setShift(200, 0); mg2.getGeometry().applyTransformation(trans); @@ -573,8 +576,8 @@ public static void testOverlaps() { OperatorOverlaps op = (OperatorOverlaps) (OperatorFactoryLocal .getInstance().getOperator(Operator.Type.Overlaps)); String str1 = "{\"points\":[[0,0],[0,200],[200,200],[200,0]]}"; - MapGeometry mg1 = importFromJson(str1); - MapGeometry mg2 = importFromJson(str1); + MapGeometry mg1 = TestCommonMethods.fromJson(str1); + MapGeometry mg2 = TestCommonMethods.fromJson(str1); Transformation2D trans = new Transformation2D(); trans.setShift(0, 0); mg2.getGeometry().applyTransformation(trans); @@ -589,9 +592,9 @@ public static void testOverlaps() { OperatorOverlaps op = (OperatorOverlaps) (OperatorFactoryLocal .getInstance().getOperator(Operator.Type.Overlaps)); String str1 = "{\"points\":[[0,0],[0,200],[200,200]]}"; - MapGeometry mg1 = importFromJson(str1); + MapGeometry mg1 = TestCommonMethods.fromJson(str1); String str2 = "{\"points\":[[0,0],[0,200]]}"; - MapGeometry mg2 = importFromJson(str2); + MapGeometry mg2 = TestCommonMethods.fromJson(str2); boolean res = op.execute((mg2.getGeometry()), (mg1.getGeometry()), null, null); assertTrue(!res); @@ -603,9 +606,9 @@ public static void testOverlaps() { OperatorOverlaps op = (OperatorOverlaps) (OperatorFactoryLocal .getInstance().getOperator(Operator.Type.Overlaps)); String str1 = "{\"points\":[[0,0],[0,200],[200,200]]}"; - MapGeometry mg1 = importFromJson(str1); + MapGeometry mg1 = TestCommonMethods.fromJson(str1); String str2 = "{\"points\":[[0,0],[0,200], [0,2]]}"; - MapGeometry mg2 = importFromJson(str2); + MapGeometry mg2 = TestCommonMethods.fromJson(str2); boolean res = op.execute((mg2.getGeometry()), (mg1.getGeometry()), null, null); assertTrue(res); @@ -629,11 +632,18 @@ public static void testPolygonPolygonEquals() { String str1 = "{\"rings\":[[[0,0],[0,5],[0,10],[0,5],[0,7],[0,10],[10,10],[10,0],[0,0]],[[1,1],[9,1],[9,9],[1,9],[1,1],[1,1]]]}"; String str2 = "{\"rings\":[[[0,10],[10,10],[10,0],[0,0],[0,10]],[[9,1],[9,6],[9,9],[1,9],[1,1],[1,1],[9,1]]]}"; - Polygon polygon1 = (Polygon) importFromJson(str1).getGeometry(); - Polygon polygon2 = (Polygon) importFromJson(str2).getGeometry(); + Polygon polygon1 = (Polygon) TestCommonMethods.fromJson(str1) + .getGeometry(); + Polygon polygon2 = (Polygon) TestCommonMethods.fromJson(str2) + .getGeometry(); // wiggleGeometry(polygon1, tolerance, 1982); // wiggleGeometry(polygon2, tolerance, 511); + equals.accelerateGeometry(polygon1, sr, + Geometry.GeometryAccelerationDegree.enumHot); + equals.accelerateGeometry(polygon2, sr, + Geometry.GeometryAccelerationDegree.enumHot); + boolean res = equals.execute(polygon1, polygon2, sr, null); assertTrue(res); equals.execute(polygon2, polygon1, sr, null); @@ -643,8 +653,8 @@ public static void testPolygonPolygonEquals() { // a hole. str1 = "{\"rings\":[[[0,0],[0,5],[0,10],[10,10],[10,0],[0,0]],[[1,1],[9,1],[9,9],[1,9],[1,1],[1,1]]]}"; str2 = "{\"rings\":[[[0,10],[10,10],[5,10],[10,10],[10,0],[0,0],[0,10]]]}"; - polygon1 = (Polygon) importFromJson(str1).getGeometry(); - polygon2 = (Polygon) importFromJson(str2).getGeometry(); + polygon1 = (Polygon) TestCommonMethods.fromJson(str1).getGeometry(); + polygon2 = (Polygon) TestCommonMethods.fromJson(str2).getGeometry(); res = equals.execute(polygon1, polygon2, sr, null); assertTrue(!res); @@ -655,8 +665,8 @@ public static void testPolygonPolygonEquals() { str1 = "{\"rings\":[[[0,0],[0,10],[10,10],[10,0],[0,0]]]}"; str2 = "{\"rings\":[[[0,10],[10,10],[10,0],[0,0],[0,10]]]}"; - polygon1 = (Polygon) importFromJson(str1).getGeometry(); - polygon2 = (Polygon) importFromJson(str2).getGeometry(); + polygon1 = (Polygon) TestCommonMethods.fromJson(str1).getGeometry(); + polygon2 = (Polygon) TestCommonMethods.fromJson(str2).getGeometry(); res = equals.execute(polygon1, polygon2, sr, null); assertTrue(res); @@ -667,8 +677,8 @@ public static void testPolygonPolygonEquals() { str1 = "{\"rings\":[[[0,0],[0,10],[10,10],[10,0],[0,0]]]}"; str2 = "{\"rings\":[[[0,0],[10,0],[10,10],[0,10],[0,0]]]}"; - polygon1 = (Polygon) importFromJson(str1).getGeometry(); - polygon2 = (Polygon) importFromJson(str2).getGeometry(); + polygon1 = (Polygon) TestCommonMethods.fromJson(str1).getGeometry(); + polygon2 = (Polygon) TestCommonMethods.fromJson(str2).getGeometry(); res = equals.execute(polygon1, polygon2, sr, null); assertTrue(res); @@ -835,8 +845,10 @@ public static void testPolygonPolygonDisjoint() { String str1 = "{\"rings\":[[[0,0],[0,5],[0,10],[10,10],[10,0],[0,0]],[[1,1],[9,1],[9,9],[1,9],[1,1],[1,1]]]}"; String str2 = "{\"rings\":[[[0,10],[10,10],[10,0],[0,0],[0,10]],[[9,1],[9,6],[9,9],[1,9],[1,1],[1,1],[9,1]]]}"; - Polygon polygon1 = (Polygon) (importFromJson(str1).getGeometry()); - Polygon polygon2 = (Polygon) (importFromJson(str2).getGeometry()); + Polygon polygon1 = (Polygon) (TestCommonMethods.fromJson(str1) + .getGeometry()); + Polygon polygon2 = (Polygon) (TestCommonMethods.fromJson(str2) + .getGeometry()); boolean res = disjoint.execute(polygon1, polygon2, sr, null); assertTrue(!res); @@ -847,8 +859,8 @@ public static void testPolygonPolygonDisjoint() { str1 = "{\"rings\":[[[0,0],[0,5],[0,10],[10,10],[10,0],[0,0]],[[1,1],[9,1],[9,9],[1,9],[1,1],[1,1]]]}"; str2 = "{\"rings\":[[[10,10],[10,15],[15,15],[15,10],[10,10]]]}"; - polygon1 = (Polygon) (importFromJson(str1).getGeometry()); - polygon2 = (Polygon) (importFromJson(str2).getGeometry()); + polygon1 = (Polygon) (TestCommonMethods.fromJson(str1).getGeometry()); + polygon2 = (Polygon) (TestCommonMethods.fromJson(str2).getGeometry()); wiggleGeometry(polygon1, tolerance, 1982); wiggleGeometry(polygon2, tolerance, 511); @@ -861,8 +873,8 @@ public static void testPolygonPolygonDisjoint() { str1 = "{\"rings\":[[[0,0],[0,5],[0,10],[10,10],[10,0],[0,0]],[[1,1],[9,1],[9,9],[1,9],[1,1],[1,1]]]}"; str2 = "{\"rings\":[[[10,0],[10,10],[15,10],[15,0],[10,0]]]}"; - polygon1 = (Polygon) (importFromJson(str1).getGeometry()); - polygon2 = (Polygon) (importFromJson(str2).getGeometry()); + polygon1 = (Polygon) (TestCommonMethods.fromJson(str1).getGeometry()); + polygon2 = (Polygon) (TestCommonMethods.fromJson(str2).getGeometry()); wiggleGeometry(polygon1, tolerance, 1982); wiggleGeometry(polygon2, tolerance, 511); @@ -875,8 +887,8 @@ public static void testPolygonPolygonDisjoint() { str1 = "{\"rings\":[[[0,0],[0,5],[0,10],[10,10],[10,0],[0,0]],[[1,1],[9,1],[9,9],[1,9],[1,1],[1,1]]]}"; str2 = "{\"rings\":[[[2,2],[2,8],[8,8],[8,2],[2,2]]]}"; - polygon1 = (Polygon) (importFromJson(str1).getGeometry()); - polygon2 = (Polygon) (importFromJson(str2).getGeometry()); + polygon1 = (Polygon) (TestCommonMethods.fromJson(str1).getGeometry()); + polygon2 = (Polygon) (TestCommonMethods.fromJson(str2).getGeometry()); res = disjoint.execute(polygon1, polygon2, sr, null); assertTrue(res); @@ -887,8 +899,8 @@ public static void testPolygonPolygonDisjoint() { str1 = "{\"rings\":[[[0,0],[0,5],[5,5],[5,0]],[[10,0],[10,10],[20,10],[20,0]]]}"; str2 = "{\"rings\":[[[0,-10],[0,-5],[5,-5],[5,-10]],[[11,1],[11,9],[19,9],[19,1]]]}"; - polygon1 = (Polygon) (importFromJson(str1).getGeometry()); - polygon2 = (Polygon) (importFromJson(str2).getGeometry()); + polygon1 = (Polygon) (TestCommonMethods.fromJson(str1).getGeometry()); + polygon2 = (Polygon) (TestCommonMethods.fromJson(str2).getGeometry()); res = disjoint.execute(polygon1, polygon2, sr, null); assertTrue(!res); @@ -908,8 +920,10 @@ public static void testPolylinePolylineDisjoint() { String str1 = "{\"paths\":[[[0,0],[0,5],[0,10],[10,10],[10,0],[0,0]],[[1,1],[9,1],[9,9],[1,9],[1,1],[1,1]]]}"; String str2 = "{\"paths\":[[[10,10],[10,15],[15,15],[15,10],[10,10]]]}"; - Polyline polyline1 = (Polyline) (importFromJson(str1).getGeometry()); - Polyline polyline2 = (Polyline) (importFromJson(str2).getGeometry()); + Polyline polyline1 = (Polyline) (TestCommonMethods.fromJson(str1) + .getGeometry()); + Polyline polyline2 = (Polyline) (TestCommonMethods.fromJson(str2) + .getGeometry()); wiggleGeometry(polyline1, tolerance, 1982); wiggleGeometry(polyline2, tolerance, 511); @@ -922,8 +936,8 @@ public static void testPolylinePolylineDisjoint() { str1 = "{\"paths\":[[[0,0],[0,5],[0,10],[10,10],[10,0],[0,0]],[[1,1],[9,1],[9,9],[1,9],[1,1],[1,1]]]}"; str2 = "{\"paths\":[[[10,0],[10,10],[15,10],[15,0],[10,0]]]}"; - polyline1 = (Polyline) (importFromJson(str1).getGeometry()); - polyline2 = (Polyline) (importFromJson(str2).getGeometry()); + polyline1 = (Polyline) (TestCommonMethods.fromJson(str1).getGeometry()); + polyline2 = (Polyline) (TestCommonMethods.fromJson(str2).getGeometry()); wiggleGeometry(polyline1, tolerance, 1982); wiggleGeometry(polyline2, tolerance, 511); @@ -936,8 +950,8 @@ public static void testPolylinePolylineDisjoint() { str1 = "{\"paths\":[[[0,0],[0,5],[0,10],[10,10],[10,0],[0,0]],[[1,1],[9,1],[9,9],[1,9],[1,1],[1,1]]]}"; str2 = "{\"paths\":[[[2,2],[2,8],[8,8],[8,2],[2,2]]]}"; - polyline1 = (Polyline) (importFromJson(str1).getGeometry()); - polyline2 = (Polyline) (importFromJson(str2).getGeometry()); + polyline1 = (Polyline) (TestCommonMethods.fromJson(str1).getGeometry()); + polyline2 = (Polyline) (TestCommonMethods.fromJson(str2).getGeometry()); res = disjoint.execute(polyline1, polyline2, sr, null); assertTrue(res); @@ -1044,6 +1058,8 @@ public static void testPolylineMultiPointDisjoint() { polyline1.lineTo(1, 0); polyline1.lineTo(1, 1); + disjoint.accelerateGeometry(polyline1, sr, + Geometry.GeometryAccelerationDegree.enumHot); res = disjoint.execute(polyline1, multipoint2, sr, null); assertTrue(!res); res = disjoint.execute(multipoint2, polyline1, sr, null); @@ -1082,6 +1098,11 @@ public static void testPolylinePointDisjoint() { point2.setXY(4, 2); + polyline1 = (Polyline) OperatorDensifyByLength.local().execute( + polyline1, 0.1, null); + disjoint.accelerateGeometry(polyline1, sr, + Geometry.GeometryAccelerationDegree.enumHot); + res = disjoint.execute(polyline1, point2, sr, null); assertTrue(!res); res = disjoint.execute(point2, polyline1, sr, null); @@ -1328,8 +1349,10 @@ public static void testPolygonPolygonTouches() { String str1 = "{\"rings\":[[[0,0],[0,5],[0,10],[10,10],[10,0],[0,0]],[[1,1],[9,1],[9,9],[1,9],[1,1],[1,1]]]}"; String str2 = "{\"rings\":[[[10,10],[10,15],[15,15],[15,10],[10,10]]]}"; - Polygon polygon1 = (Polygon) (importFromJson(str1).getGeometry()); - Polygon polygon2 = (Polygon) (importFromJson(str2).getGeometry()); + Polygon polygon1 = (Polygon) (TestCommonMethods.fromJson(str1) + .getGeometry()); + Polygon polygon2 = (Polygon) (TestCommonMethods.fromJson(str2) + .getGeometry()); wiggleGeometry(polygon1, tolerance, 1982); wiggleGeometry(polygon2, tolerance, 511); @@ -1342,8 +1365,8 @@ public static void testPolygonPolygonTouches() { str1 = "{\"rings\":[[[0,0],[0,5],[0,10],[10,10],[10,0],[0,0]],[[1,1],[9,1],[9,9],[1,9],[1,1],[1,1]]]}"; str2 = "{\"rings\":[[[10,0],[10,10],[15,10],[15,0],[10,0]]]}"; - polygon1 = (Polygon) (importFromJson(str1).getGeometry()); - polygon2 = (Polygon) (importFromJson(str2).getGeometry()); + polygon1 = (Polygon) (TestCommonMethods.fromJson(str1).getGeometry()); + polygon2 = (Polygon) (TestCommonMethods.fromJson(str2).getGeometry()); wiggleGeometry(polygon1, tolerance, 1982); wiggleGeometry(polygon2, tolerance, 511); @@ -1357,8 +1380,8 @@ public static void testPolygonPolygonTouches() { str1 = "{\"rings\":[[[0,0],[0,5],[0,10],[10,10],[10,0],[0,0]],[[1,1],[9,1],[9,9],[1,9],[1,1],[1,1]]]}"; str2 = "{\"rings\":[[[15,5],[5,15],[15,15],[15,5]]]}"; - polygon1 = (Polygon) (importFromJson(str1).getGeometry()); - polygon2 = (Polygon) (importFromJson(str2).getGeometry()); + polygon1 = (Polygon) (TestCommonMethods.fromJson(str1).getGeometry()); + polygon2 = (Polygon) (TestCommonMethods.fromJson(str2).getGeometry()); wiggleGeometry(polygon1, tolerance, 1982); wiggleGeometry(polygon2, tolerance, 511); @@ -1371,8 +1394,8 @@ public static void testPolygonPolygonTouches() { str1 = "{\"rings\":[[[0,0],[0,5],[0,10],[10,10],[10,0],[0,0]],[[1,1],[9,1],[9,9],[1,9],[1,1],[1,1]]]}"; str2 = "{\"rings\":[[[5,5],[5,15],[15,15],[15,5],[5,5]]]}"; - polygon1 = (Polygon) (importFromJson(str1).getGeometry()); - polygon2 = (Polygon) (importFromJson(str2).getGeometry()); + polygon1 = (Polygon) (TestCommonMethods.fromJson(str1).getGeometry()); + polygon2 = (Polygon) (TestCommonMethods.fromJson(str2).getGeometry()); res = touches.execute(polygon1, polygon2, sr, null); assertTrue(!res); @@ -1408,8 +1431,10 @@ public static void testPolygonPolylineTouches() { String str1 = "{\"rings\":[[[0,0],[0,5],[0,10],[10,10],[10,0]],[[1,1],[9,1],[9,9],[1,9],[1,1],[1,1]]]}"; String str2 = "{\"paths\":[[[10,10],[10,15],[15,15],[15,10]]]}"; - Polygon polygon1 = (Polygon) (importFromJson(str1).getGeometry()); - Polyline polyline2 = (Polyline) (importFromJson(str2).getGeometry()); + Polygon polygon1 = (Polygon) (TestCommonMethods.fromJson(str1) + .getGeometry()); + Polyline polyline2 = (Polyline) (TestCommonMethods.fromJson(str2) + .getGeometry()); wiggleGeometry(polygon1, tolerance, 1982); wiggleGeometry(polyline2, tolerance, 511); @@ -1422,8 +1447,8 @@ public static void testPolygonPolylineTouches() { str1 = "{\"rings\":[[[0,0],[0,5],[0,10],[10,10],[10,0],[0,0]],[[1,1],[9,1],[9,9],[1,9],[1,1],[1,1]]]}"; str2 = "{\"paths\":[[[10,0],[10,10],[15,10],[15,0]]]}"; - polygon1 = (Polygon) (importFromJson(str1).getGeometry()); - polyline2 = (Polyline) (importFromJson(str2).getGeometry()); + polygon1 = (Polygon) (TestCommonMethods.fromJson(str1).getGeometry()); + polyline2 = (Polyline) (TestCommonMethods.fromJson(str2).getGeometry()); wiggleGeometry(polygon1, tolerance, 1982); wiggleGeometry(polyline2, tolerance, 511); @@ -1435,8 +1460,8 @@ public static void testPolygonPolylineTouches() { str1 = "{\"rings\":[[[0,0],[0,5],[0,10],[10,10],[10,0],[0,0]],[[1,1],[9,1],[9,9],[1,9],[1,1],[1,1]]]}"; str2 = "{\"paths\":[[[15,5],[5,15],[15,15],[15,5]]]}"; - polygon1 = (Polygon) (importFromJson(str1).getGeometry()); - polyline2 = (Polyline) (importFromJson(str2).getGeometry()); + polygon1 = (Polygon) (TestCommonMethods.fromJson(str1).getGeometry()); + polyline2 = (Polyline) (TestCommonMethods.fromJson(str2).getGeometry()); wiggleGeometry(polygon1, tolerance, 1982); wiggleGeometry(polyline2, tolerance, 511); @@ -1448,8 +1473,8 @@ public static void testPolygonPolylineTouches() { str1 = "{\"rings\":[[[10,10],[10,0],[0,0],[0,10],[10,10]],[[1,1],[9,1],[9,9],[1,9],[1,1],[1,1]]]}"; str2 = "{\"paths\":[[[15,5],[5,15],[15,15]]]}"; - polygon1 = (Polygon) (importFromJson(str1).getGeometry()); - polyline2 = (Polyline) (importFromJson(str2).getGeometry()); + polygon1 = (Polygon) (TestCommonMethods.fromJson(str1).getGeometry()); + polyline2 = (Polyline) (TestCommonMethods.fromJson(str2).getGeometry()); wiggleGeometry(polygon1, tolerance, 1982); wiggleGeometry(polyline2, tolerance, 511); @@ -1471,8 +1496,10 @@ public static void testPolylinePolylineTouches() { String str1 = "{\"paths\":[[[0,0],[0,5],[0,10],[10,10],[10,0]],[[1,1],[9,1],[9,9],[1,9],[1,1],[1,1]]]}"; String str2 = "{\"paths\":[[[10,10],[10,15],[15,15],[15,10]]]}"; - Polyline polyline1 = (Polyline) (importFromJson(str1).getGeometry()); - Polyline polyline2 = (Polyline) (importFromJson(str2).getGeometry()); + Polyline polyline1 = (Polyline) (TestCommonMethods.fromJson(str1) + .getGeometry()); + Polyline polyline2 = (Polyline) (TestCommonMethods.fromJson(str2) + .getGeometry()); boolean res = touches.execute(polyline1, polyline2, sr, null); assertTrue(res); @@ -1483,8 +1510,8 @@ public static void testPolylinePolylineTouches() { str1 = "{\"paths\":[[[0,0],[0,5],[0,10],[10,10],[10,0],[0,0]],[[1,1],[9,1],[9,9],[1,9],[1,1],[1,1]]]}"; str2 = "{\"paths\":[[[10,0],[10,10],[15,10],[15,0],[10,0]]]}"; - polyline1 = (Polyline) (importFromJson(str1).getGeometry()); - polyline2 = (Polyline) (importFromJson(str2).getGeometry()); + polyline1 = (Polyline) (TestCommonMethods.fromJson(str1).getGeometry()); + polyline2 = (Polyline) (TestCommonMethods.fromJson(str2).getGeometry()); res = touches.execute(polyline1, polyline2, sr, null); assertTrue(!res); @@ -1495,8 +1522,8 @@ public static void testPolylinePolylineTouches() { str1 = "{\"paths\":[[[0,0],[0,5],[0,10],[10,10],[10,0],[0,0]],[[1,1],[9,1],[9,9],[1,9],[1,1],[1,1]]]}"; str2 = "{\"paths\":[[[15,5],[5,15],[15,15],[15,5]]]}"; - polyline1 = (Polyline) (importFromJson(str1).getGeometry()); - polyline2 = (Polyline) (importFromJson(str2).getGeometry()); + polyline1 = (Polyline) (TestCommonMethods.fromJson(str1).getGeometry()); + polyline2 = (Polyline) (TestCommonMethods.fromJson(str2).getGeometry()); res = touches.execute(polyline1, polyline2, sr, null); assertTrue(!res); @@ -1508,8 +1535,8 @@ public static void testPolylinePolylineTouches() { str1 = "{\"paths\":[[[10,10],[10,0],[0,0],[0,10],[10,10]],[[1,1],[9,1],[9,9],[1,9],[1,1],[1,1]]]}"; str2 = "{\"paths\":[[[15,5],[5,15],[15,15]]]}"; - polyline1 = (Polyline) (importFromJson(str1).getGeometry()); - polyline2 = (Polyline) (importFromJson(str2).getGeometry()); + polyline1 = (Polyline) (TestCommonMethods.fromJson(str1).getGeometry()); + polyline2 = (Polyline) (TestCommonMethods.fromJson(str2).getGeometry()); res = touches.execute(polyline1, polyline2, sr, null); assertTrue(!res); @@ -1522,8 +1549,8 @@ public static void testPolylinePolylineTouches() { str1 = "{\"paths\":[[[10,10],[10,0],[0,0],[0,10]],[[1,1],[9,1],[9,9],[1,9],[1,1],[1,1]]]}"; str2 = "{\"paths\":[[[15,5],[5,15],[15,15]]]}"; - polyline1 = (Polyline) (importFromJson(str1).getGeometry()); - polyline2 = (Polyline) (importFromJson(str2).getGeometry()); + polyline1 = (Polyline) (TestCommonMethods.fromJson(str1).getGeometry()); + polyline2 = (Polyline) (TestCommonMethods.fromJson(str2).getGeometry()); res = touches.execute(polyline1, polyline2, sr, null); assertTrue(res); @@ -1533,8 +1560,8 @@ public static void testPolylinePolylineTouches() { str1 = "{\"paths\":[[[10,10],[10,0],[0,0],[0,10]],[[1,1],[9,1],[9,9],[1,9],[6, 9]]]}"; str2 = "{\"paths\":[[[15,5],[5,15],[15,15],[15,5]]]}"; - polyline1 = (Polyline) (importFromJson(str1).getGeometry()); - polyline2 = (Polyline) (importFromJson(str2).getGeometry()); + polyline1 = (Polyline) (TestCommonMethods.fromJson(str1).getGeometry()); + polyline2 = (Polyline) (TestCommonMethods.fromJson(str2).getGeometry()); res = touches.execute(polyline1, polyline2, sr, null); assertTrue(res); @@ -1634,6 +1661,8 @@ public static void testPolylineMultiPointTouches() { polyline1.lineTo(1, 0); polyline1.lineTo(1, 1); + touches.accelerateGeometry(polyline1, sr, + Geometry.GeometryAccelerationDegree.enumHot); res = touches.execute(polyline1, multipoint2, sr, null); assertTrue(res); res = touches.execute(multipoint2, polyline1, sr, null); @@ -1696,6 +1725,9 @@ public static void testPolylineMultiPointCrosses() { res = crosses.execute(multipoint2, polyline1, sr, null); assertTrue(!res); + crosses.accelerateGeometry(polyline1, sr, + Geometry.GeometryAccelerationDegree.enumHot); + multipoint2.add(1, 0); res = crosses.execute(polyline1, multipoint2, sr, null); assertTrue(res); @@ -1747,8 +1779,10 @@ public static void testPolygonPolygonOverlaps() { String str1 = "{\"rings\":[[[0,0],[0,5],[0,10],[10,10],[10,0],[0,0]],[[1,1],[9,1],[9,9],[1,9],[1,1],[1,1]]]}"; String str2 = "{\"rings\":[[[10,10],[10,15],[15,15],[15,10],[10,10]]]}"; - Polygon polygon1 = (Polygon) (importFromJson(str1).getGeometry()); - Polygon polygon2 = (Polygon) (importFromJson(str2).getGeometry()); + Polygon polygon1 = (Polygon) (TestCommonMethods.fromJson(str1) + .getGeometry()); + Polygon polygon2 = (Polygon) (TestCommonMethods.fromJson(str2) + .getGeometry()); wiggleGeometry(polygon1, tolerance, 1982); wiggleGeometry(polygon2, tolerance, 511); @@ -1761,8 +1795,8 @@ public static void testPolygonPolygonOverlaps() { str1 = "{\"rings\":[[[0,0],[0,5],[0,10],[10,10],[10,0],[0,0]],[[1,1],[9,1],[9,9],[1,9],[1,1],[1,1]]]}"; str2 = "{\"rings\":[[[10,0],[10,10],[15,10],[15,0],[10,0]]]}"; - polygon1 = (Polygon) (importFromJson(str1).getGeometry()); - polygon2 = (Polygon) (importFromJson(str2).getGeometry()); + polygon1 = (Polygon) (TestCommonMethods.fromJson(str1).getGeometry()); + polygon2 = (Polygon) (TestCommonMethods.fromJson(str2).getGeometry()); wiggleGeometry(polygon1, tolerance, 1982); wiggleGeometry(polygon2, tolerance, 511); @@ -1776,8 +1810,8 @@ public static void testPolygonPolygonOverlaps() { str1 = "{\"rings\":[[[0,0],[0,5],[0,10],[10,10],[10,0],[0,0]],[[1,1],[9,1],[9,9],[1,9],[1,1],[1,1]]]}"; str2 = "{\"rings\":[[[15,5],[5,15],[15,15],[15,5]]]}"; - polygon1 = (Polygon) (importFromJson(str1).getGeometry()); - polygon2 = (Polygon) (importFromJson(str2).getGeometry()); + polygon1 = (Polygon) (TestCommonMethods.fromJson(str1).getGeometry()); + polygon2 = (Polygon) (TestCommonMethods.fromJson(str2).getGeometry()); wiggleGeometry(polygon1, tolerance, 1982); wiggleGeometry(polygon2, tolerance, 511); @@ -1790,8 +1824,8 @@ public static void testPolygonPolygonOverlaps() { str1 = "{\"rings\":[[[0,0],[0,5],[0,10],[10,10],[10,0],[0,0]],[[1,1],[9,1],[9,9],[1,9],[1,1],[1,1]]]}"; str2 = "{\"rings\":[[[5,5],[5,15],[15,15],[15,5],[5,5]]]}"; - polygon1 = (Polygon) (importFromJson(str1).getGeometry()); - polygon2 = (Polygon) (importFromJson(str2).getGeometry()); + polygon1 = (Polygon) (TestCommonMethods.fromJson(str1).getGeometry()); + polygon2 = (Polygon) (TestCommonMethods.fromJson(str2).getGeometry()); res = overlaps.execute(polygon1, polygon2, sr, null); assertTrue(res); @@ -1801,8 +1835,8 @@ public static void testPolygonPolygonOverlaps() { str1 = "{\"rings\":[[[0,0],[0,5],[0,10],[10,10],[10,0],[0,0]],[[4,4],[6,4],[6,6],[4,6],[4,4],[4,4]]]}"; str2 = "{\"rings\":[[[1,1],[1,9],[9,9],[9,1],[1,1]]]}"; - polygon1 = (Polygon) (importFromJson(str1).getGeometry()); - polygon2 = (Polygon) (importFromJson(str2).getGeometry()); + polygon1 = (Polygon) (TestCommonMethods.fromJson(str1).getGeometry()); + polygon2 = (Polygon) (TestCommonMethods.fromJson(str2).getGeometry()); res = overlaps.execute(polygon1, polygon2, sr, null); assertTrue(res); @@ -2032,8 +2066,10 @@ public static void testPolygonPolygonWithin() { String str1 = "{\"rings\":[[[0,0],[0,5],[0,10],[10,10],[10,0],[0,0]],[[1,1],[9,1],[9,9],[1,9],[1,1],[1,1]]]}"; String str2 = "{\"rings\":[[[-1,-1],[-1,11],[11,11],[11,-1],[-1,-1]]]}"; - Polygon polygon1 = (Polygon) (importFromJson(str1).getGeometry()); - Polygon polygon2 = (Polygon) (importFromJson(str2).getGeometry()); + Polygon polygon1 = (Polygon) (TestCommonMethods.fromJson(str1) + .getGeometry()); + Polygon polygon2 = (Polygon) (TestCommonMethods.fromJson(str2) + .getGeometry()); boolean res = within.execute(polygon1, polygon2, sr, null); assertTrue(res); @@ -2044,8 +2080,8 @@ public static void testPolygonPolygonWithin() { str1 = "{\"rings\":[[[0,0],[0,5],[0,10],[10,10],[10,0],[0,0]],[[4,4],[6,4],[6,6],[4,6],[4,4],[4,4]]]}"; str2 = "{\"rings\":[[[1,1],[1,9],[9,9],[9,1],[1,1]]]}"; - polygon1 = (Polygon) (importFromJson(str1).getGeometry()); - polygon2 = (Polygon) (importFromJson(str2).getGeometry()); + polygon1 = (Polygon) (TestCommonMethods.fromJson(str1).getGeometry()); + polygon2 = (Polygon) (TestCommonMethods.fromJson(str2).getGeometry()); wiggleGeometry(polygon1, tolerance, 1982); wiggleGeometry(polygon2, tolerance, 511); @@ -2056,8 +2092,8 @@ public static void testPolygonPolygonWithin() { str1 = "{\"rings\":[[[0,0],[0,5],[0,10],[10,10],[10,0],[0,0]],[[1,1],[9,1],[9,9],[1,9],[1,1],[1,1]]]}"; str2 = "{\"rings\":[[[-1,0],[-1,11],[11,11],[11,0],[-1,0]]]}"; - polygon1 = (Polygon) (importFromJson(str1).getGeometry()); - polygon2 = (Polygon) (importFromJson(str2).getGeometry()); + polygon1 = (Polygon) (TestCommonMethods.fromJson(str1).getGeometry()); + polygon2 = (Polygon) (TestCommonMethods.fromJson(str2).getGeometry()); wiggleGeometry(polygon1, tolerance, 1982); wiggleGeometry(polygon2, tolerance, 511); @@ -2068,8 +2104,8 @@ public static void testPolygonPolygonWithin() { str1 = "{\"rings\":[[[0,0],[0,5],[0,10],[10,10],[10,0],[0,0]],[[1,1],[9,1],[9,9],[1,9],[1,1],[1,1]]]}"; str2 = "{\"rings\":[[[2,2],[2,8],[8,8],[8,2],[2,2]]]}"; - polygon1 = (Polygon) (importFromJson(str1).getGeometry()); - polygon2 = (Polygon) (importFromJson(str2).getGeometry()); + polygon1 = (Polygon) (TestCommonMethods.fromJson(str1).getGeometry()); + polygon2 = (Polygon) (TestCommonMethods.fromJson(str2).getGeometry()); res = within.execute(polygon2, polygon1, sr, null); assertTrue(!res); @@ -2289,8 +2325,10 @@ public static void testPolylinePolylineCrosses() { String str1 = "{\"paths\":[[[0,0],[0,5],[0,10],[10,10],[10,0]],[[1,1],[9,1],[9,9],[1,9],[1,1],[1,1]]]}"; String str2 = "{\"paths\":[[[10,10],[10,15],[15,15],[15,10]]]}"; - Polyline polyline1 = (Polyline) (importFromJson(str1).getGeometry()); - Polyline polyline2 = (Polyline) (importFromJson(str2).getGeometry()); + Polyline polyline1 = (Polyline) (TestCommonMethods.fromJson(str1) + .getGeometry()); + Polyline polyline2 = (Polyline) (TestCommonMethods.fromJson(str2) + .getGeometry()); boolean res = crosses.execute(polyline1, polyline2, sr, null); assertTrue(!res); @@ -2301,8 +2339,8 @@ public static void testPolylinePolylineCrosses() { str1 = "{\"paths\":[[[0,0],[0,5],[0,10],[10,10],[10,0],[0,0]],[[1,1],[9,1],[9,9],[1,9],[1,1],[1,1]]]}"; str2 = "{\"paths\":[[[15,5],[5,15],[15,15],[15,5]]]}"; - polyline1 = (Polyline) (importFromJson(str1).getGeometry()); - polyline2 = (Polyline) (importFromJson(str2).getGeometry()); + polyline1 = (Polyline) (TestCommonMethods.fromJson(str1).getGeometry()); + polyline2 = (Polyline) (TestCommonMethods.fromJson(str2).getGeometry()); res = crosses.execute(polyline1, polyline2, sr, null); assertTrue(res); @@ -2314,8 +2352,8 @@ public static void testPolylinePolylineCrosses() { str1 = "{\"paths\":[[[10,10],[10,0],[0,0],[0,10],[10,10]],[[1,1],[9,1],[9,9],[1,9],[1,1],[1,1]]]}"; str2 = "{\"paths\":[[[15,5],[5,15],[15,15]]]}"; - polyline1 = (Polyline) (importFromJson(str1).getGeometry()); - polyline2 = (Polyline) (importFromJson(str2).getGeometry()); + polyline1 = (Polyline) (TestCommonMethods.fromJson(str1).getGeometry()); + polyline2 = (Polyline) (TestCommonMethods.fromJson(str2).getGeometry()); ; res = crosses.execute(polyline1, polyline2, sr, null); @@ -2329,8 +2367,8 @@ public static void testPolylinePolylineCrosses() { str1 = "{\"paths\":[[[10,10],[10,0],[0,0],[0,10]],[[1,1],[9,1],[9,9],[1,9],[1,1],[1,1]]]}"; str2 = "{\"paths\":[[[15,5],[5,15],[15,15]]]}"; - polyline1 = (Polyline) (importFromJson(str1).getGeometry()); - polyline2 = (Polyline) (importFromJson(str2).getGeometry()); + polyline1 = (Polyline) (TestCommonMethods.fromJson(str1).getGeometry()); + polyline2 = (Polyline) (TestCommonMethods.fromJson(str2).getGeometry()); res = crosses.execute(polyline1, polyline2, sr, null); assertTrue(!res); @@ -2340,8 +2378,8 @@ public static void testPolylinePolylineCrosses() { str1 = "{\"paths\":[[[10,11],[10,0],[0,0],[0,10]],[[1,1],[9,1],[9,9],[1,9],[6, 9]]]}"; str2 = "{\"paths\":[[[15,5],[5,15],[15,15],[15,5]]]}"; - polyline1 = (Polyline) (importFromJson(str1).getGeometry()); - polyline2 = (Polyline) (importFromJson(str2).getGeometry()); + polyline1 = (Polyline) (TestCommonMethods.fromJson(str1).getGeometry()); + polyline2 = (Polyline) (TestCommonMethods.fromJson(str2).getGeometry()); res = crosses.execute(polyline1, polyline2, sr, null); assertTrue(res); @@ -2387,10 +2425,12 @@ public static void testPolygonEnvelope() { SpatialReference sr = SpatialReference.create(4326); { - Polygon polygon = (Polygon) (importFromJson("{\"rings\":[[[0,0],[0,5],[0,10],[10,10],[10,0],[0,0]]]}") + Polygon polygon = (Polygon) (TestCommonMethods + .fromJson("{\"rings\":[[[0,0],[0,5],[0,10],[10,10],[10,0],[0,0]]]}") .getGeometry()); Polygon densified = (Polygon) (densify.execute(polygon, 1.0, null)); - Envelope envelope = (Envelope) (importFromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":10}") + Envelope envelope = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":10}") .getGeometry()); wiggleGeometry(densified, 0.00000001, 1982); wiggleGeometry(envelope, 0.00000001, 511); @@ -2407,10 +2447,12 @@ public static void testPolygonEnvelope() { } { - Polygon polygon = (Polygon) (importFromJson("{\"rings\":[[[0,0],[0,5],[0,10],[10,10],[10,0],[0,0]]]}") + Polygon polygon = (Polygon) (TestCommonMethods + .fromJson("{\"rings\":[[[0,0],[0,5],[0,10],[10,10],[10,0],[0,0]]]}") .getGeometry()); Polygon densified = (Polygon) (densify.execute(polygon, 1.0, null)); - Envelope envelope = (Envelope) (importFromJson("{\"xmin\":5,\"ymin\":0,\"xmax\":10,\"ymax\":10}") + Envelope envelope = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":5,\"ymin\":0,\"xmax\":10,\"ymax\":10}") .getGeometry()); wiggleGeometry(densified, 0.00000001, 1982); wiggleGeometry(envelope, 0.00000001, 511); @@ -2431,10 +2473,12 @@ public static void testPolygonEnvelope() { } { - Polygon polygon = (Polygon) (importFromJson("{\"rings\":[[[0,0],[0,5],[0,10],[10,10],[10,0],[0,0]]]}") + Polygon polygon = (Polygon) (TestCommonMethods + .fromJson("{\"rings\":[[[0,0],[0,5],[0,10],[10,10],[10,0],[0,0]]]}") .getGeometry()); Polygon densified = (Polygon) (densify.execute(polygon, 1.0, null)); - Envelope envelope = (Envelope) (importFromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":15,\"ymax\":10}") + Envelope envelope = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":15,\"ymax\":10}") .getGeometry()); wiggleGeometry(densified, 0.00000001, 1982); wiggleGeometry(envelope, 0.00000001, 511); @@ -2456,10 +2500,12 @@ public static void testPolygonEnvelope() { } { - Polygon polygon = (Polygon) (importFromJson("{\"rings\":[[[0,0],[0,5],[0,10],[10,10],[10,0],[0,0]]]}") + Polygon polygon = (Polygon) (TestCommonMethods + .fromJson("{\"rings\":[[[0,0],[0,5],[0,10],[10,10],[10,0],[0,0]]]}") .getGeometry()); Polygon densified = (Polygon) (densify.execute(polygon, 1.0, null)); - Envelope envelope = (Envelope) (importFromJson("{\"xmin\":5,\"ymin\":0,\"xmax\":15,\"ymax\":10}") + Envelope envelope = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":5,\"ymin\":0,\"xmax\":15,\"ymax\":10}") .getGeometry()); wiggleGeometry(densified, 0.00000001, 1982); wiggleGeometry(envelope, 0.00000001, 511); @@ -2483,10 +2529,12 @@ public static void testPolygonEnvelope() { } { - Polygon polygon = (Polygon) (importFromJson("{\"rings\":[[[0,0],[0,5],[0,10],[10,10],[10,0],[0,0]]]}") + Polygon polygon = (Polygon) (TestCommonMethods + .fromJson("{\"rings\":[[[0,0],[0,5],[0,10],[10,10],[10,0],[0,0]]]}") .getGeometry()); Polygon densified = (Polygon) (densify.execute(polygon, 1.0, null)); - Envelope envelope = (Envelope) (importFromJson("{\"xmin\":10,\"ymin\":0,\"xmax\":15,\"ymax\":5}") + Envelope envelope = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":10,\"ymin\":0,\"xmax\":15,\"ymax\":5}") .getGeometry()); wiggleGeometry(densified, 0.00000001, 1982); wiggleGeometry(envelope, 0.00000001, 511); @@ -2508,10 +2556,12 @@ public static void testPolygonEnvelope() { } { - Polygon polygon = (Polygon) (importFromJson("{\"rings\":[[[0,0],[0,5],[0,10],[10,10],[10,0],[0,0]]]}") + Polygon polygon = (Polygon) (TestCommonMethods + .fromJson("{\"rings\":[[[0,0],[0,5],[0,10],[10,10],[10,0],[0,0]]]}") .getGeometry()); Polygon densified = (Polygon) (densify.execute(polygon, 1.0, null)); - Envelope envelope = (Envelope) (importFromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":10}") + Envelope envelope = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":10}") .getGeometry()); wiggleGeometry(densified, 0.00000001, 1982); wiggleGeometry(envelope, 0.00000001, 511); @@ -2529,10 +2579,12 @@ public static void testPolygonEnvelope() { } { - Polygon polygon = (Polygon) (importFromJson("{\"rings\":[[[0,0],[0,5],[0,10],[10,0],[0,0]]]}") + Polygon polygon = (Polygon) (TestCommonMethods + .fromJson("{\"rings\":[[[0,0],[0,5],[0,10],[10,0],[0,0]]]}") .getGeometry()); Polygon densified = (Polygon) (densify.execute(polygon, 1.0, null)); - Envelope envelope = (Envelope) (importFromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":10}") + Envelope envelope = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":10}") .getGeometry()); wiggleGeometry(densified, 0.00000001, 1982); wiggleGeometry(envelope, 0.00000001, 511); @@ -2554,10 +2606,12 @@ public static void testPolygonEnvelope() { } { - Polygon polygon = (Polygon) (importFromJson("{\"rings\":[[[0,0],[0,5],[0,10],[10,0],[0,0]]]}") + Polygon polygon = (Polygon) (TestCommonMethods + .fromJson("{\"rings\":[[[0,0],[0,5],[0,10],[10,0],[0,0]]]}") .getGeometry()); Polygon densified = (Polygon) (densify.execute(polygon, 1.0, null)); - Envelope envelope = (Envelope) (importFromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":15,\"ymax\":10}") + Envelope envelope = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":15,\"ymax\":10}") .getGeometry()); wiggleGeometry(densified, 0.00000001, 1982); wiggleGeometry(envelope, 0.00000001, 511); @@ -2579,10 +2633,12 @@ public static void testPolygonEnvelope() { } { - Polygon polygon = (Polygon) (importFromJson("{\"rings\":[[[0,0],[0,5],[0,10],[10,10],[10,0],[0,0]]]}") + Polygon polygon = (Polygon) (TestCommonMethods + .fromJson("{\"rings\":[[[0,0],[0,5],[0,10],[10,10],[10,0],[0,0]]]}") .getGeometry()); Polygon densified = (Polygon) (densify.execute(polygon, 1.0, null)); - Envelope envelope = (Envelope) (importFromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":0,\"ymax\":0}") + Envelope envelope = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":0,\"ymax\":0}") .getGeometry()); wiggleGeometry(densified, 0.00000001, 1982); wiggleGeometry(envelope, 0.00000001, 511); @@ -2605,10 +2661,12 @@ public static void testPolygonEnvelope() { } { - Polygon polygon = (Polygon) (importFromJson("{\"rings\":[[[0,0],[0,5],[0,10],[10,10],[10,0],[0,0]]]}") + Polygon polygon = (Polygon) (TestCommonMethods + .fromJson("{\"rings\":[[[0,0],[0,5],[0,10],[10,10],[10,0],[0,0]]]}") .getGeometry()); Polygon densified = (Polygon) (densify.execute(polygon, 1.0, null)); - Envelope envelope = (Envelope) (importFromJson("{\"xmin\":1,\"ymin\":1,\"xmax\":1,\"ymax\":1}") + Envelope envelope = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":1,\"ymin\":1,\"xmax\":1,\"ymax\":1}") .getGeometry()); wiggleGeometry(densified, 0.00000001, 1982); wiggleGeometry(envelope, 0.00000001, 511); @@ -2629,10 +2687,12 @@ public static void testPolygonEnvelope() { } { - Polygon polygon = (Polygon) (importFromJson("{\"rings\":[[[0,0],[0,5],[0,10],[10,10],[10,0],[0,0]]]}") + Polygon polygon = (Polygon) (TestCommonMethods + .fromJson("{\"rings\":[[[0,0],[0,5],[0,10],[10,10],[10,0],[0,0]]]}") .getGeometry()); Polygon densified = (Polygon) (densify.execute(polygon, 1.0, null)); - Envelope envelope = (Envelope) (importFromJson("{\"xmin\":-1,\"ymin\":-1,\"xmax\":-1,\"ymax\":-1}") + Envelope envelope = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":-1,\"ymin\":-1,\"xmax\":-1,\"ymax\":-1}") .getGeometry()); wiggleGeometry(densified, 0.00000001, 1982); wiggleGeometry(envelope, 0.00000001, 511); @@ -2653,10 +2713,12 @@ public static void testPolygonEnvelope() { } { - Polygon polygon = (Polygon) (importFromJson("{\"rings\":[[[0,0],[0,5],[0,10],[10,10],[10,0],[0,0]]]}") + Polygon polygon = (Polygon) (TestCommonMethods + .fromJson("{\"rings\":[[[0,0],[0,5],[0,10],[10,10],[10,0],[0,0]]]}") .getGeometry()); Polygon densified = (Polygon) (densify.execute(polygon, 1.0, null)); - Envelope envelope = (Envelope) (importFromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":1,\"ymax\":0}") + Envelope envelope = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":1,\"ymax\":0}") .getGeometry()); wiggleGeometry(densified, 0.00000001, 1982); wiggleGeometry(envelope, 0.00000001, 511); @@ -2683,10 +2745,12 @@ public static void testPolygonEnvelope() { } { - Polygon polygon = (Polygon) (importFromJson("{\"rings\":[[[0,0],[0,5],[0,10],[10,10],[10,0],[0,0]]]}") + Polygon polygon = (Polygon) (TestCommonMethods + .fromJson("{\"rings\":[[[0,0],[0,5],[0,10],[10,10],[10,0],[0,0]]]}") .getGeometry()); Polygon densified = (Polygon) (densify.execute(polygon, 1.0, null)); - Envelope envelope = (Envelope) (importFromJson("{\"xmin\":0,\"ymin\":1,\"xmax\":1,\"ymax\":1}") + Envelope envelope = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":0,\"ymin\":1,\"xmax\":1,\"ymax\":1}") .getGeometry()); wiggleGeometry(densified, 0.00000001, 1982); wiggleGeometry(envelope, 0.00000001, 511); @@ -2713,10 +2777,12 @@ public static void testPolygonEnvelope() { } { - Polygon polygon = (Polygon) (importFromJson("{\"rings\":[[[0,0],[0,5],[0,10],[10,0],[0,0]]]}") + Polygon polygon = (Polygon) (TestCommonMethods + .fromJson("{\"rings\":[[[0,0],[0,5],[0,10],[10,0],[0,0]]]}") .getGeometry()); Polygon densified = (Polygon) (densify.execute(polygon, 1.0, null)); - Envelope envelope = (Envelope) (importFromJson("{\"xmin\":5,\"ymin\":5,\"xmax\":6,\"ymax\":5}") + Envelope envelope = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":5,\"ymin\":5,\"xmax\":6,\"ymax\":5}") .getGeometry()); wiggleGeometry(densified, 0.00000001, 1982); wiggleGeometry(envelope, 0.00000001, 511); @@ -2739,10 +2805,12 @@ public static void testPolygonEnvelope() { } { - Polygon polygon = (Polygon) (importFromJson("{\"rings\":[[[0,0],[0,5],[0,10],[10,0],[0,0]]]}") + Polygon polygon = (Polygon) (TestCommonMethods + .fromJson("{\"rings\":[[[0,0],[0,5],[0,10],[10,0],[0,0]]]}") .getGeometry()); Polygon densified = (Polygon) (densify.execute(polygon, 1.0, null)); - Envelope envelope = (Envelope) (importFromJson("{\"xmin\":6,\"ymin\":5,\"xmax\":7,\"ymax\":5}") + Envelope envelope = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":6,\"ymin\":5,\"xmax\":7,\"ymax\":5}") .getGeometry()); wiggleGeometry(densified, 0.00000001, 1982); wiggleGeometry(envelope, 0.00000001, 511); @@ -2762,10 +2830,12 @@ public static void testPolygonEnvelope() { } { - Polygon polygon = (Polygon) (importFromJson("{\"rings\":[[[0,0],[0,5],[0,10],[10,0],[0,0]]]}") + Polygon polygon = (Polygon) (TestCommonMethods + .fromJson("{\"rings\":[[[0,0],[0,5],[0,10],[10,0],[0,0]]]}") .getGeometry()); Polygon densified = (Polygon) (densify.execute(polygon, 1.0, null)); - Envelope envelope = (Envelope) (importFromJson("{\"xmin\":4,\"ymin\":5,\"xmax\":7,\"ymax\":5}") + Envelope envelope = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":4,\"ymin\":5,\"xmax\":7,\"ymax\":5}") .getGeometry()); wiggleGeometry(densified, 0.00000001, 1982); wiggleGeometry(envelope, 0.00000001, 511); @@ -2808,11 +2878,13 @@ public static void testPolylineEnvelope() { SpatialReference sr = SpatialReference.create(4326); { - Polyline polyline = (Polyline) (importFromJson("{\"paths\":[[[0,0],[0,5],[0,10],[10,10],[10,0]]]}") + Polyline polyline = (Polyline) (TestCommonMethods + .fromJson("{\"paths\":[[[0,0],[0,5],[0,10],[10,10],[10,0]]]}") .getGeometry()); Polyline densified = (Polyline) (densify.execute(polyline, 1.0, null)); - Envelope envelope = (Envelope) (importFromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":10}") + Envelope envelope = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":10}") .getGeometry()); wiggleGeometry(densified, 0.00000001, 1982); wiggleGeometry(envelope, 0.00000001, 511); @@ -2830,11 +2902,12 @@ public static void testPolylineEnvelope() { } { - Polyline polyline = (Polyline) (importFromJson("{\"paths\":[[[-10,0],[0,10]]]}") - .getGeometry()); + Polyline polyline = (Polyline) (TestCommonMethods + .fromJson("{\"paths\":[[[-10,0],[0,10]]]}").getGeometry()); Polyline densified = (Polyline) (densify.execute(polyline, 1.0, null)); - Envelope envelope = (Envelope) (importFromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":10}") + Envelope envelope = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":10}") .getGeometry()); wiggleGeometry(densified, 0.00000001, 1982); wiggleGeometry(envelope, 0.00000001, 511); @@ -2848,11 +2921,12 @@ public static void testPolylineEnvelope() { } { - Polyline polyline = (Polyline) (importFromJson("{\"paths\":[[[-11,0],[1,12]]]}") - .getGeometry()); + Polyline polyline = (Polyline) (TestCommonMethods + .fromJson("{\"paths\":[[[-11,0],[1,12]]]}").getGeometry()); Polyline densified = (Polyline) (densify.execute(polyline, 1.0, null)); - Envelope envelope = (Envelope) (importFromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":10}") + Envelope envelope = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":10}") .getGeometry()); wiggleGeometry(densified, 0.00000001, 1982); wiggleGeometry(envelope, 0.00000001, 511); @@ -2866,11 +2940,12 @@ public static void testPolylineEnvelope() { } { - Polyline polyline = (Polyline) (importFromJson("{\"paths\":[[[5,5],[6,6]]]}") - .getGeometry()); + Polyline polyline = (Polyline) (TestCommonMethods + .fromJson("{\"paths\":[[[5,5],[6,6]]]}").getGeometry()); Polyline densified = (Polyline) (densify.execute(polyline, 1.0, null)); - Envelope envelope = (Envelope) (importFromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":10}") + Envelope envelope = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":10}") .getGeometry()); wiggleGeometry(densified, 0.00000001, 1982); wiggleGeometry(envelope, 0.00000001, 511); @@ -2885,11 +2960,12 @@ public static void testPolylineEnvelope() { } { - Polyline polyline = (Polyline) (importFromJson("{\"paths\":[[[5,5],[10,10]]]}") - .getGeometry()); + Polyline polyline = (Polyline) (TestCommonMethods + .fromJson("{\"paths\":[[[5,5],[10,10]]]}").getGeometry()); Polyline densified = (Polyline) (densify.execute(polyline, 1.0, null)); - Envelope envelope = (Envelope) (importFromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":10}") + Envelope envelope = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":10}") .getGeometry()); wiggleGeometry(densified, 0.00000001, 1982); wiggleGeometry(envelope, 0.00000001, 511); @@ -2902,11 +2978,12 @@ public static void testPolylineEnvelope() { } { - Polyline polyline = (Polyline) (importFromJson("{\"paths\":[[[-5,5],[15,5]]]}") - .getGeometry()); + Polyline polyline = (Polyline) (TestCommonMethods + .fromJson("{\"paths\":[[[-5,5],[15,5]]]}").getGeometry()); Polyline densified = (Polyline) (densify.execute(polyline, 1.0, null)); - Envelope envelope = (Envelope) (importFromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":10}") + Envelope envelope = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":10}") .getGeometry()); wiggleGeometry(densified, 0.00000001, 1982); wiggleGeometry(envelope, 0.00000001, 511); @@ -2920,11 +2997,12 @@ public static void testPolylineEnvelope() { } { - Polyline polyline = (Polyline) (importFromJson("{\"paths\":[[[5,5],[5,15]]]}") - .getGeometry()); + Polyline polyline = (Polyline) (TestCommonMethods + .fromJson("{\"paths\":[[[5,5],[5,15]]]}").getGeometry()); Polyline densified = (Polyline) (densify.execute(polyline, 1.0, null)); - Envelope envelope = (Envelope) (importFromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":10}") + Envelope envelope = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":10}") .getGeometry()); wiggleGeometry(densified, 0.00000001, 1982); wiggleGeometry(envelope, 0.00000001, 511); @@ -2945,11 +3023,12 @@ public static void testPolylineEnvelope() { } { - Polyline polyline = (Polyline) (importFromJson("{\"paths\":[[[5,11],[5,15]]]}") - .getGeometry()); + Polyline polyline = (Polyline) (TestCommonMethods + .fromJson("{\"paths\":[[[5,11],[5,15]]]}").getGeometry()); Polyline densified = (Polyline) (densify.execute(polyline, 1.0, null)); - Envelope envelope = (Envelope) (importFromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":10}") + Envelope envelope = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":10}") .getGeometry()); wiggleGeometry(densified, 0.00000001, 1982); wiggleGeometry(envelope, 0.00000001, 511); @@ -2965,11 +3044,13 @@ public static void testPolylineEnvelope() { } { - Polyline polyline = (Polyline) (importFromJson("{\"paths\":[[[0,0],[0,5],[0,10],[10,10],[10,0]]]}") + Polyline polyline = (Polyline) (TestCommonMethods + .fromJson("{\"paths\":[[[0,0],[0,5],[0,10],[10,10],[10,0]]]}") .getGeometry()); Polyline densified = (Polyline) (densify.execute(polyline, 1.0, null)); - Envelope envelope = (Envelope) (importFromJson("{\"xmin\":0,\"ymin\":10,\"xmax\":10,\"ymax\":10}") + Envelope envelope = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":0,\"ymin\":10,\"xmax\":10,\"ymax\":10}") .getGeometry()); wiggleGeometry(densified, 0.00000001, 1982); wiggleGeometry(envelope, 0.00000001, 511); @@ -2988,11 +3069,13 @@ public static void testPolylineEnvelope() { } { - Polyline polyline = (Polyline) (importFromJson("{\"paths\":[[[0,0],[0,5],[0,10],[10,10],[10,0]]]}") + Polyline polyline = (Polyline) (TestCommonMethods + .fromJson("{\"paths\":[[[0,0],[0,5],[0,10],[10,10],[10,0]]]}") .getGeometry()); Polyline densified = (Polyline) (densify.execute(polyline, 1.0, null)); - Envelope envelope = (Envelope) (importFromJson("{\"xmin\":0,\"ymin\":-5,\"xmax\":0,\"ymax\":10}") + Envelope envelope = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":0,\"ymin\":-5,\"xmax\":0,\"ymax\":10}") .getGeometry()); wiggleGeometry(densified, 0.00000001, 1982); wiggleGeometry(envelope, 0.00000001, 511); @@ -3005,11 +3088,13 @@ public static void testPolylineEnvelope() { } { - Polyline polyline = (Polyline) (importFromJson("{\"paths\":[[[0,0],[0,5],[0,10],[10,10],[10,0]]]}") + Polyline polyline = (Polyline) (TestCommonMethods + .fromJson("{\"paths\":[[[0,0],[0,5],[0,10],[10,10],[10,0]]]}") .getGeometry()); Polyline densified = (Polyline) (densify.execute(polyline, 1.0, null)); - Envelope envelope = (Envelope) (importFromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":0,\"ymax\":0}") + Envelope envelope = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":0,\"ymax\":0}") .getGeometry()); wiggleGeometry(densified, 0.00000001, 511); wiggleGeometry(envelope, 0.00000001, 1982); @@ -3029,11 +3114,13 @@ public static void testPolylineEnvelope() { } { - Polyline polyline = (Polyline) (importFromJson("{\"paths\":[[[0,0],[0,5],[0,10],[10,10],[10,0]]]}") + Polyline polyline = (Polyline) (TestCommonMethods + .fromJson("{\"paths\":[[[0,0],[0,5],[0,10],[10,10],[10,0]]]}") .getGeometry()); Polyline densified = (Polyline) (densify.execute(polyline, 1.0, null)); - Envelope envelope = (Envelope) (importFromJson("{\"xmin\":0,\"ymin\":5,\"xmax\":0,\"ymax\":5}") + Envelope envelope = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":0,\"ymin\":5,\"xmax\":0,\"ymax\":5}") .getGeometry()); wiggleGeometry(densified, 0.00000001, 1982); wiggleGeometry(envelope, 0.00000001, 511); @@ -3052,11 +3139,12 @@ public static void testPolylineEnvelope() { } { - Polyline polyline = (Polyline) (importFromJson("{\"paths\":[[[2,-2],[2,2]]]}") - .getGeometry()); + Polyline polyline = (Polyline) (TestCommonMethods + .fromJson("{\"paths\":[[[2,-2],[2,2]]]}").getGeometry()); Polyline densified = (Polyline) (densify.execute(polyline, 1.0, null)); - Envelope envelope = (Envelope) (importFromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":5,\"ymax\":0}") + Envelope envelope = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":5,\"ymax\":0}") .getGeometry()); wiggleGeometry(densified, 0.00000001, 1982); wiggleGeometry(envelope, 0.00000001, 511); @@ -3072,11 +3160,12 @@ public static void testPolylineEnvelope() { } { - Polyline polyline = (Polyline) (importFromJson("{\"paths\":[[[2,0],[2,2]]]}") - .getGeometry()); + Polyline polyline = (Polyline) (TestCommonMethods + .fromJson("{\"paths\":[[[2,0],[2,2]]]}").getGeometry()); Polyline densified = (Polyline) (densify.execute(polyline, 1.0, null)); - Envelope envelope = (Envelope) (importFromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":5,\"ymax\":0}") + Envelope envelope = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":5,\"ymax\":0}") .getGeometry()); wiggleGeometry(densified, 0.00000001, 1982); wiggleGeometry(envelope, 0.00000001, 511); @@ -3092,11 +3181,12 @@ public static void testPolylineEnvelope() { } { - Polyline polyline = (Polyline) (importFromJson("{\"paths\":[[[2,0],[2,2]]]}") - .getGeometry()); + Polyline polyline = (Polyline) (TestCommonMethods + .fromJson("{\"paths\":[[[2,0],[2,2]]]}").getGeometry()); Polyline densified = (Polyline) (densify.execute(polyline, 1.0, null)); - Envelope envelope = (Envelope) (importFromJson("{\"xmin\":2,\"ymin\":0,\"xmax\":2,\"ymax\":3}") + Envelope envelope = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":2,\"ymin\":0,\"xmax\":2,\"ymax\":3}") .getGeometry()); wiggleGeometry(densified, 0.00000001, 1982); wiggleGeometry(envelope, 0.00000001, 511); @@ -3113,11 +3203,12 @@ public static void testPolylineEnvelope() { } { - Polyline polyline = (Polyline) (importFromJson("{\"paths\":[[[5,5],[6,6]]]}") - .getGeometry()); + Polyline polyline = (Polyline) (TestCommonMethods + .fromJson("{\"paths\":[[[5,5],[6,6]]]}").getGeometry()); Polyline densified = (Polyline) (densify.execute(polyline, 1.0, null)); - Envelope envelope = (Envelope) (importFromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":5,\"ymax\":5}") + Envelope envelope = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":5,\"ymax\":5}") .getGeometry()); wiggleGeometry(densified, 0.00000001, 1982); wiggleGeometry(envelope, 0.00000001, 511); @@ -3132,11 +3223,12 @@ public static void testPolylineEnvelope() { } { - Polyline polyline = (Polyline) (importFromJson("{\"paths\":[[[5,5],[5,10]]]}") - .getGeometry()); + Polyline polyline = (Polyline) (TestCommonMethods + .fromJson("{\"paths\":[[[5,5],[5,10]]]}").getGeometry()); Polyline densified = (Polyline) (densify.execute(polyline, 1.0, null)); - Envelope envelope = (Envelope) (importFromJson("{\"xmin\":5,\"ymin\":5,\"xmax\":5,\"ymax\":10}") + Envelope envelope = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":5,\"ymin\":5,\"xmax\":5,\"ymax\":10}") .getGeometry()); wiggleGeometry(densified, 0.00000001, 1982); wiggleGeometry(envelope, 0.00000001, 511); @@ -3175,9 +3267,11 @@ public static void testMultiPointEnvelope() { SpatialReference sr = SpatialReference.create(4326); { - MultiPoint multi_point = (MultiPoint) (importFromJson("{\"points\":[[0,0],[0,10],[10,10],[10,0]]}") + MultiPoint multi_point = (MultiPoint) (TestCommonMethods + .fromJson("{\"points\":[[0,0],[0,10],[10,10],[10,0]]}") .getGeometry()); - Envelope envelope = (Envelope) (importFromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":10}") + Envelope envelope = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":10}") .getGeometry()); wiggleGeometry(multi_point, 0.00000001, 1982); wiggleGeometry(envelope, 0.00000001, 511); @@ -3193,9 +3287,11 @@ public static void testMultiPointEnvelope() { } { - MultiPoint multi_point = (MultiPoint) (importFromJson("{\"points\":[[0,0],[0,10],[10,10],[5,5]]}") + MultiPoint multi_point = (MultiPoint) (TestCommonMethods + .fromJson("{\"points\":[[0,0],[0,10],[10,10],[5,5]]}") .getGeometry()); - Envelope envelope = (Envelope) (importFromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":10}") + Envelope envelope = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":10}") .getGeometry()); wiggleGeometry(multi_point, 0.00000001, 1982); wiggleGeometry(envelope, 0.00000001, 511); @@ -3215,9 +3311,11 @@ public static void testMultiPointEnvelope() { } { - MultiPoint multi_point = (MultiPoint) (importFromJson("{\"points\":[[0,0],[0,10],[10,10],[5,5],[15,15]]}") + MultiPoint multi_point = (MultiPoint) (TestCommonMethods + .fromJson("{\"points\":[[0,0],[0,10],[10,10],[5,5],[15,15]]}") .getGeometry()); - Envelope envelope = (Envelope) (importFromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":10}") + Envelope envelope = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":10}") .getGeometry()); wiggleGeometry(multi_point, 0.00000001, 1982); wiggleGeometry(envelope, 0.00000001, 511); @@ -3236,9 +3334,11 @@ public static void testMultiPointEnvelope() { } { - MultiPoint multi_point = (MultiPoint) (importFromJson("{\"points\":[[0,0],[0,10],[10,10],[15,15]]}") + MultiPoint multi_point = (MultiPoint) (TestCommonMethods + .fromJson("{\"points\":[[0,0],[0,10],[10,10],[15,15]]}") .getGeometry()); - Envelope envelope = (Envelope) (importFromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":10}") + Envelope envelope = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":10}") .getGeometry()); wiggleGeometry(multi_point, 0.00000001, 1982); wiggleGeometry(envelope, 0.00000001, 511); @@ -3255,9 +3355,11 @@ public static void testMultiPointEnvelope() { } { - MultiPoint multi_point = (MultiPoint) (importFromJson("{\"points\":[[0,-1],[0,11],[11,11],[15,15]]}") + MultiPoint multi_point = (MultiPoint) (TestCommonMethods + .fromJson("{\"points\":[[0,-1],[0,11],[11,11],[15,15]]}") .getGeometry()); - Envelope envelope = (Envelope) (importFromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":10}") + Envelope envelope = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":10}") .getGeometry()); wiggleGeometry(multi_point, 0.00000001, 1982); wiggleGeometry(envelope, 0.00000001, 511); @@ -3272,9 +3374,11 @@ public static void testMultiPointEnvelope() { } { - MultiPoint multi_point = (MultiPoint) (importFromJson("{\"points\":[[0,0],[0,10],[10,10],[10,0]]}") + MultiPoint multi_point = (MultiPoint) (TestCommonMethods + .fromJson("{\"points\":[[0,0],[0,10],[10,10],[10,0]]}") .getGeometry()); - Envelope envelope = (Envelope) (importFromJson("{\"xmin\":0,\"ymin\":10,\"xmax\":10,\"ymax\":10}") + Envelope envelope = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":0,\"ymin\":10,\"xmax\":10,\"ymax\":10}") .getGeometry()); wiggleGeometry(multi_point, 0.00000001, 1982); wiggleGeometry(envelope, 0.00000001, 511); @@ -3299,9 +3403,11 @@ public static void testMultiPointEnvelope() { } { - MultiPoint multi_point = (MultiPoint) (importFromJson("{\"points\":[[0,0],[1,10],[10,10],[10,0]]}") + MultiPoint multi_point = (MultiPoint) (TestCommonMethods + .fromJson("{\"points\":[[0,0],[1,10],[10,10],[10,0]]}") .getGeometry()); - Envelope envelope = (Envelope) (importFromJson("{\"xmin\":0,\"ymin\":10,\"xmax\":10,\"ymax\":10}") + Envelope envelope = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":0,\"ymin\":10,\"xmax\":10,\"ymax\":10}") .getGeometry()); wiggleGeometry(multi_point, 0.00000001, 1982); wiggleGeometry(envelope, 0.00000001, 511); @@ -3326,9 +3432,10 @@ public static void testMultiPointEnvelope() { } { - MultiPoint multi_point = (MultiPoint) (importFromJson("{\"points\":[[0,10],[10,10]]}") - .getGeometry()); - Envelope envelope = (Envelope) (importFromJson("{\"xmin\":0,\"ymin\":10,\"xmax\":10,\"ymax\":10}") + MultiPoint multi_point = (MultiPoint) (TestCommonMethods + .fromJson("{\"points\":[[0,10],[10,10]]}").getGeometry()); + Envelope envelope = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":0,\"ymin\":10,\"xmax\":10,\"ymax\":10}") .getGeometry()); wiggleGeometry(multi_point, 0.00000001, 1982); wiggleGeometry(envelope, 0.00000001, 511); @@ -3357,9 +3464,10 @@ public static void testMultiPointEnvelope() { } { - MultiPoint multi_point = (MultiPoint) (importFromJson("{\"points\":[[1,10],[9,10]]}") - .getGeometry()); - Envelope envelope = (Envelope) (importFromJson("{\"xmin\":0,\"ymin\":10,\"xmax\":10,\"ymax\":10}") + MultiPoint multi_point = (MultiPoint) (TestCommonMethods + .fromJson("{\"points\":[[1,10],[9,10]]}").getGeometry()); + Envelope envelope = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":0,\"ymin\":10,\"xmax\":10,\"ymax\":10}") .getGeometry()); wiggleGeometry(multi_point, 0.00000001, 1982); wiggleGeometry(envelope, 0.00000001, 511); @@ -3387,9 +3495,11 @@ public static void testMultiPointEnvelope() { } { - MultiPoint multi_point = (MultiPoint) (importFromJson("{\"points\":[[0,-1],[0,11],[11,11],[15,15]]}") + MultiPoint multi_point = (MultiPoint) (TestCommonMethods + .fromJson("{\"points\":[[0,-1],[0,11],[11,11],[15,15]]}") .getGeometry()); - Envelope envelope = (Envelope) (importFromJson("{\"xmin\":0,\"ymin\":10,\"xmax\":10,\"ymax\":10}") + Envelope envelope = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":0,\"ymin\":10,\"xmax\":10,\"ymax\":10}") .getGeometry()); wiggleGeometry(multi_point, 0.00000001, 1982); wiggleGeometry(envelope, 0.00000001, 511); @@ -3404,9 +3514,11 @@ public static void testMultiPointEnvelope() { } { - MultiPoint multi_point = (MultiPoint) (importFromJson("{\"points\":[[0,-1],[0,11],[11,11],[15,15]]}") + MultiPoint multi_point = (MultiPoint) (TestCommonMethods + .fromJson("{\"points\":[[0,-1],[0,11],[11,11],[15,15]]}") .getGeometry()); - Envelope envelope = (Envelope) (importFromJson("{\"xmin\":10,\"ymin\":10,\"xmax\":10,\"ymax\":10}") + Envelope envelope = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":10,\"ymin\":10,\"xmax\":10,\"ymax\":10}") .getGeometry()); wiggleGeometry(multi_point, 0.00000001, 1982); wiggleGeometry(envelope, 0.00000001, 511); @@ -3421,9 +3533,11 @@ public static void testMultiPointEnvelope() { } { - MultiPoint multi_point = (MultiPoint) (importFromJson("{\"points\":[[0,-1],[0,11],[11,11],[15,15]]}") + MultiPoint multi_point = (MultiPoint) (TestCommonMethods + .fromJson("{\"points\":[[0,-1],[0,11],[11,11],[15,15]]}") .getGeometry()); - Envelope envelope = (Envelope) (importFromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":11,\"ymax\":11}") + Envelope envelope = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":11,\"ymax\":11}") .getGeometry()); wiggleGeometry(multi_point, 0.00000001, 1982); wiggleGeometry(envelope, 0.00000001, 511); @@ -3439,9 +3553,10 @@ public static void testMultiPointEnvelope() { } { - MultiPoint multi_point = (MultiPoint) (importFromJson("{\"points\":[[0,-1],[0,-1]]}") - .getGeometry()); - Envelope envelope = (Envelope) (importFromJson("{\"xmin\":0,\"ymin\":-1,\"xmax\":0,\"ymax\":-1}") + MultiPoint multi_point = (MultiPoint) (TestCommonMethods + .fromJson("{\"points\":[[0,-1],[0,-1]]}").getGeometry()); + Envelope envelope = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":0,\"ymin\":-1,\"xmax\":0,\"ymax\":-1}") .getGeometry()); wiggleGeometry(multi_point, 0.00000001, 1982); wiggleGeometry(envelope, 0.00000001, 511); @@ -3477,9 +3592,10 @@ public static void testPointEnvelope() { SpatialReference sr = SpatialReference.create(4326); { - Point point = (Point) (importFromJson("{\"x\":5,\"y\":6}") - .getGeometry()); - Envelope envelope = (Envelope) (importFromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":10}") + Point point = (Point) (TestCommonMethods + .fromJson("{\"x\":5,\"y\":6}").getGeometry()); + Envelope envelope = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":10}") .getGeometry()); wiggleGeometry(point, 0.00000001, 1982); wiggleGeometry(envelope, 0.00000001, 511); @@ -3493,9 +3609,10 @@ public static void testPointEnvelope() { } { - Point point = (Point) (importFromJson("{\"x\":0,\"y\":10}") - .getGeometry()); - Envelope envelope = (Envelope) (importFromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":10}") + Point point = (Point) (TestCommonMethods + .fromJson("{\"x\":0,\"y\":10}").getGeometry()); + Envelope envelope = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":10}") .getGeometry()); wiggleGeometry(point, 0.00000001, 1982); wiggleGeometry(envelope, 0.00000001, 511); @@ -3509,9 +3626,10 @@ public static void testPointEnvelope() { } { - Point point = (Point) (importFromJson("{\"x\":0,\"y\":11}") - .getGeometry()); - Envelope envelope = (Envelope) (importFromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":10}") + Point point = (Point) (TestCommonMethods + .fromJson("{\"x\":0,\"y\":11}").getGeometry()); + Envelope envelope = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":10}") .getGeometry()); wiggleGeometry(point, 0.00000001, 1982); wiggleGeometry(envelope, 0.00000001, 511); @@ -3525,9 +3643,10 @@ public static void testPointEnvelope() { } { - Point point = (Point) (importFromJson("{\"x\":0,\"y\":0}") - .getGeometry()); - Envelope envelope = (Envelope) (importFromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":0}") + Point point = (Point) (TestCommonMethods + .fromJson("{\"x\":0,\"y\":0}").getGeometry()); + Envelope envelope = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":0}") .getGeometry()); wiggleGeometry(point, 0.00000001, 1982); wiggleGeometry(envelope, 0.00000001, 511); @@ -3541,9 +3660,10 @@ public static void testPointEnvelope() { } { - Point point = (Point) (importFromJson("{\"x\":5,\"y\":0}") - .getGeometry()); - Envelope envelope = (Envelope) (importFromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":0}") + Point point = (Point) (TestCommonMethods + .fromJson("{\"x\":5,\"y\":0}").getGeometry()); + Envelope envelope = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":0}") .getGeometry()); wiggleGeometry(point, 0.00000001, 1982); wiggleGeometry(envelope, 0.00000001, 511); @@ -3557,9 +3677,10 @@ public static void testPointEnvelope() { } { - Point point = (Point) (importFromJson("{\"x\":11,\"y\":0}") - .getGeometry()); - Envelope envelope = (Envelope) (importFromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":0}") + Point point = (Point) (TestCommonMethods + .fromJson("{\"x\":11,\"y\":0}").getGeometry()); + Envelope envelope = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":0}") .getGeometry()); wiggleGeometry(point, 0.00000001, 1982); wiggleGeometry(envelope, 0.00000001, 511); @@ -3573,9 +3694,10 @@ public static void testPointEnvelope() { } { - Point point = (Point) (importFromJson("{\"x\":0,\"y\":0}") - .getGeometry()); - Envelope envelope = (Envelope) (importFromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":0,\"ymax\":0}") + Point point = (Point) (TestCommonMethods + .fromJson("{\"x\":0,\"y\":0}").getGeometry()); + Envelope envelope = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":0,\"ymax\":0}") .getGeometry()); wiggleGeometry(point, 0.00000001, 1982); wiggleGeometry(envelope, 0.00000001, 511); @@ -3609,9 +3731,11 @@ public static void testEnvelopeEnvelope() { SpatialReference sr = SpatialReference.create(4326); { - Envelope env1 = (Envelope) (importFromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":10}") + Envelope env1 = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":10}") .getGeometry()); - Envelope env2 = (Envelope) (importFromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":10}") + Envelope env2 = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":10}") .getGeometry()); wiggleGeometry(env1, 0.00000001, 1982); wiggleGeometry(env2, 0.00000001, 511); @@ -3628,9 +3752,11 @@ public static void testEnvelopeEnvelope() { } { - Envelope env1 = (Envelope) (importFromJson("{\"xmin\":5,\"ymin\":5,\"xmax\":10,\"ymax\":10}") + Envelope env1 = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":5,\"ymin\":5,\"xmax\":10,\"ymax\":10}") .getGeometry()); - Envelope env2 = (Envelope) (importFromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":10}") + Envelope env2 = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":10}") .getGeometry()); wiggleGeometry(env1, 0.00000001, 1982); wiggleGeometry(env2, 0.00000001, 511); @@ -3646,9 +3772,11 @@ public static void testEnvelopeEnvelope() { } { - Envelope env1 = (Envelope) (importFromJson("{\"xmin\":5,\"ymin\":5,\"xmax\":15,\"ymax\":15}") + Envelope env1 = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":5,\"ymin\":5,\"xmax\":15,\"ymax\":15}") .getGeometry()); - Envelope env2 = (Envelope) (importFromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":10}") + Envelope env2 = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":10}") .getGeometry()); wiggleGeometry(env1, 0.00000001, 1982); wiggleGeometry(env2, 0.00000001, 511); @@ -3664,9 +3792,11 @@ public static void testEnvelopeEnvelope() { } { - Envelope env1 = (Envelope) (importFromJson("{\"xmin\":10,\"ymin\":0,\"xmax\":20,\"ymax\":10}") + Envelope env1 = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":10,\"ymin\":0,\"xmax\":20,\"ymax\":10}") .getGeometry()); - Envelope env2 = (Envelope) (importFromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":10}") + Envelope env2 = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":10}") .getGeometry()); wiggleGeometry(env1, 0.00000001, 1982); wiggleGeometry(env2, 0.00000001, 511); @@ -3682,9 +3812,11 @@ public static void testEnvelopeEnvelope() { } { - Envelope env1 = (Envelope) (importFromJson("{\"xmin\":10,\"ymin\":0,\"xmax\":20,\"ymax\":10}") + Envelope env1 = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":10,\"ymin\":0,\"xmax\":20,\"ymax\":10}") .getGeometry()); - Envelope env2 = (Envelope) (importFromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":10}") + Envelope env2 = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":10}") .getGeometry()); wiggleGeometry(env1, 0.00000001, 1982); wiggleGeometry(env2, 0.00000001, 511); @@ -3700,9 +3832,11 @@ public static void testEnvelopeEnvelope() { } { - Envelope env1 = (Envelope) (importFromJson("{\"xmin\":10,\"ymin\":0,\"xmax\":20,\"ymax\":10}") + Envelope env1 = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":10,\"ymin\":0,\"xmax\":20,\"ymax\":10}") .getGeometry()); - Envelope env2 = (Envelope) (importFromJson("{\"xmin\":0,\"ymin\":10,\"xmax\":10,\"ymax\":20}") + Envelope env2 = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":0,\"ymin\":10,\"xmax\":10,\"ymax\":20}") .getGeometry()); wiggleGeometry(env1, 0.00000001, 1982); wiggleGeometry(env2, 0.00000001, 511); @@ -3718,9 +3852,11 @@ public static void testEnvelopeEnvelope() { } { - Envelope env1 = (Envelope) (importFromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":10}") + Envelope env1 = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":10}") .getGeometry()); - Envelope env2 = (Envelope) (importFromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":0}") + Envelope env2 = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":0}") .getGeometry()); wiggleGeometry(env1, 0.00000001, 1982); wiggleGeometry(env2, 0.00000001, 511); @@ -3736,9 +3872,11 @@ public static void testEnvelopeEnvelope() { } { - Envelope env1 = (Envelope) (importFromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":10}") + Envelope env1 = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":10}") .getGeometry()); - Envelope env2 = (Envelope) (importFromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":15,\"ymax\":0}") + Envelope env2 = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":15,\"ymax\":0}") .getGeometry()); wiggleGeometry(env1, 0.00000001, 1982); wiggleGeometry(env2, 0.00000001, 511); @@ -3754,9 +3892,11 @@ public static void testEnvelopeEnvelope() { } { - Envelope env1 = (Envelope) (importFromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":10}") + Envelope env1 = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":10}") .getGeometry()); - Envelope env2 = (Envelope) (importFromJson("{\"xmin\":-5,\"ymin\":5,\"xmax\":0,\"ymax\":5}") + Envelope env2 = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":-5,\"ymin\":5,\"xmax\":0,\"ymax\":5}") .getGeometry()); wiggleGeometry(env1, 0.00000001, 1982); wiggleGeometry(env2, 0.00000001, 511); @@ -3772,9 +3912,11 @@ public static void testEnvelopeEnvelope() { } { - Envelope env1 = (Envelope) (importFromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":10}") + Envelope env1 = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":10}") .getGeometry()); - Envelope env2 = (Envelope) (importFromJson("{\"xmin\":-5,\"ymin\":5,\"xmax\":5,\"ymax\":5}") + Envelope env2 = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":-5,\"ymin\":5,\"xmax\":5,\"ymax\":5}") .getGeometry()); wiggleGeometry(env1, 0.00000001, 1982); wiggleGeometry(env2, 0.00000001, 511); @@ -3790,9 +3932,11 @@ public static void testEnvelopeEnvelope() { } { - Envelope env1 = (Envelope) (importFromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":10}") + Envelope env1 = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":10}") .getGeometry()); - Envelope env2 = (Envelope) (importFromJson("{\"xmin\":3,\"ymin\":5,\"xmax\":5,\"ymax\":5}") + Envelope env2 = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":3,\"ymin\":5,\"xmax\":5,\"ymax\":5}") .getGeometry()); wiggleGeometry(env1, 0.00000001, 1982); wiggleGeometry(env2, 0.00000001, 511); @@ -3808,9 +3952,11 @@ public static void testEnvelopeEnvelope() { } { - Envelope env1 = (Envelope) (importFromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":10}") + Envelope env1 = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":10}") .getGeometry()); - Envelope env2 = (Envelope) (importFromJson("{\"xmin\":3,\"ymin\":5,\"xmax\":10,\"ymax\":5}") + Envelope env2 = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":3,\"ymin\":5,\"xmax\":10,\"ymax\":5}") .getGeometry()); wiggleGeometry(env1, 0.00000001, 1982); wiggleGeometry(env2, 0.00000001, 511); @@ -3826,9 +3972,11 @@ public static void testEnvelopeEnvelope() { } { - Envelope env1 = (Envelope) (importFromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":10}") + Envelope env1 = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":10}") .getGeometry()); - Envelope env2 = (Envelope) (importFromJson("{\"xmin\":-5,\"ymin\":0,\"xmax\":0,\"ymax\":0}") + Envelope env2 = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":-5,\"ymin\":0,\"xmax\":0,\"ymax\":0}") .getGeometry()); wiggleGeometry(env1, 0.00000001, 1982); wiggleGeometry(env2, 0.00000001, 511); @@ -3844,9 +3992,11 @@ public static void testEnvelopeEnvelope() { } { - Envelope env1 = (Envelope) (importFromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":0}") + Envelope env1 = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":0}") .getGeometry()); - Envelope env2 = (Envelope) (importFromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":0}") + Envelope env2 = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":0}") .getGeometry()); wiggleGeometry(env1, 0.00000001, 1982); wiggleGeometry(env2, 0.00000001, 511); @@ -3862,9 +4012,11 @@ public static void testEnvelopeEnvelope() { } { - Envelope env1 = (Envelope) (importFromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":15,\"ymax\":0}") + Envelope env1 = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":15,\"ymax\":0}") .getGeometry()); - Envelope env2 = (Envelope) (importFromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":0}") + Envelope env2 = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":0}") .getGeometry()); wiggleGeometry(env1, 0.00000001, 1982); wiggleGeometry(env2, 0.00000001, 511); @@ -3880,9 +4032,11 @@ public static void testEnvelopeEnvelope() { } { - Envelope env1 = (Envelope) (importFromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":15,\"ymax\":0}") + Envelope env1 = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":15,\"ymax\":0}") .getGeometry()); - Envelope env2 = (Envelope) (importFromJson("{\"xmin\":-5,\"ymin\":0,\"xmax\":10,\"ymax\":0}") + Envelope env2 = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":-5,\"ymin\":0,\"xmax\":10,\"ymax\":0}") .getGeometry()); wiggleGeometry(env1, 0.00000001, 1982); wiggleGeometry(env2, 0.00000001, 511); @@ -3898,9 +4052,11 @@ public static void testEnvelopeEnvelope() { } { - Envelope env1 = (Envelope) (importFromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":5,\"ymax\":0}") + Envelope env1 = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":5,\"ymax\":0}") .getGeometry()); - Envelope env2 = (Envelope) (importFromJson("{\"xmin\":-5,\"ymin\":0,\"xmax\":0,\"ymax\":0}") + Envelope env2 = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":-5,\"ymin\":0,\"xmax\":0,\"ymax\":0}") .getGeometry()); wiggleGeometry(env1, 0.00000001, 1982); wiggleGeometry(env2, 0.00000001, 511); @@ -3916,9 +4072,11 @@ public static void testEnvelopeEnvelope() { } { - Envelope env1 = (Envelope) (importFromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":0}") + Envelope env1 = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":0}") .getGeometry()); - Envelope env2 = (Envelope) (importFromJson("{\"xmin\":5,\"ymin\":-5,\"xmax\":5,\"ymax\":5}") + Envelope env2 = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":5,\"ymin\":-5,\"xmax\":5,\"ymax\":5}") .getGeometry()); wiggleGeometry(env1, 0.00000001, 1982); wiggleGeometry(env2, 0.00000001, 511); @@ -3934,9 +4092,11 @@ public static void testEnvelopeEnvelope() { } { - Envelope env1 = (Envelope) (importFromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":0}") + Envelope env1 = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":0}") .getGeometry()); - Envelope env2 = (Envelope) (importFromJson("{\"xmin\":10,\"ymin\":0,\"xmax\":20,\"ymax\":0}") + Envelope env2 = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":10,\"ymin\":0,\"xmax\":20,\"ymax\":0}") .getGeometry()); wiggleGeometry(env1, 0.00000001, 1982); wiggleGeometry(env2, 0.00000001, 511); @@ -3952,9 +4112,11 @@ public static void testEnvelopeEnvelope() { } { - Envelope env1 = (Envelope) (importFromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":0}") + Envelope env1 = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":0}") .getGeometry()); - Envelope env2 = (Envelope) (importFromJson("{\"xmin\":5,\"ymin\":0,\"xmax\":5,\"ymax\":5}") + Envelope env2 = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":5,\"ymin\":0,\"xmax\":5,\"ymax\":5}") .getGeometry()); wiggleGeometry(env1, 0.00000001, 1982); wiggleGeometry(env2, 0.00000001, 511); @@ -3971,9 +4133,11 @@ public static void testEnvelopeEnvelope() { } { - Envelope env1 = (Envelope) (importFromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":0,\"ymax\":0}") + Envelope env1 = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":0,\"ymax\":0}") .getGeometry()); - Envelope env2 = (Envelope) (importFromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":10}") + Envelope env2 = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":10}") .getGeometry()); wiggleGeometry(env1, 0.00000001, 1982); wiggleGeometry(env2, 0.00000001, 511); @@ -3990,9 +4154,11 @@ public static void testEnvelopeEnvelope() { } { - Envelope env1 = (Envelope) (importFromJson("{\"xmin\":5,\"ymin\":0,\"xmax\":5,\"ymax\":0}") + Envelope env1 = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":5,\"ymin\":0,\"xmax\":5,\"ymax\":0}") .getGeometry()); - Envelope env2 = (Envelope) (importFromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":10}") + Envelope env2 = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":10}") .getGeometry()); wiggleGeometry(env1, 0.00000001, 1982); wiggleGeometry(env2, 0.00000001, 511); @@ -4009,9 +4175,11 @@ public static void testEnvelopeEnvelope() { } { - Envelope env1 = (Envelope) (importFromJson("{\"xmin\":5,\"ymin\":5,\"xmax\":5,\"ymax\":5}") + Envelope env1 = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":5,\"ymin\":5,\"xmax\":5,\"ymax\":5}") .getGeometry()); - Envelope env2 = (Envelope) (importFromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":10}") + Envelope env2 = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":10}") .getGeometry()); wiggleGeometry(env1, 0.00000001, 1982); wiggleGeometry(env2, 0.00000001, 511); @@ -4028,9 +4196,11 @@ public static void testEnvelopeEnvelope() { } { - Envelope env1 = (Envelope) (importFromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":0,\"ymax\":0}") + Envelope env1 = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":0,\"ymax\":0}") .getGeometry()); - Envelope env2 = (Envelope) (importFromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":0}") + Envelope env2 = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":0}") .getGeometry()); wiggleGeometry(env1, 0.00000001, 1982); wiggleGeometry(env2, 0.00000001, 511); @@ -4047,9 +4217,11 @@ public static void testEnvelopeEnvelope() { } { - Envelope env1 = (Envelope) (importFromJson("{\"xmin\":5,\"ymin\":0,\"xmax\":5,\"ymax\":0}") + Envelope env1 = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":5,\"ymin\":0,\"xmax\":5,\"ymax\":0}") .getGeometry()); - Envelope env2 = (Envelope) (importFromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":0}") + Envelope env2 = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":0}") .getGeometry()); wiggleGeometry(env1, 0.00000001, 1982); wiggleGeometry(env2, 0.00000001, 511); @@ -4066,9 +4238,11 @@ public static void testEnvelopeEnvelope() { } { - Envelope env1 = (Envelope) (importFromJson("{\"xmin\":5,\"ymin\":0,\"xmax\":5,\"ymax\":0}") + Envelope env1 = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":5,\"ymin\":0,\"xmax\":5,\"ymax\":0}") .getGeometry()); - Envelope env2 = (Envelope) (importFromJson("{\"xmin\":5,\"ymin\":0,\"xmax\":5,\"ymax\":0}") + Envelope env2 = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":5,\"ymin\":0,\"xmax\":5,\"ymax\":0}") .getGeometry()); wiggleGeometry(env1, 0.00000001, 1982); wiggleGeometry(env2, 0.00000001, 511); @@ -4349,6 +4523,43 @@ public static void testPolylinePolylineRelate() { scl = "***T*****"; res = op.execute(polyline1, polyline2, sr, scl, null); assertTrue(res); + + polyline1.setEmpty(); + polyline2.setEmpty(); + + polyline1.startPath(0, 0); + polyline1.lineTo(0, 20); + polyline1.lineTo(20, 20); + polyline1.lineTo(20, 0); + polyline1.lineTo(0, 0); // has no boundary + + polyline2.startPath(3, 3); + polyline2.lineTo(5, 5); + + op.accelerateGeometry(polyline1, sr, + Geometry.GeometryAccelerationDegree.enumHot); + + scl = "FF1FFF102"; + res = op.execute(polyline1, polyline2, sr, scl, null); + assertTrue(res); + + polyline1.setEmpty(); + polyline2.setEmpty(); + + polyline1.startPath(4, 0); + polyline1.lineTo(0, 4); + polyline1.lineTo(4, 8); + polyline1.lineTo(8, 4); + + polyline2.startPath(8, 1); + polyline2.lineTo(8, 2); + + op.accelerateGeometry(polyline1, sr, + Geometry.GeometryAccelerationDegree.enumHot); + + scl = "FF1FF0102"; + res = op.execute(polyline1, polyline2, sr, scl, null); + assertTrue(res); } @Test @@ -4429,6 +4640,30 @@ public static void testPolygonPolylineRelate() { scl = "1T*0F*T0T"; res = op.execute(polygon1, polyline2, sr, scl, null); assertTrue(res); + + polygon1.setEmpty(); + polyline2.setEmpty(); + + polygon1.startPath(2, 0); + polygon1.lineTo(0, 2); + polygon1.lineTo(2, 4); + polygon1.lineTo(4, 2); + + polyline2.startPath(1, 2); + polyline2.lineTo(3, 2); + + op.accelerateGeometry(polygon1, sr, + Geometry.GeometryAccelerationDegree.enumHot); + scl = "TTTFF****"; + res = op.execute(polygon1, polyline2, sr, scl, null); + assertTrue(res); + + polyline2.setEmpty(); + polyline2.startPath(5, 2); + polyline2.lineTo(7, 2); + scl = "FF2FFT***"; + res = op.execute(polygon1, polyline2, sr, scl, null); + assertTrue(res); } @Test @@ -4525,6 +4760,11 @@ public static void testPointPointRelate() { scl = "T********"; res = op.execute(p1, p2, sr, scl, null); assertTrue(res); + + p1.setXY(0, 0); + p2.setXY(1, 0); + res = op.execute(p1, p2, null, scl, null); + assertTrue(!res); } @Test @@ -4569,6 +4809,42 @@ public static void testPolygonMultiPointRelate() { scl = "TFF0F10FT"; res = op.execute(polygon1, multipoint2, sr, scl, null); assertTrue(!res); + + polygon1.setEmpty(); + multipoint2.setEmpty(); + + polygon1.startPath(0, 0); + polygon1.lineTo(0, 20); + polygon1.lineTo(20, 20); + polygon1.lineTo(20, 0); + + multipoint2.add(3, 3); + multipoint2.add(5, 5); + + op.accelerateGeometry(polygon1, sr, + Geometry.GeometryAccelerationDegree.enumHot); + + scl = "TF2FF****"; + res = op.execute(polygon1, multipoint2, sr, scl, null); + assertTrue(res); + + polygon1.setEmpty(); + multipoint2.setEmpty(); + + polygon1.startPath(4, 0); + polygon1.lineTo(0, 4); + polygon1.lineTo(4, 8); + polygon1.lineTo(8, 4); + + multipoint2.add(8, 1); + multipoint2.add(8, 2); + + op.accelerateGeometry(polygon1, sr, + Geometry.GeometryAccelerationDegree.enumHot); + + scl = "FF2FF10F2"; + res = op.execute(polygon1, multipoint2, sr, scl, null); + assertTrue(res); } @Test @@ -4677,24 +4953,17 @@ public static void testPolylinePointRelate() { point.setXY(0, 3); - scl = "0F1FF0FF2'"; - res = op.execute(polyline, point, sr, scl, null); - assertTrue(res); } - static MapGeometry importFromJson(String jsonString) { - JsonFactory factory = new JsonFactory(); - try { - JsonParser jsonParser = factory.createJsonParser(jsonString); - jsonParser.nextToken(); - OperatorImportFromJson importer = (OperatorImportFromJson) OperatorFactoryLocal - .getInstance().getOperator( - Operator.Type.ImportFromJson); - - return importer.execute(Geometry.Type.Unknown, jsonParser); - } catch (Exception ex) { - } - - return null; + @Test + public static void testCrosses_github_issue_40() { + // Issue 40: Acceleration without a spatial reference changes the result of relation operators + Geometry geom1 = OperatorImportFromWkt.local().execute(0, Geometry.Type.Unknown, "LINESTRING (2 0, 2 3)", null); + Geometry geom2 = OperatorImportFromWkt.local().execute(0, Geometry.Type.Unknown, "POLYGON ((1 1, 4 1, 4 4, 1 4, 1 1))", null); + boolean answer1 = OperatorCrosses.local().execute(geom1, geom2, null, null); + assertTrue(answer1); + OperatorCrosses.local().accelerateGeometry(geom1, null, GeometryAccelerationDegree.enumHot); + boolean answer2 = OperatorCrosses.local().execute(geom1, geom2, null, null); + assertTrue(answer2); } } diff --git a/src/test/java/com/esri/core/geometry/TestShapePreserving.java b/src/test/java/com/esri/core/geometry/TestShapePreserving.java deleted file mode 100644 index 0d5524e5..00000000 --- a/src/test/java/com/esri/core/geometry/TestShapePreserving.java +++ /dev/null @@ -1,151 +0,0 @@ -package com.esri.core.geometry; - -import junit.framework.TestCase; -import org.junit.Test; - -public class TestShapePreserving extends TestCase { - @Override - protected void setUp() throws Exception { - super.setUp(); - } - - @Override - protected void tearDown() throws Exception { - super.tearDown(); - } - - @Test - public static void testBoxAreasWithinToleranceCR186615() { - /* - * //TODO: Implement these OperatorShapePreservingArea shapeAreaOp = - * OperatorShapePreservingArea.local(); OperatorShapePreservingLength - * shapeLengthOp = OperatorShapePreservingLength.local(); - * - * Polyline polyline2 = new Polyline(); polyline2.startPath(190, 0); - * polyline2.lineTo(200,0); SpatialReference spatialRefWGS = - * SpatialReference.create(4326); double lengthEquator10Degree = - * shapeLengthOp.execute(polyline2, spatialRefWGS, null); - * assertTrue(lengthEquator10Degree != 0.00); - * - * Polyline polylineEquator2 = new Polyline(); - * polylineEquator2.startPath(170, 0); polylineEquator2.lineTo(180,0); - * double lengthEquator10Degree2 = - * shapeLengthOp.execute(polylineEquator2, spatialRefWGS, null); - * assertTrue(GeomCommonMethods.compareDouble(lengthEquator10Degree2, - * lengthEquator10Degree, Math.pow(10.0,-10))); - * - * SpatialReference spatialRefWGSMerc = SpatialReference.create(102100); - * double PCS5 = 111319.49079327358 * 5; double PCS180 = - * 20037508.342789244; double CSYMax = 30240970.0; double CSYMin = - * -30240970.0; - * - * Polyline polylineEquator3 = new Polyline(); - * polylineEquator3.startPath(-PCS180 - 4*PCS5, 0); - * polylineEquator3.lineTo(-PCS180 - 2*PCS5, 0); double - * lengthEquatorMercDegree = shapeLengthOp.execute(*polylineEquator3, - * spatialRefWGSMerc, null); - * assertTrue(GeomCommonMethods.compareDouble(lengthEquatorMercDegree, - * lengthEquator10Degree, Math.pow(10.0,-10))); - * - * Polyline polylineBox = new Polyline(); polylineBox.startPath(PCS180 - - * 2*PCS5, 30240970.0 / 9); polylineBox.lineTo(PCS180 + 2*PCS5, - * 30240970.0 / 9); polylineBox.lineTo(PCS180 + 2*PCS5, -30240970.0 / - * 9); polylineBox.lineTo(PCS180 - 2*PCS5, -30240970.0 / 9); - * polylineBox.lineTo(PCS180 - 2*PCS5, 30240970.0 / 9); - * - * Polygon polygonBox = new Polygon(); polygonBox.startPath(PCS180 - - * 2*PCS5, 30240970.0 / 9); polygonBox.lineTo(PCS180 + 2*PCS5, - * 30240970.0 / 9); polygonBox.lineTo(PCS180 + 2*PCS5, -30240970.0 / 9); - * polygonBox.lineTo(PCS180 - 2*PCS5, -30240970.0 / 9); - * polygonBox.lineTo(PCS180 - 2*PCS5, 30240970.0 / 9); - * - * Envelope envelopeBox = new Envelope(); - * polygonBox.queryEnvelope(envelopeBox); - * - * double lengthBox1 = shapeLengthOp.execute(polylineBox, - * spatialRefWGSMerc, null); double lengthBox2 = - * shapeLengthOp.execute(polygonBox, spatialRefWGSMerc, null); double - * lengthBox3 = shapeLengthOp.execute(envelopeBox, spatialRefWGSMerc, - * null); assertTrue(GeomCommonMethods.compareDouble(lengthBox1, - * lengthBox2, Math.pow(10.0,-10))); - * assertTrue(GeomCommonMethods.compareDouble(lengthBox1, lengthBox3, - * Math.pow(10.0,-10))); - * - * // Repeated polygon area Polygon polygonBox1 = new Polygon(); - * polygonBox1.startPath(-PCS180 - 6 * PCS5, 30240970.0 / 9); - * polygonBox1.lineTo(-PCS180 - 4 * PCS5, 30240970.0 / 9); - * polygonBox1.lineTo(-PCS180 - 4 * PCS5, -30240970.0 / 9); - * polygonBox1.lineTo(-PCS180 - 6 * PCS5, -30240970.0 / 9); - * - * Polygon polygonBox2 = new Polygon(); polygonBox2.startPath(-PCS180 - - * 2 * PCS5, 30240970.0 / 9); polygonBox2.lineTo(-PCS180, 30240970.0 / - * 9); polygonBox2.lineTo(-PCS180, -30240970.0 / 9); - * polygonBox2.lineTo(-PCS180 - 2 * PCS5, -30240970.0 / 9); - * - * Polygon polygonBox3 = new Polygon(); polygonBox3.startPath(-PCS180 - - * PCS5, 30240970.0 / 9); polygonBox3.lineTo(-PCS180 + PCS5, 30240970.0 - * / 9); polygonBox3.lineTo(-PCS180 + PCS5, -30240970.0 / 9); - * polygonBox3.lineTo(-PCS180 - PCS5, -30240970.0 / 9); - * - * Polygon polygonBox4 = new Polygon(); polygonBox4.startPath(-PCS180, - * 30240970.0 / 9); polygonBox4.lineTo(-PCS180 + 2 * PCS5, 30240970.0 / - * 9); polygonBox4.lineTo(-PCS180 + 2 * PCS5, -30240970.0 / 9); - * polygonBox4.lineTo(-PCS180, -30240970.0 / 9); - * - * Polygon polygonBox5 = new Polygon(); polygonBox5.startPath(PCS180 - 6 - * * PCS5, 30240970.0 / 9); polygonBox5.lineTo(PCS180 - 4 * PCS5, - * 30240970.0 / 9); polygonBox5.lineTo(PCS180 - 4 * PCS5, -30240970.0 / - * 9); polygonBox5.lineTo(PCS180 - 6 * PCS5, -30240970.0 / 9); - * - * Polygon polygonBox6 = new Polygon(); polygonBox6.startPath(PCS180 - 2 - * * PCS5, 30240970.0 / 9); polygonBox6.lineTo(PCS180, 30240970.0 / 9); - * polygonBox6.lineTo(PCS180, -30240970.0 / 9); - * polygonBox6.lineTo(PCS180 - 2 * PCS5, -30240970.0 / 9); - * - * Polygon polygonBox7 = new Polygon(); polygonBox7.startPath(PCS180 - - * PCS5, 30240970.0 / 9); polygonBox7.lineTo(PCS180 + PCS5, 30240970.0 / - * 9); polygonBox7.lineTo(PCS180 + PCS5, -30240970.0 / 9); - * polygonBox7.lineTo(PCS180 - PCS5, -30240970.0 / 9); - * - * Polygon polygonBox8 = new Polygon(); polygonBox8.startPath(PCS180, - * 30240970.0 / 9); polygonBox8.lineTo(PCS180 + 2 * PCS5, 30240970.0 / - * 9); polygonBox8.lineTo(PCS180 + 2 * PCS5, -30240970.0 / 9); - * polygonBox8.lineTo(PCS180, -30240970.0 / 9); - * - * Polygon polygonBox9 = new Polygon(); polygonBox9.startPath(PCS180 + 2 - * * PCS5, 30240970.0 / 9); polygonBox9.lineTo(PCS180 + 4 * PCS5, - * 30240970.0 / 9); polygonBox9.lineTo(PCS180 + 4 * PCS5, -30240970.0 / - * 9); polygonBox9.lineTo(PCS180 + 2 * PCS5, -30240970.0 / 9); - * - * double area1 = shapeAreaOp.execute(polygonBox1, spatialRefWGSMerc, - * null); double area2 = shapeAreaOp.execute(polygonBox2, - * spatialRefWGSMerc, null); double area3 = - * shapeAreaOp.execute(polygonBox3, spatialRefWGSMerc, null); double - * area4 = shapeAreaOp.execute(polygonBox4, spatialRefWGSMerc, null); - * double area5 = shapeAreaOp.execute(polygonBox5, spatialRefWGSMerc, - * null); double area6 = shapeAreaOp.execute(polygonBox6, - * spatialRefWGSMerc, null); double area7 = - * shapeAreaOp.execute(polygonBox7, spatialRefWGSMerc, null); double - * area8 = shapeAreaOp.execute(polygonBox8, spatialRefWGSMerc, null); - * double area9 = shapeAreaOp.execute(polygonBox9, spatialRefWGSMerc, - * null); - * - * assertTrue(GeomCommonMethods.compareDouble(area1, area2, - * Math.pow(10.0,-10))); - * assertTrue(GeomCommonMethods.compareDouble(area1, area3, - * Math.pow(10.0,-10))); - * assertTrue(GeomCommonMethods.compareDouble(area1, area4, - * Math.pow(10.0,-10))); - * assertTrue(GeomCommonMethods.compareDouble(area1, area5, - * Math.pow(10.0,-10))); - * assertTrue(GeomCommonMethods.compareDouble(area1, area6, - * Math.pow(10.0,-10))); - * assertTrue(GeomCommonMethods.compareDouble(area1, area7, - * Math.pow(10.0,-10))); - * assertTrue(GeomCommonMethods.compareDouble(area1, area8, - * Math.pow(10.0,-10))); - * assertTrue(GeomCommonMethods.compareDouble(area1, area9, - * Math.pow(10.0,-10))); - */ - } -} diff --git a/src/test/java/com/esri/core/geometry/TestSimplify.java b/src/test/java/com/esri/core/geometry/TestSimplify.java index 8d76da1b..b7caf8a3 100644 --- a/src/test/java/com/esri/core/geometry/TestSimplify.java +++ b/src/test/java/com/esri/core/geometry/TestSimplify.java @@ -40,6 +40,10 @@ protected void tearDown() throws Exception { } public Polygon makeNonSimplePolygon2() { + //MapGeometry mg = OperatorFactoryLocal.loadGeometryFromJSONFileDbg("c:/temp/simplify_polygon_gnomonic.txt"); + //Geometry res = OperatorSimplify.local().execute(mg.getGeometry(), mg.getSpatialReference(), true, null); + + Polygon poly = new Polygon(); poly.startPath(0, 0); poly.lineTo(0, 15); diff --git a/src/test/java/com/esri/core/geometry/TestUnion.java b/src/test/java/com/esri/core/geometry/TestUnion.java index 94f3ef86..ad06dbbc 100644 --- a/src/test/java/com/esri/core/geometry/TestUnion.java +++ b/src/test/java/com/esri/core/geometry/TestUnion.java @@ -17,7 +17,6 @@ protected void tearDown() throws Exception { @Test public static void testUnion() { Point pt = new Point(10, 20); - System.out.println(pt.getX()); Point pt2 = new Point(); pt2.setXY(10, 10); @@ -34,6 +33,5 @@ public static void testUnion() { GeometryCursor outputCursor = union.execute(inputGeometries, sr, null); Geometry result = outputCursor.next(); - System.out.println(result); } } diff --git a/src/test/java/com/esri/core/geometry/TestWKBSupport.java b/src/test/java/com/esri/core/geometry/TestWKBSupport.java index 656765d8..196e5e1e 100644 --- a/src/test/java/com/esri/core/geometry/TestWKBSupport.java +++ b/src/test/java/com/esri/core/geometry/TestWKBSupport.java @@ -48,8 +48,6 @@ public void testWKB() { // geom = operatorImport.execute(0, Geometry.Type.Polygon, // byteBuffer); String outputPolygon1 = GeometryEngine.geometryToJson(-1, geom); - System.out.println(strPolygon1); - System.out.println(outputPolygon1); } catch (JsonParseException ex) { } catch (IOException ex) { } diff --git a/src/test/java/com/esri/core/geometry/Utils.java b/src/test/java/com/esri/core/geometry/Utils.java index a984cfc5..9bbe51c0 100644 --- a/src/test/java/com/esri/core/geometry/Utils.java +++ b/src/test/java/com/esri/core/geometry/Utils.java @@ -2,6 +2,8 @@ public class Utils { static void showProjectedGeometryInfo(MapGeometry mapGeom) { + return; + /* System.out.println("\n"); MapGeometry geom = mapGeom; int wkid = geom.getSpatialReference() != null ? geom @@ -66,7 +68,7 @@ static void showProjectedGeometryInfo(MapGeometry mapGeom) { } System.out.println("wkid: " + wkid); - } + }*/ } From c171c80a46377b523fe6002eb550f438d8757bdc Mon Sep 17 00:00:00 2001 From: serg4066 Date: Thu, 24 Jul 2014 12:44:26 -0700 Subject: [PATCH 087/196] don't use Integer.compare for backward compatibility --- src/main/java/com/esri/core/geometry/Simplificator.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/esri/core/geometry/Simplificator.java b/src/main/java/com/esri/core/geometry/Simplificator.java index 5bfaf014..95cf4f6c 100644 --- a/src/main/java/com/esri/core/geometry/Simplificator.java +++ b/src/main/java/com/esri/core/geometry/Simplificator.java @@ -1002,8 +1002,9 @@ int _compareVerticesSimple(int v1, int v2) { m_shape.getXY(v2, pt2); int res = pt1.compare(pt2); if (res == 0) {// sort equal vertices by the path ID - res = Integer.compare(m_shape.getPathFromVertex(v1), - m_shape.getPathFromVertex(v2)); + int i1 = m_shape.getPathFromVertex(v1); + int i2 = m_shape.getPathFromVertex(v2); + res = i1 < i2 ? -1 : (i1 == i2 ? 0 : 1); } return res; From f264a50ab2c409fd5edf9135131bcf887f5822ba Mon Sep 17 00:00:00 2001 From: serg4066 Date: Thu, 21 Aug 2014 10:13:46 -0700 Subject: [PATCH 088/196] additional fixes in Simplify and Cut --- build.xml | 2 +- .../java/com/esri/core/geometry/Cracker.java | 13 - .../com/esri/core/geometry/EditShape.java | 12 +- .../geometry/OperatorIntersectionCursor.java | 7 +- .../core/geometry/RelationalOperations.java | 40 +- .../com/esri/core/geometry/Simplificator.java | 105 ++- .../geometry/StridedIndexTypeCollection.java | 14 +- .../com/esri/core/geometry/TopoGraph.java | 104 +-- .../com/esri/core/geometry/TestRelation.java | 613 +++++++++--------- 9 files changed, 462 insertions(+), 448 deletions(-) diff --git a/build.xml b/build.xml index 633710cf..14f0f909 100644 --- a/build.xml +++ b/build.xml @@ -57,7 +57,7 @@ - + diff --git a/src/main/java/com/esri/core/geometry/Cracker.java b/src/main/java/com/esri/core/geometry/Cracker.java index 6f320b04..98b4a909 100644 --- a/src/main/java/com/esri/core/geometry/Cracker.java +++ b/src/main/java/com/esri/core/geometry/Cracker.java @@ -23,7 +23,6 @@ */ package com.esri.core.geometry; -import java.util.Arrays; /** * Implementation for the segment cracking. @@ -33,11 +32,9 @@ */ class Cracker { private EditShape m_shape; - private Envelope2D m_extent; private ProgressTracker m_progress_tracker; private NonSimpleResult m_non_simple_result; private double m_tolerance; - private double m_tolerance_cluster; private Treap m_sweep_structure; private SweepComparator m_sweep_comparator; private boolean m_bAllowCoincident; @@ -51,7 +48,6 @@ boolean crackBruteForce_() { seg_1_env.setEmpty(); Envelope2D seg_2_env = new Envelope2D(); seg_2_env.setEmpty(); - boolean b_needs_filter = false; boolean assume_intersecting = false; Point helper_point = new Point(); SegmentIntersector segment_intersector = new SegmentIntersector(); @@ -80,7 +76,6 @@ boolean crackBruteForce_() { if (seg_1.isDegenerate(m_tolerance))// do not crack with // degenerate segments { - b_needs_filter = true; continue; } } @@ -108,7 +103,6 @@ boolean crackBruteForce_() { if (seg_2.isDegenerate(m_tolerance))// do not crack with // degenerate segments { - b_needs_filter = true; continue; } } @@ -192,7 +186,6 @@ boolean crackBruteForce_() { // degenerate // segments { - b_needs_filter = true; break; } } @@ -442,8 +435,6 @@ static boolean execute(EditShape shape, Envelope2D extent, Cracker cracker = new Cracker(progress_tracker); cracker.m_shape = shape; cracker.m_tolerance = tolerance; - cracker.m_tolerance_cluster = -1; - cracker.m_extent = extent; // Use brute force for smaller shapes, and a planesweep for bigger // shapes. boolean b_cracked = false; @@ -469,11 +460,9 @@ static boolean needsCracking(boolean allowCoincident, EditShape shape, double to if (!canBeCracked(shape)) return false; - Envelope2D shape_env = shape.getEnvelope2D(); Cracker cracker = new Cracker(progress_tracker); cracker.m_shape = shape; cracker.m_tolerance = tolerance; - cracker.m_extent = shape_env; cracker.m_bAllowCoincident = allowCoincident; if (cracker.needsCrackingImpl_()) { if (result != null) @@ -485,12 +474,10 @@ static boolean needsCracking(boolean allowCoincident, EditShape shape, double to Transformation2D transform = new Transformation2D(); transform.setSwapCoordinates(); shape.applyTransformation(transform); - transform.transform(shape_env); cracker = new Cracker(progress_tracker); cracker.m_shape = shape; cracker.m_tolerance = tolerance; - cracker.m_extent = shape_env; cracker.m_bAllowCoincident = allowCoincident; boolean b_res = cracker.needsCrackingImpl_(); diff --git a/src/main/java/com/esri/core/geometry/EditShape.java b/src/main/java/com/esri/core/geometry/EditShape.java index 23f70df5..84557893 100644 --- a/src/main/java/com/esri/core/geometry/EditShape.java +++ b/src/main/java/com/esri/core/geometry/EditShape.java @@ -1475,11 +1475,12 @@ int insertClosedPath_(int geometry, int before_path, int first_vertex, int check int path = insertPath(geometry, -1); int path_size = 0; int vertex = first_vertex; - contains_checked_vertex[0] = false; + boolean contains = false; + while(true) { if (vertex == checked_vertex) - contains_checked_vertex[0] = true; + contains = true; setPathToVertex_(vertex, path); path_size++; @@ -1493,12 +1494,17 @@ int insertClosedPath_(int geometry, int before_path, int first_vertex, int check setClosedPath(path, true); setPathSize_(path, path_size); - if (contains_checked_vertex[0]) + if (contains) first_vertex = checked_vertex; setFirstVertex_(path, first_vertex); setLastVertex_(path, getPrevVertex(first_vertex)); setRingAreaValid_(path, false); + + if (contains_checked_vertex != null) { + contains_checked_vertex[0] = contains; + } + return path; } diff --git a/src/main/java/com/esri/core/geometry/OperatorIntersectionCursor.java b/src/main/java/com/esri/core/geometry/OperatorIntersectionCursor.java index e06670dc..7706d83e 100644 --- a/src/main/java/com/esri/core/geometry/OperatorIntersectionCursor.java +++ b/src/main/java/com/esri/core/geometry/OperatorIntersectionCursor.java @@ -606,8 +606,7 @@ Geometry tryFastIntersectPolylinePolygon_(Polyline polyline, Polygon polygon) { resSeg.getStartXY(), tolerance) != 1) { if (analyseClipSegment_(polygon, resSeg, tolerance) != 1) { - assert (false);// something went wrong. - return null; + return null; //someting went wrong we'll falback to slower but robust planesweep code. } } @@ -667,9 +666,7 @@ Geometry tryFastIntersectPolylinePolygon_(Polyline polyline, Polygon polygon) { // be // inside. if (clipStatus < 0) { - assert (clipStatus >= 0);// E-mail the repro case to - // the Geometry team to - // investigate. + assert (clipStatus >= 0); return null;// something goes wrong, resort to // planesweep } diff --git a/src/main/java/com/esri/core/geometry/RelationalOperations.java b/src/main/java/com/esri/core/geometry/RelationalOperations.java index e88409ff..08d60ca3 100644 --- a/src/main/java/com/esri/core/geometry/RelationalOperations.java +++ b/src/main/java/com/esri/core/geometry/RelationalOperations.java @@ -894,7 +894,15 @@ private static boolean polygonEqualsPolygon_(Polygon polygon_a, progress_tracker)) return true; - return linearPathEqualsLinearPath_(polygon_a, polygon_b, tolerance); + double length_a = polygon_a.calculateLength2D(); + double length_b = polygon_b.calculateLength2D(); + int max_vertices = Math.max(polygon_a.getPointCount(), + polygon_b.getPointCount()); + + if (Math.abs(length_a - length_b) > max_vertices * 4.0 * tolerance) + return false; + + return linearPathEqualsLinearPath_(polygon_a, polygon_b, tolerance, true); } // Returns true if polygon_a is disjoint from polygon_b. @@ -1264,7 +1272,7 @@ private static boolean polygonEqualsEnvelope_(Polygon polygon_a, Polygon polygon_b = new Polygon(); polygon_b.addEnvelope(envelope_b, false); - return linearPathEqualsLinearPath_(polygon_a, polygon_b, tolerance); + return linearPathEqualsLinearPath_(polygon_a, polygon_b, tolerance, true); } // Returns true if polygon_a is disjoint from envelope_b. @@ -1520,7 +1528,7 @@ private static boolean polylineEqualsPolyline_(Polyline polyline_a, progress_tracker)) return true; - return linearPathEqualsLinearPath_(polyline_a, polyline_b, tolerance); + return linearPathEqualsLinearPath_(polyline_a, polyline_b, tolerance, false); } // Returns true if polyline_a is disjoint from polyline_b. @@ -1633,7 +1641,7 @@ private static boolean polylineContainsPolyline_(Polyline polyline_a, false) == Relation.disjoint) return false; - return linearPathWithinLinearPath_(polyline_b, polyline_a, tolerance); + return linearPathWithinLinearPath_(polyline_b, polyline_a, tolerance, false); } // Returns true if polyline_a is disjoint from point_b. @@ -2127,7 +2135,7 @@ private static boolean polylineContainsEnvelope_(Polyline polyline_a, polyline_b.startPath(p); envelope_b.queryCornerByVal(2, p); polyline_b.lineTo(p); - return linearPathWithinLinearPath_(polyline_b, polyline_a, tolerance); + return linearPathWithinLinearPath_(polyline_b, polyline_a, tolerance, false); } // Returns true if polyline_a crosses envelope_b. @@ -3405,16 +3413,16 @@ private static boolean multiPointIntersectsMultiPoint_( // Returns true if multipathA equals multipathB. private static boolean linearPathEqualsLinearPath_(MultiPath multipathA, - MultiPath multipathB, double tolerance) { - return linearPathWithinLinearPath_(multipathA, multipathB, tolerance) + MultiPath multipathB, double tolerance, boolean bEnforceOrientation) { + return linearPathWithinLinearPath_(multipathA, multipathB, tolerance, bEnforceOrientation) && linearPathWithinLinearPath_(multipathB, multipathA, - tolerance); + tolerance, bEnforceOrientation); } // Returns true if the segments of multipathA are within the segments of // multipathB. private static boolean linearPathWithinLinearPath_(MultiPath multipathA, - MultiPath multipathB, double tolerance) { + MultiPath multipathB, double tolerance, boolean bEnforceOrientation) { boolean bWithin = true; double[] scalarsA = new double[2]; double[] scalarsB = new double[2]; @@ -3485,7 +3493,7 @@ private static boolean linearPathWithinLinearPath_(MultiPath multipathA, int result = segmentA.intersect(segmentB, null, scalarsA, scalarsB, tolerance); - if (result == 2) { + if (result == 2 && (!bEnforceOrientation || scalarsB[0] <= scalarsB[1])) { double scalar_a_0 = scalarsA[0]; double scalar_a_1 = scalarsA[1]; double scalar_b_0 = scalarsB[0]; @@ -3515,7 +3523,7 @@ private static boolean linearPathWithinLinearPath_(MultiPath multipathA, result = segmentA.intersect(segmentB, null, scalarsA, scalarsB, tolerance); - if (result == 2) { + if (result == 2 && (!bEnforceOrientation || scalarsB[0] <= scalarsB[1])) { scalar_a_0 = scalarsA[0]; scalar_a_1 = scalarsA[1]; @@ -3533,7 +3541,7 @@ private static boolean linearPathWithinLinearPath_(MultiPath multipathA, null, scalarsA, scalarsB, tolerance); - if (result == 2) { + if (result == 2 && (!bEnforceOrientation || scalarsB[0] <= scalarsB[1])) { scalar_a_0 = scalarsA[0]; scalar_a_1 = scalarsA[1]; @@ -3642,15 +3650,15 @@ private static boolean linearPathOverlapsLinearPath_(MultiPath multipathA, if (bIntAExtB && !bIntBExtA) return !linearPathWithinLinearPath_(multipathB, multipathA, - tolerance); + tolerance, false); if (bIntBExtA && !bIntAExtB) return !linearPathWithinLinearPath_(multipathA, multipathB, - tolerance); + tolerance, false); - return !linearPathWithinLinearPath_(multipathA, multipathB, tolerance) + return !linearPathWithinLinearPath_(multipathA, multipathB, tolerance, false) && !linearPathWithinLinearPath_(multipathB, multipathA, - tolerance); + tolerance, false); } // Returns true the dimension of intersection of _multipathA and diff --git a/src/main/java/com/esri/core/geometry/Simplificator.java b/src/main/java/com/esri/core/geometry/Simplificator.java index 95cf4f6c..955fea71 100644 --- a/src/main/java/com/esri/core/geometry/Simplificator.java +++ b/src/main/java/com/esri/core/geometry/Simplificator.java @@ -46,7 +46,7 @@ class Simplificator { private void _beforeRemoveVertex(int vertex, boolean bChangePathFirst) { int vertexlistIndex = m_shape.getUserIndex(vertex, m_userIndexSortedIndexToVertex); - // _ASSERT(m_sortedVertices.getData(vertexlistIndex) != 0xdeadbeef); + if (m_nextVertexToProcess == vertexlistIndex) { m_nextVertexToProcess = m_sortedVertices .getNext(m_nextVertexToProcess); @@ -65,12 +65,26 @@ private void _beforeRemoveVertex(int vertex, boolean bChangePathFirst) { int first = m_shape.getFirstVertex(path); if (first == vertex) { int next = m_shape.getNextVertex(vertex); - if (next != vertex) - m_shape.setFirstVertex_(path, next); - else { - m_shape.setFirstVertex_(path, -1); - m_shape.setLastVertex_(path, -1); + if (next != vertex) { + int p = m_shape.getPathFromVertex(next); + if (p == path) { + m_shape.setFirstVertex_(path, next); + return; + } + else { + int prev = m_shape.getPrevVertex(vertex); + if (prev != vertex) { + p = m_shape.getPathFromVertex(prev); + if (p == path) { + m_shape.setFirstVertex_(path, prev); + return; + } + } + } } + + m_shape.setFirstVertex_(path, -1); + m_shape.setLastVertex_(path, -1); } } } @@ -709,19 +723,17 @@ private void _resolveOverlapOddEven(boolean bDirection1, m_shape.removeVertexInternal_(vertexA2, true); _removeAngleSortInfo(vertexA1); _transferVertexData(vertexB2, vertexB1); - _beforeRemoveVertex(vertexB2, false); + _beforeRemoveVertex(vertexB2, true); m_shape.removeVertexInternal_(vertexB2, false); _removeAngleSortInfo(vertexB1); } else { - // _ASSERT(m_shape.getNextVertex(vertexB1) == vertexA1); - // _ASSERT(m_shape.getNextVertex(vertexA2) == vertexB2); m_shape.setNextVertex_(vertexA2, vertexA1); // B1 B2< m_shape.setPrevVertex_(vertexA1, vertexA2); // | | m_shape.setNextVertex_(vertexB1, vertexB2); // | | m_shape.setPrevVertex_(vertexB2, vertexB1); // A1< A2 _transferVertexData(vertexA2, vertexA1); - _beforeRemoveVertex(vertexA2, false); + _beforeRemoveVertex(vertexA2, true); m_shape.removeVertexInternal_(vertexA2, false); _removeAngleSortInfo(vertexA1); _transferVertexData(vertexB2, vertexB1); @@ -750,17 +762,17 @@ private void _resolveOverlapOddEven(boolean bDirection1, // m_shape.dbgVerifyIntegrity(a2);//debug boolean bVisitedA1 = false; - m_shape.setNextVertex_(a1, a2); // ^ | <--- ^ \ --. - m_shape.setNextVertex_(a2, a1); // | | | ^ | | - m_shape.setPrevVertex_(b1, b2); // | | | | | | - m_shape.setPrevVertex_(b2, b1); // | | | | | | - int v = b2; // | | | | =>| | - while (v != a2) // | | . | | -. | - { // | <-- | | | ./ | | - int prev = m_shape.getPrevVertex(v); // | | | | | | | | - int next = m_shape.getNextVertex(v); // <-+---<--- | - // <-+---<--- | - m_shape.setPrevVertex_(v, next); // --------. <-------- + m_shape.setNextVertex_(a1, a2); + m_shape.setNextVertex_(a2, a1); + m_shape.setPrevVertex_(b1, b2); + m_shape.setPrevVertex_(b2, b1); + int v = b2; + while (v != a2) + { + int prev = m_shape.getPrevVertex(v); + int next = m_shape.getNextVertex(v); + + m_shape.setPrevVertex_(v, next); m_shape.setNextVertex_(v, prev); bVisitedA1 |= v == a1; v = next; @@ -791,12 +803,6 @@ private void _resolveOverlapOddEven(boolean bDirection1, // m_shape.dbgVerifyIntegrity(b1);//debug // m_shape.dbgVerifyIntegrity(a1);//debug } - // else - // { - // m_shape._ReverseRingInternal(vertexA2); - // _ResolveOverlapOddEven(bDirection1, !bDirection2, vertexA1, - // vertexB1, vertexA2, vertexB2); - // } } } @@ -902,12 +908,10 @@ private void _fixOrphanVertices() { int pathSize = 1; for (int vertex = m_shape.getNextVertex(first); vertex != first; vertex = m_shape .getNextVertex(vertex)) { - // _ASSERT(m_shape.getPathFromVertex(vertex) == -1); m_shape.setPathToVertex_(vertex, path); - // _ASSERT(m_shape.getNextVertex(m_shape.getPrevVertex(vertex)) - // == vertex); pathSize++; } + m_shape.setRingAreaValid_(path,false); m_shape.setPathSize_(path, pathSize); m_shape.setLastVertex_(path, m_shape.getPrevVertex(first)); geometrySize += pathSize; @@ -915,40 +919,29 @@ private void _fixOrphanVertices() { path = m_shape.getNextPath(path); } - // produce new paths for the orphan vertices. + // Some vertices do not belong to any path. We have to create new path + // objects for those. + // Produce new paths for the orphan vertices. for (int node = m_sortedVertices.getFirst(m_sortedVertices .getFirstList()); node != -1; node = m_sortedVertices .getNext(node)) { int vertex = m_sortedVertices.getData(node); if (m_shape.getPathFromVertex(vertex) != -1) continue; - int path = m_shape.insertPath(m_geometry, -1); - int pathSize = 0; - int first = vertex; - while (true) { - m_shape.setPathToVertex_(vertex, path); - pathSize++; - int next = m_shape.getNextVertex(vertex); - // _ASSERT(m_shape.getNextVertex(m_shape.getPrevVertex(vertex)) - // == vertex); - if (next == first) - break; - vertex = next; - } - - m_shape.setClosedPath(path, true); - - m_shape.setPathSize_(path, pathSize); - m_shape.setFirstVertex_(path, first); - m_shape.setLastVertex_(path, m_shape.getPrevVertex(first)); - geometrySize += pathSize; + + int path = m_shape.insertClosedPath_(m_geometry, -1, vertex, vertex, null); + geometrySize += m_shape.getPathSize(path); pathCount++; } + m_shape.setGeometryPathCount_(m_geometry, pathCount); - int totalPointCount = m_shape.getTotalPointCount() - - m_shape.getPointCount(m_geometry); m_shape.setGeometryVertexCount_(m_geometry, geometrySize); - m_shape.setTotalPointCount_(totalPointCount + geometrySize); + int totalPointCount = 0; + for (int geometry = m_shape.getFirstGeometry(); geometry != -1; geometry = m_shape.getNextGeometry(geometry)) { + totalPointCount += m_shape.getPointCount(geometry); + } + + m_shape.setTotalPointCount_(totalPointCount); } private int _getNextEdgeIndex(int indexIn) { @@ -1003,8 +996,8 @@ int _compareVerticesSimple(int v1, int v2) { int res = pt1.compare(pt2); if (res == 0) {// sort equal vertices by the path ID int i1 = m_shape.getPathFromVertex(v1); - int i2 = m_shape.getPathFromVertex(v2); - res = i1 < i2 ? -1 : (i1 == i2 ? 0 : 1); + int i2 = m_shape.getPathFromVertex(v2); + res = i1 < i2 ? -1 : (i1 == i2 ? 0 : 1); } return res; diff --git a/src/main/java/com/esri/core/geometry/StridedIndexTypeCollection.java b/src/main/java/com/esri/core/geometry/StridedIndexTypeCollection.java index 93de656f..785e696f 100644 --- a/src/main/java/com/esri/core/geometry/StridedIndexTypeCollection.java +++ b/src/main/java/com/esri/core/geometry/StridedIndexTypeCollection.java @@ -63,8 +63,13 @@ final class StridedIndexTypeCollection { m_blockSize = m_realBlockSize / m_realStride; } + private boolean dbgdelete_(int element) { + m_buffer[element >> m_blockPower][(element & m_blockMask) + 1] = -0x7eadbeed; + return true; + } + void deleteElement(int element) { - assert dbgdelete_(element); + assert(dbgdelete_(element)); int totalStrides = (element >> m_blockPower) * m_blockSize * m_realStride + (element & m_blockMask); if (totalStrides < m_last * m_realStride) { @@ -79,12 +84,14 @@ void deleteElement(int element) { // Returns the given field of the element. int getField(int element, int field) { + assert(m_buffer[element >> m_blockPower][(element & m_blockMask) + 1] != -0x7eadbeed); return m_buffer[element >> m_blockPower][(element & m_blockMask) + field]; } // Sets the given field of the element. void setField(int element, int field, int value) { + assert(m_buffer[element >> m_blockPower][(element & m_blockMask) + 1] != -0x7eadbeed); m_buffer[element >> m_blockPower][(element & m_blockMask) + field] = value; } @@ -201,11 +208,6 @@ static boolean isValidElement(int element) { return element >= 0; } - private boolean dbgdelete_(int element) { - setField(element, 1, 0x7eadbeed); - return true; - } - private void ensureBufferBlocksCapacity(int blocks) { if (m_buffer.length < blocks) { int[][] newBuffer = new int[blocks][]; diff --git a/src/main/java/com/esri/core/geometry/TopoGraph.java b/src/main/java/com/esri/core/geometry/TopoGraph.java index affcbc60..0bf61cd7 100644 --- a/src/main/java/com/esri/core/geometry/TopoGraph.java +++ b/src/main/java/com/esri/core/geometry/TopoGraph.java @@ -1502,58 +1502,78 @@ void simplifyWinding_() { //there is nothing to do } + private int getFirstUnvisitedHalfEdgeOnCluster_(int cluster, int hintEdge, + int vistiedEdgesIndex) { + // finds first half edge which is unvisited (index is not set to 1. + // when hintEdge != -1, it is used to start going around the edges. + + int edge = hintEdge != -1 ? hintEdge : getClusterHalfEdge(cluster); + if (edge == -1) + return -1; + + int f = edge; + + while (true) { + int v = getHalfEdgeUserIndex(edge, vistiedEdgesIndex); + if (v != 1) { + return edge; + } + + int next = getHalfEdgeNext(getHalfEdgeTwin(edge)); + if (next == f) + return -1; + + edge = next; + } + } + boolean removeSpikes_() { boolean removed = false; int visitedIndex = createUserIndexForHalfEdges(); for (int cluster = getFirstCluster(); cluster != -1; cluster = getNextCluster(cluster)) { - int firstHalfEdge = getClusterHalfEdge(cluster); - if (firstHalfEdge == -1) - continue; - - int half_edge = firstHalfEdge; - - do { - int visited = getHalfEdgeUserIndex(half_edge, visitedIndex); - int halfEdgeNext = getHalfEdgeNext(getHalfEdgeTwin(half_edge)); - if (visited != 1) { - int faceHalfEdge = half_edge; - do { - int faceHalfEdgeNext = getHalfEdgeNext(faceHalfEdge); - int faceHalfEdgePrev = getHalfEdgePrev(faceHalfEdge); - int faceHalfEdgeTwin = getHalfEdgeTwin(faceHalfEdge); - // We first check whether faceHalfEdge corresponds to - // the starting segment with - // "faceHalfEdge != half_edge && faceHalfEdgePrev != half_edge" - // Otherwise if we deleted the first half edge, then - // there could be an infinite loop since the terminal - // condition wouldn't occur. - // if (faceHalfEdge != half_edge && faceHalfEdgeNext != - // half_edge && faceHalfEdgePrev == faceHalfEdgeTwin) - if (faceHalfEdge != half_edge - && faceHalfEdgePrev != half_edge - && faceHalfEdgePrev == faceHalfEdgeTwin) { - deleteEdgeInternal_(faceHalfEdge); - removed = true; - assert (faceHalfEdgeNext != faceHalfEdge); - assert (faceHalfEdgeNext != faceHalfEdgeTwin); - assert (faceHalfEdgeNext != faceHalfEdgePrev); - } else { - setHalfEdgeUserIndex(faceHalfEdge, visitedIndex, 1); - } - - faceHalfEdge = faceHalfEdgeNext; - } while (faceHalfEdge != half_edge); + int nextClusterEdge = -1; //a hint + while (true) { + int firstHalfEdge = getFirstUnvisitedHalfEdgeOnCluster_(cluster, nextClusterEdge, visitedIndex); + if (firstHalfEdge == -1) + break; + + nextClusterEdge = getHalfEdgeNext(getHalfEdgeTwin(firstHalfEdge)); + int faceHalfEdge = firstHalfEdge; + while (true) { + int faceHalfEdgeNext = getHalfEdgeNext(faceHalfEdge); int faceHalfEdgePrev = getHalfEdgePrev(faceHalfEdge); int faceHalfEdgeTwin = getHalfEdgeTwin(faceHalfEdge); + if (faceHalfEdgePrev == faceHalfEdgeTwin) { - deleteEdgeInternal_(faceHalfEdge); + deleteEdgeInternal_(faceHalfEdge); //deletes the edge and its twin removed = true; + + if (nextClusterEdge == faceHalfEdge || nextClusterEdge == faceHalfEdgeTwin) + nextClusterEdge = -1; //deleted the hint edge + + if (faceHalfEdge == firstHalfEdge || faceHalfEdgePrev == firstHalfEdge) { + firstHalfEdge = faceHalfEdgeNext; + if (faceHalfEdge == firstHalfEdge || faceHalfEdgePrev == firstHalfEdge) { + //deleted all edges in a face + break; + } + + faceHalfEdge = faceHalfEdgeNext; + continue; + } + } + else { + setHalfEdgeUserIndex(faceHalfEdge, visitedIndex, 1); + } + + faceHalfEdge = faceHalfEdgeNext; + if (faceHalfEdge == firstHalfEdge) + break; } - half_edge = halfEdgeNext; - } while (half_edge != firstHalfEdge); + } } return removed; @@ -2104,7 +2124,9 @@ int deleteEdgeInternal_(int half_edge) { setChainHalfEdge_(chain, n); } - if (!NumberUtils.isNaN(getChainArea(chain))) { + int chainIndex = getChainIndex_(chain); + double v = m_chainAreas.read(chainIndex); + if (!NumberUtils.isNaN(v)) { setChainArea_(chain, NumberUtils.TheNaN); setChainPerimeter_(chain, NumberUtils.TheNaN); } diff --git a/src/test/java/com/esri/core/geometry/TestRelation.java b/src/test/java/com/esri/core/geometry/TestRelation.java index e12c1d4a..613f8b13 100644 --- a/src/test/java/com/esri/core/geometry/TestRelation.java +++ b/src/test/java/com/esri/core/geometry/TestRelation.java @@ -2,13 +2,12 @@ import junit.framework.TestCase; -import org.codehaus.jackson.JsonFactory; -import org.codehaus.jackson.JsonParser; import org.junit.Test; import com.esri.core.geometry.Geometry.GeometryAccelerationDegree; public class TestRelation extends TestCase { + @Override protected void setUp() throws Exception { super.setUp(); @@ -629,7 +628,7 @@ public static void testPolygonPolygonEquals() { // Polygon1 and Polygon2 are topologically equal, but have differing // number of vertices - String str1 = "{\"rings\":[[[0,0],[0,5],[0,10],[0,5],[0,7],[0,10],[10,10],[10,0],[0,0]],[[1,1],[9,1],[9,9],[1,9],[1,1],[1,1]]]}"; + String str1 = "{\"rings\":[[[0,0],[0,5],[0,7],[0,10],[0,10],[10,10],[10,0],[0,0]],[[1,1],[9,1],[9,9],[1,9],[1,1],[1,1]]]}"; String str2 = "{\"rings\":[[[0,10],[10,10],[10,0],[0,0],[0,10]],[[9,1],[9,6],[9,9],[1,9],[1,1],[1,1],[9,1]]]}"; Polygon polygon1 = (Polygon) TestCommonMethods.fromJson(str1) @@ -681,9 +680,20 @@ public static void testPolygonPolygonEquals() { polygon2 = (Polygon) TestCommonMethods.fromJson(str2).getGeometry(); res = equals.execute(polygon1, polygon2, sr, null); - assertTrue(res); + assertTrue(!res); res = equals.execute(polygon2, polygon1, sr, null); - assertTrue(res); + assertTrue(!res); + + // The rings are equal but first polygon has two rings stacked + str1 = "{\"rings\":[[[0,0],[0,10],[10,10],[10,0],[0,0]],[[0,10],[10,10],[10,0],[0,0],[0,10]]]}"; + str2 = "{\"rings\":[[[0,10],[10,10],[10,0],[0,0],[0,10]]]}"; + polygon1 = (Polygon) TestCommonMethods.fromJson(str1).getGeometry(); + polygon2 = (Polygon) TestCommonMethods.fromJson(str2).getGeometry(); + + res = equals.execute(polygon1, polygon2, sr, null); + assertTrue(!res); + res = equals.execute(polygon2, polygon1, sr, null); + assertTrue(!res); } @Test @@ -2435,10 +2445,10 @@ public static void testPolygonEnvelope() { wiggleGeometry(densified, 0.00000001, 1982); wiggleGeometry(envelope, 0.00000001, 511); assertTrue(equals.execute(envelope, densified, sr, null)); // they - // cover - // the - // same - // space + // cover + // the + // same + // space assertTrue(contains.execute(densified, envelope, sr, null)); assertTrue(!disjoint.execute(envelope, densified, sr, null)); assertTrue(!touches.execute(envelope, densified, sr, null)); @@ -2457,14 +2467,14 @@ public static void testPolygonEnvelope() { wiggleGeometry(densified, 0.00000001, 1982); wiggleGeometry(envelope, 0.00000001, 511); assertTrue(!equals.execute(envelope, densified, sr, null)); // the - // polygon - // contains - // the - // envelope, - // but - // they - // aren't - // equal + // polygon + // contains + // the + // envelope, + // but + // they + // aren't + // equal assertTrue(contains.execute(densified, envelope, sr, null)); assertTrue(!disjoint.execute(envelope, densified, sr, null)); assertTrue(!touches.execute(envelope, densified, sr, null)); @@ -2483,15 +2493,15 @@ public static void testPolygonEnvelope() { wiggleGeometry(densified, 0.00000001, 1982); wiggleGeometry(envelope, 0.00000001, 511); assertTrue(!equals.execute(envelope, densified, sr, null)); // the - // envelope - // sticks - // outside - // of - // the - // polygon - // but - // they - // intersect + // envelope + // sticks + // outside + // of + // the + // polygon + // but + // they + // intersect assertTrue(!contains.execute(densified, envelope, sr, null)); assertTrue(!disjoint.execute(envelope, densified, sr, null)); assertTrue(!touches.execute(envelope, densified, sr, null)); @@ -2510,17 +2520,17 @@ public static void testPolygonEnvelope() { wiggleGeometry(densified, 0.00000001, 1982); wiggleGeometry(envelope, 0.00000001, 511); assertTrue(!equals.execute(envelope, densified, sr, null)); // the - // envelope - // sticks - // outside - // of - // the - // polygon - // but - // they - // intersect - // and - // overlap + // envelope + // sticks + // outside + // of + // the + // polygon + // but + // they + // intersect + // and + // overlap assertTrue(!contains.execute(densified, envelope, sr, null)); assertTrue(!disjoint.execute(envelope, densified, sr, null)); assertTrue(!touches.execute(envelope, densified, sr, null)); @@ -2539,15 +2549,15 @@ public static void testPolygonEnvelope() { wiggleGeometry(densified, 0.00000001, 1982); wiggleGeometry(envelope, 0.00000001, 511); assertTrue(!equals.execute(envelope, densified, sr, null)); // the - // envelope - // rides - // the - // side - // of - // the - // polygon - // (they - // touch) + // envelope + // rides + // the + // side + // of + // the + // polygon + // (they + // touch) assertTrue(!contains.execute(densified, envelope, sr, null)); assertTrue(!disjoint.execute(envelope, densified, sr, null)); assertTrue(touches.execute(envelope, densified, sr, null)); @@ -2566,12 +2576,12 @@ public static void testPolygonEnvelope() { wiggleGeometry(densified, 0.00000001, 1982); wiggleGeometry(envelope, 0.00000001, 511); assertTrue(contains.execute(densified, envelope, sr, null)); // polygon - // and - // envelope - // cover - // the - // same - // space + // and + // envelope + // cover + // the + // same + // space assertTrue(!disjoint.execute(densified, envelope, sr, null)); assertTrue(!touches.execute(envelope, densified, sr, null)); assertTrue(!overlaps.execute(envelope, densified, sr, null)); @@ -2590,15 +2600,15 @@ public static void testPolygonEnvelope() { wiggleGeometry(envelope, 0.00000001, 511); assertTrue(!equals.execute(envelope, densified, sr, null)); assertTrue(!contains.execute(densified, envelope, sr, null)); // envelope - // sticks - // outside - // of - // polygon, - // but - // the - // envelopes - // are - // equal + // sticks + // outside + // of + // polygon, + // but + // the + // envelopes + // are + // equal assertTrue(!disjoint.execute(densified, envelope, sr, null)); assertTrue(!touches.execute(envelope, densified, sr, null)); assertTrue(!overlaps.execute(envelope, densified, sr, null)); @@ -2617,15 +2627,15 @@ public static void testPolygonEnvelope() { wiggleGeometry(envelope, 0.00000001, 511); assertTrue(!equals.execute(envelope, densified, sr, null)); assertTrue(!contains.execute(densified, envelope, sr, null)); // the - // polygon - // envelope - // doesn't - // contain - // the - // envelope, - // but - // they - // intersect + // polygon + // envelope + // doesn't + // contain + // the + // envelope, + // but + // they + // intersect assertTrue(!disjoint.execute(densified, envelope, sr, null)); assertTrue(!touches.execute(envelope, densified, sr, null)); assertTrue(!overlaps.execute(envelope, densified, sr, null)); @@ -2644,16 +2654,16 @@ public static void testPolygonEnvelope() { wiggleGeometry(envelope, 0.00000001, 511); assertTrue(!equals.execute(envelope, densified, sr, null)); assertTrue(!contains.execute(densified, envelope, sr, null)); // envelope - // degenerate - // to - // a - // point - // and - // is - // on - // border - // (i.e. - // touches) + // degenerate + // to + // a + // point + // and + // is + // on + // border + // (i.e. + // touches) assertTrue(!disjoint.execute(densified, envelope, sr, null)); assertTrue(touches.execute(envelope, densified, sr, null)); assertTrue(!overlaps.execute(envelope, densified, sr, null)); @@ -2672,14 +2682,14 @@ public static void testPolygonEnvelope() { wiggleGeometry(envelope, 0.00000001, 511); assertTrue(!equals.execute(envelope, densified, sr, null)); assertTrue(contains.execute(densified, envelope, sr, null)); // envelope - // degenerate - // to - // a - // point - // and - // is - // properly - // inside + // degenerate + // to + // a + // point + // and + // is + // properly + // inside assertTrue(!disjoint.execute(densified, envelope, sr, null)); assertTrue(!touches.execute(envelope, densified, sr, null)); assertTrue(!overlaps.execute(envelope, densified, sr, null)); @@ -2698,14 +2708,14 @@ public static void testPolygonEnvelope() { wiggleGeometry(envelope, 0.00000001, 511); assertTrue(!equals.execute(envelope, densified, sr, null)); assertTrue(!contains.execute(densified, envelope, sr, null)); // envelope - // degenerate - // to - // a - // point - // and - // is - // properly - // outside + // degenerate + // to + // a + // point + // and + // is + // properly + // outside assertTrue(disjoint.execute(densified, envelope, sr, null)); assertTrue(!touches.execute(envelope, densified, sr, null)); assertTrue(!overlaps.execute(envelope, densified, sr, null)); @@ -2724,20 +2734,20 @@ public static void testPolygonEnvelope() { wiggleGeometry(envelope, 0.00000001, 511); assertTrue(!equals.execute(envelope, densified, sr, null)); assertTrue(!contains.execute(densified, envelope, sr, null)); // envelope - // degenerate - // to - // a - // line - // and - // rides - // the - // bottom - // of - // the - // polygon - // (no - // interior - // intersection) + // degenerate + // to + // a + // line + // and + // rides + // the + // bottom + // of + // the + // polygon + // (no + // interior + // intersection) assertTrue(!disjoint.execute(densified, envelope, sr, null)); assertTrue(touches.execute(envelope, densified, sr, null)); assertTrue(!overlaps.execute(envelope, densified, sr, null)); @@ -2756,20 +2766,20 @@ public static void testPolygonEnvelope() { wiggleGeometry(envelope, 0.00000001, 511); assertTrue(!equals.execute(envelope, densified, sr, null)); assertTrue(contains.execute(densified, envelope, sr, null)); // envelope - // degenerate - // to - // a - // line, - // touches - // the - // border - // on - // the - // inside - // yet - // has - // interior - // intersection + // degenerate + // to + // a + // line, + // touches + // the + // border + // on + // the + // inside + // yet + // has + // interior + // intersection assertTrue(!disjoint.execute(densified, envelope, sr, null)); assertTrue(!touches.execute(envelope, densified, sr, null)); assertTrue(!overlaps.execute(envelope, densified, sr, null)); @@ -2788,16 +2798,16 @@ public static void testPolygonEnvelope() { wiggleGeometry(envelope, 0.00000001, 511); assertTrue(!equals.execute(envelope, densified, sr, null)); assertTrue(!contains.execute(densified, envelope, sr, null)); // envelope - // degenerate - // to - // a - // line, - // touches - // the - // boundary, - // and - // is - // outside + // degenerate + // to + // a + // line, + // touches + // the + // boundary, + // and + // is + // outside assertTrue(!disjoint.execute(densified, envelope, sr, null)); assertTrue(touches.execute(envelope, densified, sr, null)); assertTrue(!overlaps.execute(envelope, densified, sr, null)); @@ -2816,13 +2826,13 @@ public static void testPolygonEnvelope() { wiggleGeometry(envelope, 0.00000001, 511); assertTrue(!equals.execute(envelope, densified, sr, null)); assertTrue(!contains.execute(densified, envelope, sr, null)); // envelope - // degenerate - // to - // a - // line, - // and - // is - // outside + // degenerate + // to + // a + // line, + // and + // is + // outside assertTrue(disjoint.execute(densified, envelope, sr, null)); assertTrue(!touches.execute(envelope, densified, sr, null)); assertTrue(!overlaps.execute(envelope, densified, sr, null)); @@ -2841,13 +2851,13 @@ public static void testPolygonEnvelope() { wiggleGeometry(envelope, 0.00000001, 511); assertTrue(!equals.execute(envelope, densified, sr, null)); assertTrue(!contains.execute(densified, envelope, sr, null)); // envelope - // degenerate - // to - // a - // line, - // and - // crosses - // polygon + // degenerate + // to + // a + // line, + // and + // crosses + // polygon assertTrue(!disjoint.execute(densified, envelope, sr, null)); assertTrue(!touches.execute(envelope, densified, sr, null)); assertTrue(!overlaps.execute(envelope, densified, sr, null)); @@ -2889,11 +2899,11 @@ public static void testPolylineEnvelope() { wiggleGeometry(densified, 0.00000001, 1982); wiggleGeometry(envelope, 0.00000001, 511); assertTrue(!equals.execute(envelope, densified, sr, null)); // polyline - // straddles - // the - // envelope - // like - // a hat + // straddles + // the + // envelope + // like + // a hat assertTrue(!contains.execute(densified, envelope, sr, null)); assertTrue(!disjoint.execute(envelope, densified, sr, null)); assertTrue(touches.execute(envelope, densified, sr, null)); @@ -2950,8 +2960,8 @@ public static void testPolylineEnvelope() { wiggleGeometry(densified, 0.00000001, 1982); wiggleGeometry(envelope, 0.00000001, 511); assertTrue(!equals.execute(envelope, densified, sr, null)); // polyline - // properly - // inside + // properly + // inside assertTrue(contains.execute(envelope, densified, sr, null)); assertTrue(!disjoint.execute(envelope, densified, sr, null)); assertTrue(!touches.execute(envelope, densified, sr, null)); @@ -3007,14 +3017,14 @@ public static void testPolylineEnvelope() { wiggleGeometry(densified, 0.00000001, 1982); wiggleGeometry(envelope, 0.00000001, 511); assertTrue(!equals.execute(envelope, densified, sr, null)); // polyline - // slices - // through - // the - // envelope - // (interior - // and - // exterior - // intersection) + // slices + // through + // the + // envelope + // (interior + // and + // exterior + // intersection) assertTrue(!contains.execute(densified, envelope, sr, null)); assertTrue(!disjoint.execute(envelope, densified, sr, null)); assertTrue(!touches.execute(envelope, densified, sr, null)); @@ -3033,9 +3043,9 @@ public static void testPolylineEnvelope() { wiggleGeometry(densified, 0.00000001, 1982); wiggleGeometry(envelope, 0.00000001, 511); assertTrue(!equals.execute(envelope, densified, sr, null)); // polyline - // outside - // of - // envelope + // outside + // of + // envelope assertTrue(!contains.execute(densified, envelope, sr, null)); assertTrue(disjoint.execute(envelope, densified, sr, null)); assertTrue(!touches.execute(envelope, densified, sr, null)); @@ -3055,12 +3065,12 @@ public static void testPolylineEnvelope() { wiggleGeometry(densified, 0.00000001, 1982); wiggleGeometry(envelope, 0.00000001, 511); assertTrue(!equals.execute(envelope, densified, sr, null)); // polyline - // straddles - // the - // degenerate - // envelope - // like - // a hat + // straddles + // the + // degenerate + // envelope + // like + // a hat assertTrue(contains.execute(densified, envelope, sr, null)); assertTrue(!disjoint.execute(envelope, densified, sr, null)); assertTrue(!touches.execute(envelope, densified, sr, null)); @@ -3099,13 +3109,13 @@ public static void testPolylineEnvelope() { wiggleGeometry(densified, 0.00000001, 511); wiggleGeometry(envelope, 0.00000001, 1982); assertTrue(!equals.execute(envelope, densified, sr, null)); // degenerate - // envelope - // is at - // the - // end - // point - // of - // polyline + // envelope + // is at + // the + // end + // point + // of + // polyline assertTrue(!contains.execute(densified, envelope, sr, null)); assertTrue(!disjoint.execute(envelope, densified, sr, null)); assertTrue(touches.execute(envelope, densified, sr, null)); @@ -3125,12 +3135,12 @@ public static void testPolylineEnvelope() { wiggleGeometry(densified, 0.00000001, 1982); wiggleGeometry(envelope, 0.00000001, 511); assertTrue(!equals.execute(envelope, densified, sr, null)); // degenerate - // envelope - // is at - // the - // interior - // of - // polyline + // envelope + // is at + // the + // interior + // of + // polyline assertTrue(contains.execute(densified, envelope, sr, null)); assertTrue(!disjoint.execute(envelope, densified, sr, null)); assertTrue(!touches.execute(envelope, densified, sr, null)); @@ -3149,9 +3159,9 @@ public static void testPolylineEnvelope() { wiggleGeometry(densified, 0.00000001, 1982); wiggleGeometry(envelope, 0.00000001, 511); assertTrue(!equals.execute(envelope, densified, sr, null)); // degenerate - // envelope - // crosses - // polyline + // envelope + // crosses + // polyline assertTrue(!contains.execute(densified, envelope, sr, null)); assertTrue(!disjoint.execute(envelope, densified, sr, null)); assertTrue(!touches.execute(envelope, densified, sr, null)); @@ -3170,9 +3180,9 @@ public static void testPolylineEnvelope() { wiggleGeometry(densified, 0.00000001, 1982); wiggleGeometry(envelope, 0.00000001, 511); assertTrue(!equals.execute(envelope, densified, sr, null)); // degenerate - // envelope - // crosses - // polyline + // envelope + // crosses + // polyline assertTrue(!contains.execute(densified, envelope, sr, null)); assertTrue(!disjoint.execute(envelope, densified, sr, null)); assertTrue(touches.execute(envelope, densified, sr, null)); @@ -3191,9 +3201,9 @@ public static void testPolylineEnvelope() { wiggleGeometry(densified, 0.00000001, 1982); wiggleGeometry(envelope, 0.00000001, 511); assertTrue(!equals.execute(envelope, densified, sr, null)); // degenerate - // envelope - // contains - // polyline + // envelope + // contains + // polyline assertTrue(!contains.execute(densified, envelope, sr, null)); assertTrue(contains.execute(envelope, densified, sr, null)); assertTrue(!disjoint.execute(envelope, densified, sr, null)); @@ -3213,8 +3223,8 @@ public static void testPolylineEnvelope() { wiggleGeometry(densified, 0.00000001, 1982); wiggleGeometry(envelope, 0.00000001, 511); assertTrue(!equals.execute(envelope, densified, sr, null)); // polyline - // properly - // inside + // properly + // inside assertTrue(!contains.execute(envelope, densified, sr, null)); assertTrue(!disjoint.execute(envelope, densified, sr, null)); assertTrue(touches.execute(envelope, densified, sr, null)); @@ -3233,8 +3243,8 @@ public static void testPolylineEnvelope() { wiggleGeometry(densified, 0.00000001, 1982); wiggleGeometry(envelope, 0.00000001, 511); assertTrue(equals.execute(envelope, densified, sr, null)); // polyline - // properly - // inside + // properly + // inside assertTrue(contains.execute(envelope, densified, sr, null)); assertTrue(!disjoint.execute(envelope, densified, sr, null)); assertTrue(!touches.execute(envelope, densified, sr, null)); @@ -3276,9 +3286,9 @@ public static void testMultiPointEnvelope() { wiggleGeometry(multi_point, 0.00000001, 1982); wiggleGeometry(envelope, 0.00000001, 511); assertTrue(!equals.execute(envelope, multi_point, sr, null)); // all - // points - // on - // boundary + // points + // on + // boundary assertTrue(!contains.execute(envelope, multi_point, sr, null)); assertTrue(!disjoint.execute(envelope, multi_point, sr, null)); assertTrue(touches.execute(envelope, multi_point, sr, null)); @@ -3296,13 +3306,13 @@ public static void testMultiPointEnvelope() { wiggleGeometry(multi_point, 0.00000001, 1982); wiggleGeometry(envelope, 0.00000001, 511); assertTrue(!equals.execute(envelope, multi_point, sr, null)); // points - // on - // boundary - // and - // one - // point - // in - // interior + // on + // boundary + // and + // one + // point + // in + // interior assertTrue(contains.execute(envelope, multi_point, sr, null)); assertTrue(!disjoint.execute(envelope, multi_point, sr, null)); assertTrue(!touches.execute(envelope, multi_point, sr, null)); @@ -3320,12 +3330,12 @@ public static void testMultiPointEnvelope() { wiggleGeometry(multi_point, 0.00000001, 1982); wiggleGeometry(envelope, 0.00000001, 511); assertTrue(!equals.execute(envelope, multi_point, sr, null)); // points - // on - // boundary, - // one - // interior, - // one - // exterior + // on + // boundary, + // one + // interior, + // one + // exterior assertTrue(!contains.execute(envelope, multi_point, sr, null)); assertTrue(!disjoint.execute(envelope, multi_point, sr, null)); assertTrue(!touches.execute(envelope, multi_point, sr, null)); @@ -3343,10 +3353,10 @@ public static void testMultiPointEnvelope() { wiggleGeometry(multi_point, 0.00000001, 1982); wiggleGeometry(envelope, 0.00000001, 511); assertTrue(!equals.execute(envelope, multi_point, sr, null)); // points - // on - // boundary, - // one - // exterior + // on + // boundary, + // one + // exterior assertTrue(!contains.execute(envelope, multi_point, sr, null)); assertTrue(!disjoint.execute(envelope, multi_point, sr, null)); assertTrue(touches.execute(envelope, multi_point, sr, null)); @@ -3364,8 +3374,8 @@ public static void testMultiPointEnvelope() { wiggleGeometry(multi_point, 0.00000001, 1982); wiggleGeometry(envelope, 0.00000001, 511); assertTrue(!equals.execute(envelope, multi_point, sr, null)); // all - // points - // exterior + // points + // exterior assertTrue(!contains.execute(envelope, multi_point, sr, null)); assertTrue(disjoint.execute(envelope, multi_point, sr, null)); assertTrue(!touches.execute(envelope, multi_point, sr, null)); @@ -3383,18 +3393,18 @@ public static void testMultiPointEnvelope() { wiggleGeometry(multi_point, 0.00000001, 1982); wiggleGeometry(envelope, 0.00000001, 511); assertTrue(!equals.execute(envelope, multi_point, sr, null)); // degenerate - // envelope - // slices - // through - // some - // points, - // but - // some - // points - // are - // off - // the - // line + // envelope + // slices + // through + // some + // points, + // but + // some + // points + // are + // off + // the + // line assertTrue(!contains.execute(envelope, multi_point, sr, null)); assertTrue(!disjoint.execute(envelope, multi_point, sr, null)); assertTrue(touches.execute(envelope, multi_point, sr, null)); @@ -3412,18 +3422,18 @@ public static void testMultiPointEnvelope() { wiggleGeometry(multi_point, 0.00000001, 1982); wiggleGeometry(envelope, 0.00000001, 511); assertTrue(!equals.execute(envelope, multi_point, sr, null)); // degenerate - // envelope - // slices - // through - // some - // points, - // but - // some - // points - // are - // off - // the - // line + // envelope + // slices + // through + // some + // points, + // but + // some + // points + // are + // off + // the + // line assertTrue(!contains.execute(envelope, multi_point, sr, null)); assertTrue(!disjoint.execute(envelope, multi_point, sr, null)); assertTrue(!touches.execute(envelope, multi_point, sr, null)); @@ -3440,22 +3450,22 @@ public static void testMultiPointEnvelope() { wiggleGeometry(multi_point, 0.00000001, 1982); wiggleGeometry(envelope, 0.00000001, 511); assertTrue(!equals.execute(envelope, multi_point, sr, null)); // degenerate - // envelopes - // slices - // through - // all - // the - // points, - // and - // they - // are - // at - // the - // end - // points - // of - // the - // line + // envelopes + // slices + // through + // all + // the + // points, + // and + // they + // are + // at + // the + // end + // points + // of + // the + // line assertTrue(!contains.execute(envelope, multi_point, sr, null)); assertTrue(!disjoint.execute(envelope, multi_point, sr, null)); assertTrue(touches.execute(envelope, multi_point, sr, null)); @@ -3472,21 +3482,21 @@ public static void testMultiPointEnvelope() { wiggleGeometry(multi_point, 0.00000001, 1982); wiggleGeometry(envelope, 0.00000001, 511); assertTrue(!equals.execute(envelope, multi_point, sr, null)); // degenerate - // envelopes - // slices - // through - // all - // the - // points, - // and - // they - // are - // in - // the - // interior - // of - // the - // line + // envelopes + // slices + // through + // all + // the + // points, + // and + // they + // are + // in + // the + // interior + // of + // the + // line assertTrue(contains.execute(envelope, multi_point, sr, null)); assertTrue(!disjoint.execute(envelope, multi_point, sr, null)); assertTrue(!touches.execute(envelope, multi_point, sr, null)); @@ -3504,8 +3514,8 @@ public static void testMultiPointEnvelope() { wiggleGeometry(multi_point, 0.00000001, 1982); wiggleGeometry(envelope, 0.00000001, 511); assertTrue(!equals.execute(envelope, multi_point, sr, null)); // all - // points - // exterior + // points + // exterior assertTrue(!contains.execute(envelope, multi_point, sr, null)); assertTrue(disjoint.execute(envelope, multi_point, sr, null)); assertTrue(!touches.execute(envelope, multi_point, sr, null)); @@ -3523,8 +3533,8 @@ public static void testMultiPointEnvelope() { wiggleGeometry(multi_point, 0.00000001, 1982); wiggleGeometry(envelope, 0.00000001, 511); assertTrue(!equals.execute(envelope, multi_point, sr, null)); // all - // points - // exterior + // points + // exterior assertTrue(!contains.execute(envelope, multi_point, sr, null)); assertTrue(disjoint.execute(envelope, multi_point, sr, null)); assertTrue(!touches.execute(envelope, multi_point, sr, null)); @@ -3542,8 +3552,8 @@ public static void testMultiPointEnvelope() { wiggleGeometry(multi_point, 0.00000001, 1982); wiggleGeometry(envelope, 0.00000001, 511); assertTrue(!equals.execute(envelope, multi_point, sr, null)); // all - // points - // exterior + // points + // exterior assertTrue(!contains.execute(multi_point, envelope, sr, null)); assertTrue(!contains.execute(envelope, multi_point, sr, null)); assertTrue(!disjoint.execute(envelope, multi_point, sr, null)); @@ -3561,8 +3571,8 @@ public static void testMultiPointEnvelope() { wiggleGeometry(multi_point, 0.00000001, 1982); wiggleGeometry(envelope, 0.00000001, 511); assertTrue(equals.execute(envelope, multi_point, sr, null)); // all - // points - // exterior + // points + // exterior assertTrue(contains.execute(multi_point, envelope, sr, null)); assertTrue(contains.execute(envelope, multi_point, sr, null)); assertTrue(!disjoint.execute(envelope, multi_point, sr, null)); @@ -4937,33 +4947,22 @@ public static void testMultiPointMultipointRelate() { assertTrue(res); } - @Test - public static void testPolylinePointRelate() { - OperatorRelate op = (OperatorRelate) (OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.Relate)); - SpatialReference sr = SpatialReference.create(4326); - boolean res; - String scl; - - Polyline polyline = new Polyline(); - Point point = new Point(); - - polyline.startPath(0, 2); - polyline.lineTo(0, 4); - - point.setXY(0, 3); - - } - @Test public static void testCrosses_github_issue_40() { - // Issue 40: Acceleration without a spatial reference changes the result of relation operators - Geometry geom1 = OperatorImportFromWkt.local().execute(0, Geometry.Type.Unknown, "LINESTRING (2 0, 2 3)", null); - Geometry geom2 = OperatorImportFromWkt.local().execute(0, Geometry.Type.Unknown, "POLYGON ((1 1, 4 1, 4 4, 1 4, 1 1))", null); - boolean answer1 = OperatorCrosses.local().execute(geom1, geom2, null, null); + // Issue 40: Acceleration without a spatial reference changes the result + // of relation operators + Geometry geom1 = OperatorImportFromWkt.local().execute(0, + Geometry.Type.Unknown, "LINESTRING (2 0, 2 3)", null); + Geometry geom2 = OperatorImportFromWkt.local().execute(0, + Geometry.Type.Unknown, "POLYGON ((1 1, 4 1, 4 4, 1 4, 1 1))", + null); + boolean answer1 = OperatorCrosses.local().execute(geom1, geom2, null, + null); assertTrue(answer1); - OperatorCrosses.local().accelerateGeometry(geom1, null, GeometryAccelerationDegree.enumHot); - boolean answer2 = OperatorCrosses.local().execute(geom1, geom2, null, null); + OperatorCrosses.local().accelerateGeometry(geom1, null, + GeometryAccelerationDegree.enumHot); + boolean answer2 = OperatorCrosses.local().execute(geom1, geom2, null, + null); assertTrue(answer2); } } From 45be0d08dac0a04ae16057bfb295bfdfd35dcf08 Mon Sep 17 00:00:00 2001 From: serg4066 Date: Wed, 3 Sep 2014 18:55:33 -0700 Subject: [PATCH 089/196] javadoc edits --- .../java/com/esri/core/geometry/Geometry.java | 22 +++-- .../core/geometry/GeometryAccelerators.java | 12 --- .../esri/core/geometry/GeometryEngine.java | 97 +++++++++++++++++-- .../java/com/esri/core/geometry/Operator.java | 12 ++- .../esri/core/geometry/OperatorSimplify.java | 49 ++++++++-- .../core/geometry/OperatorSimplifyOGC.java | 34 +++++-- 6 files changed, 180 insertions(+), 46 deletions(-) diff --git a/src/main/java/com/esri/core/geometry/Geometry.java b/src/main/java/com/esri/core/geometry/Geometry.java index 08e15e40..d4cd9952 100644 --- a/src/main/java/com/esri/core/geometry/Geometry.java +++ b/src/main/java/com/esri/core/geometry/Geometry.java @@ -537,15 +537,25 @@ synchronized void _touch() { /** * Describes the degree of acceleration of the geometry. + * Acceleration usually builds a raster and a quadtree. */ static public enum GeometryAccelerationDegree { - enumMild, // m_path_envelopes; diff --git a/src/main/java/com/esri/core/geometry/GeometryEngine.java b/src/main/java/com/esri/core/geometry/GeometryEngine.java index 66406448..048994dd 100644 --- a/src/main/java/com/esri/core/geometry/GeometryEngine.java +++ b/src/main/java/com/esri/core/geometry/GeometryEngine.java @@ -34,7 +34,10 @@ import org.json.JSONException; /** - * Provides services that operate on geometry instances. + * Provides services that operate on geometry instances. The methods of GeometryEngine class call corresponding OperatorXXX classes. + * Consider using OperatorXXX classes directly as they often provide more functionality and better performance. For example, some Operators accept + * GeometryCursor class that could be implemented to wrap a feature cursor and make it feed geometries directly into an Operator. + * Also, some operators provide a way to accelerate an operation by using Operator.accelerateGeometry method. */ public class GeometryEngine { @@ -45,6 +48,8 @@ public class GeometryEngine { * Imports the MapGeometry from its JSON representation. M and Z values are * not imported from JSON representation. * + * See OperatorImportFromJson. + * * @param json * The JSON representation of the geometry (with spatial * reference). @@ -60,6 +65,8 @@ public static MapGeometry jsonToGeometry(JsonParser json) { * Imports the MapGeometry from its JSON representation. M and Z values are * not imported from JSON representation. * + * See OperatorImportFromJson. + * * @param json * The JSON representation of the geometry (with spatial * reference). @@ -76,6 +83,8 @@ public static MapGeometry jsonToGeometry(String json) throws JsonParseException, /** * Exports the specified geometry instance to it's JSON representation. * + * See OperatorExportToJson. + * * @see GeometryEngine#geometryToJson(SpatialReference spatialiReference, * Geometry geometry) * @param wkid @@ -94,6 +103,8 @@ public static String geometryToJson(int wkid, Geometry geometry) { * Exports the specified geometry instance to it's JSON representation. M * and Z values are not imported from JSON representation. * + * See OperatorExportToJson. + * * @param spatialReference * The spatial reference of associated object. * @param geometry @@ -118,6 +129,8 @@ public static String geometryToGeoJson(Geometry geometry) { /** * Exports the specified geometry instance to its GeoJSON representation. * + *See OperatorExportToGeoJson. + * * @see GeometryEngine#geometryToGeoJson(SpatialReference spatialReference, * Geometry geometry) * @@ -135,6 +148,8 @@ public static String geometryToGeoJson(int wkid, Geometry geometry) { /** * Exports the specified geometry instance to it's JSON representation. * + *See OperatorImportFromGeoJson. + * * @param spatialReference * The spatial reference of associated object. * @param geometry @@ -152,6 +167,8 @@ public static String geometryToGeoJson(SpatialReference spatialReference, /** * Imports geometry from the ESRI shape file format. * + * See OperatorImportFromESRIShape. + * * @param esriShapeBuffer * The buffer containing geometry in the ESRI shape file format. * @param geometryType @@ -180,6 +197,8 @@ public static Geometry geometryFromEsriShape(byte[] esriShapeBuffer, /** * Exports geometry to the ESRI shape file format. * + * See OperatorExportToESRIShape. + * * @param geometry * The geometry to export. (null value is not allowed) * @return Array containing the exported ESRI shape file. @@ -194,6 +213,9 @@ public static byte[] geometryToEsriShape(Geometry geometry) { /** * Imports a geometry from a WKT string. + * + * See OperatorImportFromWkt. + * * @param wkt The string containing the geometry in WKT format. * @param importFlags Use the {@link WktImportFlags} interface. * @param geometryType The required type of the Geometry to be imported. Use Geometry.Type.Unknown if the geometry type needs to be determined from the WKT context. @@ -210,6 +232,9 @@ public static Geometry geometryFromWkt(String wkt, int importFlags, /** * Imports a geometry from a geoJson string. + * + * See OperatorImportFromGeoJson. + * * @param geoJson The string containing the geometry in geoJson format. * @param importFlags Use the {@link GeoJsonImportFlags} interface. * @param geometryType The required type of the Geometry to be imported. Use Geometry.Type.Unknown if the geometry type needs to be determined from the geoJson context. @@ -226,6 +251,9 @@ public static MapGeometry geometryFromGeoJson(String geoJson, /** * Exports a geometry to a string in WKT format. + * + * See OperatorExportToWkt. + * * @param geometry The geometry to export. (null value is not allowed) * @param exportFlags Use the {@link WktExportFlags} interface. * @return A String containing the exported geometry in WKT format. @@ -240,6 +268,8 @@ public static String geometryToWkt(Geometry geometry, int exportFlags) { * Constructs a new geometry by union an array of geometries. All inputs * must be of the same type of geometries and share one spatial reference. * + * See OperatorUnion. + * * @param geometries * The geometries to union. * @param spatialReference @@ -262,6 +292,8 @@ public static Geometry union(Geometry[] geometries, * Creates the difference of two geometries. The dimension of geometry2 has * to be equal to or greater than that of geometry1. * + * See OperatorDifference. + * * @param geometry1 * The geometry being subtracted. * @param substractor @@ -282,6 +314,8 @@ public static Geometry difference(Geometry geometry1, Geometry substractor, /** * Creates the symmetric difference of two geometries. * + * See OperatorSymmetricDifference. + * * @param leftGeometry * is one of the Geometry instances in the XOR operation. * @param rightGeometry @@ -302,6 +336,8 @@ public static Geometry symmetricDifference(Geometry leftGeometry, /** * Indicates if two geometries are equal. * + * See OperatorEquals. + * * @param geometry1 * Geometry. * @param geometry2 @@ -319,6 +355,10 @@ public static boolean equals(Geometry geometry1, Geometry geometry2, return result; } + /** + * See OperatorDisjoint. + * + */ public static boolean disjoint(Geometry geometry1, Geometry geometry2, SpatialReference spatialReference) { OperatorDisjoint op = (OperatorDisjoint) factory @@ -332,6 +372,8 @@ public static boolean disjoint(Geometry geometry1, Geometry geometry2, * Constructs the set-theoretic intersection between an array of geometries * and another geometry. * + * See OperatorIntersection (also for dimension specific intersection). + * * @param inputGeometries * An array of geometry objects. * @param geometry @@ -362,6 +404,8 @@ static Geometry[] intersect(Geometry[] inputGeometries, Geometry geometry, /** * Creates a geometry through intersection between two geometries. * + * See OperatorIntersection. + * * @param geometry1 * The first geometry. * @param intersector @@ -382,6 +426,8 @@ public static Geometry intersect(Geometry geometry1, Geometry intersector, /** * Indicates if one geometry is within another geometry. * + * See OperatorWithin. + * * @param geometry1 * The base geometry that is tested for within relationship to * the other geometry. @@ -404,6 +450,8 @@ public static boolean within(Geometry geometry1, Geometry geometry2, /** * Indicates if one geometry contains another geometry. * + * See OperatorContains. + * * @param geometry1 * The geometry that is tested for the contains relationship to * the other geometry.. @@ -426,6 +474,8 @@ public static boolean contains(Geometry geometry1, Geometry geometry2, /** * Indicates if one geometry crosses another geometry. * + * See OperatorCrosses. + * * @param geometry1 * The geometry to cross. * @param geometry2 @@ -446,6 +496,8 @@ public static boolean crosses(Geometry geometry1, Geometry geometry2, /** * Indicates if one geometry touches another geometry. * + * See OperatorTouches. + * * @param geometry1 * The geometry to touch. * @param geometry2 @@ -466,6 +518,8 @@ public static boolean touches(Geometry geometry1, Geometry geometry2, /** * Indicates if one geometry overlaps another geometry. * + * See OperatorOverlaps. + * * @param geometry1 * The geometry to overlap. * @param geometry2 @@ -486,6 +540,8 @@ public static boolean overlaps(Geometry geometry1, Geometry geometry2, /** * Indicates if the given relation holds for the two geometries. * + * See OperatorRelate. + * * @param geometry1 * The first geometry for the relation. * @param geometry2 @@ -508,6 +564,8 @@ public static boolean relate(Geometry geometry1, Geometry geometry2, /** * Calculates the 2D planar distance between two geometries. * + * See OperatorDistance. + * * @param geometry1 * Geometry. * @param geometry2 @@ -528,6 +586,8 @@ public static double distance(Geometry geometry1, Geometry geometry2, /** * Calculates the clipped geometry from a target geometry using an envelope. * + * See OperatorClip. + * * @param geometry * The geometry to be clipped. * @param envelope @@ -559,6 +619,8 @@ public static Geometry clip(Geometry geometry, Envelope envelope, * part left over after cutting or a cut is bounded to the left and right of * the cutter. * + * See OperatorCut. + * * @param cuttee * The geometry to be cut. * @param cutter @@ -585,19 +647,21 @@ public static Geometry[] cut(Geometry cuttee, Polyline cutter, } return cutsList.toArray(new Geometry[0]); - } - + } /** * Calculates a buffer polygon for each geometry at each of the * corresponding specified distances. It is assumed that all geometries have * the same spatial reference. There is an option to union the * returned geometries. + * + * See OperatorBuffer. + * * @param geometries An array of geometries to be buffered. * @param spatialReference The spatial reference of the geometries. * @param distances The corresponding distances for the input geometries to be buffered. * @param toUnionResults TRUE if all geometries buffered at a given distance are to be unioned into a single polygon. * @return The buffer of the geometries. - * */ + */ public static Polygon[] buffer(Geometry[] geometries, SpatialReference spatialReference, double[] distances, boolean toUnionResults) { @@ -633,11 +697,14 @@ public static Polygon[] buffer(Geometry[] geometries, /** * Calculates a buffer polygon of the geometry as specified by the * distance input. The buffer is implemented in the xy-plane. + * + * See OperatorBuffer + * * @param geometry Geometry to be buffered. * @param spatialReference The spatial reference of the geometry. * @param distance The specified distance for buffer. Same units as the spatial reference. * @return The buffer polygon at the specified distances. - * */ + */ public static Polygon buffer(Geometry geometry, SpatialReference spatialReference, double distance) { double bufferDistance = distance; @@ -652,6 +719,8 @@ public static Polygon buffer(Geometry geometry, /** * Calculates the convex hull geometry. * + * See OperatorConvexHull. + * * @param geometry The input geometry. * @return Returns the convex hull. * @@ -676,6 +745,8 @@ public static Geometry convexHull(Geometry geometry) { /** * Calculates the convex hull. * + * See OperatorConvexHull + * * @param geometries * The input geometry array. * @param b_merge @@ -709,13 +780,15 @@ public static Geometry[] convexHull(Geometry[] geometries, boolean b_merge) { /** * Finds the coordinate of the geometry which is closest to the specified * point. + * + * See OperatorProximity2D. * * @param inputPoint * The point to find the nearest coordinate in the geometry for. * @param geometry * The geometry to consider. * @return Proximity2DResult containing the nearest coordinate. - * */ + */ public static Proximity2DResult getNearestCoordinate(Geometry geometry, Point inputPoint, boolean bTestPolygonInterior) { @@ -730,12 +803,14 @@ public static Proximity2DResult getNearestCoordinate(Geometry geometry, * Finds nearest vertex on the geometry which is closed to the specified * point. * + * See OperatorProximity2D. + * * @param inputPoint * The point to find the nearest vertex of the geometry for. * @param geometry * The geometry to consider. * @return Proximity2DResult containing the nearest vertex. - * */ + */ public static Proximity2DResult getNearestVertex(Geometry geometry, Point inputPoint) { OperatorProximity2D proximity = (OperatorProximity2D) factory @@ -749,6 +824,8 @@ public static Proximity2DResult getNearestVertex(Geometry geometry, * Finds all vertices in the given distance from the specified point, sorted * from the closest to the furthest. * + * See OperatorProximity2D. + * * @param inputPoint * The point to start from. * @param geometry @@ -758,7 +835,7 @@ public static Proximity2DResult getNearestVertex(Geometry geometry, * @param maxVertexCountToReturn * The maximum number number of vertices to return. * @return Proximity2DResult containing the array of nearest vertices. - * */ + */ public static Proximity2DResult[] getNearestVertices(Geometry geometry, Point inputPoint, double searchRadius, int maxVertexCountToReturn) { OperatorProximity2D proximity = (OperatorProximity2D) factory @@ -772,6 +849,8 @@ public static Proximity2DResult[] getNearestVertices(Geometry geometry, /** * Performs the simplify operation on the geometry. + * + * See OperatorSimplify and See OperatorSimplifyOGC. * * @param geometry * The geometry to be simplified. @@ -790,6 +869,8 @@ public static Geometry simplify(Geometry geometry, /** * Checks if the Geometry is simple. * + * See OperatorSimplify. + * * @param geometry * The geometry to be checked. * @param spatialReference diff --git a/src/main/java/com/esri/core/geometry/Operator.java b/src/main/java/com/esri/core/geometry/Operator.java index 20c00787..41f69129 100644 --- a/src/main/java/com/esri/core/geometry/Operator.java +++ b/src/main/java/com/esri/core/geometry/Operator.java @@ -33,7 +33,7 @@ public abstract class Operator { * The operator type enum. */ public enum Type { - Project, // = 10300,// Date: Fri, 5 Sep 2014 11:32:08 -0700 Subject: [PATCH 090/196] add serializable to PointXX and EnvelopeXX --- src/main/java/com/esri/core/geometry/Envelope1D.java | 5 ++++- src/main/java/com/esri/core/geometry/Envelope2D.java | 1 + src/main/java/com/esri/core/geometry/Envelope3D.java | 6 +++++- .../esri/core/geometry/OperatorGeodeticDensifyLocal.java | 2 +- src/main/java/com/esri/core/geometry/Point2D.java | 6 ++++-- src/main/java/com/esri/core/geometry/Point3D.java | 6 +++++- 6 files changed, 20 insertions(+), 6 deletions(-) diff --git a/src/main/java/com/esri/core/geometry/Envelope1D.java b/src/main/java/com/esri/core/geometry/Envelope1D.java index 6f3da52d..993eef36 100644 --- a/src/main/java/com/esri/core/geometry/Envelope1D.java +++ b/src/main/java/com/esri/core/geometry/Envelope1D.java @@ -25,10 +25,13 @@ package com.esri.core.geometry; +import java.io.Serializable; + /** * A 1-dimensional interval. */ -public final class Envelope1D { +public final class Envelope1D implements Serializable { + private static final long serialVersionUID = 1L; public double vmin; diff --git a/src/main/java/com/esri/core/geometry/Envelope2D.java b/src/main/java/com/esri/core/geometry/Envelope2D.java index a6641de9..1a3907a2 100644 --- a/src/main/java/com/esri/core/geometry/Envelope2D.java +++ b/src/main/java/com/esri/core/geometry/Envelope2D.java @@ -33,6 +33,7 @@ */ public final class Envelope2D implements Serializable { private static final long serialVersionUID = 1L; + private final static int XLESSXMIN = 1; // private final int XGREATERXMAX = 2; private final static int YLESSYMIN = 4; diff --git a/src/main/java/com/esri/core/geometry/Envelope3D.java b/src/main/java/com/esri/core/geometry/Envelope3D.java index 18fd6515..1dd38ef6 100644 --- a/src/main/java/com/esri/core/geometry/Envelope3D.java +++ b/src/main/java/com/esri/core/geometry/Envelope3D.java @@ -25,10 +25,14 @@ package com.esri.core.geometry; +import java.io.Serializable; + /** * A class that represents axis parallel 3D rectangle. */ -public final class Envelope3D { +public final class Envelope3D implements Serializable{ + private static final long serialVersionUID = 1L; + public double xmin; public double ymin; diff --git a/src/main/java/com/esri/core/geometry/OperatorGeodeticDensifyLocal.java b/src/main/java/com/esri/core/geometry/OperatorGeodeticDensifyLocal.java index 5b81f8cf..011bc4b2 100644 --- a/src/main/java/com/esri/core/geometry/OperatorGeodeticDensifyLocal.java +++ b/src/main/java/com/esri/core/geometry/OperatorGeodeticDensifyLocal.java @@ -25,7 +25,7 @@ package com.esri.core.geometry; //This is a stub -public class OperatorGeodeticDensifyLocal extends +class OperatorGeodeticDensifyLocal extends OperatorGeodeticDensifyByLength { @Override diff --git a/src/main/java/com/esri/core/geometry/Point2D.java b/src/main/java/com/esri/core/geometry/Point2D.java index cb673ff5..5525626b 100644 --- a/src/main/java/com/esri/core/geometry/Point2D.java +++ b/src/main/java/com/esri/core/geometry/Point2D.java @@ -25,6 +25,7 @@ package com.esri.core.geometry; +import java.io.Serializable; import java.math.BigDecimal; import java.util.Comparator; @@ -33,8 +34,9 @@ * Basic 2D point class. Contains only two double fields. * */ -public final class Point2D { - +public final class Point2D implements Serializable{ + private static final long serialVersionUID = 1L; + public double x; public double y; diff --git a/src/main/java/com/esri/core/geometry/Point3D.java b/src/main/java/com/esri/core/geometry/Point3D.java index f5d343ff..5f509019 100644 --- a/src/main/java/com/esri/core/geometry/Point3D.java +++ b/src/main/java/com/esri/core/geometry/Point3D.java @@ -25,12 +25,16 @@ package com.esri.core.geometry; +import java.io.Serializable; + /** * * Basic 3D point class. * */ -public final class Point3D { +public final class Point3D implements Serializable { + private static final long serialVersionUID = 1L; + public double x; public double y; public double z; From 9695562bee2d2410f604393047086abb86560def Mon Sep 17 00:00:00 2001 From: serg4066 Date: Mon, 8 Sep 2014 14:50:58 -0700 Subject: [PATCH 091/196] a javadoc fix for Envelope2D.intersects (issue #59) --- src/main/java/com/esri/core/geometry/Envelope2D.java | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/esri/core/geometry/Envelope2D.java b/src/main/java/com/esri/core/geometry/Envelope2D.java index 1a3907a2..f2e4ef41 100644 --- a/src/main/java/com/esri/core/geometry/Envelope2D.java +++ b/src/main/java/com/esri/core/geometry/Envelope2D.java @@ -272,8 +272,11 @@ public boolean isIntersectingNE(Envelope2D other) { } /** - * Checks if this envelope intersects the other. - * @return True if this envelope intersects the other. + * Intersects this envelope with the other and stores result in this + * envelope. + * + * @return True if this envelope intersects the other, otherwise sets this + * envelope to empty state and returns False. */ public boolean intersect(Envelope2D other) { if (isEmpty() || other.isEmpty()) From 5324ae50d61d308708581b2a62233d16ff9d3bf5 Mon Sep 17 00:00:00 2001 From: Mike Park Date: Wed, 10 Sep 2014 09:01:16 -0700 Subject: [PATCH 092/196] added developer info and updated distribution management --- pom.xml | 77 +++++++++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 69 insertions(+), 8 deletions(-) mode change 100644 => 100755 pom.xml diff --git a/pom.xml b/pom.xml old mode 100644 new mode 100755 index 24547bbc..f3e49e98 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ com.esri.geometry esri-geometry-api - 1.1.2-SNAPSHOT + 1.2 jar Esri Geometry API for Java @@ -19,12 +19,69 @@ + + + stolstov + Sergey Tolstov + Esri + http://www.esri.com + + developer + + + + abalog + Aaron Balog + Esri + http://www.esri.com + + developer + + + + scm:git:git@github.com:Esri/geometry-api-java.git scm:git:git@github.com:Esri/geometry-api-java.git git@github.com:Esri/geometry-api-java.git + + + release-sign-artifacts + + + performRelease + true + + + + + + org.apache.maven.plugins + maven-gpg-plugin + + + sign-artifacts + verify + + sign + + + + + + + + + + + + ossrh + https://oss.sonatype.org/content/repositories/snapshots + + + UTF-8 @@ -42,13 +99,6 @@ 2.9 - - - org.sonatype.oss - oss-parent - 7 - - org.json @@ -125,6 +175,17 @@ + + org.sonatype.plugins + nexus-staging-maven-plugin + 1.6.2 + true + + ossrh + https://oss.sonatype.org/ + true + + From 6005d9387158bb0ed4ad538611e7aa0e13c069e1 Mon Sep 17 00:00:00 2001 From: Mike Park Date: Wed, 10 Sep 2014 09:26:26 -0700 Subject: [PATCH 093/196] updated to match an even more recent change to release deployment --- pom.xml | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/pom.xml b/pom.xml index f3e49e98..760d2734 100755 --- a/pom.xml +++ b/pom.xml @@ -48,13 +48,7 @@ - release-sign-artifacts - - - performRelease - true - - + release From 6c09743e80509d91f110af0fafb3fcd470a7c682 Mon Sep 17 00:00:00 2001 From: Michael Park Date: Wed, 10 Sep 2014 12:25:13 -0700 Subject: [PATCH 094/196] Update README.md Updated Maven dependency version to 1.2 --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 618f1572..16d10710 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,7 @@ The project is also available as a [Maven](http://maven.apache.org/) dependency: com.esri.geometry esri-geometry-api - 1.1 + 1.2 ``` From eb7a0d105ab345b6a880a1a1d568aef4fcc97afa Mon Sep 17 00:00:00 2001 From: Aaron Balog Date: Mon, 27 Oct 2014 09:32:09 -0700 Subject: [PATCH 095/196] Make decimal separator locale independent for JSON --- src/main/java/com/esri/core/geometry/StringUtils.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/esri/core/geometry/StringUtils.java b/src/main/java/com/esri/core/geometry/StringUtils.java index 6797d94e..f859e9d8 100644 --- a/src/main/java/com/esri/core/geometry/StringUtils.java +++ b/src/main/java/com/esri/core/geometry/StringUtils.java @@ -22,6 +22,7 @@ email: contracts@esri.com */ package com.esri.core.geometry; +import java.util.Locale; class StringUtils { @@ -35,7 +36,7 @@ static void appendDouble(double value, int precision, String format = "%." + precision + "g"; - String str_dbl = String.format(format, value); + String str_dbl = String.format(Locale.US, format, value); boolean b_found_dot = false; boolean b_found_exponent = false; @@ -69,7 +70,7 @@ static void appendDoubleF(double value, int decimals, String format = "%." + decimals + "f"; - String str_dbl = String.format(format, value); + String str_dbl = String.format(Locale.US, format, value); boolean b_found_dot = false; From ad5fb8451e49ce334e11f2f5789dba0d7963bb2f Mon Sep 17 00:00:00 2001 From: serg4066 Date: Thu, 11 Dec 2014 15:36:35 -0800 Subject: [PATCH 096/196] fixes for release 1.2.1 --- .../java/com/esri/core/geometry/Bufferer.java | 4 +- .../esri/core/geometry/CrackAndCluster.java | 18 +- .../java/com/esri/core/geometry/Cracker.java | 111 +- .../java/com/esri/core/geometry/Cutter.java | 2 +- .../com/esri/core/geometry/EditShape.java | 83 +- .../java/com/esri/core/geometry/Envelope.java | 30 + .../geometry/Envelope2DIntersectorImpl.java | 1 + .../java/com/esri/core/geometry/Geometry.java | 43 +- .../core/geometry/GeometryAccelerators.java | 25 +- .../esri/core/geometry/GeometryException.java | 12 +- .../com/esri/core/geometry/InternalUtils.java | 202 +- .../esri/core/geometry/JsonStringWriter.java | 740 +++---- .../com/esri/core/geometry/JsonWriter.java | 76 +- .../java/com/esri/core/geometry/Line.java | 66 +- .../com/esri/core/geometry/MapGeometry.java | 2 +- .../com/esri/core/geometry/MathUtils.java | 69 +- .../com/esri/core/geometry/MultiPath.java | 5 + .../com/esri/core/geometry/MultiPathImpl.java | 72 +- .../com/esri/core/geometry/MultiPoint.java | 5 + .../geometry/MultiVertexGeometryImpl.java | 39 +- .../esri/core/geometry/NonSimpleResult.java | 87 +- .../geometry/OperatorSimplifyLocalHelper.java | 27 +- .../geometry/PairwiseIntersectorImpl.java | 249 +++ .../geometry/PlaneSweepCrackerHelper.java | 88 +- .../java/com/esri/core/geometry/Point.java | 14 + .../core/geometry/PointInPolygonHelper.java | 111 +- .../java/com/esri/core/geometry/Polygon.java | 35 +- .../com/esri/core/geometry/PolygonUtils.java | 34 +- .../java/com/esri/core/geometry/Polyline.java | 9 + .../com/esri/core/geometry/QuadTreeImpl.java | 15 +- .../core/geometry/RelationalOperations.java | 975 ++++----- .../geometry/RelationalOperationsMatrix.java | 1798 +++++++++++------ .../core/geometry/SegmentIntersector.java | 20 +- .../com/esri/core/geometry/Simplificator.java | 13 +- .../com/esri/core/geometry/TopoGraph.java | 174 +- .../core/geometry/TopologicalOperations.java | 89 +- .../com/esri/core/geometry/TestEditShape.java | 8 +- .../com/esri/core/geometry/TestGeodetic.java | 9 +- ...omToJSonExportSRFromWkiOrWkt_CR181369.java | 22 +- .../com/esri/core/geometry/TestPoint.java | 18 + .../com/esri/core/geometry/TestPolygon.java | 44 +- .../com/esri/core/geometry/TestQuadTree.java | 4 + .../com/esri/core/geometry/TestRelation.java | 1082 +++++++--- .../com/esri/core/geometry/TestSimplify.java | 17 + 44 files changed, 4381 insertions(+), 2166 deletions(-) create mode 100644 src/main/java/com/esri/core/geometry/PairwiseIntersectorImpl.java diff --git a/src/main/java/com/esri/core/geometry/Bufferer.java b/src/main/java/com/esri/core/geometry/Bufferer.java index a8517844..587287fd 100644 --- a/src/main/java/com/esri/core/geometry/Bufferer.java +++ b/src/main/java/com/esri/core/geometry/Bufferer.java @@ -996,7 +996,7 @@ private int bufferClosedPath_(Geometry input_geom, int ipath, EditShape edit_shape = new EditShape(); int geom = edit_shape.addPathFromMultiPath((MultiPath) input_geom, ipath, true); - edit_shape.filterClosePoints(m_filter_tolerance, false); + edit_shape.filterClosePoints(m_filter_tolerance, false, false); if (edit_shape.getPointCount(geom) < 2) {// Got degenerate output. // Wither bail out or // produce a circle. @@ -1429,7 +1429,7 @@ && isGap_(pt_before_before, pt_current, edit_shape.setXY(iprev, pt_middle); } - edit_shape.filterClosePoints(m_filter_tolerance, false); + edit_shape.filterClosePoints(m_filter_tolerance, false, false); if (!b_filtered) break; diff --git a/src/main/java/com/esri/core/geometry/CrackAndCluster.java b/src/main/java/com/esri/core/geometry/CrackAndCluster.java index 2a5c6286..6f6d8247 100644 --- a/src/main/java/com/esri/core/geometry/CrackAndCluster.java +++ b/src/main/java/com/esri/core/geometry/CrackAndCluster.java @@ -30,6 +30,7 @@ final class CrackAndCluster { private EditShape m_shape = null; private ProgressTracker m_progressTracker = null; private double m_tolerance; + private boolean m_filter_degenerate_segments = true; private CrackAndCluster(ProgressTracker progressTracker) { m_progressTracker = progressTracker; @@ -60,10 +61,11 @@ else if (rank2 < rank1) } public static boolean execute(EditShape shape, double tolerance, - ProgressTracker progressTracker) { + ProgressTracker progressTracker, boolean filter_degenerate_segments) { CrackAndCluster cracker = new CrackAndCluster(progressTracker); cracker.m_shape = shape; cracker.m_tolerance = tolerance; + cracker.m_filter_degenerate_segments = filter_degenerate_segments; return cracker._do(); } @@ -121,12 +123,14 @@ private boolean _do() { // clamp them // together. bChanged |= bClustered; - - boolean bFiltered = (m_shape.filterClosePoints( - tolerance_for_clustering, true) != 0); // remove all - // degenerate - // segments. - bChanged |= bFiltered; + + if (m_filter_degenerate_segments) { + boolean bFiltered = (m_shape.filterClosePoints( + tolerance_for_clustering, true, false) != 0); // remove all + // degenerate + // segments. + bChanged |= bFiltered; + } boolean b_cracked = false; if (iter == 0 diff --git a/src/main/java/com/esri/core/geometry/Cracker.java b/src/main/java/com/esri/core/geometry/Cracker.java index 98b4a909..cd8b9a72 100644 --- a/src/main/java/com/esri/core/geometry/Cracker.java +++ b/src/main/java/com/esri/core/geometry/Cracker.java @@ -30,7 +30,7 @@ * Finds and splits all intersecting segments. Used by the TopoGraph and * Simplify. */ -class Cracker { +final class Cracker { private EditShape m_shape; private ProgressTracker m_progress_tracker; private NonSimpleResult m_non_simple_result; @@ -39,7 +39,19 @@ class Cracker { private SweepComparator m_sweep_comparator; private boolean m_bAllowCoincident; - boolean crackBruteForce_() { + private Segment getSegment_(int vertex, Line lineHelper) { + Segment seg = m_shape.getSegment(vertex); + if (seg == null) { + if (!m_shape.queryLineConnector(vertex, lineHelper)) + return null; + + seg = (Segment)lineHelper; + } + + return seg; + } + + private boolean crackBruteForce_() { EditShape.VertexIterator iter_1 = m_shape.queryVertexIterator(false); boolean b_cracked = false; Line line_1 = new Line(); @@ -59,24 +71,25 @@ boolean crackBruteForce_() { int GT_1 = m_shape.getGeometryType(iter_1.currentGeometry()); Segment seg_1 = null; + boolean seg_1_zero = false; if (!Geometry.isPoint(GT_1)) { - seg_1 = m_shape.getSegment(vertex_1); - - if (seg_1 == null) { - if (!m_shape.queryLineConnector(vertex_1, line_1)) - continue; - seg_1 = line_1; - line_1.queryEnvelope2D(seg_1_env); - } else { - seg_1.queryEnvelope2D(seg_1_env); - } - + seg_1 = getSegment_(vertex_1, line_1); + if (seg_1 == null) + continue; + + seg_1.queryEnvelope2D(seg_1_env); seg_1_env.inflate(m_tolerance, m_tolerance); if (seg_1.isDegenerate(m_tolerance))// do not crack with // degenerate segments { - continue; + if (seg_1.isDegenerate(0)) { + seg_1_zero = true; + seg_1 = null; + } + else { + continue; + } } } @@ -90,20 +103,24 @@ boolean crackBruteForce_() { int GT_2 = m_shape.getGeometryType(iter_2.currentGeometry()); Segment seg_2 = null; + boolean seg_2_zero = false; if (!Geometry.isPoint(GT_2)) { - seg_2 = m_shape.getSegment(vertex_2); + seg_2 = getSegment_(vertex_2, line_2); if (seg_2 == null) { - if (!m_shape.queryLineConnector(vertex_2, line_2)) - continue; - seg_2 = line_2; - line_2.queryEnvelope2D(seg_2_env); - } else - seg_2.queryEnvelope2D(seg_2_env); - + continue; + } + + seg_2.queryEnvelope2D(seg_2_env); if (seg_2.isDegenerate(m_tolerance))// do not crack with // degenerate segments { - continue; + if (seg_2.isDegenerate(0)) { + seg_2_zero = true; + seg_2 = null; + } + else { + continue; + } } } @@ -141,8 +158,29 @@ boolean crackBruteForce_() { if (split_count_1 > 0) { m_shape.splitSegment_(vertex_1, segment_intersector, 0, true); - m_shape.setPoint(vertex_2, - segment_intersector.getResultPoint()); + if (seg_2_zero) { + //seg_2 was zero length. Need to change all coincident points + //segment at vertex_2 is dzero length, change all attached zero length segments + int v_to = -1; + for (int v = m_shape.getNextVertex(vertex_2); v != -1 && v != vertex_2; v = m_shape.getNextVertex(v)) + { + seg_2 = getSegment_(v, line_2); + v_to = v; + if (seg_2 == null || !seg_2.isDegenerate(0)) + break; + } + //change from vertex_2 to v_to (inclusive). + for (int v = vertex_2; v != -1; v = m_shape.getNextVertex(v)) + { + m_shape.setPoint(v, segment_intersector.getResultPoint()); + if (v == v_to) + break; + } + } + else { + m_shape.setPoint(vertex_2, + segment_intersector.getResultPoint()); + } } segment_intersector.clear(); } @@ -160,8 +198,27 @@ boolean crackBruteForce_() { if (split_count_2 > 0) { m_shape.splitSegment_(vertex_2, segment_intersector, 0, true); - m_shape.setPoint(vertex_1, - segment_intersector.getResultPoint()); + if (seg_1_zero) { + //seg_1 was zero length. Need to change all coincident points + //segment at vertex_2 is dzero length, change all attached zero length segments + int v_to = -1; + for (int v = m_shape.getNextVertex(vertex_1); v != -1 && v != vertex_1; v = m_shape.getNextVertex(v)) { + seg_2 = getSegment_(v, line_2);//using here seg_2 for seg_1 + v_to = v; + if (seg_2 == null || !seg_2.isDegenerate(0)) + break; + } + //change from vertex_2 to v_to (inclusive). + for (int v = vertex_1; v != -1; v = m_shape.getNextVertex(v)) { + m_shape.setPoint(v, segment_intersector.getResultPoint()); + if (v == v_to) + break; + } + } + else { + m_shape.setPoint(vertex_1, + segment_intersector.getResultPoint()); + } } segment_intersector.clear(); } diff --git a/src/main/java/com/esri/core/geometry/Cutter.java b/src/main/java/com/esri/core/geometry/Cutter.java index f3f17efe..d575e1de 100644 --- a/src/main/java/com/esri/core/geometry/Cutter.java +++ b/src/main/java/com/esri/core/geometry/Cutter.java @@ -116,7 +116,7 @@ static EditShape CutPolyline(boolean bConsiderTouch, Polyline cuttee, EditShape editShape = new EditShape(); int cutteeHandle = editShape.addGeometry(cuttee); int cutterHandle = editShape.addGeometry(cutter); - CrackAndCluster.execute(editShape, tolerance, progressTracker); + CrackAndCluster.execute(editShape, tolerance, progressTracker, true); int order = 0; int orderIndex = editShape.createUserIndex(); diff --git a/src/main/java/com/esri/core/geometry/EditShape.java b/src/main/java/com/esri/core/geometry/EditShape.java index 84557893..a85ca5a2 100644 --- a/src/main/java/com/esri/core/geometry/EditShape.java +++ b/src/main/java/com/esri/core/geometry/EditShape.java @@ -25,6 +25,8 @@ import java.util.ArrayList; +import com.esri.core.geometry.Geometry.GeometryType; + /** * A helper geometry structure that can store MultiPoint, Polyline, Polygon * geometries in linked lists. It allows constant time manipulation of geometry @@ -204,7 +206,7 @@ int newPath_(int geom) { // m_path_index_list.set(index + 4, -1);//first vertex handle // m_path_index_list.set(index + 5, -1);//last vertex handle m_path_index_list.setField(index, 6, 0);// path flags - m_path_index_list.setField(index, 7, geom);// geometry handle + setPathGeometry_(index, geom); if (pindex >= m_path_areas.size()) { int sz = pindex < 16 ? 16 : (pindex * 3) / 2; m_path_areas.resize(sz); @@ -331,10 +333,25 @@ Point getHelperPoint_() { m_helper_point = new Point(m_vertices.getDescription()); return m_helper_point; } + + void setFillRule(int geom, int rule) { + int t = m_geometry_index_list.getField(geom, 2); + t &= ~(0x8000000); + t |= rule == Polygon.FillRule.enumFillRuleWinding ? 0x8000000 : 0; + m_geometry_index_list.setField(geom, 2, t);//fill rule combined with geometry type + } + int getFillRule(int geom) { + int t = m_geometry_index_list.getField(geom, 2); + return (t & 0x8000000) != 0 ? Polygon.FillRule.enumFillRuleWinding : Polygon.FillRule.enumFillRuleOddEven; + } + int addMultiPath_(MultiPath multi_path) { int newgeom = createGeometry(multi_path.getType(), multi_path.getDescription()); + if (multi_path.getType() == Geometry.Type.Polygon) + setFillRule(newgeom, ((Polygon)multi_path).getFillRule()); + appendMultiPath_(newgeom, multi_path); return newgeom; } @@ -585,6 +602,8 @@ int addPathFromMultiPath(MultiPath multi_path, int ipath, boolean as_polygon) { : Geometry.Type.Polyline, multi_path.getDescription()); MultiPathImpl mp_impl = (MultiPathImpl) multi_path._getImpl(); + if (multi_path.getPathSize(ipath) < 2) + return newgeom; //return empty geometry // m_vertices->reserve_rounded(m_vertices->get_point_count() + // multi_path.get_path_size(ipath));//ensure reallocation happens by @@ -821,7 +840,7 @@ int getPrevGeometry(int geom) { // Returns the type of the Geometry. int getGeometryType(int geom) { - return m_geometry_index_list.getField(geom, 2); + return m_geometry_index_list.getField(geom, 2) & 0x7FFFFFFF; } // Sets value to the given user index on a geometry. @@ -898,10 +917,13 @@ int getPathCount(int geom) { // 0 if no segments have been removed. // When b_remove_last_vertices and the result path is < 3 for polygon or < 2 // for polyline, it'll be removed. - int filterClosePoints(double tolerance, boolean b_remove_last_vertices) { + int filterClosePoints(double tolerance, boolean b_remove_last_vertices, boolean only_polygons) { int res = 0; for (int geometry = getFirstGeometry(); geometry != -1; geometry = getNextGeometry(geometry)) { - if (!Geometry.isMultiPath(getGeometryType(geometry))) + int gt = getGeometryType(geometry); + if (!Geometry.isMultiPath(gt)) + continue; + if (only_polygons && gt != GeometryType.Polygon) continue; boolean b_polygon = getGeometryType(geometry) == Geometry.GeometryType.Polygon; @@ -1339,14 +1361,18 @@ void setWeight(int vertex, double weight) { } int vindex = getVertexIndex(vertex); + if (vindex >= m_weights.size()) { + m_weights.resize(vindex + 1, 1.0); + } + m_weights.write(vindex, weight); } double getWeight(int vertex) { - if (m_weights == null) - return 1.0; - int vindex = getVertexIndex(vertex); + if (m_weights == null || vindex >= m_weights.size()) + return 1.0; + return m_weights.read(vindex); } @@ -2040,8 +2066,7 @@ void interpolateAttributesForClosedPath_(int semantics, int path, } cumulative_length += segment_length; double t = cumulative_length / sub_length; - prev_interpolated_attribute = (1.0 - t) * from_attribute + t - * to_attribute; + prev_interpolated_attribute = MathUtils.lerp(from_attribute, to_attribute, t); } return; @@ -2280,5 +2305,43 @@ boolean hasPointFeatures() } return false; } - + + void swapGeometry(int geom1, int geom2) + { + int first_path1 = getFirstPath(geom1); + int first_path2 = getFirstPath(geom2); + int last_path1 = getLastPath(geom1); + int last_path2 = getLastPath(geom2); + + for (int path = getFirstPath(geom1); path != -1; path = getNextPath(path)) + { + setPathGeometry_(path, geom2); + } + + for (int path = getFirstPath(geom2); path != -1; path = getNextPath(path)) + { + setPathGeometry_(path, geom1); + } + + setFirstPath_(geom1, first_path2); + setFirstPath_(geom2, first_path1); + setLastPath_(geom1, last_path2); + setLastPath_(geom2, last_path1); + + int vc1 = getPointCount(geom1); + int pc1 = getPathCount(geom1); + int vc2 = getPointCount(geom2); + int pc2 = getPathCount(geom2); + + setGeometryVertexCount_(geom1, vc2); + setGeometryVertexCount_(geom2, vc1); + setGeometryPathCount_(geom1, pc2); + setGeometryPathCount_(geom2, pc1); + + int gt1 = m_geometry_index_list.getField(geom1, 2); + int gt2 = m_geometry_index_list.getField(geom2, 2); + m_geometry_index_list.setField(geom1, 2, gt2); + m_geometry_index_list.setField(geom2, 2, gt1); + } + } diff --git a/src/main/java/com/esri/core/geometry/Envelope.java b/src/main/java/com/esri/core/geometry/Envelope.java index 0675a1e8..9d74440b 100644 --- a/src/main/java/com/esri/core/geometry/Envelope.java +++ b/src/main/java/com/esri/core/geometry/Envelope.java @@ -1113,4 +1113,34 @@ public void setYMax(double y) { public Geometry getBoundary() { return Boundary.calculate(this, null); } + + @Override + public void replaceNaNs(int semantics, double value) { + addAttribute(semantics); + if (isEmpty()) + return; + + int ncomps = VertexDescription.getComponentCount(semantics); + for (int i = 0; i < ncomps; i++) { + Envelope1D interval = queryInterval(semantics, i); + if (interval.isEmpty()) { + interval.vmin = value; + interval.vmax = value; + setInterval(semantics, i, interval); + } + } + } + + /** + * The output of this method can be only used for debugging. It is subject to change without notice. + */ + @Override + public String toString() { + if (isEmpty()) + return "Envelope: []"; + + String s = "Envelope: [" + m_envelope.xmin + ", " + m_envelope.ymin + ", " + m_envelope.xmin + ", " + m_envelope.ymin +"]"; + return s; + } + } diff --git a/src/main/java/com/esri/core/geometry/Envelope2DIntersectorImpl.java b/src/main/java/com/esri/core/geometry/Envelope2DIntersectorImpl.java index 5fdb9fc3..d9430f96 100644 --- a/src/main/java/com/esri/core/geometry/Envelope2DIntersectorImpl.java +++ b/src/main/java/com/esri/core/geometry/Envelope2DIntersectorImpl.java @@ -43,6 +43,7 @@ void startConstruction() { m_elements_red = new AttributeStreamOfInt32(0); m_envelopes_red = new ArrayList(0); } else { + m_elements_red.resizePreserveCapacity(0); m_envelopes_red.clear(); } diff --git a/src/main/java/com/esri/core/geometry/Geometry.java b/src/main/java/com/esri/core/geometry/Geometry.java index d4cd9952..6e16c0e6 100644 --- a/src/main/java/com/esri/core/geometry/Geometry.java +++ b/src/main/java/com/esri/core/geometry/Geometry.java @@ -50,8 +50,7 @@ public abstract class Geometry implements Serializable { /** * Geometry types */ - static interface GeometryType { - + static public interface GeometryType { public final static int Unknown = 0; public final static int Point = 1 + 0x20; // points public final static int Line = 2 + 0x40 + 0x100; // lines, segment @@ -59,10 +58,10 @@ static interface GeometryType { final static int EllipticArc = 4 + 0x40 + 0x100; // lines, segment public final static int Envelope = 5 + 0x40 + 0x80; // lines, areas public final static int MultiPoint = 6 + 0x20 + 0x200; // points, - // multivertex + // multivertex public final static int Polyline = 7 + 0x40 + 0x200 + 0x400; // lines, // multivertex, - // multipath + // multipath public final static int Polygon = 8 + 0x40 + 0x80 + 0x200 + 0x400; } @@ -509,7 +508,15 @@ public Geometry copy() { * boundary is a Multi_point consisting of path endpoints. For Multi_point * and Point NULL is returned. */ - public abstract Geometry getBoundary(); + public abstract Geometry getBoundary(); + + /** + * Replaces NaNs in the attribute with the given value. + * If the geometry is not empty, it adds the attribute if geometry does not have it yet, and replaces the values. + * If the geometry is empty, it adds the attribute and does not set any values. + * + */ + public abstract void replaceNaNs(int semantics, double value); static Geometry _clone(Geometry src) { Geometry geom = src.createInstance(); @@ -578,4 +585,30 @@ public String toString() { } } + /** + *Returns count of geometry vertices: + *1 for Point, 4 for Envelope, get_point_count for MultiVertexGeometry types, + *2 for segment types + *Returns 0 if geometry is empty. + */ + public static int vertex_count(Geometry geom) { + Geometry.Type gt = geom.getType(); + if (Geometry.isMultiVertex(gt.value())) + return ((MultiVertexGeometry)geom).getPointCount(); + + if (geom.isEmpty()) + return 0; + + if (gt == Geometry.Type.Envelope) + return 4; + + if (gt == Geometry.Type.Point) + return 1; + + if (Geometry.isSegment(gt.value())) + return 2; + + throw new GeometryException("missing type"); + } + } diff --git a/src/main/java/com/esri/core/geometry/GeometryAccelerators.java b/src/main/java/com/esri/core/geometry/GeometryAccelerators.java index 18140ee3..138b134e 100644 --- a/src/main/java/com/esri/core/geometry/GeometryAccelerators.java +++ b/src/main/java/com/esri/core/geometry/GeometryAccelerators.java @@ -29,7 +29,7 @@ class GeometryAccelerators { private RasterizedGeometry2D m_rasterizedGeometry; private QuadTreeImpl m_quad_tree; - private ArrayList m_path_envelopes; + private QuadTreeImpl m_quad_tree_for_paths; public RasterizedGeometry2D getRasterizedGeometry() { return m_rasterizedGeometry; @@ -39,8 +39,8 @@ public QuadTreeImpl getQuadTree() { return m_quad_tree; } - public ArrayList getPathEnvelopes() { - return m_path_envelopes; + public QuadTreeImpl getQuadTreeForPaths() { + return m_quad_tree_for_paths; } void _setRasterizedGeometry(RasterizedGeometry2D rg) { @@ -51,9 +51,7 @@ void _setQuadTree(QuadTreeImpl quad_tree) { m_quad_tree = quad_tree; } - void _setPathEnvelopes(ArrayList pe) { - m_path_envelopes = pe; - } + void _setQuadTreeForPaths(QuadTreeImpl quad_tree) { m_quad_tree_for_paths = quad_tree; } static boolean canUseRasterizedGeometry(Geometry geom) { if (geom.isEmpty() @@ -77,12 +75,13 @@ static boolean canUseQuadTree(Geometry geom) { return true; } - static boolean canUsePathEnvelopes(Geometry geom) { - if (geom.isEmpty() - || !(geom.getType() == Geometry.Type.Polyline || geom.getType() == Geometry.Type.Polygon)) { - return false; - } + static boolean canUseQuadTreeForPaths(Geometry geom) { + if (geom.isEmpty() || !(geom.getType() == Geometry.Type.Polyline || geom.getType() == Geometry.Type.Polygon)) + return false; - return true; - } + if (((MultiVertexGeometry) geom).getPointCount() < 20) + return false; + + return true; + } } diff --git a/src/main/java/com/esri/core/geometry/GeometryException.java b/src/main/java/com/esri/core/geometry/GeometryException.java index 07b11ffd..41ea2984 100644 --- a/src/main/java/com/esri/core/geometry/GeometryException.java +++ b/src/main/java/com/esri/core/geometry/GeometryException.java @@ -31,10 +31,6 @@ public class GeometryException extends RuntimeException { private static final long serialVersionUID = 1L; - /** - * The internal code for geometry exception. - */ - public int internalCode; /** * Constructs a Geometry Exception with the given error string/message. @@ -42,14 +38,8 @@ public class GeometryException extends RuntimeException { * @param str * - The error string. */ - GeometryException(String str) { - super(str); - internalCode = 0; - } - - GeometryException(String str, int sgCode) { + public GeometryException(String str) { super(str); - internalCode = sgCode; } static GeometryException GeometryInternalError() { diff --git a/src/main/java/com/esri/core/geometry/InternalUtils.java b/src/main/java/com/esri/core/geometry/InternalUtils.java index 90c84f48..42bd8362 100644 --- a/src/main/java/com/esri/core/geometry/InternalUtils.java +++ b/src/main/java/com/esri/core/geometry/InternalUtils.java @@ -26,7 +26,7 @@ import java.util.ArrayList; -class InternalUtils { +final class InternalUtils { // p0 and p1 have to be on left/right boundary of fullRange2D (since this // fuction can be called recursively, p0 or p1 can also be fullRange2D @@ -243,6 +243,7 @@ static QuadTreeImpl buildQuadTree(MultiPathImpl multipathImpl) { SegmentIteratorImpl seg_iter = multipathImpl.querySegmentIterator(); Envelope2D boundingbox = new Envelope2D(); boolean resized_extent = false; + while (seg_iter.nextPath()) { while (seg_iter.hasNextSegment()) { Segment segment = seg_iter.nextSegment(); @@ -306,6 +307,48 @@ static QuadTreeImpl buildQuadTree(MultiPathImpl multipathImpl, return quad_tree_impl; } + static QuadTreeImpl buildQuadTreeForPaths(MultiPathImpl multipathImpl) + { + Envelope2D extent = new Envelope2D(); + multipathImpl.queryLooseEnvelope2D(extent); + if (extent.isEmpty()) + return null; + + QuadTreeImpl quad_tree_impl = new QuadTreeImpl(extent, 8); + int hint_index = -1; + Envelope2D boundingbox = new Envelope2D(); + + boolean resized_extent = false; + do + { + for (int ipath = 0, npaths = multipathImpl.getPathCount(); ipath < npaths; ipath++) + { + multipathImpl.queryPathEnvelope2D(ipath, boundingbox); + hint_index = quad_tree_impl.insert(ipath, boundingbox, hint_index); + + if (hint_index == -1) + { + if (resized_extent) + throw GeometryException.GeometryInternalError(); + + //This is usually happens because esri shape buffer contains geometry extent which is slightly different from the true extent. + //Recalculate extent + multipathImpl.calculateEnvelope2D(extent, false); + resized_extent = true; + quad_tree_impl.reset(extent, 8); + break; //break the for loop + } + else + { + resized_extent = false; + } + } + + } while(resized_extent); + + return quad_tree_impl; + } + static QuadTreeImpl buildQuadTree(MultiPointImpl multipointImpl) { Envelope2D extent = new Envelope2D(); multipointImpl.queryLooseEnvelope2D(extent); @@ -431,115 +474,94 @@ static Envelope2DIntersectorImpl getEnvelope2DIntersector( return intersector; } - static Envelope2DIntersectorImpl getEnvelope2DIntersectorForParts( - MultiPathImpl multipathImplA, MultiPathImpl multipathImplB, - double tolerance, boolean bExteriorOnlyA, boolean bExteriorOnlyB) { - int type_a = multipathImplA.getType().value(); - int type_b = multipathImplB.getType().value(); + static Envelope2DIntersectorImpl getEnvelope2DIntersectorForParts( + MultiPathImpl multipathImplA, MultiPathImpl multipathImplB, + double tolerance, boolean bExteriorOnlyA, boolean bExteriorOnlyB) { + int type_a = multipathImplA.getType().value(); + int type_b = multipathImplB.getType().value(); - Envelope2D env_a = new Envelope2D(), env_b = new Envelope2D(); - multipathImplA.queryLooseEnvelope2D(env_a); - multipathImplB.queryLooseEnvelope2D(env_b); - env_a.inflate(tolerance, tolerance); - env_b.inflate(tolerance, tolerance); + Envelope2D env_a = new Envelope2D(), env_b = new Envelope2D(); + multipathImplA.queryLooseEnvelope2D(env_a); + multipathImplB.queryLooseEnvelope2D(env_b); + env_a.inflate(tolerance, tolerance); + env_b.inflate(tolerance, tolerance); - Envelope2D envInter = new Envelope2D(); - envInter.setCoords(env_a); - envInter.intersect(env_b); - - GeometryAccelerators accel_a = multipathImplA._getAccelerators(); - ArrayList path_envelopes_a = null; - - if (accel_a != null) { - path_envelopes_a = accel_a.getPathEnvelopes(); - } + Envelope2D envInter = new Envelope2D(); + envInter.setCoords(env_a); + envInter.intersect(env_b); - Envelope2DIntersectorImpl intersector = new Envelope2DIntersectorImpl(); - intersector.setTolerance(tolerance); + Envelope2DIntersectorImpl intersector = new Envelope2DIntersectorImpl(); + intersector.setTolerance(tolerance); - boolean b_found_red = false; - intersector.startRedConstruction(); - for (int ipath_a = 0; ipath_a < multipathImplA.getPathCount(); ipath_a++) { - if (bExteriorOnlyA && type_a == Geometry.GeometryType.Polygon - && !multipathImplA.isExteriorRing(ipath_a)) { - continue; - } + boolean b_found_red = false; + intersector.startRedConstruction(); + for (int ipath_a = 0, npaths = multipathImplA.getPathCount(); ipath_a < npaths; ipath_a++) { + if (bExteriorOnlyA && type_a == Geometry.GeometryType.Polygon && !multipathImplA.isExteriorRing(ipath_a)) + continue; - if (path_envelopes_a != null) { - Envelope2D env = path_envelopes_a.get(ipath_a); + multipathImplA.queryPathEnvelope2D(ipath_a, env_a); - if (!env.isIntersecting(envInter)) { - continue; - } + if (!env_a.isIntersecting(envInter)) + continue; - b_found_red = true; - intersector.addRedEnvelope(ipath_a, env); - } else { - multipathImplA.queryPathEnvelope2D(ipath_a, env_a); + b_found_red = true; + intersector.addRedEnvelope(ipath_a, env_a); + } + intersector.endRedConstruction(); - if (!env_a.isIntersecting(envInter)) { - continue; - } + if (!b_found_red) + return null; - b_found_red = true; - Envelope2D env = new Envelope2D(); - env.setCoords(env_a); - intersector.addRedEnvelope(ipath_a, env); - } - } - intersector.endRedConstruction(); + boolean b_found_blue = false; + intersector.startBlueConstruction(); + for (int ipath_b = 0, npaths = multipathImplB.getPathCount(); ipath_b < npaths; ipath_b++) { + if (bExteriorOnlyB && type_b == Geometry.GeometryType.Polygon && !multipathImplB.isExteriorRing(ipath_b)) + continue; - if (!b_found_red) { - return null; - } + multipathImplB.queryPathEnvelope2D(ipath_b, env_b); - GeometryAccelerators accel_b = multipathImplB._getAccelerators(); - ArrayList path_envelopes_b = null; + if (!env_b.isIntersecting(envInter)) + continue; - if (accel_b != null) { - path_envelopes_b = accel_b.getPathEnvelopes(); - } + b_found_blue = true; + intersector.addBlueEnvelope(ipath_b, env_b); + } + intersector.endBlueConstruction(); - boolean b_found_blue = false; - intersector.startBlueConstruction(); - for (int ipath_b = 0; ipath_b < multipathImplB.getPathCount(); ipath_b++) { - if (bExteriorOnlyB && type_b == Geometry.GeometryType.Polygon - && !multipathImplB.isExteriorRing(ipath_b)) { - continue; - } + if (!b_found_blue) + return null; - if (path_envelopes_b != null) { - Envelope2D env = path_envelopes_b.get(ipath_b); + return intersector; + } - if (!env.isIntersecting(envInter)) { - continue; - } + static boolean isWeakSimple(MultiVertexGeometry geom, double tol) { + return ((MultiVertexGeometryImpl) geom._getImpl()).getIsSimple(tol) > 0; + } + + static QuadTree buildQuadTreeForOnePath(MultiPathImpl multipathImpl, int path) { + Envelope2D extent = new Envelope2D(); + multipathImpl.queryLoosePathEnvelope2D(path, extent); + QuadTree quad_tree = new QuadTree(extent, 8); + int hint_index = -1; + Envelope2D boundingbox = new Envelope2D(); + SegmentIteratorImpl seg_iter = multipathImpl.querySegmentIterator(); - b_found_blue = true; - intersector.addBlueEnvelope(ipath_b, env); - } else { - multipathImplB.queryPathEnvelope2D(ipath_b, env_b); + seg_iter.resetToPath(path); + if (seg_iter.nextPath()) { + while (seg_iter.hasNextSegment()) { + Segment segment = seg_iter.nextSegment(); + int index = seg_iter.getStartPointIndex(); + segment.queryLooseEnvelope2D(boundingbox); + hint_index = quad_tree.insert(index, boundingbox, hint_index); - if (!env_b.isIntersecting(envInter)) { - continue; + if (hint_index == -1) { + throw new GeometryException("internal error"); } - - b_found_blue = true; - Envelope2D env = new Envelope2D(); - env.setCoords(env_b); - intersector.addBlueEnvelope(ipath_b, env); } } - intersector.endBlueConstruction(); - if (!b_found_blue) { - return null; - } - - return intersector; - } - - static boolean isWeakSimple(MultiVertexGeometry geom, double tol) { - return ((MultiVertexGeometryImpl) geom._getImpl()).getIsSimple(tol) > 0; + return quad_tree; } + } + diff --git a/src/main/java/com/esri/core/geometry/JsonStringWriter.java b/src/main/java/com/esri/core/geometry/JsonStringWriter.java index 135b8463..6947f5a1 100644 --- a/src/main/java/com/esri/core/geometry/JsonStringWriter.java +++ b/src/main/java/com/esri/core/geometry/JsonStringWriter.java @@ -25,374 +25,374 @@ final class JsonStringWriter extends JsonWriter { - @Override - Object getJson() { - next_(Action.accept); - return m_jsonString.toString(); - } - - @Override - void startObject() { - next_(Action.addContainer); - m_jsonString.append('{'); - m_functionStack.add(State.objectStart); - } - - @Override - void startArray() { - next_(Action.addContainer); - m_jsonString.append('['); - m_functionStack.add(State.arrayStart); - } - - @Override - void endObject() { - next_(Action.popObject); - m_jsonString.append('}'); - } - - @Override - void endArray() { - next_(Action.popArray); - m_jsonString.append(']'); - } - - @Override - void addPairObject(String fieldName) { - next_(Action.addPair); - appendQuote_(fieldName); - m_jsonString.append(":"); - addValueObject_(); - } - - @Override - void addPairArray(String fieldName) { - next_(Action.addPair); - appendQuote_(fieldName); - m_jsonString.append(":"); - addValueArray_(); - } - - @Override - void addPairString(String fieldName, String v) { - next_(Action.addPair); - appendQuote_(fieldName); - m_jsonString.append(":"); - addValueString_(v); - } - - @Override - void addPairDouble(String fieldName, double v) { - next_(Action.addPair); - appendQuote_(fieldName); - m_jsonString.append(":"); - addValueDouble_(v); - } - - @Override - void addPairDoubleF(String fieldName, double v, int decimals) { - next_(Action.addPair); - appendQuote_(fieldName); - m_jsonString.append(":"); - addValueDoubleF_(v, decimals); - } - - @Override - void addPairInt(String fieldName, int v) { - next_(Action.addPair); - appendQuote_(fieldName); - m_jsonString.append(":"); - addValueInt_(v); - } - - @Override - void addPairBoolean(String fieldName, boolean v) { - next_(Action.addPair); - appendQuote_(fieldName); - m_jsonString.append(":"); - addValueBoolean_(v); - } - - @Override - void addPairNull(String fieldName) { - next_(Action.addPair); - appendQuote_(fieldName); - m_jsonString.append(":"); - addValueNull_(); - } - - @Override - void addValueObject() { - next_(Action.addValue); - addValueObject_(); - } - - @Override - void addValueArray() { - next_(Action.addValue); - addValueArray_(); - } - - @Override - void addValueString(String v) { - next_(Action.addValue); - addValueString_(v); - } - - @Override - void addValueDouble(double v) { - next_(Action.addValue); - addValueDouble_(v); - } - - @Override - void addValueDoubleF(double v, int decimals) { - next_(Action.addValue); - addValueDoubleF_(v, decimals); - } - - @Override - void addValueInt(int v) { - next_(Action.addValue); - addValueInt_(v); - } - - @Override - void addValueBoolean(boolean v) { - next_(Action.addValue); - addValueBoolean_(v); - } - - @Override - void addValueNull() { - next_(Action.addValue); - addValueNull_(); - } - - JsonStringWriter() { - m_jsonString = new StringBuilder(); - m_functionStack = new AttributeStreamOfInt32(0); - m_functionStack.add(State.accept); - m_functionStack.add(State.start); - } - private StringBuilder m_jsonString; - private AttributeStreamOfInt32 m_functionStack; - - private void addValueObject_() { - m_jsonString.append('{'); - m_functionStack.add(State.objectStart); - } - - private void addValueArray_() { - m_jsonString.append('['); - m_functionStack.add(State.arrayStart); - } - - private void addValueString_(String v) { - appendQuote_(v); - } - - private void addValueDouble_(double v) { - if (NumberUtils.isNaN(v)) { - addValueNull_(); - return; - } - - StringUtils.appendDouble(v, 17, m_jsonString); - } - - private void addValueDoubleF_(double v, int decimals) { - if (NumberUtils.isNaN(v)) { - addValueNull_(); - return; - } - - StringUtils.appendDoubleF(v, decimals, m_jsonString); - } - - private void addValueInt_(int v) { - m_jsonString.append(v); - } - - private void addValueBoolean_(boolean v) { - if (v) { - m_jsonString.append("true"); - } else { - m_jsonString.append("false"); - } - } - - private void addValueNull_() { - m_jsonString.append("null"); - } - - private void next_(int action) { - switch (m_functionStack.getLast()) { - case State.accept: - accept_(action); - break; - case State.start: - start_(action); - break; - case State.objectStart: - objectStart_(action); - break; - case State.arrayStart: - arrayStart_(action); - break; - case State.pairEnd: - pairEnd_(action); - break; - case State.elementEnd: - elementEnd_(action); - break; - default: - throw GeometryException.GeometryInternalError(); - } - } - - private void accept_(int action) { - if (action != Action.accept) { - throw new GeometryException("invalid call"); - } - } - - private void start_(int action) { - if (action == Action.addContainer) { - m_functionStack.removeLast(); - } else { - throw new GeometryException("invalid call"); - } - } - - private void objectStart_(int action) { - m_functionStack.removeLast(); - - if (action == Action.addPair) { - m_functionStack.add(State.pairEnd); - } else if (action != Action.popObject) { - throw new GeometryException("invalid call"); - } - } - - private void pairEnd_(int action) { - if (action == Action.addPair) { - m_jsonString.append(','); - } else if (action == Action.popObject) { - m_functionStack.removeLast(); - } else { - throw new GeometryException("invalid call"); - } - } - - private void arrayStart_(int action) { - m_functionStack.removeLast(); - - if (action == Action.addValue) { - m_functionStack.add(State.elementEnd); - } else if (action != Action.popArray) { - throw new GeometryException("invalid call"); - } - } - - private void elementEnd_(int action) { - if (action == Action.addValue) { - m_jsonString.append(','); - } else if (action == Action.popArray) { - m_functionStack.removeLast(); - } else { - throw new GeometryException("invalid call"); - } - } - - private void appendQuote_(String string) { - int count = 0; - int start = 0; - int end = string.length(); - - m_jsonString.append('"'); - - for (int i = 0; i < end; i++) { - switch (string.charAt(i)) { - case '"': - if (count > 0) { - m_jsonString.append(string, start, start + count); - count = 0; - } - m_jsonString.append("\\\""); - start = i + 1; - break; - case '\\': - if (count > 0) { - m_jsonString.append(string, start, start + count); - count = 0; - } - m_jsonString.append("\\\\"); - start = i + 1; - break; - case '/': - if (i > 0 && string.charAt(i - 1) == '<') { - if (count > 0) { - m_jsonString.append(string, start, start + count); - count = 0; - } - m_jsonString.append("\\/"); - start = i + 1; - } else { - count++; - } - break; - case '\b': - if (count > 0) { - m_jsonString.append(string, start, start + count); - count = 0; - } - m_jsonString.append("\\b"); - start = i + 1; - break; - case '\f': - if (count > 0) { - m_jsonString.append(string, start, start + count); - count = 0; - } - m_jsonString.append("\\f"); - start = i + 1; - break; - case '\n': - if (count > 0) { - m_jsonString.append(string, start, start + count); - count = 0; - } - m_jsonString.append("\\n"); - start = i + 1; - break; - case '\r': - if (count > 0) { - m_jsonString.append(string, start, start + count); - count = 0; - } - m_jsonString.append("\\r"); - start = i + 1; - break; - case '\t': - if (count > 0) { - m_jsonString.append(string, start, start + count); - count = 0; - } - m_jsonString.append("\\t"); - start = i + 1; - break; - default: - count++; - break; - } - } - - if (count > 0) { - m_jsonString.append(string, start, start + count); - } - - m_jsonString.append('"'); - } + @Override + Object getJson() { + next_(Action.accept); + return m_jsonString.toString(); + } + + @Override + void startObject() { + next_(Action.addContainer); + m_jsonString.append('{'); + m_functionStack.add(State.objectStart); + } + + @Override + void startArray() { + next_(Action.addContainer); + m_jsonString.append('['); + m_functionStack.add(State.arrayStart); + } + + @Override + void endObject() { + next_(Action.popObject); + m_jsonString.append('}'); + } + + @Override + void endArray() { + next_(Action.popArray); + m_jsonString.append(']'); + } + + @Override + void addPairObject(String fieldName) { + next_(Action.addPair); + appendQuote_(fieldName); + m_jsonString.append(":"); + addValueObject_(); + } + + @Override + void addPairArray(String fieldName) { + next_(Action.addPair); + appendQuote_(fieldName); + m_jsonString.append(":"); + addValueArray_(); + } + + @Override + void addPairString(String fieldName, String v) { + next_(Action.addPair); + appendQuote_(fieldName); + m_jsonString.append(":"); + addValueString_(v); + } + + @Override + void addPairDouble(String fieldName, double v) { + next_(Action.addPair); + appendQuote_(fieldName); + m_jsonString.append(":"); + addValueDouble_(v); + } + + @Override + void addPairDoubleF(String fieldName, double v, int decimals) { + next_(Action.addPair); + appendQuote_(fieldName); + m_jsonString.append(":"); + addValueDoubleF_(v, decimals); + } + + @Override + void addPairInt(String fieldName, int v) { + next_(Action.addPair); + appendQuote_(fieldName); + m_jsonString.append(":"); + addValueInt_(v); + } + + @Override + void addPairBoolean(String fieldName, boolean v) { + next_(Action.addPair); + appendQuote_(fieldName); + m_jsonString.append(":"); + addValueBoolean_(v); + } + + @Override + void addPairNull(String fieldName) { + next_(Action.addPair); + appendQuote_(fieldName); + m_jsonString.append(":"); + addValueNull_(); + } + + @Override + void addValueObject() { + next_(Action.addContainer); + addValueObject_(); + } + + @Override + void addValueArray() { + next_(Action.addContainer); + addValueArray_(); + } + + @Override + void addValueString(String v) { + next_(Action.addTerminal); + addValueString_(v); + } + + @Override + void addValueDouble(double v) { + next_(Action.addTerminal); + addValueDouble_(v); + } + + @Override + void addValueDoubleF(double v, int decimals) { + next_(Action.addTerminal); + addValueDoubleF_(v, decimals); + } + + @Override + void addValueInt(int v) { + next_(Action.addTerminal); + addValueInt_(v); + } + + @Override + void addValueBoolean(boolean v) { + next_(Action.addTerminal); + addValueBoolean_(v); + } + + @Override + void addValueNull() { + next_(Action.addTerminal); + addValueNull_(); + } + + JsonStringWriter() { + m_jsonString = new StringBuilder(); + m_functionStack = new AttributeStreamOfInt32(0); + m_functionStack.add(State.accept); + m_functionStack.add(State.start); + } + + private StringBuilder m_jsonString; + private AttributeStreamOfInt32 m_functionStack; + + private void addValueObject_() { + m_jsonString.append('{'); + m_functionStack.add(State.objectStart); + } + + private void addValueArray_() { + m_jsonString.append('['); + m_functionStack.add(State.arrayStart); + } + + private void addValueString_(String v) { + appendQuote_(v); + } + + private void addValueDouble_(double v) { + if (NumberUtils.isNaN(v)) { + addValueNull_(); + return; + } + + StringUtils.appendDouble(v, 17, m_jsonString); + } + + private void addValueDoubleF_(double v, int decimals) { + if (NumberUtils.isNaN(v)) { + addValueNull_(); + return; + } + + StringUtils.appendDoubleF(v, decimals, m_jsonString); + } + + private void addValueInt_(int v) { + m_jsonString.append(v); + } + + private void addValueBoolean_(boolean v) { + if (v) { + m_jsonString.append("true"); + } else { + m_jsonString.append("false"); + } + } + + private void addValueNull_() { + m_jsonString.append("null"); + } + + private void next_(int action) { + switch (m_functionStack.getLast()) { + case State.accept: + accept_(action); + break; + case State.start: + start_(action); + break; + case State.objectStart: + objectStart_(action); + break; + case State.arrayStart: + arrayStart_(action); + break; + case State.pairEnd: + pairEnd_(action); + break; + case State.elementEnd: + elementEnd_(action); + break; + default: + throw new GeometryException("internal error"); + } + } + + private void accept_(int action) { + if (action != Action.accept) { + throw new GeometryException("invalid call"); + } + } + + private void start_(int action) { + if (action == Action.addContainer) { + m_functionStack.removeLast(); + } else { + throw new GeometryException("invalid call"); + } + } + + private void objectStart_(int action) { + m_functionStack.removeLast(); + + if (action == Action.addPair) { + m_functionStack.add(State.pairEnd); + } else if (action != Action.popObject) { + throw new GeometryException("invalid call"); + } + } + + private void pairEnd_(int action) { + if (action == Action.addPair) { + m_jsonString.append(','); + } else if (action == Action.popObject) { + m_functionStack.removeLast(); + } else { + throw new GeometryException("invalid call"); + } + } + + private void arrayStart_(int action) { + m_functionStack.removeLast(); + + if ((action & Action.addValue) != 0) { + m_functionStack.add(State.elementEnd); + } else if (action != Action.popArray) { + throw new GeometryException("invalid call"); + } + } + + private void elementEnd_(int action) { + if ((action & Action.addValue) != 0) { + m_jsonString.append(','); + } else if (action == Action.popArray) { + m_functionStack.removeLast(); + } else { + throw new GeometryException("invalid call"); + } + } + + private void appendQuote_(String string) { + int count = 0; + int start = 0; + int end = string.length(); + + m_jsonString.append('"'); + + for (int i = 0; i < end; i++) { + switch (string.charAt(i)) { + case '"': + if (count > 0) { + m_jsonString.append(string, start, start + count); + count = 0; + } + m_jsonString.append("\\\""); + start = i + 1; + break; + case '\\': + if (count > 0) { + m_jsonString.append(string, start, start + count); + count = 0; + } + m_jsonString.append("\\\\"); + start = i + 1; + break; + case '/': + if (i > 0 && string.charAt(i - 1) == '<') { + if (count > 0) { + m_jsonString.append(string, start, start + count); + count = 0; + } + m_jsonString.append("\\/"); + start = i + 1; + } else { + count++; + } + break; + case '\b': + if (count > 0) { + m_jsonString.append(string, start, start + count); + count = 0; + } + m_jsonString.append("\\b"); + start = i + 1; + break; + case '\f': + if (count > 0) { + m_jsonString.append(string, start, start + count); + count = 0; + } + m_jsonString.append("\\f"); + start = i + 1; + break; + case '\n': + if (count > 0) { + m_jsonString.append(string, start, start + count); + count = 0; + } + m_jsonString.append("\\n"); + start = i + 1; + break; + case '\r': + if (count > 0) { + m_jsonString.append(string, start, start + count); + count = 0; + } + m_jsonString.append("\\r"); + start = i + 1; + break; + case '\t': + if (count > 0) { + m_jsonString.append(string, start, start + count); + count = 0; + } + m_jsonString.append("\\t"); + start = i + 1; + break; + default: + count++; + break; + } + } + + if (count > 0) { + m_jsonString.append(string, start, start + count); + } + + m_jsonString.append('"'); + } } - diff --git a/src/main/java/com/esri/core/geometry/JsonWriter.java b/src/main/java/com/esri/core/geometry/JsonWriter.java index 28e15874..9e71beb8 100644 --- a/src/main/java/com/esri/core/geometry/JsonWriter.java +++ b/src/main/java/com/esri/core/geometry/JsonWriter.java @@ -25,66 +25,66 @@ abstract class JsonWriter { - abstract Object getJson(); + abstract Object getJson(); - abstract void startObject(); + abstract void startObject(); - abstract void startArray(); + abstract void startArray(); - abstract void endObject(); + abstract void endObject(); - abstract void endArray(); + abstract void endArray(); - abstract void addPairObject(String fieldName); + abstract void addPairObject(String fieldName); - abstract void addPairArray(String fieldName); + abstract void addPairArray(String fieldName); - abstract void addPairString(String fieldName, String v); + abstract void addPairString(String fieldName, String v); - abstract void addPairDouble(String fieldName, double v); + abstract void addPairDouble(String fieldName, double v); - abstract void addPairDoubleF(String fieldName, double v, int decimals); + abstract void addPairDoubleF(String fieldName, double v, int decimals); - abstract void addPairInt(String fieldName, int v); + abstract void addPairInt(String fieldName, int v); - abstract void addPairBoolean(String fieldName, boolean v); + abstract void addPairBoolean(String fieldName, boolean v); - abstract void addPairNull(String fieldName); + abstract void addPairNull(String fieldName); - abstract void addValueObject(); + abstract void addValueObject(); - abstract void addValueArray(); + abstract void addValueArray(); - abstract void addValueString(String v); + abstract void addValueString(String v); - abstract void addValueDouble(double v); + abstract void addValueDouble(double v); - abstract void addValueDoubleF(double v, int decimals); + abstract void addValueDoubleF(double v, int decimals); - abstract void addValueInt(int v); + abstract void addValueInt(int v); - abstract void addValueBoolean(boolean v); + abstract void addValueBoolean(boolean v); - abstract void addValueNull(); + abstract void addValueNull(); - protected interface Action { + protected interface Action { - static final int accept = 0; - static final int addContainer = 1; - static final int popObject = 4; - static final int popArray = 8; - static final int addPair = 16; - static final int addValue = 32; - } + static final int accept = 0; + static final int addContainer = 1; + static final int popObject = 4; + static final int popArray = 8; + static final int addPair = 16; + static final int addTerminal = 32; + static final int addValue = addContainer | addTerminal; + } - protected interface State { + protected interface State { - static final int accept = 0; - static final int start = 1; - static final int objectStart = 2; - static final int arrayStart = 3; - static final int pairEnd = 4; - static final int elementEnd = 6; - } + static final int accept = 0; + static final int start = 1; + static final int objectStart = 2; + static final int arrayStart = 3; + static final int pairEnd = 4; + static final int elementEnd = 6; + } } - diff --git a/src/main/java/com/esri/core/geometry/Line.java b/src/main/java/com/esri/core/geometry/Line.java index 881cdcef..95126e81 100644 --- a/src/main/java/com/esri/core/geometry/Line.java +++ b/src/main/java/com/esri/core/geometry/Line.java @@ -35,13 +35,10 @@ */ public final class Line extends Segment implements Serializable { - // UPDATED PORT TO MATCH NATIVE AS OF JAN 30 2011 - private static final long serialVersionUID = 2L;// TODO:remove as we use // writeReplace and // GeometrySerializer - // HEADER DEF @Override public Geometry.Type getType() { return Type.Line; @@ -190,32 +187,15 @@ public Geometry createInstance() { } double getCoordX_(double t) { - // double x = m_x_end * t + (1.0 - t) * m_x_start; < 1.0 || (x >= m_xStart && x <= m_xEnd) || (x <= m_xStart && x >= m_xEnd)); - return x; + return MathUtils.lerp(m_xStart, m_xEnd, t); } double getCoordY_(double t) { // Must match query_coord_2D and vice verse // Also match get_attribute_as_dbl - double y; - if (t <= 0.5) { - y = m_yStart + (m_yEnd - m_yStart) * t; - } else { - y = m_yEnd - (m_yEnd - m_yStart) * (1.0 - t); - } - assert (t < 0 || t > 1.0 || (y >= m_yStart && y <= m_yEnd) || (y <= m_yStart && y >= m_yEnd)); - return y; + return MathUtils.lerp(m_yStart, m_yEnd, t); } @Override @@ -225,17 +205,7 @@ void getCoord2D(double t, Point2D pt) { // 2. When t == 1, get exactly End // 3. When m_x_end == m_x_start, we want m_x_start exactly // 4. When m_y_end == m_y_start, we want m_y_start exactly - if (t <= 0.5) { - pt.x = m_xStart + (m_xEnd - m_xStart) * t; - pt.y = m_yStart + (m_yEnd - m_yStart) * t; - } else { - pt.x = m_xEnd - (m_xEnd - m_xStart) * (1.0 - t); - pt.y = m_yEnd - (m_yEnd - m_yStart) * (1.0 - t); - } - // assert(t < 0 || t > 1.0 || (pt.x >= m_x_start && pt.x <= m_x_end) || - // (pt.x <= m_x_start && pt.x >= m_x_end)); - // assert(t < 0 || t > 1.0 || (pt.y >= m_y_start && pt.y <= m_y_end) || - // (pt.y <= m_y_start && pt.y >= m_y_end)); + MathUtils.lerp(m_xStart, m_yStart, m_xEnd, m_yEnd, t, pt); } @Override @@ -289,7 +259,7 @@ public double getAttributeAsDbl(double t, int semantics, int ordinate) { case VertexDescription.Interpolation.LINEAR: { double s = getStartAttributeAsDbl(semantics, ordinate); double e = getEndAttributeAsDbl(semantics, ordinate); - return s * (1.0 - t) + e * t; + return MathUtils.lerp(s, e, t); } case VertexDescription.Interpolation.ANGULAR: { throw new GeometryException("not implemented"); @@ -1002,6 +972,25 @@ static int _intersectLineLine(Line line1, Line line2, return 1; } + + @Override + public void replaceNaNs(int semantics, double value) { + addAttribute(semantics); + if (isEmpty()) + return; + + int ncomps = VertexDescription.getComponentCount(semantics); + for (int i = 0; i < ncomps; i++) { + double v = _getAttributeAsDbl(0, semantics, i); + if (Double.isNaN(v)) + _setAttribute(0, semantics, 0, value); + + v = _getAttributeAsDbl(1, semantics, i); + if (Double.isNaN(v)) + _setAttribute(1, semantics, 0, value); + } + } + @Override int getYMonotonicParts(SegmentBuffer[] monotonicSegments) { @@ -1014,4 +1003,13 @@ void _copyToImpl(Segment dst) { } + /** + * The output of this method can be only used for debugging. It is subject to change without notice. + */ + @Override + public String toString() { + String s = "Line: [" + m_xStart + ", " + m_yStart + ", " + m_xEnd + ", " + m_yEnd +"]"; + return s; + } + } diff --git a/src/main/java/com/esri/core/geometry/MapGeometry.java b/src/main/java/com/esri/core/geometry/MapGeometry.java index e28cd631..fc1ef12b 100644 --- a/src/main/java/com/esri/core/geometry/MapGeometry.java +++ b/src/main/java/com/esri/core/geometry/MapGeometry.java @@ -32,7 +32,7 @@ * together. To work with a geometry object in a map it is necessary to have a * spatial reference defined for this geometry. */ -public final class MapGeometry implements Serializable { +public class MapGeometry implements Serializable { private static final long serialVersionUID = 1L; Geometry m_geometry = null; diff --git a/src/main/java/com/esri/core/geometry/MathUtils.java b/src/main/java/com/esri/core/geometry/MathUtils.java index f6c9311a..8ed61457 100644 --- a/src/main/java/com/esri/core/geometry/MathUtils.java +++ b/src/main/java/com/esri/core/geometry/MathUtils.java @@ -24,7 +24,7 @@ package com.esri.core.geometry; -class MathUtils { +final class MathUtils { /** * The implementation of the Kahan summation algorithm. Use to get better * precision when adding a lot of values. @@ -154,4 +154,71 @@ static double round(double v) { static double sqr(double v) { return v * v; } + + /** + *Computes interpolation between two values, using the interpolation factor t. + *The interpolation formula is (end - start) * t + start. + *However, the computation ensures that t = 0 produces exactly start, and t = 1, produces exactly end. + *It also guarantees that for 0 <= t <= 1, the interpolated value v is between start and end. + */ + static double lerp(double start_, double end_, double t) { + // When end == start, we want result to be equal to start, for all t + // values. At the same time, when end != start, we want the result to be + // equal to start for t==0 and end for t == 1.0 + // The regular formula end_ * t + (1.0 - t) * start_, when end_ == + // start_, and t at 1/3, produces value different from start + double v; + if (t <= 0.5) + v = start_ + (end_ - start_) * t; + else + v = end_ - (end_ - start_) * (1.0 - t); + + assert (t < 0 || t > 1.0 || (v >= start_ && v <= end_) || (v <= start_ && v >= end_)); + return v; + } + + /** + *Computes interpolation between two values, using the interpolation factor t. + *The interpolation formula is (end - start) * t + start. + *However, the computation ensures that t = 0 produces exactly start, and t = 1, produces exactly end. + *It also guarantees that for 0 <= t <= 1, the interpolated value v is between start and end. + */ + static void lerp(Point2D start_, Point2D end_, double t, Point2D result) { + // When end == start, we want result to be equal to start, for all t + // values. At the same time, when end != start, we want the result to be + // equal to start for t==0 and end for t == 1.0 + // The regular formula end_ * t + (1.0 - t) * start_, when end_ == + // start_, and t at 1/3, produces value different from start + if (t <= 0.5) { + result.x = start_.x + (end_.x - start_.x) * t; + result.y = start_.y + (end_.y - start_.y) * t; + } + else { + result.x = end_.x - (end_.x - start_.x) * (1.0 - t); + result.y = end_.y - (end_.y - start_.y) * (1.0 - t); + } + + assert (t < 0 || t > 1.0 || (result.x >= start_.x && result.x <= end_.x) || (result.x <= start_.x && result.x >= end_.x)); + assert (t < 0 || t > 1.0 || (result.y >= start_.y && result.y <= end_.y) || (result.y <= start_.y && result.y >= end_.y)); + } + + static void lerp(double start_x, double start_y, double end_x, double end_y, double t, Point2D result) { + // When end == start, we want result to be equal to start, for all t + // values. At the same time, when end != start, we want the result to be + // equal to start for t==0 and end for t == 1.0 + // The regular formula end_ * t + (1.0 - t) * start_, when end_ == + // start_, and t at 1/3, produces value different from start + if (t <= 0.5) { + result.x = start_x + (end_x - start_x) * t; + result.y = start_y + (end_y - start_y) * t; + } + else { + result.x = end_x - (end_x - start_x) * (1.0 - t); + result.y = end_y - (end_y - start_y) * (1.0 - t); + } + + assert (t < 0 || t > 1.0 || (result.x >= start_x && result.x <= end_x) || (result.x <= start_x && result.x >= end_x)); + assert (t < 0 || t > 1.0 || (result.y >= start_y && result.y <= end_y) || (result.y <= start_y && result.y >= end_y)); + } + } diff --git a/src/main/java/com/esri/core/geometry/MultiPath.java b/src/main/java/com/esri/core/geometry/MultiPath.java index 3dbda88d..1fb76d68 100644 --- a/src/main/java/com/esri/core/geometry/MultiPath.java +++ b/src/main/java/com/esri/core/geometry/MultiPath.java @@ -738,4 +738,9 @@ public int getStateFlag() { return m_impl.getStateFlag(); } + @Override + public void replaceNaNs(int semantics, double value) { + m_impl.replaceNaNs(semantics, value); + } + } diff --git a/src/main/java/com/esri/core/geometry/MultiPathImpl.java b/src/main/java/com/esri/core/geometry/MultiPathImpl.java index 33e530b6..ba566436 100644 --- a/src/main/java/com/esri/core/geometry/MultiPathImpl.java +++ b/src/main/java/com/esri/core/geometry/MultiPathImpl.java @@ -54,6 +54,7 @@ final class MultiPathImpl extends MultiVertexGeometryImpl { protected AttributeStreamOfDbl m_segmentParams; protected int m_curveParamwritePoint; private int m_currentPathIndex; + private int m_fill_rule = Polygon.FillRule.enumFillRuleOddEven; static int[] _segmentParamSizes = { 0, 0, 6, 0, 8, 0 }; // None, Line, // Bezier, XXX, Arc, @@ -136,6 +137,7 @@ public void startPath(Point point) { throw new IllegalArgumentException();// throw new // IllegalArgumentException(); + mergeVertexDescription(point.getDescription()); _initPathStartPoint(); point.copyTo(m_moveToPoint); @@ -1632,8 +1634,7 @@ void interpolateAttributes_(int semantics, int from_path_index, double segment_length = segment.calculateLength2D(); cumulative_length += segment_length; double t = cumulative_length / sub_length; - interpolated_attribute = (1.0 - t) * from_attribute + t - * to_attribute; + interpolated_attribute = MathUtils.lerp(from_attribute, to_attribute, t); if (!seg_iter.isClosingSegment()) setAttribute(semantics, seg_iter.getEndPointIndex(), @@ -1676,8 +1677,7 @@ void interpolateAttributes_(int semantics, int path_index, double segment_length = segment.calculateLength2D(); cumulative_length += segment_length; double t = cumulative_length / sub_length; - prev_interpolated_attribute = (1.0 - t) * from_attribute + t - * to_attribute; + prev_interpolated_attribute = MathUtils.lerp(from_attribute, to_attribute, t); } while (seg_iter.getEndPointIndex() != absolute_to_index); } @@ -1846,7 +1846,8 @@ void _copyToImpl(MultiVertexGeometryImpl dst) { MultiPathImpl dstPoly = (MultiPathImpl) dst; dstPoly.m_bPathStarted = false; dstPoly.m_curveParamwritePoint = m_curveParamwritePoint; - + dstPoly.m_fill_rule = m_fill_rule; + if (m_paths != null) dstPoly.m_paths = new AttributeStreamOfInt32(m_paths); else @@ -1925,6 +1926,9 @@ public boolean equals(Object other) { if (m_paths != null && !m_paths.equals(otherMultiPath.m_paths, 0, pathCount + 1)) return false; + + if (m_fill_rule != otherMultiPath.m_fill_rule) + return false; if (m_pathFlags != null && !m_pathFlags @@ -2508,6 +2512,30 @@ void queryPathEnvelope2D(int path_index, Envelope2D envelope) { } } + public void queryLoosePathEnvelope2D(int path_index, Envelope2D envelope) { + if (path_index >= getPathCount()) + throw new IllegalArgumentException(); + + if (isEmpty()) { + envelope.setEmpty(); + return; + } + + if (hasNonLinearSegments(path_index)) { + throw new GeometryException("not implemented"); + } else { + AttributeStreamOfDbl stream = (AttributeStreamOfDbl) getAttributeStreamRef(VertexDescription.Semantics.POSITION); + Point2D pt = new Point2D(); + Envelope2D env = new Envelope2D(); + env.setEmpty(); + for (int i = getPathStart(path_index), iend = getPathEnd(path_index); i < iend; i++) { + stream.read(2 * i, pt); + env.merge(pt); + } + envelope.setCoords(env); + } + } + @Override public boolean _buildQuadTreeAccelerator(GeometryAccelerationDegree d) { if (m_accelerators == null)// (!m_accelerators) @@ -2524,22 +2552,30 @@ public boolean _buildQuadTreeAccelerator(GeometryAccelerationDegree d) { return true; } - boolean _buildPathEnvelopesAccelerator(GeometryAccelerationDegree d) { - if (m_accelerators == null) { - m_accelerators = new GeometryAccelerators(); - } + boolean _buildQuadTreeForPathsAccelerator(GeometryAccelerationDegree degree) { + if (m_accelerators == null) { + m_accelerators = new GeometryAccelerators(); + } - ArrayList path_envelopes = new ArrayList(0); + //TODO: when less than two envelopes - no need to this. - for (int ipath = 0; ipath < getPathCount(); ipath++) { - Envelope2D env = new Envelope2D(); - queryPathEnvelope2D(ipath, env); - path_envelopes.add(env); - } + if (m_accelerators.getQuadTreeForPaths() != null) + return true; - m_accelerators._setPathEnvelopes(path_envelopes); + m_accelerators._setQuadTreeForPaths(null); + QuadTreeImpl quad_tree_impl = InternalUtils.buildQuadTreeForPaths(this); + m_accelerators._setQuadTreeForPaths(quad_tree_impl); + + return true; + } + + void setFillRule(int rule) { + assert(m_bPolygon); + m_fill_rule = rule; + } + int getFillRule() { + return m_fill_rule; + } - return true; - } } diff --git a/src/main/java/com/esri/core/geometry/MultiPoint.java b/src/main/java/com/esri/core/geometry/MultiPoint.java index e890cb8a..3604e108 100644 --- a/src/main/java/com/esri/core/geometry/MultiPoint.java +++ b/src/main/java/com/esri/core/geometry/MultiPoint.java @@ -364,4 +364,9 @@ public int getStateFlag() { public Geometry getBoundary() { return m_impl.getBoundary(); } + + @Override + public void replaceNaNs(int semantics, double value) { + m_impl.replaceNaNs(semantics, value); + } } diff --git a/src/main/java/com/esri/core/geometry/MultiVertexGeometryImpl.java b/src/main/java/com/esri/core/geometry/MultiVertexGeometryImpl.java index fc614cfb..6fb511ea 100644 --- a/src/main/java/com/esri/core/geometry/MultiVertexGeometryImpl.java +++ b/src/main/java/com/esri/core/geometry/MultiVertexGeometryImpl.java @@ -972,7 +972,7 @@ void _interpolateTwoVertices(int vertex1, int vertex2, double f, * vertex1 + icomp); double v2 = m_vertexAttributes[attributeIndex].readAsDbl(ncomp * vertex2 + icomp); - outPoint.setAttribute(semantics, icomp, v1 * (1.0 - f) + v2 * f); + outPoint.setAttribute(semantics, icomp, MathUtils.lerp(v1, v2, f)); } } } @@ -1068,6 +1068,43 @@ public void queryCoordinates(Point3D[] dst) { dst[i] = getXYZ(i); } } + + @Override + public void replaceNaNs(int semantics, double value) { + addAttribute(semantics); + if (isEmpty()) + return; + + boolean modified = false; + int ncomps = VertexDescription.getComponentCount(semantics); + for (int i = 0; i < ncomps; i++) { + int attr = m_description.getAttributeIndex(semantics); + AttributeStreamBase streamBase = getAttributeStreamRef(attr); + if (streamBase instanceof AttributeStreamOfDbl) { + AttributeStreamOfDbl dblStream = (AttributeStreamOfDbl)streamBase; + for (int ivert = 0, n = m_pointCount * ncomps; ivert < n; ivert++) { + double v = dblStream.read(ivert); + if (Double.isNaN(v)) { + dblStream.write(ivert, value); + modified = true; + } + } + } + else { + for (int ivert = 0, n = m_pointCount * ncomps; ivert < n; ivert++) { + double v = streamBase.readAsDbl(ivert); + if (Double.isNaN(v)) { + streamBase.writeAsDbl(ivert, value); + modified = true; + } + } + } + } + + if (modified) { + notifyModified(DirtyFlags.DirtyCoordinates); + } + } public abstract boolean _buildRasterizedGeometryAccelerator( double toleranceXY, GeometryAccelerationDegree accelDegree); diff --git a/src/main/java/com/esri/core/geometry/NonSimpleResult.java b/src/main/java/com/esri/core/geometry/NonSimpleResult.java index b8d9d029..ac79c687 100644 --- a/src/main/java/com/esri/core/geometry/NonSimpleResult.java +++ b/src/main/java/com/esri/core/geometry/NonSimpleResult.java @@ -23,37 +23,65 @@ */ package com.esri.core.geometry; -class NonSimpleResult { +/** + * The result of the IsSimpleXXX. + * + * + */ +public class NonSimpleResult { public enum Reason { - NotDetermined, // = 1; + boolean b_simple_b = multi_path_impl_b.getIsSimple(0.0) >= 1; + m_intersector = InternalUtils.getEnvelope2DIntersectorForParts(multi_path_impl_a, multi_path_impl_b, tolerance, b_simple_a, b_simple_b); + } + } + } + + boolean next() { + if (m_b_quad_tree) { + if (m_b_done) + return false; + + boolean b_searching = true; + while (b_searching) { + switch (m_function) { + case State.nextPath: + b_searching = nextPath_(); + break; + case State.nextSegment: + b_searching = nextSegment_(); + break; + case State.iterate: + b_searching = iterate_(); + break; + default: + throw GeometryException.GeometryInternalError(); + } + } + + if (m_b_done) + return false; + + return true; + } + + if (m_intersector == null) + return false; + + return m_intersector.next(); + } + + int getRedElement() { + if (m_b_quad_tree) { + if (!m_b_swap_elements) + return (!m_b_paths ? m_seg_iter.getStartPointIndex() : m_path_index); + + return m_quad_tree.getElement(m_element_handle); + } + + return m_intersector.getRedElement(m_intersector.getHandleA()); + } + + int getBlueElement() { + if (m_b_quad_tree) { + if (m_b_swap_elements) + return (!m_b_paths ? m_seg_iter.getStartPointIndex() : m_path_index); + + return m_quad_tree.getElement(m_element_handle); + } + + return m_intersector.getBlueElement(m_intersector.getHandleB()); + } + + Envelope2D getRedEnvelope() { + if (!m_b_paths) + throw GeometryException.GeometryInternalError(); + + if (m_b_quad_tree) { + if (!m_b_swap_elements) + return m_paths_query; + + return m_quad_tree.getElementExtent(m_element_handle); + } + + return m_intersector.getRedEnvelope(m_intersector.getHandleA()); + } + + Envelope2D getBlueEnvelope() { + if (!m_b_paths) + throw GeometryException.GeometryInternalError(); + + if (m_b_quad_tree) { + if (m_b_swap_elements) + return m_paths_query; + + return m_quad_tree.getElementExtent(m_element_handle); + } + + return m_intersector.getBlueEnvelope(m_intersector.getHandleB()); + } + + boolean nextPath_() { + if (!m_b_paths) { + if (!m_seg_iter.nextPath()) { + m_b_done = true; + return false; + } + + m_function = State.nextSegment; + return true; + } + + if (--m_path_index == -1) { + m_b_done = true; + return false; + } + + if (m_b_swap_elements) + m_multi_path_impl_b.queryPathEnvelope2D(m_path_index, m_paths_query); + else + m_multi_path_impl_a.queryPathEnvelope2D(m_path_index, m_paths_query); + + m_qt_iter.resetIterator(m_paths_query, m_tolerance); + m_function = State.iterate; + return true; + } + + boolean nextSegment_() { + if (!m_seg_iter.hasNextSegment()) { + m_function = State.nextPath; + return true; + } + + Segment segment = m_seg_iter.nextSegment(); + m_qt_iter.resetIterator(segment, m_tolerance); + m_function = State.iterate; + return true; + } + + boolean iterate_() { + m_element_handle = m_qt_iter.next(); + + if (m_element_handle == -1) { + m_function = (!m_b_paths ? State.nextSegment : State.nextPath); + return true; + } + + return false; + } +} diff --git a/src/main/java/com/esri/core/geometry/PlaneSweepCrackerHelper.java b/src/main/java/com/esri/core/geometry/PlaneSweepCrackerHelper.java index ab23f7c1..41db31be 100644 --- a/src/main/java/com/esri/core/geometry/PlaneSweepCrackerHelper.java +++ b/src/main/java/com/esri/core/geometry/PlaneSweepCrackerHelper.java @@ -68,7 +68,11 @@ boolean sweep(EditShape shape, double tolerance) { b_cracked |= sweepImpl_(); } - m_shape.removeUserIndex(m_vertex_cluster_index); + if (m_vertex_cluster_index != -1) { + m_shape.removeUserIndex(m_vertex_cluster_index); + m_vertex_cluster_index = -1; + } + m_shape = null; return m_b_cracked; } @@ -82,9 +86,17 @@ boolean sweepVertical(EditShape shape, double tolerance) { m_complications = false; boolean bresult = sweepImpl_(); if (!m_complications) { - int filtered = shape.filterClosePoints(tolerance, true); + int filtered = shape.filterClosePoints(tolerance, true, false); m_complications = filtered == 1; + bresult |= filtered == 1; + } + + if (m_vertex_cluster_index != -1) { + m_shape.removeUserIndex(m_vertex_cluster_index); + m_vertex_cluster_index = -1; } + + m_shape = null; return bresult; } @@ -758,22 +770,31 @@ int compare(Treap treap, int node) { void processSplitHelper1_(int index, int edge, SegmentIntersector intersector) { + int clusterStart = getEdgeCluster(edge, 0); + Point2D ptClusterStart = new Point2D(); + getClusterXY(clusterStart, ptClusterStart); + Point2D ptClusterEnd = new Point2D(); + int clusterEnd = getEdgeCluster(edge, 1); + getClusterXY(clusterEnd, ptClusterEnd); + // Collect all edges that are affected by the split and that are in the // sweep structure. int count = intersector.getResultSegmentCount(index); Segment seg = intersector.getResultSegment(index, 0); - seg.getStartXY(pt_2); - int clusterStart = getEdgeCluster(edge, 0); - getClusterXY(clusterStart, pt_1); - if (!pt_1.isEqual(pt_2)) { - int res1 = pt_1.compare(m_sweep_point); - int res2 = pt_2.compare(m_sweep_point); - if (res1 * res2 < 0) { - m_complications = true;// point is not yet have been processed - // but moved before the sweep point, - // this will require - // repeating the cracking step and the sweep_vertical cannot - // help here + Point2D newStart = new Point2D(); + seg.getStartXY(newStart); + + if (!ptClusterStart.isEqual(newStart)) { + if (!m_complications) { + int res1 = ptClusterStart.compare(m_sweep_point); + int res2 = newStart.compare(m_sweep_point); + if (res1 * res2 < 0) { + m_complications = true;// point is not yet have been processed + // but moved before the sweep point, + // this will require + // repeating the cracking step and the sweep_vertical cannot + // help here + } } // This cluster's position needs to be changed @@ -781,18 +802,37 @@ void processSplitHelper1_(int index, int edge, m_modified_clusters.add(clusterStart); } - seg = intersector.getResultSegment(index, count - 1); - seg.getEndXY(pt_2); - int clusterEnd = getEdgeCluster(edge, 1); - getClusterXY(clusterEnd, pt_1); - if (!pt_1.isEqual(pt_2)) { - int res1 = pt_1.compare(m_sweep_point); - int res2 = pt_2.compare(m_sweep_point); - if (res1 * res2 < 0) { - m_complications = true;// point is not yet have been processed - // but moved before the sweep point. + if (!m_complications && count > 1) { + int dir = ptClusterStart.compare(ptClusterEnd); + Point2D midPoint = seg.getEndXY(); + if (ptClusterStart.compare(midPoint) != dir + || midPoint.compare(ptClusterEnd) != dir) {// split segment + // midpoint is + // above the + // sweep line. + // Therefore the + // part of the + // segment + m_complications = true; + } else { + if (midPoint.compare(m_sweep_point) < 0) { + // midpoint moved below sweepline. + m_complications = true; + } } + } + seg = intersector.getResultSegment(index, count - 1); + Point2D newEnd = seg.getEndXY(); + if (!ptClusterEnd.isEqual(newEnd)) { + if (!m_complications) { + int res1 = ptClusterEnd.compare(m_sweep_point); + int res2 = newEnd.compare(m_sweep_point); + if (res1 * res2 < 0) { + m_complications = true;// point is not yet have been processed + // but moved before the sweep point. + } + } // This cluster's position needs to be changed getAffectedEdges(clusterEnd, m_temp_edge_buffer); m_modified_clusters.add(clusterEnd); diff --git a/src/main/java/com/esri/core/geometry/Point.java b/src/main/java/com/esri/core/geometry/Point.java index 11459be5..9bbf20a4 100644 --- a/src/main/java/com/esri/core/geometry/Point.java +++ b/src/main/java/com/esri/core/geometry/Point.java @@ -623,4 +623,18 @@ public int hashCode() { public Geometry getBoundary() { return null; } + + @Override + public void replaceNaNs(int semantics, double value) { + addAttribute(semantics); + if (isEmpty()) + return; + + int ncomps = VertexDescription.getComponentCount(semantics); + for (int i = 0; i < ncomps; i++) { + double v = getAttributeAsDbl(semantics, i); + if (Double.isNaN(v)) + setAttribute(semantics, i, value); + } + } } diff --git a/src/main/java/com/esri/core/geometry/PointInPolygonHelper.java b/src/main/java/com/esri/core/geometry/PointInPolygonHelper.java index 0cda4401..8664ed8d 100644 --- a/src/main/java/com/esri/core/geometry/PointInPolygonHelper.java +++ b/src/main/java/com/esri/core/geometry/PointInPolygonHelper.java @@ -24,7 +24,7 @@ package com.esri.core.geometry; -class PointInPolygonHelper { +final class PointInPolygonHelper { private Point2D m_inputPoint; private int m_windnum; @@ -164,7 +164,7 @@ private boolean processSegment(Segment segment) { private static int _isPointInPolygonInternal(Polygon inputPolygon, Point2D inputPoint, double tolerance) { - boolean bAltenate = true;// Path.get_FillMode() == fillmodeAlternate; + boolean bAltenate = inputPolygon.getFillRule() == Polygon.FillRule.enumFillRuleOddEven; PointInPolygonHelper helper = new PointInPolygonHelper(bAltenate, inputPoint, tolerance); MultiPathImpl mpImpl = (MultiPathImpl) inputPolygon._getImpl(); @@ -187,7 +187,7 @@ private static int _isPointInPolygonInternalWithQuadTree( inputPolygon.queryLooseEnvelope(envPoly); envPoly.inflate(tolerance, tolerance); - boolean bAltenate = true;// Path.get_FillMode() == fillmodeAlternate; + boolean bAltenate = inputPolygon.getFillRule() == Polygon.FillRule.enumFillRuleOddEven; PointInPolygonHelper helper = new PointInPolygonHelper(bAltenate, inputPoint, tolerance); @@ -229,7 +229,7 @@ public static int isPointInPolygon(Polygon inputPolygon, MultiPathImpl mpImpl = (MultiPathImpl) inputPolygon._getImpl(); GeometryAccelerators accel = mpImpl._getAccelerators(); if (accel != null) { - //geometry has spatial indices built. Try using them. + // geometry has spatial indices built. Try using them. RasterizedGeometry2D rgeom = accel.getRasterizedGeometry(); if (rgeom != null) { RasterizedGeometry2D.HitType hit = rgeom.queryPointInGeometry( @@ -241,7 +241,7 @@ else if (hit == RasterizedGeometry2D.HitType.Outside) } QuadTreeImpl qtree = accel.getQuadTree(); - if (qtree != null) { + if (qtree != null) { return _isPointInPolygonInternalWithQuadTree(inputPolygon, qtree, inputPoint, tolerance); } @@ -280,28 +280,61 @@ else if (hit == RasterizedGeometry2D.HitType.Outside) } public static int isPointInRing(MultiPathImpl inputPolygonImpl, int iRing, - Point2D inputPoint, double tolerance) { + Point2D inputPoint, double tolerance, QuadTree quadTree) { Envelope2D env = new Envelope2D(); inputPolygonImpl.queryLooseEnvelope2D(env); env.inflate(tolerance, tolerance); if (!env.contains(inputPoint)) return 0; - boolean bAltenate = true;// Path.get_FillMode() == fillmodeAlternate; + boolean bAltenate = true; PointInPolygonHelper helper = new PointInPolygonHelper(bAltenate, inputPoint, tolerance); - SegmentIteratorImpl iter = inputPolygonImpl.querySegmentIterator(); - iter.resetToPath(iRing); - if (iter.nextPath()) { - while (iter.hasNextSegment()) { - Segment segment = iter.nextSegment(); - if (helper.processSegment(segment)) - return -1; // point on boundary + if (quadTree != null) { + Envelope2D queryEnv = new Envelope2D(); + queryEnv.setCoords(env); + queryEnv.xmax = inputPoint.x + tolerance;// no need to query + // segments to + // the right of the + // point. + // Only segments to the + // left + // matter. + queryEnv.ymin = inputPoint.y - tolerance; + queryEnv.ymax = inputPoint.y + tolerance; + SegmentIteratorImpl iter = inputPolygonImpl.querySegmentIterator(); + QuadTree.QuadTreeIterator qiter = quadTree.getIterator(queryEnv, + tolerance); + + for (int qhandle = qiter.next(); qhandle != -1; qhandle = qiter + .next()) { + iter.resetToVertex(quadTree.getElement(qhandle), iRing); + if (iter.hasNextSegment()) { + if (iter.getPathIndex() != iRing) + continue; + + Segment segment = iter.nextSegment(); + if (helper.processSegment(segment)) + return -1; // point on boundary + } } - } - return helper.result(); + return helper.result(); + } else { + SegmentIteratorImpl iter = inputPolygonImpl.querySegmentIterator(); + iter.resetToPath(iRing); + + if (iter.nextPath()) { + while (iter.hasNextSegment()) { + Segment segment = iter.nextSegment(); + if (helper.processSegment(segment)) + return -1; // point on boundary + } + } + + return helper.result(); + } } public static int isPointInPolygon(Polygon inputPolygon, Point inputPoint, @@ -354,4 +387,50 @@ public static int isPointInAnyOuterRing(Polygon inputPolygon, return helper.result(); } + // Tests if Ring1 is inside Ring2. + // We assume here that the Polygon is Weak Simple. That is if one point of + // Ring1 is found to be inside of Ring2, then + // we assume that all of Ring1 is inside Ring2. + static boolean _isRingInRing2D(MultiPath polygon, int iRing1, int iRing2, + double tolerance, QuadTree quadTree) { + MultiPathImpl polygonImpl = (MultiPathImpl) polygon._getImpl(); + SegmentIteratorImpl segIter = polygonImpl.querySegmentIterator(); + segIter.resetToPath(iRing1); + if (!segIter.nextPath() || !segIter.hasNextSegment()) + throw new GeometryException("corrupted geometry"); + + int res = 2; + + while (res == 2 && segIter.hasNextSegment()) { + Segment segment = segIter.nextSegment(); + Point2D point = segment.getCoord2D(0.5); + res = PointInPolygonHelper.isPointInRing(polygonImpl, iRing2, + point, tolerance, quadTree); + } + + if (res == 2) + throw GeometryException.GeometryInternalError(); + if (res == 1) + return true; + + return false; + } + + static boolean quadTreeWillHelp(Polygon polygon, int c_queries) + { + int n = polygon.getPointCount(); + + if (n < 16) + return false; + + double c_build_quad_tree = 2.0; // what's a good constant? + double c_query_quad_tree = 1.0; // what's a good constant? + double c_point_in_polygon_brute_force = 1.0; // what's a good constant? + + double c_quad_tree = c_build_quad_tree * n + c_query_quad_tree * (Math.log((double)n) / Math.log(2.0)) * c_queries; + double c_brute_force = c_point_in_polygon_brute_force * n * c_queries; + + return c_quad_tree < c_brute_force; + } + } diff --git a/src/main/java/com/esri/core/geometry/Polygon.java b/src/main/java/com/esri/core/geometry/Polygon.java index 8faea700..0c07f6f4 100644 --- a/src/main/java/com/esri/core/geometry/Polygon.java +++ b/src/main/java/com/esri/core/geometry/Polygon.java @@ -138,5 +138,38 @@ public void interpolateAttributes(int semantics, int path_index, public int getExteriorRingCount() { return m_impl.getOGCPolygonCount(); } - + + public interface FillRule { + /** + * odd-even fill rule. This is the default value. A point is in the polygon interior if a ray + * from this point to infinity crosses odd number of segments of the polygon. + */ + public final static int enumFillRuleOddEven = 0; + /** + * winding fill rule (aka non-zero winding rule). A point is in the polygon interior if a winding number is not zero. + * To compute a winding number for a point, draw a ray from this point to infinity. If N is the number of times the ray + * crosses segments directed up and the M is the number of times it crosses segments directed down, + * then the winding number is equal to N-M. + */ + public final static int enumFillRuleWinding = 1; + }; + + /** + *Fill rule for the polygon that defines the interior of the self intersecting polygon. It affects the Simplify operation. + *Can be use by drawing code to pass around the fill rule of graphic path. + *This property is not persisted in any format yet. + */ + public void setFillRule(int rule) { + m_impl.setFillRule(rule); + } + + /** + *Fill rule for the polygon that defines the interior of the self intersecting polygon. It affects the Simplify operation. + *Changing the fill rule on the polygon that has no self intersections has no physical effect. + *Can be use by drawing code to pass around the fill rule of graphic path. + *This property is not persisted in any format yet. + */ + public int getFillRule() { + return m_impl.getFillRule(); + } } diff --git a/src/main/java/com/esri/core/geometry/PolygonUtils.java b/src/main/java/com/esri/core/geometry/PolygonUtils.java index 005446d0..9a6b52f1 100644 --- a/src/main/java/com/esri/core/geometry/PolygonUtils.java +++ b/src/main/java/com/esri/core/geometry/PolygonUtils.java @@ -24,7 +24,7 @@ package com.esri.core.geometry; -class PolygonUtils { +final class PolygonUtils { enum PiPResult { PiPOutside, PiPInside, PiPBoundary @@ -89,7 +89,7 @@ public static PiPResult isPointInRing2D(Polygon polygon, int iRing, Point2D inputPoint, double tolerance) { MultiPathImpl polygonImpl = (MultiPathImpl) polygon._getImpl(); int res = PointInPolygonHelper.isPointInRing(polygonImpl, iRing, - inputPoint, tolerance); + inputPoint, tolerance, null); if (res == 0) return PiPResult.PiPOutside; if (res == 1) @@ -242,36 +242,6 @@ else if (polygon.getType() == Geometry.Type.Envelope) { throw new GeometryException("invalid_call");// GEOMTHROW(invalid_call); } - // Tests if Ring1 is inside Ring2. - // We assume here that the Polygon is Weak Simple. That is if one point of - // Ring1 is found to be inside of Ring2, then - // we assume that all of Ring1 is inside Ring2. - static boolean _isRingInRing2D(MultiPath polygon, int iRing1, - int iRing2, double tolerance) { - MultiPathImpl polygonImpl = (MultiPathImpl)polygon._getImpl(); - SegmentIteratorImpl segIter = polygonImpl.querySegmentIterator(); - segIter.resetToPath(iRing1); - if (!segIter.nextPath() || !segIter.hasNextSegment()) - throw new GeometryException("corrupted geometry"); - - int res = 2;// 2(int)PiPResult.PiPBoundary; - - while (res == 2 /* (int)PiPResult.PiPBoundary */ - && segIter.hasNextSegment()) { - Segment segment = segIter.nextSegment(); - Point2D point = segment.getCoord2D(0.5); - res = PointInPolygonHelper.isPointInRing(polygonImpl, iRing2, - point, tolerance); - } - - if (res == 2 /* (int)PiPResult.PiPBoundary */) - throw GeometryException.GeometryInternalError(); - if (res == 1 /* (int)PiPResult.PiPInside */) - return true; - - return false; - } - private static void _testPointsInEnvelope2D(Envelope2D env2D, Point2D[] inputPoints, int count, double tolerance, PiPResult[] testResults) { diff --git a/src/main/java/com/esri/core/geometry/Polyline.java b/src/main/java/com/esri/core/geometry/Polyline.java index 2124a0f4..1b648f6c 100644 --- a/src/main/java/com/esri/core/geometry/Polyline.java +++ b/src/main/java/com/esri/core/geometry/Polyline.java @@ -48,6 +48,15 @@ public Polyline() { m_impl = new MultiPathImpl(false, vd); } + /** + * Creates a polyline with one line segment. + */ + public Polyline(Point start, Point end) { + m_impl = new MultiPathImpl(false, start.getDescription()); + startPath(start); + lineTo(end); + } + @Override public Geometry createInstance() { return new Polyline(getDescription()); diff --git a/src/main/java/com/esri/core/geometry/QuadTreeImpl.java b/src/main/java/com/esri/core/geometry/QuadTreeImpl.java index 84c16f67..3b9afd77 100644 --- a/src/main/java/com/esri/core/geometry/QuadTreeImpl.java +++ b/src/main/java/com/esri/core/geometry/QuadTreeImpl.java @@ -345,13 +345,26 @@ void removeElement(int element_handle) { } /** - * Returns the element at the given element_handle. \param element_handle + * Returns the element at the given element_handle. + * \param element_handle * The handle corresponding to the element to be retrieved. */ int getElement(int element_handle) { return getElement_(element_handle); } + + /** + * Returns a reference to the element extent at the given element_handle. + * \param element_handle + * The handle corresponding to the element to be retrieved. + */ + Envelope2D getElementExtent(int element_handle) + { + int box_handle = getBoxHandle_(element_handle); + return getBoundingBox_(box_handle); + } + /** * Returns the height of the quad at the given quad_handle. \param * quad_handle The handle corresponding to the quad. diff --git a/src/main/java/com/esri/core/geometry/RelationalOperations.java b/src/main/java/com/esri/core/geometry/RelationalOperations.java index 08d60ca3..0658a8e5 100644 --- a/src/main/java/com/esri/core/geometry/RelationalOperations.java +++ b/src/main/java/com/esri/core/geometry/RelationalOperations.java @@ -1139,32 +1139,49 @@ private static boolean polygonTouchesMultiPoint_(Polygon polygon_a, if (relation == Relation.disjoint || relation == Relation.contains) return false; - Envelope2D env_a_inflated = new Envelope2D(); - polygon_a.queryEnvelope2D(env_a_inflated); - env_a_inflated.inflate(tolerance, tolerance); + Envelope2D env_a_inflated = new Envelope2D(); + polygon_a.queryEnvelope2D(env_a_inflated); + env_a_inflated.inflate(tolerance, tolerance); - Point2D ptB = new Point2D(); - boolean b_boundary = false; + Point2D ptB; + boolean b_boundary = false; - for (int i = 0; i < multipoint_b.getPointCount(); i++) { - multipoint_b.getXY(i, ptB); + MultiPathImpl polygon_a_impl = (MultiPathImpl)polygon_a._getImpl(); - if (!env_a_inflated.contains(ptB)) - continue; + Polygon pa = null; + Polygon p_polygon_a = null; - PolygonUtils.PiPResult result = PolygonUtils.isPointInPolygon2D( - polygon_a, ptB, tolerance); + if (PointInPolygonHelper.quadTreeWillHelp(polygon_a, multipoint_b.getPointCount()) && (polygon_a_impl._getAccelerators() == null || polygon_a_impl._getAccelerators().getQuadTree() == null)) + { + pa = new Polygon(); + polygon_a.copyTo(pa); + ((MultiPathImpl)pa._getImpl())._buildQuadTreeAccelerator(Geometry.GeometryAccelerationDegree.enumMedium); + p_polygon_a = pa; + } + else + { + p_polygon_a = polygon_a; + } - if (result == PolygonUtils.PiPResult.PiPBoundary) - b_boundary = true; - else if (result == PolygonUtils.PiPResult.PiPInside) - return false; - } + for (int i = 0; i < multipoint_b.getPointCount(); i++) + { + ptB = multipoint_b.getXY(i); - if (b_boundary) - return true; + if (!env_a_inflated.contains(ptB)) + continue; - return false; + PolygonUtils.PiPResult result = PolygonUtils.isPointInPolygon2D(p_polygon_a, ptB, tolerance); + + if (result == PolygonUtils.PiPResult.PiPBoundary) + b_boundary = true; + else if (result == PolygonUtils.PiPResult.PiPInside) + return false; + } + + if (b_boundary) + return true; + + return false; } // Returns true if polygon_a crosses multipoint_b. @@ -1179,36 +1196,56 @@ private static boolean polygonCrossesMultiPoint_(Polygon polygon_a, if (relation == Relation.disjoint || relation == Relation.contains) return false; - Envelope2D env_a = new Envelope2D(), env_a_inflated = new Envelope2D(), env_b = new Envelope2D(); - polygon_a.queryEnvelope2D(env_a); - multipoint_b.queryEnvelope2D(env_b); - env_a_inflated.setCoords(env_a); - env_a_inflated.inflate(tolerance, tolerance); - - boolean b_interior = false, b_exterior = false; - - Point2D pt_b = new Point2D(); - - for (int i = 0; i < multipoint_b.getPointCount(); i++) { - multipoint_b.getXY(i, pt_b); - - if (!env_a_inflated.contains(pt_b)) { - b_exterior = true; - } else { - PolygonUtils.PiPResult result = PolygonUtils - .isPointInPolygon2D(polygon_a, pt_b, tolerance); - - if (result == PolygonUtils.PiPResult.PiPOutside) - b_exterior = true; - else if (result == PolygonUtils.PiPResult.PiPInside) - b_interior = true; - } - - if (b_interior && b_exterior) - return true; - } - - return false; + Envelope2D env_a = new Envelope2D(), env_a_inflated = new Envelope2D(), env_b = new Envelope2D(); + polygon_a.queryEnvelope2D(env_a); + multipoint_b.queryEnvelope2D(env_b); + env_a_inflated.setCoords(env_a); + env_a_inflated.inflate(tolerance, tolerance); + + boolean b_interior = false, b_exterior = false; + + Point2D pt_b; + + MultiPathImpl polygon_a_impl = (MultiPathImpl)polygon_a._getImpl(); + + Polygon pa = null; + Polygon p_polygon_a = null; + + if (PointInPolygonHelper.quadTreeWillHelp(polygon_a, multipoint_b.getPointCount()) && (polygon_a_impl._getAccelerators() == null || polygon_a_impl._getAccelerators().getQuadTree() == null)) + { + pa = new Polygon(); + polygon_a.copyTo(pa); + ((MultiPathImpl)pa._getImpl())._buildQuadTreeAccelerator(Geometry.GeometryAccelerationDegree.enumMedium); + p_polygon_a = pa; + } + else + { + p_polygon_a = polygon_a; + } + + for (int i = 0; i < multipoint_b.getPointCount(); i++) + { + pt_b = multipoint_b.getXY(i); + + if (!env_a_inflated.contains(pt_b)) + { + b_exterior = true; + } + else + { + PolygonUtils.PiPResult result = PolygonUtils.isPointInPolygon2D(p_polygon_a, pt_b, tolerance); + + if (result == PolygonUtils.PiPResult.PiPOutside) + b_exterior = true; + else if (result == PolygonUtils.PiPResult.PiPInside) + b_interior = true; + } + + if (b_interior && b_exterior) + return true; + } + + return false; } // Returns true if polygon_a contains multipoint_b. @@ -1234,25 +1271,42 @@ private static boolean polygonContainsMultiPoint_(Polygon polygon_a, if (relation == Relation.contains) return true; - boolean b_interior = false; - Point2D ptB = new Point2D(); + boolean b_interior = false; + Point2D ptB; - for (int i = 0; i < multipoint_b.getPointCount(); i++) { - multipoint_b.getXY(i, ptB); + MultiPathImpl polygon_a_impl = (MultiPathImpl)polygon_a._getImpl(); - if (!env_a.contains(ptB)) - return false; + Polygon pa = null; + Polygon p_polygon_a = null; - PolygonUtils.PiPResult result = PolygonUtils.isPointInPolygon2D( - polygon_a, ptB, tolerance); + if (PointInPolygonHelper.quadTreeWillHelp(polygon_a, multipoint_b.getPointCount()) && (polygon_a_impl._getAccelerators() == null || polygon_a_impl._getAccelerators().getQuadTree() == null)) + { + pa = new Polygon(); + polygon_a.copyTo(pa); + ((MultiPathImpl)pa._getImpl())._buildQuadTreeAccelerator(Geometry.GeometryAccelerationDegree.enumMedium); + p_polygon_a = pa; + } + else + { + p_polygon_a = polygon_a; + } - if (result == PolygonUtils.PiPResult.PiPInside) - b_interior = true; - else if (result == PolygonUtils.PiPResult.PiPOutside) - return false; - } + for (int i = 0; i < multipoint_b.getPointCount(); i++) + { + ptB = multipoint_b.getXY(i); - return b_interior; + if (!env_a.contains(ptB)) + return false; + + PolygonUtils.PiPResult result = PolygonUtils.isPointInPolygon2D(p_polygon_a, ptB, tolerance); + + if (result == PolygonUtils.PiPResult.PiPInside) + b_interior = true; + else if (result == PolygonUtils.PiPResult.PiPOutside) + return false; + } + + return b_interior; } // Returns true if polygon_a equals envelope_b. @@ -1540,6 +1594,14 @@ private static boolean polylineDisjointPolyline_(Polyline polyline_a, false) == Relation.disjoint) return true; + MultiPathImpl multi_path_impl_a = (MultiPathImpl)polyline_a._getImpl(); + MultiPathImpl multi_path_impl_b = (MultiPathImpl)polyline_b._getImpl(); + + PairwiseIntersectorImpl intersector_paths = new PairwiseIntersectorImpl(multi_path_impl_a, multi_path_impl_b, tolerance, true); + + if (!intersector_paths.next()) + return false; + return !linearPathIntersectsLinearPath_(polyline_a, polyline_b, tolerance); } @@ -1716,14 +1778,16 @@ private static boolean polylineTouchesMultiPoint_(Polyline polyline_a, envInter.setCoords(env_a); envInter.intersect(env_b); - QuadTreeImpl qtA; - QuadTreeImpl quadTreeA; + QuadTreeImpl qtA = null; + QuadTreeImpl quadTreeA = null; + QuadTreeImpl quadTreePathsA = null; GeometryAccelerators accel = ((MultiPathImpl) (polyline_a._getImpl())) ._getAccelerators(); if (accel != null) { quadTreeA = accel.getQuadTree(); + quadTreePathsA = accel.getQuadTreeForPaths(); if (quadTreeA == null) { qtA = InternalUtils.buildQuadTree( (MultiPathImpl) polyline_a._getImpl(), envInter); @@ -1737,6 +1801,10 @@ private static boolean polylineTouchesMultiPoint_(Polyline polyline_a, QuadTreeImpl.QuadTreeIteratorImpl qtIterA = quadTreeA.getIterator(); + QuadTreeImpl.QuadTreeIteratorImpl qtIterPathsA = null; + if (quadTreePathsA != null) + qtIterPathsA = quadTreePathsA.getIterator(); + Point2D ptB = new Point2D(), closest = new Point2D(); boolean b_intersects = false; double toleranceSq = tolerance * tolerance; @@ -1755,6 +1823,14 @@ private static boolean polylineTouchesMultiPoint_(Polyline polyline_a, } env_b.setCoords(ptB.x, ptB.y, ptB.x, ptB.y); + + if (qtIterPathsA != null) { + qtIterPathsA.resetIterator(env_b, tolerance); + + if (qtIterPathsA.next() == -1) + continue; + } + qtIterA.resetIterator(env_b, tolerance); for (int elementHandleA = qtIterA.next(); elementHandleA != -1; elementHandleA = qtIterA @@ -1818,14 +1894,16 @@ private static boolean polylineCrossesMultiPoint_(Polyline polyline_a, envInter.setCoords(env_a); envInter.intersect(env_b); - QuadTreeImpl qtA; - QuadTreeImpl quadTreeA; + QuadTreeImpl qtA = null; + QuadTreeImpl quadTreeA = null; + QuadTreeImpl quadTreePathsA = null; GeometryAccelerators accel = ((MultiPathImpl) (polyline_a._getImpl())) ._getAccelerators(); if (accel != null) { quadTreeA = accel.getQuadTree(); + quadTreePathsA = accel.getQuadTreeForPaths(); if (quadTreeA == null) { qtA = InternalUtils.buildQuadTree( (MultiPathImpl) polyline_a._getImpl(), envInter); @@ -1839,7 +1917,11 @@ private static boolean polylineCrossesMultiPoint_(Polyline polyline_a, QuadTreeImpl.QuadTreeIteratorImpl qtIterA = quadTreeA.getIterator(); - Point2D ptB = new Point2D(), closest = new Point2D(); + QuadTreeImpl.QuadTreeIteratorImpl qtIterPathsA = null; + if (quadTreePathsA != null) + qtIterPathsA = quadTreePathsA.getIterator(); + + Point2D ptB = new Point2D(), closest = new Point2D(); boolean b_intersects = false; boolean b_exterior_found = false; double toleranceSq = tolerance * tolerance; @@ -1859,6 +1941,16 @@ private static boolean polylineCrossesMultiPoint_(Polyline polyline_a, } env_b.setCoords(ptB.x, ptB.y, ptB.x, ptB.y); + + if (qtIterPathsA != null) { + qtIterPathsA.resetIterator(env_b, tolerance); + + if (qtIterPathsA.next() == -1) { + b_exterior_found = true; + continue; + } + } + qtIterA.resetIterator(env_b, tolerance); boolean b_covered = false; @@ -3071,71 +3163,90 @@ private static boolean envelopeCrossesEnvelope_(Envelope2D env_a, private static boolean polygonDisjointMultiPath_(Polygon polygon_a, MultiPath multipath_b, double tolerance, ProgressTracker progress_tracker) { - Point2D pt_a, pt_b; - Envelope2D env_a_inf = new Envelope2D(), env_b_inf = new Envelope2D(); - - MultiPathImpl multi_path_impl_a = (MultiPathImpl) polygon_a._getImpl(); - MultiPathImpl multi_path_impl_b = (MultiPathImpl) multipath_b - ._getImpl(); - - boolean b_simple_a = multi_path_impl_a.getIsSimple(0.0) >= 1; - boolean b_simple_b = multi_path_impl_b.getIsSimple(0.0) >= 1; - Envelope2DIntersectorImpl intersector = InternalUtils - .getEnvelope2DIntersectorForParts(multi_path_impl_a, - multi_path_impl_b, tolerance, b_simple_a, b_simple_b); - - if (intersector != null) { - if (!intersector.next()) { - return true; // no rings intersect - } - } else { - return true; // no rings intersect - } - - boolean b_intersects = linearPathIntersectsLinearPath_(polygon_a, - multipath_b, tolerance); - - if (b_intersects) { - return false; - } - - do { - int index_a = intersector.getHandleA(); - int index_b = intersector.getHandleB(); - int path_a = intersector.getRedElement(index_a); - int path_b = intersector.getBlueElement(index_b); - - pt_b = multipath_b.getXY(multipath_b.getPathStart(path_b)); - env_a_inf.setCoords(intersector.getRedEnvelope(index_a)); - env_a_inf.inflate(tolerance, tolerance); - - if (env_a_inf.contains(pt_b)) { - PolygonUtils.PiPResult result = PolygonUtils - .isPointInPolygon2D(polygon_a, pt_b, 0.0); - - if (result != PolygonUtils.PiPResult.PiPOutside) { - return false; - } - } - - if (multipath_b.getType() == Geometry.Type.Polygon) { - pt_a = polygon_a.getXY(polygon_a.getPathStart(path_a)); - env_b_inf.setCoords(intersector.getBlueEnvelope(index_b)); - env_b_inf.inflate(tolerance, tolerance); - - if (env_b_inf.contains(pt_a)) { - PolygonUtils.PiPResult result = PolygonUtils - .isPointInPolygon2D((Polygon) multipath_b, pt_a, - 0.0); - - if (result != PolygonUtils.PiPResult.PiPOutside) { - return false; - } - } - } - } while (intersector.next()); - - return true; + Point2D pt_a, pt_b; + Envelope2D env_a_inf = new Envelope2D(), env_b_inf = new Envelope2D(); + + MultiPathImpl multi_path_impl_a = (MultiPathImpl)polygon_a._getImpl(); + MultiPathImpl multi_path_impl_b = (MultiPathImpl)multipath_b._getImpl(); + + PairwiseIntersectorImpl intersector = new PairwiseIntersectorImpl(multi_path_impl_a, multi_path_impl_b, tolerance, true); + + if (!intersector.next()) + return true; // no rings intersect + + boolean b_intersects = linearPathIntersectsLinearPath_(polygon_a, multipath_b, tolerance); + + if (b_intersects) + return false; + + Polygon pa = null; + Polygon p_polygon_a = null; + + if (PointInPolygonHelper.quadTreeWillHelp(polygon_a, multipath_b.getPointCount()) && (multi_path_impl_a._getAccelerators() == null || multi_path_impl_a._getAccelerators().getQuadTree() == null)) + { + pa = new Polygon(); + polygon_a.copyTo(pa); + ((MultiPathImpl)pa._getImpl())._buildQuadTreeAccelerator(Geometry.GeometryAccelerationDegree.enumMedium); + p_polygon_a = pa; + } + else + { + p_polygon_a = polygon_a; + } + + Polygon pb = null; + Polygon p_polygon_b = null; + + if (multipath_b.getType().value() == Geometry.GeometryType.Polygon) + { + Polygon polygon_b = (Polygon)multipath_b; + if (PointInPolygonHelper.quadTreeWillHelp(polygon_b, polygon_a.getPointCount()) && (multi_path_impl_b._getAccelerators() == null || multi_path_impl_b._getAccelerators().getQuadTree() == null)) + { + pb = new Polygon(); + polygon_b.copyTo(pb); + ((MultiPathImpl)pb._getImpl())._buildQuadTreeAccelerator(Geometry.GeometryAccelerationDegree.enumMedium); + p_polygon_b = pb; + } + else + { + p_polygon_b = (Polygon)multipath_b; + } + } + + do + { + int path_a = intersector.getRedElement(); + int path_b = intersector.getBlueElement(); + + pt_b = multipath_b.getXY(multipath_b.getPathStart(path_b)); + env_a_inf.setCoords(intersector.getRedEnvelope()); + env_a_inf.inflate(tolerance, tolerance); + + if (env_a_inf.contains(pt_b)) + { + PolygonUtils.PiPResult result = PolygonUtils.isPointInPolygon2D(p_polygon_a, pt_b, 0.0); + + if (result != PolygonUtils.PiPResult.PiPOutside) + return false; + } + + if (multipath_b.getType().value() == Geometry.GeometryType.Polygon) + { + pt_a = polygon_a.getXY(polygon_a.getPathStart(path_a)); + env_b_inf.setCoords(intersector.getBlueEnvelope()); + env_b_inf.inflate(tolerance, tolerance); + + if (env_b_inf.contains(pt_a)) + { + PolygonUtils.PiPResult result = PolygonUtils.isPointInPolygon2D(p_polygon_b, pt_a, 0.0); + + if (result != PolygonUtils.PiPResult.PiPOutside) + return false; + } + } + } while (intersector.next()); + + return true; } // Returns true if env_a inflated contains env_b. @@ -3448,14 +3559,16 @@ private static boolean linearPathWithinLinearPath_(MultiPath multipathA, SegmentIteratorImpl segIterB = ((MultiPathImpl) multipathB._getImpl()) .querySegmentIterator(); - QuadTreeImpl qtB; - QuadTreeImpl quadTreeB; + QuadTreeImpl qtB = null; + QuadTreeImpl quadTreeB = null; + QuadTreeImpl quadTreePathsB = null; GeometryAccelerators accel = ((MultiPathImpl) multipathB._getImpl()) ._getAccelerators(); if (accel != null) { quadTreeB = accel.getQuadTree(); + quadTreePathsB = accel.getQuadTreeForPaths(); if (quadTreeB == null) { qtB = InternalUtils.buildQuadTree( (MultiPathImpl) multipathB._getImpl(), envInter); @@ -3469,6 +3582,10 @@ private static boolean linearPathWithinLinearPath_(MultiPath multipathA, QuadTreeImpl.QuadTreeIteratorImpl qtIterB = quadTreeB.getIterator(); + QuadTreeImpl.QuadTreeIteratorImpl qtIterPathsB = null; + if (quadTreePathsB != null) + qtIterPathsB = quadTreePathsB.getIterator(); + while (segIterA.nextPath()) { while (segIterA.hasNextSegment()) { boolean bStringOfSegmentAsCovered = false; @@ -3480,6 +3597,15 @@ private static boolean linearPathWithinLinearPath_(MultiPath multipathA, return false; // bWithin = false } + if (qtIterPathsB != null) { + qtIterPathsB.resetIterator(env_a, tolerance); + + if (qtIterPathsB.next() == -1) { + bWithin = false; + return false; + } + } + double lengthA = segmentA.calculateLength2D(); qtIterB.resetIterator(segmentA, tolerance); @@ -3709,14 +3835,16 @@ static int linearPathIntersectsLinearPathMaxDim_(MultiPath _multipathA, int_point = new Point2D(); } - QuadTreeImpl qtB; - QuadTreeImpl quadTreeB; + QuadTreeImpl qtB = null; + QuadTreeImpl quadTreeB = null; + QuadTreeImpl quadTreePathsB = null; GeometryAccelerators accel = ((MultiPathImpl) multipathB._getImpl()) ._getAccelerators(); if (accel != null) { quadTreeB = accel.getQuadTree(); + quadTreePathsB = accel.getQuadTreeForPaths(); if (quadTreeB == null) { qtB = InternalUtils.buildQuadTree( (MultiPathImpl) multipathB._getImpl(), envInter); @@ -3730,6 +3858,10 @@ static int linearPathIntersectsLinearPathMaxDim_(MultiPath _multipathA, QuadTreeImpl.QuadTreeIteratorImpl qtIterB = quadTreeB.getIterator(); + QuadTreeImpl.QuadTreeIteratorImpl qtIterPathsB = null; + if (quadTreePathsB != null) + qtIterPathsB = quadTreePathsB.getIterator(); + while (segIterA.nextPath()) { overlapLength = 0.0; @@ -3741,6 +3873,13 @@ static int linearPathIntersectsLinearPathMaxDim_(MultiPath _multipathA, continue; } + if (qtIterPathsB != null) { + qtIterPathsB.resetIterator(env_a, tolerance); + + if (qtIterPathsB.next() == -1) + continue; + } + double lengthA = segmentA.calculateLength2D(); qtIterB.resetIterator(segmentA, tolerance); @@ -3954,12 +4093,11 @@ private static boolean linearPathIntersectsLinearPath_( SegmentIteratorImpl segIterA = multi_path_impl_a.querySegmentIterator(); SegmentIteratorImpl segIterB = multi_path_impl_b.querySegmentIterator(); - Pair_wise_intersector intersector = new Pair_wise_intersector( - multi_path_impl_a, multi_path_impl_b, tolerance); + PairwiseIntersectorImpl intersector = new PairwiseIntersectorImpl(multi_path_impl_a, multi_path_impl_b, tolerance, false); while (intersector.next()) { - int vertex_a = intersector.get_red_element(); - int vertex_b = intersector.get_blue_element(); + int vertex_a = intersector.getRedElement(); + int vertex_b = intersector.getBlueElement(); segIterA.resetToVertex(vertex_a); segIterB.resetToVertex(vertex_b); @@ -4006,6 +4144,7 @@ private static boolean linearPathIntersectsMultiPoint_( QuadTreeImpl qtA = null; QuadTreeImpl quadTreeA = null; + QuadTreeImpl quadTreePathsA = null; GeometryAccelerators accel = ((MultiPathImpl) multipathA._getImpl()) ._getAccelerators(); @@ -4024,6 +4163,11 @@ private static boolean linearPathIntersectsMultiPoint_( } QuadTreeImpl.QuadTreeIteratorImpl qtIterA = quadTreeA.getIterator(); + + QuadTreeImpl.QuadTreeIteratorImpl qtIterPathsA = null; + if (quadTreePathsA != null) + qtIterPathsA = quadTreePathsA.getIterator(); + Point2D ptB = new Point2D(), closest = new Point2D(); boolean b_intersects = false; double toleranceSq = tolerance * tolerance; @@ -4035,8 +4179,15 @@ private static boolean linearPathIntersectsMultiPoint_( continue; } - boolean bPtBContained = false; env_b.setCoords(ptB.x, ptB.y, ptB.x, ptB.y); + + if (qtIterPathsA != null) { + qtIterPathsA.resetIterator(env_b, tolerance); + + if (qtIterPathsA.next() == -1) + continue; + } + qtIterA.resetIterator(env_b, tolerance); boolean b_covered = false; @@ -4341,14 +4492,14 @@ private static boolean polygonTouchesPolygonImpl_(Polygon polygon_a, double[] scalarsA = new double[2]; double[] scalarsB = new double[2]; - Pair_wise_intersector intersector = new Pair_wise_intersector( - polygon_impl_a, polygon_impl_b, tolerance); + PairwiseIntersectorImpl intersector = new PairwiseIntersectorImpl( + polygon_impl_a, polygon_impl_b, tolerance, false); boolean b_boundaries_intersect = false; while (intersector.next()) { - int vertex_a = intersector.get_red_element(); - int vertex_b = intersector.get_blue_element(); + int vertex_a = intersector.getRedElement(); + int vertex_b = intersector.getBlueElement(); segIterA.resetToVertex(vertex_a); segIterB.resetToVertex(vertex_b); @@ -4450,12 +4601,12 @@ private static boolean polygonOverlapsPolygonImpl_(Polygon polygon_a, double[] scalarsA = new double[2]; double[] scalarsB = new double[2]; - Pair_wise_intersector intersector = new Pair_wise_intersector( - polygon_impl_a, polygon_impl_b, tolerance); + PairwiseIntersectorImpl intersector = new PairwiseIntersectorImpl( + polygon_impl_a, polygon_impl_b, tolerance, false); while (intersector.next()) { - int vertex_a = intersector.get_red_element(); - int vertex_b = intersector.get_blue_element(); + int vertex_a = intersector.getRedElement(); + int vertex_b = intersector.getBlueElement(); segIterA.resetToVertex(vertex_a); segIterB.resetToVertex(vertex_b); @@ -4552,65 +4703,174 @@ private static boolean polygonOverlapsPolygonImpl_(Polygon polygon_a, private static boolean polygonContainsPolygonImpl_(Polygon polygon_a, Polygon polygon_b, double tolerance, ProgressTracker progressTracker) { - MultiPathImpl polygon_impl_a = (MultiPathImpl) polygon_a._getImpl(); - MultiPathImpl polygon_impl_b = (MultiPathImpl) polygon_b._getImpl(); - - SegmentIteratorImpl segIterA = polygon_impl_a.querySegmentIterator(); - SegmentIteratorImpl segIterB = polygon_impl_b.querySegmentIterator(); - double[] scalarsA = new double[2]; - double[] scalarsB = new double[2]; - - Pair_wise_intersector intersector = new Pair_wise_intersector( - polygon_impl_a, polygon_impl_b, tolerance); - - while (intersector.next()) { - int vertex_a = intersector.get_red_element(); - int vertex_b = intersector.get_blue_element(); - - segIterA.resetToVertex(vertex_a); - segIterB.resetToVertex(vertex_b); - Segment segmentA = segIterA.nextSegment(); - Segment segmentB = segIterB.nextSegment(); - - int result = segmentB.intersect(segmentA, null, scalarsB, scalarsA, - tolerance); - - if (result == 1) { - double scalar_a_0 = scalarsA[0]; - double scalar_b_0 = scalarsB[0]; - - if (scalar_a_0 > 0.0 && scalar_a_0 < 1.0 && scalar_b_0 > 0.0 - && scalar_b_0 < 1.0) { - return false; - } - } - } - - // We can clip polygon_a to the extent of polyline_b - Envelope2D envBInflated = new Envelope2D(); - polygon_b.queryEnvelope2D(envBInflated); - envBInflated.inflate(1000.0 * tolerance, 1000.0 * tolerance); - - Polygon _polygonA; - - if (polygon_a.getPointCount() > 10) { - _polygonA = (Polygon) (Clipper.clip(polygon_a, envBInflated, - tolerance, 0.0)); - if (_polygonA.isEmpty()) { - return false; - } - } else { - _polygonA = polygon_a; - } - - String scl = "T*****F**"; // If Exterior-Interior is false, then - // Exterior-Boundary is false - - boolean bRelation = RelationalOperationsMatrix.polygonRelatePolygon_( - _polygonA, polygon_b, tolerance, scl, progressTracker); - return bRelation; + boolean[] b_result_known = new boolean[1]; + b_result_known[0] = false; + boolean res = polygonContainsMultiPath_(polygon_a, polygon_b, tolerance, b_result_known, progressTracker); + + if (b_result_known[0]) + return res; + + // We can clip polygon_a to the extent of polyline_b + + Envelope2D envBInflated = new Envelope2D(); + polygon_b.queryEnvelope2D(envBInflated); + envBInflated.inflate(1000.0 * tolerance, 1000.0 * tolerance); + + Polygon _polygonA = null; + + if (polygon_a.getPointCount() > 10) + { + _polygonA = (Polygon)Clipper.clip(polygon_a, envBInflated, tolerance, 0.0); + if (_polygonA.isEmpty()) + return false; + } + else + { + _polygonA = polygon_a; + } + + boolean bContains = RelationalOperationsMatrix.polygonContainsPolygon_(_polygonA, polygon_b, tolerance, progressTracker); + return bContains; } + private static boolean polygonContainsMultiPath_(Polygon polygon_a, MultiPath multi_path_b, double tolerance, boolean[] b_result_known, ProgressTracker progress_tracker) + { + b_result_known[0] = false; + + MultiPathImpl polygon_impl_a = (MultiPathImpl)polygon_a._getImpl(); + MultiPathImpl multi_path_impl_b = (MultiPathImpl)multi_path_b._getImpl(); + + SegmentIteratorImpl segIterA = polygon_impl_a.querySegmentIterator(); + SegmentIteratorImpl segIterB = multi_path_impl_b.querySegmentIterator(); + double[] scalarsA = new double[2]; + double[] scalarsB = new double[2]; + + PairwiseIntersectorImpl intersector = new PairwiseIntersectorImpl(polygon_impl_a, multi_path_impl_b, tolerance, false); + boolean b_boundaries_intersect = false; + + while (intersector.next()) + { + b_boundaries_intersect = true; + int vertex_a = intersector.getRedElement(); + int vertex_b = intersector.getBlueElement(); + + segIterA.resetToVertex(vertex_a, -1); + segIterB.resetToVertex(vertex_b, -1); + Segment segmentA = segIterA.nextSegment(); + Segment segmentB = segIterB.nextSegment(); + + int result = segmentB.intersect(segmentA, null, scalarsB, scalarsA, tolerance); + + if (result == 1) + { + double scalar_a_0 = scalarsA[0]; + double scalar_b_0 = scalarsB[0]; + + if (scalar_a_0 > 0.0 && scalar_a_0 < 1.0 && scalar_b_0 > 0.0 && scalar_b_0 < 1.0) + { + b_result_known[0] = true; + return false; + } + } + } + + if (!b_boundaries_intersect) + { + b_result_known[0] = true; + + //boundaries do not intersect + + Envelope2D env_a_inflated = new Envelope2D(); + polygon_a.queryEnvelope2D(env_a_inflated); + env_a_inflated.inflate(tolerance, tolerance); + + Polygon pa = null; + Polygon p_polygon_a = null; + + if (PointInPolygonHelper.quadTreeWillHelp(polygon_a, multi_path_b.getPointCount()) && (polygon_impl_a._getAccelerators() == null || polygon_impl_a._getAccelerators().getQuadTree() == null)) + { + pa = new Polygon(); + polygon_a.copyTo(pa); + ((MultiPathImpl)pa._getImpl())._buildQuadTreeAccelerator(Geometry.GeometryAccelerationDegree.enumMedium); + p_polygon_a = pa; + } + else + { + p_polygon_a = polygon_a; + } + + Envelope2D path_env_b = new Envelope2D(); + + for (int ipath = 0, npath = multi_path_b.getPathCount(); ipath < npath; ipath++) + { + if (multi_path_b.getPathSize(ipath) > 0) + { + multi_path_b.queryPathEnvelope2D(ipath, path_env_b); + + if (env_a_inflated.isIntersecting(path_env_b)) + { + Point2D anyPoint = multi_path_b.getXY(multi_path_b.getPathStart(ipath)); + int res = PointInPolygonHelper.isPointInPolygon(p_polygon_a, anyPoint, 0); + if (res == 0) + return false; + } + else + { + return false; + } + } + } + + if (polygon_a.getPathCount() == 1 || multi_path_b.getType().value() == Geometry.GeometryType.Polyline) + return true; //boundaries do not intersect. all paths of b are inside of a + + // Polygon A has multiple rings, and Multi_path B is a polygon. + + Polygon polygon_b = (Polygon)multi_path_b; + + Envelope2D env_b_inflated = new Envelope2D(); + polygon_b.queryEnvelope2D(env_b_inflated); + env_b_inflated.inflate(tolerance, tolerance); + + Polygon pb = null; + Polygon p_polygon_b = null; + + if (PointInPolygonHelper.quadTreeWillHelp(polygon_b, polygon_a.getPointCount()) && (multi_path_impl_b._getAccelerators() == null || multi_path_impl_b._getAccelerators().getQuadTree() == null)) + { + pb = new Polygon(); + polygon_b.copyTo(pb); + ((MultiPathImpl)pb._getImpl())._buildQuadTreeAccelerator(Geometry.GeometryAccelerationDegree.enumMedium); + p_polygon_b = pb; + } + else + { + p_polygon_b = polygon_b; + } + + Envelope2D path_env_a = new Envelope2D(); + + for (int ipath = 0, npath = polygon_a.getPathCount(); ipath < npath; ipath++) + { + if (polygon_a.getPathSize(ipath) > 0) + { + polygon_a.queryPathEnvelope2D(ipath, path_env_a); + + if (env_b_inflated.isIntersecting(path_env_a)) + { + Point2D anyPoint = polygon_a.getXY(polygon_a.getPathStart(ipath)); + int res = PointInPolygonHelper.isPointInPolygon(p_polygon_b, anyPoint, 0); + if (res == 1) + return false; + } + } + } + + return true; + } + + return false; + } + private static boolean polygonTouchesPolylineImpl_(Polygon polygon_a, Polyline polyline_b, double tolerance, ProgressTracker progressTracker) { @@ -4622,14 +4882,14 @@ private static boolean polygonTouchesPolylineImpl_(Polygon polygon_a, double[] scalarsA = new double[2]; double[] scalarsB = new double[2]; - Pair_wise_intersector intersector = new Pair_wise_intersector( - polygon_impl_a, polyline_impl_b, tolerance); + PairwiseIntersectorImpl intersector = new PairwiseIntersectorImpl( + polygon_impl_a, polyline_impl_b, tolerance, false); boolean b_boundaries_intersect = false; while (intersector.next()) { - int vertex_a = intersector.get_red_element(); - int vertex_b = intersector.get_blue_element(); + int vertex_a = intersector.getRedElement(); + int vertex_b = intersector.getBlueElement(); segIterA.resetToVertex(vertex_a); segIterB.resetToVertex(vertex_b); @@ -4708,14 +4968,14 @@ private static boolean polygonCrossesPolylineImpl_(Polygon polygon_a, double[] scalarsA = new double[2]; double[] scalarsB = new double[2]; - Pair_wise_intersector intersector = new Pair_wise_intersector( - polygon_impl_a, polyline_impl_b, tolerance); + PairwiseIntersectorImpl intersector = new PairwiseIntersectorImpl( + polygon_impl_a, polyline_impl_b, tolerance, false); boolean b_boundaries_intersect = false; while (intersector.next()) { - int vertex_a = intersector.get_red_element(); - int vertex_b = intersector.get_blue_element(); + int vertex_a = intersector.getRedElement(); + int vertex_b = intersector.getBlueElement(); segIterA.resetToVertex(vertex_a); segIterB.resetToVertex(vertex_b); @@ -4796,82 +5056,34 @@ private static boolean polygonCrossesPolylineImpl_(Polygon polygon_a, private static boolean polygonContainsPolylineImpl_(Polygon polygon_a, Polyline polyline_b, double tolerance, ProgressTracker progress_tracker) { - MultiPathImpl polygon_impl_a = (MultiPathImpl) polygon_a._getImpl(); - MultiPathImpl polyline_impl_b = (MultiPathImpl) polyline_b._getImpl(); - - SegmentIteratorImpl segIterA = polygon_impl_a.querySegmentIterator(); - SegmentIteratorImpl segIterB = polyline_impl_b.querySegmentIterator(); - double[] scalarsA = new double[2]; - double[] scalarsB = new double[2]; - - Pair_wise_intersector intersector = new Pair_wise_intersector( - polygon_impl_a, polyline_impl_b, tolerance); - - boolean b_boundaries_intersect = false; - - while (intersector.next()) { - int vertex_a = intersector.get_red_element(); - int vertex_b = intersector.get_blue_element(); - - segIterA.resetToVertex(vertex_a); - segIterB.resetToVertex(vertex_b); - Segment segmentA = segIterA.nextSegment(); - Segment segmentB = segIterB.nextSegment(); - - int result = segmentB.intersect(segmentA, null, scalarsB, scalarsA, - tolerance); - - if (result == 2) { - b_boundaries_intersect = true; - // Keep going to see if we find a proper intersection of two - // segments (means contains is false) - } else if (result != 0) { - double scalar_a_0 = scalarsA[0]; - double scalar_b_0 = scalarsB[0]; - - if (scalar_a_0 > 0.0 && scalar_a_0 < 1.0 && scalar_b_0 > 0.0 - && scalar_b_0 < 1.0) { - return false; - } - - b_boundaries_intersect = true; - // Keep going to see if we find a proper intersection of two - // segments (means contains is false) - } - } - - if (!b_boundaries_intersect) { - segIterB.resetToVertex(0); - Segment segmentB = segIterB.nextSegment(); - - Point2D ptB = new Point2D(); - segmentB.getCoord2D(0.5, ptB); - return (PolygonUtils.isPointInPolygon2D(polygon_a, ptB, 0.0) == PolygonUtils.PiPResult.PiPInside); - } - - // We can clip polygon_a to the extent of polyline_b - Envelope2D envBInflated = new Envelope2D(); - polyline_b.queryEnvelope2D(envBInflated); - envBInflated.inflate(1000.0 * tolerance, 1000.0 * tolerance); - - Polygon _polygonA; - - if (polygon_a.getPointCount() > 10) { - _polygonA = (Polygon) (Clipper.clip(polygon_a, envBInflated, - tolerance, 0.0)); - if (_polygonA.isEmpty()) { - return false; - } - } else { - _polygonA = polygon_a; - } - - String scl = "T*****F**"; // If Exterior-Interior is false, then - // Exterior-Boundary is false - boolean bRelation = RelationalOperationsMatrix.polygonRelatePolyline_( - _polygonA, polyline_b, tolerance, scl, progress_tracker); - - return bRelation; + boolean[] b_result_known = new boolean[1]; + b_result_known[0] = false; + boolean res = polygonContainsMultiPath_(polygon_a, polyline_b, tolerance, b_result_known, progress_tracker); + + if (b_result_known[0]) + return res; + + // We can clip polygon_a to the extent of polyline_b + + Envelope2D envBInflated = new Envelope2D(); + polyline_b.queryEnvelope2D(envBInflated); + envBInflated.inflate(1000.0 * tolerance, 1000.0 * tolerance); + + Polygon _polygonA = null; + + if (polygon_a.getPointCount() > 10) + { + _polygonA = (Polygon)Clipper.clip(polygon_a, envBInflated, tolerance, 0.0); + if (_polygonA.isEmpty()) + return false; + } + else + { + _polygonA = polygon_a; + } + + boolean bContains = RelationalOperationsMatrix.polygonContainsPolyline_(_polygonA, polyline_b, tolerance, progress_tracker); + return bContains; } private static boolean polygonContainsPointImpl_(Polygon polygon_a, @@ -4987,171 +5199,6 @@ int compareOverlapEvents_(int o_1, int o_2) { return 1; } - static final class Pair_wise_intersector { - - Pair_wise_intersector(MultiPathImpl multi_path_impl_a, - MultiPathImpl multi_path_impl_b, double tolerance) { - m_b_quad_tree = false; - - GeometryAccelerators geometry_accelerators_a = multi_path_impl_a - ._getAccelerators(); - - if (geometry_accelerators_a != null) { - QuadTreeImpl qtree_a = geometry_accelerators_a.getQuadTree(); - - if (qtree_a != null) { - m_b_done = false; - m_tolerance = tolerance; - m_quad_tree = qtree_a; - m_qt_iter = m_quad_tree.getIterator(); - m_b_quad_tree = true; - m_b_swap_elements = true; - m_seg_iter = multi_path_impl_b.querySegmentIterator(); - m_function = State.next_path; - } - } - - if (!m_b_quad_tree) { - GeometryAccelerators geometry_accelerators_b = multi_path_impl_b - ._getAccelerators(); - - if (geometry_accelerators_b != null) { - QuadTreeImpl qtree_b = geometry_accelerators_b - .getQuadTree(); - - if (qtree_b != null) { - m_b_done = false; - m_tolerance = tolerance; - m_quad_tree = qtree_b; - m_qt_iter = m_quad_tree.getIterator(); - m_b_quad_tree = true; - m_b_swap_elements = false; - m_seg_iter = multi_path_impl_a.querySegmentIterator(); - m_function = State.next_path; - } - } - } - - if (!m_b_quad_tree) { - m_intersector = InternalUtils.getEnvelope2DIntersector( - multi_path_impl_a, multi_path_impl_b, tolerance); - } - } - - boolean next() { - if (m_b_quad_tree) { - if (m_b_done) { - return false; - } - - boolean b_searching = true; - while (b_searching) { - switch (m_function) { - case State.next_path: - b_searching = next_path_(); - break; - case State.next_segment: - b_searching = next_segment_(); - break; - case State.iterate: - b_searching = iterate_(); - break; - } - - } - - if (m_b_done) { - return false; - } - - return true; - } - - if (m_intersector == null) { - return false; - } - - return m_intersector.next(); - } - - int get_red_element() { - if (m_b_quad_tree) { - if (!m_b_swap_elements) { - return m_seg_iter.getStartPointIndex(); - } - - return m_quad_tree.getElement(m_element_handle); - } - - return m_intersector.getRedElement(m_intersector.getHandleA()); - } - - int get_blue_element() { - if (m_b_quad_tree) { - if (m_b_swap_elements) { - return m_seg_iter.getStartPointIndex(); - } - - return m_quad_tree.getElement(m_element_handle); - } - - return m_intersector.getBlueElement(m_intersector.getHandleB()); - } - - private boolean next_path_() { - if (!m_seg_iter.nextPath()) { - m_b_done = true; - return false; - } - - m_function = State.next_segment; - return true; - } - - private boolean next_segment_() { - if (!m_seg_iter.hasNextSegment()) { - m_function = State.next_path; - return true; - } - - Segment segment = m_seg_iter.nextSegment(); - m_qt_iter.resetIterator(segment, m_tolerance); - m_function = State.iterate; - return true; - } - - boolean iterate_() { - m_element_handle = m_qt_iter.next(); - if (m_element_handle == -1) { - m_function = State.next_segment; - return true; - } - - return false; - } - - private interface State { - - static final int next_path = 0; - static final int next_segment = 1; - static final int iterate = 2; - } - - // Quad_tree - private boolean m_b_quad_tree; - private boolean m_b_done; - private boolean m_b_swap_elements; - private double m_tolerance; - private int m_element_handle; - private QuadTreeImpl m_quad_tree; - private QuadTreeImpl.QuadTreeIteratorImpl m_qt_iter; - private SegmentIteratorImpl m_seg_iter; - private int m_function; - - // Envelope_2D_intersector - private Envelope2DIntersectorImpl m_intersector; - } - static final class Accelerate_helper { static boolean accelerate_geometry(Geometry geometry, SpatialReference sr, @@ -5173,18 +5220,18 @@ static boolean accelerate_geometry(Geometry geometry, bAccelerated |= ((MultiVertexGeometryImpl) geometry._getImpl()) ._buildQuadTreeAccelerator(accel_degree); - if (type == Geometry.Type.Polygon - && GeometryAccelerators.canUsePathEnvelopes(geometry) + if ((type == Geometry.Type.Polygon || type == Geometry.Type.Polyline) + && GeometryAccelerators.canUseQuadTreeForPaths(geometry) && accel_degree != Geometry.GeometryAccelerationDegree.enumMild) bAccelerated |= ((MultiPathImpl) geometry._getImpl()) - ._buildPathEnvelopesAccelerator(accel_degree); + ._buildQuadTreeForPathsAccelerator(accel_degree); return bAccelerated; } - static boolean can_accelerate_geometry(Geometry geometry) { - return GeometryAccelerators.canUseRasterizedGeometry(geometry) - || GeometryAccelerators.canUseQuadTree(geometry); - } - } + static boolean can_accelerate_geometry(Geometry geometry) { + return GeometryAccelerators.canUseRasterizedGeometry(geometry) + || GeometryAccelerators.canUseQuadTree(geometry) || GeometryAccelerators.canUseQuadTreeForPaths(geometry); + } + } } diff --git a/src/main/java/com/esri/core/geometry/RelationalOperationsMatrix.java b/src/main/java/com/esri/core/geometry/RelationalOperationsMatrix.java index a3b3f02d..1ad87dc4 100644 --- a/src/main/java/com/esri/core/geometry/RelationalOperationsMatrix.java +++ b/src/main/java/com/esri/core/geometry/RelationalOperationsMatrix.java @@ -26,9 +26,11 @@ class RelationalOperationsMatrix { private TopoGraph m_topo_graph; private int[] m_matrix; + private int[] m_max_dim; private boolean[] m_perform_predicates; private String m_scl; - private int m_predicates; + private int m_predicates_half_edge; + private int m_predicates_cluster; private int m_predicate_count; private int m_cluster_index_a; private int m_cluster_index_b; @@ -60,7 +62,19 @@ private interface Predicates { // string. static boolean relate(Geometry geometry_a, Geometry geometry_b, SpatialReference sr, String scl, ProgressTracker progress_tracker) { - int relation = getPredefinedRelation_(scl, geometry_a.getDimension(), + + if (scl.length() != 9) + throw new GeometryException("relation string length has to be 9 characters"); + + for (int i = 0; i < 9; i++) + { + char c = scl.charAt(i); + + if (c != '*' && c != 'T' && c != 'F' && c != '0' && c != '1' && c != '2') + throw new GeometryException("relation string"); + } + + int relation = getPredefinedRelation_(scl, geometry_a.getDimension(), geometry_b.getDimension()); if (relation != RelationalOperations.Relation.unknown) @@ -81,6 +95,9 @@ static boolean relate(Geometry geometry_a, Geometry geometry_b, Geometry _geometryA = convertGeometry_(geometry_a, tolerance); Geometry _geometryB = convertGeometry_(geometry_b, tolerance); + if (_geometryA.isEmpty() || _geometryB.isEmpty()) + return relateEmptyGeometries_(_geometryA, _geometryB, scl); + int typeA = _geometryA.getType().value(); int typeB = _geometryB.getType().value(); @@ -218,7 +235,10 @@ private RelationalOperationsMatrix() { m_predicate_count = 0; m_topo_graph = new TopoGraph(); m_matrix = new int[9]; + m_max_dim = new int[9]; m_perform_predicates = new boolean[9]; + m_predicates_half_edge = -1; + m_predicates_cluster = -1; } // Returns true if the relation holds. @@ -238,7 +258,7 @@ static boolean polygonRelatePolygon_(Polygon polygon_a, Polygon polygon_b, env_a, env_b, tolerance, progress_tracker); if (b_disjoint) { - relOps.areaAreaDisjointPredicates_(); + relOps.areaAreaDisjointPredicates_(polygon_a, polygon_b); bRelationKnown = true; } @@ -250,13 +270,13 @@ static boolean polygonRelatePolygon_(Polygon polygon_a, Polygon polygon_b, tolerance, false); if (relation == RelationalOperations.Relation.disjoint) { - relOps.areaAreaDisjointPredicates_(); + relOps.areaAreaDisjointPredicates_(polygon_a, polygon_b); bRelationKnown = true; } else if (relation == RelationalOperations.Relation.contains) { - relOps.areaAreaContainsPredicates_(); + relOps.areaAreaContainsPredicates_(polygon_b); bRelationKnown = true; } else if (relation == RelationalOperations.Relation.within) { - relOps.areaAreaWithinPredicates_(); + relOps.areaAreaWithinPredicates_(polygon_a); bRelationKnown = true; } } @@ -275,6 +295,123 @@ static boolean polygonRelatePolygon_(Polygon polygon_a, Polygon polygon_b, return bRelation; } + // The relation is based on the simplified-Polygon A containing Polygon B, which may be non-simple. + static boolean polygonContainsPolygon_(Polygon polygon_a, Polygon polygon_b, double tolerance, ProgressTracker progress_tracker) + { + assert(!polygon_a.isEmpty()); + assert(!polygon_b.isEmpty()); + + RelationalOperationsMatrix relOps = new RelationalOperationsMatrix(); + relOps.resetMatrix_(); + relOps.setPredicates_("T*****F**"); + relOps.setAreaAreaPredicates_(); + + Envelope2D env_a = new Envelope2D(), env_b = new Envelope2D(); + polygon_a.queryEnvelope2D(env_a); + polygon_b.queryEnvelope2D(env_b); + + boolean bRelationKnown = false; + boolean b_disjoint = RelationalOperations.envelopeDisjointEnvelope_(env_a, env_b, tolerance, progress_tracker); + + if (b_disjoint) + { + relOps.areaAreaDisjointPredicates_(polygon_a, polygon_b); + bRelationKnown = true; + } + + if (!bRelationKnown) + { + // Quick rasterize test to see whether the the geometries are disjoint, or if one is contained in the other. + int relation = RelationalOperations.tryRasterizedContainsOrDisjoint_(polygon_a, polygon_b, tolerance, false); + + if (relation == RelationalOperations.Relation.disjoint) + { + relOps.areaAreaDisjointPredicates_(polygon_a, polygon_b); + bRelationKnown = true; + } + else if (relation == RelationalOperations.Relation.contains) + { + relOps.areaAreaContainsPredicates_(polygon_b); + bRelationKnown = true; + } + else if (relation == RelationalOperations.Relation.within) + { + relOps.areaAreaWithinPredicates_(polygon_a); + bRelationKnown = true; + } + } + + if (bRelationKnown) + { + boolean bContains = relationCompare_(relOps.m_matrix, relOps.m_scl); + return bContains; + } + + EditShape edit_shape = new EditShape(); + int geom_a = edit_shape.addGeometry(polygon_a); + int geom_b = edit_shape.addGeometry(polygon_b); + + CrackAndCluster.execute(edit_shape, tolerance, progress_tracker, false); + Polyline boundary_b = (Polyline)edit_shape.getGeometry(geom_b).getBoundary(); + edit_shape.filterClosePoints(0, true, true); + Simplificator.execute(edit_shape, geom_a, -1, false, progress_tracker); + + // Make sure Polygon A has exterior + // If the simplified Polygon A does not have interior, then it cannot contain anything. + if (edit_shape.getPointCount(geom_a) == 0) + return false; + + Simplificator.execute(edit_shape, geom_b, -1, false, progress_tracker); + + relOps.setEditShape_(edit_shape, progress_tracker); + + // We see if the simplified Polygon A contains the simplified Polygon B. + + boolean b_empty = edit_shape.getPointCount(geom_b) == 0; + + if (!b_empty) + {//geom_b has interior + relOps.computeMatrixTopoGraphHalfEdges_(geom_a, geom_b); + relOps.m_topo_graph.removeShape(); + boolean bContains = relationCompare_(relOps.m_matrix, relOps.m_scl); + + if (!bContains) + return bContains; + } + + Polygon polygon_simple_a = (Polygon)edit_shape.getGeometry(geom_a); + edit_shape = new EditShape(); + geom_a = edit_shape.addGeometry(polygon_simple_a); + geom_b = edit_shape.addGeometry(boundary_b); + + int pc1 = polygon_simple_a.getPointCount(); + int pc2 = boundary_b.getPointCount(); + + for (int i = 0; i < pc2; i++) + { + Point2D p = boundary_b.getXY(i); + i = i; + } + + boolean isclosed0 = boundary_b.isClosedPath(0); + boolean isclosed1 = boundary_b.isClosedPath(1); + + String s1 = OperatorExportToJson.local().execute(null, polygon_simple_a); + String s2 = OperatorExportToJson.local().execute(null, boundary_b); + relOps.setEditShape_(edit_shape, progress_tracker); + + // Check no interior lines of the boundary intersect the exterior + relOps.m_predicate_count = 0; + relOps.resetMatrix_(); + relOps.setPredicates_(b_empty ? "T*****F**" : "******F**"); + relOps.setAreaLinePredicates_(); + + relOps.computeMatrixTopoGraphHalfEdges_(geom_a, geom_b); + relOps.m_topo_graph.removeShape(); + boolean bContains = relationCompare_(relOps.m_matrix, relOps.m_scl); + return bContains; + } + // Returns true if the relation holds. static boolean polygonRelatePolyline_(Polygon polygon_a, Polyline polyline_b, double tolerance, String scl, @@ -293,7 +430,7 @@ static boolean polygonRelatePolyline_(Polygon polygon_a, env_a, env_b, tolerance, progress_tracker); if (b_disjoint) { - relOps.areaLineDisjointPredicates_(polyline_b); // passing polyline + relOps.areaLineDisjointPredicates_(polygon_a, polyline_b); // passing polyline // to get boundary // information bRelationKnown = true; @@ -307,13 +444,13 @@ static boolean polygonRelatePolyline_(Polygon polygon_a, tolerance, false); if (relation == RelationalOperations.Relation.disjoint) { - relOps.areaLineDisjointPredicates_(polyline_b); // passing + relOps.areaLineDisjointPredicates_(polygon_a, polyline_b); // passing // polyline to // get boundary // information bRelationKnown = true; } else if (relation == RelationalOperations.Relation.contains) { - relOps.areaLineContainsPredicates_(polyline_b); // passing + relOps.areaLineContainsPredicates_(polygon_a, polyline_b); // passing // polyline to // get boundary // information @@ -341,6 +478,66 @@ static boolean polygonRelatePolyline_(Polygon polygon_a, return bRelation; } + static boolean polygonContainsPolyline_(Polygon polygon_a, Polyline polyline_b, double tolerance, ProgressTracker progress_tracker) + { + RelationalOperationsMatrix relOps = new RelationalOperationsMatrix(); + relOps.resetMatrix_(); + relOps.setPredicates_("T*****F**"); + relOps.setAreaLinePredicates_(); + + Envelope2D env_a = new Envelope2D(), env_b = new Envelope2D(); + polygon_a.queryEnvelope2D(env_a); + polyline_b.queryEnvelope2D(env_b); + + boolean bRelationKnown = false; + boolean b_disjoint = RelationalOperations.envelopeDisjointEnvelope_(env_a, env_b, tolerance, progress_tracker); + + if (b_disjoint) + { + relOps.areaLineDisjointPredicates_(polygon_a, polyline_b); // passing polyline to get boundary information + bRelationKnown = true; + } + + if (!bRelationKnown) + { + // Quick rasterize test to see whether the the geometries are disjoint, or if one is contained in the other. + int relation = RelationalOperations.tryRasterizedContainsOrDisjoint_(polygon_a, polyline_b, tolerance, false); + + if (relation == RelationalOperations.Relation.disjoint) + { + relOps.areaLineDisjointPredicates_(polygon_a, polyline_b); // passing polyline to get boundary information + bRelationKnown = true; + } + else if (relation == RelationalOperations.Relation.contains) + { + relOps.areaLineContainsPredicates_(polygon_a, polyline_b); // passing polyline to get boundary information + bRelationKnown = true; + } + } + + if (bRelationKnown) + { + boolean bContains = relationCompare_(relOps.m_matrix, relOps.m_scl); + return bContains; + } + + EditShape edit_shape = new EditShape(); + int geom_a = edit_shape.addGeometry(polygon_a); + int geom_b = edit_shape.addGeometry(polyline_b); + relOps.setEditShapeCrackAndCluster_(edit_shape, tolerance, progress_tracker); + + // Make sure Polygon A has exterior + // If the simplified Polygon A does not have interior, then it cannot contain anything. + if (edit_shape.getPointCount(geom_a) == 0) + return false; + + relOps.computeMatrixTopoGraphHalfEdges_(geom_a, geom_b); + relOps.m_topo_graph.removeShape(); + + boolean bContains = relationCompare_(relOps.m_matrix, relOps.m_scl); + return bContains; + } + // Returns true if the relation holds static boolean polygonRelateMultiPoint_(Polygon polygon_a, MultiPoint multipoint_b, double tolerance, String scl, @@ -359,7 +556,7 @@ static boolean polygonRelateMultiPoint_(Polygon polygon_a, env_a, env_b, tolerance, progress_tracker); if (b_disjoint) { - relOps.areaPointDisjointPredicates_(); + relOps.areaPointDisjointPredicates_(polygon_a); bRelationKnown = true; } @@ -371,10 +568,10 @@ static boolean polygonRelateMultiPoint_(Polygon polygon_a, tolerance, false); if (relation == RelationalOperations.Relation.disjoint) { - relOps.areaPointDisjointPredicates_(); + relOps.areaPointDisjointPredicates_(polygon_a); bRelationKnown = true; } else if (relation == RelationalOperations.Relation.contains) { - relOps.areaPointContainsPredicates_(); + relOps.areaPointContainsPredicates_(polygon_a); bRelationKnown = true; } } @@ -548,144 +745,189 @@ static boolean multiPointRelateMultiPoint_(MultiPoint multipoint_a, // Returns true if the relation holds. static boolean polygonRelatePoint_(Polygon polygon_a, Point point_b, double tolerance, String scl, ProgressTracker progress_tracker) { - RelationalOperationsMatrix relOps = new RelationalOperationsMatrix(); - relOps.resetMatrix_(); - relOps.setPredicates_(scl); - relOps.setAreaPointPredicates_(); - - Envelope2D env_a = new Envelope2D(); - polygon_a.queryEnvelope2D(env_a); - Point2D pt_b = point_b.getXY(); - - boolean bRelationKnown = false; - boolean b_disjoint = RelationalOperations.pointDisjointEnvelope_(pt_b, - env_a, tolerance, progress_tracker); - - if (b_disjoint) { - relOps.areaPointDisjointPredicates_(); - bRelationKnown = true; - } - - if (!bRelationKnown) { - PolygonUtils.PiPResult res = PolygonUtils.isPointInPolygon2D( - polygon_a, pt_b, tolerance); // uses accelerator - - if (res == PolygonUtils.PiPResult.PiPInside) { - relOps.m_matrix[MatrixPredicate.InteriorInterior] = 0; - relOps.m_matrix[MatrixPredicate.BoundaryInterior] = -1; - relOps.m_matrix[MatrixPredicate.ExteriorInterior] = -1; - } else if (res == PolygonUtils.PiPResult.PiPBoundary) { - relOps.m_matrix[MatrixPredicate.InteriorInterior] = -1; - relOps.m_matrix[MatrixPredicate.BoundaryInterior] = 0; - relOps.m_matrix[MatrixPredicate.ExteriorInterior] = -1; - } else { - relOps.m_matrix[MatrixPredicate.InteriorInterior] = -1; - relOps.m_matrix[MatrixPredicate.ExteriorInterior] = 0; - relOps.m_matrix[MatrixPredicate.ExteriorInterior] = -1; - } - } - - boolean bRelation = relationCompare_(relOps.m_matrix, scl); - return bRelation; + RelationalOperationsMatrix relOps = new RelationalOperationsMatrix(); + relOps.resetMatrix_(); + relOps.setPredicates_(scl); + relOps.setAreaPointPredicates_(); + + Envelope2D env_a = new Envelope2D(); + polygon_a.queryEnvelope2D(env_a); + Point2D pt_b = point_b.getXY(); + + boolean bRelationKnown = false; + boolean b_disjoint = RelationalOperations.pointDisjointEnvelope_(pt_b, env_a, tolerance, progress_tracker); + + if (b_disjoint) + { + relOps.areaPointDisjointPredicates_(polygon_a); + bRelationKnown = true; + } + + if (!bRelationKnown) + { + PolygonUtils.PiPResult res = PolygonUtils.isPointInPolygon2D(polygon_a, pt_b, tolerance); // uses accelerator + + if (res == PolygonUtils.PiPResult.PiPInside) + {// polygon must have area + relOps.m_matrix[MatrixPredicate.InteriorInterior] = 0; + relOps.m_matrix[MatrixPredicate.InteriorExterior] = 2; + relOps.m_matrix[MatrixPredicate.BoundaryInterior] = -1; + relOps.m_matrix[MatrixPredicate.BoundaryExterior] = 1; + relOps.m_matrix[MatrixPredicate.ExteriorInterior] = -1; + } + else if (res == PolygonUtils.PiPResult.PiPBoundary) + { + relOps.m_matrix[MatrixPredicate.ExteriorInterior] = -1; + + double area = polygon_a.calculateArea2D(); + + if (area != 0) + { + relOps.m_matrix[MatrixPredicate.InteriorInterior] = -1; + relOps.m_matrix[MatrixPredicate.BoundaryInterior] = 0; + relOps.m_matrix[MatrixPredicate.InteriorExterior] = 2; + relOps.m_matrix[MatrixPredicate.BoundaryExterior] = 1; + } + else + { + relOps.m_matrix[MatrixPredicate.InteriorInterior] = 0; + relOps.m_matrix[MatrixPredicate.BoundaryInterior] = -1; + relOps.m_matrix[MatrixPredicate.BoundaryExterior] = -1; + + Envelope2D env = new Envelope2D(); + polygon_a.queryEnvelope2D(env); + relOps.m_matrix[MatrixPredicate.InteriorExterior] = (env.getHeight() == 0.0 && env.getWidth() == 0.0 ? -1 : 1); + } + } + else + { + relOps.areaPointDisjointPredicates_(polygon_a); + } + } + + boolean bRelation = relationCompare_(relOps.m_matrix, scl); + return bRelation; } // Returns true if the relation holds. static boolean polylineRelatePoint_(Polyline polyline_a, Point point_b, double tolerance, String scl, ProgressTracker progress_tracker) { - RelationalOperationsMatrix relOps = new RelationalOperationsMatrix(); - relOps.resetMatrix_(); - relOps.setPredicates_(scl); - relOps.setLinePointPredicates_(); - - Envelope2D env_a = new Envelope2D(); - polyline_a.queryEnvelope2D(env_a); - Point2D pt_b = point_b.getXY(); - - boolean bRelationKnown = false; - boolean b_disjoint = RelationalOperations.pointDisjointEnvelope_(pt_b, - env_a, tolerance, progress_tracker); - - if (b_disjoint) { - relOps.linePointDisjointPredicates_(polyline_a); - bRelationKnown = true; - } - - if (!bRelationKnown) { - MultiPoint boundary_a = null; - boolean b_boundary_contains_point_known = false; - boolean b_boundary_contains_point = false; - - if (relOps.m_perform_predicates[MatrixPredicate.InteriorInterior] - || relOps.m_perform_predicates[MatrixPredicate.ExteriorInterior]) { - boolean b_intersects = RelationalOperations - .linearPathIntersectsPoint_(polyline_a, pt_b, tolerance); - - if (b_intersects) { - if (relOps.m_perform_predicates[MatrixPredicate.InteriorInterior]) { - boundary_a = (MultiPoint) Boundary.calculate( - polyline_a, progress_tracker); - b_boundary_contains_point = !RelationalOperations - .multiPointDisjointPointImpl_(boundary_a, pt_b, - tolerance, progress_tracker); - b_boundary_contains_point_known = true; - - if (b_boundary_contains_point) - relOps.m_matrix[MatrixPredicate.InteriorInterior] = -1; - else - relOps.m_matrix[MatrixPredicate.InteriorInterior] = 0; - } - - relOps.m_matrix[MatrixPredicate.ExteriorInterior] = -1; - } else { - relOps.m_matrix[MatrixPredicate.InteriorInterior] = -1; - relOps.m_matrix[MatrixPredicate.ExteriorInterior] = 0; - } - } - - if (relOps.m_perform_predicates[MatrixPredicate.BoundaryInterior]) { - if (boundary_a != null && boundary_a.isEmpty()) { - relOps.m_matrix[MatrixPredicate.BoundaryInterior] = -1; - } else { - if (!b_boundary_contains_point_known) { - if (boundary_a == null) - boundary_a = (MultiPoint) Boundary.calculate( - polyline_a, progress_tracker); - - b_boundary_contains_point = !RelationalOperations - .multiPointDisjointPointImpl_(boundary_a, pt_b, - tolerance, progress_tracker); - b_boundary_contains_point_known = true; - } - - relOps.m_matrix[MatrixPredicate.BoundaryInterior] = (b_boundary_contains_point ? 0 - : -1); - } - } - - if (relOps.m_perform_predicates[MatrixPredicate.BoundaryExterior]) { - if (boundary_a != null && boundary_a.isEmpty()) { - relOps.m_matrix[MatrixPredicate.BoundaryExterior] = -1; - } else { - if (b_boundary_contains_point_known - && !b_boundary_contains_point) { - relOps.m_matrix[MatrixPredicate.BoundaryExterior] = 0; - } else { - if (boundary_a == null) - boundary_a = (MultiPoint) Boundary.calculate( - polyline_a, progress_tracker); - - boolean b_boundary_equals_point = RelationalOperations - .multiPointEqualsPoint_(boundary_a, point_b, - tolerance, progress_tracker); - relOps.m_matrix[MatrixPredicate.BoundaryExterior] = (b_boundary_equals_point ? -1 - : 0); - } - } - } - } - - boolean bRelation = relationCompare_(relOps.m_matrix, relOps.m_scl); - return bRelation; + RelationalOperationsMatrix relOps = new RelationalOperationsMatrix(); + relOps.resetMatrix_(); + relOps.setPredicates_(scl); + relOps.setLinePointPredicates_(); + + Envelope2D env_a = new Envelope2D(); + polyline_a.queryEnvelope2D(env_a); + Point2D pt_b = point_b.getXY(); + + boolean bRelationKnown = false; + boolean b_disjoint = RelationalOperations.pointDisjointEnvelope_(pt_b, env_a, tolerance, progress_tracker); + + if (b_disjoint) + { + relOps.linePointDisjointPredicates_(polyline_a); + bRelationKnown = true; + } + + if (!bRelationKnown) + { + MultiPoint boundary_a = null; + boolean b_boundary_contains_point_known = false; + boolean b_boundary_contains_point = false; + + if (relOps.m_perform_predicates[MatrixPredicate.InteriorInterior] || relOps.m_perform_predicates[MatrixPredicate.ExteriorInterior]) + { + boolean b_intersects = RelationalOperations.linearPathIntersectsPoint_(polyline_a, pt_b, tolerance); + + if (b_intersects) + { + if (relOps.m_perform_predicates[MatrixPredicate.InteriorInterior]) + { + boundary_a = (MultiPoint)Boundary.calculate(polyline_a, progress_tracker); + b_boundary_contains_point = !RelationalOperations.multiPointDisjointPointImpl_(boundary_a, pt_b, tolerance, progress_tracker); + b_boundary_contains_point_known = true; + + if (b_boundary_contains_point) + relOps.m_matrix[MatrixPredicate.InteriorInterior] = -1; + else + relOps.m_matrix[MatrixPredicate.InteriorInterior] = 0; + } + + relOps.m_matrix[MatrixPredicate.ExteriorInterior] = -1; + } + else + { + relOps.m_matrix[MatrixPredicate.InteriorInterior] = -1; + relOps.m_matrix[MatrixPredicate.ExteriorInterior] = 0; + } + } + + if (relOps.m_perform_predicates[MatrixPredicate.BoundaryInterior]) + { + if (boundary_a != null && boundary_a.isEmpty()) + { + relOps.m_matrix[MatrixPredicate.BoundaryInterior] = -1; + } + else + { + if (!b_boundary_contains_point_known) + { + if (boundary_a == null) + boundary_a = (MultiPoint)Boundary.calculate(polyline_a, progress_tracker); + + b_boundary_contains_point = !RelationalOperations.multiPointDisjointPointImpl_(boundary_a, pt_b, tolerance, progress_tracker); + b_boundary_contains_point_known = true; + } + + relOps.m_matrix[MatrixPredicate.BoundaryInterior] = (b_boundary_contains_point ? 0 : -1); + } + } + + if (relOps.m_perform_predicates[MatrixPredicate.BoundaryExterior]) + { + if (boundary_a != null && boundary_a.isEmpty()) + { + relOps.m_matrix[MatrixPredicate.BoundaryExterior] = -1; + } + else + { + if (b_boundary_contains_point_known && !b_boundary_contains_point) + { + relOps.m_matrix[MatrixPredicate.BoundaryExterior] = 0; + } + else + { + if (boundary_a == null) + boundary_a = (MultiPoint)Boundary.calculate(polyline_a, progress_tracker); + + boolean b_boundary_equals_point = RelationalOperations.multiPointEqualsPoint_(boundary_a, point_b, tolerance, progress_tracker); + relOps.m_matrix[MatrixPredicate.BoundaryExterior] = (b_boundary_equals_point ? -1 : 0); + } + } + } + + if (relOps.m_perform_predicates[MatrixPredicate.InteriorExterior]) + { + boolean b_has_length = polyline_a.calculateLength2D() != 0; + + if (b_has_length) + { + relOps.m_matrix[MatrixPredicate.InteriorExterior] = 1; + } + else + { + // all points are interior + MultiPoint interior_a = new MultiPoint(polyline_a.getDescription()); + interior_a.add(polyline_a, 0, polyline_a.getPointCount()); + boolean b_interior_equals_point = RelationalOperations.multiPointEqualsPoint_(interior_a, point_b, tolerance, progress_tracker); + relOps.m_matrix[MatrixPredicate.InteriorExterior] = (b_interior_equals_point ? -1 : 0); + } + } + } + + boolean bRelation = relationCompare_(relOps.m_matrix, relOps.m_scl); + return bRelation; } // Returns true if the relation holds. @@ -812,6 +1054,85 @@ private static boolean relationCompare_(int[] matrix, String scl) { return true; } + static boolean relateEmptyGeometries_(Geometry geometry_a, Geometry geometry_b, String scl) + { + int[] matrix = new int[9]; + + if (geometry_a.isEmpty() && geometry_b.isEmpty()) + { + for (int i = 0; i < 9; i++) + matrix[i] = -1; + + return relationCompare_(matrix, scl); + } + + boolean b_transpose = false; + + Geometry g_a; + Geometry g_b; + + if (!geometry_a.isEmpty()) + { + g_a = geometry_a; + g_b = geometry_b; + } + else + { + g_a = geometry_b; + g_b = geometry_a; + b_transpose = true; + } + + matrix[MatrixPredicate.InteriorInterior] = -1; + matrix[MatrixPredicate.InteriorBoundary] = -1; + matrix[MatrixPredicate.BoundaryInterior] = -1; + matrix[MatrixPredicate.BoundaryBoundary] = -1; + matrix[MatrixPredicate.ExteriorInterior] = -1; + matrix[MatrixPredicate.ExteriorBoundary] = -1; + + matrix[MatrixPredicate.ExteriorExterior] = 2; + + int type = g_a.getType().value(); + + if (Geometry.isMultiPath(type)) + { + if (type == Geometry.GeometryType.Polygon) + { + double area = ((Polygon)g_a).calculateArea2D(); + + if (area != 0) + { + matrix[MatrixPredicate.InteriorExterior] = 2; + matrix[MatrixPredicate.BoundaryExterior] = 1; + } + else + { + matrix[MatrixPredicate.BoundaryExterior] = -1; + + Envelope2D env = new Envelope2D(); + g_a.queryEnvelope2D(env); + matrix[MatrixPredicate.InteriorExterior] = (env.getHeight() == 0.0 && env.getWidth() == 0.0 ? 0 : 1); + } + } + else + { + boolean b_has_length = ((Polyline)g_a).calculateLength2D() != 0; + matrix[MatrixPredicate.InteriorExterior] = (b_has_length ? 1 : 0); + matrix[MatrixPredicate.BoundaryExterior] = (Boundary.hasNonEmptyBoundary((Polyline)g_a, null) ? 0 : -1); + } + } + else + { + matrix[MatrixPredicate.InteriorExterior] = 0; + matrix[MatrixPredicate.BoundaryExterior] = -1; + } + + if (b_transpose) + transposeMatrix_(matrix); + + return relationCompare_(matrix, scl); + } + // Checks whether scl string is a predefined relation. private static int getPredefinedRelation_(String scl, int dim_a, int dim_b) { if (equals_(scl)) @@ -971,36 +1292,41 @@ private static boolean overlaps_(String scl, int dim_a, int dim_b) { // the geometry and/or a boundary index of the geometry. private static void markClusterEndPoints_(int geometry, TopoGraph topoGraph, int clusterIndex) { - EditShape edit_shape = topoGraph.getShape(); + int id = topoGraph.getGeometryID(geometry); - for (int path = edit_shape.getFirstPath(geometry); path != -1; path = edit_shape - .getNextPath(path)) { - int vertexFirst = edit_shape.getFirstVertex(path); - int vertexLast = edit_shape.getLastVertex(path); - boolean b_closed = (vertexFirst == vertexLast); + for (int cluster = topoGraph.getFirstCluster(); cluster != -1; cluster = topoGraph.getNextCluster(cluster)) + { + int cluster_parentage = topoGraph.getClusterParentage(cluster); - int vertex = vertexFirst; + if ((cluster_parentage & id) == 0) + continue; - do { - int cluster = topoGraph.getClusterFromVertex(vertex); - int index = topoGraph - .getClusterUserIndex(cluster, clusterIndex); - - if (!b_closed - && (vertex == vertexFirst || vertex == vertexLast)) { - if (index == -1) - topoGraph.setClusterUserIndex(cluster, clusterIndex, 1); - else - topoGraph.setClusterUserIndex(cluster, clusterIndex, - index + 1); - } else { - if (index == -1) - topoGraph.setClusterUserIndex(cluster, clusterIndex, 0); - } + int first_half_edge = topoGraph.getClusterHalfEdge(cluster); + if (first_half_edge == -1) + { + topoGraph.setClusterUserIndex(cluster, clusterIndex, 0); + continue; + } - vertex = edit_shape.getNextVertex(vertex); - } while (vertex != vertexFirst && vertex != -1); - } + int next_half_edge = first_half_edge; + int index = 0; + + do + { + int half_edge = next_half_edge; + int half_edge_parentage = topoGraph.getHalfEdgeParentage(half_edge); + + if ((half_edge_parentage & id) != 0) + index++; + + next_half_edge = topoGraph.getHalfEdgeNext(topoGraph.getHalfEdgeTwin(half_edge)); + + } while (next_half_edge != first_half_edge); + + topoGraph.setClusterUserIndex(cluster, clusterIndex, index); + } + + return; } private static String getTransposeMatrix_(String scl) { @@ -1025,21 +1351,24 @@ private static String getTransposeMatrix_(String scl) { // 2: 2-dimension intersection private void resetMatrix_() { for (int i = 0; i < 9; i++) - m_matrix[i] = -2; + { + m_matrix[i] = -2; + m_max_dim[i] = -2; + } } - private void transposeMatrix_() { - int temp1 = m_matrix[1]; - int temp2 = m_matrix[2]; - int temp5 = m_matrix[5]; + private static void transposeMatrix_(int[] matrix) { + int temp1 = matrix[1]; + int temp2 = matrix[2]; + int temp5 = matrix[5]; - m_matrix[1] = m_matrix[3]; - m_matrix[2] = m_matrix[6]; - m_matrix[5] = m_matrix[7]; + matrix[1] = matrix[3]; + matrix[2] = matrix[6]; + matrix[5] = matrix[7]; - m_matrix[3] = temp1; - m_matrix[6] = temp2; - m_matrix[7] = temp5; + matrix[3] = temp1; + matrix[6] = temp2; + matrix[7] = temp5; } // Sets the relation predicates from the scl string. @@ -1066,193 +1395,254 @@ private void setRemainingPredicatesToFalse_() { } // Checks whether the predicate is known. - private boolean isPredicateKnown_(int predicate, int dim) { - assert (m_scl.charAt(predicate) != '*'); - - if (m_matrix[predicate] == -2) - return false; - - if (m_matrix[predicate] == -1) { - m_perform_predicates[predicate] = false; - m_predicate_count--; - return true; - } - - if (m_scl.charAt(predicate) != 'T' && m_scl.charAt(predicate) != 'F') { - if (m_matrix[predicate] < dim) { - return false; - } else { - m_perform_predicates[predicate] = false; - m_predicate_count--; - return true; - } - } else { - m_perform_predicates[predicate] = false; - m_predicate_count--; - return true; - } + private boolean isPredicateKnown_(int predicate) { + assert(m_scl.charAt(predicate) != '*'); + + if (m_matrix[predicate] == -2) + return false; + + if (m_matrix[predicate] == -1) + { + m_perform_predicates[predicate] = false; + m_predicate_count--; + return true; + } + + if (m_scl.charAt(predicate) != 'T' && m_scl.charAt(predicate) != 'F') + { + if (m_matrix[predicate] < m_max_dim[predicate]) + { + return false; + } + else + { + m_perform_predicates[predicate] = false; + m_predicate_count--; + return true; + } + } + else + { + m_perform_predicates[predicate] = false; + m_predicate_count--; + return true; + } } // Sets the area-area predicates function. private void setAreaAreaPredicates_() { - m_predicates = Predicates.AreaAreaPredicates; - - // set predicates that are always true/false - if (m_perform_predicates[MatrixPredicate.ExteriorExterior]) { - m_matrix[MatrixPredicate.ExteriorExterior] = 2; // Always true - m_perform_predicates[MatrixPredicate.ExteriorExterior] = false; - m_predicate_count--; - } + m_predicates_half_edge = Predicates.AreaAreaPredicates; + + m_max_dim[MatrixPredicate.InteriorInterior] = 2; + m_max_dim[MatrixPredicate.InteriorBoundary] = 1; + m_max_dim[MatrixPredicate.InteriorExterior] = 2; + m_max_dim[MatrixPredicate.BoundaryInterior] = 1; + m_max_dim[MatrixPredicate.BoundaryBoundary] = 1; + m_max_dim[MatrixPredicate.BoundaryExterior] = 1; + m_max_dim[MatrixPredicate.ExteriorInterior] = 2; + m_max_dim[MatrixPredicate.ExteriorBoundary] = 1; + m_max_dim[MatrixPredicate.ExteriorExterior] = 2; + + // set predicates that are always true/false + if (m_perform_predicates[MatrixPredicate.ExteriorExterior]) + { + m_matrix[MatrixPredicate.ExteriorExterior] = 2; // Always true + m_perform_predicates[MatrixPredicate.ExteriorExterior] = false; + m_predicate_count--; + } } // Sets the area-line predicate function. private void setAreaLinePredicates_() { - m_predicates = Predicates.AreaLinePredicates; - - // set predicates that are always true/false - if (m_perform_predicates[MatrixPredicate.InteriorExterior]) { - m_matrix[MatrixPredicate.InteriorExterior] = 2; // Always true - m_perform_predicates[MatrixPredicate.InteriorExterior] = false; - m_predicate_count--; - } - - if (m_perform_predicates[MatrixPredicate.ExteriorExterior]) { - m_matrix[MatrixPredicate.ExteriorExterior] = 2; // Always true - m_perform_predicates[MatrixPredicate.ExteriorExterior] = false; - m_predicate_count--; - } + m_predicates_half_edge = Predicates.AreaLinePredicates; + m_predicates_cluster = Predicates.AreaPointPredicates; + + m_max_dim[MatrixPredicate.InteriorInterior] = 1; + m_max_dim[MatrixPredicate.InteriorBoundary] = 0; + m_max_dim[MatrixPredicate.InteriorExterior] = 2; + m_max_dim[MatrixPredicate.BoundaryInterior] = 1; + m_max_dim[MatrixPredicate.BoundaryBoundary] = 0; + m_max_dim[MatrixPredicate.BoundaryExterior] = 1; + m_max_dim[MatrixPredicate.ExteriorInterior] = 1; + m_max_dim[MatrixPredicate.ExteriorBoundary] = 0; + m_max_dim[MatrixPredicate.ExteriorExterior] = 2; + + if (m_perform_predicates[MatrixPredicate.ExteriorExterior]) + { + m_matrix[MatrixPredicate.ExteriorExterior] = 2; // Always true + m_perform_predicates[MatrixPredicate.ExteriorExterior] = false; + m_predicate_count--; + } } // Sets the line-line predicates function. private void setLineLinePredicates_() { - m_predicates = Predicates.LineLinePredicates; - - // set predicates that are always true/false - if (m_perform_predicates[MatrixPredicate.ExteriorExterior]) { - m_matrix[MatrixPredicate.ExteriorExterior] = 2; // Always true - m_perform_predicates[MatrixPredicate.ExteriorExterior] = false; - m_predicate_count--; - } + m_predicates_half_edge = Predicates.LineLinePredicates; + m_predicates_cluster = Predicates.LinePointPredicates; + + m_max_dim[MatrixPredicate.InteriorInterior] = 1; + m_max_dim[MatrixPredicate.InteriorBoundary] = 0; + m_max_dim[MatrixPredicate.InteriorExterior] = 1; + m_max_dim[MatrixPredicate.BoundaryInterior] = 0; + m_max_dim[MatrixPredicate.BoundaryBoundary] = 0; + m_max_dim[MatrixPredicate.BoundaryExterior] = 0; + m_max_dim[MatrixPredicate.ExteriorInterior] = 1; + m_max_dim[MatrixPredicate.ExteriorBoundary] = 0; + m_max_dim[MatrixPredicate.ExteriorExterior] = 2; + + // set predicates that are always true/false + if (m_perform_predicates[MatrixPredicate.ExteriorExterior]) + { + m_matrix[MatrixPredicate.ExteriorExterior] = 2; // Always true + m_perform_predicates[MatrixPredicate.ExteriorExterior] = false; + m_predicate_count--; + } } // Sets the area-point predicate function. private void setAreaPointPredicates_() { - m_predicates = Predicates.AreaPointPredicates; - - // set predicates that are always true/false - if (m_perform_predicates[MatrixPredicate.InteriorBoundary]) { - m_matrix[MatrixPredicate.InteriorBoundary] = -1; // Always false - m_perform_predicates[MatrixPredicate.InteriorBoundary] = false; - m_predicate_count--; - } - - if (m_perform_predicates[MatrixPredicate.InteriorExterior]) { - m_matrix[MatrixPredicate.InteriorExterior] = 2; // Always true - m_perform_predicates[MatrixPredicate.InteriorExterior] = false; - m_predicate_count--; - } - - if (m_perform_predicates[MatrixPredicate.BoundaryBoundary]) { - m_matrix[MatrixPredicate.BoundaryBoundary] = -1; // Always false - m_perform_predicates[MatrixPredicate.BoundaryBoundary] = false; - m_predicate_count--; - } - - if (m_perform_predicates[MatrixPredicate.BoundaryExterior]) { - m_matrix[MatrixPredicate.BoundaryExterior] = 1; // Always true - m_perform_predicates[MatrixPredicate.BoundaryExterior] = false; - m_predicate_count--; - } - - if (m_perform_predicates[MatrixPredicate.ExteriorBoundary]) { - m_matrix[MatrixPredicate.ExteriorBoundary] = -1; // Always false - m_perform_predicates[MatrixPredicate.ExteriorBoundary] = false; - m_predicate_count--; - } - - if (m_perform_predicates[MatrixPredicate.ExteriorExterior]) { - m_matrix[MatrixPredicate.ExteriorExterior] = 2; // Always true - m_perform_predicates[MatrixPredicate.ExteriorExterior] = false; - m_predicate_count--; - } + m_predicates_cluster = Predicates.AreaPointPredicates; + + m_max_dim[MatrixPredicate.InteriorInterior] = 0; + m_max_dim[MatrixPredicate.InteriorBoundary] = -1; + m_max_dim[MatrixPredicate.InteriorExterior] = 2; + m_max_dim[MatrixPredicate.BoundaryInterior] = 0; + m_max_dim[MatrixPredicate.BoundaryBoundary] = -1; + m_max_dim[MatrixPredicate.BoundaryExterior] = 1; + m_max_dim[MatrixPredicate.ExteriorInterior] = 0; + m_max_dim[MatrixPredicate.ExteriorBoundary] = -1; + m_max_dim[MatrixPredicate.ExteriorExterior] = 2; + + // set predicates that are always true/false + if (m_perform_predicates[MatrixPredicate.InteriorBoundary]) + { + m_matrix[MatrixPredicate.InteriorBoundary] = -1; // Always false + m_perform_predicates[MatrixPredicate.InteriorBoundary] = false; + m_predicate_count--; + } + + if (m_perform_predicates[MatrixPredicate.BoundaryBoundary]) + { + m_matrix[MatrixPredicate.BoundaryBoundary] = -1; // Always false + m_perform_predicates[MatrixPredicate.BoundaryBoundary] = false; + m_predicate_count--; + } + + if (m_perform_predicates[MatrixPredicate.ExteriorBoundary]) + { + m_matrix[MatrixPredicate.ExteriorBoundary] = -1; // Always false + m_perform_predicates[MatrixPredicate.ExteriorBoundary] = false; + m_predicate_count--; + } + + if (m_perform_predicates[MatrixPredicate.ExteriorExterior]) + { + m_matrix[MatrixPredicate.ExteriorExterior] = 2; // Always true + m_perform_predicates[MatrixPredicate.ExteriorExterior] = false; + m_predicate_count--; + } } // Sets the line-point predicates function. private void setLinePointPredicates_() { - m_predicates = Predicates.LinePointPredicates; - - // set predicates that are always true/false - if (m_perform_predicates[MatrixPredicate.InteriorBoundary]) { - m_matrix[MatrixPredicate.InteriorBoundary] = -1; // Always false - m_perform_predicates[MatrixPredicate.InteriorBoundary] = false; - m_predicate_count--; - } - - if (m_perform_predicates[MatrixPredicate.InteriorExterior]) { - m_matrix[MatrixPredicate.InteriorExterior] = 1; // Always true - m_perform_predicates[MatrixPredicate.InteriorExterior] = false; - m_predicate_count--; - } - - if (m_perform_predicates[MatrixPredicate.BoundaryBoundary]) { - m_matrix[MatrixPredicate.BoundaryBoundary] = -1; // Always false - m_perform_predicates[MatrixPredicate.BoundaryBoundary] = false; - m_predicate_count--; - } - - if (m_perform_predicates[MatrixPredicate.ExteriorBoundary]) { - m_matrix[MatrixPredicate.ExteriorBoundary] = -1; // Always false - m_perform_predicates[MatrixPredicate.ExteriorBoundary] = false; - m_predicate_count--; - } - - if (m_perform_predicates[MatrixPredicate.ExteriorExterior]) { - m_matrix[MatrixPredicate.ExteriorExterior] = 2; // Always true - m_perform_predicates[MatrixPredicate.ExteriorExterior] = false; - m_predicate_count--; - } + m_predicates_cluster = Predicates.LinePointPredicates; + + m_max_dim[MatrixPredicate.InteriorInterior] = 0; + m_max_dim[MatrixPredicate.InteriorBoundary] = -1; + m_max_dim[MatrixPredicate.InteriorExterior] = 1; + m_max_dim[MatrixPredicate.BoundaryInterior] = 0; + m_max_dim[MatrixPredicate.BoundaryBoundary] = -1; + m_max_dim[MatrixPredicate.BoundaryExterior] = 0; + m_max_dim[MatrixPredicate.ExteriorInterior] = 0; + m_max_dim[MatrixPredicate.ExteriorBoundary] = -1; + m_max_dim[MatrixPredicate.ExteriorExterior] = 2; + + // set predicates that are always true/false + if (m_perform_predicates[MatrixPredicate.InteriorBoundary]) + { + m_matrix[MatrixPredicate.InteriorBoundary] = -1; // Always false + m_perform_predicates[MatrixPredicate.InteriorBoundary] = false; + m_predicate_count--; + } + + if (m_perform_predicates[MatrixPredicate.BoundaryBoundary]) + { + m_matrix[MatrixPredicate.BoundaryBoundary] = -1; // Always false + m_perform_predicates[MatrixPredicate.BoundaryBoundary] = false; + m_predicate_count--; + } + + if (m_perform_predicates[MatrixPredicate.ExteriorBoundary]) + { + m_matrix[MatrixPredicate.ExteriorBoundary] = -1; // Always false + m_perform_predicates[MatrixPredicate.ExteriorBoundary] = false; + m_predicate_count--; + } + + if (m_perform_predicates[MatrixPredicate.ExteriorExterior]) + { + m_matrix[MatrixPredicate.ExteriorExterior] = 2; // Always true + m_perform_predicates[MatrixPredicate.ExteriorExterior] = false; + m_predicate_count--; + } } // Sets the point-point predicates function. private void setPointPointPredicates_() { - m_predicates = Predicates.PointPointPredicates; - - // set predicates that are always true/false - if (m_perform_predicates[MatrixPredicate.InteriorBoundary]) { - m_matrix[MatrixPredicate.InteriorBoundary] = -1; // Always false - m_perform_predicates[MatrixPredicate.InteriorBoundary] = false; - m_predicate_count--; - } - - if (m_perform_predicates[MatrixPredicate.BoundaryInterior]) { - m_matrix[MatrixPredicate.BoundaryInterior] = -1; // Always false - m_perform_predicates[MatrixPredicate.BoundaryInterior] = false; - m_predicate_count--; - } - - if (m_perform_predicates[MatrixPredicate.BoundaryBoundary]) { - m_matrix[MatrixPredicate.BoundaryBoundary] = -1; // Always false - m_perform_predicates[MatrixPredicate.BoundaryBoundary] = false; - m_predicate_count--; - } - - if (m_perform_predicates[MatrixPredicate.BoundaryExterior]) { - m_matrix[MatrixPredicate.BoundaryExterior] = -1; // Always false - m_perform_predicates[MatrixPredicate.BoundaryExterior] = false; - m_predicate_count--; - } - - if (m_perform_predicates[MatrixPredicate.ExteriorBoundary]) { - m_matrix[MatrixPredicate.ExteriorBoundary] = -1; // Always false - m_perform_predicates[MatrixPredicate.ExteriorBoundary] = false; - m_predicate_count--; - } - - if (m_perform_predicates[MatrixPredicate.ExteriorExterior]) { - m_matrix[MatrixPredicate.ExteriorExterior] = 2; // Always true - m_perform_predicates[MatrixPredicate.ExteriorExterior] = false; - m_predicate_count--; - } + m_predicates_cluster = Predicates.PointPointPredicates; + + m_max_dim[MatrixPredicate.InteriorInterior] = 0; + m_max_dim[MatrixPredicate.InteriorBoundary] = -1; + m_max_dim[MatrixPredicate.InteriorExterior] = 0; + m_max_dim[MatrixPredicate.BoundaryInterior] = -1; + m_max_dim[MatrixPredicate.BoundaryBoundary] = -1; + m_max_dim[MatrixPredicate.BoundaryExterior] = -1; + m_max_dim[MatrixPredicate.ExteriorInterior] = 0; + m_max_dim[MatrixPredicate.ExteriorBoundary] = -1; + m_max_dim[MatrixPredicate.ExteriorExterior] = 2; + + // set predicates that are always true/false + if (m_perform_predicates[MatrixPredicate.InteriorBoundary]) + { + m_matrix[MatrixPredicate.InteriorBoundary] = -1; // Always false + m_perform_predicates[MatrixPredicate.InteriorBoundary] = false; + m_predicate_count--; + } + + if (m_perform_predicates[MatrixPredicate.BoundaryInterior]) + { + m_matrix[MatrixPredicate.BoundaryInterior] = -1; // Always false + m_perform_predicates[MatrixPredicate.BoundaryInterior] = false; + m_predicate_count--; + } + + if (m_perform_predicates[MatrixPredicate.BoundaryBoundary]) + { + m_matrix[MatrixPredicate.BoundaryBoundary] = -1; // Always false + m_perform_predicates[MatrixPredicate.BoundaryBoundary] = false; + m_predicate_count--; + } + + if (m_perform_predicates[MatrixPredicate.BoundaryExterior]) + { + m_matrix[MatrixPredicate.BoundaryExterior] = -1; // Always false + m_perform_predicates[MatrixPredicate.BoundaryExterior] = false; + m_predicate_count--; + } + + if (m_perform_predicates[MatrixPredicate.ExteriorBoundary]) + { + m_matrix[MatrixPredicate.ExteriorBoundary] = -1; // Always false + m_perform_predicates[MatrixPredicate.ExteriorBoundary] = false; + m_predicate_count--; + } + + if (m_perform_predicates[MatrixPredicate.ExteriorExterior]) + { + m_matrix[MatrixPredicate.ExteriorExterior] = 2; // Always true + m_perform_predicates[MatrixPredicate.ExteriorExterior] = false; + m_predicate_count--; + } } // Invokes the 9 relational predicates of area vs area. @@ -1262,175 +1652,228 @@ private boolean areaAreaPredicates_(int half_edge, int id_a, int id_b) { if (m_perform_predicates[MatrixPredicate.InteriorInterior]) { interiorAreaInteriorArea_(half_edge, id_a, id_b); bRelationKnown &= isPredicateKnown_( - MatrixPredicate.InteriorInterior, 2); + MatrixPredicate.InteriorInterior); } if (m_perform_predicates[MatrixPredicate.InteriorBoundary]) { interiorAreaBoundaryArea_(half_edge, id_a, MatrixPredicate.InteriorBoundary); bRelationKnown &= isPredicateKnown_( - MatrixPredicate.InteriorBoundary, 1); + MatrixPredicate.InteriorBoundary); } if (m_perform_predicates[MatrixPredicate.InteriorExterior]) { interiorAreaExteriorArea_(half_edge, id_a, id_b, MatrixPredicate.InteriorExterior); bRelationKnown &= isPredicateKnown_( - MatrixPredicate.InteriorExterior, 2); + MatrixPredicate.InteriorExterior); } if (m_perform_predicates[MatrixPredicate.BoundaryInterior]) { interiorAreaBoundaryArea_(half_edge, id_b, MatrixPredicate.BoundaryInterior); bRelationKnown &= isPredicateKnown_( - MatrixPredicate.BoundaryInterior, 1); + MatrixPredicate.BoundaryInterior); } if (m_perform_predicates[MatrixPredicate.BoundaryBoundary]) { boundaryAreaBoundaryArea_(half_edge, id_a, id_b); bRelationKnown &= isPredicateKnown_( - MatrixPredicate.BoundaryBoundary, 1); + MatrixPredicate.BoundaryBoundary); } if (m_perform_predicates[MatrixPredicate.BoundaryExterior]) { boundaryAreaExteriorArea_(half_edge, id_a, id_b, MatrixPredicate.BoundaryExterior); bRelationKnown &= isPredicateKnown_( - MatrixPredicate.BoundaryExterior, 1); + MatrixPredicate.BoundaryExterior); } if (m_perform_predicates[MatrixPredicate.ExteriorInterior]) { interiorAreaExteriorArea_(half_edge, id_b, id_a, MatrixPredicate.ExteriorInterior); bRelationKnown &= isPredicateKnown_( - MatrixPredicate.ExteriorInterior, 2); + MatrixPredicate.ExteriorInterior); } if (m_perform_predicates[MatrixPredicate.ExteriorBoundary]) { boundaryAreaExteriorArea_(half_edge, id_b, id_a, MatrixPredicate.ExteriorBoundary); bRelationKnown &= isPredicateKnown_( - MatrixPredicate.ExteriorBoundary, 1); + MatrixPredicate.ExteriorBoundary); } return bRelationKnown; } - private void areaAreaDisjointPredicates_() { - m_matrix[MatrixPredicate.InteriorInterior] = -1; - m_matrix[MatrixPredicate.InteriorBoundary] = -1; - m_matrix[MatrixPredicate.InteriorExterior] = 2; - m_matrix[MatrixPredicate.BoundaryInterior] = -1; - m_matrix[MatrixPredicate.BoundaryBoundary] = -1; - m_matrix[MatrixPredicate.BoundaryExterior] = 1; - m_matrix[MatrixPredicate.ExteriorInterior] = 2; - m_matrix[MatrixPredicate.ExteriorBoundary] = 1; - - // all other predicates should already be set by - // set_area_area_predicates - } - - private void areaAreaContainsPredicates_() { - m_matrix[MatrixPredicate.InteriorInterior] = 2; - m_matrix[MatrixPredicate.InteriorBoundary] = 1; - m_matrix[MatrixPredicate.InteriorExterior] = 2; - m_matrix[MatrixPredicate.BoundaryInterior] = -1; - m_matrix[MatrixPredicate.BoundaryBoundary] = -1; - m_matrix[MatrixPredicate.BoundaryExterior] = 1; - m_matrix[MatrixPredicate.ExteriorInterior] = -1; - m_matrix[MatrixPredicate.ExteriorBoundary] = -1; - - // all other predicates should already be set by - // set_area_area_predicates - } - - private void areaAreaWithinPredicates_() { - areaAreaContainsPredicates_(); - transposeMatrix_(); - } - - private void areaLineDisjointPredicates_(Polyline polyline) { - m_matrix[MatrixPredicate.InteriorInterior] = -1; - m_matrix[MatrixPredicate.InteriorBoundary] = -1; - m_matrix[MatrixPredicate.BoundaryInterior] = -1; - m_matrix[MatrixPredicate.BoundaryBoundary] = -1; - m_matrix[MatrixPredicate.BoundaryExterior] = 1; - m_matrix[MatrixPredicate.ExteriorInterior] = 1; - - if (m_perform_predicates[MatrixPredicate.ExteriorBoundary]) { - boolean has_non_empty_boundary = Boundary.hasNonEmptyBoundary( - polyline, null); - m_matrix[MatrixPredicate.ExteriorBoundary] = (has_non_empty_boundary ? 0 - : -1); - } - } - - private void areaLineContainsPredicates_(Polyline polyline) { - m_matrix[MatrixPredicate.InteriorInterior] = 1; - - if (m_perform_predicates[MatrixPredicate.InteriorBoundary]) { - boolean has_non_empty_boundary = Boundary.hasNonEmptyBoundary( - polyline, null); - m_matrix[MatrixPredicate.InteriorBoundary] = (has_non_empty_boundary ? 0 - : -1); - } - - m_matrix[MatrixPredicate.BoundaryInterior] = -1; - m_matrix[MatrixPredicate.BoundaryBoundary] = -1; - m_matrix[MatrixPredicate.BoundaryExterior] = 1; - m_matrix[MatrixPredicate.ExteriorInterior] = -1; - m_matrix[MatrixPredicate.ExteriorBoundary] = -1; - } - - private void areaPointDisjointPredicates_() { - m_matrix[MatrixPredicate.InteriorInterior] = -1; - m_matrix[MatrixPredicate.BoundaryInterior] = -1; - m_matrix[MatrixPredicate.ExteriorInterior] = 0; - } - - private void areaPointContainsPredicates_() { - m_matrix[MatrixPredicate.InteriorInterior] = 0; - m_matrix[MatrixPredicate.BoundaryInterior] = -1; - m_matrix[MatrixPredicate.ExteriorInterior] = -1; - } + private void areaAreaDisjointPredicates_(Polygon polygon_a, Polygon polygon_b) { + m_matrix[MatrixPredicate.InteriorInterior] = -1; + m_matrix[MatrixPredicate.InteriorBoundary] = -1; + m_matrix[MatrixPredicate.BoundaryInterior] = -1; + m_matrix[MatrixPredicate.BoundaryBoundary] = -1; + + areaGeomContainsOrDisjointPredicates_(polygon_a, m_perform_predicates[MatrixPredicate.InteriorExterior] ? MatrixPredicate.InteriorExterior : -1, m_scl.charAt(MatrixPredicate.InteriorExterior), m_perform_predicates[MatrixPredicate.BoundaryExterior] ? MatrixPredicate.BoundaryExterior : -1, m_scl.charAt(MatrixPredicate.BoundaryExterior)); + areaGeomContainsOrDisjointPredicates_(polygon_b, m_perform_predicates[MatrixPredicate.ExteriorInterior] ? MatrixPredicate.ExteriorInterior : -1, m_scl.charAt(MatrixPredicate.ExteriorInterior), m_perform_predicates[MatrixPredicate.ExteriorBoundary] ? MatrixPredicate.ExteriorBoundary : -1, m_scl.charAt(MatrixPredicate.ExteriorBoundary)); + } + + private void areaGeomContainsOrDisjointPredicates_(Polygon polygon, int matrix_interior, char c1, int matrix_boundary, char c2) + { + if (matrix_interior != -1 || matrix_boundary != -1) + { + boolean has_area = ((c1 != 'T' && c1 != 'F' && matrix_interior != -1) || (c2 != 'T' && c2 != 'F' && matrix_boundary != -1) ? polygon.calculateArea2D() != 0 : true); + + if (has_area) + { + if (matrix_interior != -1) + m_matrix[matrix_interior] = 2; + if (matrix_boundary != -1) + m_matrix[matrix_boundary] = 1; + } + else + { + if (matrix_boundary != -1) + m_matrix[matrix_boundary] = -1; + + if (matrix_interior != -1) + { + Envelope2D env = new Envelope2D(); + polygon.queryEnvelope2D(env); + m_matrix[matrix_interior] = (env.getHeight() == 0.0 && env.getWidth() == 0.0 ? 0 : 1); + } + } + } + } + + private void areaAreaContainsPredicates_(Polygon polygon_b) { + m_matrix[MatrixPredicate.InteriorExterior] = 2; // im assuming its a proper contains. + m_matrix[MatrixPredicate.BoundaryInterior] = -1; + m_matrix[MatrixPredicate.BoundaryBoundary] = -1; + m_matrix[MatrixPredicate.BoundaryExterior] = 1; + m_matrix[MatrixPredicate.ExteriorInterior] = -1; + m_matrix[MatrixPredicate.ExteriorBoundary] = -1; + + areaGeomContainsOrDisjointPredicates_(polygon_b, m_perform_predicates[MatrixPredicate.InteriorInterior] ? MatrixPredicate.InteriorInterior : -1, m_scl.charAt(MatrixPredicate.InteriorInterior), m_perform_predicates[MatrixPredicate.InteriorBoundary] ? MatrixPredicate.InteriorBoundary : -1, m_scl.charAt(MatrixPredicate.InteriorBoundary)); + + // all other predicates should already be set by set_area_area_predicates + } + + private void areaAreaWithinPredicates_(Polygon polygon_a) { + areaAreaContainsPredicates_(polygon_a); + transposeMatrix_(m_matrix); + } + + private void areaLineDisjointPredicates_(Polygon polygon, Polyline polyline) { + m_matrix[MatrixPredicate.InteriorInterior] = -1; + m_matrix[MatrixPredicate.InteriorBoundary] = -1; + m_matrix[MatrixPredicate.BoundaryInterior] = -1; + m_matrix[MatrixPredicate.BoundaryBoundary] = -1; + + if (m_perform_predicates[MatrixPredicate.ExteriorInterior]) + { + char c = m_scl.charAt(MatrixPredicate.ExteriorInterior); + boolean b_has_length = (c != 'T' && c != 'F' ? polyline.calculateLength2D() != 0 : true); + m_matrix[MatrixPredicate.ExteriorInterior] = (b_has_length ? 1 : 0); + } + + if (m_perform_predicates[MatrixPredicate.ExteriorBoundary]) + { + boolean has_non_empty_boundary = Boundary.hasNonEmptyBoundary(polyline, null); + m_matrix[MatrixPredicate.ExteriorBoundary] = has_non_empty_boundary ? 0 : -1; + } + + areaGeomContainsOrDisjointPredicates_(polygon, m_perform_predicates[MatrixPredicate.InteriorExterior] ? MatrixPredicate.InteriorExterior : -1, m_scl.charAt(MatrixPredicate.InteriorExterior), m_perform_predicates[MatrixPredicate.BoundaryExterior] ? MatrixPredicate.BoundaryExterior : -1, m_scl.charAt(MatrixPredicate.BoundaryExterior)); + } + + private void areaLineContainsPredicates_(Polygon polygon, Polyline polyline) { + if (m_perform_predicates[MatrixPredicate.InteriorInterior]) + { + char c = m_scl.charAt(MatrixPredicate.InteriorInterior); + boolean b_has_length = (c != 'T' && c != 'F' ? polyline.calculateLength2D() != 0 : true); + m_matrix[MatrixPredicate.InteriorInterior] = (b_has_length ? 1 : 0); + } + + if (m_perform_predicates[MatrixPredicate.InteriorBoundary]) + { + boolean has_non_empty_boundary = Boundary.hasNonEmptyBoundary(polyline, null); + m_matrix[MatrixPredicate.InteriorBoundary] = has_non_empty_boundary ? 0 : -1; + } + + m_matrix[MatrixPredicate.InteriorExterior] = 2; //assume polygon has area + m_matrix[MatrixPredicate.BoundaryInterior] = -1; + m_matrix[MatrixPredicate.BoundaryBoundary] = -1; + m_matrix[MatrixPredicate.BoundaryExterior] = 1; //assume polygon has area + m_matrix[MatrixPredicate.ExteriorInterior] = -1; + m_matrix[MatrixPredicate.ExteriorBoundary] = -1; + } + + private void areaPointDisjointPredicates_(Polygon polygon) { + m_matrix[MatrixPredicate.InteriorInterior] = -1; + m_matrix[MatrixPredicate.BoundaryInterior] = -1; + m_matrix[MatrixPredicate.ExteriorInterior] = 0; + + areaGeomContainsOrDisjointPredicates_(polygon, m_perform_predicates[MatrixPredicate.InteriorExterior] ? MatrixPredicate.InteriorExterior : -1, m_scl.charAt(MatrixPredicate.InteriorExterior), m_perform_predicates[MatrixPredicate.BoundaryExterior] ? MatrixPredicate.BoundaryExterior : -1, m_scl.charAt(MatrixPredicate.BoundaryExterior)); + } + + private void areaPointContainsPredicates_(Polygon polygon) { + m_matrix[MatrixPredicate.InteriorInterior] = 0; + m_matrix[MatrixPredicate.InteriorExterior] = 2; //assume polygon has area + m_matrix[MatrixPredicate.BoundaryInterior] = -1; + m_matrix[MatrixPredicate.BoundaryExterior] = 1; //assume polygon has area + m_matrix[MatrixPredicate.ExteriorInterior] = -1; + } private void lineLineDisjointPredicates_(Polyline polyline_a, Polyline polyline_b) { - m_matrix[MatrixPredicate.InteriorInterior] = -1; - m_matrix[MatrixPredicate.InteriorBoundary] = -1; - m_matrix[MatrixPredicate.InteriorExterior] = 1; - m_matrix[MatrixPredicate.BoundaryInterior] = -1; - m_matrix[MatrixPredicate.BoundaryBoundary] = -1; - - if (m_perform_predicates[MatrixPredicate.BoundaryExterior]) { - boolean has_non_empty_boundary_a = Boundary.hasNonEmptyBoundary( - polyline_a, null); - m_matrix[MatrixPredicate.BoundaryExterior] = (has_non_empty_boundary_a ? 0 - : -1); - } - - m_matrix[MatrixPredicate.ExteriorInterior] = 1; - - if (m_perform_predicates[MatrixPredicate.ExteriorBoundary]) { - boolean has_non_empty_boundary_b = Boundary.hasNonEmptyBoundary( - polyline_b, null); - m_matrix[MatrixPredicate.ExteriorBoundary] = (has_non_empty_boundary_b ? 0 - : -1); - } + m_matrix[MatrixPredicate.InteriorInterior] = -1; + m_matrix[MatrixPredicate.InteriorBoundary] = -1; + m_matrix[MatrixPredicate.BoundaryInterior] = -1; + m_matrix[MatrixPredicate.BoundaryBoundary] = -1; + + if (m_perform_predicates[MatrixPredicate.InteriorExterior]) + { + char c = m_scl.charAt(MatrixPredicate.InteriorExterior); + boolean b_has_length = (c != 'T' && c != 'F' ? polyline_a.calculateLength2D() != 0 : true); + m_matrix[MatrixPredicate.InteriorExterior] = (b_has_length ? 1 : 0); + } + + if (m_perform_predicates[MatrixPredicate.BoundaryExterior]) + { + boolean has_non_empty_boundary_a = Boundary.hasNonEmptyBoundary(polyline_a, null); + m_matrix[MatrixPredicate.BoundaryExterior] = has_non_empty_boundary_a ? 0 : -1; + } + + if (m_perform_predicates[MatrixPredicate.ExteriorInterior]) + { + char c = m_scl.charAt(MatrixPredicate.ExteriorInterior); + boolean b_has_length = (c != 'T' && c != 'F' ? polyline_b.calculateLength2D() != 0 : true); + m_matrix[MatrixPredicate.ExteriorInterior] = (b_has_length ? 1 : 0); + } + + if (m_perform_predicates[MatrixPredicate.ExteriorBoundary]) + { + boolean has_non_empty_boundary_b = Boundary.hasNonEmptyBoundary(polyline_b, null); + m_matrix[MatrixPredicate.ExteriorBoundary] = has_non_empty_boundary_b ? 0 : -1; + } } private void linePointDisjointPredicates_(Polyline polyline) { - m_matrix[MatrixPredicate.InteriorInterior] = -1; - m_matrix[MatrixPredicate.BoundaryInterior] = -1; + m_matrix[MatrixPredicate.InteriorInterior] = -1; + m_matrix[MatrixPredicate.BoundaryInterior] = -1; - if (m_perform_predicates[MatrixPredicate.BoundaryExterior]) { - boolean has_non_empty_boundary = Boundary.hasNonEmptyBoundary( - polyline, null); - m_matrix[MatrixPredicate.BoundaryExterior] = (has_non_empty_boundary ? 0 - : -1); - } + if (m_perform_predicates[MatrixPredicate.InteriorExterior]) + { + char c = m_scl.charAt(MatrixPredicate.InteriorExterior); + boolean b_has_length = (c != 'T' && c != 'F' ? polyline.calculateLength2D() != 0 : true); + m_matrix[MatrixPredicate.InteriorExterior] = (b_has_length ? 1 : 0); + } - m_matrix[MatrixPredicate.ExteriorInterior] = 0; + if (m_perform_predicates[MatrixPredicate.BoundaryExterior]) + { + boolean has_non_empty_boundary = Boundary.hasNonEmptyBoundary(polyline, null); + m_matrix[MatrixPredicate.BoundaryExterior] = (has_non_empty_boundary ? 0 : -1); + } + + m_matrix[MatrixPredicate.ExteriorInterior] = 0; } private void pointPointDisjointPredicates_() { @@ -1441,197 +1884,211 @@ private void pointPointDisjointPredicates_() { // Invokes the 9 relational predicates of area vs Line. private boolean areaLinePredicates_(int half_edge, int id_a, int id_b) { - boolean bRelationKnown = true; - - if (m_perform_predicates[MatrixPredicate.InteriorInterior]) { - interiorAreaInteriorLine_(half_edge, id_a, id_b); - bRelationKnown &= isPredicateKnown_( - MatrixPredicate.InteriorInterior, 1); - } - - if (m_perform_predicates[MatrixPredicate.InteriorBoundary]) { - interiorAreaBoundaryLine_(half_edge, id_a, id_b, m_cluster_index_b); - bRelationKnown &= isPredicateKnown_( - MatrixPredicate.InteriorBoundary, 0); - } - - if (m_perform_predicates[MatrixPredicate.BoundaryInterior]) { - boundaryAreaInteriorLine_(half_edge, id_a, id_b, m_cluster_index_b); - bRelationKnown &= isPredicateKnown_( - MatrixPredicate.BoundaryInterior, 1); - } - - if (m_perform_predicates[MatrixPredicate.BoundaryBoundary]) { - boundaryAreaBoundaryLine_(half_edge, id_a, id_b, m_cluster_index_b); - bRelationKnown &= isPredicateKnown_( - MatrixPredicate.BoundaryBoundary, 0); - } - - if (m_perform_predicates[MatrixPredicate.BoundaryExterior]) { - boundaryAreaExteriorLine_(half_edge, id_a, id_b); - bRelationKnown &= isPredicateKnown_( - MatrixPredicate.BoundaryExterior, 1); - } - - if (m_perform_predicates[MatrixPredicate.ExteriorInterior]) { - exteriorAreaInteriorLine_(half_edge, id_a); - bRelationKnown &= isPredicateKnown_( - MatrixPredicate.ExteriorInterior, 1); - } - - if (m_perform_predicates[MatrixPredicate.ExteriorBoundary]) { - exteriorAreaBoundaryLine_(half_edge, id_a, id_b, m_cluster_index_b); - bRelationKnown &= isPredicateKnown_( - MatrixPredicate.ExteriorBoundary, 0); - } - - return bRelationKnown; + boolean bRelationKnown = true; + + if (m_perform_predicates[MatrixPredicate.InteriorInterior]) + { + interiorAreaInteriorLine_(half_edge, id_a, id_b); + bRelationKnown &= isPredicateKnown_(MatrixPredicate.InteriorInterior); + } + + if (m_perform_predicates[MatrixPredicate.InteriorBoundary]) + { + interiorAreaBoundaryLine_(half_edge, id_a, id_b, m_cluster_index_b); + bRelationKnown &= isPredicateKnown_(MatrixPredicate.InteriorBoundary); + } + + if (m_perform_predicates[MatrixPredicate.InteriorExterior]) + { + interiorAreaExteriorLine_(half_edge, id_a, id_b); + bRelationKnown &= isPredicateKnown_(MatrixPredicate.InteriorExterior); + } + + if (m_perform_predicates[MatrixPredicate.BoundaryInterior]) + { + boundaryAreaInteriorLine_(half_edge, id_a, id_b, m_cluster_index_b); + bRelationKnown &= isPredicateKnown_(MatrixPredicate.BoundaryInterior); + } + + if (m_perform_predicates[MatrixPredicate.BoundaryBoundary]) + { + boundaryAreaBoundaryLine_(half_edge, id_a, id_b, m_cluster_index_b); + bRelationKnown &= isPredicateKnown_(MatrixPredicate.BoundaryBoundary); + } + + if (m_perform_predicates[MatrixPredicate.BoundaryExterior]) + { + boundaryAreaExteriorLine_(half_edge, id_a, id_b); + bRelationKnown &= isPredicateKnown_(MatrixPredicate.BoundaryExterior); + } + + if (m_perform_predicates[MatrixPredicate.ExteriorInterior]) + { + exteriorAreaInteriorLine_(half_edge, id_a); + bRelationKnown &= isPredicateKnown_(MatrixPredicate.ExteriorInterior); + } + + if (m_perform_predicates[MatrixPredicate.ExteriorBoundary]) + { + exteriorAreaBoundaryLine_(half_edge, id_a, id_b, m_cluster_index_b); + bRelationKnown &= isPredicateKnown_(MatrixPredicate.ExteriorBoundary); + } + + return bRelationKnown; } // Invokes the 9 relational predicates of Line vs Line. private boolean lineLinePredicates_(int half_edge, int id_a, int id_b) { - boolean bRelationKnown = true; - - if (m_perform_predicates[MatrixPredicate.InteriorInterior]) { - interiorLineInteriorLine_(half_edge, id_a, id_b, m_cluster_index_a, - m_cluster_index_b); - bRelationKnown &= isPredicateKnown_( - MatrixPredicate.InteriorInterior, 1); - } - - if (m_perform_predicates[MatrixPredicate.InteriorBoundary]) { - interiorLineBoundaryLine_(half_edge, id_a, id_b, m_cluster_index_a, - m_cluster_index_b, MatrixPredicate.InteriorBoundary); - bRelationKnown &= isPredicateKnown_( - MatrixPredicate.InteriorBoundary, 0); - } - - if (m_perform_predicates[MatrixPredicate.InteriorExterior]) { - interiorLineExteriorLine_(half_edge, id_a, id_b, - MatrixPredicate.InteriorExterior); - bRelationKnown &= isPredicateKnown_( - MatrixPredicate.InteriorExterior, 1); - } - - if (m_perform_predicates[MatrixPredicate.BoundaryInterior]) { - interiorLineBoundaryLine_(half_edge, id_b, id_a, m_cluster_index_b, - m_cluster_index_a, MatrixPredicate.BoundaryInterior); - bRelationKnown &= isPredicateKnown_( - MatrixPredicate.BoundaryInterior, 0); - } - - if (m_perform_predicates[MatrixPredicate.BoundaryBoundary]) { - boundaryLineBoundaryLine_(half_edge, id_a, id_b, m_cluster_index_a, - m_cluster_index_b); - bRelationKnown &= isPredicateKnown_( - MatrixPredicate.BoundaryBoundary, 0); - } - - if (m_perform_predicates[MatrixPredicate.BoundaryExterior]) { - boundaryLineExteriorLine_(half_edge, id_a, id_b, m_cluster_index_a, - MatrixPredicate.BoundaryExterior); - bRelationKnown &= isPredicateKnown_( - MatrixPredicate.BoundaryExterior, 0); - } - - if (m_perform_predicates[MatrixPredicate.ExteriorInterior]) { - interiorLineExteriorLine_(half_edge, id_b, id_a, - MatrixPredicate.ExteriorInterior); - bRelationKnown &= isPredicateKnown_( - MatrixPredicate.ExteriorInterior, 1); - } - - if (m_perform_predicates[MatrixPredicate.ExteriorBoundary]) { - boundaryLineExteriorLine_(half_edge, id_b, id_a, m_cluster_index_b, - MatrixPredicate.ExteriorBoundary); - bRelationKnown &= isPredicateKnown_( - MatrixPredicate.ExteriorBoundary, 0); - } - - return bRelationKnown; + boolean bRelationKnown = true; + + if (m_perform_predicates[MatrixPredicate.InteriorInterior]) + { + interiorLineInteriorLine_(half_edge, id_a, id_b, m_cluster_index_a, m_cluster_index_b); + bRelationKnown &= isPredicateKnown_(MatrixPredicate.InteriorInterior); + } + + if (m_perform_predicates[MatrixPredicate.InteriorBoundary]) + { + interiorLineBoundaryLine_(half_edge, id_a, id_b, m_cluster_index_a, m_cluster_index_b, MatrixPredicate.InteriorBoundary); + bRelationKnown &= isPredicateKnown_(MatrixPredicate.InteriorBoundary); + } + + if (m_perform_predicates[MatrixPredicate.InteriorExterior]) + { + interiorLineExteriorLine_(half_edge, id_a, id_b, MatrixPredicate.InteriorExterior); + bRelationKnown &= isPredicateKnown_(MatrixPredicate.InteriorExterior); + } + + if (m_perform_predicates[MatrixPredicate.BoundaryInterior]) + { + interiorLineBoundaryLine_(half_edge, id_b, id_a, m_cluster_index_b, m_cluster_index_a, MatrixPredicate.BoundaryInterior); + bRelationKnown &= isPredicateKnown_(MatrixPredicate.BoundaryInterior); + } + + if (m_perform_predicates[MatrixPredicate.BoundaryBoundary]) + { + boundaryLineBoundaryLine_(half_edge, id_a, id_b, m_cluster_index_a, m_cluster_index_b); + bRelationKnown &= isPredicateKnown_(MatrixPredicate.BoundaryBoundary); + } + + if (m_perform_predicates[MatrixPredicate.BoundaryExterior]) + { + boundaryLineExteriorLine_(half_edge, id_a, id_b, m_cluster_index_a, MatrixPredicate.BoundaryExterior); + bRelationKnown &= isPredicateKnown_(MatrixPredicate.BoundaryExterior); + } + + if (m_perform_predicates[MatrixPredicate.ExteriorInterior]) + { + interiorLineExteriorLine_(half_edge, id_b, id_a, MatrixPredicate.ExteriorInterior); + bRelationKnown &= isPredicateKnown_(MatrixPredicate.ExteriorInterior); + } + + if (m_perform_predicates[MatrixPredicate.ExteriorBoundary]) + { + boundaryLineExteriorLine_(half_edge, id_b, id_a, m_cluster_index_b, MatrixPredicate.ExteriorBoundary); + bRelationKnown &= isPredicateKnown_(MatrixPredicate.ExteriorBoundary); + } + + return bRelationKnown; } // Invokes the 9 relational predicates of area vs Point. private boolean areaPointPredicates_(int cluster, int id_a, int id_b) { - boolean bRelationKnown = true; + boolean bRelationKnown = true; - if (m_perform_predicates[MatrixPredicate.InteriorInterior]) { - interiorAreaInteriorPoint_(cluster, id_a); - bRelationKnown &= isPredicateKnown_( - MatrixPredicate.InteriorInterior, 0); - } + if (m_perform_predicates[MatrixPredicate.InteriorInterior]) + { + interiorAreaInteriorPoint_(cluster, id_a); + bRelationKnown &= isPredicateKnown_(MatrixPredicate.InteriorInterior); + } - if (m_perform_predicates[MatrixPredicate.BoundaryInterior]) { - boundaryAreaInteriorPoint_(cluster, id_a, id_b); - bRelationKnown &= isPredicateKnown_( - MatrixPredicate.BoundaryInterior, 0); - } + if (m_perform_predicates[MatrixPredicate.InteriorExterior]) + { + interiorAreaExteriorPoint_(cluster, id_a); + bRelationKnown &= isPredicateKnown_(MatrixPredicate.InteriorExterior); + } - if (m_perform_predicates[MatrixPredicate.ExteriorInterior]) { - exteriorAreaInteriorPoint_(cluster, id_a); - bRelationKnown &= isPredicateKnown_( - MatrixPredicate.ExteriorInterior, 0); - } + if (m_perform_predicates[MatrixPredicate.BoundaryInterior]) + { + boundaryAreaInteriorPoint_(cluster, id_a, id_b); + bRelationKnown &= isPredicateKnown_(MatrixPredicate.BoundaryInterior); + } - return bRelationKnown; + if (m_perform_predicates[MatrixPredicate.BoundaryExterior]) + { + boundaryAreaExteriorPoint_(cluster, id_a); + bRelationKnown &= isPredicateKnown_(MatrixPredicate.BoundaryExterior); + } + + if (m_perform_predicates[MatrixPredicate.ExteriorInterior]) + { + exteriorAreaInteriorPoint_(cluster, id_a); + bRelationKnown &= isPredicateKnown_(MatrixPredicate.ExteriorInterior); + } + + return bRelationKnown; } // Invokes the 9 relational predicates of Line vs Point. private boolean linePointPredicates_(int cluster, int id_a, int id_b) { - boolean bRelationKnown = true; + boolean bRelationKnown = true; - if (m_perform_predicates[MatrixPredicate.InteriorInterior]) { - interiorLineInteriorPoint_(cluster, id_a, id_b, m_cluster_index_a); - bRelationKnown &= isPredicateKnown_( - MatrixPredicate.InteriorInterior, 0); - } + if (m_perform_predicates[MatrixPredicate.InteriorInterior]) + { + interiorLineInteriorPoint_(cluster, id_a, id_b, m_cluster_index_a); + bRelationKnown &= isPredicateKnown_(MatrixPredicate.InteriorInterior); + } - if (m_perform_predicates[MatrixPredicate.BoundaryInterior]) { - boundaryLineInteriorPoint_(cluster, id_a, id_b, m_cluster_index_a); - bRelationKnown &= isPredicateKnown_( - MatrixPredicate.BoundaryInterior, 0); - } + if (m_perform_predicates[MatrixPredicate.InteriorExterior]) + { + interiorLineExteriorPoint_(cluster, id_a, id_b, m_cluster_index_a); + bRelationKnown &= isPredicateKnown_(MatrixPredicate.InteriorExterior); + } - if (m_perform_predicates[MatrixPredicate.BoundaryExterior]) { - boundaryLineExteriorPoint_(cluster, id_a, id_b, m_cluster_index_a); - bRelationKnown &= isPredicateKnown_( - MatrixPredicate.BoundaryExterior, 0); - } + if (m_perform_predicates[MatrixPredicate.BoundaryInterior]) + { + boundaryLineInteriorPoint_(cluster, id_a, id_b, m_cluster_index_a); + bRelationKnown &= isPredicateKnown_(MatrixPredicate.BoundaryInterior); + } - if (m_perform_predicates[MatrixPredicate.ExteriorInterior]) { - exteriorLineInteriorPoint_(cluster, id_a, id_b); - bRelationKnown &= isPredicateKnown_( - MatrixPredicate.ExteriorInterior, 0); - } + if (m_perform_predicates[MatrixPredicate.BoundaryExterior]) + { + boundaryLineExteriorPoint_(cluster, id_a, id_b, m_cluster_index_a); + bRelationKnown &= isPredicateKnown_(MatrixPredicate.BoundaryExterior); + } - return bRelationKnown; + if (m_perform_predicates[MatrixPredicate.ExteriorInterior]) + { + exteriorLineInteriorPoint_(cluster, id_a, id_b); + bRelationKnown &= isPredicateKnown_(MatrixPredicate.ExteriorInterior); + } + + return bRelationKnown; } // Invokes the 9 relational predicates of Point vs Point. private boolean pointPointPredicates_(int cluster, int id_a, int id_b) { - boolean bRelationKnown = true; + boolean bRelationKnown = true; - if (m_perform_predicates[MatrixPredicate.InteriorInterior]) { - interiorPointInteriorPoint_(cluster, id_a, id_b); - bRelationKnown &= isPredicateKnown_( - MatrixPredicate.InteriorInterior, 0); - } + if (m_perform_predicates[MatrixPredicate.InteriorInterior]) + { + interiorPointInteriorPoint_(cluster, id_a, id_b); + bRelationKnown &= isPredicateKnown_(MatrixPredicate.InteriorInterior); + } - if (m_perform_predicates[MatrixPredicate.InteriorExterior]) { - interiorPointExteriorPoint_(cluster, id_a, id_b, - MatrixPredicate.InteriorExterior); - bRelationKnown &= isPredicateKnown_( - MatrixPredicate.InteriorExterior, 0); - } + if (m_perform_predicates[MatrixPredicate.InteriorExterior]) + { + interiorPointExteriorPoint_(cluster, id_a, id_b, MatrixPredicate.InteriorExterior); + bRelationKnown &= isPredicateKnown_(MatrixPredicate.InteriorExterior); + } - if (m_perform_predicates[MatrixPredicate.ExteriorInterior]) { - interiorPointExteriorPoint_(cluster, id_b, id_a, - MatrixPredicate.ExteriorInterior); - bRelationKnown &= isPredicateKnown_( - MatrixPredicate.ExteriorInterior, 0); - } + if (m_perform_predicates[MatrixPredicate.ExteriorInterior]) + { + interiorPointExteriorPoint_(cluster, id_b, id_a, MatrixPredicate.ExteriorInterior); + bRelationKnown &= isPredicateKnown_(MatrixPredicate.ExteriorInterior); + } - return bRelationKnown; + return bRelationKnown; } // Relational predicate to determine if the interior of area A intersects @@ -1766,6 +2223,19 @@ private void interiorAreaBoundaryLine_(int half_edge, int id_a, int id_b, } } + private void interiorAreaExteriorLine_(int half_edge, int id_a, int id_b) + { + if (m_matrix[MatrixPredicate.InteriorExterior] == 2) + return; + + int half_edge_parentage = m_topo_graph.getHalfEdgeParentage(half_edge); + + if ((half_edge_parentage & id_a) != 0) + {//half edge of polygon + m_matrix[MatrixPredicate.InteriorExterior] = 2; + } + } + // Relational predicate to determine if the boundary of area A intersects // with the interior of Line B. private void boundaryAreaInteriorLine_(int half_edge, int id_a, int id_b, @@ -2047,6 +2517,19 @@ private void interiorAreaInteriorPoint_(int cluster, int id_a) { } } + private void interiorAreaExteriorPoint_(int cluster, int id_a) + { + if (m_matrix[MatrixPredicate.InteriorExterior] == 2) + return; + + int cluster_parentage = m_topo_graph.getClusterParentage(cluster); + + if ((cluster_parentage & id_a) != 0) + { + m_matrix[MatrixPredicate.InteriorExterior] = 2; + } + } + // Relational predicate to determine if the boundary of area A intersects // with the interior of Point B. private void boundaryAreaInteriorPoint_(int cluster, int id_a, int id_b) { @@ -2060,6 +2543,19 @@ private void boundaryAreaInteriorPoint_(int cluster, int id_a, int id_b) { } } + private void boundaryAreaExteriorPoint_(int cluster, int id_a) + { + if (m_matrix[MatrixPredicate.BoundaryExterior] == 1) + return; + + int cluster_parentage = m_topo_graph.getClusterParentage(cluster); + + if ((cluster_parentage & id_a) != 0) + { + m_matrix[MatrixPredicate.BoundaryExterior] = 1; + } + } + // Relational predicate to determine if the exterior of area A intersects // with the interior of Point B. private void exteriorAreaInteriorPoint_(int cluster, int id_a) { @@ -2097,6 +2593,34 @@ private void interiorLineInteriorPoint_(int cluster, int id_a, int id_b, } } + private void interiorLineExteriorPoint_(int cluster, int id_a, int id_b, int cluster_index_a) + { + if (m_matrix[MatrixPredicate.InteriorExterior] == 1) + return; + + int half_edge_a = m_topo_graph.getClusterHalfEdge(cluster); + + if (half_edge_a != -1) + { + m_matrix[MatrixPredicate.InteriorExterior] = 1; + return; + } + + if (m_matrix[MatrixPredicate.InteriorExterior] != 0) + { + int clusterParentage = m_topo_graph.getClusterParentage(cluster); + + if ((clusterParentage & id_b) == 0) + { + assert(m_topo_graph.getClusterUserIndex(cluster, cluster_index_a) % 2 == 0); + m_matrix[MatrixPredicate.InteriorExterior] = 0; + return; + } + } + + return; + } + // Relational predicate to determine if the boundary of Line A intersects // with the interior of Point B. private void boundaryLineInteriorPoint_(int cluster, int id_a, int id_b, @@ -2189,6 +2713,27 @@ private void computeMatrixTopoGraphHalfEdges_(int geometry_a, int geometry_b) { for (int cluster = m_topo_graph.getFirstCluster(); cluster != -1; cluster = m_topo_graph .getNextCluster(cluster)) { int first_half_edge = m_topo_graph.getClusterHalfEdge(cluster); + if (first_half_edge == -1) + { + if (m_predicates_cluster != -1) + { + // Treat cluster as an interior point + switch (m_predicates_cluster) + { + case Predicates.AreaPointPredicates: + bRelationKnown = areaPointPredicates_(cluster, id_a, id_b); + break; + case Predicates.LinePointPredicates: + bRelationKnown = linePointPredicates_(cluster, id_a, id_b); + break; + default: + throw GeometryException.GeometryInternalError(); + } + } + + continue; + } + int next_half_edge = first_half_edge; do { @@ -2199,7 +2744,7 @@ private void computeMatrixTopoGraphHalfEdges_(int geometry_a, int geometry_b) { if (visited != 1) { do { // Invoke relational predicates - switch (m_predicates) { + switch (m_predicates_half_edge) { case Predicates.AreaAreaPredicates: bRelationKnown = areaAreaPredicates_(half_edge, id_a, id_b); @@ -2254,7 +2799,7 @@ private void computeMatrixTopoGraphClusters_(int geometry_a, int geometry_b) { for (int cluster = m_topo_graph.getFirstCluster(); cluster != -1; cluster = m_topo_graph .getNextCluster(cluster)) { // Invoke relational predicates - switch (m_predicates) { + switch (m_predicates_cluster) { case Predicates.AreaPointPredicates: bRelationKnown = areaPointPredicates_(cluster, id_a, id_b); break; @@ -2290,12 +2835,13 @@ private void setEditShapeCrackAndCluster_(EditShape shape, private void editShapeCrackAndCluster_(EditShape shape, double tolerance, ProgressTracker progress_tracker) { - CrackAndCluster.execute(shape, tolerance, progress_tracker); + CrackAndCluster.execute(shape, tolerance, progress_tracker, false); //do not filter degenerate segments. + shape.filterClosePoints(0, true, true);//remove degeneracies from polygon geometries. for (int geometry = shape.getFirstGeometry(); geometry != -1; geometry = shape .getNextGeometry(geometry)) { if (shape.getGeometryType(geometry) == Geometry.Type.Polygon .value()) - Simplificator.execute(shape, geometry, -1, false); + Simplificator.execute(shape, geometry, -1, false, progress_tracker); } } diff --git a/src/main/java/com/esri/core/geometry/SegmentIntersector.java b/src/main/java/com/esri/core/geometry/SegmentIntersector.java index ba82fd2f..ffbadb18 100644 --- a/src/main/java/com/esri/core/geometry/SegmentIntersector.java +++ b/src/main/java/com/esri/core/geometry/SegmentIntersector.java @@ -246,25 +246,21 @@ public boolean intersect(double tolerance, boolean b_intersecting) { double ptWeight; - Point2D pt; + Point2D pt = new Point2D(); if (rank1 == rank2) {// for equal ranks use weighted sum Point2D pt_1 = new Point2D(); line_1.getCoord2D(t1, pt_1); - pt_1.scale(weight1); Point2D pt_2 = new Point2D(); line_2.getCoord2D(t2, pt_2); - pt_2.scale(weight2); - pt = new Point2D(); - pt.add(pt_1, pt_2); ptWeight = weight1 + weight2; - pt.scale(1 / ptWeight); + double t = weight2 / ptWeight; + MathUtils.lerp(pt_1, pt_2, t, pt); if (Point2D.sqrDistance(pt, pt_1) + Point2D.sqrDistance(pt, pt_2) > small_tolerance_sqr) bigmove = true; } else {// for non-equal ranks, the higher rank wins if (rank1 > rank2) { - pt = new Point2D(); line_1.getCoord2D(t1, pt); ptWeight = weight1; Point2D pt_2 = new Point2D(); @@ -272,7 +268,6 @@ public boolean intersect(double tolerance, boolean b_intersecting) { if (Point2D.sqrDistance(pt, pt_2) > small_tolerance_sqr) bigmove = true; } else { - pt = new Point2D(); line_2.getCoord2D(t2, pt); ptWeight = weight2; Point2D pt_1 = new Point2D(); @@ -392,17 +387,14 @@ public void intersect(double tolerance, Point pt_intersector_point, double ptWeight; - Point2D pt; + Point2D pt = new Point2D(); if (rank1 == rank2) {// for equal ranks use weighted sum Point2D pt_1 = new Point2D(); line_1.getCoord2D(t1, pt_1); - pt_1.scale(weight1); Point2D pt_2 = pt_intersector_point.getXY(); - pt_2.scale(weight2); - pt = new Point2D(); - pt.add(pt_1, pt_2); ptWeight = weight1 + weight2; - pt.scale(1 / ptWeight); + double t = weight2 / ptWeight; + MathUtils.lerp(pt_1, pt_2, t, pt); } else {// for non-equal ranks, the higher rank wins if (rank1 > rank2) { pt = new Point2D(); diff --git a/src/main/java/com/esri/core/geometry/Simplificator.java b/src/main/java/com/esri/core/geometry/Simplificator.java index 955fea71..8234b828 100644 --- a/src/main/java/com/esri/core/geometry/Simplificator.java +++ b/src/main/java/com/esri/core/geometry/Simplificator.java @@ -42,6 +42,7 @@ class Simplificator { private int m_knownSimpleResult; private boolean m_bWinding; private boolean m_fixSelfTangency; + private ProgressTracker m_progressTracker; private void _beforeRemoveVertex(int vertex, boolean bChangePathFirst) { int vertexlistIndex = m_shape.getUserIndex(vertex, @@ -370,6 +371,15 @@ public int compare(int v1, int v2) { } private boolean _simplify() { + if (m_shape.getGeometryType(m_geometry) == Polygon.Type.Polygon.value() + && m_shape.getFillRule(m_geometry) == Polygon.FillRule.enumFillRuleWinding) + + { + TopologicalOperations ops = new TopologicalOperations(); + ops.planarSimplifyNoCrackingAndCluster(m_fixSelfTangency, + m_shape, m_geometry, m_progressTracker); + assert (m_shape.getFillRule(m_geometry) == Polygon.FillRule.enumFillRuleOddEven); + } boolean bChanged = false; boolean bNeedWindingRepeat = true; boolean bWinding = false; @@ -978,13 +988,14 @@ protected Simplificator() { } public static boolean execute(EditShape shape, int geometry, - int knownSimpleResult, boolean fixSelfTangency) { + int knownSimpleResult, boolean fixSelfTangency, ProgressTracker progressTracker) { Simplificator simplificator = new Simplificator(); simplificator.m_shape = shape; // simplificator.m_bWinding = bWinding; simplificator.m_geometry = geometry; simplificator.m_knownSimpleResult = knownSimpleResult; simplificator.m_fixSelfTangency = fixSelfTangency; + simplificator.m_progressTracker = progressTracker; return simplificator._simplify(); } diff --git a/src/main/java/com/esri/core/geometry/TopoGraph.java b/src/main/java/com/esri/core/geometry/TopoGraph.java index 0bf61cd7..1146860d 100644 --- a/src/main/java/com/esri/core/geometry/TopoGraph.java +++ b/src/main/java/com/esri/core/geometry/TopoGraph.java @@ -70,6 +70,17 @@ static interface EnumInputMode { boolean m_buildChains = true; + private boolean m_dirty_check_failed = false; + private double m_check_dirty_planesweep_tolerance = Double.NaN; + + void check_dirty_planesweep(double tolerance) { + m_check_dirty_planesweep_tolerance = tolerance; + } + + boolean dirty_check_failed() { + return m_dirty_check_failed; + } + NonSimpleResult m_non_simple_result = new NonSimpleResult(); int m_pointCount;// point count processed in this Topo_graph. Used to @@ -990,9 +1001,16 @@ void createHalfEdges_(int inputMode, AttributeStreamOfInt32 sorted_vertices) { int clusterTo = m_shape.getUserIndex(next, m_clusterIndex); assert (clusterTo != -1); - assert (cluster != clusterTo);// probably will assert for - // verticasl segments! Need to - // refactor a little + if (cluster == clusterTo) { + if (m_shape.getSegment(vertex) != null) { + assert (m_shape.getSegment(vertex).calculateLength2D() == 0); + } else { + assert (m_shape.getXY(vertex).isEqual(m_shape.getXY(next))); + } + + continue; + } + int half_edge = newHalfEdgePair_(); int twinEdge = getHalfEdgeTwin(half_edge); @@ -1182,6 +1200,7 @@ void sortHalfEdgesByAngle_(int inputMode) { } while (edge != first); if (angleSorter.size() > 1) { + boolean changed_order = true; if (angleSorter.size() > 2) { angleSorter.Sort(0, angleSorter.size(), tgac); // std::sort(angleSorter.get_ptr(), @@ -1191,12 +1210,16 @@ void sortHalfEdgesByAngle_(int inputMode) { // TopoGraphAngleComparer(this)); angleSorter.add(angleSorter.get(0)); } else { - if (compareEdgeAngles_(angleSorter.get(0), + //no need to sort most two edge cases. we only need to make sure that edges going up are sorted + if (compareEdgeAnglesForPair_(angleSorter.get(0), angleSorter.get(1)) > 0) { int tmp = angleSorter.get(0); angleSorter.set(0, angleSorter.get(1)); angleSorter.set(1, tmp); } + else { + changed_order = false; + } } // 2. get rid of duplicate edges by merging them (duplicate // edges appear at this step because we converted all @@ -1325,7 +1348,10 @@ else if (m_tmpHalfEdgeOddEvenNumberIndex != -1) { continue; } - + else { + //edges do not coincide + } + updateVertexToHalfEdgeConnection_(prevMerged, false); prevMerged = -1; ePrev = e; @@ -1333,8 +1359,27 @@ else if (m_tmpHalfEdgeOddEvenNumberIndex != -1) { ePrevTwin = eTwin; } + updateVertexToHalfEdgeConnection_(prevMerged, false); prevMerged = -1; + + if (!changed_order) { + //small optimization to avoid reconnecting if nothing changed + e0 = -1; + for (int i = 0, n = angleSorter.size(); i < n; i++) { + int e = angleSorter.get(i); + if (e == -1) + continue; + e0 = e; + break; + } + + if (first != e0) + setClusterHalfEdge_(cluster, e0); + + continue; //next cluster + } + // 3. Reconnect edges in the sorted order. The edges are // sorted counter clockwise. @@ -1582,6 +1627,7 @@ boolean removeSpikes_() { void setEditShapeImpl_(EditShape shape, int inputMode, AttributeStreamOfInt32 editShapeGeometries, ProgressTracker progress_tracker, boolean bBuildChains) { + assert(!m_dirty_check_failed); assert (editShapeGeometries == null || editShapeGeometries.size() > 0); removeShape(); @@ -1723,6 +1769,17 @@ void setEditShapeImpl_(EditShape shape, int inputMode, if (m_non_simple_result.m_reason != NonSimpleResult.Reason.NotDetermined) return; + if (!NumberUtils.isNaN(m_check_dirty_planesweep_tolerance)) { + if (!check_structure_after_dirty_sweep_())// checks the edges. + { + m_dirty_check_failed = true;// set m_dirty_check_failed when an + // issue is found. We'll rerun the + // planesweep using robust crack and + // cluster approach. + return; + } + } + buildChains_(inputMode); if (m_non_simple_result.m_reason != NonSimpleResult.Reason.NotDetermined) return; @@ -1847,10 +1904,11 @@ void removeShape() { if (m_shape == null) return; - if (m_geometryIDIndex != -1) + if (m_geometryIDIndex != -1) { m_shape.removeGeometryUserIndex(m_geometryIDIndex); + m_geometryIDIndex = -1; + } - m_geometryIDIndex = -1; if (m_clusterIndex != -1) { m_shape.removeUserIndex(m_clusterIndex); m_clusterIndex = -1; @@ -2454,13 +2512,6 @@ int compareEdgeAngles_(int edge1, int edge2) { Point2D pt10 = new Point2D(); getHalfEdgeFromXY(edge1, pt10); - // #ifdef DEBUG - // { - // Point_2D pt20; - // get_half_edge_from_xy(edge2, pt20); - // assert(pt10.is_equal(pt20)); - // } - // #endif Point2D v_1 = new Point2D(); v_1.sub(pt_1, pt10); @@ -2469,4 +2520,99 @@ int compareEdgeAngles_(int edge1, int edge2) { int result = Point2D._compareVectors(v_1, v_2); return result; } + + int compareEdgeAnglesForPair_(int edge1, int edge2) { + if (edge1 == edge2) + return 0; + + Point2D pt_1 = new Point2D(); + getHalfEdgeToXY(edge1, pt_1); + + Point2D pt_2 = new Point2D(); + getHalfEdgeToXY(edge2, pt_2); + + if (pt_1.isEqual(pt_2)) + return 0;// overlap case + + Point2D pt10 = new Point2D(); + getHalfEdgeFromXY(edge1, pt10); + + Point2D v_1 = new Point2D(); + v_1.sub(pt_1, pt10); + Point2D v_2 = new Point2D(); + v_2.sub(pt_2, pt10); + + if (v_2.y >= 0 && v_1.y > 0) { + int result = Point2D._compareVectors(v_1, v_2); + return result; + } + else { + return 0; + } + } + + boolean check_structure_after_dirty_sweep_() { + // for each cluster go through the cluster half edges and check that + // min(edge1_length, edge2_length) * angle_between is less than + // m_check_dirty_planesweep_tolerance. + // Doing this helps us weed out cases missed by the dirty plane sweep. + // We do not need absolute accuracy here. + assert (!m_dirty_check_failed); + assert (!NumberUtils.isNaN(m_check_dirty_planesweep_tolerance)); + double sqr_tol = MathUtils.sqr(m_check_dirty_planesweep_tolerance); + Point2D pt10 = new Point2D(); + Point2D pt_2 = new Point2D(); + Point2D pt_1 = new Point2D(); + Point2D v_1 = new Point2D(); + Point2D v_2 = new Point2D(); + for (int cluster = getFirstCluster(); cluster != -1; cluster = getNextCluster(cluster)) { + int first = getClusterHalfEdge(cluster); + if (first != -1) { + int edge = first; + getHalfEdgeFromXY(edge, pt10); + getHalfEdgeToXY(edge, pt_2); + v_2.sub(pt_2, pt10); + double sqr_len2 = v_2.sqrLength(); + + do { + int prev = edge; + edge = getHalfEdgeNext(getHalfEdgeTwin(edge)); + + if (edge != prev) { + getHalfEdgeToXY(edge, pt_1); + assert (!pt_1.isEqual(pt_2)); + v_1.sub(pt_1, pt10); + double sqr_len1 = v_1.sqrLength(); + + double cross = v_1.crossProduct(v_2); // cross_prod = + // len1 * len2 * + // sinA => sinA + // = cross_prod + // / (len1 * + // len2); + double sqr_sinA = (cross * cross) + / (sqr_len1 * sqr_len2); // sqr_sinA is + // approximately A^2 + // especially for + // smaller angles + double sqr_dist = Math.min(sqr_len1, sqr_len2) + * sqr_sinA; + if (sqr_dist <= sqr_tol) { + // these edges incident on the cluster form a narrow + // wedge and thei require cracking event that was + // missed. + return false; + } + + v_2.setCoords(v_1); + sqr_len2 = sqr_len1; + pt_2.setCoords(pt_1); + } + } while (edge != first); + } + } + + return true; + } + } diff --git a/src/main/java/com/esri/core/geometry/TopologicalOperations.java b/src/main/java/com/esri/core/geometry/TopologicalOperations.java index 4e2b02b7..b6746704 100644 --- a/src/main/java/com/esri/core/geometry/TopologicalOperations.java +++ b/src/main/java/com/esri/core/geometry/TopologicalOperations.java @@ -24,7 +24,9 @@ package com.esri.core.geometry; import com.esri.core.geometry.AttributeStreamOfInt32.IntComparator; +import com.esri.core.geometry.Geometry.GeometryType; import com.esri.core.geometry.MultiVertexGeometryImpl.GeometryXSimple; + import java.util.ArrayList; final class TopologicalOperations { @@ -88,13 +90,14 @@ void setEditShape(EditShape shape, ProgressTracker progressTracker) { void setEditShapeCrackAndCluster(EditShape shape, double tolerance, ProgressTracker progressTracker) { - CrackAndCluster.execute(shape, tolerance, progressTracker); + CrackAndCluster.execute(shape, tolerance, progressTracker, true); for (int geometry = shape.getFirstGeometry(); geometry != -1; geometry = shape .getNextGeometry(geometry)) { if (shape.getGeometryType(geometry) == Geometry.Type.Polygon .value()) - Simplificator.execute(shape, geometry, -1, m_bOGCOutput); + Simplificator.execute(shape, geometry, -1, m_bOGCOutput, progressTracker); } + setEditShape(shape, progressTracker); } @@ -294,7 +297,7 @@ private int topoOperationPolygonPolygon_(int geometry_a, int geometry_b, m_topo_graph.deleteUserIndexForHalfEdges(visitedEdges); Simplificator.execute(shape, newGeometry, - MultiVertexGeometryImpl.GeometryXSimple.Weak, m_bOGCOutput); + MultiVertexGeometryImpl.GeometryXSimple.Weak, m_bOGCOutput, null); return newGeometry; } @@ -513,7 +516,7 @@ int[] topoOperationPolygonPolygonEx_(int geometry_a, int geometry_b, m_topo_graph.deleteUserIndexForClusters(visitedClusters); m_topo_graph.deleteUserIndexForHalfEdges(visitedEdges); Simplificator.execute(shape, newGeometryPolygon, - MultiVertexGeometryImpl.GeometryXSimple.Weak, m_bOGCOutput); + MultiVertexGeometryImpl.GeometryXSimple.Weak, m_bOGCOutput, null); int[] result = new int[3];// always returns size 3 result. result[0] = newGeometryMultipoint; @@ -1150,11 +1153,16 @@ MultiVertexGeometry planarSimplify(EditShape shape, int geom, // complications. Need to do // full crack and cluster. { - CrackAndCluster.execute(shape, tolerance, progress_tracker); + CrackAndCluster.execute(shape, tolerance, progress_tracker, true); + dirty_result = false; + } else { + m_topo_graph.check_dirty_planesweep(tolerance); } } else { - CrackAndCluster.execute(shape, tolerance, progress_tracker); + CrackAndCluster.execute(shape, tolerance, progress_tracker, true); + dirty_result = false; } + if (!b_use_winding_rule_for_polygons || shape.getGeometryType(geom) == Geometry.Type.MultiPoint .value()) @@ -1162,6 +1170,22 @@ MultiVertexGeometry planarSimplify(EditShape shape, int geom, else m_topo_graph.setAndSimplifyEditShapeWinding(shape, geom, progress_tracker); + if (m_topo_graph.dirty_check_failed()) { + // we ran the sweep_vertical() before and it produced some + // issues that where detected by topo graph only. + assert (dirty_result); + m_topo_graph.removeShape(); + m_topo_graph = null; + // that's at most two level recursion + return planarSimplify(shape, geom, tolerance, + b_use_winding_rule_for_polygons, false, + progress_tracker); + } else { + //can proceed + } + + m_topo_graph.check_dirty_planesweep(NumberUtils.TheNaN); + int ID_a = m_topo_graph.getGeometryID(geom); initMaskLookupArray_((ID_a) + 1); m_mask_lookup[ID_a] = true; // Works only when there is a single @@ -1175,9 +1199,11 @@ MultiVertexGeometry planarSimplify(EditShape shape, int geom, .value())) { // geom can be a polygon or a polyline. // It can be a polyline only when the winding rule is true. + shape.setFillRule(geom, Polygon.FillRule.enumFillRuleOddEven); int resGeom = topoOperationPolygonPolygon_(geom, -1, -1); Polygon polygon = (Polygon) shape.getGeometry(resGeom); + polygon.setFillRule(Polygon.FillRule.enumFillRuleOddEven);//standardize the fill rule. if (!dirty_result) { ((MultiVertexGeometryImpl) polygon._getImpl()).setIsSimple( GeometryXSimple.Strong, tolerance, false); @@ -1227,6 +1253,57 @@ static MultiVertexGeometry planarSimplify(MultiVertexGeometry input_geom, use_winding_rule_for_polygons, dirty_result, progress_tracker); } + boolean planarSimplifyNoCrackingAndCluster(boolean OGCoutput, EditShape shape, int geom, ProgressTracker progress_tracker) + { + m_bOGCOutput = OGCoutput; + m_topo_graph = new TopoGraph(); + int rule = shape.getFillRule(geom); + int gt = shape.getGeometryType(geom); + if (rule != Polygon.FillRule.enumFillRuleWinding || gt == GeometryType.MultiPoint) + m_topo_graph.setAndSimplifyEditShapeAlternate(shape, geom, progress_tracker); + else + m_topo_graph.setAndSimplifyEditShapeWinding(shape, geom, progress_tracker); + + if (m_topo_graph.dirty_check_failed()) + return false; + + m_topo_graph.check_dirty_planesweep(NumberUtils.TheNaN); + + int ID_a = m_topo_graph.getGeometryID(geom); + initMaskLookupArray_((ID_a)+1); + m_mask_lookup[ID_a] = true; //Works only when there is a single geometry in the edit shape. + //To make it work when many geometries are present, this need to be modified. + + if (shape.getGeometryType(geom) == GeometryType.Polygon || (rule == Polygon.FillRule.enumFillRuleWinding && shape.getGeometryType(geom) != GeometryType.MultiPoint)) + { + //geom can be a polygon or a polyline. + //It can be a polyline only when the winding rule is true. + shape.setFillRule(geom, Polygon.FillRule.enumFillRuleOddEven); + int resGeom = topoOperationPolygonPolygon_(geom, -1, -1); + shape.swapGeometry(resGeom, geom); + shape.removeGeometry(resGeom); + } + else if (shape.getGeometryType(geom) == GeometryType.Polyline) + { + int resGeom = topoOperationPolylinePolylineOrPolygon_(-1); + shape.swapGeometry(resGeom, geom); + shape.removeGeometry(resGeom); + } + else if (shape.getGeometryType(geom) == GeometryType.MultiPoint) + { + int resGeom = topoOperationMultiPoint_(); + shape.swapGeometry(resGeom, geom); + shape.removeGeometry(resGeom); + } + else + { + throw new GeometryException("internal error"); + } + + return true; + } + + static MultiVertexGeometry simplifyOGC(MultiVertexGeometry input_geom, double tolerance, boolean dirty_result, ProgressTracker progress_tracker) { TopologicalOperations topoOps = new TopologicalOperations(); diff --git a/src/test/java/com/esri/core/geometry/TestEditShape.java b/src/test/java/com/esri/core/geometry/TestEditShape.java index 94bd5fb2..173a4970 100644 --- a/src/test/java/com/esri/core/geometry/TestEditShape.java +++ b/src/test/java/com/esri/core/geometry/TestEditShape.java @@ -183,7 +183,7 @@ public static void testEditShape() { EditShape editShape = new EditShape(); int geom = editShape.addGeometry(poly); - editShape.filterClosePoints(0.002, true); + editShape.filterClosePoints(0.002, true, false); Polygon poly2 = (Polygon) editShape.getGeometry(geom); assertTrue(poly2.isEmpty()); } @@ -197,7 +197,7 @@ public static void testEditShape() { EditShape editShape = new EditShape(); int geom = editShape.addGeometry(poly); - editShape.filterClosePoints(0.002, true); + editShape.filterClosePoints(0.002, true, false); Polygon poly2 = (Polygon) editShape.getGeometry(geom); assertTrue(!poly2.isEmpty()); } @@ -211,7 +211,7 @@ public static void testEditShape() { EditShape editShape = new EditShape(); int geom = editShape.addGeometry(poly); - editShape.filterClosePoints(0.002, true); + editShape.filterClosePoints(0.002, true, false); Polygon poly2 = (Polygon) editShape.getGeometry(geom); assertTrue(poly2.isEmpty()); } @@ -295,7 +295,7 @@ public static void testEditShape() { EditShape shape = new EditShape(); int g1 = shape.addGeometry(line1); int g2 = shape.addGeometry(line2); - CrackAndCluster.execute(shape, 0.001, null); + CrackAndCluster.execute(shape, 0.001, null, true); Polyline chopped_line1 = (Polyline) shape.getGeometry(g1); Polyline chopped_line2 = (Polyline) shape.getGeometry(g2); diff --git a/src/test/java/com/esri/core/geometry/TestGeodetic.java b/src/test/java/com/esri/core/geometry/TestGeodetic.java index d377e6f6..0cdbf6f7 100644 --- a/src/test/java/com/esri/core/geometry/TestGeodetic.java +++ b/src/test/java/com/esri/core/geometry/TestGeodetic.java @@ -1,6 +1,7 @@ package com.esri.core.geometry; import junit.framework.TestCase; + import org.junit.Test; public class TestGeodetic extends TestCase { @@ -15,7 +16,7 @@ protected void tearDown() throws Exception { } @Test - public static void testTriangleLength() { + public void testTriangleLength() { Point pt_0 = new Point(10, 10); Point pt_1 = new Point(20, 20); Point pt_2 = new Point(20, 10); @@ -27,7 +28,7 @@ public static void testTriangleLength() { } @Test - public static void testRotationInvariance() { + public void testRotationInvariance() { Point pt_0 = new Point(10, 40); Point pt_1 = new Point(20, 60); Point pt_2 = new Point(20, 40); @@ -50,7 +51,7 @@ public static void testRotationInvariance() { } @Test - public static void testLengthAccurateCR191313() { + public void testLengthAccurateCR191313() { /* * // random_test(); OperatorFactoryLocal engine = * OperatorFactoryLocal.getInstance(); //TODO: Make this: @@ -69,4 +70,4 @@ public static void testLengthAccurateCR191313() { * assertTrue(Math.abs(length - 2738362.3249366437) < 2e-9 * length); */ } - } + } diff --git a/src/test/java/com/esri/core/geometry/TestGeomToJSonExportSRFromWkiOrWkt_CR181369.java b/src/test/java/com/esri/core/geometry/TestGeomToJSonExportSRFromWkiOrWkt_CR181369.java index 2de57b43..ce4f81dc 100644 --- a/src/test/java/com/esri/core/geometry/TestGeomToJSonExportSRFromWkiOrWkt_CR181369.java +++ b/src/test/java/com/esri/core/geometry/TestGeomToJSonExportSRFromWkiOrWkt_CR181369.java @@ -25,20 +25,18 @@ protected void tearDown() throws Exception { SpatialReference spatialReferenceWGS84 = SpatialReference.create(4326); @Test - public void testGeomToJSonExportSRFromWkiOrWkt_CR181369() + public void testLocalExport() throws JsonParseException, IOException { - testPoint(); - testPolyline(); - testPolygon(); - testEnvelope(); - testMultiPoint(); - testCR181369(); - // These tests return the result of a method called - // checkResultSpatialRef. - // However, the tests pass or fail regardless of what that method - // returns. + String s = OperatorExportToJson.local().execute(null, new Point(1000000.2, 2000000.3)); + //assertTrue(s.contains(".")); + //assertFalse(s.contains(",")); + Polyline line = new Polyline(); + line.startPath(1.1, 2.2); + line.lineTo(2.3, 4.5); + String s1 = OperatorExportToJson.local().execute(null, line); + assertTrue(s.contains(".")); } - + boolean testPoint() throws JsonParseException, IOException { boolean bAnswer = true; Point point1 = new Point(10.0, 20.0); diff --git a/src/test/java/com/esri/core/geometry/TestPoint.java b/src/test/java/com/esri/core/geometry/TestPoint.java index df53b170..0140198a 100644 --- a/src/test/java/com/esri/core/geometry/TestPoint.java +++ b/src/test/java/com/esri/core/geometry/TestPoint.java @@ -23,6 +23,8 @@ public void testPt() { assertTrue(pt.isEmpty()); pt.setXY(10, 2); assertFalse(pt.isEmpty()); + + pt.toString(); } @Test @@ -165,5 +167,21 @@ public void testEnvelope2D_corners() { assertFalse(env.containsExclusive(env.getUpperLeft())); assertTrue(env.contains(env.getUpperLeft())); assertTrue(env.containsExclusive(env.getCenter())); + } + + @Test + public void testReplaceNaNs() { + Envelope env = new Envelope(); + Point pt = new Point(); + pt.setXY(1, 2); + pt.setZ(Double.NaN); + pt.queryEnvelope(env); + pt.replaceNaNs(VertexDescription.Semantics.Z, 5); + assertTrue(pt.equals(new Point(1, 2, 5))); + + assertTrue(env.hasZ()); + assertTrue(env.queryInterval(VertexDescription.Semantics.Z, 0).isEmpty()); + env.replaceNaNs(VertexDescription.Semantics.Z, 5); + assertTrue(env.queryInterval(VertexDescription.Semantics.Z, 0).equals(new Envelope1D(5, 5))); } } diff --git a/src/test/java/com/esri/core/geometry/TestPolygon.java b/src/test/java/com/esri/core/geometry/TestPolygon.java index 22d9617c..2d6906ab 100644 --- a/src/test/java/com/esri/core/geometry/TestPolygon.java +++ b/src/test/java/com/esri/core/geometry/TestPolygon.java @@ -1,6 +1,7 @@ package com.esri.core.geometry; import junit.framework.TestCase; + import org.junit.Test; import com.esri.core.geometry.ogc.OGCGeometry; @@ -66,8 +67,9 @@ public void testCreation1() { @SuppressWarnings("unused") int number = poly.getStateFlag(); Envelope env = new Envelope(1000, 2000, 1010, 2010); - + env.toString(); poly.addEnvelope(env, false); + poly.toString(); number = poly.getStateFlag(); assertTrue(Math.abs(poly.calculateArea2D() - 100) < 1e-12); assertTrue(Math.abs(poly.calculateLength2D() - 40) < 1e-12); @@ -1074,8 +1076,12 @@ public void testCR177477getPathEnd() { // int endIndex = pg.getPathEnd(pathCount - 1); Line line = new Line(); + line.toString(); + line.setStart(new Point(0, 0)); line.setEnd(new Point(1, 0)); + + line.toString(); double geoLength = GeometryEngine.geodesicDistanceOnWGS84(new Point(0, 0), new Point(1, 0)); @@ -1138,4 +1144,40 @@ public void testBoundary() { assertTrue(s .equals("MULTILINESTRING ((-10 -10, 10 -10, 10 10, -10 10, -10 -10), (-5 -5, -5 5, 5 5, 5 -5, -5 -5))")); } + + @Test + public void testReplaceNaNs() { + { + MultiPoint mp = new MultiPoint(); + Point pt = new Point(); + pt.setXY(1, 2); + pt.setZ(Double.NaN); + mp.add(pt); + pt = new Point(); + pt.setXY(11, 12); + pt.setZ(3); + mp.add(pt); + + mp.replaceNaNs(VertexDescription.Semantics.Z, 5); + assertTrue(mp.getPoint(0).equals(new Point(1, 2, 5))); + assertTrue(mp.getPoint(1).equals(new Point(11, 12, 3))); + } + + { + Polygon mp = new Polygon(); + Point pt = new Point(); + pt.setXY(1, 2); + pt.setZ(Double.NaN); + mp.startPath(pt); + pt = new Point(); + pt.setXY(11, 12); + pt.setZ(3); + mp.lineTo(pt); + + mp.replaceNaNs(VertexDescription.Semantics.Z, 5); + assertTrue(mp.getPoint(0).equals(new Point(1, 2, 5))); + assertTrue(mp.getPoint(1).equals(new Point(11, 12, 3))); + } + } + } diff --git a/src/test/java/com/esri/core/geometry/TestQuadTree.java b/src/test/java/com/esri/core/geometry/TestQuadTree.java index 3836ec13..7ffdc06d 100644 --- a/src/test/java/com/esri/core/geometry/TestQuadTree.java +++ b/src/test/java/com/esri/core/geometry/TestQuadTree.java @@ -1,6 +1,9 @@ package com.esri.core.geometry; +import java.util.ArrayList; + import junit.framework.TestCase; + import org.junit.Test; public class TestQuadTree extends TestCase { @@ -78,6 +81,7 @@ public static void test2() { assertTrue(count == 10000); } + public static Polyline makePolyline() { Polyline poly = new Polyline(); diff --git a/src/test/java/com/esri/core/geometry/TestRelation.java b/src/test/java/com/esri/core/geometry/TestRelation.java index 613f8b13..10386f82 100644 --- a/src/test/java/com/esri/core/geometry/TestRelation.java +++ b/src/test/java/com/esri/core/geometry/TestRelation.java @@ -19,7 +19,7 @@ protected void tearDown() throws Exception { } @Test - public static void testCreation() { + public void testCreation() { { OperatorFactoryLocal projEnv = OperatorFactoryLocal.getInstance(); SpatialReference inputSR = SpatialReference.create(3857); @@ -132,7 +132,7 @@ public static void testCreation() { } @Test - public static void testOperatorDisjoint() { + public void testOperatorDisjoint() { { OperatorFactoryLocal projEnv = OperatorFactoryLocal.getInstance(); SpatialReference inputSR = SpatialReference.create(3857); @@ -191,7 +191,7 @@ public static void testOperatorDisjoint() { } @Test - public static void testTouchPointLineCR183227() {// Tests CR 183227 + public void testTouchPointLineCR183227() {// Tests CR 183227 OperatorTouches operatorTouches = (OperatorTouches) (OperatorFactoryLocal .getInstance().getOperator(Operator.Type.Touches)); @@ -219,7 +219,7 @@ public static void testTouchPointLineCR183227() {// Tests CR 183227 } @Test - public static void testTouchPointLineClosed() {// Tests CR 183227 + public void testTouchPointLineClosed() {// Tests CR 183227 OperatorTouches operatorTouches = (OperatorTouches) (OperatorFactoryLocal .getInstance().getOperator(Operator.Type.Touches)); @@ -247,7 +247,7 @@ public static void testTouchPointLineClosed() {// Tests CR 183227 } @Test - public static void testTouchPolygonPolygon() { + public void testTouchPolygonPolygon() { OperatorTouches operatorTouches = (OperatorTouches) (OperatorFactoryLocal .getInstance().getOperator(Operator.Type.Touches)); @@ -270,7 +270,7 @@ public static void testTouchPolygonPolygon() { } @Test - public static void testContainsFailureCR186456() { + public void testContainsFailureCR186456() { { OperatorContains op = (OperatorContains) (OperatorFactoryLocal .getInstance().getOperator(Operator.Type.Contains)); @@ -283,7 +283,7 @@ public static void testContainsFailureCR186456() { } @Test - public static void testWithin() { + public void testWithin() { { OperatorWithin op = (OperatorWithin) (OperatorFactoryLocal .getInstance().getOperator(Operator.Type.Within)); @@ -364,7 +364,7 @@ public static void testWithin() { } @Test - public static void testContains() { + public void testContains() { { OperatorContains op = (OperatorContains) (OperatorFactoryLocal .getInstance().getOperator(Operator.Type.Contains)); @@ -444,7 +444,7 @@ public static void testContains() { } @Test - public static void testOverlaps() { + public void testOverlaps() { {// empty polygon OperatorOverlaps op = (OperatorOverlaps) (OperatorFactoryLocal .getInstance().getOperator(Operator.Type.Overlaps)); @@ -618,7 +618,7 @@ public static void testOverlaps() { } @Test - public static void testPolygonPolygonEquals() { + public void testPolygonPolygonEquals() { OperatorEquals equals = (OperatorEquals) (OperatorFactoryLocal .getInstance().getOperator(Operator.Type.Equals)); SpatialReference sr = SpatialReference.create(102100); @@ -697,7 +697,7 @@ public static void testPolygonPolygonEquals() { } @Test - public static void testMultiPointMultiPointEquals() { + public void testMultiPointMultiPointEquals() { OperatorEquals equals = (OperatorEquals) OperatorFactoryLocal .getInstance().getOperator(Operator.Type.Equals); SpatialReference sr = SpatialReference.create(102100); @@ -738,7 +738,7 @@ public static void testMultiPointMultiPointEquals() { } @Test - public static void testMultiPointPointEquals() { + public void testMultiPointPointEquals() { OperatorEquals equals = (OperatorEquals) (OperatorFactoryLocal .getInstance().getOperator(Operator.Type.Equals)); OperatorWithin within = (OperatorWithin) (OperatorFactoryLocal @@ -780,7 +780,7 @@ public static void testMultiPointPointEquals() { } @Test - public static void testPointPointEquals() { + public void testPointPointEquals() { OperatorEquals equals = (OperatorEquals) (OperatorFactoryLocal .getInstance().getOperator(Operator.Type.Equals)); OperatorWithin within = (OperatorWithin) (OperatorFactoryLocal @@ -843,7 +843,7 @@ public static void testPointPointEquals() { } @Test - public static void testPolygonPolygonDisjoint() { + public void testPolygonPolygonDisjoint() { OperatorDisjoint disjoint = (OperatorDisjoint) (OperatorFactoryLocal .getInstance().getOperator(Operator.Type.Disjoint)); SpatialReference sr = SpatialReference.create(102100); @@ -916,10 +916,61 @@ public static void testPolygonPolygonDisjoint() { assertTrue(!res); res = disjoint.execute(polygon2, polygon1, sr, null); assertTrue(!res); + + polygon1 = (Polygon)OperatorDensifyByLength.local().execute(polygon1, 0.5, null); + disjoint.accelerateGeometry(polygon1, sr, GeometryAccelerationDegree.enumHot); + res = disjoint.execute(polygon1, polygon2, sr, null); + assertTrue(!res); + res = disjoint.execute(polygon2, polygon1, sr, null); + assertTrue(!res); + + polygon1.reverseAllPaths(); + polygon2.reverseAllPaths(); + res = disjoint.execute(polygon1, polygon2, sr, null); + assertTrue(!res); + res = disjoint.execute(polygon2, polygon1, sr, null); + assertTrue(!res); + + // Polygon1 contains polygon2, but polygon2 is counterclockwise. + str1 = "{\"rings\":[[[0,0],[10,0],[10,10],[0,10],[0,0]],[[11,0],[11,10],[21,10],[21,0],[11,0]]]}"; + str2 = "{\"rings\":[[[2,2],[8,2],[8,8],[2,8],[2,2]]]}"; + polygon1 = (Polygon) (TestCommonMethods.fromJson(str1).getGeometry()); + polygon2 = (Polygon) (TestCommonMethods.fromJson(str2).getGeometry()); + + + res = disjoint.execute(polygon1, polygon2, sr, null); + assertTrue(!res); + res = disjoint.execute(polygon2, polygon1, sr, null); + assertTrue(!res); + + polygon1 = (Polygon)OperatorDensifyByLength.local().execute(polygon1, 0.5, null); + disjoint.accelerateGeometry(polygon1, sr, GeometryAccelerationDegree.enumHot); + res = disjoint.execute(polygon1, polygon2, sr, null); + assertTrue(!res); + res = disjoint.execute(polygon2, polygon1, sr, null); + assertTrue(!res); + + str1 = "{\"rings\":[[[0,0],[0,10],[10,10],[10,0],[0,0]],[[0,20],[0,30],[10,30],[10,20],[0,20]],[[20,20],[20,30],[30,30],[30,20],[20,20]],[[20,0],[20,10],[30,10],[30,0],[20,0]]]}"; + str2 = "{\"rings\":[[[14,14],[14,16],[16,16],[16,14],[14,14]]]}"; + polygon1 = (Polygon) (TestCommonMethods.fromJson(str1).getGeometry()); + polygon2 = (Polygon) (TestCommonMethods.fromJson(str2).getGeometry()); + + + res = disjoint.execute(polygon1, polygon2, sr, null); + assertTrue(res); + res = disjoint.execute(polygon2, polygon1, sr, null); + assertTrue(res); + + polygon1 = (Polygon)OperatorDensifyByLength.local().execute(polygon1, 0.5, null); + disjoint.accelerateGeometry(polygon1, sr, GeometryAccelerationDegree.enumHot); + res = disjoint.execute(polygon1, polygon2, sr, null); + assertTrue(res); + res = disjoint.execute(polygon2, polygon1, sr, null); + assertTrue(res); } @Test - public static void testPolylinePolylineDisjoint() { + public void testPolylinePolylineDisjoint() { OperatorDisjoint disjoint = (OperatorDisjoint) (OperatorFactoryLocal .getInstance().getOperator(Operator.Type.Disjoint)); SpatialReference sr = SpatialReference.create(102100); @@ -970,7 +1021,7 @@ public static void testPolylinePolylineDisjoint() { } @Test - public static void testPolygonPolylineDisjoint() { + public void testPolygonPolylineDisjoint() { OperatorDisjoint disjoint = (OperatorDisjoint) (OperatorFactoryLocal .getInstance().getOperator(Operator.Type.Disjoint)); SpatialReference sr = SpatialReference.create(102100); @@ -1031,7 +1082,7 @@ public static void testPolygonPolylineDisjoint() { } @Test - public static void testPolylineMultiPointDisjoint() { + public void testPolylineMultiPointDisjoint() { OperatorDisjoint disjoint = (OperatorDisjoint) (OperatorFactoryLocal .getInstance().getOperator(Operator.Type.Disjoint)); SpatialReference sr = SpatialReference.create(102100); @@ -1077,7 +1128,7 @@ public static void testPolylineMultiPointDisjoint() { } @Test - public static void testPolylinePointDisjoint() { + public void testPolylinePointDisjoint() { OperatorDisjoint disjoint = (OperatorDisjoint) (OperatorFactoryLocal .getInstance().getOperator(Operator.Type.Disjoint)); OperatorContains contains = (OperatorContains) (OperatorFactoryLocal @@ -1136,7 +1187,7 @@ public static void testPolylinePointDisjoint() { } @Test - public static void testMultiPointMultiPointDisjoint() { + public void testMultiPointMultiPointDisjoint() { OperatorDisjoint disjoint = (OperatorDisjoint) (OperatorFactoryLocal .getInstance().getOperator(Operator.Type.Disjoint)); SpatialReference sr = SpatialReference.create(102100); @@ -1178,7 +1229,7 @@ public static void testMultiPointMultiPointDisjoint() { } @Test - public static void testMultiPointPointDisjoint() { + public void testMultiPointPointDisjoint() { OperatorDisjoint disjoint = (OperatorDisjoint) (OperatorFactoryLocal .getInstance().getOperator(Operator.Type.Disjoint)); OperatorContains contains = (OperatorContains) (OperatorFactoryLocal @@ -1226,7 +1277,7 @@ public static void testMultiPointPointDisjoint() { } @Test - public static void testPolygonMultiPointDisjoint() { + public void testPolygonMultiPointDisjoint() { OperatorDisjoint disjoint = (OperatorDisjoint) (OperatorFactoryLocal .getInstance().getOperator(Operator.Type.Disjoint)); SpatialReference sr = SpatialReference.create(102100); @@ -1275,7 +1326,7 @@ public static void testPolygonMultiPointDisjoint() { } @Test - public static void testPolygonMultiPointTouches() { + public void testPolygonMultiPointTouches() { OperatorTouches touches = (OperatorTouches) (OperatorFactoryLocal .getInstance().getOperator(Operator.Type.Touches)); SpatialReference sr = SpatialReference.create(102100); @@ -1316,7 +1367,7 @@ public static void testPolygonMultiPointTouches() { } @Test - public static void testPolygonPointTouches() { + public void testPolygonPointTouches() { OperatorTouches touches = (OperatorTouches) (OperatorFactoryLocal .getInstance().getOperator(Operator.Type.Touches)); SpatialReference sr = SpatialReference.create(102100); @@ -1348,7 +1399,7 @@ public static void testPolygonPointTouches() { } @Test - public static void testPolygonPolygonTouches() { + public void testPolygonPolygonTouches() { OperatorTouches touches = (OperatorTouches) (OperatorFactoryLocal .getInstance().getOperator(Operator.Type.Touches)); SpatialReference sr = SpatialReference.create(102100); @@ -1430,7 +1481,7 @@ public static void testPolygonPolygonTouches() { } @Test - public static void testPolygonPolylineTouches() { + public void testPolygonPolylineTouches() { OperatorTouches touches = (OperatorTouches) (OperatorFactoryLocal .getInstance().getOperator(Operator.Type.Touches)); SpatialReference sr = SpatialReference.create(102100); @@ -1495,7 +1546,7 @@ public static void testPolygonPolylineTouches() { } @Test - public static void testPolylinePolylineTouches() { + public void testPolylinePolylineTouches() { OperatorTouches touches = (OperatorTouches) (OperatorFactoryLocal .getInstance().getOperator(Operator.Type.Touches)); SpatialReference sr = SpatialReference.create(102100); @@ -1640,7 +1691,7 @@ public static void testPolylinePolylineTouches() { } @Test - public static void testPolylineMultiPointTouches() { + public void testPolylineMultiPointTouches() { OperatorTouches touches = (OperatorTouches) (OperatorFactoryLocal .getInstance().getOperator(Operator.Type.Touches)); SpatialReference sr = SpatialReference.create(102100); @@ -1699,7 +1750,7 @@ public static void testPolylineMultiPointTouches() { } @Test - public static void testPolylineMultiPointCrosses() { + public void testPolylineMultiPointCrosses() { OperatorCrosses crosses = (OperatorCrosses) (OperatorFactoryLocal .getInstance().getOperator(Operator.Type.Crosses)); SpatialReference sr = SpatialReference.create(102100); @@ -1752,7 +1803,7 @@ public static void testPolylineMultiPointCrosses() { } @Test - public static void testPolylinePointTouches() { + public void testPolylinePointTouches() { OperatorTouches touches = (OperatorTouches) (OperatorFactoryLocal .getInstance().getOperator(Operator.Type.Touches)); SpatialReference sr = SpatialReference.create(102100); @@ -1778,7 +1829,7 @@ public static void testPolylinePointTouches() { } @Test - public static void testPolygonPolygonOverlaps() { + public void testPolygonPolygonOverlaps() { OperatorOverlaps overlaps = (OperatorOverlaps) (OperatorFactoryLocal .getInstance().getOperator(Operator.Type.Overlaps)); SpatialReference sr = SpatialReference.create(102100); @@ -1855,7 +1906,7 @@ public static void testPolygonPolygonOverlaps() { } @Test - public static void testPolygonPolylineWithin() { + public void testPolygonPolylineWithin() { OperatorWithin within = (OperatorWithin) (OperatorFactoryLocal .getInstance().getOperator(Operator.Type.Within)); SpatialReference sr = SpatialReference.create(102100); @@ -1888,7 +1939,7 @@ public static void testPolygonPolylineWithin() { } @Test - public static void testMultiPointMultiPointWithin() { + public void testMultiPointMultiPointWithin() { OperatorWithin within = (OperatorWithin) (OperatorFactoryLocal .getInstance().getOperator(Operator.Type.Within)); SpatialReference sr = SpatialReference.create(102100); @@ -1940,7 +1991,7 @@ public static void testMultiPointMultiPointWithin() { } @Test - public static void testPolylinePolylineOverlaps() { + public void testPolylinePolylineOverlaps() { OperatorOverlaps overlaps = (OperatorOverlaps) (OperatorFactoryLocal .getInstance().getOperator(Operator.Type.Overlaps)); SpatialReference sr = SpatialReference.create(102100); @@ -2007,7 +2058,7 @@ public static void testPolylinePolylineOverlaps() { } @Test - public static void testMultiPointMultiPointOverlaps() { + public void testMultiPointMultiPointOverlaps() { OperatorOverlaps overlaps = (OperatorOverlaps) (OperatorFactoryLocal .getInstance().getOperator(Operator.Type.Overlaps)); SpatialReference sr = SpatialReference.create(102100); @@ -2065,7 +2116,7 @@ public static void testMultiPointMultiPointOverlaps() { } @Test - public static void testPolygonPolygonWithin() { + public void testPolygonPolygonWithin() { OperatorWithin within = (OperatorWithin) (OperatorFactoryLocal .getInstance().getOperator(Operator.Type.Within)); SpatialReference sr = SpatialReference.create(102100); @@ -2120,10 +2171,140 @@ public static void testPolygonPolygonWithin() { res = within.execute(polygon2, polygon1, sr, null); assertTrue(!res); + str1 = "{\"rings\":[[[0,0],[10,0],[10,10],[0,10]]]}"; + str2 = "{\"rings\":[[[2,2],[2,8],[8,8],[8,2],[2,2],[8,2],[8,8],[2,8],[2,2]]]}"; + + polygon1 = (Polygon) (TestCommonMethods.fromJson(str1).getGeometry()); + polygon2 = (Polygon) (TestCommonMethods.fromJson(str2).getGeometry()); + + res = within.execute(polygon2, polygon1, sr, null); + assertTrue(res); + + str1 = "{\"rings\":[[[0,0],[0,10],[10,10],[10,0]],[[12,8],[12,10],[18,10],[18,8],[12,8]]]}"; + str2 = "{\"paths\":[[[2,2],[2,8],[8,8],[8,2]],[[12,2],[12,4],[18,4],[18,2]]]}"; + + polygon1 = (Polygon) (TestCommonMethods.fromJson(str1).getGeometry()); + Polyline polyline2 = (Polyline) (TestCommonMethods.fromJson(str2).getGeometry()); + + res = within.execute(polyline2, polygon1, sr, null); + assertTrue(!res); + + str1 = "{\"rings\":[[[0,0],[0,10],[10,10],[10,0],[0,0]],[[4,4],[6,4],[6,6],[4,6],[4,4]]]}"; + str2 = "{\"rings\":[[[2,2],[2,8],[8,8],[8,2],[2,2],[2,8],[8,8],[8,2],[2,2]]]}"; + + polygon1 = (Polygon) (TestCommonMethods.fromJson(str1).getGeometry()); + polygon2 = (Polygon) (TestCommonMethods.fromJson(str2).getGeometry()); + + res = within.execute(polygon2, polygon1, sr, null); + assertTrue(res); + + // Same as above, but winding fill rule + str1 = "{\"rings\":[[[0,0],[0,10],[10,10],[10,0],[0,0]],[[4,4],[6,4],[6,6],[4,6],[4,4]]]}"; + str2 = "{\"rings\":[[[2,2],[2,8],[8,8],[8,2],[2,2],[2,8],[8,8],[8,2],[2,2]]]}"; + polygon1 = (Polygon) (TestCommonMethods.fromJson(str1).getGeometry()); + polygon2 = (Polygon) (TestCommonMethods.fromJson(str2).getGeometry()); + polygon1.setFillRule(Polygon.FillRule.enumFillRuleWinding); + polygon2.setFillRule(Polygon.FillRule.enumFillRuleWinding); + + res = within.execute(polygon2, polygon1, sr, null); + assertTrue(!res); + + str1 = "{\"rings\":[[[0,0],[0,10],[10,10],[10,0],[0,0]]]}"; + str2 = "{\"paths\":[[[2,2],[2,2]]]}"; + polygon1 = (Polygon) (TestCommonMethods.fromJson(str1).getGeometry()); + polyline2 = (Polyline) (TestCommonMethods.fromJson(str2).getGeometry()); + res = within.execute(polyline2, polygon1, sr, null); + assertTrue(res); + + str1 = "{\"rings\":[[[0,0],[0,10],[10,10],[10,0],[0,0]],[[11,11],[11,20],[20,20],[20,11],[11,11]]]}"; + str2 = "{\"rings\":[[[2,2],[2,8],[8,8],[15,15],[8,8],[8,2],[2,2]]]}"; + polygon1 = (Polygon) (TestCommonMethods.fromJson(str1).getGeometry()); + polygon2 = (Polygon) (TestCommonMethods.fromJson(str2).getGeometry()); + + res = within.execute(polygon2, polygon1, sr, null); + assertTrue(!res); + + str1 = "{\"rings\":[[[0,0],[0,10],[10,10],[10,0],[0,0]],[[10,10],[10,20],[20,20],[20,10],[10,10]]]}"; + str2 = "{\"rings\":[[[2,2],[2,8],[8,8],[15,15],[8,8],[8,2],[2,2]]]}"; + polygon1 = (Polygon) (TestCommonMethods.fromJson(str1).getGeometry()); + polygon2 = (Polygon) (TestCommonMethods.fromJson(str2).getGeometry()); + + res = within.execute(polygon2, polygon1, sr, null); + assertTrue(res); + + str1 = "{\"rings\":[[[0,0],[0,10],[10,10],[10,0],[0,0]]]}"; + str2 = "{\"rings\":[[[9.9999999925,4],[9.9999999925,6],[10.0000000075,6],[10.0000000075,4],[9.9999999925,4]]]}"; + polygon1 = (Polygon) (TestCommonMethods.fromJson(str1).getGeometry()); + polygon2 = (Polygon) (TestCommonMethods.fromJson(str2).getGeometry()); + + res = within.execute(polygon2, polygon1, sr, null); + assertTrue(!res); + + res = OperatorOverlaps.local().execute(polygon1, polygon2, sr, null); + assertTrue(!res); + + res = OperatorTouches.local().execute(polygon1, polygon2, sr, null); + assertTrue(res); + + str1 = "{\"rings\":[[[0,0],[0,10],[10,10],[10,0],[0,0]],[[10,10],[10,20],[20,20],[20,10],[10,10]]]}"; + str2 = "{\"rings\":[[[2,2],[2,8],[8,8],[15,15],[8,8],[8,2],[2,2]],[[15,5],[15,5],[15,5]]]}"; + polygon1 = (Polygon) (TestCommonMethods.fromJson(str1).getGeometry()); + polygon2 = (Polygon) (TestCommonMethods.fromJson(str2).getGeometry()); + res = within.execute(polygon2, polygon1, sr, null); + assertTrue(!res); + + str1 = "{\"rings\":[[[0,0],[0,10],[10,10],[10,0],[0,0]]]}"; + str2 = "{\"rings\":[[[2,2],[2,2],[2,2]],[[3,3],[3,3],[3,3]]]}"; + polygon1 = (Polygon) (TestCommonMethods.fromJson(str1).getGeometry()); + polygon2 = (Polygon) (TestCommonMethods.fromJson(str2).getGeometry()); + res = within.execute(polygon2, polygon1, sr, null); + assertTrue(res); + + str1 = "{\"rings\":[[[0,0],[0,10],[10,10],[10,0],[0,0]]]}"; + str2 = "{\"rings\":[[[2,2],[2,2],[2,2],[2,2]],[[3,3],[3,3],[3,3],[3,3]]]}"; + polygon1 = (Polygon) (TestCommonMethods.fromJson(str1).getGeometry()); + polygon2 = (Polygon) (TestCommonMethods.fromJson(str2).getGeometry()); + res = within.execute(polygon2, polygon1, sr, null); + assertTrue(res); + + str1 = "{\"rings\":[[[0,0],[0,10],[10,10],[10,0],[0,0]]]}"; + str2 = "{\"paths\":[[[2,2],[2,2]],[[3,3],[3,3]]]}"; + polygon1 = (Polygon) (TestCommonMethods.fromJson(str1).getGeometry()); + polyline2 = (Polyline) (TestCommonMethods.fromJson(str2).getGeometry()); + res = within.execute(polyline2, polygon1, sr, null); + assertTrue(res); + + str1 = "{\"rings\":[[[0,0],[0,10],[10,10],[10,0],[0,0]],[[10,10],[10,20],[20,20],[20,10],[10,10]]]}"; + str2 = "{\"paths\":[[[2,2],[2,8]],[[15,5],[15,5]]]}"; + polygon1 = (Polygon) (TestCommonMethods.fromJson(str1).getGeometry()); + polyline2 = (Polyline) (TestCommonMethods.fromJson(str2).getGeometry()); + res = within.execute(polyline2, polygon1, sr, null); + assertTrue(!res); + + str1 = "{\"rings\":[[[0,0],[0,10],[10,10],[10,0],[0,0]],[[10,10],[10,20],[20,20],[20,10],[10,10]]]}"; + str2 = "{\"paths\":[[[2,2],[2,8]],[[15,5],[15,5],[15,5],[15,5]]]}"; + polygon1 = (Polygon) (TestCommonMethods.fromJson(str1).getGeometry()); + polyline2 = (Polyline) (TestCommonMethods.fromJson(str2).getGeometry()); + res = within.execute(polyline2, polygon1, sr, null); + assertTrue(!res); + + str1 = "{\"rings\":[[[0,0],[0,10],[10,10],[10,0],[0,0]],[[10,10],[10,20],[20,20],[20,10],[10,10]]]}"; + str2 = "{\"paths\":[[[2,2],[2,2]],[[15,5],[15,6]]]}"; + polygon1 = (Polygon) (TestCommonMethods.fromJson(str1).getGeometry()); + polyline2 = (Polyline) (TestCommonMethods.fromJson(str2).getGeometry()); + res = within.execute(polyline2, polygon1, sr, null); + assertTrue(!res); + + str1 = "{\"rings\":[[[0,0],[0,10],[10,10],[10,0],[0,0]],[[10,10],[10,20],[20,20],[20,10],[10,10]]]}"; + str2 = "{\"paths\":[[[2,2],[2,2],[2,2],[2,2]],[[15,5],[15,6]]]}"; + polygon1 = (Polygon) (TestCommonMethods.fromJson(str1).getGeometry()); + polyline2 = (Polyline) (TestCommonMethods.fromJson(str2).getGeometry()); + res = within.execute(polyline2, polygon1, sr, null); + assertTrue(!res); } @Test - public static void testPolylinePolylineWithin() { + public void testPolylinePolylineWithin() { OperatorWithin within = (OperatorWithin) (OperatorFactoryLocal .getInstance().getOperator(Operator.Type.Within)); OperatorContains contains = (OperatorContains) (OperatorFactoryLocal @@ -2191,7 +2372,7 @@ public static void testPolylinePolylineWithin() { } @Test - public static void testPolylineMultiPointWithin() { + public void testPolylineMultiPointWithin() { OperatorWithin within = (OperatorWithin) (OperatorFactoryLocal .getInstance().getOperator(Operator.Type.Within)); SpatialReference sr = SpatialReference.create(102100); @@ -2239,7 +2420,7 @@ public static void testPolylineMultiPointWithin() { } @Test - public static void testPolygonMultiPointWithin() { + public void testPolygonMultiPointWithin() { OperatorWithin within = (OperatorWithin) (OperatorFactoryLocal .getInstance().getOperator(Operator.Type.Within)); SpatialReference sr = SpatialReference.create(102100); @@ -2270,7 +2451,7 @@ public static void testPolygonMultiPointWithin() { } @Test - public static void testPolygonPolylineCrosses() { + public void testPolygonPolylineCrosses() { OperatorCrosses crosses = (OperatorCrosses) (OperatorFactoryLocal .getInstance().getOperator(Operator.Type.Crosses)); SpatialReference sr = SpatialReference.create(102100); @@ -2324,7 +2505,7 @@ public static void testPolygonPolylineCrosses() { } @Test - public static void testPolylinePolylineCrosses() { + public void testPolylinePolylineCrosses() { OperatorCrosses crosses = (OperatorCrosses) (OperatorFactoryLocal .getInstance().getOperator(Operator.Type.Crosses)); SpatialReference sr = SpatialReference.create(102100); @@ -2414,7 +2595,7 @@ public static void testPolylinePolylineCrosses() { } @Test - public static void testPolygonEnvelope() { + public void testPolygonEnvelope() { OperatorEquals equals = (OperatorEquals) (OperatorFactoryLocal .getInstance().getOperator(Operator.Type.Equals)); OperatorContains contains = (OperatorContains) (OperatorFactoryLocal @@ -2866,7 +3047,7 @@ public static void testPolygonEnvelope() { } @Test - public static void testPolylineEnvelope() { + public void testPolylineEnvelope() { OperatorEquals equals = (OperatorEquals) (OperatorFactoryLocal .getInstance().getOperator(Operator.Type.Equals)); OperatorContains contains = (OperatorContains) (OperatorFactoryLocal @@ -3254,7 +3435,7 @@ public static void testPolylineEnvelope() { } @Test - public static void testMultiPointEnvelope() { + public void testMultiPointEnvelope() { OperatorEquals equals = (OperatorEquals) (OperatorFactoryLocal .getInstance().getOperator(Operator.Type.Equals)); OperatorContains contains = (OperatorContains) (OperatorFactoryLocal @@ -3583,7 +3764,7 @@ public static void testMultiPointEnvelope() { } @Test - public static void testPointEnvelope() { + public void testPointEnvelope() { OperatorEquals equals = (OperatorEquals) (OperatorFactoryLocal .getInstance().getOperator(Operator.Type.Equals)); OperatorContains contains = (OperatorContains) (OperatorFactoryLocal @@ -3722,7 +3903,7 @@ public static void testPointEnvelope() { } @Test - public static void testEnvelopeEnvelope() { + public void testEnvelopeEnvelope() { OperatorEquals equals = (OperatorEquals) (OperatorFactoryLocal .getInstance().getOperator(Operator.Type.Equals)); OperatorContains contains = (OperatorContains) (OperatorFactoryLocal @@ -4350,7 +4531,7 @@ static void wiggleGeometry(Geometry geometry, double tolerance, int rand) { } @Test - public static void testDisjointRelationFalse() { + public void testDisjointRelationFalse() { { OperatorDisjoint op = (OperatorDisjoint) (OperatorFactoryLocal .getInstance().getOperator(Operator.Type.Disjoint)); @@ -4425,259 +4606,341 @@ public static void testDisjointRelationFalse() { } @Test - public static void testPolylinePolylineRelate() { - OperatorRelate op = (OperatorRelate) (OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.Relate)); - SpatialReference sr = SpatialReference.create(4326); - boolean res; - String scl; + public void testPolylinePolylineRelate() { + OperatorRelate op = OperatorRelate.local(); + SpatialReference sr = SpatialReference.create(4326); + boolean res; + String scl; - Polyline polyline1 = new Polyline(); - Polyline polyline2 = new Polyline(); + Polyline polyline1 = new Polyline(); + Polyline polyline2 = new Polyline(); - polyline1.startPath(0, 0); - polyline1.lineTo(1, 1); + polyline1.startPath(0, 0); + polyline1.lineTo(1, 1); - polyline2.startPath(1, 1); - polyline2.lineTo(2, 0); + polyline2.startPath(1, 1); + polyline2.lineTo(2, 0); - scl = "FF1FT01T2"; - res = op.execute(polyline1, polyline2, sr, scl, null); - assertTrue(res); + scl = "FF1FT01T2"; + res = op.execute(polyline1, polyline2, sr, scl, null); + assertTrue(res); - scl = "****TF*T*"; - res = op.execute(polyline1, polyline2, sr, scl, null); - assertTrue(!res); + scl = "****TF*T*"; + res = op.execute(polyline1, polyline2, sr, scl, null); + assertTrue(!res); - scl = "****F****"; - res = op.execute(polyline1, polyline2, sr, scl, null); - assertTrue(!res); + scl = "****F****"; + res = op.execute(polyline1, polyline2, sr, scl, null); + assertTrue(!res); - scl = "**1*0*T**"; - res = op.execute(polyline1, polyline2, sr, scl, null); - assertTrue(res); + scl = "**1*0*T**"; + res = op.execute(polyline1, polyline2, sr, scl, null); + assertTrue(res); - scl = "****1****"; - res = op.execute(polyline1, polyline2, sr, scl, null); - assertTrue(!res); + scl = "****1****"; + res = op.execute(polyline1, polyline2, sr, scl, null); + assertTrue(!res); - scl = "**T*001*T"; - res = op.execute(polyline1, polyline2, sr, scl, null); - assertTrue(res); + scl = "**T*001*T"; + res = op.execute(polyline1, polyline2, sr, scl, null); + assertTrue(res); - scl = "T********"; - res = op.execute(polyline1, polyline2, sr, scl, null); - assertTrue(!res); + scl = "T********"; + res = op.execute(polyline1, polyline2, sr, scl, null); + assertTrue(!res); - scl = "F********"; - res = op.execute(polyline1, polyline2, sr, scl, null); - assertTrue(res); + scl = "F********"; + res = op.execute(polyline1, polyline2, sr, scl, null); + assertTrue(res); - polyline1.setEmpty(); - polyline2.setEmpty(); + polyline1.setEmpty(); + polyline2.setEmpty(); - polyline1.startPath(0, 0); - polyline1.lineTo(1, 0); + polyline1.startPath(0, 0); + polyline1.lineTo(1, 0); - polyline2.startPath(0, 0); - polyline2.lineTo(1, 0); + polyline2.startPath(0, 0); + polyline2.lineTo(1, 0); - scl = "1FFFTFFFT"; - res = op.execute(polyline1, polyline2, sr, scl, null); - assertTrue(res); + scl = "1FFFTFFFT"; + res = op.execute(polyline1, polyline2, sr, scl, null); + assertTrue(res); - scl = "1*T*T****"; - res = op.execute(polyline1, polyline2, sr, scl, null); - assertTrue(!res); + scl = "1*T*T****"; + res = op.execute(polyline1, polyline2, sr, scl, null); + assertTrue(!res); - scl = "1T**T****"; - res = op.execute(polyline1, polyline2, sr, scl, null); - assertTrue(!res); + scl = "1T**T****"; + res = op.execute(polyline1, polyline2, sr, scl, null); + assertTrue(!res); - polyline1.setEmpty(); - polyline2.setEmpty(); + polyline1.setEmpty(); + polyline2.setEmpty(); - polyline1.startPath(0, 0); - polyline1.lineTo(0.5, 0.5); - polyline1.lineTo(1, 1); + polyline1.startPath(0, 0); + polyline1.lineTo(0.5, 0.5); + polyline1.lineTo(1, 1); - polyline2.startPath(1, 0); - polyline2.lineTo(0.5, 0.5); - polyline2.lineTo(0, 1); + polyline2.startPath(1, 0); + polyline2.lineTo(0.5, 0.5); + polyline2.lineTo(0, 1); - scl = "0F1FFTT0T"; - res = op.execute(polyline1, polyline2, sr, scl, null); - assertTrue(res); + scl = "0F1FFTT0T"; + res = op.execute(polyline1, polyline2, sr, scl, null); + assertTrue(res); - scl = "*T*******"; - res = op.execute(polyline1, polyline2, sr, scl, null); - assertTrue(!res); + scl = "*T*******"; + res = op.execute(polyline1, polyline2, sr, scl, null); + assertTrue(!res); - scl = "*F*F*****"; - res = op.execute(polyline1, polyline2, sr, scl, null); - assertTrue(res); + scl = "*F*F*****"; + res = op.execute(polyline1, polyline2, sr, scl, null); + assertTrue(res); - polyline1.setEmpty(); - polyline2.setEmpty(); + polyline1.setEmpty(); + polyline2.setEmpty(); - polyline1.startPath(0, 0); - polyline1.lineTo(1, 0); + polyline1.startPath(0, 0); + polyline1.lineTo(1, 0); - polyline2.startPath(1, -1); - polyline2.lineTo(1, 1); + polyline2.startPath(1, -1); + polyline2.lineTo(1, 1); - scl = "FT1TF01TT"; - res = op.execute(polyline1, polyline2, sr, scl, null); - assertTrue(!res); + scl = "FT1TF01TT"; + res = op.execute(polyline1, polyline2, sr, scl, null); + assertTrue(!res); - scl = "***T*****"; - res = op.execute(polyline1, polyline2, sr, scl, null); - assertTrue(res); + scl = "***T*****"; + res = op.execute(polyline1, polyline2, sr, scl, null); + assertTrue(res); - polyline1.setEmpty(); - polyline2.setEmpty(); + polyline1.setEmpty(); + polyline2.setEmpty(); - polyline1.startPath(0, 0); - polyline1.lineTo(0, 20); - polyline1.lineTo(20, 20); - polyline1.lineTo(20, 0); - polyline1.lineTo(0, 0); // has no boundary + polyline1.startPath(0, 0); + polyline1.lineTo(0, 20); + polyline1.lineTo(20, 20); + polyline1.lineTo(20, 0); + polyline1.lineTo(0, 0); // has no boundary - polyline2.startPath(3, 3); - polyline2.lineTo(5, 5); + polyline2.startPath(3, 3); + polyline2.lineTo(5, 5); + + op.accelerateGeometry(polyline1, sr, Geometry.GeometryAccelerationDegree.enumHot); - op.accelerateGeometry(polyline1, sr, - Geometry.GeometryAccelerationDegree.enumHot); + scl = "FF1FFF102"; + res = op.execute(polyline1, polyline2, sr, scl, null); + assertTrue(res); - scl = "FF1FFF102"; - res = op.execute(polyline1, polyline2, sr, scl, null); - assertTrue(res); + polyline1.setEmpty(); + polyline2.setEmpty(); - polyline1.setEmpty(); - polyline2.setEmpty(); + polyline1.startPath(4, 0); + polyline1.lineTo(0, 4); + polyline1.lineTo(4, 8); + polyline1.lineTo(8, 4); - polyline1.startPath(4, 0); - polyline1.lineTo(0, 4); - polyline1.lineTo(4, 8); - polyline1.lineTo(8, 4); + polyline2.startPath(8, 1); + polyline2.lineTo(8, 2); - polyline2.startPath(8, 1); - polyline2.lineTo(8, 2); + op.accelerateGeometry(polyline1, sr, GeometryAccelerationDegree.enumHot); - op.accelerateGeometry(polyline1, sr, - Geometry.GeometryAccelerationDegree.enumHot); + scl = "FF1FF0102"; + res = op.execute(polyline1, polyline2, sr, scl, null); + assertTrue(res); - scl = "FF1FF0102"; - res = op.execute(polyline1, polyline2, sr, scl, null); - assertTrue(res); + polyline1.setEmpty(); + polyline2.setEmpty(); + polyline1.startPath(4, 0); + polyline1.lineTo(0, 4); + polyline2.startPath(3, 2); + polyline2.lineTo(3, 2); + assertTrue(polyline2.getBoundary().isEmpty()); + + scl = "******0F*"; + res = op.execute(polyline1, polyline2, sr, scl, null); + assertTrue(res); + + polyline2.lineTo(3, 2); + assertTrue(polyline2.getBoundary().isEmpty()); + + scl = "******0F*"; + res = op.execute(polyline1, polyline2, sr, scl, null); + assertTrue(res); + scl = "******0F*"; + + polyline2.lineTo(3, 2); + assertTrue(polyline2.getBoundary().isEmpty()); + + res = op.execute(polyline1, polyline2, sr, scl, null); + assertTrue(res); + + polyline1.setEmpty(); + polyline2.setEmpty(); + polyline1.startPath(3, 3); + polyline1.lineTo(3, 4); + polyline1.lineTo(3, 3); + polyline2.startPath(1, 1); + polyline2.lineTo(1, 1); + + scl = "FF1FFF0F2"; + res = op.execute(polyline1, polyline2, sr, scl, null); + assertTrue(res); + scl = "FF0FFF1F2"; + res = op.execute(polyline2, polyline1, sr, scl, null); + assertTrue(res); + + polyline1.setEmpty(); + polyline2.setEmpty(); + polyline1.startPath(4, 0); + polyline1.lineTo(0, 4); + polyline2.startPath(2, 2); + polyline2.lineTo(2, 2); + + scl = "0F*******"; + res = op.execute(polyline1, polyline2, sr, scl, null); + assertTrue(res); + + polyline2.lineTo(2, 2); + + scl = "0F*******"; + res = op.execute(polyline1, polyline2, sr, scl, null); + assertTrue(res); + scl = "0F*******"; + res = op.execute(polyline1, polyline2, sr, scl, null); + assertTrue(res); } @Test - public static void testPolygonPolylineRelate() { - OperatorRelate op = (OperatorRelate) (OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.Relate)); - SpatialReference sr = SpatialReference.create(4326); - boolean res; - String scl; - - Polygon polygon1 = new Polygon(); - Polyline polyline2 = new Polyline(); - - polygon1.startPath(0, 0); - polygon1.lineTo(0, 10); - polygon1.lineTo(10, 10); - polygon1.lineTo(10, 0); - - polyline2.startPath(-10, 0); - polyline2.lineTo(0, 0); - - scl = "FF2F01102"; - res = op.execute(polygon1, polyline2, sr, scl, null); - assertTrue(res); - - scl = "**1*0110*"; - res = op.execute(polygon1, polyline2, sr, scl, null); - assertTrue(!res); - - scl = "T***T****"; - res = op.execute(polygon1, polyline2, sr, scl, null); - assertTrue(!res); - - scl = "FF*FT****"; - res = op.execute(polygon1, polyline2, sr, scl, null); - assertTrue(res); - - polyline2.setEmpty(); - polyline2.startPath(0, 0); - polyline2.lineTo(10, 0); - - scl = "***1*1FF*"; - res = op.execute(polygon1, polyline2, sr, scl, null); - assertTrue(res); - - scl = "F**1*1FF*"; - res = op.execute(polygon1, polyline2, sr, scl, null); - assertTrue(res); - - scl = "0**1*1FF*"; - res = op.execute(polygon1, polyline2, sr, scl, null); - assertTrue(!res); - - scl = "F**1*1TF*"; - res = op.execute(polygon1, polyline2, sr, scl, null); - assertTrue(!res); - - polyline2.setEmpty(); - polyline2.startPath(1, 1); - polyline2.lineTo(5, 5); - - scl = "TT*******"; - res = op.execute(polygon1, polyline2, sr, scl, null); - assertTrue(res); - - scl = "1T2FF1FF*"; - res = op.execute(polygon1, polyline2, sr, scl, null); - assertTrue(res); - - scl = "1T1FF1FF*"; - res = op.execute(polygon1, polyline2, sr, scl, null); - assertTrue(!res); - - polyline2.setEmpty(); - polyline2.startPath(5, 5); - polyline2.lineTo(15, 5); - - scl = "1T*0F*T0T"; - res = op.execute(polygon1, polyline2, sr, scl, null); - assertTrue(res); - - polygon1.setEmpty(); - polyline2.setEmpty(); - - polygon1.startPath(2, 0); - polygon1.lineTo(0, 2); - polygon1.lineTo(2, 4); - polygon1.lineTo(4, 2); - - polyline2.startPath(1, 2); - polyline2.lineTo(3, 2); - - op.accelerateGeometry(polygon1, sr, - Geometry.GeometryAccelerationDegree.enumHot); - scl = "TTTFF****"; - res = op.execute(polygon1, polyline2, sr, scl, null); - assertTrue(res); - - polyline2.setEmpty(); - polyline2.startPath(5, 2); - polyline2.lineTo(7, 2); - scl = "FF2FFT***"; - res = op.execute(polygon1, polyline2, sr, scl, null); - assertTrue(res); + public void testPolygonPolylineRelate() { + OperatorRelate op = OperatorRelate.local(); + SpatialReference sr = SpatialReference.create(4326); + boolean res; + String scl; + + Polygon polygon1 = new Polygon(); + Polyline polyline2 = new Polyline(); + + polygon1.startPath(0, 0); + polygon1.lineTo(0, 10); + polygon1.lineTo(10, 10); + polygon1.lineTo(10, 0); + + polyline2.startPath(-10, 0); + polyline2.lineTo(0, 0); + + scl = "FF2F01102"; + res = op.execute(polygon1, polyline2, sr, scl, null); + assertTrue(res); + + scl = "**1*0110*"; + res = op.execute(polygon1, polyline2, sr, scl, null); + assertTrue(!res); + + scl = "T***T****"; + res = op.execute(polygon1, polyline2, sr, scl, null); + assertTrue(!res); + + scl = "FF2FT****"; + res = op.execute(polygon1, polyline2, sr, scl, null); + assertTrue(res); + + polyline2.setEmpty(); + polyline2.startPath(0, 0); + polyline2.lineTo(10, 0); + + scl = "**21*1FF*"; + res = op.execute(polygon1, polyline2, sr, scl, null); + assertTrue(res); + + scl = "F*21*1FF*"; + res = op.execute(polygon1, polyline2, sr, scl, null); + assertTrue(res); + + scl = "0**1*1FF*"; + res = op.execute(polygon1, polyline2, sr, scl, null); + assertTrue(!res); + + scl = "F**1*1TF*"; + res = op.execute(polygon1, polyline2, sr, scl, null); + assertTrue(!res); + + polyline2.setEmpty(); + polyline2.startPath(1, 1); + polyline2.lineTo(5, 5); + + scl = "TT2******"; + res = op.execute(polygon1, polyline2, sr, scl, null); + assertTrue(res); + + scl = "1T2FF1FF*"; + res = op.execute(polygon1, polyline2, sr, scl, null); + assertTrue(res); + + scl = "1T1FF1FF*"; + res = op.execute(polygon1, polyline2, sr, scl, null); + assertTrue(!res); + + polyline2.setEmpty(); + polyline2.startPath(5, 5); + polyline2.lineTo(15, 5); + + scl = "1T20F*T0T"; + res = op.execute(polygon1, polyline2, sr, scl, null); + assertTrue(res); + + polygon1.setEmpty(); + polyline2.setEmpty(); + + polygon1.startPath(2, 0); + polygon1.lineTo(0, 2); + polygon1.lineTo(2, 4); + polygon1.lineTo(4, 2); + + polyline2.startPath(1, 2); + polyline2.lineTo(3, 2); + + op.accelerateGeometry(polygon1, sr, GeometryAccelerationDegree.enumHot); + scl = "TTTFF****"; + res = op.execute(polygon1, polyline2, sr, scl, null); + assertTrue(res); + + polyline2.setEmpty(); + polyline2.startPath(5, 2); + polyline2.lineTo(7, 2); + scl = "FF2FFT***"; + res = op.execute(polygon1, polyline2, sr, scl, null); + assertTrue(res); + + polygon1.setEmpty(); + polyline2.setEmpty(); + polygon1.startPath(0, 0); + polygon1.lineTo(0, 1); + polygon1.lineTo(1, 0); + polyline2.startPath(0, 10); + polyline2.lineTo(0, 9); + polyline2.startPath(10, 0); + polyline2.lineTo(9, 0); + polyline2.startPath(0, -10); + polyline2.lineTo(0, -9); + scl = "**2******"; + res = op.execute(polygon1, polyline2, sr, scl, null); + assertTrue(res); + + polygon1.setEmpty(); + polyline2.setEmpty(); + polygon1.startPath(0, 0); + polygon1.lineTo(0, 1); + polygon1.lineTo(0, 0); + polyline2.startPath(0, 10); + polyline2.lineTo(0, 9); + scl = "**1******"; + res = op.execute(polygon1, polyline2, sr, scl, null); + assertTrue(res); } @Test - public static void testPolygonPolygonRelate() { + public void testPolygonPolygonRelate() { OperatorRelate op = (OperatorRelate) (OperatorFactoryLocal .getInstance().getOperator(Operator.Type.Relate)); SpatialReference sr = SpatialReference.create(4326); @@ -4724,10 +4987,46 @@ public static void testPolygonPolygonRelate() { scl = "212FF1FFT"; res = op.execute(polygon1, polygon2, sr, scl, null); assertTrue(res); + + polygon1.setEmpty(); + polygon2.setEmpty(); + polygon1.startPath(3, 3); + polygon1.lineTo(3, 4); + polygon1.lineTo(3, 3); + polygon2.startPath(1, 1); + polygon2.lineTo(1, 1); + + scl = "FF1FFF0F2"; + res = op.execute(polygon1, polygon2, sr, scl, null); + assertTrue(res); + scl = "FF0FFF1F2"; + res = op.execute(polygon2, polygon1, sr, scl, null); + assertTrue(res); + + polygon1.setEmpty(); + polygon2.setEmpty(); + polygon1.startPath(0, 0); + polygon1.lineTo(0, 100); + polygon1.lineTo(100, 100); + polygon1.lineTo(100, 0); + polygon2.startPath(50, 50); + polygon2.lineTo(50, 50); + polygon2.lineTo(50, 50); + + op.accelerateGeometry(polygon1, sr, GeometryAccelerationDegree.enumHot); + + scl = "0F2FF1FF2"; + res = op.execute(polygon1, polygon2, sr, scl, null); + assertTrue(res); + + polygon2.lineTo(51, 50); + scl = "1F2FF1FF2"; + res = op.execute(polygon1, polygon2, sr, scl, null); + assertTrue(res); } @Test - public static void testMultiPointPointRelate() { + public void testMultiPointPointRelate() { OperatorRelate op = (OperatorRelate) (OperatorFactoryLocal .getInstance().getOperator(Operator.Type.Relate)); SpatialReference sr = SpatialReference.create(4326); @@ -4751,10 +5050,19 @@ public static void testMultiPointPointRelate() { m1.add(1, 1); res = op.execute(m1, p2, sr, scl, null); assertTrue(res); + + m1.setEmpty(); + + m1.add(1, 1); + m1.add(2, 2); + + scl = "FF0FFFTF2"; + res = op.execute(m1, p2, sr, scl, null); + assertTrue(res); } @Test - public static void testPointPointRelate() { + public void testPointPointRelate() { OperatorRelate op = (OperatorRelate) (OperatorFactoryLocal .getInstance().getOperator(Operator.Type.Relate)); SpatialReference sr = SpatialReference.create(4326); @@ -4775,10 +5083,22 @@ public static void testPointPointRelate() { p2.setXY(1, 0); res = op.execute(p1, p2, null, scl, null); assertTrue(!res); + + p1.setEmpty(); + p2.setEmpty(); + scl = "*********"; + res = op.execute(p1, p2, null, scl, null); + assertTrue(res); + scl = "FFFFFFFFF"; + res = op.execute(p1, p2, null, scl, null); + assertTrue(res); + scl = "FFFFFFFFT"; + res = op.execute(p1, p2, null, scl, null); + assertTrue(!res); } @Test - public static void testPolygonMultiPointRelate() { + public void testPolygonMultiPointRelate() { OperatorRelate op = (OperatorRelate) (OperatorFactoryLocal .getInstance().getOperator(Operator.Type.Relate)); SpatialReference sr = SpatialReference.create(4326); @@ -4858,7 +5178,7 @@ public static void testPolygonMultiPointRelate() { } @Test - public static void testPolygonPointRelate() { + public void testPolygonPointRelate() { OperatorRelate op = (OperatorRelate) (OperatorFactoryLocal .getInstance().getOperator(Operator.Type.Relate)); SpatialReference sr = SpatialReference.create(4326); @@ -4878,42 +5198,147 @@ public static void testPolygonPointRelate() { scl = "FF20FTFFT"; res = op.execute(polygon, point, sr, scl, null); assertTrue(res); + + polygon.setEmpty(); + polygon.startPath(0, 0); + polygon.lineTo(0, 0); + polygon.lineTo(0, 0); + scl = "0FFFFFFF2"; + res = op.execute(polygon, point, sr, scl, null); + assertTrue(res); + + polygon.setEmpty(); + polygon.startPath(0, 0); + polygon.lineTo(0, 1); + polygon.lineTo(0, 0); + scl = "0F1FFFFF2"; + res = op.execute(polygon, point, sr, scl, null); + assertTrue(res); + + point.setXY(-1, 0); + + scl = "FF1FFF0F2"; + res = op.execute(polygon, point, sr, scl, null); + assertTrue(res); + + polygon.setEmpty(); + polygon.startPath(0, 0); + polygon.lineTo(0, 10); + polygon.lineTo(0, 0); + scl = "FF1FFFTFT"; + res = op.execute(polygon, point, sr, scl, null); + assertTrue(res); + + polygon.setEmpty(); + polygon.startPath(0, 0); + polygon.lineTo(0, 0); + polygon.lineTo(0, 0); + scl = "FF0FFF0F2"; + res = op.execute(polygon, point, sr, scl, null); + assertTrue(res); } @Test - public static void testPolylineMultiPointRelate() { - OperatorRelate op = (OperatorRelate) (OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.Relate)); - SpatialReference sr = SpatialReference.create(4326); - boolean res; - String scl; + public void testPolylineMultiPointRelate() { + OperatorRelate op = OperatorRelate.local(); + SpatialReference sr = SpatialReference.create(4326); + boolean res; + String scl; - Polyline polyline1 = new Polyline(); - MultiPoint multipoint2 = new MultiPoint(); + Polyline polyline1 = new Polyline(); + MultiPoint multipoint2 = new MultiPoint(); - polyline1.startPath(0, 0); - polyline1.lineTo(10, 0); + polyline1.startPath(0, 0); + polyline1.lineTo(10, 0); - multipoint2.add(0, 0); - multipoint2.add(5, 5); + multipoint2.add(0, 0); + multipoint2.add(5, 5); - scl = "FF10F00F2"; - res = op.execute(polyline1, multipoint2, sr, scl, null); - assertTrue(res); + scl = "FF10F00F2"; + res = op.execute(polyline1, multipoint2, sr, scl, null); + assertTrue(res); - multipoint2.add(5, 0); + multipoint2.add(5, 0); - scl = "0F10F00F2"; - res = op.execute(polyline1, multipoint2, sr, scl, null); - assertTrue(res); + scl = "0F10F00F2"; + res = op.execute(polyline1, multipoint2, sr, scl, null); + assertTrue(res); - scl = "0F11F00F2"; - res = op.execute(polyline1, multipoint2, sr, scl, null); - assertTrue(!res); + scl = "0F11F00F2"; + res = op.execute(polyline1, multipoint2, sr, scl, null); + assertTrue(!res); + + polyline1.setEmpty(); + multipoint2.setEmpty(); + + polyline1.startPath(4, 0); + polyline1.lineTo(0, 4); + polyline1.lineTo(4, 8); + polyline1.lineTo(8, 4); + polyline1.lineTo(4, 0); // has no boundary + + multipoint2.add(8, 1); + multipoint2.add(8, 2); + + op.accelerateGeometry(polyline1, sr, GeometryAccelerationDegree.enumHot); + + scl = "FF1FFF0F2"; + res = op.execute(polyline1, multipoint2, sr, scl, null); + assertTrue(res); + + polyline1.setEmpty(); + multipoint2.setEmpty(); + + polyline1.startPath(4, 0); + polyline1.lineTo(4, 0); + + multipoint2.add(8, 1); + multipoint2.add(8, 2); + + scl = "FF0FFF0F2"; + res = op.execute(polyline1, multipoint2, sr, scl, null); + assertTrue(res); + + multipoint2.add(-2, 0); + res = op.execute(polyline1, multipoint2, sr, scl, null); + assertTrue(res); + + op.accelerateGeometry(polyline1, sr, GeometryAccelerationDegree.enumHot); + res = op.execute(polyline1, multipoint2, sr, scl, null); + assertTrue(res); + + polyline1.setEmpty(); + multipoint2.setEmpty(); + + polyline1.startPath(10, 10); + polyline1.lineTo(10, 10); + multipoint2.add(10, 10); + + scl = "0FFFFFFF2"; + res = op.execute(polyline1, multipoint2, sr, scl, null); + assertTrue(res); + + polyline1.startPath(12, 12); + polyline1.lineTo(12, 12); + + scl = "0F0FFFFF2"; + res = op.execute(polyline1, multipoint2, sr, scl, null); + assertTrue(res); + + polyline1.setEmpty(); + multipoint2.setEmpty(); + + polyline1.startPath(10, 10); + polyline1.lineTo(10, 10); + multipoint2.add(0, 0); + + scl = "FF0FFF0F2"; + res = op.execute(polyline1, multipoint2, sr, scl, null); + assertTrue(res); } @Test - public static void testMultiPointMultipointRelate() { + public void testMultiPointMultipointRelate() { OperatorRelate op = (OperatorRelate) (OperatorFactoryLocal .getInstance().getOperator(Operator.Type.Relate)); SpatialReference sr = SpatialReference.create(4326); @@ -4945,10 +5370,101 @@ public static void testMultiPointMultipointRelate() { res = GeometryEngine.relate(multipoint1, multipoint2, sr, scl); assertTrue(res); + + multipoint1.setEmpty(); + multipoint2.setEmpty(); + + multipoint1.add(0, 0); + multipoint2.add(1, 1); + + scl = "FFTFFF0FT"; + res = op.execute(multipoint1, multipoint2, sr, scl, null); + assertTrue(res); } + @Test + public void testPolylinePointRelate() + { + OperatorRelate op = OperatorRelate.local(); + SpatialReference sr = SpatialReference.create(4326); + boolean res; + String scl; + + Polyline polyline = new Polyline(); + Point point = new Point(); + + polyline.startPath(0, 2); + polyline.lineTo(0, 4); + + point.setXY(0, 3); + + scl = "0F1FF0FF2"; + res = op.execute(polyline, point, sr, scl, null); + assertTrue(res); + + point.setXY(1, 3); + + scl = "FF1FF00F2"; + res = op.execute(polyline, point, sr, scl, null); + assertTrue(res); + + polyline.lineTo(4, 4); + polyline.lineTo(4, 2); + polyline.lineTo(0, 2); // no bounadry + point.setXY(0, 3); + + scl = "0F1FFFFF2"; + res = op.execute(polyline, point, sr, scl, null); + assertTrue(res); + + scl = "0F1FFFFF2"; + res = op.execute(polyline, point, sr, scl, null); + assertTrue(res); + + point.setXY(1, 3); + + scl = "FF1FFF0F2"; + res = op.execute(polyline, point, sr, scl, null); + assertTrue(res); + + point.setXY(10, 10); + + scl = "FF1FFF0F2"; + res = op.execute(polyline, point, sr, scl, null); + assertTrue(res); + + polyline.setEmpty(); + point.setEmpty(); + + polyline.startPath(10, 10); + polyline.lineTo(10, 10); + point.setXY(10, 10); + + scl = "0FFFFFFF2"; + res = op.execute(polyline, point, sr, scl, null); + assertTrue(res); + + polyline.startPath(12, 12); + polyline.lineTo(12, 12); + + scl = "0F0FFFFF2"; + res = op.execute(polyline, point, sr, scl, null); + assertTrue(res); + + polyline.setEmpty(); + point.setEmpty(); + + polyline.startPath(10, 10); + polyline.lineTo(10, 10); + point.setXY(0, 0); + + scl = "FF0FFF0F2"; + res = op.execute(polyline, point, sr, scl, null); + assertTrue(res); + } + @Test - public static void testCrosses_github_issue_40() { + public void testCrosses_github_issue_40() { // Issue 40: Acceleration without a spatial reference changes the result // of relation operators Geometry geom1 = OperatorImportFromWkt.local().execute(0, diff --git a/src/test/java/com/esri/core/geometry/TestSimplify.java b/src/test/java/com/esri/core/geometry/TestSimplify.java index b7caf8a3..bdc2be0f 100644 --- a/src/test/java/com/esri/core/geometry/TestSimplify.java +++ b/src/test/java/com/esri/core/geometry/TestSimplify.java @@ -10,6 +10,7 @@ import junit.framework.TestCase; import org.codehaus.jackson.JsonFactory; +import org.codehaus.jackson.JsonParseException; import org.junit.Test; public class TestSimplify extends TestCase { @@ -1325,5 +1326,21 @@ public void testPolylineIsSimpleForOGC() throws IOException { } } + + @Test + public void testFillRule() throws JsonParseException, IOException { + //self intersecting star shape + MapGeometry mg = OperatorImportFromJson.local().execute(Geometry.Type.Unknown, "{\"rings\":[[[0,0], [5,10], [10, 0], [0, 7], [10, 7], [0, 0]]]}"); + Polygon poly = (Polygon)mg.getGeometry(); + assertTrue(poly.getFillRule() == Polygon.FillRule.enumFillRuleOddEven); + poly.setFillRule(Polygon.FillRule.enumFillRuleWinding); + assertTrue(poly.getFillRule() == Polygon.FillRule.enumFillRuleWinding); + Geometry simpleResult = OperatorSimplify.local().execute(poly, null, true, null); + assertTrue(((Polygon)simpleResult).getFillRule() == Polygon.FillRule.enumFillRuleOddEven); + //solid start without holes: + MapGeometry mg1 = OperatorImportFromJson.local().execute(Geometry.Type.Unknown, "{\"rings\":[[[0,0],[2.5925925925925926,5.185185185185185],[0,7],[3.5,7],[5,10],[6.5,7],[10,7],[7.407407407407407,5.185185185185185],[10,0],[5,3.5],[0,0]]]}"); + boolean equals = OperatorEquals.local().execute(mg1.getGeometry(), simpleResult, null, null); + assertTrue(equals); + } } From fc13f7f68c7fd814c1c9dae8a1c7239700765d6d Mon Sep 17 00:00:00 2001 From: serg4066 Date: Tue, 16 Dec 2014 11:09:40 -0800 Subject: [PATCH 097/196] minor fixes --- .../com/esri/core/geometry/MathUtils.java | 2 +- .../geometry/MultiVertexGeometryImpl.java | 3 +- .../geometry/OperatorImportFromESRIShape.java | 12 +- .../core/geometry/RelationalOperations.java | 230 ++++++++++-------- .../com/esri/core/geometry/TestPolygon.java | 21 ++ 5 files changed, 159 insertions(+), 109 deletions(-) diff --git a/src/main/java/com/esri/core/geometry/MathUtils.java b/src/main/java/com/esri/core/geometry/MathUtils.java index 8ed61457..a338dccd 100644 --- a/src/main/java/com/esri/core/geometry/MathUtils.java +++ b/src/main/java/com/esri/core/geometry/MathUtils.java @@ -173,7 +173,7 @@ static double lerp(double start_, double end_, double t) { else v = end_ - (end_ - start_) * (1.0 - t); - assert (t < 0 || t > 1.0 || (v >= start_ && v <= end_) || (v <= start_ && v >= end_)); + assert (t < 0 || t > 1.0 || (v >= start_ && v <= end_) || (v <= start_ && v >= end_) || NumberUtils.isNaN(start_) || NumberUtils.isNaN(end_)); return v; } diff --git a/src/main/java/com/esri/core/geometry/MultiVertexGeometryImpl.java b/src/main/java/com/esri/core/geometry/MultiVertexGeometryImpl.java index 6fb511ea..9fbe1756 100644 --- a/src/main/java/com/esri/core/geometry/MultiVertexGeometryImpl.java +++ b/src/main/java/com/esri/core/geometry/MultiVertexGeometryImpl.java @@ -1078,8 +1078,7 @@ public void replaceNaNs(int semantics, double value) { boolean modified = false; int ncomps = VertexDescription.getComponentCount(semantics); for (int i = 0; i < ncomps; i++) { - int attr = m_description.getAttributeIndex(semantics); - AttributeStreamBase streamBase = getAttributeStreamRef(attr); + AttributeStreamBase streamBase = getAttributeStreamRef(semantics); if (streamBase instanceof AttributeStreamOfDbl) { AttributeStreamOfDbl dblStream = (AttributeStreamOfDbl)streamBase; for (int ivert = 0, n = m_pointCount * ncomps; ivert < n; ivert++) { diff --git a/src/main/java/com/esri/core/geometry/OperatorImportFromESRIShape.java b/src/main/java/com/esri/core/geometry/OperatorImportFromESRIShape.java index 32c7f778..be7bf6cc 100644 --- a/src/main/java/com/esri/core/geometry/OperatorImportFromESRIShape.java +++ b/src/main/java/com/esri/core/geometry/OperatorImportFromESRIShape.java @@ -39,7 +39,11 @@ public Type getType() { /** * Performs the ImportFromESRIShape operation on a stream of shape buffers - * + * @param importFlags Use the {@link ShapeImportFlags} interface. The default is 0, which means geometry comes from a trusted source and is topologically simple. + * If the geometry comes from non-trusted source (that is it can be non-simple), pass ShapeImportNonTrusted. + * @param type The geometry type that you want to import. Use the {@link Geometry.Type} enum. It can be Geometry.Type.Unknown if the type of geometry has to be + * figured out from the shape buffer. + * @param shapeBuffers The cursor over shape buffers that hold the Geometries in ESRIShape format. * @return Returns a GeometryCursor. */ abstract GeometryCursor execute(int importFlags, Geometry.Type type, @@ -47,8 +51,10 @@ abstract GeometryCursor execute(int importFlags, Geometry.Type type, /** * Performs the ImportFromESRIShape operation. - * @param importFlags Use the {@link ShapeImportFlags} interface. - * @param type Use the {@link Geometry.Type} enum. + * @param importFlags Use the {@link ShapeImportFlags} interface. The default is 0, which means geometry comes from a trusted source and is topologically simple. + * If the geometry comes from non-trusted source (that is it can be non-simple), pass ShapeImportNonTrusted. + * @param type The geometry type that you want to import. Use the {@link Geometry.Type} enum. It can be Geometry.Type.Unknown if the type of geometry has to be + * figured out from the shape buffer. * @param shapeBuffer The buffer holding the Geometry in ESRIShape format. * @return Returns the imported Geometry. */ diff --git a/src/main/java/com/esri/core/geometry/RelationalOperations.java b/src/main/java/com/esri/core/geometry/RelationalOperations.java index 0658a8e5..34c84cf2 100644 --- a/src/main/java/com/esri/core/geometry/RelationalOperations.java +++ b/src/main/java/com/esri/core/geometry/RelationalOperations.java @@ -1149,33 +1149,36 @@ private static boolean polygonTouchesMultiPoint_(Polygon polygon_a, MultiPathImpl polygon_a_impl = (MultiPathImpl)polygon_a._getImpl(); Polygon pa = null; - Polygon p_polygon_a = null; + Polygon p_polygon_a = polygon_a; - if (PointInPolygonHelper.quadTreeWillHelp(polygon_a, multipoint_b.getPointCount()) && (polygon_a_impl._getAccelerators() == null || polygon_a_impl._getAccelerators().getQuadTree() == null)) - { - pa = new Polygon(); - polygon_a.copyTo(pa); - ((MultiPathImpl)pa._getImpl())._buildQuadTreeAccelerator(Geometry.GeometryAccelerationDegree.enumMedium); - p_polygon_a = pa; - } - else - { - p_polygon_a = polygon_a; - } + boolean b_checked_polygon_a_quad_tree = false; for (int i = 0; i < multipoint_b.getPointCount(); i++) { ptB = multipoint_b.getXY(i); - if (!env_a_inflated.contains(ptB)) - continue; + if (env_a_inflated.contains(ptB)) { - PolygonUtils.PiPResult result = PolygonUtils.isPointInPolygon2D(p_polygon_a, ptB, tolerance); + PolygonUtils.PiPResult result = PolygonUtils.isPointInPolygon2D(p_polygon_a, ptB, tolerance); - if (result == PolygonUtils.PiPResult.PiPBoundary) - b_boundary = true; - else if (result == PolygonUtils.PiPResult.PiPInside) - return false; + if (result == PolygonUtils.PiPResult.PiPBoundary) + b_boundary = true; + else if (result == PolygonUtils.PiPResult.PiPInside) + return false; + } + + if (!b_checked_polygon_a_quad_tree) { + if (PointInPolygonHelper.quadTreeWillHelp(polygon_a, multipoint_b.getPointCount() - 1) && (polygon_a_impl._getAccelerators() == null || polygon_a_impl._getAccelerators().getQuadTree() == null)) { + pa = new Polygon(); + polygon_a.copyTo(pa); + ((MultiPathImpl) pa._getImpl())._buildQuadTreeAccelerator(Geometry.GeometryAccelerationDegree.enumMedium); + p_polygon_a = pa; + } else { + p_polygon_a = polygon_a; + } + + b_checked_polygon_a_quad_tree = true; + } } if (b_boundary) @@ -1209,19 +1212,9 @@ private static boolean polygonCrossesMultiPoint_(Polygon polygon_a, MultiPathImpl polygon_a_impl = (MultiPathImpl)polygon_a._getImpl(); Polygon pa = null; - Polygon p_polygon_a = null; + Polygon p_polygon_a = polygon_a; - if (PointInPolygonHelper.quadTreeWillHelp(polygon_a, multipoint_b.getPointCount()) && (polygon_a_impl._getAccelerators() == null || polygon_a_impl._getAccelerators().getQuadTree() == null)) - { - pa = new Polygon(); - polygon_a.copyTo(pa); - ((MultiPathImpl)pa._getImpl())._buildQuadTreeAccelerator(Geometry.GeometryAccelerationDegree.enumMedium); - p_polygon_a = pa; - } - else - { - p_polygon_a = polygon_a; - } + boolean b_checked_polygon_a_quad_tree = false; for (int i = 0; i < multipoint_b.getPointCount(); i++) { @@ -1243,6 +1236,19 @@ else if (result == PolygonUtils.PiPResult.PiPInside) if (b_interior && b_exterior) return true; + + if (!b_checked_polygon_a_quad_tree) { + if (PointInPolygonHelper.quadTreeWillHelp(polygon_a, multipoint_b.getPointCount() - 1) && (polygon_a_impl._getAccelerators() == null || polygon_a_impl._getAccelerators().getQuadTree() == null)) { + pa = new Polygon(); + polygon_a.copyTo(pa); + ((MultiPathImpl) pa._getImpl())._buildQuadTreeAccelerator(Geometry.GeometryAccelerationDegree.enumMedium); + p_polygon_a = pa; + } else { + p_polygon_a = polygon_a; + } + + b_checked_polygon_a_quad_tree = true; + } } return false; @@ -1277,19 +1283,9 @@ private static boolean polygonContainsMultiPoint_(Polygon polygon_a, MultiPathImpl polygon_a_impl = (MultiPathImpl)polygon_a._getImpl(); Polygon pa = null; - Polygon p_polygon_a = null; + Polygon p_polygon_a = polygon_a; - if (PointInPolygonHelper.quadTreeWillHelp(polygon_a, multipoint_b.getPointCount()) && (polygon_a_impl._getAccelerators() == null || polygon_a_impl._getAccelerators().getQuadTree() == null)) - { - pa = new Polygon(); - polygon_a.copyTo(pa); - ((MultiPathImpl)pa._getImpl())._buildQuadTreeAccelerator(Geometry.GeometryAccelerationDegree.enumMedium); - p_polygon_a = pa; - } - else - { - p_polygon_a = polygon_a; - } + boolean b_checked_polygon_a_quad_tree = false; for (int i = 0; i < multipoint_b.getPointCount(); i++) { @@ -1304,6 +1300,19 @@ private static boolean polygonContainsMultiPoint_(Polygon polygon_a, b_interior = true; else if (result == PolygonUtils.PiPResult.PiPOutside) return false; + + if (!b_checked_polygon_a_quad_tree) { + if (PointInPolygonHelper.quadTreeWillHelp(polygon_a, multipoint_b.getPointCount() - 1) && (polygon_a_impl._getAccelerators() == null || polygon_a_impl._getAccelerators().getQuadTree() == null)) { + pa = new Polygon(); + polygon_a.copyTo(pa); + ((MultiPathImpl) pa._getImpl())._buildQuadTreeAccelerator(Geometry.GeometryAccelerationDegree.enumMedium); + p_polygon_a = pa; + } else { + p_polygon_a = polygon_a; + } + + b_checked_polygon_a_quad_tree = true; + } } return b_interior; @@ -3180,38 +3189,16 @@ private static boolean polygonDisjointMultiPath_(Polygon polygon_a, return false; Polygon pa = null; - Polygon p_polygon_a = null; - - if (PointInPolygonHelper.quadTreeWillHelp(polygon_a, multipath_b.getPointCount()) && (multi_path_impl_a._getAccelerators() == null || multi_path_impl_a._getAccelerators().getQuadTree() == null)) - { - pa = new Polygon(); - polygon_a.copyTo(pa); - ((MultiPathImpl)pa._getImpl())._buildQuadTreeAccelerator(Geometry.GeometryAccelerationDegree.enumMedium); - p_polygon_a = pa; - } - else - { - p_polygon_a = polygon_a; - } + Polygon p_polygon_a = polygon_a; Polygon pb = null; Polygon p_polygon_b = null; if (multipath_b.getType().value() == Geometry.GeometryType.Polygon) - { - Polygon polygon_b = (Polygon)multipath_b; - if (PointInPolygonHelper.quadTreeWillHelp(polygon_b, polygon_a.getPointCount()) && (multi_path_impl_b._getAccelerators() == null || multi_path_impl_b._getAccelerators().getQuadTree() == null)) - { - pb = new Polygon(); - polygon_b.copyTo(pb); - ((MultiPathImpl)pb._getImpl())._buildQuadTreeAccelerator(Geometry.GeometryAccelerationDegree.enumMedium); - p_polygon_b = pb; - } - else - { - p_polygon_b = (Polygon)multipath_b; - } - } + p_polygon_b = (Polygon)multipath_b; + + boolean b_checked_polygon_a_quad_tree = false; + boolean b_checked_polygon_b_quad_tree = false; do { @@ -3244,6 +3231,37 @@ private static boolean polygonDisjointMultiPath_(Polygon polygon_a, return false; } } + + if (!b_checked_polygon_a_quad_tree) { + if (PointInPolygonHelper.quadTreeWillHelp(polygon_a, multipath_b.getPathCount() - 1) && (multi_path_impl_a._getAccelerators() == null || multi_path_impl_a._getAccelerators().getQuadTree() == null)) { + pa = new Polygon(); + polygon_a.copyTo(pa); + ((MultiPathImpl) pa._getImpl())._buildQuadTreeAccelerator(Geometry.GeometryAccelerationDegree.enumMedium); + p_polygon_a = pa; + } else { + p_polygon_a = polygon_a; + } + + b_checked_polygon_a_quad_tree = true; + } + + if (multipath_b.getType().value() == Geometry.GeometryType.Polygon) + { + if (!b_checked_polygon_b_quad_tree) { + Polygon polygon_b = (Polygon) multipath_b; + if (PointInPolygonHelper.quadTreeWillHelp(polygon_b, polygon_a.getPathCount() - 1) && (multi_path_impl_b._getAccelerators() == null || multi_path_impl_b._getAccelerators().getQuadTree() == null)) { + pb = new Polygon(); + polygon_b.copyTo(pb); + ((MultiPathImpl) pb._getImpl())._buildQuadTreeAccelerator(Geometry.GeometryAccelerationDegree.enumMedium); + p_polygon_b = pb; + } else { + p_polygon_b = (Polygon) multipath_b; + } + + b_checked_polygon_b_quad_tree = true; + } + } + } while (intersector.next()); return true; @@ -4750,7 +4768,6 @@ private static boolean polygonContainsMultiPath_(Polygon polygon_a, MultiPath mu while (intersector.next()) { - b_boundaries_intersect = true; int vertex_a = intersector.getRedElement(); int vertex_b = intersector.getBlueElement(); @@ -4761,15 +4778,16 @@ private static boolean polygonContainsMultiPath_(Polygon polygon_a, MultiPath mu int result = segmentB.intersect(segmentA, null, scalarsB, scalarsA, tolerance); - if (result == 1) - { - double scalar_a_0 = scalarsA[0]; - double scalar_b_0 = scalarsB[0]; + if (result != 0) { + b_boundaries_intersect = true; + if (result == 1) { + double scalar_a_0 = scalarsA[0]; + double scalar_b_0 = scalarsB[0]; - if (scalar_a_0 > 0.0 && scalar_a_0 < 1.0 && scalar_b_0 > 0.0 && scalar_b_0 < 1.0) - { - b_result_known[0] = true; - return false; + if (scalar_a_0 > 0.0 && scalar_a_0 < 1.0 && scalar_b_0 > 0.0 && scalar_b_0 < 1.0) { + b_result_known[0] = true; + return false; + } } } } @@ -4785,19 +4803,9 @@ private static boolean polygonContainsMultiPath_(Polygon polygon_a, MultiPath mu env_a_inflated.inflate(tolerance, tolerance); Polygon pa = null; - Polygon p_polygon_a = null; + Polygon p_polygon_a = polygon_a; - if (PointInPolygonHelper.quadTreeWillHelp(polygon_a, multi_path_b.getPointCount()) && (polygon_impl_a._getAccelerators() == null || polygon_impl_a._getAccelerators().getQuadTree() == null)) - { - pa = new Polygon(); - polygon_a.copyTo(pa); - ((MultiPathImpl)pa._getImpl())._buildQuadTreeAccelerator(Geometry.GeometryAccelerationDegree.enumMedium); - p_polygon_a = pa; - } - else - { - p_polygon_a = polygon_a; - } + boolean b_checked_polygon_a_quad_tree = false; Envelope2D path_env_b = new Envelope2D(); @@ -4818,6 +4826,19 @@ private static boolean polygonContainsMultiPath_(Polygon polygon_a, MultiPath mu { return false; } + + if (!b_checked_polygon_a_quad_tree) { + if (PointInPolygonHelper.quadTreeWillHelp(polygon_a, multi_path_b.getPathCount() - 1) && (polygon_impl_a._getAccelerators() == null || polygon_impl_a._getAccelerators().getQuadTree() == null)) { + pa = new Polygon(); + polygon_a.copyTo(pa); + ((MultiPathImpl) pa._getImpl())._buildQuadTreeAccelerator(Geometry.GeometryAccelerationDegree.enumMedium); + p_polygon_a = pa; + } else { + p_polygon_a = polygon_a; + } + + b_checked_polygon_a_quad_tree = true; + } } } @@ -4833,19 +4854,9 @@ private static boolean polygonContainsMultiPath_(Polygon polygon_a, MultiPath mu env_b_inflated.inflate(tolerance, tolerance); Polygon pb = null; - Polygon p_polygon_b = null; + Polygon p_polygon_b = polygon_b; - if (PointInPolygonHelper.quadTreeWillHelp(polygon_b, polygon_a.getPointCount()) && (multi_path_impl_b._getAccelerators() == null || multi_path_impl_b._getAccelerators().getQuadTree() == null)) - { - pb = new Polygon(); - polygon_b.copyTo(pb); - ((MultiPathImpl)pb._getImpl())._buildQuadTreeAccelerator(Geometry.GeometryAccelerationDegree.enumMedium); - p_polygon_b = pb; - } - else - { - p_polygon_b = polygon_b; - } + boolean b_checked_polygon_b_quad_tree = false; Envelope2D path_env_a = new Envelope2D(); @@ -4862,6 +4873,19 @@ private static boolean polygonContainsMultiPath_(Polygon polygon_a, MultiPath mu if (res == 1) return false; } + + if (!b_checked_polygon_b_quad_tree) { + if (PointInPolygonHelper.quadTreeWillHelp(polygon_b, polygon_a.getPathCount() - 1) && (multi_path_impl_b._getAccelerators() == null || multi_path_impl_b._getAccelerators().getQuadTree() == null)) { + pb = new Polygon(); + polygon_b.copyTo(pb); + ((MultiPathImpl) pb._getImpl())._buildQuadTreeAccelerator(Geometry.GeometryAccelerationDegree.enumMedium); + p_polygon_b = pb; + } else { + p_polygon_b = polygon_b; + } + + b_checked_polygon_b_quad_tree = true; + } } } diff --git a/src/test/java/com/esri/core/geometry/TestPolygon.java b/src/test/java/com/esri/core/geometry/TestPolygon.java index 2d6906ab..f83d2569 100644 --- a/src/test/java/com/esri/core/geometry/TestPolygon.java +++ b/src/test/java/com/esri/core/geometry/TestPolygon.java @@ -1178,6 +1178,27 @@ public void testReplaceNaNs() { assertTrue(mp.getPoint(0).equals(new Point(1, 2, 5))); assertTrue(mp.getPoint(1).equals(new Point(11, 12, 3))); } + + { + Polygon mp = new Polygon(); + Point pt = new Point(); + pt.setXY(1, 2); + pt.setM(Double.NaN); + mp.startPath(pt); + pt = new Point(); + pt.setXY(11, 12); + pt.setM(3); + mp.lineTo(pt); + + mp.replaceNaNs(VertexDescription.Semantics.M, 5); + Point p = new Point(1, 2); p.setM(5); + boolean b = mp.getPoint(0).equals(p); + assertTrue(b); + p = new Point(11, 12); p.setM(3); + b = mp.getPoint(1).equals(p); + assertTrue(b); + } + } } From 9748d68e86192288066fb647ad021d2b68836afe Mon Sep 17 00:00:00 2001 From: Aaron Balog Date: Wed, 14 Jan 2015 10:36:11 -0800 Subject: [PATCH 098/196] removed debugging code --- .../core/geometry/RelationalOperationsMatrix.java | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/src/main/java/com/esri/core/geometry/RelationalOperationsMatrix.java b/src/main/java/com/esri/core/geometry/RelationalOperationsMatrix.java index 1ad87dc4..7660fc4e 100644 --- a/src/main/java/com/esri/core/geometry/RelationalOperationsMatrix.java +++ b/src/main/java/com/esri/core/geometry/RelationalOperationsMatrix.java @@ -383,21 +383,6 @@ else if (relation == RelationalOperations.Relation.within) edit_shape = new EditShape(); geom_a = edit_shape.addGeometry(polygon_simple_a); geom_b = edit_shape.addGeometry(boundary_b); - - int pc1 = polygon_simple_a.getPointCount(); - int pc2 = boundary_b.getPointCount(); - - for (int i = 0; i < pc2; i++) - { - Point2D p = boundary_b.getXY(i); - i = i; - } - - boolean isclosed0 = boundary_b.isClosedPath(0); - boolean isclosed1 = boundary_b.isClosedPath(1); - - String s1 = OperatorExportToJson.local().execute(null, polygon_simple_a); - String s2 = OperatorExportToJson.local().execute(null, boundary_b); relOps.setEditShape_(edit_shape, progress_tracker); // Check no interior lines of the boundary intersect the exterior From 2700750dcf061443d7fc5bec8098a1a57b5721ac Mon Sep 17 00:00:00 2001 From: serg4066 Date: Wed, 14 Jan 2015 12:31:10 -0800 Subject: [PATCH 099/196] fix Envelope.toString --- src/main/java/com/esri/core/geometry/Envelope.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/esri/core/geometry/Envelope.java b/src/main/java/com/esri/core/geometry/Envelope.java index 9d74440b..3f3653f1 100644 --- a/src/main/java/com/esri/core/geometry/Envelope.java +++ b/src/main/java/com/esri/core/geometry/Envelope.java @@ -1139,7 +1139,7 @@ public String toString() { if (isEmpty()) return "Envelope: []"; - String s = "Envelope: [" + m_envelope.xmin + ", " + m_envelope.ymin + ", " + m_envelope.xmin + ", " + m_envelope.ymin +"]"; + String s = "Envelope: [" + m_envelope.xmin + ", " + m_envelope.ymin + ", " + m_envelope.xmax + ", " + m_envelope.ymax +"]"; return s; } From a6fb1645ea61a440bdaf7eaf2710715bf6fe84a7 Mon Sep 17 00:00:00 2001 From: Aaron Balog Date: Fri, 16 Jan 2015 08:39:16 -0800 Subject: [PATCH 100/196] Resize buffer to accomodate insertion --- .../com/esri/core/geometry/AttributeStreamOfDbl.java | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/esri/core/geometry/AttributeStreamOfDbl.java b/src/main/java/com/esri/core/geometry/AttributeStreamOfDbl.java index 09f87ff4..2009eaf0 100644 --- a/src/main/java/com/esri/core/geometry/AttributeStreamOfDbl.java +++ b/src/main/java/com/esri/core/geometry/AttributeStreamOfDbl.java @@ -405,7 +405,14 @@ public void insertRange(int start, AttributeStreamBase src, int srcStart, if (!bForward && (stride < 1 || count % stride != 0)) throw new IllegalArgumentException(); - System.arraycopy(m_buffer, start, m_buffer, start + count, validSize + int excess_space = m_size - validSize; + + if (excess_space < count) { + int original_size = m_size; + resize(original_size + count - excess_space); + } + + System.arraycopy(m_buffer, start, m_buffer, start + count, validSize - start); if (m_buffer == ((AttributeStreamOfDbl) src).m_buffer) { From bfd52befa434b57d3502835cba533cfe49b47a91 Mon Sep 17 00:00:00 2001 From: Randall Whitman Date: Fri, 6 Feb 2015 14:17:51 -0800 Subject: [PATCH 101/196] README: update copyright notice to 2015 --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 16d10710..03d91b99 100644 --- a/README.md +++ b/README.md @@ -53,7 +53,7 @@ Find a bug or want to request a new feature? Please let us know by submitting a Esri welcomes contributions from anyone and everyone. Please see our [guidelines for contributing](https://github.com/esri/contributing) ## Licensing -Copyright 2013 Esri +Copyright 2013-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. From e59f9b16f3939a76202554887ce0d9aab2155296 Mon Sep 17 00:00:00 2001 From: serg4066 Date: Fri, 6 Feb 2015 14:57:22 -0800 Subject: [PATCH 102/196] updated date for copyright to 2015. Changed SimpleRasterizer visibility --- .../core/geometry/AttributeStreamBase.java | 2 +- .../core/geometry/AttributeStreamOfDbl.java | 6 +- .../core/geometry/AttributeStreamOfFloat.java | 2 +- .../core/geometry/AttributeStreamOfInt16.java | 2 +- .../core/geometry/AttributeStreamOfInt32.java | 2 +- .../core/geometry/AttributeStreamOfInt64.java | 2 +- .../core/geometry/AttributeStreamOfInt8.java | 2 +- .../java/com/esri/core/geometry/Boundary.java | 2 +- .../com/esri/core/geometry/BucketSort.java | 2 +- .../java/com/esri/core/geometry/Bufferer.java | 2 +- .../esri/core/geometry/ByteBufferCursor.java | 2 +- .../com/esri/core/geometry/ClassicSort.java | 2 +- .../java/com/esri/core/geometry/Clipper.java | 2 +- .../com/esri/core/geometry/Clusterer.java | 2 +- .../esri/core/geometry/ConstructOffset.java | 2 +- .../com/esri/core/geometry/ConvexHull.java | 2 +- .../esri/core/geometry/CrackAndCluster.java | 2 +- .../java/com/esri/core/geometry/Cracker.java | 2 +- .../java/com/esri/core/geometry/Cutter.java | 2 +- .../com/esri/core/geometry/DirtyFlags.java | 2 +- .../com/esri/core/geometry/ECoordinate.java | 2 +- .../com/esri/core/geometry/EditShape.java | 2 +- .../java/com/esri/core/geometry/Envelope.java | 2 +- .../com/esri/core/geometry/Envelope1D.java | 2 +- .../com/esri/core/geometry/Envelope2D.java | 2 +- .../geometry/Envelope2DIntersectorImpl.java | 2 +- .../com/esri/core/geometry/Envelope3D.java | 2 +- .../java/com/esri/core/geometry/GeoDist.java | 2 +- .../core/geometry/GeoJsonExportFlags.java | 2 +- .../core/geometry/GeoJsonImportFlags.java | 2 +- .../esri/core/geometry/GeodeticCurveType.java | 2 +- .../java/com/esri/core/geometry/Geometry.java | 2 +- .../core/geometry/GeometryAccelerators.java | 2 +- .../esri/core/geometry/GeometryCursor.java | 2 +- .../core/geometry/GeometryCursorAppend.java | 2 +- .../esri/core/geometry/GeometryEngine.java | 2 +- .../esri/core/geometry/GeometryException.java | 2 +- .../core/geometry/GeometrySerializer.java | 2 +- .../esri/core/geometry/IndexHashTable.java | 2 +- .../esri/core/geometry/IndexMultiDCList.java | 2 +- .../esri/core/geometry/IndexMultiList.java | 2 +- .../com/esri/core/geometry/InternalUtils.java | 2 +- .../java/com/esri/core/geometry/Interop.java | 2 +- .../esri/core/geometry/IntervalTreeImpl.java | 2 +- .../core/geometry/JSONArrayEnumerator.java | 2 +- .../core/geometry/JSONObjectEnumerator.java | 2 +- .../com/esri/core/geometry/JSONUtils.java | 2 +- .../com/esri/core/geometry/JsonCursor.java | 2 +- .../esri/core/geometry/JsonParserCursor.java | 2 +- .../esri/core/geometry/JsonParserReader.java | 2 +- .../com/esri/core/geometry/JsonReader.java | 2 +- .../esri/core/geometry/JsonStringWriter.java | 2 +- .../esri/core/geometry/JsonValueReader.java | 2 +- .../com/esri/core/geometry/JsonWriter.java | 2 +- .../java/com/esri/core/geometry/Line.java | 2 +- .../geometry/ListeningGeometryCursor.java | 2 +- .../com/esri/core/geometry/MapGeometry.java | 2 +- .../esri/core/geometry/MapGeometryCursor.java | 2 +- .../esri/core/geometry/MapOGCStructure.java | 2 +- .../com/esri/core/geometry/MathUtils.java | 2 +- .../core/geometry/MgrsConversionMode.java | 2 +- .../com/esri/core/geometry/MultiPath.java | 2 +- .../com/esri/core/geometry/MultiPathImpl.java | 2 +- .../com/esri/core/geometry/MultiPoint.java | 2 +- .../esri/core/geometry/MultiPointImpl.java | 2 +- .../core/geometry/MultiVertexGeometry.java | 2 +- .../geometry/MultiVertexGeometryImpl.java | 2 +- .../esri/core/geometry/NonSimpleResult.java | 2 +- .../com/esri/core/geometry/NumberUtils.java | 2 +- .../com/esri/core/geometry/OGCStructure.java | 2 +- .../esri/core/geometry/ObjectCacheTable.java | 2 +- .../java/com/esri/core/geometry/Operator.java | 2 +- .../esri/core/geometry/OperatorBoundary.java | 2 +- .../core/geometry/OperatorBoundaryLocal.java | 2 +- .../geometry/OperatorBoundaryLocalCursor.java | 2 +- .../esri/core/geometry/OperatorBuffer.java | 2 +- .../core/geometry/OperatorBufferCursor.java | 2 +- .../core/geometry/OperatorBufferLocal.java | 2 +- .../com/esri/core/geometry/OperatorClip.java | 2 +- .../core/geometry/OperatorClipCursor.java | 2 +- .../esri/core/geometry/OperatorClipLocal.java | 2 +- .../esri/core/geometry/OperatorContains.java | 2 +- .../core/geometry/OperatorContainsLocal.java | 2 +- .../core/geometry/OperatorConvexHull.java | 2 +- .../geometry/OperatorConvexHullCursor.java | 2 +- .../geometry/OperatorConvexHullLocal.java | 2 +- .../esri/core/geometry/OperatorCrosses.java | 2 +- .../core/geometry/OperatorCrossesLocal.java | 2 +- .../com/esri/core/geometry/OperatorCut.java | 2 +- .../esri/core/geometry/OperatorCutCursor.java | 2 +- .../esri/core/geometry/OperatorCutLocal.java | 2 +- .../geometry/OperatorDensifyByLength.java | 2 +- .../OperatorDensifyByLengthCursor.java | 2 +- .../OperatorDensifyByLengthLocal.java | 2 +- .../core/geometry/OperatorDifference.java | 2 +- .../geometry/OperatorDifferenceCursor.java | 2 +- .../geometry/OperatorDifferenceLocal.java | 2 +- .../esri/core/geometry/OperatorDisjoint.java | 2 +- .../core/geometry/OperatorDisjointLocal.java | 2 +- .../esri/core/geometry/OperatorDistance.java | 2 +- .../core/geometry/OperatorDistanceLocal.java | 2 +- .../esri/core/geometry/OperatorEquals.java | 2 +- .../core/geometry/OperatorEqualsLocal.java | 2 +- .../geometry/OperatorExportToESRIShape.java | 2 +- .../OperatorExportToESRIShapeCursor.java | 2 +- .../OperatorExportToESRIShapeLocal.java | 2 +- .../core/geometry/OperatorExportToJson.java | 2 +- .../geometry/OperatorExportToJsonCursor.java | 2 +- .../geometry/OperatorExportToJsonLocal.java | 2 +- .../core/geometry/OperatorExportToWkb.java | 2 +- .../geometry/OperatorExportToWkbLocal.java | 2 +- .../core/geometry/OperatorExportToWkt.java | 2 +- .../geometry/OperatorExportToWktLocal.java | 2 +- .../esri/core/geometry/OperatorFactory.java | 2 +- .../core/geometry/OperatorFactoryLocal.java | 2 +- .../core/geometry/OperatorGeneralize.java | 2 +- .../geometry/OperatorGeneralizeCursor.java | 2 +- .../geometry/OperatorGeneralizeLocal.java | 2 +- .../core/geometry/OperatorGeodesicBuffer.java | 2 +- .../geometry/OperatorGeodesicBufferLocal.java | 2 +- .../core/geometry/OperatorGeodeticArea.java | 2 +- .../geometry/OperatorGeodeticAreaLocal.java | 2 +- .../OperatorGeodeticDensifyByLength.java | 2 +- .../OperatorGeodeticDensifyLocal.java | 2 +- .../core/geometry/OperatorGeodeticLength.java | 2 +- .../geometry/OperatorGeodeticLengthLocal.java | 2 +- .../geometry/OperatorImportFromESRIShape.java | 2 +- .../OperatorImportFromESRIShapeCursor.java | 2 +- .../OperatorImportFromESRIShapeLocal.java | 2 +- .../geometry/OperatorImportFromGeoJson.java | 2 +- .../OperatorImportFromGeoJsonLocal.java | 2 +- .../core/geometry/OperatorImportFromJson.java | 2 +- .../OperatorImportFromJsonCursor.java | 2 +- .../geometry/OperatorImportFromJsonLocal.java | 2 +- .../core/geometry/OperatorImportFromWkb.java | 2 +- .../geometry/OperatorImportFromWkbLocal.java | 2 +- .../core/geometry/OperatorImportFromWkt.java | 2 +- .../geometry/OperatorImportFromWktLocal.java | 2 +- .../OperatorInternalRelationUtils.java | 2 +- .../core/geometry/OperatorIntersection.java | 2 +- .../geometry/OperatorIntersectionCursor.java | 2 +- .../geometry/OperatorIntersectionLocal.java | 2 +- .../core/geometry/OperatorIntersects.java | 2 +- .../geometry/OperatorIntersectsLocal.java | 2 +- .../esri/core/geometry/OperatorOffset.java | 2 +- .../core/geometry/OperatorOffsetCursor.java | 2 +- .../core/geometry/OperatorOffsetLocal.java | 2 +- .../esri/core/geometry/OperatorOverlaps.java | 2 +- .../core/geometry/OperatorOverlapsLocal.java | 2 +- .../esri/core/geometry/OperatorProject.java | 2 +- .../core/geometry/OperatorProjectLocal.java | 2 +- .../core/geometry/OperatorProximity2D.java | 2 +- .../geometry/OperatorProximity2DLocal.java | 2 +- .../esri/core/geometry/OperatorRelate.java | 2 +- .../core/geometry/OperatorRelateLocal.java | 2 +- .../OperatorShapePreservingDensify.java | 2 +- .../OperatorShapePreservingDensifyLocal.java | 2 +- .../core/geometry/OperatorSimpleRelation.java | 2 +- .../esri/core/geometry/OperatorSimplify.java | 2 +- .../core/geometry/OperatorSimplifyCursor.java | 2 +- .../geometry/OperatorSimplifyCursorOGC.java | 2 +- .../core/geometry/OperatorSimplifyLocal.java | 2 +- .../geometry/OperatorSimplifyLocalHelper.java | 2 +- .../geometry/OperatorSimplifyLocalOGC.java | 2 +- .../core/geometry/OperatorSimplifyOGC.java | 2 +- .../geometry/OperatorSymmetricDifference.java | 2 +- .../OperatorSymmetricDifferenceCursor.java | 2 +- .../OperatorSymmetricDifferenceLocal.java | 2 +- .../esri/core/geometry/OperatorTouches.java | 2 +- .../core/geometry/OperatorTouchesLocal.java | 2 +- .../com/esri/core/geometry/OperatorUnion.java | 2 +- .../core/geometry/OperatorUnionCursor.java | 39 +- .../core/geometry/OperatorUnionLocal.java | 2 +- .../esri/core/geometry/OperatorWithin.java | 2 +- .../core/geometry/OperatorWithinLocal.java | 2 +- .../geometry/PairwiseIntersectorImpl.java | 2 +- .../com/esri/core/geometry/PathFlags.java | 2 +- .../java/com/esri/core/geometry/PeDouble.java | 2 +- .../geometry/PlaneSweepCrackerHelper.java | 2 +- .../java/com/esri/core/geometry/Point.java | 2 +- .../java/com/esri/core/geometry/Point2D.java | 2 +- .../java/com/esri/core/geometry/Point3D.java | 2 +- .../core/geometry/PointInPolygonHelper.java | 2 +- .../java/com/esri/core/geometry/Polygon.java | 4 +- .../com/esri/core/geometry/PolygonUtils.java | 2 +- .../java/com/esri/core/geometry/Polyline.java | 2 +- .../com/esri/core/geometry/PolylinePath.java | 2 +- .../esri/core/geometry/ProgressTracker.java | 2 +- .../geometry/ProjectionTransformation.java | 2 +- .../esri/core/geometry/Proximity2DResult.java | 2 +- .../geometry/Proximity2DResultComparator.java | 2 +- .../java/com/esri/core/geometry/QuadTree.java | 2 +- .../com/esri/core/geometry/QuadTreeImpl.java | 2 +- .../core/geometry/RasterizedGeometry2D.java | 2 +- .../geometry/RasterizedGeometry2DImpl.java | 62 ++-- .../core/geometry/RelationalOperations.java | 2 +- .../geometry/RelationalOperationsMatrix.java | 2 +- .../core/geometry/RingOrientationFixer.java | 2 +- .../java/com/esri/core/geometry/Segment.java | 2 +- .../com/esri/core/geometry/SegmentBuffer.java | 2 +- .../com/esri/core/geometry/SegmentFlags.java | 2 +- .../core/geometry/SegmentIntersector.java | 2 +- .../esri/core/geometry/SegmentIterator.java | 2 +- .../core/geometry/SegmentIteratorImpl.java | 2 +- .../esri/core/geometry/ShapeExportFlags.java | 2 +- .../esri/core/geometry/ShapeImportFlags.java | 2 +- .../esri/core/geometry/ShapeModifiers.java | 2 +- .../com/esri/core/geometry/ShapeType.java | 2 +- .../core/geometry/SimpleByteBufferCursor.java | 2 +- .../core/geometry/SimpleGeometryCursor.java | 2 +- .../esri/core/geometry/SimpleJsonCursor.java | 2 +- .../core/geometry/SimpleJsonParserCursor.java | 2 +- .../geometry/SimpleMapGeometryCursor.java | 2 +- .../esri/core/geometry/SimpleRasterizer.java | 335 ++++++++++++------ .../com/esri/core/geometry/Simplificator.java | 2 +- .../esri/core/geometry/SpatialReference.java | 2 +- .../core/geometry/SpatialReferenceImpl.java | 2 +- .../geometry/SpatialReferenceSerializer.java | 2 +- .../geometry/StridedIndexTypeCollection.java | 2 +- .../com/esri/core/geometry/StringUtils.java | 2 +- .../esri/core/geometry/SweepComparator.java | 2 +- .../core/geometry/SweepMonkierComparator.java | 2 +- .../com/esri/core/geometry/TopoGraph.java | 2 +- .../core/geometry/TopologicalOperations.java | 2 +- .../esri/core/geometry/Transformation2D.java | 2 +- .../esri/core/geometry/Transformation3D.java | 2 +- .../java/com/esri/core/geometry/Treap.java | 2 +- .../core/geometry/UserCancelException.java | 2 +- .../esri/core/geometry/VertexDescription.java | 2 +- .../VertexDescriptionDesignerImpl.java | 2 +- .../core/geometry/VertexDescriptionHash.java | 2 +- .../com/esri/core/geometry/WkbByteOrder.java | 2 +- .../esri/core/geometry/WkbExportFlags.java | 2 +- .../esri/core/geometry/WkbGeometryType.java | 2 +- .../esri/core/geometry/WkbImportFlags.java | 2 +- .../java/com/esri/core/geometry/Wkid.java | 2 +- src/main/java/com/esri/core/geometry/Wkt.java | 2 +- .../esri/core/geometry/WktExportFlags.java | 2 +- .../esri/core/geometry/WktImportFlags.java | 2 +- .../com/esri/core/geometry/WktParser.java | 2 +- .../com/esri/core/geometry/TestPolygon.java | 1 - .../geometry/TestRasterizedGeometry2D.java | 19 + 242 files changed, 527 insertions(+), 409 deletions(-) diff --git a/src/main/java/com/esri/core/geometry/AttributeStreamBase.java b/src/main/java/com/esri/core/geometry/AttributeStreamBase.java index 42c71a58..54f03ba2 100644 --- a/src/main/java/com/esri/core/geometry/AttributeStreamBase.java +++ b/src/main/java/com/esri/core/geometry/AttributeStreamBase.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/AttributeStreamOfDbl.java b/src/main/java/com/esri/core/geometry/AttributeStreamOfDbl.java index 2009eaf0..18ae0fc1 100644 --- a/src/main/java/com/esri/core/geometry/AttributeStreamOfDbl.java +++ b/src/main/java/com/esri/core/geometry/AttributeStreamOfDbl.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -405,14 +405,14 @@ public void insertRange(int start, AttributeStreamBase src, int srcStart, if (!bForward && (stride < 1 || count % stride != 0)) throw new IllegalArgumentException(); - int excess_space = m_size - validSize; + int excess_space = m_size - validSize; if (excess_space < count) { int original_size = m_size; resize(original_size + count - excess_space); } - System.arraycopy(m_buffer, start, m_buffer, start + count, validSize + System.arraycopy(m_buffer, start, m_buffer, start + count, validSize - start); if (m_buffer == ((AttributeStreamOfDbl) src).m_buffer) { diff --git a/src/main/java/com/esri/core/geometry/AttributeStreamOfFloat.java b/src/main/java/com/esri/core/geometry/AttributeStreamOfFloat.java index 3577267b..1e90b35b 100644 --- a/src/main/java/com/esri/core/geometry/AttributeStreamOfFloat.java +++ b/src/main/java/com/esri/core/geometry/AttributeStreamOfFloat.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/AttributeStreamOfInt16.java b/src/main/java/com/esri/core/geometry/AttributeStreamOfInt16.java index 5e9cf181..d4af7efe 100644 --- a/src/main/java/com/esri/core/geometry/AttributeStreamOfInt16.java +++ b/src/main/java/com/esri/core/geometry/AttributeStreamOfInt16.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/AttributeStreamOfInt32.java b/src/main/java/com/esri/core/geometry/AttributeStreamOfInt32.java index 609a19fd..8aa48e56 100644 --- a/src/main/java/com/esri/core/geometry/AttributeStreamOfInt32.java +++ b/src/main/java/com/esri/core/geometry/AttributeStreamOfInt32.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/AttributeStreamOfInt64.java b/src/main/java/com/esri/core/geometry/AttributeStreamOfInt64.java index 3498770b..62516ea2 100644 --- a/src/main/java/com/esri/core/geometry/AttributeStreamOfInt64.java +++ b/src/main/java/com/esri/core/geometry/AttributeStreamOfInt64.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/AttributeStreamOfInt8.java b/src/main/java/com/esri/core/geometry/AttributeStreamOfInt8.java index 2d10396a..b0a746e4 100644 --- a/src/main/java/com/esri/core/geometry/AttributeStreamOfInt8.java +++ b/src/main/java/com/esri/core/geometry/AttributeStreamOfInt8.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/Boundary.java b/src/main/java/com/esri/core/geometry/Boundary.java index 13958cb7..7ce5d361 100644 --- a/src/main/java/com/esri/core/geometry/Boundary.java +++ b/src/main/java/com/esri/core/geometry/Boundary.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/BucketSort.java b/src/main/java/com/esri/core/geometry/BucketSort.java index abd698c7..5039a4e8 100644 --- a/src/main/java/com/esri/core/geometry/BucketSort.java +++ b/src/main/java/com/esri/core/geometry/BucketSort.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/Bufferer.java b/src/main/java/com/esri/core/geometry/Bufferer.java index 587287fd..ebe961ce 100644 --- a/src/main/java/com/esri/core/geometry/Bufferer.java +++ b/src/main/java/com/esri/core/geometry/Bufferer.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/ByteBufferCursor.java b/src/main/java/com/esri/core/geometry/ByteBufferCursor.java index 53f0121a..15b65905 100644 --- a/src/main/java/com/esri/core/geometry/ByteBufferCursor.java +++ b/src/main/java/com/esri/core/geometry/ByteBufferCursor.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/ClassicSort.java b/src/main/java/com/esri/core/geometry/ClassicSort.java index 57691017..edf1c9d1 100644 --- a/src/main/java/com/esri/core/geometry/ClassicSort.java +++ b/src/main/java/com/esri/core/geometry/ClassicSort.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/Clipper.java b/src/main/java/com/esri/core/geometry/Clipper.java index 2af2e0a2..ca0fad19 100644 --- a/src/main/java/com/esri/core/geometry/Clipper.java +++ b/src/main/java/com/esri/core/geometry/Clipper.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/Clusterer.java b/src/main/java/com/esri/core/geometry/Clusterer.java index 8ff5efd8..528ea914 100644 --- a/src/main/java/com/esri/core/geometry/Clusterer.java +++ b/src/main/java/com/esri/core/geometry/Clusterer.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/ConstructOffset.java b/src/main/java/com/esri/core/geometry/ConstructOffset.java index 89db1b0b..3b143a00 100644 --- a/src/main/java/com/esri/core/geometry/ConstructOffset.java +++ b/src/main/java/com/esri/core/geometry/ConstructOffset.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/ConvexHull.java b/src/main/java/com/esri/core/geometry/ConvexHull.java index 7c5a4e5a..bafe51e0 100644 --- a/src/main/java/com/esri/core/geometry/ConvexHull.java +++ b/src/main/java/com/esri/core/geometry/ConvexHull.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/CrackAndCluster.java b/src/main/java/com/esri/core/geometry/CrackAndCluster.java index 6f6d8247..89d40da9 100644 --- a/src/main/java/com/esri/core/geometry/CrackAndCluster.java +++ b/src/main/java/com/esri/core/geometry/CrackAndCluster.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/Cracker.java b/src/main/java/com/esri/core/geometry/Cracker.java index cd8b9a72..88b4f6b8 100644 --- a/src/main/java/com/esri/core/geometry/Cracker.java +++ b/src/main/java/com/esri/core/geometry/Cracker.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/Cutter.java b/src/main/java/com/esri/core/geometry/Cutter.java index d575e1de..f56dc5de 100644 --- a/src/main/java/com/esri/core/geometry/Cutter.java +++ b/src/main/java/com/esri/core/geometry/Cutter.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/DirtyFlags.java b/src/main/java/com/esri/core/geometry/DirtyFlags.java index 030539fe..36ccafda 100644 --- a/src/main/java/com/esri/core/geometry/DirtyFlags.java +++ b/src/main/java/com/esri/core/geometry/DirtyFlags.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/ECoordinate.java b/src/main/java/com/esri/core/geometry/ECoordinate.java index cfc3146b..253a05b6 100644 --- a/src/main/java/com/esri/core/geometry/ECoordinate.java +++ b/src/main/java/com/esri/core/geometry/ECoordinate.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/EditShape.java b/src/main/java/com/esri/core/geometry/EditShape.java index a85ca5a2..36ce09a8 100644 --- a/src/main/java/com/esri/core/geometry/EditShape.java +++ b/src/main/java/com/esri/core/geometry/EditShape.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/Envelope.java b/src/main/java/com/esri/core/geometry/Envelope.java index 3f3653f1..6991f26e 100644 --- a/src/main/java/com/esri/core/geometry/Envelope.java +++ b/src/main/java/com/esri/core/geometry/Envelope.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/Envelope1D.java b/src/main/java/com/esri/core/geometry/Envelope1D.java index 993eef36..9e30e08b 100644 --- a/src/main/java/com/esri/core/geometry/Envelope1D.java +++ b/src/main/java/com/esri/core/geometry/Envelope1D.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/Envelope2D.java b/src/main/java/com/esri/core/geometry/Envelope2D.java index f2e4ef41..3b9a02f7 100644 --- a/src/main/java/com/esri/core/geometry/Envelope2D.java +++ b/src/main/java/com/esri/core/geometry/Envelope2D.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/Envelope2DIntersectorImpl.java b/src/main/java/com/esri/core/geometry/Envelope2DIntersectorImpl.java index d9430f96..98d7ad8c 100644 --- a/src/main/java/com/esri/core/geometry/Envelope2DIntersectorImpl.java +++ b/src/main/java/com/esri/core/geometry/Envelope2DIntersectorImpl.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/Envelope3D.java b/src/main/java/com/esri/core/geometry/Envelope3D.java index 1dd38ef6..c1960fd6 100644 --- a/src/main/java/com/esri/core/geometry/Envelope3D.java +++ b/src/main/java/com/esri/core/geometry/Envelope3D.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/GeoDist.java b/src/main/java/com/esri/core/geometry/GeoDist.java index 9a1b410d..81225134 100644 --- a/src/main/java/com/esri/core/geometry/GeoDist.java +++ b/src/main/java/com/esri/core/geometry/GeoDist.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/GeoJsonExportFlags.java b/src/main/java/com/esri/core/geometry/GeoJsonExportFlags.java index 04da23ce..027bf2ac 100644 --- a/src/main/java/com/esri/core/geometry/GeoJsonExportFlags.java +++ b/src/main/java/com/esri/core/geometry/GeoJsonExportFlags.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/GeoJsonImportFlags.java b/src/main/java/com/esri/core/geometry/GeoJsonImportFlags.java index e2539151..a464d20b 100644 --- a/src/main/java/com/esri/core/geometry/GeoJsonImportFlags.java +++ b/src/main/java/com/esri/core/geometry/GeoJsonImportFlags.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/GeodeticCurveType.java b/src/main/java/com/esri/core/geometry/GeodeticCurveType.java index b07a73e1..3dc053af 100644 --- a/src/main/java/com/esri/core/geometry/GeodeticCurveType.java +++ b/src/main/java/com/esri/core/geometry/GeodeticCurveType.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/Geometry.java b/src/main/java/com/esri/core/geometry/Geometry.java index 6e16c0e6..e5d08afa 100644 --- a/src/main/java/com/esri/core/geometry/Geometry.java +++ b/src/main/java/com/esri/core/geometry/Geometry.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/GeometryAccelerators.java b/src/main/java/com/esri/core/geometry/GeometryAccelerators.java index 138b134e..2ccc84cc 100644 --- a/src/main/java/com/esri/core/geometry/GeometryAccelerators.java +++ b/src/main/java/com/esri/core/geometry/GeometryAccelerators.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/GeometryCursor.java b/src/main/java/com/esri/core/geometry/GeometryCursor.java index 503dfe4c..3c2ef5d6 100644 --- a/src/main/java/com/esri/core/geometry/GeometryCursor.java +++ b/src/main/java/com/esri/core/geometry/GeometryCursor.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/GeometryCursorAppend.java b/src/main/java/com/esri/core/geometry/GeometryCursorAppend.java index cbf3faee..dec0b708 100644 --- a/src/main/java/com/esri/core/geometry/GeometryCursorAppend.java +++ b/src/main/java/com/esri/core/geometry/GeometryCursorAppend.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/GeometryEngine.java b/src/main/java/com/esri/core/geometry/GeometryEngine.java index 048994dd..e027dd6c 100644 --- a/src/main/java/com/esri/core/geometry/GeometryEngine.java +++ b/src/main/java/com/esri/core/geometry/GeometryEngine.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/GeometryException.java b/src/main/java/com/esri/core/geometry/GeometryException.java index 41ea2984..97638685 100644 --- a/src/main/java/com/esri/core/geometry/GeometryException.java +++ b/src/main/java/com/esri/core/geometry/GeometryException.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/GeometrySerializer.java b/src/main/java/com/esri/core/geometry/GeometrySerializer.java index 8bf9f401..b91c16f7 100644 --- a/src/main/java/com/esri/core/geometry/GeometrySerializer.java +++ b/src/main/java/com/esri/core/geometry/GeometrySerializer.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/IndexHashTable.java b/src/main/java/com/esri/core/geometry/IndexHashTable.java index 4e60b59e..b0117bb8 100644 --- a/src/main/java/com/esri/core/geometry/IndexHashTable.java +++ b/src/main/java/com/esri/core/geometry/IndexHashTable.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/IndexMultiDCList.java b/src/main/java/com/esri/core/geometry/IndexMultiDCList.java index b7a0a119..c20adbec 100644 --- a/src/main/java/com/esri/core/geometry/IndexMultiDCList.java +++ b/src/main/java/com/esri/core/geometry/IndexMultiDCList.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/IndexMultiList.java b/src/main/java/com/esri/core/geometry/IndexMultiList.java index 44b1da78..b008094a 100644 --- a/src/main/java/com/esri/core/geometry/IndexMultiList.java +++ b/src/main/java/com/esri/core/geometry/IndexMultiList.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/InternalUtils.java b/src/main/java/com/esri/core/geometry/InternalUtils.java index 42bd8362..257eb63f 100644 --- a/src/main/java/com/esri/core/geometry/InternalUtils.java +++ b/src/main/java/com/esri/core/geometry/InternalUtils.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/Interop.java b/src/main/java/com/esri/core/geometry/Interop.java index 38df1b52..b47801c7 100644 --- a/src/main/java/com/esri/core/geometry/Interop.java +++ b/src/main/java/com/esri/core/geometry/Interop.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/IntervalTreeImpl.java b/src/main/java/com/esri/core/geometry/IntervalTreeImpl.java index 1237e798..acdc10d8 100644 --- a/src/main/java/com/esri/core/geometry/IntervalTreeImpl.java +++ b/src/main/java/com/esri/core/geometry/IntervalTreeImpl.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/JSONArrayEnumerator.java b/src/main/java/com/esri/core/geometry/JSONArrayEnumerator.java index 5b2d93d7..1350f623 100644 --- a/src/main/java/com/esri/core/geometry/JSONArrayEnumerator.java +++ b/src/main/java/com/esri/core/geometry/JSONArrayEnumerator.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/JSONObjectEnumerator.java b/src/main/java/com/esri/core/geometry/JSONObjectEnumerator.java index f474a16c..7b8f2e8b 100644 --- a/src/main/java/com/esri/core/geometry/JSONObjectEnumerator.java +++ b/src/main/java/com/esri/core/geometry/JSONObjectEnumerator.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/JSONUtils.java b/src/main/java/com/esri/core/geometry/JSONUtils.java index 1ec51185..71feb32a 100644 --- a/src/main/java/com/esri/core/geometry/JSONUtils.java +++ b/src/main/java/com/esri/core/geometry/JSONUtils.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/JsonCursor.java b/src/main/java/com/esri/core/geometry/JsonCursor.java index 30e1beed..0af0024f 100644 --- a/src/main/java/com/esri/core/geometry/JsonCursor.java +++ b/src/main/java/com/esri/core/geometry/JsonCursor.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/JsonParserCursor.java b/src/main/java/com/esri/core/geometry/JsonParserCursor.java index fe3605ab..1a1f14f3 100644 --- a/src/main/java/com/esri/core/geometry/JsonParserCursor.java +++ b/src/main/java/com/esri/core/geometry/JsonParserCursor.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/JsonParserReader.java b/src/main/java/com/esri/core/geometry/JsonParserReader.java index efa6f55d..80666579 100644 --- a/src/main/java/com/esri/core/geometry/JsonParserReader.java +++ b/src/main/java/com/esri/core/geometry/JsonParserReader.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/JsonReader.java b/src/main/java/com/esri/core/geometry/JsonReader.java index 1b3a4f03..ba3b8cca 100644 --- a/src/main/java/com/esri/core/geometry/JsonReader.java +++ b/src/main/java/com/esri/core/geometry/JsonReader.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/JsonStringWriter.java b/src/main/java/com/esri/core/geometry/JsonStringWriter.java index 6947f5a1..f324a197 100644 --- a/src/main/java/com/esri/core/geometry/JsonStringWriter.java +++ b/src/main/java/com/esri/core/geometry/JsonStringWriter.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/JsonValueReader.java b/src/main/java/com/esri/core/geometry/JsonValueReader.java index 9f320029..d7c4b639 100644 --- a/src/main/java/com/esri/core/geometry/JsonValueReader.java +++ b/src/main/java/com/esri/core/geometry/JsonValueReader.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/JsonWriter.java b/src/main/java/com/esri/core/geometry/JsonWriter.java index 9e71beb8..0baa0f2b 100644 --- a/src/main/java/com/esri/core/geometry/JsonWriter.java +++ b/src/main/java/com/esri/core/geometry/JsonWriter.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/Line.java b/src/main/java/com/esri/core/geometry/Line.java index 95126e81..a3c5e570 100644 --- a/src/main/java/com/esri/core/geometry/Line.java +++ b/src/main/java/com/esri/core/geometry/Line.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/ListeningGeometryCursor.java b/src/main/java/com/esri/core/geometry/ListeningGeometryCursor.java index 04de301e..965a6798 100644 --- a/src/main/java/com/esri/core/geometry/ListeningGeometryCursor.java +++ b/src/main/java/com/esri/core/geometry/ListeningGeometryCursor.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/MapGeometry.java b/src/main/java/com/esri/core/geometry/MapGeometry.java index fc1ef12b..d7161d52 100644 --- a/src/main/java/com/esri/core/geometry/MapGeometry.java +++ b/src/main/java/com/esri/core/geometry/MapGeometry.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/MapGeometryCursor.java b/src/main/java/com/esri/core/geometry/MapGeometryCursor.java index 81b0b195..b7cec8e4 100644 --- a/src/main/java/com/esri/core/geometry/MapGeometryCursor.java +++ b/src/main/java/com/esri/core/geometry/MapGeometryCursor.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/MapOGCStructure.java b/src/main/java/com/esri/core/geometry/MapOGCStructure.java index 61ec600e..c4eb5241 100644 --- a/src/main/java/com/esri/core/geometry/MapOGCStructure.java +++ b/src/main/java/com/esri/core/geometry/MapOGCStructure.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/MathUtils.java b/src/main/java/com/esri/core/geometry/MathUtils.java index a338dccd..208fe4ba 100644 --- a/src/main/java/com/esri/core/geometry/MathUtils.java +++ b/src/main/java/com/esri/core/geometry/MathUtils.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/MgrsConversionMode.java b/src/main/java/com/esri/core/geometry/MgrsConversionMode.java index 21b54da8..b92ef646 100644 --- a/src/main/java/com/esri/core/geometry/MgrsConversionMode.java +++ b/src/main/java/com/esri/core/geometry/MgrsConversionMode.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/MultiPath.java b/src/main/java/com/esri/core/geometry/MultiPath.java index 1fb76d68..f2b80e89 100644 --- a/src/main/java/com/esri/core/geometry/MultiPath.java +++ b/src/main/java/com/esri/core/geometry/MultiPath.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/MultiPathImpl.java b/src/main/java/com/esri/core/geometry/MultiPathImpl.java index ba566436..43ed98a5 100644 --- a/src/main/java/com/esri/core/geometry/MultiPathImpl.java +++ b/src/main/java/com/esri/core/geometry/MultiPathImpl.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/MultiPoint.java b/src/main/java/com/esri/core/geometry/MultiPoint.java index 3604e108..2d2cbf28 100644 --- a/src/main/java/com/esri/core/geometry/MultiPoint.java +++ b/src/main/java/com/esri/core/geometry/MultiPoint.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/MultiPointImpl.java b/src/main/java/com/esri/core/geometry/MultiPointImpl.java index b29e35c3..b3d0a4b7 100644 --- a/src/main/java/com/esri/core/geometry/MultiPointImpl.java +++ b/src/main/java/com/esri/core/geometry/MultiPointImpl.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/MultiVertexGeometry.java b/src/main/java/com/esri/core/geometry/MultiVertexGeometry.java index 51948f74..668d6b33 100644 --- a/src/main/java/com/esri/core/geometry/MultiVertexGeometry.java +++ b/src/main/java/com/esri/core/geometry/MultiVertexGeometry.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/MultiVertexGeometryImpl.java b/src/main/java/com/esri/core/geometry/MultiVertexGeometryImpl.java index 9fbe1756..11ef7ed4 100644 --- a/src/main/java/com/esri/core/geometry/MultiVertexGeometryImpl.java +++ b/src/main/java/com/esri/core/geometry/MultiVertexGeometryImpl.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/NonSimpleResult.java b/src/main/java/com/esri/core/geometry/NonSimpleResult.java index ac79c687..87d95111 100644 --- a/src/main/java/com/esri/core/geometry/NonSimpleResult.java +++ b/src/main/java/com/esri/core/geometry/NonSimpleResult.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/NumberUtils.java b/src/main/java/com/esri/core/geometry/NumberUtils.java index 7de7558d..209df086 100644 --- a/src/main/java/com/esri/core/geometry/NumberUtils.java +++ b/src/main/java/com/esri/core/geometry/NumberUtils.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/OGCStructure.java b/src/main/java/com/esri/core/geometry/OGCStructure.java index 14dde139..9f0875b9 100644 --- a/src/main/java/com/esri/core/geometry/OGCStructure.java +++ b/src/main/java/com/esri/core/geometry/OGCStructure.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/ObjectCacheTable.java b/src/main/java/com/esri/core/geometry/ObjectCacheTable.java index b655ed70..54b18abd 100644 --- a/src/main/java/com/esri/core/geometry/ObjectCacheTable.java +++ b/src/main/java/com/esri/core/geometry/ObjectCacheTable.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/Operator.java b/src/main/java/com/esri/core/geometry/Operator.java index 41f69129..ffceb73f 100644 --- a/src/main/java/com/esri/core/geometry/Operator.java +++ b/src/main/java/com/esri/core/geometry/Operator.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/OperatorBoundary.java b/src/main/java/com/esri/core/geometry/OperatorBoundary.java index fd34bea1..e2a8fc1c 100644 --- a/src/main/java/com/esri/core/geometry/OperatorBoundary.java +++ b/src/main/java/com/esri/core/geometry/OperatorBoundary.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/OperatorBoundaryLocal.java b/src/main/java/com/esri/core/geometry/OperatorBoundaryLocal.java index 585ded53..0188affd 100644 --- a/src/main/java/com/esri/core/geometry/OperatorBoundaryLocal.java +++ b/src/main/java/com/esri/core/geometry/OperatorBoundaryLocal.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/OperatorBoundaryLocalCursor.java b/src/main/java/com/esri/core/geometry/OperatorBoundaryLocalCursor.java index fa01fe80..c06c3d53 100644 --- a/src/main/java/com/esri/core/geometry/OperatorBoundaryLocalCursor.java +++ b/src/main/java/com/esri/core/geometry/OperatorBoundaryLocalCursor.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/OperatorBuffer.java b/src/main/java/com/esri/core/geometry/OperatorBuffer.java index 2b8f02b4..cca44f54 100644 --- a/src/main/java/com/esri/core/geometry/OperatorBuffer.java +++ b/src/main/java/com/esri/core/geometry/OperatorBuffer.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/OperatorBufferCursor.java b/src/main/java/com/esri/core/geometry/OperatorBufferCursor.java index a779db3a..397ec89c 100644 --- a/src/main/java/com/esri/core/geometry/OperatorBufferCursor.java +++ b/src/main/java/com/esri/core/geometry/OperatorBufferCursor.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/OperatorBufferLocal.java b/src/main/java/com/esri/core/geometry/OperatorBufferLocal.java index 86f7e6bb..5e195d57 100644 --- a/src/main/java/com/esri/core/geometry/OperatorBufferLocal.java +++ b/src/main/java/com/esri/core/geometry/OperatorBufferLocal.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/OperatorClip.java b/src/main/java/com/esri/core/geometry/OperatorClip.java index 2dca9085..8b1907af 100644 --- a/src/main/java/com/esri/core/geometry/OperatorClip.java +++ b/src/main/java/com/esri/core/geometry/OperatorClip.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/OperatorClipCursor.java b/src/main/java/com/esri/core/geometry/OperatorClipCursor.java index 04c8ef7e..f45484fa 100644 --- a/src/main/java/com/esri/core/geometry/OperatorClipCursor.java +++ b/src/main/java/com/esri/core/geometry/OperatorClipCursor.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/OperatorClipLocal.java b/src/main/java/com/esri/core/geometry/OperatorClipLocal.java index 50aa6216..9f201322 100644 --- a/src/main/java/com/esri/core/geometry/OperatorClipLocal.java +++ b/src/main/java/com/esri/core/geometry/OperatorClipLocal.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/OperatorContains.java b/src/main/java/com/esri/core/geometry/OperatorContains.java index ccc21d6a..bbc4cf27 100644 --- a/src/main/java/com/esri/core/geometry/OperatorContains.java +++ b/src/main/java/com/esri/core/geometry/OperatorContains.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/OperatorContainsLocal.java b/src/main/java/com/esri/core/geometry/OperatorContainsLocal.java index d9ea5d24..3bc67286 100644 --- a/src/main/java/com/esri/core/geometry/OperatorContainsLocal.java +++ b/src/main/java/com/esri/core/geometry/OperatorContainsLocal.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/OperatorConvexHull.java b/src/main/java/com/esri/core/geometry/OperatorConvexHull.java index bd9ea418..a00b5a77 100644 --- a/src/main/java/com/esri/core/geometry/OperatorConvexHull.java +++ b/src/main/java/com/esri/core/geometry/OperatorConvexHull.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/OperatorConvexHullCursor.java b/src/main/java/com/esri/core/geometry/OperatorConvexHullCursor.java index 8c304a62..11b32738 100644 --- a/src/main/java/com/esri/core/geometry/OperatorConvexHullCursor.java +++ b/src/main/java/com/esri/core/geometry/OperatorConvexHullCursor.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/OperatorConvexHullLocal.java b/src/main/java/com/esri/core/geometry/OperatorConvexHullLocal.java index 155313a7..e314d34e 100644 --- a/src/main/java/com/esri/core/geometry/OperatorConvexHullLocal.java +++ b/src/main/java/com/esri/core/geometry/OperatorConvexHullLocal.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/OperatorCrosses.java b/src/main/java/com/esri/core/geometry/OperatorCrosses.java index 3a354bc5..0780879a 100644 --- a/src/main/java/com/esri/core/geometry/OperatorCrosses.java +++ b/src/main/java/com/esri/core/geometry/OperatorCrosses.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/OperatorCrossesLocal.java b/src/main/java/com/esri/core/geometry/OperatorCrossesLocal.java index d5b55abb..2c167080 100644 --- a/src/main/java/com/esri/core/geometry/OperatorCrossesLocal.java +++ b/src/main/java/com/esri/core/geometry/OperatorCrossesLocal.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/OperatorCut.java b/src/main/java/com/esri/core/geometry/OperatorCut.java index 59c5b589..762db7d6 100644 --- a/src/main/java/com/esri/core/geometry/OperatorCut.java +++ b/src/main/java/com/esri/core/geometry/OperatorCut.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/OperatorCutCursor.java b/src/main/java/com/esri/core/geometry/OperatorCutCursor.java index 266bdd99..cd5dae6c 100644 --- a/src/main/java/com/esri/core/geometry/OperatorCutCursor.java +++ b/src/main/java/com/esri/core/geometry/OperatorCutCursor.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/OperatorCutLocal.java b/src/main/java/com/esri/core/geometry/OperatorCutLocal.java index 98613301..75dccf2c 100644 --- a/src/main/java/com/esri/core/geometry/OperatorCutLocal.java +++ b/src/main/java/com/esri/core/geometry/OperatorCutLocal.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/OperatorDensifyByLength.java b/src/main/java/com/esri/core/geometry/OperatorDensifyByLength.java index c01ec80d..085dd397 100644 --- a/src/main/java/com/esri/core/geometry/OperatorDensifyByLength.java +++ b/src/main/java/com/esri/core/geometry/OperatorDensifyByLength.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/OperatorDensifyByLengthCursor.java b/src/main/java/com/esri/core/geometry/OperatorDensifyByLengthCursor.java index b634ff59..3a7267aa 100644 --- a/src/main/java/com/esri/core/geometry/OperatorDensifyByLengthCursor.java +++ b/src/main/java/com/esri/core/geometry/OperatorDensifyByLengthCursor.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/OperatorDensifyByLengthLocal.java b/src/main/java/com/esri/core/geometry/OperatorDensifyByLengthLocal.java index dbffbf4b..f6eb5e0a 100644 --- a/src/main/java/com/esri/core/geometry/OperatorDensifyByLengthLocal.java +++ b/src/main/java/com/esri/core/geometry/OperatorDensifyByLengthLocal.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/OperatorDifference.java b/src/main/java/com/esri/core/geometry/OperatorDifference.java index 11c1295a..5e4b40e6 100644 --- a/src/main/java/com/esri/core/geometry/OperatorDifference.java +++ b/src/main/java/com/esri/core/geometry/OperatorDifference.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/OperatorDifferenceCursor.java b/src/main/java/com/esri/core/geometry/OperatorDifferenceCursor.java index 021d1512..8c2419af 100644 --- a/src/main/java/com/esri/core/geometry/OperatorDifferenceCursor.java +++ b/src/main/java/com/esri/core/geometry/OperatorDifferenceCursor.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/OperatorDifferenceLocal.java b/src/main/java/com/esri/core/geometry/OperatorDifferenceLocal.java index 2121729b..02006bfc 100644 --- a/src/main/java/com/esri/core/geometry/OperatorDifferenceLocal.java +++ b/src/main/java/com/esri/core/geometry/OperatorDifferenceLocal.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/OperatorDisjoint.java b/src/main/java/com/esri/core/geometry/OperatorDisjoint.java index 9892661b..9404759f 100644 --- a/src/main/java/com/esri/core/geometry/OperatorDisjoint.java +++ b/src/main/java/com/esri/core/geometry/OperatorDisjoint.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/OperatorDisjointLocal.java b/src/main/java/com/esri/core/geometry/OperatorDisjointLocal.java index e65c0b3e..5bbaa1a2 100644 --- a/src/main/java/com/esri/core/geometry/OperatorDisjointLocal.java +++ b/src/main/java/com/esri/core/geometry/OperatorDisjointLocal.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/OperatorDistance.java b/src/main/java/com/esri/core/geometry/OperatorDistance.java index 206bbf93..d29c2c43 100644 --- a/src/main/java/com/esri/core/geometry/OperatorDistance.java +++ b/src/main/java/com/esri/core/geometry/OperatorDistance.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/OperatorDistanceLocal.java b/src/main/java/com/esri/core/geometry/OperatorDistanceLocal.java index 76ac6a35..9cb35c69 100644 --- a/src/main/java/com/esri/core/geometry/OperatorDistanceLocal.java +++ b/src/main/java/com/esri/core/geometry/OperatorDistanceLocal.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/OperatorEquals.java b/src/main/java/com/esri/core/geometry/OperatorEquals.java index 7fb1d5b7..2fb6d3ba 100644 --- a/src/main/java/com/esri/core/geometry/OperatorEquals.java +++ b/src/main/java/com/esri/core/geometry/OperatorEquals.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/OperatorEqualsLocal.java b/src/main/java/com/esri/core/geometry/OperatorEqualsLocal.java index d265c55a..d5edf302 100644 --- a/src/main/java/com/esri/core/geometry/OperatorEqualsLocal.java +++ b/src/main/java/com/esri/core/geometry/OperatorEqualsLocal.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/OperatorExportToESRIShape.java b/src/main/java/com/esri/core/geometry/OperatorExportToESRIShape.java index 840a1707..56aa754b 100644 --- a/src/main/java/com/esri/core/geometry/OperatorExportToESRIShape.java +++ b/src/main/java/com/esri/core/geometry/OperatorExportToESRIShape.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/OperatorExportToESRIShapeCursor.java b/src/main/java/com/esri/core/geometry/OperatorExportToESRIShapeCursor.java index 13d821ed..67d4dfce 100644 --- a/src/main/java/com/esri/core/geometry/OperatorExportToESRIShapeCursor.java +++ b/src/main/java/com/esri/core/geometry/OperatorExportToESRIShapeCursor.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/OperatorExportToESRIShapeLocal.java b/src/main/java/com/esri/core/geometry/OperatorExportToESRIShapeLocal.java index 9704054e..5b21e632 100644 --- a/src/main/java/com/esri/core/geometry/OperatorExportToESRIShapeLocal.java +++ b/src/main/java/com/esri/core/geometry/OperatorExportToESRIShapeLocal.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/OperatorExportToJson.java b/src/main/java/com/esri/core/geometry/OperatorExportToJson.java index 9b548d4f..32bfc078 100644 --- a/src/main/java/com/esri/core/geometry/OperatorExportToJson.java +++ b/src/main/java/com/esri/core/geometry/OperatorExportToJson.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/OperatorExportToJsonCursor.java b/src/main/java/com/esri/core/geometry/OperatorExportToJsonCursor.java index 85547549..b14fbb3e 100644 --- a/src/main/java/com/esri/core/geometry/OperatorExportToJsonCursor.java +++ b/src/main/java/com/esri/core/geometry/OperatorExportToJsonCursor.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/OperatorExportToJsonLocal.java b/src/main/java/com/esri/core/geometry/OperatorExportToJsonLocal.java index 5c5f819d..65554574 100644 --- a/src/main/java/com/esri/core/geometry/OperatorExportToJsonLocal.java +++ b/src/main/java/com/esri/core/geometry/OperatorExportToJsonLocal.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/OperatorExportToWkb.java b/src/main/java/com/esri/core/geometry/OperatorExportToWkb.java index d4418b2c..fba948cb 100644 --- a/src/main/java/com/esri/core/geometry/OperatorExportToWkb.java +++ b/src/main/java/com/esri/core/geometry/OperatorExportToWkb.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/OperatorExportToWkbLocal.java b/src/main/java/com/esri/core/geometry/OperatorExportToWkbLocal.java index 929e9168..b87b6a8f 100644 --- a/src/main/java/com/esri/core/geometry/OperatorExportToWkbLocal.java +++ b/src/main/java/com/esri/core/geometry/OperatorExportToWkbLocal.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/OperatorExportToWkt.java b/src/main/java/com/esri/core/geometry/OperatorExportToWkt.java index 6e893b14..10eab9b2 100644 --- a/src/main/java/com/esri/core/geometry/OperatorExportToWkt.java +++ b/src/main/java/com/esri/core/geometry/OperatorExportToWkt.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/OperatorExportToWktLocal.java b/src/main/java/com/esri/core/geometry/OperatorExportToWktLocal.java index 5fe4398f..0b3a4466 100644 --- a/src/main/java/com/esri/core/geometry/OperatorExportToWktLocal.java +++ b/src/main/java/com/esri/core/geometry/OperatorExportToWktLocal.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/OperatorFactory.java b/src/main/java/com/esri/core/geometry/OperatorFactory.java index 680cdfee..66e6030a 100644 --- a/src/main/java/com/esri/core/geometry/OperatorFactory.java +++ b/src/main/java/com/esri/core/geometry/OperatorFactory.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/OperatorFactoryLocal.java b/src/main/java/com/esri/core/geometry/OperatorFactoryLocal.java index a77628d3..f07cea96 100644 --- a/src/main/java/com/esri/core/geometry/OperatorFactoryLocal.java +++ b/src/main/java/com/esri/core/geometry/OperatorFactoryLocal.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/OperatorGeneralize.java b/src/main/java/com/esri/core/geometry/OperatorGeneralize.java index af6223bd..ce8c4703 100644 --- a/src/main/java/com/esri/core/geometry/OperatorGeneralize.java +++ b/src/main/java/com/esri/core/geometry/OperatorGeneralize.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/OperatorGeneralizeCursor.java b/src/main/java/com/esri/core/geometry/OperatorGeneralizeCursor.java index eb860616..2b1d5ba1 100644 --- a/src/main/java/com/esri/core/geometry/OperatorGeneralizeCursor.java +++ b/src/main/java/com/esri/core/geometry/OperatorGeneralizeCursor.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/OperatorGeneralizeLocal.java b/src/main/java/com/esri/core/geometry/OperatorGeneralizeLocal.java index cf759c4b..c46e0f8c 100644 --- a/src/main/java/com/esri/core/geometry/OperatorGeneralizeLocal.java +++ b/src/main/java/com/esri/core/geometry/OperatorGeneralizeLocal.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/OperatorGeodesicBuffer.java b/src/main/java/com/esri/core/geometry/OperatorGeodesicBuffer.java index fd0c9bdf..4e7068f7 100644 --- a/src/main/java/com/esri/core/geometry/OperatorGeodesicBuffer.java +++ b/src/main/java/com/esri/core/geometry/OperatorGeodesicBuffer.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/OperatorGeodesicBufferLocal.java b/src/main/java/com/esri/core/geometry/OperatorGeodesicBufferLocal.java index b3475eb9..7b9f97ba 100644 --- a/src/main/java/com/esri/core/geometry/OperatorGeodesicBufferLocal.java +++ b/src/main/java/com/esri/core/geometry/OperatorGeodesicBufferLocal.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/OperatorGeodeticArea.java b/src/main/java/com/esri/core/geometry/OperatorGeodeticArea.java index 3e45ed36..ee2e0df7 100644 --- a/src/main/java/com/esri/core/geometry/OperatorGeodeticArea.java +++ b/src/main/java/com/esri/core/geometry/OperatorGeodeticArea.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/OperatorGeodeticAreaLocal.java b/src/main/java/com/esri/core/geometry/OperatorGeodeticAreaLocal.java index 441f0ec0..12a3f805 100644 --- a/src/main/java/com/esri/core/geometry/OperatorGeodeticAreaLocal.java +++ b/src/main/java/com/esri/core/geometry/OperatorGeodeticAreaLocal.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/OperatorGeodeticDensifyByLength.java b/src/main/java/com/esri/core/geometry/OperatorGeodeticDensifyByLength.java index d9a660e6..5efcb134 100644 --- a/src/main/java/com/esri/core/geometry/OperatorGeodeticDensifyByLength.java +++ b/src/main/java/com/esri/core/geometry/OperatorGeodeticDensifyByLength.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/OperatorGeodeticDensifyLocal.java b/src/main/java/com/esri/core/geometry/OperatorGeodeticDensifyLocal.java index 011bc4b2..f704c395 100644 --- a/src/main/java/com/esri/core/geometry/OperatorGeodeticDensifyLocal.java +++ b/src/main/java/com/esri/core/geometry/OperatorGeodeticDensifyLocal.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/OperatorGeodeticLength.java b/src/main/java/com/esri/core/geometry/OperatorGeodeticLength.java index 47f79482..268dbc5c 100644 --- a/src/main/java/com/esri/core/geometry/OperatorGeodeticLength.java +++ b/src/main/java/com/esri/core/geometry/OperatorGeodeticLength.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/OperatorGeodeticLengthLocal.java b/src/main/java/com/esri/core/geometry/OperatorGeodeticLengthLocal.java index e878027d..306e3c74 100644 --- a/src/main/java/com/esri/core/geometry/OperatorGeodeticLengthLocal.java +++ b/src/main/java/com/esri/core/geometry/OperatorGeodeticLengthLocal.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/OperatorImportFromESRIShape.java b/src/main/java/com/esri/core/geometry/OperatorImportFromESRIShape.java index be7bf6cc..75bc1375 100644 --- a/src/main/java/com/esri/core/geometry/OperatorImportFromESRIShape.java +++ b/src/main/java/com/esri/core/geometry/OperatorImportFromESRIShape.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/OperatorImportFromESRIShapeCursor.java b/src/main/java/com/esri/core/geometry/OperatorImportFromESRIShapeCursor.java index 982abe03..2186b4e4 100644 --- a/src/main/java/com/esri/core/geometry/OperatorImportFromESRIShapeCursor.java +++ b/src/main/java/com/esri/core/geometry/OperatorImportFromESRIShapeCursor.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/OperatorImportFromESRIShapeLocal.java b/src/main/java/com/esri/core/geometry/OperatorImportFromESRIShapeLocal.java index 4bcdd3a0..16c2013f 100644 --- a/src/main/java/com/esri/core/geometry/OperatorImportFromESRIShapeLocal.java +++ b/src/main/java/com/esri/core/geometry/OperatorImportFromESRIShapeLocal.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/OperatorImportFromGeoJson.java b/src/main/java/com/esri/core/geometry/OperatorImportFromGeoJson.java index 9032c913..2c0c0287 100644 --- a/src/main/java/com/esri/core/geometry/OperatorImportFromGeoJson.java +++ b/src/main/java/com/esri/core/geometry/OperatorImportFromGeoJson.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/OperatorImportFromGeoJsonLocal.java b/src/main/java/com/esri/core/geometry/OperatorImportFromGeoJsonLocal.java index b5d0c788..8ed6b64e 100644 --- a/src/main/java/com/esri/core/geometry/OperatorImportFromGeoJsonLocal.java +++ b/src/main/java/com/esri/core/geometry/OperatorImportFromGeoJsonLocal.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/OperatorImportFromJson.java b/src/main/java/com/esri/core/geometry/OperatorImportFromJson.java index c05438eb..6a88d05c 100644 --- a/src/main/java/com/esri/core/geometry/OperatorImportFromJson.java +++ b/src/main/java/com/esri/core/geometry/OperatorImportFromJson.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/OperatorImportFromJsonCursor.java b/src/main/java/com/esri/core/geometry/OperatorImportFromJsonCursor.java index cc56169b..56f79a87 100644 --- a/src/main/java/com/esri/core/geometry/OperatorImportFromJsonCursor.java +++ b/src/main/java/com/esri/core/geometry/OperatorImportFromJsonCursor.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/OperatorImportFromJsonLocal.java b/src/main/java/com/esri/core/geometry/OperatorImportFromJsonLocal.java index f92842fb..4e41a491 100644 --- a/src/main/java/com/esri/core/geometry/OperatorImportFromJsonLocal.java +++ b/src/main/java/com/esri/core/geometry/OperatorImportFromJsonLocal.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/OperatorImportFromWkb.java b/src/main/java/com/esri/core/geometry/OperatorImportFromWkb.java index 9550e4cf..a80e3bd9 100644 --- a/src/main/java/com/esri/core/geometry/OperatorImportFromWkb.java +++ b/src/main/java/com/esri/core/geometry/OperatorImportFromWkb.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/OperatorImportFromWkbLocal.java b/src/main/java/com/esri/core/geometry/OperatorImportFromWkbLocal.java index 6fa9c01a..1fa9685c 100644 --- a/src/main/java/com/esri/core/geometry/OperatorImportFromWkbLocal.java +++ b/src/main/java/com/esri/core/geometry/OperatorImportFromWkbLocal.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/OperatorImportFromWkt.java b/src/main/java/com/esri/core/geometry/OperatorImportFromWkt.java index b64c70ac..60b0460d 100644 --- a/src/main/java/com/esri/core/geometry/OperatorImportFromWkt.java +++ b/src/main/java/com/esri/core/geometry/OperatorImportFromWkt.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/OperatorImportFromWktLocal.java b/src/main/java/com/esri/core/geometry/OperatorImportFromWktLocal.java index 3a9bb232..3b6a74f0 100644 --- a/src/main/java/com/esri/core/geometry/OperatorImportFromWktLocal.java +++ b/src/main/java/com/esri/core/geometry/OperatorImportFromWktLocal.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/OperatorInternalRelationUtils.java b/src/main/java/com/esri/core/geometry/OperatorInternalRelationUtils.java index 7f78b59d..5c61ad20 100644 --- a/src/main/java/com/esri/core/geometry/OperatorInternalRelationUtils.java +++ b/src/main/java/com/esri/core/geometry/OperatorInternalRelationUtils.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/OperatorIntersection.java b/src/main/java/com/esri/core/geometry/OperatorIntersection.java index c1230446..2f23f11c 100644 --- a/src/main/java/com/esri/core/geometry/OperatorIntersection.java +++ b/src/main/java/com/esri/core/geometry/OperatorIntersection.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/OperatorIntersectionCursor.java b/src/main/java/com/esri/core/geometry/OperatorIntersectionCursor.java index 7706d83e..edcf0313 100644 --- a/src/main/java/com/esri/core/geometry/OperatorIntersectionCursor.java +++ b/src/main/java/com/esri/core/geometry/OperatorIntersectionCursor.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/OperatorIntersectionLocal.java b/src/main/java/com/esri/core/geometry/OperatorIntersectionLocal.java index bb13dcf3..71d2d9e6 100644 --- a/src/main/java/com/esri/core/geometry/OperatorIntersectionLocal.java +++ b/src/main/java/com/esri/core/geometry/OperatorIntersectionLocal.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/OperatorIntersects.java b/src/main/java/com/esri/core/geometry/OperatorIntersects.java index d7984fa8..ace6c277 100644 --- a/src/main/java/com/esri/core/geometry/OperatorIntersects.java +++ b/src/main/java/com/esri/core/geometry/OperatorIntersects.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/OperatorIntersectsLocal.java b/src/main/java/com/esri/core/geometry/OperatorIntersectsLocal.java index 5a492095..1abefd6a 100644 --- a/src/main/java/com/esri/core/geometry/OperatorIntersectsLocal.java +++ b/src/main/java/com/esri/core/geometry/OperatorIntersectsLocal.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/OperatorOffset.java b/src/main/java/com/esri/core/geometry/OperatorOffset.java index 910ccd8a..7b4f3673 100644 --- a/src/main/java/com/esri/core/geometry/OperatorOffset.java +++ b/src/main/java/com/esri/core/geometry/OperatorOffset.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/OperatorOffsetCursor.java b/src/main/java/com/esri/core/geometry/OperatorOffsetCursor.java index 5f27c667..76f1f982 100644 --- a/src/main/java/com/esri/core/geometry/OperatorOffsetCursor.java +++ b/src/main/java/com/esri/core/geometry/OperatorOffsetCursor.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/OperatorOffsetLocal.java b/src/main/java/com/esri/core/geometry/OperatorOffsetLocal.java index 7006b42d..06c7f3c6 100644 --- a/src/main/java/com/esri/core/geometry/OperatorOffsetLocal.java +++ b/src/main/java/com/esri/core/geometry/OperatorOffsetLocal.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/OperatorOverlaps.java b/src/main/java/com/esri/core/geometry/OperatorOverlaps.java index d1a362b4..c7b3b207 100644 --- a/src/main/java/com/esri/core/geometry/OperatorOverlaps.java +++ b/src/main/java/com/esri/core/geometry/OperatorOverlaps.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/OperatorOverlapsLocal.java b/src/main/java/com/esri/core/geometry/OperatorOverlapsLocal.java index f767cafd..f9114687 100644 --- a/src/main/java/com/esri/core/geometry/OperatorOverlapsLocal.java +++ b/src/main/java/com/esri/core/geometry/OperatorOverlapsLocal.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/OperatorProject.java b/src/main/java/com/esri/core/geometry/OperatorProject.java index da2a1712..db74ae12 100644 --- a/src/main/java/com/esri/core/geometry/OperatorProject.java +++ b/src/main/java/com/esri/core/geometry/OperatorProject.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/OperatorProjectLocal.java b/src/main/java/com/esri/core/geometry/OperatorProjectLocal.java index 78598ca6..ae753433 100644 --- a/src/main/java/com/esri/core/geometry/OperatorProjectLocal.java +++ b/src/main/java/com/esri/core/geometry/OperatorProjectLocal.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/OperatorProximity2D.java b/src/main/java/com/esri/core/geometry/OperatorProximity2D.java index 80327e18..7449fc95 100644 --- a/src/main/java/com/esri/core/geometry/OperatorProximity2D.java +++ b/src/main/java/com/esri/core/geometry/OperatorProximity2D.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/OperatorProximity2DLocal.java b/src/main/java/com/esri/core/geometry/OperatorProximity2DLocal.java index 363c29a4..d62f514d 100644 --- a/src/main/java/com/esri/core/geometry/OperatorProximity2DLocal.java +++ b/src/main/java/com/esri/core/geometry/OperatorProximity2DLocal.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/OperatorRelate.java b/src/main/java/com/esri/core/geometry/OperatorRelate.java index b1e435bf..d5542990 100644 --- a/src/main/java/com/esri/core/geometry/OperatorRelate.java +++ b/src/main/java/com/esri/core/geometry/OperatorRelate.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/OperatorRelateLocal.java b/src/main/java/com/esri/core/geometry/OperatorRelateLocal.java index c83fbee1..88af8ad0 100644 --- a/src/main/java/com/esri/core/geometry/OperatorRelateLocal.java +++ b/src/main/java/com/esri/core/geometry/OperatorRelateLocal.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/OperatorShapePreservingDensify.java b/src/main/java/com/esri/core/geometry/OperatorShapePreservingDensify.java index 3aa5ed8e..3a4a31ce 100644 --- a/src/main/java/com/esri/core/geometry/OperatorShapePreservingDensify.java +++ b/src/main/java/com/esri/core/geometry/OperatorShapePreservingDensify.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/OperatorShapePreservingDensifyLocal.java b/src/main/java/com/esri/core/geometry/OperatorShapePreservingDensifyLocal.java index 01bcf3a7..68fd0a5c 100644 --- a/src/main/java/com/esri/core/geometry/OperatorShapePreservingDensifyLocal.java +++ b/src/main/java/com/esri/core/geometry/OperatorShapePreservingDensifyLocal.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/OperatorSimpleRelation.java b/src/main/java/com/esri/core/geometry/OperatorSimpleRelation.java index f4afeff7..1a08cf6d 100644 --- a/src/main/java/com/esri/core/geometry/OperatorSimpleRelation.java +++ b/src/main/java/com/esri/core/geometry/OperatorSimpleRelation.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/OperatorSimplify.java b/src/main/java/com/esri/core/geometry/OperatorSimplify.java index 0e14b2f9..3e9beca9 100644 --- a/src/main/java/com/esri/core/geometry/OperatorSimplify.java +++ b/src/main/java/com/esri/core/geometry/OperatorSimplify.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/OperatorSimplifyCursor.java b/src/main/java/com/esri/core/geometry/OperatorSimplifyCursor.java index 4be821a8..19c920df 100644 --- a/src/main/java/com/esri/core/geometry/OperatorSimplifyCursor.java +++ b/src/main/java/com/esri/core/geometry/OperatorSimplifyCursor.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/OperatorSimplifyCursorOGC.java b/src/main/java/com/esri/core/geometry/OperatorSimplifyCursorOGC.java index 277724d6..15e726af 100644 --- a/src/main/java/com/esri/core/geometry/OperatorSimplifyCursorOGC.java +++ b/src/main/java/com/esri/core/geometry/OperatorSimplifyCursorOGC.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/OperatorSimplifyLocal.java b/src/main/java/com/esri/core/geometry/OperatorSimplifyLocal.java index 6cb16dc1..4edc081e 100644 --- a/src/main/java/com/esri/core/geometry/OperatorSimplifyLocal.java +++ b/src/main/java/com/esri/core/geometry/OperatorSimplifyLocal.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/OperatorSimplifyLocalHelper.java b/src/main/java/com/esri/core/geometry/OperatorSimplifyLocalHelper.java index 62b35c26..b0bd4dd8 100644 --- a/src/main/java/com/esri/core/geometry/OperatorSimplifyLocalHelper.java +++ b/src/main/java/com/esri/core/geometry/OperatorSimplifyLocalHelper.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/OperatorSimplifyLocalOGC.java b/src/main/java/com/esri/core/geometry/OperatorSimplifyLocalOGC.java index aa9c5a9f..6c725abb 100644 --- a/src/main/java/com/esri/core/geometry/OperatorSimplifyLocalOGC.java +++ b/src/main/java/com/esri/core/geometry/OperatorSimplifyLocalOGC.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/OperatorSimplifyOGC.java b/src/main/java/com/esri/core/geometry/OperatorSimplifyOGC.java index d433f0c7..c04ba0c4 100644 --- a/src/main/java/com/esri/core/geometry/OperatorSimplifyOGC.java +++ b/src/main/java/com/esri/core/geometry/OperatorSimplifyOGC.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/OperatorSymmetricDifference.java b/src/main/java/com/esri/core/geometry/OperatorSymmetricDifference.java index bb914e77..ea84ce0c 100644 --- a/src/main/java/com/esri/core/geometry/OperatorSymmetricDifference.java +++ b/src/main/java/com/esri/core/geometry/OperatorSymmetricDifference.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/OperatorSymmetricDifferenceCursor.java b/src/main/java/com/esri/core/geometry/OperatorSymmetricDifferenceCursor.java index 92450ce3..3d265593 100644 --- a/src/main/java/com/esri/core/geometry/OperatorSymmetricDifferenceCursor.java +++ b/src/main/java/com/esri/core/geometry/OperatorSymmetricDifferenceCursor.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/OperatorSymmetricDifferenceLocal.java b/src/main/java/com/esri/core/geometry/OperatorSymmetricDifferenceLocal.java index e5de43db..e505a8dd 100644 --- a/src/main/java/com/esri/core/geometry/OperatorSymmetricDifferenceLocal.java +++ b/src/main/java/com/esri/core/geometry/OperatorSymmetricDifferenceLocal.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/OperatorTouches.java b/src/main/java/com/esri/core/geometry/OperatorTouches.java index a98de285..6ae67f38 100644 --- a/src/main/java/com/esri/core/geometry/OperatorTouches.java +++ b/src/main/java/com/esri/core/geometry/OperatorTouches.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/OperatorTouchesLocal.java b/src/main/java/com/esri/core/geometry/OperatorTouchesLocal.java index f18f3541..48d32d89 100644 --- a/src/main/java/com/esri/core/geometry/OperatorTouchesLocal.java +++ b/src/main/java/com/esri/core/geometry/OperatorTouchesLocal.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/OperatorUnion.java b/src/main/java/com/esri/core/geometry/OperatorUnion.java index 69340bd2..8a0a8601 100644 --- a/src/main/java/com/esri/core/geometry/OperatorUnion.java +++ b/src/main/java/com/esri/core/geometry/OperatorUnion.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/OperatorUnionCursor.java b/src/main/java/com/esri/core/geometry/OperatorUnionCursor.java index 53164ef2..0ddd88b8 100644 --- a/src/main/java/com/esri/core/geometry/OperatorUnionCursor.java +++ b/src/main/java/com/esri/core/geometry/OperatorUnionCursor.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -183,26 +183,23 @@ private boolean step_(){ } if (m_added_geoms > 0) { - for (int dim = 0; dim <= m_max_dimension; dim++) - { - while (m_dim_geom_counts[dim] > 1) - { - ArrayList batch_to_union = collect_geometries_to_union(dim); - boolean serial_execution = true; - if (serial_execution) - { - if (batch_to_union.size() != 0) - { - Geometry geomRes = TopologicalOperations.dissolveDirty(batch_to_union, m_spatial_reference, m_progress_tracker); - add_geom(dim, true, geomRes); - } - else - { - break; - } - } - } - } + for (int dim = 0; dim <= m_max_dimension; dim++) { + while (m_dim_geom_counts[dim] > 1) { + ArrayList batch_to_union = collect_geometries_to_union(dim); + boolean serial_execution = true; + if (serial_execution) { + if (batch_to_union.size() != 0) { + Geometry geomRes = TopologicalOperations + .dissolveDirty(batch_to_union, + m_spatial_reference, + m_progress_tracker); + add_geom(dim, true, geomRes); + } else { + break; + } + } + } + } } return m_b_done; diff --git a/src/main/java/com/esri/core/geometry/OperatorUnionLocal.java b/src/main/java/com/esri/core/geometry/OperatorUnionLocal.java index e3cf5ea7..1b723282 100644 --- a/src/main/java/com/esri/core/geometry/OperatorUnionLocal.java +++ b/src/main/java/com/esri/core/geometry/OperatorUnionLocal.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/OperatorWithin.java b/src/main/java/com/esri/core/geometry/OperatorWithin.java index 9032718f..00a0c15f 100644 --- a/src/main/java/com/esri/core/geometry/OperatorWithin.java +++ b/src/main/java/com/esri/core/geometry/OperatorWithin.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/OperatorWithinLocal.java b/src/main/java/com/esri/core/geometry/OperatorWithinLocal.java index 9958ff90..a370a8fe 100644 --- a/src/main/java/com/esri/core/geometry/OperatorWithinLocal.java +++ b/src/main/java/com/esri/core/geometry/OperatorWithinLocal.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/PairwiseIntersectorImpl.java b/src/main/java/com/esri/core/geometry/PairwiseIntersectorImpl.java index 465c78cd..cb3c5c00 100644 --- a/src/main/java/com/esri/core/geometry/PairwiseIntersectorImpl.java +++ b/src/main/java/com/esri/core/geometry/PairwiseIntersectorImpl.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/PathFlags.java b/src/main/java/com/esri/core/geometry/PathFlags.java index b163559e..1fa09c35 100644 --- a/src/main/java/com/esri/core/geometry/PathFlags.java +++ b/src/main/java/com/esri/core/geometry/PathFlags.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/PeDouble.java b/src/main/java/com/esri/core/geometry/PeDouble.java index 90bf9abf..fd33af49 100644 --- a/src/main/java/com/esri/core/geometry/PeDouble.java +++ b/src/main/java/com/esri/core/geometry/PeDouble.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/PlaneSweepCrackerHelper.java b/src/main/java/com/esri/core/geometry/PlaneSweepCrackerHelper.java index 41db31be..4bdf00cc 100644 --- a/src/main/java/com/esri/core/geometry/PlaneSweepCrackerHelper.java +++ b/src/main/java/com/esri/core/geometry/PlaneSweepCrackerHelper.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/Point.java b/src/main/java/com/esri/core/geometry/Point.java index 9bbf20a4..0f553257 100644 --- a/src/main/java/com/esri/core/geometry/Point.java +++ b/src/main/java/com/esri/core/geometry/Point.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/Point2D.java b/src/main/java/com/esri/core/geometry/Point2D.java index 5525626b..f9fa71ab 100644 --- a/src/main/java/com/esri/core/geometry/Point2D.java +++ b/src/main/java/com/esri/core/geometry/Point2D.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/Point3D.java b/src/main/java/com/esri/core/geometry/Point3D.java index 5f509019..a8316ff3 100644 --- a/src/main/java/com/esri/core/geometry/Point3D.java +++ b/src/main/java/com/esri/core/geometry/Point3D.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/PointInPolygonHelper.java b/src/main/java/com/esri/core/geometry/PointInPolygonHelper.java index 8664ed8d..801b2b81 100644 --- a/src/main/java/com/esri/core/geometry/PointInPolygonHelper.java +++ b/src/main/java/com/esri/core/geometry/PointInPolygonHelper.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/Polygon.java b/src/main/java/com/esri/core/geometry/Polygon.java index 0c07f6f4..6d2a97f4 100644 --- a/src/main/java/com/esri/core/geometry/Polygon.java +++ b/src/main/java/com/esri/core/geometry/Polygon.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -158,6 +158,7 @@ public interface FillRule { *Fill rule for the polygon that defines the interior of the self intersecting polygon. It affects the Simplify operation. *Can be use by drawing code to pass around the fill rule of graphic path. *This property is not persisted in any format yet. + *See also Polygon.FillRule. */ public void setFillRule(int rule) { m_impl.setFillRule(rule); @@ -168,6 +169,7 @@ public void setFillRule(int rule) { *Changing the fill rule on the polygon that has no self intersections has no physical effect. *Can be use by drawing code to pass around the fill rule of graphic path. *This property is not persisted in any format yet. + *See also Polygon.FillRule. */ public int getFillRule() { return m_impl.getFillRule(); diff --git a/src/main/java/com/esri/core/geometry/PolygonUtils.java b/src/main/java/com/esri/core/geometry/PolygonUtils.java index 9a6b52f1..26701d30 100644 --- a/src/main/java/com/esri/core/geometry/PolygonUtils.java +++ b/src/main/java/com/esri/core/geometry/PolygonUtils.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/Polyline.java b/src/main/java/com/esri/core/geometry/Polyline.java index 1b648f6c..b95e9f81 100644 --- a/src/main/java/com/esri/core/geometry/Polyline.java +++ b/src/main/java/com/esri/core/geometry/Polyline.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/PolylinePath.java b/src/main/java/com/esri/core/geometry/PolylinePath.java index b3c06dc8..6d3342da 100644 --- a/src/main/java/com/esri/core/geometry/PolylinePath.java +++ b/src/main/java/com/esri/core/geometry/PolylinePath.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/ProgressTracker.java b/src/main/java/com/esri/core/geometry/ProgressTracker.java index d4153375..09933688 100644 --- a/src/main/java/com/esri/core/geometry/ProgressTracker.java +++ b/src/main/java/com/esri/core/geometry/ProgressTracker.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/ProjectionTransformation.java b/src/main/java/com/esri/core/geometry/ProjectionTransformation.java index 4f2aedde..858bc03c 100644 --- a/src/main/java/com/esri/core/geometry/ProjectionTransformation.java +++ b/src/main/java/com/esri/core/geometry/ProjectionTransformation.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/Proximity2DResult.java b/src/main/java/com/esri/core/geometry/Proximity2DResult.java index 04ac528d..0e1efe71 100644 --- a/src/main/java/com/esri/core/geometry/Proximity2DResult.java +++ b/src/main/java/com/esri/core/geometry/Proximity2DResult.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/Proximity2DResultComparator.java b/src/main/java/com/esri/core/geometry/Proximity2DResultComparator.java index 1be0cfbb..0404aa80 100644 --- a/src/main/java/com/esri/core/geometry/Proximity2DResultComparator.java +++ b/src/main/java/com/esri/core/geometry/Proximity2DResultComparator.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/QuadTree.java b/src/main/java/com/esri/core/geometry/QuadTree.java index 59f976d5..9f6be163 100644 --- a/src/main/java/com/esri/core/geometry/QuadTree.java +++ b/src/main/java/com/esri/core/geometry/QuadTree.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/QuadTreeImpl.java b/src/main/java/com/esri/core/geometry/QuadTreeImpl.java index 3b9afd77..cbef824b 100644 --- a/src/main/java/com/esri/core/geometry/QuadTreeImpl.java +++ b/src/main/java/com/esri/core/geometry/QuadTreeImpl.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/RasterizedGeometry2D.java b/src/main/java/com/esri/core/geometry/RasterizedGeometry2D.java index 94f187d9..46ccd5c6 100644 --- a/src/main/java/com/esri/core/geometry/RasterizedGeometry2D.java +++ b/src/main/java/com/esri/core/geometry/RasterizedGeometry2D.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/RasterizedGeometry2DImpl.java b/src/main/java/com/esri/core/geometry/RasterizedGeometry2DImpl.java index 16e3dd1c..556a9fe0 100644 --- a/src/main/java/com/esri/core/geometry/RasterizedGeometry2DImpl.java +++ b/src/main/java/com/esri/core/geometry/RasterizedGeometry2DImpl.java @@ -67,22 +67,26 @@ public ScanCallbackImpl(int[] bitmap, int scanlineWidth) { } public void setColor(SimpleRasterizer rasterizer, int color) { + if (m_color != color) + rasterizer.flush(); + m_color = color;// set new color } @Override - public void drawScan(int y, int x, int numPxls) { - int x0 = x; - int x1 = x + numPxls; - if (x1 > m_width) - x1 = m_width; - - int scanlineStart = y * m_scanlineWidth; - for (int xx = x0; xx < x1; xx++) { - m_bitmap[scanlineStart + (xx >> 4)] |= m_color << ((xx & 15) * 2);// 2 - // bit - // per - // color + public void drawScan(int[] scans, int scanCount3) { + for (int i = 0; i < scanCount3; ) { + int x0 = scans[i++]; + int x1 = scans[i++]; + int y = scans[i++]; + + int scanlineStart = y * m_scanlineWidth; + for (int xx = x0; xx < x1; xx++) { + m_bitmap[scanlineStart + (xx >> 4)] |= m_color << ((xx & 15) * 2);// 2 + // bit + // per + // color + } } } } @@ -122,33 +126,7 @@ void fillConvexPolygon(SimpleRasterizer rasterizer, Point2D[] fan, int len) { } void fillEnvelope(SimpleRasterizer rasterizer, Envelope2D envIn) { - /*if (!m_identity) { - Point2D fan[] = new Point2D[4]; - envIn.queryCorners(fan); - fillConvexPolygon(rasterizer, fan, 4); - return; - }*/ - - Envelope2D env = new Envelope2D(0, 0, m_width, m_width); - if (!env.intersect(envIn)) - return; - - int x0 = (int) Math.round(env.xmin); - int x = (int) Math.round(env.xmax); - - int xn = NumberUtils.snap(x0, 0, m_width); - int xm = NumberUtils.snap(x, 0, m_width); - if (x0 < m_width && xn < xm) { - int y0 = (int) Math.round(env.ymin); - int y1 = (int) Math.round(env.ymax); - y0 = NumberUtils.snap(y0, 0, m_width); - y1 = NumberUtils.snap(y1, 0, m_width); - if (y0 < m_width) { - for (int y = y0; y < y1; y++) { - m_rasterizer.callback_.drawScan(y, xn, xm - xn); - } - } - } + rasterizer.fillEnvelope(envIn); } void strokeDrawPolyPath(SimpleRasterizer rasterizer, @@ -253,8 +231,7 @@ static RasterizedGeometry2DImpl createImpl(Geometry geom, double toleranceXY, int rasterSizeBytes) { RasterizedGeometry2DImpl rgImpl = new RasterizedGeometry2DImpl(geom, toleranceXY, rasterSizeBytes); - rgImpl.init((MultiVertexGeometryImpl) geom._getImpl(), toleranceXY, - rasterSizeBytes); + return rgImpl; } @@ -267,7 +244,6 @@ static RasterizedGeometry2DImpl createImpl(MultiVertexGeometryImpl geom, double toleranceXY, int rasterSizeBytes) { RasterizedGeometry2DImpl rgImpl = new RasterizedGeometry2DImpl(geom, toleranceXY, rasterSizeBytes); - rgImpl.init(geom, toleranceXY, rasterSizeBytes); return rgImpl; } @@ -360,6 +336,7 @@ void init(MultiVertexGeometryImpl geom, double toleranceXY, m_x0 = m_transform.xd; m_y0 = m_transform.yd; buildLevels(); + //dbgSaveToBitmap("c:/temp/_dbg.bmp"); } boolean tryRenderAsSmallEnvelope_(Envelope2D env) { @@ -389,6 +366,7 @@ boolean tryRenderAsSmallEnvelope_(Envelope2D env) { } void buildLevels() { + m_rasterizer.flush(); int iStart = 0; int iStartNext = m_width * m_scanLineSize; int width = m_width; diff --git a/src/main/java/com/esri/core/geometry/RelationalOperations.java b/src/main/java/com/esri/core/geometry/RelationalOperations.java index 34c84cf2..0b73561a 100644 --- a/src/main/java/com/esri/core/geometry/RelationalOperations.java +++ b/src/main/java/com/esri/core/geometry/RelationalOperations.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/RelationalOperationsMatrix.java b/src/main/java/com/esri/core/geometry/RelationalOperationsMatrix.java index 7660fc4e..1d93f5c8 100644 --- a/src/main/java/com/esri/core/geometry/RelationalOperationsMatrix.java +++ b/src/main/java/com/esri/core/geometry/RelationalOperationsMatrix.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/RingOrientationFixer.java b/src/main/java/com/esri/core/geometry/RingOrientationFixer.java index 970ebe3e..84fa66b5 100644 --- a/src/main/java/com/esri/core/geometry/RingOrientationFixer.java +++ b/src/main/java/com/esri/core/geometry/RingOrientationFixer.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/Segment.java b/src/main/java/com/esri/core/geometry/Segment.java index b80a4236..be94b723 100644 --- a/src/main/java/com/esri/core/geometry/Segment.java +++ b/src/main/java/com/esri/core/geometry/Segment.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/SegmentBuffer.java b/src/main/java/com/esri/core/geometry/SegmentBuffer.java index 17df913d..a5d85076 100644 --- a/src/main/java/com/esri/core/geometry/SegmentBuffer.java +++ b/src/main/java/com/esri/core/geometry/SegmentBuffer.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/SegmentFlags.java b/src/main/java/com/esri/core/geometry/SegmentFlags.java index ab3a37e9..7f0b5da1 100644 --- a/src/main/java/com/esri/core/geometry/SegmentFlags.java +++ b/src/main/java/com/esri/core/geometry/SegmentFlags.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/SegmentIntersector.java b/src/main/java/com/esri/core/geometry/SegmentIntersector.java index ffbadb18..2bf63af3 100644 --- a/src/main/java/com/esri/core/geometry/SegmentIntersector.java +++ b/src/main/java/com/esri/core/geometry/SegmentIntersector.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/SegmentIterator.java b/src/main/java/com/esri/core/geometry/SegmentIterator.java index b563ccdd..8312e028 100644 --- a/src/main/java/com/esri/core/geometry/SegmentIterator.java +++ b/src/main/java/com/esri/core/geometry/SegmentIterator.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/SegmentIteratorImpl.java b/src/main/java/com/esri/core/geometry/SegmentIteratorImpl.java index c5f97d19..03761997 100644 --- a/src/main/java/com/esri/core/geometry/SegmentIteratorImpl.java +++ b/src/main/java/com/esri/core/geometry/SegmentIteratorImpl.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/ShapeExportFlags.java b/src/main/java/com/esri/core/geometry/ShapeExportFlags.java index 03cc9b3f..ac04ba51 100644 --- a/src/main/java/com/esri/core/geometry/ShapeExportFlags.java +++ b/src/main/java/com/esri/core/geometry/ShapeExportFlags.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/ShapeImportFlags.java b/src/main/java/com/esri/core/geometry/ShapeImportFlags.java index 1b2742c9..876a983d 100644 --- a/src/main/java/com/esri/core/geometry/ShapeImportFlags.java +++ b/src/main/java/com/esri/core/geometry/ShapeImportFlags.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/ShapeModifiers.java b/src/main/java/com/esri/core/geometry/ShapeModifiers.java index 2206c8e9..9ba3b702 100644 --- a/src/main/java/com/esri/core/geometry/ShapeModifiers.java +++ b/src/main/java/com/esri/core/geometry/ShapeModifiers.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/ShapeType.java b/src/main/java/com/esri/core/geometry/ShapeType.java index 17efe606..5d1fafa3 100644 --- a/src/main/java/com/esri/core/geometry/ShapeType.java +++ b/src/main/java/com/esri/core/geometry/ShapeType.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/SimpleByteBufferCursor.java b/src/main/java/com/esri/core/geometry/SimpleByteBufferCursor.java index 5b884949..e7595439 100644 --- a/src/main/java/com/esri/core/geometry/SimpleByteBufferCursor.java +++ b/src/main/java/com/esri/core/geometry/SimpleByteBufferCursor.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/SimpleGeometryCursor.java b/src/main/java/com/esri/core/geometry/SimpleGeometryCursor.java index 5ec4034d..3c185400 100644 --- a/src/main/java/com/esri/core/geometry/SimpleGeometryCursor.java +++ b/src/main/java/com/esri/core/geometry/SimpleGeometryCursor.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/SimpleJsonCursor.java b/src/main/java/com/esri/core/geometry/SimpleJsonCursor.java index 188d7d4d..1af987a9 100644 --- a/src/main/java/com/esri/core/geometry/SimpleJsonCursor.java +++ b/src/main/java/com/esri/core/geometry/SimpleJsonCursor.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/SimpleJsonParserCursor.java b/src/main/java/com/esri/core/geometry/SimpleJsonParserCursor.java index 1d30235c..5f034a82 100644 --- a/src/main/java/com/esri/core/geometry/SimpleJsonParserCursor.java +++ b/src/main/java/com/esri/core/geometry/SimpleJsonParserCursor.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/SimpleMapGeometryCursor.java b/src/main/java/com/esri/core/geometry/SimpleMapGeometryCursor.java index 6d6879ed..fb281b76 100644 --- a/src/main/java/com/esri/core/geometry/SimpleMapGeometryCursor.java +++ b/src/main/java/com/esri/core/geometry/SimpleMapGeometryCursor.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/SimpleRasterizer.java b/src/main/java/com/esri/core/geometry/SimpleRasterizer.java index 3ca4d304..27a87351 100644 --- a/src/main/java/com/esri/core/geometry/SimpleRasterizer.java +++ b/src/main/java/com/esri/core/geometry/SimpleRasterizer.java @@ -1,5 +1,5 @@ /* - Copyright 2013 Esri + Copyright 2013-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -25,6 +25,7 @@ package com.esri.core.geometry; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.Comparator; @@ -38,6 +39,7 @@ public class SimpleRasterizer { * Even odd fill rule */ public final static int EVEN_ODD = 0; + /** * Winding fill rule */ @@ -46,11 +48,11 @@ public class SimpleRasterizer { public static interface ScanCallback { /** * Rasterizer calls this method for each scan it produced - * @param y the Y coordinate for the scan - * @param x the X coordinate for the scan - * @param numPxls the number of pixels in the scan + * @param scan array of scans. Scans are triplets of numbers. The start X coordinate for the scan (inclusive), + * the end X coordinate of the scan (exclusive), the Y coordinate for the scan. + * @param scanCount3 The number of initialized elements in the scans array. The scan count is scanCount3 / 3. */ - public abstract void drawScan(int y, int x, int numPxls); + public abstract void drawScan(int[] scans, int scanCount3); } public SimpleRasterizer() { @@ -68,31 +70,44 @@ public void setup(int width, int height, ScanCallback callback) activeEdgesTable_ = null; numEdges_ = 0; callback_ = callback; + if (scanBuffer_ == null) + scanBuffer_ = new int[128 * 3]; + startAddingEdges(); } - public int getWidth() { + public final int getWidth() { return width_; } - public int getHeight() { + public final int getHeight() { return height_; } + /** + * Flushes any cached scans. + */ + public final void flush() { + if (scanPtr_ > 0) { + callback_.drawScan(scanBuffer_, scanPtr_); + scanPtr_ = 0; + } + } + /** * Adds edges of a triangle. */ - public void addTriangle(double x1, double y1, double x2, double y2, double x3, double y3) { + public final void addTriangle(double x1, double y1, double x2, double y2, double x3, double y3) { addEdge(x1, y1, x2, y2); addEdge(x2, y2, x3, y3); addEdge(x1, y1, x3, y3); } - + /** * Adds edges of the ring to the rasterizer. * @param xy interleaved coordinates x1, y1, x2, y2,... */ - public void addRing(double xy[]) { + public final void addRing(double xy[]) { for (int i = 2; i < xy.length; i += 2) { addEdge(xy[i-2], xy[i - 1], xy[i], xy[i + 1]); } @@ -100,16 +115,33 @@ public void addRing(double xy[]) { /** * Call before starting the edges. + * * For example to render two polygons that consist of a single ring: * startAddingEdges(); * addRing(...); * renderEdges(Rasterizer.EVEN_ODD); * addRing(...); * renderEdges(Rasterizer.EVEN_ODD); + * + * For example to render a polygon consisting of three rings: + * startAddingEdges(); + * addRing(...); + * addRing(...); + * addRing(...); + * renderEdges(Rasterizer.EVEN_ODD); */ - public void startAddingEdges() { + public final void startAddingEdges() { if (numEdges_ > 0) { - ySortedEdges_ = null; + for (int i = 0; i < height_; i++) { + for (Edge e = ySortedEdges_[i]; e != null;) { + Edge p = e; + e = e.next; + p.next = null; + } + + ySortedEdges_[i] = null; + } + activeEdgesTable_ = null; } @@ -120,9 +152,13 @@ public void startAddingEdges() { /** * Renders all edges added so far, and removes them. - * @param fillMode + * Calls startAddingEdges after it's done. + * @param fillMode Fill mode for the polygon fill can be one of two values: EVEN_ODD or WINDING. + * + * Note, as any other graphics algorithm, the scan line rasterizer doesn't require polygons + * to be topologically simple, or have correct ring orientation. */ - public void renderEdges(int fillMode) { + public final void renderEdges(int fillMode) { evenOdd_ = fillMode == EVEN_ODD; for (int line = minY_; line <= maxY_; line++) { advanceAET_(); @@ -130,10 +166,6 @@ public void renderEdges(int fillMode) { emitScans_(); } - numEdges_ = 0; - if (activeEdgesTable_ != null) - activeEdgesTable_.clear(); - startAddingEdges();//reset for new edges } @@ -144,9 +176,10 @@ public void renderEdges(int fillMode) { * @param x2 * @param y2 */ - public void addEdge(double x1, double y1, double x2, double y2) { + public final void addEdge(double x1, double y1, double x2, double y2) { if (y1 == y2) return; + int dir = 1; if (y1 > y2) { double temp; @@ -180,27 +213,26 @@ else if (x1 >= width_ && x2 >= width_) y1 = 0; } - //We know that dxdy != 0, otherwise it would return earlier //do not clip x unless it is too small or too big int bigX = Math.max(width_ + 1, 0x7fffff); if (x1 < -0x7fffff) { - + //from earlier logic, x2 >= -1, therefore dxdy is not 0 y1 = (0 - x1) / dxdy + y1; x1 = 0; } else if (x1 > bigX) { - //we know that dx != 0, otherwise it would return earlier + //from earlier logic, x2 <= width_, therefore dxdy is not 0 y1 = (width_ - x1) / dxdy + y1; x1 = width_; } if (x2 < -0x7fffff) { - //we know that dx != 0, otherwise it would return earlier + //from earlier logic, x1 >= -1, therefore dxdy is not 0 y2 = (0 - x1) / dxdy + y1; x2 = 0; } else if (x2 > bigX) { - //we know that dx != 0, otherwise it would return earlier + //from earlier logic, x1 <= width_, therefore dxdy is not 0 y2 = (width_ - x1) / dxdy + y1; x2 = width_; } @@ -210,11 +242,7 @@ else if (x2 > bigX) { if (ystart == yend) return; - Edge e; - if (recycledEdges_ != null && recycledEdges_.size() > 0) - e = recycledEdges_.remove(recycledEdges_.size() - 1); - else - e = new Edge(); + Edge e = new Edge(); e.x = (long)(x1 * 4294967296.0); e.y = ystart; @@ -223,84 +251,155 @@ else if (x2 > bigX) { e.dir = dir; if (ySortedEdges_ == null) { - ySortedEdges_ = new ArrayList>(); - ySortedEdges_.ensureCapacity(height_); - for (int i = 0; i < height_; i++) { - ySortedEdges_.add(null); - } + ySortedEdges_ = new Edge[height_]; } - if (ySortedEdges_.get(e.y) == null) { - ySortedEdges_.set(e.y, new ArrayList()); - } + e.next = ySortedEdges_[e.y]; + ySortedEdges_[e.y] = e; - ySortedEdges_.get(e.y).add(e); if (e.y < minY_) minY_ = e.y; if (e.ymax > maxY_) maxY_ = e.ymax; + + numEdges_++; } - class Edge { + public final void fillEnvelope(Envelope2D envIn) { + Envelope2D env = new Envelope2D(0, 0, width_, height_); + if (!env.intersect(envIn)) + return; + + int x0 = (int)env.xmin; + int x = (int)env.xmax; + + int xn = NumberUtils.snap(x0, 0, width_); + int xm = NumberUtils.snap(x, 0, width_); + if (x0 < width_ && xn < xm) { + int y0 = (int)env.ymin; + int y1 = (int)env.ymax; + y0 = NumberUtils.snap(y0, 0, height_); + y1 = NumberUtils.snap(y1, 0, height_); + if (y0 < height_) { + for (int y = y0; y < y1; y++) { + scanBuffer_[scanPtr_++] = xn; + scanBuffer_[scanPtr_++] = xm; + scanBuffer_[scanPtr_++] = y; + if (scanPtr_ == scanBuffer_.length) { + callback_.drawScan(scanBuffer_, scanPtr_); + scanPtr_ = 0; + } + } + } + } + } + + public final ScanCallback getScanCallback() { return callback_; } + + + //PRIVATE + + private static class Edge { long x; long dxdy; int y; int ymax; int dir; + Edge next; } - private void advanceAET_() { - if (activeEdgesTable_ != null && activeEdgesTable_.size() > 0) { - for (int i = 0, n = activeEdgesTable_.size(); i < n; i++) { - Edge e = activeEdgesTable_.get(i); - e.y++; - if (e.y == e.ymax) { - if (recycledEdges_ == null) { - recycledEdges_ = new ArrayList(); - } - - recycledEdges_.add(e); - activeEdgesTable_.set(i, null); - continue; - } + private final void advanceAET_() { + if (activeEdgesTable_ == null) + return; + + boolean needSort = false; + Edge prev = null; + for (Edge e = activeEdgesTable_; e != null; ) { + e.y++; + if (e.y == e.ymax) { + Edge p = e; e = e.next; + if (prev != null) + prev.next = e; + else + activeEdgesTable_ = e; - e.x += e.dxdy; + p.next = null; + continue; } + + e.x += e.dxdy; + if (prev != null && prev.x > e.x) + needSort = true; + + prev = e; + e = e.next; + } + + if (needSort) { + //resort to fix the order + activeEdgesTable_ = sortAET_(activeEdgesTable_); } } - private void addNewEdgesToAET_(int y) { - if (y >= ySortedEdges_.size()) + private final void addNewEdgesToAET_(int y) { + if (y >= height_) return; - - if (activeEdgesTable_ == null) - activeEdgesTable_ = new ArrayList(); - - ArrayList edgesOnLine = ySortedEdges_.get(y); + + Edge edgesOnLine = ySortedEdges_[y]; if (edgesOnLine != null) { - for (int i = 0, n = edgesOnLine.size(); i < n; i++) { - activeEdgesTable_.add(edgesOnLine.get(i)); + ySortedEdges_[y] = null; + edgesOnLine = sortAET_(edgesOnLine);//sort new edges + numEdges_ -= sortedNum_;//set in the sortAET + + // merge the edges with sorted AET - O(n) operation + Edge aet = activeEdgesTable_; + boolean first = true; + Edge newEdge = edgesOnLine; + Edge prev_aet = null; + while (aet != null && newEdge != null) { + if (aet.x > newEdge.x) { + if (first) + activeEdgesTable_ = newEdge; + + Edge p = newEdge.next; + newEdge.next = aet; + if (prev_aet != null) { + prev_aet.next = newEdge; + } + + prev_aet = newEdge; + newEdge = p; + } else { // aet.x <= newEdges.x + Edge p = aet.next; + aet.next = newEdge; + if (prev_aet != null) + prev_aet.next = aet; + + prev_aet = aet; + aet = p; + } + + first = false; } - edgesOnLine.clear(); + if (activeEdgesTable_ == null) + activeEdgesTable_ = edgesOnLine; } } - static int snap_(int x, int mi, int ma) { + private static int snap_(int x, int mi, int ma) { return x < mi ? mi : x > ma ? ma : x; } - private void emitScans_() { - sortAET_(); - - if (activeEdgesTable_ == null || activeEdgesTable_.size() == 0) + + private final void emitScans_() { + if (activeEdgesTable_ == null) return; int w = 0; - Edge e0 = activeEdgesTable_.get(0); + Edge e0 = activeEdgesTable_; int x0 = (int)(e0.x >> 32); - for (int i = 1; i < activeEdgesTable_.size(); i++) { - Edge e = activeEdgesTable_.get(i); + for (Edge e = e0.next; e != null; e = e.next) { if (evenOdd_) w ^= 1; else @@ -308,11 +407,17 @@ private void emitScans_() { if (e.x > e0.x) { int x = (int)(e.x >> 32); - if (w == 1) { + if (w != 0) { int xx0 = snap_(x0, 0, width_); int xx = snap_(x, 0, width_); if (xx > xx0 && xx0 < width_) { - callback_.drawScan(e.y, xx0, xx - xx0); + scanBuffer_[scanPtr_++] = xx0; + scanBuffer_[scanPtr_++] = xx; + scanBuffer_[scanPtr_++] = e.y; + if (scanPtr_ == scanBuffer_.length) { + callback_.drawScan(scanBuffer_, scanPtr_); + scanPtr_ = 0; + } } } @@ -322,56 +427,74 @@ private void emitScans_() { } } - static class EdgeComparator implements Comparator { + static private class EdgeComparator implements Comparator { @Override public int compare(Edge o1, Edge o2) { - if (o1 == null) - return o2 == null ? 0 : 1; - else if (o2 == null) - return -1; - + if (o1 == o2) + return 0; + return o1.x < o2.x ? -1 : o1.x > o2.x ? 1 : 0; } } - private static EdgeComparator edgeCompare_ = new EdgeComparator(); + private final static EdgeComparator edgeCompare_ = new EdgeComparator(); - private void sortAET_() { - if (!checkAETIsSorted_()) + private final Edge sortAET_(Edge aet) { + int num = 0; + for (Edge e = aet; e != null; e = e.next) + num++; + + sortedNum_ = num; + if (num == 1) + return aet; + + if (sortBuffer_ == null) + sortBuffer_ = new Edge[Math.max(num, 16)]; + + else if (sortBuffer_.length < num) + sortBuffer_ = new Edge[Math.max(num, sortBuffer_.length * 2)]; + { - Collections.sort(activeEdgesTable_, edgeCompare_); - while (activeEdgesTable_.size() > 0 && activeEdgesTable_.get(activeEdgesTable_.size() - 1) == null) - activeEdgesTable_.remove(activeEdgesTable_.size() - 1); + int i = 0; + for (Edge e = aet; e != null; e = e.next) + sortBuffer_[i++] = e; } - } - - private boolean checkAETIsSorted_() { - if (activeEdgesTable_ == null || activeEdgesTable_.size() == 0) - return true; - - Edge e0 = activeEdgesTable_.get(0); - if (e0 == null) - return false; - for (int i = 1; i < activeEdgesTable_.size(); i++) { - Edge e = activeEdgesTable_.get(i); - if (e == null || e.x < e0.x) { - return false; + if (num == 2) { + if (sortBuffer_[0].x > sortBuffer_[1].x) { + Edge tmp = sortBuffer_[0]; + sortBuffer_[0] = sortBuffer_[1]; + sortBuffer_[1] = tmp; } - e0 = e; + } + else { + Arrays.sort(sortBuffer_, 0, num, edgeCompare_); + } + + aet = sortBuffer_[0]; sortBuffer_[0] = null; + Edge prev = aet; + for (int i = 1; i < num; i++) { + prev.next = sortBuffer_[i]; + prev = sortBuffer_[i]; + sortBuffer_[i] = null; } - return true; + prev.next = null; + return aet; } - - private ArrayList recycledEdges_; - private ArrayList activeEdgesTable_; - private ArrayList> ySortedEdges_; - public ScanCallback callback_; + + private Edge activeEdgesTable_; + private Edge[] ySortedEdges_; + private Edge[] sortBuffer_; + private int[] scanBuffer_; + int scanPtr_; + private ScanCallback callback_; private int width_; private int height_; private int minY_; private int maxY_; private int numEdges_; + private int sortedNum_; private boolean evenOdd_; } + diff --git a/src/main/java/com/esri/core/geometry/Simplificator.java b/src/main/java/com/esri/core/geometry/Simplificator.java index 8234b828..e0401cfa 100644 --- a/src/main/java/com/esri/core/geometry/Simplificator.java +++ b/src/main/java/com/esri/core/geometry/Simplificator.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/SpatialReference.java b/src/main/java/com/esri/core/geometry/SpatialReference.java index a416c241..39b1e90a 100644 --- a/src/main/java/com/esri/core/geometry/SpatialReference.java +++ b/src/main/java/com/esri/core/geometry/SpatialReference.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/SpatialReferenceImpl.java b/src/main/java/com/esri/core/geometry/SpatialReferenceImpl.java index 081b2b64..772b8ee7 100644 --- a/src/main/java/com/esri/core/geometry/SpatialReferenceImpl.java +++ b/src/main/java/com/esri/core/geometry/SpatialReferenceImpl.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/SpatialReferenceSerializer.java b/src/main/java/com/esri/core/geometry/SpatialReferenceSerializer.java index 5df978f2..0cf25e6c 100644 --- a/src/main/java/com/esri/core/geometry/SpatialReferenceSerializer.java +++ b/src/main/java/com/esri/core/geometry/SpatialReferenceSerializer.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/StridedIndexTypeCollection.java b/src/main/java/com/esri/core/geometry/StridedIndexTypeCollection.java index 785e696f..c54cfe9a 100644 --- a/src/main/java/com/esri/core/geometry/StridedIndexTypeCollection.java +++ b/src/main/java/com/esri/core/geometry/StridedIndexTypeCollection.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/StringUtils.java b/src/main/java/com/esri/core/geometry/StringUtils.java index f859e9d8..3237e13c 100644 --- a/src/main/java/com/esri/core/geometry/StringUtils.java +++ b/src/main/java/com/esri/core/geometry/StringUtils.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/SweepComparator.java b/src/main/java/com/esri/core/geometry/SweepComparator.java index 9fb76798..a1f94376 100644 --- a/src/main/java/com/esri/core/geometry/SweepComparator.java +++ b/src/main/java/com/esri/core/geometry/SweepComparator.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/SweepMonkierComparator.java b/src/main/java/com/esri/core/geometry/SweepMonkierComparator.java index 6589ef64..2edeef22 100644 --- a/src/main/java/com/esri/core/geometry/SweepMonkierComparator.java +++ b/src/main/java/com/esri/core/geometry/SweepMonkierComparator.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/TopoGraph.java b/src/main/java/com/esri/core/geometry/TopoGraph.java index 1146860d..5f57251c 100644 --- a/src/main/java/com/esri/core/geometry/TopoGraph.java +++ b/src/main/java/com/esri/core/geometry/TopoGraph.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/TopologicalOperations.java b/src/main/java/com/esri/core/geometry/TopologicalOperations.java index b6746704..def5cebe 100644 --- a/src/main/java/com/esri/core/geometry/TopologicalOperations.java +++ b/src/main/java/com/esri/core/geometry/TopologicalOperations.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/Transformation2D.java b/src/main/java/com/esri/core/geometry/Transformation2D.java index 1d90b17c..f5d07a54 100644 --- a/src/main/java/com/esri/core/geometry/Transformation2D.java +++ b/src/main/java/com/esri/core/geometry/Transformation2D.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/Transformation3D.java b/src/main/java/com/esri/core/geometry/Transformation3D.java index 5f914381..02719a3c 100644 --- a/src/main/java/com/esri/core/geometry/Transformation3D.java +++ b/src/main/java/com/esri/core/geometry/Transformation3D.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/Treap.java b/src/main/java/com/esri/core/geometry/Treap.java index d654fa3d..89cd6383 100644 --- a/src/main/java/com/esri/core/geometry/Treap.java +++ b/src/main/java/com/esri/core/geometry/Treap.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/UserCancelException.java b/src/main/java/com/esri/core/geometry/UserCancelException.java index bf0b1f2b..d72479a1 100644 --- a/src/main/java/com/esri/core/geometry/UserCancelException.java +++ b/src/main/java/com/esri/core/geometry/UserCancelException.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/VertexDescription.java b/src/main/java/com/esri/core/geometry/VertexDescription.java index 296e2af3..1fc1a6f6 100644 --- a/src/main/java/com/esri/core/geometry/VertexDescription.java +++ b/src/main/java/com/esri/core/geometry/VertexDescription.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/VertexDescriptionDesignerImpl.java b/src/main/java/com/esri/core/geometry/VertexDescriptionDesignerImpl.java index 9bec3a33..268b75bb 100644 --- a/src/main/java/com/esri/core/geometry/VertexDescriptionDesignerImpl.java +++ b/src/main/java/com/esri/core/geometry/VertexDescriptionDesignerImpl.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/VertexDescriptionHash.java b/src/main/java/com/esri/core/geometry/VertexDescriptionHash.java index c9f9e137..dfe372aa 100644 --- a/src/main/java/com/esri/core/geometry/VertexDescriptionHash.java +++ b/src/main/java/com/esri/core/geometry/VertexDescriptionHash.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/WkbByteOrder.java b/src/main/java/com/esri/core/geometry/WkbByteOrder.java index 9f474f20..c973d82e 100644 --- a/src/main/java/com/esri/core/geometry/WkbByteOrder.java +++ b/src/main/java/com/esri/core/geometry/WkbByteOrder.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/WkbExportFlags.java b/src/main/java/com/esri/core/geometry/WkbExportFlags.java index d4637df8..115f39c7 100644 --- a/src/main/java/com/esri/core/geometry/WkbExportFlags.java +++ b/src/main/java/com/esri/core/geometry/WkbExportFlags.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/WkbGeometryType.java b/src/main/java/com/esri/core/geometry/WkbGeometryType.java index 07d142d0..20932cf9 100644 --- a/src/main/java/com/esri/core/geometry/WkbGeometryType.java +++ b/src/main/java/com/esri/core/geometry/WkbGeometryType.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/WkbImportFlags.java b/src/main/java/com/esri/core/geometry/WkbImportFlags.java index 5f1f0392..fdadc8a5 100644 --- a/src/main/java/com/esri/core/geometry/WkbImportFlags.java +++ b/src/main/java/com/esri/core/geometry/WkbImportFlags.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/Wkid.java b/src/main/java/com/esri/core/geometry/Wkid.java index 9ecf2bd7..29b119f5 100644 --- a/src/main/java/com/esri/core/geometry/Wkid.java +++ b/src/main/java/com/esri/core/geometry/Wkid.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/Wkt.java b/src/main/java/com/esri/core/geometry/Wkt.java index 08db42f1..a4c2a7f4 100644 --- a/src/main/java/com/esri/core/geometry/Wkt.java +++ b/src/main/java/com/esri/core/geometry/Wkt.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/WktExportFlags.java b/src/main/java/com/esri/core/geometry/WktExportFlags.java index 2756135d..c9974be5 100644 --- a/src/main/java/com/esri/core/geometry/WktExportFlags.java +++ b/src/main/java/com/esri/core/geometry/WktExportFlags.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/WktImportFlags.java b/src/main/java/com/esri/core/geometry/WktImportFlags.java index 9bc57d6a..d16c2d20 100644 --- a/src/main/java/com/esri/core/geometry/WktImportFlags.java +++ b/src/main/java/com/esri/core/geometry/WktImportFlags.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/WktParser.java b/src/main/java/com/esri/core/geometry/WktParser.java index 7e5afcb6..c3b4894e 100644 --- a/src/main/java/com/esri/core/geometry/WktParser.java +++ b/src/main/java/com/esri/core/geometry/WktParser.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2013 Esri + Copyright 1995-2015 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/test/java/com/esri/core/geometry/TestPolygon.java b/src/test/java/com/esri/core/geometry/TestPolygon.java index f83d2569..8901df4e 100644 --- a/src/test/java/com/esri/core/geometry/TestPolygon.java +++ b/src/test/java/com/esri/core/geometry/TestPolygon.java @@ -1200,5 +1200,4 @@ public void testReplaceNaNs() { } } - } diff --git a/src/test/java/com/esri/core/geometry/TestRasterizedGeometry2D.java b/src/test/java/com/esri/core/geometry/TestRasterizedGeometry2D.java index a96528ca..a1ff65fc 100644 --- a/src/test/java/com/esri/core/geometry/TestRasterizedGeometry2D.java +++ b/src/test/java/com/esri/core/geometry/TestRasterizedGeometry2D.java @@ -1,6 +1,7 @@ package com.esri.core.geometry; import junit.framework.TestCase; + import org.junit.Test; import com.esri.core.geometry.Geometry.GeometryAccelerationDegree; @@ -130,5 +131,23 @@ public void test() { assertFalse(OperatorContains.local().execute(poly, new Point(1, 3), sr, null)); assertFalse(OperatorContains.local().execute(poly, new Point(1.6, 0.1), sr, null)); } + + /* + { + Geometry g = OperatorFactoryLocal.loadGeometryFromEsriShapeDbg("c:/temp/_poly_final.bin"); + RasterizedGeometry2D rg1 = RasterizedGeometry2D + .create(g, 0, 1024);//warmup + rg1 = null; + + long t0 = System.nanoTime(); + RasterizedGeometry2D rg = RasterizedGeometry2D + .create(g, 0, 1024 * 1024); + long t1 = System.nanoTime(); + double d = (t1 - t0) / 1000000.0; + System.out.printf("Time to rasterize the geometry: %f", d); + + rg.dbgSaveToBitmap("c:/temp/_dbg.bmp"); + for (;;){} + }*/ } } From 0570d128fd18099dd43deaf91ca4dcd12867c126 Mon Sep 17 00:00:00 2001 From: serg4066 Date: Thu, 19 Feb 2015 17:40:58 -0800 Subject: [PATCH 103/196] a fix for #78 --- .../esri/core/geometry/RasterizedGeometry2DImpl.java | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/esri/core/geometry/RasterizedGeometry2DImpl.java b/src/main/java/com/esri/core/geometry/RasterizedGeometry2DImpl.java index 556a9fe0..ded79caf 100644 --- a/src/main/java/com/esri/core/geometry/RasterizedGeometry2DImpl.java +++ b/src/main/java/com/esri/core/geometry/RasterizedGeometry2DImpl.java @@ -213,11 +213,11 @@ void strokeDrawPolyPath(SimpleRasterizer rasterizer, } int worldToPixX(double x) { - return (int) Math.round(x * m_dx + m_x0); + return (int) (x * m_dx + m_x0); } int worldToPixY(double y) { - return (int) Math.round(y * m_dy + m_y0); + return (int) (y * m_dy + m_y0); } RasterizedGeometry2DImpl(Geometry geom, double toleranceXY, @@ -405,6 +405,9 @@ void buildLevels() { @Override public HitType queryPointInGeometry(double x, double y) { + if (!m_geomEnv.contains(x, y)) + return HitType.Outside; + int ix = worldToPixX(x); int iy = worldToPixY(y); if (ix < 0 || ix >= m_width || iy < 0 || iy >= m_width) @@ -423,7 +426,8 @@ else if (res == 1) @Override public HitType queryEnvelopeInGeometry(Envelope2D env) { if (!env.intersect(m_geomEnv)) - return com.esri.core.geometry.RasterizedGeometry2D.HitType.Outside; + return HitType.Outside; + int ixmin = worldToPixX(env.xmin); int ixmax = worldToPixX(env.xmax); int iymin = worldToPixY(env.ymin); From 591acbe3871760ecf4626cf4321b198243af8ee3 Mon Sep 17 00:00:00 2001 From: serg4066 Date: Fri, 20 Feb 2015 11:23:04 -0800 Subject: [PATCH 104/196] fixes for #79 and #80, made MultiPath.addEnvelope public --- src/main/java/com/esri/core/geometry/MultiPath.java | 2 +- .../esri/core/geometry/PairwiseIntersectorImpl.java | 2 +- .../com/esri/core/geometry/SimpleRasterizer.java | 2 +- src/main/java/com/esri/core/geometry/Wkid.java | 2 +- .../java/com/esri/core/geometry/TestRelation.java | 12 ++++++++++++ src/test/java/com/esri/core/geometry/TestWkid.java | 9 +++++++++ 6 files changed, 25 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/esri/core/geometry/MultiPath.java b/src/main/java/com/esri/core/geometry/MultiPath.java index f2b80e89..8e778725 100644 --- a/src/main/java/com/esri/core/geometry/MultiPath.java +++ b/src/main/java/com/esri/core/geometry/MultiPath.java @@ -653,7 +653,7 @@ boolean hasNonLinearSegments(int pathIndex) { * @param bReverse * Creates reversed path. */ - void addEnvelope(Envelope2D envSrc, boolean bReverse) { + public void addEnvelope(Envelope2D envSrc, boolean bReverse) { m_impl.addEnvelope(envSrc, bReverse); } diff --git a/src/main/java/com/esri/core/geometry/PairwiseIntersectorImpl.java b/src/main/java/com/esri/core/geometry/PairwiseIntersectorImpl.java index cb3c5c00..d02cc4fa 100644 --- a/src/main/java/com/esri/core/geometry/PairwiseIntersectorImpl.java +++ b/src/main/java/com/esri/core/geometry/PairwiseIntersectorImpl.java @@ -34,7 +34,7 @@ class PairwiseIntersectorImpl { private double m_tolerance; private int m_path_index; private int m_element_handle; - private Envelope2D m_paths_query; // only used for m_b_paths == true case + private Envelope2D m_paths_query = new Envelope2D(); // only used for m_b_paths == true case private QuadTreeImpl m_quad_tree; private QuadTreeImpl.QuadTreeIteratorImpl m_qt_iter; private SegmentIteratorImpl m_seg_iter; diff --git a/src/main/java/com/esri/core/geometry/SimpleRasterizer.java b/src/main/java/com/esri/core/geometry/SimpleRasterizer.java index 27a87351..783b8a8f 100644 --- a/src/main/java/com/esri/core/geometry/SimpleRasterizer.java +++ b/src/main/java/com/esri/core/geometry/SimpleRasterizer.java @@ -48,7 +48,7 @@ public class SimpleRasterizer { public static interface ScanCallback { /** * Rasterizer calls this method for each scan it produced - * @param scan array of scans. Scans are triplets of numbers. The start X coordinate for the scan (inclusive), + * @param scans array of scans. Scans are triplets of numbers. The start X coordinate for the scan (inclusive), * the end X coordinate of the scan (exclusive), the Y coordinate for the scan. * @param scanCount3 The number of initialized elements in the scans array. The scan count is scanCount3 / 3. */ diff --git a/src/main/java/com/esri/core/geometry/Wkid.java b/src/main/java/com/esri/core/geometry/Wkid.java index 29b119f5..9fd9cca0 100644 --- a/src/main/java/com/esri/core/geometry/Wkid.java +++ b/src/main/java/com/esri/core/geometry/Wkid.java @@ -146,7 +146,7 @@ public static double find_tolerance_from_wkid(int wkid) { if (tol == 1e38) { int old = wkid_to_old(wkid); if (old != wkid) - tol = find_tolerance_from_wkid_helper(wkid); + tol = find_tolerance_from_wkid_helper(old); if (tol == 1e38) return 1e-10; } diff --git a/src/test/java/com/esri/core/geometry/TestRelation.java b/src/test/java/com/esri/core/geometry/TestRelation.java index 10386f82..ec21049d 100644 --- a/src/test/java/com/esri/core/geometry/TestRelation.java +++ b/src/test/java/com/esri/core/geometry/TestRelation.java @@ -5481,4 +5481,16 @@ public void testCrosses_github_issue_40() { null); assertTrue(answer2); } + + @Test + public void testDisjointCrash() { + Polygon g1 = new Polygon(); + g1.addEnvelope(Envelope2D.construct(0, 0, 10, 10), false); + Polygon g2 = new Polygon(); + g2.addEnvelope(Envelope2D.construct(10, 1, 21, 21), false); + g1 = (Polygon)OperatorDensifyByLength.local().execute(g1, 0.1, null); + OperatorDisjoint.local().accelerateGeometry(g1, SpatialReference.create(4267), GeometryAccelerationDegree.enumHot); + boolean res = OperatorDisjoint.local().execute(g1, g2, SpatialReference.create(4267), null); + assertTrue(!res); + } } diff --git a/src/test/java/com/esri/core/geometry/TestWkid.java b/src/test/java/com/esri/core/geometry/TestWkid.java index fc09a66c..d953ae95 100644 --- a/src/test/java/com/esri/core/geometry/TestWkid.java +++ b/src/test/java/com/esri/core/geometry/TestWkid.java @@ -19,4 +19,13 @@ public void test() { assertTrue(Math.abs(tol84 - 1e-8) < 1e-8 * 1e-8); } + @Test + public void test_80() { + SpatialReference sr = SpatialReference.create(3857); + assertTrue(sr.getID() == 3857); + assertTrue(sr.getLatestID() == 3857); + assertTrue(sr.getOldID() == 102100); + assertTrue(sr.getTolerance() == 0.001); + } + } From 1315745fe8da5a87dec8b3e83b5b05b2e0720f72 Mon Sep 17 00:00:00 2001 From: Mike Park Date: Mon, 23 Mar 2015 10:24:57 -0700 Subject: [PATCH 105/196] prepare for maven build --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 760d2734..d75b569d 100755 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ com.esri.geometry esri-geometry-api - 1.2 + 1.2.1-SNAPSHOT jar Esri Geometry API for Java From 5b28b728ac7c26ab8c804b79218eaf281981b122 Mon Sep 17 00:00:00 2001 From: Mike Park Date: Mon, 23 Mar 2015 10:26:17 -0700 Subject: [PATCH 106/196] [maven-release-plugin] prepare release v1.2.1 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index d75b569d..4f59fd07 100755 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ com.esri.geometry esri-geometry-api - 1.2.1-SNAPSHOT + 1.2.1 jar Esri Geometry API for Java From d4ade6b12134c9e74d5a1330e144c2cefc133e17 Mon Sep 17 00:00:00 2001 From: Mike Park Date: Mon, 23 Mar 2015 10:26:20 -0700 Subject: [PATCH 107/196] [maven-release-plugin] prepare for next development iteration --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 4f59fd07..4248a3ce 100755 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ com.esri.geometry esri-geometry-api - 1.2.1 + 1.2.2-SNAPSHOT jar Esri Geometry API for Java From 8b4be12076bc6268ee9e44c4d1411ddc5b92b429 Mon Sep 17 00:00:00 2001 From: Mike Park Date: Mon, 23 Mar 2015 10:51:31 -0700 Subject: [PATCH 108/196] add gpg sign activation --- pom.xml | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 4248a3ce..f85a8c4b 100755 --- a/pom.xml +++ b/pom.xml @@ -48,7 +48,13 @@ - release + release-sign-artifacts + + + performRelease + true + + From 9060e5e7a68b5c7a8d75daf16fd46a4bc578c75a Mon Sep 17 00:00:00 2001 From: Mike Park Date: Mon, 23 Mar 2015 11:03:21 -0700 Subject: [PATCH 109/196] [maven-release-plugin] prepare release v1.2.1 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index f85a8c4b..7a72602c 100755 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ com.esri.geometry esri-geometry-api - 1.2.2-SNAPSHOT + 1.2.1 jar Esri Geometry API for Java From e8eb988435197afb48d799721ea9bdf0bd74efb2 Mon Sep 17 00:00:00 2001 From: Mike Park Date: Mon, 23 Mar 2015 11:05:13 -0700 Subject: [PATCH 110/196] fighting with maven --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 7a72602c..34b515a9 100755 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ com.esri.geometry esri-geometry-api - 1.2.1 + 1.2.1-SNAPSHOT jar Esri Geometry API for Java From f3632664ec296c68f94bf12cfb9e176472a2c70c Mon Sep 17 00:00:00 2001 From: Mike Park Date: Mon, 23 Mar 2015 11:06:31 -0700 Subject: [PATCH 111/196] [maven-release-plugin] prepare release v1.2.1 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 34b515a9..7a72602c 100755 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ com.esri.geometry esri-geometry-api - 1.2.1-SNAPSHOT + 1.2.1 jar Esri Geometry API for Java From 28c9a68e1c88a58b8ae07de3ab91ce9b2ef1c82f Mon Sep 17 00:00:00 2001 From: Eugen Stoianovici Date: Sun, 17 May 2015 11:38:24 +0100 Subject: [PATCH 112/196] added support for GeoJSON to geometry collections --- .../ogc/OGCConcreteGeometryCollection.java | 35 +++++++++++++++ .../esri/core/geometry/TestGeomToGeoJson.java | 45 +++++++++++++++++++ 2 files changed, 80 insertions(+) diff --git a/src/main/java/com/esri/core/geometry/ogc/OGCConcreteGeometryCollection.java b/src/main/java/com/esri/core/geometry/ogc/OGCConcreteGeometryCollection.java index 4d91d7e2..b87a570a 100644 --- a/src/main/java/com/esri/core/geometry/ogc/OGCConcreteGeometryCollection.java +++ b/src/main/java/com/esri/core/geometry/ogc/OGCConcreteGeometryCollection.java @@ -5,6 +5,11 @@ import com.esri.core.geometry.GeometryCursor; import com.esri.core.geometry.Polygon; import com.esri.core.geometry.SpatialReference; +import com.esri.core.geometry.Operator; +import com.esri.core.geometry.JsonCursor; +import com.esri.core.geometry.OperatorFactoryLocal; +import com.esri.core.geometry.OperatorExportToGeoJson; + import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.util.ArrayList; @@ -323,4 +328,34 @@ public OGCGeometry convertToMulti() public String asJson() { throw new UnsupportedOperationException(); } + + @Override + public String asGeoJson() { + StringBuilder sb = new StringBuilder(); + + OperatorExportToGeoJson op = (OperatorExportToGeoJson) OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.ExportToGeoJson); + JsonCursor cursor = op.execute(this.esriSR, getEsriGeometryCursor()); + + sb.append("{\"type\" : \"GeometryCollection\", \"geometries\" : "); + String shape = cursor.next(); + if (shape == null){ + // geometry collection with empty list of geometries + sb.append("[]}"); + return sb.toString(); + } + + sb.append("["); + sb.append(shape); + + while(true){ + shape = cursor.next(); + if(shape == null) + break; + sb.append(", ").append(shape); + } + + sb.append("]}"); + return sb.toString(); + } } diff --git a/src/test/java/com/esri/core/geometry/TestGeomToGeoJson.java b/src/test/java/com/esri/core/geometry/TestGeomToGeoJson.java index 80099069..9c517d39 100644 --- a/src/test/java/com/esri/core/geometry/TestGeomToGeoJson.java +++ b/src/test/java/com/esri/core/geometry/TestGeomToGeoJson.java @@ -27,6 +27,7 @@ import com.esri.core.geometry.ogc.OGCMultiPoint; import com.esri.core.geometry.ogc.OGCLineString; import com.esri.core.geometry.ogc.OGCPolygon; +import com.esri.core.geometry.ogc.OGCConcreteGeometryCollection; import junit.framework.TestCase; import org.codehaus.jackson.JsonFactory; import org.codehaus.jackson.JsonParser; @@ -34,6 +35,8 @@ import org.junit.Test; import java.io.IOException; +import java.util.ArrayList; +import java.util.List; public class TestGeomToGeoJson extends TestCase { OperatorFactoryLocal factory = OperatorFactoryLocal.getInstance(); @@ -358,4 +361,46 @@ public void testEnvelopeGeometryEngine() { assertEquals("{\"bbox\":[-180.0,-90.0,180.0,90.0]}", result); } + @Test + public void testGeometryCollection(){ + SpatialReference sr = SpatialReference.create(4326); + + StringBuilder geometrySb = new StringBuilder(); + geometrySb.append("{\"type\" : \"GeometryCollection\", \"geometries\" : ["); + + OGCPoint point = new OGCPoint(new Point(1.0, 1.0), sr); + assertEquals("{\"x\":1,\"y\":1,\"spatialReference\":{\"wkid\":4326}}", point.asJson()); + assertEquals("{\"type\":\"Point\",\"coordinates\":[1.0,1.0]}", point.asGeoJson()); + geometrySb.append(point.asGeoJson()).append(", "); + + OGCLineString line = new OGCLineString(new Polyline(new Point(1.0, 1.0), new Point(2.0, 2.0)), 0, sr); + assertEquals("{\"paths\":[[[1,1],[2,2]]],\"spatialReference\":{\"wkid\":4326}}", line.asJson()); + assertEquals("{\"type\":\"LineString\",\"coordinates\":[[1.0,1.0],[2.0,2.0]]}", line.asGeoJson()); + geometrySb.append(line.asGeoJson()).append(", "); + + Polygon p = new Polygon(); + p.startPath(1.0, 1.0); + p.lineTo(2.0, 2.0); + p.lineTo(3.0, 1.0); + p.lineTo(2.0, 0.0); + + OGCPolygon polygon = new OGCPolygon(p, sr); + assertEquals("{\"rings\":[[[1,1],[2,2],[3,1],[2,0],[1,1]]],\"spatialReference\":{\"wkid\":4326}}", + polygon.asJson()); + assertEquals("{\"type\":\"Polygon\",\"coordinates\":[[[1.0,1.0],[2.0,2.0],[3.0,1.0],[2.0,0.0],[1.0,1.0]]]}", + polygon.asGeoJson()); + geometrySb.append(polygon.asGeoJson()).append("]}"); + + List geoms = new ArrayList(3); + geoms.add(point);geoms.add(line);geoms.add(polygon); + OGCConcreteGeometryCollection collection = new OGCConcreteGeometryCollection(geoms, sr); + assertEquals(geometrySb.toString(), collection.asGeoJson()); + } + + @Test + public void testEmptyGeometryCollection(){ + SpatialReference sr = SpatialReference.create(4326); + OGCConcreteGeometryCollection collection = new OGCConcreteGeometryCollection(new ArrayList(), sr); + assertEquals("{\"type\" : \"GeometryCollection\", \"geometries\" : []}", collection.asGeoJson()); + } } From d6660b1628c2e4109c19f7af369604e52975dda8 Mon Sep 17 00:00:00 2001 From: Alex Panchenko Date: Wed, 23 Sep 2015 19:08:54 +0500 Subject: [PATCH 113/196] 1.2.1 maven version in README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 03d91b99..033b44c3 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,7 @@ The project is also available as a [Maven](http://maven.apache.org/) dependency: com.esri.geometry esri-geometry-api - 1.2 + 1.2.1 ``` From fdf3c496b780e072c74988aa99c56758c136b70f Mon Sep 17 00:00:00 2001 From: Alex Panchenko Date: Wed, 30 Sep 2015 13:47:29 +0600 Subject: [PATCH 114/196] Fix for SpatialReferenceImpl.equals(Object) - compare different values --- .../core/geometry/SpatialReferenceImpl.java | 2 +- .../geometry/TestSpatialReferenceImpl.java | 24 +++++++++++++++++++ 2 files changed, 25 insertions(+), 1 deletion(-) create mode 100644 src/test/java/com/esri/core/geometry/TestSpatialReferenceImpl.java diff --git a/src/main/java/com/esri/core/geometry/SpatialReferenceImpl.java b/src/main/java/com/esri/core/geometry/SpatialReferenceImpl.java index 772b8ee7..77520c40 100644 --- a/src/main/java/com/esri/core/geometry/SpatialReferenceImpl.java +++ b/src/main/java/com/esri/core/geometry/SpatialReferenceImpl.java @@ -218,7 +218,7 @@ public boolean equals(Object obj) { return false; if (m_userWkid == 0) { - if (!m_userWkt.equals(m_userWkt))// m_userWkt cannot be null here! + if (!m_userWkt.equals(sr.m_userWkt))// m_userWkt cannot be null here! return false; } diff --git a/src/test/java/com/esri/core/geometry/TestSpatialReferenceImpl.java b/src/test/java/com/esri/core/geometry/TestSpatialReferenceImpl.java new file mode 100644 index 00000000..61c83615 --- /dev/null +++ b/src/test/java/com/esri/core/geometry/TestSpatialReferenceImpl.java @@ -0,0 +1,24 @@ +package com.esri.core.geometry; + +import org.junit.Assert; +import org.junit.Test; + +public class TestSpatialReferenceImpl extends Assert { + @Test + public void equals() { + final String wktext1 = "GEOGCS[\"GCS_WGS_1984\",DATUM[\"D_WGS_1984\",SPHEROID[\"WGS_1984\",6378137.0,298.257223563]],PRIMEM[\"Greenwich\",0.0],UNIT[\"Degree\",0.0174532925199433]]"; + final String wktext2 = "PROJCS[\"WGS_1984_Web_Mercator_Auxiliary_Sphere\",GEOGCS[\"GCS_WGS_1984\",DATUM[\"D_WGS_1984\",SPHEROID[\"WGS_1984\",6378137.0,298.257223563]],PRIMEM[\"Greenwich\",0.0],UNIT[\"Degree\",0.0174532925199433]],PROJECTION[\"Mercator_Auxiliary_Sphere\"],PARAMETER[\"False_Easting\",0.0],PARAMETER[\"False_Northing\",0.0],PARAMETER[\"Central_Meridian\",0.0],PARAMETER[\"Standard_Parallel_1\",0.0],PARAMETER[\"Auxiliary_Sphere_Type\",0.0],UNIT[\"Meter\",1.0]]"; + + final SpatialReference a1 = SpatialReference.create(wktext1); + final SpatialReference b = SpatialReference.create(wktext2); + final SpatialReference a2 = SpatialReference.create(wktext1); + + assertTrue(a1.equals(a1)); + assertTrue(b.equals(b)); + + assertTrue(a1.equals(a2)); + + assertFalse(a1.equals(b)); + assertFalse(b.equals(a1)); + } +} From b17ee469eeb706bf343fe1d7e82e14ea71e07520 Mon Sep 17 00:00:00 2001 From: Sergey Tolstov Date: Wed, 30 Sep 2015 10:22:32 -0700 Subject: [PATCH 115/196] Update and rename TestSpatialReferenceImpl.java to TestSpatialReference.java --- ...{TestSpatialReferenceImpl.java => TestSpatialReference.java} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename src/test/java/com/esri/core/geometry/{TestSpatialReferenceImpl.java => TestSpatialReference.java} (95%) diff --git a/src/test/java/com/esri/core/geometry/TestSpatialReferenceImpl.java b/src/test/java/com/esri/core/geometry/TestSpatialReference.java similarity index 95% rename from src/test/java/com/esri/core/geometry/TestSpatialReferenceImpl.java rename to src/test/java/com/esri/core/geometry/TestSpatialReference.java index 61c83615..f0f6aacd 100644 --- a/src/test/java/com/esri/core/geometry/TestSpatialReferenceImpl.java +++ b/src/test/java/com/esri/core/geometry/TestSpatialReference.java @@ -3,7 +3,7 @@ import org.junit.Assert; import org.junit.Test; -public class TestSpatialReferenceImpl extends Assert { +public class TestSpatialReference extends Assert { @Test public void equals() { final String wktext1 = "GEOGCS[\"GCS_WGS_1984\",DATUM[\"D_WGS_1984\",SPHEROID[\"WGS_1984\",6378137.0,298.257223563]],PRIMEM[\"Greenwich\",0.0],UNIT[\"Degree\",0.0174532925199433]]"; From 4b949c777660a533b04a50c2e6c61e71d2abbab8 Mon Sep 17 00:00:00 2001 From: Oliver Snowden Date: Sun, 11 Oct 2015 01:12:38 +0100 Subject: [PATCH 116/196] bump to latest dependency versions Specifically, dependency versions that work with a 1.6 JDK. Note: later org.json:json versions only work with Java 1.8. JDK 8: 20141113 20150729 --- pom.xml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pom.xml b/pom.xml index 7a72602c..88357799 100755 --- a/pom.xml +++ b/pom.xml @@ -89,9 +89,9 @@ 1.6 - 20090211 - 1.9.12 - 4.11 + 20140107 + 1.9.13 + 4.12 2.3.1 From 33a1c3cf6bcaee19db268bb2fdfadd2f533cb8a2 Mon Sep 17 00:00:00 2001 From: David Raleigh Date: Sat, 14 Nov 2015 13:19:39 +0100 Subject: [PATCH 117/196] digits on either side of decimal --- .../java/com/esri/core/geometry/ECoordinate.java | 4 ++-- src/main/java/com/esri/core/geometry/GeoDist.java | 2 +- .../com/esri/core/geometry/Transformation2D.java | 12 ++++++------ 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/main/java/com/esri/core/geometry/ECoordinate.java b/src/main/java/com/esri/core/geometry/ECoordinate.java index 253a05b6..c3d5c522 100644 --- a/src/main/java/com/esri/core/geometry/ECoordinate.java +++ b/src/main/java/com/esri/core/geometry/ECoordinate.java @@ -189,7 +189,7 @@ void div(ECoordinate divis) { if (divis.m_eps > 0.01 * fabsdivis) {// more accurate error calculation // for very inaccurate divisor double rr = divis.m_eps / fabsdivis; - e *= (1. + (1. + rr) * rr); + e *= (1.0 + (1.0 + rr) * rr); } m_value = r; m_eps = e + epsCoordinate() * Math.abs(r); @@ -226,7 +226,7 @@ void sqrt() { if (m_value >= 0) { // assume non-negative input r = Math.sqrt(m_value); - if (m_value > 10. * m_eps) { + if (m_value > 10.0 * m_eps) { dr = 0.5 * m_eps / r; } else { dr = (m_value > m_eps) ? r - Math.sqrt(m_value - m_eps) : Math diff --git a/src/main/java/com/esri/core/geometry/GeoDist.java b/src/main/java/com/esri/core/geometry/GeoDist.java index 81225134..108e04e0 100644 --- a/src/main/java/com/esri/core/geometry/GeoDist.java +++ b/src/main/java/com/esri/core/geometry/GeoDist.java @@ -88,7 +88,7 @@ static private double q90(double a, double e2) { double n2 = n * n; return a / (1.0 + n) - * (1.0 + n2 * (1. / 4. + n2 * (1. / 64. + n2 * (1. / 256.)))) + * (1.0 + n2 * (1.0 / 4.0 + n2 * (1.0 / 64.0 + n2 * (1.0 / 256.0)))) * PE_PI2; } diff --git a/src/main/java/com/esri/core/geometry/Transformation2D.java b/src/main/java/com/esri/core/geometry/Transformation2D.java index f5d07a54..d1472399 100644 --- a/src/main/java/com/esri/core/geometry/Transformation2D.java +++ b/src/main/java/com/esri/core/geometry/Transformation2D.java @@ -456,9 +456,9 @@ public boolean isIdentity() { * The tolerance value. */ public boolean isIdentity(double tol) { - Point2D pt = Point2D.construct(0., 1.); + Point2D pt = Point2D.construct(0.0, 1.0); transform(pt, pt); - pt.sub(Point2D.construct(0., 1.)); + pt.sub(Point2D.construct(0.0, 1.0)); if (pt.sqrLength() > tol * tol) return false; @@ -467,9 +467,9 @@ public boolean isIdentity(double tol) { if (pt.sqrLength() > tol * tol) return false; - pt.setCoords(1., 0.); + pt.setCoords(1.0, 0.0); transform(pt, pt); - pt.sub(Point2D.construct(1., 0)); + pt.sub(Point2D.construct(1.0, 0)); return pt.sqrLength() <= tol * tol; } @@ -510,12 +510,12 @@ public boolean isShift() { * The tolerance value. */ public boolean isShift(double tol) { - Point2D pt = transformWithoutShift(Point2D.construct(0., 1.)); + Point2D pt = transformWithoutShift(Point2D.construct(0.0, 1.0)); pt.y -= 1.0; if (pt.sqrLength() > tol * tol) return false; - pt = transformWithoutShift(Point2D.construct(1., 0.)); + pt = transformWithoutShift(Point2D.construct(1.0, 0.0)); pt.x -= 1.0; return pt.sqrLength() <= tol * tol; } From 96f83f4eea0965a0198816098ce76e7d299be97e Mon Sep 17 00:00:00 2001 From: David Raleigh Date: Sat, 14 Nov 2015 14:36:03 +0100 Subject: [PATCH 118/196] fix @Test attribute placements --- src/test/java/com/esri/core/geometry/TestClip.java | 5 ----- src/test/java/com/esri/core/geometry/TestWktParser.java | 7 +++++++ 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/test/java/com/esri/core/geometry/TestClip.java b/src/test/java/com/esri/core/geometry/TestClip.java index ee8d675f..0149d482 100644 --- a/src/test/java/com/esri/core/geometry/TestClip.java +++ b/src/test/java/com/esri/core/geometry/TestClip.java @@ -107,7 +107,6 @@ public static void testClipGeometries() { } } - @Test public static Polygon makePolygon() { Polygon poly = new Polygon(); poly.startPath(0, 0); @@ -117,7 +116,6 @@ public static Polygon makePolygon() { return poly; } - @Test public static Polyline makePolyline() { Polyline poly = new Polyline(); poly.startPath(0, 0); @@ -185,7 +183,6 @@ public static void testArcObjectsFailureCR196492() { // ((MultiPathImpl::SPtr)clippedPolygon._GetImpl()).SaveToTextFileDbg("c:\\temp\\test_ArcObjects_failure_CR196492.txt"); } - @Test public static Polyline makePolylineCR() { Polyline polyline = new Polyline(); @@ -200,7 +197,6 @@ public static Polyline makePolylineCR() { return polyline; } - @Test public static MultiPoint makeMultiPoint() { MultiPoint mpoint = new MultiPoint(); @@ -223,7 +219,6 @@ public static MultiPoint makeMultiPoint() { return mpoint; } - @Test public static Point makePoint() { Point point = new Point(); diff --git a/src/test/java/com/esri/core/geometry/TestWktParser.java b/src/test/java/com/esri/core/geometry/TestWktParser.java index 9949e5a3..db40cdff 100644 --- a/src/test/java/com/esri/core/geometry/TestWktParser.java +++ b/src/test/java/com/esri/core/geometry/TestWktParser.java @@ -7,6 +7,7 @@ public class TestWktParser extends TestCase { + @Test public void testGeometryCollection() { String s = " geometrycollection emPty "; WktParser wktParser = new WktParser(); @@ -136,6 +137,7 @@ public void testGeometryCollection() { assertTrue(currentToken == WktParser.WktToken.not_available); } + @Test public void testMultiPolygon() { String s = " MultIPolYgOn emPty "; WktParser wktParser = new WktParser(); @@ -413,6 +415,7 @@ public void testMultiPolygon() { assertTrue(currentToken == WktParser.WktToken.not_available); } + @Test public void testMultiLineString() { String s = " MultiLineString emPty "; WktParser wktParser = new WktParser(); @@ -623,6 +626,7 @@ public void testMultiLineString() { assertTrue(currentToken == WktParser.WktToken.not_available); } + @Test public void testMultiPoint() { String s = " MultipoInt emPty "; WktParser wktParser = new WktParser(); @@ -758,6 +762,7 @@ public void testMultiPoint() { assertTrue(currentToken == WktParser.WktToken.not_available); } + @Test public void testPolygon() { String s = " Polygon emPty "; WktParser wktParser = new WktParser(); @@ -968,6 +973,7 @@ public void testPolygon() { assertTrue(currentToken == WktParser.WktToken.not_available); } + @Test public void testLineString() { String s = " LineString emPty "; WktParser wktParser = new WktParser(); @@ -1022,6 +1028,7 @@ public void testLineString() { assertTrue(currentToken == WktParser.WktToken.not_available); } + @Test public void testPoint() { String s = " PoInT emPty "; WktParser wktParser = new WktParser(); From 6c3d5955ac99dff5e39adedce49b32414f8b8fba Mon Sep 17 00:00:00 2001 From: serg4066 Date: Fri, 18 Dec 2015 09:48:08 -0800 Subject: [PATCH 119/196] fix a bug in generalize for large deviations --- .../geometry/OperatorGeneralizeCursor.java | 6 +++--- .../esri/core/geometry/TestGeneralize.java | 21 +++++++++++++++++++ 2 files changed, 24 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/esri/core/geometry/OperatorGeneralizeCursor.java b/src/main/java/com/esri/core/geometry/OperatorGeneralizeCursor.java index 2b1d5ba1..5efc066d 100644 --- a/src/main/java/com/esri/core/geometry/OperatorGeneralizeCursor.java +++ b/src/main/java/com/esri/core/geometry/OperatorGeneralizeCursor.java @@ -64,8 +64,6 @@ private Geometry Generalize(Geometry geom) { if (geom.isEmpty()) return geom; MultiPath mp = (MultiPath) geom; - if (mp == null) - throw GeometryException.GeometryInternalError(); MultiPath dstmp = (MultiPath) geom.createInstance(); Line line = new Line(); for (int ipath = 0, npath = mp.getPathCount(); ipath < npath; ipath++) { @@ -113,7 +111,9 @@ private void GeneralizePath(MultiPathImpl mpsrc, int ipath, if (!bClosed) resultStack.add(stack.get(0)); - if (resultStack.size() == stack.size()) { + int rs_size = resultStack.size(); + int path_size = mpsrc.getPathSize(ipath); + if (rs_size == path_size && rs_size == stack.size()) { mpdst.addPath(mpsrc, ipath, true); } else { if (resultStack.size() >= 2) { diff --git a/src/test/java/com/esri/core/geometry/TestGeneralize.java b/src/test/java/com/esri/core/geometry/TestGeneralize.java index c7316e74..ced42b23 100644 --- a/src/test/java/com/esri/core/geometry/TestGeneralize.java +++ b/src/test/java/com/esri/core/geometry/TestGeneralize.java @@ -1,6 +1,7 @@ package com.esri.core.geometry; import junit.framework.TestCase; + import org.junit.Test; public class TestGeneralize extends TestCase { @@ -90,4 +91,24 @@ public static void test2() { assertTrue(points[0].x == 0 && points[0].y == 0); assertTrue(points[1].x == 0 && points[1].y == 10); } + + @Test + public static void testLargeDeviation() { + { + Polygon input_polygon = new Polygon(); + input_polygon + .addEnvelope(Envelope2D.construct(0, 0, 20, 10), false); + Geometry densified_geom = OperatorDensifyByLength.local().execute( + input_polygon, 1, null); + Geometry geom = OperatorGeneralize.local().execute(densified_geom, + 1, true, null); + int pc = ((MultiPath) geom).getPointCount(); + assertTrue(pc == 4); + + Geometry large_dev = OperatorGeneralize.local().execute( + densified_geom, 40, true, null); + int pc1 = ((MultiPath) large_dev).getPointCount(); + assertTrue(pc1 == 0); + } + } } From e7b03a982727be809515dcc2e330cb6fdd4d9b49 Mon Sep 17 00:00:00 2001 From: serg4066 Date: Mon, 28 Dec 2015 14:26:53 -0800 Subject: [PATCH 120/196] make sure to leave degenerate rings when requested --- .../esri/core/geometry/OperatorGeneralizeCursor.java | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/esri/core/geometry/OperatorGeneralizeCursor.java b/src/main/java/com/esri/core/geometry/OperatorGeneralizeCursor.java index 5efc066d..b0b31a5c 100644 --- a/src/main/java/com/esri/core/geometry/OperatorGeneralizeCursor.java +++ b/src/main/java/com/esri/core/geometry/OperatorGeneralizeCursor.java @@ -116,16 +116,18 @@ private void GeneralizePath(MultiPathImpl mpsrc, int ipath, if (rs_size == path_size && rs_size == stack.size()) { mpdst.addPath(mpsrc, ipath, true); } else { - if (resultStack.size() >= 2) { - if (m_bRemoveDegenerateParts && resultStack.size() == 2) { - if (bClosed) + if (resultStack.size() > 0) { + if (m_bRemoveDegenerateParts && resultStack.size() <= 2) { + if (bClosed || resultStack.size() == 1) return; + double d = Point2D.distance( mpsrc.getXY(resultStack.get(0)), mpsrc.getXY(resultStack.get(1))); if (d <= m_maxDeviation) return; } + Point point = new Point(); for (int i = 0, n = resultStack.size(); i < n; i++) { mpsrc.getPointByVal(resultStack.get(i), point); @@ -136,8 +138,9 @@ private void GeneralizePath(MultiPathImpl mpsrc, int ipath, } if (bClosed) { - if (!m_bRemoveDegenerateParts && resultStack.size() == 2) + for (int i = resultStack.size(); i < 3; i++) mpdst.lineTo(point); + mpdst.closePathWithLine(); } } From ce99bc5688094138cc9ae49a874ff7fe62b24dce Mon Sep 17 00:00:00 2001 From: serg4066 Date: Mon, 28 Dec 2015 15:36:36 -0800 Subject: [PATCH 121/196] added a test for generalize case --- src/test/java/com/esri/core/geometry/TestGeneralize.java | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/test/java/com/esri/core/geometry/TestGeneralize.java b/src/test/java/com/esri/core/geometry/TestGeneralize.java index ced42b23..5348d20d 100644 --- a/src/test/java/com/esri/core/geometry/TestGeneralize.java +++ b/src/test/java/com/esri/core/geometry/TestGeneralize.java @@ -105,10 +105,15 @@ public static void testLargeDeviation() { int pc = ((MultiPath) geom).getPointCount(); assertTrue(pc == 4); - Geometry large_dev = OperatorGeneralize.local().execute( + Geometry large_dev1 = OperatorGeneralize.local().execute( densified_geom, 40, true, null); - int pc1 = ((MultiPath) large_dev).getPointCount(); + int pc1 = ((MultiPath) large_dev1).getPointCount(); assertTrue(pc1 == 0); + + Geometry large_dev2 = OperatorGeneralize.local().execute( + densified_geom, 40, false, null); + int pc2 = ((MultiPath) large_dev2).getPointCount(); + assertTrue(pc2 == 3); } } } From 20224f98acd3c84d6364205ae127cca405776934 Mon Sep 17 00:00:00 2001 From: Randall Whitman Date: Mon, 4 Jan 2016 10:13:48 -0800 Subject: [PATCH 122/196] README: update copyright to 2016 --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 033b44c3..535a2da9 100644 --- a/README.md +++ b/README.md @@ -53,7 +53,7 @@ Find a bug or want to request a new feature? Please let us know by submitting a Esri welcomes contributions from anyone and everyone. Please see our [guidelines for contributing](https://github.com/esri/contributing) ## Licensing -Copyright 2013-2015 Esri +Copyright 2013-2016 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -67,7 +67,7 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. -A copy of the license is available in the repository's [license.txt]( https://raw.github.com/Esri/geometry-api-java/master/license.txt) file. +A copy of the license is available in the repository's [license.txt](https://raw.github.com/Esri/geometry-api-java/master/license.txt) file. [](Esri Tags: ArcGIS, Java, Geometry, Relationship, Analysis, JSON, WKT, Shape) [](Esri Language: Java) From 48cf546d25b44ff7bb8ecda64fe0e30fbb384623 Mon Sep 17 00:00:00 2001 From: serg4066 Date: Wed, 6 Jan 2016 14:57:23 -0800 Subject: [PATCH 123/196] fix geodist hang --- .../java/com/esri/core/geometry/GeoDist.java | 2 +- .../com/esri/core/geometry/TestGeodetic.java | 31 +++++++++++++++++++ 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/esri/core/geometry/GeoDist.java b/src/main/java/com/esri/core/geometry/GeoDist.java index 108e04e0..b7845151 100644 --- a/src/main/java/com/esri/core/geometry/GeoDist.java +++ b/src/main/java/com/esri/core/geometry/GeoDist.java @@ -257,7 +257,7 @@ static public void geodesic_distance_ngs(double a, double e2, double lam1, /* top of the long-line loop (kind = 1) */ q_continue_looping = true; - while (q_continue_looping == true) { + while (q_continue_looping && it < 100) { it = it + 1; if (kind == 1) { diff --git a/src/test/java/com/esri/core/geometry/TestGeodetic.java b/src/test/java/com/esri/core/geometry/TestGeodetic.java index 0cdbf6f7..9f0b9ed6 100644 --- a/src/test/java/com/esri/core/geometry/TestGeodetic.java +++ b/src/test/java/com/esri/core/geometry/TestGeodetic.java @@ -50,6 +50,37 @@ public void testRotationInvariance() { } } + @Test + public void testDistanceFailure() { + { + Point p1 = new Point(-60.668485, -31.996013333333334); + Point p2 = new Point(119.13731666666666, 32.251583333333336); + double d = GeometryEngine.geodesicDistanceOnWGS84(p1, p2); + assertTrue(Math.abs(d - 19973410.50579736) < 1e-13 * 19973410.50579736); + } + + { + Point p1 = new Point(121.27343833333333, 27.467438333333334); + Point p2 = new Point(-58.55804833333333, -27.035613333333334); + double d = GeometryEngine.geodesicDistanceOnWGS84(p1, p2); + assertTrue(Math.abs(d - 19954707.428360686) < 1e-13 * 19954707.428360686); + } + + { + Point p1 = new Point(-53.329865, -36.08110166666667); + Point p2 = new Point(126.52895166666667, 35.97385); + double d = GeometryEngine.geodesicDistanceOnWGS84(p1, p2); + assertTrue(Math.abs(d - 19990586.700431127) < 1e-13 * 19990586.700431127); + } + + { + Point p1 = new Point(-4.7181166667, 36.1160166667); + Point p2 = new Point(175.248925, -35.7606716667); + double d = GeometryEngine.geodesicDistanceOnWGS84(p1, p2); + assertTrue(Math.abs(d - 19964450.206594173) < 1e-12 * 19964450.206594173); + } + } + @Test public void testLengthAccurateCR191313() { /* From df0ce3b83daa29ca78b391f84bcdc71327180fb4 Mon Sep 17 00:00:00 2001 From: serg4066 Date: Wed, 6 Jan 2016 15:00:57 -0800 Subject: [PATCH 124/196] small change how point coords are passed --- .../java/com/esri/core/geometry/SpatialReferenceImpl.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/esri/core/geometry/SpatialReferenceImpl.java b/src/main/java/com/esri/core/geometry/SpatialReferenceImpl.java index 77520c40..32091d43 100644 --- a/src/main/java/com/esri/core/geometry/SpatialReferenceImpl.java +++ b/src/main/java/com/esri/core/geometry/SpatialReferenceImpl.java @@ -27,7 +27,6 @@ import java.util.HashMap; import java.util.Map; import java.util.concurrent.locks.ReentrantLock; - import java.lang.ref.*; import com.esri.core.geometry.Envelope2D; @@ -230,8 +229,8 @@ static double geodesicDistanceOnWGS84Impl(Point ptFrom, Point ptTo) { double e2 = 0.0066943799901413165; // ellipticity for WGS_1984 double rpu = Math.PI / 180.0; PeDouble answer = new PeDouble(); - GeoDist.geodesic_distance_ngs(a, e2, ptFrom.getXY().x * rpu, - ptFrom.getXY().y * rpu, ptTo.getXY().x * rpu, ptTo.getXY().y + GeoDist.geodesic_distance_ngs(a, e2, ptFrom.getX() * rpu, + ptFrom.getY() * rpu, ptTo.getX() * rpu, ptTo.getY() * rpu, answer, null, null); return answer.val; } From d9a9a3c2149b80ff438a1e46e501fa3828c9591a Mon Sep 17 00:00:00 2001 From: Sergey Tolstov Date: Thu, 10 Mar 2016 15:45:44 -0800 Subject: [PATCH 125/196] Update MultiVertexGeometryImpl.java Don't crash on toString() done on Impl classes. #112 --- .../java/com/esri/core/geometry/MultiVertexGeometryImpl.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/esri/core/geometry/MultiVertexGeometryImpl.java b/src/main/java/com/esri/core/geometry/MultiVertexGeometryImpl.java index 11ef7ed4..0c92a930 100644 --- a/src/main/java/com/esri/core/geometry/MultiVertexGeometryImpl.java +++ b/src/main/java/com/esri/core/geometry/MultiVertexGeometryImpl.java @@ -1110,6 +1110,9 @@ public abstract boolean _buildRasterizedGeometryAccelerator( public abstract boolean _buildQuadTreeAccelerator( GeometryAccelerationDegree d); - // //////////////////METHODS To REMOVE /////////////////////// + @Override + public String toString() { + return "MultiVertexGeometryImpl"; + } } From d08e90ccabadf00875887352e6aaadd9b99eaa34 Mon Sep 17 00:00:00 2001 From: Sergey Tolstov Date: Wed, 4 May 2016 09:49:13 -0700 Subject: [PATCH 126/196] buffer fix, geojson reqrite, more public methods --- .gitignore | 1 + .../core/geometry/AttributeStreamOfDbl.java | 20 +- .../java/com/esri/core/geometry/Bufferer.java | 895 ++++++--- .../com/esri/core/geometry/ConvexHull.java | 432 +++-- .../com/esri/core/geometry/ECoordinate.java | 14 +- .../com/esri/core/geometry/EditShape.java | 8 + .../java/com/esri/core/geometry/EnvSrlzr.java | 95 + .../java/com/esri/core/geometry/Envelope.java | 45 +- .../com/esri/core/geometry/Envelope1D.java | 10 +- .../com/esri/core/geometry/Envelope2D.java | 119 +- .../geometry/Envelope2DIntersectorImpl.java | 188 +- .../com/esri/core/geometry/Envelope3D.java | 145 +- .../geometry/GenericGeometrySerializer.java | 94 + .../java/com/esri/core/geometry/GeoDist.java | 2 +- .../esri/core/geometry/GeoJsonCrsTables.java | 183 ++ .../core/geometry/GeoJsonExportFlags.java | 29 +- .../core/geometry/GeoJsonImportFlags.java | 13 +- .../java/com/esri/core/geometry/Geometry.java | 44 +- .../esri/core/geometry/GeometryEngine.java | 1 + .../core/geometry/GeometrySerializer.java | 2 + .../esri/core/geometry/IntervalTreeImpl.java | 1708 ++++++++--------- .../core/geometry/JsonGeometryException.java | 40 + .../esri/core/geometry/JsonParserReader.java | 17 +- .../com/esri/core/geometry/JsonReader.java | 16 +- .../esri/core/geometry/JsonStringWriter.java | 58 +- .../esri/core/geometry/JsonValueReader.java | 17 +- .../com/esri/core/geometry/JsonWriter.java | 16 +- .../java/com/esri/core/geometry/Line.java | 14 +- .../java/com/esri/core/geometry/LnSrlzr.java | 93 + .../com/esri/core/geometry/MathUtils.java | 28 +- .../com/esri/core/geometry/MultiPath.java | 43 +- .../com/esri/core/geometry/MultiPathImpl.java | 25 +- .../com/esri/core/geometry/MultiPoint.java | 19 +- .../core/geometry/MultiVertexGeometry.java | 4 +- .../geometry/MultiVertexGeometryImpl.java | 11 +- .../com/esri/core/geometry/NumberUtils.java | 9 + .../esri/core/geometry/OperatorBuffer.java | 25 + .../core/geometry/OperatorBufferCursor.java | 18 +- .../core/geometry/OperatorBufferLocal.java | 27 +- .../geometry/OperatorConvexHullCursor.java | 127 +- .../geometry/OperatorExportToGeoJson.java | 60 +- .../OperatorExportToGeoJsonCursor.java | 1080 ++++++++--- .../OperatorExportToGeoJsonLocal.java | 51 +- .../geometry/OperatorExportToJsonCursor.java | 860 +++++---- .../core/geometry/OperatorFactoryLocal.java | 18 + .../core/geometry/OperatorGeodesicBuffer.java | 8 +- .../OperatorGeodeticDensifyByLength.java | 2 +- .../geometry/OperatorImportFromGeoJson.java | 39 +- .../OperatorImportFromGeoJsonLocal.java | 1581 ++++++++++++--- .../OperatorImportFromJsonCursor.java | 1 - .../core/geometry/OperatorIntersection.java | 6 +- .../OperatorShapePreservingDensify.java | 4 +- .../java/com/esri/core/geometry/Point.java | 27 +- .../java/com/esri/core/geometry/Point2D.java | 294 ++- .../java/com/esri/core/geometry/Point3D.java | 68 +- .../java/com/esri/core/geometry/Polygon.java | 4 +- .../com/esri/core/geometry/PolygonUtils.java | 53 +- .../java/com/esri/core/geometry/Polyline.java | 4 +- .../com/esri/core/geometry/PolylinePath.java | 77 - .../java/com/esri/core/geometry/PtSrlzr.java | 88 + .../java/com/esri/core/geometry/QuadTree.java | 272 ++- .../com/esri/core/geometry/QuadTreeImpl.java | 1053 +++++++--- .../geometry/RasterizedGeometry2DImpl.java | 61 +- .../java/com/esri/core/geometry/Segment.java | 67 +- .../esri/core/geometry/SegmentIterator.java | 12 +- .../esri/core/geometry/SimpleRasterizer.java | 44 + .../core/geometry/SpatialReferenceImpl.java | 72 +- .../esri/core/geometry/Transformation2D.java | 12 +- .../esri/core/geometry/Transformation3D.java | 27 +- .../esri/core/geometry/VertexDescription.java | 255 ++- .../VertexDescriptionDesignerImpl.java | 236 +-- .../core/geometry/VertexDescriptionHash.java | 72 +- .../com/esri/core/geometry/WktParser.java | 2 +- .../ogc/OGCConcreteGeometryCollection.java | 103 +- .../esri/core/geometry/ogc/OGCGeometry.java | 63 +- .../esri/core/geometry/new_to_old_wkid.txt | 1 + .../core/geometry/pcs_id_to_tolerance.txt | 1 + .../java/com/esri/core/geometry/TestClip.java | 10 +- .../esri/core/geometry/TestConvexHull.java | 365 ++-- .../esri/core/geometry/TestDifference.java | 4 + .../com/esri/core/geometry/TestGeodetic.java | 37 +- .../esri/core/geometry/TestGeomToGeoJson.java | 795 ++++---- .../esri/core/geometry/TestImportExport.java | 1054 +++++----- .../esri/core/geometry/TestIntersection.java | 46 + .../java/com/esri/core/geometry/TestOGC.java | 27 +- .../com/esri/core/geometry/TestPolygon.java | 104 +- .../com/esri/core/geometry/TestQuadTree.java | 361 +++- .../com/esri/core/geometry/TestRelation.java | 16 +- .../esri/core/geometry/TestSerialization.java | 180 +- .../core/geometry/TestSpatialReference.java | 17 +- .../java/com/esri/core/geometry/TestWkid.java | 1 + .../com/esri/core/geometry/savedEnvelope1.txt | Bin 0 -> 147 bytes .../esri/core/geometry/savedMultiPoint1.txt | Bin 0 -> 254 bytes .../com/esri/core/geometry/savedPoint1.txt | Bin 0 -> 138 bytes .../com/esri/core/geometry/savedPolygon1.txt | Bin 0 -> 317 bytes .../com/esri/core/geometry/savedPolyline1.txt | Bin 0 -> 301 bytes 96 files changed, 9314 insertions(+), 5036 deletions(-) create mode 100644 src/main/java/com/esri/core/geometry/EnvSrlzr.java create mode 100644 src/main/java/com/esri/core/geometry/GenericGeometrySerializer.java create mode 100644 src/main/java/com/esri/core/geometry/GeoJsonCrsTables.java create mode 100644 src/main/java/com/esri/core/geometry/JsonGeometryException.java create mode 100644 src/main/java/com/esri/core/geometry/LnSrlzr.java delete mode 100644 src/main/java/com/esri/core/geometry/PolylinePath.java create mode 100644 src/main/java/com/esri/core/geometry/PtSrlzr.java create mode 100644 src/test/resources/com/esri/core/geometry/savedEnvelope1.txt create mode 100644 src/test/resources/com/esri/core/geometry/savedMultiPoint1.txt create mode 100644 src/test/resources/com/esri/core/geometry/savedPoint1.txt create mode 100644 src/test/resources/com/esri/core/geometry/savedPolygon1.txt create mode 100644 src/test/resources/com/esri/core/geometry/savedPolyline1.txt diff --git a/.gitignore b/.gitignore index 85898c5a..f31512c4 100644 --- a/.gitignore +++ b/.gitignore @@ -33,3 +33,4 @@ Icon? ehthumbs.db Thumbs.db target/* +/bin/ diff --git a/src/main/java/com/esri/core/geometry/AttributeStreamOfDbl.java b/src/main/java/com/esri/core/geometry/AttributeStreamOfDbl.java index 18ae0fc1..bbf40fde 100644 --- a/src/main/java/com/esri/core/geometry/AttributeStreamOfDbl.java +++ b/src/main/java/com/esri/core/geometry/AttributeStreamOfDbl.java @@ -89,7 +89,7 @@ public AttributeStreamOfDbl(AttributeStreamOfDbl other, int maxSize) { /** * Reads a value from the buffer at given offset. - * + * * @param offset * is the element number in the stream. */ @@ -103,7 +103,7 @@ public double get(int offset) { /** * Overwrites given element with new value. - * + * * @param offset * is the element number in the stream. * @param value @@ -125,7 +125,7 @@ public void set(int offset, double value) { /** * Reads a value from the buffer at given offset. - * + * * @param offset * is the element number in the stream. */ @@ -136,7 +136,7 @@ public void read(int offset, Point2D outPoint) { /** * Overwrites given element with new value. - * + * * @param offset * is the element number in the stream. * @param value @@ -213,7 +213,7 @@ public void resize(int newSize) { if (newSize <= m_size) { if ((newSize * 5) / 4 < m_buffer.length) {// decrease when the 25% - // margin is exceeded + // margin is exceeded double[] newBuffer = new double[newSize]; System.arraycopy(m_buffer, 0, newBuffer, 0, newSize); m_buffer = newBuffer; @@ -251,7 +251,7 @@ public void resize(int newSize, double defaultValue) { "invalid call. Attribute Stream is locked and cannot be resized."); if (newSize <= m_size) { if ((newSize * 5) / 4 < m_buffer.length) {// decrease when the 25% - // margin is exceeded + // margin is exceeded double[] newBuffer = new double[newSize]; System.arraycopy(m_buffer, 0, newBuffer, 0, newSize); m_buffer = newBuffer; @@ -579,8 +579,8 @@ public void eraseRange(int index, int count, int validSize) { throw new GeometryException("invalid_call"); if (validSize - (index + count) > 0) { - System.arraycopy(m_buffer, index + count, m_buffer, index, validSize - - (index + count)); + System.arraycopy(m_buffer, index + count, m_buffer, index, + validSize - (index + count)); } m_size -= count; } @@ -656,8 +656,8 @@ public void writeRange(int startElement, int count, throw new IllegalArgumentException(); AttributeStreamOfDbl src = (AttributeStreamOfDbl) _src; // the input - // type must - // match + // type must + // match if (src.size() < (int) (srcStart + count)) throw new IllegalArgumentException(); diff --git a/src/main/java/com/esri/core/geometry/Bufferer.java b/src/main/java/com/esri/core/geometry/Bufferer.java index ebe961ce..b7008559 100644 --- a/src/main/java/com/esri/core/geometry/Bufferer.java +++ b/src/main/java/com/esri/core/geometry/Bufferer.java @@ -24,13 +24,32 @@ package com.esri.core.geometry; import java.util.ArrayList; +import java.util.List; class Bufferer { + Bufferer() { + m_buffer_commands = new ArrayList(128); + m_progress_tracker = null; + m_tolerance = 0; + m_small_tolerance = 0; + m_filter_tolerance = 0; + m_distance = 0; + m_original_geom_type = Geometry.GeometryType.Unknown; + m_abs_distance_reversed = 0; + m_abs_distance = 0; + m_densify_dist = -1; + m_dA = -1; + m_b_output_loops = true; + m_bfilter = true; + m_old_circle_template_size = 0; + } + + /** * Result is always a polygon. For non positive distance and non-areas * returns an empty polygon. For points returns circles. */ - static Geometry buffer(Geometry geometry, double distance, + Geometry buffer(Geometry geometry, double distance, SpatialReference sr, double densify_dist, int max_vertex_in_complete_circle, ProgressTracker progress_tracker) { if (geometry == null) @@ -47,34 +66,36 @@ static Geometry buffer(Geometry geometry, double distance, if (distance > 0) env2D.inflate(distance, distance); - Bufferer bufferer = new Bufferer(progress_tracker); - bufferer.m_spatialReference = sr; - bufferer.m_geometry = geometry; - bufferer.m_tolerance = InternalUtils.calculateToleranceFromGeometry(sr, + m_progress_tracker = progress_tracker; + + m_original_geom_type = geometry.getType().value(); + m_geometry = geometry; + m_tolerance = InternalUtils.calculateToleranceFromGeometry(sr, env2D, true);// conservative to have same effect as simplify - bufferer.m_small_tolerance = InternalUtils + m_small_tolerance = InternalUtils .calculateToleranceFromGeometry(null, env2D, true);// conservative // to have // same // effect as // simplify - bufferer.m_distance = distance; - bufferer.m_original_geom_type = geometry.getType().value(); + if (max_vertex_in_complete_circle <= 0) { max_vertex_in_complete_circle = 96;// 96 is the value used by SG. // This is the number of // vertices in the full circle. } - - bufferer.m_abs_distance = Math.abs(bufferer.m_distance); - bufferer.m_abs_distance_reversed = bufferer.m_abs_distance != 0 ? 1.0 / bufferer.m_abs_distance + + m_spatialReference = sr; + m_distance = distance; + m_abs_distance = Math.abs(m_distance); + m_abs_distance_reversed = m_abs_distance != 0 ? 1.0 / m_abs_distance : 0; if (NumberUtils.isNaN(densify_dist) || densify_dist == 0) { - densify_dist = bufferer.m_abs_distance * 1e-5; + densify_dist = m_abs_distance * 1e-5; } else { - if (densify_dist > bufferer.m_abs_distance * 0.5) - densify_dist = bufferer.m_abs_distance * 0.5;// do not allow too + if (densify_dist > m_abs_distance * 0.5) + densify_dist = m_abs_distance * 0.5;// do not allow too // large densify // distance (the // value will be @@ -85,6 +106,7 @@ static Geometry buffer(Geometry geometry, double distance, if (max_vertex_in_complete_circle < 12) max_vertex_in_complete_circle = 12; + double max_dd = Math.abs(distance) * (1 - Math.cos(Math.PI / max_vertex_in_complete_circle)); @@ -105,13 +127,26 @@ static Geometry buffer(Geometry geometry, double distance, } } - bufferer.m_densify_dist = densify_dist; - bufferer.m_max_vertex_in_complete_circle = max_vertex_in_complete_circle; + m_densify_dist = densify_dist; + m_max_vertex_in_complete_circle = max_vertex_in_complete_circle; // when filtering close points we do not want the filter to distort // generated buffer too much. - bufferer.m_filter_tolerance = Math.min(bufferer.m_small_tolerance, + m_filter_tolerance = Math.min(m_small_tolerance, densify_dist * 0.25); - return bufferer.buffer_(); + + + m_circle_template_size = calcN_(); + if (m_circle_template_size != m_old_circle_template_size) { + // we have an optimization for this method to be called several + // times. Here we detected too many changes and need to regenerate + // the data. + m_circle_template.clear(); + m_old_circle_template_size = m_circle_template_size; + } + + Geometry result_geom = buffer_(); + m_geometry = null; + return result_geom; } private Geometry m_geometry; @@ -120,8 +155,6 @@ private static final class BufferCommand { private interface Flags { static final int enum_line = 1; static final int enum_arc = 2; - static final int enum_dummy = 4; - static final int enum_concave_dip = 8; static final int enum_connection = enum_arc | enum_line; } @@ -175,22 +208,22 @@ private BufferCommand(Point2D from, Point2D to, int next, int prev, private double m_dA; private boolean m_b_output_loops; private boolean m_bfilter; - private ArrayList m_circle_template; + private ArrayList m_circle_template = new ArrayList(0); private ArrayList m_left_stack; private ArrayList m_middle_stack; private Line m_helper_line_1; private Line m_helper_line_2; private Point2D[] m_helper_array; private int m_progress_counter; + private int m_circle_template_size; + private int m_old_circle_template_size; private void generateCircleTemplate_() { - if (m_circle_template == null) { - m_circle_template = new ArrayList(0); - } else if (!m_circle_template.isEmpty()) { + if (!m_circle_template.isEmpty()) { return; } - int N = calcN_(4); + int N = m_circle_template_size; assert (N >= 4); int real_size = (N + 3) / 4; @@ -218,6 +251,7 @@ private void generateCircleTemplate_() { private static final class GeometryCursorForMultiPoint extends GeometryCursor { + private Bufferer m_parent; private int m_index; private Geometry m_buffered_polygon; private MultiPoint m_mp; @@ -229,10 +263,11 @@ private static final class GeometryCursorForMultiPoint extends private int m_max_vertex_in_complete_circle; private ProgressTracker m_progress_tracker; - GeometryCursorForMultiPoint(MultiPoint mp, double distance, + GeometryCursorForMultiPoint(Bufferer parent, MultiPoint mp, double distance, SpatialReference sr, double densify_dist, int max_vertex_in_complete_circle, ProgressTracker progress_tracker) { + m_parent = parent; m_index = 0; m_mp = mp; m_x = 0; @@ -263,7 +298,7 @@ public Geometry next() { m_x = point.getX(); m_y = point.getY(); - m_buffered_polygon = Bufferer.buffer(point, m_distance, + m_buffered_polygon = m_parent.buffer(point, m_distance, m_spatialReference, m_densify_dist, m_max_vertex_in_complete_circle, m_progress_tracker); b_first = true; @@ -295,64 +330,113 @@ public int getGeometryID() { } } - private static final class GeometryCursorForPolyline extends GeometryCursor { - private Bufferer m_bufferer; - private int m_index; - private boolean m_bfilter; + private static final class GlueingCursorForPolyline extends GeometryCursor { + private Polyline m_polyline; + private int m_current_path_index; - GeometryCursorForPolyline(Bufferer bufferer, boolean bfilter) { - m_bufferer = bufferer; - m_index = 0; - m_bfilter = bfilter; + GlueingCursorForPolyline(Polyline polyline) { + m_polyline = polyline; + m_current_path_index = 0; } @Override public Geometry next() { - MultiPathImpl mp = (MultiPathImpl) (m_bufferer.m_geometry - ._getImpl()); - if (m_index < mp.getPathCount()) { - int ind = m_index; - m_index++; + if (m_polyline == null) + return null; + + MultiPathImpl mp = (MultiPathImpl) m_polyline._getImpl(); + int npaths = mp.getPathCount(); + if (m_current_path_index < npaths) { + int ind = m_current_path_index; + m_current_path_index++; if (!mp.isClosedPathInXYPlane(ind)) { + // connect paths that follow one another as an optimization + // for buffering (helps when one polyline is split into many + // segments). Point2D prev_end = mp.getXY(mp.getPathEnd(ind) - 1); - while (m_index < mp.getPathCount()) { - Point2D start = mp.getXY(mp.getPathStart(m_index)); - if (mp.isClosedPathInXYPlane(m_index)) + while (m_current_path_index < mp.getPathCount()) { + Point2D start = mp.getXY(mp + .getPathStart(m_current_path_index)); + if (mp.isClosedPathInXYPlane(m_current_path_index)) break; if (start != prev_end) break; - prev_end = mp.getXY(mp.getPathEnd(m_index) - 1); - m_index++; + prev_end = mp + .getXY(mp.getPathEnd(m_current_path_index) - 1); + m_current_path_index++; } } - if (m_index - ind == 1) - return m_bufferer.bufferPolylinePath_( - (Polyline) (m_bufferer.m_geometry), ind, m_bfilter); - else { - Polyline tmp_polyline = new Polyline( - m_bufferer.m_geometry.getDescription()); - tmp_polyline.addPath((Polyline) (m_bufferer.m_geometry), - ind, true); - for (int i = ind + 1; i < m_index; i++) { - ((MultiPathImpl) tmp_polyline._getImpl()) - .addSegmentsFromPath( - (MultiPathImpl) m_bufferer.m_geometry - ._getImpl(), i, 0, mp - .getSegmentCount(i), false); - } - // Operator_factory_local::SaveJSONToTextFileDbg("c:/temp/buffer_ppp.txt", - // tmp_polyline, nullptr); - Polygon res = m_bufferer.bufferPolylinePath_(tmp_polyline, - 0, m_bfilter); - // Operator_factory_local::SaveJSONToTextFileDbg("c:/temp/buffer_ppp_res.txt", - // *res, nullptr); - return res; + if (ind == 0 + && m_current_path_index == m_polyline.getPathCount()) { + Polyline pol = m_polyline; + m_polyline = null; + return pol; } + + Polyline tmp_polyline = new Polyline( + m_polyline.getDescription()); + tmp_polyline.addPath(m_polyline, ind, true); + for (int i = ind + 1; i < m_current_path_index; i++) { + tmp_polyline.addSegmentsFromPath(m_polyline, i, 0, + mp.getSegmentCount(i), false); + } + + if (false) { + OperatorFactoryLocal.saveGeometryToEsriShapeDbg( + "c:/temp/_geom.bin", tmp_polyline); + } + + if (m_current_path_index == m_polyline.getPathCount()) + m_polyline = null; + + return tmp_polyline; + } else { + return null; } + } - return null; + @Override + public int getGeometryID() { + return 0; + } + } + + private static final class GeometryCursorForPolyline extends GeometryCursor { + private Bufferer m_bufferer; + GeometryCursor m_geoms; + Geometry m_geometry; + private int m_index; + private boolean m_bfilter; + + GeometryCursorForPolyline(Bufferer bufferer, GeometryCursor geoms, + boolean bfilter) { + m_bufferer = bufferer; + m_geoms = geoms; + m_index = 0; + m_bfilter = bfilter; + } + + @Override + public Geometry next() { + if (m_geometry == null) { + m_index = 0; + m_geometry = m_geoms.next(); + if (m_geometry == null) + return null; + } + + MultiPath mp = (MultiPath) (m_geometry); + if (m_index < mp.getPathCount()) { + int ind = m_index; + m_index++; + return m_bufferer.bufferPolylinePath_((Polyline) m_geometry, + ind, m_bfilter); + } + + m_geometry = null; + return next(); } @Override @@ -404,22 +488,6 @@ public int getGeometryID() { } } - private Bufferer(ProgressTracker progress_tracker) { - m_buffer_commands = new ArrayList(0); - m_progress_tracker = progress_tracker; - m_tolerance = 0; - m_small_tolerance = 0; - m_filter_tolerance = 0; - m_distance = 0; - m_original_geom_type = Geometry.GeometryType.Unknown; - m_abs_distance_reversed = 0; - m_abs_distance = 0; - m_densify_dist = -1; - m_dA = -1; - m_b_output_loops = true; - m_bfilter = true; - } - private Geometry buffer_() { int gt = m_geometry.getType().value(); if (Geometry.isSegment(gt)) {// convert segment to a polyline and repeat @@ -485,13 +553,15 @@ private Geometry bufferPolyline_() { } assert (m_distance > 0); - m_geometry = preparePolyline_((Polyline) (m_geometry)); - - GeometryCursorForPolyline cursor = new GeometryCursorForPolyline(this, - m_bfilter); - GeometryCursor union_cursor = ((OperatorUnion) OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.Union)).execute( - cursor, m_spatialReference, m_progress_tracker); + Polyline poly = (Polyline)m_geometry; m_geometry = null; + + GeometryCursor glueing_cursor = new GlueingCursorForPolyline(poly);//glues paths together if they connect at one point + poly = null; + GeometryCursor generalized_paths = OperatorGeneralize.local().execute(glueing_cursor, m_densify_dist * 0.25, false, m_progress_tracker); + GeometryCursor simple_paths = OperatorSimplifyOGC.local().execute(generalized_paths, null, true, m_progress_tracker);//make a planar graph. + generalized_paths = null; + GeometryCursor path_buffering_cursor = new GeometryCursorForPolyline(this, simple_paths, m_bfilter); simple_paths = null; + GeometryCursor union_cursor = OperatorUnion.local().execute(path_buffering_cursor, m_spatialReference, m_progress_tracker);//(int)Operator_union::Options::enum_disable_edge_dissolver Geometry result = union_cursor.next(); return result; } @@ -742,7 +812,7 @@ private Geometry bufferPoint_(Point point) { private Geometry bufferMultiPoint_() { assert (m_distance > 0); - GeometryCursorForMultiPoint mpCursor = new GeometryCursorForMultiPoint( + GeometryCursorForMultiPoint mpCursor = new GeometryCursorForMultiPoint(this, (MultiPoint) (m_geometry), m_distance, m_spatialReference, m_densify_dist, m_max_vertex_in_complete_circle, m_progress_tracker); @@ -861,8 +931,6 @@ private Polygon bufferPolylinePath_(Polyline polyline, int ipath, } Polyline result_polyline = new Polyline(polyline.getDescription()); - // result_polyline.reserve((m_circle_template.size() / 10 + 4) * - // mp_impl.getPathSize(ipath)); MultiPathImpl result_mp = (MultiPathImpl) result_polyline._getImpl(); boolean b_closed = mp_impl.isClosedPathInXYPlane(ipath); @@ -877,8 +945,6 @@ private Polygon bufferPolylinePath_(Polyline polyline, int ipath, (MultiPathImpl) input_multi_path._getImpl(), ipath, 0, input_multi_path.getSegmentCount(ipath), false); bufferClosedPath_(tmpPoly, 0, result_mp, bfilter, 1); - // Operator_factory_local::SaveJSONToTextFileDbg("c:/temp/buffer_prepare.txt", - // *result_polyline, nullptr); } return bufferCleanup_(result_polyline, false); @@ -902,7 +968,9 @@ private Polygon bufferCleanup_(MultiPath multi_path, boolean simplify_result) { return resultPolygon; } - private int calcN_(int minN) { + private int calcN_() { + //this method should be called only once m_circle_template_size is set then; + final int minN = 4; if (m_densify_dist == 0) return m_max_vertex_in_complete_circle; @@ -998,7 +1066,7 @@ private int bufferClosedPath_(Geometry input_geom, int ipath, ipath, true); edit_shape.filterClosePoints(m_filter_tolerance, false, false); if (edit_shape.getPointCount(geom) < 2) {// Got degenerate output. - // Wither bail out or + // Either bail out or // produce a circle. if (dir < 0) return 1;// negative direction produces nothing. @@ -1023,7 +1091,7 @@ private int bufferClosedPath_(Geometry input_geom, int ipath, if (bfilter) { // try removing the noise that does not contribute to the buffer. - int res_filter = filterPath_(edit_shape, geom, dir, true); + int res_filter = filterPath_(edit_shape, geom, dir, true, m_abs_distance, m_filter_tolerance, m_densify_dist); assert (res_filter == 1); // Operator_factory_local::SaveJSONToTextFileDbg("c:/temp/buffer_filter.txt", // *edit_shape.get_geometry(geom), nullptr); @@ -1216,228 +1284,484 @@ private int cleanupBufferCommands_() { return istart; } - private boolean isGap_(Point2D pt_before, Point2D pt_current, - Point2D pt_after) { - Point2D v_gap = new Point2D(); - v_gap.sub(pt_after, pt_before); - double gap_length = v_gap.length(); - double sqr_delta = m_abs_distance * m_abs_distance - gap_length - * gap_length * 0.25; - if (sqr_delta > 0) { - double delta = Math.sqrt(sqr_delta); - v_gap.normalize(); - v_gap.rightPerpendicular(); - Point2D p = new Point2D(); - p.sub(pt_current, pt_before); - double d = p.dotProduct(v_gap); - if (d + delta >= m_abs_distance) { - return true; + private static void protectExtremeVertices_(EditShape edit_shape, + int protection_index, int geom, int path) { + // detect very narrow corners and preserve them. We cannot reliably + // delete these. + int vprev = -1; + Point2D pt_prev = new Point2D(); + pt_prev.setNaN(); + Point2D pt = new Point2D(); + pt.setNaN(); + Point2D v_before = new Point2D(); + v_before.setNaN(); + Point2D pt_next = new Point2D(); + Point2D v_after = new Point2D(); + for (int i = 0, n = edit_shape.getPathSize(path), v = edit_shape + .getFirstVertex(path); i < n; ++i) { + if (vprev == -1) { + edit_shape.getXY(v, pt); + + vprev = edit_shape.getPrevVertex(v); + if (vprev != -1) { + edit_shape.getXY(vprev, pt_prev); + v_before.sub(pt, pt_prev); + v_before.normalize(); + } } - } - return false; + int vnext = edit_shape.getNextVertex(v); + if (vnext == -1) + break; + + edit_shape.getXY(vnext, pt_next); + v_after.sub(pt_next, pt); + v_after.normalize(); + + if (vprev != -1) { + double d = v_after.dotProduct(v_before); + if (d < -0.99 + && Math.abs(v_after.crossProduct(v_before)) < 1e-7) { + edit_shape.setUserIndex(v, protection_index, 1); + } + } + + vprev = v; + v = vnext; + pt_prev.setCoords(pt); + pt.setCoords(pt_next); + v_before.setCoords(v_after); + } } + + static private int filterPath_(EditShape edit_shape, int geom, int dir, + boolean closed, double abs_distance, double filter_tolerance, + double densify_distance) { + int path = edit_shape.getFirstPath(geom); - private int filterPath_(EditShape edit_shape, int geom, int dir, - boolean closed) { - // **********************!!!!!!!!!!!!!!!!!!!!!!!!!!!! - // return 1; - - boolean bConvex = true; - for (int pass = 0; pass < 1; pass++) { - boolean b_filtered = false; - int ipath = edit_shape.getFirstPath(geom); - int isize = edit_shape.getPathSize(ipath); - if (isize == 0) - return 0; + int concave_index = -1; + int fixed_vertices_index = edit_shape.createUserIndex(); + protectExtremeVertices_(edit_shape, fixed_vertices_index, geom, path); + + for (int iter = 0; iter < 100; ++iter) { + int isize = edit_shape.getPathSize(path); + if (isize == 0) { + edit_shape.removeUserIndex(fixed_vertices_index); + ; + return 1; + } - int ncount = isize; - if (isize < 3) + int ivert = edit_shape.getFirstVertex(path); + int nvertices = edit_shape.getPathSize(path); + if (nvertices < 3) { + edit_shape.removeUserIndex(fixed_vertices_index); + ; return 1; + } - if (closed && !edit_shape.isClosedPath(ipath))// the path is closed + if (closed && !edit_shape.isClosedPath(path))// the path is closed // only virtually { - ncount = isize - 1; + nvertices -= 1; } - assert (dir == 1 || dir == -1); - int ivert = edit_shape.getFirstVertex(ipath); - if (!closed) - edit_shape.getNextVertex(ivert); + double abs_d = abs_distance; + final int nfilter = 64; + boolean filtered = false; + int filtered_in_pass = 0; + boolean go_back = false; + for (int i = 0; i < nvertices && ivert != -1; i++) { + int filtered_now = 0; + int v = ivert; // filter == 0 + for (int filter = 1, n = (int) Math.min(nfilter, nvertices - i); filter < n; filter++) { + v = edit_shape.getNextVertex(v, dir); + if (filter > 1) { + int num = clipFilter_(edit_shape, + fixed_vertices_index, ivert, v, dir, + abs_distance, densify_distance, nfilter); + if (num == -1) + break; - int iprev = dir > 0 ? edit_shape.getPrevVertex(ivert) : edit_shape - .getNextVertex(ivert); - int inext = dir > 0 ? edit_shape.getNextVertex(ivert) : edit_shape - .getPrevVertex(ivert); - int ibefore = iprev; - boolean reload = true; - Point2D pt_current = new Point2D(), pt_after = new Point2D(), pt_before = new Point2D(), pt_before_before = new Point2D(), pt_middle = new Point2D(), pt_gap_last = new Point2D( - 0, 0); - Point2D v_after = new Point2D(), v_before = new Point2D(), v_gap = new Point2D(); - Point2D temp = new Point2D(); - double abs_d = m_abs_distance; - // When the path is open we cannot process the first and the last - // vertices, so we process size - 2. - // When the path is closed, we can process all vertices. - int iter_count = closed ? ncount : isize - 2; - int gap_counter = 0; - for (int iter = 0; iter < iter_count;) { - edit_shape.getXY(inext, pt_after); - - if (reload) { - edit_shape.getXY(ivert, pt_current); - edit_shape.getXY(iprev, pt_before); - ibefore = iprev; + filtered |= num > 0; + filtered_now += num; + nvertices -= num; + } } - v_before.sub(pt_current, pt_before); - v_before.normalize(); + filtered_in_pass += filtered_now; - v_after.sub(pt_after, pt_current); - v_after.normalize(); + go_back = filtered_now > 0; - if (ibefore == inext) { + if (go_back) { + int prev = edit_shape.getPrevVertex(ivert, dir); + if (prev != -1) { + ivert = prev; + nvertices++; + continue; + } + } + + ivert = edit_shape.getNextVertex(ivert, dir); + } + + if (filtered_in_pass == 0) + break; + } + + edit_shape.removeUserIndex(fixed_vertices_index); + edit_shape.filterClosePoints(filter_tolerance, false, false); + + return 1; + } + + // This function clips out segments connecting from_vertiex to to_vertiex if + // they do not contribute to the buffer. + private static int clipFilter_(EditShape edit_shape, + int fixed_vertices_index, int from_vertex, int to_vertex, int dir, + double abs_distance, double densify_distance, final int max_filter) { + // Note: vertices marked with fixed_vertices_index cannot be deleted. + + Point2D pt1 = edit_shape.getXY(from_vertex); + Point2D pt2 = edit_shape.getXY(to_vertex); + if (pt1.equals(pt2)) + return -1; + + double densify_distance_delta = densify_distance * 0.25;// distance by + // which we can + // move the + // point closer + // to the chord + // (introducing + // an error into + // the buffer). + double erase_distance_delta = densify_distance * 0.25;// distance when + // we can erase + // the point + // (introducing + // an error into + // the buffer). + // This function goal is to modify or remove vertices between + // from_vertex and to_vertex in such a way that the result would not + // affect buffer to the left of the + // chain. + Point2D v_gap = new Point2D(); + v_gap.sub(pt2, pt1); + double gap_length = v_gap.length(); + double h2_4 = gap_length * gap_length * 0.25; + double sqr_center_to_chord = abs_distance * abs_distance - h2_4; // squared + // distance + // from + // the + // chord + // to + // the + // circle + // center + if (sqr_center_to_chord <= h2_4) + return -1;// center to chord distance is less than half gap, that + // means the gap is too wide for useful filtering (maybe + // this). + + double center_to_chord = Math.sqrt(sqr_center_to_chord); // distance + // from + // circle + // center to + // the + // chord. + + v_gap.normalize(); + Point2D v_gap_norm = new Point2D(v_gap); + v_gap_norm.rightPerpendicular(); + double chord_to_corner = h2_4 / center_to_chord; // cos(a) = + // center_to_chord / + // distance; + // chord_to_corner = + // distance / cos(a) + // - + // center_to_chord; + boolean can_erase_corner_point = chord_to_corner <= erase_distance_delta; + Point2D chord_midpoint = new Point2D(); + MathUtils.lerp(pt2, pt1, 0.5, chord_midpoint); + Point2D corner = new Point2D(v_gap_norm); + double corrected_chord_to_corner = chord_to_corner + - densify_distance_delta;// using slightly smaller than needed + // distance let us filter more. + corner.scaleAdd(Math.max(0.0, corrected_chord_to_corner), + chord_midpoint); + // corner = (p1 + p2) * 0.5 + v_gap_norm * chord_to_corner; + + Point2D center = new Point2D(v_gap_norm); + center.negate(); + center.scaleAdd(center_to_chord, chord_midpoint); + + double allowed_distance = abs_distance - erase_distance_delta; + double sqr_allowed_distance = MathUtils.sqr(allowed_distance); + double sqr_large_distance = sqr_allowed_distance * (1.9 * 1.9); + + Point2D co_p1 = new Point2D(); + co_p1.sub(corner, pt1); + Point2D co_p2 = new Point2D(); + co_p2.sub(corner, pt2); + + boolean large_distance = false;// set to true when distance + int cnt = 0; + char[] locations = new char[64]; + { + // check all vertices in the gap verifying that the gap can be + // clipped. + // + + Point2D pt = new Point2D(); + // firstly remove any duplicate vertices in the end. + for (int v = edit_shape.getPrevVertex(to_vertex, dir); v != from_vertex;) { + if (edit_shape.getUserIndex(v, fixed_vertices_index) == 1) + return -1;// this range contains protected vertex + + edit_shape.getXY(v, pt); + if (pt.equals(pt2)) { + int v1 = edit_shape.getPrevVertex(v, dir); + edit_shape.removeVertex(v, false); + v = v1; + continue; + } else { break; } + } - double cross = v_before.crossProduct(v_after); - double dot = v_before.dotProduct(v_after); - boolean bDoJoin = cross < 0 || (dot < 0 && cross == 0); - boolean b_write = true; - if (!bDoJoin) { - if (isGap_(pt_before, pt_current, pt_after)) { - pt_gap_last.setCoords(pt_after); - b_write = false; - ++gap_counter; - b_filtered = true; - } + Point2D prev_prev_pt = new Point2D(); + prev_prev_pt.setNaN(); + Point2D prev_pt = new Point2D(); + prev_pt.setCoords(pt1); + locations[cnt++] = 1; + int prev_v = from_vertex; + Point2D dummyPt = new Point2D(); + for (int v = edit_shape.getNextVertex(from_vertex, dir); v != to_vertex;) { + if (edit_shape.getUserIndex(v, fixed_vertices_index) == 1) + return -1;// this range contains protected vertex + + edit_shape.getXY(v, pt); + if (pt.equals(prev_pt)) { + int v1 = edit_shape.getNextVertex(v, dir); + edit_shape.removeVertex(v, false); + v = v1; + continue; + } - bConvex = false; + locations[cnt++] = 0; + + Point2D v1 = new Point2D(); + v1.sub(pt, pt1); + if (v1.dotProduct(v_gap_norm) < 0)// we are crossing on the + // wrong site of the chord. + // Just bail out earlier. + // Maybe we could continue + // clipping though here, but + // it seems to be + // unnecessary complicated. + return 0; + + if (Point2D.sqrDistance(pt, pt1) > sqr_large_distance + || Point2D.sqrDistance(pt, pt2) > sqr_large_distance) + large_distance = true; // too far from points, may + // contribute to the outline (in + // case of a large loop) + + char next_location = 0; + + dummyPt.sub(pt, pt1); + double cs1 = dummyPt.crossProduct(co_p1); + if (cs1 >= 0) { + next_location = 1; } - if (b_write) { - if (gap_counter > 0) { - for (;;) {// re-test back - int ibefore_before = dir > 0 ? edit_shape - .getPrevVertex(ibefore) : edit_shape - .getNextVertex(ibefore); - if (ibefore_before == ivert) - break; - - edit_shape.getXY(ibefore_before, pt_before_before); - if (isGap_(pt_before_before, pt_before, pt_gap_last)) { - pt_before.setCoords(pt_before_before); - ibefore = ibefore_before; - b_write = false; - ++gap_counter; - continue; - } else { - if (ibefore_before != inext - && isGap_(pt_before_before, pt_before, - pt_after) - && isGap_(pt_before_before, pt_current, - pt_after)) {// now the current - // point is a part - // of the gap also. - // We retest it. - pt_before.setCoords(pt_before_before); - ibefore = ibefore_before; - b_write = false; - ++gap_counter; - } - } - break; - } - } + dummyPt.sub(pt, pt2); + double cs2 = dummyPt.crossProduct(co_p2); + if (cs2 <= 0) { + next_location |= 2; + } - if (!b_write) - continue;// retest forward - - if (gap_counter > 0) { - // remove all but one gap vertices. - int p = dir > 0 ? edit_shape.getPrevVertex(iprev) - : edit_shape.getNextVertex(iprev); - for (int i = 1; i < gap_counter; i++) { - int pp = dir > 0 ? edit_shape.getPrevVertex(p) - : edit_shape.getNextVertex(p); - edit_shape.removeVertex(p, true); - p = pp; - } + if (next_location == 0) + return 0; - v_gap.sub(pt_current, pt_before); - double gap_length = v_gap.length(); - double sqr_delta = abs_d * abs_d - gap_length - * gap_length * 0.25; - double delta = Math.sqrt(sqr_delta); - if (abs_d - delta > m_densify_dist * 0.5) { - pt_middle.add(pt_before, pt_current); - pt_middle.scale(0.5); - v_gap.normalize(); - v_gap.rightPerpendicular(); - temp.setCoords(v_gap); - temp.scale(abs_d - delta); - pt_middle.add(temp); - edit_shape.setXY(iprev, pt_middle); - } else { - // the gap is too short to be considered. Can close - // it with the straight segment; - edit_shape.removeVertex(iprev, true); - } + locations[cnt - 1] = next_location; + prev_prev_pt.setCoords(prev_pt); + prev_pt.setCoords(pt); + prev_v = v; + v = edit_shape.getNextVertex(v, dir); + } - gap_counter = 0; - } + if (cnt == 1) + return 0; - pt_before.setCoords(pt_current); - ibefore = ivert; - } + assert (!pt2.equals(prev_pt)); + locations[cnt++] = 2; + } - pt_current.setCoords(pt_after); - iprev = ivert; - ivert = inext; - // reload = false; - inext = dir > 0 ? edit_shape.getNextVertex(ivert) : edit_shape - .getPrevVertex(ivert); - iter++; - reload = false; + boolean can_clip_all = true; + // we can remove all points and replace them with a single corner point + // if we are moving from location 1 via location 3 to location 2 + for (int i = 1, k = 0; i < cnt; i++) { + if (locations[i] != locations[i - 1]) { + k++; + can_clip_all = k < 3 + && ((k == 1 && locations[i] == 3) || (k == 2 && locations[i] == 2)); + if (!can_clip_all) + return 0; + } + } + + if (cnt > 2 && can_clip_all && (cnt == 3 || !large_distance)) { + int clip_count = 0; + int v = edit_shape.getNextVertex(from_vertex, dir); + if (!can_erase_corner_point) { + edit_shape.setXY(v, corner); + v = edit_shape.getNextVertex(v, dir); + } + + // we can remove all vertices between from and to, because they + // don't contribute + while (v != to_vertex) { + int v1 = edit_shape.getNextVertex(v, dir); + edit_shape.removeVertex(v, false); + v = v1; + ++clip_count; } - if (gap_counter > 0) { - int p = dir > 0 ? edit_shape.getPrevVertex(iprev) : edit_shape - .getNextVertex(iprev); - for (int i = 1; i < gap_counter; i++) { - int pp = dir > 0 ? edit_shape.getPrevVertex(p) : edit_shape - .getNextVertex(p); - edit_shape.removeVertex(p, true); - p = pp; + return clip_count; + } + + if (cnt == 3) { + boolean case1 = (locations[0] == 1 && locations[1] == 2 && locations[2] == 2); + boolean case2 = (locations[0] == 1 && locations[1] == 1 && locations[2] == 2); + if (case1 || case2) { + // special case, when we cannot clip, but we can move the point + Point2D p1 = edit_shape.getXY(from_vertex); + int v = edit_shape.getNextVertex(from_vertex, dir); + Point2D p2 = edit_shape.getXY(v); + Point2D p3 = edit_shape.getXY(edit_shape.getNextVertex(v, dir)); + if (case2) { + Point2D temp = p1; + p1 = p3; + p3 = temp; } - pt_middle.add(pt_before, pt_current); - pt_middle.scale(0.5); - - v_gap.sub(pt_current, pt_before); - double gap_length = v_gap.length(); - double sqr_delta = abs_d * abs_d - gap_length * gap_length - * 0.25; - assert (sqr_delta > 0); - double delta = Math.sqrt(sqr_delta); - v_gap.normalize(); - v_gap.rightPerpendicular(); - temp.setCoords(v_gap); - temp.scale(abs_d - delta); - pt_middle.add(temp); - edit_shape.setXY(iprev, pt_middle); + Point2D vec = new Point2D(); + vec.sub(p1, p2); + p3.sub(p2); + double veclen = vec.length(); + double w = p3.length(); + double wcosa = vec.dotProduct(p3) / veclen; + double wsina = Math.abs(p3.crossProduct(vec) / veclen); + double z = 2 * abs_distance - wsina; + if (z < 0) + return 0; + + double x = wcosa + Math.sqrt(wsina * z); + if (x > veclen) + return 0; + + Point2D hvec = new Point2D(); + hvec.scaleAdd(-x / veclen, vec, p3); // hvec = p3 - vec * (x / + // veclen); + double h = hvec.length(); + double y = -(h * h * veclen) / (2 * hvec.dotProduct(vec)); + + double t = (x - y) / veclen; + MathUtils.lerp(p2, p1, t, p2); + edit_shape.setXY(v, p2); + return 0; + } + } + + if (large_distance && cnt > 3) { + // we are processing more than 3 points and there are some points + // further than the + return 0; + } + + int v_prev = -1; + Point2D pt_prev = new Point2D(); + int v_cur = from_vertex; + Point2D pt_cur = new Point2D(pt1); + int cur_location = 1; + int prev_location = -1; // 1 - semiplane to the right of [f,c]. 3 - + // semiplane to the right of [c,t], 2 - both + // above fc and ct, 0 - cannot clip, -1 - + // unknown + int v_next = v_cur; + int clip_count = 0; + cnt = 1; + while (v_next != to_vertex) { + v_next = edit_shape.getNextVertex(v_next, dir); + int next_location = locations[cnt++]; + if (next_location == 0) { + if (v_next == to_vertex) + break; + + continue; } - edit_shape.filterClosePoints(m_filter_tolerance, false, false); + Point2D pt_next = edit_shape.getXY(v_next); + + if (prev_location != -1) { + int common_location = (prev_location & cur_location & next_location); + if ((common_location & 3) != 0) { + // prev and next are on the same semiplane as the current we + // can safely remove the current point. + edit_shape.removeVertex(v_cur, true); + clip_count++;// do not change prev point. + v_cur = v_next; + pt_cur.setCoords(pt_next); + cur_location = next_location; + continue; + } - if (!b_filtered) - break; + if (cur_location == 3 && prev_location != 0 + && next_location != 0) { + assert ((prev_location & next_location) == 0);// going from + // one semi + // plane to + // another + // via the + // mid. + pt_cur.setCoords(corner); + if (can_erase_corner_point || pt_cur.equals(pt_prev)) {// this + // point + // can + // be + // removed + edit_shape.removeVertex(v_cur, true); + clip_count++;// do not change prev point. + v_cur = v_next; + pt_cur.setCoords(pt_next); + cur_location = next_location; + continue; + } else { + edit_shape.setXY(v_cur, pt_cur); // snap to the corner + } + } else { + if (next_location == 0 + && cur_location != 0 + || next_location != 0 + && cur_location == 0 + || ((next_location | cur_location) == 3 + && next_location != 3 && cur_location != 3)) { + // clip + } + } + } + + prev_location = cur_location; + v_prev = v_cur; + pt_prev.setCoords(pt_cur); + v_cur = v_next; + cur_location = next_location; + pt_cur.setCoords(pt_next); } - return 1; + return clip_count; } - + private boolean isDegeneratePath_(MultiPathImpl mp_impl, int ipath) { if (mp_impl.getPathSize(ipath) == 1) return true; @@ -1510,7 +1834,7 @@ private void addCircle_(MultiPathImpl result_mp, Point point) { // avoid unnecessary memory allocation for the circle template. Just do // the point here. - int N = calcN_(4); + int N = m_circle_template_size; int real_size = (N + 3) / 4; double dA = (Math.PI * 0.5) / real_size; // result_mp.reserve(real_size * 4); @@ -1590,5 +1914,4 @@ private Polygon setStrongSimple_(Polygon poly) { ((MultiPathImpl) poly._getImpl())._updateOGCFlags(); return poly; } - } diff --git a/src/main/java/com/esri/core/geometry/ConvexHull.java b/src/main/java/com/esri/core/geometry/ConvexHull.java index bafe51e0..ab4b89c9 100644 --- a/src/main/java/com/esri/core/geometry/ConvexHull.java +++ b/src/main/java/com/esri/core/geometry/ConvexHull.java @@ -25,8 +25,7 @@ class ConvexHull { /* - * Constructor for a Convex_hull object.Used for dynamic insertion of - * geometries to create a convex hull. + * Constructor for a Convex_hull object. Used for dynamic insertion of geometries to create a convex hull. */ ConvexHull() { m_tree_hull = new Treap(); @@ -37,24 +36,23 @@ class ConvexHull { m_call_back = new CallBackShape(this); } - private ConvexHull(MultiVertexGeometry mvg) { + private ConvexHull(AttributeStreamOfDbl stream, int n) { m_tree_hull = new Treap(); - m_tree_hull.setCapacity(20); - m_mvg = mvg; - m_call_back = new CallBackMvg(this); + m_tree_hull.setCapacity(Math.min(20, n)); + m_stream = stream; + m_call_back = new CallBackStream(this); } - private ConvexHull(Point2D[] points) { + private ConvexHull(Point2D[] points, int n) { m_tree_hull = new Treap(); - m_tree_hull.setCapacity(20); + m_tree_hull.setCapacity(Math.min(20, n)); m_points = points; m_call_back = new CallBackPoints(this); } /** - * Adds a geometry to the current bounding geometry using an incremental - * algorithm for dynamic insertion. \param geometry The geometry to add to - * the bounding geometry. + * Adds a geometry to the current bounding geometry using an incremental algorithm for dynamic insertion. + * \param geometry The geometry to add to the bounding geometry. */ void addGeometry(Geometry geometry) { @@ -73,20 +71,19 @@ else if (type == Geometry.GeometryType.Point) } /** - * Gets the current bounding geometry. Returns a Geometry. + * Gets the current bounding geometry. + * Returns a Geometry. */ Geometry getBoundingGeometry() { - // Extracts the convex hull from the tree. Reading the tree in order - // from first to last is the resulting convex hull. + // Extracts the convex hull from the tree. Reading the tree in order from first to last is the resulting convex hull. Point point = new Point(); int first = m_tree_hull.getFirst(-1); Polygon hull = new Polygon(m_shape.getVertexDescription()); m_shape.queryPoint(m_tree_hull.getElement(first), point); hull.startPath(point); - for (int i = m_tree_hull.getNext(first); i != -1; i = m_tree_hull - .getNext(i)) { + for (int i = m_tree_hull.getNext(first); i != -1; i = m_tree_hull.getNext(i)) { m_shape.queryPoint(m_tree_hull.getElement(i), point); hull.lineTo(point); } @@ -96,58 +93,110 @@ Geometry getBoundingGeometry() { /** * Static method to construct the convex hull of a Multi_vertex_geometry. - * Returns a Polygon. \param mvg The geometry used to create the convex - * hull. + * Returns a Geometry. + * \param mvg The geometry used to create the convex hull. */ - static Polygon construct(MultiVertexGeometry mvg) { - ConvexHull convex_hull = new ConvexHull(mvg); + static Geometry construct(MultiVertexGeometry mvg) { + if (mvg.isEmpty()) + return new Polygon(mvg.getDescription()); + + MultiVertexGeometryImpl mvg_impl = (MultiVertexGeometryImpl) mvg._getImpl(); + int N = mvg_impl.getPointCount(); + + if (N <= 2) { + if (N == 1 || mvg_impl.getXY(0).equals(mvg_impl.getXY(1))) { + Point point = new Point(mvg_impl.getDescription()); + mvg_impl.getPointByVal(0, point); + return point; + } else { + Point pt = new Point(); + Polyline polyline = new Polyline(mvg_impl.getDescription()); + mvg_impl.getPointByVal(0, pt); + polyline.startPath(pt); + mvg_impl.getPointByVal(1, pt); + polyline.lineTo(pt); + return polyline; + } + } + + AttributeStreamOfDbl stream = (AttributeStreamOfDbl) mvg_impl.getAttributeStreamRef(VertexDescription.Semantics.POSITION); + ConvexHull convex_hull = new ConvexHull(stream, N); - int N = mvg.getPointCount(); int t0 = 0, tm = 1; Point2D pt_0 = new Point2D(); Point2D pt_m = new Point2D(); Point2D pt_p = new Point2D(); - mvg.getXY(t0, pt_0); + stream.read(t0 << 1, pt_0); while (true) { - mvg.getXY(tm, pt_m); - if (!(pt_m.isEqual(pt_0, NumberUtils.doubleEps()) && tm < N - 1)) + if (tm >= N) + break; + + stream.read(tm << 1, pt_m); + if (!pt_m.isEqual(pt_0, NumberUtils.doubleEps())) break; tm++; // We don't want to close the gap between t0 and tm. } convex_hull.m_tree_hull.addElement(t0, -1); - convex_hull.m_tree_hull.addBiggestElement(tm, -1); - for (int tp = tm + 1; tp < mvg.getPointCount(); tp++) {// Dynamically - // insert into - // the current - // convex hull + if (tm < N) { + convex_hull.m_tree_hull.addBiggestElement(tm, -1); - mvg.getXY(tp, pt_p); - int p = convex_hull.treeHull_(pt_p); + for (int tp = tm + 1; tp < mvg_impl.getPointCount(); tp++) {// Dynamically insert into the current convex hull - if (p != -1) - convex_hull.m_tree_hull.setElement(p, tp); // reset the place - // holder to the - // point index. + stream.read(tp << 1, pt_p); + int p = convex_hull.treeHull_(pt_p); + + if (p != -1) + convex_hull.m_tree_hull.setElement(p, tp); // reset the place holder to the point index. + } } - // Extracts the convex hull from the tree. Reading the tree in order - // from first to last is the resulting convex hull. - Point point = new Point(); - int first = convex_hull.m_tree_hull.getFirst(-1); - Polygon hull = new Polygon(mvg.getDescription()); - mvg.getPointByVal(convex_hull.m_tree_hull.getElement(first), point); - hull.startPath(point); + // Extracts the convex hull from the tree. Reading the tree in order from first to last is the resulting convex hull. - for (int i = convex_hull.m_tree_hull.getNext(first); i != -1; i = convex_hull.m_tree_hull - .getNext(i)) { - mvg.getPointByVal(convex_hull.m_tree_hull.getElement(i), point); - hull.lineTo(point); + VertexDescription description = mvg_impl.getDescription(); + boolean b_has_attributes = (description.getAttributeCount() > 1); + int point_count = convex_hull.m_tree_hull.size(-1); + + Geometry hull; + + if (point_count >= 2) { + if (point_count >= 3) + hull = new Polygon(description); + else + hull = new Polyline(description); + + MultiPathImpl hull_impl = (MultiPathImpl) hull._getImpl(); + hull_impl.addPath((Point2D[]) null, 0, true); + + Point point = null; + if (b_has_attributes) + point = new Point(); + + for (int i = convex_hull.m_tree_hull.getFirst(-1); i != -1; i = convex_hull.m_tree_hull.getNext(i)) { + if (b_has_attributes) { + mvg_impl.getPointByVal(convex_hull.m_tree_hull.getElement(i), point); + hull_impl.insertPoint(0, -1, point); + } else { + stream.read(convex_hull.m_tree_hull.getElement(i) << 1, pt_p); + hull_impl.insertPoint(0, -1, pt_p); + } + } + } else { + assert (point_count == 1); + + if (b_has_attributes) { + Point point = new Point(description); + mvg_impl.getPointByVal(convex_hull.m_tree_hull.getElement(convex_hull.m_tree_hull.getFirst(-1)), point); + hull = point; + } else { + stream.read(convex_hull.m_tree_hull.getElement(convex_hull.m_tree_hull.getFirst(-1)) << 1, pt_p); + hull = new Point(pt_p); + } } return hull; @@ -156,65 +205,57 @@ static Polygon construct(MultiVertexGeometry mvg) { /** * Static method to construct the convex hull from an array of points. The * out_convex_hull array will be populated with the subset of index - * positions which contribute to the convex hull. Returns the number of - * points in the convex hull. \param points The points used to create the - * convex hull. \param count The number of points in the input Point2D - * array. \param out_convex_hull An index array allocated by the user at - * least as big as the size of the input points array. + * positions which contribute to the convex hull. + * Returns the number of points in the convex hull. + * \param points The points used to create the convex hull. + * \param count The number of points in the input Point2D array. + * \param out_convex_hull An index array allocated by the user at least as big as the size of the input points array. */ - static int construct(Point2D[] points, int count, int[] bounding_geometry) { - ConvexHull convex_hull = new ConvexHull(points); + static int construct(Point2D[] points, int count, int[] out_convex_hull) { + ConvexHull convex_hull = new ConvexHull(points, count); int t0 = 0, tm = 1; Point2D pt_0 = points[t0]; - while (points[tm].isEqual(pt_0, NumberUtils.doubleEps()) - && tm < count - 1) + while (tm < count && points[tm].isEqual(pt_0, NumberUtils.doubleEps())) tm++; // We don't want to close the gap between t0 and tm. convex_hull.m_tree_hull.addElement(t0, -1); - convex_hull.m_tree_hull.addBiggestElement(tm, -1); - for (int tp = tm + 1; tp < count; tp++) {// Dynamically insert into the - // current convex hull. + if (tm < count) { + convex_hull.m_tree_hull.addBiggestElement(tm, -1); + + for (int tp = tm + 1; tp < count; tp++) {// Dynamically insert into the current convex hull. - Point2D pt_p = points[tp]; - int p = convex_hull.treeHull_(pt_p); + Point2D pt_p = points[tp]; + int p = convex_hull.treeHull_(pt_p); - if (p != -1) - convex_hull.m_tree_hull.setElement(p, tp); // reset the place - // holder to the - // point index. + if (p != -1) + convex_hull.m_tree_hull.setElement(p, tp); // reset the place holder to the point index. + } } - // Extracts the convex hull from the tree. Reading the tree in order - // from first to last is the resulting convex hull. + // Extracts the convex hull from the tree. Reading the tree in order from first to last is the resulting convex hull. int out_count = 0; - for (int i = convex_hull.m_tree_hull.getFirst(-1); i != -1; i = convex_hull.m_tree_hull - .getNext(i)) - bounding_geometry[out_count++] = convex_hull.m_tree_hull - .getElement(i); + for (int i = convex_hull.m_tree_hull.getFirst(-1); i != -1; i = convex_hull.m_tree_hull.getNext(i)) + out_convex_hull[out_count++] = convex_hull.m_tree_hull.getElement(i); return out_count; } /** - * Returns true if the given path of the input MultiPath is convex. Returns - * false otherwise. \param multi_path The MultiPath to check if the path is - * convex. \param path_index The path of the MultiPath to check if its - * convex. + * Returns true if the given path of the input MultiPath is convex. Returns false otherwise. + * \param multi_path The MultiPath to check if the path is convex. + * \param path_index The path of the MultiPath to check if its convex. */ - static boolean isPathConvex(MultiPath multi_path, int path_index, - ProgressTracker progress_tracker) { + static boolean isPathConvex(MultiPath multi_path, int path_index, ProgressTracker progress_tracker) { MultiPathImpl mimpl = (MultiPathImpl) multi_path._getImpl(); int path_start = mimpl.getPathStart(path_index); int path_end = mimpl.getPathEnd(path_index); - boolean bxyclosed = !mimpl.isClosedPath(path_index) - && mimpl.isClosedPathInXYPlane(path_index); + boolean bxyclosed = !mimpl.isClosedPath(path_index) && mimpl.isClosedPathInXYPlane(path_index); - AttributeStreamOfDbl position = (AttributeStreamOfDbl) (mimpl - .getAttributeStreamRef(VertexDescription.Semantics.POSITION)); + AttributeStreamOfDbl position = (AttributeStreamOfDbl) (mimpl.getAttributeStreamRef(VertexDescription.Semantics.POSITION)); int position_start = 2 * path_start; int position_end = 2 * path_end; @@ -224,23 +265,15 @@ static boolean isPathConvex(MultiPath multi_path, int path_index, if (position_end - position_start < 6) return true; - // This matches the logic for case 1 of the tree hull algorithm. The - // idea is inductive. We assume we have a convex hull pt_0,...,pt_m, and - // we see if - // a new point (pt_pivot) is among the transitive tournament for pt_0, - // knowing that pt_pivot comes after pt_m. + // This matches the logic for case 1 of the tree hull algorithm. The idea is inductive. We assume we have a convex hull pt_0,...,pt_m, and we see if + // a new point (pt_pivot) is among the transitive tournament for pt_0, knowing that pt_pivot comes after pt_m. // We check three conditions: - // 1) pt_m->pt_pivot->pt_0 is clockwise (closure across the boundary is - // convex) - // 2) pt_1->pt_pivot->pt_0 is clockwise (the first step forward is - // convex) (pt_1 is the next point after pt_0) - // 3) pt_m->pt_pivot->pt_m_prev is clockwise (the first step backwards - // is convex) (pt_m_prev is the previous point before pt_m) + // 1) pt_m->pt_pivot->pt_0 is clockwise (closure across the boundary is convex) + // 2) pt_1->pt_pivot->pt_0 is clockwise (the first step forward is convex) (pt_1 is the next point after pt_0) + // 3) pt_m->pt_pivot->pt_m_prev is clockwise (the first step backwards is convex) (pt_m_prev is the previous point before pt_m) - // If all three of the above conditions are clockwise, then pt_pivot is - // among the transitive tournament for pt_0, and therefore the polygon - // pt_0, ..., pt_m, pt_pivot is convex. + // If all three of the above conditions are clockwise, then pt_pivot is among the transitive tournament for pt_0, and therefore the polygon pt_0, ..., pt_m, pt_pivot is convex. Point2D pt_0 = new Point2D(), pt_m = new Point2D(), pt_pivot = new Point2D(); position.read(position_start, pt_0); @@ -256,8 +289,7 @@ static boolean isPathConvex(MultiPath multi_path, int path_index, Point2D pt_1 = new Point2D(pt_m.x, pt_m.y); Point2D pt_m_prev = new Point2D(); - // Assume that pt_0,...,pt_m is convex. Check if the next point, - // pt_pivot, maintains the convex invariant. + // Assume that pt_0,...,pt_m is convex. Check if the next point, pt_pivot, maintains the convex invariant. for (int i = position_start + 6; i < position_end; i += 2) { pt_m_prev.setCoords(pt_m); pt_m.setCoords(pt_pivot); @@ -325,7 +357,7 @@ private void addSegment_(Segment segment) { segment.queryStart(point); int t_start = m_shape.addPoint(m_path_handle, point); m_tree_hull.setElement(p_start, t_start); // reset the place holder - // to tp + // to tp } Point2D pt_end = segment.getEndXY(); @@ -335,7 +367,7 @@ private void addSegment_(Segment segment) { segment.queryEnd(point); int t_end = m_shape.addPoint(m_path_handle, point); m_tree_hull.setElement(p_end, t_end); // reset the place holder to - // tp + // tp } } @@ -353,9 +385,7 @@ private int addPoint_(Point2D pt_p) { int p = -1; if (m_tree_hull.size(-1) == 0) { - p = m_tree_hull.addElement(-4, -1); // set place holder to -4 to - // indicate the first element - // being added (t0). + p = m_tree_hull.addElement(-4, -1); // reset the place holder to tp return p; } @@ -363,15 +393,8 @@ private int addPoint_(Point2D pt_p) { int t0 = m_tree_hull.getElement(m_tree_hull.getFirst(-1)); Point2D pt_0 = m_shape.getXY(t0); - if (!pt_p.isEqual(pt_0, NumberUtils.doubleEps())) // We don't want - // to close the - // gap between - // t0 and tm. - p = m_tree_hull.addBiggestElement(-5, -1); // set place holder - // to -5 to indicate - // the second - // element being - // added (tm). + if (!pt_p.isEqual(pt_0, NumberUtils.doubleEps())) // We don't want to close the gap between t0 and tm. + p = m_tree_hull.addBiggestElement(-5, -1); // set place holder to -5 to indicate the second element being added (tm). return p; } @@ -380,8 +403,7 @@ private int addPoint_(Point2D pt_p) { return p; } - // Algorithm taken from "Axioms and Hulls" by D.E. Knuth, Lecture Notes in - // Computer Science 606, page 47. + // Algorithm taken from "Axioms and Hulls" by D.E. Knuth, Lecture Notes in Computer Science 606, page 47. private int treeHull_(Point2D pt_pivot) { assert (m_tree_hull.size(-1) >= 2); @@ -398,55 +420,31 @@ private int treeHull_(Point2D pt_pivot) { m_call_back.getXY(t0, pt_0); m_call_back.getXY(tm, pt_m); - assert (!pt_0.isEqual(pt_m, NumberUtils.doubleEps())); // assert - // that the - // gap is - // not - // closed + assert (!pt_0.isEqual(pt_m, NumberUtils.doubleEps())); // assert that the gap is not closed - int orient_m_p_0 = Point2D.orientationRobust(pt_m, pt_pivot, pt_0); // determines - // case - // 1, - // 2, - // 3 + int orient_m_p_0 = Point2D.orientationRobust(pt_m, pt_pivot, pt_0); // determines case 1, 2, 3 if (isClockwise_(orient_m_p_0)) {// Case 1: tp->t0->tm is clockwise - p = m_tree_hull.addBiggestElement(-1, -1); // set place holder - // to -1 for case 1. + p = m_tree_hull.addBiggestElement(-1, -1); // set place holder to -1 for case 1. int l = treeHullWalkBackward_(pt_pivot, last, first); if (l != first) - treeHullWalkForward_(pt_pivot, first, - m_tree_hull.getPrev(l)); + treeHullWalkForward_(pt_pivot, first, m_tree_hull.getPrev(l)); continue; } - if (isCounterClockwise_(orient_m_p_0)) {// Case 2: tp->tm->t0 is - // clockwise - int k = m_tree_hull.getRoot(-1), k_min = m_tree_hull - .getFirst(-1), k_max = m_tree_hull.getLast(-1), k_prev; + if (isCounterClockwise_(orient_m_p_0)) {// Case 2: tp->tm->t0 is clockwise + int k = m_tree_hull.getRoot(-1), k_min = m_tree_hull.getFirst(-1), k_max = m_tree_hull.getLast(-1), k_prev; int tk, tk_prev; Point2D pt_k = new Point2D(); Point2D pt_k_prev = new Point2D(); - while (k_min != m_tree_hull.getPrev(k_max)) {// binary search to - // find k such - // that - // t0->tp->tj - // holds (i.e. - // clockwise) - // for j >= k. - // Hence, - // tj->tp->t0 is - // clockwise (or - // degenerate) - // for j < k. + while (k_min != m_tree_hull.getPrev(k_max)) {// binary search to find k such that t0->tp->tj holds (i.e. clockwise) for j >= k. Hence, tj->tp->t0 is clockwise (or degenerate) for j < k. tk = m_tree_hull.getElement(k); m_call_back.getXY(tk, pt_k); - int orient_k_p_0 = Point2D.orientationRobust(pt_k, - pt_pivot, pt_0); + int orient_k_p_0 = Point2D.orientationRobust(pt_k, pt_pivot, pt_0); if (isCounterClockwise_(orient_k_p_0)) { k_max = k; @@ -463,23 +461,17 @@ private int treeHull_(Point2D pt_pivot) { tk_prev = m_tree_hull.getElement(k_prev); m_call_back.getXY(tk, pt_k); m_call_back.getXY(tk_prev, pt_k_prev); - assert (isCounterClockwise_(Point2D.orientationRobust(pt_k, - pt_pivot, pt_0)) && !isCounterClockwise_(Point2D - .orientationRobust(pt_k_prev, pt_pivot, pt_0))); - assert (k_prev != first || isCounterClockwise_(Point2D - .orientationRobust(pt_k, pt_pivot, pt_0))); + assert (isCounterClockwise_(Point2D.orientationRobust(pt_k, pt_pivot, pt_0)) && !isCounterClockwise_(Point2D.orientationRobust(pt_k_prev, pt_pivot, pt_0))); + assert (k_prev != first || isCounterClockwise_(Point2D.orientationRobust(pt_k, pt_pivot, pt_0))); if (k_prev != first) { - int orient_k_prev_p_k = Point2D.orientationRobust( - pt_k_prev, pt_pivot, pt_k); + int orient_k_prev_p_k = Point2D.orientationRobust(pt_k_prev, pt_pivot, pt_k); if (!isClockwise_(orient_k_prev_p_k)) - continue; // pt_pivot is inside the hull (or on the - // boundary) + continue; // pt_pivot is inside the hull (or on the boundary) } - p = m_tree_hull.addElementAtPosition(k_prev, k, -2, true, - false, -1); // set place holder to -2 for case 2. + p = m_tree_hull.addElementAtPosition(k_prev, k, -2, true, false, -1); // set place holder to -2 for case 2. treeHullWalkForward_(pt_pivot, k, last); treeHullWalkBackward_(pt_pivot, k_prev, first); @@ -488,40 +480,17 @@ private int treeHull_(Point2D pt_pivot) { assert (isDegenerate_(orient_m_p_0)); {// Case 3: degenerate + int between = isBetween_(pt_pivot, pt_m, pt_0); - if (m_line == null) - m_line = new Line(); - - m_line.setStartXY(pt_m); - m_line.setEndXY(pt_0); - - double scalar = m_line.getClosestCoordinate(pt_pivot, true); // if - // scalar - // is - // between - // 0 - // and - // 1, - // then - // we - // don't - // need - // to - // add - // tp. - - if (scalar < 0.0) { + if (between == -1) { int l = m_tree_hull.getPrev(last); m_tree_hull.deleteNode(last, -1); - p = m_tree_hull.addBiggestElement(-3, -1); // set place - // holder to -3 - // for case 3. + p = m_tree_hull.addBiggestElement(-3, -1); // set place holder to -3 for case 3. treeHullWalkBackward_(pt_pivot, l, first); - } else if (scalar > 1.0) { + } else if (between == 1) { int j = m_tree_hull.getNext(first); m_tree_hull.deleteNode(first, -1); - p = m_tree_hull.addElementAtPosition(-1, j, -3, true, - false, -1); // set place holder to -3 for case 3. + p = m_tree_hull.addElementAtPosition(-1, j, -3, true, false, -1); // set place holder to -3 for case 3. treeHullWalkForward_(pt_pivot, j, last); } @@ -545,19 +514,11 @@ private int treeHullWalkForward_(Point2D pt_pivot, int start, int end) { m_call_back.getXY(tj, pt_j); - while (j != end && m_tree_hull.size(-1) > 2) {// Stops when we find a - // clockwise triple - // containting the pivot - // point, or when the - // tree_hull size is 2. - // Deletes non-clockwise - // triples along the - // way. + while (j != end && m_tree_hull.size(-1) > 2) {//Stops when we find a clockwise triple containting the pivot point, or when the tree_hull size is 2. Deletes non-clockwise triples along the way. int tj_next = m_tree_hull.getElement(j_next); m_call_back.getXY(tj_next, pt_j_next); - int orient_j_next_p_j = Point2D.orientationRobust(pt_j_next, - pt_pivot, pt_j); + int orient_j_next_p_j = Point2D.orientationRobust(pt_j_next, pt_pivot, pt_j); if (isClockwise_(orient_j_next_p_j)) break; @@ -585,19 +546,11 @@ private int treeHullWalkBackward_(Point2D pt_pivot, int start, int end) { m_call_back.getXY(tl, pt_l); - while (l != end && m_tree_hull.size(-1) > 2) {// Stops when we find a - // clockwise triple - // containting the pivot - // point, or when the - // tree_hull size is 2. - // Deletes non-clockwise - // triples along the - // way. + while (l != end && m_tree_hull.size(-1) > 2) {//Stops when we find a clockwise triple containting the pivot point, or when the tree_hull size is 2. Deletes non-clockwise triples along the way. int tl_prev = m_tree_hull.getElement(l_prev); m_call_back.getXY(tl_prev, pt_l_prev); - int orient_l_p_l_prev = Point2D.orientationRobust(pt_l, pt_pivot, - pt_l_prev); + int orient_l_p_l_prev = Point2D.orientationRobust(pt_l, pt_pivot, pt_l_prev); if (isClockwise_(orient_l_p_l_prev)) break; @@ -661,6 +614,71 @@ private static boolean isDegenerate_(int orientation) { return orientation == 0.0; } + private static int isBetween_(Point2D pt_pivot, Point2D pt_m, Point2D pt_0) { + int ordinate = -1; + + if (pt_m.y == pt_0.y) { + ordinate = 0; + } else if (pt_m.x == pt_0.x) { + ordinate = 1; + } else {// use bigger ordinate, but shouldn't matter + + double diff_x = Math.abs(pt_m.x - pt_0.x); + double diff_y = Math.abs(pt_m.y - pt_0.y); + + if (diff_x >= diff_y) + ordinate = 0; + else + ordinate = 1; + } + + int res = -1; + + if (ordinate == 0) { + assert (pt_m.x != pt_0.x); + + if (pt_m.x < pt_0.x) { + if (pt_pivot.x < pt_m.x) + res = -1; + else if (pt_0.x < pt_pivot.x) + res = 1; + else + res = 0; + } else { + assert (pt_0.x < pt_m.x); + + if (pt_m.x < pt_pivot.x) + res = -1; + else if (pt_pivot.x < pt_0.x) + res = 1; + else + res = 0; + } + } else { + assert (pt_m.y != pt_0.y); + + if (pt_m.y < pt_0.y) { + if (pt_pivot.y < pt_m.y) + res = -1; + else if (pt_0.y < pt_pivot.y) + res = 1; + else + res = 0; + } else { + assert (pt_0.y < pt_m.y); + + if (pt_m.y < pt_pivot.y) + res = -1; + else if (pt_pivot.y < pt_0.y) + res = 1; + else + res = 0; + } + } + + return res; + } + private static abstract class CallBack { abstract void getXY(int ti, Point2D pt); @@ -687,16 +705,16 @@ void deleteNode(int i) { } } - private static final class CallBackMvg extends CallBack { + private static final class CallBackStream extends CallBack { private ConvexHull m_convex_hull; - CallBackMvg(ConvexHull convex_hull) { + CallBackStream(ConvexHull convex_hull) { m_convex_hull = convex_hull; } @Override void getXY(int ti, Point2D pt) { - m_convex_hull.m_mvg.getXY(ti, pt); + m_convex_hull.m_stream.read(ti << 1, pt); } @Override @@ -726,7 +744,7 @@ void deleteNode(int i) { // Members private Treap m_tree_hull; private EditShape m_shape; - private MultiVertexGeometry m_mvg; + private AttributeStreamOfDbl m_stream; private Point2D[] m_points; private int m_geometry_handle; private int m_path_handle; diff --git a/src/main/java/com/esri/core/geometry/ECoordinate.java b/src/main/java/com/esri/core/geometry/ECoordinate.java index c3d5c522..5bcf0460 100644 --- a/src/main/java/com/esri/core/geometry/ECoordinate.java +++ b/src/main/java/com/esri/core/geometry/ECoordinate.java @@ -27,6 +27,18 @@ class ECoordinate { private double m_value; private double m_eps; + ECoordinate() { + set(0.0, 0.0); + } + + ECoordinate(double v) { + set(v); + } + + ECoordinate(ECoordinate v) { + set(v); + } + double epsCoordinate() { return NumberUtils.doubleEps(); } @@ -160,7 +172,7 @@ void mul(double v) { } void mul(ECoordinate v_1, ECoordinate v_2) { - double r = Math.abs(v_1.m_value) * Math.abs(v_2.m_value); + double r = v_1.m_value * v_2.m_value; m_eps = v_1.m_eps * Math.abs(v_2.m_value) + v_2.m_eps * Math.abs(v_1.m_value) + v_1.m_eps * v_2.m_eps + epsCoordinate() * Math.abs(r); diff --git a/src/main/java/com/esri/core/geometry/EditShape.java b/src/main/java/com/esri/core/geometry/EditShape.java index 36ce09a8..1dbdb37b 100644 --- a/src/main/java/com/esri/core/geometry/EditShape.java +++ b/src/main/java/com/esri/core/geometry/EditShape.java @@ -1865,6 +1865,14 @@ int getPrevVertex(int currentVertex) { return m_vertex_index_list.getField(currentVertex, 1); } + int getPrevVertex(int currentVertex, int dir) { + return dir > 0 ? m_vertex_index_list.getField(currentVertex, 1) : m_vertex_index_list.getField(currentVertex, 2); + } + + int getNextVertex(int currentVertex, int dir) { + return dir > 0 ? m_vertex_index_list.getField(currentVertex, 2) : m_vertex_index_list.getField(currentVertex, 1); + } + // Returns a path the vertex belongs to. int getPathFromVertex(int vertex) { return m_vertex_index_list.getField(vertex, 3); diff --git a/src/main/java/com/esri/core/geometry/EnvSrlzr.java b/src/main/java/com/esri/core/geometry/EnvSrlzr.java new file mode 100644 index 00000000..a6cea40a --- /dev/null +++ b/src/main/java/com/esri/core/geometry/EnvSrlzr.java @@ -0,0 +1,95 @@ +/* + Copyright 1995-2015 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ +package com.esri.core.geometry; + +import java.io.InvalidObjectException; +import java.io.ObjectStreamException; +import java.io.Serializable; + +//This is a writeReplace class for Envelope +public class EnvSrlzr implements Serializable { + private static final long serialVersionUID = 1L; + double[] attribs; + int descriptionBitMask; + + public Object readResolve() throws ObjectStreamException { + Envelope env = null; + if (descriptionBitMask == -1) + return null; + + try { + VertexDescription vd = VertexDescriptionDesignerImpl + .getVertexDescription(descriptionBitMask); + env = new Envelope(vd); + if (attribs != null) { + env.setCoords(attribs[0], attribs[1], attribs[2], attribs[3]); + int index = 4; + for (int i = 1, n = vd.getAttributeCount(); i < n; i++) { + int semantics = vd.getSemantics(i); + int comps = VertexDescription.getComponentCount(semantics); + for (int ord = 0; ord < comps; ord++) { + env.setInterval(semantics, ord, attribs[index++], attribs[index++]); + } + } + } + } catch (Exception ex) { + throw new InvalidObjectException("Cannot read geometry from stream"); + } + + return env; + } + + public void setGeometryByValue(Envelope env) throws ObjectStreamException { + try { + attribs = null; + if (env == null) { + descriptionBitMask = -1; + } + + VertexDescription vd = env.getDescription(); + descriptionBitMask = vd.m_semanticsBitArray; + if (env.isEmpty()) { + return; + } + + attribs = new double[vd.getTotalComponentCount() * 2]; + attribs[0] = env.getXMin(); + attribs[1] = env.getYMin(); + attribs[2] = env.getXMax(); + attribs[3] = env.getYMax(); + int index = 4; + for (int i = 1, n = vd.getAttributeCount(); i < n; i++) { + int semantics = vd.getSemantics(i); + int comps = VertexDescription.getComponentCount(semantics); + for (int ord = 0; ord < comps; ord++) { + Envelope1D e = env.queryInterval(semantics, ord); + attribs[index++] = e.vmin; + attribs[index++] = e.vmax; + } + } + } catch (Exception ex) { + throw new InvalidObjectException("Cannot serialize this geometry"); + } + } +} diff --git a/src/main/java/com/esri/core/geometry/Envelope.java b/src/main/java/com/esri/core/geometry/Envelope.java index 6991f26e..1370ca4f 100644 --- a/src/main/java/com/esri/core/geometry/Envelope.java +++ b/src/main/java/com/esri/core/geometry/Envelope.java @@ -32,8 +32,9 @@ /** * An envelope is an axis-aligned rectangle. */ -public final class Envelope extends Geometry implements Serializable { - private static final long serialVersionUID = 2L; +public class Envelope extends Geometry implements Serializable { + //We are using writeReplace instead. + //private static final long serialVersionUID = 2L; Envelope2D m_envelope = new Envelope2D(); @@ -58,20 +59,20 @@ public Envelope(Point center, double width, double height) { _setFromPoint(center, width, height); } - Envelope(Envelope2D env2D) { + public Envelope(Envelope2D env2D) { m_description = VertexDescriptionDesignerImpl.getDefaultDescriptor2D(); m_envelope.setCoords(env2D); m_envelope.normalize(); } - Envelope(VertexDescription vd) { + public Envelope(VertexDescription vd) { if (vd == null) throw new IllegalArgumentException(); m_description = vd; m_envelope.setEmpty(); } - Envelope(VertexDescription vd, Envelope2D env2D) { + public Envelope(VertexDescription vd, Envelope2D env2D) { if (vd == null) throw new IllegalArgumentException(); m_description = vd; @@ -215,16 +216,16 @@ public double getCenterY() { return m_envelope.getCenterY(); } - /** + /** * The x and y-coordinates of the center of the envelope. * * @return A point whose x and y-coordinates are that of the center of the envelope. */ - Point2D getCenterXY() { + public Point2D getCenterXY() { return m_envelope.getCenter(); } - void getCenter(Point point_out) { + public void getCenter(Point point_out) { point_out.assignVertexDescription(m_description); if (isEmpty()) { point_out.setEmpty(); @@ -244,7 +245,7 @@ void getCenter(Point point_out) { point_out.setXY(m_envelope.getCenter()); } - void merge(Point2D pt) { + public void merge(Point2D pt) { _touch(); m_envelope.merge(pt); } @@ -341,7 +342,7 @@ void _setFromPoint(Point centerPoint) { } } - void merge(Envelope2D other) { + public void merge(Envelope2D other) { _touch(); m_envelope.merge(other); } @@ -415,7 +416,7 @@ public void copyTo(Geometry dst) { { envDst._ensureAttributes(); System.arraycopy(m_attributes, 0, envDst.m_attributes, 0, - (m_description._getTotalComponents() - 2) * 2); + (m_description.getTotalComponentCount() - 2) * 2); } } @@ -493,7 +494,7 @@ public void setInterval(int semantics, int ordinate, Envelope1D env) { } } - void queryCoordinates(Point2D[] dst) { + public void queryCoordinates(Point2D[] dst) { if (dst == null || dst.length < 4 || m_envelope.isEmpty()) throw new IllegalArgumentException(); @@ -573,7 +574,7 @@ public void queryCornerByVal(int index, Point ptDst) { } } - void queryCorner(int index, Point2D ptDst) { + public void queryCorner(int index, Point2D ptDst) { Point2D p = m_envelope.queryCorner(index); ptDst.setCoords(p.x, p.y); } @@ -645,8 +646,8 @@ void setAttributeAsDblImpl_(int end_point, int semantics, int ordinate, void _ensureAttributes() { _touch(); - if (m_attributes == null && m_description._getTotalComponents() > 2) { - m_attributes = new double[(m_description._getTotalComponents() - 2) * 2]; + if (m_attributes == null && m_description.getTotalComponentCount() > 2) { + m_attributes = new double[(m_description.getTotalComponentCount() - 2) * 2]; int offset0 = _getEndPointOffset(m_description, 0); int offset1 = _getEndPointOffset(m_description, 1); @@ -672,10 +673,10 @@ protected void _assignVertexDescriptionImpl(VertexDescription newDescription) { return; } - if (newDescription._getTotalComponents() > 2) { + if (newDescription.getTotalComponentCount() > 2) { int[] mapping = VertexDescriptionDesignerImpl.mapAttributes(newDescription, m_description); - double[] newAttributes = new double[(newDescription._getTotalComponents() - 2) * 2]; + double[] newAttributes = new double[(newDescription.getTotalComponentCount() - 2) * 2]; int old_offset0 = _getEndPointOffset(m_description, 0); int old_offset1 = _getEndPointOffset(m_description, 1); @@ -793,10 +794,10 @@ int _getAttributeAsInt(int endPoint, int semantics, int ordinate) { } static int _getEndPointOffset(VertexDescription vd, int endPoint) { - return endPoint * (vd._getTotalComponents() - 2); + return endPoint * (vd.getTotalComponentCount() - 2); } - boolean isIntersecting(Envelope2D other) { + public boolean isIntersecting(Envelope2D other) { return m_envelope.isIntersecting(other); } @@ -875,7 +876,7 @@ public void normalize() {// TODO: attributes * * @return The center point of the envelope. */ - Point2D getCenter2D() { + public Point2D getCenter2D() { return m_envelope.getCenter(); } @@ -1005,7 +1006,7 @@ public boolean equals(Object _other) { if (!this.m_envelope.equals(other.m_envelope)) return false; - for (int i = 0, n = (m_description._getTotalComponents() - 2) * 2; i < n; i++) + for (int i = 0, n = (m_description.getTotalComponentCount() - 2) * 2; i < n; i++) if (m_attributes[i] != other.m_attributes[i]) return false; @@ -1022,7 +1023,7 @@ public int hashCode() { int hashCode = m_description.hashCode(); hashCode = NumberUtils.hash(hashCode, m_envelope.hashCode()); if (!isEmpty() && m_attributes != null) { - for (int i = 0, n = (m_description._getTotalComponents() - 2) * 2; i < n; i++) { + for (int i = 0, n = (m_description.getTotalComponentCount() - 2) * 2; i < n; i++) { hashCode = NumberUtils.hash(hashCode, m_attributes[i]); } } diff --git a/src/main/java/com/esri/core/geometry/Envelope1D.java b/src/main/java/com/esri/core/geometry/Envelope1D.java index 9e30e08b..96540895 100644 --- a/src/main/java/com/esri/core/geometry/Envelope1D.java +++ b/src/main/java/com/esri/core/geometry/Envelope1D.java @@ -45,12 +45,20 @@ public Envelope1D(double _vmin, double _vmax) { setCoords(_vmin, _vmax); } + public Envelope1D(Envelope1D other) { + setCoords(other); + } + public void setCoords(double _vmin, double _vmax) { vmin = _vmin; vmax = _vmax; normalize(); } + public void setCoords(Envelope1D other) { + setCoords(other.vmin, other.vmax); + } + public void normalize() { if (NumberUtils.isNaN(vmin)) return; @@ -71,7 +79,7 @@ public void setEmpty() { } public boolean isEmpty() { - return NumberUtils.isNaN(vmin); + return NumberUtils.isNaN(vmin) || NumberUtils.isNaN(vmax); } public void setInfinite() { diff --git a/src/main/java/com/esri/core/geometry/Envelope2D.java b/src/main/java/com/esri/core/geometry/Envelope2D.java index 3b9a02f7..172619dd 100644 --- a/src/main/java/com/esri/core/geometry/Envelope2D.java +++ b/src/main/java/com/esri/core/geometry/Envelope2D.java @@ -59,6 +59,12 @@ public static Envelope2D construct(double _xmin, double _ymin, return env; } + public static Envelope2D construct(Envelope2D other) { + Envelope2D env = new Envelope2D(); + env.setCoords(other); + return env; + } + public Envelope2D() { setEmpty(); } @@ -70,6 +76,10 @@ public Envelope2D(double _xmin, double _ymin, double _xmax, double _ymax) { ymax = _ymax; } + public Envelope2D(Envelope2D other) { + setCoords(other); + } + public void setCoords(double _x, double _y) { xmin = _x; ymin = _y; @@ -144,7 +154,7 @@ public void setInfinite() { } public boolean isEmpty() { - return NumberUtils.isNaN(xmin); + return NumberUtils.isNaN(xmin) || NumberUtils.isNaN(ymin) || NumberUtils.isNaN(xmax) || NumberUtils.isNaN(ymax); } public void setCoords(Envelope1D xinterval, Envelope1D yinterval) { @@ -240,14 +250,13 @@ public void zoom(double factorX, double factorY) { } /** - * Checks if this envelope intersects the other. + * Checks if this envelope intersects the other. * @return True if this envelope intersects the other. */ public boolean isIntersecting(Envelope2D other) { - return !isEmpty() - && !other.isEmpty() - && ((xmin <= other.xmin) ? xmax >= other.xmin - : other.xmax >= xmin) + // No need to check if empty, this will work for empty envelopes too + // (IEEE math) + return ((xmin <= other.xmin) ? xmax >= other.xmin : other.xmax >= xmin) && // check that x projections overlap ((ymin <= other.ymin) ? ymax >= other.ymin : other.ymax >= ymin); // check // that @@ -257,7 +266,7 @@ public boolean isIntersecting(Envelope2D other) { } /** - * Checks if this envelope intersects the other assuming neither one is empty. + * Checks if this envelope intersects the other assuming neither one is empty. * @return True if this envelope intersects the other. Assumes this and * other envelopes are not empty. */ @@ -271,6 +280,23 @@ public boolean isIntersectingNE(Envelope2D other) { // overlap } + /** + * Checks if this envelope intersects the other. + * @return True if this envelope intersects the other. + */ + public boolean isIntersecting(double xmin_, double ymin_, double xmax_, double ymax_) { + // No need to check if empty, this will work for empty geoms too (IEEE + // math) + return ((xmin <= xmin_) ? xmax >= xmin_ : xmax_ >= xmin) && // check + // that x + // projections + // overlap + ((ymin <= ymin_) ? ymax >= ymin_ : ymax_ >= ymin); // check that + // y + // projections + // overlap + } + /** * Intersects this envelope with the other and stores result in this * envelope. @@ -303,15 +329,20 @@ public boolean intersect(Envelope2D other) { } /** - * Queries a corner of the envelope. - * - * @param index Indicates a corner of the envelope. - *

0 => lower left or (xmin, ymin) - *

1 => upper left or (xmin, ymax) - *

2 => upper right or (xmax, ymax) - *

3 => lower right or (xmax, ymin) - * @return Point at a corner of the envelope. - * + * Queries a corner of the envelope. + * + * @param index + * Indicates a corner of the envelope. + *

+ * 0 means lower left or (xmin, ymin) + *

+ * 1 means upper left or (xmin, ymax) + *

+ * 2 means upper right or (xmax, ymax) + *

+ * 3 means lower right or (xmax, ymin) + * @return Point at a corner of the envelope. + * */ public Point2D queryCorner(int index) { switch (index) { @@ -1081,6 +1112,62 @@ public double sqrDistance(Envelope2D other) return dx * dx + dy * dy; } + /** + * Calculates minimum squared distance from this envelope to the other. + * Returns 0 for empty envelopes. + */ + public double sqrDistance(double xmin_, double ymin_, double xmax_, double ymax_) + { + double dx = 0; + double dy = 0; + double nn; + + nn = xmin - xmax_; + if (nn > dx) + dx = nn; + + nn = ymin - ymax_; + if (nn > dy) + dy = nn; + + nn = xmin_ - xmax; + if (nn > dx) + dx = nn; + + nn = ymin_ - ymax; + if (nn > dy) + dy = nn; + + return dx * dx + dy * dy; + } + + /** + *Returns squared max distance between two bounding boxes. This is furthest distance between points on the two envelopes. + * + *@param other The bounding box to calculate the max distance two. + *@return Squared distance value. + */ + public double sqrMaxDistance(Envelope2D other) { + if (isEmpty() || other.isEmpty()) + return NumberUtils.TheNaN; + + double dist = 0; + Point2D[] points = new Point2D[4]; + queryCorners(points); + Point2D[] points_o = new Point2D[4]; + other.queryCorners(points_o); + for (int i = 0; i < 4; i++) { + for (int j = 0; j < 4; j++) { + double d = Point2D.sqrDistance(points[i], points_o[j]); + if (d > dist) { + dist = d; + } + } + } + + return dist; + } + /** * Calculates minimum squared distance from this envelope to the point. * Returns 0 for empty envelopes. diff --git a/src/main/java/com/esri/core/geometry/Envelope2DIntersectorImpl.java b/src/main/java/com/esri/core/geometry/Envelope2DIntersectorImpl.java index 98d7ad8c..35b83daa 100644 --- a/src/main/java/com/esri/core/geometry/Envelope2DIntersectorImpl.java +++ b/src/main/java/com/esri/core/geometry/Envelope2DIntersectorImpl.java @@ -43,7 +43,6 @@ void startConstruction() { m_elements_red = new AttributeStreamOfInt32(0); m_envelopes_red = new ArrayList(0); } else { - m_elements_red.resizePreserveCapacity(0); m_envelopes_red.clear(); } @@ -100,8 +99,7 @@ void endRedConstruction() { m_b_add_red = false; - if (m_envelopes_red != null && m_envelopes_red.size() > 0 - && m_envelopes_blue != null && m_envelopes_blue.size() > 0) { + if (m_envelopes_red != null && m_envelopes_red.size() > 0 && m_envelopes_blue != null && m_envelopes_blue.size() > 0) { if (m_function == -1) m_function = State.initializeRedBlue; else if (m_function == State.initializeBlue) @@ -142,8 +140,7 @@ void endBlueConstruction() { m_b_add_blue = false; - if (m_envelopes_red != null && m_envelopes_red.size() > 0 - && m_envelopes_blue != null && m_envelopes_blue.size() > 0) { + if (m_envelopes_red != null && m_envelopes_red.size() > 0 && m_envelopes_blue != null && m_envelopes_blue.size() > 0) { if (m_function == -1) m_function = State.initializeRedBlue; else if (m_function == State.initializeRed) @@ -255,6 +252,20 @@ void setTolerance(double tolerance) { m_tolerance = tolerance; } + /* + * Returns a reference to the envelope at the given handle. Use this for the red/red intersection case. + */ + Envelope2D getEnvelope(int handle) { + return m_envelopes_red.get(handle); + } + + /* + * Returns the user element associated with handle. Use this for the red/red intersection case. + */ + int getElement(int handle) { + return m_elements_red.read(handle); + } + /* * Returns a reference to the red envelope at handle_a. */ @@ -279,7 +290,6 @@ int getRedElement(int handle_a) { /* * Returns the user element associated with handle_b. */ - int getBlueElement(int handle_b) { return m_elements_blue.read(handle_b); } @@ -311,8 +321,7 @@ int getBlueElement(int handle_b) { private boolean m_b_add_red; private boolean m_b_add_blue; private boolean m_b_add_red_red; - - boolean m_b_done; + private boolean m_b_done; private static boolean isTop_(int y_end_point_handle) { return (y_end_point_handle & 0x1) == 1; @@ -345,16 +354,14 @@ private boolean initialize_() { if (m_interval_tree_red == null) { m_interval_tree_red = new IntervalTreeImpl(true); - m_iterator_red = m_interval_tree_red.getIterator(); m_sorted_end_indices_red = new AttributeStreamOfInt32(0); } - m_interval_tree_red.startConstruction(); - for (int i = 0; i < m_envelopes_red.size(); i++) { - Envelope2D env = m_envelopes_red.get(i); - m_interval_tree_red.addInterval(env.xmin, env.xmax); + m_interval_tree_red.addEnvelopesRef(m_envelopes_red); + + if (m_iterator_red == null) { + m_iterator_red = m_interval_tree_red.getIterator(); } - m_interval_tree_red.endConstruction(); m_sorted_end_indices_red.reserve(2 * m_envelopes_red.size()); m_sorted_end_indices_red.resize(0); @@ -362,8 +369,7 @@ private boolean initialize_() { for (int i = 0; i < 2 * m_envelopes_red.size(); i++) m_sorted_end_indices_red.add(i); - sortYEndIndices_(m_sorted_end_indices_red, 0, - 2 * m_envelopes_red.size(), true); + sortYEndIndices_(m_sorted_end_indices_red, 0, 2 * m_envelopes_red.size(), true); m_sweep_index_red = 2 * m_envelopes_red.size(); @@ -384,16 +390,14 @@ private boolean initializeRed_() { if (m_interval_tree_red == null) { m_interval_tree_red = new IntervalTreeImpl(true); - m_iterator_red = m_interval_tree_red.getIterator(); m_sorted_end_indices_red = new AttributeStreamOfInt32(0); } - m_interval_tree_red.startConstruction(); - for (int i = 0; i < m_envelopes_red.size(); i++) { - Envelope2D env = m_envelopes_red.get(i); - m_interval_tree_red.addInterval(env.xmin, env.xmax); + m_interval_tree_red.addEnvelopesRef(m_envelopes_red); + + if (m_iterator_red == null) { + m_iterator_red = m_interval_tree_red.getIterator(); } - m_interval_tree_red.endConstruction(); m_sorted_end_indices_red.reserve(2 * m_envelopes_red.size()); m_sorted_end_indices_red.resize(0); @@ -401,8 +405,7 @@ private boolean initializeRed_() { for (int i = 0; i < 2 * m_envelopes_red.size(); i++) m_sorted_end_indices_red.add(i); - sortYEndIndices_(m_sorted_end_indices_red, 0, - m_sorted_end_indices_red.size(), true); + sortYEndIndices_(m_sorted_end_indices_red, 0, m_sorted_end_indices_red.size(), true); m_sweep_index_red = m_sorted_end_indices_red.size(); if (m_queued_list_red != -1) { @@ -428,16 +431,14 @@ private boolean initializeBlue_() { if (m_interval_tree_blue == null) { m_interval_tree_blue = new IntervalTreeImpl(true); - m_iterator_blue = m_interval_tree_blue.getIterator(); m_sorted_end_indices_blue = new AttributeStreamOfInt32(0); } - m_interval_tree_blue.startConstruction(); - for (int i = 0; i < m_envelopes_blue.size(); i++) { - Envelope2D env = m_envelopes_blue.get(i); - m_interval_tree_blue.addInterval(env.xmin, env.xmax); + m_interval_tree_blue.addEnvelopesRef(m_envelopes_blue); + + if (m_iterator_blue == null) { + m_iterator_blue = m_interval_tree_blue.getIterator(); } - m_interval_tree_blue.endConstruction(); m_sorted_end_indices_blue.reserve(2 * m_envelopes_blue.size()); m_sorted_end_indices_blue.resize(0); @@ -445,8 +446,7 @@ private boolean initializeBlue_() { for (int i = 0; i < 2 * m_envelopes_blue.size(); i++) m_sorted_end_indices_blue.add(i); - sortYEndIndices_(m_sorted_end_indices_blue, 0, - m_sorted_end_indices_blue.size(), false); + sortYEndIndices_(m_sorted_end_indices_blue, 0, m_sorted_end_indices_blue.size(), false); m_sweep_index_blue = m_sorted_end_indices_blue.size(); if (m_queued_list_blue != -1) { @@ -472,29 +472,24 @@ private boolean initializeRedBlue_() { if (m_interval_tree_red == null) { m_interval_tree_red = new IntervalTreeImpl(true); - m_iterator_red = m_interval_tree_red.getIterator(); m_sorted_end_indices_red = new AttributeStreamOfInt32(0); } if (m_interval_tree_blue == null) { m_interval_tree_blue = new IntervalTreeImpl(true); - m_iterator_blue = m_interval_tree_blue.getIterator(); m_sorted_end_indices_blue = new AttributeStreamOfInt32(0); } - m_interval_tree_red.startConstruction(); - for (int i = 0; i < m_envelopes_red.size(); i++) { - Envelope2D env = m_envelopes_red.get(i); - m_interval_tree_red.addInterval(env.xmin, env.xmax); + m_interval_tree_red.addEnvelopesRef(m_envelopes_red); + m_interval_tree_blue.addEnvelopesRef(m_envelopes_blue); + + if (m_iterator_red == null) { + m_iterator_red = m_interval_tree_red.getIterator(); } - m_interval_tree_red.endConstruction(); - m_interval_tree_blue.startConstruction(); - for (int i = 0; i < m_envelopes_blue.size(); i++) { - Envelope2D env = m_envelopes_blue.get(i); - m_interval_tree_blue.addInterval(env.xmin, env.xmax); + if (m_iterator_blue == null) { + m_iterator_blue = m_interval_tree_blue.getIterator(); } - m_interval_tree_blue.endConstruction(); m_sorted_end_indices_red.reserve(2 * m_envelopes_red.size()); m_sorted_end_indices_blue.reserve(2 * m_envelopes_blue.size()); @@ -507,10 +502,8 @@ private boolean initializeRedBlue_() { for (int i = 0; i < 2 * m_envelopes_blue.size(); i++) m_sorted_end_indices_blue.add(i); - sortYEndIndices_(m_sorted_end_indices_red, 0, - m_sorted_end_indices_red.size(), true); - sortYEndIndices_(m_sorted_end_indices_blue, 0, - m_sorted_end_indices_blue.size(), false); + sortYEndIndices_(m_sorted_end_indices_red, 0, m_sorted_end_indices_red.size(), true); + sortYEndIndices_(m_sorted_end_indices_blue, 0, m_sorted_end_indices_blue.size(), false); m_sweep_index_red = m_sorted_end_indices_red.size(); m_sweep_index_blue = m_sorted_end_indices_blue.size(); @@ -533,8 +526,7 @@ private boolean initializeRedBlue_() { } private boolean sweep_() { - int y_end_point_handle = m_sorted_end_indices_red - .get(--m_sweep_index_red); + int y_end_point_handle = m_sorted_end_indices_red.get(--m_sweep_index_red); int envelope_handle = y_end_point_handle >> 1; if (isBottom_(y_end_point_handle)) { @@ -550,17 +542,14 @@ private boolean sweep_() { return true; } - m_iterator_red.resetIterator(m_envelopes_red.get(envelope_handle).xmin, - m_envelopes_red.get(envelope_handle).xmax, m_tolerance); + m_iterator_red.resetIterator(m_envelopes_red.get(envelope_handle).xmin, m_envelopes_red.get(envelope_handle).xmax, m_tolerance); m_envelope_handle_a = envelope_handle; m_function = State.iterate; return true; } - private boolean sweepBruteForce_() {// this isn't really a sweep, it just - // walks along the array of red - // envelopes backward. + private boolean sweepBruteForce_() {// this isn't really a sweep, it just walks along the array of red envelopes backward. if (--m_sweep_index_red == -1) { m_envelope_handle_a = -1; m_envelope_handle_b = -1; @@ -575,9 +564,7 @@ private boolean sweepBruteForce_() {// this isn't really a sweep, it just return true; } - private boolean sweepRedBlueBruteForce_() {// this isn't really a sweep, it - // just walks along the array of - // red envelopes backward. + private boolean sweepRedBlueBruteForce_() {// this isn't really a sweep, it just walks along the array of red envelopes backward. if (--m_sweep_index_red == -1) { m_envelope_handle_a = -1; m_envelope_handle_b = -1; @@ -592,13 +579,9 @@ private boolean sweepRedBlueBruteForce_() {// this isn't really a sweep, it return true; } - private boolean sweepRedBlue_() {// controls whether we want to sweep the - // red envelopes or sweep the blue - // envelopes - int y_end_point_handle_red = m_sorted_end_indices_red - .get(m_sweep_index_red - 1); - int y_end_point_handle_blue = m_sorted_end_indices_blue - .get(m_sweep_index_blue - 1); + private boolean sweepRedBlue_() {// controls whether we want to sweep the red envelopes or sweep the blue envelopes + int y_end_point_handle_red = m_sorted_end_indices_red.get(m_sweep_index_red - 1); + int y_end_point_handle_blue = m_sorted_end_indices_blue.get(m_sweep_index_blue - 1); double y_red = getAdjustedValue_(y_end_point_handle_red, true); double y_blue = getAdjustedValue_(y_end_point_handle_blue, false); @@ -613,20 +596,16 @@ private boolean sweepRedBlue_() {// controls whether we want to sweep the if (isTop_(y_end_point_handle_blue)) return sweepBlue_(); - return sweepRed_(); // arbitrary. can call sweep_blue_ instead and would - // also work correctly + return sweepRed_(); // arbitrary. can call sweep_blue_ instead and would also work correctly } private boolean sweepRed_() { - int y_end_point_handle_red = m_sorted_end_indices_red - .get(--m_sweep_index_red); + int y_end_point_handle_red = m_sorted_end_indices_red.get(--m_sweep_index_red); int envelope_handle_red = y_end_point_handle_red >> 1; if (isBottom_(y_end_point_handle_red)) { - if (m_queued_list_red != -1 - && m_queued_indices_red.get(envelope_handle_red) != -1) { - m_queued_envelopes.deleteElement(m_queued_list_red, - m_queued_indices_red.get(envelope_handle_red)); + if (m_queued_list_red != -1 && m_queued_indices_red.get(envelope_handle_red) != -1) { + m_queued_envelopes.deleteElement(m_queued_list_red, m_queued_indices_red.get(envelope_handle_red)); m_queued_indices_red.set(envelope_handle_red, -1); } else m_interval_tree_red.remove(envelope_handle_red); @@ -641,8 +620,7 @@ private boolean sweepRed_() { return true; } - if (m_queued_list_blue != -1 - && m_queued_envelopes.getListSize(m_queued_list_blue) > 0) { + if (m_queued_list_blue != -1 && m_queued_envelopes.getListSize(m_queued_list_blue) > 0) { int node = m_queued_envelopes.getFirst(m_queued_list_blue); while (node != -1) { int e = m_queued_envelopes.getData(node); @@ -655,9 +633,7 @@ private boolean sweepRed_() { } if (m_interval_tree_blue.size() > 0) { - m_iterator_blue.resetIterator( - m_envelopes_red.get(envelope_handle_red).xmin, - m_envelopes_red.get(envelope_handle_red).xmax, m_tolerance); + m_iterator_blue.resetIterator(m_envelopes_red.get(envelope_handle_red).xmin, m_envelopes_red.get(envelope_handle_red).xmax, m_tolerance); m_envelope_handle_a = envelope_handle_red; m_function = State.iterateBlue; } else { @@ -671,8 +647,7 @@ private boolean sweepRed_() { m_queued_list_red = m_queued_envelopes.createList(1); } - m_queued_indices_red.set(envelope_handle_red, m_queued_envelopes - .addElement(m_queued_list_red, envelope_handle_red)); + m_queued_indices_red.set(envelope_handle_red, m_queued_envelopes.addElement(m_queued_list_red, envelope_handle_red)); m_function = State.sweepRedBlue; } @@ -680,15 +655,12 @@ private boolean sweepRed_() { } private boolean sweepBlue_() { - int y_end_point_handle_blue = m_sorted_end_indices_blue - .get(--m_sweep_index_blue); + int y_end_point_handle_blue = m_sorted_end_indices_blue.get(--m_sweep_index_blue); int envelope_handle_blue = y_end_point_handle_blue >> 1; if (isBottom_(y_end_point_handle_blue)) { - if (m_queued_list_blue != -1 - && m_queued_indices_blue.get(envelope_handle_blue) != -1) { - m_queued_envelopes.deleteElement(m_queued_list_blue, - m_queued_indices_blue.get(envelope_handle_blue)); + if (m_queued_list_blue != -1 && m_queued_indices_blue.get(envelope_handle_blue) != -1) { + m_queued_envelopes.deleteElement(m_queued_list_blue, m_queued_indices_blue.get(envelope_handle_blue)); m_queued_indices_blue.set(envelope_handle_blue, -1); } else m_interval_tree_blue.remove(envelope_handle_blue); @@ -703,8 +675,7 @@ private boolean sweepBlue_() { return true; } - if (m_queued_list_red != -1 - && m_queued_envelopes.getListSize(m_queued_list_red) > 0) { + if (m_queued_list_red != -1 && m_queued_envelopes.getListSize(m_queued_list_red) > 0) { int node = m_queued_envelopes.getFirst(m_queued_list_red); while (node != -1) { int e = m_queued_envelopes.getData(node); @@ -717,10 +688,7 @@ private boolean sweepBlue_() { } if (m_interval_tree_red.size() > 0) { - m_iterator_red.resetIterator( - m_envelopes_blue.get(envelope_handle_blue).xmin, - m_envelopes_blue.get(envelope_handle_blue).xmax, - m_tolerance); + m_iterator_red.resetIterator(m_envelopes_blue.get(envelope_handle_blue).xmin, m_envelopes_blue.get(envelope_handle_blue).xmax, m_tolerance); m_envelope_handle_b = envelope_handle_blue; m_function = State.iterateRed; } else { @@ -734,8 +702,7 @@ private boolean sweepBlue_() { m_queued_list_blue = m_queued_envelopes.createList(0); } - m_queued_indices_blue.set(envelope_handle_blue, m_queued_envelopes - .addElement(m_queued_list_blue, envelope_handle_blue)); + m_queued_indices_blue.set(envelope_handle_blue, m_queued_envelopes.addElement(m_queued_list_blue, envelope_handle_blue)); m_function = State.sweepRedBlue; } @@ -762,8 +729,7 @@ private boolean iterateRed_() { m_envelope_handle_a = -1; m_envelope_handle_b = -1; - int envelope_handle_blue = m_sorted_end_indices_blue - .get(m_sweep_index_blue) >> 1; + int envelope_handle_blue = m_sorted_end_indices_blue.get(m_sweep_index_blue) >> 1; m_interval_tree_blue.insert(envelope_handle_blue); m_function = State.sweepRedBlue; @@ -775,8 +741,7 @@ private boolean iterateBlue_() { if (m_envelope_handle_b != -1) return false; - int envelope_handle_red = m_sorted_end_indices_red - .get(m_sweep_index_red) >> 1; + int envelope_handle_red = m_sorted_end_indices_red.get(m_sweep_index_red) >> 1; m_interval_tree_red.insert(envelope_handle_red); m_function = State.sweepRedBlue; @@ -886,18 +851,15 @@ private interface State { // *********** Helpers for Bucket sort************** private BucketSort m_bucket_sort; - private void sortYEndIndices_(AttributeStreamOfInt32 end_indices, - int begin_, int end_, boolean b_red) { + private void sortYEndIndices_(AttributeStreamOfInt32 end_indices, int begin_, int end_, boolean b_red) { if (m_bucket_sort == null) m_bucket_sort = new BucketSort(); - Envelope2DBucketSortHelper sorter = new Envelope2DBucketSortHelper( - this, b_red); + Envelope2DBucketSortHelper sorter = new Envelope2DBucketSortHelper(this, b_red); m_bucket_sort.sort(end_indices, begin_, end_, sorter); } - private void sortYEndIndicesHelper_(AttributeStreamOfInt32 end_indices, - int begin_, int end_, boolean b_red) { + private void sortYEndIndicesHelper_(AttributeStreamOfInt32 end_indices, int begin_, int end_, boolean b_red) { end_indices.Sort(begin_, end_, new EndPointsComparer(this, b_red)); } @@ -905,19 +867,17 @@ private double getAdjustedValue_(int e, boolean b_red) { double dy = 0.5 * m_tolerance; if (b_red) { Envelope2D envelope_red = m_envelopes_red.get(e >> 1); - double y = (isBottom_(e) ? envelope_red.ymin - dy - : envelope_red.ymax + dy); + double y = (isBottom_(e) ? envelope_red.ymin - dy : envelope_red.ymax + dy); return y; } Envelope2D envelope_blue = m_envelopes_blue.get(e >> 1); - double y = (isBottom_(e) ? envelope_blue.ymin - dy : envelope_blue.ymax - + dy); + double y = (isBottom_(e) ? envelope_blue.ymin - dy : envelope_blue.ymax + dy); return y; } - private static final class EndPointsComparer extends - AttributeStreamOfInt32.IntComparator {// For user sort + private static final class EndPointsComparer extends AttributeStreamOfInt32.IntComparator {// For user sort + EndPointsComparer(Envelope2DIntersectorImpl intersector, boolean b_red) { m_intersector = intersector; m_b_red = b_red; @@ -939,10 +899,10 @@ public int compare(int e_1, int e_2) { } private static final class Envelope2DBucketSortHelper extends ClassicSort {// For - // bucket - // sort - Envelope2DBucketSortHelper(Envelope2DIntersectorImpl intersector, - boolean b_red) { + + // bucket + // sort + Envelope2DBucketSortHelper(Envelope2DIntersectorImpl intersector, boolean b_red) { m_intersector = intersector; m_b_red = b_red; } diff --git a/src/main/java/com/esri/core/geometry/Envelope3D.java b/src/main/java/com/esri/core/geometry/Envelope3D.java index c1960fd6..3f64b053 100644 --- a/src/main/java/com/esri/core/geometry/Envelope3D.java +++ b/src/main/java/com/esri/core/geometry/Envelope3D.java @@ -47,20 +47,23 @@ public final class Envelope3D implements Serializable{ public static Envelope3D construct(double _xmin, double _ymin, double _zmin, double _xmax, double _ymax, double _zmax) { - Envelope3D env = new Envelope3D(); - env.xmin = _xmin; - env.ymin = _ymin; - env.zmin = _zmin; - env.xmax = _xmax; - env.ymax = _ymax; - env.zmax = _zmax; + Envelope3D env = new Envelope3D(_xmin, _ymin, _zmin, _xmax, _ymax, _zmax); return env; } + public Envelope3D(double _xmin, double _ymin, double _zmin, double _xmax, double _ymax, double _zmax) { + setCoords(_xmin, _ymin, _zmin, _xmax, _ymax, _zmax); + } + public Envelope3D() { } + public Envelope3D(Envelope3D other) { + setCoords(other); + } + + public void setInfinite() { xmin = NumberUtils.negativeInf(); xmax = NumberUtils.positiveInf(); @@ -72,7 +75,11 @@ public void setInfinite() { public void setEmpty() { xmin = NumberUtils.NaN(); + ymin = NumberUtils.NaN(); zmin = NumberUtils.NaN(); + xmax = 0; + ymax = 0; + zmax = 0; } public boolean isEmpty() { @@ -99,6 +106,7 @@ public void setCoords(double _xmin, double _ymin, double _zmin, xmax = _xmax; ymax = _ymax; zmax = _zmax; + normalize(); } public void setCoords(double _x, double _y, double _z) { @@ -118,6 +126,24 @@ public void setCoords(Point3D center, double width, double height, ymax = ymin + height; zmin = center.z - depth * 0.5; zmax = zmin + depth; + normalize(); + } + + public void setCoords(Envelope3D envSrc) { + + setCoords(envSrc.xmin, envSrc.ymin, envSrc.zmin, envSrc.xmax, envSrc.ymax, envSrc.zmax); + } + + public double getWidth() { + return xmax - xmin; + } + + public double getHeight() { + return ymax - ymin; + } + + public double getDepth() { + return zmax - zmin; } public void move(Point3D vector) { @@ -129,6 +155,24 @@ public void move(Point3D vector) { zmax += vector.z; } + public void normalize() { + if (isEmpty()) + return; + + double min = Math.min(xmin, xmax); + double max = Math.max(xmin, xmax); + xmin = min; + xmax = max; + min = Math.min(ymin, ymax); + max = Math.max(ymin, ymax); + ymin = min; + ymax = max; + min = Math.min(zmin, zmax); + max = Math.max(zmin, zmax); + zmin = min; + zmax = max; + } + public void copyTo(Envelope2D env) { env.xmin = xmin; env.ymin = ymin; @@ -189,6 +233,93 @@ public void merge(double x1, double y1, double z1, double x2, double y2, merge(x2, y2, z2); } + public void inflate(double dx, double dy, double dz) { + if (isEmpty()) + return; + xmin -= dx; + xmax += dx; + ymin -= dy; + ymax += dy; + zmin -= dz; + zmax += dz; + if (xmin > xmax || ymin > ymax || zmin > zmax) + setEmpty(); + } + + /** + * Checks if this envelope intersects the other. + * + * @return True if this envelope intersects the other. + */ + public boolean isIntersecting(Envelope3D other) { + return !isEmpty() && !other.isEmpty() && ((xmin <= other.xmin) ? xmax >= other.xmin : other.xmax >= xmin) && // check that x projections overlap + ((ymin <= other.ymin) ? ymax >= other.ymin : other.ymax >= ymin) && // check that y projections overlap + ((zmin <= other.zmin) ? zmax >= other.zmin : other.zmax >= zmin); // check that z projections overlap + } + + /** + * Intersects this envelope with the other and stores result in this + * envelope. + * + * @return True if this envelope intersects the other, otherwise sets this + * envelope to empty state and returns False. + */ + public boolean intersect(Envelope3D other) { + if (isEmpty() || other.isEmpty()) + return false; + + if (other.xmin > xmin) + xmin = other.xmin; + + if (other.xmax < xmax) + xmax = other.xmax; + + if (other.ymin > ymin) + ymin = other.ymin; + + if (other.ymax < ymax) + ymax = other.ymax; + + if (other.zmin > zmin) + zmin = other.zmin; + + if (other.zmax < zmax) + zmax = other.zmax; + + boolean bIntersecting = xmin <= xmax && ymin <= ymax && zmin <= zmax; + + if (!bIntersecting) + setEmpty(); + + return bIntersecting; + } + + /** + * Returns True if the envelope contains the other envelope (boundary + * inclusive). + */ + public boolean contains(Envelope3D other) {// Note: Will return False, if either envelope is empty. + return other.xmin >= xmin && other.xmax <= xmax && other.ymin >= ymin && other.ymax <= ymax && other.zmin >= zmin && other.zmax <= zmax; + } + + @Override + public boolean equals(Object _other) { + if (_other == this) + return true; + + if (!(_other instanceof Envelope3D)) + return false; + + Envelope3D other = (Envelope3D) _other; + if (isEmpty() && other.isEmpty()) + return true; + + if (xmin != other.xmin || ymin != other.ymin || zmin != other.zmin || xmax != other.xmax || ymax != other.ymax || zmax != other.zmax) + return false; + + return true; + } + public void construct(Envelope1D xinterval, Envelope1D yinterval, Envelope1D zinterval) { if (xinterval.isEmpty() || yinterval.isEmpty()) { diff --git a/src/main/java/com/esri/core/geometry/GenericGeometrySerializer.java b/src/main/java/com/esri/core/geometry/GenericGeometrySerializer.java new file mode 100644 index 00000000..c6bd2d09 --- /dev/null +++ b/src/main/java/com/esri/core/geometry/GenericGeometrySerializer.java @@ -0,0 +1,94 @@ +/* + Copyright 1995-2015 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ +package com.esri.core.geometry; + +import java.io.InvalidObjectException; +import java.io.ObjectStreamException; +import java.io.Serializable; + +//This is a writeReplace class for MultiPoint, Polyline, and Polygon +public class GenericGeometrySerializer implements Serializable { + private static final long serialVersionUID = 1L; + int geometryType; + byte[] esriShape = null; + int simpleFlag = 0; + double tolerance = 0; + boolean[] ogcFlags = null; + + public Object readResolve() throws ObjectStreamException { + Geometry geometry = null; + try { + geometry = GeometryEngine.geometryFromEsriShape( + esriShape, Geometry.Type.intToType(geometryType)); + + if (Geometry.isMultiVertex(geometryType)) { + MultiVertexGeometryImpl mvImpl = (MultiVertexGeometryImpl) geometry + ._getImpl(); + if (!geometry.isEmpty() + && Geometry.isMultiPath(geometryType)) { + MultiPathImpl mpImpl = (MultiPathImpl) geometry._getImpl(); + AttributeStreamOfInt8 pathFlags = mpImpl + .getPathFlagsStreamRef(); + for (int i = 0, n = mpImpl.getPathCount(); i < n; i++) { + if (ogcFlags[i]) + pathFlags.setBits(i, + (byte) PathFlags.enumOGCStartPolygon); + } + } + mvImpl.setIsSimple(simpleFlag, tolerance, false); + } + } catch (Exception ex) { + throw new InvalidObjectException("Cannot read geometry from stream"); + } + return geometry; + } + + public void setGeometryByValue(Geometry geometry) + throws ObjectStreamException { + try { + esriShape = GeometryEngine + .geometryToEsriShape(geometry); + geometryType = geometry.getType().value(); + if (Geometry.isMultiVertex(geometryType)) { + MultiVertexGeometryImpl mvImpl = (MultiVertexGeometryImpl) geometry + ._getImpl(); + tolerance = mvImpl.m_simpleTolerance; + simpleFlag = mvImpl.getIsSimple(0); + if (!geometry.isEmpty() + && Geometry.isMultiPath(geometryType)) { + MultiPathImpl mpImpl = (MultiPathImpl) geometry._getImpl(); + ogcFlags = new boolean[mpImpl.getPathCount()]; + AttributeStreamOfInt8 pathFlags = mpImpl + .getPathFlagsStreamRef(); + for (int i = 0, n = mpImpl.getPathCount(); i < n; i++) { + ogcFlags[i] = (pathFlags.read(i) & (byte) PathFlags.enumOGCStartPolygon) != 0; + } + } + + } + } catch (Exception ex) { + throw new InvalidObjectException("Cannot serialize this geometry"); + } + } +} diff --git a/src/main/java/com/esri/core/geometry/GeoDist.java b/src/main/java/com/esri/core/geometry/GeoDist.java index 108e04e0..b7845151 100644 --- a/src/main/java/com/esri/core/geometry/GeoDist.java +++ b/src/main/java/com/esri/core/geometry/GeoDist.java @@ -257,7 +257,7 @@ static public void geodesic_distance_ngs(double a, double e2, double lam1, /* top of the long-line loop (kind = 1) */ q_continue_looping = true; - while (q_continue_looping == true) { + while (q_continue_looping && it < 100) { it = it + 1; if (kind == 1) { diff --git a/src/main/java/com/esri/core/geometry/GeoJsonCrsTables.java b/src/main/java/com/esri/core/geometry/GeoJsonCrsTables.java new file mode 100644 index 00000000..aa35f20d --- /dev/null +++ b/src/main/java/com/esri/core/geometry/GeoJsonCrsTables.java @@ -0,0 +1,183 @@ +/* + Copyright 1995-2015 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ +package com.esri.core.geometry; + +import java.util.Arrays; + +class GeoJsonCrsTables { + static int getWkidFromCrsShortForm(String crs_identifier) { + int last_colon = crs_identifier.lastIndexOf((int) ':'); // skip version + + if (last_colon == -1) + return -1; + + int code_start = last_colon + 1; + int wkid = getWkidFromCrsCode_(crs_identifier, code_start); + return wkid; + } + + static int getWkidFromCrsName(String crs_identifier) { + int wkid = -1; + + int last_colon = crs_identifier.lastIndexOf((int) ':'); // skip + // authority, + // version, and + // other things. + // Just try to + // get a wkid. + // This works + // for + // short/long + // form. + + if (last_colon == -1) + return -1; + + int code_start = last_colon + 1; + wkid = getWkidFromCrsCode_(crs_identifier, code_start); + + if (wkid != -1) + return wkid; + + wkid = getWkidFromCrsOgcUrn(crs_identifier); // could be an OGC + // "preferred" urn + return wkid; + } + + static int getWkidFromCrsOgcUrn(String crs_identifier) { + int wkid = -1; + if (crs_identifier.regionMatches(0, "urn:ogc:def:crs:OGC", 0, 19)) + wkid = getWkidFromCrsOgcUrn_(crs_identifier); + + return wkid; + } + + private static int getWkidFromCrsCode_(String crs_identifier, int code_start) { + assert(code_start > 0); + + int wkid = -1; + int code_count = crs_identifier.length() - code_start; + + try { + wkid = Integer.parseInt(crs_identifier.substring(code_start, code_start + code_count)); + } catch (Exception e) { + } + + return (int) wkid; + } + + private static int getWkidFromCrsOgcUrn_(String crs_identifier) { + assert(crs_identifier.regionMatches(0, "urn:ogc:def:crs:OGC", 0, 19)); + + int last_colon = crs_identifier.lastIndexOf((int) ':'); // skip version + + if (last_colon == -1) + return -1; + + int ogc_code_start = last_colon + 1; + int ogc_code_count = crs_identifier.length() - ogc_code_start; + + if (crs_identifier.regionMatches(ogc_code_start, "CRS84", 0, ogc_code_count)) + return 4326; + + if (crs_identifier.regionMatches(ogc_code_start, "CRS83", 0, ogc_code_count)) + return 4269; + + if (crs_identifier.regionMatches(ogc_code_start, "CRS27", 0, ogc_code_count)) + return 4267; + + return -1; + } + + static int getWkidFromCrsHref(String crs_identifier) { + int sr_org_code_start = -1; + + if (crs_identifier.regionMatches(0, "http://spatialreference.org/ref/epsg/", 0, 37)) + sr_org_code_start = 37; + else if (crs_identifier.regionMatches(0, "www.spatialreference.org/ref/epsg/", 0, 34)) + sr_org_code_start = 34; + else if (crs_identifier.regionMatches(0, "http://www.spatialreference.org/ref/epsg/", 0, 41)) + sr_org_code_start = 41; + + if (sr_org_code_start != -1) { + int sr_org_code_end = crs_identifier.indexOf('/', sr_org_code_start); + + if (sr_org_code_end == -1) + return -1; + + int count = sr_org_code_end - sr_org_code_start; + int wkid = -1; + + try { + wkid = Integer.parseInt(crs_identifier.substring(sr_org_code_start, sr_org_code_start + count)); + } catch (Exception e) { + } + + return wkid; + } + + int open_gis_epsg_slash_end = -1; + + if (crs_identifier.regionMatches(0, "http://opengis.net/def/crs/EPSG/", 0, 32)) + open_gis_epsg_slash_end = 32; + else if (crs_identifier.regionMatches(0, "www.opengis.net/def/crs/EPSG/", 0, 29)) + open_gis_epsg_slash_end = 29; + else if (crs_identifier.regionMatches(0, "http://www.opengis.net/def/crs/EPSG/", 0, 36)) + open_gis_epsg_slash_end = 36; + + if (open_gis_epsg_slash_end != -1) { + int last_slash = crs_identifier.lastIndexOf('/'); // skip over the + // "0/" + + if (last_slash == -1) + return -1; + + int open_gis_code_start = last_slash + 1; + + int count = crs_identifier.length() - open_gis_code_start; + int wkid = -1; + + try { + wkid = Integer.parseInt(crs_identifier.substring(open_gis_code_start, open_gis_code_start + count)); + } catch (Exception e) { + } + + return wkid; + } + + if (crs_identifier.compareToIgnoreCase("http://spatialreference.org/ref/sr-org/6928/ogcwkt/") == 0) + return 3857; + + return -1; + } + + static String getWktFromCrsName(String crs_identifier) { + int last_colon = crs_identifier.lastIndexOf((int) ':'); // skip + // authority + int wkt_start = last_colon + 1; + int wkt_count = crs_identifier.length() - wkt_start; + String wkt = crs_identifier.substring(wkt_start, wkt_start + wkt_count); + return wkt; + } +} diff --git a/src/main/java/com/esri/core/geometry/GeoJsonExportFlags.java b/src/main/java/com/esri/core/geometry/GeoJsonExportFlags.java index 027bf2ac..6290a0f6 100644 --- a/src/main/java/com/esri/core/geometry/GeoJsonExportFlags.java +++ b/src/main/java/com/esri/core/geometry/GeoJsonExportFlags.java @@ -24,6 +24,31 @@ package com.esri.core.geometry; public interface GeoJsonExportFlags { - static final int geoJsonExportDefaults = 0; - static final int geoJsonExportPreferMultiGeometry = 1; + public static final int geoJsonExportDefaults = 0; + /** + * Export MultiXXX geometries every time, by default it will export the minimum required type. + */ + public static final int geoJsonExportPreferMultiGeometry = 1; + public static final int geoJsonExportStripZs = 2; + public static final int geoJsonExportStripMs = 4; + public static final int geoJsonExportSkipCRS = 8; + public static final int geoJsonExportFailIfNotSimple = 16; + public static final int geoJsonExportPrecision16 = 0x02000; + public static final int geoJsonExportPrecision15 = 0x04000; + public static final int geoJsonExportPrecision14 = 0x06000; + public static final int geoJsonExportPrecision13 = 0x08000; + public static final int geoJsonExportPrecision12 = 0x0a000; + public static final int geoJsonExportPrecision11 = 0x0c000; + public static final int geoJsonExportPrecision10 = 0x0e000; + public static final int geoJsonExportPrecision9 = 0x10000; + public static final int geoJsonExportPrecision8 = 0x12000; + public static final int geoJsonExportPrecision7 = 0x14000; + public static final int geoJsonExportPrecision6 = 0x16000; + public static final int geoJsonExportPrecision5 = 0x18000; + public static final int geoJsonExportPrecision4 = 0x1a000; + public static final int geoJsonExportPrecision3 = 0x1c000; + public static final int geoJsonExportPrecision2 = 0x1e000; + public static final int geoJsonExportPrecision1 = 0x20000; + public static final int geoJsonExportPrecision0 = 0x22000; + public static final int geoJsonExportPrecisionFixedPoint = 0x40000; } diff --git a/src/main/java/com/esri/core/geometry/GeoJsonImportFlags.java b/src/main/java/com/esri/core/geometry/GeoJsonImportFlags.java index a464d20b..5245bd96 100644 --- a/src/main/java/com/esri/core/geometry/GeoJsonImportFlags.java +++ b/src/main/java/com/esri/core/geometry/GeoJsonImportFlags.java @@ -24,6 +24,15 @@ package com.esri.core.geometry; public interface GeoJsonImportFlags { - static final int geoJsonImportDefaults = 0; - static final int geoJsonImportNonTrusted = 2; + public static final int geoJsonImportDefaults = 0; + @Deprecated static final int geoJsonImportNonTrusted = 2; + /** + * If set, the import will skip CRS. + */ + public static final int geoJsonImportSkipCRS = 8; + /** + * If set, and the geojson does not have a spatial reference, the result geometry will not have one too, otherwise + * it'll assume WGS84. + */ + public static final int geoJsonImportNoWGS84Default = 16; } diff --git a/src/main/java/com/esri/core/geometry/Geometry.java b/src/main/java/com/esri/core/geometry/Geometry.java index e5d08afa..68e93671 100644 --- a/src/main/java/com/esri/core/geometry/Geometry.java +++ b/src/main/java/com/esri/core/geometry/Geometry.java @@ -35,10 +35,6 @@ * objects that define a spatial location and and associated geometric shape. */ public abstract class Geometry implements Serializable { - // Note: We use writeReplace with GeometrySerializer. This field is - // irrelevant. Need to be removed after final. - private static final long serialVersionUID = 2L; - VertexDescription m_description; volatile int m_touchFlag; @@ -117,6 +113,18 @@ public int value() { Type(int val) { enumValue = val; } + + static public Geometry.Type intToType(int geometryType) + { + Geometry.Type[] v = Geometry.Type.values(); + for(int i = 0; i < v.length; i++) + { + if(v[i].value() == geometryType) + return v[i]; + } + + throw new IllegalArgumentException(); + } } /** @@ -153,7 +161,7 @@ public VertexDescription getDescription() { * Assigns the new VertexDescription by adding or dropping attributes. The * Geometry will have the src description as a result. */ - void assignVertexDescription(VertexDescription src) { + public void assignVertexDescription(VertexDescription src) { _touch(); if (src == m_description) return; @@ -161,14 +169,14 @@ void assignVertexDescription(VertexDescription src) { _assignVertexDescriptionImpl(src); } - protected abstract void _assignVertexDescriptionImpl(VertexDescription src); + protected abstract void _assignVertexDescriptionImpl(VertexDescription src); /** * Merges the new VertexDescription by adding missing attributes from the * src. The Geometry will have a union of the current and the src * descriptions. */ - void mergeVertexDescription(VertexDescription src) { + public void mergeVertexDescription(VertexDescription src) { _touch(); if (src == m_description) return; @@ -566,7 +574,27 @@ static public enum GeometryAccelerationDegree { } Object writeReplace() throws ObjectStreamException { - GeometrySerializer geomSerializer = new GeometrySerializer(); + Type gt = getType(); + if (gt == Geometry.Type.Point) + { + PtSrlzr pt = new PtSrlzr(); + pt.setGeometryByValue((Point)this); + return pt; + } + else if (gt == Geometry.Type.Envelope) + { + EnvSrlzr e = new EnvSrlzr(); + e.setGeometryByValue((Envelope)this); + return e; + } + else if (gt == Geometry.Type.Line) + { + LnSrlzr ln = new LnSrlzr(); + ln.setGeometryByValue((Line)this); + return ln; + } + + GenericGeometrySerializer geomSerializer = new GenericGeometrySerializer(); geomSerializer.setGeometryByValue(this); return geomSerializer; } diff --git a/src/main/java/com/esri/core/geometry/GeometryEngine.java b/src/main/java/com/esri/core/geometry/GeometryEngine.java index e027dd6c..7bed73ea 100644 --- a/src/main/java/com/esri/core/geometry/GeometryEngine.java +++ b/src/main/java/com/esri/core/geometry/GeometryEngine.java @@ -242,6 +242,7 @@ public static Geometry geometryFromWkt(String wkt, int importFlags, * @throws GeometryException when the geometryType is not Geometry.Type.Unknown and the geoJson contains a geometry that cannot be converted to the given geometryType. * @throws IllegalArgument exception if an error is found while parsing the geoJson string. */ + @Deprecated public static MapGeometry geometryFromGeoJson(String geoJson, int importFlags, Geometry.Type geometryType) throws JSONException { OperatorImportFromGeoJson op = (OperatorImportFromGeoJson) factory diff --git a/src/main/java/com/esri/core/geometry/GeometrySerializer.java b/src/main/java/com/esri/core/geometry/GeometrySerializer.java index b91c16f7..a2fefa1e 100644 --- a/src/main/java/com/esri/core/geometry/GeometrySerializer.java +++ b/src/main/java/com/esri/core/geometry/GeometrySerializer.java @@ -27,6 +27,8 @@ import java.io.ObjectStreamException; import java.io.Serializable; +//Left here for backward compatibility. Use GenericGeometrySerializer instead +@Deprecated final class GeometrySerializer implements Serializable { private static final long serialVersionUID = 1L; diff --git a/src/main/java/com/esri/core/geometry/IntervalTreeImpl.java b/src/main/java/com/esri/core/geometry/IntervalTreeImpl.java index acdc10d8..f7248c47 100644 --- a/src/main/java/com/esri/core/geometry/IntervalTreeImpl.java +++ b/src/main/java/com/esri/core/geometry/IntervalTreeImpl.java @@ -26,504 +26,452 @@ import java.util.ArrayList; final class IntervalTreeImpl { - static final class IntervalTreeIteratorImpl { - /** - * Resets the iterator to a starting state on the Interval_tree_impl - * using the input Envelope_1D interval as the query \param query The - * Envelope_1D interval used for the query. \param tolerance The - * tolerance used for the intersection tests. - */ - void resetIterator(Envelope1D query, double tolerance) { - m_query.vmin = query.vmin - tolerance; - m_query.vmax = query.vmax + tolerance; - m_tertiary_stack.resize(0); - m_function_index = 0; - m_function_stack[0] = State.initialize; - } + private void sortEndIndices_(AttributeStreamOfInt32 end_indices, int begin_, int end_) { + IntervalTreeBucketSortHelper sorter = new IntervalTreeBucketSortHelper(this); + BucketSort bucket_sort = new BucketSort(); + bucket_sort.sort(end_indices, begin_, end_, sorter); + } - /** - * Resets the iterator to a starting state on the Interval_tree_impl - * using the input Envelope_1D interval as the query \param query The - * Envelope_1D interval used for the query. \param tolerance The - * tolerance used for the intersection tests. - */ - void resetIterator(double query_min, double query_max, double tolerance) { - if (query_min > query_max) - throw new IllegalArgumentException(); + private void sortEndIndicesHelper_(AttributeStreamOfInt32 end_indices, int begin_, int end_) { + end_indices.Sort(begin_, end_, new EndPointsComparer(this)); + } - m_query.vmin = query_min - tolerance; - m_query.vmax = query_max + tolerance; - m_tertiary_stack.resize(0); - m_function_index = 0; - m_function_stack[0] = State.initialize; + private double getValue_(int e) { + if (!m_b_envelopes_ref) { + Envelope1D interval = m_intervals.get(e >> 1); + double v = (isLeft_(e) ? interval.vmin : interval.vmax); + return v; } - /** - * Resets the iterator to a starting state on the Interval_tree_impl - * using the input double as the stabbing query \param query The double - * used for the query. \param tolerance The tolerance used for the - * intersection tests. - */ - void resetIterator(double query, double tolerance) { - m_query.vmin = query - tolerance; - m_query.vmax = query + tolerance; - m_tertiary_stack.resize(0); - m_function_index = 0; - m_function_stack[0] = State.initialize; + Envelope2D interval = m_envelopes_ref.get(e >> 1); + double v = (isLeft_(e) ? interval.xmin : interval.xmax); + return v; + } + + private static final class EndPointsComparer extends AttributeStreamOfInt32.IntComparator { // For user sort + + EndPointsComparer(IntervalTreeImpl interval_tree) { + m_interval_tree = interval_tree; } - /** - * Iterates over all intervals which interset the query interval. - * Returns an index to an interval that intersects the query. - */ - int next() { - if (!m_interval_tree.m_b_construction_ended) - throw new GeometryException("invalid call"); + @Override + public int compare(int e_1, int e_2) { + double v_1 = m_interval_tree.getValue_(e_1); + double v_2 = m_interval_tree.getValue_(e_2); - if (m_function_index < 0) + if (v_1 < v_2 || (v_1 == v_2 && isLeft_(e_1) && isRight_(e_2))) return -1; - boolean b_searching = true; - - while (b_searching) { - switch (m_function_stack[m_function_index]) { - case State.pIn: - b_searching = pIn_(); - break; - case State.pL: - b_searching = pL_(); - break; - case State.pR: - b_searching = pR_(); - break; - case State.pT: - b_searching = pT_(); - break; - case State.right: - b_searching = right_(); - break; - case State.left: - b_searching = left_(); - break; - case State.all: - b_searching = all_(); - break; - case State.initialize: - b_searching = initialize_(); - break; - default: - throw GeometryException.GeometryInternalError(); - } - } + return 1; + } - if (m_current_end_handle != -1) - return getCurrentEndIndex_() >> 1; + private IntervalTreeImpl m_interval_tree; + } - return -1; - } + private class IntervalTreeBucketSortHelper extends ClassicSort { // For bucket sort - // Creates an iterator on the input Interval_tree using the input - // Envelope_1D interval as the query. - IntervalTreeIteratorImpl(IntervalTreeImpl interval_tree, - Envelope1D query, double tolerance) { + IntervalTreeBucketSortHelper(IntervalTreeImpl interval_tree) { m_interval_tree = interval_tree; - m_tertiary_stack.reserve(20); - resetIterator(query, tolerance); } - // Creates an iterator on the input Interval_tree using the input double - // as the stabbing query. - IntervalTreeIteratorImpl(IntervalTreeImpl interval_tree, double query, - double tolerance) { - m_interval_tree = interval_tree; - m_tertiary_stack.reserve(20); - resetIterator(query, tolerance); + @Override + public void userSort(int begin, int end, AttributeStreamOfInt32 indices) { + m_interval_tree.sortEndIndicesHelper_(indices, begin, end); } - // Creates an iterator on the input Interval_tree. - IntervalTreeIteratorImpl(IntervalTreeImpl interval_tree) { - m_interval_tree = interval_tree; - m_tertiary_stack.reserve(20); - m_function_index = -1; + @Override + public double getValue(int e) { + return m_interval_tree.getValue_(e); } private IntervalTreeImpl m_interval_tree; - private Envelope1D m_query = new Envelope1D(); - private int m_primary_handle; - private int m_next_primary_handle; - private int m_forked_handle; - private int m_current_end_handle; - private int m_next_end_handle; - private AttributeStreamOfInt32 m_tertiary_stack = new AttributeStreamOfInt32( - 0); - private int m_function_index; - private int[] m_function_stack = new int[2]; + } - private interface State { - static final int initialize = 0; - static final int pIn = 1; - static final int pL = 2; - static final int pR = 3; - static final int pT = 4; - static final int right = 5; - static final int left = 6; - static final int all = 7; - } + IntervalTreeImpl(boolean b_offline_dynamic) { + m_b_envelopes_ref = false; + m_b_offline_dynamic = b_offline_dynamic; + m_b_constructing = false; + m_b_construction_ended = false; + } - private boolean initialize_() { - m_primary_handle = -1; - m_next_primary_handle = -1; - m_forked_handle = -1; - m_current_end_handle = -1; + void addEnvelopesRef(ArrayList envelopes) { + reset_(true, true); + m_b_envelopes_ref = true; + m_envelopes_ref = envelopes; - if (m_interval_tree.m_primary_nodes != null - && m_interval_tree.m_primary_nodes.size() > 0) { - m_function_stack[0] = State.pIn; // overwrite initialize - m_next_primary_handle = m_interval_tree.m_root; - return true; - } + m_b_constructing = false; + m_b_construction_ended = true; - m_function_index = -1; - return false; + if (!m_b_offline_dynamic) { + insertIntervalsStatic_(); + m_c_count = m_envelopes_ref.size(); } + } - private boolean pIn_() { - m_primary_handle = m_next_primary_handle; + void startConstruction() { + reset_(true, false); + } - if (m_primary_handle == -1) { - m_function_index = -1; - m_current_end_handle = -1; - return false; - } + void addInterval(Envelope1D interval) { + if (!m_b_constructing) + throw new GeometryException("invalid call"); - double discriminant = m_interval_tree - .getDiscriminant_(m_primary_handle); + m_intervals.add(interval); + } - if (m_query.vmax < discriminant) { - int secondary_handle = m_interval_tree - .getSecondaryFromPrimary(m_primary_handle); - m_next_primary_handle = m_interval_tree - .getLPTR_(m_primary_handle); + void addInterval(double min, double max) { + if (!m_b_constructing) + throw new GeometryException("invald call"); - if (secondary_handle != -1) { - m_next_end_handle = m_interval_tree - .getFirst_(secondary_handle); - m_function_stack[++m_function_index] = State.left; - } + m_intervals.add(new Envelope1D(min, max)); + } - return true; - } + void endConstruction() { + if (!m_b_constructing) + throw new GeometryException("invalid call"); - if (discriminant < m_query.vmin) { - int secondary_handle = m_interval_tree - .getSecondaryFromPrimary(m_primary_handle); - m_next_primary_handle = m_interval_tree - .getRPTR_(m_primary_handle); + m_b_constructing = false; + m_b_construction_ended = true; - if (secondary_handle != -1) { - m_next_end_handle = m_interval_tree - .getLast_(secondary_handle); - m_function_stack[++m_function_index] = State.right; - } + if (!m_b_offline_dynamic) { + insertIntervalsStatic_(); + m_c_count = m_intervals.size(); + } + } - return true; - } + /* + * Resets the Interval_tree_impl to an empty state, but maintains a handle + * on the current intervals. + */ + void reset() { + if (!m_b_offline_dynamic || !m_b_construction_ended) + throw new IllegalArgumentException("invalid call"); - assert (m_query.contains(discriminant)); + reset_(false, m_b_envelopes_ref); + } - m_function_stack[m_function_index] = State.pL; // overwrite pIn - m_forked_handle = m_primary_handle; - int secondary_handle = m_interval_tree - .getSecondaryFromPrimary(m_primary_handle); - m_next_primary_handle = m_interval_tree.getLPTR_(m_primary_handle); + /** + * Returns the number of intervals stored in the Interval_tree_impl + */ + int size() { + return m_c_count; + } - if (secondary_handle != -1) { - m_next_end_handle = m_interval_tree.getFirst_(secondary_handle); - m_function_stack[++m_function_index] = State.all; - } + /** + * Gets an iterator on the Interval_tree_impl using the input Envelope_1D + * interval as the query. To reuse the existing iterator on the same + * Interval_tree_impl but with a new query, use the reset_iterator function + * on the Interval_tree_iterator_impl. \param query The Envelope_1D interval + * used for the query. \param tolerance The tolerance used for the + * intersection tests. + */ + IntervalTreeIteratorImpl getIterator(Envelope1D query, double tolerance) { + return new IntervalTreeImpl.IntervalTreeIteratorImpl(this, query, tolerance); + } - return true; - } + /** + * Gets an iterator on the Interval_tree_impl using the input double as the + * stabbing query. To reuse the existing iterator on the same + * Interval_tree_impl but with a new query, use the reset_iterator function + * on the Interval_tree_iterator_impl. \param query The double used for the + * stabbing query. \param tolerance The tolerance used for the intersection + * tests. + */ + IntervalTreeIteratorImpl getIterator(double query, double tolerance) { + return new IntervalTreeImpl.IntervalTreeIteratorImpl(this, query, tolerance); + } - private boolean pL_() { - m_primary_handle = m_next_primary_handle; + /** + * Gets an iterator on the Interval_tree_impl. + */ + IntervalTreeIteratorImpl getIterator() { + return new IntervalTreeImpl.IntervalTreeIteratorImpl(this); + } - if (m_primary_handle == -1) { - m_function_stack[m_function_index] = State.pR; // overwrite pL - m_next_primary_handle = m_interval_tree - .getRPTR_(m_forked_handle); - return true; - } + private boolean m_b_envelopes_ref; + private boolean m_b_offline_dynamic; + private ArrayList m_intervals; + private ArrayList m_envelopes_ref; + private StridedIndexTypeCollection m_tertiary_nodes; // 5 elements for offline dynamic case, 4 elements for static case + private StridedIndexTypeCollection m_interval_nodes; // 3 elements + private AttributeStreamOfInt32 m_interval_handles; // for offline dynamic// case + private IndexMultiDCList m_secondary_lists; // for static case + private Treap m_secondary_treaps; // for off-line dynamic case + private AttributeStreamOfInt32 m_end_indices_unique; // for both offline dynamic and static cases + private int m_c_count; + private int m_root; + private boolean m_b_sort_intervals; + private boolean m_b_constructing; + private boolean m_b_construction_ended; - double discriminant = m_interval_tree - .getDiscriminant_(m_primary_handle); + /* m_tertiary_nodes + * 0: m_discriminant_index_1 + * 1: m_secondary + * 2: m_lptr + * 3: m_rptr + * 4: m_pptr + */ - if (discriminant < m_query.vmin) { - int secondary_handle = m_interval_tree - .getSecondaryFromPrimary(m_primary_handle); - m_next_primary_handle = m_interval_tree - .getRPTR_(m_primary_handle); + private void querySortedEndPointIndices_(AttributeStreamOfInt32 end_indices) { + int size = (!m_b_envelopes_ref ? m_intervals.size() : m_envelopes_ref.size()); - if (secondary_handle != -1) { - m_next_end_handle = m_interval_tree - .getLast_(secondary_handle); - m_function_stack[++m_function_index] = State.right; - } + for (int i = 0; i < 2 * size; i++) + end_indices.add(i); - return true; - } + sortEndIndices_(end_indices, 0, 2 * size); + } - assert (m_query.contains(discriminant)); + private void querySortedDuplicatesRemoved_(AttributeStreamOfInt32 end_indices_sorted) { + // remove duplicates - int secondary_handle = m_interval_tree - .getSecondaryFromPrimary(m_primary_handle); - m_next_primary_handle = m_interval_tree.getLPTR_(m_primary_handle); + double prev = NumberUtils.TheNaN; + for (int i = 0; i < end_indices_sorted.size(); i++) { + int e = end_indices_sorted.get(i); + double v = getValue_(e); - if (secondary_handle != -1) { - m_next_end_handle = m_interval_tree.getFirst_(secondary_handle); - m_function_stack[++m_function_index] = State.all; + if (v != prev) { + m_end_indices_unique.add(e); + prev = v; } + } + } - int rptr = m_interval_tree.getRPTR_(m_primary_handle); + void insert(int index) { + if (!m_b_offline_dynamic || !m_b_construction_ended) + throw new IllegalArgumentException("invalid call"); - if (rptr != -1) { - m_tertiary_stack.add(rptr); // we'll search this in the pT state - } + if (m_root == -1) { - return true; - } + int size = (!m_b_envelopes_ref ? m_intervals.size() : m_envelopes_ref.size()); - private boolean pR_() { - m_primary_handle = m_next_primary_handle; + if (m_b_sort_intervals) { + // sort + AttributeStreamOfInt32 end_point_indices_sorted = new AttributeStreamOfInt32(0); + end_point_indices_sorted.reserve(2 * size); + querySortedEndPointIndices_(end_point_indices_sorted); - if (m_primary_handle == -1) { - m_function_stack[m_function_index] = State.pT; // overwrite pR - return true; + // remove duplicates + m_end_indices_unique.resize(0); + querySortedDuplicatesRemoved_(end_point_indices_sorted); + m_interval_handles.resize(size, -1); + m_interval_handles.setRange(-1, 0, size); + m_b_sort_intervals = false; + } else { + m_interval_handles.setRange(-1, 0, size); } - double discriminant = m_interval_tree - .getDiscriminant_(m_primary_handle); + m_root = createRoot_(); + } - if (m_query.vmax < discriminant) { - int secondary_handle = m_interval_tree - .getSecondaryFromPrimary(m_primary_handle); - m_next_primary_handle = m_interval_tree - .getLPTR_(m_primary_handle); + int interval_handle = insertIntervalEnd_(index << 1, m_root); + int secondary_handle = getSecondaryFromInterval_(interval_handle); + int right_end_handle = m_secondary_treaps.addElement((index << 1) + 1, secondary_handle); + setRightEnd_(interval_handle, right_end_handle); + m_interval_handles.set(index, interval_handle); + m_c_count++; + // assert(check_validation_()); + } - if (secondary_handle != -1) { - m_next_end_handle = m_interval_tree - .getFirst_(secondary_handle); - m_function_stack[++m_function_index] = State.left; - } + private void insertIntervalsStatic_() { + int size = (!m_b_envelopes_ref ? m_intervals.size() : m_envelopes_ref.size()); - return true; - } + assert (m_b_sort_intervals); - assert (m_query.contains(discriminant)); + // sort + AttributeStreamOfInt32 end_indices_sorted = new AttributeStreamOfInt32(0); + end_indices_sorted.reserve(2 * size); + querySortedEndPointIndices_(end_indices_sorted); - int secondary_handle = m_interval_tree - .getSecondaryFromPrimary(m_primary_handle); + // remove duplicates + m_end_indices_unique.resize(0); + querySortedDuplicatesRemoved_(end_indices_sorted); - m_next_primary_handle = m_interval_tree.getRPTR_(m_primary_handle); + assert (m_tertiary_nodes.size() == 0); + m_interval_nodes.setCapacity(size); // one for each interval being inserted. each element contains a tertiary node, a left secondary node, and a right secondary node. + m_secondary_lists.reserveNodes(2 * size); // one for each end point of the original interval set (not the unique set) - if (secondary_handle != -1) { - m_next_end_handle = m_interval_tree.getFirst_(secondary_handle); - m_function_stack[++m_function_index] = State.all; - } + AttributeStreamOfInt32 interval_handles = (AttributeStreamOfInt32) AttributeStreamBase.createIndexStream(size); + interval_handles.setRange(-1, 0, size); - int lptr = m_interval_tree.getLPTR_(m_primary_handle); + m_root = createRoot_(); - if (lptr != -1) { - m_tertiary_stack.add(lptr); // we'll search this in the pT state - } + for (int i = 0; i < end_indices_sorted.size(); i++) { + int e = end_indices_sorted.get(i); + int interval_handle = interval_handles.get(e >> 1); - return true; + if (interval_handle != -1) {// insert the right end point + assert (isRight_(e)); + int secondary_handle = getSecondaryFromInterval_(interval_handle); + setRightEnd_(interval_handle, m_secondary_lists.addElement(secondary_handle, e)); + } else {// insert the left end point + assert (isLeft_(e)); + interval_handle = insertIntervalEnd_(e, m_root); + interval_handles.set(e >> 1, interval_handle); + } } - private boolean pT_() { - if (m_tertiary_stack.size() == 0) { - m_function_index = -1; - m_current_end_handle = -1; - return false; - } + assert (m_secondary_lists.getNodeCount() == 2 * size); + } - m_primary_handle = m_tertiary_stack - .get(m_tertiary_stack.size() - 1); - m_tertiary_stack.resize(m_tertiary_stack.size() - 1); + private int createRoot_() { + int discriminant_index_1 = calculateDiscriminantIndex1_(0, m_end_indices_unique.size() - 1); + return createTertiaryNode_(discriminant_index_1); + } - int secondary_handle = m_interval_tree - .getSecondaryFromPrimary(m_primary_handle); + private int insertIntervalEnd_(int end_index, int root) { + assert (isLeft_(end_index)); + int pptr = -1; + int ptr = root; + int secondary_handle = -1; + int interval_handle = -1; + int il = 0, ir = m_end_indices_unique.size() - 1, im = 0; + int index = end_index >> 1; + double discriminant_pptr = NumberUtils.NaN(); + double discriminant_ptr = NumberUtils.NaN(); + boolean bSearching = true; - if (secondary_handle != -1) { - m_next_end_handle = m_interval_tree.getFirst_(secondary_handle); - m_function_stack[++m_function_index] = State.all; - } + double min = getMin_(index); + double max = getMax_(index); - if (m_interval_tree.getLPTR_(m_primary_handle) != -1) - m_tertiary_stack - .add(m_interval_tree.getLPTR_(m_primary_handle)); + int discriminant_index_1 = -1; - if (m_interval_tree.getRPTR_(m_primary_handle) != -1) - m_tertiary_stack - .add(m_interval_tree.getRPTR_(m_primary_handle)); + while (bSearching) { + im = il + (ir - il) / 2; + assert (il != ir || min == max); + discriminant_index_1 = calculateDiscriminantIndex1_(il, ir); + double discriminant = getDiscriminantFromIndex1_(discriminant_index_1); + assert (!NumberUtils.isNaN(discriminant)); - return true; - } + if (max < discriminant) { + if (ptr != -1) { + if (discriminant_index_1 == getDiscriminantIndex1_(ptr)) { + assert (getDiscriminantFromIndex1_(discriminant_index_1) == getDiscriminant_(ptr)); - private boolean left_() { - m_current_end_handle = m_next_end_handle; + pptr = ptr; + discriminant_pptr = discriminant; + ptr = getLPTR_(ptr); - if (m_current_end_handle != -1 - && IntervalTreeImpl.isLeft_(getCurrentEndIndex_()) - && m_interval_tree.getValue_(getCurrentEndIndex_()) <= m_query.vmax) { - m_next_end_handle = getNext_(); - return false; - } + if (ptr != -1) + discriminant_ptr = getDiscriminant_(ptr); + else + discriminant_ptr = NumberUtils.NaN(); + } else if (discriminant_ptr > discriminant) { + int tertiary_handle = createTertiaryNode_(discriminant_index_1); - m_function_index--; - return true; - } + if (discriminant < discriminant_pptr) + setLPTR_(pptr, tertiary_handle); + else + setRPTR_(pptr, tertiary_handle); - private boolean right_() { - m_current_end_handle = m_next_end_handle; + setRPTR_(tertiary_handle, ptr); - if (m_current_end_handle != -1 - && IntervalTreeImpl.isRight_(getCurrentEndIndex_()) - && m_interval_tree.getValue_(getCurrentEndIndex_()) >= m_query.vmin) { - m_next_end_handle = getPrev_(); - return false; - } + if (m_b_offline_dynamic) { + setPPTR_(tertiary_handle, pptr); + setPPTR_(ptr, tertiary_handle); + } - m_function_index--; - return true; - } + pptr = tertiary_handle; + discriminant_pptr = discriminant; + ptr = -1; + discriminant_ptr = NumberUtils.NaN(); + } + } - private boolean all_() { - m_current_end_handle = m_next_end_handle; + ir = im; - if (m_current_end_handle != -1 - && IntervalTreeImpl.isLeft_(getCurrentEndIndex_())) { - m_next_end_handle = getNext_(); - return false; + continue; } - m_function_index--; - return true; - } - - private int getNext_() { - if (!m_interval_tree.m_b_offline_dynamic) - return m_interval_tree.m_secondary_lists - .getNext(m_current_end_handle); - - return m_interval_tree.m_secondary_treaps - .getNext(m_current_end_handle); - } + if (min > discriminant) { + if (ptr != -1) { + if (discriminant_index_1 == getDiscriminantIndex1_(ptr)) { + assert (getDiscriminantFromIndex1_(discriminant_index_1) == getDiscriminant_(ptr)); - private int getPrev_() { - if (!m_interval_tree.m_b_offline_dynamic) - return m_interval_tree.m_secondary_lists - .getPrev(m_current_end_handle); + pptr = ptr; + discriminant_pptr = discriminant; + ptr = getRPTR_(ptr); - return m_interval_tree.m_secondary_treaps - .getPrev(m_current_end_handle); - } + if (ptr != -1) + discriminant_ptr = getDiscriminant_(ptr); + else + discriminant_ptr = NumberUtils.NaN(); + } else if (discriminant_ptr < discriminant) { + int tertiary_handle = createTertiaryNode_(discriminant_index_1); - private int getCurrentEndIndex_() { - if (!m_interval_tree.m_b_offline_dynamic) - return m_interval_tree.m_secondary_lists - .getData(m_current_end_handle); + if (discriminant < discriminant_pptr) + setLPTR_(pptr, tertiary_handle); + else + setRPTR_(pptr, tertiary_handle); - return m_interval_tree.m_secondary_treaps - .getElement(m_current_end_handle); - } - } + setLPTR_(tertiary_handle, ptr); - IntervalTreeImpl(boolean b_offline_dynamic) { - m_b_offline_dynamic = b_offline_dynamic; - m_b_constructing = false; - m_b_construction_ended = false; - } + if (m_b_offline_dynamic) { + setPPTR_(tertiary_handle, pptr); + setPPTR_(ptr, tertiary_handle); + } - void startConstruction() { - reset_(true); - } + pptr = tertiary_handle; + discriminant_pptr = discriminant; + ptr = -1; + discriminant_ptr = NumberUtils.NaN(); + } + } - void addInterval(Envelope1D interval) { - if (!m_b_constructing) - throw new GeometryException("invalid call"); + il = im + 1; - m_intervals.add(interval); - } + continue; + } - void addInterval(double min, double max) { - if (!m_b_constructing) - throw new GeometryException("invald call"); + int tertiary_handle = -1; - m_intervals.add(new Envelope1D(min, max)); - } + if (ptr == -1 || discriminant_index_1 != getDiscriminantIndex1_(ptr)) { + tertiary_handle = createTertiaryNode_(discriminant_index_1); + } else { + tertiary_handle = ptr; + } - void endConstruction() { - if (!m_b_constructing) - throw new GeometryException("invalid call"); + secondary_handle = getSecondaryFromTertiary_(tertiary_handle); - m_b_constructing = false; - m_b_construction_ended = true; + if (secondary_handle == -1) { + secondary_handle = createSecondary_(tertiary_handle); + setSecondaryToTertiary_(tertiary_handle, secondary_handle); + } - if (!m_b_offline_dynamic) { - insertIntervalsStatic_(); - m_c_count = m_intervals.size(); - } - } + int left_end_handle = addEndIndex_(secondary_handle, end_index); + interval_handle = createIntervalNode_(); + setSecondaryToInterval_(interval_handle, secondary_handle); + setLeftEnd_(interval_handle, left_end_handle); - /** - * Inserts the interval from the given index into the Interval_tree_impl. - * This operation can only be performed in the offline dynamic case. \param - * index The index containing the interval to be inserted. - */ - void insert(int index) { - if (!m_b_offline_dynamic || !m_b_construction_ended) - throw new IllegalArgumentException("invalid call"); + if (ptr == -1 || discriminant_index_1 != getDiscriminantIndex1_(ptr)) { + assert (tertiary_handle != -1); + assert (getLPTR_(tertiary_handle) == -1 && getRPTR_(tertiary_handle) == -1 && (!m_b_offline_dynamic || getPPTR_(tertiary_handle) == -1)); - if (m_root == -1) { + if (discriminant < discriminant_pptr) + setLPTR_(pptr, tertiary_handle); + else + setRPTR_(pptr, tertiary_handle); - int size = m_intervals.size(); + if (m_b_offline_dynamic) + setPPTR_(tertiary_handle, pptr); - if (m_b_sort_intervals) { - // sort - AttributeStreamOfInt32 end_point_indices_sorted = new AttributeStreamOfInt32( - 0); - end_point_indices_sorted.reserve(2 * size); - querySortedEndPointIndices_(end_point_indices_sorted); + if (ptr != -1) { + if (discriminant_ptr < discriminant) + setLPTR_(tertiary_handle, ptr); + else + setRPTR_(tertiary_handle, ptr); - // remove duplicates - m_end_indices_unique.reserve(2 * size); - m_end_indices_unique.resize(0); - querySortedDuplicatesRemoved_(end_point_indices_sorted); - m_interval_handles.resize(size, -1); - m_interval_handles.setRange(-1, 0, size); - m_b_sort_intervals = false; - } else { - m_interval_handles.setRange(-1, 0, size); + if (m_b_offline_dynamic) + setPPTR_(ptr, tertiary_handle); + } } - m_root = createPrimaryNode_(); + bSearching = false; + break; } - int interval_handle = insertIntervalEnd_(index << 1, m_root); - int secondary_handle = getSecondaryFromInterval_(interval_handle); - int right_end_handle = m_secondary_treaps.addElement((index << 1) + 1, - secondary_handle); - setRightEnd_(interval_handle, right_end_handle); - m_interval_handles.set(index, interval_handle); - m_c_count++; - // assert(check_validation_()); + return interval_handle; } - /** - * Deletes the interval from the Interval_tree_impl. \param index The index - * containing the interval to be deleted from the Interval_tree_impl. - */ void remove(int index) { if (!m_b_offline_dynamic || !m_b_construction_ended) throw new GeometryException("invalid call"); @@ -531,8 +479,7 @@ void remove(int index) { int interval_handle = m_interval_handles.get(index); if (interval_handle == -1) - throw new IllegalArgumentException( - "the interval does not exist in the interval tree"); + throw new GeometryException("the interval does not exist in the interval tree"); m_interval_handles.set(index, -1); @@ -544,574 +491,595 @@ void remove(int index) { int size; int secondary_handle = getSecondaryFromInterval_(interval_handle); - int primary_handle; + int tertiary_handle = -1; - primary_handle = m_secondary_treaps.getTreapData(secondary_handle); - m_secondary_treaps.deleteNode(getLeftEnd_(interval_handle), - secondary_handle); - m_secondary_treaps.deleteNode(getRightEnd_(interval_handle), - secondary_handle); + tertiary_handle = m_secondary_treaps.getTreapData(secondary_handle); + m_secondary_treaps.deleteNode(getLeftEnd_(interval_handle), secondary_handle); + m_secondary_treaps.deleteNode(getRightEnd_(interval_handle), secondary_handle); size = m_secondary_treaps.size(secondary_handle); if (size == 0) { m_secondary_treaps.deleteTreap(secondary_handle); - setSecondaryToPrimary_(primary_handle, -1); + setSecondaryToTertiary_(tertiary_handle, -1); } m_interval_nodes.deleteElement(interval_handle); - int tertiary_handle = getPPTR_(primary_handle); - int lptr = getLPTR_(primary_handle); - int rptr = getRPTR_(primary_handle); + int pptr = getPPTR_(tertiary_handle); + int lptr = getLPTR_(tertiary_handle); + int rptr = getRPTR_(tertiary_handle); int iterations = 0; - while (!(size > 0 || primary_handle == m_root || (lptr != -1 && rptr != -1))) { + while (!(size > 0 || tertiary_handle == m_root || (lptr != -1 && rptr != -1))) { assert (size == 0); assert (lptr == -1 || rptr == -1); - assert (primary_handle != 0); + assert (tertiary_handle != 0); - if (primary_handle == getLPTR_(tertiary_handle)) { + if (tertiary_handle == getLPTR_(pptr)) { if (lptr != -1) { - setLPTR_(tertiary_handle, lptr); - setPPTR_(lptr, tertiary_handle); - setLPTR_(primary_handle, -1); - setPPTR_(primary_handle, -1); + setLPTR_(pptr, lptr); + setPPTR_(lptr, pptr); + setLPTR_(tertiary_handle, -1); + setPPTR_(tertiary_handle, -1); } else if (rptr != -1) { - setLPTR_(tertiary_handle, rptr); - setPPTR_(rptr, tertiary_handle); - setRPTR_(primary_handle, -1); - setPPTR_(primary_handle, -1); + setLPTR_(pptr, rptr); + setPPTR_(rptr, pptr); + setRPTR_(tertiary_handle, -1); + setPPTR_(tertiary_handle, -1); } else { - setLPTR_(tertiary_handle, -1); - setPPTR_(primary_handle, -1); + setLPTR_(pptr, -1); + setPPTR_(tertiary_handle, -1); } } else { if (lptr != -1) { - setRPTR_(tertiary_handle, lptr); - setPPTR_(lptr, tertiary_handle); - setLPTR_(primary_handle, -1); - setPPTR_(primary_handle, -1); + setRPTR_(pptr, lptr); + setPPTR_(lptr, pptr); + setLPTR_(tertiary_handle, -1); + setPPTR_(tertiary_handle, -1); } else if (rptr != -1) { - setRPTR_(tertiary_handle, rptr); - setPPTR_(rptr, tertiary_handle); - setRPTR_(primary_handle, -1); - setPPTR_(primary_handle, -1); - } else { + setRPTR_(pptr, rptr); + setPPTR_(rptr, pptr); setRPTR_(tertiary_handle, -1); - setPPTR_(primary_handle, -1); + setPPTR_(tertiary_handle, -1); + } else { + setRPTR_(pptr, -1); + setPPTR_(tertiary_handle, -1); } } + m_tertiary_nodes.deleteElement(tertiary_handle); + iterations++; - primary_handle = tertiary_handle; - secondary_handle = getSecondaryFromPrimary(primary_handle); - size = (secondary_handle != -1 ? m_secondary_treaps - .size(secondary_handle) : 0); - lptr = getLPTR_(primary_handle); - rptr = getRPTR_(primary_handle); - tertiary_handle = getPPTR_(primary_handle); + tertiary_handle = pptr; + secondary_handle = getSecondaryFromTertiary_(tertiary_handle); + size = (secondary_handle != -1 ? m_secondary_treaps.size(secondary_handle) : 0); + lptr = getLPTR_(tertiary_handle); + rptr = getRPTR_(tertiary_handle); + pptr = getPPTR_(tertiary_handle); } assert (iterations <= 2); - // assert(check_validation_()); + //assert(check_validation_()); } - /* - * Resets the Interval_tree_impl to an empty state, but maintains a handle - * on the current intervals. - */ - void reset() { - if (!m_b_offline_dynamic || !m_b_construction_ended) - throw new IllegalArgumentException("invalid call"); + private void reset_(boolean b_new_intervals, boolean b_envelopes_ref) { + if (b_new_intervals) { + m_b_envelopes_ref = false; + m_envelopes_ref = null; + + m_b_sort_intervals = true; + m_b_constructing = true; + m_b_construction_ended = false; + + if (m_end_indices_unique == null) + m_end_indices_unique = (AttributeStreamOfInt32) (AttributeStreamBase.createIndexStream(0)); + else + m_end_indices_unique.resize(0); + + if (!b_envelopes_ref) { + if (m_intervals == null) + m_intervals = new ArrayList(0); + else + m_intervals.clear(); + } else { + if (m_intervals != null) + m_intervals.clear(); + + m_b_envelopes_ref = true; + } + } else { + assert (m_b_offline_dynamic && m_b_construction_ended); + m_b_sort_intervals = false; + } + + if (m_b_offline_dynamic) { + if (m_interval_handles == null) { + m_interval_handles = (AttributeStreamOfInt32) (AttributeStreamBase.createIndexStream(0)); + m_secondary_treaps = new Treap(); + m_secondary_treaps.setComparator(new SecondaryComparator(this)); + } else { + m_secondary_treaps.clear(); + } + } else { + if (m_secondary_lists == null) + m_secondary_lists = new IndexMultiDCList(); + else + m_secondary_lists.clear(); + } + + if (m_tertiary_nodes == null) { + m_interval_nodes = new StridedIndexTypeCollection(3); + m_tertiary_nodes = new StridedIndexTypeCollection(m_b_offline_dynamic ? 5 : 4); + } else { + m_interval_nodes.deleteAll(false); + m_tertiary_nodes.deleteAll(false); + } - reset_(false); + m_root = -1; + m_c_count = 0; } - /** - * Returns the number of intervals stored in the Interval_tree_impl - */ - int size() { - return m_c_count; + private double getDiscriminant_(int tertiary_handle) { + int discriminant_index_1 = getDiscriminantIndex1_(tertiary_handle); + return getDiscriminantFromIndex1_(discriminant_index_1); } - /** - * Gets an iterator on the Interval_tree_impl using the input Envelope_1D - * interval as the query. To reuse the existing iterator on the same - * Interval_tree_impl but with a new query, use the reset_iterator function - * on the Interval_tree_iterator_impl. \param query The Envelope_1D interval - * used for the query. \param tolerance The tolerance used for the - * intersection tests. - */ - IntervalTreeIteratorImpl getIterator(Envelope1D query, double tolerance) { - return new IntervalTreeImpl.IntervalTreeIteratorImpl(this, query, - tolerance); - } + private double getDiscriminantFromIndex1_(int discriminant_index_1) { + if (discriminant_index_1 == -1) + return NumberUtils.NaN(); - /** - * Gets an iterator on the Interval_tree_impl using the input double as the - * stabbing query. To reuse the existing iterator on the same - * Interval_tree_impl but with a new query, use the reset_iterator function - * on the Interval_tree_iterator_impl. \param query The double used for the - * stabbing query. \param tolerance The tolerance used for the intersection - * tests. - */ - IntervalTreeIteratorImpl getIterator(double query, double tolerance) { - return new IntervalTreeImpl.IntervalTreeIteratorImpl(this, query, - tolerance); - } + if (discriminant_index_1 > 0) { + int j = discriminant_index_1 - 2; + int e_1 = m_end_indices_unique.get(j); + int e_2 = m_end_indices_unique.get(j + 1); - /** - * Gets an iterator on the Interval_tree_impl. - */ - IntervalTreeIteratorImpl getIterator() { - return new IntervalTreeImpl.IntervalTreeIteratorImpl(this); - } + double v_1 = getValue_(e_1); + double v_2 = getValue_(e_2); + assert (v_1 < v_2); - private static final class SecondaryComparator extends Treap.Comparator { - SecondaryComparator(IntervalTreeImpl interval_tree) { - m_interval_tree = interval_tree; + return 0.5 * (v_1 + v_2); } - @Override - public int compare(Treap treap, int e_1, int node) { - int e_2 = treap.getElement(node); - double v_1 = m_interval_tree.getValue_(e_1); - double v_2 = m_interval_tree.getValue_(e_2); + int j = -discriminant_index_1 - 2; + assert (j >= 0); + int e = m_end_indices_unique.get(j); + double v = getValue_(e); - if (v_1 < v_2) - return -1; - if (v_1 == v_2) { - if (isLeft_(e_1) && isRight_(e_2)) - return -1; - if (isLeft_(e_2) && isRight_(e_1)) - return 1; - return 0; - } - return 1; + return v; + } + + private int calculateDiscriminantIndex1_(int il, int ir) { + int discriminant_index_1; + + if (il < ir) { + int im = il + (ir - il) / 2; + discriminant_index_1 = im + 2; // positive discriminant means use average of im and im + 1 + } else { + discriminant_index_1 = -(il + 2); // negative discriminant just means use il (-(il + 2) will never be -1) } + return discriminant_index_1; + } + + static final class IntervalTreeIteratorImpl { + private IntervalTreeImpl m_interval_tree; - }; + private Envelope1D m_query = new Envelope1D(); + private int m_tertiary_handle; + private int m_next_tertiary_handle; + private int m_forked_handle; + private int m_current_end_handle; + private int m_next_end_handle; + private AttributeStreamOfInt32 m_tertiary_stack = new AttributeStreamOfInt32(0); + private int m_function_index; + private int[] m_function_stack = new int[2]; - private boolean m_b_offline_dynamic; - private ArrayList m_intervals; - private StridedIndexTypeCollection m_primary_nodes; // 8 elements for - // offline dynamic case, - // 7 elements for static - // case - private StridedIndexTypeCollection m_interval_nodes; // 3 elements - private AttributeStreamOfInt32 m_interval_handles; // for offline dynamic - // case - private IndexMultiDCList m_secondary_lists; // for static case - private Treap m_secondary_treaps; // for off-line dynamic case - private AttributeStreamOfInt32 m_end_indices_unique; // for both offline - // dynamic and - // static cases - private int m_c_count; - private int m_root; - private boolean m_b_sort_intervals; - private boolean m_b_constructing; - private boolean m_b_construction_ended; + private interface State { + static final int initialize = 0; + static final int pIn = 1; + static final int pL = 2; + static final int pR = 3; + static final int pT = 4; + static final int right = 5; + static final int left = 6; + static final int all = 7; + } - private void querySortedEndPointIndices_(AttributeStreamOfInt32 end_indices) { - int size = m_intervals.size(); + private int getNext_() { + if (!m_interval_tree.m_b_offline_dynamic) + return m_interval_tree.m_secondary_lists.getNext(m_current_end_handle); - for (int i = 0; i < 2 * size; i++) - end_indices.add(i); + return m_interval_tree.m_secondary_treaps.getNext(m_current_end_handle); + } - sortEndIndices_(end_indices, 0, 2 * size); - } + private int getPrev_() { + if (!m_interval_tree.m_b_offline_dynamic) + return m_interval_tree.m_secondary_lists.getPrev(m_current_end_handle); - private void querySortedDuplicatesRemoved_( - AttributeStreamOfInt32 end_indices_sorted) { - // remove duplicates + return m_interval_tree.m_secondary_treaps.getPrev(m_current_end_handle); + } - double prev = NumberUtils.TheNaN; - for (int i = 0; i < end_indices_sorted.size(); i++) { - int e = end_indices_sorted.get(i); - double v = getValue_(e); + private int getCurrentEndIndex_() { + if (!m_interval_tree.m_b_offline_dynamic) + return m_interval_tree.m_secondary_lists.getData(m_current_end_handle); - if (v != prev) { - m_end_indices_unique.add(e); - prev = v; - } + return m_interval_tree.m_secondary_treaps.getElement(m_current_end_handle); } - } - private void insertIntervalsStatic_() { - int size = m_intervals.size(); + int next() { + if (!m_interval_tree.m_b_construction_ended) + throw new GeometryException("invalid call"); - assert (m_b_sort_intervals); + if (m_function_index < 0) + return -1; - // sort - AttributeStreamOfInt32 end_indices_sorted = new AttributeStreamOfInt32( - 0); - end_indices_sorted.reserve(2 * size); - querySortedEndPointIndices_(end_indices_sorted); + boolean b_searching = true; - // remove duplicates - m_end_indices_unique.reserve(2 * size); - m_end_indices_unique.resize(0); - querySortedDuplicatesRemoved_(end_indices_sorted); + while (b_searching) { + switch (m_function_stack[m_function_index]) { + case State.pIn: + b_searching = pIn_(); + break; + case State.pL: + b_searching = pL_(); + break; + case State.pR: + b_searching = pR_(); + break; + case State.pT: + b_searching = pT_(); + break; + case State.right: + b_searching = right_(); + break; + case State.left: + b_searching = left_(); + break; + case State.all: + b_searching = all_(); + break; + case State.initialize: + b_searching = initialize_(); + break; + default: + throw GeometryException.GeometryInternalError(); + } + } - assert (m_primary_nodes.size() == 0); - m_interval_nodes.setCapacity(size); // one for each interval being - // inserted. each element contains a - // primary node, a left secondary - // node, and a right secondary node. - m_secondary_lists.reserveNodes(2 * size); // one for each end point of - // the original interval set - // (not the unique set) - - AttributeStreamOfInt32 interval_handles = (AttributeStreamOfInt32) AttributeStreamBase - .createIndexStream(size); - interval_handles.setRange(-1, 0, size); + if (m_current_end_handle != -1) + return getCurrentEndIndex_() >> 1; - m_root = createPrimaryNode_(); + return -1; + } - for (int i = 0; i < end_indices_sorted.size(); i++) { - int e = end_indices_sorted.get(i); - int interval_handle = interval_handles.get(e >> 1); + private boolean initialize_() { + m_tertiary_handle = -1; + m_next_tertiary_handle = -1; + m_forked_handle = -1; + m_current_end_handle = -1; - if (interval_handle != -1) {// insert the right end point - assert (isRight_(e)); - int secondary_handle = getSecondaryFromInterval_(interval_handle); - setRightEnd_(interval_handle, - m_secondary_lists.addElement(secondary_handle, e)); - } else {// insert the left end point - assert (isLeft_(e)); - interval_handle = insertIntervalEnd_(e, m_root); - interval_handles.set(e >> 1, interval_handle); + if (m_interval_tree.m_tertiary_nodes != null && m_interval_tree.m_tertiary_nodes.size() > 0) { + m_function_stack[0] = State.pIn; // overwrite initialize + m_next_tertiary_handle = m_interval_tree.m_root; + return true; } + + m_function_index = -1; + return false; } - assert (m_secondary_lists.getNodeCount() == 2 * size); - } + private boolean pIn_() { + m_tertiary_handle = m_next_tertiary_handle; - private int insertIntervalEnd_(int end_index, int root) { - assert (isLeft_(end_index)); - int primary_handle = root; - int tertiary_handle = root; - int ptr = root; - int secondary_handle; - int interval_handle = -1; - int il = 0, ir = m_end_indices_unique.size() - 1, im = 0; - int index = end_index >> 1; - double discriminant_tertiary = NumberUtils.TheNaN; - double discriminant_ptr = NumberUtils.TheNaN; - boolean bSearching = true; + if (m_tertiary_handle == -1) { + m_function_index = -1; + m_current_end_handle = -1; + return false; + } - double min = getMin_(index); - double max = getMax_(index); + double discriminant = m_interval_tree.getDiscriminant_(m_tertiary_handle); - while (bSearching) { - if (il < ir) { - im = il + (ir - il) / 2; + if (m_query.vmax < discriminant) { + int secondary_handle = m_interval_tree.getSecondaryFromTertiary_(m_tertiary_handle); + m_next_tertiary_handle = m_interval_tree.getLPTR_(m_tertiary_handle); - if (getDiscriminantIndex1_(primary_handle) == -1) - setDiscriminantIndices_(primary_handle, - m_end_indices_unique.get(im), - m_end_indices_unique.get(im + 1)); - } else { - assert (il == ir); - assert (min == max); + if (secondary_handle != -1) { + m_next_end_handle = m_interval_tree.getFirst_(secondary_handle); + m_function_stack[++m_function_index] = State.left; + } - if (getDiscriminantIndex1_(primary_handle) == -1) - setDiscriminantIndices_(primary_handle, - m_end_indices_unique.get(il), - m_end_indices_unique.get(il)); + return true; } - double discriminant = getDiscriminant_(primary_handle); - assert (!NumberUtils.isNaN(discriminant)); + if (discriminant < m_query.vmin) { + int secondary_handle = m_interval_tree.getSecondaryFromTertiary_(m_tertiary_handle); + m_next_tertiary_handle = m_interval_tree.getRPTR_(m_tertiary_handle); - if (max < discriminant) { - if (ptr != -1) { - if (ptr == primary_handle) { - tertiary_handle = primary_handle; - discriminant_tertiary = discriminant; - ptr = getLPTR_(primary_handle); + if (secondary_handle != -1) { + m_next_end_handle = m_interval_tree.getLast_(secondary_handle); + m_function_stack[++m_function_index] = State.right; + } - if (ptr != -1) - discriminant_ptr = getDiscriminant_(ptr); - else - discriminant_ptr = NumberUtils.TheNaN; - } else if (discriminant_ptr > discriminant) { - if (discriminant < discriminant_tertiary) - setLPTR_(tertiary_handle, primary_handle); - else - setRPTR_(tertiary_handle, primary_handle); + return true; + } - setRPTR_(primary_handle, ptr); + assert (m_query.contains(discriminant)); - if (m_b_offline_dynamic) { - setPPTR_(primary_handle, tertiary_handle); - setPPTR_(ptr, primary_handle); - } + m_function_stack[m_function_index] = State.pL; // overwrite pIn + m_forked_handle = m_tertiary_handle; + int secondary_handle = m_interval_tree.getSecondaryFromTertiary_(m_tertiary_handle); + m_next_tertiary_handle = m_interval_tree.getLPTR_(m_tertiary_handle); - tertiary_handle = primary_handle; - discriminant_tertiary = discriminant; - ptr = -1; - discriminant_ptr = NumberUtils.TheNaN; + if (secondary_handle != -1) { + m_next_end_handle = m_interval_tree.getFirst_(secondary_handle); + m_function_stack[++m_function_index] = State.all; + } - assert (getLPTR_(primary_handle) == -1); - assert (getRightPrimary_(primary_handle) != -1); - } - } + return true; + } + + private boolean pL_() { + m_tertiary_handle = m_next_tertiary_handle; + + if (m_tertiary_handle == -1) { + m_function_stack[m_function_index] = State.pR; // overwrite pL + m_next_tertiary_handle = m_interval_tree.getRPTR_(m_forked_handle); + return true; + } + + double discriminant = m_interval_tree.getDiscriminant_(m_tertiary_handle); - int left_handle = getLeftPrimary_(primary_handle); + if (discriminant < m_query.vmin) { + int secondary_handle = m_interval_tree.getSecondaryFromTertiary_(m_tertiary_handle); + m_next_tertiary_handle = m_interval_tree.getRPTR_(m_tertiary_handle); - if (left_handle == -1) { - left_handle = createPrimaryNode_(); - setLeftPrimary_(primary_handle, left_handle); + if (secondary_handle != -1) { + m_next_end_handle = m_interval_tree.getLast_(secondary_handle); + m_function_stack[++m_function_index] = State.right; } - primary_handle = left_handle; - ir = im; + return true; + } - continue; + assert (m_query.contains(discriminant)); + + int secondary_handle = m_interval_tree.getSecondaryFromTertiary_(m_tertiary_handle); + m_next_tertiary_handle = m_interval_tree.getLPTR_(m_tertiary_handle); + + if (secondary_handle != -1) { + m_next_end_handle = m_interval_tree.getFirst_(secondary_handle); + m_function_stack[++m_function_index] = State.all; } - if (min > discriminant) { - if (ptr != -1) { - if (ptr == primary_handle) { - tertiary_handle = primary_handle; - discriminant_tertiary = discriminant; - ptr = getRPTR_(primary_handle); + int rptr = m_interval_tree.getRPTR_(m_tertiary_handle); - if (ptr != -1) - discriminant_ptr = getDiscriminant_(ptr); - else - discriminant_ptr = NumberUtils.TheNaN; - } else if (discriminant_ptr < discriminant) { - if (discriminant < discriminant_tertiary) - setLPTR_(tertiary_handle, primary_handle); - else - setRPTR_(tertiary_handle, primary_handle); + if (rptr != -1) { + m_tertiary_stack.add(rptr); // we'll search this in the pT state + } - setLPTR_(primary_handle, ptr); + return true; + } - if (m_b_offline_dynamic) { - setPPTR_(primary_handle, tertiary_handle); - setPPTR_(ptr, primary_handle); - } + private boolean pR_() { + m_tertiary_handle = m_next_tertiary_handle; - tertiary_handle = primary_handle; - discriminant_tertiary = discriminant; - ptr = -1; - discriminant_ptr = NumberUtils.TheNaN; + if (m_tertiary_handle == -1) { + m_function_stack[m_function_index] = State.pT; // overwrite pR + return true; + } - assert (getRPTR_(primary_handle) == -1); - assert (getLeftPrimary_(primary_handle) != -1); - } - } + double discriminant = m_interval_tree.getDiscriminant_(m_tertiary_handle); - int right_handle = getRightPrimary_(primary_handle); + if (m_query.vmax < discriminant) { + int secondary_handle = m_interval_tree.getSecondaryFromTertiary_(m_tertiary_handle); + m_next_tertiary_handle = m_interval_tree.getLPTR_(m_tertiary_handle); - if (right_handle == -1) { - right_handle = createPrimaryNode_(); - setRightPrimary_(primary_handle, right_handle); + if (secondary_handle != -1) { + m_next_end_handle = m_interval_tree.getFirst_(secondary_handle); + m_function_stack[++m_function_index] = State.left; } - primary_handle = right_handle; - il = im + 1; - - continue; + return true; } - secondary_handle = getSecondaryFromPrimary(primary_handle); + assert (m_query.contains(discriminant)); - if (secondary_handle == -1) { - secondary_handle = createSecondary_(primary_handle); - setSecondaryToPrimary_(primary_handle, secondary_handle); - } + int secondary_handle = m_interval_tree.getSecondaryFromTertiary_(m_tertiary_handle); - int left_end_handle = addEndIndex(secondary_handle, end_index); - interval_handle = createIntervalNode_(); - setSecondaryToInterval_(interval_handle, secondary_handle); - setLeftEnd_(interval_handle, left_end_handle); + m_next_tertiary_handle = m_interval_tree.getRPTR_(m_tertiary_handle); - if (primary_handle != ptr) { - assert (primary_handle != -1); - assert (getLPTR_(primary_handle) == -1 - && getRPTR_(primary_handle) == -1 && (!m_b_offline_dynamic || getPPTR_(primary_handle) == -1)); + if (secondary_handle != -1) { + m_next_end_handle = m_interval_tree.getFirst_(secondary_handle); + m_function_stack[++m_function_index] = State.all; + } - if (discriminant < discriminant_tertiary) - setLPTR_(tertiary_handle, primary_handle); - else - setRPTR_(tertiary_handle, primary_handle); + int lptr = m_interval_tree.getLPTR_(m_tertiary_handle); - if (m_b_offline_dynamic) - setPPTR_(primary_handle, tertiary_handle); + if (lptr != -1) { + m_tertiary_stack.add(lptr); // we'll search this in the pT state + } - if (ptr != -1) { - if (discriminant_ptr < discriminant) - setLPTR_(primary_handle, ptr); - else - setRPTR_(primary_handle, ptr); + return true; + } - if (m_b_offline_dynamic) - setPPTR_(ptr, primary_handle); - } + private boolean pT_() { + if (m_tertiary_stack.size() == 0) { + m_function_index = -1; + m_current_end_handle = -1; + return false; } - bSearching = false; - } + m_tertiary_handle = m_tertiary_stack.get(m_tertiary_stack.size() - 1); + m_tertiary_stack.resize(m_tertiary_stack.size() - 1); - return interval_handle; - } + int secondary_handle = m_interval_tree.getSecondaryFromTertiary_(m_tertiary_handle); - private int createPrimaryNode_() { - return m_primary_nodes.newElement(); - } + if (secondary_handle != -1) { + m_next_end_handle = m_interval_tree.getFirst_(secondary_handle); + m_function_stack[++m_function_index] = State.all; + } - private int createSecondary_(int primary_handle) { - if (!m_b_offline_dynamic) - return m_secondary_lists.createList(primary_handle); + if (m_interval_tree.getLPTR_(m_tertiary_handle) != -1) + m_tertiary_stack.add(m_interval_tree.getLPTR_(m_tertiary_handle)); - return m_secondary_treaps.createTreap(primary_handle); - } + if (m_interval_tree.getRPTR_(m_tertiary_handle) != -1) + m_tertiary_stack.add(m_interval_tree.getRPTR_(m_tertiary_handle)); - private int createIntervalNode_() { - return m_interval_nodes.newElement(); - } + return true; + } - private void reset_(boolean b_new_intervals) { - if (b_new_intervals) { - m_b_sort_intervals = true; - m_b_constructing = true; - m_b_construction_ended = false; + private boolean left_() { + m_current_end_handle = m_next_end_handle; - if (m_end_indices_unique == null) - m_end_indices_unique = (AttributeStreamOfInt32) (AttributeStreamBase - .createIndexStream(0)); - else - m_end_indices_unique.resize(0); + if (m_current_end_handle != -1 && IntervalTreeImpl.isLeft_(getCurrentEndIndex_()) && m_interval_tree.getValue_(getCurrentEndIndex_()) <= m_query.vmax) { + m_next_end_handle = getNext_(); + return false; + } - if (m_intervals == null) - m_intervals = new ArrayList(0); - else - m_intervals.clear(); - } else { - assert (m_b_offline_dynamic && m_b_construction_ended); - m_b_sort_intervals = false; + m_function_index--; + return true; } - if (m_b_offline_dynamic) { - if (m_interval_handles == null) { - m_interval_handles = (AttributeStreamOfInt32) (AttributeStreamBase - .createIndexStream(0)); - m_secondary_treaps = new Treap(); - m_secondary_treaps.setComparator(new SecondaryComparator(this)); - } else { - m_secondary_treaps.clear(); + private boolean right_() { + m_current_end_handle = m_next_end_handle; + + if (m_current_end_handle != -1 && IntervalTreeImpl.isRight_(getCurrentEndIndex_()) && m_interval_tree.getValue_(getCurrentEndIndex_()) >= m_query.vmin) { + m_next_end_handle = getPrev_(); + return false; } - } else { - if (m_secondary_lists == null) - m_secondary_lists = new IndexMultiDCList(); - else - m_secondary_lists.clear(); - } - if (m_primary_nodes == null) { - m_interval_nodes = new StridedIndexTypeCollection(3); - m_primary_nodes = new StridedIndexTypeCollection( - m_b_offline_dynamic ? 8 : 7); - } else { - m_interval_nodes.deleteAll(false); - m_primary_nodes.deleteAll(false); + m_function_index--; + return true; } - m_root = -1; - m_c_count = 0; - } - - private void setDiscriminantIndices_(int primary_handle, int e_1, int e_2) { - setDiscriminantIndex1_(primary_handle, e_1); - setDiscriminantIndex2_(primary_handle, e_2); - } + private boolean all_() { + m_current_end_handle = m_next_end_handle; - private double getDiscriminant_(int primary_handle) { - int e_1 = getDiscriminantIndex1_(primary_handle); - if (e_1 == -1) - return NumberUtils.TheNaN; + if (m_current_end_handle != -1 && IntervalTreeImpl.isLeft_(getCurrentEndIndex_())) { + m_next_end_handle = getNext_(); + return false; + } - int e_2 = getDiscriminantIndex2_(primary_handle); - assert (e_2 != -1); + m_function_index--; + return true; + } - double v_1 = getValue_(e_1); - double v_2 = getValue_(e_2); + IntervalTreeIteratorImpl(IntervalTreeImpl interval_tree, Envelope1D query, double tolerance) { + m_interval_tree = interval_tree; + m_tertiary_stack.reserve(20); + resetIterator(query, tolerance); + } - if (v_1 == v_2) - return v_1; + IntervalTreeIteratorImpl(IntervalTreeImpl interval_tree, double query, double tolerance) { + m_interval_tree = interval_tree; + m_tertiary_stack.reserve(20); + resetIterator(query, tolerance); + } - return 0.5 * (v_1 + v_2); - } + IntervalTreeIteratorImpl(IntervalTreeImpl interval_tree) { + m_interval_tree = interval_tree; + m_tertiary_stack.reserve(20); + m_function_index = -1; + } - private boolean isActive_(int primary_handle) { - int secondary_handle = getSecondaryFromPrimary(primary_handle); + void resetIterator(Envelope1D query, double tolerance) { + m_query.vmin = query.vmin - tolerance; + m_query.vmax = query.vmax + tolerance; + m_tertiary_stack.resize(0); + m_function_index = 0; + m_function_stack[0] = State.initialize; + } - if (secondary_handle != -1) - return true; + void resetIterator(double query_min, double query_max, double tolerance) { + m_query.vmin = query_min - tolerance; + m_query.vmax = query_max + tolerance; + m_tertiary_stack.resize(0); + m_function_index = 0; + m_function_stack[0] = State.initialize; + } - int left_handle = getLeftPrimary_(primary_handle); + void resetIterator(double query, double tolerance) { + m_query.vmin = query - tolerance; + m_query.vmax = query + tolerance; + m_tertiary_stack.resize(0); + m_function_index = 0; + m_function_stack[0] = State.initialize; + } + } - if (left_handle == -1) - return false; + private static final class SecondaryComparator extends Treap.Comparator { + SecondaryComparator(IntervalTreeImpl interval_tree) { + m_interval_tree = interval_tree; + } - int right_handle = getRightPrimary_(primary_handle); + @Override + public int compare(Treap treap, int e_1, int node) { + int e_2 = treap.getElement(node); + double v_1 = m_interval_tree.getValue_(e_1); + double v_2 = m_interval_tree.getValue_(e_2); - if (right_handle == -1) - return false; + if (v_1 < v_2) + return -1; + if (v_1 == v_2) { + if (isLeft_(e_1) && isRight_(e_2)) + return -1; + if (isLeft_(e_2) && isRight_(e_1)) + return 1; + return 0; + } + return 1; + } - return true; + private IntervalTreeImpl m_interval_tree; } - private void setDiscriminantIndex1_(int primary_handle, int end_index) { - m_primary_nodes.setField(primary_handle, 0, end_index); + private int createTertiaryNode_(int discriminant_index_1) { + int tertiary_handle = m_tertiary_nodes.newElement(); + setDiscriminantIndex1_(tertiary_handle, discriminant_index_1); + return tertiary_handle; } - private void setDiscriminantIndex2_(int primary_handle, int end_index) { - m_primary_nodes.setField(primary_handle, 1, end_index); + private int createSecondary_(int tertiary_handle) { + if (!m_b_offline_dynamic) + return m_secondary_lists.createList(tertiary_handle); + + return m_secondary_treaps.createTreap(tertiary_handle); } - private void setLeftPrimary_(int primary_handle, int left_handle) { - m_primary_nodes.setField(primary_handle, 3, left_handle); + private int createIntervalNode_() { + return m_interval_nodes.newElement(); } - private void setRightPrimary_(int primary_handle, int right_handle) { - m_primary_nodes.setField(primary_handle, 4, right_handle); + private void setDiscriminantIndex1_(int tertiary_handle, int end_index) { + m_tertiary_nodes.setField(tertiary_handle, 0, end_index); } - private void setSecondaryToPrimary_(int primary_handle, int secondary_handle) { - m_primary_nodes.setField(primary_handle, 2, secondary_handle); + private void setSecondaryToTertiary_(int tertiary_handle, int secondary_handle) { + m_tertiary_nodes.setField(tertiary_handle, 1, secondary_handle); } - private void setLPTR_(int primary_handle, int lptr) { - m_primary_nodes.setField(primary_handle, 5, lptr); + private void setLPTR_(int tertiary_handle, int lptr) { + m_tertiary_nodes.setField(tertiary_handle, 2, lptr); } - private void setRPTR_(int primary_handle, int rptr) { - m_primary_nodes.setField(primary_handle, 6, rptr); + private void setRPTR_(int tertiary_handle, int rptr) { + m_tertiary_nodes.setField(tertiary_handle, 3, rptr); } - private void setPPTR_(int primary_handle, int pptr) { - m_primary_nodes.setField(primary_handle, 7, pptr); + private void setPPTR_(int tertiary_handle, int pptr) { + m_tertiary_nodes.setField(tertiary_handle, 4, pptr); } - private void setSecondaryToInterval_(int interval_handle, - int secondary_handle) { + private void setSecondaryToInterval_(int interval_handle, int secondary_handle) { m_interval_nodes.setField(interval_handle, 0, secondary_handle); } - private int addEndIndex(int secondary_handle, int end_index) { + private int addEndIndex_(int secondary_handle, int end_index) { int end_index_handle; if (!m_b_offline_dynamic) - end_index_handle = m_secondary_lists.addElement(secondary_handle, - end_index); + end_index_handle = m_secondary_lists.addElement(secondary_handle, end_index); else - end_index_handle = m_secondary_treaps.addElement(end_index, - secondary_handle); + end_index_handle = m_secondary_treaps.addElement(end_index, secondary_handle); return end_index_handle; } @@ -1124,58 +1092,24 @@ private void setRightEnd_(int interval_handle, int right_end_handle) { m_interval_nodes.setField(interval_handle, 2, right_end_handle); } - private int getFirst_(int secondary_handle) { - if (!m_b_offline_dynamic) - return m_secondary_lists.getFirst(secondary_handle); - - return m_secondary_treaps.getFirst(secondary_handle); - } - - private int getLast_(int secondary_handle) { - if (!m_b_offline_dynamic) - return m_secondary_lists.getLast(secondary_handle); - - return m_secondary_treaps.getLast(secondary_handle); - } - - private static boolean isLeft_(int end_index) { - return (end_index & 0x1) == 0; - } - - private static boolean isRight_(int end_index) { - return (end_index & 0x1) == 1; - } - - private int getDiscriminantIndex1_(int primary_handle) { - return m_primary_nodes.getField(primary_handle, 0); - } - - private int getDiscriminantIndex2_(int primary_handle) { - return m_primary_nodes.getField(primary_handle, 1); - } - - private int getSecondaryFromPrimary(int primary_handle) { - return m_primary_nodes.getField(primary_handle, 2); - } - - private int getLeftPrimary_(int primary_handle) { - return m_primary_nodes.getField(primary_handle, 3); + private int getDiscriminantIndex1_(int tertiary_handle) { + return m_tertiary_nodes.getField(tertiary_handle, 0); } - private int getRightPrimary_(int primary_handle) { - return m_primary_nodes.getField(primary_handle, 4); + private int getSecondaryFromTertiary_(int tertiary_handle) { + return m_tertiary_nodes.getField(tertiary_handle, 1); } - private int getLPTR_(int primary_handle) { - return m_primary_nodes.getField(primary_handle, 5); + private int getLPTR_(int tertiary_handle) { + return m_tertiary_nodes.getField(tertiary_handle, 2); } - private int getRPTR_(int primary_handle) { - return m_primary_nodes.getField(primary_handle, 6); + private int getRPTR_(int tertiary_handle) { + return m_tertiary_nodes.getField(tertiary_handle, 3); } - private int getPPTR_(int primary_handle) { - return m_primary_nodes.getField(primary_handle, 7); + private int getPPTR_(int tertiary_handle) { + return m_tertiary_nodes.getField(tertiary_handle, 4); } private int getSecondaryFromInterval_(int interval_handle) { @@ -1191,76 +1125,32 @@ private int getRightEnd_(int interval_handle) { } private double getMin_(int i) { - Envelope1D interval = m_intervals.get(i); - return interval.vmin; + return (!m_b_envelopes_ref ? m_intervals.get(i).vmin : m_envelopes_ref.get(i).xmin); } private double getMax_(int i) { - Envelope1D interval = m_intervals.get(i); - return interval.vmax; + return (!m_b_envelopes_ref ? m_intervals.get(i).vmax : m_envelopes_ref.get(i).xmax); } - // *********** Helpers for Bucket sort************** - private BucketSort m_bucket_sort; - - private void sortEndIndices_(AttributeStreamOfInt32 end_indices, - int begin_, int end_) { - if (m_bucket_sort == null) - m_bucket_sort = new BucketSort(); + private int getFirst_(int secondary_handle) { + if (!m_b_offline_dynamic) + return m_secondary_lists.getFirst(secondary_handle); - IntervalTreeBucketSortHelper sorter = new IntervalTreeBucketSortHelper( - this); - m_bucket_sort.sort(end_indices, begin_, end_, sorter); + return m_secondary_treaps.getFirst(secondary_handle); } - private void sortEndIndicesHelper_(AttributeStreamOfInt32 end_indices, - int begin_, int end_) { - end_indices.Sort(begin_, end_, new EndPointsComparer(this)); - } + private int getLast_(int secondary_handle) { + if (!m_b_offline_dynamic) + return m_secondary_lists.getLast(secondary_handle); - private double getValue_(int e) { - Envelope1D interval = m_intervals.get(e >> 1); - double v = (isLeft_(e) ? interval.vmin : interval.vmax); - return v; + return m_secondary_treaps.getLast(secondary_handle); } - private static final class EndPointsComparer extends - AttributeStreamOfInt32.IntComparator { // For user sort - EndPointsComparer(IntervalTreeImpl interval_tree) { - m_interval_tree = interval_tree; - } - - @Override - public int compare(int e_1, int e_2) { - double v_1 = m_interval_tree.getValue_(e_1); - double v_2 = m_interval_tree.getValue_(e_2); - - if (v_1 < v_2 || (v_1 == v_2 && isLeft_(e_1) && isRight_(e_2))) - return -1; - - return 1; - } - - private IntervalTreeImpl m_interval_tree; + private static boolean isLeft_(int end_index) { + return (end_index & 0x1) == 0; } - private class IntervalTreeBucketSortHelper extends ClassicSort { // For - // bucket - // sort - IntervalTreeBucketSortHelper(IntervalTreeImpl interval_tree) { - m_interval_tree = interval_tree; - } - - @Override - public void userSort(int begin, int end, AttributeStreamOfInt32 indices) { - m_interval_tree.sortEndIndicesHelper_(indices, begin, end); - } - - @Override - public double getValue(int e) { - return m_interval_tree.getValue_(e); - } - - private IntervalTreeImpl m_interval_tree; + private static boolean isRight_(int end_index) { + return (end_index & 0x1) == 1; } } diff --git a/src/main/java/com/esri/core/geometry/JsonGeometryException.java b/src/main/java/com/esri/core/geometry/JsonGeometryException.java new file mode 100644 index 00000000..a5552901 --- /dev/null +++ b/src/main/java/com/esri/core/geometry/JsonGeometryException.java @@ -0,0 +1,40 @@ +/* + Copyright 1995-2015 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ +package com.esri.core.geometry; + +/** + * A runtime exception raised when a JSON related exception occurs. + */ +public class JsonGeometryException extends GeometryException { + + /** + * Constructs a Json Geometry Exception with the given error string/message. + * + * @param str + * - The error string. + */ + public JsonGeometryException(String str) { + super(str); + } +} diff --git a/src/main/java/com/esri/core/geometry/JsonParserReader.java b/src/main/java/com/esri/core/geometry/JsonParserReader.java index 80666579..bf382dd9 100644 --- a/src/main/java/com/esri/core/geometry/JsonParserReader.java +++ b/src/main/java/com/esri/core/geometry/JsonParserReader.java @@ -24,10 +24,15 @@ package com.esri.core.geometry; import java.util.ArrayList; + import org.codehaus.jackson.JsonParser; import org.codehaus.jackson.JsonToken; import org.json.JSONArray; import org.json.JSONObject; +import org.json.JSONException; +import org.codehaus.jackson.JsonParseException; + +import java.io.IOException; final class JsonParserReader extends JsonReader { @@ -38,33 +43,33 @@ final class JsonParserReader extends JsonReader { } @Override - JsonToken nextToken() throws Exception { + JsonToken nextToken() throws JSONException, JsonParseException, IOException { JsonToken token = m_jsonParser.nextToken(); return token; } @Override - JsonToken currentToken() throws Exception { + JsonToken currentToken() throws JSONException, JsonParseException, IOException { return m_jsonParser.getCurrentToken(); } @Override - void skipChildren() throws Exception { + void skipChildren() throws JSONException, JsonParseException, IOException { m_jsonParser.skipChildren(); } @Override - String currentString() throws Exception { + String currentString() throws JSONException, JsonParseException, IOException { return m_jsonParser.getText(); } @Override - double currentDoubleValue() throws Exception { + double currentDoubleValue() throws JSONException, JsonParseException, IOException { return m_jsonParser.getValueAsDouble(); } @Override - int currentIntValue() throws Exception { + int currentIntValue() throws JSONException, JsonParseException, IOException { return m_jsonParser.getValueAsInt(); } } diff --git a/src/main/java/com/esri/core/geometry/JsonReader.java b/src/main/java/com/esri/core/geometry/JsonReader.java index ba3b8cca..b1f69d95 100644 --- a/src/main/java/com/esri/core/geometry/JsonReader.java +++ b/src/main/java/com/esri/core/geometry/JsonReader.java @@ -24,24 +24,28 @@ package com.esri.core.geometry; import java.util.ArrayList; + import org.codehaus.jackson.JsonParser; import org.codehaus.jackson.JsonToken; import org.json.JSONArray; import org.json.JSONObject; +import org.json.JSONException; +import org.codehaus.jackson.JsonParseException; +import java.io.IOException; abstract class JsonReader { - abstract JsonToken nextToken() throws Exception; + abstract JsonToken nextToken() throws JSONException, JsonParseException, IOException; - abstract JsonToken currentToken() throws Exception; + abstract JsonToken currentToken() throws JSONException, JsonParseException, IOException; - abstract void skipChildren() throws Exception; + abstract void skipChildren() throws JSONException, JsonParseException, IOException; - abstract String currentString() throws Exception; + abstract String currentString() throws JSONException, JsonParseException, IOException; - abstract double currentDoubleValue() throws Exception; + abstract double currentDoubleValue() throws JSONException, JsonParseException, IOException; - abstract int currentIntValue() throws Exception; + abstract int currentIntValue() throws JSONException, JsonParseException, IOException; } diff --git a/src/main/java/com/esri/core/geometry/JsonStringWriter.java b/src/main/java/com/esri/core/geometry/JsonStringWriter.java index f324a197..a8df0930 100644 --- a/src/main/java/com/esri/core/geometry/JsonStringWriter.java +++ b/src/main/java/com/esri/core/geometry/JsonStringWriter.java @@ -33,14 +33,14 @@ Object getJson() { @Override void startObject() { - next_(Action.addContainer); + next_(Action.addObject); m_jsonString.append('{'); m_functionStack.add(State.objectStart); } @Override void startArray() { - next_(Action.addContainer); + next_(Action.addArray); m_jsonString.append('['); m_functionStack.add(State.arrayStart); } @@ -57,6 +57,12 @@ void endArray() { m_jsonString.append(']'); } + @Override + void addFieldName(String fieldName) { + next_(Action.addKey); + appendQuote_(fieldName); + } + @Override void addPairObject(String fieldName) { next_(Action.addPair); @@ -90,11 +96,11 @@ void addPairDouble(String fieldName, double v) { } @Override - void addPairDoubleF(String fieldName, double v, int decimals) { + void addPairDouble(String fieldName, double v, int precision, boolean bFixedPoint) { next_(Action.addPair); appendQuote_(fieldName); m_jsonString.append(":"); - addValueDoubleF_(v, decimals); + addValueDouble_(v, precision, bFixedPoint); } @Override @@ -123,13 +129,13 @@ void addPairNull(String fieldName) { @Override void addValueObject() { - next_(Action.addContainer); + next_(Action.addObject); addValueObject_(); } @Override void addValueArray() { - next_(Action.addContainer); + next_(Action.addArray); addValueArray_(); } @@ -146,9 +152,9 @@ void addValueDouble(double v) { } @Override - void addValueDoubleF(double v, int decimals) { + void addValueDouble(double v, int precision, boolean bFixedPoint) { next_(Action.addTerminal); - addValueDoubleF_(v, decimals); + addValueDouble_(v, precision, bFixedPoint); } @Override @@ -202,13 +208,16 @@ private void addValueDouble_(double v) { StringUtils.appendDouble(v, 17, m_jsonString); } - private void addValueDoubleF_(double v, int decimals) { + private void addValueDouble_(double v, int precision, boolean bFixedPoint) { if (NumberUtils.isNaN(v)) { addValueNull_(); return; } - StringUtils.appendDoubleF(v, decimals, m_jsonString); + if (bFixedPoint) + StringUtils.appendDoubleF(v, precision, m_jsonString); + else + StringUtils.appendDouble(v, precision, m_jsonString); } private void addValueInt_(int v) { @@ -247,6 +256,9 @@ private void next_(int action) { case State.elementEnd: elementEnd_(action); break; + case State.fieldNameEnd: + fieldNameEnd_(action); + break; default: throw new GeometryException("internal error"); } @@ -259,7 +271,7 @@ private void accept_(int action) { } private void start_(int action) { - if (action == Action.addContainer) { + if ((action & Action.addContainer) != 0) { m_functionStack.removeLast(); } else { throw new GeometryException("invalid call"); @@ -267,18 +279,25 @@ private void start_(int action) { } private void objectStart_(int action) { + if (action != Action.popObject && action != Action.addPair && action != Action.addKey) + throw new GeometryException("invalid call"); + m_functionStack.removeLast(); if (action == Action.addPair) { m_functionStack.add(State.pairEnd); - } else if (action != Action.popObject) { - throw new GeometryException("invalid call"); + } else if (action == Action.addKey) { + m_functionStack.add(State.pairEnd); + m_functionStack.add(State.fieldNameEnd); } } private void pairEnd_(int action) { if (action == Action.addPair) { m_jsonString.append(','); + } else if (action == Action.addKey) { + m_jsonString.append(','); + m_functionStack.add(State.fieldNameEnd); } else if (action == Action.popObject) { m_functionStack.removeLast(); } else { @@ -287,12 +306,13 @@ private void pairEnd_(int action) { } private void arrayStart_(int action) { + if ((action & Action.addValue) == 0 && action != Action.popArray) + throw new GeometryException("invalid call"); + m_functionStack.removeLast(); if ((action & Action.addValue) != 0) { m_functionStack.add(State.elementEnd); - } else if (action != Action.popArray) { - throw new GeometryException("invalid call"); } } @@ -306,6 +326,14 @@ private void elementEnd_(int action) { } } + private void fieldNameEnd_(int action) { + if ((action & Action.addValue) == 0) + throw new GeometryException("invalid call"); + + m_functionStack.removeLast(); + m_jsonString.append(':'); + } + private void appendQuote_(String string) { int count = 0; int start = 0; diff --git a/src/main/java/com/esri/core/geometry/JsonValueReader.java b/src/main/java/com/esri/core/geometry/JsonValueReader.java index d7c4b639..91028624 100644 --- a/src/main/java/com/esri/core/geometry/JsonValueReader.java +++ b/src/main/java/com/esri/core/geometry/JsonValueReader.java @@ -24,10 +24,15 @@ package com.esri.core.geometry; import java.util.ArrayList; + import org.codehaus.jackson.JsonParser; import org.codehaus.jackson.JsonToken; import org.json.JSONArray; import org.json.JSONObject; +import org.json.JSONException; +import org.codehaus.jackson.JsonParseException; + +import java.io.IOException; final class JsonValueReader extends JsonReader { @@ -108,7 +113,7 @@ Object currentObject_() { } @Override - JsonToken nextToken() throws Exception { + JsonToken nextToken() throws JSONException, JsonParseException { if (m_parentStack.isEmpty()) { m_currentToken = JsonToken.NOT_AVAILABLE; return m_currentToken; @@ -170,12 +175,12 @@ JsonToken nextToken() throws Exception { } @Override - JsonToken currentToken() throws Exception { + JsonToken currentToken() throws JSONException, JsonParseException, IOException { return m_currentToken; } @Override - void skipChildren() throws Exception { + void skipChildren() throws JSONException, JsonParseException, IOException { assert (!m_parentStack.isEmpty()); if (m_currentToken != JsonToken.START_OBJECT && m_currentToken != JsonToken.START_ARRAY) { @@ -196,7 +201,7 @@ void skipChildren() throws Exception { } @Override - String currentString() throws Exception { + String currentString() throws JSONException, JsonParseException, IOException { if (m_currentToken == JsonToken.FIELD_NAME) { return m_objIters.get(m_objIters.size() - 1).getCurrentKey(); } @@ -209,7 +214,7 @@ String currentString() throws Exception { } @Override - double currentDoubleValue() throws Exception { + double currentDoubleValue() throws JSONException, JsonParseException, IOException { if (m_currentToken != JsonToken.VALUE_NUMBER_FLOAT && m_currentToken != JsonToken.VALUE_NUMBER_INT) { throw new GeometryException("invalid call"); } @@ -218,7 +223,7 @@ String currentString() throws Exception { } @Override - int currentIntValue() throws Exception { + int currentIntValue() throws JSONException, JsonParseException, IOException { if (m_currentToken != JsonToken.VALUE_NUMBER_INT) { throw new GeometryException("invalid call"); } diff --git a/src/main/java/com/esri/core/geometry/JsonWriter.java b/src/main/java/com/esri/core/geometry/JsonWriter.java index 0baa0f2b..095f50e1 100644 --- a/src/main/java/com/esri/core/geometry/JsonWriter.java +++ b/src/main/java/com/esri/core/geometry/JsonWriter.java @@ -35,6 +35,8 @@ abstract class JsonWriter { abstract void endArray(); + abstract void addFieldName(String fieldName); + abstract void addPairObject(String fieldName); abstract void addPairArray(String fieldName); @@ -43,7 +45,7 @@ abstract class JsonWriter { abstract void addPairDouble(String fieldName, double v); - abstract void addPairDoubleF(String fieldName, double v, int decimals); + abstract void addPairDouble(String fieldName, double v, int precision, boolean bFixedPoint); abstract void addPairInt(String fieldName, int v); @@ -59,7 +61,7 @@ abstract class JsonWriter { abstract void addValueDouble(double v); - abstract void addValueDoubleF(double v, int decimals); + abstract void addValueDouble(double v, int precision, boolean bFixedPoint); abstract void addValueInt(int v); @@ -70,11 +72,14 @@ abstract class JsonWriter { protected interface Action { static final int accept = 0; - static final int addContainer = 1; + static final int addObject = 1; + static final int addArray = 2; static final int popObject = 4; static final int popArray = 8; - static final int addPair = 16; + static final int addKey = 16; static final int addTerminal = 32; + static final int addPair = 64; + static final int addContainer = addObject | addArray; static final int addValue = addContainer | addTerminal; } @@ -85,6 +90,7 @@ protected interface State { static final int objectStart = 2; static final int arrayStart = 3; static final int pairEnd = 4; - static final int elementEnd = 6; + static final int elementEnd = 5; + static final int fieldNameEnd = 6; } } diff --git a/src/main/java/com/esri/core/geometry/Line.java b/src/main/java/com/esri/core/geometry/Line.java index a3c5e570..4eccd513 100644 --- a/src/main/java/com/esri/core/geometry/Line.java +++ b/src/main/java/com/esri/core/geometry/Line.java @@ -35,10 +35,6 @@ */ public final class Line extends Segment implements Serializable { - private static final long serialVersionUID = 2L;// TODO:remove as we use - // writeReplace and - // GeometrySerializer - @Override public Geometry.Type getType() { return Type.Line; @@ -92,7 +88,7 @@ public Line() { m_description = vd; } - Line(double x1, double y1, double x2, double y2) { + public Line(double x1, double y1, double x2, double y2) { m_description = VertexDescriptionDesignerImpl.getDefaultDescriptor2D(); setStartXY(x1, y1); setEndXY(x2, y2); @@ -199,7 +195,7 @@ public Geometry createInstance() { } @Override - void getCoord2D(double t, Point2D pt) { + public void getCoord2D(double t, Point2D pt) { // We want: // 1. When t == 0, get exactly Start // 2. When t == 1, get exactly End @@ -209,7 +205,7 @@ void getCoord2D(double t, Point2D pt) { } @Override - Segment cut(double t1, double t2) { + public Segment cut(double t1, double t2) { SegmentBuffer segmentBuffer = new SegmentBuffer(); cut(t1, t2, segmentBuffer); return segmentBuffer.get(); @@ -270,7 +266,7 @@ public double getAttributeAsDbl(double t, int semantics, int ordinate) { } @Override - double getClosestCoordinate(Point2D inputPt, boolean bExtrapolate) { + public double getClosestCoordinate(Point2D inputPt, boolean bExtrapolate) { double vx = m_xEnd - m_xStart; double vy = m_yEnd - m_yStart; double v2 = vx * vx + vy * vy; @@ -390,7 +386,7 @@ boolean _isIntersectingPoint(Point2D pt, double tolerance, * given tolerance. */ @Override - boolean isIntersecting(Point2D pt, double tolerance) { + public boolean isIntersecting(Point2D pt, double tolerance) { return _isIntersectingPoint(pt, tolerance, false); } diff --git a/src/main/java/com/esri/core/geometry/LnSrlzr.java b/src/main/java/com/esri/core/geometry/LnSrlzr.java new file mode 100644 index 00000000..b1bd001f --- /dev/null +++ b/src/main/java/com/esri/core/geometry/LnSrlzr.java @@ -0,0 +1,93 @@ +/* + Copyright 1995-2015 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ +package com.esri.core.geometry; + +import java.io.InvalidObjectException; +import java.io.ObjectStreamException; +import java.io.Serializable; + +//This is a writeReplace class for Lin +public class LnSrlzr implements Serializable { + private static final long serialVersionUID = 1L; + double[] attribs; + int descriptionBitMask; + + public Object readResolve() throws ObjectStreamException { + Line ln = null; + if (descriptionBitMask == -1) + return null; + + try { + VertexDescription vd = VertexDescriptionDesignerImpl + .getVertexDescription(descriptionBitMask); + ln = new Line(vd); + if (attribs != null) { + ln.setStartXY(attribs[0], attribs[1]); + ln.setEndXY(attribs[2], attribs[3]); + int index = 4; + for (int i = 1, n = vd.getAttributeCount(); i < n; i++) { + int semantics = vd.getSemantics(i); + int comps = VertexDescription.getComponentCount(semantics); + for (int ord = 0; ord < comps; ord++) { + ln.setStartAttribute(semantics, ord, attribs[index++]); + ln.setEndAttribute(semantics, ord, attribs[index++]); + } + } + } + } catch (Exception ex) { + throw new InvalidObjectException("Cannot read geometry from stream"); + } + + return ln; + } + + public void setGeometryByValue(Line ln) throws ObjectStreamException { + try { + attribs = null; + if (ln == null) { + descriptionBitMask = -1; + } + + VertexDescription vd = ln.getDescription(); + descriptionBitMask = vd.m_semanticsBitArray; + + attribs = new double[vd.getTotalComponentCount() * 2]; + attribs[0] = ln.getStartX(); + attribs[1] = ln.getStartY(); + attribs[2] = ln.getEndX(); + attribs[3] = ln.getEndY(); + int index = 4; + for (int i = 1, n = vd.getAttributeCount(); i < n; i++) { + int semantics = vd.getSemantics(i); + int comps = VertexDescription.getComponentCount(semantics); + for (int ord = 0; ord < comps; ord++) { + attribs[index++] = ln.getStartAttributeAsDbl(semantics, ord); + attribs[index++] = ln.getEndAttributeAsDbl(semantics, ord); + } + } + } catch (Exception ex) { + throw new InvalidObjectException("Cannot serialize this geometry"); + } + } +} diff --git a/src/main/java/com/esri/core/geometry/MathUtils.java b/src/main/java/com/esri/core/geometry/MathUtils.java index 208fe4ba..92d5d8dd 100644 --- a/src/main/java/com/esri/core/geometry/MathUtils.java +++ b/src/main/java/com/esri/core/geometry/MathUtils.java @@ -135,11 +135,21 @@ static int sign(double value) { return value < 0 ? -1 : (value > 0) ? 1 : 0; } + /** + * Rounds towards zero. + */ + static double truncate(double v) { + if (v >= 0) + return Math.floor(v); + else + return -Math.floor(-v); + } + /** * C fmod function. */ static double FMod(double x, double y) { - return x - Math.floor(x / y) * y; + return x - truncate(x / y) * y; } @@ -184,22 +194,26 @@ static double lerp(double start_, double end_, double t) { *It also guarantees that for 0 <= t <= 1, the interpolated value v is between start and end. */ static void lerp(Point2D start_, Point2D end_, double t, Point2D result) { + assert(start_ != result); // When end == start, we want result to be equal to start, for all t // values. At the same time, when end != start, we want the result to be // equal to start for t==0 and end for t == 1.0 // The regular formula end_ * t + (1.0 - t) * start_, when end_ == // start_, and t at 1/3, produces value different from start + double rx, ry; if (t <= 0.5) { - result.x = start_.x + (end_.x - start_.x) * t; - result.y = start_.y + (end_.y - start_.y) * t; + rx = start_.x + (end_.x - start_.x) * t; + ry = start_.y + (end_.y - start_.y) * t; } else { - result.x = end_.x - (end_.x - start_.x) * (1.0 - t); - result.y = end_.y - (end_.y - start_.y) * (1.0 - t); + rx = end_.x - (end_.x - start_.x) * (1.0 - t); + ry = end_.y - (end_.y - start_.y) * (1.0 - t); } - assert (t < 0 || t > 1.0 || (result.x >= start_.x && result.x <= end_.x) || (result.x <= start_.x && result.x >= end_.x)); - assert (t < 0 || t > 1.0 || (result.y >= start_.y && result.y <= end_.y) || (result.y <= start_.y && result.y >= end_.y)); + assert (t < 0 || t > 1.0 || (rx >= start_.x && rx <= end_.x) || (rx <= start_.x && rx >= end_.x)); + assert (t < 0 || t > 1.0 || (ry >= start_.y && ry <= end_.y) || (ry <= start_.y && ry >= end_.y)); + result.x = rx; + result.y = ry; } static void lerp(double start_x, double start_y, double end_x, double end_y, double t, Point2D result) { diff --git a/src/main/java/com/esri/core/geometry/MultiPath.java b/src/main/java/com/esri/core/geometry/MultiPath.java index 8e778725..48cf8bd9 100644 --- a/src/main/java/com/esri/core/geometry/MultiPath.java +++ b/src/main/java/com/esri/core/geometry/MultiPath.java @@ -39,12 +39,12 @@ public VertexDescription getDescription() { } @Override - void assignVertexDescription(VertexDescription src) { + public void assignVertexDescription(VertexDescription src) { m_impl.assignVertexDescription(src); } @Override - void mergeVertexDescription(VertexDescription src) { + public void mergeVertexDescription(VertexDescription src) { m_impl.mergeVertexDescription(src); } @@ -277,6 +277,41 @@ void addPath(Point2D[] points, int count, boolean bForward) { m_impl.addPath(points, count, bForward); } + /** + * Adds segments from a source multipath to this MultiPath. + * + * @param src + * The source MultiPath to add segments from. + * @param srcPathIndex + * The index of the path in the the source MultiPath. + * @param srcSegmentFrom + * The index of first segment in the path to start adding from. + * The value has to be between 0 and + * src.getSegmentCount(srcPathIndex) - 1. + * @param srcSegmentCount + * The number of segments to add. If 0, the function does + * nothing. + * @param bStartNewPath + * When true, a new path is added and segments are added to it. + * Otherwise the segments are added to the last path of this + * MultiPath. + * + * If bStartNewPath false, the first point of the first source + * segment is not added. This is done to ensure proper connection + * to existing segments. When the source path is closed, and the + * closing segment is among those to be added, it is added also + * as a closing segment, not as a real segment. Use add_segment + * instead if you do not like that behavior. + * + * This MultiPath obtains all missing attributes from the src + * MultiPath. + */ + public void addSegmentsFromPath(MultiPath src, int srcPathIndex, + int srcSegmentFrom, int srcSegmentCount, boolean bStartNewPath) { + m_impl.addSegmentsFromPath((MultiPathImpl) src._getImpl(), + srcPathIndex, srcSegmentFrom, srcSegmentCount, bStartNewPath); + } + /** * Adds a new segment to this multipath. * @@ -724,12 +759,12 @@ public int hashCode() { } @Override - void getPointByVal(int index, Point outPoint) { + public void getPointByVal(int index, Point outPoint) { m_impl.getPointByVal(index, outPoint); } @Override - void setPointByVal(int index, Point point) { + public void setPointByVal(int index, Point point) { m_impl.setPointByVal(index, point); } diff --git a/src/main/java/com/esri/core/geometry/MultiPathImpl.java b/src/main/java/com/esri/core/geometry/MultiPathImpl.java index 43ed98a5..f6d606bb 100644 --- a/src/main/java/com/esri/core/geometry/MultiPathImpl.java +++ b/src/main/java/com/esri/core/geometry/MultiPathImpl.java @@ -1923,18 +1923,27 @@ public boolean equals(Object other) { if (pathCount != pathCountOther) return false; - if (m_paths != null + if (pathCount > 0 && m_paths != null && !m_paths.equals(otherMultiPath.m_paths, 0, pathCount + 1)) return false; - - if (m_fill_rule != otherMultiPath.m_fill_rule) - return false; - if (m_pathFlags != null - && !m_pathFlags - .equals(otherMultiPath.m_pathFlags, 0, pathCount)) + if (m_fill_rule != otherMultiPath.m_fill_rule) return false; + { + // Note: OGC flags do not participate in the equals operation by + // design. + // Because for the polygon pathFlags will have all enum_closed set, + // we do not need to compare this stream. Only for polyline. + // Polyline does not have OGC flags set. + if (!m_bPolygon) { + if (m_pathFlags != null + && !m_pathFlags.equals(otherMultiPath.m_pathFlags, 0, + pathCount)) + return false; + } + } + return super.equals(other); } @@ -2101,7 +2110,7 @@ protected void _updateOGCFlags() { _updateRingAreas2D(); int pathCount = getPathCount(); - if (m_pathFlags == null || m_pathFlags.size() < pathCount) + if (pathCount > 0 && (m_pathFlags == null || m_pathFlags.size() < pathCount)) m_pathFlags = (AttributeStreamOfInt8) AttributeStreamBase .createByteStream(pathCount + 1); diff --git a/src/main/java/com/esri/core/geometry/MultiPoint.java b/src/main/java/com/esri/core/geometry/MultiPoint.java index 2d2cbf28..8a3f6f6a 100644 --- a/src/main/java/com/esri/core/geometry/MultiPoint.java +++ b/src/main/java/com/esri/core/geometry/MultiPoint.java @@ -32,7 +32,7 @@ * information where the order and individual identity of each point is not an * essential characteristic of the point set. */ -public final class MultiPoint extends MultiVertexGeometry implements +public class MultiPoint extends MultiVertexGeometry implements Serializable { private static final long serialVersionUID = 2L; @@ -122,6 +122,15 @@ public void add(double x, double y) { m_impl.add(x, y); } + /** + * Adds a point with the specified X, Y coordinates to this multipoint. + * + * @param pt the point to add + */ + public void add(Point2D pt) { + m_impl.add(pt.x, pt.y); + } + /** * Adds a 3DPoint with the specified X, Y, Z coordinates to this multipoint. * @@ -265,7 +274,7 @@ public void addAttribute(int semantics) { } @Override - void assignVertexDescription(VertexDescription src) { + public void assignVertexDescription(VertexDescription src) { m_impl.assignVertexDescription(src); } @@ -280,7 +289,7 @@ public void dropAttribute(int semantics) { } @Override - void mergeVertexDescription(VertexDescription src) { + public void mergeVertexDescription(VertexDescription src) { m_impl.mergeVertexDescription(src); } @@ -346,12 +355,12 @@ int queryCoordinates(Point2D[] dst, int dstSize, int beginIndex, } @Override - void getPointByVal(int index, Point outPoint) { + public void getPointByVal(int index, Point outPoint) { m_impl.getPointByVal(index, outPoint); } @Override - void setPointByVal(int index, Point pointSrc) { + public void setPointByVal(int index, Point pointSrc) { m_impl.setPointByVal(index, pointSrc); } diff --git a/src/main/java/com/esri/core/geometry/MultiVertexGeometry.java b/src/main/java/com/esri/core/geometry/MultiVertexGeometry.java index 668d6b33..c164b48c 100644 --- a/src/main/java/com/esri/core/geometry/MultiVertexGeometry.java +++ b/src/main/java/com/esri/core/geometry/MultiVertexGeometry.java @@ -197,7 +197,7 @@ abstract void setAttribute(int semantics, int index, int ordinate, * Returns given vertex of the Geometry. The outPoint will have same * VertexDescription as this Geometry. */ - abstract void getPointByVal(int index, Point outPoint); + public abstract void getPointByVal(int index, Point outPoint); /** * Sets the vertex at given index of the Geometry. @@ -214,6 +214,6 @@ abstract void setAttribute(int semantics, int index, int ordinate, * the Geometry will be set to the default values (see * VertexDescription::GetDefaultValue). */ - abstract void setPointByVal(int index, Point pointSrc); + public abstract void setPointByVal(int index, Point pointSrc); } diff --git a/src/main/java/com/esri/core/geometry/MultiVertexGeometryImpl.java b/src/main/java/com/esri/core/geometry/MultiVertexGeometryImpl.java index 11ef7ed4..6e2694c7 100644 --- a/src/main/java/com/esri/core/geometry/MultiVertexGeometryImpl.java +++ b/src/main/java/com/esri/core/geometry/MultiVertexGeometryImpl.java @@ -180,9 +180,8 @@ public MultiVertexGeometryImpl() { m_accelerators = null; } - // Checked vs. Jan 11, 2011 @Override - void getPointByVal(int index, Point dst) { + public void getPointByVal(int index, Point dst) { if (index < 0 || index >= m_pointCount) // TODO throw new GeometryException("index out of bounds"); @@ -212,9 +211,8 @@ void getPointByVal(int index, Point dst) { } } - // Checked vs. Jan 11, 2011 @Override - protected void setPointByVal(int index, Point src) { + public void setPointByVal(int index, Point src) { if (index < 0 || index >= m_pointCount) throw new GeometryException("index out of bounds"); @@ -1110,6 +1108,9 @@ public abstract boolean _buildRasterizedGeometryAccelerator( public abstract boolean _buildQuadTreeAccelerator( GeometryAccelerationDegree d); - // //////////////////METHODS To REMOVE /////////////////////// + @Override + public String toString() { + return "MultiVertexGeometryImpl"; + } } diff --git a/src/main/java/com/esri/core/geometry/NumberUtils.java b/src/main/java/com/esri/core/geometry/NumberUtils.java index 209df086..4ee00a1e 100644 --- a/src/main/java/com/esri/core/geometry/NumberUtils.java +++ b/src/main/java/com/esri/core/geometry/NumberUtils.java @@ -68,6 +68,12 @@ static double NaN() { return Double.NaN; } + //combines two hash values + public static int hashCombine(int hash1, int hash2) { + return (hash1 * 31 + hash2) & 0x7FFFFFFF; + } + + //makes a hash out of an int static int hash(int n) { int hash = 5381; hash = ((hash << 5) + hash) + (n & 0xFF); /* hash * 33 + c */ @@ -78,12 +84,14 @@ static int hash(int n) { return hash; } + // //makes a hash out of an double static int hash(double d) { long bits = Double.doubleToLongBits(d); int hc = (int) (bits ^ (bits >>> 32)); return hash(hc); } + //adds an int to a hash value static int hash(int hashIn, int n) { int hash = ((hashIn << 5) + hashIn) + (n & 0xFF); /* hash * 33 + c */ hash = ((hash << 5) + hash) + ((n >> 8) & 0xFF); @@ -93,6 +101,7 @@ static int hash(int hashIn, int n) { return hash; } + //adds a double to a hash value static int hash(int hash, double d) { long bits = Double.doubleToLongBits(d); int hc = (int) (bits ^ (bits >>> 32)); diff --git a/src/main/java/com/esri/core/geometry/OperatorBuffer.java b/src/main/java/com/esri/core/geometry/OperatorBuffer.java index cca44f54..93c71b02 100644 --- a/src/main/java/com/esri/core/geometry/OperatorBuffer.java +++ b/src/main/java/com/esri/core/geometry/OperatorBuffer.java @@ -58,6 +58,31 @@ public abstract Geometry execute(Geometry inputGeometry, SpatialReference sr, double distance, ProgressTracker progressTracker); + /** + *Creates a buffer around the input geometries + * + *@param input_geometries The geometries to buffer. + *@param sr The Spatial_reference of the Geometries. It is used to obtain the tolerance. Can be null. + *@param distances The buffer distances for the Geometries. If the size of the distances array is less than the number of geometries in the input_geometries, the last distance value is used for the rest of geometries. + *@param max_deviation The max deviation of the result buffer from the true buffer in the units of the sr. + *When max_deviation is NaN or 0, it is replaced with 1e-5 * abs(distance). + *When max_deviation is larger than MIN = 0.5 * abs(distance), it is replaced with MIN. See below for more information. + *@param max_vertices_in_full_circle The maximum number of vertices in polygon produced from a buffered point. A value of 96 is used in methods that do not accept max_vertices_in_full_circle. + *If the value is less than MIN=12, it is set to MIN. See below for more information. + *@param b_union If True, the buffered geometries will be unioned, otherwise they wont be unioned. + *@param progress_tracker The progress tracker that allows to cancel the operation. Pass null if not needed. + * + *The max_deviation and max_vertices_in_full_circle control the quality of round joins in the buffer. That is, the precision of the buffer is max_deviation unless + *the number of required vertices is too large. + *The max_vertices_in_full_circle controls how many vertices can be in each round join in the buffer. It is approximately equal to the number of vertices in the polygon around a + *buffered point. It has a priority over max_deviation. The max deviation is the distance from the result polygon to a true buffer. + *The real deviation is calculated as the max(max_deviation, abs(distance) * (1 - cos(PI / max_vertex_in_complete_circle))). + * + *Note that max_deviation can be exceeded because geometry is generalized with 0.25 * real_deviation, also input segments closer than 0.25 * real_deviation are + *snapped to a point. + */ + abstract GeometryCursor execute(GeometryCursor input_geometries, SpatialReference sr, double[] distances, double max_deviation, int max_vertices_in_full_circle, boolean b_union, ProgressTracker progress_tracker); + public static OperatorBuffer local() { return (OperatorBuffer) OperatorFactoryLocal.getInstance().getOperator( Type.Buffer); diff --git a/src/main/java/com/esri/core/geometry/OperatorBufferCursor.java b/src/main/java/com/esri/core/geometry/OperatorBufferCursor.java index 397ec89c..dd5716ca 100644 --- a/src/main/java/com/esri/core/geometry/OperatorBufferCursor.java +++ b/src/main/java/com/esri/core/geometry/OperatorBufferCursor.java @@ -25,25 +25,29 @@ package com.esri.core.geometry; class OperatorBufferCursor extends GeometryCursor { - + Bufferer m_bufferer = new Bufferer(); private GeometryCursor m_inputGeoms; private SpatialReferenceImpl m_Spatial_reference; private ProgressTracker m_progress_tracker; private double[] m_distances; private Envelope2D m_currentUnionEnvelope2D; - private boolean m_bUnion; - + double m_max_deviation; + int m_max_vertices_in_full_circle; private int m_index; private int m_dindex; OperatorBufferCursor(GeometryCursor inputGeoms, SpatialReference sr, - double[] distances, boolean b_union, + double[] distances, + double max_deviation, + int max_vertices, + boolean b_union, ProgressTracker progress_tracker) { m_index = -1; m_inputGeoms = inputGeoms; + m_max_deviation = max_deviation; + m_max_vertices_in_full_circle = max_vertices; m_Spatial_reference = (SpatialReferenceImpl) (sr); m_distances = distances; - m_bUnion = b_union; m_currentUnionEnvelope2D = new Envelope2D(); m_currentUnionEnvelope2D.setEmpty(); m_dindex = -1; @@ -72,7 +76,7 @@ public int getGeometryID() { // virtual bool IsRecycling() OVERRIDE { return false; } Geometry buffer(Geometry geom, double distance) { - return Bufferer.buffer(geom, distance, m_Spatial_reference, - NumberUtils.TheNaN, 96, m_progress_tracker); + return m_bufferer.buffer(geom, distance, m_Spatial_reference, + m_max_deviation, m_max_vertices_in_full_circle, m_progress_tracker); } } diff --git a/src/main/java/com/esri/core/geometry/OperatorBufferLocal.java b/src/main/java/com/esri/core/geometry/OperatorBufferLocal.java index 5e195d57..8c44225f 100644 --- a/src/main/java/com/esri/core/geometry/OperatorBufferLocal.java +++ b/src/main/java/com/esri/core/geometry/OperatorBufferLocal.java @@ -30,15 +30,8 @@ class OperatorBufferLocal extends OperatorBuffer { public GeometryCursor execute(GeometryCursor inputGeometries, SpatialReference sr, double[] distances, boolean bUnion, ProgressTracker progressTracker) { - if (bUnion) { - OperatorBufferCursor cursor = new OperatorBufferCursor( - inputGeometries, sr, distances, false, progressTracker); - return ((OperatorUnion) OperatorFactoryLocal.getInstance() - .getOperator(Operator.Type.Union)).execute(cursor, sr, - progressTracker); - } else - return new OperatorBufferCursor(inputGeometries, sr, distances, - false, progressTracker); + return execute(inputGeometries, sr, distances, NumberUtils.NaN(), 96, + bUnion, progressTracker); } @Override @@ -54,4 +47,20 @@ public Geometry execute(Geometry inputGeometry, SpatialReference sr, return outputCursor.next(); } + @Override + public GeometryCursor execute(GeometryCursor inputGeometries, + SpatialReference sr, double[] distances, double max_deviation, + int max_vertices_in_full_circle, boolean b_union, + ProgressTracker progressTracker) { + if (b_union) { + OperatorBufferCursor cursor = new OperatorBufferCursor( + inputGeometries, sr, distances, max_deviation, + max_vertices_in_full_circle, false, progressTracker); + return OperatorUnion.local().execute(cursor, sr, progressTracker);// (int)Operator_union::Options::enum_disable_edge_dissolver + } else { + return new OperatorBufferCursor(inputGeometries, sr, distances, + max_deviation, max_vertices_in_full_circle, false, + progressTracker); + } + } } diff --git a/src/main/java/com/esri/core/geometry/OperatorConvexHullCursor.java b/src/main/java/com/esri/core/geometry/OperatorConvexHullCursor.java index 11b32738..c90c9d72 100644 --- a/src/main/java/com/esri/core/geometry/OperatorConvexHullCursor.java +++ b/src/main/java/com/esri/core/geometry/OperatorConvexHullCursor.java @@ -30,9 +30,8 @@ class OperatorConvexHullCursor extends GeometryCursor { private GeometryCursor m_inputGeometryCursor; private int m_index; ConvexHull m_hull = new ConvexHull(); - - OperatorConvexHullCursor(boolean b_merge, GeometryCursor geoms, - ProgressTracker progress_tracker) { + + OperatorConvexHullCursor(boolean b_merge, GeometryCursor geoms, ProgressTracker progress_tracker) { m_index = -1; if (geoms == null) throw new IllegalArgumentException(); @@ -47,8 +46,7 @@ class OperatorConvexHullCursor extends GeometryCursor { public Geometry next() { if (m_b_merge) { if (!m_b_done) { - Geometry result = calculateConvexHullMerging_( - m_inputGeometryCursor, m_progress_tracker); + Geometry result = calculateConvexHullMerging_(m_inputGeometryCursor, m_progress_tracker); m_b_done = true; return result; } @@ -74,8 +72,7 @@ public int getGeometryID() { return m_index; } - private Geometry calculateConvexHullMerging_(GeometryCursor geoms, - ProgressTracker progress_tracker) { + private Geometry calculateConvexHullMerging_(GeometryCursor geoms, ProgressTracker progress_tracker) { Geometry geometry; while ((geometry = geoms.next()) != null) @@ -83,20 +80,19 @@ private Geometry calculateConvexHullMerging_(GeometryCursor geoms, return m_hull.getBoundingGeometry(); } - + @Override public boolean tock() { if (m_b_done) return true; - - if (!m_b_merge) - { + + if (!m_b_merge) { //Do not use tick/tock with the non-merging convex hull. //Call tick/next instead, //because tick pushes geometry into the cursor, and next performs a single convex hull on it. throw new GeometryException("Invalid call for non merging convex hull."); } - + Geometry geometry = m_inputGeometryCursor.next(); if (geometry != null) { m_hull.addGeometry(geometry); @@ -106,33 +102,64 @@ public boolean tock() { } } - static Geometry calculateConvexHull_(Geometry geom, - ProgressTracker progress_tracker) { - if (isConvex_(geom, progress_tracker)) - return geom; - - int type = geom.getType().value(); + static Geometry calculateConvexHull_(Geometry geom, ProgressTracker progress_tracker) { + if (geom.isEmpty()) + return geom.createInstance(); - if (MultiPath.isSegment(type)) { - Polyline polyline = new Polyline(geom.getDescription()); - polyline.addSegment((Segment) geom, true); - return polyline; - } + Geometry.Type type = geom.getType(); - if (type == Geometry.GeometryType.MultiPoint) { - MultiPoint multi_point = (MultiPoint) geom; - if (multi_point.getPointCount() == 2) { + if (Geometry.isSegment(type.value())) {// Segments are always returned either as a Point or Polyline + Segment segment = (Segment) geom; + if (segment.getStartXY().equals(segment.getEndXY())) { + Point point = new Point(); + segment.queryStart(point); + return point; + } else { + Point pt = new Point(); + Polyline polyline = new Polyline(geom.getDescription()); + segment.queryStart(pt); + polyline.startPath(pt); + segment.queryEnd(pt); + polyline.lineTo(pt); + return polyline; + } + } else if (type == Geometry.Type.Envelope) { + Envelope envelope = (Envelope) geom; + Envelope2D env = new Envelope2D(); + envelope.queryEnvelope2D(env); + if (env.xmin == env.xmax && env.ymin == env.ymax) { + Point point = new Point(); + envelope.queryCornerByVal(0, point); + return point; + } else if (env.xmin == env.xmax || env.ymin == env.ymax) { Point pt = new Point(); Polyline polyline = new Polyline(geom.getDescription()); - multi_point.getPointByVal(0, pt); + envelope.queryCornerByVal(0, pt); polyline.startPath(pt); - multi_point.getPointByVal(1, pt); + envelope.queryCornerByVal(1, pt); polyline.lineTo(pt); return polyline; + } else { + Polygon polygon = new Polygon(geom.getDescription()); + polygon.addEnvelope(envelope, false); + return polygon; } } - Polygon convex_hull = ConvexHull.construct((MultiVertexGeometry) geom); + if (isConvex_(geom, progress_tracker)) { + if (type == Geometry.Type.MultiPoint) {// Downgrade to a Point for simplistic output + MultiPoint multi_point = (MultiPoint) geom; + Point point = new Point(); + multi_point.getPointByVal(0, point); + return point; + } + + return geom; + } + + assert (Geometry.isMultiVertex(type.value())); + + Geometry convex_hull = ConvexHull.construct((MultiVertexGeometry) geom); return convex_hull; } @@ -140,44 +167,52 @@ static boolean isConvex_(Geometry geom, ProgressTracker progress_tracker) { if (geom.isEmpty()) return true; // vacuously true - int type = geom.getType().value(); + Geometry.Type type = geom.getType(); - if (type == Geometry.GeometryType.Point) + if (type == Geometry.Type.Point) return true; // vacuously true - if (type == Geometry.GeometryType.Envelope) - return true; // always convex + if (type == Geometry.Type.Envelope) { + Envelope envelope = (Envelope) geom; + if (envelope.getXMin() == envelope.getXMax() || envelope.getYMin() == envelope.getYMax()) + return false; - if (MultiPath.isSegment(type)) - return false; // upgrade to polyline + return true; + } - if (type == Geometry.GeometryType.MultiPoint) { + if (MultiPath.isSegment(type.value())) { + Segment segment = (Segment) geom; + if (segment.getStartXY().equals(segment.getEndXY())) + return false; + + return true; // true, but we will upgrade to a Polyline for the ConvexHull operation + } + + if (type == Geometry.Type.MultiPoint) { MultiPoint multi_point = (MultiPoint) geom; if (multi_point.getPointCount() == 1) - return true; // vacuously true + return true; // vacuously true, but we will downgrade to a Point for the ConvexHull operation - return false; // upgrade to polyline if point count is 2, otherwise - // create convex hull + return false; } - if (type == Geometry.GeometryType.Polyline) { + if (type == Geometry.Type.Polyline) { Polyline polyline = (Polyline) geom; - if (polyline.getPathCount() == 1 && polyline.getPointCount() <= 2) - return true; // vacuously true + if (polyline.getPathCount() == 1 && polyline.getPointCount() == 2) { + if (!polyline.getXY(0).equals(polyline.getXY(1))) + return true; // vacuously true + } return false; // create convex hull } Polygon polygon = (Polygon) geom; - if (polygon.getPathCount() != 1) + if (polygon.getPathCount() != 1 || polygon.getPointCount() < 3) return false; - if (polygon.getPointCount() <= 2) - return true; // vacuously true - return ConvexHull.isPathConvex(polygon, 0, progress_tracker); } } diff --git a/src/main/java/com/esri/core/geometry/OperatorExportToGeoJson.java b/src/main/java/com/esri/core/geometry/OperatorExportToGeoJson.java index 9a1ac765..6f9d506c 100644 --- a/src/main/java/com/esri/core/geometry/OperatorExportToGeoJson.java +++ b/src/main/java/com/esri/core/geometry/OperatorExportToGeoJson.java @@ -3,7 +3,7 @@ you may not use this file except in compliance with the License. You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 + http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, @@ -28,21 +28,53 @@ *Export to GeoJson format. */ public abstract class OperatorExportToGeoJson extends Operator { - @Override - public Type getType() { - return Type.ExportToGeoJson; - } + @Override + public Type getType() { + return Type.ExportToGeoJson; + } - public abstract JsonCursor execute(SpatialReference spatialReference, GeometryCursor geometryCursor); + /** + * Performs the ExportToGeoJson operation + * @param spatialReference The SpatialReference of the Geometry. Will be written as "crs":null if the spatialReference is null. + * @param geometryCursor The cursor of geometries to write as GeoJson. + * @return Returns a JsonCursor. + */ + public abstract JsonCursor execute(SpatialReference spatialReference, GeometryCursor geometryCursor); - public abstract String execute(SpatialReference spatialReference, Geometry geometry); + /** + * Performs the ExportToGeoJson operation + * @param spatialReference The SpatialReference of the Geometry. Will be written as "crs":null if the spatialReference is null. + * @param geometry The Geometry to write as GeoJson. + * @return Returns a string in GeoJson format. + */ + public abstract String execute(SpatialReference spatialReference, Geometry geometry); - public abstract String execute(int exportFlags, SpatialReference spatialReference, Geometry geometry); - - public abstract String execute(Geometry geometry); + /** + * Performs the ExportToGeoJson operation + * @param exportFlags Use the {@link GeoJsonExportFlags} interface. + * @param spatialReference The SpatialReference of the Geometry. Will be written as "crs":null if the spatialReference is null. + * @param geometry The Geometry to write as GeoJson. + * @return Returns a string in GeoJson format. + */ + public abstract String execute(int exportFlags, SpatialReference spatialReference, Geometry geometry); - public static OperatorExportToGeoJson local() { - return (OperatorExportToGeoJson) OperatorFactoryLocal.getInstance() - .getOperator(Type.ExportToGeoJson); - } + /** + * Performs the ExportToGeoJson operation. Will not write out a spatial reference or crs tag. Assumes the geometry is in wgs84. + * @param geometry The Geometry to write as GeoJson. + * @return Returns a string in GeoJson format. + */ + public abstract String execute(Geometry geometry); + + /** + * Performs the ExportToGeoJson operation on a spatial reference. + * + * @param export_flags The flags used for the export. + * @param spatial_reference The spatial reference being exported. Cannot be null. + * @return Returns the crs value object. + */ + public abstract String exportSpatialReference(int export_flags, SpatialReference spatial_reference); + + public static OperatorExportToGeoJson local() { + return (OperatorExportToGeoJson) OperatorFactoryLocal.getInstance().getOperator(Type.ExportToGeoJson); + } } diff --git a/src/main/java/com/esri/core/geometry/OperatorExportToGeoJsonCursor.java b/src/main/java/com/esri/core/geometry/OperatorExportToGeoJsonCursor.java index ddad1662..0678cef4 100644 --- a/src/main/java/com/esri/core/geometry/OperatorExportToGeoJsonCursor.java +++ b/src/main/java/com/esri/core/geometry/OperatorExportToGeoJsonCursor.java @@ -1,9 +1,32 @@ +/* + Copyright 1995-2015 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ /* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 + http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, @@ -23,312 +46,757 @@ package com.esri.core.geometry; import com.esri.core.geometry.VertexDescription.Semantics; -import java.io.IOException; -import java.io.StringWriter; -import org.codehaus.jackson.JsonFactory; -import org.codehaus.jackson.JsonGenerationException; -import org.codehaus.jackson.JsonGenerator; class OperatorExportToGeoJsonCursor extends JsonCursor { - GeometryCursor m_inputGeometryCursor; - int m_index; - int m_wkid = -1; - int m_latest_wkid = -1; - String m_wkt = null; - boolean m_preferMulti = false; - - private static JsonFactory factory = new JsonFactory(); - - public OperatorExportToGeoJsonCursor(boolean preferMulti, SpatialReference spatialReference, GeometryCursor geometryCursor) { - m_index = -1; - if (geometryCursor == null) - throw new IllegalArgumentException(); - if (spatialReference != null && !spatialReference.isLocal()) { - m_wkid = spatialReference.getOldID(); - m_wkt = spatialReference.getText(); - m_latest_wkid = spatialReference.getLatestID(); - } - m_inputGeometryCursor = geometryCursor; - m_preferMulti = preferMulti; - } - - public OperatorExportToGeoJsonCursor(GeometryCursor geometryCursor) { - m_index = -1; - - if (geometryCursor == null) - throw new IllegalArgumentException(); - - m_inputGeometryCursor = geometryCursor; - } - - @Override - public int getID() { - return m_index; - } - - @Override - public String next() { - Geometry geometry; - if ((geometry = m_inputGeometryCursor.next()) != null) { - m_index = m_inputGeometryCursor.getGeometryID(); - return exportToGeoJson(geometry); - } - return null; - } - - private String exportToGeoJson(Geometry geometry) { - StringWriter sw = new StringWriter(); - - try { - JsonGenerator g = factory.createJsonGenerator(sw); - - int type = geometry.getType().value(); - - switch (type) { - case Geometry.GeometryType.Point: - exportPointToGeoJson(g, (Point) geometry); - break; - case Geometry.GeometryType.MultiPoint: - exportMultiPointToGeoJson(g, (MultiPoint) geometry); - break; - case Geometry.GeometryType.Polyline: - exportMultiPathToGeoJson(g, (Polyline) geometry); - break; - case Geometry.GeometryType.Polygon: - exportMultiPathToGeoJson(g, (Polygon) geometry); - break; - case Geometry.GeometryType.Envelope: - exportEnvelopeToGeoJson(g, (Envelope) geometry); - break; - default: - throw new RuntimeException("not implemented for this geometry type"); - } - - return sw.getBuffer().toString(); - } catch (IOException e) { - e.printStackTrace(); - return null; - } - } - - private void exportPointToGeoJson(JsonGenerator g, Point p) throws JsonGenerationException, IOException { - if (m_preferMulti) { - MultiPoint mp = new MultiPoint(); - mp.add(p); - exportMultiPointToGeoJson(g, mp); - return; - } - - g.writeStartObject(); - - g.writeFieldName("type"); - g.writeString("Point"); - - g.writeFieldName("coordinates"); - - if (p.isEmpty()) { - g.writeNull(); - } else { - g.writeStartArray(); - - writeDouble(p.getX(), g); - writeDouble(p.getY(), g); - - if (p.hasAttribute(Semantics.Z)) - writeDouble(p.getZ(), g); - - g.writeEndArray(); - } - - g.writeEndObject(); - g.close(); - } - - private void exportMultiPointToGeoJson(JsonGenerator g, MultiPoint mp) throws JsonGenerationException, IOException { - g.writeStartObject(); - - g.writeFieldName("type"); - g.writeString("MultiPoint"); - - g.writeFieldName("coordinates"); - - if (mp.isEmpty()) { - g.writeNull(); - } else { - g.writeStartArray(); - - MultiPointImpl mpImpl = (MultiPointImpl) mp._getImpl(); - AttributeStreamOfDbl zs = mp.hasAttribute(Semantics.Z) ? (AttributeStreamOfDbl) mpImpl.getAttributeStreamRef(Semantics.Z) : null; - - Point2D p = new Point2D(); - - int n = mp.getPointCount(); - - for(int i = 0; i < n; i++) { - mp.getXY(i, p); - - g.writeStartArray(); - - writeDouble(p.x, g); - writeDouble(p.y, g); - - if (zs != null) - writeDouble(zs.get(i), g); - - g.writeEndArray(); - } - - g.writeEndArray(); - } - - g.writeEndObject(); - g.close(); - } - - private void exportMultiPathToGeoJson(JsonGenerator g, MultiPath p) throws JsonGenerationException, IOException { - MultiPathImpl pImpl = (MultiPathImpl) p._getImpl(); - - boolean isPolygon = pImpl.m_bPolygon; - int polyCount = isPolygon ? pImpl.getOGCPolygonCount() : 0; - - // check yo' polys playa - - g.writeStartObject(); - - g.writeFieldName("type"); - - boolean bCollection = false; - if (isPolygon) { - if (polyCount >= 2 || m_preferMulti) { // single polys seem to have a polyCount of 0, multi polys seem to be >= 2 - g.writeString("MultiPolygon"); - bCollection = true; - } else { - g.writeString("Polygon"); - } - } - else { - if (p.getPathCount() > 1 || m_preferMulti) { // single polys seem to have a polyCount of 0, multi polys seem to be >= 2 - g.writeString("MultiLineString"); - bCollection = true; - } else { - g.writeString("LineString"); - } - } - - g.writeFieldName("coordinates"); - - if (p.isEmpty()) { - g.writeNull(); - } else { - exportMultiPathToGeoJson(g, pImpl, bCollection); - } + GeometryCursor m_inputGeometryCursor; + SpatialReference m_spatialReference; + int m_index; + int m_export_flags; + + public OperatorExportToGeoJsonCursor(int export_flags, SpatialReference spatialReference, + GeometryCursor geometryCursor) { + m_index = -1; + if (geometryCursor == null) + throw new IllegalArgumentException(); + + m_export_flags = export_flags; + m_spatialReference = spatialReference; + m_inputGeometryCursor = geometryCursor; + } + + @Override + public int getID() { + return m_index; + } + + @Override + public String next() { + Geometry geometry; + if ((geometry = m_inputGeometryCursor.next()) != null) { + m_index = m_inputGeometryCursor.getGeometryID(); + return exportToGeoJson(m_export_flags, geometry, m_spatialReference); + } + return null; + } + + // Mirrors wkt + static String exportToGeoJson(int export_flags, Geometry geometry, SpatialReference spatial_reference) { + + if (geometry == null) + throw new IllegalArgumentException(""); + + JsonWriter json_writer = new JsonStringWriter(); + + json_writer.startObject(); + + exportGeometryToGeoJson_(export_flags, geometry, json_writer); + + if ((export_flags & GeoJsonExportFlags.geoJsonExportSkipCRS) == 0) { + json_writer.addFieldName("crs"); + exportSpatialReference(export_flags, spatial_reference, json_writer); + } + + json_writer.endObject(); + + return (String) json_writer.getJson(); + } + + static String exportSpatialReference(int export_flags, SpatialReference spatial_reference) { + if (spatial_reference == null || (export_flags & GeoJsonExportFlags.geoJsonExportSkipCRS) != 0) + throw new IllegalArgumentException(""); + + JsonWriter json_writer = new JsonStringWriter(); + exportSpatialReference(export_flags, spatial_reference, json_writer); + + return (String) json_writer.getJson(); + } + + private static void exportGeometryToGeoJson_(int export_flags, Geometry geometry, JsonWriter json_writer) { + int type = geometry.getType().value(); + switch (type) { + case Geometry.GeometryType.Polygon: + exportPolygonToGeoJson_(export_flags, (Polygon) geometry, json_writer); + return; + + case Geometry.GeometryType.Polyline: + exportPolylineToGeoJson_(export_flags, (Polyline) geometry, json_writer); + return; + + case Geometry.GeometryType.MultiPoint: + exportMultiPointToGeoJson_(export_flags, (MultiPoint) geometry, json_writer); + return; + + case Geometry.GeometryType.Point: + exportPointToGeoJson_(export_flags, (Point) geometry, json_writer); + return; + + case Geometry.GeometryType.Envelope: + exportEnvelopeToGeoJson_(export_flags, (Envelope) geometry, + json_writer); + return; + + default: + throw new RuntimeException("not implemented for this geometry type"); + } + } + + private static void exportSpatialReference(int export_flags, SpatialReference spatial_reference, + JsonWriter json_writer) { + if (spatial_reference != null) { + int wkid = spatial_reference.getLatestID(); + + if (wkid <= 0) + throw new GeometryException("invalid call"); + + json_writer.startObject(); + + json_writer.addFieldName("type"); + + json_writer.addValueString("name"); + + json_writer.addFieldName("properties"); + json_writer.startObject(); + + json_writer.addFieldName("name"); + + String authority = ((SpatialReferenceImpl) spatial_reference).getAuthority(); + authority = authority.toUpperCase(); + StringBuilder crs_identifier = new StringBuilder(authority); + crs_identifier.append(':'); + crs_identifier.append(wkid); + json_writer.addValueString(crs_identifier.toString()); + + json_writer.endObject(); + + json_writer.endObject(); + } else { + json_writer.addValueNull(); + } + } + + // Mirrors wkt + private static void exportPolygonToGeoJson_(int export_flags, Polygon polygon, JsonWriter json_writer) { + MultiPathImpl polygon_impl = (MultiPathImpl) (polygon._getImpl()); + + if ((export_flags & GeoJsonExportFlags.geoJsonExportFailIfNotSimple) != 0) { + int simple = polygon_impl.getIsSimple(0.0); + + if (simple != MultiPathImpl.GeometryXSimple.Strong) + throw new GeometryException("corrupted geometry"); + } + + int point_count = polygon.getPointCount(); + int polygon_count = polygon_impl.getOGCPolygonCount(); + + if (point_count > 0 && polygon_count == 0) + throw new GeometryException("corrupted geometry"); + + int precision = 17 - (31 & (export_flags >> 13)); + boolean bFixedPoint = (GeoJsonExportFlags.geoJsonExportPrecisionFixedPoint & export_flags) != 0; + boolean b_export_zs = polygon_impl.hasAttribute(VertexDescription.Semantics.Z) + && (export_flags & GeoJsonExportFlags.geoJsonExportStripZs) == 0; + boolean b_export_ms = polygon_impl.hasAttribute(VertexDescription.Semantics.M) + && (export_flags & GeoJsonExportFlags.geoJsonExportStripMs) == 0; + + if (!b_export_zs && b_export_ms) + throw new IllegalArgumentException("invalid argument"); + + int path_count = 0; + AttributeStreamOfDbl position = null; + AttributeStreamOfDbl zs = null; + AttributeStreamOfDbl ms = null; + AttributeStreamOfInt8 path_flags = null; + AttributeStreamOfInt32 paths = null; + + if (point_count > 0) { + position = (AttributeStreamOfDbl) polygon_impl.getAttributeStreamRef(Semantics.POSITION); + path_flags = polygon_impl.getPathFlagsStreamRef(); + paths = polygon_impl.getPathStreamRef(); + path_count = polygon_impl.getPathCount(); + + if (b_export_zs) { + if (polygon_impl._attributeStreamIsAllocated(Semantics.Z)) + zs = (AttributeStreamOfDbl) polygon_impl.getAttributeStreamRef(Semantics.Z); + } + + if (b_export_ms) { + if (polygon_impl._attributeStreamIsAllocated(Semantics.M)) + ms = (AttributeStreamOfDbl) polygon_impl.getAttributeStreamRef(Semantics.M); + } + } + + if ((export_flags & GeoJsonExportFlags.geoJsonExportPreferMultiGeometry) == 0 && polygon_count <= 1) + polygonTaggedText_(precision, bFixedPoint, b_export_zs, b_export_ms, zs, ms, position, paths, path_count, + json_writer); + else + multiPolygonTaggedText_(precision, bFixedPoint, b_export_zs, b_export_ms, zs, ms, position, path_flags, + paths, polygon_count, path_count, json_writer); + } + + // Mirrors wkt + private static void exportPolylineToGeoJson_(int export_flags, Polyline polyline, JsonWriter json_writer) { + MultiPathImpl polyline_impl = (MultiPathImpl) polyline._getImpl(); + + int point_count = polyline_impl.getPointCount(); + int path_count = polyline_impl.getPathCount(); + + if (point_count > 0 && path_count == 0) + throw new GeometryException("corrupted geometry"); + + int precision = 17 - (31 & (export_flags >> 13)); + boolean bFixedPoint = (GeoJsonExportFlags.geoJsonExportPrecisionFixedPoint & export_flags) != 0; + boolean b_export_zs = polyline_impl.hasAttribute(VertexDescription.Semantics.Z) + && (export_flags & GeoJsonExportFlags.geoJsonExportStripZs) == 0; + boolean b_export_ms = polyline_impl.hasAttribute(VertexDescription.Semantics.M) + && (export_flags & GeoJsonExportFlags.geoJsonExportStripMs) == 0; + + if (!b_export_zs && b_export_ms) + throw new IllegalArgumentException("invalid argument"); + + AttributeStreamOfDbl position = null; + AttributeStreamOfDbl zs = null; + AttributeStreamOfDbl ms = null; + AttributeStreamOfInt8 path_flags = null; + AttributeStreamOfInt32 paths = null; + + if (point_count > 0) { + position = (AttributeStreamOfDbl) polyline_impl.getAttributeStreamRef(Semantics.POSITION); + path_flags = polyline_impl.getPathFlagsStreamRef(); + paths = polyline_impl.getPathStreamRef(); + + if (b_export_zs) { + if (polyline_impl._attributeStreamIsAllocated(Semantics.Z)) + zs = (AttributeStreamOfDbl) polyline_impl.getAttributeStreamRef(Semantics.Z); + } + + if (b_export_ms) { + if (polyline_impl._attributeStreamIsAllocated(Semantics.M)) + ms = (AttributeStreamOfDbl) polyline_impl.getAttributeStreamRef(Semantics.M); + } + } + + if ((export_flags & GeoJsonExportFlags.geoJsonExportPreferMultiGeometry) == 0 && path_count <= 1) + lineStringTaggedText_(precision, bFixedPoint, b_export_zs, b_export_ms, zs, ms, position, path_flags, paths, + json_writer); + else + multiLineStringTaggedText_(precision, bFixedPoint, b_export_zs, b_export_ms, zs, ms, position, path_flags, + paths, path_count, json_writer); + } + + // Mirrors wkt + private static void exportMultiPointToGeoJson_(int export_flags, MultiPoint multipoint, JsonWriter json_writer) { + MultiPointImpl multipoint_impl = (MultiPointImpl) multipoint._getImpl(); + + int point_count = multipoint_impl.getPointCount(); + + int precision = 17 - (31 & (export_flags >> 13)); + boolean bFixedPoint = (GeoJsonExportFlags.geoJsonExportPrecisionFixedPoint & export_flags) != 0; + boolean b_export_zs = multipoint_impl.hasAttribute(VertexDescription.Semantics.Z) + && (export_flags & GeoJsonExportFlags.geoJsonExportStripZs) == 0; + boolean b_export_ms = multipoint_impl.hasAttribute(VertexDescription.Semantics.M) + && (export_flags & GeoJsonExportFlags.geoJsonExportStripMs) == 0; + + if (!b_export_zs && b_export_ms) + throw new IllegalArgumentException("invalid argument"); + + AttributeStreamOfDbl position = null; + AttributeStreamOfDbl zs = null; + AttributeStreamOfDbl ms = null; + + if (point_count > 0) { + position = (AttributeStreamOfDbl) multipoint_impl.getAttributeStreamRef(Semantics.POSITION); + + if (b_export_zs) { + if (multipoint_impl._attributeStreamIsAllocated(Semantics.Z)) + zs = (AttributeStreamOfDbl) multipoint_impl.getAttributeStreamRef(Semantics.Z); + } + + if (b_export_ms) { + if (multipoint_impl._attributeStreamIsAllocated(Semantics.M)) + ms = (AttributeStreamOfDbl) multipoint_impl.getAttributeStreamRef(Semantics.M); + } + } + + multiPointTaggedText_(precision, bFixedPoint, b_export_zs, b_export_ms, zs, ms, position, point_count, + json_writer); + } + + // Mirrors wkt + private static void exportPointToGeoJson_(int export_flags, Point point, JsonWriter json_writer) { + int precision = 17 - (31 & (export_flags >> 13)); + boolean bFixedPoint = (GeoJsonExportFlags.geoJsonExportPrecisionFixedPoint & export_flags) != 0; + boolean b_export_zs = point.hasAttribute(VertexDescription.Semantics.Z) + && (export_flags & GeoJsonExportFlags.geoJsonExportStripZs) == 0; + boolean b_export_ms = point.hasAttribute(VertexDescription.Semantics.M) + && (export_flags & GeoJsonExportFlags.geoJsonExportStripMs) == 0; + + if (!b_export_zs && b_export_ms) + throw new IllegalArgumentException("invalid argument"); + + double x = NumberUtils.NaN(); + double y = NumberUtils.NaN(); + double z = NumberUtils.NaN(); + double m = NumberUtils.NaN(); + + if (!point.isEmpty()) { + x = point.getX(); + y = point.getY(); + + if (b_export_zs) + z = point.getZ(); + + if (b_export_ms) + m = point.getM(); + } + + if ((export_flags & GeoJsonExportFlags.geoJsonExportPreferMultiGeometry) == 0) + pointTaggedText_(precision, bFixedPoint, b_export_zs, b_export_ms, x, y, z, m, json_writer); + else + multiPointTaggedTextFromPoint_(precision, bFixedPoint, b_export_zs, b_export_ms, x, y, z, m, json_writer); + } + + // Mirrors wkt + private static void exportEnvelopeToGeoJson_(int export_flags, Envelope envelope, JsonWriter json_writer) { + int precision = 17 - (31 & (export_flags >> 13)); + boolean bFixedPoint = (GeoJsonExportFlags.geoJsonExportPrecisionFixedPoint & export_flags) != 0; + boolean b_export_zs = envelope.hasAttribute(VertexDescription.Semantics.Z) + && (export_flags & GeoJsonExportFlags.geoJsonExportStripZs) == 0; + boolean b_export_ms = envelope.hasAttribute(VertexDescription.Semantics.M) + && (export_flags & GeoJsonExportFlags.geoJsonExportStripMs) == 0; + + if (!b_export_zs && b_export_ms) + throw new IllegalArgumentException("invalid argument"); + + double xmin = NumberUtils.NaN(); + double ymin = NumberUtils.NaN(); + double xmax = NumberUtils.NaN(); + double ymax = NumberUtils.NaN(); + double zmin = NumberUtils.NaN(); + double zmax = NumberUtils.NaN(); + double mmin = NumberUtils.NaN(); + double mmax = NumberUtils.NaN(); + + if (!envelope.isEmpty()) { + xmin = envelope.getXMin(); + ymin = envelope.getYMin(); + xmax = envelope.getXMax(); + ymax = envelope.getYMax(); + + Envelope1D interval; + + if (b_export_zs) { + interval = envelope.queryInterval(Semantics.Z, 0); + zmin = interval.vmin; + zmax = interval.vmax; + } + + if (b_export_ms) { + interval = envelope.queryInterval(Semantics.M, 0); + mmin = interval.vmin; + mmax = interval.vmax; + } + } + + if ((export_flags & GeoJsonExportFlags.geoJsonExportPreferMultiGeometry) == 0) + polygonTaggedTextFromEnvelope_(precision, bFixedPoint, b_export_zs, b_export_ms, xmin, ymin, xmax, ymax, + zmin, zmax, mmin, mmax, json_writer); + else + multiPolygonTaggedTextFromEnvelope_(precision, bFixedPoint, b_export_zs, b_export_ms, xmin, ymin, xmax, + ymax, zmin, zmax, mmin, mmax, json_writer); + } + + // Mirrors wkt + private static void multiPolygonTaggedText_(int precision, boolean bFixedPoint, boolean b_export_zs, + boolean b_export_ms, AttributeStreamOfDbl zs, AttributeStreamOfDbl ms, AttributeStreamOfDbl position, + AttributeStreamOfInt8 path_flags, AttributeStreamOfInt32 paths, int polygon_count, int path_count, + JsonWriter json_writer) { + json_writer.addFieldName("type"); + json_writer.addValueString("MultiPolygon"); + + json_writer.addFieldName("coordinates"); + + if (position == null) { + json_writer.startArray(); + json_writer.endArray(); + return; + } + + json_writer.startArray(); + + multiPolygonText_(precision, bFixedPoint, b_export_zs, b_export_ms, zs, ms, position, path_flags, paths, + polygon_count, path_count, json_writer); + + json_writer.endArray(); + } + + // Mirrors wkt + private static void multiPolygonTaggedTextFromEnvelope_(int precision, boolean bFixedPoint, boolean b_export_zs, + boolean b_export_ms, double xmin, double ymin, double xmax, double ymax, double zmin, double zmax, + double mmin, double mmax, JsonWriter json_writer) { + json_writer.addFieldName("type"); + json_writer.addValueString("MultiPolygon"); + + json_writer.addFieldName("coordinates"); + + if (NumberUtils.isNaN(xmin)) { + json_writer.startArray(); + json_writer.endArray(); + return; + } + + json_writer.startArray(); + + writeEnvelopeAsGeoJsonPolygon_(precision, bFixedPoint, b_export_zs, b_export_ms, xmin, ymin, xmax, ymax, zmin, + zmax, mmin, mmax, json_writer); + + json_writer.endArray(); + } + + // Mirrors wkt + private static void multiLineStringTaggedText_(int precision, boolean bFixedPoint, boolean b_export_zs, + boolean b_export_ms, AttributeStreamOfDbl zs, AttributeStreamOfDbl ms, AttributeStreamOfDbl position, + AttributeStreamOfInt8 path_flags, AttributeStreamOfInt32 paths, int path_count, JsonWriter json_writer) { + json_writer.addFieldName("type"); + json_writer.addValueString("MultiLineString"); + + json_writer.addFieldName("coordinates"); + + if (position == null) { + json_writer.startArray(); + json_writer.endArray(); + return; + } + + json_writer.startArray(); + + multiLineStringText_(precision, bFixedPoint, b_export_zs, b_export_ms, zs, ms, position, path_flags, paths, + path_count, json_writer); + + json_writer.endArray(); + } + + // Mirrors wkt + private static void multiPointTaggedText_(int precision, boolean bFixedPoint, boolean b_export_zs, + boolean b_export_ms, AttributeStreamOfDbl zs, AttributeStreamOfDbl ms, AttributeStreamOfDbl position, + int point_count, JsonWriter json_writer) { + json_writer.addFieldName("type"); + json_writer.addValueString("MultiPoint"); + + json_writer.addFieldName("coordinates"); + + if (position == null) { + json_writer.startArray(); + json_writer.endArray(); + return; + } + + lineStringText_(false, false, precision, bFixedPoint, b_export_zs, b_export_ms, zs, ms, position, 0, + point_count, json_writer); + } + + // Mirrors wkt + private static void multiPointTaggedTextFromPoint_(int precision, boolean bFixedPoint, boolean b_export_zs, + boolean b_export_ms, double x, double y, double z, double m, JsonWriter json_writer) { + json_writer.addFieldName("type"); + json_writer.addValueString("MultiPoint"); + + json_writer.addFieldName("coordinates"); + + if (NumberUtils.isNaN(x)) { + json_writer.startArray(); + json_writer.endArray(); + return; + } + + json_writer.startArray(); + + pointText_(precision, bFixedPoint, b_export_zs, b_export_ms, x, y, z, m, json_writer); + + json_writer.endArray(); + } + + // Mirrors wkt + private static void polygonTaggedText_(int precision, boolean bFixedPoint, boolean b_export_zs, boolean b_export_ms, + AttributeStreamOfDbl zs, AttributeStreamOfDbl ms, AttributeStreamOfDbl position, + AttributeStreamOfInt32 paths, int path_count, JsonWriter json_writer) { + json_writer.addFieldName("type"); + json_writer.addValueString("Polygon"); + + json_writer.addFieldName("coordinates"); + + if (position == null) { + json_writer.startArray(); + json_writer.endArray(); + return; + } + + polygonText_(precision, bFixedPoint, b_export_zs, b_export_ms, zs, ms, position, paths, 0, path_count, + json_writer); + } + + // Mirrors wkt + private static void polygonTaggedTextFromEnvelope_(int precision, boolean bFixedPoint, boolean b_export_zs, + boolean b_export_ms, double xmin, double ymin, double xmax, double ymax, double zmin, double zmax, + double mmin, double mmax, JsonWriter json_writer) { + json_writer.addFieldName("type"); + json_writer.addValueString("Polygon"); + + json_writer.addFieldName("coordinates"); + + if (NumberUtils.isNaN(xmin)) { + json_writer.startArray(); + json_writer.endArray(); + return; + } + + writeEnvelopeAsGeoJsonPolygon_(precision, bFixedPoint, b_export_zs, b_export_ms, xmin, ymin, xmax, ymax, zmin, + zmax, mmin, mmax, json_writer); + } + + // Mirrors wkt + private static void lineStringTaggedText_(int precision, boolean bFixedPoint, boolean b_export_zs, + boolean b_export_ms, AttributeStreamOfDbl zs, AttributeStreamOfDbl ms, AttributeStreamOfDbl position, + AttributeStreamOfInt8 path_flags, AttributeStreamOfInt32 paths, JsonWriter json_writer) { + json_writer.addFieldName("type"); + json_writer.addValueString("LineString"); + + json_writer.addFieldName("coordinates"); + + if (position == null) { + json_writer.startArray(); + json_writer.endArray(); + return; + } + + boolean b_closed = ((path_flags.read(0) & PathFlags.enumClosed) != 0); + + lineStringText_(false, b_closed, precision, bFixedPoint, b_export_zs, b_export_ms, zs, ms, position, 0, + paths.read(1), json_writer); + } + + // Mirrors wkt + private static void pointTaggedText_(int precision, boolean bFixedPoint, boolean b_export_zs, boolean b_export_ms, + double x, double y, double z, double m, JsonWriter json_writer) { + json_writer.addFieldName("type"); + json_writer.addValueString("Point"); + + json_writer.addFieldName("coordinates"); + + if (NumberUtils.isNaN(x)) { + json_writer.startArray(); + json_writer.endArray(); + + return; + } + + pointText_(precision, bFixedPoint, b_export_zs, b_export_ms, x, y, z, m, json_writer); + } + + // Mirrors wkt + private static void multiPolygonText_(int precision, boolean bFixedPoint, boolean b_export_zs, boolean b_export_ms, + AttributeStreamOfDbl zs, AttributeStreamOfDbl ms, AttributeStreamOfDbl position, + AttributeStreamOfInt8 path_flags, AttributeStreamOfInt32 paths, int polygon_count, int path_count, + JsonWriter json_writer) { + int polygon_start = 0; + int polygon_end = 1; + + while (polygon_end < path_count && ((int) path_flags.read(polygon_end) & PathFlags.enumOGCStartPolygon) == 0) + polygon_end++; + + polygonText_(precision, bFixedPoint, b_export_zs, b_export_ms, zs, ms, position, paths, polygon_start, + polygon_end, json_writer); + + for (int ipolygon = 1; ipolygon < polygon_count; ipolygon++) { + polygon_start = polygon_end; + polygon_end++; + + while (polygon_end < path_count + && ((int) path_flags.read(polygon_end) & PathFlags.enumOGCStartPolygon) == 0) + polygon_end++; + + polygonText_(precision, bFixedPoint, b_export_zs, b_export_ms, zs, ms, position, paths, polygon_start, + polygon_end, json_writer); + } + } + + // Mirrors wkt + private static void multiLineStringText_(int precision, boolean bFixedPoint, boolean b_export_zs, + boolean b_export_ms, AttributeStreamOfDbl zs, AttributeStreamOfDbl ms, AttributeStreamOfDbl position, + AttributeStreamOfInt8 path_flags, AttributeStreamOfInt32 paths, int path_count, JsonWriter json_writer) { + boolean b_closed = ((path_flags.read(0) & PathFlags.enumClosed) != 0); + + lineStringText_(false, b_closed, precision, bFixedPoint, b_export_zs, b_export_ms, zs, ms, position, 0, + paths.read(1), json_writer); + + for (int path = 1; path < path_count; path++) { + b_closed = ((path_flags.read(path) & PathFlags.enumClosed) != 0); + + int istart = paths.read(path); + int iend = paths.read(path + 1); + lineStringText_(false, b_closed, precision, bFixedPoint, b_export_zs, b_export_ms, zs, ms, position, istart, + iend, json_writer); + } + } + + // Mirrors wkt + private static void polygonText_(int precision, boolean bFixedPoint, boolean b_export_zs, boolean b_export_ms, + AttributeStreamOfDbl zs, AttributeStreamOfDbl ms, AttributeStreamOfDbl position, + AttributeStreamOfInt32 paths, int polygon_start, int polygon_end, JsonWriter json_writer) { + json_writer.startArray(); + + int istart = paths.read(polygon_start); + int iend = paths.read(polygon_start + 1); + lineStringText_(true, true, precision, bFixedPoint, b_export_zs, b_export_ms, zs, ms, position, istart, iend, + json_writer); + + for (int path = polygon_start + 1; path < polygon_end; path++) { + istart = paths.read(path); + iend = paths.read(path + 1); + lineStringText_(true, true, precision, bFixedPoint, b_export_zs, b_export_ms, zs, ms, position, istart, + iend, json_writer); + } + + json_writer.endArray(); + } + + // Mirrors wkt + private static void lineStringText_(boolean bRing, boolean b_closed, int precision, boolean bFixedPoint, + boolean b_export_zs, boolean b_export_ms, AttributeStreamOfDbl zs, AttributeStreamOfDbl ms, + AttributeStreamOfDbl position, int istart, int iend, JsonWriter json_writer) { + if (istart == iend) { + json_writer.startArray(); + json_writer.endArray(); + return; + } + + json_writer.startArray(); + + if (bRing) { + pointText_(precision, bFixedPoint, b_export_zs, b_export_ms, zs, ms, position, istart, json_writer); + + for (int point = iend - 1; point >= istart + 1; point--) + pointText_(precision, bFixedPoint, b_export_zs, b_export_ms, zs, ms, position, point, json_writer); + + pointText_(precision, bFixedPoint, b_export_zs, b_export_ms, zs, ms, position, istart, json_writer); + } else { + for (int point = istart; point < iend - 1; point++) + pointText_(precision, bFixedPoint, b_export_zs, b_export_ms, zs, ms, position, point, json_writer); + + pointText_(precision, bFixedPoint, b_export_zs, b_export_ms, zs, ms, position, iend - 1, json_writer); + + if (b_closed) + pointText_(precision, bFixedPoint, b_export_zs, b_export_ms, zs, ms, position, istart, json_writer); + } + + json_writer.endArray(); + } + + // Mirrors wkt + private static int pointText_(int precision, boolean bFixedPoint, boolean b_export_zs, boolean b_export_ms, + double x, double y, double z, double m, JsonWriter json_writer) { + + json_writer.startArray(); + + json_writer.addValueDouble(x, precision, bFixedPoint); + json_writer.addValueDouble(y, precision, bFixedPoint); + + if (b_export_zs) + json_writer.addValueDouble(z, precision, bFixedPoint); + + if (b_export_ms) + json_writer.addValueDouble(m, precision, bFixedPoint); + + json_writer.endArray(); + + return 1; + } + + // Mirrors wkt + private static void pointText_(int precision, boolean bFixedPoint, boolean b_export_zs, boolean b_export_ms, + AttributeStreamOfDbl zs, AttributeStreamOfDbl ms, AttributeStreamOfDbl position, int point, + JsonWriter json_writer) { + double x = position.readAsDbl(2 * point); + double y = position.readAsDbl(2 * point + 1); + double z = NumberUtils.NaN(); + double m = NumberUtils.NaN(); + + if (b_export_zs) + z = (zs != null ? zs.readAsDbl(point) : VertexDescription.getDefaultValue(Semantics.Z)); + + if (b_export_ms) + m = (ms != null ? ms.readAsDbl(point) : VertexDescription.getDefaultValue(Semantics.M)); + + pointText_(precision, bFixedPoint, b_export_zs, b_export_ms, x, y, z, m, json_writer); + } + + // Mirrors wkt + private static void writeEnvelopeAsGeoJsonPolygon_(int precision, boolean bFixedPoint, boolean b_export_zs, + boolean b_export_ms, double xmin, double ymin, double xmax, double ymax, double zmin, double zmax, + double mmin, double mmax, JsonWriter json_writer) { + json_writer.startArray(); + json_writer.startArray(); + + json_writer.startArray(); + json_writer.addValueDouble(xmin, precision, bFixedPoint); + json_writer.addValueDouble(ymin, precision, bFixedPoint); + + if (b_export_zs) + json_writer.addValueDouble(zmin, precision, bFixedPoint); + + if (b_export_ms) + json_writer.addValueDouble(mmin, precision, bFixedPoint); + + json_writer.endArray(); + + json_writer.startArray(); + json_writer.addValueDouble(xmax, precision, bFixedPoint); + json_writer.addValueDouble(ymin, precision, bFixedPoint); + + if (b_export_zs) + json_writer.addValueDouble(zmax, precision, bFixedPoint); + + if (b_export_ms) + json_writer.addValueDouble(mmax, precision, bFixedPoint); + + json_writer.endArray(); + + json_writer.startArray(); + json_writer.addValueDouble(xmax, precision, bFixedPoint); + json_writer.addValueDouble(ymax, precision, bFixedPoint); + + if (b_export_zs) + json_writer.addValueDouble(zmin, precision, bFixedPoint); + + if (b_export_ms) + json_writer.addValueDouble(mmin, precision, bFixedPoint); + + json_writer.endArray(); + + json_writer.startArray(); + json_writer.addValueDouble(xmin, precision, bFixedPoint); + json_writer.addValueDouble(ymax, precision, bFixedPoint); + + if (b_export_zs) + json_writer.addValueDouble(zmax, precision, bFixedPoint); + + if (b_export_ms) + json_writer.addValueDouble(mmax, precision, bFixedPoint); + + json_writer.endArray(); + + json_writer.startArray(); + json_writer.addValueDouble(xmin, precision, bFixedPoint); + json_writer.addValueDouble(ymin, precision, bFixedPoint); + + if (b_export_zs) + json_writer.addValueDouble(zmin, precision, bFixedPoint); + + if (b_export_ms) + json_writer.addValueDouble(mmin, precision, bFixedPoint); + + json_writer.endArray(); - g.writeEndObject(); - g.close(); - } - - private void exportMultiPathToGeoJson(JsonGenerator g, MultiPathImpl pImpl, boolean bCollection) throws IOException { - int startIndex; - int vertices; - - if (bCollection) - g.writeStartArray(); - - //AttributeStreamOfDbl position = (AttributeStreamOfDbl) pImpl.getAttributeStreamRef(VertexDescription.Semantics.POSITION); - //AttributeStreamOfInt8 pathFlags = pImpl.getPathFlagsStreamRef(); - //AttributeStreamOfInt32 paths = pImpl.getPathStreamRef(); - int pathCount = pImpl.getPathCount(); - boolean isPolygon = pImpl.m_bPolygon; - AttributeStreamOfDbl zs = pImpl.hasAttribute(Semantics.Z) ? (AttributeStreamOfDbl) pImpl.getAttributeStreamRef(Semantics.Z) : null; - - for (int path = 0; path < pathCount; path++) { - startIndex = pImpl.getPathStart(path); - vertices = pImpl.getPathSize(path); - - boolean isExtRing = isPolygon && pImpl.isExteriorRing(path); - if (isExtRing) {//only for polygons - if (path > 0) - g.writeEndArray();//end of OGC polygon - - g.writeStartArray();//start of next OGC polygon - } - - writePath(pImpl, g, path, startIndex, vertices, zs); - } - - if (isPolygon) - g.writeEndArray();//end of last OGC polygon - - if (bCollection) - g.writeEndArray(); - } - - private void closePath(MultiPathImpl mp, JsonGenerator g, int startIndex, AttributeStreamOfDbl zs) throws IOException { - Point2D pt = new Point2D(); - - // close ring - mp.getXY(startIndex, pt); - g.writeStartArray(); - writeDouble(pt.x, g); - writeDouble(pt.y, g); - - if (zs != null) - writeDouble(zs.get(startIndex), g); - - g.writeEndArray(); - } - - private void writePath(MultiPathImpl mp, JsonGenerator g, int pathIndex, int startIndex, int vertices, AttributeStreamOfDbl zs) throws IOException { - Point2D pt = new Point2D(); - - g.writeStartArray(); - - for (int i = startIndex; i < startIndex + vertices; i++) { - mp.getXY(i, pt); - g.writeStartArray(); - writeDouble(pt.x, g); - writeDouble(pt.y, g); - - if (zs != null) - writeDouble(zs.get(i), g); - - g.writeEndArray(); - } - - if (mp.isClosedPath(pathIndex)) - closePath(mp, g, startIndex, zs); - - g.writeEndArray(); - } - - private void exportEnvelopeToGeoJson(JsonGenerator g, Envelope e) throws JsonGenerationException, IOException { - boolean empty = e.isEmpty(); - - g.writeStartObject(); - g.writeFieldName("bbox"); - - if (empty) { - g.writeNull(); - } else { - g.writeStartArray(); - writeDouble(e.getXMin(), g); - writeDouble(e.getYMin(), g); - writeDouble(e.getXMax(), g); - writeDouble(e.getYMax(), g); - g.writeEndArray(); - } - - g.writeEndObject(); - g.close(); - } - - private void writeDouble(double d, JsonGenerator g) throws IOException, JsonGenerationException { - if (NumberUtils.isNaN(d)) { - g.writeNull(); - } else { - g.writeNumber(d); - } - - return; - } + json_writer.endArray(); + json_writer.endArray(); + } } diff --git a/src/main/java/com/esri/core/geometry/OperatorExportToGeoJsonLocal.java b/src/main/java/com/esri/core/geometry/OperatorExportToGeoJsonLocal.java index 0abbadb5..9da02767 100644 --- a/src/main/java/com/esri/core/geometry/OperatorExportToGeoJsonLocal.java +++ b/src/main/java/com/esri/core/geometry/OperatorExportToGeoJsonLocal.java @@ -3,7 +3,7 @@ you may not use this file except in compliance with the License. You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 + http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, @@ -23,29 +23,28 @@ package com.esri.core.geometry; class OperatorExportToGeoJsonLocal extends OperatorExportToGeoJson { - @Override - public JsonCursor execute(SpatialReference spatialReference, GeometryCursor geometryCursor) { - return new OperatorExportToGeoJsonCursor(false, spatialReference, geometryCursor); - } - - @Override - public String execute(SpatialReference spatialReference, Geometry geometry) { - SimpleGeometryCursor gc = new SimpleGeometryCursor(geometry); - JsonCursor cursor = new OperatorExportToGeoJsonCursor(false, spatialReference, gc); - return cursor.next(); - } - - @Override - public String execute(int exportFlags, SpatialReference spatialReference, Geometry geometry) { - SimpleGeometryCursor gc = new SimpleGeometryCursor(geometry); - JsonCursor cursor = new OperatorExportToGeoJsonCursor(exportFlags == GeoJsonExportFlags.geoJsonExportPreferMultiGeometry, spatialReference, gc); - return cursor.next(); - } - - @Override - public String execute(Geometry geometry) { - SimpleGeometryCursor gc = new SimpleGeometryCursor(geometry); - JsonCursor cursor = new OperatorExportToGeoJsonCursor(gc); - return cursor.next(); - } + @Override + public JsonCursor execute(SpatialReference spatialReference, GeometryCursor geometryCursor) { + return new OperatorExportToGeoJsonCursor(GeoJsonExportFlags.geoJsonExportDefaults, spatialReference, geometryCursor); + } + + @Override + public String execute(SpatialReference spatialReference, Geometry geometry) { + return OperatorExportToGeoJsonCursor.exportToGeoJson(GeoJsonExportFlags.geoJsonExportDefaults, geometry, spatialReference); + } + + @Override + public String execute(int exportFlags, SpatialReference spatialReference, Geometry geometry) { + return OperatorExportToGeoJsonCursor.exportToGeoJson(exportFlags, geometry, spatialReference); + } + + @Override + public String execute(Geometry geometry) { + return OperatorExportToGeoJsonCursor.exportToGeoJson(GeoJsonExportFlags.geoJsonExportSkipCRS, geometry, null); + } + + @Override + public String exportSpatialReference(int export_flags, SpatialReference spatial_reference) { + return OperatorExportToGeoJsonCursor.exportSpatialReference(export_flags, spatial_reference); + } } diff --git a/src/main/java/com/esri/core/geometry/OperatorExportToJsonCursor.java b/src/main/java/com/esri/core/geometry/OperatorExportToJsonCursor.java index b14fbb3e..9d24be43 100644 --- a/src/main/java/com/esri/core/geometry/OperatorExportToJsonCursor.java +++ b/src/main/java/com/esri/core/geometry/OperatorExportToJsonCursor.java @@ -24,442 +24,436 @@ package com.esri.core.geometry; import com.esri.core.geometry.VertexDescription.Semantics; + import java.io.IOException; import java.util.Map; class OperatorExportToJsonCursor extends JsonCursor { - GeometryCursor m_inputGeometryCursor; - SpatialReference m_spatialReference; - int m_index; - - public OperatorExportToJsonCursor(SpatialReference spatialReference, - GeometryCursor geometryCursor) { - m_index = -1; - if (geometryCursor == null) { - throw new IllegalArgumentException(); - } - - m_inputGeometryCursor = geometryCursor; - m_spatialReference = spatialReference; - } - - @Override - public int getID() { - return m_index; - } - - @Override - public String next() { - Geometry geometry; - if ((geometry = m_inputGeometryCursor.next()) != null) { - m_index = m_inputGeometryCursor.getGeometryID(); - return exportToString(geometry, m_spatialReference, null); - } - return null; - } - - static String exportToString(Geometry geometry, SpatialReference spatialReference, Map exportProperties) { - JsonWriter jsonWriter = new JsonStringWriter(); - exportToJson_(geometry, spatialReference, jsonWriter, exportProperties); - return (String) jsonWriter.getJson(); - } - - private static void exportToJson_(Geometry geometry, SpatialReference spatialReference, JsonWriter jsonWriter, Map exportProperties) { - try { - int type = geometry.getType().value(); - switch (type) { - case Geometry.GeometryType.Point: - exportPointToJson((Point) geometry, spatialReference, jsonWriter, exportProperties); - break; - - case Geometry.GeometryType.MultiPoint: - exportMultiPointToJson((MultiPoint) geometry, spatialReference, jsonWriter, exportProperties); - break; - - case Geometry.GeometryType.Polyline: - exportPolylineToJson((Polyline) geometry, spatialReference, jsonWriter, exportProperties); - break; - - case Geometry.GeometryType.Polygon: - exportPolygonToJson((Polygon) geometry, spatialReference, jsonWriter, exportProperties); - break; - - case Geometry.GeometryType.Envelope: - exportEnvelopeToJson((Envelope) geometry, spatialReference, jsonWriter, exportProperties); - break; - - default: - throw new RuntimeException( - "not implemented for this geometry type"); - } - - } catch (Exception e) { - e.printStackTrace(); - } - - } - - private static void exportPolygonToJson(Polygon pp, SpatialReference spatialReference, JsonWriter jsonWriter, Map exportProperties) { - exportPolypathToJson(pp, "rings", spatialReference, jsonWriter, exportProperties); - } - - private static void exportPolylineToJson(Polyline pp, SpatialReference spatialReference, JsonWriter jsonWriter, Map exportProperties) { - exportPolypathToJson(pp, "paths", spatialReference, jsonWriter, exportProperties); - } - - private static void exportPolypathToJson(MultiPath pp, String name, SpatialReference spatialReference, JsonWriter jsonWriter, Map exportProperties) { - boolean bExportZs = pp.hasAttribute(Semantics.Z); - boolean bExportMs = pp.hasAttribute(Semantics.M); - - boolean bPositionAsF = false; - int decimals = 17; - - if (exportProperties != null) { - Object numberOfDecimalsXY = exportProperties.get("numberOfDecimalsXY"); - if (numberOfDecimalsXY != null && numberOfDecimalsXY instanceof Number) { - bPositionAsF = true; - decimals = ((Number) numberOfDecimalsXY).intValue(); - } - } - - jsonWriter.startObject(); - - if (bExportZs) { - jsonWriter.addPairBoolean("hasZ", true); - } - - if (bExportMs) { - jsonWriter.addPairBoolean("hasM", true); - } - - jsonWriter.addPairArray(name); - - if (!pp.isEmpty()) { - int n = pp.getPathCount(); // rings or paths - - MultiPathImpl mpImpl = (MultiPathImpl) pp._getImpl();// get impl for - // faster - // access - AttributeStreamOfDbl zs = null; - AttributeStreamOfDbl ms = null; - - if (bExportZs) { - zs = (AttributeStreamOfDbl) mpImpl - .getAttributeStreamRef(Semantics.Z); - } - - if (bExportMs) { - ms = (AttributeStreamOfDbl) mpImpl - .getAttributeStreamRef(Semantics.M); - } - - boolean bPolygon = pp instanceof Polygon; - Point2D pt = new Point2D(); - - for (int i = 0; i < n; i++) { - jsonWriter.addValueArray(); - int startindex = pp.getPathStart(i); - int numVertices = pp.getPathSize(i); - double startx = 0.0, starty = 0.0, startz = NumberUtils.NaN(), startm = NumberUtils.NaN(); - double z = NumberUtils.NaN(), m = NumberUtils.NaN(); - boolean bClosed = pp.isClosedPath(i); - for (int j = startindex; j < startindex + numVertices; j++) { - pp.getXY(j, pt); - - jsonWriter.addValueArray(); - - if (bPositionAsF) { - jsonWriter.addValueDoubleF(pt.x, decimals); - jsonWriter.addValueDoubleF(pt.y, decimals); - } else { - jsonWriter.addValueDouble(pt.x); - jsonWriter.addValueDouble(pt.y); - } - - if (bExportZs) { - z = zs.get(j); - jsonWriter.addValueDouble(z); - } - - if (bExportMs) { - m = ms.get(j); - jsonWriter.addValueDouble(m); - } - - if (j == startindex && bClosed) { - startx = pt.x; - starty = pt.y; - startz = z; - startm = m; - } - - jsonWriter.endArray(); - } - - // Close the Path/Ring by writing the Point at the start index - if (bClosed && (startx != pt.x || starty != pt.y || (bExportZs && !(NumberUtils.isNaN(startz) && NumberUtils.isNaN(z)) && startz != z) || (bExportMs && !(NumberUtils.isNaN(startm) && NumberUtils.isNaN(m)) && startm != m))) { - pp.getXY(startindex, pt); - // getPoint(startindex); - jsonWriter.addValueArray(); - - if (bPositionAsF) { - jsonWriter.addValueDoubleF(pt.x, decimals); - jsonWriter.addValueDoubleF(pt.y, decimals); - } else { - jsonWriter.addValueDouble(pt.x); - jsonWriter.addValueDouble(pt.y); - } - - if (bExportZs) { - z = zs.get(startindex); - jsonWriter.addValueDouble(z); - } - - if (bExportMs) { - m = ms.get(startindex); - jsonWriter.addValueDouble(m); - } - - jsonWriter.endArray(); - } - - jsonWriter.endArray(); - } - } - - jsonWriter.endArray(); - - if (spatialReference != null) { - writeSR(spatialReference, jsonWriter); - } - - jsonWriter.endObject(); - } - - private static void exportMultiPointToJson(MultiPoint mpt, SpatialReference spatialReference, JsonWriter jsonWriter, Map exportProperties) { - boolean bExportZs = mpt.hasAttribute(Semantics.Z); - boolean bExportMs = mpt.hasAttribute(Semantics.M); - - boolean bPositionAsF = false; - int decimals = 17; - - if (exportProperties != null) { - Object numberOfDecimalsXY = exportProperties.get("numberOfDecimalsXY"); - if (numberOfDecimalsXY != null && numberOfDecimalsXY instanceof Number) { - bPositionAsF = true; - decimals = ((Number) numberOfDecimalsXY).intValue(); - } - } - - jsonWriter.startObject(); - - if (bExportZs) { - jsonWriter.addPairBoolean("hasZ", true); - } - - if (bExportMs) { - jsonWriter.addPairBoolean("hasM", true); - } - - jsonWriter.addPairArray("points"); - - if (!mpt.isEmpty()) { - MultiPointImpl mpImpl = (MultiPointImpl) mpt._getImpl();// get impl - // for - // faster - // access - AttributeStreamOfDbl zs = null; - AttributeStreamOfDbl ms = null; - - if (bExportZs) { - zs = (AttributeStreamOfDbl) mpImpl - .getAttributeStreamRef(Semantics.Z); - } - - if (bExportMs) { - ms = (AttributeStreamOfDbl) mpImpl - .getAttributeStreamRef(Semantics.M); - } - - Point2D pt = new Point2D(); - int n = mpt.getPointCount(); - for (int i = 0; i < n; i++) { - mpt.getXY(i, pt); - - jsonWriter.addValueArray(); - - if (bPositionAsF) { - jsonWriter.addValueDoubleF(pt.x, decimals); - jsonWriter.addValueDoubleF(pt.y, decimals); - } else { - jsonWriter.addValueDouble(pt.x); - jsonWriter.addValueDouble(pt.y); - } - - if (bExportZs) { - double z = zs.get(i); - jsonWriter.addValueDouble(z); - } - - if (bExportMs) { - double m = ms.get(i); - jsonWriter.addValueDouble(m); - } - - jsonWriter.endArray(); - } - } - - jsonWriter.endArray(); - - if (spatialReference != null) { - writeSR(spatialReference, jsonWriter); - } - - jsonWriter.endObject(); - } - - private static void exportPointToJson(Point pt, SpatialReference spatialReference, JsonWriter jsonWriter, Map exportProperties) { - boolean bExportZs = pt.hasAttribute(Semantics.Z); - boolean bExportMs = pt.hasAttribute(Semantics.M); - - boolean bPositionAsF = false; - int decimals = 17; - - if (exportProperties != null) { - Object numberOfDecimalsXY = exportProperties.get("numberOfDecimalsXY"); - if (numberOfDecimalsXY != null && numberOfDecimalsXY instanceof Number) { - bPositionAsF = true; - decimals = ((Number) numberOfDecimalsXY).intValue(); - } - } - - jsonWriter.startObject(); - - if (pt.isEmpty()) { - jsonWriter.addPairNull("x"); - jsonWriter.addPairNull("y"); - - if (bExportZs) { - jsonWriter.addPairNull("z"); - } - - if (bExportMs) { - jsonWriter.addPairNull("m"); - } - } else { - - if (bPositionAsF) { - jsonWriter.addPairDoubleF("x", pt.getX(), decimals); - jsonWriter.addPairDoubleF("y", pt.getY(), decimals); - } else { - jsonWriter.addPairDouble("x", pt.getX()); - jsonWriter.addPairDouble("y", pt.getY()); - } - - if (bExportZs) { - jsonWriter.addPairDouble("z", pt.getZ()); - } - - if (bExportMs) { - jsonWriter.addPairDouble("m", pt.getM()); - } - } - - if (spatialReference != null) { - writeSR(spatialReference, jsonWriter); - } - - jsonWriter.endObject(); - } - - private static void exportEnvelopeToJson(Envelope env, SpatialReference spatialReference, JsonWriter jsonWriter, Map exportProperties) { - boolean bExportZs = env.hasAttribute(Semantics.Z); - boolean bExportMs = env.hasAttribute(Semantics.M); - - boolean bPositionAsF = false; - int decimals = 17; - - if (exportProperties != null) { - Object numberOfDecimalsXY = exportProperties.get("numberOfDecimalsXY"); - if (numberOfDecimalsXY != null && numberOfDecimalsXY instanceof Number) { - bPositionAsF = true; - decimals = ((Number) numberOfDecimalsXY).intValue(); - } - } - - jsonWriter.startObject(); - - if (env.isEmpty()) { - jsonWriter.addPairNull("xmin"); - jsonWriter.addPairNull("ymin"); - jsonWriter.addPairNull("xmax"); - jsonWriter.addPairNull("ymax"); - - if (bExportZs) { - jsonWriter.addPairNull("zmin"); - jsonWriter.addPairNull("zmax"); - } - - if (bExportMs) { - jsonWriter.addPairNull("mmin"); - jsonWriter.addPairNull("mmax"); - } - } else { - - if (bPositionAsF) { - jsonWriter.addPairDoubleF("xmin", env.getXMin(), decimals); - jsonWriter.addPairDoubleF("ymin", env.getYMin(), decimals); - jsonWriter.addPairDoubleF("xmax", env.getXMax(), decimals); - jsonWriter.addPairDoubleF("ymax", env.getYMax(), decimals); - } else { - jsonWriter.addPairDouble("xmin", env.getXMin()); - jsonWriter.addPairDouble("ymin", env.getYMin()); - jsonWriter.addPairDouble("xmax", env.getXMax()); - jsonWriter.addPairDouble("ymax", env.getYMax()); - } - - if (bExportZs) { - Envelope1D z = env.queryInterval(Semantics.Z, 0); - jsonWriter.addPairDouble("zmin", z.vmin); - jsonWriter.addPairDouble("zmax", z.vmax); - } - - if (bExportMs) { - Envelope1D m = env.queryInterval(Semantics.M, 0); - jsonWriter.addPairDouble("mmin", m.vmin); - jsonWriter.addPairDouble("mmax", m.vmax); - } - } - - if (spatialReference != null) { - writeSR(spatialReference, jsonWriter); - } - - jsonWriter.endObject(); - } - - private static void writeSR(SpatialReference spatialReference, JsonWriter jsonWriter) { - int wkid = spatialReference.getOldID(); - if (wkid > 0) { - jsonWriter.addPairObject("spatialReference"); - - jsonWriter.addPairInt("wkid", wkid); - - int latest_wkid = spatialReference.getLatestID(); - if (latest_wkid > 0 && latest_wkid != wkid) { - jsonWriter.addPairInt("latestWkid", latest_wkid); - } - - jsonWriter.endObject(); - } else { - String wkt = spatialReference.getText(); - if (wkt != null) { - jsonWriter.addPairObject("spatialReference"); - jsonWriter.addPairString("wkt", wkt); - jsonWriter.endObject(); - } - } - } + GeometryCursor m_inputGeometryCursor; + SpatialReference m_spatialReference; + int m_index; + + public OperatorExportToJsonCursor(SpatialReference spatialReference, GeometryCursor geometryCursor) { + m_index = -1; + if (geometryCursor == null) { + throw new IllegalArgumentException(); + } + + m_inputGeometryCursor = geometryCursor; + m_spatialReference = spatialReference; + } + + @Override + public int getID() { + return m_index; + } + + @Override + public String next() { + Geometry geometry; + if ((geometry = m_inputGeometryCursor.next()) != null) { + m_index = m_inputGeometryCursor.getGeometryID(); + return exportToString(geometry, m_spatialReference, null); + } + return null; + } + + static String exportToString(Geometry geometry, SpatialReference spatialReference, Map exportProperties) { + JsonWriter jsonWriter = new JsonStringWriter(); + exportToJson_(geometry, spatialReference, jsonWriter, exportProperties); + return (String) jsonWriter.getJson(); + } + + private static void exportToJson_(Geometry geometry, SpatialReference spatialReference, JsonWriter jsonWriter, Map exportProperties) { + try { + int type = geometry.getType().value(); + switch (type) { + case Geometry.GeometryType.Point: + exportPointToJson((Point) geometry, spatialReference, jsonWriter, exportProperties); + break; + + case Geometry.GeometryType.MultiPoint: + exportMultiPointToJson((MultiPoint) geometry, spatialReference, jsonWriter, exportProperties); + break; + + case Geometry.GeometryType.Polyline: + exportPolylineToJson((Polyline) geometry, spatialReference, jsonWriter, exportProperties); + break; + + case Geometry.GeometryType.Polygon: + exportPolygonToJson((Polygon) geometry, spatialReference, jsonWriter, exportProperties); + break; + + case Geometry.GeometryType.Envelope: + exportEnvelopeToJson((Envelope) geometry, spatialReference, jsonWriter, exportProperties); + break; + + default: + throw new RuntimeException("not implemented for this geometry type"); + } + + } catch (Exception e) { + } + + } + + private static void exportPolygonToJson(Polygon pp, SpatialReference spatialReference, JsonWriter jsonWriter, Map exportProperties) { + exportPolypathToJson(pp, "rings", spatialReference, jsonWriter, exportProperties); + } + + private static void exportPolylineToJson(Polyline pp, SpatialReference spatialReference, JsonWriter jsonWriter, Map exportProperties) { + exportPolypathToJson(pp, "paths", spatialReference, jsonWriter, exportProperties); + } + + private static void exportPolypathToJson(MultiPath pp, String name, SpatialReference spatialReference, JsonWriter jsonWriter, Map exportProperties) { + boolean bExportZs = pp.hasAttribute(Semantics.Z); + boolean bExportMs = pp.hasAttribute(Semantics.M); + + boolean bPositionAsF = false; + int decimals = 17; + + if (exportProperties != null) { + Object numberOfDecimalsXY = exportProperties.get("numberOfDecimalsXY"); + if (numberOfDecimalsXY != null && numberOfDecimalsXY instanceof Number) { + bPositionAsF = true; + decimals = ((Number) numberOfDecimalsXY).intValue(); + } + } + + jsonWriter.startObject(); + + if (bExportZs) { + jsonWriter.addPairBoolean("hasZ", true); + } + + if (bExportMs) { + jsonWriter.addPairBoolean("hasM", true); + } + + jsonWriter.addPairArray(name); + + if (!pp.isEmpty()) { + int n = pp.getPathCount(); // rings or paths + + MultiPathImpl mpImpl = (MultiPathImpl) pp._getImpl();// get impl for + // faster + // access + AttributeStreamOfDbl zs = null; + AttributeStreamOfDbl ms = null; + + if (bExportZs) { + zs = (AttributeStreamOfDbl) mpImpl.getAttributeStreamRef(Semantics.Z); + } + + if (bExportMs) { + ms = (AttributeStreamOfDbl) mpImpl.getAttributeStreamRef(Semantics.M); + } + + boolean bPolygon = pp instanceof Polygon; + Point2D pt = new Point2D(); + + for (int i = 0; i < n; i++) { + jsonWriter.addValueArray(); + int startindex = pp.getPathStart(i); + int numVertices = pp.getPathSize(i); + double startx = 0.0, starty = 0.0, startz = NumberUtils.NaN(), startm = NumberUtils.NaN(); + double z = NumberUtils.NaN(), m = NumberUtils.NaN(); + boolean bClosed = pp.isClosedPath(i); + for (int j = startindex; j < startindex + numVertices; j++) { + pp.getXY(j, pt); + + jsonWriter.addValueArray(); + + if (bPositionAsF) { + jsonWriter.addValueDouble(pt.x, decimals, true); + jsonWriter.addValueDouble(pt.y, decimals, true); + } else { + jsonWriter.addValueDouble(pt.x); + jsonWriter.addValueDouble(pt.y); + } + + if (bExportZs) { + z = zs.get(j); + jsonWriter.addValueDouble(z); + } + + if (bExportMs) { + m = ms.get(j); + jsonWriter.addValueDouble(m); + } + + if (j == startindex && bClosed) { + startx = pt.x; + starty = pt.y; + startz = z; + startm = m; + } + + jsonWriter.endArray(); + } + + // Close the Path/Ring by writing the Point at the start index + if (bClosed && (startx != pt.x || starty != pt.y || (bExportZs && !(NumberUtils.isNaN(startz) && NumberUtils.isNaN(z)) && startz != z) || (bExportMs && !(NumberUtils.isNaN(startm) && NumberUtils.isNaN(m)) && startm != m))) { + pp.getXY(startindex, pt); + // getPoint(startindex); + jsonWriter.addValueArray(); + + if (bPositionAsF) { + jsonWriter.addValueDouble(pt.x, decimals, true); + jsonWriter.addValueDouble(pt.y, decimals, true); + } else { + jsonWriter.addValueDouble(pt.x); + jsonWriter.addValueDouble(pt.y); + } + + if (bExportZs) { + z = zs.get(startindex); + jsonWriter.addValueDouble(z); + } + + if (bExportMs) { + m = ms.get(startindex); + jsonWriter.addValueDouble(m); + } + + jsonWriter.endArray(); + } + + jsonWriter.endArray(); + } + } + + jsonWriter.endArray(); + + if (spatialReference != null) { + writeSR(spatialReference, jsonWriter); + } + + jsonWriter.endObject(); + } + + private static void exportMultiPointToJson(MultiPoint mpt, SpatialReference spatialReference, JsonWriter jsonWriter, Map exportProperties) { + boolean bExportZs = mpt.hasAttribute(Semantics.Z); + boolean bExportMs = mpt.hasAttribute(Semantics.M); + + boolean bPositionAsF = false; + int decimals = 17; + + if (exportProperties != null) { + Object numberOfDecimalsXY = exportProperties.get("numberOfDecimalsXY"); + if (numberOfDecimalsXY != null && numberOfDecimalsXY instanceof Number) { + bPositionAsF = true; + decimals = ((Number) numberOfDecimalsXY).intValue(); + } + } + + jsonWriter.startObject(); + + if (bExportZs) { + jsonWriter.addPairBoolean("hasZ", true); + } + + if (bExportMs) { + jsonWriter.addPairBoolean("hasM", true); + } + + jsonWriter.addPairArray("points"); + + if (!mpt.isEmpty()) { + MultiPointImpl mpImpl = (MultiPointImpl) mpt._getImpl();// get impl + // for + // faster + // access + AttributeStreamOfDbl zs = null; + AttributeStreamOfDbl ms = null; + + if (bExportZs) { + zs = (AttributeStreamOfDbl) mpImpl.getAttributeStreamRef(Semantics.Z); + } + + if (bExportMs) { + ms = (AttributeStreamOfDbl) mpImpl.getAttributeStreamRef(Semantics.M); + } + + Point2D pt = new Point2D(); + int n = mpt.getPointCount(); + for (int i = 0; i < n; i++) { + mpt.getXY(i, pt); + + jsonWriter.addValueArray(); + + if (bPositionAsF) { + jsonWriter.addValueDouble(pt.x, decimals, true); + jsonWriter.addValueDouble(pt.y, decimals, true); + } else { + jsonWriter.addValueDouble(pt.x); + jsonWriter.addValueDouble(pt.y); + } + + if (bExportZs) { + double z = zs.get(i); + jsonWriter.addValueDouble(z); + } + + if (bExportMs) { + double m = ms.get(i); + jsonWriter.addValueDouble(m); + } + + jsonWriter.endArray(); + } + } + + jsonWriter.endArray(); + + if (spatialReference != null) { + writeSR(spatialReference, jsonWriter); + } + + jsonWriter.endObject(); + } + + private static void exportPointToJson(Point pt, SpatialReference spatialReference, JsonWriter jsonWriter, Map exportProperties) { + boolean bExportZs = pt.hasAttribute(Semantics.Z); + boolean bExportMs = pt.hasAttribute(Semantics.M); + + boolean bPositionAsF = false; + int decimals = 17; + + if (exportProperties != null) { + Object numberOfDecimalsXY = exportProperties.get("numberOfDecimalsXY"); + if (numberOfDecimalsXY != null && numberOfDecimalsXY instanceof Number) { + bPositionAsF = true; + decimals = ((Number) numberOfDecimalsXY).intValue(); + } + } + + jsonWriter.startObject(); + + if (pt.isEmpty()) { + jsonWriter.addPairNull("x"); + jsonWriter.addPairNull("y"); + + if (bExportZs) { + jsonWriter.addPairNull("z"); + } + + if (bExportMs) { + jsonWriter.addPairNull("m"); + } + } else { + + if (bPositionAsF) { + jsonWriter.addPairDouble("x", pt.getX(), decimals, true); + jsonWriter.addPairDouble("y", pt.getY(), decimals, true); + } else { + jsonWriter.addPairDouble("x", pt.getX()); + jsonWriter.addPairDouble("y", pt.getY()); + } + + if (bExportZs) { + jsonWriter.addPairDouble("z", pt.getZ()); + } + + if (bExportMs) { + jsonWriter.addPairDouble("m", pt.getM()); + } + } + + if (spatialReference != null) { + writeSR(spatialReference, jsonWriter); + } + + jsonWriter.endObject(); + } + + private static void exportEnvelopeToJson(Envelope env, SpatialReference spatialReference, JsonWriter jsonWriter, Map exportProperties) { + boolean bExportZs = env.hasAttribute(Semantics.Z); + boolean bExportMs = env.hasAttribute(Semantics.M); + + boolean bPositionAsF = false; + int decimals = 17; + + if (exportProperties != null) { + Object numberOfDecimalsXY = exportProperties.get("numberOfDecimalsXY"); + if (numberOfDecimalsXY != null && numberOfDecimalsXY instanceof Number) { + bPositionAsF = true; + decimals = ((Number) numberOfDecimalsXY).intValue(); + } + } + + jsonWriter.startObject(); + + if (env.isEmpty()) { + jsonWriter.addPairNull("xmin"); + jsonWriter.addPairNull("ymin"); + jsonWriter.addPairNull("xmax"); + jsonWriter.addPairNull("ymax"); + + if (bExportZs) { + jsonWriter.addPairNull("zmin"); + jsonWriter.addPairNull("zmax"); + } + + if (bExportMs) { + jsonWriter.addPairNull("mmin"); + jsonWriter.addPairNull("mmax"); + } + } else { + + if (bPositionAsF) { + jsonWriter.addPairDouble("xmin", env.getXMin(), decimals, true); + jsonWriter.addPairDouble("ymin", env.getYMin(), decimals, true); + jsonWriter.addPairDouble("xmax", env.getXMax(), decimals, true); + jsonWriter.addPairDouble("ymax", env.getYMax(), decimals, true); + } else { + jsonWriter.addPairDouble("xmin", env.getXMin()); + jsonWriter.addPairDouble("ymin", env.getYMin()); + jsonWriter.addPairDouble("xmax", env.getXMax()); + jsonWriter.addPairDouble("ymax", env.getYMax()); + } + + if (bExportZs) { + Envelope1D z = env.queryInterval(Semantics.Z, 0); + jsonWriter.addPairDouble("zmin", z.vmin); + jsonWriter.addPairDouble("zmax", z.vmax); + } + + if (bExportMs) { + Envelope1D m = env.queryInterval(Semantics.M, 0); + jsonWriter.addPairDouble("mmin", m.vmin); + jsonWriter.addPairDouble("mmax", m.vmax); + } + } + + if (spatialReference != null) { + writeSR(spatialReference, jsonWriter); + } + + jsonWriter.endObject(); + } + + private static void writeSR(SpatialReference spatialReference, JsonWriter jsonWriter) { + int wkid = spatialReference.getOldID(); + if (wkid > 0) { + jsonWriter.addPairObject("spatialReference"); + + jsonWriter.addPairInt("wkid", wkid); + + int latest_wkid = spatialReference.getLatestID(); + if (latest_wkid > 0 && latest_wkid != wkid) { + jsonWriter.addPairInt("latestWkid", latest_wkid); + } + + jsonWriter.endObject(); + } else { + String wkt = spatialReference.getText(); + if (wkt != null) { + jsonWriter.addPairObject("spatialReference"); + jsonWriter.addPairString("wkt", wkt); + jsonWriter.endObject(); + } + } + } } diff --git a/src/main/java/com/esri/core/geometry/OperatorFactoryLocal.java b/src/main/java/com/esri/core/geometry/OperatorFactoryLocal.java index f07cea96..160bcffd 100644 --- a/src/main/java/com/esri/core/geometry/OperatorFactoryLocal.java +++ b/src/main/java/com/esri/core/geometry/OperatorFactoryLocal.java @@ -25,9 +25,11 @@ package com.esri.core.geometry; import com.esri.core.geometry.Operator.Type; + import java.io.BufferedReader; import java.io.FileInputStream; import java.io.FileOutputStream; +import java.io.IOException; import java.io.InputStreamReader; import java.io.PrintStream; import java.io.Reader; @@ -35,7 +37,9 @@ import java.nio.ByteOrder; import java.nio.channels.FileChannel; import java.util.HashMap; + import org.codehaus.jackson.JsonFactory; +import org.codehaus.jackson.JsonParseException; import org.codehaus.jackson.JsonParser; /** @@ -209,6 +213,20 @@ public static MapGeometry loadGeometryFromJSONFileDbg(String file_name) { return mapGeom; } + public static MapGeometry loadGeometryFromJSONStringDbg(String json) { + if (json == null) { + throw new IllegalArgumentException(); + } + + MapGeometry mapGeom = null; + try { + mapGeom = OperatorImportFromJson.local().execute(Geometry.Type.Unknown, json); + } catch (Exception e) { + throw new IllegalArgumentException(e.toString()); + } + return mapGeom; + } + public static Geometry loadGeometryFromEsriShapeDbg(String file_name) { if (file_name == null) { throw new IllegalArgumentException(); diff --git a/src/main/java/com/esri/core/geometry/OperatorGeodesicBuffer.java b/src/main/java/com/esri/core/geometry/OperatorGeodesicBuffer.java index 4e7068f7..03cbae60 100644 --- a/src/main/java/com/esri/core/geometry/OperatorGeodesicBuffer.java +++ b/src/main/java/com/esri/core/geometry/OperatorGeodesicBuffer.java @@ -33,7 +33,7 @@ public Operator.Type getType() { /** * Creates a geodesic buffer around the input geometries * - * @param input_geometries The geometries to buffer. + * @param inputGeometries The geometries to buffer. * @param sr The Spatial_reference of the Geometries. * @param curveType The geodetic curve type of the segments. If the curve_type is Geodetic_curve::shape_preserving, then the segments are densified in the projection where they are defined before * buffering. @@ -43,13 +43,15 @@ public Operator.Type getType() { * default deviation. * @param bReserved Must be false. Reserved for future development. Will throw an exception if not false. * @param bUnion If True, the buffered geometries will be unioned, otherwise they wont be unioned. + * @param progressTracker Can be null. Allows to cancel lengthy operation. + * @return Geometry cursor over result buffers. */ abstract public GeometryCursor execute(GeometryCursor inputGeometries, SpatialReference sr, int curveType, double[] distancesMeters, double maxDeviationMeters, boolean bReserved, boolean bUnion, ProgressTracker progressTracker); /** * Creates a geodesic buffer around the input geometry * - * @param input_geometry The geometry to buffer. + * @param inputGeometry The geometry to buffer. * @param sr The Spatial_reference of the Geometry. * @param curveType The geodetic curve type of the segments. If the curve_type is Geodetic_curve::shape_preserving, then the segments are densified in the projection where they are defined before * buffering. @@ -57,6 +59,8 @@ public Operator.Type getType() { * @param maxDeviationMeters The deviation offset to use for convergence. The geodesic arcs of the resulting buffer will be closer than the max deviation of the true buffer. Pass in NaN to use the * default deviation. * @param bReserved Must be false. Reserved for future development. Will throw an exception if not false. + * @param progressTracker Can be null. Allows to cancel lengthy operation. + * @return Returns result buffer. */ abstract public Geometry execute(Geometry inputGeometry, SpatialReference sr, int curveType, double distanceMeters, double maxDeviationMeters, boolean bReserved, ProgressTracker progressTracker); diff --git a/src/main/java/com/esri/core/geometry/OperatorGeodeticDensifyByLength.java b/src/main/java/com/esri/core/geometry/OperatorGeodeticDensifyByLength.java index 5efcb134..ca2d1087 100644 --- a/src/main/java/com/esri/core/geometry/OperatorGeodeticDensifyByLength.java +++ b/src/main/java/com/esri/core/geometry/OperatorGeodeticDensifyByLength.java @@ -42,7 +42,7 @@ public Type getType() { * @param maxSegmentLengthMeters The maximum segment length (in meters) allowed. Must be a positive value. * @param sr The SpatialReference of the Geometry. * @param curveType The interpretation of a line connecting two points. - * @return Returns the densified geometries (It does nothing to geometries with dim < 1, but simply passes them along). + * @return Returns the densified geometries (It does nothing to geometries with dim less than 1, but simply passes them along). * * Note the behavior is not determined for any geodetic curve segments that connect two poles, or for loxodrome segments that connect to any pole. */ diff --git a/src/main/java/com/esri/core/geometry/OperatorImportFromGeoJson.java b/src/main/java/com/esri/core/geometry/OperatorImportFromGeoJson.java index 2c0c0287..88cb4653 100644 --- a/src/main/java/com/esri/core/geometry/OperatorImportFromGeoJson.java +++ b/src/main/java/com/esri/core/geometry/OperatorImportFromGeoJson.java @@ -24,8 +24,12 @@ package com.esri.core.geometry; import org.json.JSONException; +import org.json.JSONObject; + +import java.io.IOException; public abstract class OperatorImportFromGeoJson extends Operator { + @Override public Type getType() { return Type.ImportFromGeoJson; @@ -33,29 +37,40 @@ public Type getType() { /** * Performs the ImportFromGeoJson operation. + * + * @param type Use the {@link Geometry.Type} enum. + * @param jsonObject The JSONObject holding the geometry and spatial reference. + * @return Returns the imported MapGeometry. + * @throws JsonGeometryException + */ + public abstract MapGeometry execute(int importFlags, Geometry.Type type, JSONObject jsonObject, ProgressTracker progressTracker) throws JSONException; + + /** + * Deprecated, use version without import_flags. + * + * Performs the ImportFromGeoJson operation. + * * @param import_flags Use the {@link GeoJsonImportFlags} interface. - * @param type Use the {@link Geometry.Type} enum. + * @param type Use the {@link Geometry.Type} enum. * @param geoJsonString The string holding the Geometry in geoJson format. - * @return Returns the imported Geometry. - * @throws JSONException + * @return Returns the imported MapGeometry. + * @throws JSONException + * */ - public abstract MapGeometry execute(int import_flags, Geometry.Type type, - String geoJsonString, ProgressTracker progress_tracker) - throws JSONException; + public abstract MapGeometry execute(int import_flags, Geometry.Type type, String geoJsonString, ProgressTracker progress_tracker) throws JSONException; /** + * * Performs the ImportFromGeoJson operation. + * * @param import_flags Use the {@link GeoJsonImportFlags} interface. * @param geoJsonString The string holding the Geometry in geoJson format. * @return Returns the imported MapOGCStructure. - * @throws JSONException + * @throws JSONException */ - public abstract MapOGCStructure executeOGC(int import_flags, - String geoJsonString, ProgressTracker progress_tracker) - throws JSONException; + public abstract MapOGCStructure executeOGC(int import_flags, String geoJsonString, ProgressTracker progress_tracker) throws JSONException; public static OperatorImportFromGeoJson local() { - return (OperatorImportFromGeoJson) OperatorFactoryLocal.getInstance() - .getOperator(Type.ImportFromGeoJson); + return (OperatorImportFromGeoJson) OperatorFactoryLocal.getInstance().getOperator(Type.ImportFromGeoJson); } } diff --git a/src/main/java/com/esri/core/geometry/OperatorImportFromGeoJsonLocal.java b/src/main/java/com/esri/core/geometry/OperatorImportFromGeoJsonLocal.java index 8ed6b64e..e04952a4 100644 --- a/src/main/java/com/esri/core/geometry/OperatorImportFromGeoJsonLocal.java +++ b/src/main/java/com/esri/core/geometry/OperatorImportFromGeoJsonLocal.java @@ -24,142 +24,1244 @@ package com.esri.core.geometry; import com.esri.core.geometry.VertexDescription.Semantics; +import org.codehaus.jackson.JsonFactory; +import org.codehaus.jackson.JsonParseException; +import org.codehaus.jackson.JsonParser; +import org.codehaus.jackson.JsonToken; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; + +import java.io.IOException; import java.util.ArrayList; class OperatorImportFromGeoJsonLocal extends OperatorImportFromGeoJson { + @Override - public MapGeometry execute(int importFlags, Geometry.Type type, - String geoJsonString, ProgressTracker progress_tracker) - throws JSONException { - JSONObject geoJsonObject = new JSONObject(geoJsonString); - Geometry geometry = importGeometryFromGeoJson_(importFlags, type, - geoJsonObject); - SpatialReference spatialReference = importSpatialReferenceFromGeoJson_(geoJsonObject); - MapGeometry mapGeometry = new MapGeometry(geometry, spatialReference); - return mapGeometry; + public MapGeometry execute(int importFlags, Geometry.Type type, String geoJsonString, + ProgressTracker progressTracker) throws JSONException { + try { + JsonFactory factory = new JsonFactory(); + JsonParser jsonParser = factory.createJsonParser(geoJsonString); + + jsonParser.nextToken(); + + MapGeometry map_geometry = OperatorImportFromGeoJsonHelper.importFromGeoJson(importFlags, type, + new JsonParserReader(jsonParser), progressTracker, false); + return map_geometry; + + } catch (JSONException jsonException) { + throw jsonException; + } catch (JsonParseException jsonParseException) { + throw new JSONException(jsonParseException.getMessage()); + } catch (IOException ioException) { + throw new JSONException(ioException.getMessage()); + } } - static JSONArray getJSONArray(JSONObject obj, String name) - throws JSONException { - if (obj.get(name) == JSONObject.NULL) - return new JSONArray(); - else - return obj.getJSONArray(name); + @Override + public MapGeometry execute(int importFlags, Geometry.Type type, JSONObject jsonObject, + ProgressTracker progressTracker) throws JSONException { + if (jsonObject == null) + return null; + + try { + return OperatorImportFromGeoJsonHelper.importFromGeoJson(importFlags, type, new JsonValueReader(jsonObject), + progressTracker, false); + } catch (JSONException jsonException) { + throw jsonException; + } catch (JsonParseException jsonParseException) { + throw new JSONException(jsonParseException.getMessage()); + } catch (IOException ioException) { + throw new JSONException(ioException.getMessage()); + } } - @Override - public MapOGCStructure executeOGC(int import_flags, String geoJsonString, - ProgressTracker progress_tracker) throws JSONException { - JSONObject geoJsonObject = new JSONObject(geoJsonString); - ArrayList structureStack = new ArrayList(0); - ArrayList objectStack = new ArrayList(0); - AttributeStreamOfInt32 indices = new AttributeStreamOfInt32(0); - AttributeStreamOfInt32 numGeometries = new AttributeStreamOfInt32(0); - - OGCStructure root = new OGCStructure(); - root.m_structures = new ArrayList(0); - structureStack.add(root); // add dummy root - objectStack.add(geoJsonObject); - indices.add(0); - numGeometries.add(1); - - while (!objectStack.isEmpty()) { - if (indices.getLast() == numGeometries.getLast()) { - structureStack.remove(structureStack.size() - 1); - indices.removeLast(); - numGeometries.removeLast(); - continue; - } - - OGCStructure lastStructure = structureStack.get(structureStack - .size() - 1); - JSONObject lastObject = objectStack.get(objectStack.size() - 1); - objectStack.remove(objectStack.size() - 1); - indices.write(indices.size() - 1, indices.getLast() + 1); - String typeString = lastObject.getString("type"); - - if (typeString.equalsIgnoreCase("GeometryCollection")) { - OGCStructure next = new OGCStructure(); - next.m_type = 7; - next.m_structures = new ArrayList(0); - lastStructure.m_structures.add(next); - structureStack.add(next); - - JSONArray geometries = getJSONArray(lastObject, "geometries"); - indices.add(0); - numGeometries.add(geometries.length()); - - for (int i = geometries.length() - 1; i >= 0; i--) - objectStack.add(geometries.getJSONObject(i)); + static final class OperatorImportFromGeoJsonHelper { + + private AttributeStreamOfDbl m_position; + private AttributeStreamOfDbl m_zs; + private AttributeStreamOfDbl m_ms; + private AttributeStreamOfInt32 m_paths; + private AttributeStreamOfInt8 m_path_flags; + private Point m_point; // special case for Points + private boolean m_b_has_zs; + private boolean m_b_has_ms; + private boolean m_b_has_zs_known; + private boolean m_b_has_ms_known; + private int m_num_embeddings; + + OperatorImportFromGeoJsonHelper() { + m_position = null; + m_zs = null; + m_ms = null; + m_paths = null; + m_path_flags = null; + m_point = null; + m_b_has_zs = false; + m_b_has_ms = false; + m_b_has_zs_known = false; + m_b_has_ms_known = false; + m_num_embeddings = 0; + } + + static MapGeometry importFromGeoJson(int importFlags, Geometry.Type type, JsonReader json_iterator, + ProgressTracker progress_tracker, boolean skip_coordinates) + throws JSONException, JsonParseException, IOException { + assert(json_iterator.currentToken() == JsonToken.START_OBJECT); + + OperatorImportFromGeoJsonHelper geo_json_helper = new OperatorImportFromGeoJsonHelper(); + boolean b_type_found = false; + boolean b_coordinates_found = false; + boolean b_crs_found = false; + boolean b_crsURN_found = false; + String geo_json_type = null; + + Geometry geometry = null; + SpatialReference spatial_reference = null; + + JsonToken current_token; + String field_name = null; + + while ((current_token = json_iterator.nextToken()) != JsonToken.END_OBJECT) { + field_name = json_iterator.currentString(); + + if (field_name.equals("type")) { + if (b_type_found) { + throw new IllegalArgumentException("invalid argument"); + } + + b_type_found = true; + current_token = json_iterator.nextToken(); + + if (current_token != JsonToken.VALUE_STRING) { + throw new IllegalArgumentException("invalid argument"); + } + + geo_json_type = json_iterator.currentString(); + } else if (field_name.equals("coordinates")) { + if (b_coordinates_found) { + throw new IllegalArgumentException("invalid argument"); + } + + b_coordinates_found = true; + current_token = json_iterator.nextToken(); + + if (skip_coordinates) { + json_iterator.skipChildren(); + } else {// According to the spec, the value of the + // coordinates must be an array. However, I do an + // extra check for null too. + if (current_token != JsonToken.VALUE_NULL) { + if (current_token != JsonToken.START_ARRAY) { + throw new IllegalArgumentException("invalid argument"); + } + + geo_json_helper.import_coordinates_(json_iterator, progress_tracker); + } + } + } else if (field_name.equals("crs")) { + if (b_crs_found || b_crsURN_found) { + throw new IllegalArgumentException("invalid argument"); + } + + b_crs_found = true; + current_token = json_iterator.nextToken(); + + if ((importFlags & GeoJsonImportFlags.geoJsonImportSkipCRS) == 0) + spatial_reference = importSpatialReferenceFromCrs(json_iterator, progress_tracker); + else + json_iterator.skipChildren(); + } else if (field_name.equals("crsURN")) { + if (b_crs_found || b_crsURN_found) { + throw new IllegalArgumentException("invalid argument"); + } + + b_crsURN_found = true; + current_token = json_iterator.nextToken(); + + spatial_reference = importSpatialReferenceFromCrsUrn_(json_iterator, + progress_tracker); + } else { + json_iterator.nextToken(); + json_iterator.skipChildren(); + } + } + + // According to the spec, a GeoJSON object must have both a type and + // a coordinates array + if (!b_type_found || (!b_coordinates_found && !skip_coordinates)) { + throw new IllegalArgumentException("invalid argument"); + } + + if (!skip_coordinates) + geometry = geo_json_helper.createGeometry_(geo_json_type, type.value()); + + if (!b_crs_found && !b_crsURN_found && ((importFlags & GeoJsonImportFlags.geoJsonImportSkipCRS) == 0) + && ((importFlags & GeoJsonImportFlags.geoJsonImportNoWGS84Default) == 0)) { + spatial_reference = SpatialReference.create(4326); // the spec + // gives a + // default + // of 4326 + // if no crs + // is given + } + + MapGeometry map_geometry = new MapGeometry(geometry, spatial_reference); + + assert(geo_json_helper.m_paths == null || (geo_json_helper.m_path_flags != null + && geo_json_helper.m_paths.size() == geo_json_helper.m_path_flags.size())); + + return map_geometry; + } + + // We have to import the coordinates in the most general way possible to + // not assume the type of geometry we're parsing. + // JSON allows for unordered objects, so it's possible that the + // coordinates array can come before the type tag when parsing + // sequentially, otherwise + // we would have to parse using a JSON_object, which would be easier, + // but not as space/time efficient. So this function blindly imports the + // coordinates + // into the attribute stream(s), and will later assign them to a + // geometry after the type tag is found. + private void import_coordinates_(JsonReader json_iterator, ProgressTracker progress_tracker) + throws JSONException, JsonParseException, IOException { + assert(json_iterator.currentToken() == JsonToken.START_ARRAY); + + int coordinates_level_lower = 1; + int coordinates_level_upper = 4; + + json_iterator.nextToken(); + + while (json_iterator.currentToken() != JsonToken.END_ARRAY) { + if (isDouble_(json_iterator)) { + if (coordinates_level_upper > 1) { + coordinates_level_upper = 1; + } + } else if (json_iterator.currentToken() == JsonToken.START_ARRAY) { + if (coordinates_level_lower < 2) { + coordinates_level_lower = 2; + } + } else { + throw new IllegalArgumentException("invalid argument"); + } + + if (coordinates_level_lower > coordinates_level_upper) { + throw new IllegalArgumentException("invalid argument"); + } + + if (coordinates_level_lower == coordinates_level_upper && coordinates_level_lower == 1) {// special + // code + // for + // Points + readCoordinateAsPoint_(json_iterator); + } else { + boolean b_add_path_level_3 = true; + boolean b_polygon_start_level_4 = true; + + assert(json_iterator.currentToken() == JsonToken.START_ARRAY); + json_iterator.nextToken(); + + while (json_iterator.currentToken() != JsonToken.END_ARRAY) { + if (isDouble_(json_iterator)) { + if (coordinates_level_upper > 2) { + coordinates_level_upper = 2; + } + } else if (json_iterator.currentToken() == JsonToken.START_ARRAY) { + if (coordinates_level_lower < 3) { + coordinates_level_lower = 3; + } + } else { + throw new IllegalArgumentException("invalid argument"); + } + + if (coordinates_level_lower > coordinates_level_upper) { + throw new IllegalArgumentException("invalid argument"); + } + + if (coordinates_level_lower == coordinates_level_upper && coordinates_level_lower == 2) {// LineString + // or + // MultiPoint + addCoordinate_(json_iterator); + } else { + boolean b_add_path_level_4 = true; + + assert(json_iterator.currentToken() == JsonToken.START_ARRAY); + json_iterator.nextToken(); + + while (json_iterator.currentToken() != JsonToken.END_ARRAY) { + if (isDouble_(json_iterator)) { + if (coordinates_level_upper > 3) { + coordinates_level_upper = 3; + } + } else if (json_iterator.currentToken() == JsonToken.START_ARRAY) { + if (coordinates_level_lower < 4) { + coordinates_level_lower = 4; + } + } else { + throw new IllegalArgumentException("invalid argument"); + } + + if (coordinates_level_lower > coordinates_level_upper) { + throw new IllegalArgumentException("invalid argument"); + } + + if (coordinates_level_lower == coordinates_level_upper + && coordinates_level_lower == 3) {// Polygon + // or + // MultiLineString + if (b_add_path_level_3) { + addPath_(); + b_add_path_level_3 = false; + } + + addCoordinate_(json_iterator); + } else { + assert(json_iterator.currentToken() == JsonToken.START_ARRAY); + json_iterator.nextToken(); + + if (json_iterator.currentToken() != JsonToken.END_ARRAY) { + if (!isDouble_(json_iterator)) { + throw new IllegalArgumentException("invalid argument"); + } + + assert(coordinates_level_lower == coordinates_level_upper + && coordinates_level_lower == 4); + // MultiPolygon + + if (b_add_path_level_4) { + addPath_(); + addPathFlag_(b_polygon_start_level_4); + b_add_path_level_4 = false; + b_polygon_start_level_4 = false; + } + + addCoordinate_(json_iterator); + } + + json_iterator.nextToken(); + } + } + + json_iterator.nextToken(); + } + } + + json_iterator.nextToken(); + } + } + + if (m_paths != null) { + m_paths.add(m_position.size() / 2); // add final path size + } + if (m_path_flags != null) { + m_path_flags.add((byte) 0); // to match the paths size + } + + m_num_embeddings = coordinates_level_lower; + } + + private void readCoordinateAsPoint_(JsonReader json_iterator) + throws JSONException, JsonParseException, IOException { + assert(isDouble_(json_iterator)); + + m_point = new Point(); + + double x = readDouble_(json_iterator); + json_iterator.nextToken(); + double y = readDouble_(json_iterator); + json_iterator.nextToken(); + + if (NumberUtils.isNaN(y)) { + x = NumberUtils.NaN(); + } + + m_point.setXY(x, y); + + if (isDouble_(json_iterator)) { + double z = readDouble_(json_iterator); + json_iterator.nextToken(); + m_point.setZ(z); + } + + if (isDouble_(json_iterator)) { + double m = readDouble_(json_iterator); + json_iterator.nextToken(); + m_point.setM(m); + } + + if (json_iterator.currentToken() != JsonToken.END_ARRAY) { + throw new IllegalArgumentException("invalid argument"); + } + } + + private void addCoordinate_(JsonReader json_iterator) throws JSONException, JsonParseException, IOException { + assert(isDouble_(json_iterator)); + + if (m_position == null) { + m_position = (AttributeStreamOfDbl) AttributeStreamBase.createDoubleStream(0); + } + + double x = readDouble_(json_iterator); + json_iterator.nextToken(); + double y = readDouble_(json_iterator); + json_iterator.nextToken(); + + int size = m_position.size(); + + m_position.add(x); + m_position.add(y); + + if (isDouble_(json_iterator)) { + if (!m_b_has_zs_known) { + m_b_has_zs_known = true; + m_b_has_zs = true; + m_zs = (AttributeStreamOfDbl) AttributeStreamBase.createDoubleStream(0); + } else { + if (!m_b_has_zs) { + m_zs = (AttributeStreamOfDbl) AttributeStreamBase.createDoubleStream(size >> 1, + VertexDescription.getDefaultValue(Semantics.Z)); + m_b_has_zs = true; + } + } + + double z = readDouble_(json_iterator); + json_iterator.nextToken(); + m_zs.add(z); + } else { + if (!m_b_has_zs_known) { + m_b_has_zs_known = true; + m_b_has_zs = false; + } else { + if (m_b_has_zs) { + m_zs.add(VertexDescription.getDefaultValue(Semantics.Z)); + } + } + } + + if (isDouble_(json_iterator)) { + if (!m_b_has_ms_known) { + m_b_has_ms_known = true; + m_b_has_ms = true; + m_ms = (AttributeStreamOfDbl) AttributeStreamBase.createDoubleStream(0); + } else { + if (!m_b_has_ms) { + m_ms = (AttributeStreamOfDbl) AttributeStreamBase.createDoubleStream(size >> 1, + VertexDescription.getDefaultValue(Semantics.M)); + m_b_has_ms = true; + } + } + + double m = readDouble_(json_iterator); + json_iterator.nextToken(); + m_ms.add(m); + } else { + if (!m_b_has_ms_known) { + m_b_has_ms_known = true; + m_b_has_ms = false; + } else { + if (m_b_has_ms) { + m_zs.add(VertexDescription.getDefaultValue(Semantics.M)); + } + } + } + + if (json_iterator.currentToken() != JsonToken.END_ARRAY) { + throw new IllegalArgumentException("invalid argument"); + } + } + + private void addPath_() { + if (m_paths == null) { + m_paths = (AttributeStreamOfInt32) AttributeStreamBase.createIndexStream(0); + } + + if (m_position == null) { + m_paths.add(0); + } else { + m_paths.add(m_position.size() / 2); + } + } + + private void addPathFlag_(boolean b_polygon_start) { + if (m_path_flags == null) { + m_path_flags = (AttributeStreamOfInt8) AttributeStreamBase.createByteStream(0); + } + + if (b_polygon_start) { + m_path_flags.add((byte) (PathFlags.enumClosed | PathFlags.enumOGCStartPolygon)); + } else { + m_path_flags.add((byte) PathFlags.enumClosed); + } + } + + private double readDouble_(JsonReader json_iterator) throws JSONException, JsonParseException, IOException { + JsonToken current_token = json_iterator.currentToken(); + if (current_token == JsonToken.VALUE_NULL + || (current_token == JsonToken.VALUE_STRING && json_iterator.currentString().equals("NaN"))) { + return NumberUtils.NaN(); + } else { + return json_iterator.currentDoubleValue(); + } + } + + private boolean isDouble_(JsonReader json_iterator) throws JSONException, JsonParseException, IOException { + JsonToken current_token = json_iterator.currentToken(); + + if (current_token == JsonToken.VALUE_NUMBER_FLOAT) { + return true; + } + + if (current_token == JsonToken.VALUE_NUMBER_INT) { + return true; + } + + if (current_token == JsonToken.VALUE_NULL + || (current_token == JsonToken.VALUE_STRING && json_iterator.currentString().equals("NaN"))) { + return true; + } + + return false; + } + + private Geometry createGeometry_(String geo_json_type, int type) + throws JSONException, JsonParseException, IOException { + Geometry geometry; + + if (type != Geometry.GeometryType.Unknown) { + switch (type) { + case Geometry.GeometryType.Polygon: + if (!geo_json_type.equals("MultiPolygon") && !geo_json_type.equals("Polygon")) { + throw new GeometryException("invalid shape type"); + } + break; + case Geometry.GeometryType.Polyline: + if (!geo_json_type.equals("MultiLineString") && !geo_json_type.equals("LineString")) { + throw new GeometryException("invalid shape type"); + } + break; + case Geometry.GeometryType.MultiPoint: + if (!geo_json_type.equals("MultiPoint")) { + throw new GeometryException("invalid shape type"); + } + break; + case Geometry.GeometryType.Point: + if (!geo_json_type.equals("Point")) { + throw new GeometryException("invalid shape type"); + } + break; + default: + throw new GeometryException("invalid shape type"); + } + } + + if (m_position == null && m_point == null) { + if (geo_json_type.equals("Point")) { + if (m_num_embeddings > 1) { + throw new IllegalArgumentException("invalid argument"); + } + + geometry = new Point(); + } else if (geo_json_type.equals("MultiPoint")) { + if (m_num_embeddings > 2) { + throw new IllegalArgumentException("invalid argument"); + } + + geometry = new MultiPoint(); + } else if (geo_json_type.equals("LineString")) { + if (m_num_embeddings > 2) { + throw new IllegalArgumentException("invalid argument"); + } + + geometry = new Polyline(); + } else if (geo_json_type.equals("MultiLineString")) { + if (m_num_embeddings > 3) { + throw new IllegalArgumentException("invalid argument"); + } + + geometry = new Polyline(); + } else if (geo_json_type.equals("Polygon")) { + if (m_num_embeddings > 3) { + throw new IllegalArgumentException("invalid argument"); + } + + geometry = new Polygon(); + } else if (geo_json_type.equals("MultiPolygon")) { + assert(m_num_embeddings <= 4); + geometry = new Polygon(); + } else { + throw new IllegalArgumentException("invalid argument"); + } + } else if (m_num_embeddings == 1) { + if (!geo_json_type.equals("Point")) { + throw new IllegalArgumentException("invalid argument"); + } + + assert(m_point != null); + geometry = m_point; + } else if (m_num_embeddings == 2) { + if (geo_json_type.equals("MultiPoint")) { + geometry = createMultiPointFromStreams_(); + } else if (geo_json_type.equals("LineString")) { + geometry = createPolylineFromStreams_(); + } else { + throw new IllegalArgumentException("invalid argument"); + } + } else if (m_num_embeddings == 3) { + if (geo_json_type.equals("Polygon")) { + geometry = createPolygonFromStreams_(); + } else if (geo_json_type.equals("MultiLineString")) { + geometry = createPolylineFromStreams_(); + } else { + throw new IllegalArgumentException("invalid argument"); + } } else { - int ogcType; + if (!geo_json_type.equals("MultiPolygon")) { + throw new IllegalArgumentException("invalid argument"); + } + + geometry = createPolygonFromStreams_(); + } + + return geometry; + } + + private Geometry createPolygonFromStreams_() { + assert(m_position != null); + assert(m_paths != null); + assert((m_num_embeddings == 3 && m_path_flags == null) || (m_num_embeddings == 4 && m_path_flags != null)); + + Polygon polygon = new Polygon(); + MultiPathImpl multi_path_impl = (MultiPathImpl) polygon._getImpl(); + + checkPathPointCountsForMultiPath_(true); + multi_path_impl.setAttributeStreamRef(Semantics.POSITION, m_position); + + if (m_b_has_zs) { + assert(m_zs != null); + multi_path_impl.setAttributeStreamRef(Semantics.Z, m_zs); + } + + if (m_b_has_ms) { + assert(m_ms != null); + multi_path_impl.setAttributeStreamRef(Semantics.M, m_ms); + } + + if (m_path_flags == null) { + m_path_flags = (AttributeStreamOfInt8) AttributeStreamBase.createByteStream(m_paths.size(), (byte) 0); + m_path_flags.setBits(0, (byte) (PathFlags.enumClosed | PathFlags.enumOGCStartPolygon)); + + for (int i = 1; i < m_path_flags.size() - 1; i++) { + m_path_flags.setBits(i, (byte) PathFlags.enumClosed); + } + } + + multi_path_impl.setPathStreamRef(m_paths); + multi_path_impl.setPathFlagsStreamRef(m_path_flags); + multi_path_impl.notifyModified(MultiVertexGeometryImpl.DirtyFlags.DirtyAll); - if (typeString.equalsIgnoreCase("Point")) - ogcType = 1; - else if (typeString.equalsIgnoreCase("LineString")) - ogcType = 2; - else if (typeString.equalsIgnoreCase("Polygon")) - ogcType = 3; - else if (typeString.equalsIgnoreCase("MultiPoint")) - ogcType = 4; - else if (typeString.equalsIgnoreCase("MultiLineString")) - ogcType = 5; - else if (typeString.equalsIgnoreCase("MultiPolygon")) - ogcType = 6; - else - throw new UnsupportedOperationException(); + AttributeStreamOfInt8 path_flags_clone = new AttributeStreamOfInt8(m_path_flags); - Geometry geometry = importGeometryFromGeoJson_(import_flags, - Geometry.Type.Unknown, lastObject); + for (int i = 0; i < path_flags_clone.size() - 1; i++) { + assert((path_flags_clone.read(i) & PathFlags.enumClosed) != 0); + assert((m_path_flags.read(i) & PathFlags.enumClosed) != 0); + + if ((path_flags_clone.read(i) & PathFlags.enumOGCStartPolygon) != 0) {// Should + // be + // clockwise + if (!InternalUtils.isClockwiseRing(multi_path_impl, i)) { + multi_path_impl.reversePath(i); // make clockwise + } + } else {// Should be counter-clockwise + if (InternalUtils.isClockwiseRing(multi_path_impl, i)) { + multi_path_impl.reversePath(i); // make + // counter-clockwise + } + } + } + multi_path_impl.setPathFlagsStreamRef(path_flags_clone); + multi_path_impl.setDirtyOGCFlags(false); + + return polygon; + } + + private Geometry createPolylineFromStreams_() { + assert(m_position != null); + assert((m_num_embeddings == 2 && m_paths == null) || (m_num_embeddings == 3 && m_paths != null)); + assert(m_path_flags == null); + + Polyline polyline = new Polyline(); + MultiPathImpl multi_path_impl = (MultiPathImpl) polyline._getImpl(); + + if (m_paths == null) { + m_paths = (AttributeStreamOfInt32) AttributeStreamBase.createIndexStream(0); + m_paths.add(0); + m_paths.add(m_position.size() / 2); + } + + checkPathPointCountsForMultiPath_(false); + multi_path_impl.setAttributeStreamRef(Semantics.POSITION, m_position); + + if (m_b_has_zs) { + assert(m_zs != null); + multi_path_impl.setAttributeStreamRef(Semantics.Z, m_zs); + } + + if (m_b_has_ms) { + assert(m_ms != null); + multi_path_impl.setAttributeStreamRef(Semantics.M, m_ms); + } + + m_path_flags = (AttributeStreamOfInt8) AttributeStreamBase.createByteStream(m_paths.size(), (byte) 0); + + multi_path_impl.setPathStreamRef(m_paths); + multi_path_impl.setPathFlagsStreamRef(m_path_flags); + multi_path_impl.notifyModified(MultiVertexGeometryImpl.DirtyFlags.DirtyAll); + + return polyline; + } + + private Geometry createMultiPointFromStreams_() { + assert(m_position != null); + assert(m_paths == null); + assert(m_path_flags == null); - OGCStructure leaf = new OGCStructure(); - leaf.m_type = ogcType; - leaf.m_geometry = geometry; - lastStructure.m_structures.add(leaf); + MultiPoint multi_point = new MultiPoint(); + MultiPointImpl multi_point_impl = (MultiPointImpl) multi_point._getImpl(); + multi_point_impl.setAttributeStreamRef(Semantics.POSITION, m_position); + + if (m_b_has_zs) { + assert(m_zs != null); + multi_point_impl.setAttributeStreamRef(Semantics.Z, m_zs); + } + + if (m_b_has_ms) { + assert(m_ms != null); + multi_point_impl.setAttributeStreamRef(Semantics.M, m_ms); + } + multi_point_impl.resize(m_position.size() / 2); + multi_point_impl.notifyModified(MultiVertexGeometryImpl.DirtyFlags.DirtyAll); + + return multi_point; + } + + private void checkPathPointCountsForMultiPath_(boolean b_is_polygon) { + Point2D pt1 = new Point2D(), pt2 = new Point2D(); + double z1 = 0.0, z2 = 0.0, m1 = 0.0, m2 = 0.0; + int path_count = m_paths.size() - 1; + int guess_adjustment = 0; + + if (b_is_polygon) {// Polygon + guess_adjustment = path_count; // may remove up to path_count + // number of points + } else {// Polyline + for (int path = 0; path < path_count; path++) { + int path_size = m_paths.read(path + 1) - m_paths.read(path); + + if (path_size == 1) { + guess_adjustment--; // will add a point for each path + // containing only 1 point + } + } + + if (guess_adjustment == 0) { + return; // all paths are okay + } } + + AttributeStreamOfDbl adjusted_position = (AttributeStreamOfDbl) AttributeStreamBase + .createDoubleStream(m_position.size() - guess_adjustment); + AttributeStreamOfInt32 adjusted_paths = (AttributeStreamOfInt32) AttributeStreamBase + .createIndexStream(m_paths.size()); + AttributeStreamOfDbl adjusted_zs = null; + AttributeStreamOfDbl adjusted_ms = null; + + if (m_b_has_zs) { + adjusted_zs = (AttributeStreamOfDbl) AttributeStreamBase + .createDoubleStream(m_zs.size() - guess_adjustment); + } + + if (m_b_has_ms) { + adjusted_ms = (AttributeStreamOfDbl) AttributeStreamBase + .createDoubleStream(m_ms.size() - guess_adjustment); + } + + int adjusted_start = 0; + adjusted_paths.write(0, 0); + + for (int path = 0; path < path_count; path++) { + int path_start = m_paths.read(path); + int path_end = m_paths.read(path + 1); + int path_size = path_end - path_start; + assert(path_size != 0); // we should not have added empty parts + // on import + + if (path_size == 1) { + insertIntoAdjustedStreams_(adjusted_position, adjusted_zs, adjusted_ms, adjusted_start, path_start, + path_size); + insertIntoAdjustedStreams_(adjusted_position, adjusted_zs, adjusted_ms, adjusted_start + 1, + path_start, path_size); + adjusted_start += 2; + } else if (path_size >= 3 && b_is_polygon) { + m_position.read(path_start * 2, pt1); + m_position.read((path_end - 1) * 2, pt2); + + if (m_b_has_zs) { + z1 = m_zs.readAsDbl(path_start); + z2 = m_zs.readAsDbl(path_end - 1); + } + + if (m_b_has_ms) { + m1 = m_ms.readAsDbl(path_start); + m2 = m_ms.readAsDbl(path_end - 1); + } + + if (pt1.equals(pt2) && (NumberUtils.isNaN(z1) && NumberUtils.isNaN(z2) || z1 == z2) + && (NumberUtils.isNaN(m1) && NumberUtils.isNaN(m2) || m1 == m2)) { + insertIntoAdjustedStreams_(adjusted_position, adjusted_zs, adjusted_ms, adjusted_start, + path_start, path_size - 1); + adjusted_start += path_size - 1; + } else { + insertIntoAdjustedStreams_(adjusted_position, adjusted_zs, adjusted_ms, adjusted_start, + path_start, path_size); + adjusted_start += path_size; + } + } else { + insertIntoAdjustedStreams_(adjusted_position, adjusted_zs, adjusted_ms, adjusted_start, path_start, + path_size); + adjusted_start += path_size; + } + adjusted_paths.write(path + 1, adjusted_start); + } + + m_position = adjusted_position; + m_paths = adjusted_paths; + m_zs = adjusted_zs; + m_ms = adjusted_ms; } - MapOGCStructure mapOGCStructure = new MapOGCStructure(); - mapOGCStructure.m_ogcStructure = root; - mapOGCStructure.m_spatialReference = importSpatialReferenceFromGeoJson_(geoJsonObject); + private void insertIntoAdjustedStreams_(AttributeStreamOfDbl adjusted_position, + AttributeStreamOfDbl adjusted_zs, AttributeStreamOfDbl adjusted_ms, int adjusted_start, int path_start, + int count) { + adjusted_position.insertRange(adjusted_start * 2, m_position, path_start * 2, count * 2, true, 2, + adjusted_start * 2); + + if (m_b_has_zs) { + adjusted_zs.insertRange(adjusted_start, m_zs, path_start, count, true, 1, adjusted_start); + } + + if (m_b_has_ms) { + adjusted_ms.insertRange(adjusted_start, m_ms, path_start, count, true, 1, adjusted_start); + } + } + + static SpatialReference importSpatialReferenceFromCrs(JsonReader json_iterator, + ProgressTracker progress_tracker) throws JSONException, JsonParseException, IOException { + // According to the spec, a null crs corresponds to no spatial + // reference + if (json_iterator.currentToken() == JsonToken.VALUE_NULL) { + return null; + } + + if (json_iterator.currentToken() == JsonToken.VALUE_STRING) {// see + // http://wiki.geojson.org/RFC-001 + // (this + // is + // deprecated, + // but + // there + // may + // be + // data + // with + // this + // format) + + String crs_short_form = json_iterator.currentString(); + int wkid = GeoJsonCrsTables.getWkidFromCrsShortForm(crs_short_form); + + if (wkid == -1) { + throw new GeometryException("not implemented"); + } + + SpatialReference spatial_reference = null; + + try { + spatial_reference = SpatialReference.create(wkid); + } catch (Exception e) { + } + + return spatial_reference; + } + + if (json_iterator.currentToken() != JsonToken.START_OBJECT) { + throw new IllegalArgumentException("invalid argument"); + } + + // This is to support all cases of crs identifiers I've seen. Some + // may be rare or are legacy formats, but all are simple to + // accomodate. + boolean b_found_type = false; + boolean b_found_properties = false; + boolean b_found_properties_name = false; + boolean b_found_properties_href = false; + boolean b_found_properties_urn = false; + boolean b_found_properties_url = false; + boolean b_found_properties_code = false; + boolean b_found_esriwkt = false; + String crs_field = null, properties_field = null, type = null, crs_identifier_name = null, + crs_identifier_urn = null, crs_identifier_href = null, crs_identifier_url = null, esriwkt = null; + int crs_identifier_code = -1; + JsonToken current_token; + + while (json_iterator.nextToken() != JsonToken.END_OBJECT) { + crs_field = json_iterator.currentString(); + + if (crs_field.equals("type")) { + if (b_found_type) { + throw new IllegalArgumentException("invalid argument"); + } + + b_found_type = true; + + current_token = json_iterator.nextToken(); + + if (current_token != JsonToken.VALUE_STRING) { + throw new IllegalArgumentException("invalid argument"); + } + + type = json_iterator.currentString(); + } else if (crs_field.equals("properties")) { + if (b_found_properties) { + throw new IllegalArgumentException("invalid argument"); + } + + b_found_properties = true; + + current_token = json_iterator.nextToken(); + + if (current_token != JsonToken.START_OBJECT) { + throw new IllegalArgumentException("invalid argument"); + } + + while (json_iterator.nextToken() != JsonToken.END_OBJECT) { + properties_field = json_iterator.currentString(); + + if (properties_field.equals("name")) { + if (b_found_properties_name) { + throw new IllegalArgumentException("invalid argument"); + } + + b_found_properties_name = true; + crs_identifier_name = getCrsIdentifier_(json_iterator); + } else if (properties_field.equals("href")) { + if (b_found_properties_href) { + throw new IllegalArgumentException("invalid argument"); + } + + b_found_properties_href = true; + crs_identifier_href = getCrsIdentifier_(json_iterator); + } else if (properties_field.equals("urn")) { + if (b_found_properties_urn) { + throw new IllegalArgumentException("invalid argument"); + } + + b_found_properties_urn = true; + crs_identifier_urn = getCrsIdentifier_(json_iterator); + } else if (properties_field.equals("url")) { + if (b_found_properties_url) { + throw new IllegalArgumentException("invalid argument"); + } + + b_found_properties_url = true; + crs_identifier_url = getCrsIdentifier_(json_iterator); + } else if (properties_field.equals("code")) { + if (b_found_properties_code) { + throw new IllegalArgumentException("invalid argument"); + } + + b_found_properties_code = true; + + current_token = json_iterator.nextToken(); + + if (current_token != JsonToken.VALUE_NUMBER_INT) { + throw new IllegalArgumentException("invalid argument"); + } + + crs_identifier_code = json_iterator.currentIntValue(); + } else { + json_iterator.nextToken(); + json_iterator.skipChildren(); + } + } + } else if (crs_field.equals("esriwkt")) { + if (b_found_esriwkt) { + throw new IllegalArgumentException("invalid argument"); + } + + b_found_esriwkt = true; + + current_token = json_iterator.nextToken(); + + if (current_token != JsonToken.VALUE_STRING) { + throw new IllegalArgumentException("invalid argument"); + } + + esriwkt = json_iterator.currentString(); + } else { + json_iterator.nextToken(); + json_iterator.skipChildren(); + } + } + + if ((!b_found_type || !b_found_properties) && !b_found_esriwkt) { + throw new IllegalArgumentException("invalid argument"); + } + + int wkid = -1; + + if (b_found_properties_name) { + wkid = GeoJsonCrsTables.getWkidFromCrsName(crs_identifier_name); // see + // http://wiki.geojson.org/GeoJSON_draft_version_6 + // (most + // common) + } else if (b_found_properties_href) { + wkid = GeoJsonCrsTables.getWkidFromCrsHref(crs_identifier_href); // see + // http://wiki.geojson.org/GeoJSON_draft_version_6 + // (somewhat + // common) + } else if (b_found_properties_urn) { + wkid = GeoJsonCrsTables.getWkidFromCrsOgcUrn(crs_identifier_urn); // see + // http://wiki.geojson.org/GeoJSON_draft_version_5 + // (rare) + } else if (b_found_properties_url) { + wkid = GeoJsonCrsTables.getWkidFromCrsHref(crs_identifier_url); // see + // http://wiki.geojson.org/GeoJSON_draft_version_5 + // (rare) + } else if (b_found_properties_code) { + wkid = crs_identifier_code; // see + // http://wiki.geojson.org/GeoJSON_draft_version_5 + // (rare) + } else if (!b_found_esriwkt) { + throw new GeometryException("not implemented"); + } + + if (wkid < 0 && !b_found_esriwkt && !b_found_properties_name) { + throw new GeometryException("not implemented"); + } + + SpatialReference spatial_reference = null; + + if (wkid > 0) { + try { + spatial_reference = SpatialReference.create(wkid); + } catch (Exception e) { + } + } + + if (spatial_reference == null) { + try { + if (b_found_esriwkt) {// I exported crs wkt strings like + // this + spatial_reference = SpatialReference.create(esriwkt); + } else if (b_found_properties_name) {// AGOL exported crs + // wkt strings like + // this where the + // crs identifier of + // the properties + // name is like + // "ESRI:" + String potential_wkt = GeoJsonCrsTables.getWktFromCrsName(crs_identifier_name); + spatial_reference = SpatialReference.create(potential_wkt); + } + } catch (Exception e) { + } + } + + return spatial_reference; + } + + // see http://geojsonwg.github.io/draft-geojson/draft.html + static SpatialReference importSpatialReferenceFromCrsUrn_(JsonReader json_iterator, + ProgressTracker progress_tracker) throws JSONException, JsonParseException, IOException { + // According to the spec, a null crs corresponds to no spatial + // reference + if (json_iterator.currentToken() == JsonToken.VALUE_NULL) { + return null; + } + + if (json_iterator.currentToken() != JsonToken.VALUE_STRING) { + throw new IllegalArgumentException("invalid argument"); + } + + String crs_identifier_urn = json_iterator.currentString(); + + int wkid = GeoJsonCrsTables.getWkidFromCrsName(crs_identifier_urn); // This + // will + // check + // for + // short + // form + // name, + // as + // well + // as + // long + // form + // URNs + + if (wkid == -1) { + throw new GeometryException("not implemented"); + } + + SpatialReference spatial_reference = SpatialReference.create(wkid); + + return spatial_reference; + } + + private static String getCrsIdentifier_(JsonReader json_iterator) + throws JSONException, JsonParseException, IOException { + JsonToken current_token = json_iterator.nextToken(); + + if (current_token != JsonToken.VALUE_STRING) { + throw new IllegalArgumentException("invalid argument"); + } + + return json_iterator.currentString(); + } + + } + + static JSONArray getJSONArray(JSONObject obj, String name) throws JSONException { + if (obj.get(name) == JSONObject.NULL) + return new JSONArray(); + else + return obj.getJSONArray(name); + } + + @Override + public MapOGCStructure executeOGC(int import_flags, String geoJsonString, ProgressTracker progress_tracker) + throws JSONException { + MapOGCStructure mapOGCStructure = null; + try { + JSONObject geoJsonObject = new JSONObject(geoJsonString); + ArrayList structureStack = new ArrayList(0); + ArrayList objectStack = new ArrayList(0); + AttributeStreamOfInt32 indices = new AttributeStreamOfInt32(0); + AttributeStreamOfInt32 numGeometries = new AttributeStreamOfInt32(0); + OGCStructure root = new OGCStructure(); + root.m_structures = new ArrayList(0); + structureStack.add(root); // add dummy root + objectStack.add(geoJsonObject); + indices.add(0); + numGeometries.add(1); + while (!objectStack.isEmpty()) { + if (indices.getLast() == numGeometries.getLast()) { + structureStack.remove(structureStack.size() - 1); + indices.removeLast(); + numGeometries.removeLast(); + continue; + } + OGCStructure lastStructure = structureStack.get(structureStack.size() - 1); + JSONObject lastObject = objectStack.get(objectStack.size() - 1); + objectStack.remove(objectStack.size() - 1); + indices.write(indices.size() - 1, indices.getLast() + 1); + String typeString = lastObject.getString("type"); + if (typeString.equalsIgnoreCase("GeometryCollection")) { + OGCStructure next = new OGCStructure(); + next.m_type = 7; + next.m_structures = new ArrayList(0); + lastStructure.m_structures.add(next); + structureStack.add(next); + JSONArray geometries = getJSONArray(lastObject, "geometries"); + indices.add(0); + numGeometries.add(geometries.length()); + for (int i = geometries.length() - 1; i >= 0; i--) + objectStack.add(geometries.getJSONObject(i)); + } else { + int ogcType; + if (typeString.equalsIgnoreCase("Point")) + ogcType = 1; + else if (typeString.equalsIgnoreCase("LineString")) + ogcType = 2; + else if (typeString.equalsIgnoreCase("Polygon")) + ogcType = 3; + else if (typeString.equalsIgnoreCase("MultiPoint")) + ogcType = 4; + else if (typeString.equalsIgnoreCase("MultiLineString")) + ogcType = 5; + else if (typeString.equalsIgnoreCase("MultiPolygon")) + ogcType = 6; + else + throw new UnsupportedOperationException(); + + MapGeometry map_geometry = execute(import_flags | GeoJsonImportFlags.geoJsonImportSkipCRS, + Geometry.Type.Unknown, lastObject, null); + OGCStructure leaf = new OGCStructure(); + leaf.m_type = ogcType; + leaf.m_geometry = map_geometry.getGeometry(); + lastStructure.m_structures.add(leaf); + } + } + mapOGCStructure = new MapOGCStructure(); + mapOGCStructure.m_ogcStructure = root; + + if ((import_flags & GeoJsonImportFlags.geoJsonImportSkipCRS) == 0) + mapOGCStructure.m_spatialReference = importSpatialReferenceFromGeoJson_(import_flags, geoJsonObject); + } catch (JSONException jsonException) { + throw jsonException; + } catch (JsonParseException jsonParseException) { + throw new JSONException(jsonParseException.getMessage()); + } catch (IOException ioException) { + throw new JSONException(ioException.getMessage()); + } return mapOGCStructure; } - private static SpatialReference importSpatialReferenceFromGeoJson_( - JSONObject crsJSONObject) throws JSONException { - String wkidString = crsJSONObject.optString("crs", ""); + private static SpatialReference importSpatialReferenceFromGeoJson_(int importFlags, JSONObject crsJSONObject) + throws JSONException, JsonParseException, IOException { + + SpatialReference spatial_reference = null; + boolean b_crs_found = false, b_crsURN_found = false; - if (wkidString.equals("")) { - return SpatialReference.create(4326); + Object opt = crsJSONObject.opt("crs"); + + if (opt != null) { + b_crs_found = true; + JSONObject crs_object = new JSONObject(); + crs_object.put("crs", opt); + JsonValueReader json_iterator = new JsonValueReader(crs_object); + json_iterator.nextToken(); + json_iterator.nextToken(); + return OperatorImportFromGeoJsonHelper.importSpatialReferenceFromCrs(json_iterator, null); } - // wkidString will be of the form "EPSG:#" where # is an integer, the - // EPSG ID. - // If the ID is below 32,767, then the EPSG ID will agree with the - // well-known (WKID). + opt = crsJSONObject.opt("crsURN"); - if (wkidString.length() <= 5) { - throw new IllegalArgumentException(); + if (opt != null) { + b_crsURN_found = true; + JSONObject crs_object = new JSONObject(); + crs_object.put("crsURN", opt); + JsonValueReader json_iterator = new JsonValueReader(crs_object); + json_iterator.nextToken(); + json_iterator.nextToken(); + return OperatorImportFromGeoJsonHelper.importSpatialReferenceFromCrs(json_iterator, null); } - // Throws a JSON exception if this cannot appropriately be converted to - // an integer. - int wkid = Integer.valueOf(wkidString.substring(5)).intValue(); + if ((importFlags & GeoJsonImportFlags.geoJsonImportNoWGS84Default) == 0) { + spatial_reference = SpatialReference.create(4326); + } - return SpatialReference.create(wkid); + return spatial_reference; } - private static Geometry importGeometryFromGeoJson_(int importFlags, - Geometry.Type type, JSONObject geometryJSONObject) - throws JSONException { + /* + private static Geometry importGeometryFromGeoJson_(int importFlags, Geometry.Type type, + JSONObject geometryJSONObject) throws JSONException { String typeString = geometryJSONObject.getString("type"); - JSONArray coordinateArray = getJSONArray(geometryJSONObject, - "coordinates"); - + JSONArray coordinateArray = getJSONArray(geometryJSONObject, "coordinates"); if (typeString.equalsIgnoreCase("MultiPolygon")) { if (type != Geometry.Type.Polygon && type != Geometry.Type.Unknown) throw new IllegalArgumentException("invalid shapetype"); @@ -169,8 +1271,7 @@ private static Geometry importGeometryFromGeoJson_(int importFlags, throw new IllegalArgumentException("invalid shapetype"); return lineStringTaggedText_(true, importFlags, coordinateArray); } else if (typeString.equalsIgnoreCase("MultiPoint")) { - if (type != Geometry.Type.MultiPoint - && type != Geometry.Type.Unknown) + if (type != Geometry.Type.MultiPoint && type != Geometry.Type.Unknown) throw new IllegalArgumentException("invalid shapetype"); return multiPointTaggedText_(importFlags, coordinateArray); } else if (typeString.equalsIgnoreCase("Polygon")) { @@ -190,8 +1291,8 @@ private static Geometry importGeometryFromGeoJson_(int importFlags, } } - private static Geometry polygonTaggedText_(boolean bMultiPolygon, - int importFlags, JSONArray coordinateArray) throws JSONException { + private static Geometry polygonTaggedText_(boolean bMultiPolygon, int importFlags, JSONArray coordinateArray) + throws JSONException { MultiPath multiPath; MultiPathImpl multiPathImpl; AttributeStreamOfDbl zs = null; @@ -199,49 +1300,30 @@ private static Geometry polygonTaggedText_(boolean bMultiPolygon, AttributeStreamOfDbl position; AttributeStreamOfInt32 paths; AttributeStreamOfInt8 path_flags; - - position = (AttributeStreamOfDbl) AttributeStreamBase - .createDoubleStream(0); - paths = (AttributeStreamOfInt32) AttributeStreamBase.createIndexStream( - 1, 0); - path_flags = (AttributeStreamOfInt8) AttributeStreamBase - .createByteStream(1, (byte) 0); - + position = (AttributeStreamOfDbl) AttributeStreamBase.createDoubleStream(0); + paths = (AttributeStreamOfInt32) AttributeStreamBase.createIndexStream(1, 0); + path_flags = (AttributeStreamOfInt8) AttributeStreamBase.createByteStream(1, (byte) 0); multiPath = new Polygon(); multiPathImpl = (MultiPathImpl) multiPath._getImpl(); - int pointCount; - if (bMultiPolygon) { - pointCount = multiPolygonText_(zs, ms, position, paths, path_flags, - coordinateArray); + pointCount = multiPolygonText_(zs, ms, position, paths, path_flags, coordinateArray); } else { - pointCount = polygonText_(zs, ms, position, paths, path_flags, 0, - coordinateArray); + pointCount = polygonText_(zs, ms, position, paths, path_flags, 0, coordinateArray); } - if (pointCount != 0) { - assert (2 * pointCount == position.size()); - multiPathImpl.setAttributeStreamRef( - VertexDescription.Semantics.POSITION, position); + assert(2 * pointCount == position.size()); + multiPathImpl.setAttributeStreamRef(VertexDescription.Semantics.POSITION, position); multiPathImpl.setPathStreamRef(paths); multiPathImpl.setPathFlagsStreamRef(path_flags); - if (zs != null) { - multiPathImpl.setAttributeStreamRef( - VertexDescription.Semantics.Z, zs); + multiPathImpl.setAttributeStreamRef(VertexDescription.Semantics.Z, zs); } - if (ms != null) { - multiPathImpl.setAttributeStreamRef( - VertexDescription.Semantics.M, ms); + multiPathImpl.setAttributeStreamRef(VertexDescription.Semantics.M, ms); } - multiPathImpl.notifyModified(MultiPathImpl.DirtyFlags.DirtyAll); - - AttributeStreamOfInt8 path_flags_clone = new AttributeStreamOfInt8( - path_flags); - + AttributeStreamOfInt8 path_flags_clone = new AttributeStreamOfInt8(path_flags); for (int i = 0; i < path_flags_clone.size() - 1; i++) { if (((int) path_flags_clone.read(i) & (int) PathFlags.enumOGCStartPolygon) != 0) {// Should // be @@ -253,23 +1335,17 @@ private static Geometry polygonTaggedText_(boolean bMultiPolygon, multiPathImpl.reversePath(i); // make counter-clockwise } } - multiPathImpl.setPathFlagsStreamRef(path_flags_clone); - } - if ((importFlags & (int) GeoJsonImportFlags.geoJsonImportNonTrusted) == 0) { - multiPathImpl.setIsSimple(MultiPathImpl.GeometryXSimple.Weak, 0.0, - false); + multiPathImpl.setIsSimple(MultiPathImpl.GeometryXSimple.Weak, 0.0, false); } - multiPathImpl.setDirtyOGCFlags(false); - return multiPath; } - private static Geometry lineStringTaggedText_(boolean bMultiLineString, - int importFlags, JSONArray coordinateArray) throws JSONException { + private static Geometry lineStringTaggedText_(boolean bMultiLineString, int importFlags, JSONArray coordinateArray) + throws JSONException { MultiPath multiPath; MultiPathImpl multiPathImpl; AttributeStreamOfDbl zs = null; @@ -277,265 +1353,190 @@ private static Geometry lineStringTaggedText_(boolean bMultiLineString, AttributeStreamOfDbl position; AttributeStreamOfInt32 paths; AttributeStreamOfInt8 path_flags; - - position = (AttributeStreamOfDbl) AttributeStreamBase - .createDoubleStream(0); - paths = (AttributeStreamOfInt32) AttributeStreamBase.createIndexStream( - 1, 0); - path_flags = (AttributeStreamOfInt8) AttributeStreamBase - .createByteStream(1, (byte) 0); - + position = (AttributeStreamOfDbl) AttributeStreamBase.createDoubleStream(0); + paths = (AttributeStreamOfInt32) AttributeStreamBase.createIndexStream(1, 0); + path_flags = (AttributeStreamOfInt8) AttributeStreamBase.createByteStream(1, (byte) 0); multiPath = new Polyline(); multiPathImpl = (MultiPathImpl) multiPath._getImpl(); - int pointCount; - if (bMultiLineString) { - pointCount = multiLineStringText_(zs, ms, position, paths, - path_flags, coordinateArray); + pointCount = multiLineStringText_(zs, ms, position, paths, path_flags, coordinateArray); } else { - pointCount = lineStringText_(false, zs, ms, position, paths, - path_flags, coordinateArray); + pointCount = lineStringText_(false, zs, ms, position, paths, path_flags, coordinateArray); } - if (pointCount != 0) { - assert (2 * pointCount == position.size()); - multiPathImpl.setAttributeStreamRef( - VertexDescription.Semantics.POSITION, position); + assert(2 * pointCount == position.size()); + multiPathImpl.setAttributeStreamRef(VertexDescription.Semantics.POSITION, position); multiPathImpl.setPathStreamRef(paths); multiPathImpl.setPathFlagsStreamRef(path_flags); - if (zs != null) { - multiPathImpl.setAttributeStreamRef( - VertexDescription.Semantics.Z, zs); + multiPathImpl.setAttributeStreamRef(VertexDescription.Semantics.Z, zs); } - if (ms != null) { - multiPathImpl.setAttributeStreamRef( - VertexDescription.Semantics.M, ms); + multiPathImpl.setAttributeStreamRef(VertexDescription.Semantics.M, ms); } - multiPathImpl.notifyModified(MultiPathImpl.DirtyFlags.DirtyAll); } - return multiPath; } - private static Geometry multiPointTaggedText_(int importFlags, - JSONArray coordinateArray) throws JSONException { + private static Geometry multiPointTaggedText_(int importFlags, JSONArray coordinateArray) throws JSONException { MultiPoint multiPoint; MultiPointImpl multiPointImpl; AttributeStreamOfDbl zs = null; AttributeStreamOfDbl ms = null; AttributeStreamOfDbl position; - - position = (AttributeStreamOfDbl) AttributeStreamBase - .createDoubleStream(0); - + position = (AttributeStreamOfDbl) AttributeStreamBase.createDoubleStream(0); multiPoint = new MultiPoint(); multiPointImpl = (MultiPointImpl) multiPoint._getImpl(); - int pointCount = multiPointText_(zs, ms, position, coordinateArray); - if (pointCount != 0) { - assert (2 * pointCount == position.size()); + assert(2 * pointCount == position.size()); multiPointImpl.resize(pointCount); - multiPointImpl.setAttributeStreamRef( - VertexDescription.Semantics.POSITION, position); - + multiPointImpl.setAttributeStreamRef(VertexDescription.Semantics.POSITION, position); multiPointImpl.notifyModified(MultiPointImpl.DirtyFlags.DirtyAll); } - return multiPoint; } - private static Geometry pointTaggedText_(int importFlags, - JSONArray coordinateArray) throws JSONException { + private static Geometry pointTaggedText_(int importFlags, JSONArray coordinateArray) throws JSONException { Point point = new Point(); - int length = coordinateArray.length(); - if (length == 0) { point.setEmpty(); return point; } - - point.setXY(getDouble_(coordinateArray, 0), - getDouble_(coordinateArray, 1)); - + point.setXY(getDouble_(coordinateArray, 0), getDouble_(coordinateArray, 1)); return point; } - private static int multiPolygonText_(AttributeStreamOfDbl zs, - AttributeStreamOfDbl ms, AttributeStreamOfDbl position, - AttributeStreamOfInt32 paths, AttributeStreamOfInt8 path_flags, + private static int multiPolygonText_(AttributeStreamOfDbl zs, AttributeStreamOfDbl ms, + AttributeStreamOfDbl position, AttributeStreamOfInt32 paths, AttributeStreamOfInt8 path_flags, JSONArray coordinateArray) throws JSONException { // At start of MultiPolygonText - int totalPointCount = 0; int length = coordinateArray.length(); - if (length == 0) return totalPointCount; - for (int current = 0; current < length; current++) { JSONArray subArray = coordinateArray.optJSONArray(current); - if (subArray == null) { - // Entry should be a JSONArray representing a polygon, but it is - // not a JSONArray. + if (subArray == null) {// Entry should be a JSONArray representing a + // polygon, but it is not a JSONArray. throw new IllegalArgumentException(""); } // At start of PolygonText - - totalPointCount = polygonText_(zs, ms, position, paths, path_flags, - totalPointCount, subArray); + totalPointCount = polygonText_(zs, ms, position, paths, path_flags, totalPointCount, subArray); } - return totalPointCount; } - private static int multiLineStringText_(AttributeStreamOfDbl zs, - AttributeStreamOfDbl ms, AttributeStreamOfDbl position, - AttributeStreamOfInt32 paths, AttributeStreamOfInt8 path_flags, + private static int multiLineStringText_(AttributeStreamOfDbl zs, AttributeStreamOfDbl ms, + AttributeStreamOfDbl position, AttributeStreamOfInt32 paths, AttributeStreamOfInt8 path_flags, JSONArray coordinateArray) throws JSONException { // At start of MultiLineStringText - int totalPointCount = 0; int length = coordinateArray.length(); - if (length == 0) return totalPointCount; - for (int current = 0; current < length; current++) { JSONArray subArray = coordinateArray.optJSONArray(current); - if (subArray == null) { - // Entry should be a JSONArray representing a line string, but - // it is not a JSONArray. + if (subArray == null) {// Entry should be a JSONArray representing a + // line string, but it is not a JSONArray. throw new IllegalArgumentException(""); } // At start of LineStringText - - totalPointCount += lineStringText_(false, zs, ms, position, paths, - path_flags, subArray); + totalPointCount += lineStringText_(false, zs, ms, position, paths, path_flags, subArray); } - return totalPointCount; } - - private static int multiPointText_(AttributeStreamOfDbl zs, - AttributeStreamOfDbl ms, AttributeStreamOfDbl position, + + private static int multiPointText_(AttributeStreamOfDbl zs, AttributeStreamOfDbl ms, AttributeStreamOfDbl position, JSONArray coordinateArray) throws JSONException { // At start of MultiPointText - int pointCount = 0; - for (int current = 0; current < coordinateArray.length(); current++) { JSONArray subArray = coordinateArray.optJSONArray(current); - if (subArray == null) { - // Entry should be a JSONArray representing a point, but it is - // not a JSONArray. + if (subArray == null) {// Entry should be a JSONArray representing a + // point, but it is not a JSONArray. throw new IllegalArgumentException(""); } - pointCount += pointText_(zs, ms, position, subArray); } - return pointCount; } - private static int polygonText_(AttributeStreamOfDbl zs, - AttributeStreamOfDbl ms, AttributeStreamOfDbl position, - AttributeStreamOfInt32 paths, AttributeStreamOfInt8 path_flags, - int totalPointCount, JSONArray coordinateArray) - throws JSONException { + private static int polygonText_(AttributeStreamOfDbl zs, AttributeStreamOfDbl ms, AttributeStreamOfDbl position, + AttributeStreamOfInt32 paths, AttributeStreamOfInt8 path_flags, int totalPointCount, + JSONArray coordinateArray) throws JSONException { // At start of PolygonText - int length = coordinateArray.length(); - if (length == 0) { return totalPointCount; } - boolean bFirstLineString = true; - for (int current = 0; current < length; current++) { JSONArray subArray = coordinateArray.optJSONArray(current); - if (subArray == null) { - // Entry should be a JSONArray representing a line string, but - // it is not a JSONArray. + if (subArray == null) {// Entry should be a JSONArray representing a + // line string, but it is not a JSONArray. throw new IllegalArgumentException(""); } - // At start of LineStringText - - int pointCount = lineStringText_(true, zs, ms, position, paths, - path_flags, subArray); - + int pointCount = lineStringText_(true, zs, ms, position, paths, path_flags, subArray); if (pointCount != 0) { if (bFirstLineString) { bFirstLineString = false; - path_flags.setBits(path_flags.size() - 2, - (byte) PathFlags.enumOGCStartPolygon); + path_flags.setBits(path_flags.size() - 2, (byte) PathFlags.enumOGCStartPolygon); } - - path_flags.setBits(path_flags.size() - 2, - (byte) PathFlags.enumClosed); + path_flags.setBits(path_flags.size() - 2, (byte) PathFlags.enumClosed); totalPointCount += pointCount; } } - return totalPointCount; } - - private static int lineStringText_(boolean bRing, AttributeStreamOfDbl zs, - AttributeStreamOfDbl ms, AttributeStreamOfDbl position, - AttributeStreamOfInt32 paths, AttributeStreamOfInt8 path_flags, + + private static int lineStringText_(boolean bRing, AttributeStreamOfDbl zs, AttributeStreamOfDbl ms, + AttributeStreamOfDbl position, AttributeStreamOfInt32 paths, AttributeStreamOfInt8 path_flags, JSONArray coordinateArray) throws JSONException { // At start of LineStringText - int pointCount = 0; int length = coordinateArray.length(); - if (length == 0) return pointCount; - boolean bStartPath = true; double startX = NumberUtils.TheNaN; double startY = NumberUtils.TheNaN; double startZ = NumberUtils.TheNaN; double startM = NumberUtils.TheNaN; - for (int current = 0; current < length; current++) { JSONArray subArray = coordinateArray.optJSONArray(current); - if (subArray == null) { - // Entry should be a JSONArray representing a single point, but - // it is not a JSONArray. + if (subArray == null) {// Entry should be a JSONArray representing a + // single point, but it is not a JSONArray. throw new IllegalArgumentException(""); } - // At start of x - double x = getDouble_(subArray, 0); double y = getDouble_(subArray, 1); double z = NumberUtils.TheNaN; double m = NumberUtils.TheNaN; - boolean bAddPoint = true; - - if (bRing && pointCount >= 2 && current == length - 1) { - // If the last point in the ring is not equal to the start - // point, then let's add it. - - if ((startX == x || (NumberUtils.isNaN(startX) && NumberUtils - .isNaN(x))) - && (startY == y || (NumberUtils.isNaN(startY) && NumberUtils - .isNaN(y)))) { + if (bRing && pointCount >= 2 && current == length - 1) {// If the + // last + // point in + // the ring + // is not + // equal to + // the start + // point, + // then + // let's add + // it. + if ((startX == x || (NumberUtils.isNaN(startX) && NumberUtils.isNaN(x))) + && (startY == y || (NumberUtils.isNaN(startY) && NumberUtils.isNaN(y)))) { bAddPoint = false; } } - if (bAddPoint) { if (bStartPath) { bStartPath = false; @@ -544,72 +1545,54 @@ private static int lineStringText_(boolean bRing, AttributeStreamOfDbl zs, startZ = z; startM = m; } - pointCount++; addToStreams_(zs, ms, position, x, y, z, m); } } - if (pointCount == 1) { pointCount++; addToStreams_(zs, ms, position, startX, startY, startZ, startM); } - paths.add(position.size() / 2); path_flags.add((byte) 0); - return pointCount; } - private static int pointText_(AttributeStreamOfDbl zs, - AttributeStreamOfDbl ms, AttributeStreamOfDbl position, + private static int pointText_(AttributeStreamOfDbl zs, AttributeStreamOfDbl ms, AttributeStreamOfDbl position, JSONArray coordinateArray) throws JSONException { // At start of PointText - int length = coordinateArray.length(); - if (length == 0) return 0; - // At start of x - double x = getDouble_(coordinateArray, 0); double y = getDouble_(coordinateArray, 1); double z = NumberUtils.TheNaN; double m = NumberUtils.TheNaN; - addToStreams_(zs, ms, position, x, y, z, m); - return 1; } - private static void addToStreams_(AttributeStreamOfDbl zs, - AttributeStreamOfDbl ms, AttributeStreamOfDbl position, double x, - double y, double z, double m) { + private static void addToStreams_(AttributeStreamOfDbl zs, AttributeStreamOfDbl ms, AttributeStreamOfDbl position, + double x, double y, double z, double m) { position.add(x); position.add(y); - if (zs != null) zs.add(z); - if (ms != null) ms.add(m); } - private static double getDouble_(JSONArray coordinateArray, int index) - throws JSONException { + private static double getDouble_(JSONArray coordinateArray, int index) throws JSONException { if (index < 0 || index >= coordinateArray.length()) { throw new IllegalArgumentException(""); } - if (coordinateArray.isNull(index)) { return NumberUtils.TheNaN; } - if (coordinateArray.optDouble(index, NumberUtils.TheNaN) != NumberUtils.TheNaN) { return coordinateArray.getDouble(index); } - throw new IllegalArgumentException(""); - } + }*/ } diff --git a/src/main/java/com/esri/core/geometry/OperatorImportFromJsonCursor.java b/src/main/java/com/esri/core/geometry/OperatorImportFromJsonCursor.java index 56f79a87..8c267d7a 100644 --- a/src/main/java/com/esri/core/geometry/OperatorImportFromJsonCursor.java +++ b/src/main/java/com/esri/core/geometry/OperatorImportFromJsonCursor.java @@ -271,7 +271,6 @@ static MapGeometry importFromJsonParser(int gt, JsonReader parser) { mp = new MapGeometry(geometry, spatial_reference); } catch (Exception e) { - e.printStackTrace(); return null; } diff --git a/src/main/java/com/esri/core/geometry/OperatorIntersection.java b/src/main/java/com/esri/core/geometry/OperatorIntersection.java index 2f23f11c..ee3b4833 100644 --- a/src/main/java/com/esri/core/geometry/OperatorIntersection.java +++ b/src/main/java/com/esri/core/geometry/OperatorIntersection.java @@ -59,8 +59,8 @@ public abstract GeometryCursor execute(GeometryCursor inputGeometries, *The bitmask of values (1 << dim), where dim is the desired dimension value, is used to indicate *what dimensions of geometry one wants to be returned. For example, to return *multipoints and lines only, pass (1 << 0) | (1 << 1), which is equivalen to 1 | 2, or 3. - *@return Returns the cursor of the intersection result. The cursors' get_geometry_ID method returns the current ID of the input geometry - *being processed. Wh dimensionMask is a bitmask, there will be n result geometries per one input geometry returned, where n is the number + *@return Returns the cursor of the intersection result. The cursors' getGeometryID method returns the current ID of the input geometry + *being processed. When dimensionMask is a bitmask, there will be n result geometries per one input geometry returned, where n is the number *of bits set in the bitmask. For example, if the dimensionMask is 5, there will be two geometries per one input geometry. * *The operator intersects every geometry in the input_geometries with the first geometry of the intersector and returns the result. @@ -68,7 +68,7 @@ public abstract GeometryCursor execute(GeometryCursor inputGeometries, *Note, when the dimensionMask is -1, then for each intersected pair of geometries, *the result has the lower of dimentions of the two geometries. That is, the dimension of the Polyline/Polyline intersection *is always 1 (that is, for polylines it never returns crossing points, but the overlaps only). - *If dimensionMask is 7, the operation will return any possible + *If dimensionMask is 7, the operation will return any possible intersections. */ public abstract GeometryCursor execute(GeometryCursor input_geometries, GeometryCursor intersector, SpatialReference sr, diff --git a/src/main/java/com/esri/core/geometry/OperatorShapePreservingDensify.java b/src/main/java/com/esri/core/geometry/OperatorShapePreservingDensify.java index 3a4a31ce..b25d66e0 100644 --- a/src/main/java/com/esri/core/geometry/OperatorShapePreservingDensify.java +++ b/src/main/java/com/esri/core/geometry/OperatorShapePreservingDensify.java @@ -43,7 +43,7 @@ public Type getType() { * @param maxLengthMeters The maximum segment length allowed. Must be a positive value to be used. Pass zero or NaN to disable densification by length. * @param maxDeviationMeters The maximum deviation. Must be a positive value to be used. Pass zero or NaN to disable densification by deviation. * @param reserved Must be 0 or NaN. Reserved for future use. Throws and exception if not NaN or 0. - * @return Returns the densified geometries (It does nothing to geometries with dim < 1, but simply passes them along). + * @return Returns the densified geometries (It does nothing to geometries with dim less than 1, but simply passes them along). * * The operation always starts from the lowest point on the segment, thus guaranteeing that topologically equal segments are always densified exactly the same. */ @@ -58,7 +58,7 @@ public Type getType() { * @param maxLengthMeters The maximum segment length allowed. Must be a positive value to be used. Pass zero or NaN to disable densification by length. * @param maxDeviationMeters The maximum deviation. Must be a positive value to be used. Pass zero or NaN to disable densification by deviation. * @param reserved Must be 0 or NaN. Reserved for future use. Throws and exception if not NaN or 0. - * @return Returns the densified geometries (It does nothing to geometries with dim < 1, but simply passes them along). + * @return Returns the densified geometries (It does nothing to geometries with dim less than 1, but simply passes them along). * * The operation always starts from the lowest point on the segment, thus guaranteeing that topologically equal segments are always densified exactly the same. */ diff --git a/src/main/java/com/esri/core/geometry/Point.java b/src/main/java/com/esri/core/geometry/Point.java index 0f553257..2343c5df 100644 --- a/src/main/java/com/esri/core/geometry/Point.java +++ b/src/main/java/com/esri/core/geometry/Point.java @@ -33,10 +33,9 @@ * location in a two-dimensional XY-Plane. In case of Geographic Coordinate * Systems, the X coordinate is the longitude and the Y is the latitude. */ -public final class Point extends Geometry implements Serializable { - private static final long serialVersionUID = 2L;// TODO:remove as we use - // writeReplace and - // GeometrySerializer +public class Point extends Geometry implements Serializable { + //We are using writeReplace instead. + //private static final long serialVersionUID = 2L; double[] m_attributes; // use doubles to store everything (long are bitcast) @@ -47,7 +46,7 @@ public Point() { m_description = VertexDescriptionDesignerImpl.getDefaultDescriptor2D(); } - Point(VertexDescription vd) { + public Point(VertexDescription vd) { if (vd == null) throw new IllegalArgumentException(); m_description = vd; @@ -129,7 +128,7 @@ public final void setXY(Point2D pt) { /** * Returns XYZ coordinates of the point. Z will be set to 0 if Z is missing. */ - Point3D getXYZ() { + public Point3D getXYZ() { if (isEmptyImpl()) throw new GeometryException( "This operation should not be performed on an empty geometry."); @@ -151,7 +150,7 @@ Point3D getXYZ() { * @param pt * The point to create the XYZ coordinate from. */ - void setXYZ(Point3D pt) { + public void setXYZ(Point3D pt) { _touch(); boolean bHasZ = hasAttribute(Semantics.Z); if (!bHasZ && !VertexDescription.isDefaultValue(Semantics.Z, pt.z)) {// add @@ -388,7 +387,7 @@ protected void _assignVertexDescriptionImpl(VertexDescription newDescription) { int[] mapping = VertexDescriptionDesignerImpl.mapAttributes(newDescription, m_description); - double[] newAttributes = new double[newDescription._getTotalComponents()]; + double[] newAttributes = new double[newDescription.getTotalComponentCount()]; int j = 0; for (int i = 0, n = newDescription.getAttributeCount(); i < n; i++) { @@ -424,9 +423,9 @@ protected void _assignVertexDescriptionImpl(VertexDescription newDescription) { * Sets the Point to a default, non-empty state. */ void _setToDefault() { - resizeAttributes(m_description._getTotalComponents()); + resizeAttributes(m_description.getTotalComponentCount()); Point.attributeCopy(m_description._getDefaultPointAttributes(), - m_attributes, m_description._getTotalComponents()); + m_attributes, m_description.getTotalComponentCount()); m_attributes[0] = NumberUtils.NaN(); m_attributes[1] = NumberUtils.NaN(); } @@ -465,9 +464,9 @@ public void copyTo(Geometry dst) { pointDst.assignVertexDescription(m_description); } else { pointDst.assignVertexDescription(m_description); - pointDst.resizeAttributes(m_description._getTotalComponents()); + pointDst.resizeAttributes(m_description.getTotalComponentCount()); attributeCopy(m_attributes, pointDst.m_attributes, - m_description._getTotalComponents()); + m_description.getTotalComponentCount()); } } @@ -595,7 +594,7 @@ public boolean equals(Object _other) { else return false; - for (int i = 0, n = m_description._getTotalComponents(); i < n; i++) + for (int i = 0, n = m_description.getTotalComponentCount(); i < n; i++) if (m_attributes[i] != otherPt.m_attributes[i]) return false; @@ -610,7 +609,7 @@ public boolean equals(Object _other) { public int hashCode() { int hashCode = m_description.hashCode(); if (!isEmptyImpl()) { - for (int i = 0, n = m_description._getTotalComponents(); i < n; i++) { + for (int i = 0, n = m_description.getTotalComponentCount(); i < n; i++) { long bits = Double.doubleToLongBits(m_attributes[i]); int hc = (int) (bits ^ (bits >>> 32)); hashCode = NumberUtils.hash(hashCode, hc); diff --git a/src/main/java/com/esri/core/geometry/Point2D.java b/src/main/java/com/esri/core/geometry/Point2D.java index f9fa71ab..245b8156 100644 --- a/src/main/java/com/esri/core/geometry/Point2D.java +++ b/src/main/java/com/esri/core/geometry/Point2D.java @@ -48,6 +48,10 @@ public Point2D(double x, double y) { this.y = y; } + public Point2D(Point2D other) { + setCoords(other); + } + public static Point2D construct(double x, double y) { return new Point2D(x, y); } @@ -122,20 +126,29 @@ public void negate(Point2D other) { } public void interpolate(Point2D other, double alpha) { - x = x * (1.0 - alpha) + other.x * alpha; - y = y * (1.0 - alpha) + other.y * alpha; + MathUtils.lerp(this, other, alpha, this); } public void interpolate(Point2D p1, Point2D p2, double alpha) { - x = p1.x * (1.0 - alpha) + p2.x * alpha; - y = p1.y * (1.0 - alpha) + p2.y * alpha; + MathUtils.lerp(p1, p2, alpha, this); } - + + /** + * Calculates this = this * f + shift + * @param f + * @param shift + */ public void scaleAdd(double f, Point2D shift) { x = x * f + shift.x; y = y * f + shift.y; } + /** + * Calculates this = other * f + shift + * @param f + * @param other + * @param shift + */ public void scaleAdd(double f, Point2D other, Point2D shift) { x = other.x * f + shift.x; y = other.y * f + shift.y; @@ -152,12 +165,19 @@ public void scale(double f) { } /** - * Compares two vertices lexicographicaly. + * Compares two vertices lexicographically by y. */ public int compare(Point2D other) { return y < other.y ? -1 : (y > other.y ? 1 : (x < other.x ? -1 : (x > other.x ? 1 : 0))); } + /** + * Compares two vertices lexicographically by x. + */ + int compareX(Point2D other) { + return x < other.x ? -1 : (x > other.x ? 1 : (y < other.y ? -1 + : (y > other.y ? 1 : 0))); + } public void normalize(Point2D other) { double len = other.length(); @@ -266,7 +286,7 @@ void _setNan() { } boolean _isNan() { - return NumberUtils.isNaN(x); + return NumberUtils.isNaN(x) || NumberUtils.isNaN(y); } // calculates which quarter of xy plane the vector lies in. First quater is @@ -475,9 +495,269 @@ public static int orientationRobust(Point2D p, Point2D q, Point2D r) { return det_mp.signum(); } + private static int inCircleRobustMP_(Point2D p, Point2D q, Point2D r, Point2D s) { + BigDecimal sx_mp = new BigDecimal(s.x), sy_mp = new BigDecimal(s.y); + + BigDecimal psx_mp = new BigDecimal(p.x), psy_mp = new BigDecimal(p.y); + psx_mp = psx_mp.subtract(sx_mp); + psy_mp = psy_mp.subtract(sy_mp); + + BigDecimal qsx_mp = new BigDecimal(q.x), qsy_mp = new BigDecimal(q.y); + qsx_mp = qsx_mp.subtract(sx_mp); + qsy_mp = qsy_mp.subtract(sy_mp); + + BigDecimal rsx_mp = new BigDecimal(r.x), rsy_mp = new BigDecimal(r.y); + rsx_mp = rsx_mp.subtract(sx_mp); + rsy_mp = rsy_mp.subtract(sy_mp); + + BigDecimal pq_det_mp = psx_mp.multiply(qsy_mp).subtract(psy_mp.multiply(qsx_mp)); + BigDecimal qr_det_mp = qsx_mp.multiply(rsy_mp).subtract(qsy_mp.multiply(rsx_mp)); + BigDecimal pr_det_mp = psx_mp.multiply(rsy_mp).subtract(psy_mp.multiply(rsx_mp)); + + BigDecimal p_parab_mp = psx_mp.multiply(psx_mp).add(psy_mp.multiply(psy_mp)); + BigDecimal q_parab_mp = qsx_mp.multiply(qsx_mp).add(qsy_mp.multiply(qsy_mp)); + BigDecimal r_parab_mp = rsx_mp.multiply(rsx_mp).add(rsy_mp.multiply(rsy_mp)); + + BigDecimal det_mp = (p_parab_mp.multiply(qr_det_mp).subtract(q_parab_mp.multiply(pr_det_mp))) + .add(r_parab_mp.multiply(pq_det_mp)); + + return det_mp.signum(); + } + + /** + * Calculates if the point s is inside of the circumcircle inscribed by the clockwise oriented triangle p-q-r. + * Returns 1 for outside, -1 for inside, and 0 for cocircular. + * Note that the convention used here differs from what is commonly found in literature, which can define the relation + * in terms of a counter-clockwise oriented circle, and this flips the sign (think of the signed volume of the tetrahedron). + * May use high precision arithmetics for some special cases. + */ + static int inCircleRobust(Point2D p, Point2D q, Point2D r, Point2D s) { + ECoordinate psx_ec = new ECoordinate(), psy_ec = new ECoordinate(); + psx_ec.set(p.x); + psx_ec.sub(s.x); + psy_ec.set(p.y); + psy_ec.sub(s.y); + + ECoordinate qsx_ec = new ECoordinate(), qsy_ec = new ECoordinate(); + qsx_ec.set(q.x); + qsx_ec.sub(s.x); + qsy_ec.set(q.y); + qsy_ec.sub(s.y); + + ECoordinate rsx_ec = new ECoordinate(), rsy_ec = new ECoordinate(); + rsx_ec.set(r.x); + rsx_ec.sub(s.x); + rsy_ec.set(r.y); + rsy_ec.sub(s.y); + + ECoordinate psx_ec_qsy_ec = new ECoordinate(); + psx_ec_qsy_ec.set(psx_ec); + psx_ec_qsy_ec.mul(qsy_ec); + ECoordinate psy_ec_qsx_ec = new ECoordinate(); + psy_ec_qsx_ec.set(psy_ec); + psy_ec_qsx_ec.mul(qsx_ec); + ECoordinate qsx_ec_rsy_ec = new ECoordinate(); + qsx_ec_rsy_ec.set(qsx_ec); + qsx_ec_rsy_ec.mul(rsy_ec); + ECoordinate qsy_ec_rsx_ec = new ECoordinate(); + qsy_ec_rsx_ec.set(qsy_ec); + qsy_ec_rsx_ec.mul(rsx_ec); + ECoordinate psx_ec_rsy_ec = new ECoordinate(); + psx_ec_rsy_ec.set(psx_ec); + psx_ec_rsy_ec.mul(rsy_ec); + ECoordinate psy_ec_rsx_ec = new ECoordinate(); + psy_ec_rsx_ec.set(psy_ec); + psy_ec_rsx_ec.mul(rsx_ec); + + ECoordinate pq_det_ec = new ECoordinate(); + pq_det_ec.set(psx_ec_qsy_ec); + pq_det_ec.sub(psy_ec_qsx_ec); + ECoordinate qr_det_ec = new ECoordinate(); + qr_det_ec.set(qsx_ec_rsy_ec); + qr_det_ec.sub(qsy_ec_rsx_ec); + ECoordinate pr_det_ec = new ECoordinate(); + pr_det_ec.set(psx_ec_rsy_ec); + pr_det_ec.sub(psy_ec_rsx_ec); + + ECoordinate psx_ec_psx_ec = new ECoordinate(); + psx_ec_psx_ec.set(psx_ec); + psx_ec_psx_ec.mul(psx_ec); + ECoordinate psy_ec_psy_ec = new ECoordinate(); + psy_ec_psy_ec.set(psy_ec); + psy_ec_psy_ec.mul(psy_ec); + ECoordinate qsx_ec_qsx_ec = new ECoordinate(); + qsx_ec_qsx_ec.set(qsx_ec); + qsx_ec_qsx_ec.mul(qsx_ec); + ECoordinate qsy_ec_qsy_ec = new ECoordinate(); + qsy_ec_qsy_ec.set(qsy_ec); + qsy_ec_qsy_ec.mul(qsy_ec); + ECoordinate rsx_ec_rsx_ec = new ECoordinate(); + rsx_ec_rsx_ec.set(rsx_ec); + rsx_ec_rsx_ec.mul(rsx_ec); + ECoordinate rsy_ec_rsy_ec = new ECoordinate(); + rsy_ec_rsy_ec.set(rsy_ec); + rsy_ec_rsy_ec.mul(rsy_ec); + + ECoordinate p_parab_ec = new ECoordinate(); + p_parab_ec.set(psx_ec_psx_ec); + p_parab_ec.add(psy_ec_psy_ec); + ECoordinate q_parab_ec = new ECoordinate(); + q_parab_ec.set(qsx_ec_qsx_ec); + q_parab_ec.add(qsy_ec_qsy_ec); + ECoordinate r_parab_ec = new ECoordinate(); + r_parab_ec.set(rsx_ec_rsx_ec); + r_parab_ec.add(rsy_ec_rsy_ec); + + p_parab_ec.mul(qr_det_ec); + q_parab_ec.mul(pr_det_ec); + r_parab_ec.mul(pq_det_ec); + + ECoordinate det_ec = new ECoordinate(); + det_ec.set(p_parab_ec); + det_ec.sub(q_parab_ec); + det_ec.add(r_parab_ec); + + if (!det_ec.isFuzzyZero()) { + double det_ec_value = det_ec.value(); + + if (det_ec_value < 0.0) + return -1; + + if (det_ec_value > 0.0) + return 1; + + return 0; + } + + return inCircleRobustMP_(p, q, r, s); + } + + private static Point2D calculateCenterFromThreePointsHelperMP_(Point2D from, Point2D mid_point, Point2D to) { + assert(!mid_point.isEqual(to) && !mid_point.isEqual(from) && !from.isEqual(to)); + BigDecimal mx = new BigDecimal(mid_point.x); + mx = mx.subtract(new BigDecimal(from.x)); + BigDecimal my = new BigDecimal(mid_point.y); + my = my.subtract(new BigDecimal(from.y)); + BigDecimal tx = new BigDecimal(to.x); + tx = tx.subtract(new BigDecimal(from.x)); + BigDecimal ty = new BigDecimal(to.y); + ty = ty.subtract(new BigDecimal(from.y)); + + BigDecimal d = mx.multiply(ty); + BigDecimal tmp = my.multiply(tx); + d = d.subtract(tmp); + + if (d.signum() == 0) { + return Point2D.construct(NumberUtils.NaN(), NumberUtils.NaN()); + } + + d = d.multiply(new BigDecimal(2.0)); + + BigDecimal mx2 = mx.multiply(mx); + BigDecimal my2 = my.multiply(my); + BigDecimal m_norm2 = mx2.add(my2); + BigDecimal tx2 = tx.multiply(tx); + BigDecimal ty2 = ty.multiply(ty); + BigDecimal t_norm2 = tx2.add(ty2); + + BigDecimal xo = my.multiply(t_norm2); + tmp = ty.multiply(m_norm2); + xo = xo.subtract(tmp); + xo = xo.divide(d, BigDecimal.ROUND_HALF_EVEN); + + BigDecimal yo = mx.multiply(t_norm2); + tmp = tx.multiply(m_norm2); + yo = yo.subtract(tmp); + yo = yo.divide(d, BigDecimal.ROUND_HALF_EVEN); + + Point2D center = Point2D.construct(from.x - xo.doubleValue(), from.y + yo.doubleValue()); + return center; + } + + private static Point2D calculateCenterFromThreePointsHelper_(Point2D from, Point2D mid_point, Point2D to) { + assert(!mid_point.isEqual(to) && !mid_point.isEqual(from) && !from.isEqual(to)); + ECoordinate mx = new ECoordinate(mid_point.x); + mx.sub(from.x); + ECoordinate my = new ECoordinate(mid_point.y); + my.sub(from.y); + ECoordinate tx = new ECoordinate(to.x); + tx.sub(from.x); + ECoordinate ty = new ECoordinate(to.y); + ty.sub(from.y); + + ECoordinate d = new ECoordinate(mx); + d.mul(ty); + ECoordinate tmp = new ECoordinate(my); + tmp.mul(tx); + d.sub(tmp); + + if (d.value() == 0.0) { + return Point2D.construct(NumberUtils.NaN(), NumberUtils.NaN()); + } + + d.mul(2.0); + + ECoordinate mx2 = new ECoordinate(mx); + mx2.mul(mx); + ECoordinate my2 = new ECoordinate(my); + my2.mul(my); + ECoordinate m_norm2 = new ECoordinate(mx2); + m_norm2.add(my2); + ECoordinate tx2 = new ECoordinate(tx); + tx2.mul(tx); + ECoordinate ty2 = new ECoordinate(ty); + ty2.mul(ty); + ECoordinate t_norm2 = new ECoordinate(tx2); + t_norm2.add(ty2); + + ECoordinate xo = new ECoordinate(my); + xo.mul(t_norm2); + tmp = new ECoordinate(ty); + tmp.mul(m_norm2); + xo.sub(tmp); + xo.div(d); + + ECoordinate yo = new ECoordinate(mx); + yo.mul(t_norm2); + tmp = new ECoordinate(tx); + tmp.mul(m_norm2); + yo.sub(tmp); + yo.div(d); + + Point2D center = Point2D.construct(from.x - xo.value(), from.y + yo.value()); + double r1 = Point2D.construct(from.x - center.x, from.y - center.y).length(); + double r2 = Point2D.construct(mid_point.x - center.x, mid_point.y - center.y).length(); + double r3 = Point2D.construct(to.x - center.x, to.y - center.y).length(); + double base = r1 + Math.abs(from.x) + Math.abs(mid_point.x) + Math.abs(to.x) + Math.abs(from.y) + + Math.abs(mid_point.y) + Math.abs(to.y); + + double tol = 1e-15; + if ((Math.abs(r1 - r2) <= base * tol && Math.abs(r1 - r3) <= base * tol)) + return center;//returns center value for MP_value type or when calculated radius value for from - center, mid - center, and to - center are very close. + + return Point2D.construct(NumberUtils.NaN(), NumberUtils.NaN()); + } + + static Point2D calculateCircleCenterFromThreePoints(Point2D from, Point2D mid_point, Point2D to) { + if (from.isEqual(to) || from.isEqual(mid_point) || to.isEqual(mid_point)) { + return new Point2D(NumberUtils.NaN(), NumberUtils.NaN()); + } + + Point2D pt = calculateCenterFromThreePointsHelper_(from, mid_point, to); //use error tracking calculations + if (pt.isNaN()) + return calculateCenterFromThreePointsHelperMP_(from, mid_point, to); //use precise calculations + else { + return pt; + } + } + @Override public int hashCode() { return NumberUtils.hash(NumberUtils.hash(x), y); } + double getAxis(int ordinate) { + assert(ordinate == 0 || ordinate == 1); + return (ordinate == 0 ? x : y); + } } diff --git a/src/main/java/com/esri/core/geometry/Point3D.java b/src/main/java/com/esri/core/geometry/Point3D.java index a8316ff3..849b00e1 100644 --- a/src/main/java/com/esri/core/geometry/Point3D.java +++ b/src/main/java/com/esri/core/geometry/Point3D.java @@ -42,11 +42,17 @@ public final class Point3D implements Serializable { public Point3D() { } + public Point3D(Point3D other) { + setCoords(other); + } + + public Point3D(double x, double y, double z) { + setCoords(x, y, z); + } + public static Point3D construct(double x, double y, double z) { Point3D pt = new Point3D(); - pt.x = x; - pt.y = y; - pt.z = z; + pt.setCoords(x, y, z); return pt; } @@ -56,6 +62,10 @@ public void setCoords(double x, double y, double z) { this.z = z; } + public void setCoords(Point3D other) { + setCoords(other.x, other.y, other.z); + } + public void setZero() { x = 0.0; y = 0.0; @@ -64,38 +74,62 @@ public void setZero() { public void normalize() { double len = length(); - if (len != 0) - return; + if (len == 0) { + x = 1.0; + y = 0.0; + z = 0.0; + } else { + x /= len; + y /= len; + z /= len; + } + } - x /= len; - y /= len; - z /= len; + public double dotProduct(Point3D other) { + return x * other.x + y * other.y + z * other.z; + } + + public double sqrLength() { + return x * x + y * y + z * z; } public double length() { return Math.sqrt(x * x + y * y + z * z); } - public Point3D(double x, double y, double z) { - this.x = x; - this.y = y; - this.z = z; + public void sub(Point3D other) + { + x -= other.x; + y -= other.y; + z -= other.z; + } + + public void sub(Point3D p1, Point3D p2) { + x = p1.x - p2.x; + y = p1.y - p2.y; + z = p1.z - p2.z; } - public Point3D sub(Point3D other) { - return new Point3D(x - other.x, y - other.y, z - other.z); + public void scale(double f, Point3D other) { + x = f * other.x; + y = f * other.y; + z = f * other.z; } - public Point3D mul(double factor) { - return new Point3D(x * factor, y * factor, z * factor); + public void mul(double factor) { + x *= factor; + y *= factor; + z *= factor; } void _setNan() { x = NumberUtils.NaN(); + y = NumberUtils.NaN(); + z = NumberUtils.NaN(); } boolean _isNan() { - return NumberUtils.isNaN(x); + return NumberUtils.isNaN(x) || NumberUtils.isNaN(y) || NumberUtils.isNaN(z); } } diff --git a/src/main/java/com/esri/core/geometry/Polygon.java b/src/main/java/com/esri/core/geometry/Polygon.java index 6d2a97f4..cb027357 100644 --- a/src/main/java/com/esri/core/geometry/Polygon.java +++ b/src/main/java/com/esri/core/geometry/Polygon.java @@ -30,7 +30,7 @@ /** * A polygon is a collection of one or many interior or exterior rings. */ -public final class Polygon extends MultiPath implements Serializable { +public class Polygon extends MultiPath implements Serializable { private static final long serialVersionUID = 2L;// TODO:remove as we use // writeReplace and @@ -43,7 +43,7 @@ public Polygon() { m_impl = new MultiPathImpl(true); } - Polygon(VertexDescription vd) { + public Polygon(VertexDescription vd) { m_impl = new MultiPathImpl(true, vd); } diff --git a/src/main/java/com/esri/core/geometry/PolygonUtils.java b/src/main/java/com/esri/core/geometry/PolygonUtils.java index 26701d30..d56f2220 100644 --- a/src/main/java/com/esri/core/geometry/PolygonUtils.java +++ b/src/main/java/com/esri/core/geometry/PolygonUtils.java @@ -26,7 +26,7 @@ final class PolygonUtils { - enum PiPResult { + public enum PiPResult { PiPOutside, PiPInside, PiPBoundary }; @@ -34,7 +34,7 @@ enum PiPResult { /** * Tests if Point is inside the Polygon. Returns PiPOutside if not in * polygon, PiPInside if in the polygon, PiPBoundary is if on the border. It - * tests border only if the tolerance is > 0, otherwise PiPBoundary cannot + * tests border only if the tolerance is greater than 0, otherwise PiPBoundary cannot * be returned. Note: If the tolerance is not 0, the test is more expensive * because it calculates closest distance from a point to each segment. * @@ -79,7 +79,7 @@ static PiPResult isPointInPolygon2D(Polygon polygon, double inputPointXVal, /** * Tests if Point is inside the Polygon's ring. Returns PiPOutside if not in * ring, PiPInside if in the ring, PiPBoundary is if on the border. It tests - * border only if the tolerance is > 0, otherwise PiPBoundary cannot be + * border only if the tolerance is greater than 0, otherwise PiPBoundary cannot be * returned. Note: If the tolerance is not 0, the test is more expensive * because it calculates closest distance from a point to each segment. * @@ -127,33 +127,10 @@ public static PiPResult isPointInAnyOuterRing(Polygon polygon, // internal and external boundaries. } - // #ifndef DOTNET - // /** - // *Tests point is inside the Polygon for an array of points. - // *Returns PiPOutside if not in polygon, PiPInside if in the polygon, - // PiPBoundary is if on the border. - // *It tests border only if the tolerance is > 0, otherwise PiPBoundary - // cannot be returned. - // *Note: If the tolerance is not 0, the test is more expensive. - // * - // *O(n*m) complexity, where n is the number of polygon segments, m is the - // number of input points. - // */ - // static void TestPointsInPolygon2D(Polygon polygon, const Point2D* - // inputPoints, int count, double tolerance, PiPResult testResults) - // { - // LOCALREFCLASS2(Array, Point2D*, int, inputPointsArr, - // const_cast(inputPoints), count); - // LOCALREFCLASS2(Array, PolygonUtils::PiPResult*, - // int, testResultsArr, testResults, count); - // TestPointsInPolygon2D(polygon, inputPointsArr, count, tolerance, - // testResultsArr); - // } - // #endif /** * Tests point is inside the Polygon for an array of points. Returns * PiPOutside if not in polygon, PiPInside if in the polygon, PiPBoundary is - * if on the border. It tests border only if the tolerance is > 0, otherwise + * if on the border. It tests border only if the tolerance is greater than 0, otherwise * PiPBoundary cannot be returned. Note: If the tolerance is not 0, the test * is more expensive. * @@ -182,31 +159,11 @@ static void testPointsInPolygon2D(Polygon polygon, double[] xyStreamBuffer, xyStreamBuffer[i * 2 + 1], tolerance); } - // public static void testPointsInPolygon2D(Polygon polygon, Geometry geom, - // int count, double tolerance, PiPResult[] testResults) - // { - // if(geom.getType() == Type.Point) - // { - // - // } - // else if(Geometry.isMultiVertex(geom.getType())) - // { - // - // } - // - // - // if (inputPoints.length < count || testResults.length < count) - // throw new IllegalArgumentException();//GEOMTHROW(invalid_argument); - // - // for (int i = 0; i < count; i++) - // testResults[i] = isPointInPolygon2D(polygon, inputPoints[i], tolerance); - // } - /** * Tests point is inside an Area Geometry (Envelope, Polygon) for an array * of points. Returns PiPOutside if not in area, PiPInside if in the area, * PiPBoundary is if on the border. It tests border only if the tolerance is - * > 0, otherwise PiPBoundary cannot be returned. Note: If the tolerance is + * greater than 0, otherwise PiPBoundary cannot be returned. Note: If the tolerance is * not 0, the test is more expensive. * * O(n*m) complexity, where n is the number of polygon segments, m is the diff --git a/src/main/java/com/esri/core/geometry/Polyline.java b/src/main/java/com/esri/core/geometry/Polyline.java index b95e9f81..4c83a147 100644 --- a/src/main/java/com/esri/core/geometry/Polyline.java +++ b/src/main/java/com/esri/core/geometry/Polyline.java @@ -31,7 +31,7 @@ * A polyline is a collection of one or many paths. * */ -public final class Polyline extends MultiPath implements Serializable { +public class Polyline extends MultiPath implements Serializable { private static final long serialVersionUID = 2L;// TODO:remove as we use // writeReplace and @@ -44,7 +44,7 @@ public Polyline() { m_impl = new MultiPathImpl(false); } - Polyline(VertexDescription vd) { + public Polyline(VertexDescription vd) { m_impl = new MultiPathImpl(false, vd); } diff --git a/src/main/java/com/esri/core/geometry/PolylinePath.java b/src/main/java/com/esri/core/geometry/PolylinePath.java deleted file mode 100644 index 6d3342da..00000000 --- a/src/main/java/com/esri/core/geometry/PolylinePath.java +++ /dev/null @@ -1,77 +0,0 @@ -/* - Copyright 1995-2015 Esri - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - - For additional information, contact: - Environmental Systems Research Institute, Inc. - Attn: Contracts Dept - 380 New York Street - Redlands, California, USA 92373 - - email: contracts@esri.com - */ - -package com.esri.core.geometry; - -import java.util.Comparator; - -class PolylinePath { - Point2D m_fromPoint; - Point2D m_toPoint; - double m_fromDist; // from lower left corner; -1.0 if point is not on - // clipping bounday - double m_toDist; // from lower left corner; -1.0 if point is not on clipping - // bounday - int m_path; // from polyline - boolean m_used; - - public PolylinePath() { - } - - public PolylinePath(Point2D fromPoint, Point2D toPoint, double fromDist, - double toDist, int path) { - m_fromPoint = fromPoint; - m_toPoint = toPoint; - m_fromDist = fromDist; - m_toDist = toDist; - m_path = path; - m_used = false; - } - - void setValues(Point2D fromPoint, Point2D toPoint, double fromDist, - double toDist, int path) { - m_fromPoint = fromPoint; - m_toPoint = toPoint; - m_fromDist = fromDist; - m_toDist = toDist; - m_path = path; - m_used = false; - } - - // to be used in Use SORTARRAY - -} - -class PolylinePathComparator implements Comparator { - @Override - public int compare(PolylinePath v1, PolylinePath v2) { - if ((v1).m_fromDist < (v2).m_fromDist) - return -1; - else if ((v1).m_fromDist > (v2).m_fromDist) - return 1; - else - return 0; - } - -} diff --git a/src/main/java/com/esri/core/geometry/PtSrlzr.java b/src/main/java/com/esri/core/geometry/PtSrlzr.java new file mode 100644 index 00000000..68dd1aae --- /dev/null +++ b/src/main/java/com/esri/core/geometry/PtSrlzr.java @@ -0,0 +1,88 @@ +/* + Copyright 1995-2015 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ +package com.esri.core.geometry; + +import java.io.InvalidObjectException; +import java.io.ObjectStreamException; +import java.io.Serializable; + +//This is a writeReplace class for Point +public class PtSrlzr implements Serializable { + private static final long serialVersionUID = 1L; + double[] attribs; + int descriptionBitMask; + + public Object readResolve() throws ObjectStreamException { + Point point = null; + try { + VertexDescription vd = VertexDescriptionDesignerImpl + .getVertexDescription(descriptionBitMask); + point = new Point(vd); + if (attribs != null) { + point.setXY(attribs[0], attribs[1]); + int index = 2; + for (int i = 1, n = vd.getAttributeCount(); i < n; i++) { + int semantics = vd.getSemantics(i); + int comps = VertexDescription.getComponentCount(semantics); + for (int ord = 0; ord < comps; ord++) { + point.setAttribute(semantics, ord, attribs[index++]); + } + } + } + } catch (Exception ex) { + throw new InvalidObjectException("Cannot read geometry from stream"); + } + + return point; + } + + public void setGeometryByValue(Point point) throws ObjectStreamException { + try { + attribs = null; + if (point == null) { + descriptionBitMask = 1; + } + + VertexDescription vd = point.getDescription(); + descriptionBitMask = vd.m_semanticsBitArray; + if (point.isEmpty()) { + return; + } + + attribs = new double[vd.getTotalComponentCount()]; + attribs[0] = point.getX(); + attribs[1] = point.getY(); + int index = 2; + for (int i = 1, n = vd.getAttributeCount(); i < n; i++) { + int semantics = vd.getSemantics(i); + int comps = VertexDescription.getComponentCount(semantics); + for (int ord = 0; ord < comps; ord++) { + attribs[index++] = point.getAttributeAsDbl(semantics, ord); + } + } + } catch (Exception ex) { + throw new InvalidObjectException("Cannot serialize this geometry"); + } + } +} diff --git a/src/main/java/com/esri/core/geometry/QuadTree.java b/src/main/java/com/esri/core/geometry/QuadTree.java index 9f6be163..32d81c3c 100644 --- a/src/main/java/com/esri/core/geometry/QuadTree.java +++ b/src/main/java/com/esri/core/geometry/QuadTree.java @@ -28,24 +28,31 @@ public class QuadTree { public static final class QuadTreeIterator { /** - * Resets the iterator to an starting state on the Quad_tree. If the + * Resets the iterator to an starting state on the QuadTree. If the * input Geometry is a Line segment, then the query will be the segment. - * Otherwise the query will be the Envelope_2D bounding the Geometry. - * \param query The Geometry used for the query. \param tolerance The - * tolerance used for the intersection tests. + * Otherwise the query will be the Envelope2D bounding the Geometry. + * \param query The Geometry used for the query. + * \param tolerance The tolerance used for the intersection tests. */ public void resetIterator(Geometry query, double tolerance) { - m_impl.resetIterator(query, tolerance); + if (!m_b_sorted) + ((QuadTreeImpl.QuadTreeIteratorImpl) m_impl).resetIterator(query, tolerance); + else + ((QuadTreeImpl.QuadTreeSortedIteratorImpl) m_impl).resetIterator(query, tolerance); } /** - * Resets the iterator to a starting state on the Quad_tree using the - * input Envelope_2D as the query. \param query The Envelope_2D used for - * the query. \param tolerance The tolerance used for the intersection + * Resets the iterator to a starting state on the QuadTree using the + * input Envelope2D as the query. + * \param query The Envelope2D used for the query. + * \param tolerance The tolerance used for the intersection * tests. */ public void resetIterator(Envelope2D query, double tolerance) { - m_impl.resetIterator(query, tolerance); + if (!m_b_sorted) + ((QuadTreeImpl.QuadTreeIteratorImpl) m_impl).resetIterator(query, tolerance); + else + ((QuadTreeImpl.QuadTreeSortedIteratorImpl) m_impl).resetIterator(query, tolerance); } /** @@ -53,7 +60,10 @@ public void resetIterator(Envelope2D query, double tolerance) { * Element_handle. */ public int next() { - return m_impl.next(); + if (!m_b_sorted) + return ((QuadTreeImpl.QuadTreeIteratorImpl) m_impl).next(); + else + return ((QuadTreeImpl.QuadTreeSortedIteratorImpl) m_impl).next(); } /** @@ -63,136 +73,260 @@ Object getImpl_() { return m_impl; } - // Creates an iterator on the input Quad_tree_impl. The query will be - // the Envelope_2D bounding the input Geometry. - private QuadTreeIterator(Object obj) { - m_impl = (QuadTreeImpl.QuadTreeIteratorImpl) obj; + // Creates an iterator on the input QuadTreeImpl. The query will be + // the Envelope2D bounding the input Geometry. + private QuadTreeIterator(Object obj, boolean bSorted) { + + m_impl = obj; + m_b_sorted = bSorted; } - private QuadTreeImpl.QuadTreeIteratorImpl m_impl; - }; + private Object m_impl; + private boolean m_b_sorted; + } /** - * Creates a Quad_tree with the root having the extent of the input - * Envelope_2D, and height of the input height, where the root starts at - * height 0. Note that the height cannot be larger than 16 if on a 32 bit - * platform and 32 if on a 64 bit platform. \param extent The extent of the - * Quad_tree. \param height The max height of the Quad_tree. + * Creates a QuadTree with the root having the extent of the input + * Envelope2D, and height of the input height, where the root starts at height 0. + * \param extent The extent of the QuadTree. + * \param height The max height of the QuadTree. */ public QuadTree(Envelope2D extent, int height) { m_impl = new QuadTreeImpl(extent, height); } /** - * Inserts the element and bounding_box into the Quad_tree. Note that a copy + * Creates a QuadTree with the root having the extent of the input Envelope2D, and height of the input height, where the root starts at height 0. + * \param extent The extent of the QuadTreeImpl. + * \param height The max height of the QuadTreeImpl. + * \param bStoreDuplicates Put true to place elements deeper into the quad tree at intesecting quads, duplicates will be stored. Put false to only place elements into quads that can contain it.. + */ + public QuadTree(Envelope2D extent, int height, boolean bStoreDuplicates) { + m_impl = new QuadTreeImpl(extent, height, bStoreDuplicates); + } + + /** + * Inserts the element and bounding_box into the QuadTree. Note that a copy * will me made of the input bounding_box. Note that this will invalidate - * any active iterator on the Quad_tree. Returns an Element_handle - * corresponding to the element and bounding_box. \param element The element - * of the Geometry to be inserted. \param bounding_box The bounding_box of + * any active iterator on the QuadTree. Returns an Element_handle + * corresponding to the element and bounding_box. + * \param element The element of the Geometry to be inserted. + * \param bounding_box The bounding_box of * the Geometry to be inserted. */ - public int insert(int element, Envelope2D bounding_box) { - return m_impl.insert(element, bounding_box); + public int insert(int element, Envelope2D boundingBox) { + return m_impl.insert(element, boundingBox); } /** - * Inserts the element and bounding_box into the Quad_tree at the given + * Inserts the element and bounding_box into the QuadTree at the given * quad_handle. Note that a copy will me made of the input bounding_box. - * Note that this will invalidate any active iterator on the Quad_tree. + * Note that this will invalidate any active iterator on the QuadTree. * Returns an Element_handle corresponding to the element and bounding_box. - * \param element The element of the Geometry to be inserted. \param - * bounding_box The bounding_box of the Geometry to be inserted. \param - * hint_index A handle used as a hint where to place the element. This can + * \param element The element of the Geometry to be inserted. + * \param bounding_box The bounding_box of the Geometry to be inserted. + * \param hint_index A handle used as a hint where to place the element. This can * be a handle obtained from a previous insertion and is useful on data * having strong locality such as segments of a Polygon. */ - public int insert(int element, Envelope2D bounding_box, int hint_index) { - return m_impl.insert(element, bounding_box, hint_index); + public int insert(int element, Envelope2D boundingBox, int hintIndex) { + return m_impl.insert(element, boundingBox, hintIndex); } /** * Removes the element and bounding_box at the given element_handle. Note - * that this will invalidate any active iterator on the Quad_tree. \param - * element_handle The handle corresponding to the element and bounding_box + * that this will invalidate any active iterator on the QuadTree. + * \param element_handle The handle corresponding to the element and bounding_box * to be removed. */ - public void removeElement(int element_handle) { - m_impl.removeElement(element_handle); + public void removeElement(int elementHandle) { + m_impl.removeElement(elementHandle); + } + + /** + * Returns the element at the given element_handle. + * \param element_handle The handle corresponding to the element to be retrieved. + */ + public int getElement(int elementHandle) { + return m_impl.getElement(elementHandle); + } + + /** + * Returns the element extent at the given element_handle. + * \param element_handle The handle corresponding to the element extent to be retrieved. + */ + public Envelope2D getElementExtent(int elementHandle) { + return m_impl.getElementExtent(elementHandle); + } + + /** + * Returns the extent of all elements in the quad tree. + */ + public Envelope2D getDataExtent() { + return m_impl.getDataExtent(); + } + + /** + * Returns the extent of the quad tree. + */ + public Envelope2D getQuadTreeExtent() { + return m_impl.getQuadTreeExtent(); + } + + /** + * Returns the number of elements in the subtree rooted at the given quad_handle. + * \param quad_handle The handle corresponding to the quad. + */ + public int getSubTreeElementCount(int quadHandle) { + return m_impl.getSubTreeElementCount(quadHandle); + } + + /** + * Returns the number of elements contained in the subtree rooted at the given quad_handle. + * \param quad_handle The handle corresponding to the quad. + */ + public int getContainedSubTreeElementCount(int quadHandle) { + return m_impl.getContainedSubTreeElementCount(quadHandle); + } + + /** + * Returns the number of elements in the quad tree that intersect the qiven query. Some elements may be duplicated if the quad tree stores duplicates. + * \param query The Envelope2D used for the query. + * \param tolerance The tolerance used for the intersection tests. + * \param max_count If the intersection count becomes greater than or equal to the max_count, then max_count is returned. + */ + public int getIntersectionCount(Envelope2D query, double tolerance, int maxCount) { + return m_impl.getIntersectionCount(query, tolerance, maxCount); } /** - * Returns the element at the given element_handle. \param element_handle - * The handle corresponding to the element to be retrieved. + * Returns true if the quad tree has data intersecting the given query. + * \param query The Envelope2D used for the query. + * \param tolerance The tolerance used for the intersection tests. */ - public int getElement(int element_handle) { - return m_impl.getElement(element_handle); + public boolean hasData(Envelope2D query, double tolerance) { + return m_impl.hasData(query, tolerance); } /** * Returns the height of the quad at the given quad_handle. \param * quad_handle The handle corresponding to the quad. */ - public int getHeight(int quad_handle) { - return m_impl.getHeight(quad_handle); + public int getHeight(int quadHandle) { + return m_impl.getHeight(quadHandle); } /** - * Returns the extent of the quad at the given quad_handle. \param - * quad_handle The handle corresponding to the quad. + * Returns the max height the quad tree can grow to. */ - public Envelope2D getExtent(int quad_handle) { - return m_impl.getExtent(quad_handle); + public int getMaxHeight() { + return m_impl.getMaxHeight(); + } + + /** + * Returns the extent of the quad at the given quad_handle. + * \param quad_handle The handle corresponding to the quad. + */ + public Envelope2D getExtent(int quadHandle) { + return m_impl.getExtent(quadHandle); } /** * Returns the Quad_handle of the quad containing the given element_handle. * \param element_handle The handle corresponding to the element. */ - public int getQuad(int element_handle) { - return m_impl.getQuad(element_handle); + public int getQuad(int elementHandle) { + return m_impl.getQuad(elementHandle); } /** - * Returns the number of elements in the Quad_tree. + * Returns the number of elements in the QuadTree. */ public int getElementCount() { return m_impl.getElementCount(); } /** - * Gets an iterator on the Quad_tree. The query will be the Envelope_2D that + * Gets an iterator on the QuadTree. The query will be the Envelope2D that * bounds the input Geometry. To reuse the existing iterator on the same - * Quad_tree but with a new query, use the reset_iterator function on the - * Quad_tree_iterator. \param query The Geometry used for the query. If the + * QuadTree but with a new query, use the reset_iterator function on the + * QuadTree_iterator. + * \param query The Geometry used for the query. If the * Geometry is a Line segment, then the query will be the segment. Otherwise - * the query will be the Envelope_2D bounding the Geometry. \param tolerance - * The tolerance used for the intersection tests. + * the query will be the Envelope2D bounding the Geometry. + * \param tolerance The tolerance used for the intersection tests. */ public QuadTreeIterator getIterator(Geometry query, double tolerance) { - QuadTreeImpl.QuadTreeIteratorImpl iterator = m_impl.getIterator(query, - tolerance); - return new QuadTreeIterator(iterator); + QuadTreeImpl.QuadTreeIteratorImpl iterator = m_impl.getIterator(query, tolerance); + return new QuadTreeIterator(iterator, false); } /** - * Gets an iterator on the Quad_tree using the input Envelope_2D as the - * query. To reuse the existing iterator on the same Quad_tree but with a - * new query, use the reset_iterator function on the Quad_tree_iterator. - * \param query The Envelope_2D used for the query. \param tolerance The - * tolerance used for the intersection tests. + * Gets an iterator on the QuadTree using the input Envelope2D as the + * query. To reuse the existing iterator on the same QuadTree but with a + * new query, use the reset_iterator function on the QuadTree_iterator. + * \param query The Envelope2D used for the query. + * \param tolerance The tolerance used for the intersection tests. */ public QuadTreeIterator getIterator(Envelope2D query, double tolerance) { - QuadTreeImpl.QuadTreeIteratorImpl iterator = m_impl.getIterator(query, - tolerance); - return new QuadTreeIterator(iterator); + QuadTreeImpl.QuadTreeIteratorImpl iterator = m_impl.getIterator(query, tolerance); + return new QuadTreeIterator(iterator, false); } /** - * Gets an iterator on the Quad_tree. + * Gets an iterator on the QuadTree. */ public QuadTreeIterator getIterator() { QuadTreeImpl.QuadTreeIteratorImpl iterator = m_impl.getIterator(); - return new QuadTreeIterator(iterator); + return new QuadTreeIterator(iterator, false); + } + + /** + * Gets an iterator on the QuadTree. The query will be the Envelope2D that bounds the input Geometry. + * To reuse the existing iterator on the same QuadTree but with a new query, use the reset_iterator function on the QuadTree_iterator. + * \param query The Geometry used for the query. If the Geometry is a Line segment, then the query will be the segment. Otherwise the query will be the Envelope2D bounding the Geometry. + * \param tolerance The tolerance used for the intersection tests. + * \param bSorted Put true to iterate the quad tree in the order of the Element_types. + */ + public QuadTreeIterator getIterator(Geometry query, double tolerance, boolean bSorted) { + if (!bSorted) { + QuadTreeImpl.QuadTreeIteratorImpl iterator = m_impl.getIterator(query, tolerance); + return new QuadTreeIterator(iterator, false); + } else { + QuadTreeImpl.QuadTreeSortedIteratorImpl iterator = m_impl.getSortedIterator(query, tolerance); + return new QuadTreeIterator(iterator, true); + } + } + + /** + * Gets an iterator on the QuadTree using the input Envelope2D as the query. + * To reuse the existing iterator on the same QuadTree but with a new query, use the reset_iterator function on the QuadTree_iterator. + * \param query The Envelope2D used for the query. + * \param tolerance The tolerance used for the intersection tests. + * \param bSorted Put true to iterate the quad tree in the order of the Element_types. + */ + public QuadTreeIterator getIterator(Envelope2D query, double tolerance, boolean bSorted) { + if (!bSorted) { + QuadTreeImpl.QuadTreeIteratorImpl iterator = m_impl.getIterator(query, tolerance); + return new QuadTreeIterator(iterator, false); + } else { + QuadTreeImpl.QuadTreeSortedIteratorImpl iterator = m_impl.getSortedIterator(query, tolerance); + return new QuadTreeIterator(iterator, true); + } + } + + /** + * Gets an iterator on the QuadTree. + * \param bSorted Put true to iterate the quad tree in the order of the Element_types. + */ + public QuadTreeIterator getIterator(boolean bSorted) { + if (!bSorted) { + QuadTreeImpl.QuadTreeIteratorImpl iterator = m_impl.getIterator(); + return new QuadTreeIterator(iterator, false); + } else { + QuadTreeImpl.QuadTreeSortedIteratorImpl iterator = m_impl.getSortedIterator(); + return new QuadTreeIterator(iterator, true); + } } /** diff --git a/src/main/java/com/esri/core/geometry/QuadTreeImpl.java b/src/main/java/com/esri/core/geometry/QuadTreeImpl.java index cbef824b..fe48999a 100644 --- a/src/main/java/com/esri/core/geometry/QuadTreeImpl.java +++ b/src/main/java/com/esri/core/geometry/QuadTreeImpl.java @@ -42,7 +42,7 @@ void resetIterator(Geometry query, double tolerance) { query.queryLooseEnvelope2D(m_query_box); m_query_box.inflate(tolerance, tolerance); - if (m_query_box.isIntersecting(m_quad_tree.m_extent)) { + if (m_quad_tree.m_root != -1 && m_query_box.isIntersecting(m_quad_tree.m_extent)) { int type = query.getType().value(); m_b_linear = Geometry.isSegment(type); @@ -57,8 +57,7 @@ void resetIterator(Geometry query, double tolerance) { m_quads_stack.add(m_quad_tree.m_root); m_extents_stack.add(m_quad_tree.m_extent); - m_next_element_handle = m_quad_tree - .getFirstElement_(m_quad_tree.m_root); + m_next_element_handle = m_quad_tree.get_first_element_(m_quad_tree.m_root); } else m_next_element_handle = -1; } @@ -77,11 +76,10 @@ void resetIterator(Envelope2D query, double tolerance) { m_query_box.inflate(tolerance, tolerance); m_tolerance = NumberUtils.NaN(); // we don't need it - if (m_query_box.isIntersecting(m_quad_tree.m_extent)) { + if (m_quad_tree.m_root != -1 && m_query_box.isIntersecting(m_quad_tree.m_extent)) { m_quads_stack.add(m_quad_tree.m_root); m_extents_stack.add(m_quad_tree.m_extent); - m_next_element_handle = m_quad_tree - .getFirstElement_(m_quad_tree.m_root); + m_next_element_handle = m_quad_tree.get_first_element_(m_quad_tree.m_root); m_b_linear = false; } else m_next_element_handle = -1; @@ -104,7 +102,7 @@ int next() { Envelope2D extent_inf = null; Envelope2D[] child_extents = null; - if (m_b_linear) {// Should this memory be cached for reuse? + if (m_b_linear) { start = new Point2D(); end = new Point2D(); extent_inf = new Envelope2D(); @@ -113,10 +111,8 @@ int next() { boolean b_found_hit = false; while (!b_found_hit) { while (m_current_element_handle != -1) { - int current_box_handle = m_quad_tree - .getBoxHandle_(m_current_element_handle); - bounding_box = m_quad_tree - .getBoundingBox_(current_box_handle); + int current_data_handle = m_quad_tree.get_data_(m_current_element_handle); + bounding_box = m_quad_tree.get_bounding_box_value_(current_data_handle); if (bounding_box.isIntersecting(m_query_box)) { if (m_b_linear) { @@ -136,21 +132,14 @@ int next() { } // get next element_handle - m_current_element_handle = m_quad_tree - .getNextElement_(m_current_element_handle); + m_current_element_handle = m_quad_tree.get_next_element_(m_current_element_handle); } - // If m_current_element_handle equals -1, then we've exhausted - // our search in the current quadtree node + // If m_current_element_handle equals -1, then we've exhausted our search in the current quadtree node if (m_current_element_handle == -1) { - // get the last node from the stack and add the children - // whose extent intersects m_query_box + // get the last node from the stack and add the children whose extent intersects m_query_box int current_quad = m_quads_stack.getLast(); - Envelope2D current_extent = m_extents_stack - .get(m_extents_stack.size() - 1); - - double x_mid = 0.5 * (current_extent.xmin + current_extent.xmax); - double y_mid = 0.5 * (current_extent.ymin + current_extent.ymax); + Envelope2D current_extent = m_extents_stack.get(m_extents_stack.size() - 1); if (child_extents == null) { child_extents = new Envelope2D[4]; @@ -160,38 +149,30 @@ int next() { child_extents[3] = new Envelope2D(); } - setChildExtents_(current_extent, child_extents); + set_child_extents_(current_extent, child_extents); m_quads_stack.removeLast(); m_extents_stack.remove(m_extents_stack.size() - 1); for (int quadrant = 0; quadrant < 4; quadrant++) { - int child_handle = m_quad_tree.getChild_(current_quad, - quadrant); - - if (child_handle != -1 - && m_quad_tree - .getSubTreeElementCount(child_handle) > 0) { - if (child_extents[quadrant] - .isIntersecting(m_query_box)) { + int child_handle = m_quad_tree.get_child_(current_quad, quadrant); + + if (child_handle != -1 && m_quad_tree.getSubTreeElementCount(child_handle) > 0) { + if (child_extents[quadrant].isIntersecting(m_query_box)) { if (m_b_linear) { start.setCoords(m_query_start); end.setCoords(m_query_end); - extent_inf - .setCoords(child_extents[quadrant]); - extent_inf - .inflate(m_tolerance, m_tolerance); + extent_inf.setCoords(child_extents[quadrant]); + extent_inf.inflate(m_tolerance, m_tolerance); if (extent_inf.clipLine(start, end) > 0) { Envelope2D child_extent = new Envelope2D(); - child_extent - .setCoords(child_extents[quadrant]); + child_extent.setCoords(child_extents[quadrant]); m_quads_stack.add(child_handle); m_extents_stack.add(child_extent); } } else { Envelope2D child_extent = new Envelope2D(); - child_extent - .setCoords(child_extents[quadrant]); + child_extent.setCoords(child_extents[quadrant]); m_quads_stack.add(child_handle); m_extents_stack.add(child_extent); } @@ -204,24 +185,20 @@ int next() { if (m_quads_stack.size() == 0) return -1; - m_current_element_handle = m_quad_tree - .getFirstElement_(m_quads_stack.get(m_quads_stack - .size() - 1)); + m_current_element_handle = m_quad_tree.get_first_element_(m_quads_stack.get(m_quads_stack.size() - 1)); } } // We did not exhaust our search in the current node, so we return // the element at m_current_element_handle in m_element_nodes - m_next_element_handle = m_quad_tree - .getNextElement_(m_current_element_handle); + m_next_element_handle = m_quad_tree.get_next_element_(m_current_element_handle); return m_current_element_handle; } // Creates an iterator on the input Quad_tree_impl. The query will be // the Envelope_2D bounding the input Geometry. - QuadTreeIteratorImpl(QuadTreeImpl quad_tree_impl, Geometry query, - double tolerance) { + QuadTreeIteratorImpl(QuadTreeImpl quad_tree_impl, Geometry query, double tolerance) { m_quad_tree = quad_tree_impl; m_query_box = new Envelope2D(); m_quads_stack = new AttributeStreamOfInt32(0); @@ -231,8 +208,7 @@ int next() { // Creates an iterator on the input Quad_tree_impl using the input // Envelope_2D as the query. - QuadTreeIteratorImpl(QuadTreeImpl quad_tree_impl, Envelope2D query, - double tolerance) { + QuadTreeIteratorImpl(QuadTreeImpl quad_tree_impl, Envelope2D query, double tolerance) { m_quad_tree = quad_tree_impl; m_query_box = new Envelope2D(); m_quads_stack = new AttributeStreamOfInt32(0); @@ -257,147 +233,346 @@ int next() { private int m_next_element_handle; private QuadTreeImpl m_quad_tree; private AttributeStreamOfInt32 m_quads_stack; - private ArrayList m_extents_stack; // this won't grow bigger - // than 4 * - // (m_quad_tree->m_height - // - 1) + private ArrayList m_extents_stack; // this won't grow bigger than 4 * (m_quad_tree->m_height - 1) + } + + static final class QuadTreeSortedIteratorImpl { + /** + * Resets the iterator to a starting state on the Quad_tree_impl. If the input Geometry is a Line segment, then the query will be the segment. Otherwise the query will be the Envelope_2D bounding the Geometry. + * \param query The Geometry used for the query. + * \param tolerance The tolerance used for the intersection tests. + * \param tolerance The tolerance used for the intersection tests. + */ + void resetIterator(Geometry query, double tolerance) { + m_quad_tree_iterator_impl.resetIterator(query, tolerance); + m_sorted_handles.resize(0); + m_index = -1; + } + + /** + * Resets the iterator to a starting state on the Quad_tree_impl using the input Envelope_2D as the query. + * \param query The Envelope_2D used for the query. + * \param tolerance The tolerance used for the intersection tests. + */ + void resetIterator(Envelope2D query, double tolerance) { + m_quad_tree_iterator_impl.resetIterator(query, tolerance); + m_sorted_handles.resize(0); + m_index = -1; + } + + /** + * Moves the iterator to the next Element_handle and returns the Element_handle. + */ + int next() { + if (m_index == -1) { + int element_handle = -1; + while ((element_handle = m_quad_tree_iterator_impl.next()) != -1) + m_sorted_handles.add(element_handle); + + m_bucket_sort.sort(m_sorted_handles, 0, m_sorted_handles.size(), new Sorter(m_quad_tree_iterator_impl.m_quad_tree)); + } + + if (m_index == m_sorted_handles.size() - 1) + return -1; + + m_index++; + return m_sorted_handles.get(m_index); + } + + //Creates a sorted iterator on the input Quad_tree_iterator_impl + QuadTreeSortedIteratorImpl(QuadTreeIteratorImpl quad_tree_iterator_impl) { + m_bucket_sort = new BucketSort(); + m_sorted_handles = new AttributeStreamOfInt32(0); + m_quad_tree_iterator_impl = quad_tree_iterator_impl; + m_index = -1; + } + + private class Sorter extends ClassicSort { + public Sorter(QuadTreeImpl quad_tree) { + m_quad_tree = quad_tree; + } + + @Override + public void userSort(int begin, int end, AttributeStreamOfInt32 indices) { + indices.sort(begin, end); + } + + @Override + public double getValue(int e) { + return m_quad_tree.getElement(e); + } + + private QuadTreeImpl m_quad_tree; + } + + private BucketSort m_bucket_sort; + private AttributeStreamOfInt32 m_sorted_handles; + private QuadTreeIteratorImpl m_quad_tree_iterator_impl; + int m_index; } /** - * Creates a Quad_tree_impl with the root having the extent of the input - * Envelope_2D, and height of the input height, where the root starts at - * height 0. Note that the height cannot be larger than 16 if on a 32 bit - * platform and 32 if on a 64 bit platform. \param extent The extent of the - * Quad_tree_impl. \param height The max height of the Quad_tree_impl. + * Creates a Quad_tree_impl with the root having the extent of the input Envelope_2D, and height of the input height, where the root starts at height 0. + * \param extent The extent of the Quad_tree_impl. + * \param height The max height of the Quad_tree_impl. */ QuadTreeImpl(Envelope2D extent, int height) { - m_quad_tree_nodes = new StridedIndexTypeCollection(11); - m_element_nodes = new StridedIndexTypeCollection(5); - m_boxes = new ArrayList(0); - m_free_boxes = new AttributeStreamOfInt32(0); + m_quad_tree_nodes = new StridedIndexTypeCollection(10); + m_element_nodes = new StridedIndexTypeCollection(4); + m_data = new ArrayList(0); + m_free_data = new AttributeStreamOfInt32(0); + m_b_store_duplicates = false; + + m_extent = new Envelope2D(); + m_data_extent = new Envelope2D(); + + reset_(extent, height); + } + + /** + * Creates a Quad_tree_impl with the root having the extent of the input Envelope_2D, and height of the input height, where the root starts at height 0. + * \param extent The extent of the Quad_tree_impl. + * \param height The max height of the Quad_tree_impl. + * \param b_store_duplicates Put true to place elements deeper into the quad tree at intesecting quads, duplicates will be stored. Put false to only place elements into quads that can contain it. + */ + QuadTreeImpl(Envelope2D extent, int height, boolean b_store_duplicates) { + m_quad_tree_nodes = (b_store_duplicates ? new StridedIndexTypeCollection(11) : new StridedIndexTypeCollection(10)); + m_element_nodes = new StridedIndexTypeCollection(4); + m_data = new ArrayList(0); + m_free_data = new AttributeStreamOfInt32(0); + m_b_store_duplicates = b_store_duplicates; + m_extent = new Envelope2D(); + m_data_extent = new Envelope2D(); + reset_(extent, height); } /** - * Resets the Quad_tree_impl to the given extent and height. \param extent - * The extent of the Quad_tree_impl. \param height The max height of the - * Quad_tree_impl. + * Resets the Quad_tree_impl to the given extent and height. + * \param extent The extent of the Quad_tree_impl. + * \param height The max height of the Quad_tree_impl. */ void reset(Envelope2D extent, int height) { m_quad_tree_nodes.deleteAll(false); m_element_nodes.deleteAll(false); - m_boxes.clear(); - m_free_boxes.clear(false); + m_data.clear(); + m_free_data.clear(false); reset_(extent, height); } /** - * Inserts the element and bounding_box into the Quad_tree_impl. Note that - * this will invalidate any active iterator on the Quad_tree_impl. Returns - * an int corresponding to the element and bounding_box. \param element The - * element of the Geometry to be inserted. \param bounding_box The - * bounding_box of the Geometry to be inserted. + * Inserts the element and bounding_box into the Quad_tree_impl. + * Note that this will invalidate any active iterator on the Quad_tree_impl. + * Returns an Element_handle corresponding to the element and bounding_box. + * \param element The element of the Geometry to be inserted. + * \param bounding_box The bounding_box of the Geometry to be inserted. */ int insert(int element, Envelope2D bounding_box) { - return insert_(element, bounding_box, 0, m_extent, m_root, false, -1); + if (m_root == -1) + create_root_(); + + if (m_b_store_duplicates) { + int success = insert_duplicates_(element, bounding_box, 0, m_extent, m_root, false, -1); + + if (success != -1) { + if (m_data_extent.isEmpty()) + m_data_extent.setCoords(bounding_box); + else + m_data_extent.merge(bounding_box); + } + + return success; + } + + int element_handle = insert_(element, bounding_box, 0, m_extent, m_root, false, -1); + + if (element_handle != -1) { + if (m_data_extent.isEmpty()) + m_data_extent.setCoords(bounding_box); + else + m_data_extent.merge(bounding_box); + } + + return element_handle; } /** - * Inserts the element and bounding_box into the Quad_tree_impl at the given - * quad_handle. Note that this will invalidate any active iterator on the - * Quad_tree_impl. Returns an int corresponding to the element and - * bounding_box. \param element The element of the Geometry to be inserted. + * Inserts the element and bounding_box into the Quad_tree_impl at the given quad_handle. + * Note that this will invalidate any active iterator on the Quad_tree_impl. + * Returns an Element_handle corresponding to the element and bounding_box. + * \param element The element of the Geometry to be inserted. * \param bounding_box The bounding_box of the Geometry to be inserted. - * \param hint_index A handle used as a hint where to place the element. - * This can be a handle obtained from a previous insertion and is useful on - * data having strong locality such as segments of a Polygon. + * \param hint_index A handle used as a hint where to place the element. This can be a handle obtained from a previous insertion and is useful on data having strong locality such as segments of a Polygon. */ int insert(int element, Envelope2D bounding_box, int hint_index) { + if (m_root == -1) + create_root_(); + + if (m_b_store_duplicates) { + int success = insert_duplicates_(element, bounding_box, 0, m_extent, m_root, false, -1); + + if (success != -1) { + if (m_data_extent.isEmpty()) + m_data_extent.setCoords(bounding_box); + else + m_data_extent.merge(bounding_box); + } + return success; + } + int quad_handle; if (hint_index == -1) quad_handle = m_root; else - quad_handle = getQuad_(hint_index); + quad_handle = get_quad_(hint_index); int quad_height = getHeight(quad_handle); Envelope2D quad_extent = getExtent(quad_handle); - return insert_(element, bounding_box, quad_height, quad_extent, - quad_handle, false, -1); + + int element_handle = insert_(element, bounding_box, quad_height, quad_extent, quad_handle, false, -1); + + if (element_handle != -1) { + if (m_data_extent.isEmpty()) + m_data_extent.setCoords(bounding_box); + else + m_data_extent.merge(bounding_box); + } + + return element_handle; } /** - * Removes the element and bounding_box at the given element_handle. Note - * that this will invalidate any active iterator on the Quad_tree_impl. - * \param element_handle The handle corresponding to the element and - * bounding_box to be removed. + * Removes the element and bounding_box at the given element_handle. + * Note that this will invalidate any active iterator on the Quad_tree_impl. + * \param element_handle The handle corresponding to the element and bounding_box to be removed. */ void removeElement(int element_handle) { - int quad_handle = getQuad_(element_handle); - int nextElementHandle = disconnectElementHandle_(element_handle); - freeElementAndBoxNode_(element_handle); + if (m_b_store_duplicates) + throw new GeometryException("invalid call"); + + int quad_handle = get_quad_(element_handle); + disconnect_element_handle_(element_handle); + free_element_and_box_node_(element_handle); + + int q = quad_handle; + + while (q != -1) { + set_sub_tree_element_count_(q, get_sub_tree_element_count_(q) - 1); + int parent = get_parent_(q); + + if (get_sub_tree_element_count_(q) == 0) { + assert (get_local_element_count_(q) == 0); + + if (q != m_root) { + int quadrant = get_quadrant_(q); + m_quad_tree_nodes.deleteElement(q); + set_child_(parent, quadrant, -1); + } + } - for (int q = quad_handle; q != -1; q = getParent_(q)) { - setSubTreeElementCount_(q, getSubTreeElementCount_(q) - 1); - assert (getSubTreeElementCount_(q) >= 0); + q = parent; } } /** * Returns the element at the given element_handle. - * \param element_handle - * The handle corresponding to the element to be retrieved. + * \param element_handle The handle corresponding to the element to be retrieved. */ int getElement(int element_handle) { - return getElement_(element_handle); + return get_element_value_(get_data_(element_handle)); } + /** + * Returns the ith unique element. + * \param i The index corresponding to the ith unique element. + */ + int getElementAtIndex(int i) { + return m_data.get(i).element; + } - /** - * Returns a reference to the element extent at the given element_handle. - * \param element_handle - * The handle corresponding to the element to be retrieved. - */ - Envelope2D getElementExtent(int element_handle) - { - int box_handle = getBoxHandle_(element_handle); - return getBoundingBox_(box_handle); - } + /** + * Returns the element extent at the given element_handle. + * \param element_handle The handle corresponding to the element extent to be retrieved. + */ + Envelope2D getElementExtent(int element_handle) { + int data_handle = get_data_(element_handle); + return get_bounding_box_value_(data_handle); + } /** - * Returns the height of the quad at the given quad_handle. \param - * quad_handle The handle corresponding to the quad. + * Returns the extent of the ith unique element. + * \param i The index corresponding to the ith unique element. + */ + Envelope2D getElementExtentAtIndex(int i) { + return m_data.get(i).box; + } + + /** + * Returns the extent of all elements in the quad tree. + */ + Envelope2D getDataExtent() { + return m_data_extent; + } + + /** + * Returns the extent of the quad tree. + */ + Envelope2D getQuadTreeExtent() { + return m_extent; + } + + /** + * Returns the height of the quad at the given quad_handle. + * \param quad_handle The handle corresponding to the quad. */ int getHeight(int quad_handle) { - return getHeight_(quad_handle); + return get_height_(quad_handle); + } + + int getMaxHeight() { + return m_height; } /** - * Returns the extent of the quad at the given quad_handle. \param - * quad_handle The handle corresponding to the quad. + * Returns the extent of the quad at the given quad_handle. + * \param quad_handle The handle corresponding to the quad. */ Envelope2D getExtent(int quad_handle) { Envelope2D quad_extent = new Envelope2D(); quad_extent.setCoords(m_extent); - int height = getHeight_(quad_handle); - int morten_number = getMortenNumber_(quad_handle); - int mask = 3; + if (quad_handle == m_root) + return quad_extent; + + AttributeStreamOfInt32 quadrants = new AttributeStreamOfInt32(0); - for (int i = 0; i < 2 * height; i += 2) { - int child = (int) (mask & (morten_number >> i)); + int q = quad_handle; - if (child == 0) {// northeast + do { + quadrants.add(get_quadrant_(q)); + q = get_parent_(q); + + } while (q != m_root); + + int sz = quadrants.size(); + assert (sz == getHeight(quad_handle)); + + for (int i = 0; i < sz; i++) { + int child = quadrants.getLast(); + quadrants.removeLast(); + + if (child == 0) {//northeast quad_extent.xmin = 0.5 * (quad_extent.xmin + quad_extent.xmax); quad_extent.ymin = 0.5 * (quad_extent.ymin + quad_extent.ymax); - } else if (child == 1) {// northwest + } else if (child == 1) {//northwest quad_extent.xmax = 0.5 * (quad_extent.xmin + quad_extent.xmax); quad_extent.ymin = 0.5 * (quad_extent.ymin + quad_extent.ymax); - } else if (child == 2) {// southwest + } else if (child == 2) {//southwest quad_extent.xmax = 0.5 * (quad_extent.xmin + quad_extent.xmax); quad_extent.ymax = 0.5 * (quad_extent.ymin + quad_extent.ymax); - } else {// southeast + } else {//southeast quad_extent.xmin = 0.5 * (quad_extent.xmin + quad_extent.xmax); quad_extent.ymax = 0.5 * (quad_extent.ymin + quad_extent.ymax); } @@ -407,26 +582,134 @@ Envelope2D getExtent(int quad_handle) { } /** - * Returns the int of the quad containing the given element_handle. \param - * element_handle The handle corresponding to the element. + * Returns the Quad_handle of the quad containing the given element_handle. + * \param element_handle The handle corresponding to the element. */ int getQuad(int element_handle) { - return getQuad_(element_handle); + return get_quad_(element_handle); } /** * Returns the number of elements in the Quad_tree_impl. */ int getElementCount() { - return getSubTreeElementCount_(m_root); + if (m_root == -1) + return 0; + + assert (get_sub_tree_element_count_(m_root) == m_data.size()); + return get_sub_tree_element_count_(m_root); } /** - * Returns the number of elements in the subtree rooted at the given - * quad_handle. \param quad_handle The handle corresponding to the quad. + * Returns the number of elements in the subtree rooted at the given quad_handle. + * \param quad_handle The handle corresponding to the quad. */ int getSubTreeElementCount(int quad_handle) { - return getSubTreeElementCount_(quad_handle); + return get_sub_tree_element_count_(quad_handle); + } + + /** + * Returns the number of elements contained in the subtree rooted at the given quad_handle. + * \param quad_handle The handle corresponding to the quad. + */ + int getContainedSubTreeElementCount(int quad_handle) { + if (!m_b_store_duplicates) + return get_sub_tree_element_count_(quad_handle); + + return get_contained_sub_tree_element_count_(quad_handle); + } + + /** + * Returns the number of elements in the quad tree that intersect the qiven query. Some elements may be duplicated if the quad tree stores duplicates. + * \param query The Envelope_2D used for the query. + * \param tolerance The tolerance used for the intersection tests. + * \param max_count If the intersection count becomes greater than or equal to the max_count, then max_count is returned. + */ + int getIntersectionCount(Envelope2D query, double tolerance, int max_count) { + if (m_root == -1) + return 0; + + Envelope2D query_inflated = new Envelope2D(); + query_inflated.setCoords(query); + query_inflated.inflate(tolerance, tolerance); + + AttributeStreamOfInt32 quads_stack = new AttributeStreamOfInt32(0); + ArrayList extents_stack = new ArrayList(0); + quads_stack.add(m_root); + extents_stack.add(new Envelope2D(m_extent.xmin, m_extent.ymin, m_extent.xmax, m_extent.ymax)); + + Envelope2D[] child_extents = new Envelope2D[4]; + child_extents[0] = new Envelope2D(); + child_extents[1] = new Envelope2D(); + child_extents[2] = new Envelope2D(); + child_extents[3] = new Envelope2D(); + + Envelope2D current_extent = new Envelope2D(); + + int intersection_count = 0; + + while (quads_stack.size() > 0) { + boolean b_subdivide = false; + + int current_quad_handle = quads_stack.getLast(); + current_extent.setCoords(extents_stack.get(extents_stack.size() - 1)); + + quads_stack.removeLast(); + extents_stack.remove(extents_stack.size() - 1); + + + if (query_inflated.contains(current_extent)) { + intersection_count += getSubTreeElementCount(current_quad_handle); + + if (max_count > 0 && intersection_count >= max_count) + return max_count; + } else { + if (query_inflated.isIntersecting(current_extent)) { + for (int element_handle = get_first_element_(current_quad_handle); element_handle != -1; element_handle = get_next_element_(element_handle)) { + int data_handle = get_data_(element_handle); + Envelope2D env = get_bounding_box_value_(data_handle); + + if (env.isIntersecting(query_inflated)) { + intersection_count++; + + if (max_count > 0 && intersection_count >= max_count) + return max_count; + } + } + + b_subdivide = getHeight(current_quad_handle) + 1 <= m_height; + } + } + + if (b_subdivide) { + set_child_extents_(current_extent, child_extents); + + for (int i = 0; i < 4; i++) { + int child_handle = get_child_(current_quad_handle, i); + + if (child_handle != -1 && getSubTreeElementCount(child_handle) > 0) { + boolean b_is_intersecting = query_inflated.isIntersecting(child_extents[i]); + + if (b_is_intersecting) { + quads_stack.add(child_handle); + extents_stack.add(new Envelope2D(child_extents[i].xmin, child_extents[i].ymin, child_extents[i].xmax, child_extents[i].ymax)); + } + } + } + } + } + + return intersection_count; + } + + /** + * Returns true if the quad tree has data intersecting the given query. + * \param query The Envelope_2D used for the query. + * \param tolerance The tolerance used for the intersection tests. + */ + boolean hasData(Envelope2D query, double tolerance) { + int count = getIntersectionCount(query, tolerance, 1); + return count >= 1; } /** @@ -460,37 +743,58 @@ QuadTreeIteratorImpl getIterator() { return new QuadTreeIteratorImpl(this); } + /** + * Gets a sorted iterator on the Quad_tree_impl. The Element_handles will be returned in increasing order of their corresponding Element_types. + * The query will be the Envelope_2D that bounds the input Geometry. + * To reuse the existing iterator on the same Quad_tree_impl but with a new query, use the reset_iterator function on the Quad_tree_sorted_iterator_impl. + * \param query The Geometry used for the query. If the Geometry is a Line segment, then the query will be the segment. Otherwise the query will be the Envelope_2D bounding the Geometry. + * \param tolerance The tolerance used for the intersection tests. + */ + QuadTreeSortedIteratorImpl getSortedIterator(Geometry query, double tolerance) { + return new QuadTreeSortedIteratorImpl(getIterator(query, tolerance)); + } + + /** + * Gets a sorted iterator on the Quad_tree_impl using the input Envelope_2D as the query. The Element_handles will be returned in increasing order of their corresponding Element_types. + * To reuse the existing iterator on the same Quad_tree_impl but with a new query, use the reset_iterator function on the Quad_tree_iterator_impl. + * \param query The Envelope_2D used for the query. + * \param tolerance The tolerance used for the intersection tests. + */ + QuadTreeSortedIteratorImpl getSortedIterator(Envelope2D query, double tolerance) { + return new QuadTreeSortedIteratorImpl(getIterator(query, tolerance)); + } + + /** + * Gets a sorted iterator on the Quad_tree. The Element_handles will be returned in increasing order of their corresponding Element_types + */ + QuadTreeSortedIteratorImpl getSortedIterator() { + return new QuadTreeSortedIteratorImpl(getIterator()); + } + private void reset_(Envelope2D extent, int height) { - // We need 2 * height bits for the morten number, which is of type - // Index_type (more than enough). - if (height < 0 || 2 * height > 8 * 4) + if (height < 0 || height > 127) throw new IllegalArgumentException("invalid height"); m_height = height; m_extent.setCoords(extent); m_root = m_quad_tree_nodes.newElement(); - setSubTreeElementCount_(m_root, 0); - setLocalElementCount_(m_root, 0); - setMortenNumber_(m_root, 0); - setHeight_(m_root, 0); + m_data_extent.setEmpty(); + m_root = -1; } - private int insert_(int element, Envelope2D bounding_box, int height, - Envelope2D quad_extent, int quad_handle, boolean b_flushing, - int flushed_element_handle) { + private int insert_(int element, Envelope2D bounding_box, int height, Envelope2D quad_extent, int quad_handle, boolean b_flushing, int flushed_element_handle) { if (!quad_extent.contains(bounding_box)) { assert (!b_flushing); if (height == 0) return -1; - return insert_(element, bounding_box, 0, m_extent, m_root, - b_flushing, flushed_element_handle); + return insert_(element, bounding_box, 0, m_extent, m_root, b_flushing, flushed_element_handle); } if (!b_flushing) { - for (int q = quad_handle; q != -1; q = getParent_(q)) - setSubTreeElementCount_(q, getSubTreeElementCount_(q) + 1); + for (int q = quad_handle; q != -1; q = get_parent_(q)) + set_sub_tree_element_count_(q, get_sub_tree_element_count_(q) + 1); } Envelope2D current_extent = new Envelope2D(); @@ -505,9 +809,8 @@ private int insert_(int element, Envelope2D bounding_box, int height, child_extents[3] = new Envelope2D(); int current_height; - for (current_height = height; current_height < m_height - && canPushDown_(current_quad_handle); current_height++) { - setChildExtents_(current_extent, child_extents); + for (current_height = height; current_height < m_height && can_push_down_(current_quad_handle); current_height++) { + set_child_extents_(current_extent, child_extents); boolean b_contains = false; @@ -515,12 +818,11 @@ private int insert_(int element, Envelope2D bounding_box, int height, if (child_extents[i].contains(bounding_box)) { b_contains = true; - int child_handle = getChild_(current_quad_handle, i); + int child_handle = get_child_(current_quad_handle, i); if (child_handle == -1) - child_handle = createChild_(current_quad_handle, i); + child_handle = create_child_(current_quad_handle, i); - setSubTreeElementCount_(child_handle, - getSubTreeElementCount_(child_handle) + 1); + set_sub_tree_element_count_(child_handle, get_sub_tree_element_count_(child_handle) + 1); current_quad_handle = child_handle; current_extent.setCoords(child_extents[i]); @@ -532,22 +834,112 @@ private int insert_(int element, Envelope2D bounding_box, int height, break; } - return insertAtQuad_(element, bounding_box, current_height, - current_extent, current_quad_handle, b_flushing, quad_handle, - flushed_element_handle); + return insert_at_quad_(element, bounding_box, current_height, current_extent, current_quad_handle, b_flushing, quad_handle, flushed_element_handle, -1); } - private int insertAtQuad_(int element, Envelope2D bounding_box, - int current_height, Envelope2D current_extent, - int current_quad_handle, boolean b_flushing, int quad_handle, - int flushed_element_handle) { - // If the bounding box is not contained in any of the current_node's - // children, or if the current_height is m_height, then insert the - // element and + private int insert_duplicates_(int element, Envelope2D bounding_box, int height, Envelope2D quad_extent, int quad_handle, boolean b_flushing, int flushed_element_handle) { + assert (b_flushing || m_root == quad_handle); + + if (!b_flushing) // If b_flushing is true, then the sub tree element counts are already accounted for since the element already lies in the current incoming quad + { + if (!quad_extent.contains(bounding_box)) + return -1; + + set_sub_tree_element_count_(quad_handle, get_sub_tree_element_count_(quad_handle) + 1); + set_contained_sub_tree_element_count_(quad_handle, get_contained_sub_tree_element_count_(quad_handle) + 1); + } + + double bounding_box_max_dim = Math.max(bounding_box.getWidth(), bounding_box.getHeight()); + + int element_handle = -1; + AttributeStreamOfInt32 quads_stack = new AttributeStreamOfInt32(0); + ArrayList extents_stack = new ArrayList(0); + AttributeStreamOfInt32 heights_stack = new AttributeStreamOfInt32(0); + quads_stack.add(quad_handle); + extents_stack.add(new Envelope2D(quad_extent.xmin, quad_extent.ymin, quad_extent.xmax, quad_extent.ymax)); + heights_stack.add(height); + + Envelope2D[] child_extents = new Envelope2D[4]; + child_extents[0] = new Envelope2D(); + child_extents[1] = new Envelope2D(); + child_extents[2] = new Envelope2D(); + child_extents[3] = new Envelope2D(); + + Envelope2D current_extent = new Envelope2D(); + + while (quads_stack.size() > 0) { + boolean b_subdivide = false; + + int current_quad_handle = quads_stack.getLast(); + current_extent.setCoords(extents_stack.get(extents_stack.size() - 1)); + int current_height = heights_stack.getLast(); + + quads_stack.removeLast(); + extents_stack.remove(extents_stack.size() - 1); + heights_stack.removeLast(); + + if (current_height + 1 < m_height && can_push_down_(current_quad_handle)) { + double current_extent_max_dim = Math.max(current_extent.getWidth(), current_extent.getHeight()); + + if (bounding_box_max_dim <= current_extent_max_dim / 2.0) + b_subdivide = true; + } + + if (b_subdivide) { + set_child_extents_(current_extent, child_extents); + + boolean b_contains = false; + + for (int i = 0; i < 4; i++) { + b_contains = child_extents[i].contains(bounding_box); + + if (b_contains) { + int child_handle = get_child_(current_quad_handle, i); + if (child_handle == -1) + child_handle = create_child_(current_quad_handle, i); + + quads_stack.add(child_handle); + extents_stack.add(new Envelope2D(child_extents[i].xmin, child_extents[i].ymin, child_extents[i].xmax, child_extents[i].ymax)); + heights_stack.add(current_height + 1); + + set_sub_tree_element_count_(child_handle, get_sub_tree_element_count_(child_handle) + 1); + set_contained_sub_tree_element_count_(child_handle, get_contained_sub_tree_element_count_(child_handle) + 1); + break; + } + } + + if (!b_contains) { + for (int i = 0; i < 4; i++) { + boolean b_intersects = child_extents[i].isIntersecting(bounding_box); + + if (b_intersects) { + int child_handle = get_child_(current_quad_handle, i); + if (child_handle == -1) + child_handle = create_child_(current_quad_handle, i); + + quads_stack.add(child_handle); + extents_stack.add(new Envelope2D(child_extents[i].xmin, child_extents[i].ymin, child_extents[i].xmax, child_extents[i].ymax)); + heights_stack.add(current_height + 1); + + set_sub_tree_element_count_(child_handle, get_sub_tree_element_count_(child_handle) + 1); + } + } + } + } else { + element_handle = insert_at_quad_(element, bounding_box, current_height, current_extent, current_quad_handle, b_flushing, quad_handle, flushed_element_handle, element_handle); + b_flushing = false; // flushing is false after the first inserted element has been flushed down, all subsequent inserts will be new + } + } + + return 0; + } + + private int insert_at_quad_(int element, Envelope2D bounding_box, int current_height, Envelope2D current_extent, int current_quad_handle, boolean b_flushing, int quad_handle, int flushed_element_handle, int duplicate_element_handle) { + // If the bounding box is not contained in any of the current_node's children, or if the current_height is m_height, then insert the element and // bounding box into the current_node - int head_element_handle = getFirstElement_(current_quad_handle); - int tail_element_handle = getLastElement_(current_quad_handle); + int head_element_handle = get_first_element_(current_quad_handle); + int tail_element_handle = get_last_element_(current_quad_handle); int element_handle = -1; if (b_flushing) { @@ -556,295 +948,350 @@ private int insertAtQuad_(int element, Envelope2D bounding_box, if (current_quad_handle == quad_handle) return flushed_element_handle; - disconnectElementHandle_(flushed_element_handle); // Take it out of - // the incoming - // quad_handle, - // and place in - // current_quad_handle + disconnect_element_handle_(flushed_element_handle); // Take it out of the incoming quad_handle, and place in current_quad_handle element_handle = flushed_element_handle; } else { - element_handle = createElementAndBoxNode_(); - setElement_(element_handle, element); // insert element at the new - // tail of the list - // (next_element_handle). - setBoundingBox_(getBoxHandle_(element_handle), bounding_box); // insert - // bounding_box + if (duplicate_element_handle == -1) { + element_handle = create_element_(); + set_data_values_(get_data_(element_handle), element, bounding_box); + } else { + assert (m_b_store_duplicates); + element_handle = create_element_from_duplicate_(duplicate_element_handle); + } } assert (!b_flushing || element_handle == flushed_element_handle); - setQuad_(element_handle, current_quad_handle); // set parent quad - // (needed for removal - // of element) + set_quad_(element_handle, current_quad_handle); // set parent quad (needed for removal of element) - // assign the prev pointer of the new tail to point at the old tail - // (tail_element_handle) - // assign the next pointer of the old tail to point at the new tail - // (next_element_handle) + // assign the prev pointer of the new tail to point at the old tail (tail_element_handle) + // assign the next pointer of the old tail to point at the new tail (next_element_handle) if (tail_element_handle != -1) { - setPrevElement_(element_handle, tail_element_handle); - setNextElement_(tail_element_handle, element_handle); + set_prev_element_(element_handle, tail_element_handle); + set_next_element_(tail_element_handle, element_handle); } else { assert (head_element_handle == -1); - setFirstElement_(current_quad_handle, element_handle); + set_first_element_(current_quad_handle, element_handle); } // assign the new tail - setLastElement_(current_quad_handle, element_handle); + set_last_element_(current_quad_handle, element_handle); - setLocalElementCount_(current_quad_handle, - getLocalElementCount_(current_quad_handle) + 1); + set_local_element_count_(current_quad_handle, get_local_element_count_(current_quad_handle) + 1); - if (canFlush_(current_quad_handle)) + if (can_flush_(current_quad_handle)) flush_(current_height, current_extent, current_quad_handle); return element_handle; } - private int disconnectElementHandle_(int element_handle) { + private static void set_child_extents_(Envelope2D current_extent, Envelope2D[] child_extents) { + double x_mid = 0.5 * (current_extent.xmin + current_extent.xmax); + double y_mid = 0.5 * (current_extent.ymin + current_extent.ymax); + + child_extents[0].setCoords(x_mid, y_mid, current_extent.xmax, current_extent.ymax); // northeast + child_extents[1].setCoords(current_extent.xmin, y_mid, x_mid, current_extent.ymax); // northwest + child_extents[2].setCoords(current_extent.xmin, current_extent.ymin, x_mid, y_mid); // southwest + child_extents[3].setCoords(x_mid, current_extent.ymin, current_extent.xmax, y_mid); // southeast + } + + private void disconnect_element_handle_(int element_handle) { assert (element_handle != -1); - int quad_handle = getQuad_(element_handle); - int head_element_handle = getFirstElement_(quad_handle); - int tail_element_handle = getLastElement_(quad_handle); - int prev_element_handle = getPrevElement_(element_handle); - int next_element_handle = getNextElement_(element_handle); + int quad_handle = get_quad_(element_handle); + int head_element_handle = get_first_element_(quad_handle); + int tail_element_handle = get_last_element_(quad_handle); + int prev_element_handle = get_prev_element_(element_handle); + int next_element_handle = get_next_element_(element_handle); assert (head_element_handle != -1 && tail_element_handle != -1); if (head_element_handle == element_handle) { if (next_element_handle != -1) - setPrevElement_(next_element_handle, -1); + set_prev_element_(next_element_handle, -1); else { assert (head_element_handle == tail_element_handle); - assert (getLocalElementCount_(quad_handle) == 1); - setLastElement_(quad_handle, -1); + assert (get_local_element_count_(quad_handle) == 1); + set_last_element_(quad_handle, -1); } - setFirstElement_(quad_handle, next_element_handle); + set_first_element_(quad_handle, next_element_handle); } else if (tail_element_handle == element_handle) { assert (prev_element_handle != -1); - assert (getLocalElementCount_(quad_handle) >= 2); - setNextElement_(prev_element_handle, -1); - setLastElement_(quad_handle, prev_element_handle); + assert (get_local_element_count_(quad_handle) >= 2); + set_next_element_(prev_element_handle, -1); + set_last_element_(quad_handle, prev_element_handle); } else { assert (next_element_handle != -1 && prev_element_handle != -1); - assert (getLocalElementCount_(quad_handle) >= 3); - setPrevElement_(next_element_handle, prev_element_handle); - setNextElement_(prev_element_handle, next_element_handle); + assert (get_local_element_count_(quad_handle) >= 3); + set_prev_element_(next_element_handle, prev_element_handle); + set_next_element_(prev_element_handle, next_element_handle); } - setPrevElement_(element_handle, -1); - setNextElement_(element_handle, -1); - - setLocalElementCount_(quad_handle, - getLocalElementCount_(quad_handle) - 1); - assert (getLocalElementCount_(quad_handle) >= 0); + set_prev_element_(element_handle, -1); + set_next_element_(element_handle, -1); - return next_element_handle; + set_local_element_count_(quad_handle, get_local_element_count_(quad_handle) - 1); + assert (get_local_element_count_(quad_handle) >= 0); } - private static void setChildExtents_(Envelope2D current_extent, - Envelope2D[] child_extents) { - double x_mid = 0.5 * (current_extent.xmin + current_extent.xmax); - double y_mid = 0.5 * (current_extent.ymin + current_extent.ymax); - - child_extents[0].setCoords(x_mid, y_mid, current_extent.xmax, - current_extent.ymax); // northeast - child_extents[1].setCoords(current_extent.xmin, y_mid, x_mid, - current_extent.ymax); // northwest - child_extents[2].setCoords(current_extent.xmin, current_extent.ymin, - x_mid, y_mid); // southwest - child_extents[3].setCoords(x_mid, current_extent.ymin, - current_extent.xmax, y_mid); // southeast - } - - private boolean canFlush_(int quad_handle) { - return getLocalElementCount_(quad_handle) == 8 - && !hasChildren_(quad_handle); + private boolean can_flush_(int quad_handle) { + return get_local_element_count_(quad_handle) == m_flushing_count && !has_children_(quad_handle); } private void flush_(int height, Envelope2D extent, int quad_handle) { int element; - Envelope2D bounding_box; + Envelope2D bounding_box = new Envelope2D(); assert (quad_handle != -1); - int element_handle = getFirstElement_(quad_handle), next_handle; - int box_handle; + int element_handle = get_first_element_(quad_handle), next_handle = -1; + int data_handle = -1; assert (element_handle != -1); do { - box_handle = getBoxHandle_(element_handle); - element = m_element_nodes.getField(element_handle, 0); - bounding_box = getBoundingBox_(box_handle); - insert_(element, bounding_box, height, extent, quad_handle, true, - element_handle); + data_handle = get_data_(element_handle); + element = get_element_value_(data_handle); + bounding_box.setCoords(get_bounding_box_value_(data_handle)); + + next_handle = get_next_element_(element_handle); + + if (!m_b_store_duplicates) + insert_(element, bounding_box, height, extent, quad_handle, true, element_handle); + else + insert_duplicates_(element, bounding_box, height, extent, quad_handle, true, element_handle); - next_handle = getNextElement_(element_handle); element_handle = next_handle; } while (element_handle != -1); } - boolean canPushDown_(int quad_handle) { - return getLocalElementCount_(quad_handle) >= 8 - || hasChildren_(quad_handle); + private boolean can_push_down_(int quad_handle) { + return get_local_element_count_(quad_handle) >= m_flushing_count || has_children_(quad_handle); } - boolean hasChildren_(int parent) { - return getChild_(parent, 0) != -1 || getChild_(parent, 1) != -1 - || getChild_(parent, 2) != -1 || getChild_(parent, 3) != -1; + private boolean has_children_(int parent) { + return get_child_(parent, 0) != -1 || get_child_(parent, 1) != -1 || get_child_(parent, 2) != -1 || get_child_(parent, 3) != -1; } - private int createChild_(int parent, int quadrant) { + private int create_child_(int parent, int quadrant) { int child = m_quad_tree_nodes.newElement(); - setChild_(parent, quadrant, child); - setSubTreeElementCount_(child, 0); - setLocalElementCount_(child, 0); - setParent_(child, parent); - setHeight_(child, getHeight_(parent) + 1); - setMortenNumber_(child, (quadrant << (2 * getHeight_(parent))) - | getMortenNumber_(parent)); + set_child_(parent, quadrant, child); + set_sub_tree_element_count_(child, 0); + set_local_element_count_(child, 0); + set_parent_(child, parent); + set_height_and_quadrant_(child, get_height_(parent) + 1, quadrant); + + if (m_b_store_duplicates) + set_contained_sub_tree_element_count_(child, 0); + return child; } - private int createElementAndBoxNode_() { + private void create_root_() { + m_root = m_quad_tree_nodes.newElement(); + set_sub_tree_element_count_(m_root, 0); + set_local_element_count_(m_root, 0); + set_height_and_quadrant_(m_root, 0, 0); + + if (m_b_store_duplicates) + set_contained_sub_tree_element_count_(m_root, 0); + } + + private int create_element_() { int element_handle = m_element_nodes.newElement(); - int box_handle; + int data_handle; - if (m_free_boxes.size() > 0) { - box_handle = m_free_boxes.getLast(); - m_free_boxes.removeLast(); + if (m_free_data.size() > 0) { + data_handle = m_free_data.get(m_free_data.size() - 1); + m_free_data.removeLast(); } else { - box_handle = m_boxes.size(); - m_boxes.add(new Envelope2D()); + data_handle = m_data.size(); + m_data.add(null); } - setBoxHandle_(element_handle, box_handle); + set_data_(element_handle, data_handle); return element_handle; } - private void freeElementAndBoxNode_(int element_handle) { - m_free_boxes.add(getBoxHandle_(element_handle)); + private int create_element_from_duplicate_(int duplicate_element_handle) { + int element_handle = m_element_nodes.newElement(); + int data_handle = get_data_(duplicate_element_handle); + set_data_(element_handle, data_handle); + return element_handle; + } + + private void free_element_and_box_node_(int element_handle) { + int data_handle = get_data_(element_handle); + m_free_data.add(data_handle); m_element_nodes.deleteElement(element_handle); } - private int getChild_(int quad_handle, int quadrant) { + private int get_child_(int quad_handle, int quadrant) { return m_quad_tree_nodes.getField(quad_handle, quadrant); } - private void setChild_(int parent, int quadrant, int child) { + private void set_child_(int parent, int quadrant, int child) { m_quad_tree_nodes.setField(parent, quadrant, child); } - private int getFirstElement_(int quad_handle) { + private int get_first_element_(int quad_handle) { return m_quad_tree_nodes.getField(quad_handle, 4); } - private void setFirstElement_(int quad_handle, int head) { + private void set_first_element_(int quad_handle, int head) { m_quad_tree_nodes.setField(quad_handle, 4, head); } - private int getLastElement_(int quad_handle) { + private int get_last_element_(int quad_handle) { return m_quad_tree_nodes.getField(quad_handle, 5); } - private void setLastElement_(int quad_handle, int tail) { + private void set_last_element_(int quad_handle, int tail) { m_quad_tree_nodes.setField(quad_handle, 5, tail); } - private int getMortenNumber_(int quad_handle) { - return m_quad_tree_nodes.getField(quad_handle, 6); + + private int get_quadrant_(int quad_handle) { + int height_quadrant_hybrid = m_quad_tree_nodes.getField(quad_handle, 6); + int quadrant = height_quadrant_hybrid & m_quadrant_mask; + return quadrant; } - private void setMortenNumber_(int quad_handle, int morten_number) { - m_quad_tree_nodes.setField(quad_handle, 6, morten_number); + private int get_height_(int quad_handle) { + int height_quadrant_hybrid = m_quad_tree_nodes.getField(quad_handle, 6); + int height = height_quadrant_hybrid >> m_height_bit_shift; + return height; } - private int getLocalElementCount_(int quad_handle) { - return m_quad_tree_nodes.getField(quad_handle, 7); + private void set_height_and_quadrant_(int quad_handle, int height, int quadrant) { + assert (quadrant >= 0 && quadrant <= 3); + int height_quadrant_hybrid = (int) ((height << m_height_bit_shift) | quadrant); + m_quad_tree_nodes.setField(quad_handle, 6, height_quadrant_hybrid); } - private int getSubTreeElementCount_(int quad_handle) { - return m_quad_tree_nodes.getField(quad_handle, 8); + private int get_local_element_count_(int quad_handle) { + return m_quad_tree_nodes.getField(quad_handle, 7); } - private void setLocalElementCount_(int quad_handle, int count) { + private void set_local_element_count_(int quad_handle, int count) { m_quad_tree_nodes.setField(quad_handle, 7, count); } - private void setSubTreeElementCount_(int quad_handle, int count) { + private int get_sub_tree_element_count_(int quad_handle) { + return m_quad_tree_nodes.getField(quad_handle, 8); + } + + private void set_sub_tree_element_count_(int quad_handle, int count) { m_quad_tree_nodes.setField(quad_handle, 8, count); } - private int getParent_(int child) { + private int get_parent_(int child) { return m_quad_tree_nodes.getField(child, 9); } - private void setParent_(int child, int parent) { + private void set_parent_(int child, int parent) { m_quad_tree_nodes.setField(child, 9, parent); } - private int getHeight_(int quad_handle) { - return (int) m_quad_tree_nodes.getField(quad_handle, 10); + private int get_contained_sub_tree_element_count_(int quad_handle) { + return m_quad_tree_nodes.getField(quad_handle, 10); } - private void setHeight_(int quad_handle, int height) { - m_quad_tree_nodes.setField(quad_handle, 10, height); + private void set_contained_sub_tree_element_count_(int quad_handle, int count) { + m_quad_tree_nodes.setField(quad_handle, 10, count); } - private int getElement_(int element_handle) { + private int get_data_(int element_handle) { return m_element_nodes.getField(element_handle, 0); } - private void setElement_(int element_handle, int element) { - m_element_nodes.setField(element_handle, 0, element); + private void set_data_(int element_handle, int data_handle) { + m_element_nodes.setField(element_handle, 0, data_handle); } - private int getPrevElement_(int element_handle) { + private int get_prev_element_(int element_handle) { return m_element_nodes.getField(element_handle, 1); } - private int getNextElement_(int element_handle) { + private int get_next_element_(int element_handle) { return m_element_nodes.getField(element_handle, 2); } - private void setPrevElement_(int element_handle, int prev_handle) { + private void set_prev_element_(int element_handle, int prev_handle) { m_element_nodes.setField(element_handle, 1, prev_handle); } - private void setNextElement_(int element_handle, int next_handle) { + private void set_next_element_(int element_handle, int next_handle) { m_element_nodes.setField(element_handle, 2, next_handle); } - private int getQuad_(int element_handle) { + private int get_quad_(int element_handle) { return m_element_nodes.getField(element_handle, 3); } - private void setQuad_(int element_handle, int parent) { + private void set_quad_(int element_handle, int parent) { m_element_nodes.setField(element_handle, 3, parent); } - private int getBoxHandle_(int element_handle) { - return m_element_nodes.getField(element_handle, 4); - } - - private void setBoxHandle_(int element_handle, int box_handle) { - m_element_nodes.setField(element_handle, 4, box_handle); + private int get_element_value_(int data_handle) { + return m_data.get(data_handle).element; } - private Envelope2D getBoundingBox_(int box_handle) { - return m_boxes.get(box_handle); + private Envelope2D get_bounding_box_value_(int data_handle) { + return m_data.get(data_handle).box; } - private void setBoundingBox_(int box_handle, Envelope2D bounding_box) { - m_boxes.get(box_handle).setCoords(bounding_box); + private void set_data_values_(int data_handle, int element, Envelope2D bounding_box) { + m_data.set(data_handle, new Data(element, bounding_box)); } - private int m_root; private Envelope2D m_extent; - private int m_height; + private Envelope2D m_data_extent; private StridedIndexTypeCollection m_quad_tree_nodes; private StridedIndexTypeCollection m_element_nodes; - private ArrayList m_boxes; - private AttributeStreamOfInt32 m_free_boxes; + private ArrayList m_data; + private AttributeStreamOfInt32 m_free_data; + private int m_root; + private int m_height; + private boolean m_b_store_duplicates; + + private int m_quadrant_mask = 3; + private int m_height_bit_shift = 2; + private int m_flushing_count = 5; + + static final class Data { + int element; + Envelope2D box; + + Data(int element_, Envelope2D box_) { + element = element_; + box = new Envelope2D(); + box.setCoords(box_); + } + } + + /* m_quad_tree_nodes + * 0: m_north_east_child + * 1: m_north_west_child + * 2: m_south_west_child + * 3: m_south_east_child + * 4: m_head_element + * 5: m_tail_element + * 6: m_quadrant_and_height + * 7: m_local_element_count + * 8: m_sub_tree_element_count + * 9: m_parent_quad + * 10: m_height + */ + + /* m_element_nodes + * 0: m_data_handle + * 1: m_prev + * 2: m_next + * 3: m_parent_quad + */ + + /* m_data + * element + * box + */ } diff --git a/src/main/java/com/esri/core/geometry/RasterizedGeometry2DImpl.java b/src/main/java/com/esri/core/geometry/RasterizedGeometry2DImpl.java index ded79caf..ff21c8ec 100644 --- a/src/main/java/com/esri/core/geometry/RasterizedGeometry2DImpl.java +++ b/src/main/java/com/esri/core/geometry/RasterizedGeometry2DImpl.java @@ -138,14 +138,16 @@ void strokeDrawPolyPath(SimpleRasterizer rasterizer, SegmentIteratorImpl segIter = polyPath.querySegmentIterator(); double strokeHalfWidth = m_transform.transform(tol) + 1.5; - double shortSegment = 0.5; + double shortSegment = 0.25; Point2D vec = new Point2D(); Point2D vecA = new Point2D(); Point2D vecB = new Point2D(); - // TODO check this Java workaroung Point2D ptStart = new Point2D(); Point2D ptEnd = new Point2D(); + Point2D prev_start = new Point2D(); + Point2D prev_end = new Point2D(); + double[] helper_xy_10_elm = new double[10]; Envelope2D segEnv = new Envelope2D(); Point2D ptOld = new Point2D(); while (segIter.nextPath()) { @@ -155,18 +157,22 @@ void strokeDrawPolyPath(SimpleRasterizer rasterizer, while (segIter.hasNextSegment()) { Segment seg = segIter.nextSegment(); ptStart.x = seg.getStartX(); - ptStart.y = seg.getStartY();// Point2D ptStart = - // seg.getStartXY(); + ptStart.y = seg.getStartY(); ptEnd.x = seg.getEndX(); - ptEnd.y = seg.getEndY();// Point2D ptEnd = seg.getEndXY(); + ptEnd.y = seg.getEndY(); segEnv.setEmpty(); segEnv.merge(ptStart.x, ptStart.y); segEnv.mergeNE(ptEnd.x, ptEnd.y); if (!m_geomEnv.isIntersectingNE(segEnv)) { if (hasFan) { - fillConvexPolygon(rasterizer, fan, 4); + rasterizer.startAddingEdges(); + rasterizer.addSegmentStroke(prev_start.x, prev_start.y, + prev_end.x, prev_end.y, strokeHalfWidth, false, + helper_xy_10_elm); + rasterizer.renderEdges(SimpleRasterizer.EVEN_ODD); hasFan = false; } + first = true; continue; } @@ -181,34 +187,25 @@ void strokeDrawPolyPath(SimpleRasterizer rasterizer, ptStart.setCoords(ptOld); } - vec.sub(ptEnd, ptStart); - double len = vec.length(); - boolean bShort = len < shortSegment; - if (len == 0) { - vec.setCoords(1.0, 0); - len = 1.0; - continue; - } + prev_start.setCoords(ptStart); + prev_end.setCoords(ptEnd); + + rasterizer.startAddingEdges(); + hasFan = !rasterizer.addSegmentStroke(prev_start.x, + prev_start.y, prev_end.x, prev_end.y, strokeHalfWidth, + true, helper_xy_10_elm); + rasterizer.renderEdges(SimpleRasterizer.EVEN_ODD); + if (!hasFan) + ptOld.setCoords(prev_end); + } - if (!bShort) - ptOld.setCoords(ptEnd); - - vec.scale(strokeHalfWidth / len); - vecA.setCoords(-vec.y, vec.x); - vecB.setCoords(vec.y, -vec.x); - ptStart.sub(vec); - ptEnd.add(vec); - fan[0].add(ptStart, vecA); - fan[1].add(ptStart, vecB); - fan[2].add(ptEnd, vecB); - fan[3].add(ptEnd, vecA); - if (!bShort) - fillConvexPolygon(rasterizer, fan, 4); - else - hasFan = true; + if (hasFan) { + rasterizer.startAddingEdges(); + hasFan = !rasterizer.addSegmentStroke(prev_start.x, + prev_start.y, prev_end.x, prev_end.y, strokeHalfWidth, + false, helper_xy_10_elm); + rasterizer.renderEdges(SimpleRasterizer.EVEN_ODD); } - if (hasFan) - fillConvexPolygon(rasterizer, fan, 4); } } diff --git a/src/main/java/com/esri/core/geometry/Segment.java b/src/main/java/com/esri/core/geometry/Segment.java index be94b723..ca2ea184 100644 --- a/src/main/java/com/esri/core/geometry/Segment.java +++ b/src/main/java/com/esri/core/geometry/Segment.java @@ -25,16 +25,13 @@ package com.esri.core.geometry; import com.esri.core.geometry.VertexDescription.Semantics; + import java.io.Serializable; /** * A base class for segments. Presently only Line segments are supported. */ public abstract class Segment extends Geometry implements Serializable { - - // UPDATED PORT TO MATCH NATIVE AS OF JAN 30 2011 - private static final long serialVersionUID = 1L; - double m_xStart; double m_yStart; @@ -49,11 +46,11 @@ public abstract class Segment extends Geometry implements Serializable { /** * Returns XY coordinates of the start point. */ - Point2D getStartXY() { + public Point2D getStartXY() { return Point2D.construct(m_xStart, m_yStart); } - void getStartXY(Point2D pt) { + public void getStartXY(Point2D pt) { pt.x = m_xStart; pt.y = m_yStart; } @@ -61,29 +58,29 @@ void getStartXY(Point2D pt) { /** * Sets the XY coordinates of the start point. */ - void setStartXY(Point2D pt) { + public void setStartXY(Point2D pt) { _setXY(0, pt); } - void setStartXY(double x, double y) { + public void setStartXY(double x, double y) { _setXY(0, Point2D.construct(x, y)); } /** * Returns XYZ coordinates of the start point. Z if 0 if Z is missing. */ - Point3D getStartXYZ() { + public Point3D getStartXYZ() { return _getXYZ(0); } /** * Sets the XYZ coordinates of the start point. */ - void setStartXYZ(Point3D pt) { + public void setStartXYZ(Point3D pt) { _setXYZ(0, pt); } - void setStartXYZ(double x, double y, double z) { + public void setStartXYZ(double x, double y, double z) { _setXYZ(0, Point3D.construct(x, y, z)); } @@ -193,11 +190,11 @@ public double getEndY() { * * @return The XY coordinates of the end point. */ - Point2D getEndXY() { + public Point2D getEndXY() { return Point2D.construct(m_xEnd, m_yEnd); } - void getEndXY(Point2D pt) { + public void getEndXY(Point2D pt) { pt.x = m_xEnd; pt.y = m_yEnd; } @@ -208,11 +205,11 @@ void getEndXY(Point2D pt) { * @param pt * The end point of the segment. */ - void setEndXY(Point2D pt) { + public void setEndXY(Point2D pt) { _setXY(1, pt); } - void setEndXY(double x, double y) { + public void setEndXY(double x, double y) { _setXY(1, Point2D.construct(x, y)); } @@ -221,18 +218,18 @@ void setEndXY(double x, double y) { * * @return The XYZ coordinates of the end point. */ - Point3D getEndXYZ() { + public Point3D getEndXYZ() { return _getXYZ(1); } /** * Sets the XYZ coordinates of the end point. */ - void setEndXYZ(Point3D pt) { + public void setEndXYZ(Point3D pt) { _setXYZ(1, pt); } - void setEndXYZ(double x, double y, double z) { + public void setEndXYZ(double x, double y, double z) { _setXYZ(1, Point3D.construct(x, y, z)); } @@ -358,7 +355,7 @@ int intersect(Segment other, Point2D[] intersectionPoints, * Returns TRUE if this segment intersects with the other segment with the * given tolerance. */ - boolean isIntersecting(Segment other, double tolerance) { + public boolean isIntersecting(Segment other, double tolerance) { return _isIntersecting(other, tolerance, false) != 0; } @@ -366,7 +363,7 @@ boolean isIntersecting(Segment other, double tolerance) { * Returns TRUE if the point and segment intersect (not disjoint) for the * given tolerance. */ - boolean isIntersecting(Point2D pt, double tolerance) { + public boolean isIntersecting(Point2D pt, double tolerance) { return _isIntersectingPoint(pt, tolerance, false); } @@ -485,7 +482,7 @@ protected void _assignVertexDescriptionImpl(VertexDescription newDescription) { int[] mapping = VertexDescriptionDesignerImpl.mapAttributes(newDescription, m_description); - double[] newAttributes = new double[(newDescription._getTotalComponents() - 2) * 2]; + double[] newAttributes = new double[(newDescription.getTotalComponentCount() - 2) * 2]; int old_offset0 = _getEndPointOffset(m_description, 0); int old_offset1 = _getEndPointOffset(m_description, 1); @@ -583,7 +580,7 @@ private void _set(int endPoint, Point src) { int attributeIndex = m_description.getAttributeIndex(semantics); if (attributeIndex >= 0) { if (m_attributes != null) - _resizeAttributes(m_description._getTotalComponents() - 2); + _resizeAttributes(m_description.getTotalComponentCount() - 2); return m_attributes[_getEndPointOffset(m_description, endPoint) + m_description._getPointAttributeOffset(attributeIndex) @@ -626,7 +623,7 @@ else if (ordinate != 0) } if (m_attributes == null) - _resizeAttributes(m_description._getTotalComponents() - 2); + _resizeAttributes(m_description.getTotalComponentCount() - 2); m_attributes[_getEndPointOffset(m_description, endPoint) + m_description._getPointAttributeOffset(attributeIndex) - 2 @@ -645,9 +642,9 @@ public void copyTo(Geometry dst) { Segment segDst = (Segment) dst; segDst.m_description = m_description; - segDst._resizeAttributes(m_description._getTotalComponents() - 2); + segDst._resizeAttributes(m_description.getTotalComponentCount() - 2); _attributeCopy(m_attributes, 0, segDst.m_attributes, 0, - (m_description._getTotalComponents() - 2) * 2); + (m_description.getTotalComponentCount() - 2) * 2); segDst.m_xStart = m_xStart; segDst.m_yStart = m_yStart; segDst.m_xEnd = m_xEnd; @@ -691,7 +688,7 @@ boolean _equalsImpl(Segment other) { if (m_xStart != other.m_xStart || m_xEnd != other.m_xEnd || m_yStart != other.m_yStart || m_yEnd != other.m_yEnd) return false; - for (int i = 0; i < (m_description._getTotalComponents() - 2) * 2; i++) + for (int i = 0; i < (m_description.getTotalComponentCount() - 2) * 2; i++) if (m_attributes[i] != other.m_attributes[i]) return false; @@ -711,10 +708,6 @@ boolean isClosed() { void reverse() { _reverseImpl(); - // because java doesn't support passing value types - // by reference numberutils swap won't work - // NumberUtils.swap(m_xStart, m_xEnd); - // NumberUtils.swap(m_yStart, m_yEnd); double origxStart = m_xStart; double origxEnd = m_xEnd; m_xStart = origxEnd; @@ -778,14 +771,14 @@ int _intersect(Segment other, Point2D[] intersectionPoints, abstract double _calculateArea2DHelper(double xorg, double yorg); static int _getEndPointOffset(VertexDescription vd, int endPoint) { - return endPoint * (vd._getTotalComponents() - 2); + return endPoint * (vd.getTotalComponentCount() - 2); } /** * Returns the coordinate of the point on this segment for the given * parameter value. */ - Point2D getCoord2D(double t) { + public Point2D getCoord2D(double t) { Point2D pt = new Point2D(); getCoord2D(t, pt); return pt; @@ -801,7 +794,7 @@ Point2D getCoord2D(double t) { * @param dst * the coordinate where result will be placed. */ - abstract void getCoord2D(double t, Point2D dst); + public abstract void getCoord2D(double t, Point2D dst); /** * Finds a closest coordinate on this segment. @@ -817,7 +810,7 @@ Point2D getCoord2D(double t) { * obtain the 2D coordinate on the segment from t. To find the * distance, call (inputPoint.sub(seg.getCoord2D(t))).length(); */ - abstract double getClosestCoordinate(Point2D inputPoint, + public abstract double getClosestCoordinate(Point2D inputPoint, boolean bExtrapolate); /** @@ -887,7 +880,7 @@ void _reverseImpl() { * Returns subsegment between parameters t1 and t2. The attributes are * interpolated along the length of the curve. */ - abstract Segment cut(double t1, double t2); + public abstract Segment cut(double t1, double t2); /** * Calculates the subsegment between parameters t1 and t2, and stores the @@ -927,8 +920,8 @@ abstract boolean _isIntersectingPoint(Point2D pt, double tolerance, abstract double lengthToT(double len); - double distance(/* const */Segment otherSegment, - boolean bSegmentsKnownDisjoint) /* const */ + public double distance(/* const */Segment otherSegment, + boolean bSegmentsKnownDisjoint) { // if the segments are not known to be disjoint, and // the segments are found to touch in any way, then return 0.0 diff --git a/src/main/java/com/esri/core/geometry/SegmentIterator.java b/src/main/java/com/esri/core/geometry/SegmentIterator.java index 8312e028..089f93d8 100644 --- a/src/main/java/com/esri/core/geometry/SegmentIterator.java +++ b/src/main/java/com/esri/core/geometry/SegmentIterator.java @@ -25,7 +25,17 @@ package com.esri.core.geometry; /** - * This class provides functionality to iterate over multipath segments. + * This class provides functionality to iterate over MultiPath segments. + * + * Example: + *


+ * SegmentIterator iterator = polygon.querySegmentIterator();
+ * while (iterator.nextPath()) {
+ *   while (iterator.hasNextSegment()) {
+ *     Segment segment = iterator.nextSegment();
+ *   }
+ * }
+ * 
*/ public class SegmentIterator { private SegmentIteratorImpl m_impl; diff --git a/src/main/java/com/esri/core/geometry/SimpleRasterizer.java b/src/main/java/com/esri/core/geometry/SimpleRasterizer.java index 783b8a8f..de817443 100644 --- a/src/main/java/com/esri/core/geometry/SimpleRasterizer.java +++ b/src/main/java/com/esri/core/geometry/SimpleRasterizer.java @@ -295,6 +295,50 @@ public final void fillEnvelope(Envelope2D envIn) { } } + final boolean addSegmentStroke(double x1, double y1, double x2, double y2, double half_width, boolean skip_short, double[] helper_xy_10_elm) + { + double vec_x = x2 - x1; + double vec_y = y2 - y1; + double len = Math.sqrt(vec_x * vec_x + vec_y * vec_y); + if (skip_short && len < 0.5) + return false; + + boolean bshort = len < 0.00001; + if (bshort) + { + len = 0.00001; + vec_x = len; + vec_y = 0.0; + } + + double f = half_width / len; + vec_x *= f; vec_y *= f; + double vecA_x = -vec_y; + double vecA_y = vec_x; + double vecB_x = vec_y; + double vecB_y = -vec_x; + //extend by half width + x1 -= vec_x; + y1 -= vec_y; + x2 += vec_x; + y2 += vec_y; + //create rotated rectangle + double[] fan = helper_xy_10_elm; + assert(fan.length == 10); + fan[0] = x1 + vecA_x; + fan[1] = y1 + vecA_y;//fan[0].add(pt_start, vecA); + fan[2] = x1 + vecB_x; + fan[3] = y1 + vecB_y;//fan[1].add(pt_start, vecB); + fan[4] = x2 + vecB_x; + fan[5] = y2 + vecB_y;//fan[2].add(pt_end, vecB) + fan[6] = x2 + vecA_x; + fan[7] = y2 + vecA_y;//fan[3].add(pt_end, vecA) + fan[8] = fan[0]; + fan[9] = fan[1]; + addRing(fan); + return true; + } + public final ScanCallback getScanCallback() { return callback_; } diff --git a/src/main/java/com/esri/core/geometry/SpatialReferenceImpl.java b/src/main/java/com/esri/core/geometry/SpatialReferenceImpl.java index 77520c40..1aa5886a 100644 --- a/src/main/java/com/esri/core/geometry/SpatialReferenceImpl.java +++ b/src/main/java/com/esri/core/geometry/SpatialReferenceImpl.java @@ -24,6 +24,7 @@ package com.esri.core.geometry; +import java.util.Arrays; import java.util.HashMap; import java.util.Map; import java.util.concurrent.locks.ReentrantLock; @@ -230,10 +231,77 @@ static double geodesicDistanceOnWGS84Impl(Point ptFrom, Point ptTo) { double e2 = 0.0066943799901413165; // ellipticity for WGS_1984 double rpu = Math.PI / 180.0; PeDouble answer = new PeDouble(); - GeoDist.geodesic_distance_ngs(a, e2, ptFrom.getXY().x * rpu, - ptFrom.getXY().y * rpu, ptTo.getXY().x * rpu, ptTo.getXY().y + GeoDist.geodesic_distance_ngs(a, e2, ptFrom.getX() * rpu, + ptFrom.getY() * rpu, ptTo.getX() * rpu, ptTo.getY() * rpu, answer, null, null); return answer.val; } + public String getAuthority() { + int latestWKID = getLatestID(); + if (latestWKID <= 0) + return new String(""); + + return getAuthority_(latestWKID); + } + + private String getAuthority_(int latestWKID) { + String authority; + + if (latestWKID >= 1024 && latestWKID <= 32767) { + + int index = Arrays.binarySearch(m_esri_codes, latestWKID); + + if (index >= 0) + authority = new String("ESRI"); + else + authority = new String("EPSG"); + } else { + authority = new String("ESRI"); + } + + return authority; + } + + private static final int[] m_esri_codes = { + 2181, // ED_1950_Turkey_9 + 2182, // ED_1950_Turkey_10 + 2183, // ED_1950_Turkey_11 + 2184, // ED_1950_Turkey_12 + 2185, // ED_1950_Turkey_13 + 2186, // ED_1950_Turkey_14 + 2187, // ED_1950_Turkey_15 + 4305, // GCS_Voirol_Unifie_1960 + 4812, // GCS_Voirol_Unifie_1960_Paris + 20002, // Pulkovo_1995_GK_Zone_2 + 20003, // Pulkovo_1995_GK_Zone_3 + 20062, // Pulkovo_1995_GK_Zone_2N + 20063, // Pulkovo_1995_GK_Zone_3N + 24721, // La_Canoa_UTM_Zone_21N + 26761, // NAD_1927_StatePlane_Hawaii_1_FIPS_5101 + 26762, // NAD_1927_StatePlane_Hawaii_2_FIPS_5102 + 26763, // NAD_1927_StatePlane_Hawaii_3_FIPS_5103 + 26764, // NAD_1927_StatePlane_Hawaii_4_FIPS_5104 + 26765, // NAD_1927_StatePlane_Hawaii_5_FIPS_5105 + 26788, // NAD_1927_StatePlane_Michigan_North_FIPS_2111 + 26789, // NAD_1927_StatePlane_Michigan_Central_FIPS_2112 + 26790, // NAD_1927_StatePlane_Michigan_South_FIPS_2113 + 30591, // Nord_Algerie + 30592, // Sud_Algerie + 31491, // Germany_Zone_1 + 31492, // Germany_Zone_2 + 31493, // Germany_Zone_3 + 31494, // Germany_Zone_4 + 31495, // Germany_Zone_5 + 32059, // NAD_1927_StatePlane_Puerto_Rico_FIPS_5201 + 32060, // NAD_1927_StatePlane_Virgin_Islands_St_Croix_FIPS_5202 + }; + + @Override + public int hashCode() { + if (m_userWkid != 0) + return NumberUtils.hash(m_userWkid); + + return m_userWkt.hashCode(); + } } diff --git a/src/main/java/com/esri/core/geometry/Transformation2D.java b/src/main/java/com/esri/core/geometry/Transformation2D.java index d1472399..99ea7cde 100644 --- a/src/main/java/com/esri/core/geometry/Transformation2D.java +++ b/src/main/java/com/esri/core/geometry/Transformation2D.java @@ -25,8 +25,10 @@ package com.esri.core.geometry; /** - * The affine transformation class for 2D.
- * Vector is a row: + * The affine transformation class for 2D. + * + * Vector is a row: + * *
|m11 m12 0| *
| x y 1| * |m21 m22 0| = |m11 * x + m21 * y + m31 m12 * x + m22 * y + m32 1| *
|m31 m32 1| @@ -456,9 +458,9 @@ public boolean isIdentity() { * The tolerance value. */ public boolean isIdentity(double tol) { - Point2D pt = Point2D.construct(0.0, 1.0); + Point2D pt = Point2D.construct(0., 1.); transform(pt, pt); - pt.sub(Point2D.construct(0.0, 1.0)); + pt.sub(Point2D.construct(0., 1.)); if (pt.sqrLength() > tol * tol) return false; @@ -469,7 +471,7 @@ public boolean isIdentity(double tol) { pt.setCoords(1.0, 0.0); transform(pt, pt); - pt.sub(Point2D.construct(1.0, 0)); + pt.sub(Point2D.construct(1.0, 0.0)); return pt.sqrLength() <= tol * tol; } diff --git a/src/main/java/com/esri/core/geometry/Transformation3D.java b/src/main/java/com/esri/core/geometry/Transformation3D.java index 02719a3c..99702706 100644 --- a/src/main/java/com/esri/core/geometry/Transformation3D.java +++ b/src/main/java/com/esri/core/geometry/Transformation3D.java @@ -25,18 +25,13 @@ package com.esri.core.geometry; /** - * @brief The 3D affine transformation Vector is a row: |m11 m12 0| | x y 1| * - * |m21 m22 0| = |m11 * x + m21 * y + m31 m12 * x + m22 * y + m32 1| |m31 - * m32 1| Then elements of the Transformation2D are as follows: |xx yx 0| - * | x y 1| * |xy yy 0| = |xx * x + xy * y + xd yx * x + yy * y + yd 1| - * |xd yd 1| + * The 3D affine transformation class. * - * We use matrices for transformations of the vectors as rows (case 2). - * That means the math expressions on the Geometry matrix operations - * should be writen like this: v' = v * M1 * M2 * M3 = ( (v * M1) * M2 ) - * * M3, where v is a vector, Mn are the matrices. This is equivalent to - * the following line of code: ResultVector = - * (M1.Mul(M2).Mul(M3)).Transform(Vector) + * We use matrices for transformations of the vectors as rows. That means the + * math expressions on the Geometry matrix operations should be writen like + * this: v' = v * M1 * M2 * M3 = ( (v * M1) * M2 ) * M3, where v is a vector, Mn + * are the matrices. This is equivalent to the following line of code: + * ResultVector = (M1.Mul(M2).Mul(M3)).Transform(Vector) */ final class Transformation3D { @@ -201,13 +196,11 @@ public static void multiply(Transformation3D a, Transformation3D b, * * @param src * The input transformation. - * @param dst - * The inverse of the input transformation. - * @throws Throws - * the GeometryException("math_singularity") exception if the - * Inverse can not be calculated. + * @param result + * The inverse of the input transformation. Throws the + * GeometryException("math singularity") exception if the Inverse + * can not be calculated. */ - // static public static void inverse(Transformation3D src, Transformation3D result) { double det = src.xx * (src.yy * src.zz - src.zy * src.yz) - src.yx * (src.xy * src.zz - src.zy * src.xz) + src.zx diff --git a/src/main/java/com/esri/core/geometry/VertexDescription.java b/src/main/java/com/esri/core/geometry/VertexDescription.java index 1fc1a6f6..07e7630b 100644 --- a/src/main/java/com/esri/core/geometry/VertexDescription.java +++ b/src/main/java/com/esri/core/geometry/VertexDescription.java @@ -25,6 +25,8 @@ package com.esri.core.geometry; +import java.util.Arrays; + /** * Describes the vertex format of a Geometry. * @@ -40,67 +42,39 @@ * table. You may look the vertices of a Geometry as if they are stored in a * database table, and the VertexDescription defines the fields of the table. */ -public class VertexDescription { - - private double[] m_defaultPointAttributes; - private int[] m_pointAttributeOffsets; - int m_attributeCount; - int m_total_component_count; - - int[] m_semantics; - - int[] m_semanticsToIndexMap; - - int m_hash; +public final class VertexDescription { + /** + * Describes the attribute and, in case of predefined attributes, provides a + * hint of the attribute use. + */ + public interface Semantics { + static final int POSITION = 0; // xy coordinates of a point (2D + // vector of double, linear + // interpolation) - static double[] _defaultValues = { 0, 0, NumberUtils.NaN(), 0, 0, 0, 0, 0, - 0 }; + static final int Z = 1; // z coordinates of a point (double, + // linear interpolation) - static int[] _interpolation = { Interpolation.LINEAR, Interpolation.LINEAR, - Interpolation.LINEAR, Interpolation.NONE, Interpolation.ANGULAR, - Interpolation.LINEAR, Interpolation.LINEAR, Interpolation.LINEAR, - Interpolation.NONE, - }; + static final int M = 2; // m attribute (double, linear + // interpolation) - static int[] _persistence = { Persistence.enumDouble, - Persistence.enumDouble, Persistence.enumDouble, - Persistence.enumInt32, Persistence.enumFloat, - Persistence.enumFloat, Persistence.enumFloat, - Persistence.enumFloat, Persistence.enumInt32, - }; + static final int ID = 3; // id (int, no interpolation) - static int[] _persistencesize = { 4, 8, 4, 8, 1, 2 }; + static final int NORMAL = 4; // xyz coordinates of normal vector + // (float, angular interpolation) - static int[] _components = { 2, 1, 1, 1, 3, 1, 2, 3, 2, }; + static final int TEXTURE1D = 5; // u coordinates of texture + // (float, linear interpolation) - VertexDescription() { - m_attributeCount = 0; - m_total_component_count = 0; + static final int TEXTURE2D = 6; // uv coordinates of texture + // (float, linear interpolation) - } + static final int TEXTURE3D = 7; // uvw coordinates of texture + // (float, linear interpolation) - VertexDescription(int hashValue, VertexDescription other) { - m_attributeCount = other.m_attributeCount; - m_total_component_count = other.m_total_component_count; - m_semantics = other.m_semantics.clone(); - m_semanticsToIndexMap = other.m_semanticsToIndexMap.clone(); - m_hash = other.m_hash; + static final int ID2 = 8; // two component ID - // Prepare default values for the Point geometry. - m_pointAttributeOffsets = new int[getAttributeCount()]; - int offset = 0; - for (int i = 0; i < getAttributeCount(); i++) { - m_pointAttributeOffsets[i] = offset; - offset += getComponentCount(m_semantics[i]); - } - m_total_component_count = offset; - m_defaultPointAttributes = new double[offset]; - for (int i = 0; i < getAttributeCount(); i++) { - int components = getComponentCount(getSemantics(i)); - double dv = getDefaultValue(getSemantics(i)); - for (int icomp = 0; icomp < components; icomp++) - m_defaultPointAttributes[m_pointAttributeOffsets[i] + icomp] = dv; - } + static final int MAXSEMANTICS = 8; // the max semantics value } /** @@ -134,40 +108,6 @@ interface Persistence { public static final int enumInt16 = 5; }; - /** - * Describes the attribute and, in case of predefined attributes, provides a - * hint of the attribute use. - */ - public interface Semantics { - static final int POSITION = 0; // xy coordinates of a point (2D - // vector of double, linear - // interpolation) - - static final int Z = 1; // z coordinates of a point (double, - // linear interpolation) - - static final int M = 2; // m attribute (double, linear - // interpolation) - - static final int ID = 3; // id (int, no interpolation) - - static final int NORMAL = 4; // xyz coordinates of normal vector - // (float, angular interpolation) - - static final int TEXTURE1D = 5; // u coordinates of texture - // (float, linear interpolation) - - static final int TEXTURE2D = 6; // uv coordinates of texture - // (float, linear interpolation) - - static final int TEXTURE3D = 7; // uvw coordinates of texture - // (float, linear interpolation) - - static final int ID2 = 8; // two component ID - - static final int MAXSEMANTICS = 10; // the max semantics value - } - /** * Returns the attribute count of this description. The value is always * greater or equal to 1. The first attribute is always a POSITION. @@ -181,13 +121,10 @@ public final int getAttributeCount() { * * @param attributeIndex * The index of the attribute in the description. Max value is - * GetAttributeCount() - 1. + * getAttributeCount() - 1. */ public final int getSemantics(int attributeIndex) { - if (attributeIndex < 0 || attributeIndex > m_attributeCount) - throw new IllegalArgumentException(); - - return m_semantics[attributeIndex]; + return m_indexToSemantics[attributeIndex]; } /** @@ -249,20 +186,6 @@ public static int getComponentCount(int semantics) { return _components[semantics]; } - /** - * Returns True for integer persistence type. - */ - static boolean isIntegerPersistence(int persistence) { - return persistence < Persistence.enumInt32; - } - - /** - * Returns True for integer semantics type. - */ - static boolean isIntegerSemantics(int semantics) { - return isIntegerPersistence(getPersistence(semantics)); - } - /** * Returns True if the attribute with the given name and given set exists. * @@ -270,27 +193,40 @@ static boolean isIntegerSemantics(int semantics) { * The semantics of the attribute. */ public boolean hasAttribute(int semantics) { - return m_semanticsToIndexMap[semantics] >= 0; + return (m_semanticsBitArray & (1 << semantics)) != 0; + } + + /** + * Returns True if this vertex description includes all attributes from the + * src. + * + * @param src + * The Vertex_description to compare with. + * @return The function returns false, only when this description does not + * have some of the attribute that src has. + */ + public final boolean hasAttributesFrom(VertexDescription src) { + return (m_semanticsBitArray & src.m_semanticsBitArray) == src.m_semanticsBitArray; } /** * Returns True, if the vertex has Z attribute. */ - public boolean hasZ() { + public final boolean hasZ() { return hasAttribute(Semantics.Z); } /** * Returns True, if the vertex has M attribute. */ - public boolean hasM() { + public final boolean hasM() { return hasAttribute(Semantics.M); } /** * Returns True, if the vertex has ID attribute. */ - public boolean hasID() { + public final boolean hasID() { return hasAttribute(Semantics.ID); } @@ -302,15 +238,15 @@ public static double getDefaultValue(int semantics) { return _defaultValues[semantics]; } - int getPointAttributeOffset_(int attribute_index) { - return m_pointAttributeOffsets[attribute_index]; + int getPointAttributeOffset_(int attributeIndex) { + return m_pointAttributeOffsets[attributeIndex]; } /** * Returns the total component count. */ public int getTotalComponentCount() { - return m_total_component_count; + return m_totalComponentCount; } /** @@ -323,28 +259,19 @@ public static boolean isDefaultValue(int semantics, double v) { .doubleToInt64Bits(v); } - static int getPersistenceFromInt(int size) { - if (size == 4) - return Persistence.enumInt32; - else if (size == 8) - return Persistence.enumInt64; - else - throw new IllegalArgumentException(); + static boolean isIntegerPersistence(int persistence) { + return persistence >= Persistence.enumInt32; } + static boolean isIntegerSemantics(int semantics) { + return isIntegerPersistence(getPersistence(semantics)); + } + @Override public boolean equals(Object _other) { return (Object) this == _other; } - int calculateHashImpl() { - int v = NumberUtils.hash(m_semantics[0]); - for (int i = 1; i < m_attributeCount; i++) - v = NumberUtils.hash(v, m_semantics[i]); - - return v; // if attribute size is 1, it returns 0 - } - /** * * Returns a packed array of double representation of all ordinates of @@ -373,19 +300,81 @@ int _getPointAttributeOffsetFromSemantics(int semantics) { return m_pointAttributeOffsets[getAttributeIndex(semantics)]; } - int _getTotalComponents() { - return m_defaultPointAttributes.length; - } - @Override public int hashCode() { return m_hash; } int _getSemanticsImpl(int attributeIndex) { - return m_semantics[attributeIndex]; + return m_indexToSemantics[attributeIndex]; + } + + VertexDescription(int bitMask) { + m_semanticsBitArray = bitMask; + m_attributeCount = 0; + m_totalComponentCount = 0; + m_semanticsToIndexMap = new int[Semantics.MAXSEMANTICS + 1]; + Arrays.fill(m_semanticsToIndexMap, -1); + for (int i = 0, flag = 1, n = Semantics.MAXSEMANTICS + 1; i < n; i++) { + if ((bitMask & flag) != 0) { + m_semanticsToIndexMap[i] = m_attributeCount; + m_attributeCount++; + int comps = getComponentCount(i); + m_totalComponentCount += comps; + } + + flag <<= 1; + } + + m_indexToSemantics = new int[m_attributeCount]; + for (int i = 0, n = Semantics.MAXSEMANTICS + 1; i < n; i++) { + int attrib = m_semanticsToIndexMap[i]; + if (attrib >= 0) + m_indexToSemantics[attrib] = i; + } + + m_defaultPointAttributes = new double[m_totalComponentCount]; + m_pointAttributeOffsets = new int[m_attributeCount]; + int offset = 0; + for (int i = 0, n = m_attributeCount; i < n; i++) { + int semantics = getSemantics(i); + int comps = getComponentCount(semantics); + double v = getDefaultValue(semantics); + m_pointAttributeOffsets[i] = offset; + for (int icomp = 0; icomp < comps; icomp++) { + m_defaultPointAttributes[offset] = v; + offset++; + } + } + + m_hash = NumberUtils.hash(m_semanticsBitArray); } - // TODO: clone, equald, hashcode - whats really needed? + private int m_attributeCount; + int m_semanticsBitArray; //the main component + private int m_totalComponentCount; + private int m_hash; + + private int[] m_semanticsToIndexMap; + private int[] m_indexToSemantics; + private int[] m_pointAttributeOffsets; + private double[] m_defaultPointAttributes; + + static final double[] _defaultValues = { 0, 0, NumberUtils.NaN(), 0, 0, 0, + 0, 0, 0 }; + + static final int[] _interpolation = { Interpolation.LINEAR, + Interpolation.LINEAR, Interpolation.LINEAR, Interpolation.NONE, + Interpolation.ANGULAR, Interpolation.LINEAR, Interpolation.LINEAR, + Interpolation.LINEAR, Interpolation.NONE, }; + + static final int[] _persistence = { Persistence.enumDouble, + Persistence.enumDouble, Persistence.enumDouble, + Persistence.enumInt32, Persistence.enumFloat, + Persistence.enumFloat, Persistence.enumFloat, + Persistence.enumFloat, Persistence.enumInt32, }; + + static final int[] _persistencesize = { 4, 8, 4, 8, 1, 2 }; + static final int[] _components = { 2, 1, 1, 1, 3, 1, 2, 3, 2, }; } diff --git a/src/main/java/com/esri/core/geometry/VertexDescriptionDesignerImpl.java b/src/main/java/com/esri/core/geometry/VertexDescriptionDesignerImpl.java index 268b75bb..c6d69b15 100644 --- a/src/main/java/com/esri/core/geometry/VertexDescriptionDesignerImpl.java +++ b/src/main/java/com/esri/core/geometry/VertexDescriptionDesignerImpl.java @@ -32,188 +32,53 @@ * This factory class allows to describe and create a VertexDescription * instance. */ -class VertexDescriptionDesignerImpl extends VertexDescription { - - /** - * Designer default constructor produces XY vertex description (POSITION - * semantics only). - */ - public VertexDescriptionDesignerImpl() { - super(); - m_semantics = new int[Semantics.MAXSEMANTICS]; - m_semantics[0] = Semantics.POSITION; - m_attributeCount = 1; - - m_semanticsToIndexMap = new int[Semantics.MAXSEMANTICS]; - - for (int i = 0; i < Semantics.MAXSEMANTICS; i++) - m_semanticsToIndexMap[i] = -1; - - m_semanticsToIndexMap[m_semantics[0]] = 0; - - m_bModified = true; - } - - /** - * Creates description designer and initializes it from the given - * description. Use this to add or remove attributes from the description. - */ - public VertexDescriptionDesignerImpl(VertexDescription other) { - super(other.hashCode(), other); - m_bModified = true; - } - - /** - * Adds a new attribute to the VertexDescription. - * - * @param semantics - * Attribute semantics. - */ - public void addAttribute(int semantics) { - if (hasAttribute(semantics)) - return; +final class VertexDescriptionDesignerImpl { + static VertexDescription getVertexDescription(int descriptionBitMask) { + return VertexDescriptionHash.getInstance() + .FindOrAdd(descriptionBitMask); + } + + static VertexDescription getMergedVertexDescription( + VertexDescription descr1, VertexDescription descr2) { + int mask = descr1.m_semanticsBitArray | descr2.m_semanticsBitArray; + if ((mask & descr1.m_semanticsBitArray) == mask) { + return descr1; + } else if ((mask & descr2.m_semanticsBitArray) == mask) { + return descr2; + } - m_semanticsToIndexMap[semantics] = 0;// assign a value >= 0 to mark it - // as existing - _initMapping(); + return getVertexDescription(mask); } - /** - * Removes given attribute. - * - * @param semantics - * Attribute semantics. - */ - void removeAttribute(int semantics) { - - if (semantics == Semantics.POSITION) - throw new IllegalArgumentException( - "Position attribue cannot be removed");// not allowed to - // remove the xy - - if (!hasAttribute(semantics)) - return; + static VertexDescription getMergedVertexDescription( + VertexDescription descr, int semantics) { + int mask = descr.m_semanticsBitArray | (1 << semantics); + if ((mask & descr.m_semanticsBitArray) == mask) { + return descr; + } - m_semanticsToIndexMap[semantics] = -1;// assign a value < 0 to mark it - // as removed - _initMapping(); + return getVertexDescription(mask); } - /** - * Removes all attributes from the designer with exception of the POSITION - * attribute. - */ - public void reset() { - m_semantics[0] = Semantics.POSITION; - m_attributeCount = 1; - - for (int i : m_semanticsToIndexMap) - m_semanticsToIndexMap[i] = -1; - - m_semanticsToIndexMap[m_semantics[0]] = 0; - m_bModified = true; - } + static VertexDescription removeSemanticsFromVertexDescription( + VertexDescription descr, int semanticsToRemove) { + int mask = (descr.m_semanticsBitArray | (1 << (int) semanticsToRemove)) + - (1 << (int) semanticsToRemove); + if (mask == descr.m_semanticsBitArray) { + return descr; + } - /** - * Returns a VertexDescription corresponding to the vertex design.
- * Note: the same instance of VertexDescription will be returned each time - * for the same same set of attributes and attribute properties.
- * The method searches for the VertexDescription in a global hash table. If - * found, it is returned. Else, a new instance of the VertexDescription is - * added to the has table and returned. - */ - public VertexDescription getDescription() { - VertexDescriptionHash vdhash = VertexDescriptionHash.getInstance(); - VertexDescriptionDesignerImpl vdd = this; - return vdhash.add(vdd); + return getVertexDescription(mask); } - /** - * Returns a default VertexDescription that has X and Y coordinates only. - */ static VertexDescription getDefaultDescriptor2D() { - VertexDescriptionHash vdhash = VertexDescriptionHash.getInstance(); - VertexDescription vd = vdhash.getVD2D(); - return vd; + return VertexDescriptionHash.getInstance().getVD2D(); } - /** - * Returns a default VertexDescription that has X, Y, and Z coordinates only - */ static VertexDescription getDefaultDescriptor3D() { - VertexDescriptionHash vdhash = VertexDescriptionHash.getInstance(); - VertexDescription vd = vdhash.getVD3D(); - return vd; - } - - VertexDescription _createInternal() { - int hash = hashCode(); - VertexDescription vd = new VertexDescription(hash, this); - return vd; - } - - protected boolean m_bModified; - - protected void _initMapping() { - m_attributeCount = 0; - for (int i = 0, j = 0; i < Semantics.MAXSEMANTICS; i++) { - if (m_semanticsToIndexMap[i] >= 0) { - m_semantics[j] = i; - m_semanticsToIndexMap[i] = j; - j++; - m_attributeCount++; - } - } - - m_bModified = true; - } - - @Override - public int hashCode() { - if (m_bModified) { - m_hash = calculateHashImpl(); - m_bModified = false; - } - - return m_hash; - } - - @Override - public boolean equals(Object _other) { - if (_other == null) - return false; - if (_other == this) - return true; - if (_other.getClass() != getClass()) - return false; - VertexDescriptionDesignerImpl other = (VertexDescriptionDesignerImpl) (_other); - if (other.getAttributeCount() != getAttributeCount()) - return false; - - for (int i = 0; i < m_attributeCount; i++) { - if (m_semantics[i] != other.m_semantics[i]) - return false; - } - if (m_bModified != other.m_bModified) - return false; - - return true; + return VertexDescriptionHash.getInstance().getVD3D(); } - public boolean isDesignerFor(VertexDescription vd) { - if (vd.getAttributeCount() != getAttributeCount()) - return false; - - for (int i = 0; i < m_attributeCount; i++) { - if (m_semantics[i] != vd.m_semantics[i]) - return false; - } - - return true; - } - - // returns a mapping from the source attribute indices to the destination - // attribute indices. static int[] mapAttributes(VertexDescription src, VertexDescription dest) { int[] srcToDst = new int[src.getAttributeCount()]; Arrays.fill(srcToDst, -1); @@ -222,41 +87,4 @@ static int[] mapAttributes(VertexDescription src, VertexDescription dest) { } return srcToDst; } - - static VertexDescription getMergedVertexDescription(VertexDescription src, - int semanticsToAdd) { - VertexDescriptionDesignerImpl vdd = new VertexDescriptionDesignerImpl( - src); - vdd.addAttribute(semanticsToAdd); - return vdd.getDescription(); - } - - static VertexDescription getMergedVertexDescription(VertexDescription d1, VertexDescription d2) { - VertexDescriptionDesignerImpl vdd = null; - for (int semantics = Semantics.POSITION; semantics < Semantics.MAXSEMANTICS; semantics++) { - if (!d1.hasAttribute(semantics) && d2.hasAttribute(semantics)) { - if (vdd == null) { - vdd = new VertexDescriptionDesignerImpl(d1); - } - - vdd.addAttribute(semantics); - } - } - - if (vdd != null) { - return vdd.getDescription(); - } - - return d1; - } - - static VertexDescription removeSemanticsFromVertexDescription( - VertexDescription src, int semanticsToRemove) { - VertexDescriptionDesignerImpl vdd = new VertexDescriptionDesignerImpl( - src); - vdd.removeAttribute(semanticsToRemove); - return vdd.getDescription(); - } - } - diff --git a/src/main/java/com/esri/core/geometry/VertexDescriptionHash.java b/src/main/java/com/esri/core/geometry/VertexDescriptionHash.java index dfe372aa..8e12dfec 100644 --- a/src/main/java/com/esri/core/geometry/VertexDescriptionHash.java +++ b/src/main/java/com/esri/core/geometry/VertexDescriptionHash.java @@ -25,9 +25,11 @@ package com.esri.core.geometry; import com.esri.core.geometry.VertexDescription.Semantics; + import java.lang.ref.WeakReference; import java.util.HashMap; import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; /** * A hash object singleton that stores all VertexDescription instances via @@ -35,75 +37,45 @@ * VertexDescription instances to prevent duplicates. */ final class VertexDescriptionHash { - Map> map = new HashMap>(); - - private static VertexDescription m_vd2D; + HashMap m_map = new HashMap(); - private static VertexDescription m_vd3D; + private static VertexDescription m_vd2D = new VertexDescription(1); + private static VertexDescription m_vd3D = new VertexDescription(3); private static final VertexDescriptionHash INSTANCE = new VertexDescriptionHash(); private VertexDescriptionHash() { - VertexDescriptionDesignerImpl vdd2D = new VertexDescriptionDesignerImpl(); - add(vdd2D); - VertexDescriptionDesignerImpl vdd3D = new VertexDescriptionDesignerImpl(); - vdd3D.addAttribute(Semantics.Z); - add(vdd3D); + m_map.put(1, m_vd2D); + m_map.put(3, m_vd3D); } public static VertexDescriptionHash getInstance() { return INSTANCE; } - public VertexDescription getVD2D() { + public final VertexDescription getVD2D() { return m_vd2D; } - public VertexDescription getVD3D() { + public final VertexDescription getVD3D() { return m_vd3D; } - synchronized public VertexDescription add(VertexDescriptionDesignerImpl vdd) { - // Firstly quick test for 2D/3D descriptors. - int h = vdd.hashCode(); - - if ((m_vd2D != null) && m_vd2D.hashCode() == h) { - if (vdd.isDesignerFor(m_vd2D)) - return m_vd2D; - } - - if ((m_vd3D != null) && (m_vd3D.hashCode() == h)) { - if (vdd.isDesignerFor(m_vd3D)) - return m_vd3D; - } - - // Now search in the hash. - - VertexDescription vd = null; - if (map.containsKey(h)) { - WeakReference vdweak = map.get(h); - vd = vdweak.get(); - if (vd == null) // GC'd VertexDescription - map.remove(h); - } - - if (vd == null) { // either not in map to begin with, or has been GC'd - vd = vdd._createInternal(); - - if (vd.getAttributeCount() == 1) { - m_vd2D = vd; - } else if ((vd.getAttributeCount() == 2) - && (vd.getSemantics(1) == Semantics.Z)) { - m_vd3D = vd; - } else { - WeakReference vdweak = new WeakReference( - vd); - - map.put(h, vdweak); + public final VertexDescription FindOrAdd(int bitSet) { + if (bitSet == 1) + return m_vd2D; + if (bitSet == 3) + return m_vd3D; + + synchronized (this) { + VertexDescription vd = m_map.get(bitSet); + if (vd == null) { + vd = new VertexDescription(bitSet); + m_map.put(bitSet, vd); } + return vd; } - - return vd; } + } diff --git a/src/main/java/com/esri/core/geometry/WktParser.java b/src/main/java/com/esri/core/geometry/WktParser.java index c3b4894e..7f79ed5f 100644 --- a/src/main/java/com/esri/core/geometry/WktParser.java +++ b/src/main/java/com/esri/core/geometry/WktParser.java @@ -222,7 +222,7 @@ private void geometry_() { m_function_stack.removeLast(); if (m_start_token + 5 <= m_wkt_string.length() - && m_wkt_string.regionMatches(true, m_start_token, "points", 0, + && m_wkt_string.regionMatches(true, m_start_token, "point", 0, 5)) { m_end_token = m_start_token + 5; m_current_token_type = WktToken.point; diff --git a/src/main/java/com/esri/core/geometry/ogc/OGCConcreteGeometryCollection.java b/src/main/java/com/esri/core/geometry/ogc/OGCConcreteGeometryCollection.java index b87a570a..1e4cb7be 100644 --- a/src/main/java/com/esri/core/geometry/ogc/OGCConcreteGeometryCollection.java +++ b/src/main/java/com/esri/core/geometry/ogc/OGCConcreteGeometryCollection.java @@ -3,13 +3,11 @@ import com.esri.core.geometry.Envelope; import com.esri.core.geometry.Geometry; import com.esri.core.geometry.GeometryCursor; +import com.esri.core.geometry.NumberUtils; import com.esri.core.geometry.Polygon; import com.esri.core.geometry.SpatialReference; -import com.esri.core.geometry.Operator; -import com.esri.core.geometry.JsonCursor; -import com.esri.core.geometry.OperatorFactoryLocal; +import com.esri.core.geometry.GeoJsonExportFlags; import com.esri.core.geometry.OperatorExportToGeoJson; - import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.util.ArrayList; @@ -152,6 +150,40 @@ public ByteBuffer asBinary() { return wkbBuffer; } + @Override + public String asGeoJson() { + return asGeoJsonImpl(GeoJsonExportFlags.geoJsonExportDefaults); + } + + @Override + String asGeoJsonImpl(int export_flags) { + StringBuilder sb = new StringBuilder(); + + sb.append("{\"type\":\"GeometryCollection\",\"geometries\":"); + + sb.append("["); + for (int i = 0, n = numGeometries(); i < n; i++) { + if (i > 0) + sb.append(","); + + if (geometryN(i) != null) + sb.append(geometryN(i).asGeoJsonImpl(GeoJsonExportFlags.geoJsonExportSkipCRS)); + } + + sb.append("],\"crs\":"); + + if (esriSR != null) { + String crs_value = OperatorExportToGeoJson.local().exportSpatialReference(0, esriSR); + sb.append(crs_value); + } else { + sb.append("\"null\""); + } + + sb.append("}"); + + return sb.toString(); + } + @Override public boolean isEmpty() { return numGeometries() == 0; @@ -319,8 +351,7 @@ public void setSpatialReference(SpatialReference esriSR_) { } @Override - public OGCGeometry convertToMulti() - { + public OGCGeometry convertToMulti() { return this; } @@ -330,32 +361,44 @@ public String asJson() { } @Override - public String asGeoJson() { - StringBuilder sb = new StringBuilder(); - - OperatorExportToGeoJson op = (OperatorExportToGeoJson) OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.ExportToGeoJson); - JsonCursor cursor = op.execute(this.esriSR, getEsriGeometryCursor()); - - sb.append("{\"type\" : \"GeometryCollection\", \"geometries\" : "); - String shape = cursor.next(); - if (shape == null){ - // geometry collection with empty list of geometries - sb.append("[]}"); - return sb.toString(); + public boolean equals(Object other) { + if (other == null) + return false; + + if (other == this) + return true; + + if (other.getClass() != getClass()) + return false; + + OGCConcreteGeometryCollection another = (OGCConcreteGeometryCollection)other; + if (geometries != null) { + if (!geometries.equals(another.geometries)) + return false; } - - sb.append("["); - sb.append(shape); - - while(true){ - shape = cursor.next(); - if(shape == null) - break; - sb.append(", ").append(shape); + else if (another.geometries != null) + return false; + + if (esriSR == another.esriSR) { + return true; } + + if (esriSR != null && another.esriSR != null) { + return esriSR.equals(another.esriSR); + } + + return false; + } - sb.append("]}"); - return sb.toString(); + @Override + public int hashCode() { + int hash = 1; + if (geometries != null) + hash = geometries.hashCode(); + + if (esriSR != null) + hash = NumberUtils.hashCombine(hash, esriSR.hashCode()); + + return hash; } } diff --git a/src/main/java/com/esri/core/geometry/ogc/OGCGeometry.java b/src/main/java/com/esri/core/geometry/ogc/OGCGeometry.java index 9783bb94..c0a93ce2 100644 --- a/src/main/java/com/esri/core/geometry/ogc/OGCGeometry.java +++ b/src/main/java/com/esri/core/geometry/ogc/OGCGeometry.java @@ -12,6 +12,7 @@ import com.esri.core.geometry.Envelope; import com.esri.core.geometry.Envelope1D; +import com.esri.core.geometry.GeoJsonExportFlags; import com.esri.core.geometry.Geometry; import com.esri.core.geometry.GeometryCursor; import com.esri.core.geometry.GeometryCursorAppend; @@ -19,6 +20,7 @@ import com.esri.core.geometry.MapGeometry; import com.esri.core.geometry.MapOGCStructure; import com.esri.core.geometry.MultiPoint; +import com.esri.core.geometry.NumberUtils; import com.esri.core.geometry.OGCStructure; import com.esri.core.geometry.Operator; import com.esri.core.geometry.OperatorBuffer; @@ -93,7 +95,12 @@ public ByteBuffer asBinary() { public String asGeoJson() { OperatorExportToGeoJson op = (OperatorExportToGeoJson) OperatorFactoryLocal .getInstance().getOperator(Operator.Type.ExportToGeoJson); - return op.execute(getEsriGeometry()); + return op.execute(esriSR, getEsriGeometry()); + } + + String asGeoJsonImpl(int export_flags) { + OperatorExportToGeoJson op = (OperatorExportToGeoJson) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.ExportToGeoJson); + return op.execute(export_flags, esriSR, getEsriGeometry()); } /** @@ -489,7 +496,7 @@ public static OGCGeometry fromEsriShape(ByteBuffer buffer) { } public static OGCGeometry fromJson(String string) - throws JsonParseException, IOException { + throws Exception { JsonFactory factory = new JsonFactory(); JsonParser jsonParserPt = factory.createJsonParser(string); jsonParserPt.nextToken(); @@ -498,7 +505,8 @@ public static OGCGeometry fromJson(String string) mapGeom.getSpatialReference()); } - public static OGCGeometry fromGeoJson(String string) throws JSONException { + public static OGCGeometry fromGeoJson(String string) + throws Exception { OperatorImportFromGeoJson op = (OperatorImportFromGeoJson) OperatorFactoryLocal .getInstance().getOperator(Operator.Type.ImportFromGeoJson); MapOGCStructure mapOGCStructure = op.executeOGC(0, string, null); @@ -679,4 +687,51 @@ public String toString() { return String .format("%s: %s", this.getClass().getSimpleName(), snippet); } -} \ No newline at end of file + + @Override + public boolean equals(Object other) { + if (other == null) + return false; + + if (other == this) + return true; + + if (other.getClass() != getClass()) + return false; + + OGCGeometry another = (OGCGeometry)other; + com.esri.core.geometry.Geometry geom1 = getEsriGeometry(); + com.esri.core.geometry.Geometry geom2 = another.getEsriGeometry(); + + if (geom1 == null) { + if (geom2 != null) + return false; + } + else if (!geom1.equals(geom2)) { + return false; + } + + if (esriSR == another.esriSR) { + return true; + } + + if (esriSR != null && another.esriSR != null) { + return esriSR.equals(another.esriSR); + } + + return false; + } + + @Override + public int hashCode() { + int hash = 1; + com.esri.core.geometry.Geometry geom1 = getEsriGeometry(); + if (geom1 != null) + hash = geom1.hashCode(); + + if (esriSR != null) + hash = NumberUtils.hashCombine(hash, esriSR.hashCode()); + + return hash; + } +} diff --git a/src/main/resources/com/esri/core/geometry/new_to_old_wkid.txt b/src/main/resources/com/esri/core/geometry/new_to_old_wkid.txt index 7cb8d997..86477d47 100644 --- a/src/main/resources/com/esri/core/geometry/new_to_old_wkid.txt +++ b/src/main/resources/com/esri/core/geometry/new_to_old_wkid.txt @@ -284,6 +284,7 @@ 3775 102186 3776 102187 3777 102188 +3785 102113 3800 102183 3801 102189 3812 102199 diff --git a/src/main/resources/com/esri/core/geometry/pcs_id_to_tolerance.txt b/src/main/resources/com/esri/core/geometry/pcs_id_to_tolerance.txt index f38609e5..e4386fc5 100644 --- a/src/main/resources/com/esri/core/geometry/pcs_id_to_tolerance.txt +++ b/src/main/resources/com/esri/core/geometry/pcs_id_to_tolerance.txt @@ -3126,6 +3126,7 @@ 102110 3 102111 3 102112 3 +102113 3 102114 3 102115 3 102116 3 diff --git a/src/test/java/com/esri/core/geometry/TestClip.java b/src/test/java/com/esri/core/geometry/TestClip.java index 0149d482..9653e5fa 100644 --- a/src/test/java/com/esri/core/geometry/TestClip.java +++ b/src/test/java/com/esri/core/geometry/TestClip.java @@ -107,7 +107,7 @@ public static void testClipGeometries() { } } - public static Polygon makePolygon() { + static Polygon makePolygon() { Polygon poly = new Polygon(); poly.startPath(0, 0); poly.lineTo(10, 10); @@ -116,7 +116,7 @@ public static Polygon makePolygon() { return poly; } - public static Polyline makePolyline() { + static Polyline makePolyline() { Polyline poly = new Polyline(); poly.startPath(0, 0); poly.lineTo(10, 10); @@ -183,7 +183,7 @@ public static void testArcObjectsFailureCR196492() { // ((MultiPathImpl::SPtr)clippedPolygon._GetImpl()).SaveToTextFileDbg("c:\\temp\\test_ArcObjects_failure_CR196492.txt"); } - public static Polyline makePolylineCR() { + static Polyline makePolylineCR() { Polyline polyline = new Polyline(); polyline.startPath(-200, -90); @@ -197,7 +197,7 @@ public static Polyline makePolylineCR() { return polyline; } - public static MultiPoint makeMultiPoint() { + static MultiPoint makeMultiPoint() { MultiPoint mpoint = new MultiPoint(); Point2D pt1 = new Point2D(); @@ -219,7 +219,7 @@ public static MultiPoint makeMultiPoint() { return mpoint; } - public static Point makePoint() { + static Point makePoint() { Point point = new Point(); Point2D pt = new Point2D(); diff --git a/src/test/java/com/esri/core/geometry/TestConvexHull.java b/src/test/java/com/esri/core/geometry/TestConvexHull.java index 72b6198a..62c59c2f 100644 --- a/src/test/java/com/esri/core/geometry/TestConvexHull.java +++ b/src/test/java/com/esri/core/geometry/TestConvexHull.java @@ -14,12 +14,45 @@ protected void tearDown() throws Exception { super.tearDown(); } + @Test + public static void testFewPoints() { + { + Polygon polygon = new Polygon(); + polygon.addPath((Point2D[]) null, 0, true); + polygon.insertPoint(0, -1, Point2D.construct(5, 5)); + + Point convex_hull = (Point) OperatorConvexHull.local().execute(polygon, null); + assertTrue(convex_hull.getXY().equals(Point2D.construct(5, 5))); + } + + { + Point2D[] pts = new Point2D[3]; + + pts[0] = Point2D.construct(0, 0); + pts[1] = Point2D.construct(0, 0); + pts[2] = Point2D.construct(0, 0); + + int[] out_pts = new int[3]; + int res = ConvexHull.construct(pts, 3, out_pts); + assertTrue(res == 1); + assertTrue(out_pts[0] == 0); + } + + { + Point2D[] pts = new Point2D[1]; + pts[0] = Point2D.construct(0, 0); + + int[] out_pts = new int[1]; + int res = ConvexHull.construct(pts, 1, out_pts); + assertTrue(res == 1); + assertTrue(out_pts[0] == 0); + } + } + @Test public static void testDegenerate() { - OperatorConvexHull bounding = (OperatorConvexHull) OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.ConvexHull); - OperatorDensifyByLength densify = (OperatorDensifyByLength) OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.DensifyByLength); + OperatorConvexHull bounding = (OperatorConvexHull) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.ConvexHull); + OperatorDensifyByLength densify = (OperatorDensifyByLength) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.DensifyByLength); { Polygon polygon = new Polygon(); @@ -38,7 +71,7 @@ public static void testDegenerate() { polygon.lineTo(3, 0); Polygon densified = (Polygon) (densify.execute(polygon, .5, null)); - Polygon convex_hull = (Polygon) (bounding.execute(densified, null)); + Polyline convex_hull = (Polyline) (bounding.execute(densified, null)); assertTrue(bounding.isConvex(convex_hull, null)); assertTrue(convex_hull.calculateArea2D() == 0.0); @@ -232,21 +265,19 @@ public static void testDegenerate() { mpoint.add(4, 4); mpoint.add(4, 4); - Polygon convex_hull = (Polygon) (bounding.execute(mpoint, null)); - assertTrue(convex_hull.getPointCount() == 2); + Point convex_hull = (Point) bounding.execute(mpoint, null); assertTrue(convex_hull.calculateArea2D() == 0.0); assertTrue(bounding.isConvex(convex_hull, null)); + assertTrue(convex_hull.getXY().equals(Point2D.construct(4, 4))); } { MultiPoint mpoint = new MultiPoint(); mpoint.add(4, 4); - MultiPoint convex_hull = (MultiPoint) (bounding.execute(mpoint, - null)); - assertTrue(convex_hull.getPointCount() == 1); + Point convex_hull = (Point) bounding.execute(mpoint, null); assertTrue(bounding.isConvex(convex_hull, null)); - assertTrue(convex_hull == mpoint); + assertTrue(convex_hull.getXY().equals(Point2D.construct(4, 4))); } { @@ -254,7 +285,7 @@ public static void testDegenerate() { mpoint.add(4, 4); mpoint.add(4, 5); - Polyline convex_hull = (Polyline) (bounding.execute(mpoint, null)); + Polyline convex_hull = (Polyline) bounding.execute(mpoint, null); assertTrue(convex_hull.getPointCount() == 2); assertTrue(bounding.isConvex(convex_hull, null)); assertTrue(convex_hull.calculateLength2D() == 1.0); @@ -265,7 +296,7 @@ public static void testDegenerate() { line.setStartXY(0, 0); line.setEndXY(0, 1); - Polyline convex_hull = (Polyline) (bounding.execute(line, null)); + Polyline convex_hull = (Polyline) bounding.execute(line, null); assertTrue(convex_hull.getPointCount() == 2); assertTrue(bounding.isConvex(convex_hull, null)); assertTrue(convex_hull.calculateLength2D() == 1.0); @@ -276,7 +307,7 @@ public static void testDegenerate() { polyline.startPath(0, 0); polyline.lineTo(0, 1); - Polyline convex_hull = (Polyline) (bounding.execute(polyline, null)); + Polyline convex_hull = (Polyline) bounding.execute(polyline, null); assertTrue(convex_hull.getPointCount() == 2); assertTrue(bounding.isConvex(convex_hull, null)); assertTrue(polyline == convex_hull); @@ -285,23 +316,81 @@ public static void testDegenerate() { { Envelope env = new Envelope(0, 0, 10, 10); + assertTrue(OperatorConvexHull.local().isConvex(env, null)); + + Polygon convex_hull = (Polygon) bounding.execute(env, null); + assertTrue(bounding.isConvex(convex_hull, null)); + assertTrue(convex_hull.getPointCount() == 4); + assertTrue(convex_hull.getXY(0).equals(Point2D.construct(0, 0))); + assertTrue(convex_hull.getXY(1).equals(Point2D.construct(0, 10))); + assertTrue(convex_hull.getXY(2).equals(Point2D.construct(10, 10))); + assertTrue(convex_hull.getXY(3).equals(Point2D.construct(10, 0))); + } - Envelope convex_hull = (Envelope) (bounding.execute(env, null)); + { + Envelope env = new Envelope(0, 0, 0, 10); + assertTrue(!OperatorConvexHull.local().isConvex(env, null)); + + Polyline convex_hull = (Polyline) bounding.execute(env, null); assertTrue(bounding.isConvex(convex_hull, null)); - assertTrue(env == convex_hull); + assertTrue(convex_hull.getPointCount() == 2); + assertTrue(convex_hull.getXY(0).equals(Point2D.construct(0, 0))); + assertTrue(convex_hull.getXY(1).equals(Point2D.construct(0, 10))); + } + + { + Envelope env = new Envelope(0, 0, 0, 10); + assertTrue(!OperatorConvexHull.local().isConvex(env, null)); + + Polyline convex_hull = (Polyline) bounding.execute(env, null); + assertTrue(bounding.isConvex(convex_hull, null)); + assertTrue(convex_hull.getPointCount() == 2); + assertTrue(convex_hull.getXY(0).equals(Point2D.construct(0, 0))); + assertTrue(convex_hull.getXY(1).equals(Point2D.construct(0, 10))); + } + + { + Envelope env = new Envelope(5, 5, 5, 5); + assertTrue(!OperatorConvexHull.local().isConvex(env, null)); + + Point convex_hull = (Point) bounding.execute(env, null); + assertTrue(bounding.isConvex(convex_hull, null)); + assertTrue(convex_hull.getXY().equals(Point2D.construct(5, 5))); } } + @Test + public static void testSegment() { + { + Line line = new Line(); + line.setStartXY(5, 5); + line.setEndXY(5, 5); + + assertTrue(!OperatorConvexHull.local().isConvex(line, null)); + Point point = (Point) OperatorConvexHull.local().execute(line, null); + assertTrue(point.getXY().equals(Point2D.construct(5, 5))); + } + + { + Line line = new Line(); + line.setStartXY(5, 5); + line.setEndXY(5, 6); + + assertTrue(OperatorConvexHull.local().isConvex(line, null)); + Polyline polyline = (Polyline) OperatorConvexHull.local().execute(line, null); + assertTrue(polyline.getPointCount() == 2); + assertTrue(polyline.getXY(0).equals(Point2D.construct(5, 5))); + assertTrue(polyline.getXY(1).equals(Point2D.construct(5, 6))); + } + } + + @Test public static void testSquare() { - OperatorConvexHull bounding = (OperatorConvexHull) OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.ConvexHull); - OperatorDensifyByLength densify = (OperatorDensifyByLength) OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.DensifyByLength); - OperatorDifference difference = (OperatorDifference) OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.Difference); - OperatorContains contains = (OperatorContains) OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.Contains); + OperatorConvexHull bounding = (OperatorConvexHull) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.ConvexHull); + OperatorDensifyByLength densify = (OperatorDensifyByLength) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.DensifyByLength); + OperatorDifference difference = (OperatorDifference) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.Difference); + OperatorContains contains = (OperatorContains) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.Contains); Polygon square = new Polygon(); square.startPath(0, 0); @@ -330,9 +419,13 @@ public static void testSquare() { square.lineTo(2, 1); Polygon densified = (Polygon) (densify.execute(square, 1.0, null)); + + densified.addAttribute(VertexDescription.Semantics.ID); + for (int i = 0; i < densified.getPointCount(); i++) + densified.setAttribute(VertexDescription.Semantics.ID, i, 0, i); + Polygon convex_hull = (Polygon) (bounding.execute(densified, null)); - Polygon differenced = (Polygon) (difference.execute(densified, - convex_hull, SpatialReference.create(4326), null)); + Polygon differenced = (Polygon) (difference.execute(densified, convex_hull, SpatialReference.create(4326), null)); assertTrue(differenced.isEmpty()); assertTrue(bounding.isConvex(convex_hull, null)); @@ -340,32 +433,23 @@ public static void testSquare() { @Test public static void testPolygons() { - OperatorConvexHull bounding = (OperatorConvexHull) OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.ConvexHull); - OperatorDensifyByLength densify = (OperatorDensifyByLength) OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.DensifyByLength); - OperatorDifference difference = (OperatorDifference) OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.Difference); + OperatorConvexHull bounding = (OperatorConvexHull) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.ConvexHull); + OperatorDensifyByLength densify = (OperatorDensifyByLength) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.DensifyByLength); + OperatorDifference difference = (OperatorDifference) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.Difference); { - Polygon shape = (Polygon) (TestCommonMethods - .fromJson("{\"rings\":[[[1.3734426370715553,-90],[-24.377532184629967,-63.428606327053856],[-25.684686546621553,90],[-24.260574484321914,80.526315789473699],[-25.414389575040037,90],[-23.851448513708718,90],[-23.100135788742072,87.435887853000679],[5.6085096351011448,-48.713222410606306],[1.3734426370715553,-90]]]}") - .getGeometry()); + Polygon shape = (Polygon) (TestCommonMethods.fromJson("{\"rings\":[[[1.3734426370715553,-90],[-24.377532184629967,-63.428606327053856],[-25.684686546621553,90],[-24.260574484321914,80.526315789473699],[-25.414389575040037,90],[-23.851448513708718,90],[-23.100135788742072,87.435887853000679],[5.6085096351011448,-48.713222410606306],[1.3734426370715553,-90]]]}").getGeometry()); Polygon convex_hull = (Polygon) (bounding.execute(shape, null)); - Polygon differenced = (Polygon) (difference.execute(shape, - convex_hull, SpatialReference.create(4326), null)); + Polygon differenced = (Polygon) (difference.execute(shape, convex_hull, SpatialReference.create(4326), null)); assertTrue(differenced.isEmpty()); assertTrue(bounding.isConvex(convex_hull, null)); } { - Polygon polygon = (Polygon) (TestCommonMethods - .fromJson("{\"rings\":[[[-179.64843749999693,-43.3535476539993],[-179.99999999999696,-43.430006211999284],[-179.99999999999696,39.329644416999436],[-179.64843749999693,38.98862638799943],[-89.99999999999838,29.008084980999506],[-112.8515624999981,-16.20113770599962],[-115.66406249999804,-18.882554574999688],[-124.80468749999788,-23.7925511709996],[-138.86718749999767,-30.6635901109995],[-157.49999999999736,-38.468358112999354],[-162.42187499999724,-39.56498442199932],[-179.64843749999693,-43.3535476539993]],[[179.99999999999696,-43.430006211999284],[179.64843749999693,-43.50646476999926],[162.0703124999973,-42.36267115399919],[160.3124999999973,-42.24790485699929],[143.78906249999756,-41.1680427339993],[138.16406249999767,-39.64744846799925],[98.43749999999845,-28.523889212999524],[78.39843749999878,-5.1644422999998705],[75.9374999999988,19.738611663999766],[88.2421874999986,33.51651305599954],[108.63281249999815,44.160795160999356],[138.16406249999767,51.02062617799914],[140.9765624999976,51.68129673399923],[160.3124999999973,52.8064856429991],[162.0703124999973,52.908902047999206],[163.12499999999727,52.97036560499911],[165.93749999999716,52.97036560499911],[179.99999999999696,39.329644416999436],[179.99999999999696,-43.430006211999284]]]}") - .getGeometry()); + Polygon polygon = (Polygon) (TestCommonMethods.fromJson("{\"rings\":[[[-179.64843749999693,-43.3535476539993],[-179.99999999999696,-43.430006211999284],[-179.99999999999696,39.329644416999436],[-179.64843749999693,38.98862638799943],[-89.99999999999838,29.008084980999506],[-112.8515624999981,-16.20113770599962],[-115.66406249999804,-18.882554574999688],[-124.80468749999788,-23.7925511709996],[-138.86718749999767,-30.6635901109995],[-157.49999999999736,-38.468358112999354],[-162.42187499999724,-39.56498442199932],[-179.64843749999693,-43.3535476539993]],[[179.99999999999696,-43.430006211999284],[179.64843749999693,-43.50646476999926],[162.0703124999973,-42.36267115399919],[160.3124999999973,-42.24790485699929],[143.78906249999756,-41.1680427339993],[138.16406249999767,-39.64744846799925],[98.43749999999845,-28.523889212999524],[78.39843749999878,-5.1644422999998705],[75.9374999999988,19.738611663999766],[88.2421874999986,33.51651305599954],[108.63281249999815,44.160795160999356],[138.16406249999767,51.02062617799914],[140.9765624999976,51.68129673399923],[160.3124999999973,52.8064856429991],[162.0703124999973,52.908902047999206],[163.12499999999727,52.97036560499911],[165.93749999999716,52.97036560499911],[179.99999999999696,39.329644416999436],[179.99999999999696,-43.430006211999284]]]}").getGeometry()); Polygon densified = (Polygon) (densify.execute(polygon, 10.0, null)); Polygon convex_hull = (Polygon) (bounding.execute(densified, null)); - Polygon differenced = (Polygon) (difference.execute(densified, - convex_hull, SpatialReference.create(4326), null)); + Polygon differenced = (Polygon) (difference.execute(densified, convex_hull, SpatialReference.create(4326), null)); assertTrue(differenced.isEmpty()); assertTrue(bounding.isConvex(convex_hull, null)); } @@ -376,196 +460,144 @@ public static void testPolygons() { polygon.lineTo(-1, 0); polygon.lineTo(0, -1); Polygon convex_hull = (Polygon) (bounding.execute(polygon, null)); - Polygon differenced = (Polygon) (difference.execute(polygon, - convex_hull, SpatialReference.create(4326), null)); + Polygon differenced = (Polygon) (difference.execute(polygon, convex_hull, SpatialReference.create(4326), null)); assertTrue(differenced.isEmpty()); assertTrue(bounding.isConvex(convex_hull, null)); } { - Polygon polygon = (Polygon) (TestCommonMethods - .fromJson("{\"rings\":[[[-38.554566833914528,21.902000764339238],[-30.516168471666138,90],[-38.554566833914528,21.902000764339238]],[[-43.013227444613932,28.423410187206883],[-43.632473335895916,90],[-42.342268693420237,62.208637129146894],[-37.218731802058755,63.685357222187029],[-32.522681335230686,47.080307818055296],[-40.537308829621097,-21.881392019745604],[-47.59510451722663,18.812521648505964],[-53.25344489340366,30.362745244224911],[-46.629060462410138,90],[-50.069277433245119,18.254168921734287],[-42.171214434397982,72.623347387008081],[-43.000844452530551,90],[-46.162281544954659,90],[-39.462049205071331,90],[-47.434856316742902,38.662565208814371],[-52.13115779642537,-19.952586632199857],[-56.025328966335081,90],[-60.056846215416158,-44.023645282268355],[-60.12338894192289,50.374596189881942],[-35.787508034048379,-7.8839007676038513],[-60.880218074135605,-46.447995750907815],[-67.782542852117956,-85.106300958016107],[-65.053131764313761,-0.96651520578494665],[-72.375821140304154,90],[-78.561502106749245,90],[-83.809168672565946,33.234498214085811],[-60.880218054506344,-46.447995733653201],[-75.637095425108981,59.886574792622838],[-71.364085965028096,31.976373491332097],[-67.89968380886117,90],[-67.544349171474749,8.8435794458927504],[-70.780047377934707,80.683454463576624],[-64.996733940204948,34.349882797035313],[-56.631753638680905,39.815838152456926],[-60.392350183516896,52.75446132093407],[-58.51633728692137,90],[-64.646972065627097,41.444197803942579],[-73.355591244695518,-0.15370205145035776],[-43.013227444613932,28.423410187206883]],[[-69.646471076946,-85.716191379686904],[-62.854465128320491,-45.739046580967972],[-71.377481570643141,-90],[-66.613495837251435,-90],[-66.9765142407159,-90],[-66.870099169607329,-90],[-67.23180828626819,-61.248439074609649],[-58.889775875438851,-90],[-53.391995883729322,-69.476385967096491],[-69.646471076946,-85.716191379686904]]]}") - .getGeometry()); + Polygon polygon = (Polygon) (TestCommonMethods.fromJson("{\"rings\":[[[-38.554566833914528,21.902000764339238],[-30.516168471666138,90],[-38.554566833914528,21.902000764339238]],[[-43.013227444613932,28.423410187206883],[-43.632473335895916,90],[-42.342268693420237,62.208637129146894],[-37.218731802058755,63.685357222187029],[-32.522681335230686,47.080307818055296],[-40.537308829621097,-21.881392019745604],[-47.59510451722663,18.812521648505964],[-53.25344489340366,30.362745244224911],[-46.629060462410138,90],[-50.069277433245119,18.254168921734287],[-42.171214434397982,72.623347387008081],[-43.000844452530551,90],[-46.162281544954659,90],[-39.462049205071331,90],[-47.434856316742902,38.662565208814371],[-52.13115779642537,-19.952586632199857],[-56.025328966335081,90],[-60.056846215416158,-44.023645282268355],[-60.12338894192289,50.374596189881942],[-35.787508034048379,-7.8839007676038513],[-60.880218074135605,-46.447995750907815],[-67.782542852117956,-85.106300958016107],[-65.053131764313761,-0.96651520578494665],[-72.375821140304154,90],[-78.561502106749245,90],[-83.809168672565946,33.234498214085811],[-60.880218054506344,-46.447995733653201],[-75.637095425108981,59.886574792622838],[-71.364085965028096,31.976373491332097],[-67.89968380886117,90],[-67.544349171474749,8.8435794458927504],[-70.780047377934707,80.683454463576624],[-64.996733940204948,34.349882797035313],[-56.631753638680905,39.815838152456926],[-60.392350183516896,52.75446132093407],[-58.51633728692137,90],[-64.646972065627097,41.444197803942579],[-73.355591244695518,-0.15370205145035776],[-43.013227444613932,28.423410187206883]],[[-69.646471076946,-85.716191379686904],[-62.854465128320491,-45.739046580967972],[-71.377481570643141,-90],[-66.613495837251435,-90],[-66.9765142407159,-90],[-66.870099169607329,-90],[-67.23180828626819,-61.248439074609649],[-58.889775875438851,-90],[-53.391995883729322,-69.476385967096491],[-69.646471076946,-85.716191379686904]]]}").getGeometry()); Polygon densified = (Polygon) (densify.execute(polygon, 10.0, null)); Polygon convex_hull = (Polygon) (bounding.execute(densified, null)); - Polygon differenced = (Polygon) (difference.execute(densified, - convex_hull, SpatialReference.create(4326), null)); + Polygon differenced = (Polygon) (difference.execute(densified, convex_hull, SpatialReference.create(4326), null)); assertTrue(differenced.isEmpty()); // assertTrue(bounding.isConvex(*convex_hull, null)); } { - Polygon polygon = (Polygon) (TestCommonMethods - .fromJson("{\"rings\":[[[-38.554566833914528,21.902000764339238],[-30.516168471666138,90],[-38.554566833914528,21.902000764339238]],[[-43.013227444613932,28.423410187206883],[-43.632473335895916,90],[-42.342268693420237,62.208637129146894],[-37.218731802058755,63.685357222187029],[-32.522681335230686,47.080307818055296],[-40.537308829621097,-21.881392019745604],[-47.59510451722663,18.812521648505964],[-53.25344489340366,30.362745244224911],[-46.629060462410138,90],[-50.069277433245119,18.254168921734287],[-42.171214434397982,72.623347387008081],[-43.000844452530551,90],[-46.162281544954659,90],[-39.462049205071331,90],[-47.434856316742902,38.662565208814371],[-52.13115779642537,-19.952586632199857],[-56.025328966335081,90],[-60.056846215416158,-44.023645282268355],[-60.12338894192289,50.374596189881942],[-35.787508034048379,-7.8839007676038513],[-60.880218074135605,-46.447995750907815],[-67.782542852117956,-85.106300958016107],[-65.053131764313761,-0.96651520578494665],[-72.375821140304154,90],[-78.561502106749245,90],[-83.809168672565946,33.234498214085811],[-60.880218054506344,-46.447995733653201],[-75.637095425108981,59.886574792622838],[-71.364085965028096,31.976373491332097],[-67.89968380886117,90],[-67.544349171474749,8.8435794458927504],[-70.780047377934707,80.683454463576624],[-64.996733940204948,34.349882797035313],[-56.631753638680905,39.815838152456926],[-60.392350183516896,52.75446132093407],[-58.51633728692137,90],[-64.646972065627097,41.444197803942579],[-73.355591244695518,-0.15370205145035776],[-43.013227444613932,28.423410187206883]],[[-69.646471076946,-85.716191379686904],[-62.854465128320491,-45.739046580967972],[-71.377481570643141,-90],[-66.613495837251435,-90],[-66.9765142407159,-90],[-66.870099169607329,-90],[-67.23180828626819,-61.248439074609649],[-58.889775875438851,-90],[-53.391995883729322,-69.476385967096491],[-69.646471076946,-85.716191379686904]]]}") - .getGeometry()); + Polygon polygon = (Polygon) (TestCommonMethods.fromJson("{\"rings\":[[[-38.554566833914528,21.902000764339238],[-30.516168471666138,90],[-38.554566833914528,21.902000764339238]],[[-43.013227444613932,28.423410187206883],[-43.632473335895916,90],[-42.342268693420237,62.208637129146894],[-37.218731802058755,63.685357222187029],[-32.522681335230686,47.080307818055296],[-40.537308829621097,-21.881392019745604],[-47.59510451722663,18.812521648505964],[-53.25344489340366,30.362745244224911],[-46.629060462410138,90],[-50.069277433245119,18.254168921734287],[-42.171214434397982,72.623347387008081],[-43.000844452530551,90],[-46.162281544954659,90],[-39.462049205071331,90],[-47.434856316742902,38.662565208814371],[-52.13115779642537,-19.952586632199857],[-56.025328966335081,90],[-60.056846215416158,-44.023645282268355],[-60.12338894192289,50.374596189881942],[-35.787508034048379,-7.8839007676038513],[-60.880218074135605,-46.447995750907815],[-67.782542852117956,-85.106300958016107],[-65.053131764313761,-0.96651520578494665],[-72.375821140304154,90],[-78.561502106749245,90],[-83.809168672565946,33.234498214085811],[-60.880218054506344,-46.447995733653201],[-75.637095425108981,59.886574792622838],[-71.364085965028096,31.976373491332097],[-67.89968380886117,90],[-67.544349171474749,8.8435794458927504],[-70.780047377934707,80.683454463576624],[-64.996733940204948,34.349882797035313],[-56.631753638680905,39.815838152456926],[-60.392350183516896,52.75446132093407],[-58.51633728692137,90],[-64.646972065627097,41.444197803942579],[-73.355591244695518,-0.15370205145035776],[-43.013227444613932,28.423410187206883]],[[-69.646471076946,-85.716191379686904],[-62.854465128320491,-45.739046580967972],[-71.377481570643141,-90],[-66.613495837251435,-90],[-66.9765142407159,-90],[-66.870099169607329,-90],[-67.23180828626819,-61.248439074609649],[-58.889775875438851,-90],[-53.391995883729322,-69.476385967096491],[-69.646471076946,-85.716191379686904]]]}").getGeometry()); Polygon convex_hull = (Polygon) (bounding.execute(polygon, null)); - Polygon differenced = (Polygon) (difference.execute(polygon, - convex_hull, SpatialReference.create(4326), null)); + Polygon differenced = (Polygon) (difference.execute(polygon, convex_hull, SpatialReference.create(4326), null)); assertTrue(differenced.isEmpty()); assertTrue(bounding.isConvex(convex_hull, null)); } { - Polygon polygon = (Polygon) (TestCommonMethods - .fromJson("{\"rings\":[[[-36.269498017702901,-26.37490682626369],[-49.146436641060951,-49.881862499696126],[-37.560006446488146,-45.592052597656789],[-39.13770692863632,-69.085816352131204],[-65.415587331361877,-90],[-51.462290812033373,-16.760787566546721],[-28.454456182408332,90],[-36.269498017702901,-26.37490682626369]],[[-40.542178258552283,-90],[-39.13770692863632,-69.085816352131204],[-16.295804332590937,-50.906277575066262],[-40.542178258552283,-90]],[[-16.295804332590937,-50.906277575066262],[-5.6790432913971927,-33.788307256548933],[14.686101893282586,-26.248228042967728],[-16.295804332590937,-50.906277575066262]],[[-37.560006446488146,-45.592052597656789],[-36.269498017702901,-26.37490682626369],[27.479825940672225,90],[71.095881152477034,90],[-5.6790432913971927,-33.788307256548933],[-37.560006446488146,-45.592052597656789]]]}") - .getGeometry()); + Polygon polygon = (Polygon) (TestCommonMethods.fromJson("{\"rings\":[[[-36.269498017702901,-26.37490682626369],[-49.146436641060951,-49.881862499696126],[-37.560006446488146,-45.592052597656789],[-39.13770692863632,-69.085816352131204],[-65.415587331361877,-90],[-51.462290812033373,-16.760787566546721],[-28.454456182408332,90],[-36.269498017702901,-26.37490682626369]],[[-40.542178258552283,-90],[-39.13770692863632,-69.085816352131204],[-16.295804332590937,-50.906277575066262],[-40.542178258552283,-90]],[[-16.295804332590937,-50.906277575066262],[-5.6790432913971927,-33.788307256548933],[14.686101893282586,-26.248228042967728],[-16.295804332590937,-50.906277575066262]],[[-37.560006446488146,-45.592052597656789],[-36.269498017702901,-26.37490682626369],[27.479825940672225,90],[71.095881152477034,90],[-5.6790432913971927,-33.788307256548933],[-37.560006446488146,-45.592052597656789]]]}").getGeometry()); Polygon convex_hull = (Polygon) (bounding.execute(polygon, null)); - Polygon differenced = (Polygon) (difference.execute(polygon, - convex_hull, SpatialReference.create(4326), null)); + Polygon differenced = (Polygon) (difference.execute(polygon, convex_hull, SpatialReference.create(4326), null)); assertTrue(differenced.isEmpty()); assertTrue(bounding.isConvex(convex_hull, null)); } { - Polygon polygon = (Polygon) (TestCommonMethods - .fromJson("{\"rings\":[[[-77.020281185856106,-80.085419699581706],[-77.328568930885723,-83.404479889897416],[-80.495259564600545,-90],[-77.020281185856106,-80.085419699581706]],[[-77.941187535385211,-90],[-77.328568930885723,-83.404479889897416],[-39.252034383972621,-4.0994329574862469],[-39.29471328421063,-6.5269494453154593],[-77.941187535385211,-90]],[[-77.020281185856106,-80.085419699581706],[-62.688864277996522,74.208210509833052],[-38.108861278327581,80.371071656873013],[-37.597643844595929,90],[-38.663943358642484,29.350366647752089],[-77.020281185856106,-80.085419699581706]],[[-40.265125886194951,-61.722668598742551],[-39.29471328421063,-6.5269494453154593],[-15.554402498931253,44.750073899273843],[-8.4447006412989474,13.127318978368956],[-5.310206313296316,-4.5170390491918795],[-40.265125886194951,-61.722668598742551]],[[-39.252034383972621,-4.0994329574862469],[-38.663943358642484,29.350366647752089],[-22.476078360563164,75.536520897660651],[-15.632105532320049,45.095683888365997],[-39.252034383972621,-4.0994329574862469]],[[-15.554402498931253,44.750073899273843],[-15.632105532320049,45.095683888365997],[-8.9755856576261941,58.959750756602595],[-15.554402498931253,44.750073899273843]]]}") - .getGeometry()); + Polygon polygon = (Polygon) (TestCommonMethods.fromJson("{\"rings\":[[[-77.020281185856106,-80.085419699581706],[-77.328568930885723,-83.404479889897416],[-80.495259564600545,-90],[-77.020281185856106,-80.085419699581706]],[[-77.941187535385211,-90],[-77.328568930885723,-83.404479889897416],[-39.252034383972621,-4.0994329574862469],[-39.29471328421063,-6.5269494453154593],[-77.941187535385211,-90]],[[-77.020281185856106,-80.085419699581706],[-62.688864277996522,74.208210509833052],[-38.108861278327581,80.371071656873013],[-37.597643844595929,90],[-38.663943358642484,29.350366647752089],[-77.020281185856106,-80.085419699581706]],[[-40.265125886194951,-61.722668598742551],[-39.29471328421063,-6.5269494453154593],[-15.554402498931253,44.750073899273843],[-8.4447006412989474,13.127318978368956],[-5.310206313296316,-4.5170390491918795],[-40.265125886194951,-61.722668598742551]],[[-39.252034383972621,-4.0994329574862469],[-38.663943358642484,29.350366647752089],[-22.476078360563164,75.536520897660651],[-15.632105532320049,45.095683888365997],[-39.252034383972621,-4.0994329574862469]],[[-15.554402498931253,44.750073899273843],[-15.632105532320049,45.095683888365997],[-8.9755856576261941,58.959750756602595],[-15.554402498931253,44.750073899273843]]]}").getGeometry()); Polygon convex_hull = (Polygon) (bounding.execute(polygon, null)); - Polygon differenced = (Polygon) (difference.execute(polygon, - convex_hull, SpatialReference.create(4326), null)); + Polygon differenced = (Polygon) (difference.execute(polygon, convex_hull, SpatialReference.create(4326), null)); assertTrue(differenced.isEmpty()); assertTrue(bounding.isConvex(convex_hull, null)); } { - Polygon polygon = (Polygon) (TestCommonMethods - .fromJson("{\"rings\":[[[-68.840007952128175,37.060080998089632],[-68.145986924561413,31.114096694815196],[-69.187773850176768,30.693518246958952],[-68.840007952128175,37.060080998089632]],[[-75.780513355389928,-90],[-69.21567111077384,30.182802098042274],[-50.875629803516389,37.146119571446704],[-75.780513355389928,-90]],[[4.2911006174797457,-1.144569312564311],[-66.484019915251849,80.191238371060038],[-65.948228008382316,90],[4.2911006174797457,-1.144569312564311]],[[-90,22.291441435181515],[-69.187773850176768,30.693518246958952],[-69.21567111077384,30.182802098042274],[-90,22.291441435181515]],[[-68.840007952128175,37.060080998089632],[-75.019206401201359,90],[-66.484019915251849,80.191238371060038],[-68.840007952128175,37.060080998089632]]]}") - .getGeometry()); + Polygon polygon = (Polygon) (TestCommonMethods.fromJson("{\"rings\":[[[-68.840007952128175,37.060080998089632],[-68.145986924561413,31.114096694815196],[-69.187773850176768,30.693518246958952],[-68.840007952128175,37.060080998089632]],[[-75.780513355389928,-90],[-69.21567111077384,30.182802098042274],[-50.875629803516389,37.146119571446704],[-75.780513355389928,-90]],[[4.2911006174797457,-1.144569312564311],[-66.484019915251849,80.191238371060038],[-65.948228008382316,90],[4.2911006174797457,-1.144569312564311]],[[-90,22.291441435181515],[-69.187773850176768,30.693518246958952],[-69.21567111077384,30.182802098042274],[-90,22.291441435181515]],[[-68.840007952128175,37.060080998089632],[-75.019206401201359,90],[-66.484019915251849,80.191238371060038],[-68.840007952128175,37.060080998089632]]]}").getGeometry()); Polygon convex_hull = (Polygon) (bounding.execute(polygon, null)); - Polygon differenced = (Polygon) (difference.execute(polygon, - convex_hull, SpatialReference.create(4326), null)); + Polygon differenced = (Polygon) (difference.execute(polygon, convex_hull, SpatialReference.create(4326), null)); assertTrue(differenced.isEmpty()); assertTrue(bounding.isConvex(convex_hull, null)); } { - Polygon polygon = (Polygon) (TestCommonMethods - .fromJson("{\"rings\":[[[27.570109889215438,22.850616190228489],[75.703105729477357,-90],[2.1548000876241362,-15.817792950796967],[27.570109889215438,22.850616190228489]],[[-0.069915984436478951,-90],[-46.602410662754053,-89.999999998014729],[-14.977190481820156,-41.883452819243004],[-0.069915984436478951,-90]],[[-14.977190481820156,-41.883452819243004],[-34.509989609682322,21.163004866431177],[2.1548000876241362,-15.817792950796967],[-14.977190481820156,-41.883452819243004]]]}") - .getGeometry()); + Polygon polygon = (Polygon) (TestCommonMethods.fromJson("{\"rings\":[[[27.570109889215438,22.850616190228489],[75.703105729477357,-90],[2.1548000876241362,-15.817792950796967],[27.570109889215438,22.850616190228489]],[[-0.069915984436478951,-90],[-46.602410662754053,-89.999999998014729],[-14.977190481820156,-41.883452819243004],[-0.069915984436478951,-90]],[[-14.977190481820156,-41.883452819243004],[-34.509989609682322,21.163004866431177],[2.1548000876241362,-15.817792950796967],[-14.977190481820156,-41.883452819243004]]]}").getGeometry()); Polygon convex_hull = (Polygon) (bounding.execute(polygon, null)); - Polygon differenced = (Polygon) (difference.execute(polygon, - convex_hull, SpatialReference.create(4326), null)); + Polygon differenced = (Polygon) (difference.execute(polygon, convex_hull, SpatialReference.create(4326), null)); assertTrue(differenced.isEmpty()); assertTrue(bounding.isConvex(convex_hull, null)); } { - Polygon polygon = (Polygon) (TestCommonMethods - .fromJson("{\"rings\":[[[28.865673900286581,33.379551302126075],[39.505669183485338,-34.957993133630559],[7.152466542048213,-90],[28.865673900286581,33.379551302126075]],[[-64.597291313620858,2.4515644574812248],[20.050002923927103,90],[24.375150856531356,62.220853377417541],[-64.597291313620858,2.4515644574812248]],[[28.865673900286581,33.379551302126075],[24.375150856531356,62.220853377417541],[35.223952527956932,69.508785974507163],[28.865673900286581,33.379551302126075]]]}") - .getGeometry()); + Polygon polygon = (Polygon) (TestCommonMethods.fromJson("{\"rings\":[[[28.865673900286581,33.379551302126075],[39.505669183485338,-34.957993133630559],[7.152466542048213,-90],[28.865673900286581,33.379551302126075]],[[-64.597291313620858,2.4515644574812248],[20.050002923927103,90],[24.375150856531356,62.220853377417541],[-64.597291313620858,2.4515644574812248]],[[28.865673900286581,33.379551302126075],[24.375150856531356,62.220853377417541],[35.223952527956932,69.508785974507163],[28.865673900286581,33.379551302126075]]]}").getGeometry()); Polygon convex_hull = (Polygon) (bounding.execute(polygon, null)); - Polygon differenced = (Polygon) (difference.execute(polygon, - convex_hull, SpatialReference.create(4326), null)); + Polygon differenced = (Polygon) (difference.execute(polygon, convex_hull, SpatialReference.create(4326), null)); assertTrue(differenced.isEmpty()); assertTrue(bounding.isConvex(convex_hull, null)); } { - Polygon polygon = (Polygon) (TestCommonMethods - .fromJson("{\"rings\":[[[-66.582505384413167,-51.305212522944061],[-60.169897093348865,-90],[-90,-90],[-66.582505384413167,-51.305212522944061]],[[20.858462934004656,-90],[-35.056287147954386,0.78833269359179781],[18.933251883215579,90],[20.858462934004656,-90]],[[-66.582505384413167,-51.305212522944061],[-90,90],[-35.056287147954386,0.78833269359179781],[-66.582505384413167,-51.305212522944061]]]}") - .getGeometry()); + Polygon polygon = (Polygon) (TestCommonMethods.fromJson("{\"rings\":[[[-66.582505384413167,-51.305212522944061],[-60.169897093348865,-90],[-90,-90],[-66.582505384413167,-51.305212522944061]],[[20.858462934004656,-90],[-35.056287147954386,0.78833269359179781],[18.933251883215579,90],[20.858462934004656,-90]],[[-66.582505384413167,-51.305212522944061],[-90,90],[-35.056287147954386,0.78833269359179781],[-66.582505384413167,-51.305212522944061]]]}").getGeometry()); Polygon convex_hull = (Polygon) (bounding.execute(polygon, null)); - Polygon differenced = (Polygon) (difference.execute(polygon, - convex_hull, SpatialReference.create(4326), null)); + Polygon differenced = (Polygon) (difference.execute(polygon, convex_hull, SpatialReference.create(4326), null)); assertTrue(differenced.isEmpty()); assertTrue(bounding.isConvex(convex_hull, null)); } { - Polygon polygon = (Polygon) (TestCommonMethods - .fromJson("{\"rings\":[[[36.692916710279974,-90],[-31.182443792600079,6.434474852744998],[-90,90],[52.245260790065387,57.329280208760991],[36.692916710279974,-90]]]}") - .getGeometry()); + Polygon polygon = (Polygon) (TestCommonMethods.fromJson("{\"rings\":[[[36.692916710279974,-90],[-31.182443792600079,6.434474852744998],[-90,90],[52.245260790065387,57.329280208760991],[36.692916710279974,-90]]]}").getGeometry()); Polygon convex_hull = (Polygon) (bounding.execute(polygon, null)); - Polygon differenced = (Polygon) (difference.execute(polygon, - convex_hull, SpatialReference.create(4326), null)); + Polygon differenced = (Polygon) (difference.execute(polygon, convex_hull, SpatialReference.create(4326), null)); assertTrue(differenced.isEmpty()); assertTrue(bounding.isConvex(convex_hull, null)); } { - Polygon polygon = (Polygon) (TestCommonMethods - .fromJson("{\"rings\":[[[-17.959089916602533,-4.3577640799248218],[-29.181784251472308,-90],[-65.493717350127127,15.053615507086979],[-17.959089916602533,-4.3577640799248218]],[[-21.884657435973146,-34.517617672142393],[-17.94005076020704,-4.3655389655558539],[9.3768748358343359,-15.520758655380195],[-21.884657435973146,-34.517617672142393]],[[-17.94005076020704,-4.3655389655558539],[-17.959089916602533,-4.3577640799248218],[-5.8963967801936494,87.694641571893939],[-17.94005076020704,-4.3655389655558539]]]}") - .getGeometry()); + Polygon polygon = (Polygon) (TestCommonMethods.fromJson("{\"rings\":[[[-17.959089916602533,-4.3577640799248218],[-29.181784251472308,-90],[-65.493717350127127,15.053615507086979],[-17.959089916602533,-4.3577640799248218]],[[-21.884657435973146,-34.517617672142393],[-17.94005076020704,-4.3655389655558539],[9.3768748358343359,-15.520758655380195],[-21.884657435973146,-34.517617672142393]],[[-17.94005076020704,-4.3655389655558539],[-17.959089916602533,-4.3577640799248218],[-5.8963967801936494,87.694641571893939],[-17.94005076020704,-4.3655389655558539]]]}").getGeometry()); Polygon convex_hull = (Polygon) (bounding.execute(polygon, null)); - Polygon differenced = (Polygon) (difference.execute(polygon, - convex_hull, SpatialReference.create(4326), null)); + Polygon differenced = (Polygon) (difference.execute(polygon, convex_hull, SpatialReference.create(4326), null)); assertTrue(differenced.isEmpty()); assertTrue(bounding.isConvex(convex_hull, null)); } { - Polygon polygon = (Polygon) (TestCommonMethods - .fromJson("{\"rings\":[[[17.198360589495572,-77.168667157542373],[-24.835678541343281,-83.717338556412017],[-30.259244993378722,61.914816728303791],[17.198360589495572,-77.168667157542373]],[[-8.3544985146710644,-90],[17.979891823366039,-79.459092168186686],[21.576625471325329,-90],[-8.3544985146710644,-90]],[[17.979891823366039,-79.459092168186686],[17.198360589495572,-77.168667157542373],[27.846596597209441,-75.509730732825361],[17.979891823366039,-79.459092168186686]]]}") - .getGeometry()); + Polygon polygon = (Polygon) (TestCommonMethods.fromJson("{\"rings\":[[[17.198360589495572,-77.168667157542373],[-24.835678541343281,-83.717338556412017],[-30.259244993378722,61.914816728303791],[17.198360589495572,-77.168667157542373]],[[-8.3544985146710644,-90],[17.979891823366039,-79.459092168186686],[21.576625471325329,-90],[-8.3544985146710644,-90]],[[17.979891823366039,-79.459092168186686],[17.198360589495572,-77.168667157542373],[27.846596597209441,-75.509730732825361],[17.979891823366039,-79.459092168186686]]]}").getGeometry()); Polygon convex_hull = (Polygon) (bounding.execute(polygon, null)); - Polygon differenced = (Polygon) (difference.execute(polygon, - convex_hull, SpatialReference.create(4326), null)); + Polygon differenced = (Polygon) (difference.execute(polygon, convex_hull, SpatialReference.create(4326), null)); assertTrue(differenced.isEmpty()); assertTrue(bounding.isConvex(convex_hull, null)); } { - Polygon polygon = (Polygon) (TestCommonMethods - .fromJson("{\"rings\":[[[-1.2588613620456419,13.607321860624268],[61.845346679259052,48.415944386573557],[15.226225965240992,-5.3702891526017318],[0.92681706095183469,1.6819284384951441],[3.8469417404317623,-14.250715301799051],[7.2615297628459139,-14.559458820527061],[4.4896578086498238,-17.757471781424698],[14.589138845678622,-72.861774161244625],[-10.508572009494033,-35.06149380752737],[-58.12642296329372,-90],[-15.260062192400673,90],[-1.2588613620456419,13.607321860624268]],[[0.92681706095183469,1.6819284384951441],[-1.2588613620456419,13.607321860624268],[-11.641308877525201,7.8803076458946304],[0.92681706095183469,1.6819284384951441]],[[-10.508572009494033,-35.06149380752737],[4.4896578086498238,-17.757471781424698],[3.8469417404317623,-14.250715301799051],[-26.125369947914503,-11.54064986657559],[-10.508572009494033,-35.06149380752737]],[[39.829571435268129,-17.504227477249202],[7.2615297628459139,-14.559458820527061],[15.226225965240992,-5.3702891526017318],[39.829571435268129,-17.504227477249202]]]}") - .getGeometry()); + Polygon polygon = (Polygon) (TestCommonMethods.fromJson("{\"rings\":[[[-1.2588613620456419,13.607321860624268],[61.845346679259052,48.415944386573557],[15.226225965240992,-5.3702891526017318],[0.92681706095183469,1.6819284384951441],[3.8469417404317623,-14.250715301799051],[7.2615297628459139,-14.559458820527061],[4.4896578086498238,-17.757471781424698],[14.589138845678622,-72.861774161244625],[-10.508572009494033,-35.06149380752737],[-58.12642296329372,-90],[-15.260062192400673,90],[-1.2588613620456419,13.607321860624268]],[[0.92681706095183469,1.6819284384951441],[-1.2588613620456419,13.607321860624268],[-11.641308877525201,7.8803076458946304],[0.92681706095183469,1.6819284384951441]],[[-10.508572009494033,-35.06149380752737],[4.4896578086498238,-17.757471781424698],[3.8469417404317623,-14.250715301799051],[-26.125369947914503,-11.54064986657559],[-10.508572009494033,-35.06149380752737]],[[39.829571435268129,-17.504227477249202],[7.2615297628459139,-14.559458820527061],[15.226225965240992,-5.3702891526017318],[39.829571435268129,-17.504227477249202]]]}").getGeometry()); Polygon convex_hull = (Polygon) (bounding.execute(polygon, null)); - Polygon differenced = (Polygon) (difference.execute(polygon, - convex_hull, SpatialReference.create(4326), null)); + Polygon differenced = (Polygon) (difference.execute(polygon, convex_hull, SpatialReference.create(4326), null)); assertTrue(differenced.isEmpty()); assertTrue(bounding.isConvex(convex_hull, null)); } { - Polygon polygon = (Polygon) (TestCommonMethods - .fromJson("{\"rings\":[[[-19.681975166855118,-34.10344217707847],[-90,89.999999998418275],[53.036316534501381,90],[-19.681975166855118,-34.10344217707847]],[[-52.434509065706855,-90],[-29.2339442498794,-50.405148598356135],[-2.8515119199232331,-90],[-52.434509065706855,-90]],[[18.310881874573923,-90],[-25.473718245381271,-43.987822508814972],[-19.681975166855118,-34.10344217707847],[-15.406194071963924,-41.649717163101563],[18.310881874573923,-90]],[[-29.2339442498794,-50.405148598356135],[-52.27954259799813,-15.81822990020261],[-25.473718245381271,-43.987822508814972],[-29.2339442498794,-50.405148598356135]]]}") - .getGeometry()); + Polygon polygon = (Polygon) (TestCommonMethods.fromJson("{\"rings\":[[[-19.681975166855118,-34.10344217707847],[-90,89.999999998418275],[53.036316534501381,90],[-19.681975166855118,-34.10344217707847]],[[-52.434509065706855,-90],[-29.2339442498794,-50.405148598356135],[-2.8515119199232331,-90],[-52.434509065706855,-90]],[[18.310881874573923,-90],[-25.473718245381271,-43.987822508814972],[-19.681975166855118,-34.10344217707847],[-15.406194071963924,-41.649717163101563],[18.310881874573923,-90]],[[-29.2339442498794,-50.405148598356135],[-52.27954259799813,-15.81822990020261],[-25.473718245381271,-43.987822508814972],[-29.2339442498794,-50.405148598356135]]]}").getGeometry()); Polygon convex_hull = (Polygon) (bounding.execute(polygon, null)); - Polygon differenced = (Polygon) (difference.execute(polygon, - convex_hull, SpatialReference.create(4326), null)); + Polygon differenced = (Polygon) (difference.execute(polygon, convex_hull, SpatialReference.create(4326), null)); assertTrue(differenced.isEmpty()); assertTrue(bounding.isConvex(convex_hull, null)); } { - Polygon polygon = (Polygon) (TestCommonMethods - .fromJson("{\"rings\":[[[49.939516827702498,-90],[-20.470128740962011,-68.102019032647391],[-20.124197553433845,-67.213968219799824],[34.438329237618149,-61.893901496061034],[49.939516827702498,-90]],[[-82.380918375714089,-73.284249936115529],[-4.7432060543229699,9.1484031048644194],[-11.790524932251525,21.926303986370414],[-3.4862200343039369,10.483021157965428],[19.753975453441285,35.158541777575607],[5.5896897290794696,-1.2030408273476854],[73.839023528563189,-58.052174675157325],[34.438329237618149,-61.893901496061034],[3.6757233436274213,-6.1164440290327313],[-20.124197553433845,-67.213968219799824],[-82.380918375714089,-73.284249936115529]],[[5.5896897290794696,-1.2030408273476854],[4.0842948437219349,0.050896618883412792],[-3.4862200343039369,10.483021157965428],[-4.7432060543229699,9.1484031048644194],[3.6757233436274213,-6.1164440290327313],[5.5896897290794696,-1.2030408273476854]]]}") - .getGeometry()); + Polygon polygon = (Polygon) (TestCommonMethods.fromJson("{\"rings\":[[[49.939516827702498,-90],[-20.470128740962011,-68.102019032647391],[-20.124197553433845,-67.213968219799824],[34.438329237618149,-61.893901496061034],[49.939516827702498,-90]],[[-82.380918375714089,-73.284249936115529],[-4.7432060543229699,9.1484031048644194],[-11.790524932251525,21.926303986370414],[-3.4862200343039369,10.483021157965428],[19.753975453441285,35.158541777575607],[5.5896897290794696,-1.2030408273476854],[73.839023528563189,-58.052174675157325],[34.438329237618149,-61.893901496061034],[3.6757233436274213,-6.1164440290327313],[-20.124197553433845,-67.213968219799824],[-82.380918375714089,-73.284249936115529]],[[5.5896897290794696,-1.2030408273476854],[4.0842948437219349,0.050896618883412792],[-3.4862200343039369,10.483021157965428],[-4.7432060543229699,9.1484031048644194],[3.6757233436274213,-6.1164440290327313],[5.5896897290794696,-1.2030408273476854]]]}").getGeometry()); Polygon convex_hull = (Polygon) (bounding.execute(polygon, null)); - Polygon differenced = (Polygon) (difference.execute(polygon, - convex_hull, SpatialReference.create(4326), null)); + Polygon differenced = (Polygon) (difference.execute(polygon, convex_hull, SpatialReference.create(4326), null)); assertTrue(differenced.isEmpty()); assertTrue(bounding.isConvex(convex_hull, null)); } { - Polygon polygon = (Polygon) (TestCommonMethods - .fromJson("{\"rings\":[[[4.7618727814345867,-14.245890151885444],[-7.1675180359486266,-90],[-83.232840716292529,40.620187389409224],[-29.219286930421923,6.9418934044755334],[-29.378277853968513,6.9629531745072839],[-28.933835455648254,6.7639099538036529],[4.7618727814345867,-14.245890151885444]],[[51.056303527367277,-43.111190419066219],[4.7618727814345867,-14.245890151885444],[5.632592229367642,-8.716640778187827],[-28.933835455648254,6.7639099538036529],[-29.219286930421923,6.9418934044755334],[2.700964609629902,2.7137705544807242],[12.385960896403816,0.48342578457646468],[51.056303527367277,-43.111190419066219]]]}") - .getGeometry()); + Polygon polygon = (Polygon) (TestCommonMethods.fromJson("{\"rings\":[[[4.7618727814345867,-14.245890151885444],[-7.1675180359486266,-90],[-83.232840716292529,40.620187389409224],[-29.219286930421923,6.9418934044755334],[-29.378277853968513,6.9629531745072839],[-28.933835455648254,6.7639099538036529],[4.7618727814345867,-14.245890151885444]],[[51.056303527367277,-43.111190419066219],[4.7618727814345867,-14.245890151885444],[5.632592229367642,-8.716640778187827],[-28.933835455648254,6.7639099538036529],[-29.219286930421923,6.9418934044755334],[2.700964609629902,2.7137705544807242],[12.385960896403816,0.48342578457646468],[51.056303527367277,-43.111190419066219]]]}").getGeometry()); Polygon convex_hull = (Polygon) (bounding.execute(polygon, null)); - Polygon differenced = (Polygon) (difference.execute(polygon, - convex_hull, SpatialReference.create(4326), null)); + Polygon differenced = (Polygon) (difference.execute(polygon, convex_hull, SpatialReference.create(4326), null)); assertTrue(differenced.isEmpty()); assertTrue(bounding.isConvex(convex_hull, null)); } { - Polygon polygon = (Polygon) (TestCommonMethods - .fromJson("{\"rings\":[[[-21.426619830983732,-89.379667629404466],[-72.784461583687971,-88.999754827814016],[-81.94289434769162,25.456737039611831],[-38.382426191605546,-57.204127144336077],[-41.663734179022256,-78.439084394036513],[-29.749353943865881,-73.586348060869426],[-21.426619830983732,-89.379667629404466]],[[-21.09971823441461,-90],[-21.426619830983732,-89.379667629404466],[-21.080965849893449,-89.382224558742934],[-21.09971823441461,-90]],[[62.431917153693021,-90],[-21.080965849893449,-89.382224558742934],[-20.486971473666468,-69.813772479288062],[19.166418765782844,-53.662915804391695],[63.671046682728601,-90],[62.431917153693021,-90]],[[-29.749353943865881,-73.586348060869426],[-38.382426191605546,-57.204127144336077],[-31.449272112025476,-12.336278393150847],[-41.028899505665962,-4.5147159296945967],[-30.750049689226596,-7.8112663207986941],[-15.63587330244308,90],[-18.721998818789388,-11.66880646480822],[60.158611185675326,-36.966763960486929],[19.166418765782844,-53.662915804391695],[-19.049573405176112,-22.46036923493498],[-20.486971473666468,-69.813772479288062],[-29.749353943865881,-73.586348060869426]],[[-19.049573405176112,-22.46036923493498],[-18.721998818789388,-11.66880646480822],[-30.750049689226596,-7.8112663207986941],[-31.449272112025476,-12.336278393150847],[-19.049573405176112,-22.46036923493498]]]}") - .getGeometry()); + Polygon polygon = (Polygon) (TestCommonMethods.fromJson("{\"rings\":[[[-21.426619830983732,-89.379667629404466],[-72.784461583687971,-88.999754827814016],[-81.94289434769162,25.456737039611831],[-38.382426191605546,-57.204127144336077],[-41.663734179022256,-78.439084394036513],[-29.749353943865881,-73.586348060869426],[-21.426619830983732,-89.379667629404466]],[[-21.09971823441461,-90],[-21.426619830983732,-89.379667629404466],[-21.080965849893449,-89.382224558742934],[-21.09971823441461,-90]],[[62.431917153693021,-90],[-21.080965849893449,-89.382224558742934],[-20.486971473666468,-69.813772479288062],[19.166418765782844,-53.662915804391695],[63.671046682728601,-90],[62.431917153693021,-90]],[[-29.749353943865881,-73.586348060869426],[-38.382426191605546,-57.204127144336077],[-31.449272112025476,-12.336278393150847],[-41.028899505665962,-4.5147159296945967],[-30.750049689226596,-7.8112663207986941],[-15.63587330244308,90],[-18.721998818789388,-11.66880646480822],[60.158611185675326,-36.966763960486929],[19.166418765782844,-53.662915804391695],[-19.049573405176112,-22.46036923493498],[-20.486971473666468,-69.813772479288062],[-29.749353943865881,-73.586348060869426]],[[-19.049573405176112,-22.46036923493498],[-18.721998818789388,-11.66880646480822],[-30.750049689226596,-7.8112663207986941],[-31.449272112025476,-12.336278393150847],[-19.049573405176112,-22.46036923493498]]]}").getGeometry()); Polygon convex_hull = (Polygon) (bounding.execute(polygon, null)); - Polygon differenced = (Polygon) (difference.execute(polygon, - convex_hull, SpatialReference.create(4326), null)); + Polygon differenced = (Polygon) (difference.execute(polygon, convex_hull, SpatialReference.create(4326), null)); assertTrue(differenced.isEmpty()); assertTrue(bounding.isConvex(convex_hull, null)); } { - Polygon polygon = (Polygon) (TestCommonMethods - .fromJson("{\"rings\":[[[-17.906614911617162,-53.670186894017093],[-72.687715727164573,-90],[-77.889582483879749,90],[-47.149885004784061,16.372797801863811],[-58.874489264131405,8.3403055152440846],[-44.017112148517498,8.8692333739436133],[-43.760297522359615,8.2541153357643502],[-48.398890069305921,4.7201397602360009],[-38.665987052649818,-3.9476907252248874],[-17.906614911617162,-53.670186894017093]],[[-2.7387498969355368,-90],[-17.906614911617162,-53.670186894017093],[-6.8038688963847829,-46.30705103709559],[-2.7387498969355368,-90]],[[-6.8038688963847829,-46.30705103709559],[-8.2224486207861638,-31.0597897622158],[2.1962303277340673,-40.338351652092697],[-6.8038688963847829,-46.30705103709559]],[[-8.2224486207861638,-31.0597897622158],[-38.665987052649818,-3.9476907252248874],[-43.760297522359615,8.2541153357643502],[-42.90074612601282,8.9089763975751382],[-44.017112148517498,8.8692333739436133],[-47.149885004784061,16.372797801863811],[45.190674429223662,79.635046572817728],[40.490070954305672,72.441418146356597],[63.53694979672099,90],[75.056911135062407,13.108310545642606],[-0.027204347469059975,10.435289586728711],[-10.580480746811602,-5.715051428780245],[-8.2224486207861638,-31.0597897622158]],[[-42.90074612601282,8.9089763975751382],[-0.027204347469059975,10.435289586728711],[40.490070954305672,72.441418146356597],[-42.90074612601282,8.9089763975751382]]]}") - .getGeometry()); + Polygon polygon = (Polygon) (TestCommonMethods.fromJson("{\"rings\":[[[-17.906614911617162,-53.670186894017093],[-72.687715727164573,-90],[-77.889582483879749,90],[-47.149885004784061,16.372797801863811],[-58.874489264131405,8.3403055152440846],[-44.017112148517498,8.8692333739436133],[-43.760297522359615,8.2541153357643502],[-48.398890069305921,4.7201397602360009],[-38.665987052649818,-3.9476907252248874],[-17.906614911617162,-53.670186894017093]],[[-2.7387498969355368,-90],[-17.906614911617162,-53.670186894017093],[-6.8038688963847829,-46.30705103709559],[-2.7387498969355368,-90]],[[-6.8038688963847829,-46.30705103709559],[-8.2224486207861638,-31.0597897622158],[2.1962303277340673,-40.338351652092697],[-6.8038688963847829,-46.30705103709559]],[[-8.2224486207861638,-31.0597897622158],[-38.665987052649818,-3.9476907252248874],[-43.760297522359615,8.2541153357643502],[-42.90074612601282,8.9089763975751382],[-44.017112148517498,8.8692333739436133],[-47.149885004784061,16.372797801863811],[45.190674429223662,79.635046572817728],[40.490070954305672,72.441418146356597],[63.53694979672099,90],[75.056911135062407,13.108310545642606],[-0.027204347469059975,10.435289586728711],[-10.580480746811602,-5.715051428780245],[-8.2224486207861638,-31.0597897622158]],[[-42.90074612601282,8.9089763975751382],[-0.027204347469059975,10.435289586728711],[40.490070954305672,72.441418146356597],[-42.90074612601282,8.9089763975751382]]]}").getGeometry()); Polygon convex_hull = (Polygon) (bounding.execute(polygon, null)); - Polygon differenced = (Polygon) (difference.execute(polygon, - convex_hull, SpatialReference.create(4326), null)); + Polygon differenced = (Polygon) (difference.execute(polygon, convex_hull, SpatialReference.create(4326), null)); assertTrue(differenced.isEmpty()); assertTrue(bounding.isConvex(convex_hull, null)); } @@ -573,14 +605,10 @@ public static void testPolygons() { @Test public static void testPolylines() { - OperatorConvexHull bounding = (OperatorConvexHull) OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.ConvexHull); - OperatorDensifyByLength densify = (OperatorDensifyByLength) OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.DensifyByLength); - OperatorDifference difference = (OperatorDifference) OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.Difference); - OperatorContains contains = (OperatorContains) OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.Contains); + OperatorConvexHull bounding = (OperatorConvexHull) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.ConvexHull); + OperatorDensifyByLength densify = (OperatorDensifyByLength) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.DensifyByLength); + OperatorDifference difference = (OperatorDifference) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.Difference); + OperatorContains contains = (OperatorContains) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.Contains); { Polyline poly = new Polyline(); @@ -591,9 +619,8 @@ public static void testPolylines() { poly.lineTo(0, 500); Polyline densified = (Polyline) (densify.execute(poly, 10.0, null)); - Polygon convex_hull = (Polygon) (bounding.execute(densified, null)); - Polyline differenced = (Polyline) (difference.execute(densified, - convex_hull, SpatialReference.create(4326), null)); + Polyline convex_hull = (Polyline) (bounding.execute(densified, null)); + Polyline differenced = (Polyline) (difference.execute(densified, convex_hull, SpatialReference.create(4326), null)); assertTrue(differenced.isEmpty()); assertTrue(bounding.isConvex(convex_hull, null)); } @@ -608,11 +635,9 @@ public static void testPolylines() { polyline.lineTo(170, 45); polyline.lineTo(225, 65); - Polyline densified = (Polyline) (densify.execute(polyline, 10.0, - null)); + Polyline densified = (Polyline) (densify.execute(polyline, 10.0, null)); Polygon convex_hull = (Polygon) (bounding.execute(densified, null)); - boolean bcontains = contains.execute(convex_hull, densified, - SpatialReference.create(4326), null); + boolean bcontains = contains.execute(convex_hull, densified, SpatialReference.create(4326), null); assertTrue(bcontains); assertTrue(bounding.isConvex(convex_hull, null)); @@ -621,46 +646,33 @@ public static void testPolylines() { @Test public static void testNonSimpleShape() { - OperatorConvexHull bounding = (OperatorConvexHull) OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.ConvexHull); - OperatorDensifyByLength densify = (OperatorDensifyByLength) OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.DensifyByLength); - OperatorDifference difference = (OperatorDifference) OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.Difference); - OperatorContains contains = (OperatorContains) OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.Contains); - - { - Polygon shape = (Polygon) (TestCommonMethods - .fromJson("{\"rings\":[[[6.7260599916745036,-90],[15.304037095218971,-18.924146439950675],[6.3163297788539232,-90],[5.2105387036445823,-59.980719950158637],[5.1217504663506981,-60.571174400735508],[8.2945138368731044,-27.967042187979146],[15.76606600357545,28.594953216378414],[8.4365340991447919,66.880924521951329],[10.115022726199774,65.247385313781265],[12.721206966604395,-23.793016208456883],[12.051875868947576,-11.368909618476637],[11.867118776841318,44.968896646914239],[7.5326099218274614,35.095776924526533],[8.86765609460479,90],[3.2036592678446922,55.507964789691712],[0.23585282258761486,-42.620591380394039],[-1.2660432762142744,90],[5.5580612840503001,-9.4879902323389196],[12.258387597532487,-35.945231749575591],[-48.746716054894101,90],[7.2294405148356846,-15.719232058488402],[13.798313011339591,-10.467172541381753],[7.4430022048746718,6.3951685161785656],[6.4876332898327815,31.10016146737189],[9.3645424359058911,47.123308099298804],[13.398605254542668,-6.4398318586014325],[-90,90],[13.360786277212718,82.971274676174545],[7.9405631778693566,90],[10.512482079680538,90],[16.994982794293946,19.60673041736408],[16.723893839323615,22.728853852102926],[23.178783416627525,90],[6.7260599916745036,-90]],[[26.768777234301993,90],[20.949797955126346,90],[11.967758262201434,-0.45048849056049711],[17.535751576687339,52.767528591651441],[26.768777234301993,90]],[[18.677765775891793,12.559680067559942],[19.060218406331451,90],[17.123595624401705,90],[-2.3805299720687887,-90],[-11.882782057881979,-90],[21.640575461689693,90],[11.368255808198477,85.501555553904794],[17.390084032215348,90],[23.999392897519989,78.255909006554603],[-6.8860811786563101,69.49189433189926],[29.232578855788898,90],[25.951412073846683,90],[-5.5572284181160772,-16.763772082849457],[18.677765775891793,12.559680067559942]]]}") - .getGeometry()); + OperatorConvexHull bounding = (OperatorConvexHull) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.ConvexHull); + OperatorDensifyByLength densify = (OperatorDensifyByLength) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.DensifyByLength); + OperatorDifference difference = (OperatorDifference) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.Difference); + OperatorContains contains = (OperatorContains) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.Contains); + + { + Polygon shape = (Polygon) (TestCommonMethods.fromJson("{\"rings\":[[[6.7260599916745036,-90],[15.304037095218971,-18.924146439950675],[6.3163297788539232,-90],[5.2105387036445823,-59.980719950158637],[5.1217504663506981,-60.571174400735508],[8.2945138368731044,-27.967042187979146],[15.76606600357545,28.594953216378414],[8.4365340991447919,66.880924521951329],[10.115022726199774,65.247385313781265],[12.721206966604395,-23.793016208456883],[12.051875868947576,-11.368909618476637],[11.867118776841318,44.968896646914239],[7.5326099218274614,35.095776924526533],[8.86765609460479,90],[3.2036592678446922,55.507964789691712],[0.23585282258761486,-42.620591380394039],[-1.2660432762142744,90],[5.5580612840503001,-9.4879902323389196],[12.258387597532487,-35.945231749575591],[-48.746716054894101,90],[7.2294405148356846,-15.719232058488402],[13.798313011339591,-10.467172541381753],[7.4430022048746718,6.3951685161785656],[6.4876332898327815,31.10016146737189],[9.3645424359058911,47.123308099298804],[13.398605254542668,-6.4398318586014325],[-90,90],[13.360786277212718,82.971274676174545],[7.9405631778693566,90],[10.512482079680538,90],[16.994982794293946,19.60673041736408],[16.723893839323615,22.728853852102926],[23.178783416627525,90],[6.7260599916745036,-90]],[[26.768777234301993,90],[20.949797955126346,90],[11.967758262201434,-0.45048849056049711],[17.535751576687339,52.767528591651441],[26.768777234301993,90]],[[18.677765775891793,12.559680067559942],[19.060218406331451,90],[17.123595624401705,90],[-2.3805299720687887,-90],[-11.882782057881979,-90],[21.640575461689693,90],[11.368255808198477,85.501555553904794],[17.390084032215348,90],[23.999392897519989,78.255909006554603],[-6.8860811786563101,69.49189433189926],[29.232578855788898,90],[25.951412073846683,90],[-5.5572284181160772,-16.763772082849457],[18.677765775891793,12.559680067559942]]]}").getGeometry()); Polygon densified = (Polygon) (densify.execute(shape, 10.0, null)); Polygon convex_hull = (Polygon) (bounding.execute(densified, null)); - Polygon differenced = (Polygon) (difference.execute(densified, - convex_hull, SpatialReference.create(4326), null)); + Polygon differenced = (Polygon) (difference.execute(densified, convex_hull, SpatialReference.create(4326), null)); assertTrue(differenced.isEmpty()); assertTrue(bounding.isConvex(convex_hull, null)); } { - Polygon shape = (Polygon) (TestCommonMethods - .fromJson("{\"rings\":[[[-13.630596027421603,3.6796011190640709],[-10.617275202716886,-28.133054337834409],[-81.617315194491695,90],[-4.0763362539824293,-90],[2.8213537979804073,-90],[-5.1515857979973365,-11.605767592612787],[43.477754021411123,35.507543731267589],[-45.818261267516704,-90],[-4.8545715514870018,-64.204371906322223],[-1.744951154293144,-30.257848194381509],[-7.8524748309267149,15.513561279453089],[-10.657563385538953,-81.785061432086309],[-6.3487369893289411,-31.849779201388415],[-14.768278524737962,-12.004393281111058],[-27.001635582579841,90],[-14.967554248940855,-78.970629918591811],[-12.999635147475825,-38.584472796107939],[-13.630596027421603,3.6796011190640709]],[[-16.338143621861352,-37.415690513288375],[-21.553879270366266,-90],[-18.649338100909404,-90],[-24.880584966233631,1.3133858590648728],[-16.483464632078249,-53.979692212288882],[-24.836979215403964,-68.69859399640147],[-29.708282990385214,-90],[-27.469962102507036,-1.6392995673644872],[-20.405051753708271,61.943199597870034],[-18.242567838912599,24.405109362934219],[-66.334547696572528,-52.678390155566603],[-13.471083255903507,-33.782708412943229],[-7.092757068096085,33.673785662500464],[-2.7427100969018205,74.386868339212668],[-8.2174861339989675,90],[-15.699459164009667,90],[-9.5910045204059156,90],[-8.4504603287557369,90],[-1.5498862802092637,2.5144190340747681],[-6.5326327868410639,-17.428029961128306],[-10.947786354404593,31.516236387466538],[-7.4777936485986354,12.486727826508769],[-13.89052186883092,12.397126427870356],[-10.530672679779606,-55.463541447339118],[-8.7161833631330374,-90],[-4.7231067612639519,-90],[-3.9692500849117041,-32.204677519048822],[3.740804266163555,32.88191805391007],[6.2021313886056246,76.617541950091564],[6.1183997672398194,90],[0.59730820015390673,90],[7.3242950674530753,18.030401540676614],[1.8252371571535342,90],[-16.338143621861352,-37.415690513288375]]]}") - .getGeometry()); + Polygon shape = (Polygon) (TestCommonMethods.fromJson("{\"rings\":[[[-13.630596027421603,3.6796011190640709],[-10.617275202716886,-28.133054337834409],[-81.617315194491695,90],[-4.0763362539824293,-90],[2.8213537979804073,-90],[-5.1515857979973365,-11.605767592612787],[43.477754021411123,35.507543731267589],[-45.818261267516704,-90],[-4.8545715514870018,-64.204371906322223],[-1.744951154293144,-30.257848194381509],[-7.8524748309267149,15.513561279453089],[-10.657563385538953,-81.785061432086309],[-6.3487369893289411,-31.849779201388415],[-14.768278524737962,-12.004393281111058],[-27.001635582579841,90],[-14.967554248940855,-78.970629918591811],[-12.999635147475825,-38.584472796107939],[-13.630596027421603,3.6796011190640709]],[[-16.338143621861352,-37.415690513288375],[-21.553879270366266,-90],[-18.649338100909404,-90],[-24.880584966233631,1.3133858590648728],[-16.483464632078249,-53.979692212288882],[-24.836979215403964,-68.69859399640147],[-29.708282990385214,-90],[-27.469962102507036,-1.6392995673644872],[-20.405051753708271,61.943199597870034],[-18.242567838912599,24.405109362934219],[-66.334547696572528,-52.678390155566603],[-13.471083255903507,-33.782708412943229],[-7.092757068096085,33.673785662500464],[-2.7427100969018205,74.386868339212668],[-8.2174861339989675,90],[-15.699459164009667,90],[-9.5910045204059156,90],[-8.4504603287557369,90],[-1.5498862802092637,2.5144190340747681],[-6.5326327868410639,-17.428029961128306],[-10.947786354404593,31.516236387466538],[-7.4777936485986354,12.486727826508769],[-13.89052186883092,12.397126427870356],[-10.530672679779606,-55.463541447339118],[-8.7161833631330374,-90],[-4.7231067612639519,-90],[-3.9692500849117041,-32.204677519048822],[3.740804266163555,32.88191805391007],[6.2021313886056246,76.617541950091564],[6.1183997672398194,90],[0.59730820015390673,90],[7.3242950674530753,18.030401540676614],[1.8252371571535342,90],[-16.338143621861352,-37.415690513288375]]]}").getGeometry()); Polygon densified = (Polygon) (densify.execute(shape, 10.0, null)); Polygon convex_hull = (Polygon) (bounding.execute(densified, null)); - Polygon differenced = (Polygon) (difference.execute(densified, - convex_hull, SpatialReference.create(4326), null)); + Polygon differenced = (Polygon) (difference.execute(densified, convex_hull, SpatialReference.create(4326), null)); assertTrue(differenced.isEmpty()); assertTrue(bounding.isConvex(convex_hull, null)); } { - Polygon shape = (Polygon) (TestCommonMethods - .fromJson("{\"rings\":[[[-11.752662474672046,-90],[-76.236530072126513,7.3247326417920817],[18.933251883215579,90],[51.538924439116798,90],[52.253017336758049,80.352482145105284],[41.767201918260639,51.890297432229289],[21.697252770910882,-1.3185641048567049],[45.112193442818935,60.758441021743636],[48.457184967377231,69.626584611257954],[49.531808284502759,70.202152706968036],[52.394797054144334,71.533541126234581],[ 52.9671102343993,70.704964290210626],[58.527850348069251,16.670036266565845],[62.310807912773328,-34.249918700039238],[62.775020703241523,-43.541598916699364],[64.631871865114277,-80.708319783339874],[65.096084655582459,-90],[-11.752662474672046,-90]]]}") - .getGeometry()); + Polygon shape = (Polygon) (TestCommonMethods.fromJson("{\"rings\":[[[-11.752662474672046,-90],[-76.236530072126513,7.3247326417920817],[18.933251883215579,90],[51.538924439116798,90],[52.253017336758049,80.352482145105284],[41.767201918260639,51.890297432229289],[21.697252770910882,-1.3185641048567049],[45.112193442818935,60.758441021743636],[48.457184967377231,69.626584611257954],[49.531808284502759,70.202152706968036],[52.394797054144334,71.533541126234581],[ 52.9671102343993,70.704964290210626],[58.527850348069251,16.670036266565845],[62.310807912773328,-34.249918700039238],[62.775020703241523,-43.541598916699364],[64.631871865114277,-80.708319783339874],[65.096084655582459,-90],[-11.752662474672046,-90]]]}").getGeometry()); Polygon convex_hull = (Polygon) (bounding.execute(shape, null)); - Polygon differenced = (Polygon) (difference.execute(shape, - convex_hull, SpatialReference.create(4326), null)); + Polygon differenced = (Polygon) (difference.execute(shape, convex_hull, SpatialReference.create(4326), null)); assertTrue(differenced.isEmpty()); assertTrue(bounding.isConvex(convex_hull, null)); } @@ -725,10 +737,8 @@ public static void testNonSimpleShape() { @Test public static void testStar() { - OperatorConvexHull bounding = (OperatorConvexHull) OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.ConvexHull); - OperatorDensifyByLength densify = (OperatorDensifyByLength) OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.DensifyByLength); + OperatorConvexHull bounding = (OperatorConvexHull) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.ConvexHull); + OperatorDensifyByLength densify = (OperatorDensifyByLength) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.DensifyByLength); Polygon star = new Polygon(); star.startPath(0, 0); @@ -797,8 +807,7 @@ public static void testPointsArray() { @Test public static void testMergeCursor() { - OperatorConvexHull bounding = (OperatorConvexHull) OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.ConvexHull); + OperatorConvexHull bounding = (OperatorConvexHull) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.ConvexHull); Polygon geom1 = new Polygon(); Polygon geom2 = new Polygon(); @@ -890,7 +899,7 @@ public void testHullTickTock() { Polygon geom1 = new Polygon(); Polygon geom2 = new Polygon(); Point geom3 = new Point(); - Line geom4= new Line(); + Line geom4 = new Line(); Envelope geom5 = new Envelope(); MultiPoint geom6 = new MultiPoint(); @@ -943,7 +952,7 @@ public void testHullTickTock() { ticktock.tock(); // Get the result Geometry result = ticktock.next(); - Polygon convex_hull = (Polygon)result; + Polygon convex_hull = (Polygon) result; assertTrue(OperatorConvexHull.local().isConvex(convex_hull, null)); Point2D p1 = convex_hull.getXY(0); @@ -959,5 +968,5 @@ public void testHullTickTock() { assertTrue(p5.x == -5.0 && p5.y == 1.25); assertTrue(p6.x == 0.0 && p6.y == 10.0); } - + } diff --git a/src/test/java/com/esri/core/geometry/TestDifference.java b/src/test/java/com/esri/core/geometry/TestDifference.java index ca7bc3d4..455877e6 100644 --- a/src/test/java/com/esri/core/geometry/TestDifference.java +++ b/src/test/java/com/esri/core/geometry/TestDifference.java @@ -1,6 +1,10 @@ package com.esri.core.geometry; +import java.util.ArrayList; +import java.util.List; + import junit.framework.TestCase; + import org.junit.Test; public class TestDifference extends TestCase { diff --git a/src/test/java/com/esri/core/geometry/TestGeodetic.java b/src/test/java/com/esri/core/geometry/TestGeodetic.java index 0cdbf6f7..8777ec19 100644 --- a/src/test/java/com/esri/core/geometry/TestGeodetic.java +++ b/src/test/java/com/esri/core/geometry/TestGeodetic.java @@ -24,7 +24,7 @@ public void testTriangleLength() { length += GeometryEngine.geodesicDistanceOnWGS84(pt_0, pt_1); length += GeometryEngine.geodesicDistanceOnWGS84(pt_1, pt_2); length += GeometryEngine.geodesicDistanceOnWGS84(pt_2, pt_0); - assertTrue(Math.abs(length - 3744719.4094597572) < 1e-13 * 3744719.4094597572); + assertTrue(Math.abs(length - 3744719.4094597572) < 1e-12 * 3744719.4094597572); } @Test @@ -36,7 +36,7 @@ public void testRotationInvariance() { length += GeometryEngine.geodesicDistanceOnWGS84(pt_0, pt_1); length += GeometryEngine.geodesicDistanceOnWGS84(pt_1, pt_2); length += GeometryEngine.geodesicDistanceOnWGS84(pt_2, pt_0); - assertTrue(Math.abs(length - 5409156.3896271614) < 1e-13 * 5409156.3896271614); + assertTrue(Math.abs(length - 5409156.3896271614) < 1e-12 * 5409156.3896271614); for (int i = -540; i < 540; i += 5) { pt_0.setXY(i + 10, 40); @@ -46,10 +46,41 @@ public void testRotationInvariance() { length += GeometryEngine.geodesicDistanceOnWGS84(pt_0, pt_1); length += GeometryEngine.geodesicDistanceOnWGS84(pt_1, pt_2); length += GeometryEngine.geodesicDistanceOnWGS84(pt_2, pt_0); - assertTrue(Math.abs(length - 5409156.3896271614) < 1e-13 * 5409156.3896271614); + assertTrue(Math.abs(length - 5409156.3896271614) < 1e-12 * 5409156.3896271614); } } + @Test + public void testDistanceFailure() { + { + Point p1 = new Point(-60.668485, -31.996013333333334); + Point p2 = new Point(119.13731666666666, 32.251583333333336); + double d = GeometryEngine.geodesicDistanceOnWGS84(p1, p2); + assertTrue(Math.abs(d - 19973410.50579736) < 1e-13 * 19973410.50579736); + } + + { + Point p1 = new Point(121.27343833333333, 27.467438333333334); + Point p2 = new Point(-58.55804833333333, -27.035613333333334); + double d = GeometryEngine.geodesicDistanceOnWGS84(p1, p2); + assertTrue(Math.abs(d - 19954707.428360686) < 1e-13 * 19954707.428360686); + } + + { + Point p1 = new Point(-53.329865, -36.08110166666667); + Point p2 = new Point(126.52895166666667, 35.97385); + double d = GeometryEngine.geodesicDistanceOnWGS84(p1, p2); + assertTrue(Math.abs(d - 19990586.700431127) < 1e-13 * 19990586.700431127); + } + + { + Point p1 = new Point(-4.7181166667, 36.1160166667); + Point p2 = new Point(175.248925, -35.7606716667); + double d = GeometryEngine.geodesicDistanceOnWGS84(p1, p2); + assertTrue(Math.abs(d - 19964450.206594173) < 1e-12 * 19964450.206594173); + } + } + @Test public void testLengthAccurateCR191313() { /* diff --git a/src/test/java/com/esri/core/geometry/TestGeomToGeoJson.java b/src/test/java/com/esri/core/geometry/TestGeomToGeoJson.java index 9c517d39..5ef3157e 100644 --- a/src/test/java/com/esri/core/geometry/TestGeomToGeoJson.java +++ b/src/test/java/com/esri/core/geometry/TestGeomToGeoJson.java @@ -3,7 +3,7 @@ you may not use this file except in compliance with the License. You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 + http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, @@ -32,6 +32,7 @@ import org.codehaus.jackson.JsonFactory; import org.codehaus.jackson.JsonParser; import org.json.JSONException; +import org.json.JSONObject; import org.junit.Test; import java.io.IOException; @@ -39,368 +40,432 @@ import java.util.List; public class TestGeomToGeoJson extends TestCase { - OperatorFactoryLocal factory = OperatorFactoryLocal.getInstance(); - - @Override - protected void setUp() throws Exception { - super.setUp(); - } - - @Override - protected void tearDown() throws Exception { - super.tearDown(); - } - - @Test - public void testPoint() { - Point p = new Point(10.0, 20.0); - OperatorExportToGeoJson exporter = (OperatorExportToGeoJson) factory.getOperator(Operator.Type.ExportToGeoJson); - String result = exporter.execute(p); - assertEquals("{\"type\":\"Point\",\"coordinates\":[10.0,20.0]}", result); - } - - @Test - public void testEmptyPoint() { - Point p = new Point(); - OperatorExportToGeoJson exporter = (OperatorExportToGeoJson) factory.getOperator(Operator.Type.ExportToGeoJson); - String result = exporter.execute(p); - assertEquals("{\"type\":\"Point\",\"coordinates\":null}", result); - } - - @Test - public void testPointGeometryEngine() { - Point p = new Point(10.0, 20.0); - String result = GeometryEngine.geometryToGeoJson(p); - assertEquals("{\"type\":\"Point\",\"coordinates\":[10.0,20.0]}", result); - } - - @Test - public void testOGCPoint() { - Point p = new Point(10.0, 20.0); - OGCGeometry ogcPoint = new OGCPoint(p, null); - String result = ogcPoint.asGeoJson(); - assertEquals("{\"type\":\"Point\",\"coordinates\":[10.0,20.0]}", result); - } - - @Test - public void testMultiPoint() { - MultiPoint mp = new MultiPoint(); - mp.add(10.0, 20.0); - mp.add(20.0, 30.0); - OperatorExportToGeoJson exporter = (OperatorExportToGeoJson) factory.getOperator(Operator.Type.ExportToGeoJson); - String result = exporter.execute(mp); - assertEquals("{\"type\":\"MultiPoint\",\"coordinates\":[[10.0,20.0],[20.0,30.0]]}", result); - } - - @Test - public void testEmptyMultiPoint() { - MultiPoint mp = new MultiPoint(); - OperatorExportToGeoJson exporter = (OperatorExportToGeoJson) factory.getOperator(Operator.Type.ExportToGeoJson); - String result = exporter.execute(mp); - assertEquals("{\"type\":\"MultiPoint\",\"coordinates\":null}", result); - } - - @Test - public void testMultiPointGeometryEngine() { - MultiPoint mp = new MultiPoint(); - mp.add(10.0, 20.0); - mp.add(20.0, 30.0); - String result = GeometryEngine.geometryToGeoJson(mp); - assertEquals("{\"type\":\"MultiPoint\",\"coordinates\":[[10.0,20.0],[20.0,30.0]]}", result); - } - - @Test - public void testOGCMultiPoint() { - MultiPoint mp = new MultiPoint(); - mp.add(10.0, 20.0); - mp.add(20.0, 30.0); - OGCMultiPoint ogcMultiPoint = new OGCMultiPoint(mp, null); - String result = ogcMultiPoint.asGeoJson(); - assertEquals("{\"type\":\"MultiPoint\",\"coordinates\":[[10.0,20.0],[20.0,30.0]]}", result); - } - - @Test - public void testPolyline() { - Polyline p = new Polyline(); - p.startPath(100.0, 0.0); - p.lineTo(101.0, 0.0); - p.lineTo(101.0, 1.0); - p.lineTo(100.0, 1.0); - OperatorExportToGeoJson exporter = (OperatorExportToGeoJson) factory.getOperator(Operator.Type.ExportToGeoJson); - String result = exporter.execute(p); - assertEquals("{\"type\":\"LineString\",\"coordinates\":[[100.0,0.0],[101.0,0.0],[101.0,1.0],[100.0,1.0]]}", result); - } - - @Test - public void testEmptyPolyline() { - Polyline p = new Polyline(); - OperatorExportToGeoJson exporter = (OperatorExportToGeoJson) factory.getOperator(Operator.Type.ExportToGeoJson); - String result = exporter.execute(p); - assertEquals("{\"type\":\"LineString\",\"coordinates\":null}", result); - } - - @Test - public void testPolylineGeometryEngine() { - Polyline p = new Polyline(); - p.startPath(100.0, 0.0); - p.lineTo(101.0, 0.0); - p.lineTo(101.0, 1.0); - p.lineTo(100.0, 1.0); - String result = GeometryEngine.geometryToGeoJson(p); - assertEquals("{\"type\":\"LineString\",\"coordinates\":[[100.0,0.0],[101.0,0.0],[101.0,1.0],[100.0,1.0]]}", result); - } - - @Test - public void testOGCLineString() { - Polyline p = new Polyline(); - p.startPath(100.0, 0.0); - p.lineTo(101.0, 0.0); - p.lineTo(101.0, 1.0); - p.lineTo(100.0, 1.0); - OGCLineString ogcLineString = new OGCLineString(p, 0, null); - String result = ogcLineString.asGeoJson(); - assertEquals("{\"type\":\"LineString\",\"coordinates\":[[100.0,0.0],[101.0,0.0],[101.0,1.0],[100.0,1.0]]}", result); - } - - @Test - public void testPolygon() { - Polygon p = new Polygon(); - p.startPath(100.0, 0.0); - p.lineTo(101.0, 0.0); - p.lineTo(101.0, 1.0); - p.lineTo(100.0, 1.0); - p.closePathWithLine(); - OperatorExportToGeoJson exporter = (OperatorExportToGeoJson) factory.getOperator(Operator.Type.ExportToGeoJson); - String result = exporter.execute(p); - assertEquals("{\"type\":\"Polygon\",\"coordinates\":[[[100.0,0.0],[101.0,0.0],[101.0,1.0],[100.0,1.0],[100.0,0.0]]]}", result); - } - - @Test - public void testPolygonWithHole() { - Polygon p = new Polygon(); - - //exterior ring - has to be clockwise for Esri - p.startPath(100.0, 0.0); - p.lineTo(100.0, 1.0); - p.lineTo(101.0, 1.0); - p.lineTo(101.0, 0.0); - p.closePathWithLine(); - - //hole - counterclockwise for Esri - p.startPath(100.2, 0.2); - p.lineTo(100.8, 0.2); - p.lineTo(100.8, 0.8); - p.lineTo(100.2, 0.8); - p.closePathWithLine(); - - OperatorExportToGeoJson exporter = (OperatorExportToGeoJson) factory.getOperator(Operator.Type.ExportToGeoJson); - String result = exporter.execute(p); - assertEquals("{\"type\":\"Polygon\",\"coordinates\":[[[100.0,0.0],[100.0,1.0],[101.0,1.0],[101.0,0.0],[100.0,0.0]],[[100.2,0.2],[100.8,0.2],[100.8,0.8],[100.2,0.8],[100.2,0.2]]]}", result); - } - - @Test - public void testPolygonWithHoleReversed() { - Polygon p = new Polygon(); - - //exterior ring - has to be clockwise for Esri - p.startPath(100.0, 0.0); - p.lineTo(100.0, 1.0); - p.lineTo(101.0, 1.0); - p.lineTo(101.0, 0.0); - p.closePathWithLine(); - - //hole - counterclockwise for Esri - p.startPath(100.2, 0.2); - p.lineTo(100.8, 0.2); - p.lineTo(100.8, 0.8); - p.lineTo(100.2, 0.8); - p.closePathWithLine(); - - p.reverseAllPaths();//make it reversed. Exterior ring - ccw, hole - cw. - - OperatorExportToGeoJson exporter = (OperatorExportToGeoJson) factory.getOperator(Operator.Type.ExportToGeoJson); - String result = exporter.execute(p); - assertEquals("{\"type\":\"Polygon\",\"coordinates\":[[[100.0,0.0],[101.0,0.0],[101.0,1.0],[100.0,1.0],[100.0,0.0]],[[100.2,0.2],[100.2,0.8],[100.8,0.8],[100.8,0.2],[100.2,0.2]]]}", result); - } - - @Test - public void testMultiPolygon() throws IOException { - JsonFactory jsonFactory = new JsonFactory(); - - String geoJsonPolygon = "{\"type\":\"MultiPolygon\",\"coordinates\":[[[[-100.0,-100.0],[-100.0,100.0],[100.0,100.0],[100.0,-100.0],[-100.0,-100.0]],[[-90.0,-90.0],[90.0,90.0],[-90.0,90.0],[90.0,-90.0],[-90.0,-90.0]]],[[[-10.0,-10.0],[-10.0,10.0],[10.0,10.0],[10.0,-10.0],[-10.0,-10.0]]]]}"; - String esriJsonPolygon = "{\"rings\": [[[-100, -100], [-100, 100], [100, 100], [100, -100], [-100, -100]], [[-90, -90], [90, 90], [-90, 90], [90, -90], [-90, -90]], [[-10, -10], [-10, 10], [10, 10], [10, -10], [-10, -10]]]}"; - - JsonParser parser = jsonFactory.createJsonParser(esriJsonPolygon); - MapGeometry parsedPoly = GeometryEngine.jsonToGeometry(parser); - //MapGeometry parsedPoly = GeometryEngine.geometryFromGeoJson(jsonPolygon, 0, Geometry.Type.Polygon); - - Polygon poly = (Polygon) parsedPoly.getGeometry(); - - OperatorExportToGeoJson exporter = (OperatorExportToGeoJson) factory.getOperator(Operator.Type.ExportToGeoJson); - //String result = exporter.execute(parsedPoly.getGeometry()); - String result = exporter.execute(poly); - assertEquals(geoJsonPolygon, result); - } - - - - @Test - public void testEmptyPolygon() throws JSONException { - Polygon p = new Polygon(); - OperatorExportToGeoJson exporter = (OperatorExportToGeoJson) factory.getOperator(Operator.Type.ExportToGeoJson); - String result = exporter.execute(p); - assertEquals("{\"type\":\"Polygon\",\"coordinates\":null}", result); - - MapGeometry imported = OperatorImportFromGeoJson.local().execute(0, Geometry.Type.Unknown, result, null); - assertTrue(imported.getGeometry().isEmpty()); - assertTrue(imported.getGeometry().getType() == Geometry.Type.Polygon); - } - - @Test - public void testPolygonGeometryEngine() { - Polygon p = new Polygon(); - p.startPath(100.0, 0.0); - p.lineTo(101.0, 0.0); - p.lineTo(101.0, 1.0); - p.lineTo(100.0, 1.0); - p.closePathWithLine(); - String result = GeometryEngine.geometryToGeoJson(p); - assertEquals("{\"type\":\"Polygon\",\"coordinates\":[[[100.0,0.0],[101.0,0.0],[101.0,1.0],[100.0,1.0],[100.0,0.0]]]}", result); - } - - @Test - public void testOGCPolygon() { - Polygon p = new Polygon(); - p.startPath(100.0, 0.0); - p.lineTo(101.0, 0.0); - p.lineTo(101.0, 1.0); - p.lineTo(100.0, 1.0); - p.closePathWithLine(); - OGCPolygon ogcPolygon = new OGCPolygon(p, null); - String result = ogcPolygon.asGeoJson(); - assertEquals("{\"type\":\"Polygon\",\"coordinates\":[[[100.0,0.0],[101.0,0.0],[101.0,1.0],[100.0,1.0],[100.0,0.0]]]}", result); - } - - @Test - public void testPolygonWithHoleGeometryEngine() { - Polygon p = new Polygon(); - - p.startPath(100.0, 0.0);//clockwise exterior - p.lineTo(100.0, 1.0); - p.lineTo(101.0, 1.0); - p.lineTo(101.0, 0.0); - p.closePathWithLine(); - - p.startPath(100.2, 0.2);//counterclockwise hole - p.lineTo(100.8, 0.2); - p.lineTo(100.8, 0.8); - p.lineTo(100.2, 0.8); - p.closePathWithLine(); - - String result = GeometryEngine.geometryToGeoJson(p); - assertEquals("{\"type\":\"Polygon\",\"coordinates\":[[[100.0,0.0],[100.0,1.0],[101.0,1.0],[101.0,0.0],[100.0,0.0]],[[100.2,0.2],[100.8,0.2],[100.8,0.8],[100.2,0.8],[100.2,0.2]]]}", result); - } - - @Test - public void testPolylineWithTwoPaths() { - Polyline p = new Polyline(); - - p.startPath(100.0, 0.0); - p.lineTo(100.0, 1.0); - - p.startPath(100.2, 0.2); - p.lineTo(100.8, 0.2); - - String result = GeometryEngine.geometryToGeoJson(p); - assertEquals("{\"type\":\"MultiLineString\",\"coordinates\":[[[100.0,0.0],[100.0,1.0]],[[100.2,0.2],[100.8,0.2]]]}", result); - } - - @Test - public void testOGCPolygonWithHole() { - Polygon p = new Polygon(); - - p.startPath(100.0, 0.0); - p.lineTo(100.0, 1.0); - p.lineTo(101.0, 1.0); - p.lineTo(101.0, 0.0); - p.closePathWithLine(); - - p.startPath(100.2, 0.2); - p.lineTo(100.8, 0.2); - p.lineTo(100.8, 0.8); - p.lineTo(100.2, 0.8); - p.closePathWithLine(); - - OGCPolygon ogcPolygon = new OGCPolygon(p, null); - String result = ogcPolygon.asGeoJson(); - assertEquals("{\"type\":\"Polygon\",\"coordinates\":[[[100.0,0.0],[100.0,1.0],[101.0,1.0],[101.0,0.0],[100.0,0.0]],[[100.2,0.2],[100.8,0.2],[100.8,0.8],[100.2,0.8],[100.2,0.2]]]}", result); - } - - @Test - public void testEnvelope() { - Envelope e = new Envelope(); - e.setCoords(-180.0, -90.0, 180.0, 90.0); - OperatorExportToGeoJson exporter = (OperatorExportToGeoJson) factory.getOperator(Operator.Type.ExportToGeoJson); - String result = exporter.execute(e); - assertEquals("{\"bbox\":[-180.0,-90.0,180.0,90.0]}", result); - } - - @Test - public void testEmptyEnvelope() { - Envelope e = new Envelope(); - OperatorExportToGeoJson exporter = (OperatorExportToGeoJson) factory.getOperator(Operator.Type.ExportToGeoJson); - String result = exporter.execute(e); - assertEquals("{\"bbox\":null}", result); - } - - @Test - public void testEnvelopeGeometryEngine() { - Envelope e = new Envelope(); - e.setCoords(-180.0, -90.0, 180.0, 90.0); - String result = GeometryEngine.geometryToGeoJson(e); - assertEquals("{\"bbox\":[-180.0,-90.0,180.0,90.0]}", result); - } - - @Test - public void testGeometryCollection(){ - SpatialReference sr = SpatialReference.create(4326); - - StringBuilder geometrySb = new StringBuilder(); - geometrySb.append("{\"type\" : \"GeometryCollection\", \"geometries\" : ["); - - OGCPoint point = new OGCPoint(new Point(1.0, 1.0), sr); - assertEquals("{\"x\":1,\"y\":1,\"spatialReference\":{\"wkid\":4326}}", point.asJson()); - assertEquals("{\"type\":\"Point\",\"coordinates\":[1.0,1.0]}", point.asGeoJson()); - geometrySb.append(point.asGeoJson()).append(", "); - - OGCLineString line = new OGCLineString(new Polyline(new Point(1.0, 1.0), new Point(2.0, 2.0)), 0, sr); - assertEquals("{\"paths\":[[[1,1],[2,2]]],\"spatialReference\":{\"wkid\":4326}}", line.asJson()); - assertEquals("{\"type\":\"LineString\",\"coordinates\":[[1.0,1.0],[2.0,2.0]]}", line.asGeoJson()); - geometrySb.append(line.asGeoJson()).append(", "); - - Polygon p = new Polygon(); - p.startPath(1.0, 1.0); - p.lineTo(2.0, 2.0); - p.lineTo(3.0, 1.0); - p.lineTo(2.0, 0.0); - - OGCPolygon polygon = new OGCPolygon(p, sr); - assertEquals("{\"rings\":[[[1,1],[2,2],[3,1],[2,0],[1,1]]],\"spatialReference\":{\"wkid\":4326}}", - polygon.asJson()); - assertEquals("{\"type\":\"Polygon\",\"coordinates\":[[[1.0,1.0],[2.0,2.0],[3.0,1.0],[2.0,0.0],[1.0,1.0]]]}", - polygon.asGeoJson()); - geometrySb.append(polygon.asGeoJson()).append("]}"); - - List geoms = new ArrayList(3); - geoms.add(point);geoms.add(line);geoms.add(polygon); - OGCConcreteGeometryCollection collection = new OGCConcreteGeometryCollection(geoms, sr); - assertEquals(geometrySb.toString(), collection.asGeoJson()); - } - - @Test - public void testEmptyGeometryCollection(){ - SpatialReference sr = SpatialReference.create(4326); - OGCConcreteGeometryCollection collection = new OGCConcreteGeometryCollection(new ArrayList(), sr); - assertEquals("{\"type\" : \"GeometryCollection\", \"geometries\" : []}", collection.asGeoJson()); - } + OperatorFactoryLocal factory = OperatorFactoryLocal.getInstance(); + + @Override + protected void setUp() throws Exception { + super.setUp(); + } + + @Override + protected void tearDown() throws Exception { + super.tearDown(); + } + + @Test + public void testPoint() { + Point p = new Point(10.0, 20.0); + OperatorExportToGeoJson exporter = (OperatorExportToGeoJson) factory.getOperator(Operator.Type.ExportToGeoJson); + String result = exporter.execute(p); + assertEquals("{\"type\":\"Point\",\"coordinates\":[10,20]}", result); + } + + @Test + public void testEmptyPoint() { + Point p = new Point(); + OperatorExportToGeoJson exporter = (OperatorExportToGeoJson) factory.getOperator(Operator.Type.ExportToGeoJson); + String result = exporter.execute(p); + assertEquals("{\"type\":\"Point\",\"coordinates\":[]}", result); + } + + @Test + public void testPointGeometryEngine() { + Point p = new Point(10.0, 20.0); + String result = GeometryEngine.geometryToGeoJson(p); + assertEquals("{\"type\":\"Point\",\"coordinates\":[10,20]}", result); + } + + @Test + public void testOGCPoint() { + Point p = new Point(10.0, 20.0); + OGCGeometry ogcPoint = new OGCPoint(p, null); + String result = ogcPoint.asGeoJson(); + assertEquals("{\"type\":\"Point\",\"coordinates\":[10,20],\"crs\":null}", result); + } + + @Test + public void testMultiPoint() { + MultiPoint mp = new MultiPoint(); + mp.add(10.0, 20.0); + mp.add(20.0, 30.0); + OperatorExportToGeoJson exporter = (OperatorExportToGeoJson) factory.getOperator(Operator.Type.ExportToGeoJson); + String result = exporter.execute(mp); + assertEquals("{\"type\":\"MultiPoint\",\"coordinates\":[[10,20],[20,30]]}", result); + } + + @Test + public void testEmptyMultiPoint() { + MultiPoint mp = new MultiPoint(); + OperatorExportToGeoJson exporter = (OperatorExportToGeoJson) factory.getOperator(Operator.Type.ExportToGeoJson); + String result = exporter.execute(mp); + assertEquals("{\"type\":\"MultiPoint\",\"coordinates\":[]}", result); + } + + @Test + public void testMultiPointGeometryEngine() { + MultiPoint mp = new MultiPoint(); + mp.add(10.0, 20.0); + mp.add(20.0, 30.0); + String result = GeometryEngine.geometryToGeoJson(mp); + assertEquals("{\"type\":\"MultiPoint\",\"coordinates\":[[10,20],[20,30]]}", result); + } + + @Test + public void testOGCMultiPoint() { + MultiPoint mp = new MultiPoint(); + mp.add(10.0, 20.0); + mp.add(20.0, 30.0); + OGCMultiPoint ogcMultiPoint = new OGCMultiPoint(mp, null); + String result = ogcMultiPoint.asGeoJson(); + assertEquals("{\"type\":\"MultiPoint\",\"coordinates\":[[10,20],[20,30]],\"crs\":null}", result); + } + + @Test + public void testPolyline() { + Polyline p = new Polyline(); + p.startPath(100.0, 0.0); + p.lineTo(101.0, 0.0); + p.lineTo(101.0, 1.0); + p.lineTo(100.0, 1.0); + OperatorExportToGeoJson exporter = (OperatorExportToGeoJson) factory.getOperator(Operator.Type.ExportToGeoJson); + String result = exporter.execute(p); + assertEquals("{\"type\":\"LineString\",\"coordinates\":[[100,0],[101,0],[101,1],[100,1]]}", result); + } + + @Test + public void testEmptyPolyline() { + Polyline p = new Polyline(); + OperatorExportToGeoJson exporter = (OperatorExportToGeoJson) factory.getOperator(Operator.Type.ExportToGeoJson); + String result = exporter.execute(p); + assertEquals("{\"type\":\"LineString\",\"coordinates\":[]}", result); + } + + @Test + public void testPolylineGeometryEngine() { + Polyline p = new Polyline(); + p.startPath(100.0, 0.0); + p.lineTo(101.0, 0.0); + p.lineTo(101.0, 1.0); + p.lineTo(100.0, 1.0); + String result = GeometryEngine.geometryToGeoJson(p); + assertEquals("{\"type\":\"LineString\",\"coordinates\":[[100,0],[101,0],[101,1],[100,1]]}", result); + } + + @Test + public void testOGCLineString() { + Polyline p = new Polyline(); + p.startPath(100.0, 0.0); + p.lineTo(101.0, 0.0); + p.lineTo(101.0, 1.0); + p.lineTo(100.0, 1.0); + OGCLineString ogcLineString = new OGCLineString(p, 0, null); + String result = ogcLineString.asGeoJson(); + assertEquals("{\"type\":\"LineString\",\"coordinates\":[[100,0],[101,0],[101,1],[100,1]],\"crs\":null}", result); + } + + @Test + public void testPolygon() { + Polygon p = new Polygon(); + p.startPath(100.0, 0.0); + p.lineTo(101.0, 0.0); + p.lineTo(101.0, 1.0); + p.lineTo(100.0, 1.0); + p.closePathWithLine(); + OperatorExportToGeoJson exporter = (OperatorExportToGeoJson) factory.getOperator(Operator.Type.ExportToGeoJson); + String result = exporter.execute(p); + assertEquals("{\"type\":\"Polygon\",\"coordinates\":[[[100,0],[100,1],[101,1],[101,0],[100,0]]]}", result); + } + + @Test + public void testPolygonWithHole() { + Polygon p = new Polygon(); + + //exterior ring - has to be clockwise for Esri + p.startPath(100.0, 0.0); + p.lineTo(100.0, 1.0); + p.lineTo(101.0, 1.0); + p.lineTo(101.0, 0.0); + p.closePathWithLine(); + + //hole - counterclockwise for Esri + p.startPath(100.2, 0.2); + p.lineTo(100.8, 0.2); + p.lineTo(100.8, 0.8); + p.lineTo(100.2, 0.8); + p.closePathWithLine(); + + OperatorExportToGeoJson exporter = (OperatorExportToGeoJson) factory.getOperator(Operator.Type.ExportToGeoJson); + String result = exporter.execute(p); + assertEquals("{\"type\":\"Polygon\",\"coordinates\":[[[100,0],[101,0],[101,1],[100,1],[100,0]],[[100.2,0.2],[100.2,0.8],[100.8,0.8],[100.8,0.2],[100.2,0.2]]]}", result); + } + + @Test + public void testPolygonWithHoleReversed() { + Polygon p = new Polygon(); + + //exterior ring - has to be clockwise for Esri + p.startPath(100.0, 0.0); + p.lineTo(100.0, 1.0); + p.lineTo(101.0, 1.0); + p.lineTo(101.0, 0.0); + p.closePathWithLine(); + + //hole - counterclockwise for Esri + p.startPath(100.2, 0.2); + p.lineTo(100.8, 0.2); + p.lineTo(100.8, 0.8); + p.lineTo(100.2, 0.8); + p.closePathWithLine(); + + p.reverseAllPaths();//make it reversed. Exterior ring - ccw, hole - cw. + + OperatorExportToGeoJson exporter = (OperatorExportToGeoJson) factory.getOperator(Operator.Type.ExportToGeoJson); + String result = exporter.execute(p); + assertEquals("{\"type\":\"Polygon\",\"coordinates\":[[[100,0],[100,1],[101,1],[101,0],[100,0]],[[100.2,0.2],[100.8,0.2],[100.8,0.8],[100.2,0.8],[100.2,0.2]]]}", result); + } + + @Test + public void testMultiPolygon() throws IOException { + JsonFactory jsonFactory = new JsonFactory(); + + String geoJsonPolygon = "{\"type\":\"MultiPolygon\",\"coordinates\":[[[[-100,-100],[-100,100],[100,100],[100,-100],[-100,-100]],[[-90,-90],[90,90],[-90,90],[90,-90],[-90,-90]]],[[[-10.0,-10.0],[-10.0,10.0],[10.0,10.0],[10.0,-10.0],[-10.0,-10.0]]]]}"; + String esriJsonPolygon = "{\"rings\": [[[-100, -100], [-100, 100], [100, 100], [100, -100], [-100, -100]], [[-90, -90], [90, 90], [-90, 90], [90, -90], [-90, -90]], [[-10, -10], [-10, 10], [10, 10], [10, -10], [-10, -10]]]}"; + + JsonParser parser = jsonFactory.createJsonParser(esriJsonPolygon); + MapGeometry parsedPoly = GeometryEngine.jsonToGeometry(parser); + //MapGeometry parsedPoly = GeometryEngine.geometryFromGeoJson(jsonPolygon, 0, Geometry.Type.Polygon); + + Polygon poly = (Polygon) parsedPoly.getGeometry(); + + OperatorExportToGeoJson exporter = (OperatorExportToGeoJson) factory.getOperator(Operator.Type.ExportToGeoJson); + //String result = exporter.execute(parsedPoly.getGeometry()); + String result = exporter.execute(poly); + assertEquals("{\"type\":\"MultiPolygon\",\"coordinates\":[[[[-100,-100],[100,-100],[100,100],[-100,100],[-100,-100]],[[-90,-90],[90,-90],[-90,90],[90,90],[-90,-90]]],[[[-10,-10],[10,-10],[10,10],[-10,10],[-10,-10]]]]}", result); + } + + + @Deprecated + @Test + public void testEmptyPolygon() throws JSONException { + Polygon p = new Polygon(); + OperatorExportToGeoJson exporter = (OperatorExportToGeoJson) factory.getOperator(Operator.Type.ExportToGeoJson); + String result = exporter.execute(p); + assertEquals("{\"type\":\"Polygon\",\"coordinates\":[]}", result); + + MapGeometry imported = OperatorImportFromGeoJson.local().execute(0, Geometry.Type.Unknown, result, null); + assertTrue(imported.getGeometry().isEmpty()); + assertTrue(imported.getGeometry().getType() == Geometry.Type.Polygon); + } + + @Test + public void testPolygonGeometryEngine() { + Polygon p = new Polygon(); + p.startPath(100.0, 0.0); + p.lineTo(101.0, 0.0); + p.lineTo(101.0, 1.0); + p.lineTo(100.0, 1.0); + p.closePathWithLine(); + String result = GeometryEngine.geometryToGeoJson(p); + assertEquals("{\"type\":\"Polygon\",\"coordinates\":[[[100,0],[100,1],[101,1],[101,0],[100,0]]]}", result); + } + + @Test + public void testOGCPolygon() { + Polygon p = new Polygon(); + p.startPath(100.0, 0.0); + p.lineTo(101.0, 0.0); + p.lineTo(101.0, 1.0); + p.lineTo(100.0, 1.0); + p.closePathWithLine(); + OGCPolygon ogcPolygon = new OGCPolygon(p, null); + String result = ogcPolygon.asGeoJson(); + assertEquals("{\"type\":\"Polygon\",\"coordinates\":[[[100,0],[100,1],[101,1],[101,0],[100,0]]],\"crs\":null}", result); + } + + @Test + public void testPolygonWithHoleGeometryEngine() { + Polygon p = new Polygon(); + + p.startPath(100.0, 0.0);//clockwise exterior + p.lineTo(100.0, 1.0); + p.lineTo(101.0, 1.0); + p.lineTo(101.0, 0.0); + p.closePathWithLine(); + + p.startPath(100.2, 0.2);//counterclockwise hole + p.lineTo(100.8, 0.2); + p.lineTo(100.8, 0.8); + p.lineTo(100.2, 0.8); + p.closePathWithLine(); + + String result = GeometryEngine.geometryToGeoJson(p); + assertEquals("{\"type\":\"Polygon\",\"coordinates\":[[[100,0],[101,0],[101,1],[100,1],[100,0]],[[100.2,0.2],[100.2,0.8],[100.8,0.8],[100.8,0.2],[100.2,0.2]]]}", result); + } + + @Test + public void testPolylineWithTwoPaths() { + Polyline p = new Polyline(); + + p.startPath(100.0, 0.0); + p.lineTo(100.0, 1.0); + + p.startPath(100.2, 0.2); + p.lineTo(100.8, 0.2); + + String result = GeometryEngine.geometryToGeoJson(p); + assertEquals("{\"type\":\"MultiLineString\",\"coordinates\":[[[100,0],[100,1]],[[100.2,0.2],[100.8,0.2]]]}", result); + } + + @Test + public void testOGCPolygonWithHole() { + Polygon p = new Polygon(); + + p.startPath(100.0, 0.0); + p.lineTo(100.0, 1.0); + p.lineTo(101.0, 1.0); + p.lineTo(101.0, 0.0); + p.closePathWithLine(); + + p.startPath(100.2, 0.2); + p.lineTo(100.8, 0.2); + p.lineTo(100.8, 0.8); + p.lineTo(100.2, 0.8); + p.closePathWithLine(); + + OGCPolygon ogcPolygon = new OGCPolygon(p, null); + String result = ogcPolygon.asGeoJson(); + assertEquals("{\"type\":\"Polygon\",\"coordinates\":[[[100,0],[101,0],[101,1],[100,1],[100,0]],[[100.2,0.2],[100.2,0.8],[100.8,0.8],[100.8,0.2],[100.2,0.2]]],\"crs\":null}", result); + } + + @Test + public void testGeometryCollection() { + SpatialReference sr = SpatialReference.create(4326); + + StringBuilder geometrySb = new StringBuilder(); + geometrySb + .append("{\"type\" : \"GeometryCollection\", \"geometries\" : ["); + + OGCPoint point = new OGCPoint(new Point(1.0, 1.0), sr); + assertEquals("{\"x\":1,\"y\":1,\"spatialReference\":{\"wkid\":4326}}", + point.asJson()); + assertEquals( + "{\"type\":\"Point\",\"coordinates\":[1,1],\"crs\":{\"type\":\"name\",\"properties\":{\"name\":\"EPSG:4326\"}}}", + point.asGeoJson()); + geometrySb.append(point.asGeoJson()).append(", "); + + OGCLineString line = new OGCLineString(new Polyline( + new Point(1.0, 1.0), new Point(2.0, 2.0)), 0, sr); + assertEquals( + "{\"paths\":[[[1,1],[2,2]]],\"spatialReference\":{\"wkid\":4326}}", + line.asJson()); + assertEquals( + "{\"type\":\"LineString\",\"coordinates\":[[1,1],[2,2]],\"crs\":{\"type\":\"name\",\"properties\":{\"name\":\"EPSG:4326\"}}}", + line.asGeoJson()); + geometrySb.append(line.asGeoJson()).append(", "); + + Polygon p = new Polygon(); + p.startPath(1.0, 1.0); + p.lineTo(2.0, 2.0); + p.lineTo(3.0, 1.0); + p.lineTo(2.0, 0.0); + + OGCPolygon polygon = new OGCPolygon(p, sr); + assertEquals( + "{\"rings\":[[[1,1],[2,2],[3,1],[2,0],[1,1]]],\"spatialReference\":{\"wkid\":4326}}", + polygon.asJson()); + assertEquals( + "{\"type\":\"Polygon\",\"coordinates\":[[[1,1],[2,0],[3,1],[2,2],[1,1]]],\"crs\":{\"type\":\"name\",\"properties\":{\"name\":\"EPSG:4326\"}}}", + polygon.asGeoJson()); + geometrySb.append(polygon.asGeoJson()).append("]}"); + + List geoms = new ArrayList(3); + geoms.add(point); + geoms.add(line); + geoms.add(polygon); + OGCConcreteGeometryCollection collection = new OGCConcreteGeometryCollection( + geoms, sr); + String s2 = collection.asGeoJson(); + + JSONObject json = null; + boolean valid = false; + try { + json = new JSONObject(s2); + valid = true; + } catch (Exception e) { + } + + assertTrue(valid); + + assertEquals("{\"type\":\"GeometryCollection\",\"geometries\":[{\"type\":\"Point\",\"coordinates\":[1,1]},{\"type\":\"LineString\",\"coordinates\":[[1,1],[2,2]]},{\"type\":\"Polygon\",\"coordinates\":[[[1,1],[2,0],[3,1],[2,2],[1,1]]]}],\"crs\":{\"type\":\"name\",\"properties\":{\"name\":\"EPSG:4326\"}}}", collection.asGeoJson()); + } + + @Test + public void testEmptyGeometryCollection() { + SpatialReference sr = SpatialReference.create(4326); + OGCConcreteGeometryCollection collection = new OGCConcreteGeometryCollection( + new ArrayList(), sr); + String s2 = collection.asGeoJson(); + assertEquals( + "{\"type\":\"GeometryCollection\",\"geometries\":[],\"crs\":{\"type\":\"name\",\"properties\":{\"name\":\"EPSG:4326\"}}}", + collection.asGeoJson()); + } + + //Envelope is exported as a polygon (we don't support bbox, as it is not a GeoJson geometry, but simply a field)! + @Test + public void testEnvelope() { + Envelope e = new Envelope(); + e.setCoords(-180.0, -90.0, 180.0, 90.0); + String result = OperatorExportToGeoJson.local().execute(e); + assertEquals("{\"type\":\"Polygon\",\"coordinates\":[[[-180,-90],[180,-90],[180,90],[-180,90],[-180,-90]]]}", result); + } + + @Test + public void testEmptyEnvelope() { + Envelope e = new Envelope(); + String result = OperatorExportToGeoJson.local().execute(e); + assertEquals("{\"type\":\"Polygon\",\"coordinates\":[]}", result); + } + + @Test + public void testEnvelopeGeometryEngine() { + Envelope e = new Envelope(); + e.setCoords(-180.0, -90.0, 180.0, 90.0); + String result = GeometryEngine.geometryToGeoJson(e); + assertEquals("{\"type\":\"Polygon\",\"coordinates\":[[[-180,-90],[180,-90],[180,90],[-180,90],[-180,-90]]]}", result); + } + + @Test + public void testOldCRS() throws JSONException { + String inputStr = "{\"type\":\"Polygon\",\"coordinates\":[[[-180,-90],[180,-90],[180,90],[-180,90],[-180,-90]]], \"crs\":\"EPSG:4267\"}"; + MapGeometry mg = OperatorImportFromGeoJson.local().execute(GeoJsonImportFlags.geoJsonImportDefaults, Geometry.Type.Unknown, inputStr, null); + String result = GeometryEngine.geometryToGeoJson(mg.getSpatialReference(), mg.getGeometry()); + assertEquals("{\"type\":\"Polygon\",\"coordinates\":[[[-180,-90],[180,-90],[180,90],[-180,90],[-180,-90]]],\"crs\":{\"type\":\"name\",\"properties\":{\"name\":\"EPSG:4267\"}}}", result); + } + + // bbox is not supported anymore. + // @Test + // public void testEnvelope() { + // Envelope e = new Envelope(); + // e.setCoords(-180.0, -90.0, 180.0, 90.0); + // OperatorExportToGeoJson exporter = (OperatorExportToGeoJson) factory.getOperator(Operator.Type.ExportToGeoJson); + // String result = exporter.execute(e); + // assertEquals("{\"bbox\":[-180.0,-90.0,180.0,90.0]}", result); + // } + // + // @Test + // public void testEmptyEnvelope() { + // Envelope e = new Envelope(); + // OperatorExportToGeoJson exporter = (OperatorExportToGeoJson) factory.getOperator(Operator.Type.ExportToGeoJson); + // String result = exporter.execute(e); + // assertEquals("{\"bbox\":null}", result); + // } + // + // @Test + // public void testEnvelopeGeometryEngine() { + // Envelope e = new Envelope(); + // e.setCoords(-180.0, -90.0, 180.0, 90.0); + // String result = GeometryEngine.geometryToGeoJson(e); + // assertEquals("{\"bbox\":[-180.0,-90.0,180.0,90.0]}", result); + // } + } diff --git a/src/test/java/com/esri/core/geometry/TestImportExport.java b/src/test/java/com/esri/core/geometry/TestImportExport.java index 99adee7a..1138c082 100644 --- a/src/test/java/com/esri/core/geometry/TestImportExport.java +++ b/src/test/java/com/esri/core/geometry/TestImportExport.java @@ -2,11 +2,14 @@ import java.nio.ByteBuffer; import java.nio.ByteOrder; + import junit.framework.TestCase; import org.json.JSONException; +import org.json.JSONObject; import org.junit.Test; public class TestImportExport extends TestCase { + @Override protected void setUp() throws Exception { super.setUp(); @@ -19,33 +22,35 @@ protected void tearDown() throws Exception { @Test public static void testImportExportShapePolygon() { - OperatorExportToESRIShape exporterShape = (OperatorExportToESRIShape) OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.ExportToESRIShape); - OperatorImportFromESRIShape importerShape = (OperatorImportFromESRIShape) OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.ImportFromESRIShape); +// { +// String s = "MULTIPOLYGON (((-1.4337158203098852 53.42590083930004, -1.4346462383651897 53.42590083930004, -1.4349713164114632 53.42426406667512, -1.4344808816770183 53.42391134176576, -1.4337158203098852 53.424339319373516, -1.4337158203098852 53.42590083930004, -1.4282226562499147 53.42590083930004, -1.4282226562499147 53.42262754610009, -1.423659941537096 53.42262754610009, -1.4227294921872726 53.42418897437618, -1.4199829101572732 53.42265258737483, -1.4172363281222147 53.42418897437334, -1.4144897460898278 53.42265258737625, -1.4144897460898278 53.42099079900008, -1.4117431640598568 53.42099079712516, -1.4117431640598568 53.41849780932388, -1.4112778948070286 53.41771711805022, -1.4114404909237805 53.41689867267529, -1.411277890108579 53.416080187950215, -1.4117431640598568 53.4152995338453, -1.4117431657531654 53.40953184824072, -1.41723632610001 53.40953184402311, -1.4172363281199125 53.406257299700044, -1.4227294921899158 53.406257299700044, -1.4227294921899158 53.40789459668797, -1.4254760767598498 53.40789460061099, -1.4262193642339867 53.40914148401417, -1.4273828468095076 53.409531853100034, -1.4337158203098852 53.409531790075235, -1.4337158203098852 53.41280609140024, -1.4392089843723568 53.41280609140024, -1.439208984371362 53.41608014067522, -1.441160015802268 53.41935368587538, -1.4427511170075604 53.41935368587538, -1.4447021484373863 53.42099064750012, -1.4501953124999432 53.42099064750012, -1.4501953124999432 53.43214683850347, -1.4513643355446106 53.434108816701794, -1.4502702625278232 53.43636597733034, -1.4494587195580948 53.437354845300334, -1.4431075935937656 53.437354845300334, -1.4372459179209045 53.43244635455021, -1.433996276212838 53.42917388040006, -1.4337158203098852 53.42917388040006, -1.4337158203098852 53.42590083930004)))"; +// Geometry g = OperatorImportFromWkt.local().execute(0, Geometry.Type.Unknown, s, null); +// boolean result1 = OperatorSimplify.local().isSimpleAsFeature(g, null, null); +// boolean result2 = OperatorSimplifyOGC.local().isSimpleOGC(g, null, true, null, null); +// Geometry simple = OperatorSimplifyOGC.local().execute(g, null, true, null); +// OperatorFactoryLocal.saveToWKTFileDbg("c:/temp/simplifiedeeee", simple, null); +// int i = 0; +// } + OperatorExportToESRIShape exporterShape = (OperatorExportToESRIShape) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.ExportToESRIShape); + OperatorImportFromESRIShape importerShape = (OperatorImportFromESRIShape) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.ImportFromESRIShape); Polygon polygon = makePolygon(); byte[] esriShape = GeometryEngine.geometryToEsriShape(polygon); - Geometry imported = GeometryEngine.geometryFromEsriShape(esriShape, - Geometry.Type.Unknown); + Geometry imported = GeometryEngine.geometryFromEsriShape(esriShape, Geometry.Type.Unknown); TestCommonMethods.compareGeometryContent((MultiPath) imported, polygon); // Test Import Polygon from Polygon ByteBuffer polygonShapeBuffer = exporterShape.execute(0, polygon); - Geometry polygonShapeGeometry = importerShape.execute(0, - Geometry.Type.Polygon, polygonShapeBuffer); + Geometry polygonShapeGeometry = importerShape.execute(0, Geometry.Type.Polygon, polygonShapeBuffer); - TestCommonMethods.compareGeometryContent( - (MultiPath) polygonShapeGeometry, polygon); + TestCommonMethods.compareGeometryContent((MultiPath) polygonShapeGeometry, polygon); // Test Import Envelope from Polygon - Geometry envelopeShapeGeometry = importerShape.execute(0, - Geometry.Type.Envelope, polygonShapeBuffer); + Geometry envelopeShapeGeometry = importerShape.execute(0, Geometry.Type.Envelope, polygonShapeBuffer); Envelope envelope = (Envelope) envelopeShapeGeometry; - @SuppressWarnings("unused") - Envelope env = new Envelope(), otherenv = new Envelope(); + @SuppressWarnings("unused") Envelope env = new Envelope(), otherenv = new Envelope(); polygon.queryEnvelope(otherenv); assertTrue(envelope.getXMin() == otherenv.getXMin()); assertTrue(envelope.getXMax() == otherenv.getXMax()); @@ -61,25 +66,20 @@ public static void testImportExportShapePolygon() { @Test public static void testImportExportShapePolyline() { - OperatorExportToESRIShape exporterShape = (OperatorExportToESRIShape) OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.ExportToESRIShape); - OperatorImportFromESRIShape importerShape = (OperatorImportFromESRIShape) OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.ImportFromESRIShape); + OperatorExportToESRIShape exporterShape = (OperatorExportToESRIShape) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.ExportToESRIShape); + OperatorImportFromESRIShape importerShape = (OperatorImportFromESRIShape) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.ImportFromESRIShape); Polyline polyline = makePolyline(); // Test Import Polyline from Polyline ByteBuffer polylineShapeBuffer = exporterShape.execute(0, polyline); - Geometry polylineShapeGeometry = importerShape.execute(0, - Geometry.Type.Polyline, polylineShapeBuffer); + Geometry polylineShapeGeometry = importerShape.execute(0, Geometry.Type.Polyline, polylineShapeBuffer); // TODO test this - TestCommonMethods.compareGeometryContent( - (MultiPath) polylineShapeGeometry, polyline); + TestCommonMethods.compareGeometryContent((MultiPath) polylineShapeGeometry, polyline); // Test Import Envelope from Polyline; - Geometry envelopeShapeGeometry = importerShape.execute(0, - Geometry.Type.Envelope, polylineShapeBuffer); + Geometry envelopeShapeGeometry = importerShape.execute(0, Geometry.Type.Envelope, polylineShapeBuffer); Envelope envelope = (Envelope) envelopeShapeGeometry; Envelope env = new Envelope(), otherenv = new Envelope(); @@ -92,32 +92,26 @@ public static void testImportExportShapePolyline() { Envelope1D interval, otherinterval; interval = envelope.queryInterval(VertexDescription.Semantics.Z, 0); - otherinterval = polyline - .queryInterval(VertexDescription.Semantics.Z, 0); + otherinterval = polyline.queryInterval(VertexDescription.Semantics.Z, 0); assertTrue(interval.vmin == otherinterval.vmin); assertTrue(interval.vmax == otherinterval.vmax); } @Test public static void testImportExportShapeMultiPoint() { - OperatorExportToESRIShape exporterShape = (OperatorExportToESRIShape) OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.ExportToESRIShape); - OperatorImportFromESRIShape importerShape = (OperatorImportFromESRIShape) OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.ImportFromESRIShape); + OperatorExportToESRIShape exporterShape = (OperatorExportToESRIShape) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.ExportToESRIShape); + OperatorImportFromESRIShape importerShape = (OperatorImportFromESRIShape) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.ImportFromESRIShape); MultiPoint multipoint = makeMultiPoint(); // Test Import MultiPoint from MultiPoint ByteBuffer multipointShapeBuffer = exporterShape.execute(0, multipoint); - MultiPoint multipointShapeGeometry = (MultiPoint) importerShape - .execute(0, Geometry.Type.MultiPoint, multipointShapeBuffer); + MultiPoint multipointShapeGeometry = (MultiPoint) importerShape.execute(0, Geometry.Type.MultiPoint, multipointShapeBuffer); - TestCommonMethods.compareGeometryContent( - (MultiPoint) multipointShapeGeometry, multipoint); + TestCommonMethods.compareGeometryContent((MultiPoint) multipointShapeGeometry, multipoint); // Test Import Envelope from MultiPoint - Geometry envelopeShapeGeometry = importerShape.execute(0, - Geometry.Type.Envelope, multipointShapeBuffer); + Geometry envelopeShapeGeometry = importerShape.execute(0, Geometry.Type.Envelope, multipointShapeBuffer); Envelope envelope = (Envelope) envelopeShapeGeometry; Envelope env = new Envelope(), otherenv = new Envelope(); @@ -130,32 +124,27 @@ public static void testImportExportShapeMultiPoint() { Envelope1D interval, otherinterval; interval = envelope.queryInterval(VertexDescription.Semantics.Z, 0); - otherinterval = multipoint.queryInterval(VertexDescription.Semantics.Z, - 0); + otherinterval = multipoint.queryInterval(VertexDescription.Semantics.Z, 0); assertTrue(interval.vmin == otherinterval.vmin); assertTrue(interval.vmax == otherinterval.vmax); interval = envelope.queryInterval(VertexDescription.Semantics.ID, 0); - otherinterval = multipoint.queryInterval( - VertexDescription.Semantics.ID, 0); + otherinterval = multipoint.queryInterval(VertexDescription.Semantics.ID, 0); assertTrue(interval.vmin == otherinterval.vmin); assertTrue(interval.vmax == otherinterval.vmax); } @Test public static void testImportExportShapePoint() { - OperatorExportToESRIShape exporterShape = (OperatorExportToESRIShape) OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.ExportToESRIShape); - OperatorImportFromESRIShape importerShape = (OperatorImportFromESRIShape) OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.ImportFromESRIShape); + OperatorExportToESRIShape exporterShape = (OperatorExportToESRIShape) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.ExportToESRIShape); + OperatorImportFromESRIShape importerShape = (OperatorImportFromESRIShape) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.ImportFromESRIShape); // Point Point point = makePoint(); // Test Import Point from Point ByteBuffer pointShapeBuffer = exporterShape.execute(0, point); - Point pointShapeGeometry = (Point) importerShape.execute(0, - Geometry.Type.Point, pointShapeBuffer); + Point pointShapeGeometry = (Point) importerShape.execute(0, Geometry.Type.Point, pointShapeBuffer); double x1 = point.getX(); double x2 = pointShapeGeometry.getX(); @@ -178,29 +167,24 @@ public static void testImportExportShapePoint() { assertTrue(id1 == id2); // Test Import Multipoint from Point - MultiPoint multipointShapeGeometry = (MultiPoint) importerShape - .execute(0, Geometry.Type.MultiPoint, pointShapeBuffer); + MultiPoint multipointShapeGeometry = (MultiPoint) importerShape.execute(0, Geometry.Type.MultiPoint, pointShapeBuffer); Point point2d = multipointShapeGeometry.getPoint(0); assertTrue(x1 == point2d.getX() && y1 == point2d.getY()); int pointCount = multipointShapeGeometry.getPointCount(); assertTrue(pointCount == 1); - z2 = multipointShapeGeometry.getAttributeAsDbl( - VertexDescription.Semantics.Z, 0, 0); + z2 = multipointShapeGeometry.getAttributeAsDbl(VertexDescription.Semantics.Z, 0, 0); assertTrue(z1 == z2); - m2 = multipointShapeGeometry.getAttributeAsDbl( - VertexDescription.Semantics.M, 0, 0); + m2 = multipointShapeGeometry.getAttributeAsDbl(VertexDescription.Semantics.M, 0, 0); assertTrue(m1 == m2); - id2 = multipointShapeGeometry.getAttributeAsInt( - VertexDescription.Semantics.ID, 0, 0); + id2 = multipointShapeGeometry.getAttributeAsInt(VertexDescription.Semantics.ID, 0, 0); assertTrue(id1 == id2); // Test Import Envelope from Point - Geometry envelopeShapeGeometry = importerShape.execute(0, - Geometry.Type.Envelope, pointShapeBuffer); + Geometry envelopeShapeGeometry = importerShape.execute(0, Geometry.Type.Envelope, pointShapeBuffer); Envelope envelope = (Envelope) envelopeShapeGeometry; Envelope env = new Envelope(), otherenv = new Envelope(); @@ -225,17 +209,14 @@ public static void testImportExportShapePoint() { @Test public static void testImportExportShapeEnvelope() { - OperatorExportToESRIShape exporterShape = (OperatorExportToESRIShape) OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.ExportToESRIShape); - OperatorImportFromESRIShape importerShape = (OperatorImportFromESRIShape) OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.ImportFromESRIShape); + OperatorExportToESRIShape exporterShape = (OperatorExportToESRIShape) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.ExportToESRIShape); + OperatorImportFromESRIShape importerShape = (OperatorImportFromESRIShape) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.ImportFromESRIShape); // Test Export Envelope to Polygon Envelope envelope = makeEnvelope(); ByteBuffer polygonShapeBuffer = exporterShape.execute(0, envelope); - Polygon polygon = (Polygon) importerShape.execute(0, - Geometry.Type.Polygon, polygonShapeBuffer); + Polygon polygon = (Polygon) importerShape.execute(0, Geometry.Type.Polygon, polygonShapeBuffer); int pointCount = polygon.getPointCount(); assertTrue(pointCount == 4); @@ -245,26 +226,21 @@ public static void testImportExportShapeEnvelope() { // interval = envelope.queryInterval(VertexDescription.Semantics.Z, 0); Point point3d; point3d = polygon.getPoint(0); - assertTrue(point3d.getX() == env.getXMin() - && point3d.getY() == env.getYMin());// && point3d.z == - // interval.vmin); + assertTrue(point3d.getX() == env.getXMin() && point3d.getY() == env.getYMin());// && point3d.z == + // interval.vmin); point3d = polygon.getPoint(1); - assertTrue(point3d.getX() == env.getXMin() - && point3d.getY() == env.getYMax());// && point3d.z == - // interval.vmax); + assertTrue(point3d.getX() == env.getXMin() && point3d.getY() == env.getYMax());// && point3d.z == + // interval.vmax); point3d = polygon.getPoint(2); - assertTrue(point3d.getX() == env.getXMax() - && point3d.getY() == env.getYMax());// && point3d.z == - // interval.vmin); + assertTrue(point3d.getX() == env.getXMax() && point3d.getY() == env.getYMax());// && point3d.z == + // interval.vmin); point3d = polygon.getPoint(3); - assertTrue(point3d.getX() == env.getXMax() - && point3d.getY() == env.getYMin());// && point3d.z == - // interval.vmax); + assertTrue(point3d.getX() == env.getXMax() && point3d.getY() == env.getYMin());// && point3d.z == + // interval.vmax); Envelope1D interval; interval = envelope.queryInterval(VertexDescription.Semantics.M, 0); - double m = polygon.getAttributeAsDbl(VertexDescription.Semantics.M, 0, - 0); + double m = polygon.getAttributeAsDbl(VertexDescription.Semantics.M, 0, 0); assertTrue(m == interval.vmin); m = polygon.getAttributeAsDbl(VertexDescription.Semantics.M, 1, 0); assertTrue(m == interval.vmax); @@ -274,8 +250,7 @@ public static void testImportExportShapeEnvelope() { assertTrue(m == interval.vmax); interval = envelope.queryInterval(VertexDescription.Semantics.ID, 0); - double id = polygon.getAttributeAsDbl(VertexDescription.Semantics.ID, - 0, 0); + double id = polygon.getAttributeAsDbl(VertexDescription.Semantics.ID, 0, 0); assertTrue(id == interval.vmin); id = polygon.getAttributeAsDbl(VertexDescription.Semantics.ID, 1, 0); assertTrue(id == interval.vmax); @@ -287,12 +262,10 @@ public static void testImportExportShapeEnvelope() { @Test public static void testImportExportWkbGeometryCollection() { - OperatorImportFromWkb importerWKB = (OperatorImportFromWkb) OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.ImportFromWkb); + OperatorImportFromWkb importerWKB = (OperatorImportFromWkb) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.ImportFromWkb); int offset = 0; - ByteBuffer wkbBuffer = ByteBuffer.allocate(600).order( - ByteOrder.nativeOrder()); + ByteBuffer wkbBuffer = ByteBuffer.allocate(600).order(ByteOrder.nativeOrder()); wkbBuffer.put(offset, (byte) WkbByteOrder.wkbNDR); offset += 1; // byte order wkbBuffer.putInt(offset, WkbGeometryType.wkbGeometryCollection); @@ -342,7 +315,7 @@ public static void testImportExportWkbGeometryCollection() { wkbBuffer.putInt(offset, WkbGeometryType.wkbGeometryCollection); offset += 4; wkbBuffer.putInt(offset, 0); // 0 geometries, for empty - // geometrycollection + // geometrycollection offset += 4; wkbBuffer.put(offset, (byte) WkbByteOrder.wkbNDR); offset += 1; @@ -368,8 +341,7 @@ public static void testImportExportWkbGeometryCollection() { offset += 8; // "GeometryCollection( Point (0 0), GeometryCollection( LineString empty, Polygon empty, MultiPolygon empty, MultiLineString empty, MultiPoint empty ), Point (13 17) )"; - OGCStructure structure = importerWKB.executeOGC(0, wkbBuffer, null).m_structures - .get(0); + OGCStructure structure = importerWKB.executeOGC(0, wkbBuffer, null).m_structures.get(0); assertTrue(structure.m_type == 7); assertTrue(structure.m_structures.get(0).m_type == 1); @@ -395,17 +367,13 @@ public static void testImportExportWkbGeometryCollection() { @Test public static void testImportExportWKBPolygon() { - OperatorExportToWkb exporterWKB = (OperatorExportToWkb) OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.ExportToWkb); - OperatorExportToWkt exporterWKT = (OperatorExportToWkt) OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.ExportToWkt); - OperatorImportFromWkb importerWKB = (OperatorImportFromWkb) OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.ImportFromWkb); + OperatorExportToWkb exporterWKB = (OperatorExportToWkb) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.ExportToWkb); + OperatorExportToWkt exporterWKT = (OperatorExportToWkt) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.ExportToWkt); + OperatorImportFromWkb importerWKB = (OperatorImportFromWkb) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.ImportFromWkb); // Test Import Polygon with bad rings int offset = 0; - ByteBuffer wkbBuffer = ByteBuffer.allocate(500).order( - ByteOrder.nativeOrder()); + ByteBuffer wkbBuffer = ByteBuffer.allocate(500).order(ByteOrder.nativeOrder()); wkbBuffer.put(offset, (byte) WkbByteOrder.wkbNDR); offset += 1; // byte order wkbBuffer.putInt(offset, WkbGeometryType.wkbPolygon); @@ -505,17 +473,13 @@ public static void testImportExportWKBPolygon() { wkbBuffer.putDouble(offset, 67.0); offset += 8; // y - Geometry p = importerWKB.execute(0, Geometry.Type.Polygon, wkbBuffer, - null); + Geometry p = importerWKB.execute(0, Geometry.Type.Polygon, wkbBuffer, null); int pc = ((Polygon) p).getPathCount(); String wktString = exporterWKT.execute(0, p, null); - assertTrue(wktString - .equals("MULTIPOLYGON (((0 0, 10 10, 0 10, 0 0), (36 17, 36 17, 36 17), (19 19, -19 -19, 19 19), (23 88, 83 87, 59 79, 13 43, 23 88), (23 88, 67 79, 88 43, 23 88), (23 88, 67 88, 88 43, 23 88), (23 67, 43 67, 23 67)))")); + assertTrue(wktString.equals("MULTIPOLYGON (((0 0, 10 10, 0 10, 0 0), (36 17, 36 17, 36 17), (19 19, -19 -19, 19 19), (23 88, 83 87, 59 79, 13 43, 23 88), (23 88, 67 79, 88 43, 23 88), (23 88, 67 88, 88 43, 23 88), (23 67, 43 67, 23 67)))")); - wktString = exporterWKT.execute(WktExportFlags.wktExportPolygon, p, - null); - assertTrue(wktString - .equals("POLYGON ((0 0, 10 10, 0 10, 0 0), (36 17, 36 17, 36 17), (19 19, -19 -19, 19 19), (23 88, 83 87, 59 79, 13 43, 23 88), (23 88, 67 79, 88 43, 23 88), (23 88, 67 88, 88 43, 23 88), (23 67, 43 67, 23 67))")); + wktString = exporterWKT.execute(WktExportFlags.wktExportPolygon, p, null); + assertTrue(wktString.equals("POLYGON ((0 0, 10 10, 0 10, 0 0), (36 17, 36 17, 36 17), (19 19, -19 -19, 19 19), (23 88, 83 87, 59 79, 13 43, 23 88), (23 88, 67 79, 88 43, 23 88), (23 88, 67 88, 88 43, 23 88), (23 67, 43 67, 23 67))")); Polygon polygon = makePolygon(); @@ -523,48 +487,37 @@ public static void testImportExportWKBPolygon() { ByteBuffer polygonWKBBuffer = exporterWKB.execute(0, polygon, null); int wkbType = polygonWKBBuffer.getInt(1); assertTrue(wkbType == WkbGeometryType.wkbMultiPolygonZM); - Geometry polygonWKBGeometry = importerWKB.execute(0, - Geometry.Type.Polygon, polygonWKBBuffer, null); - TestCommonMethods.compareGeometryContent( - (MultiVertexGeometry) polygonWKBGeometry, polygon); + Geometry polygonWKBGeometry = importerWKB.execute(0, Geometry.Type.Polygon, polygonWKBBuffer, null); + TestCommonMethods.compareGeometryContent((MultiVertexGeometry) polygonWKBGeometry, polygon); // Test WKB_export_multi_polygon on nonempty single part polygon Polygon polygon2 = makePolygon2(); assertTrue(polygon2.getPathCount() == 1); - polygonWKBBuffer = exporterWKB.execute( - WkbExportFlags.wkbExportMultiPolygon, polygon2, null); - polygonWKBGeometry = importerWKB.execute(0, Geometry.Type.Polygon, - polygonWKBBuffer, null); - TestCommonMethods.compareGeometryContent( - (MultiVertexGeometry) polygonWKBGeometry, polygon2); + polygonWKBBuffer = exporterWKB.execute(WkbExportFlags.wkbExportMultiPolygon, polygon2, null); + polygonWKBGeometry = importerWKB.execute(0, Geometry.Type.Polygon, polygonWKBBuffer, null); + TestCommonMethods.compareGeometryContent((MultiVertexGeometry) polygonWKBGeometry, polygon2); wkbType = polygonWKBBuffer.getInt(1); assertTrue(wkbType == WkbGeometryType.wkbMultiPolygonZM); // Test WKB_export_polygon on nonempty single part polygon assertTrue(polygon2.getPathCount() == 1); - polygonWKBBuffer = exporterWKB.execute(WkbExportFlags.wkbExportPolygon, - polygon2, null); - polygonWKBGeometry = importerWKB.execute(0, Geometry.Type.Polygon, - polygonWKBBuffer, null); - TestCommonMethods.compareGeometryContent( - (MultiVertexGeometry) polygonWKBGeometry, polygon2); + polygonWKBBuffer = exporterWKB.execute(WkbExportFlags.wkbExportPolygon, polygon2, null); + polygonWKBGeometry = importerWKB.execute(0, Geometry.Type.Polygon, polygonWKBBuffer, null); + TestCommonMethods.compareGeometryContent((MultiVertexGeometry) polygonWKBGeometry, polygon2); wkbType = polygonWKBBuffer.getInt(1); assertTrue(wkbType == WkbGeometryType.wkbPolygonZM); // Test WKB_export_polygon on empty polygon Polygon polygon3 = new Polygon(); - polygonWKBBuffer = exporterWKB.execute(WkbExportFlags.wkbExportPolygon, - polygon3, null); - polygonWKBGeometry = importerWKB.execute(0, Geometry.Type.Polygon, - polygonWKBBuffer, null); + polygonWKBBuffer = exporterWKB.execute(WkbExportFlags.wkbExportPolygon, polygon3, null); + polygonWKBGeometry = importerWKB.execute(0, Geometry.Type.Polygon, polygonWKBBuffer, null); assertTrue(polygonWKBGeometry.isEmpty() == true); wkbType = polygonWKBBuffer.getInt(1); assertTrue(wkbType == WkbGeometryType.wkbPolygon); // Test WKB_export_defaults on empty polygon polygonWKBBuffer = exporterWKB.execute(0, polygon3, null); - polygonWKBGeometry = importerWKB.execute(0, Geometry.Type.Polygon, - polygonWKBBuffer, null); + polygonWKBGeometry = importerWKB.execute(0, Geometry.Type.Polygon, polygonWKBBuffer, null); assertTrue(polygonWKBGeometry.isEmpty() == true); wkbType = polygonWKBBuffer.getInt(1); assertTrue(wkbType == WkbGeometryType.wkbMultiPolygon); @@ -572,18 +525,14 @@ public static void testImportExportWKBPolygon() { @Test public static void testImportExportWKBPolyline() { - OperatorExportToWkb exporterWKB = (OperatorExportToWkb) OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.ExportToWkb); - OperatorExportToWkt exporterWKT = (OperatorExportToWkt) OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.ExportToWkt); - OperatorImportFromWkb importerWKB = (OperatorImportFromWkb) OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.ImportFromWkb); + OperatorExportToWkb exporterWKB = (OperatorExportToWkb) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.ExportToWkb); + OperatorExportToWkt exporterWKT = (OperatorExportToWkt) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.ExportToWkt); + OperatorImportFromWkb importerWKB = (OperatorImportFromWkb) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.ImportFromWkb); // Test Import Polyline with bad paths (i.e. paths with one point or // zero points) int offset = 0; - ByteBuffer wkbBuffer = ByteBuffer.allocate(500).order( - ByteOrder.nativeOrder()); + ByteBuffer wkbBuffer = ByteBuffer.allocate(500).order(ByteOrder.nativeOrder()); wkbBuffer.put(offset, (byte) WkbByteOrder.wkbNDR); offset += 1; // byte order wkbBuffer.putInt(offset, WkbGeometryType.wkbMultiLineString); @@ -635,16 +584,14 @@ public static void testImportExportWKBPolyline() { wkbBuffer.putDouble(offset, 88); offset += 8; // y - Polyline p = (Polyline) (importerWKB.execute(0, Geometry.Type.Polyline, - wkbBuffer, null)); + Polyline p = (Polyline) (importerWKB.execute(0, Geometry.Type.Polyline, wkbBuffer, null)); int pc = p.getPointCount(); int pac = p.getPathCount(); assertTrue(p.getPointCount() == 7); assertTrue(p.getPathCount() == 3); String wktString = exporterWKT.execute(0, p, null); - assertTrue(wktString - .equals("MULTILINESTRING ((36 17, 36 17), (19 19, 19 19), (88 29, 13 43, 59 88))")); + assertTrue(wktString.equals("MULTILINESTRING ((36 17, 36 17), (19 19, 19 19), (88 29, 13 43, 59 88))")); Polyline polyline = makePolyline(); polyline.dropAttribute(VertexDescription.Semantics.ID); @@ -653,48 +600,37 @@ public static void testImportExportWKBPolyline() { ByteBuffer polylineWKBBuffer = exporterWKB.execute(0, polyline, null); int wkbType = polylineWKBBuffer.getInt(1); assertTrue(wkbType == WkbGeometryType.wkbMultiLineStringZM); - Geometry polylineWKBGeometry = importerWKB.execute(0, - Geometry.Type.Polyline, polylineWKBBuffer, null); - TestCommonMethods.compareGeometryContent( - (MultiVertexGeometry) polylineWKBGeometry, polyline); + Geometry polylineWKBGeometry = importerWKB.execute(0, Geometry.Type.Polyline, polylineWKBBuffer, null); + TestCommonMethods.compareGeometryContent((MultiVertexGeometry) polylineWKBGeometry, polyline); // Test wkbExportMultiPolyline on nonempty single part polyline Polyline polyline2 = makePolyline2(); assertTrue(polyline2.getPathCount() == 1); - polylineWKBBuffer = exporterWKB.execute( - WkbExportFlags.wkbExportMultiLineString, polyline2, null); - polylineWKBGeometry = importerWKB.execute(0, Geometry.Type.Polyline, - polylineWKBBuffer, null); - TestCommonMethods.compareGeometryContent( - (MultiVertexGeometry) polylineWKBGeometry, polyline2); + polylineWKBBuffer = exporterWKB.execute(WkbExportFlags.wkbExportMultiLineString, polyline2, null); + polylineWKBGeometry = importerWKB.execute(0, Geometry.Type.Polyline, polylineWKBBuffer, null); + TestCommonMethods.compareGeometryContent((MultiVertexGeometry) polylineWKBGeometry, polyline2); wkbType = polylineWKBBuffer.getInt(1); assertTrue(wkbType == WkbGeometryType.wkbMultiLineStringZM); // Test wkbExportPolyline on nonempty single part polyline assertTrue(polyline2.getPathCount() == 1); - polylineWKBBuffer = exporterWKB.execute( - WkbExportFlags.wkbExportLineString, polyline2, null); - polylineWKBGeometry = importerWKB.execute(0, Geometry.Type.Polyline, - polylineWKBBuffer, null); - TestCommonMethods.compareGeometryContent( - (MultiVertexGeometry) polylineWKBGeometry, polyline2); + polylineWKBBuffer = exporterWKB.execute(WkbExportFlags.wkbExportLineString, polyline2, null); + polylineWKBGeometry = importerWKB.execute(0, Geometry.Type.Polyline, polylineWKBBuffer, null); + TestCommonMethods.compareGeometryContent((MultiVertexGeometry) polylineWKBGeometry, polyline2); wkbType = polylineWKBBuffer.getInt(1); assertTrue(wkbType == WkbGeometryType.wkbLineStringZM); // Test wkbExportPolyline on empty polyline Polyline polyline3 = new Polyline(); - polylineWKBBuffer = exporterWKB.execute( - WkbExportFlags.wkbExportLineString, polyline3, null); - polylineWKBGeometry = importerWKB.execute(0, Geometry.Type.Polyline, - polylineWKBBuffer, null); + polylineWKBBuffer = exporterWKB.execute(WkbExportFlags.wkbExportLineString, polyline3, null); + polylineWKBGeometry = importerWKB.execute(0, Geometry.Type.Polyline, polylineWKBBuffer, null); assertTrue(polylineWKBGeometry.isEmpty() == true); wkbType = polylineWKBBuffer.getInt(1); assertTrue(wkbType == WkbGeometryType.wkbLineString); // Test WKB_export_defaults on empty polyline polylineWKBBuffer = exporterWKB.execute(0, polyline3, null); - polylineWKBGeometry = importerWKB.execute(0, Geometry.Type.Polyline, - polylineWKBBuffer, null); + polylineWKBGeometry = importerWKB.execute(0, Geometry.Type.Polyline, polylineWKBBuffer, null); assertTrue(polylineWKBGeometry.isEmpty() == true); wkbType = polylineWKBBuffer.getInt(1); assertTrue(wkbType == WkbGeometryType.wkbMultiLineString); @@ -702,53 +638,42 @@ public static void testImportExportWKBPolyline() { @Test public static void testImportExportWKBMultiPoint() { - OperatorExportToWkb exporterWKB = (OperatorExportToWkb) OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.ExportToWkb); - OperatorImportFromWkb importerWKB = (OperatorImportFromWkb) OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.ImportFromWkb); + OperatorExportToWkb exporterWKB = (OperatorExportToWkb) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.ExportToWkb); + OperatorImportFromWkb importerWKB = (OperatorImportFromWkb) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.ImportFromWkb); MultiPoint multipoint = makeMultiPoint(); multipoint.dropAttribute(VertexDescription.Semantics.ID); // Test Import Multi_point from Multi_point - ByteBuffer multipointWKBBuffer = exporterWKB.execute(0, multipoint, - null); + ByteBuffer multipointWKBBuffer = exporterWKB.execute(0, multipoint, null); int wkbType = multipointWKBBuffer.getInt(1); assertTrue(wkbType == WkbGeometryType.wkbMultiPointZ); - MultiPoint multipointWKBGeometry = (MultiPoint) (importerWKB.execute(0, - Geometry.Type.MultiPoint, multipointWKBBuffer, null)); - TestCommonMethods.compareGeometryContent( - (MultiVertexGeometry) multipointWKBGeometry, multipoint); + MultiPoint multipointWKBGeometry = (MultiPoint) (importerWKB.execute(0, Geometry.Type.MultiPoint, multipointWKBBuffer, null)); + TestCommonMethods.compareGeometryContent((MultiVertexGeometry) multipointWKBGeometry, multipoint); // Test WKB_export_point on nonempty single point Multi_point MultiPoint multipoint2 = makeMultiPoint2(); assertTrue(multipoint2.getPointCount() == 1); - ByteBuffer pointWKBBuffer = exporterWKB.execute( - WkbExportFlags.wkbExportPoint, multipoint2, null); - Point pointWKBGeometry = (Point) (importerWKB.execute(0, - Geometry.Type.Point, pointWKBBuffer, null)); + ByteBuffer pointWKBBuffer = exporterWKB.execute(WkbExportFlags.wkbExportPoint, multipoint2, null); + Point pointWKBGeometry = (Point) (importerWKB.execute(0, Geometry.Type.Point, pointWKBBuffer, null)); Point3D point3d, mpoint3d; point3d = pointWKBGeometry.getXYZ(); mpoint3d = multipoint2.getXYZ(0); - assertTrue(point3d.x == mpoint3d.x && point3d.y == mpoint3d.y - && point3d.z == mpoint3d.z); + assertTrue(point3d.x == mpoint3d.x && point3d.y == mpoint3d.y && point3d.z == mpoint3d.z); wkbType = pointWKBBuffer.getInt(1); assertTrue(wkbType == WkbGeometryType.wkbPointZ); // Test WKB_export_point on empty Multi_point MultiPoint multipoint3 = new MultiPoint(); - pointWKBBuffer = exporterWKB.execute(WkbExportFlags.wkbExportPoint, - multipoint3, null); - pointWKBGeometry = (Point) (importerWKB.execute(0, Geometry.Type.Point, - pointWKBBuffer, null)); + pointWKBBuffer = exporterWKB.execute(WkbExportFlags.wkbExportPoint, multipoint3, null); + pointWKBGeometry = (Point) (importerWKB.execute(0, Geometry.Type.Point, pointWKBBuffer, null)); assertTrue(pointWKBGeometry.isEmpty() == true); wkbType = pointWKBBuffer.getInt(1); assertTrue(wkbType == WkbGeometryType.wkbPoint); // Test WKB_export_defaults on empty Multi_point multipointWKBBuffer = exporterWKB.execute(0, multipoint3, null); - multipointWKBGeometry = (MultiPoint) (importerWKB.execute(0, - Geometry.Type.MultiPoint, multipointWKBBuffer, null)); + multipointWKBGeometry = (MultiPoint) (importerWKB.execute(0, Geometry.Type.MultiPoint, multipointWKBBuffer, null)); assertTrue(multipointWKBGeometry.isEmpty() == true); wkbType = multipointWKBBuffer.getInt(1); assertTrue(wkbType == WkbGeometryType.wkbMultiPoint); @@ -756,10 +681,8 @@ public static void testImportExportWKBMultiPoint() { @Test public static void testImportExportWKBPoint() { - OperatorExportToWkb exporterWKB = (OperatorExportToWkb) OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.ExportToWkb); - OperatorImportFromWkb importerWKB = (OperatorImportFromWkb) OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.ImportFromWkb); + OperatorExportToWkb exporterWKB = (OperatorExportToWkb) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.ExportToWkb); + OperatorImportFromWkb importerWKB = (OperatorImportFromWkb) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.ImportFromWkb); // Point Point point = makePoint(); @@ -768,8 +691,7 @@ public static void testImportExportWKBPoint() { ByteBuffer pointWKBBuffer = exporterWKB.execute(0, point, null); int wkbType = pointWKBBuffer.getInt(1); assertTrue(wkbType == WkbGeometryType.wkbPointZM); - Point pointWKBGeometry = (Point) (importerWKB.execute(0, - Geometry.Type.Point, pointWKBBuffer, null)); + Point pointWKBGeometry = (Point) (importerWKB.execute(0, Geometry.Type.Point, pointWKBBuffer, null)); double x_1 = point.getX(); double x2 = pointWKBGeometry.getX(); @@ -790,27 +712,22 @@ public static void testImportExportWKBPoint() { // Test WKB_export_defaults on empty point Point point2 = new Point(); pointWKBBuffer = exporterWKB.execute(0, point2, null); - pointWKBGeometry = (Point) (importerWKB.execute(0, Geometry.Type.Point, - pointWKBBuffer, null)); + pointWKBGeometry = (Point) (importerWKB.execute(0, Geometry.Type.Point, pointWKBBuffer, null)); assertTrue(pointWKBGeometry.isEmpty() == true); wkbType = pointWKBBuffer.getInt(1); assertTrue(wkbType == WkbGeometryType.wkbPoint); // Test WKB_export_point on empty point - pointWKBBuffer = exporterWKB.execute(WkbExportFlags.wkbExportPoint, - point2, null); - pointWKBGeometry = (Point) (importerWKB.execute(0, Geometry.Type.Point, - pointWKBBuffer, null)); + pointWKBBuffer = exporterWKB.execute(WkbExportFlags.wkbExportPoint, point2, null); + pointWKBGeometry = (Point) (importerWKB.execute(0, Geometry.Type.Point, pointWKBBuffer, null)); assertTrue(pointWKBGeometry.isEmpty() == true); wkbType = pointWKBBuffer.getInt(1); assertTrue(wkbType == WkbGeometryType.wkbPoint); // Test WKB_export_multi_point on empty point MultiPoint multipoint = new MultiPoint(); - ByteBuffer multipointWKBBuffer = exporterWKB.execute( - WkbExportFlags.wkbExportMultiPoint, multipoint, null); - MultiPoint multipointWKBGeometry = (MultiPoint) (importerWKB.execute(0, - Geometry.Type.MultiPoint, multipointWKBBuffer, null)); + ByteBuffer multipointWKBBuffer = exporterWKB.execute(WkbExportFlags.wkbExportMultiPoint, multipoint, null); + MultiPoint multipointWKBGeometry = (MultiPoint) (importerWKB.execute(0, Geometry.Type.MultiPoint, multipointWKBBuffer, null)); assertTrue(multipointWKBGeometry.isEmpty() == true); wkbType = multipointWKBBuffer.getInt(1); assertTrue(wkbType == WkbGeometryType.wkbMultiPoint); @@ -818,25 +735,20 @@ public static void testImportExportWKBPoint() { // Test WKB_export_point on nonempty single point Multi_point MultiPoint multipoint2 = makeMultiPoint2(); assertTrue(multipoint2.getPointCount() == 1); - pointWKBBuffer = exporterWKB.execute(WkbExportFlags.wkbExportPoint, - multipoint2, null); - pointWKBGeometry = (Point) (importerWKB.execute(0, Geometry.Type.Point, - pointWKBBuffer, null)); + pointWKBBuffer = exporterWKB.execute(WkbExportFlags.wkbExportPoint, multipoint2, null); + pointWKBGeometry = (Point) (importerWKB.execute(0, Geometry.Type.Point, pointWKBBuffer, null)); Point3D point3d, mpoint3d; point3d = pointWKBGeometry.getXYZ(); mpoint3d = multipoint2.getXYZ(0); - assertTrue(point3d.x == mpoint3d.x && point3d.y == mpoint3d.y - && point3d.z == mpoint3d.z); + assertTrue(point3d.x == mpoint3d.x && point3d.y == mpoint3d.y && point3d.z == mpoint3d.z); wkbType = pointWKBBuffer.getInt(1); assertTrue(wkbType == WkbGeometryType.wkbPointZ); } @Test public static void testImportExportWKBEnvelope() { - OperatorExportToWkb exporterWKB = (OperatorExportToWkb) OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.ExportToWkb); - OperatorImportFromWkb importerWKB = (OperatorImportFromWkb) OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.ImportFromWkb); + OperatorExportToWkb exporterWKB = (OperatorExportToWkb) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.ExportToWkb); + OperatorImportFromWkb importerWKB = (OperatorImportFromWkb) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.ImportFromWkb); // Test Export Envelope to Polygon (WKB_export_defaults) Envelope envelope = makeEnvelope(); @@ -845,8 +757,7 @@ public static void testImportExportWKBEnvelope() { ByteBuffer polygonWKBBuffer = exporterWKB.execute(0, envelope, null); int wkbType = polygonWKBBuffer.getInt(1); assertTrue(wkbType == WkbGeometryType.wkbPolygonZM); - Polygon polygon = (Polygon) (importerWKB.execute(0, - Geometry.Type.Polygon, polygonWKBBuffer, null)); + Polygon polygon = (Polygon) (importerWKB.execute(0, Geometry.Type.Polygon, polygonWKBBuffer, null)); int point_count = polygon.getPointCount(); assertTrue(point_count == 4); @@ -857,21 +768,16 @@ public static void testImportExportWKBEnvelope() { interval = envelope.queryInterval(VertexDescription.Semantics.Z, 0); Point3D point3d; point3d = polygon.getXYZ(0); - assertTrue(point3d.x == env.xmin && point3d.y == env.ymin - && point3d.z == interval.vmin); + assertTrue(point3d.x == env.xmin && point3d.y == env.ymin && point3d.z == interval.vmin); point3d = polygon.getXYZ(1); - assertTrue(point3d.x == env.xmin && point3d.y == env.ymax - && point3d.z == interval.vmax); + assertTrue(point3d.x == env.xmin && point3d.y == env.ymax && point3d.z == interval.vmax); point3d = polygon.getXYZ(2); - assertTrue(point3d.x == env.xmax && point3d.y == env.ymax - && point3d.z == interval.vmin); + assertTrue(point3d.x == env.xmax && point3d.y == env.ymax && point3d.z == interval.vmin); point3d = polygon.getXYZ(3); - assertTrue(point3d.x == env.xmax && point3d.y == env.ymin - && point3d.z == interval.vmax); + assertTrue(point3d.x == env.xmax && point3d.y == env.ymin && point3d.z == interval.vmax); interval = envelope.queryInterval(VertexDescription.Semantics.M, 0); - double m = polygon.getAttributeAsDbl(VertexDescription.Semantics.M, 0, - 0); + double m = polygon.getAttributeAsDbl(VertexDescription.Semantics.M, 0, 0); assertTrue(m == interval.vmin); m = polygon.getAttributeAsDbl(VertexDescription.Semantics.M, 1, 0); assertTrue(m == interval.vmax); @@ -881,29 +787,23 @@ public static void testImportExportWKBEnvelope() { assertTrue(m == interval.vmax); // Test WKB_export_multi_polygon on nonempty Envelope - polygonWKBBuffer = exporterWKB.execute( - WkbExportFlags.wkbExportMultiPolygon, envelope, null); + polygonWKBBuffer = exporterWKB.execute(WkbExportFlags.wkbExportMultiPolygon, envelope, null); wkbType = polygonWKBBuffer.getInt(1); assertTrue(wkbType == WkbGeometryType.wkbMultiPolygonZM); - polygon = (Polygon) (importerWKB.execute(0, Geometry.Type.Polygon, - polygonWKBBuffer, null)); + polygon = (Polygon) (importerWKB.execute(0, Geometry.Type.Polygon, polygonWKBBuffer, null)); point_count = polygon.getPointCount(); assertTrue(point_count == 4); envelope.queryEnvelope2D(env); interval = envelope.queryInterval(VertexDescription.Semantics.Z, 0); point3d = polygon.getXYZ(0); - assertTrue(point3d.x == env.xmin && point3d.y == env.ymin - && point3d.z == interval.vmin); + assertTrue(point3d.x == env.xmin && point3d.y == env.ymin && point3d.z == interval.vmin); point3d = polygon.getXYZ(1); - assertTrue(point3d.x == env.xmin && point3d.y == env.ymax - && point3d.z == interval.vmax); + assertTrue(point3d.x == env.xmin && point3d.y == env.ymax && point3d.z == interval.vmax); point3d = polygon.getXYZ(2); - assertTrue(point3d.x == env.xmax && point3d.y == env.ymax - && point3d.z == interval.vmin); + assertTrue(point3d.x == env.xmax && point3d.y == env.ymax && point3d.z == interval.vmin); point3d = polygon.getXYZ(3); - assertTrue(point3d.x == env.xmax && point3d.y == env.ymin - && point3d.z == interval.vmax); + assertTrue(point3d.x == env.xmax && point3d.y == env.ymin && point3d.z == interval.vmax); interval = envelope.queryInterval(VertexDescription.Semantics.M, 0); m = polygon.getAttributeAsDbl(VertexDescription.Semantics.M, 0, 0); @@ -920,34 +820,28 @@ public static void testImportExportWKBEnvelope() { polygonWKBBuffer = exporterWKB.execute(0, envelope2, null); wkbType = polygonWKBBuffer.getInt(1); assertTrue(wkbType == WkbGeometryType.wkbPolygon); - polygon = (Polygon) (importerWKB.execute(0, Geometry.Type.Polygon, - polygonWKBBuffer, null)); + polygon = (Polygon) (importerWKB.execute(0, Geometry.Type.Polygon, polygonWKBBuffer, null)); assertTrue(polygon.isEmpty()); // Test WKB_export_polygon on empty Envelope - polygonWKBBuffer = exporterWKB.execute(WkbExportFlags.wkbExportPolygon, - envelope2, null); + polygonWKBBuffer = exporterWKB.execute(WkbExportFlags.wkbExportPolygon, envelope2, null); wkbType = polygonWKBBuffer.getInt(1); assertTrue(wkbType == WkbGeometryType.wkbPolygon); - polygon = (Polygon) (importerWKB.execute(0, Geometry.Type.Polygon, - polygonWKBBuffer, null)); + polygon = (Polygon) (importerWKB.execute(0, Geometry.Type.Polygon, polygonWKBBuffer, null)); assertTrue(polygon.isEmpty()); } @Test public static void testImportExportWktGeometryCollection() { - OperatorImportFromWkt importerWKT = (OperatorImportFromWkt) OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.ImportFromWkt); - OperatorExportToWkt exporterWKT = (OperatorExportToWkt) OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.ExportToWkt); + OperatorImportFromWkt importerWKT = (OperatorImportFromWkt) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.ImportFromWkt); + OperatorExportToWkt exporterWKT = (OperatorExportToWkt) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.ExportToWkt); String wktString; Envelope2D envelope = new Envelope2D(); WktParser wktParser = new WktParser(); wktString = "GeometryCollection( Point (0 0), GeometryCollection( Point (0 0) , Point (1 1) , Point (2 2), LineString empty ), Point (1 1), Point (2 2) )"; - OGCStructure structure = importerWKT.executeOGC(0, wktString, null).m_structures - .get(0); + OGCStructure structure = importerWKT.executeOGC(0, wktString, null).m_structures.get(0); assertTrue(structure.m_type == 7); assertTrue(structure.m_structures.get(0).m_type == 1); @@ -964,10 +858,8 @@ public static void testImportExportWktGeometryCollection() { @Test public static void testImportExportWktMultiPolygon() { - OperatorImportFromWkt importerWKT = (OperatorImportFromWkt) OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.ImportFromWkt); - OperatorExportToWkt exporterWKT = (OperatorExportToWkt) OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.ExportToWkt); + OperatorImportFromWkt importerWKT = (OperatorImportFromWkt) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.ImportFromWkt); + OperatorExportToWkt exporterWKT = (OperatorExportToWkt) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.ExportToWkt); Polygon polygon; String wktString; @@ -975,16 +867,13 @@ public static void testImportExportWktMultiPolygon() { WktParser wktParser = new WktParser(); // Test Import from MultiPolygon - wktString = "Multipolygon M empty"; - polygon = (Polygon) importerWKT.execute(0, Geometry.Type.Polygon, - wktString, null); + polygon = (Polygon) importerWKT.execute(0, Geometry.Type.Polygon, wktString, null); assertTrue(polygon != null); assertTrue(polygon.isEmpty()); assertTrue(polygon.hasAttribute(VertexDescription.Semantics.M)); - polygon = (Polygon) GeometryEngine.geometryFromWkt(wktString, 0, - Geometry.Type.Unknown); + polygon = (Polygon) GeometryEngine.geometryFromWkt(wktString, 0, Geometry.Type.Unknown); assertTrue(polygon != null); assertTrue(polygon.isEmpty()); assertTrue(polygon.hasAttribute(VertexDescription.Semantics.M)); @@ -996,43 +885,36 @@ public static void testImportExportWktMultiPolygon() { assertTrue(wktString.equals("MULTIPOLYGON M EMPTY")); wktString = "Multipolygon Z (empty, (empty, (10 10 5, 20 10 5, 20 20 5, 10 20 5, 10 10 5), (12 12 3), empty, (10 10 1, 12 12 1)), empty, ((90 90 88, 60 90 7, 60 60 7), empty, (70 70 7, 80 80 7, 70 80 7, 70 70 7)), empty)"; - polygon = (Polygon) (importerWKT.execute(0, Geometry.Type.Polygon, - wktString, null)); + polygon = (Polygon) (importerWKT.execute(0, Geometry.Type.Polygon, wktString, null)); assertTrue(polygon != null); polygon.queryEnvelope2D(envelope); - assertTrue(envelope.xmin == 10 && envelope.xmax == 90 - && envelope.ymin == 10 && envelope.ymax == 90); + assertTrue(envelope.xmin == 10 && envelope.xmax == 90 && envelope.ymin == 10 && envelope.ymax == 90); assertTrue(polygon.getPointCount() == 14); assertTrue(polygon.getPathCount() == 5); // assertTrue(polygon.calculate_area_2D() > 0.0); assertTrue(polygon.hasAttribute(VertexDescription.Semantics.Z)); - double z = polygon.getAttributeAsDbl(VertexDescription.Semantics.Z, 0, - 0); + double z = polygon.getAttributeAsDbl(VertexDescription.Semantics.Z, 0, 0); assertTrue(z == 5); // Test Export to WKT MultiPolygon wktString = exporterWKT.execute(0, polygon, null); - assertTrue(wktString - .equals("MULTIPOLYGON Z (((10 10 5, 20 10 5, 20 20 5, 10 20 5, 10 10 5), (12 12 3, 12 12 3, 12 12 3), (10 10 1, 12 12 1, 10 10 1)), ((90 90 88, 60 90 7, 60 60 7, 90 90 88), (70 70 7, 70 80 7, 80 80 7, 70 70 7)))")); + assertTrue(wktString.equals("MULTIPOLYGON Z (((10 10 5, 20 10 5, 20 20 5, 10 20 5, 10 10 5), (12 12 3, 12 12 3, 12 12 3), (10 10 1, 12 12 1, 10 10 1)), ((90 90 88, 60 90 7, 60 60 7, 90 90 88), (70 70 7, 70 80 7, 80 80 7, 70 70 7)))")); wktParser.resetParser(wktString); while (wktParser.nextToken() != WktParser.WktToken.not_available) { } // Test import Polygon wktString = "POLYGON z (EMPTY, EMPTY, (10 10 5, 10 20 5, 20 20 5, 20 10 5), (12 12 3), EMPTY, (10 10 1, 12 12 1), EMPTY, (60 60 7, 60 90 7, 90 90 7, 60 60 7), EMPTY, (70 70 7, 70 80 7, 80 80 7), EMPTY)"; - polygon = (Polygon) (importerWKT.execute(0, Geometry.Type.Polygon, - wktString, null)); + polygon = (Polygon) (importerWKT.execute(0, Geometry.Type.Polygon, wktString, null)); assertTrue(polygon != null); assertTrue(polygon.getPointCount() == 14); assertTrue(polygon.getPathCount() == 5); assertTrue(polygon.hasAttribute(VertexDescription.Semantics.Z)); // Test Export to WKT Polygon - wktString = exporterWKT.execute(WktExportFlags.wktExportPolygon, - polygon, null); - assertTrue(wktString - .equals("POLYGON Z ((10 10 5, 20 10 5, 20 20 5, 10 20 5, 10 10 5), (12 12 3, 12 12 3, 12 12 3), (10 10 1, 12 12 1, 10 10 1), (60 60 7, 60 90 7, 90 90 7, 60 60 7), (70 70 7, 70 80 7, 80 80 7, 70 70 7))")); + wktString = exporterWKT.execute(WktExportFlags.wktExportPolygon, polygon, null); + assertTrue(wktString.equals("POLYGON Z ((10 10 5, 20 10 5, 20 20 5, 10 20 5, 10 10 5), (12 12 3, 12 12 3, 12 12 3), (10 10 1, 12 12 1, 10 10 1), (60 60 7, 60 90 7, 90 90 7, 60 60 7), (70 70 7, 70 80 7, 80 80 7, 70 70 7))")); wktParser.resetParser(wktString); while (wktParser.nextToken() != WktParser.WktToken.not_available) { } @@ -1042,16 +924,13 @@ public static void testImportExportWktMultiPolygon() { polygon.queryEnvelope(env); wktString = exporterWKT.execute(0, env, null); - assertTrue(wktString - .equals("POLYGON Z ((10 10 1, 90 10 7, 90 90 1, 10 90 7, 10 10 1))")); + assertTrue(wktString.equals("POLYGON Z ((10 10 1, 90 10 7, 90 90 1, 10 90 7, 10 10 1))")); wktParser.resetParser(wktString); while (wktParser.nextToken() != WktParser.WktToken.not_available) { } - wktString = exporterWKT.execute(WktExportFlags.wktExportMultiPolygon, - env, null); - assertTrue(wktString - .equals("MULTIPOLYGON Z (((10 10 1, 90 10 7, 90 90 1, 10 90 7, 10 10 1)))")); + wktString = exporterWKT.execute(WktExportFlags.wktExportMultiPolygon, env, null); + assertTrue(wktString.equals("MULTIPOLYGON Z (((10 10 1, 90 10 7, 90 90 1, 10 90 7, 10 10 1)))")); wktParser.resetParser(wktString); while (wktParser.nextToken() != WktParser.WktToken.not_available) { } @@ -1064,44 +943,38 @@ public static void testImportExportWktMultiPolygon() { while (wktParser.nextToken() != WktParser.WktToken.not_available) { } - wktString = exporterWKT.execute(WktExportFlags.wktExportMultiPolygon, - env, null); + wktString = exporterWKT.execute(WktExportFlags.wktExportMultiPolygon, env, null); assertTrue(wktString.equals("MULTIPOLYGON Z EMPTY")); wktParser.resetParser(wktString); while (wktParser.nextToken() != WktParser.WktToken.not_available) { } wktString = "MULTIPOLYGON (((5 10, 8 10, 10 10, 10 0, 0 0, 0 10, 2 10, 5 10)))"; // ring - // is - // oriented - // clockwise - polygon = (Polygon) (importerWKT.execute(0, Geometry.Type.Polygon, - wktString, null)); + // is + // oriented + // clockwise + polygon = (Polygon) (importerWKT.execute(0, Geometry.Type.Polygon, wktString, null)); assertTrue(polygon != null); assertTrue(polygon.calculateArea2D() > 0); wktString = "MULTIPOLYGON Z (((90 10 7, 10 10 1, 10 90 7, 90 90 1, 90 10 7)))"; // ring - // is - // oriented - // clockwise - polygon = (Polygon) (importerWKT.execute(0, Geometry.Type.Polygon, - wktString, null)); + // is + // oriented + // clockwise + polygon = (Polygon) (importerWKT.execute(0, Geometry.Type.Polygon, wktString, null)); assertTrue(polygon != null); assertTrue(polygon.getPointCount() == 4); assertTrue(polygon.getPathCount() == 1); assertTrue(polygon.hasAttribute(VertexDescription.Semantics.Z)); assertTrue(polygon.calculateArea2D() > 0); - wktString = exporterWKT.execute(WktExportFlags.wktExportMultiPolygon, - polygon, null); - assertTrue(wktString - .equals("MULTIPOLYGON Z (((90 10 7, 90 90 1, 10 90 7, 10 10 1, 90 10 7)))")); + wktString = exporterWKT.execute(WktExportFlags.wktExportMultiPolygon, polygon, null); + assertTrue(wktString.equals("MULTIPOLYGON Z (((90 10 7, 90 90 1, 10 90 7, 10 10 1, 90 10 7)))")); } @Test public static void testImportExportWktPolygon() { - OperatorImportFromWkt importerWKT = (OperatorImportFromWkt) OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.ImportFromWkt); + OperatorImportFromWkt importerWKT = (OperatorImportFromWkt) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.ImportFromWkt); // OperatorExportToWkt exporterWKT = // (OperatorExportToWkt)OperatorFactoryLocal.getInstance().getOperator(Operator.Type.ExportToWkt); @@ -1110,29 +983,24 @@ public static void testImportExportWktPolygon() { Envelope2D envelope = new Envelope2D(); // Test Import from Polygon - wktString = "Polygon ZM empty"; - polygon = (Polygon) (importerWKT.execute(0, Geometry.Type.Unknown, - wktString, null)); + polygon = (Polygon) (importerWKT.execute(0, Geometry.Type.Unknown, wktString, null)); assertTrue(polygon != null); assertTrue(polygon.isEmpty()); assertTrue(polygon.hasAttribute(VertexDescription.Semantics.Z)); assertTrue(polygon.hasAttribute(VertexDescription.Semantics.M)); wktString = "Polygon z (empty, (10 10 5, 20 10 5, 20 20 5, 10 20 5, 10 10 5), (12 12 3), empty, (10 10 1, 12 12 1))"; - polygon = (Polygon) (importerWKT.execute(0, Geometry.Type.Unknown, - wktString, null)); + polygon = (Polygon) (importerWKT.execute(0, Geometry.Type.Unknown, wktString, null)); assertTrue(polygon != null); polygon.queryEnvelope2D(envelope); - assertTrue(envelope.xmin == 10 && envelope.xmax == 20 - && envelope.ymin == 10 && envelope.ymax == 20); + assertTrue(envelope.xmin == 10 && envelope.xmax == 20 && envelope.ymin == 10 && envelope.ymax == 20); assertTrue(polygon.getPointCount() == 8); assertTrue(polygon.getPathCount() == 3); assertTrue(polygon.hasAttribute(VertexDescription.Semantics.Z)); wktString = "polygon ((35 10, 10 20, 15 40, 45 45, 35 10), (20 30, 35 35, 30 20, 20 30))"; - Polygon polygon2 = (Polygon) (importerWKT.execute(0, - Geometry.Type.Unknown, wktString, null)); + Polygon polygon2 = (Polygon) (importerWKT.execute(0, Geometry.Type.Unknown, wktString, null)); assertTrue(polygon2 != null); // wktString = exporterWKT.execute(0, *polygon2, null); @@ -1140,8 +1008,7 @@ public static void testImportExportWktPolygon() { @Test public static void testImportExportWktLineString() { - OperatorImportFromWkt importerWKT = (OperatorImportFromWkt) OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.ImportFromWkt); + OperatorImportFromWkt importerWKT = (OperatorImportFromWkt) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.ImportFromWkt); // OperatorExportToWkt exporterWKT = // (OperatorExportToWkt)OperatorFactoryLocal.getInstance().getOperator(Operator.Type.ExportToWkt); @@ -1150,22 +1017,18 @@ public static void testImportExportWktLineString() { Envelope2D envelope = new Envelope2D(); // Test Import from LineString - wktString = "LineString ZM empty"; - polyline = (Polyline) (importerWKT.execute(0, Geometry.Type.Unknown, - wktString, null)); + polyline = (Polyline) (importerWKT.execute(0, Geometry.Type.Unknown, wktString, null)); assertTrue(polyline != null); assertTrue(polyline.isEmpty()); assertTrue(polyline.hasAttribute(VertexDescription.Semantics.Z)); assertTrue(polyline.hasAttribute(VertexDescription.Semantics.M)); wktString = "LineString m (10 10 5, 10 20 5, 20 20 5, 20 10 5)"; - polyline = (Polyline) (importerWKT.execute(0, Geometry.Type.Unknown, - wktString, null)); + polyline = (Polyline) (importerWKT.execute(0, Geometry.Type.Unknown, wktString, null)); assertTrue(polyline != null); polyline.queryEnvelope2D(envelope); - assertTrue(envelope.xmin == 10 && envelope.xmax == 20 - && envelope.ymin == 10 && envelope.ymax == 20); + assertTrue(envelope.xmin == 10 && envelope.xmax == 20 && envelope.ymin == 10 && envelope.ymax == 20); assertTrue(polyline.getPointCount() == 4); assertTrue(polyline.getPathCount() == 1); assertTrue(polyline.hasAttribute(VertexDescription.Semantics.M)); @@ -1173,10 +1036,8 @@ public static void testImportExportWktLineString() { @Test public static void testImportExportWktMultiLineString() { - OperatorImportFromWkt importerWKT = (OperatorImportFromWkt) OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.ImportFromWkt); - OperatorExportToWkt exporterWKT = (OperatorExportToWkt) OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.ExportToWkt); + OperatorImportFromWkt importerWKT = (OperatorImportFromWkt) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.ImportFromWkt); + OperatorExportToWkt exporterWKT = (OperatorExportToWkt) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.ExportToWkt); Polyline polyline; String wktString; @@ -1184,49 +1045,40 @@ public static void testImportExportWktMultiLineString() { WktParser wktParser = new WktParser(); // Test Import from MultiLineString - wktString = "MultiLineStringZMempty"; - polyline = (Polyline) (importerWKT.execute(0, Geometry.Type.Unknown, - wktString, null)); + polyline = (Polyline) (importerWKT.execute(0, Geometry.Type.Unknown, wktString, null)); assertTrue(polyline != null); assertTrue(polyline.isEmpty()); assertTrue(polyline.hasAttribute(VertexDescription.Semantics.Z)); assertTrue(polyline.hasAttribute(VertexDescription.Semantics.M)); wktString = "MultiLineStringm(empty, empty, (10 10 5, 10 20 5, 20 88 5, 20 10 5), (12 88 3), empty, (10 10 1, 12 12 1), empty, (88 60 7, 60 90 7, 90 90 7), empty, (70 70 7, 70 80 7, 80 80 7), empty)"; - polyline = (Polyline) (importerWKT.execute(0, Geometry.Type.Unknown, - wktString, null)); + polyline = (Polyline) (importerWKT.execute(0, Geometry.Type.Unknown, wktString, null)); assertTrue(polyline != null); polyline.queryEnvelope2D(envelope); - assertTrue(envelope.xmin == 10 && envelope.xmax == 90 - && envelope.ymin == 10 && envelope.ymax == 90); + assertTrue(envelope.xmin == 10 && envelope.xmax == 90 && envelope.ymin == 10 && envelope.ymax == 90); assertTrue(polyline.getPointCount() == 14); assertTrue(polyline.getPathCount() == 5); assertTrue(polyline.hasAttribute(VertexDescription.Semantics.M)); wktString = exporterWKT.execute(0, polyline, null); - assertTrue(wktString - .equals("MULTILINESTRING M ((10 10 5, 10 20 5, 20 88 5, 20 10 5), (12 88 3, 12 88 3), (10 10 1, 12 12 1), (88 60 7, 60 90 7, 90 90 7), (70 70 7, 70 80 7, 80 80 7))")); + assertTrue(wktString.equals("MULTILINESTRING M ((10 10 5, 10 20 5, 20 88 5, 20 10 5), (12 88 3, 12 88 3), (10 10 1, 12 12 1), (88 60 7, 60 90 7, 90 90 7), (70 70 7, 70 80 7, 80 80 7))")); wktParser.resetParser(wktString); while (wktParser.nextToken() != WktParser.WktToken.not_available) { } // Test Import LineString wktString = "Linestring Z(10 10 5, 10 20 5, 20 20 5, 20 10 5)"; - polyline = (Polyline) (importerWKT.execute(0, Geometry.Type.Unknown, - wktString, null)); + polyline = (Polyline) (importerWKT.execute(0, Geometry.Type.Unknown, wktString, null)); assertTrue(polyline.getPointCount() == 4); - wktString = exporterWKT.execute(WktExportFlags.wktExportLineString, - polyline, null); - assertTrue(wktString - .equals("LINESTRING Z (10 10 5, 10 20 5, 20 20 5, 20 10 5)")); + wktString = exporterWKT.execute(WktExportFlags.wktExportLineString, polyline, null); + assertTrue(wktString.equals("LINESTRING Z (10 10 5, 10 20 5, 20 20 5, 20 10 5)")); wktParser.resetParser(wktString); while (wktParser.nextToken() != WktParser.WktToken.not_available) { } wktString = exporterWKT.execute(0, polyline, null); - assertTrue(wktString - .equals("MULTILINESTRING Z ((10 10 5, 10 20 5, 20 20 5, 20 10 5))")); + assertTrue(wktString.equals("MULTILINESTRING Z ((10 10 5, 10 20 5, 20 20 5, 20 10 5))")); wktParser.resetParser(wktString); while (wktParser.nextToken() != WktParser.WktToken.not_available) { } @@ -1234,10 +1086,8 @@ public static void testImportExportWktMultiLineString() { @Test public static void testImportExportWktMultiPoint() { - OperatorImportFromWkt importerWKT = (OperatorImportFromWkt) OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.ImportFromWkt); - OperatorExportToWkt exporterWKT = (OperatorExportToWkt) OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.ExportToWkt); + OperatorImportFromWkt importerWKT = (OperatorImportFromWkt) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.ImportFromWkt); + OperatorExportToWkt exporterWKT = (OperatorExportToWkt) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.ExportToWkt); MultiPoint multipoint; String wktString; @@ -1245,10 +1095,8 @@ public static void testImportExportWktMultiPoint() { WktParser wktParser = new WktParser(); // Test Import from Multi_point - wktString = " MultiPoint ZM empty"; - multipoint = (MultiPoint) (importerWKT.execute(0, - Geometry.Type.Unknown, wktString, null)); + multipoint = (MultiPoint) (importerWKT.execute(0, Geometry.Type.Unknown, wktString, null)); assertTrue(multipoint != null); assertTrue(multipoint.isEmpty()); assertTrue(multipoint.hasAttribute(VertexDescription.Semantics.Z)); @@ -1260,8 +1108,7 @@ public static void testImportExportWktMultiPoint() { while (wktParser.nextToken() != WktParser.WktToken.not_available) { } - wktString = exporterWKT.execute(WktExportFlags.wktExportPoint, - multipoint, null); + wktString = exporterWKT.execute(WktExportFlags.wktExportPoint, multipoint, null); assertTrue(wktString.equals("POINT ZM EMPTY")); wktParser.resetParser(wktString); while (wktParser.nextToken() != WktParser.WktToken.not_available) { @@ -1271,10 +1118,8 @@ public static void testImportExportWktMultiPoint() { multipoint.add(118.15114354234563, 33.82234433423462345); multipoint.add(88, 88); - wktString = exporterWKT.execute(WktExportFlags.wktExportPrecision10, - multipoint, null); - assertTrue(wktString - .equals("MULTIPOINT ((118.1511435 33.82234433), (88 88))")); + wktString = exporterWKT.execute(WktExportFlags.wktExportPrecision10, multipoint, null); + assertTrue(wktString.equals("MULTIPOINT ((118.1511435 33.82234433), (88 88))")); wktParser.resetParser(wktString); while (wktParser.nextToken() != WktParser.WktToken.not_available) { } @@ -1290,8 +1135,7 @@ public static void testImportExportWktMultiPoint() { } wktString = "Multipoint zm (empty, empty, (10 88 88 33), (10 20 5 33), (20 20 5 33), (20 10 5 33), (12 12 3 33), empty, (10 10 1 33), (12 12 1 33), empty, (60 60 7 33), (60 90.1 7 33), (90 90 7 33), empty, (70 70 7 33), (70 80 7 33), (80 80 7 33), empty)"; - multipoint = (MultiPoint) (importerWKT.execute(0, - Geometry.Type.Unknown, wktString, null)); + multipoint = (MultiPoint) (importerWKT.execute(0, Geometry.Type.Unknown, wktString, null)); assertTrue(multipoint != null); multipoint.queryEnvelope2D(envelope); // assertTrue(envelope.xmin == 10 && envelope.xmax == 90 && @@ -1301,8 +1145,7 @@ public static void testImportExportWktMultiPoint() { assertTrue(multipoint.hasAttribute(VertexDescription.Semantics.M)); wktString = "Multipoint zm (10 88 88 33, 10 20 5 33, 20 20 5 33, 20 10 5 33, 12 12 3 33, 10 10 1 33, 12 12 1 33, 60 60 7 33, 60 90.1 7 33, 90 90 7 33, 70 70 7 33, 70 80 7 33, 80 80 7 33)"; - multipoint = (MultiPoint) (importerWKT.execute(0, - Geometry.Type.Unknown, wktString, null)); + multipoint = (MultiPoint) (importerWKT.execute(0, Geometry.Type.Unknown, wktString, null)); assertTrue(multipoint != null); // assertTrue(envelope.xmin == 10 && envelope.xmax == 90 && // envelope.ymin == 10 && ::fabs(envelope.ymax - 90.1) <= 0.001); @@ -1310,20 +1153,16 @@ public static void testImportExportWktMultiPoint() { assertTrue(multipoint.hasAttribute(VertexDescription.Semantics.Z)); assertTrue(multipoint.hasAttribute(VertexDescription.Semantics.M)); - wktString = exporterWKT.execute(WktExportFlags.wktExportPrecision15, - multipoint, null); - assertTrue(wktString - .equals("MULTIPOINT ZM ((10 88 88 33), (10 20 5 33), (20 20 5 33), (20 10 5 33), (12 12 3 33), (10 10 1 33), (12 12 1 33), (60 60 7 33), (60 90.1 7 33), (90 90 7 33), (70 70 7 33), (70 80 7 33), (80 80 7 33))")); + wktString = exporterWKT.execute(WktExportFlags.wktExportPrecision15, multipoint, null); + assertTrue(wktString.equals("MULTIPOINT ZM ((10 88 88 33), (10 20 5 33), (20 20 5 33), (20 10 5 33), (12 12 3 33), (10 10 1 33), (12 12 1 33), (60 60 7 33), (60 90.1 7 33), (90 90 7 33), (70 70 7 33), (70 80 7 33), (80 80 7 33))")); wktParser.resetParser(wktString); while (wktParser.nextToken() != WktParser.WktToken.not_available) { } wktString = "Multipoint zm (empty, empty, (10 10 5 33))"; - multipoint = (MultiPoint) (importerWKT.execute(0, - Geometry.Type.Unknown, wktString, null)); + multipoint = (MultiPoint) (importerWKT.execute(0, Geometry.Type.Unknown, wktString, null)); - wktString = exporterWKT.execute(WktExportFlags.wktExportPoint, - multipoint, null); + wktString = exporterWKT.execute(WktExportFlags.wktExportPoint, multipoint, null); assertTrue(wktString.equals("POINT ZM (10 10 5 33)")); wktParser.resetParser(wktString); while (wktParser.nextToken() != WktParser.WktToken.not_available) { @@ -1332,20 +1171,16 @@ public static void testImportExportWktMultiPoint() { @Test public static void testImportExportWktPoint() { - OperatorImportFromWkt importerWKT = (OperatorImportFromWkt) OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.ImportFromWkt); - OperatorExportToWkt exporterWKT = (OperatorExportToWkt) OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.ExportToWkt); + OperatorImportFromWkt importerWKT = (OperatorImportFromWkt) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.ImportFromWkt); + OperatorExportToWkt exporterWKT = (OperatorExportToWkt) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.ExportToWkt); Point point; String wktString; WktParser wktParser = new WktParser(); // Test Import from Point - wktString = "Point ZM empty"; - point = (Point) (importerWKT.execute(0, Geometry.Type.Unknown, - wktString, null)); + point = (Point) (importerWKT.execute(0, Geometry.Type.Unknown, wktString, null)); assertTrue(point != null); assertTrue(point.isEmpty()); assertTrue(point.hasAttribute(VertexDescription.Semantics.Z)); @@ -1357,16 +1192,14 @@ public static void testImportExportWktPoint() { while (wktParser.nextToken() != WktParser.WktToken.not_available) { } - wktString = exporterWKT.execute(WktExportFlags.wktExportMultiPoint, - point, null); + wktString = exporterWKT.execute(WktExportFlags.wktExportMultiPoint, point, null); assertTrue(wktString.equals("MULTIPOINT ZM EMPTY")); wktParser.resetParser(wktString); while (wktParser.nextToken() != WktParser.WktToken.not_available) { } wktString = "Point zm (30.1 10.6 5.1 33.1)"; - point = (Point) (importerWKT.execute(0, Geometry.Type.Unknown, - wktString, null)); + point = (Point) (importerWKT.execute(0, Geometry.Type.Unknown, wktString, null)); assertTrue(point != null); assertTrue(point.hasAttribute(VertexDescription.Semantics.Z)); assertTrue(point.hasAttribute(VertexDescription.Semantics.M)); @@ -1380,34 +1213,30 @@ public static void testImportExportWktPoint() { assertTrue(z == 5.1); assertTrue(m == 33.1); - wktString = exporterWKT.execute(WktExportFlags.wktExportPrecision15, - point, null); + wktString = exporterWKT.execute(WktExportFlags.wktExportPrecision15, point, null); assertTrue(wktString.equals("POINT ZM (30.1 10.6 5.1 33.1)")); wktParser.resetParser(wktString); while (wktParser.nextToken() != WktParser.WktToken.not_available) { } - wktString = exporterWKT.execute(WktExportFlags.wktExportMultiPoint - | WktExportFlags.wktExportPrecision15, point, null); + wktString = exporterWKT.execute(WktExportFlags.wktExportMultiPoint | WktExportFlags.wktExportPrecision15, point, null); assertTrue(wktString.equals("MULTIPOINT ZM ((30.1 10.6 5.1 33.1))")); wktParser.resetParser(wktString); while (wktParser.nextToken() != WktParser.WktToken.not_available) { } } + @Deprecated @Test - public static void testImportGeoJsonGeometryCollection() - throws JSONException { - OperatorImportFromGeoJson importer = (OperatorImportFromGeoJson) OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.ImportFromGeoJson); + public static void testImportGeoJsonGeometryCollection() throws JSONException { + OperatorImportFromGeoJson importer = (OperatorImportFromGeoJson) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.ImportFromGeoJson); String geoJsonString; Envelope2D envelope = new Envelope2D(); WktParser wktParser = new WktParser(); geoJsonString = "{\"type\" : \"GeometryCollection\", \"geometries\" : [{\"type\" : \"Point\", \"coordinates\": [0,0]}, {\"type\" : \"GeometryCollection\" , \"geometries\" : [ {\"type\" : \"Point\", \"coordinates\" : [0, 0]} , {\"type\" : \"Point\", \"coordinates\" : [1, 1]} ,{ \"type\" : \"Point\", \"coordinates\" : [2, 2]}, {\"type\" : \"LineString\", \"coordinates\" : []}]} , {\"type\" : \"Point\", \"coordinates\" : [1, 1]}, {\"type\" : \"Point\" , \"coordinates\" : [2, 2]} ] }"; - OGCStructure structure = importer.executeOGC(0, geoJsonString, null).m_ogcStructure.m_structures - .get(0); + OGCStructure structure = importer.executeOGC(0, geoJsonString, null).m_ogcStructure.m_structures.get(0); assertTrue(structure.m_type == 7); assertTrue(structure.m_structures.get(0).m_type == 1); @@ -1423,288 +1252,485 @@ public static void testImportGeoJsonGeometryCollection() } @Test - public static void testImportGeoJsonMultiPolygon() throws JSONException { - OperatorImportFromGeoJson importerGeoJson = (OperatorImportFromGeoJson) OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.ImportFromGeoJson); + public static void testImportGeoJsonMultiPolygon() throws Exception { + OperatorImportFromGeoJson importerGeoJson = (OperatorImportFromGeoJson) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.ImportFromGeoJson); + OperatorExportToGeoJson exporterGeoJson = (OperatorExportToGeoJson) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.ExportToGeoJson); + MapGeometry map_geometry; Polygon polygon; + SpatialReference spatial_reference; String geoJsonString; Envelope2D envelope = new Envelope2D(); // Test Import from MultiPolygon - - geoJsonString = "{\"type\": \"Multipolygon\", \"coordinates\": []}"; - polygon = (Polygon) (importerGeoJson.execute(0, Geometry.Type.Polygon, - geoJsonString, null).getGeometry()); + geoJsonString = "{\"type\": \"MultiPolygon\", \"coordinates\": []}"; + polygon = (Polygon) (importerGeoJson.execute(0, Geometry.Type.Polygon, geoJsonString, null).getGeometry()); assertTrue(polygon != null); assertTrue(polygon.isEmpty()); assertTrue(!polygon.hasAttribute(VertexDescription.Semantics.M)); - polygon = (Polygon) (GeometryEngine.geometryFromGeoJson(geoJsonString, - 0, Geometry.Type.Unknown).getGeometry()); + geoJsonString = "{\"coordinates\" : [], \"type\": \"MultiPolygon\", \"crs\": {\"type\": \"name\", \"some\": \"stuff\", \"properties\": {\"some\" : \"stuff\", \"name\": \"urn:ogc:def:crs:OGC:1.3:CRS84\"}}}"; + map_geometry = importerGeoJson.execute(0, Geometry.Type.Polygon, geoJsonString, null); + polygon = (Polygon) map_geometry.getGeometry(); + spatial_reference = map_geometry.getSpatialReference(); assertTrue(polygon != null); assertTrue(polygon.isEmpty()); - assertTrue(!polygon.hasAttribute(VertexDescription.Semantics.M)); + assertTrue(spatial_reference.getLatestID() == 4326); + + geoJsonString = "{\"coordinates\" : null, \"crs\": null, \"type\": \"MultiPolygon\"}"; + map_geometry = importerGeoJson.execute(0, Geometry.Type.Polygon, geoJsonString, null); + polygon = (Polygon) map_geometry.getGeometry(); + spatial_reference = map_geometry.getSpatialReference(); + assertTrue(polygon != null); + assertTrue(polygon.isEmpty()); + assertTrue(spatial_reference == null); - geoJsonString = "{\"type\": \"Multipolygon\", \"coordinates\": [[], [[], [[10, 10, 5], [20, 10, 5], [20, 20, 5], [10, 20, 5], [10, 10, 5]], [[12, 12, 3]], [], [[10, 10, 1], [12, 12, 1]]], [], [[[90, 90, 88], [60, 90, 7], [60, 60, 7]], [], [[70, 70, 7], [80, 80, 7], [70, 80, 7], [70, 70, 7]]], []]}"; - polygon = (Polygon) (importerGeoJson.execute(0, Geometry.Type.Polygon, - geoJsonString, null).getGeometry()); + geoJsonString = "{\"type\": \"MultiPolygon\", \"coordinates\" : [[], [], [[[]]]], \"crsURN\": \"urn:ogc:def:crs:OGC:1.3:CRS27\"}"; + map_geometry = importerGeoJson.execute(0, Geometry.Type.Polygon, geoJsonString, null); + polygon = (Polygon) map_geometry.getGeometry(); + spatial_reference = map_geometry.getSpatialReference(); + assertTrue(polygon != null); + assertTrue(polygon.isEmpty()); + assertTrue(spatial_reference != null); + assertTrue(spatial_reference.getLatestID() == 4267); + + geoJsonString = "{\"coordinates\" : [[], [[], [[10, 10, 5], [20, 10, 5], [20, 20, 5], [10, 20, 5], [10, 10, 5]], [[12, 12, 3]], [], [[10, 10, 1], [12, 12, 1]]], [], [[[90, 90, 88], [60, 90, 7], [60, 60, 7]], [], [[70, 70, 7], [80, 80, 7], [70, 80, 7], [70, 70, 7]]], []], \"crs\": {\"type\": \"link\", \"properties\": {\"href\": \"http://spatialreference.org/ref/sr-org/6928/ogcwkt/\"}}, \"type\": \"MultiPolygon\"}"; + map_geometry = importerGeoJson.execute(0, Geometry.Type.Unknown, geoJsonString, null); + polygon = (Polygon) map_geometry.getGeometry(); + spatial_reference = map_geometry.getSpatialReference(); assertTrue(polygon != null); polygon.queryEnvelope2D(envelope); - assertTrue(envelope.xmin == 10 && envelope.xmax == 90 - && envelope.ymin == 10 && envelope.ymax == 90); + assertTrue(envelope.xmin == 10 && envelope.xmax == 90 && envelope.ymin == 10 && envelope.ymax == 90); assertTrue(polygon.getPointCount() == 14); assertTrue(polygon.getPathCount() == 5); - // assertTrue(polygon.calculate_area_2D() > 0.0); - // assertTrue(polygon.hasAttribute(VertexDescription.Semantics.Z)); + assertTrue(spatial_reference.getLatestID() == 3857); - // double z = polygon.getAttributeAsDbl(VertexDescription.Semantics.Z, - // 0, 0); - // assertTrue(z == 5); - - // Test import Polygon - geoJsonString = "{\"type\": \"POLYGON\", \"coordinates\": [[], [], [[10, 10, 5], [10, 20, 5], [20, 20, 5], [20, 10, 5]], [[12, 12, 3]], [], [[10, 10, 1], [12, 12, 1]], [], [[60, 60, 7], [60, 90, 7], [90, 90, 7], [60, 60, 7]], [], [[70, 70, 7], [70, 80, 7], [80, 80, 7]], []] }"; - polygon = (Polygon) (importerGeoJson.execute(0, Geometry.Type.Polygon, - geoJsonString, null).getGeometry()); + JSONObject jsonObject = new JSONObject(geoJsonString); + map_geometry = importerGeoJson.execute(0, Geometry.Type.Unknown, jsonObject, null); + polygon = (Polygon) map_geometry.getGeometry(); + spatial_reference = map_geometry.getSpatialReference(); assertTrue(polygon != null); + polygon.queryEnvelope2D(envelope); + assertTrue(envelope.xmin == 10 && envelope.xmax == 90 && envelope.ymin == 10 && envelope.ymax == 90); assertTrue(polygon.getPointCount() == 14); assertTrue(polygon.getPathCount() == 5); - // assertTrue(polygon.hasAttribute(VertexDescription.Semantics.Z)); - - geoJsonString = "{\"type\": \"MULTIPOLYGON\", \"coordinates\": [[[[90, 10, 7], [10, 10, 1], [10, 90, 7], [90, 90, 1], [90, 10, 7]]]] }"; // ring - // is - // oriented - // clockwise - polygon = (Polygon) (importerGeoJson.execute(0, Geometry.Type.Polygon, - geoJsonString, null).getGeometry()); + assertTrue(spatial_reference.getLatestID() == 3857); + + // Test Export to GeoJSON MultiPolygon + geoJsonString = exporterGeoJson.execute(GeoJsonExportFlags.geoJsonExportSkipCRS, spatial_reference, polygon); + assertTrue(geoJsonString.equals("{\"type\":\"MultiPolygon\",\"coordinates\":[[[[10,10,5],[20,10,5],[20,20,5],[10,20,5],[10,10,5]],[[12,12,3],[12,12,3],[12,12,3]],[[10,10,1],[12,12,1],[10,10,1]]],[[[90,90,88],[60,90,7],[60,60,7],[90,90,88]],[[70,70,7],[70,80,7],[80,80,7],[70,70,7]]]]}")); + + geoJsonString = exporterGeoJson.execute(0, spatial_reference, polygon); + assertTrue(geoJsonString.equals("{\"type\":\"MultiPolygon\",\"coordinates\":[[[[10,10,5],[20,10,5],[20,20,5],[10,20,5],[10,10,5]],[[12,12,3],[12,12,3],[12,12,3]],[[10,10,1],[12,12,1],[10,10,1]]],[[[90,90,88],[60,90,7],[60,60,7],[90,90,88]],[[70,70,7],[70,80,7],[80,80,7],[70,70,7]]]],\"crs\":{\"type\":\"name\",\"properties\":{\"name\":\"EPSG:3857\"}}}")); + + geoJsonString = "{\"type\": \"MultiPolygon\", \"coordinates\": [[[[90, 10, 7], [10, 10, 1], [10, 90, 7], [90, 90, 1], [90, 10, 7]]]] }"; // ring + // i // clockwise + polygon = (Polygon) (importerGeoJson.execute(0, Geometry.Type.Polygon, geoJsonString, null).getGeometry()); assertTrue(polygon != null); assertTrue(polygon.getPointCount() == 4); assertTrue(polygon.getPathCount() == 1); - // assertTrue(polygon.hasAttribute(VertexDescription.Semantics.Z)); + assertTrue(polygon.hasAttribute(VertexDescription.Semantics.Z)); assertTrue(polygon.calculateArea2D() > 0); + + // Test import Polygon + geoJsonString = "{\"type\": \"Polygon\", \"coordinates\": [[], [], [[10, 10, 5], [10, 20, 5], [20, 20, 5], [20, 10, 5]], [[12, 12, 3]], [], [[10, 10, 1], [12, 12, 1]], [], [[60, 60, 7], [60, 90, 7], [90, 90, 7], [60, 60, 7]], [], [[70, 70, 7], [70, 80, 7], [80, 80, 7]], []] }"; + map_geometry = importerGeoJson.execute(0, Geometry.Type.Polygon, geoJsonString, null); + polygon = (Polygon) map_geometry.getGeometry(); + spatial_reference = map_geometry.getSpatialReference(); + assertTrue(polygon != null); + assertTrue(polygon.getPointCount() == 14); + assertTrue(polygon.getPathCount() == 5); + assertTrue(spatial_reference.getLatestID() == 4326); + + geoJsonString = exporterGeoJson.execute(0, spatial_reference, polygon); + assertTrue(geoJsonString.equals("{\"type\":\"Polygon\",\"coordinates\":[[[10,10,5],[20,10,5],[20,20,5],[10,20,5],[10,10,5]],[[12,12,3],[12,12,3],[12,12,3]],[[10,10,1],[12,12,1],[10,10,1]],[[60,60,7],[60,90,7],[90,90,7],[60,60,7]],[[70,70,7],[70,80,7],[80,80,7],[70,70,7]]],\"crs\":{\"type\":\"name\",\"properties\":{\"name\":\"EPSG:4326\"}}}")); + + Envelope env = new Envelope(); + env.addAttribute(VertexDescription.Semantics.Z); + polygon.queryEnvelope(env); + + geoJsonString = "{\"coordinates\" : [], \"type\": \"MultiPolygon\", \"crs\":{\"esriwkt\":\"PROJCS[\\\"Gnomonic\\\",GEOGCS[\\\"GCS_WGS_1984\\\",DATUM[\\\"D_WGS_1984\\\",SPHEROID[\\\"WGS_1984\\\",6378137.0,298.257223563]],PRIMEM[\\\"Greenwich\\\",0.0],UNIT[\\\"Degree\\\",0.0174532925199433]],PROJECTION[\\\"Gnomonic\\\"],PARAMETER[\\\"Longitude_Of_Center\\\",0.0],PARAMETER[\\\"Latitude_Of_Center\\\",-45.0],UNIT[\\\"Meter\\\",1.0]]\"}}"; + map_geometry = importerGeoJson.execute(0, Geometry.Type.Polygon, geoJsonString, null); + polygon = (Polygon) map_geometry.getGeometry(); + spatial_reference = map_geometry.getSpatialReference(); + String wkt = spatial_reference.getText(); + assertTrue(wkt.equals( + "PROJCS[\"Gnomonic\",GEOGCS[\"GCS_WGS_1984\",DATUM[\"D_WGS_1984\",SPHEROID[\"WGS_1984\",6378137.0,298.257223563]],PRIMEM[\"Greenwich\",0.0],UNIT[\"Degree\",0.0174532925199433]],PROJECTION[\"Gnomonic\"],PARAMETER[\"Longitude_Of_Center\",0.0],PARAMETER[\"Latitude_Of_Center\",-45.0],UNIT[\"Meter\",1.0]]")); + + geoJsonString = "{\"coordinates\" : [], \"type\": \"MultiPolygon\", \"crs\":{\"type\":\"name\",\"properties\":{\"name\":\"PROJCS[\\\"Gnomonic\\\",GEOGCS[\\\"GCS_WGS_1984\\\",DATUM[\\\"D_WGS_1984\\\",SPHEROID[\\\"WGS_1984\\\",6378137.0,298.257223563]],PRIMEM[\\\"Greenwich\\\",0.0],UNIT[\\\"Degree\\\",0.0174532925199433]],PROJECTION[\\\"Gnomonic\\\"],PARAMETER[\\\"Longitude_Of_Center\\\",0.0],PARAMETER[\\\"Latitude_Of_Center\\\",-45.0],UNIT[\\\"Meter\\\",1.0]]\"}}}"; + map_geometry = importerGeoJson.execute(0, Geometry.Type.Polygon, geoJsonString, null); + polygon = (Polygon) map_geometry.getGeometry(); + spatial_reference = map_geometry.getSpatialReference(); + wkt = spatial_reference.getText(); + assertTrue(wkt.equals( + "PROJCS[\"Gnomonic\",GEOGCS[\"GCS_WGS_1984\",DATUM[\"D_WGS_1984\",SPHEROID[\"WGS_1984\",6378137.0,298.257223563]],PRIMEM[\"Greenwich\",0.0],UNIT[\"Degree\",0.0174532925199433]],PROJECTION[\"Gnomonic\"],PARAMETER[\"Longitude_Of_Center\",0.0],PARAMETER[\"Latitude_Of_Center\",-45.0],UNIT[\"Meter\",1.0]]")); + assertTrue(polygon != null); + assertTrue(polygon.isEmpty()); + + // AGOL exports wkt like this... + geoJsonString = "{\"coordinates\" : [], \"type\": \"MultiPolygon\", \"crs\":{\"type\":\"name\",\"properties\":{\"name\":\"ESRI:PROJCS[\\\"Gnomonic\\\",GEOGCS[\\\"GCS_WGS_1984\\\",DATUM[\\\"D_WGS_1984\\\",SPHEROID[\\\"WGS_1984\\\",6378137.0,298.257223563]],PRIMEM[\\\"Greenwich\\\",0.0],UNIT[\\\"Degree\\\",0.0174532925199433]],PROJECTION[\\\"Gnomonic\\\"],PARAMETER[\\\"Longitude_Of_Center\\\",0.0],PARAMETER[\\\"Latitude_Of_Center\\\",-45.0],UNIT[\\\"Meter\\\",1.0]]\"}}}"; + map_geometry = importerGeoJson.execute(0, Geometry.Type.Polygon, geoJsonString, null); + polygon = (Polygon) map_geometry.getGeometry(); + spatial_reference = map_geometry.getSpatialReference(); + wkt = spatial_reference.getText(); + assertTrue(wkt.equals( + "PROJCS[\"Gnomonic\",GEOGCS[\"GCS_WGS_1984\",DATUM[\"D_WGS_1984\",SPHEROID[\"WGS_1984\",6378137.0,298.257223563]],PRIMEM[\"Greenwich\",0.0],UNIT[\"Degree\",0.0174532925199433]],PROJECTION[\"Gnomonic\"],PARAMETER[\"Longitude_Of_Center\",0.0],PARAMETER[\"Latitude_Of_Center\",-45.0],UNIT[\"Meter\",1.0]]")); + assertTrue(polygon != null); + assertTrue(polygon.isEmpty()); + + boolean exceptionThrownNoWKT = false; + + try { + geoJsonString = exporterGeoJson.execute(GeoJsonExportFlags.geoJsonExportPreferMultiGeometry, + spatial_reference, polygon); + } catch (Exception e) { + exceptionThrownNoWKT = true; + } + + assertTrue(exceptionThrownNoWKT); } @Test - public static void testImportGeoJsonMultiLineString() throws JSONException { - OperatorImportFromGeoJson importerGeoJson = (OperatorImportFromGeoJson) OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.ImportFromGeoJson); - + public static void testImportGeoJsonMultiLineString() throws Exception { + OperatorImportFromGeoJson importerGeoJson = (OperatorImportFromGeoJson) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.ImportFromGeoJson); + OperatorExportToGeoJson exporterGeoJson = (OperatorExportToGeoJson) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.ExportToGeoJson); + MapGeometry map_geometry; Polyline polyline; + SpatialReference spatial_reference; String geoJsonString; Envelope2D envelope = new Envelope2D(); // Test Import from MultiLineString - - geoJsonString = "{\"type\": \"MultiLineString\", \"coordinates\": []}"; - polyline = (Polyline) (importerGeoJson.execute(0, - Geometry.Type.Unknown, geoJsonString, null).getGeometry()); + geoJsonString = "{\"type\":\"MultiLineString\",\"coordinates\":[], \"crs\" : {\"type\" : \"URL\", \"properties\" : {\"url\" : \"http://www.opengis.net/def/crs/EPSG/0/3857\"}}}"; + map_geometry = importerGeoJson.execute(0, Geometry.Type.Unknown, geoJsonString, null); + polyline = (Polyline) map_geometry.getGeometry(); + spatial_reference = map_geometry.getSpatialReference(); assertTrue(polyline != null); + assertTrue(spatial_reference != null); assertTrue(polyline.isEmpty()); - assertTrue(!polyline.hasAttribute(VertexDescription.Semantics.Z)); - assertTrue(!polyline.hasAttribute(VertexDescription.Semantics.M)); + assertTrue(spatial_reference.getLatestID() == 3857); - geoJsonString = "{\"type\": \"MultiLineString\", \"coordinates\": [[], [], [[10, 10, 5], [10, 20, 5], [20, 88, 5], [20, 10, 5]], [[12, 88, 3]], [], [[10, 10, 1], [12, 12, 1]], [], [[88, 60, 7], [60, 90, 7], [90, 90, 7]], [], [[70, 70, 7], [70, 80, 7], [80, 80, 7]], []]}"; - polyline = (Polyline) (importerGeoJson.execute(0, - Geometry.Type.Unknown, geoJsonString, null).getGeometry()); + geoJsonString = "{\"crs\" : {\"type\" : \"link\", \"properties\" : {\"href\" : \"www.spatialreference.org/ref/epsg/4309/\"}}, \"type\":\"MultiLineString\",\"coordinates\":[[], [], [[10, 10, 5], [10, 20, 5], [20, 88, 5], [20, 10, 5]], [[12, 88, 3]], [], [[10, 10, 1], [12, 12, 1]], [], [[88, 60, 7], [60, 90, 7], [90, 90, 7]], [], [[70, 70, 7], [70, 80, 7], [80, 80, 7]], []]}"; + map_geometry = importerGeoJson.execute(0, Geometry.Type.Unknown, geoJsonString, null); + polyline = (Polyline) map_geometry.getGeometry(); + spatial_reference = map_geometry.getSpatialReference(); assertTrue(polyline != null); polyline.queryEnvelope2D(envelope); - assertTrue(envelope.xmin == 10 && envelope.xmax == 90 - && envelope.ymin == 10 && envelope.ymax == 90); + assertTrue(envelope.xmin == 10 && envelope.xmax == 90 && envelope.ymin == 10 && envelope.ymax == 90); assertTrue(polyline.getPointCount() == 14); assertTrue(polyline.getPathCount() == 5); - // assertTrue(!polyline.hasAttribute(VertexDescription.Semantics.M)); + assertTrue(polyline.hasAttribute(VertexDescription.Semantics.Z)); + assertTrue(spatial_reference.getLatestID() == 4309); + + geoJsonString = exporterGeoJson.execute(0, spatial_reference, polyline); + assertTrue(geoJsonString.equals("{\"type\":\"MultiLineString\",\"coordinates\":[[[10,10,5],[10,20,5],[20,88,5],[20,10,5]],[[12,88,3],[12,88,3]],[[10,10,1],[12,12,1]],[[88,60,7],[60,90,7],[90,90,7]],[[70,70,7],[70,80,7],[80,80,7]]],\"crs\":{\"type\":\"name\",\"properties\":{\"name\":\"EPSG:4309\"}}}")); // Test Import LineString - geoJsonString = "{\"type\": \"Linestring\", \"coordinates\": [[10, 10, 5], [10, 20, 5], [20, 20, 5], [20, 10, 5]]}"; - polyline = (Polyline) (importerGeoJson.execute(0, - Geometry.Type.Unknown, geoJsonString, null).getGeometry()); + geoJsonString = "{\"type\": \"LineString\", \"coordinates\": [[10, 10, 5], [10, 20, 5], [20, 20, 5], [20, 10, 5]]}"; + polyline = (Polyline) (importerGeoJson.execute(0, Geometry.Type.Unknown, geoJsonString, null).getGeometry()); assertTrue(polyline.getPointCount() == 4); - // assertTrue(polyline.hasAttribute(VertexDescription.Semantics.Z)); + assertTrue(polyline.hasAttribute(VertexDescription.Semantics.Z)); - geoJsonString = "{\"type\": \"Linestring\", \"coordinates\": [[10, 10, 5], [10, 20, 5, 3], [20, 20, 5], [20, 10, 5]]}"; - polyline = (Polyline) (importerGeoJson.execute(0, - Geometry.Type.Unknown, geoJsonString, null).getGeometry()); + geoJsonString = "{\"type\": \"LineString\", \"coordinates\": [[10, 10, 5], [10, 20, 5, 3], [20, 20, 5], [20, 10, 5]]}"; + polyline = (Polyline) (importerGeoJson.execute(0, Geometry.Type.Unknown, geoJsonString, null).getGeometry()); assertTrue(polyline.getPointCount() == 4); - // assertTrue(polyline.hasAttribute(VertexDescription.Semantics.Z)); - // assertTrue(polyline.hasAttribute(VertexDescription.Semantics.M)); + assertTrue(polyline.hasAttribute(VertexDescription.Semantics.Z)); + assertTrue(polyline.hasAttribute(VertexDescription.Semantics.M)); + + geoJsonString = "{\"type\":\"LineString\",\"coordinates\": [[10, 10, 5], [10, 20, 5], [20, 20, 5], [], [20, 10, 5]],\"crs\" : {\"type\" : \"link\", \"properties\" : {\"href\" : \"www.opengis.net/def/crs/EPSG/0/3857\"}}}"; + map_geometry = importerGeoJson.execute(0, Geometry.Type.Unknown, geoJsonString, null); + polyline = (Polyline) (map_geometry.getGeometry()); + spatial_reference = map_geometry.getSpatialReference(); + assertTrue(polyline.getPointCount() == 4); + assertTrue(spatial_reference.getLatestID() == 3857); + geoJsonString = exporterGeoJson.execute(0, spatial_reference, polyline); + assertTrue(geoJsonString.equals("{\"type\":\"LineString\",\"coordinates\":[[10,10,5],[10,20,5],[20,20,5],[20,10,5]],\"crs\":{\"type\":\"name\",\"properties\":{\"name\":\"EPSG:3857\"}}}")); + + geoJsonString = exporterGeoJson.execute(0, null, polyline); + assertTrue(geoJsonString.equals("{\"type\":\"LineString\",\"coordinates\":[[10,10,5],[10,20,5],[20,20,5],[20,10,5]],\"crs\":null}")); } @Test - public static void testImportGeoJsonMultiPoint() throws JSONException { - OperatorImportFromGeoJson importerGeoJson = (OperatorImportFromGeoJson) OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.ImportFromGeoJson); - + public static void testImportGeoJsonMultiPoint() throws Exception { + OperatorImportFromGeoJson importerGeoJson = (OperatorImportFromGeoJson) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.ImportFromGeoJson); + OperatorExportToGeoJson exporterGeoJson = (OperatorExportToGeoJson) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.ExportToGeoJson); + MapGeometry map_geometry; MultiPoint multipoint; + SpatialReference spatial_reference; String geoJsonString; Envelope2D envelope = new Envelope2D(); // Test Import from Multi_point - geoJsonString = "{\"type\": \"MultiPoint\", \"coordinates\": []}"; - multipoint = (MultiPoint) (importerGeoJson.execute(0, - Geometry.Type.Unknown, geoJsonString, null).getGeometry()); + geoJsonString = "{\"type\":\"MultiPoint\",\"coordinates\":[]}"; + map_geometry = importerGeoJson.execute(0, Geometry.Type.Unknown, geoJsonString, null); + multipoint = (MultiPoint) map_geometry.getGeometry(); + spatial_reference = map_geometry.getSpatialReference(); assertTrue(multipoint != null); assertTrue(multipoint.isEmpty()); - assertTrue(!multipoint.hasAttribute(VertexDescription.Semantics.Z)); - assertTrue(!multipoint.hasAttribute(VertexDescription.Semantics.M)); + assertTrue(spatial_reference.getLatestID() == 4326); + + geoJsonString = exporterGeoJson.execute(0, null, multipoint); + assertTrue(geoJsonString.equals("{\"type\":\"MultiPoint\",\"coordinates\":[],\"crs\":null}")); multipoint = new MultiPoint(); - multipoint.add(118.15114354234563, 33.82234433423462345); + multipoint.add(118.15, 2); multipoint.add(88, 88); - multipoint = new MultiPoint(); + geoJsonString = exporterGeoJson.execute(GeoJsonExportFlags.geoJsonExportPrecision16, SpatialReference.create(4269), multipoint); + assertTrue(geoJsonString.equals("{\"type\":\"MultiPoint\",\"coordinates\":[[118.15,2],[88,88]],\"crs\":{\"type\":\"name\",\"properties\":{\"name\":\"EPSG:4269\"}}}")); + + multipoint.setEmpty(); multipoint.add(88, 2); multipoint.add(88, 88); - geoJsonString = "{\"type\": \"Multipoint\", \"coordinates\": [[], [], [10, 88, 88, 33], [10, 20, 5, 33], [20, 20, 5, 33], [20, 10, 5, 33], [12, 12, 3, 33], [], [10, 10, 1, 33], [12, 12, 1, 33], [], [60, 60, 7, 33], [60, 90.1, 7, 33], [90, 90, 7, 33], [], [70, 70, 7, 33], [70, 80, 7, 33], [80, 80, 7, 33], []]}"; - multipoint = (MultiPoint) (importerGeoJson.execute(0, - Geometry.Type.Unknown, geoJsonString, null).getGeometry()); + geoJsonString = exporterGeoJson.execute(0, SpatialReference.create(102100), multipoint); + assertTrue(geoJsonString.equals("{\"type\":\"MultiPoint\",\"coordinates\":[[88,2],[88,88]],\"crs\":{\"type\":\"name\",\"properties\":{\"name\":\"EPSG:3857\"}}}")); + + geoJsonString = "{\"type\":\"MultiPoint\",\"coordinates\":[[], [], [10, 88, 88, 33], [10, 20, 5, 33], [20, 20, 5, 33], [20, 10, 5, 33], [12, 12, 3, 33], [], [10, 10, 1, 33], [12, 12, 1, 33], [], [60, 60, 7, 33], [60, 90.1, 7, 33], [90, 90, 7, 33], [], [70, 70, 7, 33], [70, 80, 7, 33], [80, 80, 7, 33], []],\"crs\":{\"type\":\"OGC\",\"properties\":{\"urn\":\"urn:ogc:def:crs:OGC:1.3:CRS83\"}}}"; + map_geometry = importerGeoJson.execute(0, Geometry.Type.Unknown, geoJsonString, null); + multipoint = (MultiPoint) map_geometry.getGeometry(); + spatial_reference = map_geometry.getSpatialReference(); assertTrue(multipoint != null); - multipoint.queryEnvelope2D(envelope); - // assertTrue(envelope.xmin == 10 && envelope.xmax == 90 && - // envelope.ymin == 10 && Math.abs(envelope.ymax - 90.1) <= 0.001); assertTrue(multipoint.getPointCount() == 13); - // assertTrue(multipoint.hasAttribute(VertexDescription.Semantics.Z)); - // assertTrue(multipoint.hasAttribute(VertexDescription.Semantics.M)); + assertTrue(multipoint.hasAttribute(VertexDescription.Semantics.Z)); + assertTrue(multipoint.hasAttribute(VertexDescription.Semantics.M)); + assertTrue(spatial_reference.getLatestID() == 4269); - geoJsonString = "{\"type\": \"Multipoint\", \"coordinates\": [[10, 88, 88, 33], [10, 20, 5, 33], [20, 20, 5, 33], [20, 10, 5, 33], [12, 12, 3, 33], [10, 10, 1, 33], [12, 12, 1, 33], [60, 60, 7, 33], [60, 90.1, 7, 33], [90, 90, 7, 33], [70, 70, 7, 33], [70, 80, 7, 33], [80, 80, 7, 33]]}"; - multipoint = (MultiPoint) (importerGeoJson.execute(0, - Geometry.Type.Unknown, geoJsonString, null).getGeometry()); + geoJsonString = "{\"type\":\"MultiPoint\",\"coordinates\": [[10, 88, 88, 33], [10, 20, 5, 33], [20, 20, 5, 33], [], [20, 10, 5, 33], [12, 12, 3, 33], [], [10, 10, 1, 33], [12, 12, 1, 33], [60, 60, 7, 33], [60, 90.1, 7, 33], [90, 90, 7, 33], [70, 70, 7, 33], [70, 80, 7, 33], [80, 80, 7, 33]]}"; + multipoint = (MultiPoint) importerGeoJson.execute(0, Geometry.Type.Unknown, geoJsonString, null).getGeometry(); assertTrue(multipoint != null); - // assertTrue(envelope.xmin == 10 && envelope.xmax == 90 && - // envelope.ymin == 10 && ::fabs(envelope.ymax - 90.1) <= 0.001); assertTrue(multipoint.getPointCount() == 13); - // assertTrue(multipoint.hasAttribute(VertexDescription.Semantics.Z)); - // assertTrue(multipoint.hasAttribute(VertexDescription.Semantics.M)); + assertTrue(multipoint.hasAttribute(VertexDescription.Semantics.Z)); + assertTrue(multipoint.hasAttribute(VertexDescription.Semantics.M)); - geoJsonString = "{\"type\": \"Multipoint\", \"coordinates\": [[], [], [10, 10, 5, 33]]}"; - multipoint = (MultiPoint) (importerGeoJson.execute(0, - Geometry.Type.Unknown, geoJsonString, null).getGeometry()); + geoJsonString = exporterGeoJson.execute(GeoJsonExportFlags.geoJsonExportPrecision15, null, multipoint); + assertTrue(geoJsonString.equals("{\"type\":\"MultiPoint\",\"coordinates\":[[10,88,88,33],[10,20,5,33],[20,20,5,33],[20,10,5,33],[12,12,3,33],[10,10,1,33],[12,12,1,33],[60,60,7,33],[60,90.1,7,33],[90,90,7,33],[70,70,7,33],[70,80,7,33],[80,80,7,33]],\"crs\":null}")); + + geoJsonString = "{\"type\":\"MultiPoint\",\"coordinates\":[[], [], [10, 10, 5, 33]]}"; + multipoint = (MultiPoint) importerGeoJson.execute(0, Geometry.Type.Unknown, geoJsonString, null).getGeometry(); + + geoJsonString = exporterGeoJson.execute(0, null, multipoint); + assertTrue(geoJsonString.equals("{\"type\":\"MultiPoint\",\"coordinates\":[[10,10,5,33]],\"crs\":null}")); } @Test - public static void testImportGeoJsonPolygon() throws JSONException { - OperatorImportFromGeoJson importerGeoJson = (OperatorImportFromGeoJson) OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.ImportFromGeoJson); + public static void testImportGeoJsonPolygon() throws Exception { + OperatorImportFromGeoJson importerGeoJson = (OperatorImportFromGeoJson) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.ImportFromGeoJson); Polygon polygon; String geoJsonString; Envelope2D envelope = new Envelope2D(); // Test Import from Polygon - geoJsonString = "{\"type\": \"Polygon\", \"coordinates\": []}"; - polygon = (Polygon) (importerGeoJson.execute(0, Geometry.Type.Unknown, - geoJsonString, null).getGeometry()); + polygon = (Polygon) (importerGeoJson.execute(0, Geometry.Type.Unknown, geoJsonString, null).getGeometry()); assertTrue(polygon != null); assertTrue(polygon.isEmpty()); assertTrue(!polygon.hasAttribute(VertexDescription.Semantics.Z)); assertTrue(!polygon.hasAttribute(VertexDescription.Semantics.M)); geoJsonString = "{\"type\": \"Polygon\", \"coordinates\": [[], [[10, 10, 5], [20, 10, 5], [20, 20, 5], [10, 20, 5], [10, 10, 5]], [[12, 12, 3]], [], [[10, 10, 1], [12, 12, 1]]]}"; - polygon = (Polygon) (importerGeoJson.execute(0, Geometry.Type.Unknown, - geoJsonString, null).getGeometry()); + polygon = (Polygon) (importerGeoJson.execute(0, Geometry.Type.Unknown, geoJsonString, null).getGeometry()); assertTrue(polygon != null); polygon.queryEnvelope2D(envelope); - assertTrue(envelope.xmin == 10 && envelope.xmax == 20 - && envelope.ymin == 10 && envelope.ymax == 20); + assertTrue(envelope.xmin == 10 && envelope.xmax == 20 && envelope.ymin == 10 && envelope.ymax == 20); assertTrue(polygon.getPointCount() == 8); assertTrue(polygon.getPathCount() == 3); - // assertTrue(polygon.hasAttribute(VertexDescription.Semantics.Z)); + assertTrue(polygon.hasAttribute(VertexDescription.Semantics.Z)); - geoJsonString = "{\"type\": \"polygon\", \"coordinates\": [[[35, 10], [10, 20], [15, 40], [45, 45], [35, 10]], [[20, 30], [35, 35], [30, 20], [20, 30]]]}"; - Polygon polygon2 = (Polygon) (importerGeoJson.execute(0, - Geometry.Type.Unknown, geoJsonString, null).getGeometry()); + geoJsonString = "{\"type\": \"Polygon\", \"coordinates\": [[[35, 10], [10, 20], [15, 40], [45, 45], [35, 10]], [[20, 30], [35, 35], [30, 20], [20, 30]]]}"; + Polygon polygon2 = (Polygon) (importerGeoJson.execute(0, Geometry.Type.Unknown, geoJsonString, null).getGeometry()); assertTrue(polygon2 != null); } @Test - public static void testImportGeoJsonLineString() throws JSONException { - OperatorImportFromGeoJson importerGeoJson = (OperatorImportFromGeoJson) OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.ImportFromGeoJson); + public static void testImportGeoJsonLineString() throws Exception { + OperatorImportFromGeoJson importerGeoJson = (OperatorImportFromGeoJson) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.ImportFromGeoJson); Polyline polyline; String geoJsonString; Envelope2D envelope = new Envelope2D(); // Test Import from LineString - geoJsonString = "{\"type\": \"LineString\", \"coordinates\": []}"; - polyline = (Polyline) (importerGeoJson.execute(0, - Geometry.Type.Unknown, geoJsonString, null).getGeometry()); + polyline = (Polyline) (importerGeoJson.execute(0, Geometry.Type.Unknown, geoJsonString, null).getGeometry()); assertTrue(polyline != null); assertTrue(polyline.isEmpty()); assertTrue(!polyline.hasAttribute(VertexDescription.Semantics.Z)); assertTrue(!polyline.hasAttribute(VertexDescription.Semantics.M)); geoJsonString = "{\"type\": \"LineString\", \"coordinates\": [[10, 10, 5], [10, 20, 5], [20, 20, 5], [20, 10, 5]]}"; - polyline = (Polyline) (importerGeoJson.execute(0, - Geometry.Type.Unknown, geoJsonString, null).getGeometry()); + polyline = (Polyline) (importerGeoJson.execute(0, Geometry.Type.Unknown, geoJsonString, null).getGeometry()); assertTrue(polyline != null); polyline.queryEnvelope2D(envelope); - assertTrue(envelope.xmin == 10 && envelope.xmax == 20 - && envelope.ymin == 10 && envelope.ymax == 20); + assertTrue(envelope.xmin == 10 && envelope.xmax == 20 && envelope.ymin == 10 && envelope.ymax == 20); assertTrue(polyline.getPointCount() == 4); assertTrue(polyline.getPathCount() == 1); - // assertTrue(!polyline.hasAttribute(VertexDescription.Semantics.M)); + assertTrue(!polyline.hasAttribute(VertexDescription.Semantics.M)); } @Test - public static void testImportGeoJsonPoint() throws JSONException { - OperatorImportFromGeoJson importerGeoJson = (OperatorImportFromGeoJson) OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.ImportFromGeoJson); - + public static void testImportGeoJsonPoint() throws Exception { + OperatorImportFromGeoJson importerGeoJson = (OperatorImportFromGeoJson) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.ImportFromGeoJson); + OperatorExportToGeoJson exporterGeoJson = (OperatorExportToGeoJson) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.ExportToGeoJson); + MapGeometry map_geometry; + SpatialReference spatial_reference; Point point; String geoJsonString; // Test Import from Point + geoJsonString = "{\"type\":\"Point\",\"coordinates\":[],\"crs\":{\"type\":\"name\",\"properties\":{\"name\":\"EPSG:3857\"}}}"; + map_geometry = importerGeoJson.execute(0, Geometry.Type.Unknown, geoJsonString, null); + point = (Point) map_geometry.getGeometry(); + spatial_reference = map_geometry.getSpatialReference(); + assertTrue(spatial_reference.getLatestID() == 3857); - geoJsonString = "{\"type\": \"Point\", \"coordinates\": []}"; - point = (Point) (importerGeoJson.execute(0, Geometry.Type.Unknown, - geoJsonString, null).getGeometry()); assertTrue(point != null); assertTrue(point.isEmpty()); - assertTrue(!point.hasAttribute(VertexDescription.Semantics.Z)); - assertTrue(!point.hasAttribute(VertexDescription.Semantics.M)); - geoJsonString = "{\"type\": \"Point\", \"coordinates\": [30.1, 10.6, 5.1, 33.1]}"; - point = (Point) (importerGeoJson.execute(0, Geometry.Type.Unknown, - geoJsonString, null).getGeometry()); + geoJsonString = exporterGeoJson.execute(0, null, point); + assertTrue(geoJsonString.equals("{\"type\":\"Point\",\"coordinates\":[],\"crs\":null}")); + + geoJsonString = exporterGeoJson.execute(GeoJsonExportFlags.geoJsonExportPreferMultiGeometry, null, point); + assertTrue(geoJsonString.equals("{\"type\":\"MultiPoint\",\"coordinates\":[],\"crs\":null}")); + + geoJsonString = "{\"type\":\"Point\",\"coordinates\":[30.1,10.6,5.1,33.1],\"crs\":{\"type\":\"name\",\"properties\":{\"name\":\"urn:ogc:def:crs:ESRI::54051\"}}}"; + map_geometry = importerGeoJson.execute(0, Geometry.Type.Unknown, geoJsonString, null); + point = (Point) map_geometry.getGeometry(); + spatial_reference = map_geometry.getSpatialReference(); assertTrue(point != null); - // assertTrue(point.hasAttribute(VertexDescription.Semantics.Z)); - // assertTrue(point.hasAttribute(VertexDescription.Semantics.M)); + assertTrue(point.hasAttribute(VertexDescription.Semantics.Z)); + assertTrue(point.hasAttribute(VertexDescription.Semantics.M)); + assertTrue(spatial_reference.getLatestID() == 54051); double x = point.getX(); double y = point.getY(); - // double z = point.getZ(); - // double m = point.getM(); + double z = point.getZ(); + double m = point.getM(); assertTrue(x == 30.1); assertTrue(y == 10.6); - // assertTrue(z == 5.1); - // assertTrue(m == 33.1); + assertTrue(z == 5.1); + assertTrue(m == 33.1); + + geoJsonString = exporterGeoJson.execute(GeoJsonExportFlags.geoJsonExportPrecision15, spatial_reference, point); + assertTrue(geoJsonString.equals("{\"type\":\"Point\",\"coordinates\":[30.1,10.6,5.1,33.1],\"crs\":{\"type\":\"name\",\"properties\":{\"name\":\"ESRI:54051\"}}}")); + + geoJsonString = exporterGeoJson.execute(GeoJsonExportFlags.geoJsonExportPrecision15, SpatialReference.create(4287), point); + assertTrue(geoJsonString.equals("{\"type\":\"Point\",\"coordinates\":[30.1,10.6,5.1,33.1],\"crs\":{\"type\":\"name\",\"properties\":{\"name\":\"EPSG:4287\"}}}")); + + geoJsonString = exporterGeoJson.execute(GeoJsonExportFlags.geoJsonExportPreferMultiGeometry | GeoJsonExportFlags.geoJsonExportPrecision15, null, point); + assertTrue(geoJsonString.equals("{\"type\":\"MultiPoint\",\"coordinates\":[[30.1,10.6,5.1,33.1]],\"crs\":null}")); } @Test - public static void testImportGeoJsonSpatialReference() throws JSONException { - OperatorImportFromGeoJson importerGeoJson = (OperatorImportFromGeoJson) OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.ImportFromGeoJson); + public static void testImportExportGeoJsonMalformed() { + OperatorImportFromGeoJson importerGeoJson = (OperatorImportFromGeoJson) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.ImportFromGeoJson); + OperatorExportToGeoJson exporterGeoJson = (OperatorExportToGeoJson) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.ExportToGeoJson); + + String geoJsonString; + + try { + geoJsonString = "{\"type\":\"MultiPolygon\",\"coordinates\":[[[2,2,2],[3,3,3],[4,4,4],[2,2,2]]]}"; + importerGeoJson.execute(0, Geometry.Type.Unknown, geoJsonString, null); + assertTrue(false); + } catch (Exception e) { + } + + try { + geoJsonString = "{\"type\":\"Polygon\",\"coordinates\":[[2,2,2],[3,3,3],[4,4,4],[2,2,2]]}"; + importerGeoJson.execute(0, Geometry.Type.Unknown, geoJsonString, null); + assertTrue(false); + } catch (Exception e) { + } + + try { + geoJsonString = "{\"type\":\"Polygon\",\"coordinates\":[[[2,2,2],[3,3,3],[4,4,4],[2,2,2]],2,4]}"; + importerGeoJson.execute(0, Geometry.Type.Unknown, geoJsonString, null); + assertTrue(false); + } catch (Exception e) { + } + + try { + geoJsonString = "{\"type\":\"MultiPoint\",\"coordinates\":[[[2,2,2],[3,3,3],[4,4,4],[2,2,2]]]}"; + importerGeoJson.execute(0, Geometry.Type.Unknown, geoJsonString, null); + assertTrue(false); + } catch (Exception e) { + } + + try { + geoJsonString = "{\"type\":\"LineString\",\"coordinates\":[[[2,2,2],[3,3,3],[4,4,4],[2,2,2]]]}"; + importerGeoJson.execute(0, Geometry.Type.Unknown, geoJsonString, null); + assertTrue(false); + } catch (Exception e) { + } + + try { + geoJsonString = "{\"type\":\"MultiPoint\",\"coordinates\":[[2,2,2],[3,3,3],[4,4,4],[2,2,2],[[]]]}"; + importerGeoJson.execute(0, Geometry.Type.Unknown, geoJsonString, null); + assertTrue(false); + } catch (Exception e) { + } + + try { + geoJsonString = "{\"type\":\"MultiPolygon\",\"coordinates\":[[[[2,2,2],[3,3,3],[4,4,4],[2,2,2],[[]]]]]}"; + importerGeoJson.execute(0, Geometry.Type.Unknown, geoJsonString, null); + assertTrue(false); + } catch (Exception e) { + } + + try { + geoJsonString = "{\"type\":\"MultiPolygon\",\"coordinates\":[[[[2,2,2],[3,3,3],[4,4,4],[2,2,2]],[1,1,1],[2,2,2],[3,3,3],[1,1,1]]]}"; + importerGeoJson.execute(0, Geometry.Type.Unknown, geoJsonString, null); + assertTrue(false); + } catch (Exception e) { + } + + try { + geoJsonString = "{\"type\":\"Polygon\",\"coordinates\":[[[2,2,2],[3,3,3],[4,4,4],[2,2,2]],[1,1,1],[2,2,2],[3,3,3],[1,1,1]]}"; + importerGeoJson.execute(0, Geometry.Type.Unknown, geoJsonString, null); + assertTrue(false); + } catch (Exception e) { + } + + try { + geoJsonString = "{\"type\":\"MultiPolygon\",\"coordinates\":[[[[[]]]]}"; + importerGeoJson.execute(0, Geometry.Type.Unknown, geoJsonString, null); + assertTrue(false); + } catch (Exception e) { + } + + try { + geoJsonString = "{\"type\":\"MultiPolygon\",\"coordinates\":[[[[{}]]]}"; + importerGeoJson.execute(0, Geometry.Type.Unknown, geoJsonString, null); + assertTrue(false); + } catch (Exception e) { + } + + try { + geoJsonString = "{\"type\":\"Point\",\"coordinates\":[30.1,10.6,[],33.1],\"crs\":\"EPSG:3857\"}"; + importerGeoJson.execute(0, Geometry.Type.Unknown, geoJsonString, null); + assertTrue(false); + } catch (Exception e) { + } + } + + @Test + public static void testImportGeoJsonSpatialReference() throws Exception { + OperatorImportFromGeoJson importerGeoJson = (OperatorImportFromGeoJson) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.ImportFromGeoJson); String geoJsonString4326; String geoJsonString3857; // Test Import from Point - geoJsonString4326 = "{\"type\": \"Point\", \"coordinates\": [3.0, 5.0], \"crs\": \"EPSG:4326\"}"; geoJsonString3857 = "{\"type\": \"Point\", \"coordinates\": [3.0, 5.0], \"crs\": \"EPSG:3857\"}"; - MapGeometry mapGeometry4326 = importerGeoJson.execute(0, - Geometry.Type.Unknown, geoJsonString4326, null); - MapGeometry mapGeometry3857 = importerGeoJson.execute(0, - Geometry.Type.Unknown, geoJsonString3857, null); + MapGeometry mapGeometry4326 = importerGeoJson.execute(0, Geometry.Type.Unknown, geoJsonString4326, null); + MapGeometry mapGeometry3857 = importerGeoJson.execute(0, Geometry.Type.Unknown, geoJsonString3857, null); assertTrue(mapGeometry4326.equals(mapGeometry3857) == false); - assertTrue(mapGeometry4326.getGeometry().equals( - mapGeometry3857.getGeometry())); + assertTrue(mapGeometry4326.getGeometry().equals(mapGeometry3857.getGeometry())); } public static Polygon makePolygon() { diff --git a/src/test/java/com/esri/core/geometry/TestIntersection.java b/src/test/java/com/esri/core/geometry/TestIntersection.java index ab7dd9f0..8e3f7fcc 100644 --- a/src/test/java/com/esri/core/geometry/TestIntersection.java +++ b/src/test/java/com/esri/core/geometry/TestIntersection.java @@ -1,8 +1,11 @@ package com.esri.core.geometry; import junit.framework.TestCase; + import org.junit.Test; +import com.esri.core.geometry.PolygonUtils.PiPResult; + //import java.util.Random; public class TestIntersection extends TestCase { @@ -1038,4 +1041,47 @@ public void testIntersectionIssue2() { boolean eq = OperatorEquals.local().execute(g, polyline, sr, null); assertTrue(eq); } + + /* + Point2D uniqueIntersectionPointOfNonDisjointGeometries(Geometry g1, Geometry g2, SpatialReference sr) { + Geometry g1Test = g1; + boolean g1Polygon = g1.getType() == Geometry.Type.Polygon; + boolean g2Polygon = g2.getType() == Geometry.Type.Polygon; + + if (g1Polygon || g2Polygon) + { + if (g1Polygon) { + Point2D p = getFirstPoint(g2); + if (PolygonUtils.isPointInPolygon2D((Polygon)g1, p, 0) != PiPResult.PiPOutside) + return p; + } + if (g2Polygon) { + Point2D p = getFirstPoint(g1); + if (PolygonUtils.isPointInPolygon2D((Polygon)g2, p, 0) != PiPResult.PiPOutside) + return p; + } + } + + if (g1Polygon) + { + Polyline polyline = new Polyline(); + polyline.add((MultiPath)g1, false); + g1Test = polyline; + } + Geometry g2Test = g2; + if (g2Polygon) + { + Polyline polyline = new Polyline(); + polyline.add((MultiPath)g2, false); + g2Test = polyline; + } + + GeometryCursor gc = OperatorIntersection.local().execute(new SimpleGeometryCursor(g1Test), new SimpleGeometryCursor(g2Test), sr, null, 3); + for (Geometry res = gc.next(); res != null; res = gc.next()) { + return getFirstPoint(res); + } + + throw new GeometryException("internal error"); + }*/ + } diff --git a/src/test/java/com/esri/core/geometry/TestOGC.java b/src/test/java/com/esri/core/geometry/TestOGC.java index dcca0c3a..5b2ef6b3 100644 --- a/src/test/java/com/esri/core/geometry/TestOGC.java +++ b/src/test/java/com/esri/core/geometry/TestOGC.java @@ -14,7 +14,6 @@ import com.esri.core.geometry.ogc.OGCConcreteGeometryCollection; import org.codehaus.jackson.JsonParseException; -import org.json.JSONException; import org.junit.Test; import java.io.IOException; @@ -40,6 +39,8 @@ public void testPoint() { assertTrue(p.Y() == 2); assertTrue(g.equals(OGCGeometry.fromText("POINT(1 2)"))); assertTrue(!g.equals(OGCGeometry.fromText("POINT(1 3)"))); + assertTrue(g.equals((Object)OGCGeometry.fromText("POINT(1 2)"))); + assertTrue(!g.equals((Object)OGCGeometry.fromText("POINT(1 3)"))); OGCGeometry buf = g.buffer(10); assertTrue(buf.geometryType().equals("Polygon")); OGCPolygon poly = (OGCPolygon) buf.envelope(); @@ -48,7 +49,7 @@ public void testPoint() { } @Test - public void testPolygon() { + public void testPolygon() throws Exception { OGCGeometry g = OGCGeometry .fromText("POLYGON((-10 -10, 10 -10, 10 10, -10 10, -10 -10), (-5 -5, -5 5, 5 5, 5 -5, -5 -5))"); assertTrue(g.geometryType().equals("Polygon")); @@ -64,14 +65,26 @@ public void testPolygon() { b = lsi.equals(OGCGeometry .fromText("LINESTRING(-5 -5, -5 5, 5 5, 5 -5, -5 -5)")); assertTrue(b); + b = lsi.equals((Object)OGCGeometry + .fromText("LINESTRING(-5 -5, -5 5, 5 5, 5 -5, -5 -5)")); assertTrue(!lsi.equals(ls)); OGCMultiCurve boundary = p.boundary(); String s = boundary.asText(); assertTrue(s.equals("MULTILINESTRING ((-10 -10, 10 -10, 10 10, -10 10, -10 -10), (-5 -5, -5 5, 5 5, 5 -5, -5 -5))")); + + { + OGCGeometry g2 = OGCGeometry.fromGeoJson("{\"type\": \"Polygon\", \"coordinates\": [[[1.00000001,1.00000001], [4.00000001,1.00000001], [4.00000001,4.00000001], [1.00000001,4.00000001]]]}"); + OGCGeometry.fromGeoJson("{\"type\": \"LineString\", \"coordinates\": [[1.00000001,1.00000001], [7.00000001,8.00000001]]}").intersects(g2); + OGCGeometry.fromGeoJson("{\"type\": \"LineString\", \"coordinates\": [[2.449,4.865], [7.00000001,8.00000001]]}").intersects(g2); + + OGCGeometry g3 = OGCGeometry.fromGeoJson("{\"type\": \"Polygon\", \"coordinates\": [[[1.00000001,1.00000001], [4.00000001,1.00000001], [4.00000001,4.00000001], [1.00000001,4.00000001]]]}"); + boolean bb = g2.equals((Object)g3); + assertTrue(bb); + } } @Test - public void testGeometryCollection() throws JSONException { + public void testGeometryCollection() throws Exception { OGCGeometry g = OGCGeometry .fromText("GEOMETRYCOLLECTION(POLYGON EMPTY, POINT(1 1), LINESTRING EMPTY, MULTIPOLYGON EMPTY, MULTILINESTRING EMPTY)"); assertTrue(g.geometryType().equals("GeometryCollection")); @@ -139,6 +152,10 @@ public void testGeometryCollection() throws JSONException { wktString = g.asText(); assertTrue(wktString .equals("GEOMETRYCOLLECTION (POLYGON EMPTY, POINT (1 1), GEOMETRYCOLLECTION EMPTY, LINESTRING EMPTY, GEOMETRYCOLLECTION (POLYGON EMPTY, POINT (1 1), LINESTRING EMPTY, MULTIPOLYGON EMPTY, MULTILINESTRING EMPTY, MULTIPOINT EMPTY), MULTIPOLYGON EMPTY, MULTILINESTRING EMPTY)")); + + assertTrue(g.equals((Object)OGCGeometry.fromText(wktString))); + + assertTrue(g.hashCode() == OGCGeometry.fromText(wktString).hashCode()); } @@ -768,7 +785,7 @@ public void testIsectTria1() { } @Test - public void testIsectTriaJson1() throws JsonParseException, IOException { + public void testIsectTriaJson1() throws Exception { String json1 = "{\"rings\":[[[1, 0], [3, 0], [1, 2], [1, 0]]], \"spatialReference\":{\"wkid\":4326}}"; String json2 = "{\"rings\":[[[0, 1], [2, 1], [0, 3], [0, 1]]], \"spatialReference\":{\"wkid\":4326}}"; OGCGeometry g0 = OGCGeometry.fromJson(json1); @@ -868,7 +885,7 @@ public void testMultiPolygonArea() { } @Test - public void testPolylineSimplifyIssueGithub52() throws JsonParseException, IOException { + public void testPolylineSimplifyIssueGithub52() throws Exception { String json = "{\"paths\":[[[2,0],[4,3],[5,1],[3.25,1.875],[1,3]]],\"spatialReference\":{\"wkid\":4326}}"; { OGCGeometry g = OGCGeometry.fromJson(json); diff --git a/src/test/java/com/esri/core/geometry/TestPolygon.java b/src/test/java/com/esri/core/geometry/TestPolygon.java index 8901df4e..e34a14b7 100644 --- a/src/test/java/com/esri/core/geometry/TestPolygon.java +++ b/src/test/java/com/esri/core/geometry/TestPolygon.java @@ -1,7 +1,10 @@ package com.esri.core.geometry; +import java.io.IOException; + import junit.framework.TestCase; +import org.json.JSONException; import org.junit.Test; import com.esri.core.geometry.ogc.OGCGeometry; @@ -1199,5 +1202,104 @@ public void testReplaceNaNs() { assertTrue(b); } - } + } + + @Test + public void testPolygon2PolygonFails() throws IOException, JSONException { + OperatorFactoryLocal factory = OperatorFactoryLocal.getInstance(); + OperatorExportToGeoJson exporter = (OperatorExportToGeoJson) factory + .getOperator(Operator.Type.ExportToGeoJson); + String result = exporter.execute(birmingham()); + + OperatorImportFromGeoJson importer = (OperatorImportFromGeoJson) factory + .getOperator(Operator.Type.ImportFromGeoJson); + MapGeometry mapGeometry = importer.execute( + GeoJsonImportFlags.geoJsonImportDefaults, + Geometry.Type.Polygon, result, null); + Polygon polygon = (Polygon) mapGeometry.getGeometry(); + assertEquals(birmingham(), polygon); + } + + @Test + public void testPolygon2PolygonFails2() throws JSONException { + String birminghamGeojson = GeometryEngine + .geometryToGeoJson(birmingham()); + MapGeometry returnedGeometry = GeometryEngine.geometryFromGeoJson( + birminghamGeojson, GeoJsonImportFlags.geoJsonImportDefaults, + Geometry.Type.Polygon); + Polygon polygon = (Polygon) returnedGeometry.getGeometry(); + assertEquals(polygon, birmingham()); + } + + @Test + public void testPolygon2PolygonWorks() throws JSONException { + String birminghamGeojson = GeometryEngine + .geometryToGeoJson(birmingham()); + MapGeometry returnedGeometry = GeometryEngine.geometryFromGeoJson( + birminghamGeojson, GeoJsonImportFlags.geoJsonImportDefaults, + Geometry.Type.Polygon); + Polygon polygon = (Polygon) returnedGeometry.getGeometry(); + assertEquals(polygon.toString(), birmingham().toString()); + } + + @Test + public void testPolygon2Polygon2Works() throws JSONException, IOException { + String birminghamJson = GeometryEngine.geometryToJson(4326, + birmingham()); + MapGeometry returnedGeometry = GeometryEngine + .jsonToGeometry(birminghamJson); + Polygon polygon = (Polygon) returnedGeometry.getGeometry(); + assertEquals(polygon, birmingham()); + String s = polygon.toString(); + } + + @Test + public void testSegmentIteratorCrash() { + Polygon poly = new Polygon(); + + // clockwise => outer ring + poly.startPath(0, 0); + poly.lineTo(-0.5, 0.5); + poly.lineTo(0.5, 1); + poly.lineTo(1, 0.5); + poly.lineTo(0.5, 0); + + // hole + poly.startPath(0.5, 0.2); + poly.lineTo(0.6, 0.5); + poly.lineTo(0.2, 0.9); + poly.lineTo(-0.2, 0.5); + poly.lineTo(0.1, 0.2); + poly.lineTo(0.2, 0.3); + + // island + poly.startPath(0.1, 0.7); + poly.lineTo(0.3, 0.7); + poly.lineTo(0.3, 0.4); + poly.lineTo(0.1, 0.4); + + assertEquals(poly.getSegmentCount(), 15); + assertEquals(poly.getPathCount(), 3); + SegmentIterator segmentIterator = poly.querySegmentIterator(); + int paths = 0; + int segments = 0; + while (segmentIterator.nextPath()) { + paths++; + Segment segment; + while (segmentIterator.hasNextSegment()) { + segment = segmentIterator.nextSegment(); + segments++; + } + } + assertEquals(paths, 3); + assertEquals(segments, 15); + } + + private static Polygon birmingham() { + Polygon poly = new Polygon(); + poly.addEnvelope(new Envelope(-1.954245, 52.513531, -1.837357, + 52.450123), false); + poly.addEnvelope(new Envelope(0, 0, 1, 1), false); + return poly; + } } diff --git a/src/test/java/com/esri/core/geometry/TestQuadTree.java b/src/test/java/com/esri/core/geometry/TestQuadTree.java index 7ffdc06d..6dde9220 100644 --- a/src/test/java/com/esri/core/geometry/TestQuadTree.java +++ b/src/test/java/com/esri/core/geometry/TestQuadTree.java @@ -1,6 +1,12 @@ package com.esri.core.geometry; import java.util.ArrayList; +import java.util.Random; +import java.util.HashMap; +import java.util.Collection; +import java.util.Iterator; +import java.util.Set; +import java.util.Map; import junit.framework.TestCase; @@ -19,11 +25,24 @@ protected void tearDown() throws Exception { @Test public static void test1() { + + { + QuadTree quad_tree = new QuadTree(Envelope2D.construct(-10, -10, 10, 10), 8); + + QuadTree.QuadTreeIterator qt = quad_tree.getIterator(true); + assertTrue(qt.next() == -1); + + qt.resetIterator(Envelope2D.construct(0, 0, 0, 0), 0); + + assertTrue(quad_tree.getIntersectionCount(Envelope2D.construct(0, 0, 0, 0), 0, 10) == 0); + assertTrue(quad_tree.getElementCount() == 0); + } + Polyline polyline; polyline = makePolyline(); MultiPathImpl polylineImpl = (MultiPathImpl) polyline._getImpl(); - QuadTree quadtree = buildQuadTree_(polylineImpl); + QuadTree quadtree = buildQuadTree_(polylineImpl, false); Line queryline = new Line(34, 9, 66, 46); QuadTree.QuadTreeIterator qtIter = quadtree.getIterator(); @@ -37,12 +56,12 @@ public static void test1() { assertTrue(index == 6 || index == 8 || index == 14); element_handle = qtIter.next(); } - + Envelope2D envelope = new Envelope2D(34, 9, 66, 46); Polygon queryPolygon = new Polygon(); queryPolygon.addEnvelope(envelope, true); - qtIter.resetIterator(queryline, 0.0); + qtIter.resetIterator(queryline, 0.0); element_handle = qtIter.next(); while (element_handle > 0) { @@ -52,6 +71,336 @@ public static void test1() { } } + @Test + public static void testQuadTreeWithDuplicates() { + int pass_count = 10; + int figure_size = 400; + int figure_size2 = 100; + Envelope extent1 = new Envelope(); + extent1.setCoords(-100000, -100000, 100000, 100000); + + RandomCoordinateGenerator generator1 = new RandomCoordinateGenerator(Math.max(figure_size, 10000), extent1, 0.001); + Random random = new Random(2013); + int rand_max = 32; + + Polygon poly_red = new Polygon(); + Polygon poly_blue = new Polygon(); + + int r = figure_size; + + for (int c = 0; c < pass_count; c++) { + Point pt; + for (int j = 0; j < r; j++) { + int rand = random.nextInt(rand_max); + boolean b_random_new = r > 10 && ((1.0 * rand) / rand_max > 0.95); + pt = generator1.GetRandomCoord(); + if (j == 0 || b_random_new) + poly_blue.startPath(pt); + else + poly_blue.lineTo(pt); + } + + Envelope2D env = new Envelope2D(); + + QuadTree quad_tree_blue = buildQuadTree_((MultiPathImpl) poly_blue._getImpl(), false); + QuadTree quad_tree_blue_duplicates = buildQuadTree_((MultiPathImpl) poly_blue._getImpl(), true); + + Envelope2D e1 = quad_tree_blue.getDataExtent(); + Envelope2D e2 = quad_tree_blue_duplicates.getDataExtent(); + assertTrue(e1.equals(e2)); + assertTrue(quad_tree_blue.getElementCount() == poly_blue.getSegmentCount()); + + SegmentIterator seg_iter_blue = poly_blue.querySegmentIterator(); + + poly_red.setEmpty(); + + r = figure_size2; + if (r < 3) + continue; + + for (int j = 0; j < r; j++) { + int rand = random.nextInt(rand_max); + boolean b_random_new = r > 10 && ((1.0 * rand) / rand_max > 0.95); + pt = generator1.GetRandomCoord(); + if (j == 0 || b_random_new) + poly_red.startPath(pt); + else + poly_red.lineTo(pt); + } + + QuadTree.QuadTreeIterator iterator = quad_tree_blue.getIterator(); + SegmentIteratorImpl seg_iter_red = ((MultiPathImpl) poly_red._getImpl()).querySegmentIterator(); + + HashMap map1 = new HashMap(0); + + int count = 0; + int intersections_per_query = 0; + while (seg_iter_red.nextPath()) { + while (seg_iter_red.hasNextSegment()) { + Segment segment_red = seg_iter_red.nextSegment(); + segment_red.queryEnvelope2D(env); + + iterator.resetIterator(env, 0.0); + + int count_upper = 0; + int element_handle; + while ((element_handle = iterator.next()) != -1) { + count_upper++; + int index = quad_tree_blue.getElement(element_handle); + Boolean iter = (Boolean) map1.get(new Integer(index)); + if (iter == null) { + count++; + map1.put(new Integer(index), new Boolean(true)); + } + + intersections_per_query++; + } + + int intersection_count = quad_tree_blue.getIntersectionCount(env, 0.0, -1); + assertTrue(intersection_count == count_upper); + } + } + + seg_iter_red.resetToFirstPath(); + + HashMap map2 = new HashMap(0); + QuadTree.QuadTreeIterator iterator_duplicates = quad_tree_blue_duplicates.getIterator(); + + int count_duplicates = 0; + int intersections_per_query_duplicates = 0; + while (seg_iter_red.nextPath()) { + while (seg_iter_red.hasNextSegment()) { + Segment segment_red = seg_iter_red.nextSegment(); + segment_red.queryEnvelope2D(env); + + iterator_duplicates.resetIterator(env, 0.0); + + int count_lower = 0; + HashMap map_per_query = new HashMap(0); + + int count_upper = 0; + int element_handle; + while ((element_handle = iterator_duplicates.next()) != -1) { + count_upper++; + int index = quad_tree_blue_duplicates.getElement(element_handle); + Boolean iter = (Boolean) map2.get(new Integer(index)); + if (iter == null) { + count_duplicates++; + map2.put(new Integer(index), new Boolean(true)); + } + + Boolean iter_lower = (Boolean) map_per_query.get(index); + if (iter_lower == null) { + count_lower++; + intersections_per_query_duplicates++; + map_per_query.put(new Integer(index), new Boolean(true)); + } + + int q = quad_tree_blue_duplicates.getQuad(element_handle); + assertTrue(quad_tree_blue_duplicates.getSubTreeElementCount(q) >= quad_tree_blue_duplicates.getContainedSubTreeElementCount(q)); + } + + int intersection_count = quad_tree_blue_duplicates.getIntersectionCount(env, 0.0, -1); + boolean b_has_data = quad_tree_blue_duplicates.hasData(env, 0.0); + assertTrue(b_has_data || intersection_count == 0); + assertTrue(count_lower <= intersection_count && intersection_count <= count_upper); + assertTrue(count_upper <= 4 * count_lower); + } + } + + assertTrue(count == count_duplicates); + assertTrue(intersections_per_query == intersections_per_query_duplicates); + } + } + + @Test + public static void testSortedIterator() { + int pass_count = 10; + int figure_size = 400; + int figure_size2 = 100; + Envelope extent1 = new Envelope(); + extent1.setCoords(-100000, -100000, 100000, 100000); + + RandomCoordinateGenerator generator1 = new RandomCoordinateGenerator(Math.max(figure_size, 10000), extent1, 0.001); + + Random random = new Random(2013); + int rand_max = 32; + + Polygon poly_red = new Polygon(); + Polygon poly_blue = new Polygon(); + + int r = figure_size; + + for (int c = 0; c < pass_count; c++) { + Point pt; + for (int j = 0; j < r; j++) { + int rand = random.nextInt(rand_max); + boolean b_random_new = r > 10 && ((1.0 * rand) / rand_max > 0.95); + pt = generator1.GetRandomCoord(); + if (j == 0 || b_random_new) + poly_blue.startPath(pt); + else + poly_blue.lineTo(pt); + } + + Envelope2D env = new Envelope2D(); + + QuadTree quad_tree_blue = buildQuadTree_((MultiPathImpl) poly_blue._getImpl(), false); + + Envelope2D e1 = quad_tree_blue.getDataExtent(); + assertTrue(quad_tree_blue.getElementCount() == poly_blue.getSegmentCount()); + + SegmentIterator seg_iter_blue = poly_blue.querySegmentIterator(); + + poly_red.setEmpty(); + + r = figure_size2; + if (r < 3) + continue; + + for (int j = 0; j < r; j++) { + int rand = random.nextInt(rand_max); + boolean b_random_new = r > 10 && ((1.0 * rand) / rand_max > 0.95); + pt = generator1.GetRandomCoord(); + if (j == 0 || b_random_new) + poly_red.startPath(pt); + else + poly_red.lineTo(pt); + } + + QuadTree.QuadTreeIterator iterator = quad_tree_blue.getIterator(); + SegmentIteratorImpl seg_iter_red = ((MultiPathImpl) poly_red._getImpl()).querySegmentIterator(); + + HashMap map1 = new HashMap(0); + + int count = 0; + int intersections_per_query = 0; + while (seg_iter_red.nextPath()) { + while (seg_iter_red.hasNextSegment()) { + Segment segment_red = seg_iter_red.nextSegment(); + segment_red.queryEnvelope2D(env); + + iterator.resetIterator(env, 0.0); + + int count_upper = 0; + int element_handle; + while ((element_handle = iterator.next()) != -1) { + count_upper++; + int index = quad_tree_blue.getElement(element_handle); + Boolean iter = (Boolean) map1.get(index); + if (iter == null) { + count++; + map1.put(new Integer(index), new Boolean(true)); + } + + intersections_per_query++; + } + + int intersection_count = quad_tree_blue.getIntersectionCount(env, 0.0, -1); + assertTrue(intersection_count == count_upper); + } + } + + seg_iter_red.resetToFirstPath(); + + HashMap map2 = new HashMap(0); + QuadTree.QuadTreeIterator sorted_iterator = quad_tree_blue.getIterator(true); + + int count_sorted = 0; + int intersections_per_query_sorted = 0; + while (seg_iter_red.nextPath()) { + while (seg_iter_red.hasNextSegment()) { + Segment segment_red = seg_iter_red.nextSegment(); + segment_red.queryEnvelope2D(env); + + sorted_iterator.resetIterator(env, 0.0); + + int count_upper_sorted = 0; + int element_handle; + int last_index = -1; + while ((element_handle = sorted_iterator.next()) != -1) { + count_upper_sorted++; + int index = quad_tree_blue.getElement(element_handle); + assertTrue(last_index < index); // ensure the element handles are returned in sorted order + last_index = index; + Boolean iter = (Boolean) map2.get(index); + if (iter == null) { + count_sorted++; + map2.put(new Integer(index), new Boolean(true)); + } + + intersections_per_query_sorted++; + } + + int intersection_count = quad_tree_blue.getIntersectionCount(env, 0.0, -1); + assertTrue(intersection_count == count_upper_sorted); + } + } + + assertTrue(count == count_sorted); + assertTrue(intersections_per_query == intersections_per_query_sorted); + } + } + + @Test + public static void test_perf_quad_tree() { + Envelope extent1 = new Envelope(); + extent1.setCoords(-1000, -1000, 1000, 1000); + + RandomCoordinateGenerator generator1 = new RandomCoordinateGenerator(1000, extent1, 0.001); + //HiResTimer timer; + for (int N = 16; N <= 1024/**1024*/; N *= 2) { + //timer.StartMeasurement(); + + Envelope2D extent = new Envelope2D(); + extent.setCoords(-1000, -1000, 1000, 1000); + HashMap data = new HashMap(0); + QuadTree qt = new QuadTree(extent, 10); + for (int i = 0; i < N; i++) { + Envelope2D env = new Envelope2D(); + Point2D center = generator1.GetRandomCoord().getXY(); + double w = 10; + env.setCoords(center, w, w); + env.intersect(extent); + if (env.isEmpty()) + continue; + + int h = qt.insert(i, env); + data.put(new Integer(h), env); + } + + int ecount = 0; + AttributeStreamOfInt32 handles = new AttributeStreamOfInt32(0); + QuadTree.QuadTreeIterator iter = qt.getIterator(); + + Iterator> pairs = data.entrySet().iterator(); + while (pairs.hasNext()) { + Map.Entry entry = pairs.next(); + iter.resetIterator((Envelope2D) entry.getValue(), 0.001); + boolean remove_self = false; + for (int h = iter.next(); h != -1; h = iter.next()) { + if (h != entry.getKey().intValue()) + handles.add(h); + else { + remove_self = true; + } + + ecount++; + } + + for (int i = 0; i < handles.size(); i++) { + qt.removeElement(handles.get(i));//remove elements that were selected. + } + + if (remove_self) + qt.removeElement(entry.getKey().intValue()); + handles.resize(0); + } + + //printf("%d %0.3f (%I64d, %f, mem %I64d)\n", N, timer.GetMilliseconds(), ecount, ecount / double(N * N), memsize); + } + } + @Test public static void test2() { MultiPoint multipoint = new MultiPoint(); @@ -81,7 +430,7 @@ public static void test2() { assertTrue(count == 10000); } - + public static Polyline makePolyline() { Polyline poly = new Polyline(); @@ -120,10 +469,10 @@ public static Polyline makePolyline() { return poly; } - static QuadTree buildQuadTree_(MultiPathImpl multipathImpl) { + static QuadTree buildQuadTree_(MultiPathImpl multipathImpl, boolean bStoreDuplicates) { Envelope2D extent = new Envelope2D(); multipathImpl.queryEnvelope2D(extent); - QuadTree quadTree = new QuadTree(extent, 8); + QuadTree quadTree = new QuadTree(extent, 8, bStoreDuplicates); int hint_index = -1; Envelope2D boundingbox = new Envelope2D(); SegmentIteratorImpl seg_iter = multipathImpl.querySegmentIterator(); diff --git a/src/test/java/com/esri/core/geometry/TestRelation.java b/src/test/java/com/esri/core/geometry/TestRelation.java index ec21049d..5c00d8ad 100644 --- a/src/test/java/com/esri/core/geometry/TestRelation.java +++ b/src/test/java/com/esri/core/geometry/TestRelation.java @@ -1,7 +1,10 @@ package com.esri.core.geometry; +import java.io.IOException; + import junit.framework.TestCase; +import org.codehaus.jackson.JsonParseException; import org.junit.Test; import com.esri.core.geometry.Geometry.GeometryAccelerationDegree; @@ -5481,7 +5484,7 @@ public void testCrosses_github_issue_40() { null); assertTrue(answer2); } - + @Test public void testDisjointCrash() { Polygon g1 = new Polygon(); @@ -5492,5 +5495,14 @@ public void testDisjointCrash() { OperatorDisjoint.local().accelerateGeometry(g1, SpatialReference.create(4267), GeometryAccelerationDegree.enumHot); boolean res = OperatorDisjoint.local().execute(g1, g2, SpatialReference.create(4267), null); assertTrue(!res); - } + } + + @Test + public void testDisjointFail() throws JsonParseException, IOException { + MapGeometry geometry1 = OperatorImportFromJson.local().execute(Geometry.Type.Unknown, "{\"paths\":[[[3,3],[3,3]]],\"spatialReference\":{\"wkid\":4326}}"); + MapGeometry geometry2 = OperatorImportFromJson.local().execute(Geometry.Type.Unknown, "{\"rings\":[[[2,2],[2,4],[4,4],[4,2],[2,2]]],\"spatialReference\":{\"wkid\":4326}}"); + OperatorDisjoint.local().accelerateGeometry(geometry1.getGeometry(), geometry1.getSpatialReference(), GeometryAccelerationDegree.enumMedium); + boolean res = OperatorDisjoint.local().execute(geometry1.getGeometry(), geometry2.getGeometry(), geometry1.getSpatialReference(), null); + assertTrue(!res); + } } diff --git a/src/test/java/com/esri/core/geometry/TestSerialization.java b/src/test/java/com/esri/core/geometry/TestSerialization.java index 289546da..4d736a8c 100644 --- a/src/test/java/com/esri/core/geometry/TestSerialization.java +++ b/src/test/java/com/esri/core/geometry/TestSerialization.java @@ -37,18 +37,17 @@ public void testSerializePoint() { } - // try - // { - // FileOutputStream streamOut = new FileOutputStream(m_thisDirectory + - // "savedPoint.txt"); - // ObjectOutputStream oo = new ObjectOutputStream(streamOut); - // Point pt = new Point(10, 40); - // oo.writeObject(pt); - // } - // catch(Exception ex) - // { - // fail("Point serialization failure"); - // } + //try + //{ + //FileOutputStream streamOut = new FileOutputStream("c:/temp/savedPoint1.txt"); + //ObjectOutputStream oo = new ObjectOutputStream(streamOut); + //Point pt = new Point(10, 40, 2); + //oo.writeObject(pt); + //} + //catch(Exception ex) + //{ + //fail("Point serialization failure"); + //} try { InputStream s = TestSerialization.class @@ -59,6 +58,17 @@ public void testSerializePoint() { } catch (Exception ex) { fail("Point serialization failure"); } + + try { + InputStream s = TestSerialization.class + .getResourceAsStream("savedPoint1.txt"); + ObjectInputStream ii = new ObjectInputStream(s); + Point ptRes = (Point) ii.readObject(); + assertTrue(ptRes.getX() == 10 && ptRes.getY() == 40 && ptRes.getZ() == 2); + } catch (Exception ex) { + fail("Point serialization failure"); + } + } @Test @@ -98,22 +108,21 @@ public void testSerializePolygon() { fail("Polygon serialization failure"); } - // try - // { - // FileOutputStream streamOut = new FileOutputStream(m_thisDirectory + - // "savedPolygon.txt"); - // ObjectOutputStream oo = new ObjectOutputStream(streamOut); - // Polygon pt = new Polygon(); - // pt.startPath(10, 10); - // pt.lineTo(100, 100); - // pt.lineTo(200, 100); - // pt = (Polygon)GeometryEngine.simplify(pt, null); - // oo.writeObject(pt); - // } - // catch(Exception ex) - // { - // fail("Polygon serialization failure"); - // } + //try + //{ + //FileOutputStream streamOut = new FileOutputStream("c:/temp/savedPolygon1.txt"); + //ObjectOutputStream oo = new ObjectOutputStream(streamOut); + //Polygon pt = new Polygon(); + //pt.startPath(10, 10); + //pt.lineTo(100, 100); + //pt.lineTo(200, 100); + //pt = (Polygon)GeometryEngine.simplify(pt, null); + //oo.writeObject(pt); + //} + //catch(Exception ex) + //{ + //fail("Polygon serialization failure"); + //} try { InputStream s = TestSerialization.class @@ -124,6 +133,15 @@ public void testSerializePolygon() { } catch (Exception ex) { fail("Polygon serialization failure"); } + try { + InputStream s = TestSerialization.class + .getResourceAsStream("savedPolygon1.txt"); + ObjectInputStream ii = new ObjectInputStream(s); + Polygon ptRes = (Polygon) ii.readObject(); + assertTrue(ptRes != null); + } catch (Exception ex) { + fail("Polygon serialization failure"); + } } @Test @@ -145,21 +163,20 @@ public void testSerializePolyline() { fail("Polyline serialization failure"); } - // try - // { - // FileOutputStream streamOut = new FileOutputStream(m_thisDirectory + - // "savedPolyline.txt"); - // ObjectOutputStream oo = new ObjectOutputStream(streamOut); - // Polyline pt = new Polyline(); - // pt.startPath(10, 10); - // pt.lineTo(100, 100); - // pt.lineTo(200, 100); - // oo.writeObject(pt); - // } - // catch(Exception ex) - // { - // fail("Polyline serialization failure"); - // } + //try + //{ + //FileOutputStream streamOut = new FileOutputStream("c:/temp/savedPolyline1.txt"); + //ObjectOutputStream oo = new ObjectOutputStream(streamOut); + //Polyline pt = new Polyline(); + //pt.startPath(10, 10); + //pt.lineTo(100, 100); + //pt.lineTo(200, 100); + //oo.writeObject(pt); + //} + //catch(Exception ex) + //{ + //fail("Polyline serialization failure"); + //} try { InputStream s = TestSerialization.class @@ -170,6 +187,15 @@ public void testSerializePolyline() { } catch (Exception ex) { fail("Polyline serialization failure"); } + try { + InputStream s = TestSerialization.class + .getResourceAsStream("savedPolyline1.txt"); + ObjectInputStream ii = new ObjectInputStream(s); + Polyline ptRes = (Polyline) ii.readObject(); + assertTrue(ptRes != null); + } catch (Exception ex) { + fail("Polyline serialization failure"); + } } @Test @@ -188,18 +214,17 @@ public void testSerializeEnvelope() { fail("Envelope serialization failure"); } - // try - // { - // FileOutputStream streamOut = new FileOutputStream(m_thisDirectory + - // "savedEnvelope.txt"); - // ObjectOutputStream oo = new ObjectOutputStream(streamOut); - // Envelope pt = new Envelope(10, 10, 400, 300); - // oo.writeObject(pt); - // } - // catch(Exception ex) - // { - // fail("Envelope serialization failure"); - // } + //try + //{ + //FileOutputStream streamOut = new FileOutputStream("c:/temp/savedEnvelope1.txt"); + //ObjectOutputStream oo = new ObjectOutputStream(streamOut); + //Envelope pt = new Envelope(10, 10, 400, 300); + //oo.writeObject(pt); + //} + //catch(Exception ex) + //{ + //fail("Envelope serialization failure"); + //} try { InputStream s = TestSerialization.class @@ -210,6 +235,15 @@ public void testSerializeEnvelope() { } catch (Exception ex) { fail("Envelope serialization failure"); } + try { + InputStream s = TestSerialization.class + .getResourceAsStream("savedEnvelope1.txt"); + ObjectInputStream ii = new ObjectInputStream(s); + Envelope ptRes = (Envelope) ii.readObject(); + assertTrue(ptRes.getXMax() == 400); + } catch (Exception ex) { + fail("Envelope serialization failure"); + } } @Test @@ -230,20 +264,19 @@ public void testSerializeMultiPoint() { fail("MultiPoint serialization failure"); } - // try - // { - // FileOutputStream streamOut = new FileOutputStream(m_thisDirectory + - // "savedMultiPoint.txt"); - // ObjectOutputStream oo = new ObjectOutputStream(streamOut); - // MultiPoint pt = new MultiPoint(); - // pt.add(10, 30); - // pt.add(120, 40); - // oo.writeObject(pt); - // } - // catch(Exception ex) - // { - // fail("MultiPoint serialization failure"); - // } + //try + //{ + //FileOutputStream streamOut = new FileOutputStream("c:/temp/savedMultiPoint1.txt"); + //ObjectOutputStream oo = new ObjectOutputStream(streamOut); + //MultiPoint pt = new MultiPoint(); + //pt.add(10, 30); + //pt.add(120, 40); + //oo.writeObject(pt); + //} + //catch(Exception ex) + //{ + //fail("MultiPoint serialization failure"); + //} try { InputStream s = TestSerialization.class @@ -254,6 +287,15 @@ public void testSerializeMultiPoint() { } catch (Exception ex) { fail("MultiPoint serialization failure"); } + try { + InputStream s = TestSerialization.class + .getResourceAsStream("savedMultiPoint1.txt"); + ObjectInputStream ii = new ObjectInputStream(s); + MultiPoint ptRes = (MultiPoint) ii.readObject(); + assertTrue(ptRes.getPoint(1).getY() == 40); + } catch (Exception ex) { + fail("MultiPoint serialization failure"); + } } @Test diff --git a/src/test/java/com/esri/core/geometry/TestSpatialReference.java b/src/test/java/com/esri/core/geometry/TestSpatialReference.java index f0f6aacd..8d21712f 100644 --- a/src/test/java/com/esri/core/geometry/TestSpatialReference.java +++ b/src/test/java/com/esri/core/geometry/TestSpatialReference.java @@ -1,17 +1,17 @@ package com.esri.core.geometry; -import org.junit.Assert; +import junit.framework.TestCase; import org.junit.Test; -public class TestSpatialReference extends Assert { +public class TestSpatialReference extends TestCase { @Test - public void equals() { - final String wktext1 = "GEOGCS[\"GCS_WGS_1984\",DATUM[\"D_WGS_1984\",SPHEROID[\"WGS_1984\",6378137.0,298.257223563]],PRIMEM[\"Greenwich\",0.0],UNIT[\"Degree\",0.0174532925199433]]"; - final String wktext2 = "PROJCS[\"WGS_1984_Web_Mercator_Auxiliary_Sphere\",GEOGCS[\"GCS_WGS_1984\",DATUM[\"D_WGS_1984\",SPHEROID[\"WGS_1984\",6378137.0,298.257223563]],PRIMEM[\"Greenwich\",0.0],UNIT[\"Degree\",0.0174532925199433]],PROJECTION[\"Mercator_Auxiliary_Sphere\"],PARAMETER[\"False_Easting\",0.0],PARAMETER[\"False_Northing\",0.0],PARAMETER[\"Central_Meridian\",0.0],PARAMETER[\"Standard_Parallel_1\",0.0],PARAMETER[\"Auxiliary_Sphere_Type\",0.0],UNIT[\"Meter\",1.0]]"; + public void testEquals() { + String wktext1 = "GEOGCS[\"GCS_WGS_1984\",DATUM[\"D_WGS_1984\",SPHEROID[\"WGS_1984\",6378137.0,298.257223563]],PRIMEM[\"Greenwich\",0.0],UNIT[\"Degree\",0.0174532925199433]]"; + String wktext2 = "PROJCS[\"WGS_1984_Web_Mercator_Auxiliary_Sphere\",GEOGCS[\"GCS_WGS_1984\",DATUM[\"D_WGS_1984\",SPHEROID[\"WGS_1984\",6378137.0,298.257223563]],PRIMEM[\"Greenwich\",0.0],UNIT[\"Degree\",0.0174532925199433]],PROJECTION[\"Mercator_Auxiliary_Sphere\"],PARAMETER[\"False_Easting\",0.0],PARAMETER[\"False_Northing\",0.0],PARAMETER[\"Central_Meridian\",0.0],PARAMETER[\"Standard_Parallel_1\",0.0],PARAMETER[\"Auxiliary_Sphere_Type\",0.0],UNIT[\"Meter\",1.0]]"; - final SpatialReference a1 = SpatialReference.create(wktext1); - final SpatialReference b = SpatialReference.create(wktext2); - final SpatialReference a2 = SpatialReference.create(wktext1); + SpatialReference a1 = SpatialReference.create(wktext1); + SpatialReference b = SpatialReference.create(wktext2); + SpatialReference a2 = SpatialReference.create(wktext1); assertTrue(a1.equals(a1)); assertTrue(b.equals(b)); @@ -22,3 +22,4 @@ public void equals() { assertFalse(b.equals(a1)); } } + diff --git a/src/test/java/com/esri/core/geometry/TestWkid.java b/src/test/java/com/esri/core/geometry/TestWkid.java index d953ae95..cd249233 100644 --- a/src/test/java/com/esri/core/geometry/TestWkid.java +++ b/src/test/java/com/esri/core/geometry/TestWkid.java @@ -19,6 +19,7 @@ public void test() { assertTrue(Math.abs(tol84 - 1e-8) < 1e-8 * 1e-8); } + @Test public void test_80() { SpatialReference sr = SpatialReference.create(3857); diff --git a/src/test/resources/com/esri/core/geometry/savedEnvelope1.txt b/src/test/resources/com/esri/core/geometry/savedEnvelope1.txt new file mode 100644 index 0000000000000000000000000000000000000000..5f6ee18b35dd78a3e7b99ced5651419236b26633 GIT binary patch literal 147 zcmZ4UmVvdnh(SI%KUXicxF}OEIlm}XFFiFsH?^dwQqMK7EVwAAs)zvs7?~KDJQ;*i zQj3#|G7CyF^YffCOMDZHv!fZ<6H7{pGLwo+7?`46Dhhz=8B2>mY`bMWBCC_5VbQ)Hh?de9goI?Sb(Ihd!@~yD8DGIP#};cx03u! Q!VW4B!yH_oG=yRR03RzM^8f$< literal 0 HcmV?d00001 diff --git a/src/test/resources/com/esri/core/geometry/savedPolygon1.txt b/src/test/resources/com/esri/core/geometry/savedPolygon1.txt new file mode 100644 index 0000000000000000000000000000000000000000..1fd9c10556e0e3d2635c76546f9792ce3cf02af3 GIT binary patch literal 317 zcmZ4UmVvdnh`}H^KUXicxF}OEIlm}XFFiFsH?^dwQqMg#FSRH$*&WIc267T}GOJRH z7$AU=iGkIVfd{5Oq_QB@lYy%^Gq)fo)h#D6-Gza(BtIv$C^0WNHJX7FWJ7R9VnJ#N z15>n9Gy_L|dNN2^F^C^kQNX~!b^>THqwW4ku=%A$Aa!d#i~nHb2zkK7zyMOhP{0bY z97w7-K$tK(6UvWtU<69AKqR3gObv{VgsX)pKhhJ4{G&ViGA8vUFV9ljO{8ykDCr;U|*#j$y*`(9?WSX5^up z^E69nkwozvb|Dgy(VkHVTQ&H*D5C;qI1AAcF;iHs{Q8&yZTD4A^Bo7Nxau)@wmZSq u1Qdd8OXnC(zpGZ>_|?>?gFmMz&1hZySd{|*+fVI!c^*#sm(5Mr8h8V&^+ZAd literal 0 HcmV?d00001 From 04e92ca0de02a6b8ad6452bd48ebbe7361eea272 Mon Sep 17 00:00:00 2001 From: Kevin Krumwiede Date: Mon, 22 Aug 2016 00:09:28 -0700 Subject: [PATCH 127/196] Disable doclint when building with Java 1.8+. --- pom.xml | 48 ++++++++++++++++++++++++++++++------------------ 1 file changed, 30 insertions(+), 18 deletions(-) diff --git a/pom.xml b/pom.xml index 7a72602c..0933ae01 100755 --- a/pom.xml +++ b/pom.xml @@ -13,9 +13,9 @@ - The Apache Software License, Version 2.0 - http://www.apache.org/licenses/LICENSE-2.0.txt - repo + The Apache Software License, Version 2.0 + http://www.apache.org/licenses/LICENSE-2.0.txt + repo @@ -39,7 +39,7 @@ - + scm:git:git@github.com:Esri/geometry-api-java.git scm:git:git@github.com:Esri/geometry-api-java.git @@ -49,12 +49,12 @@ release-sign-artifacts - - - performRelease - true - - + + + performRelease + true + + @@ -73,15 +73,24 @@ + + java8-disable-doclint + + [1.8,) + + + -Xdoclint:none + + - + ossrh https://oss.sonatype.org/content/repositories/snapshots - + UTF-8 @@ -138,12 +147,12 @@ - maven-compiler-plugin - ${compiler.plugin.version} - - ${java.source.version} - ${java.target.version} - + maven-compiler-plugin + ${compiler.plugin.version} + + ${java.source.version} + ${java.target.version} + org.apache.maven.plugins @@ -172,6 +181,9 @@ jar + + ${javadoc.doclint.param} + From 9e8a4e4244fc3779a47c44072af5875f1006634e Mon Sep 17 00:00:00 2001 From: Kevin Krumwiede Date: Mon, 22 Aug 2016 00:23:35 -0700 Subject: [PATCH 128/196] Undo IDEA's formatting corrections to reduce noise in pull request diff. --- pom.xml | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/pom.xml b/pom.xml index 0933ae01..3bdf4a44 100755 --- a/pom.xml +++ b/pom.xml @@ -13,9 +13,9 @@ - The Apache Software License, Version 2.0 - http://www.apache.org/licenses/LICENSE-2.0.txt - repo + The Apache Software License, Version 2.0 + http://www.apache.org/licenses/LICENSE-2.0.txt + repo @@ -49,12 +49,12 @@ release-sign-artifacts - - - performRelease - true - - + + + performRelease + true + + @@ -147,12 +147,12 @@ - maven-compiler-plugin - ${compiler.plugin.version} - - ${java.source.version} - ${java.target.version} - + maven-compiler-plugin + ${compiler.plugin.version} + + ${java.source.version} + ${java.target.version} + org.apache.maven.plugins From 6c9eead489c5ec5812e591eb57630bbeac0aee78 Mon Sep 17 00:00:00 2001 From: Kevin Krumwiede Date: Mon, 22 Aug 2016 12:07:01 -0700 Subject: [PATCH 129/196] Set Buffer byte order before reading shape type. --- .../OperatorImportFromESRIShapeCursor.java | 212 +++++++++--------- 1 file changed, 106 insertions(+), 106 deletions(-) diff --git a/src/main/java/com/esri/core/geometry/OperatorImportFromESRIShapeCursor.java b/src/main/java/com/esri/core/geometry/OperatorImportFromESRIShapeCursor.java index 2186b4e4..2db25375 100644 --- a/src/main/java/com/esri/core/geometry/OperatorImportFromESRIShapeCursor.java +++ b/src/main/java/com/esri/core/geometry/OperatorImportFromESRIShapeCursor.java @@ -63,116 +63,116 @@ public int getGeometryID() { } private Geometry importFromESRIShape(ByteBuffer shapeBuffer) { - // read type - int shapetype = shapeBuffer.getInt(0); - - // Extract general type and modifiers - int generaltype; - int modifiers; - switch (shapetype & ShapeModifiers.ShapeBasicTypeMask) { - // Polygon - case ShapeType.ShapePolygon: - generaltype = ShapeType.ShapeGeneralPolygon; - modifiers = 0; - break; - case ShapeType.ShapePolygonZM: - generaltype = ShapeType.ShapeGeneralPolygon; - modifiers = ShapeModifiers.ShapeHasZs | ShapeModifiers.ShapeHasMs; - break; - case ShapeType.ShapePolygonM: - generaltype = ShapeType.ShapeGeneralPolygon; - modifiers = ShapeModifiers.ShapeHasMs; - break; - case ShapeType.ShapePolygonZ: - generaltype = ShapeType.ShapeGeneralPolygon; - modifiers = ShapeModifiers.ShapeHasZs; - break; - case ShapeType.ShapeGeneralPolygon: - generaltype = ShapeType.ShapeGeneralPolygon; - modifiers = shapetype & ShapeModifiers.ShapeModifierMask; - break; - - // Polyline - case ShapeType.ShapePolyline: - generaltype = ShapeType.ShapeGeneralPolyline; - modifiers = 0; - break; - case ShapeType.ShapePolylineZM: - generaltype = ShapeType.ShapeGeneralPolyline; - modifiers = ShapeModifiers.ShapeHasZs - | (int) ShapeModifiers.ShapeHasMs; - break; - case ShapeType.ShapePolylineM: - generaltype = ShapeType.ShapeGeneralPolyline; - modifiers = ShapeModifiers.ShapeHasMs; - break; - case ShapeType.ShapePolylineZ: - generaltype = ShapeType.ShapeGeneralPolyline; - modifiers = ShapeModifiers.ShapeHasZs; - break; - case ShapeType.ShapeGeneralPolyline: - generaltype = ShapeType.ShapeGeneralPolyline; - modifiers = shapetype & ShapeModifiers.ShapeModifierMask; - break; - - // MultiPoint - case ShapeType.ShapeMultiPoint: - generaltype = ShapeType.ShapeGeneralMultiPoint; - modifiers = 0; - break; - case ShapeType.ShapeMultiPointZM: - generaltype = ShapeType.ShapeGeneralMultiPoint; - modifiers = (int) ShapeModifiers.ShapeHasZs - | (int) ShapeModifiers.ShapeHasMs; - break; - case ShapeType.ShapeMultiPointM: - generaltype = ShapeType.ShapeGeneralMultiPoint; - modifiers = ShapeModifiers.ShapeHasMs; - break; - case ShapeType.ShapeMultiPointZ: - generaltype = ShapeType.ShapeGeneralMultiPoint; - modifiers = ShapeModifiers.ShapeHasZs; - break; - case ShapeType.ShapeGeneralMultiPoint: - generaltype = ShapeType.ShapeGeneralMultiPoint; - modifiers = shapetype & ShapeModifiers.ShapeModifierMask; - break; - - // Point - case ShapeType.ShapePoint: - generaltype = ShapeType.ShapeGeneralPoint; - modifiers = 0; - break; - case ShapeType.ShapePointZM: - generaltype = ShapeType.ShapeGeneralPoint; - modifiers = ShapeModifiers.ShapeHasZs - | (int) ShapeModifiers.ShapeHasMs; - break; - case ShapeType.ShapePointM: - generaltype = ShapeType.ShapeGeneralPoint; - modifiers = ShapeModifiers.ShapeHasMs; - break; - case ShapeType.ShapePointZ: - generaltype = ShapeType.ShapeGeneralPoint; - modifiers = ShapeModifiers.ShapeHasZs; - break; - case ShapeType.ShapeGeneralPoint: - generaltype = ShapeType.ShapeGeneralPoint; - modifiers = shapetype & ShapeModifiers.ShapeModifierMask; - break; - - // Null Geometry - case ShapeType.ShapeNull: - return null; - - default: - throw new GeometryException("invalid shape type"); - } - ByteOrder initialOrder = shapeBuffer.order(); shapeBuffer.order(ByteOrder.LITTLE_ENDIAN); try { + // read type + int shapetype = shapeBuffer.getInt(0); + + // Extract general type and modifiers + int generaltype; + int modifiers; + switch (shapetype & ShapeModifiers.ShapeBasicTypeMask) { + // Polygon + case ShapeType.ShapePolygon: + generaltype = ShapeType.ShapeGeneralPolygon; + modifiers = 0; + break; + case ShapeType.ShapePolygonZM: + generaltype = ShapeType.ShapeGeneralPolygon; + modifiers = ShapeModifiers.ShapeHasZs | ShapeModifiers.ShapeHasMs; + break; + case ShapeType.ShapePolygonM: + generaltype = ShapeType.ShapeGeneralPolygon; + modifiers = ShapeModifiers.ShapeHasMs; + break; + case ShapeType.ShapePolygonZ: + generaltype = ShapeType.ShapeGeneralPolygon; + modifiers = ShapeModifiers.ShapeHasZs; + break; + case ShapeType.ShapeGeneralPolygon: + generaltype = ShapeType.ShapeGeneralPolygon; + modifiers = shapetype & ShapeModifiers.ShapeModifierMask; + break; + + // Polyline + case ShapeType.ShapePolyline: + generaltype = ShapeType.ShapeGeneralPolyline; + modifiers = 0; + break; + case ShapeType.ShapePolylineZM: + generaltype = ShapeType.ShapeGeneralPolyline; + modifiers = ShapeModifiers.ShapeHasZs + | (int) ShapeModifiers.ShapeHasMs; + break; + case ShapeType.ShapePolylineM: + generaltype = ShapeType.ShapeGeneralPolyline; + modifiers = ShapeModifiers.ShapeHasMs; + break; + case ShapeType.ShapePolylineZ: + generaltype = ShapeType.ShapeGeneralPolyline; + modifiers = ShapeModifiers.ShapeHasZs; + break; + case ShapeType.ShapeGeneralPolyline: + generaltype = ShapeType.ShapeGeneralPolyline; + modifiers = shapetype & ShapeModifiers.ShapeModifierMask; + break; + + // MultiPoint + case ShapeType.ShapeMultiPoint: + generaltype = ShapeType.ShapeGeneralMultiPoint; + modifiers = 0; + break; + case ShapeType.ShapeMultiPointZM: + generaltype = ShapeType.ShapeGeneralMultiPoint; + modifiers = (int) ShapeModifiers.ShapeHasZs + | (int) ShapeModifiers.ShapeHasMs; + break; + case ShapeType.ShapeMultiPointM: + generaltype = ShapeType.ShapeGeneralMultiPoint; + modifiers = ShapeModifiers.ShapeHasMs; + break; + case ShapeType.ShapeMultiPointZ: + generaltype = ShapeType.ShapeGeneralMultiPoint; + modifiers = ShapeModifiers.ShapeHasZs; + break; + case ShapeType.ShapeGeneralMultiPoint: + generaltype = ShapeType.ShapeGeneralMultiPoint; + modifiers = shapetype & ShapeModifiers.ShapeModifierMask; + break; + + // Point + case ShapeType.ShapePoint: + generaltype = ShapeType.ShapeGeneralPoint; + modifiers = 0; + break; + case ShapeType.ShapePointZM: + generaltype = ShapeType.ShapeGeneralPoint; + modifiers = ShapeModifiers.ShapeHasZs + | (int) ShapeModifiers.ShapeHasMs; + break; + case ShapeType.ShapePointM: + generaltype = ShapeType.ShapeGeneralPoint; + modifiers = ShapeModifiers.ShapeHasMs; + break; + case ShapeType.ShapePointZ: + generaltype = ShapeType.ShapeGeneralPoint; + modifiers = ShapeModifiers.ShapeHasZs; + break; + case ShapeType.ShapeGeneralPoint: + generaltype = ShapeType.ShapeGeneralPoint; + modifiers = shapetype & ShapeModifiers.ShapeModifierMask; + break; + + // Null Geometry + case ShapeType.ShapeNull: + return null; + + default: + throw new GeometryException("invalid shape type"); + } + switch (generaltype) { case ShapeType.ShapeGeneralPolygon: if (m_type != Geometry.GeometryType.Polygon From cb33db43d3e9190a8fcefeab6137ae8014581343 Mon Sep 17 00:00:00 2001 From: Sergey Tolstov Date: Fri, 23 Sep 2016 12:46:08 -0700 Subject: [PATCH 130/196] don't use JSONObject.getNames --- .../core/geometry/JSONObjectEnumerator.java | 23 ++++++++++++------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/src/main/java/com/esri/core/geometry/JSONObjectEnumerator.java b/src/main/java/com/esri/core/geometry/JSONObjectEnumerator.java index 7b8f2e8b..fa1a278a 100644 --- a/src/main/java/com/esri/core/geometry/JSONObjectEnumerator.java +++ b/src/main/java/com/esri/core/geometry/JSONObjectEnumerator.java @@ -23,18 +23,17 @@ */ package com.esri.core.geometry; -import java.util.ArrayList; -import org.codehaus.jackson.JsonParser; -import org.codehaus.jackson.JsonToken; -import org.json.JSONArray; import org.json.JSONObject; +import java.util.Iterator; + final class JSONObjectEnumerator { private JSONObject m_jsonObject; private boolean m_bStarted; private int m_currentIndex; - private String[] m_keys; + private Iterator m_keys_iter; + private String m_current_key; JSONObjectEnumerator(JSONObject jsonObject) { m_bStarted = false; @@ -51,7 +50,7 @@ String getCurrentKey() { throw new GeometryException("invalid call"); } - return m_keys[m_currentIndex]; + return m_current_key; } Object getCurrentObject() { @@ -63,15 +62,23 @@ Object getCurrentObject() { throw new GeometryException("invalid call"); } - return m_jsonObject.opt(m_keys[m_currentIndex]); + return m_jsonObject.opt(m_current_key); } boolean next() { if (!m_bStarted) { m_currentIndex = 0; - m_keys = JSONObject.getNames(m_jsonObject); + m_keys_iter = m_jsonObject.keys(); m_bStarted = true; + if (m_keys_iter.hasNext()) { + m_current_key = (String)m_keys_iter.next(); + } + } else if (m_currentIndex != m_jsonObject.length()) { + if (m_keys_iter.hasNext()) { + m_current_key = (String)m_keys_iter.next(); + } + m_currentIndex++; } From ff44877f8ba5a2cbb10f50bb3e9674269675c1c0 Mon Sep 17 00:00:00 2001 From: Sergey Tolstov Date: Tue, 27 Sep 2016 11:17:34 -0700 Subject: [PATCH 131/196] resore exceptions in the interface --- src/main/java/com/esri/core/geometry/ogc/OGCGeometry.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/esri/core/geometry/ogc/OGCGeometry.java b/src/main/java/com/esri/core/geometry/ogc/OGCGeometry.java index c0a93ce2..2e597e7e 100644 --- a/src/main/java/com/esri/core/geometry/ogc/OGCGeometry.java +++ b/src/main/java/com/esri/core/geometry/ogc/OGCGeometry.java @@ -496,7 +496,7 @@ public static OGCGeometry fromEsriShape(ByteBuffer buffer) { } public static OGCGeometry fromJson(String string) - throws Exception { + throws JsonParseException, IOException { JsonFactory factory = new JsonFactory(); JsonParser jsonParserPt = factory.createJsonParser(string); jsonParserPt.nextToken(); @@ -506,7 +506,7 @@ public static OGCGeometry fromJson(String string) } public static OGCGeometry fromGeoJson(String string) - throws Exception { + throws JsonParseException, IOException { OperatorImportFromGeoJson op = (OperatorImportFromGeoJson) OperatorFactoryLocal .getInstance().getOperator(Operator.Type.ImportFromGeoJson); MapOGCStructure mapOGCStructure = op.executeOGC(0, string, null); From 0b58f1df25be5851173932a386f6d7b648b64220 Mon Sep 17 00:00:00 2001 From: Sergey Tolstov Date: Tue, 27 Sep 2016 11:57:56 -0700 Subject: [PATCH 132/196] cleanup --- .../core/geometry/JSONObjectEnumerator.java | 43 ++++++++----------- 1 file changed, 18 insertions(+), 25 deletions(-) diff --git a/src/main/java/com/esri/core/geometry/JSONObjectEnumerator.java b/src/main/java/com/esri/core/geometry/JSONObjectEnumerator.java index fa1a278a..2b4e25b4 100644 --- a/src/main/java/com/esri/core/geometry/JSONObjectEnumerator.java +++ b/src/main/java/com/esri/core/geometry/JSONObjectEnumerator.java @@ -30,23 +30,17 @@ final class JSONObjectEnumerator { private JSONObject m_jsonObject; - private boolean m_bStarted; - private int m_currentIndex; + private int m_troolean; private Iterator m_keys_iter; private String m_current_key; JSONObjectEnumerator(JSONObject jsonObject) { - m_bStarted = false; - m_currentIndex = -1; + m_troolean = 0; m_jsonObject = jsonObject; } String getCurrentKey() { - if (!m_bStarted) { - throw new GeometryException("invalid call"); - } - - if (m_currentIndex == m_jsonObject.length()) { + if (m_troolean != 1) { throw new GeometryException("invalid call"); } @@ -54,11 +48,7 @@ String getCurrentKey() { } Object getCurrentObject() { - if (!m_bStarted) { - throw new GeometryException("invalid call"); - } - - if (m_currentIndex == m_jsonObject.length()) { + if (m_troolean != 1) { throw new GeometryException("invalid call"); } @@ -66,22 +56,25 @@ Object getCurrentObject() { } boolean next() { - if (!m_bStarted) { - m_currentIndex = 0; - m_keys_iter = m_jsonObject.keys(); - m_bStarted = true; - if (m_keys_iter.hasNext()) { - m_current_key = (String)m_keys_iter.next(); + if (m_troolean == 0) { + if (m_jsonObject.length() > 0) { + m_keys_iter = m_jsonObject.keys(); + m_troolean = 1;//started } - - } else if (m_currentIndex != m_jsonObject.length()) { + else { + m_troolean = -1;//stopped + } + } + + if (m_troolean == 1) {//still exploring if (m_keys_iter.hasNext()) { m_current_key = (String)m_keys_iter.next(); } - - m_currentIndex++; + else { + m_troolean = -1; //done + } } - return m_currentIndex != m_jsonObject.length(); + return m_troolean == 1; } } From 49e9f35c8604f96f3c9a74800434fcbe2666dbbd Mon Sep 17 00:00:00 2001 From: Sergey Tolstov Date: Wed, 28 Sep 2016 10:18:39 -0700 Subject: [PATCH 133/196] fix fromGeoJson interface --- src/main/java/com/esri/core/geometry/ogc/OGCGeometry.java | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/main/java/com/esri/core/geometry/ogc/OGCGeometry.java b/src/main/java/com/esri/core/geometry/ogc/OGCGeometry.java index 2e597e7e..04b2df77 100644 --- a/src/main/java/com/esri/core/geometry/ogc/OGCGeometry.java +++ b/src/main/java/com/esri/core/geometry/ogc/OGCGeometry.java @@ -12,7 +12,6 @@ import com.esri.core.geometry.Envelope; import com.esri.core.geometry.Envelope1D; -import com.esri.core.geometry.GeoJsonExportFlags; import com.esri.core.geometry.Geometry; import com.esri.core.geometry.GeometryCursor; import com.esri.core.geometry.GeometryCursorAppend; @@ -30,7 +29,6 @@ import com.esri.core.geometry.OperatorFactoryLocal; import com.esri.core.geometry.OperatorImportFromESRIShape; import com.esri.core.geometry.OperatorImportFromGeoJson; -import com.esri.core.geometry.OperatorImportFromJson; import com.esri.core.geometry.OperatorImportFromWkb; import com.esri.core.geometry.OperatorImportFromWkt; import com.esri.core.geometry.OperatorIntersection; @@ -506,7 +504,7 @@ public static OGCGeometry fromJson(String string) } public static OGCGeometry fromGeoJson(String string) - throws JsonParseException, IOException { + throws JSONException { OperatorImportFromGeoJson op = (OperatorImportFromGeoJson) OperatorFactoryLocal .getInstance().getOperator(Operator.Type.ImportFromGeoJson); MapOGCStructure mapOGCStructure = op.executeOGC(0, string, null); From e758fc62a8700dbdc5077c0d8a6268371b2079e3 Mon Sep 17 00:00:00 2001 From: will Date: Wed, 9 Nov 2016 11:39:42 +0100 Subject: [PATCH 134/196] test case for polyline buffer which throws npe --- src/test/java/com/esri/core/geometry/TestBuffer.java | 11 +++++++++++ 1 file changed, 11 insertions(+) mode change 100644 => 100755 src/test/java/com/esri/core/geometry/TestBuffer.java diff --git a/src/test/java/com/esri/core/geometry/TestBuffer.java b/src/test/java/com/esri/core/geometry/TestBuffer.java old mode 100644 new mode 100755 index 38902f83..f381234b --- a/src/test/java/com/esri/core/geometry/TestBuffer.java +++ b/src/test/java/com/esri/core/geometry/TestBuffer.java @@ -266,6 +266,17 @@ public void testBufferPolyline() { assertTrue(Math.abs(pointCount - 208.0) < 10); assertTrue(simplify.isSimpleAsFeature(result, sr, null)); } + + { + inputGeom = new Polyline(); + inputGeom.startPath(1.762614,0.607368); + inputGeom.lineTo(1.762414,0.606655); + inputGeom.lineTo(1.763006,0.607034); + inputGeom.lineTo(1.762548,0.607135); + + Geometry result = buffer.execute(inputGeom, sr, 0.005, null); + assertTrue(simplify.isSimpleAsFeature(result, sr, null)); + } } @Test From a9435477e5dc84404f68d6b4c5e5ef48c9fc7c41 Mon Sep 17 00:00:00 2001 From: will Date: Wed, 9 Nov 2016 17:47:37 +0100 Subject: [PATCH 135/196] fixed npe caused by buffer of polyline --- src/main/java/com/esri/core/geometry/Bufferer.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) mode change 100644 => 100755 src/main/java/com/esri/core/geometry/Bufferer.java diff --git a/src/main/java/com/esri/core/geometry/Bufferer.java b/src/main/java/com/esri/core/geometry/Bufferer.java old mode 100644 new mode 100755 index b7008559..7578a76c --- a/src/main/java/com/esri/core/geometry/Bufferer.java +++ b/src/main/java/com/esri/core/geometry/Bufferer.java @@ -805,7 +805,7 @@ private Geometry bufferPoint_() { private Geometry bufferPoint_(Point point) { assert (m_distance > 0); - Polygon resultPolygon = new Polygon(m_geometry.getDescription()); + Polygon resultPolygon = new Polygon(point.getDescription()); addCircle_((MultiPathImpl) resultPolygon._getImpl(), point); return setStrongSimple_(resultPolygon); } From dcbabdc71e20dfb1b3dd19374fb49e076ed61658 Mon Sep 17 00:00:00 2001 From: Randall Whitman Date: Fri, 7 Apr 2017 15:09:21 -0700 Subject: [PATCH 136/196] README: remove tags; update copyright to 2017 --- README.md | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/README.md b/README.md index 535a2da9..d6d243c1 100644 --- a/README.md +++ b/README.md @@ -53,7 +53,7 @@ Find a bug or want to request a new feature? Please let us know by submitting a Esri welcomes contributions from anyone and everyone. Please see our [guidelines for contributing](https://github.com/esri/contributing) ## Licensing -Copyright 2013-2016 Esri +Copyright 2013-2017 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -68,7 +68,3 @@ See the License for the specific language governing permissions and limitations under the License. A copy of the license is available in the repository's [license.txt](https://raw.github.com/Esri/geometry-api-java/master/license.txt) file. - -[](Esri Tags: ArcGIS, Java, Geometry, Relationship, Analysis, JSON, WKT, Shape) -[](Esri Language: Java) - From 9bedde397f2f61675bc687b95875893aa7cd7f2f Mon Sep 17 00:00:00 2001 From: Sergey Tolstov Date: Wed, 5 Jul 2017 17:09:07 -0700 Subject: [PATCH 137/196] removed org.json dependency --- DepFiles/public/jackson-core-2.6.2.jar | Bin 0 -> 258824 bytes DepFiles/public/jackson-core-asl-1.9.11.jar | Bin 232131 -> 0 bytes DepFiles/public/java-json.jar | Bin 42100 -> 0 bytes DepFiles/unittest/junit-4.12.jar | Bin 0 -> 314932 bytes DepFiles/unittest/junit-4.8.2.jar | Bin 237344 -> 0 bytes build.xml | 6 +- pom.xml | 15 +- .../esri/core/geometry/GeometryEngine.java | 134 +- .../core/geometry/JSONArrayEnumerator.java | 67 - .../core/geometry/JSONObjectEnumerator.java | 80 -- .../com/esri/core/geometry/JSONUtils.java | 21 +- .../core/geometry/JsonGeometryException.java | 18 +- .../esri/core/geometry/JsonParserReader.java | 147 ++- .../com/esri/core/geometry/JsonReader.java | 59 +- ...arserCursor.java => JsonReaderCursor.java} | 28 +- .../esri/core/geometry/JsonValueReader.java | 233 ---- .../com/esri/core/geometry/MultiPathImpl.java | 44 +- .../java/com/esri/core/geometry/Operator.java | 3 +- .../core/geometry/OperatorFactoryLocal.java | 17 +- .../geometry/OperatorImportFromGeoJson.java | 13 +- .../OperatorImportFromGeoJsonLocal.java | 1143 +++++++---------- .../core/geometry/OperatorImportFromJson.java | 26 +- .../OperatorImportFromJsonCursor.java | 55 +- .../geometry/OperatorImportFromJsonLocal.java | 38 +- ...ursor.java => SimpleJsonReaderCursor.java} | 16 +- .../esri/core/geometry/SpatialReference.java | 23 +- .../esri/core/geometry/ogc/OGCGeometry.java | 48 +- .../com/esri/core/geometry/GeometryUtils.java | 9 +- .../esri/core/geometry/TestCommonMethods.java | 11 +- .../com/esri/core/geometry/TestContains.java | 16 +- .../com/esri/core/geometry/TestDistance.java | 14 +- .../esri/core/geometry/TestGeomToGeoJson.java | 25 +- ...omToJSonExportSRFromWkiOrWkt_CR181369.java | 43 +- .../esri/core/geometry/TestImportExport.java | 7 +- .../TestJSonToGeomFromWkiOrWkt_CR177613.java | 11 +- .../esri/core/geometry/TestJsonParser.java | 12 +- .../java/com/esri/core/geometry/TestOGC.java | 12 +- .../com/esri/core/geometry/TestPolygon.java | 13 +- .../com/esri/core/geometry/TestRelation.java | 3 +- .../com/esri/core/geometry/TestSimplify.java | 26 +- .../esri/core/geometry/TestWKBSupport.java | 58 +- 41 files changed, 902 insertions(+), 1592 deletions(-) create mode 100644 DepFiles/public/jackson-core-2.6.2.jar delete mode 100644 DepFiles/public/jackson-core-asl-1.9.11.jar delete mode 100644 DepFiles/public/java-json.jar create mode 100644 DepFiles/unittest/junit-4.12.jar delete mode 100644 DepFiles/unittest/junit-4.8.2.jar delete mode 100644 src/main/java/com/esri/core/geometry/JSONArrayEnumerator.java delete mode 100644 src/main/java/com/esri/core/geometry/JSONObjectEnumerator.java rename src/main/java/com/esri/core/geometry/{JsonParserCursor.java => JsonReaderCursor.java} (68%) delete mode 100644 src/main/java/com/esri/core/geometry/JsonValueReader.java rename src/main/java/com/esri/core/geometry/{SimpleJsonParserCursor.java => SimpleJsonReaderCursor.java} (78%) diff --git a/DepFiles/public/jackson-core-2.6.2.jar b/DepFiles/public/jackson-core-2.6.2.jar new file mode 100644 index 0000000000000000000000000000000000000000..a7d87f067340ac378230a8ccf8b4dfc9512a94c1 GIT binary patch literal 258824 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%0ANnsM4|)f9WP^dq=- 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

F9Ck<@RiJoLX=RjXyZyA$)Ipeh4?TNP+Eozyr@& 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

SN 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 literal 0 HcmV?d00001 diff --git a/DepFiles/public/jackson-core-asl-1.9.11.jar b/DepFiles/public/jackson-core-asl-1.9.11.jar deleted file mode 100644 index 145fc4892222e773e7807b6cb25c9c7162f7bfac..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 232131 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%0Di*Io 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�pcp09 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 z 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

&R7;REBZd0NaNVcN6Eoy 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

?@Ib4$R$ZbA6MIsF(ZEk4Ii=sfRBa)&Yo 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�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 TY(h;@!?}i$7z!3V8^-w`q)kqw literal 0 HcmV?d00001 diff --git a/DepFiles/unittest/junit-4.8.2.jar b/DepFiles/unittest/junit-4.8.2.jar deleted file mode 100644 index 5b4bb849af9583fec1ea0a0ccc0d571ef49aa8ba..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 237344 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 + - - - + + diff --git a/pom.xml b/pom.xml index 01d43877..6738b3ae 100755 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ com.esri.geometry esri-geometry-api - 1.2.1 + 2.0.0 jar Esri Geometry API for Java @@ -98,8 +98,7 @@ 1.6 - 20140107 - 1.9.13 + 2.6.5 4.12 @@ -110,14 +109,10 @@ - org.json - json - ${json.version} - - - org.codehaus.jackson - jackson-core-asl + com.fasterxml.jackson.core + jackson-core ${jackson.version} + false junit diff --git a/src/main/java/com/esri/core/geometry/GeometryEngine.java b/src/main/java/com/esri/core/geometry/GeometryEngine.java index 7bed73ea..1339538c 100644 --- a/src/main/java/com/esri/core/geometry/GeometryEngine.java +++ b/src/main/java/com/esri/core/geometry/GeometryEngine.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2015 Esri + Copyright 1995-2017 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -29,9 +29,7 @@ import java.nio.ByteOrder; import java.util.ArrayList; -import org.codehaus.jackson.JsonParseException; -import org.codehaus.jackson.JsonParser; -import org.json.JSONException; +import com.fasterxml.jackson.core.JsonParser; /** * Provides services that operate on geometry instances. The methods of GeometryEngine class call corresponding OperatorXXX classes. @@ -57,10 +55,27 @@ public class GeometryEngine { * spatial reference. */ public static MapGeometry jsonToGeometry(JsonParser json) { - MapGeometry geom = OperatorImportFromJson.local().execute(Geometry.Type.Unknown, json); + MapGeometry geom = OperatorImportFromJson.local().execute(Geometry.Type.Unknown, new JsonParserReader(json)); return geom; } + /** + * Imports the MapGeometry from its JSON representation. M and Z values are + * not imported from JSON representation. + * + * See OperatorImportFromJson. + * + * @param json + * The JSON representation of the geometry (with spatial + * reference). + * @return The MapGeometry instance containing the imported geometry and its + * spatial reference. + */ + public static MapGeometry jsonToGeometry(JsonReader json) { + MapGeometry geom = OperatorImportFromJson.local().execute(Geometry.Type.Unknown, json); + return geom; + } + /** * Imports the MapGeometry from its JSON representation. M and Z values are * not imported from JSON representation. @@ -75,7 +90,7 @@ public static MapGeometry jsonToGeometry(JsonParser json) { * @throws IOException * @throws JsonParseException */ - public static MapGeometry jsonToGeometry(String json) throws JsonParseException, IOException { + public static MapGeometry jsonToGeometry(String json) { MapGeometry geom = OperatorImportFromJson.local().execute(Geometry.Type.Unknown, json); return geom; } @@ -126,43 +141,60 @@ public static String geometryToGeoJson(Geometry geometry) { return exporter.execute(geometry); } - /** - * Exports the specified geometry instance to its GeoJSON representation. - * - *See OperatorExportToGeoJson. - * - * @see GeometryEngine#geometryToGeoJson(SpatialReference spatialReference, - * Geometry geometry) - * - * @param wkid - * The spatial reference Well Known ID to be used for the GeoJSON representation. - * @param geometry - * The geometry to be exported to GeoJSON. - * @return The GeoJSON representation of the specified geometry. - */ - public static String geometryToGeoJson(int wkid, Geometry geometry) { - return GeometryEngine.geometryToGeoJson( - wkid > 0 ? SpatialReference.create(wkid) : null, geometry); - } + /** + * Imports the MapGeometry from its JSON representation. M and Z values are + * not imported from JSON representation. + * + * See OperatorImportFromJson. + * + * @param json + * The JSON representation of the geometry (with spatial + * reference). + * @return The MapGeometry instance containing the imported geometry and its + * spatial reference. + * @throws IOException + * @throws JsonParseException + */ + public static MapGeometry geoJsonToGeometry(String json, int importFlags, Geometry.Type type) { + MapGeometry geom = OperatorImportFromGeoJson.local().execute(importFlags, type, json, null); + return geom; + } - /** - * Exports the specified geometry instance to it's JSON representation. - * - *See OperatorImportFromGeoJson. - * - * @param spatialReference - * The spatial reference of associated object. - * @param geometry - * The geometry. - * @return The GeoJSON representation of the specified geometry. - */ - public static String geometryToGeoJson(SpatialReference spatialReference, - Geometry geometry) { - OperatorExportToGeoJson exporter = (OperatorExportToGeoJson) factory - .getOperator(Operator.Type.ExportToGeoJson); + /** + * Exports the specified geometry instance to its GeoJSON representation. + * + * See OperatorExportToGeoJson. + * + * @see GeometryEngine#geometryToGeoJson(SpatialReference spatialReference, + * Geometry geometry) + * + * @param wkid + * The spatial reference Well Known ID to be used for the GeoJSON + * representation. + * @param geometry + * The geometry to be exported to GeoJSON. + * @return The GeoJSON representation of the specified geometry. + */ + public static String geometryToGeoJson(int wkid, Geometry geometry) { + return GeometryEngine.geometryToGeoJson(wkid > 0 ? SpatialReference.create(wkid) : null, geometry); + } - return exporter.execute(spatialReference, geometry); - } + /** + * Exports the specified geometry instance to it's JSON representation. + * + * See OperatorImportFromGeoJson. + * + * @param spatialReference + * The spatial reference of associated object. + * @param geometry + * The geometry. + * @return The GeoJSON representation of the specified geometry. + */ + public static String geometryToGeoJson(SpatialReference spatialReference, Geometry geometry) { + OperatorExportToGeoJson exporter = (OperatorExportToGeoJson) factory.getOperator(Operator.Type.ExportToGeoJson); + + return exporter.execute(spatialReference, geometry); + } /** * Imports geometry from the ESRI shape file format. @@ -230,26 +262,6 @@ public static Geometry geometryFromWkt(String wkt, int importFlags, return op.execute(importFlags, geometryType, wkt, null); } - /** - * Imports a geometry from a geoJson string. - * - * See OperatorImportFromGeoJson. - * - * @param geoJson The string containing the geometry in geoJson format. - * @param importFlags Use the {@link GeoJsonImportFlags} interface. - * @param geometryType The required type of the Geometry to be imported. Use Geometry.Type.Unknown if the geometry type needs to be determined from the geoJson context. - * @return The geometry. - * @throws GeometryException when the geometryType is not Geometry.Type.Unknown and the geoJson contains a geometry that cannot be converted to the given geometryType. - * @throws IllegalArgument exception if an error is found while parsing the geoJson string. - */ - @Deprecated - public static MapGeometry geometryFromGeoJson(String geoJson, - int importFlags, Geometry.Type geometryType) throws JSONException { - OperatorImportFromGeoJson op = (OperatorImportFromGeoJson) factory - .getOperator(Operator.Type.ImportFromGeoJson); - return op.execute(importFlags, geometryType, geoJson, null); - } - /** * Exports a geometry to a string in WKT format. * diff --git a/src/main/java/com/esri/core/geometry/JSONArrayEnumerator.java b/src/main/java/com/esri/core/geometry/JSONArrayEnumerator.java deleted file mode 100644 index 1350f623..00000000 --- a/src/main/java/com/esri/core/geometry/JSONArrayEnumerator.java +++ /dev/null @@ -1,67 +0,0 @@ -/* - Copyright 1995-2015 Esri - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - - For additional information, contact: - Environmental Systems Research Institute, Inc. - Attn: Contracts Dept - 380 New York Street - Redlands, California, USA 92373 - - email: contracts@esri.com - */ -package com.esri.core.geometry; - -import java.util.ArrayList; -import org.codehaus.jackson.JsonParser; -import org.codehaus.jackson.JsonToken; -import org.json.JSONArray; -import org.json.JSONObject; - -final class JSONArrayEnumerator { - - private JSONArray m_jsonArray; - private boolean m_bStarted; - private int m_currentIndex; - - JSONArrayEnumerator(JSONArray jsonArray) { - m_bStarted = false; - m_currentIndex = -1; - m_jsonArray = jsonArray; - } - - Object getCurrentObject() { - if (!m_bStarted) { - throw new GeometryException("invalid call"); - } - - if (m_currentIndex == m_jsonArray.length()) { - throw new GeometryException("invalid call"); - } - - return m_jsonArray.opt(m_currentIndex); - } - - boolean next() { - if (!m_bStarted) { - m_currentIndex = 0; - m_bStarted = true; - } else if (m_currentIndex != m_jsonArray.length()) { - m_currentIndex++; - } - - return m_currentIndex != m_jsonArray.length(); - } -} - diff --git a/src/main/java/com/esri/core/geometry/JSONObjectEnumerator.java b/src/main/java/com/esri/core/geometry/JSONObjectEnumerator.java deleted file mode 100644 index 2b4e25b4..00000000 --- a/src/main/java/com/esri/core/geometry/JSONObjectEnumerator.java +++ /dev/null @@ -1,80 +0,0 @@ -/* - Copyright 1995-2015 Esri - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - - For additional information, contact: - Environmental Systems Research Institute, Inc. - Attn: Contracts Dept - 380 New York Street - Redlands, California, USA 92373 - - email: contracts@esri.com - */ -package com.esri.core.geometry; - -import org.json.JSONObject; - -import java.util.Iterator; - -final class JSONObjectEnumerator { - - private JSONObject m_jsonObject; - private int m_troolean; - private Iterator m_keys_iter; - private String m_current_key; - - JSONObjectEnumerator(JSONObject jsonObject) { - m_troolean = 0; - m_jsonObject = jsonObject; - } - - String getCurrentKey() { - if (m_troolean != 1) { - throw new GeometryException("invalid call"); - } - - return m_current_key; - } - - Object getCurrentObject() { - if (m_troolean != 1) { - throw new GeometryException("invalid call"); - } - - return m_jsonObject.opt(m_current_key); - } - - boolean next() { - if (m_troolean == 0) { - if (m_jsonObject.length() > 0) { - m_keys_iter = m_jsonObject.keys(); - m_troolean = 1;//started - } - else { - m_troolean = -1;//stopped - } - } - - if (m_troolean == 1) {//still exploring - if (m_keys_iter.hasNext()) { - m_current_key = (String)m_keys_iter.next(); - } - else { - m_troolean = -1; //done - } - } - - return m_troolean == 1; - } -} diff --git a/src/main/java/com/esri/core/geometry/JSONUtils.java b/src/main/java/com/esri/core/geometry/JSONUtils.java index 71feb32a..14bcf142 100644 --- a/src/main/java/com/esri/core/geometry/JSONUtils.java +++ b/src/main/java/com/esri/core/geometry/JSONUtils.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2015 Esri + Copyright 1995-2017 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -23,26 +23,21 @@ */ package com.esri.core.geometry; -import java.io.IOException; -import org.codehaus.jackson.JsonParseException; -import org.codehaus.jackson.JsonToken; - final class JSONUtils { static boolean isObjectStart(JsonReader parser) throws Exception { - return parser.currentToken() == null ? parser.nextToken() == JsonToken.START_OBJECT - : parser.currentToken() == JsonToken.START_OBJECT; + return parser.currentToken() == null ? parser.nextToken() == JsonReader.Token.START_OBJECT + : parser.currentToken() == JsonReader.Token.START_OBJECT; } - static double readDouble(JsonReader parser) throws JsonParseException, - IOException, Exception { - if (parser.currentToken() == JsonToken.VALUE_NUMBER_FLOAT) + static double readDouble(JsonReader parser) { + if (parser.currentToken() == JsonReader.Token.VALUE_NUMBER_FLOAT) return parser.currentDoubleValue(); - else if (parser.currentToken() == JsonToken.VALUE_NUMBER_INT) + else if (parser.currentToken() == JsonReader.Token.VALUE_NUMBER_INT) return parser.currentIntValue(); - else if (parser.currentToken() == JsonToken.VALUE_NULL) + else if (parser.currentToken() == JsonReader.Token.VALUE_NULL) return NumberUtils.NaN(); - else if (parser.currentToken() == JsonToken.VALUE_STRING) + else if (parser.currentToken() == JsonReader.Token.VALUE_STRING) if (parser.currentString().equals("NaN")) return NumberUtils.NaN(); diff --git a/src/main/java/com/esri/core/geometry/JsonGeometryException.java b/src/main/java/com/esri/core/geometry/JsonGeometryException.java index a5552901..7402e0de 100644 --- a/src/main/java/com/esri/core/geometry/JsonGeometryException.java +++ b/src/main/java/com/esri/core/geometry/JsonGeometryException.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2015 Esri + Copyright 1995-2017 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -21,13 +21,15 @@ email: contracts@esri.com */ + package com.esri.core.geometry; /** * A runtime exception raised when a JSON related exception occurs. */ public class JsonGeometryException extends GeometryException { - + private static final long serialVersionUID = 1L; + /** * Constructs a Json Geometry Exception with the given error string/message. * @@ -37,4 +39,16 @@ public class JsonGeometryException extends GeometryException { public JsonGeometryException(String str) { super(str); } + + /** + * Constructs a Json Geometry Exception with the given another exception. + * + * @param ex + * - The exception to copy the message from. + */ + public JsonGeometryException(Exception ex) { + super(ex.getMessage()); + } + } + diff --git a/src/main/java/com/esri/core/geometry/JsonParserReader.java b/src/main/java/com/esri/core/geometry/JsonParserReader.java index bf382dd9..90427c63 100644 --- a/src/main/java/com/esri/core/geometry/JsonParserReader.java +++ b/src/main/java/com/esri/core/geometry/JsonParserReader.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2015 Esri + Copyright 1995-2017 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -21,56 +21,151 @@ email: contracts@esri.com */ -package com.esri.core.geometry; - -import java.util.ArrayList; -import org.codehaus.jackson.JsonParser; -import org.codehaus.jackson.JsonToken; -import org.json.JSONArray; -import org.json.JSONObject; -import org.json.JSONException; -import org.codehaus.jackson.JsonParseException; +package com.esri.core.geometry; -import java.io.IOException; +import com.fasterxml.jackson.core.*; -final class JsonParserReader extends JsonReader { +/** + * A throw in JsonReader built around the Jackson JsonParser. + * + */ +public class JsonParserReader implements JsonReader { private JsonParser m_jsonParser; - JsonParserReader(JsonParser jsonParser) { + public JsonParserReader(JsonParser jsonParser) { m_jsonParser = jsonParser; } + + /** + * Creates a JsonReader for the string. + * The nextToken is called by this method. + */ + public static JsonReader createFromString(String str) { + try { + JsonFactory factory = new JsonFactory(); + JsonParser jsonParser = factory.createParser(str); + + jsonParser.nextToken(); + return new JsonParserReader(jsonParser); + } + catch (Exception ex) { + throw new JsonGeometryException(ex.getMessage()); + } + } + + /** + * Creates a JsonReader for the string. + * The nextToken is not called by this method. + */ + public static JsonReader createFromStringNNT(String str) { + try { + JsonFactory factory = new JsonFactory(); + JsonParser jsonParser = factory.createParser(str); + + return new JsonParserReader(jsonParser); + } + catch (Exception ex) { + throw new JsonGeometryException(ex.getMessage()); + } + } + + private static Token mapToken(JsonToken token) { + if (token == JsonToken.END_ARRAY) + return Token.END_ARRAY; + if (token == JsonToken.END_OBJECT) + return Token.END_OBJECT; + if (token == JsonToken.FIELD_NAME) + return Token.FIELD_NAME; + if (token == JsonToken.START_ARRAY) + return Token.START_ARRAY; + if (token == JsonToken.START_OBJECT) + return Token.START_OBJECT; + if (token == JsonToken.VALUE_FALSE) + return Token.VALUE_FALSE; + if (token == JsonToken.VALUE_NULL) + return Token.VALUE_NULL; + if (token == JsonToken.VALUE_NUMBER_FLOAT) + return Token.VALUE_NUMBER_FLOAT; + if (token == JsonToken.VALUE_NUMBER_INT) + return Token.VALUE_NUMBER_INT; + if (token == JsonToken.VALUE_STRING) + return Token.VALUE_STRING; + if (token == JsonToken.VALUE_TRUE) + return Token.VALUE_TRUE; + if (token == null) + return null; + + throw new JsonGeometryException("unexpected token"); + } @Override - JsonToken nextToken() throws JSONException, JsonParseException, IOException { - JsonToken token = m_jsonParser.nextToken(); - return token; + public Token nextToken() throws JsonGeometryException { + try { + JsonToken token = m_jsonParser.nextToken(); + return mapToken(token); + } catch (Exception ex) { + throw new JsonGeometryException(ex); + } } @Override - JsonToken currentToken() throws JSONException, JsonParseException, IOException { - return m_jsonParser.getCurrentToken(); + public Token currentToken() throws JsonGeometryException { + try { + return mapToken(m_jsonParser.getCurrentToken()); + } catch (Exception ex) { + throw new JsonGeometryException(ex); + } } @Override - void skipChildren() throws JSONException, JsonParseException, IOException { - m_jsonParser.skipChildren(); + public void skipChildren() throws JsonGeometryException { + try { + m_jsonParser.skipChildren(); + } catch (Exception ex) { + throw new JsonGeometryException(ex); + } + } @Override - String currentString() throws JSONException, JsonParseException, IOException { - return m_jsonParser.getText(); + public String currentString() throws JsonGeometryException { + try { + return m_jsonParser.getText(); + } catch (Exception ex) { + throw new JsonGeometryException(ex); + } + } @Override - double currentDoubleValue() throws JSONException, JsonParseException, IOException { - return m_jsonParser.getValueAsDouble(); + public double currentDoubleValue() throws JsonGeometryException { + try { + return m_jsonParser.getValueAsDouble(); + } catch (Exception ex) { + throw new JsonGeometryException(ex); + } + } @Override - int currentIntValue() throws JSONException, JsonParseException, IOException { - return m_jsonParser.getValueAsInt(); + public int currentIntValue() throws JsonGeometryException { + try { + return m_jsonParser.getValueAsInt(); + } catch (Exception ex) { + throw new JsonGeometryException(ex); + } + } + + @Override + public boolean currentBooleanValue() { + Token t = currentToken(); + if (t == Token.VALUE_TRUE) + return true; + else if (t == Token.VALUE_FALSE) + return false; + throw new JsonGeometryException("Not a boolean"); } } diff --git a/src/main/java/com/esri/core/geometry/JsonReader.java b/src/main/java/com/esri/core/geometry/JsonReader.java index b1f69d95..541143e3 100644 --- a/src/main/java/com/esri/core/geometry/JsonReader.java +++ b/src/main/java/com/esri/core/geometry/JsonReader.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2015 Esri + Copyright 1995-2017 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -23,29 +23,38 @@ */ package com.esri.core.geometry; -import java.util.ArrayList; - -import org.codehaus.jackson.JsonParser; -import org.codehaus.jackson.JsonToken; -import org.json.JSONArray; -import org.json.JSONObject; -import org.json.JSONException; -import org.codehaus.jackson.JsonParseException; - -import java.io.IOException; - -abstract class JsonReader { - - abstract JsonToken nextToken() throws JSONException, JsonParseException, IOException; - - abstract JsonToken currentToken() throws JSONException, JsonParseException, IOException; - - abstract void skipChildren() throws JSONException, JsonParseException, IOException; - - abstract String currentString() throws JSONException, JsonParseException, IOException; - - abstract double currentDoubleValue() throws JSONException, JsonParseException, IOException; - - abstract int currentIntValue() throws JSONException, JsonParseException, IOException; +/** + * An abstract reader for Json. + * + * See JsonParserReader for a concrete implementation around JsonParser. + */ +abstract public interface JsonReader { + public static enum Token { + END_ARRAY, + END_OBJECT, + FIELD_NAME, + START_ARRAY, + START_OBJECT, + VALUE_FALSE, + VALUE_NULL, + VALUE_NUMBER_FLOAT, + VALUE_NUMBER_INT, + VALUE_STRING, + VALUE_TRUE + } + + abstract public Token nextToken() throws JsonGeometryException; + + abstract public Token currentToken() throws JsonGeometryException; + + abstract public void skipChildren() throws JsonGeometryException; + + abstract public String currentString() throws JsonGeometryException; + + abstract public double currentDoubleValue() throws JsonGeometryException; + + abstract public int currentIntValue() throws JsonGeometryException; + + abstract public boolean currentBooleanValue() throws JsonGeometryException; } diff --git a/src/main/java/com/esri/core/geometry/JsonParserCursor.java b/src/main/java/com/esri/core/geometry/JsonReaderCursor.java similarity index 68% rename from src/main/java/com/esri/core/geometry/JsonParserCursor.java rename to src/main/java/com/esri/core/geometry/JsonReaderCursor.java index 1a1f14f3..94f72a30 100644 --- a/src/main/java/com/esri/core/geometry/JsonParserCursor.java +++ b/src/main/java/com/esri/core/geometry/JsonReaderCursor.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2015 Esri + Copyright 1995-2017 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -21,20 +21,34 @@ email: contracts@esri.com */ -package com.esri.core.geometry; +/* + COPYRIGHT 1995-2017 ESRI -import org.codehaus.jackson.JsonParser; + TRADE SECRETS: ESRI PROPRIETARY AND CONFIDENTIAL + Unpublished material - all rights reserved under the + Copyright Laws of the United States. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ + +package com.esri.core.geometry; /** - * An abstract JsonParser Cursor class. + * An abstract JsonReader Cursor class. */ -abstract class JsonParserCursor { +abstract class JsonReaderCursor { /** - * Moves the cursor to the next JsonParser. Returns null when reached the + * Moves the cursor to the next JsonReader. Returns null when reached the * end. */ - public abstract JsonParser next(); + public abstract JsonReader next(); /** * Returns the ID of the current geometry. The ID is propagated across the diff --git a/src/main/java/com/esri/core/geometry/JsonValueReader.java b/src/main/java/com/esri/core/geometry/JsonValueReader.java deleted file mode 100644 index 91028624..00000000 --- a/src/main/java/com/esri/core/geometry/JsonValueReader.java +++ /dev/null @@ -1,233 +0,0 @@ -/* - Copyright 1995-2015 Esri - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - - For additional information, contact: - Environmental Systems Research Institute, Inc. - Attn: Contracts Dept - 380 New York Street - Redlands, California, USA 92373 - - email: contracts@esri.com - */ -package com.esri.core.geometry; - -import java.util.ArrayList; - -import org.codehaus.jackson.JsonParser; -import org.codehaus.jackson.JsonToken; -import org.json.JSONArray; -import org.json.JSONObject; -import org.json.JSONException; -import org.codehaus.jackson.JsonParseException; - -import java.io.IOException; - - -final class JsonValueReader extends JsonReader { - - private Object m_object; - private JsonToken m_currentToken; - private ArrayList m_parentStack; - private ArrayList m_objIters; - private ArrayList m_arrIters; - - JsonValueReader(Object object) { - m_object = object; - - boolean bJSONObject = (m_object instanceof JSONObject); - boolean bJSONArray = (m_object instanceof JSONArray); - - if (!bJSONObject && !bJSONArray) { - throw new IllegalArgumentException(); - } - - m_parentStack = new ArrayList(0); - m_objIters = new ArrayList(0); - m_arrIters = new ArrayList(0); - - m_parentStack.ensureCapacity(4); - m_objIters.ensureCapacity(4); - m_arrIters.ensureCapacity(4); - - if (bJSONObject) { - JSONObjectEnumerator objIter = new JSONObjectEnumerator((JSONObject) m_object); - m_parentStack.add(JsonToken.START_OBJECT); - m_objIters.add(objIter); - m_currentToken = JsonToken.START_OBJECT; - } else { - JSONArrayEnumerator arrIter = new JSONArrayEnumerator((JSONArray) m_object); - m_parentStack.add(JsonToken.START_ARRAY); - m_arrIters.add(arrIter); - m_currentToken = JsonToken.START_ARRAY; - } - } - - private void setCurrentToken_(Object obj) { - if (obj instanceof String) { - m_currentToken = JsonToken.VALUE_STRING; - } else if (obj instanceof Double || obj instanceof Float) { - m_currentToken = JsonToken.VALUE_NUMBER_FLOAT; - } else if (obj instanceof Integer || obj instanceof Long || obj instanceof Short) { - m_currentToken = JsonToken.VALUE_NUMBER_INT; - } else if (obj instanceof Boolean) { - Boolean bObj = (Boolean) obj; - boolean b = bObj.booleanValue(); - if (b) { - m_currentToken = JsonToken.VALUE_TRUE; - } else { - m_currentToken = JsonToken.VALUE_FALSE; - } - } else if (obj instanceof JSONObject) { - m_currentToken = JsonToken.START_OBJECT; - } else if (obj instanceof JSONArray) { - m_currentToken = JsonToken.START_ARRAY; - } else { - m_currentToken = JsonToken.VALUE_NULL; - } - } - - Object currentObject_() { - assert (!m_parentStack.isEmpty()); - - JsonToken parentType = m_parentStack.get(m_parentStack.size() - 1); - - if (parentType == JsonToken.START_OBJECT) { - JSONObjectEnumerator objIter = m_objIters.get(m_objIters.size() - 1); - return objIter.getCurrentObject(); - } - - JSONArrayEnumerator arrIter = m_arrIters.get(m_arrIters.size() - 1); - return arrIter.getCurrentObject(); - } - - @Override - JsonToken nextToken() throws JSONException, JsonParseException { - if (m_parentStack.isEmpty()) { - m_currentToken = JsonToken.NOT_AVAILABLE; - return m_currentToken; - } - - JsonToken parentType = m_parentStack.get(m_parentStack.size() - 1); - - if (parentType == JsonToken.START_OBJECT) { - JSONObjectEnumerator iterator = m_objIters.get(m_objIters.size() - 1); - - if (m_currentToken == JsonToken.FIELD_NAME) { - Object nextJSONValue = iterator.getCurrentObject(); - - if (nextJSONValue instanceof JSONObject) { - m_parentStack.add(JsonToken.START_OBJECT); - m_objIters.add(new JSONObjectEnumerator((JSONObject) nextJSONValue)); - m_currentToken = JsonToken.START_OBJECT; - } else if (nextJSONValue instanceof JSONArray) { - m_parentStack.add(JsonToken.START_ARRAY); - m_arrIters.add(new JSONArrayEnumerator((JSONArray) nextJSONValue)); - m_currentToken = JsonToken.START_ARRAY; - } else { - setCurrentToken_(nextJSONValue); - } - } else { - if (iterator.next()) { - m_currentToken = JsonToken.FIELD_NAME; - } else { - m_objIters.remove(m_objIters.size() - 1); - m_parentStack.remove(m_parentStack.size() - 1); - m_currentToken = JsonToken.END_OBJECT; - } - } - } else { - assert (parentType == JsonToken.START_ARRAY); - JSONArrayEnumerator iterator = m_arrIters.get(m_arrIters.size() - 1); - if (iterator.next()) { - Object nextJSONValue = iterator.getCurrentObject(); - - if (nextJSONValue instanceof JSONObject) { - m_parentStack.add(JsonToken.START_OBJECT); - m_objIters.add(new JSONObjectEnumerator((JSONObject) nextJSONValue)); - m_currentToken = JsonToken.START_OBJECT; - } else if (nextJSONValue instanceof JSONArray) { - m_parentStack.add(JsonToken.START_ARRAY); - m_arrIters.add(new JSONArrayEnumerator((JSONArray) nextJSONValue)); - m_currentToken = JsonToken.START_ARRAY; - } else { - setCurrentToken_(nextJSONValue); - } - } else { - m_arrIters.remove(m_arrIters.size() - 1); - m_parentStack.remove(m_parentStack.size() - 1); - m_currentToken = JsonToken.END_ARRAY; - } - } - - return m_currentToken; - } - - @Override - JsonToken currentToken() throws JSONException, JsonParseException, IOException { - return m_currentToken; - } - - @Override - void skipChildren() throws JSONException, JsonParseException, IOException { - assert (!m_parentStack.isEmpty()); - - if (m_currentToken != JsonToken.START_OBJECT && m_currentToken != JsonToken.START_ARRAY) { - return; - } - - JsonToken parentType = m_parentStack.get(m_parentStack.size() - 1); - - if (parentType == JsonToken.START_OBJECT) { - m_objIters.remove(m_objIters.size() - 1); - m_parentStack.remove(m_parentStack.size() - 1); - m_currentToken = JsonToken.END_OBJECT; - } else { - m_arrIters.remove(m_arrIters.size() - 1); - m_parentStack.remove(m_parentStack.size() - 1); - m_currentToken = JsonToken.END_ARRAY; - } - } - - @Override - String currentString() throws JSONException, JsonParseException, IOException { - if (m_currentToken == JsonToken.FIELD_NAME) { - return m_objIters.get(m_objIters.size() - 1).getCurrentKey(); - } - - if (m_currentToken != JsonToken.VALUE_STRING) { - throw new GeometryException("invalid call"); - } - - return ((String) currentObject_()).toString(); - } - - @Override - double currentDoubleValue() throws JSONException, JsonParseException, IOException { - if (m_currentToken != JsonToken.VALUE_NUMBER_FLOAT && m_currentToken != JsonToken.VALUE_NUMBER_INT) { - throw new GeometryException("invalid call"); - } - - return ((Number) currentObject_()).doubleValue(); - } - - @Override - int currentIntValue() throws JSONException, JsonParseException, IOException { - if (m_currentToken != JsonToken.VALUE_NUMBER_INT) { - throw new GeometryException("invalid call"); - } - - return ((Number) currentObject_()).intValue(); - } -} diff --git a/src/main/java/com/esri/core/geometry/MultiPathImpl.java b/src/main/java/com/esri/core/geometry/MultiPathImpl.java index f6d606bb..dce85615 100644 --- a/src/main/java/com/esri/core/geometry/MultiPathImpl.java +++ b/src/main/java/com/esri/core/geometry/MultiPathImpl.java @@ -25,7 +25,7 @@ package com.esri.core.geometry; -import java.util.ArrayList; +import com.esri.core.geometry.MultiVertexGeometryImpl.DirtyFlags; final class MultiPathImpl extends MultiVertexGeometryImpl { @@ -2561,30 +2561,34 @@ public boolean _buildQuadTreeAccelerator(GeometryAccelerationDegree d) { return true; } - boolean _buildQuadTreeForPathsAccelerator(GeometryAccelerationDegree degree) { - if (m_accelerators == null) { - m_accelerators = new GeometryAccelerators(); - } + boolean _buildQuadTreeForPathsAccelerator(GeometryAccelerationDegree degree) { + if (m_accelerators == null) { + m_accelerators = new GeometryAccelerators(); + } - //TODO: when less than two envelopes - no need to this. + // TODO: when less than two envelopes - no need to this. - if (m_accelerators.getQuadTreeForPaths() != null) - return true; + if (m_accelerators.getQuadTreeForPaths() != null) + return true; - m_accelerators._setQuadTreeForPaths(null); - QuadTreeImpl quad_tree_impl = InternalUtils.buildQuadTreeForPaths(this); - m_accelerators._setQuadTreeForPaths(quad_tree_impl); + m_accelerators._setQuadTreeForPaths(null); + QuadTreeImpl quad_tree_impl = InternalUtils.buildQuadTreeForPaths(this); + m_accelerators._setQuadTreeForPaths(quad_tree_impl); - return true; - } + return true; + } - void setFillRule(int rule) { - assert(m_bPolygon); - m_fill_rule = rule; - } - int getFillRule() { - return m_fill_rule; - } + void setFillRule(int rule) { + assert (m_bPolygon); + m_fill_rule = rule; + } + int getFillRule() { + return m_fill_rule; + } + void clearDirtyOGCFlags() { + _setDirtyFlag(DirtyFlags.DirtyOGCFlags, false); + } } + diff --git a/src/main/java/com/esri/core/geometry/Operator.java b/src/main/java/com/esri/core/geometry/Operator.java index ffceb73f..9dcd804d 100644 --- a/src/main/java/com/esri/core/geometry/Operator.java +++ b/src/main/java/com/esri/core/geometry/Operator.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2015 Esri + Copyright 1995-2017 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -36,7 +36,6 @@ public enum Type { Project, ExportToJson, ImportFromJson, - @Deprecated ImportMapGeometryFromJson, ExportToESRIShape, ImportFromESRIShape, Union, Difference, diff --git a/src/main/java/com/esri/core/geometry/OperatorFactoryLocal.java b/src/main/java/com/esri/core/geometry/OperatorFactoryLocal.java index 160bcffd..727b4a69 100644 --- a/src/main/java/com/esri/core/geometry/OperatorFactoryLocal.java +++ b/src/main/java/com/esri/core/geometry/OperatorFactoryLocal.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2015 Esri + Copyright 1995-2017 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -38,10 +38,6 @@ import java.nio.channels.FileChannel; import java.util.HashMap; -import org.codehaus.jackson.JsonFactory; -import org.codehaus.jackson.JsonParseException; -import org.codehaus.jackson.JsonParser; - /** *An abstract class that represent the basic OperatorFactory interface. */ @@ -58,8 +54,6 @@ public class OperatorFactoryLocal extends OperatorFactory { new OperatorExportToJsonLocal()); st_supportedOperators.put(Type.ImportFromJson, new OperatorImportFromJsonLocal()); - st_supportedOperators.put(Type.ImportMapGeometryFromJson, - new OperatorImportFromJsonLocal()); st_supportedOperators.put(Type.ExportToESRIShape, new OperatorExportToESRIShapeLocal()); st_supportedOperators.put(Type.ImportFromESRIShape, @@ -202,14 +196,7 @@ public static MapGeometry loadGeometryFromJSONFileDbg(String file_name) { } catch (Exception ex) { } - JsonFactory jf = new JsonFactory(); - JsonParser jp = null; - try { - jp = jf.createJsonParser(jsonString); - jp.nextToken(); - } catch (Exception ex) { - } - MapGeometry mapGeom = OperatorImportFromJson.local().execute(Geometry.Type.Unknown, jp); + MapGeometry mapGeom = OperatorImportFromJson.local().execute(Geometry.Type.Unknown, jsonString); return mapGeom; } diff --git a/src/main/java/com/esri/core/geometry/OperatorImportFromGeoJson.java b/src/main/java/com/esri/core/geometry/OperatorImportFromGeoJson.java index 88cb4653..87de2d4a 100644 --- a/src/main/java/com/esri/core/geometry/OperatorImportFromGeoJson.java +++ b/src/main/java/com/esri/core/geometry/OperatorImportFromGeoJson.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2015 Esri + Copyright 1995-2017 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -23,11 +23,6 @@ */ package com.esri.core.geometry; -import org.json.JSONException; -import org.json.JSONObject; - -import java.io.IOException; - public abstract class OperatorImportFromGeoJson extends Operator { @Override @@ -43,7 +38,7 @@ public Type getType() { * @return Returns the imported MapGeometry. * @throws JsonGeometryException */ - public abstract MapGeometry execute(int importFlags, Geometry.Type type, JSONObject jsonObject, ProgressTracker progressTracker) throws JSONException; + public abstract MapGeometry execute(int importFlags, Geometry.Type type, JsonReader jsonReader, ProgressTracker progressTracker); /** * Deprecated, use version without import_flags. @@ -57,7 +52,7 @@ public Type getType() { * @throws JSONException * */ - public abstract MapGeometry execute(int import_flags, Geometry.Type type, String geoJsonString, ProgressTracker progress_tracker) throws JSONException; + public abstract MapGeometry execute(int import_flags, Geometry.Type type, String geoJsonString, ProgressTracker progress_tracker); /** * @@ -68,7 +63,7 @@ public Type getType() { * @return Returns the imported MapOGCStructure. * @throws JSONException */ - public abstract MapOGCStructure executeOGC(int import_flags, String geoJsonString, ProgressTracker progress_tracker) throws JSONException; + public abstract MapOGCStructure executeOGC(int import_flags, String geoJsonString, ProgressTracker progress_tracker); public static OperatorImportFromGeoJson local() { return (OperatorImportFromGeoJson) OperatorFactoryLocal.getInstance().getOperator(Type.ImportFromGeoJson); diff --git a/src/main/java/com/esri/core/geometry/OperatorImportFromGeoJsonLocal.java b/src/main/java/com/esri/core/geometry/OperatorImportFromGeoJsonLocal.java index e04952a4..78e9c8b1 100644 --- a/src/main/java/com/esri/core/geometry/OperatorImportFromGeoJsonLocal.java +++ b/src/main/java/com/esri/core/geometry/OperatorImportFromGeoJsonLocal.java @@ -24,57 +24,48 @@ package com.esri.core.geometry; import com.esri.core.geometry.VertexDescription.Semantics; -import org.codehaus.jackson.JsonFactory; -import org.codehaus.jackson.JsonParseException; -import org.codehaus.jackson.JsonParser; -import org.codehaus.jackson.JsonToken; -import org.json.JSONArray; -import org.json.JSONException; -import org.json.JSONObject; - -import java.io.IOException; import java.util.ArrayList; class OperatorImportFromGeoJsonLocal extends OperatorImportFromGeoJson { + static enum GeoJsonType { + Point, LineString, Polygon, MultiPoint, MultiLineString, MultiPolygon, GeometryCollection; + static GeoJsonType fromGeoJsonValue(int v) { + return GeoJsonType.values()[v - 1]; + } + + public int geogsjonvalue() { + return ordinal() + 1; + } + }; + + static interface GeoJsonValues { + public final static int Point = GeoJsonType.Point.geogsjonvalue(); + public final static int LineString = GeoJsonType.LineString.geogsjonvalue(); + public final static int Polygon = GeoJsonType.Polygon.geogsjonvalue(); + public final static int MultiPoint = GeoJsonType.MultiPoint.geogsjonvalue(); + public final static int MultiLineString = GeoJsonType.MultiLineString.geogsjonvalue(); + public final static int MultiPolygon = GeoJsonType.MultiPolygon.geogsjonvalue(); + public final static int GeometryCollection = GeoJsonType.GeometryCollection.geogsjonvalue(); + }; @Override - public MapGeometry execute(int importFlags, Geometry.Type type, String geoJsonString, - ProgressTracker progressTracker) throws JSONException { - try { - JsonFactory factory = new JsonFactory(); - JsonParser jsonParser = factory.createJsonParser(geoJsonString); - - jsonParser.nextToken(); - - MapGeometry map_geometry = OperatorImportFromGeoJsonHelper.importFromGeoJson(importFlags, type, - new JsonParserReader(jsonParser), progressTracker, false); - return map_geometry; - - } catch (JSONException jsonException) { - throw jsonException; - } catch (JsonParseException jsonParseException) { - throw new JSONException(jsonParseException.getMessage()); - } catch (IOException ioException) { - throw new JSONException(ioException.getMessage()); - } + public MapGeometry execute(int importFlags, Geometry.Type type, + String geoJsonString, ProgressTracker progressTracker) + throws JsonGeometryException { + MapGeometry map_geometry = OperatorImportFromGeoJsonHelper + .importFromGeoJson(importFlags, type, JsonParserReader.createFromString(geoJsonString), progressTracker, false); + return map_geometry; } @Override - public MapGeometry execute(int importFlags, Geometry.Type type, JSONObject jsonObject, - ProgressTracker progressTracker) throws JSONException { - if (jsonObject == null) + public MapGeometry execute(int importFlags, Geometry.Type type, + JsonReader jsonReader, ProgressTracker progressTracker) + throws JsonGeometryException { + if (jsonReader == null) return null; - try { - return OperatorImportFromGeoJsonHelper.importFromGeoJson(importFlags, type, new JsonValueReader(jsonObject), - progressTracker, false); - } catch (JSONException jsonException) { - throw jsonException; - } catch (JsonParseException jsonParseException) { - throw new JSONException(jsonParseException.getMessage()); - } catch (IOException ioException) { - throw new JSONException(ioException.getMessage()); - } + return OperatorImportFromGeoJsonHelper.importFromGeoJson(importFlags, + type, jsonReader, progressTracker, false); } static final class OperatorImportFromGeoJsonHelper { @@ -91,6 +82,8 @@ static final class OperatorImportFromGeoJsonHelper { private boolean m_b_has_ms_known; private int m_num_embeddings; + int m_ogcType; + OperatorImportFromGeoJsonHelper() { m_position = null; m_zs = null; @@ -103,45 +96,129 @@ static final class OperatorImportFromGeoJsonHelper { m_b_has_zs_known = false; m_b_has_ms_known = false; m_num_embeddings = 0; + m_ogcType = 0; } - static MapGeometry importFromGeoJson(int importFlags, Geometry.Type type, JsonReader json_iterator, + static MapGeometry importFromGeoJson(int importFlags, + Geometry.Type type, JsonReader json_iterator, ProgressTracker progress_tracker, boolean skip_coordinates) - throws JSONException, JsonParseException, IOException { - assert(json_iterator.currentToken() == JsonToken.START_OBJECT); + throws JsonGeometryException { + OperatorImportFromGeoJsonHelper geo_json_helper = new OperatorImportFromGeoJsonHelper(); + MapOGCStructure ms = geo_json_helper.importFromGeoJsonImpl( + importFlags, type, json_iterator, progress_tracker, + skip_coordinates, 0); + + if (geo_json_helper.m_ogcType == GeoJsonValues.GeometryCollection && !skip_coordinates) + throw new JsonGeometryException("parsing error"); + + return new MapGeometry(ms.m_ogcStructure.m_geometry, + ms.m_spatialReference); + } + static MapOGCStructure importFromGeoJson(int importFlags, + Geometry.Type type, JsonReader json_iterator, + ProgressTracker progress_tracker, boolean skip_coordinates, + int recursion) throws JsonGeometryException { OperatorImportFromGeoJsonHelper geo_json_helper = new OperatorImportFromGeoJsonHelper(); + MapOGCStructure ms = geo_json_helper.importFromGeoJsonImpl( + importFlags, type, json_iterator, progress_tracker, + skip_coordinates, recursion); + + if (geo_json_helper.m_ogcType == GeoJsonValues.GeometryCollection && !skip_coordinates) + throw new JsonGeometryException("parsing error"); + + return ms; + } + MapOGCStructure importFromGeoJsonImpl(int importFlags, + Geometry.Type type, JsonReader json_iterator, + ProgressTracker progress_tracker, boolean skip_coordinates, + int recursion) throws JsonGeometryException { + OperatorImportFromGeoJsonHelper geo_json_helper = this; boolean b_type_found = false; boolean b_coordinates_found = false; boolean b_crs_found = false; boolean b_crsURN_found = false; - String geo_json_type = null; + boolean b_geometry_collection = false; + boolean b_geometries_found = false; + GeoJsonType geo_json_type = null; Geometry geometry = null; SpatialReference spatial_reference = null; - JsonToken current_token; + JsonReader.Token current_token; String field_name = null; + MapOGCStructure ms = new MapOGCStructure(); - while ((current_token = json_iterator.nextToken()) != JsonToken.END_OBJECT) { + while ((current_token = json_iterator.nextToken()) != JsonReader.Token.END_OBJECT) { field_name = json_iterator.currentString(); if (field_name.equals("type")) { if (b_type_found) { - throw new IllegalArgumentException("invalid argument"); + throw new JsonGeometryException("parsing error"); } b_type_found = true; current_token = json_iterator.nextToken(); - if (current_token != JsonToken.VALUE_STRING) { - throw new IllegalArgumentException("invalid argument"); + if (current_token != JsonReader.Token.VALUE_STRING) { + throw new JsonGeometryException("parsing error"); } - geo_json_type = json_iterator.currentString(); + String s = json_iterator.currentString(); + try { + geo_json_type = GeoJsonType.valueOf(s); + } catch (Exception ex) { + throw new JsonGeometryException(s); + } + + if (geo_json_type == GeoJsonType.GeometryCollection) { + if (type != Geometry.Type.Unknown) + throw new JsonGeometryException("parsing error"); + + b_geometry_collection = true; + } + } else if (field_name.equals("geometries")) { + b_geometries_found = true; + if (type != Geometry.Type.Unknown) + throw new JsonGeometryException("parsing error"); + + if (recursion > 10) { + throw new JsonGeometryException("deep geojson"); + } + + if (skip_coordinates) { + json_iterator.skipChildren(); + } else { + current_token = json_iterator.nextToken(); + + ms.m_ogcStructure = new OGCStructure(); + ms.m_ogcStructure.m_type = GeoJsonValues.GeometryCollection; + ms.m_ogcStructure.m_structures = new ArrayList( + 0); + + if (current_token == JsonReader.Token.START_ARRAY) { + current_token = json_iterator.nextToken(); + while (current_token != JsonReader.Token.END_ARRAY) { + MapOGCStructure child = importFromGeoJson( + importFlags + | GeoJsonImportFlags.geoJsonImportSkipCRS, + type, json_iterator, + progress_tracker, false, + recursion + 1); + ms.m_ogcStructure.m_structures + .add(child.m_ogcStructure); + + current_token = json_iterator.nextToken(); + } + } + else if (current_token != JsonReader.Token.VALUE_NULL) { + throw new JsonGeometryException("parsing error"); + } + } } else if (field_name.equals("coordinates")) { + if (b_coordinates_found) { - throw new IllegalArgumentException("invalid argument"); + throw new JsonGeometryException("parsing error"); } b_coordinates_found = true; @@ -152,36 +229,38 @@ static MapGeometry importFromGeoJson(int importFlags, Geometry.Type type, JsonRe } else {// According to the spec, the value of the // coordinates must be an array. However, I do an // extra check for null too. - if (current_token != JsonToken.VALUE_NULL) { - if (current_token != JsonToken.START_ARRAY) { - throw new IllegalArgumentException("invalid argument"); + if (current_token != JsonReader.Token.VALUE_NULL) { + if (current_token != JsonReader.Token.START_ARRAY) { + throw new JsonGeometryException("parsing error"); } - geo_json_helper.import_coordinates_(json_iterator, progress_tracker); + geo_json_helper.import_coordinates_(json_iterator, + progress_tracker); } } } else if (field_name.equals("crs")) { if (b_crs_found || b_crsURN_found) { - throw new IllegalArgumentException("invalid argument"); + throw new JsonGeometryException("parsing error"); } b_crs_found = true; current_token = json_iterator.nextToken(); if ((importFlags & GeoJsonImportFlags.geoJsonImportSkipCRS) == 0) - spatial_reference = importSpatialReferenceFromCrs(json_iterator, progress_tracker); + spatial_reference = importSpatialReferenceFromCrs( + json_iterator, progress_tracker); else json_iterator.skipChildren(); } else if (field_name.equals("crsURN")) { if (b_crs_found || b_crsURN_found) { - throw new IllegalArgumentException("invalid argument"); + throw new JsonGeometryException("parsing error"); } b_crsURN_found = true; current_token = json_iterator.nextToken(); - spatial_reference = importSpatialReferenceFromCrsUrn_(json_iterator, - progress_tracker); + spatial_reference = importSpatialReferenceFromCrsUrn_( + json_iterator, progress_tracker); } else { json_iterator.nextToken(); json_iterator.skipChildren(); @@ -190,14 +269,27 @@ static MapGeometry importFromGeoJson(int importFlags, Geometry.Type type, JsonRe // According to the spec, a GeoJSON object must have both a type and // a coordinates array - if (!b_type_found || (!b_coordinates_found && !skip_coordinates)) { - throw new IllegalArgumentException("invalid argument"); + if (!b_type_found || (!b_geometry_collection && !b_coordinates_found && !skip_coordinates)) { + throw new JsonGeometryException("parsing error"); + } + + if ((!b_geometry_collection && b_geometries_found) || (b_geometry_collection && !b_geometries_found)) { + throw new JsonGeometryException("parsing error");//found "geometries" but did not see "GeometryCollection" } + - if (!skip_coordinates) - geometry = geo_json_helper.createGeometry_(geo_json_type, type.value()); + if (!skip_coordinates && !b_geometry_collection) { + geometry = geo_json_helper.createGeometry_(geo_json_type, + type.value()); + + ms.m_ogcStructure = new OGCStructure(); + ms.m_ogcStructure.m_type = m_ogcType; + ms.m_ogcStructure.m_geometry = geometry; + } - if (!b_crs_found && !b_crsURN_found && ((importFlags & GeoJsonImportFlags.geoJsonImportSkipCRS) == 0) + if (!b_crs_found + && !b_crsURN_found + && ((importFlags & GeoJsonImportFlags.geoJsonImportSkipCRS) == 0) && ((importFlags & GeoJsonImportFlags.geoJsonImportNoWGS84Default) == 0)) { spatial_reference = SpatialReference.create(4326); // the spec // gives a @@ -207,12 +299,8 @@ static MapGeometry importFromGeoJson(int importFlags, Geometry.Type type, JsonRe // is given } - MapGeometry map_geometry = new MapGeometry(geometry, spatial_reference); - - assert(geo_json_helper.m_paths == null || (geo_json_helper.m_path_flags != null - && geo_json_helper.m_paths.size() == geo_json_helper.m_path_flags.size())); - - return map_geometry; + ms.m_spatialReference = spatial_reference; + return ms; } // We have to import the coordinates in the most general way possible to @@ -225,86 +313,88 @@ static MapGeometry importFromGeoJson(int importFlags, Geometry.Type type, JsonRe // coordinates // into the attribute stream(s), and will later assign them to a // geometry after the type tag is found. - private void import_coordinates_(JsonReader json_iterator, ProgressTracker progress_tracker) - throws JSONException, JsonParseException, IOException { - assert(json_iterator.currentToken() == JsonToken.START_ARRAY); + private void import_coordinates_(JsonReader json_iterator, + ProgressTracker progress_tracker) throws JsonGeometryException { + assert (json_iterator.currentToken() == JsonReader.Token.START_ARRAY); int coordinates_level_lower = 1; int coordinates_level_upper = 4; json_iterator.nextToken(); - while (json_iterator.currentToken() != JsonToken.END_ARRAY) { + while (json_iterator.currentToken() != JsonReader.Token.END_ARRAY) { if (isDouble_(json_iterator)) { if (coordinates_level_upper > 1) { coordinates_level_upper = 1; } - } else if (json_iterator.currentToken() == JsonToken.START_ARRAY) { + } else if (json_iterator.currentToken() == JsonReader.Token.START_ARRAY) { if (coordinates_level_lower < 2) { coordinates_level_lower = 2; } } else { - throw new IllegalArgumentException("invalid argument"); + throw new JsonGeometryException("parsing error"); } if (coordinates_level_lower > coordinates_level_upper) { throw new IllegalArgumentException("invalid argument"); } - if (coordinates_level_lower == coordinates_level_upper && coordinates_level_lower == 1) {// special - // code - // for - // Points + if (coordinates_level_lower == coordinates_level_upper + && coordinates_level_lower == 1) {// special + // code + // for + // Points readCoordinateAsPoint_(json_iterator); } else { boolean b_add_path_level_3 = true; boolean b_polygon_start_level_4 = true; - assert(json_iterator.currentToken() == JsonToken.START_ARRAY); + assert (json_iterator.currentToken() == JsonReader.Token.START_ARRAY); json_iterator.nextToken(); - while (json_iterator.currentToken() != JsonToken.END_ARRAY) { + while (json_iterator.currentToken() != JsonReader.Token.END_ARRAY) { if (isDouble_(json_iterator)) { if (coordinates_level_upper > 2) { coordinates_level_upper = 2; } - } else if (json_iterator.currentToken() == JsonToken.START_ARRAY) { + } else if (json_iterator.currentToken() == JsonReader.Token.START_ARRAY) { if (coordinates_level_lower < 3) { coordinates_level_lower = 3; } } else { - throw new IllegalArgumentException("invalid argument"); + throw new JsonGeometryException("parsing error"); } if (coordinates_level_lower > coordinates_level_upper) { - throw new IllegalArgumentException("invalid argument"); + throw new JsonGeometryException("parsing error"); } - if (coordinates_level_lower == coordinates_level_upper && coordinates_level_lower == 2) {// LineString - // or - // MultiPoint + if (coordinates_level_lower == coordinates_level_upper + && coordinates_level_lower == 2) {// LineString + // or + // MultiPoint addCoordinate_(json_iterator); } else { boolean b_add_path_level_4 = true; - assert(json_iterator.currentToken() == JsonToken.START_ARRAY); + assert (json_iterator.currentToken() == JsonReader.Token.START_ARRAY); json_iterator.nextToken(); - while (json_iterator.currentToken() != JsonToken.END_ARRAY) { + while (json_iterator.currentToken() != JsonReader.Token.END_ARRAY) { if (isDouble_(json_iterator)) { if (coordinates_level_upper > 3) { coordinates_level_upper = 3; } - } else if (json_iterator.currentToken() == JsonToken.START_ARRAY) { + } else if (json_iterator.currentToken() == JsonReader.Token.START_ARRAY) { if (coordinates_level_lower < 4) { coordinates_level_lower = 4; } } else { - throw new IllegalArgumentException("invalid argument"); + throw new JsonGeometryException("parsing error"); } if (coordinates_level_lower > coordinates_level_upper) { - throw new IllegalArgumentException("invalid argument"); + throw new JsonGeometryException("parsing error"); } if (coordinates_level_lower == coordinates_level_upper @@ -318,16 +408,15 @@ private void import_coordinates_(JsonReader json_iterator, ProgressTracker progr addCoordinate_(json_iterator); } else { - assert(json_iterator.currentToken() == JsonToken.START_ARRAY); + assert (json_iterator.currentToken() == JsonReader.Token.START_ARRAY); json_iterator.nextToken(); - if (json_iterator.currentToken() != JsonToken.END_ARRAY) { + if (json_iterator.currentToken() != JsonReader.Token.END_ARRAY) { if (!isDouble_(json_iterator)) { - throw new IllegalArgumentException("invalid argument"); + throw new JsonGeometryException("parsing error"); } - assert(coordinates_level_lower == coordinates_level_upper - && coordinates_level_lower == 4); + assert (coordinates_level_lower == coordinates_level_upper && coordinates_level_lower == 4); // MultiPolygon if (b_add_path_level_4) { @@ -363,8 +452,8 @@ private void import_coordinates_(JsonReader json_iterator, ProgressTracker progr } private void readCoordinateAsPoint_(JsonReader json_iterator) - throws JSONException, JsonParseException, IOException { - assert(isDouble_(json_iterator)); + throws JsonGeometryException { + assert (isDouble_(json_iterator)); m_point = new Point(); @@ -391,16 +480,18 @@ private void readCoordinateAsPoint_(JsonReader json_iterator) m_point.setM(m); } - if (json_iterator.currentToken() != JsonToken.END_ARRAY) { - throw new IllegalArgumentException("invalid argument"); + if (json_iterator.currentToken() != JsonReader.Token.END_ARRAY) { + throw new JsonGeometryException("parsing error"); } } - private void addCoordinate_(JsonReader json_iterator) throws JSONException, JsonParseException, IOException { - assert(isDouble_(json_iterator)); + private void addCoordinate_(JsonReader json_iterator) + throws JsonGeometryException { + assert (isDouble_(json_iterator)); if (m_position == null) { - m_position = (AttributeStreamOfDbl) AttributeStreamBase.createDoubleStream(0); + m_position = (AttributeStreamOfDbl) AttributeStreamBase + .createDoubleStream(0); } double x = readDouble_(json_iterator); @@ -417,11 +508,14 @@ private void addCoordinate_(JsonReader json_iterator) throws JSONException, Json if (!m_b_has_zs_known) { m_b_has_zs_known = true; m_b_has_zs = true; - m_zs = (AttributeStreamOfDbl) AttributeStreamBase.createDoubleStream(0); + m_zs = (AttributeStreamOfDbl) AttributeStreamBase + .createDoubleStream(0); } else { if (!m_b_has_zs) { - m_zs = (AttributeStreamOfDbl) AttributeStreamBase.createDoubleStream(size >> 1, - VertexDescription.getDefaultValue(Semantics.Z)); + m_zs = (AttributeStreamOfDbl) AttributeStreamBase + .createDoubleStream(size >> 1, + VertexDescription + .getDefaultValue(Semantics.Z)); m_b_has_zs = true; } } @@ -444,11 +538,14 @@ private void addCoordinate_(JsonReader json_iterator) throws JSONException, Json if (!m_b_has_ms_known) { m_b_has_ms_known = true; m_b_has_ms = true; - m_ms = (AttributeStreamOfDbl) AttributeStreamBase.createDoubleStream(0); + m_ms = (AttributeStreamOfDbl) AttributeStreamBase + .createDoubleStream(0); } else { if (!m_b_has_ms) { - m_ms = (AttributeStreamOfDbl) AttributeStreamBase.createDoubleStream(size >> 1, - VertexDescription.getDefaultValue(Semantics.M)); + m_ms = (AttributeStreamOfDbl) AttributeStreamBase + .createDoubleStream(size >> 1, + VertexDescription + .getDefaultValue(Semantics.M)); m_b_has_ms = true; } } @@ -467,14 +564,15 @@ private void addCoordinate_(JsonReader json_iterator) throws JSONException, Json } } - if (json_iterator.currentToken() != JsonToken.END_ARRAY) { - throw new IllegalArgumentException("invalid argument"); + if (json_iterator.currentToken() != JsonReader.Token.END_ARRAY) { + throw new JsonGeometryException("parsing error"); } } private void addPath_() { if (m_paths == null) { - m_paths = (AttributeStreamOfInt32) AttributeStreamBase.createIndexStream(0); + m_paths = (AttributeStreamOfInt32) AttributeStreamBase + .createIndexStream(0); } if (m_position == null) { @@ -486,68 +584,77 @@ private void addPath_() { private void addPathFlag_(boolean b_polygon_start) { if (m_path_flags == null) { - m_path_flags = (AttributeStreamOfInt8) AttributeStreamBase.createByteStream(0); + m_path_flags = (AttributeStreamOfInt8) AttributeStreamBase + .createByteStream(0); } if (b_polygon_start) { - m_path_flags.add((byte) (PathFlags.enumClosed | PathFlags.enumOGCStartPolygon)); + m_path_flags + .add((byte) (PathFlags.enumClosed | PathFlags.enumOGCStartPolygon)); } else { m_path_flags.add((byte) PathFlags.enumClosed); } } - private double readDouble_(JsonReader json_iterator) throws JSONException, JsonParseException, IOException { - JsonToken current_token = json_iterator.currentToken(); - if (current_token == JsonToken.VALUE_NULL - || (current_token == JsonToken.VALUE_STRING && json_iterator.currentString().equals("NaN"))) { + private double readDouble_(JsonReader json_iterator) + throws JsonGeometryException { + JsonReader.Token current_token = json_iterator.currentToken(); + if (current_token == JsonReader.Token.VALUE_NULL + || (current_token == JsonReader.Token.VALUE_STRING && json_iterator + .currentString().equals("NaN"))) { return NumberUtils.NaN(); } else { return json_iterator.currentDoubleValue(); } } - private boolean isDouble_(JsonReader json_iterator) throws JSONException, JsonParseException, IOException { - JsonToken current_token = json_iterator.currentToken(); + private boolean isDouble_(JsonReader json_iterator) + throws JsonGeometryException { + JsonReader.Token current_token = json_iterator.currentToken(); - if (current_token == JsonToken.VALUE_NUMBER_FLOAT) { + if (current_token == JsonReader.Token.VALUE_NUMBER_FLOAT) { return true; } - if (current_token == JsonToken.VALUE_NUMBER_INT) { + if (current_token == JsonReader.Token.VALUE_NUMBER_INT) { return true; } - if (current_token == JsonToken.VALUE_NULL - || (current_token == JsonToken.VALUE_STRING && json_iterator.currentString().equals("NaN"))) { + if (current_token == JsonReader.Token.VALUE_NULL + || (current_token == JsonReader.Token.VALUE_STRING && json_iterator + .currentString().equals("NaN"))) { return true; } return false; } - private Geometry createGeometry_(String geo_json_type, int type) - throws JSONException, JsonParseException, IOException { + //does not accept GeometryCollection + private Geometry createGeometry_(GeoJsonType geo_json_type, int type) + throws JsonGeometryException { Geometry geometry; if (type != Geometry.GeometryType.Unknown) { switch (type) { case Geometry.GeometryType.Polygon: - if (!geo_json_type.equals("MultiPolygon") && !geo_json_type.equals("Polygon")) { + if (geo_json_type != GeoJsonType.MultiPolygon + && geo_json_type != GeoJsonType.Polygon) { throw new GeometryException("invalid shape type"); } break; case Geometry.GeometryType.Polyline: - if (!geo_json_type.equals("MultiLineString") && !geo_json_type.equals("LineString")) { + if (geo_json_type != GeoJsonType.MultiLineString + && geo_json_type != GeoJsonType.LineString) { throw new GeometryException("invalid shape type"); } break; case Geometry.GeometryType.MultiPoint: - if (!geo_json_type.equals("MultiPoint")) { + if (geo_json_type != GeoJsonType.MultiPoint) { throw new GeometryException("invalid shape type"); } break; case Geometry.GeometryType.Point: - if (!geo_json_type.equals("Point")) { + if (geo_json_type != GeoJsonType.Point) { throw new GeometryException("invalid shape type"); } break; @@ -555,70 +662,88 @@ private Geometry createGeometry_(String geo_json_type, int type) throw new GeometryException("invalid shape type"); } } - + + m_ogcType = geo_json_type.geogsjonvalue(); + if (geo_json_type == GeoJsonType.GeometryCollection) + throw new IllegalArgumentException("invalid argument"); + if (m_position == null && m_point == null) { - if (geo_json_type.equals("Point")) { + switch (geo_json_type) + { + case Point: { if (m_num_embeddings > 1) { - throw new IllegalArgumentException("invalid argument"); + throw new JsonGeometryException("parsing error"); } geometry = new Point(); - } else if (geo_json_type.equals("MultiPoint")) { + break; + } + case MultiPoint: { if (m_num_embeddings > 2) { - throw new IllegalArgumentException("invalid argument"); + throw new JsonGeometryException("parsing error"); } geometry = new MultiPoint(); - } else if (geo_json_type.equals("LineString")) { + break; + } + case LineString: { if (m_num_embeddings > 2) { - throw new IllegalArgumentException("invalid argument"); + throw new JsonGeometryException("parsing error"); } geometry = new Polyline(); - } else if (geo_json_type.equals("MultiLineString")) { + break; + } + case MultiLineString: { if (m_num_embeddings > 3) { - throw new IllegalArgumentException("invalid argument"); + throw new JsonGeometryException("parsing error"); } geometry = new Polyline(); - } else if (geo_json_type.equals("Polygon")) { + break; + } + case Polygon: { if (m_num_embeddings > 3) { - throw new IllegalArgumentException("invalid argument"); + throw new JsonGeometryException("parsing error"); } geometry = new Polygon(); - } else if (geo_json_type.equals("MultiPolygon")) { - assert(m_num_embeddings <= 4); + break; + } + case MultiPolygon: { + assert (m_num_embeddings <= 4); geometry = new Polygon(); - } else { - throw new IllegalArgumentException("invalid argument"); + break; + } + default: + throw new JsonGeometryException("parsing error"); } } else if (m_num_embeddings == 1) { - if (!geo_json_type.equals("Point")) { - throw new IllegalArgumentException("invalid argument"); + if (geo_json_type != GeoJsonType.Point) { + throw new JsonGeometryException("parsing error"); } - assert(m_point != null); + assert (m_point != null); geometry = m_point; } else if (m_num_embeddings == 2) { - if (geo_json_type.equals("MultiPoint")) { + if (geo_json_type == GeoJsonType.MultiPoint) { geometry = createMultiPointFromStreams_(); - } else if (geo_json_type.equals("LineString")) { + } else if (geo_json_type == GeoJsonType.LineString) { geometry = createPolylineFromStreams_(); } else { - throw new IllegalArgumentException("invalid argument"); + throw new JsonGeometryException("parsing error"); } } else if (m_num_embeddings == 3) { - if (geo_json_type.equals("Polygon")) { + if (geo_json_type == GeoJsonType.Polygon) { geometry = createPolygonFromStreams_(); - } else if (geo_json_type.equals("MultiLineString")) { + } else if (geo_json_type == GeoJsonType.MultiLineString) { geometry = createPolylineFromStreams_(); } else { - throw new IllegalArgumentException("invalid argument"); + throw new JsonGeometryException("parsing error"); } } else { - if (!geo_json_type.equals("MultiPolygon")) { - throw new IllegalArgumentException("invalid argument"); + if (geo_json_type != GeoJsonType.MultiPolygon) { + throw new JsonGeometryException("parsing error"); } geometry = createPolygonFromStreams_(); @@ -628,29 +753,34 @@ private Geometry createGeometry_(String geo_json_type, int type) } private Geometry createPolygonFromStreams_() { - assert(m_position != null); - assert(m_paths != null); - assert((m_num_embeddings == 3 && m_path_flags == null) || (m_num_embeddings == 4 && m_path_flags != null)); + assert (m_position != null); + assert (m_paths != null); + assert ((m_num_embeddings == 3 && m_path_flags == null) || (m_num_embeddings == 4 && m_path_flags != null)); Polygon polygon = new Polygon(); MultiPathImpl multi_path_impl = (MultiPathImpl) polygon._getImpl(); checkPathPointCountsForMultiPath_(true); - multi_path_impl.setAttributeStreamRef(Semantics.POSITION, m_position); + multi_path_impl.setAttributeStreamRef(Semantics.POSITION, + m_position); if (m_b_has_zs) { - assert(m_zs != null); + assert (m_zs != null); multi_path_impl.setAttributeStreamRef(Semantics.Z, m_zs); } if (m_b_has_ms) { - assert(m_ms != null); + assert (m_ms != null); multi_path_impl.setAttributeStreamRef(Semantics.M, m_ms); } if (m_path_flags == null) { - m_path_flags = (AttributeStreamOfInt8) AttributeStreamBase.createByteStream(m_paths.size(), (byte) 0); - m_path_flags.setBits(0, (byte) (PathFlags.enumClosed | PathFlags.enumOGCStartPolygon)); + m_path_flags = (AttributeStreamOfInt8) AttributeStreamBase + .createByteStream(m_paths.size(), (byte) 0); + m_path_flags + .setBits( + 0, + (byte) (PathFlags.enumClosed | PathFlags.enumOGCStartPolygon)); for (int i = 1; i < m_path_flags.size() - 1; i++) { m_path_flags.setBits(i, (byte) PathFlags.enumClosed); @@ -659,13 +789,15 @@ private Geometry createPolygonFromStreams_() { multi_path_impl.setPathStreamRef(m_paths); multi_path_impl.setPathFlagsStreamRef(m_path_flags); - multi_path_impl.notifyModified(MultiVertexGeometryImpl.DirtyFlags.DirtyAll); + multi_path_impl + .notifyModified(MultiVertexGeometryImpl.DirtyFlags.DirtyAll); - AttributeStreamOfInt8 path_flags_clone = new AttributeStreamOfInt8(m_path_flags); + AttributeStreamOfInt8 path_flags_clone = new AttributeStreamOfInt8( + m_path_flags); for (int i = 0; i < path_flags_clone.size() - 1; i++) { - assert((path_flags_clone.read(i) & PathFlags.enumClosed) != 0); - assert((m_path_flags.read(i) & PathFlags.enumClosed) != 0); + assert ((path_flags_clone.read(i) & PathFlags.enumClosed) != 0); + assert ((m_path_flags.read(i) & PathFlags.enumClosed) != 0); if ((path_flags_clone.read(i) & PathFlags.enumOGCStartPolygon) != 0) {// Should // be @@ -680,69 +812,76 @@ private Geometry createPolygonFromStreams_() { } } } + multi_path_impl.setPathFlagsStreamRef(path_flags_clone); - multi_path_impl.setDirtyOGCFlags(false); + multi_path_impl.clearDirtyOGCFlags(); return polygon; } private Geometry createPolylineFromStreams_() { - assert(m_position != null); - assert((m_num_embeddings == 2 && m_paths == null) || (m_num_embeddings == 3 && m_paths != null)); - assert(m_path_flags == null); + assert (m_position != null); + assert ((m_num_embeddings == 2 && m_paths == null) || (m_num_embeddings == 3 && m_paths != null)); + assert (m_path_flags == null); Polyline polyline = new Polyline(); MultiPathImpl multi_path_impl = (MultiPathImpl) polyline._getImpl(); if (m_paths == null) { - m_paths = (AttributeStreamOfInt32) AttributeStreamBase.createIndexStream(0); + m_paths = (AttributeStreamOfInt32) AttributeStreamBase + .createIndexStream(0); m_paths.add(0); m_paths.add(m_position.size() / 2); } checkPathPointCountsForMultiPath_(false); - multi_path_impl.setAttributeStreamRef(Semantics.POSITION, m_position); + multi_path_impl.setAttributeStreamRef(Semantics.POSITION, + m_position); if (m_b_has_zs) { - assert(m_zs != null); + assert (m_zs != null); multi_path_impl.setAttributeStreamRef(Semantics.Z, m_zs); } if (m_b_has_ms) { - assert(m_ms != null); + assert (m_ms != null); multi_path_impl.setAttributeStreamRef(Semantics.M, m_ms); } - m_path_flags = (AttributeStreamOfInt8) AttributeStreamBase.createByteStream(m_paths.size(), (byte) 0); + m_path_flags = (AttributeStreamOfInt8) AttributeStreamBase + .createByteStream(m_paths.size(), (byte) 0); multi_path_impl.setPathStreamRef(m_paths); multi_path_impl.setPathFlagsStreamRef(m_path_flags); - multi_path_impl.notifyModified(MultiVertexGeometryImpl.DirtyFlags.DirtyAll); + multi_path_impl + .notifyModified(MultiVertexGeometryImpl.DirtyFlags.DirtyAll); return polyline; } private Geometry createMultiPointFromStreams_() { - assert(m_position != null); - assert(m_paths == null); - assert(m_path_flags == null); + assert (m_position != null); + assert (m_paths == null); + assert (m_path_flags == null); MultiPoint multi_point = new MultiPoint(); - MultiPointImpl multi_point_impl = (MultiPointImpl) multi_point._getImpl(); - multi_point_impl.setAttributeStreamRef(Semantics.POSITION, m_position); + MultiPointImpl multi_point_impl = (MultiPointImpl) multi_point + ._getImpl(); + multi_point_impl.setAttributeStreamRef(Semantics.POSITION, + m_position); if (m_b_has_zs) { - assert(m_zs != null); + assert (m_zs != null); multi_point_impl.setAttributeStreamRef(Semantics.Z, m_zs); } if (m_b_has_ms) { - assert(m_ms != null); + assert (m_ms != null); multi_point_impl.setAttributeStreamRef(Semantics.M, m_ms); } + multi_point_impl.resize(m_position.size() / 2); multi_point_impl.notifyModified(MultiVertexGeometryImpl.DirtyFlags.DirtyAll); - return multi_point; } @@ -794,14 +933,15 @@ private void checkPathPointCountsForMultiPath_(boolean b_is_polygon) { int path_start = m_paths.read(path); int path_end = m_paths.read(path + 1); int path_size = path_end - path_start; - assert(path_size != 0); // we should not have added empty parts - // on import + assert (path_size != 0); // we should not have added empty parts + // on import if (path_size == 1) { - insertIntoAdjustedStreams_(adjusted_position, adjusted_zs, adjusted_ms, adjusted_start, path_start, + insertIntoAdjustedStreams_(adjusted_position, adjusted_zs, + adjusted_ms, adjusted_start, path_start, path_size); + insertIntoAdjustedStreams_(adjusted_position, adjusted_zs, + adjusted_ms, adjusted_start + 1, path_start, path_size); - insertIntoAdjustedStreams_(adjusted_position, adjusted_zs, adjusted_ms, adjusted_start + 1, - path_start, path_size); adjusted_start += 2; } else if (path_size >= 3 && b_is_polygon) { m_position.read(path_start * 2, pt1); @@ -817,19 +957,22 @@ private void checkPathPointCountsForMultiPath_(boolean b_is_polygon) { m2 = m_ms.readAsDbl(path_end - 1); } - if (pt1.equals(pt2) && (NumberUtils.isNaN(z1) && NumberUtils.isNaN(z2) || z1 == z2) + if (pt1.equals(pt2) + && (NumberUtils.isNaN(z1) && NumberUtils.isNaN(z2) || z1 == z2) && (NumberUtils.isNaN(m1) && NumberUtils.isNaN(m2) || m1 == m2)) { - insertIntoAdjustedStreams_(adjusted_position, adjusted_zs, adjusted_ms, adjusted_start, + insertIntoAdjustedStreams_(adjusted_position, + adjusted_zs, adjusted_ms, adjusted_start, path_start, path_size - 1); adjusted_start += path_size - 1; } else { - insertIntoAdjustedStreams_(adjusted_position, adjusted_zs, adjusted_ms, adjusted_start, + insertIntoAdjustedStreams_(adjusted_position, + adjusted_zs, adjusted_ms, adjusted_start, path_start, path_size); adjusted_start += path_size; } } else { - insertIntoAdjustedStreams_(adjusted_position, adjusted_zs, adjusted_ms, adjusted_start, path_start, - path_size); + insertIntoAdjustedStreams_(adjusted_position, adjusted_zs, + adjusted_ms, adjusted_start, path_start, path_size); adjusted_start += path_size; } adjusted_paths.write(path + 1, adjusted_start); @@ -841,30 +984,35 @@ private void checkPathPointCountsForMultiPath_(boolean b_is_polygon) { m_ms = adjusted_ms; } - private void insertIntoAdjustedStreams_(AttributeStreamOfDbl adjusted_position, - AttributeStreamOfDbl adjusted_zs, AttributeStreamOfDbl adjusted_ms, int adjusted_start, int path_start, - int count) { - adjusted_position.insertRange(adjusted_start * 2, m_position, path_start * 2, count * 2, true, 2, - adjusted_start * 2); + private void insertIntoAdjustedStreams_( + AttributeStreamOfDbl adjusted_position, + AttributeStreamOfDbl adjusted_zs, + AttributeStreamOfDbl adjusted_ms, int adjusted_start, + int path_start, int count) { + adjusted_position.insertRange(adjusted_start * 2, m_position, + path_start * 2, count * 2, true, 2, adjusted_start * 2); if (m_b_has_zs) { - adjusted_zs.insertRange(adjusted_start, m_zs, path_start, count, true, 1, adjusted_start); + adjusted_zs.insertRange(adjusted_start, m_zs, path_start, + count, true, 1, adjusted_start); } if (m_b_has_ms) { - adjusted_ms.insertRange(adjusted_start, m_ms, path_start, count, true, 1, adjusted_start); + adjusted_ms.insertRange(adjusted_start, m_ms, path_start, + count, true, 1, adjusted_start); } } - static SpatialReference importSpatialReferenceFromCrs(JsonReader json_iterator, - ProgressTracker progress_tracker) throws JSONException, JsonParseException, IOException { + static SpatialReference importSpatialReferenceFromCrs( + JsonReader json_iterator, ProgressTracker progress_tracker) + throws JsonGeometryException { // According to the spec, a null crs corresponds to no spatial // reference - if (json_iterator.currentToken() == JsonToken.VALUE_NULL) { + if (json_iterator.currentToken() == JsonReader.Token.VALUE_NULL) { return null; } - if (json_iterator.currentToken() == JsonToken.VALUE_STRING) {// see + if (json_iterator.currentToken() == JsonReader.Token.VALUE_STRING) {// see // http://wiki.geojson.org/RFC-001 // (this // is @@ -879,7 +1027,8 @@ static SpatialReference importSpatialReferenceFromCrs(JsonReader json_iterator, // format) String crs_short_form = json_iterator.currentString(); - int wkid = GeoJsonCrsTables.getWkidFromCrsShortForm(crs_short_form); + int wkid = GeoJsonCrsTables + .getWkidFromCrsShortForm(crs_short_form); if (wkid == -1) { throw new GeometryException("not implemented"); @@ -895,8 +1044,8 @@ static SpatialReference importSpatialReferenceFromCrs(JsonReader json_iterator, return spatial_reference; } - if (json_iterator.currentToken() != JsonToken.START_OBJECT) { - throw new IllegalArgumentException("invalid argument"); + if (json_iterator.currentToken() != JsonReader.Token.START_OBJECT) { + throw new JsonGeometryException("parsing error"); } // This is to support all cases of crs identifiers I've seen. Some @@ -910,86 +1059,92 @@ static SpatialReference importSpatialReferenceFromCrs(JsonReader json_iterator, boolean b_found_properties_url = false; boolean b_found_properties_code = false; boolean b_found_esriwkt = false; - String crs_field = null, properties_field = null, type = null, crs_identifier_name = null, - crs_identifier_urn = null, crs_identifier_href = null, crs_identifier_url = null, esriwkt = null; + String crs_field = null; + String properties_field = null; + String crs_identifier_name = null; + String crs_identifier_urn = null; + String crs_identifier_href = null; + String crs_identifier_url = null; + String esriwkt = null; int crs_identifier_code = -1; - JsonToken current_token; + JsonReader.Token current_token; - while (json_iterator.nextToken() != JsonToken.END_OBJECT) { + while (json_iterator.nextToken() != JsonReader.Token.END_OBJECT) { crs_field = json_iterator.currentString(); if (crs_field.equals("type")) { if (b_found_type) { - throw new IllegalArgumentException("invalid argument"); + throw new JsonGeometryException("parsing error"); } b_found_type = true; current_token = json_iterator.nextToken(); - if (current_token != JsonToken.VALUE_STRING) { - throw new IllegalArgumentException("invalid argument"); + if (current_token != JsonReader.Token.VALUE_STRING) { + throw new JsonGeometryException("parsing error"); } - type = json_iterator.currentString(); + //type = json_iterator.currentString(); } else if (crs_field.equals("properties")) { if (b_found_properties) { - throw new IllegalArgumentException("invalid argument"); + throw new JsonGeometryException("parsing error"); } b_found_properties = true; current_token = json_iterator.nextToken(); - if (current_token != JsonToken.START_OBJECT) { - throw new IllegalArgumentException("invalid argument"); + if (current_token != JsonReader.Token.START_OBJECT) { + throw new JsonGeometryException("parsing error"); } - while (json_iterator.nextToken() != JsonToken.END_OBJECT) { + while (json_iterator.nextToken() != JsonReader.Token.END_OBJECT) { properties_field = json_iterator.currentString(); if (properties_field.equals("name")) { if (b_found_properties_name) { - throw new IllegalArgumentException("invalid argument"); + throw new JsonGeometryException("parsing error"); } b_found_properties_name = true; crs_identifier_name = getCrsIdentifier_(json_iterator); } else if (properties_field.equals("href")) { if (b_found_properties_href) { - throw new IllegalArgumentException("invalid argument"); + throw new JsonGeometryException("parsing error"); } b_found_properties_href = true; crs_identifier_href = getCrsIdentifier_(json_iterator); } else if (properties_field.equals("urn")) { if (b_found_properties_urn) { - throw new IllegalArgumentException("invalid argument"); + throw new JsonGeometryException("parsing error"); } b_found_properties_urn = true; crs_identifier_urn = getCrsIdentifier_(json_iterator); } else if (properties_field.equals("url")) { if (b_found_properties_url) { - throw new IllegalArgumentException("invalid argument"); + throw new JsonGeometryException("parsing error"); } b_found_properties_url = true; crs_identifier_url = getCrsIdentifier_(json_iterator); } else if (properties_field.equals("code")) { if (b_found_properties_code) { - throw new IllegalArgumentException("invalid argument"); + throw new JsonGeometryException("parsing error"); } b_found_properties_code = true; current_token = json_iterator.nextToken(); - if (current_token != JsonToken.VALUE_NUMBER_INT) { - throw new IllegalArgumentException("invalid argument"); + if (current_token != JsonReader.Token.VALUE_NUMBER_INT) { + throw new JsonGeometryException("parsing error"); } - crs_identifier_code = json_iterator.currentIntValue(); + crs_identifier_code = json_iterator + .currentIntValue(); } else { json_iterator.nextToken(); json_iterator.skipChildren(); @@ -997,15 +1152,15 @@ static SpatialReference importSpatialReferenceFromCrs(JsonReader json_iterator, } } else if (crs_field.equals("esriwkt")) { if (b_found_esriwkt) { - throw new IllegalArgumentException("invalid argument"); + throw new JsonGeometryException("parsing error"); } b_found_esriwkt = true; current_token = json_iterator.nextToken(); - if (current_token != JsonToken.VALUE_STRING) { - throw new IllegalArgumentException("invalid argument"); + if (current_token != JsonReader.Token.VALUE_STRING) { + throw new JsonGeometryException("parsing error"); } esriwkt = json_iterator.currentString(); @@ -1016,7 +1171,7 @@ static SpatialReference importSpatialReferenceFromCrs(JsonReader json_iterator, } if ((!b_found_type || !b_found_properties) && !b_found_esriwkt) { - throw new IllegalArgumentException("invalid argument"); + throw new JsonGeometryException("parsing error"); } int wkid = -1; @@ -1032,9 +1187,10 @@ static SpatialReference importSpatialReferenceFromCrs(JsonReader json_iterator, // (somewhat // common) } else if (b_found_properties_urn) { - wkid = GeoJsonCrsTables.getWkidFromCrsOgcUrn(crs_identifier_urn); // see - // http://wiki.geojson.org/GeoJSON_draft_version_5 - // (rare) + wkid = GeoJsonCrsTables + .getWkidFromCrsOgcUrn(crs_identifier_urn); // see + // http://wiki.geojson.org/GeoJSON_draft_version_5 + // (rare) } else if (b_found_properties_url) { wkid = GeoJsonCrsTables.getWkidFromCrsHref(crs_identifier_url); // see // http://wiki.geojson.org/GeoJSON_draft_version_5 @@ -1044,11 +1200,11 @@ static SpatialReference importSpatialReferenceFromCrs(JsonReader json_iterator, // http://wiki.geojson.org/GeoJSON_draft_version_5 // (rare) } else if (!b_found_esriwkt) { - throw new GeometryException("not implemented"); + throw new JsonGeometryException("parsing error"); } if (wkid < 0 && !b_found_esriwkt && !b_found_properties_name) { - throw new GeometryException("not implemented"); + throw new JsonGeometryException("parsing error"); } SpatialReference spatial_reference = null; @@ -1072,8 +1228,10 @@ static SpatialReference importSpatialReferenceFromCrs(JsonReader json_iterator, // the properties // name is like // "ESRI:" - String potential_wkt = GeoJsonCrsTables.getWktFromCrsName(crs_identifier_name); - spatial_reference = SpatialReference.create(potential_wkt); + String potential_wkt = GeoJsonCrsTables + .getWktFromCrsName(crs_identifier_name); + spatial_reference = SpatialReference + .create(potential_wkt); } } catch (Exception e) { } @@ -1083,16 +1241,17 @@ static SpatialReference importSpatialReferenceFromCrs(JsonReader json_iterator, } // see http://geojsonwg.github.io/draft-geojson/draft.html - static SpatialReference importSpatialReferenceFromCrsUrn_(JsonReader json_iterator, - ProgressTracker progress_tracker) throws JSONException, JsonParseException, IOException { + static SpatialReference importSpatialReferenceFromCrsUrn_( + JsonReader json_iterator, ProgressTracker progress_tracker) + throws JsonGeometryException { // According to the spec, a null crs corresponds to no spatial // reference - if (json_iterator.currentToken() == JsonToken.VALUE_NULL) { + if (json_iterator.currentToken() == JsonReader.Token.VALUE_NULL) { return null; } - if (json_iterator.currentToken() != JsonToken.VALUE_STRING) { - throw new IllegalArgumentException("invalid argument"); + if (json_iterator.currentToken() != JsonReader.Token.VALUE_STRING) { + throw new JsonGeometryException("parsing error"); } String crs_identifier_urn = json_iterator.currentString(); @@ -1121,11 +1280,11 @@ static SpatialReference importSpatialReferenceFromCrsUrn_(JsonReader json_iterat } private static String getCrsIdentifier_(JsonReader json_iterator) - throws JSONException, JsonParseException, IOException { - JsonToken current_token = json_iterator.nextToken(); + throws JsonGeometryException { + JsonReader.Token current_token = json_iterator.nextToken(); - if (current_token != JsonToken.VALUE_STRING) { - throw new IllegalArgumentException("invalid argument"); + if (current_token != JsonReader.Token.VALUE_STRING) { + throw new JsonGeometryException("parsing error"); } return json_iterator.currentString(); @@ -1133,466 +1292,28 @@ private static String getCrsIdentifier_(JsonReader json_iterator) } - static JSONArray getJSONArray(JSONObject obj, String name) throws JSONException { - if (obj.get(name) == JSONObject.NULL) - return new JSONArray(); - else - return obj.getJSONArray(name); - } - @Override - public MapOGCStructure executeOGC(int import_flags, String geoJsonString, ProgressTracker progress_tracker) - throws JSONException { - MapOGCStructure mapOGCStructure = null; - try { - JSONObject geoJsonObject = new JSONObject(geoJsonString); - ArrayList structureStack = new ArrayList(0); - ArrayList objectStack = new ArrayList(0); - AttributeStreamOfInt32 indices = new AttributeStreamOfInt32(0); - AttributeStreamOfInt32 numGeometries = new AttributeStreamOfInt32(0); - OGCStructure root = new OGCStructure(); - root.m_structures = new ArrayList(0); - structureStack.add(root); // add dummy root - objectStack.add(geoJsonObject); - indices.add(0); - numGeometries.add(1); - while (!objectStack.isEmpty()) { - if (indices.getLast() == numGeometries.getLast()) { - structureStack.remove(structureStack.size() - 1); - indices.removeLast(); - numGeometries.removeLast(); - continue; - } - OGCStructure lastStructure = structureStack.get(structureStack.size() - 1); - JSONObject lastObject = objectStack.get(objectStack.size() - 1); - objectStack.remove(objectStack.size() - 1); - indices.write(indices.size() - 1, indices.getLast() + 1); - String typeString = lastObject.getString("type"); - if (typeString.equalsIgnoreCase("GeometryCollection")) { - OGCStructure next = new OGCStructure(); - next.m_type = 7; - next.m_structures = new ArrayList(0); - lastStructure.m_structures.add(next); - structureStack.add(next); - JSONArray geometries = getJSONArray(lastObject, "geometries"); - indices.add(0); - numGeometries.add(geometries.length()); - for (int i = geometries.length() - 1; i >= 0; i--) - objectStack.add(geometries.getJSONObject(i)); - } else { - int ogcType; - if (typeString.equalsIgnoreCase("Point")) - ogcType = 1; - else if (typeString.equalsIgnoreCase("LineString")) - ogcType = 2; - else if (typeString.equalsIgnoreCase("Polygon")) - ogcType = 3; - else if (typeString.equalsIgnoreCase("MultiPoint")) - ogcType = 4; - else if (typeString.equalsIgnoreCase("MultiLineString")) - ogcType = 5; - else if (typeString.equalsIgnoreCase("MultiPolygon")) - ogcType = 6; - else - throw new UnsupportedOperationException(); - - MapGeometry map_geometry = execute(import_flags | GeoJsonImportFlags.geoJsonImportSkipCRS, - Geometry.Type.Unknown, lastObject, null); - OGCStructure leaf = new OGCStructure(); - leaf.m_type = ogcType; - leaf.m_geometry = map_geometry.getGeometry(); - lastStructure.m_structures.add(leaf); - } - } - mapOGCStructure = new MapOGCStructure(); - mapOGCStructure.m_ogcStructure = root; - - if ((import_flags & GeoJsonImportFlags.geoJsonImportSkipCRS) == 0) - mapOGCStructure.m_spatialReference = importSpatialReferenceFromGeoJson_(import_flags, geoJsonObject); - } catch (JSONException jsonException) { - throw jsonException; - } catch (JsonParseException jsonParseException) { - throw new JSONException(jsonParseException.getMessage()); - } catch (IOException ioException) { - throw new JSONException(ioException.getMessage()); - } - - return mapOGCStructure; - } - - private static SpatialReference importSpatialReferenceFromGeoJson_(int importFlags, JSONObject crsJSONObject) - throws JSONException, JsonParseException, IOException { - - SpatialReference spatial_reference = null; - boolean b_crs_found = false, b_crsURN_found = false; - - Object opt = crsJSONObject.opt("crs"); - - if (opt != null) { - b_crs_found = true; - JSONObject crs_object = new JSONObject(); - crs_object.put("crs", opt); - JsonValueReader json_iterator = new JsonValueReader(crs_object); - json_iterator.nextToken(); - json_iterator.nextToken(); - return OperatorImportFromGeoJsonHelper.importSpatialReferenceFromCrs(json_iterator, null); - } - - opt = crsJSONObject.opt("crsURN"); - - if (opt != null) { - b_crsURN_found = true; - JSONObject crs_object = new JSONObject(); - crs_object.put("crsURN", opt); - JsonValueReader json_iterator = new JsonValueReader(crs_object); - json_iterator.nextToken(); - json_iterator.nextToken(); - return OperatorImportFromGeoJsonHelper.importSpatialReferenceFromCrs(json_iterator, null); - } - - if ((importFlags & GeoJsonImportFlags.geoJsonImportNoWGS84Default) == 0) { - spatial_reference = SpatialReference.create(4326); - } - - return spatial_reference; - } - - /* - private static Geometry importGeometryFromGeoJson_(int importFlags, Geometry.Type type, - JSONObject geometryJSONObject) throws JSONException { - String typeString = geometryJSONObject.getString("type"); - JSONArray coordinateArray = getJSONArray(geometryJSONObject, "coordinates"); - if (typeString.equalsIgnoreCase("MultiPolygon")) { - if (type != Geometry.Type.Polygon && type != Geometry.Type.Unknown) - throw new IllegalArgumentException("invalid shapetype"); - return polygonTaggedText_(true, importFlags, coordinateArray); - } else if (typeString.equalsIgnoreCase("MultiLineString")) { - if (type != Geometry.Type.Polyline && type != Geometry.Type.Unknown) - throw new IllegalArgumentException("invalid shapetype"); - return lineStringTaggedText_(true, importFlags, coordinateArray); - } else if (typeString.equalsIgnoreCase("MultiPoint")) { - if (type != Geometry.Type.MultiPoint && type != Geometry.Type.Unknown) - throw new IllegalArgumentException("invalid shapetype"); - return multiPointTaggedText_(importFlags, coordinateArray); - } else if (typeString.equalsIgnoreCase("Polygon")) { - if (type != Geometry.Type.Polygon && type != Geometry.Type.Unknown) - throw new IllegalArgumentException("invalid shapetype"); - return polygonTaggedText_(false, importFlags, coordinateArray); - } else if (typeString.equalsIgnoreCase("LineString")) { - if (type != Geometry.Type.Polyline && type != Geometry.Type.Unknown) - throw new IllegalArgumentException("invalid shapetype"); - return lineStringTaggedText_(false, importFlags, coordinateArray); - } else if (typeString.equalsIgnoreCase("Point")) { - if (type != Geometry.Type.Point && type != Geometry.Type.Unknown) - throw new IllegalArgumentException("invalid shapetype"); - return pointTaggedText_(importFlags, coordinateArray); - } else { - return null; - } - } - - private static Geometry polygonTaggedText_(boolean bMultiPolygon, int importFlags, JSONArray coordinateArray) - throws JSONException { - MultiPath multiPath; - MultiPathImpl multiPathImpl; - AttributeStreamOfDbl zs = null; - AttributeStreamOfDbl ms = null; - AttributeStreamOfDbl position; - AttributeStreamOfInt32 paths; - AttributeStreamOfInt8 path_flags; - position = (AttributeStreamOfDbl) AttributeStreamBase.createDoubleStream(0); - paths = (AttributeStreamOfInt32) AttributeStreamBase.createIndexStream(1, 0); - path_flags = (AttributeStreamOfInt8) AttributeStreamBase.createByteStream(1, (byte) 0); - multiPath = new Polygon(); - multiPathImpl = (MultiPathImpl) multiPath._getImpl(); - int pointCount; - if (bMultiPolygon) { - pointCount = multiPolygonText_(zs, ms, position, paths, path_flags, coordinateArray); - } else { - pointCount = polygonText_(zs, ms, position, paths, path_flags, 0, coordinateArray); - } - if (pointCount != 0) { - assert(2 * pointCount == position.size()); - multiPathImpl.setAttributeStreamRef(VertexDescription.Semantics.POSITION, position); - multiPathImpl.setPathStreamRef(paths); - multiPathImpl.setPathFlagsStreamRef(path_flags); - if (zs != null) { - multiPathImpl.setAttributeStreamRef(VertexDescription.Semantics.Z, zs); - } - if (ms != null) { - multiPathImpl.setAttributeStreamRef(VertexDescription.Semantics.M, ms); - } - multiPathImpl.notifyModified(MultiPathImpl.DirtyFlags.DirtyAll); - AttributeStreamOfInt8 path_flags_clone = new AttributeStreamOfInt8(path_flags); - for (int i = 0; i < path_flags_clone.size() - 1; i++) { - if (((int) path_flags_clone.read(i) & (int) PathFlags.enumOGCStartPolygon) != 0) {// Should - // be - // clockwise - if (!InternalUtils.isClockwiseRing(multiPathImpl, i)) - multiPathImpl.reversePath(i); // make clockwise - } else {// Should be counter-clockwise - if (InternalUtils.isClockwiseRing(multiPathImpl, i)) - multiPathImpl.reversePath(i); // make counter-clockwise - } - } - multiPathImpl.setPathFlagsStreamRef(path_flags_clone); - } - if ((importFlags & (int) GeoJsonImportFlags.geoJsonImportNonTrusted) == 0) { - multiPathImpl.setIsSimple(MultiPathImpl.GeometryXSimple.Weak, 0.0, false); - } - multiPathImpl.setDirtyOGCFlags(false); - return multiPath; - } - - private static Geometry lineStringTaggedText_(boolean bMultiLineString, int importFlags, JSONArray coordinateArray) - throws JSONException { - MultiPath multiPath; - MultiPathImpl multiPathImpl; - AttributeStreamOfDbl zs = null; - AttributeStreamOfDbl ms = null; - AttributeStreamOfDbl position; - AttributeStreamOfInt32 paths; - AttributeStreamOfInt8 path_flags; - position = (AttributeStreamOfDbl) AttributeStreamBase.createDoubleStream(0); - paths = (AttributeStreamOfInt32) AttributeStreamBase.createIndexStream(1, 0); - path_flags = (AttributeStreamOfInt8) AttributeStreamBase.createByteStream(1, (byte) 0); - multiPath = new Polyline(); - multiPathImpl = (MultiPathImpl) multiPath._getImpl(); - int pointCount; - if (bMultiLineString) { - pointCount = multiLineStringText_(zs, ms, position, paths, path_flags, coordinateArray); - } else { - pointCount = lineStringText_(false, zs, ms, position, paths, path_flags, coordinateArray); - } - if (pointCount != 0) { - assert(2 * pointCount == position.size()); - multiPathImpl.setAttributeStreamRef(VertexDescription.Semantics.POSITION, position); - multiPathImpl.setPathStreamRef(paths); - multiPathImpl.setPathFlagsStreamRef(path_flags); - if (zs != null) { - multiPathImpl.setAttributeStreamRef(VertexDescription.Semantics.Z, zs); - } - if (ms != null) { - multiPathImpl.setAttributeStreamRef(VertexDescription.Semantics.M, ms); - } - multiPathImpl.notifyModified(MultiPathImpl.DirtyFlags.DirtyAll); - } - return multiPath; - } - - private static Geometry multiPointTaggedText_(int importFlags, JSONArray coordinateArray) throws JSONException { - MultiPoint multiPoint; - MultiPointImpl multiPointImpl; - AttributeStreamOfDbl zs = null; - AttributeStreamOfDbl ms = null; - AttributeStreamOfDbl position; - position = (AttributeStreamOfDbl) AttributeStreamBase.createDoubleStream(0); - multiPoint = new MultiPoint(); - multiPointImpl = (MultiPointImpl) multiPoint._getImpl(); - int pointCount = multiPointText_(zs, ms, position, coordinateArray); - if (pointCount != 0) { - assert(2 * pointCount == position.size()); - multiPointImpl.resize(pointCount); - multiPointImpl.setAttributeStreamRef(VertexDescription.Semantics.POSITION, position); - multiPointImpl.notifyModified(MultiPointImpl.DirtyFlags.DirtyAll); - } - return multiPoint; - } - - private static Geometry pointTaggedText_(int importFlags, JSONArray coordinateArray) throws JSONException { - Point point = new Point(); - int length = coordinateArray.length(); - if (length == 0) { - point.setEmpty(); - return point; - } - point.setXY(getDouble_(coordinateArray, 0), getDouble_(coordinateArray, 1)); - return point; - } - - private static int multiPolygonText_(AttributeStreamOfDbl zs, AttributeStreamOfDbl ms, - AttributeStreamOfDbl position, AttributeStreamOfInt32 paths, AttributeStreamOfInt8 path_flags, - JSONArray coordinateArray) throws JSONException { - // At start of MultiPolygonText - int totalPointCount = 0; - int length = coordinateArray.length(); - if (length == 0) - return totalPointCount; - for (int current = 0; current < length; current++) { - JSONArray subArray = coordinateArray.optJSONArray(current); - if (subArray == null) {// Entry should be a JSONArray representing a - // polygon, but it is not a JSONArray. - throw new IllegalArgumentException(""); - } - - // At start of PolygonText - totalPointCount = polygonText_(zs, ms, position, paths, path_flags, totalPointCount, subArray); - } - return totalPointCount; - } - - private static int multiLineStringText_(AttributeStreamOfDbl zs, AttributeStreamOfDbl ms, - AttributeStreamOfDbl position, AttributeStreamOfInt32 paths, AttributeStreamOfInt8 path_flags, - JSONArray coordinateArray) throws JSONException { - // At start of MultiLineStringText - int totalPointCount = 0; - int length = coordinateArray.length(); - if (length == 0) - return totalPointCount; - for (int current = 0; current < length; current++) { - JSONArray subArray = coordinateArray.optJSONArray(current); - if (subArray == null) {// Entry should be a JSONArray representing a - // line string, but it is not a JSONArray. - throw new IllegalArgumentException(""); - } - - // At start of LineStringText - totalPointCount += lineStringText_(false, zs, ms, position, paths, path_flags, subArray); - } - return totalPointCount; - } - - private static int multiPointText_(AttributeStreamOfDbl zs, AttributeStreamOfDbl ms, AttributeStreamOfDbl position, - JSONArray coordinateArray) throws JSONException { - // At start of MultiPointText - int pointCount = 0; - for (int current = 0; current < coordinateArray.length(); current++) { - JSONArray subArray = coordinateArray.optJSONArray(current); - if (subArray == null) {// Entry should be a JSONArray representing a - // point, but it is not a JSONArray. - throw new IllegalArgumentException(""); - } - pointCount += pointText_(zs, ms, position, subArray); - } - return pointCount; - } - - private static int polygonText_(AttributeStreamOfDbl zs, AttributeStreamOfDbl ms, AttributeStreamOfDbl position, - AttributeStreamOfInt32 paths, AttributeStreamOfInt8 path_flags, int totalPointCount, - JSONArray coordinateArray) throws JSONException { - // At start of PolygonText - int length = coordinateArray.length(); - if (length == 0) { - return totalPointCount; - } - boolean bFirstLineString = true; - for (int current = 0; current < length; current++) { - JSONArray subArray = coordinateArray.optJSONArray(current); - if (subArray == null) {// Entry should be a JSONArray representing a - // line string, but it is not a JSONArray. - throw new IllegalArgumentException(""); - } - // At start of LineStringText - int pointCount = lineStringText_(true, zs, ms, position, paths, path_flags, subArray); - if (pointCount != 0) { - if (bFirstLineString) { - bFirstLineString = false; - path_flags.setBits(path_flags.size() - 2, (byte) PathFlags.enumOGCStartPolygon); - } - path_flags.setBits(path_flags.size() - 2, (byte) PathFlags.enumClosed); - totalPointCount += pointCount; - } - } - return totalPointCount; + public MapOGCStructure executeOGC(int import_flags, String geoJsonString, + ProgressTracker progress_tracker) throws JsonGeometryException { + return executeOGC(import_flags, JsonParserReader.createFromString(geoJsonString), + progress_tracker); } - private static int lineStringText_(boolean bRing, AttributeStreamOfDbl zs, AttributeStreamOfDbl ms, - AttributeStreamOfDbl position, AttributeStreamOfInt32 paths, AttributeStreamOfInt8 path_flags, - JSONArray coordinateArray) throws JSONException { - // At start of LineStringText - int pointCount = 0; - int length = coordinateArray.length(); - if (length == 0) - return pointCount; - boolean bStartPath = true; - double startX = NumberUtils.TheNaN; - double startY = NumberUtils.TheNaN; - double startZ = NumberUtils.TheNaN; - double startM = NumberUtils.TheNaN; - for (int current = 0; current < length; current++) { - JSONArray subArray = coordinateArray.optJSONArray(current); - if (subArray == null) {// Entry should be a JSONArray representing a - // single point, but it is not a JSONArray. - throw new IllegalArgumentException(""); - } - // At start of x - double x = getDouble_(subArray, 0); - double y = getDouble_(subArray, 1); - double z = NumberUtils.TheNaN; - double m = NumberUtils.TheNaN; - boolean bAddPoint = true; - if (bRing && pointCount >= 2 && current == length - 1) {// If the - // last - // point in - // the ring - // is not - // equal to - // the start - // point, - // then - // let's add - // it. - if ((startX == x || (NumberUtils.isNaN(startX) && NumberUtils.isNaN(x))) - && (startY == y || (NumberUtils.isNaN(startY) && NumberUtils.isNaN(y)))) { - bAddPoint = false; - } - } - if (bAddPoint) { - if (bStartPath) { - bStartPath = false; - startX = x; - startY = y; - startZ = z; - startM = m; - } - pointCount++; - addToStreams_(zs, ms, position, x, y, z, m); - } - } - if (pointCount == 1) { - pointCount++; - addToStreams_(zs, ms, position, startX, startY, startZ, startM); - } - paths.add(position.size() / 2); - path_flags.add((byte) 0); - return pointCount; + public MapOGCStructure executeOGC(int import_flags, + JsonReader json_iterator, ProgressTracker progress_tracker) + throws JsonGeometryException { + MapOGCStructure mapOGCStructure = OperatorImportFromGeoJsonHelper.importFromGeoJson( + import_flags, Geometry.Type.Unknown, json_iterator, + progress_tracker, false, 0); + + //This is to restore legacy behavior when we always return a geometry collection of one element. + MapOGCStructure res = new MapOGCStructure(); + res.m_ogcStructure = new OGCStructure(); + res.m_ogcStructure.m_type = 0; + res.m_ogcStructure.m_structures = new ArrayList(); + res.m_ogcStructure.m_structures.add(mapOGCStructure.m_ogcStructure); + res.m_spatialReference = mapOGCStructure.m_spatialReference; + return res; } - private static int pointText_(AttributeStreamOfDbl zs, AttributeStreamOfDbl ms, AttributeStreamOfDbl position, - JSONArray coordinateArray) throws JSONException { - // At start of PointText - int length = coordinateArray.length(); - if (length == 0) - return 0; - // At start of x - double x = getDouble_(coordinateArray, 0); - double y = getDouble_(coordinateArray, 1); - double z = NumberUtils.TheNaN; - double m = NumberUtils.TheNaN; - addToStreams_(zs, ms, position, x, y, z, m); - return 1; - } - - private static void addToStreams_(AttributeStreamOfDbl zs, AttributeStreamOfDbl ms, AttributeStreamOfDbl position, - double x, double y, double z, double m) { - position.add(x); - position.add(y); - if (zs != null) - zs.add(z); - if (ms != null) - ms.add(m); - } - - private static double getDouble_(JSONArray coordinateArray, int index) throws JSONException { - if (index < 0 || index >= coordinateArray.length()) { - throw new IllegalArgumentException(""); - } - if (coordinateArray.isNull(index)) { - return NumberUtils.TheNaN; - } - if (coordinateArray.optDouble(index, NumberUtils.TheNaN) != NumberUtils.TheNaN) { - return coordinateArray.getDouble(index); - } - throw new IllegalArgumentException(""); - }*/ } diff --git a/src/main/java/com/esri/core/geometry/OperatorImportFromJson.java b/src/main/java/com/esri/core/geometry/OperatorImportFromJson.java index 6a88d05c..93f30da5 100644 --- a/src/main/java/com/esri/core/geometry/OperatorImportFromJson.java +++ b/src/main/java/com/esri/core/geometry/OperatorImportFromJson.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2015 Esri + Copyright 1995-2017 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -24,15 +24,6 @@ package com.esri.core.geometry; -import java.io.IOException; - -import org.codehaus.jackson.JsonParseException; -import org.codehaus.jackson.JsonParser; -import org.json.JSONObject; -import org.json.JSONException; - -import com.esri.core.geometry.Operator.Type; - /** *Import from JSON format. */ @@ -48,29 +39,20 @@ public Type getType() { * @return Returns a MapGeometryCursor. */ abstract MapGeometryCursor execute(Geometry.Type type, - JsonParserCursor jsonParserCursor); + JsonReaderCursor jsonReaderCursor); /** *Performs the ImportFromJson operation on a single Json string *@return Returns a MapGeometry. */ public abstract MapGeometry execute(Geometry.Type type, - JsonParser jsonParser); + JsonReader jsonReader); /** *Performs the ImportFromJson operation on a single Json string *@return Returns a MapGeometry. */ - public abstract MapGeometry execute(Geometry.Type type, String string) - throws JsonParseException, IOException; - - /** - *Performs the ImportFromJson operation on a JSONObject - *@return Returns a MapGeometry. - */ - public abstract MapGeometry execute(Geometry.Type type, JSONObject jsonObject) - throws JSONException, IOException; - + public abstract MapGeometry execute(Geometry.Type type, String string); public static OperatorImportFromJson local() { return (OperatorImportFromJson) OperatorFactoryLocal.getInstance() diff --git a/src/main/java/com/esri/core/geometry/OperatorImportFromJsonCursor.java b/src/main/java/com/esri/core/geometry/OperatorImportFromJsonCursor.java index 8c267d7a..2971f441 100644 --- a/src/main/java/com/esri/core/geometry/OperatorImportFromJsonCursor.java +++ b/src/main/java/com/esri/core/geometry/OperatorImportFromJsonCursor.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2015 Esri + Copyright 1995-2017 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -26,18 +26,15 @@ import com.esri.core.geometry.MultiVertexGeometryImpl.DirtyFlags; import com.esri.core.geometry.VertexDescription.Semantics; -import org.codehaus.jackson.JsonParseException; -import org.codehaus.jackson.JsonParser; -import org.codehaus.jackson.JsonToken; class OperatorImportFromJsonCursor extends MapGeometryCursor { - JsonParserCursor m_inputJsonParsers; + JsonReaderCursor m_inputJsonParsers; int m_type; int m_index; - public OperatorImportFromJsonCursor(int type, JsonParserCursor jsonParsers) { + public OperatorImportFromJsonCursor(int type, JsonReaderCursor jsonParsers) { m_index = -1; if (jsonParsers == null) throw new IllegalArgumentException(); @@ -53,10 +50,10 @@ public int getGeometryID() { @Override public MapGeometry next() { - JsonParser jsonParser; + JsonReader jsonParser; if ((jsonParser = m_inputJsonParsers.next()) != null) { m_index = m_inputJsonParsers.getID(); - return importFromJsonParser(m_type, new JsonParserReader(jsonParser)); + return importFromJsonParser(m_type, jsonParser); } return null; } @@ -108,26 +105,26 @@ static MapGeometry importFromJsonParser(int gt, JsonReader parser) { Geometry geometry = null; SpatialReference spatial_reference = null; - while (parser.nextToken() != JsonToken.END_OBJECT) { + while (parser.nextToken() != JsonReader.Token.END_OBJECT) { String name = parser.currentString(); parser.nextToken(); if (!bFoundSpatial_reference && name.equals("spatialReference")) { bFoundSpatial_reference = true; - if (parser.currentToken() == JsonToken.START_OBJECT) { + if (parser.currentToken() == JsonReader.Token.START_OBJECT) { spatial_reference = SpatialReference.fromJson(parser); } else { - if (parser.currentToken() != JsonToken.VALUE_NULL) + if (parser.currentToken() != JsonReader.Token.VALUE_NULL) throw new GeometryException( "failed to parse spatial reference: object or null is expected"); } } else if (!bFoundHasZ && name.equals("hasZ")) { bFoundHasZ = true; - bHasZ = (parser.currentToken() == JsonToken.VALUE_TRUE); + bHasZ = (parser.currentToken() == JsonReader.Token.VALUE_TRUE); } else if (!bFoundHasM && name.equals("hasM")) { bFoundHasM = true; - bHasM = (parser.currentToken() == JsonToken.VALUE_TRUE); + bHasM = (parser.currentToken() == JsonReader.Token.VALUE_TRUE); } else if (!bFoundPolygon && name.equals("rings") && (gt == Geometry.GeometryType.Unknown || gt == Geometry.GeometryType.Polygon)) { @@ -308,15 +305,13 @@ public static MapGeometry fromJsonToMultiPoint(JsonReader parser) return importFromJsonParser(Geometry.GeometryType.MultiPoint, parser); } - private static void windup(JsonReader parser) throws Exception, - JsonParseException { + private static void windup(JsonReader parser) { parser.skipChildren(); } - private static double readDouble(JsonReader parser) throws Exception, - JsonParseException { - if (parser.currentToken() == JsonToken.VALUE_NULL - || parser.currentToken() == JsonToken.VALUE_STRING + private static double readDouble(JsonReader parser) { + if (parser.currentToken() == JsonReader.Token.VALUE_NULL + || parser.currentToken() == JsonReader.Token.VALUE_STRING && parser.currentString().equals("NaN")) return NumberUtils.NaN(); else @@ -325,7 +320,7 @@ private static double readDouble(JsonReader parser) throws Exception, private static Geometry importFromJsonMultiPoint(JsonReader parser, AttributeStreamOfDbl as, AttributeStreamOfDbl bs) throws Exception { - if (parser.currentToken() != JsonToken.START_ARRAY) + if (parser.currentToken() != JsonReader.Token.START_ARRAY) throw new GeometryException( "failed to parse multipoint: array of vertices is expected"); @@ -340,13 +335,13 @@ private static Geometry importFromJsonMultiPoint(JsonReader parser, // At start of rings int sz; double[] buf = new double[4]; - while (parser.nextToken() != JsonToken.END_ARRAY) { - if (parser.currentToken() != JsonToken.START_ARRAY) + while (parser.nextToken() != JsonReader.Token.END_ARRAY) { + if (parser.currentToken() != JsonReader.Token.START_ARRAY) throw new GeometryException( "failed to parse multipoint: array is expected, multipoint vertices consist of arrays of cooridinates"); sz = 0; - while (parser.nextToken() != JsonToken.END_ARRAY) { + while (parser.nextToken() != JsonReader.Token.END_ARRAY) { buf[sz++] = readDouble(parser); } @@ -408,7 +403,7 @@ else if (c < 16) private static Geometry importFromJsonMultiPath(boolean b_polygon, JsonReader parser, AttributeStreamOfDbl as, AttributeStreamOfDbl bs) throws Exception { - if (parser.currentToken() != JsonToken.START_ARRAY) + if (parser.currentToken() != JsonReader.Token.START_ARRAY) throw new GeometryException( "failed to parse multipath: array of array of vertices is expected"); @@ -436,8 +431,8 @@ private static Geometry importFromJsonMultiPath(boolean b_polygon, int requiredSize = b_polygon ? 3 : 2; // At start of rings - while (parser.nextToken() != JsonToken.END_ARRAY) { - if (parser.currentToken() != JsonToken.START_ARRAY) + while (parser.nextToken() != JsonReader.Token.END_ARRAY) { + if (parser.currentToken() != JsonReader.Token.START_ARRAY) throw new GeometryException( "failed to parse multipath: ring/path array is expected"); @@ -447,13 +442,13 @@ private static Geometry importFromJsonMultiPath(boolean b_polygon, int szstart = 0; parser.nextToken(); - while (parser.currentToken() != JsonToken.END_ARRAY) { - if (parser.currentToken() != JsonToken.START_ARRAY) + while (parser.currentToken() != JsonReader.Token.END_ARRAY) { + if (parser.currentToken() != JsonReader.Token.START_ARRAY) throw new GeometryException( "failed to parse multipath: array is expected, rings/paths vertices consist of arrays of cooridinates"); sz = 0; - while (parser.nextToken() != JsonToken.END_ARRAY) { + while (parser.nextToken() != JsonReader.Token.END_ARRAY) { buf[sz++] = readDouble(parser); } @@ -522,7 +517,7 @@ else if (c < 16) point_count++; pathPointCount++; } while (pathPointCount < requiredSize - && parser.currentToken() == JsonToken.END_ARRAY); + && parser.currentToken() == JsonReader.Token.END_ARRAY); } if (b_polygon && pathPointCount > requiredSize && sz == szstart diff --git a/src/main/java/com/esri/core/geometry/OperatorImportFromJsonLocal.java b/src/main/java/com/esri/core/geometry/OperatorImportFromJsonLocal.java index 4e41a491..55d94171 100644 --- a/src/main/java/com/esri/core/geometry/OperatorImportFromJsonLocal.java +++ b/src/main/java/com/esri/core/geometry/OperatorImportFromJsonLocal.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2015 Esri + Copyright 1995-2017 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -23,46 +23,20 @@ */ package com.esri.core.geometry; -import java.io.IOException; - -import org.codehaus.jackson.JsonFactory; -import org.codehaus.jackson.JsonParseException; -import org.codehaus.jackson.JsonParser; -import org.json.JSONObject; -import org.json.JSONException; - -import com.esri.core.geometry.ogc.OGCGeometry; - class OperatorImportFromJsonLocal extends OperatorImportFromJson { @Override public MapGeometryCursor execute(Geometry.Type type, - JsonParserCursor jsonParserCursor) { + JsonReaderCursor jsonParserCursor) { return new OperatorImportFromJsonCursor(type.value(), jsonParserCursor); } @Override - public MapGeometry execute(Geometry.Type type, JsonParser jsonParser) { - SimpleJsonParserCursor jsonParserCursor = new SimpleJsonParserCursor( - jsonParser); - OperatorImportFromJsonCursor cursor = new OperatorImportFromJsonCursor( - type.value(), jsonParserCursor); - return cursor.next(); + public MapGeometry execute(Geometry.Type type, JsonReader jsonParser) { + return OperatorImportFromJsonCursor.importFromJsonParser(type.value(), jsonParser); } @Override - public MapGeometry execute(Geometry.Type type, String string) - throws JsonParseException, IOException { - JsonFactory factory = new JsonFactory(); - JsonParser jsonParserPt = factory.createJsonParser(string); - jsonParserPt.nextToken(); - return execute(type, jsonParserPt); + public MapGeometry execute(Geometry.Type type, String string) { + return execute(type, JsonParserReader.createFromString(string)); } - @Override - public MapGeometry execute(Geometry.Type type, JSONObject jsonObject) - throws JSONException, IOException { - if (jsonObject == null) - return null; - - return OperatorImportFromJsonCursor.importFromJsonParser(type.value(), new JsonValueReader(jsonObject)); - } } diff --git a/src/main/java/com/esri/core/geometry/SimpleJsonParserCursor.java b/src/main/java/com/esri/core/geometry/SimpleJsonReaderCursor.java similarity index 78% rename from src/main/java/com/esri/core/geometry/SimpleJsonParserCursor.java rename to src/main/java/com/esri/core/geometry/SimpleJsonReaderCursor.java index 5f034a82..9a0793fc 100644 --- a/src/main/java/com/esri/core/geometry/SimpleJsonParserCursor.java +++ b/src/main/java/com/esri/core/geometry/SimpleJsonReaderCursor.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2015 Esri + Copyright 1995-2017 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -23,23 +23,21 @@ */ package com.esri.core.geometry; -import org.codehaus.jackson.JsonParser; +class SimpleJsonReaderCursor extends JsonReaderCursor { -class SimpleJsonParserCursor extends JsonParserCursor { - - JsonParser m_jsonParser; - JsonParser[] m_jsonParserArray; + JsonReader m_jsonParser; + JsonReader[] m_jsonParserArray; int m_index; int m_count; - public SimpleJsonParserCursor(JsonParser jsonString) { + public SimpleJsonReaderCursor(JsonReader jsonString) { m_jsonParser = jsonString; m_index = -1; m_count = 1; } - public SimpleJsonParserCursor(JsonParser[] jsonStringArray) { + public SimpleJsonReaderCursor(JsonReader[] jsonStringArray) { m_jsonParserArray = jsonStringArray; m_index = -1; m_count = jsonStringArray.length; @@ -51,7 +49,7 @@ public int getID() { } @Override - public JsonParser next() { + public JsonReader next() { if (m_index < m_count - 1) { m_index++; return m_jsonParser != null ? m_jsonParser diff --git a/src/main/java/com/esri/core/geometry/SpatialReference.java b/src/main/java/com/esri/core/geometry/SpatialReference.java index 39b1e90a..44fe3912 100644 --- a/src/main/java/com/esri/core/geometry/SpatialReference.java +++ b/src/main/java/com/esri/core/geometry/SpatialReference.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2015 Esri + Copyright 1995-2017 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -26,12 +26,11 @@ import java.io.ObjectStreamException; import java.io.Serializable; -import org.codehaus.jackson.JsonParser; -import org.codehaus.jackson.JsonToken; import com.esri.core.geometry.SpatialReference; import com.esri.core.geometry.SpatialReferenceSerializer; import com.esri.core.geometry.VertexDescription; +import com.fasterxml.jackson.core.JsonParser; /** * A class that represents the spatial reference for the geometry. @@ -91,7 +90,11 @@ public static SpatialReference fromJson(JsonParser parser) throws Exception { return fromJson(new JsonParserReader(parser)); } - static SpatialReference fromJson(JsonReader parser) throws Exception { + public static SpatialReference fromJson(String string) throws Exception { + return fromJson(JsonParserReader.createFromString(string)); + } + + public static SpatialReference fromJson(JsonReader parser) throws Exception { // Note this class is processed specially: it is expected that the // iterator points to the first element of the SR object. boolean bFoundWkid = false; @@ -105,34 +108,34 @@ static SpatialReference fromJson(JsonReader parser) throws Exception { int vcs_wkid = -1; int latestVcsWkid = -1; String wkt = null; - while (parser.nextToken() != JsonToken.END_OBJECT) { + while (parser.nextToken() != JsonReader.Token.END_OBJECT) { String name = parser.currentString(); parser.nextToken(); if (!bFoundWkid && name.equals("wkid")) { bFoundWkid = true; - if (parser.currentToken() == JsonToken.VALUE_NUMBER_INT) + if (parser.currentToken() == JsonReader.Token.VALUE_NUMBER_INT) wkid = parser.currentIntValue(); } else if (!bFoundLatestWkid && name.equals("latestWkid")) { bFoundLatestWkid = true; - if (parser.currentToken() == JsonToken.VALUE_NUMBER_INT) + if (parser.currentToken() == JsonReader.Token.VALUE_NUMBER_INT) latestWkid = parser.currentIntValue(); } else if (!bFoundWkt && name.equals("wkt")) { bFoundWkt = true; - if (parser.currentToken() == JsonToken.VALUE_STRING) + if (parser.currentToken() == JsonReader.Token.VALUE_STRING) wkt = parser.currentString(); } else if (!bFoundVcsWkid && name.equals("vcsWkid")) { bFoundVcsWkid = true; - if (parser.currentToken() == JsonToken.VALUE_NUMBER_INT) + if (parser.currentToken() == JsonReader.Token.VALUE_NUMBER_INT) vcs_wkid = parser.currentIntValue(); } else if (!bFoundLatestVcsWkid && name.equals("latestVcsWkid")) { bFoundLatestVcsWkid = true; - if (parser.currentToken() == JsonToken.VALUE_NUMBER_INT) + if (parser.currentToken() == JsonReader.Token.VALUE_NUMBER_INT) latestVcsWkid = parser.currentIntValue(); } } diff --git a/src/main/java/com/esri/core/geometry/ogc/OGCGeometry.java b/src/main/java/com/esri/core/geometry/ogc/OGCGeometry.java index 04b2df77..49197b83 100644 --- a/src/main/java/com/esri/core/geometry/ogc/OGCGeometry.java +++ b/src/main/java/com/esri/core/geometry/ogc/OGCGeometry.java @@ -5,42 +5,7 @@ import java.util.ArrayList; import java.util.Arrays; -import org.codehaus.jackson.JsonFactory; -import org.codehaus.jackson.JsonParseException; -import org.codehaus.jackson.JsonParser; -import org.json.JSONException; - -import com.esri.core.geometry.Envelope; -import com.esri.core.geometry.Envelope1D; -import com.esri.core.geometry.Geometry; -import com.esri.core.geometry.GeometryCursor; -import com.esri.core.geometry.GeometryCursorAppend; -import com.esri.core.geometry.GeometryEngine; -import com.esri.core.geometry.MapGeometry; -import com.esri.core.geometry.MapOGCStructure; -import com.esri.core.geometry.MultiPoint; -import com.esri.core.geometry.NumberUtils; -import com.esri.core.geometry.OGCStructure; -import com.esri.core.geometry.Operator; -import com.esri.core.geometry.OperatorBuffer; -import com.esri.core.geometry.OperatorConvexHull; -import com.esri.core.geometry.OperatorExportToWkb; -import com.esri.core.geometry.OperatorExportToGeoJson; -import com.esri.core.geometry.OperatorFactoryLocal; -import com.esri.core.geometry.OperatorImportFromESRIShape; -import com.esri.core.geometry.OperatorImportFromGeoJson; -import com.esri.core.geometry.OperatorImportFromWkb; -import com.esri.core.geometry.OperatorImportFromWkt; -import com.esri.core.geometry.OperatorIntersection; -import com.esri.core.geometry.OperatorSimplify; -import com.esri.core.geometry.OperatorSimplifyOGC; -import com.esri.core.geometry.OperatorUnion; -import com.esri.core.geometry.Point; -import com.esri.core.geometry.Polygon; -import com.esri.core.geometry.Polyline; -import com.esri.core.geometry.SimpleGeometryCursor; -import com.esri.core.geometry.SpatialReference; -import com.esri.core.geometry.VertexDescription; +import com.esri.core.geometry.*; /** * OGC Simple Feature Access specification v.1.2.1 @@ -493,18 +458,13 @@ public static OGCGeometry fromEsriShape(ByteBuffer buffer) { SpatialReference.create(4326)); } - public static OGCGeometry fromJson(String string) - throws JsonParseException, IOException { - JsonFactory factory = new JsonFactory(); - JsonParser jsonParserPt = factory.createJsonParser(string); - jsonParserPt.nextToken(); - MapGeometry mapGeom = GeometryEngine.jsonToGeometry(jsonParserPt); + public static OGCGeometry fromJson(String string) { + MapGeometry mapGeom = GeometryEngine.jsonToGeometry(JsonParserReader.createFromString(string)); return OGCGeometry.createFromEsriGeometry(mapGeom.getGeometry(), mapGeom.getSpatialReference()); } - public static OGCGeometry fromGeoJson(String string) - throws JSONException { + public static OGCGeometry fromGeoJson(String string) { OperatorImportFromGeoJson op = (OperatorImportFromGeoJson) OperatorFactoryLocal .getInstance().getOperator(Operator.Type.ImportFromGeoJson); MapOGCStructure mapOGCStructure = op.executeOGC(0, string, null); diff --git a/src/test/java/com/esri/core/geometry/GeometryUtils.java b/src/test/java/com/esri/core/geometry/GeometryUtils.java index c1af6866..62ddbc37 100644 --- a/src/test/java/com/esri/core/geometry/GeometryUtils.java +++ b/src/test/java/com/esri/core/geometry/GeometryUtils.java @@ -4,9 +4,6 @@ import java.io.FileNotFoundException; import java.util.Scanner; -import org.codehaus.jackson.JsonFactory; -import org.codehaus.jackson.JsonParser; - public class GeometryUtils { public static String getGeometryType(Geometry geomIn) { // there are five types: esriGeometryPoint @@ -29,12 +26,8 @@ public static String getGeometryType(Geometry geomIn) { } static Geometry getGeometryFromJSon(String jsonStr) { - JsonFactory jf = new JsonFactory(); - try { - JsonParser jp = jf.createJsonParser(jsonStr); - jp.nextToken(); - Geometry geom = GeometryEngine.jsonToGeometry(jp).getGeometry(); + Geometry geom = GeometryEngine.jsonToGeometry(jsonStr).getGeometry(); return geom; } catch (Exception ex) { return null; diff --git a/src/test/java/com/esri/core/geometry/TestCommonMethods.java b/src/test/java/com/esri/core/geometry/TestCommonMethods.java index 420718d4..b38aeec4 100644 --- a/src/test/java/com/esri/core/geometry/TestCommonMethods.java +++ b/src/test/java/com/esri/core/geometry/TestCommonMethods.java @@ -5,8 +5,6 @@ import java.io.FileOutputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; -import org.codehaus.jackson.JsonFactory; -import org.codehaus.jackson.JsonParser; import junit.framework.TestCase; import org.junit.Test; @@ -237,15 +235,8 @@ public static Object readObjectFromFile(String fileName) { } public static MapGeometry fromJson(String jsonString) { - JsonFactory factory = new JsonFactory(); try { - JsonParser jsonParser = factory.createJsonParser(jsonString); - jsonParser.nextToken(); - OperatorImportFromJson importer = (OperatorImportFromJson) OperatorFactoryLocal - .getInstance().getOperator( - Operator.Type.ImportFromJson); - - return importer.execute(Geometry.Type.Unknown, jsonParser); + return OperatorImportFromJson.local().execute(Geometry.Type.Unknown, jsonString); } catch (Exception ex) { } diff --git a/src/test/java/com/esri/core/geometry/TestContains.java b/src/test/java/com/esri/core/geometry/TestContains.java index 71c811e9..4488b3ef 100644 --- a/src/test/java/com/esri/core/geometry/TestContains.java +++ b/src/test/java/com/esri/core/geometry/TestContains.java @@ -1,12 +1,15 @@ package com.esri.core.geometry; -import java.io.IOException; import junit.framework.TestCase; -import org.codehaus.jackson.JsonFactory; -import org.codehaus.jackson.JsonParseException; -import org.codehaus.jackson.JsonParser; + +import java.io.IOException; + import org.junit.Test; +import com.fasterxml.jackson.core.JsonFactory; +import com.fasterxml.jackson.core.JsonParseException; +import com.fasterxml.jackson.core.JsonParser; + public class TestContains extends TestCase { @Override protected void setUp() throws Exception { @@ -19,11 +22,10 @@ protected void tearDown() throws Exception { } @Test - public static void testContainsFailureCR186456() throws JsonParseException, - IOException { + public static void testContainsFailureCR186456() throws JsonParseException, IOException { String str = "{\"rings\":[[[406944.399999999,287461.450000001],[406947.750000011,287462.299999997],[406946.44999999,287467.450000001],[406943.050000005,287466.550000005],[406927.799999992,287456.849999994],[406926.949999996,287456.599999995],[406924.800000005,287455.999999998],[406924.300000007,287455.849999999],[406924.200000008,287456.099999997],[406923.450000011,287458.449999987],[406922.999999987,287459.800000008],[406922.29999999,287462.099999998],[406921.949999991,287463.449999992],[406921.449999993,287465.050000011],[406920.749999996,287466.700000004],[406919.800000001,287468.599999996],[406919.050000004,287469.99999999],[406917.800000009,287471.800000008],[406916.04999999,287473.550000001],[406915.449999993,287473.999999999],[406913.700000001,287475.449999993],[406913.300000002,287475.899999991],[406912.050000008,287477.250000011],[406913.450000002,287478.150000007],[406915.199999994,287478.650000005],[406915.999999991,287478.800000005],[406918.300000007,287479.200000003],[406920.649999997,287479.450000002],[406923.100000013,287479.550000001],[406925.750000001,287479.450000002],[406928.39999999,287479.150000003],[406929.80000001,287478.950000004],[406932.449999998,287478.350000006],[406935.099999987,287477.60000001],[406938.699999998,287476.349999989],[406939.649999994,287473.949999999],[406939.799999993,287473.949999999],[406941.249999987,287473.75],[406942.700000007,287473.250000002],[406943.100000005,287473.100000003],[406943.950000001,287472.750000004],[406944.799999998,287472.300000006],[406944.999999997,287472.200000007],[406946.099999992,287471.200000011],[406946.299999991,287470.950000012],[406948.00000001,287468.599999996],[406948.10000001,287468.399999997],[406950.100000001,287465.050000011],[406951.949999993,287461.450000001],[406952.049999993,287461.300000001],[406952.69999999,287459.900000007],[406953.249999987,287458.549999987],[406953.349999987,287458.299999988],[406953.650000012,287457.299999992],[406953.900000011,287456.349999996],[406954.00000001,287455.300000001],[406954.00000001,287454.750000003],[406953.850000011,287453.750000008],[406953.550000012,287452.900000011],[406953.299999987,287452.299999988],[406954.500000008,287450.299999996],[406954.00000001,287449.000000002],[406953.399999987,287447.950000006],[406953.199999988,287447.550000008],[406952.69999999,287446.850000011],[406952.149999992,287446.099999988],[406951.499999995,287445.499999991],[406951.149999996,287445.249999992],[406950.449999999,287444.849999994],[406949.600000003,287444.599999995],[406949.350000004,287444.549999995],[406948.250000009,287444.499999995],[406947.149999987,287444.699999994],[406946.849999989,287444.749999994],[406945.899999993,287444.949999993],[406944.999999997,287445.349999991],[406944.499999999,287445.64999999],[406943.650000003,287446.349999987],[406942.900000006,287447.10000001],[406942.500000008,287447.800000007],[406942.00000001,287448.700000003],[406941.600000011,287449.599999999],[406941.350000013,287450.849999994],[406941.350000013,287451.84999999],[406941.450000012,287452.850000012],[406941.750000011,287453.850000007],[406941.800000011,287454.000000007],[406942.150000009,287454.850000003],[406942.650000007,287455.6],[406943.150000005,287456.299999997],[406944.499999999,287457.299999992],[406944.899999997,287457.599999991],[406945.299999995,287457.949999989],[406944.399999999,287461.450000001],[406941.750000011,287461.999999998],[406944.399999999,287461.450000001]],[[406944.399999999,287461.450000001],[406947.750000011,287462.299999997],[406946.44999999,287467.450000001],[406943.050000005,287466.550000005],[406927.799999992,287456.849999994],[406944.399999999,287461.450000001]]]}"; JsonFactory jsonFactory = new JsonFactory(); - JsonParser jsonParser = jsonFactory.createJsonParser(str); + JsonParser jsonParser = jsonFactory.createParser(str); MapGeometry mg = GeometryEngine.jsonToGeometry(jsonParser); boolean res = GeometryEngine.contains(mg.getGeometry(), mg.getGeometry(), null); diff --git a/src/test/java/com/esri/core/geometry/TestDistance.java b/src/test/java/com/esri/core/geometry/TestDistance.java index b531bb17..efcdaeeb 100644 --- a/src/test/java/com/esri/core/geometry/TestDistance.java +++ b/src/test/java/com/esri/core/geometry/TestDistance.java @@ -1,10 +1,6 @@ package com.esri.core.geometry; -import java.io.IOException; import junit.framework.TestCase; -import org.codehaus.jackson.JsonFactory; -import org.codehaus.jackson.JsonParseException; -import org.codehaus.jackson.JsonParser; import org.junit.Test; public class TestDistance extends TestCase { @@ -146,17 +142,13 @@ private static Point makePoint() { } @Test - public static void testDistanceWithNullSpatialReference() - throws JsonParseException, IOException { + public static void testDistanceWithNullSpatialReference() { // There was a bug that distance op did not work with null Spatial // Reference. String str1 = "{\"paths\":[[[-117.138791850991,34.017492675023],[-117.138762336971,34.0174925550462]]]}"; String str2 = "{\"paths\":[[[-117.138867827972,34.0174854109623],[-117.138850197027,34.0174929160126],[-117.138791850991,34.017492675023]]]}"; - JsonFactory jsonFactory = new JsonFactory(); - JsonParser jsonParser1 = jsonFactory.createJsonParser(str1); - JsonParser jsonParser2 = jsonFactory.createJsonParser(str2); - MapGeometry geom1 = GeometryEngine.jsonToGeometry(jsonParser1); - MapGeometry geom2 = GeometryEngine.jsonToGeometry(jsonParser2); + MapGeometry geom1 = GeometryEngine.jsonToGeometry(JsonParserReader.createFromString(str1)); + MapGeometry geom2 = GeometryEngine.jsonToGeometry(JsonParserReader.createFromString(str2)); double distance = GeometryEngine.distance(geom1.getGeometry(), geom2.getGeometry(), null); assertTrue(distance == 0); diff --git a/src/test/java/com/esri/core/geometry/TestGeomToGeoJson.java b/src/test/java/com/esri/core/geometry/TestGeomToGeoJson.java index 5ef3157e..aa480b26 100644 --- a/src/test/java/com/esri/core/geometry/TestGeomToGeoJson.java +++ b/src/test/java/com/esri/core/geometry/TestGeomToGeoJson.java @@ -27,12 +27,10 @@ import com.esri.core.geometry.ogc.OGCMultiPoint; import com.esri.core.geometry.ogc.OGCLineString; import com.esri.core.geometry.ogc.OGCPolygon; +import com.fasterxml.jackson.core.JsonFactory; +import com.fasterxml.jackson.core.JsonParser; import com.esri.core.geometry.ogc.OGCConcreteGeometryCollection; import junit.framework.TestCase; -import org.codehaus.jackson.JsonFactory; -import org.codehaus.jackson.JsonParser; -import org.json.JSONException; -import org.json.JSONObject; import org.junit.Test; import java.io.IOException; @@ -228,10 +226,10 @@ public void testPolygonWithHoleReversed() { public void testMultiPolygon() throws IOException { JsonFactory jsonFactory = new JsonFactory(); - String geoJsonPolygon = "{\"type\":\"MultiPolygon\",\"coordinates\":[[[[-100,-100],[-100,100],[100,100],[100,-100],[-100,-100]],[[-90,-90],[90,90],[-90,90],[90,-90],[-90,-90]]],[[[-10.0,-10.0],[-10.0,10.0],[10.0,10.0],[10.0,-10.0],[-10.0,-10.0]]]]}"; + //String geoJsonPolygon = "{\"type\":\"MultiPolygon\",\"coordinates\":[[[[-100,-100],[-100,100],[100,100],[100,-100],[-100,-100]],[[-90,-90],[90,90],[-90,90],[90,-90],[-90,-90]]],[[[-10.0,-10.0],[-10.0,10.0],[10.0,10.0],[10.0,-10.0],[-10.0,-10.0]]]]}"; String esriJsonPolygon = "{\"rings\": [[[-100, -100], [-100, 100], [100, 100], [100, -100], [-100, -100]], [[-90, -90], [90, 90], [-90, 90], [90, -90], [-90, -90]], [[-10, -10], [-10, 10], [10, 10], [10, -10], [-10, -10]]]}"; - JsonParser parser = jsonFactory.createJsonParser(esriJsonPolygon); + JsonParser parser = jsonFactory.createParser(esriJsonPolygon); MapGeometry parsedPoly = GeometryEngine.jsonToGeometry(parser); //MapGeometry parsedPoly = GeometryEngine.geometryFromGeoJson(jsonPolygon, 0, Geometry.Type.Polygon); @@ -244,9 +242,8 @@ public void testMultiPolygon() throws IOException { } - @Deprecated @Test - public void testEmptyPolygon() throws JSONException { + public void testEmptyPolygon() { Polygon p = new Polygon(); OperatorExportToGeoJson exporter = (OperatorExportToGeoJson) factory.getOperator(Operator.Type.ExportToGeoJson); String result = exporter.execute(p); @@ -386,16 +383,6 @@ public void testGeometryCollection() { geoms, sr); String s2 = collection.asGeoJson(); - JSONObject json = null; - boolean valid = false; - try { - json = new JSONObject(s2); - valid = true; - } catch (Exception e) { - } - - assertTrue(valid); - assertEquals("{\"type\":\"GeometryCollection\",\"geometries\":[{\"type\":\"Point\",\"coordinates\":[1,1]},{\"type\":\"LineString\",\"coordinates\":[[1,1],[2,2]]},{\"type\":\"Polygon\",\"coordinates\":[[[1,1],[2,0],[3,1],[2,2],[1,1]]]}],\"crs\":{\"type\":\"name\",\"properties\":{\"name\":\"EPSG:4326\"}}}", collection.asGeoJson()); } @@ -435,7 +422,7 @@ public void testEnvelopeGeometryEngine() { } @Test - public void testOldCRS() throws JSONException { + public void testOldCRS() { String inputStr = "{\"type\":\"Polygon\",\"coordinates\":[[[-180,-90],[180,-90],[180,90],[-180,90],[-180,-90]]], \"crs\":\"EPSG:4267\"}"; MapGeometry mg = OperatorImportFromGeoJson.local().execute(GeoJsonImportFlags.geoJsonImportDefaults, Geometry.Type.Unknown, inputStr, null); String result = GeometryEngine.geometryToGeoJson(mg.getSpatialReference(), mg.getGeometry()); diff --git a/src/test/java/com/esri/core/geometry/TestGeomToJSonExportSRFromWkiOrWkt_CR181369.java b/src/test/java/com/esri/core/geometry/TestGeomToJSonExportSRFromWkiOrWkt_CR181369.java index ce4f81dc..237abb4c 100644 --- a/src/test/java/com/esri/core/geometry/TestGeomToJSonExportSRFromWkiOrWkt_CR181369.java +++ b/src/test/java/com/esri/core/geometry/TestGeomToJSonExportSRFromWkiOrWkt_CR181369.java @@ -1,12 +1,13 @@ package com.esri.core.geometry; import java.io.IOException; -import org.codehaus.jackson.JsonFactory; -import org.codehaus.jackson.JsonParseException; -import org.codehaus.jackson.JsonParser; import junit.framework.TestCase; import org.junit.Test; +import com.fasterxml.jackson.core.JsonFactory; +import com.fasterxml.jackson.core.JsonParseException; +import com.fasterxml.jackson.core.JsonParser; + public class TestGeomToJSonExportSRFromWkiOrWkt_CR181369 extends TestCase { @Override protected void setUp() throws Exception { @@ -43,7 +44,7 @@ boolean testPoint() throws JsonParseException, IOException { Point pointEmpty = new Point(); { JsonParser pointWebMerc1Parser = factory - .createJsonParser(GeometryEngine.geometryToJson( + .createParser(GeometryEngine.geometryToJson( spatialReferenceWebMerc1, point1)); MapGeometry pointWebMerc1MP = GeometryEngine .jsonToGeometry(pointWebMerc1Parser); @@ -59,7 +60,7 @@ boolean testPoint() throws JsonParseException, IOException { bAnswer = false; } - pointWebMerc1Parser = factory.createJsonParser(GeometryEngine + pointWebMerc1Parser = factory.createParser(GeometryEngine .geometryToJson(null, point1)); pointWebMerc1MP = GeometryEngine .jsonToGeometry(pointWebMerc1Parser); @@ -73,11 +74,11 @@ boolean testPoint() throws JsonParseException, IOException { String pointEmptyString = GeometryEngine.geometryToJson( spatialReferenceWebMerc1, pointEmpty); - pointWebMerc1Parser = factory.createJsonParser(pointEmptyString); + pointWebMerc1Parser = factory.createParser(pointEmptyString); } JsonParser pointWebMerc2Parser = factory - .createJsonParser(GeometryEngine.geometryToJson( + .createParser(GeometryEngine.geometryToJson( spatialReferenceWebMerc2, point1)); MapGeometry pointWebMerc2MP = GeometryEngine .jsonToGeometry(pointWebMerc2Parser); @@ -94,7 +95,7 @@ boolean testPoint() throws JsonParseException, IOException { { JsonParser pointWgs84Parser = factory - .createJsonParser(GeometryEngine.geometryToJson( + .createParser(GeometryEngine.geometryToJson( spatialReferenceWGS84, point1)); MapGeometry pointWgs84MP = GeometryEngine .jsonToGeometry(pointWgs84Parser); @@ -135,7 +136,7 @@ boolean testPoint() throws JsonParseException, IOException { {// import String s = "{\"x\":0.0,\"y\":1.0,\"z\":5.0,\"m\":11.0,\"spatialReference\":{\"wkid\":102100,\"latestWkid\":3857}}"; - JsonParser parser = factory.createJsonParser(s); + JsonParser parser = factory.createParser(s); MapGeometry map_pt = GeometryEngine.jsonToGeometry(parser); Point pt = (Point) map_pt.getGeometry(); assertTrue(pt.getX() == 0.0); @@ -146,7 +147,7 @@ boolean testPoint() throws JsonParseException, IOException { { String s = "{\"x\" : 5.0, \"y\" : null, \"spatialReference\" : {\"wkid\" : 4326}} "; - JsonParser parser = factory.createJsonParser(s); + JsonParser parser = factory.createParser(s); MapGeometry map_pt = GeometryEngine.jsonToGeometry(parser); Point pt = (Point) map_pt.getGeometry(); assertTrue(pt.isEmpty()); @@ -169,7 +170,7 @@ boolean testMultiPoint() throws JsonParseException, IOException { { String s = GeometryEngine.geometryToJson(spatialReferenceWGS84, multiPoint1); - JsonParser mPointWgs84Parser = factory.createJsonParser(s); + JsonParser mPointWgs84Parser = factory.createParser(s); MapGeometry mPointWgs84MP = GeometryEngine .jsonToGeometry(mPointWgs84Parser); assertTrue(multiPoint1.getPointCount() == ((MultiPoint) mPointWgs84MP @@ -210,7 +211,7 @@ boolean testMultiPoint() throws JsonParseException, IOException { { String points = "{\"hasM\" : false, \"hasZ\" : true, \"uncle remus\" : null, \"points\" : [ [0,0,1], [0.0,10.0,1], [10.0,10.0,1], [10.0,0.0,1, 6666] ],\"spatialReference\" : {\"wkid\" : 4326}}"; MapGeometry mp = GeometryEngine.jsonToGeometry(factory - .createJsonParser(points)); + .createParser(points)); MultiPoint multipoint = (MultiPoint) mp.getGeometry(); assertTrue(multipoint.getPointCount() == 4); Point2D point2d; @@ -248,7 +249,7 @@ boolean testPolyline() throws JsonParseException, IOException { { JsonParser polylinePathsWgs84Parser = factory - .createJsonParser(GeometryEngine.geometryToJson( + .createParser(GeometryEngine.geometryToJson( spatialReferenceWGS84, polyline)); MapGeometry mPolylineWGS84MP = GeometryEngine .jsonToGeometry(polylinePathsWgs84Parser); @@ -307,7 +308,7 @@ boolean testPolyline() throws JsonParseException, IOException { { String paths = "{\"hasZ\" : true, \"paths\" : [ [ [0.0, 0.0,3], [0, 10.0,3], [10.0, 10.0,3, 6666], [10.0, 0.0,3, 6666] ], [ [1.0, 1,3], [1.0, 9.0,3], [9.0, 9.0,3], [1.0, 9.0,3] ] ], \"spatialReference\" : {\"wkid\" : 4326}, \"hasM\" : false}"; MapGeometry mapGeometry = GeometryEngine.jsonToGeometry(factory - .createJsonParser(paths)); + .createParser(paths)); Polyline p = (Polyline) mapGeometry.getGeometry(); assertTrue(p.getPathCount() == 2); @SuppressWarnings("unused") @@ -341,7 +342,7 @@ boolean testPolygon() throws JsonParseException, IOException { { JsonParser polygonPathsWgs84Parser = factory - .createJsonParser(GeometryEngine.geometryToJson( + .createParser(GeometryEngine.geometryToJson( spatialReferenceWGS84, polygon)); MapGeometry mPolygonWGS84MP = GeometryEngine .jsonToGeometry(polygonPathsWgs84Parser); @@ -405,7 +406,7 @@ boolean testPolygon() throws JsonParseException, IOException { // Test Import Polygon from Polygon String rings = "{\"hasZ\": true, \"rings\" : [ [ [0,0, 5], [0.0, 10.0, 5], [10.0,10.0, 5, 66666], [10.0,0.0, 5] ], [ [12, 12] ], [ [13 , 17], [13 , 17] ], [ [1.0, 1.0, 5, 66666], [9.0,1.0, 5], [9.0,9.0, 5], [1.0,9.0, 5], [1.0, 1.0, 5] ] ] }"; MapGeometry mapGeometry = GeometryEngine.jsonToGeometry(factory - .createJsonParser(rings)); + .createParser(rings)); Polygon p = (Polygon) mapGeometry.getGeometry(); @SuppressWarnings("unused") double area = p.calculateArea2D(); @@ -429,7 +430,7 @@ boolean testEnvelope() throws JsonParseException, IOException { { JsonParser envelopeWGS84Parser = factory - .createJsonParser(GeometryEngine.geometryToJson( + .createParser(GeometryEngine.geometryToJson( spatialReferenceWGS84, envelope)); MapGeometry envelopeWGS84MP = GeometryEngine .jsonToGeometry(envelopeWGS84Parser); @@ -475,7 +476,7 @@ boolean testEnvelope() throws JsonParseException, IOException { {// import String s = "{\"xmin\":0.0,\"ymin\":1.0,\"xmax\":2.0,\"ymax\":3.0,\"zmin\":5.0,\"zmax\":7.0,\"mmin\":11.0,\"mmax\":13.0,\"spatialReference\":{\"wkid\":102100,\"latestWkid\":3857}}"; - JsonParser parser = factory.createJsonParser(s); + JsonParser parser = factory.createParser(s); MapGeometry map_env = GeometryEngine.jsonToGeometry(parser); Envelope env = (Envelope) map_env.getGeometry(); Envelope1D z = env.queryInterval(VertexDescription.Semantics.Z, 0); @@ -488,7 +489,7 @@ boolean testEnvelope() throws JsonParseException, IOException { { String s = "{ \"zmin\" : 33, \"xmin\" : -109.55, \"zmax\" : 53, \"ymin\" : 25.76, \"xmax\" : -86.39, \"ymax\" : 49.94, \"mmax\" : 13}"; - JsonParser parser = factory.createJsonParser(s); + JsonParser parser = factory.createParser(s); MapGeometry map_env = GeometryEngine.jsonToGeometry(parser); Envelope env = (Envelope) map_env.getGeometry(); Envelope2D e = new Envelope2D(); @@ -513,13 +514,13 @@ boolean testCR181369() throws JsonParseException, IOException { String jsonStringPointAndWKT = "{\"x\":10.0,\"y\":20.0,\"spatialReference\":{\"wkt\" : \"PROJCS[\\\"NAD83_UTM_zone_15N\\\",GEOGCS[\\\"GCS_North_American_1983\\\",DATUM[\\\"D_North_American_1983\\\",SPHEROID[\\\"GRS_1980\\\",6378137.0,298.257222101]],PRIMEM[\\\"Greenwich\\\",0.0],UNIT[\\\"Degree\\\",0.0174532925199433]],PROJECTION[\\\"Transverse_Mercator\\\"],PARAMETER[\\\"false_easting\\\",500000.0],PARAMETER[\\\"false_northing\\\",0.0],PARAMETER[\\\"central_meridian\\\",-93.0],PARAMETER[\\\"scale_factor\\\",0.9996],PARAMETER[\\\"latitude_of_origin\\\",0.0],UNIT[\\\"Meter\\\",1.0]]\"} }"; JsonParser jsonParserPointAndWKT = factory - .createJsonParser(jsonStringPointAndWKT); + .createParser(jsonStringPointAndWKT); MapGeometry mapGeom2 = GeometryEngine .jsonToGeometry(jsonParserPointAndWKT); String jsonStringPointAndWKT2 = GeometryEngine.geometryToJson( mapGeom2.getSpatialReference(), mapGeom2.getGeometry()); JsonParser jsonParserPointAndWKT2 = factory - .createJsonParser(jsonStringPointAndWKT2); + .createParser(jsonStringPointAndWKT2); MapGeometry mapGeom3 = GeometryEngine .jsonToGeometry(jsonParserPointAndWKT2); assertTrue(((Point) mapGeom2.getGeometry()).getX() == ((Point) mapGeom3 diff --git a/src/test/java/com/esri/core/geometry/TestImportExport.java b/src/test/java/com/esri/core/geometry/TestImportExport.java index 1138c082..5fd97f10 100644 --- a/src/test/java/com/esri/core/geometry/TestImportExport.java +++ b/src/test/java/com/esri/core/geometry/TestImportExport.java @@ -4,8 +4,6 @@ import java.nio.ByteOrder; import junit.framework.TestCase; -import org.json.JSONException; -import org.json.JSONObject; import org.junit.Test; public class TestImportExport extends TestCase { @@ -1228,7 +1226,7 @@ public static void testImportExportWktPoint() { @Deprecated @Test - public static void testImportGeoJsonGeometryCollection() throws JSONException { + public static void testImportGeoJsonGeometryCollection() { OperatorImportFromGeoJson importer = (OperatorImportFromGeoJson) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.ImportFromGeoJson); String geoJsonString; @@ -1305,8 +1303,7 @@ public static void testImportGeoJsonMultiPolygon() throws Exception { assertTrue(polygon.getPathCount() == 5); assertTrue(spatial_reference.getLatestID() == 3857); - JSONObject jsonObject = new JSONObject(geoJsonString); - map_geometry = importerGeoJson.execute(0, Geometry.Type.Unknown, jsonObject, null); + map_geometry = importerGeoJson.execute(0, Geometry.Type.Unknown, geoJsonString, null); polygon = (Polygon) map_geometry.getGeometry(); spatial_reference = map_geometry.getSpatialReference(); assertTrue(polygon != null); diff --git a/src/test/java/com/esri/core/geometry/TestJSonToGeomFromWkiOrWkt_CR177613.java b/src/test/java/com/esri/core/geometry/TestJSonToGeomFromWkiOrWkt_CR177613.java index 243f7c26..8abd75cf 100644 --- a/src/test/java/com/esri/core/geometry/TestJSonToGeomFromWkiOrWkt_CR177613.java +++ b/src/test/java/com/esri/core/geometry/TestJSonToGeomFromWkiOrWkt_CR177613.java @@ -1,13 +1,14 @@ package com.esri.core.geometry; import java.io.IOException; -import org.codehaus.jackson.JsonFactory; -import org.codehaus.jackson.JsonParseException; -import org.codehaus.jackson.JsonParser; -import org.codehaus.jackson.JsonToken; import junit.framework.TestCase; import org.junit.Test; +import com.fasterxml.jackson.core.JsonFactory; +import com.fasterxml.jackson.core.JsonParseException; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonToken; + public class TestJSonToGeomFromWkiOrWkt_CR177613 extends TestCase { JsonFactory factory = new JsonFactory(); @@ -28,7 +29,7 @@ public void testPolygonWithEmptyWKT_NoWKI() throws JsonParseException, + "[-97.06124,32.834], [-97.06127,32.832], [-97.06138,32.837] ], " + "[ [-97.06326,32.759], [-97.06298,32.755], [-97.06153,32.749], [-97.06326,32.759] ]], " + "\"spatialReference\" : {\"wkt\" : \"\"}}"; - JsonParser jsonParserPg = factory.createJsonParser(jsonStringPg); + JsonParser jsonParserPg = factory.createParser(jsonStringPg); jsonParserPg.nextToken(); MapGeometry mapGeom = GeometryEngine.jsonToGeometry(jsonParserPg); diff --git a/src/test/java/com/esri/core/geometry/TestJsonParser.java b/src/test/java/com/esri/core/geometry/TestJsonParser.java index a2214e5e..d6593115 100644 --- a/src/test/java/com/esri/core/geometry/TestJsonParser.java +++ b/src/test/java/com/esri/core/geometry/TestJsonParser.java @@ -4,14 +4,14 @@ import java.io.IOException; import java.util.Map; import junit.framework.TestCase; -import org.codehaus.jackson.JsonFactory; -import org.codehaus.jackson.JsonParseException; -import org.codehaus.jackson.JsonParser; -import org.codehaus.jackson.JsonToken; -import org.json.JSONObject; import org.junit.Assert; import org.junit.Test; +import com.fasterxml.jackson.core.JsonFactory; +import com.fasterxml.jackson.core.JsonParseException; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonToken; + public class TestJsonParser extends TestCase { JsonFactory factory = new JsonFactory(); @@ -34,7 +34,7 @@ protected void tearDown() throws Exception { public void test3DPoint() throws JsonParseException, IOException { String jsonString3DPt = "{\"x\" : -118.15, \"y\" : 33.80, \"z\" : 10.0, \"spatialReference\" : {\"wkid\" : 4326}}"; - JsonParser jsonParser3DPt = factory.createJsonParser(jsonString3DPt); + JsonParser jsonParser3DPt = factory.createParser(jsonString3DPt); MapGeometry point3DMP = GeometryEngine.jsonToGeometry(jsonParser3DPt); assertTrue(-118.15 == ((Point) point3DMP.getGeometry()).getX()); assertTrue(33.80 == ((Point) point3DMP.getGeometry()).getY()); diff --git a/src/test/java/com/esri/core/geometry/TestOGC.java b/src/test/java/com/esri/core/geometry/TestOGC.java index 5b2ef6b3..3edf417c 100644 --- a/src/test/java/com/esri/core/geometry/TestOGC.java +++ b/src/test/java/com/esri/core/geometry/TestOGC.java @@ -11,9 +11,9 @@ import com.esri.core.geometry.ogc.OGCMultiPolygon; import com.esri.core.geometry.ogc.OGCPoint; import com.esri.core.geometry.ogc.OGCPolygon; +import com.fasterxml.jackson.core.JsonParseException; import com.esri.core.geometry.ogc.OGCConcreteGeometryCollection; -import org.codehaus.jackson.JsonParseException; import org.junit.Test; import java.io.IOException; @@ -850,15 +850,7 @@ public void testMultiPointSinglePoint() { public void testWktMultiPolygon() { String restJson = "{\"rings\": [[[-100, -100], [-100, 100], [100, 100], [100, -100], [-100, -100]], [[-90, -90], [90, 90], [-90, 90], [90, -90], [-90, -90]], [[-10, -10], [-10, 10], [10, 10], [10, -10], [-10, -10]]]}"; MapGeometry g = null; - try { - g = OperatorImportFromJson.local().execute(Geometry.Type.Unknown, restJson); - } catch (JsonParseException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } catch (IOException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } + g = OperatorImportFromJson.local().execute(Geometry.Type.Unknown, restJson); String wkt = OperatorExportToWkt.local().execute(0, g.getGeometry(), null); assertTrue(wkt.equals("MULTIPOLYGON (((-100 -100, 100 -100, 100 100, -100 100, -100 -100), (-90 -90, 90 -90, -90 90, 90 90, -90 -90)), ((-10 -10, 10 -10, 10 10, -10 10, -10 -10)))")); } diff --git a/src/test/java/com/esri/core/geometry/TestPolygon.java b/src/test/java/com/esri/core/geometry/TestPolygon.java index e34a14b7..8b918e9c 100644 --- a/src/test/java/com/esri/core/geometry/TestPolygon.java +++ b/src/test/java/com/esri/core/geometry/TestPolygon.java @@ -4,7 +4,6 @@ import junit.framework.TestCase; -import org.json.JSONException; import org.junit.Test; import com.esri.core.geometry.ogc.OGCGeometry; @@ -1205,7 +1204,7 @@ public void testReplaceNaNs() { } @Test - public void testPolygon2PolygonFails() throws IOException, JSONException { + public void testPolygon2PolygonFails() { OperatorFactoryLocal factory = OperatorFactoryLocal.getInstance(); OperatorExportToGeoJson exporter = (OperatorExportToGeoJson) factory .getOperator(Operator.Type.ExportToGeoJson); @@ -1221,10 +1220,10 @@ public void testPolygon2PolygonFails() throws IOException, JSONException { } @Test - public void testPolygon2PolygonFails2() throws JSONException { + public void testPolygon2PolygonFails2() { String birminghamGeojson = GeometryEngine .geometryToGeoJson(birmingham()); - MapGeometry returnedGeometry = GeometryEngine.geometryFromGeoJson( + MapGeometry returnedGeometry = GeometryEngine.geoJsonToGeometry( birminghamGeojson, GeoJsonImportFlags.geoJsonImportDefaults, Geometry.Type.Polygon); Polygon polygon = (Polygon) returnedGeometry.getGeometry(); @@ -1232,10 +1231,10 @@ public void testPolygon2PolygonFails2() throws JSONException { } @Test - public void testPolygon2PolygonWorks() throws JSONException { + public void testPolygon2PolygonWorks() { String birminghamGeojson = GeometryEngine .geometryToGeoJson(birmingham()); - MapGeometry returnedGeometry = GeometryEngine.geometryFromGeoJson( + MapGeometry returnedGeometry = GeometryEngine.geoJsonToGeometry( birminghamGeojson, GeoJsonImportFlags.geoJsonImportDefaults, Geometry.Type.Polygon); Polygon polygon = (Polygon) returnedGeometry.getGeometry(); @@ -1243,7 +1242,7 @@ public void testPolygon2PolygonWorks() throws JSONException { } @Test - public void testPolygon2Polygon2Works() throws JSONException, IOException { + public void testPolygon2Polygon2Works() { String birminghamJson = GeometryEngine.geometryToJson(4326, birmingham()); MapGeometry returnedGeometry = GeometryEngine diff --git a/src/test/java/com/esri/core/geometry/TestRelation.java b/src/test/java/com/esri/core/geometry/TestRelation.java index 5c00d8ad..041908cf 100644 --- a/src/test/java/com/esri/core/geometry/TestRelation.java +++ b/src/test/java/com/esri/core/geometry/TestRelation.java @@ -4,7 +4,6 @@ import junit.framework.TestCase; -import org.codehaus.jackson.JsonParseException; import org.junit.Test; import com.esri.core.geometry.Geometry.GeometryAccelerationDegree; @@ -5498,7 +5497,7 @@ public void testDisjointCrash() { } @Test - public void testDisjointFail() throws JsonParseException, IOException { + public void testDisjointFail() { MapGeometry geometry1 = OperatorImportFromJson.local().execute(Geometry.Type.Unknown, "{\"paths\":[[[3,3],[3,3]]],\"spatialReference\":{\"wkid\":4326}}"); MapGeometry geometry2 = OperatorImportFromJson.local().execute(Geometry.Type.Unknown, "{\"rings\":[[[2,2],[2,4],[4,4],[4,2],[2,2]]],\"spatialReference\":{\"wkid\":4326}}"); OperatorDisjoint.local().accelerateGeometry(geometry1.getGeometry(), geometry1.getSpatialReference(), GeometryAccelerationDegree.enumMedium); diff --git a/src/test/java/com/esri/core/geometry/TestSimplify.java b/src/test/java/com/esri/core/geometry/TestSimplify.java index bdc2be0f..b7380851 100644 --- a/src/test/java/com/esri/core/geometry/TestSimplify.java +++ b/src/test/java/com/esri/core/geometry/TestSimplify.java @@ -9,10 +9,10 @@ import junit.framework.TestCase; -import org.codehaus.jackson.JsonFactory; -import org.codehaus.jackson.JsonParseException; import org.junit.Test; +import com.fasterxml.jackson.core.JsonFactory; + public class TestSimplify extends TestCase { OperatorFactoryLocal factory = null; OperatorSimplify simplifyOp = null; @@ -1233,18 +1233,16 @@ public void testisSimpleOGC() { } @Test - public void testPolylineIsSimpleForOGC() throws IOException { + public void testPolylineIsSimpleForOGC() { OperatorImportFromJson importerJson = (OperatorImportFromJson) factory .getOperator(Operator.Type.ImportFromJson); OperatorSimplify simplify = (OperatorSimplify) factory .getOperator(Operator.Type.Simplify); - JsonFactory f = new JsonFactory(); - { String s = "{\"paths\":[[[0, 10], [8, 5], [5, 2], [6, 0]]]}"; Geometry g = importerJson.execute(Geometry.Type.Unknown, - f.createJsonParser(s)).getGeometry(); + JsonParserReader.createFromString(s)).getGeometry(); boolean res = simplifyOpOGC.isSimpleOGC(g, null, true, null, null); assertTrue(res); } @@ -1252,7 +1250,7 @@ public void testPolylineIsSimpleForOGC() throws IOException { String s = "{\"paths\":[[[0, 10], [6, 0], [7, 5], [0, 3]]]}";// self // intersection Geometry g = importerJson.execute(Geometry.Type.Unknown, - f.createJsonParser(s)).getGeometry(); + JsonParserReader.createFromString(s)).getGeometry(); boolean res = simplifyOpOGC.isSimpleOGC(g, null, true, null, null); assertTrue(!res); } @@ -1260,7 +1258,7 @@ public void testPolylineIsSimpleForOGC() throws IOException { { String s = "{\"paths\":[[[0, 10], [6, 0], [0, 3], [0, 10]]]}"; // closed Geometry g = importerJson.execute(Geometry.Type.Unknown, - f.createJsonParser(s)).getGeometry(); + JsonParserReader.createFromString(s)).getGeometry(); boolean res = simplifyOpOGC.isSimpleOGC(g, null, true, null, null); assertTrue(res); } @@ -1271,7 +1269,7 @@ public void testPolylineIsSimpleForOGC() throws IOException { // self // tangent Geometry g = importerJson.execute(Geometry.Type.Unknown, - f.createJsonParser(s)).getGeometry(); + JsonParserReader.createFromString(s)).getGeometry(); boolean res = simplifyOpOGC.isSimpleOGC(g, null, true, null, null); assertTrue(!res); } @@ -1284,7 +1282,7 @@ public void testPolylineIsSimpleForOGC() throws IOException { // a // point Geometry g = importerJson.execute(Geometry.Type.Unknown, - f.createJsonParser(s)).getGeometry(); + JsonParserReader.createFromString(s)).getGeometry(); boolean res = simplifyOpOGC.isSimpleOGC(g, null, true, null, null); assertTrue(res); } @@ -1298,7 +1296,7 @@ public void testPolylineIsSimpleForOGC() throws IOException { // one // point Geometry g = importerJson.execute(Geometry.Type.Unknown, - f.createJsonParser(s)).getGeometry(); + JsonParserReader.createFromString(s)).getGeometry(); boolean res = simplifyOpOGC.isSimpleOGC(g, null, true, null, null); assertTrue(!res); } @@ -1308,7 +1306,7 @@ public void testPolylineIsSimpleForOGC() throws IOException { // lines // intersect Geometry g = importerJson.execute(Geometry.Type.Unknown, - f.createJsonParser(s)).getGeometry(); + JsonParserReader.createFromString(s)).getGeometry(); boolean res = simplifyOpOGC.isSimpleOGC(g, null, true, null, null); assertTrue(!res); } @@ -1320,7 +1318,7 @@ public void testPolylineIsSimpleForOGC() throws IOException { // mid // point. Geometry g = importerJson.execute(Geometry.Type.Unknown, - f.createJsonParser(s)).getGeometry(); + JsonParserReader.createFromString(s)).getGeometry(); boolean res = simplifyOpOGC.isSimpleOGC(g, null, true, null, null); assertTrue(!res); } @@ -1328,7 +1326,7 @@ public void testPolylineIsSimpleForOGC() throws IOException { } @Test - public void testFillRule() throws JsonParseException, IOException { + public void testFillRule() { //self intersecting star shape MapGeometry mg = OperatorImportFromJson.local().execute(Geometry.Type.Unknown, "{\"rings\":[[[0,0], [5,10], [10, 0], [0, 7], [10, 7], [0, 0]]]}"); Polygon poly = (Polygon)mg.getGeometry(); diff --git a/src/test/java/com/esri/core/geometry/TestWKBSupport.java b/src/test/java/com/esri/core/geometry/TestWKBSupport.java index 196e5e1e..0c84d883 100644 --- a/src/test/java/com/esri/core/geometry/TestWKBSupport.java +++ b/src/test/java/com/esri/core/geometry/TestWKBSupport.java @@ -2,9 +2,6 @@ import java.io.IOException; import java.nio.ByteBuffer; -import org.codehaus.jackson.JsonFactory; -import org.codehaus.jackson.JsonParseException; -import org.codehaus.jackson.JsonParser; import junit.framework.TestCase; import org.junit.Test; @@ -23,35 +20,27 @@ protected void tearDown() throws Exception { @Test public void testWKB() { - try { - // JSON -> GEOM -> WKB - - String strPolygon1 = "{\"xmin\":-1.1663479012889031E7,\"ymin\":4919777.494405342,\"xmax\":-1.1658587043078788E7,\"ymax\":4924669.464215587,\"spatialReference\":{\"wkid\":102100}}"; - // String strPolygon1 = - // "{\"rings\":[[[-119.152450421001,38.4118009590513],[-119.318825070203,38.5271086243914],[-119.575687062955,38.7029101298904],[-119.889341639399,38.9222515603984],[-119.995254694357,38.9941061536377],[-119.995150114198,39.0634913594691],[-119.994541258334,39.1061318056708],[-119.995527335641,39.1587132866355],[-119.995304181493,39.3115454332125],[-119.996011479298,39.4435009764511],[-119.996165311172,39.7206108077274],[-119.996324660047,41.1775662656441],[-119.993459369715,41.9892049531992],[-119.351692186077,41.9888529749781],[-119.3109421304,41.9891353872811],[-118.185316829038,41.9966370981387],[-117.018864363596,41.9947941808341],[-116.992313337997,41.9947945094663],[-115.947544658193,41.9945994628997],[-115.024862911148,41.996506455953],[-114.269471632824,41.9959242345073],[-114.039072662345,41.9953908974688],[-114.038151248682,40.9976868405942],[-114.038108189376,40.1110466529553],[-114.039844684228,39.9087788600023],[-114.040105338584,39.5386849268845],[-114.044267501155,38.6789958815881],[-114.045090206153,38.5710950539539],[-114.047272999176,38.1376524399918],[-114.047260595159,37.5984784866001],[-114.043939384154,36.9965379371421],[-114.043716435713,36.8418489458647],[-114.037392074194,36.2160228969702],[-114.045105557286,36.1939778840226],[-114.107775185788,36.1210907070504],[-114.12902308363,36.041730493896],[-114.206768869568,36.0172554164834],[-114.233472615347,36.0183310595897],[-114.307587598189,36.0622330993643],[-114.303857056018,36.0871084040611],[-114.316095374696,36.1114380366653],[-114.344233941709,36.1374802520568],[-114.380803116644,36.1509912717765],[-114.443945697733,36.1210532841897],[-114.466613475422,36.1247112590539],[-114.530573568745,36.1550902046725],[-114.598935242024,36.1383354528834],[-114.621610747198,36.1419666834504],[-114.712761724737,36.1051810523675],[-114.728150311069,36.0859627711604],[-114.728966012834,36.0587530361083],[-114.717673567756,36.0367580437018],[-114.736212493583,35.9876483502758],[-114.699275906446,35.9116119537412],[-114.661600122152,35.8804735854242],[-114.662462095522,35.8709599070091],[-114.689867343369,35.8474424944766],[-114.682739704595,35.7647034175617],[-114.688820027649,35.7325957399896],[-114.665091345861,35.6930994107107],[-114.668486064922,35.6563989882404],[-114.654065925137,35.6465840800053],[-114.6398667219,35.6113485698329],[-114.653134321223,35.5848331056108],[-114.649792053474,35.5466373866597],[-114.672215155693,35.5157541647721],[-114.645396168451,35.4507608261463],[-114.589584275424,35.3583787306827],[-114.587889840369,35.30476812919],[-114.559583045727,35.2201828714608],[-114.561039964054,35.1743461616313],[-114.572255261053,35.1400677445931],[-114.582616239058,35.1325604694085],[-114.626440825485,35.1339067529872],[-114.6359090842,35.1186557767895],[-114.595631971944,35.0760579746697],[-114.633779872695,35.0418633504303],[-114.621068606189,34.9989144286133],[-115.626197382816,35.7956983148418],[-115.88576934392,36.0012259572723],[-117.160423771838,36.9595941441767],[-117.838686423167,37.457298239715],[-118.417419755966,37.8866767486211],[-119.152450421001,38.4118009590513]]], \"spatialReference\":{\"wkid\":4326}}"; - - JsonFactory factory = new JsonFactory(); - JsonParser parser = factory.createJsonParser(strPolygon1); - parser.nextToken(); - MapGeometry mapGeom = GeometryEngine.jsonToGeometry(parser); - Geometry geom = mapGeom.getGeometry(); - OperatorExportToWkb operatorExport = (OperatorExportToWkb) OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.ExportToWkb); - ByteBuffer byteBuffer = operatorExport.execute(0, geom, null); - byte[] wkb = byteBuffer.array(); - - // WKB -> GEOM -> JSON - OperatorImportFromWkb operatorImport = (OperatorImportFromWkb) OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.ImportFromWkb); - geom = operatorImport.execute(0, Geometry.Type.Polygon, - ByteBuffer.wrap(wkb), null); - // geom = operatorImport.execute(0, Geometry.Type.Polygon, - // byteBuffer); - String outputPolygon1 = GeometryEngine.geometryToJson(-1, geom); - } catch (JsonParseException ex) { - } catch (IOException ex) { - } - + // JSON -> GEOM -> WKB + + String strPolygon1 = "{\"xmin\":-1.1663479012889031E7,\"ymin\":4919777.494405342,\"xmax\":-1.1658587043078788E7,\"ymax\":4924669.464215587,\"spatialReference\":{\"wkid\":102100}}"; + // String strPolygon1 = + // "{\"rings\":[[[-119.152450421001,38.4118009590513],[-119.318825070203,38.5271086243914],[-119.575687062955,38.7029101298904],[-119.889341639399,38.9222515603984],[-119.995254694357,38.9941061536377],[-119.995150114198,39.0634913594691],[-119.994541258334,39.1061318056708],[-119.995527335641,39.1587132866355],[-119.995304181493,39.3115454332125],[-119.996011479298,39.4435009764511],[-119.996165311172,39.7206108077274],[-119.996324660047,41.1775662656441],[-119.993459369715,41.9892049531992],[-119.351692186077,41.9888529749781],[-119.3109421304,41.9891353872811],[-118.185316829038,41.9966370981387],[-117.018864363596,41.9947941808341],[-116.992313337997,41.9947945094663],[-115.947544658193,41.9945994628997],[-115.024862911148,41.996506455953],[-114.269471632824,41.9959242345073],[-114.039072662345,41.9953908974688],[-114.038151248682,40.9976868405942],[-114.038108189376,40.1110466529553],[-114.039844684228,39.9087788600023],[-114.040105338584,39.5386849268845],[-114.044267501155,38.6789958815881],[-114.045090206153,38.5710950539539],[-114.047272999176,38.1376524399918],[-114.047260595159,37.5984784866001],[-114.043939384154,36.9965379371421],[-114.043716435713,36.8418489458647],[-114.037392074194,36.2160228969702],[-114.045105557286,36.1939778840226],[-114.107775185788,36.1210907070504],[-114.12902308363,36.041730493896],[-114.206768869568,36.0172554164834],[-114.233472615347,36.0183310595897],[-114.307587598189,36.0622330993643],[-114.303857056018,36.0871084040611],[-114.316095374696,36.1114380366653],[-114.344233941709,36.1374802520568],[-114.380803116644,36.1509912717765],[-114.443945697733,36.1210532841897],[-114.466613475422,36.1247112590539],[-114.530573568745,36.1550902046725],[-114.598935242024,36.1383354528834],[-114.621610747198,36.1419666834504],[-114.712761724737,36.1051810523675],[-114.728150311069,36.0859627711604],[-114.728966012834,36.0587530361083],[-114.717673567756,36.0367580437018],[-114.736212493583,35.9876483502758],[-114.699275906446,35.9116119537412],[-114.661600122152,35.8804735854242],[-114.662462095522,35.8709599070091],[-114.689867343369,35.8474424944766],[-114.682739704595,35.7647034175617],[-114.688820027649,35.7325957399896],[-114.665091345861,35.6930994107107],[-114.668486064922,35.6563989882404],[-114.654065925137,35.6465840800053],[-114.6398667219,35.6113485698329],[-114.653134321223,35.5848331056108],[-114.649792053474,35.5466373866597],[-114.672215155693,35.5157541647721],[-114.645396168451,35.4507608261463],[-114.589584275424,35.3583787306827],[-114.587889840369,35.30476812919],[-114.559583045727,35.2201828714608],[-114.561039964054,35.1743461616313],[-114.572255261053,35.1400677445931],[-114.582616239058,35.1325604694085],[-114.626440825485,35.1339067529872],[-114.6359090842,35.1186557767895],[-114.595631971944,35.0760579746697],[-114.633779872695,35.0418633504303],[-114.621068606189,34.9989144286133],[-115.626197382816,35.7956983148418],[-115.88576934392,36.0012259572723],[-117.160423771838,36.9595941441767],[-117.838686423167,37.457298239715],[-118.417419755966,37.8866767486211],[-119.152450421001,38.4118009590513]]], \"spatialReference\":{\"wkid\":4326}}"; + + MapGeometry mapGeom = GeometryEngine.jsonToGeometry(strPolygon1); + Geometry geom = mapGeom.getGeometry(); + OperatorExportToWkb operatorExport = (OperatorExportToWkb) OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.ExportToWkb); + ByteBuffer byteBuffer = operatorExport.execute(0, geom, null); + byte[] wkb = byteBuffer.array(); + + // WKB -> GEOM -> JSON + OperatorImportFromWkb operatorImport = (OperatorImportFromWkb) OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.ImportFromWkb); + geom = operatorImport.execute(0, Geometry.Type.Polygon, + ByteBuffer.wrap(wkb), null); + // geom = operatorImport.execute(0, Geometry.Type.Polygon, + // byteBuffer); + String outputPolygon1 = GeometryEngine.geometryToJson(-1, geom); } @Test @@ -62,10 +51,7 @@ public void testWKB2() throws Exception { // "{\"xmin\":-1.16605115291E7,\"ymin\":4925189.941699997,\"xmax\":-1.16567772126E7,\"ymax\":4928658.771399997,\"spatialReference\":{\"wkid\":102100}}"; String strPolygon1 = "{\"rings\" : [ [ [-1.16605115291E7,4925189.941699997], [-1.16567772126E7,4925189.941699997], [-1.16567772126E7,4928658.771399997], [-1.16605115291E7,4928658.771399997], [-1.16605115291E7,4925189.941699997] ] ], \"spatialReference\" : {\"wkid\" : 102100}}"; - JsonFactory factory = new JsonFactory(); - JsonParser parser = factory.createJsonParser(strPolygon1); - parser.nextToken(); - MapGeometry mapGeom = GeometryEngine.jsonToGeometry(parser); + MapGeometry mapGeom = GeometryEngine.jsonToGeometry(strPolygon1); Geometry geom = mapGeom.getGeometry(); // simplifying geom From d6999a9d0bfff7113f331ced7165ab46cf341881 Mon Sep 17 00:00:00 2001 From: Sergey Tolstov Date: Fri, 7 Jul 2017 14:27:29 -0700 Subject: [PATCH 138/196] Sergey/gitattribs (#137) --- .gitattributes | 9 + .../esri/core/geometry/GeometryEngine.java | 1823 +++++------ .../java/com/esri/core/geometry/TestCut.java | 1040 +++---- .../com/esri/core/geometry/TestFailed.java | 128 +- .../com/esri/core/geometry/TestGeodetic.java | 209 +- ...omToJSonExportSRFromWkiOrWkt_CR181369.java | 1130 +++---- .../esri/core/geometry/TestJSonGeometry.java | 95 +- .../TestJSonToGeomFromWkiOrWkt_CR177613.java | 248 +- .../esri/core/geometry/TestJsonParser.java | 1328 ++++---- .../esri/core/geometry/TestSerialization.java | 760 ++--- .../com/esri/core/geometry/TestSimplify.java | 2688 ++++++++--------- .../esri/core/geometry/TestWKBSupport.java | 171 +- 12 files changed, 4821 insertions(+), 4808 deletions(-) create mode 100644 .gitattributes diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000..e1945df3 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,9 @@ +* text=auto +*.java text +*.xml text + +# +# Binary specific files +# +resouces/* binary +*.jar binary diff --git a/src/main/java/com/esri/core/geometry/GeometryEngine.java b/src/main/java/com/esri/core/geometry/GeometryEngine.java index 1339538c..6f729cea 100644 --- a/src/main/java/com/esri/core/geometry/GeometryEngine.java +++ b/src/main/java/com/esri/core/geometry/GeometryEngine.java @@ -1,911 +1,912 @@ -/* - Copyright 1995-2017 Esri - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - - For additional information, contact: - Environmental Systems Research Institute, Inc. - Attn: Contracts Dept - 380 New York Street - Redlands, California, USA 92373 - - email: contracts@esri.com - */ - -package com.esri.core.geometry; - -import java.io.IOException; -import java.nio.ByteBuffer; -import java.nio.ByteOrder; -import java.util.ArrayList; - -import com.fasterxml.jackson.core.JsonParser; - -/** - * Provides services that operate on geometry instances. The methods of GeometryEngine class call corresponding OperatorXXX classes. - * Consider using OperatorXXX classes directly as they often provide more functionality and better performance. For example, some Operators accept - * GeometryCursor class that could be implemented to wrap a feature cursor and make it feed geometries directly into an Operator. - * Also, some operators provide a way to accelerate an operation by using Operator.accelerateGeometry method. - */ -public class GeometryEngine { - - private static OperatorFactoryLocal factory = OperatorFactoryLocal - .getInstance(); - - /** - * Imports the MapGeometry from its JSON representation. M and Z values are - * not imported from JSON representation. - * - * See OperatorImportFromJson. - * - * @param json - * The JSON representation of the geometry (with spatial - * reference). - * @return The MapGeometry instance containing the imported geometry and its - * spatial reference. - */ - public static MapGeometry jsonToGeometry(JsonParser json) { - MapGeometry geom = OperatorImportFromJson.local().execute(Geometry.Type.Unknown, new JsonParserReader(json)); - return geom; - } - - /** - * Imports the MapGeometry from its JSON representation. M and Z values are - * not imported from JSON representation. - * - * See OperatorImportFromJson. - * - * @param json - * The JSON representation of the geometry (with spatial - * reference). - * @return The MapGeometry instance containing the imported geometry and its - * spatial reference. - */ - public static MapGeometry jsonToGeometry(JsonReader json) { - MapGeometry geom = OperatorImportFromJson.local().execute(Geometry.Type.Unknown, json); - return geom; - } - - /** - * Imports the MapGeometry from its JSON representation. M and Z values are - * not imported from JSON representation. - * - * See OperatorImportFromJson. - * - * @param json - * The JSON representation of the geometry (with spatial - * reference). - * @return The MapGeometry instance containing the imported geometry and its - * spatial reference. - * @throws IOException - * @throws JsonParseException - */ - public static MapGeometry jsonToGeometry(String json) { - MapGeometry geom = OperatorImportFromJson.local().execute(Geometry.Type.Unknown, json); - return geom; - } - - /** - * Exports the specified geometry instance to it's JSON representation. - * - * See OperatorExportToJson. - * - * @see GeometryEngine#geometryToJson(SpatialReference spatialiReference, - * Geometry geometry) - * @param wkid - * The spatial reference Well Known ID to be used for the JSON - * representation. - * @param geometry - * The geometry to be exported to JSON. - * @return The JSON representation of the specified Geometry. - */ - public static String geometryToJson(int wkid, Geometry geometry) { - return GeometryEngine.geometryToJson( - wkid > 0 ? SpatialReference.create(wkid) : null, geometry); - } - - /** - * Exports the specified geometry instance to it's JSON representation. M - * and Z values are not imported from JSON representation. - * - * See OperatorExportToJson. - * - * @param spatialReference - * The spatial reference of associated object. - * @param geometry - * The geometry. - * @return The JSON representation of the specified geometry. - */ - public static String geometryToJson(SpatialReference spatialReference, - Geometry geometry) { - OperatorExportToJson exporter = (OperatorExportToJson) factory - .getOperator(Operator.Type.ExportToJson); - - return exporter.execute(spatialReference, geometry); - } - - public static String geometryToGeoJson(Geometry geometry) { - OperatorExportToGeoJson exporter = (OperatorExportToGeoJson) factory - .getOperator(Operator.Type.ExportToGeoJson); - - return exporter.execute(geometry); - } - - /** - * Imports the MapGeometry from its JSON representation. M and Z values are - * not imported from JSON representation. - * - * See OperatorImportFromJson. - * - * @param json - * The JSON representation of the geometry (with spatial - * reference). - * @return The MapGeometry instance containing the imported geometry and its - * spatial reference. - * @throws IOException - * @throws JsonParseException - */ - public static MapGeometry geoJsonToGeometry(String json, int importFlags, Geometry.Type type) { - MapGeometry geom = OperatorImportFromGeoJson.local().execute(importFlags, type, json, null); - return geom; - } - - /** - * Exports the specified geometry instance to its GeoJSON representation. - * - * See OperatorExportToGeoJson. - * - * @see GeometryEngine#geometryToGeoJson(SpatialReference spatialReference, - * Geometry geometry) - * - * @param wkid - * The spatial reference Well Known ID to be used for the GeoJSON - * representation. - * @param geometry - * The geometry to be exported to GeoJSON. - * @return The GeoJSON representation of the specified geometry. - */ - public static String geometryToGeoJson(int wkid, Geometry geometry) { - return GeometryEngine.geometryToGeoJson(wkid > 0 ? SpatialReference.create(wkid) : null, geometry); - } - - /** - * Exports the specified geometry instance to it's JSON representation. - * - * See OperatorImportFromGeoJson. - * - * @param spatialReference - * The spatial reference of associated object. - * @param geometry - * The geometry. - * @return The GeoJSON representation of the specified geometry. - */ - public static String geometryToGeoJson(SpatialReference spatialReference, Geometry geometry) { - OperatorExportToGeoJson exporter = (OperatorExportToGeoJson) factory.getOperator(Operator.Type.ExportToGeoJson); - - return exporter.execute(spatialReference, geometry); - } - - /** - * Imports geometry from the ESRI shape file format. - * - * See OperatorImportFromESRIShape. - * - * @param esriShapeBuffer - * The buffer containing geometry in the ESRI shape file format. - * @param geometryType - * The required type of the Geometry to be imported. Use - * Geometry.Type.Unknown if the geometry type needs to be - * determined from the buffer content. - * @return The geometry or null if the buffer contains null shape. - * @throws GeometryException - * when the geometryType is not Geometry.Type.Unknown and the - * buffer contains geometry that cannot be converted to the - * given geometryType. or the buffer is corrupt. Another - * exception possible is IllegalArgumentsException. - */ - public static Geometry geometryFromEsriShape(byte[] esriShapeBuffer, - Geometry.Type geometryType) { - OperatorImportFromESRIShape op = (OperatorImportFromESRIShape) factory - .getOperator(Operator.Type.ImportFromESRIShape); - return op - .execute( - ShapeImportFlags.ShapeImportNonTrusted, - geometryType, - ByteBuffer.wrap(esriShapeBuffer).order( - ByteOrder.LITTLE_ENDIAN)); - } - - /** - * Exports geometry to the ESRI shape file format. - * - * See OperatorExportToESRIShape. - * - * @param geometry - * The geometry to export. (null value is not allowed) - * @return Array containing the exported ESRI shape file. - */ - public static byte[] geometryToEsriShape(Geometry geometry) { - if (geometry == null) - throw new IllegalArgumentException(); - OperatorExportToESRIShape op = (OperatorExportToESRIShape) factory - .getOperator(Operator.Type.ExportToESRIShape); - return op.execute(0, geometry).array(); - } - - /** - * Imports a geometry from a WKT string. - * - * See OperatorImportFromWkt. - * - * @param wkt The string containing the geometry in WKT format. - * @param importFlags Use the {@link WktImportFlags} interface. - * @param geometryType The required type of the Geometry to be imported. Use Geometry.Type.Unknown if the geometry type needs to be determined from the WKT context. - * @return The geometry. - * @throws GeometryException when the geometryType is not Geometry.Type.Unknown and the WKT contains a geometry that cannot be converted to the given geometryType. - * @throws IllegalArgument exception if an error is found while parsing the WKT string. - */ - public static Geometry geometryFromWkt(String wkt, int importFlags, - Geometry.Type geometryType) { - OperatorImportFromWkt op = (OperatorImportFromWkt) factory - .getOperator(Operator.Type.ImportFromWkt); - return op.execute(importFlags, geometryType, wkt, null); - } - - /** - * Exports a geometry to a string in WKT format. - * - * See OperatorExportToWkt. - * - * @param geometry The geometry to export. (null value is not allowed) - * @param exportFlags Use the {@link WktExportFlags} interface. - * @return A String containing the exported geometry in WKT format. - */ - public static String geometryToWkt(Geometry geometry, int exportFlags) { - OperatorExportToWkt op = (OperatorExportToWkt) factory - .getOperator(Operator.Type.ExportToWkt); - return op.execute(exportFlags, geometry, null); - } - - /** - * Constructs a new geometry by union an array of geometries. All inputs - * must be of the same type of geometries and share one spatial reference. - * - * See OperatorUnion. - * - * @param geometries - * The geometries to union. - * @param spatialReference - * The spatial reference of the geometries. - * @return The geometry object representing the resultant union. - */ - public static Geometry union(Geometry[] geometries, - SpatialReference spatialReference) { - OperatorUnion op = (OperatorUnion) factory - .getOperator(Operator.Type.Union); - - SimpleGeometryCursor inputGeometries = new SimpleGeometryCursor( - geometries); - GeometryCursor result = op.execute(inputGeometries, spatialReference, - null); - return result.next(); - } - - /** - * Creates the difference of two geometries. The dimension of geometry2 has - * to be equal to or greater than that of geometry1. - * - * See OperatorDifference. - * - * @param geometry1 - * The geometry being subtracted. - * @param substractor - * The geometry object to subtract from. - * @param spatialReference - * The spatial reference of the geometries. - * @return The geometry of the differences. - */ - public static Geometry difference(Geometry geometry1, Geometry substractor, - SpatialReference spatialReference) { - OperatorDifference op = (OperatorDifference) factory - .getOperator(Operator.Type.Difference); - Geometry result = op.execute(geometry1, substractor, spatialReference, - null); - return result; - } - - /** - * Creates the symmetric difference of two geometries. - * - * See OperatorSymmetricDifference. - * - * @param leftGeometry - * is one of the Geometry instances in the XOR operation. - * @param rightGeometry - * is one of the Geometry instances in the XOR operation. - * @param spatialReference - * The spatial reference of the geometries. - * @return Returns the result of the symmetric difference. - */ - public static Geometry symmetricDifference(Geometry leftGeometry, - Geometry rightGeometry, SpatialReference spatialReference) { - OperatorSymmetricDifference op = (OperatorSymmetricDifference) factory - .getOperator(Operator.Type.SymmetricDifference); - Geometry result = op.execute(leftGeometry, rightGeometry, - spatialReference, null); - return result; - } - - /** - * Indicates if two geometries are equal. - * - * See OperatorEquals. - * - * @param geometry1 - * Geometry. - * @param geometry2 - * Geometry. - * @param spatialReference - * The spatial reference of the geometries. - * @return TRUE if both geometry objects are equal. - */ - public static boolean equals(Geometry geometry1, Geometry geometry2, - SpatialReference spatialReference) { - OperatorEquals op = (OperatorEquals) factory - .getOperator(Operator.Type.Equals); - boolean result = op.execute(geometry1, geometry2, spatialReference, - null); - return result; - } - - /** - * See OperatorDisjoint. - * - */ - public static boolean disjoint(Geometry geometry1, Geometry geometry2, - SpatialReference spatialReference) { - OperatorDisjoint op = (OperatorDisjoint) factory - .getOperator(Operator.Type.Disjoint); - boolean result = op.execute(geometry1, geometry2, spatialReference, - null); - return result; - } - - /** - * Constructs the set-theoretic intersection between an array of geometries - * and another geometry. - * - * See OperatorIntersection (also for dimension specific intersection). - * - * @param inputGeometries - * An array of geometry objects. - * @param geometry - * The geometry object. - * @return Any array of geometry objects showing the intersection. - */ - static Geometry[] intersect(Geometry[] inputGeometries, Geometry geometry, - SpatialReference spatialReference) { - OperatorIntersection op = (OperatorIntersection) factory - .getOperator(Operator.Type.Intersection); - SimpleGeometryCursor inputGeometriesCursor = new SimpleGeometryCursor( - inputGeometries); - SimpleGeometryCursor intersectorCursor = new SimpleGeometryCursor( - geometry); - GeometryCursor result = op.execute(inputGeometriesCursor, - intersectorCursor, spatialReference, null); - - ArrayList resultGeoms = new ArrayList(); - Geometry g; - while ((g = result.next()) != null) { - resultGeoms.add(g); - } - - Geometry[] resultarr = resultGeoms.toArray(new Geometry[0]); - return resultarr; - } - - /** - * Creates a geometry through intersection between two geometries. - * - * See OperatorIntersection. - * - * @param geometry1 - * The first geometry. - * @param intersector - * The geometry to intersect the first geometry. - * @param spatialReference - * The spatial reference of the geometries. - * @return The geometry created through intersection. - */ - public static Geometry intersect(Geometry geometry1, Geometry intersector, - SpatialReference spatialReference) { - OperatorIntersection op = (OperatorIntersection) factory - .getOperator(Operator.Type.Intersection); - Geometry result = op.execute(geometry1, intersector, spatialReference, - null); - return result; - } - - /** - * Indicates if one geometry is within another geometry. - * - * See OperatorWithin. - * - * @param geometry1 - * The base geometry that is tested for within relationship to - * the other geometry. - * @param geometry2 - * The comparison geometry that is tested for the contains - * relationship to the other geometry. - * @param spatialReference - * The spatial reference of the geometries. - * @return TRUE if the first geometry is within the other geometry. - */ - public static boolean within(Geometry geometry1, Geometry geometry2, - SpatialReference spatialReference) { - OperatorWithin op = (OperatorWithin) factory - .getOperator(Operator.Type.Within); - boolean result = op.execute(geometry1, geometry2, spatialReference, - null); - return result; - } - - /** - * Indicates if one geometry contains another geometry. - * - * See OperatorContains. - * - * @param geometry1 - * The geometry that is tested for the contains relationship to - * the other geometry.. - * @param geometry2 - * The geometry that is tested for within relationship to the - * other geometry. - * @param spatialReference - * The spatial reference of the geometries. - * @return TRUE if geometry1 contains geometry2. - */ - public static boolean contains(Geometry geometry1, Geometry geometry2, - SpatialReference spatialReference) { - OperatorContains op = (OperatorContains) factory - .getOperator(Operator.Type.Contains); - boolean result = op.execute(geometry1, geometry2, spatialReference, - null); - return result; - } - - /** - * Indicates if one geometry crosses another geometry. - * - * See OperatorCrosses. - * - * @param geometry1 - * The geometry to cross. - * @param geometry2 - * The geometry being crossed. - * @param spatialReference - * The spatial reference of the geometries. - * @return TRUE if geometry1 crosses geometry2. - */ - public static boolean crosses(Geometry geometry1, Geometry geometry2, - SpatialReference spatialReference) { - OperatorCrosses op = (OperatorCrosses) factory - .getOperator(Operator.Type.Crosses); - boolean result = op.execute(geometry1, geometry2, spatialReference, - null); - return result; - } - - /** - * Indicates if one geometry touches another geometry. - * - * See OperatorTouches. - * - * @param geometry1 - * The geometry to touch. - * @param geometry2 - * The geometry to be touched. - * @param spatialReference - * The spatial reference of the geometries. - * @return TRUE if geometry1 touches geometry2. - */ - public static boolean touches(Geometry geometry1, Geometry geometry2, - SpatialReference spatialReference) { - OperatorTouches op = (OperatorTouches) factory - .getOperator(Operator.Type.Touches); - boolean result = op.execute(geometry1, geometry2, spatialReference, - null); - return result; - } - - /** - * Indicates if one geometry overlaps another geometry. - * - * See OperatorOverlaps. - * - * @param geometry1 - * The geometry to overlap. - * @param geometry2 - * The geometry to be overlapped. - * @param spatialReference - * The spatial reference of the geometries. - * @return TRUE if geometry1 overlaps geometry2. - */ - public static boolean overlaps(Geometry geometry1, Geometry geometry2, - SpatialReference spatialReference) { - OperatorOverlaps op = (OperatorOverlaps) factory - .getOperator(Operator.Type.Overlaps); - boolean result = op.execute(geometry1, geometry2, spatialReference, - null); - return result; - } - - /** - * Indicates if the given relation holds for the two geometries. - * - * See OperatorRelate. - * - * @param geometry1 - * The first geometry for the relation. - * @param geometry2 - * The second geometry for the relation. - * @param spatialReference - * The spatial reference of the geometries. - * @param relation - * The DE-9IM relation. - * @return TRUE if the given relation holds between geometry1 and geometry2. - */ - public static boolean relate(Geometry geometry1, Geometry geometry2, - SpatialReference spatialReference, String relation) { - OperatorRelate op = (OperatorRelate) factory - .getOperator(Operator.Type.Relate); - boolean result = op.execute(geometry1, geometry2, spatialReference, - relation, null); - return result; - } - - /** - * Calculates the 2D planar distance between two geometries. - * - * See OperatorDistance. - * - * @param geometry1 - * Geometry. - * @param geometry2 - * Geometry. - * @param spatialReference - * The spatial reference of the geometries. This parameter is not - * used and can be null. - * @return The distance between the two geometries. - */ - public static double distance(Geometry geometry1, Geometry geometry2, - SpatialReference spatialReference) { - OperatorDistance op = (OperatorDistance) factory - .getOperator(Operator.Type.Distance); - double result = op.execute(geometry1, geometry2, null); - return result; - } - - /** - * Calculates the clipped geometry from a target geometry using an envelope. - * - * See OperatorClip. - * - * @param geometry - * The geometry to be clipped. - * @param envelope - * The envelope used to clip. - * @param spatialReference - * The spatial reference of the geometries. - * @return The geometry created by clipping. - */ - public static Geometry clip(Geometry geometry, Envelope envelope, - SpatialReference spatialReference) { - OperatorClip op = (OperatorClip) factory - .getOperator(Operator.Type.Clip); - Geometry result = op.execute(geometry, Envelope2D.construct( - envelope.getXMin(), envelope.getYMin(), envelope.getXMax(), - envelope.getYMax()), spatialReference, null); - return result; - } - - /** - * Calculates the cut geometry from a target geometry using a polyline. For - * Polylines, all left cuts will be grouped together in the first Geometry, - * Right cuts and coincident cuts are grouped in the second Geometry, and - * each undefined cut, along with any uncut parts, are output as separate - * Polylines. For Polygons, all left cuts are grouped in the first Polygon, - * all right cuts are in the second Polygon, and each undefined cut, along - * with any left-over parts after cutting, are output as a separate Polygon. - * If there were no cuts then the array will be empty. An undefined cut will - * only be produced if a left cut or right cut was produced, and there was a - * part left over after cutting or a cut is bounded to the left and right of - * the cutter. - * - * See OperatorCut. - * - * @param cuttee - * The geometry to be cut. - * @param cutter - * The polyline to cut the geometry. - * @param spatialReference - * The spatial reference of the geometries. - * @return An array of geometries created from cutting. - */ - public static Geometry[] cut(Geometry cuttee, Polyline cutter, - SpatialReference spatialReference) { - if (cuttee == null || cutter == null) - return null; - - OperatorCut op = (OperatorCut) factory.getOperator(Operator.Type.Cut); - GeometryCursor cursor = op.execute(true, cuttee, cutter, - spatialReference, null); - ArrayList cutsList = new ArrayList(); - - Geometry geometry; - while ((geometry = cursor.next()) != null) { - if (!geometry.isEmpty()) { - cutsList.add(geometry); - } - } - - return cutsList.toArray(new Geometry[0]); - } - /** - * Calculates a buffer polygon for each geometry at each of the - * corresponding specified distances. It is assumed that all geometries have - * the same spatial reference. There is an option to union the - * returned geometries. - * - * See OperatorBuffer. - * - * @param geometries An array of geometries to be buffered. - * @param spatialReference The spatial reference of the geometries. - * @param distances The corresponding distances for the input geometries to be buffered. - * @param toUnionResults TRUE if all geometries buffered at a given distance are to be unioned into a single polygon. - * @return The buffer of the geometries. - */ - public static Polygon[] buffer(Geometry[] geometries, - SpatialReference spatialReference, double[] distances, - boolean toUnionResults) { - // initially assume distances are in unit of spatial reference - double[] bufferDistances = distances; - - OperatorBuffer op = (OperatorBuffer) factory - .getOperator(Operator.Type.Buffer); - - if (toUnionResults) { - SimpleGeometryCursor inputGeometriesCursor = new SimpleGeometryCursor( - geometries); - GeometryCursor result = op.execute(inputGeometriesCursor, - spatialReference, bufferDistances, toUnionResults, null); - - ArrayList resultGeoms = new ArrayList(); - Geometry g; - while ((g = result.next()) != null) { - resultGeoms.add((Polygon) g); - } - Polygon[] buffers = resultGeoms.toArray(new Polygon[0]); - return buffers; - } else { - Polygon[] buffers = new Polygon[geometries.length]; - for (int i = 0; i < geometries.length; i++) { - buffers[i] = (Polygon) op.execute(geometries[i], - spatialReference, bufferDistances[i], null); - } - return buffers; - } - } - - /** - * Calculates a buffer polygon of the geometry as specified by the - * distance input. The buffer is implemented in the xy-plane. - * - * See OperatorBuffer - * - * @param geometry Geometry to be buffered. - * @param spatialReference The spatial reference of the geometry. - * @param distance The specified distance for buffer. Same units as the spatial reference. - * @return The buffer polygon at the specified distances. - */ - public static Polygon buffer(Geometry geometry, - SpatialReference spatialReference, double distance) { - double bufferDistance = distance; - - OperatorBuffer op = (OperatorBuffer) factory - .getOperator(Operator.Type.Buffer); - Geometry result = op.execute(geometry, spatialReference, - bufferDistance, null); - return (Polygon) result; - } - - /** - * Calculates the convex hull geometry. - * - * See OperatorConvexHull. - * - * @param geometry The input geometry. - * @return Returns the convex hull. - * - * For a Point - returns the same point. For an Envelope - - * returns the same envelope. For a MultiPoint - If the point - * count is one, returns the same multipoint. If the point count - * is two, returns a polyline of the points. Otherwise computes - * and returns the convex hull polygon. For a Segment - returns a - * polyline consisting of the segment. For a Polyline - If - * consists of only one segment, returns the same polyline. - * Otherwise computes and returns the convex hull polygon. For a - * Polygon - If more than one path, or if the path isn't already - * convex, computes and returns the convex hull polygon. - * Otherwise returns the same polygon. - */ - public static Geometry convexHull(Geometry geometry) { - OperatorConvexHull op = (OperatorConvexHull) factory - .getOperator(Operator.Type.ConvexHull); - return op.execute(geometry, null); - } - - /** - * Calculates the convex hull. - * - * See OperatorConvexHull - * - * @param geometries - * The input geometry array. - * @param b_merge - * Put true if you want the convex hull of all the geometries in - * the array combined. Put false if you want the convex hull of - * each geometry in the array individually. - * @return Returns an array of convex hulls. If b_merge is true, the result - * will be a one element array consisting of the merged convex hull. - */ - public static Geometry[] convexHull(Geometry[] geometries, boolean b_merge) { - OperatorConvexHull op = (OperatorConvexHull) factory - .getOperator(Operator.Type.ConvexHull); - SimpleGeometryCursor simple_cursor = new SimpleGeometryCursor( - geometries); - GeometryCursor cursor = op.execute(simple_cursor, b_merge, null); - - ArrayList resultGeoms = new ArrayList(); - Geometry g; - while ((g = cursor.next()) != null) { - resultGeoms.add(g); - } - - Geometry[] output = new Geometry[resultGeoms.size()]; - - for (int i = 0; i < resultGeoms.size(); i++) - output[i] = resultGeoms.get(i); - - return output; - } - - /** - * Finds the coordinate of the geometry which is closest to the specified - * point. - * - * See OperatorProximity2D. - * - * @param inputPoint - * The point to find the nearest coordinate in the geometry for. - * @param geometry - * The geometry to consider. - * @return Proximity2DResult containing the nearest coordinate. - */ - public static Proximity2DResult getNearestCoordinate(Geometry geometry, - Point inputPoint, boolean bTestPolygonInterior) { - - OperatorProximity2D proximity = (OperatorProximity2D) factory - .getOperator(com.esri.core.geometry.Operator.Type.Proximity2D); - Proximity2DResult result = proximity.getNearestCoordinate(geometry, - inputPoint, bTestPolygonInterior); - return result; - } - - /** - * Finds nearest vertex on the geometry which is closed to the specified - * point. - * - * See OperatorProximity2D. - * - * @param inputPoint - * The point to find the nearest vertex of the geometry for. - * @param geometry - * The geometry to consider. - * @return Proximity2DResult containing the nearest vertex. - */ - public static Proximity2DResult getNearestVertex(Geometry geometry, - Point inputPoint) { - OperatorProximity2D proximity = (OperatorProximity2D) factory - .getOperator(com.esri.core.geometry.Operator.Type.Proximity2D); - Proximity2DResult result = proximity.getNearestVertex(geometry, - inputPoint); - return result; - } - - /** - * Finds all vertices in the given distance from the specified point, sorted - * from the closest to the furthest. - * - * See OperatorProximity2D. - * - * @param inputPoint - * The point to start from. - * @param geometry - * The geometry to consider. - * @param searchRadius - * The search radius. - * @param maxVertexCountToReturn - * The maximum number number of vertices to return. - * @return Proximity2DResult containing the array of nearest vertices. - */ - public static Proximity2DResult[] getNearestVertices(Geometry geometry, - Point inputPoint, double searchRadius, int maxVertexCountToReturn) { - OperatorProximity2D proximity = (OperatorProximity2D) factory - .getOperator(com.esri.core.geometry.Operator.Type.Proximity2D); - - Proximity2DResult[] results = proximity.getNearestVertices(geometry, - inputPoint, searchRadius, maxVertexCountToReturn); - - return results; - } - - /** - * Performs the simplify operation on the geometry. - * - * See OperatorSimplify and See OperatorSimplifyOGC. - * - * @param geometry - * The geometry to be simplified. - * @param spatialReference - * The spatial reference of the geometry to be simplified. - * @return The simplified geometry. - */ - public static Geometry simplify(Geometry geometry, - SpatialReference spatialReference) { - OperatorSimplify op = (OperatorSimplify) factory - .getOperator(Operator.Type.Simplify); - Geometry result = op.execute(geometry, spatialReference, false, null); - return result; - } - - /** - * Checks if the Geometry is simple. - * - * See OperatorSimplify. - * - * @param geometry - * The geometry to be checked. - * @param spatialReference - * The spatial reference of the geometry. - * @return TRUE if the geometry is simple. - */ - static boolean isSimple(Geometry geometry, SpatialReference spatialReference) { - OperatorSimplify op = (OperatorSimplify) factory - .getOperator(Operator.Type.Simplify); - boolean result = op.isSimpleAsFeature(geometry, spatialReference, null); - return result; - } - - /** - * A geodesic distance is the shortest distance between any two points on the earth's surface when the earth's - * surface is approximated by a spheroid. The function returns the shortest distance between two points on the - * WGS84 spheroid. - * @param ptFrom The "from" point: long, lat in degrees. - * @param ptTo The "to" point: long, lat in degrees. - * @return The geodesic distance between two points in meters. - */ - public static double geodesicDistanceOnWGS84(Point ptFrom, Point ptTo) { - return SpatialReferenceImpl.geodesicDistanceOnWGS84Impl(ptFrom, ptTo); - } -} +/* + Copyright 1995-2017 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ + +package com.esri.core.geometry; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.util.ArrayList; + +import com.fasterxml.jackson.core.JsonParser; + +/** + * Provides services that operate on geometry instances. The methods of GeometryEngine class call corresponding OperatorXXX classes. + * Consider using OperatorXXX classes directly as they often provide more functionality and better performance. For example, some Operators accept + * GeometryCursor class that could be implemented to wrap a feature cursor and make it feed geometries directly into an Operator. + * Also, some operators provide a way to accelerate an operation by using Operator.accelerateGeometry method. + */ +public class GeometryEngine { + + private static OperatorFactoryLocal factory = OperatorFactoryLocal + .getInstance(); + + + /** + * Imports the MapGeometry from its JSON representation. M and Z values are + * not imported from JSON representation. + * + * See OperatorImportFromJson. + * + * @param json + * The JSON representation of the geometry (with spatial + * reference). + * @return The MapGeometry instance containing the imported geometry and its + * spatial reference. + */ + public static MapGeometry jsonToGeometry(JsonParser json) { + MapGeometry geom = OperatorImportFromJson.local().execute(Geometry.Type.Unknown, new JsonParserReader(json)); + return geom; + } + + /** + * Imports the MapGeometry from its JSON representation. M and Z values are + * not imported from JSON representation. + * + * See OperatorImportFromJson. + * + * @param json + * The JSON representation of the geometry (with spatial + * reference). + * @return The MapGeometry instance containing the imported geometry and its + * spatial reference. + */ + public static MapGeometry jsonToGeometry(JsonReader json) { + MapGeometry geom = OperatorImportFromJson.local().execute(Geometry.Type.Unknown, json); + return geom; + } + + /** + * Imports the MapGeometry from its JSON representation. M and Z values are + * not imported from JSON representation. + * + * See OperatorImportFromJson. + * + * @param json + * The JSON representation of the geometry (with spatial + * reference). + * @return The MapGeometry instance containing the imported geometry and its + * spatial reference. + * @throws IOException + * @throws JsonParseException + */ + public static MapGeometry jsonToGeometry(String json) { + MapGeometry geom = OperatorImportFromJson.local().execute(Geometry.Type.Unknown, json); + return geom; + } + + /** + * Exports the specified geometry instance to it's JSON representation. + * + * See OperatorExportToJson. + * + * @see GeometryEngine#geometryToJson(SpatialReference spatialiReference, + * Geometry geometry) + * @param wkid + * The spatial reference Well Known ID to be used for the JSON + * representation. + * @param geometry + * The geometry to be exported to JSON. + * @return The JSON representation of the specified Geometry. + */ + public static String geometryToJson(int wkid, Geometry geometry) { + return GeometryEngine.geometryToJson( + wkid > 0 ? SpatialReference.create(wkid) : null, geometry); + } + + /** + * Exports the specified geometry instance to it's JSON representation. M + * and Z values are not imported from JSON representation. + * + * See OperatorExportToJson. + * + * @param spatialReference + * The spatial reference of associated object. + * @param geometry + * The geometry. + * @return The JSON representation of the specified geometry. + */ + public static String geometryToJson(SpatialReference spatialReference, + Geometry geometry) { + OperatorExportToJson exporter = (OperatorExportToJson) factory + .getOperator(Operator.Type.ExportToJson); + + return exporter.execute(spatialReference, geometry); + } + + public static String geometryToGeoJson(Geometry geometry) { + OperatorExportToGeoJson exporter = (OperatorExportToGeoJson) factory + .getOperator(Operator.Type.ExportToGeoJson); + + return exporter.execute(geometry); + } + + /** + * Imports the MapGeometry from its JSON representation. M and Z values are + * not imported from JSON representation. + * + * See OperatorImportFromJson. + * + * @param json + * The JSON representation of the geometry (with spatial + * reference). + * @return The MapGeometry instance containing the imported geometry and its + * spatial reference. + * @throws IOException + * @throws JsonParseException + */ + public static MapGeometry geoJsonToGeometry(String json, int importFlags, Geometry.Type type) { + MapGeometry geom = OperatorImportFromGeoJson.local().execute(importFlags, type, json, null); + return geom; + } + + /** + * Exports the specified geometry instance to its GeoJSON representation. + * + * See OperatorExportToGeoJson. + * + * @see GeometryEngine#geometryToGeoJson(SpatialReference spatialReference, + * Geometry geometry) + * + * @param wkid + * The spatial reference Well Known ID to be used for the GeoJSON + * representation. + * @param geometry + * The geometry to be exported to GeoJSON. + * @return The GeoJSON representation of the specified geometry. + */ + public static String geometryToGeoJson(int wkid, Geometry geometry) { + return GeometryEngine.geometryToGeoJson(wkid > 0 ? SpatialReference.create(wkid) : null, geometry); + } + + /** + * Exports the specified geometry instance to it's JSON representation. + * + * See OperatorImportFromGeoJson. + * + * @param spatialReference + * The spatial reference of associated object. + * @param geometry + * The geometry. + * @return The GeoJSON representation of the specified geometry. + */ + public static String geometryToGeoJson(SpatialReference spatialReference, Geometry geometry) { + OperatorExportToGeoJson exporter = (OperatorExportToGeoJson) factory.getOperator(Operator.Type.ExportToGeoJson); + + return exporter.execute(spatialReference, geometry); + } + + /** + * Imports geometry from the ESRI shape file format. + * + * See OperatorImportFromESRIShape. + * + * @param esriShapeBuffer + * The buffer containing geometry in the ESRI shape file format. + * @param geometryType + * The required type of the Geometry to be imported. Use + * Geometry.Type.Unknown if the geometry type needs to be + * determined from the buffer content. + * @return The geometry or null if the buffer contains null shape. + * @throws GeometryException + * when the geometryType is not Geometry.Type.Unknown and the + * buffer contains geometry that cannot be converted to the + * given geometryType. or the buffer is corrupt. Another + * exception possible is IllegalArgumentsException. + */ + public static Geometry geometryFromEsriShape(byte[] esriShapeBuffer, + Geometry.Type geometryType) { + OperatorImportFromESRIShape op = (OperatorImportFromESRIShape) factory + .getOperator(Operator.Type.ImportFromESRIShape); + return op + .execute( + ShapeImportFlags.ShapeImportNonTrusted, + geometryType, + ByteBuffer.wrap(esriShapeBuffer).order( + ByteOrder.LITTLE_ENDIAN)); + } + + /** + * Exports geometry to the ESRI shape file format. + * + * See OperatorExportToESRIShape. + * + * @param geometry + * The geometry to export. (null value is not allowed) + * @return Array containing the exported ESRI shape file. + */ + public static byte[] geometryToEsriShape(Geometry geometry) { + if (geometry == null) + throw new IllegalArgumentException(); + OperatorExportToESRIShape op = (OperatorExportToESRIShape) factory + .getOperator(Operator.Type.ExportToESRIShape); + return op.execute(0, geometry).array(); + } + + /** + * Imports a geometry from a WKT string. + * + * See OperatorImportFromWkt. + * + * @param wkt The string containing the geometry in WKT format. + * @param importFlags Use the {@link WktImportFlags} interface. + * @param geometryType The required type of the Geometry to be imported. Use Geometry.Type.Unknown if the geometry type needs to be determined from the WKT context. + * @return The geometry. + * @throws GeometryException when the geometryType is not Geometry.Type.Unknown and the WKT contains a geometry that cannot be converted to the given geometryType. + * @throws IllegalArgument exception if an error is found while parsing the WKT string. + */ + public static Geometry geometryFromWkt(String wkt, int importFlags, + Geometry.Type geometryType) { + OperatorImportFromWkt op = (OperatorImportFromWkt) factory + .getOperator(Operator.Type.ImportFromWkt); + return op.execute(importFlags, geometryType, wkt, null); + } + + /** + * Exports a geometry to a string in WKT format. + * + * See OperatorExportToWkt. + * + * @param geometry The geometry to export. (null value is not allowed) + * @param exportFlags Use the {@link WktExportFlags} interface. + * @return A String containing the exported geometry in WKT format. + */ + public static String geometryToWkt(Geometry geometry, int exportFlags) { + OperatorExportToWkt op = (OperatorExportToWkt) factory + .getOperator(Operator.Type.ExportToWkt); + return op.execute(exportFlags, geometry, null); + } + + /** + * Constructs a new geometry by union an array of geometries. All inputs + * must be of the same type of geometries and share one spatial reference. + * + * See OperatorUnion. + * + * @param geometries + * The geometries to union. + * @param spatialReference + * The spatial reference of the geometries. + * @return The geometry object representing the resultant union. + */ + public static Geometry union(Geometry[] geometries, + SpatialReference spatialReference) { + OperatorUnion op = (OperatorUnion) factory + .getOperator(Operator.Type.Union); + + SimpleGeometryCursor inputGeometries = new SimpleGeometryCursor( + geometries); + GeometryCursor result = op.execute(inputGeometries, spatialReference, + null); + return result.next(); + } + + /** + * Creates the difference of two geometries. The dimension of geometry2 has + * to be equal to or greater than that of geometry1. + * + * See OperatorDifference. + * + * @param geometry1 + * The geometry being subtracted. + * @param substractor + * The geometry object to subtract from. + * @param spatialReference + * The spatial reference of the geometries. + * @return The geometry of the differences. + */ + public static Geometry difference(Geometry geometry1, Geometry substractor, + SpatialReference spatialReference) { + OperatorDifference op = (OperatorDifference) factory + .getOperator(Operator.Type.Difference); + Geometry result = op.execute(geometry1, substractor, spatialReference, + null); + return result; + } + + /** + * Creates the symmetric difference of two geometries. + * + * See OperatorSymmetricDifference. + * + * @param leftGeometry + * is one of the Geometry instances in the XOR operation. + * @param rightGeometry + * is one of the Geometry instances in the XOR operation. + * @param spatialReference + * The spatial reference of the geometries. + * @return Returns the result of the symmetric difference. + */ + public static Geometry symmetricDifference(Geometry leftGeometry, + Geometry rightGeometry, SpatialReference spatialReference) { + OperatorSymmetricDifference op = (OperatorSymmetricDifference) factory + .getOperator(Operator.Type.SymmetricDifference); + Geometry result = op.execute(leftGeometry, rightGeometry, + spatialReference, null); + return result; + } + + /** + * Indicates if two geometries are equal. + * + * See OperatorEquals. + * + * @param geometry1 + * Geometry. + * @param geometry2 + * Geometry. + * @param spatialReference + * The spatial reference of the geometries. + * @return TRUE if both geometry objects are equal. + */ + public static boolean equals(Geometry geometry1, Geometry geometry2, + SpatialReference spatialReference) { + OperatorEquals op = (OperatorEquals) factory + .getOperator(Operator.Type.Equals); + boolean result = op.execute(geometry1, geometry2, spatialReference, + null); + return result; + } + + /** + * See OperatorDisjoint. + * + */ + public static boolean disjoint(Geometry geometry1, Geometry geometry2, + SpatialReference spatialReference) { + OperatorDisjoint op = (OperatorDisjoint) factory + .getOperator(Operator.Type.Disjoint); + boolean result = op.execute(geometry1, geometry2, spatialReference, + null); + return result; + } + + /** + * Constructs the set-theoretic intersection between an array of geometries + * and another geometry. + * + * See OperatorIntersection (also for dimension specific intersection). + * + * @param inputGeometries + * An array of geometry objects. + * @param geometry + * The geometry object. + * @return Any array of geometry objects showing the intersection. + */ + static Geometry[] intersect(Geometry[] inputGeometries, Geometry geometry, + SpatialReference spatialReference) { + OperatorIntersection op = (OperatorIntersection) factory + .getOperator(Operator.Type.Intersection); + SimpleGeometryCursor inputGeometriesCursor = new SimpleGeometryCursor( + inputGeometries); + SimpleGeometryCursor intersectorCursor = new SimpleGeometryCursor( + geometry); + GeometryCursor result = op.execute(inputGeometriesCursor, + intersectorCursor, spatialReference, null); + + ArrayList resultGeoms = new ArrayList(); + Geometry g; + while ((g = result.next()) != null) { + resultGeoms.add(g); + } + + Geometry[] resultarr = resultGeoms.toArray(new Geometry[0]); + return resultarr; + } + + /** + * Creates a geometry through intersection between two geometries. + * + * See OperatorIntersection. + * + * @param geometry1 + * The first geometry. + * @param intersector + * The geometry to intersect the first geometry. + * @param spatialReference + * The spatial reference of the geometries. + * @return The geometry created through intersection. + */ + public static Geometry intersect(Geometry geometry1, Geometry intersector, + SpatialReference spatialReference) { + OperatorIntersection op = (OperatorIntersection) factory + .getOperator(Operator.Type.Intersection); + Geometry result = op.execute(geometry1, intersector, spatialReference, + null); + return result; + } + + /** + * Indicates if one geometry is within another geometry. + * + * See OperatorWithin. + * + * @param geometry1 + * The base geometry that is tested for within relationship to + * the other geometry. + * @param geometry2 + * The comparison geometry that is tested for the contains + * relationship to the other geometry. + * @param spatialReference + * The spatial reference of the geometries. + * @return TRUE if the first geometry is within the other geometry. + */ + public static boolean within(Geometry geometry1, Geometry geometry2, + SpatialReference spatialReference) { + OperatorWithin op = (OperatorWithin) factory + .getOperator(Operator.Type.Within); + boolean result = op.execute(geometry1, geometry2, spatialReference, + null); + return result; + } + + /** + * Indicates if one geometry contains another geometry. + * + * See OperatorContains. + * + * @param geometry1 + * The geometry that is tested for the contains relationship to + * the other geometry.. + * @param geometry2 + * The geometry that is tested for within relationship to the + * other geometry. + * @param spatialReference + * The spatial reference of the geometries. + * @return TRUE if geometry1 contains geometry2. + */ + public static boolean contains(Geometry geometry1, Geometry geometry2, + SpatialReference spatialReference) { + OperatorContains op = (OperatorContains) factory + .getOperator(Operator.Type.Contains); + boolean result = op.execute(geometry1, geometry2, spatialReference, + null); + return result; + } + + /** + * Indicates if one geometry crosses another geometry. + * + * See OperatorCrosses. + * + * @param geometry1 + * The geometry to cross. + * @param geometry2 + * The geometry being crossed. + * @param spatialReference + * The spatial reference of the geometries. + * @return TRUE if geometry1 crosses geometry2. + */ + public static boolean crosses(Geometry geometry1, Geometry geometry2, + SpatialReference spatialReference) { + OperatorCrosses op = (OperatorCrosses) factory + .getOperator(Operator.Type.Crosses); + boolean result = op.execute(geometry1, geometry2, spatialReference, + null); + return result; + } + + /** + * Indicates if one geometry touches another geometry. + * + * See OperatorTouches. + * + * @param geometry1 + * The geometry to touch. + * @param geometry2 + * The geometry to be touched. + * @param spatialReference + * The spatial reference of the geometries. + * @return TRUE if geometry1 touches geometry2. + */ + public static boolean touches(Geometry geometry1, Geometry geometry2, + SpatialReference spatialReference) { + OperatorTouches op = (OperatorTouches) factory + .getOperator(Operator.Type.Touches); + boolean result = op.execute(geometry1, geometry2, spatialReference, + null); + return result; + } + + /** + * Indicates if one geometry overlaps another geometry. + * + * See OperatorOverlaps. + * + * @param geometry1 + * The geometry to overlap. + * @param geometry2 + * The geometry to be overlapped. + * @param spatialReference + * The spatial reference of the geometries. + * @return TRUE if geometry1 overlaps geometry2. + */ + public static boolean overlaps(Geometry geometry1, Geometry geometry2, + SpatialReference spatialReference) { + OperatorOverlaps op = (OperatorOverlaps) factory + .getOperator(Operator.Type.Overlaps); + boolean result = op.execute(geometry1, geometry2, spatialReference, + null); + return result; + } + + /** + * Indicates if the given relation holds for the two geometries. + * + * See OperatorRelate. + * + * @param geometry1 + * The first geometry for the relation. + * @param geometry2 + * The second geometry for the relation. + * @param spatialReference + * The spatial reference of the geometries. + * @param relation + * The DE-9IM relation. + * @return TRUE if the given relation holds between geometry1 and geometry2. + */ + public static boolean relate(Geometry geometry1, Geometry geometry2, + SpatialReference spatialReference, String relation) { + OperatorRelate op = (OperatorRelate) factory + .getOperator(Operator.Type.Relate); + boolean result = op.execute(geometry1, geometry2, spatialReference, + relation, null); + return result; + } + + /** + * Calculates the 2D planar distance between two geometries. + * + * See OperatorDistance. + * + * @param geometry1 + * Geometry. + * @param geometry2 + * Geometry. + * @param spatialReference + * The spatial reference of the geometries. This parameter is not + * used and can be null. + * @return The distance between the two geometries. + */ + public static double distance(Geometry geometry1, Geometry geometry2, + SpatialReference spatialReference) { + OperatorDistance op = (OperatorDistance) factory + .getOperator(Operator.Type.Distance); + double result = op.execute(geometry1, geometry2, null); + return result; + } + + /** + * Calculates the clipped geometry from a target geometry using an envelope. + * + * See OperatorClip. + * + * @param geometry + * The geometry to be clipped. + * @param envelope + * The envelope used to clip. + * @param spatialReference + * The spatial reference of the geometries. + * @return The geometry created by clipping. + */ + public static Geometry clip(Geometry geometry, Envelope envelope, + SpatialReference spatialReference) { + OperatorClip op = (OperatorClip) factory + .getOperator(Operator.Type.Clip); + Geometry result = op.execute(geometry, Envelope2D.construct( + envelope.getXMin(), envelope.getYMin(), envelope.getXMax(), + envelope.getYMax()), spatialReference, null); + return result; + } + + /** + * Calculates the cut geometry from a target geometry using a polyline. For + * Polylines, all left cuts will be grouped together in the first Geometry, + * Right cuts and coincident cuts are grouped in the second Geometry, and + * each undefined cut, along with any uncut parts, are output as separate + * Polylines. For Polygons, all left cuts are grouped in the first Polygon, + * all right cuts are in the second Polygon, and each undefined cut, along + * with any left-over parts after cutting, are output as a separate Polygon. + * If there were no cuts then the array will be empty. An undefined cut will + * only be produced if a left cut or right cut was produced, and there was a + * part left over after cutting or a cut is bounded to the left and right of + * the cutter. + * + * See OperatorCut. + * + * @param cuttee + * The geometry to be cut. + * @param cutter + * The polyline to cut the geometry. + * @param spatialReference + * The spatial reference of the geometries. + * @return An array of geometries created from cutting. + */ + public static Geometry[] cut(Geometry cuttee, Polyline cutter, + SpatialReference spatialReference) { + if (cuttee == null || cutter == null) + return null; + + OperatorCut op = (OperatorCut) factory.getOperator(Operator.Type.Cut); + GeometryCursor cursor = op.execute(true, cuttee, cutter, + spatialReference, null); + ArrayList cutsList = new ArrayList(); + + Geometry geometry; + while ((geometry = cursor.next()) != null) { + if (!geometry.isEmpty()) { + cutsList.add(geometry); + } + } + + return cutsList.toArray(new Geometry[0]); + } + /** + * Calculates a buffer polygon for each geometry at each of the + * corresponding specified distances. It is assumed that all geometries have + * the same spatial reference. There is an option to union the + * returned geometries. + * + * See OperatorBuffer. + * + * @param geometries An array of geometries to be buffered. + * @param spatialReference The spatial reference of the geometries. + * @param distances The corresponding distances for the input geometries to be buffered. + * @param toUnionResults TRUE if all geometries buffered at a given distance are to be unioned into a single polygon. + * @return The buffer of the geometries. + */ + public static Polygon[] buffer(Geometry[] geometries, + SpatialReference spatialReference, double[] distances, + boolean toUnionResults) { + // initially assume distances are in unit of spatial reference + double[] bufferDistances = distances; + + OperatorBuffer op = (OperatorBuffer) factory + .getOperator(Operator.Type.Buffer); + + if (toUnionResults) { + SimpleGeometryCursor inputGeometriesCursor = new SimpleGeometryCursor( + geometries); + GeometryCursor result = op.execute(inputGeometriesCursor, + spatialReference, bufferDistances, toUnionResults, null); + + ArrayList resultGeoms = new ArrayList(); + Geometry g; + while ((g = result.next()) != null) { + resultGeoms.add((Polygon) g); + } + Polygon[] buffers = resultGeoms.toArray(new Polygon[0]); + return buffers; + } else { + Polygon[] buffers = new Polygon[geometries.length]; + for (int i = 0; i < geometries.length; i++) { + buffers[i] = (Polygon) op.execute(geometries[i], + spatialReference, bufferDistances[i], null); + } + return buffers; + } + } + + /** + * Calculates a buffer polygon of the geometry as specified by the + * distance input. The buffer is implemented in the xy-plane. + * + * See OperatorBuffer + * + * @param geometry Geometry to be buffered. + * @param spatialReference The spatial reference of the geometry. + * @param distance The specified distance for buffer. Same units as the spatial reference. + * @return The buffer polygon at the specified distances. + */ + public static Polygon buffer(Geometry geometry, + SpatialReference spatialReference, double distance) { + double bufferDistance = distance; + + OperatorBuffer op = (OperatorBuffer) factory + .getOperator(Operator.Type.Buffer); + Geometry result = op.execute(geometry, spatialReference, + bufferDistance, null); + return (Polygon) result; + } + + /** + * Calculates the convex hull geometry. + * + * See OperatorConvexHull. + * + * @param geometry The input geometry. + * @return Returns the convex hull. + * + * For a Point - returns the same point. For an Envelope - + * returns the same envelope. For a MultiPoint - If the point + * count is one, returns the same multipoint. If the point count + * is two, returns a polyline of the points. Otherwise computes + * and returns the convex hull polygon. For a Segment - returns a + * polyline consisting of the segment. For a Polyline - If + * consists of only one segment, returns the same polyline. + * Otherwise computes and returns the convex hull polygon. For a + * Polygon - If more than one path, or if the path isn't already + * convex, computes and returns the convex hull polygon. + * Otherwise returns the same polygon. + */ + public static Geometry convexHull(Geometry geometry) { + OperatorConvexHull op = (OperatorConvexHull) factory + .getOperator(Operator.Type.ConvexHull); + return op.execute(geometry, null); + } + + /** + * Calculates the convex hull. + * + * See OperatorConvexHull + * + * @param geometries + * The input geometry array. + * @param b_merge + * Put true if you want the convex hull of all the geometries in + * the array combined. Put false if you want the convex hull of + * each geometry in the array individually. + * @return Returns an array of convex hulls. If b_merge is true, the result + * will be a one element array consisting of the merged convex hull. + */ + public static Geometry[] convexHull(Geometry[] geometries, boolean b_merge) { + OperatorConvexHull op = (OperatorConvexHull) factory + .getOperator(Operator.Type.ConvexHull); + SimpleGeometryCursor simple_cursor = new SimpleGeometryCursor( + geometries); + GeometryCursor cursor = op.execute(simple_cursor, b_merge, null); + + ArrayList resultGeoms = new ArrayList(); + Geometry g; + while ((g = cursor.next()) != null) { + resultGeoms.add(g); + } + + Geometry[] output = new Geometry[resultGeoms.size()]; + + for (int i = 0; i < resultGeoms.size(); i++) + output[i] = resultGeoms.get(i); + + return output; + } + + /** + * Finds the coordinate of the geometry which is closest to the specified + * point. + * + * See OperatorProximity2D. + * + * @param inputPoint + * The point to find the nearest coordinate in the geometry for. + * @param geometry + * The geometry to consider. + * @return Proximity2DResult containing the nearest coordinate. + */ + public static Proximity2DResult getNearestCoordinate(Geometry geometry, + Point inputPoint, boolean bTestPolygonInterior) { + + OperatorProximity2D proximity = (OperatorProximity2D) factory + .getOperator(com.esri.core.geometry.Operator.Type.Proximity2D); + Proximity2DResult result = proximity.getNearestCoordinate(geometry, + inputPoint, bTestPolygonInterior); + return result; + } + + /** + * Finds nearest vertex on the geometry which is closed to the specified + * point. + * + * See OperatorProximity2D. + * + * @param inputPoint + * The point to find the nearest vertex of the geometry for. + * @param geometry + * The geometry to consider. + * @return Proximity2DResult containing the nearest vertex. + */ + public static Proximity2DResult getNearestVertex(Geometry geometry, + Point inputPoint) { + OperatorProximity2D proximity = (OperatorProximity2D) factory + .getOperator(com.esri.core.geometry.Operator.Type.Proximity2D); + Proximity2DResult result = proximity.getNearestVertex(geometry, + inputPoint); + return result; + } + + /** + * Finds all vertices in the given distance from the specified point, sorted + * from the closest to the furthest. + * + * See OperatorProximity2D. + * + * @param inputPoint + * The point to start from. + * @param geometry + * The geometry to consider. + * @param searchRadius + * The search radius. + * @param maxVertexCountToReturn + * The maximum number number of vertices to return. + * @return Proximity2DResult containing the array of nearest vertices. + */ + public static Proximity2DResult[] getNearestVertices(Geometry geometry, + Point inputPoint, double searchRadius, int maxVertexCountToReturn) { + OperatorProximity2D proximity = (OperatorProximity2D) factory + .getOperator(com.esri.core.geometry.Operator.Type.Proximity2D); + + Proximity2DResult[] results = proximity.getNearestVertices(geometry, + inputPoint, searchRadius, maxVertexCountToReturn); + + return results; + } + + /** + * Performs the simplify operation on the geometry. + * + * See OperatorSimplify and See OperatorSimplifyOGC. + * + * @param geometry + * The geometry to be simplified. + * @param spatialReference + * The spatial reference of the geometry to be simplified. + * @return The simplified geometry. + */ + public static Geometry simplify(Geometry geometry, + SpatialReference spatialReference) { + OperatorSimplify op = (OperatorSimplify) factory + .getOperator(Operator.Type.Simplify); + Geometry result = op.execute(geometry, spatialReference, false, null); + return result; + } + + /** + * Checks if the Geometry is simple. + * + * See OperatorSimplify. + * + * @param geometry + * The geometry to be checked. + * @param spatialReference + * The spatial reference of the geometry. + * @return TRUE if the geometry is simple. + */ + static boolean isSimple(Geometry geometry, SpatialReference spatialReference) { + OperatorSimplify op = (OperatorSimplify) factory + .getOperator(Operator.Type.Simplify); + boolean result = op.isSimpleAsFeature(geometry, spatialReference, null); + return result; + } + + /** + * A geodesic distance is the shortest distance between any two points on the earth's surface when the earth's + * surface is approximated by a spheroid. The function returns the shortest distance between two points on the + * WGS84 spheroid. + * @param ptFrom The "from" point: long, lat in degrees. + * @param ptTo The "to" point: long, lat in degrees. + * @return The geodesic distance between two points in meters. + */ + public static double geodesicDistanceOnWGS84(Point ptFrom, Point ptTo) { + return SpatialReferenceImpl.geodesicDistanceOnWGS84Impl(ptFrom, ptTo); + } +} diff --git a/src/test/java/com/esri/core/geometry/TestCut.java b/src/test/java/com/esri/core/geometry/TestCut.java index f67a4d1f..8bb8f25c 100644 --- a/src/test/java/com/esri/core/geometry/TestCut.java +++ b/src/test/java/com/esri/core/geometry/TestCut.java @@ -1,520 +1,520 @@ -package com.esri.core.geometry; - -import junit.framework.TestCase; -import org.junit.Test; - -public class TestCut extends TestCase { - @Override - protected void setUp() throws Exception { - super.setUp(); - } - - @Override - protected void tearDown() throws Exception { - super.tearDown(); - } - - @Test - public static void testCut4326() { - SpatialReference sr = SpatialReference.create(4326); - testConsiderTouch1(sr); - testConsiderTouch2(sr); - testPolygon5(sr); - testPolygon7(sr); - testPolygon8(sr); - testPolygon9(sr); - testEngine(sr); - - } - - public static void testConsiderTouch1(SpatialReference spatialReference) { - OperatorFactoryLocal engine = OperatorFactoryLocal.getInstance(); - OperatorCut opCut = (OperatorCut) engine.getOperator(Operator.Type.Cut); - - Polyline polyline1 = makePolyline1(); - Polyline cutter1 = makePolylineCutter1(); - - GeometryCursor cursor = opCut.execute(true, polyline1, cutter1, - spatialReference, null); - Polyline cut; - int pathCount; - int segmentCount; - double length; - - cut = (Polyline) cursor.next(); - pathCount = cut.getPathCount(); - segmentCount = cut.getSegmentCount(); - length = cut.calculateLength2D(); - assertTrue(pathCount == 4); - assertTrue(segmentCount == 4); - assertTrue(length == 6); - - cut = (Polyline) cursor.next(); - pathCount = cut.getPathCount(); - segmentCount = cut.getSegmentCount(); - length = cut.calculateLength2D(); - assertTrue(pathCount == 6); - assertTrue(segmentCount == 8); - assertTrue(length == 12); - - cut = (Polyline) cursor.next(); - pathCount = cut.getPathCount(); - segmentCount = cut.getSegmentCount(); - length = cut.calculateLength2D(); - assertTrue(pathCount == 1); - assertTrue(segmentCount == 1); - assertTrue(length == 1); - - cut = (Polyline) cursor.next(); - pathCount = cut.getPathCount(); - segmentCount = cut.getSegmentCount(); - length = cut.calculateLength2D(); - assertTrue(pathCount == 1); - assertTrue(segmentCount == 1); - assertTrue(length == 1); - - cut = (Polyline) cursor.next(); - assertTrue(cut == null); - } - - public static void testConsiderTouch2(SpatialReference spatialReference) { - OperatorFactoryLocal engine = OperatorFactoryLocal.getInstance(); - OperatorCut opCut = (OperatorCut) engine.getOperator(Operator.Type.Cut); - - Polyline polyline2 = makePolyline2(); - Polyline cutter2 = makePolylineCutter2(); - - GeometryCursor cursor = opCut.execute(true, polyline2, cutter2, - spatialReference, null); - Polyline cut; - int pathCount; - int segmentCount; - double length; - - cut = (Polyline) cursor.next(); - pathCount = cut.getPathCount(); - segmentCount = cut.getSegmentCount(); - length = cut.calculateLength2D(); - assertTrue(pathCount == 4); - assertTrue(segmentCount == 4); - assertTrue(Math.abs(length - 5.74264068) <= 0.001); - - cut = (Polyline) cursor.next(); - pathCount = cut.getPathCount(); - segmentCount = cut.getSegmentCount(); - length = cut.calculateLength2D(); - assertTrue(pathCount == 6); - assertTrue(segmentCount == 8); - assertTrue(length == 6.75); - - cut = (Polyline) cursor.next(); - pathCount = cut.getPathCount(); - segmentCount = cut.getSegmentCount(); - length = cut.calculateLength2D(); - assertTrue(pathCount == 1); - assertTrue(segmentCount == 1); - assertTrue(Math.abs(length - 0.5) <= 0.001); - - cut = (Polyline) cursor.next(); - pathCount = cut.getPathCount(); - segmentCount = cut.getSegmentCount(); - length = cut.calculateLength2D(); - assertTrue(pathCount == 1); - assertTrue(segmentCount == 1); - assertTrue(Math.abs(length - 0.25) <= 0.001); - - cut = (Polyline) cursor.next(); - pathCount = cut.getPathCount(); - segmentCount = cut.getSegmentCount(); - length = cut.calculateLength2D(); - assertTrue(pathCount == 1); - assertTrue(segmentCount == 1); - assertTrue(Math.abs(length - 1) <= 0.001); - - cut = (Polyline) cursor.next(); - pathCount = cut.getPathCount(); - segmentCount = cut.getSegmentCount(); - length = cut.calculateLength2D(); - assertTrue(pathCount == 1); - assertTrue(segmentCount == 1); - assertTrue(Math.abs(length - 1.41421356) <= 0.001); - - cut = (Polyline) cursor.next(); - assertTrue(cut == null); - } - - public static void testPolygon5(SpatialReference spatialReference) { - OperatorFactoryLocal engine = OperatorFactoryLocal.getInstance(); - OperatorCut opCut = (OperatorCut) engine.getOperator(Operator.Type.Cut); - - Polygon polygon5 = makePolygon5(); - Polyline cutter5 = makePolygonCutter5(); - - GeometryCursor cursor = opCut.execute(true, polygon5, cutter5, - spatialReference, null); - Polygon cut; - int pathCount; - int pointCount; - double area; - - cut = (Polygon) cursor.next(); - pathCount = cut.getPathCount(); - pointCount = cut.getPointCount(); - area = cut.calculateArea2D(); - assertTrue(pathCount == 4); - assertTrue(pointCount == 12); - assertTrue(area == 450); - - cut = (Polygon) cursor.next(); - pathCount = cut.getPathCount(); - pointCount = cut.getPointCount(); - area = cut.calculateArea2D(); - assertTrue(pathCount == 1); - assertTrue(pointCount == 4); - assertTrue(area == 450); - - cut = (Polygon) cursor.next(); - assertTrue(cut == null); - } - - public static void testPolygon7(SpatialReference spatialReference) { - OperatorFactoryLocal engine = OperatorFactoryLocal.getInstance(); - OperatorCut opCut = (OperatorCut) engine.getOperator(Operator.Type.Cut); - - Polygon cut; - int path_count; - int point_count; - double area; - - Polygon polygon7 = makePolygon7(); - Polyline cutter7 = makePolygonCutter7(); - GeometryCursor cursor = opCut.execute(false, polygon7, cutter7, - spatialReference, null); - - cut = (Polygon) cursor.next(); - path_count = cut.getPathCount(); - point_count = cut.getPointCount(); - area = cut.calculateArea2D(); - assertTrue(path_count == 1); - assertTrue(point_count == 4); - assertTrue(area == 100); - - cut = (Polygon) cursor.next(); - assertTrue(cut.isEmpty()); - - cut = (Polygon) cursor.next(); - path_count = cut.getPathCount(); - point_count = cut.getPointCount(); - area = cut.calculateArea2D(); - assertTrue(path_count == 2); - assertTrue(point_count == 8); - assertTrue(area == 800); - - cut = (Polygon) cursor.next(); - assertTrue(cut == null); - } - - public static void testPolygon8(SpatialReference spatialReference) { - OperatorFactoryLocal engine = OperatorFactoryLocal.getInstance(); - OperatorCut opCut = (OperatorCut) engine.getOperator(Operator.Type.Cut); - - Polygon polygon8 = makePolygon8(); - Polyline cutter8 = makePolygonCutter8(); - - GeometryCursor cursor = opCut.execute(true, polygon8, cutter8, - spatialReference, null); - Polygon cut; - int pathCount; - int pointCount; - double area; - - cut = (Polygon) cursor.next(); - assertTrue(cut.isEmpty()); - - cut = (Polygon) cursor.next(); - pathCount = cut.getPathCount(); - pointCount = cut.getPointCount(); - area = cut.calculateArea2D(); - assertTrue(pathCount == 1); - assertTrue(pointCount == 4); - assertTrue(area == 100); - - cut = (Polygon) cursor.next(); - pathCount = cut.getPathCount(); - pointCount = cut.getPointCount(); - area = cut.calculateArea2D(); - assertTrue(pathCount == 2); - assertTrue(pointCount == 8); - assertTrue(area == 800); - - cut = (Polygon) cursor.next(); - assertTrue(cut == null); - } - - public static void testPolygon9(SpatialReference spatialReference) { - OperatorFactoryLocal engine = OperatorFactoryLocal.getInstance(); - OperatorCut opCut = (OperatorCut) engine.getOperator(Operator.Type.Cut); - - Polygon cut; - int path_count; - int point_count; - double area; - - Polygon polygon9 = makePolygon9(); - Polyline cutter9 = makePolygonCutter9(); - GeometryCursor cursor = opCut.execute(false, polygon9, cutter9, - spatialReference, null); - - cut = (Polygon) cursor.next(); - path_count = cut.getPathCount(); - point_count = cut.getPointCount(); - area = cut.calculateArea2D(); - assertTrue(path_count == 3); - assertTrue(point_count == 12); - assertTrue(area == 150); - - cut = (Polygon) cursor.next(); - path_count = cut.getPathCount(); - point_count = cut.getPointCount(); - area = cut.calculateArea2D(); - assertTrue(path_count == 3); - assertTrue(point_count == 12); - assertTrue(area == 150); - - cut = (Polygon) cursor.next(); - assertTrue(cut == null); - } - - public static void testEngine(SpatialReference spatialReference) { - Polygon polygon8 = makePolygon8(); - Polyline cutter8 = makePolygonCutter8(); - - Geometry[] cuts = GeometryEngine.cut(polygon8, cutter8, - spatialReference); - Polygon cut; - int pathCount; - int pointCount; - double area; - - cut = (Polygon) cuts[0]; - pathCount = cut.getPathCount(); - pointCount = cut.getPointCount(); - area = cut.calculateArea2D(); - assertTrue(pathCount == 1); - assertTrue(pointCount == 4); - assertTrue(area == 100); - - cut = (Polygon) cuts[1]; - pathCount = cut.getPathCount(); - pointCount = cut.getPointCount(); - area = cut.calculateArea2D(); - assertTrue(pathCount == 2); - assertTrue(pointCount == 8); - assertTrue(area == 800); - } - - public static Polyline makePolyline1() { - Polyline poly = new Polyline(); - - poly.startPath(0, 0); - poly.lineTo(2, 0); - poly.lineTo(4, 0); - poly.lineTo(6, 0); - poly.lineTo(8, 0); - poly.lineTo(10, 0); - poly.lineTo(12, 0); - poly.lineTo(14, 0); - poly.lineTo(16, 0); - poly.lineTo(18, 0); - poly.lineTo(20, 0); - - return poly; - } - - public static Polyline makePolylineCutter1() { - Polyline poly = new Polyline(); - - poly.startPath(1, 0); - poly.lineTo(4, 0); - - poly.startPath(6, -1); - poly.lineTo(6, 1); - - poly.startPath(6, 0); - poly.lineTo(8, 0); - - poly.startPath(9, -1); - poly.lineTo(9, 1); - - poly.startPath(10, 0); - poly.lineTo(12, 0); - - poly.startPath(12, 1); - poly.lineTo(12, -1); - - poly.startPath(12, 0); - poly.lineTo(15, 0); - - poly.startPath(15, 1); - poly.lineTo(15, -1); - - poly.startPath(16, 0); - poly.lineTo(16, -1); - poly.lineTo(17, -1); - poly.lineTo(17, 1); - poly.lineTo(17, 0); - poly.lineTo(18, 0); - - poly.startPath(18, 0); - poly.lineTo(18, -1); - - return poly; - } - - public static Polyline makePolyline2() { - Polyline poly = new Polyline(); - - poly.startPath(-2, 0); - poly.lineTo(-1, 0); - poly.lineTo(0, 0); - poly.lineTo(2, 0); - poly.lineTo(4, 2); - poly.lineTo(8, 2); - poly.lineTo(10, 4); - poly.lineTo(12, 4); - - return poly; - } - - public static Polyline makePolylineCutter2() { - Polyline poly = new Polyline(); - - poly.startPath(-1.5, 0); - poly.lineTo(-.75, 0); - - poly.startPath(-.5, 0); - poly.lineTo(1, 0); - poly.lineTo(1, 2); - poly.lineTo(3, -2); - poly.lineTo(4, 2); - poly.lineTo(5, -2); - poly.lineTo(5, 4); - poly.lineTo(8, 2); - poly.lineTo(6, 0); - poly.lineTo(6, 3); - - poly.startPath(9, 5); - poly.lineTo(9, 2); - poly.lineTo(10, 2); - poly.lineTo(10, 5); - poly.lineTo(10.5, 5); - poly.lineTo(10.5, 3); - - poly.startPath(11, 4); - poly.lineTo(11, 5); - - poly.startPath(12, 5); - poly.lineTo(12, 4); - - return poly; - } - - public static Polygon makePolygon5() { - Polygon poly = new Polygon(); - - poly.startPath(0, 0); - poly.lineTo(0, 30); - poly.lineTo(30, 30); - poly.lineTo(30, 0); - - return poly; - } - - public static Polyline makePolygonCutter5() { - Polyline poly = new Polyline(); - - poly.startPath(15, 0); - poly.lineTo(0, 15); - poly.lineTo(15, 30); - poly.lineTo(30, 15); - poly.lineTo(15, 0); - - return poly; - } - - public static Polygon makePolygon7() { - Polygon poly = new Polygon(); - - poly.startPath(0, 0); - poly.lineTo(0, 30); - poly.lineTo(30, 30); - poly.lineTo(30, 0); - - return poly; - } - - public static Polyline makePolygonCutter7() { - Polyline poly = new Polyline(); - - poly.startPath(10, 10); - poly.lineTo(20, 10); - poly.lineTo(20, 20); - poly.lineTo(10, 20); - poly.lineTo(10, 10); - - return poly; - } - - public static Polygon makePolygon8() { - Polygon poly = new Polygon(); - - poly.startPath(0, 0); - poly.lineTo(0, 30); - poly.lineTo(30, 30); - poly.lineTo(30, 0); - - return poly; - } - - public static Polyline makePolygonCutter8() { - Polyline poly = new Polyline(); - - poly.startPath(10, 10); - poly.lineTo(10, 20); - poly.lineTo(20, 20); - poly.lineTo(20, 10); - poly.lineTo(10, 10); - - return poly; - } - - public static Polygon makePolygon9() { - Polygon poly = new Polygon(); - - poly.startPath(0, 0); - poly.lineTo(0, 10); - poly.lineTo(10, 10); - poly.lineTo(10, 0); - - poly.startPath(0, 20); - poly.lineTo(0, 30); - poly.lineTo(10, 30); - poly.lineTo(10, 20); - - poly.startPath(0, 40); - poly.lineTo(0, 50); - poly.lineTo(10, 50); - poly.lineTo(10, 40); - - return poly; - } - - public static Polyline makePolygonCutter9() { - Polyline poly = new Polyline(); - - poly.startPath(5, -1); - poly.lineTo(5, 51); - - return poly; - } -} +package com.esri.core.geometry; + +import junit.framework.TestCase; +import org.junit.Test; + +public class TestCut extends TestCase { + @Override + protected void setUp() throws Exception { + super.setUp(); + } + + @Override + protected void tearDown() throws Exception { + super.tearDown(); + } + + @Test + public static void testCut4326() { + SpatialReference sr = SpatialReference.create(4326); + testConsiderTouch1(sr); + testConsiderTouch2(sr); + testPolygon5(sr); + testPolygon7(sr); + testPolygon8(sr); + testPolygon9(sr); + testEngine(sr); + + } + + public static void testConsiderTouch1(SpatialReference spatialReference) { + OperatorFactoryLocal engine = OperatorFactoryLocal.getInstance(); + OperatorCut opCut = (OperatorCut) engine.getOperator(Operator.Type.Cut); + + Polyline polyline1 = makePolyline1(); + Polyline cutter1 = makePolylineCutter1(); + + GeometryCursor cursor = opCut.execute(true, polyline1, cutter1, + spatialReference, null); + Polyline cut; + int pathCount; + int segmentCount; + double length; + + cut = (Polyline) cursor.next(); + pathCount = cut.getPathCount(); + segmentCount = cut.getSegmentCount(); + length = cut.calculateLength2D(); + assertTrue(pathCount == 4); + assertTrue(segmentCount == 4); + assertTrue(length == 6); + + cut = (Polyline) cursor.next(); + pathCount = cut.getPathCount(); + segmentCount = cut.getSegmentCount(); + length = cut.calculateLength2D(); + assertTrue(pathCount == 6); + assertTrue(segmentCount == 8); + assertTrue(length == 12); + + cut = (Polyline) cursor.next(); + pathCount = cut.getPathCount(); + segmentCount = cut.getSegmentCount(); + length = cut.calculateLength2D(); + assertTrue(pathCount == 1); + assertTrue(segmentCount == 1); + assertTrue(length == 1); + + cut = (Polyline) cursor.next(); + pathCount = cut.getPathCount(); + segmentCount = cut.getSegmentCount(); + length = cut.calculateLength2D(); + assertTrue(pathCount == 1); + assertTrue(segmentCount == 1); + assertTrue(length == 1); + + cut = (Polyline) cursor.next(); + assertTrue(cut == null); + } + + public static void testConsiderTouch2(SpatialReference spatialReference) { + OperatorFactoryLocal engine = OperatorFactoryLocal.getInstance(); + OperatorCut opCut = (OperatorCut) engine.getOperator(Operator.Type.Cut); + + Polyline polyline2 = makePolyline2(); + Polyline cutter2 = makePolylineCutter2(); + + GeometryCursor cursor = opCut.execute(true, polyline2, cutter2, + spatialReference, null); + Polyline cut; + int pathCount; + int segmentCount; + double length; + + cut = (Polyline) cursor.next(); + pathCount = cut.getPathCount(); + segmentCount = cut.getSegmentCount(); + length = cut.calculateLength2D(); + assertTrue(pathCount == 4); + assertTrue(segmentCount == 4); + assertTrue(Math.abs(length - 5.74264068) <= 0.001); + + cut = (Polyline) cursor.next(); + pathCount = cut.getPathCount(); + segmentCount = cut.getSegmentCount(); + length = cut.calculateLength2D(); + assertTrue(pathCount == 6); + assertTrue(segmentCount == 8); + assertTrue(length == 6.75); + + cut = (Polyline) cursor.next(); + pathCount = cut.getPathCount(); + segmentCount = cut.getSegmentCount(); + length = cut.calculateLength2D(); + assertTrue(pathCount == 1); + assertTrue(segmentCount == 1); + assertTrue(Math.abs(length - 0.5) <= 0.001); + + cut = (Polyline) cursor.next(); + pathCount = cut.getPathCount(); + segmentCount = cut.getSegmentCount(); + length = cut.calculateLength2D(); + assertTrue(pathCount == 1); + assertTrue(segmentCount == 1); + assertTrue(Math.abs(length - 0.25) <= 0.001); + + cut = (Polyline) cursor.next(); + pathCount = cut.getPathCount(); + segmentCount = cut.getSegmentCount(); + length = cut.calculateLength2D(); + assertTrue(pathCount == 1); + assertTrue(segmentCount == 1); + assertTrue(Math.abs(length - 1) <= 0.001); + + cut = (Polyline) cursor.next(); + pathCount = cut.getPathCount(); + segmentCount = cut.getSegmentCount(); + length = cut.calculateLength2D(); + assertTrue(pathCount == 1); + assertTrue(segmentCount == 1); + assertTrue(Math.abs(length - 1.41421356) <= 0.001); + + cut = (Polyline) cursor.next(); + assertTrue(cut == null); + } + + public static void testPolygon5(SpatialReference spatialReference) { + OperatorFactoryLocal engine = OperatorFactoryLocal.getInstance(); + OperatorCut opCut = (OperatorCut) engine.getOperator(Operator.Type.Cut); + + Polygon polygon5 = makePolygon5(); + Polyline cutter5 = makePolygonCutter5(); + + GeometryCursor cursor = opCut.execute(true, polygon5, cutter5, + spatialReference, null); + Polygon cut; + int pathCount; + int pointCount; + double area; + + cut = (Polygon) cursor.next(); + pathCount = cut.getPathCount(); + pointCount = cut.getPointCount(); + area = cut.calculateArea2D(); + assertTrue(pathCount == 4); + assertTrue(pointCount == 12); + assertTrue(area == 450); + + cut = (Polygon) cursor.next(); + pathCount = cut.getPathCount(); + pointCount = cut.getPointCount(); + area = cut.calculateArea2D(); + assertTrue(pathCount == 1); + assertTrue(pointCount == 4); + assertTrue(area == 450); + + cut = (Polygon) cursor.next(); + assertTrue(cut == null); + } + + public static void testPolygon7(SpatialReference spatialReference) { + OperatorFactoryLocal engine = OperatorFactoryLocal.getInstance(); + OperatorCut opCut = (OperatorCut) engine.getOperator(Operator.Type.Cut); + + Polygon cut; + int path_count; + int point_count; + double area; + + Polygon polygon7 = makePolygon7(); + Polyline cutter7 = makePolygonCutter7(); + GeometryCursor cursor = opCut.execute(false, polygon7, cutter7, + spatialReference, null); + + cut = (Polygon) cursor.next(); + path_count = cut.getPathCount(); + point_count = cut.getPointCount(); + area = cut.calculateArea2D(); + assertTrue(path_count == 1); + assertTrue(point_count == 4); + assertTrue(area == 100); + + cut = (Polygon) cursor.next(); + assertTrue(cut.isEmpty()); + + cut = (Polygon) cursor.next(); + path_count = cut.getPathCount(); + point_count = cut.getPointCount(); + area = cut.calculateArea2D(); + assertTrue(path_count == 2); + assertTrue(point_count == 8); + assertTrue(area == 800); + + cut = (Polygon) cursor.next(); + assertTrue(cut == null); + } + + public static void testPolygon8(SpatialReference spatialReference) { + OperatorFactoryLocal engine = OperatorFactoryLocal.getInstance(); + OperatorCut opCut = (OperatorCut) engine.getOperator(Operator.Type.Cut); + + Polygon polygon8 = makePolygon8(); + Polyline cutter8 = makePolygonCutter8(); + + GeometryCursor cursor = opCut.execute(true, polygon8, cutter8, + spatialReference, null); + Polygon cut; + int pathCount; + int pointCount; + double area; + + cut = (Polygon) cursor.next(); + assertTrue(cut.isEmpty()); + + cut = (Polygon) cursor.next(); + pathCount = cut.getPathCount(); + pointCount = cut.getPointCount(); + area = cut.calculateArea2D(); + assertTrue(pathCount == 1); + assertTrue(pointCount == 4); + assertTrue(area == 100); + + cut = (Polygon) cursor.next(); + pathCount = cut.getPathCount(); + pointCount = cut.getPointCount(); + area = cut.calculateArea2D(); + assertTrue(pathCount == 2); + assertTrue(pointCount == 8); + assertTrue(area == 800); + + cut = (Polygon) cursor.next(); + assertTrue(cut == null); + } + + public static void testPolygon9(SpatialReference spatialReference) { + OperatorFactoryLocal engine = OperatorFactoryLocal.getInstance(); + OperatorCut opCut = (OperatorCut) engine.getOperator(Operator.Type.Cut); + + Polygon cut; + int path_count; + int point_count; + double area; + + Polygon polygon9 = makePolygon9(); + Polyline cutter9 = makePolygonCutter9(); + GeometryCursor cursor = opCut.execute(false, polygon9, cutter9, + spatialReference, null); + + cut = (Polygon) cursor.next(); + path_count = cut.getPathCount(); + point_count = cut.getPointCount(); + area = cut.calculateArea2D(); + assertTrue(path_count == 3); + assertTrue(point_count == 12); + assertTrue(area == 150); + + cut = (Polygon) cursor.next(); + path_count = cut.getPathCount(); + point_count = cut.getPointCount(); + area = cut.calculateArea2D(); + assertTrue(path_count == 3); + assertTrue(point_count == 12); + assertTrue(area == 150); + + cut = (Polygon) cursor.next(); + assertTrue(cut == null); + } + + public static void testEngine(SpatialReference spatialReference) { + Polygon polygon8 = makePolygon8(); + Polyline cutter8 = makePolygonCutter8(); + + Geometry[] cuts = GeometryEngine.cut(polygon8, cutter8, + spatialReference); + Polygon cut; + int pathCount; + int pointCount; + double area; + + cut = (Polygon) cuts[0]; + pathCount = cut.getPathCount(); + pointCount = cut.getPointCount(); + area = cut.calculateArea2D(); + assertTrue(pathCount == 1); + assertTrue(pointCount == 4); + assertTrue(area == 100); + + cut = (Polygon) cuts[1]; + pathCount = cut.getPathCount(); + pointCount = cut.getPointCount(); + area = cut.calculateArea2D(); + assertTrue(pathCount == 2); + assertTrue(pointCount == 8); + assertTrue(area == 800); + } + + public static Polyline makePolyline1() { + Polyline poly = new Polyline(); + + poly.startPath(0, 0); + poly.lineTo(2, 0); + poly.lineTo(4, 0); + poly.lineTo(6, 0); + poly.lineTo(8, 0); + poly.lineTo(10, 0); + poly.lineTo(12, 0); + poly.lineTo(14, 0); + poly.lineTo(16, 0); + poly.lineTo(18, 0); + poly.lineTo(20, 0); + + return poly; + } + + public static Polyline makePolylineCutter1() { + Polyline poly = new Polyline(); + + poly.startPath(1, 0); + poly.lineTo(4, 0); + + poly.startPath(6, -1); + poly.lineTo(6, 1); + + poly.startPath(6, 0); + poly.lineTo(8, 0); + + poly.startPath(9, -1); + poly.lineTo(9, 1); + + poly.startPath(10, 0); + poly.lineTo(12, 0); + + poly.startPath(12, 1); + poly.lineTo(12, -1); + + poly.startPath(12, 0); + poly.lineTo(15, 0); + + poly.startPath(15, 1); + poly.lineTo(15, -1); + + poly.startPath(16, 0); + poly.lineTo(16, -1); + poly.lineTo(17, -1); + poly.lineTo(17, 1); + poly.lineTo(17, 0); + poly.lineTo(18, 0); + + poly.startPath(18, 0); + poly.lineTo(18, -1); + + return poly; + } + + public static Polyline makePolyline2() { + Polyline poly = new Polyline(); + + poly.startPath(-2, 0); + poly.lineTo(-1, 0); + poly.lineTo(0, 0); + poly.lineTo(2, 0); + poly.lineTo(4, 2); + poly.lineTo(8, 2); + poly.lineTo(10, 4); + poly.lineTo(12, 4); + + return poly; + } + + public static Polyline makePolylineCutter2() { + Polyline poly = new Polyline(); + + poly.startPath(-1.5, 0); + poly.lineTo(-.75, 0); + + poly.startPath(-.5, 0); + poly.lineTo(1, 0); + poly.lineTo(1, 2); + poly.lineTo(3, -2); + poly.lineTo(4, 2); + poly.lineTo(5, -2); + poly.lineTo(5, 4); + poly.lineTo(8, 2); + poly.lineTo(6, 0); + poly.lineTo(6, 3); + + poly.startPath(9, 5); + poly.lineTo(9, 2); + poly.lineTo(10, 2); + poly.lineTo(10, 5); + poly.lineTo(10.5, 5); + poly.lineTo(10.5, 3); + + poly.startPath(11, 4); + poly.lineTo(11, 5); + + poly.startPath(12, 5); + poly.lineTo(12, 4); + + return poly; + } + + public static Polygon makePolygon5() { + Polygon poly = new Polygon(); + + poly.startPath(0, 0); + poly.lineTo(0, 30); + poly.lineTo(30, 30); + poly.lineTo(30, 0); + + return poly; + } + + public static Polyline makePolygonCutter5() { + Polyline poly = new Polyline(); + + poly.startPath(15, 0); + poly.lineTo(0, 15); + poly.lineTo(15, 30); + poly.lineTo(30, 15); + poly.lineTo(15, 0); + + return poly; + } + + public static Polygon makePolygon7() { + Polygon poly = new Polygon(); + + poly.startPath(0, 0); + poly.lineTo(0, 30); + poly.lineTo(30, 30); + poly.lineTo(30, 0); + + return poly; + } + + public static Polyline makePolygonCutter7() { + Polyline poly = new Polyline(); + + poly.startPath(10, 10); + poly.lineTo(20, 10); + poly.lineTo(20, 20); + poly.lineTo(10, 20); + poly.lineTo(10, 10); + + return poly; + } + + public static Polygon makePolygon8() { + Polygon poly = new Polygon(); + + poly.startPath(0, 0); + poly.lineTo(0, 30); + poly.lineTo(30, 30); + poly.lineTo(30, 0); + + return poly; + } + + public static Polyline makePolygonCutter8() { + Polyline poly = new Polyline(); + + poly.startPath(10, 10); + poly.lineTo(10, 20); + poly.lineTo(20, 20); + poly.lineTo(20, 10); + poly.lineTo(10, 10); + + return poly; + } + + public static Polygon makePolygon9() { + Polygon poly = new Polygon(); + + poly.startPath(0, 0); + poly.lineTo(0, 10); + poly.lineTo(10, 10); + poly.lineTo(10, 0); + + poly.startPath(0, 20); + poly.lineTo(0, 30); + poly.lineTo(10, 30); + poly.lineTo(10, 20); + + poly.startPath(0, 40); + poly.lineTo(0, 50); + poly.lineTo(10, 50); + poly.lineTo(10, 40); + + return poly; + } + + public static Polyline makePolygonCutter9() { + Polyline poly = new Polyline(); + + poly.startPath(5, -1); + poly.lineTo(5, 51); + + return poly; + } +} diff --git a/src/test/java/com/esri/core/geometry/TestFailed.java b/src/test/java/com/esri/core/geometry/TestFailed.java index 9c7a915d..5c5b6c41 100644 --- a/src/test/java/com/esri/core/geometry/TestFailed.java +++ b/src/test/java/com/esri/core/geometry/TestFailed.java @@ -1,64 +1,64 @@ -package com.esri.core.geometry; - -import junit.framework.TestCase; -import org.junit.Test; - -public class TestFailed extends TestCase { - @Override - protected void setUp() throws Exception { - super.setUp(); - } - - @Override - protected void tearDown() throws Exception { - super.tearDown(); - } - - @Test - public void testCenterXY() { - Envelope env = new Envelope(-130, 30, -70, 50); - assertEquals(-100, env.getCenterX(), 0); - assertEquals(40, env.getCenterY(), 0); - } - - @Test - public void testGeometryOperationSupport() { - Geometry baseGeom = new Point(-130, 10); - Geometry comparisonGeom = new Point(-130, 10); - SpatialReference sr = SpatialReference.create(4326); - - @SuppressWarnings("unused") - Geometry diffGeom = null; - int noException = 1; // no exception - try { - diffGeom = GeometryEngine.difference(baseGeom, comparisonGeom, sr); - - } catch (IllegalArgumentException ex) { - noException = 0; - } catch (GeometryException ex) { - noException = 0; - } - assertEquals(noException, 1); - } - - @Test - public void TestIntersection() { - OperatorIntersects op = (OperatorIntersects) OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.Intersects); - Polygon polygon = new Polygon(); - // outer ring1 - polygon.startPath(0, 0); - polygon.lineTo(10, 10); - polygon.lineTo(20, 0); - - Point point1 = new Point(15, 10); - Point point2 = new Point(2, 10); - Point point3 = new Point(5, 5); - boolean res = op.execute(polygon, point1, null, null); - assertTrue(!res); - res = op.execute(polygon, point2, null, null); - assertTrue(!res); - res = op.execute(polygon, point3, null, null); - assertTrue(res); - } -} +package com.esri.core.geometry; + +import junit.framework.TestCase; +import org.junit.Test; + +public class TestFailed extends TestCase { + @Override + protected void setUp() throws Exception { + super.setUp(); + } + + @Override + protected void tearDown() throws Exception { + super.tearDown(); + } + + @Test + public void testCenterXY() { + Envelope env = new Envelope(-130, 30, -70, 50); + assertEquals(-100, env.getCenterX(), 0); + assertEquals(40, env.getCenterY(), 0); + } + + @Test + public void testGeometryOperationSupport() { + Geometry baseGeom = new Point(-130, 10); + Geometry comparisonGeom = new Point(-130, 10); + SpatialReference sr = SpatialReference.create(4326); + + @SuppressWarnings("unused") + Geometry diffGeom = null; + int noException = 1; // no exception + try { + diffGeom = GeometryEngine.difference(baseGeom, comparisonGeom, sr); + + } catch (IllegalArgumentException ex) { + noException = 0; + } catch (GeometryException ex) { + noException = 0; + } + assertEquals(noException, 1); + } + + @Test + public void TestIntersection() { + OperatorIntersects op = (OperatorIntersects) OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Intersects); + Polygon polygon = new Polygon(); + // outer ring1 + polygon.startPath(0, 0); + polygon.lineTo(10, 10); + polygon.lineTo(20, 0); + + Point point1 = new Point(15, 10); + Point point2 = new Point(2, 10); + Point point3 = new Point(5, 5); + boolean res = op.execute(polygon, point1, null, null); + assertTrue(!res); + res = op.execute(polygon, point2, null, null); + assertTrue(!res); + res = op.execute(polygon, point3, null, null); + assertTrue(res); + } +} diff --git a/src/test/java/com/esri/core/geometry/TestGeodetic.java b/src/test/java/com/esri/core/geometry/TestGeodetic.java index 8777ec19..35c805ea 100644 --- a/src/test/java/com/esri/core/geometry/TestGeodetic.java +++ b/src/test/java/com/esri/core/geometry/TestGeodetic.java @@ -1,104 +1,105 @@ -package com.esri.core.geometry; - -import junit.framework.TestCase; - -import org.junit.Test; - -public class TestGeodetic extends TestCase { - @Override - protected void setUp() throws Exception { - super.setUp(); - } - - @Override - protected void tearDown() throws Exception { - super.tearDown(); - } - - @Test - public void testTriangleLength() { - Point pt_0 = new Point(10, 10); - Point pt_1 = new Point(20, 20); - Point pt_2 = new Point(20, 10); - double length = 0.0; - length += GeometryEngine.geodesicDistanceOnWGS84(pt_0, pt_1); - length += GeometryEngine.geodesicDistanceOnWGS84(pt_1, pt_2); - length += GeometryEngine.geodesicDistanceOnWGS84(pt_2, pt_0); - assertTrue(Math.abs(length - 3744719.4094597572) < 1e-12 * 3744719.4094597572); - } - - @Test - public void testRotationInvariance() { - Point pt_0 = new Point(10, 40); - Point pt_1 = new Point(20, 60); - Point pt_2 = new Point(20, 40); - double length = 0.0; - length += GeometryEngine.geodesicDistanceOnWGS84(pt_0, pt_1); - length += GeometryEngine.geodesicDistanceOnWGS84(pt_1, pt_2); - length += GeometryEngine.geodesicDistanceOnWGS84(pt_2, pt_0); - assertTrue(Math.abs(length - 5409156.3896271614) < 1e-12 * 5409156.3896271614); - - for (int i = -540; i < 540; i += 5) { - pt_0.setXY(i + 10, 40); - pt_1.setXY(i + 20, 60); - pt_2.setXY(i + 20, 40); - length = 0.0; - length += GeometryEngine.geodesicDistanceOnWGS84(pt_0, pt_1); - length += GeometryEngine.geodesicDistanceOnWGS84(pt_1, pt_2); - length += GeometryEngine.geodesicDistanceOnWGS84(pt_2, pt_0); - assertTrue(Math.abs(length - 5409156.3896271614) < 1e-12 * 5409156.3896271614); - } - } - - @Test - public void testDistanceFailure() { - { - Point p1 = new Point(-60.668485, -31.996013333333334); - Point p2 = new Point(119.13731666666666, 32.251583333333336); - double d = GeometryEngine.geodesicDistanceOnWGS84(p1, p2); - assertTrue(Math.abs(d - 19973410.50579736) < 1e-13 * 19973410.50579736); - } - - { - Point p1 = new Point(121.27343833333333, 27.467438333333334); - Point p2 = new Point(-58.55804833333333, -27.035613333333334); - double d = GeometryEngine.geodesicDistanceOnWGS84(p1, p2); - assertTrue(Math.abs(d - 19954707.428360686) < 1e-13 * 19954707.428360686); - } - - { - Point p1 = new Point(-53.329865, -36.08110166666667); - Point p2 = new Point(126.52895166666667, 35.97385); - double d = GeometryEngine.geodesicDistanceOnWGS84(p1, p2); - assertTrue(Math.abs(d - 19990586.700431127) < 1e-13 * 19990586.700431127); - } - - { - Point p1 = new Point(-4.7181166667, 36.1160166667); - Point p2 = new Point(175.248925, -35.7606716667); - double d = GeometryEngine.geodesicDistanceOnWGS84(p1, p2); - assertTrue(Math.abs(d - 19964450.206594173) < 1e-12 * 19964450.206594173); - } - } - - @Test - public void testLengthAccurateCR191313() { - /* - * // random_test(); OperatorFactoryLocal engine = - * OperatorFactoryLocal.getInstance(); //TODO: Make this: - * OperatorShapePreservingLength geoLengthOp = - * (OperatorShapePreservingLength) - * factory.getOperator(Operator.Type.ShapePreservingLength); - * SpatialReference spatialRef = SpatialReference.create(102631); - * //[6097817.59407673 - * ,17463475.2931517],[-1168053.34617516,11199801.3734424 - * ]]],"spatialReference":{"wkid":102631} - * - * Polyline polyline = new Polyline(); - * polyline.startPath(6097817.59407673, 17463475.2931517); - * polyline.lineTo(-1168053.34617516, 11199801.3734424); double length = - * geoLengthOp.execute(polyline, spatialRef, null); - * assertTrue(Math.abs(length - 2738362.3249366437) < 2e-9 * length); - */ - } - } +package com.esri.core.geometry; + +import junit.framework.TestCase; + +import org.junit.Test; + +public class TestGeodetic extends TestCase { + @Override + protected void setUp() throws Exception { + super.setUp(); + } + + @Override + protected void tearDown() throws Exception { + super.tearDown(); + } + + @Test + public void testTriangleLength() { + Point pt_0 = new Point(10, 10); + Point pt_1 = new Point(20, 20); + Point pt_2 = new Point(20, 10); + double length = 0.0; + length += GeometryEngine.geodesicDistanceOnWGS84(pt_0, pt_1); + length += GeometryEngine.geodesicDistanceOnWGS84(pt_1, pt_2); + length += GeometryEngine.geodesicDistanceOnWGS84(pt_2, pt_0); + assertTrue(Math.abs(length - 3744719.4094597572) < 1e-12 * 3744719.4094597572); + } + + @Test + public void testRotationInvariance() { + Point pt_0 = new Point(10, 40); + Point pt_1 = new Point(20, 60); + Point pt_2 = new Point(20, 40); + double length = 0.0; + length += GeometryEngine.geodesicDistanceOnWGS84(pt_0, pt_1); + length += GeometryEngine.geodesicDistanceOnWGS84(pt_1, pt_2); + length += GeometryEngine.geodesicDistanceOnWGS84(pt_2, pt_0); + assertTrue(Math.abs(length - 5409156.3896271614) < 1e-12 * 5409156.3896271614); + + for (int i = -540; i < 540; i += 5) { + pt_0.setXY(i + 10, 40); + pt_1.setXY(i + 20, 60); + pt_2.setXY(i + 20, 40); + length = 0.0; + length += GeometryEngine.geodesicDistanceOnWGS84(pt_0, pt_1); + length += GeometryEngine.geodesicDistanceOnWGS84(pt_1, pt_2); + length += GeometryEngine.geodesicDistanceOnWGS84(pt_2, pt_0); + assertTrue(Math.abs(length - 5409156.3896271614) < 1e-12 * 5409156.3896271614); + } + } + + @Test + public void testDistanceFailure() { + { + Point p1 = new Point(-60.668485, -31.996013333333334); + Point p2 = new Point(119.13731666666666, 32.251583333333336); + double d = GeometryEngine.geodesicDistanceOnWGS84(p1, p2); + assertTrue(Math.abs(d - 19973410.50579736) < 1e-13 * 19973410.50579736); + } + + { + Point p1 = new Point(121.27343833333333, 27.467438333333334); + Point p2 = new Point(-58.55804833333333, -27.035613333333334); + double d = GeometryEngine.geodesicDistanceOnWGS84(p1, p2); + assertTrue(Math.abs(d - 19954707.428360686) < 1e-13 * 19954707.428360686); + } + + { + Point p1 = new Point(-53.329865, -36.08110166666667); + Point p2 = new Point(126.52895166666667, 35.97385); + double d = GeometryEngine.geodesicDistanceOnWGS84(p1, p2); + assertTrue(Math.abs(d - 19990586.700431127) < 1e-13 * 19990586.700431127); + } + + { + Point p1 = new Point(-4.7181166667, 36.1160166667); + Point p2 = new Point(175.248925, -35.7606716667); + double d = GeometryEngine.geodesicDistanceOnWGS84(p1, p2); + assertTrue(Math.abs(d - 19964450.206594173) < 1e-12 * 19964450.206594173); + } + } + + @Test + public void testLengthAccurateCR191313() { + /* + * // random_test(); OperatorFactoryLocal engine = + * OperatorFactoryLocal.getInstance(); //TODO: Make this: + * OperatorShapePreservingLength geoLengthOp = + * (OperatorShapePreservingLength) + * factory.getOperator(Operator.Type.ShapePreservingLength); + * SpatialReference spatialRef = SpatialReference.create(102631); + * //[6097817.59407673 + * ,17463475.2931517],[-1168053.34617516,11199801.3734424 + * ]]],"spatialReference":{"wkid":102631} + * + * Polyline polyline = new Polyline(); + * polyline.startPath(6097817.59407673, 17463475.2931517); + * polyline.lineTo(-1168053.34617516, 11199801.3734424); double length = + * geoLengthOp.execute(polyline, spatialRef, null); + * assertTrue(Math.abs(length - 2738362.3249366437) < 2e-9 * length); + */ + } + +} diff --git a/src/test/java/com/esri/core/geometry/TestGeomToJSonExportSRFromWkiOrWkt_CR181369.java b/src/test/java/com/esri/core/geometry/TestGeomToJSonExportSRFromWkiOrWkt_CR181369.java index 237abb4c..498dab4b 100644 --- a/src/test/java/com/esri/core/geometry/TestGeomToJSonExportSRFromWkiOrWkt_CR181369.java +++ b/src/test/java/com/esri/core/geometry/TestGeomToJSonExportSRFromWkiOrWkt_CR181369.java @@ -1,565 +1,565 @@ -package com.esri.core.geometry; - -import java.io.IOException; -import junit.framework.TestCase; -import org.junit.Test; - -import com.fasterxml.jackson.core.JsonFactory; -import com.fasterxml.jackson.core.JsonParseException; -import com.fasterxml.jackson.core.JsonParser; - -public class TestGeomToJSonExportSRFromWkiOrWkt_CR181369 extends TestCase { - @Override - protected void setUp() throws Exception { - super.setUp(); - } - - @Override - protected void tearDown() throws Exception { - super.tearDown(); - } - - JsonFactory factory = new JsonFactory(); - SpatialReference spatialReferenceWebMerc1 = SpatialReference.create(102100); - SpatialReference spatialReferenceWebMerc2 = SpatialReference - .create(spatialReferenceWebMerc1.getLatestID()); - SpatialReference spatialReferenceWGS84 = SpatialReference.create(4326); - - @Test - public void testLocalExport() - throws JsonParseException, IOException { - String s = OperatorExportToJson.local().execute(null, new Point(1000000.2, 2000000.3)); - //assertTrue(s.contains(".")); - //assertFalse(s.contains(",")); - Polyline line = new Polyline(); - line.startPath(1.1, 2.2); - line.lineTo(2.3, 4.5); - String s1 = OperatorExportToJson.local().execute(null, line); - assertTrue(s.contains(".")); - } - - boolean testPoint() throws JsonParseException, IOException { - boolean bAnswer = true; - Point point1 = new Point(10.0, 20.0); - Point pointEmpty = new Point(); - { - JsonParser pointWebMerc1Parser = factory - .createParser(GeometryEngine.geometryToJson( - spatialReferenceWebMerc1, point1)); - MapGeometry pointWebMerc1MP = GeometryEngine - .jsonToGeometry(pointWebMerc1Parser); - assertTrue(point1.getX() == ((Point) pointWebMerc1MP.getGeometry()) - .getX()); - assertTrue(point1.getY() == ((Point) pointWebMerc1MP.getGeometry()) - .getY()); - assertTrue(spatialReferenceWebMerc1.getID() == pointWebMerc1MP - .getSpatialReference().getID() - || pointWebMerc1MP.getSpatialReference().getID() == 3857); - - if (!checkResultSpatialRef(pointWebMerc1MP, 102100, 3857)) { - bAnswer = false; - } - - pointWebMerc1Parser = factory.createParser(GeometryEngine - .geometryToJson(null, point1)); - pointWebMerc1MP = GeometryEngine - .jsonToGeometry(pointWebMerc1Parser); - assertTrue(null == pointWebMerc1MP.getSpatialReference()); - - if (pointWebMerc1MP.getSpatialReference() != null) { - if (!checkResultSpatialRef(pointWebMerc1MP, 102100, 3857)) { - bAnswer = false; - } - } - - String pointEmptyString = GeometryEngine.geometryToJson( - spatialReferenceWebMerc1, pointEmpty); - pointWebMerc1Parser = factory.createParser(pointEmptyString); - } - - JsonParser pointWebMerc2Parser = factory - .createParser(GeometryEngine.geometryToJson( - spatialReferenceWebMerc2, point1)); - MapGeometry pointWebMerc2MP = GeometryEngine - .jsonToGeometry(pointWebMerc2Parser); - assertTrue(point1.getX() == ((Point) pointWebMerc2MP.getGeometry()) - .getX()); - assertTrue(point1.getY() == ((Point) pointWebMerc2MP.getGeometry()) - .getY()); - assertTrue(spatialReferenceWebMerc2.getLatestID() == pointWebMerc2MP - .getSpatialReference().getLatestID()); - if (!checkResultSpatialRef(pointWebMerc2MP, - spatialReferenceWebMerc2.getLatestID(), 0)) { - bAnswer = false; - } - - { - JsonParser pointWgs84Parser = factory - .createParser(GeometryEngine.geometryToJson( - spatialReferenceWGS84, point1)); - MapGeometry pointWgs84MP = GeometryEngine - .jsonToGeometry(pointWgs84Parser); - assertTrue(point1.getX() == ((Point) pointWgs84MP.getGeometry()) - .getX()); - assertTrue(point1.getY() == ((Point) pointWgs84MP.getGeometry()) - .getY()); - assertTrue(spatialReferenceWGS84.getID() == pointWgs84MP - .getSpatialReference().getID()); - if (!checkResultSpatialRef(pointWgs84MP, 4326, 0)) { - bAnswer = false; - } - } - - { - Point p = new Point(); - String s = GeometryEngine.geometryToJson(spatialReferenceWebMerc1, - p); - assertTrue(s - .equals("{\"x\":null,\"y\":null,\"spatialReference\":{\"wkid\":102100,\"latestWkid\":3857}}")); - - p.addAttribute(VertexDescription.Semantics.Z); - p.addAttribute(VertexDescription.Semantics.M); - s = GeometryEngine.geometryToJson(spatialReferenceWebMerc1, p); - assertTrue(s - .equals("{\"x\":null,\"y\":null,\"z\":null,\"m\":null,\"spatialReference\":{\"wkid\":102100,\"latestWkid\":3857}}")); - - } - - { - Point p = new Point(10.0, 20.0, 30.0); - p.addAttribute(VertexDescription.Semantics.M); - String s = GeometryEngine.geometryToJson(spatialReferenceWebMerc1, - p); - assertTrue(s - .equals("{\"x\":10,\"y\":20,\"z\":30,\"m\":null,\"spatialReference\":{\"wkid\":102100,\"latestWkid\":3857}}")); - } - - {// import - String s = "{\"x\":0.0,\"y\":1.0,\"z\":5.0,\"m\":11.0,\"spatialReference\":{\"wkid\":102100,\"latestWkid\":3857}}"; - JsonParser parser = factory.createParser(s); - MapGeometry map_pt = GeometryEngine.jsonToGeometry(parser); - Point pt = (Point) map_pt.getGeometry(); - assertTrue(pt.getX() == 0.0); - assertTrue(pt.getY() == 1.0); - assertTrue(pt.getZ() == 5.0); - assertTrue(pt.getM() == 11.0); - } - - { - String s = "{\"x\" : 5.0, \"y\" : null, \"spatialReference\" : {\"wkid\" : 4326}} "; - JsonParser parser = factory.createParser(s); - MapGeometry map_pt = GeometryEngine.jsonToGeometry(parser); - Point pt = (Point) map_pt.getGeometry(); - assertTrue(pt.isEmpty()); - SpatialReference spatial_reference = map_pt.getSpatialReference(); - assertTrue(spatial_reference.getID() == 4326); - } - - return bAnswer; - } - - boolean testMultiPoint() throws JsonParseException, IOException { - boolean bAnswer = true; - - MultiPoint multiPoint1 = new MultiPoint(); - multiPoint1.add(-97.06138, 32.837); - multiPoint1.add(-97.06133, 32.836); - multiPoint1.add(-97.06124, 32.834); - multiPoint1.add(-97.06127, 32.832); - - { - String s = GeometryEngine.geometryToJson(spatialReferenceWGS84, - multiPoint1); - JsonParser mPointWgs84Parser = factory.createParser(s); - MapGeometry mPointWgs84MP = GeometryEngine - .jsonToGeometry(mPointWgs84Parser); - assertTrue(multiPoint1.getPointCount() == ((MultiPoint) mPointWgs84MP - .getGeometry()).getPointCount()); - assertTrue(multiPoint1.getPoint(0).getX() == ((MultiPoint) mPointWgs84MP - .getGeometry()).getPoint(0).getX()); - assertTrue(multiPoint1.getPoint(0).getY() == ((MultiPoint) mPointWgs84MP - .getGeometry()).getPoint(0).getY()); - int lastIndex = multiPoint1.getPointCount() - 1; - assertTrue(multiPoint1.getPoint(lastIndex).getX() == ((MultiPoint) mPointWgs84MP - .getGeometry()).getPoint(lastIndex).getX()); - assertTrue(multiPoint1.getPoint(lastIndex).getY() == ((MultiPoint) mPointWgs84MP - .getGeometry()).getPoint(lastIndex).getY()); - - assertTrue(spatialReferenceWGS84.getID() == mPointWgs84MP - .getSpatialReference().getID()); - if (!checkResultSpatialRef(mPointWgs84MP, 4326, 0)) { - bAnswer = false; - } - - } - - { - MultiPoint p = new MultiPoint(); - p.addAttribute(VertexDescription.Semantics.Z); - p.addAttribute(VertexDescription.Semantics.M); - String s = GeometryEngine.geometryToJson(spatialReferenceWebMerc1, - p); - assertTrue(s - .equals("{\"hasZ\":true,\"hasM\":true,\"points\":[],\"spatialReference\":{\"wkid\":102100,\"latestWkid\":3857}}")); - - p.add(10.0, 20.0, 30.0); - p.add(20.0, 40.0, 60.0); - s = GeometryEngine.geometryToJson(spatialReferenceWebMerc1, p); - assertTrue(s - .equals("{\"hasZ\":true,\"hasM\":true,\"points\":[[10,20,30,null],[20,40,60,null]],\"spatialReference\":{\"wkid\":102100,\"latestWkid\":3857}}")); - } - { - String points = "{\"hasM\" : false, \"hasZ\" : true, \"uncle remus\" : null, \"points\" : [ [0,0,1], [0.0,10.0,1], [10.0,10.0,1], [10.0,0.0,1, 6666] ],\"spatialReference\" : {\"wkid\" : 4326}}"; - MapGeometry mp = GeometryEngine.jsonToGeometry(factory - .createParser(points)); - MultiPoint multipoint = (MultiPoint) mp.getGeometry(); - assertTrue(multipoint.getPointCount() == 4); - Point2D point2d; - point2d = multipoint.getXY(0); - assertTrue(point2d.x == 0.0 && point2d.y == 0.0); - point2d = multipoint.getXY(1); - assertTrue(point2d.x == 0.0 && point2d.y == 10.0); - point2d = multipoint.getXY(2); - assertTrue(point2d.x == 10.0 && point2d.y == 10.0); - point2d = multipoint.getXY(3); - assertTrue(point2d.x == 10.0 && point2d.y == 0.0); - assertTrue(multipoint.hasAttribute(VertexDescription.Semantics.Z)); - assertTrue(!multipoint.hasAttribute(VertexDescription.Semantics.M)); - double z = multipoint.getAttributeAsDbl( - VertexDescription.Semantics.Z, 0, 0); - assertTrue(z == 1); - SpatialReference spatial_reference = mp.getSpatialReference(); - assertTrue(spatial_reference.getID() == 4326); - } - - return bAnswer; - } - - boolean testPolyline() throws JsonParseException, IOException { - boolean bAnswer = true; - - Polyline polyline = new Polyline(); - polyline.startPath(-97.06138, 32.837); - polyline.lineTo(-97.06133, 32.836); - polyline.lineTo(-97.06124, 32.834); - polyline.lineTo(-97.06127, 32.832); - - polyline.startPath(-97.06326, 32.759); - polyline.lineTo(-97.06298, 32.755); - - { - JsonParser polylinePathsWgs84Parser = factory - .createParser(GeometryEngine.geometryToJson( - spatialReferenceWGS84, polyline)); - MapGeometry mPolylineWGS84MP = GeometryEngine - .jsonToGeometry(polylinePathsWgs84Parser); - - assertTrue(polyline.getPointCount() == ((Polyline) mPolylineWGS84MP - .getGeometry()).getPointCount()); - assertTrue(polyline.getPoint(0).getX() == ((Polyline) mPolylineWGS84MP - .getGeometry()).getPoint(0).getX()); - assertTrue(polyline.getPoint(0).getY() == ((Polyline) mPolylineWGS84MP - .getGeometry()).getPoint(0).getY()); - - assertTrue(polyline.getPathCount() == ((Polyline) mPolylineWGS84MP - .getGeometry()).getPathCount()); - assertTrue(polyline.getSegmentCount() == ((Polyline) mPolylineWGS84MP - .getGeometry()).getSegmentCount()); - assertTrue(polyline.getSegmentCount(0) == ((Polyline) mPolylineWGS84MP - .getGeometry()).getSegmentCount(0)); - assertTrue(polyline.getSegmentCount(1) == ((Polyline) mPolylineWGS84MP - .getGeometry()).getSegmentCount(1)); - - int lastIndex = polyline.getPointCount() - 1; - assertTrue(polyline.getPoint(lastIndex).getX() == ((Polyline) mPolylineWGS84MP - .getGeometry()).getPoint(lastIndex).getX()); - assertTrue(polyline.getPoint(lastIndex).getY() == ((Polyline) mPolylineWGS84MP - .getGeometry()).getPoint(lastIndex).getY()); - - assertTrue(spatialReferenceWGS84.getID() == mPolylineWGS84MP - .getSpatialReference().getID()); - - if (!checkResultSpatialRef(mPolylineWGS84MP, 4326, 0)) { - bAnswer = false; - } - } - - { - Polyline p = new Polyline(); - p.addAttribute(VertexDescription.Semantics.Z); - p.addAttribute(VertexDescription.Semantics.M); - String s = GeometryEngine.geometryToJson(spatialReferenceWebMerc1, - p); - assertTrue(s - .equals("{\"hasZ\":true,\"hasM\":true,\"paths\":[],\"spatialReference\":{\"wkid\":102100,\"latestWkid\":3857}}")); - - p.startPath(0, 0); - p.lineTo(0, 1); - p.startPath(2, 2); - p.lineTo(3, 3); - - p.setAttribute(VertexDescription.Semantics.Z, 0, 0, 3); - p.setAttribute(VertexDescription.Semantics.M, 1, 0, 5); - s = GeometryEngine.geometryToJson(spatialReferenceWebMerc1, p); - assertTrue(s - .equals("{\"hasZ\":true,\"hasM\":true,\"paths\":[[[0,0,3,null],[0,1,0,5]],[[2,2,0,null],[3,3,0,null]]],\"spatialReference\":{\"wkid\":102100,\"latestWkid\":3857}}")); - } - - { - String paths = "{\"hasZ\" : true, \"paths\" : [ [ [0.0, 0.0,3], [0, 10.0,3], [10.0, 10.0,3, 6666], [10.0, 0.0,3, 6666] ], [ [1.0, 1,3], [1.0, 9.0,3], [9.0, 9.0,3], [1.0, 9.0,3] ] ], \"spatialReference\" : {\"wkid\" : 4326}, \"hasM\" : false}"; - MapGeometry mapGeometry = GeometryEngine.jsonToGeometry(factory - .createParser(paths)); - Polyline p = (Polyline) mapGeometry.getGeometry(); - assertTrue(p.getPathCount() == 2); - @SuppressWarnings("unused") - int count = p.getPathCount(); - assertTrue(p.getPointCount() == 8); - assertTrue(p.hasAttribute(VertexDescription.Semantics.Z)); - assertTrue(!p.hasAttribute(VertexDescription.Semantics.M)); - double z = p.getAttributeAsDbl(VertexDescription.Semantics.Z, 0, 0); - assertTrue(z == 3); - double length = p.calculateLength2D(); - assertTrue(Math.abs(length - 54.0) <= 0.001); - SpatialReference spatial_reference = mapGeometry - .getSpatialReference(); - assertTrue(spatial_reference.getID() == 4326); - } - - return bAnswer; - } - - boolean testPolygon() throws JsonParseException, IOException { - boolean bAnswer = true; - - Polygon polygon = new Polygon(); - polygon.startPath(-97.06138, 32.837); - polygon.lineTo(-97.06133, 32.836); - polygon.lineTo(-97.06124, 32.834); - polygon.lineTo(-97.06127, 32.832); - - polygon.startPath(-97.06326, 32.759); - polygon.lineTo(-97.06298, 32.755); - - { - JsonParser polygonPathsWgs84Parser = factory - .createParser(GeometryEngine.geometryToJson( - spatialReferenceWGS84, polygon)); - MapGeometry mPolygonWGS84MP = GeometryEngine - .jsonToGeometry(polygonPathsWgs84Parser); - - assertTrue(polygon.getPointCount() + 1 == ((Polygon) mPolygonWGS84MP - .getGeometry()).getPointCount()); - assertTrue(polygon.getPoint(0).getX() == ((Polygon) mPolygonWGS84MP - .getGeometry()).getPoint(0).getX()); - assertTrue(polygon.getPoint(0).getY() == ((Polygon) mPolygonWGS84MP - .getGeometry()).getPoint(0).getY()); - - assertTrue(polygon.getPathCount() == ((Polygon) mPolygonWGS84MP - .getGeometry()).getPathCount()); - assertTrue(polygon.getSegmentCount() + 1 == ((Polygon) mPolygonWGS84MP - .getGeometry()).getSegmentCount()); - assertTrue(polygon.getSegmentCount(0) == ((Polygon) mPolygonWGS84MP - .getGeometry()).getSegmentCount(0)); - assertTrue(polygon.getSegmentCount(1) + 1 == ((Polygon) mPolygonWGS84MP - .getGeometry()).getSegmentCount(1)); - - int lastIndex = polygon.getPointCount() - 1; - assertTrue(polygon.getPoint(lastIndex).getX() == ((Polygon) mPolygonWGS84MP - .getGeometry()).getPoint(lastIndex).getX()); - assertTrue(polygon.getPoint(lastIndex).getY() == ((Polygon) mPolygonWGS84MP - .getGeometry()).getPoint(lastIndex).getY()); - - assertTrue(spatialReferenceWGS84.getID() == mPolygonWGS84MP - .getSpatialReference().getID()); - - if (!checkResultSpatialRef(mPolygonWGS84MP, 4326, 0)) { - bAnswer = false; - } - } - - { - Polygon p = new Polygon(); - p.addAttribute(VertexDescription.Semantics.Z); - p.addAttribute(VertexDescription.Semantics.M); - String s = GeometryEngine.geometryToJson(spatialReferenceWebMerc1, - p); - assertTrue(s - .equals("{\"hasZ\":true,\"hasM\":true,\"rings\":[],\"spatialReference\":{\"wkid\":102100,\"latestWkid\":3857}}")); - - p.startPath(0, 0); - p.lineTo(0, 1); - p.lineTo(4, 4); - p.startPath(2, 2); - p.lineTo(3, 3); - p.lineTo(7, 8); - - p.setAttribute(VertexDescription.Semantics.Z, 0, 0, 3); - p.setAttribute(VertexDescription.Semantics.M, 1, 0, 7); - p.setAttribute(VertexDescription.Semantics.M, 2, 0, 5); - p.setAttribute(VertexDescription.Semantics.M, 5, 0, 5); - s = GeometryEngine.geometryToJson(spatialReferenceWebMerc1, p); - assertTrue(s - .equals("{\"hasZ\":true,\"hasM\":true,\"rings\":[[[0,0,3,null],[0,1,0,7],[4,4,0,5],[0,0,3,null]],[[2,2,0,null],[3,3,0,null],[7,8,0,5],[2,2,0,null]]],\"spatialReference\":{\"wkid\":102100,\"latestWkid\":3857}}")); - } - - { - // Test Import Polygon from Polygon - String rings = "{\"hasZ\": true, \"rings\" : [ [ [0,0, 5], [0.0, 10.0, 5], [10.0,10.0, 5, 66666], [10.0,0.0, 5] ], [ [12, 12] ], [ [13 , 17], [13 , 17] ], [ [1.0, 1.0, 5, 66666], [9.0,1.0, 5], [9.0,9.0, 5], [1.0,9.0, 5], [1.0, 1.0, 5] ] ] }"; - MapGeometry mapGeometry = GeometryEngine.jsonToGeometry(factory - .createParser(rings)); - Polygon p = (Polygon) mapGeometry.getGeometry(); - @SuppressWarnings("unused") - double area = p.calculateArea2D(); - @SuppressWarnings("unused") - double length = p.calculateLength2D(); - assertTrue(p.getPathCount() == 4); - int count = p.getPointCount(); - assertTrue(count == 15); - assertTrue(p.hasAttribute(VertexDescription.Semantics.Z)); - assertTrue(!p.hasAttribute(VertexDescription.Semantics.M)); - } - - return bAnswer; - } - - boolean testEnvelope() throws JsonParseException, IOException { - boolean bAnswer = true; - - Envelope envelope = new Envelope(); - envelope.setCoords(-109.55, 25.76, -86.39, 49.94); - - { - JsonParser envelopeWGS84Parser = factory - .createParser(GeometryEngine.geometryToJson( - spatialReferenceWGS84, envelope)); - MapGeometry envelopeWGS84MP = GeometryEngine - .jsonToGeometry(envelopeWGS84Parser); - assertTrue(envelope.isEmpty() == envelopeWGS84MP.getGeometry() - .isEmpty()); - assertTrue(envelope.getXMax() == ((Envelope) envelopeWGS84MP - .getGeometry()).getXMax()); - assertTrue(envelope.getYMax() == ((Envelope) envelopeWGS84MP - .getGeometry()).getYMax()); - assertTrue(envelope.getXMin() == ((Envelope) envelopeWGS84MP - .getGeometry()).getXMin()); - assertTrue(envelope.getYMin() == ((Envelope) envelopeWGS84MP - .getGeometry()).getYMin()); - assertTrue(spatialReferenceWGS84.getID() == envelopeWGS84MP - .getSpatialReference().getID()); - if (!checkResultSpatialRef(envelopeWGS84MP, 4326, 0)) { - bAnswer = false; - } - } - - {// export - Envelope e = new Envelope(); - e.addAttribute(VertexDescription.Semantics.Z); - e.addAttribute(VertexDescription.Semantics.M); - String s = GeometryEngine.geometryToJson(spatialReferenceWebMerc1, - e); - assertTrue(s - .equals("{\"xmin\":null,\"ymin\":null,\"xmax\":null,\"ymax\":null,\"zmin\":null,\"zmax\":null,\"mmin\":null,\"mmax\":null,\"spatialReference\":{\"wkid\":102100,\"latestWkid\":3857}}")); - - e.setCoords(0, 1, 2, 3); - - Envelope1D z = new Envelope1D(); - Envelope1D m = new Envelope1D(); - z.setCoords(5, 7); - m.setCoords(11, 13); - - e.setInterval(VertexDescription.Semantics.Z, 0, z); - e.setInterval(VertexDescription.Semantics.M, 0, m); - s = GeometryEngine.geometryToJson(spatialReferenceWebMerc1, e); - assertTrue(s - .equals("{\"xmin\":0,\"ymin\":1,\"xmax\":2,\"ymax\":3,\"zmin\":5,\"zmax\":7,\"mmin\":11,\"mmax\":13,\"spatialReference\":{\"wkid\":102100,\"latestWkid\":3857}}")); - } - - {// import - String s = "{\"xmin\":0.0,\"ymin\":1.0,\"xmax\":2.0,\"ymax\":3.0,\"zmin\":5.0,\"zmax\":7.0,\"mmin\":11.0,\"mmax\":13.0,\"spatialReference\":{\"wkid\":102100,\"latestWkid\":3857}}"; - JsonParser parser = factory.createParser(s); - MapGeometry map_env = GeometryEngine.jsonToGeometry(parser); - Envelope env = (Envelope) map_env.getGeometry(); - Envelope1D z = env.queryInterval(VertexDescription.Semantics.Z, 0); - Envelope1D m = env.queryInterval(VertexDescription.Semantics.M, 0); - assertTrue(z.vmin == 5.0); - assertTrue(z.vmax == 7.0); - assertTrue(m.vmin == 11.0); - assertTrue(m.vmax == 13.0); - } - - { - String s = "{ \"zmin\" : 33, \"xmin\" : -109.55, \"zmax\" : 53, \"ymin\" : 25.76, \"xmax\" : -86.39, \"ymax\" : 49.94, \"mmax\" : 13}"; - JsonParser parser = factory.createParser(s); - MapGeometry map_env = GeometryEngine.jsonToGeometry(parser); - Envelope env = (Envelope) map_env.getGeometry(); - Envelope2D e = new Envelope2D(); - env.queryEnvelope2D(e); - assertTrue(e.xmin == -109.55 && e.ymin == 25.76 && e.xmax == -86.39 - && e.ymax == 49.94); - - Envelope1D e1D; - assertTrue(env.hasAttribute(VertexDescription.Semantics.Z)); - e1D = env.queryInterval(VertexDescription.Semantics.Z, 0); - assertTrue(e1D.vmin == 33 && e1D.vmax == 53); - - assertTrue(!env.hasAttribute(VertexDescription.Semantics.M)); - } - - return bAnswer; - } - - boolean testCR181369() throws JsonParseException, IOException { - // CR181369 - boolean bAnswer = true; - - String jsonStringPointAndWKT = "{\"x\":10.0,\"y\":20.0,\"spatialReference\":{\"wkt\" : \"PROJCS[\\\"NAD83_UTM_zone_15N\\\",GEOGCS[\\\"GCS_North_American_1983\\\",DATUM[\\\"D_North_American_1983\\\",SPHEROID[\\\"GRS_1980\\\",6378137.0,298.257222101]],PRIMEM[\\\"Greenwich\\\",0.0],UNIT[\\\"Degree\\\",0.0174532925199433]],PROJECTION[\\\"Transverse_Mercator\\\"],PARAMETER[\\\"false_easting\\\",500000.0],PARAMETER[\\\"false_northing\\\",0.0],PARAMETER[\\\"central_meridian\\\",-93.0],PARAMETER[\\\"scale_factor\\\",0.9996],PARAMETER[\\\"latitude_of_origin\\\",0.0],UNIT[\\\"Meter\\\",1.0]]\"} }"; - JsonParser jsonParserPointAndWKT = factory - .createParser(jsonStringPointAndWKT); - MapGeometry mapGeom2 = GeometryEngine - .jsonToGeometry(jsonParserPointAndWKT); - String jsonStringPointAndWKT2 = GeometryEngine.geometryToJson( - mapGeom2.getSpatialReference(), mapGeom2.getGeometry()); - JsonParser jsonParserPointAndWKT2 = factory - .createParser(jsonStringPointAndWKT2); - MapGeometry mapGeom3 = GeometryEngine - .jsonToGeometry(jsonParserPointAndWKT2); - assertTrue(((Point) mapGeom2.getGeometry()).getX() == ((Point) mapGeom3 - .getGeometry()).getX()); - assertTrue(((Point) mapGeom2.getGeometry()).getY() == ((Point) mapGeom3 - .getGeometry()).getY()); - - String s1 = mapGeom2.getSpatialReference().getText(); - String s2 = mapGeom3.getSpatialReference().getText(); - assertTrue(s1.equals(s2)); - - int id2 = mapGeom2.getSpatialReference().getID(); - int id3 = mapGeom3.getSpatialReference().getID(); - assertTrue(id2 == id3); - if (!checkResultSpatialRef(mapGeom3, mapGeom2.getSpatialReference() - .getID(), 0)) { - bAnswer = false; - } - return bAnswer; - } - - boolean checkResultSpatialRef(MapGeometry mapGeometry, int expectWki1, - int expectWki2) { - SpatialReference sr = mapGeometry.getSpatialReference(); - String Wkt = sr.getText(); - int wki1 = sr.getLatestID(); - if (!(wki1 == expectWki1 || wki1 == expectWki2)) - return false; - if (!(Wkt != null && Wkt.length() > 0)) - return false; - SpatialReference sr2 = SpatialReference.create(Wkt); - int wki2 = sr2.getID(); - if (expectWki2 > 0) { - if (!(wki2 == expectWki1 || wki2 == expectWki2)) - return false; - } else { - if (!(wki2 == expectWki1)) - return false; - } - return true; - } -} +package com.esri.core.geometry; + +import java.io.IOException; +import junit.framework.TestCase; +import org.junit.Test; + +import com.fasterxml.jackson.core.JsonFactory; +import com.fasterxml.jackson.core.JsonParseException; +import com.fasterxml.jackson.core.JsonParser; + +public class TestGeomToJSonExportSRFromWkiOrWkt_CR181369 extends TestCase { + @Override + protected void setUp() throws Exception { + super.setUp(); + } + + @Override + protected void tearDown() throws Exception { + super.tearDown(); + } + + JsonFactory factory = new JsonFactory(); + SpatialReference spatialReferenceWebMerc1 = SpatialReference.create(102100); + SpatialReference spatialReferenceWebMerc2 = SpatialReference + .create(spatialReferenceWebMerc1.getLatestID()); + SpatialReference spatialReferenceWGS84 = SpatialReference.create(4326); + + @Test + public void testLocalExport() + throws JsonParseException, IOException { + String s = OperatorExportToJson.local().execute(null, new Point(1000000.2, 2000000.3)); + //assertTrue(s.contains(".")); + //assertFalse(s.contains(",")); + Polyline line = new Polyline(); + line.startPath(1.1, 2.2); + line.lineTo(2.3, 4.5); + String s1 = OperatorExportToJson.local().execute(null, line); + assertTrue(s.contains(".")); + } + + boolean testPoint() throws JsonParseException, IOException { + boolean bAnswer = true; + Point point1 = new Point(10.0, 20.0); + Point pointEmpty = new Point(); + { + JsonParser pointWebMerc1Parser = factory + .createParser(GeometryEngine.geometryToJson( + spatialReferenceWebMerc1, point1)); + MapGeometry pointWebMerc1MP = GeometryEngine + .jsonToGeometry(pointWebMerc1Parser); + assertTrue(point1.getX() == ((Point) pointWebMerc1MP.getGeometry()) + .getX()); + assertTrue(point1.getY() == ((Point) pointWebMerc1MP.getGeometry()) + .getY()); + assertTrue(spatialReferenceWebMerc1.getID() == pointWebMerc1MP + .getSpatialReference().getID() + || pointWebMerc1MP.getSpatialReference().getID() == 3857); + + if (!checkResultSpatialRef(pointWebMerc1MP, 102100, 3857)) { + bAnswer = false; + } + + pointWebMerc1Parser = factory.createParser(GeometryEngine + .geometryToJson(null, point1)); + pointWebMerc1MP = GeometryEngine + .jsonToGeometry(pointWebMerc1Parser); + assertTrue(null == pointWebMerc1MP.getSpatialReference()); + + if (pointWebMerc1MP.getSpatialReference() != null) { + if (!checkResultSpatialRef(pointWebMerc1MP, 102100, 3857)) { + bAnswer = false; + } + } + + String pointEmptyString = GeometryEngine.geometryToJson( + spatialReferenceWebMerc1, pointEmpty); + pointWebMerc1Parser = factory.createParser(pointEmptyString); + } + + JsonParser pointWebMerc2Parser = factory + .createParser(GeometryEngine.geometryToJson( + spatialReferenceWebMerc2, point1)); + MapGeometry pointWebMerc2MP = GeometryEngine + .jsonToGeometry(pointWebMerc2Parser); + assertTrue(point1.getX() == ((Point) pointWebMerc2MP.getGeometry()) + .getX()); + assertTrue(point1.getY() == ((Point) pointWebMerc2MP.getGeometry()) + .getY()); + assertTrue(spatialReferenceWebMerc2.getLatestID() == pointWebMerc2MP + .getSpatialReference().getLatestID()); + if (!checkResultSpatialRef(pointWebMerc2MP, + spatialReferenceWebMerc2.getLatestID(), 0)) { + bAnswer = false; + } + + { + JsonParser pointWgs84Parser = factory + .createParser(GeometryEngine.geometryToJson( + spatialReferenceWGS84, point1)); + MapGeometry pointWgs84MP = GeometryEngine + .jsonToGeometry(pointWgs84Parser); + assertTrue(point1.getX() == ((Point) pointWgs84MP.getGeometry()) + .getX()); + assertTrue(point1.getY() == ((Point) pointWgs84MP.getGeometry()) + .getY()); + assertTrue(spatialReferenceWGS84.getID() == pointWgs84MP + .getSpatialReference().getID()); + if (!checkResultSpatialRef(pointWgs84MP, 4326, 0)) { + bAnswer = false; + } + } + + { + Point p = new Point(); + String s = GeometryEngine.geometryToJson(spatialReferenceWebMerc1, + p); + assertTrue(s + .equals("{\"x\":null,\"y\":null,\"spatialReference\":{\"wkid\":102100,\"latestWkid\":3857}}")); + + p.addAttribute(VertexDescription.Semantics.Z); + p.addAttribute(VertexDescription.Semantics.M); + s = GeometryEngine.geometryToJson(spatialReferenceWebMerc1, p); + assertTrue(s + .equals("{\"x\":null,\"y\":null,\"z\":null,\"m\":null,\"spatialReference\":{\"wkid\":102100,\"latestWkid\":3857}}")); + + } + + { + Point p = new Point(10.0, 20.0, 30.0); + p.addAttribute(VertexDescription.Semantics.M); + String s = GeometryEngine.geometryToJson(spatialReferenceWebMerc1, + p); + assertTrue(s + .equals("{\"x\":10,\"y\":20,\"z\":30,\"m\":null,\"spatialReference\":{\"wkid\":102100,\"latestWkid\":3857}}")); + } + + {// import + String s = "{\"x\":0.0,\"y\":1.0,\"z\":5.0,\"m\":11.0,\"spatialReference\":{\"wkid\":102100,\"latestWkid\":3857}}"; + JsonParser parser = factory.createParser(s); + MapGeometry map_pt = GeometryEngine.jsonToGeometry(parser); + Point pt = (Point) map_pt.getGeometry(); + assertTrue(pt.getX() == 0.0); + assertTrue(pt.getY() == 1.0); + assertTrue(pt.getZ() == 5.0); + assertTrue(pt.getM() == 11.0); + } + + { + String s = "{\"x\" : 5.0, \"y\" : null, \"spatialReference\" : {\"wkid\" : 4326}} "; + JsonParser parser = factory.createParser(s); + MapGeometry map_pt = GeometryEngine.jsonToGeometry(parser); + Point pt = (Point) map_pt.getGeometry(); + assertTrue(pt.isEmpty()); + SpatialReference spatial_reference = map_pt.getSpatialReference(); + assertTrue(spatial_reference.getID() == 4326); + } + + return bAnswer; + } + + boolean testMultiPoint() throws JsonParseException, IOException { + boolean bAnswer = true; + + MultiPoint multiPoint1 = new MultiPoint(); + multiPoint1.add(-97.06138, 32.837); + multiPoint1.add(-97.06133, 32.836); + multiPoint1.add(-97.06124, 32.834); + multiPoint1.add(-97.06127, 32.832); + + { + String s = GeometryEngine.geometryToJson(spatialReferenceWGS84, + multiPoint1); + JsonParser mPointWgs84Parser = factory.createParser(s); + MapGeometry mPointWgs84MP = GeometryEngine + .jsonToGeometry(mPointWgs84Parser); + assertTrue(multiPoint1.getPointCount() == ((MultiPoint) mPointWgs84MP + .getGeometry()).getPointCount()); + assertTrue(multiPoint1.getPoint(0).getX() == ((MultiPoint) mPointWgs84MP + .getGeometry()).getPoint(0).getX()); + assertTrue(multiPoint1.getPoint(0).getY() == ((MultiPoint) mPointWgs84MP + .getGeometry()).getPoint(0).getY()); + int lastIndex = multiPoint1.getPointCount() - 1; + assertTrue(multiPoint1.getPoint(lastIndex).getX() == ((MultiPoint) mPointWgs84MP + .getGeometry()).getPoint(lastIndex).getX()); + assertTrue(multiPoint1.getPoint(lastIndex).getY() == ((MultiPoint) mPointWgs84MP + .getGeometry()).getPoint(lastIndex).getY()); + + assertTrue(spatialReferenceWGS84.getID() == mPointWgs84MP + .getSpatialReference().getID()); + if (!checkResultSpatialRef(mPointWgs84MP, 4326, 0)) { + bAnswer = false; + } + + } + + { + MultiPoint p = new MultiPoint(); + p.addAttribute(VertexDescription.Semantics.Z); + p.addAttribute(VertexDescription.Semantics.M); + String s = GeometryEngine.geometryToJson(spatialReferenceWebMerc1, + p); + assertTrue(s + .equals("{\"hasZ\":true,\"hasM\":true,\"points\":[],\"spatialReference\":{\"wkid\":102100,\"latestWkid\":3857}}")); + + p.add(10.0, 20.0, 30.0); + p.add(20.0, 40.0, 60.0); + s = GeometryEngine.geometryToJson(spatialReferenceWebMerc1, p); + assertTrue(s + .equals("{\"hasZ\":true,\"hasM\":true,\"points\":[[10,20,30,null],[20,40,60,null]],\"spatialReference\":{\"wkid\":102100,\"latestWkid\":3857}}")); + } + { + String points = "{\"hasM\" : false, \"hasZ\" : true, \"uncle remus\" : null, \"points\" : [ [0,0,1], [0.0,10.0,1], [10.0,10.0,1], [10.0,0.0,1, 6666] ],\"spatialReference\" : {\"wkid\" : 4326}}"; + MapGeometry mp = GeometryEngine.jsonToGeometry(factory + .createParser(points)); + MultiPoint multipoint = (MultiPoint) mp.getGeometry(); + assertTrue(multipoint.getPointCount() == 4); + Point2D point2d; + point2d = multipoint.getXY(0); + assertTrue(point2d.x == 0.0 && point2d.y == 0.0); + point2d = multipoint.getXY(1); + assertTrue(point2d.x == 0.0 && point2d.y == 10.0); + point2d = multipoint.getXY(2); + assertTrue(point2d.x == 10.0 && point2d.y == 10.0); + point2d = multipoint.getXY(3); + assertTrue(point2d.x == 10.0 && point2d.y == 0.0); + assertTrue(multipoint.hasAttribute(VertexDescription.Semantics.Z)); + assertTrue(!multipoint.hasAttribute(VertexDescription.Semantics.M)); + double z = multipoint.getAttributeAsDbl( + VertexDescription.Semantics.Z, 0, 0); + assertTrue(z == 1); + SpatialReference spatial_reference = mp.getSpatialReference(); + assertTrue(spatial_reference.getID() == 4326); + } + + return bAnswer; + } + + boolean testPolyline() throws JsonParseException, IOException { + boolean bAnswer = true; + + Polyline polyline = new Polyline(); + polyline.startPath(-97.06138, 32.837); + polyline.lineTo(-97.06133, 32.836); + polyline.lineTo(-97.06124, 32.834); + polyline.lineTo(-97.06127, 32.832); + + polyline.startPath(-97.06326, 32.759); + polyline.lineTo(-97.06298, 32.755); + + { + JsonParser polylinePathsWgs84Parser = factory + .createParser(GeometryEngine.geometryToJson( + spatialReferenceWGS84, polyline)); + MapGeometry mPolylineWGS84MP = GeometryEngine + .jsonToGeometry(polylinePathsWgs84Parser); + + assertTrue(polyline.getPointCount() == ((Polyline) mPolylineWGS84MP + .getGeometry()).getPointCount()); + assertTrue(polyline.getPoint(0).getX() == ((Polyline) mPolylineWGS84MP + .getGeometry()).getPoint(0).getX()); + assertTrue(polyline.getPoint(0).getY() == ((Polyline) mPolylineWGS84MP + .getGeometry()).getPoint(0).getY()); + + assertTrue(polyline.getPathCount() == ((Polyline) mPolylineWGS84MP + .getGeometry()).getPathCount()); + assertTrue(polyline.getSegmentCount() == ((Polyline) mPolylineWGS84MP + .getGeometry()).getSegmentCount()); + assertTrue(polyline.getSegmentCount(0) == ((Polyline) mPolylineWGS84MP + .getGeometry()).getSegmentCount(0)); + assertTrue(polyline.getSegmentCount(1) == ((Polyline) mPolylineWGS84MP + .getGeometry()).getSegmentCount(1)); + + int lastIndex = polyline.getPointCount() - 1; + assertTrue(polyline.getPoint(lastIndex).getX() == ((Polyline) mPolylineWGS84MP + .getGeometry()).getPoint(lastIndex).getX()); + assertTrue(polyline.getPoint(lastIndex).getY() == ((Polyline) mPolylineWGS84MP + .getGeometry()).getPoint(lastIndex).getY()); + + assertTrue(spatialReferenceWGS84.getID() == mPolylineWGS84MP + .getSpatialReference().getID()); + + if (!checkResultSpatialRef(mPolylineWGS84MP, 4326, 0)) { + bAnswer = false; + } + } + + { + Polyline p = new Polyline(); + p.addAttribute(VertexDescription.Semantics.Z); + p.addAttribute(VertexDescription.Semantics.M); + String s = GeometryEngine.geometryToJson(spatialReferenceWebMerc1, + p); + assertTrue(s + .equals("{\"hasZ\":true,\"hasM\":true,\"paths\":[],\"spatialReference\":{\"wkid\":102100,\"latestWkid\":3857}}")); + + p.startPath(0, 0); + p.lineTo(0, 1); + p.startPath(2, 2); + p.lineTo(3, 3); + + p.setAttribute(VertexDescription.Semantics.Z, 0, 0, 3); + p.setAttribute(VertexDescription.Semantics.M, 1, 0, 5); + s = GeometryEngine.geometryToJson(spatialReferenceWebMerc1, p); + assertTrue(s + .equals("{\"hasZ\":true,\"hasM\":true,\"paths\":[[[0,0,3,null],[0,1,0,5]],[[2,2,0,null],[3,3,0,null]]],\"spatialReference\":{\"wkid\":102100,\"latestWkid\":3857}}")); + } + + { + String paths = "{\"hasZ\" : true, \"paths\" : [ [ [0.0, 0.0,3], [0, 10.0,3], [10.0, 10.0,3, 6666], [10.0, 0.0,3, 6666] ], [ [1.0, 1,3], [1.0, 9.0,3], [9.0, 9.0,3], [1.0, 9.0,3] ] ], \"spatialReference\" : {\"wkid\" : 4326}, \"hasM\" : false}"; + MapGeometry mapGeometry = GeometryEngine.jsonToGeometry(factory + .createParser(paths)); + Polyline p = (Polyline) mapGeometry.getGeometry(); + assertTrue(p.getPathCount() == 2); + @SuppressWarnings("unused") + int count = p.getPathCount(); + assertTrue(p.getPointCount() == 8); + assertTrue(p.hasAttribute(VertexDescription.Semantics.Z)); + assertTrue(!p.hasAttribute(VertexDescription.Semantics.M)); + double z = p.getAttributeAsDbl(VertexDescription.Semantics.Z, 0, 0); + assertTrue(z == 3); + double length = p.calculateLength2D(); + assertTrue(Math.abs(length - 54.0) <= 0.001); + SpatialReference spatial_reference = mapGeometry + .getSpatialReference(); + assertTrue(spatial_reference.getID() == 4326); + } + + return bAnswer; + } + + boolean testPolygon() throws JsonParseException, IOException { + boolean bAnswer = true; + + Polygon polygon = new Polygon(); + polygon.startPath(-97.06138, 32.837); + polygon.lineTo(-97.06133, 32.836); + polygon.lineTo(-97.06124, 32.834); + polygon.lineTo(-97.06127, 32.832); + + polygon.startPath(-97.06326, 32.759); + polygon.lineTo(-97.06298, 32.755); + + { + JsonParser polygonPathsWgs84Parser = factory + .createParser(GeometryEngine.geometryToJson( + spatialReferenceWGS84, polygon)); + MapGeometry mPolygonWGS84MP = GeometryEngine + .jsonToGeometry(polygonPathsWgs84Parser); + + assertTrue(polygon.getPointCount() + 1 == ((Polygon) mPolygonWGS84MP + .getGeometry()).getPointCount()); + assertTrue(polygon.getPoint(0).getX() == ((Polygon) mPolygonWGS84MP + .getGeometry()).getPoint(0).getX()); + assertTrue(polygon.getPoint(0).getY() == ((Polygon) mPolygonWGS84MP + .getGeometry()).getPoint(0).getY()); + + assertTrue(polygon.getPathCount() == ((Polygon) mPolygonWGS84MP + .getGeometry()).getPathCount()); + assertTrue(polygon.getSegmentCount() + 1 == ((Polygon) mPolygonWGS84MP + .getGeometry()).getSegmentCount()); + assertTrue(polygon.getSegmentCount(0) == ((Polygon) mPolygonWGS84MP + .getGeometry()).getSegmentCount(0)); + assertTrue(polygon.getSegmentCount(1) + 1 == ((Polygon) mPolygonWGS84MP + .getGeometry()).getSegmentCount(1)); + + int lastIndex = polygon.getPointCount() - 1; + assertTrue(polygon.getPoint(lastIndex).getX() == ((Polygon) mPolygonWGS84MP + .getGeometry()).getPoint(lastIndex).getX()); + assertTrue(polygon.getPoint(lastIndex).getY() == ((Polygon) mPolygonWGS84MP + .getGeometry()).getPoint(lastIndex).getY()); + + assertTrue(spatialReferenceWGS84.getID() == mPolygonWGS84MP + .getSpatialReference().getID()); + + if (!checkResultSpatialRef(mPolygonWGS84MP, 4326, 0)) { + bAnswer = false; + } + } + + { + Polygon p = new Polygon(); + p.addAttribute(VertexDescription.Semantics.Z); + p.addAttribute(VertexDescription.Semantics.M); + String s = GeometryEngine.geometryToJson(spatialReferenceWebMerc1, + p); + assertTrue(s + .equals("{\"hasZ\":true,\"hasM\":true,\"rings\":[],\"spatialReference\":{\"wkid\":102100,\"latestWkid\":3857}}")); + + p.startPath(0, 0); + p.lineTo(0, 1); + p.lineTo(4, 4); + p.startPath(2, 2); + p.lineTo(3, 3); + p.lineTo(7, 8); + + p.setAttribute(VertexDescription.Semantics.Z, 0, 0, 3); + p.setAttribute(VertexDescription.Semantics.M, 1, 0, 7); + p.setAttribute(VertexDescription.Semantics.M, 2, 0, 5); + p.setAttribute(VertexDescription.Semantics.M, 5, 0, 5); + s = GeometryEngine.geometryToJson(spatialReferenceWebMerc1, p); + assertTrue(s + .equals("{\"hasZ\":true,\"hasM\":true,\"rings\":[[[0,0,3,null],[0,1,0,7],[4,4,0,5],[0,0,3,null]],[[2,2,0,null],[3,3,0,null],[7,8,0,5],[2,2,0,null]]],\"spatialReference\":{\"wkid\":102100,\"latestWkid\":3857}}")); + } + + { + // Test Import Polygon from Polygon + String rings = "{\"hasZ\": true, \"rings\" : [ [ [0,0, 5], [0.0, 10.0, 5], [10.0,10.0, 5, 66666], [10.0,0.0, 5] ], [ [12, 12] ], [ [13 , 17], [13 , 17] ], [ [1.0, 1.0, 5, 66666], [9.0,1.0, 5], [9.0,9.0, 5], [1.0,9.0, 5], [1.0, 1.0, 5] ] ] }"; + MapGeometry mapGeometry = GeometryEngine.jsonToGeometry(factory + .createParser(rings)); + Polygon p = (Polygon) mapGeometry.getGeometry(); + @SuppressWarnings("unused") + double area = p.calculateArea2D(); + @SuppressWarnings("unused") + double length = p.calculateLength2D(); + assertTrue(p.getPathCount() == 4); + int count = p.getPointCount(); + assertTrue(count == 15); + assertTrue(p.hasAttribute(VertexDescription.Semantics.Z)); + assertTrue(!p.hasAttribute(VertexDescription.Semantics.M)); + } + + return bAnswer; + } + + boolean testEnvelope() throws JsonParseException, IOException { + boolean bAnswer = true; + + Envelope envelope = new Envelope(); + envelope.setCoords(-109.55, 25.76, -86.39, 49.94); + + { + JsonParser envelopeWGS84Parser = factory + .createParser(GeometryEngine.geometryToJson( + spatialReferenceWGS84, envelope)); + MapGeometry envelopeWGS84MP = GeometryEngine + .jsonToGeometry(envelopeWGS84Parser); + assertTrue(envelope.isEmpty() == envelopeWGS84MP.getGeometry() + .isEmpty()); + assertTrue(envelope.getXMax() == ((Envelope) envelopeWGS84MP + .getGeometry()).getXMax()); + assertTrue(envelope.getYMax() == ((Envelope) envelopeWGS84MP + .getGeometry()).getYMax()); + assertTrue(envelope.getXMin() == ((Envelope) envelopeWGS84MP + .getGeometry()).getXMin()); + assertTrue(envelope.getYMin() == ((Envelope) envelopeWGS84MP + .getGeometry()).getYMin()); + assertTrue(spatialReferenceWGS84.getID() == envelopeWGS84MP + .getSpatialReference().getID()); + if (!checkResultSpatialRef(envelopeWGS84MP, 4326, 0)) { + bAnswer = false; + } + } + + {// export + Envelope e = new Envelope(); + e.addAttribute(VertexDescription.Semantics.Z); + e.addAttribute(VertexDescription.Semantics.M); + String s = GeometryEngine.geometryToJson(spatialReferenceWebMerc1, + e); + assertTrue(s + .equals("{\"xmin\":null,\"ymin\":null,\"xmax\":null,\"ymax\":null,\"zmin\":null,\"zmax\":null,\"mmin\":null,\"mmax\":null,\"spatialReference\":{\"wkid\":102100,\"latestWkid\":3857}}")); + + e.setCoords(0, 1, 2, 3); + + Envelope1D z = new Envelope1D(); + Envelope1D m = new Envelope1D(); + z.setCoords(5, 7); + m.setCoords(11, 13); + + e.setInterval(VertexDescription.Semantics.Z, 0, z); + e.setInterval(VertexDescription.Semantics.M, 0, m); + s = GeometryEngine.geometryToJson(spatialReferenceWebMerc1, e); + assertTrue(s + .equals("{\"xmin\":0,\"ymin\":1,\"xmax\":2,\"ymax\":3,\"zmin\":5,\"zmax\":7,\"mmin\":11,\"mmax\":13,\"spatialReference\":{\"wkid\":102100,\"latestWkid\":3857}}")); + } + + {// import + String s = "{\"xmin\":0.0,\"ymin\":1.0,\"xmax\":2.0,\"ymax\":3.0,\"zmin\":5.0,\"zmax\":7.0,\"mmin\":11.0,\"mmax\":13.0,\"spatialReference\":{\"wkid\":102100,\"latestWkid\":3857}}"; + JsonParser parser = factory.createParser(s); + MapGeometry map_env = GeometryEngine.jsonToGeometry(parser); + Envelope env = (Envelope) map_env.getGeometry(); + Envelope1D z = env.queryInterval(VertexDescription.Semantics.Z, 0); + Envelope1D m = env.queryInterval(VertexDescription.Semantics.M, 0); + assertTrue(z.vmin == 5.0); + assertTrue(z.vmax == 7.0); + assertTrue(m.vmin == 11.0); + assertTrue(m.vmax == 13.0); + } + + { + String s = "{ \"zmin\" : 33, \"xmin\" : -109.55, \"zmax\" : 53, \"ymin\" : 25.76, \"xmax\" : -86.39, \"ymax\" : 49.94, \"mmax\" : 13}"; + JsonParser parser = factory.createParser(s); + MapGeometry map_env = GeometryEngine.jsonToGeometry(parser); + Envelope env = (Envelope) map_env.getGeometry(); + Envelope2D e = new Envelope2D(); + env.queryEnvelope2D(e); + assertTrue(e.xmin == -109.55 && e.ymin == 25.76 && e.xmax == -86.39 + && e.ymax == 49.94); + + Envelope1D e1D; + assertTrue(env.hasAttribute(VertexDescription.Semantics.Z)); + e1D = env.queryInterval(VertexDescription.Semantics.Z, 0); + assertTrue(e1D.vmin == 33 && e1D.vmax == 53); + + assertTrue(!env.hasAttribute(VertexDescription.Semantics.M)); + } + + return bAnswer; + } + + boolean testCR181369() throws JsonParseException, IOException { + // CR181369 + boolean bAnswer = true; + + String jsonStringPointAndWKT = "{\"x\":10.0,\"y\":20.0,\"spatialReference\":{\"wkt\" : \"PROJCS[\\\"NAD83_UTM_zone_15N\\\",GEOGCS[\\\"GCS_North_American_1983\\\",DATUM[\\\"D_North_American_1983\\\",SPHEROID[\\\"GRS_1980\\\",6378137.0,298.257222101]],PRIMEM[\\\"Greenwich\\\",0.0],UNIT[\\\"Degree\\\",0.0174532925199433]],PROJECTION[\\\"Transverse_Mercator\\\"],PARAMETER[\\\"false_easting\\\",500000.0],PARAMETER[\\\"false_northing\\\",0.0],PARAMETER[\\\"central_meridian\\\",-93.0],PARAMETER[\\\"scale_factor\\\",0.9996],PARAMETER[\\\"latitude_of_origin\\\",0.0],UNIT[\\\"Meter\\\",1.0]]\"} }"; + JsonParser jsonParserPointAndWKT = factory + .createParser(jsonStringPointAndWKT); + MapGeometry mapGeom2 = GeometryEngine + .jsonToGeometry(jsonParserPointAndWKT); + String jsonStringPointAndWKT2 = GeometryEngine.geometryToJson( + mapGeom2.getSpatialReference(), mapGeom2.getGeometry()); + JsonParser jsonParserPointAndWKT2 = factory + .createParser(jsonStringPointAndWKT2); + MapGeometry mapGeom3 = GeometryEngine + .jsonToGeometry(jsonParserPointAndWKT2); + assertTrue(((Point) mapGeom2.getGeometry()).getX() == ((Point) mapGeom3 + .getGeometry()).getX()); + assertTrue(((Point) mapGeom2.getGeometry()).getY() == ((Point) mapGeom3 + .getGeometry()).getY()); + + String s1 = mapGeom2.getSpatialReference().getText(); + String s2 = mapGeom3.getSpatialReference().getText(); + assertTrue(s1.equals(s2)); + + int id2 = mapGeom2.getSpatialReference().getID(); + int id3 = mapGeom3.getSpatialReference().getID(); + assertTrue(id2 == id3); + if (!checkResultSpatialRef(mapGeom3, mapGeom2.getSpatialReference() + .getID(), 0)) { + bAnswer = false; + } + return bAnswer; + } + + boolean checkResultSpatialRef(MapGeometry mapGeometry, int expectWki1, + int expectWki2) { + SpatialReference sr = mapGeometry.getSpatialReference(); + String Wkt = sr.getText(); + int wki1 = sr.getLatestID(); + if (!(wki1 == expectWki1 || wki1 == expectWki2)) + return false; + if (!(Wkt != null && Wkt.length() > 0)) + return false; + SpatialReference sr2 = SpatialReference.create(Wkt); + int wki2 = sr2.getID(); + if (expectWki2 > 0) { + if (!(wki2 == expectWki1 || wki2 == expectWki2)) + return false; + } else { + if (!(wki2 == expectWki1)) + return false; + } + return true; + } +} diff --git a/src/test/java/com/esri/core/geometry/TestJSonGeometry.java b/src/test/java/com/esri/core/geometry/TestJSonGeometry.java index 571a103e..a996575f 100644 --- a/src/test/java/com/esri/core/geometry/TestJSonGeometry.java +++ b/src/test/java/com/esri/core/geometry/TestJSonGeometry.java @@ -1,47 +1,48 @@ -package com.esri.core.geometry; - -import java.util.HashMap; -import java.util.Iterator; -import java.util.Map; -import java.util.Set; -import junit.framework.TestCase; -import org.junit.Test; - -public class TestJSonGeometry extends TestCase { - @Override - protected void setUp() throws Exception { - super.setUp(); - } - - @Override - protected void tearDown() throws Exception { - super.tearDown(); - } - - @Test - public void testGetSpatialReferenceFor4326() { - String completeStr = "GEOGCS[\"GCS_Sphere\",DATUM[\"D_Sphere\"," - + "SPHEROID[\"Sphere\",6371000.0,0.0]],PRIMEM[\"Greenwich\",0.0]," - + "UNIT[\"Degree\",0.0174532925199433]]"; - - // 4326 GCS_WGS_1984 - SpatialReference sr = SpatialReference.create(completeStr); - assertNotNull(sr); - } - } - -final class HashMapClassForTesting { - static Map SR_WKI_WKTs = new HashMap() { - /** - * added to get rid of warning - */ - private static final long serialVersionUID = 8630934425353750539L; - - { - put(4035, - "GEOGCS[\"GCS_Sphere\",DATUM[\"D_Sphere\"," - + "SPHEROID[\"Sphere\",6371000.0,0.0]],PRIMEM[\"Greenwich\",0.0]," - + "UNIT[\"Degree\",0.0174532925199433]]"); - } - }; -} +package com.esri.core.geometry; + +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import java.util.Set; +import junit.framework.TestCase; +import org.junit.Test; + +public class TestJSonGeometry extends TestCase { + @Override + protected void setUp() throws Exception { + super.setUp(); + } + + @Override + protected void tearDown() throws Exception { + super.tearDown(); + } + + @Test + public void testGetSpatialReferenceFor4326() { + String completeStr = "GEOGCS[\"GCS_Sphere\",DATUM[\"D_Sphere\"," + + "SPHEROID[\"Sphere\",6371000.0,0.0]],PRIMEM[\"Greenwich\",0.0]," + + "UNIT[\"Degree\",0.0174532925199433]]"; + + // 4326 GCS_WGS_1984 + SpatialReference sr = SpatialReference.create(completeStr); + assertNotNull(sr); + } + +} + +final class HashMapClassForTesting { + static Map SR_WKI_WKTs = new HashMap() { + /** + * added to get rid of warning + */ + private static final long serialVersionUID = 8630934425353750539L; + + { + put(4035, + "GEOGCS[\"GCS_Sphere\",DATUM[\"D_Sphere\"," + + "SPHEROID[\"Sphere\",6371000.0,0.0]],PRIMEM[\"Greenwich\",0.0]," + + "UNIT[\"Degree\",0.0174532925199433]]"); + } + }; +} diff --git a/src/test/java/com/esri/core/geometry/TestJSonToGeomFromWkiOrWkt_CR177613.java b/src/test/java/com/esri/core/geometry/TestJSonToGeomFromWkiOrWkt_CR177613.java index 8abd75cf..8b036785 100644 --- a/src/test/java/com/esri/core/geometry/TestJSonToGeomFromWkiOrWkt_CR177613.java +++ b/src/test/java/com/esri/core/geometry/TestJSonToGeomFromWkiOrWkt_CR177613.java @@ -1,124 +1,124 @@ -package com.esri.core.geometry; - -import java.io.IOException; -import junit.framework.TestCase; -import org.junit.Test; - -import com.fasterxml.jackson.core.JsonFactory; -import com.fasterxml.jackson.core.JsonParseException; -import com.fasterxml.jackson.core.JsonParser; -import com.fasterxml.jackson.core.JsonToken; - -public class TestJSonToGeomFromWkiOrWkt_CR177613 extends TestCase { - JsonFactory factory = new JsonFactory(); - - @Override - protected void setUp() throws Exception { - super.setUp(); - } - - @Override - protected void tearDown() throws Exception { - super.tearDown(); - } - - @Test - public void testPolygonWithEmptyWKT_NoWKI() throws JsonParseException, - IOException { - String jsonStringPg = "{ \"rings\" :[ [ [-97.06138,32.837], [-97.06133,32.836], " - + "[-97.06124,32.834], [-97.06127,32.832], [-97.06138,32.837] ], " - + "[ [-97.06326,32.759], [-97.06298,32.755], [-97.06153,32.749], [-97.06326,32.759] ]], " - + "\"spatialReference\" : {\"wkt\" : \"\"}}"; - JsonParser jsonParserPg = factory.createParser(jsonStringPg); - jsonParserPg.nextToken(); - - MapGeometry mapGeom = GeometryEngine.jsonToGeometry(jsonParserPg); - Utils.showProjectedGeometryInfo(mapGeom); - SpatialReference sr = mapGeom.getSpatialReference(); - assertTrue(sr == null); - } - - @Test - public void testOnlyWKI() throws JsonParseException, IOException { - String jsonStringSR = "{\"wkid\" : 4326}"; - JsonParser jsonParserSR = factory.createJsonParser(jsonStringSR); - jsonParserSR.nextToken(); - - MapGeometry mapGeom = GeometryEngine.jsonToGeometry(jsonParserSR); - Utils.showProjectedGeometryInfo(mapGeom); - SpatialReference sr = mapGeom.getSpatialReference(); - assertTrue(sr == null); - } - - @Test - public void testMP2onCR175871() throws Exception { - Polygon pg = new Polygon(); - pg.startPath(-50, 10); - pg.lineTo(-50, 12); - pg.lineTo(-45, 12); - pg.lineTo(-45, 10); - - Polygon pg1 = new Polygon(); - pg1.startPath(-45, 10); - pg1.lineTo(-40, 10); - pg1.lineTo(-40, 8); - pg.add(pg1, false); - - try { - String jSonStr = GeometryEngine.geometryToJson(4326, pg); - JsonFactory jf = new JsonFactory(); - - JsonParser jp = jf.createJsonParser(jSonStr); - jp.nextToken(); - MapGeometry mg = GeometryEngine.jsonToGeometry(jp); - Geometry gm = mg.getGeometry(); - assertEquals(Geometry.Type.Polygon, gm.getType()); - - Polygon pgNew = (Polygon) gm; - - assertEquals(pgNew.getPathCount(), pg.getPathCount()); - assertEquals(pgNew.getPointCount(), pg.getPointCount()); - assertEquals(pgNew.getSegmentCount(), pg.getSegmentCount()); - - assertEquals(pgNew.getPoint(0).getX(), pg.getPoint(0).getX(), - 0.000000001); - assertEquals(pgNew.getPoint(1).getX(), pg.getPoint(1).getX(), - 0.000000001); - assertEquals(pgNew.getPoint(2).getX(), pg.getPoint(2).getX(), - 0.000000001); - assertEquals(pgNew.getPoint(3).getX(), pg.getPoint(3).getX(), - 0.000000001); - - assertEquals(pgNew.getPoint(0).getY(), pg.getPoint(0).getY(), - 0.000000001); - assertEquals(pgNew.getPoint(1).getY(), pg.getPoint(1).getY(), - 0.000000001); - assertEquals(pgNew.getPoint(2).getY(), pg.getPoint(2).getY(), - 0.000000001); - assertEquals(pgNew.getPoint(3).getY(), pg.getPoint(3).getY(), - 0.000000001); - } catch (Exception ex) { - String err = ex.getMessage(); - System.out.print(err); - throw ex; - } - } - - public static int fromJsonToWkid(JsonParser parser) - throws JsonParseException, IOException { - int wkid = 0; - if (parser.getCurrentToken() != JsonToken.START_OBJECT) { - return 0; - } - - while (parser.nextToken() != JsonToken.END_OBJECT) { - String fieldName = parser.getCurrentName(); - - if ("wkid".equals(fieldName)) { - parser.nextToken(); - wkid = parser.getIntValue(); - } - } - return wkid; - } -} +package com.esri.core.geometry; + +import java.io.IOException; +import junit.framework.TestCase; +import org.junit.Test; + +import com.fasterxml.jackson.core.JsonFactory; +import com.fasterxml.jackson.core.JsonParseException; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonToken; + +public class TestJSonToGeomFromWkiOrWkt_CR177613 extends TestCase { + JsonFactory factory = new JsonFactory(); + + @Override + protected void setUp() throws Exception { + super.setUp(); + } + + @Override + protected void tearDown() throws Exception { + super.tearDown(); + } + + @Test + public void testPolygonWithEmptyWKT_NoWKI() throws JsonParseException, + IOException { + String jsonStringPg = "{ \"rings\" :[ [ [-97.06138,32.837], [-97.06133,32.836], " + + "[-97.06124,32.834], [-97.06127,32.832], [-97.06138,32.837] ], " + + "[ [-97.06326,32.759], [-97.06298,32.755], [-97.06153,32.749], [-97.06326,32.759] ]], " + + "\"spatialReference\" : {\"wkt\" : \"\"}}"; + JsonParser jsonParserPg = factory.createParser(jsonStringPg); + jsonParserPg.nextToken(); + + MapGeometry mapGeom = GeometryEngine.jsonToGeometry(jsonParserPg); + Utils.showProjectedGeometryInfo(mapGeom); + SpatialReference sr = mapGeom.getSpatialReference(); + assertTrue(sr == null); + } + + @Test + public void testOnlyWKI() throws JsonParseException, IOException { + String jsonStringSR = "{\"wkid\" : 4326}"; + JsonParser jsonParserSR = factory.createJsonParser(jsonStringSR); + jsonParserSR.nextToken(); + + MapGeometry mapGeom = GeometryEngine.jsonToGeometry(jsonParserSR); + Utils.showProjectedGeometryInfo(mapGeom); + SpatialReference sr = mapGeom.getSpatialReference(); + assertTrue(sr == null); + } + + @Test + public void testMP2onCR175871() throws Exception { + Polygon pg = new Polygon(); + pg.startPath(-50, 10); + pg.lineTo(-50, 12); + pg.lineTo(-45, 12); + pg.lineTo(-45, 10); + + Polygon pg1 = new Polygon(); + pg1.startPath(-45, 10); + pg1.lineTo(-40, 10); + pg1.lineTo(-40, 8); + pg.add(pg1, false); + + try { + String jSonStr = GeometryEngine.geometryToJson(4326, pg); + JsonFactory jf = new JsonFactory(); + + JsonParser jp = jf.createJsonParser(jSonStr); + jp.nextToken(); + MapGeometry mg = GeometryEngine.jsonToGeometry(jp); + Geometry gm = mg.getGeometry(); + assertEquals(Geometry.Type.Polygon, gm.getType()); + + Polygon pgNew = (Polygon) gm; + + assertEquals(pgNew.getPathCount(), pg.getPathCount()); + assertEquals(pgNew.getPointCount(), pg.getPointCount()); + assertEquals(pgNew.getSegmentCount(), pg.getSegmentCount()); + + assertEquals(pgNew.getPoint(0).getX(), pg.getPoint(0).getX(), + 0.000000001); + assertEquals(pgNew.getPoint(1).getX(), pg.getPoint(1).getX(), + 0.000000001); + assertEquals(pgNew.getPoint(2).getX(), pg.getPoint(2).getX(), + 0.000000001); + assertEquals(pgNew.getPoint(3).getX(), pg.getPoint(3).getX(), + 0.000000001); + + assertEquals(pgNew.getPoint(0).getY(), pg.getPoint(0).getY(), + 0.000000001); + assertEquals(pgNew.getPoint(1).getY(), pg.getPoint(1).getY(), + 0.000000001); + assertEquals(pgNew.getPoint(2).getY(), pg.getPoint(2).getY(), + 0.000000001); + assertEquals(pgNew.getPoint(3).getY(), pg.getPoint(3).getY(), + 0.000000001); + } catch (Exception ex) { + String err = ex.getMessage(); + System.out.print(err); + throw ex; + } + } + + public static int fromJsonToWkid(JsonParser parser) + throws JsonParseException, IOException { + int wkid = 0; + if (parser.getCurrentToken() != JsonToken.START_OBJECT) { + return 0; + } + + while (parser.nextToken() != JsonToken.END_OBJECT) { + String fieldName = parser.getCurrentName(); + + if ("wkid".equals(fieldName)) { + parser.nextToken(); + wkid = parser.getIntValue(); + } + } + return wkid; + } +} diff --git a/src/test/java/com/esri/core/geometry/TestJsonParser.java b/src/test/java/com/esri/core/geometry/TestJsonParser.java index d6593115..9f2d887f 100644 --- a/src/test/java/com/esri/core/geometry/TestJsonParser.java +++ b/src/test/java/com/esri/core/geometry/TestJsonParser.java @@ -1,664 +1,664 @@ -package com.esri.core.geometry; - -import java.util.Hashtable; -import java.io.IOException; -import java.util.Map; -import junit.framework.TestCase; -import org.junit.Assert; -import org.junit.Test; - -import com.fasterxml.jackson.core.JsonFactory; -import com.fasterxml.jackson.core.JsonParseException; -import com.fasterxml.jackson.core.JsonParser; -import com.fasterxml.jackson.core.JsonToken; - -public class TestJsonParser extends TestCase { - - JsonFactory factory = new JsonFactory(); - SpatialReference spatialReferenceWebMerc1 = SpatialReference.create(102100); - SpatialReference spatialReferenceWebMerc2 = SpatialReference - .create(spatialReferenceWebMerc1.getLatestID()); - SpatialReference spatialReferenceWGS84 = SpatialReference.create(4326); - - @Override - protected void setUp() throws Exception { - super.setUp(); - } - - @Override - protected void tearDown() throws Exception { - super.tearDown(); - } - - @Test - public void test3DPoint() throws JsonParseException, IOException { - String jsonString3DPt = "{\"x\" : -118.15, \"y\" : 33.80, \"z\" : 10.0, \"spatialReference\" : {\"wkid\" : 4326}}"; - - JsonParser jsonParser3DPt = factory.createParser(jsonString3DPt); - MapGeometry point3DMP = GeometryEngine.jsonToGeometry(jsonParser3DPt); - assertTrue(-118.15 == ((Point) point3DMP.getGeometry()).getX()); - assertTrue(33.80 == ((Point) point3DMP.getGeometry()).getY()); - assertTrue(spatialReferenceWGS84.getID() == point3DMP - .getSpatialReference().getID()); - } - - @Test - public void test3DPoint1() throws JsonParseException, IOException { - Point point1 = new Point(10.0, 20.0); - Point pointEmpty = new Point(); - { - JsonParser pointWebMerc1Parser = factory - .createJsonParser(GeometryEngine.geometryToJson( - spatialReferenceWebMerc1, point1)); - MapGeometry pointWebMerc1MP = GeometryEngine - .jsonToGeometry(pointWebMerc1Parser); - assertTrue(point1.getX() == ((Point) pointWebMerc1MP.getGeometry()) - .getX()); - assertTrue(point1.getY() == ((Point) pointWebMerc1MP.getGeometry()) - .getY()); - int srIdOri = spatialReferenceWebMerc1.getID(); - int srIdAfter = pointWebMerc1MP.getSpatialReference().getID(); - assertTrue(srIdOri == srIdAfter || srIdAfter == 3857); - - pointWebMerc1Parser = factory.createJsonParser(GeometryEngine - .geometryToJson(null, point1)); - pointWebMerc1MP = GeometryEngine - .jsonToGeometry(pointWebMerc1Parser); - assertTrue(null == pointWebMerc1MP.getSpatialReference()); - - String pointEmptyString = GeometryEngine.geometryToJson( - spatialReferenceWebMerc1, pointEmpty); - pointWebMerc1Parser = factory.createJsonParser(pointEmptyString); - - pointWebMerc1MP = GeometryEngine - .jsonToGeometry(pointWebMerc1Parser); - assertTrue(pointWebMerc1MP.getGeometry().isEmpty()); - int srIdOri2 = spatialReferenceWebMerc1.getID(); - int srIdAfter2 = pointWebMerc1MP.getSpatialReference().getID(); - assertTrue(srIdOri2 == srIdAfter2 || srIdAfter2 == 3857); - } - } - - @Test - public void test3DPoint2() throws JsonParseException, IOException { - { - Point point1 = new Point(10.0, 20.0); - JsonParser pointWebMerc2Parser = factory - .createJsonParser(GeometryEngine.geometryToJson( - spatialReferenceWebMerc2, point1)); - MapGeometry pointWebMerc2MP = GeometryEngine - .jsonToGeometry(pointWebMerc2Parser); - assertTrue(point1.getX() == ((Point) pointWebMerc2MP.getGeometry()) - .getX()); - assertTrue(point1.getY() == ((Point) pointWebMerc2MP.getGeometry()) - .getY()); - assertTrue(spatialReferenceWebMerc2.getLatestID() == pointWebMerc2MP - .getSpatialReference().getLatestID()); - } - } - - @Test - public void test3DPoint3() throws JsonParseException, IOException { - { - Point point1 = new Point(10.0, 20.0); - JsonParser pointWgs84Parser = factory - .createJsonParser(GeometryEngine.geometryToJson( - spatialReferenceWGS84, point1)); - MapGeometry pointWgs84MP = GeometryEngine - .jsonToGeometry(pointWgs84Parser); - assertTrue(point1.getX() == ((Point) pointWgs84MP.getGeometry()) - .getX()); - assertTrue(point1.getY() == ((Point) pointWgs84MP.getGeometry()) - .getY()); - assertTrue(spatialReferenceWGS84.getID() == pointWgs84MP - .getSpatialReference().getID()); - } - } - - @Test - public void testMultiPoint() throws JsonParseException, IOException { - MultiPoint multiPoint1 = new MultiPoint(); - multiPoint1.add(-97.06138, 32.837); - multiPoint1.add(-97.06133, 32.836); - multiPoint1.add(-97.06124, 32.834); - multiPoint1.add(-97.06127, 32.832); - - { - JsonParser mPointWgs84Parser = factory - .createJsonParser(GeometryEngine.geometryToJson( - spatialReferenceWGS84, multiPoint1)); - MapGeometry mPointWgs84MP = GeometryEngine - .jsonToGeometry(mPointWgs84Parser); - assertTrue(multiPoint1.getPointCount() == ((MultiPoint) mPointWgs84MP - .getGeometry()).getPointCount()); - assertTrue(multiPoint1.getPoint(0).getX() == ((MultiPoint) mPointWgs84MP - .getGeometry()).getPoint(0).getX()); - assertTrue(multiPoint1.getPoint(0).getY() == ((MultiPoint) mPointWgs84MP - .getGeometry()).getPoint(0).getY()); - int lastIndex = multiPoint1.getPointCount() - 1; - assertTrue(multiPoint1.getPoint(lastIndex).getX() == ((MultiPoint) mPointWgs84MP - .getGeometry()).getPoint(lastIndex).getX()); - assertTrue(multiPoint1.getPoint(lastIndex).getY() == ((MultiPoint) mPointWgs84MP - .getGeometry()).getPoint(lastIndex).getY()); - - assertTrue(spatialReferenceWGS84.getID() == mPointWgs84MP - .getSpatialReference().getID()); - - MultiPoint mPointEmpty = new MultiPoint(); - String mPointEmptyString = GeometryEngine.geometryToJson( - spatialReferenceWGS84, mPointEmpty); - mPointWgs84Parser = factory.createJsonParser(mPointEmptyString); - - mPointWgs84MP = GeometryEngine.jsonToGeometry(mPointWgs84Parser); - assertTrue(mPointWgs84MP.getGeometry().isEmpty()); - assertTrue(spatialReferenceWGS84.getID() == mPointWgs84MP - .getSpatialReference().getID()); - - } - } - - @Test - public void testPolyline() throws JsonParseException, IOException { - Polyline polyline = new Polyline(); - polyline.startPath(-97.06138, 32.837); - polyline.lineTo(-97.06133, 32.836); - polyline.lineTo(-97.06124, 32.834); - polyline.lineTo(-97.06127, 32.832); - - polyline.startPath(-97.06326, 32.759); - polyline.lineTo(-97.06298, 32.755); - - { - JsonParser polylinePathsWgs84Parser = factory - .createJsonParser(GeometryEngine.geometryToJson( - spatialReferenceWGS84, polyline)); - MapGeometry mPolylineWGS84MP = GeometryEngine - .jsonToGeometry(polylinePathsWgs84Parser); - - assertTrue(polyline.getPointCount() == ((Polyline) mPolylineWGS84MP - .getGeometry()).getPointCount()); - assertTrue(polyline.getPoint(0).getX() == ((Polyline) mPolylineWGS84MP - .getGeometry()).getPoint(0).getX()); - assertTrue(polyline.getPoint(0).getY() == ((Polyline) mPolylineWGS84MP - .getGeometry()).getPoint(0).getY()); - - assertTrue(polyline.getPathCount() == ((Polyline) mPolylineWGS84MP - .getGeometry()).getPathCount()); - assertTrue(polyline.getSegmentCount() == ((Polyline) mPolylineWGS84MP - .getGeometry()).getSegmentCount()); - assertTrue(polyline.getSegmentCount(0) == ((Polyline) mPolylineWGS84MP - .getGeometry()).getSegmentCount(0)); - assertTrue(polyline.getSegmentCount(1) == ((Polyline) mPolylineWGS84MP - .getGeometry()).getSegmentCount(1)); - - int lastIndex = polyline.getPointCount() - 1; - assertTrue(polyline.getPoint(lastIndex).getX() == ((Polyline) mPolylineWGS84MP - .getGeometry()).getPoint(lastIndex).getX()); - assertTrue(polyline.getPoint(lastIndex).getY() == ((Polyline) mPolylineWGS84MP - .getGeometry()).getPoint(lastIndex).getY()); - - assertTrue(spatialReferenceWGS84.getID() == mPolylineWGS84MP - .getSpatialReference().getID()); - - Polyline emptyPolyline = new Polyline(); - String emptyString = GeometryEngine.geometryToJson( - spatialReferenceWGS84, emptyPolyline); - mPolylineWGS84MP = GeometryEngine.jsonToGeometry(factory - .createJsonParser(emptyString)); - assertTrue(mPolylineWGS84MP.getGeometry().isEmpty()); - assertTrue(spatialReferenceWGS84.getID() == mPolylineWGS84MP - .getSpatialReference().getID()); - } - } - - @Test - public void testPolygon() throws JsonParseException, IOException { - Polygon polygon = new Polygon(); - polygon.startPath(-97.06138, 32.837); - polygon.lineTo(-97.06133, 32.836); - polygon.lineTo(-97.06124, 32.834); - polygon.lineTo(-97.06127, 32.832); - - polygon.startPath(-97.06326, 32.759); - polygon.lineTo(-97.06298, 32.755); - - { - JsonParser polygonPathsWgs84Parser = factory - .createJsonParser(GeometryEngine.geometryToJson( - spatialReferenceWGS84, polygon)); - MapGeometry mPolygonWGS84MP = GeometryEngine - .jsonToGeometry(polygonPathsWgs84Parser); - - assertTrue(polygon.getPointCount() + 1 == ((Polygon) mPolygonWGS84MP - .getGeometry()).getPointCount()); - assertTrue(polygon.getPoint(0).getX() == ((Polygon) mPolygonWGS84MP - .getGeometry()).getPoint(0).getX()); - assertTrue(polygon.getPoint(0).getY() == ((Polygon) mPolygonWGS84MP - .getGeometry()).getPoint(0).getY()); - - assertTrue(polygon.getPathCount() == ((Polygon) mPolygonWGS84MP - .getGeometry()).getPathCount()); - assertTrue(polygon.getSegmentCount() + 1 == ((Polygon) mPolygonWGS84MP - .getGeometry()).getSegmentCount()); - assertTrue(polygon.getSegmentCount(0) == ((Polygon) mPolygonWGS84MP - .getGeometry()).getSegmentCount(0)); - assertTrue(polygon.getSegmentCount(1) + 1 == ((Polygon) mPolygonWGS84MP - .getGeometry()).getSegmentCount(1)); - - int lastIndex = polygon.getPointCount() - 1; - assertTrue(polygon.getPoint(lastIndex).getX() == ((Polygon) mPolygonWGS84MP - .getGeometry()).getPoint(lastIndex).getX()); - assertTrue(polygon.getPoint(lastIndex).getY() == ((Polygon) mPolygonWGS84MP - .getGeometry()).getPoint(lastIndex).getY()); - - assertTrue(spatialReferenceWGS84.getID() == mPolygonWGS84MP - .getSpatialReference().getID()); - - Polygon emptyPolygon = new Polygon(); - String emptyPolygonString = GeometryEngine.geometryToJson( - spatialReferenceWGS84, emptyPolygon); - polygonPathsWgs84Parser = factory - .createJsonParser(emptyPolygonString); - mPolygonWGS84MP = GeometryEngine - .jsonToGeometry(polygonPathsWgs84Parser); - - assertTrue(mPolygonWGS84MP.getGeometry().isEmpty()); - assertTrue(spatialReferenceWGS84.getID() == mPolygonWGS84MP - .getSpatialReference().getID()); - } - } - - @Test - public void testEnvelope() throws JsonParseException, IOException { - Envelope envelope = new Envelope(); - envelope.setCoords(-109.55, 25.76, -86.39, 49.94); - - { - JsonParser envelopeWGS84Parser = factory - .createJsonParser(GeometryEngine.geometryToJson( - spatialReferenceWGS84, envelope)); - MapGeometry envelopeWGS84MP = GeometryEngine - .jsonToGeometry(envelopeWGS84Parser); - assertTrue(envelope.isEmpty() == envelopeWGS84MP.getGeometry() - .isEmpty()); - assertTrue(envelope.getXMax() == ((Envelope) envelopeWGS84MP - .getGeometry()).getXMax()); - assertTrue(envelope.getYMax() == ((Envelope) envelopeWGS84MP - .getGeometry()).getYMax()); - assertTrue(envelope.getXMin() == ((Envelope) envelopeWGS84MP - .getGeometry()).getXMin()); - assertTrue(envelope.getYMin() == ((Envelope) envelopeWGS84MP - .getGeometry()).getYMin()); - assertTrue(spatialReferenceWGS84.getID() == envelopeWGS84MP - .getSpatialReference().getID()); - - Envelope emptyEnvelope = new Envelope(); - String emptyEnvString = GeometryEngine.geometryToJson( - spatialReferenceWGS84, emptyEnvelope); - envelopeWGS84Parser = factory.createJsonParser(emptyEnvString); - envelopeWGS84MP = GeometryEngine - .jsonToGeometry(envelopeWGS84Parser); - - assertTrue(envelopeWGS84MP.getGeometry().isEmpty()); - assertTrue(spatialReferenceWGS84.getID() == envelopeWGS84MP - .getSpatialReference().getID()); - } - } - - @Test - public void testCR181369() throws JsonParseException, IOException { - // CR181369 - { - String jsonStringPointAndWKT = "{\"x\":10.0,\"y\":20.0,\"spatialReference\":{\"wkt\" : \"PROJCS[\\\"NAD83_UTM_zone_15N\\\",GEOGCS[\\\"GCS_North_American_1983\\\",DATUM[\\\"D_North_American_1983\\\",SPHEROID[\\\"GRS_1980\\\",6378137.0,298.257222101]],PRIMEM[\\\"Greenwich\\\",0.0],UNIT[\\\"Degree\\\",0.0174532925199433]],PROJECTION[\\\"Transverse_Mercator\\\"],PARAMETER[\\\"false_easting\\\",500000.0],PARAMETER[\\\"false_northing\\\",0.0],PARAMETER[\\\"central_meridian\\\",-93.0],PARAMETER[\\\"scale_factor\\\",0.9996],PARAMETER[\\\"latitude_of_origin\\\",0.0],UNIT[\\\"Meter\\\",1.0]]\"} }"; - JsonParser jsonParserPointAndWKT = factory - .createJsonParser(jsonStringPointAndWKT); - MapGeometry mapGeom2 = GeometryEngine - .jsonToGeometry(jsonParserPointAndWKT); - String jsonStringPointAndWKT2 = GeometryEngine.geometryToJson( - mapGeom2.getSpatialReference(), mapGeom2.getGeometry()); - JsonParser jsonParserPointAndWKT2 = factory - .createJsonParser(jsonStringPointAndWKT2); - MapGeometry mapGeom3 = GeometryEngine - .jsonToGeometry(jsonParserPointAndWKT2); - assertTrue(((Point) mapGeom2.getGeometry()).getX() == ((Point) mapGeom3 - .getGeometry()).getX()); - assertTrue(((Point) mapGeom2.getGeometry()).getY() == ((Point) mapGeom3 - .getGeometry()).getY()); - assertTrue(mapGeom2.getSpatialReference().getText() - .equals(mapGeom3.getSpatialReference().getText())); - assertTrue(mapGeom2.getSpatialReference().getID() == mapGeom3 - .getSpatialReference().getID()); - } - } - - @Test - public void testSpatialRef() throws JsonParseException, IOException { - // String jsonStringPt = - // "{\"x\":-20037508.342787,\"y\":20037508.342787},\"spatialReference\":{\"wkid\":102100}}"; - String jsonStringPt = "{\"x\":10.0,\"y\":20.0,\"spatialReference\":{\"wkid\": 102100}}";// 102100 - @SuppressWarnings("unused") - String jsonStringPt2 = "{\"x\":10.0,\"y\":20.0,\"spatialReference\":{\"wkid\":4326}}"; - String jsonStringMpt = "{ \"points\" : [ [-97.06138,32.837], [-97.06133,32.836], [-97.06124,32.834], [-97.06127,32.832] ], \"spatialReference\" : {\"wkid\" : 4326}}";// 4326 - String jsonStringMpt3D = "{\"hasZs\" : true,\"points\" : [ [-97.06138,32.837,35.0], [-97.06133,32.836,35.1], [-97.06124,32.834,35.2], [-97.06127,32.832,35.3] ],\"spatialReference\" : {\"wkid\" : 4326}}"; - String jsonStringPl = "{\"paths\" : [ [ [-97.06138,32.837], [-97.06133,32.836], [-97.06124,32.834], [-97.06127,32.832] ], [ [-97.06326,32.759], [-97.06298,32.755] ]],\"spatialReference\" : {\"wkid\" : 4326}}"; - String jsonStringPl3D = "{\"hasMs\" : true,\"paths\" : [[ [-97.06138,32.837,5], [-97.06133,32.836,6], [-97.06124,32.834,7], [-97.06127,32.832,8] ],[ [-97.06326,32.759], [-97.06298,32.755] ]],\"spatialReference\" : {\"wkid\" : 4326}}"; - String jsonStringPg = "{ \"rings\" :[ [ [-97.06138,32.837], [-97.06133,32.836], [-97.06124,32.834], [-97.06127,32.832], [-97.06138,32.837] ], [ [-97.06326,32.759], [-97.06298,32.755], [-97.06153,32.749], [-97.06326,32.759] ]], \"spatialReference\" : {\"wkt\" : \"\"}}"; - String jsonStringPg3D = "{\"hasZs\" : true,\"hasMs\" : true,\"rings\" : [ [ [-97.06138, 32.837, 35.1, 4], [-97.06133, 32.836, 35.2, 4.1], [-97.06124, 32.834, 35.3, 4.2], [-97.06127, 32.832, 35.2, 44.3], [-97.06138, 32.837, 35.1, 4] ],[ [-97.06326, 32.759, 35.4], [-97.06298, 32.755, 35.5], [-97.06153, 32.749, 35.6], [-97.06326, 32.759, 35.4] ]],\"spatialReference\" : {\"wkid\" : 4326}}"; - String jsonStringPg2 = "{ \"spatialReference\" : {\"wkid\" : 4326}, \"rings\" : [[[-118.35,32.81],[-118.42,32.806],[-118.511,32.892],[-118.35,32.81]]]}"; - String jsonStringPg3 = "{ \"spatialReference\": {\"layerName\":\"GAS_POINTS\",\"name\":null,\"sdesrid\":102100,\"wkid\":102100,\"wkt\":null}}"; - String jsonString2SpatialReferences = "{ \"spatialReference\": {\"layerName\":\"GAS_POINTS\",\"name\":null,\"sdesrid\":102100,\"wkid\":102100,\"wkt\":\"GEOGCS[\\\"GCS_WGS_1984\\\",DATUM[\\\"D_WGS_1984\\\",SPHEROID[\\\"WGS_1984\\\",6378137,298.257223563]],PRIMEM[\\\"Greenwich\\\",0],UNIT[\\\"Degree\\\",0.017453292519943295]]\"}}"; - String jsonString2SpatialReferences2 = "{ \"spatialReference\": {\"layerName\":\"GAS_POINTS\",\"name\":null,\"sdesrid\":10,\"wkid\":10,\"wkt\":\"GEOGCS[\\\"GCS_WGS_1984\\\",DATUM[\\\"D_WGS_1984\\\",SPHEROID[\\\"WGS_1984\\\",6378137,298.257223563]],PRIMEM[\\\"Greenwich\\\",0],UNIT[\\\"Degree\\\",0.017453292519943295]]\"}}"; - String jsonStringSR = "{\"wkid\" : 4326}"; - String jsonStringEnv = "{\"xmin\" : -109.55, \"ymin\" : 25.76, \"xmax\" : -86.39, \"ymax\" : 49.94,\"spatialReference\" : {\"wkid\" : 4326}}"; - String jsonStringHongKon = "{\"xmin\" : -122.55, \"ymin\" : 37.65, \"xmax\" : -122.28, \"ymax\" : 37.84,\"spatialReference\" : {\"wkid\" : 4326}}"; - @SuppressWarnings("unused") - String jsonStringWKT = " {\"wkt\" : \"GEOGCS[\\\"GCS_WGS_1984\\\",DATUM[\\\"D_WGS_1984\\\",SPHEROID[\\\"WGS_1984\\\",6378137,298.257223563]],PRIMEM[\\\"Greenwich\\\",0],UNIT[\\\"Degree\\\",0.017453292519943295]]\"}"; - String jsonStringInvalidWKID = "{\"x\":10.0,\"y\":20.0},\"spatialReference\":{\"wkid\":35253523}}"; - String jsonStringOregon = "{\"xmin\":7531831.219849482,\"ymin\":585702.9799639136,\"xmax\":7750143.589982405,\"ymax\":733289.6299999952,\"spatialReference\":{\"wkid\":102726}}"; - - JsonParser jsonParserPt = factory.createJsonParser(jsonStringPt); - JsonParser jsonParserMpt = factory.createJsonParser(jsonStringMpt); - JsonParser jsonParserMpt3D = factory.createJsonParser(jsonStringMpt3D); - JsonParser jsonParserPl = factory.createJsonParser(jsonStringPl); - JsonParser jsonParserPl3D = factory.createJsonParser(jsonStringPl3D); - JsonParser jsonParserPg = factory.createJsonParser(jsonStringPg); - JsonParser jsonParserPg3D = factory.createJsonParser(jsonStringPg3D); - JsonParser jsonParserPg2 = factory.createJsonParser(jsonStringPg2); - @SuppressWarnings("unused") - JsonParser jsonParserSR = factory.createJsonParser(jsonStringSR); - JsonParser jsonParserEnv = factory.createJsonParser(jsonStringEnv); - JsonParser jsonParserPg3 = factory.createJsonParser(jsonStringPg3); - @SuppressWarnings("unused") - JsonParser jsonParserCrazy1 = factory - .createJsonParser(jsonString2SpatialReferences); - @SuppressWarnings("unused") - JsonParser jsonParserCrazy2 = factory - .createJsonParser(jsonString2SpatialReferences2); - JsonParser jsonParserInvalidWKID = factory - .createJsonParser(jsonStringInvalidWKID); - @SuppressWarnings("unused") - JsonParser jsonParseHongKon = factory - .createJsonParser(jsonStringHongKon); - JsonParser jsonParseOregon = factory.createJsonParser(jsonStringOregon); - - MapGeometry mapGeom = GeometryEngine.jsonToGeometry(jsonParserPt); - // showProjectedGeometryInfo(mapGeom); - Assert.assertTrue(mapGeom.getSpatialReference().getID() == 102100); - - MapGeometry mapGeomOregon = GeometryEngine - .jsonToGeometry(jsonParseOregon); - Assert.assertTrue(mapGeomOregon.getSpatialReference().getID() == 102726); - - mapGeom = GeometryEngine.jsonToGeometry(jsonParserMpt); - Assert.assertTrue(mapGeom.getSpatialReference().getID() == 4326); - - mapGeom = GeometryEngine.jsonToGeometry(jsonParserMpt3D); - Assert.assertTrue(mapGeom.getSpatialReference().getID() == 4326); - { - Assert.assertTrue(((MultiPoint) mapGeom.getGeometry()).getPoint(0) - .getX() == -97.06138); - Assert.assertTrue(((MultiPoint) mapGeom.getGeometry()).getPoint(0) - .getY() == 32.837); - Assert.assertTrue(((MultiPoint) mapGeom.getGeometry()).getPoint(3) - .getX() == -97.06127); - Assert.assertTrue(((MultiPoint) mapGeom.getGeometry()).getPoint(3) - .getY() == 32.832); - } - // showProjectedGeometryInfo(mapGeom); - - mapGeom = GeometryEngine.jsonToGeometry(jsonParserPl); - Assert.assertTrue(mapGeom.getSpatialReference().getID() == 4326); - // showProjectedGeometryInfo(mapGeom); - - mapGeom = GeometryEngine.jsonToGeometry(jsonParserPl3D); - { - // [[ [-97.06138,32.837,5], [-97.06133,32.836,6], - // [-97.06124,32.834,7], [-97.06127,32.832,8] ], - // [ [-97.06326,32.759], [-97.06298,32.755] ]]"; - Assert.assertTrue(((Polyline) mapGeom.getGeometry()).getPoint(0) - .getX() == -97.06138); - Assert.assertTrue(((Polyline) mapGeom.getGeometry()).getPoint(0) - .getY() == 32.837); - int lastIndex = ((Polyline) mapGeom.getGeometry()).getPointCount() - 1; - Assert.assertTrue(((Polyline) mapGeom.getGeometry()).getPoint( - lastIndex).getX() == -97.06298);// -97.06153, 32.749 - Assert.assertTrue(((Polyline) mapGeom.getGeometry()).getPoint( - lastIndex).getY() == 32.755); - int lastIndexFirstLine = ((Polyline) mapGeom.getGeometry()) - .getPathEnd(0) - 1; - Assert.assertTrue(((Polyline) mapGeom.getGeometry()).getPoint( - lastIndexFirstLine).getX() == -97.06127);// -97.06153, - // 32.749 - Assert.assertTrue(((Polyline) mapGeom.getGeometry()).getPoint( - lastIndexFirstLine).getY() == 32.832); - } - - mapGeom = GeometryEngine.jsonToGeometry(jsonParserPg); - Assert.assertTrue(mapGeom.getSpatialReference() == null); - - mapGeom = GeometryEngine.jsonToGeometry(jsonParserPg3D); - { - Assert.assertTrue(((Polygon) mapGeom.getGeometry()).getPoint(0) - .getX() == -97.06138); - Assert.assertTrue(((Polygon) mapGeom.getGeometry()).getPoint(0) - .getY() == 32.837); - int lastIndex = ((Polygon) mapGeom.getGeometry()).getPointCount() - 1; - Assert.assertTrue(((Polygon) mapGeom.getGeometry()).getPoint( - lastIndex).getX() == -97.06153);// -97.06153, 32.749 - Assert.assertTrue(((Polygon) mapGeom.getGeometry()).getPoint( - lastIndex).getY() == 32.749); - } - - mapGeom = GeometryEngine.jsonToGeometry(jsonParserPg2); - Assert.assertTrue(mapGeom.getSpatialReference().getID() == 4326); - // showProjectedGeometryInfo(mapGeom); - - mapGeom = GeometryEngine.jsonToGeometry(jsonParserPg3); - Assert.assertTrue(mapGeom.getSpatialReference().getID() == 102100); - // showProjectedGeometryInfo(mapGeom); - - // mapGeom = GeometryEngine.jsonToGeometry(jsonParserCrazy1); - // Assert.assertTrue(mapGeom.getSpatialReference().getText().equals("")); - // showProjectedGeometryInfo(mapGeom); - - mapGeom = GeometryEngine.jsonToGeometry(jsonParserEnv); - Assert.assertTrue(mapGeom.getSpatialReference().getID() == 4326); - // showProjectedGeometryInfo(mapGeom); - - try { - GeometryEngine.jsonToGeometry(jsonParserInvalidWKID); - } catch (Exception ex) { - Assert.assertTrue("Should not throw for invalid wkid", false); - } - } - - @Test - public void testMP2onCR175871() throws Exception { - Polygon pg = new Polygon(); - pg.startPath(-50, 10); - pg.lineTo(-50, 12); - pg.lineTo(-45, 12); - pg.lineTo(-45, 10); - - Polygon pg1 = new Polygon(); - pg1.startPath(-45, 10); - pg1.lineTo(-40, 10); - pg1.lineTo(-40, 8); - pg.add(pg1, false); - - SpatialReference spatialReference = SpatialReference.create(4326); - - try { - String jSonStr = GeometryEngine - .geometryToJson(spatialReference, pg); - JsonFactory jf = new JsonFactory(); - - JsonParser jp = jf.createJsonParser(jSonStr); - jp.nextToken(); - MapGeometry mg = GeometryEngine.jsonToGeometry(jp); - Geometry gm = mg.getGeometry(); - Assert.assertEquals(Geometry.Type.Polygon, gm.getType()); - Assert.assertTrue(mg.getSpatialReference().getID() == 4326); - - Polygon pgNew = (Polygon) gm; - - Assert.assertEquals(pgNew.getPathCount(), pg.getPathCount()); - Assert.assertEquals(pgNew.getPointCount(), pg.getPointCount()); - Assert.assertEquals(pgNew.getSegmentCount(), pg.getSegmentCount()); - - Assert.assertEquals(pgNew.getPoint(0).getX(), - pg.getPoint(0).getX(), 0.000000001); - Assert.assertEquals(pgNew.getPoint(1).getX(), - pg.getPoint(1).getX(), 0.000000001); - Assert.assertEquals(pgNew.getPoint(2).getX(), - pg.getPoint(2).getX(), 0.000000001); - Assert.assertEquals(pgNew.getPoint(3).getX(), - pg.getPoint(3).getX(), 0.000000001); - - Assert.assertEquals(pgNew.getPoint(0).getY(), - pg.getPoint(0).getY(), 0.000000001); - Assert.assertEquals(pgNew.getPoint(1).getY(), - pg.getPoint(1).getY(), 0.000000001); - Assert.assertEquals(pgNew.getPoint(2).getY(), - pg.getPoint(2).getY(), 0.000000001); - Assert.assertEquals(pgNew.getPoint(3).getY(), - pg.getPoint(3).getY(), 0.000000001); - } catch (Exception ex) { - String err = ex.getMessage(); - System.out.print(err); - throw ex; - } - } - - @Test - public static int fromJsonToWkid(JsonParser parser) - throws JsonParseException, IOException { - int wkid = 0; - if (parser.getCurrentToken() != JsonToken.START_OBJECT) { - return 0; - } - - while (parser.nextToken() != JsonToken.END_OBJECT) { - String fieldName = parser.getCurrentName(); - - if ("wkid".equals(fieldName)) { - parser.nextToken(); - wkid = parser.getIntValue(); - } - } - return wkid; - } - - @SuppressWarnings("unused") - private static void showProjectedGeometryInfo(MapGeometry mapGeom) { - System.out.println("\n"); - MapGeometry geom = mapGeom; - // while ((geom = geomCursor.next()) != null) { - - if (geom.getGeometry() instanceof Point) { - Point pnt = (Point) geom.getGeometry(); - System.out - .println("Point(" + pnt.getX() + " , " + pnt.getY() + ")"); - if (geom.getSpatialReference() == null) { - System.out.println("No spatial reference"); - } else { - System.out.println("wkid: " - + geom.getSpatialReference().getID()); - } - - } else if (geom.getGeometry() instanceof MultiPoint) { - MultiPoint mp = (MultiPoint) geom.getGeometry(); - System.out.println("Multipoint has " + mp.getPointCount() - + " points."); - - System.out.println("wkid: " + geom.getSpatialReference().getID()); - - } else if (geom.getGeometry() instanceof Polygon) { - Polygon mp = (Polygon) geom.getGeometry(); - System.out.println("Polygon has " + mp.getPointCount() - + " points and " + mp.getPathCount() + " parts."); - if (mp.getPathCount() > 1) { - System.out.println("Part start of 2nd segment : " - + mp.getPathStart(1)); - System.out.println("Part end of 2nd segment : " - + mp.getPathEnd(1)); - System.out.println("Part size of 2nd segment : " - + mp.getPathSize(1)); - - int start = mp.getPathStart(1); - int end = mp.getPathEnd(1); - for (int i = start; i < end; i++) { - Point pp = mp.getPoint(i); - System.out.println("Point(" + i + ") = (" + pp.getX() - + ", " + pp.getY() + ")"); - } - } - System.out.println("wkid: " + geom.getSpatialReference().getID()); - - } else if (geom.getGeometry() instanceof Polyline) { - Polyline mp = (Polyline) geom.getGeometry(); - System.out.println("Polyline has " + mp.getPointCount() - + " points and " + mp.getPathCount() + " parts."); - System.out.println("Part start of 2nd segment : " - + mp.getPathStart(1)); - System.out.println("Part end of 2nd segment : " - + mp.getPathEnd(1)); - System.out.println("Part size of 2nd segment : " - + mp.getPathSize(1)); - int start = mp.getPathStart(1); - int end = mp.getPathEnd(1); - for (int i = start; i < end; i++) { - Point pp = mp.getPoint(i); - System.out.println("Point(" + i + ") = (" + pp.getX() + ", " - + pp.getY() + ")"); - } - - System.out.println("wkid: " + geom.getSpatialReference().getID()); - } - } - - @Test - public void testGeometryToJSON() { - Polygon geom = new Polygon(); - geom.startPath(new Point(-113, 34)); - geom.lineTo(new Point(-105, 34)); - geom.lineTo(new Point(-108, 40)); - - String outputPolygon1 = GeometryEngine.geometryToJson(-1, geom);// Test - // WKID - // == -1 - //System.out.println("Geom JSON STRING is" + outputPolygon1); - String correctPolygon1 = "{\"rings\":[[[-113,34],[-105,34],[-108,40],[-113,34]]]}"; - - assertEquals(correctPolygon1, outputPolygon1); - - String outputPolygon2 = GeometryEngine.geometryToJson(4326, geom); - //System.out.println("Geom JSON STRING is" + outputPolygon2); - - String correctPolygon2 = "{\"rings\":[[[-113,34],[-105,34],[-108,40],[-113,34]]],\"spatialReference\":{\"wkid\":4326}}"; - assertEquals(correctPolygon2, outputPolygon2); - } - - @Test - public void testGeometryToJSONOldID() throws Exception {// CR - Polygon geom = new Polygon(); - geom.startPath(new Point(-113, 34)); - geom.lineTo(new Point(-105, 34)); - geom.lineTo(new Point(-108, 40)); - String outputPolygon = GeometryEngine.geometryToJson( - SpatialReference.create(3857), geom);// Test WKID == -1 - String correctPolygon = "{\"rings\":[[[-113,34],[-105,34],[-108,40],[-113,34]]],\"spatialReference\":{\"wkid\":102100,\"latestWkid\":3857}}"; - assertTrue(outputPolygon.equals(correctPolygon)); - JsonFactory jf = new JsonFactory(); - JsonParser jp = jf.createJsonParser(outputPolygon); - jp.nextToken(); - MapGeometry mg = GeometryEngine.jsonToGeometry(jp); - @SuppressWarnings("unused") - int srId = mg.getSpatialReference().getID(); - @SuppressWarnings("unused") - int srOldId = mg.getSpatialReference().getOldID(); - Assert.assertTrue(mg.getSpatialReference().getID() == 3857); - Assert.assertTrue(mg.getSpatialReference().getLatestID() == 3857); - Assert.assertTrue(mg.getSpatialReference().getOldID() == 102100); - } -} +package com.esri.core.geometry; + +import java.util.Hashtable; +import java.io.IOException; +import java.util.Map; +import junit.framework.TestCase; +import org.junit.Assert; +import org.junit.Test; + +import com.fasterxml.jackson.core.JsonFactory; +import com.fasterxml.jackson.core.JsonParseException; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonToken; + +public class TestJsonParser extends TestCase { + + JsonFactory factory = new JsonFactory(); + SpatialReference spatialReferenceWebMerc1 = SpatialReference.create(102100); + SpatialReference spatialReferenceWebMerc2 = SpatialReference + .create(spatialReferenceWebMerc1.getLatestID()); + SpatialReference spatialReferenceWGS84 = SpatialReference.create(4326); + + @Override + protected void setUp() throws Exception { + super.setUp(); + } + + @Override + protected void tearDown() throws Exception { + super.tearDown(); + } + + @Test + public void test3DPoint() throws JsonParseException, IOException { + String jsonString3DPt = "{\"x\" : -118.15, \"y\" : 33.80, \"z\" : 10.0, \"spatialReference\" : {\"wkid\" : 4326}}"; + + JsonParser jsonParser3DPt = factory.createParser(jsonString3DPt); + MapGeometry point3DMP = GeometryEngine.jsonToGeometry(jsonParser3DPt); + assertTrue(-118.15 == ((Point) point3DMP.getGeometry()).getX()); + assertTrue(33.80 == ((Point) point3DMP.getGeometry()).getY()); + assertTrue(spatialReferenceWGS84.getID() == point3DMP + .getSpatialReference().getID()); + } + + @Test + public void test3DPoint1() throws JsonParseException, IOException { + Point point1 = new Point(10.0, 20.0); + Point pointEmpty = new Point(); + { + JsonParser pointWebMerc1Parser = factory + .createJsonParser(GeometryEngine.geometryToJson( + spatialReferenceWebMerc1, point1)); + MapGeometry pointWebMerc1MP = GeometryEngine + .jsonToGeometry(pointWebMerc1Parser); + assertTrue(point1.getX() == ((Point) pointWebMerc1MP.getGeometry()) + .getX()); + assertTrue(point1.getY() == ((Point) pointWebMerc1MP.getGeometry()) + .getY()); + int srIdOri = spatialReferenceWebMerc1.getID(); + int srIdAfter = pointWebMerc1MP.getSpatialReference().getID(); + assertTrue(srIdOri == srIdAfter || srIdAfter == 3857); + + pointWebMerc1Parser = factory.createJsonParser(GeometryEngine + .geometryToJson(null, point1)); + pointWebMerc1MP = GeometryEngine + .jsonToGeometry(pointWebMerc1Parser); + assertTrue(null == pointWebMerc1MP.getSpatialReference()); + + String pointEmptyString = GeometryEngine.geometryToJson( + spatialReferenceWebMerc1, pointEmpty); + pointWebMerc1Parser = factory.createJsonParser(pointEmptyString); + + pointWebMerc1MP = GeometryEngine + .jsonToGeometry(pointWebMerc1Parser); + assertTrue(pointWebMerc1MP.getGeometry().isEmpty()); + int srIdOri2 = spatialReferenceWebMerc1.getID(); + int srIdAfter2 = pointWebMerc1MP.getSpatialReference().getID(); + assertTrue(srIdOri2 == srIdAfter2 || srIdAfter2 == 3857); + } + } + + @Test + public void test3DPoint2() throws JsonParseException, IOException { + { + Point point1 = new Point(10.0, 20.0); + JsonParser pointWebMerc2Parser = factory + .createJsonParser(GeometryEngine.geometryToJson( + spatialReferenceWebMerc2, point1)); + MapGeometry pointWebMerc2MP = GeometryEngine + .jsonToGeometry(pointWebMerc2Parser); + assertTrue(point1.getX() == ((Point) pointWebMerc2MP.getGeometry()) + .getX()); + assertTrue(point1.getY() == ((Point) pointWebMerc2MP.getGeometry()) + .getY()); + assertTrue(spatialReferenceWebMerc2.getLatestID() == pointWebMerc2MP + .getSpatialReference().getLatestID()); + } + } + + @Test + public void test3DPoint3() throws JsonParseException, IOException { + { + Point point1 = new Point(10.0, 20.0); + JsonParser pointWgs84Parser = factory + .createJsonParser(GeometryEngine.geometryToJson( + spatialReferenceWGS84, point1)); + MapGeometry pointWgs84MP = GeometryEngine + .jsonToGeometry(pointWgs84Parser); + assertTrue(point1.getX() == ((Point) pointWgs84MP.getGeometry()) + .getX()); + assertTrue(point1.getY() == ((Point) pointWgs84MP.getGeometry()) + .getY()); + assertTrue(spatialReferenceWGS84.getID() == pointWgs84MP + .getSpatialReference().getID()); + } + } + + @Test + public void testMultiPoint() throws JsonParseException, IOException { + MultiPoint multiPoint1 = new MultiPoint(); + multiPoint1.add(-97.06138, 32.837); + multiPoint1.add(-97.06133, 32.836); + multiPoint1.add(-97.06124, 32.834); + multiPoint1.add(-97.06127, 32.832); + + { + JsonParser mPointWgs84Parser = factory + .createJsonParser(GeometryEngine.geometryToJson( + spatialReferenceWGS84, multiPoint1)); + MapGeometry mPointWgs84MP = GeometryEngine + .jsonToGeometry(mPointWgs84Parser); + assertTrue(multiPoint1.getPointCount() == ((MultiPoint) mPointWgs84MP + .getGeometry()).getPointCount()); + assertTrue(multiPoint1.getPoint(0).getX() == ((MultiPoint) mPointWgs84MP + .getGeometry()).getPoint(0).getX()); + assertTrue(multiPoint1.getPoint(0).getY() == ((MultiPoint) mPointWgs84MP + .getGeometry()).getPoint(0).getY()); + int lastIndex = multiPoint1.getPointCount() - 1; + assertTrue(multiPoint1.getPoint(lastIndex).getX() == ((MultiPoint) mPointWgs84MP + .getGeometry()).getPoint(lastIndex).getX()); + assertTrue(multiPoint1.getPoint(lastIndex).getY() == ((MultiPoint) mPointWgs84MP + .getGeometry()).getPoint(lastIndex).getY()); + + assertTrue(spatialReferenceWGS84.getID() == mPointWgs84MP + .getSpatialReference().getID()); + + MultiPoint mPointEmpty = new MultiPoint(); + String mPointEmptyString = GeometryEngine.geometryToJson( + spatialReferenceWGS84, mPointEmpty); + mPointWgs84Parser = factory.createJsonParser(mPointEmptyString); + + mPointWgs84MP = GeometryEngine.jsonToGeometry(mPointWgs84Parser); + assertTrue(mPointWgs84MP.getGeometry().isEmpty()); + assertTrue(spatialReferenceWGS84.getID() == mPointWgs84MP + .getSpatialReference().getID()); + + } + } + + @Test + public void testPolyline() throws JsonParseException, IOException { + Polyline polyline = new Polyline(); + polyline.startPath(-97.06138, 32.837); + polyline.lineTo(-97.06133, 32.836); + polyline.lineTo(-97.06124, 32.834); + polyline.lineTo(-97.06127, 32.832); + + polyline.startPath(-97.06326, 32.759); + polyline.lineTo(-97.06298, 32.755); + + { + JsonParser polylinePathsWgs84Parser = factory + .createJsonParser(GeometryEngine.geometryToJson( + spatialReferenceWGS84, polyline)); + MapGeometry mPolylineWGS84MP = GeometryEngine + .jsonToGeometry(polylinePathsWgs84Parser); + + assertTrue(polyline.getPointCount() == ((Polyline) mPolylineWGS84MP + .getGeometry()).getPointCount()); + assertTrue(polyline.getPoint(0).getX() == ((Polyline) mPolylineWGS84MP + .getGeometry()).getPoint(0).getX()); + assertTrue(polyline.getPoint(0).getY() == ((Polyline) mPolylineWGS84MP + .getGeometry()).getPoint(0).getY()); + + assertTrue(polyline.getPathCount() == ((Polyline) mPolylineWGS84MP + .getGeometry()).getPathCount()); + assertTrue(polyline.getSegmentCount() == ((Polyline) mPolylineWGS84MP + .getGeometry()).getSegmentCount()); + assertTrue(polyline.getSegmentCount(0) == ((Polyline) mPolylineWGS84MP + .getGeometry()).getSegmentCount(0)); + assertTrue(polyline.getSegmentCount(1) == ((Polyline) mPolylineWGS84MP + .getGeometry()).getSegmentCount(1)); + + int lastIndex = polyline.getPointCount() - 1; + assertTrue(polyline.getPoint(lastIndex).getX() == ((Polyline) mPolylineWGS84MP + .getGeometry()).getPoint(lastIndex).getX()); + assertTrue(polyline.getPoint(lastIndex).getY() == ((Polyline) mPolylineWGS84MP + .getGeometry()).getPoint(lastIndex).getY()); + + assertTrue(spatialReferenceWGS84.getID() == mPolylineWGS84MP + .getSpatialReference().getID()); + + Polyline emptyPolyline = new Polyline(); + String emptyString = GeometryEngine.geometryToJson( + spatialReferenceWGS84, emptyPolyline); + mPolylineWGS84MP = GeometryEngine.jsonToGeometry(factory + .createJsonParser(emptyString)); + assertTrue(mPolylineWGS84MP.getGeometry().isEmpty()); + assertTrue(spatialReferenceWGS84.getID() == mPolylineWGS84MP + .getSpatialReference().getID()); + } + } + + @Test + public void testPolygon() throws JsonParseException, IOException { + Polygon polygon = new Polygon(); + polygon.startPath(-97.06138, 32.837); + polygon.lineTo(-97.06133, 32.836); + polygon.lineTo(-97.06124, 32.834); + polygon.lineTo(-97.06127, 32.832); + + polygon.startPath(-97.06326, 32.759); + polygon.lineTo(-97.06298, 32.755); + + { + JsonParser polygonPathsWgs84Parser = factory + .createJsonParser(GeometryEngine.geometryToJson( + spatialReferenceWGS84, polygon)); + MapGeometry mPolygonWGS84MP = GeometryEngine + .jsonToGeometry(polygonPathsWgs84Parser); + + assertTrue(polygon.getPointCount() + 1 == ((Polygon) mPolygonWGS84MP + .getGeometry()).getPointCount()); + assertTrue(polygon.getPoint(0).getX() == ((Polygon) mPolygonWGS84MP + .getGeometry()).getPoint(0).getX()); + assertTrue(polygon.getPoint(0).getY() == ((Polygon) mPolygonWGS84MP + .getGeometry()).getPoint(0).getY()); + + assertTrue(polygon.getPathCount() == ((Polygon) mPolygonWGS84MP + .getGeometry()).getPathCount()); + assertTrue(polygon.getSegmentCount() + 1 == ((Polygon) mPolygonWGS84MP + .getGeometry()).getSegmentCount()); + assertTrue(polygon.getSegmentCount(0) == ((Polygon) mPolygonWGS84MP + .getGeometry()).getSegmentCount(0)); + assertTrue(polygon.getSegmentCount(1) + 1 == ((Polygon) mPolygonWGS84MP + .getGeometry()).getSegmentCount(1)); + + int lastIndex = polygon.getPointCount() - 1; + assertTrue(polygon.getPoint(lastIndex).getX() == ((Polygon) mPolygonWGS84MP + .getGeometry()).getPoint(lastIndex).getX()); + assertTrue(polygon.getPoint(lastIndex).getY() == ((Polygon) mPolygonWGS84MP + .getGeometry()).getPoint(lastIndex).getY()); + + assertTrue(spatialReferenceWGS84.getID() == mPolygonWGS84MP + .getSpatialReference().getID()); + + Polygon emptyPolygon = new Polygon(); + String emptyPolygonString = GeometryEngine.geometryToJson( + spatialReferenceWGS84, emptyPolygon); + polygonPathsWgs84Parser = factory + .createJsonParser(emptyPolygonString); + mPolygonWGS84MP = GeometryEngine + .jsonToGeometry(polygonPathsWgs84Parser); + + assertTrue(mPolygonWGS84MP.getGeometry().isEmpty()); + assertTrue(spatialReferenceWGS84.getID() == mPolygonWGS84MP + .getSpatialReference().getID()); + } + } + + @Test + public void testEnvelope() throws JsonParseException, IOException { + Envelope envelope = new Envelope(); + envelope.setCoords(-109.55, 25.76, -86.39, 49.94); + + { + JsonParser envelopeWGS84Parser = factory + .createJsonParser(GeometryEngine.geometryToJson( + spatialReferenceWGS84, envelope)); + MapGeometry envelopeWGS84MP = GeometryEngine + .jsonToGeometry(envelopeWGS84Parser); + assertTrue(envelope.isEmpty() == envelopeWGS84MP.getGeometry() + .isEmpty()); + assertTrue(envelope.getXMax() == ((Envelope) envelopeWGS84MP + .getGeometry()).getXMax()); + assertTrue(envelope.getYMax() == ((Envelope) envelopeWGS84MP + .getGeometry()).getYMax()); + assertTrue(envelope.getXMin() == ((Envelope) envelopeWGS84MP + .getGeometry()).getXMin()); + assertTrue(envelope.getYMin() == ((Envelope) envelopeWGS84MP + .getGeometry()).getYMin()); + assertTrue(spatialReferenceWGS84.getID() == envelopeWGS84MP + .getSpatialReference().getID()); + + Envelope emptyEnvelope = new Envelope(); + String emptyEnvString = GeometryEngine.geometryToJson( + spatialReferenceWGS84, emptyEnvelope); + envelopeWGS84Parser = factory.createJsonParser(emptyEnvString); + envelopeWGS84MP = GeometryEngine + .jsonToGeometry(envelopeWGS84Parser); + + assertTrue(envelopeWGS84MP.getGeometry().isEmpty()); + assertTrue(spatialReferenceWGS84.getID() == envelopeWGS84MP + .getSpatialReference().getID()); + } + } + + @Test + public void testCR181369() throws JsonParseException, IOException { + // CR181369 + { + String jsonStringPointAndWKT = "{\"x\":10.0,\"y\":20.0,\"spatialReference\":{\"wkt\" : \"PROJCS[\\\"NAD83_UTM_zone_15N\\\",GEOGCS[\\\"GCS_North_American_1983\\\",DATUM[\\\"D_North_American_1983\\\",SPHEROID[\\\"GRS_1980\\\",6378137.0,298.257222101]],PRIMEM[\\\"Greenwich\\\",0.0],UNIT[\\\"Degree\\\",0.0174532925199433]],PROJECTION[\\\"Transverse_Mercator\\\"],PARAMETER[\\\"false_easting\\\",500000.0],PARAMETER[\\\"false_northing\\\",0.0],PARAMETER[\\\"central_meridian\\\",-93.0],PARAMETER[\\\"scale_factor\\\",0.9996],PARAMETER[\\\"latitude_of_origin\\\",0.0],UNIT[\\\"Meter\\\",1.0]]\"} }"; + JsonParser jsonParserPointAndWKT = factory + .createJsonParser(jsonStringPointAndWKT); + MapGeometry mapGeom2 = GeometryEngine + .jsonToGeometry(jsonParserPointAndWKT); + String jsonStringPointAndWKT2 = GeometryEngine.geometryToJson( + mapGeom2.getSpatialReference(), mapGeom2.getGeometry()); + JsonParser jsonParserPointAndWKT2 = factory + .createJsonParser(jsonStringPointAndWKT2); + MapGeometry mapGeom3 = GeometryEngine + .jsonToGeometry(jsonParserPointAndWKT2); + assertTrue(((Point) mapGeom2.getGeometry()).getX() == ((Point) mapGeom3 + .getGeometry()).getX()); + assertTrue(((Point) mapGeom2.getGeometry()).getY() == ((Point) mapGeom3 + .getGeometry()).getY()); + assertTrue(mapGeom2.getSpatialReference().getText() + .equals(mapGeom3.getSpatialReference().getText())); + assertTrue(mapGeom2.getSpatialReference().getID() == mapGeom3 + .getSpatialReference().getID()); + } + } + + @Test + public void testSpatialRef() throws JsonParseException, IOException { + // String jsonStringPt = + // "{\"x\":-20037508.342787,\"y\":20037508.342787},\"spatialReference\":{\"wkid\":102100}}"; + String jsonStringPt = "{\"x\":10.0,\"y\":20.0,\"spatialReference\":{\"wkid\": 102100}}";// 102100 + @SuppressWarnings("unused") + String jsonStringPt2 = "{\"x\":10.0,\"y\":20.0,\"spatialReference\":{\"wkid\":4326}}"; + String jsonStringMpt = "{ \"points\" : [ [-97.06138,32.837], [-97.06133,32.836], [-97.06124,32.834], [-97.06127,32.832] ], \"spatialReference\" : {\"wkid\" : 4326}}";// 4326 + String jsonStringMpt3D = "{\"hasZs\" : true,\"points\" : [ [-97.06138,32.837,35.0], [-97.06133,32.836,35.1], [-97.06124,32.834,35.2], [-97.06127,32.832,35.3] ],\"spatialReference\" : {\"wkid\" : 4326}}"; + String jsonStringPl = "{\"paths\" : [ [ [-97.06138,32.837], [-97.06133,32.836], [-97.06124,32.834], [-97.06127,32.832] ], [ [-97.06326,32.759], [-97.06298,32.755] ]],\"spatialReference\" : {\"wkid\" : 4326}}"; + String jsonStringPl3D = "{\"hasMs\" : true,\"paths\" : [[ [-97.06138,32.837,5], [-97.06133,32.836,6], [-97.06124,32.834,7], [-97.06127,32.832,8] ],[ [-97.06326,32.759], [-97.06298,32.755] ]],\"spatialReference\" : {\"wkid\" : 4326}}"; + String jsonStringPg = "{ \"rings\" :[ [ [-97.06138,32.837], [-97.06133,32.836], [-97.06124,32.834], [-97.06127,32.832], [-97.06138,32.837] ], [ [-97.06326,32.759], [-97.06298,32.755], [-97.06153,32.749], [-97.06326,32.759] ]], \"spatialReference\" : {\"wkt\" : \"\"}}"; + String jsonStringPg3D = "{\"hasZs\" : true,\"hasMs\" : true,\"rings\" : [ [ [-97.06138, 32.837, 35.1, 4], [-97.06133, 32.836, 35.2, 4.1], [-97.06124, 32.834, 35.3, 4.2], [-97.06127, 32.832, 35.2, 44.3], [-97.06138, 32.837, 35.1, 4] ],[ [-97.06326, 32.759, 35.4], [-97.06298, 32.755, 35.5], [-97.06153, 32.749, 35.6], [-97.06326, 32.759, 35.4] ]],\"spatialReference\" : {\"wkid\" : 4326}}"; + String jsonStringPg2 = "{ \"spatialReference\" : {\"wkid\" : 4326}, \"rings\" : [[[-118.35,32.81],[-118.42,32.806],[-118.511,32.892],[-118.35,32.81]]]}"; + String jsonStringPg3 = "{ \"spatialReference\": {\"layerName\":\"GAS_POINTS\",\"name\":null,\"sdesrid\":102100,\"wkid\":102100,\"wkt\":null}}"; + String jsonString2SpatialReferences = "{ \"spatialReference\": {\"layerName\":\"GAS_POINTS\",\"name\":null,\"sdesrid\":102100,\"wkid\":102100,\"wkt\":\"GEOGCS[\\\"GCS_WGS_1984\\\",DATUM[\\\"D_WGS_1984\\\",SPHEROID[\\\"WGS_1984\\\",6378137,298.257223563]],PRIMEM[\\\"Greenwich\\\",0],UNIT[\\\"Degree\\\",0.017453292519943295]]\"}}"; + String jsonString2SpatialReferences2 = "{ \"spatialReference\": {\"layerName\":\"GAS_POINTS\",\"name\":null,\"sdesrid\":10,\"wkid\":10,\"wkt\":\"GEOGCS[\\\"GCS_WGS_1984\\\",DATUM[\\\"D_WGS_1984\\\",SPHEROID[\\\"WGS_1984\\\",6378137,298.257223563]],PRIMEM[\\\"Greenwich\\\",0],UNIT[\\\"Degree\\\",0.017453292519943295]]\"}}"; + String jsonStringSR = "{\"wkid\" : 4326}"; + String jsonStringEnv = "{\"xmin\" : -109.55, \"ymin\" : 25.76, \"xmax\" : -86.39, \"ymax\" : 49.94,\"spatialReference\" : {\"wkid\" : 4326}}"; + String jsonStringHongKon = "{\"xmin\" : -122.55, \"ymin\" : 37.65, \"xmax\" : -122.28, \"ymax\" : 37.84,\"spatialReference\" : {\"wkid\" : 4326}}"; + @SuppressWarnings("unused") + String jsonStringWKT = " {\"wkt\" : \"GEOGCS[\\\"GCS_WGS_1984\\\",DATUM[\\\"D_WGS_1984\\\",SPHEROID[\\\"WGS_1984\\\",6378137,298.257223563]],PRIMEM[\\\"Greenwich\\\",0],UNIT[\\\"Degree\\\",0.017453292519943295]]\"}"; + String jsonStringInvalidWKID = "{\"x\":10.0,\"y\":20.0},\"spatialReference\":{\"wkid\":35253523}}"; + String jsonStringOregon = "{\"xmin\":7531831.219849482,\"ymin\":585702.9799639136,\"xmax\":7750143.589982405,\"ymax\":733289.6299999952,\"spatialReference\":{\"wkid\":102726}}"; + + JsonParser jsonParserPt = factory.createJsonParser(jsonStringPt); + JsonParser jsonParserMpt = factory.createJsonParser(jsonStringMpt); + JsonParser jsonParserMpt3D = factory.createJsonParser(jsonStringMpt3D); + JsonParser jsonParserPl = factory.createJsonParser(jsonStringPl); + JsonParser jsonParserPl3D = factory.createJsonParser(jsonStringPl3D); + JsonParser jsonParserPg = factory.createJsonParser(jsonStringPg); + JsonParser jsonParserPg3D = factory.createJsonParser(jsonStringPg3D); + JsonParser jsonParserPg2 = factory.createJsonParser(jsonStringPg2); + @SuppressWarnings("unused") + JsonParser jsonParserSR = factory.createJsonParser(jsonStringSR); + JsonParser jsonParserEnv = factory.createJsonParser(jsonStringEnv); + JsonParser jsonParserPg3 = factory.createJsonParser(jsonStringPg3); + @SuppressWarnings("unused") + JsonParser jsonParserCrazy1 = factory + .createJsonParser(jsonString2SpatialReferences); + @SuppressWarnings("unused") + JsonParser jsonParserCrazy2 = factory + .createJsonParser(jsonString2SpatialReferences2); + JsonParser jsonParserInvalidWKID = factory + .createJsonParser(jsonStringInvalidWKID); + @SuppressWarnings("unused") + JsonParser jsonParseHongKon = factory + .createJsonParser(jsonStringHongKon); + JsonParser jsonParseOregon = factory.createJsonParser(jsonStringOregon); + + MapGeometry mapGeom = GeometryEngine.jsonToGeometry(jsonParserPt); + // showProjectedGeometryInfo(mapGeom); + Assert.assertTrue(mapGeom.getSpatialReference().getID() == 102100); + + MapGeometry mapGeomOregon = GeometryEngine + .jsonToGeometry(jsonParseOregon); + Assert.assertTrue(mapGeomOregon.getSpatialReference().getID() == 102726); + + mapGeom = GeometryEngine.jsonToGeometry(jsonParserMpt); + Assert.assertTrue(mapGeom.getSpatialReference().getID() == 4326); + + mapGeom = GeometryEngine.jsonToGeometry(jsonParserMpt3D); + Assert.assertTrue(mapGeom.getSpatialReference().getID() == 4326); + { + Assert.assertTrue(((MultiPoint) mapGeom.getGeometry()).getPoint(0) + .getX() == -97.06138); + Assert.assertTrue(((MultiPoint) mapGeom.getGeometry()).getPoint(0) + .getY() == 32.837); + Assert.assertTrue(((MultiPoint) mapGeom.getGeometry()).getPoint(3) + .getX() == -97.06127); + Assert.assertTrue(((MultiPoint) mapGeom.getGeometry()).getPoint(3) + .getY() == 32.832); + } + // showProjectedGeometryInfo(mapGeom); + + mapGeom = GeometryEngine.jsonToGeometry(jsonParserPl); + Assert.assertTrue(mapGeom.getSpatialReference().getID() == 4326); + // showProjectedGeometryInfo(mapGeom); + + mapGeom = GeometryEngine.jsonToGeometry(jsonParserPl3D); + { + // [[ [-97.06138,32.837,5], [-97.06133,32.836,6], + // [-97.06124,32.834,7], [-97.06127,32.832,8] ], + // [ [-97.06326,32.759], [-97.06298,32.755] ]]"; + Assert.assertTrue(((Polyline) mapGeom.getGeometry()).getPoint(0) + .getX() == -97.06138); + Assert.assertTrue(((Polyline) mapGeom.getGeometry()).getPoint(0) + .getY() == 32.837); + int lastIndex = ((Polyline) mapGeom.getGeometry()).getPointCount() - 1; + Assert.assertTrue(((Polyline) mapGeom.getGeometry()).getPoint( + lastIndex).getX() == -97.06298);// -97.06153, 32.749 + Assert.assertTrue(((Polyline) mapGeom.getGeometry()).getPoint( + lastIndex).getY() == 32.755); + int lastIndexFirstLine = ((Polyline) mapGeom.getGeometry()) + .getPathEnd(0) - 1; + Assert.assertTrue(((Polyline) mapGeom.getGeometry()).getPoint( + lastIndexFirstLine).getX() == -97.06127);// -97.06153, + // 32.749 + Assert.assertTrue(((Polyline) mapGeom.getGeometry()).getPoint( + lastIndexFirstLine).getY() == 32.832); + } + + mapGeom = GeometryEngine.jsonToGeometry(jsonParserPg); + Assert.assertTrue(mapGeom.getSpatialReference() == null); + + mapGeom = GeometryEngine.jsonToGeometry(jsonParserPg3D); + { + Assert.assertTrue(((Polygon) mapGeom.getGeometry()).getPoint(0) + .getX() == -97.06138); + Assert.assertTrue(((Polygon) mapGeom.getGeometry()).getPoint(0) + .getY() == 32.837); + int lastIndex = ((Polygon) mapGeom.getGeometry()).getPointCount() - 1; + Assert.assertTrue(((Polygon) mapGeom.getGeometry()).getPoint( + lastIndex).getX() == -97.06153);// -97.06153, 32.749 + Assert.assertTrue(((Polygon) mapGeom.getGeometry()).getPoint( + lastIndex).getY() == 32.749); + } + + mapGeom = GeometryEngine.jsonToGeometry(jsonParserPg2); + Assert.assertTrue(mapGeom.getSpatialReference().getID() == 4326); + // showProjectedGeometryInfo(mapGeom); + + mapGeom = GeometryEngine.jsonToGeometry(jsonParserPg3); + Assert.assertTrue(mapGeom.getSpatialReference().getID() == 102100); + // showProjectedGeometryInfo(mapGeom); + + // mapGeom = GeometryEngine.jsonToGeometry(jsonParserCrazy1); + // Assert.assertTrue(mapGeom.getSpatialReference().getText().equals("")); + // showProjectedGeometryInfo(mapGeom); + + mapGeom = GeometryEngine.jsonToGeometry(jsonParserEnv); + Assert.assertTrue(mapGeom.getSpatialReference().getID() == 4326); + // showProjectedGeometryInfo(mapGeom); + + try { + GeometryEngine.jsonToGeometry(jsonParserInvalidWKID); + } catch (Exception ex) { + Assert.assertTrue("Should not throw for invalid wkid", false); + } + } + + @Test + public void testMP2onCR175871() throws Exception { + Polygon pg = new Polygon(); + pg.startPath(-50, 10); + pg.lineTo(-50, 12); + pg.lineTo(-45, 12); + pg.lineTo(-45, 10); + + Polygon pg1 = new Polygon(); + pg1.startPath(-45, 10); + pg1.lineTo(-40, 10); + pg1.lineTo(-40, 8); + pg.add(pg1, false); + + SpatialReference spatialReference = SpatialReference.create(4326); + + try { + String jSonStr = GeometryEngine + .geometryToJson(spatialReference, pg); + JsonFactory jf = new JsonFactory(); + + JsonParser jp = jf.createJsonParser(jSonStr); + jp.nextToken(); + MapGeometry mg = GeometryEngine.jsonToGeometry(jp); + Geometry gm = mg.getGeometry(); + Assert.assertEquals(Geometry.Type.Polygon, gm.getType()); + Assert.assertTrue(mg.getSpatialReference().getID() == 4326); + + Polygon pgNew = (Polygon) gm; + + Assert.assertEquals(pgNew.getPathCount(), pg.getPathCount()); + Assert.assertEquals(pgNew.getPointCount(), pg.getPointCount()); + Assert.assertEquals(pgNew.getSegmentCount(), pg.getSegmentCount()); + + Assert.assertEquals(pgNew.getPoint(0).getX(), + pg.getPoint(0).getX(), 0.000000001); + Assert.assertEquals(pgNew.getPoint(1).getX(), + pg.getPoint(1).getX(), 0.000000001); + Assert.assertEquals(pgNew.getPoint(2).getX(), + pg.getPoint(2).getX(), 0.000000001); + Assert.assertEquals(pgNew.getPoint(3).getX(), + pg.getPoint(3).getX(), 0.000000001); + + Assert.assertEquals(pgNew.getPoint(0).getY(), + pg.getPoint(0).getY(), 0.000000001); + Assert.assertEquals(pgNew.getPoint(1).getY(), + pg.getPoint(1).getY(), 0.000000001); + Assert.assertEquals(pgNew.getPoint(2).getY(), + pg.getPoint(2).getY(), 0.000000001); + Assert.assertEquals(pgNew.getPoint(3).getY(), + pg.getPoint(3).getY(), 0.000000001); + } catch (Exception ex) { + String err = ex.getMessage(); + System.out.print(err); + throw ex; + } + } + + @Test + public static int fromJsonToWkid(JsonParser parser) + throws JsonParseException, IOException { + int wkid = 0; + if (parser.getCurrentToken() != JsonToken.START_OBJECT) { + return 0; + } + + while (parser.nextToken() != JsonToken.END_OBJECT) { + String fieldName = parser.getCurrentName(); + + if ("wkid".equals(fieldName)) { + parser.nextToken(); + wkid = parser.getIntValue(); + } + } + return wkid; + } + + @SuppressWarnings("unused") + private static void showProjectedGeometryInfo(MapGeometry mapGeom) { + System.out.println("\n"); + MapGeometry geom = mapGeom; + // while ((geom = geomCursor.next()) != null) { + + if (geom.getGeometry() instanceof Point) { + Point pnt = (Point) geom.getGeometry(); + System.out + .println("Point(" + pnt.getX() + " , " + pnt.getY() + ")"); + if (geom.getSpatialReference() == null) { + System.out.println("No spatial reference"); + } else { + System.out.println("wkid: " + + geom.getSpatialReference().getID()); + } + + } else if (geom.getGeometry() instanceof MultiPoint) { + MultiPoint mp = (MultiPoint) geom.getGeometry(); + System.out.println("Multipoint has " + mp.getPointCount() + + " points."); + + System.out.println("wkid: " + geom.getSpatialReference().getID()); + + } else if (geom.getGeometry() instanceof Polygon) { + Polygon mp = (Polygon) geom.getGeometry(); + System.out.println("Polygon has " + mp.getPointCount() + + " points and " + mp.getPathCount() + " parts."); + if (mp.getPathCount() > 1) { + System.out.println("Part start of 2nd segment : " + + mp.getPathStart(1)); + System.out.println("Part end of 2nd segment : " + + mp.getPathEnd(1)); + System.out.println("Part size of 2nd segment : " + + mp.getPathSize(1)); + + int start = mp.getPathStart(1); + int end = mp.getPathEnd(1); + for (int i = start; i < end; i++) { + Point pp = mp.getPoint(i); + System.out.println("Point(" + i + ") = (" + pp.getX() + + ", " + pp.getY() + ")"); + } + } + System.out.println("wkid: " + geom.getSpatialReference().getID()); + + } else if (geom.getGeometry() instanceof Polyline) { + Polyline mp = (Polyline) geom.getGeometry(); + System.out.println("Polyline has " + mp.getPointCount() + + " points and " + mp.getPathCount() + " parts."); + System.out.println("Part start of 2nd segment : " + + mp.getPathStart(1)); + System.out.println("Part end of 2nd segment : " + + mp.getPathEnd(1)); + System.out.println("Part size of 2nd segment : " + + mp.getPathSize(1)); + int start = mp.getPathStart(1); + int end = mp.getPathEnd(1); + for (int i = start; i < end; i++) { + Point pp = mp.getPoint(i); + System.out.println("Point(" + i + ") = (" + pp.getX() + ", " + + pp.getY() + ")"); + } + + System.out.println("wkid: " + geom.getSpatialReference().getID()); + } + } + + @Test + public void testGeometryToJSON() { + Polygon geom = new Polygon(); + geom.startPath(new Point(-113, 34)); + geom.lineTo(new Point(-105, 34)); + geom.lineTo(new Point(-108, 40)); + + String outputPolygon1 = GeometryEngine.geometryToJson(-1, geom);// Test + // WKID + // == -1 + //System.out.println("Geom JSON STRING is" + outputPolygon1); + String correctPolygon1 = "{\"rings\":[[[-113,34],[-105,34],[-108,40],[-113,34]]]}"; + + assertEquals(correctPolygon1, outputPolygon1); + + String outputPolygon2 = GeometryEngine.geometryToJson(4326, geom); + //System.out.println("Geom JSON STRING is" + outputPolygon2); + + String correctPolygon2 = "{\"rings\":[[[-113,34],[-105,34],[-108,40],[-113,34]]],\"spatialReference\":{\"wkid\":4326}}"; + assertEquals(correctPolygon2, outputPolygon2); + } + + @Test + public void testGeometryToJSONOldID() throws Exception {// CR + Polygon geom = new Polygon(); + geom.startPath(new Point(-113, 34)); + geom.lineTo(new Point(-105, 34)); + geom.lineTo(new Point(-108, 40)); + String outputPolygon = GeometryEngine.geometryToJson( + SpatialReference.create(3857), geom);// Test WKID == -1 + String correctPolygon = "{\"rings\":[[[-113,34],[-105,34],[-108,40],[-113,34]]],\"spatialReference\":{\"wkid\":102100,\"latestWkid\":3857}}"; + assertTrue(outputPolygon.equals(correctPolygon)); + JsonFactory jf = new JsonFactory(); + JsonParser jp = jf.createJsonParser(outputPolygon); + jp.nextToken(); + MapGeometry mg = GeometryEngine.jsonToGeometry(jp); + @SuppressWarnings("unused") + int srId = mg.getSpatialReference().getID(); + @SuppressWarnings("unused") + int srOldId = mg.getSpatialReference().getOldID(); + Assert.assertTrue(mg.getSpatialReference().getID() == 3857); + Assert.assertTrue(mg.getSpatialReference().getLatestID() == 3857); + Assert.assertTrue(mg.getSpatialReference().getOldID() == 102100); + } +} diff --git a/src/test/java/com/esri/core/geometry/TestSerialization.java b/src/test/java/com/esri/core/geometry/TestSerialization.java index 4d736a8c..269b0879 100644 --- a/src/test/java/com/esri/core/geometry/TestSerialization.java +++ b/src/test/java/com/esri/core/geometry/TestSerialization.java @@ -1,380 +1,380 @@ -package com.esri.core.geometry; - -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.FileOutputStream; -import java.io.InputStream; -import java.io.ObjectInputStream; -import java.io.ObjectOutputStream; -import junit.framework.TestCase; -import org.junit.Test; - -public class TestSerialization extends TestCase { - @Override - protected void setUp() throws Exception { - super.setUp(); - } - - @Override - protected void tearDown() throws Exception { - super.tearDown(); - } - - @Test - public void testSerializePoint() { - try { - ByteArrayOutputStream streamOut = new ByteArrayOutputStream(); - ObjectOutputStream oo = new ObjectOutputStream(streamOut); - Point pt = new Point(10, 40); - oo.writeObject(pt); - ByteArrayInputStream streamIn = new ByteArrayInputStream( - streamOut.toByteArray()); - ObjectInputStream ii = new ObjectInputStream(streamIn); - Point ptRes = (Point) ii.readObject(); - assertTrue(ptRes.equals(pt)); - } catch (Exception ex) { - fail("Point serialization failure"); - - } - - //try - //{ - //FileOutputStream streamOut = new FileOutputStream("c:/temp/savedPoint1.txt"); - //ObjectOutputStream oo = new ObjectOutputStream(streamOut); - //Point pt = new Point(10, 40, 2); - //oo.writeObject(pt); - //} - //catch(Exception ex) - //{ - //fail("Point serialization failure"); - //} - - try { - InputStream s = TestSerialization.class - .getResourceAsStream("savedPoint.txt"); - ObjectInputStream ii = new ObjectInputStream(s); - Point ptRes = (Point) ii.readObject(); - assertTrue(ptRes.getX() == 10 && ptRes.getY() == 40); - } catch (Exception ex) { - fail("Point serialization failure"); - } - - try { - InputStream s = TestSerialization.class - .getResourceAsStream("savedPoint1.txt"); - ObjectInputStream ii = new ObjectInputStream(s); - Point ptRes = (Point) ii.readObject(); - assertTrue(ptRes.getX() == 10 && ptRes.getY() == 40 && ptRes.getZ() == 2); - } catch (Exception ex) { - fail("Point serialization failure"); - } - - } - - @Test - public void testSerializePolygon() { - try { - ByteArrayOutputStream streamOut = new ByteArrayOutputStream(); - ObjectOutputStream oo = new ObjectOutputStream(streamOut); - Polygon pt = new Polygon(); - pt.startPath(10, 10); - pt.lineTo(100, 100); - pt.lineTo(200, 100); - oo.writeObject(pt); - ByteArrayInputStream streamIn = new ByteArrayInputStream( - streamOut.toByteArray()); - ObjectInputStream ii = new ObjectInputStream(streamIn); - Polygon ptRes = (Polygon) ii.readObject(); - assertTrue(ptRes.equals(pt)); - } catch (Exception ex) { - fail("Polygon serialization failure"); - } - - try { - ByteArrayOutputStream streamOut = new ByteArrayOutputStream(); - ObjectOutputStream oo = new ObjectOutputStream(streamOut); - Polygon pt = new Polygon(); - pt.startPath(10, 10); - pt.lineTo(100, 100); - pt.lineTo(200, 100); - pt = (Polygon) GeometryEngine.simplify(pt, null); - oo.writeObject(pt); - ByteArrayInputStream streamIn = new ByteArrayInputStream( - streamOut.toByteArray()); - ObjectInputStream ii = new ObjectInputStream(streamIn); - Polygon ptRes = (Polygon) ii.readObject(); - assertTrue(ptRes.equals(pt)); - } catch (Exception ex) { - fail("Polygon serialization failure"); - } - - //try - //{ - //FileOutputStream streamOut = new FileOutputStream("c:/temp/savedPolygon1.txt"); - //ObjectOutputStream oo = new ObjectOutputStream(streamOut); - //Polygon pt = new Polygon(); - //pt.startPath(10, 10); - //pt.lineTo(100, 100); - //pt.lineTo(200, 100); - //pt = (Polygon)GeometryEngine.simplify(pt, null); - //oo.writeObject(pt); - //} - //catch(Exception ex) - //{ - //fail("Polygon serialization failure"); - //} - - try { - InputStream s = TestSerialization.class - .getResourceAsStream("savedPolygon.txt"); - ObjectInputStream ii = new ObjectInputStream(s); - Polygon ptRes = (Polygon) ii.readObject(); - assertTrue(ptRes != null); - } catch (Exception ex) { - fail("Polygon serialization failure"); - } - try { - InputStream s = TestSerialization.class - .getResourceAsStream("savedPolygon1.txt"); - ObjectInputStream ii = new ObjectInputStream(s); - Polygon ptRes = (Polygon) ii.readObject(); - assertTrue(ptRes != null); - } catch (Exception ex) { - fail("Polygon serialization failure"); - } - } - - @Test - public void testSerializePolyline() { - try { - ByteArrayOutputStream streamOut = new ByteArrayOutputStream(); - ObjectOutputStream oo = new ObjectOutputStream(streamOut); - Polyline pt = new Polyline(); - pt.startPath(10, 10); - pt.lineTo(100, 100); - pt.lineTo(200, 100); - oo.writeObject(pt); - ByteArrayInputStream streamIn = new ByteArrayInputStream( - streamOut.toByteArray()); - ObjectInputStream ii = new ObjectInputStream(streamIn); - Polyline ptRes = (Polyline) ii.readObject(); - assertTrue(ptRes.equals(pt)); - } catch (Exception ex) { - fail("Polyline serialization failure"); - } - - //try - //{ - //FileOutputStream streamOut = new FileOutputStream("c:/temp/savedPolyline1.txt"); - //ObjectOutputStream oo = new ObjectOutputStream(streamOut); - //Polyline pt = new Polyline(); - //pt.startPath(10, 10); - //pt.lineTo(100, 100); - //pt.lineTo(200, 100); - //oo.writeObject(pt); - //} - //catch(Exception ex) - //{ - //fail("Polyline serialization failure"); - //} - - try { - InputStream s = TestSerialization.class - .getResourceAsStream("savedPolyline.txt"); - ObjectInputStream ii = new ObjectInputStream(s); - Polyline ptRes = (Polyline) ii.readObject(); - assertTrue(ptRes != null); - } catch (Exception ex) { - fail("Polyline serialization failure"); - } - try { - InputStream s = TestSerialization.class - .getResourceAsStream("savedPolyline1.txt"); - ObjectInputStream ii = new ObjectInputStream(s); - Polyline ptRes = (Polyline) ii.readObject(); - assertTrue(ptRes != null); - } catch (Exception ex) { - fail("Polyline serialization failure"); - } - } - - @Test - public void testSerializeEnvelope() { - try { - ByteArrayOutputStream streamOut = new ByteArrayOutputStream(); - ObjectOutputStream oo = new ObjectOutputStream(streamOut); - Envelope pt = new Envelope(10, 10, 400, 300); - oo.writeObject(pt); - ByteArrayInputStream streamIn = new ByteArrayInputStream( - streamOut.toByteArray()); - ObjectInputStream ii = new ObjectInputStream(streamIn); - Envelope ptRes = (Envelope) ii.readObject(); - assertTrue(ptRes.equals(pt)); - } catch (Exception ex) { - fail("Envelope serialization failure"); - } - - //try - //{ - //FileOutputStream streamOut = new FileOutputStream("c:/temp/savedEnvelope1.txt"); - //ObjectOutputStream oo = new ObjectOutputStream(streamOut); - //Envelope pt = new Envelope(10, 10, 400, 300); - //oo.writeObject(pt); - //} - //catch(Exception ex) - //{ - //fail("Envelope serialization failure"); - //} - - try { - InputStream s = TestSerialization.class - .getResourceAsStream("savedEnvelope.txt"); - ObjectInputStream ii = new ObjectInputStream(s); - Envelope ptRes = (Envelope) ii.readObject(); - assertTrue(ptRes.getXMax() == 400); - } catch (Exception ex) { - fail("Envelope serialization failure"); - } - try { - InputStream s = TestSerialization.class - .getResourceAsStream("savedEnvelope1.txt"); - ObjectInputStream ii = new ObjectInputStream(s); - Envelope ptRes = (Envelope) ii.readObject(); - assertTrue(ptRes.getXMax() == 400); - } catch (Exception ex) { - fail("Envelope serialization failure"); - } - } - - @Test - public void testSerializeMultiPoint() { - try { - ByteArrayOutputStream streamOut = new ByteArrayOutputStream(); - ObjectOutputStream oo = new ObjectOutputStream(streamOut); - MultiPoint pt = new MultiPoint(); - pt.add(10, 30); - pt.add(120, 40); - oo.writeObject(pt); - ByteArrayInputStream streamIn = new ByteArrayInputStream( - streamOut.toByteArray()); - ObjectInputStream ii = new ObjectInputStream(streamIn); - MultiPoint ptRes = (MultiPoint) ii.readObject(); - assertTrue(ptRes.equals(pt)); - } catch (Exception ex) { - fail("MultiPoint serialization failure"); - } - - //try - //{ - //FileOutputStream streamOut = new FileOutputStream("c:/temp/savedMultiPoint1.txt"); - //ObjectOutputStream oo = new ObjectOutputStream(streamOut); - //MultiPoint pt = new MultiPoint(); - //pt.add(10, 30); - //pt.add(120, 40); - //oo.writeObject(pt); - //} - //catch(Exception ex) - //{ - //fail("MultiPoint serialization failure"); - //} - - try { - InputStream s = TestSerialization.class - .getResourceAsStream("savedMultiPoint.txt"); - ObjectInputStream ii = new ObjectInputStream(s); - MultiPoint ptRes = (MultiPoint) ii.readObject(); - assertTrue(ptRes.getPoint(1).getY() == 40); - } catch (Exception ex) { - fail("MultiPoint serialization failure"); - } - try { - InputStream s = TestSerialization.class - .getResourceAsStream("savedMultiPoint1.txt"); - ObjectInputStream ii = new ObjectInputStream(s); - MultiPoint ptRes = (MultiPoint) ii.readObject(); - assertTrue(ptRes.getPoint(1).getY() == 40); - } catch (Exception ex) { - fail("MultiPoint serialization failure"); - } - } - - @Test - public void testSerializeLine() { - try { - ByteArrayOutputStream streamOut = new ByteArrayOutputStream(); - ObjectOutputStream oo = new ObjectOutputStream(streamOut); - Line pt = new Line(); - pt.setStart(new Point(10, 30)); - pt.setEnd(new Point(120, 40)); - oo.writeObject(pt); - ByteArrayInputStream streamIn = new ByteArrayInputStream( - streamOut.toByteArray()); - ObjectInputStream ii = new ObjectInputStream(streamIn); - Line ptRes = (Line) ii.readObject(); - assertTrue(ptRes.equals(pt)); - } catch (Exception ex) { - // fail("Line serialization failure"); - assertEquals(ex.getMessage(), "Cannot serialize this geometry"); - } - } - - @Test - public void testSerializeSR() { - try { - ByteArrayOutputStream streamOut = new ByteArrayOutputStream(); - ObjectOutputStream oo = new ObjectOutputStream(streamOut); - SpatialReference sr = SpatialReference.create(102100); - oo.writeObject(sr); - ByteArrayInputStream streamIn = new ByteArrayInputStream( - streamOut.toByteArray()); - ObjectInputStream ii = new ObjectInputStream(streamIn); - SpatialReference ptRes = (SpatialReference) ii.readObject(); - assertTrue(ptRes.equals(sr)); - } catch (Exception ex) { - fail("Spatial Reference serialization failure"); - } - } - - @Test - public void testSerializeEnvelope2D() { - try { - ByteArrayOutputStream streamOut = new ByteArrayOutputStream(); - ObjectOutputStream oo = new ObjectOutputStream(streamOut); - Envelope2D env = new Envelope2D(1.213948734, 2.213948734, 11.213948734, 12.213948734); - oo.writeObject(env); - ByteArrayInputStream streamIn = new ByteArrayInputStream( - streamOut.toByteArray()); - ObjectInputStream ii = new ObjectInputStream(streamIn); - Envelope2D envRes = (Envelope2D)ii.readObject(); - assertTrue(envRes.equals(env)); - } catch (Exception ex) { - fail("Envelope2D serialization failure"); - } - -// try -// { -// FileOutputStream streamOut = new FileOutputStream( -// "c:/temp/savedEnvelope2D.txt"); -// ObjectOutputStream oo = new ObjectOutputStream(streamOut); -// Envelope2D e = new Envelope2D(177.123, 188.234, 999.122, 888.999); -// oo.writeObject(e); -// } -// catch(Exception ex) -// { -// fail("Envelope2D serialization failure"); -// } - - try { - InputStream s = TestSerialization.class - .getResourceAsStream("savedEnvelope2D.txt"); - ObjectInputStream ii = new ObjectInputStream(s); - Envelope2D e = (Envelope2D) ii - .readObject(); - assertTrue(e != null); - assertTrue(e.equals(new Envelope2D(177.123, 188.234, 999.122, 888.999))); - } catch (Exception ex) { - fail("Envelope2D serialization failure"); - } - } - -} +package com.esri.core.geometry; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.FileOutputStream; +import java.io.InputStream; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import junit.framework.TestCase; +import org.junit.Test; + +public class TestSerialization extends TestCase { + @Override + protected void setUp() throws Exception { + super.setUp(); + } + + @Override + protected void tearDown() throws Exception { + super.tearDown(); + } + + @Test + public void testSerializePoint() { + try { + ByteArrayOutputStream streamOut = new ByteArrayOutputStream(); + ObjectOutputStream oo = new ObjectOutputStream(streamOut); + Point pt = new Point(10, 40); + oo.writeObject(pt); + ByteArrayInputStream streamIn = new ByteArrayInputStream( + streamOut.toByteArray()); + ObjectInputStream ii = new ObjectInputStream(streamIn); + Point ptRes = (Point) ii.readObject(); + assertTrue(ptRes.equals(pt)); + } catch (Exception ex) { + fail("Point serialization failure"); + + } + + //try + //{ + //FileOutputStream streamOut = new FileOutputStream("c:/temp/savedPoint1.txt"); + //ObjectOutputStream oo = new ObjectOutputStream(streamOut); + //Point pt = new Point(10, 40, 2); + //oo.writeObject(pt); + //} + //catch(Exception ex) + //{ + //fail("Point serialization failure"); + //} + + try { + InputStream s = TestSerialization.class + .getResourceAsStream("savedPoint.txt"); + ObjectInputStream ii = new ObjectInputStream(s); + Point ptRes = (Point) ii.readObject(); + assertTrue(ptRes.getX() == 10 && ptRes.getY() == 40); + } catch (Exception ex) { + fail("Point serialization failure"); + } + + try { + InputStream s = TestSerialization.class + .getResourceAsStream("savedPoint1.txt"); + ObjectInputStream ii = new ObjectInputStream(s); + Point ptRes = (Point) ii.readObject(); + assertTrue(ptRes.getX() == 10 && ptRes.getY() == 40 && ptRes.getZ() == 2); + } catch (Exception ex) { + fail("Point serialization failure"); + } + + } + + @Test + public void testSerializePolygon() { + try { + ByteArrayOutputStream streamOut = new ByteArrayOutputStream(); + ObjectOutputStream oo = new ObjectOutputStream(streamOut); + Polygon pt = new Polygon(); + pt.startPath(10, 10); + pt.lineTo(100, 100); + pt.lineTo(200, 100); + oo.writeObject(pt); + ByteArrayInputStream streamIn = new ByteArrayInputStream( + streamOut.toByteArray()); + ObjectInputStream ii = new ObjectInputStream(streamIn); + Polygon ptRes = (Polygon) ii.readObject(); + assertTrue(ptRes.equals(pt)); + } catch (Exception ex) { + fail("Polygon serialization failure"); + } + + try { + ByteArrayOutputStream streamOut = new ByteArrayOutputStream(); + ObjectOutputStream oo = new ObjectOutputStream(streamOut); + Polygon pt = new Polygon(); + pt.startPath(10, 10); + pt.lineTo(100, 100); + pt.lineTo(200, 100); + pt = (Polygon) GeometryEngine.simplify(pt, null); + oo.writeObject(pt); + ByteArrayInputStream streamIn = new ByteArrayInputStream( + streamOut.toByteArray()); + ObjectInputStream ii = new ObjectInputStream(streamIn); + Polygon ptRes = (Polygon) ii.readObject(); + assertTrue(ptRes.equals(pt)); + } catch (Exception ex) { + fail("Polygon serialization failure"); + } + + //try + //{ + //FileOutputStream streamOut = new FileOutputStream("c:/temp/savedPolygon1.txt"); + //ObjectOutputStream oo = new ObjectOutputStream(streamOut); + //Polygon pt = new Polygon(); + //pt.startPath(10, 10); + //pt.lineTo(100, 100); + //pt.lineTo(200, 100); + //pt = (Polygon)GeometryEngine.simplify(pt, null); + //oo.writeObject(pt); + //} + //catch(Exception ex) + //{ + //fail("Polygon serialization failure"); + //} + + try { + InputStream s = TestSerialization.class + .getResourceAsStream("savedPolygon.txt"); + ObjectInputStream ii = new ObjectInputStream(s); + Polygon ptRes = (Polygon) ii.readObject(); + assertTrue(ptRes != null); + } catch (Exception ex) { + fail("Polygon serialization failure"); + } + try { + InputStream s = TestSerialization.class + .getResourceAsStream("savedPolygon1.txt"); + ObjectInputStream ii = new ObjectInputStream(s); + Polygon ptRes = (Polygon) ii.readObject(); + assertTrue(ptRes != null); + } catch (Exception ex) { + fail("Polygon serialization failure"); + } + } + + @Test + public void testSerializePolyline() { + try { + ByteArrayOutputStream streamOut = new ByteArrayOutputStream(); + ObjectOutputStream oo = new ObjectOutputStream(streamOut); + Polyline pt = new Polyline(); + pt.startPath(10, 10); + pt.lineTo(100, 100); + pt.lineTo(200, 100); + oo.writeObject(pt); + ByteArrayInputStream streamIn = new ByteArrayInputStream( + streamOut.toByteArray()); + ObjectInputStream ii = new ObjectInputStream(streamIn); + Polyline ptRes = (Polyline) ii.readObject(); + assertTrue(ptRes.equals(pt)); + } catch (Exception ex) { + fail("Polyline serialization failure"); + } + + //try + //{ + //FileOutputStream streamOut = new FileOutputStream("c:/temp/savedPolyline1.txt"); + //ObjectOutputStream oo = new ObjectOutputStream(streamOut); + //Polyline pt = new Polyline(); + //pt.startPath(10, 10); + //pt.lineTo(100, 100); + //pt.lineTo(200, 100); + //oo.writeObject(pt); + //} + //catch(Exception ex) + //{ + //fail("Polyline serialization failure"); + //} + + try { + InputStream s = TestSerialization.class + .getResourceAsStream("savedPolyline.txt"); + ObjectInputStream ii = new ObjectInputStream(s); + Polyline ptRes = (Polyline) ii.readObject(); + assertTrue(ptRes != null); + } catch (Exception ex) { + fail("Polyline serialization failure"); + } + try { + InputStream s = TestSerialization.class + .getResourceAsStream("savedPolyline1.txt"); + ObjectInputStream ii = new ObjectInputStream(s); + Polyline ptRes = (Polyline) ii.readObject(); + assertTrue(ptRes != null); + } catch (Exception ex) { + fail("Polyline serialization failure"); + } + } + + @Test + public void testSerializeEnvelope() { + try { + ByteArrayOutputStream streamOut = new ByteArrayOutputStream(); + ObjectOutputStream oo = new ObjectOutputStream(streamOut); + Envelope pt = new Envelope(10, 10, 400, 300); + oo.writeObject(pt); + ByteArrayInputStream streamIn = new ByteArrayInputStream( + streamOut.toByteArray()); + ObjectInputStream ii = new ObjectInputStream(streamIn); + Envelope ptRes = (Envelope) ii.readObject(); + assertTrue(ptRes.equals(pt)); + } catch (Exception ex) { + fail("Envelope serialization failure"); + } + + //try + //{ + //FileOutputStream streamOut = new FileOutputStream("c:/temp/savedEnvelope1.txt"); + //ObjectOutputStream oo = new ObjectOutputStream(streamOut); + //Envelope pt = new Envelope(10, 10, 400, 300); + //oo.writeObject(pt); + //} + //catch(Exception ex) + //{ + //fail("Envelope serialization failure"); + //} + + try { + InputStream s = TestSerialization.class + .getResourceAsStream("savedEnvelope.txt"); + ObjectInputStream ii = new ObjectInputStream(s); + Envelope ptRes = (Envelope) ii.readObject(); + assertTrue(ptRes.getXMax() == 400); + } catch (Exception ex) { + fail("Envelope serialization failure"); + } + try { + InputStream s = TestSerialization.class + .getResourceAsStream("savedEnvelope1.txt"); + ObjectInputStream ii = new ObjectInputStream(s); + Envelope ptRes = (Envelope) ii.readObject(); + assertTrue(ptRes.getXMax() == 400); + } catch (Exception ex) { + fail("Envelope serialization failure"); + } + } + + @Test + public void testSerializeMultiPoint() { + try { + ByteArrayOutputStream streamOut = new ByteArrayOutputStream(); + ObjectOutputStream oo = new ObjectOutputStream(streamOut); + MultiPoint pt = new MultiPoint(); + pt.add(10, 30); + pt.add(120, 40); + oo.writeObject(pt); + ByteArrayInputStream streamIn = new ByteArrayInputStream( + streamOut.toByteArray()); + ObjectInputStream ii = new ObjectInputStream(streamIn); + MultiPoint ptRes = (MultiPoint) ii.readObject(); + assertTrue(ptRes.equals(pt)); + } catch (Exception ex) { + fail("MultiPoint serialization failure"); + } + + //try + //{ + //FileOutputStream streamOut = new FileOutputStream("c:/temp/savedMultiPoint1.txt"); + //ObjectOutputStream oo = new ObjectOutputStream(streamOut); + //MultiPoint pt = new MultiPoint(); + //pt.add(10, 30); + //pt.add(120, 40); + //oo.writeObject(pt); + //} + //catch(Exception ex) + //{ + //fail("MultiPoint serialization failure"); + //} + + try { + InputStream s = TestSerialization.class + .getResourceAsStream("savedMultiPoint.txt"); + ObjectInputStream ii = new ObjectInputStream(s); + MultiPoint ptRes = (MultiPoint) ii.readObject(); + assertTrue(ptRes.getPoint(1).getY() == 40); + } catch (Exception ex) { + fail("MultiPoint serialization failure"); + } + try { + InputStream s = TestSerialization.class + .getResourceAsStream("savedMultiPoint1.txt"); + ObjectInputStream ii = new ObjectInputStream(s); + MultiPoint ptRes = (MultiPoint) ii.readObject(); + assertTrue(ptRes.getPoint(1).getY() == 40); + } catch (Exception ex) { + fail("MultiPoint serialization failure"); + } + } + + @Test + public void testSerializeLine() { + try { + ByteArrayOutputStream streamOut = new ByteArrayOutputStream(); + ObjectOutputStream oo = new ObjectOutputStream(streamOut); + Line pt = new Line(); + pt.setStart(new Point(10, 30)); + pt.setEnd(new Point(120, 40)); + oo.writeObject(pt); + ByteArrayInputStream streamIn = new ByteArrayInputStream( + streamOut.toByteArray()); + ObjectInputStream ii = new ObjectInputStream(streamIn); + Line ptRes = (Line) ii.readObject(); + assertTrue(ptRes.equals(pt)); + } catch (Exception ex) { + // fail("Line serialization failure"); + assertEquals(ex.getMessage(), "Cannot serialize this geometry"); + } + } + + @Test + public void testSerializeSR() { + try { + ByteArrayOutputStream streamOut = new ByteArrayOutputStream(); + ObjectOutputStream oo = new ObjectOutputStream(streamOut); + SpatialReference sr = SpatialReference.create(102100); + oo.writeObject(sr); + ByteArrayInputStream streamIn = new ByteArrayInputStream( + streamOut.toByteArray()); + ObjectInputStream ii = new ObjectInputStream(streamIn); + SpatialReference ptRes = (SpatialReference) ii.readObject(); + assertTrue(ptRes.equals(sr)); + } catch (Exception ex) { + fail("Spatial Reference serialization failure"); + } + } + + @Test + public void testSerializeEnvelope2D() { + try { + ByteArrayOutputStream streamOut = new ByteArrayOutputStream(); + ObjectOutputStream oo = new ObjectOutputStream(streamOut); + Envelope2D env = new Envelope2D(1.213948734, 2.213948734, 11.213948734, 12.213948734); + oo.writeObject(env); + ByteArrayInputStream streamIn = new ByteArrayInputStream( + streamOut.toByteArray()); + ObjectInputStream ii = new ObjectInputStream(streamIn); + Envelope2D envRes = (Envelope2D)ii.readObject(); + assertTrue(envRes.equals(env)); + } catch (Exception ex) { + fail("Envelope2D serialization failure"); + } + +// try +// { +// FileOutputStream streamOut = new FileOutputStream( +// "c:/temp/savedEnvelope2D.txt"); +// ObjectOutputStream oo = new ObjectOutputStream(streamOut); +// Envelope2D e = new Envelope2D(177.123, 188.234, 999.122, 888.999); +// oo.writeObject(e); +// } +// catch(Exception ex) +// { +// fail("Envelope2D serialization failure"); +// } + + try { + InputStream s = TestSerialization.class + .getResourceAsStream("savedEnvelope2D.txt"); + ObjectInputStream ii = new ObjectInputStream(s); + Envelope2D e = (Envelope2D) ii + .readObject(); + assertTrue(e != null); + assertTrue(e.equals(new Envelope2D(177.123, 188.234, 999.122, 888.999))); + } catch (Exception ex) { + fail("Envelope2D serialization failure"); + } + } + +} diff --git a/src/test/java/com/esri/core/geometry/TestSimplify.java b/src/test/java/com/esri/core/geometry/TestSimplify.java index b7380851..47a741c1 100644 --- a/src/test/java/com/esri/core/geometry/TestSimplify.java +++ b/src/test/java/com/esri/core/geometry/TestSimplify.java @@ -1,1344 +1,1344 @@ -package com.esri.core.geometry; - -//import java.io.FileOutputStream; -//import java.io.PrintStream; -//import java.util.ArrayList; -//import java.util.List; -//import java.util.Random; -import java.io.IOException; - -import junit.framework.TestCase; - -import org.junit.Test; - -import com.fasterxml.jackson.core.JsonFactory; - -public class TestSimplify extends TestCase { - OperatorFactoryLocal factory = null; - OperatorSimplify simplifyOp = null; - OperatorSimplifyOGC simplifyOpOGC = null; - SpatialReference sr102100 = null; - SpatialReference sr4326 = null; - SpatialReference sr3857 = null; - - @Override - protected void setUp() throws Exception { - super.setUp(); - factory = OperatorFactoryLocal.getInstance(); - simplifyOp = (OperatorSimplify) factory - .getOperator(Operator.Type.Simplify); - simplifyOpOGC = (OperatorSimplifyOGC) factory - .getOperator(Operator.Type.SimplifyOGC); - sr102100 = SpatialReference.create(102100); - sr3857 = SpatialReference.create(3857);// PE_PCS_WGS_1984_WEB_MERCATOR_AUXSPHERE); - sr4326 = SpatialReference.create(4326);// enum_value2(SpatialReference, - // Code, GCS_WGS_1984)); - } - - @Override - protected void tearDown() throws Exception { - super.tearDown(); - } - - public Polygon makeNonSimplePolygon2() { - //MapGeometry mg = OperatorFactoryLocal.loadGeometryFromJSONFileDbg("c:/temp/simplify_polygon_gnomonic.txt"); - //Geometry res = OperatorSimplify.local().execute(mg.getGeometry(), mg.getSpatialReference(), true, null); - - - Polygon poly = new Polygon(); - poly.startPath(0, 0); - poly.lineTo(0, 15); - poly.lineTo(15, 15); - poly.lineTo(15, 0); - - // This is an interior ring but it is clockwise - poly.startPath(5, 5); - poly.lineTo(5, 6); - poly.lineTo(6, 6); - poly.lineTo(6, 5); - - return poly; - }// done - - /* - * ------------>---------------->--------------- | | | (1) | | | | --->--- - * ------->------- | | | | | (5) | | | | | | --<-- | | | | (2) | | | | | | | - * | | | | (4) | | | | | | | -->-- | | --<-- | ---<--- | | | | | | - * -------<------- | | (3) | -------------<---------------<--------------- - * -->-- - */ - - // Bowtie case with vertices at intersection - - public Polygon makeNonSimplePolygon5() { - Polygon poly = new Polygon(); - poly.startPath(10, 0); - poly.lineTo(0, 0); - poly.lineTo(5, 5); - poly.lineTo(10, 10); - poly.lineTo(0, 10); - poly.lineTo(5, 5); - - return poly; - }// done - - @Test - public void test0() { - Polygon poly1 = new Polygon(); - poly1.addEnvelope(new Envelope(10, 10, 40, 20), false); - Polygon poly2 = (Polygon) simplifyOp.execute(poly1, null, false, null); - boolean res = simplifyOp.isSimpleAsFeature(poly2, null, true, null, - null); - assertTrue(res); - // assertTrue(poly1.equals(poly2)); - }// done - - @Test - public void test0Poly() {// simple - Polygon poly1 = new Polygon(); - poly1.addEnvelope(new Envelope(10, 10, 40, 20), false); - poly1.addEnvelope(new Envelope(50, 10, 100, 20), false); - Polygon poly2 = (Polygon) simplifyOp.execute(poly1, null, false, null); - boolean res = simplifyOp.isSimpleAsFeature(poly2, null, true, null, - null); - assertTrue(res); - // assertTrue(poly1.equals(poly2)); - }// done - - @Test - public void test0Polygon_Spike1() {// non-simple (spike) - Polygon poly1 = new Polygon(); - poly1.startPath(10, 10); - poly1.lineTo(10, 20); - poly1.lineTo(40, 20); - poly1.lineTo(40, 10); - poly1.lineTo(60, 10); - poly1.lineTo(70, 10); - - boolean res = simplifyOp.isSimpleAsFeature(poly1, null, true, null, - null); - assertTrue(!res); - Polygon poly2 = (Polygon) simplifyOp.execute(poly1, null, false, null); - res = simplifyOp.isSimpleAsFeature(poly2, null, true, null, null); - assertTrue(res); - assertTrue(poly2.getPointCount() == 4); - }// done - - @Test - public void test0Polygon_Spike2() {// non-simple (spikes) - Polygon poly1 = new Polygon(); - // rectangle with a spike - poly1.startPath(10, 10); - poly1.lineTo(10, 20); - poly1.lineTo(40, 20); - poly1.lineTo(40, 10); - poly1.lineTo(60, 10); - poly1.lineTo(70, 10); - - // degenerate - poly1.startPath(100, 100); - poly1.lineTo(100, 120); - poly1.lineTo(100, 130); - - boolean res = simplifyOp.isSimpleAsFeature(poly1, null, true, null, - null); - assertTrue(!res); - Polygon poly2 = (Polygon) simplifyOp.execute(poly1, null, false, null); - res = simplifyOp.isSimpleAsFeature(poly2, null, true, null, null); - assertTrue(res); - assertTrue(poly2.getPointCount() == 4); - }// done - - @Test - public void test0Polygon_Spike3() {// non-simple (spikes) - Polygon poly1 = new Polygon(); - // degenerate - poly1.startPath(100, 100); - poly1.lineTo(100, 120); - poly1.lineTo(100, 130); - - boolean res = simplifyOp.isSimpleAsFeature(poly1, null, true, null, - null); - assertTrue(!res); - Polygon poly2 = (Polygon) simplifyOp.execute(poly1, null, false, null); - res = simplifyOp.isSimpleAsFeature(poly2, null, true, null, null); - assertTrue(res); - assertTrue(poly2.isEmpty()); - }// done - - @Test - public void test0PolygonSelfIntersect1() {// non-simple (self-intersection) - Polygon poly1 = new Polygon(); - // touch uncracked - poly1.startPath(0, 0); - poly1.lineTo(0, 100); - poly1.lineTo(100, 100); - poly1.lineTo(0, 50); - poly1.lineTo(100, 0); - - boolean res = simplifyOp.isSimpleAsFeature(poly1, null, true, null, - null); - assertTrue(!res); - Polygon poly2 = (Polygon) simplifyOp.execute(poly1, null, false, null); - res = simplifyOp.isSimpleAsFeature(poly2, null, true, null, null); - assertTrue(res); - assertTrue(!poly2.isEmpty()); - }// done - - @Test - public void test0PolygonSelfIntersect2() {// non-simple (self-intersection) - Polygon poly1 = new Polygon(); - poly1.startPath(0, 0); - poly1.lineTo(0, 100); - poly1.lineTo(100, 100); - poly1.lineTo(-100, 0); - // poly1.lineTo(100, 0); - - boolean res = simplifyOp.isSimpleAsFeature(poly1, null, true, null, - null); - assertTrue(!res); - Polygon poly2 = (Polygon) simplifyOp.execute(poly1, null, false, null); - res = simplifyOp.isSimpleAsFeature(poly2, null, true, null, null); - assertTrue(res); - assertTrue(!poly2.isEmpty()); - }// done - - @Test - public void test0PolygonSelfIntersect3() { - Polygon poly = new Polygon(); - poly.startPath(0, 0); - poly.lineTo(0, 15); - poly.lineTo(15, 15); - poly.lineTo(15, 0); - - // This part intersects with the first part - poly.startPath(10, 10); - poly.lineTo(10, 20); - poly.lineTo(20, 20); - poly.lineTo(20, 10); - - boolean res = simplifyOp - .isSimpleAsFeature(poly, null, true, null, null); - assertTrue(!res); - Polygon poly2 = (Polygon) simplifyOp.execute(poly, null, false, null); - res = simplifyOp.isSimpleAsFeature(poly2, null, true, null, null); - assertTrue(res); - assertTrue(!poly2.isEmpty()); - }// done - - @Test - public void test0PolygonInteriorRing1() { - Polygon poly = new Polygon(); - poly.startPath(0, 0); - poly.lineTo(0, 15); - poly.lineTo(15, 15); - poly.lineTo(15, 0); - - // This is an interior ring but it is clockwise - poly.startPath(5, 5); - poly.lineTo(5, 6); - poly.lineTo(6, 6); - poly.lineTo(6, 5); - - boolean res = simplifyOp - .isSimpleAsFeature(poly, null, true, null, null); - assertTrue(!res); - Polygon poly2 = (Polygon) simplifyOp.execute(poly, null, false, null); - res = simplifyOp.isSimpleAsFeature(poly2, null, true, null, null); - assertTrue(res); - assertTrue(!poly2.isEmpty()); - }// done - - @Test - public void test0PolygonInteriorRing2() { - Polygon poly = new Polygon(); - poly.startPath(0, 0); - poly.lineTo(0, 15); - poly.lineTo(15, 15); - poly.lineTo(15, 0); - - // This is an interior ring but it is clockwise - poly.startPath(5, 5); - poly.lineTo(5, 6); - poly.lineTo(6, 6); - poly.lineTo(6, 5); - - // This part intersects with the first part - poly.startPath(10, 10); - poly.lineTo(10, 20); - poly.lineTo(20, 20); - poly.lineTo(20, 10); - - boolean res = simplifyOp - .isSimpleAsFeature(poly, null, true, null, null); - assertTrue(!res); - Polygon poly2 = (Polygon) simplifyOp.execute(poly, null, false, null); - res = simplifyOp.isSimpleAsFeature(poly2, null, true, null, null); - assertTrue(res); - assertTrue(!poly2.isEmpty()); - }// done - - @Test - public void test0PolygonInteriorRingWithCommonBoundary1() { - // Two rings have common boundary - Polygon poly = new Polygon(); - poly.startPath(0, 0); - poly.lineTo(0, 10); - poly.lineTo(10, 10); - poly.lineTo(10, 0); - - poly.startPath(10, 0); - poly.lineTo(10, 10); - poly.lineTo(20, 10); - poly.lineTo(20, 0); - - boolean res = simplifyOp - .isSimpleAsFeature(poly, null, true, null, null); - assertTrue(!res); - Polygon poly2 = (Polygon) simplifyOp.execute(poly, null, false, null); - res = simplifyOp.isSimpleAsFeature(poly2, null, true, null, null); - assertTrue(res); - assertTrue(!poly2.isEmpty()); - }// done - - @Test - public void test0PolygonInteriorRingWithCommonBoundary2() { - // Two rings have common boundary - Polygon poly = new Polygon(); - poly.startPath(0, 0); - poly.lineTo(0, 10); - poly.lineTo(10, 10); - poly.lineTo(10, 0); - - poly.startPath(10, 5); - poly.lineTo(10, 6); - poly.lineTo(20, 6); - poly.lineTo(20, 5); - - boolean res = simplifyOp - .isSimpleAsFeature(poly, null, true, null, null); - assertTrue(!res); - Polygon poly2 = (Polygon) simplifyOp.execute(poly, null, false, null); - res = simplifyOp.isSimpleAsFeature(poly2, null, true, null, null); - assertTrue(res); - assertTrue(!poly2.isEmpty()); - }// done - - @Test - public void testPolygon() { - Polygon nonSimplePolygon = makeNonSimplePolygon(); - Polygon simplePolygon = (Polygon) simplifyOp.execute(nonSimplePolygon, - sr3857, false, null); - - boolean res = simplifyOp.isSimpleAsFeature(simplePolygon, sr3857, true, - null, null); - assertTrue(res); - - @SuppressWarnings("unused") - int partCount = simplePolygon.getPathCount(); - // assertTrue(partCount == 2); - - double area = simplePolygon.calculateRingArea2D(0); - assertTrue(Math.abs(area - 300) <= 0.0001); - - area = simplePolygon.calculateRingArea2D(1); - assertTrue(Math.abs(area - (-25.0)) <= 0.0001); - }// done - - @Test - public void testPolygon2() { - Polygon nonSimplePolygon2 = makeNonSimplePolygon2(); - double area = nonSimplePolygon2.calculateRingArea2D(1); - assertTrue(Math.abs(area - 1.0) <= 0.0001); - - Polygon simplePolygon2 = (Polygon) simplifyOp.execute( - nonSimplePolygon2, sr3857, false, null); - - boolean res = simplifyOp.isSimpleAsFeature(simplePolygon2, sr3857, - true, null, null); - assertTrue(res); - - area = simplePolygon2.calculateRingArea2D(0); - assertTrue(Math.abs(area - 225) <= 0.0001); - - area = simplePolygon2.calculateRingArea2D(1); - assertTrue(Math.abs(area - (-1.0)) <= 0.0001); - }// done - - @Test - public void testPolygon3() { - Polygon nonSimplePolygon3 = makeNonSimplePolygon3(); - Polygon simplePolygon3 = (Polygon) simplifyOp.execute( - nonSimplePolygon3, sr3857, false, null); - - boolean res = simplifyOp.isSimpleAsFeature(simplePolygon3, sr3857, - true, null, null); - assertTrue(res); - - double area = simplePolygon3.calculateRingArea2D(0); - assertTrue(Math.abs(area - 875) <= 0.0001); - - area = simplePolygon3.calculateRingArea2D(1); - assertTrue(Math.abs(area - (-225)) <= 0.0001 - || Math.abs(area - (-50.0)) <= 0.0001); - - area = simplePolygon3.calculateRingArea2D(2); - assertTrue(Math.abs(area - (-225)) <= 0.0001 - || Math.abs(area - (-50.0)) <= 0.0001); - - area = simplePolygon3.calculateRingArea2D(3); - assertTrue(Math.abs(area - 25) <= 0.0001); - - area = simplePolygon3.calculateRingArea2D(4); - assertTrue(Math.abs(area - 25) <= 0.0001); - }// done - - @Test - public void testPolyline() { - Polyline nonSimplePolyline = makeNonSimplePolyline(); - Polyline simplePolyline = (Polyline) simplifyOp.execute( - nonSimplePolyline, sr3857, false, null); - - int segmentCount = simplePolyline.getSegmentCount(); - assertTrue(segmentCount == 4); - }// done - - @Test - public void testPolygon4() { - Polygon nonSimplePolygon4 = makeNonSimplePolygon4(); - Polygon simplePolygon4 = (Polygon) simplifyOp.execute( - nonSimplePolygon4, sr3857, false, null); - boolean res = simplifyOp.isSimpleAsFeature(simplePolygon4, sr3857, - true, null, null); - assertTrue(res); - - assertTrue(simplePolygon4.getPointCount() == 5); - Point point = nonSimplePolygon4.getPoint(0); - assertTrue(point.getX() == 0.0 && point.getY() == 0.0); - point = nonSimplePolygon4.getPoint(1); - assertTrue(point.getX() == 0.0 && point.getY() == 10.0); - point = nonSimplePolygon4.getPoint(2); - assertTrue(point.getX() == 10.0 && point.getY() == 10.0); - point = nonSimplePolygon4.getPoint(3); - assertTrue(point.getX() == 10.0 && point.getY() == 0.0); - point = nonSimplePolygon4.getPoint(4); - assertTrue(point.getX() == 5.0 && point.getY() == 0.0); - }// done - - @Test - public void testPolygon5() { - Polygon nonSimplePolygon5 = makeNonSimplePolygon5(); - Polygon simplePolygon5 = (Polygon) simplifyOp.execute( - nonSimplePolygon5, sr3857, false, null); - assertTrue(simplePolygon5 != null); - - boolean res = simplifyOp.isSimpleAsFeature(simplePolygon5, sr3857, - true, null, null); - assertTrue(res); - - int pointCount = simplePolygon5.getPointCount(); - assertTrue(pointCount == 6); - - double area = simplePolygon5.calculateArea2D(); - assertTrue(Math.abs(area - 50.0) <= 0.001); - - }// done - - @Test - public void testPolygon6() { - Polygon nonSimplePolygon6 = makeNonSimplePolygon6(); - Polygon simplePolygon6 = (Polygon) simplifyOp.execute( - nonSimplePolygon6, sr3857, false, null); - - boolean res = simplifyOp.isSimpleAsFeature(simplePolygon6, sr3857, - true, null, null); - assertTrue(res); - } - - @Test - public void testPolygon7() { - Polygon nonSimplePolygon7 = makeNonSimplePolygon7(); - Polygon simplePolygon7 = (Polygon) simplifyOp.execute( - nonSimplePolygon7, sr3857, false, null); - - boolean res = simplifyOp.isSimpleAsFeature(simplePolygon7, sr3857, - true, null, null); - assertTrue(res); - } - - public Polygon makeNonSimplePolygon() { - Polygon poly = new Polygon(); - poly.startPath(0, 0); - poly.lineTo(0, 15); - poly.lineTo(15, 15); - poly.lineTo(15, 0); - - // This is an interior ring but it is clockwise - poly.startPath(5, 5); - poly.lineTo(5, 6); - poly.lineTo(6, 6); - poly.lineTo(6, 5); - - // This part intersects with the first part - poly.startPath(10, 10); - poly.lineTo(10, 20); - poly.lineTo(20, 20); - poly.lineTo(20, 10); - - return poly; - }// done - - /* - * ------------>---------------->--------------- | | | (1) | | | | --->--- - * ------->------- | | | | | (5) | | | | | | --<-- | | | | (2) | | | | | | | - * | | | | (4) | | | | | | | -->-- | | --<-- | ---<--- | | | | | | - * -------<------- | | (3) | -------------<---------------<--------------- - * -->-- - */ - - public Polygon makeNonSimplePolygon3() { - Polygon poly = new Polygon(); - poly.startPath(0, 0); - poly.lineTo(0, 25); - poly.lineTo(35, 25); - poly.lineTo(35, 0); - - poly.startPath(5, 5); - poly.lineTo(5, 15); - poly.lineTo(10, 15); - poly.lineTo(10, 5); - - poly.startPath(40, 0); - poly.lineTo(45, 0); - poly.lineTo(45, 5); - poly.lineTo(40, 5); - - poly.startPath(20, 10); - poly.lineTo(25, 10); - poly.lineTo(25, 15); - poly.lineTo(20, 15); - - poly.startPath(15, 5); - poly.lineTo(15, 20); - poly.lineTo(30, 20); - poly.lineTo(30, 5); - - return poly; - }// done - - public Polygon makeNonSimplePolygon4() { - Polygon poly = new Polygon(); - poly.startPath(0, 0); - poly.lineTo(0, 10); - poly.lineTo(10, 10); - poly.lineTo(10, 0); - poly.lineTo(5, 0); - poly.lineTo(5, 5); - poly.lineTo(5, 0); - - return poly; - }// done - - public Polygon makeNonSimplePolygon6() { - Polygon poly = new Polygon(); - poly.startPath(35.34407570857744, 54.00551247713412); - poly.lineTo(41.07663499357954, 20.0); - poly.lineTo(40.66372033705177, 26.217432321849017); - - poly.startPath(42.81936574509338, 20.0); - poly.lineTo(43.58226670584747, 20.0); - poly.lineTo(39.29611825817084, 22.64634933678729); - poly.lineTo(44.369873312241346, 25.81893670527215); - poly.lineTo(42.68845660737179, 20.0); - poly.lineTo(38.569549792944244, 56.47456192829393); - poly.lineTo(42.79274114188401, 45.45117792578003); - poly.lineTo(41.09512147544657, 70.0); - - return poly; - } - - public Polygon makeNonSimplePolygon7() { - Polygon poly = new Polygon(); - - poly.startPath(41.987895433319686, 53.75822619011542); - poly.lineTo(41.98789542535497, 53.75822618803151); - poly.lineTo(40.15120412113667, 68.12604154722113); - poly.lineTo(37.72272697311022, 67.92767094118877); - poly.lineTo(37.147347454283086, 49.497473094145505); - poly.lineTo(38.636627026664385, 51.036687142232736); - - poly.startPath(39.00920080789793, 62.063425518369016); - poly.lineTo(38.604912643136885, 70.0); - poly.lineTo(40.71826863485308, 43.60337143116787); - poly.lineTo(35.34407570857744, 54.005512477134126); - poly.lineTo(39.29611825817084, 22.64634933678729); - - return poly; - } - - public Polyline makeNonSimplePolyline() { - // This polyline has a short segment - Polyline poly = new Polyline(); - poly.startPath(0, 0); - poly.lineTo(10, 0); - poly.lineTo(10, 10); - poly.lineTo(10, 5); - poly.lineTo(-5, 5); - - return poly; - }// done - - @Test - public void testIsSimpleBasicsPoint() { - boolean result; - // point is always simple - Point pt = new Point(); - result = simplifyOp.isSimpleAsFeature(pt, sr4326, false, null, null); - assertTrue(result); - pt.setXY(0, 0); - result = simplifyOp.isSimpleAsFeature(pt, sr4326, false, null, null); - assertTrue(result); - pt.setXY(100000, 10000); - result = simplifyOp.isSimpleAsFeature(pt, sr4326, false, null, null); - assertTrue(result); - }// done - - @Test - public void testIsSimpleBasicsEnvelope() { - // Envelope is simple, when it's width and height are not degenerate - Envelope env = new Envelope(); - boolean result = simplifyOp.isSimpleAsFeature(env, sr4326, false, null, - null); // Empty is simple - assertTrue(result); - env.setCoords(0, 0, 10, 10); - result = simplifyOp.isSimpleAsFeature(env, sr4326, false, null, null); - assertTrue(result); - // sliver but still simple - env.setCoords(0, 0, 0 + sr4326.getTolerance() * 2, 10); - result = simplifyOp.isSimpleAsFeature(env, sr4326, false, null, null); - assertTrue(result); - // sliver and not simple - env.setCoords(0, 0, 0 + sr4326.getTolerance() * 0.5, 10); - result = simplifyOp.isSimpleAsFeature(env, sr4326, false, null, null); - assertTrue(!result); - }// done - - @Test - public void testIsSimpleBasicsLine() { - Line line = new Line(); - boolean result = simplifyOp.isSimpleAsFeature(line, sr4326, false, - null, null); - assertTrue(!result); - - line.setStart(new Point(0, 0)); - // line.setEndXY(0, 0); - result = simplifyOp.isSimpleAsFeature(line, sr4326, false, null, null); - assertTrue(!result); - line.setEnd(new Point(1, 0)); - result = simplifyOp.isSimpleAsFeature(line, sr4326, false, null, null); - assertTrue(result); - }// done - - @Test - public void testIsSimpleMultiPoint1() { - MultiPoint mp = new MultiPoint(); - boolean result = simplifyOp.isSimpleAsFeature(mp, sr4326, false, null, - null); - assertTrue(result);// empty is simple - result = simplifyOp.isSimpleAsFeature( - simplifyOp.execute(mp, sr4326, false, null), sr4326, false, - null, null); - assertTrue(result); - }// done - - @Test - public void testIsSimpleMultiPoint2FarApart() { - // Two point test: far apart - MultiPoint mp = new MultiPoint(); - mp.add(20, 10); - mp.add(100, 100); - boolean result = simplifyOp.isSimpleAsFeature(mp, sr4326, false, null, - null); - assertTrue(result); - result = simplifyOp.isSimpleAsFeature( - simplifyOp.execute(mp, sr4326, false, null), sr4326, false, - null, null); - assertTrue(result); - assertTrue(mp.getPointCount() == 2); - }// done - - @Test - public void testIsSimpleMultiPointCoincident() { - // Two point test: coincident - MultiPoint mp = new MultiPoint(); - mp.add(100, 100); - mp.add(100, 100); - boolean result = simplifyOp.isSimpleAsFeature(mp, sr4326, false, null, - null); - assertTrue(!result); - MultiPoint mpS; - result = simplifyOp.isSimpleAsFeature( - mpS = (MultiPoint) simplifyOp.execute(mp, sr4326, false, null), - sr4326, false, null, null); - assertTrue(result); - assertTrue(mpS.getPointCount() == 1); - }// done - - @Test - public void testMultiPointSR4326_CR184439() { - OperatorFactoryLocal engine = OperatorFactoryLocal.getInstance(); - OperatorSimplify simpOp = (OperatorSimplify) engine - .getOperator(Operator.Type.Simplify); - NonSimpleResult nonSimpResult = new NonSimpleResult(); - nonSimpResult.m_reason = NonSimpleResult.Reason.NotDetermined; - MultiPoint multiPoint = new MultiPoint(); - multiPoint.add(0, 0); - multiPoint.add(0, 1); - multiPoint.add(0, 0); - Boolean multiPointIsSimple = simpOp.isSimpleAsFeature(multiPoint, - SpatialReference.create(4326), true, nonSimpResult, null); - assertFalse(multiPointIsSimple); - assertTrue(nonSimpResult.m_reason == NonSimpleResult.Reason.Clustering); - assertTrue(nonSimpResult.m_vertexIndex1 == 0); - assertTrue(nonSimpResult.m_vertexIndex2 == 2); - } - - @Test - public void testIsSimpleMultiPointCloserThanTolerance() { - // Two point test: closer than tolerance - MultiPoint mp = new MultiPoint(); - MultiPoint mpS; - mp.add(100, 100); - mp.add(100, 100 + sr4326.getTolerance() * .5); - boolean result = simplifyOp.isSimpleAsFeature(mp, sr4326, false, null, - null); - assertTrue(result); - result = simplifyOp.isSimpleAsFeature( - mpS = (MultiPoint) simplifyOp.execute(mp, sr4326, false, null), - sr4326, false, null, null); - assertTrue(result); - assertTrue(mpS.getPointCount() == 2); - }// done - - @Test - public void testIsSimpleMultiPointFarApart2() { - // 5 point test: far apart - MultiPoint mp = new MultiPoint(); - mp.add(100, 100); - mp.add(100, 101); - mp.add(101, 101); - mp.add(11, 1); - mp.add(11, 14); - MultiPoint mpS; - boolean result = simplifyOp.isSimpleAsFeature(mp, sr4326, false, null, - null); - assertTrue(result); - result = simplifyOp.isSimpleAsFeature( - mpS = (MultiPoint) simplifyOp.execute(mp, sr4326, false, null), - sr4326, false, null, null); - assertTrue(result); - assertTrue(mpS.getPointCount() == 5); - }// done - - @Test - public void testIsSimpleMultiPoint_coincident2() { - // 5 point test: coincident - MultiPoint mp = new MultiPoint(); - mp.add(100, 100); - mp.add(100, 101); - mp.add(100, 100); - mp.add(11, 1); - mp.add(11, 14); - boolean result = simplifyOp.isSimpleAsFeature(mp, sr4326, false, null, - null); - assertTrue(!result); - MultiPoint mpS; - result = simplifyOp.isSimpleAsFeature( - mpS = (MultiPoint) simplifyOp.execute(mp, sr4326, false, null), - sr4326, false, null, null); - assertTrue(result); - assertTrue(mpS.getPointCount() == 4); - assertEquals(mpS.getPoint(0).getX(), 100, 1e-7); - assertEquals(mpS.getPoint(0).getY(), 100, 1e-7); - assertEquals(mpS.getPoint(1).getX(), 100, 1e-7); - assertEquals(mpS.getPoint(1).getY(), 101, 1e-7); - assertEquals(mpS.getPoint(2).getX(), 11, 1e-7); - assertEquals(mpS.getPoint(2).getY(), 1, 1e-7); - assertEquals(mpS.getPoint(3).getX(), 11, 1e-7); - assertEquals(mpS.getPoint(3).getY(), 14, 1e-7); - }// done - - @Test - public void testIsSimpleMultiPointCloserThanTolerance2() { - // 5 point test: closer than tolerance - MultiPoint mp = new MultiPoint(); - mp.add(100, 100); - mp.add(100, 101); - mp.add(100, 100 + sr4326.getTolerance() / 2); - mp.add(11, 1); - mp.add(11, 14); - MultiPoint mpS; - boolean result = simplifyOp.isSimpleAsFeature(mp, sr4326, false, null, - null); - assertTrue(result); - result = simplifyOp.isSimpleAsFeature( - mpS = (MultiPoint) simplifyOp.execute(mp, sr4326, false, null), - sr4326, false, null, null); - assertTrue(result); - assertTrue(mpS.getPointCount() == 5); - }// done - - @Test - public void testIsSimplePolyline() { - Polyline poly = new Polyline(); - boolean result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, - null, null); - assertTrue(result);// empty is simple - } - - @Test - public void testIsSimplePolylineFarApart() { - // Two point test: far apart - Polyline poly = new Polyline(); - poly.startPath(20, 10); - poly.lineTo(100, 100); - boolean result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, - null, null); - assertTrue(result); - } - - @Test - public void testIsSimplePolylineCoincident() { - // Two point test: coincident - Polyline poly = new Polyline(); - poly.startPath(100, 100); - poly.lineTo(100, 100); - boolean result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, - null, null); - assertTrue(!result); - @SuppressWarnings("unused") - Polyline polyS; - result = simplifyOp.isSimpleAsFeature( - polyS = (Polyline) simplifyOp - .execute(poly, sr4326, false, null), sr4326, false, - null, null); - assertTrue(result); - } - - @Test - public void testIsSimplePolylineCloserThanTolerance() { - // Two point test: closer than tolerance - Polyline poly = new Polyline(); - poly.startPath(100, 100); - poly.lineTo(100, 100 + sr4326.getTolerance() / 2); - boolean result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, - null, null); - assertTrue(!result); - @SuppressWarnings("unused") - Polyline polyS; - result = simplifyOp.isSimpleAsFeature( - polyS = (Polyline) simplifyOp - .execute(poly, sr4326, false, null), sr4326, false, - null, null); - assertTrue(result); - } - - @Test - public void testIsSimplePolylineFarApartSelfOverlap0() { - // 3 point test: far apart, self overlapping - Polyline poly = new Polyline(); - poly.startPath(0, 0); - poly.lineTo(100, 100); - poly.lineTo(0, 0); - boolean result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, - null, null); - assertTrue(result);// TO CONFIRM should be false - } - - @Test - public void testIsSimplePolylineSelfIntersect() { - // 4 point test: far apart, self intersecting - Polyline poly = new Polyline(); - poly.startPath(0, 0); - poly.lineTo(100, 100); - poly.lineTo(0, 100); - poly.lineTo(100, 0); - boolean result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, - null, null); - assertTrue(result);// TO CONFIRM should be false - } - - @Test - public void testIsSimplePolylineDegenerateSegment() { - // 4 point test: degenerate segment - Polyline poly = new Polyline(); - poly.startPath(0, 0); - poly.lineTo(100, 100); - poly.lineTo(100, 100 + sr4326.getTolerance() / 2); - poly.lineTo(100, 0); - boolean result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, - null, null); - assertTrue(!result); - @SuppressWarnings("unused") - Polyline polyS; - result = simplifyOp.isSimpleAsFeature( - polyS = (Polyline) simplifyOp - .execute(poly, sr4326, false, null), sr4326, false, - null, null); - assertTrue(result); - { - Polyline other = new Polyline(); - other.startPath(0, 0); - other.lineTo(100, 100); - other.lineTo(100, 0); - other.equals(poly); - } - } - - @Test - public void testIsSimplePolylineFarApartSelfOverlap() { - // 3 point test: far apart, self overlapping - Polyline poly = new Polyline(); - poly.startPath(0, 0); - poly.lineTo(100, 100); - poly.lineTo(0, 0); - boolean result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, - null, null); - assertTrue(result);// TO CONFIRM should be false - } - - @Test - public void testIsSimplePolylineFarApartIntersect() { - // 4 point 2 parts test: far apart, intersecting parts - Polyline poly = new Polyline(); - poly.startPath(0, 0); - poly.lineTo(100, 100); - poly.startPath(100, 0); - poly.lineTo(0, 100); - - boolean result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, - null, null); - assertTrue(result);// TO CONFIRM should be false - } - - @Test - public void testIsSimplePolylineFarApartOverlap2() { - // 4 point 2 parts test: far apart, overlapping parts. second part - // starts where first one ends - Polyline poly = new Polyline(); - poly.startPath(0, 0); - poly.lineTo(100, 100); - poly.startPath(100, 100); - poly.lineTo(0, 100); - - boolean result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, - null, null); - assertTrue(result);// TO CONFIRM should be false - } - - @Test - public void testIsSimplePolylineDegenerateVertical() { - // 3 point test: degenerate vertical line - Polyline poly = new Polyline(); - poly.startPath(0, 0); - poly.lineTo(new Point(100, 100)); - poly.lineTo(new Point(100, 100)); - boolean result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, - null, null); - assertTrue(!result); - Polyline polyS; - result = simplifyOp.isSimpleAsFeature( - polyS = (Polyline) simplifyOp - .execute(poly, sr4326, false, null), sr4326, false, - null, null); - assertTrue(result); - assertTrue(polyS.getPointCount() == 2); - } - - @Test - public void testIsSimplePolylineEmptyPath() { - // TODO: any way to test this? - // Empty path - // Polyline poly = new Polyline(); - // assertTrue(poly.isEmpty()); - // poly.addPath(new Polyline(), 0, true); - // assertTrue(poly.isEmpty()); - // boolean result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, - // null, null); - // assertTrue(result); - } - - @Test - public void testIsSimplePolylineSinglePointInPath() { - // Single point in path - Polyline poly = new Polyline(); - poly.startPath(0, 0); - poly.lineTo(100, 100); - poly.removePoint(0, 1); - boolean result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, - null, null); - assertTrue(!result); - Polyline polyS; - result = simplifyOp.isSimpleAsFeature( - polyS = (Polyline) simplifyOp - .execute(poly, sr4326, false, null), sr4326, false, - null, null); - assertTrue(result); - assertTrue(polyS.isEmpty()); - } - - @Test - public void testIsSimplePolygon() { - Polygon poly = new Polygon(); - boolean result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, - null, null); - assertTrue(result);// empty is simple - result = simplifyOp.isSimpleAsFeature( - simplifyOp.execute(poly, sr4326, false, null), sr4326, false, - null, null); - assertTrue(result);// empty is simple - } - - @Test - public void testIsSimplePolygonEmptyPath() { - // TODO: - // Empty path - // Polygon poly = new Polygon(); - // poly.addPath(new Polyline(), 0, true); - // assertTrue(poly.getPathCount() == 1); - // boolean result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, - // null, - // null); - // assertTrue(result); - // result = simplifyOp.isSimpleAsFeature(simplifyOp.execute(poly, - // sr4326, false, null), sr4326, false, null, null); - // assertTrue(result);// empty is simple - // assertTrue(poly.getPathCount() == 1); - } - - @Test - public void testIsSimplePolygonIncomplete1() { - // Incomplete polygon 1 - Polygon poly = new Polygon(); - poly.startPath(0, 0); - poly.lineTo(100, 100); - // poly.removePoint(0, 1);//TO CONFIRM no removePoint method in Java - boolean result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, - null, null); - assertTrue(!result); - } - - @Test - public void testIsSimplePolygonIncomplete2() { - // Incomplete polygon 2 - Polygon poly = new Polygon(); - poly.startPath(0, 0); - poly.lineTo(100, 100); - boolean result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, - null, null); - assertTrue(!result); - } - - @Test - public void testIsSimplePolygonDegenerateTriangle() { - // Degenerate triangle (self overlap) - Polygon poly = new Polygon(); - poly.startPath(0, 0); - poly.lineTo(100, 100); - poly.lineTo(0, 0); - boolean result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, - null, null); - assertTrue(!result); - } - - @Test - public void testIsSimplePolygonSelfIntersect() { - // Self intersection - cracking is needed - Polygon poly = new Polygon(); - poly.startPath(0, 0); - poly.lineTo(100, 100); - poly.lineTo(0, 100); - poly.lineTo(100, 0); - boolean result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, - null, null); - assertTrue(!result); - } - - @Test - public void testIsSimplePolygonRectangleHole() { - // Rectangle and rectangular hole that has one segment overlapping - // with the with the exterior ring. Cracking is needed. - Polygon poly = new Polygon(); - poly.addEnvelope(new Envelope(-200, -100, 200, 100), false); - poly.addEnvelope(new Envelope(-100, -100, 100, 50), true); - boolean result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, - null, null); - assertTrue(!result); - poly.reverseAllPaths(); - result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, null, null); - assertTrue(!result); - } - - @Test - public void testIsSimplePolygonRectangleHole2() { - // Rectangle and rectangular hole - Polygon poly = new Polygon(); - poly.addEnvelope(new Envelope(-200, -100, 200, 100), false); - poly.addEnvelope(new Envelope(-100, -50, 100, 50), true); - boolean result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, - null, null); - assertTrue(result); - poly.reverseAllPaths(); - result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, null, null); - assertTrue(!result); - } - - @Test - public void testIsSimplePolygonSelfIntersectAtVertex() { - // Self intersection at vertex - Polygon poly = new Polygon(); - poly.startPath(0, 0); - poly.lineTo(50, 50); - poly.lineTo(100, 100); - poly.lineTo(0, 100); - poly.lineTo(50, 50); - poly.lineTo(100, 0); - boolean result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, - null, null); - assertTrue(!result); - result = simplifyOp.isSimpleAsFeature( - simplifyOp.execute(poly, sr4326, false, null), sr4326, false, - null, null); - assertTrue(result); - } - - @Test - public void testIsSimplePolygon_2EdgesTouchAtVertex() { - // No self-intersection, but more than two edges touch at the same - // vertex. Simple for ArcGIS, not simple for OGC - Polygon poly = new Polygon(); - poly.startPath(0, 0); - poly.lineTo(50, 50); - poly.lineTo(0, 100); - poly.lineTo(100, 100); - poly.lineTo(50, 50); - poly.lineTo(100, 0); - boolean result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, - null, null); - assertTrue(result); - } - - @Test - public void testIsSimplePolygonTriangle() { - // Triangle - Polygon poly = new Polygon(); - poly.startPath(0, 0); - poly.lineTo(100, 100); - poly.lineTo(100, 0); - boolean result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, - null, null); - assertTrue(result); - poly.reverseAllPaths(); - result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, null, null); - assertTrue(!result); - } - - @Test - public void testIsSimplePolygonRectangle() { - // Rectangle - Polygon poly = new Polygon(); - poly.addEnvelope(new Envelope(-200, -100, 100, 200), false); - boolean result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, - null, null); - assertTrue(result); - poly.reverseAllPaths(); - result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, null, null); - assertTrue(!result); - } - - @Test - public void testIsSimplePolygonRectangleHoleWrongDirection() { - // Rectangle and rectangular hole that has wrong direction - Polygon poly = new Polygon(); - poly.addEnvelope(new Envelope(-200, -100, 200, 100), false); - poly.addEnvelope(new Envelope(-100, -50, 100, 50), false); - boolean result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, - null, null); - assertTrue(!result); - poly.reverseAllPaths(); - result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, null, null); - assertTrue(!result); - } - - @Test - public void testIsSimplePolygon_2RectanglesSideBySide() { - // Two rectangles side by side, simple - Polygon poly = new Polygon(); - poly.addEnvelope(new Envelope(-200, -100, 200, 100), false); - poly.addEnvelope(new Envelope(220, -50, 300, 50), false); - boolean result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, - null, null); - assertTrue(result); - poly.reverseAllPaths(); - result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, null, null); - assertTrue(!result); - } - - @Test - public void testIsSimplePolygonRectangleOneBelow() { - // Two rectangles one below another, simple - Polygon poly = new Polygon(); - poly.addEnvelope(new Envelope(50, 50, 100, 100), false); - poly.addEnvelope(new Envelope(50, 200, 100, 250), false); - boolean result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, - null, null); - assertTrue(result); - poly.reverseAllPaths(); - result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, null, null); - assertTrue(!result); - } - - @Test - public void testisSimpleOGC() { - Polyline poly = new Polyline(); - poly.startPath(0, 0); - poly.lineTo(10, 0); - boolean result = simplifyOpOGC.isSimpleOGC(poly, sr4326, true, null, - null); - assertTrue(result); - - poly = new Polyline(); - poly.startPath(0, 0); - poly.lineTo(10, 10); - poly.lineTo(0, 10); - poly.lineTo(10, 0); - NonSimpleResult nsr = new NonSimpleResult(); - result = simplifyOpOGC.isSimpleOGC(poly, sr4326, true, nsr, null); - assertTrue(!result); - assertTrue(nsr.m_reason == NonSimpleResult.Reason.Cracking); - - MultiPoint mp = new MultiPoint(); - mp.add(0, 0); - mp.add(10, 0); - result = simplifyOpOGC.isSimpleOGC(mp, sr4326, true, null, null); - assertTrue(result); - - mp = new MultiPoint(); - mp.add(10, 0); - mp.add(10, 0); - nsr = new NonSimpleResult(); - result = simplifyOpOGC.isSimpleOGC(mp, sr4326, true, nsr, null); - assertTrue(!result); - assertTrue(nsr.m_reason == NonSimpleResult.Reason.Clustering); - } - - @Test - public void testPolylineIsSimpleForOGC() { - OperatorImportFromJson importerJson = (OperatorImportFromJson) factory - .getOperator(Operator.Type.ImportFromJson); - OperatorSimplify simplify = (OperatorSimplify) factory - .getOperator(Operator.Type.Simplify); - - { - String s = "{\"paths\":[[[0, 10], [8, 5], [5, 2], [6, 0]]]}"; - Geometry g = importerJson.execute(Geometry.Type.Unknown, - JsonParserReader.createFromString(s)).getGeometry(); - boolean res = simplifyOpOGC.isSimpleOGC(g, null, true, null, null); - assertTrue(res); - } - { - String s = "{\"paths\":[[[0, 10], [6, 0], [7, 5], [0, 3]]]}";// self - // intersection - Geometry g = importerJson.execute(Geometry.Type.Unknown, - JsonParserReader.createFromString(s)).getGeometry(); - boolean res = simplifyOpOGC.isSimpleOGC(g, null, true, null, null); - assertTrue(!res); - } - - { - String s = "{\"paths\":[[[0, 10], [6, 0], [0, 3], [0, 10]]]}"; // closed - Geometry g = importerJson.execute(Geometry.Type.Unknown, - JsonParserReader.createFromString(s)).getGeometry(); - boolean res = simplifyOpOGC.isSimpleOGC(g, null, true, null, null); - assertTrue(res); - } - - { - String s = "{\"paths\":[[[0, 10], [5, 5], [6, 0], [0, 3], [5, 5], [0, 9], [0, 10]]]}"; // closed - // with - // self - // tangent - Geometry g = importerJson.execute(Geometry.Type.Unknown, - JsonParserReader.createFromString(s)).getGeometry(); - boolean res = simplifyOpOGC.isSimpleOGC(g, null, true, null, null); - assertTrue(!res); - } - - { - String s = "{\"paths\":[[[0, 10], [5, 2]], [[5, 2], [6, 0]]]}";// two - // paths - // connected - // at - // a - // point - Geometry g = importerJson.execute(Geometry.Type.Unknown, - JsonParserReader.createFromString(s)).getGeometry(); - boolean res = simplifyOpOGC.isSimpleOGC(g, null, true, null, null); - assertTrue(res); - } - - { - String s = "{\"paths\":[[[0, 0], [3, 3], [5, 0], [0, 0]], [[0, 10], [3, 3], [10, 10], [0, 10]]]}";// two - // closed - // rings - // touch - // at - // one - // point - Geometry g = importerJson.execute(Geometry.Type.Unknown, - JsonParserReader.createFromString(s)).getGeometry(); - boolean res = simplifyOpOGC.isSimpleOGC(g, null, true, null, null); - assertTrue(!res); - } - - { - String s = "{\"paths\":[[[0, 0], [10, 10]], [[0, 10], [10, 0]]]}";// two - // lines - // intersect - Geometry g = importerJson.execute(Geometry.Type.Unknown, - JsonParserReader.createFromString(s)).getGeometry(); - boolean res = simplifyOpOGC.isSimpleOGC(g, null, true, null, null); - assertTrue(!res); - } - - { - String s = "{\"paths\":[[[0, 0], [5, 5], [0, 10]], [[10, 10], [5, 5], [10, 0]]]}";// two - // paths - // share - // mid - // point. - Geometry g = importerJson.execute(Geometry.Type.Unknown, - JsonParserReader.createFromString(s)).getGeometry(); - boolean res = simplifyOpOGC.isSimpleOGC(g, null, true, null, null); - assertTrue(!res); - } - - } - - @Test - public void testFillRule() { - //self intersecting star shape - MapGeometry mg = OperatorImportFromJson.local().execute(Geometry.Type.Unknown, "{\"rings\":[[[0,0], [5,10], [10, 0], [0, 7], [10, 7], [0, 0]]]}"); - Polygon poly = (Polygon)mg.getGeometry(); - assertTrue(poly.getFillRule() == Polygon.FillRule.enumFillRuleOddEven); - poly.setFillRule(Polygon.FillRule.enumFillRuleWinding); - assertTrue(poly.getFillRule() == Polygon.FillRule.enumFillRuleWinding); - Geometry simpleResult = OperatorSimplify.local().execute(poly, null, true, null); - assertTrue(((Polygon)simpleResult).getFillRule() == Polygon.FillRule.enumFillRuleOddEven); - //solid start without holes: - MapGeometry mg1 = OperatorImportFromJson.local().execute(Geometry.Type.Unknown, "{\"rings\":[[[0,0],[2.5925925925925926,5.185185185185185],[0,7],[3.5,7],[5,10],[6.5,7],[10,7],[7.407407407407407,5.185185185185185],[10,0],[5,3.5],[0,0]]]}"); - boolean equals = OperatorEquals.local().execute(mg1.getGeometry(), simpleResult, null, null); - assertTrue(equals); - } - -} +package com.esri.core.geometry; + +//import java.io.FileOutputStream; +//import java.io.PrintStream; +//import java.util.ArrayList; +//import java.util.List; +//import java.util.Random; +import java.io.IOException; + +import junit.framework.TestCase; + +import org.junit.Test; + +import com.fasterxml.jackson.core.JsonFactory; + +public class TestSimplify extends TestCase { + OperatorFactoryLocal factory = null; + OperatorSimplify simplifyOp = null; + OperatorSimplifyOGC simplifyOpOGC = null; + SpatialReference sr102100 = null; + SpatialReference sr4326 = null; + SpatialReference sr3857 = null; + + @Override + protected void setUp() throws Exception { + super.setUp(); + factory = OperatorFactoryLocal.getInstance(); + simplifyOp = (OperatorSimplify) factory + .getOperator(Operator.Type.Simplify); + simplifyOpOGC = (OperatorSimplifyOGC) factory + .getOperator(Operator.Type.SimplifyOGC); + sr102100 = SpatialReference.create(102100); + sr3857 = SpatialReference.create(3857);// PE_PCS_WGS_1984_WEB_MERCATOR_AUXSPHERE); + sr4326 = SpatialReference.create(4326);// enum_value2(SpatialReference, + // Code, GCS_WGS_1984)); + } + + @Override + protected void tearDown() throws Exception { + super.tearDown(); + } + + public Polygon makeNonSimplePolygon2() { + //MapGeometry mg = OperatorFactoryLocal.loadGeometryFromJSONFileDbg("c:/temp/simplify_polygon_gnomonic.txt"); + //Geometry res = OperatorSimplify.local().execute(mg.getGeometry(), mg.getSpatialReference(), true, null); + + + Polygon poly = new Polygon(); + poly.startPath(0, 0); + poly.lineTo(0, 15); + poly.lineTo(15, 15); + poly.lineTo(15, 0); + + // This is an interior ring but it is clockwise + poly.startPath(5, 5); + poly.lineTo(5, 6); + poly.lineTo(6, 6); + poly.lineTo(6, 5); + + return poly; + }// done + + /* + * ------------>---------------->--------------- | | | (1) | | | | --->--- + * ------->------- | | | | | (5) | | | | | | --<-- | | | | (2) | | | | | | | + * | | | | (4) | | | | | | | -->-- | | --<-- | ---<--- | | | | | | + * -------<------- | | (3) | -------------<---------------<--------------- + * -->-- + */ + + // Bowtie case with vertices at intersection + + public Polygon makeNonSimplePolygon5() { + Polygon poly = new Polygon(); + poly.startPath(10, 0); + poly.lineTo(0, 0); + poly.lineTo(5, 5); + poly.lineTo(10, 10); + poly.lineTo(0, 10); + poly.lineTo(5, 5); + + return poly; + }// done + + @Test + public void test0() { + Polygon poly1 = new Polygon(); + poly1.addEnvelope(new Envelope(10, 10, 40, 20), false); + Polygon poly2 = (Polygon) simplifyOp.execute(poly1, null, false, null); + boolean res = simplifyOp.isSimpleAsFeature(poly2, null, true, null, + null); + assertTrue(res); + // assertTrue(poly1.equals(poly2)); + }// done + + @Test + public void test0Poly() {// simple + Polygon poly1 = new Polygon(); + poly1.addEnvelope(new Envelope(10, 10, 40, 20), false); + poly1.addEnvelope(new Envelope(50, 10, 100, 20), false); + Polygon poly2 = (Polygon) simplifyOp.execute(poly1, null, false, null); + boolean res = simplifyOp.isSimpleAsFeature(poly2, null, true, null, + null); + assertTrue(res); + // assertTrue(poly1.equals(poly2)); + }// done + + @Test + public void test0Polygon_Spike1() {// non-simple (spike) + Polygon poly1 = new Polygon(); + poly1.startPath(10, 10); + poly1.lineTo(10, 20); + poly1.lineTo(40, 20); + poly1.lineTo(40, 10); + poly1.lineTo(60, 10); + poly1.lineTo(70, 10); + + boolean res = simplifyOp.isSimpleAsFeature(poly1, null, true, null, + null); + assertTrue(!res); + Polygon poly2 = (Polygon) simplifyOp.execute(poly1, null, false, null); + res = simplifyOp.isSimpleAsFeature(poly2, null, true, null, null); + assertTrue(res); + assertTrue(poly2.getPointCount() == 4); + }// done + + @Test + public void test0Polygon_Spike2() {// non-simple (spikes) + Polygon poly1 = new Polygon(); + // rectangle with a spike + poly1.startPath(10, 10); + poly1.lineTo(10, 20); + poly1.lineTo(40, 20); + poly1.lineTo(40, 10); + poly1.lineTo(60, 10); + poly1.lineTo(70, 10); + + // degenerate + poly1.startPath(100, 100); + poly1.lineTo(100, 120); + poly1.lineTo(100, 130); + + boolean res = simplifyOp.isSimpleAsFeature(poly1, null, true, null, + null); + assertTrue(!res); + Polygon poly2 = (Polygon) simplifyOp.execute(poly1, null, false, null); + res = simplifyOp.isSimpleAsFeature(poly2, null, true, null, null); + assertTrue(res); + assertTrue(poly2.getPointCount() == 4); + }// done + + @Test + public void test0Polygon_Spike3() {// non-simple (spikes) + Polygon poly1 = new Polygon(); + // degenerate + poly1.startPath(100, 100); + poly1.lineTo(100, 120); + poly1.lineTo(100, 130); + + boolean res = simplifyOp.isSimpleAsFeature(poly1, null, true, null, + null); + assertTrue(!res); + Polygon poly2 = (Polygon) simplifyOp.execute(poly1, null, false, null); + res = simplifyOp.isSimpleAsFeature(poly2, null, true, null, null); + assertTrue(res); + assertTrue(poly2.isEmpty()); + }// done + + @Test + public void test0PolygonSelfIntersect1() {// non-simple (self-intersection) + Polygon poly1 = new Polygon(); + // touch uncracked + poly1.startPath(0, 0); + poly1.lineTo(0, 100); + poly1.lineTo(100, 100); + poly1.lineTo(0, 50); + poly1.lineTo(100, 0); + + boolean res = simplifyOp.isSimpleAsFeature(poly1, null, true, null, + null); + assertTrue(!res); + Polygon poly2 = (Polygon) simplifyOp.execute(poly1, null, false, null); + res = simplifyOp.isSimpleAsFeature(poly2, null, true, null, null); + assertTrue(res); + assertTrue(!poly2.isEmpty()); + }// done + + @Test + public void test0PolygonSelfIntersect2() {// non-simple (self-intersection) + Polygon poly1 = new Polygon(); + poly1.startPath(0, 0); + poly1.lineTo(0, 100); + poly1.lineTo(100, 100); + poly1.lineTo(-100, 0); + // poly1.lineTo(100, 0); + + boolean res = simplifyOp.isSimpleAsFeature(poly1, null, true, null, + null); + assertTrue(!res); + Polygon poly2 = (Polygon) simplifyOp.execute(poly1, null, false, null); + res = simplifyOp.isSimpleAsFeature(poly2, null, true, null, null); + assertTrue(res); + assertTrue(!poly2.isEmpty()); + }// done + + @Test + public void test0PolygonSelfIntersect3() { + Polygon poly = new Polygon(); + poly.startPath(0, 0); + poly.lineTo(0, 15); + poly.lineTo(15, 15); + poly.lineTo(15, 0); + + // This part intersects with the first part + poly.startPath(10, 10); + poly.lineTo(10, 20); + poly.lineTo(20, 20); + poly.lineTo(20, 10); + + boolean res = simplifyOp + .isSimpleAsFeature(poly, null, true, null, null); + assertTrue(!res); + Polygon poly2 = (Polygon) simplifyOp.execute(poly, null, false, null); + res = simplifyOp.isSimpleAsFeature(poly2, null, true, null, null); + assertTrue(res); + assertTrue(!poly2.isEmpty()); + }// done + + @Test + public void test0PolygonInteriorRing1() { + Polygon poly = new Polygon(); + poly.startPath(0, 0); + poly.lineTo(0, 15); + poly.lineTo(15, 15); + poly.lineTo(15, 0); + + // This is an interior ring but it is clockwise + poly.startPath(5, 5); + poly.lineTo(5, 6); + poly.lineTo(6, 6); + poly.lineTo(6, 5); + + boolean res = simplifyOp + .isSimpleAsFeature(poly, null, true, null, null); + assertTrue(!res); + Polygon poly2 = (Polygon) simplifyOp.execute(poly, null, false, null); + res = simplifyOp.isSimpleAsFeature(poly2, null, true, null, null); + assertTrue(res); + assertTrue(!poly2.isEmpty()); + }// done + + @Test + public void test0PolygonInteriorRing2() { + Polygon poly = new Polygon(); + poly.startPath(0, 0); + poly.lineTo(0, 15); + poly.lineTo(15, 15); + poly.lineTo(15, 0); + + // This is an interior ring but it is clockwise + poly.startPath(5, 5); + poly.lineTo(5, 6); + poly.lineTo(6, 6); + poly.lineTo(6, 5); + + // This part intersects with the first part + poly.startPath(10, 10); + poly.lineTo(10, 20); + poly.lineTo(20, 20); + poly.lineTo(20, 10); + + boolean res = simplifyOp + .isSimpleAsFeature(poly, null, true, null, null); + assertTrue(!res); + Polygon poly2 = (Polygon) simplifyOp.execute(poly, null, false, null); + res = simplifyOp.isSimpleAsFeature(poly2, null, true, null, null); + assertTrue(res); + assertTrue(!poly2.isEmpty()); + }// done + + @Test + public void test0PolygonInteriorRingWithCommonBoundary1() { + // Two rings have common boundary + Polygon poly = new Polygon(); + poly.startPath(0, 0); + poly.lineTo(0, 10); + poly.lineTo(10, 10); + poly.lineTo(10, 0); + + poly.startPath(10, 0); + poly.lineTo(10, 10); + poly.lineTo(20, 10); + poly.lineTo(20, 0); + + boolean res = simplifyOp + .isSimpleAsFeature(poly, null, true, null, null); + assertTrue(!res); + Polygon poly2 = (Polygon) simplifyOp.execute(poly, null, false, null); + res = simplifyOp.isSimpleAsFeature(poly2, null, true, null, null); + assertTrue(res); + assertTrue(!poly2.isEmpty()); + }// done + + @Test + public void test0PolygonInteriorRingWithCommonBoundary2() { + // Two rings have common boundary + Polygon poly = new Polygon(); + poly.startPath(0, 0); + poly.lineTo(0, 10); + poly.lineTo(10, 10); + poly.lineTo(10, 0); + + poly.startPath(10, 5); + poly.lineTo(10, 6); + poly.lineTo(20, 6); + poly.lineTo(20, 5); + + boolean res = simplifyOp + .isSimpleAsFeature(poly, null, true, null, null); + assertTrue(!res); + Polygon poly2 = (Polygon) simplifyOp.execute(poly, null, false, null); + res = simplifyOp.isSimpleAsFeature(poly2, null, true, null, null); + assertTrue(res); + assertTrue(!poly2.isEmpty()); + }// done + + @Test + public void testPolygon() { + Polygon nonSimplePolygon = makeNonSimplePolygon(); + Polygon simplePolygon = (Polygon) simplifyOp.execute(nonSimplePolygon, + sr3857, false, null); + + boolean res = simplifyOp.isSimpleAsFeature(simplePolygon, sr3857, true, + null, null); + assertTrue(res); + + @SuppressWarnings("unused") + int partCount = simplePolygon.getPathCount(); + // assertTrue(partCount == 2); + + double area = simplePolygon.calculateRingArea2D(0); + assertTrue(Math.abs(area - 300) <= 0.0001); + + area = simplePolygon.calculateRingArea2D(1); + assertTrue(Math.abs(area - (-25.0)) <= 0.0001); + }// done + + @Test + public void testPolygon2() { + Polygon nonSimplePolygon2 = makeNonSimplePolygon2(); + double area = nonSimplePolygon2.calculateRingArea2D(1); + assertTrue(Math.abs(area - 1.0) <= 0.0001); + + Polygon simplePolygon2 = (Polygon) simplifyOp.execute( + nonSimplePolygon2, sr3857, false, null); + + boolean res = simplifyOp.isSimpleAsFeature(simplePolygon2, sr3857, + true, null, null); + assertTrue(res); + + area = simplePolygon2.calculateRingArea2D(0); + assertTrue(Math.abs(area - 225) <= 0.0001); + + area = simplePolygon2.calculateRingArea2D(1); + assertTrue(Math.abs(area - (-1.0)) <= 0.0001); + }// done + + @Test + public void testPolygon3() { + Polygon nonSimplePolygon3 = makeNonSimplePolygon3(); + Polygon simplePolygon3 = (Polygon) simplifyOp.execute( + nonSimplePolygon3, sr3857, false, null); + + boolean res = simplifyOp.isSimpleAsFeature(simplePolygon3, sr3857, + true, null, null); + assertTrue(res); + + double area = simplePolygon3.calculateRingArea2D(0); + assertTrue(Math.abs(area - 875) <= 0.0001); + + area = simplePolygon3.calculateRingArea2D(1); + assertTrue(Math.abs(area - (-225)) <= 0.0001 + || Math.abs(area - (-50.0)) <= 0.0001); + + area = simplePolygon3.calculateRingArea2D(2); + assertTrue(Math.abs(area - (-225)) <= 0.0001 + || Math.abs(area - (-50.0)) <= 0.0001); + + area = simplePolygon3.calculateRingArea2D(3); + assertTrue(Math.abs(area - 25) <= 0.0001); + + area = simplePolygon3.calculateRingArea2D(4); + assertTrue(Math.abs(area - 25) <= 0.0001); + }// done + + @Test + public void testPolyline() { + Polyline nonSimplePolyline = makeNonSimplePolyline(); + Polyline simplePolyline = (Polyline) simplifyOp.execute( + nonSimplePolyline, sr3857, false, null); + + int segmentCount = simplePolyline.getSegmentCount(); + assertTrue(segmentCount == 4); + }// done + + @Test + public void testPolygon4() { + Polygon nonSimplePolygon4 = makeNonSimplePolygon4(); + Polygon simplePolygon4 = (Polygon) simplifyOp.execute( + nonSimplePolygon4, sr3857, false, null); + boolean res = simplifyOp.isSimpleAsFeature(simplePolygon4, sr3857, + true, null, null); + assertTrue(res); + + assertTrue(simplePolygon4.getPointCount() == 5); + Point point = nonSimplePolygon4.getPoint(0); + assertTrue(point.getX() == 0.0 && point.getY() == 0.0); + point = nonSimplePolygon4.getPoint(1); + assertTrue(point.getX() == 0.0 && point.getY() == 10.0); + point = nonSimplePolygon4.getPoint(2); + assertTrue(point.getX() == 10.0 && point.getY() == 10.0); + point = nonSimplePolygon4.getPoint(3); + assertTrue(point.getX() == 10.0 && point.getY() == 0.0); + point = nonSimplePolygon4.getPoint(4); + assertTrue(point.getX() == 5.0 && point.getY() == 0.0); + }// done + + @Test + public void testPolygon5() { + Polygon nonSimplePolygon5 = makeNonSimplePolygon5(); + Polygon simplePolygon5 = (Polygon) simplifyOp.execute( + nonSimplePolygon5, sr3857, false, null); + assertTrue(simplePolygon5 != null); + + boolean res = simplifyOp.isSimpleAsFeature(simplePolygon5, sr3857, + true, null, null); + assertTrue(res); + + int pointCount = simplePolygon5.getPointCount(); + assertTrue(pointCount == 6); + + double area = simplePolygon5.calculateArea2D(); + assertTrue(Math.abs(area - 50.0) <= 0.001); + + }// done + + @Test + public void testPolygon6() { + Polygon nonSimplePolygon6 = makeNonSimplePolygon6(); + Polygon simplePolygon6 = (Polygon) simplifyOp.execute( + nonSimplePolygon6, sr3857, false, null); + + boolean res = simplifyOp.isSimpleAsFeature(simplePolygon6, sr3857, + true, null, null); + assertTrue(res); + } + + @Test + public void testPolygon7() { + Polygon nonSimplePolygon7 = makeNonSimplePolygon7(); + Polygon simplePolygon7 = (Polygon) simplifyOp.execute( + nonSimplePolygon7, sr3857, false, null); + + boolean res = simplifyOp.isSimpleAsFeature(simplePolygon7, sr3857, + true, null, null); + assertTrue(res); + } + + public Polygon makeNonSimplePolygon() { + Polygon poly = new Polygon(); + poly.startPath(0, 0); + poly.lineTo(0, 15); + poly.lineTo(15, 15); + poly.lineTo(15, 0); + + // This is an interior ring but it is clockwise + poly.startPath(5, 5); + poly.lineTo(5, 6); + poly.lineTo(6, 6); + poly.lineTo(6, 5); + + // This part intersects with the first part + poly.startPath(10, 10); + poly.lineTo(10, 20); + poly.lineTo(20, 20); + poly.lineTo(20, 10); + + return poly; + }// done + + /* + * ------------>---------------->--------------- | | | (1) | | | | --->--- + * ------->------- | | | | | (5) | | | | | | --<-- | | | | (2) | | | | | | | + * | | | | (4) | | | | | | | -->-- | | --<-- | ---<--- | | | | | | + * -------<------- | | (3) | -------------<---------------<--------------- + * -->-- + */ + + public Polygon makeNonSimplePolygon3() { + Polygon poly = new Polygon(); + poly.startPath(0, 0); + poly.lineTo(0, 25); + poly.lineTo(35, 25); + poly.lineTo(35, 0); + + poly.startPath(5, 5); + poly.lineTo(5, 15); + poly.lineTo(10, 15); + poly.lineTo(10, 5); + + poly.startPath(40, 0); + poly.lineTo(45, 0); + poly.lineTo(45, 5); + poly.lineTo(40, 5); + + poly.startPath(20, 10); + poly.lineTo(25, 10); + poly.lineTo(25, 15); + poly.lineTo(20, 15); + + poly.startPath(15, 5); + poly.lineTo(15, 20); + poly.lineTo(30, 20); + poly.lineTo(30, 5); + + return poly; + }// done + + public Polygon makeNonSimplePolygon4() { + Polygon poly = new Polygon(); + poly.startPath(0, 0); + poly.lineTo(0, 10); + poly.lineTo(10, 10); + poly.lineTo(10, 0); + poly.lineTo(5, 0); + poly.lineTo(5, 5); + poly.lineTo(5, 0); + + return poly; + }// done + + public Polygon makeNonSimplePolygon6() { + Polygon poly = new Polygon(); + poly.startPath(35.34407570857744, 54.00551247713412); + poly.lineTo(41.07663499357954, 20.0); + poly.lineTo(40.66372033705177, 26.217432321849017); + + poly.startPath(42.81936574509338, 20.0); + poly.lineTo(43.58226670584747, 20.0); + poly.lineTo(39.29611825817084, 22.64634933678729); + poly.lineTo(44.369873312241346, 25.81893670527215); + poly.lineTo(42.68845660737179, 20.0); + poly.lineTo(38.569549792944244, 56.47456192829393); + poly.lineTo(42.79274114188401, 45.45117792578003); + poly.lineTo(41.09512147544657, 70.0); + + return poly; + } + + public Polygon makeNonSimplePolygon7() { + Polygon poly = new Polygon(); + + poly.startPath(41.987895433319686, 53.75822619011542); + poly.lineTo(41.98789542535497, 53.75822618803151); + poly.lineTo(40.15120412113667, 68.12604154722113); + poly.lineTo(37.72272697311022, 67.92767094118877); + poly.lineTo(37.147347454283086, 49.497473094145505); + poly.lineTo(38.636627026664385, 51.036687142232736); + + poly.startPath(39.00920080789793, 62.063425518369016); + poly.lineTo(38.604912643136885, 70.0); + poly.lineTo(40.71826863485308, 43.60337143116787); + poly.lineTo(35.34407570857744, 54.005512477134126); + poly.lineTo(39.29611825817084, 22.64634933678729); + + return poly; + } + + public Polyline makeNonSimplePolyline() { + // This polyline has a short segment + Polyline poly = new Polyline(); + poly.startPath(0, 0); + poly.lineTo(10, 0); + poly.lineTo(10, 10); + poly.lineTo(10, 5); + poly.lineTo(-5, 5); + + return poly; + }// done + + @Test + public void testIsSimpleBasicsPoint() { + boolean result; + // point is always simple + Point pt = new Point(); + result = simplifyOp.isSimpleAsFeature(pt, sr4326, false, null, null); + assertTrue(result); + pt.setXY(0, 0); + result = simplifyOp.isSimpleAsFeature(pt, sr4326, false, null, null); + assertTrue(result); + pt.setXY(100000, 10000); + result = simplifyOp.isSimpleAsFeature(pt, sr4326, false, null, null); + assertTrue(result); + }// done + + @Test + public void testIsSimpleBasicsEnvelope() { + // Envelope is simple, when it's width and height are not degenerate + Envelope env = new Envelope(); + boolean result = simplifyOp.isSimpleAsFeature(env, sr4326, false, null, + null); // Empty is simple + assertTrue(result); + env.setCoords(0, 0, 10, 10); + result = simplifyOp.isSimpleAsFeature(env, sr4326, false, null, null); + assertTrue(result); + // sliver but still simple + env.setCoords(0, 0, 0 + sr4326.getTolerance() * 2, 10); + result = simplifyOp.isSimpleAsFeature(env, sr4326, false, null, null); + assertTrue(result); + // sliver and not simple + env.setCoords(0, 0, 0 + sr4326.getTolerance() * 0.5, 10); + result = simplifyOp.isSimpleAsFeature(env, sr4326, false, null, null); + assertTrue(!result); + }// done + + @Test + public void testIsSimpleBasicsLine() { + Line line = new Line(); + boolean result = simplifyOp.isSimpleAsFeature(line, sr4326, false, + null, null); + assertTrue(!result); + + line.setStart(new Point(0, 0)); + // line.setEndXY(0, 0); + result = simplifyOp.isSimpleAsFeature(line, sr4326, false, null, null); + assertTrue(!result); + line.setEnd(new Point(1, 0)); + result = simplifyOp.isSimpleAsFeature(line, sr4326, false, null, null); + assertTrue(result); + }// done + + @Test + public void testIsSimpleMultiPoint1() { + MultiPoint mp = new MultiPoint(); + boolean result = simplifyOp.isSimpleAsFeature(mp, sr4326, false, null, + null); + assertTrue(result);// empty is simple + result = simplifyOp.isSimpleAsFeature( + simplifyOp.execute(mp, sr4326, false, null), sr4326, false, + null, null); + assertTrue(result); + }// done + + @Test + public void testIsSimpleMultiPoint2FarApart() { + // Two point test: far apart + MultiPoint mp = new MultiPoint(); + mp.add(20, 10); + mp.add(100, 100); + boolean result = simplifyOp.isSimpleAsFeature(mp, sr4326, false, null, + null); + assertTrue(result); + result = simplifyOp.isSimpleAsFeature( + simplifyOp.execute(mp, sr4326, false, null), sr4326, false, + null, null); + assertTrue(result); + assertTrue(mp.getPointCount() == 2); + }// done + + @Test + public void testIsSimpleMultiPointCoincident() { + // Two point test: coincident + MultiPoint mp = new MultiPoint(); + mp.add(100, 100); + mp.add(100, 100); + boolean result = simplifyOp.isSimpleAsFeature(mp, sr4326, false, null, + null); + assertTrue(!result); + MultiPoint mpS; + result = simplifyOp.isSimpleAsFeature( + mpS = (MultiPoint) simplifyOp.execute(mp, sr4326, false, null), + sr4326, false, null, null); + assertTrue(result); + assertTrue(mpS.getPointCount() == 1); + }// done + + @Test + public void testMultiPointSR4326_CR184439() { + OperatorFactoryLocal engine = OperatorFactoryLocal.getInstance(); + OperatorSimplify simpOp = (OperatorSimplify) engine + .getOperator(Operator.Type.Simplify); + NonSimpleResult nonSimpResult = new NonSimpleResult(); + nonSimpResult.m_reason = NonSimpleResult.Reason.NotDetermined; + MultiPoint multiPoint = new MultiPoint(); + multiPoint.add(0, 0); + multiPoint.add(0, 1); + multiPoint.add(0, 0); + Boolean multiPointIsSimple = simpOp.isSimpleAsFeature(multiPoint, + SpatialReference.create(4326), true, nonSimpResult, null); + assertFalse(multiPointIsSimple); + assertTrue(nonSimpResult.m_reason == NonSimpleResult.Reason.Clustering); + assertTrue(nonSimpResult.m_vertexIndex1 == 0); + assertTrue(nonSimpResult.m_vertexIndex2 == 2); + } + + @Test + public void testIsSimpleMultiPointCloserThanTolerance() { + // Two point test: closer than tolerance + MultiPoint mp = new MultiPoint(); + MultiPoint mpS; + mp.add(100, 100); + mp.add(100, 100 + sr4326.getTolerance() * .5); + boolean result = simplifyOp.isSimpleAsFeature(mp, sr4326, false, null, + null); + assertTrue(result); + result = simplifyOp.isSimpleAsFeature( + mpS = (MultiPoint) simplifyOp.execute(mp, sr4326, false, null), + sr4326, false, null, null); + assertTrue(result); + assertTrue(mpS.getPointCount() == 2); + }// done + + @Test + public void testIsSimpleMultiPointFarApart2() { + // 5 point test: far apart + MultiPoint mp = new MultiPoint(); + mp.add(100, 100); + mp.add(100, 101); + mp.add(101, 101); + mp.add(11, 1); + mp.add(11, 14); + MultiPoint mpS; + boolean result = simplifyOp.isSimpleAsFeature(mp, sr4326, false, null, + null); + assertTrue(result); + result = simplifyOp.isSimpleAsFeature( + mpS = (MultiPoint) simplifyOp.execute(mp, sr4326, false, null), + sr4326, false, null, null); + assertTrue(result); + assertTrue(mpS.getPointCount() == 5); + }// done + + @Test + public void testIsSimpleMultiPoint_coincident2() { + // 5 point test: coincident + MultiPoint mp = new MultiPoint(); + mp.add(100, 100); + mp.add(100, 101); + mp.add(100, 100); + mp.add(11, 1); + mp.add(11, 14); + boolean result = simplifyOp.isSimpleAsFeature(mp, sr4326, false, null, + null); + assertTrue(!result); + MultiPoint mpS; + result = simplifyOp.isSimpleAsFeature( + mpS = (MultiPoint) simplifyOp.execute(mp, sr4326, false, null), + sr4326, false, null, null); + assertTrue(result); + assertTrue(mpS.getPointCount() == 4); + assertEquals(mpS.getPoint(0).getX(), 100, 1e-7); + assertEquals(mpS.getPoint(0).getY(), 100, 1e-7); + assertEquals(mpS.getPoint(1).getX(), 100, 1e-7); + assertEquals(mpS.getPoint(1).getY(), 101, 1e-7); + assertEquals(mpS.getPoint(2).getX(), 11, 1e-7); + assertEquals(mpS.getPoint(2).getY(), 1, 1e-7); + assertEquals(mpS.getPoint(3).getX(), 11, 1e-7); + assertEquals(mpS.getPoint(3).getY(), 14, 1e-7); + }// done + + @Test + public void testIsSimpleMultiPointCloserThanTolerance2() { + // 5 point test: closer than tolerance + MultiPoint mp = new MultiPoint(); + mp.add(100, 100); + mp.add(100, 101); + mp.add(100, 100 + sr4326.getTolerance() / 2); + mp.add(11, 1); + mp.add(11, 14); + MultiPoint mpS; + boolean result = simplifyOp.isSimpleAsFeature(mp, sr4326, false, null, + null); + assertTrue(result); + result = simplifyOp.isSimpleAsFeature( + mpS = (MultiPoint) simplifyOp.execute(mp, sr4326, false, null), + sr4326, false, null, null); + assertTrue(result); + assertTrue(mpS.getPointCount() == 5); + }// done + + @Test + public void testIsSimplePolyline() { + Polyline poly = new Polyline(); + boolean result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, + null, null); + assertTrue(result);// empty is simple + } + + @Test + public void testIsSimplePolylineFarApart() { + // Two point test: far apart + Polyline poly = new Polyline(); + poly.startPath(20, 10); + poly.lineTo(100, 100); + boolean result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, + null, null); + assertTrue(result); + } + + @Test + public void testIsSimplePolylineCoincident() { + // Two point test: coincident + Polyline poly = new Polyline(); + poly.startPath(100, 100); + poly.lineTo(100, 100); + boolean result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, + null, null); + assertTrue(!result); + @SuppressWarnings("unused") + Polyline polyS; + result = simplifyOp.isSimpleAsFeature( + polyS = (Polyline) simplifyOp + .execute(poly, sr4326, false, null), sr4326, false, + null, null); + assertTrue(result); + } + + @Test + public void testIsSimplePolylineCloserThanTolerance() { + // Two point test: closer than tolerance + Polyline poly = new Polyline(); + poly.startPath(100, 100); + poly.lineTo(100, 100 + sr4326.getTolerance() / 2); + boolean result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, + null, null); + assertTrue(!result); + @SuppressWarnings("unused") + Polyline polyS; + result = simplifyOp.isSimpleAsFeature( + polyS = (Polyline) simplifyOp + .execute(poly, sr4326, false, null), sr4326, false, + null, null); + assertTrue(result); + } + + @Test + public void testIsSimplePolylineFarApartSelfOverlap0() { + // 3 point test: far apart, self overlapping + Polyline poly = new Polyline(); + poly.startPath(0, 0); + poly.lineTo(100, 100); + poly.lineTo(0, 0); + boolean result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, + null, null); + assertTrue(result);// TO CONFIRM should be false + } + + @Test + public void testIsSimplePolylineSelfIntersect() { + // 4 point test: far apart, self intersecting + Polyline poly = new Polyline(); + poly.startPath(0, 0); + poly.lineTo(100, 100); + poly.lineTo(0, 100); + poly.lineTo(100, 0); + boolean result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, + null, null); + assertTrue(result);// TO CONFIRM should be false + } + + @Test + public void testIsSimplePolylineDegenerateSegment() { + // 4 point test: degenerate segment + Polyline poly = new Polyline(); + poly.startPath(0, 0); + poly.lineTo(100, 100); + poly.lineTo(100, 100 + sr4326.getTolerance() / 2); + poly.lineTo(100, 0); + boolean result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, + null, null); + assertTrue(!result); + @SuppressWarnings("unused") + Polyline polyS; + result = simplifyOp.isSimpleAsFeature( + polyS = (Polyline) simplifyOp + .execute(poly, sr4326, false, null), sr4326, false, + null, null); + assertTrue(result); + { + Polyline other = new Polyline(); + other.startPath(0, 0); + other.lineTo(100, 100); + other.lineTo(100, 0); + other.equals(poly); + } + } + + @Test + public void testIsSimplePolylineFarApartSelfOverlap() { + // 3 point test: far apart, self overlapping + Polyline poly = new Polyline(); + poly.startPath(0, 0); + poly.lineTo(100, 100); + poly.lineTo(0, 0); + boolean result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, + null, null); + assertTrue(result);// TO CONFIRM should be false + } + + @Test + public void testIsSimplePolylineFarApartIntersect() { + // 4 point 2 parts test: far apart, intersecting parts + Polyline poly = new Polyline(); + poly.startPath(0, 0); + poly.lineTo(100, 100); + poly.startPath(100, 0); + poly.lineTo(0, 100); + + boolean result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, + null, null); + assertTrue(result);// TO CONFIRM should be false + } + + @Test + public void testIsSimplePolylineFarApartOverlap2() { + // 4 point 2 parts test: far apart, overlapping parts. second part + // starts where first one ends + Polyline poly = new Polyline(); + poly.startPath(0, 0); + poly.lineTo(100, 100); + poly.startPath(100, 100); + poly.lineTo(0, 100); + + boolean result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, + null, null); + assertTrue(result);// TO CONFIRM should be false + } + + @Test + public void testIsSimplePolylineDegenerateVertical() { + // 3 point test: degenerate vertical line + Polyline poly = new Polyline(); + poly.startPath(0, 0); + poly.lineTo(new Point(100, 100)); + poly.lineTo(new Point(100, 100)); + boolean result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, + null, null); + assertTrue(!result); + Polyline polyS; + result = simplifyOp.isSimpleAsFeature( + polyS = (Polyline) simplifyOp + .execute(poly, sr4326, false, null), sr4326, false, + null, null); + assertTrue(result); + assertTrue(polyS.getPointCount() == 2); + } + + @Test + public void testIsSimplePolylineEmptyPath() { + // TODO: any way to test this? + // Empty path + // Polyline poly = new Polyline(); + // assertTrue(poly.isEmpty()); + // poly.addPath(new Polyline(), 0, true); + // assertTrue(poly.isEmpty()); + // boolean result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, + // null, null); + // assertTrue(result); + } + + @Test + public void testIsSimplePolylineSinglePointInPath() { + // Single point in path + Polyline poly = new Polyline(); + poly.startPath(0, 0); + poly.lineTo(100, 100); + poly.removePoint(0, 1); + boolean result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, + null, null); + assertTrue(!result); + Polyline polyS; + result = simplifyOp.isSimpleAsFeature( + polyS = (Polyline) simplifyOp + .execute(poly, sr4326, false, null), sr4326, false, + null, null); + assertTrue(result); + assertTrue(polyS.isEmpty()); + } + + @Test + public void testIsSimplePolygon() { + Polygon poly = new Polygon(); + boolean result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, + null, null); + assertTrue(result);// empty is simple + result = simplifyOp.isSimpleAsFeature( + simplifyOp.execute(poly, sr4326, false, null), sr4326, false, + null, null); + assertTrue(result);// empty is simple + } + + @Test + public void testIsSimplePolygonEmptyPath() { + // TODO: + // Empty path + // Polygon poly = new Polygon(); + // poly.addPath(new Polyline(), 0, true); + // assertTrue(poly.getPathCount() == 1); + // boolean result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, + // null, + // null); + // assertTrue(result); + // result = simplifyOp.isSimpleAsFeature(simplifyOp.execute(poly, + // sr4326, false, null), sr4326, false, null, null); + // assertTrue(result);// empty is simple + // assertTrue(poly.getPathCount() == 1); + } + + @Test + public void testIsSimplePolygonIncomplete1() { + // Incomplete polygon 1 + Polygon poly = new Polygon(); + poly.startPath(0, 0); + poly.lineTo(100, 100); + // poly.removePoint(0, 1);//TO CONFIRM no removePoint method in Java + boolean result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, + null, null); + assertTrue(!result); + } + + @Test + public void testIsSimplePolygonIncomplete2() { + // Incomplete polygon 2 + Polygon poly = new Polygon(); + poly.startPath(0, 0); + poly.lineTo(100, 100); + boolean result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, + null, null); + assertTrue(!result); + } + + @Test + public void testIsSimplePolygonDegenerateTriangle() { + // Degenerate triangle (self overlap) + Polygon poly = new Polygon(); + poly.startPath(0, 0); + poly.lineTo(100, 100); + poly.lineTo(0, 0); + boolean result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, + null, null); + assertTrue(!result); + } + + @Test + public void testIsSimplePolygonSelfIntersect() { + // Self intersection - cracking is needed + Polygon poly = new Polygon(); + poly.startPath(0, 0); + poly.lineTo(100, 100); + poly.lineTo(0, 100); + poly.lineTo(100, 0); + boolean result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, + null, null); + assertTrue(!result); + } + + @Test + public void testIsSimplePolygonRectangleHole() { + // Rectangle and rectangular hole that has one segment overlapping + // with the with the exterior ring. Cracking is needed. + Polygon poly = new Polygon(); + poly.addEnvelope(new Envelope(-200, -100, 200, 100), false); + poly.addEnvelope(new Envelope(-100, -100, 100, 50), true); + boolean result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, + null, null); + assertTrue(!result); + poly.reverseAllPaths(); + result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, null, null); + assertTrue(!result); + } + + @Test + public void testIsSimplePolygonRectangleHole2() { + // Rectangle and rectangular hole + Polygon poly = new Polygon(); + poly.addEnvelope(new Envelope(-200, -100, 200, 100), false); + poly.addEnvelope(new Envelope(-100, -50, 100, 50), true); + boolean result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, + null, null); + assertTrue(result); + poly.reverseAllPaths(); + result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, null, null); + assertTrue(!result); + } + + @Test + public void testIsSimplePolygonSelfIntersectAtVertex() { + // Self intersection at vertex + Polygon poly = new Polygon(); + poly.startPath(0, 0); + poly.lineTo(50, 50); + poly.lineTo(100, 100); + poly.lineTo(0, 100); + poly.lineTo(50, 50); + poly.lineTo(100, 0); + boolean result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, + null, null); + assertTrue(!result); + result = simplifyOp.isSimpleAsFeature( + simplifyOp.execute(poly, sr4326, false, null), sr4326, false, + null, null); + assertTrue(result); + } + + @Test + public void testIsSimplePolygon_2EdgesTouchAtVertex() { + // No self-intersection, but more than two edges touch at the same + // vertex. Simple for ArcGIS, not simple for OGC + Polygon poly = new Polygon(); + poly.startPath(0, 0); + poly.lineTo(50, 50); + poly.lineTo(0, 100); + poly.lineTo(100, 100); + poly.lineTo(50, 50); + poly.lineTo(100, 0); + boolean result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, + null, null); + assertTrue(result); + } + + @Test + public void testIsSimplePolygonTriangle() { + // Triangle + Polygon poly = new Polygon(); + poly.startPath(0, 0); + poly.lineTo(100, 100); + poly.lineTo(100, 0); + boolean result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, + null, null); + assertTrue(result); + poly.reverseAllPaths(); + result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, null, null); + assertTrue(!result); + } + + @Test + public void testIsSimplePolygonRectangle() { + // Rectangle + Polygon poly = new Polygon(); + poly.addEnvelope(new Envelope(-200, -100, 100, 200), false); + boolean result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, + null, null); + assertTrue(result); + poly.reverseAllPaths(); + result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, null, null); + assertTrue(!result); + } + + @Test + public void testIsSimplePolygonRectangleHoleWrongDirection() { + // Rectangle and rectangular hole that has wrong direction + Polygon poly = new Polygon(); + poly.addEnvelope(new Envelope(-200, -100, 200, 100), false); + poly.addEnvelope(new Envelope(-100, -50, 100, 50), false); + boolean result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, + null, null); + assertTrue(!result); + poly.reverseAllPaths(); + result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, null, null); + assertTrue(!result); + } + + @Test + public void testIsSimplePolygon_2RectanglesSideBySide() { + // Two rectangles side by side, simple + Polygon poly = new Polygon(); + poly.addEnvelope(new Envelope(-200, -100, 200, 100), false); + poly.addEnvelope(new Envelope(220, -50, 300, 50), false); + boolean result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, + null, null); + assertTrue(result); + poly.reverseAllPaths(); + result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, null, null); + assertTrue(!result); + } + + @Test + public void testIsSimplePolygonRectangleOneBelow() { + // Two rectangles one below another, simple + Polygon poly = new Polygon(); + poly.addEnvelope(new Envelope(50, 50, 100, 100), false); + poly.addEnvelope(new Envelope(50, 200, 100, 250), false); + boolean result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, + null, null); + assertTrue(result); + poly.reverseAllPaths(); + result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, null, null); + assertTrue(!result); + } + + @Test + public void testisSimpleOGC() { + Polyline poly = new Polyline(); + poly.startPath(0, 0); + poly.lineTo(10, 0); + boolean result = simplifyOpOGC.isSimpleOGC(poly, sr4326, true, null, + null); + assertTrue(result); + + poly = new Polyline(); + poly.startPath(0, 0); + poly.lineTo(10, 10); + poly.lineTo(0, 10); + poly.lineTo(10, 0); + NonSimpleResult nsr = new NonSimpleResult(); + result = simplifyOpOGC.isSimpleOGC(poly, sr4326, true, nsr, null); + assertTrue(!result); + assertTrue(nsr.m_reason == NonSimpleResult.Reason.Cracking); + + MultiPoint mp = new MultiPoint(); + mp.add(0, 0); + mp.add(10, 0); + result = simplifyOpOGC.isSimpleOGC(mp, sr4326, true, null, null); + assertTrue(result); + + mp = new MultiPoint(); + mp.add(10, 0); + mp.add(10, 0); + nsr = new NonSimpleResult(); + result = simplifyOpOGC.isSimpleOGC(mp, sr4326, true, nsr, null); + assertTrue(!result); + assertTrue(nsr.m_reason == NonSimpleResult.Reason.Clustering); + } + + @Test + public void testPolylineIsSimpleForOGC() { + OperatorImportFromJson importerJson = (OperatorImportFromJson) factory + .getOperator(Operator.Type.ImportFromJson); + OperatorSimplify simplify = (OperatorSimplify) factory + .getOperator(Operator.Type.Simplify); + + { + String s = "{\"paths\":[[[0, 10], [8, 5], [5, 2], [6, 0]]]}"; + Geometry g = importerJson.execute(Geometry.Type.Unknown, + JsonParserReader.createFromString(s)).getGeometry(); + boolean res = simplifyOpOGC.isSimpleOGC(g, null, true, null, null); + assertTrue(res); + } + { + String s = "{\"paths\":[[[0, 10], [6, 0], [7, 5], [0, 3]]]}";// self + // intersection + Geometry g = importerJson.execute(Geometry.Type.Unknown, + JsonParserReader.createFromString(s)).getGeometry(); + boolean res = simplifyOpOGC.isSimpleOGC(g, null, true, null, null); + assertTrue(!res); + } + + { + String s = "{\"paths\":[[[0, 10], [6, 0], [0, 3], [0, 10]]]}"; // closed + Geometry g = importerJson.execute(Geometry.Type.Unknown, + JsonParserReader.createFromString(s)).getGeometry(); + boolean res = simplifyOpOGC.isSimpleOGC(g, null, true, null, null); + assertTrue(res); + } + + { + String s = "{\"paths\":[[[0, 10], [5, 5], [6, 0], [0, 3], [5, 5], [0, 9], [0, 10]]]}"; // closed + // with + // self + // tangent + Geometry g = importerJson.execute(Geometry.Type.Unknown, + JsonParserReader.createFromString(s)).getGeometry(); + boolean res = simplifyOpOGC.isSimpleOGC(g, null, true, null, null); + assertTrue(!res); + } + + { + String s = "{\"paths\":[[[0, 10], [5, 2]], [[5, 2], [6, 0]]]}";// two + // paths + // connected + // at + // a + // point + Geometry g = importerJson.execute(Geometry.Type.Unknown, + JsonParserReader.createFromString(s)).getGeometry(); + boolean res = simplifyOpOGC.isSimpleOGC(g, null, true, null, null); + assertTrue(res); + } + + { + String s = "{\"paths\":[[[0, 0], [3, 3], [5, 0], [0, 0]], [[0, 10], [3, 3], [10, 10], [0, 10]]]}";// two + // closed + // rings + // touch + // at + // one + // point + Geometry g = importerJson.execute(Geometry.Type.Unknown, + JsonParserReader.createFromString(s)).getGeometry(); + boolean res = simplifyOpOGC.isSimpleOGC(g, null, true, null, null); + assertTrue(!res); + } + + { + String s = "{\"paths\":[[[0, 0], [10, 10]], [[0, 10], [10, 0]]]}";// two + // lines + // intersect + Geometry g = importerJson.execute(Geometry.Type.Unknown, + JsonParserReader.createFromString(s)).getGeometry(); + boolean res = simplifyOpOGC.isSimpleOGC(g, null, true, null, null); + assertTrue(!res); + } + + { + String s = "{\"paths\":[[[0, 0], [5, 5], [0, 10]], [[10, 10], [5, 5], [10, 0]]]}";// two + // paths + // share + // mid + // point. + Geometry g = importerJson.execute(Geometry.Type.Unknown, + JsonParserReader.createFromString(s)).getGeometry(); + boolean res = simplifyOpOGC.isSimpleOGC(g, null, true, null, null); + assertTrue(!res); + } + + } + + @Test + public void testFillRule() { + //self intersecting star shape + MapGeometry mg = OperatorImportFromJson.local().execute(Geometry.Type.Unknown, "{\"rings\":[[[0,0], [5,10], [10, 0], [0, 7], [10, 7], [0, 0]]]}"); + Polygon poly = (Polygon)mg.getGeometry(); + assertTrue(poly.getFillRule() == Polygon.FillRule.enumFillRuleOddEven); + poly.setFillRule(Polygon.FillRule.enumFillRuleWinding); + assertTrue(poly.getFillRule() == Polygon.FillRule.enumFillRuleWinding); + Geometry simpleResult = OperatorSimplify.local().execute(poly, null, true, null); + assertTrue(((Polygon)simpleResult).getFillRule() == Polygon.FillRule.enumFillRuleOddEven); + //solid start without holes: + MapGeometry mg1 = OperatorImportFromJson.local().execute(Geometry.Type.Unknown, "{\"rings\":[[[0,0],[2.5925925925925926,5.185185185185185],[0,7],[3.5,7],[5,10],[6.5,7],[10,7],[7.407407407407407,5.185185185185185],[10,0],[5,3.5],[0,0]]]}"); + boolean equals = OperatorEquals.local().execute(mg1.getGeometry(), simpleResult, null, null); + assertTrue(equals); + } + +} diff --git a/src/test/java/com/esri/core/geometry/TestWKBSupport.java b/src/test/java/com/esri/core/geometry/TestWKBSupport.java index 0c84d883..f0f4752a 100644 --- a/src/test/java/com/esri/core/geometry/TestWKBSupport.java +++ b/src/test/java/com/esri/core/geometry/TestWKBSupport.java @@ -1,85 +1,86 @@ -package com.esri.core.geometry; - -import java.io.IOException; -import java.nio.ByteBuffer; -import junit.framework.TestCase; -import org.junit.Test; - -//import com.vividsolutions.jts.io.WKBReader; - -public class TestWKBSupport extends TestCase { - @Override - protected void setUp() throws Exception { - super.setUp(); - } - - @Override - protected void tearDown() throws Exception { - super.tearDown(); - } - - @Test - public void testWKB() { - // JSON -> GEOM -> WKB - - String strPolygon1 = "{\"xmin\":-1.1663479012889031E7,\"ymin\":4919777.494405342,\"xmax\":-1.1658587043078788E7,\"ymax\":4924669.464215587,\"spatialReference\":{\"wkid\":102100}}"; - // String strPolygon1 = - // "{\"rings\":[[[-119.152450421001,38.4118009590513],[-119.318825070203,38.5271086243914],[-119.575687062955,38.7029101298904],[-119.889341639399,38.9222515603984],[-119.995254694357,38.9941061536377],[-119.995150114198,39.0634913594691],[-119.994541258334,39.1061318056708],[-119.995527335641,39.1587132866355],[-119.995304181493,39.3115454332125],[-119.996011479298,39.4435009764511],[-119.996165311172,39.7206108077274],[-119.996324660047,41.1775662656441],[-119.993459369715,41.9892049531992],[-119.351692186077,41.9888529749781],[-119.3109421304,41.9891353872811],[-118.185316829038,41.9966370981387],[-117.018864363596,41.9947941808341],[-116.992313337997,41.9947945094663],[-115.947544658193,41.9945994628997],[-115.024862911148,41.996506455953],[-114.269471632824,41.9959242345073],[-114.039072662345,41.9953908974688],[-114.038151248682,40.9976868405942],[-114.038108189376,40.1110466529553],[-114.039844684228,39.9087788600023],[-114.040105338584,39.5386849268845],[-114.044267501155,38.6789958815881],[-114.045090206153,38.5710950539539],[-114.047272999176,38.1376524399918],[-114.047260595159,37.5984784866001],[-114.043939384154,36.9965379371421],[-114.043716435713,36.8418489458647],[-114.037392074194,36.2160228969702],[-114.045105557286,36.1939778840226],[-114.107775185788,36.1210907070504],[-114.12902308363,36.041730493896],[-114.206768869568,36.0172554164834],[-114.233472615347,36.0183310595897],[-114.307587598189,36.0622330993643],[-114.303857056018,36.0871084040611],[-114.316095374696,36.1114380366653],[-114.344233941709,36.1374802520568],[-114.380803116644,36.1509912717765],[-114.443945697733,36.1210532841897],[-114.466613475422,36.1247112590539],[-114.530573568745,36.1550902046725],[-114.598935242024,36.1383354528834],[-114.621610747198,36.1419666834504],[-114.712761724737,36.1051810523675],[-114.728150311069,36.0859627711604],[-114.728966012834,36.0587530361083],[-114.717673567756,36.0367580437018],[-114.736212493583,35.9876483502758],[-114.699275906446,35.9116119537412],[-114.661600122152,35.8804735854242],[-114.662462095522,35.8709599070091],[-114.689867343369,35.8474424944766],[-114.682739704595,35.7647034175617],[-114.688820027649,35.7325957399896],[-114.665091345861,35.6930994107107],[-114.668486064922,35.6563989882404],[-114.654065925137,35.6465840800053],[-114.6398667219,35.6113485698329],[-114.653134321223,35.5848331056108],[-114.649792053474,35.5466373866597],[-114.672215155693,35.5157541647721],[-114.645396168451,35.4507608261463],[-114.589584275424,35.3583787306827],[-114.587889840369,35.30476812919],[-114.559583045727,35.2201828714608],[-114.561039964054,35.1743461616313],[-114.572255261053,35.1400677445931],[-114.582616239058,35.1325604694085],[-114.626440825485,35.1339067529872],[-114.6359090842,35.1186557767895],[-114.595631971944,35.0760579746697],[-114.633779872695,35.0418633504303],[-114.621068606189,34.9989144286133],[-115.626197382816,35.7956983148418],[-115.88576934392,36.0012259572723],[-117.160423771838,36.9595941441767],[-117.838686423167,37.457298239715],[-118.417419755966,37.8866767486211],[-119.152450421001,38.4118009590513]]], \"spatialReference\":{\"wkid\":4326}}"; - - MapGeometry mapGeom = GeometryEngine.jsonToGeometry(strPolygon1); - Geometry geom = mapGeom.getGeometry(); - OperatorExportToWkb operatorExport = (OperatorExportToWkb) OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.ExportToWkb); - ByteBuffer byteBuffer = operatorExport.execute(0, geom, null); - byte[] wkb = byteBuffer.array(); - - // WKB -> GEOM -> JSON - OperatorImportFromWkb operatorImport = (OperatorImportFromWkb) OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.ImportFromWkb); - geom = operatorImport.execute(0, Geometry.Type.Polygon, - ByteBuffer.wrap(wkb), null); - // geom = operatorImport.execute(0, Geometry.Type.Polygon, - // byteBuffer); - String outputPolygon1 = GeometryEngine.geometryToJson(-1, geom); - } - - @Test - public void testWKB2() throws Exception { - // JSON -> GEOM -> WKB - - // String strPolygon1 = - // "{\"xmin\":-1.16605115291E7,\"ymin\":4925189.941699997,\"xmax\":-1.16567772126E7,\"ymax\":4928658.771399997,\"spatialReference\":{\"wkid\":102100}}"; - String strPolygon1 = "{\"rings\" : [ [ [-1.16605115291E7,4925189.941699997], [-1.16567772126E7,4925189.941699997], [-1.16567772126E7,4928658.771399997], [-1.16605115291E7,4928658.771399997], [-1.16605115291E7,4925189.941699997] ] ], \"spatialReference\" : {\"wkid\" : 102100}}"; - - MapGeometry mapGeom = GeometryEngine.jsonToGeometry(strPolygon1); - Geometry geom = mapGeom.getGeometry(); - - // simplifying geom - OperatorSimplify operatorSimplify = (OperatorSimplify) OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.Simplify); - SpatialReference sr = SpatialReference.create(102100); - geom = operatorSimplify.execute(geom, sr, true, null); - - OperatorExportToWkb operatorExport = (OperatorExportToWkb) OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.ExportToWkb); - ByteBuffer byteBuffer = operatorExport.execute(0, geom, null); - byte[] wkb = byteBuffer.array(); - - // // checking WKB correctness - // WKBReader jtsReader = new WKBReader(); - // com.vividsolutions.jts.geom.Geometry jtsGeom = jtsReader.read(wkb); - // System.out.println("jtsGeom = " + jtsGeom); - - // WKB -> GEOM -> JSON - OperatorImportFromWkb operatorImport = (OperatorImportFromWkb) OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.ImportFromWkb); - geom = operatorImport.execute(0, Geometry.Type.Polygon, - ByteBuffer.wrap(wkb), null); - assertTrue(!geom.isEmpty()); - // geom = operatorImport.execute(0, Geometry.Type.Polygon, byteBuffer); - // String outputPolygon1 = GeometryEngine.geometryToJson(-1, geom); - // System.out.println(strPolygon1); - // System.out.println(outputPolygon1); - - } - } +package com.esri.core.geometry; + +import java.io.IOException; +import java.nio.ByteBuffer; +import junit.framework.TestCase; +import org.junit.Test; + +//import com.vividsolutions.jts.io.WKBReader; + +public class TestWKBSupport extends TestCase { + @Override + protected void setUp() throws Exception { + super.setUp(); + } + + @Override + protected void tearDown() throws Exception { + super.tearDown(); + } + + @Test + public void testWKB() { + // JSON -> GEOM -> WKB + + String strPolygon1 = "{\"xmin\":-1.1663479012889031E7,\"ymin\":4919777.494405342,\"xmax\":-1.1658587043078788E7,\"ymax\":4924669.464215587,\"spatialReference\":{\"wkid\":102100}}"; + // String strPolygon1 = + // "{\"rings\":[[[-119.152450421001,38.4118009590513],[-119.318825070203,38.5271086243914],[-119.575687062955,38.7029101298904],[-119.889341639399,38.9222515603984],[-119.995254694357,38.9941061536377],[-119.995150114198,39.0634913594691],[-119.994541258334,39.1061318056708],[-119.995527335641,39.1587132866355],[-119.995304181493,39.3115454332125],[-119.996011479298,39.4435009764511],[-119.996165311172,39.7206108077274],[-119.996324660047,41.1775662656441],[-119.993459369715,41.9892049531992],[-119.351692186077,41.9888529749781],[-119.3109421304,41.9891353872811],[-118.185316829038,41.9966370981387],[-117.018864363596,41.9947941808341],[-116.992313337997,41.9947945094663],[-115.947544658193,41.9945994628997],[-115.024862911148,41.996506455953],[-114.269471632824,41.9959242345073],[-114.039072662345,41.9953908974688],[-114.038151248682,40.9976868405942],[-114.038108189376,40.1110466529553],[-114.039844684228,39.9087788600023],[-114.040105338584,39.5386849268845],[-114.044267501155,38.6789958815881],[-114.045090206153,38.5710950539539],[-114.047272999176,38.1376524399918],[-114.047260595159,37.5984784866001],[-114.043939384154,36.9965379371421],[-114.043716435713,36.8418489458647],[-114.037392074194,36.2160228969702],[-114.045105557286,36.1939778840226],[-114.107775185788,36.1210907070504],[-114.12902308363,36.041730493896],[-114.206768869568,36.0172554164834],[-114.233472615347,36.0183310595897],[-114.307587598189,36.0622330993643],[-114.303857056018,36.0871084040611],[-114.316095374696,36.1114380366653],[-114.344233941709,36.1374802520568],[-114.380803116644,36.1509912717765],[-114.443945697733,36.1210532841897],[-114.466613475422,36.1247112590539],[-114.530573568745,36.1550902046725],[-114.598935242024,36.1383354528834],[-114.621610747198,36.1419666834504],[-114.712761724737,36.1051810523675],[-114.728150311069,36.0859627711604],[-114.728966012834,36.0587530361083],[-114.717673567756,36.0367580437018],[-114.736212493583,35.9876483502758],[-114.699275906446,35.9116119537412],[-114.661600122152,35.8804735854242],[-114.662462095522,35.8709599070091],[-114.689867343369,35.8474424944766],[-114.682739704595,35.7647034175617],[-114.688820027649,35.7325957399896],[-114.665091345861,35.6930994107107],[-114.668486064922,35.6563989882404],[-114.654065925137,35.6465840800053],[-114.6398667219,35.6113485698329],[-114.653134321223,35.5848331056108],[-114.649792053474,35.5466373866597],[-114.672215155693,35.5157541647721],[-114.645396168451,35.4507608261463],[-114.589584275424,35.3583787306827],[-114.587889840369,35.30476812919],[-114.559583045727,35.2201828714608],[-114.561039964054,35.1743461616313],[-114.572255261053,35.1400677445931],[-114.582616239058,35.1325604694085],[-114.626440825485,35.1339067529872],[-114.6359090842,35.1186557767895],[-114.595631971944,35.0760579746697],[-114.633779872695,35.0418633504303],[-114.621068606189,34.9989144286133],[-115.626197382816,35.7956983148418],[-115.88576934392,36.0012259572723],[-117.160423771838,36.9595941441767],[-117.838686423167,37.457298239715],[-118.417419755966,37.8866767486211],[-119.152450421001,38.4118009590513]]], \"spatialReference\":{\"wkid\":4326}}"; + + MapGeometry mapGeom = GeometryEngine.jsonToGeometry(strPolygon1); + Geometry geom = mapGeom.getGeometry(); + OperatorExportToWkb operatorExport = (OperatorExportToWkb) OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.ExportToWkb); + ByteBuffer byteBuffer = operatorExport.execute(0, geom, null); + byte[] wkb = byteBuffer.array(); + + // WKB -> GEOM -> JSON + OperatorImportFromWkb operatorImport = (OperatorImportFromWkb) OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.ImportFromWkb); + geom = operatorImport.execute(0, Geometry.Type.Polygon, + ByteBuffer.wrap(wkb), null); + // geom = operatorImport.execute(0, Geometry.Type.Polygon, + // byteBuffer); + String outputPolygon1 = GeometryEngine.geometryToJson(-1, geom); + } + + @Test + public void testWKB2() throws Exception { + // JSON -> GEOM -> WKB + + // String strPolygon1 = + // "{\"xmin\":-1.16605115291E7,\"ymin\":4925189.941699997,\"xmax\":-1.16567772126E7,\"ymax\":4928658.771399997,\"spatialReference\":{\"wkid\":102100}}"; + String strPolygon1 = "{\"rings\" : [ [ [-1.16605115291E7,4925189.941699997], [-1.16567772126E7,4925189.941699997], [-1.16567772126E7,4928658.771399997], [-1.16605115291E7,4928658.771399997], [-1.16605115291E7,4925189.941699997] ] ], \"spatialReference\" : {\"wkid\" : 102100}}"; + + MapGeometry mapGeom = GeometryEngine.jsonToGeometry(strPolygon1); + Geometry geom = mapGeom.getGeometry(); + + // simplifying geom + OperatorSimplify operatorSimplify = (OperatorSimplify) OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Simplify); + SpatialReference sr = SpatialReference.create(102100); + geom = operatorSimplify.execute(geom, sr, true, null); + + OperatorExportToWkb operatorExport = (OperatorExportToWkb) OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.ExportToWkb); + ByteBuffer byteBuffer = operatorExport.execute(0, geom, null); + byte[] wkb = byteBuffer.array(); + + // // checking WKB correctness + // WKBReader jtsReader = new WKBReader(); + // com.vividsolutions.jts.geom.Geometry jtsGeom = jtsReader.read(wkb); + // System.out.println("jtsGeom = " + jtsGeom); + + // WKB -> GEOM -> JSON + OperatorImportFromWkb operatorImport = (OperatorImportFromWkb) OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.ImportFromWkb); + geom = operatorImport.execute(0, Geometry.Type.Polygon, + ByteBuffer.wrap(wkb), null); + assertTrue(!geom.isEmpty()); + // geom = operatorImport.execute(0, Geometry.Type.Polygon, byteBuffer); + // String outputPolygon1 = GeometryEngine.geometryToJson(-1, geom); + // System.out.println(strPolygon1); + // System.out.println(outputPolygon1); + + } + +} From b0bc20e9f3a8e6ea880dbfefebeb54d2552b97fe Mon Sep 17 00:00:00 2001 From: Sergey Tolstov Date: Thu, 13 Jul 2017 15:01:14 -0700 Subject: [PATCH 139/196] added missing license header (#138) --- .gitignore | 1 + .../core/geometry/AttributeStreamOfDbl.java | 12 +- .../core/geometry/AttributeStreamOfFloat.java | 12 +- .../core/geometry/AttributeStreamOfInt16.java | 12 +- .../core/geometry/AttributeStreamOfInt32.java | 14 +- .../core/geometry/AttributeStreamOfInt64.java | 12 +- .../core/geometry/AttributeStreamOfInt8.java | 12 +- .../ogc/OGCConcreteGeometryCollection.java | 24 + .../com/esri/core/geometry/ogc/OGCCurve.java | 24 + .../esri/core/geometry/ogc/OGCGeometry.java | 24 + .../geometry/ogc/OGCGeometryCollection.java | 24 + .../esri/core/geometry/ogc/OGCLineString.java | 24 + .../esri/core/geometry/ogc/OGCLinearRing.java | 24 + .../esri/core/geometry/ogc/OGCMultiCurve.java | 24 + .../core/geometry/ogc/OGCMultiLineString.java | 24 + .../esri/core/geometry/ogc/OGCMultiPoint.java | 24 + .../core/geometry/ogc/OGCMultiPolygon.java | 24 + .../core/geometry/ogc/OGCMultiSurface.java | 24 + .../com/esri/core/geometry/ogc/OGCPoint.java | 24 + .../esri/core/geometry/ogc/OGCPolygon.java | 24 + .../esri/core/geometry/ogc/OGCSurface.java | 24 + .../com/esri/core/geometry/GeometryUtils.java | 24 + .../geometry/RandomCoordinateGenerator.java | 24 + .../esri/core/geometry/TestAttributes.java | 24 + .../com/esri/core/geometry/TestBuffer.java | 24 + .../java/com/esri/core/geometry/TestClip.java | 24 + .../esri/core/geometry/TestCommonMethods.java | 24 + .../com/esri/core/geometry/TestContains.java | 24 + .../esri/core/geometry/TestConvexHull.java | 24 + .../java/com/esri/core/geometry/TestCut.java | 24 + .../esri/core/geometry/TestDifference.java | 24 + .../com/esri/core/geometry/TestDistance.java | 24 + .../com/esri/core/geometry/TestEditShape.java | 26 +- .../geometry/TestEnvelope2DIntersector.java | 24 + .../com/esri/core/geometry/TestEquals.java | 24 + .../com/esri/core/geometry/TestFailed.java | 24 + .../esri/core/geometry/TestGeneralize.java | 24 + .../com/esri/core/geometry/TestGeodetic.java | 24 + ...omToJSonExportSRFromWkiOrWkt_CR181369.java | 24 + .../esri/core/geometry/TestImportExport.java | 24 + .../geometry/TestInterpolateAttributes.java | 24 + .../esri/core/geometry/TestIntersect2.java | 24 + .../esri/core/geometry/TestIntersection.java | 24 + .../esri/core/geometry/TestIntervalTree.java | 24 + .../esri/core/geometry/TestJSonGeometry.java | 24 + .../TestJSonToGeomFromWkiOrWkt_CR177613.java | 24 + .../esri/core/geometry/TestJsonParser.java | 1205 ++++++++--------- .../com/esri/core/geometry/TestMathUtils.java | 24 + .../esri/core/geometry/TestMultiPoint.java | 24 + .../java/com/esri/core/geometry/TestOGC.java | 24 + .../com/esri/core/geometry/TestOffset.java | 24 + .../com/esri/core/geometry/TestPoint.java | 24 + .../com/esri/core/geometry/TestPolygon.java | 24 + .../esri/core/geometry/TestPolygonUtils.java | 24 + .../esri/core/geometry/TestProximity2D.java | 24 + .../com/esri/core/geometry/TestQuadTree.java | 24 + .../geometry/TestRasterizedGeometry2D.java | 24 + .../com/esri/core/geometry/TestRelation.java | 24 + .../esri/core/geometry/TestSerialization.java | 24 + .../com/esri/core/geometry/TestSimplify.java | 24 + .../core/geometry/TestSpatialReference.java | 24 + .../com/esri/core/geometry/TestTouch.java | 24 + .../com/esri/core/geometry/TestTreap.java | 24 + .../com/esri/core/geometry/TestUnion.java | 24 + .../esri/core/geometry/TestWKBSupport.java | 24 + .../geometry/TestWkbImportOnPostgresST.java | 24 + .../java/com/esri/core/geometry/TestWkid.java | 24 + .../com/esri/core/geometry/TestWktParser.java | 24 + .../java/com/esri/core/geometry/Utils.java | 24 + 69 files changed, 2072 insertions(+), 674 deletions(-) diff --git a/.gitignore b/.gitignore index f31512c4..7328fe5d 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,7 @@ esri-geometry-api.jar description.jardesc *.bak *.properties +*.zip # Intellij project files *.iml diff --git a/src/main/java/com/esri/core/geometry/AttributeStreamOfDbl.java b/src/main/java/com/esri/core/geometry/AttributeStreamOfDbl.java index bbf40fde..88a639b8 100644 --- a/src/main/java/com/esri/core/geometry/AttributeStreamOfDbl.java +++ b/src/main/java/com/esri/core/geometry/AttributeStreamOfDbl.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2015 Esri + Copyright 1995-2017 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -31,14 +31,14 @@ final class AttributeStreamOfDbl extends AttributeStreamBase { - double[] m_buffer = null; - int m_size; + private double[] m_buffer = null; + private int m_size; public int size() { return m_size; } - public void reserve(int reserve)// only in Java + public void reserve(int reserve) { if (reserve <= 0) return; @@ -54,6 +54,10 @@ public void reserve(int reserve)// only in Java } + public int capacity() { + return m_buffer != null ? m_buffer.length : 0; + } + public AttributeStreamOfDbl(int size) { int sz = size; if (sz < 2) diff --git a/src/main/java/com/esri/core/geometry/AttributeStreamOfFloat.java b/src/main/java/com/esri/core/geometry/AttributeStreamOfFloat.java index 1e90b35b..491ae221 100644 --- a/src/main/java/com/esri/core/geometry/AttributeStreamOfFloat.java +++ b/src/main/java/com/esri/core/geometry/AttributeStreamOfFloat.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2015 Esri + Copyright 1995-2017 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -30,8 +30,8 @@ final class AttributeStreamOfFloat extends AttributeStreamBase { - float[] m_buffer = null; - int m_size; + private float[] m_buffer = null; + private int m_size; public int size() { return m_size; @@ -53,6 +53,10 @@ public void reserve(int reserve)// only in Java } + public int capacity() { + return m_buffer != null ? m_buffer.length : 0; + } + public AttributeStreamOfFloat(int size) { int sz = size; if (sz < 2) @@ -314,7 +318,7 @@ public void addRange(AttributeStreamBase src, int start, int count, resize(newSize); if (bForward) { - System.arraycopy(((AttributeStreamOfDbl) src).m_buffer, start, + System.arraycopy(((AttributeStreamOfFloat) src).m_buffer, start, m_buffer, oldSize, count); } else { int n = count; diff --git a/src/main/java/com/esri/core/geometry/AttributeStreamOfInt16.java b/src/main/java/com/esri/core/geometry/AttributeStreamOfInt16.java index d4af7efe..a7d1e175 100644 --- a/src/main/java/com/esri/core/geometry/AttributeStreamOfInt16.java +++ b/src/main/java/com/esri/core/geometry/AttributeStreamOfInt16.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2015 Esri + Copyright 1995-2017 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -30,8 +30,8 @@ final class AttributeStreamOfInt16 extends AttributeStreamBase { - short[] m_buffer = null; - int m_size; + private short[] m_buffer = null; + private int m_size; public int size() { return m_size; @@ -53,6 +53,10 @@ public void reserve(int reserve)// only in Java } + public int capacity() { + return m_buffer != null ? m_buffer.length : 0; + } + public AttributeStreamOfInt16(int size) { int sz = size; if (sz < 2) @@ -299,7 +303,7 @@ public void addRange(AttributeStreamBase src, int start, int count, resize(newSize); if (bForward) { - System.arraycopy(((AttributeStreamOfDbl) src).m_buffer, start, + System.arraycopy(((AttributeStreamOfInt16) src).m_buffer, start, m_buffer, oldSize, count); } else { int n = count; diff --git a/src/main/java/com/esri/core/geometry/AttributeStreamOfInt32.java b/src/main/java/com/esri/core/geometry/AttributeStreamOfInt32.java index 8aa48e56..6ece2a71 100644 --- a/src/main/java/com/esri/core/geometry/AttributeStreamOfInt32.java +++ b/src/main/java/com/esri/core/geometry/AttributeStreamOfInt32.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2015 Esri + Copyright 1995-2017 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -31,10 +31,10 @@ final class AttributeStreamOfInt32 extends AttributeStreamBase { - int[] m_buffer = null; - int m_size; + private int[] m_buffer = null; + private int m_size; - public void reserve(int reserve)// only in Java + public void reserve(int reserve) { if (reserve <= 0) return; @@ -54,6 +54,10 @@ public int size() { return m_size; } + public int capacity() { + return m_buffer != null ? m_buffer.length : 0; + } + public AttributeStreamOfInt32(int size) { int sz = size; if (sz < 2) @@ -352,7 +356,7 @@ public void addRange(AttributeStreamBase src, int start, int count, resize(newSize); if (bForward) { - System.arraycopy(((AttributeStreamOfDbl) src).m_buffer, start, + System.arraycopy(((AttributeStreamOfInt32) src).m_buffer, start, m_buffer, oldSize, count); } else { int n = count; diff --git a/src/main/java/com/esri/core/geometry/AttributeStreamOfInt64.java b/src/main/java/com/esri/core/geometry/AttributeStreamOfInt64.java index 62516ea2..92688ccd 100644 --- a/src/main/java/com/esri/core/geometry/AttributeStreamOfInt64.java +++ b/src/main/java/com/esri/core/geometry/AttributeStreamOfInt64.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2015 Esri + Copyright 1995-2017 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -30,8 +30,8 @@ final class AttributeStreamOfInt64 extends AttributeStreamBase { - long[] m_buffer = null; - int m_size; + private long[] m_buffer = null; + private int m_size; public int size() { return m_size; @@ -53,6 +53,10 @@ public void reserve(int reserve)// only in Java } + public int capacity() { + return m_buffer != null ? m_buffer.length : 0; + } + public AttributeStreamOfInt64(int size) { int sz = size; if (sz < 2) @@ -299,7 +303,7 @@ public void addRange(AttributeStreamBase src, int start, int count, resize(newSize); if (bForward) { - System.arraycopy(((AttributeStreamOfDbl) src).m_buffer, start, + System.arraycopy(((AttributeStreamOfInt64) src).m_buffer, start, m_buffer, oldSize, count); } else { int n = count; diff --git a/src/main/java/com/esri/core/geometry/AttributeStreamOfInt8.java b/src/main/java/com/esri/core/geometry/AttributeStreamOfInt8.java index b0a746e4..a4af8ff5 100644 --- a/src/main/java/com/esri/core/geometry/AttributeStreamOfInt8.java +++ b/src/main/java/com/esri/core/geometry/AttributeStreamOfInt8.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2015 Esri + Copyright 1995-2017 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -30,8 +30,8 @@ final class AttributeStreamOfInt8 extends AttributeStreamBase { - byte[] m_buffer = null; - int m_size; + private byte[] m_buffer = null; + private int m_size; public int size() { return m_size; @@ -53,6 +53,10 @@ public void reserve(int reserve)// only in Java } + public int capacity() { + return m_buffer != null ? m_buffer.length : 0; + } + public AttributeStreamOfInt8(int size) { int sz = size; if (sz < 2) @@ -350,7 +354,7 @@ public void addRange(AttributeStreamBase src, int start, int count, resize(newSize); if (bForward) { - System.arraycopy(((AttributeStreamOfDbl) src).m_buffer, start, + System.arraycopy(((AttributeStreamOfInt8) src).m_buffer, start, m_buffer, oldSize, count); } else { int n = count; diff --git a/src/main/java/com/esri/core/geometry/ogc/OGCConcreteGeometryCollection.java b/src/main/java/com/esri/core/geometry/ogc/OGCConcreteGeometryCollection.java index 1e4cb7be..3560333c 100644 --- a/src/main/java/com/esri/core/geometry/ogc/OGCConcreteGeometryCollection.java +++ b/src/main/java/com/esri/core/geometry/ogc/OGCConcreteGeometryCollection.java @@ -1,3 +1,27 @@ +/* + Copyright 1995-2017 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ + package com.esri.core.geometry.ogc; import com.esri.core.geometry.Envelope; diff --git a/src/main/java/com/esri/core/geometry/ogc/OGCCurve.java b/src/main/java/com/esri/core/geometry/ogc/OGCCurve.java index 2cad67f7..30f50dd0 100644 --- a/src/main/java/com/esri/core/geometry/ogc/OGCCurve.java +++ b/src/main/java/com/esri/core/geometry/ogc/OGCCurve.java @@ -1,3 +1,27 @@ +/* + Copyright 1995-2017 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ + package com.esri.core.geometry.ogc; import com.esri.core.geometry.MultiPoint; diff --git a/src/main/java/com/esri/core/geometry/ogc/OGCGeometry.java b/src/main/java/com/esri/core/geometry/ogc/OGCGeometry.java index 49197b83..716ee745 100644 --- a/src/main/java/com/esri/core/geometry/ogc/OGCGeometry.java +++ b/src/main/java/com/esri/core/geometry/ogc/OGCGeometry.java @@ -1,3 +1,27 @@ +/* + Copyright 1995-2017 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ + package com.esri.core.geometry.ogc; import java.io.IOException; diff --git a/src/main/java/com/esri/core/geometry/ogc/OGCGeometryCollection.java b/src/main/java/com/esri/core/geometry/ogc/OGCGeometryCollection.java index ea0d84af..ef5a3631 100644 --- a/src/main/java/com/esri/core/geometry/ogc/OGCGeometryCollection.java +++ b/src/main/java/com/esri/core/geometry/ogc/OGCGeometryCollection.java @@ -1,3 +1,27 @@ +/* + Copyright 1995-2017 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ + package com.esri.core.geometry.ogc; public abstract class OGCGeometryCollection extends OGCGeometry { diff --git a/src/main/java/com/esri/core/geometry/ogc/OGCLineString.java b/src/main/java/com/esri/core/geometry/ogc/OGCLineString.java index eb3ee7bb..da51e2d9 100644 --- a/src/main/java/com/esri/core/geometry/ogc/OGCLineString.java +++ b/src/main/java/com/esri/core/geometry/ogc/OGCLineString.java @@ -1,3 +1,27 @@ +/* + Copyright 1995-2017 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ + package com.esri.core.geometry.ogc; import com.esri.core.geometry.Geometry; diff --git a/src/main/java/com/esri/core/geometry/ogc/OGCLinearRing.java b/src/main/java/com/esri/core/geometry/ogc/OGCLinearRing.java index e733f6a5..a4b67d78 100644 --- a/src/main/java/com/esri/core/geometry/ogc/OGCLinearRing.java +++ b/src/main/java/com/esri/core/geometry/ogc/OGCLinearRing.java @@ -1,3 +1,27 @@ +/* + Copyright 1995-2017 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ + package com.esri.core.geometry.ogc; import com.esri.core.geometry.MultiPath; diff --git a/src/main/java/com/esri/core/geometry/ogc/OGCMultiCurve.java b/src/main/java/com/esri/core/geometry/ogc/OGCMultiCurve.java index 1084d3e2..9aae3bee 100644 --- a/src/main/java/com/esri/core/geometry/ogc/OGCMultiCurve.java +++ b/src/main/java/com/esri/core/geometry/ogc/OGCMultiCurve.java @@ -1,3 +1,27 @@ +/* + Copyright 1995-2017 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ + package com.esri.core.geometry.ogc; import com.esri.core.geometry.MultiPath; diff --git a/src/main/java/com/esri/core/geometry/ogc/OGCMultiLineString.java b/src/main/java/com/esri/core/geometry/ogc/OGCMultiLineString.java index 2ea784a3..37006a16 100644 --- a/src/main/java/com/esri/core/geometry/ogc/OGCMultiLineString.java +++ b/src/main/java/com/esri/core/geometry/ogc/OGCMultiLineString.java @@ -1,3 +1,27 @@ +/* + Copyright 1995-2017 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ + package com.esri.core.geometry.ogc; import com.esri.core.geometry.GeoJsonExportFlags; diff --git a/src/main/java/com/esri/core/geometry/ogc/OGCMultiPoint.java b/src/main/java/com/esri/core/geometry/ogc/OGCMultiPoint.java index a57c3b4e..1b0473b8 100644 --- a/src/main/java/com/esri/core/geometry/ogc/OGCMultiPoint.java +++ b/src/main/java/com/esri/core/geometry/ogc/OGCMultiPoint.java @@ -1,3 +1,27 @@ +/* + Copyright 1995-2017 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ + package com.esri.core.geometry.ogc; import java.nio.ByteBuffer; diff --git a/src/main/java/com/esri/core/geometry/ogc/OGCMultiPolygon.java b/src/main/java/com/esri/core/geometry/ogc/OGCMultiPolygon.java index 878ea168..944e88d5 100644 --- a/src/main/java/com/esri/core/geometry/ogc/OGCMultiPolygon.java +++ b/src/main/java/com/esri/core/geometry/ogc/OGCMultiPolygon.java @@ -1,3 +1,27 @@ +/* + Copyright 1995-2017 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ + package com.esri.core.geometry.ogc; import com.esri.core.geometry.GeoJsonExportFlags; diff --git a/src/main/java/com/esri/core/geometry/ogc/OGCMultiSurface.java b/src/main/java/com/esri/core/geometry/ogc/OGCMultiSurface.java index fbb71977..1c98be92 100644 --- a/src/main/java/com/esri/core/geometry/ogc/OGCMultiSurface.java +++ b/src/main/java/com/esri/core/geometry/ogc/OGCMultiSurface.java @@ -1,3 +1,27 @@ +/* + Copyright 1995-2017 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ + package com.esri.core.geometry.ogc; public abstract class OGCMultiSurface extends OGCGeometryCollection { diff --git a/src/main/java/com/esri/core/geometry/ogc/OGCPoint.java b/src/main/java/com/esri/core/geometry/ogc/OGCPoint.java index b6a8f9e0..98b2aaf9 100644 --- a/src/main/java/com/esri/core/geometry/ogc/OGCPoint.java +++ b/src/main/java/com/esri/core/geometry/ogc/OGCPoint.java @@ -1,3 +1,27 @@ +/* + Copyright 1995-2017 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ + package com.esri.core.geometry.ogc; import java.nio.ByteBuffer; diff --git a/src/main/java/com/esri/core/geometry/ogc/OGCPolygon.java b/src/main/java/com/esri/core/geometry/ogc/OGCPolygon.java index 5a038d2a..b27e4e48 100644 --- a/src/main/java/com/esri/core/geometry/ogc/OGCPolygon.java +++ b/src/main/java/com/esri/core/geometry/ogc/OGCPolygon.java @@ -1,3 +1,27 @@ +/* + Copyright 1995-2017 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ + package com.esri.core.geometry.ogc; import com.esri.core.geometry.Geometry; diff --git a/src/main/java/com/esri/core/geometry/ogc/OGCSurface.java b/src/main/java/com/esri/core/geometry/ogc/OGCSurface.java index 99886778..43d192e6 100644 --- a/src/main/java/com/esri/core/geometry/ogc/OGCSurface.java +++ b/src/main/java/com/esri/core/geometry/ogc/OGCSurface.java @@ -1,3 +1,27 @@ +/* + Copyright 1995-2017 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ + package com.esri.core.geometry.ogc; public abstract class OGCSurface extends OGCGeometry { diff --git a/src/test/java/com/esri/core/geometry/GeometryUtils.java b/src/test/java/com/esri/core/geometry/GeometryUtils.java index 62ddbc37..2734db7c 100644 --- a/src/test/java/com/esri/core/geometry/GeometryUtils.java +++ b/src/test/java/com/esri/core/geometry/GeometryUtils.java @@ -1,3 +1,27 @@ +/* + Copyright 1995-2017 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ + package com.esri.core.geometry; import java.io.File; diff --git a/src/test/java/com/esri/core/geometry/RandomCoordinateGenerator.java b/src/test/java/com/esri/core/geometry/RandomCoordinateGenerator.java index f50f12d7..dcaa0444 100644 --- a/src/test/java/com/esri/core/geometry/RandomCoordinateGenerator.java +++ b/src/test/java/com/esri/core/geometry/RandomCoordinateGenerator.java @@ -1,3 +1,27 @@ +/* + Copyright 1995-2017 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ + package com.esri.core.geometry; import java.util.Random; diff --git a/src/test/java/com/esri/core/geometry/TestAttributes.java b/src/test/java/com/esri/core/geometry/TestAttributes.java index bd59cc9b..16c1d9df 100644 --- a/src/test/java/com/esri/core/geometry/TestAttributes.java +++ b/src/test/java/com/esri/core/geometry/TestAttributes.java @@ -1,3 +1,27 @@ +/* + Copyright 1995-2017 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ + package com.esri.core.geometry; import static org.junit.Assert.*; diff --git a/src/test/java/com/esri/core/geometry/TestBuffer.java b/src/test/java/com/esri/core/geometry/TestBuffer.java index f381234b..341751f5 100755 --- a/src/test/java/com/esri/core/geometry/TestBuffer.java +++ b/src/test/java/com/esri/core/geometry/TestBuffer.java @@ -1,3 +1,27 @@ +/* + Copyright 1995-2017 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ + package com.esri.core.geometry; import junit.framework.TestCase; diff --git a/src/test/java/com/esri/core/geometry/TestClip.java b/src/test/java/com/esri/core/geometry/TestClip.java index 9653e5fa..3dcca075 100644 --- a/src/test/java/com/esri/core/geometry/TestClip.java +++ b/src/test/java/com/esri/core/geometry/TestClip.java @@ -1,3 +1,27 @@ +/* + Copyright 1995-2017 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ + package com.esri.core.geometry; import junit.framework.TestCase; diff --git a/src/test/java/com/esri/core/geometry/TestCommonMethods.java b/src/test/java/com/esri/core/geometry/TestCommonMethods.java index b38aeec4..9b937d4b 100644 --- a/src/test/java/com/esri/core/geometry/TestCommonMethods.java +++ b/src/test/java/com/esri/core/geometry/TestCommonMethods.java @@ -1,3 +1,27 @@ +/* + Copyright 1995-2017 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ + package com.esri.core.geometry; import java.io.File; diff --git a/src/test/java/com/esri/core/geometry/TestContains.java b/src/test/java/com/esri/core/geometry/TestContains.java index 4488b3ef..d6da4af7 100644 --- a/src/test/java/com/esri/core/geometry/TestContains.java +++ b/src/test/java/com/esri/core/geometry/TestContains.java @@ -1,3 +1,27 @@ +/* + Copyright 1995-2017 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ + package com.esri.core.geometry; import junit.framework.TestCase; diff --git a/src/test/java/com/esri/core/geometry/TestConvexHull.java b/src/test/java/com/esri/core/geometry/TestConvexHull.java index 62c59c2f..b2e2d59e 100644 --- a/src/test/java/com/esri/core/geometry/TestConvexHull.java +++ b/src/test/java/com/esri/core/geometry/TestConvexHull.java @@ -1,3 +1,27 @@ +/* + Copyright 1995-2017 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ + package com.esri.core.geometry; import junit.framework.TestCase; diff --git a/src/test/java/com/esri/core/geometry/TestCut.java b/src/test/java/com/esri/core/geometry/TestCut.java index 8bb8f25c..456973cd 100644 --- a/src/test/java/com/esri/core/geometry/TestCut.java +++ b/src/test/java/com/esri/core/geometry/TestCut.java @@ -1,3 +1,27 @@ +/* + Copyright 1995-2017 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ + package com.esri.core.geometry; import junit.framework.TestCase; diff --git a/src/test/java/com/esri/core/geometry/TestDifference.java b/src/test/java/com/esri/core/geometry/TestDifference.java index 455877e6..c6f22321 100644 --- a/src/test/java/com/esri/core/geometry/TestDifference.java +++ b/src/test/java/com/esri/core/geometry/TestDifference.java @@ -1,3 +1,27 @@ +/* + Copyright 1995-2017 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ + package com.esri.core.geometry; import java.util.ArrayList; diff --git a/src/test/java/com/esri/core/geometry/TestDistance.java b/src/test/java/com/esri/core/geometry/TestDistance.java index efcdaeeb..50399967 100644 --- a/src/test/java/com/esri/core/geometry/TestDistance.java +++ b/src/test/java/com/esri/core/geometry/TestDistance.java @@ -1,3 +1,27 @@ +/* + Copyright 1995-2017 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ + package com.esri.core.geometry; import junit.framework.TestCase; diff --git a/src/test/java/com/esri/core/geometry/TestEditShape.java b/src/test/java/com/esri/core/geometry/TestEditShape.java index 173a4970..95b5ea30 100644 --- a/src/test/java/com/esri/core/geometry/TestEditShape.java +++ b/src/test/java/com/esri/core/geometry/TestEditShape.java @@ -1,3 +1,27 @@ +/* + Copyright 1995-2017 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ + package com.esri.core.geometry; import junit.framework.TestCase; @@ -17,8 +41,6 @@ protected void tearDown() throws Exception { @Test public static void testEditShape() { { - // std::shared_ptr poly_base_6 - // = std::make_shared(); // Single part polygon Polygon poly = new Polygon(); poly.startPath(10, 10); diff --git a/src/test/java/com/esri/core/geometry/TestEnvelope2DIntersector.java b/src/test/java/com/esri/core/geometry/TestEnvelope2DIntersector.java index b7bc7d2e..2f33fadb 100644 --- a/src/test/java/com/esri/core/geometry/TestEnvelope2DIntersector.java +++ b/src/test/java/com/esri/core/geometry/TestEnvelope2DIntersector.java @@ -1,3 +1,27 @@ +/* + Copyright 1995-2017 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ + package com.esri.core.geometry; import java.util.ArrayList; diff --git a/src/test/java/com/esri/core/geometry/TestEquals.java b/src/test/java/com/esri/core/geometry/TestEquals.java index a42abf30..90ed71a0 100644 --- a/src/test/java/com/esri/core/geometry/TestEquals.java +++ b/src/test/java/com/esri/core/geometry/TestEquals.java @@ -1,3 +1,27 @@ +/* + Copyright 1995-2017 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ + package com.esri.core.geometry; import junit.framework.TestCase; diff --git a/src/test/java/com/esri/core/geometry/TestFailed.java b/src/test/java/com/esri/core/geometry/TestFailed.java index 5c5b6c41..05bede19 100644 --- a/src/test/java/com/esri/core/geometry/TestFailed.java +++ b/src/test/java/com/esri/core/geometry/TestFailed.java @@ -1,3 +1,27 @@ +/* + Copyright 1995-2017 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ + package com.esri.core.geometry; import junit.framework.TestCase; diff --git a/src/test/java/com/esri/core/geometry/TestGeneralize.java b/src/test/java/com/esri/core/geometry/TestGeneralize.java index 5348d20d..34d497e4 100644 --- a/src/test/java/com/esri/core/geometry/TestGeneralize.java +++ b/src/test/java/com/esri/core/geometry/TestGeneralize.java @@ -1,3 +1,27 @@ +/* + Copyright 1995-2017 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ + package com.esri.core.geometry; import junit.framework.TestCase; diff --git a/src/test/java/com/esri/core/geometry/TestGeodetic.java b/src/test/java/com/esri/core/geometry/TestGeodetic.java index 35c805ea..93185af9 100644 --- a/src/test/java/com/esri/core/geometry/TestGeodetic.java +++ b/src/test/java/com/esri/core/geometry/TestGeodetic.java @@ -1,3 +1,27 @@ +/* + Copyright 1995-2017 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ + package com.esri.core.geometry; import junit.framework.TestCase; diff --git a/src/test/java/com/esri/core/geometry/TestGeomToJSonExportSRFromWkiOrWkt_CR181369.java b/src/test/java/com/esri/core/geometry/TestGeomToJSonExportSRFromWkiOrWkt_CR181369.java index 498dab4b..1a0bf432 100644 --- a/src/test/java/com/esri/core/geometry/TestGeomToJSonExportSRFromWkiOrWkt_CR181369.java +++ b/src/test/java/com/esri/core/geometry/TestGeomToJSonExportSRFromWkiOrWkt_CR181369.java @@ -1,3 +1,27 @@ +/* + Copyright 1995-2017 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ + package com.esri.core.geometry; import java.io.IOException; diff --git a/src/test/java/com/esri/core/geometry/TestImportExport.java b/src/test/java/com/esri/core/geometry/TestImportExport.java index 5fd97f10..0b9e2bc8 100644 --- a/src/test/java/com/esri/core/geometry/TestImportExport.java +++ b/src/test/java/com/esri/core/geometry/TestImportExport.java @@ -1,3 +1,27 @@ +/* + Copyright 1995-2017 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ + package com.esri.core.geometry; import java.nio.ByteBuffer; diff --git a/src/test/java/com/esri/core/geometry/TestInterpolateAttributes.java b/src/test/java/com/esri/core/geometry/TestInterpolateAttributes.java index de462a02..f20f3063 100644 --- a/src/test/java/com/esri/core/geometry/TestInterpolateAttributes.java +++ b/src/test/java/com/esri/core/geometry/TestInterpolateAttributes.java @@ -1,3 +1,27 @@ +/* + Copyright 1995-2017 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ + package com.esri.core.geometry; import junit.framework.TestCase; diff --git a/src/test/java/com/esri/core/geometry/TestIntersect2.java b/src/test/java/com/esri/core/geometry/TestIntersect2.java index 8bdcd3c8..36860635 100644 --- a/src/test/java/com/esri/core/geometry/TestIntersect2.java +++ b/src/test/java/com/esri/core/geometry/TestIntersect2.java @@ -1,3 +1,27 @@ +/* + Copyright 1995-2017 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ + package com.esri.core.geometry; import com.esri.core.geometry.Geometry.Type; diff --git a/src/test/java/com/esri/core/geometry/TestIntersection.java b/src/test/java/com/esri/core/geometry/TestIntersection.java index 8e3f7fcc..eb6a2a73 100644 --- a/src/test/java/com/esri/core/geometry/TestIntersection.java +++ b/src/test/java/com/esri/core/geometry/TestIntersection.java @@ -1,3 +1,27 @@ +/* + Copyright 1995-2017 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ + package com.esri.core.geometry; import junit.framework.TestCase; diff --git a/src/test/java/com/esri/core/geometry/TestIntervalTree.java b/src/test/java/com/esri/core/geometry/TestIntervalTree.java index 0b12d678..4c0cde97 100644 --- a/src/test/java/com/esri/core/geometry/TestIntervalTree.java +++ b/src/test/java/com/esri/core/geometry/TestIntervalTree.java @@ -1,3 +1,27 @@ +/* + Copyright 1995-2017 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ + package com.esri.core.geometry; import java.util.ArrayList; diff --git a/src/test/java/com/esri/core/geometry/TestJSonGeometry.java b/src/test/java/com/esri/core/geometry/TestJSonGeometry.java index a996575f..62342524 100644 --- a/src/test/java/com/esri/core/geometry/TestJSonGeometry.java +++ b/src/test/java/com/esri/core/geometry/TestJSonGeometry.java @@ -1,3 +1,27 @@ +/* + Copyright 1995-2017 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ + package com.esri.core.geometry; import java.util.HashMap; diff --git a/src/test/java/com/esri/core/geometry/TestJSonToGeomFromWkiOrWkt_CR177613.java b/src/test/java/com/esri/core/geometry/TestJSonToGeomFromWkiOrWkt_CR177613.java index 8b036785..a6ac4d96 100644 --- a/src/test/java/com/esri/core/geometry/TestJSonToGeomFromWkiOrWkt_CR177613.java +++ b/src/test/java/com/esri/core/geometry/TestJSonToGeomFromWkiOrWkt_CR177613.java @@ -1,3 +1,27 @@ +/* + Copyright 1995-2017 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ + package com.esri.core.geometry; import java.io.IOException; diff --git a/src/test/java/com/esri/core/geometry/TestJsonParser.java b/src/test/java/com/esri/core/geometry/TestJsonParser.java index 9f2d887f..96f6343c 100644 --- a/src/test/java/com/esri/core/geometry/TestJsonParser.java +++ b/src/test/java/com/esri/core/geometry/TestJsonParser.java @@ -1,3 +1,27 @@ +/* + Copyright 1995-2017 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ + package com.esri.core.geometry; import java.util.Hashtable; @@ -14,651 +38,538 @@ public class TestJsonParser extends TestCase { - JsonFactory factory = new JsonFactory(); - SpatialReference spatialReferenceWebMerc1 = SpatialReference.create(102100); - SpatialReference spatialReferenceWebMerc2 = SpatialReference - .create(spatialReferenceWebMerc1.getLatestID()); - SpatialReference spatialReferenceWGS84 = SpatialReference.create(4326); - - @Override - protected void setUp() throws Exception { - super.setUp(); - } - - @Override - protected void tearDown() throws Exception { - super.tearDown(); - } - - @Test - public void test3DPoint() throws JsonParseException, IOException { - String jsonString3DPt = "{\"x\" : -118.15, \"y\" : 33.80, \"z\" : 10.0, \"spatialReference\" : {\"wkid\" : 4326}}"; - - JsonParser jsonParser3DPt = factory.createParser(jsonString3DPt); - MapGeometry point3DMP = GeometryEngine.jsonToGeometry(jsonParser3DPt); - assertTrue(-118.15 == ((Point) point3DMP.getGeometry()).getX()); - assertTrue(33.80 == ((Point) point3DMP.getGeometry()).getY()); - assertTrue(spatialReferenceWGS84.getID() == point3DMP - .getSpatialReference().getID()); - } - - @Test - public void test3DPoint1() throws JsonParseException, IOException { - Point point1 = new Point(10.0, 20.0); - Point pointEmpty = new Point(); - { - JsonParser pointWebMerc1Parser = factory - .createJsonParser(GeometryEngine.geometryToJson( - spatialReferenceWebMerc1, point1)); - MapGeometry pointWebMerc1MP = GeometryEngine - .jsonToGeometry(pointWebMerc1Parser); - assertTrue(point1.getX() == ((Point) pointWebMerc1MP.getGeometry()) - .getX()); - assertTrue(point1.getY() == ((Point) pointWebMerc1MP.getGeometry()) - .getY()); - int srIdOri = spatialReferenceWebMerc1.getID(); - int srIdAfter = pointWebMerc1MP.getSpatialReference().getID(); - assertTrue(srIdOri == srIdAfter || srIdAfter == 3857); - - pointWebMerc1Parser = factory.createJsonParser(GeometryEngine - .geometryToJson(null, point1)); - pointWebMerc1MP = GeometryEngine - .jsonToGeometry(pointWebMerc1Parser); - assertTrue(null == pointWebMerc1MP.getSpatialReference()); - - String pointEmptyString = GeometryEngine.geometryToJson( - spatialReferenceWebMerc1, pointEmpty); - pointWebMerc1Parser = factory.createJsonParser(pointEmptyString); - - pointWebMerc1MP = GeometryEngine - .jsonToGeometry(pointWebMerc1Parser); - assertTrue(pointWebMerc1MP.getGeometry().isEmpty()); - int srIdOri2 = spatialReferenceWebMerc1.getID(); - int srIdAfter2 = pointWebMerc1MP.getSpatialReference().getID(); - assertTrue(srIdOri2 == srIdAfter2 || srIdAfter2 == 3857); - } - } - - @Test - public void test3DPoint2() throws JsonParseException, IOException { - { - Point point1 = new Point(10.0, 20.0); - JsonParser pointWebMerc2Parser = factory - .createJsonParser(GeometryEngine.geometryToJson( - spatialReferenceWebMerc2, point1)); - MapGeometry pointWebMerc2MP = GeometryEngine - .jsonToGeometry(pointWebMerc2Parser); - assertTrue(point1.getX() == ((Point) pointWebMerc2MP.getGeometry()) - .getX()); - assertTrue(point1.getY() == ((Point) pointWebMerc2MP.getGeometry()) - .getY()); - assertTrue(spatialReferenceWebMerc2.getLatestID() == pointWebMerc2MP - .getSpatialReference().getLatestID()); - } - } - - @Test - public void test3DPoint3() throws JsonParseException, IOException { - { - Point point1 = new Point(10.0, 20.0); - JsonParser pointWgs84Parser = factory - .createJsonParser(GeometryEngine.geometryToJson( - spatialReferenceWGS84, point1)); - MapGeometry pointWgs84MP = GeometryEngine - .jsonToGeometry(pointWgs84Parser); - assertTrue(point1.getX() == ((Point) pointWgs84MP.getGeometry()) - .getX()); - assertTrue(point1.getY() == ((Point) pointWgs84MP.getGeometry()) - .getY()); - assertTrue(spatialReferenceWGS84.getID() == pointWgs84MP - .getSpatialReference().getID()); - } - } - - @Test - public void testMultiPoint() throws JsonParseException, IOException { - MultiPoint multiPoint1 = new MultiPoint(); - multiPoint1.add(-97.06138, 32.837); - multiPoint1.add(-97.06133, 32.836); - multiPoint1.add(-97.06124, 32.834); - multiPoint1.add(-97.06127, 32.832); - - { - JsonParser mPointWgs84Parser = factory - .createJsonParser(GeometryEngine.geometryToJson( - spatialReferenceWGS84, multiPoint1)); - MapGeometry mPointWgs84MP = GeometryEngine - .jsonToGeometry(mPointWgs84Parser); - assertTrue(multiPoint1.getPointCount() == ((MultiPoint) mPointWgs84MP - .getGeometry()).getPointCount()); - assertTrue(multiPoint1.getPoint(0).getX() == ((MultiPoint) mPointWgs84MP - .getGeometry()).getPoint(0).getX()); - assertTrue(multiPoint1.getPoint(0).getY() == ((MultiPoint) mPointWgs84MP - .getGeometry()).getPoint(0).getY()); - int lastIndex = multiPoint1.getPointCount() - 1; - assertTrue(multiPoint1.getPoint(lastIndex).getX() == ((MultiPoint) mPointWgs84MP - .getGeometry()).getPoint(lastIndex).getX()); - assertTrue(multiPoint1.getPoint(lastIndex).getY() == ((MultiPoint) mPointWgs84MP - .getGeometry()).getPoint(lastIndex).getY()); - - assertTrue(spatialReferenceWGS84.getID() == mPointWgs84MP - .getSpatialReference().getID()); - - MultiPoint mPointEmpty = new MultiPoint(); - String mPointEmptyString = GeometryEngine.geometryToJson( - spatialReferenceWGS84, mPointEmpty); - mPointWgs84Parser = factory.createJsonParser(mPointEmptyString); - - mPointWgs84MP = GeometryEngine.jsonToGeometry(mPointWgs84Parser); - assertTrue(mPointWgs84MP.getGeometry().isEmpty()); - assertTrue(spatialReferenceWGS84.getID() == mPointWgs84MP - .getSpatialReference().getID()); - - } - } - - @Test - public void testPolyline() throws JsonParseException, IOException { - Polyline polyline = new Polyline(); - polyline.startPath(-97.06138, 32.837); - polyline.lineTo(-97.06133, 32.836); - polyline.lineTo(-97.06124, 32.834); - polyline.lineTo(-97.06127, 32.832); - - polyline.startPath(-97.06326, 32.759); - polyline.lineTo(-97.06298, 32.755); - - { - JsonParser polylinePathsWgs84Parser = factory - .createJsonParser(GeometryEngine.geometryToJson( - spatialReferenceWGS84, polyline)); - MapGeometry mPolylineWGS84MP = GeometryEngine - .jsonToGeometry(polylinePathsWgs84Parser); - - assertTrue(polyline.getPointCount() == ((Polyline) mPolylineWGS84MP - .getGeometry()).getPointCount()); - assertTrue(polyline.getPoint(0).getX() == ((Polyline) mPolylineWGS84MP - .getGeometry()).getPoint(0).getX()); - assertTrue(polyline.getPoint(0).getY() == ((Polyline) mPolylineWGS84MP - .getGeometry()).getPoint(0).getY()); - - assertTrue(polyline.getPathCount() == ((Polyline) mPolylineWGS84MP - .getGeometry()).getPathCount()); - assertTrue(polyline.getSegmentCount() == ((Polyline) mPolylineWGS84MP - .getGeometry()).getSegmentCount()); - assertTrue(polyline.getSegmentCount(0) == ((Polyline) mPolylineWGS84MP - .getGeometry()).getSegmentCount(0)); - assertTrue(polyline.getSegmentCount(1) == ((Polyline) mPolylineWGS84MP - .getGeometry()).getSegmentCount(1)); - - int lastIndex = polyline.getPointCount() - 1; - assertTrue(polyline.getPoint(lastIndex).getX() == ((Polyline) mPolylineWGS84MP - .getGeometry()).getPoint(lastIndex).getX()); - assertTrue(polyline.getPoint(lastIndex).getY() == ((Polyline) mPolylineWGS84MP - .getGeometry()).getPoint(lastIndex).getY()); - - assertTrue(spatialReferenceWGS84.getID() == mPolylineWGS84MP - .getSpatialReference().getID()); - - Polyline emptyPolyline = new Polyline(); - String emptyString = GeometryEngine.geometryToJson( - spatialReferenceWGS84, emptyPolyline); - mPolylineWGS84MP = GeometryEngine.jsonToGeometry(factory - .createJsonParser(emptyString)); - assertTrue(mPolylineWGS84MP.getGeometry().isEmpty()); - assertTrue(spatialReferenceWGS84.getID() == mPolylineWGS84MP - .getSpatialReference().getID()); - } - } - - @Test - public void testPolygon() throws JsonParseException, IOException { - Polygon polygon = new Polygon(); - polygon.startPath(-97.06138, 32.837); - polygon.lineTo(-97.06133, 32.836); - polygon.lineTo(-97.06124, 32.834); - polygon.lineTo(-97.06127, 32.832); - - polygon.startPath(-97.06326, 32.759); - polygon.lineTo(-97.06298, 32.755); - - { - JsonParser polygonPathsWgs84Parser = factory - .createJsonParser(GeometryEngine.geometryToJson( - spatialReferenceWGS84, polygon)); - MapGeometry mPolygonWGS84MP = GeometryEngine - .jsonToGeometry(polygonPathsWgs84Parser); - - assertTrue(polygon.getPointCount() + 1 == ((Polygon) mPolygonWGS84MP - .getGeometry()).getPointCount()); - assertTrue(polygon.getPoint(0).getX() == ((Polygon) mPolygonWGS84MP - .getGeometry()).getPoint(0).getX()); - assertTrue(polygon.getPoint(0).getY() == ((Polygon) mPolygonWGS84MP - .getGeometry()).getPoint(0).getY()); - - assertTrue(polygon.getPathCount() == ((Polygon) mPolygonWGS84MP - .getGeometry()).getPathCount()); - assertTrue(polygon.getSegmentCount() + 1 == ((Polygon) mPolygonWGS84MP - .getGeometry()).getSegmentCount()); - assertTrue(polygon.getSegmentCount(0) == ((Polygon) mPolygonWGS84MP - .getGeometry()).getSegmentCount(0)); - assertTrue(polygon.getSegmentCount(1) + 1 == ((Polygon) mPolygonWGS84MP - .getGeometry()).getSegmentCount(1)); - - int lastIndex = polygon.getPointCount() - 1; - assertTrue(polygon.getPoint(lastIndex).getX() == ((Polygon) mPolygonWGS84MP - .getGeometry()).getPoint(lastIndex).getX()); - assertTrue(polygon.getPoint(lastIndex).getY() == ((Polygon) mPolygonWGS84MP - .getGeometry()).getPoint(lastIndex).getY()); - - assertTrue(spatialReferenceWGS84.getID() == mPolygonWGS84MP - .getSpatialReference().getID()); - - Polygon emptyPolygon = new Polygon(); - String emptyPolygonString = GeometryEngine.geometryToJson( - spatialReferenceWGS84, emptyPolygon); - polygonPathsWgs84Parser = factory - .createJsonParser(emptyPolygonString); - mPolygonWGS84MP = GeometryEngine - .jsonToGeometry(polygonPathsWgs84Parser); - - assertTrue(mPolygonWGS84MP.getGeometry().isEmpty()); - assertTrue(spatialReferenceWGS84.getID() == mPolygonWGS84MP - .getSpatialReference().getID()); - } - } - - @Test - public void testEnvelope() throws JsonParseException, IOException { - Envelope envelope = new Envelope(); - envelope.setCoords(-109.55, 25.76, -86.39, 49.94); - - { - JsonParser envelopeWGS84Parser = factory - .createJsonParser(GeometryEngine.geometryToJson( - spatialReferenceWGS84, envelope)); - MapGeometry envelopeWGS84MP = GeometryEngine - .jsonToGeometry(envelopeWGS84Parser); - assertTrue(envelope.isEmpty() == envelopeWGS84MP.getGeometry() - .isEmpty()); - assertTrue(envelope.getXMax() == ((Envelope) envelopeWGS84MP - .getGeometry()).getXMax()); - assertTrue(envelope.getYMax() == ((Envelope) envelopeWGS84MP - .getGeometry()).getYMax()); - assertTrue(envelope.getXMin() == ((Envelope) envelopeWGS84MP - .getGeometry()).getXMin()); - assertTrue(envelope.getYMin() == ((Envelope) envelopeWGS84MP - .getGeometry()).getYMin()); - assertTrue(spatialReferenceWGS84.getID() == envelopeWGS84MP - .getSpatialReference().getID()); - - Envelope emptyEnvelope = new Envelope(); - String emptyEnvString = GeometryEngine.geometryToJson( - spatialReferenceWGS84, emptyEnvelope); - envelopeWGS84Parser = factory.createJsonParser(emptyEnvString); - envelopeWGS84MP = GeometryEngine - .jsonToGeometry(envelopeWGS84Parser); - - assertTrue(envelopeWGS84MP.getGeometry().isEmpty()); - assertTrue(spatialReferenceWGS84.getID() == envelopeWGS84MP - .getSpatialReference().getID()); - } - } - - @Test - public void testCR181369() throws JsonParseException, IOException { - // CR181369 - { - String jsonStringPointAndWKT = "{\"x\":10.0,\"y\":20.0,\"spatialReference\":{\"wkt\" : \"PROJCS[\\\"NAD83_UTM_zone_15N\\\",GEOGCS[\\\"GCS_North_American_1983\\\",DATUM[\\\"D_North_American_1983\\\",SPHEROID[\\\"GRS_1980\\\",6378137.0,298.257222101]],PRIMEM[\\\"Greenwich\\\",0.0],UNIT[\\\"Degree\\\",0.0174532925199433]],PROJECTION[\\\"Transverse_Mercator\\\"],PARAMETER[\\\"false_easting\\\",500000.0],PARAMETER[\\\"false_northing\\\",0.0],PARAMETER[\\\"central_meridian\\\",-93.0],PARAMETER[\\\"scale_factor\\\",0.9996],PARAMETER[\\\"latitude_of_origin\\\",0.0],UNIT[\\\"Meter\\\",1.0]]\"} }"; - JsonParser jsonParserPointAndWKT = factory - .createJsonParser(jsonStringPointAndWKT); - MapGeometry mapGeom2 = GeometryEngine - .jsonToGeometry(jsonParserPointAndWKT); - String jsonStringPointAndWKT2 = GeometryEngine.geometryToJson( - mapGeom2.getSpatialReference(), mapGeom2.getGeometry()); - JsonParser jsonParserPointAndWKT2 = factory - .createJsonParser(jsonStringPointAndWKT2); - MapGeometry mapGeom3 = GeometryEngine - .jsonToGeometry(jsonParserPointAndWKT2); - assertTrue(((Point) mapGeom2.getGeometry()).getX() == ((Point) mapGeom3 - .getGeometry()).getX()); - assertTrue(((Point) mapGeom2.getGeometry()).getY() == ((Point) mapGeom3 - .getGeometry()).getY()); - assertTrue(mapGeom2.getSpatialReference().getText() - .equals(mapGeom3.getSpatialReference().getText())); - assertTrue(mapGeom2.getSpatialReference().getID() == mapGeom3 - .getSpatialReference().getID()); - } - } - - @Test - public void testSpatialRef() throws JsonParseException, IOException { - // String jsonStringPt = - // "{\"x\":-20037508.342787,\"y\":20037508.342787},\"spatialReference\":{\"wkid\":102100}}"; - String jsonStringPt = "{\"x\":10.0,\"y\":20.0,\"spatialReference\":{\"wkid\": 102100}}";// 102100 - @SuppressWarnings("unused") - String jsonStringPt2 = "{\"x\":10.0,\"y\":20.0,\"spatialReference\":{\"wkid\":4326}}"; - String jsonStringMpt = "{ \"points\" : [ [-97.06138,32.837], [-97.06133,32.836], [-97.06124,32.834], [-97.06127,32.832] ], \"spatialReference\" : {\"wkid\" : 4326}}";// 4326 - String jsonStringMpt3D = "{\"hasZs\" : true,\"points\" : [ [-97.06138,32.837,35.0], [-97.06133,32.836,35.1], [-97.06124,32.834,35.2], [-97.06127,32.832,35.3] ],\"spatialReference\" : {\"wkid\" : 4326}}"; - String jsonStringPl = "{\"paths\" : [ [ [-97.06138,32.837], [-97.06133,32.836], [-97.06124,32.834], [-97.06127,32.832] ], [ [-97.06326,32.759], [-97.06298,32.755] ]],\"spatialReference\" : {\"wkid\" : 4326}}"; - String jsonStringPl3D = "{\"hasMs\" : true,\"paths\" : [[ [-97.06138,32.837,5], [-97.06133,32.836,6], [-97.06124,32.834,7], [-97.06127,32.832,8] ],[ [-97.06326,32.759], [-97.06298,32.755] ]],\"spatialReference\" : {\"wkid\" : 4326}}"; - String jsonStringPg = "{ \"rings\" :[ [ [-97.06138,32.837], [-97.06133,32.836], [-97.06124,32.834], [-97.06127,32.832], [-97.06138,32.837] ], [ [-97.06326,32.759], [-97.06298,32.755], [-97.06153,32.749], [-97.06326,32.759] ]], \"spatialReference\" : {\"wkt\" : \"\"}}"; - String jsonStringPg3D = "{\"hasZs\" : true,\"hasMs\" : true,\"rings\" : [ [ [-97.06138, 32.837, 35.1, 4], [-97.06133, 32.836, 35.2, 4.1], [-97.06124, 32.834, 35.3, 4.2], [-97.06127, 32.832, 35.2, 44.3], [-97.06138, 32.837, 35.1, 4] ],[ [-97.06326, 32.759, 35.4], [-97.06298, 32.755, 35.5], [-97.06153, 32.749, 35.6], [-97.06326, 32.759, 35.4] ]],\"spatialReference\" : {\"wkid\" : 4326}}"; - String jsonStringPg2 = "{ \"spatialReference\" : {\"wkid\" : 4326}, \"rings\" : [[[-118.35,32.81],[-118.42,32.806],[-118.511,32.892],[-118.35,32.81]]]}"; - String jsonStringPg3 = "{ \"spatialReference\": {\"layerName\":\"GAS_POINTS\",\"name\":null,\"sdesrid\":102100,\"wkid\":102100,\"wkt\":null}}"; - String jsonString2SpatialReferences = "{ \"spatialReference\": {\"layerName\":\"GAS_POINTS\",\"name\":null,\"sdesrid\":102100,\"wkid\":102100,\"wkt\":\"GEOGCS[\\\"GCS_WGS_1984\\\",DATUM[\\\"D_WGS_1984\\\",SPHEROID[\\\"WGS_1984\\\",6378137,298.257223563]],PRIMEM[\\\"Greenwich\\\",0],UNIT[\\\"Degree\\\",0.017453292519943295]]\"}}"; - String jsonString2SpatialReferences2 = "{ \"spatialReference\": {\"layerName\":\"GAS_POINTS\",\"name\":null,\"sdesrid\":10,\"wkid\":10,\"wkt\":\"GEOGCS[\\\"GCS_WGS_1984\\\",DATUM[\\\"D_WGS_1984\\\",SPHEROID[\\\"WGS_1984\\\",6378137,298.257223563]],PRIMEM[\\\"Greenwich\\\",0],UNIT[\\\"Degree\\\",0.017453292519943295]]\"}}"; - String jsonStringSR = "{\"wkid\" : 4326}"; - String jsonStringEnv = "{\"xmin\" : -109.55, \"ymin\" : 25.76, \"xmax\" : -86.39, \"ymax\" : 49.94,\"spatialReference\" : {\"wkid\" : 4326}}"; - String jsonStringHongKon = "{\"xmin\" : -122.55, \"ymin\" : 37.65, \"xmax\" : -122.28, \"ymax\" : 37.84,\"spatialReference\" : {\"wkid\" : 4326}}"; - @SuppressWarnings("unused") - String jsonStringWKT = " {\"wkt\" : \"GEOGCS[\\\"GCS_WGS_1984\\\",DATUM[\\\"D_WGS_1984\\\",SPHEROID[\\\"WGS_1984\\\",6378137,298.257223563]],PRIMEM[\\\"Greenwich\\\",0],UNIT[\\\"Degree\\\",0.017453292519943295]]\"}"; - String jsonStringInvalidWKID = "{\"x\":10.0,\"y\":20.0},\"spatialReference\":{\"wkid\":35253523}}"; - String jsonStringOregon = "{\"xmin\":7531831.219849482,\"ymin\":585702.9799639136,\"xmax\":7750143.589982405,\"ymax\":733289.6299999952,\"spatialReference\":{\"wkid\":102726}}"; - - JsonParser jsonParserPt = factory.createJsonParser(jsonStringPt); - JsonParser jsonParserMpt = factory.createJsonParser(jsonStringMpt); - JsonParser jsonParserMpt3D = factory.createJsonParser(jsonStringMpt3D); - JsonParser jsonParserPl = factory.createJsonParser(jsonStringPl); - JsonParser jsonParserPl3D = factory.createJsonParser(jsonStringPl3D); - JsonParser jsonParserPg = factory.createJsonParser(jsonStringPg); - JsonParser jsonParserPg3D = factory.createJsonParser(jsonStringPg3D); - JsonParser jsonParserPg2 = factory.createJsonParser(jsonStringPg2); - @SuppressWarnings("unused") - JsonParser jsonParserSR = factory.createJsonParser(jsonStringSR); - JsonParser jsonParserEnv = factory.createJsonParser(jsonStringEnv); - JsonParser jsonParserPg3 = factory.createJsonParser(jsonStringPg3); - @SuppressWarnings("unused") - JsonParser jsonParserCrazy1 = factory - .createJsonParser(jsonString2SpatialReferences); - @SuppressWarnings("unused") - JsonParser jsonParserCrazy2 = factory - .createJsonParser(jsonString2SpatialReferences2); - JsonParser jsonParserInvalidWKID = factory - .createJsonParser(jsonStringInvalidWKID); - @SuppressWarnings("unused") - JsonParser jsonParseHongKon = factory - .createJsonParser(jsonStringHongKon); - JsonParser jsonParseOregon = factory.createJsonParser(jsonStringOregon); - - MapGeometry mapGeom = GeometryEngine.jsonToGeometry(jsonParserPt); - // showProjectedGeometryInfo(mapGeom); - Assert.assertTrue(mapGeom.getSpatialReference().getID() == 102100); - - MapGeometry mapGeomOregon = GeometryEngine - .jsonToGeometry(jsonParseOregon); - Assert.assertTrue(mapGeomOregon.getSpatialReference().getID() == 102726); - - mapGeom = GeometryEngine.jsonToGeometry(jsonParserMpt); - Assert.assertTrue(mapGeom.getSpatialReference().getID() == 4326); - - mapGeom = GeometryEngine.jsonToGeometry(jsonParserMpt3D); - Assert.assertTrue(mapGeom.getSpatialReference().getID() == 4326); - { - Assert.assertTrue(((MultiPoint) mapGeom.getGeometry()).getPoint(0) - .getX() == -97.06138); - Assert.assertTrue(((MultiPoint) mapGeom.getGeometry()).getPoint(0) - .getY() == 32.837); - Assert.assertTrue(((MultiPoint) mapGeom.getGeometry()).getPoint(3) - .getX() == -97.06127); - Assert.assertTrue(((MultiPoint) mapGeom.getGeometry()).getPoint(3) - .getY() == 32.832); - } - // showProjectedGeometryInfo(mapGeom); - - mapGeom = GeometryEngine.jsonToGeometry(jsonParserPl); - Assert.assertTrue(mapGeom.getSpatialReference().getID() == 4326); - // showProjectedGeometryInfo(mapGeom); - - mapGeom = GeometryEngine.jsonToGeometry(jsonParserPl3D); - { - // [[ [-97.06138,32.837,5], [-97.06133,32.836,6], - // [-97.06124,32.834,7], [-97.06127,32.832,8] ], - // [ [-97.06326,32.759], [-97.06298,32.755] ]]"; - Assert.assertTrue(((Polyline) mapGeom.getGeometry()).getPoint(0) - .getX() == -97.06138); - Assert.assertTrue(((Polyline) mapGeom.getGeometry()).getPoint(0) - .getY() == 32.837); - int lastIndex = ((Polyline) mapGeom.getGeometry()).getPointCount() - 1; - Assert.assertTrue(((Polyline) mapGeom.getGeometry()).getPoint( - lastIndex).getX() == -97.06298);// -97.06153, 32.749 - Assert.assertTrue(((Polyline) mapGeom.getGeometry()).getPoint( - lastIndex).getY() == 32.755); - int lastIndexFirstLine = ((Polyline) mapGeom.getGeometry()) - .getPathEnd(0) - 1; - Assert.assertTrue(((Polyline) mapGeom.getGeometry()).getPoint( - lastIndexFirstLine).getX() == -97.06127);// -97.06153, - // 32.749 - Assert.assertTrue(((Polyline) mapGeom.getGeometry()).getPoint( - lastIndexFirstLine).getY() == 32.832); - } - - mapGeom = GeometryEngine.jsonToGeometry(jsonParserPg); - Assert.assertTrue(mapGeom.getSpatialReference() == null); - - mapGeom = GeometryEngine.jsonToGeometry(jsonParserPg3D); - { - Assert.assertTrue(((Polygon) mapGeom.getGeometry()).getPoint(0) - .getX() == -97.06138); - Assert.assertTrue(((Polygon) mapGeom.getGeometry()).getPoint(0) - .getY() == 32.837); - int lastIndex = ((Polygon) mapGeom.getGeometry()).getPointCount() - 1; - Assert.assertTrue(((Polygon) mapGeom.getGeometry()).getPoint( - lastIndex).getX() == -97.06153);// -97.06153, 32.749 - Assert.assertTrue(((Polygon) mapGeom.getGeometry()).getPoint( - lastIndex).getY() == 32.749); - } - - mapGeom = GeometryEngine.jsonToGeometry(jsonParserPg2); - Assert.assertTrue(mapGeom.getSpatialReference().getID() == 4326); - // showProjectedGeometryInfo(mapGeom); - - mapGeom = GeometryEngine.jsonToGeometry(jsonParserPg3); - Assert.assertTrue(mapGeom.getSpatialReference().getID() == 102100); - // showProjectedGeometryInfo(mapGeom); - - // mapGeom = GeometryEngine.jsonToGeometry(jsonParserCrazy1); - // Assert.assertTrue(mapGeom.getSpatialReference().getText().equals("")); - // showProjectedGeometryInfo(mapGeom); - - mapGeom = GeometryEngine.jsonToGeometry(jsonParserEnv); - Assert.assertTrue(mapGeom.getSpatialReference().getID() == 4326); - // showProjectedGeometryInfo(mapGeom); - - try { - GeometryEngine.jsonToGeometry(jsonParserInvalidWKID); - } catch (Exception ex) { - Assert.assertTrue("Should not throw for invalid wkid", false); - } - } - - @Test - public void testMP2onCR175871() throws Exception { - Polygon pg = new Polygon(); - pg.startPath(-50, 10); - pg.lineTo(-50, 12); - pg.lineTo(-45, 12); - pg.lineTo(-45, 10); - - Polygon pg1 = new Polygon(); - pg1.startPath(-45, 10); - pg1.lineTo(-40, 10); - pg1.lineTo(-40, 8); - pg.add(pg1, false); - - SpatialReference spatialReference = SpatialReference.create(4326); - - try { - String jSonStr = GeometryEngine - .geometryToJson(spatialReference, pg); - JsonFactory jf = new JsonFactory(); - - JsonParser jp = jf.createJsonParser(jSonStr); - jp.nextToken(); - MapGeometry mg = GeometryEngine.jsonToGeometry(jp); - Geometry gm = mg.getGeometry(); - Assert.assertEquals(Geometry.Type.Polygon, gm.getType()); - Assert.assertTrue(mg.getSpatialReference().getID() == 4326); - - Polygon pgNew = (Polygon) gm; - - Assert.assertEquals(pgNew.getPathCount(), pg.getPathCount()); - Assert.assertEquals(pgNew.getPointCount(), pg.getPointCount()); - Assert.assertEquals(pgNew.getSegmentCount(), pg.getSegmentCount()); - - Assert.assertEquals(pgNew.getPoint(0).getX(), - pg.getPoint(0).getX(), 0.000000001); - Assert.assertEquals(pgNew.getPoint(1).getX(), - pg.getPoint(1).getX(), 0.000000001); - Assert.assertEquals(pgNew.getPoint(2).getX(), - pg.getPoint(2).getX(), 0.000000001); - Assert.assertEquals(pgNew.getPoint(3).getX(), - pg.getPoint(3).getX(), 0.000000001); - - Assert.assertEquals(pgNew.getPoint(0).getY(), - pg.getPoint(0).getY(), 0.000000001); - Assert.assertEquals(pgNew.getPoint(1).getY(), - pg.getPoint(1).getY(), 0.000000001); - Assert.assertEquals(pgNew.getPoint(2).getY(), - pg.getPoint(2).getY(), 0.000000001); - Assert.assertEquals(pgNew.getPoint(3).getY(), - pg.getPoint(3).getY(), 0.000000001); - } catch (Exception ex) { - String err = ex.getMessage(); - System.out.print(err); - throw ex; - } - } - - @Test - public static int fromJsonToWkid(JsonParser parser) - throws JsonParseException, IOException { - int wkid = 0; - if (parser.getCurrentToken() != JsonToken.START_OBJECT) { - return 0; - } - - while (parser.nextToken() != JsonToken.END_OBJECT) { - String fieldName = parser.getCurrentName(); - - if ("wkid".equals(fieldName)) { - parser.nextToken(); - wkid = parser.getIntValue(); - } - } - return wkid; - } - - @SuppressWarnings("unused") - private static void showProjectedGeometryInfo(MapGeometry mapGeom) { - System.out.println("\n"); - MapGeometry geom = mapGeom; - // while ((geom = geomCursor.next()) != null) { - - if (geom.getGeometry() instanceof Point) { - Point pnt = (Point) geom.getGeometry(); - System.out - .println("Point(" + pnt.getX() + " , " + pnt.getY() + ")"); - if (geom.getSpatialReference() == null) { - System.out.println("No spatial reference"); - } else { - System.out.println("wkid: " - + geom.getSpatialReference().getID()); - } - - } else if (geom.getGeometry() instanceof MultiPoint) { - MultiPoint mp = (MultiPoint) geom.getGeometry(); - System.out.println("Multipoint has " + mp.getPointCount() - + " points."); - - System.out.println("wkid: " + geom.getSpatialReference().getID()); - - } else if (geom.getGeometry() instanceof Polygon) { - Polygon mp = (Polygon) geom.getGeometry(); - System.out.println("Polygon has " + mp.getPointCount() - + " points and " + mp.getPathCount() + " parts."); - if (mp.getPathCount() > 1) { - System.out.println("Part start of 2nd segment : " - + mp.getPathStart(1)); - System.out.println("Part end of 2nd segment : " - + mp.getPathEnd(1)); - System.out.println("Part size of 2nd segment : " - + mp.getPathSize(1)); - - int start = mp.getPathStart(1); - int end = mp.getPathEnd(1); - for (int i = start; i < end; i++) { - Point pp = mp.getPoint(i); - System.out.println("Point(" + i + ") = (" + pp.getX() - + ", " + pp.getY() + ")"); - } - } - System.out.println("wkid: " + geom.getSpatialReference().getID()); - - } else if (geom.getGeometry() instanceof Polyline) { - Polyline mp = (Polyline) geom.getGeometry(); - System.out.println("Polyline has " + mp.getPointCount() - + " points and " + mp.getPathCount() + " parts."); - System.out.println("Part start of 2nd segment : " - + mp.getPathStart(1)); - System.out.println("Part end of 2nd segment : " - + mp.getPathEnd(1)); - System.out.println("Part size of 2nd segment : " - + mp.getPathSize(1)); - int start = mp.getPathStart(1); - int end = mp.getPathEnd(1); - for (int i = start; i < end; i++) { - Point pp = mp.getPoint(i); - System.out.println("Point(" + i + ") = (" + pp.getX() + ", " - + pp.getY() + ")"); - } - - System.out.println("wkid: " + geom.getSpatialReference().getID()); - } - } - - @Test - public void testGeometryToJSON() { - Polygon geom = new Polygon(); - geom.startPath(new Point(-113, 34)); - geom.lineTo(new Point(-105, 34)); - geom.lineTo(new Point(-108, 40)); - - String outputPolygon1 = GeometryEngine.geometryToJson(-1, geom);// Test - // WKID - // == -1 - //System.out.println("Geom JSON STRING is" + outputPolygon1); - String correctPolygon1 = "{\"rings\":[[[-113,34],[-105,34],[-108,40],[-113,34]]]}"; - - assertEquals(correctPolygon1, outputPolygon1); - - String outputPolygon2 = GeometryEngine.geometryToJson(4326, geom); - //System.out.println("Geom JSON STRING is" + outputPolygon2); - - String correctPolygon2 = "{\"rings\":[[[-113,34],[-105,34],[-108,40],[-113,34]]],\"spatialReference\":{\"wkid\":4326}}"; - assertEquals(correctPolygon2, outputPolygon2); - } - - @Test - public void testGeometryToJSONOldID() throws Exception {// CR - Polygon geom = new Polygon(); - geom.startPath(new Point(-113, 34)); - geom.lineTo(new Point(-105, 34)); - geom.lineTo(new Point(-108, 40)); - String outputPolygon = GeometryEngine.geometryToJson( - SpatialReference.create(3857), geom);// Test WKID == -1 - String correctPolygon = "{\"rings\":[[[-113,34],[-105,34],[-108,40],[-113,34]]],\"spatialReference\":{\"wkid\":102100,\"latestWkid\":3857}}"; - assertTrue(outputPolygon.equals(correctPolygon)); - JsonFactory jf = new JsonFactory(); - JsonParser jp = jf.createJsonParser(outputPolygon); - jp.nextToken(); - MapGeometry mg = GeometryEngine.jsonToGeometry(jp); - @SuppressWarnings("unused") - int srId = mg.getSpatialReference().getID(); - @SuppressWarnings("unused") - int srOldId = mg.getSpatialReference().getOldID(); - Assert.assertTrue(mg.getSpatialReference().getID() == 3857); - Assert.assertTrue(mg.getSpatialReference().getLatestID() == 3857); - Assert.assertTrue(mg.getSpatialReference().getOldID() == 102100); - } + JsonFactory factory = new JsonFactory(); + SpatialReference spatialReferenceWebMerc1 = SpatialReference.create(102100); + SpatialReference spatialReferenceWebMerc2 = SpatialReference.create(spatialReferenceWebMerc1.getLatestID()); + SpatialReference spatialReferenceWGS84 = SpatialReference.create(4326); + + @Override + protected void setUp() throws Exception { + super.setUp(); + } + + @Override + protected void tearDown() throws Exception { + super.tearDown(); + } + + @Test + public void test3DPoint() throws JsonParseException, IOException { + String jsonString3DPt = "{\"x\" : -118.15, \"y\" : 33.80, \"z\" : 10.0, \"spatialReference\" : {\"wkid\" : 4326}}"; + + JsonParser jsonParser3DPt = factory.createParser(jsonString3DPt); + MapGeometry point3DMP = GeometryEngine.jsonToGeometry(jsonParser3DPt); + assertTrue(-118.15 == ((Point) point3DMP.getGeometry()).getX()); + assertTrue(33.80 == ((Point) point3DMP.getGeometry()).getY()); + assertTrue(spatialReferenceWGS84.getID() == point3DMP.getSpatialReference().getID()); + } + + @Test + public void test3DPoint1() throws JsonParseException, IOException { + Point point1 = new Point(10.0, 20.0); + Point pointEmpty = new Point(); + { + JsonParser pointWebMerc1Parser = factory + .createJsonParser(GeometryEngine.geometryToJson(spatialReferenceWebMerc1, point1)); + MapGeometry pointWebMerc1MP = GeometryEngine.jsonToGeometry(pointWebMerc1Parser); + assertTrue(point1.getX() == ((Point) pointWebMerc1MP.getGeometry()).getX()); + assertTrue(point1.getY() == ((Point) pointWebMerc1MP.getGeometry()).getY()); + int srIdOri = spatialReferenceWebMerc1.getID(); + int srIdAfter = pointWebMerc1MP.getSpatialReference().getID(); + assertTrue(srIdOri == srIdAfter || srIdAfter == 3857); + + pointWebMerc1Parser = factory.createJsonParser(GeometryEngine.geometryToJson(null, point1)); + pointWebMerc1MP = GeometryEngine.jsonToGeometry(pointWebMerc1Parser); + assertTrue(null == pointWebMerc1MP.getSpatialReference()); + + String pointEmptyString = GeometryEngine.geometryToJson(spatialReferenceWebMerc1, pointEmpty); + pointWebMerc1Parser = factory.createJsonParser(pointEmptyString); + + pointWebMerc1MP = GeometryEngine.jsonToGeometry(pointWebMerc1Parser); + assertTrue(pointWebMerc1MP.getGeometry().isEmpty()); + int srIdOri2 = spatialReferenceWebMerc1.getID(); + int srIdAfter2 = pointWebMerc1MP.getSpatialReference().getID(); + assertTrue(srIdOri2 == srIdAfter2 || srIdAfter2 == 3857); + } + } + + @Test + public void test3DPoint2() throws JsonParseException, IOException { + { + Point point1 = new Point(10.0, 20.0); + JsonParser pointWebMerc2Parser = factory + .createJsonParser(GeometryEngine.geometryToJson(spatialReferenceWebMerc2, point1)); + MapGeometry pointWebMerc2MP = GeometryEngine.jsonToGeometry(pointWebMerc2Parser); + assertTrue(point1.getX() == ((Point) pointWebMerc2MP.getGeometry()).getX()); + assertTrue(point1.getY() == ((Point) pointWebMerc2MP.getGeometry()).getY()); + assertTrue(spatialReferenceWebMerc2.getLatestID() == pointWebMerc2MP.getSpatialReference().getLatestID()); + } + } + + @Test + public void test3DPoint3() throws JsonParseException, IOException { + { + Point point1 = new Point(10.0, 20.0); + JsonParser pointWgs84Parser = factory + .createJsonParser(GeometryEngine.geometryToJson(spatialReferenceWGS84, point1)); + MapGeometry pointWgs84MP = GeometryEngine.jsonToGeometry(pointWgs84Parser); + assertTrue(point1.getX() == ((Point) pointWgs84MP.getGeometry()).getX()); + assertTrue(point1.getY() == ((Point) pointWgs84MP.getGeometry()).getY()); + assertTrue(spatialReferenceWGS84.getID() == pointWgs84MP.getSpatialReference().getID()); + } + } + + @Test + public void testMultiPoint() throws JsonParseException, IOException { + MultiPoint multiPoint1 = new MultiPoint(); + multiPoint1.add(-97.06138, 32.837); + multiPoint1.add(-97.06133, 32.836); + multiPoint1.add(-97.06124, 32.834); + multiPoint1.add(-97.06127, 32.832); + + { + JsonParser mPointWgs84Parser = factory + .createJsonParser(GeometryEngine.geometryToJson(spatialReferenceWGS84, multiPoint1)); + MapGeometry mPointWgs84MP = GeometryEngine.jsonToGeometry(mPointWgs84Parser); + assertTrue(multiPoint1.getPointCount() == ((MultiPoint) mPointWgs84MP.getGeometry()).getPointCount()); + assertTrue(multiPoint1.getPoint(0).getX() == ((MultiPoint) mPointWgs84MP.getGeometry()).getPoint(0).getX()); + assertTrue(multiPoint1.getPoint(0).getY() == ((MultiPoint) mPointWgs84MP.getGeometry()).getPoint(0).getY()); + int lastIndex = multiPoint1.getPointCount() - 1; + assertTrue(multiPoint1.getPoint(lastIndex).getX() == ((MultiPoint) mPointWgs84MP.getGeometry()) + .getPoint(lastIndex).getX()); + assertTrue(multiPoint1.getPoint(lastIndex).getY() == ((MultiPoint) mPointWgs84MP.getGeometry()) + .getPoint(lastIndex).getY()); + + assertTrue(spatialReferenceWGS84.getID() == mPointWgs84MP.getSpatialReference().getID()); + + MultiPoint mPointEmpty = new MultiPoint(); + String mPointEmptyString = GeometryEngine.geometryToJson(spatialReferenceWGS84, mPointEmpty); + mPointWgs84Parser = factory.createJsonParser(mPointEmptyString); + + mPointWgs84MP = GeometryEngine.jsonToGeometry(mPointWgs84Parser); + assertTrue(mPointWgs84MP.getGeometry().isEmpty()); + assertTrue(spatialReferenceWGS84.getID() == mPointWgs84MP.getSpatialReference().getID()); + + } + } + + @Test + public void testPolyline() throws JsonParseException, IOException { + Polyline polyline = new Polyline(); + polyline.startPath(-97.06138, 32.837); + polyline.lineTo(-97.06133, 32.836); + polyline.lineTo(-97.06124, 32.834); + polyline.lineTo(-97.06127, 32.832); + + polyline.startPath(-97.06326, 32.759); + polyline.lineTo(-97.06298, 32.755); + + { + JsonParser polylinePathsWgs84Parser = factory + .createJsonParser(GeometryEngine.geometryToJson(spatialReferenceWGS84, polyline)); + MapGeometry mPolylineWGS84MP = GeometryEngine.jsonToGeometry(polylinePathsWgs84Parser); + + assertTrue(polyline.getPointCount() == ((Polyline) mPolylineWGS84MP.getGeometry()).getPointCount()); + assertTrue(polyline.getPoint(0).getX() == ((Polyline) mPolylineWGS84MP.getGeometry()).getPoint(0).getX()); + assertTrue(polyline.getPoint(0).getY() == ((Polyline) mPolylineWGS84MP.getGeometry()).getPoint(0).getY()); + + assertTrue(polyline.getPathCount() == ((Polyline) mPolylineWGS84MP.getGeometry()).getPathCount()); + assertTrue(polyline.getSegmentCount() == ((Polyline) mPolylineWGS84MP.getGeometry()).getSegmentCount()); + assertTrue(polyline.getSegmentCount(0) == ((Polyline) mPolylineWGS84MP.getGeometry()).getSegmentCount(0)); + assertTrue(polyline.getSegmentCount(1) == ((Polyline) mPolylineWGS84MP.getGeometry()).getSegmentCount(1)); + + int lastIndex = polyline.getPointCount() - 1; + assertTrue(polyline.getPoint(lastIndex).getX() == ((Polyline) mPolylineWGS84MP.getGeometry()) + .getPoint(lastIndex).getX()); + assertTrue(polyline.getPoint(lastIndex).getY() == ((Polyline) mPolylineWGS84MP.getGeometry()) + .getPoint(lastIndex).getY()); + + assertTrue(spatialReferenceWGS84.getID() == mPolylineWGS84MP.getSpatialReference().getID()); + + Polyline emptyPolyline = new Polyline(); + String emptyString = GeometryEngine.geometryToJson(spatialReferenceWGS84, emptyPolyline); + mPolylineWGS84MP = GeometryEngine.jsonToGeometry(factory.createJsonParser(emptyString)); + assertTrue(mPolylineWGS84MP.getGeometry().isEmpty()); + assertTrue(spatialReferenceWGS84.getID() == mPolylineWGS84MP.getSpatialReference().getID()); + } + } + + @Test + public void testPolygon() throws JsonParseException, IOException { + Polygon polygon = new Polygon(); + polygon.startPath(-97.06138, 32.837); + polygon.lineTo(-97.06133, 32.836); + polygon.lineTo(-97.06124, 32.834); + polygon.lineTo(-97.06127, 32.832); + + polygon.startPath(-97.06326, 32.759); + polygon.lineTo(-97.06298, 32.755); + + { + JsonParser polygonPathsWgs84Parser = factory + .createJsonParser(GeometryEngine.geometryToJson(spatialReferenceWGS84, polygon)); + MapGeometry mPolygonWGS84MP = GeometryEngine.jsonToGeometry(polygonPathsWgs84Parser); + + assertTrue(polygon.getPointCount() + 1 == ((Polygon) mPolygonWGS84MP.getGeometry()).getPointCount()); + assertTrue(polygon.getPoint(0).getX() == ((Polygon) mPolygonWGS84MP.getGeometry()).getPoint(0).getX()); + assertTrue(polygon.getPoint(0).getY() == ((Polygon) mPolygonWGS84MP.getGeometry()).getPoint(0).getY()); + + assertTrue(polygon.getPathCount() == ((Polygon) mPolygonWGS84MP.getGeometry()).getPathCount()); + assertTrue(polygon.getSegmentCount() + 1 == ((Polygon) mPolygonWGS84MP.getGeometry()).getSegmentCount()); + assertTrue(polygon.getSegmentCount(0) == ((Polygon) mPolygonWGS84MP.getGeometry()).getSegmentCount(0)); + assertTrue(polygon.getSegmentCount(1) + 1 == ((Polygon) mPolygonWGS84MP.getGeometry()).getSegmentCount(1)); + + int lastIndex = polygon.getPointCount() - 1; + assertTrue(polygon.getPoint(lastIndex).getX() == ((Polygon) mPolygonWGS84MP.getGeometry()) + .getPoint(lastIndex).getX()); + assertTrue(polygon.getPoint(lastIndex).getY() == ((Polygon) mPolygonWGS84MP.getGeometry()) + .getPoint(lastIndex).getY()); + + assertTrue(spatialReferenceWGS84.getID() == mPolygonWGS84MP.getSpatialReference().getID()); + + Polygon emptyPolygon = new Polygon(); + String emptyPolygonString = GeometryEngine.geometryToJson(spatialReferenceWGS84, emptyPolygon); + polygonPathsWgs84Parser = factory.createJsonParser(emptyPolygonString); + mPolygonWGS84MP = GeometryEngine.jsonToGeometry(polygonPathsWgs84Parser); + + assertTrue(mPolygonWGS84MP.getGeometry().isEmpty()); + assertTrue(spatialReferenceWGS84.getID() == mPolygonWGS84MP.getSpatialReference().getID()); + } + } + + @Test + public void testEnvelope() throws JsonParseException, IOException { + Envelope envelope = new Envelope(); + envelope.setCoords(-109.55, 25.76, -86.39, 49.94); + + { + JsonParser envelopeWGS84Parser = factory + .createJsonParser(GeometryEngine.geometryToJson(spatialReferenceWGS84, envelope)); + MapGeometry envelopeWGS84MP = GeometryEngine.jsonToGeometry(envelopeWGS84Parser); + assertTrue(envelope.isEmpty() == envelopeWGS84MP.getGeometry().isEmpty()); + assertTrue(envelope.getXMax() == ((Envelope) envelopeWGS84MP.getGeometry()).getXMax()); + assertTrue(envelope.getYMax() == ((Envelope) envelopeWGS84MP.getGeometry()).getYMax()); + assertTrue(envelope.getXMin() == ((Envelope) envelopeWGS84MP.getGeometry()).getXMin()); + assertTrue(envelope.getYMin() == ((Envelope) envelopeWGS84MP.getGeometry()).getYMin()); + assertTrue(spatialReferenceWGS84.getID() == envelopeWGS84MP.getSpatialReference().getID()); + + Envelope emptyEnvelope = new Envelope(); + String emptyEnvString = GeometryEngine.geometryToJson(spatialReferenceWGS84, emptyEnvelope); + envelopeWGS84Parser = factory.createJsonParser(emptyEnvString); + envelopeWGS84MP = GeometryEngine.jsonToGeometry(envelopeWGS84Parser); + + assertTrue(envelopeWGS84MP.getGeometry().isEmpty()); + assertTrue(spatialReferenceWGS84.getID() == envelopeWGS84MP.getSpatialReference().getID()); + } + } + + @Test + public void testCR181369() throws JsonParseException, IOException { + // CR181369 + { + String jsonStringPointAndWKT = "{\"x\":10.0,\"y\":20.0,\"spatialReference\":{\"wkt\" : \"PROJCS[\\\"NAD83_UTM_zone_15N\\\",GEOGCS[\\\"GCS_North_American_1983\\\",DATUM[\\\"D_North_American_1983\\\",SPHEROID[\\\"GRS_1980\\\",6378137.0,298.257222101]],PRIMEM[\\\"Greenwich\\\",0.0],UNIT[\\\"Degree\\\",0.0174532925199433]],PROJECTION[\\\"Transverse_Mercator\\\"],PARAMETER[\\\"false_easting\\\",500000.0],PARAMETER[\\\"false_northing\\\",0.0],PARAMETER[\\\"central_meridian\\\",-93.0],PARAMETER[\\\"scale_factor\\\",0.9996],PARAMETER[\\\"latitude_of_origin\\\",0.0],UNIT[\\\"Meter\\\",1.0]]\"} }"; + JsonParser jsonParserPointAndWKT = factory.createJsonParser(jsonStringPointAndWKT); + MapGeometry mapGeom2 = GeometryEngine.jsonToGeometry(jsonParserPointAndWKT); + String jsonStringPointAndWKT2 = GeometryEngine.geometryToJson(mapGeom2.getSpatialReference(), + mapGeom2.getGeometry()); + JsonParser jsonParserPointAndWKT2 = factory.createJsonParser(jsonStringPointAndWKT2); + MapGeometry mapGeom3 = GeometryEngine.jsonToGeometry(jsonParserPointAndWKT2); + assertTrue(((Point) mapGeom2.getGeometry()).getX() == ((Point) mapGeom3.getGeometry()).getX()); + assertTrue(((Point) mapGeom2.getGeometry()).getY() == ((Point) mapGeom3.getGeometry()).getY()); + assertTrue(mapGeom2.getSpatialReference().getText().equals(mapGeom3.getSpatialReference().getText())); + assertTrue(mapGeom2.getSpatialReference().getID() == mapGeom3.getSpatialReference().getID()); + } + } + + @Test + public void testSpatialRef() throws JsonParseException, IOException { + // String jsonStringPt = + // "{\"x\":-20037508.342787,\"y\":20037508.342787},\"spatialReference\":{\"wkid\":102100}}"; + String jsonStringPt = "{\"x\":10.0,\"y\":20.0,\"spatialReference\":{\"wkid\": 102100}}";// 102100 + @SuppressWarnings("unused") + String jsonStringPt2 = "{\"x\":10.0,\"y\":20.0,\"spatialReference\":{\"wkid\":4326}}"; + String jsonStringMpt = "{ \"points\" : [ [-97.06138,32.837], [-97.06133,32.836], [-97.06124,32.834], [-97.06127,32.832] ], \"spatialReference\" : {\"wkid\" : 4326}}";// 4326 + String jsonStringMpt3D = "{\"hasZs\" : true,\"points\" : [ [-97.06138,32.837,35.0], [-97.06133,32.836,35.1], [-97.06124,32.834,35.2], [-97.06127,32.832,35.3] ],\"spatialReference\" : {\"wkid\" : 4326}}"; + String jsonStringPl = "{\"paths\" : [ [ [-97.06138,32.837], [-97.06133,32.836], [-97.06124,32.834], [-97.06127,32.832] ], [ [-97.06326,32.759], [-97.06298,32.755] ]],\"spatialReference\" : {\"wkid\" : 4326}}"; + String jsonStringPl3D = "{\"hasMs\" : true,\"paths\" : [[ [-97.06138,32.837,5], [-97.06133,32.836,6], [-97.06124,32.834,7], [-97.06127,32.832,8] ],[ [-97.06326,32.759], [-97.06298,32.755] ]],\"spatialReference\" : {\"wkid\" : 4326}}"; + String jsonStringPg = "{ \"rings\" :[ [ [-97.06138,32.837], [-97.06133,32.836], [-97.06124,32.834], [-97.06127,32.832], [-97.06138,32.837] ], [ [-97.06326,32.759], [-97.06298,32.755], [-97.06153,32.749], [-97.06326,32.759] ]], \"spatialReference\" : {\"wkt\" : \"\"}}"; + String jsonStringPg3D = "{\"hasZs\" : true,\"hasMs\" : true,\"rings\" : [ [ [-97.06138, 32.837, 35.1, 4], [-97.06133, 32.836, 35.2, 4.1], [-97.06124, 32.834, 35.3, 4.2], [-97.06127, 32.832, 35.2, 44.3], [-97.06138, 32.837, 35.1, 4] ],[ [-97.06326, 32.759, 35.4], [-97.06298, 32.755, 35.5], [-97.06153, 32.749, 35.6], [-97.06326, 32.759, 35.4] ]],\"spatialReference\" : {\"wkid\" : 4326}}"; + String jsonStringPg2 = "{ \"spatialReference\" : {\"wkid\" : 4326}, \"rings\" : [[[-118.35,32.81],[-118.42,32.806],[-118.511,32.892],[-118.35,32.81]]]}"; + String jsonStringPg3 = "{ \"spatialReference\": {\"layerName\":\"GAS_POINTS\",\"name\":null,\"sdesrid\":102100,\"wkid\":102100,\"wkt\":null}}"; + String jsonString2SpatialReferences = "{ \"spatialReference\": {\"layerName\":\"GAS_POINTS\",\"name\":null,\"sdesrid\":102100,\"wkid\":102100,\"wkt\":\"GEOGCS[\\\"GCS_WGS_1984\\\",DATUM[\\\"D_WGS_1984\\\",SPHEROID[\\\"WGS_1984\\\",6378137,298.257223563]],PRIMEM[\\\"Greenwich\\\",0],UNIT[\\\"Degree\\\",0.017453292519943295]]\"}}"; + String jsonString2SpatialReferences2 = "{ \"spatialReference\": {\"layerName\":\"GAS_POINTS\",\"name\":null,\"sdesrid\":10,\"wkid\":10,\"wkt\":\"GEOGCS[\\\"GCS_WGS_1984\\\",DATUM[\\\"D_WGS_1984\\\",SPHEROID[\\\"WGS_1984\\\",6378137,298.257223563]],PRIMEM[\\\"Greenwich\\\",0],UNIT[\\\"Degree\\\",0.017453292519943295]]\"}}"; + String jsonStringSR = "{\"wkid\" : 4326}"; + String jsonStringEnv = "{\"xmin\" : -109.55, \"ymin\" : 25.76, \"xmax\" : -86.39, \"ymax\" : 49.94,\"spatialReference\" : {\"wkid\" : 4326}}"; + String jsonStringHongKon = "{\"xmin\" : -122.55, \"ymin\" : 37.65, \"xmax\" : -122.28, \"ymax\" : 37.84,\"spatialReference\" : {\"wkid\" : 4326}}"; + @SuppressWarnings("unused") + String jsonStringWKT = " {\"wkt\" : \"GEOGCS[\\\"GCS_WGS_1984\\\",DATUM[\\\"D_WGS_1984\\\",SPHEROID[\\\"WGS_1984\\\",6378137,298.257223563]],PRIMEM[\\\"Greenwich\\\",0],UNIT[\\\"Degree\\\",0.017453292519943295]]\"}"; + String jsonStringInvalidWKID = "{\"x\":10.0,\"y\":20.0},\"spatialReference\":{\"wkid\":35253523}}"; + String jsonStringOregon = "{\"xmin\":7531831.219849482,\"ymin\":585702.9799639136,\"xmax\":7750143.589982405,\"ymax\":733289.6299999952,\"spatialReference\":{\"wkid\":102726}}"; + + JsonParser jsonParserPt = factory.createJsonParser(jsonStringPt); + JsonParser jsonParserMpt = factory.createJsonParser(jsonStringMpt); + JsonParser jsonParserMpt3D = factory.createJsonParser(jsonStringMpt3D); + JsonParser jsonParserPl = factory.createJsonParser(jsonStringPl); + JsonParser jsonParserPl3D = factory.createJsonParser(jsonStringPl3D); + JsonParser jsonParserPg = factory.createJsonParser(jsonStringPg); + JsonParser jsonParserPg3D = factory.createJsonParser(jsonStringPg3D); + JsonParser jsonParserPg2 = factory.createJsonParser(jsonStringPg2); + @SuppressWarnings("unused") + JsonParser jsonParserSR = factory.createJsonParser(jsonStringSR); + JsonParser jsonParserEnv = factory.createJsonParser(jsonStringEnv); + JsonParser jsonParserPg3 = factory.createJsonParser(jsonStringPg3); + @SuppressWarnings("unused") + JsonParser jsonParserCrazy1 = factory.createJsonParser(jsonString2SpatialReferences); + @SuppressWarnings("unused") + JsonParser jsonParserCrazy2 = factory.createJsonParser(jsonString2SpatialReferences2); + JsonParser jsonParserInvalidWKID = factory.createJsonParser(jsonStringInvalidWKID); + @SuppressWarnings("unused") + JsonParser jsonParseHongKon = factory.createJsonParser(jsonStringHongKon); + JsonParser jsonParseOregon = factory.createJsonParser(jsonStringOregon); + + MapGeometry mapGeom = GeometryEngine.jsonToGeometry(jsonParserPt); + // showProjectedGeometryInfo(mapGeom); + Assert.assertTrue(mapGeom.getSpatialReference().getID() == 102100); + + MapGeometry mapGeomOregon = GeometryEngine.jsonToGeometry(jsonParseOregon); + Assert.assertTrue(mapGeomOregon.getSpatialReference().getID() == 102726); + + mapGeom = GeometryEngine.jsonToGeometry(jsonParserMpt); + Assert.assertTrue(mapGeom.getSpatialReference().getID() == 4326); + + mapGeom = GeometryEngine.jsonToGeometry(jsonParserMpt3D); + Assert.assertTrue(mapGeom.getSpatialReference().getID() == 4326); + { + Assert.assertTrue(((MultiPoint) mapGeom.getGeometry()).getPoint(0).getX() == -97.06138); + Assert.assertTrue(((MultiPoint) mapGeom.getGeometry()).getPoint(0).getY() == 32.837); + Assert.assertTrue(((MultiPoint) mapGeom.getGeometry()).getPoint(3).getX() == -97.06127); + Assert.assertTrue(((MultiPoint) mapGeom.getGeometry()).getPoint(3).getY() == 32.832); + } + // showProjectedGeometryInfo(mapGeom); + + mapGeom = GeometryEngine.jsonToGeometry(jsonParserPl); + Assert.assertTrue(mapGeom.getSpatialReference().getID() == 4326); + // showProjectedGeometryInfo(mapGeom); + + mapGeom = GeometryEngine.jsonToGeometry(jsonParserPl3D); + { + // [[ [-97.06138,32.837,5], [-97.06133,32.836,6], + // [-97.06124,32.834,7], [-97.06127,32.832,8] ], + // [ [-97.06326,32.759], [-97.06298,32.755] ]]"; + Assert.assertTrue(((Polyline) mapGeom.getGeometry()).getPoint(0).getX() == -97.06138); + Assert.assertTrue(((Polyline) mapGeom.getGeometry()).getPoint(0).getY() == 32.837); + int lastIndex = ((Polyline) mapGeom.getGeometry()).getPointCount() - 1; + Assert.assertTrue(((Polyline) mapGeom.getGeometry()).getPoint(lastIndex).getX() == -97.06298);// -97.06153, + // 32.749 + Assert.assertTrue(((Polyline) mapGeom.getGeometry()).getPoint(lastIndex).getY() == 32.755); + int lastIndexFirstLine = ((Polyline) mapGeom.getGeometry()).getPathEnd(0) - 1; + Assert.assertTrue(((Polyline) mapGeom.getGeometry()).getPoint(lastIndexFirstLine).getX() == -97.06127);// -97.06153, + // 32.749 + Assert.assertTrue(((Polyline) mapGeom.getGeometry()).getPoint(lastIndexFirstLine).getY() == 32.832); + } + + mapGeom = GeometryEngine.jsonToGeometry(jsonParserPg); + Assert.assertTrue(mapGeom.getSpatialReference() == null); + + mapGeom = GeometryEngine.jsonToGeometry(jsonParserPg3D); + { + Assert.assertTrue(((Polygon) mapGeom.getGeometry()).getPoint(0).getX() == -97.06138); + Assert.assertTrue(((Polygon) mapGeom.getGeometry()).getPoint(0).getY() == 32.837); + int lastIndex = ((Polygon) mapGeom.getGeometry()).getPointCount() - 1; + Assert.assertTrue(((Polygon) mapGeom.getGeometry()).getPoint(lastIndex).getX() == -97.06153);// -97.06153, + // 32.749 + Assert.assertTrue(((Polygon) mapGeom.getGeometry()).getPoint(lastIndex).getY() == 32.749); + } + + mapGeom = GeometryEngine.jsonToGeometry(jsonParserPg2); + Assert.assertTrue(mapGeom.getSpatialReference().getID() == 4326); + // showProjectedGeometryInfo(mapGeom); + + mapGeom = GeometryEngine.jsonToGeometry(jsonParserPg3); + Assert.assertTrue(mapGeom.getSpatialReference().getID() == 102100); + // showProjectedGeometryInfo(mapGeom); + + // mapGeom = GeometryEngine.jsonToGeometry(jsonParserCrazy1); + // Assert.assertTrue(mapGeom.getSpatialReference().getText().equals("")); + // showProjectedGeometryInfo(mapGeom); + + mapGeom = GeometryEngine.jsonToGeometry(jsonParserEnv); + Assert.assertTrue(mapGeom.getSpatialReference().getID() == 4326); + // showProjectedGeometryInfo(mapGeom); + + try { + GeometryEngine.jsonToGeometry(jsonParserInvalidWKID); + } catch (Exception ex) { + Assert.assertTrue("Should not throw for invalid wkid", false); + } + } + + @Test + public void testMP2onCR175871() throws Exception { + Polygon pg = new Polygon(); + pg.startPath(-50, 10); + pg.lineTo(-50, 12); + pg.lineTo(-45, 12); + pg.lineTo(-45, 10); + + Polygon pg1 = new Polygon(); + pg1.startPath(-45, 10); + pg1.lineTo(-40, 10); + pg1.lineTo(-40, 8); + pg.add(pg1, false); + + SpatialReference spatialReference = SpatialReference.create(4326); + + try { + String jSonStr = GeometryEngine.geometryToJson(spatialReference, pg); + JsonFactory jf = new JsonFactory(); + + JsonParser jp = jf.createJsonParser(jSonStr); + jp.nextToken(); + MapGeometry mg = GeometryEngine.jsonToGeometry(jp); + Geometry gm = mg.getGeometry(); + Assert.assertEquals(Geometry.Type.Polygon, gm.getType()); + Assert.assertTrue(mg.getSpatialReference().getID() == 4326); + + Polygon pgNew = (Polygon) gm; + + Assert.assertEquals(pgNew.getPathCount(), pg.getPathCount()); + Assert.assertEquals(pgNew.getPointCount(), pg.getPointCount()); + Assert.assertEquals(pgNew.getSegmentCount(), pg.getSegmentCount()); + + Assert.assertEquals(pgNew.getPoint(0).getX(), pg.getPoint(0).getX(), 0.000000001); + Assert.assertEquals(pgNew.getPoint(1).getX(), pg.getPoint(1).getX(), 0.000000001); + Assert.assertEquals(pgNew.getPoint(2).getX(), pg.getPoint(2).getX(), 0.000000001); + Assert.assertEquals(pgNew.getPoint(3).getX(), pg.getPoint(3).getX(), 0.000000001); + + Assert.assertEquals(pgNew.getPoint(0).getY(), pg.getPoint(0).getY(), 0.000000001); + Assert.assertEquals(pgNew.getPoint(1).getY(), pg.getPoint(1).getY(), 0.000000001); + Assert.assertEquals(pgNew.getPoint(2).getY(), pg.getPoint(2).getY(), 0.000000001); + Assert.assertEquals(pgNew.getPoint(3).getY(), pg.getPoint(3).getY(), 0.000000001); + } catch (Exception ex) { + String err = ex.getMessage(); + System.out.print(err); + throw ex; + } + } + + @Test + public static int fromJsonToWkid(JsonParser parser) throws JsonParseException, IOException { + int wkid = 0; + if (parser.getCurrentToken() != JsonToken.START_OBJECT) { + return 0; + } + + while (parser.nextToken() != JsonToken.END_OBJECT) { + String fieldName = parser.getCurrentName(); + + if ("wkid".equals(fieldName)) { + parser.nextToken(); + wkid = parser.getIntValue(); + } + } + return wkid; + } + + @SuppressWarnings("unused") + private static void showProjectedGeometryInfo(MapGeometry mapGeom) { + System.out.println("\n"); + MapGeometry geom = mapGeom; + // while ((geom = geomCursor.next()) != null) { + + if (geom.getGeometry() instanceof Point) { + Point pnt = (Point) geom.getGeometry(); + System.out.println("Point(" + pnt.getX() + " , " + pnt.getY() + ")"); + if (geom.getSpatialReference() == null) { + System.out.println("No spatial reference"); + } else { + System.out.println("wkid: " + geom.getSpatialReference().getID()); + } + + } else if (geom.getGeometry() instanceof MultiPoint) { + MultiPoint mp = (MultiPoint) geom.getGeometry(); + System.out.println("Multipoint has " + mp.getPointCount() + " points."); + + System.out.println("wkid: " + geom.getSpatialReference().getID()); + + } else if (geom.getGeometry() instanceof Polygon) { + Polygon mp = (Polygon) geom.getGeometry(); + System.out.println("Polygon has " + mp.getPointCount() + " points and " + mp.getPathCount() + " parts."); + if (mp.getPathCount() > 1) { + System.out.println("Part start of 2nd segment : " + mp.getPathStart(1)); + System.out.println("Part end of 2nd segment : " + mp.getPathEnd(1)); + System.out.println("Part size of 2nd segment : " + mp.getPathSize(1)); + + int start = mp.getPathStart(1); + int end = mp.getPathEnd(1); + for (int i = start; i < end; i++) { + Point pp = mp.getPoint(i); + System.out.println("Point(" + i + ") = (" + pp.getX() + ", " + pp.getY() + ")"); + } + } + System.out.println("wkid: " + geom.getSpatialReference().getID()); + + } else if (geom.getGeometry() instanceof Polyline) { + Polyline mp = (Polyline) geom.getGeometry(); + System.out.println("Polyline has " + mp.getPointCount() + " points and " + mp.getPathCount() + " parts."); + System.out.println("Part start of 2nd segment : " + mp.getPathStart(1)); + System.out.println("Part end of 2nd segment : " + mp.getPathEnd(1)); + System.out.println("Part size of 2nd segment : " + mp.getPathSize(1)); + int start = mp.getPathStart(1); + int end = mp.getPathEnd(1); + for (int i = start; i < end; i++) { + Point pp = mp.getPoint(i); + System.out.println("Point(" + i + ") = (" + pp.getX() + ", " + pp.getY() + ")"); + } + + System.out.println("wkid: " + geom.getSpatialReference().getID()); + } + } + + @Test + public void testGeometryToJSON() { + Polygon geom = new Polygon(); + geom.startPath(new Point(-113, 34)); + geom.lineTo(new Point(-105, 34)); + geom.lineTo(new Point(-108, 40)); + + String outputPolygon1 = GeometryEngine.geometryToJson(-1, geom);// Test + // WKID + // == -1 + // System.out.println("Geom JSON STRING is" + outputPolygon1); + String correctPolygon1 = "{\"rings\":[[[-113,34],[-105,34],[-108,40],[-113,34]]]}"; + + assertEquals(correctPolygon1, outputPolygon1); + + String outputPolygon2 = GeometryEngine.geometryToJson(4326, geom); + // System.out.println("Geom JSON STRING is" + outputPolygon2); + + String correctPolygon2 = "{\"rings\":[[[-113,34],[-105,34],[-108,40],[-113,34]]],\"spatialReference\":{\"wkid\":4326}}"; + assertEquals(correctPolygon2, outputPolygon2); + } + + @Test + public void testGeometryToJSONOldID() throws Exception {// CR + Polygon geom = new Polygon(); + geom.startPath(new Point(-113, 34)); + geom.lineTo(new Point(-105, 34)); + geom.lineTo(new Point(-108, 40)); + String outputPolygon = GeometryEngine.geometryToJson(SpatialReference.create(3857), geom);// Test + // WKID + // == + // -1 + String correctPolygon = "{\"rings\":[[[-113,34],[-105,34],[-108,40],[-113,34]]],\"spatialReference\":{\"wkid\":102100,\"latestWkid\":3857}}"; + assertTrue(outputPolygon.equals(correctPolygon)); + JsonFactory jf = new JsonFactory(); + JsonParser jp = jf.createJsonParser(outputPolygon); + jp.nextToken(); + MapGeometry mg = GeometryEngine.jsonToGeometry(jp); + @SuppressWarnings("unused") + int srId = mg.getSpatialReference().getID(); + @SuppressWarnings("unused") + int srOldId = mg.getSpatialReference().getOldID(); + Assert.assertTrue(mg.getSpatialReference().getID() == 3857); + Assert.assertTrue(mg.getSpatialReference().getLatestID() == 3857); + Assert.assertTrue(mg.getSpatialReference().getOldID() == 102100); + } } diff --git a/src/test/java/com/esri/core/geometry/TestMathUtils.java b/src/test/java/com/esri/core/geometry/TestMathUtils.java index 0d41a739..c6d39735 100644 --- a/src/test/java/com/esri/core/geometry/TestMathUtils.java +++ b/src/test/java/com/esri/core/geometry/TestMathUtils.java @@ -1,3 +1,27 @@ +/* + Copyright 1995-2017 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ + package com.esri.core.geometry; import junit.framework.TestCase; diff --git a/src/test/java/com/esri/core/geometry/TestMultiPoint.java b/src/test/java/com/esri/core/geometry/TestMultiPoint.java index b5b7d534..cd8e8d70 100644 --- a/src/test/java/com/esri/core/geometry/TestMultiPoint.java +++ b/src/test/java/com/esri/core/geometry/TestMultiPoint.java @@ -1,3 +1,27 @@ +/* + Copyright 1995-2017 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ + package com.esri.core.geometry; import junit.framework.TestCase; diff --git a/src/test/java/com/esri/core/geometry/TestOGC.java b/src/test/java/com/esri/core/geometry/TestOGC.java index 3edf417c..50fdcf5f 100644 --- a/src/test/java/com/esri/core/geometry/TestOGC.java +++ b/src/test/java/com/esri/core/geometry/TestOGC.java @@ -1,3 +1,27 @@ +/* + Copyright 1995-2017 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ + package com.esri.core.geometry; import junit.framework.TestCase; diff --git a/src/test/java/com/esri/core/geometry/TestOffset.java b/src/test/java/com/esri/core/geometry/TestOffset.java index 1bd18b9a..385e3504 100644 --- a/src/test/java/com/esri/core/geometry/TestOffset.java +++ b/src/test/java/com/esri/core/geometry/TestOffset.java @@ -1,3 +1,27 @@ +/* + Copyright 1995-2017 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ + package com.esri.core.geometry; import com.esri.core.geometry.OperatorOffset.JoinType; diff --git a/src/test/java/com/esri/core/geometry/TestPoint.java b/src/test/java/com/esri/core/geometry/TestPoint.java index 0140198a..c2e8bd2f 100644 --- a/src/test/java/com/esri/core/geometry/TestPoint.java +++ b/src/test/java/com/esri/core/geometry/TestPoint.java @@ -1,3 +1,27 @@ +/* + Copyright 1995-2017 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ + package com.esri.core.geometry; import java.util.Random; diff --git a/src/test/java/com/esri/core/geometry/TestPolygon.java b/src/test/java/com/esri/core/geometry/TestPolygon.java index 8b918e9c..2c4b0b4f 100644 --- a/src/test/java/com/esri/core/geometry/TestPolygon.java +++ b/src/test/java/com/esri/core/geometry/TestPolygon.java @@ -1,3 +1,27 @@ +/* + Copyright 1995-2017 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ + package com.esri.core.geometry; import java.io.IOException; diff --git a/src/test/java/com/esri/core/geometry/TestPolygonUtils.java b/src/test/java/com/esri/core/geometry/TestPolygonUtils.java index 16bc5215..2967d2f7 100644 --- a/src/test/java/com/esri/core/geometry/TestPolygonUtils.java +++ b/src/test/java/com/esri/core/geometry/TestPolygonUtils.java @@ -1,3 +1,27 @@ +/* + Copyright 1995-2017 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ + package com.esri.core.geometry; import junit.framework.TestCase; diff --git a/src/test/java/com/esri/core/geometry/TestProximity2D.java b/src/test/java/com/esri/core/geometry/TestProximity2D.java index f06c8ce9..9f6cd184 100644 --- a/src/test/java/com/esri/core/geometry/TestProximity2D.java +++ b/src/test/java/com/esri/core/geometry/TestProximity2D.java @@ -1,3 +1,27 @@ +/* + Copyright 1995-2017 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ + package com.esri.core.geometry; import junit.framework.TestCase; diff --git a/src/test/java/com/esri/core/geometry/TestQuadTree.java b/src/test/java/com/esri/core/geometry/TestQuadTree.java index 6dde9220..9c4800ad 100644 --- a/src/test/java/com/esri/core/geometry/TestQuadTree.java +++ b/src/test/java/com/esri/core/geometry/TestQuadTree.java @@ -1,3 +1,27 @@ +/* + Copyright 1995-2017 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ + package com.esri.core.geometry; import java.util.ArrayList; diff --git a/src/test/java/com/esri/core/geometry/TestRasterizedGeometry2D.java b/src/test/java/com/esri/core/geometry/TestRasterizedGeometry2D.java index a1ff65fc..c8c835be 100644 --- a/src/test/java/com/esri/core/geometry/TestRasterizedGeometry2D.java +++ b/src/test/java/com/esri/core/geometry/TestRasterizedGeometry2D.java @@ -1,3 +1,27 @@ +/* + Copyright 1995-2017 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ + package com.esri.core.geometry; import junit.framework.TestCase; diff --git a/src/test/java/com/esri/core/geometry/TestRelation.java b/src/test/java/com/esri/core/geometry/TestRelation.java index 041908cf..4ecbf514 100644 --- a/src/test/java/com/esri/core/geometry/TestRelation.java +++ b/src/test/java/com/esri/core/geometry/TestRelation.java @@ -1,3 +1,27 @@ +/* + Copyright 1995-2017 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ + package com.esri.core.geometry; import java.io.IOException; diff --git a/src/test/java/com/esri/core/geometry/TestSerialization.java b/src/test/java/com/esri/core/geometry/TestSerialization.java index 269b0879..8cdb4ad9 100644 --- a/src/test/java/com/esri/core/geometry/TestSerialization.java +++ b/src/test/java/com/esri/core/geometry/TestSerialization.java @@ -1,3 +1,27 @@ +/* + Copyright 1995-2017 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ + package com.esri.core.geometry; import java.io.ByteArrayInputStream; diff --git a/src/test/java/com/esri/core/geometry/TestSimplify.java b/src/test/java/com/esri/core/geometry/TestSimplify.java index 47a741c1..944d271d 100644 --- a/src/test/java/com/esri/core/geometry/TestSimplify.java +++ b/src/test/java/com/esri/core/geometry/TestSimplify.java @@ -1,3 +1,27 @@ +/* + Copyright 1995-2017 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ + package com.esri.core.geometry; //import java.io.FileOutputStream; diff --git a/src/test/java/com/esri/core/geometry/TestSpatialReference.java b/src/test/java/com/esri/core/geometry/TestSpatialReference.java index 8d21712f..c002f977 100644 --- a/src/test/java/com/esri/core/geometry/TestSpatialReference.java +++ b/src/test/java/com/esri/core/geometry/TestSpatialReference.java @@ -1,3 +1,27 @@ +/* + Copyright 1995-2017 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ + package com.esri.core.geometry; import junit.framework.TestCase; diff --git a/src/test/java/com/esri/core/geometry/TestTouch.java b/src/test/java/com/esri/core/geometry/TestTouch.java index b677ba7d..8be45c11 100644 --- a/src/test/java/com/esri/core/geometry/TestTouch.java +++ b/src/test/java/com/esri/core/geometry/TestTouch.java @@ -1,3 +1,27 @@ +/* + Copyright 1995-2017 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ + package com.esri.core.geometry; import junit.framework.TestCase; diff --git a/src/test/java/com/esri/core/geometry/TestTreap.java b/src/test/java/com/esri/core/geometry/TestTreap.java index 3bd20606..e1f2b5b5 100644 --- a/src/test/java/com/esri/core/geometry/TestTreap.java +++ b/src/test/java/com/esri/core/geometry/TestTreap.java @@ -1,3 +1,27 @@ +/* + Copyright 1995-2017 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ + package com.esri.core.geometry; import junit.framework.TestCase; diff --git a/src/test/java/com/esri/core/geometry/TestUnion.java b/src/test/java/com/esri/core/geometry/TestUnion.java index ad06dbbc..55392e39 100644 --- a/src/test/java/com/esri/core/geometry/TestUnion.java +++ b/src/test/java/com/esri/core/geometry/TestUnion.java @@ -1,3 +1,27 @@ +/* + Copyright 1995-2017 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ + package com.esri.core.geometry; import junit.framework.TestCase; diff --git a/src/test/java/com/esri/core/geometry/TestWKBSupport.java b/src/test/java/com/esri/core/geometry/TestWKBSupport.java index f0f4752a..a55252fb 100644 --- a/src/test/java/com/esri/core/geometry/TestWKBSupport.java +++ b/src/test/java/com/esri/core/geometry/TestWKBSupport.java @@ -1,3 +1,27 @@ +/* + Copyright 1995-2017 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ + package com.esri.core.geometry; import java.io.IOException; diff --git a/src/test/java/com/esri/core/geometry/TestWkbImportOnPostgresST.java b/src/test/java/com/esri/core/geometry/TestWkbImportOnPostgresST.java index ccac6c5f..95c55d2b 100644 --- a/src/test/java/com/esri/core/geometry/TestWkbImportOnPostgresST.java +++ b/src/test/java/com/esri/core/geometry/TestWkbImportOnPostgresST.java @@ -1,3 +1,27 @@ +/* + Copyright 1995-2017 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ + package com.esri.core.geometry; import java.nio.ByteBuffer; diff --git a/src/test/java/com/esri/core/geometry/TestWkid.java b/src/test/java/com/esri/core/geometry/TestWkid.java index cd249233..9bac7c5b 100644 --- a/src/test/java/com/esri/core/geometry/TestWkid.java +++ b/src/test/java/com/esri/core/geometry/TestWkid.java @@ -1,3 +1,27 @@ +/* + Copyright 1995-2017 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ + package com.esri.core.geometry; import static org.junit.Assert.*; diff --git a/src/test/java/com/esri/core/geometry/TestWktParser.java b/src/test/java/com/esri/core/geometry/TestWktParser.java index db40cdff..71ce5b1d 100644 --- a/src/test/java/com/esri/core/geometry/TestWktParser.java +++ b/src/test/java/com/esri/core/geometry/TestWktParser.java @@ -1,3 +1,27 @@ +/* + Copyright 1995-2017 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ + package com.esri.core.geometry; import static org.junit.Assert.*; diff --git a/src/test/java/com/esri/core/geometry/Utils.java b/src/test/java/com/esri/core/geometry/Utils.java index 9bbe51c0..d9923281 100644 --- a/src/test/java/com/esri/core/geometry/Utils.java +++ b/src/test/java/com/esri/core/geometry/Utils.java @@ -1,3 +1,27 @@ +/* + Copyright 1995-2017 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ + package com.esri.core.geometry; public class Utils { From 43fbcaaad1ebdfb9f1c832335c3acee7f8a2943c Mon Sep 17 00:00:00 2001 From: Sergey Tolstov Date: Sat, 12 Aug 2017 00:06:48 -0700 Subject: [PATCH 140/196] rename equals to Equals (#143) --- .../com/esri/core/geometry/ogc/OGCCurve.java | 1 - .../esri/core/geometry/ogc/OGCGeometry.java | 18 +++++++++--- .../esri/core/geometry/ogc/OGCMultiPoint.java | 2 -- .../com/esri/core/geometry/ogc/OGCPoint.java | 1 - .../java/com/esri/core/geometry/TestOGC.java | 28 ++++++++++++------- 5 files changed, 32 insertions(+), 18 deletions(-) diff --git a/src/main/java/com/esri/core/geometry/ogc/OGCCurve.java b/src/main/java/com/esri/core/geometry/ogc/OGCCurve.java index 30f50dd0..0755dc2e 100644 --- a/src/main/java/com/esri/core/geometry/ogc/OGCCurve.java +++ b/src/main/java/com/esri/core/geometry/ogc/OGCCurve.java @@ -25,7 +25,6 @@ package com.esri.core.geometry.ogc; import com.esri.core.geometry.MultiPoint; -import com.esri.core.geometry.Point; public abstract class OGCCurve extends OGCGeometry { public abstract double length(); diff --git a/src/main/java/com/esri/core/geometry/ogc/OGCGeometry.java b/src/main/java/com/esri/core/geometry/ogc/OGCGeometry.java index 716ee745..7bcc17c8 100644 --- a/src/main/java/com/esri/core/geometry/ogc/OGCGeometry.java +++ b/src/main/java/com/esri/core/geometry/ogc/OGCGeometry.java @@ -24,7 +24,6 @@ package com.esri.core.geometry.ogc; -import java.io.IOException; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.Arrays; @@ -205,16 +204,27 @@ public boolean isMeasured() { abstract public OGCGeometry boundary(); /** - * OGC equals - * + * OGC equals. Performs topological comparison with tolerance. + * This is different from equals(Object), that uses exact comparison. */ - public boolean equals(OGCGeometry another) { + public boolean Equals(OGCGeometry another) { + if (this == another) + return true; + + if (another == null) + return false; + com.esri.core.geometry.Geometry geom1 = getEsriGeometry(); com.esri.core.geometry.Geometry geom2 = another.getEsriGeometry(); return com.esri.core.geometry.GeometryEngine.equals(geom1, geom2, getEsriSpatialReference()); } + @Deprecated + public boolean equals(OGCGeometry another) { + return Equals(another); + } + public boolean disjoint(OGCGeometry another) { com.esri.core.geometry.Geometry geom1 = getEsriGeometry(); com.esri.core.geometry.Geometry geom2 = another.getEsriGeometry(); diff --git a/src/main/java/com/esri/core/geometry/ogc/OGCMultiPoint.java b/src/main/java/com/esri/core/geometry/ogc/OGCMultiPoint.java index 1b0473b8..77258957 100644 --- a/src/main/java/com/esri/core/geometry/ogc/OGCMultiPoint.java +++ b/src/main/java/com/esri/core/geometry/ogc/OGCMultiPoint.java @@ -27,13 +27,11 @@ import java.nio.ByteBuffer; import com.esri.core.geometry.Geometry; -import com.esri.core.geometry.GeometryCursor; import com.esri.core.geometry.GeometryEngine; import com.esri.core.geometry.MultiPoint; import com.esri.core.geometry.Operator; import com.esri.core.geometry.OperatorExportToWkb; import com.esri.core.geometry.OperatorFactoryLocal; -import com.esri.core.geometry.OperatorUnion; import com.esri.core.geometry.Point; import com.esri.core.geometry.SpatialReference; import com.esri.core.geometry.WkbExportFlags; diff --git a/src/main/java/com/esri/core/geometry/ogc/OGCPoint.java b/src/main/java/com/esri/core/geometry/ogc/OGCPoint.java index 98b2aaf9..7e246a6e 100644 --- a/src/main/java/com/esri/core/geometry/ogc/OGCPoint.java +++ b/src/main/java/com/esri/core/geometry/ogc/OGCPoint.java @@ -26,7 +26,6 @@ import java.nio.ByteBuffer; -import com.esri.core.geometry.GeometryCursor; import com.esri.core.geometry.GeometryEngine; import com.esri.core.geometry.MultiPoint; import com.esri.core.geometry.Operator; diff --git a/src/test/java/com/esri/core/geometry/TestOGC.java b/src/test/java/com/esri/core/geometry/TestOGC.java index 50fdcf5f..ca2bcf6d 100644 --- a/src/test/java/com/esri/core/geometry/TestOGC.java +++ b/src/test/java/com/esri/core/geometry/TestOGC.java @@ -82,28 +82,36 @@ public void testPolygon() throws Exception { OGCLineString ls = p.exteriorRing(); // assertTrue(ls.pointN(1).equals(OGCGeometry.fromText("POINT(10 -10)"))); boolean b = ls - .equals(OGCGeometry + .Equals(OGCGeometry .fromText("LINESTRING(-10 -10, 10 -10, 10 10, -10 10, -10 -10)")); assertTrue(b); OGCLineString lsi = p.interiorRingN(0); - b = lsi.equals(OGCGeometry + b = lsi.Equals(OGCGeometry .fromText("LINESTRING(-5 -5, -5 5, 5 5, 5 -5, -5 -5)")); assertTrue(b); b = lsi.equals((Object)OGCGeometry .fromText("LINESTRING(-5 -5, -5 5, 5 5, 5 -5, -5 -5)")); - assertTrue(!lsi.equals(ls)); + assertTrue(!lsi.Equals(ls)); OGCMultiCurve boundary = p.boundary(); String s = boundary.asText(); assertTrue(s.equals("MULTILINESTRING ((-10 -10, 10 -10, 10 10, -10 10, -10 -10), (-5 -5, -5 5, 5 5, 5 -5, -5 -5))")); { - OGCGeometry g2 = OGCGeometry.fromGeoJson("{\"type\": \"Polygon\", \"coordinates\": [[[1.00000001,1.00000001], [4.00000001,1.00000001], [4.00000001,4.00000001], [1.00000001,4.00000001]]]}"); - OGCGeometry.fromGeoJson("{\"type\": \"LineString\", \"coordinates\": [[1.00000001,1.00000001], [7.00000001,8.00000001]]}").intersects(g2); - OGCGeometry.fromGeoJson("{\"type\": \"LineString\", \"coordinates\": [[2.449,4.865], [7.00000001,8.00000001]]}").intersects(g2); - - OGCGeometry g3 = OGCGeometry.fromGeoJson("{\"type\": \"Polygon\", \"coordinates\": [[[1.00000001,1.00000001], [4.00000001,1.00000001], [4.00000001,4.00000001], [1.00000001,4.00000001]]]}"); - boolean bb = g2.equals((Object)g3); - assertTrue(bb); + OGCGeometry g2 = OGCGeometry.fromGeoJson( + "{\"type\": \"Polygon\", \"coordinates\": [[[1.00000001,1.00000001], [4.00000001,1.00000001], [4.00000001,4.00000001], [1.00000001,4.00000001]]]}"); + OGCGeometry + .fromGeoJson( + "{\"type\": \"LineString\", \"coordinates\": [[1.00000001,1.00000001], [7.00000001,8.00000001]]}") + .intersects(g2); + OGCGeometry + .fromGeoJson( + "{\"type\": \"LineString\", \"coordinates\": [[2.449,4.865], [7.00000001,8.00000001]]}") + .intersects(g2); + + OGCGeometry g3 = OGCGeometry.fromGeoJson( + "{\"type\": \"Polygon\", \"coordinates\": [[[1.00000001,1.00000001], [4.00000001,1.00000001], [4.00000001,4.00000001], [1.00000001,4.00000001]]]}"); + boolean bb = g2.equals((Object) g3); + assertTrue(bb); } } From 630a46e35db64d112a79fbe0ea587a57a32ac7f6 Mon Sep 17 00:00:00 2001 From: GISDev01 Date: Thu, 30 Nov 2017 13:19:42 -0500 Subject: [PATCH 141/196] Update Maven coordinates to the latest stable version of the Esri Geometry API for Java, 2.0.0 (#148) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index d6d243c1..ca345997 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,7 @@ The project is also available as a [Maven](http://maven.apache.org/) dependency: com.esri.geometry esri-geometry-api - 1.2.1 + 2.0.0 ``` From e2f5fadf29e517618ff3c5f109f64e837d43220f Mon Sep 17 00:00:00 2001 From: Sergey Tolstov Date: Mon, 4 Dec 2017 10:00:04 -0800 Subject: [PATCH 142/196] Update .travis.yml (#149) * Update .travis.yml Update to use JDKs that are available: https://docs.travis-ci.com/user/reference/trusty/#JVM-(Clojure%2C-Groovy%2C-Java%2C-Scala)-images * Update pom.xml Update javadoc plugin version --- .travis.yml | 7 ++++--- pom.xml | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/.travis.yml b/.travis.yml index 7406d2b7..d1fa4fad 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,7 +1,8 @@ language: java jdk: - - openjdk6 - openjdk7 - - oraclejdk7 + - openjdk8 + - oraclejdk8 + - oraclejdk9 notifications: - email: false \ No newline at end of file + email: false diff --git a/pom.xml b/pom.xml index 6738b3ae..bb048091 100755 --- a/pom.xml +++ b/pom.xml @@ -104,7 +104,7 @@ 2.3.1 2.2.1 - 2.9 + 3.0.0-M1 From 3704c2205b435788573303a0698a082734ae76c4 Mon Sep 17 00:00:00 2001 From: Randall Whitman Date: Fri, 29 Dec 2017 16:14:11 -0800 Subject: [PATCH 143/196] README: update copyright to 2018 --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index ca345997..34589c2f 100644 --- a/README.md +++ b/README.md @@ -53,7 +53,7 @@ Find a bug or want to request a new feature? Please let us know by submitting a Esri welcomes contributions from anyone and everyone. Please see our [guidelines for contributing](https://github.com/esri/contributing) ## Licensing -Copyright 2013-2017 Esri +Copyright 2013-2018 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. From 1adecb86f11052e835cf1bb4ae97fd13bb49d4de Mon Sep 17 00:00:00 2001 From: Maria Basmanova Date: Mon, 5 Mar 2018 13:32:15 -0500 Subject: [PATCH 144/196] Add OGCGeometry::estimateMemorySize API (#157) --- pom.xml | 9 ++ .../core/geometry/AttributeStreamBase.java | 7 + .../core/geometry/AttributeStreamOfDbl.java | 11 ++ .../core/geometry/AttributeStreamOfFloat.java | 11 +- .../core/geometry/AttributeStreamOfInt16.java | 11 +- .../core/geometry/AttributeStreamOfInt32.java | 11 +- .../core/geometry/AttributeStreamOfInt64.java | 11 +- .../core/geometry/AttributeStreamOfInt8.java | 10 ++ .../java/com/esri/core/geometry/Envelope.java | 10 +- .../com/esri/core/geometry/Envelope2D.java | 9 +- .../java/com/esri/core/geometry/Geometry.java | 20 ++- .../java/com/esri/core/geometry/Line.java | 8 ++ .../com/esri/core/geometry/MultiPathImpl.java | 24 +++- .../com/esri/core/geometry/MultiPoint.java | 8 ++ .../esri/core/geometry/MultiPointImpl.java | 16 ++- .../java/com/esri/core/geometry/Point.java | 10 +- .../java/com/esri/core/geometry/Polygon.java | 8 +- .../java/com/esri/core/geometry/Polyline.java | 7 + .../java/com/esri/core/geometry/SizeOf.java | 127 ++++++++++++++++++ .../esri/core/geometry/SpatialReference.java | 7 +- .../core/geometry/SpatialReferenceImpl.java | 13 -- .../ogc/OGCConcreteGeometryCollection.java | 15 +++ .../esri/core/geometry/ogc/OGCGeometry.java | 46 ++++++- .../esri/core/geometry/ogc/OGCLineString.java | 10 ++ .../core/geometry/ogc/OGCMultiLineString.java | 10 +- .../esri/core/geometry/ogc/OGCMultiPoint.java | 12 +- .../core/geometry/ogc/OGCMultiPolygon.java | 9 ++ .../com/esri/core/geometry/ogc/OGCPoint.java | 12 +- .../esri/core/geometry/ogc/OGCPolygon.java | 9 ++ .../core/geometry/TestEstimateMemorySize.java | 106 +++++++++++++++ 30 files changed, 539 insertions(+), 38 deletions(-) create mode 100644 src/main/java/com/esri/core/geometry/SizeOf.java create mode 100644 src/test/java/com/esri/core/geometry/TestEstimateMemorySize.java diff --git a/pom.xml b/pom.xml index bb048091..f9e62a0c 100755 --- a/pom.xml +++ b/pom.xml @@ -100,6 +100,7 @@ 2.6.5 4.12 + 0.2 2.3.1 @@ -120,6 +121,14 @@ ${junit.version} test + + + + org.openjdk.jol + jol-core + ${jol.version} + test + diff --git a/src/main/java/com/esri/core/geometry/AttributeStreamBase.java b/src/main/java/com/esri/core/geometry/AttributeStreamBase.java index 54f03ba2..2755406a 100644 --- a/src/main/java/com/esri/core/geometry/AttributeStreamBase.java +++ b/src/main/java/com/esri/core/geometry/AttributeStreamBase.java @@ -46,6 +46,13 @@ public AttributeStreamBase() { */ public abstract int virtualSize(); + /** + * Returns an estimate of this object size in bytes. + * + * @return Returns an estimate of this object size in bytes. + */ + public abstract long estimateMemorySize(); + /** * Returns the Persistence type of the stream. */ diff --git a/src/main/java/com/esri/core/geometry/AttributeStreamOfDbl.java b/src/main/java/com/esri/core/geometry/AttributeStreamOfDbl.java index 88a639b8..75d45a76 100644 --- a/src/main/java/com/esri/core/geometry/AttributeStreamOfDbl.java +++ b/src/main/java/com/esri/core/geometry/AttributeStreamOfDbl.java @@ -26,9 +26,14 @@ package com.esri.core.geometry; import com.esri.core.geometry.VertexDescription.Persistence; + import java.nio.ByteBuffer; import java.util.Arrays; +import static com.esri.core.geometry.SizeOf.SIZE_OF_ATTRIBUTE_STREAM_OF_DBL; +import static com.esri.core.geometry.SizeOf.SIZE_OF_ATTRIBUTE_STREAM_OF_INT32; +import static com.esri.core.geometry.SizeOf.sizeOfDoubleArray; + final class AttributeStreamOfDbl extends AttributeStreamBase { private double[] m_buffer = null; @@ -173,6 +178,12 @@ public int virtualSize() { return size(); } + @Override + public long estimateMemorySize() + { + return SIZE_OF_ATTRIBUTE_STREAM_OF_DBL + sizeOfDoubleArray(m_buffer.length); + } + // @Override // public void addRange(AttributeStreamBase src, int srcStartIndex, int // count) { diff --git a/src/main/java/com/esri/core/geometry/AttributeStreamOfFloat.java b/src/main/java/com/esri/core/geometry/AttributeStreamOfFloat.java index 491ae221..95ef8c5f 100644 --- a/src/main/java/com/esri/core/geometry/AttributeStreamOfFloat.java +++ b/src/main/java/com/esri/core/geometry/AttributeStreamOfFloat.java @@ -26,10 +26,13 @@ package com.esri.core.geometry; import com.esri.core.geometry.VertexDescription.Persistence; + import java.nio.ByteBuffer; -final class AttributeStreamOfFloat extends AttributeStreamBase { +import static com.esri.core.geometry.SizeOf.SIZE_OF_ATTRIBUTE_STREAM_OF_FLOAT; +import static com.esri.core.geometry.SizeOf.sizeOfFloatArray; +final class AttributeStreamOfFloat extends AttributeStreamBase { private float[] m_buffer = null; private int m_size; @@ -145,6 +148,12 @@ public int virtualSize() { return size(); } + @Override + public long estimateMemorySize() + { + return SIZE_OF_ATTRIBUTE_STREAM_OF_FLOAT + sizeOfFloatArray(m_buffer.length); + } + @Override public int getPersistence() { return Persistence.enumFloat; diff --git a/src/main/java/com/esri/core/geometry/AttributeStreamOfInt16.java b/src/main/java/com/esri/core/geometry/AttributeStreamOfInt16.java index a7d1e175..987ffae3 100644 --- a/src/main/java/com/esri/core/geometry/AttributeStreamOfInt16.java +++ b/src/main/java/com/esri/core/geometry/AttributeStreamOfInt16.java @@ -26,10 +26,13 @@ package com.esri.core.geometry; import com.esri.core.geometry.VertexDescription.Persistence; + import java.nio.ByteBuffer; -final class AttributeStreamOfInt16 extends AttributeStreamBase { +import static com.esri.core.geometry.SizeOf.SIZE_OF_ATTRIBUTE_STREAM_OF_INT16; +import static com.esri.core.geometry.SizeOf.sizeOfShortArray; +final class AttributeStreamOfInt16 extends AttributeStreamBase { private short[] m_buffer = null; private int m_size; @@ -145,6 +148,12 @@ public int virtualSize() { return size(); } + @Override + public long estimateMemorySize() + { + return SIZE_OF_ATTRIBUTE_STREAM_OF_INT16 + sizeOfShortArray(m_buffer.length); + } + @Override public int getPersistence() { return Persistence.enumInt16; diff --git a/src/main/java/com/esri/core/geometry/AttributeStreamOfInt32.java b/src/main/java/com/esri/core/geometry/AttributeStreamOfInt32.java index 6ece2a71..1939bb0f 100644 --- a/src/main/java/com/esri/core/geometry/AttributeStreamOfInt32.java +++ b/src/main/java/com/esri/core/geometry/AttributeStreamOfInt32.java @@ -26,11 +26,14 @@ package com.esri.core.geometry; import com.esri.core.geometry.VertexDescription.Persistence; + import java.nio.ByteBuffer; import java.util.Arrays; -final class AttributeStreamOfInt32 extends AttributeStreamBase { +import static com.esri.core.geometry.SizeOf.SIZE_OF_ATTRIBUTE_STREAM_OF_INT32; +import static com.esri.core.geometry.SizeOf.sizeOfIntArray; +final class AttributeStreamOfInt32 extends AttributeStreamBase { private int[] m_buffer = null; private int m_size; @@ -158,6 +161,12 @@ public int virtualSize() { return size(); } + @Override + public long estimateMemorySize() + { + return SIZE_OF_ATTRIBUTE_STREAM_OF_INT32 + sizeOfIntArray(m_buffer.length); + } + @Override public int getPersistence() { return Persistence.enumInt32; diff --git a/src/main/java/com/esri/core/geometry/AttributeStreamOfInt64.java b/src/main/java/com/esri/core/geometry/AttributeStreamOfInt64.java index 92688ccd..99376590 100644 --- a/src/main/java/com/esri/core/geometry/AttributeStreamOfInt64.java +++ b/src/main/java/com/esri/core/geometry/AttributeStreamOfInt64.java @@ -26,10 +26,13 @@ package com.esri.core.geometry; import com.esri.core.geometry.VertexDescription.Persistence; + import java.nio.ByteBuffer; -final class AttributeStreamOfInt64 extends AttributeStreamBase { +import static com.esri.core.geometry.SizeOf.SIZE_OF_ATTRIBUTE_STREAM_OF_INT64; +import static com.esri.core.geometry.SizeOf.sizeOfLongArray; +final class AttributeStreamOfInt64 extends AttributeStreamBase { private long[] m_buffer = null; private int m_size; @@ -145,6 +148,12 @@ public int virtualSize() { return size(); } + @Override + public long estimateMemorySize() + { + return SIZE_OF_ATTRIBUTE_STREAM_OF_INT64 + sizeOfLongArray(m_buffer.length); + } + @Override public int getPersistence() { return Persistence.enumInt64; diff --git a/src/main/java/com/esri/core/geometry/AttributeStreamOfInt8.java b/src/main/java/com/esri/core/geometry/AttributeStreamOfInt8.java index a4af8ff5..a93d154a 100644 --- a/src/main/java/com/esri/core/geometry/AttributeStreamOfInt8.java +++ b/src/main/java/com/esri/core/geometry/AttributeStreamOfInt8.java @@ -26,8 +26,12 @@ package com.esri.core.geometry; import com.esri.core.geometry.VertexDescription.Persistence; + import java.nio.ByteBuffer; +import static com.esri.core.geometry.SizeOf.SIZE_OF_ATTRIBUTE_STREAM_OF_INT8; +import static com.esri.core.geometry.SizeOf.sizeOfByteArray; + final class AttributeStreamOfInt8 extends AttributeStreamBase { private byte[] m_buffer = null; @@ -152,6 +156,12 @@ public int virtualSize() { return size(); } + @Override + public long estimateMemorySize() + { + return SIZE_OF_ATTRIBUTE_STREAM_OF_INT8 + sizeOfByteArray(m_buffer.length); + } + @Override public int getPersistence() { return Persistence.enumInt8; diff --git a/src/main/java/com/esri/core/geometry/Envelope.java b/src/main/java/com/esri/core/geometry/Envelope.java index 1370ca4f..ca884b55 100644 --- a/src/main/java/com/esri/core/geometry/Envelope.java +++ b/src/main/java/com/esri/core/geometry/Envelope.java @@ -25,9 +25,11 @@ package com.esri.core.geometry; +import com.esri.core.geometry.VertexDescription.Semantics; + import java.io.Serializable; -import com.esri.core.geometry.VertexDescription.Semantics; +import static com.esri.core.geometry.SizeOf.SIZE_OF_ENVELOPE; /** * An envelope is an axis-aligned rectangle. @@ -445,6 +447,12 @@ public int getDimension() { return 2; } + @Override + public long estimateMemorySize() + { + return SIZE_OF_ENVELOPE + m_envelope.estimateMemorySize() + estimateMemorySize(m_attributes); + } + @Override public void queryEnvelope(Envelope env) { copyTo(env); diff --git a/src/main/java/com/esri/core/geometry/Envelope2D.java b/src/main/java/com/esri/core/geometry/Envelope2D.java index 172619dd..fa41db68 100644 --- a/src/main/java/com/esri/core/geometry/Envelope2D.java +++ b/src/main/java/com/esri/core/geometry/Envelope2D.java @@ -28,12 +28,14 @@ import java.io.ObjectStreamException; import java.io.Serializable; +import static com.esri.core.geometry.SizeOf.SIZE_OF_ENVELOPE2D; + /** * An axis parallel 2-dimensional rectangle. */ public final class Envelope2D implements Serializable { private static final long serialVersionUID = 1L; - + private final static int XLESSXMIN = 1; // private final int XGREATERXMAX = 2; private final static int YLESSYMIN = 4; @@ -79,6 +81,11 @@ public Envelope2D(double _xmin, double _ymin, double _xmax, double _ymax) { public Envelope2D(Envelope2D other) { setCoords(other); } + + public int estimateMemorySize() + { + return SIZE_OF_ENVELOPE2D; + } public void setCoords(double _x, double _y) { xmin = _x; diff --git a/src/main/java/com/esri/core/geometry/Geometry.java b/src/main/java/com/esri/core/geometry/Geometry.java index 68e93671..01614b14 100644 --- a/src/main/java/com/esri/core/geometry/Geometry.java +++ b/src/main/java/com/esri/core/geometry/Geometry.java @@ -25,11 +25,11 @@ package com.esri.core.geometry; -import com.esri.core.geometry.VertexDescription.Semantics; - import java.io.ObjectStreamException; import java.io.Serializable; +import static com.esri.core.geometry.SizeOf.sizeOfDoubleArray; + /** * Common properties and methods shared by all geometric objects. Geometries are * objects that define a spatial location and and associated geometric shape. @@ -150,6 +150,22 @@ static public Geometry.Type intToType(int geometryType) */ public abstract int getDimension(); + /** + * Returns an estimate of this object size in bytes. + *

+ * This estimate doesn't include the size of the {@link VertexDescription} object + * because instances of {@link VertexDescription} are shared among + * geometry objects. + * + * @return Returns an estimate of this object size in bytes. + */ + public abstract long estimateMemorySize(); + + protected static long estimateMemorySize(double[] attributes) + { + return attributes != null ? sizeOfDoubleArray(attributes.length) : 0; + } + /** * Returns the VertexDescription of this geomtry. */ diff --git a/src/main/java/com/esri/core/geometry/Line.java b/src/main/java/com/esri/core/geometry/Line.java index 4eccd513..90b08561 100644 --- a/src/main/java/com/esri/core/geometry/Line.java +++ b/src/main/java/com/esri/core/geometry/Line.java @@ -29,6 +29,8 @@ import java.io.Serializable; +import static com.esri.core.geometry.SizeOf.SIZE_OF_LINE; + /** * A straight line between a pair of points. * @@ -40,6 +42,12 @@ public Geometry.Type getType() { return Type.Line; } + @Override + public long estimateMemorySize() + { + return SIZE_OF_LINE + estimateMemorySize(m_attributes); + } + @Override public double calculateLength2D() { double dx = m_xStart - m_xEnd; diff --git a/src/main/java/com/esri/core/geometry/MultiPathImpl.java b/src/main/java/com/esri/core/geometry/MultiPathImpl.java index dce85615..54ec0a5d 100644 --- a/src/main/java/com/esri/core/geometry/MultiPathImpl.java +++ b/src/main/java/com/esri/core/geometry/MultiPathImpl.java @@ -25,10 +25,9 @@ package com.esri.core.geometry; -import com.esri.core.geometry.MultiVertexGeometryImpl.DirtyFlags; +import static com.esri.core.geometry.SizeOf.SIZE_OF_MULTI_PATH_IMPL; final class MultiPathImpl extends MultiVertexGeometryImpl { - protected boolean m_bPolygon; protected Point m_moveToPoint; protected double m_cachedLength2D; @@ -60,6 +59,27 @@ final class MultiPathImpl extends MultiVertexGeometryImpl { // Bezier, XXX, Arc, // XXX; + @Override + public long estimateMemorySize() + { + long size = SIZE_OF_MULTI_PATH_IMPL + + + (m_envelope != null ? m_envelope.estimateMemorySize() : 0) + + (m_moveToPoint != null ? m_moveToPoint.estimateMemorySize() : 0) + + (m_cachedRingAreas2D != null ? m_cachedRingAreas2D.estimateMemorySize() : 0) + + m_paths.estimateMemorySize() + + m_pathFlags.estimateMemorySize() + + (m_segmentFlags != null ? m_segmentFlags.estimateMemorySize() : 0) + + (m_segmentParamIndex != null ? m_segmentParamIndex.estimateMemorySize() : 0) + + (m_segmentParams != null ? m_segmentParams.estimateMemorySize() : 0); + + if (m_vertexAttributes != null) { + for (int i = 0; i < m_vertexAttributes.length; i++) { + size += m_vertexAttributes[i].estimateMemorySize(); + } + } + return size; + } + public boolean hasNonLinearSegments() { return m_curveParamwritePoint > 0; } diff --git a/src/main/java/com/esri/core/geometry/MultiPoint.java b/src/main/java/com/esri/core/geometry/MultiPoint.java index 8a3f6f6a..4beca5b5 100644 --- a/src/main/java/com/esri/core/geometry/MultiPoint.java +++ b/src/main/java/com/esri/core/geometry/MultiPoint.java @@ -26,6 +26,8 @@ import java.io.Serializable; +import static com.esri.core.geometry.SizeOf.SIZE_OF_MULTI_POINT; + /** * A Multipoint is a collection of points. A multipoint is a one-dimensional * geometry object. Multipoints can be used to store a collection of point-based @@ -258,6 +260,12 @@ public int getDimension() { return 0; } + @Override + public long estimateMemorySize() + { + return SIZE_OF_MULTI_POINT + m_impl.estimateMemorySize(); + } + @Override public Geometry.Type getType() { return Type.MultiPoint; diff --git a/src/main/java/com/esri/core/geometry/MultiPointImpl.java b/src/main/java/com/esri/core/geometry/MultiPointImpl.java index b3d0a4b7..bb16671f 100644 --- a/src/main/java/com/esri/core/geometry/MultiPointImpl.java +++ b/src/main/java/com/esri/core/geometry/MultiPointImpl.java @@ -26,11 +26,12 @@ import com.esri.core.geometry.VertexDescription.Semantics; +import static com.esri.core.geometry.SizeOf.SIZE_OF_MULTI_POINT_IMPL; + /** * The MultiPoint is a collection of points. */ final class MultiPointImpl extends MultiVertexGeometryImpl { - public MultiPointImpl() { super(); m_description = VertexDescriptionDesignerImpl.getDefaultDescriptor2D(); @@ -247,6 +248,19 @@ public int getDimension() { return 0; } + @Override + public long estimateMemorySize() + { + long size = SIZE_OF_MULTI_POINT_IMPL + (m_envelope != null ? m_envelope.estimateMemorySize() : 0); + + if (m_vertexAttributes != null) { + for (int i = 0; i < m_vertexAttributes.length; i++) { + size += m_vertexAttributes[i].estimateMemorySize(); + } + } + return size; + } + @Override public Geometry.Type getType() { return Type.MultiPoint; diff --git a/src/main/java/com/esri/core/geometry/Point.java b/src/main/java/com/esri/core/geometry/Point.java index 2343c5df..d96589ae 100644 --- a/src/main/java/com/esri/core/geometry/Point.java +++ b/src/main/java/com/esri/core/geometry/Point.java @@ -28,6 +28,8 @@ import java.io.Serializable; +import static com.esri.core.geometry.SizeOf.SIZE_OF_POINT; + /** * A Point is a zero-dimensional object that represents a specific (X,Y) * location in a two-dimensional XY-Plane. In case of Geographic Coordinate @@ -37,7 +39,7 @@ public class Point extends Geometry implements Serializable { //We are using writeReplace instead. //private static final long serialVersionUID = 2L; - double[] m_attributes; // use doubles to store everything (long are bitcast) + double[] m_attributes; // use doubles to store everything (long are bitcast) /** * Creates an empty 2D point. @@ -369,6 +371,12 @@ public int getDimension() { return 0; } + @Override + public long estimateMemorySize() + { + return SIZE_OF_POINT + estimateMemorySize(m_attributes); + } + @Override public void setEmpty() { _touch(); diff --git a/src/main/java/com/esri/core/geometry/Polygon.java b/src/main/java/com/esri/core/geometry/Polygon.java index cb027357..a8298077 100644 --- a/src/main/java/com/esri/core/geometry/Polygon.java +++ b/src/main/java/com/esri/core/geometry/Polygon.java @@ -27,11 +27,12 @@ import java.io.Serializable; +import static com.esri.core.geometry.SizeOf.SIZE_OF_POLYGON; + /** * A polygon is a collection of one or many interior or exterior rings. */ public class Polygon extends MultiPath implements Serializable { - private static final long serialVersionUID = 2L;// TODO:remove as we use // writeReplace and // GeometrySerializer @@ -62,6 +63,11 @@ public Geometry.Type getType() { return Type.Polygon; } + @Override + public long estimateMemorySize() { + return SIZE_OF_POLYGON + m_impl.estimateMemorySize(); + } + /** * Calculates the ring area for this ring. * diff --git a/src/main/java/com/esri/core/geometry/Polyline.java b/src/main/java/com/esri/core/geometry/Polyline.java index 4c83a147..0d842806 100644 --- a/src/main/java/com/esri/core/geometry/Polyline.java +++ b/src/main/java/com/esri/core/geometry/Polyline.java @@ -27,6 +27,8 @@ import java.io.Serializable; +import static com.esri.core.geometry.SizeOf.SIZE_OF_POLYLINE; + /** * A polyline is a collection of one or many paths. * @@ -72,6 +74,11 @@ public Geometry.Type getType() { return Type.Polyline; } + @Override + public long estimateMemorySize() { + return SIZE_OF_POLYLINE + m_impl.estimateMemorySize(); + } + /** * Returns TRUE when this geometry has exactly same type, properties, and * coordinates as the other geometry. diff --git a/src/main/java/com/esri/core/geometry/SizeOf.java b/src/main/java/com/esri/core/geometry/SizeOf.java new file mode 100644 index 00000000..86683a93 --- /dev/null +++ b/src/main/java/com/esri/core/geometry/SizeOf.java @@ -0,0 +1,127 @@ +/* + Copyright 1995-2018 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ +package com.esri.core.geometry; + +import static sun.misc.Unsafe.ARRAY_BYTE_BASE_OFFSET; +import static sun.misc.Unsafe.ARRAY_BYTE_INDEX_SCALE; +import static sun.misc.Unsafe.ARRAY_CHAR_BASE_OFFSET; +import static sun.misc.Unsafe.ARRAY_CHAR_INDEX_SCALE; +import static sun.misc.Unsafe.ARRAY_DOUBLE_BASE_OFFSET; +import static sun.misc.Unsafe.ARRAY_DOUBLE_INDEX_SCALE; +import static sun.misc.Unsafe.ARRAY_FLOAT_BASE_OFFSET; +import static sun.misc.Unsafe.ARRAY_FLOAT_INDEX_SCALE; +import static sun.misc.Unsafe.ARRAY_INT_BASE_OFFSET; +import static sun.misc.Unsafe.ARRAY_INT_INDEX_SCALE; +import static sun.misc.Unsafe.ARRAY_LONG_BASE_OFFSET; +import static sun.misc.Unsafe.ARRAY_LONG_INDEX_SCALE; +import static sun.misc.Unsafe.ARRAY_SHORT_BASE_OFFSET; +import static sun.misc.Unsafe.ARRAY_SHORT_INDEX_SCALE; + +public final class SizeOf +{ + public static final int SIZE_OF_ATTRIBUTE_STREAM_OF_FLOAT = 24; + + public static final int SIZE_OF_ATTRIBUTE_STREAM_OF_DBL = 24; + + public static final int SIZE_OF_ATTRIBUTE_STREAM_OF_INT8 = 24; + + public static final int SIZE_OF_ATTRIBUTE_STREAM_OF_INT16 = 24; + + public static final int SIZE_OF_ATTRIBUTE_STREAM_OF_INT32 = 24; + + public static final int SIZE_OF_ATTRIBUTE_STREAM_OF_INT64 = 24; + + public static final int SIZE_OF_ENVELOPE = 32; + + public static final int SIZE_OF_ENVELOPE2D = 48; + + public static final int SIZE_OF_LINE = 56; + + public static final int SIZE_OF_MULTI_PATH = 24; + + public static final int SIZE_OF_MULTI_PATH_IMPL = 112; + + public static final int SIZE_OF_MULTI_POINT = 24; + + public static final int SIZE_OF_MULTI_POINT_IMPL = 56; + + public static final int SIZE_OF_POINT = 24; + + public static final int SIZE_OF_POLYGON = 24; + + public static final int SIZE_OF_POLYLINE = 24; + + public static final int SIZE_OF_OGC_CONCRETE_GEOMETRY_COLLECTION = 24; + + public static final int SIZE_OF_OGC_LINE_STRING = 24; + + public static final int SIZE_OF_OGC_MULTI_LINE_STRING = 24; + + public static final int SIZE_OF_OGC_MULTI_POINT = 24; + + public static final int SIZE_OF_OGC_MULTI_POLYGON = 24; + + public static final int SIZE_OF_OGC_POINT = 24; + + public static final int SIZE_OF_OGC_POLYGON = 24; + + public static long sizeOfByteArray(int length) + { + return ARRAY_BYTE_BASE_OFFSET + (((long) ARRAY_BYTE_INDEX_SCALE) * length); + } + + public static long sizeOfShortArray(int length) + { + return ARRAY_SHORT_BASE_OFFSET + (((long) ARRAY_SHORT_INDEX_SCALE) * length); + } + + public static long sizeOfCharArray(int length) + { + return ARRAY_CHAR_BASE_OFFSET + (((long) ARRAY_CHAR_INDEX_SCALE) * length); + } + + public static long sizeOfIntArray(int length) + { + return ARRAY_INT_BASE_OFFSET + (((long) ARRAY_INT_INDEX_SCALE) * length); + } + + public static long sizeOfLongArray(int length) + { + return ARRAY_LONG_BASE_OFFSET + (((long) ARRAY_LONG_INDEX_SCALE) * length); + } + + public static long sizeOfFloatArray(int length) + { + return ARRAY_FLOAT_BASE_OFFSET + (((long) ARRAY_FLOAT_INDEX_SCALE) * length); + } + + public static long sizeOfDoubleArray(int length) + { + return ARRAY_DOUBLE_BASE_OFFSET + (((long) ARRAY_DOUBLE_INDEX_SCALE) * length); + } + + private SizeOf() + { + } +} diff --git a/src/main/java/com/esri/core/geometry/SpatialReference.java b/src/main/java/com/esri/core/geometry/SpatialReference.java index 44fe3912..22b0c74f 100644 --- a/src/main/java/com/esri/core/geometry/SpatialReference.java +++ b/src/main/java/com/esri/core/geometry/SpatialReference.java @@ -24,14 +24,11 @@ package com.esri.core.geometry; +import com.fasterxml.jackson.core.JsonParser; + import java.io.ObjectStreamException; import java.io.Serializable; -import com.esri.core.geometry.SpatialReference; -import com.esri.core.geometry.SpatialReferenceSerializer; -import com.esri.core.geometry.VertexDescription; -import com.fasterxml.jackson.core.JsonParser; - /** * A class that represents the spatial reference for the geometry. * This class provide tolerance value for the topological and relational operations. diff --git a/src/main/java/com/esri/core/geometry/SpatialReferenceImpl.java b/src/main/java/com/esri/core/geometry/SpatialReferenceImpl.java index 618eb6b8..25158369 100644 --- a/src/main/java/com/esri/core/geometry/SpatialReferenceImpl.java +++ b/src/main/java/com/esri/core/geometry/SpatialReferenceImpl.java @@ -25,20 +25,7 @@ package com.esri.core.geometry; import java.util.Arrays; -import java.util.HashMap; -import java.util.Map; import java.util.concurrent.locks.ReentrantLock; -import java.lang.ref.*; - -import com.esri.core.geometry.Envelope2D; -import com.esri.core.geometry.GeoDist; -import com.esri.core.geometry.GeometryException; -import com.esri.core.geometry.PeDouble; -import com.esri.core.geometry.Point; -import com.esri.core.geometry.Polyline; -import com.esri.core.geometry.SpatialReference; -import com.esri.core.geometry.SpatialReferenceImpl; -import com.esri.core.geometry.VertexDescription.Semantics; class SpatialReferenceImpl extends SpatialReference { static final boolean no_projection_engine = true; diff --git a/src/main/java/com/esri/core/geometry/ogc/OGCConcreteGeometryCollection.java b/src/main/java/com/esri/core/geometry/ogc/OGCConcreteGeometryCollection.java index 3560333c..51e171e7 100644 --- a/src/main/java/com/esri/core/geometry/ogc/OGCConcreteGeometryCollection.java +++ b/src/main/java/com/esri/core/geometry/ogc/OGCConcreteGeometryCollection.java @@ -32,11 +32,14 @@ import com.esri.core.geometry.SpatialReference; import com.esri.core.geometry.GeoJsonExportFlags; import com.esri.core.geometry.OperatorExportToGeoJson; + import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.util.ArrayList; import java.util.List; +import static com.esri.core.geometry.SizeOf.SIZE_OF_OGC_CONCRETE_GEOMETRY_COLLECTION; + public class OGCConcreteGeometryCollection extends OGCGeometryCollection { public OGCConcreteGeometryCollection(List geoms, SpatialReference sr) { @@ -104,6 +107,18 @@ public String geometryType() { return "GeometryCollection"; } + @Override + public long estimateMemorySize() + { + long size = SIZE_OF_OGC_CONCRETE_GEOMETRY_COLLECTION; + if (geometries != null) { + for (OGCGeometry geometry : geometries) { + size += geometry.estimateMemorySize(); + } + } + return size; + } + @Override public String asText() { StringBuilder sb = new StringBuilder("GEOMETRYCOLLECTION "); diff --git a/src/main/java/com/esri/core/geometry/ogc/OGCGeometry.java b/src/main/java/com/esri/core/geometry/ogc/OGCGeometry.java index 7bcc17c8..4ad748be 100644 --- a/src/main/java/com/esri/core/geometry/ogc/OGCGeometry.java +++ b/src/main/java/com/esri/core/geometry/ogc/OGCGeometry.java @@ -24,12 +24,43 @@ package com.esri.core.geometry.ogc; +import com.esri.core.geometry.Envelope; +import com.esri.core.geometry.Envelope1D; +import com.esri.core.geometry.Geometry; +import com.esri.core.geometry.GeometryCursor; +import com.esri.core.geometry.GeometryCursorAppend; +import com.esri.core.geometry.GeometryEngine; +import com.esri.core.geometry.JsonParserReader; +import com.esri.core.geometry.MapGeometry; +import com.esri.core.geometry.MapOGCStructure; +import com.esri.core.geometry.MultiPoint; +import com.esri.core.geometry.NumberUtils; +import com.esri.core.geometry.OGCStructure; +import com.esri.core.geometry.Operator; +import com.esri.core.geometry.OperatorBuffer; +import com.esri.core.geometry.OperatorConvexHull; +import com.esri.core.geometry.OperatorExportToGeoJson; +import com.esri.core.geometry.OperatorExportToWkb; +import com.esri.core.geometry.OperatorFactoryLocal; +import com.esri.core.geometry.OperatorImportFromESRIShape; +import com.esri.core.geometry.OperatorImportFromGeoJson; +import com.esri.core.geometry.OperatorImportFromWkb; +import com.esri.core.geometry.OperatorImportFromWkt; +import com.esri.core.geometry.OperatorIntersection; +import com.esri.core.geometry.OperatorSimplify; +import com.esri.core.geometry.OperatorSimplifyOGC; +import com.esri.core.geometry.OperatorUnion; +import com.esri.core.geometry.Point; +import com.esri.core.geometry.Polygon; +import com.esri.core.geometry.Polyline; +import com.esri.core.geometry.SimpleGeometryCursor; +import com.esri.core.geometry.SpatialReference; +import com.esri.core.geometry.VertexDescription; + import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.Arrays; -import com.esri.core.geometry.*; - /** * OGC Simple Feature Access specification v.1.2.1 * @@ -53,6 +84,17 @@ public int coordinateDimension() { abstract public String geometryType(); + /** + * Returns an estimate of this object size in bytes. + *

+ * This estimate doesn't include the size of the {@link SpatialReference} object + * because instances of {@link SpatialReference} are expected to be shared among + * geometry objects. + * + * @return Returns an estimate of this object size in bytes. + */ + public abstract long estimateMemorySize(); + public int SRID() { if (esriSR == null) return 0; diff --git a/src/main/java/com/esri/core/geometry/ogc/OGCLineString.java b/src/main/java/com/esri/core/geometry/ogc/OGCLineString.java index da51e2d9..464b9a7c 100644 --- a/src/main/java/com/esri/core/geometry/ogc/OGCLineString.java +++ b/src/main/java/com/esri/core/geometry/ogc/OGCLineString.java @@ -34,9 +34,13 @@ import com.esri.core.geometry.SpatialReference; import com.esri.core.geometry.WkbExportFlags; import com.esri.core.geometry.WktExportFlags; + import java.nio.ByteBuffer; +import static com.esri.core.geometry.SizeOf.SIZE_OF_OGC_LINE_STRING; + public class OGCLineString extends OGCCurve { + /** * The number of Points in this LineString. */ @@ -116,6 +120,12 @@ public String geometryType() { return "LineString"; } + @Override + public long estimateMemorySize() + { + return SIZE_OF_OGC_LINE_STRING + (multiPath != null ? multiPath.estimateMemorySize() : 0); + } + @Override public OGCGeometry locateAlong(double mValue) { throw new UnsupportedOperationException(); diff --git a/src/main/java/com/esri/core/geometry/ogc/OGCMultiLineString.java b/src/main/java/com/esri/core/geometry/ogc/OGCMultiLineString.java index 37006a16..8fa020c8 100644 --- a/src/main/java/com/esri/core/geometry/ogc/OGCMultiLineString.java +++ b/src/main/java/com/esri/core/geometry/ogc/OGCMultiLineString.java @@ -36,10 +36,12 @@ import com.esri.core.geometry.SpatialReference; import com.esri.core.geometry.WkbExportFlags; import com.esri.core.geometry.WktExportFlags; + import java.nio.ByteBuffer; -public class OGCMultiLineString extends OGCMultiCurve { +import static com.esri.core.geometry.SizeOf.SIZE_OF_OGC_MULTI_LINE_STRING; +public class OGCMultiLineString extends OGCMultiCurve { public OGCMultiLineString(Polyline poly, SpatialReference sr) { polyline = poly; esriSR = sr; @@ -75,6 +77,12 @@ public String geometryType() { return "MultiLineString"; } + @Override + public long estimateMemorySize() + { + return SIZE_OF_OGC_MULTI_LINE_STRING + (polyline != null ? polyline.estimateMemorySize() : 0); + } + @Override public OGCGeometry boundary() { OperatorBoundary op = (OperatorBoundary) OperatorFactoryLocal diff --git a/src/main/java/com/esri/core/geometry/ogc/OGCMultiPoint.java b/src/main/java/com/esri/core/geometry/ogc/OGCMultiPoint.java index 77258957..b25a948a 100644 --- a/src/main/java/com/esri/core/geometry/ogc/OGCMultiPoint.java +++ b/src/main/java/com/esri/core/geometry/ogc/OGCMultiPoint.java @@ -24,8 +24,6 @@ package com.esri.core.geometry.ogc; -import java.nio.ByteBuffer; - import com.esri.core.geometry.Geometry; import com.esri.core.geometry.GeometryEngine; import com.esri.core.geometry.MultiPoint; @@ -37,6 +35,10 @@ import com.esri.core.geometry.WkbExportFlags; import com.esri.core.geometry.WktExportFlags; +import java.nio.ByteBuffer; + +import static com.esri.core.geometry.SizeOf.SIZE_OF_OGC_MULTI_POINT; + public class OGCMultiPoint extends OGCGeometryCollection { public int numGeometries() { return multiPoint.getPointCount(); @@ -66,6 +68,12 @@ public String geometryType() { return "MultiPoint"; } + @Override + public long estimateMemorySize() + { + return SIZE_OF_OGC_MULTI_POINT + (multiPoint != null ? multiPoint.estimateMemorySize() : 0); + } + /** * * @param mp diff --git a/src/main/java/com/esri/core/geometry/ogc/OGCMultiPolygon.java b/src/main/java/com/esri/core/geometry/ogc/OGCMultiPolygon.java index 944e88d5..bed0e114 100644 --- a/src/main/java/com/esri/core/geometry/ogc/OGCMultiPolygon.java +++ b/src/main/java/com/esri/core/geometry/ogc/OGCMultiPolygon.java @@ -36,8 +36,11 @@ import com.esri.core.geometry.SpatialReference; import com.esri.core.geometry.WkbExportFlags; import com.esri.core.geometry.WktExportFlags; + import java.nio.ByteBuffer; +import static com.esri.core.geometry.SizeOf.SIZE_OF_OGC_MULTI_POLYGON; + public class OGCMultiPolygon extends OGCMultiSurface { public OGCMultiPolygon(Polygon src, SpatialReference sr) { @@ -89,6 +92,12 @@ public String geometryType() { return "MultiPolygon"; } + @Override + public long estimateMemorySize() + { + return SIZE_OF_OGC_MULTI_POLYGON + (polygon != null ? polygon.estimateMemorySize() : 0); + } + @Override public OGCGeometry boundary() { Polyline polyline = new Polyline(); diff --git a/src/main/java/com/esri/core/geometry/ogc/OGCPoint.java b/src/main/java/com/esri/core/geometry/ogc/OGCPoint.java index 7e246a6e..9db01268 100644 --- a/src/main/java/com/esri/core/geometry/ogc/OGCPoint.java +++ b/src/main/java/com/esri/core/geometry/ogc/OGCPoint.java @@ -24,8 +24,6 @@ package com.esri.core.geometry.ogc; -import java.nio.ByteBuffer; - import com.esri.core.geometry.GeometryEngine; import com.esri.core.geometry.MultiPoint; import com.esri.core.geometry.Operator; @@ -36,6 +34,10 @@ import com.esri.core.geometry.WkbExportFlags; import com.esri.core.geometry.WktExportFlags; +import java.nio.ByteBuffer; + +import static com.esri.core.geometry.SizeOf.SIZE_OF_OGC_POINT; + public final class OGCPoint extends OGCGeometry { public OGCPoint(Point pt, SpatialReference sr) { point = pt; @@ -77,6 +79,12 @@ public String geometryType() { return "Point"; } + @Override + public long estimateMemorySize() + { + return SIZE_OF_OGC_POINT + (point != null ? point.estimateMemorySize() : 0); + } + @Override public OGCGeometry boundary() { return new OGCMultiPoint(new MultiPoint(getEsriGeometry() diff --git a/src/main/java/com/esri/core/geometry/ogc/OGCPolygon.java b/src/main/java/com/esri/core/geometry/ogc/OGCPolygon.java index b27e4e48..6f7a74f2 100644 --- a/src/main/java/com/esri/core/geometry/ogc/OGCPolygon.java +++ b/src/main/java/com/esri/core/geometry/ogc/OGCPolygon.java @@ -34,8 +34,11 @@ import com.esri.core.geometry.SpatialReference; import com.esri.core.geometry.WkbExportFlags; import com.esri.core.geometry.WktExportFlags; + import java.nio.ByteBuffer; +import static com.esri.core.geometry.SizeOf.SIZE_OF_OGC_POLYGON; + public class OGCPolygon extends OGCSurface { public OGCPolygon(Polygon src, int exteriorRing, SpatialReference sr) { polygon = new Polygon(); @@ -109,6 +112,12 @@ public String geometryType() { return "Polygon"; } + @Override + public long estimateMemorySize() + { + return SIZE_OF_OGC_POLYGON + (polygon != null ? polygon.estimateMemorySize() : 0); + } + @Override public OGCGeometry locateAlong(double mValue) { // TODO Auto-generated method stub diff --git a/src/test/java/com/esri/core/geometry/TestEstimateMemorySize.java b/src/test/java/com/esri/core/geometry/TestEstimateMemorySize.java new file mode 100644 index 00000000..ba516b5f --- /dev/null +++ b/src/test/java/com/esri/core/geometry/TestEstimateMemorySize.java @@ -0,0 +1,106 @@ +package com.esri.core.geometry; + +import com.esri.core.geometry.ogc.OGCConcreteGeometryCollection; +import com.esri.core.geometry.ogc.OGCGeometry; +import com.esri.core.geometry.ogc.OGCLineString; +import com.esri.core.geometry.ogc.OGCMultiLineString; +import com.esri.core.geometry.ogc.OGCMultiPoint; +import com.esri.core.geometry.ogc.OGCMultiPolygon; +import com.esri.core.geometry.ogc.OGCPoint; +import com.esri.core.geometry.ogc.OGCPolygon; +import org.junit.Test; +// ClassLayout is GPL with Classpath exception, see http://openjdk.java.net/legal/gplv2+ce.html +import org.openjdk.jol.info.ClassLayout; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +public class TestEstimateMemorySize +{ + @Test + public void testInstanceSizes() + { + assertEquals(getInstanceSize(AttributeStreamOfFloat.class), SizeOf.SIZE_OF_ATTRIBUTE_STREAM_OF_FLOAT); + assertEquals(getInstanceSize(AttributeStreamOfDbl.class), SizeOf.SIZE_OF_ATTRIBUTE_STREAM_OF_DBL); + assertEquals(getInstanceSize(AttributeStreamOfInt8.class), SizeOf.SIZE_OF_ATTRIBUTE_STREAM_OF_INT8); + assertEquals(getInstanceSize(AttributeStreamOfInt16.class), SizeOf.SIZE_OF_ATTRIBUTE_STREAM_OF_INT16); + assertEquals(getInstanceSize(AttributeStreamOfInt32.class), SizeOf.SIZE_OF_ATTRIBUTE_STREAM_OF_INT32); + assertEquals(getInstanceSize(AttributeStreamOfInt64.class), SizeOf.SIZE_OF_ATTRIBUTE_STREAM_OF_INT64); + assertEquals(getInstanceSize(Envelope.class), SizeOf.SIZE_OF_ENVELOPE); + assertEquals(getInstanceSize(Envelope2D.class), SizeOf.SIZE_OF_ENVELOPE2D); + assertEquals(getInstanceSize(Line.class), SizeOf.SIZE_OF_LINE); + assertEquals(getInstanceSize(MultiPath.class), SizeOf.SIZE_OF_MULTI_PATH); + assertEquals(getInstanceSize(MultiPathImpl.class), SizeOf.SIZE_OF_MULTI_PATH_IMPL); + assertEquals(getInstanceSize(MultiPoint.class), SizeOf.SIZE_OF_MULTI_POINT); + assertEquals(getInstanceSize(MultiPointImpl.class), SizeOf.SIZE_OF_MULTI_POINT_IMPL); + assertEquals(getInstanceSize(Point.class), SizeOf.SIZE_OF_POINT); + assertEquals(getInstanceSize(Polygon.class), SizeOf.SIZE_OF_POLYGON); + assertEquals(getInstanceSize(Polyline.class), SizeOf.SIZE_OF_POLYLINE); + assertEquals(getInstanceSize(OGCConcreteGeometryCollection.class), SizeOf.SIZE_OF_OGC_CONCRETE_GEOMETRY_COLLECTION); + assertEquals(getInstanceSize(OGCLineString.class), SizeOf.SIZE_OF_OGC_LINE_STRING); + assertEquals(getInstanceSize(OGCMultiLineString.class), SizeOf.SIZE_OF_OGC_MULTI_LINE_STRING); + assertEquals(getInstanceSize(OGCMultiPoint.class), SizeOf.SIZE_OF_OGC_MULTI_POINT); + assertEquals(getInstanceSize(OGCMultiPolygon.class), SizeOf.SIZE_OF_OGC_MULTI_POLYGON); + assertEquals(getInstanceSize(OGCPoint.class), SizeOf.SIZE_OF_OGC_POINT); + assertEquals(getInstanceSize(OGCPolygon.class), SizeOf.SIZE_OF_OGC_POLYGON); + } + + private static int getInstanceSize(Class clazz) + { + return ClassLayout.parseClass(clazz).instanceSize(); + } + + @Test + public void testPoint() + { + testGeometry(parseWkt("POINT (1 2)")); + } + + @Test + public void testMultiPoint() + { + testGeometry(parseWkt("MULTIPOINT (0 0, 1 1, 2 3)")); + } + + @Test + public void testLineString() + { + testGeometry(parseWkt("LINESTRING (0 1, 2 3, 4 5)")); + } + + @Test + public void testMultiLineString() + { + testGeometry(parseWkt("MULTILINESTRING ((0 1, 2 3, 4 5), (1 1, 2 2))")); + } + + @Test + public void testPolygon() + { + testGeometry(parseWkt("POLYGON ((30 10, 40 40, 20 40, 10 20, 30 10))")); + } + + @Test + public void testMultiPolygon() + { + testGeometry(parseWkt("MULTIPOLYGON (((30 20, 45 40, 10 40, 30 20)), ((15 5, 40 10, 10 20, 5 10, 15 5)))")); + } + + @Test + public void testGeometryCollection() + { + testGeometry(parseWkt("GEOMETRYCOLLECTION (POINT(4 6), LINESTRING(4 6,7 10))")); + } + + private void testGeometry(OGCGeometry geometry) + { + assertTrue(geometry.estimateMemorySize() > 0); + } + + private static OGCGeometry parseWkt(String wkt) + { + OGCGeometry geometry = OGCGeometry.fromText(wkt); + geometry.setSpatialReference(null); + return geometry; + } +} From 9ba95a6b212c30fe8e0ed96bcb827414e7ddbbcb Mon Sep 17 00:00:00 2001 From: Sergey Tolstov Date: Mon, 5 Mar 2018 15:31:39 -0800 Subject: [PATCH 145/196] Stolstov/update jackson (#158) * fixed some errors in javadoc and updated jol to 0.9 * updated jackson to 2.9.4 * remove jars from DepFiles --- DepFiles/public/jackson-core-2.6.2.jar | Bin 258824 -> 0 bytes DepFiles/unittest/junit-4.12.jar | Bin 314932 -> 0 bytes build.xml | 8 +- pom.xml | 8 +- .../esri/core/geometry/CombineOperator.java | 3 +- .../java/com/esri/core/geometry/Envelope.java | 46 +++--- .../com/esri/core/geometry/Envelope1D.java | 4 +- .../com/esri/core/geometry/Envelope2D.java | 42 ++++- .../java/com/esri/core/geometry/Geometry.java | 99 +++++++----- .../esri/core/geometry/GeometryEngine.java | 8 +- .../com/esri/core/geometry/MapGeometry.java | 20 ++- .../geometry/OperatorDensifyByLength.java | 4 +- .../geometry/OperatorImportFromGeoJson.java | 4 +- .../core/geometry/OperatorIntersection.java | 8 +- .../esri/core/geometry/OperatorOffset.java | 4 +- .../java/com/esri/core/geometry/Point.java | 38 ++--- .../java/com/esri/core/geometry/Point2D.java | 4 +- .../java/com/esri/core/geometry/Polygon.java | 74 ++++----- .../java/com/esri/core/geometry/Polyline.java | 2 +- .../java/com/esri/core/geometry/SizeOf.java | 106 ++++++------- .../esri/core/geometry/SpatialReference.java | 2 +- .../esri/core/geometry/ogc/OGCGeometry.java | 2 +- .../core/geometry/TestEstimateMemorySize.java | 145 ++++++++---------- 23 files changed, 342 insertions(+), 289 deletions(-) delete mode 100644 DepFiles/public/jackson-core-2.6.2.jar delete mode 100644 DepFiles/unittest/junit-4.12.jar diff --git a/DepFiles/public/jackson-core-2.6.2.jar b/DepFiles/public/jackson-core-2.6.2.jar deleted file mode 100644 index a7d87f067340ac378230a8ccf8b4dfc9512a94c1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 258824 zcmbrkW0YoHvM!pev~AnAZQHi(O53(=+p4skm9}l1>h3-E?(^N-eQw_~$Gd*KYp#en zB38^No)t6XrGP<@0l>k*0SdBd#Q^?kAOnB^$cQKl&`8LN(#Z(ON{EUmDbvb`evJbF zBrD6ruG7PJ6W!ub!=i{=B^tky3t+qYOBV6Z395NjN*e~aC9*f;@P7G_1r-NP%{xkQ zUr%*;;LZezdJiX!%t%wx5@Mwfk*DGk!Nr!(BkkUznBiixVWvDbTVPJbIKcf30>gFf zYE7CY9igjC$FclqYmG1Al=vp1(|7MnqVlMMJAD^qJdl5u2?w7mo=@no5dNJ{qOvJ6 z{V|H%&rnDZ>-|8FmG1~~OAo95i%=YTr?))#{oca5uz9(@;oK$n+@*oI2K;=RpEr>% z(czP&GDL^m$1tXSf^e9TlD>6=CZEA&B?|m>C6K!6Y+u2TJsYlG1iHZOZ73Hzn(Ngx zTTls8P`9+~%o~pu^k5nvKd}my(anHAhEYNiKR6>-;{d{+bc+vgGQkkEMf?Hzpi3%(Ur5gwNro1lS<{=-s#|I>7 zsDwU$$u}7UhT^9N#xG^zy**T&+MA_dX*T&q-j60AV@mxKAMZqs@T2nm)FelXBrg(n zuOi?y5OnB4Ys138sUM77u7w6{yRA0pc;ZksfpGg?{uJm#f*`maE2b4clkxV+p15`s zc6qxdN#NQ-98kxPgX(Qr8C+`>KNWd$9q1_8kpVi=wwyj&jv{5o;J?n z$xDHO8=C4ca{&PWWPt(z$p6a#g!mJT{~dt9e;xz_|66$ek^B4jAHnp$L@xZF`TrJ_ zmJkw=RT7bjW3U5afDV5CfQ*D7&Zy*UP(cwwC>V4(J(2QuLyZ$|iBCSY z|KMm4{B?-s2*(92HU;5&ISLqYz+a)kF9SZ4^@E&TavnNKe6~V4Rt}q-LL^O*7!Yf$ z8x@+UzhSC-io7Y{^LieWmUx_HZs|I`=5C4R*GS zWSND@`-|Izn_l>7n+g!@ncfh|nD6A)DegBX7(t!W$*}pmn?w=hbE2sR& zMJkFib{q82-RElXCnSK3rK=`=t{U+q{1Hu{N7$~#CY;NZkv~Y-9((bMiE%s=$bGv? zpWVS?&Gm@D+^h6*43Jxpi6R+V-OvE@-AcbE#OLdw(W$HT$%M~SeCM;gm56)H|2PwV7jXO|-Qe(u#z35L}3l;cp#35gG6jG*7Cnm-^t zWDZUN(Ohv!dfh^-XA}#WK8;#TypulhUgRR}b5LdsXBnRscf`?L`dPMEFr|)uWZiGj zyT-esm3>go$c2i|$c7dXEsTO`Syx))ky}`si6kWw6GmXP0>rssfwi}__i41lUbny9 ztz{-g*Sv!4%;&(L*B94-VYr+fnJ#jbQXcSEQ7Rw5ok{p7h8fS6vecqo22d(X=aYu5 z1OzCzf<~oCw}+-+)}xw`?nQ4Ar`&M1-{0e8Ql0PoPmHhv|8t!DH3j@{abjcOYGOE&6WM@NbYT)E-;^=N;O>1djWaVUMOKW83X!0LvX8Vsc|JO1!|M4;+ z|Nob<|6{1Vqn*8pqqBvH6U}%(WB@-r!0fy4@XzBPC@_d!pmPM^XJ8{PO;avr7%O$X zbOq=8CS%}w8WSq z>|b;N6Y%*AYp1ExW(7CIK@rxR(h=_Fkz@ZbU62~zur{FcM zn}`q8uq_H2126e9Bxf;{nYqWyDKZd4QL8#JJKkQgE?X+q!BZQQRcmOS^(5zp@yXv* zZ668eE^c5RWn)~KIOKGUylwtiOvx*}&+X=zrb1)fxO0^jO(|d@$l%K-xCc0pvc}yBxdod3=%9ucsJ&adc68SfknFJfKFM| z%61UFAzyI{iEqU$M^OP7$cTKx2$F|_#Cz6oV^@WVmKk)SMCB2u=;gI zEBR4umIVA>wN6(IICjACd;0=okh>3qO1qYR!eeV?e4r4@Wi4ejcKwdQ(UnQ74CRlk za$l3EAj4b;DM)M|OIV{Fl|1@v7GmH$ToqK(VEnDscG&mpd{ne*OvFI_w3hBVL5<>c z#YkO-jioYQyg@J3j3p}2muEz!t6j|~JvC%HXp#b*kGVF@Mh>aU@L+%v+=fFUd^i>- zG2Lu}{`R5Rdhl-A@#LXRfFUv-4Pyy2f^d^A37F2b&J||PG)}qa4ffDnO-Y)Y}oEc}o9Ms6j9@c-VbY=WA2s z6v3nvTu8syaAO-0C(?H)9(54F?&Gcfi@I1WXr{i^F$`==?u;Xl-P8PsQdw-dN^k{X ztoPFvSgOO3d^|rWEpOAW=gH3l=8xPdp+*M{NmW5jCK|4L#p@NNBD`2BZv?#LnH;ae zu~zEm>29CI8B6IvZM{#AfS?fCQ@yuKpYqu{P||5%TYXWn60UYDpY6EZ9V5|KHehz> zTAB-mjT}nRm+~1d0_Q)wPqShr*Yt7 zu1(N!gnf^A=cVC>i%M*)o2DJ2rZq(F&GB4JBfTU0nSkm}VCHgtb%IO1;4X(> zm51H37W20!M+67ukG`Dwy1M-vO7C)OnYGNjT%1Gxs?e=S+fuEPW0twf0*Vko`8t$K zN*pg5SZv{PgS;7U;0&{$?{Sg*W4L+(4B?LWzFX`VlX1bH%Zw{=G+%$`ZJPTT&2)d* z@DAcXL-St~>YvH~A+}B?j;6Y5r;s z$e-CiOaEH~{wf3eUo!tqBK{+3)c=R{e{qBVNDJ1#)bf917}b^g%s z{uUekhTPE`x>56gX2ZGmx&`E2yp4~Eb@|X4HNeSrcK|8mhIb~+M$XpeYLSTv%*2EkS52vHDpaCn8_68DtpsR|0d57Shs%jkZ`&qL zs{M`H!@fC-7W2q#ElyTgpQ8jJI?6vT-5lE4;XVaqhmaT4Y<&qs77YHm+uuBw*1jC| zSmXt9Hg8Uyp(|Pj6I05L+fdA++?c*&8|_vPUmT#g!S7d^zKMQyk!UzfWW7v}mAb+@ zi*zOR5TcPoZdBq`NO9D{ig;~z0$Vz7>R8uXYoNv6z=IOYBD^Qj*v}YCj40KpX)ASz zIDtPEpkA6ziX1!F;vQ9yc=xba4BfR?4P$ZCLYo!0H}>lmF$FAEr|8zTzKRfpxap&p z+=MzyQdF}_uJX}9-(eowE!(DK&j*bum!dxd`{6NrV=}BRW!b1?qo{ub*S=r~rE0g2 ze2O8*gwd@|#adoiTF9_Mc-4_a+HR6##1048b}gNiBn|Y5!Tq=T!u^lX_S%Uk`&sof zGmH>w(^oL+ge(2X)qL46gLS1T_33bP&ZVqzcu0tNZnQ|~yi>FL8YT-lQL4*Nw=A&izQ zS=2P0KlKu_AM=8Iy5(J#a%3#l_|kk4;MBlq*8!-%)G3Lp=$fqKFNn>oDu_8*tPM?3 z8I>SxZ79$&a?GPlh_FY&T9COFjKk^f%fh<-mie_Q3#2BK>uF)5!s^^BhA)on^}Iw) zYjx7r#DhDh;+&equc}x&(@QPe^~Bk79HPm6B4biifltl#0_#Ma7%{feA0yok^@pU3 z+J7}BRYESc$;l`W3DuL>tVGynKxI;qBrR}O19x2^Jlc+p32q3^I~la zYPWwur)PLicM|Z%MWvYun}FgeIEl6``H$gFSLheh=cuqT_e!%#W}_Gw4}c>_~CKj-x?|>=8 zR2C3}CZ7D`YPgQSjM1K>hW^H6(|Sksf`s~|so!8nfZI@>MTX7%M~|^STaWn;1-H!( z*FXplIx=_qf-E#b#biIo``QKMYN{Ik0BCeGQ=)(S587dqsyXdIpeO+{@Fucg54omj z%+)z8nM{9WLTm;n`anWvV5lD~85XnD8B;a25OvuNWTptHV0*v)M?3+c)X}W;f9e5A z0ma)>!^hTpyat>YI+ePmnMz+dd`zz*b4C2>Z`bxdjyYapM5u*^7@JWHcR(ZS1iCfO zi`Th|n=_9QAT#`x*FIoBYDP`3YgIGM;%`npNRAj=%yEUzi5oopI6WnxoN}{J$0{bm zw5)=6UDGa1Yy-fxZJ>%_t|>CN^%O7YQbO8lR9 zHHso}ERB&;)P6Rb2(7i=xiBX(86_~36Xhw`YCHuXj7v*L`XirTg40N~G1OHoNK1Q~ zGS&2*ub?9ahQdnuFz4-{l4Scn>c3!CPB;6hL;d&XqEK^M9pwu3L2K3apv}4=7vKjl z&3s5H>sXIJICWbOt5)_&9RzMZNFBs}(di+5WE0v#er*xbLcJco3;Mo}?_5KdIPlW$ z+%|4%Uj~ASK>O4^;g_{QcjnyS{!~2I`U*M*(B7-W``OuY1;E|B1eUk__8Tm3>CHne zmo1=xbgA|&W!rx(cF=2UpPcs^Kk80`i2Syah@9s6)*8eFOyTQ+J5{gR2bp#Sob+Bz z0oEX*QsUmq_tAC|fy=kLUg9`B)8H){YnY(-YGwI)+1&l1`HY92_}3*@gD+p?g&s8m;;fNMRb`@>ba8!JT0x!!^(Y*6(7=n3Z1Da2lVeG~pA#>=I&F}8`U^TS zwfASVA4om^RJSzR(NO7Uuhtor^yv=E{AM4R&J7SO2DHMnB$+E<1`X{J(;Xfqzck=7 zw@A#oqs~(r$@e|gV3{`S2dLV&gA_NZ4{MvJxf}A;bCWdVnFvy)93@u;7#3h7 zhUNkdalt^Lc6AF>%V>Am)Pmm@38YQZtio0E+lxA>n6XNz=ct?km9$Yhenvx;Rgq4p zgVo86c)HMex!?t~;mEWBOF58jJkiPqc50k?X-wGF(LUG548}aGgNeNd75WU!@$R}) z{`nbrQHCvH^JT7{NzMIIX!7UTjsZ~2a^=jGc7oQ((HPPbPy?wwhL7?3H7AsOBUa2E z;xdvF$`0$g`U1n+Beru)#Frj;;Sl#~pah`N%?I~-8u04_wIP1x*1{k(L2_jID(DE; zlx^$UI922jL&BuOWxn1Ff;Jx1shy;1ZUhJDV}-pPt^={&~*dMBpJ4IoZB_! zp!TROf6_pObs45O;VoGkk*}H4?&jqP@z8@FadXB;7VhV%EwqNh8#ttG!gY zvs;-VKDfeRJj^~0=A=){pg86*FY09j*E32SbY1wp8kpj9w4`Dtt;Dj-j6uwSAsWoi zW|Fn>e7t7Dc{~s8>I}f*3^bjm!Jc`vgKc}`Sx!pEUKpcO5VKG?FBp+^8Q)J)r2z3< zjQIp{`TOp+A+|{1NufM<&h{7?E4T4#eAt6S>i*kcdW%D0y5o{vtBV25Feu4x^ey}z}kVPmM(=&G11K0 zk)0BHaQ}IAl^Y`!T|>tSsW63#-kgO@Gz`fIhHNC=yr`Kya-E%Hc^}A9j4V4CaGjh* zcf^rxPuN#Uhe*j}cFt#3B3Xb;uLd^J2r9<;-mG(%EdV4DMXX z3lEsX2cp{&F0xvi50lARblyDE;(-H8`I04?8)=1JCm7n|>(XyZny1r1nGU^HHElM! z-z3Y>xKI_<_V14f%9l@3Z#?hvV~aC?9>khBWaRdAYtCJxkKU*!9~@#^RX$h=*7_9T za8947DIpZ-5QndwM(G@wf>{=d={3sA$Ya)K4ntfIhwK1Zdf=GMpICiPH3HP}PdtfK zmsB%k(Ft}swX`m^iYu^kfY1r0I+g%+T-oV9do)_F|9t@y$BBU;Uvg9DLI7T3C?+GhQ!O=-+0Ko z_Ht2FJ8G1z&6Y5;iRFk%|J3@`AY1z&S_TS^0U@Nuf(tF$ZqS>f!E#wrIe) zeSHghh4YEyCP3t&ahEr;%S0BL<+n-$XT|F>TYJ@@P=Rqcv>|`B z(v(kXlap%~(?0mJKNLiJ5cBm5Cm}~{HZ#uQhV^RW6PIP-X~n*AX7uH{>5jAVHg)gd z2FFE)Je$nPa*KXhj`}x*#;LN@hW5~y#vP(EpZCGYg~jNa-aQRZJi1vh_h8;3G3??( z=QQpIh|`ZEN;Tw+k~WKwy?l-cNtX!G`(#4RBIE_d;XIUaVn_;-#hRq~c1a8$RfpAscVuW=A8>CxpovuQc*|$cGEt zTRJFJL#IdQvG?0cNxdE1$(aLw;8CAL0_q*PS3%s0O|*#ZK$?bm1BeMf85@$)=^X0u zy{smS$%3pT5spv_)=U3XKCRq6pU4qOxu#SJ=Q}OfU|u#1j{DCV9$)R>z<)dFK;c$g!bboA_$K=2^3z{y zFLeJw?ZxT8iZ3cQN|CB93?pf8iXIw-ccvf`O){rIR5i4b z{==)jr_x^{egeJgtK-ty61=V@d_{bPPn#&o!c0abc&sJB7)9I|^rM9A#skBxx*X6}suhC*g|^Aq3~5y2}}mfh;I;63}19<*G0><1j+yc52N#9op;W zix2wcs%ilS?fFr$hld@KzCm-NK9Hx<1Q>Es3=$Eh7}QXT5&g;I!u$1Y$TUNYk-^2o z#DO-FODaYo)LlK?!;y>Q$WMZbOUcfSn#yM!?PV0)8=4Z!lla7~$*M*&HuO9j^bLR#!K?& zZRo?4NUDDq*NDa&>?lv#D=m#nX5V11X*TLA8d8egcIkcSTpb1B5y@_j(2&_WHUyjl z^|RL<{PEiPQgZ-3Y%{u-^mw1wBaS>L8;;*??N1mZ2iT7Gq`5 zmQVxJow_(nwWN-!CW%zpK6c#3ZCIeemU010i}{Aue)jAtVtmh+D9T zn=Z^1H=rUl*`}x2ECt1^jt!ZpfpkUaL$vEQj4t;5L0YfyxeMzPeZ+-+6Tch^7y9Ng z{~lnQB++YQ)69`-ZN37bTSzgEeV_dcb=8hk_-EOaa)(1|+5PNK>1%{mrIWdq`@Gj+ z{OvreQ1$HRTBQS`koigk7cbud zziCK)hj(}{CTQ3YDoz`#{yWB?S*pIwes8^U(3Q3w*1B zY8O8nqKi~C3a3-06<jhvDVEFU|zUwpbR+S=2dlE!3y zfPpNOw#4?Bv~FD1ca-Riq0(Lr45%!EJ(D~i(`avrti%`OVokNs*Fj;?4PRR9Mh0zP z_cVkGsOiJ$&A(Fep`BP0`iYo4sZ(Qz=LIL8W0yFyKgKx+(wYDv1Q94`FnF3B=My<- zYo;msM%7N8XE7s`eEl7yz+wZ#~08|v1=bf*S@-N_w@SY;VvN43+niAJy;B$v++dI6yL{Gl^s)&Upmhl6ttFu!c ztb^!fs;O2>2YOr?XI{sHgf|3n+VA#kgB3W=rA(xXtC1c@!QzjEFlgm08)rn`Y5ivf znJ&rI@qq+gXFpA&blX5`>$K3l|9)KH*oT%``v;BJ|6Kpt6*T`Ium9l+B_nea8-ss% zLMDpgk0+3Yk4MwW@X6i5njQL=d6V@K=x%g;4GmeTGF2WdQE~f(!Xdyphm9iG?mhW( z0G825!i=E7_qDw%Vh!E>_@E40)NQ4D8Yt^H|GBBU`E8&$j;cp*6bhVCM)YGA*rGJvUX0X&0*9wARik0NO|9YMuO(r{D?6~*!8mg z>Z2dlcW_EI$S{b_6oKKBlz(z%LnSGKlI>3Mv1QY;-GA0(@iOM>Wj*&@E@#`kK-KWr zF30oqHIV=+Oj_Ig((Exq1#^3tVr@9F$q zgVPqR^PVReMhfnhBjp~)sWs%`X@-0jqeHU}6Uzkl5JC5br1Rum+blR7;%$6St-K|T zM$!HlDOTJd=M(ejqI}BAWa@#kZQG26j0*=5MSt$UY9;OA~^iLu@qSvqR^bjj2teiYRGw6)sqcBSz+WRJA3Sp82kccB#zrHV<;dl z0Zl;uRc5N40&E=hDHXTb4_aB|99Ji42_~dUu`V!AX@L`^VTa#+Wi!@>egq%wleW$) z7)*K_G@?vOjM(Ih0vD|N4*oZ$xxMv{X#b~&3Los>As6$%N3MT5VPJd+qXg$=^a6%Mmc!!v zmZuV(r$PBAIu2L&lidqhS3{0lV<2EVe7pOM*R&UJ&V5$m;d8IA2gD8{FAV>XAiM`M zaaM92`Vf2)K8D@RK9O(PX3G#xq&Cef!{p)U=3O|R0_}uvlzsUt!oH)%0HlyQ1?};K zNO#`B5@>gt&bSq{Vn1^R&8Tr4$_8t8mI*VaY2a>wX7zOmTeRqc^VOB+sZC+WLfOWt zoe5eC6e1HOOFZ4Z35eLSEmJR9_*A*GFHTBkCgzX+unb1%U zE*ot|MKpt3l2$UMrgkQXWVOhu=PHAmELoUzJ1Ph^Mk@z)-xd666)qEul~cj2MvDdT za^yo828b}D7(@o?tPrBhHJCcynJM)mG2Oy;E@opdn>0d|;@ne<7n)8fyNe8$qHYA<*oN3$K%9raEz9Fw20a(%JD{0jVQNhT z!YxJ-*h%+#d$^jo>2;7@%dm3iFkaaDf1k4lJ4j7N-s@R}+; zf=3LW;|;lgRE+a`1e8IVb?e)U_VeTE&sOOJt9fSjm=E#PE|Rzt*Sj+V!NZYwwgHRh zrNbG7uJPy>xjQO;6H5?YLW9E(ha-UemS2*+*8!{e0#^RCy`Qwn;k?Y==Qo85VLqMG zK%{V+`CT~MbN!RlgX8lhPTewe+btAi^9{w2gc@qk>48@uxAHO4hUUQ1?qywbb?HaP z#(LfZR`~$$TT?_^Y@F)tB~g`1$1_7251^NKUD2hB{G3>B3_7dDjONh81K_sCWG7?j zg7*}KbM4PSTfTl@p?jX5-13^f=Dun_&cS#a^27560Z^6DWlP#4 zVAGD=x9tLW6 ziqD3(cQnZ88?@$~lxdZ=GHTqVIuhGyKpS(sL)P|PgvA$}JKcZoKKuA_z{K&Qam9BW z!r0}Hx1AQ6JDR!a&a{;fsJBDb+OaFx#e!xxxbgZFxw*L;BDa_A?SjVU*17BQ`6RWu zxi5-woph_qwzv=D;f{X2KPqO-eHP;4&bN)dZyJy(VH@Lg{}T&uh@!_c43PT*pbhcm z2>}DoMJO`nCJ3u$gzo-l?aigprnkySZF@cr-f?E)rgwIxo&7xs+xEec7We%|`wPwo z?Y3`h=C#v3u&wW}so1vH`Y1k!!B@`NT*v#OD7kt}-;t>q6YmU|S17iZo7ByZ2)u92 zk?l4EbiawI>$cbKsF^l`pEHj4T(&1;c$@SQGh;7Znf4!ZFa{q;k)LL^Uwb3Hd)sZV zUbGZ1c$06r)?0qDuW_yZC?c72Q^6>u_PZ zJ-RVpnV)Vi|6cQ#-|X4;n$<$P`4M{7QE1?2Zw@^0F-21y&2!oLriGR1=HmMJ*xJH; zb#`ibYG-4&IWHr<4nJNxdSQKWW_#KItHJ(xpOO-jQX^Vmh+_bMa(1z~w7s#qwJ^W^ zTX4~xUQnl#j#-iIBwBdsCO@A4w{Pt=KQVIEeei&#PAh5aH{x~APs=>jx4{a#O*HvI zoiqFa>_p?vkiiPKJf4I5ALjbDt$yU>%$(MI75xwFRj|u0p+5?RRbVOwa@XS^*VPyC z%B(Q4u#M3- z8`Xys4_JJ@j}%tOQeLqIHJ|=sottzT?E*%iQou1v6ktb+;^u@9eKvJTirR~=00uYF zG3qb#6TW;D`!H@D(Wgg#6diL2AG1#EF>+!q40*a#^O-xE{dp!-CJ9=?#Hxs7GJ75w zGO%y>2%nn%Sy&{?g6*Za$@izocQ@a*I#}R~SDsB;xtkI;!!q>UzA$m9!zkY2o|90~fHl7hdKsjwgKL5z)% zy&~n2kX^$jsQovdGS`UcQ~9n=R~tM3jpxbZd_bYs?JNe)li0kv5tdH#87e;f5{6dH zoH#*)i`&RSBf(`@P3iY=8YYg|jWW%IHnJ<+l3N~)d)$Y2o(7S;qKTtE4e$Bn zqEI0-!7iKY3wv`+tZZe=5NBKeU_VhWmt>f$yV!6Y2L!YS_Wk5O{&KOXpnsrr3~?mP zVsd{!QI#(95t-dK25fZ2?uomZJO3ej#p@t$!S(%*R?k3Sz~(W$hmcV&^I}|OU}8&x z&v_(;wOy=1gKPHJehVSXCKhDeTg9jc*s$Os#`yc;+uRgirM-oaGiB7-a;R}lW*R2< zE74_}kzF7mWi z?7Q+p&At?pU}>&Pp(&$yb8}(g6K}+DrAUkVgFsK$fI8wwLL?1nt6?-q*tgK8PY=5i zfjq}`jz1m3Ne=Wf#;r#DbAb(&lxkkGeu0zG9zqM zhGLYDl8uE)!Hcjgf+#wSgt5mXBiacNYjV%Ffrx|yub4Sjl&(4E%v3n3LlpNv?| z!ix$R*CcO3PY+J3%5O1C$sU#&96z%N0(~0HDw3MP>y=_0~1E+-G6EfYQW>{xH) zKSqZFgN|BQKwwo}W1Psk>OcpS6Q8P5uH>cc8tR9N32-lqa@ zW^S*hxj$tR3=BL-FUtEV*|BX;S=onA`o5abPhQ$IXU~P?e1U~!>^zmBJ(a#dD3H;J zP$d7nzskg^bru^%9%YYjO2ZQb?p3-!k65&?CE7usRzEYRv0lckuHi@w^EOW%%f59$ zkCure$`wMNTSBa80@{*4F3Dbb$}6?$PQ^?qHQaPKd|}OK?d#`_j@6*If)QFU`Dv>s z2;dHtcCm~qG!nF6ruxIARd-y$kdO>K!SiAY;ewo2$nm(mrf(_Dxyo={^~@ zU<^wEtcuu}*39Mp>kCUGL&VIAKxU?9xpB+bp2MYmTAB)Bu#pJ?E?&G!3e&QSdGW7jE*6gkY6}wUSO|Dul&7GUbr`l`WS3&kN%JYt+w=oBPjbypjdk+Ff7p&R;vgh^OxNN=p3!Lptj@%tT%pkM}?7EqwGIJVg-61qYlam*&t5*eefzMm<_ z&7KlZ{*biQYBXAU`QnH3-Wr;D;!wL9L0u7)3W!CyJ9am2U)=%7y;X+zMWU1e`Q{>F zqCua)6?ygf19;WwPok6s1R|OsGl+_(&Y(H|W+tc5A{7U`a`C8AQor!Ey_I)rZ?dRq zREj7q(t+p(sUW)~qYvUMPE%quZYbI43Nvdr!6%g7*B_w7rgX&B*WHoJL*n!S#7dG0 z5@-^0>1Q=TXGWortp{x&b%P9gE{``Z8=AD^&x%mI7xKlaFg9j(B-ZCj#!e?6=u3Bi z9?0h@Po+gPX_oGbTzy-%hPKGZBPoV_VPZ9YO(EK*I4Dg_N!qGX&1;@gYiO2=pV!Cd z>y+u!U8&l`T^(-tK)UY9f@0lR2Q5q*W&o3XZw<&$U0$BD3SM3ow1}<(XPPsWE`+-| zE01;x#A6cgspQ5)(H6uLoQy>i+9XXIFlM${9F#!pRw+%fy8AW;fX>%%&Yo9G}S*_?}uoYXTRfY#!88w&}#X z4#>eD6^9*`e{PeeF}*vXluL1<8CZlEXbRxL-VE7l76%>(G&D)Ydy1o$r)K`%%p!ZF zn8&rKCCsK%nV1Jf-YIG&NLueHE`70#egaQE-DbFfh1W7Fv4c=N?=Q)~4dvIXb|z(X zjglqOA6MkVDQKh=nwSr6vNF?Jn2d|0NJw36*R*5|-`2yg;u)61FdtoP1f*SX7$Z9D z$vr9zQ&u2)4TaNYlbm2VWkWgzk$dtH7HuebX%nPkxT{o>^ket_0oEpMq((2;WZJN>xzN+gQ~sF|If_9v)KSlfMWnYIi1Y;G193%fD3nsMf)>7&+4Q!68|WQ^8Y? zR)d=EHjgCv>tl>Z1YG%TY2=UJUnG<5Y6d~)~tW+sD_OZRdF4Y zoXsJW94~A7;8>aD9$}pt7|^g&1Lq+Iv>*wzN|wwYy@RPrv~ZtmEV8-6rDMjZ`Vnc_ zTa-)hEYV5(;M&{NFKr{_Mn1tpk~21UQh_^cuu%qk#bZv}wP4d80Syw~AA?PG!)1(G@k)5Z`ugY*cfmFe&Z z>@||~LTn@M3pxH)qHnuRJV`~H`}6g~8whn)s#SjP;C0BL@$$o@bnb$K{G+_<3 z4to`hqb>BH2R~e(7iyb`eVa}HHW6=j3GQ?quuYdb8xLU)FHzAYe+4rCp2dRaSx6zP zuR{%w9wO7u{ZVdLLZB$b^Gn8Y(ZO?<4_T@@>!Qb9Y-(R#QRx$aS^!c;sup<7gNvOd znqAt~4N?2WB~dRGtA#FfyU#AX0qUsln!G|XW6EOLie4>b+s?3Qrmr#JnH8bmv;iui za{C8UL?x_M0lW0o6LH%u#jnxaOyLX)b;#85(onyj$3d#Dk*liKvr~`Ca(xZL^E2?L zX8G{BOdgK)1TqZVaDGv*HUu9MTHxXW0K0S;ZPQe&qV{)G;DF9Jp99o7OQZcp^?AhU z^zG2nwEknzhkp_+ph^4BJ#L^SRnJ#adRc+mFvB&z;Z3L@GUQY}FpsL*j17uuMLn#A z3|Whuu_sfTPbl5lGPJ=+WIUIR34Dr7lNYwn8^$g|b({S11k)GZi1{JI*JSJpnq3)A z;JaeUL{wAEE>lR|Uv_pNbF2s_5zqzmDybXs)&A*<)#29h0xjZkG<%rs_+B-;(gA0O z;S+*t6KBM-MGGJKrrhQBNS2!4c8t|45Y@GHmfaNP%iH4RF0Mnu)l@@P-up>#==~F5)x?~&qj))t-DGk1vs-N)W zO#LQhZ2VfYKcT{g&!{*E8uof4F8o{=->teadqtV1zam=X)Gzg{o4+4hp6L40U7zS=;U7pFzNaKE^u%9zh$sp+84x7G zQO2@c#7zytaS{?hh@TM>h$MJ!4PYcFrcsAPgGkK0D&*#WZV=c(p1oI)8NeJbGT1Fz z@IjKyLm*wQ{kU9X7vqwvKbzpUBx3Up#7Xd!1bo1iBU&~QC7h*Al9!YuUreX+eg&u! zsjCT0BrQ(Lm(G3zjC5dhk%e9ki`3goE zk_%DP4k}%k6qLV#=6vI5deHyD^U6fKEKiL<{sAPC&fgz@bb$PhThPxC6{8Ca6Hv+--)@RKN@(cYL2)s~h(0r)MsFlS-_mej zWZWA6HC^9ON#<&mP**vUk#u1R+Ic?qQdNku09gWa?FdRc$_aDG=%26x zddXbdkC-l7t~yBtdQ=#c?k?CJUmfVh8s-kg3>2%gNaeS`= zbc6fL;UXkG=3c+25Kh+Z!rwB~SAJ8xcP11FhW$GiZV{{R9Mrx?6O_$Dsy-)Xo(I`DSn725k5Q#vYi(p-&i1+m4iEqhIR#tx9KAR@Zk z75|oLJYpR3XgrkJP#)KW{0fJlfUnLqmvaplvImC@KX)ZV zH^h`d?yk#Sr%Q3u6&aY3Kf}z!z*DE&pB5TJu(#~;xaqWIeEL*Fru$r&o-b2}pW#xc zo3Ah|X0UVFRg0$U9?eHHQ4*HVKwFR@cb9JRFvVmn84s}M8s1y}sc%PV)DhOQk|ZkZ z7x(uh8fX!t<^k!nLQ>dI>Gq)CSaSyTyemG{RnhGZ;(h7BK|g?VwNMf$`xsgOdEt;i ze#o7nXxxj2W`REI%n%YbZ(ZrUo*A6PHETcWBAU4u8V4pGeoBI+CdAKf)WS8(kc-f= z0^17Af!j36gdYR-0(R48eN{`=BngS6&`N$Pzo$WJ%6_{#e@o`#lbn}b)_AwdWL_7% zh58)()|*bvS-=qj%gx*Nd8Tm|t5p)oOuB#S-9_xPy8bZcb*)Fxg#7B%GSMu-*_U|z zER^H|bjGQsd59N^bN-{&=`+qR5eqj_NA#qa)+k-qa*_44kY&lb>Fe)QoNdW4C+I5g zjc;wbYuvV^&a2bu3@c}`H<^utO84xa@;M6`VuhImo{r_jQ?M_+-lE0j(Bj-fJT<*l ziA{^2Fc&PL-O)oU($QkEVqMeaPn&}Jn$1$18FiNXYpcO=%-?&u6+><)%#ZCPMxVj^ z0XyW|e39_nu`=@m)cg!L6JG^YL zwb@5+)TwXCsc%!tLmK{!`_L2qNyp7sOqLmTi_+q7OLwpDcUW1y6jp1q|5B^xtk(Eb zsTPF6WlTha`=Cs#=TVRwlp&OrA(6*~E_`T;VeU|73X1)(&Pp^)@sb>=LBhG`2BF$TV-#k?RO?31CS+hCXh5k>Zg9Cy7hdMc;BK`eitIj8XPiTInET?2 z?a>E8&ipzWwGz{VItD!&#bS(1X-O$hrIhQ9VnH9}f)Uk{Ke{D&#G1&7n-c93AIEu4 zj$W}Kp3B#%5$Vz-seo~zBl?XN`?{9Qiiw{e>_{jPJ4q%=-_>lxGFgv7qKy+0DN)C6w)oJXscc~1XPlAWH^1K zwJp+`Y4C+Gt|&QoF^agW6SF9>FspPV1Bs}SFe^sHWR2b2V`TYl2E$g{h! zD>iX=E|Q*5WPLM{b-U1NE&}pX##?dci$;>4-5XxeBXs8pw`KqsLtbDV+5= z2iMyvGaM~@aH4MWs-W5KoW12eqYQUnY<2XN(qaLLF#*5<7C!A_G zzr^(`gmwqVu=s}D;zc+1wywbsQyO5zYEH>YS}PgsVI#TZTj6f@#L<>ZkJ(Spfy5^` z$3BG9P|tvxSjhcCUM)s$0wB+{jLz8%`EN{a@!v~jQ~E!^HQu}^4)fMdQyv&>3I}#t z-_Wt583u{MSv|oytI-ehel>W7=G|ks?<@YYZ@%+s&8Tj3ebM({o4&H`COBL9{l#q! zi>Wa9B>>CPHcZ5q6^e*g{vEP@;Q@!XA#wtZDAIMUqOa=r$aiMuDazgM(}B_Vj`pbQL<4tfn2IT7gCJPq){8N%FWzty6#-QA2Kv0v3n%1lv-ZEDgNc=%Y~8 z+-7KX-o>oD?M{@pvs5JPS zwRkhn1&wx}!UL&!RvpHueq(89mK*x#mLD-)Cl2RK3mvJA($v?aEqdu1TjjW>-jn=i z^exd(Knp&HUU*uvUNY1T@pz~~BtPA~C;7_joGomi?fMGd1Zm4z&$;rjUo%p!p7{Rx zNMZ(77Y6<+ea`+rrO$ul3EBV0ABltWe}GX`WgJj7(7q`MEXK`+WraZypcjDr6R|)p zLqb{O`Jh4l!4mRT7uWMRQw~j;%i~j)<<``H1E+Nx?}%+RQkCX!9L&dL!(bnzQP_BGs;L6rCUH z*XFmM(datN(=#<{JnHr9vzy>Ja#<(#t2J%@9HWted^yazi!E8oBso(x*wZU~Su_{t zGAmAc8Y$$?%Rjl;^2>`E(3`#5SgWe4OsP>7YPtgrT^K}wnZsJMS5RV6t>f4K)@NAt zZJv~M&n30ZqdV9#C|avZvlo{i(Hi6{j7|CH*sOga_ z@}$@rD4w*_e|cD|WZBBoqcdZ&Y-wd%($3|;j%TZFT#M(OpfJEbq^rlSnc=9gRs~Sx z(>T6VH1H<4)?J6UjSnM63@@My%Br4M)xWBav9s9Fx|q{D%xbZcawWCEf>4N5k(=yA zYK8Y{5=j-BES#LF($7+YEbb5*IXfk=-3UF0cJFhj*I`eI8B`oRfoc-87efV2EHmF-P zH?tY>^)_2_zwgF!#se*%b2_Koce-|-E*}5f1)noFh^r_SYsq)-rcX$x?|^tJ|1l*O zR{6R;ebFMrEspc?a;05oE3d9O8&B^ak}Eib{hbI$>j}#S?K+&YkS#H!wTj*wh>32R zsFpa=8T?aI#Sx_ft(FCU*eZVo4R44Ed}1qfGjvOIQ*}D554}#m};!l3y!B{!ayoI%C&rWX}kn~dD9v`4xYHizhjJ@_zDNA%l*>F5)&WGriM37Y8%XkP-!OJpV;zQi=G9CzL*b}>TZ z$6a`**uMTS@WLc$Yk4u^g4g(`x4_W1<-wvB6V-l*D=3e*C(!`MQ;|gH$4NrIPV3My z*<}&yAP3rsjw^ua^8r78nKSx6Z`6Xy6B8sd1%~8v>)<@rAJ9GX5lIFvf58u44INc){T`E=UCGDdO}Kk9Wqf^pdc^E(o&D*}JxUbw5T zNF?I;FBrU>93pAb-8O>Mo##nB{AzHNNNjTP@}FbNdyfE`ZEa{ zv*FYV*e}1FVbhCA4h~qMil2iG2EugitzM&Fn_iH&2(XcI?K4LB`xMu%n|JFJ*Up+9 ztEN79?HaF~bQg{Jf;K64E|w00*30}6cZi+yka*rd4sDi2fd6c;0;~%47^C5DmSgrI zxL=HIK=r(ptPKmb`aDEWf)lSzWL zk_XAOHl*Sa6~`wk0V=%-4G+kC*#Y%JLG{?jbbGqq8EA5&=+6m|e<1#uxi|cqkuC*-r@v=}p8(N2!(HY`j#2E5pruJ&LX z+OG9^5p}0qy7IiaHUY<5y!yPEF)!U;NfUVsZLqpv-<0+8s>3s6 zy}HA+yNU?`=?~K1`s9GMUwz`kHXlBDp*HV6dccr?y;{RMyN44xo_~to0yC*5I z{k5n>Ef>-)h$Ht}ISpA02Zp(zg=!R2Bw|s2XG>HICZm$?_F`0tcj%%iQ7W1T_X?bl zw0^y4agZaFk?8pfPODQbA9<A6Y_VhZY5q@bX2R9ar*3r2$-9vU9jr>vRwM}NCo$NuOP0f*0qJA#kXx?HijEjD%yQC5}{#v(>fK4S!i&zN1n=~r=pZS7-MeaO|M&t>%5M|yYkh~6S{p8#eh7e_loF&&c<6hP%f z3hfRFGI?oTx^XCDd$MQjrL^m?MlXGu5%XUKNfZjd>=ejjjP?gQhQ;)-5m3fYqkJ*h zt#kOfIGO?SZbi=dlC7U4L7BymH7_;5S;wx;CH;MBM$Q9dMI=a1;@cKpCPAmtjd}9W zSDAY#k!xV7g$1*$9^eVZRM=5+QKG%9!+L$o6|LhJH*wPMP?xdBJe2fHgEZPsjBdVe zlMM;;GIJZDBu2?1%g{7ObPh3Uc2X|ZhHuwZ@XMumdLR^w$ojpF&Fbi#f@KSj^_e;b zEJLe$MQ%2*9B*7Hs8o_>$ci~0WN7zBYkaQ3)$`_mRU*%B>Hp)7)o9Q+@cxjTRnVIpG`Y=Q##_=aIIcz#jJZc zigh#_sp_j>eSA@CoAo7aO__Y#7k9lpM~oE4uC6|^Wi`2h^%0ANnsM4|)f9WP^dq=- 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

F9Ck<@RiJoLX=RjXyZyA$)Ipeh4?TNP+Eozyr@& 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

SN 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 diff --git a/DepFiles/unittest/junit-4.12.jar b/DepFiles/unittest/junit-4.12.jar deleted file mode 100644 index 3a7fc266c3e32283a2b21fe12166ebdcc33a1da1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 314932 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

&R7;REBZd0NaNVcN6Eoy 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

+ * This estimate doesn't include the size of the {@link SpatialReference} object + * because instances of {@link SpatialReference} are expected to be shared among + * geometry objects. + * + * @return Returns an estimate of this object size in bytes. + */ + public long estimateMemorySize() { + long sz = SIZE_OF_MAPGEOMETRY; + if (m_geometry != null) + sz += m_geometry.estimateMemorySize(); + return sz; + } + @Override public int hashCode() { SpatialReference sr = getSpatialReference(); diff --git a/src/main/java/com/esri/core/geometry/OperatorDensifyByLength.java b/src/main/java/com/esri/core/geometry/OperatorDensifyByLength.java index 085dd397..d9d9d692 100644 --- a/src/main/java/com/esri/core/geometry/OperatorDensifyByLength.java +++ b/src/main/java/com/esri/core/geometry/OperatorDensifyByLength.java @@ -49,7 +49,7 @@ public Type getType() { * After that the curves are replaced with straight segments. * @param progressTracker * @return Returns the densified geometries (It does nothing to geometries - * with dim < 1, but simply passes them along). + * with dim < 1, but simply passes them along). */ public abstract GeometryCursor execute(GeometryCursor inputGeometries, double maxLength, ProgressTracker progressTracker); @@ -67,7 +67,7 @@ public abstract GeometryCursor execute(GeometryCursor inputGeometries, * After that the curves are replaced with straight segments. * @param progressTracker * @return Returns the densified geometry. (It does nothing to geometries - * with dim < 1, but simply passes them along). + * with dim < 1, but simply passes them along). */ public abstract Geometry execute(Geometry inputGeometry, double maxLength, ProgressTracker progressTracker); diff --git a/src/main/java/com/esri/core/geometry/OperatorImportFromGeoJson.java b/src/main/java/com/esri/core/geometry/OperatorImportFromGeoJson.java index 87de2d4a..2a25215d 100644 --- a/src/main/java/com/esri/core/geometry/OperatorImportFromGeoJson.java +++ b/src/main/java/com/esri/core/geometry/OperatorImportFromGeoJson.java @@ -34,7 +34,7 @@ public Type getType() { * Performs the ImportFromGeoJson operation. * * @param type Use the {@link Geometry.Type} enum. - * @param jsonObject The JSONObject holding the geometry and spatial reference. + * @param jsonReader The JSONReader. * @return Returns the imported MapGeometry. * @throws JsonGeometryException */ @@ -49,7 +49,6 @@ public Type getType() { * @param type Use the {@link Geometry.Type} enum. * @param geoJsonString The string holding the Geometry in geoJson format. * @return Returns the imported MapGeometry. - * @throws JSONException * */ public abstract MapGeometry execute(int import_flags, Geometry.Type type, String geoJsonString, ProgressTracker progress_tracker); @@ -61,7 +60,6 @@ public Type getType() { * @param import_flags Use the {@link GeoJsonImportFlags} interface. * @param geoJsonString The string holding the Geometry in geoJson format. * @return Returns the imported MapOGCStructure. - * @throws JSONException */ public abstract MapOGCStructure executeOGC(int import_flags, String geoJsonString, ProgressTracker progress_tracker); diff --git a/src/main/java/com/esri/core/geometry/OperatorIntersection.java b/src/main/java/com/esri/core/geometry/OperatorIntersection.java index ee3b4833..5afc6e37 100644 --- a/src/main/java/com/esri/core/geometry/OperatorIntersection.java +++ b/src/main/java/com/esri/core/geometry/OperatorIntersection.java @@ -53,12 +53,12 @@ public abstract GeometryCursor execute(GeometryCursor inputGeometries, *@param sr The spatial reference is used to get tolerance value. Can be null, then the tolerance is not used and the operation is performed with *a small tolerance value just enough to make the operation robust. *@param progress_tracker Allows to cancel the operation. Can be null. - *@param dimensionMask The dimension of the intersection. The value is either -1, or a bitmask mask of values (1 << dim). + *@param dimensionMask The dimension of the intersection. The value is either -1, or a bitmask mask of values (1 << dim). *The value of -1 means the lower dimension in the intersecting pair. *This is a fastest option when intersecting polygons with polygons or polylines. - *The bitmask of values (1 << dim), where dim is the desired dimension value, is used to indicate + *The bitmask of values (1 << dim), where dim is the desired dimension value, is used to indicate *what dimensions of geometry one wants to be returned. For example, to return - *multipoints and lines only, pass (1 << 0) | (1 << 1), which is equivalen to 1 | 2, or 3. + *multipoints and lines only, pass (1 << 0) | (1 << 1), which is equivalen to 1 | 2, or 3. *@return Returns the cursor of the intersection result. The cursors' getGeometryID method returns the current ID of the input geometry *being processed. When dimensionMask is a bitmask, there will be n result geometries per one input geometry returned, where n is the number *of bits set in the bitmask. For example, if the dimensionMask is 5, there will be two geometries per one input geometry. @@ -81,7 +81,7 @@ public abstract GeometryCursor execute(GeometryCursor input_geometries, *points, but the overlaps only). *The call is equivalent to calling the overloaded method using cursors: *execute(new SimpleGeometryCursor(input_geometry), new SimpleGeometryCursor(intersector), sr, progress_tracker, mask).next(); - *where mask can be either -1 or min(1 << input_geometry.getDimension(), 1 << intersector.getDimension()); + *where mask can be either -1 or min(1 << input_geometry.getDimension(), 1 << intersector.getDimension()); *@param inputGeometry is the Geometry instance to be intersected by the intersector. *@param intersector is the intersector Geometry. *@param sr The spatial reference to get the tolerance value from. Can be null, then the tolerance is calculated from the input geometries. diff --git a/src/main/java/com/esri/core/geometry/OperatorOffset.java b/src/main/java/com/esri/core/geometry/OperatorOffset.java index 7b4f3673..bdcf5005 100644 --- a/src/main/java/com/esri/core/geometry/OperatorOffset.java +++ b/src/main/java/com/esri/core/geometry/OperatorOffset.java @@ -43,7 +43,7 @@ public enum JoinType { * * The offset operation creates a geometry that is a constant distance from * an input polyline or polygon. It is similar to buffering, but produces a - * one sided result. If offsetDistance > 0, then the offset geometry is + * one sided result. If offsetDistance greater than 0, then the offset geometry is * constructed to the right of the oriented input geometry, otherwise it is * constructed to the left. For a simple polygon, the orientation of outer * rings is clockwise and for inner rings it is counter clockwise. So the @@ -82,7 +82,7 @@ public abstract GeometryCursor execute(GeometryCursor inputGeometries, * * The offset operation creates a geometry that is a constant distance from * an input polyline or polygon. It is similar to buffering, but produces a - * one sided result. If offsetDistance > 0, then the offset geometry is + * one sided result. If offsetDistance greater than 0, then the offset geometry is * constructed to the right of the oriented input geometry, otherwise it is * constructed to the left. For a simple polygon, the orientation of outer * rings is clockwise and for inner rings it is counter clockwise. So the diff --git a/src/main/java/com/esri/core/geometry/Point.java b/src/main/java/com/esri/core/geometry/Point.java index d96589ae..a421400f 100644 --- a/src/main/java/com/esri/core/geometry/Point.java +++ b/src/main/java/com/esri/core/geometry/Point.java @@ -39,7 +39,7 @@ public class Point extends Geometry implements Serializable { //We are using writeReplace instead. //private static final long serialVersionUID = 2L; - double[] m_attributes; // use doubles to store everything (long are bitcast) + double[] m_attributes; // use doubles to store everything (long are bitcast) /** * Creates an empty 2D point. @@ -626,22 +626,22 @@ public int hashCode() { return hashCode; } - @Override - public Geometry getBoundary() { - return null; - } - - @Override - public void replaceNaNs(int semantics, double value) { - addAttribute(semantics); - if (isEmpty()) - return; - - int ncomps = VertexDescription.getComponentCount(semantics); - for (int i = 0; i < ncomps; i++) { - double v = getAttributeAsDbl(semantics, i); - if (Double.isNaN(v)) - setAttribute(semantics, i, value); - } - } + @Override + public Geometry getBoundary() { + return null; + } + + @Override + public void replaceNaNs(int semantics, double value) { + addAttribute(semantics); + if (isEmpty()) + return; + + int ncomps = VertexDescription.getComponentCount(semantics); + for (int i = 0; i < ncomps; i++) { + double v = getAttributeAsDbl(semantics, i); + if (Double.isNaN(v)) + setAttribute(semantics, i, value); + } + } } diff --git a/src/main/java/com/esri/core/geometry/Point2D.java b/src/main/java/com/esri/core/geometry/Point2D.java index 245b8156..90cc1e46 100644 --- a/src/main/java/com/esri/core/geometry/Point2D.java +++ b/src/main/java/com/esri/core/geometry/Point2D.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2015 Esri + Copyright 1995-2018 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -435,7 +435,7 @@ public boolean isNaN() { } /** - * Calculates the orientation of the triangle formed by p->q->r. Returns 1 + * Calculates the orientation of the triangle formed by p, q, r. Returns 1 * for counter-clockwise, -1 for clockwise, and 0 for collinear. May use * high precision arithmetics for some special degenerate cases. */ diff --git a/src/main/java/com/esri/core/geometry/Polygon.java b/src/main/java/com/esri/core/geometry/Polygon.java index a8298077..949a3797 100644 --- a/src/main/java/com/esri/core/geometry/Polygon.java +++ b/src/main/java/com/esri/core/geometry/Polygon.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2015 Esri + Copyright 1995-2018 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -145,39 +145,41 @@ public int getExteriorRingCount() { return m_impl.getOGCPolygonCount(); } - public interface FillRule { - /** - * odd-even fill rule. This is the default value. A point is in the polygon interior if a ray - * from this point to infinity crosses odd number of segments of the polygon. - */ - public final static int enumFillRuleOddEven = 0; - /** - * winding fill rule (aka non-zero winding rule). A point is in the polygon interior if a winding number is not zero. - * To compute a winding number for a point, draw a ray from this point to infinity. If N is the number of times the ray - * crosses segments directed up and the M is the number of times it crosses segments directed down, - * then the winding number is equal to N-M. - */ - public final static int enumFillRuleWinding = 1; - }; - - /** - *Fill rule for the polygon that defines the interior of the self intersecting polygon. It affects the Simplify operation. - *Can be use by drawing code to pass around the fill rule of graphic path. - *This property is not persisted in any format yet. - *See also Polygon.FillRule. - */ - public void setFillRule(int rule) { - m_impl.setFillRule(rule); - } - - /** - *Fill rule for the polygon that defines the interior of the self intersecting polygon. It affects the Simplify operation. - *Changing the fill rule on the polygon that has no self intersections has no physical effect. - *Can be use by drawing code to pass around the fill rule of graphic path. - *This property is not persisted in any format yet. - *See also Polygon.FillRule. - */ - public int getFillRule() { - return m_impl.getFillRule(); - } + public interface FillRule { + /** + * odd-even fill rule. This is the default value. A point is in the polygon + * interior if a ray from this point to infinity crosses odd number of segments + * of the polygon. + */ + public final static int enumFillRuleOddEven = 0; + /** + * winding fill rule (aka non-zero winding rule). A point is in the polygon + * interior if a winding number is not zero. To compute a winding number for a + * point, draw a ray from this point to infinity. If N is the number of times + * the ray crosses segments directed up and the M is the number of times it + * crosses segments directed down, then the winding number is equal to N-M. + */ + public final static int enumFillRuleWinding = 1; + }; + + /** + * Fill rule for the polygon that defines the interior of the self intersecting + * polygon. It affects the Simplify operation. Can be use by drawing code to + * pass around the fill rule of graphic path. This property is not persisted in + * any format yet. See also Polygon.FillRule. + */ + public void setFillRule(int rule) { + m_impl.setFillRule(rule); + } + + /** + * Fill rule for the polygon that defines the interior of the self intersecting + * polygon. It affects the Simplify operation. Changing the fill rule on the + * polygon that has no self intersections has no physical effect. Can be use by + * drawing code to pass around the fill rule of graphic path. This property is + * not persisted in any format yet. See also Polygon.FillRule. + */ + public int getFillRule() { + return m_impl.getFillRule(); + } } diff --git a/src/main/java/com/esri/core/geometry/Polyline.java b/src/main/java/com/esri/core/geometry/Polyline.java index 0d842806..9f45d7ea 100644 --- a/src/main/java/com/esri/core/geometry/Polyline.java +++ b/src/main/java/com/esri/core/geometry/Polyline.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2015 Esri + Copyright 1995-2018 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/SizeOf.java b/src/main/java/com/esri/core/geometry/SizeOf.java index 86683a93..31460366 100644 --- a/src/main/java/com/esri/core/geometry/SizeOf.java +++ b/src/main/java/com/esri/core/geometry/SizeOf.java @@ -21,6 +21,7 @@ email: contracts@esri.com */ + package com.esri.core.geometry; import static sun.misc.Unsafe.ARRAY_BYTE_BASE_OFFSET; @@ -38,90 +39,83 @@ import static sun.misc.Unsafe.ARRAY_SHORT_BASE_OFFSET; import static sun.misc.Unsafe.ARRAY_SHORT_INDEX_SCALE; -public final class SizeOf -{ - public static final int SIZE_OF_ATTRIBUTE_STREAM_OF_FLOAT = 24; +public final class SizeOf { + public static final int SIZE_OF_ATTRIBUTE_STREAM_OF_FLOAT = 24; - public static final int SIZE_OF_ATTRIBUTE_STREAM_OF_DBL = 24; + public static final int SIZE_OF_ATTRIBUTE_STREAM_OF_DBL = 24; - public static final int SIZE_OF_ATTRIBUTE_STREAM_OF_INT8 = 24; + public static final int SIZE_OF_ATTRIBUTE_STREAM_OF_INT8 = 24; - public static final int SIZE_OF_ATTRIBUTE_STREAM_OF_INT16 = 24; + public static final int SIZE_OF_ATTRIBUTE_STREAM_OF_INT16 = 24; - public static final int SIZE_OF_ATTRIBUTE_STREAM_OF_INT32 = 24; + public static final int SIZE_OF_ATTRIBUTE_STREAM_OF_INT32 = 24; - public static final int SIZE_OF_ATTRIBUTE_STREAM_OF_INT64 = 24; + public static final int SIZE_OF_ATTRIBUTE_STREAM_OF_INT64 = 24; - public static final int SIZE_OF_ENVELOPE = 32; + public static final int SIZE_OF_ENVELOPE = 32; - public static final int SIZE_OF_ENVELOPE2D = 48; + public static final int SIZE_OF_ENVELOPE2D = 48; - public static final int SIZE_OF_LINE = 56; + public static final int SIZE_OF_LINE = 56; - public static final int SIZE_OF_MULTI_PATH = 24; + public static final int SIZE_OF_MULTI_PATH = 24; - public static final int SIZE_OF_MULTI_PATH_IMPL = 112; + public static final int SIZE_OF_MULTI_PATH_IMPL = 112; - public static final int SIZE_OF_MULTI_POINT = 24; + public static final int SIZE_OF_MULTI_POINT = 24; - public static final int SIZE_OF_MULTI_POINT_IMPL = 56; + public static final int SIZE_OF_MULTI_POINT_IMPL = 56; - public static final int SIZE_OF_POINT = 24; + public static final int SIZE_OF_POINT = 24; - public static final int SIZE_OF_POLYGON = 24; + public static final int SIZE_OF_POLYGON = 24; - public static final int SIZE_OF_POLYLINE = 24; + public static final int SIZE_OF_POLYLINE = 24; - public static final int SIZE_OF_OGC_CONCRETE_GEOMETRY_COLLECTION = 24; + public static final int SIZE_OF_OGC_CONCRETE_GEOMETRY_COLLECTION = 24; - public static final int SIZE_OF_OGC_LINE_STRING = 24; + public static final int SIZE_OF_OGC_LINE_STRING = 24; - public static final int SIZE_OF_OGC_MULTI_LINE_STRING = 24; + public static final int SIZE_OF_OGC_MULTI_LINE_STRING = 24; - public static final int SIZE_OF_OGC_MULTI_POINT = 24; + public static final int SIZE_OF_OGC_MULTI_POINT = 24; - public static final int SIZE_OF_OGC_MULTI_POLYGON = 24; + public static final int SIZE_OF_OGC_MULTI_POLYGON = 24; - public static final int SIZE_OF_OGC_POINT = 24; + public static final int SIZE_OF_OGC_POINT = 24; - public static final int SIZE_OF_OGC_POLYGON = 24; + public static final int SIZE_OF_OGC_POLYGON = 24; + + public static final int SIZE_OF_MAPGEOMETRY = 24; - public static long sizeOfByteArray(int length) - { - return ARRAY_BYTE_BASE_OFFSET + (((long) ARRAY_BYTE_INDEX_SCALE) * length); - } + public static long sizeOfByteArray(int length) { + return ARRAY_BYTE_BASE_OFFSET + (((long) ARRAY_BYTE_INDEX_SCALE) * length); + } - public static long sizeOfShortArray(int length) - { - return ARRAY_SHORT_BASE_OFFSET + (((long) ARRAY_SHORT_INDEX_SCALE) * length); - } + public static long sizeOfShortArray(int length) { + return ARRAY_SHORT_BASE_OFFSET + (((long) ARRAY_SHORT_INDEX_SCALE) * length); + } - public static long sizeOfCharArray(int length) - { - return ARRAY_CHAR_BASE_OFFSET + (((long) ARRAY_CHAR_INDEX_SCALE) * length); - } + public static long sizeOfCharArray(int length) { + return ARRAY_CHAR_BASE_OFFSET + (((long) ARRAY_CHAR_INDEX_SCALE) * length); + } - public static long sizeOfIntArray(int length) - { - return ARRAY_INT_BASE_OFFSET + (((long) ARRAY_INT_INDEX_SCALE) * length); - } + public static long sizeOfIntArray(int length) { + return ARRAY_INT_BASE_OFFSET + (((long) ARRAY_INT_INDEX_SCALE) * length); + } - public static long sizeOfLongArray(int length) - { - return ARRAY_LONG_BASE_OFFSET + (((long) ARRAY_LONG_INDEX_SCALE) * length); - } + public static long sizeOfLongArray(int length) { + return ARRAY_LONG_BASE_OFFSET + (((long) ARRAY_LONG_INDEX_SCALE) * length); + } - public static long sizeOfFloatArray(int length) - { - return ARRAY_FLOAT_BASE_OFFSET + (((long) ARRAY_FLOAT_INDEX_SCALE) * length); - } + public static long sizeOfFloatArray(int length) { + return ARRAY_FLOAT_BASE_OFFSET + (((long) ARRAY_FLOAT_INDEX_SCALE) * length); + } - public static long sizeOfDoubleArray(int length) - { - return ARRAY_DOUBLE_BASE_OFFSET + (((long) ARRAY_DOUBLE_INDEX_SCALE) * length); - } + public static long sizeOfDoubleArray(int length) { + return ARRAY_DOUBLE_BASE_OFFSET + (((long) ARRAY_DOUBLE_INDEX_SCALE) * length); + } - private SizeOf() - { - } + private SizeOf() { + } } diff --git a/src/main/java/com/esri/core/geometry/SpatialReference.java b/src/main/java/com/esri/core/geometry/SpatialReference.java index 22b0c74f..4c337e27 100644 --- a/src/main/java/com/esri/core/geometry/SpatialReference.java +++ b/src/main/java/com/esri/core/geometry/SpatialReference.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2017 Esri + Copyright 1995-2018 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/main/java/com/esri/core/geometry/ogc/OGCGeometry.java b/src/main/java/com/esri/core/geometry/ogc/OGCGeometry.java index 4ad748be..a3f60a6d 100644 --- a/src/main/java/com/esri/core/geometry/ogc/OGCGeometry.java +++ b/src/main/java/com/esri/core/geometry/ogc/OGCGeometry.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2017 Esri + Copyright 1995-2018 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/test/java/com/esri/core/geometry/TestEstimateMemorySize.java b/src/test/java/com/esri/core/geometry/TestEstimateMemorySize.java index ba516b5f..64d2bfc2 100644 --- a/src/test/java/com/esri/core/geometry/TestEstimateMemorySize.java +++ b/src/test/java/com/esri/core/geometry/TestEstimateMemorySize.java @@ -15,92 +15,81 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; -public class TestEstimateMemorySize -{ - @Test - public void testInstanceSizes() - { - assertEquals(getInstanceSize(AttributeStreamOfFloat.class), SizeOf.SIZE_OF_ATTRIBUTE_STREAM_OF_FLOAT); - assertEquals(getInstanceSize(AttributeStreamOfDbl.class), SizeOf.SIZE_OF_ATTRIBUTE_STREAM_OF_DBL); - assertEquals(getInstanceSize(AttributeStreamOfInt8.class), SizeOf.SIZE_OF_ATTRIBUTE_STREAM_OF_INT8); - assertEquals(getInstanceSize(AttributeStreamOfInt16.class), SizeOf.SIZE_OF_ATTRIBUTE_STREAM_OF_INT16); - assertEquals(getInstanceSize(AttributeStreamOfInt32.class), SizeOf.SIZE_OF_ATTRIBUTE_STREAM_OF_INT32); - assertEquals(getInstanceSize(AttributeStreamOfInt64.class), SizeOf.SIZE_OF_ATTRIBUTE_STREAM_OF_INT64); - assertEquals(getInstanceSize(Envelope.class), SizeOf.SIZE_OF_ENVELOPE); - assertEquals(getInstanceSize(Envelope2D.class), SizeOf.SIZE_OF_ENVELOPE2D); - assertEquals(getInstanceSize(Line.class), SizeOf.SIZE_OF_LINE); - assertEquals(getInstanceSize(MultiPath.class), SizeOf.SIZE_OF_MULTI_PATH); - assertEquals(getInstanceSize(MultiPathImpl.class), SizeOf.SIZE_OF_MULTI_PATH_IMPL); - assertEquals(getInstanceSize(MultiPoint.class), SizeOf.SIZE_OF_MULTI_POINT); - assertEquals(getInstanceSize(MultiPointImpl.class), SizeOf.SIZE_OF_MULTI_POINT_IMPL); - assertEquals(getInstanceSize(Point.class), SizeOf.SIZE_OF_POINT); - assertEquals(getInstanceSize(Polygon.class), SizeOf.SIZE_OF_POLYGON); - assertEquals(getInstanceSize(Polyline.class), SizeOf.SIZE_OF_POLYLINE); - assertEquals(getInstanceSize(OGCConcreteGeometryCollection.class), SizeOf.SIZE_OF_OGC_CONCRETE_GEOMETRY_COLLECTION); - assertEquals(getInstanceSize(OGCLineString.class), SizeOf.SIZE_OF_OGC_LINE_STRING); - assertEquals(getInstanceSize(OGCMultiLineString.class), SizeOf.SIZE_OF_OGC_MULTI_LINE_STRING); - assertEquals(getInstanceSize(OGCMultiPoint.class), SizeOf.SIZE_OF_OGC_MULTI_POINT); - assertEquals(getInstanceSize(OGCMultiPolygon.class), SizeOf.SIZE_OF_OGC_MULTI_POLYGON); - assertEquals(getInstanceSize(OGCPoint.class), SizeOf.SIZE_OF_OGC_POINT); - assertEquals(getInstanceSize(OGCPolygon.class), SizeOf.SIZE_OF_OGC_POLYGON); - } +public class TestEstimateMemorySize { + @Test + public void testInstanceSizes() { + assertEquals(getInstanceSize(AttributeStreamOfFloat.class), SizeOf.SIZE_OF_ATTRIBUTE_STREAM_OF_FLOAT); + assertEquals(getInstanceSize(AttributeStreamOfDbl.class), SizeOf.SIZE_OF_ATTRIBUTE_STREAM_OF_DBL); + assertEquals(getInstanceSize(AttributeStreamOfInt8.class), SizeOf.SIZE_OF_ATTRIBUTE_STREAM_OF_INT8); + assertEquals(getInstanceSize(AttributeStreamOfInt16.class), SizeOf.SIZE_OF_ATTRIBUTE_STREAM_OF_INT16); + assertEquals(getInstanceSize(AttributeStreamOfInt32.class), SizeOf.SIZE_OF_ATTRIBUTE_STREAM_OF_INT32); + assertEquals(getInstanceSize(AttributeStreamOfInt64.class), SizeOf.SIZE_OF_ATTRIBUTE_STREAM_OF_INT64); + assertEquals(getInstanceSize(Envelope.class), SizeOf.SIZE_OF_ENVELOPE); + assertEquals(getInstanceSize(Envelope2D.class), SizeOf.SIZE_OF_ENVELOPE2D); + assertEquals(getInstanceSize(Line.class), SizeOf.SIZE_OF_LINE); + assertEquals(getInstanceSize(MultiPath.class), SizeOf.SIZE_OF_MULTI_PATH); + assertEquals(getInstanceSize(MultiPathImpl.class), SizeOf.SIZE_OF_MULTI_PATH_IMPL); + assertEquals(getInstanceSize(MultiPoint.class), SizeOf.SIZE_OF_MULTI_POINT); + assertEquals(getInstanceSize(MultiPointImpl.class), SizeOf.SIZE_OF_MULTI_POINT_IMPL); + assertEquals(getInstanceSize(Point.class), SizeOf.SIZE_OF_POINT); + assertEquals(getInstanceSize(Polygon.class), SizeOf.SIZE_OF_POLYGON); + assertEquals(getInstanceSize(Polyline.class), SizeOf.SIZE_OF_POLYLINE); + assertEquals(getInstanceSize(OGCConcreteGeometryCollection.class), + SizeOf.SIZE_OF_OGC_CONCRETE_GEOMETRY_COLLECTION); + assertEquals(getInstanceSize(OGCLineString.class), SizeOf.SIZE_OF_OGC_LINE_STRING); + assertEquals(getInstanceSize(OGCMultiLineString.class), SizeOf.SIZE_OF_OGC_MULTI_LINE_STRING); + assertEquals(getInstanceSize(OGCMultiPoint.class), SizeOf.SIZE_OF_OGC_MULTI_POINT); + assertEquals(getInstanceSize(OGCMultiPolygon.class), SizeOf.SIZE_OF_OGC_MULTI_POLYGON); + assertEquals(getInstanceSize(OGCPoint.class), SizeOf.SIZE_OF_OGC_POINT); + assertEquals(getInstanceSize(OGCPolygon.class), SizeOf.SIZE_OF_OGC_POLYGON); + } - private static int getInstanceSize(Class clazz) - { - return ClassLayout.parseClass(clazz).instanceSize(); - } + private static long getInstanceSize(Class clazz) { + return ClassLayout.parseClass(clazz).instanceSize(); + } - @Test - public void testPoint() - { - testGeometry(parseWkt("POINT (1 2)")); - } + @Test + public void testPoint() { + testGeometry(parseWkt("POINT (1 2)")); + } - @Test - public void testMultiPoint() - { - testGeometry(parseWkt("MULTIPOINT (0 0, 1 1, 2 3)")); - } + @Test + public void testMultiPoint() { + testGeometry(parseWkt("MULTIPOINT (0 0, 1 1, 2 3)")); + } - @Test - public void testLineString() - { - testGeometry(parseWkt("LINESTRING (0 1, 2 3, 4 5)")); - } + @Test + public void testLineString() { + testGeometry(parseWkt("LINESTRING (0 1, 2 3, 4 5)")); + } - @Test - public void testMultiLineString() - { - testGeometry(parseWkt("MULTILINESTRING ((0 1, 2 3, 4 5), (1 1, 2 2))")); - } + @Test + public void testMultiLineString() { + testGeometry(parseWkt("MULTILINESTRING ((0 1, 2 3, 4 5), (1 1, 2 2))")); + } - @Test - public void testPolygon() - { - testGeometry(parseWkt("POLYGON ((30 10, 40 40, 20 40, 10 20, 30 10))")); - } + @Test + public void testPolygon() { + testGeometry(parseWkt("POLYGON ((30 10, 40 40, 20 40, 10 20, 30 10))")); + } - @Test - public void testMultiPolygon() - { - testGeometry(parseWkt("MULTIPOLYGON (((30 20, 45 40, 10 40, 30 20)), ((15 5, 40 10, 10 20, 5 10, 15 5)))")); - } + @Test + public void testMultiPolygon() { + testGeometry(parseWkt("MULTIPOLYGON (((30 20, 45 40, 10 40, 30 20)), ((15 5, 40 10, 10 20, 5 10, 15 5)))")); + } - @Test - public void testGeometryCollection() - { - testGeometry(parseWkt("GEOMETRYCOLLECTION (POINT(4 6), LINESTRING(4 6,7 10))")); - } + @Test + public void testGeometryCollection() { + testGeometry(parseWkt("GEOMETRYCOLLECTION (POINT(4 6), LINESTRING(4 6,7 10))")); + } - private void testGeometry(OGCGeometry geometry) - { - assertTrue(geometry.estimateMemorySize() > 0); - } + private void testGeometry(OGCGeometry geometry) { + assertTrue(geometry.estimateMemorySize() > 0); + } - private static OGCGeometry parseWkt(String wkt) - { - OGCGeometry geometry = OGCGeometry.fromText(wkt); - geometry.setSpatialReference(null); - return geometry; - } + private static OGCGeometry parseWkt(String wkt) { + OGCGeometry geometry = OGCGeometry.fromText(wkt); + geometry.setSpatialReference(null); + return geometry; + } } From b3d9cc30cb68c883d3155eaa3271eaa468c4a195 Mon Sep 17 00:00:00 2001 From: Annette Locke Date: Fri, 16 Mar 2018 14:53:43 -0700 Subject: [PATCH 146/196] Add license header. --- .../core/geometry/TestEstimateMemorySize.java | 26 ++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/src/test/java/com/esri/core/geometry/TestEstimateMemorySize.java b/src/test/java/com/esri/core/geometry/TestEstimateMemorySize.java index 64d2bfc2..e4195c58 100644 --- a/src/test/java/com/esri/core/geometry/TestEstimateMemorySize.java +++ b/src/test/java/com/esri/core/geometry/TestEstimateMemorySize.java @@ -1,4 +1,28 @@ -package com.esri.core.geometry; +/* + Copyright 1995-2018 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ + + package com.esri.core.geometry; import com.esri.core.geometry.ogc.OGCConcreteGeometryCollection; import com.esri.core.geometry.ogc.OGCGeometry; From c733591b114d2d55fdd4646331f10ba0d8ca47d2 Mon Sep 17 00:00:00 2001 From: Randall Whitman Date: Mon, 19 Mar 2018 11:40:45 -0700 Subject: [PATCH 147/196] Geometry release v2.1.0 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index d55f9fcf..2c6e2580 100755 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ com.esri.geometry esri-geometry-api - 2.0.0 + 2.1.0 jar Esri Geometry API for Java From 517039c635cd07acbb61aa99d260855da23f531e Mon Sep 17 00:00:00 2001 From: Randall Whitman Date: Tue, 20 Mar 2018 09:50:15 -0700 Subject: [PATCH 148/196] README: v2.1.0 ; POM: v2.2 development (#161) --- README.md | 2 +- pom.xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 34589c2f..bcc099fe 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,7 @@ The project is also available as a [Maven](http://maven.apache.org/) dependency: com.esri.geometry esri-geometry-api - 2.0.0 + 2.1.0 ``` diff --git a/pom.xml b/pom.xml index 2c6e2580..0255ec18 100755 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ com.esri.geometry esri-geometry-api - 2.1.0 + 2.2.0-SNAPSHOT jar Esri Geometry API for Java From 5602a2a5d5266fc7cae7548b456a2d16cd1c9af9 Mon Sep 17 00:00:00 2001 From: danio Date: Thu, 29 Mar 2018 21:22:59 +0100 Subject: [PATCH 149/196] Test union operator with some geometry (#165) @danio Looks good. Thank you for the tests! --- .../com/esri/core/geometry/TestUnion.java | 129 +++++++++++++++++- 1 file changed, 124 insertions(+), 5 deletions(-) diff --git a/src/test/java/com/esri/core/geometry/TestUnion.java b/src/test/java/com/esri/core/geometry/TestUnion.java index 55392e39..77032413 100644 --- a/src/test/java/com/esri/core/geometry/TestUnion.java +++ b/src/test/java/com/esri/core/geometry/TestUnion.java @@ -40,11 +40,6 @@ protected void tearDown() throws Exception { @Test public static void testUnion() { - Point pt = new Point(10, 20); - - Point pt2 = new Point(); - pt2.setXY(10, 10); - Envelope env1 = new Envelope(10, 10, 30, 50); Envelope env2 = new Envelope(30, 10, 60, 50); Geometry[] geomArray = new Geometry[] { env1, env2 }; @@ -57,5 +52,129 @@ public static void testUnion() { GeometryCursor outputCursor = union.execute(inputGeometries, sr, null); Geometry result = outputCursor.next(); + + MultiPath path = (MultiPath)result; + assertEquals(1, path.getPathCount()); + assertEquals(6, path.getPathEnd(0)); + assertEquals(new Point2D(10, 10), path.getXY(0)); + assertEquals(new Point2D(10, 50), path.getXY(1)); + assertEquals(new Point2D(30, 50), path.getXY(2)); + assertEquals(new Point2D(60, 50), path.getXY(3)); + assertEquals(new Point2D(60, 10), path.getXY(4)); + assertEquals(new Point2D(30, 10), path.getXY(5)); + } + + @Test + public static void testUnionDistinctGeometries() { + Envelope env = new Envelope(1, 5, 3, 10); + + Polygon polygon = new Polygon(); + polygon.startPath(new Point(4, 3)); + polygon.lineTo(new Point(7, 6)); + polygon.lineTo(new Point(6, 8)); + polygon.lineTo(new Point(4, 3)); + + Geometry[] geomArray = new Geometry[] { env, polygon }; + SimpleGeometryCursor inputGeometries = new SimpleGeometryCursor( + geomArray); + OperatorUnion union = (OperatorUnion) OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Union); + SpatialReference sr = SpatialReference.create(4326); + + GeometryCursor outputCursor = union.execute(inputGeometries, sr, null); + Geometry result = outputCursor.next(); + + MultiPath path = (MultiPath)result; + assertEquals(2, path.getPathCount()); + + assertEquals(3, path.getPathEnd(0)); + assertEquals(7, path.getPathEnd(1)); + // from polygon + assertEquals(new Point2D(4, 3), path.getXY(0)); + assertEquals(new Point2D(6, 8), path.getXY(1)); + assertEquals(new Point2D(7, 6), path.getXY(2)); + // from envelope + assertEquals(new Point2D(1, 5), path.getXY(3)); + assertEquals(new Point2D(1, 10), path.getXY(4)); + assertEquals(new Point2D(3, 10), path.getXY(5)); + assertEquals(new Point2D(3, 5), path.getXY(6)); + } + + @Test + public static void testUnionCoincidentPolygons() { + Polygon polygon1 = new Polygon(); + polygon1.startPath(new Point(3, 2)); + polygon1.lineTo(new Point(1, 2)); + polygon1.lineTo(new Point(1, 4)); + polygon1.lineTo(new Point(3, 4)); + polygon1.lineTo(new Point(3, 2)); + + Polygon polygon2 = new Polygon(); + polygon2.startPath(new Point(1, 2)); + polygon2.lineTo(new Point(1, 4)); + polygon2.lineTo(new Point(3, 4)); + polygon2.lineTo(new Point(3, 2)); + polygon2.lineTo(new Point(1, 2)); + + Geometry[] geomArray = new Geometry[] { polygon1, polygon2 }; + SimpleGeometryCursor inputGeometries = new SimpleGeometryCursor( + geomArray); + OperatorUnion union = (OperatorUnion) OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Union); + SpatialReference sr = SpatialReference.create(4326); + + GeometryCursor outputCursor = union.execute(inputGeometries, sr, null); + Geometry result = outputCursor.next(); + + MultiPath path = (MultiPath)result; + assertEquals(1, path.getPathCount()); + assertEquals(4, path.getPathEnd(0)); + assertEquals(new Point2D(1, 2), path.getXY(0)); + assertEquals(new Point2D(1, 4), path.getXY(1)); + assertEquals(new Point2D(3, 4), path.getXY(2)); + assertEquals(new Point2D(3, 2), path.getXY(3)); + } + + @Test + public static void testUnionCoincidentPolygonsWithReverseWinding() { + // Input polygons have CCW winding, result is always CW + Polygon polygon1 = new Polygon(); + polygon1.startPath(new Point(3, 2)); + polygon1.lineTo(new Point(3, 4)); + polygon1.lineTo(new Point(1, 4)); + polygon1.lineTo(new Point(1, 2)); + polygon1.lineTo(new Point(3, 2)); + + Polygon polygon2 = new Polygon(); + polygon2.startPath(new Point(1, 2)); + polygon2.lineTo(new Point(3, 2)); + polygon2.lineTo(new Point(3, 4)); + polygon2.lineTo(new Point(1, 4)); + polygon2.lineTo(new Point(1, 2)); + + Polygon expectedPolygon = new Polygon(); + expectedPolygon.startPath(new Point(1, 2)); + expectedPolygon.lineTo(new Point(1, 4)); + expectedPolygon.lineTo(new Point(3, 4)); + expectedPolygon.lineTo(new Point(3, 2)); + expectedPolygon.lineTo(new Point(1, 2)); + + Geometry[] geomArray = new Geometry[] { polygon1, polygon2 }; + SimpleGeometryCursor inputGeometries = new SimpleGeometryCursor( + geomArray); + OperatorUnion union = (OperatorUnion) OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Union); + SpatialReference sr = SpatialReference.create(4326); + + GeometryCursor outputCursor = union.execute(inputGeometries, sr, null); + Geometry result = outputCursor.next(); + + MultiPath path = (MultiPath)result; + assertEquals(1, path.getPathCount()); + assertEquals(4, path.getPathEnd(0)); + assertEquals(new Point2D(1, 2), path.getXY(0)); + assertEquals(new Point2D(1, 4), path.getXY(1)); + assertEquals(new Point2D(3, 4), path.getXY(2)); + assertEquals(new Point2D(3, 2), path.getXY(3)); } } From e631c8aadcc23d6dfdcfc6ec38668d7079bda522 Mon Sep 17 00:00:00 2001 From: danio Date: Thu, 29 Mar 2018 23:04:10 +0100 Subject: [PATCH 150/196] Ant doesn't work for building (#163) --- README.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index bcc099fe..46b42a05 100644 --- a/README.md +++ b/README.md @@ -14,8 +14,9 @@ The Esri Geometry API for Java can be used to enable spatial data processing in Building the source: 1. Download and unzip the .zip file, or clone the repository. -2. Deploy the esri-geometry-api.jar to the target system, add a reference to it in a Java project. -3. To build the jar, Javadoc, and run the unit-tests, run the “ant” command-line command from within the cloned directory. The ant tool runs the “build.xml” script which creates the jar, runs the unit tests, then creates the Javadoc documentation files. +1. To build the jar, run the `mvn compile` command-line command from within the cloned directory. +1. Deploy the esri-geometry-api.jar to the target system, add a reference to it in a Java project. +1. To run the unit-tests, run the `mvn test` command-line command from within the cloned directory. The project is also available as a [Maven](http://maven.apache.org/) dependency: @@ -30,7 +31,7 @@ The project is also available as a [Maven](http://maven.apache.org/) dependency: ## Requirements * Java JDK 1.6 or greater. -* [Apache Ant](http://ant.apache.org/) build system. +* [Apache Maven](https://maven.apache.org/) build system. * Experience developing MapReduce applications for [Apache Hadoop](http://hadoop.apache.org/). * Familiarity with text-based spatial data formats such as JSON or WKT would be useful. From 27adbbbb8e5bd54d8248dcb681347cfaec285994 Mon Sep 17 00:00:00 2001 From: Maria Basmanova Date: Mon, 9 Apr 2018 19:26:20 -0400 Subject: [PATCH 151/196] Add OGCGeometry#centroid operation (#169) @mbasmanova Thank you! --- .../java/com/esri/core/geometry/Operator.java | 2 +- .../core/geometry/OperatorCentroid2D.java | 40 +++++ .../geometry/OperatorCentroid2DLocal.java | 164 ++++++++++++++++++ .../core/geometry/OperatorFactoryLocal.java | 3 +- .../esri/core/geometry/ogc/OGCGeometry.java | 13 ++ .../core/geometry/ogc/OGCMultiSurface.java | 5 - .../esri/core/geometry/ogc/OGCSurface.java | 5 - .../com/esri/core/geometry/TestCentroid.java | 115 ++++++++++++ .../esri/core/geometry/TestOGCCentroid.java | 90 ++++++++++ 9 files changed, 425 insertions(+), 12 deletions(-) create mode 100644 src/main/java/com/esri/core/geometry/OperatorCentroid2D.java create mode 100644 src/main/java/com/esri/core/geometry/OperatorCentroid2DLocal.java create mode 100644 src/test/java/com/esri/core/geometry/TestCentroid.java create mode 100644 src/test/java/com/esri/core/geometry/TestOGCCentroid.java diff --git a/src/main/java/com/esri/core/geometry/Operator.java b/src/main/java/com/esri/core/geometry/Operator.java index 9dcd804d..6866fb16 100644 --- a/src/main/java/com/esri/core/geometry/Operator.java +++ b/src/main/java/com/esri/core/geometry/Operator.java @@ -40,7 +40,7 @@ public enum Type { Union, Difference, - Proximity2D, + Proximity2D, Centroid2D, Relate, Equals, Disjoint, Intersects, Within, Contains, Crosses, Touches, Overlaps, diff --git a/src/main/java/com/esri/core/geometry/OperatorCentroid2D.java b/src/main/java/com/esri/core/geometry/OperatorCentroid2D.java new file mode 100644 index 00000000..f44a21f8 --- /dev/null +++ b/src/main/java/com/esri/core/geometry/OperatorCentroid2D.java @@ -0,0 +1,40 @@ +/* + Copyright 1995-2017 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ +package com.esri.core.geometry; + +public abstract class OperatorCentroid2D extends Operator +{ + @Override + public Type getType() + { + return Type.Centroid2D; + } + + public abstract Point2D execute(Geometry geometry, ProgressTracker progressTracker); + + public static OperatorCentroid2D local() + { + return (OperatorCentroid2D) OperatorFactoryLocal.getInstance().getOperator(Type.Centroid2D); + } +} diff --git a/src/main/java/com/esri/core/geometry/OperatorCentroid2DLocal.java b/src/main/java/com/esri/core/geometry/OperatorCentroid2DLocal.java new file mode 100644 index 00000000..91b7c948 --- /dev/null +++ b/src/main/java/com/esri/core/geometry/OperatorCentroid2DLocal.java @@ -0,0 +1,164 @@ +/* + Copyright 1995-2017 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ +package com.esri.core.geometry; + +import static java.lang.Math.sqrt; + +public class OperatorCentroid2DLocal extends OperatorCentroid2D +{ + @Override + public Point2D execute(Geometry geometry, ProgressTracker progressTracker) + { + if (geometry.isEmpty()) { + return null; + } + + Geometry.Type geometryType = geometry.getType(); + switch (geometryType) { + case Point: + return ((Point) geometry).getXY(); + case Line: + return computeLineCentroid((Line) geometry); + case Envelope: + return ((Envelope) geometry).getCenterXY(); + case MultiPoint: + return computePointsCentroid((MultiPoint) geometry); + case Polyline: + return computePolylineCentroid(((Polyline) geometry)); + case Polygon: + return computePolygonCentroid((Polygon) geometry); + default: + throw new UnsupportedOperationException("Unexpected geometry type: " + geometryType); + } + } + + private static Point2D computeLineCentroid(Line line) + { + return new Point2D((line.getEndX() - line.getStartX()) / 2, (line.getEndY() - line.getStartY()) / 2); + } + + // Points centroid is arithmetic mean of the input points + private static Point2D computePointsCentroid(MultiPoint multiPoint) + { + double xSum = 0; + double ySum = 0; + int pointCount = multiPoint.getPointCount(); + Point2D point2D = new Point2D(); + for (int i = 0; i < pointCount; i++) { + multiPoint.getXY(i, point2D); + xSum += point2D.x; + ySum += point2D.y; + } + return new Point2D(xSum / pointCount, ySum / pointCount); + } + + // Lines centroid is weighted mean of each line segment, weight in terms of line length + private static Point2D computePolylineCentroid(Polyline polyline) + { + double xSum = 0; + double ySum = 0; + double weightSum = 0; + + Point2D startPoint = new Point2D(); + Point2D endPoint = new Point2D(); + for (int i = 0; i < polyline.getPathCount(); i++) { + polyline.getXY(polyline.getPathStart(i), startPoint); + polyline.getXY(polyline.getPathEnd(i) - 1, endPoint); + double dx = endPoint.x - startPoint.x; + double dy = endPoint.y - startPoint.y; + double length = sqrt(dx * dx + dy * dy); + weightSum += length; + xSum += (startPoint.x + endPoint.x) * length / 2; + ySum += (startPoint.y + endPoint.y) * length / 2; + } + return new Point2D(xSum / weightSum, ySum / weightSum); + } + + // Polygon centroid: area weighted average of centroids in case of holes + private static Point2D computePolygonCentroid(Polygon polygon) + { + int pathCount = polygon.getPathCount(); + + if (pathCount == 1) { + return getPolygonSansHolesCentroid(polygon); + } + + double xSum = 0; + double ySum = 0; + double areaSum = 0; + + for (int i = 0; i < pathCount; i++) { + int startIndex = polygon.getPathStart(i); + int endIndex = polygon.getPathEnd(i); + + Polygon sansHoles = getSubPolygon(polygon, startIndex, endIndex); + + Point2D centroid = getPolygonSansHolesCentroid(sansHoles); + double area = sansHoles.calculateArea2D(); + + xSum += centroid.x * area; + ySum += centroid.y * area; + areaSum += area; + } + + return new Point2D(xSum / areaSum, ySum / areaSum); + } + + private static Polygon getSubPolygon(Polygon polygon, int startIndex, int endIndex) + { + Polyline boundary = new Polyline(); + boundary.startPath(polygon.getPoint(startIndex)); + for (int i = startIndex + 1; i < endIndex; i++) { + Point current = polygon.getPoint(i); + boundary.lineTo(current); + } + + final Polygon newPolygon = new Polygon(); + newPolygon.add(boundary, false); + return newPolygon; + } + + // Polygon sans holes centroid: + // c[x] = (Sigma(x[i] + x[i + 1]) * (x[i] * y[i + 1] - x[i + 1] * y[i]), for i = 0 to N - 1) / (6 * signedArea) + // c[y] = (Sigma(y[i] + y[i + 1]) * (x[i] * y[i + 1] - x[i + 1] * y[i]), for i = 0 to N - 1) / (6 * signedArea) + private static Point2D getPolygonSansHolesCentroid(Polygon polygon) + { + int pointCount = polygon.getPointCount(); + double xSum = 0; + double ySum = 0; + double signedArea = 0; + + Point2D current = new Point2D(); + Point2D next = new Point2D(); + for (int i = 0; i < pointCount; i++) { + polygon.getXY(i, current); + polygon.getXY((i + 1) % pointCount, next); + double ladder = current.x * next.y - next.x * current.y; + xSum += (current.x + next.x) * ladder; + ySum += (current.y + next.y) * ladder; + signedArea += ladder / 2; + } + return new Point2D(xSum / (signedArea * 6), ySum / (signedArea * 6)); + } +} diff --git a/src/main/java/com/esri/core/geometry/OperatorFactoryLocal.java b/src/main/java/com/esri/core/geometry/OperatorFactoryLocal.java index 727b4a69..153b4728 100644 --- a/src/main/java/com/esri/core/geometry/OperatorFactoryLocal.java +++ b/src/main/java/com/esri/core/geometry/OperatorFactoryLocal.java @@ -29,7 +29,6 @@ import java.io.BufferedReader; import java.io.FileInputStream; import java.io.FileOutputStream; -import java.io.IOException; import java.io.InputStreamReader; import java.io.PrintStream; import java.io.Reader; @@ -61,6 +60,8 @@ public class OperatorFactoryLocal extends OperatorFactory { st_supportedOperators.put(Type.Proximity2D, new OperatorProximity2DLocal()); + st_supportedOperators.put(Type.Centroid2D, + new OperatorCentroid2DLocal()); st_supportedOperators.put(Type.DensifyByLength, new OperatorDensifyByLengthLocal()); diff --git a/src/main/java/com/esri/core/geometry/ogc/OGCGeometry.java b/src/main/java/com/esri/core/geometry/ogc/OGCGeometry.java index a3f60a6d..30d11f67 100644 --- a/src/main/java/com/esri/core/geometry/ogc/OGCGeometry.java +++ b/src/main/java/com/esri/core/geometry/ogc/OGCGeometry.java @@ -38,6 +38,7 @@ import com.esri.core.geometry.OGCStructure; import com.esri.core.geometry.Operator; import com.esri.core.geometry.OperatorBuffer; +import com.esri.core.geometry.OperatorCentroid2D; import com.esri.core.geometry.OperatorConvexHull; import com.esri.core.geometry.OperatorExportToGeoJson; import com.esri.core.geometry.OperatorExportToWkb; @@ -51,6 +52,7 @@ import com.esri.core.geometry.OperatorSimplifyOGC; import com.esri.core.geometry.OperatorUnion; import com.esri.core.geometry.Point; +import com.esri.core.geometry.Point2D; import com.esri.core.geometry.Polygon; import com.esri.core.geometry.Polyline; import com.esri.core.geometry.SimpleGeometryCursor; @@ -427,6 +429,17 @@ public OGCGeometry buffer(double distance) { return OGCGeometry.createFromEsriGeometry(cursor.next(), esriSR); } + public OGCGeometry centroid() { + OperatorCentroid2D op = (OperatorCentroid2D) OperatorFactoryLocal.getInstance() + .getOperator(Operator.Type.Centroid2D); + + Point2D centroid = op.execute(getEsriGeometry(), null); + if (centroid == null) { + return OGCGeometry.createFromEsriGeometry(new Point(), esriSR); + } + return OGCGeometry.createFromEsriGeometry(new Point(centroid), esriSR); + } + public OGCGeometry convexHull() { com.esri.core.geometry.OperatorConvexHull op = (OperatorConvexHull) OperatorFactoryLocal .getInstance().getOperator(Operator.Type.ConvexHull); diff --git a/src/main/java/com/esri/core/geometry/ogc/OGCMultiSurface.java b/src/main/java/com/esri/core/geometry/ogc/OGCMultiSurface.java index 1c98be92..5a36dd0e 100644 --- a/src/main/java/com/esri/core/geometry/ogc/OGCMultiSurface.java +++ b/src/main/java/com/esri/core/geometry/ogc/OGCMultiSurface.java @@ -29,11 +29,6 @@ public double area() { return getEsriGeometry().calculateArea2D(); } - public OGCPoint centroid() { - // TODO - throw new UnsupportedOperationException(); - } - public OGCPoint pointOnSurface() { // TODO throw new UnsupportedOperationException(); diff --git a/src/main/java/com/esri/core/geometry/ogc/OGCSurface.java b/src/main/java/com/esri/core/geometry/ogc/OGCSurface.java index 43d192e6..989dfec8 100644 --- a/src/main/java/com/esri/core/geometry/ogc/OGCSurface.java +++ b/src/main/java/com/esri/core/geometry/ogc/OGCSurface.java @@ -29,11 +29,6 @@ public double area() { return getEsriGeometry().calculateArea2D(); } - public OGCPoint centroid() { - // TODO: implement me; - throw new UnsupportedOperationException(); - } - public OGCPoint pointOnSurface() { // TODO: support this (need to port OperatorLabelPoint) throw new UnsupportedOperationException(); diff --git a/src/test/java/com/esri/core/geometry/TestCentroid.java b/src/test/java/com/esri/core/geometry/TestCentroid.java new file mode 100644 index 00000000..58c430fb --- /dev/null +++ b/src/test/java/com/esri/core/geometry/TestCentroid.java @@ -0,0 +1,115 @@ +/* + Copyright 1995-2017 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ +package com.esri.core.geometry; + +import org.junit.Assert; +import org.junit.Test; + +public class TestCentroid +{ + @Test + public void testPoint() + { + assertCentroid(new Point(1, 2), new Point2D(1, 2)); + } + + @Test + public void testLine() + { + assertCentroid(new Line(0, 0, 10, 20), new Point2D(5, 10)); + } + + @Test + public void testEnvelope() + { + assertCentroid(new Envelope(1, 2, 3,4), new Point2D(2, 3)); + assertCentroid(new Envelope(), null); + } + + @Test + public void testMultiPoint() + { + MultiPoint multiPoint = new MultiPoint(); + multiPoint.add(0, 0); + multiPoint.add(1, 2); + multiPoint.add(3, 1); + multiPoint.add(0, 1); + + assertCentroid(multiPoint, new Point2D(1, 1)); + assertCentroid(new MultiPoint(), null); + } + + @Test + public void testPolyline() + { + Polyline polyline = new Polyline(); + polyline.startPath(0, 0); + polyline.lineTo(1, 2); + polyline.lineTo(3, 4); + assertCentroid(polyline, new Point2D(1.5, 2)); + + polyline.startPath(1, -1); + polyline.lineTo(2, 0); + polyline.lineTo(10, 1); + assertCentroid(polyline, new Point2D(4.093485180902371 , 0.7032574095488145)); + + assertCentroid(new Polyline(), null); + } + + @Test + public void testPolygon() + { + Polygon polygon = new Polygon(); + polygon.startPath(0, 0); + polygon.lineTo(1, 2); + polygon.lineTo(3, 4); + polygon.lineTo(5, 2); + polygon.lineTo(0, 0); + assertCentroid(polygon, new Point2D(2.5, 2)); + + // add a hole + polygon.startPath(2, 2); + polygon.lineTo(2.3, 2); + polygon.lineTo(2.3, 2.4); + polygon.lineTo(2, 2); + assertCentroid(polygon, new Point2D(2.5022670025188916 , 1.9989924433249369)); + + // add another polygon + polygon.startPath(-1, -1); + polygon.lineTo(3, -1); + polygon.lineTo(0.5, -2); + polygon.lineTo(-1, -1); + assertCentroid(polygon, new Point2D(2.166465459423206 , 1.3285043594902748)); + + assertCentroid(new Polygon(), null); + } + + private static void assertCentroid(Geometry geometry, Point2D expectedCentroid) + { + OperatorCentroid2D operator = (OperatorCentroid2D) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.Centroid2D); + + Point2D actualCentroid = operator.execute(geometry, null); + Assert.assertEquals(expectedCentroid, actualCentroid); + } +} diff --git a/src/test/java/com/esri/core/geometry/TestOGCCentroid.java b/src/test/java/com/esri/core/geometry/TestOGCCentroid.java new file mode 100644 index 00000000..bf183bb9 --- /dev/null +++ b/src/test/java/com/esri/core/geometry/TestOGCCentroid.java @@ -0,0 +1,90 @@ +/* + Copyright 1995-2017 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ +package com.esri.core.geometry; + +import com.esri.core.geometry.ogc.OGCGeometry; +import com.esri.core.geometry.ogc.OGCPoint; +import org.junit.Assert; +import org.junit.Test; + +public class TestOGCCentroid +{ + @Test + public void testPoint() + { + assertCentroid("POINT (1 2)", new Point(1, 2)); + assertEmptyCentroid("POINT EMPTY"); + } + + @Test + public void testLineString() + { + assertCentroid("LINESTRING (1 1, 2 2, 3 3)", new Point(2, 2)); + assertEmptyCentroid("LINESTRING EMPTY"); + } + + @Test + public void testPolygon() + { + assertCentroid("POLYGON ((1 1, 1 4, 4 4, 4 1))'", new Point(2.5, 2.5)); + assertCentroid("POLYGON ((1 1, 5 1, 3 4))", new Point(3, 2)); + assertCentroid("POLYGON ((0 0, 0 5, 5 5, 5 0, 0 0), (1 1, 1 2, 2 2, 2 1, 1 1))", new Point(2.5416666666666665, 2.5416666666666665)); + assertEmptyCentroid("POLYGON EMPTY"); + } + + @Test + public void testMultiPoint() + { + assertCentroid("MULTIPOINT (1 2, 2 4, 3 6, 4 8)", new Point(2.5, 5)); + assertEmptyCentroid("MULTIPOINT EMPTY"); + } + + @Test + public void testMultiLineString() + { + assertCentroid("MULTILINESTRING ((1 1, 5 1), (2 4, 4 4))')))", new Point(3, 2)); + assertEmptyCentroid("MULTILINESTRING EMPTY"); + } + + @Test + public void testMultiPolygon() + { + assertCentroid("MULTIPOLYGON (((1 1, 1 3, 3 3, 3 1)), ((2 4, 2 6, 6 6, 6 4)))", new Point (3.3333333333333335,4)); + assertEmptyCentroid("MULTIPOLYGON EMPTY"); + } + + private static void assertCentroid(String wkt, Point expectedCentroid) + { + OGCGeometry geometry = OGCGeometry.fromText(wkt); + OGCGeometry centroid = geometry.centroid(); + Assert.assertEquals(centroid, new OGCPoint(expectedCentroid, geometry.getEsriSpatialReference())); + } + + private static void assertEmptyCentroid(String wkt) + { + OGCGeometry geometry = OGCGeometry.fromText(wkt); + OGCGeometry centroid = geometry.centroid(); + Assert.assertEquals(centroid, new OGCPoint(new Point(), geometry.getEsriSpatialReference())); + } +} From 2407d0b1e3149445f7cee7abe9761fe0109c51d4 Mon Sep 17 00:00:00 2001 From: Maria Basmanova Date: Mon, 9 Apr 2018 20:12:38 -0400 Subject: [PATCH 152/196] Fix Envelope#intersect when other is empty (#168) Thanks! --- .../com/esri/core/geometry/Envelope2D.java | 4 +- .../com/esri/core/geometry/TestEnvelope.java | 49 +++++++++++++++++++ 2 files changed, 52 insertions(+), 1 deletion(-) create mode 100644 src/test/java/com/esri/core/geometry/TestEnvelope.java diff --git a/src/main/java/com/esri/core/geometry/Envelope2D.java b/src/main/java/com/esri/core/geometry/Envelope2D.java index 8e44dd33..79433dd7 100644 --- a/src/main/java/com/esri/core/geometry/Envelope2D.java +++ b/src/main/java/com/esri/core/geometry/Envelope2D.java @@ -321,8 +321,10 @@ public boolean isIntersecting(double xmin_, double ymin_, double xmax_, double y * envelope to empty state and returns False. */ public boolean intersect(Envelope2D other) { - if (isEmpty() || other.isEmpty()) + if (isEmpty() || other.isEmpty()) { + setEmpty(); return false; + } if (other.xmin > xmin) xmin = other.xmin; diff --git a/src/test/java/com/esri/core/geometry/TestEnvelope.java b/src/test/java/com/esri/core/geometry/TestEnvelope.java new file mode 100644 index 00000000..56edd466 --- /dev/null +++ b/src/test/java/com/esri/core/geometry/TestEnvelope.java @@ -0,0 +1,49 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.esri.core.geometry; + +import org.junit.Test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +public class TestEnvelope +{ + @Test + public void testIntersect() + { + assertIntersection(new Envelope(0, 0, 5, 5), new Envelope(0, 0, 5, 5), new Envelope(0, 0, 5, 5)); + assertIntersection(new Envelope(0, 0, 5, 5), new Envelope(1, 1, 6, 6), new Envelope(1, 1, 5, 5)); + assertIntersection(new Envelope(1, 2, 3, 4), new Envelope(0, 0, 2, 3), new Envelope(1, 2, 2, 3)); + + assertNoIntersection(new Envelope(), new Envelope()); + assertNoIntersection(new Envelope(0, 0, 5, 5), new Envelope()); + assertNoIntersection(new Envelope(), new Envelope(0, 0, 5, 5)); + } + + private static void assertIntersection(Envelope envelope, Envelope other, Envelope intersection) + { + boolean intersects = envelope.intersect(other); + assertTrue(intersects); + assertEquals(envelope, intersection); + } + + private static void assertNoIntersection(Envelope envelope, Envelope other) + { + boolean intersects = envelope.intersect(other); + assertFalse(intersects); + assertTrue(envelope.isEmpty()); + } +} From f68c2413d6a1849238a71d59741f9667e98a1924 Mon Sep 17 00:00:00 2001 From: Sergey Tolstov Date: Fri, 20 Apr 2018 09:55:34 -0700 Subject: [PATCH 153/196] Fix a typo in usage of Export flags (#171) WktExportFlags.wktExportPolygon is used instead of WkbExportFlags.wkbExportPolygon. They have same value, so there is no bug yet, but it's better to fix that. --- .../java/com/esri/core/geometry/OperatorExportToWkbLocal.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/esri/core/geometry/OperatorExportToWkbLocal.java b/src/main/java/com/esri/core/geometry/OperatorExportToWkbLocal.java index b87b6a8f..bb3abaad 100644 --- a/src/main/java/com/esri/core/geometry/OperatorExportToWkbLocal.java +++ b/src/main/java/com/esri/core/geometry/OperatorExportToWkbLocal.java @@ -170,7 +170,7 @@ else if (wkbBuffer.capacity() < size) if (!bExportZs && !bExportMs) { type = WkbGeometryType.wkbPolygon; - if ((exportFlags & WktExportFlags.wktExportPolygon) == 0) { + if ((exportFlags & WkbExportFlags.wkbExportPolygon) == 0) { wkbBuffer.put(offset, byteOrder); offset += 1; wkbBuffer.putInt(offset, WkbGeometryType.wkbMultiPolygon); From bbfe1d32f9faece46677c651d6965ba1f24e1e46 Mon Sep 17 00:00:00 2001 From: Sergey Tolstov Date: Mon, 14 May 2018 14:14:19 -0700 Subject: [PATCH 154/196] Stolstov/issue 172 (#174) --- .../com/esri/core/geometry/ConvexHull.java | 8 ++- .../ogc/OGCConcreteGeometryCollection.java | 61 +++++++++++++++++ .../com/esri/core/geometry/ogc/OGCCurve.java | 3 + .../esri/core/geometry/ogc/OGCGeometry.java | 16 ++--- .../esri/core/geometry/ogc/OGCLineString.java | 3 + .../esri/core/geometry/TestConvexHull.java | 66 +++++++++++++++++++ .../java/com/esri/core/geometry/TestOGC.java | 30 +++++++++ 7 files changed, 177 insertions(+), 10 deletions(-) diff --git a/src/main/java/com/esri/core/geometry/ConvexHull.java b/src/main/java/com/esri/core/geometry/ConvexHull.java index ab4b89c9..8e47bb86 100644 --- a/src/main/java/com/esri/core/geometry/ConvexHull.java +++ b/src/main/java/com/esri/core/geometry/ConvexHull.java @@ -52,10 +52,13 @@ private ConvexHull(Point2D[] points, int n) { /** * Adds a geometry to the current bounding geometry using an incremental algorithm for dynamic insertion. - * \param geometry The geometry to add to the bounding geometry. + * @param geometry The geometry to add to the bounding geometry. */ void addGeometry(Geometry geometry) { + if (geometry.isEmpty()) + return; + int type = geometry.getType().value(); if (MultiVertexGeometry.isMultiVertex(type)) @@ -80,6 +83,9 @@ Geometry getBoundingGeometry() { Point point = new Point(); int first = m_tree_hull.getFirst(-1); Polygon hull = new Polygon(m_shape.getVertexDescription()); + if (m_tree_hull.size(-1) == 0) + return hull; + m_shape.queryPoint(m_tree_hull.getElement(first), point); hull.startPath(point); diff --git a/src/main/java/com/esri/core/geometry/ogc/OGCConcreteGeometryCollection.java b/src/main/java/com/esri/core/geometry/ogc/OGCConcreteGeometryCollection.java index 51e171e7..ce48b82a 100644 --- a/src/main/java/com/esri/core/geometry/ogc/OGCConcreteGeometryCollection.java +++ b/src/main/java/com/esri/core/geometry/ogc/OGCConcreteGeometryCollection.java @@ -27,11 +27,19 @@ import com.esri.core.geometry.Envelope; import com.esri.core.geometry.Geometry; import com.esri.core.geometry.GeometryCursor; +import com.esri.core.geometry.GeometryException; +import com.esri.core.geometry.MultiPath; +import com.esri.core.geometry.MultiPoint; +import com.esri.core.geometry.MultiVertexGeometry; import com.esri.core.geometry.NumberUtils; +import com.esri.core.geometry.OperatorConvexHull; import com.esri.core.geometry.Polygon; +import com.esri.core.geometry.SimpleGeometryCursor; import com.esri.core.geometry.SpatialReference; +import com.esri.core.geometry.VertexDescription; import com.esri.core.geometry.GeoJsonExportFlags; import com.esri.core.geometry.OperatorExportToGeoJson; +import com.esri.core.geometry.Point; import java.nio.ByteBuffer; import java.nio.ByteOrder; @@ -377,6 +385,59 @@ public int getGeometryID() { } } + + @Override + public OGCGeometry convexHull() { + GeometryCursor cursor = OperatorConvexHull.local().execute( + getEsriGeometryCursor(), false, null); + MultiPoint mp = new MultiPoint(); + Polygon polygon = new Polygon(); + VertexDescription vd = null; + for (Geometry geom = cursor.next(); geom != null; geom = cursor.next()) { + vd = geom.getDescription(); + if (geom.isEmpty()) + continue; + + if (geom.getType() == Geometry.Type.Polygon) { + polygon.add((MultiPath) geom, false); + } + else if (geom.getType() == Geometry.Type.Polyline) { + mp.add((MultiVertexGeometry) geom, 0, -1); + } + else if (geom.getType() == Geometry.Type.Point) { + mp.add((Point) geom); + } + else { + throw new GeometryException("internal error"); + } + } + + Geometry resultGeom = null; + if (!mp.isEmpty()) { + resultGeom = OperatorConvexHull.local().execute(mp, null); + } + + if (!polygon.isEmpty()) { + if (!resultGeom.isEmpty()) { + Geometry[] geoms = { resultGeom, polygon }; + resultGeom = OperatorConvexHull.local().execute( + new SimpleGeometryCursor(geoms), true, null).next(); + } + else { + resultGeom = polygon; + } + } + + if (resultGeom == null) { + Point pt = new Point(); + if (vd != null) + pt.assignVertexDescription(vd); + + return new OGCPoint(pt, getEsriSpatialReference()); + } + + return OGCGeometry.createFromEsriGeometry(resultGeom, getEsriSpatialReference(), false); + } List geometries; diff --git a/src/main/java/com/esri/core/geometry/ogc/OGCCurve.java b/src/main/java/com/esri/core/geometry/ogc/OGCCurve.java index 0755dc2e..dd229e5e 100644 --- a/src/main/java/com/esri/core/geometry/ogc/OGCCurve.java +++ b/src/main/java/com/esri/core/geometry/ogc/OGCCurve.java @@ -41,6 +41,9 @@ public boolean isRing() { @Override public OGCGeometry boundary() { + if (isEmpty()) + return new OGCMultiPoint(this.getEsriSpatialReference()); + if (isClosed()) return new OGCMultiPoint(new MultiPoint(getEsriGeometry() .getDescription()), esriSR);// return empty multipoint; diff --git a/src/main/java/com/esri/core/geometry/ogc/OGCGeometry.java b/src/main/java/com/esri/core/geometry/ogc/OGCGeometry.java index 30d11f67..058a47b0 100644 --- a/src/main/java/com/esri/core/geometry/ogc/OGCGeometry.java +++ b/src/main/java/com/esri/core/geometry/ogc/OGCGeometry.java @@ -433,18 +433,16 @@ public OGCGeometry centroid() { OperatorCentroid2D op = (OperatorCentroid2D) OperatorFactoryLocal.getInstance() .getOperator(Operator.Type.Centroid2D); - Point2D centroid = op.execute(getEsriGeometry(), null); - if (centroid == null) { - return OGCGeometry.createFromEsriGeometry(new Point(), esriSR); - } - return OGCGeometry.createFromEsriGeometry(new Point(centroid), esriSR); + Point2D centroid = op.execute(getEsriGeometry(), null); + if (centroid == null) { + return OGCGeometry.createFromEsriGeometry(new Point(), esriSR); + } + return OGCGeometry.createFromEsriGeometry(new Point(centroid), esriSR); } public OGCGeometry convexHull() { - com.esri.core.geometry.OperatorConvexHull op = (OperatorConvexHull) OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.ConvexHull); - com.esri.core.geometry.GeometryCursor cursor = op.execute( - getEsriGeometryCursor(), true, null); + com.esri.core.geometry.GeometryCursor cursor = OperatorConvexHull.local().execute( + getEsriGeometryCursor(), false, null); return OGCGeometry.createFromEsriCursor(cursor, esriSR); } diff --git a/src/main/java/com/esri/core/geometry/ogc/OGCLineString.java b/src/main/java/com/esri/core/geometry/ogc/OGCLineString.java index 464b9a7c..8d086c3b 100644 --- a/src/main/java/com/esri/core/geometry/ogc/OGCLineString.java +++ b/src/main/java/com/esri/core/geometry/ogc/OGCLineString.java @@ -82,6 +82,9 @@ public OGCPoint pointN(int n) { @Override public boolean isClosed() { + if (isEmpty()) + return false; + return multiPath.isClosedPathInXYPlane(0); } diff --git a/src/test/java/com/esri/core/geometry/TestConvexHull.java b/src/test/java/com/esri/core/geometry/TestConvexHull.java index b2e2d59e..b29d3aaf 100644 --- a/src/test/java/com/esri/core/geometry/TestConvexHull.java +++ b/src/test/java/com/esri/core/geometry/TestConvexHull.java @@ -27,6 +27,8 @@ import junit.framework.TestCase; import org.junit.Test; +import com.esri.core.geometry.ogc.OGCGeometry; + public class TestConvexHull extends TestCase { @Override protected void setUp() throws Exception { @@ -992,5 +994,69 @@ public void testHullTickTock() { assertTrue(p5.x == -5.0 && p5.y == 1.25); assertTrue(p6.x == 0.0 && p6.y == 10.0); } + + @Test + public void testHullIssueGithub172() { + { + //empty + OGCGeometry geom = OGCGeometry.fromText("MULTIPOINT EMPTY"); + OGCGeometry result = geom.convexHull(); + String text = result.asText(); + assertTrue(text.compareTo("POINT EMPTY") == 0); + } + { + //Point + OGCGeometry geom = OGCGeometry.fromText("POINT (1 2)"); + OGCGeometry result = geom.convexHull(); + String text = result.asText(); + assertTrue(text.compareTo("POINT (1 2)") == 0); + } + { + //line + OGCGeometry geom = OGCGeometry.fromText("MULTIPOINT (1 1, 2 2)"); + OGCGeometry result = geom.convexHull(); + String text = result.asText(); + assertTrue(text.compareTo("LINESTRING (1 1, 2 2)") == 0); + } + { + //empty + OGCGeometry geom = OGCGeometry.fromText("GEOMETRYCOLLECTION EMPTY"); + OGCGeometry result = geom.convexHull(); + String text = result.asText(); + assertTrue(text.compareTo("POINT EMPTY") == 0); + } + + { + //empty + OGCGeometry geom = OGCGeometry.fromText("GEOMETRYCOLLECTION (POINT (1 2))"); + OGCGeometry result = geom.convexHull(); + String text = result.asText(); + assertTrue(text.compareTo("POINT (1 2)") == 0); + } + + { + //empty + OGCGeometry geom = OGCGeometry.fromText("GEOMETRYCOLLECTION(POLYGON EMPTY, POINT(1 1), LINESTRING EMPTY, MULTIPOLYGON EMPTY, MULTILINESTRING EMPTY)"); + OGCGeometry result = geom.convexHull(); + String text = result.asText(); + assertTrue(text.compareTo("POINT (1 1)") == 0); + } + + { + //empty + OGCGeometry geom = OGCGeometry.fromText("GEOMETRYCOLLECTION(POLYGON EMPTY, LINESTRING (1 1, 2 2), POINT(3 3), LINESTRING EMPTY, MULTIPOLYGON EMPTY, MULTILINESTRING EMPTY)"); + OGCGeometry result = geom.convexHull(); + String text = result.asText(); + assertTrue(text.compareTo("LINESTRING (1 1, 3 3)") == 0); + } + + { + //empty + OGCGeometry geom = OGCGeometry.fromText("GEOMETRYCOLLECTION(POLYGON EMPTY, LINESTRING (1 1, 2 2), POINT(3 3), LINESTRING EMPTY, POLYGON((-10 -10, 10 -10, 10 10, -10 10, -10 -10), (-5 -5, -5 5, 5 5, 5 -5, -5 -5)))"); + OGCGeometry result = geom.convexHull(); + String text = result.asText(); + assertTrue(text.compareTo("POLYGON ((-10 -10, 10 -10, 10 10, -10 10, -10 -10))") == 0); + } + } } diff --git a/src/test/java/com/esri/core/geometry/TestOGC.java b/src/test/java/com/esri/core/geometry/TestOGC.java index ca2bcf6d..31263705 100644 --- a/src/test/java/com/esri/core/geometry/TestOGC.java +++ b/src/test/java/com/esri/core/geometry/TestOGC.java @@ -926,4 +926,34 @@ public void testPolylineSimplifyIssueGithub52() throws Exception { } } + @Test + public void testEmptyBoundary() throws Exception { + { + OGCGeometry g = OGCGeometry.fromText("POINT EMPTY"); + OGCGeometry b = g.boundary(); + assertTrue(b.asText().compareTo("MULTIPOINT EMPTY") == 0); + } + { + OGCGeometry g = OGCGeometry.fromText("MULTIPOINT EMPTY"); + OGCGeometry b = g.boundary(); + assertTrue(b.asText().compareTo("MULTIPOINT EMPTY") == 0); + } + { + OGCGeometry g = OGCGeometry.fromText("LINESTRING EMPTY"); + OGCGeometry b = g.boundary(); + assertTrue(b.asText().compareTo("MULTIPOINT EMPTY") == 0); + } + { + OGCGeometry g = OGCGeometry.fromText("POLYGON EMPTY"); + OGCGeometry b = g.boundary(); + assertTrue(b.asText().compareTo("MULTILINESTRING EMPTY") == 0); + } + { + OGCGeometry g = OGCGeometry.fromText("MULTIPOLYGON EMPTY"); + OGCGeometry b = g.boundary(); + assertTrue(b.asText().compareTo("MULTILINESTRING EMPTY") == 0); + } + } + + } From e3e0d6ee3d3c5837e29c2acdb35ede34c0857353 Mon Sep 17 00:00:00 2001 From: Sergey Tolstov Date: Sun, 27 May 2018 22:50:07 -0700 Subject: [PATCH 155/196] Adding collection handling methods --- .../java/com/esri/core/geometry/Geometry.java | 2 +- .../com/esri/core/geometry/OGCStructure.java | 5 + .../ogc/OGCConcreteGeometryCollection.java | 308 +++++++++++++++++- .../esri/core/geometry/ogc/OGCGeometry.java | 17 +- .../esri/core/geometry/ogc/OGCLineString.java | 5 +- .../core/geometry/ogc/OGCMultiLineString.java | 21 +- .../esri/core/geometry/ogc/OGCMultiPoint.java | 4 +- .../core/geometry/ogc/OGCMultiPolygon.java | 10 +- .../com/esri/core/geometry/ogc/OGCPoint.java | 4 +- .../esri/core/geometry/ogc/OGCPolygon.java | 4 +- .../java/com/esri/core/geometry/TestOGC.java | 31 ++ 11 files changed, 390 insertions(+), 21 deletions(-) diff --git a/src/main/java/com/esri/core/geometry/Geometry.java b/src/main/java/com/esri/core/geometry/Geometry.java index 8a71a236..d108d328 100644 --- a/src/main/java/com/esri/core/geometry/Geometry.java +++ b/src/main/java/com/esri/core/geometry/Geometry.java @@ -456,7 +456,7 @@ public static int getDimensionFromType(int type) { * @param type * The integer value from geometry enumeration. You can use the * method {@link Type#value()} to get at the integer value. - * @return TRUE if the geometry is a point. + * @return TRUE if the geometry is a point (a Point or a Multipoint). */ public static boolean isPoint(int type) { return (type & 0x20) != 0; diff --git a/src/main/java/com/esri/core/geometry/OGCStructure.java b/src/main/java/com/esri/core/geometry/OGCStructure.java index 9f0875b9..1a9891d9 100644 --- a/src/main/java/com/esri/core/geometry/OGCStructure.java +++ b/src/main/java/com/esri/core/geometry/OGCStructure.java @@ -23,8 +23,13 @@ */ package com.esri.core.geometry; +import java.util.ArrayList; import java.util.List; +import com.esri.core.geometry.ogc.OGCConcreteGeometryCollection; +import com.esri.core.geometry.ogc.OGCGeometry; +import com.esri.core.geometry.ogc.OGCGeometryCollection; + public class OGCStructure { public int m_type; public List m_structures; diff --git a/src/main/java/com/esri/core/geometry/ogc/OGCConcreteGeometryCollection.java b/src/main/java/com/esri/core/geometry/ogc/OGCConcreteGeometryCollection.java index ce48b82a..aa4805f3 100644 --- a/src/main/java/com/esri/core/geometry/ogc/OGCConcreteGeometryCollection.java +++ b/src/main/java/com/esri/core/geometry/ogc/OGCConcreteGeometryCollection.java @@ -32,13 +32,17 @@ import com.esri.core.geometry.MultiPoint; import com.esri.core.geometry.MultiVertexGeometry; import com.esri.core.geometry.NumberUtils; +import com.esri.core.geometry.OGCStructureInternal; import com.esri.core.geometry.OperatorConvexHull; +import com.esri.core.geometry.OperatorDifference; import com.esri.core.geometry.Polygon; +import com.esri.core.geometry.Polyline; import com.esri.core.geometry.SimpleGeometryCursor; import com.esri.core.geometry.SpatialReference; import com.esri.core.geometry.VertexDescription; import com.esri.core.geometry.GeoJsonExportFlags; import com.esri.core.geometry.OperatorExportToGeoJson; +import com.esri.core.geometry.OperatorUnion; import com.esri.core.geometry.Point; import java.nio.ByteBuffer; @@ -49,11 +53,26 @@ import static com.esri.core.geometry.SizeOf.SIZE_OF_OGC_CONCRETE_GEOMETRY_COLLECTION; public class OGCConcreteGeometryCollection extends OGCGeometryCollection { + static public String TYPE = "GeometryCollection"; + + List geometries; + public OGCConcreteGeometryCollection(List geoms, SpatialReference sr) { geometries = geoms; esriSR = sr; } + + public OGCConcreteGeometryCollection(GeometryCursor geoms, + SpatialReference sr) { + List ogcGeoms = new ArrayList(10); + for (Geometry g = geoms.next(); g != null; g = geoms.next()) { + ogcGeoms.add(createFromEsriGeometry(g, sr)); + } + + geometries = ogcGeoms; + esriSR = sr; + } public OGCConcreteGeometryCollection(OGCGeometry geom, SpatialReference sr) { geometries = new ArrayList(1); @@ -112,7 +131,7 @@ public OGCGeometry geometryN(int n) { @Override public String geometryType() { - return "GeometryCollection"; + return TYPE; } @Override @@ -275,6 +294,7 @@ public boolean isSimple() { for (int i = 0, n = numGeometries(); i < n; i++) if (!geometryN(i).isSimple()) return false; + return true; } @@ -439,8 +459,6 @@ else if (geom.getType() == Geometry.Type.Point) { return OGCGeometry.createFromEsriGeometry(resultGeom, getEsriSpatialReference(), false); } - List geometries; - @Override public void setSpatialReference(SpatialReference esriSR_) { esriSR = esriSR_; @@ -501,4 +519,288 @@ public int hashCode() { return hash; } + + //Relational operations + @Override + public boolean disjoint(OGCGeometry another) { + if (isEmpty() || another.isEmpty()) + return true; + + if (this == another) + return false; + + //TODO: a simple envelope test + + OGCConcreteGeometryCollection flattened1 = flatten(); + if (flattened1.isEmpty()) + return true; + OGCConcreteGeometryCollection otherCol = new OGCConcreteGeometryCollection(another, esriSR); + OGCConcreteGeometryCollection flattened2 = otherCol.flatten(); + if (flattened2.isEmpty()) + return true; + + for (int i = 0, n1 = flattened1.numGeometries(); i < n1; ++i) { + OGCGeometry g1 = flattened1.geometryN(i); + for (int j = 0, n2 = flattened2.numGeometries(); j < n2; ++j) { + OGCGeometry g2 = flattened2.geometryN(i); + if (!g1.disjoint(g2)) + return false; + } + } + + return true; + } + + @Override + public boolean contains(OGCGeometry another) { + if (isEmpty() || another.isEmpty()) + return true; + if (this == another) + return false; + + //TODO: a simple envelope test + + OGCConcreteGeometryCollection flattened1 = flatten(); + if (flattened1.isEmpty()) + return true; + OGCConcreteGeometryCollection otherCol = new OGCConcreteGeometryCollection(another, esriSR); + OGCConcreteGeometryCollection flattened2 = otherCol.flatten(); + if (flattened2.isEmpty()) + return true; + + for (int i = 0, n2 = flattened2.numGeometries(); i < n2; ++i) { + OGCGeometry g2 = flattened2.geometryN(i); + boolean good = false; + for (int j = 0, n1 = flattened1.numGeometries(); j < n1; ++j) { + OGCGeometry g1 = flattened1.geometryN(i); + if (g1.contains(g2)) { + good = true; + break; + } + } + + if (!good) + return false; + } + + //each geometry of another is contained in a geometry from this. + return true; + } + + /** + * Checks if collection is flattened. + * @return True for the flattened collection. A flattened collection contains up to three non-empty geometries: + * an OGCMultiPoint, an OGCMultiPolygon, and an OGCMultiLineString. + */ + public boolean isFlattened() { + int n = numGeometries(); + if (n > 3) + return false; + + for (int i = 0; i < n; ++i) { + OGCGeometry g = geometryN(i); + if (g.isEmpty()) + return false;//no empty allowed + + String t = g.geometryType(); + if (t != OGCMultiPoint.TYPE && t != OGCMultiPolygon.TYPE && t != OGCMultiLineString.TYPE) + return false; + } + + return true; + } + + /** + * Flattens Geometry Collection. + * The result collection contains up to three geometries: + * an OGCMultiPoint, an OGCMultiPolygon, and an OGCMultilineString. + * @return A flattened Geometry Collection, or self if already flattened. + */ + public OGCConcreteGeometryCollection flatten() { + if (isFlattened()) { + return this; + } + + OGCMultiPoint multiPoint = null; + ArrayList polygons = null; + OGCMultiLineString polyline = null; + GeometryCursor gc = getEsriGeometryCursor(); + for (Geometry g = gc.next(); g != null; g = gc.next()) { + if (g.isEmpty()) + continue; + + Geometry.Type t = g.getType(); + + if (t == Geometry.Type.Point) { + if (multiPoint == null) { + multiPoint = new OGCMultiPoint(esriSR); + } + + ((MultiPoint)multiPoint.getEsriGeometry()).add((Point)g); + continue; + } + + if (t == Geometry.Type.MultiPoint) { + if (multiPoint == null) + multiPoint = new OGCMultiPoint(esriSR); + + ((MultiPoint)multiPoint.getEsriGeometry()).add((MultiPoint)g, 0, -1); + continue; + } + + if (t == Geometry.Type.Polyline) { + if (polyline == null) + polyline = new OGCMultiLineString(esriSR); + + ((MultiPath)polyline.getEsriGeometry()).add((Polyline)g, false); + continue; + } + + if (t == Geometry.Type.Polygon) { + if (polygons == null) + polygons = new ArrayList(); + + polygons.add(g); + continue; + } + + throw new GeometryException("internal error");//what else? + } + + List list = new ArrayList(); + + if (multiPoint != null) + list.add(multiPoint); + + if (polyline != null) + list.add(polyline); + + if (polygons != null) { + GeometryCursor unionedPolygons = OperatorUnion.local().execute(new SimpleGeometryCursor(polygons), esriSR, null); + Geometry g = unionedPolygons.next(); + if (!g.isEmpty()) { + list.add(new OGCMultiPolygon((Polygon)g, esriSR)); + } + + } + + return new OGCConcreteGeometryCollection(list, esriSR); + } + + /** + * Fixes topological overlaps in the GeometryCollecion. + * This is equivalent to union of the geometry collection elements. + * + * @return A geometry collection that is flattened and has no overlapping elements. + */ + public OGCConcreteGeometryCollection flattenAndRemoveOverlaps() { + ArrayList geoms = new ArrayList(); + + //flatten and crack/cluster + GeometryCursor cursor = OGCStructureInternal.prepare_for_ops_(flatten().getEsriGeometryCursor(), esriSR); + for (Geometry g = cursor.next(); g != null; g = cursor.next()) { + geoms.add(g); + } + + //make sure geometries don't overlap + return removeOverlapsHelper_(geoms); + } + + private OGCConcreteGeometryCollection removeOverlapsHelper_(List geoms) { + ArrayList result = new ArrayList(); + for (int i = 0; i < geoms.size() - 1; ++i) { + Geometry current = geoms.get(i); + if (current.isEmpty()) + continue; + + for (int j = 1; j < geoms.size(); ++j) { + Geometry subG = geoms.get(j); + current = OperatorDifference.local().execute(current, subG, esriSR, null); + if (current.isEmpty()) + break; + } + + if (current.isEmpty()) + continue; + + result.add(current); + } + + return new OGCConcreteGeometryCollection(new SimpleGeometryCursor(geoms), esriSR); + } + + private static class FlatteningCollectionCursor extends GeometryCursor { + private List m_collections; + private GeometryCursor m_current; + private int m_index; + FlatteningCollectionCursor(List collections) { + m_collections = collections; + m_index = -1; + m_current = null; + } + + @Override + public Geometry next() { + while (m_collections != null) { + if (m_current != null) { + Geometry g = m_current.next(); + if (g == null) { + m_current = null; + continue; + } + + return g; + } + else { + m_index++; + if (m_index < m_collections.size()) { + m_current = m_collections.get(m_index).flatten().getEsriGeometryCursor(); + continue; + } + else { + m_collections = null; + m_index = -1; + } + } + } + + return null; + } + + @Override + public int getGeometryID() { + return m_index; + } + + }; + + //Collectively processes group of geometry collections (intersects all segments and clusters points). + //Flattens collections, removes overlaps. + //Once done, the result collections would work well for topological and relational operations. + private List prepare_for_ops_(List geoms) { + assert(geoms != null && !geoms.isEmpty()); + GeometryCursor prepared = OGCStructureInternal.prepare_for_ops_(new FlatteningCollectionCursor(geoms), esriSR); + + ArrayList result = new ArrayList(); + int prevCollectionIndex = -1; + ArrayList list = null; + for (Geometry g = prepared.next(); g != null; g = prepared.next()) { + int c = prepared.getGeometryID(); + if (c != prevCollectionIndex) { + if (list != null) { + result.add(removeOverlapsHelper_(list)); + } + + list = new ArrayList(); + } + + list.add(g); + } + + if (list != null) { + result.add(removeOverlapsHelper_(list)); + } + + return result; + } } diff --git a/src/main/java/com/esri/core/geometry/ogc/OGCGeometry.java b/src/main/java/com/esri/core/geometry/ogc/OGCGeometry.java index 058a47b0..a34d7af1 100644 --- a/src/main/java/com/esri/core/geometry/ogc/OGCGeometry.java +++ b/src/main/java/com/esri/core/geometry/ogc/OGCGeometry.java @@ -295,10 +295,7 @@ public boolean crosses(OGCGeometry another) { } public boolean within(OGCGeometry another) { - com.esri.core.geometry.Geometry geom1 = getEsriGeometry(); - com.esri.core.geometry.Geometry geom2 = another.getEsriGeometry(); - return com.esri.core.geometry.GeometryEngine.within(geom1, geom2, - getEsriSpatialReference()); + return another.contains(this); } public boolean contains(OGCGeometry another) { @@ -456,6 +453,18 @@ public OGCGeometry intersection(OGCGeometry another) { } public OGCGeometry union(OGCGeometry another) { + String thisType = geometryType(); + String anotherType = another.geometryType(); + if (thisType != anotherType || thisType == OGCConcreteGeometryCollection.TYPE) { + //heterogeneous union. + //We make a geometry collection, then process to union parts and remove overlaps. + ArrayList geoms = new ArrayList(); + geoms.add(this); + geoms.add(another); + OGCConcreteGeometryCollection geomCol = new OGCConcreteGeometryCollection(geoms, esriSR); + return geomCol.flattenAndRemoveOverlaps(); + } + OperatorUnion op = (OperatorUnion) OperatorFactoryLocal.getInstance() .getOperator(Operator.Type.Union); GeometryCursorAppend ap = new GeometryCursorAppend( diff --git a/src/main/java/com/esri/core/geometry/ogc/OGCLineString.java b/src/main/java/com/esri/core/geometry/ogc/OGCLineString.java index 8d086c3b..4df18c15 100644 --- a/src/main/java/com/esri/core/geometry/ogc/OGCLineString.java +++ b/src/main/java/com/esri/core/geometry/ogc/OGCLineString.java @@ -40,7 +40,8 @@ import static com.esri.core.geometry.SizeOf.SIZE_OF_OGC_LINE_STRING; public class OGCLineString extends OGCCurve { - + static public String TYPE = "LineString"; + /** * The number of Points in this LineString. */ @@ -120,7 +121,7 @@ public OGCPoint endPoint() { @Override public String geometryType() { - return "LineString"; + return TYPE; } @Override diff --git a/src/main/java/com/esri/core/geometry/ogc/OGCMultiLineString.java b/src/main/java/com/esri/core/geometry/ogc/OGCMultiLineString.java index 8fa020c8..91a6580e 100644 --- a/src/main/java/com/esri/core/geometry/ogc/OGCMultiLineString.java +++ b/src/main/java/com/esri/core/geometry/ogc/OGCMultiLineString.java @@ -42,22 +42,31 @@ import static com.esri.core.geometry.SizeOf.SIZE_OF_OGC_MULTI_LINE_STRING; public class OGCMultiLineString extends OGCMultiCurve { + static public String TYPE = "MultiLineString"; + public OGCMultiLineString(Polyline poly, SpatialReference sr) { polyline = poly; esriSR = sr; } + public OGCMultiLineString(SpatialReference sr) { + polyline = new Polyline(); + esriSR = sr; + } + @Override public String asText() { return GeometryEngine.geometryToWkt(getEsriGeometry(), WktExportFlags.wktExportMultiLineString); } + @Override - public String asGeoJson() { - OperatorExportToGeoJson op = (OperatorExportToGeoJson) OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.ExportToGeoJson); - return op.execute(GeoJsonExportFlags.geoJsonExportPreferMultiGeometry, null, getEsriGeometry()); - } + public String asGeoJson() { + OperatorExportToGeoJson op = (OperatorExportToGeoJson) OperatorFactoryLocal.getInstance() + .getOperator(Operator.Type.ExportToGeoJson); + return op.execute(GeoJsonExportFlags.geoJsonExportPreferMultiGeometry, null, getEsriGeometry()); + } + @Override public ByteBuffer asBinary() { OperatorExportToWkb op = (OperatorExportToWkb) OperatorFactoryLocal @@ -74,7 +83,7 @@ public OGCGeometry geometryN(int n) { @Override public String geometryType() { - return "MultiLineString"; + return TYPE; } @Override diff --git a/src/main/java/com/esri/core/geometry/ogc/OGCMultiPoint.java b/src/main/java/com/esri/core/geometry/ogc/OGCMultiPoint.java index b25a948a..4f300ea3 100644 --- a/src/main/java/com/esri/core/geometry/ogc/OGCMultiPoint.java +++ b/src/main/java/com/esri/core/geometry/ogc/OGCMultiPoint.java @@ -40,6 +40,8 @@ import static com.esri.core.geometry.SizeOf.SIZE_OF_OGC_MULTI_POINT; public class OGCMultiPoint extends OGCGeometryCollection { + public static String TYPE = "MultiPoint"; + public int numGeometries() { return multiPoint.getPointCount(); } @@ -65,7 +67,7 @@ public OGCGeometry geometryN(int n) { @Override public String geometryType() { - return "MultiPoint"; + return TYPE; } @Override diff --git a/src/main/java/com/esri/core/geometry/ogc/OGCMultiPolygon.java b/src/main/java/com/esri/core/geometry/ogc/OGCMultiPolygon.java index bed0e114..520cc3f7 100644 --- a/src/main/java/com/esri/core/geometry/ogc/OGCMultiPolygon.java +++ b/src/main/java/com/esri/core/geometry/ogc/OGCMultiPolygon.java @@ -42,12 +42,18 @@ import static com.esri.core.geometry.SizeOf.SIZE_OF_OGC_MULTI_POLYGON; public class OGCMultiPolygon extends OGCMultiSurface { - + static public String TYPE = "MultiPolygon"; + public OGCMultiPolygon(Polygon src, SpatialReference sr) { polygon = src; esriSR = sr; } + public OGCMultiPolygon(SpatialReference sr) { + polygon = new Polygon(); + esriSR = sr; + } + @Override public String asText() { return GeometryEngine.geometryToWkt(getEsriGeometry(), @@ -89,7 +95,7 @@ public OGCGeometry geometryN(int n) { @Override public String geometryType() { - return "MultiPolygon"; + return TYPE; } @Override diff --git a/src/main/java/com/esri/core/geometry/ogc/OGCPoint.java b/src/main/java/com/esri/core/geometry/ogc/OGCPoint.java index 9db01268..608e809f 100644 --- a/src/main/java/com/esri/core/geometry/ogc/OGCPoint.java +++ b/src/main/java/com/esri/core/geometry/ogc/OGCPoint.java @@ -39,6 +39,8 @@ import static com.esri.core.geometry.SizeOf.SIZE_OF_OGC_POINT; public final class OGCPoint extends OGCGeometry { + public static String TYPE = "Point"; + public OGCPoint(Point pt, SpatialReference sr) { point = pt; esriSR = sr; @@ -76,7 +78,7 @@ public double M() { @Override public String geometryType() { - return "Point"; + return TYPE; } @Override diff --git a/src/main/java/com/esri/core/geometry/ogc/OGCPolygon.java b/src/main/java/com/esri/core/geometry/ogc/OGCPolygon.java index 6f7a74f2..2bc65935 100644 --- a/src/main/java/com/esri/core/geometry/ogc/OGCPolygon.java +++ b/src/main/java/com/esri/core/geometry/ogc/OGCPolygon.java @@ -40,6 +40,8 @@ import static com.esri.core.geometry.SizeOf.SIZE_OF_OGC_POLYGON; public class OGCPolygon extends OGCSurface { + public static String TYPE = "Polygon"; + public OGCPolygon(Polygon src, int exteriorRing, SpatialReference sr) { polygon = new Polygon(); for (int i = exteriorRing, n = src.getPathCount(); i < n; i++) { @@ -109,7 +111,7 @@ public OGCMultiCurve boundary() { @Override public String geometryType() { - return "Polygon"; + return TYPE; } @Override diff --git a/src/test/java/com/esri/core/geometry/TestOGC.java b/src/test/java/com/esri/core/geometry/TestOGC.java index 31263705..fd7151ec 100644 --- a/src/test/java/com/esri/core/geometry/TestOGC.java +++ b/src/test/java/com/esri/core/geometry/TestOGC.java @@ -955,5 +955,36 @@ public void testEmptyBoundary() throws Exception { } } + @Test + public void testUnionPointWithEmptyLineString() { + assertUnion("POINT (1 2)", "LINESTRING EMPTY", "GEOMETRYCOLLECTION (POINT (1 2))"); + } + + @Test + public void testUnionPointWithLinestring() { + assertUnion("POINT (1 2)", "LINESTRING (3 4, 5 6)", "GEOMETRYCOLLECTION (POINT (1 2), LINESTRING (3 4, 5 6))"); + } + + @Test + public void testUnionLinestringWithEmptyPolygon() { + assertUnion("LINESTRING (1 2, 3 4)", "POLYGON EMPTY", "GEOMETRYCOLLECTION (LINESTRING ((1 2, 3 4)))"); + } + + @Test + public void testUnionLinestringWithPolygon() { + assertUnion("LINESTRING (1 2, 3 4)", "POLYGON ((0 0, 1 1, 0 1, 0 0))", + "GEOMETRYCOLLECTION (LINESTRING (1 2, 3 4), POLYGON ((0 0, 1 1, 0 1, 0 0)))"); + } + @Test + public void testUnionGeometryCollectionWithGeometryCollection() { + assertUnion("GEOMETRYCOLLECTION (LINESTRING (1 2, 3 4), POLYGON ((0 0, 1 1, 0 1, 0 0)))", + "GEOMETRYCOLLECTION (POINT (1 2), POINT (2 3), POINT (0.5 0.5), POINT (3 5), LINESTRING (3 4, 5 6), POLYGON ((0 0, 1 0, 1 1, 0 0)))", + "GEOMETRYCOLLECTION (LINESTRING (1 2, 3 4), POLYGON ((0 0, 1 1, 0 1, 0 0)))"); + } + + private void assertUnion(String leftWkt, String rightWkt, String expectedWkt) { + OGCGeometry union = OGCGeometry.fromText(leftWkt).union(OGCGeometry.fromText(rightWkt)); + assertEquals(expectedWkt, union.asText()); + } } From 91a0bdaa7eef5b87f1c9a7215d6484ced6d517b1 Mon Sep 17 00:00:00 2001 From: Sergey Tolstov Date: Sun, 27 May 2018 23:19:44 -0700 Subject: [PATCH 156/196] Fixed distance --- .../ogc/OGCConcreteGeometryCollection.java | 21 ++++++++++++-- .../esri/core/geometry/ogc/OGCGeometry.java | 4 +++ .../java/com/esri/core/geometry/TestOGC.java | 28 +++++++++++++++++-- 3 files changed, 48 insertions(+), 5 deletions(-) diff --git a/src/main/java/com/esri/core/geometry/ogc/OGCConcreteGeometryCollection.java b/src/main/java/com/esri/core/geometry/ogc/OGCConcreteGeometryCollection.java index aa4805f3..2e931696 100644 --- a/src/main/java/com/esri/core/geometry/ogc/OGCConcreteGeometryCollection.java +++ b/src/main/java/com/esri/core/geometry/ogc/OGCConcreteGeometryCollection.java @@ -520,6 +520,21 @@ public int hashCode() { return hash; } + @Override + public double distance(OGCGeometry another) { + double minD = 0; + for (int i = 0, n = numGeometries(); i < n; ++i) { + double d = geometryN(i).distance(another); + if (d < minD) { + minD = d; + if (minD == 0) { + break; + } + } + } + + return minD; + } //Relational operations @Override public boolean disjoint(OGCGeometry another) { @@ -708,12 +723,12 @@ public OGCConcreteGeometryCollection flattenAndRemoveOverlaps() { private OGCConcreteGeometryCollection removeOverlapsHelper_(List geoms) { ArrayList result = new ArrayList(); - for (int i = 0; i < geoms.size() - 1; ++i) { + for (int i = 0; i < geoms.size(); ++i) { Geometry current = geoms.get(i); if (current.isEmpty()) continue; - for (int j = 1; j < geoms.size(); ++j) { + for (int j = i + 1; j < geoms.size(); ++j) { Geometry subG = geoms.get(j); current = OperatorDifference.local().execute(current, subG, esriSR, null); if (current.isEmpty()) @@ -726,7 +741,7 @@ private OGCConcreteGeometryCollection removeOverlapsHelper_(List geoms result.add(current); } - return new OGCConcreteGeometryCollection(new SimpleGeometryCursor(geoms), esriSR); + return new OGCConcreteGeometryCollection(new SimpleGeometryCursor(result), esriSR); } private static class FlatteningCollectionCursor extends GeometryCursor { diff --git a/src/main/java/com/esri/core/geometry/ogc/OGCGeometry.java b/src/main/java/com/esri/core/geometry/ogc/OGCGeometry.java index a34d7af1..158d79ef 100644 --- a/src/main/java/com/esri/core/geometry/ogc/OGCGeometry.java +++ b/src/main/java/com/esri/core/geometry/ogc/OGCGeometry.java @@ -325,6 +325,10 @@ public boolean relate(OGCGeometry another, String matrix) { // analysis public double distance(OGCGeometry another) { + if (another.geometryType() == OGCConcreteGeometryCollection.TYPE) { + return another.distance(this); + } + com.esri.core.geometry.Geometry geom1 = getEsriGeometry(); com.esri.core.geometry.Geometry geom2 = another.getEsriGeometry(); return com.esri.core.geometry.GeometryEngine.distance(geom1, geom2, diff --git a/src/test/java/com/esri/core/geometry/TestOGC.java b/src/test/java/com/esri/core/geometry/TestOGC.java index fd7151ec..c0a744cc 100644 --- a/src/test/java/com/esri/core/geometry/TestOGC.java +++ b/src/test/java/com/esri/core/geometry/TestOGC.java @@ -967,7 +967,7 @@ public void testUnionPointWithLinestring() { @Test public void testUnionLinestringWithEmptyPolygon() { - assertUnion("LINESTRING (1 2, 3 4)", "POLYGON EMPTY", "GEOMETRYCOLLECTION (LINESTRING ((1 2, 3 4)))"); + assertUnion("LINESTRING (1 2, 3 4)", "POLYGON EMPTY", "GEOMETRYCOLLECTION (LINESTRING (1 2, 3 4))"); } @Test @@ -980,11 +980,35 @@ public void testUnionLinestringWithPolygon() { public void testUnionGeometryCollectionWithGeometryCollection() { assertUnion("GEOMETRYCOLLECTION (LINESTRING (1 2, 3 4), POLYGON ((0 0, 1 1, 0 1, 0 0)))", "GEOMETRYCOLLECTION (POINT (1 2), POINT (2 3), POINT (0.5 0.5), POINT (3 5), LINESTRING (3 4, 5 6), POLYGON ((0 0, 1 0, 1 1, 0 0)))", - "GEOMETRYCOLLECTION (LINESTRING (1 2, 3 4), POLYGON ((0 0, 1 1, 0 1, 0 0)))"); + "GEOMETRYCOLLECTION (POINT (3 5), LINESTRING (1 2, 2 3, 3 4, 5 6), POLYGON ((0 0, 1 0, 1 1, 0 1, 0 0)))"); } private void assertUnion(String leftWkt, String rightWkt, String expectedWkt) { OGCGeometry union = OGCGeometry.fromText(leftWkt).union(OGCGeometry.fromText(rightWkt)); assertEquals(expectedWkt, union.asText()); } + + @Test + public void testDisjointOnGeometryCollection() { + OGCGeometry ogcGeometry = OGCGeometry.fromText("GEOMETRYCOLLECTION (POINT (1 1))"); + assertFalse(ogcGeometry.disjoint(OGCGeometry.fromText("POINT (1 1)"))); + } + + @Test + public void testContainsOnGeometryCollection() { + OGCGeometry ogcGeometry = OGCGeometry.fromText("GEOMETRYCOLLECTION (POINT (1 1))"); + assertTrue(ogcGeometry.contains(OGCGeometry.fromText("POINT (1 1)"))); + } + + @Test + public void testIntersectsOnGeometryCollection() { + OGCGeometry ogcGeometry = OGCGeometry.fromText("GEOMETRYCOLLECTION (POINT (1 1))"); + assertTrue(ogcGeometry.intersects(OGCGeometry.fromText("POINT (1 1)"))); + } + + @Test + public void testDistanceOnGeometryCollection() { + OGCGeometry ogcGeometry = OGCGeometry.fromText("GEOMETRYCOLLECTION (POINT (1 1))"); + assertTrue(ogcGeometry.distance(OGCGeometry.fromText("POINT (1 1)")) == 0); + } } From 13c1fa322441f8a24d89899a5a6d54454a2b467a Mon Sep 17 00:00:00 2001 From: Sergey Tolstov Date: Sun, 27 May 2018 23:33:48 -0700 Subject: [PATCH 157/196] added missing file --- .../core/geometry/OGCStructureInternal.java | 89 +++++++++++++++++++ 1 file changed, 89 insertions(+) create mode 100644 src/main/java/com/esri/core/geometry/OGCStructureInternal.java diff --git a/src/main/java/com/esri/core/geometry/OGCStructureInternal.java b/src/main/java/com/esri/core/geometry/OGCStructureInternal.java new file mode 100644 index 00000000..dcf4aeef --- /dev/null +++ b/src/main/java/com/esri/core/geometry/OGCStructureInternal.java @@ -0,0 +1,89 @@ +/* + Copyright 1995-2018 Esri + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + For additional information, contact: + Environmental Systems Research Institute, Inc. + Attn: Contracts Dept + 380 New York Street + Redlands, California, USA 92373 + + email: contracts@esri.com + */ +package com.esri.core.geometry; + +import java.util.ArrayList; +import java.util.List; + +import com.esri.core.geometry.ogc.OGCConcreteGeometryCollection; +import com.esri.core.geometry.ogc.OGCGeometry; +import com.esri.core.geometry.ogc.OGCGeometryCollection; + +//An internal helper class. Do not use. +public class OGCStructureInternal { + private static class EditShapeCursor extends GeometryCursor { + EditShape m_shape; + int m_geom; + int m_index; + + EditShapeCursor(EditShape shape, int index) { + m_shape = shape; + m_geom = -1; + m_index = index; + } + @Override + public Geometry next() { + if (m_shape != null) { + if (m_geom == -1) + m_geom = m_shape.getFirstGeometry(); + else + m_geom = m_shape.getNextGeometry(m_geom); + + if (m_geom == -1) { + m_shape = null; + } + else { + return m_shape.getGeometry(m_geom); + } + + } + + return null; + } + + @Override + public int getGeometryID() { + return m_shape.getGeometryUserIndex(m_geom, m_index); + } + + }; + + public static GeometryCursor prepare_for_ops_(GeometryCursor geoms, SpatialReference sr) { + assert(geoms != null); + EditShape editShape = new EditShape(); + int geomIndex = editShape.createGeometryUserIndex(); + for (Geometry g = geoms.next(); g != null; g = geoms.next()) { + int egeom = editShape.addGeometry(g); + editShape.setGeometryUserIndex(egeom, geomIndex, geoms.getGeometryID()); + } + + Envelope2D env = editShape.getEnvelope2D(); + double tolerance = InternalUtils.calculateToleranceFromGeometry(sr, + env, true); + + CrackAndCluster.execute(editShape, tolerance, null, true); + return OperatorSimplifyOGC.local().execute(new EditShapeCursor(editShape, geomIndex), sr, false, null); + } +} + From 559fe2f61c9c81ee741985d3a034f646bf8fedc8 Mon Sep 17 00:00:00 2001 From: Sergey Tolstov Date: Tue, 29 May 2018 18:38:33 -0700 Subject: [PATCH 158/196] Fixed a couple of review comments --- .../ogc/OGCConcreteGeometryCollection.java | 12 ++++++++++-- .../com/esri/core/geometry/ogc/OGCGeometry.java | 8 ++++++++ src/test/java/com/esri/core/geometry/TestOGC.java | 14 ++++++++++++++ 3 files changed, 32 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/esri/core/geometry/ogc/OGCConcreteGeometryCollection.java b/src/main/java/com/esri/core/geometry/ogc/OGCConcreteGeometryCollection.java index 2e931696..35807cd2 100644 --- a/src/main/java/com/esri/core/geometry/ogc/OGCConcreteGeometryCollection.java +++ b/src/main/java/com/esri/core/geometry/ogc/OGCConcreteGeometryCollection.java @@ -522,10 +522,10 @@ public int hashCode() { @Override public double distance(OGCGeometry another) { - double minD = 0; + double minD = Double.NaN; for (int i = 0, n = numGeometries(); i < n; ++i) { double d = geometryN(i).distance(another); - if (d < minD) { + if (d < minD || Double.isNaN(minD)) { minD = d; if (minD == 0) { break; @@ -612,6 +612,7 @@ public boolean isFlattened() { if (n > 3) return false; + int dimension = -1; for (int i = 0; i < n; ++i) { OGCGeometry g = geometryN(i); if (g.isEmpty()) @@ -620,6 +621,13 @@ public boolean isFlattened() { String t = g.geometryType(); if (t != OGCMultiPoint.TYPE && t != OGCMultiPolygon.TYPE && t != OGCMultiLineString.TYPE) return false; + + //check strict order of geometry dimensions + int d = g.dimension(); + if (d <= dimension) + return false; + + dimension = d; } return true; diff --git a/src/main/java/com/esri/core/geometry/ogc/OGCGeometry.java b/src/main/java/com/esri/core/geometry/ogc/OGCGeometry.java index 158d79ef..72093351 100644 --- a/src/main/java/com/esri/core/geometry/ogc/OGCGeometry.java +++ b/src/main/java/com/esri/core/geometry/ogc/OGCGeometry.java @@ -270,6 +270,10 @@ public boolean equals(OGCGeometry another) { } public boolean disjoint(OGCGeometry another) { + if (another.geometryType() == OGCConcreteGeometryCollection.TYPE) { + return another.disjoint(this); + } + com.esri.core.geometry.Geometry geom1 = getEsriGeometry(); com.esri.core.geometry.Geometry geom2 = another.getEsriGeometry(); return com.esri.core.geometry.GeometryEngine.disjoint(geom1, geom2, @@ -299,6 +303,10 @@ public boolean within(OGCGeometry another) { } public boolean contains(OGCGeometry another) { + if (another.geometryType() == OGCConcreteGeometryCollection.TYPE) { + return another.contains(this); + } + com.esri.core.geometry.Geometry geom1 = getEsriGeometry(); com.esri.core.geometry.Geometry geom2 = another.getEsriGeometry(); return com.esri.core.geometry.GeometryEngine.contains(geom1, geom2, diff --git a/src/test/java/com/esri/core/geometry/TestOGC.java b/src/test/java/com/esri/core/geometry/TestOGC.java index c0a744cc..a8afb7da 100644 --- a/src/test/java/com/esri/core/geometry/TestOGC.java +++ b/src/test/java/com/esri/core/geometry/TestOGC.java @@ -1004,11 +1004,25 @@ public void testContainsOnGeometryCollection() { public void testIntersectsOnGeometryCollection() { OGCGeometry ogcGeometry = OGCGeometry.fromText("GEOMETRYCOLLECTION (POINT (1 1))"); assertTrue(ogcGeometry.intersects(OGCGeometry.fromText("POINT (1 1)"))); + ogcGeometry = OGCGeometry.fromText("POINT (1 1)"); + assertTrue(ogcGeometry.intersects(OGCGeometry.fromText("GEOMETRYCOLLECTION (POINT (1 1))"))); } @Test public void testDistanceOnGeometryCollection() { OGCGeometry ogcGeometry = OGCGeometry.fromText("GEOMETRYCOLLECTION (POINT (1 1))"); assertTrue(ogcGeometry.distance(OGCGeometry.fromText("POINT (1 1)")) == 0); + + //distance to empty is NAN + ogcGeometry = OGCGeometry.fromText("GEOMETRYCOLLECTION (POINT (1 1))"); + assertTrue(Double.isNaN(ogcGeometry.distance(OGCGeometry.fromText("POINT EMPTY")))); + } + + @Test + public void testFlattened() { + OGCConcreteGeometryCollection ogcGeometry = (OGCConcreteGeometryCollection)OGCGeometry.fromText("GEOMETRYCOLLECTION (MULTILINESTRING ((1 2, 3 4)), MULTIPOLYGON (((1 2, 3 4, 5 6, 1 2))), MULTIPOINT (1 1))"); + assertFalse(ogcGeometry.isFlattened()); + ogcGeometry = (OGCConcreteGeometryCollection)OGCGeometry.fromText("GEOMETRYCOLLECTION (MULTIPOINT (1 1), MULTILINESTRING ((1 2, 3 4)), MULTIPOLYGON (((1 2, 3 4, 5 6, 1 2))))"); + assertTrue(ogcGeometry.isFlattened()); } } From 0618e303c9e3958c3dac68eb38706d6b664a2f9a Mon Sep 17 00:00:00 2001 From: Sergey Tolstov Date: Tue, 29 May 2018 19:36:58 -0700 Subject: [PATCH 159/196] a few fixes, added intersection and difference for collections --- .../com/esri/core/geometry/OGCStructure.java | 5 - .../core/geometry/OGCStructureInternal.java | 7 -- .../ogc/OGCConcreteGeometryCollection.java | 101 ++++++++++++++++-- .../esri/core/geometry/ogc/OGCGeometry.java | 13 ++- .../java/com/esri/core/geometry/TestOGC.java | 12 +++ 5 files changed, 119 insertions(+), 19 deletions(-) diff --git a/src/main/java/com/esri/core/geometry/OGCStructure.java b/src/main/java/com/esri/core/geometry/OGCStructure.java index 1a9891d9..9f0875b9 100644 --- a/src/main/java/com/esri/core/geometry/OGCStructure.java +++ b/src/main/java/com/esri/core/geometry/OGCStructure.java @@ -23,13 +23,8 @@ */ package com.esri.core.geometry; -import java.util.ArrayList; import java.util.List; -import com.esri.core.geometry.ogc.OGCConcreteGeometryCollection; -import com.esri.core.geometry.ogc.OGCGeometry; -import com.esri.core.geometry.ogc.OGCGeometryCollection; - public class OGCStructure { public int m_type; public List m_structures; diff --git a/src/main/java/com/esri/core/geometry/OGCStructureInternal.java b/src/main/java/com/esri/core/geometry/OGCStructureInternal.java index dcf4aeef..59cf1e61 100644 --- a/src/main/java/com/esri/core/geometry/OGCStructureInternal.java +++ b/src/main/java/com/esri/core/geometry/OGCStructureInternal.java @@ -23,13 +23,6 @@ */ package com.esri.core.geometry; -import java.util.ArrayList; -import java.util.List; - -import com.esri.core.geometry.ogc.OGCConcreteGeometryCollection; -import com.esri.core.geometry.ogc.OGCGeometry; -import com.esri.core.geometry.ogc.OGCGeometryCollection; - //An internal helper class. Do not use. public class OGCStructureInternal { private static class EditShapeCursor extends GeometryCursor { diff --git a/src/main/java/com/esri/core/geometry/ogc/OGCConcreteGeometryCollection.java b/src/main/java/com/esri/core/geometry/ogc/OGCConcreteGeometryCollection.java index 35807cd2..c7737ab0 100644 --- a/src/main/java/com/esri/core/geometry/ogc/OGCConcreteGeometryCollection.java +++ b/src/main/java/com/esri/core/geometry/ogc/OGCConcreteGeometryCollection.java @@ -80,6 +80,11 @@ public OGCConcreteGeometryCollection(OGCGeometry geom, SpatialReference sr) { esriSR = sr; } + public OGCConcreteGeometryCollection(SpatialReference sr) { + geometries = new ArrayList(); + esriSR = sr; + } + @Override public int dimension() { int maxD = 0; @@ -356,7 +361,7 @@ protected boolean isConcreteGeometryCollection() { return true; } - static class GeometryCursorOGC extends GeometryCursor { + private static class GeometryCursorOGC extends GeometryCursor { private int m_index; private int m_ind; private List m_geoms; @@ -522,6 +527,9 @@ public int hashCode() { @Override public double distance(OGCGeometry another) { + if (this == another) + return isEmpty() ? Double.NaN : 0; + double minD = Double.NaN; for (int i = 0, n = numGeometries(); i < n; ++i) { double d = geometryN(i).distance(another); @@ -535,6 +543,8 @@ public double distance(OGCGeometry another) { return minD; } + + // //Relational operations @Override public boolean disjoint(OGCGeometry another) { @@ -569,7 +579,8 @@ public boolean disjoint(OGCGeometry another) { @Override public boolean contains(OGCGeometry another) { if (isEmpty() || another.isEmpty()) - return true; + return false; + if (this == another) return false; @@ -601,7 +612,79 @@ public boolean contains(OGCGeometry another) { //each geometry of another is contained in a geometry from this. return true; } - + //Topological + @Override + public OGCGeometry difference(OGCGeometry another) { + List list = wrapGeomsIntoList_(this, another); + list = prepare_for_ops_(list); + if (list.size() != 2) // this should not happen + throw new GeometryException("internal error"); + + List result = new ArrayList(); + OGCConcreteGeometryCollection coll1 = list.get(0); + OGCConcreteGeometryCollection coll2 = list.get(1); + for (int i = 0, n = coll1.numGeometries(); i < n; ++i) { + OGCGeometry cur = coll1.geometryN(i); + for (int j = 0, n2 = coll2.numGeometries(); j < n2; ++j) { + OGCGeometry geom2 = coll2.geometryN(j); + if (cur.dimension() > geom2.dimension()) + continue; //subtracting lower dimension has no effect. + + cur = cur.difference(geom2); + if (cur.isEmpty()) + break; + } + + if (cur.isEmpty()) + continue; + + result.add(cur); + } + + return new OGCConcreteGeometryCollection(result, esriSR); + } + + @Override + public OGCGeometry intersection(OGCGeometry another) { + List list = wrapGeomsIntoList_(this, another); + list = prepare_for_ops_(list); + if (list.size() != 2) // this should not happen + throw new GeometryException("internal error"); + + List result = new ArrayList(); + OGCConcreteGeometryCollection coll1 = list.get(0); + OGCConcreteGeometryCollection coll2 = list.get(1); + for (int i = 0, n = coll1.numGeometries(); i < n; ++i) { + OGCGeometry cur = coll1.geometryN(i); + for (int j = 0, n2 = coll2.numGeometries(); j < n2; ++j) { + OGCGeometry geom2 = coll2.geometryN(j); + + OGCGeometry partialIntersection = cur.intersection(geom2); + if (partialIntersection.isEmpty()) + continue; + + result.add(partialIntersection); + } + } + + return (new OGCConcreteGeometryCollection(result, esriSR)).flattenAndRemoveOverlaps(); + } + + //make a list of collections out of two geometries + private static List wrapGeomsIntoList_(OGCGeometry g1, OGCGeometry g2) { + List list = new ArrayList(); + if (g1.geometryType() != OGCConcreteGeometryCollection.TYPE) { + g1 = new OGCConcreteGeometryCollection(g1, g1.getEsriSpatialReference()); + } + if (g2.geometryType() != OGCConcreteGeometryCollection.TYPE) { + g2 = new OGCConcreteGeometryCollection(g2, g2.getEsriSpatialReference()); + } + + list.add((OGCConcreteGeometryCollection)g1); + list.add((OGCConcreteGeometryCollection)g2); + + return list; + } /** * Checks if collection is flattened. * @return True for the flattened collection. A flattened collection contains up to three non-empty geometries: @@ -636,7 +719,7 @@ public boolean isFlattened() { /** * Flattens Geometry Collection. * The result collection contains up to three geometries: - * an OGCMultiPoint, an OGCMultiPolygon, and an OGCMultilineString. + * an OGCMultiPoint, an OGCMultilineString, and an OGCMultiPolygon (in that order). * @return A flattened Geometry Collection, or self if already flattened. */ public OGCConcreteGeometryCollection flatten() { @@ -804,17 +887,23 @@ private List prepare_for_ops_(List result = new ArrayList(); + List result = new ArrayList(); int prevCollectionIndex = -1; - ArrayList list = null; + List list = null; for (Geometry g = prepared.next(); g != null; g = prepared.next()) { int c = prepared.getGeometryID(); if (c != prevCollectionIndex) { + //add empty collections for all skipped indices + for (int i = prevCollectionIndex; i < c - 1; i++) { + result.add(new OGCConcreteGeometryCollection(esriSR)); + } + if (list != null) { result.add(removeOverlapsHelper_(list)); } list = new ArrayList(); + prevCollectionIndex = c; } list.add(g); diff --git a/src/main/java/com/esri/core/geometry/ogc/OGCGeometry.java b/src/main/java/com/esri/core/geometry/ogc/OGCGeometry.java index 72093351..8fda3edb 100644 --- a/src/main/java/com/esri/core/geometry/ogc/OGCGeometry.java +++ b/src/main/java/com/esri/core/geometry/ogc/OGCGeometry.java @@ -253,7 +253,7 @@ public boolean isMeasured() { */ public boolean Equals(OGCGeometry another) { if (this == another) - return true; + return true; //should return false for empty if (another == null) return false; @@ -333,6 +333,9 @@ public boolean relate(OGCGeometry another, String matrix) { // analysis public double distance(OGCGeometry another) { + if (this == another) + return isEmpty() ? Double.NaN : 0; + if (another.geometryType() == OGCConcreteGeometryCollection.TYPE) { return another.distance(this); } @@ -456,6 +459,10 @@ public OGCGeometry convexHull() { } public OGCGeometry intersection(OGCGeometry another) { + if (another.geometryType() == OGCConcreteGeometryCollection.TYPE) { + return (new OGCConcreteGeometryCollection(this, esriSR)).intersection(another); + } + com.esri.core.geometry.OperatorIntersection op = (OperatorIntersection) OperatorFactoryLocal .getInstance().getOperator(Operator.Type.Intersection); com.esri.core.geometry.GeometryCursor cursor = op.execute( @@ -487,6 +494,10 @@ public OGCGeometry union(OGCGeometry another) { } public OGCGeometry difference(OGCGeometry another) { + if (another.geometryType() == OGCConcreteGeometryCollection.TYPE) { + return (new OGCConcreteGeometryCollection(this, esriSR)).difference(another); + } + com.esri.core.geometry.Geometry geom1 = getEsriGeometry(); com.esri.core.geometry.Geometry geom2 = another.getEsriGeometry(); return createFromEsriGeometry( diff --git a/src/test/java/com/esri/core/geometry/TestOGC.java b/src/test/java/com/esri/core/geometry/TestOGC.java index a8afb7da..b4dadf38 100644 --- a/src/test/java/com/esri/core/geometry/TestOGC.java +++ b/src/test/java/com/esri/core/geometry/TestOGC.java @@ -983,6 +983,18 @@ public void testUnionGeometryCollectionWithGeometryCollection() { "GEOMETRYCOLLECTION (POINT (3 5), LINESTRING (1 2, 2 3, 3 4, 5 6), POLYGON ((0 0, 1 0, 1 1, 0 1, 0 0)))"); } + @Test + public void testIntersectionGeometryCollectionWithGeometryCollection() { + assertIntersection("GEOMETRYCOLLECTION (LINESTRING (1 2, 3 4), POLYGON ((0 0, 1 1, 0 1, 0 0)))", + "GEOMETRYCOLLECTION (POINT (1 2), POINT (2 3), POINT (0.5 0.5), POINT (3 5), LINESTRING (3 4, 5 6), POLYGON ((0 0, 1 0, 1 1, 0 0)))", + "GEOMETRYCOLLECTION (MULTIPOINT ((1 2), (2 3), (3 4)), LINESTRING (0 0, 0.5 0.5, 1 1))"); + } + + private void assertIntersection(String leftWkt, String rightWkt, String expectedWkt) { + OGCGeometry intersection = OGCGeometry.fromText(leftWkt).intersection(OGCGeometry.fromText(rightWkt)); + assertEquals(expectedWkt, intersection.asText()); + } + private void assertUnion(String leftWkt, String rightWkt, String expectedWkt) { OGCGeometry union = OGCGeometry.fromText(leftWkt).union(OGCGeometry.fromText(rightWkt)); assertEquals(expectedWkt, union.asText()); From af34eaf7e156346a588dca57b9118f6e790079c3 Mon Sep 17 00:00:00 2001 From: Sergey Tolstov Date: Wed, 30 May 2018 21:40:45 -0700 Subject: [PATCH 160/196] Equals for collection, reducFromMulti --- .../ogc/OGCConcreteGeometryCollection.java | 70 ++++++++++++++++++- .../esri/core/geometry/ogc/OGCGeometry.java | 21 +++++- .../esri/core/geometry/ogc/OGCLineString.java | 7 +- .../core/geometry/ogc/OGCMultiLineString.java | 16 ++++- .../esri/core/geometry/ogc/OGCMultiPoint.java | 14 ++++ .../core/geometry/ogc/OGCMultiPolygon.java | 16 ++++- .../com/esri/core/geometry/ogc/OGCPoint.java | 7 +- .../esri/core/geometry/ogc/OGCPolygon.java | 7 +- .../java/com/esri/core/geometry/TestOGC.java | 4 +- 9 files changed, 149 insertions(+), 13 deletions(-) diff --git a/src/main/java/com/esri/core/geometry/ogc/OGCConcreteGeometryCollection.java b/src/main/java/com/esri/core/geometry/ogc/OGCConcreteGeometryCollection.java index c7737ab0..74e5876b 100644 --- a/src/main/java/com/esri/core/geometry/ogc/OGCConcreteGeometryCollection.java +++ b/src/main/java/com/esri/core/geometry/ogc/OGCConcreteGeometryCollection.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2017 Esri + Copyright 1995-2018 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -477,6 +477,20 @@ public void setSpatialReference(SpatialReference esriSR_) { public OGCGeometry convertToMulti() { return this; } + + @Override + public OGCGeometry reduceFromMulti() { + int n = numGeometries(); + if (n == 0) { + return this; + } + + if (n == 1) { + return geometryN(0).reduceFromMulti(); + } + + return this; + } @Override public String asJson() { @@ -582,7 +596,7 @@ public boolean contains(OGCGeometry another) { return false; if (this == another) - return false; + return true; //TODO: a simple envelope test @@ -612,6 +626,48 @@ public boolean contains(OGCGeometry another) { //each geometry of another is contained in a geometry from this. return true; } + + @Override + public boolean Equals(OGCGeometry another) { + if (this == another) + return !isEmpty(); + + if (another == null) + return false; + + + OGCGeometry g1 = reduceFromMulti(); + String t1 = g1.geometryType(); + OGCGeometry g2 = reduceFromMulti(); + if (t1 != g2.geometryType()) { + return false; + } + + if (t1 != OGCConcreteGeometryCollection.TYPE) { + return g1.Equals(g2); + } + + OGCConcreteGeometryCollection gc1 = (OGCConcreteGeometryCollection)g1; + OGCConcreteGeometryCollection gc2 = (OGCConcreteGeometryCollection)g2; + gc1 = gc1.flattenAndRemoveOverlaps(); + gc2 = gc2.flattenAndRemoveOverlaps(); + int n = gc1.numGeometries(); + if (n != gc2.numGeometries()) { + return false; + } + + boolean res = false; + for (int i = 0; i < n; ++i) { + if (!gc1.geometryN(i).Equals(gc2.geometryN(i))) { + return false; + } + + res = true; + } + + return res; + } + //Topological @Override public OGCGeometry difference(OGCGeometry another) { @@ -641,9 +697,13 @@ public OGCGeometry difference(OGCGeometry another) { result.add(cur); } + if (result.size() == 1) { + result.get(0).reduceFromMulti(); + } + return new OGCConcreteGeometryCollection(result, esriSR); } - + @Override public OGCGeometry intersection(OGCGeometry another) { List list = wrapGeomsIntoList_(this, another); @@ -666,6 +726,10 @@ public OGCGeometry intersection(OGCGeometry another) { result.add(partialIntersection); } } + + if (result.size() == 1) { + result.get(0).reduceFromMulti(); + } return (new OGCConcreteGeometryCollection(result, esriSR)).flattenAndRemoveOverlaps(); } diff --git a/src/main/java/com/esri/core/geometry/ogc/OGCGeometry.java b/src/main/java/com/esri/core/geometry/ogc/OGCGeometry.java index 8fda3edb..5014e207 100644 --- a/src/main/java/com/esri/core/geometry/ogc/OGCGeometry.java +++ b/src/main/java/com/esri/core/geometry/ogc/OGCGeometry.java @@ -253,17 +253,21 @@ public boolean isMeasured() { */ public boolean Equals(OGCGeometry another) { if (this == another) - return true; //should return false for empty + return !isEmpty(); if (another == null) return false; + if (another.geometryType() == OGCConcreteGeometryCollection.TYPE) { + return another.Equals(this); + } + com.esri.core.geometry.Geometry geom1 = getEsriGeometry(); com.esri.core.geometry.Geometry geom2 = another.getEsriGeometry(); return com.esri.core.geometry.GeometryEngine.equals(geom1, geom2, getEsriSpatialReference()); } - + @Deprecated public boolean equals(OGCGeometry another) { return Equals(another); @@ -481,7 +485,7 @@ public OGCGeometry union(OGCGeometry another) { geoms.add(this); geoms.add(another); OGCConcreteGeometryCollection geomCol = new OGCConcreteGeometryCollection(geoms, esriSR); - return geomCol.flattenAndRemoveOverlaps(); + return geomCol.flattenAndRemoveOverlaps().reduceFromMulti(); } OperatorUnion op = (OperatorUnion) OperatorFactoryLocal.getInstance() @@ -755,6 +759,17 @@ public void setSpatialReference(SpatialReference esriSR_) { */ public abstract OGCGeometry convertToMulti(); + /** + * For the geometry collection types, when it has 1 or 0 elements, converts a MultiPolygon to Polygon, + * MultiPoint to Point, MultiLineString to a LineString, and + * OGCConcretGeometryCollection to the reduced element it contains. + * + * If OGCConcretGeometryCollection is empty, returns self. + * + * @return A reduced geometry or this. + */ + public abstract OGCGeometry reduceFromMulti(); + @Override public String toString() { String snippet = asText(); diff --git a/src/main/java/com/esri/core/geometry/ogc/OGCLineString.java b/src/main/java/com/esri/core/geometry/ogc/OGCLineString.java index 4df18c15..f044128d 100644 --- a/src/main/java/com/esri/core/geometry/ogc/OGCLineString.java +++ b/src/main/java/com/esri/core/geometry/ogc/OGCLineString.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2017 Esri + Copyright 1995-2018 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -151,5 +151,10 @@ public OGCGeometry convertToMulti() return new OGCMultiLineString((Polyline)multiPath, esriSR); } + @Override + public OGCGeometry reduceFromMulti() { + return this; + } + MultiPath multiPath; } diff --git a/src/main/java/com/esri/core/geometry/ogc/OGCMultiLineString.java b/src/main/java/com/esri/core/geometry/ogc/OGCMultiLineString.java index 91a6580e..6a2381e3 100644 --- a/src/main/java/com/esri/core/geometry/ogc/OGCMultiLineString.java +++ b/src/main/java/com/esri/core/geometry/ogc/OGCMultiLineString.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2017 Esri + Copyright 1995-2018 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -123,5 +123,19 @@ public OGCGeometry convertToMulti() return this; } + @Override + public OGCGeometry reduceFromMulti() { + int n = numGeometries(); + if (n == 0) { + return new OGCLineString(new Polyline(polyline.getDescription()), 0, esriSR); + } + + if (n == 1) { + return geometryN(0); + } + + return this; + } + Polyline polyline; } diff --git a/src/main/java/com/esri/core/geometry/ogc/OGCMultiPoint.java b/src/main/java/com/esri/core/geometry/ogc/OGCMultiPoint.java index 4f300ea3..b1c70a02 100644 --- a/src/main/java/com/esri/core/geometry/ogc/OGCMultiPoint.java +++ b/src/main/java/com/esri/core/geometry/ogc/OGCMultiPoint.java @@ -133,5 +133,19 @@ public OGCGeometry convertToMulti() return this; } + @Override + public OGCGeometry reduceFromMulti() { + int n = numGeometries(); + if (n == 0) { + return new OGCPoint(new Point(multiPoint.getDescription()), esriSR); + } + + if (n == 1) { + return geometryN(0); + } + + return this; + } + private com.esri.core.geometry.MultiPoint multiPoint; } diff --git a/src/main/java/com/esri/core/geometry/ogc/OGCMultiPolygon.java b/src/main/java/com/esri/core/geometry/ogc/OGCMultiPolygon.java index 520cc3f7..d2e64956 100644 --- a/src/main/java/com/esri/core/geometry/ogc/OGCMultiPolygon.java +++ b/src/main/java/com/esri/core/geometry/ogc/OGCMultiPolygon.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2017 Esri + Copyright 1995-2018 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -135,5 +135,19 @@ public OGCGeometry convertToMulti() return this; } + @Override + public OGCGeometry reduceFromMulti() { + int n = numGeometries(); + if (n == 0) { + return new OGCLineString(new Polygon(polygon.getDescription()), 0, esriSR); + } + + if (n == 1) { + return geometryN(0); + } + + return this; + } + Polygon polygon; } diff --git a/src/main/java/com/esri/core/geometry/ogc/OGCPoint.java b/src/main/java/com/esri/core/geometry/ogc/OGCPoint.java index 608e809f..d0fba6e7 100644 --- a/src/main/java/com/esri/core/geometry/ogc/OGCPoint.java +++ b/src/main/java/com/esri/core/geometry/ogc/OGCPoint.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2017 Esri + Copyright 1995-2018 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -115,6 +115,11 @@ public OGCGeometry convertToMulti() { return new OGCMultiPoint(point, esriSR); } + + @Override + public OGCGeometry reduceFromMulti() { + return this; + } com.esri.core.geometry.Point point; diff --git a/src/main/java/com/esri/core/geometry/ogc/OGCPolygon.java b/src/main/java/com/esri/core/geometry/ogc/OGCPolygon.java index 2bc65935..4c95250a 100644 --- a/src/main/java/com/esri/core/geometry/ogc/OGCPolygon.java +++ b/src/main/java/com/esri/core/geometry/ogc/OGCPolygon.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2017 Esri + Copyright 1995-2018 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -143,5 +143,10 @@ public OGCGeometry convertToMulti() return new OGCMultiPolygon(polygon, esriSR); } + @Override + public OGCGeometry reduceFromMulti() { + return this; + } + Polygon polygon; } diff --git a/src/test/java/com/esri/core/geometry/TestOGC.java b/src/test/java/com/esri/core/geometry/TestOGC.java index b4dadf38..eb40a2d4 100644 --- a/src/test/java/com/esri/core/geometry/TestOGC.java +++ b/src/test/java/com/esri/core/geometry/TestOGC.java @@ -957,7 +957,7 @@ public void testEmptyBoundary() throws Exception { @Test public void testUnionPointWithEmptyLineString() { - assertUnion("POINT (1 2)", "LINESTRING EMPTY", "GEOMETRYCOLLECTION (POINT (1 2))"); + assertUnion("POINT (1 2)", "LINESTRING EMPTY", "POINT (1 2)"); } @Test @@ -967,7 +967,7 @@ public void testUnionPointWithLinestring() { @Test public void testUnionLinestringWithEmptyPolygon() { - assertUnion("LINESTRING (1 2, 3 4)", "POLYGON EMPTY", "GEOMETRYCOLLECTION (LINESTRING (1 2, 3 4))"); + assertUnion("MULTILINESTRING ((1 2, 3 4))", "POLYGON EMPTY", "LINESTRING (1 2, 3 4)"); } @Test From aefd7d73ed31149f9a3d1e795d0ec1c856fac690 Mon Sep 17 00:00:00 2001 From: Sergey Tolstov Date: Sun, 3 Jun 2018 17:28:56 -0700 Subject: [PATCH 161/196] update some comments --- .../geometry/OperatorDifferenceLocal.java | 4 +- .../esri/core/geometry/TestDifference.java | 12 +- .../java/com/esri/core/geometry/TestOGC.java | 3 +- .../geometry/TestOGCGeometryCollection.java | 153 ++++++++++++++++++ 4 files changed, 165 insertions(+), 7 deletions(-) create mode 100644 src/test/java/com/esri/core/geometry/TestOGCGeometryCollection.java diff --git a/src/main/java/com/esri/core/geometry/OperatorDifferenceLocal.java b/src/main/java/com/esri/core/geometry/OperatorDifferenceLocal.java index 02006bfc..b68b050f 100644 --- a/src/main/java/com/esri/core/geometry/OperatorDifferenceLocal.java +++ b/src/main/java/com/esri/core/geometry/OperatorDifferenceLocal.java @@ -81,9 +81,9 @@ static Geometry difference(Geometry geometry_a, Geometry geometry_b, if (!env_a_inflated.isIntersecting(env_b)) return geometry_a; - if (dimension_a == 1 && dimension_b == 2) + /*if (dimension_a == 1 && dimension_b == 2) return polylineMinusArea_(geometry_a, geometry_b, type_b, - spatial_reference, progress_tracker); + spatial_reference, progress_tracker);*/ if (type_a == Geometry.GeometryType.Point) { Geometry geometry_b_; diff --git a/src/test/java/com/esri/core/geometry/TestDifference.java b/src/test/java/com/esri/core/geometry/TestDifference.java index c6f22321..2ea73ffc 100644 --- a/src/test/java/com/esri/core/geometry/TestDifference.java +++ b/src/test/java/com/esri/core/geometry/TestDifference.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2017 Esri + Copyright 1995-2018 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -24,8 +24,6 @@ package com.esri.core.geometry; -import java.util.ArrayList; -import java.util.List; import junit.framework.TestCase; @@ -504,6 +502,14 @@ public static void testDifferenceOnPolyline() { assertEquals(5, pointCountDiffPolyline); } + + @Test + public static void testDifferencePolylineAlongPolygonBoundary() { + Polyline polyline = (Polyline)GeometryEngine.geometryFromWkt("LINESTRING(0 0, 0 5, -2 5)", 0, Geometry.Type.Unknown); + Polygon polygon = (Polygon)GeometryEngine.geometryFromWkt("POLYGON((0 0, 5 0, 5 5, 0 5, 0 0))", 0, Geometry.Type.Unknown); + Geometry result = OperatorDifference.local().execute(polyline, polygon, null, null); + assertEquals(GeometryEngine.geometryToJson(null, result), "{\"paths\":[[[0,5],[-2,5]]]}"); + } public static Polygon makePolygon1() { Polygon poly = new Polygon(); diff --git a/src/test/java/com/esri/core/geometry/TestOGC.java b/src/test/java/com/esri/core/geometry/TestOGC.java index eb40a2d4..34403661 100644 --- a/src/test/java/com/esri/core/geometry/TestOGC.java +++ b/src/test/java/com/esri/core/geometry/TestOGC.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2017 Esri + Copyright 1995-2018 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -35,7 +35,6 @@ import com.esri.core.geometry.ogc.OGCMultiPolygon; import com.esri.core.geometry.ogc.OGCPoint; import com.esri.core.geometry.ogc.OGCPolygon; -import com.fasterxml.jackson.core.JsonParseException; import com.esri.core.geometry.ogc.OGCConcreteGeometryCollection; import org.junit.Test; diff --git a/src/test/java/com/esri/core/geometry/TestOGCGeometryCollection.java b/src/test/java/com/esri/core/geometry/TestOGCGeometryCollection.java new file mode 100644 index 00000000..1d1a32d7 --- /dev/null +++ b/src/test/java/com/esri/core/geometry/TestOGCGeometryCollection.java @@ -0,0 +1,153 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.esri.core.geometry; + +import com.esri.core.geometry.ogc.OGCGeometry; +import org.junit.Assert; +import org.junit.Test; + +public class TestOGCGeometryCollection +{ + @Test + public void testUnionPoint() + { + // point - point + assertUnion("POINT (1 2)", "POINT (1 2)", "POINT (1 2)"); + assertUnion("POINT (1 2)", "POINT EMPTY", "POINT (1 2)"); + assertUnion("POINT (1 2)", "POINT (3 4)", "MULTIPOINT ((1 2), (3 4))"); + + // point - multi-point + assertUnion("POINT (1 2)", "MULTIPOINT (1 2)", "POINT (1 2)"); + assertUnion("POINT (1 2)", "MULTIPOINT EMPTY", "POINT (1 2)"); + assertUnion("POINT (1 2)", "MULTIPOINT (3 4)", "MULTIPOINT ((1 2), (3 4))"); + assertUnion("POINT (1 2)", "MULTIPOINT (1 2, 3 4)", "MULTIPOINT ((1 2), (3 4))"); + assertUnion("POINT (1 2)", "MULTIPOINT (3 4, 5 6)", "MULTIPOINT ((1 2), (3 4), (5 6))"); + + // point - linestring + assertUnion("POINT (1 2)", "LINESTRING (3 4, 5 6)", "GEOMETRYCOLLECTION (POINT (1 2), LINESTRING (3 4, 5 6))"); + assertUnion("POINT (1 2)", "LINESTRING EMPTY", "POINT (1 2)"); + assertUnion("POINT (1 2)", "LINESTRING (1 2, 3 4)", "LINESTRING (1 2, 3 4)"); + assertUnion("POINT (1 2)", "LINESTRING (1 1, 1 3, 3 4)", "LINESTRING (1 1, 1 2, 1 3, 3 4)"); + + // point - multi-linestring + assertUnion("POINT (1 2)", "MULTILINESTRING ((3 4, 5 6))", "GEOMETRYCOLLECTION (POINT (1 2), LINESTRING (3 4, 5 6))"); + assertUnion("POINT (1 2)", "MULTILINESTRING EMPTY", "POINT (1 2)"); + assertUnion("POINT (1 2)", "MULTILINESTRING ((3 4, 5 6), (7 8, 9 10, 11 12))", "GEOMETRYCOLLECTION (POINT (1 2), MULTILINESTRING ((3 4, 5 6), (7 8, 9 10, 11 12)))"); + assertUnion("POINT (1 2)", "MULTILINESTRING ((1 2, 3 4))", "LINESTRING (1 2, 3 4)"); + assertUnion("POINT (1 2)", "MULTILINESTRING ((1 1, 1 3, 3 4), (7 8, 9 10, 11 12))", "MULTILINESTRING ((1 1, 1 2, 1 3, 3 4), (7 8, 9 10, 11 12))"); + + // point - polygon + assertUnion("POINT (1 2)", "POLYGON ((0 0, 1 0, 1 1, 0 0))", "GEOMETRYCOLLECTION (POINT (1 2), POLYGON ((0 0, 1 0, 1 1, 0 0)))"); + assertUnion("POINT (1 2)", "POLYGON EMPTY", "POINT (1 2)"); + // point inside polygon + assertUnion("POINT (1 2)", "POLYGON ((0 0, 3 0, 3 3, 0 3, 0 0))", "POLYGON ((0 0, 3 0, 3 3, 0 3, 0 0))"); + // point inside polygon with a hole + assertUnion("POINT (1 2)", "POLYGON ((0 0, 3 0, 3 3, 0 3, 0 0), (2 2, 2 2.5, 2.5 2.5, 2.5 2, 2 2))", "POLYGON ((0 0, 3 0, 3 3, 0 3, 0 0), (2 2, 2 2.5, 2.5 2.5, 2.5 2, 2 2))"); + // point inside polygon's hole + assertUnion("POINT (1 2)", "POLYGON ((0 0, 3 0, 3 3, 0 3, 0 0), (0.5 1, 0.5 2.5, 2.5 2.5, 2.5 1, 0.5 1))", "GEOMETRYCOLLECTION (POINT (1 2), POLYGON ((0 0, 3 0, 3 3, 0 3, 0 0), (0.5 1, 0.5 2.5, 2.5 2.5, 2.5 1, 0.5 1)))"); + // point is a vertex of the polygon + assertUnion("POINT (1 2)", "POLYGON ((1 2, 2 2, 2 3, 1 3, 1 2))", "POLYGON ((1 2, 2 2, 2 3, 1 3, 1 2))"); + // point on the boundary of the polybon + assertUnion("POINT (1 2)", "POLYGON ((1 1, 2 1, 2 3, 1 3, 1 1))", "POLYGON ((1 1, 2 1, 2 3, 1 3, 1 2, 1 1))"); + + // point - multi-polygon + assertUnion("POINT (1 2)", "MULTIPOLYGON (((0 0, 1 0, 1 1, 0 0)))", "GEOMETRYCOLLECTION (POINT (1 2), POLYGON ((0 0, 1 0, 1 1, 0 0)))"); + assertUnion("POINT (1 2)", "MULTIPOLYGON EMPTY", "POINT (1 2)"); + assertUnion("POINT (1 2)", "MULTIPOLYGON (((0 0, 3 0, 3 3, 0 3, 0 0)))", "POLYGON ((0 0, 3 0, 3 3, 0 3, 0 0))"); + assertUnion("POINT (1 2)", "MULTIPOLYGON (((0 0, 3 0, 3 3, 0 3, 0 0)), ((4 4, 5 4, 5 5, 4 4)))", "MULTIPOLYGON (((0 0, 3 0, 3 3, 0 3, 0 0)), ((4 4, 5 4, 5 5, 4 4)))"); + assertUnion("POINT (1 2)", "MULTIPOLYGON (((0 0, 3 0, 3 3, 0 3, 0 0), (0.5 1, 0.5 2.5, 2.5 2.5, 2.5 1, 0.5 1)))", "GEOMETRYCOLLECTION (POINT (1 2), POLYGON ((0 0, 3 0, 3 3, 0 3, 0 0), (0.5 1, 0.5 2.5, 2.5 2.5, 2.5 1, 0.5 1)))"); + assertUnion("POINT (1 2)", "MULTIPOLYGON (((0 0, 3 0, 3 3, 0 3, 0 0), (0.5 1, 0.5 2.5, 2.5 2.5, 2.5 1, 0.5 1)), ((4 4, 5 4, 5 5, 4 4)))", "GEOMETRYCOLLECTION (POINT (1 2), MULTIPOLYGON (((0 0, 3 0, 3 3, 0 3, 0 0), (0.5 1, 0.5 2.5, 2.5 2.5, 2.5 1, 0.5 1)), ((4 4, 5 4, 5 5, 4 4))))"); + + // point - geometry collection + assertUnion("POINT (1 2)", "GEOMETRYCOLLECTION (POINT (1 2))", "POINT (1 2)"); + assertUnion("POINT (1 2)", "GEOMETRYCOLLECTION EMPTY", "POINT (1 2)"); + assertUnion("POINT (1 2)", "GEOMETRYCOLLECTION (POINT (1 2), POINT (2 3))", "MULTIPOINT ((1 2), (2 3))"); + assertUnion("POINT (1 2)", "GEOMETRYCOLLECTION (MULTIPOINT (1 2, 2 3))", "MULTIPOINT ((1 2), (2 3))"); + assertUnion("POINT (1 2)", "GEOMETRYCOLLECTION (LINESTRING (1 2, 3 4))", "LINESTRING (1 2, 3 4)"); + assertUnion("POINT (1 2)", "GEOMETRYCOLLECTION (POINT (0 0), LINESTRING (1 2, 3 4))", "GEOMETRYCOLLECTION (POINT (0 0), LINESTRING (1 2, 3 4))"); + assertUnion("POINT (1 2)", "GEOMETRYCOLLECTION (POLYGON ((0 0, 3 0, 3 3, 0 3, 0 0)))", "POLYGON ((0 0, 3 0, 3 3, 0 3, 0 0))"); + assertUnion("POINT (1 2)", "GEOMETRYCOLLECTION (POINT (5 5), POLYGON ((0 0, 3 0, 3 3, 0 3, 0 0)))", "GEOMETRYCOLLECTION (POINT (5 5), POLYGON ((0 0, 3 0, 3 3, 0 3, 0 0)))"); + } + + @Test + public void testUnionLinestring() + { + // linestring - linestring + assertUnion("LINESTRING (1 2, 3 4)", "LINESTRING (1 2, 3 4)", "LINESTRING (1 2, 3 4)"); + assertUnion("LINESTRING (1 2, 3 4)", "LINESTRING EMPTY", "LINESTRING (1 2, 3 4)"); + assertUnion("LINESTRING (1 2, 3 4)", "LINESTRING (3 4, 1 2)", "LINESTRING (1 2, 3 4)"); + assertUnion("LINESTRING (1 2, 3 4)", "LINESTRING (3 4, 5 6)", "LINESTRING (1 2, 3 4, 5 6)"); + assertUnion("LINESTRING (1 2, 3 4)", "LINESTRING (5 6, 7 8)", "MULTILINESTRING ((1 2, 3 4), (5 6, 7 8))"); + assertUnion("LINESTRING (1 2, 3 4)", "LINESTRING (2 1, 2 5)", "MULTILINESTRING ((2 1, 2 3), (1 2, 2 3), (2 3, 3 4), (2 3, 2 5))"); + assertUnion("LINESTRING (1 2, 3 4)", "LINESTRING (1 2, 2 3, 4 3)", "MULTILINESTRING ((1 2, 2 3), (2 3, 4 3), (2 3, 3 4))"); + assertUnion("LINESTRING (1 2, 3 4)", "LINESTRING (2 3, 2.1 3.1)", "LINESTRING (1 2, 2 3, 2.0999999999999996 3.0999999999999996, 3 4)"); + + // linestring - polygon + assertUnion("LINESTRING (1 2, 3 4)", "POLYGON ((5 5, 6 5, 6 6, 5 5))", "GEOMETRYCOLLECTION (LINESTRING (1 2, 3 4), POLYGON ((5 5, 6 5, 6 6, 5 5)))"); + assertUnion("LINESTRING (1 2, 3 4)", "POLYGON EMPTY", "LINESTRING (1 2, 3 4)"); + // linestring inside polygon + assertUnion("LINESTRING (1 2, 3 4)", "POLYGON ((0 0, 5 0, 5 5, 0 5, 0 0))", "POLYGON ((0 0, 5 0, 5 5, 0 5, 0 0))"); + assertUnion("LINESTRING (0 0, 5 0)", "POLYGON ((0 0, 5 0, 5 5, 0 5, 0 0))", "POLYGON ((0 0, 5 0, 5 5, 0 5, 0 0))"); + // linestring crosses polygon's vertex + assertUnion("LINESTRING (0 0, 6 6)", "POLYGON ((0 0, 5 0, 5 5, 0 5, 0 0))", "GEOMETRYCOLLECTION (LINESTRING (5 5, 6 6), POLYGON ((0 0, 5 0, 5 5, 0 5, 0 0)))"); + assertUnion("LINESTRING (4 6, 6 4)", "POLYGON ((0 0, 5 0, 5 5, 0 5, 0 0))", "GEOMETRYCOLLECTION (LINESTRING (4 6, 5 5, 6 4), POLYGON ((0 0, 5 0, 5 5, 0 5, 0 0)))"); + // linestring crosses polygon's boundary + assertUnion("LINESTRING (1 1, 1 6)", "POLYGON ((0 0, 5 0, 5 5, 0 5, 0 0))", "GEOMETRYCOLLECTION (LINESTRING (1 5, 1 6), POLYGON ((0 0, 5 0, 5 5, 1 5, 0 5, 0 0)))"); + + // linestring - geometry collection + assertUnion("LINESTRING (1 2, 3 4)", "GEOMETRYCOLLECTION (LINESTRING (1 2, 3 4))", "LINESTRING (1 2, 3 4)"); + assertUnion("LINESTRING (1 2, 3 4)", "GEOMETRYCOLLECTION EMPTY", "LINESTRING (1 2, 3 4)"); + assertUnion("LINESTRING (1 2, 3 4)", "GEOMETRYCOLLECTION (LINESTRING (3 4, 5 6))", "LINESTRING (1 2, 3 4, 5 6)"); + assertUnion("LINESTRING (1 2, 3 4)", "GEOMETRYCOLLECTION (LINESTRING (3 4, 5 6), LINESTRING (7 8, 9 10))", "MULTILINESTRING ((1 2, 3 4, 5 6), (7 8, 9 10))"); + assertUnion("LINESTRING (1 2, 3 4)", "GEOMETRYCOLLECTION (POINT (1 2), LINESTRING (3 4, 5 6))", "LINESTRING (1 2, 3 4, 5 6)"); + assertUnion("LINESTRING (1 2, 3 4)", "GEOMETRYCOLLECTION (POINT (1 2), LINESTRING (3 4, 5 6), POLYGON ((3 0, 4 0, 4 1, 3 0)))", "GEOMETRYCOLLECTION (LINESTRING (1 2, 3 4, 5 6), POLYGON ((3 0, 4 0, 4 1, 3 0)))"); + } + + @Test + public void testUnionPolygon() + { + // polygon - polygon + assertUnion("POLYGON ((0 0, 1 0, 1 1, 0 0))", "POLYGON ((0 0, 1 0, 1 1, 0 0))", "POLYGON ((0 0, 1 0, 1 1, 0 0))"); + assertUnion("POLYGON ((0 0, 1 0, 1 1, 0 0))", "POLYGON EMPTY", "POLYGON ((0 0, 1 0, 1 1, 0 0))"); + // one polygon contains the other + assertUnion("POLYGON ((0 0, 5 0, 5 5, 0 5, 0 0))", "POLYGON ((1 1, 2 1, 2 2, 1 1))", "POLYGON ((0 0, 5 0, 5 5, 0 5, 0 0))"); + // The following test fails because vertex order in the union geometry depends on the order of union inputs + //assertUnion("POLYGON ((0 0, 1 0, 1 1, 0 0))", "POLYGON ((0 0, 0.5 0, 0.5 0.5, 0 0))", "POLYGON ((0 0, 0.5 0, 1 0, 1 1, 0.49999999999999994 0.49999999999999994, 0 0))"); + // polygons intersect + assertUnion("POLYGON ((0 0, 1 0, 1 1, 0 0))", "POLYGON ((0 0.5, 2 0.5, 2 2, 0 2, 0 0.5))", "POLYGON ((0 0, 1 0, 1 0.5, 2 0.5, 2 2, 0 2, 0 0.5, 0.5 0.5, 0 0))"); + // disjoint polygons + assertUnion("POLYGON ((0 0, 1 0, 1 1, 0 0))", "POLYGON ((3 3, 5 3, 5 5, 3 3))", "MULTIPOLYGON (((0 0, 1 0, 1 1, 0 0)), ((3 3, 5 3, 5 5, 3 3)))"); + + // polygon - multi-polygon + assertUnion("POLYGON ((0 0, 1 0, 1 1, 0 0))", "MULTIPOLYGON (((0 0, 1 0, 1 1, 0 0)))", "POLYGON ((0 0, 1 0, 1 1, 0 0))"); + assertUnion("POLYGON ((0 0, 1 0, 1 1, 0 0))", "MULTIPOLYGON EMPTY", "POLYGON ((0 0, 1 0, 1 1, 0 0))"); + assertUnion("POLYGON ((0 0, 1 0, 1 1, 0 0))", "MULTIPOLYGON (((0 0.5, 2 0.5, 2 2, 0 2, 0 0.5)))", "POLYGON ((0 0, 1 0, 1 0.5, 2 0.5, 2 2, 0 2, 0 0.5, 0.5 0.5, 0 0))"); + assertUnion("POLYGON ((0 0, 1 0, 1 1, 0 0))", "MULTIPOLYGON (((3 3, 5 3, 5 5, 3 3)))", "MULTIPOLYGON (((0 0, 1 0, 1 1, 0 0)), ((3 3, 5 3, 5 5, 3 3)))"); + + // polygon - geometry collection + assertUnion("POLYGON ((0 0, 1 0, 1 1, 0 0))", "GEOMETRYCOLLECTION (POLYGON ((0 0, 1 0, 1 1, 0 0)))", "POLYGON ((0 0, 1 0, 1 1, 0 0))"); + assertUnion("POLYGON ((0 0, 1 0, 1 1, 0 0))", "GEOMETRYCOLLECTION EMPTY", "POLYGON ((0 0, 1 0, 1 1, 0 0))"); + assertUnion("POLYGON ((0 0, 1 0, 1 1, 0 0))", "GEOMETRYCOLLECTION (POLYGON ((3 3, 5 3, 5 5, 3 3)))", "MULTIPOLYGON (((0 0, 1 0, 1 1, 0 0)), ((3 3, 5 3, 5 5, 3 3)))"); + assertUnion("POLYGON ((0 0, 1 0, 1 1, 0 0))", "GEOMETRYCOLLECTION (POINT (0 0), POLYGON ((3 3, 5 3, 5 5, 3 3)))", "MULTIPOLYGON (((0 0, 1 0, 1 1, 0 0)), ((3 3, 5 3, 5 5, 3 3)))"); + assertUnion("POLYGON ((0 0, 1 0, 1 1, 0 0))", "GEOMETRYCOLLECTION (POINT (10 10), POLYGON ((3 3, 5 3, 5 5, 3 3)))", "GEOMETRYCOLLECTION (POINT (10 10), MULTIPOLYGON (((0 0, 1 0, 1 1, 0 0)), ((3 3, 5 3, 5 5, 3 3))))"); + } + + private void assertUnion(String wkt, String otherWkt, String expectedWkt) + { + OGCGeometry geometry = OGCGeometry.fromText(wkt); + OGCGeometry otherGeometry = OGCGeometry.fromText(otherWkt); + Assert.assertEquals(expectedWkt, geometry.union(otherGeometry).asText()); + Assert.assertEquals(expectedWkt, otherGeometry.union(geometry).asText()); + } +} From b06e5793b00a4369cae7c559606147a29d910683 Mon Sep 17 00:00:00 2001 From: Sergey Tolstov Date: Sun, 3 Jun 2018 17:32:24 -0700 Subject: [PATCH 162/196] comment out unused code --- .../com/esri/core/geometry/OperatorDifferenceLocal.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/esri/core/geometry/OperatorDifferenceLocal.java b/src/main/java/com/esri/core/geometry/OperatorDifferenceLocal.java index b68b050f..e52e670d 100644 --- a/src/main/java/com/esri/core/geometry/OperatorDifferenceLocal.java +++ b/src/main/java/com/esri/core/geometry/OperatorDifferenceLocal.java @@ -358,7 +358,9 @@ static Geometry multiPointMinusPoint_(MultiPoint multi_point, Point point, return new_multipoint; } - static Geometry polylineMinusArea_(Geometry geometry, Geometry area, + /* + This optimization causes a bug when polyline segments are on the boundary. + static Geometry polylineMinusArea_(Geometry geometry, Geometry area, int area_type, SpatialReference sr, ProgressTracker progress_tracker) { // construct the complement of the Polygon (or Envelope) Envelope envelope = new Envelope(); @@ -387,6 +389,6 @@ static Geometry polylineMinusArea_(Geometry geometry, Geometry area, Geometry difference = operatorIntersection.execute(geometry, complement, sr, progress_tracker); return difference; - } + }*/ } From c47a0e49ba6b258cf0025cb6c9d9d2675cb0c4c6 Mon Sep 17 00:00:00 2001 From: Sergey Tolstov Date: Mon, 4 Jun 2018 20:36:39 -0700 Subject: [PATCH 163/196] reflect review comments. fix formatting in a test --- .../geometry/OperatorDifferenceLocal.java | 39 +-- .../ogc/OGCConcreteGeometryCollection.java | 28 +- .../geometry/TestOGCGeometryCollection.java | 319 ++++++++++-------- 3 files changed, 188 insertions(+), 198 deletions(-) diff --git a/src/main/java/com/esri/core/geometry/OperatorDifferenceLocal.java b/src/main/java/com/esri/core/geometry/OperatorDifferenceLocal.java index e52e670d..31db5027 100644 --- a/src/main/java/com/esri/core/geometry/OperatorDifferenceLocal.java +++ b/src/main/java/com/esri/core/geometry/OperatorDifferenceLocal.java @@ -81,10 +81,6 @@ static Geometry difference(Geometry geometry_a, Geometry geometry_b, if (!env_a_inflated.isIntersecting(env_b)) return geometry_a; - /*if (dimension_a == 1 && dimension_b == 2) - return polylineMinusArea_(geometry_a, geometry_b, type_b, - spatial_reference, progress_tracker);*/ - if (type_a == Geometry.GeometryType.Point) { Geometry geometry_b_; if (MultiPath.isSegment(type_b)) { @@ -357,38 +353,5 @@ static Geometry multiPointMinusPoint_(MultiPoint multi_point, Point point, return new_multipoint; } - - /* - This optimization causes a bug when polyline segments are on the boundary. - static Geometry polylineMinusArea_(Geometry geometry, Geometry area, - int area_type, SpatialReference sr, ProgressTracker progress_tracker) { - // construct the complement of the Polygon (or Envelope) - Envelope envelope = new Envelope(); - geometry.queryEnvelope(envelope); - Envelope2D env_2D = new Envelope2D(); - area.queryEnvelope2D(env_2D); - envelope.merge(env_2D); - double dw = 0.1 * envelope.getWidth(); - double dh = 0.1 * envelope.getHeight(); - envelope.inflate(dw, dh); - - Polygon complement = new Polygon(); - complement.addEnvelope(envelope, false); - - MultiPathImpl complementImpl = (MultiPathImpl) (complement._getImpl()); - - if (area_type == Geometry.GeometryType.Polygon) { - MultiPathImpl polygonImpl = (MultiPathImpl) (area._getImpl()); - complementImpl.add(polygonImpl, true); - } else - complementImpl.addEnvelope((Envelope) (area), true); - - OperatorFactoryLocal projEnv = OperatorFactoryLocal.getInstance(); - OperatorIntersection operatorIntersection = (OperatorIntersection) projEnv - .getOperator(Operator.Type.Intersection); - Geometry difference = operatorIntersection.execute(geometry, - complement, sr, progress_tracker); - return difference; - }*/ - } + diff --git a/src/main/java/com/esri/core/geometry/ogc/OGCConcreteGeometryCollection.java b/src/main/java/com/esri/core/geometry/ogc/OGCConcreteGeometryCollection.java index 74e5876b..d0a82e3f 100644 --- a/src/main/java/com/esri/core/geometry/ogc/OGCConcreteGeometryCollection.java +++ b/src/main/java/com/esri/core/geometry/ogc/OGCConcreteGeometryCollection.java @@ -598,33 +598,7 @@ public boolean contains(OGCGeometry another) { if (this == another) return true; - //TODO: a simple envelope test - - OGCConcreteGeometryCollection flattened1 = flatten(); - if (flattened1.isEmpty()) - return true; - OGCConcreteGeometryCollection otherCol = new OGCConcreteGeometryCollection(another, esriSR); - OGCConcreteGeometryCollection flattened2 = otherCol.flatten(); - if (flattened2.isEmpty()) - return true; - - for (int i = 0, n2 = flattened2.numGeometries(); i < n2; ++i) { - OGCGeometry g2 = flattened2.geometryN(i); - boolean good = false; - for (int j = 0, n1 = flattened1.numGeometries(); j < n1; ++j) { - OGCGeometry g1 = flattened1.geometryN(i); - if (g1.contains(g2)) { - good = true; - break; - } - } - - if (!good) - return false; - } - - //each geometry of another is contained in a geometry from this. - return true; + return another.difference(this).isEmpty(); } @Override diff --git a/src/test/java/com/esri/core/geometry/TestOGCGeometryCollection.java b/src/test/java/com/esri/core/geometry/TestOGCGeometryCollection.java index 1d1a32d7..deadad18 100644 --- a/src/test/java/com/esri/core/geometry/TestOGCGeometryCollection.java +++ b/src/test/java/com/esri/core/geometry/TestOGCGeometryCollection.java @@ -17,137 +17,190 @@ import org.junit.Assert; import org.junit.Test; -public class TestOGCGeometryCollection -{ - @Test - public void testUnionPoint() - { - // point - point - assertUnion("POINT (1 2)", "POINT (1 2)", "POINT (1 2)"); - assertUnion("POINT (1 2)", "POINT EMPTY", "POINT (1 2)"); - assertUnion("POINT (1 2)", "POINT (3 4)", "MULTIPOINT ((1 2), (3 4))"); - - // point - multi-point - assertUnion("POINT (1 2)", "MULTIPOINT (1 2)", "POINT (1 2)"); - assertUnion("POINT (1 2)", "MULTIPOINT EMPTY", "POINT (1 2)"); - assertUnion("POINT (1 2)", "MULTIPOINT (3 4)", "MULTIPOINT ((1 2), (3 4))"); - assertUnion("POINT (1 2)", "MULTIPOINT (1 2, 3 4)", "MULTIPOINT ((1 2), (3 4))"); - assertUnion("POINT (1 2)", "MULTIPOINT (3 4, 5 6)", "MULTIPOINT ((1 2), (3 4), (5 6))"); - - // point - linestring - assertUnion("POINT (1 2)", "LINESTRING (3 4, 5 6)", "GEOMETRYCOLLECTION (POINT (1 2), LINESTRING (3 4, 5 6))"); - assertUnion("POINT (1 2)", "LINESTRING EMPTY", "POINT (1 2)"); - assertUnion("POINT (1 2)", "LINESTRING (1 2, 3 4)", "LINESTRING (1 2, 3 4)"); - assertUnion("POINT (1 2)", "LINESTRING (1 1, 1 3, 3 4)", "LINESTRING (1 1, 1 2, 1 3, 3 4)"); - - // point - multi-linestring - assertUnion("POINT (1 2)", "MULTILINESTRING ((3 4, 5 6))", "GEOMETRYCOLLECTION (POINT (1 2), LINESTRING (3 4, 5 6))"); - assertUnion("POINT (1 2)", "MULTILINESTRING EMPTY", "POINT (1 2)"); - assertUnion("POINT (1 2)", "MULTILINESTRING ((3 4, 5 6), (7 8, 9 10, 11 12))", "GEOMETRYCOLLECTION (POINT (1 2), MULTILINESTRING ((3 4, 5 6), (7 8, 9 10, 11 12)))"); - assertUnion("POINT (1 2)", "MULTILINESTRING ((1 2, 3 4))", "LINESTRING (1 2, 3 4)"); - assertUnion("POINT (1 2)", "MULTILINESTRING ((1 1, 1 3, 3 4), (7 8, 9 10, 11 12))", "MULTILINESTRING ((1 1, 1 2, 1 3, 3 4), (7 8, 9 10, 11 12))"); - - // point - polygon - assertUnion("POINT (1 2)", "POLYGON ((0 0, 1 0, 1 1, 0 0))", "GEOMETRYCOLLECTION (POINT (1 2), POLYGON ((0 0, 1 0, 1 1, 0 0)))"); - assertUnion("POINT (1 2)", "POLYGON EMPTY", "POINT (1 2)"); - // point inside polygon - assertUnion("POINT (1 2)", "POLYGON ((0 0, 3 0, 3 3, 0 3, 0 0))", "POLYGON ((0 0, 3 0, 3 3, 0 3, 0 0))"); - // point inside polygon with a hole - assertUnion("POINT (1 2)", "POLYGON ((0 0, 3 0, 3 3, 0 3, 0 0), (2 2, 2 2.5, 2.5 2.5, 2.5 2, 2 2))", "POLYGON ((0 0, 3 0, 3 3, 0 3, 0 0), (2 2, 2 2.5, 2.5 2.5, 2.5 2, 2 2))"); - // point inside polygon's hole - assertUnion("POINT (1 2)", "POLYGON ((0 0, 3 0, 3 3, 0 3, 0 0), (0.5 1, 0.5 2.5, 2.5 2.5, 2.5 1, 0.5 1))", "GEOMETRYCOLLECTION (POINT (1 2), POLYGON ((0 0, 3 0, 3 3, 0 3, 0 0), (0.5 1, 0.5 2.5, 2.5 2.5, 2.5 1, 0.5 1)))"); - // point is a vertex of the polygon - assertUnion("POINT (1 2)", "POLYGON ((1 2, 2 2, 2 3, 1 3, 1 2))", "POLYGON ((1 2, 2 2, 2 3, 1 3, 1 2))"); - // point on the boundary of the polybon - assertUnion("POINT (1 2)", "POLYGON ((1 1, 2 1, 2 3, 1 3, 1 1))", "POLYGON ((1 1, 2 1, 2 3, 1 3, 1 2, 1 1))"); - - // point - multi-polygon - assertUnion("POINT (1 2)", "MULTIPOLYGON (((0 0, 1 0, 1 1, 0 0)))", "GEOMETRYCOLLECTION (POINT (1 2), POLYGON ((0 0, 1 0, 1 1, 0 0)))"); - assertUnion("POINT (1 2)", "MULTIPOLYGON EMPTY", "POINT (1 2)"); - assertUnion("POINT (1 2)", "MULTIPOLYGON (((0 0, 3 0, 3 3, 0 3, 0 0)))", "POLYGON ((0 0, 3 0, 3 3, 0 3, 0 0))"); - assertUnion("POINT (1 2)", "MULTIPOLYGON (((0 0, 3 0, 3 3, 0 3, 0 0)), ((4 4, 5 4, 5 5, 4 4)))", "MULTIPOLYGON (((0 0, 3 0, 3 3, 0 3, 0 0)), ((4 4, 5 4, 5 5, 4 4)))"); - assertUnion("POINT (1 2)", "MULTIPOLYGON (((0 0, 3 0, 3 3, 0 3, 0 0), (0.5 1, 0.5 2.5, 2.5 2.5, 2.5 1, 0.5 1)))", "GEOMETRYCOLLECTION (POINT (1 2), POLYGON ((0 0, 3 0, 3 3, 0 3, 0 0), (0.5 1, 0.5 2.5, 2.5 2.5, 2.5 1, 0.5 1)))"); - assertUnion("POINT (1 2)", "MULTIPOLYGON (((0 0, 3 0, 3 3, 0 3, 0 0), (0.5 1, 0.5 2.5, 2.5 2.5, 2.5 1, 0.5 1)), ((4 4, 5 4, 5 5, 4 4)))", "GEOMETRYCOLLECTION (POINT (1 2), MULTIPOLYGON (((0 0, 3 0, 3 3, 0 3, 0 0), (0.5 1, 0.5 2.5, 2.5 2.5, 2.5 1, 0.5 1)), ((4 4, 5 4, 5 5, 4 4))))"); - - // point - geometry collection - assertUnion("POINT (1 2)", "GEOMETRYCOLLECTION (POINT (1 2))", "POINT (1 2)"); - assertUnion("POINT (1 2)", "GEOMETRYCOLLECTION EMPTY", "POINT (1 2)"); - assertUnion("POINT (1 2)", "GEOMETRYCOLLECTION (POINT (1 2), POINT (2 3))", "MULTIPOINT ((1 2), (2 3))"); - assertUnion("POINT (1 2)", "GEOMETRYCOLLECTION (MULTIPOINT (1 2, 2 3))", "MULTIPOINT ((1 2), (2 3))"); - assertUnion("POINT (1 2)", "GEOMETRYCOLLECTION (LINESTRING (1 2, 3 4))", "LINESTRING (1 2, 3 4)"); - assertUnion("POINT (1 2)", "GEOMETRYCOLLECTION (POINT (0 0), LINESTRING (1 2, 3 4))", "GEOMETRYCOLLECTION (POINT (0 0), LINESTRING (1 2, 3 4))"); - assertUnion("POINT (1 2)", "GEOMETRYCOLLECTION (POLYGON ((0 0, 3 0, 3 3, 0 3, 0 0)))", "POLYGON ((0 0, 3 0, 3 3, 0 3, 0 0))"); - assertUnion("POINT (1 2)", "GEOMETRYCOLLECTION (POINT (5 5), POLYGON ((0 0, 3 0, 3 3, 0 3, 0 0)))", "GEOMETRYCOLLECTION (POINT (5 5), POLYGON ((0 0, 3 0, 3 3, 0 3, 0 0)))"); - } - - @Test - public void testUnionLinestring() - { - // linestring - linestring - assertUnion("LINESTRING (1 2, 3 4)", "LINESTRING (1 2, 3 4)", "LINESTRING (1 2, 3 4)"); - assertUnion("LINESTRING (1 2, 3 4)", "LINESTRING EMPTY", "LINESTRING (1 2, 3 4)"); - assertUnion("LINESTRING (1 2, 3 4)", "LINESTRING (3 4, 1 2)", "LINESTRING (1 2, 3 4)"); - assertUnion("LINESTRING (1 2, 3 4)", "LINESTRING (3 4, 5 6)", "LINESTRING (1 2, 3 4, 5 6)"); - assertUnion("LINESTRING (1 2, 3 4)", "LINESTRING (5 6, 7 8)", "MULTILINESTRING ((1 2, 3 4), (5 6, 7 8))"); - assertUnion("LINESTRING (1 2, 3 4)", "LINESTRING (2 1, 2 5)", "MULTILINESTRING ((2 1, 2 3), (1 2, 2 3), (2 3, 3 4), (2 3, 2 5))"); - assertUnion("LINESTRING (1 2, 3 4)", "LINESTRING (1 2, 2 3, 4 3)", "MULTILINESTRING ((1 2, 2 3), (2 3, 4 3), (2 3, 3 4))"); - assertUnion("LINESTRING (1 2, 3 4)", "LINESTRING (2 3, 2.1 3.1)", "LINESTRING (1 2, 2 3, 2.0999999999999996 3.0999999999999996, 3 4)"); - - // linestring - polygon - assertUnion("LINESTRING (1 2, 3 4)", "POLYGON ((5 5, 6 5, 6 6, 5 5))", "GEOMETRYCOLLECTION (LINESTRING (1 2, 3 4), POLYGON ((5 5, 6 5, 6 6, 5 5)))"); - assertUnion("LINESTRING (1 2, 3 4)", "POLYGON EMPTY", "LINESTRING (1 2, 3 4)"); - // linestring inside polygon - assertUnion("LINESTRING (1 2, 3 4)", "POLYGON ((0 0, 5 0, 5 5, 0 5, 0 0))", "POLYGON ((0 0, 5 0, 5 5, 0 5, 0 0))"); - assertUnion("LINESTRING (0 0, 5 0)", "POLYGON ((0 0, 5 0, 5 5, 0 5, 0 0))", "POLYGON ((0 0, 5 0, 5 5, 0 5, 0 0))"); - // linestring crosses polygon's vertex - assertUnion("LINESTRING (0 0, 6 6)", "POLYGON ((0 0, 5 0, 5 5, 0 5, 0 0))", "GEOMETRYCOLLECTION (LINESTRING (5 5, 6 6), POLYGON ((0 0, 5 0, 5 5, 0 5, 0 0)))"); - assertUnion("LINESTRING (4 6, 6 4)", "POLYGON ((0 0, 5 0, 5 5, 0 5, 0 0))", "GEOMETRYCOLLECTION (LINESTRING (4 6, 5 5, 6 4), POLYGON ((0 0, 5 0, 5 5, 0 5, 0 0)))"); - // linestring crosses polygon's boundary - assertUnion("LINESTRING (1 1, 1 6)", "POLYGON ((0 0, 5 0, 5 5, 0 5, 0 0))", "GEOMETRYCOLLECTION (LINESTRING (1 5, 1 6), POLYGON ((0 0, 5 0, 5 5, 1 5, 0 5, 0 0)))"); - - // linestring - geometry collection - assertUnion("LINESTRING (1 2, 3 4)", "GEOMETRYCOLLECTION (LINESTRING (1 2, 3 4))", "LINESTRING (1 2, 3 4)"); - assertUnion("LINESTRING (1 2, 3 4)", "GEOMETRYCOLLECTION EMPTY", "LINESTRING (1 2, 3 4)"); - assertUnion("LINESTRING (1 2, 3 4)", "GEOMETRYCOLLECTION (LINESTRING (3 4, 5 6))", "LINESTRING (1 2, 3 4, 5 6)"); - assertUnion("LINESTRING (1 2, 3 4)", "GEOMETRYCOLLECTION (LINESTRING (3 4, 5 6), LINESTRING (7 8, 9 10))", "MULTILINESTRING ((1 2, 3 4, 5 6), (7 8, 9 10))"); - assertUnion("LINESTRING (1 2, 3 4)", "GEOMETRYCOLLECTION (POINT (1 2), LINESTRING (3 4, 5 6))", "LINESTRING (1 2, 3 4, 5 6)"); - assertUnion("LINESTRING (1 2, 3 4)", "GEOMETRYCOLLECTION (POINT (1 2), LINESTRING (3 4, 5 6), POLYGON ((3 0, 4 0, 4 1, 3 0)))", "GEOMETRYCOLLECTION (LINESTRING (1 2, 3 4, 5 6), POLYGON ((3 0, 4 0, 4 1, 3 0)))"); - } - - @Test - public void testUnionPolygon() - { - // polygon - polygon - assertUnion("POLYGON ((0 0, 1 0, 1 1, 0 0))", "POLYGON ((0 0, 1 0, 1 1, 0 0))", "POLYGON ((0 0, 1 0, 1 1, 0 0))"); - assertUnion("POLYGON ((0 0, 1 0, 1 1, 0 0))", "POLYGON EMPTY", "POLYGON ((0 0, 1 0, 1 1, 0 0))"); - // one polygon contains the other - assertUnion("POLYGON ((0 0, 5 0, 5 5, 0 5, 0 0))", "POLYGON ((1 1, 2 1, 2 2, 1 1))", "POLYGON ((0 0, 5 0, 5 5, 0 5, 0 0))"); - // The following test fails because vertex order in the union geometry depends on the order of union inputs - //assertUnion("POLYGON ((0 0, 1 0, 1 1, 0 0))", "POLYGON ((0 0, 0.5 0, 0.5 0.5, 0 0))", "POLYGON ((0 0, 0.5 0, 1 0, 1 1, 0.49999999999999994 0.49999999999999994, 0 0))"); - // polygons intersect - assertUnion("POLYGON ((0 0, 1 0, 1 1, 0 0))", "POLYGON ((0 0.5, 2 0.5, 2 2, 0 2, 0 0.5))", "POLYGON ((0 0, 1 0, 1 0.5, 2 0.5, 2 2, 0 2, 0 0.5, 0.5 0.5, 0 0))"); - // disjoint polygons - assertUnion("POLYGON ((0 0, 1 0, 1 1, 0 0))", "POLYGON ((3 3, 5 3, 5 5, 3 3))", "MULTIPOLYGON (((0 0, 1 0, 1 1, 0 0)), ((3 3, 5 3, 5 5, 3 3)))"); - - // polygon - multi-polygon - assertUnion("POLYGON ((0 0, 1 0, 1 1, 0 0))", "MULTIPOLYGON (((0 0, 1 0, 1 1, 0 0)))", "POLYGON ((0 0, 1 0, 1 1, 0 0))"); - assertUnion("POLYGON ((0 0, 1 0, 1 1, 0 0))", "MULTIPOLYGON EMPTY", "POLYGON ((0 0, 1 0, 1 1, 0 0))"); - assertUnion("POLYGON ((0 0, 1 0, 1 1, 0 0))", "MULTIPOLYGON (((0 0.5, 2 0.5, 2 2, 0 2, 0 0.5)))", "POLYGON ((0 0, 1 0, 1 0.5, 2 0.5, 2 2, 0 2, 0 0.5, 0.5 0.5, 0 0))"); - assertUnion("POLYGON ((0 0, 1 0, 1 1, 0 0))", "MULTIPOLYGON (((3 3, 5 3, 5 5, 3 3)))", "MULTIPOLYGON (((0 0, 1 0, 1 1, 0 0)), ((3 3, 5 3, 5 5, 3 3)))"); - - // polygon - geometry collection - assertUnion("POLYGON ((0 0, 1 0, 1 1, 0 0))", "GEOMETRYCOLLECTION (POLYGON ((0 0, 1 0, 1 1, 0 0)))", "POLYGON ((0 0, 1 0, 1 1, 0 0))"); - assertUnion("POLYGON ((0 0, 1 0, 1 1, 0 0))", "GEOMETRYCOLLECTION EMPTY", "POLYGON ((0 0, 1 0, 1 1, 0 0))"); - assertUnion("POLYGON ((0 0, 1 0, 1 1, 0 0))", "GEOMETRYCOLLECTION (POLYGON ((3 3, 5 3, 5 5, 3 3)))", "MULTIPOLYGON (((0 0, 1 0, 1 1, 0 0)), ((3 3, 5 3, 5 5, 3 3)))"); - assertUnion("POLYGON ((0 0, 1 0, 1 1, 0 0))", "GEOMETRYCOLLECTION (POINT (0 0), POLYGON ((3 3, 5 3, 5 5, 3 3)))", "MULTIPOLYGON (((0 0, 1 0, 1 1, 0 0)), ((3 3, 5 3, 5 5, 3 3)))"); - assertUnion("POLYGON ((0 0, 1 0, 1 1, 0 0))", "GEOMETRYCOLLECTION (POINT (10 10), POLYGON ((3 3, 5 3, 5 5, 3 3)))", "GEOMETRYCOLLECTION (POINT (10 10), MULTIPOLYGON (((0 0, 1 0, 1 1, 0 0)), ((3 3, 5 3, 5 5, 3 3))))"); - } - - private void assertUnion(String wkt, String otherWkt, String expectedWkt) - { - OGCGeometry geometry = OGCGeometry.fromText(wkt); - OGCGeometry otherGeometry = OGCGeometry.fromText(otherWkt); - Assert.assertEquals(expectedWkt, geometry.union(otherGeometry).asText()); - Assert.assertEquals(expectedWkt, otherGeometry.union(geometry).asText()); - } +public class TestOGCGeometryCollection { + @Test + public void testUnionPoint() { + // point - point + assertUnion("POINT (1 2)", "POINT (1 2)", "POINT (1 2)"); + assertUnion("POINT (1 2)", "POINT EMPTY", "POINT (1 2)"); + assertUnion("POINT (1 2)", "POINT (3 4)", "MULTIPOINT ((1 2), (3 4))"); + + // point - multi-point + assertUnion("POINT (1 2)", "MULTIPOINT (1 2)", "POINT (1 2)"); + assertUnion("POINT (1 2)", "MULTIPOINT EMPTY", "POINT (1 2)"); + assertUnion("POINT (1 2)", "MULTIPOINT (3 4)", "MULTIPOINT ((1 2), (3 4))"); + assertUnion("POINT (1 2)", "MULTIPOINT (1 2, 3 4)", "MULTIPOINT ((1 2), (3 4))"); + assertUnion("POINT (1 2)", "MULTIPOINT (3 4, 5 6)", "MULTIPOINT ((1 2), (3 4), (5 6))"); + + // point - linestring + assertUnion("POINT (1 2)", "LINESTRING (3 4, 5 6)", "GEOMETRYCOLLECTION (POINT (1 2), LINESTRING (3 4, 5 6))"); + assertUnion("POINT (1 2)", "LINESTRING EMPTY", "POINT (1 2)"); + assertUnion("POINT (1 2)", "LINESTRING (1 2, 3 4)", "LINESTRING (1 2, 3 4)"); + assertUnion("POINT (1 2)", "LINESTRING (1 1, 1 3, 3 4)", "LINESTRING (1 1, 1 2, 1 3, 3 4)"); + + // point - multi-linestring + assertUnion("POINT (1 2)", "MULTILINESTRING ((3 4, 5 6))", + "GEOMETRYCOLLECTION (POINT (1 2), LINESTRING (3 4, 5 6))"); + assertUnion("POINT (1 2)", "MULTILINESTRING EMPTY", "POINT (1 2)"); + assertUnion("POINT (1 2)", "MULTILINESTRING ((3 4, 5 6), (7 8, 9 10, 11 12))", + "GEOMETRYCOLLECTION (POINT (1 2), MULTILINESTRING ((3 4, 5 6), (7 8, 9 10, 11 12)))"); + assertUnion("POINT (1 2)", "MULTILINESTRING ((1 2, 3 4))", "LINESTRING (1 2, 3 4)"); + assertUnion("POINT (1 2)", "MULTILINESTRING ((1 1, 1 3, 3 4), (7 8, 9 10, 11 12))", + "MULTILINESTRING ((1 1, 1 2, 1 3, 3 4), (7 8, 9 10, 11 12))"); + + // point - polygon + assertUnion("POINT (1 2)", "POLYGON ((0 0, 1 0, 1 1, 0 0))", + "GEOMETRYCOLLECTION (POINT (1 2), POLYGON ((0 0, 1 0, 1 1, 0 0)))"); + assertUnion("POINT (1 2)", "POLYGON EMPTY", "POINT (1 2)"); + // point inside polygon + assertUnion("POINT (1 2)", "POLYGON ((0 0, 3 0, 3 3, 0 3, 0 0))", "POLYGON ((0 0, 3 0, 3 3, 0 3, 0 0))"); + // point inside polygon with a hole + assertUnion("POINT (1 2)", "POLYGON ((0 0, 3 0, 3 3, 0 3, 0 0), (2 2, 2 2.5, 2.5 2.5, 2.5 2, 2 2))", + "POLYGON ((0 0, 3 0, 3 3, 0 3, 0 0), (2 2, 2 2.5, 2.5 2.5, 2.5 2, 2 2))"); + // point inside polygon's hole + assertUnion("POINT (1 2)", "POLYGON ((0 0, 3 0, 3 3, 0 3, 0 0), (0.5 1, 0.5 2.5, 2.5 2.5, 2.5 1, 0.5 1))", + "GEOMETRYCOLLECTION (POINT (1 2), POLYGON ((0 0, 3 0, 3 3, 0 3, 0 0), (0.5 1, 0.5 2.5, 2.5 2.5, 2.5 1, 0.5 1)))"); + // point is a vertex of the polygon + assertUnion("POINT (1 2)", "POLYGON ((1 2, 2 2, 2 3, 1 3, 1 2))", "POLYGON ((1 2, 2 2, 2 3, 1 3, 1 2))"); + // point on the boundary of the polybon + assertUnion("POINT (1 2)", "POLYGON ((1 1, 2 1, 2 3, 1 3, 1 1))", "POLYGON ((1 1, 2 1, 2 3, 1 3, 1 2, 1 1))"); + + // point - multi-polygon + assertUnion("POINT (1 2)", "MULTIPOLYGON (((0 0, 1 0, 1 1, 0 0)))", + "GEOMETRYCOLLECTION (POINT (1 2), POLYGON ((0 0, 1 0, 1 1, 0 0)))"); + assertUnion("POINT (1 2)", "MULTIPOLYGON EMPTY", "POINT (1 2)"); + assertUnion("POINT (1 2)", "MULTIPOLYGON (((0 0, 3 0, 3 3, 0 3, 0 0)))", "POLYGON ((0 0, 3 0, 3 3, 0 3, 0 0))"); + assertUnion("POINT (1 2)", "MULTIPOLYGON (((0 0, 3 0, 3 3, 0 3, 0 0)), ((4 4, 5 4, 5 5, 4 4)))", + "MULTIPOLYGON (((0 0, 3 0, 3 3, 0 3, 0 0)), ((4 4, 5 4, 5 5, 4 4)))"); + assertUnion("POINT (1 2)", + "MULTIPOLYGON (((0 0, 3 0, 3 3, 0 3, 0 0), (0.5 1, 0.5 2.5, 2.5 2.5, 2.5 1, 0.5 1)))", + "GEOMETRYCOLLECTION (POINT (1 2), POLYGON ((0 0, 3 0, 3 3, 0 3, 0 0), (0.5 1, 0.5 2.5, 2.5 2.5, 2.5 1, 0.5 1)))"); + assertUnion("POINT (1 2)", + "MULTIPOLYGON (((0 0, 3 0, 3 3, 0 3, 0 0), (0.5 1, 0.5 2.5, 2.5 2.5, 2.5 1, 0.5 1)), ((4 4, 5 4, 5 5, 4 4)))", + "GEOMETRYCOLLECTION (POINT (1 2), MULTIPOLYGON (((0 0, 3 0, 3 3, 0 3, 0 0), (0.5 1, 0.5 2.5, 2.5 2.5, 2.5 1, 0.5 1)), ((4 4, 5 4, 5 5, 4 4))))"); + + // point - geometry collection + assertUnion("POINT (1 2)", "GEOMETRYCOLLECTION (POINT (1 2))", "POINT (1 2)"); + assertUnion("POINT (1 2)", "GEOMETRYCOLLECTION EMPTY", "POINT (1 2)"); + assertUnion("POINT (1 2)", "GEOMETRYCOLLECTION (POINT (1 2), POINT (2 3))", "MULTIPOINT ((1 2), (2 3))"); + assertUnion("POINT (1 2)", "GEOMETRYCOLLECTION (MULTIPOINT (1 2, 2 3))", "MULTIPOINT ((1 2), (2 3))"); + assertUnion("POINT (1 2)", "GEOMETRYCOLLECTION (LINESTRING (1 2, 3 4))", "LINESTRING (1 2, 3 4)"); + assertUnion("POINT (1 2)", "GEOMETRYCOLLECTION (POINT (0 0), LINESTRING (1 2, 3 4))", + "GEOMETRYCOLLECTION (POINT (0 0), LINESTRING (1 2, 3 4))"); + assertUnion("POINT (1 2)", "GEOMETRYCOLLECTION (POLYGON ((0 0, 3 0, 3 3, 0 3, 0 0)))", + "POLYGON ((0 0, 3 0, 3 3, 0 3, 0 0))"); + assertUnion("POINT (1 2)", "GEOMETRYCOLLECTION (POINT (5 5), POLYGON ((0 0, 3 0, 3 3, 0 3, 0 0)))", + "GEOMETRYCOLLECTION (POINT (5 5), POLYGON ((0 0, 3 0, 3 3, 0 3, 0 0)))"); + } + + @Test + public void testUnionLinestring() { + // linestring - linestring + assertUnion("LINESTRING (1 2, 3 4)", "LINESTRING (1 2, 3 4)", "LINESTRING (1 2, 3 4)"); + assertUnion("LINESTRING (1 2, 3 4)", "LINESTRING EMPTY", "LINESTRING (1 2, 3 4)"); + assertUnion("LINESTRING (1 2, 3 4)", "LINESTRING (3 4, 1 2)", "LINESTRING (1 2, 3 4)"); + assertUnion("LINESTRING (1 2, 3 4)", "LINESTRING (3 4, 5 6)", "LINESTRING (1 2, 3 4, 5 6)"); + assertUnion("LINESTRING (1 2, 3 4)", "LINESTRING (5 6, 7 8)", "MULTILINESTRING ((1 2, 3 4), (5 6, 7 8))"); + assertUnion("LINESTRING (1 2, 3 4)", "LINESTRING (2 1, 2 5)", + "MULTILINESTRING ((2 1, 2 3), (1 2, 2 3), (2 3, 3 4), (2 3, 2 5))"); + assertUnion("LINESTRING (1 2, 3 4)", "LINESTRING (1 2, 2 3, 4 3)", + "MULTILINESTRING ((1 2, 2 3), (2 3, 4 3), (2 3, 3 4))"); + assertUnion("LINESTRING (1 2, 3 4)", "LINESTRING (2 3, 2.1 3.1)", + "LINESTRING (1 2, 2 3, 2.0999999999999996 3.0999999999999996, 3 4)"); + + // linestring - polygon + assertUnion("LINESTRING (1 2, 3 4)", "POLYGON ((5 5, 6 5, 6 6, 5 5))", + "GEOMETRYCOLLECTION (LINESTRING (1 2, 3 4), POLYGON ((5 5, 6 5, 6 6, 5 5)))"); + assertUnion("LINESTRING (1 2, 3 4)", "POLYGON EMPTY", "LINESTRING (1 2, 3 4)"); + // linestring inside polygon + assertUnion("LINESTRING (1 2, 3 4)", "POLYGON ((0 0, 5 0, 5 5, 0 5, 0 0))", + "POLYGON ((0 0, 5 0, 5 5, 0 5, 0 0))"); + assertUnion("LINESTRING (0 0, 5 0)", "POLYGON ((0 0, 5 0, 5 5, 0 5, 0 0))", + "POLYGON ((0 0, 5 0, 5 5, 0 5, 0 0))"); + // linestring crosses polygon's vertex + assertUnion("LINESTRING (0 0, 6 6)", "POLYGON ((0 0, 5 0, 5 5, 0 5, 0 0))", + "GEOMETRYCOLLECTION (LINESTRING (5 5, 6 6), POLYGON ((0 0, 5 0, 5 5, 0 5, 0 0)))"); + assertUnion("LINESTRING (4 6, 6 4)", "POLYGON ((0 0, 5 0, 5 5, 0 5, 0 0))", + "GEOMETRYCOLLECTION (LINESTRING (4 6, 5 5, 6 4), POLYGON ((0 0, 5 0, 5 5, 0 5, 0 0)))"); + // linestring crosses polygon's boundary + assertUnion("LINESTRING (1 1, 1 6)", "POLYGON ((0 0, 5 0, 5 5, 0 5, 0 0))", + "GEOMETRYCOLLECTION (LINESTRING (1 5, 1 6), POLYGON ((0 0, 5 0, 5 5, 1 5, 0 5, 0 0)))"); + + // linestring - geometry collection + assertUnion("LINESTRING (1 2, 3 4)", "GEOMETRYCOLLECTION (LINESTRING (1 2, 3 4))", "LINESTRING (1 2, 3 4)"); + assertUnion("LINESTRING (1 2, 3 4)", "GEOMETRYCOLLECTION EMPTY", "LINESTRING (1 2, 3 4)"); + assertUnion("LINESTRING (1 2, 3 4)", "GEOMETRYCOLLECTION (LINESTRING (3 4, 5 6))", + "LINESTRING (1 2, 3 4, 5 6)"); + assertUnion("LINESTRING (1 2, 3 4)", "GEOMETRYCOLLECTION (LINESTRING (3 4, 5 6), LINESTRING (7 8, 9 10))", + "MULTILINESTRING ((1 2, 3 4, 5 6), (7 8, 9 10))"); + assertUnion("LINESTRING (1 2, 3 4)", "GEOMETRYCOLLECTION (POINT (1 2), LINESTRING (3 4, 5 6))", + "LINESTRING (1 2, 3 4, 5 6)"); + assertUnion("LINESTRING (1 2, 3 4)", + "GEOMETRYCOLLECTION (POINT (1 2), LINESTRING (3 4, 5 6), POLYGON ((3 0, 4 0, 4 1, 3 0)))", + "GEOMETRYCOLLECTION (LINESTRING (1 2, 3 4, 5 6), POLYGON ((3 0, 4 0, 4 1, 3 0)))"); + } + + @Test + public void testUnionPolygon() { + // polygon - polygon + assertUnion("POLYGON ((0 0, 1 0, 1 1, 0 0))", "POLYGON ((0 0, 1 0, 1 1, 0 0))", + "POLYGON ((0 0, 1 0, 1 1, 0 0))"); + assertUnion("POLYGON ((0 0, 1 0, 1 1, 0 0))", "POLYGON EMPTY", "POLYGON ((0 0, 1 0, 1 1, 0 0))"); + // one polygon contains the other + assertUnion("POLYGON ((0 0, 5 0, 5 5, 0 5, 0 0))", "POLYGON ((1 1, 2 1, 2 2, 1 1))", + "POLYGON ((0 0, 5 0, 5 5, 0 5, 0 0))"); + // The following test fails because vertex order in the union geometry depends + // on the order of union inputs + // assertUnion("POLYGON ((0 0, 1 0, 1 1, 0 0))", "POLYGON ((0 0, 0.5 0, 0.5 0.5, + // 0 0))", "POLYGON ((0 0, 0.5 0, 1 0, 1 1, 0.49999999999999994 + // 0.49999999999999994, 0 0))"); + // polygons intersect + assertUnion("POLYGON ((0 0, 1 0, 1 1, 0 0))", "POLYGON ((0 0.5, 2 0.5, 2 2, 0 2, 0 0.5))", + "POLYGON ((0 0, 1 0, 1 0.5, 2 0.5, 2 2, 0 2, 0 0.5, 0.5 0.5, 0 0))"); + // disjoint polygons + assertUnion("POLYGON ((0 0, 1 0, 1 1, 0 0))", "POLYGON ((3 3, 5 3, 5 5, 3 3))", + "MULTIPOLYGON (((0 0, 1 0, 1 1, 0 0)), ((3 3, 5 3, 5 5, 3 3)))"); + + // polygon - multi-polygon + assertUnion("POLYGON ((0 0, 1 0, 1 1, 0 0))", "MULTIPOLYGON (((0 0, 1 0, 1 1, 0 0)))", + "POLYGON ((0 0, 1 0, 1 1, 0 0))"); + assertUnion("POLYGON ((0 0, 1 0, 1 1, 0 0))", "MULTIPOLYGON EMPTY", "POLYGON ((0 0, 1 0, 1 1, 0 0))"); + assertUnion("POLYGON ((0 0, 1 0, 1 1, 0 0))", "MULTIPOLYGON (((0 0.5, 2 0.5, 2 2, 0 2, 0 0.5)))", + "POLYGON ((0 0, 1 0, 1 0.5, 2 0.5, 2 2, 0 2, 0 0.5, 0.5 0.5, 0 0))"); + assertUnion("POLYGON ((0 0, 1 0, 1 1, 0 0))", "MULTIPOLYGON (((3 3, 5 3, 5 5, 3 3)))", + "MULTIPOLYGON (((0 0, 1 0, 1 1, 0 0)), ((3 3, 5 3, 5 5, 3 3)))"); + + // polygon - geometry collection + assertUnion("POLYGON ((0 0, 1 0, 1 1, 0 0))", "GEOMETRYCOLLECTION (POLYGON ((0 0, 1 0, 1 1, 0 0)))", + "POLYGON ((0 0, 1 0, 1 1, 0 0))"); + assertUnion("POLYGON ((0 0, 1 0, 1 1, 0 0))", "GEOMETRYCOLLECTION EMPTY", "POLYGON ((0 0, 1 0, 1 1, 0 0))"); + assertUnion("POLYGON ((0 0, 1 0, 1 1, 0 0))", "GEOMETRYCOLLECTION (POLYGON ((3 3, 5 3, 5 5, 3 3)))", + "MULTIPOLYGON (((0 0, 1 0, 1 1, 0 0)), ((3 3, 5 3, 5 5, 3 3)))"); + assertUnion("POLYGON ((0 0, 1 0, 1 1, 0 0))", + "GEOMETRYCOLLECTION (POINT (0 0), POLYGON ((3 3, 5 3, 5 5, 3 3)))", + "MULTIPOLYGON (((0 0, 1 0, 1 1, 0 0)), ((3 3, 5 3, 5 5, 3 3)))"); + assertUnion("POLYGON ((0 0, 1 0, 1 1, 0 0))", + "GEOMETRYCOLLECTION (POINT (10 10), POLYGON ((3 3, 5 3, 5 5, 3 3)))", + "GEOMETRYCOLLECTION (POINT (10 10), MULTIPOLYGON (((0 0, 1 0, 1 1, 0 0)), ((3 3, 5 3, 5 5, 3 3))))"); + } + + private void assertUnion(String wkt, String otherWkt, String expectedWkt) { + OGCGeometry geometry = OGCGeometry.fromText(wkt); + OGCGeometry otherGeometry = OGCGeometry.fromText(otherWkt); + Assert.assertEquals(expectedWkt, geometry.union(otherGeometry).asText()); + Assert.assertEquals(expectedWkt, otherGeometry.union(geometry).asText()); + } + + @Test + public void testGeometryCollectionOverlappingContains() { + assertContains("GEOMETRYCOLLECTION (POINT (0 0), LINESTRING (0 1, 5 1))", + "GEOMETRYCOLLECTION (MULTIPOINT (0 0, 2 1))"); + } + + private void assertContains(String wkt, String otherWkt) { + OGCGeometry geometry = OGCGeometry.fromText(wkt); + OGCGeometry otherGeometry = OGCGeometry.fromText(otherWkt); + Assert.assertTrue(geometry.contains(otherGeometry)); + Assert.assertTrue(otherGeometry.within(geometry)); + } } From 873d073cbc66efd4616a8db2f0717538c646ac3f Mon Sep 17 00:00:00 2001 From: Sergey Tolstov Date: Wed, 6 Jun 2018 20:18:22 -0700 Subject: [PATCH 164/196] fix a typo in disjoint and a test --- .../ogc/OGCConcreteGeometryCollection.java | 2 +- .../core/geometry/TestOGCGeometryCollection.java | 14 ++++++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/esri/core/geometry/ogc/OGCConcreteGeometryCollection.java b/src/main/java/com/esri/core/geometry/ogc/OGCConcreteGeometryCollection.java index d0a82e3f..004c7a8f 100644 --- a/src/main/java/com/esri/core/geometry/ogc/OGCConcreteGeometryCollection.java +++ b/src/main/java/com/esri/core/geometry/ogc/OGCConcreteGeometryCollection.java @@ -581,7 +581,7 @@ public boolean disjoint(OGCGeometry another) { for (int i = 0, n1 = flattened1.numGeometries(); i < n1; ++i) { OGCGeometry g1 = flattened1.geometryN(i); for (int j = 0, n2 = flattened2.numGeometries(); j < n2; ++j) { - OGCGeometry g2 = flattened2.geometryN(i); + OGCGeometry g2 = flattened2.geometryN(j); if (!g1.disjoint(g2)) return false; } diff --git a/src/test/java/com/esri/core/geometry/TestOGCGeometryCollection.java b/src/test/java/com/esri/core/geometry/TestOGCGeometryCollection.java index deadad18..c74752ee 100644 --- a/src/test/java/com/esri/core/geometry/TestOGCGeometryCollection.java +++ b/src/test/java/com/esri/core/geometry/TestOGCGeometryCollection.java @@ -203,4 +203,18 @@ private void assertContains(String wkt, String otherWkt) { Assert.assertTrue(geometry.contains(otherGeometry)); Assert.assertTrue(otherGeometry.within(geometry)); } + + @Test + public void testGeometryCollectionDisjoint() { + assertDisjoint("GEOMETRYCOLLECTION (POINT (0 0), LINESTRING (0 1, 5 1))", + "GEOMETRYCOLLECTION (MULTIPOINT (10 0, 21 1), LINESTRING (30 0, 31 1), POLYGON ((40 0, 41 1, 40 1, 40 0)))"); + } + + private void assertDisjoint(String wkt, String otherWkt) { + OGCGeometry geometry = OGCGeometry.fromText(wkt); + OGCGeometry otherGeometry = OGCGeometry.fromText(otherWkt); + Assert.assertTrue(geometry.disjoint(otherGeometry)); + Assert.assertTrue(otherGeometry.disjoint(geometry)); + } + } From 7e19e6f09d07c749b13aa346a946aea1aff413e2 Mon Sep 17 00:00:00 2001 From: Sergey Tolstov Date: Thu, 7 Jun 2018 08:16:36 -0700 Subject: [PATCH 165/196] Reflect review comments, throw from unsupported methods --- .../ogc/OGCConcreteGeometryCollection.java | 37 ++++++++++++++++++- .../esri/core/geometry/ogc/OGCGeometry.java | 27 +++++++++++++- .../geometry/TestOGCGeometryCollection.java | 17 +++++++++ 3 files changed, 77 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/esri/core/geometry/ogc/OGCConcreteGeometryCollection.java b/src/main/java/com/esri/core/geometry/ogc/OGCConcreteGeometryCollection.java index 004c7a8f..3868a469 100644 --- a/src/main/java/com/esri/core/geometry/ogc/OGCConcreteGeometryCollection.java +++ b/src/main/java/com/esri/core/geometry/ogc/OGCConcreteGeometryCollection.java @@ -560,6 +560,29 @@ public double distance(OGCGeometry another) { // //Relational operations + @Override + public boolean overlaps(OGCGeometry another) { + //TODO + throw new UnsupportedOperationException(); + } + + @Override + public boolean touches(OGCGeometry another) { + //TODO + throw new UnsupportedOperationException(); + } + + @Override + public boolean crosses(OGCGeometry another) { + //TODO + throw new UnsupportedOperationException(); + } + + @Override + public boolean relate(OGCGeometry another, String matrix) { + throw new UnsupportedOperationException(); + } + @Override public boolean disjoint(OGCGeometry another) { if (isEmpty() || another.isEmpty()) @@ -672,7 +695,7 @@ public OGCGeometry difference(OGCGeometry another) { } if (result.size() == 1) { - result.get(0).reduceFromMulti(); + return result.get(0).reduceFromMulti(); } return new OGCConcreteGeometryCollection(result, esriSR); @@ -680,6 +703,10 @@ public OGCGeometry difference(OGCGeometry another) { @Override public OGCGeometry intersection(OGCGeometry another) { + if (isEmpty() || another.isEmpty()) { + return new OGCConcreteGeometryCollection(esriSR); + } + List list = wrapGeomsIntoList_(this, another); list = prepare_for_ops_(list); if (list.size() != 2) // this should not happen @@ -702,11 +729,17 @@ public OGCGeometry intersection(OGCGeometry another) { } if (result.size() == 1) { - result.get(0).reduceFromMulti(); + return result.get(0).reduceFromMulti(); } return (new OGCConcreteGeometryCollection(result, esriSR)).flattenAndRemoveOverlaps(); } + + @Override + public OGCGeometry symDifference(OGCGeometry another) { + //TODO + throw new UnsupportedOperationException(); + } //make a list of collections out of two geometries private static List wrapGeomsIntoList_(OGCGeometry g1, OGCGeometry g2) { diff --git a/src/main/java/com/esri/core/geometry/ogc/OGCGeometry.java b/src/main/java/com/esri/core/geometry/ogc/OGCGeometry.java index 5014e207..87e8c620 100644 --- a/src/main/java/com/esri/core/geometry/ogc/OGCGeometry.java +++ b/src/main/java/com/esri/core/geometry/ogc/OGCGeometry.java @@ -289,6 +289,11 @@ public boolean intersects(OGCGeometry another) { } public boolean touches(OGCGeometry another) { + if (another.geometryType() == OGCConcreteGeometryCollection.TYPE) { + //TODO + throw new UnsupportedOperationException(); + } + com.esri.core.geometry.Geometry geom1 = getEsriGeometry(); com.esri.core.geometry.Geometry geom2 = another.getEsriGeometry(); return com.esri.core.geometry.GeometryEngine.touches(geom1, geom2, @@ -296,6 +301,11 @@ public boolean touches(OGCGeometry another) { } public boolean crosses(OGCGeometry another) { + if (another.geometryType() == OGCConcreteGeometryCollection.TYPE) { + //TODO + throw new UnsupportedOperationException(); + } + com.esri.core.geometry.Geometry geom1 = getEsriGeometry(); com.esri.core.geometry.Geometry geom2 = another.getEsriGeometry(); return com.esri.core.geometry.GeometryEngine.crosses(geom1, geom2, @@ -308,7 +318,7 @@ public boolean within(OGCGeometry another) { public boolean contains(OGCGeometry another) { if (another.geometryType() == OGCConcreteGeometryCollection.TYPE) { - return another.contains(this); + return new OGCConcreteGeometryCollection(this, esriSR).contains(another); } com.esri.core.geometry.Geometry geom1 = getEsriGeometry(); @@ -318,6 +328,10 @@ public boolean contains(OGCGeometry another) { } public boolean overlaps(OGCGeometry another) { + if (another.geometryType() == OGCConcreteGeometryCollection.TYPE) { + return another.overlaps(this); //overlaps should be symmetric + } + com.esri.core.geometry.Geometry geom1 = getEsriGeometry(); com.esri.core.geometry.Geometry geom2 = another.getEsriGeometry(); return com.esri.core.geometry.GeometryEngine.overlaps(geom1, geom2, @@ -325,6 +339,11 @@ public boolean overlaps(OGCGeometry another) { } public boolean relate(OGCGeometry another, String matrix) { + if (another.geometryType() == OGCConcreteGeometryCollection.TYPE) { + //TODO + throw new UnsupportedOperationException(); + } + com.esri.core.geometry.Geometry geom1 = getEsriGeometry(); com.esri.core.geometry.Geometry geom2 = another.getEsriGeometry(); return com.esri.core.geometry.GeometryEngine.relate(geom1, geom2, @@ -337,8 +356,9 @@ public boolean relate(OGCGeometry another, String matrix) { // analysis public double distance(OGCGeometry another) { - if (this == another) + if (this == another) { return isEmpty() ? Double.NaN : 0; + } if (another.geometryType() == OGCConcreteGeometryCollection.TYPE) { return another.distance(this); @@ -510,6 +530,9 @@ public OGCGeometry difference(OGCGeometry another) { } public OGCGeometry symDifference(OGCGeometry another) { + if (another.geometryType() == OGCConcreteGeometryCollection.TYPE) + return another.symDifference(this); + com.esri.core.geometry.Geometry geom1 = getEsriGeometry(); com.esri.core.geometry.Geometry geom2 = another.getEsriGeometry(); return createFromEsriGeometry( diff --git a/src/test/java/com/esri/core/geometry/TestOGCGeometryCollection.java b/src/test/java/com/esri/core/geometry/TestOGCGeometryCollection.java index c74752ee..53007166 100644 --- a/src/test/java/com/esri/core/geometry/TestOGCGeometryCollection.java +++ b/src/test/java/com/esri/core/geometry/TestOGCGeometryCollection.java @@ -216,5 +216,22 @@ private void assertDisjoint(String wkt, String otherWkt) { Assert.assertTrue(geometry.disjoint(otherGeometry)); Assert.assertTrue(otherGeometry.disjoint(geometry)); } + + @Test + public void testGeometryCollectionIntersect() { + assertIntersection("GEOMETRYCOLLECTION (POINT (1 2))", "POINT EMPTY", "GEOMETRYCOLLECTION EMPTY"); + assertIntersection("GEOMETRYCOLLECTION (POINT (1 2), MULTIPOINT (3 4, 5 6))", "POINT (3 4)", + "POINT (3 4)"); + assertIntersection("GEOMETRYCOLLECTION (POINT (1 2), MULTIPOINT (3 4, 5 6))", "POINT (30 40)", + "GEOMETRYCOLLECTION EMPTY"); + assertIntersection("POINT (30 40)", "GEOMETRYCOLLECTION (POINT (1 2), MULTIPOINT (3 4, 5 6))", + "GEOMETRYCOLLECTION EMPTY"); + } + private void assertIntersection(String wkt, String otherWkt, String expectedWkt) { + OGCGeometry geometry = OGCGeometry.fromText(wkt); + OGCGeometry otherGeometry = OGCGeometry.fromText(otherWkt); + OGCGeometry result = geometry.intersection(otherGeometry); + Assert.assertEquals(expectedWkt, result.asText()); + } } From 104c2ef50e834f48d270133464945963137dfc58 Mon Sep 17 00:00:00 2001 From: Sergey Tolstov Date: Thu, 7 Jun 2018 08:18:41 -0700 Subject: [PATCH 166/196] Added a test from @mbasmanova --- .../esri/core/geometry/TestOGCContains.java | 74 +++++++++++++++++++ 1 file changed, 74 insertions(+) create mode 100644 src/test/java/com/esri/core/geometry/TestOGCContains.java diff --git a/src/test/java/com/esri/core/geometry/TestOGCContains.java b/src/test/java/com/esri/core/geometry/TestOGCContains.java new file mode 100644 index 00000000..fd2c5116 --- /dev/null +++ b/src/test/java/com/esri/core/geometry/TestOGCContains.java @@ -0,0 +1,74 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.esri.core.geometry; + +import com.esri.core.geometry.ogc.OGCGeometry; +import org.junit.Test; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +public class TestOGCContains { + @Test + public void testPoint() { + // point + assertContains("POINT (1 2)", "POINT (1 2)"); + assertContains("POINT (1 2)", "GEOMETRYCOLLECTION (POINT (1 2))"); + assertNotContains("POINT (1 2)", "POINT EMPTY"); + assertNotContains("POINT (1 2)", "POINT (3 4)"); + + // multi-point + assertContains("MULTIPOINT (1 2, 3 4)", "MULTIPOINT (1 2, 3 4)"); + assertContains("MULTIPOINT (1 2, 3 4)", "MULTIPOINT (1 2)"); + assertContains("MULTIPOINT (1 2, 3 4)", "POINT (3 4)"); + assertContains("MULTIPOINT (1 2, 3 4)", "GEOMETRYCOLLECTION (MULTIPOINT (1 2), POINT (3 4))"); + assertContains("MULTIPOINT (1 2, 3 4)", "GEOMETRYCOLLECTION (POINT (1 2))"); + assertNotContains("MULTIPOINT (1 2, 3 4)", "MULTIPOINT EMPTY"); + } + + @Test + public void testLineString() { + // TODO Add more tests + assertContains("LINESTRING (0 1, 5 1)", "POINT (2 1)"); + } + + @Test + public void testPolygon() { + // TODO Fill in + } + + @Test + public void testGeometryCollection() { + // TODO Add more tests + assertContains("GEOMETRYCOLLECTION (POINT (0 0), LINESTRING (0 1, 5 1))", + "GEOMETRYCOLLECTION (MULTIPOINT (0 0, 2 1))"); + } + + private void assertContains(String wkt, String otherWkt) { + OGCGeometry geometry = OGCGeometry.fromText(wkt); + OGCGeometry otherGeometry = OGCGeometry.fromText(otherWkt); + + assertTrue(geometry.contains(otherGeometry)); + assertTrue(otherGeometry.within(geometry)); + assertFalse(geometry.disjoint(otherGeometry)); + } + + private void assertNotContains(String wkt, String otherWkt) { + OGCGeometry geometry = OGCGeometry.fromText(wkt); + OGCGeometry otherGeometry = OGCGeometry.fromText(otherWkt); + assertFalse(geometry.contains(otherGeometry)); + assertFalse(otherGeometry.within(geometry)); + } +} + From 7030d57367fa2587cfcdc6786c42ee49cf1d5a2d Mon Sep 17 00:00:00 2001 From: Masha Basmanova Date: Thu, 7 Jun 2018 22:40:24 -0400 Subject: [PATCH 167/196] Fix OGCCollection#reduceFromMulti and add test --- .../core/geometry/ogc/OGCMultiPolygon.java | 2 +- .../core/geometry/TestOGCReduceFromMulti.java | 80 +++++++++++++++++++ 2 files changed, 81 insertions(+), 1 deletion(-) create mode 100644 src/test/java/com/esri/core/geometry/TestOGCReduceFromMulti.java diff --git a/src/main/java/com/esri/core/geometry/ogc/OGCMultiPolygon.java b/src/main/java/com/esri/core/geometry/ogc/OGCMultiPolygon.java index d2e64956..52afbe86 100644 --- a/src/main/java/com/esri/core/geometry/ogc/OGCMultiPolygon.java +++ b/src/main/java/com/esri/core/geometry/ogc/OGCMultiPolygon.java @@ -139,7 +139,7 @@ public OGCGeometry convertToMulti() public OGCGeometry reduceFromMulti() { int n = numGeometries(); if (n == 0) { - return new OGCLineString(new Polygon(polygon.getDescription()), 0, esriSR); + return new OGCPolygon(new Polygon(polygon.getDescription()), 0, esriSR); } if (n == 1) { diff --git a/src/test/java/com/esri/core/geometry/TestOGCReduceFromMulti.java b/src/test/java/com/esri/core/geometry/TestOGCReduceFromMulti.java new file mode 100644 index 00000000..f47c817c --- /dev/null +++ b/src/test/java/com/esri/core/geometry/TestOGCReduceFromMulti.java @@ -0,0 +1,80 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.esri.core.geometry; + +import org.junit.Test; + +import static com.esri.core.geometry.ogc.OGCGeometry.fromText; +import static java.lang.String.format; +import static org.junit.Assert.assertEquals; + +public class TestOGCReduceFromMulti +{ + @Test + public void testPoint() + { + assertReduceFromMulti("POINT (1 2)", "POINT (1 2)"); + assertReduceFromMulti("POINT EMPTY", "POINT EMPTY"); + assertReduceFromMulti("MULTIPOINT (1 2)", "POINT (1 2)"); + assertReduceFromMulti("MULTIPOINT (1 2, 3 4)", "MULTIPOINT ((1 2), (3 4))"); + assertReduceFromMulti("MULTIPOINT EMPTY", "POINT EMPTY"); + } + + @Test + public void testLineString() + { + assertReduceFromMulti("LINESTRING (0 0, 1 1, 2 3)", "LINESTRING (0 0, 1 1, 2 3)"); + assertReduceFromMulti("LINESTRING EMPTY", "LINESTRING EMPTY"); + assertReduceFromMulti("MULTILINESTRING ((0 0, 1 1, 2 3))", "LINESTRING (0 0, 1 1, 2 3)"); + assertReduceFromMulti("MULTILINESTRING ((0 0, 1 1, 2 3), (4 4, 5 5))", "MULTILINESTRING ((0 0, 1 1, 2 3), (4 4, 5 5))"); + assertReduceFromMulti("MULTILINESTRING EMPTY", "LINESTRING EMPTY"); + } + + @Test + public void testPolygon() + { + assertReduceFromMulti("POLYGON ((0 0, 1 0, 1 1, 0 0))", "POLYGON ((0 0, 1 0, 1 1, 0 0))"); + assertReduceFromMulti("POLYGON EMPTY", "POLYGON EMPTY"); + assertReduceFromMulti("MULTIPOLYGON (((0 0, 1 0, 1 1, 0 0)))", "POLYGON ((0 0, 1 0, 1 1, 0 0))"); + assertReduceFromMulti("MULTIPOLYGON (((0 0, 1 0, 1 1, 0 0)), ((2 2, 3 2, 3 3, 2 2)))", "MULTIPOLYGON (((0 0, 1 0, 1 1, 0 0)), ((2 2, 3 2, 3 3, 2 2)))"); + assertReduceFromMulti("MULTIPOLYGON EMPTY", "POLYGON EMPTY"); + } + + @Test + public void testGeometryCollection() + { + assertReduceFromMulti(gc("POINT (1 2)"), "POINT (1 2)"); + assertReduceFromMulti(gc("MULTIPOINT (1 2)"), "POINT (1 2)"); + assertReduceFromMulti(gc(gc("POINT (1 2)")), "POINT (1 2)"); + assertReduceFromMulti(gc("POINT EMPTY"), "POINT EMPTY"); + + assertReduceFromMulti(gc("LINESTRING (0 0, 1 1, 2 3)"), "LINESTRING (0 0, 1 1, 2 3)"); + assertReduceFromMulti(gc("POLYGON ((0 0, 1 0, 1 1, 0 0))"), "POLYGON ((0 0, 1 0, 1 1, 0 0))"); + + assertReduceFromMulti(gc("POINT (1 2), LINESTRING (0 0, 1 1, 2 3)"), gc("POINT (1 2), LINESTRING (0 0, 1 1, 2 3)")); + + assertReduceFromMulti("GEOMETRYCOLLECTION EMPTY", "GEOMETRYCOLLECTION EMPTY"); + assertReduceFromMulti(gc("GEOMETRYCOLLECTION EMPTY"), "GEOMETRYCOLLECTION EMPTY"); + } + + private void assertReduceFromMulti(String wkt, String reducedWkt) + { + assertEquals(reducedWkt, fromText(wkt).reduceFromMulti().asText()); + } + + private String gc(String wkts) + { + return format("GEOMETRYCOLLECTION (%s)", wkts); + } +} From 40b324e8d01eec1d98eeac872fcca992bcb24c1a Mon Sep 17 00:00:00 2001 From: Masha Basmanova Date: Fri, 8 Jun 2018 00:30:02 -0400 Subject: [PATCH 168/196] Add tests for distance and flatten --- .../ogc/OGCConcreteGeometryCollection.java | 2 + .../esri/core/geometry/TestOGCDistance.java | 59 +++++++++++++++++++ .../TestOGCGeometryCollectionFlatten.java | 48 +++++++++++++++ 3 files changed, 109 insertions(+) create mode 100644 src/test/java/com/esri/core/geometry/TestOGCDistance.java create mode 100644 src/test/java/com/esri/core/geometry/TestOGCGeometryCollectionFlatten.java diff --git a/src/main/java/com/esri/core/geometry/ogc/OGCConcreteGeometryCollection.java b/src/main/java/com/esri/core/geometry/ogc/OGCConcreteGeometryCollection.java index 3868a469..660f70ed 100644 --- a/src/main/java/com/esri/core/geometry/ogc/OGCConcreteGeometryCollection.java +++ b/src/main/java/com/esri/core/geometry/ogc/OGCConcreteGeometryCollection.java @@ -546,9 +546,11 @@ public double distance(OGCGeometry another) { double minD = Double.NaN; for (int i = 0, n = numGeometries(); i < n; ++i) { + // TODO Skip expensive distance computation if bounding boxes are further away than minD double d = geometryN(i).distance(another); if (d < minD || Double.isNaN(minD)) { minD = d; + // TODO Replace zero with tolerance defined by the spatial reference if (minD == 0) { break; } diff --git a/src/test/java/com/esri/core/geometry/TestOGCDistance.java b/src/test/java/com/esri/core/geometry/TestOGCDistance.java new file mode 100644 index 00000000..dea491f9 --- /dev/null +++ b/src/test/java/com/esri/core/geometry/TestOGCDistance.java @@ -0,0 +1,59 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.esri.core.geometry; + +import org.junit.Test; + +import static com.esri.core.geometry.ogc.OGCGeometry.fromText; +import static java.lang.String.format; +import static org.junit.Assert.assertEquals; + +public class TestOGCDistance +{ + @Test + public void testPoint() + { + assertDistance("POINT (1 2)", "POINT (2 2)", 1); + assertDistance("POINT (1 2)", "POINT (1 2)", 0); + assertNanDistance("POINT (1 2)", "POINT EMPTY"); + + assertDistance(gc("POINT (1 2)"), "POINT (2 2)", 1); + assertDistance(gc("POINT (1 2)"), "POINT (1 2)", 0); + assertNanDistance(gc("POINT (1 2)"), "POINT EMPTY"); + assertDistance(gc("POINT (1 2)"), gc("POINT (2 2)"), 1); + + assertDistance("MULTIPOINT (1 0, 2 0, 3 0)", "POINT (2 1)", 1); + assertDistance(gc("MULTIPOINT (1 0, 2 0, 3 0)"), "POINT (2 1)", 1); + assertDistance(gc("POINT (1 0), POINT (2 0), POINT (3 0))"), "POINT (2 1)", 1); + + assertDistance(gc("POINT (1 0), POINT EMPTY"), "POINT (2 0)", 1); + } + + private void assertDistance(String wkt, String otherWkt, double distance) + { + assertEquals(distance, fromText(wkt).distance(fromText(otherWkt)), 0.000001); + assertEquals(distance, fromText(otherWkt).distance(fromText(wkt)), 0.000001); + } + + private void assertNanDistance(String wkt, String otherWkt) + { + assertEquals(Double.NaN, fromText(wkt).distance(fromText(otherWkt)), 0.000001); + assertEquals(Double.NaN, fromText(otherWkt).distance(fromText(wkt)), 0.000001); + } + + private String gc(String wkts) + { + return format("GEOMETRYCOLLECTION (%s)", wkts); + } +} diff --git a/src/test/java/com/esri/core/geometry/TestOGCGeometryCollectionFlatten.java b/src/test/java/com/esri/core/geometry/TestOGCGeometryCollectionFlatten.java new file mode 100644 index 00000000..50832f2d --- /dev/null +++ b/src/test/java/com/esri/core/geometry/TestOGCGeometryCollectionFlatten.java @@ -0,0 +1,48 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.esri.core.geometry; + +import com.esri.core.geometry.ogc.OGCConcreteGeometryCollection; +import com.esri.core.geometry.ogc.OGCGeometry; +import org.junit.Test; + +import static java.lang.String.format; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +public class TestOGCGeometryCollectionFlatten +{ + @Test + public void test() + { + assertFlatten("GEOMETRYCOLLECTION EMPTY", "GEOMETRYCOLLECTION EMPTY"); + assertFlatten(gc("POINT (1 2)"), gc("MULTIPOINT ((1 2))")); + assertFlatten(gc("POINT (1 2), POINT EMPTY"), gc("MULTIPOINT ((1 2))")); + assertFlatten(gc("POINT (1 2), MULTIPOINT (3 4, 5 6)"), gc("MULTIPOINT ((1 2), (3 4), (5 6))")); + assertFlatten(gc("POINT (1 2), POINT (3 4), " + gc("POINT (5 6)")), gc("MULTIPOINT ((1 2), (3 4), (5 6))")); + } + + private void assertFlatten(String wkt, String flattenedWkt) + { + OGCConcreteGeometryCollection collection = (OGCConcreteGeometryCollection) OGCGeometry.fromText(wkt); + assertEquals(flattenedWkt, collection.flatten().asText()); + assertTrue(collection.flatten().isFlattened()); + assertEquals(flattenedWkt, collection.flatten().flatten().asText()); + } + + private String gc(String wkts) + { + return format("GEOMETRYCOLLECTION (%s)", wkts); + } +} From 8c20f1d25f206d07bff65c5664f5ab702e30293c Mon Sep 17 00:00:00 2001 From: Masha Basmanova Date: Fri, 8 Jun 2018 00:30:26 -0400 Subject: [PATCH 169/196] Simplify intersection and difference --- .../ogc/OGCConcreteGeometryCollection.java | 183 +++++++----------- .../esri/core/geometry/TestOGCDisjoint.java | 126 ++++++++++++ .../TestOGCGeometryCollectionFlatten.java | 4 +- 3 files changed, 203 insertions(+), 110 deletions(-) create mode 100644 src/test/java/com/esri/core/geometry/TestOGCDisjoint.java diff --git a/src/main/java/com/esri/core/geometry/ogc/OGCConcreteGeometryCollection.java b/src/main/java/com/esri/core/geometry/ogc/OGCConcreteGeometryCollection.java index 660f70ed..155b8c41 100644 --- a/src/main/java/com/esri/core/geometry/ogc/OGCConcreteGeometryCollection.java +++ b/src/main/java/com/esri/core/geometry/ogc/OGCConcreteGeometryCollection.java @@ -25,6 +25,7 @@ package com.esri.core.geometry.ogc; import com.esri.core.geometry.Envelope; +import com.esri.core.geometry.GeoJsonExportFlags; import com.esri.core.geometry.Geometry; import com.esri.core.geometry.GeometryCursor; import com.esri.core.geometry.GeometryException; @@ -35,15 +36,15 @@ import com.esri.core.geometry.OGCStructureInternal; import com.esri.core.geometry.OperatorConvexHull; import com.esri.core.geometry.OperatorDifference; +import com.esri.core.geometry.OperatorExportToGeoJson; +import com.esri.core.geometry.OperatorIntersection; +import com.esri.core.geometry.OperatorUnion; +import com.esri.core.geometry.Point; import com.esri.core.geometry.Polygon; import com.esri.core.geometry.Polyline; import com.esri.core.geometry.SimpleGeometryCursor; import com.esri.core.geometry.SpatialReference; import com.esri.core.geometry.VertexDescription; -import com.esri.core.geometry.GeoJsonExportFlags; -import com.esri.core.geometry.OperatorExportToGeoJson; -import com.esri.core.geometry.OperatorUnion; -import com.esri.core.geometry.Point; import java.nio.ByteBuffer; import java.nio.ByteOrder; @@ -648,6 +649,8 @@ public boolean Equals(OGCGeometry another) { OGCConcreteGeometryCollection gc1 = (OGCConcreteGeometryCollection)g1; OGCConcreteGeometryCollection gc2 = (OGCConcreteGeometryCollection)g2; + // TODO Assuming input geometries are simple and valid, remove-overlaps would be a no-op. + // Hence, calling flatten() should be sufficient. gc1 = gc1.flattenAndRemoveOverlaps(); gc2 = gc2.flattenAndRemoveOverlaps(); int n = gc1.numGeometries(); @@ -655,52 +658,66 @@ public boolean Equals(OGCGeometry another) { return false; } - boolean res = false; for (int i = 0; i < n; ++i) { if (!gc1.geometryN(i).Equals(gc2.geometryN(i))) { return false; } - - res = true; } - return res; + return n > 0; } - + + private static OGCConcreteGeometryCollection toGeometryCollection(OGCGeometry geometry) + { + if (geometry.geometryType() != OGCConcreteGeometryCollection.TYPE) { + return new OGCConcreteGeometryCollection(geometry, geometry.getEsriSpatialReference()); + } + + return (OGCConcreteGeometryCollection) geometry; + } + + private static List toList(GeometryCursor cursor) + { + List geometries = new ArrayList(); + for (Geometry geometry = cursor.next(); geometry != null; geometry = cursor.next()) { + geometries.add(geometry); + } + return geometries; + } + //Topological @Override public OGCGeometry difference(OGCGeometry another) { - List list = wrapGeomsIntoList_(this, another); - list = prepare_for_ops_(list); - if (list.size() != 2) // this should not happen - throw new GeometryException("internal error"); - + if (isEmpty() || another.isEmpty()) { + return this; + } + + List geometries = toList(prepare_for_ops_(toGeometryCollection(this))); + List otherGeometries = toList(prepare_for_ops_(toGeometryCollection(another))); + List result = new ArrayList(); - OGCConcreteGeometryCollection coll1 = list.get(0); - OGCConcreteGeometryCollection coll2 = list.get(1); - for (int i = 0, n = coll1.numGeometries(); i < n; ++i) { - OGCGeometry cur = coll1.geometryN(i); - for (int j = 0, n2 = coll2.numGeometries(); j < n2; ++j) { - OGCGeometry geom2 = coll2.geometryN(j); - if (cur.dimension() > geom2.dimension()) + for (Geometry geometry : geometries) { + for (Geometry otherGeometry : otherGeometries) { + if (geometry.getDimension() > otherGeometry.getDimension()) { continue; //subtracting lower dimension has no effect. - - cur = cur.difference(geom2); - if (cur.isEmpty()) + } + + geometry = OperatorDifference.local().execute(geometry, otherGeometry, esriSR, null); + if (geometry.isEmpty()) { break; + } + } + + if (!geometry.isEmpty()) { + result.add(OGCGeometry.createFromEsriGeometry(geometry, esriSR)); } - - if (cur.isEmpty()) - continue; - - result.add(cur); } - + if (result.size() == 1) { return result.get(0).reduceFromMulti(); } - return new OGCConcreteGeometryCollection(result, esriSR); + return new OGCConcreteGeometryCollection(result, esriSR).flattenAndRemoveOverlaps(); } @Override @@ -708,25 +725,18 @@ public OGCGeometry intersection(OGCGeometry another) { if (isEmpty() || another.isEmpty()) { return new OGCConcreteGeometryCollection(esriSR); } - - List list = wrapGeomsIntoList_(this, another); - list = prepare_for_ops_(list); - if (list.size() != 2) // this should not happen - throw new GeometryException("internal error"); - + + List geometries = toList(prepare_for_ops_(toGeometryCollection(this))); + List otherGeometries = toList(prepare_for_ops_(toGeometryCollection(another))); + List result = new ArrayList(); - OGCConcreteGeometryCollection coll1 = list.get(0); - OGCConcreteGeometryCollection coll2 = list.get(1); - for (int i = 0, n = coll1.numGeometries(); i < n; ++i) { - OGCGeometry cur = coll1.geometryN(i); - for (int j = 0, n2 = coll2.numGeometries(); j < n2; ++j) { - OGCGeometry geom2 = coll2.geometryN(j); - - OGCGeometry partialIntersection = cur.intersection(geom2); - if (partialIntersection.isEmpty()) - continue; - - result.add(partialIntersection); + for (Geometry geometry : geometries) { + for (Geometry otherGeometry : otherGeometries) { + GeometryCursor intersectionCursor = OperatorIntersection.local().execute(new SimpleGeometryCursor(geometry), new SimpleGeometryCursor(otherGeometry), esriSR, null, 7); + OGCGeometry intersection = OGCGeometry.createFromEsriCursor(intersectionCursor, esriSR, true); + if (!intersection.isEmpty()) { + result.add(intersection); + } } } @@ -734,7 +744,7 @@ public OGCGeometry intersection(OGCGeometry another) { return result.get(0).reduceFromMulti(); } - return (new OGCConcreteGeometryCollection(result, esriSR)).flattenAndRemoveOverlaps(); + return new OGCConcreteGeometryCollection(result, esriSR).flattenAndRemoveOverlaps(); } @Override @@ -743,21 +753,6 @@ public OGCGeometry symDifference(OGCGeometry another) { throw new UnsupportedOperationException(); } - //make a list of collections out of two geometries - private static List wrapGeomsIntoList_(OGCGeometry g1, OGCGeometry g2) { - List list = new ArrayList(); - if (g1.geometryType() != OGCConcreteGeometryCollection.TYPE) { - g1 = new OGCConcreteGeometryCollection(g1, g1.getEsriSpatialReference()); - } - if (g2.geometryType() != OGCConcreteGeometryCollection.TYPE) { - g2 = new OGCConcreteGeometryCollection(g2, g2.getEsriSpatialReference()); - } - - list.add((OGCConcreteGeometryCollection)g1); - list.add((OGCConcreteGeometryCollection)g2); - - return list; - } /** * Checks if collection is flattened. * @return True for the flattened collection. A flattened collection contains up to three non-empty geometries: @@ -869,24 +864,23 @@ public OGCConcreteGeometryCollection flatten() { /** * Fixes topological overlaps in the GeometryCollecion. * This is equivalent to union of the geometry collection elements. - * + * + * TODO "flattened" collection is supposed to contain only mutli-geometries, but this method may return single geometries + * e.g. for GEOMETRYCOLLECTION (LINESTRING (...)) it returns GEOMETRYCOLLECTION (LINESTRING (...)) + * and not GEOMETRYCOLLECTION (MULTILINESTRING (...)) * @return A geometry collection that is flattened and has no overlapping elements. */ public OGCConcreteGeometryCollection flattenAndRemoveOverlaps() { - ArrayList geoms = new ArrayList(); - + //flatten and crack/cluster GeometryCursor cursor = OGCStructureInternal.prepare_for_ops_(flatten().getEsriGeometryCursor(), esriSR); - for (Geometry g = cursor.next(); g != null; g = cursor.next()) { - geoms.add(g); - } - + //make sure geometries don't overlap - return removeOverlapsHelper_(geoms); + return new OGCConcreteGeometryCollection(removeOverlapsHelper_(toList(cursor)), esriSR); } - - private OGCConcreteGeometryCollection removeOverlapsHelper_(List geoms) { - ArrayList result = new ArrayList(); + + private GeometryCursor removeOverlapsHelper_(List geoms) { + List result = new ArrayList(); for (int i = 0; i < geoms.size(); ++i) { Geometry current = geoms.get(i); if (current.isEmpty()) @@ -905,7 +899,7 @@ private OGCConcreteGeometryCollection removeOverlapsHelper_(List geoms result.add(current); } - return new OGCConcreteGeometryCollection(new SimpleGeometryCursor(result), esriSR); + return new SimpleGeometryCursor(result); } private static class FlatteningCollectionCursor extends GeometryCursor { @@ -956,36 +950,9 @@ public int getGeometryID() { //Collectively processes group of geometry collections (intersects all segments and clusters points). //Flattens collections, removes overlaps. //Once done, the result collections would work well for topological and relational operations. - private List prepare_for_ops_(List geoms) { - assert(geoms != null && !geoms.isEmpty()); - GeometryCursor prepared = OGCStructureInternal.prepare_for_ops_(new FlatteningCollectionCursor(geoms), esriSR); - - List result = new ArrayList(); - int prevCollectionIndex = -1; - List list = null; - for (Geometry g = prepared.next(); g != null; g = prepared.next()) { - int c = prepared.getGeometryID(); - if (c != prevCollectionIndex) { - //add empty collections for all skipped indices - for (int i = prevCollectionIndex; i < c - 1; i++) { - result.add(new OGCConcreteGeometryCollection(esriSR)); - } - - if (list != null) { - result.add(removeOverlapsHelper_(list)); - } - - list = new ArrayList(); - prevCollectionIndex = c; - } - - list.add(g); - } - - if (list != null) { - result.add(removeOverlapsHelper_(list)); - } - - return result; + private GeometryCursor prepare_for_ops_(OGCConcreteGeometryCollection collection) { + assert(collection != null && !collection.isEmpty()); + GeometryCursor prepared = OGCStructureInternal.prepare_for_ops_(collection.flatten().getEsriGeometryCursor(), esriSR); + return removeOverlapsHelper_(toList(prepared)); } } diff --git a/src/test/java/com/esri/core/geometry/TestOGCDisjoint.java b/src/test/java/com/esri/core/geometry/TestOGCDisjoint.java new file mode 100644 index 00000000..45090e83 --- /dev/null +++ b/src/test/java/com/esri/core/geometry/TestOGCDisjoint.java @@ -0,0 +1,126 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.esri.core.geometry; + +import com.esri.core.geometry.ogc.OGCGeometry; +import org.junit.Test; + +import static java.lang.String.format; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +public class TestOGCDisjoint +{ + @Test + public void testPoint() + { + // point + assertDisjoint("POINT (1 2)", "POINT (3 4)"); + assertDisjoint("POINT (1 2)", "POINT EMPTY"); + assertNotDisjoint("POINT (1 2)", "POINT (1 2)", "POINT (1 2)"); + + // multi-point + assertDisjoint("POINT (1 2)", "MULTIPOINT (3 4, 5 6)"); + assertDisjoint("POINT (1 2)", "MULTIPOINT EMPTY"); + assertNotDisjoint("POINT (1 2)", "MULTIPOINT (1 2, 3 4, 5 6)", "POINT (1 2)"); + assertNotDisjoint("POINT (1 2)", "MULTIPOINT (1 2)", "POINT (1 2)"); + } + + @Test + public void testLinestring() + { + // TODO Fill in + } + + @Test + public void testPolygon() + { + // TODO Fill in + } + + @Test + public void testGeometryCollection() + { + assertDisjoint("GEOMETRYCOLLECTION (POINT (1 2))", "POINT (3 4)"); + // GeometryException: internal error + assertDisjoint("GEOMETRYCOLLECTION (POINT (1 2))", "POINT EMPTY"); + assertNotDisjoint("GEOMETRYCOLLECTION (POINT (1 2))", "POINT (1 2)", "POINT (1 2)"); + + assertDisjoint("GEOMETRYCOLLECTION (POINT (1 2), MULTIPOINT (3 4, 5 6))", "POINT (0 0)"); + assertNotDisjoint("GEOMETRYCOLLECTION (POINT (1 2), MULTIPOINT (3 4, 5 6))", "POINT (3 4)", "POINT (3 4)"); + + String wkt = "GEOMETRYCOLLECTION (POINT (1 2), LINESTRING (0 0, 5 0), POLYGON ((2 2, 3 2, 3 3, 2 2)))"; + assertDisjoint(wkt, gc("POINT (0 2)")); + + assertNotDisjoint(wkt, gc("POINT (1 2)"), "POINT (1 2)"); + // point within the line + assertNotDisjoint(wkt, gc("POINT (0 0)"), "POINT (0 0)"); + assertNotDisjoint(wkt, gc("POINT (1 0)"), "POINT (1 0)"); + // point within the polygon + assertNotDisjoint(wkt, gc("POINT (2 2)"), "POINT (2 2)"); + assertNotDisjoint(wkt, gc("POINT (2.5 2)"), "POINT (2.5 2)"); + assertNotDisjoint(wkt, gc("POINT (2.5 2.1)"), "POINT (2.5 2.1)"); + + assertDisjoint(wkt, gc("LINESTRING (0 2, 1 3)")); + + // line intersects the point + assertNotDisjoint(wkt, gc("LINESTRING (0 1, 2 3)"), "POINT (1 2)"); + // line intersects the line + assertNotDisjoint(wkt, gc("LINESTRING (0 0, 1 0)"), "LINESTRING (0 0, 1 0)"); + assertNotDisjoint(wkt, gc("LINESTRING (5 -1, 5 1)"), "POINT (5 0)"); + // line intersects the polygon + assertNotDisjoint(wkt, gc("LINESTRING (0 0, 5 5)"), gc("POINT (0 0), LINESTRING (2 2, 3 3)")); + assertNotDisjoint(wkt, gc("LINESTRING (0 2.5, 2.6 2.5)"), "LINESTRING (2.5 2.5, 2.6 2.5)"); + + assertDisjoint(wkt, gc("POLYGON ((5 5, 6 5, 6 6, 5 5))")); + assertDisjoint(wkt, gc("POLYGON ((-1 -1, 10 -1, 10 10, -1 10, -1 -1), (-0.1 -0.1, 5.1 -0.1, 5.1 5.1, -0.1 5.1, -0.1 -0.1))")); + + assertNotDisjoint(wkt, gc("POLYGON ((-1 -1, 10 -1, 10 10, -1 10, -1 -1))"), gc("POINT (1 2), LINESTRING (0 0, 5 0), POLYGON ((2 2, 3 2, 3 3, 2 2))")); + assertNotDisjoint(wkt, gc("POLYGON ((2 -1, 4 -1, 4 1, 2 1, 2 -1))"), "LINESTRING (2 0, 4 0)"); + assertNotDisjoint(wkt, gc("POLYGON ((0 1, 1.5 1, 1.5 2.5, 0 2.5, 0 1))"), "POINT (1 2)"); + assertNotDisjoint(wkt, gc("POLYGON ((5 0, 6 0, 6 5, 5 0))"), "POINT (5 0)"); + } + + private String gc(String wkts) + { + return format("GEOMETRYCOLLECTION (%s)", wkts); + } + + private void assertDisjoint(String wkt, String otherWkt) + { + OGCGeometry geometry = OGCGeometry.fromText(wkt); + OGCGeometry otherGeometry = OGCGeometry.fromText(otherWkt); + assertTrue(geometry.disjoint(otherGeometry)); + assertFalse(geometry.intersects(otherGeometry)); + assertTrue(geometry.intersection(otherGeometry).isEmpty()); + + assertTrue(otherGeometry.disjoint(geometry)); + assertFalse(otherGeometry.intersects(geometry)); + assertTrue(otherGeometry.intersection(geometry).isEmpty()); + } + + private void assertNotDisjoint(String wkt, String otherWkt, String intersectionWkt) + { + OGCGeometry geometry = OGCGeometry.fromText(wkt); + OGCGeometry otherGeometry = OGCGeometry.fromText(otherWkt); + assertFalse(geometry.disjoint(otherGeometry)); + assertTrue(geometry.intersects(otherGeometry)); + assertEquals(intersectionWkt, geometry.intersection(otherGeometry).asText()); + + assertFalse(otherGeometry.disjoint(geometry)); + assertTrue(otherGeometry.intersects(geometry)); + assertEquals(intersectionWkt, otherGeometry.intersection(geometry).asText()); + } +} diff --git a/src/test/java/com/esri/core/geometry/TestOGCGeometryCollectionFlatten.java b/src/test/java/com/esri/core/geometry/TestOGCGeometryCollectionFlatten.java index 50832f2d..8a98d5d1 100644 --- a/src/test/java/com/esri/core/geometry/TestOGCGeometryCollectionFlatten.java +++ b/src/test/java/com/esri/core/geometry/TestOGCGeometryCollectionFlatten.java @@ -14,9 +14,9 @@ package com.esri.core.geometry; import com.esri.core.geometry.ogc.OGCConcreteGeometryCollection; -import com.esri.core.geometry.ogc.OGCGeometry; import org.junit.Test; +import static com.esri.core.geometry.ogc.OGCGeometry.fromText; import static java.lang.String.format; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; @@ -35,7 +35,7 @@ public void test() private void assertFlatten(String wkt, String flattenedWkt) { - OGCConcreteGeometryCollection collection = (OGCConcreteGeometryCollection) OGCGeometry.fromText(wkt); + OGCConcreteGeometryCollection collection = (OGCConcreteGeometryCollection) fromText(wkt); assertEquals(flattenedWkt, collection.flatten().asText()); assertTrue(collection.flatten().isFlattened()); assertEquals(flattenedWkt, collection.flatten().flatten().asText()); From 7b2bc6af4f50fea99ce9ccea252b1547f5a3cf78 Mon Sep 17 00:00:00 2001 From: Masha Basmanova Date: Fri, 8 Jun 2018 01:10:08 -0400 Subject: [PATCH 170/196] Block overlaps and symDifference for geometry collections --- .../java/com/esri/core/geometry/ogc/OGCGeometry.java | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/esri/core/geometry/ogc/OGCGeometry.java b/src/main/java/com/esri/core/geometry/ogc/OGCGeometry.java index 87e8c620..17ef2f8f 100644 --- a/src/main/java/com/esri/core/geometry/ogc/OGCGeometry.java +++ b/src/main/java/com/esri/core/geometry/ogc/OGCGeometry.java @@ -329,7 +329,8 @@ public boolean contains(OGCGeometry another) { public boolean overlaps(OGCGeometry another) { if (another.geometryType() == OGCConcreteGeometryCollection.TYPE) { - return another.overlaps(this); //overlaps should be symmetric + // TODO + throw new UnsupportedOperationException(); } com.esri.core.geometry.Geometry geom1 = getEsriGeometry(); @@ -530,9 +531,11 @@ public OGCGeometry difference(OGCGeometry another) { } public OGCGeometry symDifference(OGCGeometry another) { - if (another.geometryType() == OGCConcreteGeometryCollection.TYPE) - return another.symDifference(this); - + if (another.geometryType() == OGCConcreteGeometryCollection.TYPE) { + // TODO + throw new UnsupportedOperationException(); + } + com.esri.core.geometry.Geometry geom1 = getEsriGeometry(); com.esri.core.geometry.Geometry geom2 = another.getEsriGeometry(); return createFromEsriGeometry( From 6fcc5e68237dc2cca710ee594d8ab8e9c69fa1a3 Mon Sep 17 00:00:00 2001 From: Randall Whitman Date: Tue, 26 Jun 2018 16:36:29 -0700 Subject: [PATCH 171/196] update jackson to 2.9.6 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 0255ec18..3035ef99 100755 --- a/pom.xml +++ b/pom.xml @@ -98,7 +98,7 @@ 1.6 - 2.9.4 + 2.9.6 4.12 0.9 From ac760efd95d14d5fb8550f1970c8a886df66b171 Mon Sep 17 00:00:00 2001 From: Randall Whitman Date: Wed, 27 Jun 2018 08:19:56 -0700 Subject: [PATCH 172/196] jackson-2.9.6 --- build.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.xml b/build.xml index a4bc51b1..a2e894a4 100644 --- a/build.xml +++ b/build.xml @@ -14,7 +14,7 @@ - + From c74e7a8fb604bf1831e0982f71e5ee67e692354a Mon Sep 17 00:00:00 2001 From: Randall Whitman Date: Fri, 29 Jun 2018 09:01:15 -0700 Subject: [PATCH 173/196] Geometry release v2.2.0 --- README.md | 2 +- pom.xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 46b42a05..a4b5e0ae 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,7 @@ The project is also available as a [Maven](http://maven.apache.org/) dependency: com.esri.geometry esri-geometry-api - 2.1.0 + 2.2.0 ``` diff --git a/pom.xml b/pom.xml index 3035ef99..e4c2b890 100755 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ com.esri.geometry esri-geometry-api - 2.2.0-SNAPSHOT + 2.2.0 jar Esri Geometry API for Java From e7fb7cae3468c14d33dad528f7dfe6b08d4048e2 Mon Sep 17 00:00:00 2001 From: Sergey Tolstov Date: Fri, 6 Jul 2018 10:56:30 -0700 Subject: [PATCH 174/196] Add serialization for QuadTree and related classes --- .../core/geometry/AttributeStreamOfInt32.java | 58 ++++++++++- .../java/com/esri/core/geometry/QuadTree.java | 8 +- .../com/esri/core/geometry/QuadTreeImpl.java | 78 +++++++++++++-- .../geometry/StridedIndexTypeCollection.java | 8 +- .../esri/core/geometry/TestSerialization.java | 96 ++++++++++++++++++- 5 files changed, 232 insertions(+), 16 deletions(-) diff --git a/src/main/java/com/esri/core/geometry/AttributeStreamOfInt32.java b/src/main/java/com/esri/core/geometry/AttributeStreamOfInt32.java index 1939bb0f..52d07517 100644 --- a/src/main/java/com/esri/core/geometry/AttributeStreamOfInt32.java +++ b/src/main/java/com/esri/core/geometry/AttributeStreamOfInt32.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2017 Esri + Copyright 1995-2018 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -27,14 +27,20 @@ import com.esri.core.geometry.VertexDescription.Persistence; +import java.io.IOException; +import java.io.ObjectStreamException; +import java.io.Serializable; import java.nio.ByteBuffer; +import java.nio.IntBuffer; import java.util.Arrays; import static com.esri.core.geometry.SizeOf.SIZE_OF_ATTRIBUTE_STREAM_OF_INT32; import static com.esri.core.geometry.SizeOf.sizeOfIntArray; -final class AttributeStreamOfInt32 extends AttributeStreamBase { - private int[] m_buffer = null; +final class AttributeStreamOfInt32 extends AttributeStreamBase implements Serializable { + private static final long serialVersionUID = 1L; + + transient private int[] m_buffer = null; private int m_size; public void reserve(int reserve) @@ -594,7 +600,6 @@ private void _selfWriteRangeImpl(int toElement, int count, int fromElement, // reverse what we written int j = toElement; int offset = toElement + count - stride; - int dj = stride; for (int i = 0, n = count / 2; i < n; i++) { for (int k = 0; k < stride; k++) { int v = m_buffer[j + k]; @@ -728,4 +733,49 @@ void quicksort(int leftIn, int rightIn, IntComparator compare, public void sort(int start, int end) { Arrays.sort(m_buffer, start, end); } + + private void writeObject(java.io.ObjectOutputStream stream) + throws IOException { + stream.defaultWriteObject(); + IntBuffer intBuf = null; + byte[] bytes = null; + for (int i = 0; i < m_size;) { + int n = Math.min(32, m_size - i); + if (bytes == null) { + bytes = new byte[n * 4]; //32 elements at a time + ByteBuffer buf = ByteBuffer.wrap(bytes); + intBuf = buf.asIntBuffer(); + } + intBuf.rewind(); + intBuf.put(m_buffer, i, n); + stream.write(bytes, 0, n * 4); + i += n; + } + } + + private void readObject(java.io.ObjectInputStream stream) + throws IOException, ClassNotFoundException { + stream.defaultReadObject(); + m_buffer = new int[m_size]; + IntBuffer intBuf = null; + byte[] bytes = null; + for (int i = 0; i < m_size;) { + int n = Math.min(32, m_size - i); + if (bytes == null) { + bytes = new byte[n * 4]; //32 elements at a time + ByteBuffer buf = ByteBuffer.wrap(bytes); + intBuf = buf.asIntBuffer(); + } + stream.read(bytes, 0, n * 4); + intBuf.rewind(); + intBuf.get(m_buffer, i, n); + i += n; + } + } + + @SuppressWarnings("unused") + private void readObjectNoData() throws ObjectStreamException { + m_buffer = new int[2]; + m_size = 0; + } } diff --git a/src/main/java/com/esri/core/geometry/QuadTree.java b/src/main/java/com/esri/core/geometry/QuadTree.java index 32d81c3c..0b4a4945 100644 --- a/src/main/java/com/esri/core/geometry/QuadTree.java +++ b/src/main/java/com/esri/core/geometry/QuadTree.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2015 Esri + Copyright 1995-2018 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -25,7 +25,11 @@ package com.esri.core.geometry; -public class QuadTree { +import java.io.Serializable; + +public class QuadTree implements Serializable { + private static final long serialVersionUID = 1L; + public static final class QuadTreeIterator { /** * Resets the iterator to an starting state on the QuadTree. If the diff --git a/src/main/java/com/esri/core/geometry/QuadTreeImpl.java b/src/main/java/com/esri/core/geometry/QuadTreeImpl.java index fe48999a..bedabdac 100644 --- a/src/main/java/com/esri/core/geometry/QuadTreeImpl.java +++ b/src/main/java/com/esri/core/geometry/QuadTreeImpl.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2015 Esri + Copyright 1995-2018 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -23,9 +23,15 @@ */ package com.esri.core.geometry; +import java.io.IOException; +import java.io.InvalidObjectException; +import java.io.ObjectStreamException; +import java.io.Serializable; import java.util.ArrayList; -class QuadTreeImpl { +class QuadTreeImpl implements Serializable { + private static final long serialVersionUID = 1L; + static final class QuadTreeIteratorImpl { /** * Resets the iterator to an starting state on the Quad_tree_impl. If @@ -1248,19 +1254,28 @@ private void set_data_values_(int data_handle, int element, Envelope2D bounding_ private Envelope2D m_data_extent; private StridedIndexTypeCollection m_quad_tree_nodes; private StridedIndexTypeCollection m_element_nodes; - private ArrayList m_data; + transient private ArrayList m_data; private AttributeStreamOfInt32 m_free_data; private int m_root; private int m_height; private boolean m_b_store_duplicates; - private int m_quadrant_mask = 3; - private int m_height_bit_shift = 2; - private int m_flushing_count = 5; + final static private int m_quadrant_mask = 3; + final static private int m_height_bit_shift = 2; + final static private int m_flushing_count = 5; static final class Data { int element; Envelope2D box; + + Data() { + } + + Data(int element_, double x1, double y1, double x2, double y2) { + element = element_; + box = new Envelope2D(); + box.setCoords(x1, y1, x2, y2); + } Data(int element_, Envelope2D box_) { element = element_; @@ -1269,6 +1284,57 @@ static final class Data { } } + private void writeObject(java.io.ObjectOutputStream stream) + throws IOException { + stream.defaultWriteObject(); + stream.writeInt(m_data.size()); + for (int i = 0, n = m_data.size(); i < n; ++i) { + Data d = m_data.get(i); + if (d != null) { + stream.writeByte(1); + stream.writeInt(d.element); + stream.writeDouble(d.box.xmin); + stream.writeDouble(d.box.ymin); + stream.writeDouble(d.box.xmax); + stream.writeDouble(d.box.ymax); + } + else { + stream.writeByte(0); + } + + } + } + + private void readObject(java.io.ObjectInputStream stream) + throws IOException, ClassNotFoundException { + stream.defaultReadObject(); + int dataSize = stream.readInt(); + m_data = new ArrayList(dataSize); + for (int i = 0, n = dataSize; i < n; ++i) { + int b = stream.readByte(); + if (b == 1) { + int elm = stream.readInt(); + double x1 = stream.readDouble(); + double y1 = stream.readDouble(); + double x2 = stream.readDouble(); + double y2 = stream.readDouble(); + Data d = new Data(elm, x1, y1, x2, y2); + m_data.add(d); + } + else if (b == 0) { + m_data.add(null); + } + else { + throw new IOException(); + } + } + } + + @SuppressWarnings("unused") + private void readObjectNoData() throws ObjectStreamException { + throw new InvalidObjectException("Stream data required"); + } + /* m_quad_tree_nodes * 0: m_north_east_child * 1: m_north_west_child diff --git a/src/main/java/com/esri/core/geometry/StridedIndexTypeCollection.java b/src/main/java/com/esri/core/geometry/StridedIndexTypeCollection.java index c54cfe9a..8bd607e5 100644 --- a/src/main/java/com/esri/core/geometry/StridedIndexTypeCollection.java +++ b/src/main/java/com/esri/core/geometry/StridedIndexTypeCollection.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2015 Esri + Copyright 1995-2018 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -23,13 +23,17 @@ */ package com.esri.core.geometry; +import java.io.Serializable; + /** * A collection of strides of Index_type elements. To be used when one needs a * collection of homogeneous elements that contain only integer fields (i.e. * structs with Index_type members) Recycles the strides. Allows for constant * time creation and deletion of an element. */ -final class StridedIndexTypeCollection { +final class StridedIndexTypeCollection implements Serializable { + private static final long serialVersionUID = 1L; + private int[][] m_buffer = null; private int m_firstFree = -1; private int m_last = 0; diff --git a/src/test/java/com/esri/core/geometry/TestSerialization.java b/src/test/java/com/esri/core/geometry/TestSerialization.java index 8cdb4ad9..5f3796a1 100644 --- a/src/test/java/com/esri/core/geometry/TestSerialization.java +++ b/src/test/java/com/esri/core/geometry/TestSerialization.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2017 Esri + Copyright 1995-2018 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -26,7 +26,6 @@ import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; -import java.io.FileOutputStream; import java.io.InputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; @@ -401,4 +400,97 @@ public void testSerializeEnvelope2D() { } } + public void testAttributeStreamOfInt32() { + AttributeStreamOfInt32 a = new AttributeStreamOfInt32(0); + for (int i = 0; i < 100; i++) + a.add(i); + + try { + // serialize + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + ObjectOutputStream os = new ObjectOutputStream(baos); + os.writeObject(a); + os.close(); + baos.close(); + + // deserialize + ByteArrayInputStream bais = new ByteArrayInputStream( + baos.toByteArray()); + ObjectInputStream in = new ObjectInputStream(bais); + AttributeStreamOfInt32 aOut = (AttributeStreamOfInt32) in.readObject(); + in.close(); + bais.close(); + + assertTrue(aOut.size() == a.size()); + for (int i = 0; i < 100; i++) + assertTrue(aOut.get(i) == a.get(i)); + + } catch (Exception e) { + fail("AttributeStreamOfInt32 serialization failure"); + } + + } + + @Test + public void testQuadTree() { + MultiPoint mp = new MultiPoint(); + int r = 124124; + for (int i = 0; i < 100; ++i) { + r = NumberUtils.nextRand(r); + int x = r; + r = NumberUtils.nextRand(r); + int y = r; + mp.add(x, y); + } + + Envelope2D extent = new Envelope2D(); + mp.queryEnvelope2D(extent); + QuadTree quadTree = new QuadTree(extent, 8); + Envelope2D boundingbox = new Envelope2D(); + Point2D pt; + + for (int i = 0; i < mp.getPointCount(); i++) { + pt = mp.getXY(i); + boundingbox.setCoords(pt.x, pt.y, pt.x, pt.y); + quadTree.insert(i, boundingbox, -1); + } + + try { + // serialize + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + ObjectOutputStream os = new ObjectOutputStream(baos); + os.writeObject(quadTree); + os.close(); + baos.close(); + + // deserialize + ByteArrayInputStream bais = new ByteArrayInputStream( + baos.toByteArray()); + ObjectInputStream in = new ObjectInputStream(bais); + QuadTree qOut = (QuadTree) in.readObject(); + in.close(); + bais.close(); + + assertTrue(quadTree.getElementCount() == qOut.getElementCount()); + QuadTree.QuadTreeIterator iter1 = quadTree.getIterator(); + QuadTree.QuadTreeIterator iter2 = qOut.getIterator(); + int h1 = iter1.next(); + int h2 = iter2.next(); + for (; h1 != -1 && h2 != -1; h1 = iter1.next(), h2 = iter2.next()) { + assertTrue(quadTree.getElement(h1) == qOut.getElement(h2)); + assertTrue(quadTree.getElementExtent(h1).equals(qOut.getElementExtent(h2))); + assertTrue(quadTree.getExtent(quadTree.getQuad(h1)).equals(qOut.getExtent(qOut.getQuad(h2)))); + int c1 = quadTree.getSubTreeElementCount(quadTree.getQuad(h1)); + int c2 = qOut.getSubTreeElementCount(qOut.getQuad(h2)); + assertTrue(c1 == c2); + } + + assertTrue(h1 == -1 && h2 == -1); + + assertTrue(quadTree.getDataExtent().equals(qOut.getDataExtent())); + } catch (Exception e) { + fail("QuadTree serialization failure"); + } + + } } From acc586158532658c73a43bdfeec5a75ee8bab34d Mon Sep 17 00:00:00 2001 From: Tim Meehan Date: Wed, 1 Aug 2018 12:42:03 -0700 Subject: [PATCH 175/196] Fix NPE on estimateMemorySize against empty multipart geometries This commit fixes an NPE in the estimateMemorySize method when the following geometries are empty: - LINESTRING - MULTILINESTRING - GEOMETRY - MULTIGEOMETRY It also adds test "empty" versions the current unit test suite. --- .../com/esri/core/geometry/MultiPathImpl.java | 4 +-- .../core/geometry/TestEstimateMemorySize.java | 35 +++++++++++++++++++ 2 files changed, 37 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/esri/core/geometry/MultiPathImpl.java b/src/main/java/com/esri/core/geometry/MultiPathImpl.java index 54ec0a5d..249a2a48 100644 --- a/src/main/java/com/esri/core/geometry/MultiPathImpl.java +++ b/src/main/java/com/esri/core/geometry/MultiPathImpl.java @@ -66,8 +66,8 @@ public long estimateMemorySize() + (m_envelope != null ? m_envelope.estimateMemorySize() : 0) + (m_moveToPoint != null ? m_moveToPoint.estimateMemorySize() : 0) + (m_cachedRingAreas2D != null ? m_cachedRingAreas2D.estimateMemorySize() : 0) - + m_paths.estimateMemorySize() - + m_pathFlags.estimateMemorySize() + + (m_paths != null ? m_paths.estimateMemorySize() : 0) + + (m_pathFlags != null ? m_pathFlags.estimateMemorySize() : 0) + (m_segmentFlags != null ? m_segmentFlags.estimateMemorySize() : 0) + (m_segmentParamIndex != null ? m_segmentParamIndex.estimateMemorySize() : 0) + (m_segmentParams != null ? m_segmentParams.estimateMemorySize() : 0); diff --git a/src/test/java/com/esri/core/geometry/TestEstimateMemorySize.java b/src/test/java/com/esri/core/geometry/TestEstimateMemorySize.java index e4195c58..ae391d8c 100644 --- a/src/test/java/com/esri/core/geometry/TestEstimateMemorySize.java +++ b/src/test/java/com/esri/core/geometry/TestEstimateMemorySize.java @@ -77,36 +77,71 @@ public void testPoint() { testGeometry(parseWkt("POINT (1 2)")); } + @Test + public void testEmptyPoint() { + testGeometry(parseWkt("POINT EMPTY")); + } + @Test public void testMultiPoint() { testGeometry(parseWkt("MULTIPOINT (0 0, 1 1, 2 3)")); } + @Test + public void testEmptyMultiPoint() { + testGeometry(parseWkt("MULTIPOINT EMPTY")); + } + @Test public void testLineString() { testGeometry(parseWkt("LINESTRING (0 1, 2 3, 4 5)")); } + @Test + public void testEmptyLineString() { + testGeometry(parseWkt("LINESTRING EMPTY")); + } + @Test public void testMultiLineString() { testGeometry(parseWkt("MULTILINESTRING ((0 1, 2 3, 4 5), (1 1, 2 2))")); } + @Test + public void testEmptyMultiLineString() { + testGeometry(parseWkt("MULTILINESTRING EMPTY")); + } + @Test public void testPolygon() { testGeometry(parseWkt("POLYGON ((30 10, 40 40, 20 40, 10 20, 30 10))")); } + @Test + public void testEmptyPolygon() { + testGeometry(parseWkt("POLYGON EMPTY")); + } + @Test public void testMultiPolygon() { testGeometry(parseWkt("MULTIPOLYGON (((30 20, 45 40, 10 40, 30 20)), ((15 5, 40 10, 10 20, 5 10, 15 5)))")); } + @Test + public void testEmptyMultiPolygon() { + testGeometry(parseWkt("MULTIPOLYGON EMPTY")); + } + @Test public void testGeometryCollection() { testGeometry(parseWkt("GEOMETRYCOLLECTION (POINT(4 6), LINESTRING(4 6,7 10))")); } + @Test + public void testEmptyGeometryCollection() { + testGeometry(parseWkt("GEOMETRYCOLLECTION EMPTY")); + } + private void testGeometry(OGCGeometry geometry) { assertTrue(geometry.estimateMemorySize() > 0); } From 072434b521ad1dd62ef6d9ee2214888a1868e5a3 Mon Sep 17 00:00:00 2001 From: Sergey Tolstov Date: Tue, 14 Aug 2018 13:43:22 -0700 Subject: [PATCH 176/196] Fix convex hull crash for collection of polygons --- .gitignore | 1 + .../geometry/ogc/OGCConcreteGeometryCollection.java | 4 ++-- .../java/com/esri/core/geometry/TestConvexHull.java | 10 ++++++++++ 3 files changed, 13 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index 7328fe5d..1e2db53a 100644 --- a/.gitignore +++ b/.gitignore @@ -35,3 +35,4 @@ ehthumbs.db Thumbs.db target/* /bin/ +/target/ diff --git a/src/main/java/com/esri/core/geometry/ogc/OGCConcreteGeometryCollection.java b/src/main/java/com/esri/core/geometry/ogc/OGCConcreteGeometryCollection.java index 155b8c41..66eb1310 100644 --- a/src/main/java/com/esri/core/geometry/ogc/OGCConcreteGeometryCollection.java +++ b/src/main/java/com/esri/core/geometry/ogc/OGCConcreteGeometryCollection.java @@ -444,13 +444,13 @@ else if (geom.getType() == Geometry.Type.Point) { } if (!polygon.isEmpty()) { - if (!resultGeom.isEmpty()) { + if (resultGeom != null && !resultGeom.isEmpty()) { Geometry[] geoms = { resultGeom, polygon }; resultGeom = OperatorConvexHull.local().execute( new SimpleGeometryCursor(geoms), true, null).next(); } else { - resultGeom = polygon; + resultGeom = OperatorConvexHull.local().execute(polygon, null); } } diff --git a/src/test/java/com/esri/core/geometry/TestConvexHull.java b/src/test/java/com/esri/core/geometry/TestConvexHull.java index b29d3aaf..ee9c764e 100644 --- a/src/test/java/com/esri/core/geometry/TestConvexHull.java +++ b/src/test/java/com/esri/core/geometry/TestConvexHull.java @@ -1059,4 +1059,14 @@ public void testHullIssueGithub172() { } } + @Test + public void testHullIssueGithub194() { + { + //empty + OGCGeometry geom = OGCGeometry.fromText("GEOMETRYCOLLECTION(POLYGON EMPTY, POLYGON((0 0, 1 0, 1 1, 0 0)), POLYGON((-10 -10, 10 -10, 10 10, -10 10, -10 -10), (-5 -5, -5 5, 5 5, 5 -5, -5 -5)))"); + OGCGeometry result = geom.convexHull(); + String text = result.asText(); + assertTrue(text.compareTo("POLYGON ((-10 -10, 10 -10, 10 10, -10 10, -10 -10))") == 0); + } + } } From 28666569726a3b45184180ba3398219eab653f06 Mon Sep 17 00:00:00 2001 From: Sergey Tolstov Date: Fri, 17 Aug 2018 13:19:11 -0700 Subject: [PATCH 177/196] Delete MgrsConversionMode.java This file is unused. --- .../core/geometry/MgrsConversionMode.java | 60 ------------------- 1 file changed, 60 deletions(-) delete mode 100644 src/main/java/com/esri/core/geometry/MgrsConversionMode.java diff --git a/src/main/java/com/esri/core/geometry/MgrsConversionMode.java b/src/main/java/com/esri/core/geometry/MgrsConversionMode.java deleted file mode 100644 index b92ef646..00000000 --- a/src/main/java/com/esri/core/geometry/MgrsConversionMode.java +++ /dev/null @@ -1,60 +0,0 @@ -/* - Copyright 1995-2015 Esri - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - - For additional information, contact: - Environmental Systems Research Institute, Inc. - Attn: Contracts Dept - 380 New York Street - Redlands, California, USA 92373 - - email: contracts@esri.com - */ - -package com.esri.core.geometry; - -/** - * The modes for converting between military grid reference system (MGRS) - * notation and coordinates. - * */ -public interface MgrsConversionMode { - /** - * Uses the spheroid to determine the military grid string. - */ - public static final int mgrsAutomatic = 0;// PE_MGRS_STYLE_AUTO - /** - * Treats all spheroids as new, like WGS 1984, when creating or reading a - * military grid string. The 180 longitude falls into zone 60. - */ - public static final int mgrsNewStyle = 0x100; // PE_MGRS_STYLE_NEW - /** - * Treats all spheroids as old, like Bessel 1841, when creating or reading a - * military grid string. The 180 longitude falls into zone 60. - */ - public static final int mgrsOldStyle = 0x200; // PE_MGRS_STYLE_OLD - /** - * Treats all spheroids as new, like WGS 1984, when creating or reading a - * military grid string. The 180 longitude falls into zone 01. - */ - public static final int mgrsNewWith180InZone01 = 0x1000 + 0x100; // PE_MGRS_180_ZONE_1_PLUS - // | - // PE_MGRS_STYLE_NEW - /** - * Treats all spheroids as old, like Bessel 1841, when creating or reading a - * military grid string. The 180 longitude falls into zone 01. - */ - public static final int mgrsOldWith180InZone01 = 0x1000 + 0x200; // PE_MGRS_180_ZONE_1_PLUS - // | - // PE_MGRS_STYLE_OLD -} From bc4769aa254e82a33b0959abc15ff146886ce932 Mon Sep 17 00:00:00 2001 From: Randall Whitman Date: Fri, 31 Aug 2018 09:36:43 -0700 Subject: [PATCH 178/196] Geometry release v2.2.1 --- README.md | 2 +- pom.xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index a4b5e0ae..171fa4c6 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,7 @@ The project is also available as a [Maven](http://maven.apache.org/) dependency: com.esri.geometry esri-geometry-api - 2.2.0 + 2.2.1 ``` diff --git a/pom.xml b/pom.xml index e4c2b890..2815d47f 100755 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ com.esri.geometry esri-geometry-api - 2.2.0 + 2.2.1 jar Esri Geometry API for Java From 17fdf4e57264fdd778e3d87e59566d51e6ce923a Mon Sep 17 00:00:00 2001 From: Masha Basmanova Date: Wed, 21 Nov 2018 14:46:25 -0500 Subject: [PATCH 179/196] Add accelerator size to OGCGeometry#estimateMemorySize --- .../core/geometry/GeometryAccelerators.java | 9 ++- .../com/esri/core/geometry/MultiPathImpl.java | 5 ++ .../com/esri/core/geometry/QuadTreeImpl.java | 23 +++++++- .../core/geometry/RasterizedGeometry2D.java | 6 ++ .../geometry/RasterizedGeometry2DImpl.java | 31 +++++++--- .../esri/core/geometry/SimpleRasterizer.java | 59 ++++++++++++++++--- .../java/com/esri/core/geometry/SizeOf.java | 23 ++++++++ .../geometry/StridedIndexTypeCollection.java | 18 ++++++ .../esri/core/geometry/Transformation2D.java | 6 ++ .../core/geometry/TestEstimateMemorySize.java | 47 +++++++++++++-- 10 files changed, 201 insertions(+), 26 deletions(-) diff --git a/src/main/java/com/esri/core/geometry/GeometryAccelerators.java b/src/main/java/com/esri/core/geometry/GeometryAccelerators.java index 2ccc84cc..90d699d0 100644 --- a/src/main/java/com/esri/core/geometry/GeometryAccelerators.java +++ b/src/main/java/com/esri/core/geometry/GeometryAccelerators.java @@ -23,8 +23,6 @@ */ package com.esri.core.geometry; -import java.util.ArrayList; - class GeometryAccelerators { private RasterizedGeometry2D m_rasterizedGeometry; @@ -84,4 +82,11 @@ static boolean canUseQuadTreeForPaths(Geometry geom) { return true; } + + public long estimateMemorySize() + { + return (m_rasterizedGeometry != null ? m_rasterizedGeometry.estimateMemorySize() : 0) + + (m_quad_tree != null ? m_quad_tree.estimateMemorySize() : 0) + + (m_quad_tree_for_paths != null ? m_quad_tree_for_paths.estimateMemorySize() : 0); + } } diff --git a/src/main/java/com/esri/core/geometry/MultiPathImpl.java b/src/main/java/com/esri/core/geometry/MultiPathImpl.java index 249a2a48..c9400ee0 100644 --- a/src/main/java/com/esri/core/geometry/MultiPathImpl.java +++ b/src/main/java/com/esri/core/geometry/MultiPathImpl.java @@ -77,6 +77,11 @@ public long estimateMemorySize() size += m_vertexAttributes[i].estimateMemorySize(); } } + + if (m_accelerators != null) { + size += m_accelerators.estimateMemorySize(); + } + return size; } diff --git a/src/main/java/com/esri/core/geometry/QuadTreeImpl.java b/src/main/java/com/esri/core/geometry/QuadTreeImpl.java index bedabdac..e9234bb5 100644 --- a/src/main/java/com/esri/core/geometry/QuadTreeImpl.java +++ b/src/main/java/com/esri/core/geometry/QuadTreeImpl.java @@ -29,6 +29,10 @@ import java.io.Serializable; import java.util.ArrayList; +import static com.esri.core.geometry.SizeOf.SIZE_OF_DATA; +import static com.esri.core.geometry.SizeOf.SIZE_OF_QUAD_TREE_IMPL; +import static com.esri.core.geometry.SizeOf.sizeOfObjectArray; + class QuadTreeImpl implements Serializable { private static final long serialVersionUID = 1L; @@ -777,6 +781,22 @@ QuadTreeSortedIteratorImpl getSortedIterator() { return new QuadTreeSortedIteratorImpl(getIterator()); } + public long estimateMemorySize() + { + long size = SIZE_OF_QUAD_TREE_IMPL + + (m_extent != null ? m_extent.estimateMemorySize() : 0) + + (m_data_extent != null ? m_data_extent.estimateMemorySize() : 0) + + (m_quad_tree_nodes != null ? m_quad_tree_nodes.estimateMemorySize() : 0) + + (m_element_nodes != null ? m_element_nodes.estimateMemorySize() : 0) + + (m_free_data != null ? m_free_data.estimateMemorySize() : 0); + + if (m_data != null) { + size += sizeOfObjectArray(m_data.size()) + m_data.size() * SIZE_OF_DATA; + } + + return size; + } + private void reset_(Envelope2D extent, int height) { if (height < 0 || height > 127) throw new IllegalArgumentException("invalid height"); @@ -1268,9 +1288,6 @@ static final class Data { int element; Envelope2D box; - Data() { - } - Data(int element_, double x1, double y1, double x2, double y2) { element = element_; box = new Envelope2D(); diff --git a/src/main/java/com/esri/core/geometry/RasterizedGeometry2D.java b/src/main/java/com/esri/core/geometry/RasterizedGeometry2D.java index 46ccd5c6..ff1b8257 100644 --- a/src/main/java/com/esri/core/geometry/RasterizedGeometry2D.java +++ b/src/main/java/com/esri/core/geometry/RasterizedGeometry2D.java @@ -136,4 +136,10 @@ static boolean canUseAccelerator(Geometry geom) { */ public abstract boolean dbgSaveToBitmap(String fileName); + /** + * Returns an estimate of this object size in bytes. + * + * @return Returns an estimate of this object size in bytes. + */ + public abstract long estimateMemorySize(); } diff --git a/src/main/java/com/esri/core/geometry/RasterizedGeometry2DImpl.java b/src/main/java/com/esri/core/geometry/RasterizedGeometry2DImpl.java index ff21c8ec..6b9a2e4d 100644 --- a/src/main/java/com/esri/core/geometry/RasterizedGeometry2DImpl.java +++ b/src/main/java/com/esri/core/geometry/RasterizedGeometry2DImpl.java @@ -24,18 +24,14 @@ package com.esri.core.geometry; -import java.io.*; +import java.io.FileOutputStream; +import java.io.IOException; import java.nio.ByteBuffer; import java.nio.ByteOrder; -import com.esri.core.geometry.Envelope2D; -import com.esri.core.geometry.Geometry; -import com.esri.core.geometry.GeometryException; -import com.esri.core.geometry.NumberUtils; -import com.esri.core.geometry.Point2D; -import com.esri.core.geometry.Segment; -import com.esri.core.geometry.SegmentIteratorImpl; -import com.esri.core.geometry.SimpleRasterizer; +import static com.esri.core.geometry.SizeOf.SIZE_OF_RASTERIZED_GEOMETRY_2D_IMPL; +import static com.esri.core.geometry.SizeOf.SIZE_OF_SCAN_CALLBACK_IMPL; +import static com.esri.core.geometry.SizeOf.sizeOfIntArray; final class RasterizedGeometry2DImpl extends RasterizedGeometry2D { int[] m_bitmap; @@ -89,6 +85,13 @@ public void drawScan(int[] scans, int scanCount3) { } } } + + @Override + public long estimateMemorySize() + { + return SIZE_OF_SCAN_CALLBACK_IMPL + + (m_bitmap != null ? sizeOfIntArray(m_bitmap.length) : 0); + } } void fillMultiPath(SimpleRasterizer rasterizer, Transformation2D trans, MultiPathImpl polygon, boolean isWinding) { @@ -559,4 +562,14 @@ public boolean dbgSaveToBitmap(String fileName) { } } + + @Override + public long estimateMemorySize() + { + return SIZE_OF_RASTERIZED_GEOMETRY_2D_IMPL + + (m_geomEnv != null ? m_geomEnv.estimateMemorySize() : 0) + + (m_transform != null ? m_transform.estimateMemorySize(): 0) + + (m_rasterizer != null ? m_rasterizer.estimateMemorySize(): 0) + + (m_callback != null ? m_callback.estimateMemorySize(): 0); + } } diff --git a/src/main/java/com/esri/core/geometry/SimpleRasterizer.java b/src/main/java/com/esri/core/geometry/SimpleRasterizer.java index de817443..d91f734f 100644 --- a/src/main/java/com/esri/core/geometry/SimpleRasterizer.java +++ b/src/main/java/com/esri/core/geometry/SimpleRasterizer.java @@ -24,11 +24,14 @@ package com.esri.core.geometry; -import java.util.ArrayList; import java.util.Arrays; -import java.util.Collections; import java.util.Comparator; +import static com.esri.core.geometry.SizeOf.SIZE_OF_EDGE; +import static com.esri.core.geometry.SizeOf.SIZE_OF_SIMPLE_RASTERIZER; +import static com.esri.core.geometry.SizeOf.sizeOfIntArray; +import static com.esri.core.geometry.SizeOf.sizeOfObjectArray; + /** * Simple scanline rasterizer. Caller provides a callback to draw pixels to actual surface. * @@ -44,15 +47,22 @@ public class SimpleRasterizer { * Winding fill rule */ public final static int WINDING = 1; - - public static interface ScanCallback { + + public interface ScanCallback { /** * Rasterizer calls this method for each scan it produced * @param scans array of scans. Scans are triplets of numbers. The start X coordinate for the scan (inclusive), * the end X coordinate of the scan (exclusive), the Y coordinate for the scan. * @param scanCount3 The number of initialized elements in the scans array. The scan count is scanCount3 / 3. */ - public abstract void drawScan(int[] scans, int scanCount3); + void drawScan(int[] scans, int scanCount3); + + /** + * Returns an estimate of this object size in bytes. + * + * @return Returns an estimate of this object size in bytes. + */ + long estimateMemorySize(); } public SimpleRasterizer() { @@ -340,17 +350,50 @@ final boolean addSegmentStroke(double x1, double y1, double x2, double y2, doubl } public final ScanCallback getScanCallback() { return callback_; } - - + + public long estimateMemorySize() + { + // callback_ is only a pointer, the actual size is accounted for in the caller of setup() + long size = SIZE_OF_SIMPLE_RASTERIZER + + (activeEdgesTable_ != null ? activeEdgesTable_.estimateMemorySize() : 0) + + (scanBuffer_ != null ? sizeOfIntArray(scanBuffer_.length) : 0); + + if (ySortedEdges_ != null) { + size += sizeOfObjectArray(ySortedEdges_.length); + for (int i = 0; i < ySortedEdges_.length; i++) { + if (ySortedEdges_[i] != null) { + size += ySortedEdges_[i].estimateMemorySize(); + } + } + } + + if (sortBuffer_ != null) { + size += sizeOfObjectArray(sortBuffer_.length); + for (int i = 0; i < sortBuffer_.length; i++) { + if (sortBuffer_[i] != null) { + size += sortBuffer_[i].estimateMemorySize(); + } + } + } + + return size; + } + //PRIVATE - private static class Edge { + static class Edge { long x; long dxdy; int y; int ymax; int dir; Edge next; + + long estimateMemorySize() + { + // next is only a pointer, the actual size is accounted for in SimpleRasterizer#estimateMemorySize + return SIZE_OF_EDGE; + } } private final void advanceAET_() { diff --git a/src/main/java/com/esri/core/geometry/SizeOf.java b/src/main/java/com/esri/core/geometry/SizeOf.java index 31460366..6b097dad 100644 --- a/src/main/java/com/esri/core/geometry/SizeOf.java +++ b/src/main/java/com/esri/core/geometry/SizeOf.java @@ -36,6 +36,8 @@ import static sun.misc.Unsafe.ARRAY_INT_INDEX_SCALE; import static sun.misc.Unsafe.ARRAY_LONG_BASE_OFFSET; import static sun.misc.Unsafe.ARRAY_LONG_INDEX_SCALE; +import static sun.misc.Unsafe.ARRAY_OBJECT_BASE_OFFSET; +import static sun.misc.Unsafe.ARRAY_OBJECT_INDEX_SCALE; import static sun.misc.Unsafe.ARRAY_SHORT_BASE_OFFSET; import static sun.misc.Unsafe.ARRAY_SHORT_INDEX_SCALE; @@ -88,6 +90,22 @@ public final class SizeOf { public static final int SIZE_OF_MAPGEOMETRY = 24; + public static final int SIZE_OF_RASTERIZED_GEOMETRY_2D_IMPL = 112; + + public static final int SIZE_OF_SCAN_CALLBACK_IMPL = 32; + + public static final int SIZE_OF_TRANSFORMATION_2D = 64; + + public static final int SIZE_OF_SIMPLE_RASTERIZER = 64; + + public static final int SIZE_OF_EDGE = 48; + + public static final int SIZE_OF_QUAD_TREE_IMPL = 48; + + public static final int SIZE_OF_DATA = 24; + + public static final int SIZE_OF_STRIDED_INDEX_TYPE_COLLECTION = 48; + public static long sizeOfByteArray(int length) { return ARRAY_BYTE_BASE_OFFSET + (((long) ARRAY_BYTE_INDEX_SCALE) * length); } @@ -116,6 +134,11 @@ public static long sizeOfDoubleArray(int length) { return ARRAY_DOUBLE_BASE_OFFSET + (((long) ARRAY_DOUBLE_INDEX_SCALE) * length); } + public static long sizeOfObjectArray(int length) + { + return ARRAY_OBJECT_BASE_OFFSET + (((long) ARRAY_OBJECT_INDEX_SCALE) * length); + } + private SizeOf() { } } diff --git a/src/main/java/com/esri/core/geometry/StridedIndexTypeCollection.java b/src/main/java/com/esri/core/geometry/StridedIndexTypeCollection.java index 8bd607e5..ae1f4dca 100644 --- a/src/main/java/com/esri/core/geometry/StridedIndexTypeCollection.java +++ b/src/main/java/com/esri/core/geometry/StridedIndexTypeCollection.java @@ -25,6 +25,10 @@ import java.io.Serializable; +import static com.esri.core.geometry.SizeOf.SIZE_OF_STRIDED_INDEX_TYPE_COLLECTION; +import static com.esri.core.geometry.SizeOf.sizeOfIntArray; +import static com.esri.core.geometry.SizeOf.sizeOfObjectArray; + /** * A collection of strides of Index_type elements. To be used when one needs a * collection of homogeneous elements that contain only integer fields (i.e. @@ -277,4 +281,18 @@ private void grow_(long newsize) { } } } + + public long estimateMemorySize() + { + long size = SIZE_OF_STRIDED_INDEX_TYPE_COLLECTION; + if (m_buffer != null) { + size += sizeOfObjectArray(m_buffer.length); + for (int i = 0; i< m_buffer.length; i++) { + if (m_buffer[i] != null) { + size += sizeOfIntArray(m_buffer[i].length); + } + } + } + return size; + } } diff --git a/src/main/java/com/esri/core/geometry/Transformation2D.java b/src/main/java/com/esri/core/geometry/Transformation2D.java index 99ea7cde..1704628a 100644 --- a/src/main/java/com/esri/core/geometry/Transformation2D.java +++ b/src/main/java/com/esri/core/geometry/Transformation2D.java @@ -24,6 +24,8 @@ package com.esri.core.geometry; +import static com.esri.core.geometry.SizeOf.SIZE_OF_TRANSFORMATION_2D; + /** * The affine transformation class for 2D. * @@ -921,4 +923,8 @@ public void extractScaleTransform(Transformation2D scale, rotateNshearNshift.multiply(this); } + public long estimateMemorySize() + { + return SIZE_OF_TRANSFORMATION_2D; + } } diff --git a/src/test/java/com/esri/core/geometry/TestEstimateMemorySize.java b/src/test/java/com/esri/core/geometry/TestEstimateMemorySize.java index ae391d8c..b1b40b22 100644 --- a/src/test/java/com/esri/core/geometry/TestEstimateMemorySize.java +++ b/src/test/java/com/esri/core/geometry/TestEstimateMemorySize.java @@ -24,6 +24,7 @@ package com.esri.core.geometry; +import com.esri.core.geometry.Geometry.GeometryAccelerationDegree; import com.esri.core.geometry.ogc.OGCConcreteGeometryCollection; import com.esri.core.geometry.ogc.OGCGeometry; import com.esri.core.geometry.ogc.OGCLineString; @@ -66,6 +67,14 @@ public void testInstanceSizes() { assertEquals(getInstanceSize(OGCMultiPolygon.class), SizeOf.SIZE_OF_OGC_MULTI_POLYGON); assertEquals(getInstanceSize(OGCPoint.class), SizeOf.SIZE_OF_OGC_POINT); assertEquals(getInstanceSize(OGCPolygon.class), SizeOf.SIZE_OF_OGC_POLYGON); + assertEquals(getInstanceSize(RasterizedGeometry2DImpl.class), SizeOf.SIZE_OF_RASTERIZED_GEOMETRY_2D_IMPL); + assertEquals(getInstanceSize(RasterizedGeometry2DImpl.ScanCallbackImpl.class), SizeOf.SIZE_OF_SCAN_CALLBACK_IMPL); + assertEquals(getInstanceSize(Transformation2D.class), SizeOf.SIZE_OF_TRANSFORMATION_2D); + assertEquals(getInstanceSize(SimpleRasterizer.class), SizeOf.SIZE_OF_SIMPLE_RASTERIZER); + assertEquals(getInstanceSize(SimpleRasterizer.Edge.class), SizeOf.SIZE_OF_EDGE); + assertEquals(getInstanceSize(QuadTreeImpl.class), SizeOf.SIZE_OF_QUAD_TREE_IMPL); + assertEquals(getInstanceSize(QuadTreeImpl.Data.class), SizeOf.SIZE_OF_DATA); + assertEquals(getInstanceSize(StridedIndexTypeCollection.class), SizeOf.SIZE_OF_STRIDED_INDEX_TYPE_COLLECTION); } private static long getInstanceSize(Class clazz) { @@ -93,7 +102,7 @@ public void testEmptyMultiPoint() { } @Test - public void testLineString() { + public void testAcceleratedGeometry() { testGeometry(parseWkt("LINESTRING (0 1, 2 3, 4 5)")); } @@ -104,7 +113,7 @@ public void testEmptyLineString() { @Test public void testMultiLineString() { - testGeometry(parseWkt("MULTILINESTRING ((0 1, 2 3, 4 5), (1 1, 2 2))")); + testAcceleratedGeometry(parseWkt("MULTILINESTRING ((0 1, 2 3, 4 5), (1 1, 2 2))")); } @Test @@ -114,7 +123,7 @@ public void testEmptyMultiLineString() { @Test public void testPolygon() { - testGeometry(parseWkt("POLYGON ((30 10, 40 40, 20 40, 10 20, 30 10))")); + testAcceleratedGeometry(parseWkt("POLYGON ((30 10, 40 40, 20 40, 10 20, 30 10))")); } @Test @@ -124,7 +133,7 @@ public void testEmptyPolygon() { @Test public void testMultiPolygon() { - testGeometry(parseWkt("MULTIPOLYGON (((30 20, 45 40, 10 40, 30 20)), ((15 5, 40 10, 10 20, 5 10, 15 5)))")); + testAcceleratedGeometry(parseWkt("MULTIPOLYGON (((30 20, 45 40, 10 40, 30 20)), ((15 5, 40 10, 10 20, 5 10, 15 5)))")); } @Test @@ -146,6 +155,36 @@ private void testGeometry(OGCGeometry geometry) { assertTrue(geometry.estimateMemorySize() > 0); } + private void testAcceleratedGeometry(OGCGeometry geometry) { + long initialSize = geometry.estimateMemorySize(); + assertTrue(initialSize > 0); + + Envelope envelope = new Envelope(); + geometry.getEsriGeometry().queryEnvelope(envelope); + + long withEnvelopeSize = geometry.estimateMemorySize(); + assertTrue(withEnvelopeSize > initialSize); + + accelerate(geometry, GeometryAccelerationDegree.enumMild); + long mildAcceleratedSize = geometry.estimateMemorySize(); + assertTrue(mildAcceleratedSize > withEnvelopeSize); + + accelerate(geometry, GeometryAccelerationDegree.enumMedium); + long mediumAcceleratedSize = geometry.estimateMemorySize(); + assertTrue(mediumAcceleratedSize > mildAcceleratedSize); + + accelerate(geometry, GeometryAccelerationDegree.enumHot); + long hotAcceleratedSize = geometry.estimateMemorySize(); + assertTrue(hotAcceleratedSize > mediumAcceleratedSize); + } + + private void accelerate(OGCGeometry geometry, GeometryAccelerationDegree accelerationDegree) + { + Operator relateOperator = OperatorFactoryLocal.getInstance().getOperator(Operator.Type.Relate); + boolean accelerated = relateOperator.accelerateGeometry(geometry.getEsriGeometry(), geometry.getEsriSpatialReference(), accelerationDegree); + assertTrue(accelerated); + } + private static OGCGeometry parseWkt(String wkt) { OGCGeometry geometry = OGCGeometry.fromText(wkt); geometry.setSpatialReference(null); From 937ca24b82ddce87a66948c5e8652ab64a83aa4d Mon Sep 17 00:00:00 2001 From: Sergey Tolstov Date: Fri, 30 Nov 2018 11:11:01 -0800 Subject: [PATCH 180/196] Fix rasterization with degenerate segments (#207) * Fix rasterization with degenerate segments * Change javadoc source version to 1.6 --- .gitignore | 2 + build.xml | 4 +- .../geometry/RasterizedGeometry2DImpl.java | 26 +++--- .../esri/core/geometry/SimpleRasterizer.java | 89 ++++++++++--------- .../esri/core/geometry/TestOGCContains.java | 14 +++ 5 files changed, 77 insertions(+), 58 deletions(-) diff --git a/.gitignore b/.gitignore index 1e2db53a..4f5d1e04 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,8 @@ bin/ results/ javadoc/ +depfiles/ +DepFiles/ esri-geometry-api.jar .project .classpath diff --git a/build.xml b/build.xml index a2e894a4..49f70b58 100644 --- a/build.xml +++ b/build.xml @@ -86,14 +86,14 @@ - + - + diff --git a/src/main/java/com/esri/core/geometry/RasterizedGeometry2DImpl.java b/src/main/java/com/esri/core/geometry/RasterizedGeometry2DImpl.java index 6b9a2e4d..c6bf1f8d 100644 --- a/src/main/java/com/esri/core/geometry/RasterizedGeometry2DImpl.java +++ b/src/main/java/com/esri/core/geometry/RasterizedGeometry2DImpl.java @@ -141,10 +141,6 @@ void strokeDrawPolyPath(SimpleRasterizer rasterizer, SegmentIteratorImpl segIter = polyPath.querySegmentIterator(); double strokeHalfWidth = m_transform.transform(tol) + 1.5; - double shortSegment = 0.25; - Point2D vec = new Point2D(); - Point2D vecA = new Point2D(); - Point2D vecB = new Point2D(); Point2D ptStart = new Point2D(); Point2D ptEnd = new Point2D(); @@ -153,6 +149,7 @@ void strokeDrawPolyPath(SimpleRasterizer rasterizer, double[] helper_xy_10_elm = new double[10]; Envelope2D segEnv = new Envelope2D(); Point2D ptOld = new Point2D(); + double extraWidth = 0; while (segIter.nextPath()) { boolean hasFan = false; boolean first = true; @@ -170,10 +167,11 @@ void strokeDrawPolyPath(SimpleRasterizer rasterizer, if (hasFan) { rasterizer.startAddingEdges(); rasterizer.addSegmentStroke(prev_start.x, prev_start.y, - prev_end.x, prev_end.y, strokeHalfWidth, false, + prev_end.x, prev_end.y, strokeHalfWidth + extraWidth, false, helper_xy_10_elm); rasterizer.renderEdges(SimpleRasterizer.EVEN_ODD); hasFan = false; + extraWidth = 0.0; } first = true; @@ -195,19 +193,26 @@ void strokeDrawPolyPath(SimpleRasterizer rasterizer, rasterizer.startAddingEdges(); hasFan = !rasterizer.addSegmentStroke(prev_start.x, - prev_start.y, prev_end.x, prev_end.y, strokeHalfWidth, + prev_start.y, prev_end.x, prev_end.y, strokeHalfWidth + extraWidth, true, helper_xy_10_elm); rasterizer.renderEdges(SimpleRasterizer.EVEN_ODD); - if (!hasFan) + if (!hasFan) { ptOld.setCoords(prev_end); + extraWidth = 0.0; + } + else { + //track length of skipped segment to add it to the stroke width for the next edge. + extraWidth = Math.max(extraWidth, Point2D.distance(prev_start, prev_end)); + } } if (hasFan) { rasterizer.startAddingEdges(); hasFan = !rasterizer.addSegmentStroke(prev_start.x, - prev_start.y, prev_end.x, prev_end.y, strokeHalfWidth, + prev_start.y, prev_end.x, prev_end.y, strokeHalfWidth + extraWidth, false, helper_xy_10_elm); rasterizer.renderEdges(SimpleRasterizer.EVEN_ODD); + extraWidth = 0.0; } } } @@ -308,12 +313,10 @@ void init(MultiVertexGeometryImpl geom, double toleranceXY, m_transform = new Transformation2D(); m_transform.initializeFromRect(worldEnv, pixEnv);// geom to pixels - Transformation2D identityTransform = new Transformation2D(); - switch (geom.getType().value()) { case Geometry.GeometryType.MultiPoint: callback.setColor(m_rasterizer, 2); - fillPoints(m_rasterizer, (MultiPointImpl) geom, m_stroke_half_width); + fillPoints(m_rasterizer, (MultiPointImpl) geom, m_stroke_half_width); break; case Geometry.GeometryType.Polyline: callback.setColor(m_rasterizer, 2); @@ -545,7 +548,6 @@ public boolean dbgSaveToBitmap(String fileName) { // int32_t* rgb4 = (int32_t*)malloc(biSizeImage); for (int y = 0; y < height; y++) { int scanlineIn = y * ((width * 2 + 31) / 32); - int scanlineOut = offset + width * y; for (int x = 0; x < width; x++) { int res = (m_bitmap[scanlineIn + (x >> 4)] >> ((x & 15) * 2)) & 3; diff --git a/src/main/java/com/esri/core/geometry/SimpleRasterizer.java b/src/main/java/com/esri/core/geometry/SimpleRasterizer.java index d91f734f..a2e9dfa0 100644 --- a/src/main/java/com/esri/core/geometry/SimpleRasterizer.java +++ b/src/main/java/com/esri/core/geometry/SimpleRasterizer.java @@ -305,50 +305,51 @@ public final void fillEnvelope(Envelope2D envIn) { } } - final boolean addSegmentStroke(double x1, double y1, double x2, double y2, double half_width, boolean skip_short, double[] helper_xy_10_elm) - { - double vec_x = x2 - x1; - double vec_y = y2 - y1; - double len = Math.sqrt(vec_x * vec_x + vec_y * vec_y); - if (skip_short && len < 0.5) - return false; - - boolean bshort = len < 0.00001; - if (bshort) - { - len = 0.00001; - vec_x = len; - vec_y = 0.0; - } - - double f = half_width / len; - vec_x *= f; vec_y *= f; - double vecA_x = -vec_y; - double vecA_y = vec_x; - double vecB_x = vec_y; - double vecB_y = -vec_x; - //extend by half width - x1 -= vec_x; - y1 -= vec_y; - x2 += vec_x; - y2 += vec_y; - //create rotated rectangle - double[] fan = helper_xy_10_elm; - assert(fan.length == 10); - fan[0] = x1 + vecA_x; - fan[1] = y1 + vecA_y;//fan[0].add(pt_start, vecA); - fan[2] = x1 + vecB_x; - fan[3] = y1 + vecB_y;//fan[1].add(pt_start, vecB); - fan[4] = x2 + vecB_x; - fan[5] = y2 + vecB_y;//fan[2].add(pt_end, vecB) - fan[6] = x2 + vecA_x; - fan[7] = y2 + vecA_y;//fan[3].add(pt_end, vecA) - fan[8] = fan[0]; - fan[9] = fan[1]; - addRing(fan); - return true; - } - + final boolean addSegmentStroke(double x1, double y1, double x2, double y2, double half_width, boolean skip_short, + double[] helper_xy_10_elm) { + double vec_x = x2 - x1; + double vec_y = y2 - y1; + double sqr_len = vec_x * vec_x + vec_y * vec_y; + if (skip_short && sqr_len < (0.5 * 0.5)) { + return false; + } + + boolean veryShort = !skip_short && (sqr_len < (0.00001 * 0.00001)); + if (veryShort) { + vec_x = half_width + 0.00001; + vec_y = 0.0; + } else { + double f = half_width / Math.sqrt(sqr_len); + vec_x *= f; + vec_y *= f; + } + + double vecA_x = -vec_y; + double vecA_y = vec_x; + double vecB_x = vec_y; + double vecB_y = -vec_x; + // extend by half width + x1 -= vec_x; + y1 -= vec_y; + x2 += vec_x; + y2 += vec_y; + // create rotated rectangle + double[] fan = helper_xy_10_elm; + assert (fan.length == 10); + fan[0] = x1 + vecA_x; + fan[1] = y1 + vecA_y;// fan[0].add(pt_start, vecA); + fan[2] = x1 + vecB_x; + fan[3] = y1 + vecB_y;// fan[1].add(pt_start, vecB); + fan[4] = x2 + vecB_x; + fan[5] = y2 + vecB_y;// fan[2].add(pt_end, vecB) + fan[6] = x2 + vecA_x; + fan[7] = y2 + vecA_y;// fan[3].add(pt_end, vecA) + fan[8] = fan[0]; + fan[9] = fan[1]; + addRing(fan); + return true; + } + public final ScanCallback getScanCallback() { return callback_; } public long estimateMemorySize() diff --git a/src/test/java/com/esri/core/geometry/TestOGCContains.java b/src/test/java/com/esri/core/geometry/TestOGCContains.java index fd2c5116..04a328bf 100644 --- a/src/test/java/com/esri/core/geometry/TestOGCContains.java +++ b/src/test/java/com/esri/core/geometry/TestOGCContains.java @@ -55,6 +55,20 @@ public void testGeometryCollection() { "GEOMETRYCOLLECTION (MULTIPOINT (0 0, 2 1))"); } + @Test + public void testAcceleratedPiP() { + String wkt = "MULTIPOLYGON (((-109.642707 30.5236901, -109.607932 30.5367411, -109.5820257 30.574184, -109.5728286 30.5874766, -109.568679 30.5934741, -109.5538097 30.5918356, -109.553714 30.5918251, -109.553289 30.596034, -109.550951 30.6191889, -109.5474935 30.6221179, -109.541059 30.6275689, -109.5373751 30.6326491, -109.522538 30.6531099, -109.514671 30.6611981, -109.456764 30.6548095, -109.4556456 30.6546861, -109.4536755 30.6544688, -109.4526481 30.6543554, -109.446824 30.6537129, -109.437751 30.6702901, -109.433968 30.6709781, -109.43338 30.6774591, -109.416243 30.7164651, -109.401643 30.7230741, -109.377583 30.7145241, -109.3487939 30.7073896, -109.348594 30.7073401, -109.3483718 30.7073797, -109.3477608 30.7074887, -109.3461903 30.7078834, -109.3451022 30.7081569, -109.3431732 30.7086416, -109.3423301 30.708844, -109.3419714 30.7089301, -109.3416347 30.709011, -109.3325693 30.7111874, -109.3323814 30.7112325, -109.332233 30.7112681, -109.332191 30.7112686, -109.3247809 30.7113581, -109.322215 30.7159391, -109.327776 30.7234381, -109.350134 30.7646001, -109.364505 30.8382481, -109.410211 30.8749199, -109.400048 30.8733419, -109.3847799 30.9652412, -109.3841625 30.9689575, -109.375268 31.0224939, -109.390544 31.0227899, -109.399749 31.0363341, -109.395787 31.0468411, -109.388174 31.0810249, -109.3912446 31.0891966, -109.3913452 31.0894644, -109.392735 31.0931629, -109.4000839 31.0979214, -109.402803 31.0996821, -109.4110458 31.1034586, -109.419153 31.1071729, -109.449782 31.1279489, -109.469654 31.1159979, -109.4734874 31.1131178, -109.473753 31.1129183, -109.4739754 31.1127512, -109.491296 31.0997381, -109.507789 31.0721811, -109.512776 31.0537519, -109.5271478 31.0606861, -109.5313703 31.0627234, -109.540698 31.0672239, -109.5805468 31.0674089, -109.5807399 31.0674209, -109.595423 31.0674779, -109.60347 31.0690241, -109.6048011 31.068808, -109.6050803 31.0687627, -109.6192237 31.0664664, -109.635432 31.0638349, -109.6520068 31.0955326, -109.6522294 31.0959584, -109.652373 31.0962329, -109.657709 31.0959719, -109.718258 31.0930099, -109.821036 31.0915909, -109.8183088 31.0793374, -109.8165128 31.0712679, -109.8140062 31.0600052, -109.8138512 31.0593089, -109.812707 31.0541679, -109.8188146 31.0531909, -109.8215447 31.0527542, -109.8436765 31.0492138, -109.8514316 31.0479733, -109.8620535 31.0462742, -109.8655958 31.0457076, -109.868388 31.0452609, -109.8795483 31.0359656, -109.909274 31.0112075, -109.9210382 31.0014092, -109.9216329 31.0009139, -109.920594 30.994183, -109.9195356 30.9873254, -109.9192113 30.9852243, -109.9186281 30.9814453, -109.917814 30.9761709, -109.933894 30.9748879, -109.94094 30.9768059, -109.944854 30.9719821, -109.950803 30.9702809, -109.954025 30.9652409, -109.9584129 30.9636033, -109.958471 30.9635809, -109.9590542 30.9644372, -109.959896 30.9656733, -109.9604184 30.9664405, -109.9606288 30.9667494, -109.9608462 30.9670686, -109.961225 30.9676249, -109.9611615 30.9702903, -109.9611179 30.9721175, -109.9610885 30.9733488, -109.9610882 30.9733604, -109.9610624 30.9744451, -109.961017 30.9763469, -109.962609 30.9786559, -109.9634437 30.9783167, -110.00172 30.9627641, -110.0021152 30.9627564, -110.0224353 30.9623622, -110.0365868 30.9620877, -110.037493 30.9620701, -110.0374055 30.961663, -110.033653 30.9442059, -110.0215506 30.9492932, -110.0180392 30.9507693, -110.011203 30.9536429, -110.0062891 30.9102124, -110.0058721 30.9065268, -110.004869 30.8976609, -109.996392 30.8957129, -109.985038 30.8870439, -109.969416 30.9006011, -109.967905 30.8687239, -109.903498 30.8447749, -109.882925 30.8458289, -109.865184 30.8206519, -109.86465 30.777698, -109.864515 30.7668429, -109.837007 30.7461781, -109.83453 30.7164469, -109.839017 30.7089009, -109.813394 30.6906529, -109.808694 30.6595701, -109.795334 30.6630041, -109.7943042 30.6427223, -109.7940456 30.6376287, -109.7940391 30.637501, -109.793823 30.6332449, -109.833511 30.6274289, -109.830299 30.6252799, -109.844198 30.6254801, -109.852442 30.6056949, -109.832973 30.6021201, -109.8050409 30.591211, -109.773847 30.5790279, -109.772859 30.5521999, -109.754427 30.5393969, -109.743293 30.5443401, -109.6966136 30.5417334, -109.6648181 30.5399578, -109.6560456 30.5394679, -109.6528439 30.5392912, -109.6504039 30.5391565, -109.6473602 30.5389885, -109.646906 30.5389634, -109.6414545 30.5386625, -109.639708 30.5385661, -109.6397729 30.5382443, -109.642707 30.5236901)))"; + String pointWkt = "POINT (-109.65 31.091666666673)"; + + OGCGeometry polygon = OGCGeometry.fromText(wkt); + OGCGeometry point = OGCGeometry.fromText(pointWkt); + assertTrue(polygon.contains(point)); + + OperatorContains.local() + .accelerateGeometry(polygon.getEsriGeometry(), null, Geometry.GeometryAccelerationDegree.enumMild); + assertTrue(polygon.contains(point));; + } + private void assertContains(String wkt, String otherWkt) { OGCGeometry geometry = OGCGeometry.fromText(wkt); OGCGeometry otherGeometry = OGCGeometry.fromText(otherWkt); From fe623e815bad4fca9db6723bcd8bf0e1cf83e7ef Mon Sep 17 00:00:00 2001 From: Maria Basmanova Date: Mon, 3 Dec 2018 14:53:30 -0500 Subject: [PATCH 181/196] Move ScanCallback#estimateMemorySize to implementation (#209) --- .../com/esri/core/geometry/RasterizedGeometry2DImpl.java | 6 +++++- src/main/java/com/esri/core/geometry/SimpleRasterizer.java | 7 ------- 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/src/main/java/com/esri/core/geometry/RasterizedGeometry2DImpl.java b/src/main/java/com/esri/core/geometry/RasterizedGeometry2DImpl.java index c6bf1f8d..c7def2d4 100644 --- a/src/main/java/com/esri/core/geometry/RasterizedGeometry2DImpl.java +++ b/src/main/java/com/esri/core/geometry/RasterizedGeometry2DImpl.java @@ -86,7 +86,11 @@ public void drawScan(int[] scans, int scanCount3) { } } - @Override + /** + * Returns an estimate of this object size in bytes. + * + * @return Returns an estimate of this object size in bytes. + */ public long estimateMemorySize() { return SIZE_OF_SCAN_CALLBACK_IMPL + diff --git a/src/main/java/com/esri/core/geometry/SimpleRasterizer.java b/src/main/java/com/esri/core/geometry/SimpleRasterizer.java index a2e9dfa0..8c6d7e46 100644 --- a/src/main/java/com/esri/core/geometry/SimpleRasterizer.java +++ b/src/main/java/com/esri/core/geometry/SimpleRasterizer.java @@ -56,13 +56,6 @@ public interface ScanCallback { * @param scanCount3 The number of initialized elements in the scans array. The scan count is scanCount3 / 3. */ void drawScan(int[] scans, int scanCount3); - - /** - * Returns an estimate of this object size in bytes. - * - * @return Returns an estimate of this object size in bytes. - */ - long estimateMemorySize(); } public SimpleRasterizer() { From 7e51ba0757719bcf3ac4072da26aa261c8a9484f Mon Sep 17 00:00:00 2001 From: Randall Whitman Date: Mon, 3 Dec 2018 12:03:40 -0800 Subject: [PATCH 182/196] Geometry release v2.2.2 --- README.md | 2 +- pom.xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 171fa4c6..01481e54 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,7 @@ The project is also available as a [Maven](http://maven.apache.org/) dependency: com.esri.geometry esri-geometry-api - 2.2.1 + 2.2.2 ``` diff --git a/pom.xml b/pom.xml index 2815d47f..e5907321 100755 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ com.esri.geometry esri-geometry-api - 2.2.1 + 2.2.2 jar Esri Geometry API for Java From 494da8ec953d76e7c6072afbc081abfe48ff07cf Mon Sep 17 00:00:00 2001 From: Randall Whitman Date: Thu, 3 Jan 2019 10:10:28 -0800 Subject: [PATCH 183/196] v2.2.3 development & copyright year --- README.md | 2 +- pom.xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 01481e54..d4ea84c7 100644 --- a/README.md +++ b/README.md @@ -54,7 +54,7 @@ Find a bug or want to request a new feature? Please let us know by submitting a Esri welcomes contributions from anyone and everyone. Please see our [guidelines for contributing](https://github.com/esri/contributing) ## Licensing -Copyright 2013-2018 Esri +Copyright 2013-2019 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/pom.xml b/pom.xml index e5907321..eec05faa 100755 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ com.esri.geometry esri-geometry-api - 2.2.2 + 2.2.3-SNAPSHOT jar Esri Geometry API for Java From 8070e1e24afa22396624e900388f31b7405bab11 Mon Sep 17 00:00:00 2001 From: Sergey Tolstov Date: Tue, 30 Jul 2019 13:15:05 -0700 Subject: [PATCH 184/196] Fix formatting in OperatorCentroid (#226) * Fix formatting in OperatorCentroid * formatting in the unit test --- .../core/geometry/OperatorCentroid2D.java | 25 +- .../geometry/OperatorCentroid2DLocal.java | 267 +++++++++--------- .../esri/core/geometry/TestOGCCentroid.java | 97 +++---- 3 files changed, 187 insertions(+), 202 deletions(-) diff --git a/src/main/java/com/esri/core/geometry/OperatorCentroid2D.java b/src/main/java/com/esri/core/geometry/OperatorCentroid2D.java index f44a21f8..9453053d 100644 --- a/src/main/java/com/esri/core/geometry/OperatorCentroid2D.java +++ b/src/main/java/com/esri/core/geometry/OperatorCentroid2D.java @@ -23,18 +23,15 @@ */ package com.esri.core.geometry; -public abstract class OperatorCentroid2D extends Operator -{ - @Override - public Type getType() - { - return Type.Centroid2D; - } - - public abstract Point2D execute(Geometry geometry, ProgressTracker progressTracker); - - public static OperatorCentroid2D local() - { - return (OperatorCentroid2D) OperatorFactoryLocal.getInstance().getOperator(Type.Centroid2D); - } +public abstract class OperatorCentroid2D extends Operator { + @Override + public Type getType() { + return Type.Centroid2D; + } + + public abstract Point2D execute(Geometry geometry, ProgressTracker progressTracker); + + public static OperatorCentroid2D local() { + return (OperatorCentroid2D) OperatorFactoryLocal.getInstance().getOperator(Type.Centroid2D); + } } diff --git a/src/main/java/com/esri/core/geometry/OperatorCentroid2DLocal.java b/src/main/java/com/esri/core/geometry/OperatorCentroid2DLocal.java index 91b7c948..6a6a7394 100644 --- a/src/main/java/com/esri/core/geometry/OperatorCentroid2DLocal.java +++ b/src/main/java/com/esri/core/geometry/OperatorCentroid2DLocal.java @@ -25,140 +25,135 @@ import static java.lang.Math.sqrt; -public class OperatorCentroid2DLocal extends OperatorCentroid2D -{ - @Override - public Point2D execute(Geometry geometry, ProgressTracker progressTracker) - { - if (geometry.isEmpty()) { - return null; - } - - Geometry.Type geometryType = geometry.getType(); - switch (geometryType) { - case Point: - return ((Point) geometry).getXY(); - case Line: - return computeLineCentroid((Line) geometry); - case Envelope: - return ((Envelope) geometry).getCenterXY(); - case MultiPoint: - return computePointsCentroid((MultiPoint) geometry); - case Polyline: - return computePolylineCentroid(((Polyline) geometry)); - case Polygon: - return computePolygonCentroid((Polygon) geometry); - default: - throw new UnsupportedOperationException("Unexpected geometry type: " + geometryType); - } - } - - private static Point2D computeLineCentroid(Line line) - { - return new Point2D((line.getEndX() - line.getStartX()) / 2, (line.getEndY() - line.getStartY()) / 2); - } - - // Points centroid is arithmetic mean of the input points - private static Point2D computePointsCentroid(MultiPoint multiPoint) - { - double xSum = 0; - double ySum = 0; - int pointCount = multiPoint.getPointCount(); - Point2D point2D = new Point2D(); - for (int i = 0; i < pointCount; i++) { - multiPoint.getXY(i, point2D); - xSum += point2D.x; - ySum += point2D.y; - } - return new Point2D(xSum / pointCount, ySum / pointCount); - } - - // Lines centroid is weighted mean of each line segment, weight in terms of line length - private static Point2D computePolylineCentroid(Polyline polyline) - { - double xSum = 0; - double ySum = 0; - double weightSum = 0; - - Point2D startPoint = new Point2D(); - Point2D endPoint = new Point2D(); - for (int i = 0; i < polyline.getPathCount(); i++) { - polyline.getXY(polyline.getPathStart(i), startPoint); - polyline.getXY(polyline.getPathEnd(i) - 1, endPoint); - double dx = endPoint.x - startPoint.x; - double dy = endPoint.y - startPoint.y; - double length = sqrt(dx * dx + dy * dy); - weightSum += length; - xSum += (startPoint.x + endPoint.x) * length / 2; - ySum += (startPoint.y + endPoint.y) * length / 2; - } - return new Point2D(xSum / weightSum, ySum / weightSum); - } - - // Polygon centroid: area weighted average of centroids in case of holes - private static Point2D computePolygonCentroid(Polygon polygon) - { - int pathCount = polygon.getPathCount(); - - if (pathCount == 1) { - return getPolygonSansHolesCentroid(polygon); - } - - double xSum = 0; - double ySum = 0; - double areaSum = 0; - - for (int i = 0; i < pathCount; i++) { - int startIndex = polygon.getPathStart(i); - int endIndex = polygon.getPathEnd(i); - - Polygon sansHoles = getSubPolygon(polygon, startIndex, endIndex); - - Point2D centroid = getPolygonSansHolesCentroid(sansHoles); - double area = sansHoles.calculateArea2D(); - - xSum += centroid.x * area; - ySum += centroid.y * area; - areaSum += area; - } - - return new Point2D(xSum / areaSum, ySum / areaSum); - } - - private static Polygon getSubPolygon(Polygon polygon, int startIndex, int endIndex) - { - Polyline boundary = new Polyline(); - boundary.startPath(polygon.getPoint(startIndex)); - for (int i = startIndex + 1; i < endIndex; i++) { - Point current = polygon.getPoint(i); - boundary.lineTo(current); - } - - final Polygon newPolygon = new Polygon(); - newPolygon.add(boundary, false); - return newPolygon; - } - - // Polygon sans holes centroid: - // c[x] = (Sigma(x[i] + x[i + 1]) * (x[i] * y[i + 1] - x[i + 1] * y[i]), for i = 0 to N - 1) / (6 * signedArea) - // c[y] = (Sigma(y[i] + y[i + 1]) * (x[i] * y[i + 1] - x[i + 1] * y[i]), for i = 0 to N - 1) / (6 * signedArea) - private static Point2D getPolygonSansHolesCentroid(Polygon polygon) - { - int pointCount = polygon.getPointCount(); - double xSum = 0; - double ySum = 0; - double signedArea = 0; - - Point2D current = new Point2D(); - Point2D next = new Point2D(); - for (int i = 0; i < pointCount; i++) { - polygon.getXY(i, current); - polygon.getXY((i + 1) % pointCount, next); - double ladder = current.x * next.y - next.x * current.y; - xSum += (current.x + next.x) * ladder; - ySum += (current.y + next.y) * ladder; - signedArea += ladder / 2; - } - return new Point2D(xSum / (signedArea * 6), ySum / (signedArea * 6)); - } +public class OperatorCentroid2DLocal extends OperatorCentroid2D { + @Override + public Point2D execute(Geometry geometry, ProgressTracker progressTracker) { + if (geometry.isEmpty()) { + return null; + } + + Geometry.Type geometryType = geometry.getType(); + switch (geometryType) { + case Point: + return ((Point) geometry).getXY(); + case Line: + return computeLineCentroid((Line) geometry); + case Envelope: + return ((Envelope) geometry).getCenterXY(); + case MultiPoint: + return computePointsCentroid((MultiPoint) geometry); + case Polyline: + return computePolylineCentroid(((Polyline) geometry)); + case Polygon: + return computePolygonCentroid((Polygon) geometry); + default: + throw new UnsupportedOperationException("Unexpected geometry type: " + geometryType); + } + } + + private static Point2D computeLineCentroid(Line line) { + return new Point2D((line.getEndX() - line.getStartX()) / 2, (line.getEndY() - line.getStartY()) / 2); + } + + // Points centroid is arithmetic mean of the input points + private static Point2D computePointsCentroid(MultiPoint multiPoint) { + double xSum = 0; + double ySum = 0; + int pointCount = multiPoint.getPointCount(); + Point2D point2D = new Point2D(); + for (int i = 0; i < pointCount; i++) { + multiPoint.getXY(i, point2D); + xSum += point2D.x; + ySum += point2D.y; + } + return new Point2D(xSum / pointCount, ySum / pointCount); + } + + // Lines centroid is weighted mean of each line segment, weight in terms of line + // length + private static Point2D computePolylineCentroid(Polyline polyline) { + double xSum = 0; + double ySum = 0; + double weightSum = 0; + + Point2D startPoint = new Point2D(); + Point2D endPoint = new Point2D(); + for (int i = 0; i < polyline.getPathCount(); i++) { + polyline.getXY(polyline.getPathStart(i), startPoint); + polyline.getXY(polyline.getPathEnd(i) - 1, endPoint); + double dx = endPoint.x - startPoint.x; + double dy = endPoint.y - startPoint.y; + double length = sqrt(dx * dx + dy * dy); + weightSum += length; + xSum += (startPoint.x + endPoint.x) * length / 2; + ySum += (startPoint.y + endPoint.y) * length / 2; + } + return new Point2D(xSum / weightSum, ySum / weightSum); + } + + // Polygon centroid: area weighted average of centroids in case of holes + private static Point2D computePolygonCentroid(Polygon polygon) { + int pathCount = polygon.getPathCount(); + + if (pathCount == 1) { + return getPolygonSansHolesCentroid(polygon); + } + + double xSum = 0; + double ySum = 0; + double areaSum = 0; + + for (int i = 0; i < pathCount; i++) { + int startIndex = polygon.getPathStart(i); + int endIndex = polygon.getPathEnd(i); + + Polygon sansHoles = getSubPolygon(polygon, startIndex, endIndex); + + Point2D centroid = getPolygonSansHolesCentroid(sansHoles); + double area = sansHoles.calculateArea2D(); + + xSum += centroid.x * area; + ySum += centroid.y * area; + areaSum += area; + } + + return new Point2D(xSum / areaSum, ySum / areaSum); + } + + private static Polygon getSubPolygon(Polygon polygon, int startIndex, int endIndex) { + Polyline boundary = new Polyline(); + boundary.startPath(polygon.getPoint(startIndex)); + for (int i = startIndex + 1; i < endIndex; i++) { + Point current = polygon.getPoint(i); + boundary.lineTo(current); + } + + final Polygon newPolygon = new Polygon(); + newPolygon.add(boundary, false); + return newPolygon; + } + + // Polygon sans holes centroid: + // c[x] = (Sigma(x[i] + x[i + 1]) * (x[i] * y[i + 1] - x[i + 1] * y[i]), for i = + // 0 to N - 1) / (6 * signedArea) + // c[y] = (Sigma(y[i] + y[i + 1]) * (x[i] * y[i + 1] - x[i + 1] * y[i]), for i = + // 0 to N - 1) / (6 * signedArea) + private static Point2D getPolygonSansHolesCentroid(Polygon polygon) { + int pointCount = polygon.getPointCount(); + double xSum = 0; + double ySum = 0; + double signedArea = 0; + + Point2D current = new Point2D(); + Point2D next = new Point2D(); + for (int i = 0; i < pointCount; i++) { + polygon.getXY(i, current); + polygon.getXY((i + 1) % pointCount, next); + double ladder = current.x * next.y - next.x * current.y; + xSum += (current.x + next.x) * ladder; + ySum += (current.y + next.y) * ladder; + signedArea += ladder / 2; + } + return new Point2D(xSum / (signedArea * 6), ySum / (signedArea * 6)); + } } diff --git a/src/test/java/com/esri/core/geometry/TestOGCCentroid.java b/src/test/java/com/esri/core/geometry/TestOGCCentroid.java index bf183bb9..d9d39adb 100644 --- a/src/test/java/com/esri/core/geometry/TestOGCCentroid.java +++ b/src/test/java/com/esri/core/geometry/TestOGCCentroid.java @@ -28,63 +28,56 @@ import org.junit.Assert; import org.junit.Test; -public class TestOGCCentroid -{ - @Test - public void testPoint() - { - assertCentroid("POINT (1 2)", new Point(1, 2)); - assertEmptyCentroid("POINT EMPTY"); - } +public class TestOGCCentroid { + @Test + public void testPoint() { + assertCentroid("POINT (1 2)", new Point(1, 2)); + assertEmptyCentroid("POINT EMPTY"); + } - @Test - public void testLineString() - { - assertCentroid("LINESTRING (1 1, 2 2, 3 3)", new Point(2, 2)); - assertEmptyCentroid("LINESTRING EMPTY"); - } + @Test + public void testLineString() { + assertCentroid("LINESTRING (1 1, 2 2, 3 3)", new Point(2, 2)); + assertEmptyCentroid("LINESTRING EMPTY"); + } - @Test - public void testPolygon() - { - assertCentroid("POLYGON ((1 1, 1 4, 4 4, 4 1))'", new Point(2.5, 2.5)); - assertCentroid("POLYGON ((1 1, 5 1, 3 4))", new Point(3, 2)); - assertCentroid("POLYGON ((0 0, 0 5, 5 5, 5 0, 0 0), (1 1, 1 2, 2 2, 2 1, 1 1))", new Point(2.5416666666666665, 2.5416666666666665)); - assertEmptyCentroid("POLYGON EMPTY"); - } + @Test + public void testPolygon() { + assertCentroid("POLYGON ((1 1, 1 4, 4 4, 4 1))'", new Point(2.5, 2.5)); + assertCentroid("POLYGON ((1 1, 5 1, 3 4))", new Point(3, 2)); + assertCentroid("POLYGON ((0 0, 0 5, 5 5, 5 0, 0 0), (1 1, 1 2, 2 2, 2 1, 1 1))", + new Point(2.5416666666666665, 2.5416666666666665)); + assertEmptyCentroid("POLYGON EMPTY"); + } - @Test - public void testMultiPoint() - { - assertCentroid("MULTIPOINT (1 2, 2 4, 3 6, 4 8)", new Point(2.5, 5)); - assertEmptyCentroid("MULTIPOINT EMPTY"); - } + @Test + public void testMultiPoint() { + assertCentroid("MULTIPOINT (1 2, 2 4, 3 6, 4 8)", new Point(2.5, 5)); + assertEmptyCentroid("MULTIPOINT EMPTY"); + } - @Test - public void testMultiLineString() - { - assertCentroid("MULTILINESTRING ((1 1, 5 1), (2 4, 4 4))')))", new Point(3, 2)); - assertEmptyCentroid("MULTILINESTRING EMPTY"); - } + @Test + public void testMultiLineString() { + assertCentroid("MULTILINESTRING ((1 1, 5 1), (2 4, 4 4))')))", new Point(3, 2)); + assertEmptyCentroid("MULTILINESTRING EMPTY"); + } - @Test - public void testMultiPolygon() - { - assertCentroid("MULTIPOLYGON (((1 1, 1 3, 3 3, 3 1)), ((2 4, 2 6, 6 6, 6 4)))", new Point (3.3333333333333335,4)); - assertEmptyCentroid("MULTIPOLYGON EMPTY"); - } + @Test + public void testMultiPolygon() { + assertCentroid("MULTIPOLYGON (((1 1, 1 3, 3 3, 3 1)), ((2 4, 2 6, 6 6, 6 4)))", + new Point(3.3333333333333335, 4)); + assertEmptyCentroid("MULTIPOLYGON EMPTY"); + } - private static void assertCentroid(String wkt, Point expectedCentroid) - { - OGCGeometry geometry = OGCGeometry.fromText(wkt); - OGCGeometry centroid = geometry.centroid(); - Assert.assertEquals(centroid, new OGCPoint(expectedCentroid, geometry.getEsriSpatialReference())); - } + private static void assertCentroid(String wkt, Point expectedCentroid) { + OGCGeometry geometry = OGCGeometry.fromText(wkt); + OGCGeometry centroid = geometry.centroid(); + Assert.assertEquals(centroid, new OGCPoint(expectedCentroid, geometry.getEsriSpatialReference())); + } - private static void assertEmptyCentroid(String wkt) - { - OGCGeometry geometry = OGCGeometry.fromText(wkt); - OGCGeometry centroid = geometry.centroid(); - Assert.assertEquals(centroid, new OGCPoint(new Point(), geometry.getEsriSpatialReference())); - } + private static void assertEmptyCentroid(String wkt) { + OGCGeometry geometry = OGCGeometry.fromText(wkt); + OGCGeometry centroid = geometry.centroid(); + Assert.assertEquals(centroid, new OGCPoint(new Point(), geometry.getEsriSpatialReference())); + } } From 8e390b90639bf690550012fe7f4b412f7f7795bd Mon Sep 17 00:00:00 2001 From: Sergey Tolstov Date: Sat, 3 Aug 2019 04:18:19 -0700 Subject: [PATCH 185/196] centroid fixes (#227) --- .gitignore | 2 + .travis.yml | 9 +- pom.xml | 6 +- .../geometry/OperatorCentroid2DLocal.java | 144 +++++------ .../com/esri/core/geometry/TestCentroid.java | 226 +++++++++++------- .../esri/core/geometry/TestOGCCentroid.java | 25 +- 6 files changed, 238 insertions(+), 174 deletions(-) diff --git a/.gitignore b/.gitignore index 4f5d1e04..f2a3f602 100644 --- a/.gitignore +++ b/.gitignore @@ -38,3 +38,5 @@ Thumbs.db target/* /bin/ /target/ + +.metadata/ diff --git a/.travis.yml b/.travis.yml index d1fa4fad..2113e3d7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,8 +1,9 @@ language: java jdk: - - openjdk7 - - openjdk8 - - oraclejdk8 - - oraclejdk9 + - openjdk9 + - openjdk10 + - openjdk14 +# - oraclejdk9 + - oraclejdk11 notifications: email: false diff --git a/pom.xml b/pom.xml index eec05faa..09ac3899 100755 --- a/pom.xml +++ b/pom.xml @@ -94,8 +94,8 @@ UTF-8 - 1.6 - 1.6 + 1.7 + 1.7 2.9.6 @@ -194,7 +194,7 @@ org.sonatype.plugins nexus-staging-maven-plugin - 1.6.2 + 1.6.8 true ossrh diff --git a/src/main/java/com/esri/core/geometry/OperatorCentroid2DLocal.java b/src/main/java/com/esri/core/geometry/OperatorCentroid2DLocal.java index 6a6a7394..ce7079b8 100644 --- a/src/main/java/com/esri/core/geometry/OperatorCentroid2DLocal.java +++ b/src/main/java/com/esri/core/geometry/OperatorCentroid2DLocal.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2017 Esri + Copyright 1995-2019 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -23,8 +23,6 @@ */ package com.esri.core.geometry; -import static java.lang.Math.sqrt; - public class OperatorCentroid2DLocal extends OperatorCentroid2D { @Override public Point2D execute(Geometry geometry, ProgressTracker progressTracker) { @@ -56,7 +54,7 @@ private static Point2D computeLineCentroid(Line line) { } // Points centroid is arithmetic mean of the input points - private static Point2D computePointsCentroid(MultiPoint multiPoint) { + private static Point2D computePointsCentroid(MultiVertexGeometry multiPoint) { double xSum = 0; double ySum = 0; int pointCount = multiPoint.getPointCount(); @@ -71,89 +69,75 @@ private static Point2D computePointsCentroid(MultiPoint multiPoint) { // Lines centroid is weighted mean of each line segment, weight in terms of line // length - private static Point2D computePolylineCentroid(Polyline polyline) { - double xSum = 0; - double ySum = 0; - double weightSum = 0; - - Point2D startPoint = new Point2D(); - Point2D endPoint = new Point2D(); - for (int i = 0; i < polyline.getPathCount(); i++) { - polyline.getXY(polyline.getPathStart(i), startPoint); - polyline.getXY(polyline.getPathEnd(i) - 1, endPoint); - double dx = endPoint.x - startPoint.x; - double dy = endPoint.y - startPoint.y; - double length = sqrt(dx * dx + dy * dy); - weightSum += length; - xSum += (startPoint.x + endPoint.x) * length / 2; - ySum += (startPoint.y + endPoint.y) * length / 2; - } - return new Point2D(xSum / weightSum, ySum / weightSum); - } - - // Polygon centroid: area weighted average of centroids in case of holes - private static Point2D computePolygonCentroid(Polygon polygon) { - int pathCount = polygon.getPathCount(); - - if (pathCount == 1) { - return getPolygonSansHolesCentroid(polygon); + private static Point2D computePolylineCentroid(MultiPath polyline) { + double totalLength = polyline.calculateLength2D(); + if (totalLength == 0) { + return computePointsCentroid(polyline); } - - double xSum = 0; - double ySum = 0; - double areaSum = 0; - - for (int i = 0; i < pathCount; i++) { - int startIndex = polygon.getPathStart(i); - int endIndex = polygon.getPathEnd(i); - - Polygon sansHoles = getSubPolygon(polygon, startIndex, endIndex); - - Point2D centroid = getPolygonSansHolesCentroid(sansHoles); - double area = sansHoles.calculateArea2D(); - - xSum += centroid.x * area; - ySum += centroid.y * area; - areaSum += area; + + MathUtils.KahanSummator xSum = new MathUtils.KahanSummator(0); + MathUtils.KahanSummator ySum = new MathUtils.KahanSummator(0); + Point2D point = new Point2D(); + SegmentIterator iter = polyline.querySegmentIterator(); + while (iter.nextPath()) { + while (iter.hasNextSegment()) { + Segment seg = iter.nextSegment(); + seg.getCoord2D(0.5, point); + double length = seg.calculateLength2D(); + point.scale(length); + xSum.add(point.x); + ySum.add(point.y); + } } - - return new Point2D(xSum / areaSum, ySum / areaSum); + + return new Point2D(xSum.getResult() / totalLength, ySum.getResult() / totalLength); } - private static Polygon getSubPolygon(Polygon polygon, int startIndex, int endIndex) { - Polyline boundary = new Polyline(); - boundary.startPath(polygon.getPoint(startIndex)); - for (int i = startIndex + 1; i < endIndex; i++) { - Point current = polygon.getPoint(i); - boundary.lineTo(current); + // Polygon centroid: area weighted average of centroids + private static Point2D computePolygonCentroid(Polygon polygon) { + double totalArea = polygon.calculateArea2D(); + if (totalArea == 0) + { + return computePolylineCentroid(polygon); } - - final Polygon newPolygon = new Polygon(); - newPolygon.add(boundary, false); - return newPolygon; - } - - // Polygon sans holes centroid: - // c[x] = (Sigma(x[i] + x[i + 1]) * (x[i] * y[i + 1] - x[i + 1] * y[i]), for i = - // 0 to N - 1) / (6 * signedArea) - // c[y] = (Sigma(y[i] + y[i + 1]) * (x[i] * y[i + 1] - x[i + 1] * y[i]), for i = - // 0 to N - 1) / (6 * signedArea) - private static Point2D getPolygonSansHolesCentroid(Polygon polygon) { - int pointCount = polygon.getPointCount(); - double xSum = 0; - double ySum = 0; - double signedArea = 0; - + + MathUtils.KahanSummator xSum = new MathUtils.KahanSummator(0); + MathUtils.KahanSummator ySum = new MathUtils.KahanSummator(0); + Point2D startPoint = new Point2D(); Point2D current = new Point2D(); Point2D next = new Point2D(); - for (int i = 0; i < pointCount; i++) { - polygon.getXY(i, current); - polygon.getXY((i + 1) % pointCount, next); - double ladder = current.x * next.y - next.x * current.y; - xSum += (current.x + next.x) * ladder; - ySum += (current.y + next.y) * ladder; - signedArea += ladder / 2; + Point2D origin = polygon.getXY(0); + + for (int ipath = 0, npaths = polygon.getPathCount(); ipath < npaths; ipath++) { + int startIndex = polygon.getPathStart(ipath); + int endIndex = polygon.getPathEnd(ipath); + int pointCount = endIndex - startIndex; + if (pointCount < 3) { + continue; + } + + polygon.getXY(startIndex, startPoint); + polygon.getXY(startIndex + 1, current); + current.sub(startPoint); + for (int i = startIndex + 2, n = endIndex; i < n; i++) { + polygon.getXY(i, next); + next.sub(startPoint); + double twiceTriangleArea = next.x * current.y - current.x * next.y; + xSum.add((current.x + next.x) * twiceTriangleArea); + ySum.add((current.y + next.y) * twiceTriangleArea); + current.setCoords(next); + } + + startPoint.sub(origin); + startPoint.scale(6.0 * polygon.calculateRingArea2D(ipath)); + //add weighted startPoint + xSum.add(startPoint.x); + ySum.add(startPoint.y); } - return new Point2D(xSum / (signedArea * 6), ySum / (signedArea * 6)); + + totalArea *= 6.0; + Point2D res = new Point2D(xSum.getResult() / totalArea, ySum.getResult() / totalArea); + res.add(origin); + return res; } } diff --git a/src/test/java/com/esri/core/geometry/TestCentroid.java b/src/test/java/com/esri/core/geometry/TestCentroid.java index 58c430fb..3065ec9e 100644 --- a/src/test/java/com/esri/core/geometry/TestCentroid.java +++ b/src/test/java/com/esri/core/geometry/TestCentroid.java @@ -26,90 +26,144 @@ import org.junit.Assert; import org.junit.Test; -public class TestCentroid -{ - @Test - public void testPoint() - { - assertCentroid(new Point(1, 2), new Point2D(1, 2)); - } - - @Test - public void testLine() - { - assertCentroid(new Line(0, 0, 10, 20), new Point2D(5, 10)); - } - - @Test - public void testEnvelope() - { - assertCentroid(new Envelope(1, 2, 3,4), new Point2D(2, 3)); - assertCentroid(new Envelope(), null); - } - - @Test - public void testMultiPoint() - { - MultiPoint multiPoint = new MultiPoint(); - multiPoint.add(0, 0); - multiPoint.add(1, 2); - multiPoint.add(3, 1); - multiPoint.add(0, 1); - - assertCentroid(multiPoint, new Point2D(1, 1)); - assertCentroid(new MultiPoint(), null); - } - - @Test - public void testPolyline() - { - Polyline polyline = new Polyline(); - polyline.startPath(0, 0); - polyline.lineTo(1, 2); - polyline.lineTo(3, 4); - assertCentroid(polyline, new Point2D(1.5, 2)); - - polyline.startPath(1, -1); - polyline.lineTo(2, 0); - polyline.lineTo(10, 1); - assertCentroid(polyline, new Point2D(4.093485180902371 , 0.7032574095488145)); - - assertCentroid(new Polyline(), null); - } - - @Test - public void testPolygon() - { - Polygon polygon = new Polygon(); - polygon.startPath(0, 0); - polygon.lineTo(1, 2); - polygon.lineTo(3, 4); - polygon.lineTo(5, 2); - polygon.lineTo(0, 0); - assertCentroid(polygon, new Point2D(2.5, 2)); - - // add a hole - polygon.startPath(2, 2); - polygon.lineTo(2.3, 2); - polygon.lineTo(2.3, 2.4); - polygon.lineTo(2, 2); - assertCentroid(polygon, new Point2D(2.5022670025188916 , 1.9989924433249369)); - - // add another polygon - polygon.startPath(-1, -1); - polygon.lineTo(3, -1); - polygon.lineTo(0.5, -2); - polygon.lineTo(-1, -1); - assertCentroid(polygon, new Point2D(2.166465459423206 , 1.3285043594902748)); - - assertCentroid(new Polygon(), null); - } - - private static void assertCentroid(Geometry geometry, Point2D expectedCentroid) - { - OperatorCentroid2D operator = (OperatorCentroid2D) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.Centroid2D); - - Point2D actualCentroid = operator.execute(geometry, null); - Assert.assertEquals(expectedCentroid, actualCentroid); - } +public class TestCentroid { + @Test + public void testPoint() { + assertCentroid(new Point(1, 2), new Point2D(1, 2)); + } + + @Test + public void testLine() { + assertCentroid(new Line(0, 0, 10, 20), new Point2D(5, 10)); + } + + @Test + public void testEnvelope() { + assertCentroid(new Envelope(1, 2, 3, 4), new Point2D(2, 3)); + assertCentroidEmpty(new Envelope()); + } + + @Test + public void testMultiPoint() { + MultiPoint multiPoint = new MultiPoint(); + multiPoint.add(0, 0); + multiPoint.add(1, 2); + multiPoint.add(3, 1); + multiPoint.add(0, 1); + + assertCentroid(multiPoint, new Point2D(1, 1)); + assertCentroidEmpty(new MultiPoint()); + } + + @Test + public void testPolyline() { + Polyline polyline = new Polyline(); + polyline.startPath(0, 0); + polyline.lineTo(1, 2); + polyline.lineTo(3, 4); + assertCentroid(polyline, new Point2D(1.3377223398316207, 2.1169631197754946)); + + polyline.startPath(1, -1); + polyline.lineTo(2, 0); + polyline.lineTo(10, 1); + assertCentroid(polyline, new Point2D(3.93851092460519, 0.9659173294165462)); + + assertCentroidEmpty(new Polyline()); + } + + @Test + public void testPolygon() { + Polygon polygon = new Polygon(); + polygon.startPath(0, 0); + polygon.lineTo(1, 2); + polygon.lineTo(3, 4); + polygon.lineTo(5, 2); + polygon.lineTo(0, 0); + assertCentroid(polygon, new Point2D(2.5, 2)); + + // add a hole + polygon.startPath(2, 2); + polygon.lineTo(2.3, 2); + polygon.lineTo(2.3, 2.4); + polygon.lineTo(2, 2); + assertCentroid(polygon, new Point2D(2.5022670025188916, 1.9989924433249369)); + + // add another polygon + polygon.startPath(-1, -1); + polygon.lineTo(3, -1); + polygon.lineTo(0.5, -2); + polygon.lineTo(-1, -1); + assertCentroid(polygon, new Point2D(2.166465459423206, 1.3285043594902748)); + + assertCentroidEmpty(new Polygon()); + } + + @Test + public void testSmallPolygon() { + // https://github.com/Esri/geometry-api-java/issues/225 + + Polygon polygon = new Polygon(); + polygon.startPath(153.492818, -28.13729); + polygon.lineTo(153.492821, -28.137291); + polygon.lineTo(153.492816, -28.137289); + polygon.lineTo(153.492818, -28.13729); + + assertCentroid(polygon, new Point2D(153.492818333333333, -28.13729)); + } + + @Test + public void testZeroAreaPolygon() { + Polygon polygon = new Polygon(); + polygon.startPath(153, 28); + polygon.lineTo(163, 28); + polygon.lineTo(153, 28); + + Polyline polyline = (Polyline) polygon.getBoundary(); + Point2D expectedCentroid = new Point2D(158, 28); + + assertCentroid(polyline, expectedCentroid); + assertCentroid(polygon, expectedCentroid); + } + + @Test + public void testDegeneratesToPointPolygon() { + Polygon polygon = new Polygon(); + polygon.startPath(-8406364, 560828); + polygon.lineTo(-8406364, 560828); + polygon.lineTo(-8406364, 560828); + polygon.lineTo(-8406364, 560828); + + assertCentroid(polygon, new Point2D(-8406364, 560828)); + } + + @Test + public void testZeroLengthPolyline() { + Polyline polyline = new Polyline(); + polyline.startPath(153, 28); + polyline.lineTo(153, 28); + + assertCentroid(polyline, new Point2D(153, 28)); + } + + @Test + public void testDegeneratesToPointPolyline() { + Polyline polyline = new Polyline(); + polyline.startPath(-8406364, 560828); + polyline.lineTo(-8406364, 560828); + + assertCentroid(polyline, new Point2D(-8406364, 560828)); + } + + private static void assertCentroid(Geometry geometry, Point2D expectedCentroid) { + + Point2D actualCentroid = OperatorCentroid2D.local().execute(geometry, null); + Assert.assertEquals(expectedCentroid.x, actualCentroid.x, 1e-13); + Assert.assertEquals(expectedCentroid.y, actualCentroid.y, 1e-13); + } + + private static void assertCentroidEmpty(Geometry geometry) { + + Point2D actualCentroid = OperatorCentroid2D.local().execute(geometry, null); + Assert.assertTrue(actualCentroid == null); + } } diff --git a/src/test/java/com/esri/core/geometry/TestOGCCentroid.java b/src/test/java/com/esri/core/geometry/TestOGCCentroid.java index d9d39adb..a64c67b5 100644 --- a/src/test/java/com/esri/core/geometry/TestOGCCentroid.java +++ b/src/test/java/com/esri/core/geometry/TestOGCCentroid.java @@ -38,6 +38,10 @@ public void testPoint() { @Test public void testLineString() { assertCentroid("LINESTRING (1 1, 2 2, 3 3)", new Point(2, 2)); + //closed path + assertCentroid("LINESTRING (0 0, 1 0, 1 1, 0 1, 0 0)", new Point(0.5, 0.5)); + //all points coincide + assertCentroid("LINESTRING (0 0, 0 0, 0 0, 0 0, 0 0)", new Point(0.0, 0.0)); assertEmptyCentroid("LINESTRING EMPTY"); } @@ -59,20 +63,39 @@ public void testMultiPoint() { @Test public void testMultiLineString() { assertCentroid("MULTILINESTRING ((1 1, 5 1), (2 4, 4 4))')))", new Point(3, 2)); + assertCentroid("MULTILINESTRING ((1 1, 5 1), (2 4, 3 3, 4 4))')))", new Point(3, 2.0355339059327378)); + assertCentroid("MULTILINESTRING ((0 0, 0 0, 0 0), (1 1, 1 1, 1 1, 1 1))", new Point(0.571428571428571429, 0.571428571428571429)); assertEmptyCentroid("MULTILINESTRING EMPTY"); } @Test public void testMultiPolygon() { + assertCentroid("MULTIPOLYGON (((0 0, 1 0, 1 1, 0 1, 0 0)), ((2 2, 3 2, 3 3, 2 3, 2 2)))", new Point(1.5, 1.5)); + assertCentroid("MULTIPOLYGON (((2 2, 3 2, 3 3, 2 3, 2 2)), ((4 4, 5 4, 5 5, 4 5, 4 4)))", new Point(3.5, 3.5)); assertCentroid("MULTIPOLYGON (((1 1, 1 3, 3 3, 3 1)), ((2 4, 2 6, 6 6, 6 4)))", new Point(3.3333333333333335, 4)); + + //hole is same as exterior - compute as polyline + assertCentroid("MULTIPOLYGON (((0 0, 1 0, 1 1, 0 1, 0 0), (0 0, 0 1, 1 1, 1 0, 0 0)))", new Point(0.5, 0.5)); + + //polygon is only vertices - compute as multipoint. Note that the closing vertex of the ring is not counted + assertCentroid("MULTIPOLYGON (((0 0, 0 0, 0 0), (1 1, 1 1, 1 1, 1 1)))", new Point(0.6, 0.6)); + + // test cases from https://github.com/Esri/geometry-api-java/issues/225 + assertCentroid( + "MULTIPOLYGON (((153.492818 -28.13729, 153.492821 -28.137291, 153.492816 -28.137289, 153.492818 -28.13729)))", + new Point(153.49281833333333, -28.13729)); + assertCentroid( + "MULTIPOLYGON (((153.112475 -28.360526, 153.1124759 -28.360527, 153.1124759 -28.360526, 153.112475 -28.360526)))", + new Point(153.1124756, -28.360526333333333)); assertEmptyCentroid("MULTIPOLYGON EMPTY"); } private static void assertCentroid(String wkt, Point expectedCentroid) { OGCGeometry geometry = OGCGeometry.fromText(wkt); OGCGeometry centroid = geometry.centroid(); - Assert.assertEquals(centroid, new OGCPoint(expectedCentroid, geometry.getEsriSpatialReference())); + Assert.assertEquals(((OGCPoint)centroid).X(), expectedCentroid.getX(), 1e-13); + Assert.assertEquals(((OGCPoint)centroid).Y(), expectedCentroid.getY(), 1e-13); } private static void assertEmptyCentroid(String wkt) { From 43ece8840f291f6eab8bfd44bbca5480a18d3eae Mon Sep 17 00:00:00 2001 From: Sergey Tolstov Date: Tue, 6 Aug 2019 11:23:36 -0700 Subject: [PATCH 186/196] Remove duplicate interface (#237) --- .../com/esri/core/geometry/DirtyFlags.java | 64 --------------- .../com/esri/core/geometry/EditShape.java | 4 +- .../geometry/MultiVertexGeometryImpl.java | 77 +++++++------------ 3 files changed, 28 insertions(+), 117 deletions(-) delete mode 100644 src/main/java/com/esri/core/geometry/DirtyFlags.java diff --git a/src/main/java/com/esri/core/geometry/DirtyFlags.java b/src/main/java/com/esri/core/geometry/DirtyFlags.java deleted file mode 100644 index 36ccafda..00000000 --- a/src/main/java/com/esri/core/geometry/DirtyFlags.java +++ /dev/null @@ -1,64 +0,0 @@ -/* - Copyright 1995-2015 Esri - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - - For additional information, contact: - Environmental Systems Research Institute, Inc. - Attn: Contracts Dept - 380 New York Street - Redlands, California, USA 92373 - - email: contracts@esri.com - */ -package com.esri.core.geometry; - -interface DirtyFlags { - public static final int dirtyIsKnownSimple = 1; // !<0 when is_weak_simple - // or is_strong_simple flag - // is valid - public static final int isWeakSimple = 2; // ! Date: Tue, 6 Aug 2019 11:24:03 -0700 Subject: [PATCH 187/196] Cleanup unused variables and code and small optimization (#233) --- .../com/esri/core/geometry/Simplificator.java | 410 +++++++----------- 1 file changed, 154 insertions(+), 256 deletions(-) diff --git a/src/main/java/com/esri/core/geometry/Simplificator.java b/src/main/java/com/esri/core/geometry/Simplificator.java index e0401cfa..a5102528 100644 --- a/src/main/java/com/esri/core/geometry/Simplificator.java +++ b/src/main/java/com/esri/core/geometry/Simplificator.java @@ -33,16 +33,16 @@ class Simplificator { private AttributeStreamOfInt32 m_bunchEdgeIndices; // private AttributeStreamOfInt32 m_orphanVertices; - private int m_dbgCounter; private int m_sortedVerticesListIndex; private int m_userIndexSortedIndexToVertex; private int m_userIndexSortedAngleIndexToVertex; private int m_nextVertexToProcess; private int m_firstCoincidentVertex; - private int m_knownSimpleResult; - private boolean m_bWinding; + //private int m_knownSimpleResult; private boolean m_fixSelfTangency; private ProgressTracker m_progressTracker; + private int[] m_ar = null; + private int[] m_br = null; private void _beforeRemoveVertex(int vertex, boolean bChangePathFirst) { int vertexlistIndex = m_shape.getUserIndex(vertex, @@ -91,9 +91,15 @@ private void _beforeRemoveVertex(int vertex, boolean bChangePathFirst) { } } - static class SimplificatorAngleComparer extends + static private class SimplificatorAngleComparer extends AttributeStreamOfInt32.IntComparator { - Simplificator m_parent; + private Simplificator m_parent; + private Point2D pt1 = new Point2D(); + private Point2D pt2 = new Point2D(); + private Point2D pt10 = new Point2D(); + private Point2D pt20 = new Point2D(); + private Point2D v1 = new Point2D(); + private Point2D v2 = new Point2D(); public SimplificatorAngleComparer(Simplificator parent) { m_parent = parent; @@ -101,19 +107,35 @@ public SimplificatorAngleComparer(Simplificator parent) { @Override public int compare(int v1, int v2) { - return m_parent._compareAngles(v1, v2); + return _compareAngles(v1, v2); } + private int _compareAngles(int index1, int index2) { + int vert1 = m_parent.m_bunchEdgeEndPoints.get(index1); + m_parent.m_shape.getXY(vert1, pt1); + int vert2 = m_parent.m_bunchEdgeEndPoints.get(index2); + m_parent.m_shape.getXY(vert2, pt2); + + if (pt1.isEqual(pt2)) + return 0;// overlap case + + int vert10 = m_parent.m_bunchEdgeCenterPoints.get(index1); + m_parent.m_shape.getXY(vert10, pt10); + + int vert20 = m_parent.m_bunchEdgeCenterPoints.get(index2); + m_parent.m_shape.getXY(vert20, pt20); + + v1.sub(pt1, pt10); + v2.sub(pt2, pt20); + int result = Point2D._compareVectors(v1, v2); + return result; + } } private boolean _processBunch() { boolean bModified = false; - int iter = 0; Point2D ptCenter = new Point2D(); while (true) { - m_dbgCounter++;// only for debugging - iter++; - // _ASSERT(iter < 10); if (m_bunchEdgeEndPoints == null) { m_bunchEdgeEndPoints = new AttributeStreamOfInt32(0); m_bunchEdgeCenterPoints = new AttributeStreamOfInt32(0); @@ -129,25 +151,17 @@ private boolean _processBunch() { boolean bFirst = true; while (currentVertex != m_nextVertexToProcess) { int v = m_sortedVertices.getData(currentVertex); - {// debug - Point2D pt = new Point2D(); - m_shape.getXY(v, pt); - double y = pt.x; - } if (bFirst) { m_shape.getXY(v, ptCenter); bFirst = false; } int vertP = m_shape.getPrevVertex(v); int vertN = m_shape.getNextVertex(v); - // _ASSERT(vertP != vertN || m_shape.getPrevVertex(vertN) == v - // && m_shape.getNextVertex(vertP) == v); int id = m_shape.getUserIndex(vertP, m_userIndexSortedAngleIndexToVertex); if (id != 0xdeadbeef)// avoid adding a point twice { - // _ASSERT(id == -1); m_bunchEdgeEndPoints.add(vertP); m_shape.setUserIndex(vertP, m_userIndexSortedAngleIndexToVertex, 0xdeadbeef);// mark @@ -165,7 +179,6 @@ private boolean _processBunch() { m_userIndexSortedAngleIndexToVertex); if (id2 != 0xdeadbeef) // avoid adding a point twice { - // _ASSERT(id2 == -1); m_bunchEdgeEndPoints.add(vertN); m_shape.setUserIndex(vertN, m_userIndexSortedAngleIndexToVertex, 0xdeadbeef);// mark @@ -189,8 +202,6 @@ private boolean _processBunch() { // the edge, connecting the endpoint with the bunch center) m_bunchEdgeIndices.Sort(0, m_bunchEdgeIndices.size(), new SimplificatorAngleComparer(this)); - // SORTDYNAMICARRAYEX(m_bunchEdgeIndices, int, 0, - // m_bunchEdgeIndices.size(), SimplificatorAngleComparer, this); for (int i = 0, n = m_bunchEdgeIndices.size(); i < n; i++) { int indexL = m_bunchEdgeIndices.get(i); @@ -199,11 +210,6 @@ private boolean _processBunch() { m_userIndexSortedAngleIndexToVertex, i);// rember the // sort by angle // order - {// debug - Point2D pt = new Point2D(); - m_shape.getXY(vertex, pt); - double y = pt.x; - } } boolean bCrossOverResolved = _processCrossOvers(ptCenter);// see of @@ -254,7 +260,6 @@ private boolean _processCrossOvers(Point2D ptCenter) { int vertexB1 = m_bunchEdgeEndPoints.get(edgeindex1); int vertexB2 = m_bunchEdgeEndPoints.get(edgeindex2); - // _ASSERT(vertexB2 != vertexB1); int vertexA1 = m_shape.getNextVertex(vertexB1); if (!m_shape.isEqualXY(vertexA1, ptCenter)) @@ -263,9 +268,6 @@ private boolean _processCrossOvers(Point2D ptCenter) { if (!m_shape.isEqualXY(vertexA2, ptCenter)) vertexA2 = m_shape.getPrevVertex(vertexB2); - // _ASSERT(m_shape.isEqualXY(vertexA1, vertexA2)); - // _ASSERT(m_shape.isEqualXY(vertexA1, ptCenter)); - boolean bDirection1 = _getDirection(vertexA1, vertexB1); boolean bDirection2 = _getDirection(vertexA2, vertexB2); int vertexC1 = bDirection1 ? m_shape.getPrevVertex(vertexA1) @@ -331,9 +333,6 @@ else if (_removeSpike(vertexC2)) if (!m_shape.isEqualXY(vertexA2, ptCenter)) vertexA2 = m_shape.getPrevVertex(vertexB2); - // _ASSERT(m_shape.isEqualXY(vertexA1, vertexA2)); - // _ASSERT(m_shape.isEqualXY(vertexA1, ptCenter)); - boolean bDirection1 = _getDirection(vertexA1, vertexB1); boolean bDirection2 = _getDirection(vertexA2, vertexB2); int vertexC1 = bDirection1 ? m_shape.getPrevVertex(vertexA1) @@ -355,9 +354,11 @@ else if (_removeSpike(vertexC2)) return bFound; } - static class SimplificatorVertexComparer extends + static private class SimplificatorVertexComparer extends AttributeStreamOfInt32.IntComparator { - Simplificator m_parent; + private Simplificator m_parent; + private Point2D pt1 = new Point2D(); + private Point2D pt2 = new Point2D(); SimplificatorVertexComparer(Simplificator parent) { m_parent = parent; @@ -365,9 +366,21 @@ static class SimplificatorVertexComparer extends @Override public int compare(int v1, int v2) { - return m_parent._compareVerticesSimple(v1, v2); + return _compareVerticesSimple(v1, v2); } + private int _compareVerticesSimple(int v1, int v2) { + m_parent.m_shape.getXY(v1, pt1); + m_parent.m_shape.getXY(v2, pt2); + int res = pt1.compare(pt2); + if (res == 0) {// sort equal vertices by the path ID + int i1 = m_parent.m_shape.getPathFromVertex(v1); + int i2 = m_parent.m_shape.getPathFromVertex(v2); + res = i1 < i2 ? -1 : (i1 == i2 ? 0 : 1); + } + + return res; + } } private boolean _simplify() { @@ -381,8 +394,6 @@ private boolean _simplify() { assert (m_shape.getFillRule(m_geometry) == Polygon.FillRule.enumFillRuleOddEven); } boolean bChanged = false; - boolean bNeedWindingRepeat = true; - boolean bWinding = false; m_userIndexSortedIndexToVertex = -1; m_userIndexSortedAngleIndexToVertex = -1; @@ -406,8 +417,6 @@ private boolean _simplify() { // Sort verticesSorter.Sort(0, pointCount, new SimplificatorVertexComparer(this)); - // SORTDYNAMICARRAYEX(verticesSorter, int, 0, pointCount, - // SimplificatorVertexComparer, this); // Copy sorted vertices to the m_sortedVertices list. Make a mapping // from the edit shape vertices to the sorted vertices. @@ -424,11 +433,6 @@ private boolean _simplify() { m_sortedVerticesListIndex = m_sortedVertices.createList(0); for (int i = 0; i < pointCount; i++) { int vertex = verticesSorter.get(i); - {// debug - Point2D pt = new Point2D(); - m_shape.getXY(vertex, pt);// for debugging - double y = pt.x; - } int vertexlistIndex = m_sortedVertices.addElement( m_sortedVerticesListIndex, vertex); m_shape.setUserIndex(vertex, m_userIndexSortedIndexToVertex, @@ -452,120 +456,100 @@ private boolean _simplify() { if (_cleanupSpikes())// cleanup any spikes on the polygon. bChanged = true; - // External iteration loop for the simplificator. - // ST. I am not sure if it actually needs this loop. TODO: figure this - // out. - while (bNeedWindingRepeat) { - bNeedWindingRepeat = false; + // Simplify polygon + int iRepeatNum = 0; + boolean bNeedRepeat = false; - int max_iter = m_shape.getPointCount(m_geometry) + 10 > 30 ? 1000 - : (m_shape.getPointCount(m_geometry) + 10) - * (m_shape.getPointCount(m_geometry) + 10); - - // Simplify polygon - int iRepeatNum = 0; - boolean bNeedRepeat = false; - - // Internal iteration loop for the simplificator. - // ST. I am not sure if it actually needs this loop. TODO: figure - // this out. - do// while (bNeedRepeat); - { - bNeedRepeat = false; - - boolean bVertexRecheck = false; - m_firstCoincidentVertex = -1; - int coincidentCount = 0; - Point2D ptFirst = new Point2D(); - Point2D pt = new Point2D(); - // Main loop of the simplificator. Go through the vertices and - // for those that have same coordinates, - for (int vlistindex = m_sortedVertices - .getFirst(m_sortedVerticesListIndex); vlistindex != IndexMultiDCList - .nullNode();) { - int vertex = m_sortedVertices.getData(vlistindex); - {// debug - // Point2D pt = new Point2D(); - m_shape.getXY(vertex, pt); - double d = pt.x; - } - - if (m_firstCoincidentVertex != -1) { - // Point2D pt = new Point2D(); - m_shape.getXY(vertex, pt); - if (ptFirst.isEqual(pt)) { - coincidentCount++; - } else { - ptFirst.setCoords(pt); - m_nextVertexToProcess = vlistindex;// we remeber the - // next index in - // the member - // variable to - // allow it to - // be updated if - // a vertex is - // removed - // inside of the - // _ProcessBunch. - if (coincidentCount > 0) { - boolean result = _processBunch();// process a - // bunch of - // coinciding - // vertices - if (result) {// something has changed. - // Note that ProcessBunch may - // change m_nextVertexToProcess - // and m_firstCoincidentVertex. - bNeedRepeat = true; - if (m_nextVertexToProcess != IndexMultiDCList - .nullNode()) { - int v = m_sortedVertices - .getData(m_nextVertexToProcess); - m_shape.getXY(v, ptFirst); - } + // Internal iteration loop for the simplificator. + // ST. I am not sure if it actually needs this loop. TODO: figure + // this out. + do// while (bNeedRepeat); + { + bNeedRepeat = false; + + m_firstCoincidentVertex = -1; + int coincidentCount = 0; + Point2D ptFirst = new Point2D(); + Point2D pt = new Point2D(); + // Main loop of the simplificator. Go through the vertices and + // for those that have same coordinates, + for (int vlistindex = m_sortedVertices + .getFirst(m_sortedVerticesListIndex); vlistindex != IndexMultiDCList + .nullNode();) { + int vertex = m_sortedVertices.getData(vlistindex); + + if (m_firstCoincidentVertex != -1) { + // Point2D pt = new Point2D(); + m_shape.getXY(vertex, pt); + if (ptFirst.isEqual(pt)) { + coincidentCount++; + } else { + ptFirst.setCoords(pt); + m_nextVertexToProcess = vlistindex;// we remeber the + // next index in + // the member + // variable to + // allow it to + // be updated if + // a vertex is + // removed + // inside of the + // _ProcessBunch. + if (coincidentCount > 0) { + boolean result = _processBunch();// process a + // bunch of + // coinciding + // vertices + if (result) {// something has changed. + // Note that ProcessBunch may + // change m_nextVertexToProcess + // and m_firstCoincidentVertex. + bNeedRepeat = true; + if (m_nextVertexToProcess != IndexMultiDCList + .nullNode()) { + int v = m_sortedVertices + .getData(m_nextVertexToProcess); + m_shape.getXY(v, ptFirst); } } - - vlistindex = m_nextVertexToProcess; - m_firstCoincidentVertex = vlistindex; - coincidentCount = 0; } - } else { + + vlistindex = m_nextVertexToProcess; m_firstCoincidentVertex = vlistindex; - m_shape.getXY(m_sortedVertices.getData(vlistindex), - ptFirst); coincidentCount = 0; } - - if (vlistindex != -1)//vlistindex can be set to -1 after ProcessBunch call above - vlistindex = m_sortedVertices.getNext(vlistindex); - } - - m_nextVertexToProcess = -1; - - if (coincidentCount > 0) { - boolean result = _processBunch(); - if (result) - bNeedRepeat = true; + } else { + m_firstCoincidentVertex = vlistindex; + m_shape.getXY(m_sortedVertices.getData(vlistindex), + ptFirst); + coincidentCount = 0; } - if (iRepeatNum++ > 10) { - throw GeometryException.GeometryInternalError(); - } + if (vlistindex != -1)//vlistindex can be set to -1 after ProcessBunch call above + vlistindex = m_sortedVertices.getNext(vlistindex); + } - if (bNeedRepeat) - _fixOrphanVertices();// fix broken structure of the shape + m_nextVertexToProcess = -1; - if (_cleanupSpikes()) + if (coincidentCount > 0) { + boolean result = _processBunch(); + if (result) bNeedRepeat = true; + } + + if (iRepeatNum++ > 10) { + throw GeometryException.GeometryInternalError(); + } - bNeedWindingRepeat |= bNeedRepeat && bWinding; + if (bNeedRepeat) + _fixOrphanVertices();// fix broken structure of the shape - bChanged |= bNeedRepeat; + if (_cleanupSpikes()) + bNeedRepeat = true; - } while (bNeedRepeat); + bChanged |= bNeedRepeat; - }// while (bNeedWindingRepeat) + } while (bNeedRepeat); // Now process rings. Fix ring orientation and determine rings that need // to be deleted. @@ -581,11 +565,8 @@ private boolean _simplify() { private boolean _getDirection(int vert1, int vert2) { if (m_shape.getNextVertex(vert2) == vert1) { - // _ASSERT(m_shape.getPrevVertex(vert1) == vert2); return false; } else { - // _ASSERT(m_shape.getPrevVertex(vert2) == vert1); - // _ASSERT(m_shape.getNextVertex(vert1) == vert2); return true; } } @@ -593,8 +574,6 @@ private boolean _getDirection(int vert1, int vert2) { private boolean _detectAndResolveCrossOver(boolean bDirection1, boolean bDirection2, int vertexB1, int vertexA1, int vertexC1, int vertexB2, int vertexA2, int vertexC2) { - // _ASSERT(!m_shape.isEqualXY(vertexB1, vertexB2)); - // _ASSERT(!m_shape.isEqualXY(vertexC1, vertexC2)); if (vertexA1 == vertexA2) { _removeAngleSortInfo(vertexB1); @@ -602,17 +581,6 @@ private boolean _detectAndResolveCrossOver(boolean bDirection1, return false; } - // _ASSERT(!m_shape.isEqualXY(vertexB1, vertexC2)); - // _ASSERT(!m_shape.isEqualXY(vertexB1, vertexC1)); - // _ASSERT(!m_shape.isEqualXY(vertexB2, vertexC2)); - // _ASSERT(!m_shape.isEqualXY(vertexB2, vertexC1)); - // _ASSERT(!m_shape.isEqualXY(vertexA1, vertexB1)); - // _ASSERT(!m_shape.isEqualXY(vertexA1, vertexC1)); - // _ASSERT(!m_shape.isEqualXY(vertexA2, vertexB2)); - // _ASSERT(!m_shape.isEqualXY(vertexA2, vertexC2)); - - // _ASSERT(m_shape.isEqualXY(vertexA1, vertexA2)); - // get indices of the vertices for the angle sort. int iB1 = m_shape.getUserIndex(vertexB1, m_userIndexSortedAngleIndexToVertex); @@ -622,44 +590,42 @@ private boolean _detectAndResolveCrossOver(boolean bDirection1, m_userIndexSortedAngleIndexToVertex); int iC2 = m_shape.getUserIndex(vertexC2, m_userIndexSortedAngleIndexToVertex); - // _ASSERT(iB1 >= 0); - // _ASSERT(iC1 >= 0); - // _ASSERT(iB2 >= 0); - // _ASSERT(iC2 >= 0); // Sort the indices to restore the angle-sort order - int[] ar = new int[8]; - int[] br = new int[4]; - - ar[0] = 0; - br[0] = iB1; - ar[1] = 0; - br[1] = iC1; - ar[2] = 1; - br[2] = iB2; - ar[3] = 1; - br[3] = iC2; + + if (m_ar == null) { + m_ar = new int[8]; + m_br = new int[4]; + } + m_ar[0] = 0; + m_br[0] = iB1; + m_ar[1] = 0; + m_br[1] = iC1; + m_ar[2] = 1; + m_br[2] = iB2; + m_ar[3] = 1; + m_br[3] = iC2; for (int j = 1; j < 4; j++)// insertion sort { - int key = br[j]; - int data = ar[j]; + int key = m_br[j]; + int data = m_ar[j]; int i = j - 1; - while (i >= 0 && br[i] > key) { - br[i + 1] = br[i]; - ar[i + 1] = ar[i]; + while (i >= 0 && m_br[i] > key) { + m_br[i + 1] = m_br[i]; + m_ar[i + 1] = m_ar[i]; i--; } - br[i + 1] = key; - ar[i + 1] = data; + m_br[i + 1] = key; + m_ar[i + 1] = data; } int detector = 0; - if (ar[0] != 0) + if (m_ar[0] != 0) detector |= 1; - if (ar[1] != 0) + if (m_ar[1] != 0) detector |= 2; - if (ar[2] != 0) + if (m_ar[2] != 0) detector |= 4; - if (ar[3] != 0) + if (m_ar[3] != 0) detector |= 8; if (detector != 5 && detector != 10)// not an overlap return false; @@ -701,19 +667,8 @@ private boolean _detectAndResolveCrossOver(boolean bDirection1, private void _resolveOverlap(boolean bDirection1, boolean bDirection2, int vertexA1, int vertexB1, int vertexA2, int vertexB2) { - if (m_bWinding) { - _resolveOverlapWinding(bDirection1, bDirection2, vertexA1, - vertexB1, vertexA2, vertexB2); - } else { - _resolveOverlapOddEven(bDirection1, bDirection2, vertexA1, - vertexB1, vertexA2, vertexB2); - } - } - - private void _resolveOverlapWinding(boolean bDirection1, - boolean bDirection2, int vertexA1, int vertexB1, int vertexA2, - int vertexB2) { - throw new GeometryException("not implemented."); + _resolveOverlapOddEven(bDirection1, bDirection2, vertexA1, + vertexB1, vertexA2, vertexB2); } private void _resolveOverlapOddEven(boolean bDirection1, @@ -721,8 +676,6 @@ private void _resolveOverlapOddEven(boolean bDirection1, int vertexB2) { if (bDirection1 != bDirection2) { if (bDirection1) { - // _ASSERT(m_shape.getNextVertex(vertexA1) == vertexB1); - // _ASSERT(m_shape.getNextVertex(vertexB2) == vertexA2); m_shape.setNextVertex_(vertexA1, vertexA2); // B1< B2 m_shape.setPrevVertex_(vertexA2, vertexA1); // | | m_shape.setNextVertex_(vertexB2, vertexB1); // | | @@ -753,15 +706,6 @@ private void _resolveOverlapOddEven(boolean bDirection1, } } else// bDirection1 == bDirection2 { - if (!bDirection1) { - // _ASSERT(m_shape.getNextVertex(vertexB1) == vertexA1); - // _ASSERT(m_shape.getNextVertex(vertexB2) == vertexA2); - } else { - // _ASSERT(m_shape.getNextVertex(vertexA1) == vertexB1); - // _ASSERT(m_shape.getNextVertex(vertexA2) == vertexB2); - } - - // if (m_shape._RingParentageCheckInternal(vertexA1, vertexA2)) { int a1 = bDirection1 ? vertexA1 : vertexB1; int a2 = bDirection2 ? vertexA2 : vertexB2; @@ -861,7 +805,6 @@ private boolean _removeSpike(int vertexIn) { // m_shape.dbgVerifyIntegrity(vertex);//debug int vertex = vertexIn; - // _ASSERT(m_shape.isEqualXY(m_shape.getNextVertex(vertex), // m_shape.getPrevVertex(vertex))); boolean bFound = false; while (true) { @@ -984,61 +927,16 @@ private void _removeAngleSortInfo(int vertex) { } protected Simplificator() { - m_dbgCounter = 0; } public static boolean execute(EditShape shape, int geometry, int knownSimpleResult, boolean fixSelfTangency, ProgressTracker progressTracker) { Simplificator simplificator = new Simplificator(); simplificator.m_shape = shape; - // simplificator.m_bWinding = bWinding; simplificator.m_geometry = geometry; - simplificator.m_knownSimpleResult = knownSimpleResult; + //simplificator.m_knownSimpleResult = knownSimpleResult; simplificator.m_fixSelfTangency = fixSelfTangency; simplificator.m_progressTracker = progressTracker; return simplificator._simplify(); } - - int _compareVerticesSimple(int v1, int v2) { - Point2D pt1 = new Point2D(); - m_shape.getXY(v1, pt1); - Point2D pt2 = new Point2D(); - m_shape.getXY(v2, pt2); - int res = pt1.compare(pt2); - if (res == 0) {// sort equal vertices by the path ID - int i1 = m_shape.getPathFromVertex(v1); - int i2 = m_shape.getPathFromVertex(v2); - res = i1 < i2 ? -1 : (i1 == i2 ? 0 : 1); - } - - return res; - } - - int _compareAngles(int index1, int index2) { - int vert1 = m_bunchEdgeEndPoints.get(index1); - Point2D pt1 = new Point2D(); - m_shape.getXY(vert1, pt1); - Point2D pt2 = new Point2D(); - int vert2 = m_bunchEdgeEndPoints.get(index2); - m_shape.getXY(vert2, pt2); - - if (pt1.isEqual(pt2)) - return 0;// overlap case - - int vert10 = m_bunchEdgeCenterPoints.get(index1); - Point2D pt10 = new Point2D(); - m_shape.getXY(vert10, pt10); - - int vert20 = m_bunchEdgeCenterPoints.get(index2); - Point2D pt20 = new Point2D(); - m_shape.getXY(vert20, pt20); - // _ASSERT(pt10.isEqual(pt20)); - - Point2D v1 = new Point2D(); - v1.sub(pt1, pt10); - Point2D v2 = new Point2D(); - v2.sub(pt2, pt20); - int result = Point2D._compareVectors(v1, v2); - return result; - } } From 48b9cbbce57d47cd354d60a8b8ef60288f25bdb4 Mon Sep 17 00:00:00 2001 From: Sergey Tolstov Date: Tue, 6 Aug 2019 11:26:15 -0700 Subject: [PATCH 188/196] Fix unused variables and a typo (#231) --- .../core/geometry/RelationalOperations.java | 21 +++++++------------ 1 file changed, 7 insertions(+), 14 deletions(-) diff --git a/src/main/java/com/esri/core/geometry/RelationalOperations.java b/src/main/java/com/esri/core/geometry/RelationalOperations.java index 0b73561a..abf03372 100644 --- a/src/main/java/com/esri/core/geometry/RelationalOperations.java +++ b/src/main/java/com/esri/core/geometry/RelationalOperations.java @@ -4142,9 +4142,6 @@ private static boolean linearPathIntersectsMultiPoint_( SegmentIteratorImpl segIterA = ((MultiPathImpl) multipathA._getImpl()) .querySegmentIterator(); - boolean bContained = true; - boolean bInteriorHitFound = false; - Envelope2D env_a = new Envelope2D(); Envelope2D env_b = new Envelope2D(); Envelope2D envInter = new Envelope2D(); @@ -4152,10 +4149,6 @@ private static boolean linearPathIntersectsMultiPoint_( multipoint_b.queryEnvelope2D(env_b); env_a.inflate(tolerance, tolerance); - if (!env_a.contains(env_b)) { - bContained = false; - } - env_b.inflate(tolerance, tolerance); envInter.setCoords(env_a); envInter.intersect(env_b); @@ -4169,6 +4162,7 @@ private static boolean linearPathIntersectsMultiPoint_( if (accel != null) { quadTreeA = accel.getQuadTree(); + quadTreePathsA = accel.getQuadTreeForPaths(); if (quadTreeA == null) { qtA = InternalUtils.buildQuadTree( (MultiPathImpl) multipathA._getImpl(), envInter); @@ -4187,7 +4181,6 @@ private static boolean linearPathIntersectsMultiPoint_( qtIterPathsA = quadTreePathsA.getIterator(); Point2D ptB = new Point2D(), closest = new Point2D(); - boolean b_intersects = false; double toleranceSq = tolerance * tolerance; for (int i = 0; i < multipoint_b.getPointCount(); i++) { @@ -5153,9 +5146,9 @@ private static final class OverlapEvent { double m_scalar_a_0; double m_scalar_a_1; int m_ivertex_b; - int m_ipath_b; - double m_scalar_b_0; - double m_scalar_b_1; +// int m_ipath_b; +// double m_scalar_b_0; +// double m_scalar_b_1; static OverlapEvent construct(int ivertex_a, int ipath_a, double scalar_a_0, double scalar_a_1, int ivertex_b, @@ -5166,9 +5159,9 @@ static OverlapEvent construct(int ivertex_a, int ipath_a, overlapEvent.m_scalar_a_0 = scalar_a_0; overlapEvent.m_scalar_a_1 = scalar_a_1; overlapEvent.m_ivertex_b = ivertex_b; - overlapEvent.m_ipath_b = ipath_b; - overlapEvent.m_scalar_b_0 = scalar_b_0; - overlapEvent.m_scalar_b_1 = scalar_b_1; +// overlapEvent.m_ipath_b = ipath_b; +// overlapEvent.m_scalar_b_0 = scalar_b_0; +// overlapEvent.m_scalar_b_1 = scalar_b_1; return overlapEvent; } } From 7812fd39290efdb19202b19f1d2f8924e2c9ecb7 Mon Sep 17 00:00:00 2001 From: Sergey Tolstov Date: Thu, 15 Aug 2019 11:46:37 -0700 Subject: [PATCH 189/196] Don't return null buffer polygon (#243) --- .../java/com/esri/core/geometry/Bufferer.java | 10 +++++++- .../com/esri/core/geometry/TestBuffer.java | 25 +++++++++++++++++++ 2 files changed, 34 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/esri/core/geometry/Bufferer.java b/src/main/java/com/esri/core/geometry/Bufferer.java index 7578a76c..f098e7e9 100755 --- a/src/main/java/com/esri/core/geometry/Bufferer.java +++ b/src/main/java/com/esri/core/geometry/Bufferer.java @@ -576,6 +576,9 @@ private Geometry bufferPolygon_() { generateCircleTemplate_(); m_geometry = simplify.execute(m_geometry, null, false, m_progress_tracker); + if(m_geometry.isEmpty()) { + return m_geometry; + } if (m_distance < 0) { Polygon poly = (Polygon) (m_geometry); @@ -600,7 +603,12 @@ private Geometry bufferPolygon_() { .getInstance().getOperator(Operator.Type.Union)).execute( cursor, m_spatialReference, m_progress_tracker); Geometry result = union_cursor.next(); - return result; + if (result != null) { + return result; + } else { + //never return empty. + return new Polygon(m_geometry.getDescription()); + } } } diff --git a/src/test/java/com/esri/core/geometry/TestBuffer.java b/src/test/java/com/esri/core/geometry/TestBuffer.java index 341751f5..e60145bb 100755 --- a/src/test/java/com/esri/core/geometry/TestBuffer.java +++ b/src/test/java/com/esri/core/geometry/TestBuffer.java @@ -27,6 +27,8 @@ import junit.framework.TestCase; import org.junit.Test; +import com.esri.core.geometry.ogc.OGCGeometry; + public class TestBuffer extends TestCase { @Override protected void setUp() throws Exception { @@ -389,4 +391,27 @@ public void testBufferPolygon() { assertTrue(simplify.isSimpleAsFeature(result, sr, null)); } } + + @Test + public static void testTinyBufferOfPoint() { + { + Geometry result1 = OperatorBuffer.local().execute(new Point(0, 0), SpatialReference.create(4326), 1e-9, null); + assertTrue(result1 != null); + assertTrue(result1.isEmpty()); + Geometry geom1 = OperatorImportFromWkt.local().execute(0, Geometry.Type.Unknown, "POLYGON ((177.0 64.0, 177.0000000001 64.0, 177.0000000001 64.0000000001, 177.0 64.0000000001, 177.0 64.0))", null); + Geometry result2 = OperatorBuffer.local().execute(geom1, SpatialReference.create(4326), 0.01, null); + assertTrue(result2 != null); + assertTrue(result2.isEmpty()); + + } + + { + OGCGeometry p = OGCGeometry.fromText( + "POLYGON ((177.0 64.0, 177.0000000001 64.0, 177.0000000001 64.0000000001, 177.0 64.0000000001, 177.0 64.0))"); + OGCGeometry buffered = p.buffer(0.01); + assertTrue(buffered != null); + } + + + } } From 4205cd15ac9b453b905df9663520e379b59e81f7 Mon Sep 17 00:00:00 2001 From: Sergey Tolstov Date: Mon, 19 Aug 2019 11:33:50 -0700 Subject: [PATCH 190/196] Fix hash calculation in few cases, change Point internals to be more compact (#238) --- .../core/geometry/AttributeStreamOfDbl.java | 2 +- .../java/com/esri/core/geometry/Envelope.java | 44 +-- .../com/esri/core/geometry/Envelope1D.java | 8 +- .../com/esri/core/geometry/Envelope2D.java | 25 +- .../com/esri/core/geometry/Envelope3D.java | 16 + .../java/com/esri/core/geometry/Line.java | 5 + .../geometry/MultiVertexGeometryImpl.java | 7 - .../com/esri/core/geometry/NumberUtils.java | 13 + .../java/com/esri/core/geometry/Point.java | 314 ++++++++---------- .../java/com/esri/core/geometry/Point2D.java | 11 +- .../java/com/esri/core/geometry/Point3D.java | 27 ++ .../java/com/esri/core/geometry/Segment.java | 19 +- .../java/com/esri/core/geometry/SizeOf.java | 2 +- .../com/esri/core/geometry/TestEnvelope.java | 73 ++-- .../com/esri/core/geometry/TestPoint.java | 28 +- 15 files changed, 333 insertions(+), 261 deletions(-) diff --git a/src/main/java/com/esri/core/geometry/AttributeStreamOfDbl.java b/src/main/java/com/esri/core/geometry/AttributeStreamOfDbl.java index 75d45a76..889121d2 100644 --- a/src/main/java/com/esri/core/geometry/AttributeStreamOfDbl.java +++ b/src/main/java/com/esri/core/geometry/AttributeStreamOfDbl.java @@ -352,7 +352,7 @@ public boolean equals(AttributeStreamBase other, int start, int end) { end = size; for (int i = start; i < end; i++) - if (read(i) != _other.read(i)) + if (!NumberUtils.isEqualNonIEEE(read(i), _other.read(i))) return false; return true; diff --git a/src/main/java/com/esri/core/geometry/Envelope.java b/src/main/java/com/esri/core/geometry/Envelope.java index 98c46738..c254df1c 100644 --- a/src/main/java/com/esri/core/geometry/Envelope.java +++ b/src/main/java/com/esri/core/geometry/Envelope.java @@ -70,16 +70,20 @@ public Envelope(Envelope2D env2D) { public Envelope(VertexDescription vd) { if (vd == null) throw new IllegalArgumentException(); + m_description = vd; m_envelope.setEmpty(); + _ensureAttributes(); } public Envelope(VertexDescription vd, Envelope2D env2D) { if (vd == null) throw new IllegalArgumentException(); + m_description = vd; m_envelope.setCoords(env2D); m_envelope.normalize(); + _ensureAttributes(); } /** @@ -331,8 +335,8 @@ void _setFromPoint(Point centerPoint, double width, double height) { } void _setFromPoint(Point centerPoint) { - m_envelope.setCoords(centerPoint.m_attributes[0], - centerPoint.m_attributes[1]); + mergeVertexDescription(centerPoint.getDescription()); + m_envelope.setCoords(centerPoint.getX(), centerPoint.getY()); VertexDescription pointVD = centerPoint.m_description; for (int iattrib = 1, nattrib = pointVD.getAttributeCount(); iattrib < nattrib; iattrib++) { int semantics = pointVD._getSemanticsImpl(iattrib); @@ -610,7 +614,6 @@ int getEndPointOffset(VertexDescription descr, int end_point) { throw new IllegalArgumentException(); int attribute_index = m_description.getAttributeIndex(semantics); - _ensureAttributes(); if (attribute_index >= 0) { return m_attributes[getEndPointOffset(m_description, end_point) + m_description.getPointAttributeOffset_(attribute_index) @@ -645,7 +648,6 @@ void setAttributeAsDblImpl_(int end_point, int semantics, int ordinate, throw new IllegalArgumentException(); addAttribute(semantics); - _ensureAttributes(); int attribute_index = m_description.getAttributeIndex(semantics); m_attributes[getEndPointOffset(m_description, end_point) + m_description.getPointAttributeOffset_(attribute_index) - 2 @@ -655,32 +657,17 @@ void setAttributeAsDblImpl_(int end_point, int semantics, int ordinate, void _ensureAttributes() { _touch(); if (m_attributes == null && m_description.getTotalComponentCount() > 2) { - m_attributes = new double[(m_description.getTotalComponentCount() - 2) * 2]; + int halfLength = m_description.getTotalComponentCount() - 2; + m_attributes = new double[halfLength * 2]; int offset0 = _getEndPointOffset(m_description, 0); int offset1 = _getEndPointOffset(m_description, 1); - - int j = 0; - for (int i = 1, n = m_description.getAttributeCount(); i < n; i++) { - int semantics = m_description.getSemantics(i); - int nords = VertexDescription.getComponentCount(semantics); - double d = VertexDescription.getDefaultValue(semantics); - for (int ord = 0; ord < nords; ord++) - { - m_attributes[offset0 + j] = d; - m_attributes[offset1 + j] = d; - j++; - } - } + System.arraycopy(m_description._getDefaultPointAttributes(), 2, m_attributes, offset0, halfLength); + System.arraycopy(m_description._getDefaultPointAttributes(), 2, m_attributes, offset1, halfLength); } } @Override protected void _assignVertexDescriptionImpl(VertexDescription newDescription) { - if (m_attributes == null) { - m_description = newDescription; - return; - } - if (newDescription.getTotalComponentCount() > 2) { int[] mapping = VertexDescriptionDesignerImpl.mapAttributes(newDescription, m_description); @@ -734,8 +721,6 @@ protected void _assignVertexDescriptionImpl(VertexDescription newDescription) { throw new GeometryException( "This operation was performed on an Empty Geometry."); - // _ASSERT(endPoint == 0 || endPoint == 1); - if (semantics == Semantics.POSITION) { if (endPoint != 0) { return ordinate != 0 ? m_envelope.ymax : m_envelope.xmax; @@ -750,7 +735,6 @@ protected void _assignVertexDescriptionImpl(VertexDescription newDescription) { int attributeIndex = m_description.getAttributeIndex(semantics); if (attributeIndex >= 0) { - _ensureAttributes(); return m_attributes[_getEndPointOffset(m_description, endPoint) + m_description._getPointAttributeOffset(attributeIndex) - 2 + ordinate]; @@ -784,14 +768,10 @@ void _setAttributeAsDbl(int endPoint, int semantics, int ordinate, throw new IndexOutOfBoundsException(); if (!hasAttribute(semantics)) { - if (VertexDescription.isDefaultValue(semantics, value)) - return; - addAttribute(semantics); } int attributeIndex = m_description.getAttributeIndex(semantics); - _ensureAttributes(); m_attributes[_getEndPointOffset(m_description, endPoint) + m_description._getPointAttributeOffset(attributeIndex) - 2 + ordinate] = value; @@ -1015,7 +995,7 @@ public boolean equals(Object _other) { return false; for (int i = 0, n = (m_description.getTotalComponentCount() - 2) * 2; i < n; i++) - if (m_attributes[i] != other.m_attributes[i]) + if (!NumberUtils.isEqualNonIEEE(m_attributes[i], other.m_attributes[i])) return false; return true; @@ -1030,7 +1010,7 @@ public boolean equals(Object _other) { public int hashCode() { int hashCode = m_description.hashCode(); hashCode = NumberUtils.hash(hashCode, m_envelope.hashCode()); - if (!isEmpty() && m_attributes != null) { + if (!isEmpty()) { for (int i = 0, n = (m_description.getTotalComponentCount() - 2) * 2; i < n; i++) { hashCode = NumberUtils.hash(hashCode, m_attributes[i]); } diff --git a/src/main/java/com/esri/core/geometry/Envelope1D.java b/src/main/java/com/esri/core/geometry/Envelope1D.java index c9d0d259..e000ef0a 100644 --- a/src/main/java/com/esri/core/geometry/Envelope1D.java +++ b/src/main/java/com/esri/core/geometry/Envelope1D.java @@ -225,7 +225,13 @@ public boolean equals(Object _other) @Override public int hashCode() { - return NumberUtils.hash(NumberUtils.hash(vmin), vmax); + if (isEmpty()) { + return NumberUtils.hash(NumberUtils.TheNaN); + } + + int hash = NumberUtils.hash(vmin); + hash = NumberUtils.hash(hash, vmax); + return hash; } } diff --git a/src/main/java/com/esri/core/geometry/Envelope2D.java b/src/main/java/com/esri/core/geometry/Envelope2D.java index 79433dd7..5c8bebf5 100644 --- a/src/main/java/com/esri/core/geometry/Envelope2D.java +++ b/src/main/java/com/esri/core/geometry/Envelope2D.java @@ -699,23 +699,14 @@ public boolean equals(Object _other) { @Override public int hashCode() { - - long bits = Double.doubleToLongBits(xmin); - int hc = (int) (bits ^ (bits >>> 32)); - - int hash = NumberUtils.hash(hc); - - bits = Double.doubleToLongBits(xmax); - hc = (int) (bits ^ (bits >>> 32)); - hash = NumberUtils.hash(hash, hc); - - bits = Double.doubleToLongBits(ymin); - hc = (int) (bits ^ (bits >>> 32)); - hash = NumberUtils.hash(hash, hc); - - bits = Double.doubleToLongBits(ymax); - hc = (int) (bits ^ (bits >>> 32)); - hash = NumberUtils.hash(hash, hc); + if (isEmpty()) { + return NumberUtils.hash(NumberUtils.TheNaN); + } + + int hash = NumberUtils.hash(xmin); + hash = NumberUtils.hash(hash, xmax); + hash = NumberUtils.hash(hash, ymin); + hash = NumberUtils.hash(hash, ymax); return hash; } diff --git a/src/main/java/com/esri/core/geometry/Envelope3D.java b/src/main/java/com/esri/core/geometry/Envelope3D.java index 3f64b053..6fa8b522 100644 --- a/src/main/java/com/esri/core/geometry/Envelope3D.java +++ b/src/main/java/com/esri/core/geometry/Envelope3D.java @@ -320,6 +320,22 @@ public boolean equals(Object _other) { return true; } + + @Override + public int hashCode() { + if (isEmpty()) { + return NumberUtils.hash(NumberUtils.TheNaN); + } + + int hash = NumberUtils.hash(xmin); + hash = NumberUtils.hash(hash, xmax); + hash = NumberUtils.hash(hash, ymin); + hash = NumberUtils.hash(hash, ymax); + hash = NumberUtils.hash(hash, zmin); + hash = NumberUtils.hash(hash, zmax); + return hash; + } + public void construct(Envelope1D xinterval, Envelope1D yinterval, Envelope1D zinterval) { if (xinterval.isEmpty() || yinterval.isEmpty()) { diff --git a/src/main/java/com/esri/core/geometry/Line.java b/src/main/java/com/esri/core/geometry/Line.java index 90b08561..ce7b7b23 100644 --- a/src/main/java/com/esri/core/geometry/Line.java +++ b/src/main/java/com/esri/core/geometry/Line.java @@ -548,6 +548,11 @@ public boolean equals(Object other) { return _equalsImpl((Segment)other); } + @Override + public int hashCode() { + return super.hashCode(); + } + boolean equals(Line other) { if (other == this) return true; diff --git a/src/main/java/com/esri/core/geometry/MultiVertexGeometryImpl.java b/src/main/java/com/esri/core/geometry/MultiVertexGeometryImpl.java index 6e847943..e045aa2a 100644 --- a/src/main/java/com/esri/core/geometry/MultiVertexGeometryImpl.java +++ b/src/main/java/com/esri/core/geometry/MultiVertexGeometryImpl.java @@ -168,8 +168,6 @@ public void getPointByVal(int index, Point dst) { Point outPoint = dst; outPoint.assignVertexDescription(m_description); - if (outPoint.isEmpty()) - outPoint._setToDefault(); for (int attributeIndex = 0; attributeIndex < m_description .getAttributeCount(); attributeIndex++) { @@ -933,9 +931,6 @@ void _interpolateTwoVertices(int vertex1, int vertex2, double f, _verifyAllStreams(); outPoint.assignVertexDescription(m_description); - if (outPoint.isEmpty()) - outPoint._setToDefault(); - for (int attributeIndex = 0; attributeIndex < m_description .getAttributeCount(); attributeIndex++) { int semantics = m_description._getSemanticsImpl(attributeIndex); @@ -966,8 +961,6 @@ public Point getPoint(int index) { Point outPoint = new Point(); outPoint.assignVertexDescription(m_description); - if (outPoint.isEmpty()) - outPoint._setToDefault(); for (int attributeIndex = 0; attributeIndex < m_description .getAttributeCount(); attributeIndex++) { diff --git a/src/main/java/com/esri/core/geometry/NumberUtils.java b/src/main/java/com/esri/core/geometry/NumberUtils.java index 4ee00a1e..01b6fd92 100644 --- a/src/main/java/com/esri/core/geometry/NumberUtils.java +++ b/src/main/java/com/esri/core/geometry/NumberUtils.java @@ -136,5 +136,18 @@ static int nextRand(int prevRand) { return (1103515245 * prevRand + 12345) & intMax(); // according to Wiki, // this is gcc's } + + /** + * Returns true if two values are equal (also can compare inf and nan). + */ + static boolean isEqualNonIEEE(double a, double b) { + return a == b || (Double.isNaN(a) && Double.isNaN(b)); + } + /** + * Returns true if two values are equal (also can compare inf and nan). + */ + static boolean isEqualNonIEEE(double a, double b, double tolerance) { + return a == b || Math.abs(a - b) <= tolerance || (Double.isNaN(a) && Double.isNaN(b)); + } } diff --git a/src/main/java/com/esri/core/geometry/Point.java b/src/main/java/com/esri/core/geometry/Point.java index a421400f..cc5b7042 100644 --- a/src/main/java/com/esri/core/geometry/Point.java +++ b/src/main/java/com/esri/core/geometry/Point.java @@ -39,19 +39,24 @@ public class Point extends Geometry implements Serializable { //We are using writeReplace instead. //private static final long serialVersionUID = 2L; - double[] m_attributes; // use doubles to store everything (long are bitcast) + private double m_x; + private double m_y; + private double[] m_attributes; // use doubles to store everything (long are bitcast) /** * Creates an empty 2D point. */ public Point() { m_description = VertexDescriptionDesignerImpl.getDefaultDescriptor2D(); + m_x = NumberUtils.TheNaN; + m_y = NumberUtils.TheNaN; } public Point(VertexDescription vd) { if (vd == null) throw new IllegalArgumentException(); m_description = vd; + _setToDefault(); } /** @@ -68,6 +73,7 @@ public Point(double x, double y) { m_description = VertexDescriptionDesignerImpl.getDefaultDescriptor2D(); setXY(x, y); } + public Point(Point2D pt) { m_description = VertexDescriptionDesignerImpl.getDefaultDescriptor2D(); setXY(pt); @@ -91,19 +97,14 @@ public Point(double x, double y, double z) { Point3D pt = new Point3D(); pt.setCoords(x, y, z); setXYZ(pt); - } /** * Returns XY coordinates of this point. */ public final Point2D getXY() { - if (isEmptyImpl()) - throw new GeometryException( - "This operation should not be performed on an empty geometry."); - Point2D pt = new Point2D(); - pt.setCoords(m_attributes[0], m_attributes[1]); + pt.setCoords(m_x, m_y); return pt; } @@ -111,11 +112,7 @@ public final Point2D getXY() { * Returns XY coordinates of this point. */ public final void getXY(Point2D pt) { - if (isEmptyImpl()) - throw new GeometryException( - "This operation should not be performed on an empty geometry."); - - pt.setCoords(m_attributes[0], m_attributes[1]); + pt.setCoords(m_x, m_y); } /** @@ -131,17 +128,10 @@ public final void setXY(Point2D pt) { * Returns XYZ coordinates of the point. Z will be set to 0 if Z is missing. */ public Point3D getXYZ() { - if (isEmptyImpl()) - throw new GeometryException( - "This operation should not be performed on an empty geometry."); - Point3D pt = new Point3D(); - pt.x = m_attributes[0]; - pt.y = m_attributes[1]; - if (m_description.hasZ()) - pt.z = m_attributes[2]; - else - pt.z = VertexDescription.getDefaultValue(Semantics.Z); + pt.x = m_x; + pt.y = m_y; + pt.z = hasZ() ? m_attributes[0] : VertexDescription.getDefaultValue(VertexDescription.Semantics.Z); return pt; } @@ -154,39 +144,17 @@ public Point3D getXYZ() { */ public void setXYZ(Point3D pt) { _touch(); - boolean bHasZ = hasAttribute(Semantics.Z); - if (!bHasZ && !VertexDescription.isDefaultValue(Semantics.Z, pt.z)) {// add - // Z - // only - // if - // pt.z - // is - // not - // a - // default - // value. - addAttribute(Semantics.Z); - bHasZ = true; - } - - if (m_attributes == null) - _setToDefault(); - - m_attributes[0] = pt.x; - m_attributes[1] = pt.y; - if (bHasZ) - m_attributes[2] = pt.z; + addAttribute(Semantics.Z); + m_x = pt.x; + m_y = pt.y; + m_attributes[0] = pt.z; } /** * Returns the X coordinate of the point. */ public final double getX() { - if (isEmptyImpl()) - throw new GeometryException( - "This operation should not be performed on an empty geometry."); - - return m_attributes[0]; + return m_x; } /** @@ -196,18 +164,14 @@ public final double getX() { * The X coordinate to be set for this point. */ public void setX(double x) { - setAttribute(Semantics.POSITION, 0, x); + m_x = x; } /** * Returns the Y coordinate of this point. */ public final double getY() { - if (isEmptyImpl()) - throw new GeometryException( - "This operation should not be performed on an empty geometry."); - - return m_attributes[1]; + return m_y; } /** @@ -217,14 +181,14 @@ public final double getY() { * The Y coordinate to be set for this point. */ public void setY(double y) { - setAttribute(Semantics.POSITION, 1, y); + m_y = y; } /** * Returns the Z coordinate of this point. */ public double getZ() { - return getAttributeAsDbl(Semantics.Z, 0); + return hasZ() ? m_attributes[0] : VertexDescription.getDefaultValue(VertexDescription.Semantics.Z); } /** @@ -282,10 +246,18 @@ public void setID(int id) { * @return The ordinate as double value. */ public double getAttributeAsDbl(int semantics, int ordinate) { - if (isEmptyImpl()) - throw new GeometryException( - "This operation was performed on an Empty Geometry."); - + if (semantics == VertexDescription.Semantics.POSITION) { + if (ordinate == 0) { + return m_x; + } + else if (ordinate == 1) { + return m_y; + } + else { + throw new IndexOutOfBoundsException(); + } + } + int ncomps = VertexDescription.getComponentCount(semantics); if (ordinate >= ncomps) throw new IndexOutOfBoundsException(); @@ -293,7 +265,7 @@ public double getAttributeAsDbl(int semantics, int ordinate) { int attributeIndex = m_description.getAttributeIndex(semantics); if (attributeIndex >= 0) return m_attributes[m_description - ._getPointAttributeOffset(attributeIndex) + ordinate]; + ._getPointAttributeOffset(attributeIndex) - 2 + ordinate]; else return VertexDescription.getDefaultValue(semantics); } @@ -310,20 +282,7 @@ public double getAttributeAsDbl(int semantics, int ordinate) { * @return The ordinate value truncated to a 32 bit integer value. */ public int getAttributeAsInt(int semantics, int ordinate) { - if (isEmptyImpl()) - throw new GeometryException( - "This operation was performed on an Empty Geometry."); - - int ncomps = VertexDescription.getComponentCount(semantics); - if (ordinate >= ncomps) - throw new IndexOutOfBoundsException(); - - int attributeIndex = m_description.getAttributeIndex(semantics); - if (attributeIndex >= 0) - return (int) m_attributes[m_description - ._getPointAttributeOffset(attributeIndex) + ordinate]; - else - return (int) VertexDescription.getDefaultValue(semantics); + return (int)getAttributeAsDbl(semantics, ordinate); } /** @@ -340,6 +299,19 @@ public int getAttributeAsInt(int semantics, int ordinate) { */ public void setAttribute(int semantics, int ordinate, double value) { _touch(); + if (semantics == VertexDescription.Semantics.POSITION) { + if (ordinate == 0) { + m_x = value; + } + else if (ordinate == 1) { + m_y = value; + } + else { + throw new IndexOutOfBoundsException(); + } + return; + } + int ncomps = VertexDescription.getComponentCount(semantics); if (ncomps < ordinate) throw new IndexOutOfBoundsException(); @@ -350,10 +322,7 @@ public void setAttribute(int semantics, int ordinate, double value) { attributeIndex = m_description.getAttributeIndex(semantics); } - if (m_attributes == null) - _setToDefault(); - - m_attributes[m_description._getPointAttributeOffset(attributeIndex) + m_attributes[m_description._getPointAttributeOffset(attributeIndex) - 2 + ordinate] = value; } @@ -380,62 +349,70 @@ public long estimateMemorySize() @Override public void setEmpty() { _touch(); - if (m_attributes != null) { - m_attributes[0] = NumberUtils.NaN(); - m_attributes[1] = NumberUtils.NaN(); - } + _setToDefault(); } @Override protected void _assignVertexDescriptionImpl(VertexDescription newDescription) { - if (m_attributes == null) { - m_description = newDescription; - return; - } - int[] mapping = VertexDescriptionDesignerImpl.mapAttributes(newDescription, m_description); - double[] newAttributes = new double[newDescription.getTotalComponentCount()]; - - int j = 0; - for (int i = 0, n = newDescription.getAttributeCount(); i < n; i++) { - int semantics = newDescription.getSemantics(i); - int nords = VertexDescription.getComponentCount(semantics); - if (mapping[i] == -1) - { - double d = VertexDescription.getDefaultValue(semantics); - for (int ord = 0; ord < nords; ord++) + int newLen = newDescription.getTotalComponentCount() - 2; + if (newLen > 0) { + double[] newAttributes = new double[newLen]; + + int j = 0; + for (int i = 1, n = newDescription.getAttributeCount(); i < n; i++) { + int semantics = newDescription.getSemantics(i); + int nords = VertexDescription.getComponentCount(semantics); + if (mapping[i] == -1) { - newAttributes[j] = d; - j++; + double d = VertexDescription.getDefaultValue(semantics); + for (int ord = 0; ord < nords; ord++) + { + newAttributes[j] = d; + j++; + } } - } - else { - int m = mapping[i]; - int offset = m_description._getPointAttributeOffset(m); - for (int ord = 0; ord < nords; ord++) - { - newAttributes[j] = m_attributes[offset]; - j++; - offset++; + else { + int m = mapping[i]; + int offset = m_description._getPointAttributeOffset(m) - 2; + for (int ord = 0; ord < nords; ord++) + { + newAttributes[j] = m_attributes[offset]; + j++; + offset++; + } } + } - + + m_attributes = newAttributes; } - - m_attributes = newAttributes; + else { + m_attributes = null; + } + m_description = newDescription; } /** - * Sets the Point to a default, non-empty state. + * Sets to a default empty state. */ - void _setToDefault() { - resizeAttributes(m_description.getTotalComponentCount()); - Point.attributeCopy(m_description._getDefaultPointAttributes(), - m_attributes, m_description.getTotalComponentCount()); - m_attributes[0] = NumberUtils.NaN(); - m_attributes[1] = NumberUtils.NaN(); + private void _setToDefault() { + int len = m_description.getTotalComponentCount() - 2; + if (len != 0) { + if (m_attributes == null || m_attributes.length != len) { + m_attributes = new double[len]; + } + + System.arraycopy(m_description._getDefaultPointAttributes(), 2, m_attributes, 0, len); + } + else { + m_attributes = null; + } + + m_x = NumberUtils.TheNaN; + m_y = NumberUtils.TheNaN; } @Override @@ -449,7 +426,7 @@ public void applyTransformation(Transformation2D transform) { } @Override - void applyTransformation(Transformation3D transform) { + public void applyTransformation(Transformation3D transform) { if (isEmptyImpl()) return; @@ -462,20 +439,27 @@ void applyTransformation(Transformation3D transform) { public void copyTo(Geometry dst) { if (dst.getType() != Type.Point) throw new IllegalArgumentException(); - - Point pointDst = (Point) dst; + + if (this == dst) + return; + dst._touch(); - if (m_attributes == null) { - pointDst.setEmpty(); + Point pointDst = (Point) dst; + dst.m_description = m_description; + pointDst.m_x = m_x; + pointDst.m_y = m_y; + int attrLen = m_description.getTotalComponentCount() - 2; + if (attrLen == 0) { pointDst.m_attributes = null; - pointDst.assignVertexDescription(m_description); - } else { - pointDst.assignVertexDescription(m_description); - pointDst.resizeAttributes(m_description.getTotalComponentCount()); - attributeCopy(m_attributes, pointDst.m_attributes, - m_description.getTotalComponentCount()); + return; + } + + if (pointDst.m_attributes == null || pointDst.m_attributes.length != attrLen) { + pointDst.m_attributes = new double[attrLen]; } + + System.arraycopy(m_attributes, 0, pointDst.m_attributes, 0, attrLen); } @Override @@ -490,30 +474,29 @@ public boolean isEmpty() { } final boolean isEmptyImpl() { - return ((m_attributes == null) || NumberUtils.isNaN(m_attributes[0]) || NumberUtils - .isNaN(m_attributes[1])); + return NumberUtils.isNaN(m_x) || NumberUtils.isNaN(m_y); } @Override public void queryEnvelope(Envelope env) { - env.setEmpty(); if (m_description != env.m_description) env.assignVertexDescription(m_description); + + env.setEmpty(); env.merge(this); } @Override public void queryEnvelope2D(Envelope2D env) { - if (isEmptyImpl()) { env.setEmpty(); return; } - env.xmin = m_attributes[0]; - env.ymin = m_attributes[1]; - env.xmax = m_attributes[0]; - env.ymax = m_attributes[1]; + env.xmin = m_x; + env.ymin = m_y; + env.xmax = m_x; + env.ymax = m_y; } @Override @@ -523,13 +506,13 @@ void queryEnvelope3D(Envelope3D env) { return; } - Point3D pt = getXYZ(); - env.xmin = pt.x; - env.ymin = pt.y; - env.zmin = pt.z; - env.xmax = pt.x; - env.ymax = pt.y; - env.zmax = pt.z; + env.xmin = m_x; + env.ymin = m_y; + env.xmax = m_x; + env.ymax = m_y; + double z = getZ(); + env.zmin = z; + env.zmax = z; } @Override @@ -546,21 +529,6 @@ public Envelope1D queryInterval(int semantics, int ordinate) { return env; } - private void resizeAttributes(int newSize) { - if (m_attributes == null) { - m_attributes = new double[newSize]; - } else if (m_attributes.length < newSize) { - double[] newbuffer = new double[newSize]; - System.arraycopy(m_attributes, 0, newbuffer, 0, m_attributes.length); - m_attributes = newbuffer; - } - } - - static void attributeCopy(double[] src, double[] dst, int count) { - if (count > 0) - System.arraycopy(src, 0, dst, 0, count); - } - /** * Set the X and Y coordinate of the point. * @@ -572,11 +540,8 @@ static void attributeCopy(double[] src, double[] dst, int count) { public void setXY(double x, double y) { _touch(); - if (m_attributes == null) - _setToDefault(); - - m_attributes[0] = x; - m_attributes[1] = y; + m_x = x; + m_y = y; } /** @@ -596,15 +561,21 @@ public boolean equals(Object _other) { if (m_description != otherPt.m_description) return false; - if (isEmptyImpl()) + if (isEmptyImpl()) { if (otherPt.isEmptyImpl()) return true; else return false; + } + + if (m_x != otherPt.m_x || m_y != otherPt.m_y) { + return false; + } - for (int i = 0, n = m_description.getTotalComponentCount(); i < n; i++) - if (m_attributes[i] != otherPt.m_attributes[i]) + for (int i = 0, n = m_description.getTotalComponentCount() - 2; i < n; i++) { + if (!NumberUtils.isEqualNonIEEE(m_attributes[i], otherPt.m_attributes[i])) return false; + } return true; } @@ -617,12 +588,15 @@ public boolean equals(Object _other) { public int hashCode() { int hashCode = m_description.hashCode(); if (!isEmptyImpl()) { - for (int i = 0, n = m_description.getTotalComponentCount(); i < n; i++) { + hashCode = NumberUtils.hash(hashCode, m_x); + hashCode = NumberUtils.hash(hashCode, m_y); + for (int i = 0, n = m_description.getTotalComponentCount() - 2; i < n; i++) { long bits = Double.doubleToLongBits(m_attributes[i]); int hc = (int) (bits ^ (bits >>> 32)); hashCode = NumberUtils.hash(hashCode, hc); } } + return hashCode; } diff --git a/src/main/java/com/esri/core/geometry/Point2D.java b/src/main/java/com/esri/core/geometry/Point2D.java index 90cc1e46..95a46c66 100644 --- a/src/main/java/com/esri/core/geometry/Point2D.java +++ b/src/main/java/com/esri/core/geometry/Point2D.java @@ -94,6 +94,12 @@ public boolean equals(Object other) { return x == v.x && y == v.y; } + + @Override + public int hashCode() { + return NumberUtils.hash(NumberUtils.hash(x), y); + } + public void sub(Point2D other) { x -= other.x; @@ -751,11 +757,6 @@ static Point2D calculateCircleCenterFromThreePoints(Point2D from, Point2D mid_po } } - @Override - public int hashCode() { - return NumberUtils.hash(NumberUtils.hash(x), y); - } - double getAxis(int ordinate) { assert(ordinate == 0 || ordinate == 1); return (ordinate == 0 ? x : y); diff --git a/src/main/java/com/esri/core/geometry/Point3D.java b/src/main/java/com/esri/core/geometry/Point3D.java index 849b00e1..71cbfdba 100644 --- a/src/main/java/com/esri/core/geometry/Point3D.java +++ b/src/main/java/com/esri/core/geometry/Point3D.java @@ -132,4 +132,31 @@ boolean _isNan() { return NumberUtils.isNaN(x) || NumberUtils.isNaN(y) || NumberUtils.isNaN(z); } + public boolean equals(Point3D other) { + //note that for nan value this returns false. + //this is by design for this class. + return x == other.x && y == other.y && z == other.z; + } + + @Override + public boolean equals(Object other_) { + if (other_ == this) + return true; + + if (!(other_ instanceof Point3D)) + return false; + + Point3D other = (Point3D)other_; + //note that for nan value this returns false. + //this is by design for this class. + return x == other.x && y == other.y && z == other.z; + } + + @Override + public int hashCode() { + int hash = NumberUtils.hash(x); + hash = NumberUtils.hash(hash, y); + hash = NumberUtils.hash(hash, z); + return hash; + } } diff --git a/src/main/java/com/esri/core/geometry/Segment.java b/src/main/java/com/esri/core/geometry/Segment.java index ca2ea184..b15364a2 100644 --- a/src/main/java/com/esri/core/geometry/Segment.java +++ b/src/main/java/com/esri/core/geometry/Segment.java @@ -528,9 +528,6 @@ private void _get(int endPoint, Point outPoint) { outPoint.assignVertexDescription(m_description); - if (outPoint.isEmptyImpl()) - outPoint._setToDefault(); - for (int attributeIndex = 0; attributeIndex < m_description .getAttributeCount(); attributeIndex++) { int semantics = m_description._getSemanticsImpl(attributeIndex); @@ -689,12 +686,26 @@ boolean _equalsImpl(Segment other) { || m_yStart != other.m_yStart || m_yEnd != other.m_yEnd) return false; for (int i = 0; i < (m_description.getTotalComponentCount() - 2) * 2; i++) - if (m_attributes[i] != other.m_attributes[i]) + if (!NumberUtils.isEqualNonIEEE(m_attributes[i], other.m_attributes[i])) return false; return true; } + @Override + public int hashCode() { + int hash = m_description.hashCode(); + hash = NumberUtils.hash(hash, m_xStart); + hash = NumberUtils.hash(hash, m_yStart); + hash = NumberUtils.hash(hash, m_xEnd); + hash = NumberUtils.hash(hash, m_yEnd); + for (int i = 0; i < (m_description.getTotalComponentCount() - 2) * 2; i++) { + hash = NumberUtils.hash(hash, m_attributes[i]); + } + + return hash; + } + /** * Returns true, when this segment is a closed curve (start point is equal * to end point exactly). diff --git a/src/main/java/com/esri/core/geometry/SizeOf.java b/src/main/java/com/esri/core/geometry/SizeOf.java index 6b097dad..8d9f4c77 100644 --- a/src/main/java/com/esri/core/geometry/SizeOf.java +++ b/src/main/java/com/esri/core/geometry/SizeOf.java @@ -68,7 +68,7 @@ public final class SizeOf { public static final int SIZE_OF_MULTI_POINT_IMPL = 56; - public static final int SIZE_OF_POINT = 24; + public static final int SIZE_OF_POINT = 40; public static final int SIZE_OF_POLYGON = 24; diff --git a/src/test/java/com/esri/core/geometry/TestEnvelope.java b/src/test/java/com/esri/core/geometry/TestEnvelope.java index 56edd466..6b5622e8 100644 --- a/src/test/java/com/esri/core/geometry/TestEnvelope.java +++ b/src/test/java/com/esri/core/geometry/TestEnvelope.java @@ -21,29 +21,58 @@ public class TestEnvelope { - @Test - public void testIntersect() - { - assertIntersection(new Envelope(0, 0, 5, 5), new Envelope(0, 0, 5, 5), new Envelope(0, 0, 5, 5)); - assertIntersection(new Envelope(0, 0, 5, 5), new Envelope(1, 1, 6, 6), new Envelope(1, 1, 5, 5)); - assertIntersection(new Envelope(1, 2, 3, 4), new Envelope(0, 0, 2, 3), new Envelope(1, 2, 2, 3)); + @Test + public void testIntersect() { + assertIntersection(new Envelope(0, 0, 5, 5), new Envelope(0, 0, 5, 5), new Envelope(0, 0, 5, 5)); + assertIntersection(new Envelope(0, 0, 5, 5), new Envelope(1, 1, 6, 6), new Envelope(1, 1, 5, 5)); + assertIntersection(new Envelope(1, 2, 3, 4), new Envelope(0, 0, 2, 3), new Envelope(1, 2, 2, 3)); - assertNoIntersection(new Envelope(), new Envelope()); - assertNoIntersection(new Envelope(0, 0, 5, 5), new Envelope()); - assertNoIntersection(new Envelope(), new Envelope(0, 0, 5, 5)); - } + assertNoIntersection(new Envelope(), new Envelope()); + assertNoIntersection(new Envelope(0, 0, 5, 5), new Envelope()); + assertNoIntersection(new Envelope(), new Envelope(0, 0, 5, 5)); + } - private static void assertIntersection(Envelope envelope, Envelope other, Envelope intersection) - { - boolean intersects = envelope.intersect(other); - assertTrue(intersects); - assertEquals(envelope, intersection); - } + @Test + public void testEquals() { + Envelope env1 = new Envelope(10, 9, 11, 12); + Envelope env2 = new Envelope(10, 9, 11, 13); + Envelope1D emptyInterval = new Envelope1D(); + emptyInterval.setEmpty(); + assertFalse(env1.equals(env2)); + env1.queryInterval(VertexDescription.Semantics.M, 0).equals(emptyInterval); + env2.setCoords(10, 9, 11, 12); + assertTrue(env1.equals(env2)); + env1.addAttribute(VertexDescription.Semantics.M); + env1.queryInterval(VertexDescription.Semantics.M, 0).equals(emptyInterval); + assertFalse(env1.equals(env2)); + env2.addAttribute(VertexDescription.Semantics.M); + assertTrue(env1.equals(env2)); + Envelope1D nonEmptyInterval = new Envelope1D(); + nonEmptyInterval.setCoords(1, 2); + env1.setInterval(VertexDescription.Semantics.M, 0, emptyInterval); + assertTrue(env1.equals(env2)); + env2.setInterval(VertexDescription.Semantics.M, 0, emptyInterval); + assertTrue(env1.equals(env2)); + env2.setInterval(VertexDescription.Semantics.M, 0, nonEmptyInterval); + assertFalse(env1.equals(env2)); + env1.setInterval(VertexDescription.Semantics.M, 0, nonEmptyInterval); + assertTrue(env1.equals(env2)); + env1.queryInterval(VertexDescription.Semantics.M, 0).equals(nonEmptyInterval); + env1.queryInterval(VertexDescription.Semantics.POSITION, 0).equals(new Envelope1D(10, 11)); + env1.queryInterval(VertexDescription.Semantics.POSITION, 0).equals(new Envelope1D(9, 13)); + } + + private static void assertIntersection(Envelope envelope, Envelope other, Envelope intersection) { + boolean intersects = envelope.intersect(other); + assertTrue(intersects); + assertEquals(envelope, intersection); + } - private static void assertNoIntersection(Envelope envelope, Envelope other) - { - boolean intersects = envelope.intersect(other); - assertFalse(intersects); - assertTrue(envelope.isEmpty()); - } + private static void assertNoIntersection(Envelope envelope, Envelope other) { + boolean intersects = envelope.intersect(other); + assertFalse(intersects); + assertTrue(envelope.isEmpty()); + } + } + diff --git a/src/test/java/com/esri/core/geometry/TestPoint.java b/src/test/java/com/esri/core/geometry/TestPoint.java index c2e8bd2f..c30f612f 100644 --- a/src/test/java/com/esri/core/geometry/TestPoint.java +++ b/src/test/java/com/esri/core/geometry/TestPoint.java @@ -45,9 +45,35 @@ protected void tearDown() throws Exception { public void testPt() { Point pt = new Point(); assertTrue(pt.isEmpty()); + assertTrue(Double.isNaN(pt.getX())); + assertTrue(Double.isNaN(pt.getY())); + assertTrue(Double.isNaN(pt.getM())); + assertTrue(pt.getZ() == 0); + Point pt1 = new Point(); + assertTrue(pt.equals(pt1)); + int hash1 = pt.hashCode(); pt.setXY(10, 2); assertFalse(pt.isEmpty()); - + assertTrue(pt.getX() == 10); + assertTrue(pt.getY() == 2); + assertTrue(pt.getXY().equals(new Point2D(10, 2))); + assertTrue(pt.getXYZ().x == 10); + assertTrue(pt.getXYZ().y == 2); + assertTrue(pt.getXYZ().z == 0); + assertFalse(pt.equals(pt1)); + pt.copyTo(pt1); + assertTrue(pt.equals(pt1)); + int hash2 = pt.hashCode(); + assertFalse(hash1 == hash2); + pt.setZ(5); + assertFalse(pt.equals(pt1)); + pt.copyTo(pt1); + assertTrue(pt.equals(pt1)); + assertFalse(hash1 == pt.hashCode()); + assertFalse(hash2 == pt.hashCode()); + assertTrue(pt.hasZ()); + assertTrue(pt.getZ() == 5); + assertTrue(pt.hasAttribute(VertexDescription.Semantics.Z)); pt.toString(); } From 961b53555259f691b96948418997d6698d3df950 Mon Sep 17 00:00:00 2001 From: Randall Whitman Date: Thu, 29 Aug 2019 08:39:58 -0700 Subject: [PATCH 191/196] Geometry release v2.2.3 --- README.md | 2 +- pom.xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index d4ea84c7..401315df 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,7 @@ The project is also available as a [Maven](http://maven.apache.org/) dependency: com.esri.geometry esri-geometry-api - 2.2.2 + 2.2.3 ``` diff --git a/pom.xml b/pom.xml index 09ac3899..a3dcbc19 100755 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ com.esri.geometry esri-geometry-api - 2.2.3-SNAPSHOT + 2.2.3 jar Esri Geometry API for Java From 73a8be8d62f809b56ff432144aca468b3d1d9d2c Mon Sep 17 00:00:00 2001 From: Sergey Tolstov Date: Thu, 21 Nov 2019 19:09:43 -0800 Subject: [PATCH 192/196] Fix self-tangency test typo (#248) --- .../geometry/OperatorSimplifyLocalHelper.java | 35 +++++++------------ .../java/com/esri/core/geometry/TestOGC.java | 9 +++++ 2 files changed, 21 insertions(+), 23 deletions(-) diff --git a/src/main/java/com/esri/core/geometry/OperatorSimplifyLocalHelper.java b/src/main/java/com/esri/core/geometry/OperatorSimplifyLocalHelper.java index b0bd4dd8..2513693e 100644 --- a/src/main/java/com/esri/core/geometry/OperatorSimplifyLocalHelper.java +++ b/src/main/java/com/esri/core/geometry/OperatorSimplifyLocalHelper.java @@ -476,10 +476,12 @@ boolean checkSelfIntersectionsPolylinePlanar_() { || (xyindex == path_last); if (m_bOGCRestrictions) vi_prev.boundary = !is_closed_path && vi_prev.end_point; - else + else { // for regular planar simplify, only the end points are allowed // to coincide vi_prev.boundary = vi_prev.end_point; + } + vi_prev.ipath = ipath; vi_prev.x = pt.x; vi_prev.y = pt.y; @@ -506,11 +508,11 @@ boolean checkSelfIntersectionsPolylinePlanar_() { boolean end_point = (xyindex == path_start) || (xyindex == path_last); if (m_bOGCRestrictions) - boundary = !is_closed_path && vi_prev.end_point; + boundary = !is_closed_path && end_point; else // for regular planar simplify, only the end points are allowed // to coincide - boundary = vi_prev.end_point; + boundary = end_point; vi.x = pt.x; vi.y = pt.y; @@ -522,22 +524,12 @@ boolean checkSelfIntersectionsPolylinePlanar_() { if (vi.x == vi_prev.x && vi.y == vi_prev.y) { if (m_bOGCRestrictions) { if (!vi.boundary || !vi_prev.boundary) { + // check that this is not the endpoints of a closed path if ((vi.ipath != vi_prev.ipath) - || (!vi.end_point && !vi_prev.end_point))// check - // that - // this - // is - // not - // the - // endpoints - // of - // a - // closed - // path - { + || (!vi.end_point && !vi_prev.end_point)) { // one of coincident vertices is not on the boundary - // this is either Non_simple_result::cross_over or - // Non_simple_result::ogc_self_tangency. + // this is either NonSimpleResult.CrossOver or + // NonSimpleResult.OGCPolylineSelfTangency. // too expensive to distinguish between the two. m_nonSimpleResult = new NonSimpleResult( NonSimpleResult.Reason.OGCPolylineSelfTangency, @@ -546,12 +538,9 @@ boolean checkSelfIntersectionsPolylinePlanar_() { } } } else { - if (!vi.end_point || !vi_prev.end_point) {// one of - // coincident - // vertices is - // not an - // endpoint - m_nonSimpleResult = new NonSimpleResult( + if (!vi.end_point || !vi_prev.end_point) { + //one of coincident vertices is not an endpoint + m_nonSimpleResult = new NonSimpleResult( NonSimpleResult.Reason.CrossOver, vi.ivertex, vi_prev.ivertex); return false;// common point not on the boundary diff --git a/src/test/java/com/esri/core/geometry/TestOGC.java b/src/test/java/com/esri/core/geometry/TestOGC.java index 34403661..f55dbb21 100644 --- a/src/test/java/com/esri/core/geometry/TestOGC.java +++ b/src/test/java/com/esri/core/geometry/TestOGC.java @@ -1036,4 +1036,13 @@ public void testFlattened() { ogcGeometry = (OGCConcreteGeometryCollection)OGCGeometry.fromText("GEOMETRYCOLLECTION (MULTIPOINT (1 1), MULTILINESTRING ((1 2, 3 4)), MULTIPOLYGON (((1 2, 3 4, 5 6, 1 2))))"); assertTrue(ogcGeometry.isFlattened()); } + + @Test + public void testIssue247IsSimple() { + //https://github.com/Esri/geometry-api-java/issues/247 + String wkt = "MULTILINESTRING ((-103.4894322 25.6164519, -103.4889647 25.6159054, -103.489434 25.615654), (-103.489434 25.615654, -103.4894322 25.6164519), (-103.4897361 25.6168342, -103.4894322 25.6164519))"; + OGCGeometry ogcGeom = OGCGeometry.fromText(wkt); + boolean b = ogcGeom.isSimple(); + assertTrue(b); + } } From abf6b7c6b825788b866266e2aad834f7309bab42 Mon Sep 17 00:00:00 2001 From: Sergey Tolstov Date: Fri, 6 Dec 2019 12:19:14 -0800 Subject: [PATCH 193/196] close a stream in debug methods (#252) --- .../core/geometry/OperatorFactoryLocal.java | 25 ++++++++++++++++--- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/esri/core/geometry/OperatorFactoryLocal.java b/src/main/java/com/esri/core/geometry/OperatorFactoryLocal.java index 153b4728..04368b9d 100644 --- a/src/main/java/com/esri/core/geometry/OperatorFactoryLocal.java +++ b/src/main/java/com/esri/core/geometry/OperatorFactoryLocal.java @@ -29,6 +29,7 @@ import java.io.BufferedReader; import java.io.FileInputStream; import java.io.FileOutputStream; +import java.io.IOException; import java.io.InputStreamReader; import java.io.PrintStream; import java.io.Reader; @@ -182,20 +183,28 @@ public static MapGeometry loadGeometryFromJSONFileDbg(String file_name) { } String jsonString = null; + Reader reader = null; try { FileInputStream stream = new FileInputStream(file_name); - Reader reader = new BufferedReader(new InputStreamReader(stream)); + reader = new BufferedReader(new InputStreamReader(stream)); StringBuilder builder = new StringBuilder(); char[] buffer = new char[8192]; int read; while ((read = reader.read(buffer, 0, buffer.length)) > 0) { builder.append(buffer, 0, read); } - stream.close(); jsonString = builder.toString(); } catch (Exception ex) { } + finally { + if (reader != null) { + try { + reader.close(); + } catch (IOException e) { + } + } + } MapGeometry mapGeom = OperatorImportFromJson.local().execute(Geometry.Type.Unknown, jsonString); return mapGeom; @@ -276,20 +285,28 @@ public static Geometry loadGeometryFromWKTFileDbg(String file_name) { } String s = null; + Reader reader = null; try { FileInputStream stream = new FileInputStream(file_name); - Reader reader = new BufferedReader(new InputStreamReader(stream)); + reader = new BufferedReader(new InputStreamReader(stream)); StringBuilder builder = new StringBuilder(); char[] buffer = new char[8192]; int read; while ((read = reader.read(buffer, 0, buffer.length)) > 0) { builder.append(buffer, 0, read); } - stream.close(); s = builder.toString(); } catch (Exception ex) { } + finally { + if (reader != null) { + try { + reader.close(); + } catch (IOException e) { + } + } + } return OperatorImportFromWkt.local().execute(0, Geometry.Type.Unknown, s, null); } From 4c2fbd3d35e83c39544e85b3ca9fec57b8c03566 Mon Sep 17 00:00:00 2001 From: Sergey Tolstov Date: Tue, 14 Jan 2020 09:18:29 -0800 Subject: [PATCH 194/196] Make Transformation3D class public (#254) --- src/main/java/com/esri/core/geometry/Transformation3D.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/esri/core/geometry/Transformation3D.java b/src/main/java/com/esri/core/geometry/Transformation3D.java index 99702706..cac98407 100644 --- a/src/main/java/com/esri/core/geometry/Transformation3D.java +++ b/src/main/java/com/esri/core/geometry/Transformation3D.java @@ -33,7 +33,7 @@ * are the matrices. This is equivalent to the following line of code: * ResultVector = (M1.Mul(M2).Mul(M3)).Transform(Vector) */ -final class Transformation3D { +final public class Transformation3D { public double xx, yx, zx, xd, xy, yy, zy, yd, xz, yz, zz, zd; @@ -112,7 +112,7 @@ public Envelope3D transform(Envelope3D env) { return env; } - void transform(Point3D[] pointsIn, int count, Point3D[] pointsOut) { + public void transform(Point3D[] pointsIn, int count, Point3D[] pointsOut) { for (int i = 0; i < count; i++) { Point3D res = new Point3D(); Point3D src = pointsIn[i]; From 37b7635d9c1f3d1f4119191272a8e8406a379d0d Mon Sep 17 00:00:00 2001 From: Sergey Tolstov Date: Wed, 15 Jan 2020 20:04:29 -0800 Subject: [PATCH 195/196] Add a unit test and a fix for the crash in cut (#255) --- .../java/com/esri/core/geometry/Cutter.java | 5 +- .../java/com/esri/core/geometry/TestCut.java | 54 ++++++++++++------- 2 files changed, 37 insertions(+), 22 deletions(-) diff --git a/src/main/java/com/esri/core/geometry/Cutter.java b/src/main/java/com/esri/core/geometry/Cutter.java index f56dc5de..3c34c064 100644 --- a/src/main/java/com/esri/core/geometry/Cutter.java +++ b/src/main/java/com/esri/core/geometry/Cutter.java @@ -27,7 +27,6 @@ import com.esri.core.geometry.OperatorCutLocal; -import java.lang.reflect.Array; import java.util.ArrayList; import java.util.Arrays; @@ -138,6 +137,8 @@ static EditShape CutPolyline(boolean bConsiderTouch, Polyline cuttee, private static ArrayList _getCutEvents(int orderIndex, EditShape editShape) { int pointCount = editShape.getTotalPointCount(); + if (pointCount == 0) + return null; // Sort vertices lexicographically // Firstly copy allvertices to an array. @@ -156,8 +157,6 @@ private static ArrayList _getCutEvents(int orderIndex, CompareVertices compareVertices = new CompareVertices(orderIndex, editShape); vertices.Sort(0, pointCount, new CutterVertexComparer(compareVertices)); - // SORTDYNAMICARRAYEX(vertices, index_type, 0, pointCount, - // CutterVertexComparer, compareVertices); // Find Cut Events ArrayList cutEvents = new ArrayList(0); diff --git a/src/test/java/com/esri/core/geometry/TestCut.java b/src/test/java/com/esri/core/geometry/TestCut.java index 456973cd..a388ff01 100644 --- a/src/test/java/com/esri/core/geometry/TestCut.java +++ b/src/test/java/com/esri/core/geometry/TestCut.java @@ -51,7 +51,7 @@ public static void testCut4326() { } - public static void testConsiderTouch1(SpatialReference spatialReference) { + private static void testConsiderTouch1(SpatialReference spatialReference) { OperatorFactoryLocal engine = OperatorFactoryLocal.getInstance(); OperatorCut opCut = (OperatorCut) engine.getOperator(Operator.Type.Cut); @@ -101,7 +101,7 @@ public static void testConsiderTouch1(SpatialReference spatialReference) { assertTrue(cut == null); } - public static void testConsiderTouch2(SpatialReference spatialReference) { + private static void testConsiderTouch2(SpatialReference spatialReference) { OperatorFactoryLocal engine = OperatorFactoryLocal.getInstance(); OperatorCut opCut = (OperatorCut) engine.getOperator(Operator.Type.Cut); @@ -167,7 +167,7 @@ public static void testConsiderTouch2(SpatialReference spatialReference) { assertTrue(cut == null); } - public static void testPolygon5(SpatialReference spatialReference) { + private static void testPolygon5(SpatialReference spatialReference) { OperatorFactoryLocal engine = OperatorFactoryLocal.getInstance(); OperatorCut opCut = (OperatorCut) engine.getOperator(Operator.Type.Cut); @@ -201,7 +201,7 @@ public static void testPolygon5(SpatialReference spatialReference) { assertTrue(cut == null); } - public static void testPolygon7(SpatialReference spatialReference) { + private static void testPolygon7(SpatialReference spatialReference) { OperatorFactoryLocal engine = OperatorFactoryLocal.getInstance(); OperatorCut opCut = (OperatorCut) engine.getOperator(Operator.Type.Cut); @@ -238,7 +238,7 @@ public static void testPolygon7(SpatialReference spatialReference) { assertTrue(cut == null); } - public static void testPolygon8(SpatialReference spatialReference) { + private static void testPolygon8(SpatialReference spatialReference) { OperatorFactoryLocal engine = OperatorFactoryLocal.getInstance(); OperatorCut opCut = (OperatorCut) engine.getOperator(Operator.Type.Cut); @@ -275,7 +275,7 @@ public static void testPolygon8(SpatialReference spatialReference) { assertTrue(cut == null); } - public static void testPolygon9(SpatialReference spatialReference) { + private static void testPolygon9(SpatialReference spatialReference) { OperatorFactoryLocal engine = OperatorFactoryLocal.getInstance(); OperatorCut opCut = (OperatorCut) engine.getOperator(Operator.Type.Cut); @@ -309,7 +309,7 @@ public static void testPolygon9(SpatialReference spatialReference) { assertTrue(cut == null); } - public static void testEngine(SpatialReference spatialReference) { + private static void testEngine(SpatialReference spatialReference) { Polygon polygon8 = makePolygon8(); Polyline cutter8 = makePolygonCutter8(); @@ -337,7 +337,7 @@ public static void testEngine(SpatialReference spatialReference) { assertTrue(area == 800); } - public static Polyline makePolyline1() { + private static Polyline makePolyline1() { Polyline poly = new Polyline(); poly.startPath(0, 0); @@ -355,7 +355,7 @@ public static Polyline makePolyline1() { return poly; } - public static Polyline makePolylineCutter1() { + private static Polyline makePolylineCutter1() { Polyline poly = new Polyline(); poly.startPath(1, 0); @@ -395,7 +395,7 @@ public static Polyline makePolylineCutter1() { return poly; } - public static Polyline makePolyline2() { + private static Polyline makePolyline2() { Polyline poly = new Polyline(); poly.startPath(-2, 0); @@ -410,7 +410,7 @@ public static Polyline makePolyline2() { return poly; } - public static Polyline makePolylineCutter2() { + private static Polyline makePolylineCutter2() { Polyline poly = new Polyline(); poly.startPath(-1.5, 0); @@ -443,7 +443,7 @@ public static Polyline makePolylineCutter2() { return poly; } - public static Polygon makePolygon5() { + private static Polygon makePolygon5() { Polygon poly = new Polygon(); poly.startPath(0, 0); @@ -454,7 +454,7 @@ public static Polygon makePolygon5() { return poly; } - public static Polyline makePolygonCutter5() { + private static Polyline makePolygonCutter5() { Polyline poly = new Polyline(); poly.startPath(15, 0); @@ -466,7 +466,7 @@ public static Polyline makePolygonCutter5() { return poly; } - public static Polygon makePolygon7() { + private static Polygon makePolygon7() { Polygon poly = new Polygon(); poly.startPath(0, 0); @@ -477,7 +477,7 @@ public static Polygon makePolygon7() { return poly; } - public static Polyline makePolygonCutter7() { + private static Polyline makePolygonCutter7() { Polyline poly = new Polyline(); poly.startPath(10, 10); @@ -489,7 +489,7 @@ public static Polyline makePolygonCutter7() { return poly; } - public static Polygon makePolygon8() { + private static Polygon makePolygon8() { Polygon poly = new Polygon(); poly.startPath(0, 0); @@ -500,7 +500,7 @@ public static Polygon makePolygon8() { return poly; } - public static Polyline makePolygonCutter8() { + private static Polyline makePolygonCutter8() { Polyline poly = new Polyline(); poly.startPath(10, 10); @@ -512,7 +512,7 @@ public static Polyline makePolygonCutter8() { return poly; } - public static Polygon makePolygon9() { + private static Polygon makePolygon9() { Polygon poly = new Polygon(); poly.startPath(0, 0); @@ -533,7 +533,7 @@ public static Polygon makePolygon9() { return poly; } - public static Polyline makePolygonCutter9() { + private static Polyline makePolygonCutter9() { Polyline poly = new Polyline(); poly.startPath(5, -1); @@ -541,4 +541,20 @@ public static Polyline makePolygonCutter9() { return poly; } + + @Test + public void testGithubIssue253() { + //https://github.com/Esri/geometry-api-java/issues/253 + SpatialReference spatialReference = SpatialReference.create(3857); + Polyline poly1 = new Polyline(); + poly1.startPath(610, 552); + poly1.lineTo(610, 552); + Polyline poly2 = new Polyline(); + poly2.startPath(610, 552); + poly2.lineTo(610, 552); + GeometryCursor cursor = OperatorCut.local().execute(true, poly1, poly2, spatialReference, null); + + Geometry res = cursor.next(); + assertTrue(res == null); + } } From c1978bb25228d1a1f237800e49399aa0a840fe20 Mon Sep 17 00:00:00 2001 From: David Raleigh Date: Sun, 1 Mar 2020 13:21:59 -0500 Subject: [PATCH 196/196] tabs --- .../core/geometry/AttributeStreamBase.java | 808 +- .../core/geometry/AttributeStreamOfDbl.java | 1565 ++- .../core/geometry/AttributeStreamOfFloat.java | 1137 +- .../core/geometry/AttributeStreamOfInt16.java | 1101 +- .../core/geometry/AttributeStreamOfInt32.java | 1353 +- .../core/geometry/AttributeStreamOfInt64.java | 1101 +- .../core/geometry/AttributeStreamOfInt8.java | 1203 +- .../java/com/esri/core/geometry/Boundary.java | 418 +- .../com/esri/core/geometry/BucketSort.java | 300 +- .../java/com/esri/core/geometry/Bufferer.java | 3810 +++--- .../esri/core/geometry/ByteBufferCursor.java | 44 +- .../com/esri/core/geometry/ClassicSort.java | 6 +- .../java/com/esri/core/geometry/Clipper.java | 2490 ++-- .../com/esri/core/geometry/Clusterer.java | 1094 +- .../esri/core/geometry/CombineOperator.java | 10 +- .../esri/core/geometry/ConstructOffset.java | 1952 +-- .../com/esri/core/geometry/ConvexHull.java | 1388 +- .../esri/core/geometry/CrackAndCluster.java | 258 +- .../java/com/esri/core/geometry/Cracker.java | 1020 +- .../java/com/esri/core/geometry/Cutter.java | 2772 ++-- .../com/esri/core/geometry/DirtyFlags.java | 72 +- .../com/esri/core/geometry/ECoordinate.java | 716 +- .../com/esri/core/geometry/EditShape.java | 4630 +++---- .../esri/core/geometry/EnclosingCircler.java | 293 +- .../java/com/esri/core/geometry/EnvSrlzr.java | 114 +- .../java/com/esri/core/geometry/Envelope.java | 2129 ++- .../com/esri/core/geometry/Envelope1D.java | 378 +- .../com/esri/core/geometry/Envelope2D.java | 1902 +-- .../geometry/Envelope2DIntersectorImpl.java | 1732 +-- .../com/esri/core/geometry/Envelope3D.java | 664 +- .../core/geometry/GeneralizeComparator.java | 550 +- .../esri/core/geometry/GeneralizeType.java | 6 +- .../geometry/GenericGeometrySerializer.java | 116 +- .../java/com/esri/core/geometry/GeoDist.java | 1328 +- .../esri/core/geometry/GeoJsonCrsTables.java | 228 +- .../core/geometry/GeoJsonExportFlags.java | 54 +- .../core/geometry/GeoJsonImportFlags.java | 24 +- .../esri/core/geometry/GeodesicBufferer.java | 2398 ++-- .../esri/core/geometry/GeodesicDensifier.java | 330 +- .../esri/core/geometry/GeodeticCurveType.java | 42 +- .../java/com/esri/core/geometry/Geometry.java | 933 +- .../core/geometry/GeometryAccelerators.java | 90 +- .../esri/core/geometry/GeometryCursor.java | 72 +- .../core/geometry/GeometryCursorAppend.java | 56 +- .../esri/core/geometry/GeometryEngine.java | 1465 ++- .../esri/core/geometry/GeometryException.java | 28 +- .../core/geometry/GeometrySerializer.java | 154 +- .../esri/core/geometry/IndexHashTable.java | 496 +- .../esri/core/geometry/IndexMultiDCList.java | 564 +- .../esri/core/geometry/IndexMultiList.java | 476 +- .../com/esri/core/geometry/InternalUtils.java | 1064 +- .../java/com/esri/core/geometry/Interop.java | 12 +- .../esri/core/geometry/IntervalTreeImpl.java | 2186 +-- .../com/esri/core/geometry/InverseResult.java | 52 +- .../com/esri/core/geometry/JSONUtils.java | 36 +- .../com/esri/core/geometry/JsonCursor.java | 30 +- .../core/geometry/JsonGeometryException.java | 38 +- .../esri/core/geometry/JsonParserReader.java | 266 +- .../com/esri/core/geometry/JsonReader.java | 40 +- .../esri/core/geometry/JsonReaderCursor.java | 44 +- .../esri/core/geometry/JsonStringWriter.java | 796 +- .../com/esri/core/geometry/JsonWriter.java | 86 +- .../java/com/esri/core/geometry/Line.java | 1933 ++- .../geometry/ListeningGeometryCursor.java | 74 +- .../java/com/esri/core/geometry/LnSrlzr.java | 110 +- .../com/esri/core/geometry/MapGeometry.java | 244 +- .../esri/core/geometry/MapGeometryCursor.java | 44 +- .../esri/core/geometry/MapOGCStructure.java | 4 +- .../com/esri/core/geometry/MathUtils.java | 412 +- .../core/geometry/MgrsConversionMode.java | 56 +- .../com/esri/core/geometry/MultiPath.java | 1364 +- .../com/esri/core/geometry/MultiPathImpl.java | 4983 ++++--- .../com/esri/core/geometry/MultiPoint.java | 659 +- .../esri/core/geometry/MultiPointImpl.java | 623 +- .../core/geometry/MultiVertexGeometry.java | 336 +- .../geometry/MultiVertexGeometryImpl.java | 2150 +-- .../esri/core/geometry/NonSimpleResult.java | 144 +- .../com/esri/core/geometry/NumberUtils.java | 220 +- .../com/esri/core/geometry/OGCStructure.java | 6 +- .../esri/core/geometry/ObjectCacheTable.java | 56 +- .../java/com/esri/core/geometry/Operator.java | 198 +- .../esri/core/geometry/OperatorBoundary.java | 68 +- .../core/geometry/OperatorBoundaryLocal.java | 28 +- .../geometry/OperatorBoundaryLocalCursor.java | 50 +- .../esri/core/geometry/OperatorBuffer.java | 122 +- .../core/geometry/OperatorBufferCursor.java | 90 +- .../core/geometry/OperatorBufferLocal.java | 70 +- .../com/esri/core/geometry/OperatorClip.java | 40 +- .../core/geometry/OperatorClipCursor.java | 42 +- .../esri/core/geometry/OperatorClipLocal.java | 28 +- .../esri/core/geometry/OperatorContains.java | 18 +- .../core/geometry/OperatorContainsLocal.java | 12 +- .../core/geometry/OperatorConvexHull.java | 86 +- .../geometry/OperatorConvexHullCursor.java | 360 +- .../geometry/OperatorConvexHullLocal.java | 32 +- .../esri/core/geometry/OperatorCrosses.java | 18 +- .../core/geometry/OperatorCrossesLocal.java | 12 +- .../com/esri/core/geometry/OperatorCut.java | 70 +- .../esri/core/geometry/OperatorCutCursor.java | 362 +- .../esri/core/geometry/OperatorCutLocal.java | 120 +- .../geometry/OperatorDensifyByLength.java | 68 +- .../OperatorDensifyByLengthCursor.java | 238 +- .../OperatorDensifyByLengthLocal.java | 44 +- .../core/geometry/OperatorDifference.java | 66 +- .../geometry/OperatorDifferenceCursor.java | 58 +- .../geometry/OperatorDifferenceLocal.java | 646 +- .../esri/core/geometry/OperatorDisjoint.java | 18 +- .../core/geometry/OperatorDisjointLocal.java | 12 +- .../esri/core/geometry/OperatorDistance.java | 30 +- .../core/geometry/OperatorDistanceLocal.java | 808 +- .../geometry/OperatorEnclosingCircle.java | 28 +- .../OperatorEnclosingCircleCursor.java | 48 +- .../OperatorEnclosingCircleLocal.java | 18 +- .../esri/core/geometry/OperatorEquals.java | 18 +- .../core/geometry/OperatorEqualsLocal.java | 12 +- .../geometry/OperatorExportToESRIShape.java | 60 +- .../OperatorExportToESRIShapeCursor.java | 1762 +-- .../OperatorExportToESRIShapeLocal.java | 38 +- .../geometry/OperatorExportToGeoJson.java | 94 +- .../OperatorExportToGeoJsonCursor.java | 1412 +- .../OperatorExportToGeoJsonLocal.java | 48 +- .../core/geometry/OperatorExportToJson.java | 58 +- .../geometry/OperatorExportToJsonCursor.java | 880 +- .../geometry/OperatorExportToJsonLocal.java | 36 +- .../core/geometry/OperatorExportToWkb.java | 64 +- .../geometry/OperatorExportToWkbCursor.java | 90 +- .../geometry/OperatorExportToWkbLocal.java | 2438 ++-- .../core/geometry/OperatorExportToWkt.java | 34 +- .../geometry/OperatorExportToWktCursor.java | 84 +- .../geometry/OperatorExportToWktLocal.java | 1730 +-- .../esri/core/geometry/OperatorFactory.java | 18 +- .../core/geometry/OperatorFactoryLocal.java | 456 +- .../core/geometry/OperatorGeneralize.java | 50 +- .../geometry/OperatorGeneralizeByArea.java | 102 +- .../OperatorGeneralizeByAreaCursor.java | 288 +- .../OperatorGeneralizeByAreaLocal.java | 120 +- .../geometry/OperatorGeneralizeCursor.java | 280 +- .../geometry/OperatorGeneralizeLocal.java | 36 +- .../core/geometry/OperatorGeodesicBuffer.java | 108 +- .../OperatorGeodesicBufferCursor.java | 100 +- .../geometry/OperatorGeodesicBufferLocal.java | 92 +- .../core/geometry/OperatorGeodeticArea.java | 66 +- .../geometry/OperatorGeodeticAreaLocal.java | 36 +- .../OperatorGeodeticDensifyByLength.java | 66 +- .../OperatorGeodeticDensifyCursor.java | 44 +- .../OperatorGeodeticDensifyLocal.java | 46 +- .../geometry/OperatorGeodeticInverse.java | 53 +- .../OperatorGeodeticInverseLocal.java | 62 +- .../core/geometry/OperatorGeodeticLength.java | 44 +- .../geometry/OperatorGeodeticLengthLocal.java | 70 +- .../geometry/OperatorImportFromESRIShape.java | 66 +- .../OperatorImportFromESRIShapeCursor.java | 1972 +-- .../OperatorImportFromESRIShapeLocal.java | 24 +- .../geometry/OperatorImportFromGeoJson.java | 74 +- .../OperatorImportFromGeoJsonCursor.java | 74 +- .../OperatorImportFromGeoJsonLocal.java | 2590 ++-- .../core/geometry/OperatorImportFromJson.java | 62 +- .../OperatorImportFromJsonCursor.java | 1068 +- .../geometry/OperatorImportFromJsonLocal.java | 30 +- .../core/geometry/OperatorImportFromWkb.java | 86 +- .../geometry/OperatorImportFromWkbCursor.java | 78 +- .../geometry/OperatorImportFromWkbLocal.java | 2050 +-- .../core/geometry/OperatorImportFromWkt.java | 64 +- .../geometry/OperatorImportFromWktCursor.java | 78 +- .../geometry/OperatorImportFromWktLocal.java | 1120 +- .../OperatorInternalRelationUtils.java | 1404 +- .../core/geometry/OperatorIntersection.java | 126 +- .../geometry/OperatorIntersectionCursor.java | 1542 +-- .../geometry/OperatorIntersectionLocal.java | 98 +- .../core/geometry/OperatorIntersects.java | 18 +- .../geometry/OperatorIntersectsLocal.java | 16 +- .../esri/core/geometry/OperatorOffset.java | 125 +- .../core/geometry/OperatorOffsetCursor.java | 72 +- .../core/geometry/OperatorOffsetLocal.java | 38 +- .../esri/core/geometry/OperatorOverlaps.java | 18 +- .../core/geometry/OperatorOverlapsLocal.java | 12 +- .../esri/core/geometry/OperatorProject.java | 260 +- .../core/geometry/OperatorProjectCursor.java | 36 +- .../core/geometry/OperatorProjectLocal.java | 56 +- .../core/geometry/OperatorProximity2D.java | 118 +- .../geometry/OperatorProximity2DLocal.java | 974 +- .../core/geometry/OperatorRandomPoints.java | 38 +- .../geometry/OperatorRandomPointsCursor.java | 78 +- .../geometry/OperatorRandomPointsLocal.java | 42 +- .../esri/core/geometry/OperatorRelate.java | 62 +- .../core/geometry/OperatorRelateLocal.java | 12 +- .../OperatorShapePreservingDensify.java | 72 +- .../OperatorShapePreservingDensifyLocal.java | 30 +- .../core/geometry/OperatorSimpleRelation.java | 70 +- .../esri/core/geometry/OperatorSimplify.java | 126 +- .../core/geometry/OperatorSimplifyCursor.java | 62 +- .../geometry/OperatorSimplifyCursorOGC.java | 70 +- .../core/geometry/OperatorSimplifyLocal.java | 62 +- .../geometry/OperatorSimplifyLocalHelper.java | 4378 +++---- .../geometry/OperatorSimplifyLocalOGC.java | 52 +- .../core/geometry/OperatorSimplifyOGC.java | 108 +- .../geometry/OperatorSymmetricDifference.java | 60 +- .../OperatorSymmetricDifferenceCursor.java | 60 +- .../OperatorSymmetricDifferenceLocal.java | 280 +- .../esri/core/geometry/OperatorTouches.java | 18 +- .../core/geometry/OperatorTouchesLocal.java | 12 +- .../com/esri/core/geometry/OperatorUnion.java | 50 +- .../core/geometry/OperatorUnionCursor.java | 520 +- .../core/geometry/OperatorUnionLocal.java | 33 +- .../esri/core/geometry/OperatorWithin.java | 18 +- .../core/geometry/OperatorWithinLocal.java | 12 +- .../geometry/PairwiseIntersectorImpl.java | 438 +- .../com/esri/core/geometry/PathFlags.java | 22 +- .../java/com/esri/core/geometry/PeDouble.java | 14 +- .../geometry/PlaneSweepCrackerHelper.java | 3004 ++--- .../java/com/esri/core/geometry/Point.java | 1101 +- .../java/com/esri/core/geometry/Point2D.java | 1553 +-- .../java/com/esri/core/geometry/Point3D.java | 194 +- .../core/geometry/PointInPolygonHelper.java | 810 +- .../java/com/esri/core/geometry/Polygon.java | 183 +- .../com/esri/core/geometry/PolygonUtils.java | 602 +- .../java/com/esri/core/geometry/Polyline.java | 130 +- .../esri/core/geometry/ProgressTracker.java | 34 +- .../com/esri/core/geometry/Projecter.java | 368 +- .../geometry/ProjectionTransformation.java | 59 +- .../esri/core/geometry/Proximity2DResult.java | 200 +- .../geometry/Proximity2DResultComparator.java | 16 +- .../java/com/esri/core/geometry/PtSrlzr.java | 102 +- .../java/com/esri/core/geometry/QuadTree.java | 622 +- .../com/esri/core/geometry/QuadTreeImpl.java | 2534 ++-- .../esri/core/geometry/RandomPointMaker.java | 208 +- .../core/geometry/RasterizedGeometry2D.java | 214 +- .../geometry/RasterizedGeometry2DImpl.java | 1042 +- .../core/geometry/RelationalOperations.java | 10358 +++++++-------- .../geometry/RelationalOperationsMatrix.java | 5396 ++++---- .../core/geometry/RingOrientationFixer.java | 1314 +- .../java/com/esri/core/geometry/Segment.java | 1810 +-- .../com/esri/core/geometry/SegmentBuffer.java | 82 +- .../com/esri/core/geometry/SegmentFlags.java | 16 +- .../core/geometry/SegmentIntersector.java | 820 +- .../esri/core/geometry/SegmentIterator.java | 370 +- .../core/geometry/SegmentIteratorImpl.java | 882 +- .../esri/core/geometry/ShapeExportFlags.java | 26 +- .../esri/core/geometry/ShapeImportFlags.java | 8 +- .../esri/core/geometry/ShapeModifiers.java | 30 +- .../com/esri/core/geometry/ShapeType.java | 50 +- .../core/geometry/SimpleByteBufferCursor.java | 224 +- .../core/geometry/SimpleGeometryCursor.java | 136 +- .../esri/core/geometry/SimpleJsonCursor.java | 66 +- .../core/geometry/SimpleJsonReaderCursor.java | 84 +- .../geometry/SimpleMapGeometryCursor.java | 90 +- .../esri/core/geometry/SimpleRasterizer.java | 1024 +- .../esri/core/geometry/SimpleStateEnum.java | 40 +- .../core/geometry/SimpleStringCursor.java | 230 +- .../com/esri/core/geometry/Simplificator.java | 2028 ++- .../java/com/esri/core/geometry/SizeOf.java | 2 +- .../esri/core/geometry/SpatialReference.java | 557 +- .../core/geometry/SpatialReferenceImpl.java | 716 +- .../geometry/SpatialReferenceSerializer.java | 58 +- .../geometry/StridedIndexTypeCollection.java | 470 +- .../com/esri/core/geometry/StringCursor.java | 10 +- .../com/esri/core/geometry/StringUtils.java | 162 +- .../esri/core/geometry/SweepComparator.java | 1286 +- .../core/geometry/SweepMonkierComparator.java | 206 +- .../com/esri/core/geometry/TopoGraph.java | 5160 ++++---- .../core/geometry/TopologicalOperations.java | 4242 +++--- .../esri/core/geometry/Transformation2D.java | 1620 +-- .../esri/core/geometry/Transformation3D.java | 448 +- .../java/com/esri/core/geometry/Treap.java | 1882 +-- .../core/geometry/UserCancelException.java | 8 +- .../esri/core/geometry/VertexDescription.java | 654 +- .../VertexDescriptionDesignerImpl.java | 108 +- .../core/geometry/VertexDescriptionHash.java | 80 +- .../com/esri/core/geometry/WkbByteOrder.java | 4 +- .../esri/core/geometry/WkbExportFlags.java | 20 +- .../esri/core/geometry/WkbGeometryType.java | 58 +- .../esri/core/geometry/WkbImportFlags.java | 4 +- .../java/com/esri/core/geometry/Wkid.java | 294 +- src/main/java/com/esri/core/geometry/Wkt.java | 146 +- .../esri/core/geometry/WktExportFlags.java | 34 +- .../esri/core/geometry/WktImportFlags.java | 4 +- .../com/esri/core/geometry/WktParser.java | 1368 +- .../ogc/OGCConcreteGeometryCollection.java | 755 +- .../com/esri/core/geometry/ogc/OGCCurve.java | 30 +- .../esri/core/geometry/ogc/OGCGeometry.java | 1354 +- .../geometry/ogc/OGCGeometryCollection.java | 22 +- .../esri/core/geometry/ogc/OGCLineString.java | 167 +- .../esri/core/geometry/ogc/OGCLinearRing.java | 54 +- .../esri/core/geometry/ogc/OGCMultiCurve.java | 36 +- .../core/geometry/ogc/OGCMultiLineString.java | 121 +- .../esri/core/geometry/ogc/OGCMultiPoint.java | 155 +- .../core/geometry/ogc/OGCMultiPolygon.java | 149 +- .../core/geometry/ogc/OGCMultiSurface.java | 26 +- .../com/esri/core/geometry/ogc/OGCPoint.java | 127 +- .../esri/core/geometry/ogc/OGCPolygon.java | 177 +- .../esri/core/geometry/ogc/OGCSurface.java | 24 +- .../com/esri/core/geometry/GeometryUtils.java | 186 +- .../geometry/RandomCoordinateGenerator.java | 730 +- .../esri/core/geometry/TestAttributes.java | 628 +- .../com/esri/core/geometry/TestBuffer.java | 736 +- .../com/esri/core/geometry/TestCircles.java | 298 +- .../java/com/esri/core/geometry/TestClip.java | 772 +- .../esri/core/geometry/TestCommonMethods.java | 466 +- .../com/esri/core/geometry/TestContains.java | 40 +- .../esri/core/geometry/TestConvexHull.java | 1928 +-- .../java/com/esri/core/geometry/TestCut.java | 1058 +- .../esri/core/geometry/TestDifference.java | 1282 +- .../com/esri/core/geometry/TestDistance.java | 300 +- .../com/esri/core/geometry/TestEditShape.java | 776 +- .../geometry/TestEnvelope2DIntersector.java | 664 +- .../com/esri/core/geometry/TestEquals.java | 322 +- .../com/esri/core/geometry/TestFailed.java | 100 +- .../esri/core/geometry/TestGeneralize.java | 488 +- .../com/esri/core/geometry/TestGeodetic.java | 1594 +-- .../esri/core/geometry/TestGeomToGeoJson.java | 902 +- ...omToJSonExportSRFromWkiOrWkt_CR181369.java | 1106 +- .../esri/core/geometry/TestImportExport.java | 4294 +++--- .../geometry/TestInterpolateAttributes.java | 380 +- .../esri/core/geometry/TestIntersect2.java | 744 +- .../esri/core/geometry/TestIntersection.java | 2067 ++- .../esri/core/geometry/TestIntervalTree.java | 626 +- .../esri/core/geometry/TestJSonGeometry.java | 58 +- .../TestJSonToGeomFromWkiOrWkt_CR177613.java | 222 +- .../esri/core/geometry/TestJsonParser.java | 1068 +- .../com/esri/core/geometry/TestMathUtils.java | 62 +- .../esri/core/geometry/TestMultiPoint.java | 582 +- .../java/com/esri/core/geometry/TestOGC.java | 1762 +-- .../com/esri/core/geometry/TestOffset.java | 288 +- .../com/esri/core/geometry/TestPoint.java | 420 +- .../com/esri/core/geometry/TestPolygon.java | 2588 ++-- .../esri/core/geometry/TestPolygonUtils.java | 250 +- .../esri/core/geometry/TestProjection.java | 1084 +- .../core/geometry/TestProjectionGigsData.java | 556 +- .../esri/core/geometry/TestProximity2D.java | 510 +- .../com/esri/core/geometry/TestQuadTree.java | 986 +- .../esri/core/geometry/TestRandomPoints.java | 144 +- .../geometry/TestRasterizedGeometry2D.java | 250 +- .../com/esri/core/geometry/TestRelation.java | 10938 ++++++++-------- .../esri/core/geometry/TestSerialization.java | 704 +- .../com/esri/core/geometry/TestSimplify.java | 2618 ++-- .../core/geometry/TestSpatialReference.java | 392 +- .../com/esri/core/geometry/TestTouch.java | 860 +- .../com/esri/core/geometry/TestTreap.java | 134 +- .../com/esri/core/geometry/TestUnion.java | 300 +- .../esri/core/geometry/TestWKBSupport.java | 148 +- .../geometry/TestWkbImportOnPostgresST.java | 66 +- .../java/com/esri/core/geometry/TestWkid.java | 44 +- .../com/esri/core/geometry/TestWktParser.java | 1464 +-- .../java/com/esri/core/geometry/Utils.java | 6 +- 344 files changed, 105947 insertions(+), 105891 deletions(-) diff --git a/src/main/java/com/esri/core/geometry/AttributeStreamBase.java b/src/main/java/com/esri/core/geometry/AttributeStreamBase.java index aac7b2a3..91e8464a 100644 --- a/src/main/java/com/esri/core/geometry/AttributeStreamBase.java +++ b/src/main/java/com/esri/core/geometry/AttributeStreamBase.java @@ -34,18 +34,18 @@ */ abstract class AttributeStreamBase { - protected boolean m_bLockedInSize; - protected boolean m_bReadonly; + protected boolean m_bLockedInSize; + protected boolean m_bReadonly; - public AttributeStreamBase() { - m_bReadonly = false; - m_bLockedInSize = false; - } + public AttributeStreamBase() { + m_bReadonly = false; + m_bLockedInSize = false; + } - /** - * Returns the number of elements in the stream. - */ - public abstract int virtualSize(); + /** + * Returns the number of elements in the stream. + */ + public abstract int virtualSize(); /** * Returns an estimate of this object size in bytes. @@ -59,399 +59,399 @@ public AttributeStreamBase() { */ public abstract int getPersistence(); - /** - * Reads given element and returns it as double. - */ - public abstract double readAsDbl(int offset); - - /** - * Writes given element as double. The double is cast to the internal - * representation (truncated when int). - */ - public abstract void writeAsDbl(int offset, double d); - - /** - * Reads given element and returns it as int (truncated if double). - */ - public abstract int readAsInt(int offset); - - /** - * Writes given element as int. The int is cast to the internal - * representation. - */ - public abstract void writeAsInt(int offset, int d); - - /** - * Reads given element and returns it as int (truncated if double). - */ - public abstract long readAsInt64(int offset); - - /** - * Writes given element as int. The int is cast to the internal - * representation. - */ - public abstract void writeAsInt64(int offset, long d); - - /** - * Resizes the AttributeStream to the new size. - */ - public abstract void resize(int newSize, double defaultValue); - - /** - * Resizes the AttributeStream to the new size. - */ - public abstract void resize(int newSize); - - /** - * Resizes the AttributeStream to the new size. Does not change the capacity - * of the stream. - */ - public abstract void resizePreserveCapacity(int newSize);// java only method - - /** - * Same as resize(0) - */ - void clear(boolean bFreeMemory) { - if (bFreeMemory) - resize(0); - else - resizePreserveCapacity(0); - } - - /** - * Adds a range of elements from the source stream. The streams must be of - * the same type. - * - * @param src The source stream to read elements from. - * @param srcStart The index of the element in the source stream to start reading - * from. - * @param count The number of elements to add. - * @param bForward True if adding the elements in order of the incoming source - * stream. False if adding the elements in reverse. - * @param stride The number of elements to be grouped together if adding the - * elements in reverse. - */ - public abstract void addRange(AttributeStreamBase src, int srcStart, - int count, boolean bForward, int stride); - - /** - * Inserts a range of elements from the source stream. The streams must be - * of the same type. - * - * @param start The index where to start the insert. - * @param src The source stream to read elements from. - * @param srcStart The index of the element in the source stream to start reading - * from. - * @param count The number of elements to read from the source stream. - * @param validSize The number of valid elements in this stream. - */ - public abstract void insertRange(int start, AttributeStreamBase src, - int srcStart, int count, boolean bForward, int stride, int validSize); - - /** - * Inserts a range of elements of the given value. - * - * @param start The index where to start the insert. - * @param value The value to be inserted. - * @param count The number of elements to be inserted. - * @param validSize The number of valid elements in this stream. - */ - public abstract void insertRange(int start, double value, int count, - int validSize); - - /** - * Inserts the attributes of a given semantics from a Point geometry. - * - * @param start The index where to start the insert. - * @param pt The Point geometry holding the attributes to be inserted. - * @param semantics The attribute semantics that are being inserted. - * @param validSize The number of valid elements in this stream. - */ - public abstract void insertAttributes(int start, Point pt, int semantics, - int validSize); - - /** - * Sets a range of values to given value. - * - * @param value The value to set stream elements to. - * @param start The index of the element to start writing to. - * @param count The number of elements to set. - */ - public abstract void setRange(double value, int start, int count); - - /** - * Adds a range of elements from the source byte buffer. This stream is - * resized automatically to accomodate required number of elements. - * - * @param startElement the index of the element in this stream to start setting - * elements from. - * @param count The number of AttributeStream elements to read. - * @param src The source ByteBuffer to read elements from. - * @param sourceStart The offset from the start of the ByteBuffer in bytes. - * @param bForward When False, the source is written in reversed order. - * @param stride Used for reversed writing only to indicate the unit of - * writing. elements inside a stride are not reversed. Only the - * strides are reversed. - */ - public abstract void writeRange(int startElement, int count, - AttributeStreamBase src, int sourceStart, boolean bForward, - int stride); - - /** - * Adds a range of elements from the source byte buffer. The stream is - * resized automatically to accomodate required number of elements. - * - * @param startElement the index of the element in this stream to start setting - * elements from. - * @param count The number of AttributeStream elements to read. - * @param src The source ByteBuffer to read elements from. - * @param offsetBytes The offset from the start of the ByteBuffer in bytes. - */ - public abstract void writeRange(int startElement, int count, - ByteBuffer src, int offsetBytes, boolean bForward); - - /** - * Write a range of elements to the source byte buffer. - * - * @param srcStart The element index to start writing from. - * @param count The number of AttributeStream elements to write. - * @param dst The destination ByteBuffer. The buffer must be large enough or - * it will throw. - * @param dstOffsetBytes The offset in the destination ByteBuffer to start write - * elements from. - */ - public abstract void readRange(int srcStart, int count, ByteBuffer dst, - int dstOffsetBytes, boolean bForward); - - /** - * Erases a range from the buffer and defragments the result. - * - * @param index The index in this stream where the erasing starts. - * @param count The number of elements to be erased. - * @param validSize The number of valid elements in this stream. - */ - public abstract void eraseRange(int index, int count, int validSize); - - /** - * Reverses a range from the buffer. - * - * @param index The index in this stream where the reversing starts. - * @param count The number of elements to be reversed. - * @param stride The number of elements to be grouped together when doing the - * reverse. - */ - public abstract void reverseRange(int index, int count, int stride); - - /** - * Creates a new attribute stream for storing bytes. - * - * @param size The number of elements in the stream. - */ - public static AttributeStreamBase createByteStream(int size) { - AttributeStreamBase newStream = new AttributeStreamOfInt8(size); - return newStream; - } - - /** - * Creates a new attribute stream for storing bytes. - * - * @param size The number of elements in the stream. - * @param defaultValue The default value to fill the stream with. - */ - public static AttributeStreamBase createByteStream(int size, - byte defaultValue) { - AttributeStreamBase newStream = new AttributeStreamOfInt8(size, - defaultValue); - return newStream; - - } - - /** - * Creates a new attribute stream for storing doubles. - * - * @param size The number of elements in the stream. - */ - public static AttributeStreamBase createDoubleStream(int size) { - AttributeStreamBase newStream = new AttributeStreamOfDbl(size); - return newStream; - } - - /** - * Creates a new attribute stream for storing doubles. - * - * @param size The number of elements in the stream. - * @param defaultValue The default value to fill the stream with. - */ - public static AttributeStreamBase createDoubleStream(int size, - double defaultValue) { - AttributeStreamBase newStream = new AttributeStreamOfDbl(size, - defaultValue); - return newStream; - } - - /** - * Creats a copy of the stream that contains upto maxsize elements. - */ - public abstract AttributeStreamBase restrictedClone(int maxsize); - - /** - * Makes the stream to be readonly. Any operation that changes the content - * or size of the stream will throw. - */ - public void setReadonly() { - m_bReadonly = true; - m_bLockedInSize = true; - } - - public boolean isReadonly() { - return m_bReadonly; - } - - /** - * Lock the size of the stream. Any operation that changes the size of the - * stream will throw. - */ - public void lockSize() { - m_bLockedInSize = true; - } - - public boolean isLockedSize() { - return m_bLockedInSize; - } - - /** - * Creates a new attribute stream of given persistence type and size. - * - * @param persistence The persistence type of the stream (see VertexDescription). - * @param size The number of elements (floats, doubles, or 32 bit integers) - * of the given type in the stream. - */ - public static AttributeStreamBase createAttributeStreamWithPersistence( - int persistence, int size) { - AttributeStreamBase newStream; - switch (persistence) { - case (Persistence.enumFloat): - newStream = new AttributeStreamOfFloat(size); - break; - case (Persistence.enumDouble): - newStream = new AttributeStreamOfDbl(size); - break; - case (Persistence.enumInt32): - newStream = new AttributeStreamOfInt32(size); - break; - case (Persistence.enumInt64): - newStream = new AttributeStreamOfInt64(size); - break; - case (Persistence.enumInt8): - newStream = new AttributeStreamOfInt8(size); - break; - case (Persistence.enumInt16): - newStream = new AttributeStreamOfInt16(size); - break; - default: - throw new GeometryException("Internal Error"); - } - return newStream; - } - - /** - * Creates a new attribute stream of given persistence type and size. - * - * @param persistence The persistence type of the stream (see VertexDescription). - * @param size The number of elements (floats, doubles, or 32 bit integers) - * of the given type in the stream. - * @param defaultValue The default value to fill the stream with. - */ - public static AttributeStreamBase createAttributeStreamWithPersistence( - int persistence, int size, double defaultValue) { - AttributeStreamBase newStream; - switch (persistence) { - case (Persistence.enumFloat): - newStream = new AttributeStreamOfFloat(size, (float) defaultValue); - break; - case (Persistence.enumDouble): - newStream = new AttributeStreamOfDbl(size, (double) defaultValue); - break; - case (Persistence.enumInt32): - newStream = new AttributeStreamOfInt32(size, (int) defaultValue); - break; - case (Persistence.enumInt64): - newStream = new AttributeStreamOfInt64(size, (long) defaultValue); - break; - case (Persistence.enumInt8): - newStream = new AttributeStreamOfInt8(size, (byte) defaultValue); - break; - case (Persistence.enumInt16): - newStream = new AttributeStreamOfInt16(size, (short) defaultValue); - break; - default: - throw new GeometryException("Internal Error"); - } - return newStream; - } - - /** - * Creates a new attribute stream for the given semantics and vertex count. - * - * @param semantics The semantics of the attribute (see VertexDescription). - * @param vertexCount The number of vertices in the geometry. The actual number of - * elements in the stream is vertexCount * ncomponents. - */ - public static AttributeStreamBase createAttributeStreamWithSemantics( - int semantics, int vertexCount) { - int ncomps = VertexDescription.getComponentCount(semantics); - int persistence = VertexDescription.getPersistence(semantics); - return createAttributeStreamWithPersistence(persistence, vertexCount - * ncomps, VertexDescription.getDefaultValue(semantics)); - } - - /** - * Creates a new attribute stream for storing vertex indices. - * - * @param size The number of elements in the stream. - */ - public static AttributeStreamBase createIndexStream(int size) { - int persistence = Persistence.enumInt32;// VertexDescription.getPersistenceFromInt(NumberUtils::SizeOf((int)0)); - AttributeStreamBase newStream; - switch (persistence) { - case (Persistence.enumInt32): - newStream = new AttributeStreamOfInt32(size); - break; - case (Persistence.enumInt64): - newStream = new AttributeStreamOfInt64(size); - break; - default: - throw new GeometryException("Internal Error"); - } - return newStream; - - } - - /** - * Creates a new attribute stream for storing vertex indices. - * - * @param size The number of elements in the stream. - * @param defaultValue The default value to fill the stream with. - */ - public static AttributeStreamBase createIndexStream(int size, - int defaultValue) { - int persistence = Persistence.enumInt32;// VertexDescription.getPersistenceFromInt(NumberUtils::SizeOf((int)0)); - AttributeStreamBase newStream; - switch (persistence) { - case (Persistence.enumInt32): - newStream = new AttributeStreamOfInt32(size, (int) defaultValue); - break; - case (Persistence.enumInt64): - newStream = new AttributeStreamOfInt64(size, (long) defaultValue); - break; - default: - throw new GeometryException("Internal Error"); - } - return newStream; - } - - public abstract int calculateHashImpl(int hashCode, int start, int end); - - public abstract boolean equals(AttributeStreamBase other, int start, int end); + /** + * Reads given element and returns it as double. + */ + public abstract double readAsDbl(int offset); + + /** + * Writes given element as double. The double is cast to the internal + * representation (truncated when int). + */ + public abstract void writeAsDbl(int offset, double d); + + /** + * Reads given element and returns it as int (truncated if double). + */ + public abstract int readAsInt(int offset); + + /** + * Writes given element as int. The int is cast to the internal + * representation. + */ + public abstract void writeAsInt(int offset, int d); + + /** + * Reads given element and returns it as int (truncated if double). + */ + public abstract long readAsInt64(int offset); + + /** + * Writes given element as int. The int is cast to the internal + * representation. + */ + public abstract void writeAsInt64(int offset, long d); + + /** + * Resizes the AttributeStream to the new size. + */ + public abstract void resize(int newSize, double defaultValue); + + /** + * Resizes the AttributeStream to the new size. + */ + public abstract void resize(int newSize); + + /** + * Resizes the AttributeStream to the new size. Does not change the capacity + * of the stream. + */ + public abstract void resizePreserveCapacity(int newSize);// java only method + + /** + * Same as resize(0) + */ + void clear(boolean bFreeMemory) { + if (bFreeMemory) + resize(0); + else + resizePreserveCapacity(0); + } + + /** + * Adds a range of elements from the source stream. The streams must be of + * the same type. + * + * @param src The source stream to read elements from. + * @param srcStart The index of the element in the source stream to start reading + * from. + * @param count The number of elements to add. + * @param bForward True if adding the elements in order of the incoming source + * stream. False if adding the elements in reverse. + * @param stride The number of elements to be grouped together if adding the + * elements in reverse. + */ + public abstract void addRange(AttributeStreamBase src, int srcStart, + int count, boolean bForward, int stride); + + /** + * Inserts a range of elements from the source stream. The streams must be + * of the same type. + * + * @param start The index where to start the insert. + * @param src The source stream to read elements from. + * @param srcStart The index of the element in the source stream to start reading + * from. + * @param count The number of elements to read from the source stream. + * @param validSize The number of valid elements in this stream. + */ + public abstract void insertRange(int start, AttributeStreamBase src, + int srcStart, int count, boolean bForward, int stride, int validSize); + + /** + * Inserts a range of elements of the given value. + * + * @param start The index where to start the insert. + * @param value The value to be inserted. + * @param count The number of elements to be inserted. + * @param validSize The number of valid elements in this stream. + */ + public abstract void insertRange(int start, double value, int count, + int validSize); + + /** + * Inserts the attributes of a given semantics from a Point geometry. + * + * @param start The index where to start the insert. + * @param pt The Point geometry holding the attributes to be inserted. + * @param semantics The attribute semantics that are being inserted. + * @param validSize The number of valid elements in this stream. + */ + public abstract void insertAttributes(int start, Point pt, int semantics, + int validSize); + + /** + * Sets a range of values to given value. + * + * @param value The value to set stream elements to. + * @param start The index of the element to start writing to. + * @param count The number of elements to set. + */ + public abstract void setRange(double value, int start, int count); + + /** + * Adds a range of elements from the source byte buffer. This stream is + * resized automatically to accomodate required number of elements. + * + * @param startElement the index of the element in this stream to start setting + * elements from. + * @param count The number of AttributeStream elements to read. + * @param src The source ByteBuffer to read elements from. + * @param sourceStart The offset from the start of the ByteBuffer in bytes. + * @param bForward When False, the source is written in reversed order. + * @param stride Used for reversed writing only to indicate the unit of + * writing. elements inside a stride are not reversed. Only the + * strides are reversed. + */ + public abstract void writeRange(int startElement, int count, + AttributeStreamBase src, int sourceStart, boolean bForward, + int stride); + + /** + * Adds a range of elements from the source byte buffer. The stream is + * resized automatically to accomodate required number of elements. + * + * @param startElement the index of the element in this stream to start setting + * elements from. + * @param count The number of AttributeStream elements to read. + * @param src The source ByteBuffer to read elements from. + * @param offsetBytes The offset from the start of the ByteBuffer in bytes. + */ + public abstract void writeRange(int startElement, int count, + ByteBuffer src, int offsetBytes, boolean bForward); + + /** + * Write a range of elements to the source byte buffer. + * + * @param srcStart The element index to start writing from. + * @param count The number of AttributeStream elements to write. + * @param dst The destination ByteBuffer. The buffer must be large enough or + * it will throw. + * @param dstOffsetBytes The offset in the destination ByteBuffer to start write + * elements from. + */ + public abstract void readRange(int srcStart, int count, ByteBuffer dst, + int dstOffsetBytes, boolean bForward); + + /** + * Erases a range from the buffer and defragments the result. + * + * @param index The index in this stream where the erasing starts. + * @param count The number of elements to be erased. + * @param validSize The number of valid elements in this stream. + */ + public abstract void eraseRange(int index, int count, int validSize); + + /** + * Reverses a range from the buffer. + * + * @param index The index in this stream where the reversing starts. + * @param count The number of elements to be reversed. + * @param stride The number of elements to be grouped together when doing the + * reverse. + */ + public abstract void reverseRange(int index, int count, int stride); + + /** + * Creates a new attribute stream for storing bytes. + * + * @param size The number of elements in the stream. + */ + public static AttributeStreamBase createByteStream(int size) { + AttributeStreamBase newStream = new AttributeStreamOfInt8(size); + return newStream; + } + + /** + * Creates a new attribute stream for storing bytes. + * + * @param size The number of elements in the stream. + * @param defaultValue The default value to fill the stream with. + */ + public static AttributeStreamBase createByteStream(int size, + byte defaultValue) { + AttributeStreamBase newStream = new AttributeStreamOfInt8(size, + defaultValue); + return newStream; + + } + + /** + * Creates a new attribute stream for storing doubles. + * + * @param size The number of elements in the stream. + */ + public static AttributeStreamBase createDoubleStream(int size) { + AttributeStreamBase newStream = new AttributeStreamOfDbl(size); + return newStream; + } + + /** + * Creates a new attribute stream for storing doubles. + * + * @param size The number of elements in the stream. + * @param defaultValue The default value to fill the stream with. + */ + public static AttributeStreamBase createDoubleStream(int size, + double defaultValue) { + AttributeStreamBase newStream = new AttributeStreamOfDbl(size, + defaultValue); + return newStream; + } + + /** + * Creats a copy of the stream that contains upto maxsize elements. + */ + public abstract AttributeStreamBase restrictedClone(int maxsize); + + /** + * Makes the stream to be readonly. Any operation that changes the content + * or size of the stream will throw. + */ + public void setReadonly() { + m_bReadonly = true; + m_bLockedInSize = true; + } + + public boolean isReadonly() { + return m_bReadonly; + } + + /** + * Lock the size of the stream. Any operation that changes the size of the + * stream will throw. + */ + public void lockSize() { + m_bLockedInSize = true; + } + + public boolean isLockedSize() { + return m_bLockedInSize; + } + + /** + * Creates a new attribute stream of given persistence type and size. + * + * @param persistence The persistence type of the stream (see VertexDescription). + * @param size The number of elements (floats, doubles, or 32 bit integers) + * of the given type in the stream. + */ + public static AttributeStreamBase createAttributeStreamWithPersistence( + int persistence, int size) { + AttributeStreamBase newStream; + switch (persistence) { + case (Persistence.enumFloat): + newStream = new AttributeStreamOfFloat(size); + break; + case (Persistence.enumDouble): + newStream = new AttributeStreamOfDbl(size); + break; + case (Persistence.enumInt32): + newStream = new AttributeStreamOfInt32(size); + break; + case (Persistence.enumInt64): + newStream = new AttributeStreamOfInt64(size); + break; + case (Persistence.enumInt8): + newStream = new AttributeStreamOfInt8(size); + break; + case (Persistence.enumInt16): + newStream = new AttributeStreamOfInt16(size); + break; + default: + throw new GeometryException("Internal Error"); + } + return newStream; + } + + /** + * Creates a new attribute stream of given persistence type and size. + * + * @param persistence The persistence type of the stream (see VertexDescription). + * @param size The number of elements (floats, doubles, or 32 bit integers) + * of the given type in the stream. + * @param defaultValue The default value to fill the stream with. + */ + public static AttributeStreamBase createAttributeStreamWithPersistence( + int persistence, int size, double defaultValue) { + AttributeStreamBase newStream; + switch (persistence) { + case (Persistence.enumFloat): + newStream = new AttributeStreamOfFloat(size, (float) defaultValue); + break; + case (Persistence.enumDouble): + newStream = new AttributeStreamOfDbl(size, (double) defaultValue); + break; + case (Persistence.enumInt32): + newStream = new AttributeStreamOfInt32(size, (int) defaultValue); + break; + case (Persistence.enumInt64): + newStream = new AttributeStreamOfInt64(size, (long) defaultValue); + break; + case (Persistence.enumInt8): + newStream = new AttributeStreamOfInt8(size, (byte) defaultValue); + break; + case (Persistence.enumInt16): + newStream = new AttributeStreamOfInt16(size, (short) defaultValue); + break; + default: + throw new GeometryException("Internal Error"); + } + return newStream; + } + + /** + * Creates a new attribute stream for the given semantics and vertex count. + * + * @param semantics The semantics of the attribute (see VertexDescription). + * @param vertexCount The number of vertices in the geometry. The actual number of + * elements in the stream is vertexCount * ncomponents. + */ + public static AttributeStreamBase createAttributeStreamWithSemantics( + int semantics, int vertexCount) { + int ncomps = VertexDescription.getComponentCount(semantics); + int persistence = VertexDescription.getPersistence(semantics); + return createAttributeStreamWithPersistence(persistence, vertexCount + * ncomps, VertexDescription.getDefaultValue(semantics)); + } + + /** + * Creates a new attribute stream for storing vertex indices. + * + * @param size The number of elements in the stream. + */ + public static AttributeStreamBase createIndexStream(int size) { + int persistence = Persistence.enumInt32;// VertexDescription.getPersistenceFromInt(NumberUtils::SizeOf((int)0)); + AttributeStreamBase newStream; + switch (persistence) { + case (Persistence.enumInt32): + newStream = new AttributeStreamOfInt32(size); + break; + case (Persistence.enumInt64): + newStream = new AttributeStreamOfInt64(size); + break; + default: + throw new GeometryException("Internal Error"); + } + return newStream; + + } + + /** + * Creates a new attribute stream for storing vertex indices. + * + * @param size The number of elements in the stream. + * @param defaultValue The default value to fill the stream with. + */ + public static AttributeStreamBase createIndexStream(int size, + int defaultValue) { + int persistence = Persistence.enumInt32;// VertexDescription.getPersistenceFromInt(NumberUtils::SizeOf((int)0)); + AttributeStreamBase newStream; + switch (persistence) { + case (Persistence.enumInt32): + newStream = new AttributeStreamOfInt32(size, (int) defaultValue); + break; + case (Persistence.enumInt64): + newStream = new AttributeStreamOfInt64(size, (long) defaultValue); + break; + default: + throw new GeometryException("Internal Error"); + } + return newStream; + } + + public abstract int calculateHashImpl(int hashCode, int start, int end); + + public abstract boolean equals(AttributeStreamBase other, int start, int end); } diff --git a/src/main/java/com/esri/core/geometry/AttributeStreamOfDbl.java b/src/main/java/com/esri/core/geometry/AttributeStreamOfDbl.java index 23b33941..0b907ff2 100644 --- a/src/main/java/com/esri/core/geometry/AttributeStreamOfDbl.java +++ b/src/main/java/com/esri/core/geometry/AttributeStreamOfDbl.java @@ -36,144 +36,143 @@ final class AttributeStreamOfDbl extends AttributeStreamBase { - protected double[] m_buffer = null; - private int m_size; - - public int size() { - return m_size; - } - - public void reserve(int reserve) { - if (reserve <= 0) - return; - if (m_buffer == null) - m_buffer = new double[reserve]; - else { - if (reserve <= m_buffer.length) - return; - double[] buf = new double[reserve]; - System.arraycopy(m_buffer, 0, buf, 0, m_size); - m_buffer = buf; - } - - } - - public int capacity() { - return m_buffer != null ? m_buffer.length : 0; - } - - public AttributeStreamOfDbl(int size) { - int sz = size; - if (sz < 2) - sz = 2; - m_buffer = new double[sz]; - m_size = size; - } - - public AttributeStreamOfDbl(int size, double defaultValue) { - int sz = size; - if (sz < 2) - sz = 2; - m_buffer = new double[sz]; - m_size = size; - Arrays.fill(m_buffer, 0, size, defaultValue); - } - - public AttributeStreamOfDbl(AttributeStreamOfDbl other) { - m_buffer = other.m_buffer.clone(); - m_size = other.m_size; - } - - public AttributeStreamOfDbl(AttributeStreamOfDbl other, int maxSize) { - m_size = other.size(); - if (m_size > maxSize) - m_size = maxSize; - int sz = m_size; - if (sz < 2) - sz = 2; - m_buffer = new double[sz]; - System.arraycopy(other.m_buffer, 0, m_buffer, 0, m_size); - } - - /** - * Reads a value from the buffer at given offset. - * - * @param offset is the element number in the stream. - */ - public double read(int offset) { - return m_buffer[offset]; - } - - public double get(int offset) { - return m_buffer[offset]; - } - - /** - * Overwrites given element with new value. - * - * @param offset is the element number in the stream. - * @param value is the value to write. - */ - public void write(int offset, double value) { - if (m_bReadonly) { - throw new RuntimeException("invalid_call"); - } - m_buffer[offset] = value; - } - - public void set(int offset, double value) { - if (m_bReadonly) { - throw new RuntimeException("invalid_call"); - } - m_buffer[offset] = value; - } - - /** - * Reads a value from the buffer at given offset. - * - * @param offset is the element number in the stream. - */ - public void read(int offset, Point2D outPoint) { - outPoint.x = m_buffer[offset]; - outPoint.y = m_buffer[offset + 1]; - } - - /** - * Overwrites given element with new value. - * - * @param offset is the element number in the stream. - * @param value is the value to write. - */ - void write(int offset, Point2D point) { - if (m_bReadonly) { - throw new RuntimeException("invalid_call"); - } - m_buffer[offset] = point.x; - m_buffer[offset + 1] = point.y; - } - - /** - * Adds a new value at the end of the stream. - */ - public void add(double v) { - resize(m_size + 1); - m_buffer[m_size - 1] = v; - } - - @Override - public AttributeStreamBase restrictedClone(int maxsize) { - AttributeStreamOfDbl clone = new AttributeStreamOfDbl(this, maxsize); - return clone; - } - - @Override - public int virtualSize() { - return size(); - } + protected double[] m_buffer = null; + private int m_size; + + public int size() { + return m_size; + } + + public void reserve(int reserve) { + if (reserve <= 0) + return; + if (m_buffer == null) + m_buffer = new double[reserve]; + else { + if (reserve <= m_buffer.length) + return; + double[] buf = new double[reserve]; + System.arraycopy(m_buffer, 0, buf, 0, m_size); + m_buffer = buf; + } + + } + + public int capacity() { + return m_buffer != null ? m_buffer.length : 0; + } + + public AttributeStreamOfDbl(int size) { + int sz = size; + if (sz < 2) + sz = 2; + m_buffer = new double[sz]; + m_size = size; + } + + public AttributeStreamOfDbl(int size, double defaultValue) { + int sz = size; + if (sz < 2) + sz = 2; + m_buffer = new double[sz]; + m_size = size; + Arrays.fill(m_buffer, 0, size, defaultValue); + } + + public AttributeStreamOfDbl(AttributeStreamOfDbl other) { + m_buffer = other.m_buffer.clone(); + m_size = other.m_size; + } + + public AttributeStreamOfDbl(AttributeStreamOfDbl other, int maxSize) { + m_size = other.size(); + if (m_size > maxSize) + m_size = maxSize; + int sz = m_size; + if (sz < 2) + sz = 2; + m_buffer = new double[sz]; + System.arraycopy(other.m_buffer, 0, m_buffer, 0, m_size); + } + + /** + * Reads a value from the buffer at given offset. + * + * @param offset is the element number in the stream. + */ + public double read(int offset) { + return m_buffer[offset]; + } + + public double get(int offset) { + return m_buffer[offset]; + } + + /** + * Overwrites given element with new value. + * + * @param offset is the element number in the stream. + * @param value is the value to write. + */ + public void write(int offset, double value) { + if (m_bReadonly) { + throw new RuntimeException("invalid_call"); + } + m_buffer[offset] = value; + } + + public void set(int offset, double value) { + if (m_bReadonly) { + throw new RuntimeException("invalid_call"); + } + m_buffer[offset] = value; + } + + /** + * Reads a value from the buffer at given offset. + * + * @param offset is the element number in the stream. + */ + public void read(int offset, Point2D outPoint) { + outPoint.x = m_buffer[offset]; + outPoint.y = m_buffer[offset + 1]; + } + + /** + * Overwrites given element with new value. + * + * @param offset is the element number in the stream. + * @param value is the value to write. + */ + void write(int offset, Point2D point) { + if (m_bReadonly) { + throw new RuntimeException("invalid_call"); + } + m_buffer[offset] = point.x; + m_buffer[offset + 1] = point.y; + } + + /** + * Adds a new value at the end of the stream. + */ + public void add(double v) { + resize(m_size + 1); + m_buffer[m_size - 1] = v; + } @Override - public long estimateMemorySize() - { + public AttributeStreamBase restrictedClone(int maxsize) { + AttributeStreamOfDbl clone = new AttributeStreamOfDbl(this, maxsize); + return clone; + } + + @Override + public int virtualSize() { + return size(); + } + + @Override + public long estimateMemorySize() { return SIZE_OF_ATTRIBUTE_STREAM_OF_DBL + sizeOfDoubleArray(m_buffer.length); } @@ -193,651 +192,651 @@ public long estimateMemorySize() // } // } - @Override - public int getPersistence() { - return Persistence.enumDouble; - } - - @Override - public double readAsDbl(int offset) { - return read(offset); - } - - @Override - public int readAsInt(int offset) { - return (int) read(offset); - } - - @Override - public long readAsInt64(int offset) { - return (long) read(offset); - } - - @Override - public void resize(int newSize) { - if (m_bLockedInSize) - throw new GeometryException( - "invalid call. Attribute Stream is locked and cannot be resized."); - - if (newSize <= m_size) { - if ((newSize * 5) / 4 < m_buffer.length) {// decrease when the 25% - // margin is exceeded - double[] newBuffer = new double[newSize]; - System.arraycopy(m_buffer, 0, newBuffer, 0, newSize); - m_buffer = newBuffer; - } - m_size = newSize; - } else { - if (newSize > m_buffer.length) { - int sz = (newSize < 64) ? Math.max(newSize * 2, 4) - : (newSize * 5) / 4; - double[] newBuffer = new double[sz]; - System.arraycopy(m_buffer, 0, newBuffer, 0, m_size); - m_buffer = newBuffer; - } - - m_size = newSize; - } - } - - @Override - public void resizePreserveCapacity(int newSize)// java only method - { - if (m_buffer == null || newSize > m_buffer.length) - resize(newSize); - if (m_bLockedInSize) - throw new GeometryException( - "invalid call. Attribute Stream is locked and cannot be resized."); - - m_size = newSize; - } - - @Override - public void resize(int newSize, double defaultValue) { - if (m_bLockedInSize) - throw new GeometryException( - "invalid call. Attribute Stream is locked and cannot be resized."); - if (newSize <= m_size) { - if ((newSize * 5) / 4 < m_buffer.length) {// decrease when the 25% - // margin is exceeded - double[] newBuffer = new double[newSize]; - System.arraycopy(m_buffer, 0, newBuffer, 0, newSize); - m_buffer = newBuffer; - } - m_size = newSize; - } else { - if (newSize > m_buffer.length) { - int sz = (newSize < 64) ? Math.max(newSize * 2, 4) - : (newSize * 5) / 4; - double[] newBuffer = new double[sz]; - System.arraycopy(m_buffer, 0, newBuffer, 0, m_size); - m_buffer = newBuffer; - } - - Arrays.fill(m_buffer, m_size, newSize, defaultValue); - - m_size = newSize; - } - } - - @Override - public void writeAsDbl(int offset, double d) { - write(offset, d); - } - - @Override - public void writeAsInt64(int offset, long d) { - write(offset, (double) d); - } - - @Override - public void writeAsInt(int offset, int d) { - write(offset, (double) d); - } - - /** - * Sets the envelope from the attribute stream. The attribute stream stores - * interleaved x and y. The envelope will be set to empty if the pointCount - * is zero. - */ - public void setEnvelopeFromPoints(int pointCount, Envelope2D inOutEnv) { - if (pointCount == 0) { - inOutEnv.setEmpty(); - return; - } - if (pointCount < 0) - pointCount = size() / 2; - else if (pointCount * 2 > size()) - throw new IllegalArgumentException(); - - inOutEnv.setCoords(read(0), read(1)); - for (int i = 1; i < pointCount; i++) { - inOutEnv.mergeNE(read(i * 2), read(i * 2 + 1)); - } - } - - @Override - public int calculateHashImpl(int hashCodeIn, int start, int end) { - int hashCode = hashCodeIn; - for (int i = start, n = size(); i < n && i < end; i++) - hashCode = NumberUtils.hash(hashCode, read(i)); - - return hashCode; - } - - @Override - public boolean equals(AttributeStreamBase other, int start, int end) { - if (other == null) - return false; - - if (!(other instanceof AttributeStreamOfDbl)) - return false; - - AttributeStreamOfDbl _other = (AttributeStreamOfDbl) other; - - int size = size(); - int sizeOther = _other.size(); - - if (end > size || end > sizeOther && (size != sizeOther)) - return false; - - if (end > size) - end = size; - - for (int i = start; i < end; i++) - if (read(i) != _other.read(i)) - return false; - - return true; - } - - @Override - public void addRange(AttributeStreamBase src, int start, int count, - boolean bForward, int stride) { - if (m_bReadonly) - throw new GeometryException("invalid_call"); - - if (!bForward && (stride < 1 || count % stride != 0)) - throw new IllegalArgumentException(); - - int oldSize = m_size; - int newSize = oldSize + count; - resize(newSize); - - if (bForward) { - System.arraycopy(((AttributeStreamOfDbl) src).m_buffer, start, - m_buffer, oldSize, count); - } else { - int n = count; - - for (int i = 0; i < count; i += stride) { - n -= stride; - - for (int s = 0; s < stride; s++) { - m_buffer[oldSize + i + s] = ((AttributeStreamOfDbl) src).m_buffer[start - + n + s]; - } - } - } - } - - // public void addRange(AttributeStreamBase src, int start, - // int count, boolean bForward, int stride) { - // - // if (m_bReadonly) - // throw new GeometryException("invalid_call"); - // - // if (!bForward && (stride < 1 || count % stride != 0)) - // throw new IllegalArgumentException(); - // - // if (bForward) - // { - // double[] otherbuffer = ((AttributeStreamOfDbl) src).m_buffer; - // // int newSize = size() + count; - // // resize(newSize); - // // System.arraycopy(otherbuffer, start, m_buffer, pos, count); - // for (int i = 0; i < count; i++) { - // add(otherbuffer[start + i]); - // } - // } else { - // throw new GeometryException("not implemented for reverse add"); - // } - // } - - @Override - public void insertRange(int start, AttributeStreamBase src, int srcStart, - int count, boolean bForward, int stride, int validSize) { - if (m_bReadonly) - throw new GeometryException("invalid_call"); - - if (!bForward && (stride < 1 || count % stride != 0)) - throw new IllegalArgumentException(); - - int excess_space = m_size - validSize; - - if (excess_space < count) { - int original_size = m_size; - resize(original_size + count - excess_space); - } - - System.arraycopy(m_buffer, start, m_buffer, start + count, validSize - - start); - - if (m_buffer == ((AttributeStreamOfDbl) src).m_buffer) { - if (start < srcStart) - srcStart += count; - } - - if (bForward) { - System.arraycopy(((AttributeStreamOfDbl) src).m_buffer, srcStart, - m_buffer, start, count); - } else { - int n = count; - - for (int i = 0; i < count; i += stride) { - n -= stride; - - for (int s = 0; s < stride; s++) { - m_buffer[start + i + s] = ((AttributeStreamOfDbl) src).m_buffer[srcStart - + n + s]; - } - } - } - } - - @Override - public void insertRange(int start, double value, int count, int validSize) { - if (m_bReadonly) - throw new GeometryException("invalid_call"); - - System.arraycopy(m_buffer, start, m_buffer, start + count, validSize - - start); - - for (int i = 0; i < count; i++) { - m_buffer[start + i] = value; - } - } - - @Override - public void insertAttributes(int start, Point pt, int semantics, - int validSize) { - if (m_bReadonly) - throw new GeometryException("invalid_call"); - - int comp = VertexDescription.getComponentCount(semantics); - - System.arraycopy(m_buffer, start, m_buffer, start + comp, validSize - - start); - - for (int c = 0; c < comp; c++) { - m_buffer[start + c] = pt.getAttributeAsDbl(semantics, c); - } - } - - public void insert(int index, Point2D point, int validSize) { - if (m_bReadonly) - throw new GeometryException("invalid_call"); - - System.arraycopy(m_buffer, index, m_buffer, index + 2, validSize - - index); - m_buffer[index] = point.x; - m_buffer[index + 1] = point.y; - } - - // special case for .net 2d array syntax [,] - // writes count doubles, 2 at a time, into this stream. count is assumed to - // be even, arrayOffset is an index of the zeroth dimension (i.e. you can't - // start writing from dst[0,1]) - public void writeRange(int streamOffset, int count, double[][] src, - int arrayOffset, boolean bForward) { - if (streamOffset < 0 || count < 0 || arrayOffset < 0 - || count > NumberUtils.intMax()) - throw new IllegalArgumentException(); - - if (src.length * 2 < (int) ((arrayOffset << 1) + count)) - throw new IllegalArgumentException(); - if (count == 0) - return; - - if (size() < count + streamOffset) - resize(count + streamOffset); - - int j = streamOffset; - if (!bForward) - j += count - 1; - - final int dj = bForward ? 2 : -2; - - int end = arrayOffset + (count >> 1); - for (int i = arrayOffset; i < end; i++) { - m_buffer[j] = (double) src[i][0]; - m_buffer[j + 1] = (double) src[i][1]; - j += dj; - } - } - - public void writeRange(int streamOffset, int count, double[] src, - int arrayOffset, boolean bForward) { - if (streamOffset < 0 || count < 0 || arrayOffset < 0) - throw new IllegalArgumentException(); - - if (src.length < arrayOffset + count) - throw new IllegalArgumentException(); - if (count == 0) - return; - - if (size() < count + streamOffset) - resize(count + streamOffset); - - if (bForward) { - System.arraycopy(src, arrayOffset, m_buffer, streamOffset, count); - } else { - int j = streamOffset; - if (!bForward) - j += count - 1; - - int end = arrayOffset + count; - for (int i = arrayOffset; i < end; i++) { - m_buffer[j] = src[i]; - j--; - } - } - } - - /** - * WARNING, this is like move semantics in C++, that means outside world could mess up a polygon if they still have - * a pointer to array. Only used internally for functions where array pointer never leaves scope of function - * - * @param src - */ - protected void writeRangeMove(double[] src) { - if (src.length == 0) - return; - - if (size() < src.length) - resize(src.length); - - m_buffer = src; - } - - // reads count doubles, 2 at a time, into dst. count is assumed to be even, - // arrayOffset is an index of the zeroth dimension (i.e. you can't start - // reading into dst[0,1]) - // void AttributeStreamOfDbl::ReadRange(int streamOffset, int count, - // array^ dst, int arrayOffset, bool bForward) - - public void readRange(int streamOffset, int count, double[][] dst, - int arrayOffset, boolean bForward) { - if (streamOffset < 0 || count < 0 || arrayOffset < 0 - || count > NumberUtils.intMax() - || size() < count + streamOffset) - throw new IllegalArgumentException(); - - if (dst.length * 2 < (int) ((arrayOffset << 1) + count)) - throw new IllegalArgumentException(); - - if (count == 0) - return; - - int j = streamOffset; - if (!bForward) - j += count - 1; - - final int dj = bForward ? 2 : -2; - - int end = arrayOffset + (count >> 1); - for (int i = arrayOffset; i < end; i++) { - dst[i][0] = m_buffer[j]; - dst[i][1] = m_buffer[j + 1]; - j += dj; - } - - } - - @Override - public void eraseRange(int index, int count, int validSize) { - if (m_bReadonly) - throw new GeometryException("invalid_call"); - - if (index + count > m_size) - throw new GeometryException("invalid_call"); - - if (validSize - (index + count) > 0) { - System.arraycopy(m_buffer, index + count, m_buffer, index, - validSize - (index + count)); - } - m_size -= count; - } - - @Override - public void readRange(int srcStart, int count, ByteBuffer dst, - int dstOffset, boolean bForward) { - if (srcStart < 0 || count < 0 || dstOffset < 0 - || size() < count + srcStart) - throw new IllegalArgumentException(); - - final int elmSize = NumberUtils.sizeOf((double) 0); - - if (dst.capacity() < (int) (dstOffset + elmSize * count)) - throw new IllegalArgumentException(); - - if (count == 0) - return; - - int j = srcStart; - if (!bForward) - j += count - 1; - - final int dj = bForward ? 1 : -1; - - int offset = dstOffset; - for (int i = 0; i < count; i++, offset += elmSize) { - dst.putDouble(offset, m_buffer[j]); - j += dj; - } - } - - @Override - public void reverseRange(int index, int count, int stride) { - if (m_bReadonly) - throw new GeometryException("invalid_call"); - - if (stride < 1 || count % stride != 0) - throw new GeometryException("invalid_call"); - - int cIterations = count >> 1; - int n = count; - - for (int i = 0; i < cIterations; i += stride) { - n -= stride; - - for (int s = 0; s < stride; s++) { - double temp = m_buffer[index + i + s]; - m_buffer[index + i + s] = m_buffer[index + n + s]; - m_buffer[index + n + s] = temp; - } - } - } - - @Override - public void setRange(double value, int start, int count) { - if (start < 0 || count < 0 || start < 0 || count + start > size()) - throw new IllegalArgumentException(); - - double v = value; - Arrays.fill(m_buffer, start, start + count, v); - // for (int i = start, n = start + count; i < n; i++) - // write(i, v); - } - - @Override - public void writeRange(int startElement, int count, - AttributeStreamBase _src, int srcStart, boolean bForward, int stride) { - if (startElement < 0 || count < 0 || srcStart < 0) - throw new IllegalArgumentException(); - - if (!bForward && (stride <= 0 || (count % stride != 0))) - throw new IllegalArgumentException(); - - AttributeStreamOfDbl src = (AttributeStreamOfDbl) _src; // the input - // type must - // match - - if (src.size() < (int) (srcStart + count)) - throw new IllegalArgumentException(); - - if (count == 0) - return; - - if (size() < count + startElement) - resize(count + startElement); - - if (_src == (AttributeStreamBase) this) { - _selfWriteRangeImpl(startElement, count, srcStart, bForward, stride); - return; - } - - if (bForward) { - int j = startElement; - int offset = srcStart; - for (int i = 0; i < count; i++) { - m_buffer[j] = src.m_buffer[offset]; - j++; - offset++; - } - } else { - int j = startElement; - int offset = srcStart + count - stride; - if (stride == 1) { - for (int i = 0; i < count; i++) { - m_buffer[j] = src.m_buffer[offset]; - j++; - offset--; - } - } else { - for (int i = 0, n = count / stride; i < n; i++) { - for (int k = 0; k < stride; k++) - m_buffer[j + k] = src.m_buffer[offset + k]; - - j += stride; - offset -= stride; - } - } - } - } - - private void _selfWriteRangeImpl(int toElement, int count, int fromElement, - boolean bForward, int stride) { - - // writing from to this stream. - if (bForward) { - if (toElement == fromElement) - return; - } - - System.arraycopy(m_buffer, fromElement, m_buffer, toElement, count); - - if (bForward) - return; - // reverse what we written - int j = toElement; - int offset = toElement + count - stride; - int dj = stride; - for (int i = 0, n = count / 2; i < n; i++) { - for (int k = 0; k < stride; k++) { - double v = m_buffer[j + k]; - m_buffer[j + k] = m_buffer[offset + k]; - m_buffer[offset + k] = v; - } - j += stride; - offset -= stride; - } - - } - - @Override - public void writeRange(int startElement, int count, ByteBuffer src, - int offsetBytes, boolean bForward) { - if (startElement < 0 || count < 0 || offsetBytes < 0) - throw new IllegalArgumentException(); - - final int elmSize = NumberUtils.sizeOf((double) 0); - if (src.capacity() < (int) (offsetBytes + elmSize * count)) - throw new IllegalArgumentException(); - - if (count == 0) - return; - - if (size() < count + startElement) - resize(count + startElement); - - int j = startElement; - if (!bForward) - j += count - 1; - - final int dj = bForward ? 1 : -1; - - int offset = offsetBytes; - for (int i = 0; i < count; i++, offset += elmSize) { - m_buffer[j] = src.getDouble(offset); - j += dj; - } - - } - - public void writeRange(int streamOffset, int pointCount, Point2D[] src, - int arrayOffset, boolean bForward) { - if (streamOffset < 0 || pointCount < 0 || arrayOffset < 0) - throw new IllegalArgumentException(); - - // if (src->Length < (int)(arrayOffset + pointCount)) jt: we have lost - // the length check, not sure about this - // GEOMTHROW(invalid_argument); - - if (pointCount == 0) - return; - - if (size() < (pointCount << 1) + streamOffset) - resize((pointCount << 1) + streamOffset); - - int j = streamOffset; - if (!bForward) - j += (pointCount - 1) << 1; - - final int dj = bForward ? 2 : -2; - - // TODO: refactor to take advantage of the known block array structure - - final int i0 = arrayOffset; - pointCount += i0; - for (int i = i0; i < pointCount; i++) { - m_buffer[j] = src[i].x; - m_buffer[j + 1] = src[i].y; - j += dj; - } - } - - // Less efficient as boolean bForward set to false, as it is looping through - // half - // of the elements of the array - public void readRange(int srcStart, int count, double[] dst, int dstOffset, - boolean bForward) { - if (srcStart < 0 || count < 0 || dstOffset < 0 - || size() < count + srcStart) - throw new IllegalArgumentException(); - - if (bForward) - System.arraycopy(m_buffer, srcStart, dst, dstOffset, count); - else { - int j = dstOffset + count - 1; - for (int i = srcStart; i < count; i++) { - dst[j] = m_buffer[i]; - j--; - } - } - } - - public void sort(int start, int end) { - Arrays.sort(m_buffer, start, end); - } + @Override + public int getPersistence() { + return Persistence.enumDouble; + } + + @Override + public double readAsDbl(int offset) { + return read(offset); + } + + @Override + public int readAsInt(int offset) { + return (int) read(offset); + } + + @Override + public long readAsInt64(int offset) { + return (long) read(offset); + } + + @Override + public void resize(int newSize) { + if (m_bLockedInSize) + throw new GeometryException( + "invalid call. Attribute Stream is locked and cannot be resized."); + + if (newSize <= m_size) { + if ((newSize * 5) / 4 < m_buffer.length) {// decrease when the 25% + // margin is exceeded + double[] newBuffer = new double[newSize]; + System.arraycopy(m_buffer, 0, newBuffer, 0, newSize); + m_buffer = newBuffer; + } + m_size = newSize; + } else { + if (newSize > m_buffer.length) { + int sz = (newSize < 64) ? Math.max(newSize * 2, 4) + : (newSize * 5) / 4; + double[] newBuffer = new double[sz]; + System.arraycopy(m_buffer, 0, newBuffer, 0, m_size); + m_buffer = newBuffer; + } + + m_size = newSize; + } + } + + @Override + public void resizePreserveCapacity(int newSize)// java only method + { + if (m_buffer == null || newSize > m_buffer.length) + resize(newSize); + if (m_bLockedInSize) + throw new GeometryException( + "invalid call. Attribute Stream is locked and cannot be resized."); + + m_size = newSize; + } + + @Override + public void resize(int newSize, double defaultValue) { + if (m_bLockedInSize) + throw new GeometryException( + "invalid call. Attribute Stream is locked and cannot be resized."); + if (newSize <= m_size) { + if ((newSize * 5) / 4 < m_buffer.length) {// decrease when the 25% + // margin is exceeded + double[] newBuffer = new double[newSize]; + System.arraycopy(m_buffer, 0, newBuffer, 0, newSize); + m_buffer = newBuffer; + } + m_size = newSize; + } else { + if (newSize > m_buffer.length) { + int sz = (newSize < 64) ? Math.max(newSize * 2, 4) + : (newSize * 5) / 4; + double[] newBuffer = new double[sz]; + System.arraycopy(m_buffer, 0, newBuffer, 0, m_size); + m_buffer = newBuffer; + } + + Arrays.fill(m_buffer, m_size, newSize, defaultValue); + + m_size = newSize; + } + } + + @Override + public void writeAsDbl(int offset, double d) { + write(offset, d); + } + + @Override + public void writeAsInt64(int offset, long d) { + write(offset, (double) d); + } + + @Override + public void writeAsInt(int offset, int d) { + write(offset, (double) d); + } + + /** + * Sets the envelope from the attribute stream. The attribute stream stores + * interleaved x and y. The envelope will be set to empty if the pointCount + * is zero. + */ + public void setEnvelopeFromPoints(int pointCount, Envelope2D inOutEnv) { + if (pointCount == 0) { + inOutEnv.setEmpty(); + return; + } + if (pointCount < 0) + pointCount = size() / 2; + else if (pointCount * 2 > size()) + throw new IllegalArgumentException(); + + inOutEnv.setCoords(read(0), read(1)); + for (int i = 1; i < pointCount; i++) { + inOutEnv.mergeNE(read(i * 2), read(i * 2 + 1)); + } + } + + @Override + public int calculateHashImpl(int hashCodeIn, int start, int end) { + int hashCode = hashCodeIn; + for (int i = start, n = size(); i < n && i < end; i++) + hashCode = NumberUtils.hash(hashCode, read(i)); + + return hashCode; + } + + @Override + public boolean equals(AttributeStreamBase other, int start, int end) { + if (other == null) + return false; + + if (!(other instanceof AttributeStreamOfDbl)) + return false; + + AttributeStreamOfDbl _other = (AttributeStreamOfDbl) other; + + int size = size(); + int sizeOther = _other.size(); + + if (end > size || end > sizeOther && (size != sizeOther)) + return false; + + if (end > size) + end = size; + + for (int i = start; i < end; i++) + if (read(i) != _other.read(i)) + return false; + + return true; + } + + @Override + public void addRange(AttributeStreamBase src, int start, int count, + boolean bForward, int stride) { + if (m_bReadonly) + throw new GeometryException("invalid_call"); + + if (!bForward && (stride < 1 || count % stride != 0)) + throw new IllegalArgumentException(); + + int oldSize = m_size; + int newSize = oldSize + count; + resize(newSize); + + if (bForward) { + System.arraycopy(((AttributeStreamOfDbl) src).m_buffer, start, + m_buffer, oldSize, count); + } else { + int n = count; + + for (int i = 0; i < count; i += stride) { + n -= stride; + + for (int s = 0; s < stride; s++) { + m_buffer[oldSize + i + s] = ((AttributeStreamOfDbl) src).m_buffer[start + + n + s]; + } + } + } + } + + // public void addRange(AttributeStreamBase src, int start, + // int count, boolean bForward, int stride) { + // + // if (m_bReadonly) + // throw new GeometryException("invalid_call"); + // + // if (!bForward && (stride < 1 || count % stride != 0)) + // throw new IllegalArgumentException(); + // + // if (bForward) + // { + // double[] otherbuffer = ((AttributeStreamOfDbl) src).m_buffer; + // // int newSize = size() + count; + // // resize(newSize); + // // System.arraycopy(otherbuffer, start, m_buffer, pos, count); + // for (int i = 0; i < count; i++) { + // add(otherbuffer[start + i]); + // } + // } else { + // throw new GeometryException("not implemented for reverse add"); + // } + // } + + @Override + public void insertRange(int start, AttributeStreamBase src, int srcStart, + int count, boolean bForward, int stride, int validSize) { + if (m_bReadonly) + throw new GeometryException("invalid_call"); + + if (!bForward && (stride < 1 || count % stride != 0)) + throw new IllegalArgumentException(); + + int excess_space = m_size - validSize; + + if (excess_space < count) { + int original_size = m_size; + resize(original_size + count - excess_space); + } + + System.arraycopy(m_buffer, start, m_buffer, start + count, validSize + - start); + + if (m_buffer == ((AttributeStreamOfDbl) src).m_buffer) { + if (start < srcStart) + srcStart += count; + } + + if (bForward) { + System.arraycopy(((AttributeStreamOfDbl) src).m_buffer, srcStart, + m_buffer, start, count); + } else { + int n = count; + + for (int i = 0; i < count; i += stride) { + n -= stride; + + for (int s = 0; s < stride; s++) { + m_buffer[start + i + s] = ((AttributeStreamOfDbl) src).m_buffer[srcStart + + n + s]; + } + } + } + } + + @Override + public void insertRange(int start, double value, int count, int validSize) { + if (m_bReadonly) + throw new GeometryException("invalid_call"); + + System.arraycopy(m_buffer, start, m_buffer, start + count, validSize + - start); + + for (int i = 0; i < count; i++) { + m_buffer[start + i] = value; + } + } + + @Override + public void insertAttributes(int start, Point pt, int semantics, + int validSize) { + if (m_bReadonly) + throw new GeometryException("invalid_call"); + + int comp = VertexDescription.getComponentCount(semantics); + + System.arraycopy(m_buffer, start, m_buffer, start + comp, validSize + - start); + + for (int c = 0; c < comp; c++) { + m_buffer[start + c] = pt.getAttributeAsDbl(semantics, c); + } + } + + public void insert(int index, Point2D point, int validSize) { + if (m_bReadonly) + throw new GeometryException("invalid_call"); + + System.arraycopy(m_buffer, index, m_buffer, index + 2, validSize + - index); + m_buffer[index] = point.x; + m_buffer[index + 1] = point.y; + } + + // special case for .net 2d array syntax [,] + // writes count doubles, 2 at a time, into this stream. count is assumed to + // be even, arrayOffset is an index of the zeroth dimension (i.e. you can't + // start writing from dst[0,1]) + public void writeRange(int streamOffset, int count, double[][] src, + int arrayOffset, boolean bForward) { + if (streamOffset < 0 || count < 0 || arrayOffset < 0 + || count > NumberUtils.intMax()) + throw new IllegalArgumentException(); + + if (src.length * 2 < (int) ((arrayOffset << 1) + count)) + throw new IllegalArgumentException(); + if (count == 0) + return; + + if (size() < count + streamOffset) + resize(count + streamOffset); + + int j = streamOffset; + if (!bForward) + j += count - 1; + + final int dj = bForward ? 2 : -2; + + int end = arrayOffset + (count >> 1); + for (int i = arrayOffset; i < end; i++) { + m_buffer[j] = (double) src[i][0]; + m_buffer[j + 1] = (double) src[i][1]; + j += dj; + } + } + + public void writeRange(int streamOffset, int count, double[] src, + int arrayOffset, boolean bForward) { + if (streamOffset < 0 || count < 0 || arrayOffset < 0) + throw new IllegalArgumentException(); + + if (src.length < arrayOffset + count) + throw new IllegalArgumentException(); + if (count == 0) + return; + + if (size() < count + streamOffset) + resize(count + streamOffset); + + if (bForward) { + System.arraycopy(src, arrayOffset, m_buffer, streamOffset, count); + } else { + int j = streamOffset; + if (!bForward) + j += count - 1; + + int end = arrayOffset + count; + for (int i = arrayOffset; i < end; i++) { + m_buffer[j] = src[i]; + j--; + } + } + } + + /** + * WARNING, this is like move semantics in C++, that means outside world could mess up a polygon if they still have + * a pointer to array. Only used internally for functions where array pointer never leaves scope of function + * + * @param src + */ + protected void writeRangeMove(double[] src) { + if (src.length == 0) + return; + + if (size() < src.length) + resize(src.length); + + m_buffer = src; + } + + // reads count doubles, 2 at a time, into dst. count is assumed to be even, + // arrayOffset is an index of the zeroth dimension (i.e. you can't start + // reading into dst[0,1]) + // void AttributeStreamOfDbl::ReadRange(int streamOffset, int count, + // array^ dst, int arrayOffset, bool bForward) + + public void readRange(int streamOffset, int count, double[][] dst, + int arrayOffset, boolean bForward) { + if (streamOffset < 0 || count < 0 || arrayOffset < 0 + || count > NumberUtils.intMax() + || size() < count + streamOffset) + throw new IllegalArgumentException(); + + if (dst.length * 2 < (int) ((arrayOffset << 1) + count)) + throw new IllegalArgumentException(); + + if (count == 0) + return; + + int j = streamOffset; + if (!bForward) + j += count - 1; + + final int dj = bForward ? 2 : -2; + + int end = arrayOffset + (count >> 1); + for (int i = arrayOffset; i < end; i++) { + dst[i][0] = m_buffer[j]; + dst[i][1] = m_buffer[j + 1]; + j += dj; + } + + } + + @Override + public void eraseRange(int index, int count, int validSize) { + if (m_bReadonly) + throw new GeometryException("invalid_call"); + + if (index + count > m_size) + throw new GeometryException("invalid_call"); + + if (validSize - (index + count) > 0) { + System.arraycopy(m_buffer, index + count, m_buffer, index, + validSize - (index + count)); + } + m_size -= count; + } + + @Override + public void readRange(int srcStart, int count, ByteBuffer dst, + int dstOffset, boolean bForward) { + if (srcStart < 0 || count < 0 || dstOffset < 0 + || size() < count + srcStart) + throw new IllegalArgumentException(); + + final int elmSize = NumberUtils.sizeOf((double) 0); + + if (dst.capacity() < (int) (dstOffset + elmSize * count)) + throw new IllegalArgumentException(); + + if (count == 0) + return; + + int j = srcStart; + if (!bForward) + j += count - 1; + + final int dj = bForward ? 1 : -1; + + int offset = dstOffset; + for (int i = 0; i < count; i++, offset += elmSize) { + dst.putDouble(offset, m_buffer[j]); + j += dj; + } + } + + @Override + public void reverseRange(int index, int count, int stride) { + if (m_bReadonly) + throw new GeometryException("invalid_call"); + + if (stride < 1 || count % stride != 0) + throw new GeometryException("invalid_call"); + + int cIterations = count >> 1; + int n = count; + + for (int i = 0; i < cIterations; i += stride) { + n -= stride; + + for (int s = 0; s < stride; s++) { + double temp = m_buffer[index + i + s]; + m_buffer[index + i + s] = m_buffer[index + n + s]; + m_buffer[index + n + s] = temp; + } + } + } + + @Override + public void setRange(double value, int start, int count) { + if (start < 0 || count < 0 || start < 0 || count + start > size()) + throw new IllegalArgumentException(); + + double v = value; + Arrays.fill(m_buffer, start, start + count, v); + // for (int i = start, n = start + count; i < n; i++) + // write(i, v); + } + + @Override + public void writeRange(int startElement, int count, + AttributeStreamBase _src, int srcStart, boolean bForward, int stride) { + if (startElement < 0 || count < 0 || srcStart < 0) + throw new IllegalArgumentException(); + + if (!bForward && (stride <= 0 || (count % stride != 0))) + throw new IllegalArgumentException(); + + AttributeStreamOfDbl src = (AttributeStreamOfDbl) _src; // the input + // type must + // match + + if (src.size() < (int) (srcStart + count)) + throw new IllegalArgumentException(); + + if (count == 0) + return; + + if (size() < count + startElement) + resize(count + startElement); + + if (_src == (AttributeStreamBase) this) { + _selfWriteRangeImpl(startElement, count, srcStart, bForward, stride); + return; + } + + if (bForward) { + int j = startElement; + int offset = srcStart; + for (int i = 0; i < count; i++) { + m_buffer[j] = src.m_buffer[offset]; + j++; + offset++; + } + } else { + int j = startElement; + int offset = srcStart + count - stride; + if (stride == 1) { + for (int i = 0; i < count; i++) { + m_buffer[j] = src.m_buffer[offset]; + j++; + offset--; + } + } else { + for (int i = 0, n = count / stride; i < n; i++) { + for (int k = 0; k < stride; k++) + m_buffer[j + k] = src.m_buffer[offset + k]; + + j += stride; + offset -= stride; + } + } + } + } + + private void _selfWriteRangeImpl(int toElement, int count, int fromElement, + boolean bForward, int stride) { + + // writing from to this stream. + if (bForward) { + if (toElement == fromElement) + return; + } + + System.arraycopy(m_buffer, fromElement, m_buffer, toElement, count); + + if (bForward) + return; + // reverse what we written + int j = toElement; + int offset = toElement + count - stride; + int dj = stride; + for (int i = 0, n = count / 2; i < n; i++) { + for (int k = 0; k < stride; k++) { + double v = m_buffer[j + k]; + m_buffer[j + k] = m_buffer[offset + k]; + m_buffer[offset + k] = v; + } + j += stride; + offset -= stride; + } + + } + + @Override + public void writeRange(int startElement, int count, ByteBuffer src, + int offsetBytes, boolean bForward) { + if (startElement < 0 || count < 0 || offsetBytes < 0) + throw new IllegalArgumentException(); + + final int elmSize = NumberUtils.sizeOf((double) 0); + if (src.capacity() < (int) (offsetBytes + elmSize * count)) + throw new IllegalArgumentException(); + + if (count == 0) + return; + + if (size() < count + startElement) + resize(count + startElement); + + int j = startElement; + if (!bForward) + j += count - 1; + + final int dj = bForward ? 1 : -1; + + int offset = offsetBytes; + for (int i = 0; i < count; i++, offset += elmSize) { + m_buffer[j] = src.getDouble(offset); + j += dj; + } + + } + + public void writeRange(int streamOffset, int pointCount, Point2D[] src, + int arrayOffset, boolean bForward) { + if (streamOffset < 0 || pointCount < 0 || arrayOffset < 0) + throw new IllegalArgumentException(); + + // if (src->Length < (int)(arrayOffset + pointCount)) jt: we have lost + // the length check, not sure about this + // GEOMTHROW(invalid_argument); + + if (pointCount == 0) + return; + + if (size() < (pointCount << 1) + streamOffset) + resize((pointCount << 1) + streamOffset); + + int j = streamOffset; + if (!bForward) + j += (pointCount - 1) << 1; + + final int dj = bForward ? 2 : -2; + + // TODO: refactor to take advantage of the known block array structure + + final int i0 = arrayOffset; + pointCount += i0; + for (int i = i0; i < pointCount; i++) { + m_buffer[j] = src[i].x; + m_buffer[j + 1] = src[i].y; + j += dj; + } + } + + // Less efficient as boolean bForward set to false, as it is looping through + // half + // of the elements of the array + public void readRange(int srcStart, int count, double[] dst, int dstOffset, + boolean bForward) { + if (srcStart < 0 || count < 0 || dstOffset < 0 + || size() < count + srcStart) + throw new IllegalArgumentException(); + + if (bForward) + System.arraycopy(m_buffer, srcStart, dst, dstOffset, count); + else { + int j = dstOffset + count - 1; + for (int i = srcStart; i < count; i++) { + dst[j] = m_buffer[i]; + j--; + } + } + } + + public void sort(int start, int end) { + Arrays.sort(m_buffer, start, end); + } } diff --git a/src/main/java/com/esri/core/geometry/AttributeStreamOfFloat.java b/src/main/java/com/esri/core/geometry/AttributeStreamOfFloat.java index 3d9cab23..9156363f 100644 --- a/src/main/java/com/esri/core/geometry/AttributeStreamOfFloat.java +++ b/src/main/java/com/esri/core/geometry/AttributeStreamOfFloat.java @@ -34,119 +34,118 @@ final class AttributeStreamOfFloat extends AttributeStreamBase { - private float[] m_buffer = null; - private int m_size; - - public int size() { - return m_size; - } - - public void reserve(int reserve)// only in Java - { - if (reserve <= 0) - return; - if (m_buffer == null) - m_buffer = new float[reserve]; - else { - if (reserve <= m_buffer.length) - return; - float[] buf = new float[reserve]; - System.arraycopy(m_buffer, 0, buf, 0, m_size); - m_buffer = buf; - } - - } - - public int capacity() { - return m_buffer != null ? m_buffer.length : 0; - } - - public AttributeStreamOfFloat(int size) { - int sz = size; - if (sz < 2) - sz = 2; - m_buffer = new float[sz]; - m_size = size; - } - - public AttributeStreamOfFloat(int size, float defaultValue) { - int sz = size; - if (sz < 2) - sz = 2; - m_buffer = new float[sz]; - m_size = size; - for (int i = 0; i < size; i++) - m_buffer[i] = defaultValue; - } - - public AttributeStreamOfFloat(AttributeStreamOfFloat other) { - m_buffer = other.m_buffer.clone(); - m_size = other.m_size; - } - - public AttributeStreamOfFloat(AttributeStreamOfFloat other, int maxSize) { - m_size = other.size(); - if (m_size > maxSize) - m_size = maxSize; - int sz = m_size; - if (sz < 2) - sz = 2; - m_buffer = new float[sz]; - System.arraycopy(other.m_buffer, 0, m_buffer, 0, m_size); - } - - /** - * Reads a value from the buffer at given offset. - * - * @param offset is the element number in the stream. - */ - public float read(int offset) { - return m_buffer[offset]; - } - - /** - * Overwrites given element with new value. - * - * @param offset is the element number in the stream. - * @param value is the value to write. - */ - public void write(int offset, float value) { - if (m_bReadonly) { - throw new RuntimeException("invalid_call"); - } - m_buffer[offset] = value; - } - - /** - * Adds a new value at the end of the stream. - * - * @param offset is the element number in the stream. - * @param value is the value to write. - */ - public void add(float v) { - resize(m_size + 1); - m_buffer[m_size - 1] = v; - } - - @Override - public AttributeStreamBase restrictedClone(int maxsize) { - int len = m_size; - int newSize = maxsize < len ? maxsize : len; - float[] newBuffer = new float[newSize]; - System.arraycopy(m_buffer, 0, newBuffer, 0, newSize); - m_buffer = newBuffer; - m_size = newSize; - return this; - } - - @Override - public int virtualSize() { - return size(); - } + private float[] m_buffer = null; + private int m_size; - @Override - public long estimateMemorySize() + public int size() { + return m_size; + } + + public void reserve(int reserve)// only in Java { + if (reserve <= 0) + return; + if (m_buffer == null) + m_buffer = new float[reserve]; + else { + if (reserve <= m_buffer.length) + return; + float[] buf = new float[reserve]; + System.arraycopy(m_buffer, 0, buf, 0, m_size); + m_buffer = buf; + } + + } + + public int capacity() { + return m_buffer != null ? m_buffer.length : 0; + } + + public AttributeStreamOfFloat(int size) { + int sz = size; + if (sz < 2) + sz = 2; + m_buffer = new float[sz]; + m_size = size; + } + + public AttributeStreamOfFloat(int size, float defaultValue) { + int sz = size; + if (sz < 2) + sz = 2; + m_buffer = new float[sz]; + m_size = size; + for (int i = 0; i < size; i++) + m_buffer[i] = defaultValue; + } + + public AttributeStreamOfFloat(AttributeStreamOfFloat other) { + m_buffer = other.m_buffer.clone(); + m_size = other.m_size; + } + + public AttributeStreamOfFloat(AttributeStreamOfFloat other, int maxSize) { + m_size = other.size(); + if (m_size > maxSize) + m_size = maxSize; + int sz = m_size; + if (sz < 2) + sz = 2; + m_buffer = new float[sz]; + System.arraycopy(other.m_buffer, 0, m_buffer, 0, m_size); + } + + /** + * Reads a value from the buffer at given offset. + * + * @param offset is the element number in the stream. + */ + public float read(int offset) { + return m_buffer[offset]; + } + + /** + * Overwrites given element with new value. + * + * @param offset is the element number in the stream. + * @param value is the value to write. + */ + public void write(int offset, float value) { + if (m_bReadonly) { + throw new RuntimeException("invalid_call"); + } + m_buffer[offset] = value; + } + + /** + * Adds a new value at the end of the stream. + * + * @param offset is the element number in the stream. + * @param value is the value to write. + */ + public void add(float v) { + resize(m_size + 1); + m_buffer[m_size - 1] = v; + } + + @Override + public AttributeStreamBase restrictedClone(int maxsize) { + int len = m_size; + int newSize = maxsize < len ? maxsize : len; + float[] newBuffer = new float[newSize]; + System.arraycopy(m_buffer, 0, newBuffer, 0, newSize); + m_buffer = newBuffer; + m_size = newSize; + return this; + } + + @Override + public int virtualSize() { + return size(); + } + + @Override + public long estimateMemorySize() { return SIZE_OF_ATTRIBUTE_STREAM_OF_FLOAT + sizeOfFloatArray(m_buffer.length); } @@ -155,462 +154,462 @@ public int getPersistence() { return Persistence.enumFloat; } - @Override - public double readAsDbl(int offset) { - return read(offset); - } - - @Override - public int readAsInt(int offset) { - return (int) read(offset); - } - - @Override - public long readAsInt64(int offset) { - return (long) read(offset); - } - - @Override - public void resize(int newSize) { - if (m_bLockedInSize) - throw new GeometryException( - "invalid call. Attribute Stream is locked and cannot be resized."); - - if (newSize <= m_size) { - if ((newSize * 5) / 4 < m_buffer.length) {// decrease when the 25% - // margin is exceeded - float[] newBuffer = new float[newSize]; - System.arraycopy(m_buffer, 0, newBuffer, 0, newSize); - m_buffer = newBuffer; - } - m_size = newSize; - } else { - if (newSize > m_buffer.length) { - int sz = (newSize < 64) ? Math.max(newSize * 2, 4) - : (newSize * 5) / 4; - float[] newBuffer = new float[sz]; - System.arraycopy(m_buffer, 0, newBuffer, 0, m_size); - m_buffer = newBuffer; - } - - m_size = newSize; - } - } - - @Override - public void resizePreserveCapacity(int newSize)// java only method - { - if (m_buffer == null || newSize > m_buffer.length) - resize(newSize); - if (m_bLockedInSize) - throw new GeometryException( - "invalid call. Attribute Stream is locked and cannot be resized."); - - m_size = newSize; - } - - @Override - public void resize(int newSize, double defaultValue) { - if (m_bLockedInSize) - throw new GeometryException( - "invalid call. Attribute Stream is locked and cannot be resized."); - if (newSize <= m_size) { - if ((newSize * 5) / 4 < m_buffer.length) {// decrease when the 25% - // margin is exceeded - float[] newBuffer = new float[newSize]; - System.arraycopy(m_buffer, 0, newBuffer, 0, newSize); - m_buffer = newBuffer; - } - m_size = newSize; - } else { - if (newSize > m_buffer.length) { - int sz = (newSize < 64) ? Math.max(newSize * 2, 4) - : (newSize * 5) / 4; - float[] newBuffer = new float[sz]; - System.arraycopy(m_buffer, 0, newBuffer, 0, m_size); - m_buffer = newBuffer; - } - - for (int i = m_size; i < newSize; i++) - m_buffer[i] = (float) defaultValue; - - m_size = newSize; - } - } - - @Override - public void writeAsDbl(int offset, double d) { - write(offset, (float) d); - } - - @Override - public void writeAsInt64(int offset, long d) { - write(offset, (float) d); - } - - @Override - public void writeAsInt(int offset, int d) { - write(offset, (float) d); - } - - // @Override - // public void writeRange(int srcStart, int count, ByteBuffer dst, - // int dstOffsetBytes) { - // // TODO Auto-generated method stub - // - // } - - @Override - public int calculateHashImpl(int hashCode, int start, int end) { - for (int i = start, n = size(); i < n && i < end; i++) - hashCode = NumberUtils.hash(hashCode, read(i)); - - return hashCode; - } - - @Override - public boolean equals(AttributeStreamBase other, int start, int end) { - if (other == null) - return false; - - if (!(other instanceof AttributeStreamOfFloat)) - return false; - - AttributeStreamOfFloat _other = (AttributeStreamOfFloat) other; - - int size = size(); - int sizeOther = _other.size(); - - if (end > size || end > sizeOther && (size != sizeOther)) - return false; - - if (end > size) - end = size; - - for (int i = start; i < end; i++) - if (read(i) != _other.read(i)) - return false; - - return true; - } - - // public void addRange(AttributeStreamBase src, int srcStartIndex, int - // count) { - // if ((src == this) || !(src instanceof AttributeStreamOfFloat)) - // throw new IllegalArgumentException(); - // - // AttributeStreamOfFloat as = (AttributeStreamOfFloat) src; - // - // int len = as.size(); - // int oldSize = m_size; - // resize(oldSize + len, 0); - // for (int i = 0; i < len; i++) { - // m_buffer[oldSize + i] = as.read(i); - // } - // } - - @Override - public void addRange(AttributeStreamBase src, int start, int count, - boolean bForward, int stride) { - if (m_bReadonly) - throw new GeometryException("invalid_call"); - - if (!bForward && (stride < 1 || count % stride != 0)) - throw new IllegalArgumentException(); - - int oldSize = m_size; - int newSize = oldSize + count; - resize(newSize); - - if (bForward) { - System.arraycopy(((AttributeStreamOfFloat) src).m_buffer, start, - m_buffer, oldSize, count); - } else { - int n = count; - - for (int i = 0; i < count; i += stride) { - n -= stride; - - for (int s = 0; s < stride; s++) { - m_buffer[oldSize + i + s] = ((AttributeStreamOfFloat) src).m_buffer[start - + n + s]; - } - } - } - } - - @Override - public void insertRange(int start, AttributeStreamBase src, int srcStart, - int count, boolean bForward, int stride, int validSize) { - if (m_bReadonly) - throw new GeometryException("invalid_call"); - - if (!bForward && (stride < 1 || count % stride != 0)) - throw new IllegalArgumentException(); - - System.arraycopy(m_buffer, start, m_buffer, start + count, validSize - - start); - - if (m_buffer == ((AttributeStreamOfFloat) src).m_buffer) { - if (start < srcStart) - srcStart += count; - } - - if (bForward) { - System.arraycopy(((AttributeStreamOfFloat) src).m_buffer, srcStart, - m_buffer, start, count); - } else { - int n = count; - - for (int i = 0; i < count; i += stride) { - n -= stride; - - for (int s = 0; s < stride; s++) { - m_buffer[start + i + s] = ((AttributeStreamOfFloat) src).m_buffer[srcStart - + n + s]; - } - } - } - } - - @Override - public void insertRange(int start, double value, int count, int validSize) { - if (m_bReadonly) - throw new GeometryException("invalid_call"); - - System.arraycopy(m_buffer, start, m_buffer, start + count, validSize - - start); - - float v = (float) value; - for (int i = 0; i < count; i++) { - m_buffer[start + i] = v; - } - } - - @Override - public void insertAttributes(int start, Point pt, int semantics, - int validSize) { - if (m_bReadonly) - throw new GeometryException("invalid_call"); - - int comp = VertexDescription.getComponentCount(semantics); - - System.arraycopy(m_buffer, start, m_buffer, start + comp, validSize - - start); - - for (int c = 0; c < comp; c++) { - m_buffer[start + c] = (float) pt.getAttributeAsDbl(semantics, c); - } - } - - @Override - public void eraseRange(int index, int count, int validSize) { - if (m_bReadonly) - throw new GeometryException("invalid_call"); - - if (index + count > m_size) - throw new GeometryException("invalid_call"); - - System.arraycopy(m_buffer, index + count, m_buffer, index, validSize - - (index + count)); - m_size -= count; - } - - @Override - public void readRange(int srcStart, int count, ByteBuffer dst, - int dstOffset, boolean bForward) { - if (srcStart < 0 || count < 0 || dstOffset < 0 - || size() < count + srcStart) - throw new IllegalArgumentException(); - - final int elmSize = NumberUtils.sizeOf((double) 0); - - if (dst.capacity() < (int) (dstOffset + elmSize * count)) - throw new IllegalArgumentException(); - - if (count == 0) - return; - - int j = srcStart; - if (!bForward) - j += count - 1; - - final int dj = bForward ? 1 : -1; - - int offset = dstOffset; - for (int i = 0; i < count; i++, offset += elmSize) { - dst.putFloat(offset, m_buffer[j]); - j += dj; - } - - } - - @Override - public void reverseRange(int index, int count, int stride) { - if (m_bReadonly) - throw new GeometryException("invalid_call"); - - if (stride < 1 || count % stride != 0) - throw new GeometryException("invalid_call"); - - int cIterations = count >> 1; - int n = count; - - for (int i = 0; i < cIterations; i += stride) { - n -= stride; - - for (int s = 0; s < stride; s++) { - float temp = m_buffer[index + i + s]; - m_buffer[index + i + s] = m_buffer[index + n + s]; - m_buffer[index + n + s] = temp; - } - } - } - - @Override - public void setRange(double value, int start, int count) { - if (start < 0 || count < 0 || start < 0 || count + start > size()) - throw new IllegalArgumentException(); - - float v = (float) value; - for (int i = start, n = start + count; i < n; i++) - write(i, v); - } - - @Override - public void writeRange(int startElement, int count, - AttributeStreamBase _src, int srcStart, boolean bForward, int stride) { - if (startElement < 0 || count < 0 || srcStart < 0) - throw new IllegalArgumentException(); - - if (!bForward && (stride <= 0 || (count % stride != 0))) - throw new IllegalArgumentException(); - - AttributeStreamOfFloat src = (AttributeStreamOfFloat) _src; // the input - // type must - // match - - if (src.size() < (int) (srcStart + count)) - throw new IllegalArgumentException(); - - if (count == 0) - return; - - if (size() < count + startElement) - resize(count + startElement); - - if (_src == (AttributeStreamBase) this) { - _selfWriteRangeImpl(startElement, count, srcStart, bForward, stride); - return; - } - - if (bForward) { - int j = startElement; - int offset = srcStart; - for (int i = 0; i < count; i++) { - m_buffer[j] = src.m_buffer[offset]; - j++; - offset++; - } - } else { - int j = startElement; - int offset = srcStart + count - stride; - if (stride == 1) { - for (int i = 0; i < count; i++) { - m_buffer[j] = src.m_buffer[offset]; - j++; - offset--; - } - } else { - for (int i = 0, n = count / stride; i < n; i++) { - for (int k = 0; k < stride; k++) - m_buffer[j + k] = src.m_buffer[offset + k]; - - j += stride; - offset -= stride; - } - } - } - } - - private void _selfWriteRangeImpl(int toElement, int count, int fromElement, - boolean bForward, int stride) { - - // writing from to this stream. - if (bForward) { - if (toElement == fromElement) - return; - } - - int offset; - int j; - int dj; - - if (fromElement < toElement) { - offset = fromElement + count - stride; - j = toElement + count - stride; - for (int i = 0, n = count / 2; i < n; i++) { - for (int k = 0; k < stride; k++) { - m_buffer[j + k] = m_buffer[offset + k]; - } - j -= stride; - offset -= stride; - } - } else { - offset = fromElement; - j = toElement; - dj = 1; - for (int i = 0; i < count; i++) { - m_buffer[j] = m_buffer[offset]; - j += 1; - offset++; - } - } - - if (!bForward) { - // reverse what we written - j = toElement; - offset = toElement + count - stride; - dj = stride; - for (int i = 0, n = count / 2; i < n; i++) { - for (int k = 0; k < stride; k++) { - float v = m_buffer[j + k]; - m_buffer[j + k] = m_buffer[offset + k]; - m_buffer[offset + k] = v; - } - j += stride; - offset -= stride; - } - } - } - - @Override - public void writeRange(int startElement, int count, ByteBuffer src, - int offsetBytes, boolean bForward) { - if (startElement < 0 || count < 0 || offsetBytes < 0) - throw new IllegalArgumentException(); - - final int elmSize = NumberUtils.sizeOf((double) 0); - if (src.capacity() < (int) (offsetBytes + elmSize * count)) - throw new IllegalArgumentException(); - - if (count == 0) - return; - - if (size() < count + startElement) - resize(count + startElement); - - int j = startElement; - if (!bForward) - j += count - 1; - - final int dj = bForward ? 1 : -1; - - int offset = offsetBytes; - for (int i = 0; i < count; i++, offset += elmSize) { - m_buffer[j] = src.getFloat(offset); - j += dj; - } - - } + @Override + public double readAsDbl(int offset) { + return read(offset); + } + + @Override + public int readAsInt(int offset) { + return (int) read(offset); + } + + @Override + public long readAsInt64(int offset) { + return (long) read(offset); + } + + @Override + public void resize(int newSize) { + if (m_bLockedInSize) + throw new GeometryException( + "invalid call. Attribute Stream is locked and cannot be resized."); + + if (newSize <= m_size) { + if ((newSize * 5) / 4 < m_buffer.length) {// decrease when the 25% + // margin is exceeded + float[] newBuffer = new float[newSize]; + System.arraycopy(m_buffer, 0, newBuffer, 0, newSize); + m_buffer = newBuffer; + } + m_size = newSize; + } else { + if (newSize > m_buffer.length) { + int sz = (newSize < 64) ? Math.max(newSize * 2, 4) + : (newSize * 5) / 4; + float[] newBuffer = new float[sz]; + System.arraycopy(m_buffer, 0, newBuffer, 0, m_size); + m_buffer = newBuffer; + } + + m_size = newSize; + } + } + + @Override + public void resizePreserveCapacity(int newSize)// java only method + { + if (m_buffer == null || newSize > m_buffer.length) + resize(newSize); + if (m_bLockedInSize) + throw new GeometryException( + "invalid call. Attribute Stream is locked and cannot be resized."); + + m_size = newSize; + } + + @Override + public void resize(int newSize, double defaultValue) { + if (m_bLockedInSize) + throw new GeometryException( + "invalid call. Attribute Stream is locked and cannot be resized."); + if (newSize <= m_size) { + if ((newSize * 5) / 4 < m_buffer.length) {// decrease when the 25% + // margin is exceeded + float[] newBuffer = new float[newSize]; + System.arraycopy(m_buffer, 0, newBuffer, 0, newSize); + m_buffer = newBuffer; + } + m_size = newSize; + } else { + if (newSize > m_buffer.length) { + int sz = (newSize < 64) ? Math.max(newSize * 2, 4) + : (newSize * 5) / 4; + float[] newBuffer = new float[sz]; + System.arraycopy(m_buffer, 0, newBuffer, 0, m_size); + m_buffer = newBuffer; + } + + for (int i = m_size; i < newSize; i++) + m_buffer[i] = (float) defaultValue; + + m_size = newSize; + } + } + + @Override + public void writeAsDbl(int offset, double d) { + write(offset, (float) d); + } + + @Override + public void writeAsInt64(int offset, long d) { + write(offset, (float) d); + } + + @Override + public void writeAsInt(int offset, int d) { + write(offset, (float) d); + } + + // @Override + // public void writeRange(int srcStart, int count, ByteBuffer dst, + // int dstOffsetBytes) { + // // TODO Auto-generated method stub + // + // } + + @Override + public int calculateHashImpl(int hashCode, int start, int end) { + for (int i = start, n = size(); i < n && i < end; i++) + hashCode = NumberUtils.hash(hashCode, read(i)); + + return hashCode; + } + + @Override + public boolean equals(AttributeStreamBase other, int start, int end) { + if (other == null) + return false; + + if (!(other instanceof AttributeStreamOfFloat)) + return false; + + AttributeStreamOfFloat _other = (AttributeStreamOfFloat) other; + + int size = size(); + int sizeOther = _other.size(); + + if (end > size || end > sizeOther && (size != sizeOther)) + return false; + + if (end > size) + end = size; + + for (int i = start; i < end; i++) + if (read(i) != _other.read(i)) + return false; + + return true; + } + + // public void addRange(AttributeStreamBase src, int srcStartIndex, int + // count) { + // if ((src == this) || !(src instanceof AttributeStreamOfFloat)) + // throw new IllegalArgumentException(); + // + // AttributeStreamOfFloat as = (AttributeStreamOfFloat) src; + // + // int len = as.size(); + // int oldSize = m_size; + // resize(oldSize + len, 0); + // for (int i = 0; i < len; i++) { + // m_buffer[oldSize + i] = as.read(i); + // } + // } + + @Override + public void addRange(AttributeStreamBase src, int start, int count, + boolean bForward, int stride) { + if (m_bReadonly) + throw new GeometryException("invalid_call"); + + if (!bForward && (stride < 1 || count % stride != 0)) + throw new IllegalArgumentException(); + + int oldSize = m_size; + int newSize = oldSize + count; + resize(newSize); + + if (bForward) { + System.arraycopy(((AttributeStreamOfFloat) src).m_buffer, start, + m_buffer, oldSize, count); + } else { + int n = count; + + for (int i = 0; i < count; i += stride) { + n -= stride; + + for (int s = 0; s < stride; s++) { + m_buffer[oldSize + i + s] = ((AttributeStreamOfFloat) src).m_buffer[start + + n + s]; + } + } + } + } + + @Override + public void insertRange(int start, AttributeStreamBase src, int srcStart, + int count, boolean bForward, int stride, int validSize) { + if (m_bReadonly) + throw new GeometryException("invalid_call"); + + if (!bForward && (stride < 1 || count % stride != 0)) + throw new IllegalArgumentException(); + + System.arraycopy(m_buffer, start, m_buffer, start + count, validSize + - start); + + if (m_buffer == ((AttributeStreamOfFloat) src).m_buffer) { + if (start < srcStart) + srcStart += count; + } + + if (bForward) { + System.arraycopy(((AttributeStreamOfFloat) src).m_buffer, srcStart, + m_buffer, start, count); + } else { + int n = count; + + for (int i = 0; i < count; i += stride) { + n -= stride; + + for (int s = 0; s < stride; s++) { + m_buffer[start + i + s] = ((AttributeStreamOfFloat) src).m_buffer[srcStart + + n + s]; + } + } + } + } + + @Override + public void insertRange(int start, double value, int count, int validSize) { + if (m_bReadonly) + throw new GeometryException("invalid_call"); + + System.arraycopy(m_buffer, start, m_buffer, start + count, validSize + - start); + + float v = (float) value; + for (int i = 0; i < count; i++) { + m_buffer[start + i] = v; + } + } + + @Override + public void insertAttributes(int start, Point pt, int semantics, + int validSize) { + if (m_bReadonly) + throw new GeometryException("invalid_call"); + + int comp = VertexDescription.getComponentCount(semantics); + + System.arraycopy(m_buffer, start, m_buffer, start + comp, validSize + - start); + + for (int c = 0; c < comp; c++) { + m_buffer[start + c] = (float) pt.getAttributeAsDbl(semantics, c); + } + } + + @Override + public void eraseRange(int index, int count, int validSize) { + if (m_bReadonly) + throw new GeometryException("invalid_call"); + + if (index + count > m_size) + throw new GeometryException("invalid_call"); + + System.arraycopy(m_buffer, index + count, m_buffer, index, validSize + - (index + count)); + m_size -= count; + } + + @Override + public void readRange(int srcStart, int count, ByteBuffer dst, + int dstOffset, boolean bForward) { + if (srcStart < 0 || count < 0 || dstOffset < 0 + || size() < count + srcStart) + throw new IllegalArgumentException(); + + final int elmSize = NumberUtils.sizeOf((double) 0); + + if (dst.capacity() < (int) (dstOffset + elmSize * count)) + throw new IllegalArgumentException(); + + if (count == 0) + return; + + int j = srcStart; + if (!bForward) + j += count - 1; + + final int dj = bForward ? 1 : -1; + + int offset = dstOffset; + for (int i = 0; i < count; i++, offset += elmSize) { + dst.putFloat(offset, m_buffer[j]); + j += dj; + } + + } + + @Override + public void reverseRange(int index, int count, int stride) { + if (m_bReadonly) + throw new GeometryException("invalid_call"); + + if (stride < 1 || count % stride != 0) + throw new GeometryException("invalid_call"); + + int cIterations = count >> 1; + int n = count; + + for (int i = 0; i < cIterations; i += stride) { + n -= stride; + + for (int s = 0; s < stride; s++) { + float temp = m_buffer[index + i + s]; + m_buffer[index + i + s] = m_buffer[index + n + s]; + m_buffer[index + n + s] = temp; + } + } + } + + @Override + public void setRange(double value, int start, int count) { + if (start < 0 || count < 0 || start < 0 || count + start > size()) + throw new IllegalArgumentException(); + + float v = (float) value; + for (int i = start, n = start + count; i < n; i++) + write(i, v); + } + + @Override + public void writeRange(int startElement, int count, + AttributeStreamBase _src, int srcStart, boolean bForward, int stride) { + if (startElement < 0 || count < 0 || srcStart < 0) + throw new IllegalArgumentException(); + + if (!bForward && (stride <= 0 || (count % stride != 0))) + throw new IllegalArgumentException(); + + AttributeStreamOfFloat src = (AttributeStreamOfFloat) _src; // the input + // type must + // match + + if (src.size() < (int) (srcStart + count)) + throw new IllegalArgumentException(); + + if (count == 0) + return; + + if (size() < count + startElement) + resize(count + startElement); + + if (_src == (AttributeStreamBase) this) { + _selfWriteRangeImpl(startElement, count, srcStart, bForward, stride); + return; + } + + if (bForward) { + int j = startElement; + int offset = srcStart; + for (int i = 0; i < count; i++) { + m_buffer[j] = src.m_buffer[offset]; + j++; + offset++; + } + } else { + int j = startElement; + int offset = srcStart + count - stride; + if (stride == 1) { + for (int i = 0; i < count; i++) { + m_buffer[j] = src.m_buffer[offset]; + j++; + offset--; + } + } else { + for (int i = 0, n = count / stride; i < n; i++) { + for (int k = 0; k < stride; k++) + m_buffer[j + k] = src.m_buffer[offset + k]; + + j += stride; + offset -= stride; + } + } + } + } + + private void _selfWriteRangeImpl(int toElement, int count, int fromElement, + boolean bForward, int stride) { + + // writing from to this stream. + if (bForward) { + if (toElement == fromElement) + return; + } + + int offset; + int j; + int dj; + + if (fromElement < toElement) { + offset = fromElement + count - stride; + j = toElement + count - stride; + for (int i = 0, n = count / 2; i < n; i++) { + for (int k = 0; k < stride; k++) { + m_buffer[j + k] = m_buffer[offset + k]; + } + j -= stride; + offset -= stride; + } + } else { + offset = fromElement; + j = toElement; + dj = 1; + for (int i = 0; i < count; i++) { + m_buffer[j] = m_buffer[offset]; + j += 1; + offset++; + } + } + + if (!bForward) { + // reverse what we written + j = toElement; + offset = toElement + count - stride; + dj = stride; + for (int i = 0, n = count / 2; i < n; i++) { + for (int k = 0; k < stride; k++) { + float v = m_buffer[j + k]; + m_buffer[j + k] = m_buffer[offset + k]; + m_buffer[offset + k] = v; + } + j += stride; + offset -= stride; + } + } + } + + @Override + public void writeRange(int startElement, int count, ByteBuffer src, + int offsetBytes, boolean bForward) { + if (startElement < 0 || count < 0 || offsetBytes < 0) + throw new IllegalArgumentException(); + + final int elmSize = NumberUtils.sizeOf((double) 0); + if (src.capacity() < (int) (offsetBytes + elmSize * count)) + throw new IllegalArgumentException(); + + if (count == 0) + return; + + if (size() < count + startElement) + resize(count + startElement); + + int j = startElement; + if (!bForward) + j += count - 1; + + final int dj = bForward ? 1 : -1; + + int offset = offsetBytes; + for (int i = 0; i < count; i++, offset += elmSize) { + m_buffer[j] = src.getFloat(offset); + j += dj; + } + + } } diff --git a/src/main/java/com/esri/core/geometry/AttributeStreamOfInt16.java b/src/main/java/com/esri/core/geometry/AttributeStreamOfInt16.java index a75ede20..b9400dd7 100644 --- a/src/main/java/com/esri/core/geometry/AttributeStreamOfInt16.java +++ b/src/main/java/com/esri/core/geometry/AttributeStreamOfInt16.java @@ -36,116 +36,115 @@ final class AttributeStreamOfInt16 extends AttributeStreamBase { private short[] m_buffer = null; private int m_size; - public int size() { - return m_size; - } - - public void reserve(int reserve)// only in Java - { - if (reserve <= 0) - return; - if (m_buffer == null) - m_buffer = new short[reserve]; - else { - if (reserve <= m_buffer.length) - return; - short[] buf = new short[reserve]; - System.arraycopy(m_buffer, 0, buf, 0, m_size); - m_buffer = buf; - } - - } - - public int capacity() { - return m_buffer != null ? m_buffer.length : 0; - } - - public AttributeStreamOfInt16(int size) { - int sz = size; - if (sz < 2) - sz = 2; - m_buffer = new short[sz]; - m_size = size; - } - - public AttributeStreamOfInt16(int size, short defaultValue) { - int sz = size; - if (sz < 2) - sz = 2; - m_buffer = new short[sz]; - m_size = size; - for (int i = 0; i < size; i++) - m_buffer[i] = defaultValue; - } - - public AttributeStreamOfInt16(AttributeStreamOfInt16 other) { - m_buffer = other.m_buffer.clone(); - m_size = other.m_size; - } - - public AttributeStreamOfInt16(AttributeStreamOfInt16 other, int maxSize) { - m_size = other.size(); - if (m_size > maxSize) - m_size = maxSize; - int sz = m_size; - if (sz < 2) - sz = 2; - m_buffer = new short[sz]; - System.arraycopy(other.m_buffer, 0, m_buffer, 0, m_size); - } - - /** - * Reads a value from the buffer at given offset. - * - * @param offset is the element number in the stream. - */ - public short read(int offset) { - return m_buffer[offset]; - } - - /** - * Overwrites given element with new value. - * - * @param offset is the element number in the stream. - * @param value is the value to write. - */ - public void write(int offset, short value) { - if (m_bReadonly) { - throw new RuntimeException("invalid_call"); - } - m_buffer[offset] = value; - } - - /** - * Adds a new value at the end of the stream. - * - * @param offset is the element number in the stream. - * @param value is the value to write. - */ - public void add(short v) { - resize(m_size + 1); - m_buffer[m_size - 1] = v; - } - - @Override - public AttributeStreamBase restrictedClone(int maxsize) { - int len = m_size; - int newSize = maxsize < len ? maxsize : len; - short[] newBuffer = new short[newSize]; - System.arraycopy(m_buffer, 0, newBuffer, 0, newSize); - m_buffer = newBuffer; - m_size = newSize; - return this; - } - - @Override - public int virtualSize() { - return size(); - } + public int size() { + return m_size; + } - @Override - public long estimateMemorySize() + public void reserve(int reserve)// only in Java { + if (reserve <= 0) + return; + if (m_buffer == null) + m_buffer = new short[reserve]; + else { + if (reserve <= m_buffer.length) + return; + short[] buf = new short[reserve]; + System.arraycopy(m_buffer, 0, buf, 0, m_size); + m_buffer = buf; + } + + } + + public int capacity() { + return m_buffer != null ? m_buffer.length : 0; + } + + public AttributeStreamOfInt16(int size) { + int sz = size; + if (sz < 2) + sz = 2; + m_buffer = new short[sz]; + m_size = size; + } + + public AttributeStreamOfInt16(int size, short defaultValue) { + int sz = size; + if (sz < 2) + sz = 2; + m_buffer = new short[sz]; + m_size = size; + for (int i = 0; i < size; i++) + m_buffer[i] = defaultValue; + } + + public AttributeStreamOfInt16(AttributeStreamOfInt16 other) { + m_buffer = other.m_buffer.clone(); + m_size = other.m_size; + } + + public AttributeStreamOfInt16(AttributeStreamOfInt16 other, int maxSize) { + m_size = other.size(); + if (m_size > maxSize) + m_size = maxSize; + int sz = m_size; + if (sz < 2) + sz = 2; + m_buffer = new short[sz]; + System.arraycopy(other.m_buffer, 0, m_buffer, 0, m_size); + } + + /** + * Reads a value from the buffer at given offset. + * + * @param offset is the element number in the stream. + */ + public short read(int offset) { + return m_buffer[offset]; + } + + /** + * Overwrites given element with new value. + * + * @param offset is the element number in the stream. + * @param value is the value to write. + */ + public void write(int offset, short value) { + if (m_bReadonly) { + throw new RuntimeException("invalid_call"); + } + m_buffer[offset] = value; + } + + /** + * Adds a new value at the end of the stream. + * + * @param offset is the element number in the stream. + * @param value is the value to write. + */ + public void add(short v) { + resize(m_size + 1); + m_buffer[m_size - 1] = v; + } + + @Override + public AttributeStreamBase restrictedClone(int maxsize) { + int len = m_size; + int newSize = maxsize < len ? maxsize : len; + short[] newBuffer = new short[newSize]; + System.arraycopy(m_buffer, 0, newBuffer, 0, newSize); + m_buffer = newBuffer; + m_size = newSize; + return this; + } + + @Override + public int virtualSize() { + return size(); + } + + @Override + public long estimateMemorySize() { return SIZE_OF_ATTRIBUTE_STREAM_OF_INT16 + sizeOfShortArray(m_buffer.length); } @@ -154,447 +153,447 @@ public int getPersistence() { return Persistence.enumInt16; } - @Override - public double readAsDbl(int offset) { - return read(offset); - } - - @Override - public int readAsInt(int offset) { - return (int) read(offset); - } - - @Override - public long readAsInt64(int offset) { - return (long) read(offset); - } - - @Override - public void resize(int newSize) { - if (m_bLockedInSize) - throw new GeometryException( - "invalid call. Attribute Stream is locked and cannot be resized."); - - if (newSize <= m_size) { - if ((newSize * 5) / 4 < m_buffer.length) {// decrease when the 25% - // margin is exceeded - short[] newBuffer = new short[newSize]; - System.arraycopy(m_buffer, 0, newBuffer, 0, newSize); - m_buffer = newBuffer; - } - m_size = newSize; - } else { - if (newSize > m_buffer.length) { - int sz = (newSize < 64) ? Math.max(newSize * 2, 4) - : (newSize * 5) / 4; - short[] newBuffer = new short[sz]; - System.arraycopy(m_buffer, 0, newBuffer, 0, m_size); - m_buffer = newBuffer; - } - - m_size = newSize; - } - } - - @Override - public void resizePreserveCapacity(int newSize)// java only method - { - if (m_buffer == null || newSize > m_buffer.length) - resize(newSize); - if (m_bLockedInSize) - throw new GeometryException( - "invalid call. Attribute Stream is locked and cannot be resized."); - - m_size = newSize; - } - - @Override - public void resize(int newSize, double defaultValue) { - if (m_bLockedInSize) - throw new GeometryException( - "invalid call. Attribute Stream is locked and cannot be resized."); - if (newSize <= m_size) { - if ((newSize * 5) / 4 < m_buffer.length) {// decrease when the 25% - // margin is exceeded - short[] newBuffer = new short[newSize]; - System.arraycopy(m_buffer, 0, newBuffer, 0, newSize); - m_buffer = newBuffer; - } - m_size = newSize; - } else { - if (newSize > m_buffer.length) { - int sz = (newSize < 64) ? Math.max(newSize * 2, 4) - : (newSize * 5) / 4; - short[] newBuffer = new short[sz]; - System.arraycopy(m_buffer, 0, newBuffer, 0, m_size); - m_buffer = newBuffer; - } - - for (int i = m_size; i < newSize; i++) - m_buffer[i] = (short) defaultValue; - - m_size = newSize; - } - } - - @Override - public void writeAsDbl(int offset, double d) { - write(offset, (short) d); - } - - @Override - public void writeAsInt64(int offset, long d) { - write(offset, (short) d); - } - - @Override - public void writeAsInt(int offset, int d) { - write(offset, (short) d); - } - - // @Override - // public void writeRange(int srcStart, int count, ByteBuffer dst, - // int dstOffsetBytes) { - // // TODO Auto-generated method stub - // - // } - - @Override - public int calculateHashImpl(int hashCode, int start, int end) { - for (int i = start, n = size(); i < n && i < end; i++) - hashCode = NumberUtils.hash(hashCode, read(i)); - - return hashCode; - } - - @Override - public boolean equals(AttributeStreamBase other, int start, int end) { - if (other == null) - return false; - - if (!(other instanceof AttributeStreamOfInt16)) - return false; - - AttributeStreamOfInt16 _other = (AttributeStreamOfInt16) other; - - int size = size(); - int sizeOther = _other.size(); - - if (end > size || end > sizeOther && (size != sizeOther)) - return false; - - if (end > size) - end = size; - - for (int i = start; i < end; i++) - if (read(i) != _other.read(i)) - return false; - - return true; - } - - @Override - public void addRange(AttributeStreamBase src, int start, int count, - boolean bForward, int stride) { - if (m_bReadonly) - throw new GeometryException("invalid_call"); - - if (!bForward && (stride < 1 || count % stride != 0)) - throw new IllegalArgumentException(); - - int oldSize = m_size; - int newSize = oldSize + count; - resize(newSize); - - if (bForward) { - System.arraycopy(((AttributeStreamOfInt16) src).m_buffer, start, - m_buffer, oldSize, count); - } else { - int n = count; - - for (int i = 0; i < count; i += stride) { - n -= stride; - - for (int s = 0; s < stride; s++) { - m_buffer[oldSize + i + s] = ((AttributeStreamOfInt16) src).m_buffer[start - + n + s]; - } - } - } - } - - @Override - public void insertRange(int start, AttributeStreamBase src, int srcStart, - int count, boolean bForward, int stride, int validSize) { - if (m_bReadonly) - throw new GeometryException("invalid_call"); - - if (!bForward && (stride < 1 || count % stride != 0)) - throw new IllegalArgumentException(); - - System.arraycopy(m_buffer, start, m_buffer, start + count, validSize - - start); - - if (m_buffer == ((AttributeStreamOfInt16) src).m_buffer) { - if (start < srcStart) - srcStart += count; - } - - if (bForward) { - System.arraycopy(((AttributeStreamOfInt16) src).m_buffer, srcStart, - m_buffer, start, count); - } else { - int n = count; - - for (int i = 0; i < count; i += stride) { - n -= stride; - - for (int s = 0; s < stride; s++) { - m_buffer[start + i + s] = ((AttributeStreamOfInt16) src).m_buffer[srcStart - + n + s]; - } - } - } - } - - @Override - public void insertRange(int start, double value, int count, int validSize) { - if (m_bReadonly) - throw new GeometryException("invalid_call"); - - System.arraycopy(m_buffer, start, m_buffer, start + count, validSize - - start); - - short v = (short) value; - for (int i = 0; i < count; i++) { - m_buffer[start + i] = v; - } - } - - @Override - public void insertAttributes(int start, Point pt, int semantics, - int validSize) { - if (m_bReadonly) - throw new GeometryException("invalid_call"); - - int comp = VertexDescription.getComponentCount(semantics); - - System.arraycopy(m_buffer, start, m_buffer, start + comp, validSize - - start); - - for (int c = 0; c < comp; c++) { - m_buffer[start + c] = (short) pt.getAttributeAsDbl(semantics, c); - } - } - - @Override - public void eraseRange(int index, int count, int validSize) { - if (m_bReadonly) - throw new GeometryException("invalid_call"); - - if (index + count > m_size) - throw new GeometryException("invalid_call"); - - System.arraycopy(m_buffer, index + count, m_buffer, index, validSize - - (index + count)); - m_size -= count; - } - - @Override - public void readRange(int srcStart, int count, ByteBuffer dst, - int dstOffset, boolean bForward) { - if (srcStart < 0 || count < 0 || dstOffset < 0 - || size() < count + srcStart) - throw new IllegalArgumentException(); - - final int elmSize = NumberUtils.sizeOf((double) 0); - - if (dst.capacity() < (int) (dstOffset + elmSize * count)) - throw new IllegalArgumentException(); - - if (count == 0) - return; - - int j = srcStart; - if (!bForward) - j += count - 1; - - final int dj = bForward ? 1 : -1; - - int offset = dstOffset; - for (int i = 0; i < count; i++, offset += elmSize) { - dst.putShort(offset, m_buffer[j]); - j += dj; - } - - } - - @Override - public void reverseRange(int index, int count, int stride) { - if (m_bReadonly) - throw new GeometryException("invalid_call"); - - if (stride < 1 || count % stride != 0) - throw new GeometryException("invalid_call"); - - int cIterations = count >> 1; - int n = count; - - for (int i = 0; i < cIterations; i += stride) { - n -= stride; - - for (int s = 0; s < stride; s++) { - short temp = m_buffer[index + i + s]; - m_buffer[index + i + s] = m_buffer[index + n + s]; - m_buffer[index + n + s] = temp; - } - } - } - - @Override - public void setRange(double value, int start, int count) { - if (start < 0 || count < 0 || start < 0 || count + start > size()) - throw new IllegalArgumentException(); - - short v = (short) value; - for (int i = start, n = start + count; i < n; i++) - write(i, v); - } - - @Override - public void writeRange(int startElement, int count, - AttributeStreamBase _src, int srcStart, boolean bForward, int stride) { - if (startElement < 0 || count < 0 || srcStart < 0) - throw new IllegalArgumentException(); - - if (!bForward && (stride <= 0 || (count % stride != 0))) - throw new IllegalArgumentException(); - - AttributeStreamOfInt16 src = (AttributeStreamOfInt16) _src; // the input - // type must - // match - - if (src.size() < (int) (srcStart + count)) - throw new IllegalArgumentException(); - - if (count == 0) - return; - - if (size() < count + startElement) - resize(count + startElement); - - if (_src == (AttributeStreamBase) this) { - _selfWriteRangeImpl(startElement, count, srcStart, bForward, stride); - return; - } - - if (bForward) { - int j = startElement; - int offset = srcStart; - for (int i = 0; i < count; i++) { - m_buffer[j] = src.m_buffer[offset]; - j++; - offset++; - } - } else { - int j = startElement; - int offset = srcStart + count - stride; - if (stride == 1) { - for (int i = 0; i < count; i++) { - m_buffer[j] = src.m_buffer[offset]; - j++; - offset--; - } - } else { - for (int i = 0, n = count / stride; i < n; i++) { - for (int k = 0; k < stride; k++) - m_buffer[j + k] = src.m_buffer[offset + k]; - - j += stride; - offset -= stride; - } - } - } - } - - private void _selfWriteRangeImpl(int toElement, int count, int fromElement, - boolean bForward, int stride) { - - // writing from to this stream. - if (bForward) { - if (toElement == fromElement) - return; - } - - int offset; - int j; - int dj; - - if (fromElement < toElement) { - offset = fromElement + count - stride; - j = toElement + count - stride; - for (int i = 0, n = count / 2; i < n; i++) { - for (int k = 0; k < stride; k++) { - m_buffer[j + k] = m_buffer[offset + k]; - } - j -= stride; - offset -= stride; - } - } else { - offset = fromElement; - j = toElement; - dj = 1; - for (int i = 0; i < count; i++) { - m_buffer[j] = m_buffer[offset]; - j += 1; - offset++; - } - } - - if (!bForward) { - // reverse what we written - j = toElement; - offset = toElement + count - stride; - dj = stride; - for (int i = 0, n = count / 2; i < n; i++) { - for (int k = 0; k < stride; k++) { - short v = m_buffer[j + k]; - m_buffer[j + k] = m_buffer[offset + k]; - m_buffer[offset + k] = v; - } - j += stride; - offset -= stride; - } - } - } - - @Override - public void writeRange(int startElement, int count, ByteBuffer src, - int offsetBytes, boolean bForward) { - if (startElement < 0 || count < 0 || offsetBytes < 0) - throw new IllegalArgumentException(); - - final int elmSize = NumberUtils.sizeOf((double) 0); - if (src.capacity() < (int) (offsetBytes + elmSize * count)) - throw new IllegalArgumentException(); - - if (count == 0) - return; - - if (size() < count + startElement) - resize(count + startElement); - - int j = startElement; - if (!bForward) - j += count - 1; - - final int dj = bForward ? 1 : -1; - - int offset = offsetBytes; - for (int i = 0; i < count; i++, offset += elmSize) { - m_buffer[j] = src.getShort(offset); - j += dj; - } - - } + @Override + public double readAsDbl(int offset) { + return read(offset); + } + + @Override + public int readAsInt(int offset) { + return (int) read(offset); + } + + @Override + public long readAsInt64(int offset) { + return (long) read(offset); + } + + @Override + public void resize(int newSize) { + if (m_bLockedInSize) + throw new GeometryException( + "invalid call. Attribute Stream is locked and cannot be resized."); + + if (newSize <= m_size) { + if ((newSize * 5) / 4 < m_buffer.length) {// decrease when the 25% + // margin is exceeded + short[] newBuffer = new short[newSize]; + System.arraycopy(m_buffer, 0, newBuffer, 0, newSize); + m_buffer = newBuffer; + } + m_size = newSize; + } else { + if (newSize > m_buffer.length) { + int sz = (newSize < 64) ? Math.max(newSize * 2, 4) + : (newSize * 5) / 4; + short[] newBuffer = new short[sz]; + System.arraycopy(m_buffer, 0, newBuffer, 0, m_size); + m_buffer = newBuffer; + } + + m_size = newSize; + } + } + + @Override + public void resizePreserveCapacity(int newSize)// java only method + { + if (m_buffer == null || newSize > m_buffer.length) + resize(newSize); + if (m_bLockedInSize) + throw new GeometryException( + "invalid call. Attribute Stream is locked and cannot be resized."); + + m_size = newSize; + } + + @Override + public void resize(int newSize, double defaultValue) { + if (m_bLockedInSize) + throw new GeometryException( + "invalid call. Attribute Stream is locked and cannot be resized."); + if (newSize <= m_size) { + if ((newSize * 5) / 4 < m_buffer.length) {// decrease when the 25% + // margin is exceeded + short[] newBuffer = new short[newSize]; + System.arraycopy(m_buffer, 0, newBuffer, 0, newSize); + m_buffer = newBuffer; + } + m_size = newSize; + } else { + if (newSize > m_buffer.length) { + int sz = (newSize < 64) ? Math.max(newSize * 2, 4) + : (newSize * 5) / 4; + short[] newBuffer = new short[sz]; + System.arraycopy(m_buffer, 0, newBuffer, 0, m_size); + m_buffer = newBuffer; + } + + for (int i = m_size; i < newSize; i++) + m_buffer[i] = (short) defaultValue; + + m_size = newSize; + } + } + + @Override + public void writeAsDbl(int offset, double d) { + write(offset, (short) d); + } + + @Override + public void writeAsInt64(int offset, long d) { + write(offset, (short) d); + } + + @Override + public void writeAsInt(int offset, int d) { + write(offset, (short) d); + } + + // @Override + // public void writeRange(int srcStart, int count, ByteBuffer dst, + // int dstOffsetBytes) { + // // TODO Auto-generated method stub + // + // } + + @Override + public int calculateHashImpl(int hashCode, int start, int end) { + for (int i = start, n = size(); i < n && i < end; i++) + hashCode = NumberUtils.hash(hashCode, read(i)); + + return hashCode; + } + + @Override + public boolean equals(AttributeStreamBase other, int start, int end) { + if (other == null) + return false; + + if (!(other instanceof AttributeStreamOfInt16)) + return false; + + AttributeStreamOfInt16 _other = (AttributeStreamOfInt16) other; + + int size = size(); + int sizeOther = _other.size(); + + if (end > size || end > sizeOther && (size != sizeOther)) + return false; + + if (end > size) + end = size; + + for (int i = start; i < end; i++) + if (read(i) != _other.read(i)) + return false; + + return true; + } + + @Override + public void addRange(AttributeStreamBase src, int start, int count, + boolean bForward, int stride) { + if (m_bReadonly) + throw new GeometryException("invalid_call"); + + if (!bForward && (stride < 1 || count % stride != 0)) + throw new IllegalArgumentException(); + + int oldSize = m_size; + int newSize = oldSize + count; + resize(newSize); + + if (bForward) { + System.arraycopy(((AttributeStreamOfInt16) src).m_buffer, start, + m_buffer, oldSize, count); + } else { + int n = count; + + for (int i = 0; i < count; i += stride) { + n -= stride; + + for (int s = 0; s < stride; s++) { + m_buffer[oldSize + i + s] = ((AttributeStreamOfInt16) src).m_buffer[start + + n + s]; + } + } + } + } + + @Override + public void insertRange(int start, AttributeStreamBase src, int srcStart, + int count, boolean bForward, int stride, int validSize) { + if (m_bReadonly) + throw new GeometryException("invalid_call"); + + if (!bForward && (stride < 1 || count % stride != 0)) + throw new IllegalArgumentException(); + + System.arraycopy(m_buffer, start, m_buffer, start + count, validSize + - start); + + if (m_buffer == ((AttributeStreamOfInt16) src).m_buffer) { + if (start < srcStart) + srcStart += count; + } + + if (bForward) { + System.arraycopy(((AttributeStreamOfInt16) src).m_buffer, srcStart, + m_buffer, start, count); + } else { + int n = count; + + for (int i = 0; i < count; i += stride) { + n -= stride; + + for (int s = 0; s < stride; s++) { + m_buffer[start + i + s] = ((AttributeStreamOfInt16) src).m_buffer[srcStart + + n + s]; + } + } + } + } + + @Override + public void insertRange(int start, double value, int count, int validSize) { + if (m_bReadonly) + throw new GeometryException("invalid_call"); + + System.arraycopy(m_buffer, start, m_buffer, start + count, validSize + - start); + + short v = (short) value; + for (int i = 0; i < count; i++) { + m_buffer[start + i] = v; + } + } + + @Override + public void insertAttributes(int start, Point pt, int semantics, + int validSize) { + if (m_bReadonly) + throw new GeometryException("invalid_call"); + + int comp = VertexDescription.getComponentCount(semantics); + + System.arraycopy(m_buffer, start, m_buffer, start + comp, validSize + - start); + + for (int c = 0; c < comp; c++) { + m_buffer[start + c] = (short) pt.getAttributeAsDbl(semantics, c); + } + } + + @Override + public void eraseRange(int index, int count, int validSize) { + if (m_bReadonly) + throw new GeometryException("invalid_call"); + + if (index + count > m_size) + throw new GeometryException("invalid_call"); + + System.arraycopy(m_buffer, index + count, m_buffer, index, validSize + - (index + count)); + m_size -= count; + } + + @Override + public void readRange(int srcStart, int count, ByteBuffer dst, + int dstOffset, boolean bForward) { + if (srcStart < 0 || count < 0 || dstOffset < 0 + || size() < count + srcStart) + throw new IllegalArgumentException(); + + final int elmSize = NumberUtils.sizeOf((double) 0); + + if (dst.capacity() < (int) (dstOffset + elmSize * count)) + throw new IllegalArgumentException(); + + if (count == 0) + return; + + int j = srcStart; + if (!bForward) + j += count - 1; + + final int dj = bForward ? 1 : -1; + + int offset = dstOffset; + for (int i = 0; i < count; i++, offset += elmSize) { + dst.putShort(offset, m_buffer[j]); + j += dj; + } + + } + + @Override + public void reverseRange(int index, int count, int stride) { + if (m_bReadonly) + throw new GeometryException("invalid_call"); + + if (stride < 1 || count % stride != 0) + throw new GeometryException("invalid_call"); + + int cIterations = count >> 1; + int n = count; + + for (int i = 0; i < cIterations; i += stride) { + n -= stride; + + for (int s = 0; s < stride; s++) { + short temp = m_buffer[index + i + s]; + m_buffer[index + i + s] = m_buffer[index + n + s]; + m_buffer[index + n + s] = temp; + } + } + } + + @Override + public void setRange(double value, int start, int count) { + if (start < 0 || count < 0 || start < 0 || count + start > size()) + throw new IllegalArgumentException(); + + short v = (short) value; + for (int i = start, n = start + count; i < n; i++) + write(i, v); + } + + @Override + public void writeRange(int startElement, int count, + AttributeStreamBase _src, int srcStart, boolean bForward, int stride) { + if (startElement < 0 || count < 0 || srcStart < 0) + throw new IllegalArgumentException(); + + if (!bForward && (stride <= 0 || (count % stride != 0))) + throw new IllegalArgumentException(); + + AttributeStreamOfInt16 src = (AttributeStreamOfInt16) _src; // the input + // type must + // match + + if (src.size() < (int) (srcStart + count)) + throw new IllegalArgumentException(); + + if (count == 0) + return; + + if (size() < count + startElement) + resize(count + startElement); + + if (_src == (AttributeStreamBase) this) { + _selfWriteRangeImpl(startElement, count, srcStart, bForward, stride); + return; + } + + if (bForward) { + int j = startElement; + int offset = srcStart; + for (int i = 0; i < count; i++) { + m_buffer[j] = src.m_buffer[offset]; + j++; + offset++; + } + } else { + int j = startElement; + int offset = srcStart + count - stride; + if (stride == 1) { + for (int i = 0; i < count; i++) { + m_buffer[j] = src.m_buffer[offset]; + j++; + offset--; + } + } else { + for (int i = 0, n = count / stride; i < n; i++) { + for (int k = 0; k < stride; k++) + m_buffer[j + k] = src.m_buffer[offset + k]; + + j += stride; + offset -= stride; + } + } + } + } + + private void _selfWriteRangeImpl(int toElement, int count, int fromElement, + boolean bForward, int stride) { + + // writing from to this stream. + if (bForward) { + if (toElement == fromElement) + return; + } + + int offset; + int j; + int dj; + + if (fromElement < toElement) { + offset = fromElement + count - stride; + j = toElement + count - stride; + for (int i = 0, n = count / 2; i < n; i++) { + for (int k = 0; k < stride; k++) { + m_buffer[j + k] = m_buffer[offset + k]; + } + j -= stride; + offset -= stride; + } + } else { + offset = fromElement; + j = toElement; + dj = 1; + for (int i = 0; i < count; i++) { + m_buffer[j] = m_buffer[offset]; + j += 1; + offset++; + } + } + + if (!bForward) { + // reverse what we written + j = toElement; + offset = toElement + count - stride; + dj = stride; + for (int i = 0, n = count / 2; i < n; i++) { + for (int k = 0; k < stride; k++) { + short v = m_buffer[j + k]; + m_buffer[j + k] = m_buffer[offset + k]; + m_buffer[offset + k] = v; + } + j += stride; + offset -= stride; + } + } + } + + @Override + public void writeRange(int startElement, int count, ByteBuffer src, + int offsetBytes, boolean bForward) { + if (startElement < 0 || count < 0 || offsetBytes < 0) + throw new IllegalArgumentException(); + + final int elmSize = NumberUtils.sizeOf((double) 0); + if (src.capacity() < (int) (offsetBytes + elmSize * count)) + throw new IllegalArgumentException(); + + if (count == 0) + return; + + if (size() < count + startElement) + resize(count + startElement); + + int j = startElement; + if (!bForward) + j += count - 1; + + final int dj = bForward ? 1 : -1; + + int offset = offsetBytes; + for (int i = 0; i < count; i++, offset += elmSize) { + m_buffer[j] = src.getShort(offset); + j += dj; + } + + } } diff --git a/src/main/java/com/esri/core/geometry/AttributeStreamOfInt32.java b/src/main/java/com/esri/core/geometry/AttributeStreamOfInt32.java index e8d9b492..e3c7708d 100644 --- a/src/main/java/com/esri/core/geometry/AttributeStreamOfInt32.java +++ b/src/main/java/com/esri/core/geometry/AttributeStreamOfInt32.java @@ -37,127 +37,126 @@ final class AttributeStreamOfInt32 extends AttributeStreamBase { private int[] m_buffer = null; private int m_size; - public void reserve(int reserve) { - if (reserve <= 0) - return; - if (m_buffer == null) - m_buffer = new int[reserve]; - else { - if (reserve <= m_buffer.length) - return; - int[] buf = new int[reserve]; - System.arraycopy(m_buffer, 0, buf, 0, m_buffer.length); - m_buffer = buf; - } - - } - - public int size() { - return m_size; - } - - public int capacity() { - return m_buffer != null ? m_buffer.length : 0; - } - - public AttributeStreamOfInt32(int size) { - int sz = size; - if (sz < 2) - sz = 2; - m_buffer = new int[sz]; - m_size = size; - } - - public AttributeStreamOfInt32(int size, int defaultValue) { - int sz = size; - if (sz < 2) - sz = 2; - m_buffer = new int[sz]; - m_size = size; - Arrays.fill(m_buffer, 0, size, defaultValue); - // for (int i = 0; i < size; i++) - // m_buffer[i] = defaultValue; - } - - public AttributeStreamOfInt32(AttributeStreamOfInt32 other) { - m_buffer = other.m_buffer.clone(); - m_size = other.m_size; - } - - public AttributeStreamOfInt32(AttributeStreamOfInt32 other, int maxSize) { - m_size = other.size(); - if (m_size > maxSize) - m_size = maxSize; - int sz = m_size; - if (sz < 2) - sz = 2; - m_buffer = new int[sz]; - System.arraycopy(other.m_buffer, 0, m_buffer, 0, m_size); - } - - /** - * Reads a value from the buffer at given offset. - * - * @param offset is the element number in the stream. - */ - public int read(int offset) { - return m_buffer[offset]; - } - - public int get(int offset) { - return m_buffer[offset]; - } - - /** - * Overwrites given element with new value. - * - * @param offset is the element number in the stream. - * @param value is the value to write. - */ - public void write(int offset, int value) { - if (m_bReadonly) { - throw new RuntimeException("invalid_call"); - } - m_buffer[offset] = value; - } - - public void set(int offset, int value) { - if (m_bReadonly) { - throw new RuntimeException("invalid_call"); - } - m_buffer[offset] = value; - } - - /** - * Adds a new value at the end of the stream. - * - * @param offset is the element number in the stream. - * @param value is the value to write. - */ - public void add(int v) { - resize(m_size + 1); - m_buffer[m_size - 1] = v; - } - - @Override - public AttributeStreamBase restrictedClone(int maxsize) { - int len = m_size; - int newSize = maxsize < len ? maxsize : len; - int[] newBuffer = new int[newSize]; - System.arraycopy(m_buffer, 0, newBuffer, 0, newSize); - m_buffer = newBuffer; - m_size = newSize; - return this; - } - - @Override - public int virtualSize() { - return size(); - } + public void reserve(int reserve) { + if (reserve <= 0) + return; + if (m_buffer == null) + m_buffer = new int[reserve]; + else { + if (reserve <= m_buffer.length) + return; + int[] buf = new int[reserve]; + System.arraycopy(m_buffer, 0, buf, 0, m_buffer.length); + m_buffer = buf; + } + + } + + public int size() { + return m_size; + } + + public int capacity() { + return m_buffer != null ? m_buffer.length : 0; + } + + public AttributeStreamOfInt32(int size) { + int sz = size; + if (sz < 2) + sz = 2; + m_buffer = new int[sz]; + m_size = size; + } + + public AttributeStreamOfInt32(int size, int defaultValue) { + int sz = size; + if (sz < 2) + sz = 2; + m_buffer = new int[sz]; + m_size = size; + Arrays.fill(m_buffer, 0, size, defaultValue); + // for (int i = 0; i < size; i++) + // m_buffer[i] = defaultValue; + } + + public AttributeStreamOfInt32(AttributeStreamOfInt32 other) { + m_buffer = other.m_buffer.clone(); + m_size = other.m_size; + } + + public AttributeStreamOfInt32(AttributeStreamOfInt32 other, int maxSize) { + m_size = other.size(); + if (m_size > maxSize) + m_size = maxSize; + int sz = m_size; + if (sz < 2) + sz = 2; + m_buffer = new int[sz]; + System.arraycopy(other.m_buffer, 0, m_buffer, 0, m_size); + } + + /** + * Reads a value from the buffer at given offset. + * + * @param offset is the element number in the stream. + */ + public int read(int offset) { + return m_buffer[offset]; + } + + public int get(int offset) { + return m_buffer[offset]; + } + + /** + * Overwrites given element with new value. + * + * @param offset is the element number in the stream. + * @param value is the value to write. + */ + public void write(int offset, int value) { + if (m_bReadonly) { + throw new RuntimeException("invalid_call"); + } + m_buffer[offset] = value; + } + + public void set(int offset, int value) { + if (m_bReadonly) { + throw new RuntimeException("invalid_call"); + } + m_buffer[offset] = value; + } + + /** + * Adds a new value at the end of the stream. + * + * @param offset is the element number in the stream. + * @param value is the value to write. + */ + public void add(int v) { + resize(m_size + 1); + m_buffer[m_size - 1] = v; + } @Override - public long estimateMemorySize() - { + public AttributeStreamBase restrictedClone(int maxsize) { + int len = m_size; + int newSize = maxsize < len ? maxsize : len; + int[] newBuffer = new int[newSize]; + System.arraycopy(m_buffer, 0, newBuffer, 0, newSize); + m_buffer = newBuffer; + m_size = newSize; + return this; + } + + @Override + public int virtualSize() { + return size(); + } + + @Override + public long estimateMemorySize() { return SIZE_OF_ATTRIBUTE_STREAM_OF_INT32 + sizeOfIntArray(m_buffer.length); } @@ -166,562 +165,562 @@ public int getPersistence() { return Persistence.enumInt32; } - @Override - public double readAsDbl(int offset) { - return read(offset); - } - - @Override - public int readAsInt(int offset) { - return (int) read(offset); - } - - @Override - public long readAsInt64(int offset) { - return (long) read(offset); - } - - @Override - public void resize(int newSize) { - if (m_bLockedInSize) - throw new GeometryException( - "invalid call. Attribute Stream is locked and cannot be resized."); - - if (newSize <= m_size) { - if ((newSize * 5) / 4 < m_buffer.length) {// decrease when the 25% - // margin is exceeded - int[] newBuffer = new int[newSize]; - System.arraycopy(m_buffer, 0, newBuffer, 0, newSize); - m_buffer = newBuffer; - } - m_size = newSize; - } else { - if (newSize > m_buffer.length) { - int sz = (newSize < 64) ? Math.max(newSize * 2, 4) - : (newSize * 5) / 4; - int[] newBuffer = new int[sz]; - System.arraycopy(m_buffer, 0, newBuffer, 0, m_size); - m_buffer = newBuffer; - } - - m_size = newSize; - } - } - - @Override - public void resizePreserveCapacity(int newSize)// java only method - { - if (m_buffer == null || newSize > m_buffer.length) - resize(newSize); - if (m_bLockedInSize) - throw new GeometryException( - "invalid call. Attribute Stream is locked and cannot be resized."); - - m_size = newSize; - } - - @Override - public void resize(int newSize, double defaultValue) { - if (m_bLockedInSize) - throw new GeometryException( - "invalid call. Attribute Stream is locked and cannot be resized."); - if (newSize <= m_size) { - if ((newSize * 5) / 4 < m_buffer.length) {// decrease when the 25% - // margin is exceeded - int[] newBuffer = new int[newSize]; - System.arraycopy(m_buffer, 0, newBuffer, 0, newSize); - m_buffer = newBuffer; - } - m_size = newSize; - } else { - if (newSize > m_buffer.length) { - int sz = (newSize < 64) ? Math.max(newSize * 2, 4) - : (newSize * 5) / 4; - int[] newBuffer = new int[sz]; - System.arraycopy(m_buffer, 0, newBuffer, 0, m_size); - m_buffer = newBuffer; - } - - Arrays.fill(m_buffer, m_size, newSize, (int) defaultValue); - // for (int i = m_size; i < newSize; i++) - // m_buffer[i] = (int)defaultValue; - - m_size = newSize; - } - } - - @Override - public void writeAsDbl(int offset, double d) { - write(offset, (int) d); - } - - @Override - public void writeAsInt64(int offset, long d) { - write(offset, (int) d); - } - - @Override - public void writeAsInt(int offset, int d) { - write(offset, (int) d); - } - - // @Override - // public void writeRange(int srcStart, int count, ByteBuffer dst, - // int dstOffsetBytes) { - // // TODO Auto-generated method stub - // - // } - - @Override - public int calculateHashImpl(int hashCode, int start, int end) { - for (int i = start, n = size(); i < n && i < end; i++) - hashCode = NumberUtils.hash(hashCode, read(i)); - - return hashCode; - } - - @Override - public boolean equals(AttributeStreamBase other, int start, int end) { - if (other == null) - return false; - - if (!(other instanceof AttributeStreamOfInt32)) - return false; - - AttributeStreamOfInt32 _other = (AttributeStreamOfInt32) other; - - int size = size(); - int sizeOther = _other.size(); - - if (end > size || end > sizeOther && (size != sizeOther)) - return false; - - if (end > size) - end = size; - - for (int i = start; i < end; i++) - if (read(i) != _other.read(i)) - return false; - - return true; - } - - public int getLast() { - return m_buffer[m_size - 1]; - } - - public void setLast(int v) { - m_buffer[m_size - 1] = v; - } - - public void removeLast() { - resize(m_size - 1); - } - - // Finds element in the unsorted array and returns its index. Returns -1 if - // the element could not be found. - int findElement(int value) { - for (int i = 0, n = m_size; i < n; i++) { - if (m_buffer[i] == value) - return i; - } - return -1; - } - - // Returns True if element could be found in the array. - boolean hasElement(int value) { - return findElement(value) >= 0; - } - - // Removes the element from the array in constant time. - // It moves the last element of the array to the index and decrements the - // array size by 1. - void popElement(int index) { - assert (index >= 0 && index < m_size); - if (index < m_size - 1) { - m_buffer[index] = m_buffer[m_size - 1]; - } - - resize(m_size - 1); - } - - @Override - public void addRange(AttributeStreamBase src, int start, int count, - boolean bForward, int stride) { - if (m_bReadonly) - throw new GeometryException("invalid_call"); - - if (!bForward && (stride < 1 || count % stride != 0)) - throw new IllegalArgumentException(); - - int oldSize = m_size; - int newSize = oldSize + count; - resize(newSize); - - if (bForward) { - System.arraycopy(((AttributeStreamOfInt32) src).m_buffer, start, - m_buffer, oldSize, count); - } else { - int n = count; - - for (int i = 0; i < count; i += stride) { - n -= stride; - - for (int s = 0; s < stride; s++) { - m_buffer[oldSize + i + s] = ((AttributeStreamOfInt32) src).m_buffer[start - + n + s]; - } - } - } - } - - @Override - public void insertRange(int start, AttributeStreamBase src, int srcStart, - int count, boolean bForward, int stride, int validSize) { - if (m_bReadonly) - throw new GeometryException("invalid_call"); - - if (!bForward && (stride < 1 || count % stride != 0)) - throw new IllegalArgumentException(); - - System.arraycopy(m_buffer, start, m_buffer, start + count, validSize - - start); - - if (m_buffer == ((AttributeStreamOfInt32) src).m_buffer) { - if (start < srcStart) - srcStart += count; - } - - if (bForward) { - System.arraycopy(((AttributeStreamOfInt32) src).m_buffer, srcStart, - m_buffer, start, count); - } else { - int n = count; - - for (int i = 0; i < count; i += stride) { - n -= stride; - - for (int s = 0; s < stride; s++) { - m_buffer[start + i + s] = ((AttributeStreamOfInt32) src).m_buffer[srcStart - + n + s]; - } - } - } - } - - @Override - public void insertRange(int start, double value, int count, int validSize) { - if (m_bReadonly) - throw new GeometryException("invalid_call"); - - System.arraycopy(m_buffer, start, m_buffer, start + count, validSize - - start); - - int v = (int) value; - Arrays.fill(m_buffer, start, start + count, v); - // for (int i = 0; i < count; i++) - // { - // m_buffer[start + i] = v; - // } - } - - @Override - public void insertAttributes(int start, Point pt, int semantics, - int validSize) { - if (m_bReadonly) - throw new GeometryException("invalid_call"); - - int comp = VertexDescription.getComponentCount(semantics); - - System.arraycopy(m_buffer, start, m_buffer, start + comp, validSize - - start); - - for (int c = 0; c < comp; c++) { - m_buffer[start + c] = (int) pt.getAttributeAsDbl(semantics, c); - } - } - - @Override - public void eraseRange(int index, int count, int validSize) { - if (m_bReadonly) - throw new GeometryException("invalid_call"); - - if (index + count > m_size) - throw new GeometryException("invalid_call"); - - System.arraycopy(m_buffer, index + count, m_buffer, index, validSize - - (index + count)); - m_size -= count; - } - - @Override - public void readRange(int srcStart, int count, ByteBuffer dst, - int dstOffset, boolean bForward) { - if (srcStart < 0 || count < 0 || dstOffset < 0 - || size() < count + srcStart) - throw new IllegalArgumentException(); - - final int elmSize = NumberUtils.sizeOf((double) 0); - - if (dst.capacity() < (int) (dstOffset + elmSize * count)) - throw new IllegalArgumentException(); - - if (count == 0) - return; - - int j = srcStart; - if (!bForward) - j += count - 1; - final int dj = bForward ? 1 : -1; - int offset = dstOffset; - for (int i = 0; i < count; i++, offset += elmSize) { - dst.putInt(offset, m_buffer[j]); - j += dj; - } - } - - @Override - public void reverseRange(int index, int count, int stride) { - if (m_bReadonly) - throw new GeometryException("invalid_call"); - - if (stride < 1 || count % stride != 0) - throw new GeometryException("invalid_call"); - - int cIterations = count >> 1; - int n = count; - - for (int i = 0; i < cIterations; i += stride) { - n -= stride; - - for (int s = 0; s < stride; s++) { - int temp = m_buffer[index + i + s]; - m_buffer[index + i + s] = m_buffer[index + n + s]; - m_buffer[index + n + s] = temp; - } - } - } - - @Override - public void setRange(double value, int start, int count) { - if (start < 0 || count < 0 || start < 0 || count + start > size()) - throw new IllegalArgumentException(); - - int v = (int) value; - Arrays.fill(m_buffer, start, start + count, v); - // for (int i = start, n = start + count; i < n; i++) - // write(i, v); - } - - @Override - public void writeRange(int startElement, int count, - AttributeStreamBase _src, int srcStart, boolean bForward, int stride) { - if (startElement < 0 || count < 0 || srcStart < 0) - throw new IllegalArgumentException(); - - if (!bForward && (stride <= 0 || (count % stride != 0))) - throw new IllegalArgumentException(); - - AttributeStreamOfInt32 src = (AttributeStreamOfInt32) _src; // the input - // type must - // match - - if (src.size() < (int) (srcStart + count)) - throw new IllegalArgumentException(); - - if (count == 0) - return; - - if (size() < count + startElement) - resize(count + startElement); - - if (_src == (AttributeStreamBase) this) { - _selfWriteRangeImpl(startElement, count, srcStart, bForward, stride); - return; - } - - if (bForward) { - System.arraycopy(src.m_buffer, srcStart, m_buffer, startElement, - count); - // int j = startElement; - // int offset = srcStart; - // for (int i = 0; i < count; i++) - // { - // m_buffer[j] = src.m_buffer[offset]; - // j++; - // offset++; - // } - } else { - int j = startElement; - int offset = srcStart + count - stride; - if (stride == 1) { - for (int i = 0; i < count; i++) { - m_buffer[j] = src.m_buffer[offset]; - j++; - offset--; - } - } else { - for (int i = 0, n = count / stride; i < n; i++) { - for (int k = 0; k < stride; k++) - m_buffer[j + k] = src.m_buffer[offset + k]; - - j += stride; - offset -= stride; - } - } - } - } - - private void _selfWriteRangeImpl(int toElement, int count, int fromElement, - boolean bForward, int stride) { - - // writing from to this stream. - if (bForward) { - if (toElement == fromElement) - return; - } - - System.arraycopy(m_buffer, fromElement, m_buffer, toElement, count); - if (bForward) - return; - - // reverse what we written - int j = toElement; - int offset = toElement + count - stride; - int dj = stride; - for (int i = 0, n = count / 2; i < n; i++) { - for (int k = 0; k < stride; k++) { - int v = m_buffer[j + k]; - m_buffer[j + k] = m_buffer[offset + k]; - m_buffer[offset + k] = v; - } - j += stride; - offset -= stride; - } - } - - @Override - public void writeRange(int startElement, int count, ByteBuffer src, - int offsetBytes, boolean bForward) { - if (startElement < 0 || count < 0 || offsetBytes < 0) - throw new IllegalArgumentException(); - - final int elmSize = NumberUtils.sizeOf((double) 0); - if (src.capacity() < (int) (offsetBytes + elmSize * count)) - throw new IllegalArgumentException(); - - if (count == 0) - return; - - if (size() < count + startElement) - resize(count + startElement); - - int j = startElement; - if (!bForward) - j += count - 1; - - final int dj = bForward ? 1 : -1; - - int offset = offsetBytes; - for (int i = 0; i < count; i++, offset += elmSize) { - m_buffer[j] = src.getInt(offset); - j += dj; - } - - } - - static public abstract class IntComparator { - public abstract int compare(int v1, int v2); - } - - ; - - static class RandomSeed { - public int random; - - public RandomSeed() { - random = 1973; - } - } - - public void Sort(int start, int end, IntComparator compare) { - if (end - start < 10) - insertionsort(start, end, compare); - else { - quicksort(start, end - 1, compare, new RandomSeed()); - } - } - - void insertionsort(int start, int end, IntComparator compare) { - for (int j = start; j < end; j++)// insertion sort - { - int key = m_buffer[j]; - int i = j - 1; - while (i >= start && compare.compare(m_buffer[i], key) > 0) { - m_buffer[i + 1] = m_buffer[i]; - i--; - } - m_buffer[i + 1] = key; - } - } - - void swap(int left, int right) { - int tmp = m_buffer[right]; - m_buffer[right] = m_buffer[left]; - m_buffer[left] = tmp; - } - - void quicksort(int leftIn, int rightIn, IntComparator compare, - RandomSeed seed) { - if (leftIn >= rightIn) - return; - - int left = leftIn; - int right = rightIn; - - while (true)// tail recursion loop - { - if (right - left < 9) { - insertionsort(left, right + 1, compare); - return; - } - // Select random index for the pivot - seed.random = NumberUtils.nextRand(seed.random); - long nom = ((long) (right - left)) * seed.random; - int pivotIndex = (int) (nom / NumberUtils.intMax()) + left; - // Get the pivot value - int pivotValue = m_buffer[pivotIndex]; - - // Start partition - // Move pivot to the right - swap(pivotIndex, right); - int storeIndex = left; - for (int i = left; i < right; i++) { - int elm = m_buffer[i]; - if (compare.compare(elm, pivotValue) <= 0) { - swap(storeIndex, i); - storeIndex = storeIndex + 1; - } - } - - // Move pivot to its final place - swap(storeIndex, right); - // End partition - - // Shorter part is regular recursion - // Longer part is tail recursion - if (storeIndex - left < right - storeIndex) { - quicksort(left, storeIndex - 1, compare, seed); - left = storeIndex + 1; - } else { - quicksort(storeIndex + 1, right, compare, seed); - right = storeIndex - 1; - } - - } - } - - public void sort(int start, int end) { - Arrays.sort(m_buffer, start, end); - } + @Override + public double readAsDbl(int offset) { + return read(offset); + } + + @Override + public int readAsInt(int offset) { + return (int) read(offset); + } + + @Override + public long readAsInt64(int offset) { + return (long) read(offset); + } + + @Override + public void resize(int newSize) { + if (m_bLockedInSize) + throw new GeometryException( + "invalid call. Attribute Stream is locked and cannot be resized."); + + if (newSize <= m_size) { + if ((newSize * 5) / 4 < m_buffer.length) {// decrease when the 25% + // margin is exceeded + int[] newBuffer = new int[newSize]; + System.arraycopy(m_buffer, 0, newBuffer, 0, newSize); + m_buffer = newBuffer; + } + m_size = newSize; + } else { + if (newSize > m_buffer.length) { + int sz = (newSize < 64) ? Math.max(newSize * 2, 4) + : (newSize * 5) / 4; + int[] newBuffer = new int[sz]; + System.arraycopy(m_buffer, 0, newBuffer, 0, m_size); + m_buffer = newBuffer; + } + + m_size = newSize; + } + } + + @Override + public void resizePreserveCapacity(int newSize)// java only method + { + if (m_buffer == null || newSize > m_buffer.length) + resize(newSize); + if (m_bLockedInSize) + throw new GeometryException( + "invalid call. Attribute Stream is locked and cannot be resized."); + + m_size = newSize; + } + + @Override + public void resize(int newSize, double defaultValue) { + if (m_bLockedInSize) + throw new GeometryException( + "invalid call. Attribute Stream is locked and cannot be resized."); + if (newSize <= m_size) { + if ((newSize * 5) / 4 < m_buffer.length) {// decrease when the 25% + // margin is exceeded + int[] newBuffer = new int[newSize]; + System.arraycopy(m_buffer, 0, newBuffer, 0, newSize); + m_buffer = newBuffer; + } + m_size = newSize; + } else { + if (newSize > m_buffer.length) { + int sz = (newSize < 64) ? Math.max(newSize * 2, 4) + : (newSize * 5) / 4; + int[] newBuffer = new int[sz]; + System.arraycopy(m_buffer, 0, newBuffer, 0, m_size); + m_buffer = newBuffer; + } + + Arrays.fill(m_buffer, m_size, newSize, (int) defaultValue); + // for (int i = m_size; i < newSize; i++) + // m_buffer[i] = (int)defaultValue; + + m_size = newSize; + } + } + + @Override + public void writeAsDbl(int offset, double d) { + write(offset, (int) d); + } + + @Override + public void writeAsInt64(int offset, long d) { + write(offset, (int) d); + } + + @Override + public void writeAsInt(int offset, int d) { + write(offset, (int) d); + } + + // @Override + // public void writeRange(int srcStart, int count, ByteBuffer dst, + // int dstOffsetBytes) { + // // TODO Auto-generated method stub + // + // } + + @Override + public int calculateHashImpl(int hashCode, int start, int end) { + for (int i = start, n = size(); i < n && i < end; i++) + hashCode = NumberUtils.hash(hashCode, read(i)); + + return hashCode; + } + + @Override + public boolean equals(AttributeStreamBase other, int start, int end) { + if (other == null) + return false; + + if (!(other instanceof AttributeStreamOfInt32)) + return false; + + AttributeStreamOfInt32 _other = (AttributeStreamOfInt32) other; + + int size = size(); + int sizeOther = _other.size(); + + if (end > size || end > sizeOther && (size != sizeOther)) + return false; + + if (end > size) + end = size; + + for (int i = start; i < end; i++) + if (read(i) != _other.read(i)) + return false; + + return true; + } + + public int getLast() { + return m_buffer[m_size - 1]; + } + + public void setLast(int v) { + m_buffer[m_size - 1] = v; + } + + public void removeLast() { + resize(m_size - 1); + } + + // Finds element in the unsorted array and returns its index. Returns -1 if + // the element could not be found. + int findElement(int value) { + for (int i = 0, n = m_size; i < n; i++) { + if (m_buffer[i] == value) + return i; + } + return -1; + } + + // Returns True if element could be found in the array. + boolean hasElement(int value) { + return findElement(value) >= 0; + } + + // Removes the element from the array in constant time. + // It moves the last element of the array to the index and decrements the + // array size by 1. + void popElement(int index) { + assert (index >= 0 && index < m_size); + if (index < m_size - 1) { + m_buffer[index] = m_buffer[m_size - 1]; + } + + resize(m_size - 1); + } + + @Override + public void addRange(AttributeStreamBase src, int start, int count, + boolean bForward, int stride) { + if (m_bReadonly) + throw new GeometryException("invalid_call"); + + if (!bForward && (stride < 1 || count % stride != 0)) + throw new IllegalArgumentException(); + + int oldSize = m_size; + int newSize = oldSize + count; + resize(newSize); + + if (bForward) { + System.arraycopy(((AttributeStreamOfInt32) src).m_buffer, start, + m_buffer, oldSize, count); + } else { + int n = count; + + for (int i = 0; i < count; i += stride) { + n -= stride; + + for (int s = 0; s < stride; s++) { + m_buffer[oldSize + i + s] = ((AttributeStreamOfInt32) src).m_buffer[start + + n + s]; + } + } + } + } + + @Override + public void insertRange(int start, AttributeStreamBase src, int srcStart, + int count, boolean bForward, int stride, int validSize) { + if (m_bReadonly) + throw new GeometryException("invalid_call"); + + if (!bForward && (stride < 1 || count % stride != 0)) + throw new IllegalArgumentException(); + + System.arraycopy(m_buffer, start, m_buffer, start + count, validSize + - start); + + if (m_buffer == ((AttributeStreamOfInt32) src).m_buffer) { + if (start < srcStart) + srcStart += count; + } + + if (bForward) { + System.arraycopy(((AttributeStreamOfInt32) src).m_buffer, srcStart, + m_buffer, start, count); + } else { + int n = count; + + for (int i = 0; i < count; i += stride) { + n -= stride; + + for (int s = 0; s < stride; s++) { + m_buffer[start + i + s] = ((AttributeStreamOfInt32) src).m_buffer[srcStart + + n + s]; + } + } + } + } + + @Override + public void insertRange(int start, double value, int count, int validSize) { + if (m_bReadonly) + throw new GeometryException("invalid_call"); + + System.arraycopy(m_buffer, start, m_buffer, start + count, validSize + - start); + + int v = (int) value; + Arrays.fill(m_buffer, start, start + count, v); + // for (int i = 0; i < count; i++) + // { + // m_buffer[start + i] = v; + // } + } + + @Override + public void insertAttributes(int start, Point pt, int semantics, + int validSize) { + if (m_bReadonly) + throw new GeometryException("invalid_call"); + + int comp = VertexDescription.getComponentCount(semantics); + + System.arraycopy(m_buffer, start, m_buffer, start + comp, validSize + - start); + + for (int c = 0; c < comp; c++) { + m_buffer[start + c] = (int) pt.getAttributeAsDbl(semantics, c); + } + } + + @Override + public void eraseRange(int index, int count, int validSize) { + if (m_bReadonly) + throw new GeometryException("invalid_call"); + + if (index + count > m_size) + throw new GeometryException("invalid_call"); + + System.arraycopy(m_buffer, index + count, m_buffer, index, validSize + - (index + count)); + m_size -= count; + } + + @Override + public void readRange(int srcStart, int count, ByteBuffer dst, + int dstOffset, boolean bForward) { + if (srcStart < 0 || count < 0 || dstOffset < 0 + || size() < count + srcStart) + throw new IllegalArgumentException(); + + final int elmSize = NumberUtils.sizeOf((double) 0); + + if (dst.capacity() < (int) (dstOffset + elmSize * count)) + throw new IllegalArgumentException(); + + if (count == 0) + return; + + int j = srcStart; + if (!bForward) + j += count - 1; + final int dj = bForward ? 1 : -1; + int offset = dstOffset; + for (int i = 0; i < count; i++, offset += elmSize) { + dst.putInt(offset, m_buffer[j]); + j += dj; + } + } + + @Override + public void reverseRange(int index, int count, int stride) { + if (m_bReadonly) + throw new GeometryException("invalid_call"); + + if (stride < 1 || count % stride != 0) + throw new GeometryException("invalid_call"); + + int cIterations = count >> 1; + int n = count; + + for (int i = 0; i < cIterations; i += stride) { + n -= stride; + + for (int s = 0; s < stride; s++) { + int temp = m_buffer[index + i + s]; + m_buffer[index + i + s] = m_buffer[index + n + s]; + m_buffer[index + n + s] = temp; + } + } + } + + @Override + public void setRange(double value, int start, int count) { + if (start < 0 || count < 0 || start < 0 || count + start > size()) + throw new IllegalArgumentException(); + + int v = (int) value; + Arrays.fill(m_buffer, start, start + count, v); + // for (int i = start, n = start + count; i < n; i++) + // write(i, v); + } + + @Override + public void writeRange(int startElement, int count, + AttributeStreamBase _src, int srcStart, boolean bForward, int stride) { + if (startElement < 0 || count < 0 || srcStart < 0) + throw new IllegalArgumentException(); + + if (!bForward && (stride <= 0 || (count % stride != 0))) + throw new IllegalArgumentException(); + + AttributeStreamOfInt32 src = (AttributeStreamOfInt32) _src; // the input + // type must + // match + + if (src.size() < (int) (srcStart + count)) + throw new IllegalArgumentException(); + + if (count == 0) + return; + + if (size() < count + startElement) + resize(count + startElement); + + if (_src == (AttributeStreamBase) this) { + _selfWriteRangeImpl(startElement, count, srcStart, bForward, stride); + return; + } + + if (bForward) { + System.arraycopy(src.m_buffer, srcStart, m_buffer, startElement, + count); + // int j = startElement; + // int offset = srcStart; + // for (int i = 0; i < count; i++) + // { + // m_buffer[j] = src.m_buffer[offset]; + // j++; + // offset++; + // } + } else { + int j = startElement; + int offset = srcStart + count - stride; + if (stride == 1) { + for (int i = 0; i < count; i++) { + m_buffer[j] = src.m_buffer[offset]; + j++; + offset--; + } + } else { + for (int i = 0, n = count / stride; i < n; i++) { + for (int k = 0; k < stride; k++) + m_buffer[j + k] = src.m_buffer[offset + k]; + + j += stride; + offset -= stride; + } + } + } + } + + private void _selfWriteRangeImpl(int toElement, int count, int fromElement, + boolean bForward, int stride) { + + // writing from to this stream. + if (bForward) { + if (toElement == fromElement) + return; + } + + System.arraycopy(m_buffer, fromElement, m_buffer, toElement, count); + if (bForward) + return; + + // reverse what we written + int j = toElement; + int offset = toElement + count - stride; + int dj = stride; + for (int i = 0, n = count / 2; i < n; i++) { + for (int k = 0; k < stride; k++) { + int v = m_buffer[j + k]; + m_buffer[j + k] = m_buffer[offset + k]; + m_buffer[offset + k] = v; + } + j += stride; + offset -= stride; + } + } + + @Override + public void writeRange(int startElement, int count, ByteBuffer src, + int offsetBytes, boolean bForward) { + if (startElement < 0 || count < 0 || offsetBytes < 0) + throw new IllegalArgumentException(); + + final int elmSize = NumberUtils.sizeOf((double) 0); + if (src.capacity() < (int) (offsetBytes + elmSize * count)) + throw new IllegalArgumentException(); + + if (count == 0) + return; + + if (size() < count + startElement) + resize(count + startElement); + + int j = startElement; + if (!bForward) + j += count - 1; + + final int dj = bForward ? 1 : -1; + + int offset = offsetBytes; + for (int i = 0; i < count; i++, offset += elmSize) { + m_buffer[j] = src.getInt(offset); + j += dj; + } + + } + + static public abstract class IntComparator { + public abstract int compare(int v1, int v2); + } + + ; + + static class RandomSeed { + public int random; + + public RandomSeed() { + random = 1973; + } + } + + public void Sort(int start, int end, IntComparator compare) { + if (end - start < 10) + insertionsort(start, end, compare); + else { + quicksort(start, end - 1, compare, new RandomSeed()); + } + } + + void insertionsort(int start, int end, IntComparator compare) { + for (int j = start; j < end; j++)// insertion sort + { + int key = m_buffer[j]; + int i = j - 1; + while (i >= start && compare.compare(m_buffer[i], key) > 0) { + m_buffer[i + 1] = m_buffer[i]; + i--; + } + m_buffer[i + 1] = key; + } + } + + void swap(int left, int right) { + int tmp = m_buffer[right]; + m_buffer[right] = m_buffer[left]; + m_buffer[left] = tmp; + } + + void quicksort(int leftIn, int rightIn, IntComparator compare, + RandomSeed seed) { + if (leftIn >= rightIn) + return; + + int left = leftIn; + int right = rightIn; + + while (true)// tail recursion loop + { + if (right - left < 9) { + insertionsort(left, right + 1, compare); + return; + } + // Select random index for the pivot + seed.random = NumberUtils.nextRand(seed.random); + long nom = ((long) (right - left)) * seed.random; + int pivotIndex = (int) (nom / NumberUtils.intMax()) + left; + // Get the pivot value + int pivotValue = m_buffer[pivotIndex]; + + // Start partition + // Move pivot to the right + swap(pivotIndex, right); + int storeIndex = left; + for (int i = left; i < right; i++) { + int elm = m_buffer[i]; + if (compare.compare(elm, pivotValue) <= 0) { + swap(storeIndex, i); + storeIndex = storeIndex + 1; + } + } + + // Move pivot to its final place + swap(storeIndex, right); + // End partition + + // Shorter part is regular recursion + // Longer part is tail recursion + if (storeIndex - left < right - storeIndex) { + quicksort(left, storeIndex - 1, compare, seed); + left = storeIndex + 1; + } else { + quicksort(storeIndex + 1, right, compare, seed); + right = storeIndex - 1; + } + + } + } + + public void sort(int start, int end) { + Arrays.sort(m_buffer, start, end); + } } diff --git a/src/main/java/com/esri/core/geometry/AttributeStreamOfInt64.java b/src/main/java/com/esri/core/geometry/AttributeStreamOfInt64.java index 1644b672..df216992 100644 --- a/src/main/java/com/esri/core/geometry/AttributeStreamOfInt64.java +++ b/src/main/java/com/esri/core/geometry/AttributeStreamOfInt64.java @@ -36,116 +36,115 @@ final class AttributeStreamOfInt64 extends AttributeStreamBase { private long[] m_buffer = null; private int m_size; - public int size() { - return m_size; - } - - public void reserve(int reserve)// only in Java - { - if (reserve <= 0) - return; - if (m_buffer == null) - m_buffer = new long[reserve]; - else { - if (reserve <= m_buffer.length) - return; - long[] buf = new long[reserve]; - System.arraycopy(m_buffer, 0, buf, 0, m_size); - m_buffer = buf; - } - - } - - public int capacity() { - return m_buffer != null ? m_buffer.length : 0; - } - - public AttributeStreamOfInt64(int size) { - int sz = size; - if (sz < 2) - sz = 2; - m_buffer = new long[sz]; - m_size = size; - } - - public AttributeStreamOfInt64(int size, long defaultValue) { - int sz = size; - if (sz < 2) - sz = 2; - m_buffer = new long[sz]; - m_size = size; - for (int i = 0; i < size; i++) - m_buffer[i] = defaultValue; - } - - public AttributeStreamOfInt64(AttributeStreamOfInt64 other) { - m_buffer = other.m_buffer.clone(); - m_size = other.m_size; - } - - public AttributeStreamOfInt64(AttributeStreamOfInt64 other, int maxSize) { - m_size = other.size(); - if (m_size > maxSize) - m_size = maxSize; - int sz = m_size; - if (sz < 2) - sz = 2; - m_buffer = new long[sz]; - System.arraycopy(other.m_buffer, 0, m_buffer, 0, m_size); - } - - /** - * Reads a value from the buffer at given offset. - * - * @param offset is the element number in the stream. - */ - public long read(int offset) { - return m_buffer[offset]; - } - - /** - * Overwrites given element with new value. - * - * @param offset is the element number in the stream. - * @param value is the value to write. - */ - public void write(int offset, long value) { - if (m_bReadonly) { - throw new RuntimeException("invalid_call"); - } - m_buffer[offset] = value; - } - - /** - * Adds a new value at the end of the stream. - * - * @param offset is the element number in the stream. - * @param value is the value to write. - */ - public void add(long v) { - resize(m_size + 1); - m_buffer[m_size - 1] = v; - } - - @Override - public AttributeStreamBase restrictedClone(int maxsize) { - int len = m_size; - int newSize = maxsize < len ? maxsize : len; - long[] newBuffer = new long[newSize]; - System.arraycopy(m_buffer, 0, newBuffer, 0, newSize); - m_buffer = newBuffer; - m_size = newSize; - return this; - } - - @Override - public int virtualSize() { - return size(); - } + public int size() { + return m_size; + } - @Override - public long estimateMemorySize() + public void reserve(int reserve)// only in Java { + if (reserve <= 0) + return; + if (m_buffer == null) + m_buffer = new long[reserve]; + else { + if (reserve <= m_buffer.length) + return; + long[] buf = new long[reserve]; + System.arraycopy(m_buffer, 0, buf, 0, m_size); + m_buffer = buf; + } + + } + + public int capacity() { + return m_buffer != null ? m_buffer.length : 0; + } + + public AttributeStreamOfInt64(int size) { + int sz = size; + if (sz < 2) + sz = 2; + m_buffer = new long[sz]; + m_size = size; + } + + public AttributeStreamOfInt64(int size, long defaultValue) { + int sz = size; + if (sz < 2) + sz = 2; + m_buffer = new long[sz]; + m_size = size; + for (int i = 0; i < size; i++) + m_buffer[i] = defaultValue; + } + + public AttributeStreamOfInt64(AttributeStreamOfInt64 other) { + m_buffer = other.m_buffer.clone(); + m_size = other.m_size; + } + + public AttributeStreamOfInt64(AttributeStreamOfInt64 other, int maxSize) { + m_size = other.size(); + if (m_size > maxSize) + m_size = maxSize; + int sz = m_size; + if (sz < 2) + sz = 2; + m_buffer = new long[sz]; + System.arraycopy(other.m_buffer, 0, m_buffer, 0, m_size); + } + + /** + * Reads a value from the buffer at given offset. + * + * @param offset is the element number in the stream. + */ + public long read(int offset) { + return m_buffer[offset]; + } + + /** + * Overwrites given element with new value. + * + * @param offset is the element number in the stream. + * @param value is the value to write. + */ + public void write(int offset, long value) { + if (m_bReadonly) { + throw new RuntimeException("invalid_call"); + } + m_buffer[offset] = value; + } + + /** + * Adds a new value at the end of the stream. + * + * @param offset is the element number in the stream. + * @param value is the value to write. + */ + public void add(long v) { + resize(m_size + 1); + m_buffer[m_size - 1] = v; + } + + @Override + public AttributeStreamBase restrictedClone(int maxsize) { + int len = m_size; + int newSize = maxsize < len ? maxsize : len; + long[] newBuffer = new long[newSize]; + System.arraycopy(m_buffer, 0, newBuffer, 0, newSize); + m_buffer = newBuffer; + m_size = newSize; + return this; + } + + @Override + public int virtualSize() { + return size(); + } + + @Override + public long estimateMemorySize() { return SIZE_OF_ATTRIBUTE_STREAM_OF_INT64 + sizeOfLongArray(m_buffer.length); } @@ -154,447 +153,447 @@ public int getPersistence() { return Persistence.enumInt64; } - @Override - public double readAsDbl(int offset) { - return read(offset); - } - - @Override - public int readAsInt(int offset) { - return (int) read(offset); - } - - @Override - public long readAsInt64(int offset) { - return (long) read(offset); - } - - @Override - public void resize(int newSize) { - if (m_bLockedInSize) - throw new GeometryException( - "invalid call. Attribute Stream is locked and cannot be resized."); - - if (newSize <= m_size) { - if ((newSize * 5) / 4 < m_buffer.length) {// decrease when the 25% - // margin is exceeded - long[] newBuffer = new long[newSize]; - System.arraycopy(m_buffer, 0, newBuffer, 0, newSize); - m_buffer = newBuffer; - } - m_size = newSize; - } else { - if (newSize > m_buffer.length) { - int sz = (newSize < 64) ? Math.max(newSize * 2, 4) - : (newSize * 5) / 4; - long[] newBuffer = new long[sz]; - System.arraycopy(m_buffer, 0, newBuffer, 0, m_size); - m_buffer = newBuffer; - } - - m_size = newSize; - } - } - - @Override - public void resizePreserveCapacity(int newSize)// java only method - { - if (m_buffer == null || newSize > m_buffer.length) - resize(newSize); - if (m_bLockedInSize) - throw new GeometryException( - "invalid call. Attribute Stream is locked and cannot be resized."); - - m_size = newSize; - } - - @Override - public void resize(int newSize, double defaultValue) { - if (m_bLockedInSize) - throw new GeometryException( - "invalid call. Attribute Stream is locked and cannot be resized."); - if (newSize <= m_size) { - if ((newSize * 5) / 4 < m_buffer.length) {// decrease when the 25% - // margin is exceeded - long[] newBuffer = new long[newSize]; - System.arraycopy(m_buffer, 0, newBuffer, 0, newSize); - m_buffer = newBuffer; - } - m_size = newSize; - } else { - if (newSize > m_buffer.length) { - int sz = (newSize < 64) ? Math.max(newSize * 2, 4) - : (newSize * 5) / 4; - long[] newBuffer = new long[sz]; - System.arraycopy(m_buffer, 0, newBuffer, 0, m_size); - m_buffer = newBuffer; - } - - for (int i = m_size; i < newSize; i++) - m_buffer[i] = (long) defaultValue; - - m_size = newSize; - } - } - - @Override - public void writeAsDbl(int offset, double d) { - write(offset, (long) d); - } - - @Override - public void writeAsInt64(int offset, long d) { - write(offset, (long) d); - } - - @Override - public void writeAsInt(int offset, int d) { - write(offset, (long) d); - } - - // @Override - // public void writeRange(int srcStart, int count, ByteBuffer dst, - // int dstOffsetBytes) { - // // TODO Auto-generated method stub - // - // } - - @Override - public int calculateHashImpl(int hashCode, int start, int end) { - for (int i = start, n = size(); i < n && i < end; i++) - hashCode = NumberUtils.hash(hashCode, read(i)); - - return hashCode; - } - - @Override - public boolean equals(AttributeStreamBase other, int start, int end) { - if (other == null) - return false; - - if (!(other instanceof AttributeStreamOfInt64)) - return false; - - AttributeStreamOfInt64 _other = (AttributeStreamOfInt64) other; - - int size = size(); - int sizeOther = _other.size(); - - if (end > size || end > sizeOther && (size != sizeOther)) - return false; - - if (end > size) - end = size; - - for (int i = start; i < end; i++) - if (read(i) != _other.read(i)) - return false; - - return true; - } - - @Override - public void addRange(AttributeStreamBase src, int start, int count, - boolean bForward, int stride) { - if (m_bReadonly) - throw new GeometryException("invalid_call"); - - if (!bForward && (stride < 1 || count % stride != 0)) - throw new IllegalArgumentException(); - - int oldSize = m_size; - int newSize = oldSize + count; - resize(newSize); - - if (bForward) { - System.arraycopy(((AttributeStreamOfInt64) src).m_buffer, start, - m_buffer, oldSize, count); - } else { - int n = count; - - for (int i = 0; i < count; i += stride) { - n -= stride; - - for (int s = 0; s < stride; s++) { - m_buffer[oldSize + i + s] = ((AttributeStreamOfInt64) src).m_buffer[start - + n + s]; - } - } - } - } - - @Override - public void insertRange(int start, AttributeStreamBase src, int srcStart, - int count, boolean bForward, int stride, int validSize) { - if (m_bReadonly) - throw new GeometryException("invalid_call"); - - if (!bForward && (stride < 1 || count % stride != 0)) - throw new IllegalArgumentException(); - - System.arraycopy(m_buffer, start, m_buffer, start + count, validSize - - start); - - if (m_buffer == ((AttributeStreamOfInt64) src).m_buffer) { - if (start < srcStart) - srcStart += count; - } - - if (bForward) { - System.arraycopy(((AttributeStreamOfInt64) src).m_buffer, srcStart, - m_buffer, start, count); - } else { - int n = count; - - for (int i = 0; i < count; i += stride) { - n -= stride; - - for (int s = 0; s < stride; s++) { - m_buffer[start + i + s] = ((AttributeStreamOfInt64) src).m_buffer[srcStart - + n + s]; - } - } - } - } - - @Override - public void insertRange(int start, double value, int count, int validSize) { - if (m_bReadonly) - throw new GeometryException("invalid_call"); - - System.arraycopy(m_buffer, start, m_buffer, start + count, validSize - - start); - - long v = (long) value; - for (int i = 0; i < count; i++) { - m_buffer[start + i] = v; - } - } - - @Override - public void insertAttributes(int start, Point pt, int semantics, - int validSize) { - if (m_bReadonly) - throw new GeometryException("invalid_call"); - - int comp = VertexDescription.getComponentCount(semantics); - - System.arraycopy(m_buffer, start, m_buffer, start + comp, validSize - - start); - - for (int c = 0; c < comp; c++) { - m_buffer[start + c] = (long) pt.getAttributeAsDbl(semantics, c); - } - } - - @Override - public void eraseRange(int index, int count, int validSize) { - if (m_bReadonly) - throw new GeometryException("invalid_call"); - - if (index + count > m_size) - throw new GeometryException("invalid_call"); - - System.arraycopy(m_buffer, index + count, m_buffer, index, validSize - - (index + count)); - m_size -= count; - } - - @Override - public void readRange(int srcStart, int count, ByteBuffer dst, - int dstOffset, boolean bForward) { - if (srcStart < 0 || count < 0 || dstOffset < 0 - || size() < count + srcStart) - throw new IllegalArgumentException(); - - final int elmSize = NumberUtils.sizeOf((double) 0); - - if (dst.capacity() < (int) (dstOffset + elmSize * count)) - throw new IllegalArgumentException(); - - if (count == 0) - return; - - int j = srcStart; - if (!bForward) - j += count - 1; - - final int dj = bForward ? 1 : -1; - - int offset = dstOffset; - for (int i = 0; i < count; i++, offset += elmSize) { - dst.putLong(offset, m_buffer[j]); - j += dj; - } - - } - - @Override - public void reverseRange(int index, int count, int stride) { - if (m_bReadonly) - throw new GeometryException("invalid_call"); - - if (stride < 1 || count % stride != 0) - throw new GeometryException("invalid_call"); - - int cIterations = count >> 1; - int n = count; - - for (int i = 0; i < cIterations; i += stride) { - n -= stride; - - for (int s = 0; s < stride; s++) { - long temp = m_buffer[index + i + s]; - m_buffer[index + i + s] = m_buffer[index + n + s]; - m_buffer[index + n + s] = temp; - } - } - } - - @Override - public void setRange(double value, int start, int count) { - if (start < 0 || count < 0 || start < 0 || count + start > size()) - throw new IllegalArgumentException(); - - long v = (long) value; - for (int i = start, n = start + count; i < n; i++) - write(i, v); - } - - @Override - public void writeRange(int startElement, int count, - AttributeStreamBase _src, int srcStart, boolean bForward, int stride) { - if (startElement < 0 || count < 0 || srcStart < 0) - throw new IllegalArgumentException(); - - if (!bForward && (stride <= 0 || (count % stride != 0))) - throw new IllegalArgumentException(); - - AttributeStreamOfInt64 src = (AttributeStreamOfInt64) _src; // the input - // type must - // match - - if (src.size() < (int) (srcStart + count)) - throw new IllegalArgumentException(); - - if (count == 0) - return; - - if (size() < count + startElement) - resize(count + startElement); - - if (_src == (AttributeStreamBase) this) { - _selfWriteRangeImpl(startElement, count, srcStart, bForward, stride); - return; - } - - if (bForward) { - int j = startElement; - int offset = srcStart; - for (int i = 0; i < count; i++) { - m_buffer[j] = src.m_buffer[offset]; - j++; - offset++; - } - } else { - int j = startElement; - int offset = srcStart + count - stride; - if (stride == 1) { - for (int i = 0; i < count; i++) { - m_buffer[j] = src.m_buffer[offset]; - j++; - offset--; - } - } else { - for (int i = 0, n = count / stride; i < n; i++) { - for (int k = 0; k < stride; k++) - m_buffer[j + k] = src.m_buffer[offset + k]; - - j += stride; - offset -= stride; - } - } - } - } - - private void _selfWriteRangeImpl(int toElement, int count, int fromElement, - boolean bForward, int stride) { - - // writing from to this stream. - if (bForward) { - if (toElement == fromElement) - return; - } - - int offset; - int j; - int dj; - - if (fromElement < toElement) { - offset = fromElement + count - stride; - j = toElement + count - stride; - for (int i = 0, n = count / 2; i < n; i++) { - for (int k = 0; k < stride; k++) { - m_buffer[j + k] = m_buffer[offset + k]; - } - j -= stride; - offset -= stride; - } - } else { - offset = fromElement; - j = toElement; - dj = 1; - for (int i = 0; i < count; i++) { - m_buffer[j] = m_buffer[offset]; - j += 1; - offset++; - } - } - - if (!bForward) { - // reverse what we written - j = toElement; - offset = toElement + count - stride; - dj = stride; - for (int i = 0, n = count / 2; i < n; i++) { - for (int k = 0; k < stride; k++) { - long v = m_buffer[j + k]; - m_buffer[j + k] = m_buffer[offset + k]; - m_buffer[offset + k] = v; - } - j += stride; - offset -= stride; - } - } - } - - @Override - public void writeRange(int startElement, int count, ByteBuffer src, - int offsetBytes, boolean bForward) { - if (startElement < 0 || count < 0 || offsetBytes < 0) - throw new IllegalArgumentException(); - - final int elmSize = NumberUtils.sizeOf((double) 0); - if (src.capacity() < (int) (offsetBytes + elmSize * count)) - throw new IllegalArgumentException(); - - if (count == 0) - return; - - if (size() < count + startElement) - resize(count + startElement); - - int j = startElement; - if (!bForward) - j += count - 1; - - final int dj = bForward ? 1 : -1; - - int offset = offsetBytes; - for (int i = 0; i < count; i++, offset += elmSize) { - m_buffer[j] = src.getLong(offset); - j += dj; - } - - } + @Override + public double readAsDbl(int offset) { + return read(offset); + } + + @Override + public int readAsInt(int offset) { + return (int) read(offset); + } + + @Override + public long readAsInt64(int offset) { + return (long) read(offset); + } + + @Override + public void resize(int newSize) { + if (m_bLockedInSize) + throw new GeometryException( + "invalid call. Attribute Stream is locked and cannot be resized."); + + if (newSize <= m_size) { + if ((newSize * 5) / 4 < m_buffer.length) {// decrease when the 25% + // margin is exceeded + long[] newBuffer = new long[newSize]; + System.arraycopy(m_buffer, 0, newBuffer, 0, newSize); + m_buffer = newBuffer; + } + m_size = newSize; + } else { + if (newSize > m_buffer.length) { + int sz = (newSize < 64) ? Math.max(newSize * 2, 4) + : (newSize * 5) / 4; + long[] newBuffer = new long[sz]; + System.arraycopy(m_buffer, 0, newBuffer, 0, m_size); + m_buffer = newBuffer; + } + + m_size = newSize; + } + } + + @Override + public void resizePreserveCapacity(int newSize)// java only method + { + if (m_buffer == null || newSize > m_buffer.length) + resize(newSize); + if (m_bLockedInSize) + throw new GeometryException( + "invalid call. Attribute Stream is locked and cannot be resized."); + + m_size = newSize; + } + + @Override + public void resize(int newSize, double defaultValue) { + if (m_bLockedInSize) + throw new GeometryException( + "invalid call. Attribute Stream is locked and cannot be resized."); + if (newSize <= m_size) { + if ((newSize * 5) / 4 < m_buffer.length) {// decrease when the 25% + // margin is exceeded + long[] newBuffer = new long[newSize]; + System.arraycopy(m_buffer, 0, newBuffer, 0, newSize); + m_buffer = newBuffer; + } + m_size = newSize; + } else { + if (newSize > m_buffer.length) { + int sz = (newSize < 64) ? Math.max(newSize * 2, 4) + : (newSize * 5) / 4; + long[] newBuffer = new long[sz]; + System.arraycopy(m_buffer, 0, newBuffer, 0, m_size); + m_buffer = newBuffer; + } + + for (int i = m_size; i < newSize; i++) + m_buffer[i] = (long) defaultValue; + + m_size = newSize; + } + } + + @Override + public void writeAsDbl(int offset, double d) { + write(offset, (long) d); + } + + @Override + public void writeAsInt64(int offset, long d) { + write(offset, (long) d); + } + + @Override + public void writeAsInt(int offset, int d) { + write(offset, (long) d); + } + + // @Override + // public void writeRange(int srcStart, int count, ByteBuffer dst, + // int dstOffsetBytes) { + // // TODO Auto-generated method stub + // + // } + + @Override + public int calculateHashImpl(int hashCode, int start, int end) { + for (int i = start, n = size(); i < n && i < end; i++) + hashCode = NumberUtils.hash(hashCode, read(i)); + + return hashCode; + } + + @Override + public boolean equals(AttributeStreamBase other, int start, int end) { + if (other == null) + return false; + + if (!(other instanceof AttributeStreamOfInt64)) + return false; + + AttributeStreamOfInt64 _other = (AttributeStreamOfInt64) other; + + int size = size(); + int sizeOther = _other.size(); + + if (end > size || end > sizeOther && (size != sizeOther)) + return false; + + if (end > size) + end = size; + + for (int i = start; i < end; i++) + if (read(i) != _other.read(i)) + return false; + + return true; + } + + @Override + public void addRange(AttributeStreamBase src, int start, int count, + boolean bForward, int stride) { + if (m_bReadonly) + throw new GeometryException("invalid_call"); + + if (!bForward && (stride < 1 || count % stride != 0)) + throw new IllegalArgumentException(); + + int oldSize = m_size; + int newSize = oldSize + count; + resize(newSize); + + if (bForward) { + System.arraycopy(((AttributeStreamOfInt64) src).m_buffer, start, + m_buffer, oldSize, count); + } else { + int n = count; + + for (int i = 0; i < count; i += stride) { + n -= stride; + + for (int s = 0; s < stride; s++) { + m_buffer[oldSize + i + s] = ((AttributeStreamOfInt64) src).m_buffer[start + + n + s]; + } + } + } + } + + @Override + public void insertRange(int start, AttributeStreamBase src, int srcStart, + int count, boolean bForward, int stride, int validSize) { + if (m_bReadonly) + throw new GeometryException("invalid_call"); + + if (!bForward && (stride < 1 || count % stride != 0)) + throw new IllegalArgumentException(); + + System.arraycopy(m_buffer, start, m_buffer, start + count, validSize + - start); + + if (m_buffer == ((AttributeStreamOfInt64) src).m_buffer) { + if (start < srcStart) + srcStart += count; + } + + if (bForward) { + System.arraycopy(((AttributeStreamOfInt64) src).m_buffer, srcStart, + m_buffer, start, count); + } else { + int n = count; + + for (int i = 0; i < count; i += stride) { + n -= stride; + + for (int s = 0; s < stride; s++) { + m_buffer[start + i + s] = ((AttributeStreamOfInt64) src).m_buffer[srcStart + + n + s]; + } + } + } + } + + @Override + public void insertRange(int start, double value, int count, int validSize) { + if (m_bReadonly) + throw new GeometryException("invalid_call"); + + System.arraycopy(m_buffer, start, m_buffer, start + count, validSize + - start); + + long v = (long) value; + for (int i = 0; i < count; i++) { + m_buffer[start + i] = v; + } + } + + @Override + public void insertAttributes(int start, Point pt, int semantics, + int validSize) { + if (m_bReadonly) + throw new GeometryException("invalid_call"); + + int comp = VertexDescription.getComponentCount(semantics); + + System.arraycopy(m_buffer, start, m_buffer, start + comp, validSize + - start); + + for (int c = 0; c < comp; c++) { + m_buffer[start + c] = (long) pt.getAttributeAsDbl(semantics, c); + } + } + + @Override + public void eraseRange(int index, int count, int validSize) { + if (m_bReadonly) + throw new GeometryException("invalid_call"); + + if (index + count > m_size) + throw new GeometryException("invalid_call"); + + System.arraycopy(m_buffer, index + count, m_buffer, index, validSize + - (index + count)); + m_size -= count; + } + + @Override + public void readRange(int srcStart, int count, ByteBuffer dst, + int dstOffset, boolean bForward) { + if (srcStart < 0 || count < 0 || dstOffset < 0 + || size() < count + srcStart) + throw new IllegalArgumentException(); + + final int elmSize = NumberUtils.sizeOf((double) 0); + + if (dst.capacity() < (int) (dstOffset + elmSize * count)) + throw new IllegalArgumentException(); + + if (count == 0) + return; + + int j = srcStart; + if (!bForward) + j += count - 1; + + final int dj = bForward ? 1 : -1; + + int offset = dstOffset; + for (int i = 0; i < count; i++, offset += elmSize) { + dst.putLong(offset, m_buffer[j]); + j += dj; + } + + } + + @Override + public void reverseRange(int index, int count, int stride) { + if (m_bReadonly) + throw new GeometryException("invalid_call"); + + if (stride < 1 || count % stride != 0) + throw new GeometryException("invalid_call"); + + int cIterations = count >> 1; + int n = count; + + for (int i = 0; i < cIterations; i += stride) { + n -= stride; + + for (int s = 0; s < stride; s++) { + long temp = m_buffer[index + i + s]; + m_buffer[index + i + s] = m_buffer[index + n + s]; + m_buffer[index + n + s] = temp; + } + } + } + + @Override + public void setRange(double value, int start, int count) { + if (start < 0 || count < 0 || start < 0 || count + start > size()) + throw new IllegalArgumentException(); + + long v = (long) value; + for (int i = start, n = start + count; i < n; i++) + write(i, v); + } + + @Override + public void writeRange(int startElement, int count, + AttributeStreamBase _src, int srcStart, boolean bForward, int stride) { + if (startElement < 0 || count < 0 || srcStart < 0) + throw new IllegalArgumentException(); + + if (!bForward && (stride <= 0 || (count % stride != 0))) + throw new IllegalArgumentException(); + + AttributeStreamOfInt64 src = (AttributeStreamOfInt64) _src; // the input + // type must + // match + + if (src.size() < (int) (srcStart + count)) + throw new IllegalArgumentException(); + + if (count == 0) + return; + + if (size() < count + startElement) + resize(count + startElement); + + if (_src == (AttributeStreamBase) this) { + _selfWriteRangeImpl(startElement, count, srcStart, bForward, stride); + return; + } + + if (bForward) { + int j = startElement; + int offset = srcStart; + for (int i = 0; i < count; i++) { + m_buffer[j] = src.m_buffer[offset]; + j++; + offset++; + } + } else { + int j = startElement; + int offset = srcStart + count - stride; + if (stride == 1) { + for (int i = 0; i < count; i++) { + m_buffer[j] = src.m_buffer[offset]; + j++; + offset--; + } + } else { + for (int i = 0, n = count / stride; i < n; i++) { + for (int k = 0; k < stride; k++) + m_buffer[j + k] = src.m_buffer[offset + k]; + + j += stride; + offset -= stride; + } + } + } + } + + private void _selfWriteRangeImpl(int toElement, int count, int fromElement, + boolean bForward, int stride) { + + // writing from to this stream. + if (bForward) { + if (toElement == fromElement) + return; + } + + int offset; + int j; + int dj; + + if (fromElement < toElement) { + offset = fromElement + count - stride; + j = toElement + count - stride; + for (int i = 0, n = count / 2; i < n; i++) { + for (int k = 0; k < stride; k++) { + m_buffer[j + k] = m_buffer[offset + k]; + } + j -= stride; + offset -= stride; + } + } else { + offset = fromElement; + j = toElement; + dj = 1; + for (int i = 0; i < count; i++) { + m_buffer[j] = m_buffer[offset]; + j += 1; + offset++; + } + } + + if (!bForward) { + // reverse what we written + j = toElement; + offset = toElement + count - stride; + dj = stride; + for (int i = 0, n = count / 2; i < n; i++) { + for (int k = 0; k < stride; k++) { + long v = m_buffer[j + k]; + m_buffer[j + k] = m_buffer[offset + k]; + m_buffer[offset + k] = v; + } + j += stride; + offset -= stride; + } + } + } + + @Override + public void writeRange(int startElement, int count, ByteBuffer src, + int offsetBytes, boolean bForward) { + if (startElement < 0 || count < 0 || offsetBytes < 0) + throw new IllegalArgumentException(); + + final int elmSize = NumberUtils.sizeOf((double) 0); + if (src.capacity() < (int) (offsetBytes + elmSize * count)) + throw new IllegalArgumentException(); + + if (count == 0) + return; + + if (size() < count + startElement) + resize(count + startElement); + + int j = startElement; + if (!bForward) + j += count - 1; + + final int dj = bForward ? 1 : -1; + + int offset = offsetBytes; + for (int i = 0; i < count; i++, offset += elmSize) { + m_buffer[j] = src.getLong(offset); + j += dj; + } + + } } diff --git a/src/main/java/com/esri/core/geometry/AttributeStreamOfInt8.java b/src/main/java/com/esri/core/geometry/AttributeStreamOfInt8.java index 8de6d38a..56d8f80d 100644 --- a/src/main/java/com/esri/core/geometry/AttributeStreamOfInt8.java +++ b/src/main/java/com/esri/core/geometry/AttributeStreamOfInt8.java @@ -34,126 +34,125 @@ final class AttributeStreamOfInt8 extends AttributeStreamBase { - private byte[] m_buffer = null; - private int m_size; - - public int size() { - return m_size; - } - - public void reserve(int reserve)// only in Java - { - if (reserve <= 0) - return; - if (m_buffer == null) - m_buffer = new byte[reserve]; - else { - if (reserve <= m_buffer.length) - return; - byte[] buf = new byte[reserve]; - System.arraycopy(m_buffer, 0, buf, 0, m_size); - m_buffer = buf; - } - - } - - public int capacity() { - return m_buffer != null ? m_buffer.length : 0; - } - - public AttributeStreamOfInt8(int size) { - int sz = size; - if (sz < 2) - sz = 2; - m_buffer = new byte[sz]; - m_size = size; - } - - public AttributeStreamOfInt8(int size, byte defaultValue) { - int sz = size; - if (sz < 2) - sz = 2; - m_buffer = new byte[sz]; - m_size = size; - for (int i = 0; i < size; i++) - m_buffer[i] = defaultValue; - } - - public AttributeStreamOfInt8(AttributeStreamOfInt8 other) { - m_buffer = other.m_buffer.clone(); - m_size = other.m_size; - } - - public AttributeStreamOfInt8(AttributeStreamOfInt8 other, int maxSize) { - m_size = other.size(); - if (m_size > maxSize) - m_size = maxSize; - int sz = m_size; - if (sz < 2) - sz = 2; - m_buffer = new byte[sz]; - System.arraycopy(other.m_buffer, 0, m_buffer, 0, m_size); - } - - /** - * Reads a value from the buffer at given offset. - * - * @param offset is the element number in the stream. - */ - public byte read(int offset) { - return m_buffer[offset]; - } - - /** - * Overwrites given element with new value. - * - * @param offset is the element number in the stream. - * @param value is the value to write. - */ - public void write(int offset, byte value) { - if (m_bReadonly) { - throw new RuntimeException("invalid_call"); - } - m_buffer[offset] = value; - } - - public void set(int offset, byte value) { - if (m_bReadonly) { - throw new RuntimeException("invalid_call"); - } - m_buffer[offset] = value; - } - - /** - * Adds a new value at the end of the stream. - * - * @param offset is the element number in the stream. - * @param value is the value to write. - */ - public void add(byte v) { - resize(m_size + 1); - m_buffer[m_size - 1] = v; - } - - @Override - public AttributeStreamBase restrictedClone(int maxsize) { - int len = m_size; - int newSize = maxsize < len ? maxsize : len; - byte[] newBuffer = new byte[newSize]; - System.arraycopy(m_buffer, 0, newBuffer, 0, newSize); - m_buffer = newBuffer; - m_size = newSize; - return this; - } - - @Override - public int virtualSize() { - return size(); - } + private byte[] m_buffer = null; + private int m_size; - @Override - public long estimateMemorySize() + public int size() { + return m_size; + } + + public void reserve(int reserve)// only in Java { + if (reserve <= 0) + return; + if (m_buffer == null) + m_buffer = new byte[reserve]; + else { + if (reserve <= m_buffer.length) + return; + byte[] buf = new byte[reserve]; + System.arraycopy(m_buffer, 0, buf, 0, m_size); + m_buffer = buf; + } + + } + + public int capacity() { + return m_buffer != null ? m_buffer.length : 0; + } + + public AttributeStreamOfInt8(int size) { + int sz = size; + if (sz < 2) + sz = 2; + m_buffer = new byte[sz]; + m_size = size; + } + + public AttributeStreamOfInt8(int size, byte defaultValue) { + int sz = size; + if (sz < 2) + sz = 2; + m_buffer = new byte[sz]; + m_size = size; + for (int i = 0; i < size; i++) + m_buffer[i] = defaultValue; + } + + public AttributeStreamOfInt8(AttributeStreamOfInt8 other) { + m_buffer = other.m_buffer.clone(); + m_size = other.m_size; + } + + public AttributeStreamOfInt8(AttributeStreamOfInt8 other, int maxSize) { + m_size = other.size(); + if (m_size > maxSize) + m_size = maxSize; + int sz = m_size; + if (sz < 2) + sz = 2; + m_buffer = new byte[sz]; + System.arraycopy(other.m_buffer, 0, m_buffer, 0, m_size); + } + + /** + * Reads a value from the buffer at given offset. + * + * @param offset is the element number in the stream. + */ + public byte read(int offset) { + return m_buffer[offset]; + } + + /** + * Overwrites given element with new value. + * + * @param offset is the element number in the stream. + * @param value is the value to write. + */ + public void write(int offset, byte value) { + if (m_bReadonly) { + throw new RuntimeException("invalid_call"); + } + m_buffer[offset] = value; + } + + public void set(int offset, byte value) { + if (m_bReadonly) { + throw new RuntimeException("invalid_call"); + } + m_buffer[offset] = value; + } + + /** + * Adds a new value at the end of the stream. + * + * @param offset is the element number in the stream. + * @param value is the value to write. + */ + public void add(byte v) { + resize(m_size + 1); + m_buffer[m_size - 1] = v; + } + + @Override + public AttributeStreamBase restrictedClone(int maxsize) { + int len = m_size; + int newSize = maxsize < len ? maxsize : len; + byte[] newBuffer = new byte[newSize]; + System.arraycopy(m_buffer, 0, newBuffer, 0, newSize); + m_buffer = newBuffer; + m_size = newSize; + return this; + } + + @Override + public int virtualSize() { + return size(); + } + + @Override + public long estimateMemorySize() { return SIZE_OF_ATTRIBUTE_STREAM_OF_INT8 + sizeOfByteArray(m_buffer.length); } @@ -162,488 +161,488 @@ public int getPersistence() { return Persistence.enumInt8; } - @Override - public double readAsDbl(int offset) { - return read(offset); - } - - int get(int offset) { - return m_buffer[offset]; - } - - @Override - public int readAsInt(int offset) { - return (int) read(offset); - } - - @Override - public long readAsInt64(int offset) { - return (long) read(offset); - } - - @Override - public void resize(int newSize) { - if (m_bLockedInSize) - throw new GeometryException( - "invalid call. Attribute Stream is locked and cannot be resized."); - - if (newSize <= m_size) { - if ((newSize * 5) / 4 < m_buffer.length) {// decrease when the 25% - // margin is exceeded - byte[] newBuffer = new byte[newSize]; - System.arraycopy(m_buffer, 0, newBuffer, 0, newSize); - m_buffer = newBuffer; - } - m_size = newSize; - } else { - if (newSize > m_buffer.length) { - int sz = (newSize < 64) ? Math.max(newSize * 2, 4) - : (newSize * 5) / 4; - byte[] newBuffer = new byte[sz]; - System.arraycopy(m_buffer, 0, newBuffer, 0, m_size); - m_buffer = newBuffer; - } - - m_size = newSize; - } - } - - @Override - public void resizePreserveCapacity(int newSize)// java only method - { - if (m_buffer == null || newSize > m_buffer.length) - resize(newSize); - if (m_bLockedInSize) - throw new GeometryException( - "invalid call. Attribute Stream is locked and cannot be resized."); - - m_size = newSize; - } - - @Override - public void resize(int newSize, double defaultValue) { - if (m_bLockedInSize) - throw new GeometryException( - "invalid call. Attribute Stream is locked and cannot be resized."); - if (newSize <= m_size) { - if ((newSize * 5) / 4 < m_buffer.length) {// decrease when the 25% - // margin is exceeded - byte[] newBuffer = new byte[newSize]; - System.arraycopy(m_buffer, 0, newBuffer, 0, newSize); - m_buffer = newBuffer; - } - m_size = newSize; - } else { - if (newSize > m_buffer.length) { - int sz = (newSize < 64) ? Math.max(newSize * 2, 4) - : (newSize * 5) / 4; - byte[] newBuffer = new byte[sz]; - System.arraycopy(m_buffer, 0, newBuffer, 0, m_size); - m_buffer = newBuffer; - } - - for (int i = m_size; i < newSize; i++) - m_buffer[i] = (byte) defaultValue; - - m_size = newSize; - } - } - - @Override - public void writeAsDbl(int offset, double d) { - write(offset, (byte) d); - } - - @Override - public void writeAsInt64(int offset, long d) { - write(offset, (byte) d); - } - - @Override - public void writeAsInt(int offset, int d) { - write(offset, (byte) d); - } - - // @Override - // public void writeRange(int srcStart, int count, ByteBuffer dst, - // int dstOffsetBytes) { - // // TODO Auto-generated method stub - // - // } - - /** - * OR's the given element with new value. - * - * @param offset is the element number in the stream. - * @param value is the value to OR. - */ - public void setBits(int offset, byte mask) { - if (m_bReadonly) - throw new GeometryException( - "invalid call. Attribute Stream is read only."); - - m_buffer[offset] = (byte) (m_buffer[offset] | mask); - } - - /** - * Clears bits in the given element that a set in the value param. - * - * @param offset is the element number in the stream. - * @param value is the mask to clear. - */ - void clearBits(int offset, byte mask) { - - if (m_bReadonly) - throw new GeometryException( - "invalid call. Attribute Stream is read only."); - - m_buffer[offset] = (byte) (m_buffer[offset] & (~mask)); - } - - @Override - public int calculateHashImpl(int hashCode, int start, int end) { - for (int i = start, n = size(); i < n && i < end; i++) - hashCode = NumberUtils.hash(hashCode, read(i)); - - return hashCode; - } - - @Override - public boolean equals(AttributeStreamBase other, int start, int end) { - if (other == null) - return false; - - if (!(other instanceof AttributeStreamOfInt8)) - return false; - - AttributeStreamOfInt8 _other = (AttributeStreamOfInt8) other; - - int size = size(); - int sizeOther = _other.size(); - - if (end > size || end > sizeOther && (size != sizeOther)) - return false; - - if (end > size) - end = size; - - for (int i = start; i < end; i++) - if (read(i) != _other.read(i)) - return false; - - return true; - } - - public byte getLast() { - return m_buffer[m_size - 1]; - } - - public void removeLast() { - resize(m_size - 1); - } - - @Override - public void addRange(AttributeStreamBase src, int start, int count, - boolean bForward, int stride) { - if (m_bReadonly) - throw new GeometryException("invalid_call"); - - if (!bForward && (stride < 1 || count % stride != 0)) - throw new IllegalArgumentException(); - - int oldSize = m_size; - int newSize = oldSize + count; - resize(newSize); - - if (bForward) { - System.arraycopy(((AttributeStreamOfInt8) src).m_buffer, start, - m_buffer, oldSize, count); - } else { - int n = count; - - for (int i = 0; i < count; i += stride) { - n -= stride; - - for (int s = 0; s < stride; s++) { - m_buffer[oldSize + i + s] = ((AttributeStreamOfInt8) src).m_buffer[start - + n + s]; - } - } - } - } - - @Override - public void insertRange(int start, AttributeStreamBase src, int srcStart, - int count, boolean bForward, int stride, int validSize) { - if (m_bReadonly) - throw new GeometryException("invalid_call"); - - if (!bForward && (stride < 1 || count % stride != 0)) - throw new IllegalArgumentException(); - - System.arraycopy(m_buffer, start, m_buffer, start + count, validSize - - start); - - if (m_buffer == ((AttributeStreamOfInt8) src).m_buffer) { - if (start < srcStart) - srcStart += count; - } - - if (bForward) { - System.arraycopy(((AttributeStreamOfInt8) src).m_buffer, srcStart, - m_buffer, start, count); - } else { - int n = count; - - for (int i = 0; i < count; i += stride) { - n -= stride; - - for (int s = 0; s < stride; s++) { - m_buffer[start + i + s] = ((AttributeStreamOfInt8) src).m_buffer[srcStart - + n + s]; - } - } - } - } - - @Override - public void insertRange(int start, double value, int count, int validSize) { - if (m_bReadonly) - throw new GeometryException("invalid_call"); - - System.arraycopy(m_buffer, start, m_buffer, start + count, validSize - - start); - - byte v = (byte) value; - for (int i = 0; i < count; i++) { - m_buffer[start + i] = v; - } - } - - @Override - public void insertAttributes(int start, Point pt, int semantics, - int validSize) { - if (m_bReadonly) - throw new GeometryException("invalid_call"); - - int comp = VertexDescription.getComponentCount(semantics); - - System.arraycopy(m_buffer, start, m_buffer, start + comp, validSize - - start); - - for (int c = 0; c < comp; c++) { - m_buffer[start + c] = (byte) pt.getAttributeAsDbl(semantics, c); - } - } - - @Override - public void eraseRange(int index, int count, int validSize) { - if (m_bReadonly) - throw new GeometryException("invalid_call"); - - if (index + count > m_size) - throw new GeometryException("invalid_call"); - - System.arraycopy(m_buffer, index + count, m_buffer, index, validSize - - (index + count)); - m_size -= count; - } - - @Override - public void readRange(int srcStart, int count, ByteBuffer dst, - int dstOffset, boolean bForward) { - if (srcStart < 0 || count < 0 || dstOffset < 0 - || size() < count + srcStart) - throw new IllegalArgumentException(); - - final int elmSize = NumberUtils.sizeOf((double) 0); - - if (dst.capacity() < (int) (dstOffset + elmSize * count)) - throw new IllegalArgumentException(); - - if (count == 0) - return; - - int j = srcStart; - if (!bForward) - j += count - 1; - - final int dj = bForward ? 1 : -1; - - int offset = dstOffset; - for (int i = 0; i < count; i++, offset += elmSize) { - dst.put(offset, m_buffer[j]); - j += dj; - } - - } - - @Override - public void reverseRange(int index, int count, int stride) { - if (m_bReadonly) - throw new GeometryException("invalid_call"); - - if (stride < 1 || count % stride != 0) - throw new GeometryException("invalid_call"); - - int cIterations = count >> 1; - int n = count; - - for (int i = 0; i < cIterations; i += stride) { - n -= stride; - - for (int s = 0; s < stride; s++) { - byte temp = m_buffer[index + i + s]; - m_buffer[index + i + s] = m_buffer[index + n + s]; - m_buffer[index + n + s] = temp; - } - } - } - - @Override - public void setRange(double value, int start, int count) { - if (start < 0 || count < 0 || start < 0 || count + start > size()) - throw new IllegalArgumentException(); - - byte v = (byte) value; - for (int i = start, n = start + count; i < n; i++) - write(i, v); - } - - @Override - public void writeRange(int startElement, int count, - AttributeStreamBase _src, int srcStart, boolean bForward, int stride) { - if (startElement < 0 || count < 0 || srcStart < 0) - throw new IllegalArgumentException(); - - if (!bForward && (stride <= 0 || (count % stride != 0))) - throw new IllegalArgumentException(); - - AttributeStreamOfInt8 src = (AttributeStreamOfInt8) _src; // the input - // type must - // match - - if (src.size() < (int) (srcStart + count)) - throw new IllegalArgumentException(); - - if (count == 0) - return; - - if (size() < count + startElement) - resize(count + startElement); - - if (_src == (AttributeStreamBase) this) { - _selfWriteRangeImpl(startElement, count, srcStart, bForward, stride); - return; - } - - if (bForward) { - int j = startElement; - int offset = srcStart; - for (int i = 0; i < count; i++) { - m_buffer[j] = src.m_buffer[offset]; - j++; - offset++; - } - } else { - int j = startElement; - int offset = srcStart + count - stride; - if (stride == 1) { - for (int i = 0; i < count; i++) { - m_buffer[j] = src.m_buffer[offset]; - j++; - offset--; - } - } else { - for (int i = 0, n = count / stride; i < n; i++) { - for (int k = 0; k < stride; k++) - m_buffer[j + k] = src.m_buffer[offset + k]; - - j += stride; - offset -= stride; - } - } - } - } - - private void _selfWriteRangeImpl(int toElement, int count, int fromElement, - boolean bForward, int stride) { - - // writing from to this stream. - if (bForward) { - if (toElement == fromElement) - return; - } - - int offset; - int j; - int dj; - - if (fromElement < toElement) { - offset = fromElement + count - stride; - j = toElement + count - stride; - for (int i = 0, n = count / 2; i < n; i++) { - for (int k = 0; k < stride; k++) { - m_buffer[j + k] = m_buffer[offset + k]; - } - j -= stride; - offset -= stride; - } - } else { - offset = fromElement; - j = toElement; - dj = 1; - for (int i = 0; i < count; i++) { - m_buffer[j] = m_buffer[offset]; - j += 1; - offset++; - } - } - - if (!bForward) { - // reverse what we written - j = toElement; - offset = toElement + count - stride; - dj = stride; - for (int i = 0, n = count / 2; i < n; i++) { - for (int k = 0; k < stride; k++) { - byte v = m_buffer[j + k]; - m_buffer[j + k] = m_buffer[offset + k]; - m_buffer[offset + k] = v; - } - j += stride; - offset -= stride; - } - } - } - - @Override - public void writeRange(int startElement, int count, ByteBuffer src, - int offsetBytes, boolean bForward) { - if (startElement < 0 || count < 0 || offsetBytes < 0) - throw new IllegalArgumentException(); - - final int elmSize = NumberUtils.sizeOf((double) 0); - if (src.capacity() < (int) (offsetBytes + elmSize * count)) - throw new IllegalArgumentException(); - - if (count == 0) - return; - - if (size() < count + startElement) - resize(count + startElement); - - int j = startElement; - if (!bForward) - j += count - 1; - - final int dj = bForward ? 1 : -1; - - int offset = offsetBytes; - for (int i = 0; i < count; i++, offset += elmSize) { - m_buffer[j] = src.get(offset); - j += dj; - } - - } + @Override + public double readAsDbl(int offset) { + return read(offset); + } + + int get(int offset) { + return m_buffer[offset]; + } + + @Override + public int readAsInt(int offset) { + return (int) read(offset); + } + + @Override + public long readAsInt64(int offset) { + return (long) read(offset); + } + + @Override + public void resize(int newSize) { + if (m_bLockedInSize) + throw new GeometryException( + "invalid call. Attribute Stream is locked and cannot be resized."); + + if (newSize <= m_size) { + if ((newSize * 5) / 4 < m_buffer.length) {// decrease when the 25% + // margin is exceeded + byte[] newBuffer = new byte[newSize]; + System.arraycopy(m_buffer, 0, newBuffer, 0, newSize); + m_buffer = newBuffer; + } + m_size = newSize; + } else { + if (newSize > m_buffer.length) { + int sz = (newSize < 64) ? Math.max(newSize * 2, 4) + : (newSize * 5) / 4; + byte[] newBuffer = new byte[sz]; + System.arraycopy(m_buffer, 0, newBuffer, 0, m_size); + m_buffer = newBuffer; + } + + m_size = newSize; + } + } + + @Override + public void resizePreserveCapacity(int newSize)// java only method + { + if (m_buffer == null || newSize > m_buffer.length) + resize(newSize); + if (m_bLockedInSize) + throw new GeometryException( + "invalid call. Attribute Stream is locked and cannot be resized."); + + m_size = newSize; + } + + @Override + public void resize(int newSize, double defaultValue) { + if (m_bLockedInSize) + throw new GeometryException( + "invalid call. Attribute Stream is locked and cannot be resized."); + if (newSize <= m_size) { + if ((newSize * 5) / 4 < m_buffer.length) {// decrease when the 25% + // margin is exceeded + byte[] newBuffer = new byte[newSize]; + System.arraycopy(m_buffer, 0, newBuffer, 0, newSize); + m_buffer = newBuffer; + } + m_size = newSize; + } else { + if (newSize > m_buffer.length) { + int sz = (newSize < 64) ? Math.max(newSize * 2, 4) + : (newSize * 5) / 4; + byte[] newBuffer = new byte[sz]; + System.arraycopy(m_buffer, 0, newBuffer, 0, m_size); + m_buffer = newBuffer; + } + + for (int i = m_size; i < newSize; i++) + m_buffer[i] = (byte) defaultValue; + + m_size = newSize; + } + } + + @Override + public void writeAsDbl(int offset, double d) { + write(offset, (byte) d); + } + + @Override + public void writeAsInt64(int offset, long d) { + write(offset, (byte) d); + } + + @Override + public void writeAsInt(int offset, int d) { + write(offset, (byte) d); + } + + // @Override + // public void writeRange(int srcStart, int count, ByteBuffer dst, + // int dstOffsetBytes) { + // // TODO Auto-generated method stub + // + // } + + /** + * OR's the given element with new value. + * + * @param offset is the element number in the stream. + * @param value is the value to OR. + */ + public void setBits(int offset, byte mask) { + if (m_bReadonly) + throw new GeometryException( + "invalid call. Attribute Stream is read only."); + + m_buffer[offset] = (byte) (m_buffer[offset] | mask); + } + + /** + * Clears bits in the given element that a set in the value param. + * + * @param offset is the element number in the stream. + * @param value is the mask to clear. + */ + void clearBits(int offset, byte mask) { + + if (m_bReadonly) + throw new GeometryException( + "invalid call. Attribute Stream is read only."); + + m_buffer[offset] = (byte) (m_buffer[offset] & (~mask)); + } + + @Override + public int calculateHashImpl(int hashCode, int start, int end) { + for (int i = start, n = size(); i < n && i < end; i++) + hashCode = NumberUtils.hash(hashCode, read(i)); + + return hashCode; + } + + @Override + public boolean equals(AttributeStreamBase other, int start, int end) { + if (other == null) + return false; + + if (!(other instanceof AttributeStreamOfInt8)) + return false; + + AttributeStreamOfInt8 _other = (AttributeStreamOfInt8) other; + + int size = size(); + int sizeOther = _other.size(); + + if (end > size || end > sizeOther && (size != sizeOther)) + return false; + + if (end > size) + end = size; + + for (int i = start; i < end; i++) + if (read(i) != _other.read(i)) + return false; + + return true; + } + + public byte getLast() { + return m_buffer[m_size - 1]; + } + + public void removeLast() { + resize(m_size - 1); + } + + @Override + public void addRange(AttributeStreamBase src, int start, int count, + boolean bForward, int stride) { + if (m_bReadonly) + throw new GeometryException("invalid_call"); + + if (!bForward && (stride < 1 || count % stride != 0)) + throw new IllegalArgumentException(); + + int oldSize = m_size; + int newSize = oldSize + count; + resize(newSize); + + if (bForward) { + System.arraycopy(((AttributeStreamOfInt8) src).m_buffer, start, + m_buffer, oldSize, count); + } else { + int n = count; + + for (int i = 0; i < count; i += stride) { + n -= stride; + + for (int s = 0; s < stride; s++) { + m_buffer[oldSize + i + s] = ((AttributeStreamOfInt8) src).m_buffer[start + + n + s]; + } + } + } + } + + @Override + public void insertRange(int start, AttributeStreamBase src, int srcStart, + int count, boolean bForward, int stride, int validSize) { + if (m_bReadonly) + throw new GeometryException("invalid_call"); + + if (!bForward && (stride < 1 || count % stride != 0)) + throw new IllegalArgumentException(); + + System.arraycopy(m_buffer, start, m_buffer, start + count, validSize + - start); + + if (m_buffer == ((AttributeStreamOfInt8) src).m_buffer) { + if (start < srcStart) + srcStart += count; + } + + if (bForward) { + System.arraycopy(((AttributeStreamOfInt8) src).m_buffer, srcStart, + m_buffer, start, count); + } else { + int n = count; + + for (int i = 0; i < count; i += stride) { + n -= stride; + + for (int s = 0; s < stride; s++) { + m_buffer[start + i + s] = ((AttributeStreamOfInt8) src).m_buffer[srcStart + + n + s]; + } + } + } + } + + @Override + public void insertRange(int start, double value, int count, int validSize) { + if (m_bReadonly) + throw new GeometryException("invalid_call"); + + System.arraycopy(m_buffer, start, m_buffer, start + count, validSize + - start); + + byte v = (byte) value; + for (int i = 0; i < count; i++) { + m_buffer[start + i] = v; + } + } + + @Override + public void insertAttributes(int start, Point pt, int semantics, + int validSize) { + if (m_bReadonly) + throw new GeometryException("invalid_call"); + + int comp = VertexDescription.getComponentCount(semantics); + + System.arraycopy(m_buffer, start, m_buffer, start + comp, validSize + - start); + + for (int c = 0; c < comp; c++) { + m_buffer[start + c] = (byte) pt.getAttributeAsDbl(semantics, c); + } + } + + @Override + public void eraseRange(int index, int count, int validSize) { + if (m_bReadonly) + throw new GeometryException("invalid_call"); + + if (index + count > m_size) + throw new GeometryException("invalid_call"); + + System.arraycopy(m_buffer, index + count, m_buffer, index, validSize + - (index + count)); + m_size -= count; + } + + @Override + public void readRange(int srcStart, int count, ByteBuffer dst, + int dstOffset, boolean bForward) { + if (srcStart < 0 || count < 0 || dstOffset < 0 + || size() < count + srcStart) + throw new IllegalArgumentException(); + + final int elmSize = NumberUtils.sizeOf((double) 0); + + if (dst.capacity() < (int) (dstOffset + elmSize * count)) + throw new IllegalArgumentException(); + + if (count == 0) + return; + + int j = srcStart; + if (!bForward) + j += count - 1; + + final int dj = bForward ? 1 : -1; + + int offset = dstOffset; + for (int i = 0; i < count; i++, offset += elmSize) { + dst.put(offset, m_buffer[j]); + j += dj; + } + + } + + @Override + public void reverseRange(int index, int count, int stride) { + if (m_bReadonly) + throw new GeometryException("invalid_call"); + + if (stride < 1 || count % stride != 0) + throw new GeometryException("invalid_call"); + + int cIterations = count >> 1; + int n = count; + + for (int i = 0; i < cIterations; i += stride) { + n -= stride; + + for (int s = 0; s < stride; s++) { + byte temp = m_buffer[index + i + s]; + m_buffer[index + i + s] = m_buffer[index + n + s]; + m_buffer[index + n + s] = temp; + } + } + } + + @Override + public void setRange(double value, int start, int count) { + if (start < 0 || count < 0 || start < 0 || count + start > size()) + throw new IllegalArgumentException(); + + byte v = (byte) value; + for (int i = start, n = start + count; i < n; i++) + write(i, v); + } + + @Override + public void writeRange(int startElement, int count, + AttributeStreamBase _src, int srcStart, boolean bForward, int stride) { + if (startElement < 0 || count < 0 || srcStart < 0) + throw new IllegalArgumentException(); + + if (!bForward && (stride <= 0 || (count % stride != 0))) + throw new IllegalArgumentException(); + + AttributeStreamOfInt8 src = (AttributeStreamOfInt8) _src; // the input + // type must + // match + + if (src.size() < (int) (srcStart + count)) + throw new IllegalArgumentException(); + + if (count == 0) + return; + + if (size() < count + startElement) + resize(count + startElement); + + if (_src == (AttributeStreamBase) this) { + _selfWriteRangeImpl(startElement, count, srcStart, bForward, stride); + return; + } + + if (bForward) { + int j = startElement; + int offset = srcStart; + for (int i = 0; i < count; i++) { + m_buffer[j] = src.m_buffer[offset]; + j++; + offset++; + } + } else { + int j = startElement; + int offset = srcStart + count - stride; + if (stride == 1) { + for (int i = 0; i < count; i++) { + m_buffer[j] = src.m_buffer[offset]; + j++; + offset--; + } + } else { + for (int i = 0, n = count / stride; i < n; i++) { + for (int k = 0; k < stride; k++) + m_buffer[j + k] = src.m_buffer[offset + k]; + + j += stride; + offset -= stride; + } + } + } + } + + private void _selfWriteRangeImpl(int toElement, int count, int fromElement, + boolean bForward, int stride) { + + // writing from to this stream. + if (bForward) { + if (toElement == fromElement) + return; + } + + int offset; + int j; + int dj; + + if (fromElement < toElement) { + offset = fromElement + count - stride; + j = toElement + count - stride; + for (int i = 0, n = count / 2; i < n; i++) { + for (int k = 0; k < stride; k++) { + m_buffer[j + k] = m_buffer[offset + k]; + } + j -= stride; + offset -= stride; + } + } else { + offset = fromElement; + j = toElement; + dj = 1; + for (int i = 0; i < count; i++) { + m_buffer[j] = m_buffer[offset]; + j += 1; + offset++; + } + } + + if (!bForward) { + // reverse what we written + j = toElement; + offset = toElement + count - stride; + dj = stride; + for (int i = 0, n = count / 2; i < n; i++) { + for (int k = 0; k < stride; k++) { + byte v = m_buffer[j + k]; + m_buffer[j + k] = m_buffer[offset + k]; + m_buffer[offset + k] = v; + } + j += stride; + offset -= stride; + } + } + } + + @Override + public void writeRange(int startElement, int count, ByteBuffer src, + int offsetBytes, boolean bForward) { + if (startElement < 0 || count < 0 || offsetBytes < 0) + throw new IllegalArgumentException(); + + final int elmSize = NumberUtils.sizeOf((double) 0); + if (src.capacity() < (int) (offsetBytes + elmSize * count)) + throw new IllegalArgumentException(); + + if (count == 0) + return; + + if (size() < count + startElement) + resize(count + startElement); + + int j = startElement; + if (!bForward) + j += count - 1; + + final int dj = bForward ? 1 : -1; + + int offset = offsetBytes; + for (int i = 0; i < count; i++, offset += elmSize) { + m_buffer[j] = src.get(offset); + j += dj; + } + + } } diff --git a/src/main/java/com/esri/core/geometry/Boundary.java b/src/main/java/com/esri/core/geometry/Boundary.java index 7dcfc243..f8ee242f 100644 --- a/src/main/java/com/esri/core/geometry/Boundary.java +++ b/src/main/java/com/esri/core/geometry/Boundary.java @@ -25,213 +25,213 @@ class Boundary { - static boolean hasNonEmptyBoundary(Geometry geom, - ProgressTracker progress_tracker) { - if (geom.isEmpty()) - return false; - - Geometry.Type gt = geom.getType(); - if (gt == Geometry.Type.Polygon) { - if (geom.calculateArea2D() == 0) - return false; - - return true; - } else if (gt == Geometry.Type.Polyline) { - boolean[] b = new boolean[1]; - b[0] = false; - calculatePolylineBoundary_(geom._getImpl(), progress_tracker, true, - b); - return b[0]; - } else if (gt == Geometry.Type.Envelope) { - return true; - } else if (Geometry.isSegment(gt.value())) { - if (!((Segment) geom).isClosed()) { - return true; - } - - return false; - } else if (Geometry.isPoint(gt.value())) { - return false; - } - - return false; - } - - static Geometry calculate(Geometry geom, ProgressTracker progress_tracker) { - int gt = geom.getType().value(); - if (gt == Geometry.GeometryType.Polygon) { - Polyline dst = new Polyline(geom.getDescription()); - if (!geom.isEmpty()) { - ((MultiPathImpl) geom._getImpl()) - ._copyToUnsafe((MultiPathImpl) dst._getImpl()); - } - - return dst; - } else if (gt == Geometry.GeometryType.Polyline) { - return calculatePolylineBoundary_(geom._getImpl(), - progress_tracker, false, null); - } else if (gt == Geometry.GeometryType.Envelope) { - Polyline dst = new Polyline(geom.getDescription()); - if (!geom.isEmpty()) - dst.addEnvelope((Envelope) geom, false); - - return dst; - } else if (Geometry.isSegment(gt)) { - MultiPoint mp = new MultiPoint(geom.getDescription()); - if (!geom.isEmpty() && !((Segment) geom).isClosed()) { - Point pt = new Point(); - ((Segment) geom).queryStart(pt); - mp.add(pt); - ((Segment) geom).queryEnd(pt); - mp.add(pt); - } - return mp; - } else if (Geometry.isPoint(gt)) { - // returns empty point for points and multipoints. - return null; - } - - throw new IllegalArgumentException(); - } - - private static final class MultiPathImplBoundarySorter extends ClassicSort { - AttributeStreamOfDbl m_xy; - - static final class CompareIndices extends - AttributeStreamOfInt32.IntComparator { - AttributeStreamOfDbl m_xy; - Point2D pt1_helper; - Point2D pt2_helper; - - CompareIndices(AttributeStreamOfDbl xy) { - m_xy = xy; - pt1_helper = new Point2D(); - pt2_helper = new Point2D(); - } - - @Override - public int compare(int v1, int v2) { - m_xy.read(2 * v1, pt1_helper); - m_xy.read(2 * v2, pt2_helper); - return pt1_helper.compare(pt2_helper); - } - } - - MultiPathImplBoundarySorter(AttributeStreamOfDbl xy) { - m_xy = xy; - } - - @Override - public void userSort(int begin, int end, AttributeStreamOfInt32 indices) { - indices.Sort(begin, end, new CompareIndices(m_xy)); - } - - @Override - public double getValue(int index) { - return m_xy.read(2 * index + 1); - } - } - - static MultiPoint calculatePolylineBoundary_(Object impl, - ProgressTracker progress_tracker, - boolean only_check_non_empty_boundary, boolean[] not_empty) { - if (not_empty != null) - not_empty[0] = false; - MultiPathImpl mpImpl = (MultiPathImpl) impl; - MultiPoint dst = null; - if (!only_check_non_empty_boundary) - dst = new MultiPoint(mpImpl.getDescription()); - - if (!mpImpl.isEmpty()) { - AttributeStreamOfInt32 indices = new AttributeStreamOfInt32(0); - indices.reserve(mpImpl.getPathCount() * 2); - for (int ipath = 0, nPathCount = mpImpl.getPathCount(); ipath < nPathCount; ipath++) { - int path_size = mpImpl.getPathSize(ipath); - if (path_size > 0 && !mpImpl.isClosedPathInXYPlane(ipath))// closed - // paths - // of - // polyline - // do - // not - // contribute - // to - // the - // boundary. - { - int start = mpImpl.getPathStart(ipath); - indices.add(start); - int end = mpImpl.getPathEnd(ipath) - 1; - indices.add(end); - } - } - if (indices.size() > 0) { - BucketSort sorter = new BucketSort(); - AttributeStreamOfDbl xy = (AttributeStreamOfDbl) (mpImpl - .getAttributeStreamRef(VertexDescription.Semantics.POSITION)); - sorter.sort(indices, 0, indices.size(), - new MultiPathImplBoundarySorter(xy)); - Point2D ptPrev = new Point2D(); - xy.read(2 * indices.get(0), ptPrev); - int ind = 0; - int counter = 1; - Point point = new Point(); - Point2D pt = new Point2D(); - for (int i = 1, n = indices.size(); i < n; i++) { - xy.read(2 * indices.get(i), pt); - if (pt.isEqual(ptPrev)) { - if (indices.get(ind) > indices.get(i)) { - // remove duplicate point - indices.set(ind, NumberUtils.intMax()); - ind = i;// just for the heck of it, have the first - // point in the order to be added to the - // boundary. - } else - indices.set(i, NumberUtils.intMax()); - - counter++; - } else { - if ((counter & 1) == 0) {// remove boundary point - indices.set(ind, NumberUtils.intMax()); - } else { - if (only_check_non_empty_boundary) { - if (not_empty != null) - not_empty[0] = true; - return null; - } - } - - ptPrev.setCoords(pt); - ind = i; - counter = 1; - } - } - - if ((counter & 1) == 0) {// remove the point - indices.set(ind, NumberUtils.intMax()); - } else { - if (only_check_non_empty_boundary) { - if (not_empty != null) - not_empty[0] = true; - return null; - } - } - if (!only_check_non_empty_boundary) { - indices.sort(0, indices.size()); - - for (int i = 0, n = indices.size(); i < n; i++) { - if (indices.get(i) == NumberUtils.intMax()) - break; - - mpImpl.getPointByVal(indices.get(i), point); - dst.add(point); - } - } - } - } - - if (only_check_non_empty_boundary) - return null; - - return dst; - } + static boolean hasNonEmptyBoundary(Geometry geom, + ProgressTracker progress_tracker) { + if (geom.isEmpty()) + return false; + + Geometry.Type gt = geom.getType(); + if (gt == Geometry.Type.Polygon) { + if (geom.calculateArea2D() == 0) + return false; + + return true; + } else if (gt == Geometry.Type.Polyline) { + boolean[] b = new boolean[1]; + b[0] = false; + calculatePolylineBoundary_(geom._getImpl(), progress_tracker, true, + b); + return b[0]; + } else if (gt == Geometry.Type.Envelope) { + return true; + } else if (Geometry.isSegment(gt.value())) { + if (!((Segment) geom).isClosed()) { + return true; + } + + return false; + } else if (Geometry.isPoint(gt.value())) { + return false; + } + + return false; + } + + static Geometry calculate(Geometry geom, ProgressTracker progress_tracker) { + int gt = geom.getType().value(); + if (gt == Geometry.GeometryType.Polygon) { + Polyline dst = new Polyline(geom.getDescription()); + if (!geom.isEmpty()) { + ((MultiPathImpl) geom._getImpl()) + ._copyToUnsafe((MultiPathImpl) dst._getImpl()); + } + + return dst; + } else if (gt == Geometry.GeometryType.Polyline) { + return calculatePolylineBoundary_(geom._getImpl(), + progress_tracker, false, null); + } else if (gt == Geometry.GeometryType.Envelope) { + Polyline dst = new Polyline(geom.getDescription()); + if (!geom.isEmpty()) + dst.addEnvelope((Envelope) geom, false); + + return dst; + } else if (Geometry.isSegment(gt)) { + MultiPoint mp = new MultiPoint(geom.getDescription()); + if (!geom.isEmpty() && !((Segment) geom).isClosed()) { + Point pt = new Point(); + ((Segment) geom).queryStart(pt); + mp.add(pt); + ((Segment) geom).queryEnd(pt); + mp.add(pt); + } + return mp; + } else if (Geometry.isPoint(gt)) { + // returns empty point for points and multipoints. + return null; + } + + throw new IllegalArgumentException(); + } + + private static final class MultiPathImplBoundarySorter extends ClassicSort { + AttributeStreamOfDbl m_xy; + + static final class CompareIndices extends + AttributeStreamOfInt32.IntComparator { + AttributeStreamOfDbl m_xy; + Point2D pt1_helper; + Point2D pt2_helper; + + CompareIndices(AttributeStreamOfDbl xy) { + m_xy = xy; + pt1_helper = new Point2D(); + pt2_helper = new Point2D(); + } + + @Override + public int compare(int v1, int v2) { + m_xy.read(2 * v1, pt1_helper); + m_xy.read(2 * v2, pt2_helper); + return pt1_helper.compare(pt2_helper); + } + } + + MultiPathImplBoundarySorter(AttributeStreamOfDbl xy) { + m_xy = xy; + } + + @Override + public void userSort(int begin, int end, AttributeStreamOfInt32 indices) { + indices.Sort(begin, end, new CompareIndices(m_xy)); + } + + @Override + public double getValue(int index) { + return m_xy.read(2 * index + 1); + } + } + + static MultiPoint calculatePolylineBoundary_(Object impl, + ProgressTracker progress_tracker, + boolean only_check_non_empty_boundary, boolean[] not_empty) { + if (not_empty != null) + not_empty[0] = false; + MultiPathImpl mpImpl = (MultiPathImpl) impl; + MultiPoint dst = null; + if (!only_check_non_empty_boundary) + dst = new MultiPoint(mpImpl.getDescription()); + + if (!mpImpl.isEmpty()) { + AttributeStreamOfInt32 indices = new AttributeStreamOfInt32(0); + indices.reserve(mpImpl.getPathCount() * 2); + for (int ipath = 0, nPathCount = mpImpl.getPathCount(); ipath < nPathCount; ipath++) { + int path_size = mpImpl.getPathSize(ipath); + if (path_size > 0 && !mpImpl.isClosedPathInXYPlane(ipath))// closed + // paths + // of + // polyline + // do + // not + // contribute + // to + // the + // boundary. + { + int start = mpImpl.getPathStart(ipath); + indices.add(start); + int end = mpImpl.getPathEnd(ipath) - 1; + indices.add(end); + } + } + if (indices.size() > 0) { + BucketSort sorter = new BucketSort(); + AttributeStreamOfDbl xy = (AttributeStreamOfDbl) (mpImpl + .getAttributeStreamRef(VertexDescription.Semantics.POSITION)); + sorter.sort(indices, 0, indices.size(), + new MultiPathImplBoundarySorter(xy)); + Point2D ptPrev = new Point2D(); + xy.read(2 * indices.get(0), ptPrev); + int ind = 0; + int counter = 1; + Point point = new Point(); + Point2D pt = new Point2D(); + for (int i = 1, n = indices.size(); i < n; i++) { + xy.read(2 * indices.get(i), pt); + if (pt.isEqual(ptPrev)) { + if (indices.get(ind) > indices.get(i)) { + // remove duplicate point + indices.set(ind, NumberUtils.intMax()); + ind = i;// just for the heck of it, have the first + // point in the order to be added to the + // boundary. + } else + indices.set(i, NumberUtils.intMax()); + + counter++; + } else { + if ((counter & 1) == 0) {// remove boundary point + indices.set(ind, NumberUtils.intMax()); + } else { + if (only_check_non_empty_boundary) { + if (not_empty != null) + not_empty[0] = true; + return null; + } + } + + ptPrev.setCoords(pt); + ind = i; + counter = 1; + } + } + + if ((counter & 1) == 0) {// remove the point + indices.set(ind, NumberUtils.intMax()); + } else { + if (only_check_non_empty_boundary) { + if (not_empty != null) + not_empty[0] = true; + return null; + } + } + if (!only_check_non_empty_boundary) { + indices.sort(0, indices.size()); + + for (int i = 0, n = indices.size(); i < n; i++) { + if (indices.get(i) == NumberUtils.intMax()) + break; + + mpImpl.getPointByVal(indices.get(i), point); + dst.add(point); + } + } + } + } + + if (only_check_non_empty_boundary) + return null; + + return dst; + } } diff --git a/src/main/java/com/esri/core/geometry/BucketSort.java b/src/main/java/com/esri/core/geometry/BucketSort.java index 17fadefe..94ced6df 100644 --- a/src/main/java/com/esri/core/geometry/BucketSort.java +++ b/src/main/java/com/esri/core/geometry/BucketSort.java @@ -24,155 +24,155 @@ package com.esri.core.geometry; final class BucketSort { - AttributeStreamOfInt32 m_buckets; - AttributeStreamOfInt32 m_bucketed_indices; - double m_min_value; - double m_max_value; - double m_dy; - - static int MAXBUCKETS = 65536; - - public BucketSort() { - m_buckets = new AttributeStreamOfInt32(0); - m_bucketed_indices = new AttributeStreamOfInt32(0); - m_min_value = 1; - m_max_value = -1; - m_dy = NumberUtils.TheNaN; - } - - /** - * Executes sort on the Bucket_sort. The result is fed into the indices - * array in the range between begin (inclusive) and end (exclusive). Uses - * user supplied sorter to execute sort on each bucket. Users either supply - * the sorter and use this method of Bucket_sort class, or use other methods - * to form the buckets and take care of bucket sorting themselves. - */ - public void sort(AttributeStreamOfInt32 indices, int begin, int end, - ClassicSort sorter) { - if (end - begin < 32) { - sorter.userSort(begin, end, indices); - return; - } - boolean b_fallback = true; - try { - double miny = NumberUtils.positiveInf(); - double maxy = NumberUtils.negativeInf(); - for (int i = begin; i < end; i++) { - double y = sorter.getValue(indices.get(i)); - if (y < miny) - miny = y; - if (y > maxy) - maxy = y; - } - - if (reset(end - begin, miny, maxy, end - begin)) { - for (int i = begin; i < end; i++) { - int vertex = indices.get(i); - double y = sorter.getValue(vertex); - int bucket = getBucket(y); - m_buckets.set(bucket, m_buckets.get(bucket) + 1);// counting - // values - // in a - // bucket. - m_bucketed_indices.write(i - begin, vertex); - } - - // Recalculate buckets to contain start positions of buckets. - int c = m_buckets.get(0); - m_buckets.set(0, 0); - for (int i = 1, n = m_buckets.size(); i < n; i++) { - int b = m_buckets.get(i); - m_buckets.set(i, c); - c += b; - } - - for (int i = begin; i < end; i++) { - int vertex = m_bucketed_indices.read(i - begin); - double y = sorter.getValue(vertex); - int bucket = getBucket(y); - int bucket_index = m_buckets.get(bucket); - indices.set(bucket_index + begin, vertex); - m_buckets.set(bucket, bucket_index + 1); - } - - b_fallback = false; - } - } catch (Exception e) { - m_buckets.resize(0); - m_bucketed_indices.resize(0); - } - - if (b_fallback) { - sorter.userSort(begin, end, indices); - return; - } - - int j = 0; - for (int i = 0, n = m_buckets.size(); i < n; i++) { - int j0 = j; - j = m_buckets.get(i); - if (j > j0) - sorter.userSort(begin + j0, begin + j, indices); - } - assert (j == end); - - if (getBucketCount() > 100) // some heuristics to preserve memory - { - m_buckets.resize(0); - m_bucketed_indices.resize(0); - } - } - - /** - * Clears and resets Bucket_sort to the new state, preparing for the - * accumulation of new data. - * - * @param bucket_count - the number of buckets. Usually equal to the number of - * elements to sort. - * @param min_value - the minimum value of elements to sort. - * @param max_value - the maximum value of elements to sort. - * @param capacity - the number of elements to sort (-1 if not known). The - * bucket_count are usually equal. - * @return Returns False, if the bucket sort cannot be used with the given - * parameters. The method also can throw out of memory exception. In - * the later case, one should fall back to the regular sort. - */ - private boolean reset(int bucket_count, double min_value, double max_value, - int capacity) { - if (bucket_count < 2 || max_value == min_value) - return false; - - int bc = Math.min(MAXBUCKETS, bucket_count); - m_buckets.reserve(bc); - m_buckets.resize(bc); - m_buckets.setRange(0, 0, m_buckets.size()); - m_min_value = min_value; - m_max_value = max_value; - m_bucketed_indices.resize(capacity); - - m_dy = (max_value - min_value) / (bc - 1); - return true; - } - - /** - * Adds new element to the bucket builder. The value must be between - * min_value and max_value. - * - * @param The value used for bucketing. - * @param The index of the element to store in the buffer. Usually it is an - * index into some array, where the real elements are stored. - */ - private int getBucket(double value) { - assert (value >= m_min_value && value <= m_max_value); - int bucket = (int) ((value - m_min_value) / m_dy); - return bucket; - } - - /** - * Returns the bucket count. - */ - private int getBucketCount() { - return m_buckets.size(); - } + AttributeStreamOfInt32 m_buckets; + AttributeStreamOfInt32 m_bucketed_indices; + double m_min_value; + double m_max_value; + double m_dy; + + static int MAXBUCKETS = 65536; + + public BucketSort() { + m_buckets = new AttributeStreamOfInt32(0); + m_bucketed_indices = new AttributeStreamOfInt32(0); + m_min_value = 1; + m_max_value = -1; + m_dy = NumberUtils.TheNaN; + } + + /** + * Executes sort on the Bucket_sort. The result is fed into the indices + * array in the range between begin (inclusive) and end (exclusive). Uses + * user supplied sorter to execute sort on each bucket. Users either supply + * the sorter and use this method of Bucket_sort class, or use other methods + * to form the buckets and take care of bucket sorting themselves. + */ + public void sort(AttributeStreamOfInt32 indices, int begin, int end, + ClassicSort sorter) { + if (end - begin < 32) { + sorter.userSort(begin, end, indices); + return; + } + boolean b_fallback = true; + try { + double miny = NumberUtils.positiveInf(); + double maxy = NumberUtils.negativeInf(); + for (int i = begin; i < end; i++) { + double y = sorter.getValue(indices.get(i)); + if (y < miny) + miny = y; + if (y > maxy) + maxy = y; + } + + if (reset(end - begin, miny, maxy, end - begin)) { + for (int i = begin; i < end; i++) { + int vertex = indices.get(i); + double y = sorter.getValue(vertex); + int bucket = getBucket(y); + m_buckets.set(bucket, m_buckets.get(bucket) + 1);// counting + // values + // in a + // bucket. + m_bucketed_indices.write(i - begin, vertex); + } + + // Recalculate buckets to contain start positions of buckets. + int c = m_buckets.get(0); + m_buckets.set(0, 0); + for (int i = 1, n = m_buckets.size(); i < n; i++) { + int b = m_buckets.get(i); + m_buckets.set(i, c); + c += b; + } + + for (int i = begin; i < end; i++) { + int vertex = m_bucketed_indices.read(i - begin); + double y = sorter.getValue(vertex); + int bucket = getBucket(y); + int bucket_index = m_buckets.get(bucket); + indices.set(bucket_index + begin, vertex); + m_buckets.set(bucket, bucket_index + 1); + } + + b_fallback = false; + } + } catch (Exception e) { + m_buckets.resize(0); + m_bucketed_indices.resize(0); + } + + if (b_fallback) { + sorter.userSort(begin, end, indices); + return; + } + + int j = 0; + for (int i = 0, n = m_buckets.size(); i < n; i++) { + int j0 = j; + j = m_buckets.get(i); + if (j > j0) + sorter.userSort(begin + j0, begin + j, indices); + } + assert (j == end); + + if (getBucketCount() > 100) // some heuristics to preserve memory + { + m_buckets.resize(0); + m_bucketed_indices.resize(0); + } + } + + /** + * Clears and resets Bucket_sort to the new state, preparing for the + * accumulation of new data. + * + * @param bucket_count - the number of buckets. Usually equal to the number of + * elements to sort. + * @param min_value - the minimum value of elements to sort. + * @param max_value - the maximum value of elements to sort. + * @param capacity - the number of elements to sort (-1 if not known). The + * bucket_count are usually equal. + * @return Returns False, if the bucket sort cannot be used with the given + * parameters. The method also can throw out of memory exception. In + * the later case, one should fall back to the regular sort. + */ + private boolean reset(int bucket_count, double min_value, double max_value, + int capacity) { + if (bucket_count < 2 || max_value == min_value) + return false; + + int bc = Math.min(MAXBUCKETS, bucket_count); + m_buckets.reserve(bc); + m_buckets.resize(bc); + m_buckets.setRange(0, 0, m_buckets.size()); + m_min_value = min_value; + m_max_value = max_value; + m_bucketed_indices.resize(capacity); + + m_dy = (max_value - min_value) / (bc - 1); + return true; + } + + /** + * Adds new element to the bucket builder. The value must be between + * min_value and max_value. + * + * @param The value used for bucketing. + * @param The index of the element to store in the buffer. Usually it is an + * index into some array, where the real elements are stored. + */ + private int getBucket(double value) { + assert (value >= m_min_value && value <= m_max_value); + int bucket = (int) ((value - m_min_value) / m_dy); + return bucket; + } + + /** + * Returns the bucket count. + */ + private int getBucketCount() { + return m_buckets.size(); + } } diff --git a/src/main/java/com/esri/core/geometry/Bufferer.java b/src/main/java/com/esri/core/geometry/Bufferer.java index 535b93e4..9280486f 100755 --- a/src/main/java/com/esri/core/geometry/Bufferer.java +++ b/src/main/java/com/esri/core/geometry/Bufferer.java @@ -27,1908 +27,1910 @@ import java.util.List; class Bufferer { - Bufferer() { - m_buffer_commands = new ArrayList(128); - m_progress_tracker = null; - m_tolerance = 0; - m_small_tolerance = 0; - m_filter_tolerance = 0; - m_distance = 0; - m_original_geom_type = Geometry.GeometryType.Unknown; - m_abs_distance_reversed = 0; - m_abs_distance = 0; - m_densify_dist = -1; - m_dA = -1; - m_b_output_loops = true; - m_bfilter = true; - m_old_circle_template_size = 0; - } - - - /** - * Result is always a polygon. For non positive distance and non-areas - * returns an empty polygon. For points returns circles. - */ - Geometry buffer(Geometry geometry, double distance, - SpatialReference sr, double densify_dist, - int max_vertex_in_complete_circle, ProgressTracker progress_tracker) { - if (geometry == null) - throw new IllegalArgumentException(); - - if (densify_dist < 0) - throw new IllegalArgumentException(); - - if (geometry.isEmpty()) - return new Polygon(geometry.getDescription()); - - Envelope2D env2D = new Envelope2D(); - geometry.queryLooseEnvelope2D(env2D); - if (distance > 0) - env2D.inflate(distance, distance); - - m_progress_tracker = progress_tracker; - - m_original_geom_type = geometry.getType().value(); - m_geometry = geometry; - m_tolerance = InternalUtils.calculateToleranceFromGeometry(sr, - env2D, true);// conservative to have same effect as simplify - m_small_tolerance = InternalUtils - .calculateToleranceFromGeometry(null, env2D, true);// conservative - // to have - // same - // effect as - // simplify - - if (max_vertex_in_complete_circle <= 0) { - max_vertex_in_complete_circle = 96;// 96 is the value used by SG. - // This is the number of - // vertices in the full circle. - } - - m_spatialReference = sr; - m_distance = distance; - m_abs_distance = Math.abs(m_distance); - m_abs_distance_reversed = m_abs_distance != 0 ? 1.0 / m_abs_distance - : 0; - - if (NumberUtils.isNaN(densify_dist) || densify_dist == 0) { - densify_dist = m_abs_distance * 1e-5; - } else { - if (densify_dist > m_abs_distance * 0.5) - densify_dist = m_abs_distance * 0.5;// do not allow too - // large densify - // distance (the - // value will be - // adjusted - // anyway later) - } - - if (max_vertex_in_complete_circle < 12) - max_vertex_in_complete_circle = 12; - - - double max_dd = Math.abs(distance) - * (1 - Math.cos(Math.PI / max_vertex_in_complete_circle)); - - if (max_dd > densify_dist) - densify_dist = max_dd;// the densify distance has to agree with the - // max_vertex_in_complete_circle - else { - double vertex_count = Math.PI / Math.acos(1.0 - densify_dist / Math.abs(distance)); - if (vertex_count < (double) max_vertex_in_complete_circle - 1.0) { - max_vertex_in_complete_circle = (int) vertex_count; - if (max_vertex_in_complete_circle < 12) { - max_vertex_in_complete_circle = 12; - densify_dist = Math.abs(distance) * (1 - Math.cos(Math.PI / max_vertex_in_complete_circle)); - } - } - } - - m_densify_dist = densify_dist; - m_max_vertex_in_complete_circle = max_vertex_in_complete_circle; - // when filtering close points we do not want the filter to distort - // generated buffer too much. - m_filter_tolerance = Math.min(m_small_tolerance, - densify_dist * 0.25); - - - m_circle_template_size = calcN_(); - if (m_circle_template_size != m_old_circle_template_size) { - // we have an optimization for this method to be called several - // times. Here we detected too many changes and need to regenerate - // the data. - m_circle_template.clear(); - m_old_circle_template_size = m_circle_template_size; - } - - Geometry result_geom = buffer_(); - m_geometry = null; - return result_geom; - } - - private Geometry m_geometry; - - private static final class BufferCommand { - private interface Flags { - static final int enum_line = 1; - static final int enum_arc = 2; - static final int enum_connection = enum_arc | enum_line; - } - - private Point2D m_from; - private Point2D m_to; - private Point2D m_center; - private int m_next; - private int m_prev; - private int m_type; - - private BufferCommand(Point2D from, Point2D to, Point2D center, - int type, int next, int prev) { - m_from = new Point2D(); - m_to = new Point2D(); - m_center = new Point2D(); - m_from.setCoords(from); - m_to.setCoords(to); - m_center.setCoords(center); - m_type = type; - m_next = next; - m_prev = prev; - } - - private BufferCommand(Point2D from, Point2D to, int next, int prev, - String dummy) { - m_from = new Point2D(); - m_to = new Point2D(); - m_center = new Point2D(); - m_from.setCoords(from); - m_to.setCoords(to); - m_center.setNaN(); - m_type = 4; - m_next = next; - m_prev = prev; - } - } - - private ArrayList m_buffer_commands; - - private int m_original_geom_type; - private ProgressTracker m_progress_tracker; - private int m_max_vertex_in_complete_circle; - private SpatialReference m_spatialReference; - private double m_tolerance; - private double m_small_tolerance; - private double m_filter_tolerance; - private double m_densify_dist; - private double m_distance; - private double m_abs_distance; - private double m_abs_distance_reversed; - private double m_dA; - private boolean m_b_output_loops; - private boolean m_bfilter; - private ArrayList m_circle_template = new ArrayList(0); - private ArrayList m_left_stack; - private ArrayList m_middle_stack; - private Line m_helper_line_1; - private Line m_helper_line_2; - private Point2D[] m_helper_array; - private int m_progress_counter; - private int m_circle_template_size; - private int m_old_circle_template_size; - - private void generateCircleTemplate_() { - if (!m_circle_template.isEmpty()) { - return; - } - - int N = m_circle_template_size; - - assert (N >= 4); - int real_size = (N + 3) / 4; - double dA = (Math.PI * 0.5) / real_size; - m_dA = dA; - - for (int i = 0; i < real_size * 4; i++) - m_circle_template.add(null); - - double dcos = Math.cos(dA); - double dsin = Math.sin(dA); - Point2D pt = new Point2D(0.0, 1.0); - - for (int i = 0; i < real_size; i++) { - m_circle_template.set(i + real_size * 0, new Point2D(pt.y, -pt.x)); - m_circle_template.set(i + real_size * 1, new Point2D(-pt.x, -pt.y)); - m_circle_template.set(i + real_size * 2, new Point2D(-pt.y, pt.x)); - m_circle_template.set(i + real_size * 3, pt); - pt = new Point2D(pt.x, pt.y); - pt.rotateReverse(dcos, dsin); - } - // the template is filled with the index 0 corresponding to the point - // (0, 0), following clockwise direction (0, -1), (-1, 0), (1, 0) - } - - private static final class GeometryCursorForMultiPoint extends GeometryCursor { - private Bufferer m_parent; - private int m_index; - private Geometry m_buffered_polygon; - private MultiPoint m_mp; - private SpatialReference m_spatialReference; - private double m_distance; - private double m_densify_dist; - private double m_x; - private double m_y; - private int m_max_vertex_in_complete_circle; - private ProgressTracker m_progress_tracker; - - GeometryCursorForMultiPoint(Bufferer parent, MultiPoint mp, double distance, - SpatialReference sr, double densify_dist, - int max_vertex_in_complete_circle, - ProgressTracker progress_tracker) { - m_parent = parent; - m_index = 0; - m_mp = mp; - m_x = 0; - m_y = 0; - m_distance = distance; - m_spatialReference = sr; - m_densify_dist = densify_dist; - m_max_vertex_in_complete_circle = max_vertex_in_complete_circle; - m_progress_tracker = progress_tracker; - } - - @Override - public Geometry next() { - Point point = new Point(); - while (true) { - if (m_index == m_mp.getPointCount()) - return null; - - m_mp.getPointByVal(m_index, point); - m_index++; - if (point.isEmpty()) - continue; - break; - } - - boolean b_first = false; - if (m_buffered_polygon == null) { - m_x = point.getX(); - m_y = point.getY(); - - m_buffered_polygon = m_parent.buffer(point, m_distance, - m_spatialReference, m_densify_dist, - m_max_vertex_in_complete_circle, m_progress_tracker); - b_first = true; - } - - Geometry res; - if (m_index < m_mp.getPointCount()) { - res = new Polygon(); - m_buffered_polygon.copyTo(res); - } else { - res = m_buffered_polygon; // do not clone the last geometry. - } - - if (!b_first)// don't apply transformation unnecessary - { - Transformation2D transform = new Transformation2D(); - double dx = point.getX() - m_x; - double dy = point.getY() - m_y; - transform.setShift(dx, dy); - res.applyTransformation(transform); - } - - return res; - } - - @Override - // TODO unclear how this might work - public boolean hasNext() { return m_mp.getPointCount() > m_index; } - - @Override - public long getGeometryID() { - return 0; - } - } - - private static final class GlueingCursorForPolyline extends GeometryCursor { - private Polyline m_polyline; - private int m_current_path_index; - - GlueingCursorForPolyline(Polyline polyline) { - m_polyline = polyline; - m_current_path_index = 0; - } - - @Override - public Geometry next() { - if (m_polyline == null) - return null; - - MultiPathImpl mp = (MultiPathImpl) m_polyline._getImpl(); - int npaths = mp.getPathCount(); - if (m_current_path_index < npaths) { - int ind = m_current_path_index; - m_current_path_index++; - if (!mp.isClosedPathInXYPlane(ind)) { - // connect paths that follow one another as an optimization - // for buffering (helps when one polyline is split into many - // segments). - Point2D prev_end = mp.getXY(mp.getPathEnd(ind) - 1); - while (m_current_path_index < mp.getPathCount()) { - Point2D start = mp.getXY(mp - .getPathStart(m_current_path_index)); - if (mp.isClosedPathInXYPlane(m_current_path_index)) - break; - if (start != prev_end) - break; - - prev_end = mp - .getXY(mp.getPathEnd(m_current_path_index) - 1); - m_current_path_index++; - } - } - - if (ind == 0 - && m_current_path_index == m_polyline.getPathCount()) { - Polyline pol = m_polyline; - m_polyline = null; - return pol; - } - - Polyline tmp_polyline = new Polyline( - m_polyline.getDescription()); - tmp_polyline.addPath(m_polyline, ind, true); - for (int i = ind + 1; i < m_current_path_index; i++) { - tmp_polyline.addSegmentsFromPath(m_polyline, i, 0, - mp.getSegmentCount(i), false); - } - - if (false) { - OperatorFactoryLocal.saveGeometryToEsriShapeDbg( - "c:/temp/_geom.bin", tmp_polyline); - } - - if (m_current_path_index == m_polyline.getPathCount()) - m_polyline = null; - - return tmp_polyline; - } else { - return null; - } - } - - @Override - public boolean hasNext() { - return m_polyline != null && m_current_path_index < ((MultiPathImpl)m_polyline._getImpl()).getPathCount(); - } - - @Override - public long getGeometryID() { - return 0; - } - } - - private static final class GeometryCursorForPolyline extends GeometryCursor { - private Bufferer m_bufferer; - GeometryCursor m_geoms; - Geometry m_geometry; - private int m_index; - private boolean m_bfilter; - - GeometryCursorForPolyline(Bufferer bufferer, GeometryCursor geoms, - boolean bfilter) { - m_bufferer = bufferer; - m_geoms = geoms; - m_index = 0; - m_bfilter = bfilter; - } - - @Override - public Geometry next() { - if (m_geometry == null) { - m_index = 0; - m_geometry = m_geoms.next(); - if (m_geometry == null) - return null; - } - - MultiPath mp = (MultiPath) (m_geometry); - if (m_index < mp.getPathCount()) { - int ind = m_index; - m_index++; - return m_bufferer.bufferPolylinePath_((Polyline) m_geometry, - ind, m_bfilter); - } - - m_geometry = null; - return next(); - } - - @Override - public boolean hasNext() { - return (m_geoms != null && m_geoms.hasNext()) || (m_geometry != null && m_index < ((MultiPath)m_geometry).getPathCount()); - } - - @Override - public long getGeometryID() { - return 0; - } - } - - private static final class GeometryCursorForPolygon extends GeometryCursor { - private Bufferer m_bufferer; - private int m_index; - - GeometryCursorForPolygon(Bufferer bufferer) { - m_bufferer = bufferer; - m_index = 0; - } - - @Override - public Geometry next() { - Polygon input_polygon = (Polygon) (m_bufferer.m_geometry); - if (m_index < input_polygon.getPathCount()) { - int ind = m_index; - double area = input_polygon.calculateRingArea2D(m_index); - assert (area > 0); - m_index++; - while (m_index < input_polygon.getPathCount()) { - double hole_area = input_polygon - .calculateRingArea2D(m_index); - if (hole_area > 0) - break;// not a hole - m_index++; - } - - if (ind == 0 && m_index == input_polygon.getPathCount()) { - return m_bufferer.bufferPolygonImpl_(input_polygon, 0, - input_polygon.getPathCount()); - } else { - return m_bufferer.bufferPolygonImpl_(input_polygon, ind, - m_index); - } - } - - return null; - } - - @Override - public boolean hasNext() { - return m_index < ((Polygon)m_bufferer.m_geometry).getPathCount(); - } - - @Override - public long getGeometryID() { - return 0; - } - } - - private Geometry buffer_() { - int gt = m_geometry.getType().value(); - if (Geometry.isSegment(gt)) {// convert segment to a polyline and repeat - // the call - Polyline polyline = new Polyline(m_geometry.getDescription()); - polyline.addSegment((Segment) (m_geometry), true); - m_geometry = polyline; - return buffer_(); - } - - if (m_distance <= m_tolerance) { - if (Geometry.isArea(gt)) { - if (m_distance <= 0) { - // if the geometry is area type, then the negative distance - // may produce a degenerate shape. Check for this and return - // empty geometry. - Envelope2D env = new Envelope2D(); - m_geometry.queryEnvelope2D(env); - if (env.getWidth() <= -m_distance * 2 - || env.getHeight() <= m_distance * 2) - return new Polygon(m_geometry.getDescription()); - } - } else { - return new Polygon(m_geometry.getDescription());// return an - // empty polygon - // for distance - // <= - // m_tolerance - // and any input - // other than - // polygon. - } - } - - // Operator_factory_local::SaveJSONToTextFileDbg("c:/temp/buffer_input.txt", - // *m_geometry, nullptr); - - // Complex cases: - switch (m_geometry.getType().value()) { - case Geometry.GeometryType.Point: - return bufferPoint_(); - case Geometry.GeometryType.MultiPoint: - return bufferMultiPoint_(); - case Geometry.GeometryType.Polyline: - return bufferPolyline_(); - case Geometry.GeometryType.Polygon: - return bufferPolygon_(); - case Geometry.GeometryType.Envelope: - return bufferEnvelope_(); - default: - throw GeometryException.GeometryInternalError(); - } - } - - private Geometry bufferPolyline_() { - if (isDegenerateGeometry_(m_geometry)) { - Point point = new Point(); - ((MultiVertexGeometry) m_geometry).getPointByVal(0, point); - Envelope2D env2D = new Envelope2D(); - m_geometry.queryEnvelope2D(env2D); - point.setXY(env2D.getCenter()); - return bufferPoint_(point); - } - - assert (m_distance > 0); - Polyline poly = (Polyline) m_geometry; - m_geometry = null; - - GeometryCursor glueing_cursor = new GlueingCursorForPolyline(poly);//glues paths together if they connect at one point - poly = null; - GeometryCursor generalized_paths = OperatorGeneralize.local().execute(glueing_cursor, m_densify_dist * 0.25, false, m_progress_tracker); - GeometryCursor simple_paths = OperatorSimplifyOGC.local().execute(generalized_paths, null, true, m_progress_tracker);//make a planar graph. - generalized_paths = null; - GeometryCursor path_buffering_cursor = new GeometryCursorForPolyline(this, simple_paths, m_bfilter); - simple_paths = null; - GeometryCursor union_cursor = OperatorUnion.local().execute(path_buffering_cursor, m_spatialReference, m_progress_tracker);//(int)Operator_union::Options::enum_disable_edge_dissolver - Geometry result = union_cursor.next(); - return result; - } - - private Geometry bufferPolygon_() { - if (m_distance == 0) - return m_geometry;// return input to the output. - - OperatorSimplify simplify = (OperatorSimplify) OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.Simplify); - - generateCircleTemplate_(); - m_geometry = simplify.execute(m_geometry, null, false, - m_progress_tracker); - - if (m_distance < 0) { - Polygon poly = (Polygon) (m_geometry); - Polygon buffered_result = bufferPolygonImpl_(poly, 0, - poly.getPathCount()); - return simplify.execute(buffered_result, m_spatialReference, false, - m_progress_tracker); - } else { - if (isDegenerateGeometry_(m_geometry)) { - Point point = new Point(); - ((MultiVertexGeometry) m_geometry).getPointByVal(0, point); - Envelope2D env2D = new Envelope2D(); - m_geometry.queryEnvelope2D(env2D); - point.setXY(env2D.getCenter()); - return bufferPoint_(point); - } - - // For the positive distance we need to process polygon in the parts - // such that each exterior ring with holes is processed separatelly. - GeometryCursorForPolygon cursor = new GeometryCursorForPolygon(this); - GeometryCursor union_cursor = ((OperatorUnion) OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.Union)).execute( - cursor, m_spatialReference, m_progress_tracker); - Geometry result = union_cursor.next(); - return result; - } - } - - private Polygon bufferPolygonImpl_(Polygon input_geom, int ipath_begin, - int ipath_end) { - MultiPath input_mp = (MultiPath) (input_geom); - MultiPathImpl mp_impl = (MultiPathImpl) (input_mp._getImpl()); - Polygon intermediate_polygon = new Polygon(input_geom.getDescription()); - for (int ipath = ipath_begin; ipath < ipath_end; ipath++) { - if (mp_impl.getPathSize(ipath) < 1) - continue; - - double path_area = mp_impl.calculateRingArea2D(ipath); - Envelope2D env2D = new Envelope2D(); - mp_impl.queryPathEnvelope2D(ipath, env2D); - - if (m_distance > 0) { - if (path_area > 0) { - if (isDegeneratePath_(mp_impl, ipath)) {// if a path is - // degenerate - // (almost a point), - // then we can draw - // a circle instead - // of it as a buffer - // and nobody would - // notice :) - Point point = new Point(); - mp_impl.getPointByVal(mp_impl.getPathStart(ipath), - point); - point.setXY(env2D.getCenter()); - addCircle_( - (MultiPathImpl) intermediate_polygon._getImpl(), - point); - } else { - Polyline result_polyline = new Polyline( - input_geom.getDescription()); - MultiPathImpl result_mp = (MultiPathImpl) result_polyline - ._getImpl(); - - // We often see convex hulls, buffering those is an - // extremely simple task. - boolean bConvex = ConvexHull.isPathConvex( - (Polygon) (m_geometry), ipath, - m_progress_tracker); - if (bConvex - || bufferClosedPath_(m_geometry, ipath, - result_mp, true, 1) == 2) { - Polygon buffered_path = bufferConvexPath_(input_mp, - ipath); - intermediate_polygon.add(buffered_path, false); - } else { - Polygon buffered_path = bufferCleanup_( - result_polyline, false); - intermediate_polygon.add(buffered_path, false); - } - } - } else { - if (env2D.getWidth() + m_tolerance <= 2 * m_abs_distance - || env2D.getHeight() + m_tolerance <= 2 * m_abs_distance) // skip - // parts - // that - // will - // dissapear - continue; - - Polyline result_polyline = new Polyline( - input_geom.getDescription()); - MultiPathImpl result_mp = (MultiPathImpl) result_polyline - ._getImpl(); - bufferClosedPath_(m_geometry, ipath, result_mp, true, 1); - if (!result_polyline.isEmpty()) { - Envelope2D env = new Envelope2D(); - env.setCoords(env2D); - env.inflate(m_abs_distance, m_abs_distance); - result_mp.addEnvelope(env, false); - Polygon buffered_path = bufferCleanup_(result_polyline, - false); - // intermediate_polygon.reserve(intermediate_polygon.getPointCount() - // + buffered_path.getPointCount() - 4); - for (int i = 1, n = buffered_path.getPathCount(); i < n; i++) - intermediate_polygon - .addPath(buffered_path, i, true); - } - } - } else { - if (path_area > 0) { - if (env2D.getWidth() + m_tolerance <= 2 * m_abs_distance - || env2D.getHeight() + m_tolerance <= 2 * m_abs_distance) // skip - // parts - // that - // will - // dissapear - continue; - - Polyline result_polyline = new Polyline( - input_geom.getDescription()); - MultiPathImpl result_mp = (MultiPathImpl) result_polyline - ._getImpl(); - bufferClosedPath_(m_geometry, ipath, result_mp, true, -1);// this - // will - // provide - // a - // shape - // buffered - // inwards. - // It - // has - // counterclockwise - // orientation - if (!result_polyline.isEmpty()) { - Envelope2D env = new Envelope2D(); - result_mp.queryLooseEnvelope2D(env); - env.inflate(m_abs_distance, m_abs_distance); - result_mp.addEnvelope(env, false);// add an envelope - // exterior shell - Polygon buffered_path = bufferCleanup_(result_polyline, - false);// simplify with winding rule - // extract all parts but the first one (which is the - // envelope we added previously) - for (int i = 1, npaths = buffered_path.getPathCount(); i < npaths; i++) { - // the extracted parts have inverted orientation. - intermediate_polygon - .addPath(buffered_path, i, true); - } - } else { - // the path has been erased - } - } else { - // When buffering a hole with negative distance, buffer it - // as if it is an exterior ring buffered with positive - // distance - Polyline result_polyline = new Polyline( - input_geom.getDescription()); - MultiPathImpl result_mp = (MultiPathImpl) result_polyline - ._getImpl(); - bufferClosedPath_(m_geometry, ipath, result_mp, true, -1);// this - // will - // provide - // a - // shape - // buffered - // inwards. - Polygon buffered_path = bufferCleanup_(result_polyline, - false); - for (int i = 0, npaths = buffered_path.getPathCount(); i < npaths; i++) { - intermediate_polygon.addPath(buffered_path, i, true);// adds - // buffered - // hole - // reversed - // as - // if - // it - // is - // exteror - // ring - } - } - - // intermediate_polygon has inverted orientation. - } - } - - if (m_distance > 0) { - if (intermediate_polygon.getPathCount() > 1) { - Polygon cleaned_polygon = bufferCleanup_(intermediate_polygon, - false); - return cleaned_polygon; - } else { - return setWeakSimple_(intermediate_polygon); - } - } else { - Envelope2D polyenv = new Envelope2D(); - intermediate_polygon.queryLooseEnvelope2D(polyenv); - if (!intermediate_polygon.isEmpty()) { - // negative buffer distance. We got buffered holes and exterior - // rings. They all have wrong orientation. - // we need to apply winding simplify again to ensure all holes - // are unioned. - // For that create a big envelope and add all rings of the - // intermediate_polygon to it. - polyenv.inflate(m_abs_distance, m_abs_distance); - intermediate_polygon.addEnvelope(polyenv, false); - Polygon cleaned_polygon = bufferCleanup_(intermediate_polygon, - false); - // intermediate_polygon.reset();//free memory - - Polygon result_polygon = new Polygon( - cleaned_polygon.getDescription()); - for (int i = 1, n = cleaned_polygon.getPathCount(); i < n; i++) { - result_polygon.addPath(cleaned_polygon, i, false); - } - return setWeakSimple_(result_polygon); - } else { - return setWeakSimple_(intermediate_polygon); - } - } - } - - private Geometry bufferPoint_() { - return bufferPoint_((Point) (m_geometry)); - } - - private Geometry bufferPoint_(Point point) { - assert (m_distance > 0); - Polygon resultPolygon = new Polygon(point.getDescription()); - addCircle_((MultiPathImpl) resultPolygon._getImpl(), point); - return setStrongSimple_(resultPolygon); - } - - private Geometry bufferMultiPoint_() { - assert (m_distance > 0); - GeometryCursorForMultiPoint mpCursor = new GeometryCursorForMultiPoint(this, - (MultiPoint) (m_geometry), m_distance, m_spatialReference, - m_densify_dist, m_max_vertex_in_complete_circle, - m_progress_tracker); - GeometryCursor c = ((OperatorUnion) OperatorFactoryLocal.getInstance() - .getOperator(Operator.Type.Union)).execute(mpCursor, - m_spatialReference, m_progress_tracker); - return c.next(); - } - - private Geometry bufferEnvelope_() { - Polygon polygon = new Polygon(m_geometry.getDescription()); - if (m_distance <= 0) { - if (m_distance == 0) - polygon.addEnvelope((Envelope) (m_geometry), false); - else { - Envelope env = new Envelope(); - m_geometry.queryEnvelope(env); - env.inflate(m_distance, m_distance); - polygon.addEnvelope(env, false); - } - - return polygon;// nothing is easier than negative buffer on the - // envelope. - } - - polygon.addEnvelope((Envelope) (m_geometry), false); - m_geometry = polygon; - return bufferConvexPath_(polygon, 0); - } - - private Polygon bufferConvexPath_(MultiPath src, int ipath) { - generateCircleTemplate_(); - - Polygon resultPolygon = new Polygon(src.getDescription()); - MultiPathImpl result_mp = (MultiPathImpl) resultPolygon._getImpl(); - - // resultPolygon.reserve((m_circle_template.size() / 10 + 4) * - // src.getPathSize(ipath)); - - Point2D pt_1_tmp = new Point2D(), pt_1 = new Point2D(); - Point2D pt_2_tmp = new Point2D(), pt_2 = new Point2D(); - Point2D pt_3_tmp = new Point2D(), pt_3 = new Point2D(); - Point2D v_1 = new Point2D(); - Point2D v_2 = new Point2D(); - MultiPathImpl src_mp = (MultiPathImpl) src._getImpl(); - int path_size = src.getPathSize(ipath); - int path_start = src.getPathStart(ipath); - for (int i = 0, n = src.getPathSize(ipath); i < n; i++) { - src_mp.getXY(path_start + i, pt_1); - src_mp.getXY(path_start + (i + 1) % path_size, pt_2); - src_mp.getXY(path_start + (i + 2) % path_size, pt_3); - v_1.sub(pt_2, pt_1); - if (v_1.length() == 0) - throw GeometryException.GeometryInternalError(); - - v_1.leftPerpendicular(); - v_1.normalize(); - v_1.scale(m_abs_distance); - pt_1_tmp.add(v_1, pt_1); - pt_2_tmp.add(v_1, pt_2); - if (i == 0) - result_mp.startPath(pt_1_tmp); - else { - result_mp.lineTo(pt_1_tmp); - } - - result_mp.lineTo(pt_2_tmp); - - v_2.sub(pt_3, pt_2); - if (v_2.length() == 0) - throw GeometryException.GeometryInternalError(); - - v_2.leftPerpendicular(); - v_2.normalize(); - v_2.scale(m_abs_distance); - pt_3_tmp.add(v_2, pt_2); - - addJoin_(result_mp, pt_2, pt_2_tmp, pt_3_tmp, false, false); - } - - return setWeakSimple_(resultPolygon); - } - - private Polygon bufferPolylinePath_(Polyline polyline, int ipath, - boolean bfilter) { - assert (m_distance != 0); - generateCircleTemplate_(); - - MultiPath input_multi_path = polyline; - MultiPathImpl mp_impl = (MultiPathImpl) (input_multi_path._getImpl()); - - if (mp_impl.getPathSize(ipath) < 1) - return null; - - if (isDegeneratePath_(mp_impl, ipath) && m_distance > 0) {// if a path - // is - // degenerate - // (almost a - // point), - // then we - // can draw - // a circle - // instead - // of it as - // a buffer - // and - // nobody - // would - // notice :) - Point point = new Point(); - mp_impl.getPointByVal(mp_impl.getPathStart(ipath), point); - Envelope2D env2D = new Envelope2D(); - mp_impl.queryPathEnvelope2D(ipath, env2D); - point.setXY(env2D.getCenter()); - return (Polygon) (bufferPoint_(point)); - } - - Polyline result_polyline = new Polyline(polyline.getDescription()); - - MultiPathImpl result_mp = (MultiPathImpl) result_polyline._getImpl(); - boolean b_closed = mp_impl.isClosedPathInXYPlane(ipath); - - if (b_closed) { - bufferClosedPath_(input_multi_path, ipath, result_mp, bfilter, 1); - bufferClosedPath_(input_multi_path, ipath, result_mp, bfilter, -1); - } else { - Polyline tmpPoly = new Polyline(input_multi_path.getDescription()); - tmpPoly.addPath(input_multi_path, ipath, false); - ((MultiPathImpl) tmpPoly._getImpl()).addSegmentsFromPath( - (MultiPathImpl) input_multi_path._getImpl(), ipath, 0, - input_multi_path.getSegmentCount(ipath), false); - bufferClosedPath_(tmpPoly, 0, result_mp, bfilter, 1); - } - - return bufferCleanup_(result_polyline, false); - } - - private void progress_() { - m_progress_counter++; - if (m_progress_counter % 1024 == 0) { - if ((m_progress_tracker != null) - && !(m_progress_tracker.progress(-1, -1))) - throw new RuntimeException("user_canceled"); - } - } - - private Polygon bufferCleanup_(MultiPath multi_path, boolean simplify_result) { - double tol = simplify_result ? m_tolerance : m_small_tolerance; - Polygon resultPolygon = (Polygon) (TopologicalOperations - .planarSimplify(multi_path, tol, true, !simplify_result, - m_progress_tracker)); - assert (InternalUtils.isWeakSimple(resultPolygon, 0.0)); - return resultPolygon; - } - - private int calcN_() { - //this method should be called only once m_circle_template_size is set then; - final int minN = 4; - if (m_densify_dist == 0) - return m_max_vertex_in_complete_circle; - - double r = m_densify_dist * Math.abs(m_abs_distance_reversed); - double cos_a = 1 - r; - double N; - if (cos_a < -1) - N = minN; - else - N = 2.0 * Math.PI / Math.acos(cos_a) + 0.5; - - if (N < minN) - N = minN; - else if (N > m_max_vertex_in_complete_circle) - N = m_max_vertex_in_complete_circle; - - return (int) N; - } - - private void addJoin_(MultiPathImpl dst, Point2D center, Point2D fromPt, - Point2D toPt, boolean bStartPath, boolean bFinishAtToPt) { - generateCircleTemplate_(); - - Point2D v_1 = new Point2D(); - v_1.sub(fromPt, center); - v_1.scale(m_abs_distance_reversed); - Point2D v_2 = new Point2D(); - v_2.sub(toPt, center); - v_2.scale(m_abs_distance_reversed); - double angle_from = Math.atan2(v_1.y, v_1.x); - double dindex_from = angle_from / m_dA; - if (dindex_from < 0) - dindex_from = (double) m_circle_template.size() + dindex_from; - - dindex_from = (double) m_circle_template.size() - dindex_from; - - double angle_to = Math.atan2(v_2.y, v_2.x); - double dindex_to = angle_to / m_dA; - if (dindex_to < 0) - dindex_to = (double) m_circle_template.size() + dindex_to; - - dindex_to = (double) m_circle_template.size() - dindex_to; - - if (dindex_to < dindex_from) - dindex_to += (double) m_circle_template.size(); - assert (dindex_to >= dindex_from); - - int index_to = (int) dindex_to; - int index_from = (int) Math.ceil(dindex_from); - - if (bStartPath) { - dst.startPath(fromPt); - bStartPath = false; - } - - Point2D p = new Point2D(); - p.setCoords(m_circle_template.get(index_from % m_circle_template.size())); - p.scaleAdd(m_abs_distance, center); - double ddd = m_tolerance * 10; - p.sub(fromPt); - if (p.length() < ddd)// if too close to the fromPt, then use the next - // point - index_from += 1; - - p.setCoords(m_circle_template.get(index_to % m_circle_template.size())); - p.scaleAdd(m_abs_distance, center); - p.sub(toPt); - if (p.length() < ddd)// if too close to the toPt, then use the prev - // point - index_to -= 1; - - int count = index_to - index_from; - count++; - - for (int i = 0, j = index_from % m_circle_template.size(); i < count; i++, j = (j + 1) - % m_circle_template.size()) { - p.setCoords(m_circle_template.get(j)); - p.scaleAdd(m_abs_distance, center); - dst.lineTo(p); - progress_(); - } - - if (bFinishAtToPt) { - dst.lineTo(toPt); - } - } - - private int bufferClosedPath_(Geometry input_geom, int ipath, - MultiPathImpl result_mp, boolean bfilter, int dir) { - // Use temporary polyline for the path buffering. - EditShape edit_shape = new EditShape(); - int geom = edit_shape.addPathFromMultiPath((MultiPath) input_geom, - ipath, true); - edit_shape.filterClosePoints(m_filter_tolerance, false, false); - if (edit_shape.getPointCount(geom) < 2) {// Got degenerate output. - // Either bail out or - // produce a circle. - if (dir < 0) - return 1;// negative direction produces nothing. - - MultiPath mpIn = (MultiPath) input_geom; - // Add a circle - Point pt = new Point(); - mpIn.getPointByVal(mpIn.getPathStart(ipath), pt); - addCircle_(result_mp, pt); - return 1; - } - - assert (edit_shape.getFirstPath(geom) != -1); - assert (edit_shape.getFirstVertex(edit_shape.getFirstPath(geom)) != -1); - - Point2D origin = edit_shape.getXY(edit_shape.getFirstVertex(edit_shape - .getFirstPath(geom))); - Transformation2D tr = new Transformation2D(); - tr.setShift(-origin.x, -origin.y); - // move the path to origin for better accuracy in calculations. - edit_shape.applyTransformation(tr); - - if (bfilter) { - // try removing the noise that does not contribute to the buffer. - int res_filter = filterPath_(edit_shape, geom, dir, true, m_abs_distance, m_filter_tolerance, m_densify_dist); - assert (res_filter == 1); - // Operator_factory_local::SaveJSONToTextFileDbg("c:/temp/buffer_filter.txt", - // *edit_shape.get_geometry(geom), nullptr); - if (edit_shape.getPointCount(geom) < 2) {// got degenerate output. - // Wither bail out or - // produce a circle. - if (dir < 0) - return 1;// negative direction produces nothing. - - MultiPath mpIn = (MultiPath) input_geom; - // Add a circle - Point pt = new Point(); - mpIn.getPointByVal(mpIn.getPathStart(ipath), pt); - addCircle_(result_mp, pt); - return 1; - } - } - - m_buffer_commands.clear(); - int path = edit_shape.getFirstPath(geom); - int ivert = edit_shape.getFirstVertex(path); - int iprev = dir == 1 ? edit_shape.getPrevVertex(ivert) : edit_shape - .getNextVertex(ivert); - int inext = dir == 1 ? edit_shape.getNextVertex(ivert) : edit_shape - .getPrevVertex(ivert); - boolean b_first = true; - Point2D pt_current = new Point2D(), pt_after = new Point2D(), pt_before = new Point2D(), pt_left_prev = new Point2D(), pt = new Point2D(), pt1 = new Point2D(); - Point2D v_after = new Point2D(), v_before = new Point2D(), v_left = new Point2D(), v_left_prev = new Point2D(); - double abs_d = m_abs_distance; - int ncount = edit_shape.getPathSize(path); - - // write out buffer commands as a set of arcs and line segments. - // if we'd convert this directly to a polygon and draw using winding - // fill rule, we'd get the buffered result. - for (int index = 0; index < ncount; index++) { - edit_shape.getXY(inext, pt_after); - - if (b_first) { - edit_shape.getXY(ivert, pt_current); - edit_shape.getXY(iprev, pt_before); - v_before.sub(pt_current, pt_before); - v_before.normalize(); - v_left_prev.leftPerpendicular(v_before); - v_left_prev.scale(abs_d); - pt_left_prev.add(v_left_prev, pt_current); - } - - v_after.sub(pt_after, pt_current); - v_after.normalize(); - - v_left.leftPerpendicular(v_after); - v_left.scale(abs_d); - pt.add(pt_current, v_left); - double cross = v_before.crossProduct(v_after); - double dot = v_before.dotProduct(v_after); - boolean bDoJoin = cross < 0 || (dot < 0 && cross == 0); - if (bDoJoin) { - m_buffer_commands.add(new BufferCommand(pt_left_prev, pt, - pt_current, BufferCommand.Flags.enum_arc, - m_buffer_commands.size() + 1, - m_buffer_commands.size() - 1)); - } else if (!pt_left_prev.isEqual(pt)) { - m_buffer_commands.add(new BufferCommand(pt_left_prev, - pt_current, m_buffer_commands.size() + 1, - m_buffer_commands.size() - 1, "dummy")); - m_buffer_commands.add(new BufferCommand(pt_current, pt, - m_buffer_commands.size() + 1, - m_buffer_commands.size() - 1, "dummy")); - } - - pt1.add(pt_after, v_left); - m_buffer_commands - .add(new BufferCommand(pt, pt1, pt_current, - BufferCommand.Flags.enum_line, m_buffer_commands - .size() + 1, m_buffer_commands.size() - 1)); - - pt_left_prev.setCoords(pt1); - v_left_prev.setCoords(v_left); - pt_before.setCoords(pt_current); - pt_current.setCoords(pt_after); - v_before.setCoords(v_after); - iprev = ivert; - ivert = inext; - b_first = false; - inext = dir == 1 ? edit_shape.getNextVertex(ivert) : edit_shape - .getPrevVertex(ivert); - } - - m_buffer_commands.get(m_buffer_commands.size() - 1).m_next = 0; - m_buffer_commands.get(0).m_prev = m_buffer_commands.size() - 1; - processBufferCommands_(result_mp); - tr.setShift(origin.x, origin.y);// move the path to improve precision. - result_mp.applyTransformation(tr, result_mp.getPathCount() - 1); - return 1; - } - - private void processBufferCommands_(MultiPathImpl result_mp) { - int ifirst_seg = cleanupBufferCommands_(); - boolean first = true; - int iseg_next = ifirst_seg + 1; - for (int iseg = ifirst_seg; iseg_next != ifirst_seg; iseg = iseg_next) { - BufferCommand command = m_buffer_commands.get(iseg); - iseg_next = command.m_next != -1 ? command.m_next : (iseg + 1) - % m_buffer_commands.size(); - if (command.m_type == 0) - continue;// deleted segment - - if (first) { - result_mp.startPath(command.m_from); - first = false; - } - - if (command.m_type == BufferCommand.Flags.enum_arc) {// arc - addJoin_(result_mp, command.m_center, command.m_from, - command.m_to, false, true); - } else { - result_mp.lineTo(command.m_to); - } - first = false; - } - } - - private int cleanupBufferCommands_() { - // The purpose of this function is to remove as many self intersections - // from the buffered shape as possible. - // The buffer works without cleanup also, but slower. - - if (m_helper_array == null) - m_helper_array = new Point2D[9]; - - int istart = 0; - for (int iseg = 0, nseg = m_buffer_commands.size(); iseg < nseg; ) { - BufferCommand command = m_buffer_commands.get(iseg); - if ((command.m_type & BufferCommand.Flags.enum_connection) != 0) { - istart = iseg; - break; - } - - iseg = command.m_next; - } - - int iseg_next = istart + 1; - for (int iseg = istart; iseg_next != istart; iseg = iseg_next) { - BufferCommand command = m_buffer_commands.get(iseg); - iseg_next = command.m_next; - int count = 1; - BufferCommand command_next = null; - while (iseg_next != iseg) {// find next segement - command_next = m_buffer_commands.get(iseg_next); - if ((command_next.m_type & BufferCommand.Flags.enum_connection) != 0) - break; - - iseg_next = command_next.m_next; - count++; - } - - if (count == 1) { - // Next segment starts where this one ends. Skip this case as it - // is simple. - assert (command.m_to.isEqual(command_next.m_from)); - continue; - } - - if ((command.m_type & command_next.m_type) == BufferCommand.Flags.enum_line) {// simplest - // cleanup - // - - // intersect - // lines - if (m_helper_line_1 == null) { - m_helper_line_1 = new Line(); - m_helper_line_2 = new Line(); - } - m_helper_line_1.setStartXY(command.m_from); - m_helper_line_1.setEndXY(command.m_to); - m_helper_line_2.setStartXY(command_next.m_from); - m_helper_line_2.setEndXY(command_next.m_to); - - int count_ = m_helper_line_1.intersect(m_helper_line_2, - m_helper_array, null, null, m_small_tolerance); - if (count_ == 1) { - command.m_to.setCoords(m_helper_array[0]); - command_next.m_from.setCoords(m_helper_array[0]); - command.m_next = iseg_next;// skip until iseg_next - command_next.m_prev = iseg; - } else if (count_ == 2) {// TODO: this case needs improvement - } - } - } - - return istart; - } - - private static void protectExtremeVertices_(EditShape edit_shape, - int protection_index, int geom, int path) { - // detect very narrow corners and preserve them. We cannot reliably - // delete these. - int vprev = -1; - Point2D pt_prev = new Point2D(); - pt_prev.setNaN(); - Point2D pt = new Point2D(); - pt.setNaN(); - Point2D v_before = new Point2D(); - v_before.setNaN(); - Point2D pt_next = new Point2D(); - Point2D v_after = new Point2D(); - for (int i = 0, n = edit_shape.getPathSize(path), v = edit_shape - .getFirstVertex(path); i < n; ++i) { - if (vprev == -1) { - edit_shape.getXY(v, pt); - - vprev = edit_shape.getPrevVertex(v); - if (vprev != -1) { - edit_shape.getXY(vprev, pt_prev); - v_before.sub(pt, pt_prev); - v_before.normalize(); - } - } - - int vnext = edit_shape.getNextVertex(v); - if (vnext == -1) - break; - - edit_shape.getXY(vnext, pt_next); - v_after.sub(pt_next, pt); - v_after.normalize(); - - if (vprev != -1) { - double d = v_after.dotProduct(v_before); - if (d < -0.99 - && Math.abs(v_after.crossProduct(v_before)) < 1e-7) { - edit_shape.setUserIndex(v, protection_index, 1); - } - } - - vprev = v; - v = vnext; - pt_prev.setCoords(pt); - pt.setCoords(pt_next); - v_before.setCoords(v_after); - } - } - - static private int filterPath_(EditShape edit_shape, int geom, int dir, - boolean closed, double abs_distance, double filter_tolerance, - double densify_distance) { - int path = edit_shape.getFirstPath(geom); - - int concave_index = -1; - int fixed_vertices_index = edit_shape.createUserIndex(); - protectExtremeVertices_(edit_shape, fixed_vertices_index, geom, path); - - for (int iter = 0; iter < 100; ++iter) { - int isize = edit_shape.getPathSize(path); - if (isize == 0) { - edit_shape.removeUserIndex(fixed_vertices_index); - ; - return 1; - } - - int ivert = edit_shape.getFirstVertex(path); - int nvertices = edit_shape.getPathSize(path); - if (nvertices < 3) { - edit_shape.removeUserIndex(fixed_vertices_index); - ; - return 1; - } - - if (closed && !edit_shape.isClosedPath(path))// the path is closed - // only virtually - { - nvertices -= 1; - } - - double abs_d = abs_distance; - final int nfilter = 64; - boolean filtered = false; - int filtered_in_pass = 0; - boolean go_back = false; - for (int i = 0; i < nvertices && ivert != -1; i++) { - int filtered_now = 0; - int v = ivert; // filter == 0 - for (int filter = 1, n = (int) Math.min(nfilter, nvertices - i); filter < n; filter++) { - v = edit_shape.getNextVertex(v, dir); - if (filter > 1) { - int num = clipFilter_(edit_shape, - fixed_vertices_index, ivert, v, dir, - abs_distance, densify_distance, nfilter); - if (num == -1) - break; - - filtered |= num > 0; - filtered_now += num; - nvertices -= num; - } - } - - filtered_in_pass += filtered_now; - - go_back = filtered_now > 0; - - if (go_back) { - int prev = edit_shape.getPrevVertex(ivert, dir); - if (prev != -1) { - ivert = prev; - nvertices++; - continue; - } - } - - ivert = edit_shape.getNextVertex(ivert, dir); - } - - if (filtered_in_pass == 0) - break; - } - - edit_shape.removeUserIndex(fixed_vertices_index); - edit_shape.filterClosePoints(filter_tolerance, false, false); - - return 1; - } - - // This function clips out segments connecting from_vertiex to to_vertiex if - // they do not contribute to the buffer. - private static int clipFilter_(EditShape edit_shape, - int fixed_vertices_index, int from_vertex, int to_vertex, int dir, - double abs_distance, double densify_distance, final int max_filter) { - // Note: vertices marked with fixed_vertices_index cannot be deleted. - - Point2D pt1 = edit_shape.getXY(from_vertex); - Point2D pt2 = edit_shape.getXY(to_vertex); - if (pt1.equals(pt2)) - return -1; - - double densify_distance_delta = densify_distance * 0.25;// distance by - // which we can - // move the - // point closer - // to the chord - // (introducing - // an error into - // the buffer). - double erase_distance_delta = densify_distance * 0.25;// distance when - // we can erase - // the point - // (introducing - // an error into - // the buffer). - // This function goal is to modify or remove vertices between - // from_vertex and to_vertex in such a way that the result would not - // affect buffer to the left of the - // chain. - Point2D v_gap = new Point2D(); - v_gap.sub(pt2, pt1); - double gap_length = v_gap.length(); - double h2_4 = gap_length * gap_length * 0.25; - double sqr_center_to_chord = abs_distance * abs_distance - h2_4; // squared - // distance - // from - // the - // chord - // to - // the - // circle - // center - if (sqr_center_to_chord <= h2_4) - return -1;// center to chord distance is less than half gap, that - // means the gap is too wide for useful filtering (maybe - // this). - - double center_to_chord = Math.sqrt(sqr_center_to_chord); // distance - // from - // circle - // center to - // the - // chord. - - v_gap.normalize(); - Point2D v_gap_norm = new Point2D(v_gap); - v_gap_norm.rightPerpendicular(); - double chord_to_corner = h2_4 / center_to_chord; // cos(a) = - // center_to_chord / - // distance; - // chord_to_corner = - // distance / cos(a) - // - - // center_to_chord; - boolean can_erase_corner_point = chord_to_corner <= erase_distance_delta; - Point2D chord_midpoint = new Point2D(); - MathUtils.lerp(pt2, pt1, 0.5, chord_midpoint); - Point2D corner = new Point2D(v_gap_norm); - double corrected_chord_to_corner = chord_to_corner - - densify_distance_delta;// using slightly smaller than needed - // distance let us filter more. - corner.scaleAdd(Math.max(0.0, corrected_chord_to_corner), - chord_midpoint); - // corner = (p1 + p2) * 0.5 + v_gap_norm * chord_to_corner; - - Point2D center = new Point2D(v_gap_norm); - center.negate(); - center.scaleAdd(center_to_chord, chord_midpoint); - - double allowed_distance = abs_distance - erase_distance_delta; - double sqr_allowed_distance = MathUtils.sqr(allowed_distance); - double sqr_large_distance = sqr_allowed_distance * (1.9 * 1.9); - - Point2D co_p1 = new Point2D(); - co_p1.sub(corner, pt1); - Point2D co_p2 = new Point2D(); - co_p2.sub(corner, pt2); - - boolean large_distance = false;// set to true when distance - int cnt = 0; - char[] locations = new char[64]; - { - // check all vertices in the gap verifying that the gap can be - // clipped. - // - - Point2D pt = new Point2D(); - // firstly remove any duplicate vertices in the end. - for (int v = edit_shape.getPrevVertex(to_vertex, dir); v != from_vertex; ) { - if (edit_shape.getUserIndex(v, fixed_vertices_index) == 1) - return -1;// this range contains protected vertex - - edit_shape.getXY(v, pt); - if (pt.equals(pt2)) { - int v1 = edit_shape.getPrevVertex(v, dir); - edit_shape.removeVertex(v, false); - v = v1; - continue; - } else { - break; - } - } - - Point2D prev_prev_pt = new Point2D(); - prev_prev_pt.setNaN(); - Point2D prev_pt = new Point2D(); - prev_pt.setCoords(pt1); - locations[cnt++] = 1; - int prev_v = from_vertex; - Point2D dummyPt = new Point2D(); - for (int v = edit_shape.getNextVertex(from_vertex, dir); v != to_vertex; ) { - if (edit_shape.getUserIndex(v, fixed_vertices_index) == 1) - return -1;// this range contains protected vertex - - edit_shape.getXY(v, pt); - if (pt.equals(prev_pt)) { - int v1 = edit_shape.getNextVertex(v, dir); - edit_shape.removeVertex(v, false); - v = v1; - continue; - } - - locations[cnt++] = 0; - - Point2D v1 = new Point2D(); - v1.sub(pt, pt1); - if (v1.dotProduct(v_gap_norm) < 0)// we are crossing on the - // wrong site of the chord. - // Just bail out earlier. - // Maybe we could continue - // clipping though here, but - // it seems to be - // unnecessary complicated. - return 0; - - if (Point2D.sqrDistance(pt, pt1) > sqr_large_distance - || Point2D.sqrDistance(pt, pt2) > sqr_large_distance) - large_distance = true; // too far from points, may - // contribute to the outline (in - // case of a large loop) - - char next_location = 0; - - dummyPt.sub(pt, pt1); - double cs1 = dummyPt.crossProduct(co_p1); - if (cs1 >= 0) { - next_location = 1; - } - - dummyPt.sub(pt, pt2); - double cs2 = dummyPt.crossProduct(co_p2); - if (cs2 <= 0) { - next_location |= 2; - } - - if (next_location == 0) - return 0; - - locations[cnt - 1] = next_location; - prev_prev_pt.setCoords(prev_pt); - prev_pt.setCoords(pt); - prev_v = v; - v = edit_shape.getNextVertex(v, dir); - } - - if (cnt == 1) - return 0; - - assert (!pt2.equals(prev_pt)); - locations[cnt++] = 2; - } - - boolean can_clip_all = true; - // we can remove all points and replace them with a single corner point - // if we are moving from location 1 via location 3 to location 2 - for (int i = 1, k = 0; i < cnt; i++) { - if (locations[i] != locations[i - 1]) { - k++; - can_clip_all = k < 3 - && ((k == 1 && locations[i] == 3) || (k == 2 && locations[i] == 2)); - if (!can_clip_all) - return 0; - } - } - - if (cnt > 2 && can_clip_all && (cnt == 3 || !large_distance)) { - int clip_count = 0; - int v = edit_shape.getNextVertex(from_vertex, dir); - if (!can_erase_corner_point) { - edit_shape.setXY(v, corner); - v = edit_shape.getNextVertex(v, dir); - } - - // we can remove all vertices between from and to, because they - // don't contribute - while (v != to_vertex) { - int v1 = edit_shape.getNextVertex(v, dir); - edit_shape.removeVertex(v, false); - v = v1; - ++clip_count; - } - - return clip_count; - } - - if (cnt == 3) { - boolean case1 = (locations[0] == 1 && locations[1] == 2 && locations[2] == 2); - boolean case2 = (locations[0] == 1 && locations[1] == 1 && locations[2] == 2); - if (case1 || case2) { - // special case, when we cannot clip, but we can move the point - Point2D p1 = edit_shape.getXY(from_vertex); - int v = edit_shape.getNextVertex(from_vertex, dir); - Point2D p2 = edit_shape.getXY(v); - Point2D p3 = edit_shape.getXY(edit_shape.getNextVertex(v, dir)); - if (case2) { - Point2D temp = p1; - p1 = p3; - p3 = temp; - } - - Point2D vec = new Point2D(); - vec.sub(p1, p2); - p3.sub(p2); - double veclen = vec.length(); - double w = p3.length(); - double wcosa = vec.dotProduct(p3) / veclen; - double wsina = Math.abs(p3.crossProduct(vec) / veclen); - double z = 2 * abs_distance - wsina; - if (z < 0) - return 0; - - double x = wcosa + Math.sqrt(wsina * z); - if (x > veclen) - return 0; - - Point2D hvec = new Point2D(); - hvec.scaleAdd(-x / veclen, vec, p3); // hvec = p3 - vec * (x / - // veclen); - double h = hvec.length(); - double y = -(h * h * veclen) / (2 * hvec.dotProduct(vec)); - - double t = (x - y) / veclen; - MathUtils.lerp(p2, p1, t, p2); - edit_shape.setXY(v, p2); - return 0; - } - } - - if (large_distance && cnt > 3) { - // we are processing more than 3 points and there are some points - // further than the - return 0; - } - - int v_prev = -1; - Point2D pt_prev = new Point2D(); - int v_cur = from_vertex; - Point2D pt_cur = new Point2D(pt1); - int cur_location = 1; - int prev_location = -1; // 1 - semiplane to the right of [f,c]. 3 - - // semiplane to the right of [c,t], 2 - both - // above fc and ct, 0 - cannot clip, -1 - - // unknown - int v_next = v_cur; - int clip_count = 0; - cnt = 1; - while (v_next != to_vertex) { - v_next = edit_shape.getNextVertex(v_next, dir); - int next_location = locations[cnt++]; - if (next_location == 0) { - if (v_next == to_vertex) - break; - - continue; - } - - Point2D pt_next = edit_shape.getXY(v_next); - - if (prev_location != -1) { - int common_location = (prev_location & cur_location & next_location); - if ((common_location & 3) != 0) { - // prev and next are on the same semiplane as the current we - // can safely remove the current point. - edit_shape.removeVertex(v_cur, true); - clip_count++;// do not change prev point. - v_cur = v_next; - pt_cur.setCoords(pt_next); - cur_location = next_location; - continue; - } - - if (cur_location == 3 && prev_location != 0 - && next_location != 0) { - assert ((prev_location & next_location) == 0);// going from - // one semi - // plane to - // another - // via the - // mid. - pt_cur.setCoords(corner); - if (can_erase_corner_point || pt_cur.equals(pt_prev)) {// this - // point - // can - // be - // removed - edit_shape.removeVertex(v_cur, true); - clip_count++;// do not change prev point. - v_cur = v_next; - pt_cur.setCoords(pt_next); - cur_location = next_location; - continue; - } else { - edit_shape.setXY(v_cur, pt_cur); // snap to the corner - } - } else { - if (next_location == 0 - && cur_location != 0 - || next_location != 0 - && cur_location == 0 - || ((next_location | cur_location) == 3 - && next_location != 3 && cur_location != 3)) { - // clip - } - } - } - - prev_location = cur_location; - v_prev = v_cur; - pt_prev.setCoords(pt_cur); - v_cur = v_next; - cur_location = next_location; - pt_cur.setCoords(pt_next); - } - - return clip_count; - } - - private boolean isDegeneratePath_(MultiPathImpl mp_impl, int ipath) { - if (mp_impl.getPathSize(ipath) == 1) - return true; - Envelope2D env = new Envelope2D(); - mp_impl.queryPathEnvelope2D(ipath, env); - if (Math.max(env.getWidth(), env.getHeight()) < m_densify_dist * 0.5) - return true; - - return false; - } - - private boolean isDegenerateGeometry_(Geometry geom) { - Envelope2D env = new Envelope2D(); - geom.queryEnvelope2D(env); - if (Math.max(env.getWidth(), env.getHeight()) < m_densify_dist * 0.5) - return true; - - return false; - } - - private Polyline preparePolyline_(Polyline input_geom) { - // Generalize it firstly using 25% of the densification deviation as a - // criterion. - Polyline generalized_polyline = (Polyline) ((OperatorGeneralize) OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.Generalize)).execute( - input_geom, m_densify_dist * 0.25, false, m_progress_tracker); - - int path_point_count = 0; - for (int i = 0, npath = generalized_polyline.getPathCount(); i < npath; i++) { - path_point_count = Math.max(generalized_polyline.getPathSize(i), - path_point_count); - } - - if (path_point_count < 32) { - m_bfilter = false; - return generalized_polyline; - } else { - m_bfilter = true; - // If we apply a filter to the polyline, then we have to resolve all - // self intersections. - Polyline simple_polyline = (Polyline) (TopologicalOperations - .planarSimplify(generalized_polyline, m_small_tolerance, - false, true, m_progress_tracker)); - // Operator_factory_local::SaveJSONToTextFileDbg("c:/temp/buffer_simplify.txt", - // *simple_polyline, nullptr); - return simple_polyline; - } - } - - private void addCircle_(MultiPathImpl result_mp, Point point) { - // Uses same calculations for each of the quadrants, generating a - // symmetric distribution of points. - Point2D center = point.getXY(); - if (m_circle_template != null && !m_circle_template.isEmpty()) {// use - // template - // if - // available. - Point2D p = new Point2D(); - p.setCoords(m_circle_template.get(0)); - p.scaleAdd(m_abs_distance, center); - result_mp.startPath(p); - for (int i = 1, n = (int) m_circle_template.size(); i < n; i++) { - p.setCoords(m_circle_template.get(i)); - p.scaleAdd(m_abs_distance, center); - result_mp.lineTo(p); - } - return; - } - - // avoid unnecessary memory allocation for the circle template. Just do - // the point here. - - int N = m_circle_template_size; - int real_size = (N + 3) / 4; - double dA = (Math.PI * 0.5) / real_size; - // result_mp.reserve(real_size * 4); - - double dcos = Math.cos(dA); - double dsin = Math.sin(dA); - Point2D pt = new Point2D(); - for (int quadrant = 3; quadrant >= 0; quadrant--) { - pt.setCoords(0.0, m_abs_distance); - switch (quadrant) { - case 0: {// upper left quadrant - for (int i = 0; i < real_size; i++) { - result_mp.lineTo(pt.x + center.x, pt.y + center.y); - pt.rotateReverse(dcos, dsin); - } - break; - } - case 1: {// upper left quadrant - for (int i = 0; i < real_size; i++) {// m_circle_template.set(i - // + real_size * 1, - // Point_2D::construct(-pt.y, - // pt.x)); - result_mp.lineTo(-pt.y + center.x, pt.x + center.y); - pt.rotateReverse(dcos, dsin); - } - break; - } - case 2: {// lower left quadrant - // m_circle_template.set(i + real_size * 2, - // Point_2D::construct(-pt.x, -pt.y)); - for (int i = 0; i < real_size; i++) { - result_mp.lineTo(-pt.x + center.x, -pt.y + center.y); - pt.rotateReverse(dcos, dsin); - } - break; - } - default:// case 3: - {// lower right quadrant - // m_circle_template.set(i + real_size * 3, - // Point_2D::construct(pt.y, -pt.x)); - result_mp.startPath(pt.y + center.x, -pt.x + center.y);// we - // start - // at - // the - // quadrant - // 3. - // The - // first - // point - // is - // (0, - // -m_distance) - // + - // center - for (int i = 1; i < real_size; i++) { - pt.rotateReverse(dcos, dsin); - result_mp.lineTo(pt.y + center.x, -pt.x + center.y); - } - break; - } - } - - progress_(); - } - } - - private static Polygon setWeakSimple_(Polygon poly) { - ((MultiPathImpl) poly._getImpl()).setIsSimple( - MultiVertexGeometryImpl.GeometryXSimple.Weak, 0.0, false); - return poly; - } - - private Polygon setStrongSimple_(Polygon poly) { - ((MultiPathImpl) poly._getImpl()).setIsSimple( - MultiVertexGeometryImpl.GeometryXSimple.Strong, m_tolerance, - false); - ((MultiPathImpl) poly._getImpl())._updateOGCFlags(); - return poly; - } + Bufferer() { + m_buffer_commands = new ArrayList(128); + m_progress_tracker = null; + m_tolerance = 0; + m_small_tolerance = 0; + m_filter_tolerance = 0; + m_distance = 0; + m_original_geom_type = Geometry.GeometryType.Unknown; + m_abs_distance_reversed = 0; + m_abs_distance = 0; + m_densify_dist = -1; + m_dA = -1; + m_b_output_loops = true; + m_bfilter = true; + m_old_circle_template_size = 0; + } + + + /** + * Result is always a polygon. For non positive distance and non-areas + * returns an empty polygon. For points returns circles. + */ + Geometry buffer(Geometry geometry, double distance, + SpatialReference sr, double densify_dist, + int max_vertex_in_complete_circle, ProgressTracker progress_tracker) { + if (geometry == null) + throw new IllegalArgumentException(); + + if (densify_dist < 0) + throw new IllegalArgumentException(); + + if (geometry.isEmpty()) + return new Polygon(geometry.getDescription()); + + Envelope2D env2D = new Envelope2D(); + geometry.queryLooseEnvelope2D(env2D); + if (distance > 0) + env2D.inflate(distance, distance); + + m_progress_tracker = progress_tracker; + + m_original_geom_type = geometry.getType().value(); + m_geometry = geometry; + m_tolerance = InternalUtils.calculateToleranceFromGeometry(sr, + env2D, true);// conservative to have same effect as simplify + m_small_tolerance = InternalUtils + .calculateToleranceFromGeometry(null, env2D, true);// conservative + // to have + // same + // effect as + // simplify + + if (max_vertex_in_complete_circle <= 0) { + max_vertex_in_complete_circle = 96;// 96 is the value used by SG. + // This is the number of + // vertices in the full circle. + } + + m_spatialReference = sr; + m_distance = distance; + m_abs_distance = Math.abs(m_distance); + m_abs_distance_reversed = m_abs_distance != 0 ? 1.0 / m_abs_distance + : 0; + + if (NumberUtils.isNaN(densify_dist) || densify_dist == 0) { + densify_dist = m_abs_distance * 1e-5; + } else { + if (densify_dist > m_abs_distance * 0.5) + densify_dist = m_abs_distance * 0.5;// do not allow too + // large densify + // distance (the + // value will be + // adjusted + // anyway later) + } + + if (max_vertex_in_complete_circle < 12) + max_vertex_in_complete_circle = 12; + + + double max_dd = Math.abs(distance) + * (1 - Math.cos(Math.PI / max_vertex_in_complete_circle)); + + if (max_dd > densify_dist) + densify_dist = max_dd;// the densify distance has to agree with the + // max_vertex_in_complete_circle + else { + double vertex_count = Math.PI / Math.acos(1.0 - densify_dist / Math.abs(distance)); + if (vertex_count < (double) max_vertex_in_complete_circle - 1.0) { + max_vertex_in_complete_circle = (int) vertex_count; + if (max_vertex_in_complete_circle < 12) { + max_vertex_in_complete_circle = 12; + densify_dist = Math.abs(distance) * (1 - Math.cos(Math.PI / max_vertex_in_complete_circle)); + } + } + } + + m_densify_dist = densify_dist; + m_max_vertex_in_complete_circle = max_vertex_in_complete_circle; + // when filtering close points we do not want the filter to distort + // generated buffer too much. + m_filter_tolerance = Math.min(m_small_tolerance, + densify_dist * 0.25); + + + m_circle_template_size = calcN_(); + if (m_circle_template_size != m_old_circle_template_size) { + // we have an optimization for this method to be called several + // times. Here we detected too many changes and need to regenerate + // the data. + m_circle_template.clear(); + m_old_circle_template_size = m_circle_template_size; + } + + Geometry result_geom = buffer_(); + m_geometry = null; + return result_geom; + } + + private Geometry m_geometry; + + private static final class BufferCommand { + private interface Flags { + static final int enum_line = 1; + static final int enum_arc = 2; + static final int enum_connection = enum_arc | enum_line; + } + + private Point2D m_from; + private Point2D m_to; + private Point2D m_center; + private int m_next; + private int m_prev; + private int m_type; + + private BufferCommand(Point2D from, Point2D to, Point2D center, + int type, int next, int prev) { + m_from = new Point2D(); + m_to = new Point2D(); + m_center = new Point2D(); + m_from.setCoords(from); + m_to.setCoords(to); + m_center.setCoords(center); + m_type = type; + m_next = next; + m_prev = prev; + } + + private BufferCommand(Point2D from, Point2D to, int next, int prev, + String dummy) { + m_from = new Point2D(); + m_to = new Point2D(); + m_center = new Point2D(); + m_from.setCoords(from); + m_to.setCoords(to); + m_center.setNaN(); + m_type = 4; + m_next = next; + m_prev = prev; + } + } + + private ArrayList m_buffer_commands; + + private int m_original_geom_type; + private ProgressTracker m_progress_tracker; + private int m_max_vertex_in_complete_circle; + private SpatialReference m_spatialReference; + private double m_tolerance; + private double m_small_tolerance; + private double m_filter_tolerance; + private double m_densify_dist; + private double m_distance; + private double m_abs_distance; + private double m_abs_distance_reversed; + private double m_dA; + private boolean m_b_output_loops; + private boolean m_bfilter; + private ArrayList m_circle_template = new ArrayList(0); + private ArrayList m_left_stack; + private ArrayList m_middle_stack; + private Line m_helper_line_1; + private Line m_helper_line_2; + private Point2D[] m_helper_array; + private int m_progress_counter; + private int m_circle_template_size; + private int m_old_circle_template_size; + + private void generateCircleTemplate_() { + if (!m_circle_template.isEmpty()) { + return; + } + + int N = m_circle_template_size; + + assert (N >= 4); + int real_size = (N + 3) / 4; + double dA = (Math.PI * 0.5) / real_size; + m_dA = dA; + + for (int i = 0; i < real_size * 4; i++) + m_circle_template.add(null); + + double dcos = Math.cos(dA); + double dsin = Math.sin(dA); + Point2D pt = new Point2D(0.0, 1.0); + + for (int i = 0; i < real_size; i++) { + m_circle_template.set(i + real_size * 0, new Point2D(pt.y, -pt.x)); + m_circle_template.set(i + real_size * 1, new Point2D(-pt.x, -pt.y)); + m_circle_template.set(i + real_size * 2, new Point2D(-pt.y, pt.x)); + m_circle_template.set(i + real_size * 3, pt); + pt = new Point2D(pt.x, pt.y); + pt.rotateReverse(dcos, dsin); + } + // the template is filled with the index 0 corresponding to the point + // (0, 0), following clockwise direction (0, -1), (-1, 0), (1, 0) + } + + private static final class GeometryCursorForMultiPoint extends GeometryCursor { + private Bufferer m_parent; + private int m_index; + private Geometry m_buffered_polygon; + private MultiPoint m_mp; + private SpatialReference m_spatialReference; + private double m_distance; + private double m_densify_dist; + private double m_x; + private double m_y; + private int m_max_vertex_in_complete_circle; + private ProgressTracker m_progress_tracker; + + GeometryCursorForMultiPoint(Bufferer parent, MultiPoint mp, double distance, + SpatialReference sr, double densify_dist, + int max_vertex_in_complete_circle, + ProgressTracker progress_tracker) { + m_parent = parent; + m_index = 0; + m_mp = mp; + m_x = 0; + m_y = 0; + m_distance = distance; + m_spatialReference = sr; + m_densify_dist = densify_dist; + m_max_vertex_in_complete_circle = max_vertex_in_complete_circle; + m_progress_tracker = progress_tracker; + } + + @Override + public Geometry next() { + Point point = new Point(); + while (true) { + if (m_index == m_mp.getPointCount()) + return null; + + m_mp.getPointByVal(m_index, point); + m_index++; + if (point.isEmpty()) + continue; + break; + } + + boolean b_first = false; + if (m_buffered_polygon == null) { + m_x = point.getX(); + m_y = point.getY(); + + m_buffered_polygon = m_parent.buffer(point, m_distance, + m_spatialReference, m_densify_dist, + m_max_vertex_in_complete_circle, m_progress_tracker); + b_first = true; + } + + Geometry res; + if (m_index < m_mp.getPointCount()) { + res = new Polygon(); + m_buffered_polygon.copyTo(res); + } else { + res = m_buffered_polygon; // do not clone the last geometry. + } + + if (!b_first)// don't apply transformation unnecessary + { + Transformation2D transform = new Transformation2D(); + double dx = point.getX() - m_x; + double dy = point.getY() - m_y; + transform.setShift(dx, dy); + res.applyTransformation(transform); + } + + return res; + } + + @Override + // TODO unclear how this might work + public boolean hasNext() { + return m_mp.getPointCount() > m_index; + } + + @Override + public long getGeometryID() { + return 0; + } + } + + private static final class GlueingCursorForPolyline extends GeometryCursor { + private Polyline m_polyline; + private int m_current_path_index; + + GlueingCursorForPolyline(Polyline polyline) { + m_polyline = polyline; + m_current_path_index = 0; + } + + @Override + public Geometry next() { + if (m_polyline == null) + return null; + + MultiPathImpl mp = (MultiPathImpl) m_polyline._getImpl(); + int npaths = mp.getPathCount(); + if (m_current_path_index < npaths) { + int ind = m_current_path_index; + m_current_path_index++; + if (!mp.isClosedPathInXYPlane(ind)) { + // connect paths that follow one another as an optimization + // for buffering (helps when one polyline is split into many + // segments). + Point2D prev_end = mp.getXY(mp.getPathEnd(ind) - 1); + while (m_current_path_index < mp.getPathCount()) { + Point2D start = mp.getXY(mp + .getPathStart(m_current_path_index)); + if (mp.isClosedPathInXYPlane(m_current_path_index)) + break; + if (start != prev_end) + break; + + prev_end = mp + .getXY(mp.getPathEnd(m_current_path_index) - 1); + m_current_path_index++; + } + } + + if (ind == 0 + && m_current_path_index == m_polyline.getPathCount()) { + Polyline pol = m_polyline; + m_polyline = null; + return pol; + } + + Polyline tmp_polyline = new Polyline( + m_polyline.getDescription()); + tmp_polyline.addPath(m_polyline, ind, true); + for (int i = ind + 1; i < m_current_path_index; i++) { + tmp_polyline.addSegmentsFromPath(m_polyline, i, 0, + mp.getSegmentCount(i), false); + } + + if (false) { + OperatorFactoryLocal.saveGeometryToEsriShapeDbg( + "c:/temp/_geom.bin", tmp_polyline); + } + + if (m_current_path_index == m_polyline.getPathCount()) + m_polyline = null; + + return tmp_polyline; + } else { + return null; + } + } + + @Override + public boolean hasNext() { + return m_polyline != null && m_current_path_index < ((MultiPathImpl) m_polyline._getImpl()).getPathCount(); + } + + @Override + public long getGeometryID() { + return 0; + } + } + + private static final class GeometryCursorForPolyline extends GeometryCursor { + private Bufferer m_bufferer; + GeometryCursor m_geoms; + Geometry m_geometry; + private int m_index; + private boolean m_bfilter; + + GeometryCursorForPolyline(Bufferer bufferer, GeometryCursor geoms, + boolean bfilter) { + m_bufferer = bufferer; + m_geoms = geoms; + m_index = 0; + m_bfilter = bfilter; + } + + @Override + public Geometry next() { + if (m_geometry == null) { + m_index = 0; + m_geometry = m_geoms.next(); + if (m_geometry == null) + return null; + } + + MultiPath mp = (MultiPath) (m_geometry); + if (m_index < mp.getPathCount()) { + int ind = m_index; + m_index++; + return m_bufferer.bufferPolylinePath_((Polyline) m_geometry, + ind, m_bfilter); + } + + m_geometry = null; + return next(); + } + + @Override + public boolean hasNext() { + return (m_geoms != null && m_geoms.hasNext()) || (m_geometry != null && m_index < ((MultiPath) m_geometry).getPathCount()); + } + + @Override + public long getGeometryID() { + return 0; + } + } + + private static final class GeometryCursorForPolygon extends GeometryCursor { + private Bufferer m_bufferer; + private int m_index; + + GeometryCursorForPolygon(Bufferer bufferer) { + m_bufferer = bufferer; + m_index = 0; + } + + @Override + public Geometry next() { + Polygon input_polygon = (Polygon) (m_bufferer.m_geometry); + if (m_index < input_polygon.getPathCount()) { + int ind = m_index; + double area = input_polygon.calculateRingArea2D(m_index); + assert (area > 0); + m_index++; + while (m_index < input_polygon.getPathCount()) { + double hole_area = input_polygon + .calculateRingArea2D(m_index); + if (hole_area > 0) + break;// not a hole + m_index++; + } + + if (ind == 0 && m_index == input_polygon.getPathCount()) { + return m_bufferer.bufferPolygonImpl_(input_polygon, 0, + input_polygon.getPathCount()); + } else { + return m_bufferer.bufferPolygonImpl_(input_polygon, ind, + m_index); + } + } + + return null; + } + + @Override + public boolean hasNext() { + return m_index < ((Polygon) m_bufferer.m_geometry).getPathCount(); + } + + @Override + public long getGeometryID() { + return 0; + } + } + + private Geometry buffer_() { + int gt = m_geometry.getType().value(); + if (Geometry.isSegment(gt)) {// convert segment to a polyline and repeat + // the call + Polyline polyline = new Polyline(m_geometry.getDescription()); + polyline.addSegment((Segment) (m_geometry), true); + m_geometry = polyline; + return buffer_(); + } + + if (m_distance <= m_tolerance) { + if (Geometry.isArea(gt)) { + if (m_distance <= 0) { + // if the geometry is area type, then the negative distance + // may produce a degenerate shape. Check for this and return + // empty geometry. + Envelope2D env = new Envelope2D(); + m_geometry.queryEnvelope2D(env); + if (env.getWidth() <= -m_distance * 2 + || env.getHeight() <= m_distance * 2) + return new Polygon(m_geometry.getDescription()); + } + } else { + return new Polygon(m_geometry.getDescription());// return an + // empty polygon + // for distance + // <= + // m_tolerance + // and any input + // other than + // polygon. + } + } + + // Operator_factory_local::SaveJSONToTextFileDbg("c:/temp/buffer_input.txt", + // *m_geometry, nullptr); + + // Complex cases: + switch (m_geometry.getType().value()) { + case Geometry.GeometryType.Point: + return bufferPoint_(); + case Geometry.GeometryType.MultiPoint: + return bufferMultiPoint_(); + case Geometry.GeometryType.Polyline: + return bufferPolyline_(); + case Geometry.GeometryType.Polygon: + return bufferPolygon_(); + case Geometry.GeometryType.Envelope: + return bufferEnvelope_(); + default: + throw GeometryException.GeometryInternalError(); + } + } + + private Geometry bufferPolyline_() { + if (isDegenerateGeometry_(m_geometry)) { + Point point = new Point(); + ((MultiVertexGeometry) m_geometry).getPointByVal(0, point); + Envelope2D env2D = new Envelope2D(); + m_geometry.queryEnvelope2D(env2D); + point.setXY(env2D.getCenter()); + return bufferPoint_(point); + } + + assert (m_distance > 0); + Polyline poly = (Polyline) m_geometry; + m_geometry = null; + + GeometryCursor glueing_cursor = new GlueingCursorForPolyline(poly);//glues paths together if they connect at one point + poly = null; + GeometryCursor generalized_paths = OperatorGeneralize.local().execute(glueing_cursor, m_densify_dist * 0.25, false, m_progress_tracker); + GeometryCursor simple_paths = OperatorSimplifyOGC.local().execute(generalized_paths, null, true, m_progress_tracker);//make a planar graph. + generalized_paths = null; + GeometryCursor path_buffering_cursor = new GeometryCursorForPolyline(this, simple_paths, m_bfilter); + simple_paths = null; + GeometryCursor union_cursor = OperatorUnion.local().execute(path_buffering_cursor, m_spatialReference, m_progress_tracker);//(int)Operator_union::Options::enum_disable_edge_dissolver + Geometry result = union_cursor.next(); + return result; + } + + private Geometry bufferPolygon_() { + if (m_distance == 0) + return m_geometry;// return input to the output. + + OperatorSimplify simplify = (OperatorSimplify) OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Simplify); + + generateCircleTemplate_(); + m_geometry = simplify.execute(m_geometry, null, false, + m_progress_tracker); + + if (m_distance < 0) { + Polygon poly = (Polygon) (m_geometry); + Polygon buffered_result = bufferPolygonImpl_(poly, 0, + poly.getPathCount()); + return simplify.execute(buffered_result, m_spatialReference, false, + m_progress_tracker); + } else { + if (isDegenerateGeometry_(m_geometry)) { + Point point = new Point(); + ((MultiVertexGeometry) m_geometry).getPointByVal(0, point); + Envelope2D env2D = new Envelope2D(); + m_geometry.queryEnvelope2D(env2D); + point.setXY(env2D.getCenter()); + return bufferPoint_(point); + } + + // For the positive distance we need to process polygon in the parts + // such that each exterior ring with holes is processed separatelly. + GeometryCursorForPolygon cursor = new GeometryCursorForPolygon(this); + GeometryCursor union_cursor = ((OperatorUnion) OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Union)).execute( + cursor, m_spatialReference, m_progress_tracker); + Geometry result = union_cursor.next(); + return result; + } + } + + private Polygon bufferPolygonImpl_(Polygon input_geom, int ipath_begin, + int ipath_end) { + MultiPath input_mp = (MultiPath) (input_geom); + MultiPathImpl mp_impl = (MultiPathImpl) (input_mp._getImpl()); + Polygon intermediate_polygon = new Polygon(input_geom.getDescription()); + for (int ipath = ipath_begin; ipath < ipath_end; ipath++) { + if (mp_impl.getPathSize(ipath) < 1) + continue; + + double path_area = mp_impl.calculateRingArea2D(ipath); + Envelope2D env2D = new Envelope2D(); + mp_impl.queryPathEnvelope2D(ipath, env2D); + + if (m_distance > 0) { + if (path_area > 0) { + if (isDegeneratePath_(mp_impl, ipath)) {// if a path is + // degenerate + // (almost a point), + // then we can draw + // a circle instead + // of it as a buffer + // and nobody would + // notice :) + Point point = new Point(); + mp_impl.getPointByVal(mp_impl.getPathStart(ipath), + point); + point.setXY(env2D.getCenter()); + addCircle_( + (MultiPathImpl) intermediate_polygon._getImpl(), + point); + } else { + Polyline result_polyline = new Polyline( + input_geom.getDescription()); + MultiPathImpl result_mp = (MultiPathImpl) result_polyline + ._getImpl(); + + // We often see convex hulls, buffering those is an + // extremely simple task. + boolean bConvex = ConvexHull.isPathConvex( + (Polygon) (m_geometry), ipath, + m_progress_tracker); + if (bConvex + || bufferClosedPath_(m_geometry, ipath, + result_mp, true, 1) == 2) { + Polygon buffered_path = bufferConvexPath_(input_mp, + ipath); + intermediate_polygon.add(buffered_path, false); + } else { + Polygon buffered_path = bufferCleanup_( + result_polyline, false); + intermediate_polygon.add(buffered_path, false); + } + } + } else { + if (env2D.getWidth() + m_tolerance <= 2 * m_abs_distance + || env2D.getHeight() + m_tolerance <= 2 * m_abs_distance) // skip + // parts + // that + // will + // dissapear + continue; + + Polyline result_polyline = new Polyline( + input_geom.getDescription()); + MultiPathImpl result_mp = (MultiPathImpl) result_polyline + ._getImpl(); + bufferClosedPath_(m_geometry, ipath, result_mp, true, 1); + if (!result_polyline.isEmpty()) { + Envelope2D env = new Envelope2D(); + env.setCoords(env2D); + env.inflate(m_abs_distance, m_abs_distance); + result_mp.addEnvelope(env, false); + Polygon buffered_path = bufferCleanup_(result_polyline, + false); + // intermediate_polygon.reserve(intermediate_polygon.getPointCount() + // + buffered_path.getPointCount() - 4); + for (int i = 1, n = buffered_path.getPathCount(); i < n; i++) + intermediate_polygon + .addPath(buffered_path, i, true); + } + } + } else { + if (path_area > 0) { + if (env2D.getWidth() + m_tolerance <= 2 * m_abs_distance + || env2D.getHeight() + m_tolerance <= 2 * m_abs_distance) // skip + // parts + // that + // will + // dissapear + continue; + + Polyline result_polyline = new Polyline( + input_geom.getDescription()); + MultiPathImpl result_mp = (MultiPathImpl) result_polyline + ._getImpl(); + bufferClosedPath_(m_geometry, ipath, result_mp, true, -1);// this + // will + // provide + // a + // shape + // buffered + // inwards. + // It + // has + // counterclockwise + // orientation + if (!result_polyline.isEmpty()) { + Envelope2D env = new Envelope2D(); + result_mp.queryLooseEnvelope2D(env); + env.inflate(m_abs_distance, m_abs_distance); + result_mp.addEnvelope(env, false);// add an envelope + // exterior shell + Polygon buffered_path = bufferCleanup_(result_polyline, + false);// simplify with winding rule + // extract all parts but the first one (which is the + // envelope we added previously) + for (int i = 1, npaths = buffered_path.getPathCount(); i < npaths; i++) { + // the extracted parts have inverted orientation. + intermediate_polygon + .addPath(buffered_path, i, true); + } + } else { + // the path has been erased + } + } else { + // When buffering a hole with negative distance, buffer it + // as if it is an exterior ring buffered with positive + // distance + Polyline result_polyline = new Polyline( + input_geom.getDescription()); + MultiPathImpl result_mp = (MultiPathImpl) result_polyline + ._getImpl(); + bufferClosedPath_(m_geometry, ipath, result_mp, true, -1);// this + // will + // provide + // a + // shape + // buffered + // inwards. + Polygon buffered_path = bufferCleanup_(result_polyline, + false); + for (int i = 0, npaths = buffered_path.getPathCount(); i < npaths; i++) { + intermediate_polygon.addPath(buffered_path, i, true);// adds + // buffered + // hole + // reversed + // as + // if + // it + // is + // exteror + // ring + } + } + + // intermediate_polygon has inverted orientation. + } + } + + if (m_distance > 0) { + if (intermediate_polygon.getPathCount() > 1) { + Polygon cleaned_polygon = bufferCleanup_(intermediate_polygon, + false); + return cleaned_polygon; + } else { + return setWeakSimple_(intermediate_polygon); + } + } else { + Envelope2D polyenv = new Envelope2D(); + intermediate_polygon.queryLooseEnvelope2D(polyenv); + if (!intermediate_polygon.isEmpty()) { + // negative buffer distance. We got buffered holes and exterior + // rings. They all have wrong orientation. + // we need to apply winding simplify again to ensure all holes + // are unioned. + // For that create a big envelope and add all rings of the + // intermediate_polygon to it. + polyenv.inflate(m_abs_distance, m_abs_distance); + intermediate_polygon.addEnvelope(polyenv, false); + Polygon cleaned_polygon = bufferCleanup_(intermediate_polygon, + false); + // intermediate_polygon.reset();//free memory + + Polygon result_polygon = new Polygon( + cleaned_polygon.getDescription()); + for (int i = 1, n = cleaned_polygon.getPathCount(); i < n; i++) { + result_polygon.addPath(cleaned_polygon, i, false); + } + return setWeakSimple_(result_polygon); + } else { + return setWeakSimple_(intermediate_polygon); + } + } + } + + private Geometry bufferPoint_() { + return bufferPoint_((Point) (m_geometry)); + } + + private Geometry bufferPoint_(Point point) { + assert (m_distance > 0); + Polygon resultPolygon = new Polygon(point.getDescription()); + addCircle_((MultiPathImpl) resultPolygon._getImpl(), point); + return setStrongSimple_(resultPolygon); + } + + private Geometry bufferMultiPoint_() { + assert (m_distance > 0); + GeometryCursorForMultiPoint mpCursor = new GeometryCursorForMultiPoint(this, + (MultiPoint) (m_geometry), m_distance, m_spatialReference, + m_densify_dist, m_max_vertex_in_complete_circle, + m_progress_tracker); + GeometryCursor c = ((OperatorUnion) OperatorFactoryLocal.getInstance() + .getOperator(Operator.Type.Union)).execute(mpCursor, + m_spatialReference, m_progress_tracker); + return c.next(); + } + + private Geometry bufferEnvelope_() { + Polygon polygon = new Polygon(m_geometry.getDescription()); + if (m_distance <= 0) { + if (m_distance == 0) + polygon.addEnvelope((Envelope) (m_geometry), false); + else { + Envelope env = new Envelope(); + m_geometry.queryEnvelope(env); + env.inflate(m_distance, m_distance); + polygon.addEnvelope(env, false); + } + + return polygon;// nothing is easier than negative buffer on the + // envelope. + } + + polygon.addEnvelope((Envelope) (m_geometry), false); + m_geometry = polygon; + return bufferConvexPath_(polygon, 0); + } + + private Polygon bufferConvexPath_(MultiPath src, int ipath) { + generateCircleTemplate_(); + + Polygon resultPolygon = new Polygon(src.getDescription()); + MultiPathImpl result_mp = (MultiPathImpl) resultPolygon._getImpl(); + + // resultPolygon.reserve((m_circle_template.size() / 10 + 4) * + // src.getPathSize(ipath)); + + Point2D pt_1_tmp = new Point2D(), pt_1 = new Point2D(); + Point2D pt_2_tmp = new Point2D(), pt_2 = new Point2D(); + Point2D pt_3_tmp = new Point2D(), pt_3 = new Point2D(); + Point2D v_1 = new Point2D(); + Point2D v_2 = new Point2D(); + MultiPathImpl src_mp = (MultiPathImpl) src._getImpl(); + int path_size = src.getPathSize(ipath); + int path_start = src.getPathStart(ipath); + for (int i = 0, n = src.getPathSize(ipath); i < n; i++) { + src_mp.getXY(path_start + i, pt_1); + src_mp.getXY(path_start + (i + 1) % path_size, pt_2); + src_mp.getXY(path_start + (i + 2) % path_size, pt_3); + v_1.sub(pt_2, pt_1); + if (v_1.length() == 0) + throw GeometryException.GeometryInternalError(); + + v_1.leftPerpendicular(); + v_1.normalize(); + v_1.scale(m_abs_distance); + pt_1_tmp.add(v_1, pt_1); + pt_2_tmp.add(v_1, pt_2); + if (i == 0) + result_mp.startPath(pt_1_tmp); + else { + result_mp.lineTo(pt_1_tmp); + } + + result_mp.lineTo(pt_2_tmp); + + v_2.sub(pt_3, pt_2); + if (v_2.length() == 0) + throw GeometryException.GeometryInternalError(); + + v_2.leftPerpendicular(); + v_2.normalize(); + v_2.scale(m_abs_distance); + pt_3_tmp.add(v_2, pt_2); + + addJoin_(result_mp, pt_2, pt_2_tmp, pt_3_tmp, false, false); + } + + return setWeakSimple_(resultPolygon); + } + + private Polygon bufferPolylinePath_(Polyline polyline, int ipath, + boolean bfilter) { + assert (m_distance != 0); + generateCircleTemplate_(); + + MultiPath input_multi_path = polyline; + MultiPathImpl mp_impl = (MultiPathImpl) (input_multi_path._getImpl()); + + if (mp_impl.getPathSize(ipath) < 1) + return null; + + if (isDegeneratePath_(mp_impl, ipath) && m_distance > 0) {// if a path + // is + // degenerate + // (almost a + // point), + // then we + // can draw + // a circle + // instead + // of it as + // a buffer + // and + // nobody + // would + // notice :) + Point point = new Point(); + mp_impl.getPointByVal(mp_impl.getPathStart(ipath), point); + Envelope2D env2D = new Envelope2D(); + mp_impl.queryPathEnvelope2D(ipath, env2D); + point.setXY(env2D.getCenter()); + return (Polygon) (bufferPoint_(point)); + } + + Polyline result_polyline = new Polyline(polyline.getDescription()); + + MultiPathImpl result_mp = (MultiPathImpl) result_polyline._getImpl(); + boolean b_closed = mp_impl.isClosedPathInXYPlane(ipath); + + if (b_closed) { + bufferClosedPath_(input_multi_path, ipath, result_mp, bfilter, 1); + bufferClosedPath_(input_multi_path, ipath, result_mp, bfilter, -1); + } else { + Polyline tmpPoly = new Polyline(input_multi_path.getDescription()); + tmpPoly.addPath(input_multi_path, ipath, false); + ((MultiPathImpl) tmpPoly._getImpl()).addSegmentsFromPath( + (MultiPathImpl) input_multi_path._getImpl(), ipath, 0, + input_multi_path.getSegmentCount(ipath), false); + bufferClosedPath_(tmpPoly, 0, result_mp, bfilter, 1); + } + + return bufferCleanup_(result_polyline, false); + } + + private void progress_() { + m_progress_counter++; + if (m_progress_counter % 1024 == 0) { + if ((m_progress_tracker != null) + && !(m_progress_tracker.progress(-1, -1))) + throw new RuntimeException("user_canceled"); + } + } + + private Polygon bufferCleanup_(MultiPath multi_path, boolean simplify_result) { + double tol = simplify_result ? m_tolerance : m_small_tolerance; + Polygon resultPolygon = (Polygon) (TopologicalOperations + .planarSimplify(multi_path, tol, true, !simplify_result, + m_progress_tracker)); + assert (InternalUtils.isWeakSimple(resultPolygon, 0.0)); + return resultPolygon; + } + + private int calcN_() { + //this method should be called only once m_circle_template_size is set then; + final int minN = 4; + if (m_densify_dist == 0) + return m_max_vertex_in_complete_circle; + + double r = m_densify_dist * Math.abs(m_abs_distance_reversed); + double cos_a = 1 - r; + double N; + if (cos_a < -1) + N = minN; + else + N = 2.0 * Math.PI / Math.acos(cos_a) + 0.5; + + if (N < minN) + N = minN; + else if (N > m_max_vertex_in_complete_circle) + N = m_max_vertex_in_complete_circle; + + return (int) N; + } + + private void addJoin_(MultiPathImpl dst, Point2D center, Point2D fromPt, + Point2D toPt, boolean bStartPath, boolean bFinishAtToPt) { + generateCircleTemplate_(); + + Point2D v_1 = new Point2D(); + v_1.sub(fromPt, center); + v_1.scale(m_abs_distance_reversed); + Point2D v_2 = new Point2D(); + v_2.sub(toPt, center); + v_2.scale(m_abs_distance_reversed); + double angle_from = Math.atan2(v_1.y, v_1.x); + double dindex_from = angle_from / m_dA; + if (dindex_from < 0) + dindex_from = (double) m_circle_template.size() + dindex_from; + + dindex_from = (double) m_circle_template.size() - dindex_from; + + double angle_to = Math.atan2(v_2.y, v_2.x); + double dindex_to = angle_to / m_dA; + if (dindex_to < 0) + dindex_to = (double) m_circle_template.size() + dindex_to; + + dindex_to = (double) m_circle_template.size() - dindex_to; + + if (dindex_to < dindex_from) + dindex_to += (double) m_circle_template.size(); + assert (dindex_to >= dindex_from); + + int index_to = (int) dindex_to; + int index_from = (int) Math.ceil(dindex_from); + + if (bStartPath) { + dst.startPath(fromPt); + bStartPath = false; + } + + Point2D p = new Point2D(); + p.setCoords(m_circle_template.get(index_from % m_circle_template.size())); + p.scaleAdd(m_abs_distance, center); + double ddd = m_tolerance * 10; + p.sub(fromPt); + if (p.length() < ddd)// if too close to the fromPt, then use the next + // point + index_from += 1; + + p.setCoords(m_circle_template.get(index_to % m_circle_template.size())); + p.scaleAdd(m_abs_distance, center); + p.sub(toPt); + if (p.length() < ddd)// if too close to the toPt, then use the prev + // point + index_to -= 1; + + int count = index_to - index_from; + count++; + + for (int i = 0, j = index_from % m_circle_template.size(); i < count; i++, j = (j + 1) + % m_circle_template.size()) { + p.setCoords(m_circle_template.get(j)); + p.scaleAdd(m_abs_distance, center); + dst.lineTo(p); + progress_(); + } + + if (bFinishAtToPt) { + dst.lineTo(toPt); + } + } + + private int bufferClosedPath_(Geometry input_geom, int ipath, + MultiPathImpl result_mp, boolean bfilter, int dir) { + // Use temporary polyline for the path buffering. + EditShape edit_shape = new EditShape(); + int geom = edit_shape.addPathFromMultiPath((MultiPath) input_geom, + ipath, true); + edit_shape.filterClosePoints(m_filter_tolerance, false, false); + if (edit_shape.getPointCount(geom) < 2) {// Got degenerate output. + // Either bail out or + // produce a circle. + if (dir < 0) + return 1;// negative direction produces nothing. + + MultiPath mpIn = (MultiPath) input_geom; + // Add a circle + Point pt = new Point(); + mpIn.getPointByVal(mpIn.getPathStart(ipath), pt); + addCircle_(result_mp, pt); + return 1; + } + + assert (edit_shape.getFirstPath(geom) != -1); + assert (edit_shape.getFirstVertex(edit_shape.getFirstPath(geom)) != -1); + + Point2D origin = edit_shape.getXY(edit_shape.getFirstVertex(edit_shape + .getFirstPath(geom))); + Transformation2D tr = new Transformation2D(); + tr.setShift(-origin.x, -origin.y); + // move the path to origin for better accuracy in calculations. + edit_shape.applyTransformation(tr); + + if (bfilter) { + // try removing the noise that does not contribute to the buffer. + int res_filter = filterPath_(edit_shape, geom, dir, true, m_abs_distance, m_filter_tolerance, m_densify_dist); + assert (res_filter == 1); + // Operator_factory_local::SaveJSONToTextFileDbg("c:/temp/buffer_filter.txt", + // *edit_shape.get_geometry(geom), nullptr); + if (edit_shape.getPointCount(geom) < 2) {// got degenerate output. + // Wither bail out or + // produce a circle. + if (dir < 0) + return 1;// negative direction produces nothing. + + MultiPath mpIn = (MultiPath) input_geom; + // Add a circle + Point pt = new Point(); + mpIn.getPointByVal(mpIn.getPathStart(ipath), pt); + addCircle_(result_mp, pt); + return 1; + } + } + + m_buffer_commands.clear(); + int path = edit_shape.getFirstPath(geom); + int ivert = edit_shape.getFirstVertex(path); + int iprev = dir == 1 ? edit_shape.getPrevVertex(ivert) : edit_shape + .getNextVertex(ivert); + int inext = dir == 1 ? edit_shape.getNextVertex(ivert) : edit_shape + .getPrevVertex(ivert); + boolean b_first = true; + Point2D pt_current = new Point2D(), pt_after = new Point2D(), pt_before = new Point2D(), pt_left_prev = new Point2D(), pt = new Point2D(), pt1 = new Point2D(); + Point2D v_after = new Point2D(), v_before = new Point2D(), v_left = new Point2D(), v_left_prev = new Point2D(); + double abs_d = m_abs_distance; + int ncount = edit_shape.getPathSize(path); + + // write out buffer commands as a set of arcs and line segments. + // if we'd convert this directly to a polygon and draw using winding + // fill rule, we'd get the buffered result. + for (int index = 0; index < ncount; index++) { + edit_shape.getXY(inext, pt_after); + + if (b_first) { + edit_shape.getXY(ivert, pt_current); + edit_shape.getXY(iprev, pt_before); + v_before.sub(pt_current, pt_before); + v_before.normalize(); + v_left_prev.leftPerpendicular(v_before); + v_left_prev.scale(abs_d); + pt_left_prev.add(v_left_prev, pt_current); + } + + v_after.sub(pt_after, pt_current); + v_after.normalize(); + + v_left.leftPerpendicular(v_after); + v_left.scale(abs_d); + pt.add(pt_current, v_left); + double cross = v_before.crossProduct(v_after); + double dot = v_before.dotProduct(v_after); + boolean bDoJoin = cross < 0 || (dot < 0 && cross == 0); + if (bDoJoin) { + m_buffer_commands.add(new BufferCommand(pt_left_prev, pt, + pt_current, BufferCommand.Flags.enum_arc, + m_buffer_commands.size() + 1, + m_buffer_commands.size() - 1)); + } else if (!pt_left_prev.isEqual(pt)) { + m_buffer_commands.add(new BufferCommand(pt_left_prev, + pt_current, m_buffer_commands.size() + 1, + m_buffer_commands.size() - 1, "dummy")); + m_buffer_commands.add(new BufferCommand(pt_current, pt, + m_buffer_commands.size() + 1, + m_buffer_commands.size() - 1, "dummy")); + } + + pt1.add(pt_after, v_left); + m_buffer_commands + .add(new BufferCommand(pt, pt1, pt_current, + BufferCommand.Flags.enum_line, m_buffer_commands + .size() + 1, m_buffer_commands.size() - 1)); + + pt_left_prev.setCoords(pt1); + v_left_prev.setCoords(v_left); + pt_before.setCoords(pt_current); + pt_current.setCoords(pt_after); + v_before.setCoords(v_after); + iprev = ivert; + ivert = inext; + b_first = false; + inext = dir == 1 ? edit_shape.getNextVertex(ivert) : edit_shape + .getPrevVertex(ivert); + } + + m_buffer_commands.get(m_buffer_commands.size() - 1).m_next = 0; + m_buffer_commands.get(0).m_prev = m_buffer_commands.size() - 1; + processBufferCommands_(result_mp); + tr.setShift(origin.x, origin.y);// move the path to improve precision. + result_mp.applyTransformation(tr, result_mp.getPathCount() - 1); + return 1; + } + + private void processBufferCommands_(MultiPathImpl result_mp) { + int ifirst_seg = cleanupBufferCommands_(); + boolean first = true; + int iseg_next = ifirst_seg + 1; + for (int iseg = ifirst_seg; iseg_next != ifirst_seg; iseg = iseg_next) { + BufferCommand command = m_buffer_commands.get(iseg); + iseg_next = command.m_next != -1 ? command.m_next : (iseg + 1) + % m_buffer_commands.size(); + if (command.m_type == 0) + continue;// deleted segment + + if (first) { + result_mp.startPath(command.m_from); + first = false; + } + + if (command.m_type == BufferCommand.Flags.enum_arc) {// arc + addJoin_(result_mp, command.m_center, command.m_from, + command.m_to, false, true); + } else { + result_mp.lineTo(command.m_to); + } + first = false; + } + } + + private int cleanupBufferCommands_() { + // The purpose of this function is to remove as many self intersections + // from the buffered shape as possible. + // The buffer works without cleanup also, but slower. + + if (m_helper_array == null) + m_helper_array = new Point2D[9]; + + int istart = 0; + for (int iseg = 0, nseg = m_buffer_commands.size(); iseg < nseg; ) { + BufferCommand command = m_buffer_commands.get(iseg); + if ((command.m_type & BufferCommand.Flags.enum_connection) != 0) { + istart = iseg; + break; + } + + iseg = command.m_next; + } + + int iseg_next = istart + 1; + for (int iseg = istart; iseg_next != istart; iseg = iseg_next) { + BufferCommand command = m_buffer_commands.get(iseg); + iseg_next = command.m_next; + int count = 1; + BufferCommand command_next = null; + while (iseg_next != iseg) {// find next segement + command_next = m_buffer_commands.get(iseg_next); + if ((command_next.m_type & BufferCommand.Flags.enum_connection) != 0) + break; + + iseg_next = command_next.m_next; + count++; + } + + if (count == 1) { + // Next segment starts where this one ends. Skip this case as it + // is simple. + assert (command.m_to.isEqual(command_next.m_from)); + continue; + } + + if ((command.m_type & command_next.m_type) == BufferCommand.Flags.enum_line) {// simplest + // cleanup + // - + // intersect + // lines + if (m_helper_line_1 == null) { + m_helper_line_1 = new Line(); + m_helper_line_2 = new Line(); + } + m_helper_line_1.setStartXY(command.m_from); + m_helper_line_1.setEndXY(command.m_to); + m_helper_line_2.setStartXY(command_next.m_from); + m_helper_line_2.setEndXY(command_next.m_to); + + int count_ = m_helper_line_1.intersect(m_helper_line_2, + m_helper_array, null, null, m_small_tolerance); + if (count_ == 1) { + command.m_to.setCoords(m_helper_array[0]); + command_next.m_from.setCoords(m_helper_array[0]); + command.m_next = iseg_next;// skip until iseg_next + command_next.m_prev = iseg; + } else if (count_ == 2) {// TODO: this case needs improvement + } + } + } + + return istart; + } + + private static void protectExtremeVertices_(EditShape edit_shape, + int protection_index, int geom, int path) { + // detect very narrow corners and preserve them. We cannot reliably + // delete these. + int vprev = -1; + Point2D pt_prev = new Point2D(); + pt_prev.setNaN(); + Point2D pt = new Point2D(); + pt.setNaN(); + Point2D v_before = new Point2D(); + v_before.setNaN(); + Point2D pt_next = new Point2D(); + Point2D v_after = new Point2D(); + for (int i = 0, n = edit_shape.getPathSize(path), v = edit_shape + .getFirstVertex(path); i < n; ++i) { + if (vprev == -1) { + edit_shape.getXY(v, pt); + + vprev = edit_shape.getPrevVertex(v); + if (vprev != -1) { + edit_shape.getXY(vprev, pt_prev); + v_before.sub(pt, pt_prev); + v_before.normalize(); + } + } + + int vnext = edit_shape.getNextVertex(v); + if (vnext == -1) + break; + + edit_shape.getXY(vnext, pt_next); + v_after.sub(pt_next, pt); + v_after.normalize(); + + if (vprev != -1) { + double d = v_after.dotProduct(v_before); + if (d < -0.99 + && Math.abs(v_after.crossProduct(v_before)) < 1e-7) { + edit_shape.setUserIndex(v, protection_index, 1); + } + } + + vprev = v; + v = vnext; + pt_prev.setCoords(pt); + pt.setCoords(pt_next); + v_before.setCoords(v_after); + } + } + + static private int filterPath_(EditShape edit_shape, int geom, int dir, + boolean closed, double abs_distance, double filter_tolerance, + double densify_distance) { + int path = edit_shape.getFirstPath(geom); + + int concave_index = -1; + int fixed_vertices_index = edit_shape.createUserIndex(); + protectExtremeVertices_(edit_shape, fixed_vertices_index, geom, path); + + for (int iter = 0; iter < 100; ++iter) { + int isize = edit_shape.getPathSize(path); + if (isize == 0) { + edit_shape.removeUserIndex(fixed_vertices_index); + ; + return 1; + } + + int ivert = edit_shape.getFirstVertex(path); + int nvertices = edit_shape.getPathSize(path); + if (nvertices < 3) { + edit_shape.removeUserIndex(fixed_vertices_index); + ; + return 1; + } + + if (closed && !edit_shape.isClosedPath(path))// the path is closed + // only virtually + { + nvertices -= 1; + } + + double abs_d = abs_distance; + final int nfilter = 64; + boolean filtered = false; + int filtered_in_pass = 0; + boolean go_back = false; + for (int i = 0; i < nvertices && ivert != -1; i++) { + int filtered_now = 0; + int v = ivert; // filter == 0 + for (int filter = 1, n = (int) Math.min(nfilter, nvertices - i); filter < n; filter++) { + v = edit_shape.getNextVertex(v, dir); + if (filter > 1) { + int num = clipFilter_(edit_shape, + fixed_vertices_index, ivert, v, dir, + abs_distance, densify_distance, nfilter); + if (num == -1) + break; + + filtered |= num > 0; + filtered_now += num; + nvertices -= num; + } + } + + filtered_in_pass += filtered_now; + + go_back = filtered_now > 0; + + if (go_back) { + int prev = edit_shape.getPrevVertex(ivert, dir); + if (prev != -1) { + ivert = prev; + nvertices++; + continue; + } + } + + ivert = edit_shape.getNextVertex(ivert, dir); + } + + if (filtered_in_pass == 0) + break; + } + + edit_shape.removeUserIndex(fixed_vertices_index); + edit_shape.filterClosePoints(filter_tolerance, false, false); + + return 1; + } + + // This function clips out segments connecting from_vertiex to to_vertiex if + // they do not contribute to the buffer. + private static int clipFilter_(EditShape edit_shape, + int fixed_vertices_index, int from_vertex, int to_vertex, int dir, + double abs_distance, double densify_distance, final int max_filter) { + // Note: vertices marked with fixed_vertices_index cannot be deleted. + + Point2D pt1 = edit_shape.getXY(from_vertex); + Point2D pt2 = edit_shape.getXY(to_vertex); + if (pt1.equals(pt2)) + return -1; + + double densify_distance_delta = densify_distance * 0.25;// distance by + // which we can + // move the + // point closer + // to the chord + // (introducing + // an error into + // the buffer). + double erase_distance_delta = densify_distance * 0.25;// distance when + // we can erase + // the point + // (introducing + // an error into + // the buffer). + // This function goal is to modify or remove vertices between + // from_vertex and to_vertex in such a way that the result would not + // affect buffer to the left of the + // chain. + Point2D v_gap = new Point2D(); + v_gap.sub(pt2, pt1); + double gap_length = v_gap.length(); + double h2_4 = gap_length * gap_length * 0.25; + double sqr_center_to_chord = abs_distance * abs_distance - h2_4; // squared + // distance + // from + // the + // chord + // to + // the + // circle + // center + if (sqr_center_to_chord <= h2_4) + return -1;// center to chord distance is less than half gap, that + // means the gap is too wide for useful filtering (maybe + // this). + + double center_to_chord = Math.sqrt(sqr_center_to_chord); // distance + // from + // circle + // center to + // the + // chord. + + v_gap.normalize(); + Point2D v_gap_norm = new Point2D(v_gap); + v_gap_norm.rightPerpendicular(); + double chord_to_corner = h2_4 / center_to_chord; // cos(a) = + // center_to_chord / + // distance; + // chord_to_corner = + // distance / cos(a) + // - + // center_to_chord; + boolean can_erase_corner_point = chord_to_corner <= erase_distance_delta; + Point2D chord_midpoint = new Point2D(); + MathUtils.lerp(pt2, pt1, 0.5, chord_midpoint); + Point2D corner = new Point2D(v_gap_norm); + double corrected_chord_to_corner = chord_to_corner + - densify_distance_delta;// using slightly smaller than needed + // distance let us filter more. + corner.scaleAdd(Math.max(0.0, corrected_chord_to_corner), + chord_midpoint); + // corner = (p1 + p2) * 0.5 + v_gap_norm * chord_to_corner; + + Point2D center = new Point2D(v_gap_norm); + center.negate(); + center.scaleAdd(center_to_chord, chord_midpoint); + + double allowed_distance = abs_distance - erase_distance_delta; + double sqr_allowed_distance = MathUtils.sqr(allowed_distance); + double sqr_large_distance = sqr_allowed_distance * (1.9 * 1.9); + + Point2D co_p1 = new Point2D(); + co_p1.sub(corner, pt1); + Point2D co_p2 = new Point2D(); + co_p2.sub(corner, pt2); + + boolean large_distance = false;// set to true when distance + int cnt = 0; + char[] locations = new char[64]; + { + // check all vertices in the gap verifying that the gap can be + // clipped. + // + + Point2D pt = new Point2D(); + // firstly remove any duplicate vertices in the end. + for (int v = edit_shape.getPrevVertex(to_vertex, dir); v != from_vertex; ) { + if (edit_shape.getUserIndex(v, fixed_vertices_index) == 1) + return -1;// this range contains protected vertex + + edit_shape.getXY(v, pt); + if (pt.equals(pt2)) { + int v1 = edit_shape.getPrevVertex(v, dir); + edit_shape.removeVertex(v, false); + v = v1; + continue; + } else { + break; + } + } + + Point2D prev_prev_pt = new Point2D(); + prev_prev_pt.setNaN(); + Point2D prev_pt = new Point2D(); + prev_pt.setCoords(pt1); + locations[cnt++] = 1; + int prev_v = from_vertex; + Point2D dummyPt = new Point2D(); + for (int v = edit_shape.getNextVertex(from_vertex, dir); v != to_vertex; ) { + if (edit_shape.getUserIndex(v, fixed_vertices_index) == 1) + return -1;// this range contains protected vertex + + edit_shape.getXY(v, pt); + if (pt.equals(prev_pt)) { + int v1 = edit_shape.getNextVertex(v, dir); + edit_shape.removeVertex(v, false); + v = v1; + continue; + } + + locations[cnt++] = 0; + + Point2D v1 = new Point2D(); + v1.sub(pt, pt1); + if (v1.dotProduct(v_gap_norm) < 0)// we are crossing on the + // wrong site of the chord. + // Just bail out earlier. + // Maybe we could continue + // clipping though here, but + // it seems to be + // unnecessary complicated. + return 0; + + if (Point2D.sqrDistance(pt, pt1) > sqr_large_distance + || Point2D.sqrDistance(pt, pt2) > sqr_large_distance) + large_distance = true; // too far from points, may + // contribute to the outline (in + // case of a large loop) + + char next_location = 0; + + dummyPt.sub(pt, pt1); + double cs1 = dummyPt.crossProduct(co_p1); + if (cs1 >= 0) { + next_location = 1; + } + + dummyPt.sub(pt, pt2); + double cs2 = dummyPt.crossProduct(co_p2); + if (cs2 <= 0) { + next_location |= 2; + } + + if (next_location == 0) + return 0; + + locations[cnt - 1] = next_location; + prev_prev_pt.setCoords(prev_pt); + prev_pt.setCoords(pt); + prev_v = v; + v = edit_shape.getNextVertex(v, dir); + } + + if (cnt == 1) + return 0; + + assert (!pt2.equals(prev_pt)); + locations[cnt++] = 2; + } + + boolean can_clip_all = true; + // we can remove all points and replace them with a single corner point + // if we are moving from location 1 via location 3 to location 2 + for (int i = 1, k = 0; i < cnt; i++) { + if (locations[i] != locations[i - 1]) { + k++; + can_clip_all = k < 3 + && ((k == 1 && locations[i] == 3) || (k == 2 && locations[i] == 2)); + if (!can_clip_all) + return 0; + } + } + + if (cnt > 2 && can_clip_all && (cnt == 3 || !large_distance)) { + int clip_count = 0; + int v = edit_shape.getNextVertex(from_vertex, dir); + if (!can_erase_corner_point) { + edit_shape.setXY(v, corner); + v = edit_shape.getNextVertex(v, dir); + } + + // we can remove all vertices between from and to, because they + // don't contribute + while (v != to_vertex) { + int v1 = edit_shape.getNextVertex(v, dir); + edit_shape.removeVertex(v, false); + v = v1; + ++clip_count; + } + + return clip_count; + } + + if (cnt == 3) { + boolean case1 = (locations[0] == 1 && locations[1] == 2 && locations[2] == 2); + boolean case2 = (locations[0] == 1 && locations[1] == 1 && locations[2] == 2); + if (case1 || case2) { + // special case, when we cannot clip, but we can move the point + Point2D p1 = edit_shape.getXY(from_vertex); + int v = edit_shape.getNextVertex(from_vertex, dir); + Point2D p2 = edit_shape.getXY(v); + Point2D p3 = edit_shape.getXY(edit_shape.getNextVertex(v, dir)); + if (case2) { + Point2D temp = p1; + p1 = p3; + p3 = temp; + } + + Point2D vec = new Point2D(); + vec.sub(p1, p2); + p3.sub(p2); + double veclen = vec.length(); + double w = p3.length(); + double wcosa = vec.dotProduct(p3) / veclen; + double wsina = Math.abs(p3.crossProduct(vec) / veclen); + double z = 2 * abs_distance - wsina; + if (z < 0) + return 0; + + double x = wcosa + Math.sqrt(wsina * z); + if (x > veclen) + return 0; + + Point2D hvec = new Point2D(); + hvec.scaleAdd(-x / veclen, vec, p3); // hvec = p3 - vec * (x / + // veclen); + double h = hvec.length(); + double y = -(h * h * veclen) / (2 * hvec.dotProduct(vec)); + + double t = (x - y) / veclen; + MathUtils.lerp(p2, p1, t, p2); + edit_shape.setXY(v, p2); + return 0; + } + } + + if (large_distance && cnt > 3) { + // we are processing more than 3 points and there are some points + // further than the + return 0; + } + + int v_prev = -1; + Point2D pt_prev = new Point2D(); + int v_cur = from_vertex; + Point2D pt_cur = new Point2D(pt1); + int cur_location = 1; + int prev_location = -1; // 1 - semiplane to the right of [f,c]. 3 - + // semiplane to the right of [c,t], 2 - both + // above fc and ct, 0 - cannot clip, -1 - + // unknown + int v_next = v_cur; + int clip_count = 0; + cnt = 1; + while (v_next != to_vertex) { + v_next = edit_shape.getNextVertex(v_next, dir); + int next_location = locations[cnt++]; + if (next_location == 0) { + if (v_next == to_vertex) + break; + + continue; + } + + Point2D pt_next = edit_shape.getXY(v_next); + + if (prev_location != -1) { + int common_location = (prev_location & cur_location & next_location); + if ((common_location & 3) != 0) { + // prev and next are on the same semiplane as the current we + // can safely remove the current point. + edit_shape.removeVertex(v_cur, true); + clip_count++;// do not change prev point. + v_cur = v_next; + pt_cur.setCoords(pt_next); + cur_location = next_location; + continue; + } + + if (cur_location == 3 && prev_location != 0 + && next_location != 0) { + assert ((prev_location & next_location) == 0);// going from + // one semi + // plane to + // another + // via the + // mid. + pt_cur.setCoords(corner); + if (can_erase_corner_point || pt_cur.equals(pt_prev)) {// this + // point + // can + // be + // removed + edit_shape.removeVertex(v_cur, true); + clip_count++;// do not change prev point. + v_cur = v_next; + pt_cur.setCoords(pt_next); + cur_location = next_location; + continue; + } else { + edit_shape.setXY(v_cur, pt_cur); // snap to the corner + } + } else { + if (next_location == 0 + && cur_location != 0 + || next_location != 0 + && cur_location == 0 + || ((next_location | cur_location) == 3 + && next_location != 3 && cur_location != 3)) { + // clip + } + } + } + + prev_location = cur_location; + v_prev = v_cur; + pt_prev.setCoords(pt_cur); + v_cur = v_next; + cur_location = next_location; + pt_cur.setCoords(pt_next); + } + + return clip_count; + } + + private boolean isDegeneratePath_(MultiPathImpl mp_impl, int ipath) { + if (mp_impl.getPathSize(ipath) == 1) + return true; + Envelope2D env = new Envelope2D(); + mp_impl.queryPathEnvelope2D(ipath, env); + if (Math.max(env.getWidth(), env.getHeight()) < m_densify_dist * 0.5) + return true; + + return false; + } + + private boolean isDegenerateGeometry_(Geometry geom) { + Envelope2D env = new Envelope2D(); + geom.queryEnvelope2D(env); + if (Math.max(env.getWidth(), env.getHeight()) < m_densify_dist * 0.5) + return true; + + return false; + } + + private Polyline preparePolyline_(Polyline input_geom) { + // Generalize it firstly using 25% of the densification deviation as a + // criterion. + Polyline generalized_polyline = (Polyline) ((OperatorGeneralize) OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Generalize)).execute( + input_geom, m_densify_dist * 0.25, false, m_progress_tracker); + + int path_point_count = 0; + for (int i = 0, npath = generalized_polyline.getPathCount(); i < npath; i++) { + path_point_count = Math.max(generalized_polyline.getPathSize(i), + path_point_count); + } + + if (path_point_count < 32) { + m_bfilter = false; + return generalized_polyline; + } else { + m_bfilter = true; + // If we apply a filter to the polyline, then we have to resolve all + // self intersections. + Polyline simple_polyline = (Polyline) (TopologicalOperations + .planarSimplify(generalized_polyline, m_small_tolerance, + false, true, m_progress_tracker)); + // Operator_factory_local::SaveJSONToTextFileDbg("c:/temp/buffer_simplify.txt", + // *simple_polyline, nullptr); + return simple_polyline; + } + } + + private void addCircle_(MultiPathImpl result_mp, Point point) { + // Uses same calculations for each of the quadrants, generating a + // symmetric distribution of points. + Point2D center = point.getXY(); + if (m_circle_template != null && !m_circle_template.isEmpty()) {// use + // template + // if + // available. + Point2D p = new Point2D(); + p.setCoords(m_circle_template.get(0)); + p.scaleAdd(m_abs_distance, center); + result_mp.startPath(p); + for (int i = 1, n = (int) m_circle_template.size(); i < n; i++) { + p.setCoords(m_circle_template.get(i)); + p.scaleAdd(m_abs_distance, center); + result_mp.lineTo(p); + } + return; + } + + // avoid unnecessary memory allocation for the circle template. Just do + // the point here. + + int N = m_circle_template_size; + int real_size = (N + 3) / 4; + double dA = (Math.PI * 0.5) / real_size; + // result_mp.reserve(real_size * 4); + + double dcos = Math.cos(dA); + double dsin = Math.sin(dA); + Point2D pt = new Point2D(); + for (int quadrant = 3; quadrant >= 0; quadrant--) { + pt.setCoords(0.0, m_abs_distance); + switch (quadrant) { + case 0: {// upper left quadrant + for (int i = 0; i < real_size; i++) { + result_mp.lineTo(pt.x + center.x, pt.y + center.y); + pt.rotateReverse(dcos, dsin); + } + break; + } + case 1: {// upper left quadrant + for (int i = 0; i < real_size; i++) {// m_circle_template.set(i + // + real_size * 1, + // Point_2D::construct(-pt.y, + // pt.x)); + result_mp.lineTo(-pt.y + center.x, pt.x + center.y); + pt.rotateReverse(dcos, dsin); + } + break; + } + case 2: {// lower left quadrant + // m_circle_template.set(i + real_size * 2, + // Point_2D::construct(-pt.x, -pt.y)); + for (int i = 0; i < real_size; i++) { + result_mp.lineTo(-pt.x + center.x, -pt.y + center.y); + pt.rotateReverse(dcos, dsin); + } + break; + } + default:// case 3: + {// lower right quadrant + // m_circle_template.set(i + real_size * 3, + // Point_2D::construct(pt.y, -pt.x)); + result_mp.startPath(pt.y + center.x, -pt.x + center.y);// we + // start + // at + // the + // quadrant + // 3. + // The + // first + // point + // is + // (0, + // -m_distance) + // + + // center + for (int i = 1; i < real_size; i++) { + pt.rotateReverse(dcos, dsin); + result_mp.lineTo(pt.y + center.x, -pt.x + center.y); + } + break; + } + } + + progress_(); + } + } + + private static Polygon setWeakSimple_(Polygon poly) { + ((MultiPathImpl) poly._getImpl()).setIsSimple( + MultiVertexGeometryImpl.GeometryXSimple.Weak, 0.0, false); + return poly; + } + + private Polygon setStrongSimple_(Polygon poly) { + ((MultiPathImpl) poly._getImpl()).setIsSimple( + MultiVertexGeometryImpl.GeometryXSimple.Strong, m_tolerance, + false); + ((MultiPathImpl) poly._getImpl())._updateOGCFlags(); + return poly; + } } diff --git a/src/main/java/com/esri/core/geometry/ByteBufferCursor.java b/src/main/java/com/esri/core/geometry/ByteBufferCursor.java index 1e96a53c..7870ad4f 100644 --- a/src/main/java/com/esri/core/geometry/ByteBufferCursor.java +++ b/src/main/java/com/esri/core/geometry/ByteBufferCursor.java @@ -31,27 +31,27 @@ */ public abstract class ByteBufferCursor implements Iterator { - /** - * Moves the cursor to the next ByteBuffer. Returns null when reached the - * end. - */ - public abstract ByteBuffer next(); - - /** - * Returns the ID of the current ByteBuffer. The ID is propagated across the - * operations (when possible). - *

- * Returns an ID associated with the current Geometry. The ID is passed - * along and is returned by some operators to preserve relationship between - * the input and output geometry classes. It is not always possible to - * preserve an ID during an operation. - */ - public abstract long getByteBufferID(); - - public abstract SimpleStateEnum getSimpleState(); - - public abstract String getFeatureID(); - - public abstract boolean hasNext(); + /** + * Moves the cursor to the next ByteBuffer. Returns null when reached the + * end. + */ + public abstract ByteBuffer next(); + + /** + * Returns the ID of the current ByteBuffer. The ID is propagated across the + * operations (when possible). + *

+ * Returns an ID associated with the current Geometry. The ID is passed + * along and is returned by some operators to preserve relationship between + * the input and output geometry classes. It is not always possible to + * preserve an ID during an operation. + */ + public abstract long getByteBufferID(); + + public abstract SimpleStateEnum getSimpleState(); + + public abstract String getFeatureID(); + + public abstract boolean hasNext(); } diff --git a/src/main/java/com/esri/core/geometry/ClassicSort.java b/src/main/java/com/esri/core/geometry/ClassicSort.java index 8a5d0355..b6828897 100644 --- a/src/main/java/com/esri/core/geometry/ClassicSort.java +++ b/src/main/java/com/esri/core/geometry/ClassicSort.java @@ -28,8 +28,8 @@ package com.esri.core.geometry; abstract class ClassicSort { - public abstract void userSort(int begin, int end, - AttributeStreamOfInt32 indices); + public abstract void userSort(int begin, int end, + AttributeStreamOfInt32 indices); - public abstract double getValue(int index); + public abstract double getValue(int index); } diff --git a/src/main/java/com/esri/core/geometry/Clipper.java b/src/main/java/com/esri/core/geometry/Clipper.java index 08e19e8d..59dc9d1d 100644 --- a/src/main/java/com/esri/core/geometry/Clipper.java +++ b/src/main/java/com/esri/core/geometry/Clipper.java @@ -24,1249 +24,1249 @@ package com.esri.core.geometry; class Clipper { - Envelope2D m_extent; - EditShape m_shape; - int m_geometry; - int m_vertices_on_extent_index; - AttributeStreamOfInt32 m_vertices_on_extent; - - int checkSegmentIntersection_(Envelope2D seg_env, int side, - double clip_value) { - switch (side) { - case 0: - if (seg_env.xmin < clip_value && seg_env.xmax <= clip_value) { - return 0; // outside (or on the border) - } else if (seg_env.xmin >= clip_value) { - return 1;// inside - } else - return -1; // intersects - case 1: - if (seg_env.ymin < clip_value && seg_env.ymax <= clip_value) { - return 0; - } else if (seg_env.ymin >= clip_value) { - return 1; - } else - return -1; - case 2: - if (seg_env.xmin >= clip_value && seg_env.xmax > clip_value) { - return 0; - } else if (seg_env.xmax <= clip_value) { - return 1; - } else - return -1; - case 3: - if (seg_env.ymin >= clip_value && seg_env.ymax > clip_value) { - return 0; - } else if (seg_env.ymax <= clip_value) { - return 1; - } else - return -1; - } - assert (false);// cannot be here - return 0; - } - - MultiPath clipMultiPath2_(MultiPath multi_path_in, double tolerance, - double densify_dist) { - boolean b_is_polygon = multi_path_in.getType() == Geometry.Type.Polygon; - if (b_is_polygon) - return clipPolygon2_((Polygon) multi_path_in, tolerance, - densify_dist); - else - return clipPolyline_((Polyline) multi_path_in, tolerance); - } - - MultiPath clipPolygon2_(Polygon polygon_in, double tolerance, - double densify_dist) { - // If extent is degenerate, return 0. - if (m_extent.getWidth() == 0 || m_extent.getHeight() == 0) - return (MultiPath) polygon_in.createInstance(); - - Envelope2D orig_env2D = new Envelope2D(); - polygon_in.queryLooseEnvelope(orig_env2D); - - // m_shape = GCNEW Edit_shape(); - m_geometry = m_shape.addGeometry(polygon_in); - - // Forward decl for java port - Envelope2D seg_env = new Envelope2D(); - Envelope2D sub_seg_env = new Envelope2D(); - Point2D pt_1 = new Point2D(); - Point2D pt_2 = new Point2D(); - double[] result_ordinates = new double[9]; - double[] parameters = new double[9]; - SegmentBuffer sub_segment_buffer = new SegmentBuffer(); - Line line = new Line(); - AttributeStreamOfInt32 delete_candidates = new AttributeStreamOfInt32(0); - delete_candidates.reserve(Math.min(100, polygon_in.getPointCount())); - // clip the polygon successively by each plane - boolean b_all_outside = false; - for (int iclip_plane = 0; !b_all_outside && iclip_plane < 4; iclip_plane++) { - boolean b_intersects_plane = false; - boolean b_axis_x = (iclip_plane & 1) != 0; - double clip_value = 0; - switch (iclip_plane) { - case 0: - clip_value = m_extent.xmin; - b_intersects_plane = orig_env2D.xmin <= clip_value - && orig_env2D.xmax >= clip_value; - assert (b_intersects_plane || clip_value < orig_env2D.xmin); - break; - case 1: - clip_value = m_extent.ymin; - b_intersects_plane = orig_env2D.ymin <= clip_value - && orig_env2D.ymax >= clip_value; - assert (b_intersects_plane || clip_value < orig_env2D.ymin); - break; - case 2: - clip_value = m_extent.xmax; - b_intersects_plane = orig_env2D.xmin <= clip_value - && orig_env2D.xmax >= clip_value; - assert (b_intersects_plane || clip_value > orig_env2D.xmax); - break; - case 3: - clip_value = m_extent.ymax; - b_intersects_plane = orig_env2D.ymin <= clip_value - && orig_env2D.ymax >= clip_value; - assert (b_intersects_plane || clip_value > orig_env2D.ymax); - break; - } - - if (!b_intersects_plane) - continue;// Optimize for common case when only few sides of the - // clipper envelope intersect the geometry. - - b_all_outside = true; - for (int path = m_shape.getFirstPath(m_geometry); path != -1; ) { - int inside = -1; - int firstinside = -1; - int first = m_shape.getFirstVertex(path); - int vertex = first; - do { - Segment segment = m_shape.getSegment(vertex); - if (segment == null) { - segment = line; - m_shape.getXY(vertex, pt_1); - segment.setStartXY(pt_1); - m_shape.getXY(m_shape.getNextVertex(vertex), pt_2); - segment.setEndXY(pt_2); - } - segment.queryEnvelope2D(seg_env); - int seg_plane_intersection_status = checkSegmentIntersection_( - seg_env, iclip_plane, clip_value); - int split_count = 0; - int next_vertex = -1; - - if (seg_plane_intersection_status == -1) // intersects plane - { - int count = segment.intersectionWithAxis2D(b_axis_x, - clip_value, result_ordinates, parameters); - if (count > 0) { - split_count = m_shape.splitSegment(vertex, - parameters, count); - } else { - assert (count == 0);// might be -1 when the segment - // is almost parallel to the - // clip lane. Just to see this - // happens. - split_count = 0; - } - - // add +1 to ensure we check the original segment if no - // split produced due to degeneracy. - // Also +1 is necessary to check the last segment of the - // split - split_count += 1;// split_count will never be 0 after - // this if-block. - - int split_vert = vertex; - int next_split_vert = m_shape.getNextVertex(split_vert); - for (int i = 0; i < split_count; i++) { - m_shape.getXY(split_vert, pt_1); - m_shape.getXY(next_split_vert, pt_2); - - Segment sub_seg = m_shape.getSegment(split_vert); - if (sub_seg == null) { - sub_seg = line; - sub_seg.setStartXY(pt_1); - sub_seg.setEndXY(pt_2); - } - - sub_seg.queryEnvelope2D(sub_seg_env); - int sub_segment_plane_intersection_status = checkSegmentIntersection_( - sub_seg_env, iclip_plane, clip_value); - if (sub_segment_plane_intersection_status == -1) { - // subsegment is intertsecting the plane. We - // need to snap one of the endpoints to ensure - // no intersection. - // TODO: ensure this works for curves. For - // curves we have to adjust the curve shape. - if (!b_axis_x) { - assert ((pt_1.x < clip_value && pt_2.x > clip_value) || (pt_1.x > clip_value && pt_2.x < clip_value)); - double d_1 = Math.abs(pt_1.x - clip_value); - double d_2 = Math.abs(pt_2.x - clip_value); - if (d_1 < d_2) { - pt_1.x = clip_value; - m_shape.setXY(split_vert, pt_1); - } else { - pt_2.x = clip_value; - m_shape.setXY(next_split_vert, pt_2); - } - } else { - assert ((pt_1.y < clip_value && pt_2.y > clip_value) || (pt_1.y > clip_value && pt_2.y < clip_value)); - double d_1 = Math.abs(pt_1.y - clip_value); - double d_2 = Math.abs(pt_2.y - clip_value); - if (d_1 < d_2) { - pt_1.y = clip_value; - m_shape.setXY(split_vert, pt_1); - } else { - pt_2.y = clip_value; - m_shape.setXY(next_split_vert, pt_2); - } - } - - // after the endpoint has been adjusted, recheck - // the segment. - sub_seg = m_shape.getSegment(split_vert); - if (sub_seg == null) { - sub_seg = line; - sub_seg.setStartXY(pt_1); - sub_seg.setEndXY(pt_2); - } - sub_seg.queryEnvelope2D(sub_seg_env); - sub_segment_plane_intersection_status = checkSegmentIntersection_( - sub_seg_env, iclip_plane, clip_value); - } - - assert (sub_segment_plane_intersection_status != -1); - - int old_inside = inside; - inside = sub_segment_plane_intersection_status; - if (firstinside == -1) - firstinside = inside; - - // add connections along the clipping plane line - if (old_inside == 0 && inside == 1) { - // going from outside to inside. Do nothing - } else if (old_inside == 1 && inside == 0) { - // going from inside to outside - } else if (old_inside == 0 && inside == 0) { - // staying outside - // remember the start point of the outside - // segment to be deleted. - delete_candidates.add(split_vert); // is a - // candidate - // to be - // deleted - } - - if (inside == 1) { - b_all_outside = false; - } - - split_vert = next_split_vert; - next_vertex = split_vert; - next_split_vert = m_shape - .getNextVertex(next_split_vert); - } - } - - if (split_count == 0) { - assert (seg_plane_intersection_status != -1);// cannot - // happen. - int old_inside = inside; - inside = seg_plane_intersection_status; - if (firstinside == -1) - firstinside = inside; - - if (old_inside == 0 && inside == 1) { - // going from outside to inside. - } else if (old_inside == 1 && inside == 0) { - // going from inside to outside - } else if (old_inside == 0 && inside == 0) { - // remember the start point of the outside segment - // to be deleted. - delete_candidates.add(vertex); // is a candidate to - // be deleted - } - - if (inside == 1) { - b_all_outside = false; - } - - next_vertex = m_shape.getNextVertex(vertex); - } - vertex = next_vertex; - } while (vertex != first); - - if (firstinside == 0 && inside == 0) {// first vertex need to be - // deleted. - delete_candidates.add(first); // is a candidate to be - // deleted - } - - for (int i = 0, n = delete_candidates.size(); i < n; i++) { - int delete_vert = delete_candidates.get(i); - m_shape.removeVertex(delete_vert, false); - } - delete_candidates.clear(false); - if (m_shape.getPathSize(path) < 3) { - path = m_shape.removePath(path); - } else { - path = m_shape.getNextPath(path); - } - } - } - - if (b_all_outside) - return (MultiPath) polygon_in.createInstance(); - - // After the clipping, we could have produced unwanted segment overlaps - // along the clipping envelope boundary. - // Detect and resolve that case if possible. - resolveBoundaryOverlaps_(); - if (densify_dist > 0) - densifyAlongClipExtent_(densify_dist); - - return (MultiPath) m_shape.getGeometry(m_geometry); - } - - MultiPath clipPolyline_(Polyline polyline_in, double tolerance) { - // Forward decl for java port - Envelope2D seg_env = new Envelope2D(); - Envelope2D sub_seg_env = new Envelope2D(); - double[] result_ordinates = new double[9]; - double[] parameters = new double[9]; - SegmentBuffer sub_segment_buffer = new SegmentBuffer(); - MultiPath result_poly = polyline_in; - Envelope2D orig_env2D = new Envelope2D(); - polyline_in.queryLooseEnvelope(orig_env2D); - for (int iclip_plane = 0; iclip_plane < 4; iclip_plane++) { - boolean b_intersects_plane = false; - boolean b_axis_x = (iclip_plane & 1) != 0; - double clip_value = 0; - switch (iclip_plane) { - case 0: - clip_value = m_extent.xmin; - b_intersects_plane = orig_env2D.xmin <= clip_value - && orig_env2D.xmax >= clip_value; - assert (b_intersects_plane || clip_value < orig_env2D.xmin); - break; - case 1: - clip_value = m_extent.ymin; - b_intersects_plane = orig_env2D.ymin <= clip_value - && orig_env2D.ymax >= clip_value; - assert (b_intersects_plane || clip_value < orig_env2D.ymin); - break; - case 2: - clip_value = m_extent.xmax; - b_intersects_plane = orig_env2D.xmin <= clip_value - && orig_env2D.xmax >= clip_value; - assert (b_intersects_plane || clip_value > orig_env2D.xmax); - break; - case 3: - clip_value = m_extent.ymax; - b_intersects_plane = orig_env2D.ymin <= clip_value - && orig_env2D.ymax >= clip_value; - assert (b_intersects_plane || clip_value > orig_env2D.ymax); - break; - } - - if (!b_intersects_plane) - continue;// Optimize for common case when only few sides of the - // clipper envelope intersect the geometry. - - MultiPath src_poly = result_poly; - result_poly = (MultiPath) polyline_in.createInstance(); - - MultiPathImpl mp_impl_src = (MultiPathImpl) src_poly._getImpl(); - SegmentIteratorImpl seg_iter = mp_impl_src.querySegmentIterator(); - seg_iter.resetToFirstPath(); - Point2D pt_prev; - Point2D pt = new Point2D(); - while (seg_iter.nextPath()) { - int inside = -1; - boolean b_start_new_path = true; - while (seg_iter.hasNextSegment()) { - Segment segment = seg_iter.nextSegment(); - segment.queryEnvelope2D(seg_env); - int seg_plane_intersection_status = checkSegmentIntersection_( - seg_env, iclip_plane, clip_value); - if (seg_plane_intersection_status == -1) // intersects plane - { - int count = segment.intersectionWithAxis2D(b_axis_x, - clip_value, result_ordinates, parameters); - if (count > 0) { - double t0 = 0.0; - pt_prev = segment.getStartXY(); - for (int i = 0; i <= count; i++) { - double t = i < count ? parameters[i] : 1.0; - if (t0 == t) - continue; - - segment.cut(t0, t, sub_segment_buffer); - Segment sub_seg = sub_segment_buffer.get(); - sub_seg.setStartXY(pt_prev); - if (i < count) {// snap to plane - if (b_axis_x) { - pt.x = result_ordinates[i]; - pt.y = clip_value; - } else { - pt.x = clip_value; - pt.y = result_ordinates[i]; - } - sub_seg.setEndXY(pt); - } - - sub_seg.queryEnvelope2D(sub_seg_env); - int sub_segment_plane_intersection_status = checkSegmentIntersection_( - sub_seg_env, iclip_plane, clip_value); - - if (sub_segment_plane_intersection_status == -1) { - // subsegment is intertsecting the plane. We - // need to snap one of the endpoints to - // ensure no intersection. - // TODO: ensure this works for curves. For - // curves we have to adjust the curve shape. - Point2D pt_1 = sub_seg.getStartXY(); - Point2D pt_2 = sub_seg.getEndXY(); - if (!b_axis_x) { - assert ((pt_1.x < clip_value && pt_2.x > clip_value) || (pt_1.x > clip_value && pt_2.x < clip_value)); - double d_1 = Math.abs(pt_1.x - - clip_value); - double d_2 = Math.abs(pt_2.x - - clip_value); - if (d_1 < d_2) { - pt_1.x = clip_value; - sub_seg.setStartXY(pt_1); - } else { - pt_2.x = clip_value; - sub_seg.setEndXY(pt_2); - } - } else { - assert ((pt_1.y < clip_value && pt_2.y > clip_value) || (pt_1.y > clip_value && pt_2.y < clip_value)); - double d_1 = Math.abs(pt_1.y - - clip_value); - double d_2 = Math.abs(pt_2.y - - clip_value); - if (d_1 < d_2) { - pt_1.y = clip_value; - sub_seg.setStartXY(pt_1); - } else { - pt_2.y = clip_value; - sub_seg.setEndXY(pt_2); - } - } - - // after the endpoint has been adjusted, - // recheck the segment. - sub_seg.queryEnvelope2D(sub_seg_env); - sub_segment_plane_intersection_status = checkSegmentIntersection_( - sub_seg_env, iclip_plane, - clip_value); - } - - assert (sub_segment_plane_intersection_status != -1); - - pt_prev = sub_seg.getEndXY(); - t0 = t; - - inside = sub_segment_plane_intersection_status; - if (inside == 1) { - result_poly.addSegment(sub_seg, - b_start_new_path); - b_start_new_path = false; - } else - b_start_new_path = true; - } - } - } else { - inside = seg_plane_intersection_status; - if (inside == 1) { - result_poly.addSegment(segment, b_start_new_path); - b_start_new_path = false; - } else - b_start_new_path = true; - } - } - } - } - - return result_poly; - } - - void resolveBoundaryOverlaps_() { - m_vertices_on_extent_index = -1; - splitSegments_(false, m_extent.xmin); - splitSegments_(false, m_extent.xmax); - splitSegments_(true, m_extent.ymin); - splitSegments_(true, m_extent.ymax); - - m_vertices_on_extent.resize(0); - m_vertices_on_extent.reserve(100); - m_vertices_on_extent_index = m_shape.createUserIndex(); - - Point2D pt = new Point2D(); - for (int path = m_shape.getFirstPath(m_geometry); path != -1; path = m_shape - .getNextPath(path)) { - int vertex = m_shape.getFirstVertex(path); - for (int ivert = 0, nvert = m_shape.getPathSize(path); ivert < nvert; ivert++, vertex = m_shape - .getNextVertex(vertex)) { - m_shape.getXY(vertex, pt); - if (m_extent.xmin == pt.x || m_extent.xmax == pt.x - || m_extent.ymin == pt.y || m_extent.ymax == pt.y) { - m_shape.setUserIndex(vertex, m_vertices_on_extent_index, - m_vertices_on_extent.size()); - m_vertices_on_extent.add(vertex); - } - } - } - // dbg_check_path_first_(); - resolveOverlaps_(false, m_extent.xmin); - // dbg_check_path_first_(); - resolveOverlaps_(false, m_extent.xmax); - // dbg_check_path_first_(); - resolveOverlaps_(true, m_extent.ymin); - // dbg_check_path_first_(); - resolveOverlaps_(true, m_extent.ymax); - fixPaths_(); - } - - void densifyAlongClipExtent_(double densify_dist) { - assert (densify_dist > 0); - Point2D pt_1 = new Point2D(); - Point2D pt_2 = new Point2D(); - double[] split_scalars = new double[2048]; - for (int path = m_shape.getFirstPath(m_geometry); path != -1; path = m_shape - .getNextPath(path)) { - int first_vertex = m_shape.getFirstVertex(path); - int vertex = first_vertex; - do { - int next_vertex = m_shape.getNextVertex(vertex); - m_shape.getXY(vertex, pt_1); - int b_densify_x = -1; - if (pt_1.x == m_extent.xmin) { - m_shape.getXY(next_vertex, pt_2); - if (pt_2.x == m_extent.xmin) { - b_densify_x = 1; - } - } else if (pt_1.x == m_extent.xmax) { - m_shape.getXY(next_vertex, pt_2); - if (pt_2.x == m_extent.xmax) { - b_densify_x = 1; - } - } - - if (pt_1.y == m_extent.ymin) { - m_shape.getXY(next_vertex, pt_2); - if (pt_2.y == m_extent.ymin) { - b_densify_x = 0; - } - } else if (pt_1.y == m_extent.ymax) { - m_shape.getXY(next_vertex, pt_2); - if (pt_2.y == m_extent.ymax) { - b_densify_x = 0; - } - } - - if (b_densify_x == -1) { - vertex = next_vertex; - continue; - } - - double len = Point2D.distance(pt_1, pt_2); - int num = (int) Math.min(Math.ceil(len / densify_dist), 2048.0); - if (num <= 1) { - vertex = next_vertex; - continue; - } - - for (int i = 1; i < num; i++) { - split_scalars[i - 1] = (1.0 * i) / num; - } - - int actual_splits = m_shape.splitSegment(vertex, split_scalars, - num - 1); - assert (actual_splits == num - 1); - vertex = next_vertex; - } while (vertex != first_vertex); - } - } - - void splitSegments_(boolean b_axis_x, double clip_value) { - // After the clipping, we could have produced unwanted segment overlaps - // along the clipping envelope boundary. - // Detect and resolve that case if possible. - int usage_index = m_shape.createUserIndex(); - Point2D pt = new Point2D(); - AttributeStreamOfInt32 sorted_vertices = new AttributeStreamOfInt32(0); - sorted_vertices.reserve(100); - for (int path = m_shape.getFirstPath(m_geometry); path != -1; path = m_shape - .getNextPath(path)) { - int vertex = m_shape.getFirstVertex(path); - for (int ivert = 0, nvert = m_shape.getPathSize(path); ivert < nvert; ivert++) { - int next_vertex = m_shape.getNextVertex(vertex); - m_shape.getXY(vertex, pt); - if (b_axis_x ? pt.y == clip_value : pt.x == clip_value) { - m_shape.getXY(next_vertex, pt); - if (b_axis_x ? pt.y == clip_value : pt.x == clip_value) { - if (m_shape.getUserIndex(vertex, usage_index) != 1) { - sorted_vertices.add(vertex); - m_shape.setUserIndex(vertex, usage_index, 1); - } - - if (m_shape.getUserIndex(next_vertex, usage_index) != 1) { - sorted_vertices.add(next_vertex); - m_shape.setUserIndex(next_vertex, usage_index, 1); - } - } - } - vertex = next_vertex; - } - } - - m_shape.removeUserIndex(usage_index); - if (sorted_vertices.size() < 3) { - return; - } - - sorted_vertices.Sort(0, sorted_vertices.size(), - new ClipperVertexComparer(this)); - - Point2D pt_tmp = new Point2D(); // forward declare for java port - // optimization - Point2D pt_0 = new Point2D(); - Point2D pt_1 = new Point2D(); - pt_0.setNaN(); - int index_0 = -1; - AttributeStreamOfInt32 active_intervals = new AttributeStreamOfInt32(0); - AttributeStreamOfInt32 new_active_intervals = new AttributeStreamOfInt32( - 0); - - int node1 = m_shape.createUserIndex(); - int node2 = m_shape.createUserIndex(); - for (int index = 0, n = sorted_vertices.size(); index < n; index++) { - int vert = sorted_vertices.get(index); - m_shape.getXY(vert, pt); - if (!pt.isEqual(pt_0)) { - if (index_0 == -1) { - index_0 = index; - pt_0.setCoords(pt); - continue; - } - - // add new intervals, that started at pt_0 - for (int i = index_0; i < index; i++) { - int v = sorted_vertices.get(i); - int nextv = m_shape.getNextVertex(v); - int prevv = m_shape.getPrevVertex(v); - boolean bAdded = false; - if (compareVertices_(v, nextv) < 0) { - m_shape.getXY(nextv, pt_tmp); - if (b_axis_x ? pt_tmp.y == clip_value - : pt_tmp.x == clip_value) { - active_intervals.add(v); - bAdded = true; - m_shape.setUserIndex(v, node2, 1); - } - } - if (compareVertices_(v, prevv) < 0) { - m_shape.getXY(prevv, pt_tmp); - if (b_axis_x ? pt_tmp.y == clip_value - : pt_tmp.x == clip_value) { - if (!bAdded) - active_intervals.add(v); - m_shape.setUserIndex(v, node1, 1); - } - } - } - - // Split all active intervals at new point - for (int ia = 0, na = active_intervals.size(); ia < na; ia++) { - int v = active_intervals.get(ia); - int n_1 = m_shape.getUserIndex(v, node1); - int n_2 = m_shape.getUserIndex(v, node2); - if (n_1 == 1) { - int prevv = m_shape.getPrevVertex(v); - m_shape.getXY(prevv, pt_1); - double[] t = new double[1]; - t[0] = 0; - if (!pt_1.isEqual(pt)) {// Split the active segment - double active_segment_length = Point2D - .distance(pt_0, pt_1); - t[0] = Point2D.distance(pt_1, pt) - / active_segment_length; - assert (t[0] >= 0 && t[0] <= 1.0); - if (t[0] == 0) - t[0] = NumberUtils.doubleEps();// some - // roundoff - // issue. - // split - // anyway. - else if (t[0] == 1.0) { - t[0] = 1.0 - NumberUtils.doubleEps();// some - // roundoff - // issue. - // split - // anyway. - assert (t[0] != 1.0); - } - - int split_count = m_shape.splitSegment(prevv, - t, 1); - assert (split_count > 0); - int v_1 = m_shape.getPrevVertex(v); - m_shape.setXY(v_1, pt); - new_active_intervals.add(v_1); - m_shape.setUserIndex(v_1, node1, 1); - m_shape.setUserIndex(v_1, node2, -1); - } else { - // The active segment ends at the current point. - // We skip it, and it goes away. - } - } - if (n_2 == 1) { - int nextv = m_shape.getNextVertex(v); - m_shape.getXY(nextv, pt_1); - double[] t = new double[1]; - t[0] = 0; - if (!pt_1.isEqual(pt)) { - double active_segment_length = Point2D - .distance(pt_0, pt_1); - t[0] = Point2D.distance(pt_0, pt) - / active_segment_length; - assert (t[0] >= 0 && t[0] <= 1.0); - if (t[0] == 0) - t[0] = NumberUtils.doubleEps();// some - // roundoff - // issue. - // split - // anyway. - else if (t[0] == 1.0) { - t[0] = 1.0 - NumberUtils.doubleEps();// some - // roundoff - // issue. - // split - // anyway. - assert (t[0] != 1.0); - } - - int split_count = m_shape.splitSegment(v, t, 1); - assert (split_count > 0); - int v_1 = m_shape.getNextVertex(v); - m_shape.setXY(v_1, pt); - new_active_intervals.add(v_1); - m_shape.setUserIndex(v_1, node1, -1); - m_shape.setUserIndex(v_1, node2, 1); - } - } - } - - AttributeStreamOfInt32 tmp = active_intervals; - active_intervals = new_active_intervals; - new_active_intervals = tmp; - new_active_intervals.clear(false); - - index_0 = index; - pt_0.setCoords(pt); - } - } - - m_shape.removeUserIndex(node1); - m_shape.removeUserIndex(node2); - } - - void resolveOverlaps_(boolean b_axis_x, double clip_value) { - // Along the envelope boundary there could be overlapped segments. - // Example, exterior ring with a hole is cut with a line, that - // passes through the center of the hole. - // Detect pairs of opposite overlapping segments and get rid of them - Point2D pt = new Point2D(); - AttributeStreamOfInt32 sorted_vertices = new AttributeStreamOfInt32(0); - sorted_vertices.reserve(100); - int sorted_index = m_shape.createUserIndex(); - // DEBUGPRINTF(L"ee\n"); - for (int ivert = 0, nvert = m_vertices_on_extent.size(); ivert < nvert; ivert++) { - int vertex = m_vertices_on_extent.get(ivert); - if (vertex == -1) - continue; - - int next_vertex = m_shape.getNextVertex(vertex); - m_shape.getXY(vertex, pt); - // DEBUGPRINTF(L"%f\t%f\n", pt.x, pt.y); - if (b_axis_x ? pt.y == clip_value : pt.x == clip_value) { - m_shape.getXY(next_vertex, pt); - if (b_axis_x ? pt.y == clip_value : pt.x == clip_value) { - assert (m_shape.getUserIndex(next_vertex, - m_vertices_on_extent_index) != -1); - if (m_shape.getUserIndex(vertex, sorted_index) != -2) { - sorted_vertices.add(vertex);// remember the vertex. The - // attached segment belongs - // to the given clip plane. - m_shape.setUserIndex(vertex, sorted_index, -2); - } - - if (m_shape.getUserIndex(next_vertex, sorted_index) != -2) { - sorted_vertices.add(next_vertex); - m_shape.setUserIndex(next_vertex, sorted_index, -2); - } - } - } - } - - if (sorted_vertices.size() == 0) { - m_shape.removeUserIndex(sorted_index); - return; - } - - sorted_vertices.Sort(0, sorted_vertices.size(), - new ClipperVertexComparer(this)); - // std::sort(sorted_vertices.get_ptr(), sorted_vertices.get_ptr() + - // sorted_vertices.size(), Clipper_vertex_comparer(this)); - - // DEBUGPRINTF(L"**\n"); - for (int index = 0, n = sorted_vertices.size(); index < n; index++) { - int vert = sorted_vertices.get(index); - m_shape.setUserIndex(vert, sorted_index, index); - // Point_2D pt; - // m_shape.get_xy(vert, pt); - // DEBUGPRINTF(L"%f\t%f\t%d\n", pt.x, pt.y, vert); - } - - Point2D pt_tmp = new Point2D(); - Point2D pt_0 = new Point2D(); - pt_0.setNaN(); - int index_0 = -1; - for (int index = 0, n = sorted_vertices.size(); index < n; index++) { - int vert = sorted_vertices.get(index); - if (vert == -1) - continue; - - m_shape.getXY(vert, pt); - if (!pt.isEqual(pt_0)) { - if (index_0 != -1) { - while (true) { - boolean b_overlap_resolved = false; - int index_to = index - index_0 > 1 ? index - 1 : index; - for (int i = index_0; i < index_to; i++) { - int v = sorted_vertices.get(i); - if (v == -1) - continue; - int nextv = -1; - int nv = m_shape.getNextVertex(v); - if (compareVertices_(v, nv) < 0) { - m_shape.getXY(nv, pt_tmp); - if (b_axis_x ? pt_tmp.y == clip_value - : pt_tmp.x == clip_value) - nextv = nv; - } - int prevv = -1; - int pv = m_shape.getPrevVertex(v); - if (compareVertices_(v, pv) < 0) { - m_shape.getXY(pv, pt_tmp); - if (b_axis_x ? pt_tmp.y == clip_value - : pt_tmp.x == clip_value) - prevv = pv; - } - - if (nextv != -1 && prevv != -1) { - // we have a cusp here. remove the vertex. - beforeRemoveVertex_(v, sorted_vertices, - sorted_index); - m_shape.removeVertex(v, false); - beforeRemoveVertex_(nextv, sorted_vertices, - sorted_index); - m_shape.removeVertex(nextv, false); - b_overlap_resolved = true; - continue; - } - - if (nextv == -1 && prevv == -1) - continue; - - for (int j = i + 1; j < index; j++) { - int v_1 = sorted_vertices.get(j); - if (v_1 == -1) - continue; - int nv1 = m_shape.getNextVertex(v_1); - int nextv1 = -1; - if (compareVertices_(v_1, nv1) < 0) { - m_shape.getXY(nv1, pt_tmp); - if (b_axis_x ? pt_tmp.y == clip_value - : pt_tmp.x == clip_value) - nextv1 = nv1; - } - - int pv1 = m_shape.getPrevVertex(v_1); - int prevv_1 = -1; - if (compareVertices_(v_1, pv1) < 0) { - m_shape.getXY(pv1, pt_tmp); - if (b_axis_x ? pt_tmp.y == clip_value - : pt_tmp.x == clip_value) - prevv_1 = pv1; - } - if (nextv1 != -1 && prevv_1 != -1) { - // we have a cusp here. remove the vertex. - beforeRemoveVertex_(v_1, sorted_vertices, - sorted_index); - m_shape.removeVertex(v_1, false); - beforeRemoveVertex_(nextv1, - sorted_vertices, sorted_index); - m_shape.removeVertex(nextv1, false); - b_overlap_resolved = true; - break; - } - if (nextv != -1 && prevv_1 != -1) { - removeOverlap_(sorted_vertices, v, nextv, - v_1, prevv_1, sorted_index); - b_overlap_resolved = true; - break; - } else if (prevv != -1 && nextv1 != -1) { - removeOverlap_(sorted_vertices, v_1, - nextv1, v, prevv, sorted_index); - b_overlap_resolved = true; - break; - } - } - - if (b_overlap_resolved) - break; - } - - if (!b_overlap_resolved) - break; - } - } - - index_0 = index; - pt_0.setCoords(pt); - } - } - - m_shape.removeUserIndex(sorted_index); - } - - void beforeRemoveVertex_(int v_1, AttributeStreamOfInt32 sorted_vertices, - int sorted_index) { - int ind = m_shape.getUserIndex(v_1, sorted_index); - sorted_vertices.set(ind, -1); - ind = m_shape.getUserIndex(v_1, m_vertices_on_extent_index); - m_vertices_on_extent.set(ind, -1); - int path = m_shape.getPathFromVertex(v_1); - if (path != -1) { - int first = m_shape.getFirstVertex(path); - if (first == v_1) { - m_shape.setFirstVertex_(path, -1); - m_shape.setLastVertex_(path, -1); - } - } - } - - void removeOverlap_(AttributeStreamOfInt32 sorted_vertices, int v, - int nextv, int v_1, int prevv_1, int sorted_index) { - assert (m_shape.isEqualXY(v, v_1)); - assert (m_shape.isEqualXY(nextv, prevv_1)); - assert (m_shape.getNextVertex(v) == nextv); - assert (m_shape.getNextVertex(prevv_1) == v_1); - m_shape.setNextVertex_(v, v_1); - m_shape.setPrevVertex_(v_1, v); - m_shape.setPrevVertex_(nextv, prevv_1); - m_shape.setNextVertex_(prevv_1, nextv); - - beforeRemoveVertex_(v_1, sorted_vertices, sorted_index); - m_shape.removeVertexInternal_(v_1, false); - beforeRemoveVertex_(prevv_1, sorted_vertices, sorted_index); - m_shape.removeVertexInternal_(prevv_1, true); - } - - void fixPaths_() { - for (int ivert = 0, nvert = m_vertices_on_extent.size(); ivert < nvert; ivert++) { - int vertex = m_vertices_on_extent.get(ivert); - if (vertex != -1) - m_shape.setPathToVertex_(vertex, -1); - } - - int path_count = 0; - int geometry_size = 0; - for (int path = m_shape.getFirstPath(m_geometry); path != -1; ) { - int first = m_shape.getFirstVertex(path); - if (first == -1 || path != m_shape.getPathFromVertex(first)) { // The - // path's - // first - // vertex - // has - // been - // deleted. - // Or - // the - // path - // first - // vertex - // is - // now - // part - // of - // another - // path. - // We - // have - // to - // delete - // such - // path - // object. - int p = path; - path = m_shape.getNextPath(path); - m_shape.setFirstVertex_(p, -1); - m_shape.removePathOnly_(p); - continue; - } - assert (path == m_shape.getPathFromVertex(first)); - int vertex = first; - int path_size = 0; - do { - m_shape.setPathToVertex_(vertex, path); - path_size++; - vertex = m_shape.getNextVertex(vertex); - } while (vertex != first); - - if (path_size <= 2) { - int ind = m_shape.getUserIndex(first, - m_vertices_on_extent_index); - m_vertices_on_extent.set(ind, -1); - int nv = m_shape.removeVertex(first, false); - if (path_size == 2) { - ind = m_shape.getUserIndex(nv, m_vertices_on_extent_index); - m_vertices_on_extent.set(ind, -1); - m_shape.removeVertex(nv, false); - } - int p = path; - path = m_shape.getNextPath(path); - m_shape.setFirstVertex_(p, -1); - m_shape.removePathOnly_(p); - continue; - } - - m_shape.setRingAreaValid_(path, false); - m_shape.setLastVertex_(path, m_shape.getPrevVertex(first)); - m_shape.setPathSize_(path, path_size); - geometry_size += path_size; - path_count++; - path = m_shape.getNextPath(path); - } - - for (int ivert = 0, nvert = m_vertices_on_extent.size(); ivert < nvert; ivert++) { - int vertex = m_vertices_on_extent.get(ivert); - if (vertex == -1) - continue; - int path = m_shape.getPathFromVertex(vertex); - if (path != -1) - continue; - - path = m_shape.insertPath(m_geometry, -1); - int path_size = 0; - int first = vertex; - do { - m_shape.setPathToVertex_(vertex, path); - path_size++; - vertex = m_shape.getNextVertex(vertex); - } while (vertex != first); - - if (path_size <= 2) { - int ind = m_shape.getUserIndex(first, - m_vertices_on_extent_index); - m_vertices_on_extent.set(ind, -1); - int nv = m_shape.removeVertex(first, false); - if (path_size == 2) { - ind = m_shape.getUserIndex(nv, m_vertices_on_extent_index); - if (ind >= 0) - m_vertices_on_extent.set(ind, -1); - else { - // this vertex is not on the extent. - } - m_shape.removeVertex(nv, false); - } - - int p = path; - path = m_shape.getNextPath(path); - m_shape.setFirstVertex_(p, -1); - m_shape.removePathOnly_(p); - continue; - } - - m_shape.setClosedPath(path, true); - m_shape.setPathSize_(path, path_size); - m_shape.setFirstVertex_(path, first); - m_shape.setLastVertex_(path, m_shape.getPrevVertex(first)); - m_shape.setRingAreaValid_(path, false); - geometry_size += path_size; - path_count++; - } - - m_shape.setGeometryPathCount_(m_geometry, path_count); - m_shape.setGeometryVertexCount_(m_geometry, geometry_size); - - int total_point_count = 0; - for (int geometry = m_shape.getFirstGeometry(); geometry != -1; geometry = m_shape - .getNextGeometry(geometry)) { - total_point_count += m_shape.getPointCount(geometry); - } - - m_shape.setTotalPointCount_(total_point_count); - } - - static Geometry clipMultiPath_(MultiPath multipath, Envelope2D extent, - double tolerance, double densify_dist) { - Clipper clipper = new Clipper(extent); - return clipper.clipMultiPath2_(multipath, tolerance, densify_dist); - } - - Clipper(Envelope2D extent) { - m_extent = extent; - m_shape = new EditShape(); - m_vertices_on_extent = new AttributeStreamOfInt32(0); - } - - // static std::shared_ptr create_polygon_from_polyline(const - // std::shared_ptr& polyline, const Envelope_2D& env_2D, bool - // add_envelope, double tolerance, double densify_dist, int - // corner_is_inside); - static Geometry clip(Geometry geometry, Envelope2D extent, - double tolerance, double densify_dist) { - if (geometry.isEmpty()) - return geometry; - - if (extent.isEmpty()) - return geometry.createInstance(); // return an empty geometry - - int geomtype = geometry.getType().value(); - - // Test firstly the simplest geometry types point and envelope. - // After that we'll check the envelope intersection for the optimization - if (geomtype == Geometry.Type.Point.value()) { - Point2D pt = ((Point) geometry).getXY(); - if (extent.contains(pt)) - return geometry; - else - return geometry.createInstance(); // return an empty geometry - } else if (geomtype == Geometry.Type.Envelope.value()) { - Envelope2D env = new Envelope2D(); - geometry.queryEnvelope2D(env); - if (env.intersect(extent)) { - Envelope result_env = new Envelope(); - geometry.copyTo(result_env); - result_env.setEnvelope2D(env); - return result_env; - } else - return geometry.createInstance(); // return an empty geometry - } - - // Test the geometry envelope - Envelope2D env_2D = new Envelope2D(); - geometry.queryLooseEnvelope2D(env_2D); - if (extent.contains(env_2D)) - return geometry;// completely inside of bounds - if (!extent.isIntersecting(env_2D)) - return geometry.createInstance();// outside of bounds. return empty - // geometry. - - MultiVertexGeometryImpl impl = (MultiVertexGeometryImpl) geometry - ._getImpl(); - GeometryAccelerators accel = impl._getAccelerators(); - if (accel != null) { - RasterizedGeometry2D rgeom = accel.getRasterizedGeometry(); - if (rgeom != null) { - RasterizedGeometry2D.HitType hit = rgeom - .queryEnvelopeInGeometry(extent); - if (hit == RasterizedGeometry2D.HitType.Inside) { - if (geomtype != Geometry.Type.Polygon.value()) - throw GeometryException.GeometryInternalError(); - - Polygon poly = new Polygon(geometry.getDescription()); - poly.addEnvelope(extent, false); - return poly; - } else if (hit == RasterizedGeometry2D.HitType.Outside) { - return geometry.createInstance();// outside of bounds. - // return empty - // geometry. - } - } - } - - switch (geomtype) { - case Geometry.GeometryType.MultiPoint: { - MultiPoint multi_point = (MultiPoint) geometry; - MultiPoint multi_point_out = null; - int npoints = multi_point.getPointCount(); - AttributeStreamOfDbl xy = (AttributeStreamOfDbl) ((MultiPointImpl) multi_point - ._getImpl()) - .getAttributeStreamRef(VertexDescription.Semantics.POSITION); - // create the new geometry only if there are points that has been - // clipped out. - // If all vertices are inside of the envelope, it returns the input - // multipoint. - int ipoints0 = 0; - for (int ipoints = 0; ipoints < npoints; ipoints++) { - Point2D pt = new Point2D(); - xy.read(2 * ipoints, pt); - - if (!extent.contains(pt)) {// vertex is outside of the envelope - if (ipoints0 == 0) - multi_point_out = (MultiPoint) multi_point - .createInstance(); - - if (ipoints0 < ipoints) - multi_point_out.add(multi_point, ipoints0, ipoints); - - ipoints0 = ipoints + 1;// ipoints0 contains index of vertex - // right after the last clipped out - // vertex. - } - } - - // add the rest of the batch to the result multipoint (only if - // something has been already clipped out) - if (ipoints0 > 0) - multi_point_out.add(multi_point, ipoints0, npoints); - - if (ipoints0 == 0) - return multi_point;// everything is inside, so return the input - // geometry - else - return multi_point_out;// clipping has happend, return the - // clipped geometry - } - case Geometry.GeometryType.Polygon: - case Geometry.GeometryType.Polyline: - return clipMultiPath_((MultiPath) geometry, extent, tolerance, - densify_dist); - default: - assert (false); - throw GeometryException.GeometryInternalError(); - } - } - - int compareVertices_(int v_1, int v_2) { - Point2D pt_1 = new Point2D(); - m_shape.getXY(v_1, pt_1); - Point2D pt_2 = new Point2D(); - m_shape.getXY(v_2, pt_2); - int res = pt_1.compare(pt_2); - return res; - } - - static final class ClipperVertexComparer extends - AttributeStreamOfInt32.IntComparator { - Clipper m_clipper; - - ClipperVertexComparer(Clipper clipper) { - m_clipper = clipper; - } - - @Override - public int compare(int v1, int v2) { - return m_clipper.compareVertices_(v1, v2); - } - - } + Envelope2D m_extent; + EditShape m_shape; + int m_geometry; + int m_vertices_on_extent_index; + AttributeStreamOfInt32 m_vertices_on_extent; + + int checkSegmentIntersection_(Envelope2D seg_env, int side, + double clip_value) { + switch (side) { + case 0: + if (seg_env.xmin < clip_value && seg_env.xmax <= clip_value) { + return 0; // outside (or on the border) + } else if (seg_env.xmin >= clip_value) { + return 1;// inside + } else + return -1; // intersects + case 1: + if (seg_env.ymin < clip_value && seg_env.ymax <= clip_value) { + return 0; + } else if (seg_env.ymin >= clip_value) { + return 1; + } else + return -1; + case 2: + if (seg_env.xmin >= clip_value && seg_env.xmax > clip_value) { + return 0; + } else if (seg_env.xmax <= clip_value) { + return 1; + } else + return -1; + case 3: + if (seg_env.ymin >= clip_value && seg_env.ymax > clip_value) { + return 0; + } else if (seg_env.ymax <= clip_value) { + return 1; + } else + return -1; + } + assert (false);// cannot be here + return 0; + } + + MultiPath clipMultiPath2_(MultiPath multi_path_in, double tolerance, + double densify_dist) { + boolean b_is_polygon = multi_path_in.getType() == Geometry.Type.Polygon; + if (b_is_polygon) + return clipPolygon2_((Polygon) multi_path_in, tolerance, + densify_dist); + else + return clipPolyline_((Polyline) multi_path_in, tolerance); + } + + MultiPath clipPolygon2_(Polygon polygon_in, double tolerance, + double densify_dist) { + // If extent is degenerate, return 0. + if (m_extent.getWidth() == 0 || m_extent.getHeight() == 0) + return (MultiPath) polygon_in.createInstance(); + + Envelope2D orig_env2D = new Envelope2D(); + polygon_in.queryLooseEnvelope(orig_env2D); + + // m_shape = GCNEW Edit_shape(); + m_geometry = m_shape.addGeometry(polygon_in); + + // Forward decl for java port + Envelope2D seg_env = new Envelope2D(); + Envelope2D sub_seg_env = new Envelope2D(); + Point2D pt_1 = new Point2D(); + Point2D pt_2 = new Point2D(); + double[] result_ordinates = new double[9]; + double[] parameters = new double[9]; + SegmentBuffer sub_segment_buffer = new SegmentBuffer(); + Line line = new Line(); + AttributeStreamOfInt32 delete_candidates = new AttributeStreamOfInt32(0); + delete_candidates.reserve(Math.min(100, polygon_in.getPointCount())); + // clip the polygon successively by each plane + boolean b_all_outside = false; + for (int iclip_plane = 0; !b_all_outside && iclip_plane < 4; iclip_plane++) { + boolean b_intersects_plane = false; + boolean b_axis_x = (iclip_plane & 1) != 0; + double clip_value = 0; + switch (iclip_plane) { + case 0: + clip_value = m_extent.xmin; + b_intersects_plane = orig_env2D.xmin <= clip_value + && orig_env2D.xmax >= clip_value; + assert (b_intersects_plane || clip_value < orig_env2D.xmin); + break; + case 1: + clip_value = m_extent.ymin; + b_intersects_plane = orig_env2D.ymin <= clip_value + && orig_env2D.ymax >= clip_value; + assert (b_intersects_plane || clip_value < orig_env2D.ymin); + break; + case 2: + clip_value = m_extent.xmax; + b_intersects_plane = orig_env2D.xmin <= clip_value + && orig_env2D.xmax >= clip_value; + assert (b_intersects_plane || clip_value > orig_env2D.xmax); + break; + case 3: + clip_value = m_extent.ymax; + b_intersects_plane = orig_env2D.ymin <= clip_value + && orig_env2D.ymax >= clip_value; + assert (b_intersects_plane || clip_value > orig_env2D.ymax); + break; + } + + if (!b_intersects_plane) + continue;// Optimize for common case when only few sides of the + // clipper envelope intersect the geometry. + + b_all_outside = true; + for (int path = m_shape.getFirstPath(m_geometry); path != -1; ) { + int inside = -1; + int firstinside = -1; + int first = m_shape.getFirstVertex(path); + int vertex = first; + do { + Segment segment = m_shape.getSegment(vertex); + if (segment == null) { + segment = line; + m_shape.getXY(vertex, pt_1); + segment.setStartXY(pt_1); + m_shape.getXY(m_shape.getNextVertex(vertex), pt_2); + segment.setEndXY(pt_2); + } + segment.queryEnvelope2D(seg_env); + int seg_plane_intersection_status = checkSegmentIntersection_( + seg_env, iclip_plane, clip_value); + int split_count = 0; + int next_vertex = -1; + + if (seg_plane_intersection_status == -1) // intersects plane + { + int count = segment.intersectionWithAxis2D(b_axis_x, + clip_value, result_ordinates, parameters); + if (count > 0) { + split_count = m_shape.splitSegment(vertex, + parameters, count); + } else { + assert (count == 0);// might be -1 when the segment + // is almost parallel to the + // clip lane. Just to see this + // happens. + split_count = 0; + } + + // add +1 to ensure we check the original segment if no + // split produced due to degeneracy. + // Also +1 is necessary to check the last segment of the + // split + split_count += 1;// split_count will never be 0 after + // this if-block. + + int split_vert = vertex; + int next_split_vert = m_shape.getNextVertex(split_vert); + for (int i = 0; i < split_count; i++) { + m_shape.getXY(split_vert, pt_1); + m_shape.getXY(next_split_vert, pt_2); + + Segment sub_seg = m_shape.getSegment(split_vert); + if (sub_seg == null) { + sub_seg = line; + sub_seg.setStartXY(pt_1); + sub_seg.setEndXY(pt_2); + } + + sub_seg.queryEnvelope2D(sub_seg_env); + int sub_segment_plane_intersection_status = checkSegmentIntersection_( + sub_seg_env, iclip_plane, clip_value); + if (sub_segment_plane_intersection_status == -1) { + // subsegment is intertsecting the plane. We + // need to snap one of the endpoints to ensure + // no intersection. + // TODO: ensure this works for curves. For + // curves we have to adjust the curve shape. + if (!b_axis_x) { + assert ((pt_1.x < clip_value && pt_2.x > clip_value) || (pt_1.x > clip_value && pt_2.x < clip_value)); + double d_1 = Math.abs(pt_1.x - clip_value); + double d_2 = Math.abs(pt_2.x - clip_value); + if (d_1 < d_2) { + pt_1.x = clip_value; + m_shape.setXY(split_vert, pt_1); + } else { + pt_2.x = clip_value; + m_shape.setXY(next_split_vert, pt_2); + } + } else { + assert ((pt_1.y < clip_value && pt_2.y > clip_value) || (pt_1.y > clip_value && pt_2.y < clip_value)); + double d_1 = Math.abs(pt_1.y - clip_value); + double d_2 = Math.abs(pt_2.y - clip_value); + if (d_1 < d_2) { + pt_1.y = clip_value; + m_shape.setXY(split_vert, pt_1); + } else { + pt_2.y = clip_value; + m_shape.setXY(next_split_vert, pt_2); + } + } + + // after the endpoint has been adjusted, recheck + // the segment. + sub_seg = m_shape.getSegment(split_vert); + if (sub_seg == null) { + sub_seg = line; + sub_seg.setStartXY(pt_1); + sub_seg.setEndXY(pt_2); + } + sub_seg.queryEnvelope2D(sub_seg_env); + sub_segment_plane_intersection_status = checkSegmentIntersection_( + sub_seg_env, iclip_plane, clip_value); + } + + assert (sub_segment_plane_intersection_status != -1); + + int old_inside = inside; + inside = sub_segment_plane_intersection_status; + if (firstinside == -1) + firstinside = inside; + + // add connections along the clipping plane line + if (old_inside == 0 && inside == 1) { + // going from outside to inside. Do nothing + } else if (old_inside == 1 && inside == 0) { + // going from inside to outside + } else if (old_inside == 0 && inside == 0) { + // staying outside + // remember the start point of the outside + // segment to be deleted. + delete_candidates.add(split_vert); // is a + // candidate + // to be + // deleted + } + + if (inside == 1) { + b_all_outside = false; + } + + split_vert = next_split_vert; + next_vertex = split_vert; + next_split_vert = m_shape + .getNextVertex(next_split_vert); + } + } + + if (split_count == 0) { + assert (seg_plane_intersection_status != -1);// cannot + // happen. + int old_inside = inside; + inside = seg_plane_intersection_status; + if (firstinside == -1) + firstinside = inside; + + if (old_inside == 0 && inside == 1) { + // going from outside to inside. + } else if (old_inside == 1 && inside == 0) { + // going from inside to outside + } else if (old_inside == 0 && inside == 0) { + // remember the start point of the outside segment + // to be deleted. + delete_candidates.add(vertex); // is a candidate to + // be deleted + } + + if (inside == 1) { + b_all_outside = false; + } + + next_vertex = m_shape.getNextVertex(vertex); + } + vertex = next_vertex; + } while (vertex != first); + + if (firstinside == 0 && inside == 0) {// first vertex need to be + // deleted. + delete_candidates.add(first); // is a candidate to be + // deleted + } + + for (int i = 0, n = delete_candidates.size(); i < n; i++) { + int delete_vert = delete_candidates.get(i); + m_shape.removeVertex(delete_vert, false); + } + delete_candidates.clear(false); + if (m_shape.getPathSize(path) < 3) { + path = m_shape.removePath(path); + } else { + path = m_shape.getNextPath(path); + } + } + } + + if (b_all_outside) + return (MultiPath) polygon_in.createInstance(); + + // After the clipping, we could have produced unwanted segment overlaps + // along the clipping envelope boundary. + // Detect and resolve that case if possible. + resolveBoundaryOverlaps_(); + if (densify_dist > 0) + densifyAlongClipExtent_(densify_dist); + + return (MultiPath) m_shape.getGeometry(m_geometry); + } + + MultiPath clipPolyline_(Polyline polyline_in, double tolerance) { + // Forward decl for java port + Envelope2D seg_env = new Envelope2D(); + Envelope2D sub_seg_env = new Envelope2D(); + double[] result_ordinates = new double[9]; + double[] parameters = new double[9]; + SegmentBuffer sub_segment_buffer = new SegmentBuffer(); + MultiPath result_poly = polyline_in; + Envelope2D orig_env2D = new Envelope2D(); + polyline_in.queryLooseEnvelope(orig_env2D); + for (int iclip_plane = 0; iclip_plane < 4; iclip_plane++) { + boolean b_intersects_plane = false; + boolean b_axis_x = (iclip_plane & 1) != 0; + double clip_value = 0; + switch (iclip_plane) { + case 0: + clip_value = m_extent.xmin; + b_intersects_plane = orig_env2D.xmin <= clip_value + && orig_env2D.xmax >= clip_value; + assert (b_intersects_plane || clip_value < orig_env2D.xmin); + break; + case 1: + clip_value = m_extent.ymin; + b_intersects_plane = orig_env2D.ymin <= clip_value + && orig_env2D.ymax >= clip_value; + assert (b_intersects_plane || clip_value < orig_env2D.ymin); + break; + case 2: + clip_value = m_extent.xmax; + b_intersects_plane = orig_env2D.xmin <= clip_value + && orig_env2D.xmax >= clip_value; + assert (b_intersects_plane || clip_value > orig_env2D.xmax); + break; + case 3: + clip_value = m_extent.ymax; + b_intersects_plane = orig_env2D.ymin <= clip_value + && orig_env2D.ymax >= clip_value; + assert (b_intersects_plane || clip_value > orig_env2D.ymax); + break; + } + + if (!b_intersects_plane) + continue;// Optimize for common case when only few sides of the + // clipper envelope intersect the geometry. + + MultiPath src_poly = result_poly; + result_poly = (MultiPath) polyline_in.createInstance(); + + MultiPathImpl mp_impl_src = (MultiPathImpl) src_poly._getImpl(); + SegmentIteratorImpl seg_iter = mp_impl_src.querySegmentIterator(); + seg_iter.resetToFirstPath(); + Point2D pt_prev; + Point2D pt = new Point2D(); + while (seg_iter.nextPath()) { + int inside = -1; + boolean b_start_new_path = true; + while (seg_iter.hasNextSegment()) { + Segment segment = seg_iter.nextSegment(); + segment.queryEnvelope2D(seg_env); + int seg_plane_intersection_status = checkSegmentIntersection_( + seg_env, iclip_plane, clip_value); + if (seg_plane_intersection_status == -1) // intersects plane + { + int count = segment.intersectionWithAxis2D(b_axis_x, + clip_value, result_ordinates, parameters); + if (count > 0) { + double t0 = 0.0; + pt_prev = segment.getStartXY(); + for (int i = 0; i <= count; i++) { + double t = i < count ? parameters[i] : 1.0; + if (t0 == t) + continue; + + segment.cut(t0, t, sub_segment_buffer); + Segment sub_seg = sub_segment_buffer.get(); + sub_seg.setStartXY(pt_prev); + if (i < count) {// snap to plane + if (b_axis_x) { + pt.x = result_ordinates[i]; + pt.y = clip_value; + } else { + pt.x = clip_value; + pt.y = result_ordinates[i]; + } + sub_seg.setEndXY(pt); + } + + sub_seg.queryEnvelope2D(sub_seg_env); + int sub_segment_plane_intersection_status = checkSegmentIntersection_( + sub_seg_env, iclip_plane, clip_value); + + if (sub_segment_plane_intersection_status == -1) { + // subsegment is intertsecting the plane. We + // need to snap one of the endpoints to + // ensure no intersection. + // TODO: ensure this works for curves. For + // curves we have to adjust the curve shape. + Point2D pt_1 = sub_seg.getStartXY(); + Point2D pt_2 = sub_seg.getEndXY(); + if (!b_axis_x) { + assert ((pt_1.x < clip_value && pt_2.x > clip_value) || (pt_1.x > clip_value && pt_2.x < clip_value)); + double d_1 = Math.abs(pt_1.x + - clip_value); + double d_2 = Math.abs(pt_2.x + - clip_value); + if (d_1 < d_2) { + pt_1.x = clip_value; + sub_seg.setStartXY(pt_1); + } else { + pt_2.x = clip_value; + sub_seg.setEndXY(pt_2); + } + } else { + assert ((pt_1.y < clip_value && pt_2.y > clip_value) || (pt_1.y > clip_value && pt_2.y < clip_value)); + double d_1 = Math.abs(pt_1.y + - clip_value); + double d_2 = Math.abs(pt_2.y + - clip_value); + if (d_1 < d_2) { + pt_1.y = clip_value; + sub_seg.setStartXY(pt_1); + } else { + pt_2.y = clip_value; + sub_seg.setEndXY(pt_2); + } + } + + // after the endpoint has been adjusted, + // recheck the segment. + sub_seg.queryEnvelope2D(sub_seg_env); + sub_segment_plane_intersection_status = checkSegmentIntersection_( + sub_seg_env, iclip_plane, + clip_value); + } + + assert (sub_segment_plane_intersection_status != -1); + + pt_prev = sub_seg.getEndXY(); + t0 = t; + + inside = sub_segment_plane_intersection_status; + if (inside == 1) { + result_poly.addSegment(sub_seg, + b_start_new_path); + b_start_new_path = false; + } else + b_start_new_path = true; + } + } + } else { + inside = seg_plane_intersection_status; + if (inside == 1) { + result_poly.addSegment(segment, b_start_new_path); + b_start_new_path = false; + } else + b_start_new_path = true; + } + } + } + } + + return result_poly; + } + + void resolveBoundaryOverlaps_() { + m_vertices_on_extent_index = -1; + splitSegments_(false, m_extent.xmin); + splitSegments_(false, m_extent.xmax); + splitSegments_(true, m_extent.ymin); + splitSegments_(true, m_extent.ymax); + + m_vertices_on_extent.resize(0); + m_vertices_on_extent.reserve(100); + m_vertices_on_extent_index = m_shape.createUserIndex(); + + Point2D pt = new Point2D(); + for (int path = m_shape.getFirstPath(m_geometry); path != -1; path = m_shape + .getNextPath(path)) { + int vertex = m_shape.getFirstVertex(path); + for (int ivert = 0, nvert = m_shape.getPathSize(path); ivert < nvert; ivert++, vertex = m_shape + .getNextVertex(vertex)) { + m_shape.getXY(vertex, pt); + if (m_extent.xmin == pt.x || m_extent.xmax == pt.x + || m_extent.ymin == pt.y || m_extent.ymax == pt.y) { + m_shape.setUserIndex(vertex, m_vertices_on_extent_index, + m_vertices_on_extent.size()); + m_vertices_on_extent.add(vertex); + } + } + } + // dbg_check_path_first_(); + resolveOverlaps_(false, m_extent.xmin); + // dbg_check_path_first_(); + resolveOverlaps_(false, m_extent.xmax); + // dbg_check_path_first_(); + resolveOverlaps_(true, m_extent.ymin); + // dbg_check_path_first_(); + resolveOverlaps_(true, m_extent.ymax); + fixPaths_(); + } + + void densifyAlongClipExtent_(double densify_dist) { + assert (densify_dist > 0); + Point2D pt_1 = new Point2D(); + Point2D pt_2 = new Point2D(); + double[] split_scalars = new double[2048]; + for (int path = m_shape.getFirstPath(m_geometry); path != -1; path = m_shape + .getNextPath(path)) { + int first_vertex = m_shape.getFirstVertex(path); + int vertex = first_vertex; + do { + int next_vertex = m_shape.getNextVertex(vertex); + m_shape.getXY(vertex, pt_1); + int b_densify_x = -1; + if (pt_1.x == m_extent.xmin) { + m_shape.getXY(next_vertex, pt_2); + if (pt_2.x == m_extent.xmin) { + b_densify_x = 1; + } + } else if (pt_1.x == m_extent.xmax) { + m_shape.getXY(next_vertex, pt_2); + if (pt_2.x == m_extent.xmax) { + b_densify_x = 1; + } + } + + if (pt_1.y == m_extent.ymin) { + m_shape.getXY(next_vertex, pt_2); + if (pt_2.y == m_extent.ymin) { + b_densify_x = 0; + } + } else if (pt_1.y == m_extent.ymax) { + m_shape.getXY(next_vertex, pt_2); + if (pt_2.y == m_extent.ymax) { + b_densify_x = 0; + } + } + + if (b_densify_x == -1) { + vertex = next_vertex; + continue; + } + + double len = Point2D.distance(pt_1, pt_2); + int num = (int) Math.min(Math.ceil(len / densify_dist), 2048.0); + if (num <= 1) { + vertex = next_vertex; + continue; + } + + for (int i = 1; i < num; i++) { + split_scalars[i - 1] = (1.0 * i) / num; + } + + int actual_splits = m_shape.splitSegment(vertex, split_scalars, + num - 1); + assert (actual_splits == num - 1); + vertex = next_vertex; + } while (vertex != first_vertex); + } + } + + void splitSegments_(boolean b_axis_x, double clip_value) { + // After the clipping, we could have produced unwanted segment overlaps + // along the clipping envelope boundary. + // Detect and resolve that case if possible. + int usage_index = m_shape.createUserIndex(); + Point2D pt = new Point2D(); + AttributeStreamOfInt32 sorted_vertices = new AttributeStreamOfInt32(0); + sorted_vertices.reserve(100); + for (int path = m_shape.getFirstPath(m_geometry); path != -1; path = m_shape + .getNextPath(path)) { + int vertex = m_shape.getFirstVertex(path); + for (int ivert = 0, nvert = m_shape.getPathSize(path); ivert < nvert; ivert++) { + int next_vertex = m_shape.getNextVertex(vertex); + m_shape.getXY(vertex, pt); + if (b_axis_x ? pt.y == clip_value : pt.x == clip_value) { + m_shape.getXY(next_vertex, pt); + if (b_axis_x ? pt.y == clip_value : pt.x == clip_value) { + if (m_shape.getUserIndex(vertex, usage_index) != 1) { + sorted_vertices.add(vertex); + m_shape.setUserIndex(vertex, usage_index, 1); + } + + if (m_shape.getUserIndex(next_vertex, usage_index) != 1) { + sorted_vertices.add(next_vertex); + m_shape.setUserIndex(next_vertex, usage_index, 1); + } + } + } + vertex = next_vertex; + } + } + + m_shape.removeUserIndex(usage_index); + if (sorted_vertices.size() < 3) { + return; + } + + sorted_vertices.Sort(0, sorted_vertices.size(), + new ClipperVertexComparer(this)); + + Point2D pt_tmp = new Point2D(); // forward declare for java port + // optimization + Point2D pt_0 = new Point2D(); + Point2D pt_1 = new Point2D(); + pt_0.setNaN(); + int index_0 = -1; + AttributeStreamOfInt32 active_intervals = new AttributeStreamOfInt32(0); + AttributeStreamOfInt32 new_active_intervals = new AttributeStreamOfInt32( + 0); + + int node1 = m_shape.createUserIndex(); + int node2 = m_shape.createUserIndex(); + for (int index = 0, n = sorted_vertices.size(); index < n; index++) { + int vert = sorted_vertices.get(index); + m_shape.getXY(vert, pt); + if (!pt.isEqual(pt_0)) { + if (index_0 == -1) { + index_0 = index; + pt_0.setCoords(pt); + continue; + } + + // add new intervals, that started at pt_0 + for (int i = index_0; i < index; i++) { + int v = sorted_vertices.get(i); + int nextv = m_shape.getNextVertex(v); + int prevv = m_shape.getPrevVertex(v); + boolean bAdded = false; + if (compareVertices_(v, nextv) < 0) { + m_shape.getXY(nextv, pt_tmp); + if (b_axis_x ? pt_tmp.y == clip_value + : pt_tmp.x == clip_value) { + active_intervals.add(v); + bAdded = true; + m_shape.setUserIndex(v, node2, 1); + } + } + if (compareVertices_(v, prevv) < 0) { + m_shape.getXY(prevv, pt_tmp); + if (b_axis_x ? pt_tmp.y == clip_value + : pt_tmp.x == clip_value) { + if (!bAdded) + active_intervals.add(v); + m_shape.setUserIndex(v, node1, 1); + } + } + } + + // Split all active intervals at new point + for (int ia = 0, na = active_intervals.size(); ia < na; ia++) { + int v = active_intervals.get(ia); + int n_1 = m_shape.getUserIndex(v, node1); + int n_2 = m_shape.getUserIndex(v, node2); + if (n_1 == 1) { + int prevv = m_shape.getPrevVertex(v); + m_shape.getXY(prevv, pt_1); + double[] t = new double[1]; + t[0] = 0; + if (!pt_1.isEqual(pt)) {// Split the active segment + double active_segment_length = Point2D + .distance(pt_0, pt_1); + t[0] = Point2D.distance(pt_1, pt) + / active_segment_length; + assert (t[0] >= 0 && t[0] <= 1.0); + if (t[0] == 0) + t[0] = NumberUtils.doubleEps();// some + // roundoff + // issue. + // split + // anyway. + else if (t[0] == 1.0) { + t[0] = 1.0 - NumberUtils.doubleEps();// some + // roundoff + // issue. + // split + // anyway. + assert (t[0] != 1.0); + } + + int split_count = m_shape.splitSegment(prevv, + t, 1); + assert (split_count > 0); + int v_1 = m_shape.getPrevVertex(v); + m_shape.setXY(v_1, pt); + new_active_intervals.add(v_1); + m_shape.setUserIndex(v_1, node1, 1); + m_shape.setUserIndex(v_1, node2, -1); + } else { + // The active segment ends at the current point. + // We skip it, and it goes away. + } + } + if (n_2 == 1) { + int nextv = m_shape.getNextVertex(v); + m_shape.getXY(nextv, pt_1); + double[] t = new double[1]; + t[0] = 0; + if (!pt_1.isEqual(pt)) { + double active_segment_length = Point2D + .distance(pt_0, pt_1); + t[0] = Point2D.distance(pt_0, pt) + / active_segment_length; + assert (t[0] >= 0 && t[0] <= 1.0); + if (t[0] == 0) + t[0] = NumberUtils.doubleEps();// some + // roundoff + // issue. + // split + // anyway. + else if (t[0] == 1.0) { + t[0] = 1.0 - NumberUtils.doubleEps();// some + // roundoff + // issue. + // split + // anyway. + assert (t[0] != 1.0); + } + + int split_count = m_shape.splitSegment(v, t, 1); + assert (split_count > 0); + int v_1 = m_shape.getNextVertex(v); + m_shape.setXY(v_1, pt); + new_active_intervals.add(v_1); + m_shape.setUserIndex(v_1, node1, -1); + m_shape.setUserIndex(v_1, node2, 1); + } + } + } + + AttributeStreamOfInt32 tmp = active_intervals; + active_intervals = new_active_intervals; + new_active_intervals = tmp; + new_active_intervals.clear(false); + + index_0 = index; + pt_0.setCoords(pt); + } + } + + m_shape.removeUserIndex(node1); + m_shape.removeUserIndex(node2); + } + + void resolveOverlaps_(boolean b_axis_x, double clip_value) { + // Along the envelope boundary there could be overlapped segments. + // Example, exterior ring with a hole is cut with a line, that + // passes through the center of the hole. + // Detect pairs of opposite overlapping segments and get rid of them + Point2D pt = new Point2D(); + AttributeStreamOfInt32 sorted_vertices = new AttributeStreamOfInt32(0); + sorted_vertices.reserve(100); + int sorted_index = m_shape.createUserIndex(); + // DEBUGPRINTF(L"ee\n"); + for (int ivert = 0, nvert = m_vertices_on_extent.size(); ivert < nvert; ivert++) { + int vertex = m_vertices_on_extent.get(ivert); + if (vertex == -1) + continue; + + int next_vertex = m_shape.getNextVertex(vertex); + m_shape.getXY(vertex, pt); + // DEBUGPRINTF(L"%f\t%f\n", pt.x, pt.y); + if (b_axis_x ? pt.y == clip_value : pt.x == clip_value) { + m_shape.getXY(next_vertex, pt); + if (b_axis_x ? pt.y == clip_value : pt.x == clip_value) { + assert (m_shape.getUserIndex(next_vertex, + m_vertices_on_extent_index) != -1); + if (m_shape.getUserIndex(vertex, sorted_index) != -2) { + sorted_vertices.add(vertex);// remember the vertex. The + // attached segment belongs + // to the given clip plane. + m_shape.setUserIndex(vertex, sorted_index, -2); + } + + if (m_shape.getUserIndex(next_vertex, sorted_index) != -2) { + sorted_vertices.add(next_vertex); + m_shape.setUserIndex(next_vertex, sorted_index, -2); + } + } + } + } + + if (sorted_vertices.size() == 0) { + m_shape.removeUserIndex(sorted_index); + return; + } + + sorted_vertices.Sort(0, sorted_vertices.size(), + new ClipperVertexComparer(this)); + // std::sort(sorted_vertices.get_ptr(), sorted_vertices.get_ptr() + + // sorted_vertices.size(), Clipper_vertex_comparer(this)); + + // DEBUGPRINTF(L"**\n"); + for (int index = 0, n = sorted_vertices.size(); index < n; index++) { + int vert = sorted_vertices.get(index); + m_shape.setUserIndex(vert, sorted_index, index); + // Point_2D pt; + // m_shape.get_xy(vert, pt); + // DEBUGPRINTF(L"%f\t%f\t%d\n", pt.x, pt.y, vert); + } + + Point2D pt_tmp = new Point2D(); + Point2D pt_0 = new Point2D(); + pt_0.setNaN(); + int index_0 = -1; + for (int index = 0, n = sorted_vertices.size(); index < n; index++) { + int vert = sorted_vertices.get(index); + if (vert == -1) + continue; + + m_shape.getXY(vert, pt); + if (!pt.isEqual(pt_0)) { + if (index_0 != -1) { + while (true) { + boolean b_overlap_resolved = false; + int index_to = index - index_0 > 1 ? index - 1 : index; + for (int i = index_0; i < index_to; i++) { + int v = sorted_vertices.get(i); + if (v == -1) + continue; + int nextv = -1; + int nv = m_shape.getNextVertex(v); + if (compareVertices_(v, nv) < 0) { + m_shape.getXY(nv, pt_tmp); + if (b_axis_x ? pt_tmp.y == clip_value + : pt_tmp.x == clip_value) + nextv = nv; + } + int prevv = -1; + int pv = m_shape.getPrevVertex(v); + if (compareVertices_(v, pv) < 0) { + m_shape.getXY(pv, pt_tmp); + if (b_axis_x ? pt_tmp.y == clip_value + : pt_tmp.x == clip_value) + prevv = pv; + } + + if (nextv != -1 && prevv != -1) { + // we have a cusp here. remove the vertex. + beforeRemoveVertex_(v, sorted_vertices, + sorted_index); + m_shape.removeVertex(v, false); + beforeRemoveVertex_(nextv, sorted_vertices, + sorted_index); + m_shape.removeVertex(nextv, false); + b_overlap_resolved = true; + continue; + } + + if (nextv == -1 && prevv == -1) + continue; + + for (int j = i + 1; j < index; j++) { + int v_1 = sorted_vertices.get(j); + if (v_1 == -1) + continue; + int nv1 = m_shape.getNextVertex(v_1); + int nextv1 = -1; + if (compareVertices_(v_1, nv1) < 0) { + m_shape.getXY(nv1, pt_tmp); + if (b_axis_x ? pt_tmp.y == clip_value + : pt_tmp.x == clip_value) + nextv1 = nv1; + } + + int pv1 = m_shape.getPrevVertex(v_1); + int prevv_1 = -1; + if (compareVertices_(v_1, pv1) < 0) { + m_shape.getXY(pv1, pt_tmp); + if (b_axis_x ? pt_tmp.y == clip_value + : pt_tmp.x == clip_value) + prevv_1 = pv1; + } + if (nextv1 != -1 && prevv_1 != -1) { + // we have a cusp here. remove the vertex. + beforeRemoveVertex_(v_1, sorted_vertices, + sorted_index); + m_shape.removeVertex(v_1, false); + beforeRemoveVertex_(nextv1, + sorted_vertices, sorted_index); + m_shape.removeVertex(nextv1, false); + b_overlap_resolved = true; + break; + } + if (nextv != -1 && prevv_1 != -1) { + removeOverlap_(sorted_vertices, v, nextv, + v_1, prevv_1, sorted_index); + b_overlap_resolved = true; + break; + } else if (prevv != -1 && nextv1 != -1) { + removeOverlap_(sorted_vertices, v_1, + nextv1, v, prevv, sorted_index); + b_overlap_resolved = true; + break; + } + } + + if (b_overlap_resolved) + break; + } + + if (!b_overlap_resolved) + break; + } + } + + index_0 = index; + pt_0.setCoords(pt); + } + } + + m_shape.removeUserIndex(sorted_index); + } + + void beforeRemoveVertex_(int v_1, AttributeStreamOfInt32 sorted_vertices, + int sorted_index) { + int ind = m_shape.getUserIndex(v_1, sorted_index); + sorted_vertices.set(ind, -1); + ind = m_shape.getUserIndex(v_1, m_vertices_on_extent_index); + m_vertices_on_extent.set(ind, -1); + int path = m_shape.getPathFromVertex(v_1); + if (path != -1) { + int first = m_shape.getFirstVertex(path); + if (first == v_1) { + m_shape.setFirstVertex_(path, -1); + m_shape.setLastVertex_(path, -1); + } + } + } + + void removeOverlap_(AttributeStreamOfInt32 sorted_vertices, int v, + int nextv, int v_1, int prevv_1, int sorted_index) { + assert (m_shape.isEqualXY(v, v_1)); + assert (m_shape.isEqualXY(nextv, prevv_1)); + assert (m_shape.getNextVertex(v) == nextv); + assert (m_shape.getNextVertex(prevv_1) == v_1); + m_shape.setNextVertex_(v, v_1); + m_shape.setPrevVertex_(v_1, v); + m_shape.setPrevVertex_(nextv, prevv_1); + m_shape.setNextVertex_(prevv_1, nextv); + + beforeRemoveVertex_(v_1, sorted_vertices, sorted_index); + m_shape.removeVertexInternal_(v_1, false); + beforeRemoveVertex_(prevv_1, sorted_vertices, sorted_index); + m_shape.removeVertexInternal_(prevv_1, true); + } + + void fixPaths_() { + for (int ivert = 0, nvert = m_vertices_on_extent.size(); ivert < nvert; ivert++) { + int vertex = m_vertices_on_extent.get(ivert); + if (vertex != -1) + m_shape.setPathToVertex_(vertex, -1); + } + + int path_count = 0; + int geometry_size = 0; + for (int path = m_shape.getFirstPath(m_geometry); path != -1; ) { + int first = m_shape.getFirstVertex(path); + if (first == -1 || path != m_shape.getPathFromVertex(first)) { // The + // path's + // first + // vertex + // has + // been + // deleted. + // Or + // the + // path + // first + // vertex + // is + // now + // part + // of + // another + // path. + // We + // have + // to + // delete + // such + // path + // object. + int p = path; + path = m_shape.getNextPath(path); + m_shape.setFirstVertex_(p, -1); + m_shape.removePathOnly_(p); + continue; + } + assert (path == m_shape.getPathFromVertex(first)); + int vertex = first; + int path_size = 0; + do { + m_shape.setPathToVertex_(vertex, path); + path_size++; + vertex = m_shape.getNextVertex(vertex); + } while (vertex != first); + + if (path_size <= 2) { + int ind = m_shape.getUserIndex(first, + m_vertices_on_extent_index); + m_vertices_on_extent.set(ind, -1); + int nv = m_shape.removeVertex(first, false); + if (path_size == 2) { + ind = m_shape.getUserIndex(nv, m_vertices_on_extent_index); + m_vertices_on_extent.set(ind, -1); + m_shape.removeVertex(nv, false); + } + int p = path; + path = m_shape.getNextPath(path); + m_shape.setFirstVertex_(p, -1); + m_shape.removePathOnly_(p); + continue; + } + + m_shape.setRingAreaValid_(path, false); + m_shape.setLastVertex_(path, m_shape.getPrevVertex(first)); + m_shape.setPathSize_(path, path_size); + geometry_size += path_size; + path_count++; + path = m_shape.getNextPath(path); + } + + for (int ivert = 0, nvert = m_vertices_on_extent.size(); ivert < nvert; ivert++) { + int vertex = m_vertices_on_extent.get(ivert); + if (vertex == -1) + continue; + int path = m_shape.getPathFromVertex(vertex); + if (path != -1) + continue; + + path = m_shape.insertPath(m_geometry, -1); + int path_size = 0; + int first = vertex; + do { + m_shape.setPathToVertex_(vertex, path); + path_size++; + vertex = m_shape.getNextVertex(vertex); + } while (vertex != first); + + if (path_size <= 2) { + int ind = m_shape.getUserIndex(first, + m_vertices_on_extent_index); + m_vertices_on_extent.set(ind, -1); + int nv = m_shape.removeVertex(first, false); + if (path_size == 2) { + ind = m_shape.getUserIndex(nv, m_vertices_on_extent_index); + if (ind >= 0) + m_vertices_on_extent.set(ind, -1); + else { + // this vertex is not on the extent. + } + m_shape.removeVertex(nv, false); + } + + int p = path; + path = m_shape.getNextPath(path); + m_shape.setFirstVertex_(p, -1); + m_shape.removePathOnly_(p); + continue; + } + + m_shape.setClosedPath(path, true); + m_shape.setPathSize_(path, path_size); + m_shape.setFirstVertex_(path, first); + m_shape.setLastVertex_(path, m_shape.getPrevVertex(first)); + m_shape.setRingAreaValid_(path, false); + geometry_size += path_size; + path_count++; + } + + m_shape.setGeometryPathCount_(m_geometry, path_count); + m_shape.setGeometryVertexCount_(m_geometry, geometry_size); + + int total_point_count = 0; + for (int geometry = m_shape.getFirstGeometry(); geometry != -1; geometry = m_shape + .getNextGeometry(geometry)) { + total_point_count += m_shape.getPointCount(geometry); + } + + m_shape.setTotalPointCount_(total_point_count); + } + + static Geometry clipMultiPath_(MultiPath multipath, Envelope2D extent, + double tolerance, double densify_dist) { + Clipper clipper = new Clipper(extent); + return clipper.clipMultiPath2_(multipath, tolerance, densify_dist); + } + + Clipper(Envelope2D extent) { + m_extent = extent; + m_shape = new EditShape(); + m_vertices_on_extent = new AttributeStreamOfInt32(0); + } + + // static std::shared_ptr create_polygon_from_polyline(const + // std::shared_ptr& polyline, const Envelope_2D& env_2D, bool + // add_envelope, double tolerance, double densify_dist, int + // corner_is_inside); + static Geometry clip(Geometry geometry, Envelope2D extent, + double tolerance, double densify_dist) { + if (geometry.isEmpty()) + return geometry; + + if (extent.isEmpty()) + return geometry.createInstance(); // return an empty geometry + + int geomtype = geometry.getType().value(); + + // Test firstly the simplest geometry types point and envelope. + // After that we'll check the envelope intersection for the optimization + if (geomtype == Geometry.Type.Point.value()) { + Point2D pt = ((Point) geometry).getXY(); + if (extent.contains(pt)) + return geometry; + else + return geometry.createInstance(); // return an empty geometry + } else if (geomtype == Geometry.Type.Envelope.value()) { + Envelope2D env = new Envelope2D(); + geometry.queryEnvelope2D(env); + if (env.intersect(extent)) { + Envelope result_env = new Envelope(); + geometry.copyTo(result_env); + result_env.setEnvelope2D(env); + return result_env; + } else + return geometry.createInstance(); // return an empty geometry + } + + // Test the geometry envelope + Envelope2D env_2D = new Envelope2D(); + geometry.queryLooseEnvelope2D(env_2D); + if (extent.contains(env_2D)) + return geometry;// completely inside of bounds + if (!extent.isIntersecting(env_2D)) + return geometry.createInstance();// outside of bounds. return empty + // geometry. + + MultiVertexGeometryImpl impl = (MultiVertexGeometryImpl) geometry + ._getImpl(); + GeometryAccelerators accel = impl._getAccelerators(); + if (accel != null) { + RasterizedGeometry2D rgeom = accel.getRasterizedGeometry(); + if (rgeom != null) { + RasterizedGeometry2D.HitType hit = rgeom + .queryEnvelopeInGeometry(extent); + if (hit == RasterizedGeometry2D.HitType.Inside) { + if (geomtype != Geometry.Type.Polygon.value()) + throw GeometryException.GeometryInternalError(); + + Polygon poly = new Polygon(geometry.getDescription()); + poly.addEnvelope(extent, false); + return poly; + } else if (hit == RasterizedGeometry2D.HitType.Outside) { + return geometry.createInstance();// outside of bounds. + // return empty + // geometry. + } + } + } + + switch (geomtype) { + case Geometry.GeometryType.MultiPoint: { + MultiPoint multi_point = (MultiPoint) geometry; + MultiPoint multi_point_out = null; + int npoints = multi_point.getPointCount(); + AttributeStreamOfDbl xy = (AttributeStreamOfDbl) ((MultiPointImpl) multi_point + ._getImpl()) + .getAttributeStreamRef(VertexDescription.Semantics.POSITION); + // create the new geometry only if there are points that has been + // clipped out. + // If all vertices are inside of the envelope, it returns the input + // multipoint. + int ipoints0 = 0; + for (int ipoints = 0; ipoints < npoints; ipoints++) { + Point2D pt = new Point2D(); + xy.read(2 * ipoints, pt); + + if (!extent.contains(pt)) {// vertex is outside of the envelope + if (ipoints0 == 0) + multi_point_out = (MultiPoint) multi_point + .createInstance(); + + if (ipoints0 < ipoints) + multi_point_out.add(multi_point, ipoints0, ipoints); + + ipoints0 = ipoints + 1;// ipoints0 contains index of vertex + // right after the last clipped out + // vertex. + } + } + + // add the rest of the batch to the result multipoint (only if + // something has been already clipped out) + if (ipoints0 > 0) + multi_point_out.add(multi_point, ipoints0, npoints); + + if (ipoints0 == 0) + return multi_point;// everything is inside, so return the input + // geometry + else + return multi_point_out;// clipping has happend, return the + // clipped geometry + } + case Geometry.GeometryType.Polygon: + case Geometry.GeometryType.Polyline: + return clipMultiPath_((MultiPath) geometry, extent, tolerance, + densify_dist); + default: + assert (false); + throw GeometryException.GeometryInternalError(); + } + } + + int compareVertices_(int v_1, int v_2) { + Point2D pt_1 = new Point2D(); + m_shape.getXY(v_1, pt_1); + Point2D pt_2 = new Point2D(); + m_shape.getXY(v_2, pt_2); + int res = pt_1.compare(pt_2); + return res; + } + + static final class ClipperVertexComparer extends + AttributeStreamOfInt32.IntComparator { + Clipper m_clipper; + + ClipperVertexComparer(Clipper clipper) { + m_clipper = clipper; + } + + @Override + public int compare(int v1, int v2) { + return m_clipper.compareVertices_(v1, v2); + } + + } } diff --git a/src/main/java/com/esri/core/geometry/Clusterer.java b/src/main/java/com/esri/core/geometry/Clusterer.java index 891da967..fb595580 100644 --- a/src/main/java/com/esri/core/geometry/Clusterer.java +++ b/src/main/java/com/esri/core/geometry/Clusterer.java @@ -29,11 +29,11 @@ * Used by the TopoGraph and Simplify. */ final class Clusterer { - // Clusters vertices of the shape. Returns True, if some vertices were moved - // (clustered). - // Uses reciprocal clustering (cluster vertices that are mutual nearest - // neighbours) - /* + // Clusters vertices of the shape. Returns True, if some vertices were moved + // (clustered). + // Uses reciprocal clustering (cluster vertices that are mutual nearest + // neighbours) + /* * static boolean executeReciprocal(EditShape shape, double tolerance) { * Clusterer clusterer = new Clusterer(); clusterer.m_shape = shape; * clusterer.m_tolerance = tolerance; clusterer.m_sqr_tolerance = tolerance @@ -42,547 +42,547 @@ final class Clusterer { * clusterer.clusterReciprocal_(); } */ - // Clusters vertices of the shape. Returns True, if some vertices were moved - // (clustered). - // Uses non-reciprocal clustering (cluster any vertices that are closer than - // the tolerance in the first-found-first-clustered order) - static boolean executeNonReciprocal(EditShape shape, double tolerance) { - Clusterer clusterer = new Clusterer(); - clusterer.m_shape = shape; - clusterer.m_tolerance = tolerance; - clusterer.m_sqr_tolerance = tolerance * tolerance; - clusterer.m_cell_size = 2 * tolerance; - clusterer.m_inv_cell_size = 1.0 / clusterer.m_cell_size; - return clusterer.clusterNonReciprocal_(); - } - - // Use b_conservative == True for simplify, and False for IsSimple. This - // makes sure Simplified shape is more robust to transformations. - static boolean isClusterCandidate_(double x_1, double y1, double x2, - double y2, double sqr_tolerance) { - double dx = x_1 - x2; - double dy = y1 - y2; - return dx * dx + dy * dy <= sqr_tolerance; - } - - Point2D m_origin = new Point2D(); - double m_tolerance; - double m_sqr_tolerance; - double m_cell_size; - double m_inv_cell_size; - int[] m_bucket_array = new int[4];// temporary 4 element array - int[] m_bucket_hash = new int[4];// temporary 4 element array - int m_dbg_candidate_check_count = 0; - int m_hash_values = -1; - int m_new_clusters = -1; - - static int hashFunction_(int xi, int yi) { - int h = NumberUtils.hash(xi); - return NumberUtils.hash(h, yi); - } - - final class ClusterHashFunction extends IndexHashTable.HashFunction { - EditShape m_shape; - double m_sqr_tolerance; - double m_inv_cell_size; - Point2D m_origin = new Point2D(); - Point2D m_pt = new Point2D(); - Point2D m_pt_2 = new Point2D(); - int m_hash_values; - - public ClusterHashFunction(EditShape shape, Point2D origin, - double sqr_tolerance, double inv_cell_size, int hash_values) { - m_shape = shape; - m_sqr_tolerance = sqr_tolerance; - m_inv_cell_size = inv_cell_size; - m_origin = origin; - m_hash_values = hash_values; - m_pt.setNaN(); - m_pt_2.setNaN(); - } - - int calculate_hash(int element) { - return calculate_hash_from_vertex(element); - } - - int dbg_calculate_hash_from_xy(double x, double y) { - double dx = x - m_origin.x; - int xi = (int) (dx * m_inv_cell_size + 0.5); - double dy = y - m_origin.y; - int yi = (int) (dy * m_inv_cell_size + 0.5); - return hashFunction_(xi, yi); - } - - int calculate_hash_from_vertex(int vertex) { - m_shape.getXY(vertex, m_pt); - double dx = m_pt.x - m_origin.x; - int xi = (int) (dx * m_inv_cell_size + 0.5); - double dy = m_pt.y - m_origin.y; - int yi = (int) (dy * m_inv_cell_size + 0.5); - return hashFunction_(xi, yi); - } - - @Override - public int getHash(int element) { - return m_shape.getUserIndex(element, m_hash_values); - } - - @Override - public boolean equal(int element_1, int element_2) { - int xyindex_1 = element_1; - int xyindex_2 = element_2; - m_shape.getXY(xyindex_1, m_pt); - m_shape.getXY(xyindex_2, m_pt_2); - return isClusterCandidate_(m_pt.x, m_pt.y, m_pt_2.x, m_pt_2.y, - m_sqr_tolerance); - } - - @Override - public int getHash(Object element_descriptor) { - // UNUSED - return 0; - } - - @Override - public boolean equal(Object element_descriptor, int element) { - // UNUSED - return false; - } - } - - ; - - EditShape m_shape; - IndexMultiList m_clusters; - ClusterHashFunction m_hash_function; - IndexHashTable m_hash_table; - - static class ClusterCandidate { - public int vertex; - double distance; - } - - ; - - void getNearestNeighbourCandidate_(int xyindex, Point2D pointOfInterest, - int bucket_ptr, ClusterCandidate candidate) { - candidate.vertex = -1; - candidate.distance = NumberUtils.doubleMax(); - - Point2D pt = new Point2D(); - for (int node = bucket_ptr; node != -1; node = m_hash_table - .getNextInBucket(node)) { - int xyind = m_hash_table.getElement(node); - if (xyindex == xyind) - continue; - - m_shape.getXY(xyind, pt); - if (isClusterCandidate_(pointOfInterest.x, pointOfInterest.y, pt.x, - pt.y, m_sqr_tolerance)) { - pt.sub(pointOfInterest); - double l = pt.length(); - if (l < candidate.distance) { - candidate.distance = l; - candidate.vertex = xyind; - } - } - } - } - - void findClusterCandidate_(int xyindex, ClusterCandidate candidate) { - Point2D pointOfInterest = new Point2D(); - m_shape.getXY(xyindex, pointOfInterest); - double x_0 = pointOfInterest.x - m_origin.x; - double x = x_0 * m_inv_cell_size; - double y0 = pointOfInterest.y - m_origin.y; - double y = y0 * m_inv_cell_size; - - int xi = (int) x; - int yi = (int) y; - - // find the nearest neighbour in the 4 neigbouring cells. - - candidate.vertex = -1; - candidate.distance = NumberUtils.doubleMax(); - ClusterCandidate c = new ClusterCandidate(); - for (int dx = 0; dx <= 1; dx += 1) { - for (int dy = 0; dy <= 1; dy += 1) { - int bucket_ptr = m_hash_table.getFirstInBucket(hashFunction_(xi - + dx, yi + dy)); - if (bucket_ptr != IndexHashTable.nullNode()) { - getNearestNeighbourCandidate_(xyindex, pointOfInterest, - bucket_ptr, c); - if (c.vertex != IndexHashTable.nullNode() - && c.distance < candidate.distance) { - candidate = c; - } - } - } - } - } - - void collectClusterCandidates_(int xyindex, - AttributeStreamOfInt32 candidates) { - Point2D pointOfInterest = new Point2D(); - m_shape.getXY(xyindex, pointOfInterest); - double x_0 = pointOfInterest.x - m_origin.x; - double x = x_0 * m_inv_cell_size; - double y0 = pointOfInterest.y - m_origin.y; - double y = y0 * m_inv_cell_size; - - int xi = (int) x; - int yi = (int) y; - - int bucket_count = 0; - // find all nearest neighbours in the 4 neigbouring cells. - // Note, because we check four neighbours, there should be 4 times more - // bins in the hash table to reduce collision probability in this loop. - for (int dx = 0; dx <= 1; dx += 1) { - for (int dy = 0; dy <= 1; dy += 1) { - int hash = hashFunction_(xi + dx, yi + dy); - int bucket_ptr = m_hash_table.getFirstInBucket(hash); - if (bucket_ptr != -1) { - // Check if we already have this bucket. - // There could be a hash collision for neighbouring buckets. - m_bucket_array[bucket_count] = bucket_ptr; - m_bucket_hash[bucket_count] = hash; - - bucket_count++; - } - } - } - - // Clear duplicate buckets - // There could be a hash collision for neighboring buckets. - for (int j = bucket_count - 1; j >= 1; j--) { - int bucket_ptr = m_bucket_array[j]; - for (int i = j - 1; i >= 0; i--) { - if (bucket_ptr == m_bucket_array[i])// hash values for two - // neighbouring cells have - // collided. - { - m_bucket_hash[i] = -1; // forget collided hash - bucket_count--; - if (j != bucket_count) { - m_bucket_hash[j] = m_bucket_hash[bucket_count]; - m_bucket_array[j] = m_bucket_array[bucket_count]; - } - break;// duplicate - } - } - } - - for (int i = 0; i < bucket_count; i++) { - collectNearestNeighbourCandidates_(xyindex, m_bucket_hash[i], - pointOfInterest, m_bucket_array[i], candidates); - } - } - - void collectNearestNeighbourCandidates_(int xyindex, int hash, - Point2D pointOfInterest, int bucket_ptr, - AttributeStreamOfInt32 candidates) { - Point2D pt = new Point2D(); - for (int node = bucket_ptr; node != -1; node = m_hash_table - .getNextInBucket(node)) { - int xyind = m_hash_table.getElement(node); - if (xyindex == xyind || hash != -1 - && m_shape.getUserIndex(xyind, m_hash_values) != hash) - continue;// processing same vertex, or the bucket hash modulo - // bin count collides. - - m_shape.getXY(xyind, pt); - m_dbg_candidate_check_count++; - if (isClusterCandidate_(pointOfInterest.x, pointOfInterest.y, pt.x, - pt.y, m_sqr_tolerance)) { - candidates.add(node);// note that we add the cluster node - // instead of the cluster. - } - } - } - - boolean mergeClusters_(int vertex1, int vertex2, boolean update_hash) { - int cluster_1 = m_shape.getUserIndex(vertex1, m_new_clusters); - int cluster_2 = m_shape.getUserIndex(vertex2, m_new_clusters); - assert (cluster_1 != StridedIndexTypeCollection.impossibleIndex2()); - assert (cluster_2 != StridedIndexTypeCollection.impossibleIndex2()); - - if (cluster_1 == -1) { - cluster_1 = m_clusters.createList(); - m_clusters.addElement(cluster_1, vertex1); - m_shape.setUserIndex(vertex1, m_new_clusters, cluster_1); - } - - if (cluster_2 == -1) { - m_clusters.addElement(cluster_1, vertex2); - } else { - m_clusters.concatenateLists(cluster_1, cluster_2); - } - - // ensure only single vertex refers to the cluster. - m_shape.setUserIndex(vertex2, m_new_clusters, - StridedIndexTypeCollection.impossibleIndex2()); - - // merge cordinates - boolean res = mergeVertices_(vertex1, vertex2); - - if (update_hash) { - int hash = m_hash_function.calculate_hash_from_vertex(vertex1); - m_shape.setUserIndex(vertex1, m_hash_values, hash); - } else { - - } - - return res; - } - - // recalculate coordinates of the vertices by averaging them using weights. - // return true if the coordinates has changed. - static boolean mergeVertices(Point pt_1, Point pt_2, double w_1, - int rank_1, double w_2, int rank_2, Point pt_res, double[] w_res, - int[] rank_res) { - assert (!pt_1.isEmpty() && !pt_2.isEmpty()); - boolean res = pt_1.equals(pt_2); - - if (rank_1 > rank_2) { - pt_res = pt_1; - if (w_res != null) { - rank_res[0] = rank_1; - w_res[0] = w_1; - } - return res; - } else if (rank_2 > rank_1) { - pt_res = pt_2; - if (w_res != null) { - rank_res[0] = rank_1; - w_res[0] = w_1; - } - return res; - } - - pt_res = pt_1; - Point2D pt2d = new Point2D(); - mergeVertices2D(pt_1.getXY(), pt_2.getXY(), w_1, rank_1, w_2, rank_2, - pt2d, w_res, rank_res); - pt_res.setXY(pt2d); - return res; - } - - static boolean mergeVertices2D(Point2D pt_1, Point2D pt_2, double w_1, - int rank_1, double w_2, int rank_2, Point2D pt_res, double[] w_res, - int[] rank_res) { - double w = w_1 + w_2; - boolean r = false; - double x = pt_1.x; - if (pt_1.x != pt_2.x) { - if (rank_1 == rank_2) - x = (pt_1.x * w_1 + pt_2.x * w_2) / w; - - r = true; - } - double y = pt_1.y; - if (pt_1.y != pt_2.y) { - if (rank_1 == rank_2) - y = (pt_1.y * w_1 + pt_2.y * w_2) / w; - - r = true; - } - - if (rank_1 != rank_2) { - if (rank_1 > rank_2) { - if (w_res != null) { - rank_res[0] = rank_1; - w_res[0] = w_1; - } - pt_res = pt_1; - } else { - if (w_res != null) { - rank_res[0] = rank_2; - w_res[0] = w_2; - } - pt_res = pt_2; - } - } else { - pt_res.setCoords(x, y); - if (w_res != null) { - w_res[0] = w; - rank_res[0] = rank_1; - } - } - - return r; - } - - boolean mergeVertices_(int vert_1, int vert_2) { - Point2D pt_1 = new Point2D(); - m_shape.getXY(vert_1, pt_1); - Point2D pt_2 = new Point2D(); - m_shape.getXY(vert_2, pt_2); - - double w_1 = m_shape.getWeight(vert_1); - double w_2 = m_shape.getWeight(vert_2); - double w = w_1 + w_2; - int r = 0; - double x = pt_1.x; - if (pt_1.x != pt_2.x) { - x = (pt_1.x * w_1 + pt_2.x * w_2) / w; - r++; - } - double y = pt_1.y; - if (pt_1.y != pt_2.y) { - y = (pt_1.y * w_1 + pt_2.y * w_2) / w; - r++; - } - - if (r > 0) - m_shape.setXY(vert_1, x, y); - - m_shape.setWeight(vert_1, w); - return r != 0; - } - - boolean clusterNonReciprocal_() { - int point_count = m_shape.getTotalPointCount(); - Envelope2D env = m_shape.getEnvelope2D(); - m_origin = env.getLowerLeft(); - double dim = Math.max(env.getHeight(), env.getWidth()); - double mincell = dim / (NumberUtils.intMax() - 1); - if (m_cell_size < mincell) { - m_cell_size = mincell; - m_inv_cell_size = 1.0 / m_cell_size; - } - - // This holds clusters. - m_clusters = new IndexMultiList(); - m_clusters.reserveLists(m_shape.getTotalPointCount() / 3 + 1); - m_clusters.reserveNodes(m_shape.getTotalPointCount() / 3 + 1); - - m_hash_values = m_shape.createUserIndex(); - m_new_clusters = m_shape.createUserIndex(); - - // Make the hash table. It serves a purpose of fine grain grid. - // Make it 25% larger than the 4 times point count to reduce the chance - // of collision. - // The 4 times comes from the fact that we check four neighbouring cells - // in the grid for each point. - m_hash_function = new ClusterHashFunction(m_shape, m_origin, - m_sqr_tolerance, m_inv_cell_size, m_hash_values); - m_hash_table = new IndexHashTable(4 * point_count / 3, m_hash_function); - m_hash_table.reserveElements(m_shape.getTotalPointCount()); - boolean b_clustered = false; - - // Go through all vertices stored in the m_shape and put the handles of - // the vertices into the clusters and the hash table. - for (int geometry = m_shape.getFirstGeometry(); geometry != -1; geometry = m_shape - .getNextGeometry(geometry)) { - for (int path = m_shape.getFirstPath(geometry); path != -1; path = m_shape - .getNextPath(path)) { - int vertex = m_shape.getFirstVertex(path); - for (int index = 0, nindex = m_shape.getPathSize(path); index < nindex; index++) { - assert (vertex != -1); - int hash = m_hash_function - .calculate_hash_from_vertex(vertex); - m_shape.setUserIndex(vertex, m_hash_values, hash); - m_hash_table.addElement(vertex, hash); // add cluster to the - // hash table - assert (m_shape.getUserIndex(vertex, m_new_clusters) == -1); - vertex = m_shape.getNextVertex(vertex); - } - } - } - - // m_hash_table->dbg_print_bucket_histogram_(); - - {// scope for candidates array - AttributeStreamOfInt32 candidates = new AttributeStreamOfInt32(0); - candidates.reserve(10); - - for (int geometry = m_shape.getFirstGeometry(); geometry != -1; geometry = m_shape - .getNextGeometry(geometry)) { - for (int path = m_shape.getFirstPath(geometry); path != -1; path = m_shape - .getNextPath(path)) { - int vertex = m_shape.getFirstVertex(path); - for (int index = 0, nindex = m_shape.getPathSize(path); index < nindex; index++) { - if (m_shape.getUserIndex(vertex, m_new_clusters) == StridedIndexTypeCollection - .impossibleIndex2()) { - vertex = m_shape.getNextVertex(vertex); - continue;// this vertex was merged with another - // cluster. It also was removed from the - // hash table. - } - - int hash = m_shape.getUserIndex(vertex, m_hash_values); - m_hash_table.deleteElement(vertex, hash); - - while (true) { - collectClusterCandidates_(vertex, candidates); - if (candidates.size() == 0) {// no candidate for - // clustering has - // been found for - // the cluster_1. - break; - } - - boolean clustered = false; - for (int candidate_index = 0, ncandidates = candidates - .size(); candidate_index < ncandidates; candidate_index++) { - int cluster_node = candidates - .get(candidate_index); - int other_vertex = m_hash_table - .getElement(cluster_node); - m_hash_table.deleteNode(cluster_node); - clustered |= mergeClusters_(vertex, - other_vertex, - candidate_index + 1 == ncandidates); - } - - b_clustered |= clustered; - candidates.clear(false); - // repeat search for the cluster candidates for - // cluster_1 - if (!clustered) - break;// positions did not change - } - - // m_shape->set_user_index(vertex, m_new_clusters, - // Strided_index_type_collection::impossible_index_2()); - vertex = m_shape.getNextVertex(vertex); - } - } - } - } - - if (b_clustered) { - applyClusterPositions_(); - } - - m_hash_table = null; - m_hash_function = null; - m_shape.removeUserIndex(m_hash_values); - m_shape.removeUserIndex(m_new_clusters); - - // output_debug_printf("total: %d\n",m_shape->get_total_point_count()); - // output_debug_printf("clustered: %d\n",m_dbg_candidate_check_count); - return b_clustered; - } - - void applyClusterPositions_() { - Point2D cluster_pt = new Point2D(); - // move vertices to the clustered positions. - for (int list = m_clusters.getFirstList(); list != -1; list = m_clusters - .getNextList(list)) { - int node = m_clusters.getFirst(list); - assert (node != -1); - int vertex = m_clusters.getElement(node); - m_shape.getXY(vertex, cluster_pt); - for (node = m_clusters.getNext(node); node != -1; node = m_clusters - .getNext(node)) { - int vertex_1 = m_clusters.getElement(node); - m_shape.setXY(vertex_1, cluster_pt); - } - } - } - - Clusterer() { - } + // Clusters vertices of the shape. Returns True, if some vertices were moved + // (clustered). + // Uses non-reciprocal clustering (cluster any vertices that are closer than + // the tolerance in the first-found-first-clustered order) + static boolean executeNonReciprocal(EditShape shape, double tolerance) { + Clusterer clusterer = new Clusterer(); + clusterer.m_shape = shape; + clusterer.m_tolerance = tolerance; + clusterer.m_sqr_tolerance = tolerance * tolerance; + clusterer.m_cell_size = 2 * tolerance; + clusterer.m_inv_cell_size = 1.0 / clusterer.m_cell_size; + return clusterer.clusterNonReciprocal_(); + } + + // Use b_conservative == True for simplify, and False for IsSimple. This + // makes sure Simplified shape is more robust to transformations. + static boolean isClusterCandidate_(double x_1, double y1, double x2, + double y2, double sqr_tolerance) { + double dx = x_1 - x2; + double dy = y1 - y2; + return dx * dx + dy * dy <= sqr_tolerance; + } + + Point2D m_origin = new Point2D(); + double m_tolerance; + double m_sqr_tolerance; + double m_cell_size; + double m_inv_cell_size; + int[] m_bucket_array = new int[4];// temporary 4 element array + int[] m_bucket_hash = new int[4];// temporary 4 element array + int m_dbg_candidate_check_count = 0; + int m_hash_values = -1; + int m_new_clusters = -1; + + static int hashFunction_(int xi, int yi) { + int h = NumberUtils.hash(xi); + return NumberUtils.hash(h, yi); + } + + final class ClusterHashFunction extends IndexHashTable.HashFunction { + EditShape m_shape; + double m_sqr_tolerance; + double m_inv_cell_size; + Point2D m_origin = new Point2D(); + Point2D m_pt = new Point2D(); + Point2D m_pt_2 = new Point2D(); + int m_hash_values; + + public ClusterHashFunction(EditShape shape, Point2D origin, + double sqr_tolerance, double inv_cell_size, int hash_values) { + m_shape = shape; + m_sqr_tolerance = sqr_tolerance; + m_inv_cell_size = inv_cell_size; + m_origin = origin; + m_hash_values = hash_values; + m_pt.setNaN(); + m_pt_2.setNaN(); + } + + int calculate_hash(int element) { + return calculate_hash_from_vertex(element); + } + + int dbg_calculate_hash_from_xy(double x, double y) { + double dx = x - m_origin.x; + int xi = (int) (dx * m_inv_cell_size + 0.5); + double dy = y - m_origin.y; + int yi = (int) (dy * m_inv_cell_size + 0.5); + return hashFunction_(xi, yi); + } + + int calculate_hash_from_vertex(int vertex) { + m_shape.getXY(vertex, m_pt); + double dx = m_pt.x - m_origin.x; + int xi = (int) (dx * m_inv_cell_size + 0.5); + double dy = m_pt.y - m_origin.y; + int yi = (int) (dy * m_inv_cell_size + 0.5); + return hashFunction_(xi, yi); + } + + @Override + public int getHash(int element) { + return m_shape.getUserIndex(element, m_hash_values); + } + + @Override + public boolean equal(int element_1, int element_2) { + int xyindex_1 = element_1; + int xyindex_2 = element_2; + m_shape.getXY(xyindex_1, m_pt); + m_shape.getXY(xyindex_2, m_pt_2); + return isClusterCandidate_(m_pt.x, m_pt.y, m_pt_2.x, m_pt_2.y, + m_sqr_tolerance); + } + + @Override + public int getHash(Object element_descriptor) { + // UNUSED + return 0; + } + + @Override + public boolean equal(Object element_descriptor, int element) { + // UNUSED + return false; + } + } + + ; + + EditShape m_shape; + IndexMultiList m_clusters; + ClusterHashFunction m_hash_function; + IndexHashTable m_hash_table; + + static class ClusterCandidate { + public int vertex; + double distance; + } + + ; + + void getNearestNeighbourCandidate_(int xyindex, Point2D pointOfInterest, + int bucket_ptr, ClusterCandidate candidate) { + candidate.vertex = -1; + candidate.distance = NumberUtils.doubleMax(); + + Point2D pt = new Point2D(); + for (int node = bucket_ptr; node != -1; node = m_hash_table + .getNextInBucket(node)) { + int xyind = m_hash_table.getElement(node); + if (xyindex == xyind) + continue; + + m_shape.getXY(xyind, pt); + if (isClusterCandidate_(pointOfInterest.x, pointOfInterest.y, pt.x, + pt.y, m_sqr_tolerance)) { + pt.sub(pointOfInterest); + double l = pt.length(); + if (l < candidate.distance) { + candidate.distance = l; + candidate.vertex = xyind; + } + } + } + } + + void findClusterCandidate_(int xyindex, ClusterCandidate candidate) { + Point2D pointOfInterest = new Point2D(); + m_shape.getXY(xyindex, pointOfInterest); + double x_0 = pointOfInterest.x - m_origin.x; + double x = x_0 * m_inv_cell_size; + double y0 = pointOfInterest.y - m_origin.y; + double y = y0 * m_inv_cell_size; + + int xi = (int) x; + int yi = (int) y; + + // find the nearest neighbour in the 4 neigbouring cells. + + candidate.vertex = -1; + candidate.distance = NumberUtils.doubleMax(); + ClusterCandidate c = new ClusterCandidate(); + for (int dx = 0; dx <= 1; dx += 1) { + for (int dy = 0; dy <= 1; dy += 1) { + int bucket_ptr = m_hash_table.getFirstInBucket(hashFunction_(xi + + dx, yi + dy)); + if (bucket_ptr != IndexHashTable.nullNode()) { + getNearestNeighbourCandidate_(xyindex, pointOfInterest, + bucket_ptr, c); + if (c.vertex != IndexHashTable.nullNode() + && c.distance < candidate.distance) { + candidate = c; + } + } + } + } + } + + void collectClusterCandidates_(int xyindex, + AttributeStreamOfInt32 candidates) { + Point2D pointOfInterest = new Point2D(); + m_shape.getXY(xyindex, pointOfInterest); + double x_0 = pointOfInterest.x - m_origin.x; + double x = x_0 * m_inv_cell_size; + double y0 = pointOfInterest.y - m_origin.y; + double y = y0 * m_inv_cell_size; + + int xi = (int) x; + int yi = (int) y; + + int bucket_count = 0; + // find all nearest neighbours in the 4 neigbouring cells. + // Note, because we check four neighbours, there should be 4 times more + // bins in the hash table to reduce collision probability in this loop. + for (int dx = 0; dx <= 1; dx += 1) { + for (int dy = 0; dy <= 1; dy += 1) { + int hash = hashFunction_(xi + dx, yi + dy); + int bucket_ptr = m_hash_table.getFirstInBucket(hash); + if (bucket_ptr != -1) { + // Check if we already have this bucket. + // There could be a hash collision for neighbouring buckets. + m_bucket_array[bucket_count] = bucket_ptr; + m_bucket_hash[bucket_count] = hash; + + bucket_count++; + } + } + } + + // Clear duplicate buckets + // There could be a hash collision for neighboring buckets. + for (int j = bucket_count - 1; j >= 1; j--) { + int bucket_ptr = m_bucket_array[j]; + for (int i = j - 1; i >= 0; i--) { + if (bucket_ptr == m_bucket_array[i])// hash values for two + // neighbouring cells have + // collided. + { + m_bucket_hash[i] = -1; // forget collided hash + bucket_count--; + if (j != bucket_count) { + m_bucket_hash[j] = m_bucket_hash[bucket_count]; + m_bucket_array[j] = m_bucket_array[bucket_count]; + } + break;// duplicate + } + } + } + + for (int i = 0; i < bucket_count; i++) { + collectNearestNeighbourCandidates_(xyindex, m_bucket_hash[i], + pointOfInterest, m_bucket_array[i], candidates); + } + } + + void collectNearestNeighbourCandidates_(int xyindex, int hash, + Point2D pointOfInterest, int bucket_ptr, + AttributeStreamOfInt32 candidates) { + Point2D pt = new Point2D(); + for (int node = bucket_ptr; node != -1; node = m_hash_table + .getNextInBucket(node)) { + int xyind = m_hash_table.getElement(node); + if (xyindex == xyind || hash != -1 + && m_shape.getUserIndex(xyind, m_hash_values) != hash) + continue;// processing same vertex, or the bucket hash modulo + // bin count collides. + + m_shape.getXY(xyind, pt); + m_dbg_candidate_check_count++; + if (isClusterCandidate_(pointOfInterest.x, pointOfInterest.y, pt.x, + pt.y, m_sqr_tolerance)) { + candidates.add(node);// note that we add the cluster node + // instead of the cluster. + } + } + } + + boolean mergeClusters_(int vertex1, int vertex2, boolean update_hash) { + int cluster_1 = m_shape.getUserIndex(vertex1, m_new_clusters); + int cluster_2 = m_shape.getUserIndex(vertex2, m_new_clusters); + assert (cluster_1 != StridedIndexTypeCollection.impossibleIndex2()); + assert (cluster_2 != StridedIndexTypeCollection.impossibleIndex2()); + + if (cluster_1 == -1) { + cluster_1 = m_clusters.createList(); + m_clusters.addElement(cluster_1, vertex1); + m_shape.setUserIndex(vertex1, m_new_clusters, cluster_1); + } + + if (cluster_2 == -1) { + m_clusters.addElement(cluster_1, vertex2); + } else { + m_clusters.concatenateLists(cluster_1, cluster_2); + } + + // ensure only single vertex refers to the cluster. + m_shape.setUserIndex(vertex2, m_new_clusters, + StridedIndexTypeCollection.impossibleIndex2()); + + // merge cordinates + boolean res = mergeVertices_(vertex1, vertex2); + + if (update_hash) { + int hash = m_hash_function.calculate_hash_from_vertex(vertex1); + m_shape.setUserIndex(vertex1, m_hash_values, hash); + } else { + + } + + return res; + } + + // recalculate coordinates of the vertices by averaging them using weights. + // return true if the coordinates has changed. + static boolean mergeVertices(Point pt_1, Point pt_2, double w_1, + int rank_1, double w_2, int rank_2, Point pt_res, double[] w_res, + int[] rank_res) { + assert (!pt_1.isEmpty() && !pt_2.isEmpty()); + boolean res = pt_1.equals(pt_2); + + if (rank_1 > rank_2) { + pt_res = pt_1; + if (w_res != null) { + rank_res[0] = rank_1; + w_res[0] = w_1; + } + return res; + } else if (rank_2 > rank_1) { + pt_res = pt_2; + if (w_res != null) { + rank_res[0] = rank_1; + w_res[0] = w_1; + } + return res; + } + + pt_res = pt_1; + Point2D pt2d = new Point2D(); + mergeVertices2D(pt_1.getXY(), pt_2.getXY(), w_1, rank_1, w_2, rank_2, + pt2d, w_res, rank_res); + pt_res.setXY(pt2d); + return res; + } + + static boolean mergeVertices2D(Point2D pt_1, Point2D pt_2, double w_1, + int rank_1, double w_2, int rank_2, Point2D pt_res, double[] w_res, + int[] rank_res) { + double w = w_1 + w_2; + boolean r = false; + double x = pt_1.x; + if (pt_1.x != pt_2.x) { + if (rank_1 == rank_2) + x = (pt_1.x * w_1 + pt_2.x * w_2) / w; + + r = true; + } + double y = pt_1.y; + if (pt_1.y != pt_2.y) { + if (rank_1 == rank_2) + y = (pt_1.y * w_1 + pt_2.y * w_2) / w; + + r = true; + } + + if (rank_1 != rank_2) { + if (rank_1 > rank_2) { + if (w_res != null) { + rank_res[0] = rank_1; + w_res[0] = w_1; + } + pt_res = pt_1; + } else { + if (w_res != null) { + rank_res[0] = rank_2; + w_res[0] = w_2; + } + pt_res = pt_2; + } + } else { + pt_res.setCoords(x, y); + if (w_res != null) { + w_res[0] = w; + rank_res[0] = rank_1; + } + } + + return r; + } + + boolean mergeVertices_(int vert_1, int vert_2) { + Point2D pt_1 = new Point2D(); + m_shape.getXY(vert_1, pt_1); + Point2D pt_2 = new Point2D(); + m_shape.getXY(vert_2, pt_2); + + double w_1 = m_shape.getWeight(vert_1); + double w_2 = m_shape.getWeight(vert_2); + double w = w_1 + w_2; + int r = 0; + double x = pt_1.x; + if (pt_1.x != pt_2.x) { + x = (pt_1.x * w_1 + pt_2.x * w_2) / w; + r++; + } + double y = pt_1.y; + if (pt_1.y != pt_2.y) { + y = (pt_1.y * w_1 + pt_2.y * w_2) / w; + r++; + } + + if (r > 0) + m_shape.setXY(vert_1, x, y); + + m_shape.setWeight(vert_1, w); + return r != 0; + } + + boolean clusterNonReciprocal_() { + int point_count = m_shape.getTotalPointCount(); + Envelope2D env = m_shape.getEnvelope2D(); + m_origin = env.getLowerLeft(); + double dim = Math.max(env.getHeight(), env.getWidth()); + double mincell = dim / (NumberUtils.intMax() - 1); + if (m_cell_size < mincell) { + m_cell_size = mincell; + m_inv_cell_size = 1.0 / m_cell_size; + } + + // This holds clusters. + m_clusters = new IndexMultiList(); + m_clusters.reserveLists(m_shape.getTotalPointCount() / 3 + 1); + m_clusters.reserveNodes(m_shape.getTotalPointCount() / 3 + 1); + + m_hash_values = m_shape.createUserIndex(); + m_new_clusters = m_shape.createUserIndex(); + + // Make the hash table. It serves a purpose of fine grain grid. + // Make it 25% larger than the 4 times point count to reduce the chance + // of collision. + // The 4 times comes from the fact that we check four neighbouring cells + // in the grid for each point. + m_hash_function = new ClusterHashFunction(m_shape, m_origin, + m_sqr_tolerance, m_inv_cell_size, m_hash_values); + m_hash_table = new IndexHashTable(4 * point_count / 3, m_hash_function); + m_hash_table.reserveElements(m_shape.getTotalPointCount()); + boolean b_clustered = false; + + // Go through all vertices stored in the m_shape and put the handles of + // the vertices into the clusters and the hash table. + for (int geometry = m_shape.getFirstGeometry(); geometry != -1; geometry = m_shape + .getNextGeometry(geometry)) { + for (int path = m_shape.getFirstPath(geometry); path != -1; path = m_shape + .getNextPath(path)) { + int vertex = m_shape.getFirstVertex(path); + for (int index = 0, nindex = m_shape.getPathSize(path); index < nindex; index++) { + assert (vertex != -1); + int hash = m_hash_function + .calculate_hash_from_vertex(vertex); + m_shape.setUserIndex(vertex, m_hash_values, hash); + m_hash_table.addElement(vertex, hash); // add cluster to the + // hash table + assert (m_shape.getUserIndex(vertex, m_new_clusters) == -1); + vertex = m_shape.getNextVertex(vertex); + } + } + } + + // m_hash_table->dbg_print_bucket_histogram_(); + + {// scope for candidates array + AttributeStreamOfInt32 candidates = new AttributeStreamOfInt32(0); + candidates.reserve(10); + + for (int geometry = m_shape.getFirstGeometry(); geometry != -1; geometry = m_shape + .getNextGeometry(geometry)) { + for (int path = m_shape.getFirstPath(geometry); path != -1; path = m_shape + .getNextPath(path)) { + int vertex = m_shape.getFirstVertex(path); + for (int index = 0, nindex = m_shape.getPathSize(path); index < nindex; index++) { + if (m_shape.getUserIndex(vertex, m_new_clusters) == StridedIndexTypeCollection + .impossibleIndex2()) { + vertex = m_shape.getNextVertex(vertex); + continue;// this vertex was merged with another + // cluster. It also was removed from the + // hash table. + } + + int hash = m_shape.getUserIndex(vertex, m_hash_values); + m_hash_table.deleteElement(vertex, hash); + + while (true) { + collectClusterCandidates_(vertex, candidates); + if (candidates.size() == 0) {// no candidate for + // clustering has + // been found for + // the cluster_1. + break; + } + + boolean clustered = false; + for (int candidate_index = 0, ncandidates = candidates + .size(); candidate_index < ncandidates; candidate_index++) { + int cluster_node = candidates + .get(candidate_index); + int other_vertex = m_hash_table + .getElement(cluster_node); + m_hash_table.deleteNode(cluster_node); + clustered |= mergeClusters_(vertex, + other_vertex, + candidate_index + 1 == ncandidates); + } + + b_clustered |= clustered; + candidates.clear(false); + // repeat search for the cluster candidates for + // cluster_1 + if (!clustered) + break;// positions did not change + } + + // m_shape->set_user_index(vertex, m_new_clusters, + // Strided_index_type_collection::impossible_index_2()); + vertex = m_shape.getNextVertex(vertex); + } + } + } + } + + if (b_clustered) { + applyClusterPositions_(); + } + + m_hash_table = null; + m_hash_function = null; + m_shape.removeUserIndex(m_hash_values); + m_shape.removeUserIndex(m_new_clusters); + + // output_debug_printf("total: %d\n",m_shape->get_total_point_count()); + // output_debug_printf("clustered: %d\n",m_dbg_candidate_check_count); + return b_clustered; + } + + void applyClusterPositions_() { + Point2D cluster_pt = new Point2D(); + // move vertices to the clustered positions. + for (int list = m_clusters.getFirstList(); list != -1; list = m_clusters + .getNextList(list)) { + int node = m_clusters.getFirst(list); + assert (node != -1); + int vertex = m_clusters.getElement(node); + m_shape.getXY(vertex, cluster_pt); + for (node = m_clusters.getNext(node); node != -1; node = m_clusters + .getNext(node)) { + int vertex_1 = m_clusters.getElement(node); + m_shape.setXY(vertex_1, cluster_pt); + } + } + } + + Clusterer() { + } } diff --git a/src/main/java/com/esri/core/geometry/CombineOperator.java b/src/main/java/com/esri/core/geometry/CombineOperator.java index e3d1bcc1..4cbee26e 100644 --- a/src/main/java/com/esri/core/geometry/CombineOperator.java +++ b/src/main/java/com/esri/core/geometry/CombineOperator.java @@ -36,16 +36,16 @@ public interface CombineOperator { * Operation on two geometries, returning a third. Examples include * Intersection, Difference, and so forth. * - * @param geom1 is the geometry instance to be operated on. - * @param geom2 is the geometry instance to be operated on. - * @param sr The spatial reference to get the tolerance value from. - * When sr is null, the tolerance is calculated from the input geometries. + * @param geom1 is the geometry instance to be operated on. + * @param geom2 is the geometry instance to be operated on. + * @param sr The spatial reference to get the tolerance value from. + * When sr is null, the tolerance is calculated from the input geometries. * @param progressTracker ProgressTracker instance that is used to cancel the lengthy operation. Can be null. * @return Returns the result geoemtry. In some cases the returned value can point to geom1 or geom2 * instance. For example, the OperatorIntersection may return geom2 when it is completely * inside of the geom1. */ public Geometry execute(Geometry geom1, Geometry geom2, - SpatialReference sr, ProgressTracker progressTracker); + SpatialReference sr, ProgressTracker progressTracker); } diff --git a/src/main/java/com/esri/core/geometry/ConstructOffset.java b/src/main/java/com/esri/core/geometry/ConstructOffset.java index 4d31472b..e461be0a 100644 --- a/src/main/java/com/esri/core/geometry/ConstructOffset.java +++ b/src/main/java/com/esri/core/geometry/ConstructOffset.java @@ -28,980 +28,980 @@ // Note: m_distance<0 offsets to the left, m_distance>0 offsets to the right class ConstructOffset { - ProgressTracker m_progressTracker; - Geometry m_inputGeometry; - double m_distance; - double m_tolerance; - OperatorOffset.JoinType m_joins; - double m_miterLimit; - - // multipath offset - static class GraphicPoint { - double x, y; - int m_next, m_prev; - double m; - int type; - - GraphicPoint(double x_, double y_) { - x = x_; - y = y_; - type = 0; - m = 0; - } - - GraphicPoint(Point2D r) { - x = r.x; - y = r.y; - type = 0; - m = 0; - } - - GraphicPoint(GraphicPoint pt) { - x = pt.x; - y = pt.y; - type = pt.type; - m = pt.m; - } - - GraphicPoint(GraphicPoint srcPt, double d, double angle) { - x = srcPt.x + d * Math.cos(angle); - y = srcPt.y + d * Math.sin(angle); - type = srcPt.type; - m = srcPt.m; - } - - GraphicPoint(GraphicPoint pt1, GraphicPoint pt2) { - x = (pt1.x + pt2.x) * 0.5; - y = (pt1.y + pt2.y) * 0.5; - type = pt1.type; - m = pt1.m; - } - - GraphicPoint(GraphicPoint pt1, GraphicPoint pt2, double ratio) { - x = pt1.x + (pt2.x - pt1.x) * ratio; - y = pt1.y + (pt2.y - pt1.y) * ratio; - type = pt1.type; - m = pt1.m; - } - - } - - ; - - static class GraphicRect { - double x1, x2, y1, y2; - } - - ; - - static class IntersectionInfo { - GraphicPoint pt; - double rFirst; - double rSecond; - boolean atExistingPt; - } - - ; - - ArrayList m_srcPts; - int m_srcPtCount; - ArrayList m_offsetPts; - int m_offsetPtCount; - - MultiPath m_resultPath; - int m_resultPoints; - double m_a1, m_a2; - boolean m_bBadSegs; - - ConstructOffset(ProgressTracker progressTracker) { - m_progressTracker = progressTracker; - } - - // static - static Geometry execute(Geometry inputGeometry, double distance, - OperatorOffset.JoinType joins, double miterLimit, double tolerance, - ProgressTracker progressTracker) { - if (inputGeometry == null) - throw new IllegalArgumentException(); - if (inputGeometry.getDimension() < 1)// can offset Polygons and - // Polylines only - throw new IllegalArgumentException(); - if (distance == 0 || inputGeometry.isEmpty()) - return inputGeometry; - ConstructOffset offset = new ConstructOffset(progressTracker); - offset.m_inputGeometry = inputGeometry; - offset.m_distance = distance; - offset.m_tolerance = tolerance; - offset.m_joins = joins; - offset.m_miterLimit = miterLimit; - return offset._ConstructOffset(); - } - - Geometry _OffsetLine() { - Line line = (Line) m_inputGeometry; - Point2D start = line.getStartXY(); - Point2D end = line.getEndXY(); - Point2D v = new Point2D(); - v.sub(end, start); - v.normalize(); - v.leftPerpendicular(); - v.scale(m_distance); - start.add(v); - end.add(v); - Line resLine = (Line) line.createInstance(); - line.setStartXY(start); - line.setEndXY(end); - return resLine; - } - - Geometry _OffsetEnvelope() { - Envelope envelope = (Envelope) m_inputGeometry; - if ((m_distance > 0) && (m_joins != OperatorOffset.JoinType.Miter)) { - Polygon poly = new Polygon(); - poly.addEnvelope(envelope, false); - m_inputGeometry = poly; - return _ConstructOffset(); - } - - Envelope resEnv = new Envelope(envelope.m_envelope); - resEnv.inflate(m_distance, m_distance); - return resEnv; - } - - private final double pi = Math.PI;// GEOMETRYX_PI; - private final double two_pi = Math.PI * 2;// GEOMETRYX_2PI; - private final double half_pi = Math.PI / 2;// GEOMETRYX_HalfPI; - private final double sqrt2 = 1.4142135623730950488016887242097; - private final double oneDegree = 0.01745329251994329576923690768489; - - private final int BAD_SEG = 0x0100; - private final int IS_END = 0x0200; - private final int CLOSING_SEG = 0x0400; - - void addPoint(GraphicPoint pt) { - m_offsetPts.add(pt); - m_offsetPtCount++; - } - - double scal(GraphicPoint pt1, GraphicPoint pt2, GraphicPoint pt3, - GraphicPoint pt4) { - return (pt2.x - pt1.x) * (pt4.x - pt3.x) + (pt2.y - pt1.y) - * (pt4.y - pt3.y); - } - - // offPt is the point to add. - // this point corresponds to the offset version of the end of seg1. - // it could generate a segment going in the opposite direction of the - // original segment - // this situation is handled here by adding an additional "bad" segment - void addPoint(GraphicPoint offPt, int i_src) { - if (m_offsetPtCount == 0) // TODO: can we have this outside of this - // method? - { - addPoint(offPt); - return; - } - - int n_src = m_srcPtCount; - GraphicPoint pt1, pt; - pt1 = m_srcPts.get(i_src == 0 ? n_src - 1 : i_src - 1); - pt = m_srcPts.get(i_src); - - // calculate scalar product to determine if the offset segment goes in - // the same/opposite direction compared to the original one - double s = scal(pt1, pt, m_offsetPts.get(m_offsetPtCount - 1), offPt); - if (s > 0) - // original segment and offset segment go in the same direction. Just - // add the point - { - addPoint(offPt); - return; - } - - if (s < 0) { - // we will add a loop. We need to make sure the points we introduce - // don't generate a "reversed" segment - // let's project the first point of the reversed segment - // (m_offsetPts + m_offsetPtCount - 1) to check - // if it falls on the good side of the original segment (scalar - // product sign again) - if (scal(pt1, pt, pt, m_offsetPts.get(m_offsetPtCount - 1)) > 0) { - GraphicPoint p; - - // change value of m_offsetPts + m_offsetPtCount - 1 - int k; - if (i_src == 0) - k = n_src - 2; - else if (i_src == 1) - k = n_src - 1; - else - k = i_src - 2; - GraphicPoint pt0 = m_srcPts.get(k); - - double a = Math.atan2(pt1.y - pt0.y, pt1.x - pt0.x); - p = new GraphicPoint(pt1, m_distance, a - half_pi); - m_offsetPts.set(m_offsetPtCount - 1, p); - - if (m_joins == OperatorOffset.JoinType.Bevel - || m_joins == OperatorOffset.JoinType.Miter) { - // this block is added as well as the commented BAD_SEG in - // the next block - p = new GraphicPoint(p, pt1); - addPoint(p); - - // "bad" segment - p = new GraphicPoint(pt1, m_distance, m_a1 + half_pi); - - GraphicPoint p_ = new GraphicPoint(p, pt1); - p_.type |= BAD_SEG; - addPoint(p_); - - addPoint(p); - } else { - // the working stuff for round and square - - // "bad" segment - p = new GraphicPoint(pt1, m_distance, m_a1 + half_pi); - p.type |= BAD_SEG; - addPoint(p); - } - - // add offPt - addPoint(offPt, i_src); - } else { - GraphicPoint p; - - // we don't add offPt but the loop containing the "bad" segment - p = new GraphicPoint(pt, m_distance, m_a1 + half_pi); - addPoint(p); - - if (m_joins == OperatorOffset.JoinType.Bevel - || m_joins == OperatorOffset.JoinType.Miter) { - // this block is added as well as the commented BAD_SEG in - // the next block - p = new GraphicPoint(p, pt); - addPoint(p); - - p = new GraphicPoint(pt, m_distance, m_a2 - half_pi); - GraphicPoint p_ = new GraphicPoint(p, pt); - p_.type |= BAD_SEG; - addPoint(p_); - - addPoint(p); - } else { - // the working stuff for round and square - p = new GraphicPoint(pt, m_distance, m_a2 - half_pi); - p.type |= BAD_SEG; - addPoint(p); - } - } - } - } - - boolean buildOffset() { - // make sure we have at least three points and no identical points - int i; - double a1, a2; - GraphicPoint pt, pt1, pt2; - GraphicPoint p; - - // number of points to deal with - int n = m_srcPtCount; - - m_offsetPtCount = 0; - - double flattenTolerance = m_tolerance * 0.5; - - double a1_0 = 0; - double a2_0 = 0; - for (i = 0; i < n; i++) { - pt = m_srcPts.get(i); - - // point before - if (i == 0) - pt1 = m_srcPts.get(n - 1); - else - pt1 = m_srcPts.get(i - 1); - - // point after - if (i == n - 1) - pt2 = m_srcPts.get(0); - else - pt2 = m_srcPts.get(i + 1); - - // angles of enclosing segments - double dx1 = pt1.x - pt.x; - double dy1 = pt1.y - pt.y; - double dx2 = pt2.x - pt.x; - double dy2 = pt2.y - pt.y; - a1 = Math.atan2(dy1, dx1); - a2 = Math.atan2(dy2, dx2); - m_a1 = a1; - m_a2 = a2; - if (i == 0) { - a1_0 = a1; - a2_0 = a2; - } - - // double dot_product = dx1 * dx2 + dy1 * dy2; - double cross_product = dx1 * dy2 - dx2 * dy1; - // boolean bInnerAngle = (cross_product == 0) ? (m_distance > 0) : - // (cross_product * m_distance >= 0.0); - - // check for inner angles (always managed the same, whatever the - // type of join) - double saved_a2 = a2; - if (a2 < a1) - a2 += two_pi; // this guaranties that (a1 + a2) / 2 is on the - // right side of the curve - if (cross_product * m_distance > 0.0) // inner angle - { - // inner angle - if (m_joins == OperatorOffset.JoinType.Bevel - || m_joins == OperatorOffset.JoinType.Miter) { - p = new GraphicPoint(pt, m_distance, a1 + half_pi); - addPoint(p); - - // this block is added as well as the commented BAD_SEG in - // the next block - double ratio = 0.001; // TODO: the higher the ratio, the - // better the result (shorter - // segments) - p = new GraphicPoint(pt, p, ratio); - addPoint(p); - - // this is the "bad" segment - p = new GraphicPoint(pt, m_distance, a2 - half_pi); - - GraphicPoint p_ = new GraphicPoint(pt, p, ratio); - p_.type |= BAD_SEG; - addPoint(p_); - - addPoint(p); - } else { - // this method works for square and round, but not bevel - double r = (a2 - a1) * 0.5; - double d = m_distance / Math.abs(Math.sin(r)); - p = new GraphicPoint(pt, d, (a1 + a2) * 0.5); - addPoint(p, i); // will deal with reversed segments - } - continue; - } - - // outer angles - // check if we have an end point first - if ((pt.type & IS_END) != 0) { - // TODO: deal with other options. assume rounded and - // perpendicular for now - // we need to use the outer regular polygon of the round join - // TODO: explain this in a doc - - // calculate the number of points based on a flatten tolerance - double r = 1.0 - flattenTolerance / Math.abs(m_distance); - long na = 1; - double da = (m_distance < 0) ? -pi : pi; // da is negative when - // m_offset is - // negative (???) - if (r > -1.0 && r < 1.0) { - double a = Math.acos(r) * 2; // angle where "arrow?" is less - // than flattenTolerance - // do not consider an angle smaller than a degree - if (a < oneDegree) - a = oneDegree; - na = (long) (pi / a + 1.5); - if (na > 1) - da /= na; - } - // add first point - double a = a1 + half_pi; - p = new GraphicPoint(pt, m_distance, a); - if (i == 0) - p.type |= CLOSING_SEG; // TODO: should we simplify this by - // considering the last point - // instead of the first one?? - addPoint(p, i); // will deal with reversed segments - - double d = m_distance / Math.cos(da / 2); - a += da / 2; - p = new GraphicPoint(pt, d, a); - p.type |= CLOSING_SEG; - addPoint(p); - - while (--na > 0) { - a += da; - p = new GraphicPoint(pt, d, a); - p.type |= CLOSING_SEG; - addPoint(p); - } - - // last point (optional except for the first point) - p = new GraphicPoint(pt, m_distance, a2 - half_pi); // this one - // is - // optional - // except - // for the - // first - // point - p.type |= CLOSING_SEG; - addPoint(p); - - continue; - } else if (m_joins == OperatorOffset.JoinType.Bevel) // bevel - { - p = new GraphicPoint(pt, m_distance, a1 + half_pi); - addPoint(p, i); // will deal with reversed segments - p = new GraphicPoint(pt, m_distance, a2 - half_pi); - addPoint(p); - continue; - } else if (m_joins == OperatorOffset.JoinType.Round) { - // we need to use the outer regular polygon of the round join - // TODO: explain this in a doc - - // calculate the number of points based on a flatten tolerance - double r = 1.0 - flattenTolerance / Math.abs(m_distance); - long na = 1; - double da = (a2 - half_pi) - (a1 + half_pi); // da is negative - // when - // m_distance is - // negative - if (r > -1.0 && r < 1.0) { - double a = Math.acos(r) * 2.0; // angle where "arrow?" is - // less than - // flattenTolerance - // do not consider an angle smaller than a degree - if (a < oneDegree) - a = oneDegree; - na = (long) (Math.abs(da) / a + 1.5); - if (na > 1) - da /= na; - } - double d = m_distance / Math.cos(da * 0.5); - double a = a1 + half_pi + da * 0.5; - p = new GraphicPoint(pt, d, a); - addPoint(p, i); // will deal with reversed segments - while (--na > 0) { - a += da; - p = new GraphicPoint(pt, d, a); - addPoint(p); - } - continue; - } else if (m_joins == OperatorOffset.JoinType.Miter) { - dx1 = pt1.x - pt.x; - dy1 = pt1.y - pt.y; - dx2 = pt2.x - pt.x; - dy2 = pt2.y - pt.y; - double d1 = Math.sqrt(dx1 * dx1 + dy1 * dy1); - double d2 = Math.sqrt(dx2 * dx2 + dy2 * dy2); - double cosa = (dx1 * dx2 + dy1 * dy2) / d1 / d2; - if (cosa > 1.0 - 1.0e-8) { - // there's a spike in the polygon boundary; this could - // happen when filtering out short segments in Init() - p = new GraphicPoint(pt, sqrt2 * m_distance, a2 - pi * 0.25); - addPoint(p, i); - p = new GraphicPoint(pt, sqrt2 * m_distance, a2 + pi * 0.25); - addPoint(p); - continue; - } - // original miter code - // if (m_miterLimit * m_miterLimit * (1 - cosa) < 2) - // { - // // bevel join - // p = new GraphicPoint(pt, m_distance, a1 + half_pi); - // AddPoint(p, src_poly, srcPtCount, i); // will deal with - // reversed segments - // p = new GraphicPoint(pt, m_distance, a2 - half_pi); - // AddPoint(p); - // continue; - // } - double distanceFromCorner = Math.abs(m_distance - / Math.sin(Math.acos(cosa) * 0.5)); - double bevelDistance = Math.abs(m_miterLimit * m_distance); - if (distanceFromCorner > bevelDistance) { - double r = (a2 - a1) * 0.5; - double d = m_distance / Math.abs(Math.sin(r)); - p = new GraphicPoint(pt, d, (a1 + a2) * 0.5); - - // construct bevel points, see comment in - // c:\ArcGIS\System\Geometry\Geometry\ConstructCurveImpl.cpp, - // ESRI::OffsetCurve::EstimateBevelPoints - Point2D corner = new Point2D(p.x, p.y); - Point2D through = new Point2D(pt.x, pt.y); - Point2D delta = new Point2D(); - delta.sub(corner, through); - - // Point2D midPoint = through + delta * (bevelDistance / - // delta.Length()); - Point2D midPoint = new Point2D(); - midPoint.scaleAdd(bevelDistance / delta.length(), delta, - through); - - double sideLength = Math.sqrt(distanceFromCorner - * distanceFromCorner - m_distance * m_distance), halfWidth = (distanceFromCorner - bevelDistance) - * Math.abs(m_distance) / sideLength; - - // delta = delta.RotateDirect(0.0, m_distance > 0.0 ? -1.0 : - // 1.0) * (halfWidth/delta.Length()); - if (m_distance > 0.0) - delta.leftPerpendicular(); - else - delta.rightPerpendicular(); - delta.scale(halfWidth / delta.length()); - - Point2D from = new Point2D(); - from.add(midPoint, delta); - Point2D to = new Point2D(); - to.sub(midPoint, delta); - p = new GraphicPoint(from); - // _ASSERT(::_finite(p.x)); - // _ASSERT(::_finite(p.y)); - addPoint(p, i); - p = new GraphicPoint(to); - // _ASSERT(::_finite(p.x)); - // _ASSERT(::_finite(p.y)); - addPoint(p); - continue; - } - // miter join - double r = (a2 - a1) * 0.5; - double d = m_distance / Math.abs(Math.sin(r)); // r should not - // be null - // (trapped by - // the bevel - // case) - p = new GraphicPoint(pt, d, (a1 + a2) * 0.5); - addPoint(p, i); // will deal with reversed segments - continue; - } else // the new "square" join - { - a2 = saved_a2; - - // identify if angle is less than pi/2 - // in this case, we introduce a segment that is perpendicular to - // the bissector of the angle - // TODO: see figure X for details - boolean bAddSegment; - if (m_distance > 0.0) { - if (a2 > a1) // > and not >= - a2 -= two_pi; - bAddSegment = (a1 - a2 < half_pi); - } else { - if (a2 < a1) // < and not <= - a2 += two_pi; - bAddSegment = (a2 - a1 < half_pi); - } - if (bAddSegment) { - // make it continuous when angle is pi/2 (but not tangent to - // the round join) - double d = m_distance * sqrt2; - double a; - - if (d < 0.0) - a = a1 + pi * 0.25; - else - a = a1 + 3.0 * pi * 0.25; - p = new GraphicPoint(pt, d, a); - addPoint(p, i); - - if (d < 0) - a = a2 - pi * 0.25; - else - a = a2 - 3.0 * pi * 0.25; - p = new GraphicPoint(pt, d, a); - addPoint(p); - } else // standard case: we just add the intersection point of - // offset segments - { - double r = (a2 - a1) * 0.5; - double d = m_distance / Math.abs(Math.sin(r)); - if (a2 < a1) - a2 += two_pi; // this guaranties that (a1 + a2) / 2 is - // on the right side with a positive - // offset - p = new GraphicPoint(pt, d, (a1 + a2) / 2); - addPoint(p, i); - } - } - } - - // closing point - m_a1 = a1_0; - m_a2 = a2_0; - addPoint(m_offsetPts.get(0), 0); - - // make sure the first point matches the last (in case a problem of - // reversed segment happens there) - pt = new GraphicPoint(m_offsetPts.get(m_offsetPtCount - 1)); - m_offsetPts.set(0, pt); - - // remove loops - return removeBadSegsFast(); - } - - void addPart(int iStart, int cPts) { - if (cPts < 2) - return; - - for (int i = 0; i < cPts; i++) { - GraphicPoint pt = m_offsetPts.get(iStart + i); - if (i != 0) - m_resultPath.lineTo(new Point2D(pt.x, pt.y)); - else - m_resultPath.startPath(new Point2D(pt.x, pt.y)); - } - } - - void _OffsetPath(MultiPath multiPath, int pathIndex, MultiPath resultingPath) { - int startVertex = multiPath.getPathStart(pathIndex); - int endVertex = multiPath.getPathEnd(pathIndex); - - m_offsetPts = new ArrayList(); - - // test if part is closed - m_resultPath = resultingPath; - m_resultPoints = 0; - if (multiPath.isClosedPath(pathIndex)) { - // check if last point is a duplicate of first - Point2D ptStart = multiPath.getXY(startVertex); - while (multiPath.getXY(endVertex - 1).isEqual(ptStart)) - endVertex--; - - // we need at least three points for a polygon - if (endVertex - startVertex >= 2) { - m_srcPtCount = endVertex - startVertex; - m_srcPts = new ArrayList(m_srcPtCount); - // TODO: may throw std::bad:alloc() - for (int i = startVertex; i < endVertex; i++) - m_srcPts.add(new GraphicPoint(multiPath.getXY(i))); - - if (buildOffset()) - addPart(0, m_offsetPtCount - 1); // do not repeat closing - // point - } - } else { - // remove duplicate points at extremities - Point2D ptStart = multiPath.getXY(startVertex); - while ((startVertex < endVertex) - && multiPath.getXY(startVertex + 1).isEqual(ptStart)) - startVertex++; - Point2D ptEnd = multiPath.getXY(endVertex - 1); - while ((startVertex < endVertex) - && multiPath.getXY(endVertex - 2).isEqual(ptEnd)) - endVertex--; - - // we need at least two points for a polyline - if (endVertex - startVertex >= 2) { - // close the line and mark the opposite segments as non valid - m_srcPtCount = (endVertex - startVertex) * 2 - 2; - m_srcPts = new ArrayList(m_srcPtCount); - // TODO: may throw std::bad:alloc() - - GraphicPoint pt = new GraphicPoint(multiPath.getXY(startVertex)); - pt.type |= IS_END + CLOSING_SEG; - m_srcPts.add(pt); - - for (int i = startVertex + 1; i < endVertex - 1; i++) { - pt = new GraphicPoint(multiPath.getXY(i)); - m_srcPts.add(pt); - } - - pt = new GraphicPoint(multiPath.getXY(endVertex - 1)); - pt.type |= IS_END; - m_srcPts.add(pt); - - for (int i = endVertex - 2; i >= startVertex + 1; i--) { - pt = new GraphicPoint(multiPath.getXY(i)); - pt.type |= CLOSING_SEG; - m_srcPts.add(pt); - } - - if (buildOffset()) - - if (m_offsetPts.size() >= 2) { - // extract the part that doesn't have the CLOSING_SEG - // attribute - - int iStart = -1; - int iEnd = -1; - boolean prevClosed = (m_offsetPts - .get(m_offsetPtCount - 1).type & CLOSING_SEG) != 0; - if (!prevClosed) - iStart = 0; - for (int i = 1; i < m_offsetPtCount; i++) { - boolean closed = (m_offsetPts.get(i).type & CLOSING_SEG) != 0; - if (!closed) { - if (prevClosed) { - // if ((m_offsetPts[i - 1].type & MOVE_TO) - // == 0) - // m_offsetPts[i - 1].type += MOVE_TO - - // LINE_TO; - iStart = i - 1; - } - } else { - if (!prevClosed) { - iEnd = i - 1; - // for (long i = iStart; i <= iEnd; i++) - // m_offsetPts[i].type &= OUR_FLAGS_MASK; - if (iEnd - iStart + 1 > 1) - addPart(iStart, iEnd - iStart + 1); - } - } - prevClosed = closed; - } - if (!prevClosed) { - iEnd = m_offsetPtCount - 1; - // for (long i = iStart; i <= iEnd; i++) - // m_offsetPts[i].type &= OUR_FLAGS_MASK; - if (iEnd - iStart + 1 > 1) - addPart(iStart, iEnd - iStart + 1); - } - } else { - int iStart = 0; - int iEnd = m_offsetPtCount - 1; - if (iStart >= 0 && iEnd - iStart >= 1) { - // for (long i = iStart; i <= iEnd; i++) - // m_offsetPts[i].type &= OUR_FLAGS_MASK; - addPart(iStart, iEnd - iStart + 1); - } - } - } - } - - // clear source - m_srcPts = null; - m_srcPtCount = 0; - // free offset buffer - m_offsetPts = null; - m_offsetPtCount = 0; - } - - boolean removeBadSegsFast() { - boolean bWrong = false; - - // initialize circular doubly-linked list - // skip last point which is dup of first point - for (int i = 0; i < m_offsetPtCount; i++) { - GraphicPoint pt = m_offsetPts.get(i); - pt.m_next = i + 1; - pt.m_prev = i - 1; - m_offsetPts.set(i, pt); - } - - // need to update the first and last elements - GraphicPoint pt; - - pt = m_offsetPts.get(0); - pt.m_prev = m_offsetPtCount - 2; - m_offsetPts.set(0, pt); - - pt = m_offsetPts.get(m_offsetPtCount - 2); - pt.m_next = 0; - m_offsetPts.set(m_offsetPtCount - 2, pt); - - int w = 0; - for (int i = 0; i < m_offsetPtCount; i++) { - if ((m_offsetPts.get(w).type & BAD_SEG) != 0) { - int wNext = deleteClosedSeg(w); - if (wNext != -1) - w = wNext; - else { - bWrong = true; - break; - } - } else - w = m_offsetPts.get(w).m_next; - } - - if (bWrong) - return false; - - // w is the index of a known good (i.e. surviving ) point in the offset - // array - compressOffsetArray(w); - return true; - } - - int deleteClosedSeg(int seg) { - int n = m_offsetPtCount - 1; // number of segments - - // check combinations of segments - int ip0 = seg, ip, im; - - for (int i = 1; i <= n - 2; i++) { - ip0 = m_offsetPts.get(ip0).m_next; - - ip = ip0; - im = seg; - - for (int j = 1; j <= i; j++) { - im = m_offsetPts.get(im).m_prev; - - if ((m_offsetPts.get(im).type & BAD_SEG) == 0 - && (m_offsetPts.get(ip).type & BAD_SEG) == 0) { - int rSegNext = handleClosedIntersection(im, ip); - if (rSegNext != -1) - return rSegNext; - } - - ip = m_offsetPts.get(ip).m_prev; - } - } - - return -1; - } - - // line segments defined by (im-1, im) and (ip-1, ip) - int handleClosedIntersection(int im, int ip) { - GraphicPoint pt1, pt2, pt3, pt4; - pt1 = m_offsetPts.get(m_offsetPts.get(im).m_prev); - pt2 = m_offsetPts.get(im); - pt3 = m_offsetPts.get(m_offsetPts.get(ip).m_prev); - pt4 = m_offsetPts.get(ip); - - if (!sectGraphicRect(pt1, pt2, pt3, pt4)) - return -1; - - // intersection - IntersectionInfo ii = new IntersectionInfo(); - if (findIntersection(pt1, pt2, pt3, pt4, ii) && !ii.atExistingPt) - if (Math.signum((pt2.x - pt1.x) * (pt4.y - pt3.y) - (pt2.y - pt1.y) - * (pt4.x - pt3.x)) != Math.signum(m_distance)) { - int prev0 = m_offsetPts.get(im).m_prev; - - ii.pt.type = pt2.type; - ii.pt.m_next = ip; - ii.pt.m_prev = prev0; - m_offsetPts.set(im, ii.pt); - - ii.pt = m_offsetPts.get(ip); - ii.pt.m_prev = im; - m_offsetPts.set(ip, ii.pt); - - return ip; - } - return -1; - } - - boolean sectGraphicRect(GraphicPoint pt1, GraphicPoint pt2, - GraphicPoint pt3, GraphicPoint pt4) { - return (Math.max(pt1.x, pt2.x) >= Math.min(pt3.x, pt4.x) - && Math.max(pt3.x, pt4.x) >= Math.min(pt1.x, pt2.x) - && Math.max(pt1.y, pt2.y) >= Math.min(pt3.y, pt4.y) && Math - .max(pt3.y, pt4.y) >= Math.min(pt1.y, pt2.y)); - } - - boolean findIntersection(GraphicPoint bp1, GraphicPoint bp2, - GraphicPoint bp3, GraphicPoint bp4, - IntersectionInfo intersectionInfo) { - intersectionInfo.atExistingPt = false; - - // Note: test if rectangles intersect already done by caller - - // intersection - double i, j, r, r1; - i = (bp2.y - bp1.y) * (bp4.x - bp3.x) - (bp2.x - bp1.x) - * (bp4.y - bp3.y); - j = (bp3.y - bp1.y) * (bp2.x - bp1.x) - (bp3.x - bp1.x) - * (bp2.y - bp1.y); - if (i == 0.0) - r = 2.0; - else - r = j / i; - - if ((r >= 0.0) && (r <= 1.0)) { - r1 = r; - i = (bp4.y - bp3.y) * (bp2.x - bp1.x) - (bp4.x - bp3.x) - * (bp2.y - bp1.y); - j = (bp1.y - bp3.y) * (bp4.x - bp3.x) - (bp1.x - bp3.x) - * (bp4.y - bp3.y); - - if (i == 0.0) - r = 2.0; - else - r = j / i; - - if ((r >= 0.0) && (r <= 1.0)) { - intersectionInfo.pt = new GraphicPoint(bp1.x + r - * (bp2.x - bp1.x), bp1.y + r * (bp2.y - bp1.y)); - intersectionInfo.pt.m = bp3.m + r1 * (bp4.m - bp3.m); - if (((r1 == 0.0) || (r1 == 1.0)) && ((r == 0.0) || (r == 1.0))) - intersectionInfo.atExistingPt = true; - - intersectionInfo.rFirst = r; - intersectionInfo.rSecond = r1; - - if (((r1 == 0.0) || (r1 == 1.0)) && ((r > 0.0) && (r < 1.0)) - || ((r == 0.0) || (r == 1.0)) - && ((r1 > 0.0) && (r1 < 1.0))) { - return false; - } - - return true; - } - } - return false; - } - - // i0 is the index of a known good point in the offset points array; that - // is, its the index of a point that isn't part of a deleted loop - void compressOffsetArray(int i0) { - int i_ = i0; - while (m_offsetPts.get(i_).m_prev < i_) - i_ = m_offsetPts.get(i_).m_prev; - - int j = 0, i = i_; - - do { - GraphicPoint pt = m_offsetPts.get(i); - m_offsetPts.set(j, pt); - i = pt.m_next; - j++; - } while (i != i_); - - m_offsetPts.set(j, m_offsetPts.get(0)); // duplicate closing point - - m_offsetPtCount = j + 1; - } - - void _OffsetMultiPath(MultiPath resultingPath) { - // we process all path independently, then merge the results - MultiPath multiPath = (MultiPath) m_inputGeometry; - SegmentIterator segmentIterator = multiPath.querySegmentIterator(); - if (segmentIterator == null) - return; // TODO: strategy on error? - - segmentIterator.resetToFirstPath(); - int pathIndex = -1; - while (segmentIterator.nextPath()) { - pathIndex++; - _OffsetPath(multiPath, pathIndex, resultingPath); - } - } - - Geometry _ConstructOffset() { - int gt = m_inputGeometry.getType().value(); - if (gt == Geometry.GeometryType.Line) { - return _OffsetLine(); - } - if (gt == Geometry.GeometryType.Envelope) { - return _OffsetEnvelope(); - } - if (Geometry.isSegment(gt)) { - Polyline poly = new Polyline(); - poly.addSegment((Segment) m_inputGeometry, true); - m_inputGeometry = poly; - return _ConstructOffset(); - } - if (gt == Geometry.GeometryType.Polyline) { - Polyline polyline = new Polyline(); - _OffsetMultiPath(polyline); - return polyline; - } - if (gt == Geometry.GeometryType.Polygon) { - Polygon polygon = new Polygon(); - _OffsetMultiPath(polygon); - return polygon; - } - // throw new GeometryException("not implemented"); - return null; - } + ProgressTracker m_progressTracker; + Geometry m_inputGeometry; + double m_distance; + double m_tolerance; + OperatorOffset.JoinType m_joins; + double m_miterLimit; + + // multipath offset + static class GraphicPoint { + double x, y; + int m_next, m_prev; + double m; + int type; + + GraphicPoint(double x_, double y_) { + x = x_; + y = y_; + type = 0; + m = 0; + } + + GraphicPoint(Point2D r) { + x = r.x; + y = r.y; + type = 0; + m = 0; + } + + GraphicPoint(GraphicPoint pt) { + x = pt.x; + y = pt.y; + type = pt.type; + m = pt.m; + } + + GraphicPoint(GraphicPoint srcPt, double d, double angle) { + x = srcPt.x + d * Math.cos(angle); + y = srcPt.y + d * Math.sin(angle); + type = srcPt.type; + m = srcPt.m; + } + + GraphicPoint(GraphicPoint pt1, GraphicPoint pt2) { + x = (pt1.x + pt2.x) * 0.5; + y = (pt1.y + pt2.y) * 0.5; + type = pt1.type; + m = pt1.m; + } + + GraphicPoint(GraphicPoint pt1, GraphicPoint pt2, double ratio) { + x = pt1.x + (pt2.x - pt1.x) * ratio; + y = pt1.y + (pt2.y - pt1.y) * ratio; + type = pt1.type; + m = pt1.m; + } + + } + + ; + + static class GraphicRect { + double x1, x2, y1, y2; + } + + ; + + static class IntersectionInfo { + GraphicPoint pt; + double rFirst; + double rSecond; + boolean atExistingPt; + } + + ; + + ArrayList m_srcPts; + int m_srcPtCount; + ArrayList m_offsetPts; + int m_offsetPtCount; + + MultiPath m_resultPath; + int m_resultPoints; + double m_a1, m_a2; + boolean m_bBadSegs; + + ConstructOffset(ProgressTracker progressTracker) { + m_progressTracker = progressTracker; + } + + // static + static Geometry execute(Geometry inputGeometry, double distance, + OperatorOffset.JoinType joins, double miterLimit, double tolerance, + ProgressTracker progressTracker) { + if (inputGeometry == null) + throw new IllegalArgumentException(); + if (inputGeometry.getDimension() < 1)// can offset Polygons and + // Polylines only + throw new IllegalArgumentException(); + if (distance == 0 || inputGeometry.isEmpty()) + return inputGeometry; + ConstructOffset offset = new ConstructOffset(progressTracker); + offset.m_inputGeometry = inputGeometry; + offset.m_distance = distance; + offset.m_tolerance = tolerance; + offset.m_joins = joins; + offset.m_miterLimit = miterLimit; + return offset._ConstructOffset(); + } + + Geometry _OffsetLine() { + Line line = (Line) m_inputGeometry; + Point2D start = line.getStartXY(); + Point2D end = line.getEndXY(); + Point2D v = new Point2D(); + v.sub(end, start); + v.normalize(); + v.leftPerpendicular(); + v.scale(m_distance); + start.add(v); + end.add(v); + Line resLine = (Line) line.createInstance(); + line.setStartXY(start); + line.setEndXY(end); + return resLine; + } + + Geometry _OffsetEnvelope() { + Envelope envelope = (Envelope) m_inputGeometry; + if ((m_distance > 0) && (m_joins != OperatorOffset.JoinType.Miter)) { + Polygon poly = new Polygon(); + poly.addEnvelope(envelope, false); + m_inputGeometry = poly; + return _ConstructOffset(); + } + + Envelope resEnv = new Envelope(envelope.m_envelope); + resEnv.inflate(m_distance, m_distance); + return resEnv; + } + + private final double pi = Math.PI;// GEOMETRYX_PI; + private final double two_pi = Math.PI * 2;// GEOMETRYX_2PI; + private final double half_pi = Math.PI / 2;// GEOMETRYX_HalfPI; + private final double sqrt2 = 1.4142135623730950488016887242097; + private final double oneDegree = 0.01745329251994329576923690768489; + + private final int BAD_SEG = 0x0100; + private final int IS_END = 0x0200; + private final int CLOSING_SEG = 0x0400; + + void addPoint(GraphicPoint pt) { + m_offsetPts.add(pt); + m_offsetPtCount++; + } + + double scal(GraphicPoint pt1, GraphicPoint pt2, GraphicPoint pt3, + GraphicPoint pt4) { + return (pt2.x - pt1.x) * (pt4.x - pt3.x) + (pt2.y - pt1.y) + * (pt4.y - pt3.y); + } + + // offPt is the point to add. + // this point corresponds to the offset version of the end of seg1. + // it could generate a segment going in the opposite direction of the + // original segment + // this situation is handled here by adding an additional "bad" segment + void addPoint(GraphicPoint offPt, int i_src) { + if (m_offsetPtCount == 0) // TODO: can we have this outside of this + // method? + { + addPoint(offPt); + return; + } + + int n_src = m_srcPtCount; + GraphicPoint pt1, pt; + pt1 = m_srcPts.get(i_src == 0 ? n_src - 1 : i_src - 1); + pt = m_srcPts.get(i_src); + + // calculate scalar product to determine if the offset segment goes in + // the same/opposite direction compared to the original one + double s = scal(pt1, pt, m_offsetPts.get(m_offsetPtCount - 1), offPt); + if (s > 0) + // original segment and offset segment go in the same direction. Just + // add the point + { + addPoint(offPt); + return; + } + + if (s < 0) { + // we will add a loop. We need to make sure the points we introduce + // don't generate a "reversed" segment + // let's project the first point of the reversed segment + // (m_offsetPts + m_offsetPtCount - 1) to check + // if it falls on the good side of the original segment (scalar + // product sign again) + if (scal(pt1, pt, pt, m_offsetPts.get(m_offsetPtCount - 1)) > 0) { + GraphicPoint p; + + // change value of m_offsetPts + m_offsetPtCount - 1 + int k; + if (i_src == 0) + k = n_src - 2; + else if (i_src == 1) + k = n_src - 1; + else + k = i_src - 2; + GraphicPoint pt0 = m_srcPts.get(k); + + double a = Math.atan2(pt1.y - pt0.y, pt1.x - pt0.x); + p = new GraphicPoint(pt1, m_distance, a - half_pi); + m_offsetPts.set(m_offsetPtCount - 1, p); + + if (m_joins == OperatorOffset.JoinType.Bevel + || m_joins == OperatorOffset.JoinType.Miter) { + // this block is added as well as the commented BAD_SEG in + // the next block + p = new GraphicPoint(p, pt1); + addPoint(p); + + // "bad" segment + p = new GraphicPoint(pt1, m_distance, m_a1 + half_pi); + + GraphicPoint p_ = new GraphicPoint(p, pt1); + p_.type |= BAD_SEG; + addPoint(p_); + + addPoint(p); + } else { + // the working stuff for round and square + + // "bad" segment + p = new GraphicPoint(pt1, m_distance, m_a1 + half_pi); + p.type |= BAD_SEG; + addPoint(p); + } + + // add offPt + addPoint(offPt, i_src); + } else { + GraphicPoint p; + + // we don't add offPt but the loop containing the "bad" segment + p = new GraphicPoint(pt, m_distance, m_a1 + half_pi); + addPoint(p); + + if (m_joins == OperatorOffset.JoinType.Bevel + || m_joins == OperatorOffset.JoinType.Miter) { + // this block is added as well as the commented BAD_SEG in + // the next block + p = new GraphicPoint(p, pt); + addPoint(p); + + p = new GraphicPoint(pt, m_distance, m_a2 - half_pi); + GraphicPoint p_ = new GraphicPoint(p, pt); + p_.type |= BAD_SEG; + addPoint(p_); + + addPoint(p); + } else { + // the working stuff for round and square + p = new GraphicPoint(pt, m_distance, m_a2 - half_pi); + p.type |= BAD_SEG; + addPoint(p); + } + } + } + } + + boolean buildOffset() { + // make sure we have at least three points and no identical points + int i; + double a1, a2; + GraphicPoint pt, pt1, pt2; + GraphicPoint p; + + // number of points to deal with + int n = m_srcPtCount; + + m_offsetPtCount = 0; + + double flattenTolerance = m_tolerance * 0.5; + + double a1_0 = 0; + double a2_0 = 0; + for (i = 0; i < n; i++) { + pt = m_srcPts.get(i); + + // point before + if (i == 0) + pt1 = m_srcPts.get(n - 1); + else + pt1 = m_srcPts.get(i - 1); + + // point after + if (i == n - 1) + pt2 = m_srcPts.get(0); + else + pt2 = m_srcPts.get(i + 1); + + // angles of enclosing segments + double dx1 = pt1.x - pt.x; + double dy1 = pt1.y - pt.y; + double dx2 = pt2.x - pt.x; + double dy2 = pt2.y - pt.y; + a1 = Math.atan2(dy1, dx1); + a2 = Math.atan2(dy2, dx2); + m_a1 = a1; + m_a2 = a2; + if (i == 0) { + a1_0 = a1; + a2_0 = a2; + } + + // double dot_product = dx1 * dx2 + dy1 * dy2; + double cross_product = dx1 * dy2 - dx2 * dy1; + // boolean bInnerAngle = (cross_product == 0) ? (m_distance > 0) : + // (cross_product * m_distance >= 0.0); + + // check for inner angles (always managed the same, whatever the + // type of join) + double saved_a2 = a2; + if (a2 < a1) + a2 += two_pi; // this guaranties that (a1 + a2) / 2 is on the + // right side of the curve + if (cross_product * m_distance > 0.0) // inner angle + { + // inner angle + if (m_joins == OperatorOffset.JoinType.Bevel + || m_joins == OperatorOffset.JoinType.Miter) { + p = new GraphicPoint(pt, m_distance, a1 + half_pi); + addPoint(p); + + // this block is added as well as the commented BAD_SEG in + // the next block + double ratio = 0.001; // TODO: the higher the ratio, the + // better the result (shorter + // segments) + p = new GraphicPoint(pt, p, ratio); + addPoint(p); + + // this is the "bad" segment + p = new GraphicPoint(pt, m_distance, a2 - half_pi); + + GraphicPoint p_ = new GraphicPoint(pt, p, ratio); + p_.type |= BAD_SEG; + addPoint(p_); + + addPoint(p); + } else { + // this method works for square and round, but not bevel + double r = (a2 - a1) * 0.5; + double d = m_distance / Math.abs(Math.sin(r)); + p = new GraphicPoint(pt, d, (a1 + a2) * 0.5); + addPoint(p, i); // will deal with reversed segments + } + continue; + } + + // outer angles + // check if we have an end point first + if ((pt.type & IS_END) != 0) { + // TODO: deal with other options. assume rounded and + // perpendicular for now + // we need to use the outer regular polygon of the round join + // TODO: explain this in a doc + + // calculate the number of points based on a flatten tolerance + double r = 1.0 - flattenTolerance / Math.abs(m_distance); + long na = 1; + double da = (m_distance < 0) ? -pi : pi; // da is negative when + // m_offset is + // negative (???) + if (r > -1.0 && r < 1.0) { + double a = Math.acos(r) * 2; // angle where "arrow?" is less + // than flattenTolerance + // do not consider an angle smaller than a degree + if (a < oneDegree) + a = oneDegree; + na = (long) (pi / a + 1.5); + if (na > 1) + da /= na; + } + // add first point + double a = a1 + half_pi; + p = new GraphicPoint(pt, m_distance, a); + if (i == 0) + p.type |= CLOSING_SEG; // TODO: should we simplify this by + // considering the last point + // instead of the first one?? + addPoint(p, i); // will deal with reversed segments + + double d = m_distance / Math.cos(da / 2); + a += da / 2; + p = new GraphicPoint(pt, d, a); + p.type |= CLOSING_SEG; + addPoint(p); + + while (--na > 0) { + a += da; + p = new GraphicPoint(pt, d, a); + p.type |= CLOSING_SEG; + addPoint(p); + } + + // last point (optional except for the first point) + p = new GraphicPoint(pt, m_distance, a2 - half_pi); // this one + // is + // optional + // except + // for the + // first + // point + p.type |= CLOSING_SEG; + addPoint(p); + + continue; + } else if (m_joins == OperatorOffset.JoinType.Bevel) // bevel + { + p = new GraphicPoint(pt, m_distance, a1 + half_pi); + addPoint(p, i); // will deal with reversed segments + p = new GraphicPoint(pt, m_distance, a2 - half_pi); + addPoint(p); + continue; + } else if (m_joins == OperatorOffset.JoinType.Round) { + // we need to use the outer regular polygon of the round join + // TODO: explain this in a doc + + // calculate the number of points based on a flatten tolerance + double r = 1.0 - flattenTolerance / Math.abs(m_distance); + long na = 1; + double da = (a2 - half_pi) - (a1 + half_pi); // da is negative + // when + // m_distance is + // negative + if (r > -1.0 && r < 1.0) { + double a = Math.acos(r) * 2.0; // angle where "arrow?" is + // less than + // flattenTolerance + // do not consider an angle smaller than a degree + if (a < oneDegree) + a = oneDegree; + na = (long) (Math.abs(da) / a + 1.5); + if (na > 1) + da /= na; + } + double d = m_distance / Math.cos(da * 0.5); + double a = a1 + half_pi + da * 0.5; + p = new GraphicPoint(pt, d, a); + addPoint(p, i); // will deal with reversed segments + while (--na > 0) { + a += da; + p = new GraphicPoint(pt, d, a); + addPoint(p); + } + continue; + } else if (m_joins == OperatorOffset.JoinType.Miter) { + dx1 = pt1.x - pt.x; + dy1 = pt1.y - pt.y; + dx2 = pt2.x - pt.x; + dy2 = pt2.y - pt.y; + double d1 = Math.sqrt(dx1 * dx1 + dy1 * dy1); + double d2 = Math.sqrt(dx2 * dx2 + dy2 * dy2); + double cosa = (dx1 * dx2 + dy1 * dy2) / d1 / d2; + if (cosa > 1.0 - 1.0e-8) { + // there's a spike in the polygon boundary; this could + // happen when filtering out short segments in Init() + p = new GraphicPoint(pt, sqrt2 * m_distance, a2 - pi * 0.25); + addPoint(p, i); + p = new GraphicPoint(pt, sqrt2 * m_distance, a2 + pi * 0.25); + addPoint(p); + continue; + } + // original miter code + // if (m_miterLimit * m_miterLimit * (1 - cosa) < 2) + // { + // // bevel join + // p = new GraphicPoint(pt, m_distance, a1 + half_pi); + // AddPoint(p, src_poly, srcPtCount, i); // will deal with + // reversed segments + // p = new GraphicPoint(pt, m_distance, a2 - half_pi); + // AddPoint(p); + // continue; + // } + double distanceFromCorner = Math.abs(m_distance + / Math.sin(Math.acos(cosa) * 0.5)); + double bevelDistance = Math.abs(m_miterLimit * m_distance); + if (distanceFromCorner > bevelDistance) { + double r = (a2 - a1) * 0.5; + double d = m_distance / Math.abs(Math.sin(r)); + p = new GraphicPoint(pt, d, (a1 + a2) * 0.5); + + // construct bevel points, see comment in + // c:\ArcGIS\System\Geometry\Geometry\ConstructCurveImpl.cpp, + // ESRI::OffsetCurve::EstimateBevelPoints + Point2D corner = new Point2D(p.x, p.y); + Point2D through = new Point2D(pt.x, pt.y); + Point2D delta = new Point2D(); + delta.sub(corner, through); + + // Point2D midPoint = through + delta * (bevelDistance / + // delta.Length()); + Point2D midPoint = new Point2D(); + midPoint.scaleAdd(bevelDistance / delta.length(), delta, + through); + + double sideLength = Math.sqrt(distanceFromCorner + * distanceFromCorner - m_distance * m_distance), halfWidth = (distanceFromCorner - bevelDistance) + * Math.abs(m_distance) / sideLength; + + // delta = delta.RotateDirect(0.0, m_distance > 0.0 ? -1.0 : + // 1.0) * (halfWidth/delta.Length()); + if (m_distance > 0.0) + delta.leftPerpendicular(); + else + delta.rightPerpendicular(); + delta.scale(halfWidth / delta.length()); + + Point2D from = new Point2D(); + from.add(midPoint, delta); + Point2D to = new Point2D(); + to.sub(midPoint, delta); + p = new GraphicPoint(from); + // _ASSERT(::_finite(p.x)); + // _ASSERT(::_finite(p.y)); + addPoint(p, i); + p = new GraphicPoint(to); + // _ASSERT(::_finite(p.x)); + // _ASSERT(::_finite(p.y)); + addPoint(p); + continue; + } + // miter join + double r = (a2 - a1) * 0.5; + double d = m_distance / Math.abs(Math.sin(r)); // r should not + // be null + // (trapped by + // the bevel + // case) + p = new GraphicPoint(pt, d, (a1 + a2) * 0.5); + addPoint(p, i); // will deal with reversed segments + continue; + } else // the new "square" join + { + a2 = saved_a2; + + // identify if angle is less than pi/2 + // in this case, we introduce a segment that is perpendicular to + // the bissector of the angle + // TODO: see figure X for details + boolean bAddSegment; + if (m_distance > 0.0) { + if (a2 > a1) // > and not >= + a2 -= two_pi; + bAddSegment = (a1 - a2 < half_pi); + } else { + if (a2 < a1) // < and not <= + a2 += two_pi; + bAddSegment = (a2 - a1 < half_pi); + } + if (bAddSegment) { + // make it continuous when angle is pi/2 (but not tangent to + // the round join) + double d = m_distance * sqrt2; + double a; + + if (d < 0.0) + a = a1 + pi * 0.25; + else + a = a1 + 3.0 * pi * 0.25; + p = new GraphicPoint(pt, d, a); + addPoint(p, i); + + if (d < 0) + a = a2 - pi * 0.25; + else + a = a2 - 3.0 * pi * 0.25; + p = new GraphicPoint(pt, d, a); + addPoint(p); + } else // standard case: we just add the intersection point of + // offset segments + { + double r = (a2 - a1) * 0.5; + double d = m_distance / Math.abs(Math.sin(r)); + if (a2 < a1) + a2 += two_pi; // this guaranties that (a1 + a2) / 2 is + // on the right side with a positive + // offset + p = new GraphicPoint(pt, d, (a1 + a2) / 2); + addPoint(p, i); + } + } + } + + // closing point + m_a1 = a1_0; + m_a2 = a2_0; + addPoint(m_offsetPts.get(0), 0); + + // make sure the first point matches the last (in case a problem of + // reversed segment happens there) + pt = new GraphicPoint(m_offsetPts.get(m_offsetPtCount - 1)); + m_offsetPts.set(0, pt); + + // remove loops + return removeBadSegsFast(); + } + + void addPart(int iStart, int cPts) { + if (cPts < 2) + return; + + for (int i = 0; i < cPts; i++) { + GraphicPoint pt = m_offsetPts.get(iStart + i); + if (i != 0) + m_resultPath.lineTo(new Point2D(pt.x, pt.y)); + else + m_resultPath.startPath(new Point2D(pt.x, pt.y)); + } + } + + void _OffsetPath(MultiPath multiPath, int pathIndex, MultiPath resultingPath) { + int startVertex = multiPath.getPathStart(pathIndex); + int endVertex = multiPath.getPathEnd(pathIndex); + + m_offsetPts = new ArrayList(); + + // test if part is closed + m_resultPath = resultingPath; + m_resultPoints = 0; + if (multiPath.isClosedPath(pathIndex)) { + // check if last point is a duplicate of first + Point2D ptStart = multiPath.getXY(startVertex); + while (multiPath.getXY(endVertex - 1).isEqual(ptStart)) + endVertex--; + + // we need at least three points for a polygon + if (endVertex - startVertex >= 2) { + m_srcPtCount = endVertex - startVertex; + m_srcPts = new ArrayList(m_srcPtCount); + // TODO: may throw std::bad:alloc() + for (int i = startVertex; i < endVertex; i++) + m_srcPts.add(new GraphicPoint(multiPath.getXY(i))); + + if (buildOffset()) + addPart(0, m_offsetPtCount - 1); // do not repeat closing + // point + } + } else { + // remove duplicate points at extremities + Point2D ptStart = multiPath.getXY(startVertex); + while ((startVertex < endVertex) + && multiPath.getXY(startVertex + 1).isEqual(ptStart)) + startVertex++; + Point2D ptEnd = multiPath.getXY(endVertex - 1); + while ((startVertex < endVertex) + && multiPath.getXY(endVertex - 2).isEqual(ptEnd)) + endVertex--; + + // we need at least two points for a polyline + if (endVertex - startVertex >= 2) { + // close the line and mark the opposite segments as non valid + m_srcPtCount = (endVertex - startVertex) * 2 - 2; + m_srcPts = new ArrayList(m_srcPtCount); + // TODO: may throw std::bad:alloc() + + GraphicPoint pt = new GraphicPoint(multiPath.getXY(startVertex)); + pt.type |= IS_END + CLOSING_SEG; + m_srcPts.add(pt); + + for (int i = startVertex + 1; i < endVertex - 1; i++) { + pt = new GraphicPoint(multiPath.getXY(i)); + m_srcPts.add(pt); + } + + pt = new GraphicPoint(multiPath.getXY(endVertex - 1)); + pt.type |= IS_END; + m_srcPts.add(pt); + + for (int i = endVertex - 2; i >= startVertex + 1; i--) { + pt = new GraphicPoint(multiPath.getXY(i)); + pt.type |= CLOSING_SEG; + m_srcPts.add(pt); + } + + if (buildOffset()) + + if (m_offsetPts.size() >= 2) { + // extract the part that doesn't have the CLOSING_SEG + // attribute + + int iStart = -1; + int iEnd = -1; + boolean prevClosed = (m_offsetPts + .get(m_offsetPtCount - 1).type & CLOSING_SEG) != 0; + if (!prevClosed) + iStart = 0; + for (int i = 1; i < m_offsetPtCount; i++) { + boolean closed = (m_offsetPts.get(i).type & CLOSING_SEG) != 0; + if (!closed) { + if (prevClosed) { + // if ((m_offsetPts[i - 1].type & MOVE_TO) + // == 0) + // m_offsetPts[i - 1].type += MOVE_TO - + // LINE_TO; + iStart = i - 1; + } + } else { + if (!prevClosed) { + iEnd = i - 1; + // for (long i = iStart; i <= iEnd; i++) + // m_offsetPts[i].type &= OUR_FLAGS_MASK; + if (iEnd - iStart + 1 > 1) + addPart(iStart, iEnd - iStart + 1); + } + } + prevClosed = closed; + } + if (!prevClosed) { + iEnd = m_offsetPtCount - 1; + // for (long i = iStart; i <= iEnd; i++) + // m_offsetPts[i].type &= OUR_FLAGS_MASK; + if (iEnd - iStart + 1 > 1) + addPart(iStart, iEnd - iStart + 1); + } + } else { + int iStart = 0; + int iEnd = m_offsetPtCount - 1; + if (iStart >= 0 && iEnd - iStart >= 1) { + // for (long i = iStart; i <= iEnd; i++) + // m_offsetPts[i].type &= OUR_FLAGS_MASK; + addPart(iStart, iEnd - iStart + 1); + } + } + } + } + + // clear source + m_srcPts = null; + m_srcPtCount = 0; + // free offset buffer + m_offsetPts = null; + m_offsetPtCount = 0; + } + + boolean removeBadSegsFast() { + boolean bWrong = false; + + // initialize circular doubly-linked list + // skip last point which is dup of first point + for (int i = 0; i < m_offsetPtCount; i++) { + GraphicPoint pt = m_offsetPts.get(i); + pt.m_next = i + 1; + pt.m_prev = i - 1; + m_offsetPts.set(i, pt); + } + + // need to update the first and last elements + GraphicPoint pt; + + pt = m_offsetPts.get(0); + pt.m_prev = m_offsetPtCount - 2; + m_offsetPts.set(0, pt); + + pt = m_offsetPts.get(m_offsetPtCount - 2); + pt.m_next = 0; + m_offsetPts.set(m_offsetPtCount - 2, pt); + + int w = 0; + for (int i = 0; i < m_offsetPtCount; i++) { + if ((m_offsetPts.get(w).type & BAD_SEG) != 0) { + int wNext = deleteClosedSeg(w); + if (wNext != -1) + w = wNext; + else { + bWrong = true; + break; + } + } else + w = m_offsetPts.get(w).m_next; + } + + if (bWrong) + return false; + + // w is the index of a known good (i.e. surviving ) point in the offset + // array + compressOffsetArray(w); + return true; + } + + int deleteClosedSeg(int seg) { + int n = m_offsetPtCount - 1; // number of segments + + // check combinations of segments + int ip0 = seg, ip, im; + + for (int i = 1; i <= n - 2; i++) { + ip0 = m_offsetPts.get(ip0).m_next; + + ip = ip0; + im = seg; + + for (int j = 1; j <= i; j++) { + im = m_offsetPts.get(im).m_prev; + + if ((m_offsetPts.get(im).type & BAD_SEG) == 0 + && (m_offsetPts.get(ip).type & BAD_SEG) == 0) { + int rSegNext = handleClosedIntersection(im, ip); + if (rSegNext != -1) + return rSegNext; + } + + ip = m_offsetPts.get(ip).m_prev; + } + } + + return -1; + } + + // line segments defined by (im-1, im) and (ip-1, ip) + int handleClosedIntersection(int im, int ip) { + GraphicPoint pt1, pt2, pt3, pt4; + pt1 = m_offsetPts.get(m_offsetPts.get(im).m_prev); + pt2 = m_offsetPts.get(im); + pt3 = m_offsetPts.get(m_offsetPts.get(ip).m_prev); + pt4 = m_offsetPts.get(ip); + + if (!sectGraphicRect(pt1, pt2, pt3, pt4)) + return -1; + + // intersection + IntersectionInfo ii = new IntersectionInfo(); + if (findIntersection(pt1, pt2, pt3, pt4, ii) && !ii.atExistingPt) + if (Math.signum((pt2.x - pt1.x) * (pt4.y - pt3.y) - (pt2.y - pt1.y) + * (pt4.x - pt3.x)) != Math.signum(m_distance)) { + int prev0 = m_offsetPts.get(im).m_prev; + + ii.pt.type = pt2.type; + ii.pt.m_next = ip; + ii.pt.m_prev = prev0; + m_offsetPts.set(im, ii.pt); + + ii.pt = m_offsetPts.get(ip); + ii.pt.m_prev = im; + m_offsetPts.set(ip, ii.pt); + + return ip; + } + return -1; + } + + boolean sectGraphicRect(GraphicPoint pt1, GraphicPoint pt2, + GraphicPoint pt3, GraphicPoint pt4) { + return (Math.max(pt1.x, pt2.x) >= Math.min(pt3.x, pt4.x) + && Math.max(pt3.x, pt4.x) >= Math.min(pt1.x, pt2.x) + && Math.max(pt1.y, pt2.y) >= Math.min(pt3.y, pt4.y) && Math + .max(pt3.y, pt4.y) >= Math.min(pt1.y, pt2.y)); + } + + boolean findIntersection(GraphicPoint bp1, GraphicPoint bp2, + GraphicPoint bp3, GraphicPoint bp4, + IntersectionInfo intersectionInfo) { + intersectionInfo.atExistingPt = false; + + // Note: test if rectangles intersect already done by caller + + // intersection + double i, j, r, r1; + i = (bp2.y - bp1.y) * (bp4.x - bp3.x) - (bp2.x - bp1.x) + * (bp4.y - bp3.y); + j = (bp3.y - bp1.y) * (bp2.x - bp1.x) - (bp3.x - bp1.x) + * (bp2.y - bp1.y); + if (i == 0.0) + r = 2.0; + else + r = j / i; + + if ((r >= 0.0) && (r <= 1.0)) { + r1 = r; + i = (bp4.y - bp3.y) * (bp2.x - bp1.x) - (bp4.x - bp3.x) + * (bp2.y - bp1.y); + j = (bp1.y - bp3.y) * (bp4.x - bp3.x) - (bp1.x - bp3.x) + * (bp4.y - bp3.y); + + if (i == 0.0) + r = 2.0; + else + r = j / i; + + if ((r >= 0.0) && (r <= 1.0)) { + intersectionInfo.pt = new GraphicPoint(bp1.x + r + * (bp2.x - bp1.x), bp1.y + r * (bp2.y - bp1.y)); + intersectionInfo.pt.m = bp3.m + r1 * (bp4.m - bp3.m); + if (((r1 == 0.0) || (r1 == 1.0)) && ((r == 0.0) || (r == 1.0))) + intersectionInfo.atExistingPt = true; + + intersectionInfo.rFirst = r; + intersectionInfo.rSecond = r1; + + if (((r1 == 0.0) || (r1 == 1.0)) && ((r > 0.0) && (r < 1.0)) + || ((r == 0.0) || (r == 1.0)) + && ((r1 > 0.0) && (r1 < 1.0))) { + return false; + } + + return true; + } + } + return false; + } + + // i0 is the index of a known good point in the offset points array; that + // is, its the index of a point that isn't part of a deleted loop + void compressOffsetArray(int i0) { + int i_ = i0; + while (m_offsetPts.get(i_).m_prev < i_) + i_ = m_offsetPts.get(i_).m_prev; + + int j = 0, i = i_; + + do { + GraphicPoint pt = m_offsetPts.get(i); + m_offsetPts.set(j, pt); + i = pt.m_next; + j++; + } while (i != i_); + + m_offsetPts.set(j, m_offsetPts.get(0)); // duplicate closing point + + m_offsetPtCount = j + 1; + } + + void _OffsetMultiPath(MultiPath resultingPath) { + // we process all path independently, then merge the results + MultiPath multiPath = (MultiPath) m_inputGeometry; + SegmentIterator segmentIterator = multiPath.querySegmentIterator(); + if (segmentIterator == null) + return; // TODO: strategy on error? + + segmentIterator.resetToFirstPath(); + int pathIndex = -1; + while (segmentIterator.nextPath()) { + pathIndex++; + _OffsetPath(multiPath, pathIndex, resultingPath); + } + } + + Geometry _ConstructOffset() { + int gt = m_inputGeometry.getType().value(); + if (gt == Geometry.GeometryType.Line) { + return _OffsetLine(); + } + if (gt == Geometry.GeometryType.Envelope) { + return _OffsetEnvelope(); + } + if (Geometry.isSegment(gt)) { + Polyline poly = new Polyline(); + poly.addSegment((Segment) m_inputGeometry, true); + m_inputGeometry = poly; + return _ConstructOffset(); + } + if (gt == Geometry.GeometryType.Polyline) { + Polyline polyline = new Polyline(); + _OffsetMultiPath(polyline); + return polyline; + } + if (gt == Geometry.GeometryType.Polygon) { + Polygon polygon = new Polygon(); + _OffsetMultiPath(polygon); + return polygon; + } + // throw new GeometryException("not implemented"); + return null; + } } diff --git a/src/main/java/com/esri/core/geometry/ConvexHull.java b/src/main/java/com/esri/core/geometry/ConvexHull.java index 172ca92b..ab4b89c9 100644 --- a/src/main/java/com/esri/core/geometry/ConvexHull.java +++ b/src/main/java/com/esri/core/geometry/ConvexHull.java @@ -24,730 +24,730 @@ package com.esri.core.geometry; class ConvexHull { - /* - * Constructor for a Convex_hull object. Used for dynamic insertion of geometries to create a convex hull. - */ - ConvexHull() { - m_tree_hull = new Treap(); - m_tree_hull.setCapacity(20); - m_shape = new EditShape(); - m_geometry_handle = m_shape.createGeometry(Geometry.Type.MultiPoint); - m_path_handle = m_shape.insertPath(m_geometry_handle, -1); - m_call_back = new CallBackShape(this); - } - - private ConvexHull(AttributeStreamOfDbl stream, int n) { - m_tree_hull = new Treap(); - m_tree_hull.setCapacity(Math.min(20, n)); - m_stream = stream; - m_call_back = new CallBackStream(this); - } - - private ConvexHull(Point2D[] points, int n) { - m_tree_hull = new Treap(); - m_tree_hull.setCapacity(Math.min(20, n)); - m_points = points; - m_call_back = new CallBackPoints(this); - } - - /** - * Adds a geometry to the current bounding geometry using an incremental algorithm for dynamic insertion. - * \param geometry The geometry to add to the bounding geometry. - */ - - void addGeometry(Geometry geometry) { - int type = geometry.getType().value(); - - if (MultiVertexGeometry.isMultiVertex(type)) - addMultiVertexGeometry_((MultiVertexGeometry) geometry); - else if (MultiPath.isSegment(type)) - addSegment_((Segment) geometry); - else if (type == Geometry.GeometryType.Envelope) - addEnvelope_((Envelope) geometry); - else if (type == Geometry.GeometryType.Point) - addPoint_((Point) geometry); - else - throw new IllegalArgumentException("invalid shape type"); - } - - /** - * Gets the current bounding geometry. - * Returns a Geometry. - */ - - Geometry getBoundingGeometry() { - // Extracts the convex hull from the tree. Reading the tree in order from first to last is the resulting convex hull. - Point point = new Point(); - int first = m_tree_hull.getFirst(-1); - Polygon hull = new Polygon(m_shape.getVertexDescription()); - m_shape.queryPoint(m_tree_hull.getElement(first), point); - hull.startPath(point); - - for (int i = m_tree_hull.getNext(first); i != -1; i = m_tree_hull.getNext(i)) { - m_shape.queryPoint(m_tree_hull.getElement(i), point); - hull.lineTo(point); - } - - return hull; - } - - /** - * Static method to construct the convex hull of a Multi_vertex_geometry. - * Returns a Geometry. - * \param mvg The geometry used to create the convex hull. - */ - - static Geometry construct(MultiVertexGeometry mvg) { - if (mvg.isEmpty()) - return new Polygon(mvg.getDescription()); - - MultiVertexGeometryImpl mvg_impl = (MultiVertexGeometryImpl) mvg._getImpl(); - int N = mvg_impl.getPointCount(); - - if (N <= 2) { - if (N == 1 || mvg_impl.getXY(0).equals(mvg_impl.getXY(1))) { - Point point = new Point(mvg_impl.getDescription()); - mvg_impl.getPointByVal(0, point); - return point; - } else { - Point pt = new Point(); - Polyline polyline = new Polyline(mvg_impl.getDescription()); - mvg_impl.getPointByVal(0, pt); - polyline.startPath(pt); - mvg_impl.getPointByVal(1, pt); - polyline.lineTo(pt); - return polyline; - } - } - - AttributeStreamOfDbl stream = (AttributeStreamOfDbl) mvg_impl.getAttributeStreamRef(VertexDescription.Semantics.POSITION); - ConvexHull convex_hull = new ConvexHull(stream, N); - - int t0 = 0, tm = 1; - Point2D pt_0 = new Point2D(); - Point2D pt_m = new Point2D(); - Point2D pt_p = new Point2D(); - - stream.read(t0 << 1, pt_0); - - while (true) { - if (tm >= N) - break; - - stream.read(tm << 1, pt_m); - if (!pt_m.isEqual(pt_0, NumberUtils.doubleEps())) - break; - - tm++; // We don't want to close the gap between t0 and tm. - } - - convex_hull.m_tree_hull.addElement(t0, -1); - - if (tm < N) { - convex_hull.m_tree_hull.addBiggestElement(tm, -1); - - for (int tp = tm + 1; tp < mvg_impl.getPointCount(); tp++) {// Dynamically insert into the current convex hull - - stream.read(tp << 1, pt_p); - int p = convex_hull.treeHull_(pt_p); - - if (p != -1) - convex_hull.m_tree_hull.setElement(p, tp); // reset the place holder to the point index. - } - } - - // Extracts the convex hull from the tree. Reading the tree in order from first to last is the resulting convex hull. - - VertexDescription description = mvg_impl.getDescription(); - boolean b_has_attributes = (description.getAttributeCount() > 1); - int point_count = convex_hull.m_tree_hull.size(-1); - - Geometry hull; - - if (point_count >= 2) { - if (point_count >= 3) - hull = new Polygon(description); - else - hull = new Polyline(description); - - MultiPathImpl hull_impl = (MultiPathImpl) hull._getImpl(); - hull_impl.addPath((Point2D[]) null, 0, true); - - Point point = null; - if (b_has_attributes) - point = new Point(); - - for (int i = convex_hull.m_tree_hull.getFirst(-1); i != -1; i = convex_hull.m_tree_hull.getNext(i)) { - if (b_has_attributes) { - mvg_impl.getPointByVal(convex_hull.m_tree_hull.getElement(i), point); - hull_impl.insertPoint(0, -1, point); - } else { - stream.read(convex_hull.m_tree_hull.getElement(i) << 1, pt_p); - hull_impl.insertPoint(0, -1, pt_p); - } - } - } else { - assert (point_count == 1); - - if (b_has_attributes) { - Point point = new Point(description); - mvg_impl.getPointByVal(convex_hull.m_tree_hull.getElement(convex_hull.m_tree_hull.getFirst(-1)), point); - hull = point; - } else { - stream.read(convex_hull.m_tree_hull.getElement(convex_hull.m_tree_hull.getFirst(-1)) << 1, pt_p); - hull = new Point(pt_p); - } - } - - return hull; - } - - /** - * Static method to construct the convex hull from an array of points. The - * out_convex_hull array will be populated with the subset of index - * positions which contribute to the convex hull. - * Returns the number of points in the convex hull. - * \param points The points used to create the convex hull. - * \param count The number of points in the input Point2D array. - * \param out_convex_hull An index array allocated by the user at least as big as the size of the input points array. - */ - static int construct(Point2D[] points, int count, int[] out_convex_hull) { - ConvexHull convex_hull = new ConvexHull(points, count); + /* + * Constructor for a Convex_hull object. Used for dynamic insertion of geometries to create a convex hull. + */ + ConvexHull() { + m_tree_hull = new Treap(); + m_tree_hull.setCapacity(20); + m_shape = new EditShape(); + m_geometry_handle = m_shape.createGeometry(Geometry.Type.MultiPoint); + m_path_handle = m_shape.insertPath(m_geometry_handle, -1); + m_call_back = new CallBackShape(this); + } + + private ConvexHull(AttributeStreamOfDbl stream, int n) { + m_tree_hull = new Treap(); + m_tree_hull.setCapacity(Math.min(20, n)); + m_stream = stream; + m_call_back = new CallBackStream(this); + } + + private ConvexHull(Point2D[] points, int n) { + m_tree_hull = new Treap(); + m_tree_hull.setCapacity(Math.min(20, n)); + m_points = points; + m_call_back = new CallBackPoints(this); + } + + /** + * Adds a geometry to the current bounding geometry using an incremental algorithm for dynamic insertion. + * \param geometry The geometry to add to the bounding geometry. + */ + + void addGeometry(Geometry geometry) { + int type = geometry.getType().value(); + + if (MultiVertexGeometry.isMultiVertex(type)) + addMultiVertexGeometry_((MultiVertexGeometry) geometry); + else if (MultiPath.isSegment(type)) + addSegment_((Segment) geometry); + else if (type == Geometry.GeometryType.Envelope) + addEnvelope_((Envelope) geometry); + else if (type == Geometry.GeometryType.Point) + addPoint_((Point) geometry); + else + throw new IllegalArgumentException("invalid shape type"); + } + + /** + * Gets the current bounding geometry. + * Returns a Geometry. + */ + + Geometry getBoundingGeometry() { + // Extracts the convex hull from the tree. Reading the tree in order from first to last is the resulting convex hull. + Point point = new Point(); + int first = m_tree_hull.getFirst(-1); + Polygon hull = new Polygon(m_shape.getVertexDescription()); + m_shape.queryPoint(m_tree_hull.getElement(first), point); + hull.startPath(point); + + for (int i = m_tree_hull.getNext(first); i != -1; i = m_tree_hull.getNext(i)) { + m_shape.queryPoint(m_tree_hull.getElement(i), point); + hull.lineTo(point); + } + + return hull; + } + + /** + * Static method to construct the convex hull of a Multi_vertex_geometry. + * Returns a Geometry. + * \param mvg The geometry used to create the convex hull. + */ + + static Geometry construct(MultiVertexGeometry mvg) { + if (mvg.isEmpty()) + return new Polygon(mvg.getDescription()); + + MultiVertexGeometryImpl mvg_impl = (MultiVertexGeometryImpl) mvg._getImpl(); + int N = mvg_impl.getPointCount(); + + if (N <= 2) { + if (N == 1 || mvg_impl.getXY(0).equals(mvg_impl.getXY(1))) { + Point point = new Point(mvg_impl.getDescription()); + mvg_impl.getPointByVal(0, point); + return point; + } else { + Point pt = new Point(); + Polyline polyline = new Polyline(mvg_impl.getDescription()); + mvg_impl.getPointByVal(0, pt); + polyline.startPath(pt); + mvg_impl.getPointByVal(1, pt); + polyline.lineTo(pt); + return polyline; + } + } + + AttributeStreamOfDbl stream = (AttributeStreamOfDbl) mvg_impl.getAttributeStreamRef(VertexDescription.Semantics.POSITION); + ConvexHull convex_hull = new ConvexHull(stream, N); + + int t0 = 0, tm = 1; + Point2D pt_0 = new Point2D(); + Point2D pt_m = new Point2D(); + Point2D pt_p = new Point2D(); + + stream.read(t0 << 1, pt_0); + + while (true) { + if (tm >= N) + break; + + stream.read(tm << 1, pt_m); + if (!pt_m.isEqual(pt_0, NumberUtils.doubleEps())) + break; + + tm++; // We don't want to close the gap between t0 and tm. + } + + convex_hull.m_tree_hull.addElement(t0, -1); + + if (tm < N) { + convex_hull.m_tree_hull.addBiggestElement(tm, -1); + + for (int tp = tm + 1; tp < mvg_impl.getPointCount(); tp++) {// Dynamically insert into the current convex hull + + stream.read(tp << 1, pt_p); + int p = convex_hull.treeHull_(pt_p); + + if (p != -1) + convex_hull.m_tree_hull.setElement(p, tp); // reset the place holder to the point index. + } + } + + // Extracts the convex hull from the tree. Reading the tree in order from first to last is the resulting convex hull. + + VertexDescription description = mvg_impl.getDescription(); + boolean b_has_attributes = (description.getAttributeCount() > 1); + int point_count = convex_hull.m_tree_hull.size(-1); + + Geometry hull; + + if (point_count >= 2) { + if (point_count >= 3) + hull = new Polygon(description); + else + hull = new Polyline(description); + + MultiPathImpl hull_impl = (MultiPathImpl) hull._getImpl(); + hull_impl.addPath((Point2D[]) null, 0, true); + + Point point = null; + if (b_has_attributes) + point = new Point(); + + for (int i = convex_hull.m_tree_hull.getFirst(-1); i != -1; i = convex_hull.m_tree_hull.getNext(i)) { + if (b_has_attributes) { + mvg_impl.getPointByVal(convex_hull.m_tree_hull.getElement(i), point); + hull_impl.insertPoint(0, -1, point); + } else { + stream.read(convex_hull.m_tree_hull.getElement(i) << 1, pt_p); + hull_impl.insertPoint(0, -1, pt_p); + } + } + } else { + assert (point_count == 1); + + if (b_has_attributes) { + Point point = new Point(description); + mvg_impl.getPointByVal(convex_hull.m_tree_hull.getElement(convex_hull.m_tree_hull.getFirst(-1)), point); + hull = point; + } else { + stream.read(convex_hull.m_tree_hull.getElement(convex_hull.m_tree_hull.getFirst(-1)) << 1, pt_p); + hull = new Point(pt_p); + } + } + + return hull; + } + + /** + * Static method to construct the convex hull from an array of points. The + * out_convex_hull array will be populated with the subset of index + * positions which contribute to the convex hull. + * Returns the number of points in the convex hull. + * \param points The points used to create the convex hull. + * \param count The number of points in the input Point2D array. + * \param out_convex_hull An index array allocated by the user at least as big as the size of the input points array. + */ + static int construct(Point2D[] points, int count, int[] out_convex_hull) { + ConvexHull convex_hull = new ConvexHull(points, count); - int t0 = 0, tm = 1; - Point2D pt_0 = points[t0]; + int t0 = 0, tm = 1; + Point2D pt_0 = points[t0]; - while (tm < count && points[tm].isEqual(pt_0, NumberUtils.doubleEps())) - tm++; // We don't want to close the gap between t0 and tm. + while (tm < count && points[tm].isEqual(pt_0, NumberUtils.doubleEps())) + tm++; // We don't want to close the gap between t0 and tm. - convex_hull.m_tree_hull.addElement(t0, -1); + convex_hull.m_tree_hull.addElement(t0, -1); - if (tm < count) { - convex_hull.m_tree_hull.addBiggestElement(tm, -1); + if (tm < count) { + convex_hull.m_tree_hull.addBiggestElement(tm, -1); - for (int tp = tm + 1; tp < count; tp++) {// Dynamically insert into the current convex hull. + for (int tp = tm + 1; tp < count; tp++) {// Dynamically insert into the current convex hull. - Point2D pt_p = points[tp]; - int p = convex_hull.treeHull_(pt_p); + Point2D pt_p = points[tp]; + int p = convex_hull.treeHull_(pt_p); - if (p != -1) - convex_hull.m_tree_hull.setElement(p, tp); // reset the place holder to the point index. - } - } + if (p != -1) + convex_hull.m_tree_hull.setElement(p, tp); // reset the place holder to the point index. + } + } - // Extracts the convex hull from the tree. Reading the tree in order from first to last is the resulting convex hull. - int out_count = 0; - for (int i = convex_hull.m_tree_hull.getFirst(-1); i != -1; i = convex_hull.m_tree_hull.getNext(i)) - out_convex_hull[out_count++] = convex_hull.m_tree_hull.getElement(i); + // Extracts the convex hull from the tree. Reading the tree in order from first to last is the resulting convex hull. + int out_count = 0; + for (int i = convex_hull.m_tree_hull.getFirst(-1); i != -1; i = convex_hull.m_tree_hull.getNext(i)) + out_convex_hull[out_count++] = convex_hull.m_tree_hull.getElement(i); - return out_count; - } - - /** - * Returns true if the given path of the input MultiPath is convex. Returns false otherwise. - * \param multi_path The MultiPath to check if the path is convex. - * \param path_index The path of the MultiPath to check if its convex. - */ - static boolean isPathConvex(MultiPath multi_path, int path_index, ProgressTracker progress_tracker) { - MultiPathImpl mimpl = (MultiPathImpl) multi_path._getImpl(); - int path_start = mimpl.getPathStart(path_index); - int path_end = mimpl.getPathEnd(path_index); - - boolean bxyclosed = !mimpl.isClosedPath(path_index) && mimpl.isClosedPathInXYPlane(path_index); - - AttributeStreamOfDbl position = (AttributeStreamOfDbl) (mimpl.getAttributeStreamRef(VertexDescription.Semantics.POSITION)); - int position_start = 2 * path_start; - int position_end = 2 * path_end; - - if (bxyclosed) - position_end -= 2; - - if (position_end - position_start < 6) - return true; - - // This matches the logic for case 1 of the tree hull algorithm. The idea is inductive. We assume we have a convex hull pt_0,...,pt_m, and we see if - // a new point (pt_pivot) is among the transitive tournament for pt_0, knowing that pt_pivot comes after pt_m. - - // We check three conditions: - // 1) pt_m->pt_pivot->pt_0 is clockwise (closure across the boundary is convex) - // 2) pt_1->pt_pivot->pt_0 is clockwise (the first step forward is convex) (pt_1 is the next point after pt_0) - // 3) pt_m->pt_pivot->pt_m_prev is clockwise (the first step backwards is convex) (pt_m_prev is the previous point before pt_m) - - // If all three of the above conditions are clockwise, then pt_pivot is among the transitive tournament for pt_0, and therefore the polygon pt_0, ..., pt_m, pt_pivot is convex. - - Point2D pt_0 = new Point2D(), pt_m = new Point2D(), pt_pivot = new Point2D(); - position.read(position_start, pt_0); - position.read(position_start + 2, pt_m); - position.read(position_start + 4, pt_pivot); - - // Initial inductive step - ECoordinate det_ec = determinant_(pt_m, pt_pivot, pt_0); - - if (det_ec.isFuzzyZero() || !isClockwise_(det_ec.value())) - return false; - - Point2D pt_1 = new Point2D(pt_m.x, pt_m.y); - Point2D pt_m_prev = new Point2D(); - - // Assume that pt_0,...,pt_m is convex. Check if the next point, pt_pivot, maintains the convex invariant. - for (int i = position_start + 6; i < position_end; i += 2) { - pt_m_prev.setCoords(pt_m); - pt_m.setCoords(pt_pivot); - position.read(i, pt_pivot); - - det_ec = determinant_(pt_m, pt_pivot, pt_0); - - if (det_ec.isFuzzyZero() || !isClockwise_(det_ec.value())) - return false; - - det_ec = determinant_(pt_1, pt_pivot, pt_0); - - if (det_ec.isFuzzyZero() || !isClockwise_(det_ec.value())) - return false; - - det_ec = determinant_(pt_m, pt_pivot, pt_m_prev); + return out_count; + } + + /** + * Returns true if the given path of the input MultiPath is convex. Returns false otherwise. + * \param multi_path The MultiPath to check if the path is convex. + * \param path_index The path of the MultiPath to check if its convex. + */ + static boolean isPathConvex(MultiPath multi_path, int path_index, ProgressTracker progress_tracker) { + MultiPathImpl mimpl = (MultiPathImpl) multi_path._getImpl(); + int path_start = mimpl.getPathStart(path_index); + int path_end = mimpl.getPathEnd(path_index); + + boolean bxyclosed = !mimpl.isClosedPath(path_index) && mimpl.isClosedPathInXYPlane(path_index); + + AttributeStreamOfDbl position = (AttributeStreamOfDbl) (mimpl.getAttributeStreamRef(VertexDescription.Semantics.POSITION)); + int position_start = 2 * path_start; + int position_end = 2 * path_end; + + if (bxyclosed) + position_end -= 2; + + if (position_end - position_start < 6) + return true; + + // This matches the logic for case 1 of the tree hull algorithm. The idea is inductive. We assume we have a convex hull pt_0,...,pt_m, and we see if + // a new point (pt_pivot) is among the transitive tournament for pt_0, knowing that pt_pivot comes after pt_m. + + // We check three conditions: + // 1) pt_m->pt_pivot->pt_0 is clockwise (closure across the boundary is convex) + // 2) pt_1->pt_pivot->pt_0 is clockwise (the first step forward is convex) (pt_1 is the next point after pt_0) + // 3) pt_m->pt_pivot->pt_m_prev is clockwise (the first step backwards is convex) (pt_m_prev is the previous point before pt_m) + + // If all three of the above conditions are clockwise, then pt_pivot is among the transitive tournament for pt_0, and therefore the polygon pt_0, ..., pt_m, pt_pivot is convex. + + Point2D pt_0 = new Point2D(), pt_m = new Point2D(), pt_pivot = new Point2D(); + position.read(position_start, pt_0); + position.read(position_start + 2, pt_m); + position.read(position_start + 4, pt_pivot); + + // Initial inductive step + ECoordinate det_ec = determinant_(pt_m, pt_pivot, pt_0); + + if (det_ec.isFuzzyZero() || !isClockwise_(det_ec.value())) + return false; + + Point2D pt_1 = new Point2D(pt_m.x, pt_m.y); + Point2D pt_m_prev = new Point2D(); + + // Assume that pt_0,...,pt_m is convex. Check if the next point, pt_pivot, maintains the convex invariant. + for (int i = position_start + 6; i < position_end; i += 2) { + pt_m_prev.setCoords(pt_m); + pt_m.setCoords(pt_pivot); + position.read(i, pt_pivot); + + det_ec = determinant_(pt_m, pt_pivot, pt_0); + + if (det_ec.isFuzzyZero() || !isClockwise_(det_ec.value())) + return false; + + det_ec = determinant_(pt_1, pt_pivot, pt_0); + + if (det_ec.isFuzzyZero() || !isClockwise_(det_ec.value())) + return false; + + det_ec = determinant_(pt_m, pt_pivot, pt_m_prev); - if (det_ec.isFuzzyZero() || !isClockwise_(det_ec.value())) - return false; - } + if (det_ec.isFuzzyZero() || !isClockwise_(det_ec.value())) + return false; + } - return true; - } + return true; + } - // Dynamically inserts each geometry into the convex hull. - private void addMultiVertexGeometry_(MultiVertexGeometry mvg) { - Point point = new Point(); - Point2D pt_p = new Point2D(); + // Dynamically inserts each geometry into the convex hull. + private void addMultiVertexGeometry_(MultiVertexGeometry mvg) { + Point point = new Point(); + Point2D pt_p = new Point2D(); - for (int i = 0; i < mvg.getPointCount(); i++) { - mvg.getXY(i, pt_p); - int p = addPoint_(pt_p); + for (int i = 0; i < mvg.getPointCount(); i++) { + mvg.getXY(i, pt_p); + int p = addPoint_(pt_p); - if (p != -1) { - mvg.getPointByVal(i, point); - int tp = m_shape.addPoint(m_path_handle, point); - m_tree_hull.setElement(p, tp); // reset the place holder to tp - } - } - } + if (p != -1) { + mvg.getPointByVal(i, point); + int tp = m_shape.addPoint(m_path_handle, point); + m_tree_hull.setElement(p, tp); // reset the place holder to tp + } + } + } - private void addEnvelope_(Envelope envelope) { - Point point = new Point(); - Point2D pt_p = new Point2D(); + private void addEnvelope_(Envelope envelope) { + Point point = new Point(); + Point2D pt_p = new Point2D(); - for (int i = 0; i < 4; i++) { - envelope.queryCorner(i, pt_p); - int p = addPoint_(pt_p); + for (int i = 0; i < 4; i++) { + envelope.queryCorner(i, pt_p); + int p = addPoint_(pt_p); - if (p != -1) { - envelope.queryCornerByVal(i, point); - int tp = m_shape.addPoint(m_path_handle, point); - m_tree_hull.setElement(p, tp); // reset the place holder to tp - } - } - } + if (p != -1) { + envelope.queryCornerByVal(i, point); + int tp = m_shape.addPoint(m_path_handle, point); + m_tree_hull.setElement(p, tp); // reset the place holder to tp + } + } + } - private void addSegment_(Segment segment) { - Point point = new Point(); + private void addSegment_(Segment segment) { + Point point = new Point(); - Point2D pt_start = segment.getStartXY(); - int p_start = addPoint_(pt_start); + Point2D pt_start = segment.getStartXY(); + int p_start = addPoint_(pt_start); - if (p_start != -1) { - segment.queryStart(point); - int t_start = m_shape.addPoint(m_path_handle, point); - m_tree_hull.setElement(p_start, t_start); // reset the place holder - // to tp - } - - Point2D pt_end = segment.getEndXY(); - int p_end = addPoint_(pt_end); - - if (p_end != -1) { - segment.queryEnd(point); - int t_end = m_shape.addPoint(m_path_handle, point); - m_tree_hull.setElement(p_end, t_end); // reset the place holder to - // tp - } - } - - private void addPoint_(Point point) { - Point2D pt_p = point.getXY(); - int p = addPoint_(pt_p); - - if (p != -1) { - int tp = m_shape.addPoint(m_path_handle, point); - m_tree_hull.setElement(p, tp); // reset the place holder to tp - } - } - - private int addPoint_(Point2D pt_p) { - int p = -1; - - if (m_tree_hull.size(-1) == 0) { - p = m_tree_hull.addElement(-4, -1); // reset the place holder to tp - return p; - } - - if (m_tree_hull.size(-1) == 1) { - int t0 = m_tree_hull.getElement(m_tree_hull.getFirst(-1)); - Point2D pt_0 = m_shape.getXY(t0); - - if (!pt_p.isEqual(pt_0, NumberUtils.doubleEps())) // We don't want to close the gap between t0 and tm. - p = m_tree_hull.addBiggestElement(-5, -1); // set place holder to -5 to indicate the second element being added (tm). - - return p; - } - - p = treeHull_(pt_p); - return p; - } - - // Algorithm taken from "Axioms and Hulls" by D.E. Knuth, Lecture Notes in Computer Science 606, page 47. - private int treeHull_(Point2D pt_pivot) { - assert (m_tree_hull.size(-1) >= 2); - - int p = -1; - - do { - int first = m_tree_hull.getFirst(-1); - int last = m_tree_hull.getLast(-1); - int t0 = m_tree_hull.getElement(first); - int tm = m_tree_hull.getElement(last); - - Point2D pt_0 = new Point2D(); // should the memory be cached? - Point2D pt_m = new Point2D(); // should the memory be cached? - m_call_back.getXY(t0, pt_0); - m_call_back.getXY(tm, pt_m); - - assert (!pt_0.isEqual(pt_m, NumberUtils.doubleEps())); // assert that the gap is not closed - - int orient_m_p_0 = Point2D.orientationRobust(pt_m, pt_pivot, pt_0); // determines case 1, 2, 3 - - if (isClockwise_(orient_m_p_0)) {// Case 1: tp->t0->tm is clockwise - - p = m_tree_hull.addBiggestElement(-1, -1); // set place holder to -1 for case 1. - int l = treeHullWalkBackward_(pt_pivot, last, first); - - if (l != first) - treeHullWalkForward_(pt_pivot, first, m_tree_hull.getPrev(l)); - - continue; - } - - if (isCounterClockwise_(orient_m_p_0)) {// Case 2: tp->tm->t0 is clockwise - int k = m_tree_hull.getRoot(-1), k_min = m_tree_hull.getFirst(-1), k_max = m_tree_hull.getLast(-1), k_prev; - int tk, tk_prev; - Point2D pt_k = new Point2D(); - Point2D pt_k_prev = new Point2D(); - - while (k_min != m_tree_hull.getPrev(k_max)) {// binary search to find k such that t0->tp->tj holds (i.e. clockwise) for j >= k. Hence, tj->tp->t0 is clockwise (or degenerate) for j < k. - tk = m_tree_hull.getElement(k); - m_call_back.getXY(tk, pt_k); - int orient_k_p_0 = Point2D.orientationRobust(pt_k, pt_pivot, pt_0); - - if (isCounterClockwise_(orient_k_p_0)) { - k_max = k; - k = m_tree_hull.getLeft(k); - } else { - k_min = k; - k = m_tree_hull.getRight(k); - } - } - - k = k_max; - k_prev = k_min; - tk = m_tree_hull.getElement(k); - tk_prev = m_tree_hull.getElement(k_prev); - m_call_back.getXY(tk, pt_k); - m_call_back.getXY(tk_prev, pt_k_prev); - assert (isCounterClockwise_(Point2D.orientationRobust(pt_k, pt_pivot, pt_0)) && !isCounterClockwise_(Point2D.orientationRobust(pt_k_prev, pt_pivot, pt_0))); - assert (k_prev != first || isCounterClockwise_(Point2D.orientationRobust(pt_k, pt_pivot, pt_0))); - - if (k_prev != first) { - int orient_k_prev_p_k = Point2D.orientationRobust(pt_k_prev, pt_pivot, pt_k); + if (p_start != -1) { + segment.queryStart(point); + int t_start = m_shape.addPoint(m_path_handle, point); + m_tree_hull.setElement(p_start, t_start); // reset the place holder + // to tp + } + + Point2D pt_end = segment.getEndXY(); + int p_end = addPoint_(pt_end); + + if (p_end != -1) { + segment.queryEnd(point); + int t_end = m_shape.addPoint(m_path_handle, point); + m_tree_hull.setElement(p_end, t_end); // reset the place holder to + // tp + } + } + + private void addPoint_(Point point) { + Point2D pt_p = point.getXY(); + int p = addPoint_(pt_p); + + if (p != -1) { + int tp = m_shape.addPoint(m_path_handle, point); + m_tree_hull.setElement(p, tp); // reset the place holder to tp + } + } + + private int addPoint_(Point2D pt_p) { + int p = -1; + + if (m_tree_hull.size(-1) == 0) { + p = m_tree_hull.addElement(-4, -1); // reset the place holder to tp + return p; + } + + if (m_tree_hull.size(-1) == 1) { + int t0 = m_tree_hull.getElement(m_tree_hull.getFirst(-1)); + Point2D pt_0 = m_shape.getXY(t0); + + if (!pt_p.isEqual(pt_0, NumberUtils.doubleEps())) // We don't want to close the gap between t0 and tm. + p = m_tree_hull.addBiggestElement(-5, -1); // set place holder to -5 to indicate the second element being added (tm). + + return p; + } + + p = treeHull_(pt_p); + return p; + } + + // Algorithm taken from "Axioms and Hulls" by D.E. Knuth, Lecture Notes in Computer Science 606, page 47. + private int treeHull_(Point2D pt_pivot) { + assert (m_tree_hull.size(-1) >= 2); + + int p = -1; + + do { + int first = m_tree_hull.getFirst(-1); + int last = m_tree_hull.getLast(-1); + int t0 = m_tree_hull.getElement(first); + int tm = m_tree_hull.getElement(last); + + Point2D pt_0 = new Point2D(); // should the memory be cached? + Point2D pt_m = new Point2D(); // should the memory be cached? + m_call_back.getXY(t0, pt_0); + m_call_back.getXY(tm, pt_m); + + assert (!pt_0.isEqual(pt_m, NumberUtils.doubleEps())); // assert that the gap is not closed + + int orient_m_p_0 = Point2D.orientationRobust(pt_m, pt_pivot, pt_0); // determines case 1, 2, 3 + + if (isClockwise_(orient_m_p_0)) {// Case 1: tp->t0->tm is clockwise + + p = m_tree_hull.addBiggestElement(-1, -1); // set place holder to -1 for case 1. + int l = treeHullWalkBackward_(pt_pivot, last, first); + + if (l != first) + treeHullWalkForward_(pt_pivot, first, m_tree_hull.getPrev(l)); + + continue; + } + + if (isCounterClockwise_(orient_m_p_0)) {// Case 2: tp->tm->t0 is clockwise + int k = m_tree_hull.getRoot(-1), k_min = m_tree_hull.getFirst(-1), k_max = m_tree_hull.getLast(-1), k_prev; + int tk, tk_prev; + Point2D pt_k = new Point2D(); + Point2D pt_k_prev = new Point2D(); + + while (k_min != m_tree_hull.getPrev(k_max)) {// binary search to find k such that t0->tp->tj holds (i.e. clockwise) for j >= k. Hence, tj->tp->t0 is clockwise (or degenerate) for j < k. + tk = m_tree_hull.getElement(k); + m_call_back.getXY(tk, pt_k); + int orient_k_p_0 = Point2D.orientationRobust(pt_k, pt_pivot, pt_0); + + if (isCounterClockwise_(orient_k_p_0)) { + k_max = k; + k = m_tree_hull.getLeft(k); + } else { + k_min = k; + k = m_tree_hull.getRight(k); + } + } + + k = k_max; + k_prev = k_min; + tk = m_tree_hull.getElement(k); + tk_prev = m_tree_hull.getElement(k_prev); + m_call_back.getXY(tk, pt_k); + m_call_back.getXY(tk_prev, pt_k_prev); + assert (isCounterClockwise_(Point2D.orientationRobust(pt_k, pt_pivot, pt_0)) && !isCounterClockwise_(Point2D.orientationRobust(pt_k_prev, pt_pivot, pt_0))); + assert (k_prev != first || isCounterClockwise_(Point2D.orientationRobust(pt_k, pt_pivot, pt_0))); + + if (k_prev != first) { + int orient_k_prev_p_k = Point2D.orientationRobust(pt_k_prev, pt_pivot, pt_k); - if (!isClockwise_(orient_k_prev_p_k)) - continue; // pt_pivot is inside the hull (or on the boundary) - } + if (!isClockwise_(orient_k_prev_p_k)) + continue; // pt_pivot is inside the hull (or on the boundary) + } - p = m_tree_hull.addElementAtPosition(k_prev, k, -2, true, false, -1); // set place holder to -2 for case 2. - treeHullWalkForward_(pt_pivot, k, last); - treeHullWalkBackward_(pt_pivot, k_prev, first); + p = m_tree_hull.addElementAtPosition(k_prev, k, -2, true, false, -1); // set place holder to -2 for case 2. + treeHullWalkForward_(pt_pivot, k, last); + treeHullWalkBackward_(pt_pivot, k_prev, first); - continue; - } + continue; + } - assert (isDegenerate_(orient_m_p_0)); - {// Case 3: degenerate - int between = isBetween_(pt_pivot, pt_m, pt_0); + assert (isDegenerate_(orient_m_p_0)); + {// Case 3: degenerate + int between = isBetween_(pt_pivot, pt_m, pt_0); - if (between == -1) { - int l = m_tree_hull.getPrev(last); - m_tree_hull.deleteNode(last, -1); - p = m_tree_hull.addBiggestElement(-3, -1); // set place holder to -3 for case 3. - treeHullWalkBackward_(pt_pivot, l, first); - } else if (between == 1) { - int j = m_tree_hull.getNext(first); - m_tree_hull.deleteNode(first, -1); - p = m_tree_hull.addElementAtPosition(-1, j, -3, true, false, -1); // set place holder to -3 for case 3. - treeHullWalkForward_(pt_pivot, j, last); - } + if (between == -1) { + int l = m_tree_hull.getPrev(last); + m_tree_hull.deleteNode(last, -1); + p = m_tree_hull.addBiggestElement(-3, -1); // set place holder to -3 for case 3. + treeHullWalkBackward_(pt_pivot, l, first); + } else if (between == 1) { + int j = m_tree_hull.getNext(first); + m_tree_hull.deleteNode(first, -1); + p = m_tree_hull.addElementAtPosition(-1, j, -3, true, false, -1); // set place holder to -3 for case 3. + treeHullWalkForward_(pt_pivot, j, last); + } - continue; - } - - } while (false); + continue; + } + + } while (false); - return p; - } + return p; + } - private int treeHullWalkForward_(Point2D pt_pivot, int start, int end) { - if (start == end) - return end; + private int treeHullWalkForward_(Point2D pt_pivot, int start, int end) { + if (start == end) + return end; - int j = start; - int tj = m_tree_hull.getElement(j); - int j_next = m_tree_hull.getNext(j); - Point2D pt_j = new Point2D(); - Point2D pt_j_next = new Point2D(); - - m_call_back.getXY(tj, pt_j); + int j = start; + int tj = m_tree_hull.getElement(j); + int j_next = m_tree_hull.getNext(j); + Point2D pt_j = new Point2D(); + Point2D pt_j_next = new Point2D(); + + m_call_back.getXY(tj, pt_j); - while (j != end && m_tree_hull.size(-1) > 2) {//Stops when we find a clockwise triple containting the pivot point, or when the tree_hull size is 2. Deletes non-clockwise triples along the way. - int tj_next = m_tree_hull.getElement(j_next); - m_call_back.getXY(tj_next, pt_j_next); + while (j != end && m_tree_hull.size(-1) > 2) {//Stops when we find a clockwise triple containting the pivot point, or when the tree_hull size is 2. Deletes non-clockwise triples along the way. + int tj_next = m_tree_hull.getElement(j_next); + m_call_back.getXY(tj_next, pt_j_next); - int orient_j_next_p_j = Point2D.orientationRobust(pt_j_next, pt_pivot, pt_j); + int orient_j_next_p_j = Point2D.orientationRobust(pt_j_next, pt_pivot, pt_j); - if (isClockwise_(orient_j_next_p_j)) - break; - - int ccw = j; - - j = j_next; - pt_j.setCoords(pt_j_next); - j_next = m_tree_hull.getNext(j); - m_call_back.deleteNode(ccw); - } - - return j; - } - - private int treeHullWalkBackward_(Point2D pt_pivot, int start, int end) { - if (start == end) - return end; - - int l = start; - int tl = m_tree_hull.getElement(l); - int l_prev = m_tree_hull.getPrev(l); - Point2D pt_l = new Point2D(); - Point2D pt_l_prev = new Point2D(); - - m_call_back.getXY(tl, pt_l); - - while (l != end && m_tree_hull.size(-1) > 2) {//Stops when we find a clockwise triple containting the pivot point, or when the tree_hull size is 2. Deletes non-clockwise triples along the way. - int tl_prev = m_tree_hull.getElement(l_prev); - m_call_back.getXY(tl_prev, pt_l_prev); - - int orient_l_p_l_prev = Point2D.orientationRobust(pt_l, pt_pivot, pt_l_prev); - - if (isClockwise_(orient_l_p_l_prev)) - break; - - int ccw = l; - - l = l_prev; - pt_l.setCoords(pt_l_prev); - l_prev = m_tree_hull.getPrev(l); - m_call_back.deleteNode(ccw); - } - - return l; - } - - // Orientation predicates - private static ECoordinate determinant_(Point2D p, Point2D q, Point2D r) { - ECoordinate det_ec = new ECoordinate(); - det_ec.set(q.x); - det_ec.sub(p.x); - - ECoordinate rp_y_ec = new ECoordinate(); - rp_y_ec.set(r.y); - rp_y_ec.sub(p.y); - - ECoordinate qp_y_ec = new ECoordinate(); - qp_y_ec.set(q.y); - qp_y_ec.sub(p.y); - - ECoordinate rp_x_ec = new ECoordinate(); - rp_x_ec.set(r.x); - rp_x_ec.sub(p.x); - - det_ec.mul(rp_y_ec); - qp_y_ec.mul(rp_x_ec); - det_ec.sub(qp_y_ec); - return det_ec; - } - - private static boolean isClockwise_(double det) { - return det < 0.0; - } - - private static boolean isCounterClockwise_(double det) { - return det > 0.0; - } - - private static boolean isDegenerate_(double det) { - return det == 0.0; - } - - private static boolean isClockwise_(int orientation) { - return orientation < 0.0; - } - - private static boolean isCounterClockwise_(int orientation) { - return orientation > 0.0; - } - - private static boolean isDegenerate_(int orientation) { - return orientation == 0.0; - } - - private static int isBetween_(Point2D pt_pivot, Point2D pt_m, Point2D pt_0) { - int ordinate = -1; - - if (pt_m.y == pt_0.y) { - ordinate = 0; - } else if (pt_m.x == pt_0.x) { - ordinate = 1; - } else {// use bigger ordinate, but shouldn't matter - - double diff_x = Math.abs(pt_m.x - pt_0.x); - double diff_y = Math.abs(pt_m.y - pt_0.y); - - if (diff_x >= diff_y) - ordinate = 0; - else - ordinate = 1; - } - - int res = -1; - - if (ordinate == 0) { - assert (pt_m.x != pt_0.x); - - if (pt_m.x < pt_0.x) { - if (pt_pivot.x < pt_m.x) - res = -1; - else if (pt_0.x < pt_pivot.x) - res = 1; - else - res = 0; - } else { - assert (pt_0.x < pt_m.x); - - if (pt_m.x < pt_pivot.x) - res = -1; - else if (pt_pivot.x < pt_0.x) - res = 1; - else - res = 0; - } - } else { - assert (pt_m.y != pt_0.y); - - if (pt_m.y < pt_0.y) { - if (pt_pivot.y < pt_m.y) - res = -1; - else if (pt_0.y < pt_pivot.y) - res = 1; - else - res = 0; - } else { - assert (pt_0.y < pt_m.y); - - if (pt_m.y < pt_pivot.y) - res = -1; - else if (pt_pivot.y < pt_0.y) - res = 1; - else - res = 0; - } - } - - return res; - } - - private static abstract class CallBack { - abstract void getXY(int ti, Point2D pt); - - abstract void deleteNode(int i); - } - - private static final class CallBackShape extends CallBack { - private ConvexHull m_convex_hull; - - CallBackShape(ConvexHull convex_hull) { - m_convex_hull = convex_hull; - } - - @Override - void getXY(int ti, Point2D pt) { - m_convex_hull.m_shape.getXY(ti, pt); - } - - @Override - void deleteNode(int i) { - int ti = m_convex_hull.m_tree_hull.getElement(i); - m_convex_hull.m_tree_hull.deleteNode(i, -1); - m_convex_hull.m_shape.removeVertex(ti, false); - } - } - - private static final class CallBackStream extends CallBack { - private ConvexHull m_convex_hull; - - CallBackStream(ConvexHull convex_hull) { - m_convex_hull = convex_hull; - } - - @Override - void getXY(int ti, Point2D pt) { - m_convex_hull.m_stream.read(ti << 1, pt); - } - - @Override - void deleteNode(int i) { - m_convex_hull.m_tree_hull.deleteNode(i, -1); - } - } - - private static final class CallBackPoints extends CallBack { - private ConvexHull m_convex_hull; - - CallBackPoints(ConvexHull convex_hull) { - m_convex_hull = convex_hull; - } - - @Override - void getXY(int ti, Point2D pt) { - pt.setCoords(m_convex_hull.m_points[ti]); - } - - @Override - void deleteNode(int i) { - m_convex_hull.m_tree_hull.deleteNode(i, -1); - } - } - - // Members - private Treap m_tree_hull; - private EditShape m_shape; - private AttributeStreamOfDbl m_stream; - private Point2D[] m_points; - private int m_geometry_handle; - private int m_path_handle; - private Line m_line; - private CallBack m_call_back; + if (isClockwise_(orient_j_next_p_j)) + break; + + int ccw = j; + + j = j_next; + pt_j.setCoords(pt_j_next); + j_next = m_tree_hull.getNext(j); + m_call_back.deleteNode(ccw); + } + + return j; + } + + private int treeHullWalkBackward_(Point2D pt_pivot, int start, int end) { + if (start == end) + return end; + + int l = start; + int tl = m_tree_hull.getElement(l); + int l_prev = m_tree_hull.getPrev(l); + Point2D pt_l = new Point2D(); + Point2D pt_l_prev = new Point2D(); + + m_call_back.getXY(tl, pt_l); + + while (l != end && m_tree_hull.size(-1) > 2) {//Stops when we find a clockwise triple containting the pivot point, or when the tree_hull size is 2. Deletes non-clockwise triples along the way. + int tl_prev = m_tree_hull.getElement(l_prev); + m_call_back.getXY(tl_prev, pt_l_prev); + + int orient_l_p_l_prev = Point2D.orientationRobust(pt_l, pt_pivot, pt_l_prev); + + if (isClockwise_(orient_l_p_l_prev)) + break; + + int ccw = l; + + l = l_prev; + pt_l.setCoords(pt_l_prev); + l_prev = m_tree_hull.getPrev(l); + m_call_back.deleteNode(ccw); + } + + return l; + } + + // Orientation predicates + private static ECoordinate determinant_(Point2D p, Point2D q, Point2D r) { + ECoordinate det_ec = new ECoordinate(); + det_ec.set(q.x); + det_ec.sub(p.x); + + ECoordinate rp_y_ec = new ECoordinate(); + rp_y_ec.set(r.y); + rp_y_ec.sub(p.y); + + ECoordinate qp_y_ec = new ECoordinate(); + qp_y_ec.set(q.y); + qp_y_ec.sub(p.y); + + ECoordinate rp_x_ec = new ECoordinate(); + rp_x_ec.set(r.x); + rp_x_ec.sub(p.x); + + det_ec.mul(rp_y_ec); + qp_y_ec.mul(rp_x_ec); + det_ec.sub(qp_y_ec); + return det_ec; + } + + private static boolean isClockwise_(double det) { + return det < 0.0; + } + + private static boolean isCounterClockwise_(double det) { + return det > 0.0; + } + + private static boolean isDegenerate_(double det) { + return det == 0.0; + } + + private static boolean isClockwise_(int orientation) { + return orientation < 0.0; + } + + private static boolean isCounterClockwise_(int orientation) { + return orientation > 0.0; + } + + private static boolean isDegenerate_(int orientation) { + return orientation == 0.0; + } + + private static int isBetween_(Point2D pt_pivot, Point2D pt_m, Point2D pt_0) { + int ordinate = -1; + + if (pt_m.y == pt_0.y) { + ordinate = 0; + } else if (pt_m.x == pt_0.x) { + ordinate = 1; + } else {// use bigger ordinate, but shouldn't matter + + double diff_x = Math.abs(pt_m.x - pt_0.x); + double diff_y = Math.abs(pt_m.y - pt_0.y); + + if (diff_x >= diff_y) + ordinate = 0; + else + ordinate = 1; + } + + int res = -1; + + if (ordinate == 0) { + assert (pt_m.x != pt_0.x); + + if (pt_m.x < pt_0.x) { + if (pt_pivot.x < pt_m.x) + res = -1; + else if (pt_0.x < pt_pivot.x) + res = 1; + else + res = 0; + } else { + assert (pt_0.x < pt_m.x); + + if (pt_m.x < pt_pivot.x) + res = -1; + else if (pt_pivot.x < pt_0.x) + res = 1; + else + res = 0; + } + } else { + assert (pt_m.y != pt_0.y); + + if (pt_m.y < pt_0.y) { + if (pt_pivot.y < pt_m.y) + res = -1; + else if (pt_0.y < pt_pivot.y) + res = 1; + else + res = 0; + } else { + assert (pt_0.y < pt_m.y); + + if (pt_m.y < pt_pivot.y) + res = -1; + else if (pt_pivot.y < pt_0.y) + res = 1; + else + res = 0; + } + } + + return res; + } + + private static abstract class CallBack { + abstract void getXY(int ti, Point2D pt); + + abstract void deleteNode(int i); + } + + private static final class CallBackShape extends CallBack { + private ConvexHull m_convex_hull; + + CallBackShape(ConvexHull convex_hull) { + m_convex_hull = convex_hull; + } + + @Override + void getXY(int ti, Point2D pt) { + m_convex_hull.m_shape.getXY(ti, pt); + } + + @Override + void deleteNode(int i) { + int ti = m_convex_hull.m_tree_hull.getElement(i); + m_convex_hull.m_tree_hull.deleteNode(i, -1); + m_convex_hull.m_shape.removeVertex(ti, false); + } + } + + private static final class CallBackStream extends CallBack { + private ConvexHull m_convex_hull; + + CallBackStream(ConvexHull convex_hull) { + m_convex_hull = convex_hull; + } + + @Override + void getXY(int ti, Point2D pt) { + m_convex_hull.m_stream.read(ti << 1, pt); + } + + @Override + void deleteNode(int i) { + m_convex_hull.m_tree_hull.deleteNode(i, -1); + } + } + + private static final class CallBackPoints extends CallBack { + private ConvexHull m_convex_hull; + + CallBackPoints(ConvexHull convex_hull) { + m_convex_hull = convex_hull; + } + + @Override + void getXY(int ti, Point2D pt) { + pt.setCoords(m_convex_hull.m_points[ti]); + } + + @Override + void deleteNode(int i) { + m_convex_hull.m_tree_hull.deleteNode(i, -1); + } + } + + // Members + private Treap m_tree_hull; + private EditShape m_shape; + private AttributeStreamOfDbl m_stream; + private Point2D[] m_points; + private int m_geometry_handle; + private int m_path_handle; + private Line m_line; + private CallBack m_call_back; } diff --git a/src/main/java/com/esri/core/geometry/CrackAndCluster.java b/src/main/java/com/esri/core/geometry/CrackAndCluster.java index 68b920a9..f2bf559a 100644 --- a/src/main/java/com/esri/core/geometry/CrackAndCluster.java +++ b/src/main/java/com/esri/core/geometry/CrackAndCluster.java @@ -27,134 +27,134 @@ //Cracks and clusters all segments and vertices in the EditShape. final class CrackAndCluster { - private EditShape m_shape = null; - private ProgressTracker m_progressTracker = null; - private double m_tolerance; - private boolean m_filter_degenerate_segments = true; - - private CrackAndCluster(ProgressTracker progressTracker) { - m_progressTracker = progressTracker; - } - - static boolean non_empty_points_need_to_cluster(double tolerance, Point pt1, Point pt2) { - double tolerance_for_clustering = InternalUtils.adjust_tolerance_for_TE_clustering(tolerance); - return Clusterer.isClusterCandidate_(pt1.getX(), pt1.getY(), pt2.getX(), pt2.getY(), MathUtils.sqr(tolerance_for_clustering)); - } - - static Point cluster_non_empty_points(Point pt1, Point pt2, double w1, int rank1, double w2, int rank2) { - if (rank1 > rank2) { - return pt1; - } else if (rank2 < rank1) { - return pt2; - } - - int[] rank = null; - double[] w = null; - Point pt = new Point(); - Clusterer.mergeVertices(pt1, pt2, w1, rank1, w2, rank2, pt, w, rank); - return pt; - } - - public static boolean execute(EditShape shape, double tolerance, - ProgressTracker progressTracker, boolean filter_degenerate_segments) { - CrackAndCluster cracker = new CrackAndCluster(progressTracker); - cracker.m_shape = shape; - cracker.m_tolerance = tolerance; - cracker.m_filter_degenerate_segments = filter_degenerate_segments; - return cracker._do(); - } - - private boolean _cluster(double toleranceCluster) { - boolean res = Clusterer.executeNonReciprocal(m_shape, toleranceCluster); - return res; - } - - private boolean _crack(double tolerance_for_cracking) { - boolean res = Cracker.execute(m_shape, tolerance_for_cracking, m_progressTracker); - return res; - } - - private boolean _do() { - double tol = m_tolerance; - - // Use same tolerances as ArcObjects (2 * sqrt(2) * tolerance for - // clustering) - // sqrt(2) * tolerance for cracking. - // Also, inflate the tolerances slightly to insure the simplified result - // would not change after small rounding issues. - - final double c_factor = 1e-5; - final double c_factor_for_needs_cracking = 1e-6; - double tolerance_for_clustering = InternalUtils - .adjust_tolerance_for_TE_clustering(tol); - double tolerance_for_needs_cracking = InternalUtils - .adjust_tolerance_for_TE_cracking(tol); - double tolerance_for_cracking = tolerance_for_needs_cracking - * (1.0 + c_factor); - tolerance_for_needs_cracking *= (1.0 + c_factor_for_needs_cracking); - - // Require tolerance_for_clustering > tolerance_for_cracking > - // tolerance_for_needs_cracking - assert (tolerance_for_clustering > tolerance_for_cracking); - assert (tolerance_for_cracking > tolerance_for_needs_cracking); - - // double toleranceCluster = m_tolerance * Math.sqrt(2.0) * 1.00001; - boolean bChanged = false; - int max_iter = m_shape.getTotalPointCount() + 10 > 30 ? 1000 : (m_shape - .getTotalPointCount() + 10) - * (m_shape.getTotalPointCount() + 10); - int iter = 0; - boolean has_point_features = m_shape.hasPointFeatures(); - for (; ; iter++) { - if (iter > max_iter) - throw new GeometryException( - "Internal Error: max number of iterations exceeded");// too - // many - // iterations - - boolean bClustered = _cluster(tolerance_for_clustering); // find - // close - // vertices and - // clamp them - // together. - bChanged |= bClustered; - - if (m_filter_degenerate_segments) { - boolean bFiltered = (m_shape.filterClosePoints( - tolerance_for_clustering, true, false) != 0); // remove all - // degenerate - // segments. - bChanged |= bFiltered; - } - - boolean b_cracked = false; - if (iter == 0 - || has_point_features - || Cracker.needsCracking(true, m_shape, - tolerance_for_needs_cracking, null, - m_progressTracker)) { - // Cracks only if shape contains segments. - b_cracked = _crack(tolerance_for_cracking); // crack all - // segments at - // intersection - // points and touch - // points. If - // Cracked, then the - // iteration will be - // repeated. - bChanged |= b_cracked; - } - - if (!b_cracked) - break;// was not cracked, so we can bail out. - else { - // Loop while cracking happens. - } - - ProgressTracker.checkAndThrow(m_progressTracker); - } - - return bChanged; - } + private EditShape m_shape = null; + private ProgressTracker m_progressTracker = null; + private double m_tolerance; + private boolean m_filter_degenerate_segments = true; + + private CrackAndCluster(ProgressTracker progressTracker) { + m_progressTracker = progressTracker; + } + + static boolean non_empty_points_need_to_cluster(double tolerance, Point pt1, Point pt2) { + double tolerance_for_clustering = InternalUtils.adjust_tolerance_for_TE_clustering(tolerance); + return Clusterer.isClusterCandidate_(pt1.getX(), pt1.getY(), pt2.getX(), pt2.getY(), MathUtils.sqr(tolerance_for_clustering)); + } + + static Point cluster_non_empty_points(Point pt1, Point pt2, double w1, int rank1, double w2, int rank2) { + if (rank1 > rank2) { + return pt1; + } else if (rank2 < rank1) { + return pt2; + } + + int[] rank = null; + double[] w = null; + Point pt = new Point(); + Clusterer.mergeVertices(pt1, pt2, w1, rank1, w2, rank2, pt, w, rank); + return pt; + } + + public static boolean execute(EditShape shape, double tolerance, + ProgressTracker progressTracker, boolean filter_degenerate_segments) { + CrackAndCluster cracker = new CrackAndCluster(progressTracker); + cracker.m_shape = shape; + cracker.m_tolerance = tolerance; + cracker.m_filter_degenerate_segments = filter_degenerate_segments; + return cracker._do(); + } + + private boolean _cluster(double toleranceCluster) { + boolean res = Clusterer.executeNonReciprocal(m_shape, toleranceCluster); + return res; + } + + private boolean _crack(double tolerance_for_cracking) { + boolean res = Cracker.execute(m_shape, tolerance_for_cracking, m_progressTracker); + return res; + } + + private boolean _do() { + double tol = m_tolerance; + + // Use same tolerances as ArcObjects (2 * sqrt(2) * tolerance for + // clustering) + // sqrt(2) * tolerance for cracking. + // Also, inflate the tolerances slightly to insure the simplified result + // would not change after small rounding issues. + + final double c_factor = 1e-5; + final double c_factor_for_needs_cracking = 1e-6; + double tolerance_for_clustering = InternalUtils + .adjust_tolerance_for_TE_clustering(tol); + double tolerance_for_needs_cracking = InternalUtils + .adjust_tolerance_for_TE_cracking(tol); + double tolerance_for_cracking = tolerance_for_needs_cracking + * (1.0 + c_factor); + tolerance_for_needs_cracking *= (1.0 + c_factor_for_needs_cracking); + + // Require tolerance_for_clustering > tolerance_for_cracking > + // tolerance_for_needs_cracking + assert (tolerance_for_clustering > tolerance_for_cracking); + assert (tolerance_for_cracking > tolerance_for_needs_cracking); + + // double toleranceCluster = m_tolerance * Math.sqrt(2.0) * 1.00001; + boolean bChanged = false; + int max_iter = m_shape.getTotalPointCount() + 10 > 30 ? 1000 : (m_shape + .getTotalPointCount() + 10) + * (m_shape.getTotalPointCount() + 10); + int iter = 0; + boolean has_point_features = m_shape.hasPointFeatures(); + for (; ; iter++) { + if (iter > max_iter) + throw new GeometryException( + "Internal Error: max number of iterations exceeded");// too + // many + // iterations + + boolean bClustered = _cluster(tolerance_for_clustering); // find + // close + // vertices and + // clamp them + // together. + bChanged |= bClustered; + + if (m_filter_degenerate_segments) { + boolean bFiltered = (m_shape.filterClosePoints( + tolerance_for_clustering, true, false) != 0); // remove all + // degenerate + // segments. + bChanged |= bFiltered; + } + + boolean b_cracked = false; + if (iter == 0 + || has_point_features + || Cracker.needsCracking(true, m_shape, + tolerance_for_needs_cracking, null, + m_progressTracker)) { + // Cracks only if shape contains segments. + b_cracked = _crack(tolerance_for_cracking); // crack all + // segments at + // intersection + // points and touch + // points. If + // Cracked, then the + // iteration will be + // repeated. + bChanged |= b_cracked; + } + + if (!b_cracked) + break;// was not cracked, so we can bail out. + else { + // Loop while cracking happens. + } + + ProgressTracker.checkAndThrow(m_progressTracker); + } + + return bChanged; + } } diff --git a/src/main/java/com/esri/core/geometry/Cracker.java b/src/main/java/com/esri/core/geometry/Cracker.java index b0cd16f2..55d61ef4 100644 --- a/src/main/java/com/esri/core/geometry/Cracker.java +++ b/src/main/java/com/esri/core/geometry/Cracker.java @@ -31,516 +31,516 @@ * Simplify. */ final class Cracker { - private EditShape m_shape; - private ProgressTracker m_progress_tracker; - private NonSimpleResult m_non_simple_result; - private double m_tolerance; - private Treap m_sweep_structure; - private SweepComparator m_sweep_comparator; - private boolean m_bAllowCoincident; - - private Segment getSegment_(int vertex, Line lineHelper) { - Segment seg = m_shape.getSegment(vertex); - if (seg == null) { - if (!m_shape.queryLineConnector(vertex, lineHelper)) - return null; - - seg = (Segment) lineHelper; - } - - return seg; - } - - private boolean crackBruteForce_() { - EditShape.VertexIterator iter_1 = m_shape.queryVertexIterator(false); - boolean b_cracked = false; - Line line_1 = new Line(); - Line line_2 = new Line(); - Envelope2D seg_1_env = new Envelope2D(); - seg_1_env.setEmpty(); - Envelope2D seg_2_env = new Envelope2D(); - seg_2_env.setEmpty(); - boolean assume_intersecting = false; - Point helper_point = new Point(); - SegmentIntersector segment_intersector = new SegmentIntersector(); - - for (int vertex_1 = iter_1.next(); vertex_1 != -1; vertex_1 = iter_1 - .next()) { - ProgressTracker.checkAndThrow(m_progress_tracker); - - int GT_1 = m_shape.getGeometryType(iter_1.currentGeometry()); - - Segment seg_1 = null; - boolean seg_1_zero = false; - if (!Geometry.isPoint(GT_1)) { - seg_1 = getSegment_(vertex_1, line_1); - if (seg_1 == null) - continue; - - seg_1.queryEnvelope2D(seg_1_env); - seg_1_env.inflate(m_tolerance, m_tolerance); - - if (seg_1.isDegenerate(m_tolerance))// do not crack with - // degenerate segments - { - if (seg_1.isDegenerate(0)) { - seg_1_zero = true; - seg_1 = null; - } else { - continue; - } - } - } - - EditShape.VertexIterator iter_2 = m_shape - .queryVertexIterator(iter_1); - int vertex_2 = iter_2.next(); - if (vertex_2 != -1) - vertex_2 = iter_2.next(); - - for (; vertex_2 != -1; vertex_2 = iter_2.next()) { - int GT_2 = m_shape.getGeometryType(iter_2.currentGeometry()); - - Segment seg_2 = null; - boolean seg_2_zero = false; - if (!Geometry.isPoint(GT_2)) { - seg_2 = getSegment_(vertex_2, line_2); - if (seg_2 == null) { - continue; - } - - seg_2.queryEnvelope2D(seg_2_env); - if (seg_2.isDegenerate(m_tolerance))// do not crack with - // degenerate segments - { - if (seg_2.isDegenerate(0)) { - seg_2_zero = true; - seg_2 = null; - } else { - continue; - } - } - } - - int split_count_1 = 0; - int split_count_2 = 0; - if (seg_1 != null && seg_2 != null) { - if (seg_1_env.isIntersectingNE(seg_2_env)) { - segment_intersector.pushSegment(seg_1); - segment_intersector.pushSegment(seg_2); - segment_intersector.intersect(m_tolerance, - assume_intersecting); - split_count_1 = segment_intersector - .getResultSegmentCount(0); - split_count_2 = segment_intersector - .getResultSegmentCount(1); - if (split_count_1 + split_count_2 > 0) { - m_shape.splitSegment_(vertex_1, - segment_intersector, 0, true); - m_shape.splitSegment_(vertex_2, - segment_intersector, 1, true); - } - segment_intersector.clear(); - } - } else { - if (seg_1 != null) { - Point2D pt = new Point2D(); - m_shape.getXY(vertex_2, pt); - if (seg_1_env.contains(pt)) { - segment_intersector.pushSegment(seg_1); - m_shape.queryPoint(vertex_2, helper_point); - segment_intersector.intersect(m_tolerance, - helper_point, 0, 1.0, assume_intersecting); - split_count_1 = segment_intersector - .getResultSegmentCount(0); - if (split_count_1 > 0) { - m_shape.splitSegment_(vertex_1, - segment_intersector, 0, true); - if (seg_2_zero) { - //seg_2 was zero length. Need to change all coincident points - //segment at vertex_2 is dzero length, change all attached zero length segments - int v_to = -1; - for (int v = m_shape.getNextVertex(vertex_2); v != -1 && v != vertex_2; v = m_shape.getNextVertex(v)) { - seg_2 = getSegment_(v, line_2); - v_to = v; - if (seg_2 == null || !seg_2.isDegenerate(0)) - break; - } - //change from vertex_2 to v_to (inclusive). - for (int v = vertex_2; v != -1; v = m_shape.getNextVertex(v)) { - m_shape.setPoint(v, segment_intersector.getResultPoint()); - if (v == v_to) - break; - } - } else { - m_shape.setPoint(vertex_2, - segment_intersector.getResultPoint()); - } - } - segment_intersector.clear(); - } - } else if (seg_2 != null) { - Point2D pt = new Point2D(); - m_shape.getXY(vertex_1, pt); - seg_2_env.inflate(m_tolerance, m_tolerance); - if (seg_2_env.contains(pt)) { - segment_intersector.pushSegment(seg_2); - m_shape.queryPoint(vertex_1, helper_point); - segment_intersector.intersect(m_tolerance, - helper_point, 0, 1.0, assume_intersecting); - split_count_2 = segment_intersector - .getResultSegmentCount(0); - if (split_count_2 > 0) { - m_shape.splitSegment_(vertex_2, - segment_intersector, 0, true); - if (seg_1_zero) { - //seg_1 was zero length. Need to change all coincident points - //segment at vertex_2 is dzero length, change all attached zero length segments - int v_to = -1; - for (int v = m_shape.getNextVertex(vertex_1); v != -1 && v != vertex_1; v = m_shape.getNextVertex(v)) { - seg_2 = getSegment_(v, line_2);//using here seg_2 for seg_1 - v_to = v; - if (seg_2 == null || !seg_2.isDegenerate(0)) - break; - } - //change from vertex_2 to v_to (inclusive). - for (int v = vertex_1; v != -1; v = m_shape.getNextVertex(v)) { - m_shape.setPoint(v, segment_intersector.getResultPoint()); - if (v == v_to) - break; - } - } else { - m_shape.setPoint(vertex_1, - segment_intersector.getResultPoint()); - } - } - segment_intersector.clear(); - } - } else { - continue;// points on points - } - } - - if (split_count_1 + split_count_2 != 0) { - if (split_count_1 != 0) { - seg_1 = m_shape.getSegment(vertex_1);// reload segment - // after split - if (seg_1 == null) { - if (!m_shape.queryLineConnector(vertex_1, line_1)) - continue; - seg_1 = line_1; - line_1.queryEnvelope2D(seg_1_env); - } else - seg_1.queryEnvelope2D(seg_1_env); - - if (seg_1.isDegenerate(m_tolerance))// do not crack with - // degenerate - // segments - { - break; - } - } - - b_cracked = true; - } - } - } - - return b_cracked; - } - - boolean crackerPlaneSweep_() { - boolean b_cracked = planeSweep_(); - return b_cracked; - } - - boolean planeSweep_() { - PlaneSweepCrackerHelper plane_sweep = new PlaneSweepCrackerHelper(); - boolean b_cracked = plane_sweep.sweep(m_shape, m_tolerance); - return b_cracked; - } - - boolean needsCrackingImpl_() { - boolean b_needs_cracking = false; - - if (m_sweep_structure == null) - m_sweep_structure = new Treap(); - - AttributeStreamOfInt32 event_q = new AttributeStreamOfInt32(0); - event_q.reserve(m_shape.getTotalPointCount() + 1); - - EditShape.VertexIterator iter = m_shape.queryVertexIterator(); - for (int vert = iter.next(); vert != -1; vert = iter.next()) { - event_q.add(vert); - } - assert (m_shape.getTotalPointCount() == event_q.size()); - - m_shape.sortVerticesSimpleByY_(event_q, 0, event_q.size()); - event_q.add(-1);// for termination; - // create user indices to store edges that end at vertices. - int edge_index_1 = m_shape.createUserIndex(); - int edge_index_2 = m_shape.createUserIndex(); - m_sweep_comparator = new SweepComparator(m_shape, m_tolerance, !m_bAllowCoincident); - m_sweep_structure.setComparator(m_sweep_comparator); - - AttributeStreamOfInt32 swept_edges_to_delete = new AttributeStreamOfInt32( - 0); - AttributeStreamOfInt32 edges_to_insert = new AttributeStreamOfInt32(0); - - // Go throught the sorted vertices - int event_q_index = 0; - Point2D cluster_pt = new Point2D(); - - // sweep-line algorithm: - for (int vertex = event_q.get(event_q_index++); vertex != -1; ) { - m_shape.getXY(vertex, cluster_pt); - - do { - int next_vertex = m_shape.getNextVertex(vertex); - int prev_vertex = m_shape.getPrevVertex(vertex); - - if (next_vertex != -1 - && m_shape.compareVerticesSimpleY_(vertex, next_vertex) < 0) { - edges_to_insert.add(vertex); - edges_to_insert.add(next_vertex); - } - - if (prev_vertex != -1 - && m_shape.compareVerticesSimpleY_(vertex, prev_vertex) < 0) { - edges_to_insert.add(prev_vertex); - edges_to_insert.add(prev_vertex); - } - - // Continue accumulating current cluster - int attached_edge_1 = m_shape - .getUserIndex(vertex, edge_index_1); - if (attached_edge_1 != -1) { - swept_edges_to_delete.add(attached_edge_1); - m_shape.setUserIndex(vertex, edge_index_1, -1); - } - int attached_edge_2 = m_shape - .getUserIndex(vertex, edge_index_2); - if (attached_edge_2 != -1) { - swept_edges_to_delete.add(attached_edge_2); - m_shape.setUserIndex(vertex, edge_index_2, -1); - } - vertex = event_q.get(event_q_index++); - } while (vertex != -1 && m_shape.isEqualXY(vertex, cluster_pt)); - - boolean b_continuing_segment_chain_optimization = swept_edges_to_delete - .size() == 1 && edges_to_insert.size() == 2; - - int new_left = -1; - int new_right = -1; - // Process the cluster - for (int i = 0, n = swept_edges_to_delete.size(); i < n; i++) { - // Find left and right neighbour of the edges that terminate at - // the cluster (there will be atmost only one left and one - // right). - int edge = swept_edges_to_delete.get(i); - int left = m_sweep_structure.getPrev(edge); - if (left != -1 && !swept_edges_to_delete.hasElement(left))// Note: - // for - // some - // heavy - // cases, - // it - // could - // be - // better - // to - // use - // binary - // search. - { - assert (new_left == -1); - new_left = left; - } - - int right = m_sweep_structure.getNext(edge); - if (right != -1 && !swept_edges_to_delete.hasElement(right)) { - assert (new_right == -1); - new_right = right; - } + private EditShape m_shape; + private ProgressTracker m_progress_tracker; + private NonSimpleResult m_non_simple_result; + private double m_tolerance; + private Treap m_sweep_structure; + private SweepComparator m_sweep_comparator; + private boolean m_bAllowCoincident; + + private Segment getSegment_(int vertex, Line lineHelper) { + Segment seg = m_shape.getSegment(vertex); + if (seg == null) { + if (!m_shape.queryLineConnector(vertex, lineHelper)) + return null; + + seg = (Segment) lineHelper; + } + + return seg; + } + + private boolean crackBruteForce_() { + EditShape.VertexIterator iter_1 = m_shape.queryVertexIterator(false); + boolean b_cracked = false; + Line line_1 = new Line(); + Line line_2 = new Line(); + Envelope2D seg_1_env = new Envelope2D(); + seg_1_env.setEmpty(); + Envelope2D seg_2_env = new Envelope2D(); + seg_2_env.setEmpty(); + boolean assume_intersecting = false; + Point helper_point = new Point(); + SegmentIntersector segment_intersector = new SegmentIntersector(); + + for (int vertex_1 = iter_1.next(); vertex_1 != -1; vertex_1 = iter_1 + .next()) { + ProgressTracker.checkAndThrow(m_progress_tracker); + + int GT_1 = m_shape.getGeometryType(iter_1.currentGeometry()); + + Segment seg_1 = null; + boolean seg_1_zero = false; + if (!Geometry.isPoint(GT_1)) { + seg_1 = getSegment_(vertex_1, line_1); + if (seg_1 == null) + continue; + + seg_1.queryEnvelope2D(seg_1_env); + seg_1_env.inflate(m_tolerance, m_tolerance); + + if (seg_1.isDegenerate(m_tolerance))// do not crack with + // degenerate segments + { + if (seg_1.isDegenerate(0)) { + seg_1_zero = true; + seg_1 = null; + } else { + continue; + } + } + } + + EditShape.VertexIterator iter_2 = m_shape + .queryVertexIterator(iter_1); + int vertex_2 = iter_2.next(); + if (vertex_2 != -1) + vertex_2 = iter_2.next(); + + for (; vertex_2 != -1; vertex_2 = iter_2.next()) { + int GT_2 = m_shape.getGeometryType(iter_2.currentGeometry()); + + Segment seg_2 = null; + boolean seg_2_zero = false; + if (!Geometry.isPoint(GT_2)) { + seg_2 = getSegment_(vertex_2, line_2); + if (seg_2 == null) { + continue; + } + + seg_2.queryEnvelope2D(seg_2_env); + if (seg_2.isDegenerate(m_tolerance))// do not crack with + // degenerate segments + { + if (seg_2.isDegenerate(0)) { + seg_2_zero = true; + seg_2 = null; + } else { + continue; + } + } + } + + int split_count_1 = 0; + int split_count_2 = 0; + if (seg_1 != null && seg_2 != null) { + if (seg_1_env.isIntersectingNE(seg_2_env)) { + segment_intersector.pushSegment(seg_1); + segment_intersector.pushSegment(seg_2); + segment_intersector.intersect(m_tolerance, + assume_intersecting); + split_count_1 = segment_intersector + .getResultSegmentCount(0); + split_count_2 = segment_intersector + .getResultSegmentCount(1); + if (split_count_1 + split_count_2 > 0) { + m_shape.splitSegment_(vertex_1, + segment_intersector, 0, true); + m_shape.splitSegment_(vertex_2, + segment_intersector, 1, true); + } + segment_intersector.clear(); + } + } else { + if (seg_1 != null) { + Point2D pt = new Point2D(); + m_shape.getXY(vertex_2, pt); + if (seg_1_env.contains(pt)) { + segment_intersector.pushSegment(seg_1); + m_shape.queryPoint(vertex_2, helper_point); + segment_intersector.intersect(m_tolerance, + helper_point, 0, 1.0, assume_intersecting); + split_count_1 = segment_intersector + .getResultSegmentCount(0); + if (split_count_1 > 0) { + m_shape.splitSegment_(vertex_1, + segment_intersector, 0, true); + if (seg_2_zero) { + //seg_2 was zero length. Need to change all coincident points + //segment at vertex_2 is dzero length, change all attached zero length segments + int v_to = -1; + for (int v = m_shape.getNextVertex(vertex_2); v != -1 && v != vertex_2; v = m_shape.getNextVertex(v)) { + seg_2 = getSegment_(v, line_2); + v_to = v; + if (seg_2 == null || !seg_2.isDegenerate(0)) + break; + } + //change from vertex_2 to v_to (inclusive). + for (int v = vertex_2; v != -1; v = m_shape.getNextVertex(v)) { + m_shape.setPoint(v, segment_intersector.getResultPoint()); + if (v == v_to) + break; + } + } else { + m_shape.setPoint(vertex_2, + segment_intersector.getResultPoint()); + } + } + segment_intersector.clear(); + } + } else if (seg_2 != null) { + Point2D pt = new Point2D(); + m_shape.getXY(vertex_1, pt); + seg_2_env.inflate(m_tolerance, m_tolerance); + if (seg_2_env.contains(pt)) { + segment_intersector.pushSegment(seg_2); + m_shape.queryPoint(vertex_1, helper_point); + segment_intersector.intersect(m_tolerance, + helper_point, 0, 1.0, assume_intersecting); + split_count_2 = segment_intersector + .getResultSegmentCount(0); + if (split_count_2 > 0) { + m_shape.splitSegment_(vertex_2, + segment_intersector, 0, true); + if (seg_1_zero) { + //seg_1 was zero length. Need to change all coincident points + //segment at vertex_2 is dzero length, change all attached zero length segments + int v_to = -1; + for (int v = m_shape.getNextVertex(vertex_1); v != -1 && v != vertex_1; v = m_shape.getNextVertex(v)) { + seg_2 = getSegment_(v, line_2);//using here seg_2 for seg_1 + v_to = v; + if (seg_2 == null || !seg_2.isDegenerate(0)) + break; + } + //change from vertex_2 to v_to (inclusive). + for (int v = vertex_1; v != -1; v = m_shape.getNextVertex(v)) { + m_shape.setPoint(v, segment_intersector.getResultPoint()); + if (v == v_to) + break; + } + } else { + m_shape.setPoint(vertex_1, + segment_intersector.getResultPoint()); + } + } + segment_intersector.clear(); + } + } else { + continue;// points on points + } + } + + if (split_count_1 + split_count_2 != 0) { + if (split_count_1 != 0) { + seg_1 = m_shape.getSegment(vertex_1);// reload segment + // after split + if (seg_1 == null) { + if (!m_shape.queryLineConnector(vertex_1, line_1)) + continue; + seg_1 = line_1; + line_1.queryEnvelope2D(seg_1_env); + } else + seg_1.queryEnvelope2D(seg_1_env); + + if (seg_1.isDegenerate(m_tolerance))// do not crack with + // degenerate + // segments + { + break; + } + } + + b_cracked = true; + } + } + } + + return b_cracked; + } + + boolean crackerPlaneSweep_() { + boolean b_cracked = planeSweep_(); + return b_cracked; + } + + boolean planeSweep_() { + PlaneSweepCrackerHelper plane_sweep = new PlaneSweepCrackerHelper(); + boolean b_cracked = plane_sweep.sweep(m_shape, m_tolerance); + return b_cracked; + } + + boolean needsCrackingImpl_() { + boolean b_needs_cracking = false; + + if (m_sweep_structure == null) + m_sweep_structure = new Treap(); + + AttributeStreamOfInt32 event_q = new AttributeStreamOfInt32(0); + event_q.reserve(m_shape.getTotalPointCount() + 1); + + EditShape.VertexIterator iter = m_shape.queryVertexIterator(); + for (int vert = iter.next(); vert != -1; vert = iter.next()) { + event_q.add(vert); + } + assert (m_shape.getTotalPointCount() == event_q.size()); + + m_shape.sortVerticesSimpleByY_(event_q, 0, event_q.size()); + event_q.add(-1);// for termination; + // create user indices to store edges that end at vertices. + int edge_index_1 = m_shape.createUserIndex(); + int edge_index_2 = m_shape.createUserIndex(); + m_sweep_comparator = new SweepComparator(m_shape, m_tolerance, !m_bAllowCoincident); + m_sweep_structure.setComparator(m_sweep_comparator); + + AttributeStreamOfInt32 swept_edges_to_delete = new AttributeStreamOfInt32( + 0); + AttributeStreamOfInt32 edges_to_insert = new AttributeStreamOfInt32(0); + + // Go throught the sorted vertices + int event_q_index = 0; + Point2D cluster_pt = new Point2D(); + + // sweep-line algorithm: + for (int vertex = event_q.get(event_q_index++); vertex != -1; ) { + m_shape.getXY(vertex, cluster_pt); + + do { + int next_vertex = m_shape.getNextVertex(vertex); + int prev_vertex = m_shape.getPrevVertex(vertex); + + if (next_vertex != -1 + && m_shape.compareVerticesSimpleY_(vertex, next_vertex) < 0) { + edges_to_insert.add(vertex); + edges_to_insert.add(next_vertex); + } + + if (prev_vertex != -1 + && m_shape.compareVerticesSimpleY_(vertex, prev_vertex) < 0) { + edges_to_insert.add(prev_vertex); + edges_to_insert.add(prev_vertex); + } + + // Continue accumulating current cluster + int attached_edge_1 = m_shape + .getUserIndex(vertex, edge_index_1); + if (attached_edge_1 != -1) { + swept_edges_to_delete.add(attached_edge_1); + m_shape.setUserIndex(vertex, edge_index_1, -1); + } + int attached_edge_2 = m_shape + .getUserIndex(vertex, edge_index_2); + if (attached_edge_2 != -1) { + swept_edges_to_delete.add(attached_edge_2); + m_shape.setUserIndex(vertex, edge_index_2, -1); + } + vertex = event_q.get(event_q_index++); + } while (vertex != -1 && m_shape.isEqualXY(vertex, cluster_pt)); + + boolean b_continuing_segment_chain_optimization = swept_edges_to_delete + .size() == 1 && edges_to_insert.size() == 2; + + int new_left = -1; + int new_right = -1; + // Process the cluster + for (int i = 0, n = swept_edges_to_delete.size(); i < n; i++) { + // Find left and right neighbour of the edges that terminate at + // the cluster (there will be atmost only one left and one + // right). + int edge = swept_edges_to_delete.get(i); + int left = m_sweep_structure.getPrev(edge); + if (left != -1 && !swept_edges_to_delete.hasElement(left))// Note: + // for + // some + // heavy + // cases, + // it + // could + // be + // better + // to + // use + // binary + // search. + { + assert (new_left == -1); + new_left = left; + } + + int right = m_sweep_structure.getNext(edge); + if (right != -1 && !swept_edges_to_delete.hasElement(right)) { + assert (new_right == -1); + new_right = right; + } //#ifdef NDEBUG - if (new_left != -1 && new_right != -1) - break; + if (new_left != -1 && new_right != -1) + break; //#endif - } - - assert (new_left == -1 || new_left != new_right); - - m_sweep_comparator.setSweepY(cluster_pt.y, cluster_pt.x); - - // Delete the edges that terminate at the cluster. - for (int i = 0, n = swept_edges_to_delete.size(); i < n; i++) { - int edge = swept_edges_to_delete.get(i); - m_sweep_structure.deleteNode(edge, -1); - } - swept_edges_to_delete.clear(false); - - if (!b_continuing_segment_chain_optimization && new_left != -1 && new_right != -1) { - if (checkForIntersections_(new_left, new_right)) { - b_needs_cracking = true; - m_non_simple_result = m_sweep_comparator.getResult(); - break; - } - } - - for (int i = 0, n = edges_to_insert.size(); i < n; i += 2) { - int v = edges_to_insert.get(i); - int otherv = edges_to_insert.get(i + 1); - - int new_edge_1 = -1; - if (b_continuing_segment_chain_optimization) { - new_edge_1 = m_sweep_structure.addElementAtPosition( - new_left, new_right, v, true, true, -1); - b_continuing_segment_chain_optimization = false; - } else { - new_edge_1 = m_sweep_structure.addElement(v, -1); // the - // sweep - // structure - // consist - // of - // the - // origin - // vertices - // for - // edges. - // One - // can - // always - // get - // the - // other - // endpoint - // as - // the - // next - // vertex. - } - - if (m_sweep_comparator.intersectionDetected()) { - m_non_simple_result = m_sweep_comparator.getResult(); - b_needs_cracking = true; - break; - } - - int e_1 = m_shape.getUserIndex(otherv, edge_index_1); - if (e_1 == -1) - m_shape.setUserIndex(otherv, edge_index_1, new_edge_1); - else { - assert (m_shape.getUserIndex(otherv, edge_index_2) == -1); - m_shape.setUserIndex(otherv, edge_index_2, new_edge_1); - } - } - - if (b_needs_cracking) - break; - - // Start accumulating new cluster - edges_to_insert.resizePreserveCapacity(0); - } - - m_shape.removeUserIndex(edge_index_1); - m_shape.removeUserIndex(edge_index_2); - return b_needs_cracking; - } - - boolean checkForIntersections_(int sweep_edge_1, int sweep_edge_2) { - assert (sweep_edge_1 != sweep_edge_2); - int left = m_sweep_structure.getElement(sweep_edge_1); - assert (left != m_sweep_structure.getElement(sweep_edge_2)); - m_sweep_comparator.compare(m_sweep_structure, left, sweep_edge_2);// compare - // detects - // intersections - boolean b_intersects = m_sweep_comparator.intersectionDetected(); - m_sweep_comparator.clearIntersectionDetectedFlag(); - return b_intersects; - } - - // void dbg_print_sweep_edge_(int edge); - // void dbg_print_sweep_structure_(); - // void dbg_check_sweep_structure_(); - Cracker(ProgressTracker progress_tracker) { - m_progress_tracker = progress_tracker; - m_bAllowCoincident = true; - } - - static boolean canBeCracked(EditShape shape) { - for (int geometry = shape.getFirstGeometry(); geometry != -1; geometry = shape - .getNextGeometry(geometry)) { - if (!Geometry.isMultiPath(shape.getGeometryType(geometry))) - continue; - return true; - } - return false; - } - - static boolean execute(EditShape shape, Envelope2D extent, - double tolerance, ProgressTracker progress_tracker) { - if (!canBeCracked(shape)) // make sure it contains some segments, - // otherwise no need to crack. - return false; - - Cracker cracker = new Cracker(progress_tracker); - cracker.m_shape = shape; - cracker.m_tolerance = tolerance; - // Use brute force for smaller shapes, and a planesweep for bigger - // shapes. - boolean b_cracked = false; - if (shape.getTotalPointCount() < 15) // what is a good number? - { - b_cracked = cracker.crackBruteForce_(); - } else { - boolean b_cracked_1 = cracker.crackerPlaneSweep_(); - return b_cracked_1; - } - return b_cracked; - } - - static boolean execute(EditShape shape, double tolerance, - ProgressTracker progress_tracker) { - return Cracker.execute(shape, shape.getEnvelope2D(), tolerance, - progress_tracker); - } - - // Used for IsSimple. - static boolean needsCracking(boolean allowCoincident, EditShape shape, double tolerance, - NonSimpleResult result, ProgressTracker progress_tracker) { - if (!canBeCracked(shape)) - return false; - - Cracker cracker = new Cracker(progress_tracker); - cracker.m_shape = shape; - cracker.m_tolerance = tolerance; - cracker.m_bAllowCoincident = allowCoincident; - if (cracker.needsCrackingImpl_()) { - if (result != null) - result.Assign(cracker.m_non_simple_result); - return true; - } - - // Now swap the coordinates to catch horizontal cases. - Transformation2D transform = new Transformation2D(); - transform.setSwapCoordinates(); - shape.applyTransformation(transform); - - cracker = new Cracker(progress_tracker); - cracker.m_shape = shape; - cracker.m_tolerance = tolerance; - cracker.m_bAllowCoincident = allowCoincident; - boolean b_res = cracker.needsCrackingImpl_(); - - transform.setSwapCoordinates(); - shape.applyTransformation(transform);// restore shape - - if (b_res) { - if (result != null) - result.Assign(cracker.m_non_simple_result); - return true; - } - - return false; - } + } + + assert (new_left == -1 || new_left != new_right); + + m_sweep_comparator.setSweepY(cluster_pt.y, cluster_pt.x); + + // Delete the edges that terminate at the cluster. + for (int i = 0, n = swept_edges_to_delete.size(); i < n; i++) { + int edge = swept_edges_to_delete.get(i); + m_sweep_structure.deleteNode(edge, -1); + } + swept_edges_to_delete.clear(false); + + if (!b_continuing_segment_chain_optimization && new_left != -1 && new_right != -1) { + if (checkForIntersections_(new_left, new_right)) { + b_needs_cracking = true; + m_non_simple_result = m_sweep_comparator.getResult(); + break; + } + } + + for (int i = 0, n = edges_to_insert.size(); i < n; i += 2) { + int v = edges_to_insert.get(i); + int otherv = edges_to_insert.get(i + 1); + + int new_edge_1 = -1; + if (b_continuing_segment_chain_optimization) { + new_edge_1 = m_sweep_structure.addElementAtPosition( + new_left, new_right, v, true, true, -1); + b_continuing_segment_chain_optimization = false; + } else { + new_edge_1 = m_sweep_structure.addElement(v, -1); // the + // sweep + // structure + // consist + // of + // the + // origin + // vertices + // for + // edges. + // One + // can + // always + // get + // the + // other + // endpoint + // as + // the + // next + // vertex. + } + + if (m_sweep_comparator.intersectionDetected()) { + m_non_simple_result = m_sweep_comparator.getResult(); + b_needs_cracking = true; + break; + } + + int e_1 = m_shape.getUserIndex(otherv, edge_index_1); + if (e_1 == -1) + m_shape.setUserIndex(otherv, edge_index_1, new_edge_1); + else { + assert (m_shape.getUserIndex(otherv, edge_index_2) == -1); + m_shape.setUserIndex(otherv, edge_index_2, new_edge_1); + } + } + + if (b_needs_cracking) + break; + + // Start accumulating new cluster + edges_to_insert.resizePreserveCapacity(0); + } + + m_shape.removeUserIndex(edge_index_1); + m_shape.removeUserIndex(edge_index_2); + return b_needs_cracking; + } + + boolean checkForIntersections_(int sweep_edge_1, int sweep_edge_2) { + assert (sweep_edge_1 != sweep_edge_2); + int left = m_sweep_structure.getElement(sweep_edge_1); + assert (left != m_sweep_structure.getElement(sweep_edge_2)); + m_sweep_comparator.compare(m_sweep_structure, left, sweep_edge_2);// compare + // detects + // intersections + boolean b_intersects = m_sweep_comparator.intersectionDetected(); + m_sweep_comparator.clearIntersectionDetectedFlag(); + return b_intersects; + } + + // void dbg_print_sweep_edge_(int edge); + // void dbg_print_sweep_structure_(); + // void dbg_check_sweep_structure_(); + Cracker(ProgressTracker progress_tracker) { + m_progress_tracker = progress_tracker; + m_bAllowCoincident = true; + } + + static boolean canBeCracked(EditShape shape) { + for (int geometry = shape.getFirstGeometry(); geometry != -1; geometry = shape + .getNextGeometry(geometry)) { + if (!Geometry.isMultiPath(shape.getGeometryType(geometry))) + continue; + return true; + } + return false; + } + + static boolean execute(EditShape shape, Envelope2D extent, + double tolerance, ProgressTracker progress_tracker) { + if (!canBeCracked(shape)) // make sure it contains some segments, + // otherwise no need to crack. + return false; + + Cracker cracker = new Cracker(progress_tracker); + cracker.m_shape = shape; + cracker.m_tolerance = tolerance; + // Use brute force for smaller shapes, and a planesweep for bigger + // shapes. + boolean b_cracked = false; + if (shape.getTotalPointCount() < 15) // what is a good number? + { + b_cracked = cracker.crackBruteForce_(); + } else { + boolean b_cracked_1 = cracker.crackerPlaneSweep_(); + return b_cracked_1; + } + return b_cracked; + } + + static boolean execute(EditShape shape, double tolerance, + ProgressTracker progress_tracker) { + return Cracker.execute(shape, shape.getEnvelope2D(), tolerance, + progress_tracker); + } + + // Used for IsSimple. + static boolean needsCracking(boolean allowCoincident, EditShape shape, double tolerance, + NonSimpleResult result, ProgressTracker progress_tracker) { + if (!canBeCracked(shape)) + return false; + + Cracker cracker = new Cracker(progress_tracker); + cracker.m_shape = shape; + cracker.m_tolerance = tolerance; + cracker.m_bAllowCoincident = allowCoincident; + if (cracker.needsCrackingImpl_()) { + if (result != null) + result.Assign(cracker.m_non_simple_result); + return true; + } + + // Now swap the coordinates to catch horizontal cases. + Transformation2D transform = new Transformation2D(); + transform.setSwapCoordinates(); + shape.applyTransformation(transform); + + cracker = new Cracker(progress_tracker); + cracker.m_shape = shape; + cracker.m_tolerance = tolerance; + cracker.m_bAllowCoincident = allowCoincident; + boolean b_res = cracker.needsCrackingImpl_(); + + transform.setSwapCoordinates(); + shape.applyTransformation(transform);// restore shape + + if (b_res) { + if (result != null) + result.Assign(cracker.m_non_simple_result); + return true; + } + + return false; + } } diff --git a/src/main/java/com/esri/core/geometry/Cutter.java b/src/main/java/com/esri/core/geometry/Cutter.java index e4b106f4..5098fce9 100644 --- a/src/main/java/com/esri/core/geometry/Cutter.java +++ b/src/main/java/com/esri/core/geometry/Cutter.java @@ -32,1390 +32,1390 @@ import java.util.Arrays; class Cutter { - static class CompareVertices { - int m_orderIndex; - EditShape m_editShape; - - CompareVertices(int orderIndex, EditShape editShape) { - m_orderIndex = orderIndex; - m_editShape = editShape; - } - - int _compareVertices(int v1, int v2) { - Point2D pt1 = new Point2D(); - m_editShape.getXY(v1, pt1); - Point2D pt2 = new Point2D(); - m_editShape.getXY(v2, pt2); - int res = pt1.compare(pt2); - if (res != 0) - return res; - int z1 = m_editShape.getUserIndex(v1, m_orderIndex); - int z2 = m_editShape.getUserIndex(v2, m_orderIndex); - if (z1 < z2) - return -1; - if (z1 == z2) - return 0; - return 1; - } - } - - static class CutterVertexComparer extends - AttributeStreamOfInt32.IntComparator { - CompareVertices m_compareVertices; - - CutterVertexComparer(CompareVertices _compareVertices) { - m_compareVertices = _compareVertices; - } - - @Override - public int compare(int v1, int v2) { - return m_compareVertices._compareVertices(v1, v2); - } - } - - static class CutEvent { - int m_ivertexCuttee; - int m_ipartCuttee; - double m_scalarCuttee0; - double m_scalarCuttee1; - int m_count; - int m_ivertexCutter; - int m_ipartCutter; - double m_scalarCutter0; - double m_scalarCutter1; - - CutEvent(int ivertexCuttee, int ipartCuttee, double scalarCuttee0, - double scalarCuttee1, int count, int ivertexCutter, - int ipartCutter, double scalarCutter0, double scalarCutter1) { - m_ivertexCuttee = ivertexCuttee; - m_ipartCuttee = ipartCuttee; - m_scalarCuttee0 = scalarCuttee0; - m_scalarCuttee1 = scalarCuttee1; - m_count = count; - m_ivertexCutter = ivertexCutter; - m_ipartCutter = ipartCutter; - m_scalarCutter0 = scalarCutter0; - m_scalarCutter1 = scalarCutter1; - } - } - - static EditShape CutPolyline(boolean bConsiderTouch, Polyline cuttee, - Polyline cutter, double tolerance, - ArrayList cutPairs, - AttributeStreamOfInt32 segmentCounts, ProgressTracker progressTracker) { - if (cuttee.isEmpty()) { - OperatorCutLocal.CutPair cutPair; - cutPair = new OperatorCutLocal.CutPair(cuttee, - OperatorCutLocal.Side.Uncut, -1, -1, NumberUtils.NaN(), - OperatorCutLocal.Side.Uncut, -1, -1, NumberUtils.NaN(), -1, - -1, NumberUtils.NaN(), -1, -1, NumberUtils.NaN()); - cutPairs.add(cutPair); - return null; - } - - EditShape editShape = new EditShape(); - int cutteeHandle = editShape.addGeometry(cuttee); - int cutterHandle = editShape.addGeometry(cutter); - CrackAndCluster.execute(editShape, tolerance, progressTracker, true); - - int order = 0; - int orderIndex = editShape.createUserIndex(); - for (int igeometry = editShape.getFirstGeometry(); igeometry != -1; igeometry = editShape - .getNextGeometry(igeometry)) - for (int ipath = editShape.getFirstPath(igeometry); ipath != -1; ipath = editShape - .getNextPath(ipath)) - for (int ivertex = editShape.getFirstVertex(ipath), i = 0, n = editShape - .getPathSize(ipath); i < n; ivertex = editShape - .getNextVertex(ivertex), i++) - editShape.setUserIndex(ivertex, orderIndex, order++); - - ArrayList cutEvents = _getCutEvents(orderIndex, editShape); - _Cut(bConsiderTouch, false, cutEvents, editShape, cutPairs, - segmentCounts); - return editShape; - } - - private static ArrayList _getCutEvents(int orderIndex, - EditShape editShape) { - int pointCount = editShape.getTotalPointCount(); - - // Sort vertices lexicographically - // Firstly copy allvertices to an array. - AttributeStreamOfInt32 vertices = new AttributeStreamOfInt32(0); - - for (int igeometry = editShape.getFirstGeometry(); igeometry != -1; igeometry = editShape - .getNextGeometry(igeometry)) - for (int ipath = editShape.getFirstPath(igeometry); ipath != -1; ipath = editShape - .getNextPath(ipath)) - for (int ivertex = editShape.getFirstVertex(ipath), i = 0, n = editShape - .getPathSize(ipath); i < n; ivertex = editShape - .getNextVertex(ivertex), i++) - vertices.add(ivertex); - - // Sort - CompareVertices compareVertices = new CompareVertices(orderIndex, - editShape); - vertices.Sort(0, pointCount, new CutterVertexComparer(compareVertices)); - // SORTDYNAMICARRAYEX(vertices, index_type, 0, pointCount, - // CutterVertexComparer, compareVertices); - - // Find Cut Events - ArrayList cutEvents = new ArrayList(0); - ArrayList cutEventsTemp = new ArrayList(0); - - int eventIndex = editShape.createUserIndex(); - int eventIndexTemp = editShape.createUserIndex(); - - int cutteeHandle = editShape.getFirstGeometry(); - int cutterHandle = editShape.getNextGeometry(cutteeHandle); - - Point2D pointCuttee = new Point2D(); - Point2D pointCutter = new Point2D(); - - int ivertexCuttee = vertices.get(0); - ; - int ipartCuttee = editShape.getPathFromVertex(ivertexCuttee); - int igeometryCuttee = editShape.getGeometryFromPath(ipartCuttee); - editShape.getXY(ivertexCuttee, pointCuttee); - - int istart = 1; - int ivertex = 0; - while (istart < pointCount - 1) { - boolean bCutEvent = false; - for (int i = istart; i < pointCount; i++) { - if (i == ivertex) - continue; - - int ivertexCutter = vertices.get(i); - int ipartCutter = editShape.getPathFromVertex(ivertexCutter); - int igeometryCutter = editShape - .getGeometryFromPath(ipartCutter); - editShape.getXY(ivertexCutter, pointCutter); - - if (pointCuttee.isEqual(pointCutter)) { - boolean bCondition = igeometryCuttee == cutteeHandle - && igeometryCutter == cutterHandle; - - if (bCondition) - bCutEvent = _cutteeCutterEvents(eventIndex, - eventIndexTemp, editShape, cutEvents, - cutEventsTemp, ipartCuttee, ivertexCuttee, - ipartCutter, ivertexCutter); - } else - break; - } - - if (bCutEvent || ivertex == istart - 1) { - if (bCutEvent && (ivertex == istart - 1)) - istart--; - - if (++ivertex == pointCount) - break; - - ivertexCuttee = vertices.get(ivertex); - ipartCuttee = editShape.getPathFromVertex(ivertexCuttee); - igeometryCuttee = editShape.getGeometryFromPath(ipartCuttee); - editShape.getXY(ivertexCuttee, pointCuttee); - } - - if (!bCutEvent) - istart = ivertex + 1; - } - - ArrayList cutEventsSorted = new ArrayList(0); - - // Sort CutEvents - int icutEvent; - int icutEventTemp; - for (int igeometry = editShape.getFirstGeometry(); igeometry != -1; igeometry = editShape.getNextGeometry(igeometry)) { - for (int ipath = editShape.getFirstPath(igeometry); ipath != -1; ipath = editShape.getNextPath(ipath)) { - for (int iv = editShape.getFirstVertex(ipath), i = 0, n = editShape.getPathSize(ipath); i < n; iv = editShape.getNextVertex(iv), i++) { - icutEventTemp = editShape.getUserIndex(iv, eventIndexTemp); - if (icutEventTemp >= 0) { - // _ASSERT(cutEventsTemp.get(icutEventTemp).m_ivertexCuttee - // == iv); - while (icutEventTemp < cutEventsTemp.size() && cutEventsTemp.get(icutEventTemp).m_ivertexCuttee == iv) - cutEventsSorted.add(cutEventsTemp.get(icutEventTemp++)); - } - - icutEvent = editShape.getUserIndex(iv, eventIndex); - if (icutEvent >= 0) { - // _ASSERT(cutEvents->Get(icutEvent)->m_ivertexCuttee == - // iv); - while (icutEvent < cutEvents.size() && cutEvents.get(icutEvent).m_ivertexCuttee == iv) - cutEventsSorted.add(cutEvents.get(icutEvent++)); - } - } - } - } - - // _ASSERT(cutEvents->Size() + cutEventsTemp->Size() == - // cutEventsSorted->Size()); - editShape.removeUserIndex(eventIndex); - editShape.removeUserIndex(eventIndexTemp); - return cutEventsSorted; - } - - static boolean _cutteeCutterEvents(int eventIndex, int eventIndexTemp, - EditShape editShape, ArrayList cutEvents, - ArrayList cutEventsTemp, int ipartCuttee, - int ivertexCuttee, int ipartCutter, int ivertexCutter) { - int ilastVertexCuttee = editShape.getLastVertex(ipartCuttee); - int ilastVertexCutter = editShape.getLastVertex(ipartCutter); - int ifirstVertexCuttee = editShape.getFirstVertex(ipartCuttee); - int ifirstVertexCutter = editShape.getFirstVertex(ipartCutter); - int ivertexCutteePrev = editShape.getPrevVertex(ivertexCuttee); - int ivertexCutterPrev = editShape.getPrevVertex(ivertexCutter); - - boolean bEndEnd = false; - boolean bEndStart = false; - boolean bStartEnd = false; - boolean bStartStart = false; - - if (ivertexCuttee != ifirstVertexCuttee) { - if (ivertexCutter != ifirstVertexCutter) - bEndEnd = _cutteeEndCutterEndEvent(eventIndex, editShape, - cutEvents, ipartCuttee, ivertexCutteePrev, ipartCutter, - ivertexCutterPrev); - - if (ivertexCutter != ilastVertexCutter) - bEndStart = _cutteeEndCutterStartEvent(eventIndex, editShape, - cutEvents, ipartCuttee, ivertexCutteePrev, ipartCutter, - ivertexCutter); - } - - if (ivertexCuttee != ilastVertexCuttee) { - if (ivertexCutter != ifirstVertexCutter) - bStartEnd = _cutteeStartCutterEndEvent(eventIndexTemp, - editShape, cutEventsTemp, ipartCuttee, ivertexCuttee, - ipartCutter, ivertexCutterPrev, ifirstVertexCuttee); - - if (ivertexCutter != ilastVertexCutter) - bStartStart = _cutteeStartCutterStartEvent(eventIndexTemp, - editShape, cutEventsTemp, ipartCuttee, ivertexCuttee, - ipartCutter, ivertexCutter, ifirstVertexCuttee); - } - - if (bEndEnd && bEndStart && bStartEnd) { - int iendstart = cutEvents.size() - 1; - int istartend = (bStartStart ? cutEventsTemp.size() - 2 - : cutEventsTemp.size() - 1); - - if (cutEventsTemp.get(istartend).m_count == 2) { - // Replace bEndEnd with bEndStart, and remove duplicate - // bEndStart (get rid of bEndEnd) - cutEvents.set(iendstart - 1, cutEvents.get(iendstart)); - cutEvents.remove(cutEvents.size() - 1); - } - } else if (bEndEnd && bEndStart && bStartStart) { - int istartstart = cutEventsTemp.size() - 1; - - if (cutEventsTemp.get(istartstart).m_count == 2) { - // Remove bEndStart - CutEvent lastEvent = cutEvents.get(cutEvents.size() - 1); - cutEvents.remove(cutEvents.get(cutEvents.size() - 1)); - int icutEvent = editShape.getUserIndex( - lastEvent.m_ivertexCuttee, eventIndex); - if (icutEvent == cutEvents.size()) - editShape.setUserIndex(lastEvent.m_ivertexCuttee, - eventIndex, -1); - } - } - - return bEndEnd || bEndStart || bStartEnd || bStartStart; - } - - private static boolean _cutteeEndCutterEndEvent(int eventIndex, - EditShape editShape, ArrayList cutEvents, - int ipartCuttee, int ivertexCuttee, int ipartCutter, - int ivertexCutter) { - Segment segmentCuttee; - Segment segmentCutter; - Line lineCuttee = new Line(); - Line lineCutter = new Line(); - double[] scalarsCuttee = new double[2]; - double[] scalarsCutter = new double[2]; - - CutEvent cutEvent; - - segmentCuttee = editShape.getSegment(ivertexCuttee); - if (segmentCuttee == null) { - editShape.queryLineConnector(ivertexCuttee, lineCuttee); - segmentCuttee = lineCuttee; - } - - segmentCutter = editShape.getSegment(ivertexCutter); - if (segmentCutter == null) { - editShape.queryLineConnector(ivertexCutter, lineCutter); - segmentCutter = lineCutter; - } - - int count = segmentCuttee.intersect(segmentCutter, null, scalarsCuttee, - scalarsCutter, 0.0); - // _ASSERT(count > 0); - int icutEvent; - - // If count == 2 (i.e. when they overlap), this this event would have - // been discovered by _CutteeStartCutterStartEvent at the previous index - if (count < 2) { - cutEvent = new CutEvent(ivertexCuttee, ipartCuttee, - scalarsCuttee[0], NumberUtils.NaN(), count, ivertexCutter, - ipartCutter, scalarsCutter[0], NumberUtils.NaN()); - cutEvents.add(cutEvent); - icutEvent = editShape.getUserIndex(ivertexCuttee, eventIndex); - - if (icutEvent < 0) - editShape.setUserIndex(ivertexCuttee, eventIndex, - cutEvents.size() - 1); - } - - return true; - } - - private static boolean _cutteeEndCutterStartEvent(int eventIndex, - EditShape editShape, ArrayList cutEvents, - int ipartCuttee, int ivertexCuttee, int ipartCutter, - int ivertexCutter) { - Segment segmentCuttee; - Segment segmentCutter; - Line lineCuttee = new Line(); - Line lineCutter = new Line(); - double[] scalarsCuttee = new double[2]; - double[] scalarsCutter = new double[2]; - - CutEvent cutEvent; - - segmentCuttee = editShape.getSegment(ivertexCuttee); - if (segmentCuttee == null) { - editShape.queryLineConnector(ivertexCuttee, lineCuttee); - segmentCuttee = lineCuttee; - } - - segmentCutter = editShape.getSegment(ivertexCutter); - if (segmentCutter == null) { - editShape.queryLineConnector(ivertexCutter, lineCutter); - segmentCutter = lineCutter; - } - - int count = segmentCuttee.intersect(segmentCutter, null, scalarsCuttee, - scalarsCutter, 0.0); - // _ASSERT(count > 0); - int icutEvent; - - // If count == 2 (i.e. when they overlap), this this event would have - // been discovered by _CutteeStartCutterEndEvent at the previous index - if (count < 2) { - cutEvent = new CutEvent(ivertexCuttee, ipartCuttee, - scalarsCuttee[0], NumberUtils.NaN(), count, ivertexCutter, - ipartCutter, scalarsCutter[0], NumberUtils.NaN()); - cutEvents.add(cutEvent); - icutEvent = editShape.getUserIndex(ivertexCuttee, eventIndex); - - if (icutEvent < 0) - editShape.setUserIndex(ivertexCuttee, eventIndex, - cutEvents.size() - 1); - - return true; - } - - return false; - } - - private static boolean _cutteeStartCutterEndEvent(int eventIndex, - EditShape editShape, ArrayList cutEvents, - int ipartCuttee, int ivertexCuttee, int ipartCutter, - int ivertexCutter, int ifirstVertexCuttee) { - Segment segmentCuttee; - Segment segmentCutter; - Line lineCuttee = new Line(); - Line lineCutter = new Line(); - double[] scalarsCuttee = new double[2]; - double[] scalarsCutter = new double[2]; - - CutEvent cutEvent; - - segmentCuttee = editShape.getSegment(ivertexCuttee); - if (segmentCuttee == null) { - editShape.queryLineConnector(ivertexCuttee, lineCuttee); - segmentCuttee = lineCuttee; - } - - segmentCutter = editShape.getSegment(ivertexCutter); - if (segmentCutter == null) { - editShape.queryLineConnector(ivertexCutter, lineCutter); - segmentCutter = lineCutter; - } - - int count = segmentCuttee.intersect(segmentCutter, null, scalarsCuttee, - scalarsCutter, 0.0); - // _ASSERT(count > 0); - int icutEvent; - - if (count == 2) { - cutEvent = new CutEvent(ivertexCuttee, ipartCuttee, - scalarsCuttee[0], scalarsCuttee[1], count, ivertexCutter, - ipartCutter, scalarsCutter[0], scalarsCutter[1]); - cutEvents.add(cutEvent); - icutEvent = editShape.getUserIndex(ivertexCuttee, eventIndex); - - if (icutEvent < 0) - editShape.setUserIndex(ivertexCuttee, eventIndex, - cutEvents.size() - 1); - - return true; - } else { - boolean bCutEvent = false; - - if (ivertexCuttee == ifirstVertexCuttee) { - cutEvent = new CutEvent(ivertexCuttee, ipartCuttee, - scalarsCuttee[0], NumberUtils.NaN(), count, - ivertexCutter, ipartCutter, scalarsCutter[0], - NumberUtils.NaN()); - cutEvents.add(cutEvent); - icutEvent = editShape.getUserIndex(ivertexCuttee, eventIndex); - - if (icutEvent < 0) - editShape.setUserIndex(ivertexCuttee, eventIndex, - cutEvents.size() - 1); - - bCutEvent = true; - } - - return bCutEvent; - } - - } - - private static boolean _cutteeStartCutterStartEvent(int eventIndex, - EditShape editShape, ArrayList cutEvents, - int ipartCuttee, int ivertexCuttee, int ipartCutter, - int ivertexCutter, int ifirstVertexCuttee) { - Segment segmentCuttee; - Segment segmentCutter; - Line lineCuttee = new Line(); - Line lineCutter = new Line(); - double[] scalarsCuttee = new double[2]; - double[] scalarsCutter = new double[2]; - - CutEvent cutEvent; - - segmentCuttee = editShape.getSegment(ivertexCuttee); - if (segmentCuttee == null) { - editShape.queryLineConnector(ivertexCuttee, lineCuttee); - segmentCuttee = lineCuttee; - } - - segmentCutter = editShape.getSegment(ivertexCutter); - if (segmentCutter == null) { - editShape.queryLineConnector(ivertexCutter, lineCutter); - segmentCutter = lineCutter; - } - - int count = segmentCuttee.intersect(segmentCutter, null, scalarsCuttee, - scalarsCutter, 0.0); - // _ASSERT(count > 0); - int icutEvent; - - if (count == 2) { - cutEvent = new CutEvent(ivertexCuttee, ipartCuttee, - scalarsCuttee[0], scalarsCuttee[1], count, ivertexCutter, - ipartCutter, scalarsCutter[0], scalarsCutter[1]); - cutEvents.add(cutEvent); - icutEvent = editShape.getUserIndex(ivertexCuttee, eventIndex); - - if (icutEvent < 0) - editShape.setUserIndex(ivertexCuttee, eventIndex, - cutEvents.size() - 1); - - return true; - } else { - boolean bCutEvent = false; - - if (ivertexCuttee == ifirstVertexCuttee) { - cutEvent = new CutEvent(ivertexCuttee, ipartCuttee, - scalarsCuttee[0], NumberUtils.NaN(), count, - ivertexCutter, ipartCutter, scalarsCutter[0], - NumberUtils.NaN()); - cutEvents.add(cutEvent); - icutEvent = editShape.getUserIndex(ivertexCuttee, eventIndex); - - if (icutEvent < 0) - editShape.setUserIndex(ivertexCuttee, eventIndex, - cutEvents.size() - 1); - - bCutEvent = true; - } - - return bCutEvent; - } - - } - - static void _Cut(boolean bConsiderTouch, boolean bLocalCutsOnly, - ArrayList cutEvents, EditShape shape, - ArrayList cutPairs, - AttributeStreamOfInt32 segmentCounts) { - OperatorCutLocal.CutPair cutPair; - - Point2D[] tangents = new Point2D[4]; - tangents[0] = new Point2D(); - tangents[1] = new Point2D(); - tangents[2] = new Point2D(); - tangents[3] = new Point2D(); - Point2D tangent0 = new Point2D(); - Point2D tangent1 = new Point2D(); - Point2D tangent2 = new Point2D(); - Point2D tangent3 = new Point2D(); - - SegmentBuffer segmentBufferCuttee = null; - if (cutPairs != null) { - segmentBufferCuttee = new SegmentBuffer(); - segmentBufferCuttee.createLine(); - } - - Segment segmentCuttee = null; - int icutEvent = 0; - MultiPath multipath = null; - - Line lineCuttee = new Line(); - Line lineCutter = new Line(); - - int polyline = shape.getFirstGeometry(); - for (int ipath = shape.getFirstPath(polyline); ipath != -1; ipath = shape - .getNextPath(ipath)) { - int cut; - int cutPrev = OperatorCutLocal.Side.Uncut; - int ipartCuttee = -1; - int ivertexCuttee = -1; - double scalarCuttee = NumberUtils.NaN(); - int ipartCutteePrev = -1; - int ivertexCutteePrev = -1; - double scalarCutteePrev = NumberUtils.NaN(); - int ipartCutter = -1; - int ivertexCutter = -1; - double scalarCutter = NumberUtils.NaN(); - int ipartCutterPrev = -1; - int ivertexCutterPrev = -1; - double scalarCutterPrev = NumberUtils.NaN(); - boolean bNoCutYet = true; // Indicates whether a cut as occured for - // the current part - boolean bCoincidentNotAdded = false; // Indicates whether the - // current coincident - // multipath has been added - // to cutPairs - boolean bCurrentMultiPathNotAdded = true; // Indicates whether there - // is a multipath not - // yet added to cutPairs - // (left, right, or - // undefined) - boolean bStartNewPath = true; - boolean bCreateNewMultiPath = true; - int segmentCount = 0; - - ipartCutteePrev = ipath; - scalarCutteePrev = 0.0; - - for (int ivertex = shape.getFirstVertex(ipath), n = shape - .getPathSize(ipath), i = 0; i < n; ivertex = shape - .getNextVertex(ivertex), i++) { - segmentCuttee = shape.getSegment(ivertex); - if (segmentCuttee == null) { - if (!shape.queryLineConnector(ivertex, lineCuttee)) - continue; - segmentCuttee = lineCuttee; - } - - if (ivertexCutteePrev == -1) - ivertexCutteePrev = ivertex; - - double lastScalarCuttee = 0.0; // last scalar along the current - // segment - - while (icutEvent < cutEvents.size() - && ivertex == cutEvents.get(icutEvent).m_ivertexCuttee) { - ipartCuttee = cutEvents.get(icutEvent).m_ipartCuttee; - ivertexCuttee = cutEvents.get(icutEvent).m_ivertexCuttee; - scalarCuttee = cutEvents.get(icutEvent).m_scalarCuttee0; - ipartCutter = cutEvents.get(icutEvent).m_ipartCutter; - ivertexCutter = cutEvents.get(icutEvent).m_ivertexCutter; - scalarCutter = cutEvents.get(icutEvent).m_scalarCutter0; - - if (cutEvents.get(icutEvent).m_count == 2) { - // We have an overlap - - if (!bCoincidentNotAdded) { - ipartCutteePrev = ipartCuttee; - ivertexCutteePrev = ivertexCuttee; - scalarCutteePrev = scalarCuttee; - ipartCutterPrev = ipartCutter; - ivertexCutterPrev = ivertexCutter; - scalarCutterPrev = scalarCutter; - cutPrev = OperatorCutLocal.Side.Coincident; - - // Create new multipath - if (cutPairs != null) - multipath = new Polyline(); - else - segmentCount = 0; - - bCreateNewMultiPath = false; - bStartNewPath = true; - } - - scalarCuttee = cutEvents.get(icutEvent).m_scalarCuttee1; - scalarCutter = cutEvents.get(icutEvent).m_scalarCutter1; - - if (cutPairs != null) { - segmentCuttee.cut(lastScalarCuttee, - cutEvents.get(icutEvent).m_scalarCuttee1, - segmentBufferCuttee); - multipath.addSegment(segmentBufferCuttee.get(), - bStartNewPath); - } else - segmentCount++; - - lastScalarCuttee = scalarCuttee; - - bCoincidentNotAdded = true; - bNoCutYet = false; - bStartNewPath = false; - - if (icutEvent + 1 == cutEvents.size() - || cutEvents.get(icutEvent + 1).m_count != 2 - || cutEvents.get(icutEvent + 1).m_ivertexCuttee == ivertexCuttee - && cutEvents.get(icutEvent + 1).m_scalarCuttee0 != lastScalarCuttee) { - if (cutPairs != null) { - cutPair = new OperatorCutLocal.CutPair( - (Geometry) multipath, - OperatorCutLocal.Side.Coincident, - ipartCuttee, ivertexCuttee, - scalarCuttee, cutPrev, ipartCutteePrev, - ivertexCutteePrev, scalarCutteePrev, - ipartCutter, ivertexCutter, - scalarCutter, ipartCutterPrev, - ivertexCutterPrev, scalarCutterPrev); - cutPairs.add(cutPair); - } else { - segmentCounts.add(segmentCount); - } - - ipartCutteePrev = ipartCuttee; - ivertexCutteePrev = ivertexCuttee; - scalarCutteePrev = scalarCuttee; - ipartCutterPrev = ipartCutter; - ivertexCutterPrev = ivertexCutter; - scalarCutterPrev = scalarCutter; - cutPrev = OperatorCutLocal.Side.Coincident; - - bNoCutYet = false; - bCoincidentNotAdded = false; - bCreateNewMultiPath = true; - bStartNewPath = true; - } - - icutEvent++; - continue; - } - - int ivertexCutteePlus = shape.getNextVertex(ivertexCuttee); - int ivertexCutterPlus = shape.getNextVertex(ivertexCutter); - int ivertexCutterMinus = shape.getPrevVertex(ivertexCutter); - - if (icutEvent < cutEvents.size() - 1 - && cutEvents.get(icutEvent + 1).m_ivertexCuttee == ivertexCutteePlus - && cutEvents.get(icutEvent + 1).m_ivertexCutter == ivertexCutter - && cutEvents.get(icutEvent + 1).m_count == 2) { - if (scalarCuttee != lastScalarCuttee) { - if (bCreateNewMultiPath) { - if (cutPairs != null) - multipath = new Polyline(); - else - segmentCount = 0; - } - - if (icutEvent > 0 - && cutEvents.get(icutEvent - 1).m_ipartCuttee == ipartCuttee) { - if (cutPrev == OperatorCutLocal.Side.Right) - cut = OperatorCutLocal.Side.Left; - else if (cutPrev == OperatorCutLocal.Side.Left) - cut = OperatorCutLocal.Side.Right; - else - cut = OperatorCutLocal.Side.Undefined; - } else - cut = OperatorCutLocal.Side.Undefined; - - if (cutPairs != null) { - segmentCuttee.cut(lastScalarCuttee, - scalarCuttee, segmentBufferCuttee); - multipath.addSegment(segmentBufferCuttee.get(), - bStartNewPath); - cutPair = new OperatorCutLocal.CutPair( - multipath, cut, ipartCuttee, - ivertexCuttee, scalarCuttee, cutPrev, - ipartCutteePrev, ivertexCutteePrev, - scalarCutteePrev, ipartCutter, - ivertexCutter, scalarCutter, - ipartCutterPrev, ivertexCutterPrev, - scalarCutterPrev); - cutPairs.add(cutPair); - } else { - segmentCount++; - segmentCounts.add(segmentCount); - } - - lastScalarCuttee = scalarCuttee; - - ipartCutteePrev = ipartCuttee; - ivertexCutteePrev = ivertexCuttee; - scalarCutteePrev = scalarCuttee; - ipartCutterPrev = ipartCutter; - ivertexCutterPrev = ivertexCutter; - scalarCutterPrev = scalarCutter; - cutPrev = cut; - - bCurrentMultiPathNotAdded = false; - bNoCutYet = false; - bCreateNewMultiPath = true; - bStartNewPath = true; - } - - icutEvent++; - continue; - } - - boolean bContinue = _cutterTangents(bConsiderTouch, shape, - cutEvents, icutEvent, tangent0, tangent1); - if (bContinue) { - icutEvent++; - continue; - } - - _cutteeTangents(shape, cutEvents, icutEvent, ipath, - ivertex, tangent2, tangent3); - - boolean bCut = false; - boolean bTouch = false; - boolean bCutRight = true; - - if (!tangent0.isEqual(tangent2) - && !tangent1.isEqual(tangent2) - && !tangent0.isEqual(tangent3) - && !tangent1.isEqual(tangent3)) { - tangents[0].setCoords(tangent0); - tangents[1].setCoords(tangent1); - tangents[2].setCoords(tangent2); - tangents[3].setCoords(tangent3); - - Arrays.sort(tangents, new Point2D.CompareVectors()); - // SORTARRAY(tangents, Point2D, - // Point2D::_CompareVectors); - - Point2D value0 = (Point2D) tangents[0]; - Point2D value1 = (Point2D) tangents[1]; - Point2D value2 = (Point2D) tangents[2]; - Point2D value3 = (Point2D) tangents[3]; - - if (value0.isEqual(tangent0)) { - if (value1.isEqual(tangent1)) { - if (!bConsiderTouch) - bCut = false; - else { - bCut = true; - bTouch = true; - bCutRight = false; - } - } else if (value3.isEqual(tangent1)) { - if (!bConsiderTouch) - bCut = false; - else { - bCut = true; - bTouch = true; - bCutRight = true; - } - } else { - bCut = true; - bCutRight = value1.isEqual(tangent2); - } - } else if (value1.isEqual(tangent0)) { - if (value2.isEqual(tangent1)) { - if (!bConsiderTouch) - bCut = false; - else { - bCut = true; - bTouch = true; - bCutRight = false; - } - } else if (value0.isEqual(tangent1)) { - if (!bConsiderTouch) - bCut = false; - else { - bCut = true; - bTouch = true; - bCutRight = true; - } - } else { - bCut = true; - bCutRight = value2.isEqual(tangent2); - } - } else if (value2.isEqual(tangent0)) { - if (value3.isEqual(tangent1)) { - if (!bConsiderTouch) - bCut = false; - else { - bCut = true; - bTouch = true; - bCutRight = false; - } - } else if (value1.isEqual(tangent1)) { - if (!bConsiderTouch) - bCut = false; - else { - bCut = true; - bTouch = true; - bCutRight = true; - } - } else { - bCut = true; - bCutRight = value3.isEqual(tangent2); - } - } else { - if (value0.isEqual(tangent1)) { - if (!bConsiderTouch) - bCut = false; - else { - bCut = true; - bTouch = true; - bCutRight = false; - } - } else if (value2.isEqual(tangent1)) { - if (!bConsiderTouch) - bCut = false; - else { - bCut = true; - bTouch = true; - bCutRight = true; - } - } else { - bCut = true; - bCutRight = value0.isEqual(tangent2); - } - } - } - - if (bCut) { - boolean bIsFirstSegmentInPath = (ivertex == ivertexCuttee); - - if (scalarCuttee != lastScalarCuttee - || bIsFirstSegmentInPath - && lastScalarCuttee == 0.0) { - if (bCreateNewMultiPath) { - if (cutPairs != null) - multipath = new Polyline(); - else - segmentCount = 0; - } - - if (cutPairs != null) { - segmentCuttee.cut(lastScalarCuttee, - scalarCuttee, segmentBufferCuttee); - multipath.addSegment(segmentBufferCuttee.get(), - bStartNewPath); - } else - segmentCount++; - } - - if (bCutRight) { - if (cutPrev != OperatorCutLocal.Side.Right - || bLocalCutsOnly) { - if (scalarCuttee != lastScalarCuttee - || bIsFirstSegmentInPath - && lastScalarCuttee == 0.0 - || bLocalCutsOnly) { - if (cutPairs != null) { - cutPair = new OperatorCutLocal.CutPair( - multipath, - OperatorCutLocal.Side.Right, - ipartCuttee, ivertexCuttee, - scalarCuttee, cutPrev, - ipartCutteePrev, - ivertexCutteePrev, - scalarCutteePrev, ipartCutter, - ivertexCutter, scalarCutter, - ipartCutterPrev, - ivertexCutterPrev, - scalarCutterPrev); - cutPairs.add(cutPair); - } else { - segmentCounts.add(segmentCount); - } - } - - if (!bTouch) - cutPrev = OperatorCutLocal.Side.Right; - else if (icutEvent == cutEvents.size() - 2 - || cutEvents.get(icutEvent + 2).m_ipartCuttee != ipartCuttee) - cutPrev = OperatorCutLocal.Side.Left; - } else { - if (scalarCuttee != lastScalarCuttee - || bIsFirstSegmentInPath - && lastScalarCuttee == 0.0 - || bLocalCutsOnly) { - if (cutPairs != null) { - cutPair = new OperatorCutLocal.CutPair( - multipath, - OperatorCutLocal.Side.Undefined, - ipartCuttee, ivertexCuttee, - scalarCuttee, cutPrev, - ipartCutteePrev, - ivertexCutteePrev, - scalarCutteePrev, ipartCutter, - ivertexCutter, scalarCutter, - ipartCutterPrev, - ivertexCutterPrev, - scalarCutterPrev); - cutPairs.add(cutPair); - } else { - segmentCounts.add(segmentCount); - } - } - - cutPrev = OperatorCutLocal.Side.Right; - } - } else { - if (cutPrev != OperatorCutLocal.Side.Left - || bLocalCutsOnly) { - if (scalarCuttee != lastScalarCuttee - || bIsFirstSegmentInPath - && lastScalarCuttee == 0.0 - || bLocalCutsOnly) { - if (cutPairs != null) { - cutPair = new OperatorCutLocal.CutPair( - multipath, - OperatorCutLocal.Side.Left, - ipartCuttee, ivertexCuttee, - scalarCuttee, cutPrev, - ipartCutteePrev, - ivertexCutteePrev, - scalarCutteePrev, ipartCutter, - ivertexCutter, scalarCutter, - ipartCutterPrev, - ivertexCutterPrev, - scalarCutterPrev); - cutPairs.add(cutPair); - } else { - segmentCounts.add(segmentCount); - } - } - - if (!bTouch) - cutPrev = OperatorCutLocal.Side.Left; - else if (icutEvent == cutEvents.size() - 2 - || cutEvents.get(icutEvent + 2).m_ipartCuttee != ipartCuttee) - cutPrev = OperatorCutLocal.Side.Right; - } else { - if (scalarCuttee != lastScalarCuttee - || bIsFirstSegmentInPath - && lastScalarCuttee == 0.0 - || bLocalCutsOnly) { - if (cutPairs != null) { - cutPair = new OperatorCutLocal.CutPair( - multipath, - OperatorCutLocal.Side.Undefined, - ipartCuttee, ivertexCuttee, - scalarCuttee, cutPrev, - ipartCutteePrev, - ivertexCutteePrev, - scalarCutteePrev, ipartCutter, - ivertexCutter, scalarCutter, - ipartCutterPrev, - ivertexCutterPrev, - scalarCutterPrev); - cutPairs.add(cutPair); - } else { - segmentCounts.add(segmentCount); - } - } - - cutPrev = OperatorCutLocal.Side.Left; - } - } - - if (scalarCuttee != lastScalarCuttee - || bIsFirstSegmentInPath - && lastScalarCuttee == 0.0 || bLocalCutsOnly) { - lastScalarCuttee = scalarCuttee; - - ipartCutteePrev = ipartCuttee; - ivertexCutteePrev = ivertexCuttee; - scalarCutteePrev = scalarCuttee; - ipartCutterPrev = ipartCutter; - ivertexCutterPrev = ivertexCutter; - scalarCutterPrev = scalarCutter; - - bCurrentMultiPathNotAdded = false; - bNoCutYet = false; - bCreateNewMultiPath = true; - bStartNewPath = true; - } - } - - icutEvent++; - } - - if (lastScalarCuttee != 1.0) { - if (bCreateNewMultiPath) { - if (cutPairs != null) - multipath = new Polyline(); - else - segmentCount = 0; - } - - if (cutPairs != null) { - segmentCuttee.cut(lastScalarCuttee, 1.0, - segmentBufferCuttee); - multipath.addSegment(segmentBufferCuttee.get(), - bStartNewPath); - } else - segmentCount++; - - bCreateNewMultiPath = false; - bStartNewPath = false; - bCurrentMultiPathNotAdded = true; - } - } - - if (bCurrentMultiPathNotAdded) { - scalarCuttee = 1.0; - ivertexCuttee = shape.getLastVertex(ipath); - ivertexCuttee = shape.getPrevVertex(ivertexCuttee); - - ipartCutter = -1; - ivertexCutter = -1; - scalarCutter = NumberUtils.NaN(); - - if (bNoCutYet) { - if (cutPairs != null) { - cutPair = new OperatorCutLocal.CutPair(multipath, - OperatorCutLocal.Side.Uncut, ipartCuttee, - ivertexCuttee, scalarCuttee, cutPrev, - ipartCutteePrev, ivertexCutteePrev, - scalarCutteePrev, ipartCutter, ivertexCutter, - scalarCutter, ipartCutterPrev, - ivertexCutterPrev, scalarCutterPrev); - cutPairs.add(cutPair); - } else { - segmentCounts.add(segmentCount); - } - } else { - if (cutPrev == OperatorCutLocal.Side.Right) - cut = OperatorCutLocal.Side.Left; - else if (cutPrev == OperatorCutLocal.Side.Left) - cut = OperatorCutLocal.Side.Right; - else - cut = OperatorCutLocal.Side.Undefined; - - if (cutPairs != null) { - cutPair = new OperatorCutLocal.CutPair(multipath, cut, - ipartCuttee, ivertexCuttee, scalarCuttee, - cutPrev, ipartCutteePrev, ivertexCutteePrev, - scalarCutteePrev, ipartCutter, ivertexCutter, - scalarCutter, ipartCutterPrev, - ivertexCutterPrev, scalarCutterPrev); - cutPairs.add(cutPair); - } else { - segmentCounts.add(segmentCount); - } - } - } - } - } - - static boolean _cutterTangents(boolean bConsiderTouch, EditShape shape, - ArrayList cutEvents, int icutEvent, Point2D tangent0, - Point2D tangent1) { - double scalarCutter = cutEvents.get(icutEvent).m_scalarCutter0; - - if (scalarCutter == 1.0) - return _cutterEndTangents(bConsiderTouch, shape, cutEvents, - icutEvent, tangent0, tangent1); - - if (scalarCutter == 0.0) - return _cutterStartTangents(bConsiderTouch, shape, cutEvents, - icutEvent, tangent0, tangent1); - - throw GeometryException.GeometryInternalError(); - } - - static boolean _cutterEndTangents(boolean bConsiderTouch, EditShape shape, - ArrayList cutEvents, int icutEvent, Point2D tangent0, - Point2D tangent1) { - Line lineCutter = new Line(); - Segment segmentCutter; - - int ivertexCuttee = cutEvents.get(icutEvent).m_ivertexCuttee; - int ipartCutter = cutEvents.get(icutEvent).m_ipartCutter; - int ivertexCutter = cutEvents.get(icutEvent).m_ivertexCutter; - - int ivertexCutteePrev = -1; - int ipartCutterPrev = -1; - int ivertexCutterPrev = -1; - int countPrev = -1; - - if (!bConsiderTouch && icutEvent > 0) { - CutEvent cutEvent = cutEvents.get(icutEvent - 1); - ivertexCutteePrev = cutEvent.m_ivertexCuttee; - ipartCutterPrev = cutEvent.m_ipartCutter; - ivertexCutterPrev = cutEvent.m_ivertexCutter; - countPrev = cutEvent.m_count; - } - - int ivertexCutteeNext = -1; - int ipartCutterNext = -1; - int ivertexCutterNext = -1; - int countNext = -1; - - if (icutEvent < cutEvents.size() - 1) { - CutEvent cutEvent = cutEvents.get(icutEvent + 1); - ivertexCutteeNext = cutEvent.m_ivertexCuttee; - ipartCutterNext = cutEvent.m_ipartCutter; - ivertexCutterNext = cutEvent.m_ivertexCutter; - countNext = cutEvent.m_count; - } - - int ivertexCutteePlus = shape.getNextVertex(ivertexCuttee); - int ivertexCutterPlus = shape.getNextVertex(ivertexCutter); - - if (!bConsiderTouch) { - if ((icutEvent > 0 && ivertexCutteePrev == ivertexCuttee - && ipartCutterPrev == ipartCutter - && ivertexCutterPrev == ivertexCutterPlus && countPrev == 2) - || (icutEvent < cutEvents.size() - 1 - && ivertexCutteeNext == ivertexCutteePlus - && ipartCutterNext == ipartCutter - && ivertexCutterNext == ivertexCutterPlus && countNext == 2)) { - segmentCutter = shape.getSegment(ivertexCutter); - if (segmentCutter == null) { - shape.queryLineConnector(ivertexCutter, lineCutter); - segmentCutter = lineCutter; - } - - tangent1.setCoords(segmentCutter._getTangent(1.0)); - tangent0.negate(tangent1); - tangent1.normalize(); - tangent0.normalize(); - - return false; - } - - if (icutEvent < cutEvents.size() - 1 - && ivertexCutteeNext == ivertexCuttee - && ipartCutterNext == ipartCutter - && ivertexCutterNext == ivertexCutterPlus) { - segmentCutter = shape.getSegment(ivertexCutter); - if (segmentCutter == null) { - shape.queryLineConnector(ivertexCutter, lineCutter); - segmentCutter = lineCutter; - } - - tangent0.setCoords(segmentCutter._getTangent(1.0)); - - segmentCutter = shape.getSegment(ivertexCutterPlus); - if (segmentCutter == null) { - shape.queryLineConnector(ivertexCutterPlus, lineCutter); - segmentCutter = lineCutter; - } - - tangent1.setCoords(segmentCutter._getTangent(0.0)); - tangent0.negate(); - tangent1.normalize(); - tangent0.normalize(); - - return false; - } - - return true; - } - - if (icutEvent == cutEvents.size() - 1 - || ivertexCutteeNext != ivertexCuttee - || ipartCutterNext != ipartCutter - || ivertexCutterNext != ivertexCutterPlus || countNext == 2) { - segmentCutter = shape.getSegment(ivertexCutter); - if (segmentCutter == null) { - shape.queryLineConnector(ivertexCutter, lineCutter); - segmentCutter = lineCutter; - } - - tangent1.setCoords(segmentCutter._getTangent(1.0)); - tangent0.negate(tangent1); - tangent1.normalize(); - tangent0.normalize(); - - return false; - } - - segmentCutter = shape.getSegment(ivertexCutter); - if (segmentCutter == null) { - shape.queryLineConnector(ivertexCutter, lineCutter); - segmentCutter = lineCutter; - } - - tangent0.setCoords(segmentCutter._getTangent(1.0)); - - segmentCutter = shape.getSegment(ivertexCutterPlus); - if (segmentCutter == null) { - shape.queryLineConnector(ivertexCutterPlus, lineCutter); - segmentCutter = lineCutter; - } - - tangent1.setCoords(segmentCutter._getTangent(0.0)); - tangent0.negate(); - tangent1.normalize(); - tangent0.normalize(); - - return false; - } - - static boolean _cutterStartTangents(boolean bConsiderTouch, - EditShape shape, ArrayList cutEvents, int icutEvent, - Point2D tangent0, Point2D tangent1) { - Line lineCutter = new Line(); - Segment segmentCutter; - - int ivertexCuttee = cutEvents.get(icutEvent).m_ivertexCuttee; - int ipartCutter = cutEvents.get(icutEvent).m_ipartCutter; - int ivertexCutter = cutEvents.get(icutEvent).m_ivertexCutter; - - int ivertexCutteeNext = -1; - int ipartCutterNext = -1; - int ivertexCutterNext = -1; - int countNext = -1; - - if (!bConsiderTouch && icutEvent < cutEvents.size() - 1) { - CutEvent cutEvent = cutEvents.get(icutEvent + 1); - ivertexCutteeNext = cutEvent.m_ivertexCuttee; - ipartCutterNext = cutEvent.m_ipartCutter; - ivertexCutterNext = cutEvent.m_ivertexCutter; - countNext = cutEvent.m_count; - } - - int ivertexCutteePrev = -1; - int ipartCutterPrev = -1; - int ivertexCutterPrev = -1; - int countPrev = -1; - - if (icutEvent > 0) { - CutEvent cutEvent = cutEvents.get(icutEvent - 1); - ivertexCutteePrev = cutEvent.m_ivertexCuttee; - ipartCutterPrev = cutEvent.m_ipartCutter; - ivertexCutterPrev = cutEvent.m_ivertexCutter; - countPrev = cutEvent.m_count; - } - - int ivertexCutteePlus = shape.getNextVertex(ivertexCuttee); - int ivertexCutterMinus = shape.getPrevVertex(ivertexCutter); - - if (!bConsiderTouch) { - if ((icutEvent > 0 && ivertexCutteePrev == ivertexCuttee - && ipartCutterPrev == ipartCutter - && ivertexCutterPrev == ivertexCutterMinus && countPrev == 2) - || (icutEvent < cutEvents.size() - 1 - && ivertexCutteeNext == ivertexCutteePlus - && ipartCutterNext == ipartCutter - && ivertexCutterNext == ivertexCutterMinus && countNext == 2)) { - segmentCutter = shape.getSegment(ivertexCutter); - if (segmentCutter == null) { - shape.queryLineConnector(ivertexCutter, lineCutter); - segmentCutter = lineCutter; - } - - tangent1.setCoords(segmentCutter._getTangent(0.0)); - tangent0.negate(tangent1); - tangent1.normalize(); - tangent0.normalize(); - - return false; - } - - return true; - } - - if (icutEvent == 0 || ivertexCutteePrev != ivertexCuttee - || ipartCutterPrev != ipartCutter - || ivertexCutterPrev != ivertexCutterMinus || countPrev == 2) { - segmentCutter = shape.getSegment(ivertexCutter); - if (segmentCutter == null) { - shape.queryLineConnector(ivertexCutter, lineCutter); - segmentCutter = lineCutter; - } - - tangent1.setCoords(segmentCutter._getTangent(0.0)); - tangent0.negate(tangent1); - tangent1.normalize(); - tangent0.normalize(); - - return false; - } - - // Already processed the event - - return true; - } - - static boolean _cutteeTangents(EditShape shape, - ArrayList cutEvents, int icutEvent, int ipath, - int ivertex, Point2D tangent2, Point2D tangent3) { - Line lineCuttee = new Line(); - Segment segmentCuttee = shape.getSegment(ivertex); - if (segmentCuttee == null) { - shape.queryLineConnector(ivertex, lineCuttee); - segmentCuttee = lineCuttee; - } - - CutEvent cutEvent = cutEvents.get(icutEvent); - int ivertexCuttee = cutEvent.m_ivertexCuttee; - double scalarCuttee = cutEvent.m_scalarCuttee0; - - int ivertexCutteePlus = shape.getNextVertex(ivertexCuttee); - - if (scalarCuttee == 1.0) { - tangent2.setCoords(segmentCuttee._getTangent(1.0)); - - if (ivertexCutteePlus != -1 - && ivertexCutteePlus != shape.getLastVertex(ipath)) { - segmentCuttee = shape.getSegment(ivertexCutteePlus); - if (segmentCuttee == null) { - shape.queryLineConnector(ivertexCutteePlus, lineCuttee); - segmentCuttee = lineCuttee; - } - - tangent3.setCoords(segmentCuttee._getTangent(0.0)); - - segmentCuttee = shape.getSegment(ivertexCuttee); - if (segmentCuttee == null) { - shape.queryLineConnector(ivertexCuttee, lineCuttee); - segmentCuttee = lineCuttee; - } - } else - tangent3.setCoords(tangent2); - - tangent2.negate(); - - tangent3.normalize(); - tangent2.normalize(); - - return false; - } - - if (scalarCuttee == 0.0) { - tangent3.setCoords(segmentCuttee._getTangent(scalarCuttee)); - tangent2.negate(tangent3); - tangent3.normalize(); - tangent2.normalize(); - - return false; - } - - throw GeometryException.GeometryInternalError(); - } + static class CompareVertices { + int m_orderIndex; + EditShape m_editShape; + + CompareVertices(int orderIndex, EditShape editShape) { + m_orderIndex = orderIndex; + m_editShape = editShape; + } + + int _compareVertices(int v1, int v2) { + Point2D pt1 = new Point2D(); + m_editShape.getXY(v1, pt1); + Point2D pt2 = new Point2D(); + m_editShape.getXY(v2, pt2); + int res = pt1.compare(pt2); + if (res != 0) + return res; + int z1 = m_editShape.getUserIndex(v1, m_orderIndex); + int z2 = m_editShape.getUserIndex(v2, m_orderIndex); + if (z1 < z2) + return -1; + if (z1 == z2) + return 0; + return 1; + } + } + + static class CutterVertexComparer extends + AttributeStreamOfInt32.IntComparator { + CompareVertices m_compareVertices; + + CutterVertexComparer(CompareVertices _compareVertices) { + m_compareVertices = _compareVertices; + } + + @Override + public int compare(int v1, int v2) { + return m_compareVertices._compareVertices(v1, v2); + } + } + + static class CutEvent { + int m_ivertexCuttee; + int m_ipartCuttee; + double m_scalarCuttee0; + double m_scalarCuttee1; + int m_count; + int m_ivertexCutter; + int m_ipartCutter; + double m_scalarCutter0; + double m_scalarCutter1; + + CutEvent(int ivertexCuttee, int ipartCuttee, double scalarCuttee0, + double scalarCuttee1, int count, int ivertexCutter, + int ipartCutter, double scalarCutter0, double scalarCutter1) { + m_ivertexCuttee = ivertexCuttee; + m_ipartCuttee = ipartCuttee; + m_scalarCuttee0 = scalarCuttee0; + m_scalarCuttee1 = scalarCuttee1; + m_count = count; + m_ivertexCutter = ivertexCutter; + m_ipartCutter = ipartCutter; + m_scalarCutter0 = scalarCutter0; + m_scalarCutter1 = scalarCutter1; + } + } + + static EditShape CutPolyline(boolean bConsiderTouch, Polyline cuttee, + Polyline cutter, double tolerance, + ArrayList cutPairs, + AttributeStreamOfInt32 segmentCounts, ProgressTracker progressTracker) { + if (cuttee.isEmpty()) { + OperatorCutLocal.CutPair cutPair; + cutPair = new OperatorCutLocal.CutPair(cuttee, + OperatorCutLocal.Side.Uncut, -1, -1, NumberUtils.NaN(), + OperatorCutLocal.Side.Uncut, -1, -1, NumberUtils.NaN(), -1, + -1, NumberUtils.NaN(), -1, -1, NumberUtils.NaN()); + cutPairs.add(cutPair); + return null; + } + + EditShape editShape = new EditShape(); + int cutteeHandle = editShape.addGeometry(cuttee); + int cutterHandle = editShape.addGeometry(cutter); + CrackAndCluster.execute(editShape, tolerance, progressTracker, true); + + int order = 0; + int orderIndex = editShape.createUserIndex(); + for (int igeometry = editShape.getFirstGeometry(); igeometry != -1; igeometry = editShape + .getNextGeometry(igeometry)) + for (int ipath = editShape.getFirstPath(igeometry); ipath != -1; ipath = editShape + .getNextPath(ipath)) + for (int ivertex = editShape.getFirstVertex(ipath), i = 0, n = editShape + .getPathSize(ipath); i < n; ivertex = editShape + .getNextVertex(ivertex), i++) + editShape.setUserIndex(ivertex, orderIndex, order++); + + ArrayList cutEvents = _getCutEvents(orderIndex, editShape); + _Cut(bConsiderTouch, false, cutEvents, editShape, cutPairs, + segmentCounts); + return editShape; + } + + private static ArrayList _getCutEvents(int orderIndex, + EditShape editShape) { + int pointCount = editShape.getTotalPointCount(); + + // Sort vertices lexicographically + // Firstly copy allvertices to an array. + AttributeStreamOfInt32 vertices = new AttributeStreamOfInt32(0); + + for (int igeometry = editShape.getFirstGeometry(); igeometry != -1; igeometry = editShape + .getNextGeometry(igeometry)) + for (int ipath = editShape.getFirstPath(igeometry); ipath != -1; ipath = editShape + .getNextPath(ipath)) + for (int ivertex = editShape.getFirstVertex(ipath), i = 0, n = editShape + .getPathSize(ipath); i < n; ivertex = editShape + .getNextVertex(ivertex), i++) + vertices.add(ivertex); + + // Sort + CompareVertices compareVertices = new CompareVertices(orderIndex, + editShape); + vertices.Sort(0, pointCount, new CutterVertexComparer(compareVertices)); + // SORTDYNAMICARRAYEX(vertices, index_type, 0, pointCount, + // CutterVertexComparer, compareVertices); + + // Find Cut Events + ArrayList cutEvents = new ArrayList(0); + ArrayList cutEventsTemp = new ArrayList(0); + + int eventIndex = editShape.createUserIndex(); + int eventIndexTemp = editShape.createUserIndex(); + + int cutteeHandle = editShape.getFirstGeometry(); + int cutterHandle = editShape.getNextGeometry(cutteeHandle); + + Point2D pointCuttee = new Point2D(); + Point2D pointCutter = new Point2D(); + + int ivertexCuttee = vertices.get(0); + ; + int ipartCuttee = editShape.getPathFromVertex(ivertexCuttee); + int igeometryCuttee = editShape.getGeometryFromPath(ipartCuttee); + editShape.getXY(ivertexCuttee, pointCuttee); + + int istart = 1; + int ivertex = 0; + while (istart < pointCount - 1) { + boolean bCutEvent = false; + for (int i = istart; i < pointCount; i++) { + if (i == ivertex) + continue; + + int ivertexCutter = vertices.get(i); + int ipartCutter = editShape.getPathFromVertex(ivertexCutter); + int igeometryCutter = editShape + .getGeometryFromPath(ipartCutter); + editShape.getXY(ivertexCutter, pointCutter); + + if (pointCuttee.isEqual(pointCutter)) { + boolean bCondition = igeometryCuttee == cutteeHandle + && igeometryCutter == cutterHandle; + + if (bCondition) + bCutEvent = _cutteeCutterEvents(eventIndex, + eventIndexTemp, editShape, cutEvents, + cutEventsTemp, ipartCuttee, ivertexCuttee, + ipartCutter, ivertexCutter); + } else + break; + } + + if (bCutEvent || ivertex == istart - 1) { + if (bCutEvent && (ivertex == istart - 1)) + istart--; + + if (++ivertex == pointCount) + break; + + ivertexCuttee = vertices.get(ivertex); + ipartCuttee = editShape.getPathFromVertex(ivertexCuttee); + igeometryCuttee = editShape.getGeometryFromPath(ipartCuttee); + editShape.getXY(ivertexCuttee, pointCuttee); + } + + if (!bCutEvent) + istart = ivertex + 1; + } + + ArrayList cutEventsSorted = new ArrayList(0); + + // Sort CutEvents + int icutEvent; + int icutEventTemp; + for (int igeometry = editShape.getFirstGeometry(); igeometry != -1; igeometry = editShape.getNextGeometry(igeometry)) { + for (int ipath = editShape.getFirstPath(igeometry); ipath != -1; ipath = editShape.getNextPath(ipath)) { + for (int iv = editShape.getFirstVertex(ipath), i = 0, n = editShape.getPathSize(ipath); i < n; iv = editShape.getNextVertex(iv), i++) { + icutEventTemp = editShape.getUserIndex(iv, eventIndexTemp); + if (icutEventTemp >= 0) { + // _ASSERT(cutEventsTemp.get(icutEventTemp).m_ivertexCuttee + // == iv); + while (icutEventTemp < cutEventsTemp.size() && cutEventsTemp.get(icutEventTemp).m_ivertexCuttee == iv) + cutEventsSorted.add(cutEventsTemp.get(icutEventTemp++)); + } + + icutEvent = editShape.getUserIndex(iv, eventIndex); + if (icutEvent >= 0) { + // _ASSERT(cutEvents->Get(icutEvent)->m_ivertexCuttee == + // iv); + while (icutEvent < cutEvents.size() && cutEvents.get(icutEvent).m_ivertexCuttee == iv) + cutEventsSorted.add(cutEvents.get(icutEvent++)); + } + } + } + } + + // _ASSERT(cutEvents->Size() + cutEventsTemp->Size() == + // cutEventsSorted->Size()); + editShape.removeUserIndex(eventIndex); + editShape.removeUserIndex(eventIndexTemp); + return cutEventsSorted; + } + + static boolean _cutteeCutterEvents(int eventIndex, int eventIndexTemp, + EditShape editShape, ArrayList cutEvents, + ArrayList cutEventsTemp, int ipartCuttee, + int ivertexCuttee, int ipartCutter, int ivertexCutter) { + int ilastVertexCuttee = editShape.getLastVertex(ipartCuttee); + int ilastVertexCutter = editShape.getLastVertex(ipartCutter); + int ifirstVertexCuttee = editShape.getFirstVertex(ipartCuttee); + int ifirstVertexCutter = editShape.getFirstVertex(ipartCutter); + int ivertexCutteePrev = editShape.getPrevVertex(ivertexCuttee); + int ivertexCutterPrev = editShape.getPrevVertex(ivertexCutter); + + boolean bEndEnd = false; + boolean bEndStart = false; + boolean bStartEnd = false; + boolean bStartStart = false; + + if (ivertexCuttee != ifirstVertexCuttee) { + if (ivertexCutter != ifirstVertexCutter) + bEndEnd = _cutteeEndCutterEndEvent(eventIndex, editShape, + cutEvents, ipartCuttee, ivertexCutteePrev, ipartCutter, + ivertexCutterPrev); + + if (ivertexCutter != ilastVertexCutter) + bEndStart = _cutteeEndCutterStartEvent(eventIndex, editShape, + cutEvents, ipartCuttee, ivertexCutteePrev, ipartCutter, + ivertexCutter); + } + + if (ivertexCuttee != ilastVertexCuttee) { + if (ivertexCutter != ifirstVertexCutter) + bStartEnd = _cutteeStartCutterEndEvent(eventIndexTemp, + editShape, cutEventsTemp, ipartCuttee, ivertexCuttee, + ipartCutter, ivertexCutterPrev, ifirstVertexCuttee); + + if (ivertexCutter != ilastVertexCutter) + bStartStart = _cutteeStartCutterStartEvent(eventIndexTemp, + editShape, cutEventsTemp, ipartCuttee, ivertexCuttee, + ipartCutter, ivertexCutter, ifirstVertexCuttee); + } + + if (bEndEnd && bEndStart && bStartEnd) { + int iendstart = cutEvents.size() - 1; + int istartend = (bStartStart ? cutEventsTemp.size() - 2 + : cutEventsTemp.size() - 1); + + if (cutEventsTemp.get(istartend).m_count == 2) { + // Replace bEndEnd with bEndStart, and remove duplicate + // bEndStart (get rid of bEndEnd) + cutEvents.set(iendstart - 1, cutEvents.get(iendstart)); + cutEvents.remove(cutEvents.size() - 1); + } + } else if (bEndEnd && bEndStart && bStartStart) { + int istartstart = cutEventsTemp.size() - 1; + + if (cutEventsTemp.get(istartstart).m_count == 2) { + // Remove bEndStart + CutEvent lastEvent = cutEvents.get(cutEvents.size() - 1); + cutEvents.remove(cutEvents.get(cutEvents.size() - 1)); + int icutEvent = editShape.getUserIndex( + lastEvent.m_ivertexCuttee, eventIndex); + if (icutEvent == cutEvents.size()) + editShape.setUserIndex(lastEvent.m_ivertexCuttee, + eventIndex, -1); + } + } + + return bEndEnd || bEndStart || bStartEnd || bStartStart; + } + + private static boolean _cutteeEndCutterEndEvent(int eventIndex, + EditShape editShape, ArrayList cutEvents, + int ipartCuttee, int ivertexCuttee, int ipartCutter, + int ivertexCutter) { + Segment segmentCuttee; + Segment segmentCutter; + Line lineCuttee = new Line(); + Line lineCutter = new Line(); + double[] scalarsCuttee = new double[2]; + double[] scalarsCutter = new double[2]; + + CutEvent cutEvent; + + segmentCuttee = editShape.getSegment(ivertexCuttee); + if (segmentCuttee == null) { + editShape.queryLineConnector(ivertexCuttee, lineCuttee); + segmentCuttee = lineCuttee; + } + + segmentCutter = editShape.getSegment(ivertexCutter); + if (segmentCutter == null) { + editShape.queryLineConnector(ivertexCutter, lineCutter); + segmentCutter = lineCutter; + } + + int count = segmentCuttee.intersect(segmentCutter, null, scalarsCuttee, + scalarsCutter, 0.0); + // _ASSERT(count > 0); + int icutEvent; + + // If count == 2 (i.e. when they overlap), this this event would have + // been discovered by _CutteeStartCutterStartEvent at the previous index + if (count < 2) { + cutEvent = new CutEvent(ivertexCuttee, ipartCuttee, + scalarsCuttee[0], NumberUtils.NaN(), count, ivertexCutter, + ipartCutter, scalarsCutter[0], NumberUtils.NaN()); + cutEvents.add(cutEvent); + icutEvent = editShape.getUserIndex(ivertexCuttee, eventIndex); + + if (icutEvent < 0) + editShape.setUserIndex(ivertexCuttee, eventIndex, + cutEvents.size() - 1); + } + + return true; + } + + private static boolean _cutteeEndCutterStartEvent(int eventIndex, + EditShape editShape, ArrayList cutEvents, + int ipartCuttee, int ivertexCuttee, int ipartCutter, + int ivertexCutter) { + Segment segmentCuttee; + Segment segmentCutter; + Line lineCuttee = new Line(); + Line lineCutter = new Line(); + double[] scalarsCuttee = new double[2]; + double[] scalarsCutter = new double[2]; + + CutEvent cutEvent; + + segmentCuttee = editShape.getSegment(ivertexCuttee); + if (segmentCuttee == null) { + editShape.queryLineConnector(ivertexCuttee, lineCuttee); + segmentCuttee = lineCuttee; + } + + segmentCutter = editShape.getSegment(ivertexCutter); + if (segmentCutter == null) { + editShape.queryLineConnector(ivertexCutter, lineCutter); + segmentCutter = lineCutter; + } + + int count = segmentCuttee.intersect(segmentCutter, null, scalarsCuttee, + scalarsCutter, 0.0); + // _ASSERT(count > 0); + int icutEvent; + + // If count == 2 (i.e. when they overlap), this this event would have + // been discovered by _CutteeStartCutterEndEvent at the previous index + if (count < 2) { + cutEvent = new CutEvent(ivertexCuttee, ipartCuttee, + scalarsCuttee[0], NumberUtils.NaN(), count, ivertexCutter, + ipartCutter, scalarsCutter[0], NumberUtils.NaN()); + cutEvents.add(cutEvent); + icutEvent = editShape.getUserIndex(ivertexCuttee, eventIndex); + + if (icutEvent < 0) + editShape.setUserIndex(ivertexCuttee, eventIndex, + cutEvents.size() - 1); + + return true; + } + + return false; + } + + private static boolean _cutteeStartCutterEndEvent(int eventIndex, + EditShape editShape, ArrayList cutEvents, + int ipartCuttee, int ivertexCuttee, int ipartCutter, + int ivertexCutter, int ifirstVertexCuttee) { + Segment segmentCuttee; + Segment segmentCutter; + Line lineCuttee = new Line(); + Line lineCutter = new Line(); + double[] scalarsCuttee = new double[2]; + double[] scalarsCutter = new double[2]; + + CutEvent cutEvent; + + segmentCuttee = editShape.getSegment(ivertexCuttee); + if (segmentCuttee == null) { + editShape.queryLineConnector(ivertexCuttee, lineCuttee); + segmentCuttee = lineCuttee; + } + + segmentCutter = editShape.getSegment(ivertexCutter); + if (segmentCutter == null) { + editShape.queryLineConnector(ivertexCutter, lineCutter); + segmentCutter = lineCutter; + } + + int count = segmentCuttee.intersect(segmentCutter, null, scalarsCuttee, + scalarsCutter, 0.0); + // _ASSERT(count > 0); + int icutEvent; + + if (count == 2) { + cutEvent = new CutEvent(ivertexCuttee, ipartCuttee, + scalarsCuttee[0], scalarsCuttee[1], count, ivertexCutter, + ipartCutter, scalarsCutter[0], scalarsCutter[1]); + cutEvents.add(cutEvent); + icutEvent = editShape.getUserIndex(ivertexCuttee, eventIndex); + + if (icutEvent < 0) + editShape.setUserIndex(ivertexCuttee, eventIndex, + cutEvents.size() - 1); + + return true; + } else { + boolean bCutEvent = false; + + if (ivertexCuttee == ifirstVertexCuttee) { + cutEvent = new CutEvent(ivertexCuttee, ipartCuttee, + scalarsCuttee[0], NumberUtils.NaN(), count, + ivertexCutter, ipartCutter, scalarsCutter[0], + NumberUtils.NaN()); + cutEvents.add(cutEvent); + icutEvent = editShape.getUserIndex(ivertexCuttee, eventIndex); + + if (icutEvent < 0) + editShape.setUserIndex(ivertexCuttee, eventIndex, + cutEvents.size() - 1); + + bCutEvent = true; + } + + return bCutEvent; + } + + } + + private static boolean _cutteeStartCutterStartEvent(int eventIndex, + EditShape editShape, ArrayList cutEvents, + int ipartCuttee, int ivertexCuttee, int ipartCutter, + int ivertexCutter, int ifirstVertexCuttee) { + Segment segmentCuttee; + Segment segmentCutter; + Line lineCuttee = new Line(); + Line lineCutter = new Line(); + double[] scalarsCuttee = new double[2]; + double[] scalarsCutter = new double[2]; + + CutEvent cutEvent; + + segmentCuttee = editShape.getSegment(ivertexCuttee); + if (segmentCuttee == null) { + editShape.queryLineConnector(ivertexCuttee, lineCuttee); + segmentCuttee = lineCuttee; + } + + segmentCutter = editShape.getSegment(ivertexCutter); + if (segmentCutter == null) { + editShape.queryLineConnector(ivertexCutter, lineCutter); + segmentCutter = lineCutter; + } + + int count = segmentCuttee.intersect(segmentCutter, null, scalarsCuttee, + scalarsCutter, 0.0); + // _ASSERT(count > 0); + int icutEvent; + + if (count == 2) { + cutEvent = new CutEvent(ivertexCuttee, ipartCuttee, + scalarsCuttee[0], scalarsCuttee[1], count, ivertexCutter, + ipartCutter, scalarsCutter[0], scalarsCutter[1]); + cutEvents.add(cutEvent); + icutEvent = editShape.getUserIndex(ivertexCuttee, eventIndex); + + if (icutEvent < 0) + editShape.setUserIndex(ivertexCuttee, eventIndex, + cutEvents.size() - 1); + + return true; + } else { + boolean bCutEvent = false; + + if (ivertexCuttee == ifirstVertexCuttee) { + cutEvent = new CutEvent(ivertexCuttee, ipartCuttee, + scalarsCuttee[0], NumberUtils.NaN(), count, + ivertexCutter, ipartCutter, scalarsCutter[0], + NumberUtils.NaN()); + cutEvents.add(cutEvent); + icutEvent = editShape.getUserIndex(ivertexCuttee, eventIndex); + + if (icutEvent < 0) + editShape.setUserIndex(ivertexCuttee, eventIndex, + cutEvents.size() - 1); + + bCutEvent = true; + } + + return bCutEvent; + } + + } + + static void _Cut(boolean bConsiderTouch, boolean bLocalCutsOnly, + ArrayList cutEvents, EditShape shape, + ArrayList cutPairs, + AttributeStreamOfInt32 segmentCounts) { + OperatorCutLocal.CutPair cutPair; + + Point2D[] tangents = new Point2D[4]; + tangents[0] = new Point2D(); + tangents[1] = new Point2D(); + tangents[2] = new Point2D(); + tangents[3] = new Point2D(); + Point2D tangent0 = new Point2D(); + Point2D tangent1 = new Point2D(); + Point2D tangent2 = new Point2D(); + Point2D tangent3 = new Point2D(); + + SegmentBuffer segmentBufferCuttee = null; + if (cutPairs != null) { + segmentBufferCuttee = new SegmentBuffer(); + segmentBufferCuttee.createLine(); + } + + Segment segmentCuttee = null; + int icutEvent = 0; + MultiPath multipath = null; + + Line lineCuttee = new Line(); + Line lineCutter = new Line(); + + int polyline = shape.getFirstGeometry(); + for (int ipath = shape.getFirstPath(polyline); ipath != -1; ipath = shape + .getNextPath(ipath)) { + int cut; + int cutPrev = OperatorCutLocal.Side.Uncut; + int ipartCuttee = -1; + int ivertexCuttee = -1; + double scalarCuttee = NumberUtils.NaN(); + int ipartCutteePrev = -1; + int ivertexCutteePrev = -1; + double scalarCutteePrev = NumberUtils.NaN(); + int ipartCutter = -1; + int ivertexCutter = -1; + double scalarCutter = NumberUtils.NaN(); + int ipartCutterPrev = -1; + int ivertexCutterPrev = -1; + double scalarCutterPrev = NumberUtils.NaN(); + boolean bNoCutYet = true; // Indicates whether a cut as occured for + // the current part + boolean bCoincidentNotAdded = false; // Indicates whether the + // current coincident + // multipath has been added + // to cutPairs + boolean bCurrentMultiPathNotAdded = true; // Indicates whether there + // is a multipath not + // yet added to cutPairs + // (left, right, or + // undefined) + boolean bStartNewPath = true; + boolean bCreateNewMultiPath = true; + int segmentCount = 0; + + ipartCutteePrev = ipath; + scalarCutteePrev = 0.0; + + for (int ivertex = shape.getFirstVertex(ipath), n = shape + .getPathSize(ipath), i = 0; i < n; ivertex = shape + .getNextVertex(ivertex), i++) { + segmentCuttee = shape.getSegment(ivertex); + if (segmentCuttee == null) { + if (!shape.queryLineConnector(ivertex, lineCuttee)) + continue; + segmentCuttee = lineCuttee; + } + + if (ivertexCutteePrev == -1) + ivertexCutteePrev = ivertex; + + double lastScalarCuttee = 0.0; // last scalar along the current + // segment + + while (icutEvent < cutEvents.size() + && ivertex == cutEvents.get(icutEvent).m_ivertexCuttee) { + ipartCuttee = cutEvents.get(icutEvent).m_ipartCuttee; + ivertexCuttee = cutEvents.get(icutEvent).m_ivertexCuttee; + scalarCuttee = cutEvents.get(icutEvent).m_scalarCuttee0; + ipartCutter = cutEvents.get(icutEvent).m_ipartCutter; + ivertexCutter = cutEvents.get(icutEvent).m_ivertexCutter; + scalarCutter = cutEvents.get(icutEvent).m_scalarCutter0; + + if (cutEvents.get(icutEvent).m_count == 2) { + // We have an overlap + + if (!bCoincidentNotAdded) { + ipartCutteePrev = ipartCuttee; + ivertexCutteePrev = ivertexCuttee; + scalarCutteePrev = scalarCuttee; + ipartCutterPrev = ipartCutter; + ivertexCutterPrev = ivertexCutter; + scalarCutterPrev = scalarCutter; + cutPrev = OperatorCutLocal.Side.Coincident; + + // Create new multipath + if (cutPairs != null) + multipath = new Polyline(); + else + segmentCount = 0; + + bCreateNewMultiPath = false; + bStartNewPath = true; + } + + scalarCuttee = cutEvents.get(icutEvent).m_scalarCuttee1; + scalarCutter = cutEvents.get(icutEvent).m_scalarCutter1; + + if (cutPairs != null) { + segmentCuttee.cut(lastScalarCuttee, + cutEvents.get(icutEvent).m_scalarCuttee1, + segmentBufferCuttee); + multipath.addSegment(segmentBufferCuttee.get(), + bStartNewPath); + } else + segmentCount++; + + lastScalarCuttee = scalarCuttee; + + bCoincidentNotAdded = true; + bNoCutYet = false; + bStartNewPath = false; + + if (icutEvent + 1 == cutEvents.size() + || cutEvents.get(icutEvent + 1).m_count != 2 + || cutEvents.get(icutEvent + 1).m_ivertexCuttee == ivertexCuttee + && cutEvents.get(icutEvent + 1).m_scalarCuttee0 != lastScalarCuttee) { + if (cutPairs != null) { + cutPair = new OperatorCutLocal.CutPair( + (Geometry) multipath, + OperatorCutLocal.Side.Coincident, + ipartCuttee, ivertexCuttee, + scalarCuttee, cutPrev, ipartCutteePrev, + ivertexCutteePrev, scalarCutteePrev, + ipartCutter, ivertexCutter, + scalarCutter, ipartCutterPrev, + ivertexCutterPrev, scalarCutterPrev); + cutPairs.add(cutPair); + } else { + segmentCounts.add(segmentCount); + } + + ipartCutteePrev = ipartCuttee; + ivertexCutteePrev = ivertexCuttee; + scalarCutteePrev = scalarCuttee; + ipartCutterPrev = ipartCutter; + ivertexCutterPrev = ivertexCutter; + scalarCutterPrev = scalarCutter; + cutPrev = OperatorCutLocal.Side.Coincident; + + bNoCutYet = false; + bCoincidentNotAdded = false; + bCreateNewMultiPath = true; + bStartNewPath = true; + } + + icutEvent++; + continue; + } + + int ivertexCutteePlus = shape.getNextVertex(ivertexCuttee); + int ivertexCutterPlus = shape.getNextVertex(ivertexCutter); + int ivertexCutterMinus = shape.getPrevVertex(ivertexCutter); + + if (icutEvent < cutEvents.size() - 1 + && cutEvents.get(icutEvent + 1).m_ivertexCuttee == ivertexCutteePlus + && cutEvents.get(icutEvent + 1).m_ivertexCutter == ivertexCutter + && cutEvents.get(icutEvent + 1).m_count == 2) { + if (scalarCuttee != lastScalarCuttee) { + if (bCreateNewMultiPath) { + if (cutPairs != null) + multipath = new Polyline(); + else + segmentCount = 0; + } + + if (icutEvent > 0 + && cutEvents.get(icutEvent - 1).m_ipartCuttee == ipartCuttee) { + if (cutPrev == OperatorCutLocal.Side.Right) + cut = OperatorCutLocal.Side.Left; + else if (cutPrev == OperatorCutLocal.Side.Left) + cut = OperatorCutLocal.Side.Right; + else + cut = OperatorCutLocal.Side.Undefined; + } else + cut = OperatorCutLocal.Side.Undefined; + + if (cutPairs != null) { + segmentCuttee.cut(lastScalarCuttee, + scalarCuttee, segmentBufferCuttee); + multipath.addSegment(segmentBufferCuttee.get(), + bStartNewPath); + cutPair = new OperatorCutLocal.CutPair( + multipath, cut, ipartCuttee, + ivertexCuttee, scalarCuttee, cutPrev, + ipartCutteePrev, ivertexCutteePrev, + scalarCutteePrev, ipartCutter, + ivertexCutter, scalarCutter, + ipartCutterPrev, ivertexCutterPrev, + scalarCutterPrev); + cutPairs.add(cutPair); + } else { + segmentCount++; + segmentCounts.add(segmentCount); + } + + lastScalarCuttee = scalarCuttee; + + ipartCutteePrev = ipartCuttee; + ivertexCutteePrev = ivertexCuttee; + scalarCutteePrev = scalarCuttee; + ipartCutterPrev = ipartCutter; + ivertexCutterPrev = ivertexCutter; + scalarCutterPrev = scalarCutter; + cutPrev = cut; + + bCurrentMultiPathNotAdded = false; + bNoCutYet = false; + bCreateNewMultiPath = true; + bStartNewPath = true; + } + + icutEvent++; + continue; + } + + boolean bContinue = _cutterTangents(bConsiderTouch, shape, + cutEvents, icutEvent, tangent0, tangent1); + if (bContinue) { + icutEvent++; + continue; + } + + _cutteeTangents(shape, cutEvents, icutEvent, ipath, + ivertex, tangent2, tangent3); + + boolean bCut = false; + boolean bTouch = false; + boolean bCutRight = true; + + if (!tangent0.isEqual(tangent2) + && !tangent1.isEqual(tangent2) + && !tangent0.isEqual(tangent3) + && !tangent1.isEqual(tangent3)) { + tangents[0].setCoords(tangent0); + tangents[1].setCoords(tangent1); + tangents[2].setCoords(tangent2); + tangents[3].setCoords(tangent3); + + Arrays.sort(tangents, new Point2D.CompareVectors()); + // SORTARRAY(tangents, Point2D, + // Point2D::_CompareVectors); + + Point2D value0 = (Point2D) tangents[0]; + Point2D value1 = (Point2D) tangents[1]; + Point2D value2 = (Point2D) tangents[2]; + Point2D value3 = (Point2D) tangents[3]; + + if (value0.isEqual(tangent0)) { + if (value1.isEqual(tangent1)) { + if (!bConsiderTouch) + bCut = false; + else { + bCut = true; + bTouch = true; + bCutRight = false; + } + } else if (value3.isEqual(tangent1)) { + if (!bConsiderTouch) + bCut = false; + else { + bCut = true; + bTouch = true; + bCutRight = true; + } + } else { + bCut = true; + bCutRight = value1.isEqual(tangent2); + } + } else if (value1.isEqual(tangent0)) { + if (value2.isEqual(tangent1)) { + if (!bConsiderTouch) + bCut = false; + else { + bCut = true; + bTouch = true; + bCutRight = false; + } + } else if (value0.isEqual(tangent1)) { + if (!bConsiderTouch) + bCut = false; + else { + bCut = true; + bTouch = true; + bCutRight = true; + } + } else { + bCut = true; + bCutRight = value2.isEqual(tangent2); + } + } else if (value2.isEqual(tangent0)) { + if (value3.isEqual(tangent1)) { + if (!bConsiderTouch) + bCut = false; + else { + bCut = true; + bTouch = true; + bCutRight = false; + } + } else if (value1.isEqual(tangent1)) { + if (!bConsiderTouch) + bCut = false; + else { + bCut = true; + bTouch = true; + bCutRight = true; + } + } else { + bCut = true; + bCutRight = value3.isEqual(tangent2); + } + } else { + if (value0.isEqual(tangent1)) { + if (!bConsiderTouch) + bCut = false; + else { + bCut = true; + bTouch = true; + bCutRight = false; + } + } else if (value2.isEqual(tangent1)) { + if (!bConsiderTouch) + bCut = false; + else { + bCut = true; + bTouch = true; + bCutRight = true; + } + } else { + bCut = true; + bCutRight = value0.isEqual(tangent2); + } + } + } + + if (bCut) { + boolean bIsFirstSegmentInPath = (ivertex == ivertexCuttee); + + if (scalarCuttee != lastScalarCuttee + || bIsFirstSegmentInPath + && lastScalarCuttee == 0.0) { + if (bCreateNewMultiPath) { + if (cutPairs != null) + multipath = new Polyline(); + else + segmentCount = 0; + } + + if (cutPairs != null) { + segmentCuttee.cut(lastScalarCuttee, + scalarCuttee, segmentBufferCuttee); + multipath.addSegment(segmentBufferCuttee.get(), + bStartNewPath); + } else + segmentCount++; + } + + if (bCutRight) { + if (cutPrev != OperatorCutLocal.Side.Right + || bLocalCutsOnly) { + if (scalarCuttee != lastScalarCuttee + || bIsFirstSegmentInPath + && lastScalarCuttee == 0.0 + || bLocalCutsOnly) { + if (cutPairs != null) { + cutPair = new OperatorCutLocal.CutPair( + multipath, + OperatorCutLocal.Side.Right, + ipartCuttee, ivertexCuttee, + scalarCuttee, cutPrev, + ipartCutteePrev, + ivertexCutteePrev, + scalarCutteePrev, ipartCutter, + ivertexCutter, scalarCutter, + ipartCutterPrev, + ivertexCutterPrev, + scalarCutterPrev); + cutPairs.add(cutPair); + } else { + segmentCounts.add(segmentCount); + } + } + + if (!bTouch) + cutPrev = OperatorCutLocal.Side.Right; + else if (icutEvent == cutEvents.size() - 2 + || cutEvents.get(icutEvent + 2).m_ipartCuttee != ipartCuttee) + cutPrev = OperatorCutLocal.Side.Left; + } else { + if (scalarCuttee != lastScalarCuttee + || bIsFirstSegmentInPath + && lastScalarCuttee == 0.0 + || bLocalCutsOnly) { + if (cutPairs != null) { + cutPair = new OperatorCutLocal.CutPair( + multipath, + OperatorCutLocal.Side.Undefined, + ipartCuttee, ivertexCuttee, + scalarCuttee, cutPrev, + ipartCutteePrev, + ivertexCutteePrev, + scalarCutteePrev, ipartCutter, + ivertexCutter, scalarCutter, + ipartCutterPrev, + ivertexCutterPrev, + scalarCutterPrev); + cutPairs.add(cutPair); + } else { + segmentCounts.add(segmentCount); + } + } + + cutPrev = OperatorCutLocal.Side.Right; + } + } else { + if (cutPrev != OperatorCutLocal.Side.Left + || bLocalCutsOnly) { + if (scalarCuttee != lastScalarCuttee + || bIsFirstSegmentInPath + && lastScalarCuttee == 0.0 + || bLocalCutsOnly) { + if (cutPairs != null) { + cutPair = new OperatorCutLocal.CutPair( + multipath, + OperatorCutLocal.Side.Left, + ipartCuttee, ivertexCuttee, + scalarCuttee, cutPrev, + ipartCutteePrev, + ivertexCutteePrev, + scalarCutteePrev, ipartCutter, + ivertexCutter, scalarCutter, + ipartCutterPrev, + ivertexCutterPrev, + scalarCutterPrev); + cutPairs.add(cutPair); + } else { + segmentCounts.add(segmentCount); + } + } + + if (!bTouch) + cutPrev = OperatorCutLocal.Side.Left; + else if (icutEvent == cutEvents.size() - 2 + || cutEvents.get(icutEvent + 2).m_ipartCuttee != ipartCuttee) + cutPrev = OperatorCutLocal.Side.Right; + } else { + if (scalarCuttee != lastScalarCuttee + || bIsFirstSegmentInPath + && lastScalarCuttee == 0.0 + || bLocalCutsOnly) { + if (cutPairs != null) { + cutPair = new OperatorCutLocal.CutPair( + multipath, + OperatorCutLocal.Side.Undefined, + ipartCuttee, ivertexCuttee, + scalarCuttee, cutPrev, + ipartCutteePrev, + ivertexCutteePrev, + scalarCutteePrev, ipartCutter, + ivertexCutter, scalarCutter, + ipartCutterPrev, + ivertexCutterPrev, + scalarCutterPrev); + cutPairs.add(cutPair); + } else { + segmentCounts.add(segmentCount); + } + } + + cutPrev = OperatorCutLocal.Side.Left; + } + } + + if (scalarCuttee != lastScalarCuttee + || bIsFirstSegmentInPath + && lastScalarCuttee == 0.0 || bLocalCutsOnly) { + lastScalarCuttee = scalarCuttee; + + ipartCutteePrev = ipartCuttee; + ivertexCutteePrev = ivertexCuttee; + scalarCutteePrev = scalarCuttee; + ipartCutterPrev = ipartCutter; + ivertexCutterPrev = ivertexCutter; + scalarCutterPrev = scalarCutter; + + bCurrentMultiPathNotAdded = false; + bNoCutYet = false; + bCreateNewMultiPath = true; + bStartNewPath = true; + } + } + + icutEvent++; + } + + if (lastScalarCuttee != 1.0) { + if (bCreateNewMultiPath) { + if (cutPairs != null) + multipath = new Polyline(); + else + segmentCount = 0; + } + + if (cutPairs != null) { + segmentCuttee.cut(lastScalarCuttee, 1.0, + segmentBufferCuttee); + multipath.addSegment(segmentBufferCuttee.get(), + bStartNewPath); + } else + segmentCount++; + + bCreateNewMultiPath = false; + bStartNewPath = false; + bCurrentMultiPathNotAdded = true; + } + } + + if (bCurrentMultiPathNotAdded) { + scalarCuttee = 1.0; + ivertexCuttee = shape.getLastVertex(ipath); + ivertexCuttee = shape.getPrevVertex(ivertexCuttee); + + ipartCutter = -1; + ivertexCutter = -1; + scalarCutter = NumberUtils.NaN(); + + if (bNoCutYet) { + if (cutPairs != null) { + cutPair = new OperatorCutLocal.CutPair(multipath, + OperatorCutLocal.Side.Uncut, ipartCuttee, + ivertexCuttee, scalarCuttee, cutPrev, + ipartCutteePrev, ivertexCutteePrev, + scalarCutteePrev, ipartCutter, ivertexCutter, + scalarCutter, ipartCutterPrev, + ivertexCutterPrev, scalarCutterPrev); + cutPairs.add(cutPair); + } else { + segmentCounts.add(segmentCount); + } + } else { + if (cutPrev == OperatorCutLocal.Side.Right) + cut = OperatorCutLocal.Side.Left; + else if (cutPrev == OperatorCutLocal.Side.Left) + cut = OperatorCutLocal.Side.Right; + else + cut = OperatorCutLocal.Side.Undefined; + + if (cutPairs != null) { + cutPair = new OperatorCutLocal.CutPair(multipath, cut, + ipartCuttee, ivertexCuttee, scalarCuttee, + cutPrev, ipartCutteePrev, ivertexCutteePrev, + scalarCutteePrev, ipartCutter, ivertexCutter, + scalarCutter, ipartCutterPrev, + ivertexCutterPrev, scalarCutterPrev); + cutPairs.add(cutPair); + } else { + segmentCounts.add(segmentCount); + } + } + } + } + } + + static boolean _cutterTangents(boolean bConsiderTouch, EditShape shape, + ArrayList cutEvents, int icutEvent, Point2D tangent0, + Point2D tangent1) { + double scalarCutter = cutEvents.get(icutEvent).m_scalarCutter0; + + if (scalarCutter == 1.0) + return _cutterEndTangents(bConsiderTouch, shape, cutEvents, + icutEvent, tangent0, tangent1); + + if (scalarCutter == 0.0) + return _cutterStartTangents(bConsiderTouch, shape, cutEvents, + icutEvent, tangent0, tangent1); + + throw GeometryException.GeometryInternalError(); + } + + static boolean _cutterEndTangents(boolean bConsiderTouch, EditShape shape, + ArrayList cutEvents, int icutEvent, Point2D tangent0, + Point2D tangent1) { + Line lineCutter = new Line(); + Segment segmentCutter; + + int ivertexCuttee = cutEvents.get(icutEvent).m_ivertexCuttee; + int ipartCutter = cutEvents.get(icutEvent).m_ipartCutter; + int ivertexCutter = cutEvents.get(icutEvent).m_ivertexCutter; + + int ivertexCutteePrev = -1; + int ipartCutterPrev = -1; + int ivertexCutterPrev = -1; + int countPrev = -1; + + if (!bConsiderTouch && icutEvent > 0) { + CutEvent cutEvent = cutEvents.get(icutEvent - 1); + ivertexCutteePrev = cutEvent.m_ivertexCuttee; + ipartCutterPrev = cutEvent.m_ipartCutter; + ivertexCutterPrev = cutEvent.m_ivertexCutter; + countPrev = cutEvent.m_count; + } + + int ivertexCutteeNext = -1; + int ipartCutterNext = -1; + int ivertexCutterNext = -1; + int countNext = -1; + + if (icutEvent < cutEvents.size() - 1) { + CutEvent cutEvent = cutEvents.get(icutEvent + 1); + ivertexCutteeNext = cutEvent.m_ivertexCuttee; + ipartCutterNext = cutEvent.m_ipartCutter; + ivertexCutterNext = cutEvent.m_ivertexCutter; + countNext = cutEvent.m_count; + } + + int ivertexCutteePlus = shape.getNextVertex(ivertexCuttee); + int ivertexCutterPlus = shape.getNextVertex(ivertexCutter); + + if (!bConsiderTouch) { + if ((icutEvent > 0 && ivertexCutteePrev == ivertexCuttee + && ipartCutterPrev == ipartCutter + && ivertexCutterPrev == ivertexCutterPlus && countPrev == 2) + || (icutEvent < cutEvents.size() - 1 + && ivertexCutteeNext == ivertexCutteePlus + && ipartCutterNext == ipartCutter + && ivertexCutterNext == ivertexCutterPlus && countNext == 2)) { + segmentCutter = shape.getSegment(ivertexCutter); + if (segmentCutter == null) { + shape.queryLineConnector(ivertexCutter, lineCutter); + segmentCutter = lineCutter; + } + + tangent1.setCoords(segmentCutter._getTangent(1.0)); + tangent0.negate(tangent1); + tangent1.normalize(); + tangent0.normalize(); + + return false; + } + + if (icutEvent < cutEvents.size() - 1 + && ivertexCutteeNext == ivertexCuttee + && ipartCutterNext == ipartCutter + && ivertexCutterNext == ivertexCutterPlus) { + segmentCutter = shape.getSegment(ivertexCutter); + if (segmentCutter == null) { + shape.queryLineConnector(ivertexCutter, lineCutter); + segmentCutter = lineCutter; + } + + tangent0.setCoords(segmentCutter._getTangent(1.0)); + + segmentCutter = shape.getSegment(ivertexCutterPlus); + if (segmentCutter == null) { + shape.queryLineConnector(ivertexCutterPlus, lineCutter); + segmentCutter = lineCutter; + } + + tangent1.setCoords(segmentCutter._getTangent(0.0)); + tangent0.negate(); + tangent1.normalize(); + tangent0.normalize(); + + return false; + } + + return true; + } + + if (icutEvent == cutEvents.size() - 1 + || ivertexCutteeNext != ivertexCuttee + || ipartCutterNext != ipartCutter + || ivertexCutterNext != ivertexCutterPlus || countNext == 2) { + segmentCutter = shape.getSegment(ivertexCutter); + if (segmentCutter == null) { + shape.queryLineConnector(ivertexCutter, lineCutter); + segmentCutter = lineCutter; + } + + tangent1.setCoords(segmentCutter._getTangent(1.0)); + tangent0.negate(tangent1); + tangent1.normalize(); + tangent0.normalize(); + + return false; + } + + segmentCutter = shape.getSegment(ivertexCutter); + if (segmentCutter == null) { + shape.queryLineConnector(ivertexCutter, lineCutter); + segmentCutter = lineCutter; + } + + tangent0.setCoords(segmentCutter._getTangent(1.0)); + + segmentCutter = shape.getSegment(ivertexCutterPlus); + if (segmentCutter == null) { + shape.queryLineConnector(ivertexCutterPlus, lineCutter); + segmentCutter = lineCutter; + } + + tangent1.setCoords(segmentCutter._getTangent(0.0)); + tangent0.negate(); + tangent1.normalize(); + tangent0.normalize(); + + return false; + } + + static boolean _cutterStartTangents(boolean bConsiderTouch, + EditShape shape, ArrayList cutEvents, int icutEvent, + Point2D tangent0, Point2D tangent1) { + Line lineCutter = new Line(); + Segment segmentCutter; + + int ivertexCuttee = cutEvents.get(icutEvent).m_ivertexCuttee; + int ipartCutter = cutEvents.get(icutEvent).m_ipartCutter; + int ivertexCutter = cutEvents.get(icutEvent).m_ivertexCutter; + + int ivertexCutteeNext = -1; + int ipartCutterNext = -1; + int ivertexCutterNext = -1; + int countNext = -1; + + if (!bConsiderTouch && icutEvent < cutEvents.size() - 1) { + CutEvent cutEvent = cutEvents.get(icutEvent + 1); + ivertexCutteeNext = cutEvent.m_ivertexCuttee; + ipartCutterNext = cutEvent.m_ipartCutter; + ivertexCutterNext = cutEvent.m_ivertexCutter; + countNext = cutEvent.m_count; + } + + int ivertexCutteePrev = -1; + int ipartCutterPrev = -1; + int ivertexCutterPrev = -1; + int countPrev = -1; + + if (icutEvent > 0) { + CutEvent cutEvent = cutEvents.get(icutEvent - 1); + ivertexCutteePrev = cutEvent.m_ivertexCuttee; + ipartCutterPrev = cutEvent.m_ipartCutter; + ivertexCutterPrev = cutEvent.m_ivertexCutter; + countPrev = cutEvent.m_count; + } + + int ivertexCutteePlus = shape.getNextVertex(ivertexCuttee); + int ivertexCutterMinus = shape.getPrevVertex(ivertexCutter); + + if (!bConsiderTouch) { + if ((icutEvent > 0 && ivertexCutteePrev == ivertexCuttee + && ipartCutterPrev == ipartCutter + && ivertexCutterPrev == ivertexCutterMinus && countPrev == 2) + || (icutEvent < cutEvents.size() - 1 + && ivertexCutteeNext == ivertexCutteePlus + && ipartCutterNext == ipartCutter + && ivertexCutterNext == ivertexCutterMinus && countNext == 2)) { + segmentCutter = shape.getSegment(ivertexCutter); + if (segmentCutter == null) { + shape.queryLineConnector(ivertexCutter, lineCutter); + segmentCutter = lineCutter; + } + + tangent1.setCoords(segmentCutter._getTangent(0.0)); + tangent0.negate(tangent1); + tangent1.normalize(); + tangent0.normalize(); + + return false; + } + + return true; + } + + if (icutEvent == 0 || ivertexCutteePrev != ivertexCuttee + || ipartCutterPrev != ipartCutter + || ivertexCutterPrev != ivertexCutterMinus || countPrev == 2) { + segmentCutter = shape.getSegment(ivertexCutter); + if (segmentCutter == null) { + shape.queryLineConnector(ivertexCutter, lineCutter); + segmentCutter = lineCutter; + } + + tangent1.setCoords(segmentCutter._getTangent(0.0)); + tangent0.negate(tangent1); + tangent1.normalize(); + tangent0.normalize(); + + return false; + } + + // Already processed the event + + return true; + } + + static boolean _cutteeTangents(EditShape shape, + ArrayList cutEvents, int icutEvent, int ipath, + int ivertex, Point2D tangent2, Point2D tangent3) { + Line lineCuttee = new Line(); + Segment segmentCuttee = shape.getSegment(ivertex); + if (segmentCuttee == null) { + shape.queryLineConnector(ivertex, lineCuttee); + segmentCuttee = lineCuttee; + } + + CutEvent cutEvent = cutEvents.get(icutEvent); + int ivertexCuttee = cutEvent.m_ivertexCuttee; + double scalarCuttee = cutEvent.m_scalarCuttee0; + + int ivertexCutteePlus = shape.getNextVertex(ivertexCuttee); + + if (scalarCuttee == 1.0) { + tangent2.setCoords(segmentCuttee._getTangent(1.0)); + + if (ivertexCutteePlus != -1 + && ivertexCutteePlus != shape.getLastVertex(ipath)) { + segmentCuttee = shape.getSegment(ivertexCutteePlus); + if (segmentCuttee == null) { + shape.queryLineConnector(ivertexCutteePlus, lineCuttee); + segmentCuttee = lineCuttee; + } + + tangent3.setCoords(segmentCuttee._getTangent(0.0)); + + segmentCuttee = shape.getSegment(ivertexCuttee); + if (segmentCuttee == null) { + shape.queryLineConnector(ivertexCuttee, lineCuttee); + segmentCuttee = lineCuttee; + } + } else + tangent3.setCoords(tangent2); + + tangent2.negate(); + + tangent3.normalize(); + tangent2.normalize(); + + return false; + } + + if (scalarCuttee == 0.0) { + tangent3.setCoords(segmentCuttee._getTangent(scalarCuttee)); + tangent2.negate(tangent3); + tangent3.normalize(); + tangent2.normalize(); + + return false; + } + + throw GeometryException.GeometryInternalError(); + } } diff --git a/src/main/java/com/esri/core/geometry/DirtyFlags.java b/src/main/java/com/esri/core/geometry/DirtyFlags.java index 554edc71..67fddbca 100644 --- a/src/main/java/com/esri/core/geometry/DirtyFlags.java +++ b/src/main/java/com/esri/core/geometry/DirtyFlags.java @@ -24,41 +24,41 @@ package com.esri.core.geometry; interface DirtyFlags { - public static final int dirtyIsKnownSimple = 1; // !<0 when is_weak_simple - // or is_strong_simple flag - // is valid - public static final int isWeakSimple = 2; // ! 0.01 * fabsdivis) {// more accurate error calculation - // for very inaccurate divisor - double rr = divis.m_eps / fabsdivis; - e *= (1.0 + (1.0 + rr) * rr); - } - m_value = r; - m_eps = e + epsCoordinate() * Math.abs(r); - } - - void div(double v) { - double fabsdivis = Math.abs(v); - m_value /= v; - m_eps = m_eps / fabsdivis + epsCoordinate() * Math.abs(m_value); - } - - void div(ECoordinate v_1, ECoordinate v_2) { - set(v_1); - div(v_2); - } - - void div(double v_1, double v_2) { - m_value = v_1 / v_2; - m_eps = epsCoordinate() * Math.abs(m_value); - } - - void div(ECoordinate v_1, double v_2) { - set(v_1); - div(v_2); - } - - void div(double v_1, ECoordinate v_2) { - set(v_1); - div(v_2); - } - - void sqrt() { - double r, dr; - - if (m_value >= 0) { // assume non-negative input - r = Math.sqrt(m_value); - if (m_value > 10.0 * m_eps) { - dr = 0.5 * m_eps / r; - } else { - dr = (m_value > m_eps) ? r - Math.sqrt(m_value - m_eps) : Math - .max(r, Math.sqrt(m_value + m_eps) - r); - } - - dr += epsCoordinate() * Math.abs(r); - } else { - if (m_value < -m_eps) { // Assume negative input. Return value - // undefined - r = NumberUtils.TheNaN; - dr = NumberUtils.TheNaN; - } else { // assume zero input - r = 0.0; - dr = Math.sqrt(m_eps); - } - } - - m_value = r; - m_eps = dr; - } - - void sqr() { - double r = m_value * m_value; - m_eps = 2 * m_eps * m_value + m_eps * m_eps + epsCoordinate() * r; - m_value = r; - } - - // Assigns sin(angle) to this coordinate. - void sin(ECoordinate angle) { - double sinv = Math.sin(angle.m_value); - double cosv = Math.cos(angle.m_value); - m_value = sinv; - double absv = Math.abs(sinv); - m_eps = (Math.abs(cosv) + absv * 0.5 * angle.m_eps) * angle.m_eps - + epsCoordinate() * absv; - } - - // Assigns cos(angle) to this coordinate. - void cos(ECoordinate angle) { - double sinv = Math.sin(angle.m_value); - double cosv = Math.cos(angle.m_value); - m_value = cosv; - double absv = Math.abs(cosv); - m_eps = (Math.abs(sinv) + absv * 0.5 * angle.m_eps) * angle.m_eps - + epsCoordinate() * absv; - } - - // Calculates natural log of v and assigns to this coordinate - void log(ECoordinate v) { - double d = v.m_eps / v.m_value; - m_value = Math.log(v.m_value); - m_eps = d * (1.0 + 0.5 * d) + epsCoordinate() * Math.abs(m_value); - } - - // void SinAndCos(ECoordinate& _sin, ECoordinate& _cos); - // ECoordinate abs(); - // ECoordinate exp(); - // ECoordinate acos(); - // ECoordinate asin(); - // ECoordinate atan(); - - boolean eq(ECoordinate v) // == - { - return Math.abs(m_value - v.m_value) <= m_eps + v.m_eps; - } - - boolean ne(ECoordinate v) // != - { - return !eq(v); - } - - boolean GT(ECoordinate v) // > - { - return m_value - v.m_value > m_eps + v.m_eps; - } - - boolean lt(ECoordinate v) // < - { - return v.m_value - m_value > m_eps + v.m_eps; - } - - boolean ge(ECoordinate v) // >= - { - return !lt(v); - } - - boolean le(ECoordinate v) // <= - { - return !GT(v); - } - - // The following methods take into account the rounding erros as well as - // user defined tolerance. - boolean tolEq(ECoordinate v, double tolerance) // ! == with tolerance - { - return Math.abs(m_value - v.m_value) <= tolerance || eq(v); - } - - boolean tol_ne(ECoordinate v, double tolerance) // ! != - { - return !tolEq(v, tolerance); - } - - boolean tolGT(ECoordinate v, double tolerance) // ! > - { - return (m_value - v.m_value > tolerance) && GT(v); - } - - boolean tollt(ECoordinate v, double tolerance) // ! < - { - return (v.m_value - m_value > tolerance) && lt(v); - } - - boolean tolge(ECoordinate v, double tolerance) // ! >= - { - return !tollt(v, tolerance); - } - - boolean tolle(ECoordinate v, double tolerance) // ! <= - { - return !tolGT(v, tolerance); - } - - boolean isZero() { - return Math.abs(m_value) <= m_eps; - } - - boolean isFuzzyZero() { - return isZero() && m_eps != 0.0; - } - - boolean tolIsZero(double tolerance) { - return Math.abs(m_value) <= Math.max(m_eps, tolerance); - } - - void setPi() { - set(Math.PI, epsCoordinate()); - } - - void setE() { - set(2.71828182845904523536, epsCoordinate()); - } + private double m_value; + private double m_eps; + + ECoordinate() { + set(0.0, 0.0); + } + + ECoordinate(double v) { + set(v); + } + + ECoordinate(ECoordinate v) { + set(v); + } + + double epsCoordinate() { + return NumberUtils.doubleEps(); + } + + void scaleError(double f) { + m_eps *= f; + } + + void setError(double e) { + m_eps = e; + } + + void set(double v, double e) { + m_value = v; + m_eps = e; + } + + void set(double v) { + m_value = v; + m_eps = 0; + } + + void set(ECoordinate v) { + m_value = v.m_value; + m_eps = v.m_eps; + } + + double value() { + return m_value; + } + + double eps() { + return m_eps; + } + + void resetError() { + m_eps = 0; + } + + void add(ECoordinate v) // += + { + double r = m_value + v.m_value; + double e = m_eps + v.m_eps + epsCoordinate() * Math.abs(r); + m_value = r; + m_eps = e; + } + + void add(double v) // += + { + double r = m_value + v; + double e = m_eps + epsCoordinate() * Math.abs(r); + m_value = r; + m_eps = e; + } + + void sub(ECoordinate v) // -= + { + double r = m_value - v.m_value; + double e = m_eps + v.m_eps + epsCoordinate() * Math.abs(r); + m_value = r; + m_eps = e; + } + + void sub(double v) // -= + { + double r = m_value - v; + double e = m_eps + epsCoordinate() * Math.abs(r); + m_value = r; + m_eps = e; + } + + void add(ECoordinate v_1, ECoordinate v_2) // + + { + m_value = v_1.m_value + v_2.m_value; + m_eps = v_1.m_eps + v_2.m_eps + epsCoordinate() * Math.abs(m_value); + } + + void add(double v_1, double v_2) // + + { + m_value = v_1 + v_2; + m_eps = epsCoordinate() * Math.abs(m_value); + } + + void add(ECoordinate v_1, double v_2) // + + { + m_value = v_1.m_value + v_2; + m_eps = v_1.m_eps + epsCoordinate() * Math.abs(m_value); + } + + void add(double v_1, ECoordinate v_2) // + + { + m_value = v_1 + v_2.m_value; + m_eps = v_2.m_eps + epsCoordinate() * Math.abs(m_value); + } + + void sub(ECoordinate v_1, ECoordinate v_2) // - + { + m_value = v_1.m_value - v_2.m_value; + m_eps = v_1.m_eps + v_2.m_eps + epsCoordinate() * Math.abs(m_value); + } + + void sub(double v_1, double v_2) // - + { + m_value = v_1 - v_2; + m_eps = epsCoordinate() * Math.abs(m_value); + } + + void sub(ECoordinate v_1, double v_2) // - + { + m_value = v_1.m_value - v_2; + m_eps = v_1.m_eps + epsCoordinate() * Math.abs(m_value); + } + + void sub(double v_1, ECoordinate v_2) // - + { + m_value = v_1 - v_2.m_value; + m_eps = v_2.m_eps + epsCoordinate() * Math.abs(m_value); + } + + void mul(ECoordinate v) { + double r = m_value * v.m_value; + m_eps = m_eps * Math.abs(v.m_value) + v.m_eps * Math.abs(m_value) + + m_eps * v.m_eps + epsCoordinate() * Math.abs(r); + m_value = r; + } + + void mul(double v) { + double r = m_value * v; + m_eps = m_eps * Math.abs(v) + epsCoordinate() * Math.abs(r); + m_value = r; + } + + void mul(ECoordinate v_1, ECoordinate v_2) { + double r = v_1.m_value * v_2.m_value; + m_eps = v_1.m_eps * Math.abs(v_2.m_value) + v_2.m_eps + * Math.abs(v_1.m_value) + v_1.m_eps * v_2.m_eps + + epsCoordinate() * Math.abs(r); + m_value = r; + } + + void mul(double v_1, double v_2) { + m_value = v_1 * v_2; + m_eps = epsCoordinate() * Math.abs(m_value); + } + + void mul(ECoordinate v_1, double v_2) { + set(v_1); + mul(v_2); + } + + void mul(double v_1, ECoordinate v_2) { + set(v_2); + mul(v_1); + } + + void div(ECoordinate divis) { + double fabsdivis = Math.abs(divis.m_value); + double r = m_value / divis.m_value; + double e = (m_eps + Math.abs(r) * divis.m_eps) / fabsdivis; + if (divis.m_eps > 0.01 * fabsdivis) {// more accurate error calculation + // for very inaccurate divisor + double rr = divis.m_eps / fabsdivis; + e *= (1.0 + (1.0 + rr) * rr); + } + m_value = r; + m_eps = e + epsCoordinate() * Math.abs(r); + } + + void div(double v) { + double fabsdivis = Math.abs(v); + m_value /= v; + m_eps = m_eps / fabsdivis + epsCoordinate() * Math.abs(m_value); + } + + void div(ECoordinate v_1, ECoordinate v_2) { + set(v_1); + div(v_2); + } + + void div(double v_1, double v_2) { + m_value = v_1 / v_2; + m_eps = epsCoordinate() * Math.abs(m_value); + } + + void div(ECoordinate v_1, double v_2) { + set(v_1); + div(v_2); + } + + void div(double v_1, ECoordinate v_2) { + set(v_1); + div(v_2); + } + + void sqrt() { + double r, dr; + + if (m_value >= 0) { // assume non-negative input + r = Math.sqrt(m_value); + if (m_value > 10.0 * m_eps) { + dr = 0.5 * m_eps / r; + } else { + dr = (m_value > m_eps) ? r - Math.sqrt(m_value - m_eps) : Math + .max(r, Math.sqrt(m_value + m_eps) - r); + } + + dr += epsCoordinate() * Math.abs(r); + } else { + if (m_value < -m_eps) { // Assume negative input. Return value + // undefined + r = NumberUtils.TheNaN; + dr = NumberUtils.TheNaN; + } else { // assume zero input + r = 0.0; + dr = Math.sqrt(m_eps); + } + } + + m_value = r; + m_eps = dr; + } + + void sqr() { + double r = m_value * m_value; + m_eps = 2 * m_eps * m_value + m_eps * m_eps + epsCoordinate() * r; + m_value = r; + } + + // Assigns sin(angle) to this coordinate. + void sin(ECoordinate angle) { + double sinv = Math.sin(angle.m_value); + double cosv = Math.cos(angle.m_value); + m_value = sinv; + double absv = Math.abs(sinv); + m_eps = (Math.abs(cosv) + absv * 0.5 * angle.m_eps) * angle.m_eps + + epsCoordinate() * absv; + } + + // Assigns cos(angle) to this coordinate. + void cos(ECoordinate angle) { + double sinv = Math.sin(angle.m_value); + double cosv = Math.cos(angle.m_value); + m_value = cosv; + double absv = Math.abs(cosv); + m_eps = (Math.abs(sinv) + absv * 0.5 * angle.m_eps) * angle.m_eps + + epsCoordinate() * absv; + } + + // Calculates natural log of v and assigns to this coordinate + void log(ECoordinate v) { + double d = v.m_eps / v.m_value; + m_value = Math.log(v.m_value); + m_eps = d * (1.0 + 0.5 * d) + epsCoordinate() * Math.abs(m_value); + } + + // void SinAndCos(ECoordinate& _sin, ECoordinate& _cos); + // ECoordinate abs(); + // ECoordinate exp(); + // ECoordinate acos(); + // ECoordinate asin(); + // ECoordinate atan(); + + boolean eq(ECoordinate v) // == + { + return Math.abs(m_value - v.m_value) <= m_eps + v.m_eps; + } + + boolean ne(ECoordinate v) // != + { + return !eq(v); + } + + boolean GT(ECoordinate v) // > + { + return m_value - v.m_value > m_eps + v.m_eps; + } + + boolean lt(ECoordinate v) // < + { + return v.m_value - m_value > m_eps + v.m_eps; + } + + boolean ge(ECoordinate v) // >= + { + return !lt(v); + } + + boolean le(ECoordinate v) // <= + { + return !GT(v); + } + + // The following methods take into account the rounding erros as well as + // user defined tolerance. + boolean tolEq(ECoordinate v, double tolerance) // ! == with tolerance + { + return Math.abs(m_value - v.m_value) <= tolerance || eq(v); + } + + boolean tol_ne(ECoordinate v, double tolerance) // ! != + { + return !tolEq(v, tolerance); + } + + boolean tolGT(ECoordinate v, double tolerance) // ! > + { + return (m_value - v.m_value > tolerance) && GT(v); + } + + boolean tollt(ECoordinate v, double tolerance) // ! < + { + return (v.m_value - m_value > tolerance) && lt(v); + } + + boolean tolge(ECoordinate v, double tolerance) // ! >= + { + return !tollt(v, tolerance); + } + + boolean tolle(ECoordinate v, double tolerance) // ! <= + { + return !tolGT(v, tolerance); + } + + boolean isZero() { + return Math.abs(m_value) <= m_eps; + } + + boolean isFuzzyZero() { + return isZero() && m_eps != 0.0; + } + + boolean tolIsZero(double tolerance) { + return Math.abs(m_value) <= Math.max(m_eps, tolerance); + } + + void setPi() { + set(Math.PI, epsCoordinate()); + } + + void setE() { + set(2.71828182845904523536, epsCoordinate()); + } } diff --git a/src/main/java/com/esri/core/geometry/EditShape.java b/src/main/java/com/esri/core/geometry/EditShape.java index dfc93283..eb1a679f 100644 --- a/src/main/java/com/esri/core/geometry/EditShape.java +++ b/src/main/java/com/esri/core/geometry/EditShape.java @@ -33,2320 +33,2320 @@ * vertices. */ final class EditShape { - interface PathFlags_ { - static final int closedPath = 1; - static final int exteriorPath = 2; - static final int ringAreaValid = 4; - } - - private int m_geometryCount; - private int m_path_count; - private int m_point_count; - private int m_first_geometry; - private int m_last_geometry; - - private StridedIndexTypeCollection m_vertex_index_list; - - // ****************Vertex Data****************** - private MultiPoint m_vertices_mp; // vertex coordinates are stored here - // Attribute_stream_of_index_type::SPtr m_indexRemap; - private MultiPointImpl m_vertices; // Internals of m_vertices_mp - AttributeStreamOfDbl m_xy_stream; // The xy stream of the m_vertices. - VertexDescription m_vertex_description;// a shortcut to the vertex - // description. - boolean m_b_has_attributes; // a short cut to know if we have something in - // addition to x and y. - - ArrayList m_segments;// may be NULL if all segments a Lines, - // otherwise contains NULLs for Line - // segments. Curves are not NULL. - AttributeStreamOfDbl m_weights;// may be NULL if no weights are provided. - // NULL weights assumes weight value of 1. - ArrayList m_indices;// user indices are here - // ****************End Vertex Data************** - StridedIndexTypeCollection m_path_index_list; // doubly connected list. Path - // index into the Path Data - // arrays, Prev path, next - // path. - // ******************Path Data****************** - AttributeStreamOfDbl m_path_areas; - AttributeStreamOfDbl m_path_lengths; - // Block_array::SPtr m_path_envelopes; - ArrayList m_pathindices;// path user indices are - // here - // *****************End Path Data*************** - StridedIndexTypeCollection m_geometry_index_list; - ArrayList m_geometry_indices;// geometry user - // indices are here - - // *********** Helpers for Bucket sort************** - static class EditShapeBucketSortHelper extends ClassicSort { - EditShape m_shape; - - EditShapeBucketSortHelper(EditShape shape) { - m_shape = shape; - } - - @Override - public void userSort(int begin, int end, AttributeStreamOfInt32 indices) { - m_shape.sortVerticesSimpleByYHelper_(indices, begin, end); - } - - @Override - public double getValue(int index) { - return m_shape.getY(index); - } - } - - ; - - BucketSort m_bucket_sort; - - // Envelope::SPtr m_envelope; //the BBOX for all attributes - Point m_helper_point; // a helper point for intermediate operations - - Segment getSegmentFromIndex_(int vindex) { - return m_segments != null ? m_segments.get(vindex) : null; - } - - void setSegmentToIndex_(int vindex, Segment seg) { - if (m_segments == null) { - if (seg == null) - return; - m_segments = new ArrayList(); - for (int i = 0, n = m_vertices.getPointCount(); i < n; i++) - m_segments.add(null); - } - m_segments.set(vindex, seg); - } - - void setPrevPath_(int path, int prev) { - m_path_index_list.setField(path, 1, prev); - } - - void setNextPath_(int path, int next) { - m_path_index_list.setField(path, 2, next); - } - - void setPathFlags_(int path, int flags) { - m_path_index_list.setField(path, 6, flags); - } - - int getPathFlags_(int path) { - return m_path_index_list.getField(path, 6); - } - - void setPathGeometry_(int path, int geom) { - m_path_index_list.setField(path, 7, geom); - } - - int getPathIndex_(int path) { - return m_path_index_list.getField(path, 0); - } - - void setNextGeometry_(int geom, int next) { - m_geometry_index_list.setField(geom, 1, next); - } - - void setPrevGeometry_(int geom, int prev) { - m_geometry_index_list.setField(geom, 0, prev); - } - - int getGeometryIndex_(int geom) { - return m_geometry_index_list.getField(geom, 7); - } - - int getFirstPath_(int geom) { - return m_geometry_index_list.getField(geom, 3); - } - - void setFirstPath_(int geom, int firstPath) { - m_geometry_index_list.setField(geom, 3, firstPath); - } - - void setLastPath_(int geom, int path) { - m_geometry_index_list.setField(geom, 4, path); - } - - int newGeometry_(int gt) { - // Index_type index = m_first_free_geometry; - if (m_geometry_index_list == null) - m_geometry_index_list = new StridedIndexTypeCollection(8); - - int index = m_geometry_index_list.newElement(); - // m_geometry_index_list.set(index + 0, -1);//prev - // m_geometry_index_list.set(index + 1, -1);//next - m_geometry_index_list.setField(index, 2, gt);// Geometry_type - // m_geometry_index_list.set(index + 3, -1);//first path - // m_geometry_index_list.set(index + 4, -1);//last path - m_geometry_index_list.setField(index, 5, 0);// point count - m_geometry_index_list.setField(index, 6, 0);// path count - m_geometry_index_list.setField(index, 7, - m_geometry_index_list.elementToIndex(index));// geometry index - - return index; - } - - void freeGeometry_(int geom) { - m_geometry_index_list.deleteElement(geom); - } - - int newPath_(int geom) { - if (m_path_index_list == null) { - m_path_index_list = new StridedIndexTypeCollection(8); - m_vertex_index_list = new StridedIndexTypeCollection(5); - m_path_areas = new AttributeStreamOfDbl(0); - m_path_lengths = new AttributeStreamOfDbl(0); - } - - int index = m_path_index_list.newElement(); - int pindex = m_path_index_list.elementToIndex(index); - m_path_index_list.setField(index, 0, pindex);// size - // m_path_index_list.set(index + 1, -1);//prev - // m_path_index_list.set(index + 2, -1);//next - m_path_index_list.setField(index, 3, 0);// size - // m_path_index_list.set(index + 4, -1);//first vertex handle - // m_path_index_list.set(index + 5, -1);//last vertex handle - m_path_index_list.setField(index, 6, 0);// path flags - setPathGeometry_(index, geom); - if (pindex >= m_path_areas.size()) { - int sz = pindex < 16 ? 16 : (pindex * 3) / 2; - m_path_areas.resize(sz); - m_path_lengths.resize(sz); - // if (m_path_envelopes) - // m_path_envelopes.resize(sz); - } - m_path_areas.set(pindex, 0); - m_path_lengths.set(pindex, 0); - // if (m_path_envelopes) - // m_path_envelopes.set(pindex, nullptr); - - m_path_count++; - return index; - } - - void freePath_(int path) { - m_path_index_list.deleteElement(path); - m_path_count--; - } - - void freeVertex_(int vertex) { - m_vertex_index_list.deleteElement(vertex); - m_point_count--; - } - - int newVertex_(int vindex) { - assert (vindex >= 0 || vindex == -1);// vindex is not a handle - - if (m_path_index_list == null) { - m_path_index_list = new StridedIndexTypeCollection(8); - m_vertex_index_list = new StridedIndexTypeCollection(5); - m_path_areas = new AttributeStreamOfDbl(0); - m_path_lengths = new AttributeStreamOfDbl(0); - } - - int index = m_vertex_index_list.newElement(); - int vi = vindex >= 0 ? vindex : m_vertex_index_list - .elementToIndex(index); - m_vertex_index_list.setField(index, 0, vi); - if (vindex < 0) { - if (vi >= m_vertices.getPointCount()) { - int sz = vi < 16 ? 16 : (vi * 3) / 2; - // m_vertices.reserveRounded(sz); - m_vertices.resize(sz); - if (m_segments != null) { - for (int i = 0; i < sz; i++) - m_segments.add(null); - } - - if (m_weights != null) - m_weights.resize(sz); - - m_xy_stream = (AttributeStreamOfDbl) m_vertices - .getAttributeStreamRef(VertexDescription.Semantics.POSITION); - } - - m_vertices.setXY(vi, -1e38, -1e38); - - if (m_segments != null) - m_segments.set(vi, null); - - if (m_weights != null) - m_weights.write(vi, 1.0); - } else { - // We do not set vertices or segments here, because we assume those - // are set correctly already. - // We only here to create linked list of indices on existing vertex - // value. - // m_segments->set(m_point_count, nullptr); - } - - m_vertex_index_list.setField(index, 4, vi * 2); - m_point_count++; - return index; - } - - void free_vertex_(int vertex) { - m_vertex_index_list.deleteElement(vertex); - m_point_count--; - } - - int insertVertex_(int path, int before, Point point) { - int prev = before != -1 ? getPrevVertex(before) : getLastVertex(path); - int next = prev != -1 ? getNextVertex(prev) : -1; - - int vertex = newVertex_(point == null ? m_point_count : -1); - int vindex = getVertexIndex(vertex); - if (point != null) - m_vertices.setPointByVal(vindex, point); - - setPathToVertex_(vertex, path); - setNextVertex_(vertex, next); - setPrevVertex_(vertex, prev); - - if (next != -1) - setPrevVertex_(next, vertex); - - if (prev != -1) - setNextVertex_(prev, vertex); - - boolean b_closed = isClosedPath(path); - int first = getFirstVertex(path); - if (before == -1) - setLastVertex_(path, vertex); - - if (before == first) - setFirstVertex_(path, vertex); - - if (b_closed && next == -1) { - setNextVertex_(vertex, vertex); - setPrevVertex_(vertex, vertex); - } - - setPathSize_(path, getPathSize(path) + 1); - int geometry = getGeometryFromPath(path); - setGeometryVertexCount_(geometry, getPointCount(geometry) + 1); - - return vertex; - } - - Point getHelperPoint_() { - if (m_helper_point == null) - m_helper_point = new Point(m_vertices.getDescription()); - return m_helper_point; - } - - void setFillRule(int geom, int rule) { - int t = m_geometry_index_list.getField(geom, 2); - t &= ~(0x8000000); - t |= rule == Polygon.FillRule.enumFillRuleWinding ? 0x8000000 : 0; - m_geometry_index_list.setField(geom, 2, t);//fill rule combined with geometry type - } - - int getFillRule(int geom) { - int t = m_geometry_index_list.getField(geom, 2); - return (t & 0x8000000) != 0 ? Polygon.FillRule.enumFillRuleWinding : Polygon.FillRule.enumFillRuleOddEven; - } - - int addMultiPath_(MultiPath multi_path) { - int newgeom = createGeometry(multi_path.getType(), - multi_path.getDescription()); - if (multi_path.getType() == Geometry.Type.Polygon) - setFillRule(newgeom, ((Polygon) multi_path).getFillRule()); - - appendMultiPath_(newgeom, multi_path); - return newgeom; - } - - int addMultiPoint_(MultiPoint multi_point) { - int newgeometry = createGeometry(multi_point.getType(), - multi_point.getDescription()); - appendMultiPoint_(newgeometry, multi_point); - return newgeometry; - } - - void appendMultiPath_(int dstGeom, MultiPath multi_path) { - MultiPathImpl mp_impl = (MultiPathImpl) multi_path._getImpl(); - // m_vertices->reserve_rounded(m_vertices->get_point_count() + - // mp_impl->get_point_count());//ensure reallocation happens by blocks - // so that already allocated vertices do not get reallocated. - m_vertices_mp.add(multi_path, 0, mp_impl.getPointCount()); - m_xy_stream = (AttributeStreamOfDbl) m_vertices - .getAttributeStreamRef(VertexDescription.Semantics.POSITION); - boolean b_some_segments = m_segments != null - && mp_impl.getSegmentFlagsStreamRef() != null; - - for (int ipath = 0, npath = mp_impl.getPathCount(); ipath < npath; ipath++) { - if (mp_impl.getPathSize(ipath) < 2) // CR249862 - Clipping geometry - // which has empty part produces - // a crash - continue; - - int path = insertPath(dstGeom, -1); - setClosedPath(path, mp_impl.isClosedPath(ipath)); - for (int ivertex = mp_impl.getPathStart(ipath), iend = mp_impl - .getPathEnd(ipath); ivertex < iend; ivertex++) { - int vertex = insertVertex_(path, -1, null); - if (b_some_segments) { - int vindex = getVertexIndex(vertex); - if ((mp_impl.getSegmentFlags(ivertex) & (byte) SegmentFlags.enumLineSeg) != 0) { - setSegmentToIndex_(vindex, null); - } else { - SegmentBuffer seg_buffer = new SegmentBuffer(); - mp_impl.getSegment(ivertex, seg_buffer, true); - setSegmentToIndex_(vindex, seg_buffer.get()); - } - } - } - } - - // {//debug - // #ifdef DEBUG - // for (Index_type geometry = get_first_geometry(); geometry != -1; - // geometry = get_next_geometry(geometry)) - // { - // for (Index_type path = get_first_path(geometry); path != -1; path = - // get_next_path(path)) - // { - // Index_type first = get_first_vertex(path); - // Index_type v = first; - // for (get_next_vertex(v); v != first; v = get_next_vertex(v)) - // { - // assert(get_next_vertex(get_prev_vertex(v)) == v); - // } - // } - // } - // #endif - // } - } - - void appendMultiPoint_(int dstGeom, MultiPoint multi_point) { - // m_vertices->reserve_rounded(m_vertices->get_point_count() + - // multi_point.get_point_count());//ensure reallocation happens by - // blocks so that already allocated vertices do not get reallocated. - m_vertices_mp.add(multi_point, 0, multi_point.getPointCount()); - m_xy_stream = (AttributeStreamOfDbl) m_vertices - .getAttributeStreamRef(VertexDescription.Semantics.POSITION); - - int path = insertPath(dstGeom, -1); - for (int ivertex = 0, iend = multi_point.getPointCount(); ivertex < iend; ivertex++) { - insertVertex_(path, -1, null); - } - } - - void splitSegmentForward_(int origin_vertex, - SegmentIntersector intersector, int intersector_index) { - int last_vertex = getNextVertex(origin_vertex); - if (last_vertex == -1) - throw GeometryException.GeometryInternalError(); - Point point = getHelperPoint_(); - int path = getPathFromVertex(origin_vertex); - int vertex = origin_vertex; - for (int i = 0, n = intersector - .getResultSegmentCount(intersector_index); i < n; i++) { - int vindex = getVertexIndex(vertex); - int next_vertex = getNextVertex(vertex); - Segment seg = intersector.getResultSegment(intersector_index, i); - - if (i == 0) { - seg.queryStart(point); - // #ifdef DEBUG - // Point2D pt = new Point2D(); - // getXY(vertex, pt); - // assert(Point2D.distance(point.getXY(), pt) <= - // intersector.get_tolerance_()); - // #endif - setPoint(vertex, point); - } - - if (seg.getType().value() == Geometry.GeometryType.Line) - setSegmentToIndex_(vindex, null); - else - setSegmentToIndex_(vindex, (Segment) Geometry._clone(seg)); - - seg.queryEnd(point); - if (i < n - 1) { - int inserted_vertex = insertVertex_(path, next_vertex, point); - vertex = inserted_vertex; - } else { - // #ifdef DEBUG - // Point_2D pt; - // get_xy(last_vertex, pt); - // assert(Point_2D::distance(point->get_xy(), pt) <= - // intersector.getTolerance_()); - // #endif - setPoint(last_vertex, point); - assert (last_vertex == next_vertex); - } - } - } - - void splitSegmentBackward_(int origin_vertex, - SegmentIntersector intersector, int intersector_index) { - int last_vertex = getNextVertex(origin_vertex); - if (last_vertex == -1) - throw GeometryException.GeometryInternalError(); - - Point point = getHelperPoint_(); - int path = getPathFromVertex(origin_vertex); - int vertex = origin_vertex; - for (int i = 0, n = intersector - .getResultSegmentCount(intersector_index); i < n; i++) { - int vindex = getVertexIndex(vertex); - int next_vertex = getNextVertex(vertex); - Segment seg = intersector.getResultSegment(intersector_index, n - i - - 1); - - if (i == 0) { - seg.queryEnd(point); - // #ifdef DEBUG - // Point2D pt = new Point2D(); - // getXY(vertex, pt); - // assert(Point2D.distance(point.getXY(), pt) <= - // intersector.getTolerance_()); - // #endif - setPoint(vertex, point); - } - - if (seg.getType().value() == Geometry.GeometryType.Line) - setSegmentToIndex_(vindex, null); - else - setSegmentToIndex_(vindex, (Segment) Geometry._clone(seg)); - - seg.queryStart(point); - if (i < n - 1) { - int inserted_vertex = insertVertex_(path, next_vertex, point); - vertex = inserted_vertex; - } else { - // #ifdef DEBUG - // Point2D pt = new Point2D(); - // getXY(last_vertex, pt); - // assert(Point2D.distance(point.getXY(), pt) <= - // intersector.getTolerance_()); - // #endif - setPoint(last_vertex, point); - assert (last_vertex == next_vertex); - } - } - } - - EditShape() { - m_path_count = 0; - m_first_geometry = -1; - m_last_geometry = -1; - m_point_count = 0; - m_geometryCount = 0; - m_b_has_attributes = false; - m_vertices = null; - m_xy_stream = null; - m_vertex_description = null; - } - - // Total point count in all geometries - int getTotalPointCount() { - return m_point_count; - } - - // Returns envelope of all coordinates. - Envelope2D getEnvelope2D() { - Envelope2D env = new Envelope2D(); - env.setEmpty(); - VertexIterator vert_iter = queryVertexIterator(); - Point2D pt = new Point2D(); - boolean b_first = true; - for (int ivertex = vert_iter.next(); ivertex != -1; ivertex = vert_iter - .next()) { - getXY(ivertex, pt); - if (b_first) - env.merge(pt.x, pt.y); - else - env.mergeNE(pt.x, pt.y); - - b_first = false; - } - - return env; - } - - // Returns geometry count in the edit shape - int getGeometryCount() { - return m_geometryCount; - } - - // Adds a Geometry to the Edit_shape - int addGeometry(Geometry geometry) { - Geometry.Type gt = geometry.getType(); - if (Geometry.isMultiPath(gt.value())) - return addMultiPath_((MultiPath) geometry); - if (gt == Geometry.Type.MultiPoint) - return addMultiPoint_((MultiPoint) geometry); - - throw GeometryException.GeometryInternalError(); - } - - // Append a Geometry to the given geometry of the Edit_shape - void appendGeometry(int dstGeometry, Geometry srcGeometry) { - Geometry.Type gt = srcGeometry.getType(); - if (Geometry.isMultiPath(gt.value())) { - appendMultiPath_(dstGeometry, (MultiPath) srcGeometry); - return; - } else if (gt.value() == Geometry.GeometryType.MultiPoint) { - appendMultiPoint_(dstGeometry, (MultiPoint) srcGeometry); - return; - } - - throw GeometryException.GeometryInternalError(); - } - - // Adds a path - int addPathFromMultiPath(MultiPath multi_path, int ipath, boolean as_polygon) { - int newgeom = createGeometry(as_polygon ? Geometry.Type.Polygon - : Geometry.Type.Polyline, multi_path.getDescription()); - - MultiPathImpl mp_impl = (MultiPathImpl) multi_path._getImpl(); - if (multi_path.getPathSize(ipath) < 2) - return newgeom; //return empty geometry - - // m_vertices->reserve_rounded(m_vertices->get_point_count() + - // multi_path.get_path_size(ipath));//ensure reallocation happens by - // blocks so that already allocated vertices do not get reallocated. - m_vertices_mp.add(multi_path, multi_path.getPathStart(ipath), - mp_impl.getPathEnd(ipath)); - m_xy_stream = (AttributeStreamOfDbl) m_vertices - .getAttributeStreamRef(VertexDescription.Semantics.POSITION); - - int path = insertPath(newgeom, -1); - setClosedPath(path, mp_impl.isClosedPath(ipath) || as_polygon); - - boolean b_some_segments = m_segments != null - && mp_impl.getSegmentFlagsStreamRef() != null; - - for (int ivertex = mp_impl.getPathStart(ipath), iend = mp_impl - .getPathEnd(ipath); ivertex < iend; ivertex++) { - int vertex = insertVertex_(path, -1, null); - if (b_some_segments) { - int vindex = getVertexIndex(vertex); - if ((mp_impl.getSegmentFlags(ivertex) & SegmentFlags.enumLineSeg) != 0) { - setSegmentToIndex_(vindex, null); - } else { - SegmentBuffer seg_buffer = new SegmentBuffer(); - mp_impl.getSegment(ivertex, seg_buffer, true); - setSegmentToIndex_(vindex, seg_buffer.get()); - } - } - } - - return newgeom; - } - - // Extracts a geometry from the Edit_shape. The method creates a new - // Geometry instance and initializes it with the Edit_shape data for the - // given geometry. - Geometry getGeometry(int geometry) { - int gt = getGeometryType(geometry); - Geometry geom = InternalUtils.createGeometry(gt, - m_vertices_mp.getDescription()); - int point_count = getPointCount(geometry); - - if (point_count == 0) - return geom; - - if (Geometry.isMultiPath(gt)) { - MultiPathImpl mp_impl = (MultiPathImpl) geom._getImpl(); - int path_count = getPathCount(geometry); - AttributeStreamOfInt32 parts = (AttributeStreamOfInt32) (AttributeStreamBase - .createIndexStream(path_count + 1)); - AttributeStreamOfInt8 pathFlags = (AttributeStreamOfInt8) (AttributeStreamBase - .createByteStream(path_count + 1, (byte) 0)); - VertexDescription description = geom.getDescription(); - - for (int iattrib = 0, nattrib = description.getAttributeCount(); iattrib < nattrib; iattrib++) { - int semantics = description.getSemantics(iattrib); - int ncomps = VertexDescription.getComponentCount(semantics); - AttributeStreamBase dst_stream = AttributeStreamBase - .createAttributeStreamWithSemantics(semantics, - point_count); - AttributeStreamBase src_stream = m_vertices - .getAttributeStreamRef(semantics); - int dst_index = 0; - int ipath = 0; - int nvert = 0; - for (int path = getFirstPath(geometry); path != -1; path = getNextPath(path)) { - byte flag_mask = 0; - if (isClosedPath(path)) { - flag_mask |= (byte) PathFlags.enumClosed; - } else { - assert (gt != Geometry.GeometryType.Polygon); - } - - if (isExterior(path)) { - flag_mask |= (byte) PathFlags.enumOGCStartPolygon; - } - - if (flag_mask != 0) - pathFlags.setBits(ipath, flag_mask); - - int path_size = getPathSize(path); - parts.write(ipath++, nvert); - nvert += path_size; - if (semantics == VertexDescription.Semantics.POSITION) { - AttributeStreamOfDbl src_stream_dbl = (AttributeStreamOfDbl) (src_stream); - AttributeStreamOfDbl dst_stream_dbl = (AttributeStreamOfDbl) (dst_stream); - Point2D pt = new Point2D(); - for (int vertex = getFirstVertex(path); dst_index < nvert; vertex = getNextVertex(vertex), dst_index++) { - int src_index = getVertexIndex(vertex); - src_stream_dbl.read(src_index * 2, pt); - dst_stream_dbl.write(dst_index * 2, pt); - } - } else { - for (int vertex = getFirstVertex(path); dst_index < nvert; vertex = getNextVertex(vertex), dst_index++) { - int src_index = getVertexIndex(vertex); - for (int icomp = 0; icomp < ncomps; icomp++) { - double d = src_stream.readAsDbl(src_index - * ncomps + icomp); - dst_stream.writeAsDbl(dst_index * ncomps - + icomp, d); - } - } - } - } - - assert (nvert == point_count);// Inconsistent content in the - // Edit_shape. Please, fix. - assert (ipath == path_count); - mp_impl.setAttributeStreamRef(semantics, dst_stream); - parts.write(path_count, point_count); - } - - mp_impl.setPathFlagsStreamRef(pathFlags); - mp_impl.setPathStreamRef(parts); - mp_impl.notifyModified(DirtyFlags.dirtyAll); - } else if (gt == Geometry.GeometryType.MultiPoint) { - MultiPointImpl mp_impl = (MultiPointImpl) geom._getImpl(); - VertexDescription description = geom.getDescription(); - // mp_impl.reserve(point_count); - mp_impl.resize(point_count); - - for (int iattrib = 0, nattrib = description.getAttributeCount(); iattrib < nattrib; iattrib++) { - int semantics = description.getSemantics(iattrib); - int ncomps = VertexDescription.getComponentCount(semantics); - AttributeStreamBase dst_stream = mp_impl - .getAttributeStreamRef(semantics); - // std::shared_ptr dst_stream = - // Attribute_stream_base::create_attribute_stream(semantics, - // point_count); - AttributeStreamBase src_stream = m_vertices - .getAttributeStreamRef(semantics); - int dst_index = 0; - assert (getPathCount(geometry) == 1); - int path = getFirstPath(geometry); - int path_size = getPathSize(path); - for (int vertex = getFirstVertex(path); dst_index < path_size; vertex = getNextVertex(vertex), dst_index++) { - int src_index = getVertexIndex(vertex); - for (int icomp = 0; icomp < ncomps; icomp++) { - double d = src_stream.readAsDbl(src_index * ncomps - + icomp); - dst_stream.writeAsDbl(dst_index * ncomps + icomp, d); - } - } - - mp_impl.setAttributeStreamRef(semantics, dst_stream); - } - - mp_impl.notifyModified(DirtyFlags.dirtyAll); - } else { - assert (false); - } - - return geom; - } - - // create a new empty geometry of the given type - int createGeometry(Geometry.Type geometry_type) { - return createGeometry(geometry_type, - VertexDescriptionDesignerImpl.getDefaultDescriptor2D()); - } - - // Deletes existing geometry from the edit shape and returns the next one. - int removeGeometry(int geometry) { - for (int path = getFirstPath(geometry); path != -1; path = removePath(path)) { - // removing paths in a loop - } - - int prev = getPrevGeometry(geometry); - int next = getNextGeometry(geometry); - if (prev != -1) - setNextGeometry_(prev, next); - else { - m_first_geometry = next; - } - if (next != -1) - setPrevGeometry_(next, prev); - else { - m_last_geometry = prev; - } - - freeGeometry_(geometry); - return next; - } - - // create a new empty geometry of the given type and attribute set. - int createGeometry(Geometry.Type geometry_type, - VertexDescription description) { - int newgeom = newGeometry_(geometry_type.value()); - if (m_vertices == null) { - m_vertices_mp = new MultiPoint(description); - m_vertices = (MultiPointImpl) m_vertices_mp._getImpl(); - } else - m_vertices_mp.mergeVertexDescription(description); - - m_vertex_description = m_vertices_mp.getDescription();// this - // description - // will be a - // merge of - // existing - // description - // and the - // description - // of the - // multi_path - m_b_has_attributes = m_vertex_description.getAttributeCount() > 1; - - if (m_first_geometry == -1) { - m_first_geometry = newgeom; - m_last_geometry = newgeom; - } else { - setPrevGeometry_(newgeom, m_last_geometry); - setNextGeometry_(m_last_geometry, newgeom); - m_last_geometry = newgeom; - } - return newgeom; - } - - // Returns the first geometry in the Edit_shape. - int getFirstGeometry() { - return m_first_geometry; - } - - // Returns the next geometry in the Edit_shape. Returns -1 when there are no - // more geometries. - int getNextGeometry(int geom) { - return m_geometry_index_list.getField(geom, 1); - } - - // Returns the previous geometry in the Edit_shape. Returns -1 when there - // are no more geometries. - int getPrevGeometry(int geom) { - return m_geometry_index_list.getField(geom, 0); - } - - // Returns the type of the Geometry. - int getGeometryType(int geom) { - return m_geometry_index_list.getField(geom, 2) & 0x7FFFFFFF; - } - - // Sets value to the given user index on a geometry. - void setGeometryUserIndex(int geom, int index, int value) { - AttributeStreamOfInt32 stream = m_geometry_indices.get(index); - int pindex = getGeometryIndex_(geom); - if (pindex >= stream.size()) - stream.resize(Math.max((int) (pindex * 1.25), (int) 16), -1); - stream.write(pindex, value); - } - - // Returns the value of the given user index of a geometry - int getGeometryUserIndex(int geom, int index) { - int pindex = getGeometryIndex_(geom); - AttributeStreamOfInt32 stream = m_geometry_indices.get(index); - if (pindex < stream.size()) - return stream.read(pindex); - else - return -1; - } - - // Creates new user index on a geometry. The geometry index allows to store - // an integer user value on the geometry. - // Until set_geometry_user_index is called for a given geometry, the index - // stores -1 for that geometry. - int createGeometryUserIndex() { - if (m_geometry_indices == null) - m_geometry_indices = new ArrayList(4); - - // Try getting existing index. Use linear search. We do not expect many - // indices to be created. - for (int i = 0; i < m_geometry_indices.size(); i++) { - if (m_geometry_indices.get(i) == null) { - m_geometry_indices.set(i, - (AttributeStreamOfInt32) AttributeStreamBase - .createIndexStream(0)); - return i; - } - } - - m_geometry_indices.add((AttributeStreamOfInt32) AttributeStreamBase - .createIndexStream(0)); - return m_geometry_indices.size() - 1; - } - - // Removes the geometry user index. - void removeGeometryUserIndex(int index) { - m_geometry_indices.set(index, null); - } - - // Returns the first path of the geometry. - int getFirstPath(int geometry) { - return m_geometry_index_list.getField(geometry, 3); - } - - // Returns the first path of the geometry. - int getLastPath(int geometry) { - return m_geometry_index_list.getField(geometry, 4); - } - - // Point count in a geometry - int getPointCount(int geom) { - return m_geometry_index_list.getField(geom, 5); - } - - // Path count in a geometry - int getPathCount(int geom) { - return m_geometry_index_list.getField(geom, 6); - } - - // Filters degenerate segments in all multipath geometries - // Returns 1 if a non-zero length segment has been removed. -1, if only zero - // length segments have been removed. - // 0 if no segments have been removed. - // When b_remove_last_vertices and the result path is < 3 for polygon or < 2 - // for polyline, it'll be removed. - int filterClosePoints(double tolerance, boolean b_remove_last_vertices, boolean only_polygons) { - int res = 0; - for (int geometry = getFirstGeometry(); geometry != -1; geometry = getNextGeometry(geometry)) { - int gt = getGeometryType(geometry); - if (!Geometry.isMultiPath(gt)) - continue; - if (only_polygons && gt != GeometryType.Polygon) - continue; - - boolean b_polygon = getGeometryType(geometry) == Geometry.GeometryType.Polygon; - - for (int path = getFirstPath(geometry); path != -1; ) { - // We go from the start to the half of the path first, then we - // go from the end to the half of the path. - int vertex_counter = 0; - for (int vertex = getFirstVertex(path); vertex_counter < getPathSize(path) / 2; ) { - int next = getNextVertex(vertex); - if (next == -1) - break; - int vindex = getVertexIndex(vertex); - Segment seg = getSegmentFromIndex_(vindex); - double length = 0; - if (seg != null) { - length = seg.calculateLength2D(); - } else { - int vindex_next = getVertexIndex(next); - length = m_vertices._getShortestDistance(vindex, - vindex_next); - } - - if (length <= tolerance) { - if (length == 0) { - if (res == 0) - res = -1; - } else - res = 1; - - if (next != getLastVertex(path)) { - transferAllDataToTheVertex(next, vertex); - removeVertex(next, true); - } - } else { - vertex = getNextVertex(vertex); - } - vertex_counter++; - } - - int first_vertex = getFirstVertex(path); - for (int vertex = isClosedPath(path) ? first_vertex - : getLastVertex(path); getPathSize(path) > 0; ) { - int prev = getPrevVertex(vertex); - if (prev != -1) { - int vindex_prev = getVertexIndex(prev); - Segment seg = getSegmentFromIndex_(vindex_prev); - double length = 0; - if (seg != null) { - length = seg.calculateLength2D(); - } else { - int vindex = getVertexIndex(vertex); - length = m_vertices._getShortestDistance(vindex, - vindex_prev); - } - - if (length <= tolerance) { - if (length == 0) { - if (res == 0) - res = -1; - } else - res = 1; - - transferAllDataToTheVertex(prev, vertex); - removeVertex(prev, false); - if (first_vertex == prev) - first_vertex = getFirstVertex(path); - } else { - vertex = getPrevVertex(vertex); - if (vertex == first_vertex) - break; - } - } else { - removeVertex(vertex, true);// remove the last vertex in - // the path - if (res == 0) - res = -1; - break; - } - } - - int path_size = getPathSize(path); - if (b_remove_last_vertices - && (b_polygon ? path_size < 3 : path_size < 2)) { - path = removePath(path); - res = path_size > 0 ? 1 : (res == 0 ? -1 : res); - } else - path = getNextPath(path); - } - } - - return res; - } - - // Checks if there are degenerate segments in any of multipath geometries - boolean hasDegenerateSegments(double tolerance) { - for (int geometry = getFirstGeometry(); geometry != -1; geometry = getNextGeometry(geometry)) { - if (!Geometry.isMultiPath(getGeometryType(geometry))) - continue; - - boolean b_polygon = getGeometryType(geometry) == Geometry.GeometryType.Polygon; - - for (int path = getFirstPath(geometry); path != -1; ) { - int path_size = getPathSize(path); - if (b_polygon ? path_size < 3 : path_size < 2) - return true; - - int vertex = getFirstVertex(path); - for (int index = 0; index < path_size; index++) { - int next = getNextVertex(vertex); - if (next == -1) - break; - int vindex = getVertexIndex(vertex); - Segment seg = getSegmentFromIndex_(vindex); - double length = 0; - if (seg != null) { - length = seg.calculateLength2D(); - } else { - int vindex_next = getVertexIndex(next); - length = m_vertices._getShortestDistance(vindex, - vindex_next); - } - - if (length <= tolerance) - return true; - - vertex = next; - } - - path = getNextPath(path); - } - } - - return false; - } - - void transferAllDataToTheVertex(int from_vertex, int to_vertex) { - int vindexFrom = getVertexIndex(from_vertex); - int vindexTo = getVertexIndex(to_vertex); - if (m_weights != null) { - double weight = m_weights.read(vindexFrom); - m_weights.write(vindexTo, weight); - } - - if (m_b_has_attributes) { - // TODO: implement copying of attributes with exception of x and y - // - // for (int i = 0, nattrib = 0; i < nattrib; i++) - // { - // m_vertices->get_attribute - // } - } - // Copy user index data - if (m_indices != null) { - for (int i = 0, n = (int) m_indices.size(); i < n; i++) { - if (m_indices.get(i) != null) { - int value = getUserIndex(from_vertex, i); - if (value != -1) - setUserIndex(to_vertex, i, value); - } - } - } - } - - // Splits segment originating from the origingVertex split_count times at - // splitScalar points and inserts new vertices into the shape. - // The split is not done, when the splitScalar[i] is 0 or 1, or is equal to - // the splitScalar[i - 1]. - // Returns the number of splits actually happend (0 if no splits have - // happend). - int splitSegment(int origin_vertex, double[] split_scalars, int split_count) { - int actual_splits = 0; - int next_vertex = getNextVertex(origin_vertex); - if (next_vertex == -1) - throw GeometryException.GeometryInternalError(); - - int vindex = getVertexIndex(origin_vertex); - int vindex_next = getVertexIndex(next_vertex); - Segment seg = getSegmentFromIndex_(vindex); - double seg_length = seg == null ? m_vertices._getShortestDistance( - vindex, vindex_next) : seg.calculateLength2D(); - double told = 0.0; - for (int i = 0; i < split_count; i++) { - double t = split_scalars[i]; - if (told < t && t < 1.0) { - double f = t; - if (seg != null) { - f = seg_length > 0 ? seg._calculateSubLength(t) - / seg_length : 0.0; - } - - m_vertices._interpolateTwoVertices(vindex, vindex_next, f, - getHelperPoint_());// use this call mainly to - // interpolate the attributes. XYs - // are interpolated incorrectly for - // curves and are recalculated when - // segment is cut below. - int inserted_vertex = insertVertex_( - getPathFromVertex(origin_vertex), next_vertex, - getHelperPoint_()); - actual_splits++; - if (seg != null) { - Segment subseg = seg.cut(told, t); - int prev_vertex = getPrevVertex(inserted_vertex); - int vindex_prev = getVertexIndex(prev_vertex); - setSegmentToIndex_(vindex_prev, subseg); - setXY(inserted_vertex, subseg.getEndXY()); // fix XY - // coordinates - // to be - // parameter - // based - // (interpolate_two_vertices_) - if (i == split_count - 1 || split_scalars[i + 1] == 1.0) {// last - // chance - // to - // set - // last - // split - // segment - // here: - Segment subseg_end = seg.cut(t, 1.0); - setSegmentToIndex_(vindex_prev, subseg_end); - } - } - } - } - - return actual_splits; - } - - // interpolates the attributes for the specified path between from_vertex - // and to_vertex - void interpolateAttributesForClosedPath(int path, int from_vertex, - int to_vertex) { - assert (isClosedPath(path)); - - if (!m_b_has_attributes) - return; - - double sub_length = calculateSubLength2D(path, from_vertex, to_vertex); - - if (sub_length == 0.0) - return; - - int nattr = m_vertex_description.getAttributeCount(); - - for (int iattr = 1; iattr < nattr; iattr++) { - int semantics = m_vertex_description.getSemantics(iattr); - - int interpolation = VertexDescription.getInterpolation(semantics); - if (interpolation == VertexDescription.Interpolation.ANGULAR) - continue; - - int components = VertexDescription.getComponentCount(semantics); - - for (int ordinate = 0; ordinate < components; ordinate++) - interpolateAttributesForClosedPath_(semantics, path, - from_vertex, to_vertex, sub_length, ordinate); - } - - return; - } - - // calculates the length for the specified path between from_vertex and - // to_vertex - double calculateSubLength2D(int path, int from_vertex, int to_vertex) { - int shape_from_index = getVertexIndex(from_vertex); - int shape_to_index = getVertexIndex(to_vertex); - - if (shape_from_index < 0 || shape_to_index > getTotalPointCount() - 1) - throw new IllegalArgumentException("invalid call"); - - if (shape_from_index > shape_to_index) { - if (!isClosedPath(path)) - throw new IllegalArgumentException( - "cannot iterate across an open path"); - } - - double sub_length = 0.0; - - for (int vertex = from_vertex; vertex != to_vertex; vertex = getNextVertex(vertex)) { - int vertex_index = getVertexIndex(vertex); - Segment segment = getSegmentFromIndex_(vertex_index); - if (segment != null) { - sub_length += segment.calculateLength2D(); - } else { - int next_vertex_index = getVertexIndex(getNextVertex(vertex)); - sub_length += m_vertices._getShortestDistance(vertex_index, - next_vertex_index); - } - } - - return sub_length; - } - - // set_point modifies the vertex and associated segments. - void setPoint(int vertex, Point new_coord) { - int vindex = getVertexIndex(vertex); - m_vertices.setPointByVal(vindex, new_coord); - Segment seg = getSegmentFromIndex_(vindex); - if (seg != null) { - seg.setStart(new_coord); - } - int prev = getPrevVertex(vertex); - if (prev != -1) { - int vindex_p = getVertexIndex(prev); - Segment seg_p = getSegmentFromIndex_(vindex_p); - if (seg_p != null) { - seg.setEnd(new_coord); - } - } - } - - // Queries point for a given vertex. - void queryPoint(int vertex, Point point) { - int vindex = getVertexIndex(vertex); - m_vertices.getPointByVal(vindex, point); - // assert(getXY(vertex) == point.getXY()); - } - - // set_xy modifies the vertex and associated segments. - void setXY(int vertex, Point2D new_coord) { - setXY(vertex, new_coord.x, new_coord.y); - } - - // set_xy modifies the vertex and associated segments. - void setXY(int vertex, double new_x, double new_y) { - int vindex = getVertexIndex(vertex); - m_vertices.setXY(vindex, new_x, new_y); - Segment seg = getSegmentFromIndex_(vindex); - if (seg != null) { - seg.setStartXY(new_x, new_y); - } - int prev = getPrevVertex(vertex); - if (prev != -1) { - int vindex_p = getVertexIndex(prev); - Segment seg_p = getSegmentFromIndex_(vindex_p); - if (seg_p != null) { - seg.setEndXY(new_x, new_y); - } - } - } - - Point2D getXY(int vertex) { - Point2D pt = new Point2D(); - int vindex = getVertexIndex(vertex); - pt.setCoords(m_vertices.getXY(vindex)); - return pt; - } - - // Returns the coordinates of the vertex. - void getXY(int vertex, Point2D ptOut) { - int vindex = getVertexIndex(vertex); - ptOut.setCoords(m_vertices.getXY(vindex)); - } - - void getXYWithIndex(int index, Point2D ptOut) { - m_xy_stream.read(2 * index, ptOut); - } - - // Gets the attribute for the given semantics and ordinate. - double getAttributeAsDbl(int semantics, int vertex, int ordinate) { - return m_vertices.getAttributeAsDbl(semantics, getVertexIndex(vertex), - ordinate); - } - - // Sets the attribute for the given semantics and ordinate. - void setAttribute(int semantics, int vertex, int ordinate, double value) { - m_vertices.setAttribute(semantics, getVertexIndex(vertex), ordinate, - value); - } - - // Sets the attribute for the given semantics and ordinate. - void setAttribute(int semantics, int vertex, int ordinate, int value) { - m_vertices.setAttribute(semantics, getVertexIndex(vertex), ordinate, - value); - } - - // Returns a reference to the vertex description - VertexDescription getVertexDescription() { - return m_vertex_description; - } - - int getMinPathVertexY(int path) { - int first_vert = getFirstVertex(path); - int minv = first_vert; - int vert = getNextVertex(first_vert); - while (vert != -1 && vert != first_vert) { - if (compareVerticesSimpleY_(vert, minv) < 0) - minv = vert; - vert = getNextVertex(vert); - } - return minv; - } - - // Returns an index value for the vertex inside of the underlying array of - // vertices. - // This index is for the use with the get_xy_with_index. get_xy is - // equivalent to calling get_vertex_index and get_xy_with_index. - int getVertexIndex(int vertex) { - return m_vertex_index_list.getField(vertex, 0); - } - - // Returns the y coordinate of the vertex. - double getY(int vertex) { - Point2D pt = new Point2D(); - getXY(vertex, pt); - return pt.y; - } - - // returns True if xy coordinates at vertices are equal. - boolean isEqualXY(int vertex_1, int vertex_2) { - int vindex1 = getVertexIndex(vertex_1); - int vindex2 = getVertexIndex(vertex_2); - return m_vertices.getXY(vindex1).isEqual(m_vertices.getXY(vindex2)); - } - - // returns True if xy coordinates at vertices are equal. - boolean isEqualXY(int vertex, Point2D pt) { - int vindex = getVertexIndex(vertex); - return m_vertices.getXY(vindex).isEqual(pt); - } - - // Sets weight to the vertex. Weight is used by clustering and cracking. - void setWeight(int vertex, double weight) { - if (weight < 1.0) - weight = 1.0; - - if (m_weights == null) { - if (weight == 1.0) - return; - - m_weights = (AttributeStreamOfDbl) (AttributeStreamBase - .createDoubleStream(m_vertices.getPointCount(), 1.0)); - } - - int vindex = getVertexIndex(vertex); - if (vindex >= m_weights.size()) { - m_weights.resize(vindex + 1, 1.0); - } - - m_weights.write(vindex, weight); - } - - double getWeight(int vertex) { - int vindex = getVertexIndex(vertex); - if (m_weights == null || vindex >= m_weights.size()) - return 1.0; - - return m_weights.read(vindex); - } - - // Removes associated weights - void removeWeights() { - m_weights = null; - } - - // Sets value to the given user index. - void setUserIndex(int vertex, int index, int value) { - // CHECKVERTEXHANDLE(vertex); - AttributeStreamOfInt32 stream = m_indices.get(index); - // assert(get_prev_vertex(vertex) != -0x7eadbeaf);//using deleted vertex - int vindex = getVertexIndex(vertex); - if (stream.size() < m_vertices.getPointCount()) - stream.resize(m_vertices.getPointCount(), -1); - stream.write(vindex, value); - } - - int getUserIndex(int vertex, int index) { - // CHECKVERTEXHANDLE(vertex); - int vindex = getVertexIndex(vertex); - AttributeStreamOfInt32 stream = m_indices.get(index); - if (vindex < stream.size()) { - int val = stream.read(vindex); - return val; - } else - return -1; - } - - // Creates new user index. The index have random values. The index allows to - // store an integer user value on the vertex. - int createUserIndex() { - if (m_indices == null) - m_indices = new ArrayList(0); - - // Try getting existing index. Use linear search. We do not expect many - // indices to be created. - for (int i = 0; i < m_indices.size(); i++) { - if (m_indices.get(i) == null) { - m_indices.set(i, (AttributeStreamOfInt32) AttributeStreamBase - .createIndexStream(0, -1)); - return i; - } - } - - m_indices.add((AttributeStreamOfInt32) AttributeStreamBase - .createIndexStream(0, -1)); - return m_indices.size() - 1; - } - - // Removes the user index. - void removeUserIndex(int index) { - m_indices.set(index, null); - } - - // Returns segment, connecting currentVertex and next vertex. Returns NULL - // if it is a Line. - Segment getSegment(int vertex) { - if (m_segments != null) { - int vindex = getVertexIndex(vertex); - return m_segments.get(vindex); - } - return null; - } - - // Returns a straight line that connects this and next vertices. No - // attributes. Returns false if no next vertex exists (end of polyline - // part). - // Can be used together with get_segment. - boolean queryLineConnector(int vertex, Line line) { - int next = getNextVertex(vertex); - if (next == -1) - return false; - - if (!m_b_has_attributes) { - Point2D pt = new Point2D(); - getXY(vertex, pt); - line.setStartXY(pt); - getXY(next, pt); - line.setEndXY(pt); - } else { - Point pt = new Point(); - queryPoint(vertex, pt); - line.setStart(pt); - queryPoint(next, pt); - line.setEnd(pt); - } - - return true; - } - - // Inserts an empty path before the given one. If before_path is -1, adds - // path at the end. - int insertPath(int geometry, int before_path) { - int prev = -1; - - if (before_path != -1) { - if (geometry != getGeometryFromPath(before_path)) - throw GeometryException.GeometryInternalError(); - - prev = getPrevPath(before_path); - } else - prev = getLastPath(geometry); - - int newpath = newPath_(geometry); - if (before_path != -1) - setPrevPath_(before_path, newpath); - - setNextPath_(newpath, before_path); - setPrevPath_(newpath, prev); - if (prev != -1) - setNextPath_(prev, newpath); - else - setFirstPath_(geometry, newpath); - - if (before_path == -1) - setLastPath_(geometry, newpath); - - setGeometryPathCount_(geometry, getPathCount(geometry) + 1); - return newpath; - } - - int insertClosedPath_(int geometry, int before_path, int first_vertex, int checked_vertex, boolean[] contains_checked_vertex) { - int path = insertPath(geometry, -1); - int path_size = 0; - int vertex = first_vertex; - boolean contains = false; - - while (true) { - if (vertex == checked_vertex) - contains = true; - - setPathToVertex_(vertex, path); - path_size++; - int next = getNextVertex(vertex); - assert (getNextVertex(getPrevVertex(vertex)) == vertex); - if (next == first_vertex) - break; - - vertex = next; - } - - setClosedPath(path, true); - setPathSize_(path, path_size); - if (contains) - first_vertex = checked_vertex; - - setFirstVertex_(path, first_vertex); - setLastVertex_(path, getPrevVertex(first_vertex)); - setRingAreaValid_(path, false); - - if (contains_checked_vertex != null) { - contains_checked_vertex[0] = contains; - } - - return path; - } - - - // Removes a path, gets rid of all its vertices, and returns the next one - int removePath(int path) { - int prev = getPrevPath(path); - int next = getNextPath(path); - int geometry = getGeometryFromPath(path); - if (prev != -1) - setNextPath_(prev, next); - else { - setFirstPath_(geometry, next); - } - if (next != -1) - setPrevPath_(next, prev); - else { - setLastPath_(geometry, prev); - } - - clearPath(path); - - setGeometryPathCount_(geometry, getPathCount(geometry) - 1); - freePath_(path); - return next; - } - - // Clears all vertices from the path - void clearPath(int path) { - int first_vertex = getFirstVertex(path); - if (first_vertex != -1) { - // TODO: can ve do this in one shot? - int vertex = first_vertex; - for (int i = 0, n = getPathSize(path); i < n; i++) { - int v = vertex; - vertex = getNextVertex(vertex); - freeVertex_(v); - } - int geometry = getGeometryFromPath(path); - setGeometryVertexCount_(geometry, getPointCount(geometry) - - getPathSize(path)); - } - setPathSize_(path, 0); - } - - // Returns the next path (-1 if there are no more paths in the geometry). - int getNextPath(int currentPath) { - return m_path_index_list.getField(currentPath, 2); - } - - // Returns the previous path (-1 if there are no more paths in the - // geometry). - int getPrevPath(int currentPath) { - return m_path_index_list.getField(currentPath, 1); - } - - // Returns the number of vertices in the path. - int getPathSize(int path) { - return m_path_index_list.getField(path, 3); - } - - // Returns True if the path is closed. - boolean isClosedPath(int path) { - return (getPathFlags_(path) & PathFlags_.closedPath) != 0; - } - - // Makes path closed. Closed paths are circular lists. get_next_vertex - // always succeeds - void setClosedPath(int path, boolean b_yes_no) { - if (isClosedPath(path) == b_yes_no) - return; - if (getPathSize(path) > 0) { - int first = getFirstVertex(path); - int last = getLastVertex(path); - if (b_yes_no) { - // make a circular list - setNextVertex_(last, first); - setPrevVertex_(first, last); - // set segment to NULL (just in case) - int vindex = getVertexIndex(last); - setSegmentToIndex_(vindex, null); - } else { - setNextVertex_(last, -1); - setPrevVertex_(first, -1); - int vindex = getVertexIndex(last); - setSegmentToIndex_(vindex, null); - } - } - - int oldflags = getPathFlags_(path); - int flags = (oldflags | (int) PathFlags_.closedPath) - - (int) PathFlags_.closedPath;// clear the bit; - setPathFlags_(path, flags - | (b_yes_no ? (int) PathFlags_.closedPath : 0)); - } - - // Closes all paths of the geometry (has to be a polyline or polygon). - void closeAllPaths(int geometry) { - if (getGeometryType(geometry) == Geometry.GeometryType.Polygon) - return; - if (!Geometry.isLinear(getGeometryType(geometry))) - throw GeometryException.GeometryInternalError(); - - for (int path = getFirstPath(geometry); path != -1; path = getNextPath(path)) { - setClosedPath(path, true); - } - } - - // Returns geometry from path - int getGeometryFromPath(int path) { - return m_path_index_list.getField(path, 7); - } - - // Returns True if the path is exterior. - boolean isExterior(int path) { - return (getPathFlags_(path) & PathFlags_.exteriorPath) != 0; - } - - // Sets exterior flag - void setExterior(int path, boolean b_yes_no) { - int oldflags = getPathFlags_(path); - int flags = (oldflags | (int) PathFlags_.exteriorPath) - - (int) PathFlags_.exteriorPath;// clear the bit; - setPathFlags_(path, flags - | (b_yes_no ? (int) PathFlags_.exteriorPath : 0)); - } - - // Returns the ring area - double getRingArea(int path) { - if (isRingAreaValid_(path)) - return m_path_areas.get(getPathIndex_(path)); - - Line line = new Line(); - int vertex = getFirstVertex(path); - if (vertex == -1) - return 0; - Point2D pt0 = new Point2D(); - getXY(vertex, pt0); - double area = 0; - for (int i = 0, n = getPathSize(path); i < n; i++, vertex = getNextVertex(vertex)) { - Segment seg = getSegment(vertex); - if (seg == null) { - if (!queryLineConnector(vertex, line)) - continue; - - seg = line; - } - - double a = seg._calculateArea2DHelper(pt0.x, pt0.y); - area += a; - } - - setRingAreaValid_(path, true); - m_path_areas.set(getPathIndex_(path), area); - - return area; - } - - // Sets value to the given user index on a path. - void setPathUserIndex(int path, int index, int value) { - AttributeStreamOfInt32 stream = m_pathindices.get(index); - int pindex = getPathIndex_(path); - if (stream.size() < m_path_areas.size()) - stream.resize(m_path_areas.size(), -1); - stream.write(pindex, value); - } - - // Returns the value of the given user index of a path - int getPathUserIndex(int path, int index) { - int pindex = getPathIndex_(path); - AttributeStreamOfInt32 stream = m_pathindices.get(index); - if (pindex < stream.size()) - return stream.read(pindex); - else - return -1; - } - - // Creates new user index on a path. The index have random values. The path - // index allows to store an integer user value on the path. - int createPathUserIndex() { - if (m_pathindices == null) - m_pathindices = new ArrayList(0); - // Try getting existing index. Use linear search. We do not expect many - // indices to be created. - for (int i = 0; i < m_pathindices.size(); i++) { - if (m_pathindices.get(i) == null) { - m_pathindices.set(i, - (AttributeStreamOfInt32) (AttributeStreamBase - .createIndexStream(0))); - return i; - } - } - - m_pathindices.add((AttributeStreamOfInt32) (AttributeStreamBase - .createIndexStream(0))); - return (int) (m_pathindices.size() - 1); - } - - // Removes the path user index. - void removePathUserIndex(int index) { - m_pathindices.set(index, null); - } - - // Moves a path from any geometry before a given path in the dst_geom - // geometry. The path_handle do not change after the operation. - // before_path can be -1, then the path is moved to the end of the dst_geom. - void movePath(int geom, int before_path, int path_to_move) { - if (path_to_move == -1) - throw new IllegalArgumentException(); - - if (before_path == path_to_move) - return; - - int next = getNextPath(path_to_move); - int prev = getPrevPath(path_to_move); - int geom_src = getGeometryFromPath(path_to_move); - if (prev == -1) { - setFirstPath_(geom_src, next); - } else { - setNextPath_(prev, next); - } - - if (next == -1) { - setLastPath_(geom_src, prev); - } else { - setPrevPath_(next, prev); - } - - setGeometryVertexCount_(geom_src, getPointCount(geom_src) - - getPathSize(path_to_move)); - setGeometryPathCount_(geom_src, getPathCount(geom_src) - 1); - - if (before_path == -1) - prev = getLastPath(geom); - else - prev = getPrevPath(before_path); - - setPrevPath_(path_to_move, prev); - setNextPath_(path_to_move, before_path); - if (before_path == -1) - setLastPath_(geom, path_to_move); - else - setPrevPath_(before_path, path_to_move); - if (prev == -1) - setFirstPath_(geom, path_to_move); - else - setNextPath_(prev, path_to_move); - setGeometryVertexCount_(geom, getPointCount(geom) - + getPathSize(path_to_move)); - setGeometryPathCount_(geom, getPathCount(geom) + 1); - setPathGeometry_(path_to_move, geom); - } - - // Adds a copy of a vertex to a path. Connects with a straight line. - // Returns new vertex handle. - int addVertex(int path, int vertex) { - m_vertices.getPointByVal(getVertexIndex(vertex), getHelperPoint_()); - return insertVertex_(path, -1, getHelperPoint_()); - } - - // Removes vertex from path. Uses either left or right segments to - // reconnect. Returns next vertex after erased one. - int removeVertex(int vertex, boolean b_left_segment) { - int path = getPathFromVertex(vertex); - int prev = getPrevVertex(vertex); - int next = getNextVertex(vertex); - if (prev != -1) - setNextVertex_(prev, next); - - int path_size = getPathSize(path); - - if (vertex == getFirstVertex(path)) { - setFirstVertex_(path, path_size > 1 ? next : -1); - } - - if (next != -1) - setPrevVertex_(next, prev); - - if (vertex == getLastVertex(path)) { - setLastVertex_(path, path_size > 1 ? prev : -1); - } - - if (prev != -1 && next != -1) { - int vindex_prev = getVertexIndex(prev); - int vindex_next = getVertexIndex(next); - if (b_left_segment) { - Segment seg = getSegmentFromIndex_(vindex_prev); - if (seg != null) { - Point2D pt = new Point2D(); - m_vertices.getXY(vindex_next, pt); - seg.setEndXY(pt); - } - } else { - int vindex_erased = getVertexIndex(vertex); - Segment seg = getSegmentFromIndex_(vindex_erased); - setSegmentToIndex_(vindex_prev, seg); - if (seg != null) { - Point2D pt = m_vertices.getXY(vindex_prev); - seg.setStartXY(pt); - } - } - } - - setPathSize_(path, path_size - 1); - int geometry = getGeometryFromPath(path); - setGeometryVertexCount_(geometry, getPointCount(geometry) - 1); - freeVertex_(vertex); - return next; - } - - // Returns first vertex of the given path. - int getFirstVertex(int path) { - return m_path_index_list.getField(path, 4); - } - - // Returns last vertex of the given path. For the closed paths - // get_next_vertex for the last vertex returns the first vertex. - int getLastVertex(int path) { - return m_path_index_list.getField(path, 5); - } - - // Returns next vertex. Closed paths are circular lists, so get_next_vertex - // always returns vertex. Open paths return -1 for last vertex. - int getNextVertex(int currentVertex) { - return m_vertex_index_list.getField(currentVertex, 2); - } - - // Returns previous vertex. Closed paths are circular lists, so - // get_prev_vertex always returns vertex. Open paths return -1 for first - // vertex. - int getPrevVertex(int currentVertex) { - return m_vertex_index_list.getField(currentVertex, 1); - } - - int getPrevVertex(int currentVertex, int dir) { - return dir > 0 ? m_vertex_index_list.getField(currentVertex, 1) : m_vertex_index_list.getField(currentVertex, 2); - } - - int getNextVertex(int currentVertex, int dir) { - return dir > 0 ? m_vertex_index_list.getField(currentVertex, 2) : m_vertex_index_list.getField(currentVertex, 1); - } - - // Returns a path the vertex belongs to. - int getPathFromVertex(int vertex) { - return m_vertex_index_list.getField(vertex, 3); - } - - // Adds a copy of the point to a path. Connects with a straight line. - // Returns new vertex handle. - int addPoint(int path, Point point) { - return insertVertex_(path, -1, point); - } - - // Vertex iterator allows to go through all vertices of the Edit_shape. - static class VertexIterator { - private EditShape m_parent; - private int m_geometry; - private int m_path; - private int m_vertex; - private int m_first_vertex; - private int m_index; - boolean m_b_first; - boolean m_b_skip_mulit_points; - - private VertexIterator(EditShape parent, int geometry, int path, - int vertex, int first_vertex, int index, - boolean b_skip_mulit_points) { - m_parent = parent; - m_geometry = geometry; - m_path = path; - m_vertex = vertex; - m_index = index; - m_b_skip_mulit_points = b_skip_mulit_points; - m_first_vertex = first_vertex; - m_b_first = true; - } - - int moveToNext_() { - if (m_b_first) { - m_b_first = false; - return m_vertex; - } - - if (m_vertex != -1) { - m_vertex = m_parent.getNextVertex(m_vertex); - m_index++; - if (m_vertex != -1 && m_vertex != m_first_vertex) - return m_vertex; - - return moveToNextHelper_();// separate into another function for - // inlining - } - - return -1; - } - - int moveToNextHelper_() { - m_path = m_parent.getNextPath(m_path); - m_index = 0; - while (m_geometry != -1) { - for (; m_path != -1; m_path = m_parent.getNextPath(m_path)) { - m_vertex = m_parent.getFirstVertex(m_path); - m_first_vertex = m_vertex; - if (m_vertex != -1) - return m_vertex; - } - - m_geometry = m_parent.getNextGeometry(m_geometry); - if (m_geometry == -1) - break; - - if (m_b_skip_mulit_points - && !Geometry.isMultiPath(m_parent - .getGeometryType(m_geometry))) { - continue; - } - - m_path = m_parent.getFirstPath(m_geometry); - } - - return -1; - } - - // moves to next vertex. Returns -1 when there are no more vertices. - VertexIterator(VertexIterator source) { - m_parent = source.m_parent; - m_geometry = source.m_geometry; - m_path = source.m_path; - m_vertex = source.m_vertex; - m_index = source.m_index; - m_b_skip_mulit_points = source.m_b_skip_mulit_points; - m_first_vertex = source.m_first_vertex; - m_b_first = true; - } - - public int next() { - return moveToNext_(); - } - - public int currentGeometry() { - assert (m_vertex != -1); - return m_geometry; - } - - public int currentPath() { - assert (m_vertex != -1); - return m_path; - } - - public static VertexIterator create_(EditShape parent, int geometry, - int path, int vertex, int first_vertex, int index, - boolean b_skip_mulit_points) { - return new VertexIterator(parent, geometry, path, vertex, - first_vertex, index, b_skip_mulit_points); - } - } - - ; - - // Returns the vertex iterator that allows iteration through all vertices of - // all paths of all geometries. - VertexIterator queryVertexIterator() { - return queryVertexIterator(false); - } - - VertexIterator queryVertexIterator(VertexIterator source) { - return new VertexIterator(source); - } - - // Returns the vertex iterator that allows iteration through all vertices of - // all paths of all geometries. - // If bSkipMultiPoints is true, then the iterator will skip the Multi_point - // vertices - VertexIterator queryVertexIterator(boolean b_skip_multi_points) { - int geometry = -1; - int path = -1; - int vertex = -1; - int first_vertex = -1; - int index = 0; - boolean bFound = false; - - for (geometry = getFirstGeometry(); geometry != -1; geometry = getNextGeometry(geometry)) { - if (b_skip_multi_points - && !Geometry.isMultiPath(getGeometryType(geometry))) - continue; - - for (path = getFirstPath(geometry); path != -1; path = getNextPath(path)) { - vertex = getFirstVertex(path); - first_vertex = vertex; - index = 0; - if (vertex == -1) - continue; - - bFound = true; - break; - } - - if (bFound) - break; - } - - return VertexIterator.create_(this, geometry, path, vertex, - first_vertex, index, b_skip_multi_points); - } - - // Applies affine transformation - void applyTransformation(Transformation2D transform) { - m_vertices_mp.applyTransformation(transform); - if (m_segments != null) { - for (int i = 0, n = m_segments.size(); i < n; i++) { - if (m_segments.get(i) != null) { - m_segments.get(i).applyTransformation(transform); - } - } - } - } - - void interpolateAttributesForClosedPath_(int semantics, int path, - int from_vertex, int to_vertex, double sub_length, int ordinate) { - if (from_vertex == to_vertex) - return; - - double from_attribute = getAttributeAsDbl(semantics, from_vertex, - ordinate); - double to_attribute = getAttributeAsDbl(semantics, to_vertex, ordinate); - double cumulative_length = 0.0; - double prev_interpolated_attribute = from_attribute; - - for (int vertex = from_vertex; vertex != to_vertex; vertex = getNextVertex(vertex)) { - setAttribute(semantics, vertex, ordinate, - prev_interpolated_attribute); - - int vertex_index = getVertexIndex(vertex); - Segment segment = getSegmentFromIndex_(vertex_index); - double segment_length; - - if (segment != null) { - segment_length = segment.calculateLength2D(); - } else { - int next_vertex_index = getVertexIndex(getNextVertex(vertex)); - segment_length = m_vertices._getShortestDistance(vertex_index, - next_vertex_index); - } - cumulative_length += segment_length; - double t = cumulative_length / sub_length; - prev_interpolated_attribute = MathUtils.lerp(from_attribute, to_attribute, t); - } - - return; - } - - void SetGeometryType_(int geom, int gt) { - m_geometry_index_list.setField(geom, 2, gt); - } - - void splitSegment_(int origin_vertex, SegmentIntersector intersector, - int intersector_index, boolean b_forward) { - if (b_forward) { - splitSegmentForward_(origin_vertex, intersector, intersector_index); - } else { - splitSegmentBackward_(origin_vertex, intersector, intersector_index); - } - } - - void setPrevVertex_(int vertex, int prev) { - m_vertex_index_list.setField(vertex, 1, prev); - } - - void setNextVertex_(int vertex, int next) { - m_vertex_index_list.setField(vertex, 2, next); - } - - void setPathToVertex_(int vertex, int path) { - m_vertex_index_list.setField(vertex, 3, path); - } - - void setPathSize_(int path, int size) { - m_path_index_list.setField(path, 3, size); - } - - void setFirstVertex_(int path, int first_vertex) { - m_path_index_list.setField(path, 4, first_vertex); - } - - void setLastVertex_(int path, int last_vertex) { - m_path_index_list.setField(path, 5, last_vertex); - } - - void setGeometryPathCount_(int geom, int path_count) { - m_geometry_index_list.setField(geom, 6, path_count); - } - - void setGeometryVertexCount_(int geom, int vertex_count) { - m_geometry_index_list.setField(geom, 5, vertex_count); - } - - boolean ringParentageCheckInternal_(int vertex_1, int vertex_2) { - if (vertex_1 == vertex_2) - return true; - int vprev_1 = vertex_1; - int vprev_2 = vertex_2; - for (int v_1 = getNextVertex(vertex_1), v_2 = getNextVertex(vertex_2); v_1 != vertex_1 - && v_2 != vertex_2; v_1 = getNextVertex(v_1), v_2 = getNextVertex(v_2)) { - if (v_1 == vertex_2) - return true; - if (v_2 == vertex_1) - return true; - - assert (getPrevVertex(v_1) == vprev_1);// detect malformed list - assert (getPrevVertex(v_2) == vprev_2);// detect malformed list - vprev_1 = v_1; - vprev_2 = v_2; - } - - return false; - } - - void reverseRingInternal_(int vertex) { - int v = vertex; - do { - int prev = getPrevVertex(v); - int next = getNextVertex(v); - setNextVertex_(v, prev); - setPrevVertex_(v, next); - v = next; - } while (v != vertex); - // Path's last becomes invalid. Do not attempt to fix it here, because - // this is not the intent of the method - // Note: only last is invalid. other things sould not change. - } - - void setTotalPointCount_(int count) { - m_point_count = count; - } - - void removePathOnly_(int path) { - int prev = getPrevPath(path); - int next = getNextPath(path); - int geometry = getGeometryFromPath(path); - if (prev != -1) - setNextPath_(prev, next); - else { - setFirstPath_(geometry, next); - } - if (next != -1) - setPrevPath_(next, prev); - else { - setLastPath_(geometry, prev); - } - - setFirstVertex_(path, -1); - setLastVertex_(path, -1); - freePath_(path); - } - - // void DbgVerifyIntegrity(int vertex); - // void dbg_verify_vertex_counts(); - int removeVertexInternal_(int vertex, boolean b_left_segment) { - int prev = getPrevVertex(vertex); - int next = getNextVertex(vertex); - if (prev != -1) - setNextVertex_(prev, next); - - if (next != -1) - setPrevVertex_(next, prev); - - if (prev != -1 && next != -1) { - int vindex_prev = getVertexIndex(prev); - int vindex_next = getVertexIndex(next); - if (b_left_segment) { - Segment seg = getSegmentFromIndex_(vindex_prev); - if (seg != null) { - Point2D pt = new Point2D(); - m_vertices.getXY(vindex_next, pt); - seg.setEndXY(pt); - } - } else { - int vindex_erased = getVertexIndex(vertex); - Segment seg = getSegmentFromIndex_(vindex_erased); - setSegmentToIndex_(vindex_prev, seg); - if (seg != null) { - Point2D pt = new Point2D(); - m_vertices.getXY(vindex_prev, pt); - seg.setStartXY(pt); - } - } - } - freeVertex_(vertex); - return next; - } - - boolean isRingAreaValid_(int path) { - return (getPathFlags_(path) & PathFlags_.ringAreaValid) != 0; - } - - // Sets exterior flag - void setRingAreaValid_(int path, boolean b_yes_no) { - int oldflags = getPathFlags_(path); - int flags = (oldflags | (int) PathFlags_.ringAreaValid) - - (int) PathFlags_.ringAreaValid;// clear the bit; - setPathFlags_(path, flags - | (b_yes_no ? (int) PathFlags_.ringAreaValid : 0)); - } - - int compareVerticesSimpleY_(int v_1, int v_2) { - Point2D pt_1 = new Point2D(); - getXY(v_1, pt_1); - Point2D pt_2 = new Point2D(); - getXY(v_2, pt_2); - int res = pt_1.compare(pt_2); - return res; - } - - int compareVerticesSimpleX_(int v_1, int v_2) { - Point2D pt_1 = new Point2D(); - getXY(v_1, pt_1); - Point2D pt_2 = new Point2D(); - getXY(v_2, pt_2); - int res = pt_1.compare(pt_2); - return res; - } - - public static class SimplificatorVertexComparerY extends - AttributeStreamOfInt32.IntComparator { - EditShape parent; - - SimplificatorVertexComparerY(EditShape parent_) { - parent = parent_; - } - - @Override - public int compare(int i_1, int i_2) { - return parent.compareVerticesSimpleY_(i_1, i_2); - } - } - - public static class SimplificatorVertexComparerX extends - AttributeStreamOfInt32.IntComparator { - EditShape parent; - - SimplificatorVertexComparerX(EditShape parent_) { - parent = parent_; - } - - @Override - public int compare(int i_1, int i_2) { - return parent.compareVerticesSimpleX_(i_1, i_2); - } - } - - // void sort_vertices_simple_by_y_heap_merge(Dynamic_array& points, - // const Dynamic_array* geoms); - - void sortVerticesSimpleByY_(AttributeStreamOfInt32 points, int begin_, - int end_) { - if (m_bucket_sort == null) - m_bucket_sort = new BucketSort(); - m_bucket_sort.sort(points, begin_, end_, new EditShapeBucketSortHelper( - this)); - } - - void sortVerticesSimpleByYHelper_(AttributeStreamOfInt32 points, - int begin_, int end_) { - points.Sort(begin_, end_, new SimplificatorVertexComparerY(this)); - } - - void sortVerticesSimpleByX_(AttributeStreamOfInt32 points, int begin_, - int end_) { - points.Sort(begin_, end_, new SimplificatorVertexComparerX(this)); - } - - // Approximate size of the structure in memory. - // The estimated size can be very slightly less than the actual size. - // int estimate_memory_size() const; - - boolean hasPointFeatures() { - for (int geometry = getFirstGeometry(); geometry != -1; geometry = getNextGeometry(geometry)) { - if (!Geometry.isMultiPath(getGeometryType(geometry))) - return true; - } - return false; - } - - void swapGeometry(int geom1, int geom2) { - int first_path1 = getFirstPath(geom1); - int first_path2 = getFirstPath(geom2); - int last_path1 = getLastPath(geom1); - int last_path2 = getLastPath(geom2); - - for (int path = getFirstPath(geom1); path != -1; path = getNextPath(path)) { - setPathGeometry_(path, geom2); - } - - for (int path = getFirstPath(geom2); path != -1; path = getNextPath(path)) { - setPathGeometry_(path, geom1); - } - - setFirstPath_(geom1, first_path2); - setFirstPath_(geom2, first_path1); - setLastPath_(geom1, last_path2); - setLastPath_(geom2, last_path1); - - int vc1 = getPointCount(geom1); - int pc1 = getPathCount(geom1); - int vc2 = getPointCount(geom2); - int pc2 = getPathCount(geom2); - - setGeometryVertexCount_(geom1, vc2); - setGeometryVertexCount_(geom2, vc1); - setGeometryPathCount_(geom1, pc2); - setGeometryPathCount_(geom2, pc1); - - int gt1 = m_geometry_index_list.getField(geom1, 2); - int gt2 = m_geometry_index_list.getField(geom2, 2); - m_geometry_index_list.setField(geom1, 2, gt2); - m_geometry_index_list.setField(geom2, 2, gt1); - } + interface PathFlags_ { + static final int closedPath = 1; + static final int exteriorPath = 2; + static final int ringAreaValid = 4; + } + + private int m_geometryCount; + private int m_path_count; + private int m_point_count; + private int m_first_geometry; + private int m_last_geometry; + + private StridedIndexTypeCollection m_vertex_index_list; + + // ****************Vertex Data****************** + private MultiPoint m_vertices_mp; // vertex coordinates are stored here + // Attribute_stream_of_index_type::SPtr m_indexRemap; + private MultiPointImpl m_vertices; // Internals of m_vertices_mp + AttributeStreamOfDbl m_xy_stream; // The xy stream of the m_vertices. + VertexDescription m_vertex_description;// a shortcut to the vertex + // description. + boolean m_b_has_attributes; // a short cut to know if we have something in + // addition to x and y. + + ArrayList m_segments;// may be NULL if all segments a Lines, + // otherwise contains NULLs for Line + // segments. Curves are not NULL. + AttributeStreamOfDbl m_weights;// may be NULL if no weights are provided. + // NULL weights assumes weight value of 1. + ArrayList m_indices;// user indices are here + // ****************End Vertex Data************** + StridedIndexTypeCollection m_path_index_list; // doubly connected list. Path + // index into the Path Data + // arrays, Prev path, next + // path. + // ******************Path Data****************** + AttributeStreamOfDbl m_path_areas; + AttributeStreamOfDbl m_path_lengths; + // Block_array::SPtr m_path_envelopes; + ArrayList m_pathindices;// path user indices are + // here + // *****************End Path Data*************** + StridedIndexTypeCollection m_geometry_index_list; + ArrayList m_geometry_indices;// geometry user + // indices are here + + // *********** Helpers for Bucket sort************** + static class EditShapeBucketSortHelper extends ClassicSort { + EditShape m_shape; + + EditShapeBucketSortHelper(EditShape shape) { + m_shape = shape; + } + + @Override + public void userSort(int begin, int end, AttributeStreamOfInt32 indices) { + m_shape.sortVerticesSimpleByYHelper_(indices, begin, end); + } + + @Override + public double getValue(int index) { + return m_shape.getY(index); + } + } + + ; + + BucketSort m_bucket_sort; + + // Envelope::SPtr m_envelope; //the BBOX for all attributes + Point m_helper_point; // a helper point for intermediate operations + + Segment getSegmentFromIndex_(int vindex) { + return m_segments != null ? m_segments.get(vindex) : null; + } + + void setSegmentToIndex_(int vindex, Segment seg) { + if (m_segments == null) { + if (seg == null) + return; + m_segments = new ArrayList(); + for (int i = 0, n = m_vertices.getPointCount(); i < n; i++) + m_segments.add(null); + } + m_segments.set(vindex, seg); + } + + void setPrevPath_(int path, int prev) { + m_path_index_list.setField(path, 1, prev); + } + + void setNextPath_(int path, int next) { + m_path_index_list.setField(path, 2, next); + } + + void setPathFlags_(int path, int flags) { + m_path_index_list.setField(path, 6, flags); + } + + int getPathFlags_(int path) { + return m_path_index_list.getField(path, 6); + } + + void setPathGeometry_(int path, int geom) { + m_path_index_list.setField(path, 7, geom); + } + + int getPathIndex_(int path) { + return m_path_index_list.getField(path, 0); + } + + void setNextGeometry_(int geom, int next) { + m_geometry_index_list.setField(geom, 1, next); + } + + void setPrevGeometry_(int geom, int prev) { + m_geometry_index_list.setField(geom, 0, prev); + } + + int getGeometryIndex_(int geom) { + return m_geometry_index_list.getField(geom, 7); + } + + int getFirstPath_(int geom) { + return m_geometry_index_list.getField(geom, 3); + } + + void setFirstPath_(int geom, int firstPath) { + m_geometry_index_list.setField(geom, 3, firstPath); + } + + void setLastPath_(int geom, int path) { + m_geometry_index_list.setField(geom, 4, path); + } + + int newGeometry_(int gt) { + // Index_type index = m_first_free_geometry; + if (m_geometry_index_list == null) + m_geometry_index_list = new StridedIndexTypeCollection(8); + + int index = m_geometry_index_list.newElement(); + // m_geometry_index_list.set(index + 0, -1);//prev + // m_geometry_index_list.set(index + 1, -1);//next + m_geometry_index_list.setField(index, 2, gt);// Geometry_type + // m_geometry_index_list.set(index + 3, -1);//first path + // m_geometry_index_list.set(index + 4, -1);//last path + m_geometry_index_list.setField(index, 5, 0);// point count + m_geometry_index_list.setField(index, 6, 0);// path count + m_geometry_index_list.setField(index, 7, + m_geometry_index_list.elementToIndex(index));// geometry index + + return index; + } + + void freeGeometry_(int geom) { + m_geometry_index_list.deleteElement(geom); + } + + int newPath_(int geom) { + if (m_path_index_list == null) { + m_path_index_list = new StridedIndexTypeCollection(8); + m_vertex_index_list = new StridedIndexTypeCollection(5); + m_path_areas = new AttributeStreamOfDbl(0); + m_path_lengths = new AttributeStreamOfDbl(0); + } + + int index = m_path_index_list.newElement(); + int pindex = m_path_index_list.elementToIndex(index); + m_path_index_list.setField(index, 0, pindex);// size + // m_path_index_list.set(index + 1, -1);//prev + // m_path_index_list.set(index + 2, -1);//next + m_path_index_list.setField(index, 3, 0);// size + // m_path_index_list.set(index + 4, -1);//first vertex handle + // m_path_index_list.set(index + 5, -1);//last vertex handle + m_path_index_list.setField(index, 6, 0);// path flags + setPathGeometry_(index, geom); + if (pindex >= m_path_areas.size()) { + int sz = pindex < 16 ? 16 : (pindex * 3) / 2; + m_path_areas.resize(sz); + m_path_lengths.resize(sz); + // if (m_path_envelopes) + // m_path_envelopes.resize(sz); + } + m_path_areas.set(pindex, 0); + m_path_lengths.set(pindex, 0); + // if (m_path_envelopes) + // m_path_envelopes.set(pindex, nullptr); + + m_path_count++; + return index; + } + + void freePath_(int path) { + m_path_index_list.deleteElement(path); + m_path_count--; + } + + void freeVertex_(int vertex) { + m_vertex_index_list.deleteElement(vertex); + m_point_count--; + } + + int newVertex_(int vindex) { + assert (vindex >= 0 || vindex == -1);// vindex is not a handle + + if (m_path_index_list == null) { + m_path_index_list = new StridedIndexTypeCollection(8); + m_vertex_index_list = new StridedIndexTypeCollection(5); + m_path_areas = new AttributeStreamOfDbl(0); + m_path_lengths = new AttributeStreamOfDbl(0); + } + + int index = m_vertex_index_list.newElement(); + int vi = vindex >= 0 ? vindex : m_vertex_index_list + .elementToIndex(index); + m_vertex_index_list.setField(index, 0, vi); + if (vindex < 0) { + if (vi >= m_vertices.getPointCount()) { + int sz = vi < 16 ? 16 : (vi * 3) / 2; + // m_vertices.reserveRounded(sz); + m_vertices.resize(sz); + if (m_segments != null) { + for (int i = 0; i < sz; i++) + m_segments.add(null); + } + + if (m_weights != null) + m_weights.resize(sz); + + m_xy_stream = (AttributeStreamOfDbl) m_vertices + .getAttributeStreamRef(VertexDescription.Semantics.POSITION); + } + + m_vertices.setXY(vi, -1e38, -1e38); + + if (m_segments != null) + m_segments.set(vi, null); + + if (m_weights != null) + m_weights.write(vi, 1.0); + } else { + // We do not set vertices or segments here, because we assume those + // are set correctly already. + // We only here to create linked list of indices on existing vertex + // value. + // m_segments->set(m_point_count, nullptr); + } + + m_vertex_index_list.setField(index, 4, vi * 2); + m_point_count++; + return index; + } + + void free_vertex_(int vertex) { + m_vertex_index_list.deleteElement(vertex); + m_point_count--; + } + + int insertVertex_(int path, int before, Point point) { + int prev = before != -1 ? getPrevVertex(before) : getLastVertex(path); + int next = prev != -1 ? getNextVertex(prev) : -1; + + int vertex = newVertex_(point == null ? m_point_count : -1); + int vindex = getVertexIndex(vertex); + if (point != null) + m_vertices.setPointByVal(vindex, point); + + setPathToVertex_(vertex, path); + setNextVertex_(vertex, next); + setPrevVertex_(vertex, prev); + + if (next != -1) + setPrevVertex_(next, vertex); + + if (prev != -1) + setNextVertex_(prev, vertex); + + boolean b_closed = isClosedPath(path); + int first = getFirstVertex(path); + if (before == -1) + setLastVertex_(path, vertex); + + if (before == first) + setFirstVertex_(path, vertex); + + if (b_closed && next == -1) { + setNextVertex_(vertex, vertex); + setPrevVertex_(vertex, vertex); + } + + setPathSize_(path, getPathSize(path) + 1); + int geometry = getGeometryFromPath(path); + setGeometryVertexCount_(geometry, getPointCount(geometry) + 1); + + return vertex; + } + + Point getHelperPoint_() { + if (m_helper_point == null) + m_helper_point = new Point(m_vertices.getDescription()); + return m_helper_point; + } + + void setFillRule(int geom, int rule) { + int t = m_geometry_index_list.getField(geom, 2); + t &= ~(0x8000000); + t |= rule == Polygon.FillRule.enumFillRuleWinding ? 0x8000000 : 0; + m_geometry_index_list.setField(geom, 2, t);//fill rule combined with geometry type + } + + int getFillRule(int geom) { + int t = m_geometry_index_list.getField(geom, 2); + return (t & 0x8000000) != 0 ? Polygon.FillRule.enumFillRuleWinding : Polygon.FillRule.enumFillRuleOddEven; + } + + int addMultiPath_(MultiPath multi_path) { + int newgeom = createGeometry(multi_path.getType(), + multi_path.getDescription()); + if (multi_path.getType() == Geometry.Type.Polygon) + setFillRule(newgeom, ((Polygon) multi_path).getFillRule()); + + appendMultiPath_(newgeom, multi_path); + return newgeom; + } + + int addMultiPoint_(MultiPoint multi_point) { + int newgeometry = createGeometry(multi_point.getType(), + multi_point.getDescription()); + appendMultiPoint_(newgeometry, multi_point); + return newgeometry; + } + + void appendMultiPath_(int dstGeom, MultiPath multi_path) { + MultiPathImpl mp_impl = (MultiPathImpl) multi_path._getImpl(); + // m_vertices->reserve_rounded(m_vertices->get_point_count() + + // mp_impl->get_point_count());//ensure reallocation happens by blocks + // so that already allocated vertices do not get reallocated. + m_vertices_mp.add(multi_path, 0, mp_impl.getPointCount()); + m_xy_stream = (AttributeStreamOfDbl) m_vertices + .getAttributeStreamRef(VertexDescription.Semantics.POSITION); + boolean b_some_segments = m_segments != null + && mp_impl.getSegmentFlagsStreamRef() != null; + + for (int ipath = 0, npath = mp_impl.getPathCount(); ipath < npath; ipath++) { + if (mp_impl.getPathSize(ipath) < 2) // CR249862 - Clipping geometry + // which has empty part produces + // a crash + continue; + + int path = insertPath(dstGeom, -1); + setClosedPath(path, mp_impl.isClosedPath(ipath)); + for (int ivertex = mp_impl.getPathStart(ipath), iend = mp_impl + .getPathEnd(ipath); ivertex < iend; ivertex++) { + int vertex = insertVertex_(path, -1, null); + if (b_some_segments) { + int vindex = getVertexIndex(vertex); + if ((mp_impl.getSegmentFlags(ivertex) & (byte) SegmentFlags.enumLineSeg) != 0) { + setSegmentToIndex_(vindex, null); + } else { + SegmentBuffer seg_buffer = new SegmentBuffer(); + mp_impl.getSegment(ivertex, seg_buffer, true); + setSegmentToIndex_(vindex, seg_buffer.get()); + } + } + } + } + + // {//debug + // #ifdef DEBUG + // for (Index_type geometry = get_first_geometry(); geometry != -1; + // geometry = get_next_geometry(geometry)) + // { + // for (Index_type path = get_first_path(geometry); path != -1; path = + // get_next_path(path)) + // { + // Index_type first = get_first_vertex(path); + // Index_type v = first; + // for (get_next_vertex(v); v != first; v = get_next_vertex(v)) + // { + // assert(get_next_vertex(get_prev_vertex(v)) == v); + // } + // } + // } + // #endif + // } + } + + void appendMultiPoint_(int dstGeom, MultiPoint multi_point) { + // m_vertices->reserve_rounded(m_vertices->get_point_count() + + // multi_point.get_point_count());//ensure reallocation happens by + // blocks so that already allocated vertices do not get reallocated. + m_vertices_mp.add(multi_point, 0, multi_point.getPointCount()); + m_xy_stream = (AttributeStreamOfDbl) m_vertices + .getAttributeStreamRef(VertexDescription.Semantics.POSITION); + + int path = insertPath(dstGeom, -1); + for (int ivertex = 0, iend = multi_point.getPointCount(); ivertex < iend; ivertex++) { + insertVertex_(path, -1, null); + } + } + + void splitSegmentForward_(int origin_vertex, + SegmentIntersector intersector, int intersector_index) { + int last_vertex = getNextVertex(origin_vertex); + if (last_vertex == -1) + throw GeometryException.GeometryInternalError(); + Point point = getHelperPoint_(); + int path = getPathFromVertex(origin_vertex); + int vertex = origin_vertex; + for (int i = 0, n = intersector + .getResultSegmentCount(intersector_index); i < n; i++) { + int vindex = getVertexIndex(vertex); + int next_vertex = getNextVertex(vertex); + Segment seg = intersector.getResultSegment(intersector_index, i); + + if (i == 0) { + seg.queryStart(point); + // #ifdef DEBUG + // Point2D pt = new Point2D(); + // getXY(vertex, pt); + // assert(Point2D.distance(point.getXY(), pt) <= + // intersector.get_tolerance_()); + // #endif + setPoint(vertex, point); + } + + if (seg.getType().value() == Geometry.GeometryType.Line) + setSegmentToIndex_(vindex, null); + else + setSegmentToIndex_(vindex, (Segment) Geometry._clone(seg)); + + seg.queryEnd(point); + if (i < n - 1) { + int inserted_vertex = insertVertex_(path, next_vertex, point); + vertex = inserted_vertex; + } else { + // #ifdef DEBUG + // Point_2D pt; + // get_xy(last_vertex, pt); + // assert(Point_2D::distance(point->get_xy(), pt) <= + // intersector.getTolerance_()); + // #endif + setPoint(last_vertex, point); + assert (last_vertex == next_vertex); + } + } + } + + void splitSegmentBackward_(int origin_vertex, + SegmentIntersector intersector, int intersector_index) { + int last_vertex = getNextVertex(origin_vertex); + if (last_vertex == -1) + throw GeometryException.GeometryInternalError(); + + Point point = getHelperPoint_(); + int path = getPathFromVertex(origin_vertex); + int vertex = origin_vertex; + for (int i = 0, n = intersector + .getResultSegmentCount(intersector_index); i < n; i++) { + int vindex = getVertexIndex(vertex); + int next_vertex = getNextVertex(vertex); + Segment seg = intersector.getResultSegment(intersector_index, n - i + - 1); + + if (i == 0) { + seg.queryEnd(point); + // #ifdef DEBUG + // Point2D pt = new Point2D(); + // getXY(vertex, pt); + // assert(Point2D.distance(point.getXY(), pt) <= + // intersector.getTolerance_()); + // #endif + setPoint(vertex, point); + } + + if (seg.getType().value() == Geometry.GeometryType.Line) + setSegmentToIndex_(vindex, null); + else + setSegmentToIndex_(vindex, (Segment) Geometry._clone(seg)); + + seg.queryStart(point); + if (i < n - 1) { + int inserted_vertex = insertVertex_(path, next_vertex, point); + vertex = inserted_vertex; + } else { + // #ifdef DEBUG + // Point2D pt = new Point2D(); + // getXY(last_vertex, pt); + // assert(Point2D.distance(point.getXY(), pt) <= + // intersector.getTolerance_()); + // #endif + setPoint(last_vertex, point); + assert (last_vertex == next_vertex); + } + } + } + + EditShape() { + m_path_count = 0; + m_first_geometry = -1; + m_last_geometry = -1; + m_point_count = 0; + m_geometryCount = 0; + m_b_has_attributes = false; + m_vertices = null; + m_xy_stream = null; + m_vertex_description = null; + } + + // Total point count in all geometries + int getTotalPointCount() { + return m_point_count; + } + + // Returns envelope of all coordinates. + Envelope2D getEnvelope2D() { + Envelope2D env = new Envelope2D(); + env.setEmpty(); + VertexIterator vert_iter = queryVertexIterator(); + Point2D pt = new Point2D(); + boolean b_first = true; + for (int ivertex = vert_iter.next(); ivertex != -1; ivertex = vert_iter + .next()) { + getXY(ivertex, pt); + if (b_first) + env.merge(pt.x, pt.y); + else + env.mergeNE(pt.x, pt.y); + + b_first = false; + } + + return env; + } + + // Returns geometry count in the edit shape + int getGeometryCount() { + return m_geometryCount; + } + + // Adds a Geometry to the Edit_shape + int addGeometry(Geometry geometry) { + Geometry.Type gt = geometry.getType(); + if (Geometry.isMultiPath(gt.value())) + return addMultiPath_((MultiPath) geometry); + if (gt == Geometry.Type.MultiPoint) + return addMultiPoint_((MultiPoint) geometry); + + throw GeometryException.GeometryInternalError(); + } + + // Append a Geometry to the given geometry of the Edit_shape + void appendGeometry(int dstGeometry, Geometry srcGeometry) { + Geometry.Type gt = srcGeometry.getType(); + if (Geometry.isMultiPath(gt.value())) { + appendMultiPath_(dstGeometry, (MultiPath) srcGeometry); + return; + } else if (gt.value() == Geometry.GeometryType.MultiPoint) { + appendMultiPoint_(dstGeometry, (MultiPoint) srcGeometry); + return; + } + + throw GeometryException.GeometryInternalError(); + } + + // Adds a path + int addPathFromMultiPath(MultiPath multi_path, int ipath, boolean as_polygon) { + int newgeom = createGeometry(as_polygon ? Geometry.Type.Polygon + : Geometry.Type.Polyline, multi_path.getDescription()); + + MultiPathImpl mp_impl = (MultiPathImpl) multi_path._getImpl(); + if (multi_path.getPathSize(ipath) < 2) + return newgeom; //return empty geometry + + // m_vertices->reserve_rounded(m_vertices->get_point_count() + + // multi_path.get_path_size(ipath));//ensure reallocation happens by + // blocks so that already allocated vertices do not get reallocated. + m_vertices_mp.add(multi_path, multi_path.getPathStart(ipath), + mp_impl.getPathEnd(ipath)); + m_xy_stream = (AttributeStreamOfDbl) m_vertices + .getAttributeStreamRef(VertexDescription.Semantics.POSITION); + + int path = insertPath(newgeom, -1); + setClosedPath(path, mp_impl.isClosedPath(ipath) || as_polygon); + + boolean b_some_segments = m_segments != null + && mp_impl.getSegmentFlagsStreamRef() != null; + + for (int ivertex = mp_impl.getPathStart(ipath), iend = mp_impl + .getPathEnd(ipath); ivertex < iend; ivertex++) { + int vertex = insertVertex_(path, -1, null); + if (b_some_segments) { + int vindex = getVertexIndex(vertex); + if ((mp_impl.getSegmentFlags(ivertex) & SegmentFlags.enumLineSeg) != 0) { + setSegmentToIndex_(vindex, null); + } else { + SegmentBuffer seg_buffer = new SegmentBuffer(); + mp_impl.getSegment(ivertex, seg_buffer, true); + setSegmentToIndex_(vindex, seg_buffer.get()); + } + } + } + + return newgeom; + } + + // Extracts a geometry from the Edit_shape. The method creates a new + // Geometry instance and initializes it with the Edit_shape data for the + // given geometry. + Geometry getGeometry(int geometry) { + int gt = getGeometryType(geometry); + Geometry geom = InternalUtils.createGeometry(gt, + m_vertices_mp.getDescription()); + int point_count = getPointCount(geometry); + + if (point_count == 0) + return geom; + + if (Geometry.isMultiPath(gt)) { + MultiPathImpl mp_impl = (MultiPathImpl) geom._getImpl(); + int path_count = getPathCount(geometry); + AttributeStreamOfInt32 parts = (AttributeStreamOfInt32) (AttributeStreamBase + .createIndexStream(path_count + 1)); + AttributeStreamOfInt8 pathFlags = (AttributeStreamOfInt8) (AttributeStreamBase + .createByteStream(path_count + 1, (byte) 0)); + VertexDescription description = geom.getDescription(); + + for (int iattrib = 0, nattrib = description.getAttributeCount(); iattrib < nattrib; iattrib++) { + int semantics = description.getSemantics(iattrib); + int ncomps = VertexDescription.getComponentCount(semantics); + AttributeStreamBase dst_stream = AttributeStreamBase + .createAttributeStreamWithSemantics(semantics, + point_count); + AttributeStreamBase src_stream = m_vertices + .getAttributeStreamRef(semantics); + int dst_index = 0; + int ipath = 0; + int nvert = 0; + for (int path = getFirstPath(geometry); path != -1; path = getNextPath(path)) { + byte flag_mask = 0; + if (isClosedPath(path)) { + flag_mask |= (byte) PathFlags.enumClosed; + } else { + assert (gt != Geometry.GeometryType.Polygon); + } + + if (isExterior(path)) { + flag_mask |= (byte) PathFlags.enumOGCStartPolygon; + } + + if (flag_mask != 0) + pathFlags.setBits(ipath, flag_mask); + + int path_size = getPathSize(path); + parts.write(ipath++, nvert); + nvert += path_size; + if (semantics == VertexDescription.Semantics.POSITION) { + AttributeStreamOfDbl src_stream_dbl = (AttributeStreamOfDbl) (src_stream); + AttributeStreamOfDbl dst_stream_dbl = (AttributeStreamOfDbl) (dst_stream); + Point2D pt = new Point2D(); + for (int vertex = getFirstVertex(path); dst_index < nvert; vertex = getNextVertex(vertex), dst_index++) { + int src_index = getVertexIndex(vertex); + src_stream_dbl.read(src_index * 2, pt); + dst_stream_dbl.write(dst_index * 2, pt); + } + } else { + for (int vertex = getFirstVertex(path); dst_index < nvert; vertex = getNextVertex(vertex), dst_index++) { + int src_index = getVertexIndex(vertex); + for (int icomp = 0; icomp < ncomps; icomp++) { + double d = src_stream.readAsDbl(src_index + * ncomps + icomp); + dst_stream.writeAsDbl(dst_index * ncomps + + icomp, d); + } + } + } + } + + assert (nvert == point_count);// Inconsistent content in the + // Edit_shape. Please, fix. + assert (ipath == path_count); + mp_impl.setAttributeStreamRef(semantics, dst_stream); + parts.write(path_count, point_count); + } + + mp_impl.setPathFlagsStreamRef(pathFlags); + mp_impl.setPathStreamRef(parts); + mp_impl.notifyModified(DirtyFlags.dirtyAll); + } else if (gt == Geometry.GeometryType.MultiPoint) { + MultiPointImpl mp_impl = (MultiPointImpl) geom._getImpl(); + VertexDescription description = geom.getDescription(); + // mp_impl.reserve(point_count); + mp_impl.resize(point_count); + + for (int iattrib = 0, nattrib = description.getAttributeCount(); iattrib < nattrib; iattrib++) { + int semantics = description.getSemantics(iattrib); + int ncomps = VertexDescription.getComponentCount(semantics); + AttributeStreamBase dst_stream = mp_impl + .getAttributeStreamRef(semantics); + // std::shared_ptr dst_stream = + // Attribute_stream_base::create_attribute_stream(semantics, + // point_count); + AttributeStreamBase src_stream = m_vertices + .getAttributeStreamRef(semantics); + int dst_index = 0; + assert (getPathCount(geometry) == 1); + int path = getFirstPath(geometry); + int path_size = getPathSize(path); + for (int vertex = getFirstVertex(path); dst_index < path_size; vertex = getNextVertex(vertex), dst_index++) { + int src_index = getVertexIndex(vertex); + for (int icomp = 0; icomp < ncomps; icomp++) { + double d = src_stream.readAsDbl(src_index * ncomps + + icomp); + dst_stream.writeAsDbl(dst_index * ncomps + icomp, d); + } + } + + mp_impl.setAttributeStreamRef(semantics, dst_stream); + } + + mp_impl.notifyModified(DirtyFlags.dirtyAll); + } else { + assert (false); + } + + return geom; + } + + // create a new empty geometry of the given type + int createGeometry(Geometry.Type geometry_type) { + return createGeometry(geometry_type, + VertexDescriptionDesignerImpl.getDefaultDescriptor2D()); + } + + // Deletes existing geometry from the edit shape and returns the next one. + int removeGeometry(int geometry) { + for (int path = getFirstPath(geometry); path != -1; path = removePath(path)) { + // removing paths in a loop + } + + int prev = getPrevGeometry(geometry); + int next = getNextGeometry(geometry); + if (prev != -1) + setNextGeometry_(prev, next); + else { + m_first_geometry = next; + } + if (next != -1) + setPrevGeometry_(next, prev); + else { + m_last_geometry = prev; + } + + freeGeometry_(geometry); + return next; + } + + // create a new empty geometry of the given type and attribute set. + int createGeometry(Geometry.Type geometry_type, + VertexDescription description) { + int newgeom = newGeometry_(geometry_type.value()); + if (m_vertices == null) { + m_vertices_mp = new MultiPoint(description); + m_vertices = (MultiPointImpl) m_vertices_mp._getImpl(); + } else + m_vertices_mp.mergeVertexDescription(description); + + m_vertex_description = m_vertices_mp.getDescription();// this + // description + // will be a + // merge of + // existing + // description + // and the + // description + // of the + // multi_path + m_b_has_attributes = m_vertex_description.getAttributeCount() > 1; + + if (m_first_geometry == -1) { + m_first_geometry = newgeom; + m_last_geometry = newgeom; + } else { + setPrevGeometry_(newgeom, m_last_geometry); + setNextGeometry_(m_last_geometry, newgeom); + m_last_geometry = newgeom; + } + return newgeom; + } + + // Returns the first geometry in the Edit_shape. + int getFirstGeometry() { + return m_first_geometry; + } + + // Returns the next geometry in the Edit_shape. Returns -1 when there are no + // more geometries. + int getNextGeometry(int geom) { + return m_geometry_index_list.getField(geom, 1); + } + + // Returns the previous geometry in the Edit_shape. Returns -1 when there + // are no more geometries. + int getPrevGeometry(int geom) { + return m_geometry_index_list.getField(geom, 0); + } + + // Returns the type of the Geometry. + int getGeometryType(int geom) { + return m_geometry_index_list.getField(geom, 2) & 0x7FFFFFFF; + } + + // Sets value to the given user index on a geometry. + void setGeometryUserIndex(int geom, int index, int value) { + AttributeStreamOfInt32 stream = m_geometry_indices.get(index); + int pindex = getGeometryIndex_(geom); + if (pindex >= stream.size()) + stream.resize(Math.max((int) (pindex * 1.25), (int) 16), -1); + stream.write(pindex, value); + } + + // Returns the value of the given user index of a geometry + int getGeometryUserIndex(int geom, int index) { + int pindex = getGeometryIndex_(geom); + AttributeStreamOfInt32 stream = m_geometry_indices.get(index); + if (pindex < stream.size()) + return stream.read(pindex); + else + return -1; + } + + // Creates new user index on a geometry. The geometry index allows to store + // an integer user value on the geometry. + // Until set_geometry_user_index is called for a given geometry, the index + // stores -1 for that geometry. + int createGeometryUserIndex() { + if (m_geometry_indices == null) + m_geometry_indices = new ArrayList(4); + + // Try getting existing index. Use linear search. We do not expect many + // indices to be created. + for (int i = 0; i < m_geometry_indices.size(); i++) { + if (m_geometry_indices.get(i) == null) { + m_geometry_indices.set(i, + (AttributeStreamOfInt32) AttributeStreamBase + .createIndexStream(0)); + return i; + } + } + + m_geometry_indices.add((AttributeStreamOfInt32) AttributeStreamBase + .createIndexStream(0)); + return m_geometry_indices.size() - 1; + } + + // Removes the geometry user index. + void removeGeometryUserIndex(int index) { + m_geometry_indices.set(index, null); + } + + // Returns the first path of the geometry. + int getFirstPath(int geometry) { + return m_geometry_index_list.getField(geometry, 3); + } + + // Returns the first path of the geometry. + int getLastPath(int geometry) { + return m_geometry_index_list.getField(geometry, 4); + } + + // Point count in a geometry + int getPointCount(int geom) { + return m_geometry_index_list.getField(geom, 5); + } + + // Path count in a geometry + int getPathCount(int geom) { + return m_geometry_index_list.getField(geom, 6); + } + + // Filters degenerate segments in all multipath geometries + // Returns 1 if a non-zero length segment has been removed. -1, if only zero + // length segments have been removed. + // 0 if no segments have been removed. + // When b_remove_last_vertices and the result path is < 3 for polygon or < 2 + // for polyline, it'll be removed. + int filterClosePoints(double tolerance, boolean b_remove_last_vertices, boolean only_polygons) { + int res = 0; + for (int geometry = getFirstGeometry(); geometry != -1; geometry = getNextGeometry(geometry)) { + int gt = getGeometryType(geometry); + if (!Geometry.isMultiPath(gt)) + continue; + if (only_polygons && gt != GeometryType.Polygon) + continue; + + boolean b_polygon = getGeometryType(geometry) == Geometry.GeometryType.Polygon; + + for (int path = getFirstPath(geometry); path != -1; ) { + // We go from the start to the half of the path first, then we + // go from the end to the half of the path. + int vertex_counter = 0; + for (int vertex = getFirstVertex(path); vertex_counter < getPathSize(path) / 2; ) { + int next = getNextVertex(vertex); + if (next == -1) + break; + int vindex = getVertexIndex(vertex); + Segment seg = getSegmentFromIndex_(vindex); + double length = 0; + if (seg != null) { + length = seg.calculateLength2D(); + } else { + int vindex_next = getVertexIndex(next); + length = m_vertices._getShortestDistance(vindex, + vindex_next); + } + + if (length <= tolerance) { + if (length == 0) { + if (res == 0) + res = -1; + } else + res = 1; + + if (next != getLastVertex(path)) { + transferAllDataToTheVertex(next, vertex); + removeVertex(next, true); + } + } else { + vertex = getNextVertex(vertex); + } + vertex_counter++; + } + + int first_vertex = getFirstVertex(path); + for (int vertex = isClosedPath(path) ? first_vertex + : getLastVertex(path); getPathSize(path) > 0; ) { + int prev = getPrevVertex(vertex); + if (prev != -1) { + int vindex_prev = getVertexIndex(prev); + Segment seg = getSegmentFromIndex_(vindex_prev); + double length = 0; + if (seg != null) { + length = seg.calculateLength2D(); + } else { + int vindex = getVertexIndex(vertex); + length = m_vertices._getShortestDistance(vindex, + vindex_prev); + } + + if (length <= tolerance) { + if (length == 0) { + if (res == 0) + res = -1; + } else + res = 1; + + transferAllDataToTheVertex(prev, vertex); + removeVertex(prev, false); + if (first_vertex == prev) + first_vertex = getFirstVertex(path); + } else { + vertex = getPrevVertex(vertex); + if (vertex == first_vertex) + break; + } + } else { + removeVertex(vertex, true);// remove the last vertex in + // the path + if (res == 0) + res = -1; + break; + } + } + + int path_size = getPathSize(path); + if (b_remove_last_vertices + && (b_polygon ? path_size < 3 : path_size < 2)) { + path = removePath(path); + res = path_size > 0 ? 1 : (res == 0 ? -1 : res); + } else + path = getNextPath(path); + } + } + + return res; + } + + // Checks if there are degenerate segments in any of multipath geometries + boolean hasDegenerateSegments(double tolerance) { + for (int geometry = getFirstGeometry(); geometry != -1; geometry = getNextGeometry(geometry)) { + if (!Geometry.isMultiPath(getGeometryType(geometry))) + continue; + + boolean b_polygon = getGeometryType(geometry) == Geometry.GeometryType.Polygon; + + for (int path = getFirstPath(geometry); path != -1; ) { + int path_size = getPathSize(path); + if (b_polygon ? path_size < 3 : path_size < 2) + return true; + + int vertex = getFirstVertex(path); + for (int index = 0; index < path_size; index++) { + int next = getNextVertex(vertex); + if (next == -1) + break; + int vindex = getVertexIndex(vertex); + Segment seg = getSegmentFromIndex_(vindex); + double length = 0; + if (seg != null) { + length = seg.calculateLength2D(); + } else { + int vindex_next = getVertexIndex(next); + length = m_vertices._getShortestDistance(vindex, + vindex_next); + } + + if (length <= tolerance) + return true; + + vertex = next; + } + + path = getNextPath(path); + } + } + + return false; + } + + void transferAllDataToTheVertex(int from_vertex, int to_vertex) { + int vindexFrom = getVertexIndex(from_vertex); + int vindexTo = getVertexIndex(to_vertex); + if (m_weights != null) { + double weight = m_weights.read(vindexFrom); + m_weights.write(vindexTo, weight); + } + + if (m_b_has_attributes) { + // TODO: implement copying of attributes with exception of x and y + // + // for (int i = 0, nattrib = 0; i < nattrib; i++) + // { + // m_vertices->get_attribute + // } + } + // Copy user index data + if (m_indices != null) { + for (int i = 0, n = (int) m_indices.size(); i < n; i++) { + if (m_indices.get(i) != null) { + int value = getUserIndex(from_vertex, i); + if (value != -1) + setUserIndex(to_vertex, i, value); + } + } + } + } + + // Splits segment originating from the origingVertex split_count times at + // splitScalar points and inserts new vertices into the shape. + // The split is not done, when the splitScalar[i] is 0 or 1, or is equal to + // the splitScalar[i - 1]. + // Returns the number of splits actually happend (0 if no splits have + // happend). + int splitSegment(int origin_vertex, double[] split_scalars, int split_count) { + int actual_splits = 0; + int next_vertex = getNextVertex(origin_vertex); + if (next_vertex == -1) + throw GeometryException.GeometryInternalError(); + + int vindex = getVertexIndex(origin_vertex); + int vindex_next = getVertexIndex(next_vertex); + Segment seg = getSegmentFromIndex_(vindex); + double seg_length = seg == null ? m_vertices._getShortestDistance( + vindex, vindex_next) : seg.calculateLength2D(); + double told = 0.0; + for (int i = 0; i < split_count; i++) { + double t = split_scalars[i]; + if (told < t && t < 1.0) { + double f = t; + if (seg != null) { + f = seg_length > 0 ? seg._calculateSubLength(t) + / seg_length : 0.0; + } + + m_vertices._interpolateTwoVertices(vindex, vindex_next, f, + getHelperPoint_());// use this call mainly to + // interpolate the attributes. XYs + // are interpolated incorrectly for + // curves and are recalculated when + // segment is cut below. + int inserted_vertex = insertVertex_( + getPathFromVertex(origin_vertex), next_vertex, + getHelperPoint_()); + actual_splits++; + if (seg != null) { + Segment subseg = seg.cut(told, t); + int prev_vertex = getPrevVertex(inserted_vertex); + int vindex_prev = getVertexIndex(prev_vertex); + setSegmentToIndex_(vindex_prev, subseg); + setXY(inserted_vertex, subseg.getEndXY()); // fix XY + // coordinates + // to be + // parameter + // based + // (interpolate_two_vertices_) + if (i == split_count - 1 || split_scalars[i + 1] == 1.0) {// last + // chance + // to + // set + // last + // split + // segment + // here: + Segment subseg_end = seg.cut(t, 1.0); + setSegmentToIndex_(vindex_prev, subseg_end); + } + } + } + } + + return actual_splits; + } + + // interpolates the attributes for the specified path between from_vertex + // and to_vertex + void interpolateAttributesForClosedPath(int path, int from_vertex, + int to_vertex) { + assert (isClosedPath(path)); + + if (!m_b_has_attributes) + return; + + double sub_length = calculateSubLength2D(path, from_vertex, to_vertex); + + if (sub_length == 0.0) + return; + + int nattr = m_vertex_description.getAttributeCount(); + + for (int iattr = 1; iattr < nattr; iattr++) { + int semantics = m_vertex_description.getSemantics(iattr); + + int interpolation = VertexDescription.getInterpolation(semantics); + if (interpolation == VertexDescription.Interpolation.ANGULAR) + continue; + + int components = VertexDescription.getComponentCount(semantics); + + for (int ordinate = 0; ordinate < components; ordinate++) + interpolateAttributesForClosedPath_(semantics, path, + from_vertex, to_vertex, sub_length, ordinate); + } + + return; + } + + // calculates the length for the specified path between from_vertex and + // to_vertex + double calculateSubLength2D(int path, int from_vertex, int to_vertex) { + int shape_from_index = getVertexIndex(from_vertex); + int shape_to_index = getVertexIndex(to_vertex); + + if (shape_from_index < 0 || shape_to_index > getTotalPointCount() - 1) + throw new IllegalArgumentException("invalid call"); + + if (shape_from_index > shape_to_index) { + if (!isClosedPath(path)) + throw new IllegalArgumentException( + "cannot iterate across an open path"); + } + + double sub_length = 0.0; + + for (int vertex = from_vertex; vertex != to_vertex; vertex = getNextVertex(vertex)) { + int vertex_index = getVertexIndex(vertex); + Segment segment = getSegmentFromIndex_(vertex_index); + if (segment != null) { + sub_length += segment.calculateLength2D(); + } else { + int next_vertex_index = getVertexIndex(getNextVertex(vertex)); + sub_length += m_vertices._getShortestDistance(vertex_index, + next_vertex_index); + } + } + + return sub_length; + } + + // set_point modifies the vertex and associated segments. + void setPoint(int vertex, Point new_coord) { + int vindex = getVertexIndex(vertex); + m_vertices.setPointByVal(vindex, new_coord); + Segment seg = getSegmentFromIndex_(vindex); + if (seg != null) { + seg.setStart(new_coord); + } + int prev = getPrevVertex(vertex); + if (prev != -1) { + int vindex_p = getVertexIndex(prev); + Segment seg_p = getSegmentFromIndex_(vindex_p); + if (seg_p != null) { + seg.setEnd(new_coord); + } + } + } + + // Queries point for a given vertex. + void queryPoint(int vertex, Point point) { + int vindex = getVertexIndex(vertex); + m_vertices.getPointByVal(vindex, point); + // assert(getXY(vertex) == point.getXY()); + } + + // set_xy modifies the vertex and associated segments. + void setXY(int vertex, Point2D new_coord) { + setXY(vertex, new_coord.x, new_coord.y); + } + + // set_xy modifies the vertex and associated segments. + void setXY(int vertex, double new_x, double new_y) { + int vindex = getVertexIndex(vertex); + m_vertices.setXY(vindex, new_x, new_y); + Segment seg = getSegmentFromIndex_(vindex); + if (seg != null) { + seg.setStartXY(new_x, new_y); + } + int prev = getPrevVertex(vertex); + if (prev != -1) { + int vindex_p = getVertexIndex(prev); + Segment seg_p = getSegmentFromIndex_(vindex_p); + if (seg_p != null) { + seg.setEndXY(new_x, new_y); + } + } + } + + Point2D getXY(int vertex) { + Point2D pt = new Point2D(); + int vindex = getVertexIndex(vertex); + pt.setCoords(m_vertices.getXY(vindex)); + return pt; + } + + // Returns the coordinates of the vertex. + void getXY(int vertex, Point2D ptOut) { + int vindex = getVertexIndex(vertex); + ptOut.setCoords(m_vertices.getXY(vindex)); + } + + void getXYWithIndex(int index, Point2D ptOut) { + m_xy_stream.read(2 * index, ptOut); + } + + // Gets the attribute for the given semantics and ordinate. + double getAttributeAsDbl(int semantics, int vertex, int ordinate) { + return m_vertices.getAttributeAsDbl(semantics, getVertexIndex(vertex), + ordinate); + } + + // Sets the attribute for the given semantics and ordinate. + void setAttribute(int semantics, int vertex, int ordinate, double value) { + m_vertices.setAttribute(semantics, getVertexIndex(vertex), ordinate, + value); + } + + // Sets the attribute for the given semantics and ordinate. + void setAttribute(int semantics, int vertex, int ordinate, int value) { + m_vertices.setAttribute(semantics, getVertexIndex(vertex), ordinate, + value); + } + + // Returns a reference to the vertex description + VertexDescription getVertexDescription() { + return m_vertex_description; + } + + int getMinPathVertexY(int path) { + int first_vert = getFirstVertex(path); + int minv = first_vert; + int vert = getNextVertex(first_vert); + while (vert != -1 && vert != first_vert) { + if (compareVerticesSimpleY_(vert, minv) < 0) + minv = vert; + vert = getNextVertex(vert); + } + return minv; + } + + // Returns an index value for the vertex inside of the underlying array of + // vertices. + // This index is for the use with the get_xy_with_index. get_xy is + // equivalent to calling get_vertex_index and get_xy_with_index. + int getVertexIndex(int vertex) { + return m_vertex_index_list.getField(vertex, 0); + } + + // Returns the y coordinate of the vertex. + double getY(int vertex) { + Point2D pt = new Point2D(); + getXY(vertex, pt); + return pt.y; + } + + // returns True if xy coordinates at vertices are equal. + boolean isEqualXY(int vertex_1, int vertex_2) { + int vindex1 = getVertexIndex(vertex_1); + int vindex2 = getVertexIndex(vertex_2); + return m_vertices.getXY(vindex1).isEqual(m_vertices.getXY(vindex2)); + } + + // returns True if xy coordinates at vertices are equal. + boolean isEqualXY(int vertex, Point2D pt) { + int vindex = getVertexIndex(vertex); + return m_vertices.getXY(vindex).isEqual(pt); + } + + // Sets weight to the vertex. Weight is used by clustering and cracking. + void setWeight(int vertex, double weight) { + if (weight < 1.0) + weight = 1.0; + + if (m_weights == null) { + if (weight == 1.0) + return; + + m_weights = (AttributeStreamOfDbl) (AttributeStreamBase + .createDoubleStream(m_vertices.getPointCount(), 1.0)); + } + + int vindex = getVertexIndex(vertex); + if (vindex >= m_weights.size()) { + m_weights.resize(vindex + 1, 1.0); + } + + m_weights.write(vindex, weight); + } + + double getWeight(int vertex) { + int vindex = getVertexIndex(vertex); + if (m_weights == null || vindex >= m_weights.size()) + return 1.0; + + return m_weights.read(vindex); + } + + // Removes associated weights + void removeWeights() { + m_weights = null; + } + + // Sets value to the given user index. + void setUserIndex(int vertex, int index, int value) { + // CHECKVERTEXHANDLE(vertex); + AttributeStreamOfInt32 stream = m_indices.get(index); + // assert(get_prev_vertex(vertex) != -0x7eadbeaf);//using deleted vertex + int vindex = getVertexIndex(vertex); + if (stream.size() < m_vertices.getPointCount()) + stream.resize(m_vertices.getPointCount(), -1); + stream.write(vindex, value); + } + + int getUserIndex(int vertex, int index) { + // CHECKVERTEXHANDLE(vertex); + int vindex = getVertexIndex(vertex); + AttributeStreamOfInt32 stream = m_indices.get(index); + if (vindex < stream.size()) { + int val = stream.read(vindex); + return val; + } else + return -1; + } + + // Creates new user index. The index have random values. The index allows to + // store an integer user value on the vertex. + int createUserIndex() { + if (m_indices == null) + m_indices = new ArrayList(0); + + // Try getting existing index. Use linear search. We do not expect many + // indices to be created. + for (int i = 0; i < m_indices.size(); i++) { + if (m_indices.get(i) == null) { + m_indices.set(i, (AttributeStreamOfInt32) AttributeStreamBase + .createIndexStream(0, -1)); + return i; + } + } + + m_indices.add((AttributeStreamOfInt32) AttributeStreamBase + .createIndexStream(0, -1)); + return m_indices.size() - 1; + } + + // Removes the user index. + void removeUserIndex(int index) { + m_indices.set(index, null); + } + + // Returns segment, connecting currentVertex and next vertex. Returns NULL + // if it is a Line. + Segment getSegment(int vertex) { + if (m_segments != null) { + int vindex = getVertexIndex(vertex); + return m_segments.get(vindex); + } + return null; + } + + // Returns a straight line that connects this and next vertices. No + // attributes. Returns false if no next vertex exists (end of polyline + // part). + // Can be used together with get_segment. + boolean queryLineConnector(int vertex, Line line) { + int next = getNextVertex(vertex); + if (next == -1) + return false; + + if (!m_b_has_attributes) { + Point2D pt = new Point2D(); + getXY(vertex, pt); + line.setStartXY(pt); + getXY(next, pt); + line.setEndXY(pt); + } else { + Point pt = new Point(); + queryPoint(vertex, pt); + line.setStart(pt); + queryPoint(next, pt); + line.setEnd(pt); + } + + return true; + } + + // Inserts an empty path before the given one. If before_path is -1, adds + // path at the end. + int insertPath(int geometry, int before_path) { + int prev = -1; + + if (before_path != -1) { + if (geometry != getGeometryFromPath(before_path)) + throw GeometryException.GeometryInternalError(); + + prev = getPrevPath(before_path); + } else + prev = getLastPath(geometry); + + int newpath = newPath_(geometry); + if (before_path != -1) + setPrevPath_(before_path, newpath); + + setNextPath_(newpath, before_path); + setPrevPath_(newpath, prev); + if (prev != -1) + setNextPath_(prev, newpath); + else + setFirstPath_(geometry, newpath); + + if (before_path == -1) + setLastPath_(geometry, newpath); + + setGeometryPathCount_(geometry, getPathCount(geometry) + 1); + return newpath; + } + + int insertClosedPath_(int geometry, int before_path, int first_vertex, int checked_vertex, boolean[] contains_checked_vertex) { + int path = insertPath(geometry, -1); + int path_size = 0; + int vertex = first_vertex; + boolean contains = false; + + while (true) { + if (vertex == checked_vertex) + contains = true; + + setPathToVertex_(vertex, path); + path_size++; + int next = getNextVertex(vertex); + assert (getNextVertex(getPrevVertex(vertex)) == vertex); + if (next == first_vertex) + break; + + vertex = next; + } + + setClosedPath(path, true); + setPathSize_(path, path_size); + if (contains) + first_vertex = checked_vertex; + + setFirstVertex_(path, first_vertex); + setLastVertex_(path, getPrevVertex(first_vertex)); + setRingAreaValid_(path, false); + + if (contains_checked_vertex != null) { + contains_checked_vertex[0] = contains; + } + + return path; + } + + + // Removes a path, gets rid of all its vertices, and returns the next one + int removePath(int path) { + int prev = getPrevPath(path); + int next = getNextPath(path); + int geometry = getGeometryFromPath(path); + if (prev != -1) + setNextPath_(prev, next); + else { + setFirstPath_(geometry, next); + } + if (next != -1) + setPrevPath_(next, prev); + else { + setLastPath_(geometry, prev); + } + + clearPath(path); + + setGeometryPathCount_(geometry, getPathCount(geometry) - 1); + freePath_(path); + return next; + } + + // Clears all vertices from the path + void clearPath(int path) { + int first_vertex = getFirstVertex(path); + if (first_vertex != -1) { + // TODO: can ve do this in one shot? + int vertex = first_vertex; + for (int i = 0, n = getPathSize(path); i < n; i++) { + int v = vertex; + vertex = getNextVertex(vertex); + freeVertex_(v); + } + int geometry = getGeometryFromPath(path); + setGeometryVertexCount_(geometry, getPointCount(geometry) + - getPathSize(path)); + } + setPathSize_(path, 0); + } + + // Returns the next path (-1 if there are no more paths in the geometry). + int getNextPath(int currentPath) { + return m_path_index_list.getField(currentPath, 2); + } + + // Returns the previous path (-1 if there are no more paths in the + // geometry). + int getPrevPath(int currentPath) { + return m_path_index_list.getField(currentPath, 1); + } + + // Returns the number of vertices in the path. + int getPathSize(int path) { + return m_path_index_list.getField(path, 3); + } + + // Returns True if the path is closed. + boolean isClosedPath(int path) { + return (getPathFlags_(path) & PathFlags_.closedPath) != 0; + } + + // Makes path closed. Closed paths are circular lists. get_next_vertex + // always succeeds + void setClosedPath(int path, boolean b_yes_no) { + if (isClosedPath(path) == b_yes_no) + return; + if (getPathSize(path) > 0) { + int first = getFirstVertex(path); + int last = getLastVertex(path); + if (b_yes_no) { + // make a circular list + setNextVertex_(last, first); + setPrevVertex_(first, last); + // set segment to NULL (just in case) + int vindex = getVertexIndex(last); + setSegmentToIndex_(vindex, null); + } else { + setNextVertex_(last, -1); + setPrevVertex_(first, -1); + int vindex = getVertexIndex(last); + setSegmentToIndex_(vindex, null); + } + } + + int oldflags = getPathFlags_(path); + int flags = (oldflags | (int) PathFlags_.closedPath) + - (int) PathFlags_.closedPath;// clear the bit; + setPathFlags_(path, flags + | (b_yes_no ? (int) PathFlags_.closedPath : 0)); + } + + // Closes all paths of the geometry (has to be a polyline or polygon). + void closeAllPaths(int geometry) { + if (getGeometryType(geometry) == Geometry.GeometryType.Polygon) + return; + if (!Geometry.isLinear(getGeometryType(geometry))) + throw GeometryException.GeometryInternalError(); + + for (int path = getFirstPath(geometry); path != -1; path = getNextPath(path)) { + setClosedPath(path, true); + } + } + + // Returns geometry from path + int getGeometryFromPath(int path) { + return m_path_index_list.getField(path, 7); + } + + // Returns True if the path is exterior. + boolean isExterior(int path) { + return (getPathFlags_(path) & PathFlags_.exteriorPath) != 0; + } + + // Sets exterior flag + void setExterior(int path, boolean b_yes_no) { + int oldflags = getPathFlags_(path); + int flags = (oldflags | (int) PathFlags_.exteriorPath) + - (int) PathFlags_.exteriorPath;// clear the bit; + setPathFlags_(path, flags + | (b_yes_no ? (int) PathFlags_.exteriorPath : 0)); + } + + // Returns the ring area + double getRingArea(int path) { + if (isRingAreaValid_(path)) + return m_path_areas.get(getPathIndex_(path)); + + Line line = new Line(); + int vertex = getFirstVertex(path); + if (vertex == -1) + return 0; + Point2D pt0 = new Point2D(); + getXY(vertex, pt0); + double area = 0; + for (int i = 0, n = getPathSize(path); i < n; i++, vertex = getNextVertex(vertex)) { + Segment seg = getSegment(vertex); + if (seg == null) { + if (!queryLineConnector(vertex, line)) + continue; + + seg = line; + } + + double a = seg._calculateArea2DHelper(pt0.x, pt0.y); + area += a; + } + + setRingAreaValid_(path, true); + m_path_areas.set(getPathIndex_(path), area); + + return area; + } + + // Sets value to the given user index on a path. + void setPathUserIndex(int path, int index, int value) { + AttributeStreamOfInt32 stream = m_pathindices.get(index); + int pindex = getPathIndex_(path); + if (stream.size() < m_path_areas.size()) + stream.resize(m_path_areas.size(), -1); + stream.write(pindex, value); + } + + // Returns the value of the given user index of a path + int getPathUserIndex(int path, int index) { + int pindex = getPathIndex_(path); + AttributeStreamOfInt32 stream = m_pathindices.get(index); + if (pindex < stream.size()) + return stream.read(pindex); + else + return -1; + } + + // Creates new user index on a path. The index have random values. The path + // index allows to store an integer user value on the path. + int createPathUserIndex() { + if (m_pathindices == null) + m_pathindices = new ArrayList(0); + // Try getting existing index. Use linear search. We do not expect many + // indices to be created. + for (int i = 0; i < m_pathindices.size(); i++) { + if (m_pathindices.get(i) == null) { + m_pathindices.set(i, + (AttributeStreamOfInt32) (AttributeStreamBase + .createIndexStream(0))); + return i; + } + } + + m_pathindices.add((AttributeStreamOfInt32) (AttributeStreamBase + .createIndexStream(0))); + return (int) (m_pathindices.size() - 1); + } + + // Removes the path user index. + void removePathUserIndex(int index) { + m_pathindices.set(index, null); + } + + // Moves a path from any geometry before a given path in the dst_geom + // geometry. The path_handle do not change after the operation. + // before_path can be -1, then the path is moved to the end of the dst_geom. + void movePath(int geom, int before_path, int path_to_move) { + if (path_to_move == -1) + throw new IllegalArgumentException(); + + if (before_path == path_to_move) + return; + + int next = getNextPath(path_to_move); + int prev = getPrevPath(path_to_move); + int geom_src = getGeometryFromPath(path_to_move); + if (prev == -1) { + setFirstPath_(geom_src, next); + } else { + setNextPath_(prev, next); + } + + if (next == -1) { + setLastPath_(geom_src, prev); + } else { + setPrevPath_(next, prev); + } + + setGeometryVertexCount_(geom_src, getPointCount(geom_src) + - getPathSize(path_to_move)); + setGeometryPathCount_(geom_src, getPathCount(geom_src) - 1); + + if (before_path == -1) + prev = getLastPath(geom); + else + prev = getPrevPath(before_path); + + setPrevPath_(path_to_move, prev); + setNextPath_(path_to_move, before_path); + if (before_path == -1) + setLastPath_(geom, path_to_move); + else + setPrevPath_(before_path, path_to_move); + if (prev == -1) + setFirstPath_(geom, path_to_move); + else + setNextPath_(prev, path_to_move); + setGeometryVertexCount_(geom, getPointCount(geom) + + getPathSize(path_to_move)); + setGeometryPathCount_(geom, getPathCount(geom) + 1); + setPathGeometry_(path_to_move, geom); + } + + // Adds a copy of a vertex to a path. Connects with a straight line. + // Returns new vertex handle. + int addVertex(int path, int vertex) { + m_vertices.getPointByVal(getVertexIndex(vertex), getHelperPoint_()); + return insertVertex_(path, -1, getHelperPoint_()); + } + + // Removes vertex from path. Uses either left or right segments to + // reconnect. Returns next vertex after erased one. + int removeVertex(int vertex, boolean b_left_segment) { + int path = getPathFromVertex(vertex); + int prev = getPrevVertex(vertex); + int next = getNextVertex(vertex); + if (prev != -1) + setNextVertex_(prev, next); + + int path_size = getPathSize(path); + + if (vertex == getFirstVertex(path)) { + setFirstVertex_(path, path_size > 1 ? next : -1); + } + + if (next != -1) + setPrevVertex_(next, prev); + + if (vertex == getLastVertex(path)) { + setLastVertex_(path, path_size > 1 ? prev : -1); + } + + if (prev != -1 && next != -1) { + int vindex_prev = getVertexIndex(prev); + int vindex_next = getVertexIndex(next); + if (b_left_segment) { + Segment seg = getSegmentFromIndex_(vindex_prev); + if (seg != null) { + Point2D pt = new Point2D(); + m_vertices.getXY(vindex_next, pt); + seg.setEndXY(pt); + } + } else { + int vindex_erased = getVertexIndex(vertex); + Segment seg = getSegmentFromIndex_(vindex_erased); + setSegmentToIndex_(vindex_prev, seg); + if (seg != null) { + Point2D pt = m_vertices.getXY(vindex_prev); + seg.setStartXY(pt); + } + } + } + + setPathSize_(path, path_size - 1); + int geometry = getGeometryFromPath(path); + setGeometryVertexCount_(geometry, getPointCount(geometry) - 1); + freeVertex_(vertex); + return next; + } + + // Returns first vertex of the given path. + int getFirstVertex(int path) { + return m_path_index_list.getField(path, 4); + } + + // Returns last vertex of the given path. For the closed paths + // get_next_vertex for the last vertex returns the first vertex. + int getLastVertex(int path) { + return m_path_index_list.getField(path, 5); + } + + // Returns next vertex. Closed paths are circular lists, so get_next_vertex + // always returns vertex. Open paths return -1 for last vertex. + int getNextVertex(int currentVertex) { + return m_vertex_index_list.getField(currentVertex, 2); + } + + // Returns previous vertex. Closed paths are circular lists, so + // get_prev_vertex always returns vertex. Open paths return -1 for first + // vertex. + int getPrevVertex(int currentVertex) { + return m_vertex_index_list.getField(currentVertex, 1); + } + + int getPrevVertex(int currentVertex, int dir) { + return dir > 0 ? m_vertex_index_list.getField(currentVertex, 1) : m_vertex_index_list.getField(currentVertex, 2); + } + + int getNextVertex(int currentVertex, int dir) { + return dir > 0 ? m_vertex_index_list.getField(currentVertex, 2) : m_vertex_index_list.getField(currentVertex, 1); + } + + // Returns a path the vertex belongs to. + int getPathFromVertex(int vertex) { + return m_vertex_index_list.getField(vertex, 3); + } + + // Adds a copy of the point to a path. Connects with a straight line. + // Returns new vertex handle. + int addPoint(int path, Point point) { + return insertVertex_(path, -1, point); + } + + // Vertex iterator allows to go through all vertices of the Edit_shape. + static class VertexIterator { + private EditShape m_parent; + private int m_geometry; + private int m_path; + private int m_vertex; + private int m_first_vertex; + private int m_index; + boolean m_b_first; + boolean m_b_skip_mulit_points; + + private VertexIterator(EditShape parent, int geometry, int path, + int vertex, int first_vertex, int index, + boolean b_skip_mulit_points) { + m_parent = parent; + m_geometry = geometry; + m_path = path; + m_vertex = vertex; + m_index = index; + m_b_skip_mulit_points = b_skip_mulit_points; + m_first_vertex = first_vertex; + m_b_first = true; + } + + int moveToNext_() { + if (m_b_first) { + m_b_first = false; + return m_vertex; + } + + if (m_vertex != -1) { + m_vertex = m_parent.getNextVertex(m_vertex); + m_index++; + if (m_vertex != -1 && m_vertex != m_first_vertex) + return m_vertex; + + return moveToNextHelper_();// separate into another function for + // inlining + } + + return -1; + } + + int moveToNextHelper_() { + m_path = m_parent.getNextPath(m_path); + m_index = 0; + while (m_geometry != -1) { + for (; m_path != -1; m_path = m_parent.getNextPath(m_path)) { + m_vertex = m_parent.getFirstVertex(m_path); + m_first_vertex = m_vertex; + if (m_vertex != -1) + return m_vertex; + } + + m_geometry = m_parent.getNextGeometry(m_geometry); + if (m_geometry == -1) + break; + + if (m_b_skip_mulit_points + && !Geometry.isMultiPath(m_parent + .getGeometryType(m_geometry))) { + continue; + } + + m_path = m_parent.getFirstPath(m_geometry); + } + + return -1; + } + + // moves to next vertex. Returns -1 when there are no more vertices. + VertexIterator(VertexIterator source) { + m_parent = source.m_parent; + m_geometry = source.m_geometry; + m_path = source.m_path; + m_vertex = source.m_vertex; + m_index = source.m_index; + m_b_skip_mulit_points = source.m_b_skip_mulit_points; + m_first_vertex = source.m_first_vertex; + m_b_first = true; + } + + public int next() { + return moveToNext_(); + } + + public int currentGeometry() { + assert (m_vertex != -1); + return m_geometry; + } + + public int currentPath() { + assert (m_vertex != -1); + return m_path; + } + + public static VertexIterator create_(EditShape parent, int geometry, + int path, int vertex, int first_vertex, int index, + boolean b_skip_mulit_points) { + return new VertexIterator(parent, geometry, path, vertex, + first_vertex, index, b_skip_mulit_points); + } + } + + ; + + // Returns the vertex iterator that allows iteration through all vertices of + // all paths of all geometries. + VertexIterator queryVertexIterator() { + return queryVertexIterator(false); + } + + VertexIterator queryVertexIterator(VertexIterator source) { + return new VertexIterator(source); + } + + // Returns the vertex iterator that allows iteration through all vertices of + // all paths of all geometries. + // If bSkipMultiPoints is true, then the iterator will skip the Multi_point + // vertices + VertexIterator queryVertexIterator(boolean b_skip_multi_points) { + int geometry = -1; + int path = -1; + int vertex = -1; + int first_vertex = -1; + int index = 0; + boolean bFound = false; + + for (geometry = getFirstGeometry(); geometry != -1; geometry = getNextGeometry(geometry)) { + if (b_skip_multi_points + && !Geometry.isMultiPath(getGeometryType(geometry))) + continue; + + for (path = getFirstPath(geometry); path != -1; path = getNextPath(path)) { + vertex = getFirstVertex(path); + first_vertex = vertex; + index = 0; + if (vertex == -1) + continue; + + bFound = true; + break; + } + + if (bFound) + break; + } + + return VertexIterator.create_(this, geometry, path, vertex, + first_vertex, index, b_skip_multi_points); + } + + // Applies affine transformation + void applyTransformation(Transformation2D transform) { + m_vertices_mp.applyTransformation(transform); + if (m_segments != null) { + for (int i = 0, n = m_segments.size(); i < n; i++) { + if (m_segments.get(i) != null) { + m_segments.get(i).applyTransformation(transform); + } + } + } + } + + void interpolateAttributesForClosedPath_(int semantics, int path, + int from_vertex, int to_vertex, double sub_length, int ordinate) { + if (from_vertex == to_vertex) + return; + + double from_attribute = getAttributeAsDbl(semantics, from_vertex, + ordinate); + double to_attribute = getAttributeAsDbl(semantics, to_vertex, ordinate); + double cumulative_length = 0.0; + double prev_interpolated_attribute = from_attribute; + + for (int vertex = from_vertex; vertex != to_vertex; vertex = getNextVertex(vertex)) { + setAttribute(semantics, vertex, ordinate, + prev_interpolated_attribute); + + int vertex_index = getVertexIndex(vertex); + Segment segment = getSegmentFromIndex_(vertex_index); + double segment_length; + + if (segment != null) { + segment_length = segment.calculateLength2D(); + } else { + int next_vertex_index = getVertexIndex(getNextVertex(vertex)); + segment_length = m_vertices._getShortestDistance(vertex_index, + next_vertex_index); + } + cumulative_length += segment_length; + double t = cumulative_length / sub_length; + prev_interpolated_attribute = MathUtils.lerp(from_attribute, to_attribute, t); + } + + return; + } + + void SetGeometryType_(int geom, int gt) { + m_geometry_index_list.setField(geom, 2, gt); + } + + void splitSegment_(int origin_vertex, SegmentIntersector intersector, + int intersector_index, boolean b_forward) { + if (b_forward) { + splitSegmentForward_(origin_vertex, intersector, intersector_index); + } else { + splitSegmentBackward_(origin_vertex, intersector, intersector_index); + } + } + + void setPrevVertex_(int vertex, int prev) { + m_vertex_index_list.setField(vertex, 1, prev); + } + + void setNextVertex_(int vertex, int next) { + m_vertex_index_list.setField(vertex, 2, next); + } + + void setPathToVertex_(int vertex, int path) { + m_vertex_index_list.setField(vertex, 3, path); + } + + void setPathSize_(int path, int size) { + m_path_index_list.setField(path, 3, size); + } + + void setFirstVertex_(int path, int first_vertex) { + m_path_index_list.setField(path, 4, first_vertex); + } + + void setLastVertex_(int path, int last_vertex) { + m_path_index_list.setField(path, 5, last_vertex); + } + + void setGeometryPathCount_(int geom, int path_count) { + m_geometry_index_list.setField(geom, 6, path_count); + } + + void setGeometryVertexCount_(int geom, int vertex_count) { + m_geometry_index_list.setField(geom, 5, vertex_count); + } + + boolean ringParentageCheckInternal_(int vertex_1, int vertex_2) { + if (vertex_1 == vertex_2) + return true; + int vprev_1 = vertex_1; + int vprev_2 = vertex_2; + for (int v_1 = getNextVertex(vertex_1), v_2 = getNextVertex(vertex_2); v_1 != vertex_1 + && v_2 != vertex_2; v_1 = getNextVertex(v_1), v_2 = getNextVertex(v_2)) { + if (v_1 == vertex_2) + return true; + if (v_2 == vertex_1) + return true; + + assert (getPrevVertex(v_1) == vprev_1);// detect malformed list + assert (getPrevVertex(v_2) == vprev_2);// detect malformed list + vprev_1 = v_1; + vprev_2 = v_2; + } + + return false; + } + + void reverseRingInternal_(int vertex) { + int v = vertex; + do { + int prev = getPrevVertex(v); + int next = getNextVertex(v); + setNextVertex_(v, prev); + setPrevVertex_(v, next); + v = next; + } while (v != vertex); + // Path's last becomes invalid. Do not attempt to fix it here, because + // this is not the intent of the method + // Note: only last is invalid. other things sould not change. + } + + void setTotalPointCount_(int count) { + m_point_count = count; + } + + void removePathOnly_(int path) { + int prev = getPrevPath(path); + int next = getNextPath(path); + int geometry = getGeometryFromPath(path); + if (prev != -1) + setNextPath_(prev, next); + else { + setFirstPath_(geometry, next); + } + if (next != -1) + setPrevPath_(next, prev); + else { + setLastPath_(geometry, prev); + } + + setFirstVertex_(path, -1); + setLastVertex_(path, -1); + freePath_(path); + } + + // void DbgVerifyIntegrity(int vertex); + // void dbg_verify_vertex_counts(); + int removeVertexInternal_(int vertex, boolean b_left_segment) { + int prev = getPrevVertex(vertex); + int next = getNextVertex(vertex); + if (prev != -1) + setNextVertex_(prev, next); + + if (next != -1) + setPrevVertex_(next, prev); + + if (prev != -1 && next != -1) { + int vindex_prev = getVertexIndex(prev); + int vindex_next = getVertexIndex(next); + if (b_left_segment) { + Segment seg = getSegmentFromIndex_(vindex_prev); + if (seg != null) { + Point2D pt = new Point2D(); + m_vertices.getXY(vindex_next, pt); + seg.setEndXY(pt); + } + } else { + int vindex_erased = getVertexIndex(vertex); + Segment seg = getSegmentFromIndex_(vindex_erased); + setSegmentToIndex_(vindex_prev, seg); + if (seg != null) { + Point2D pt = new Point2D(); + m_vertices.getXY(vindex_prev, pt); + seg.setStartXY(pt); + } + } + } + freeVertex_(vertex); + return next; + } + + boolean isRingAreaValid_(int path) { + return (getPathFlags_(path) & PathFlags_.ringAreaValid) != 0; + } + + // Sets exterior flag + void setRingAreaValid_(int path, boolean b_yes_no) { + int oldflags = getPathFlags_(path); + int flags = (oldflags | (int) PathFlags_.ringAreaValid) + - (int) PathFlags_.ringAreaValid;// clear the bit; + setPathFlags_(path, flags + | (b_yes_no ? (int) PathFlags_.ringAreaValid : 0)); + } + + int compareVerticesSimpleY_(int v_1, int v_2) { + Point2D pt_1 = new Point2D(); + getXY(v_1, pt_1); + Point2D pt_2 = new Point2D(); + getXY(v_2, pt_2); + int res = pt_1.compare(pt_2); + return res; + } + + int compareVerticesSimpleX_(int v_1, int v_2) { + Point2D pt_1 = new Point2D(); + getXY(v_1, pt_1); + Point2D pt_2 = new Point2D(); + getXY(v_2, pt_2); + int res = pt_1.compare(pt_2); + return res; + } + + public static class SimplificatorVertexComparerY extends + AttributeStreamOfInt32.IntComparator { + EditShape parent; + + SimplificatorVertexComparerY(EditShape parent_) { + parent = parent_; + } + + @Override + public int compare(int i_1, int i_2) { + return parent.compareVerticesSimpleY_(i_1, i_2); + } + } + + public static class SimplificatorVertexComparerX extends + AttributeStreamOfInt32.IntComparator { + EditShape parent; + + SimplificatorVertexComparerX(EditShape parent_) { + parent = parent_; + } + + @Override + public int compare(int i_1, int i_2) { + return parent.compareVerticesSimpleX_(i_1, i_2); + } + } + + // void sort_vertices_simple_by_y_heap_merge(Dynamic_array& points, + // const Dynamic_array* geoms); + + void sortVerticesSimpleByY_(AttributeStreamOfInt32 points, int begin_, + int end_) { + if (m_bucket_sort == null) + m_bucket_sort = new BucketSort(); + m_bucket_sort.sort(points, begin_, end_, new EditShapeBucketSortHelper( + this)); + } + + void sortVerticesSimpleByYHelper_(AttributeStreamOfInt32 points, + int begin_, int end_) { + points.Sort(begin_, end_, new SimplificatorVertexComparerY(this)); + } + + void sortVerticesSimpleByX_(AttributeStreamOfInt32 points, int begin_, + int end_) { + points.Sort(begin_, end_, new SimplificatorVertexComparerX(this)); + } + + // Approximate size of the structure in memory. + // The estimated size can be very slightly less than the actual size. + // int estimate_memory_size() const; + + boolean hasPointFeatures() { + for (int geometry = getFirstGeometry(); geometry != -1; geometry = getNextGeometry(geometry)) { + if (!Geometry.isMultiPath(getGeometryType(geometry))) + return true; + } + return false; + } + + void swapGeometry(int geom1, int geom2) { + int first_path1 = getFirstPath(geom1); + int first_path2 = getFirstPath(geom2); + int last_path1 = getLastPath(geom1); + int last_path2 = getLastPath(geom2); + + for (int path = getFirstPath(geom1); path != -1; path = getNextPath(path)) { + setPathGeometry_(path, geom2); + } + + for (int path = getFirstPath(geom2); path != -1; path = getNextPath(path)) { + setPathGeometry_(path, geom1); + } + + setFirstPath_(geom1, first_path2); + setFirstPath_(geom2, first_path1); + setLastPath_(geom1, last_path2); + setLastPath_(geom2, last_path1); + + int vc1 = getPointCount(geom1); + int pc1 = getPathCount(geom1); + int vc2 = getPointCount(geom2); + int pc2 = getPathCount(geom2); + + setGeometryVertexCount_(geom1, vc2); + setGeometryVertexCount_(geom2, vc1); + setGeometryPathCount_(geom1, pc2); + setGeometryPathCount_(geom2, pc1); + + int gt1 = m_geometry_index_list.getField(geom1, 2); + int gt2 = m_geometry_index_list.getField(geom2, 2); + m_geometry_index_list.setField(geom1, 2, gt2); + m_geometry_index_list.setField(geom2, 2, gt1); + } } diff --git a/src/main/java/com/esri/core/geometry/EnclosingCircler.java b/src/main/java/com/esri/core/geometry/EnclosingCircler.java index 1c9dd069..0230e823 100644 --- a/src/main/java/com/esri/core/geometry/EnclosingCircler.java +++ b/src/main/java/com/esri/core/geometry/EnclosingCircler.java @@ -7,149 +7,152 @@ import java.util.stream.IntStream; public class EnclosingCircler { - class Circle { - final Point2D m_center; - final double m_radius; - Circle(Point2D center, double radius) { - m_center = center; - m_radius = radius; - } - - public boolean contains(Point2D point2D) { - return Point2D.distance(point2D, m_center) <= m_radius * (1 + m_tolerance / 2.0); - } - }; - - private Random m_random; - - // P is the set of all points to be processed. - private List m_indices; - - // S is the set of processed points. that will be kept track of using an index into the indices stream - private int m_processedIndex = 0; - - private Circle m_circle = null; - // Geometry to search - private MultiVertexGeometryImpl m_multiVertexGeometry; - - private int m_circleCount = 96; - SpatialReference m_bufferSpatialReference = null; - private ProjectionTransformation m_projectionTransformation; - private ProgressTracker m_progressTracker; - private double m_tolerance = 1e-10; - - EnclosingCircler(Geometry geometry, SpatialReference spatialReference, ProgressTracker progressTracker) { - if (spatialReference != null && !spatialReference.isLocal()) { - m_projectionTransformation = ProjectionTransformation.getEqualArea(geometry, spatialReference); - m_bufferSpatialReference = m_projectionTransformation.m_toSpatialReference; - m_tolerance = m_bufferSpatialReference.getTolerance(); - m_multiVertexGeometry = (MultiVertexGeometryImpl)Projecter.project(geometry, m_projectionTransformation, progressTracker)._getImpl(); - m_multiVertexGeometry._updateAllDirtyIntervals(true); - } else { - m_multiVertexGeometry = (MultiVertexGeometryImpl) geometry._getImpl(); - } - m_progressTracker = progressTracker; - m_random = new Random(1977); - } - - Geometry search() { - m_indices = IntStream.range(0, m_multiVertexGeometry.getPointCount()).boxed().collect(Collectors.toList()); - Collections.shuffle(m_indices, m_random); - - // Place first two points in boundary list - Point2D pt1 = __getShuffledPoint(m_processedIndex++); - Point2D pt2 = __getShuffledPoint(m_processedIndex++); - Point2D testCenter = new Point2D(); - testCenter.interpolate(pt1, pt2, .5); - double radius = Point2D.distance(pt1, pt2) / 2.0; - - m_circle = new Circle(testCenter, radius); - - __updateCircle(); - - if (m_projectionTransformation == null) - return __constructCircle(); - - return Projecter.project(__constructCircle(), m_projectionTransformation.getReverse(), m_progressTracker); - } - - private Geometry __constructCircle() { - Point point = new Point(m_circle.m_center); - - return OperatorBuffer.local().execute(point, m_bufferSpatialReference, m_circle.m_radius, m_progressTracker); - } - - private Point2D __getShuffledPoint(int index) { - return m_multiVertexGeometry.getXY(m_indices.get(index)); - } - - private void __updateCircle() { - // loop through all points in geometry - Circle circle = new Circle(m_circle.m_center, m_circle.m_radius); - while (m_processedIndex < m_multiVertexGeometry.getPointCount()) { - Point2D testPoint = __getShuffledPoint(m_processedIndex++); - if (!circle.contains(testPoint)) { - // if the point is outside the current circle - circle = __updateCircle(testPoint); - } - } - - m_circle = circle; - } - - private Circle __updateCircle(Point2D newBoundaryPoint) { - // two point option - Point2D testCenter = new Point2D(); - - // TODO geodesic - testCenter.interpolate(newBoundaryPoint, __getShuffledPoint(0), .5); - - Circle circle = new Circle(testCenter, Point2D.distance(testCenter, newBoundaryPoint)); - for (int i = 1; i < m_processedIndex; i++) { - Point2D testPoint = __getShuffledPoint(i); - if (!circle.contains(testPoint)) { - circle = __updateCircle(newBoundaryPoint, testPoint); - } - } - - return circle; - } - - private Circle __updateCircle(Point2D newBoundaryPoint, Point2D testPoint) { - Point2D testCenter = new Point2D(); - - // TODO geodesic - testCenter.interpolate(newBoundaryPoint, testPoint, .5); - - Circle circle = new Circle(testCenter, Point2D.distance(testCenter, newBoundaryPoint)); - for (int i = 0; i < m_processedIndex; i++) { - if (circle.contains(__getShuffledPoint(i))) { - continue; - } - - // test current index against testPoint - Point2D testCurrentCenter = new Point2D(); - testCurrentCenter.interpolate(testPoint, __getShuffledPoint(i), .5); - Circle testCurrentCircle = new Circle(testCurrentCenter, Point2D.distance(testCurrentCenter, __getShuffledPoint(i))); - if (testCurrentCircle.contains(newBoundaryPoint)) { - circle = testCurrentCircle; - continue; - } - - Point2D newCurrentCenter = new Point2D(); - newCurrentCenter.interpolate(newBoundaryPoint, __getShuffledPoint(i), .5); - Circle newCurrentCircle = new Circle(newCurrentCenter, Point2D.distance(newCurrentCenter, __getShuffledPoint(i))); - if (newCurrentCircle.contains(testPoint)) { - circle = newCurrentCircle; - continue; - } - - - // create circle from three points - testCenter = Point2D.calculateCircleCenterFromThreePoints(newBoundaryPoint, testPoint, __getShuffledPoint(i)); - circle = new Circle(testCenter, Point2D.distance(testCenter, newBoundaryPoint)); - } - - return circle; - } + class Circle { + final Point2D m_center; + final double m_radius; + + Circle(Point2D center, double radius) { + m_center = center; + m_radius = radius; + } + + public boolean contains(Point2D point2D) { + return Point2D.distance(point2D, m_center) <= m_radius * (1 + m_tolerance / 2.0); + } + } + + ; + + private Random m_random; + + // P is the set of all points to be processed. + private List m_indices; + + // S is the set of processed points. that will be kept track of using an index into the indices stream + private int m_processedIndex = 0; + + private Circle m_circle = null; + // Geometry to search + private MultiVertexGeometryImpl m_multiVertexGeometry; + + private int m_circleCount = 96; + SpatialReference m_bufferSpatialReference = null; + private ProjectionTransformation m_projectionTransformation; + private ProgressTracker m_progressTracker; + private double m_tolerance = 1e-10; + + EnclosingCircler(Geometry geometry, SpatialReference spatialReference, ProgressTracker progressTracker) { + if (spatialReference != null && !spatialReference.isLocal()) { + m_projectionTransformation = ProjectionTransformation.getEqualArea(geometry, spatialReference); + m_bufferSpatialReference = m_projectionTransformation.m_toSpatialReference; + m_tolerance = m_bufferSpatialReference.getTolerance(); + m_multiVertexGeometry = (MultiVertexGeometryImpl) Projecter.project(geometry, m_projectionTransformation, progressTracker)._getImpl(); + m_multiVertexGeometry._updateAllDirtyIntervals(true); + } else { + m_multiVertexGeometry = (MultiVertexGeometryImpl) geometry._getImpl(); + } + m_progressTracker = progressTracker; + m_random = new Random(1977); + } + + Geometry search() { + m_indices = IntStream.range(0, m_multiVertexGeometry.getPointCount()).boxed().collect(Collectors.toList()); + Collections.shuffle(m_indices, m_random); + + // Place first two points in boundary list + Point2D pt1 = __getShuffledPoint(m_processedIndex++); + Point2D pt2 = __getShuffledPoint(m_processedIndex++); + Point2D testCenter = new Point2D(); + testCenter.interpolate(pt1, pt2, .5); + double radius = Point2D.distance(pt1, pt2) / 2.0; + + m_circle = new Circle(testCenter, radius); + + __updateCircle(); + + if (m_projectionTransformation == null) + return __constructCircle(); + + return Projecter.project(__constructCircle(), m_projectionTransformation.getReverse(), m_progressTracker); + } + + private Geometry __constructCircle() { + Point point = new Point(m_circle.m_center); + + return OperatorBuffer.local().execute(point, m_bufferSpatialReference, m_circle.m_radius, m_progressTracker); + } + + private Point2D __getShuffledPoint(int index) { + return m_multiVertexGeometry.getXY(m_indices.get(index)); + } + + private void __updateCircle() { + // loop through all points in geometry + Circle circle = new Circle(m_circle.m_center, m_circle.m_radius); + while (m_processedIndex < m_multiVertexGeometry.getPointCount()) { + Point2D testPoint = __getShuffledPoint(m_processedIndex++); + if (!circle.contains(testPoint)) { + // if the point is outside the current circle + circle = __updateCircle(testPoint); + } + } + + m_circle = circle; + } + + private Circle __updateCircle(Point2D newBoundaryPoint) { + // two point option + Point2D testCenter = new Point2D(); + + // TODO geodesic + testCenter.interpolate(newBoundaryPoint, __getShuffledPoint(0), .5); + + Circle circle = new Circle(testCenter, Point2D.distance(testCenter, newBoundaryPoint)); + for (int i = 1; i < m_processedIndex; i++) { + Point2D testPoint = __getShuffledPoint(i); + if (!circle.contains(testPoint)) { + circle = __updateCircle(newBoundaryPoint, testPoint); + } + } + + return circle; + } + + private Circle __updateCircle(Point2D newBoundaryPoint, Point2D testPoint) { + Point2D testCenter = new Point2D(); + + // TODO geodesic + testCenter.interpolate(newBoundaryPoint, testPoint, .5); + + Circle circle = new Circle(testCenter, Point2D.distance(testCenter, newBoundaryPoint)); + for (int i = 0; i < m_processedIndex; i++) { + if (circle.contains(__getShuffledPoint(i))) { + continue; + } + + // test current index against testPoint + Point2D testCurrentCenter = new Point2D(); + testCurrentCenter.interpolate(testPoint, __getShuffledPoint(i), .5); + Circle testCurrentCircle = new Circle(testCurrentCenter, Point2D.distance(testCurrentCenter, __getShuffledPoint(i))); + if (testCurrentCircle.contains(newBoundaryPoint)) { + circle = testCurrentCircle; + continue; + } + + Point2D newCurrentCenter = new Point2D(); + newCurrentCenter.interpolate(newBoundaryPoint, __getShuffledPoint(i), .5); + Circle newCurrentCircle = new Circle(newCurrentCenter, Point2D.distance(newCurrentCenter, __getShuffledPoint(i))); + if (newCurrentCircle.contains(testPoint)) { + circle = newCurrentCircle; + continue; + } + + + // create circle from three points + testCenter = Point2D.calculateCircleCenterFromThreePoints(newBoundaryPoint, testPoint, __getShuffledPoint(i)); + circle = new Circle(testCenter, Point2D.distance(testCenter, newBoundaryPoint)); + } + + return circle; + } } diff --git a/src/main/java/com/esri/core/geometry/EnvSrlzr.java b/src/main/java/com/esri/core/geometry/EnvSrlzr.java index a7b6da7f..f884ef1f 100644 --- a/src/main/java/com/esri/core/geometry/EnvSrlzr.java +++ b/src/main/java/com/esri/core/geometry/EnvSrlzr.java @@ -29,67 +29,67 @@ //This is a writeReplace class for Envelope public class EnvSrlzr implements Serializable { - private static final long serialVersionUID = 1L; - double[] attribs; - int descriptionBitMask; + private static final long serialVersionUID = 1L; + double[] attribs; + int descriptionBitMask; - public Object readResolve() throws ObjectStreamException { - Envelope env = null; - if (descriptionBitMask == -1) - return null; + public Object readResolve() throws ObjectStreamException { + Envelope env = null; + if (descriptionBitMask == -1) + return null; - try { - VertexDescription vd = VertexDescriptionDesignerImpl - .getVertexDescription(descriptionBitMask); - env = new Envelope(vd); - if (attribs != null) { - env.setCoords(attribs[0], attribs[1], attribs[2], attribs[3]); - int index = 4; - for (int i = 1, n = vd.getAttributeCount(); i < n; i++) { - int semantics = vd.getSemantics(i); - int comps = VertexDescription.getComponentCount(semantics); - for (int ord = 0; ord < comps; ord++) { - env.setInterval(semantics, ord, attribs[index++], attribs[index++]); - } - } - } - } catch (Exception ex) { - throw new InvalidObjectException("Cannot read geometry from stream"); - } + try { + VertexDescription vd = VertexDescriptionDesignerImpl + .getVertexDescription(descriptionBitMask); + env = new Envelope(vd); + if (attribs != null) { + env.setCoords(attribs[0], attribs[1], attribs[2], attribs[3]); + int index = 4; + for (int i = 1, n = vd.getAttributeCount(); i < n; i++) { + int semantics = vd.getSemantics(i); + int comps = VertexDescription.getComponentCount(semantics); + for (int ord = 0; ord < comps; ord++) { + env.setInterval(semantics, ord, attribs[index++], attribs[index++]); + } + } + } + } catch (Exception ex) { + throw new InvalidObjectException("Cannot read geometry from stream"); + } - return env; - } + return env; + } - public void setGeometryByValue(Envelope env) throws ObjectStreamException { - try { - attribs = null; - if (env == null) { - descriptionBitMask = -1; - } + public void setGeometryByValue(Envelope env) throws ObjectStreamException { + try { + attribs = null; + if (env == null) { + descriptionBitMask = -1; + } - VertexDescription vd = env.getDescription(); - descriptionBitMask = vd.m_semanticsBitArray; - if (env.isEmpty()) { - return; - } + VertexDescription vd = env.getDescription(); + descriptionBitMask = vd.m_semanticsBitArray; + if (env.isEmpty()) { + return; + } - attribs = new double[vd.getTotalComponentCount() * 2]; - attribs[0] = env.getXMin(); - attribs[1] = env.getYMin(); - attribs[2] = env.getXMax(); - attribs[3] = env.getYMax(); - int index = 4; - for (int i = 1, n = vd.getAttributeCount(); i < n; i++) { - int semantics = vd.getSemantics(i); - int comps = VertexDescription.getComponentCount(semantics); - for (int ord = 0; ord < comps; ord++) { - Envelope1D e = env.queryInterval(semantics, ord); - attribs[index++] = e.vmin; - attribs[index++] = e.vmax; - } - } - } catch (Exception ex) { - throw new InvalidObjectException("Cannot serialize this geometry"); - } - } + attribs = new double[vd.getTotalComponentCount() * 2]; + attribs[0] = env.getXMin(); + attribs[1] = env.getYMin(); + attribs[2] = env.getXMax(); + attribs[3] = env.getYMax(); + int index = 4; + for (int i = 1, n = vd.getAttributeCount(); i < n; i++) { + int semantics = vd.getSemantics(i); + int comps = VertexDescription.getComponentCount(semantics); + for (int ord = 0; ord < comps; ord++) { + Envelope1D e = env.queryInterval(semantics, ord); + attribs[index++] = e.vmin; + attribs[index++] = e.vmax; + } + } + } catch (Exception ex) { + throw new InvalidObjectException("Cannot serialize this geometry"); + } + } } diff --git a/src/main/java/com/esri/core/geometry/Envelope.java b/src/main/java/com/esri/core/geometry/Envelope.java index 93dafd7e..9ec64de5 100644 --- a/src/main/java/com/esri/core/geometry/Envelope.java +++ b/src/main/java/com/esri/core/geometry/Envelope.java @@ -35,402 +35,401 @@ * An envelope is an axis-aligned rectangle. */ public class Envelope extends Geometry implements Serializable { - //We are using writeReplace instead. - //private static final long serialVersionUID = 2L; - - Envelope2D m_envelope = new Envelope2D(); - - double[] m_attributes;// use doubles to store everything - - /** - * Creates an envelope by defining its center, width, and height. - * - * @param center The center point of the envelope. - * @param width The width of the envelope. - * @param height The height of the envelope. - */ - public Envelope(Point center, double width, double height) { - m_description = VertexDescriptionDesignerImpl.getDefaultDescriptor2D(); - m_envelope.setEmpty(); - if (center.isEmpty()) - return; - - _setFromPoint(center, width, height); - } - - public Envelope(Envelope2D env2D) { - m_description = VertexDescriptionDesignerImpl.getDefaultDescriptor2D(); - m_envelope.setCoords(env2D); - m_envelope.normalize(); - } - - public Envelope(VertexDescription vd) { - if (vd == null) - throw new IllegalArgumentException(); - m_description = vd; - m_envelope.setEmpty(); - } - - public Envelope(VertexDescription vd, Envelope2D env2D) { - if (vd == null) - throw new IllegalArgumentException(); - m_description = vd; - m_envelope.setCoords(env2D); - m_envelope.normalize(); - } - - /** - * Constructs an empty envelope. - */ - public Envelope() { - m_description = VertexDescriptionDesignerImpl.getDefaultDescriptor2D(); - m_envelope.setEmpty(); - } - - /** - * Constructs an envelope that covers the given point. The coordinates of - * the point are used to set the extent of the envelope. - * - * @param point The point that the envelope covers. - */ - public Envelope(Point point) { - m_description = VertexDescriptionDesignerImpl.getDefaultDescriptor2D(); - m_envelope.setEmpty(); - if (point.isEmpty()) - return; - - _setFromPoint(point); - } - - /** - * Constructs an envelope with the specified X and Y extents. - * - * @param xmin The minimum x-coordinate of the envelope. - * @param ymin The minimum y-coordinate of the envelope. - * @param xmax The maximum x-coordinate of the envelope. - * @param ymax The maximum y-coordinate of the envelope. - */ - public Envelope(double xmin, double ymin, double xmax, double ymax) { - m_description = VertexDescriptionDesignerImpl.getDefaultDescriptor2D(); - setCoords(xmin, ymin, xmax, ymax); - } - - /** - * Sets the 2-dimensional extents of the envelope. - * - * @param xmin The minimum x-coordinate of the envelope. - * @param ymin The minimum y-coordinate of the envelope. - * @param xmax The maximum x-coordinate of the envelope. - * @param ymax The maximum y-coordinate of the envelope. - */ - public void setCoords(double xmin, double ymin, double xmax, double ymax) { - _touch(); - m_envelope.setCoords(xmin, ymin, xmax, ymax); - } - - /** - * Sets the envelope from the array of points. The result envelope is a - * bounding box of all the points in the array. If the array has zero - * length, the envelope will be empty. - * - * @param points The point array. - */ - void setCoords(Point[] points) { - _touch(); - setEmpty(); - for (int i = 0, n = points.length; i < n; i++) - merge(points[i]); - } - - void setEnvelope2D(Envelope2D e2d) { - _touch(); - if (!e2d.isValid()) - throw new IllegalArgumentException(); - - m_envelope.setCoords(e2d); - } - - /** - * Removes all points from this geometry. - */ - @Override - public void setEmpty() { - _touch(); - m_envelope.setEmpty(); - } - - /** - * Indicates whether this envelope contains any points. - * - * @return boolean Returns true if the envelope is empty. - */ - @Override - public boolean isEmpty() { - return m_envelope.isEmpty(); - } - - /** - * The width of the envelope. - * - * @return The width of the envelope. - */ - - public double getWidth() { - return m_envelope.getWidth(); - } - - /** - * The height of the envelope. - * - * @return The height of the envelope. - */ - public double getHeight() { - return m_envelope.getHeight(); - } - - /** - * The x-coordinate of the center of the envelope. - * - * @return The x-coordinate of the center of the envelope. - */ - public double getCenterX() { - return m_envelope.getCenterX(); - } - - /** - * The y-coordinate of center of the envelope. - * - * @return The y-coordinate of center of the envelope. - */ - public double getCenterY() { - return m_envelope.getCenterY(); - } - - /** - * The x and y-coordinates of the center of the envelope. - * - * @return A point whose x and y-coordinates are that of the center of the envelope. - */ - public Point2D getCenterXY() { - return m_envelope.getCenter(); - } - - public void getCenter(Point point_out) { - point_out.assignVertexDescription(m_description); - if (isEmpty()) { - point_out.setEmpty(); - return; - } - - int nattrib = m_description.getAttributeCount(); - for (int i = 1; i < nattrib; i++) { - int semantics = m_description.getSemantics(i); - int ncomp = VertexDescription.getComponentCount(semantics); - for (int iord = 0; iord < ncomp; iord++) { - double v = 0.5 * (getAttributeAsDblImpl_(0, semantics, iord) + getAttributeAsDblImpl_( - 1, semantics, iord)); - point_out.setAttribute(semantics, iord, v); - } - } - point_out.setXY(m_envelope.getCenter()); - } - - public void merge(Point2D pt) { - _touch(); - m_envelope.merge(pt); - } - - /** - * Merges this envelope with the extent of the given envelope. If this - * envelope is empty, the coordinates of the given envelope - * are assigned. If the given envelope is empty, this envelope is unchanged. - * - * @param other The envelope to merge. - */ - public void merge(Envelope other) { - _touch(); - if (other.isEmpty()) - return; - - VertexDescription otherVD = other.m_description; - if (otherVD != m_description) - mergeVertexDescription(otherVD); - m_envelope.merge(other.m_envelope); - for (int iattrib = 1, nattrib = otherVD.getAttributeCount(); iattrib < nattrib; iattrib++) { - int semantics = otherVD.getSemantics(iattrib); - int ncomps = VertexDescription.getComponentCount(semantics); - for (int iord = 0; iord < ncomps; iord++) { - Envelope1D intervalOther = other.queryInterval(semantics, iord); - Envelope1D interval = queryInterval(semantics, iord); - interval.merge(intervalOther); - setInterval(semantics, iord, interval); - } - } - } - - /** - * Merges this envelope with the point. The boundary of the envelope is - * increased to include the point. If the envelope is empty, the coordinates - * of the point to merge are assigned. If the point is empty, the original - * envelope is unchanged. - * - * @param point The point to be merged. - */ - public void merge(Point point) { - _touch(); - if (point.isEmptyImpl()) - return; - - VertexDescription pointVD = point.m_description; - if (m_description != pointVD) - mergeVertexDescription(pointVD); - - if (isEmpty()) { - _setFromPoint(point); - return; - } - - m_envelope.merge(point.getXY()); - for (int iattrib = 1, nattrib = pointVD.getAttributeCount(); iattrib < nattrib; iattrib++) { - int semantics = pointVD._getSemanticsImpl(iattrib); - int ncomps = VertexDescription.getComponentCount(semantics); - for (int iord = 0; iord < ncomps; iord++) { - double v = point.getAttributeAsDbl(semantics, iord); - Envelope1D interval = queryInterval(semantics, iord); - interval.merge(v); - setInterval(semantics, iord, interval); - } - } - } - - void _setFromPoint(Point centerPoint, double width, double height) { - m_envelope.setCoords(centerPoint.getXY(), width, height); - VertexDescription pointVD = centerPoint.m_description; - for (int iattrib = 1, nattrib = pointVD.getAttributeCount(); iattrib < nattrib; iattrib++) { - int semantics = pointVD._getSemanticsImpl(iattrib); - int ncomps = VertexDescription.getComponentCount(semantics); - for (int iord = 0; iord < ncomps; iord++) { - double v = centerPoint.getAttributeAsDbl(semantics, iord); - setInterval(semantics, iord, v, v); - } - } - } - - void _setFromPoint(Point centerPoint) { - m_envelope.setCoords(centerPoint.m_attributes[0], - centerPoint.m_attributes[1]); - VertexDescription pointVD = centerPoint.m_description; - for (int iattrib = 1, nattrib = pointVD.getAttributeCount(); iattrib < nattrib; iattrib++) { - int semantics = pointVD._getSemanticsImpl(iattrib); - int ncomps = VertexDescription.getComponentCount(semantics); - for (int iord = 0; iord < ncomps; iord++) { - double v = centerPoint.getAttributeAsDbl(semantics, iord); - setInterval(semantics, iord, v, v); - } - } - } - - public void merge(Envelope2D other) { - _touch(); - m_envelope.merge(other); - } - - public void setInterval(int semantics, int ordinate, double vmin, - double vmax) { - setInterval(semantics, ordinate, new Envelope1D(vmin, vmax)); - } - - /** - * Re-aspects this envelope to fit within the specified width and height. - * - * @param arWidth The width within which to fit the envelope. - * @param arHeight The height within which to fit the envelope. - */ - public void reaspect(double arWidth, double arHeight) { - _touch(); - m_envelope.reaspect(arWidth, arHeight); - } - - /** - * Changes the dimensions of the envelope while preserving the center. New width - * is Width + 2 * dx, new height is Height + 2 * dy. If the result envelope - * width or height becomes negative, the envelope is set to be empty. - * - * @param dx The inflation along the x-axis. - * @param dy The inflation along the y-axis. - */ - public void inflate(double dx, double dy) { - _touch(); - m_envelope.inflate(dx, dy); - } - - @Override - public void applyTransformation(Transformation2D transform) { - _touch(); - transform.transform(m_envelope); - } - - @Override - void applyTransformation(Transformation3D transform) { - _touch(); - if (!m_envelope.isEmpty()) { - Envelope3D env = new Envelope3D(); - queryEnvelope3D(env); - if (env.isEmptyZ()) - env.setEmpty(); // Z components is empty, the - // AffineTransformation3D makes the whole - // envelope empty. Consider - // throwing an assert instead. - else - transform.transform(env); - } - } - - @Override - public void copyTo(Geometry dst) { - if (dst.getType() != getType()) - throw new IllegalArgumentException(); - - Envelope envDst = (Envelope) dst; - dst._touch(); - envDst.m_description = m_description; - envDst.m_envelope.setCoords(m_envelope); - envDst.m_attributes = null; - if (m_attributes != null) { - envDst._ensureAttributes(); - System.arraycopy(m_attributes, 0, envDst.m_attributes, 0, - (m_description.getTotalComponentCount() - 2) * 2); - } - } - - @Override - public Geometry createInstance() { - return new Envelope(m_description); - } - - @Override - public double calculateArea2D() { - return m_envelope.getArea(); - } - - @Override - public double calculateLength2D() { - return m_envelope.getLength(); - } - - @Override - public Geometry.Type getType() { - return Type.Envelope; - } - - @Override - public int getDimension() { - return 2; - } + //We are using writeReplace instead. + //private static final long serialVersionUID = 2L; + + Envelope2D m_envelope = new Envelope2D(); + + double[] m_attributes;// use doubles to store everything + + /** + * Creates an envelope by defining its center, width, and height. + * + * @param center The center point of the envelope. + * @param width The width of the envelope. + * @param height The height of the envelope. + */ + public Envelope(Point center, double width, double height) { + m_description = VertexDescriptionDesignerImpl.getDefaultDescriptor2D(); + m_envelope.setEmpty(); + if (center.isEmpty()) + return; + + _setFromPoint(center, width, height); + } + + public Envelope(Envelope2D env2D) { + m_description = VertexDescriptionDesignerImpl.getDefaultDescriptor2D(); + m_envelope.setCoords(env2D); + m_envelope.normalize(); + } + + public Envelope(VertexDescription vd) { + if (vd == null) + throw new IllegalArgumentException(); + m_description = vd; + m_envelope.setEmpty(); + } + + public Envelope(VertexDescription vd, Envelope2D env2D) { + if (vd == null) + throw new IllegalArgumentException(); + m_description = vd; + m_envelope.setCoords(env2D); + m_envelope.normalize(); + } + + /** + * Constructs an empty envelope. + */ + public Envelope() { + m_description = VertexDescriptionDesignerImpl.getDefaultDescriptor2D(); + m_envelope.setEmpty(); + } + + /** + * Constructs an envelope that covers the given point. The coordinates of + * the point are used to set the extent of the envelope. + * + * @param point The point that the envelope covers. + */ + public Envelope(Point point) { + m_description = VertexDescriptionDesignerImpl.getDefaultDescriptor2D(); + m_envelope.setEmpty(); + if (point.isEmpty()) + return; + + _setFromPoint(point); + } + + /** + * Constructs an envelope with the specified X and Y extents. + * + * @param xmin The minimum x-coordinate of the envelope. + * @param ymin The minimum y-coordinate of the envelope. + * @param xmax The maximum x-coordinate of the envelope. + * @param ymax The maximum y-coordinate of the envelope. + */ + public Envelope(double xmin, double ymin, double xmax, double ymax) { + m_description = VertexDescriptionDesignerImpl.getDefaultDescriptor2D(); + setCoords(xmin, ymin, xmax, ymax); + } + + /** + * Sets the 2-dimensional extents of the envelope. + * + * @param xmin The minimum x-coordinate of the envelope. + * @param ymin The minimum y-coordinate of the envelope. + * @param xmax The maximum x-coordinate of the envelope. + * @param ymax The maximum y-coordinate of the envelope. + */ + public void setCoords(double xmin, double ymin, double xmax, double ymax) { + _touch(); + m_envelope.setCoords(xmin, ymin, xmax, ymax); + } + + /** + * Sets the envelope from the array of points. The result envelope is a + * bounding box of all the points in the array. If the array has zero + * length, the envelope will be empty. + * + * @param points The point array. + */ + void setCoords(Point[] points) { + _touch(); + setEmpty(); + for (int i = 0, n = points.length; i < n; i++) + merge(points[i]); + } + + void setEnvelope2D(Envelope2D e2d) { + _touch(); + if (!e2d.isValid()) + throw new IllegalArgumentException(); + m_envelope.setCoords(e2d); + } + + /** + * Removes all points from this geometry. + */ + @Override + public void setEmpty() { + _touch(); + m_envelope.setEmpty(); + } + + /** + * Indicates whether this envelope contains any points. + * + * @return boolean Returns true if the envelope is empty. + */ @Override - public long estimateMemorySize() - { + public boolean isEmpty() { + return m_envelope.isEmpty(); + } + + /** + * The width of the envelope. + * + * @return The width of the envelope. + */ + + public double getWidth() { + return m_envelope.getWidth(); + } + + /** + * The height of the envelope. + * + * @return The height of the envelope. + */ + public double getHeight() { + return m_envelope.getHeight(); + } + + /** + * The x-coordinate of the center of the envelope. + * + * @return The x-coordinate of the center of the envelope. + */ + public double getCenterX() { + return m_envelope.getCenterX(); + } + + /** + * The y-coordinate of center of the envelope. + * + * @return The y-coordinate of center of the envelope. + */ + public double getCenterY() { + return m_envelope.getCenterY(); + } + + /** + * The x and y-coordinates of the center of the envelope. + * + * @return A point whose x and y-coordinates are that of the center of the envelope. + */ + public Point2D getCenterXY() { + return m_envelope.getCenter(); + } + + public void getCenter(Point point_out) { + point_out.assignVertexDescription(m_description); + if (isEmpty()) { + point_out.setEmpty(); + return; + } + + int nattrib = m_description.getAttributeCount(); + for (int i = 1; i < nattrib; i++) { + int semantics = m_description.getSemantics(i); + int ncomp = VertexDescription.getComponentCount(semantics); + for (int iord = 0; iord < ncomp; iord++) { + double v = 0.5 * (getAttributeAsDblImpl_(0, semantics, iord) + getAttributeAsDblImpl_( + 1, semantics, iord)); + point_out.setAttribute(semantics, iord, v); + } + } + point_out.setXY(m_envelope.getCenter()); + } + + public void merge(Point2D pt) { + _touch(); + m_envelope.merge(pt); + } + + /** + * Merges this envelope with the extent of the given envelope. If this + * envelope is empty, the coordinates of the given envelope + * are assigned. If the given envelope is empty, this envelope is unchanged. + * + * @param other The envelope to merge. + */ + public void merge(Envelope other) { + _touch(); + if (other.isEmpty()) + return; + + VertexDescription otherVD = other.m_description; + if (otherVD != m_description) + mergeVertexDescription(otherVD); + m_envelope.merge(other.m_envelope); + for (int iattrib = 1, nattrib = otherVD.getAttributeCount(); iattrib < nattrib; iattrib++) { + int semantics = otherVD.getSemantics(iattrib); + int ncomps = VertexDescription.getComponentCount(semantics); + for (int iord = 0; iord < ncomps; iord++) { + Envelope1D intervalOther = other.queryInterval(semantics, iord); + Envelope1D interval = queryInterval(semantics, iord); + interval.merge(intervalOther); + setInterval(semantics, iord, interval); + } + } + } + + /** + * Merges this envelope with the point. The boundary of the envelope is + * increased to include the point. If the envelope is empty, the coordinates + * of the point to merge are assigned. If the point is empty, the original + * envelope is unchanged. + * + * @param point The point to be merged. + */ + public void merge(Point point) { + _touch(); + if (point.isEmptyImpl()) + return; + + VertexDescription pointVD = point.m_description; + if (m_description != pointVD) + mergeVertexDescription(pointVD); + + if (isEmpty()) { + _setFromPoint(point); + return; + } + + m_envelope.merge(point.getXY()); + for (int iattrib = 1, nattrib = pointVD.getAttributeCount(); iattrib < nattrib; iattrib++) { + int semantics = pointVD._getSemanticsImpl(iattrib); + int ncomps = VertexDescription.getComponentCount(semantics); + for (int iord = 0; iord < ncomps; iord++) { + double v = point.getAttributeAsDbl(semantics, iord); + Envelope1D interval = queryInterval(semantics, iord); + interval.merge(v); + setInterval(semantics, iord, interval); + } + } + } + + void _setFromPoint(Point centerPoint, double width, double height) { + m_envelope.setCoords(centerPoint.getXY(), width, height); + VertexDescription pointVD = centerPoint.m_description; + for (int iattrib = 1, nattrib = pointVD.getAttributeCount(); iattrib < nattrib; iattrib++) { + int semantics = pointVD._getSemanticsImpl(iattrib); + int ncomps = VertexDescription.getComponentCount(semantics); + for (int iord = 0; iord < ncomps; iord++) { + double v = centerPoint.getAttributeAsDbl(semantics, iord); + setInterval(semantics, iord, v, v); + } + } + } + + void _setFromPoint(Point centerPoint) { + m_envelope.setCoords(centerPoint.m_attributes[0], + centerPoint.m_attributes[1]); + VertexDescription pointVD = centerPoint.m_description; + for (int iattrib = 1, nattrib = pointVD.getAttributeCount(); iattrib < nattrib; iattrib++) { + int semantics = pointVD._getSemanticsImpl(iattrib); + int ncomps = VertexDescription.getComponentCount(semantics); + for (int iord = 0; iord < ncomps; iord++) { + double v = centerPoint.getAttributeAsDbl(semantics, iord); + setInterval(semantics, iord, v, v); + } + } + } + + public void merge(Envelope2D other) { + _touch(); + m_envelope.merge(other); + } + + public void setInterval(int semantics, int ordinate, double vmin, + double vmax) { + setInterval(semantics, ordinate, new Envelope1D(vmin, vmax)); + } + + /** + * Re-aspects this envelope to fit within the specified width and height. + * + * @param arWidth The width within which to fit the envelope. + * @param arHeight The height within which to fit the envelope. + */ + public void reaspect(double arWidth, double arHeight) { + _touch(); + m_envelope.reaspect(arWidth, arHeight); + } + + /** + * Changes the dimensions of the envelope while preserving the center. New width + * is Width + 2 * dx, new height is Height + 2 * dy. If the result envelope + * width or height becomes negative, the envelope is set to be empty. + * + * @param dx The inflation along the x-axis. + * @param dy The inflation along the y-axis. + */ + public void inflate(double dx, double dy) { + _touch(); + m_envelope.inflate(dx, dy); + } + + @Override + public void applyTransformation(Transformation2D transform) { + _touch(); + transform.transform(m_envelope); + } + + @Override + void applyTransformation(Transformation3D transform) { + _touch(); + if (!m_envelope.isEmpty()) { + Envelope3D env = new Envelope3D(); + queryEnvelope3D(env); + if (env.isEmptyZ()) + env.setEmpty(); // Z components is empty, the + // AffineTransformation3D makes the whole + // envelope empty. Consider + // throwing an assert instead. + else + transform.transform(env); + } + } + + @Override + public void copyTo(Geometry dst) { + if (dst.getType() != getType()) + throw new IllegalArgumentException(); + + Envelope envDst = (Envelope) dst; + dst._touch(); + envDst.m_description = m_description; + envDst.m_envelope.setCoords(m_envelope); + envDst.m_attributes = null; + if (m_attributes != null) { + envDst._ensureAttributes(); + System.arraycopy(m_attributes, 0, envDst.m_attributes, 0, + (m_description.getTotalComponentCount() - 2) * 2); + } + } + + @Override + public Geometry createInstance() { + return new Envelope(m_description); + } + + @Override + public double calculateArea2D() { + return m_envelope.getArea(); + } + + @Override + public double calculateLength2D() { + return m_envelope.getLength(); + } + + @Override + public Geometry.Type getType() { + return Type.Envelope; + } + + @Override + public int getDimension() { + return 2; + } + + @Override + public long estimateMemorySize() { return SIZE_OF_ENVELOPE + m_envelope.estimateMemorySize() + estimateMemorySize(m_attributes); } @@ -439,676 +438,676 @@ public void queryEnvelope(Envelope env) { copyTo(env); } - @Override - public void queryEnvelope2D(Envelope2D env) { - env.xmin = m_envelope.xmin; - env.ymin = m_envelope.ymin; - env.xmax = m_envelope.xmax; - env.ymax = m_envelope.ymax; - } - - @Override - void queryEnvelope3D(Envelope3D env) { - env.xmin = m_envelope.xmin; - env.ymin = m_envelope.ymin; - env.xmax = m_envelope.xmax; - env.ymax = m_envelope.ymax; - env.setCoords(m_envelope.xmin, m_envelope.ymin, - _getAttributeAsDbl(0, Semantics.Z, 0), m_envelope.xmax, - m_envelope.ymax, _getAttributeAsDbl(1, Semantics.Z, 0)); - } - - @Override - public Envelope1D queryInterval(int semantics, int ordinate) { - Envelope1D env = new Envelope1D(); - env.setCoords(_getAttributeAsDbl(0, semantics, ordinate), - _getAttributeAsDbl(1, semantics, ordinate)); - return env; - } - - public void setInterval(int semantics, int ordinate, Envelope1D env) { - _touch(); - if (semantics == Semantics.POSITION) { - if (ordinate == 0) { - m_envelope.xmin = env.vmin; - m_envelope.xmax = env.vmax; - } else if (ordinate == 1) { - m_envelope.ymin = env.vmin; - m_envelope.ymax = env.vmax; - } else - throw new IndexOutOfBoundsException(); - } else { - _setAttributeAsDbl(0, semantics, ordinate, env.vmin); - _setAttributeAsDbl(1, semantics, ordinate, env.vmax); - } - } - - public void queryCoordinates(Point2D[] dst) { - if (dst == null || dst.length < 4 || m_envelope.isEmpty()) - throw new IllegalArgumentException(); - - m_envelope.queryCorners(dst); - } - - /** - * Sets the point's coordinates to the coordinates of the envelope at the - * given corner. - * - * @param index The index of the envelope's corners from 0 to 3. - *

- * 0 = lower left corner - *

- * 1 = top-left corner - *

- * 2 = top right corner - *

- * 3 = bottom right corner - * @param ptDst The point whose coordinates are used to set the envelope's - * coordinate at a specified corner. - */ - public void queryCornerByVal(int index, Point ptDst) { - ptDst.assignVertexDescription(m_description); - int nattrib = getDescription().getAttributeCount() - 1; - switch (index) { - case 0: { - for (int i = 0; i < nattrib; i++) { - int semantics = m_description.getSemantics(i); - int ncomp = VertexDescription.getComponentCount(semantics); - for (int iord = 0; iord < ncomp; iord++) - ptDst.setAttribute(semantics, iord, - _getAttributeAsDbl(0, semantics, iord)); - } - ptDst.setXY(m_envelope.xmin, m_envelope.ymin); - return; - } - - case 1: { - for (int i = 0; i < nattrib; i++) { - int semantics = m_description.getSemantics(i); - int ncomp = VertexDescription.getComponentCount(semantics); - for (int iord = 0; iord < ncomp; iord++) - ptDst.setAttribute(semantics, iord, - _getAttributeAsDbl(1, semantics, iord)); - } - ptDst.setXY(m_envelope.xmin, m_envelope.ymax); - return; - } - case 2: { - for (int i = 0; i < nattrib; i++) { - int semantics = m_description.getSemantics(i); - int ncomp = VertexDescription.getComponentCount(semantics); - for (int iord = 0; iord < ncomp; iord++) - ptDst.setAttribute(semantics, iord, - _getAttributeAsDbl(0, semantics, iord)); - } - ptDst.setXY(m_envelope.xmax, m_envelope.ymax); - - return; - } - case 3: { - for (int i = 0; i < nattrib; i++) { - int semantics = m_description.getSemantics(i); - int ncomp = VertexDescription.getComponentCount(semantics); - for (int iord = 0; iord < ncomp; iord++) - ptDst.setAttribute(semantics, iord, - _getAttributeAsDbl(1, semantics, iord)); - } - ptDst.setXY(m_envelope.xmax, m_envelope.ymin); - return; - } - default: - throw new IndexOutOfBoundsException(); - } - } - - public void queryCorner(int index, Point2D ptDst) { - Point2D p = m_envelope.queryCorner(index); - ptDst.setCoords(p.x, p.y); - } - - int getEndPointOffset(VertexDescription descr, int end_point) { - return end_point * (descr.getTotalComponentCount() - 2); - } - - double getAttributeAsDblImpl_(int end_point, int semantics, int ordinate) { - if (m_envelope.isEmpty()) - throw new GeometryException("empty geometry"); - - assert (end_point == 0 || end_point == 1); - - if (semantics == VertexDescription.Semantics.POSITION) { - if (end_point != 0) { - return ordinate != 0 ? m_envelope.ymax : m_envelope.xmax; - } else { - return ordinate != 0 ? m_envelope.ymin : m_envelope.xmin; - } - } - - int ncomps = VertexDescription.getComponentCount(semantics); - if (ordinate >= ncomps) - throw new IllegalArgumentException(); - - int attribute_index = m_description.getAttributeIndex(semantics); - _ensureAttributes(); - if (attribute_index >= 0) { - return m_attributes[getEndPointOffset(m_description, end_point) - + m_description.getPointAttributeOffset_(attribute_index) - - 2 + ordinate]; - } - - return VertexDescription.getDefaultValue(semantics); - } - - void setAttributeAsDblImpl_(int end_point, int semantics, int ordinate, - double value) { - assert (end_point == 0 || end_point == 1); - - if (semantics == VertexDescription.Semantics.POSITION) { - if (end_point != 0) { - if (ordinate != 0) - m_envelope.ymax = value; - else - m_envelope.xmax = value; - } else { - if (ordinate != 0) - m_envelope.ymin = value; - else - m_envelope.xmin = value; - } - - return; - } - - int ncomps = VertexDescription.getComponentCount(semantics); - if (ordinate >= ncomps) - throw new IllegalArgumentException(); - - addAttribute(semantics); - _ensureAttributes(); - int attribute_index = m_description.getAttributeIndex(semantics); - m_attributes[getEndPointOffset(m_description, end_point) - + m_description.getPointAttributeOffset_(attribute_index) - 2 - + ordinate] = value; - } - - void _ensureAttributes() { - _touch(); - if (m_attributes == null && m_description.getTotalComponentCount() > 2) { - m_attributes = new double[(m_description.getTotalComponentCount() - 2) * 2]; - int offset0 = _getEndPointOffset(m_description, 0); - int offset1 = _getEndPointOffset(m_description, 1); - - int j = 0; - for (int i = 1, n = m_description.getAttributeCount(); i < n; i++) { - int semantics = m_description.getSemantics(i); - int nords = VertexDescription.getComponentCount(semantics); - double d = VertexDescription.getDefaultValue(semantics); - for (int ord = 0; ord < nords; ord++) { - m_attributes[offset0 + j] = d; - m_attributes[offset1 + j] = d; - j++; - } - } - } - } - - @Override - protected void _assignVertexDescriptionImpl(VertexDescription newDescription) { - if (m_attributes == null) { - m_description = newDescription; - return; - } - - if (newDescription.getTotalComponentCount() > 2) { - int[] mapping = VertexDescriptionDesignerImpl.mapAttributes(newDescription, m_description); - - double[] newAttributes = new double[(newDescription.getTotalComponentCount() - 2) * 2]; - - int old_offset0 = _getEndPointOffset(m_description, 0); - int old_offset1 = _getEndPointOffset(m_description, 1); - - int new_offset0 = _getEndPointOffset(newDescription, 0); - int new_offset1 = _getEndPointOffset(newDescription, 1); - - int j = 0; - for (int i = 1, n = newDescription.getAttributeCount(); i < n; i++) { - int semantics = newDescription.getSemantics(i); - int nords = VertexDescription.getComponentCount(semantics); - if (mapping[i] == -1) { - double d = VertexDescription.getDefaultValue(semantics); - for (int ord = 0; ord < nords; ord++) { - newAttributes[new_offset0 + j] = d; - newAttributes[new_offset1 + j] = d; - j++; - } - } else { - int m = mapping[i]; - int offset = m_description._getPointAttributeOffset(m) - 2; - for (int ord = 0; ord < nords; ord++) { - newAttributes[new_offset0 + j] = m_attributes[old_offset0 + offset]; - newAttributes[new_offset1 + j] = m_attributes[old_offset1 + offset]; - j++; - offset++; - } - } - - } - - m_attributes = newAttributes; - } else { - m_attributes = null; - } - - m_description = newDescription; - } - - double _getAttributeAsDbl(int endPoint, int semantics, int ordinate) { - if (m_envelope.isEmpty()) - throw new GeometryException( - "This operation was performed on an Empty Geometry."); - - // _ASSERT(endPoint == 0 || endPoint == 1); - - if (semantics == Semantics.POSITION) { - if (endPoint != 0) { - return ordinate != 0 ? m_envelope.ymax : m_envelope.xmax; - } else { - return ordinate != 0 ? m_envelope.ymin : m_envelope.xmin; - } - } - - int ncomps = VertexDescription.getComponentCount(semantics); - if (ordinate >= ncomps) - throw new IndexOutOfBoundsException(); - - int attributeIndex = m_description.getAttributeIndex(semantics); - if (attributeIndex >= 0) { - _ensureAttributes(); - return m_attributes[_getEndPointOffset(m_description, endPoint) - + m_description._getPointAttributeOffset(attributeIndex) - - 2 + ordinate]; - } else - return VertexDescription.getDefaultValue(semantics); - } - - void _setAttributeAsDbl(int endPoint, int semantics, int ordinate, - double value) { - _touch(); - // _ASSERT(endPoint == 0 || endPoint == 1); - - if (semantics == Semantics.POSITION) { - if (endPoint != 0) { - if (ordinate != 0) - m_envelope.ymax = value; - else - m_envelope.xmax = value; - } else { - if (ordinate != 0) - m_envelope.ymin = value; - else - m_envelope.xmin = value; - } - - return; - } - - int ncomps = VertexDescription.getComponentCount(semantics); - if (ordinate >= ncomps) - throw new IndexOutOfBoundsException(); - - if (!hasAttribute(semantics)) { - if (VertexDescription.isDefaultValue(semantics, value)) - return; - - addAttribute(semantics); - } - - int attributeIndex = m_description.getAttributeIndex(semantics); - _ensureAttributes(); - m_attributes[_getEndPointOffset(m_description, endPoint) - + m_description._getPointAttributeOffset(attributeIndex) - 2 - + ordinate] = value; - } - - int _getAttributeAsInt(int endPoint, int semantics, int ordinate) { - return (int) _getAttributeAsDbl(endPoint, semantics, ordinate); - } - - static int _getEndPointOffset(VertexDescription vd, int endPoint) { - return endPoint * (vd.getTotalComponentCount() - 2); - } - - public boolean isIntersecting(Envelope2D other) { - return m_envelope.isIntersecting(other); - } - - /** - * Changes this envelope to be the intersection of itself with the other - * envelope. - * - * @param other The envelope to intersect. - * @return Returns true if the result is not empty. - */ - public boolean intersect(Envelope other) { - _touch(); - Envelope2D e2d = new Envelope2D(); - other.queryEnvelope2D(e2d); - return m_envelope.intersect(e2d); - } - - /** - * Returns true if the envelope and the other given envelope intersect. - * - * @param other The envelope to with which to test intersection. - * @return Returns true if the two envelopes intersect. - */ - public boolean isIntersecting(Envelope other) {// TODO: attributes. - return m_envelope.isIntersecting(other.m_envelope); - } - - /** - * Sets the envelope's corners to be centered around the specified point, - * using its center, width, and height. - * - * @param c The point around which to center the envelope. - * @param w The width to be set for the envelope. - * @param h The height to be set for this envelope. - */ - public void centerAt(Point c, double w, double h) { - _touch(); - if (c.isEmpty()) { - setEmpty(); - return; - } - - _setFromPoint(c, w, h); - } - - /** - * Offsets the envelope by the specified distances along x and y-coordinates. - * - * @param dx The X offset to be applied. - * @param dy The Y offset to be applied. - */ - public void offset(double dx, double dy) { - _touch(); - m_envelope.offset(dx, dy); - } - - /** - * Normalizes envelopes if the minimum dimension is larger than the - * maximum dimension. - */ - public void normalize() {// TODO: attributes - _touch(); - m_envelope.normalize(); - } - - /** - * Gets the center point of the envelope. The center point occurs at: ((XMin - * + XMax) / 2, (YMin + YMax) / 2). - * - * @return The center point of the envelope. - */ - public Point2D getCenter2D() { - return m_envelope.getCenter(); - } - - /** - * Returns the center point of the envelope. - * - * @return The center point of the envelope. - */ - public Point getCenter() { - Point pointOut = new Point(m_description); - if (isEmpty()) { - return pointOut; - } - int nattrib = m_description.getAttributeCount(); - for (int i = 1; i < nattrib; i++) { - int semantics = m_description._getSemanticsImpl(i); - int ncomp = VertexDescription.getComponentCount(semantics); - for (int iord = 0; iord < ncomp; iord++) { - double v = 0.5 * (_getAttributeAsDbl(0, semantics, iord) + _getAttributeAsDbl( - 1, semantics, iord)); - pointOut.setAttribute(semantics, iord, v); - } - } - pointOut.setXY(m_envelope.getCenterX(), m_envelope.getCenterY()); - return pointOut; - } - - /** - * Centers the envelope around the specified point preserving the envelope's - * width and height. - * - * @param c The new center point. - */ - public void centerAt(Point c) { - _touch(); - if (c.isEmpty()) { - setEmpty(); - return; - } - m_envelope.centerAt(c.getX(), c.getY()); - } - - /** - * Returns the envelope's lower left corner point. - * - * @return Returns the lower left corner point. - */ - public Point getLowerLeft() { - return new Point(m_envelope.getLowerLeft()); - } - - /** - * Returns the envelope's upper right corner point. - * - * @return Returns the upper right corner point. - */ - public Point getUpperRight() { - return new Point(m_envelope.getUpperRight()); - } - - /** - * Returns the envelope's lower right corner point. - * - * @return Returns the lower right corner point. - */ - public Point getLowerRight() { - return new Point(m_envelope.getLowerRight()); - } - - /** - * Returns the envelope's upper left corner point. - * - * @return Returns the upper left corner point. - */ - public Point getUpperLeft() { - return new Point(m_envelope.getUpperLeft()); - } - - /** - * Checks if this envelope contains (covers) the specified point. - * - * @param p The Point to be tested for coverage. - * @return TRUE if this envelope contains (covers) the specified point. - */ - public boolean contains(Point p) { - if (p.isEmpty()) - return false; - return m_envelope.contains(p.getX(), p.getY()); - } - - /** - * Checks if this envelope contains (covers) other envelope. - * - * @param env The envelope to be tested for coverage. - * @return TRUE if this envelope contains (covers) the specified envelope. - */ - public boolean contains(Envelope env) { - return m_envelope.contains(env.m_envelope); - } - - /** - * Returns TRUE when this geometry has exactly same type, properties, and - * coordinates as the other geometry. - */ - @Override - public boolean equals(Object _other) { - if (_other == this) - return true; - - if (!(_other instanceof Envelope)) - return false; - - Envelope other = (Envelope) _other; - - if (m_description != other.m_description) - return false; - - if (isEmpty()) - if (other.isEmpty()) - return true; - else - return false; - - if (!this.m_envelope.equals(other.m_envelope)) - return false; - - for (int i = 0, n = (m_description.getTotalComponentCount() - 2) * 2; i < n; i++) - if (m_attributes[i] != other.m_attributes[i]) - return false; - - return true; - } - - /** - * Returns a hash code value for this envelope. - * - * @return A hash code value for this envelope. - */ - @Override - public int hashCode() { - int hashCode = m_description.hashCode(); - hashCode = NumberUtils.hash(hashCode, m_envelope.hashCode()); - if (!isEmpty() && m_attributes != null) { - for (int i = 0, n = (m_description.getTotalComponentCount() - 2) * 2; i < n; i++) { - hashCode = NumberUtils.hash(hashCode, m_attributes[i]); - } - } - return hashCode; - } - - /** - * Returns the X coordinate of the left corners of the envelope. - * - * @return The X coordinate of the left corners. - */ - public final double getXMin() { - return m_envelope.xmin; - } - - /** - * Returns the Y coordinate of the bottom corners of the envelope. - * - * @return The Y coordinate of the bottom corners. - */ - public final double getYMin() { - return m_envelope.ymin; - } - - /** - * Returns the X coordinate of the right corners of the envelope. - * - * @return The X coordinate of the right corners. - */ - public final double getXMax() { - return m_envelope.xmax; - } - - /** - * Returns the Y coordinate of the top corners of the envelope. - * - * @return The Y coordinate of the top corners. - */ - public final double getYMax() { - return m_envelope.ymax; - } - - /** - * Sets the left X coordinate. - * - * @param x The X coordinate of the left corner - */ - public void setXMin(double x) { - _touch(); - m_envelope.xmin = x; - } - - /** - * Sets the right X coordinate. - * - * @param x The X coordinate of the right corner. - */ - public void setXMax(double x) { - _touch(); - m_envelope.xmax = x; - } - - /** - * Sets the bottom Y coordinate. - * - * @param y the Y coordinate of the bottom corner. - */ - public void setYMin(double y) { - _touch(); - m_envelope.ymin = y; - } - - /** - * Sets the top Y coordinate. - * - * @param y The Y coordinate of the top corner. - */ - public void setYMax(double y) { - _touch(); - m_envelope.ymax = y; - } - - @Override - public Geometry getBoundary() { - return Boundary.calculate(this, null); - } - - @Override - public void replaceNaNs(int semantics, double value) { - addAttribute(semantics); - if (isEmpty()) - return; - - int ncomps = VertexDescription.getComponentCount(semantics); - for (int i = 0; i < ncomps; i++) { - Envelope1D interval = queryInterval(semantics, i); - if (interval.isEmpty()) { - interval.vmin = value; - interval.vmax = value; - setInterval(semantics, i, interval); - } - } - } - - /** - * The output of this method can be only used for debugging. It is subject to change without notice. - */ - @Override - public String toString() { - if (isEmpty()) - return "Envelope: []"; - - String s = "Envelope: [" + m_envelope.xmin + ", " + m_envelope.ymin + ", " + m_envelope.xmax + ", " + m_envelope.ymax + "]"; - return s; - } + @Override + public void queryEnvelope2D(Envelope2D env) { + env.xmin = m_envelope.xmin; + env.ymin = m_envelope.ymin; + env.xmax = m_envelope.xmax; + env.ymax = m_envelope.ymax; + } + + @Override + void queryEnvelope3D(Envelope3D env) { + env.xmin = m_envelope.xmin; + env.ymin = m_envelope.ymin; + env.xmax = m_envelope.xmax; + env.ymax = m_envelope.ymax; + env.setCoords(m_envelope.xmin, m_envelope.ymin, + _getAttributeAsDbl(0, Semantics.Z, 0), m_envelope.xmax, + m_envelope.ymax, _getAttributeAsDbl(1, Semantics.Z, 0)); + } + + @Override + public Envelope1D queryInterval(int semantics, int ordinate) { + Envelope1D env = new Envelope1D(); + env.setCoords(_getAttributeAsDbl(0, semantics, ordinate), + _getAttributeAsDbl(1, semantics, ordinate)); + return env; + } + + public void setInterval(int semantics, int ordinate, Envelope1D env) { + _touch(); + if (semantics == Semantics.POSITION) { + if (ordinate == 0) { + m_envelope.xmin = env.vmin; + m_envelope.xmax = env.vmax; + } else if (ordinate == 1) { + m_envelope.ymin = env.vmin; + m_envelope.ymax = env.vmax; + } else + throw new IndexOutOfBoundsException(); + } else { + _setAttributeAsDbl(0, semantics, ordinate, env.vmin); + _setAttributeAsDbl(1, semantics, ordinate, env.vmax); + } + } + + public void queryCoordinates(Point2D[] dst) { + if (dst == null || dst.length < 4 || m_envelope.isEmpty()) + throw new IllegalArgumentException(); + + m_envelope.queryCorners(dst); + } + + /** + * Sets the point's coordinates to the coordinates of the envelope at the + * given corner. + * + * @param index The index of the envelope's corners from 0 to 3. + *

+ * 0 = lower left corner + *

+ * 1 = top-left corner + *

+ * 2 = top right corner + *

+ * 3 = bottom right corner + * @param ptDst The point whose coordinates are used to set the envelope's + * coordinate at a specified corner. + */ + public void queryCornerByVal(int index, Point ptDst) { + ptDst.assignVertexDescription(m_description); + int nattrib = getDescription().getAttributeCount() - 1; + switch (index) { + case 0: { + for (int i = 0; i < nattrib; i++) { + int semantics = m_description.getSemantics(i); + int ncomp = VertexDescription.getComponentCount(semantics); + for (int iord = 0; iord < ncomp; iord++) + ptDst.setAttribute(semantics, iord, + _getAttributeAsDbl(0, semantics, iord)); + } + ptDst.setXY(m_envelope.xmin, m_envelope.ymin); + return; + } + + case 1: { + for (int i = 0; i < nattrib; i++) { + int semantics = m_description.getSemantics(i); + int ncomp = VertexDescription.getComponentCount(semantics); + for (int iord = 0; iord < ncomp; iord++) + ptDst.setAttribute(semantics, iord, + _getAttributeAsDbl(1, semantics, iord)); + } + ptDst.setXY(m_envelope.xmin, m_envelope.ymax); + return; + } + case 2: { + for (int i = 0; i < nattrib; i++) { + int semantics = m_description.getSemantics(i); + int ncomp = VertexDescription.getComponentCount(semantics); + for (int iord = 0; iord < ncomp; iord++) + ptDst.setAttribute(semantics, iord, + _getAttributeAsDbl(0, semantics, iord)); + } + ptDst.setXY(m_envelope.xmax, m_envelope.ymax); + + return; + } + case 3: { + for (int i = 0; i < nattrib; i++) { + int semantics = m_description.getSemantics(i); + int ncomp = VertexDescription.getComponentCount(semantics); + for (int iord = 0; iord < ncomp; iord++) + ptDst.setAttribute(semantics, iord, + _getAttributeAsDbl(1, semantics, iord)); + } + ptDst.setXY(m_envelope.xmax, m_envelope.ymin); + return; + } + default: + throw new IndexOutOfBoundsException(); + } + } + + public void queryCorner(int index, Point2D ptDst) { + Point2D p = m_envelope.queryCorner(index); + ptDst.setCoords(p.x, p.y); + } + + int getEndPointOffset(VertexDescription descr, int end_point) { + return end_point * (descr.getTotalComponentCount() - 2); + } + + double getAttributeAsDblImpl_(int end_point, int semantics, int ordinate) { + if (m_envelope.isEmpty()) + throw new GeometryException("empty geometry"); + + assert (end_point == 0 || end_point == 1); + + if (semantics == VertexDescription.Semantics.POSITION) { + if (end_point != 0) { + return ordinate != 0 ? m_envelope.ymax : m_envelope.xmax; + } else { + return ordinate != 0 ? m_envelope.ymin : m_envelope.xmin; + } + } + + int ncomps = VertexDescription.getComponentCount(semantics); + if (ordinate >= ncomps) + throw new IllegalArgumentException(); + + int attribute_index = m_description.getAttributeIndex(semantics); + _ensureAttributes(); + if (attribute_index >= 0) { + return m_attributes[getEndPointOffset(m_description, end_point) + + m_description.getPointAttributeOffset_(attribute_index) + - 2 + ordinate]; + } + + return VertexDescription.getDefaultValue(semantics); + } + + void setAttributeAsDblImpl_(int end_point, int semantics, int ordinate, + double value) { + assert (end_point == 0 || end_point == 1); + + if (semantics == VertexDescription.Semantics.POSITION) { + if (end_point != 0) { + if (ordinate != 0) + m_envelope.ymax = value; + else + m_envelope.xmax = value; + } else { + if (ordinate != 0) + m_envelope.ymin = value; + else + m_envelope.xmin = value; + } + + return; + } + + int ncomps = VertexDescription.getComponentCount(semantics); + if (ordinate >= ncomps) + throw new IllegalArgumentException(); + + addAttribute(semantics); + _ensureAttributes(); + int attribute_index = m_description.getAttributeIndex(semantics); + m_attributes[getEndPointOffset(m_description, end_point) + + m_description.getPointAttributeOffset_(attribute_index) - 2 + + ordinate] = value; + } + + void _ensureAttributes() { + _touch(); + if (m_attributes == null && m_description.getTotalComponentCount() > 2) { + m_attributes = new double[(m_description.getTotalComponentCount() - 2) * 2]; + int offset0 = _getEndPointOffset(m_description, 0); + int offset1 = _getEndPointOffset(m_description, 1); + + int j = 0; + for (int i = 1, n = m_description.getAttributeCount(); i < n; i++) { + int semantics = m_description.getSemantics(i); + int nords = VertexDescription.getComponentCount(semantics); + double d = VertexDescription.getDefaultValue(semantics); + for (int ord = 0; ord < nords; ord++) { + m_attributes[offset0 + j] = d; + m_attributes[offset1 + j] = d; + j++; + } + } + } + } + + @Override + protected void _assignVertexDescriptionImpl(VertexDescription newDescription) { + if (m_attributes == null) { + m_description = newDescription; + return; + } + + if (newDescription.getTotalComponentCount() > 2) { + int[] mapping = VertexDescriptionDesignerImpl.mapAttributes(newDescription, m_description); + + double[] newAttributes = new double[(newDescription.getTotalComponentCount() - 2) * 2]; + + int old_offset0 = _getEndPointOffset(m_description, 0); + int old_offset1 = _getEndPointOffset(m_description, 1); + + int new_offset0 = _getEndPointOffset(newDescription, 0); + int new_offset1 = _getEndPointOffset(newDescription, 1); + + int j = 0; + for (int i = 1, n = newDescription.getAttributeCount(); i < n; i++) { + int semantics = newDescription.getSemantics(i); + int nords = VertexDescription.getComponentCount(semantics); + if (mapping[i] == -1) { + double d = VertexDescription.getDefaultValue(semantics); + for (int ord = 0; ord < nords; ord++) { + newAttributes[new_offset0 + j] = d; + newAttributes[new_offset1 + j] = d; + j++; + } + } else { + int m = mapping[i]; + int offset = m_description._getPointAttributeOffset(m) - 2; + for (int ord = 0; ord < nords; ord++) { + newAttributes[new_offset0 + j] = m_attributes[old_offset0 + offset]; + newAttributes[new_offset1 + j] = m_attributes[old_offset1 + offset]; + j++; + offset++; + } + } + + } + + m_attributes = newAttributes; + } else { + m_attributes = null; + } + + m_description = newDescription; + } + + double _getAttributeAsDbl(int endPoint, int semantics, int ordinate) { + if (m_envelope.isEmpty()) + throw new GeometryException( + "This operation was performed on an Empty Geometry."); + + // _ASSERT(endPoint == 0 || endPoint == 1); + + if (semantics == Semantics.POSITION) { + if (endPoint != 0) { + return ordinate != 0 ? m_envelope.ymax : m_envelope.xmax; + } else { + return ordinate != 0 ? m_envelope.ymin : m_envelope.xmin; + } + } + + int ncomps = VertexDescription.getComponentCount(semantics); + if (ordinate >= ncomps) + throw new IndexOutOfBoundsException(); + + int attributeIndex = m_description.getAttributeIndex(semantics); + if (attributeIndex >= 0) { + _ensureAttributes(); + return m_attributes[_getEndPointOffset(m_description, endPoint) + + m_description._getPointAttributeOffset(attributeIndex) + - 2 + ordinate]; + } else + return VertexDescription.getDefaultValue(semantics); + } + + void _setAttributeAsDbl(int endPoint, int semantics, int ordinate, + double value) { + _touch(); + // _ASSERT(endPoint == 0 || endPoint == 1); + + if (semantics == Semantics.POSITION) { + if (endPoint != 0) { + if (ordinate != 0) + m_envelope.ymax = value; + else + m_envelope.xmax = value; + } else { + if (ordinate != 0) + m_envelope.ymin = value; + else + m_envelope.xmin = value; + } + + return; + } + + int ncomps = VertexDescription.getComponentCount(semantics); + if (ordinate >= ncomps) + throw new IndexOutOfBoundsException(); + + if (!hasAttribute(semantics)) { + if (VertexDescription.isDefaultValue(semantics, value)) + return; + + addAttribute(semantics); + } + + int attributeIndex = m_description.getAttributeIndex(semantics); + _ensureAttributes(); + m_attributes[_getEndPointOffset(m_description, endPoint) + + m_description._getPointAttributeOffset(attributeIndex) - 2 + + ordinate] = value; + } + + int _getAttributeAsInt(int endPoint, int semantics, int ordinate) { + return (int) _getAttributeAsDbl(endPoint, semantics, ordinate); + } + + static int _getEndPointOffset(VertexDescription vd, int endPoint) { + return endPoint * (vd.getTotalComponentCount() - 2); + } + + public boolean isIntersecting(Envelope2D other) { + return m_envelope.isIntersecting(other); + } + + /** + * Changes this envelope to be the intersection of itself with the other + * envelope. + * + * @param other The envelope to intersect. + * @return Returns true if the result is not empty. + */ + public boolean intersect(Envelope other) { + _touch(); + Envelope2D e2d = new Envelope2D(); + other.queryEnvelope2D(e2d); + return m_envelope.intersect(e2d); + } + + /** + * Returns true if the envelope and the other given envelope intersect. + * + * @param other The envelope to with which to test intersection. + * @return Returns true if the two envelopes intersect. + */ + public boolean isIntersecting(Envelope other) {// TODO: attributes. + return m_envelope.isIntersecting(other.m_envelope); + } + + /** + * Sets the envelope's corners to be centered around the specified point, + * using its center, width, and height. + * + * @param c The point around which to center the envelope. + * @param w The width to be set for the envelope. + * @param h The height to be set for this envelope. + */ + public void centerAt(Point c, double w, double h) { + _touch(); + if (c.isEmpty()) { + setEmpty(); + return; + } + + _setFromPoint(c, w, h); + } + + /** + * Offsets the envelope by the specified distances along x and y-coordinates. + * + * @param dx The X offset to be applied. + * @param dy The Y offset to be applied. + */ + public void offset(double dx, double dy) { + _touch(); + m_envelope.offset(dx, dy); + } + + /** + * Normalizes envelopes if the minimum dimension is larger than the + * maximum dimension. + */ + public void normalize() {// TODO: attributes + _touch(); + m_envelope.normalize(); + } + + /** + * Gets the center point of the envelope. The center point occurs at: ((XMin + * + XMax) / 2, (YMin + YMax) / 2). + * + * @return The center point of the envelope. + */ + public Point2D getCenter2D() { + return m_envelope.getCenter(); + } + + /** + * Returns the center point of the envelope. + * + * @return The center point of the envelope. + */ + public Point getCenter() { + Point pointOut = new Point(m_description); + if (isEmpty()) { + return pointOut; + } + int nattrib = m_description.getAttributeCount(); + for (int i = 1; i < nattrib; i++) { + int semantics = m_description._getSemanticsImpl(i); + int ncomp = VertexDescription.getComponentCount(semantics); + for (int iord = 0; iord < ncomp; iord++) { + double v = 0.5 * (_getAttributeAsDbl(0, semantics, iord) + _getAttributeAsDbl( + 1, semantics, iord)); + pointOut.setAttribute(semantics, iord, v); + } + } + pointOut.setXY(m_envelope.getCenterX(), m_envelope.getCenterY()); + return pointOut; + } + + /** + * Centers the envelope around the specified point preserving the envelope's + * width and height. + * + * @param c The new center point. + */ + public void centerAt(Point c) { + _touch(); + if (c.isEmpty()) { + setEmpty(); + return; + } + m_envelope.centerAt(c.getX(), c.getY()); + } + + /** + * Returns the envelope's lower left corner point. + * + * @return Returns the lower left corner point. + */ + public Point getLowerLeft() { + return new Point(m_envelope.getLowerLeft()); + } + + /** + * Returns the envelope's upper right corner point. + * + * @return Returns the upper right corner point. + */ + public Point getUpperRight() { + return new Point(m_envelope.getUpperRight()); + } + + /** + * Returns the envelope's lower right corner point. + * + * @return Returns the lower right corner point. + */ + public Point getLowerRight() { + return new Point(m_envelope.getLowerRight()); + } + + /** + * Returns the envelope's upper left corner point. + * + * @return Returns the upper left corner point. + */ + public Point getUpperLeft() { + return new Point(m_envelope.getUpperLeft()); + } + + /** + * Checks if this envelope contains (covers) the specified point. + * + * @param p The Point to be tested for coverage. + * @return TRUE if this envelope contains (covers) the specified point. + */ + public boolean contains(Point p) { + if (p.isEmpty()) + return false; + return m_envelope.contains(p.getX(), p.getY()); + } + + /** + * Checks if this envelope contains (covers) other envelope. + * + * @param env The envelope to be tested for coverage. + * @return TRUE if this envelope contains (covers) the specified envelope. + */ + public boolean contains(Envelope env) { + return m_envelope.contains(env.m_envelope); + } + + /** + * Returns TRUE when this geometry has exactly same type, properties, and + * coordinates as the other geometry. + */ + @Override + public boolean equals(Object _other) { + if (_other == this) + return true; + + if (!(_other instanceof Envelope)) + return false; + + Envelope other = (Envelope) _other; + + if (m_description != other.m_description) + return false; + + if (isEmpty()) + if (other.isEmpty()) + return true; + else + return false; + + if (!this.m_envelope.equals(other.m_envelope)) + return false; + + for (int i = 0, n = (m_description.getTotalComponentCount() - 2) * 2; i < n; i++) + if (m_attributes[i] != other.m_attributes[i]) + return false; + + return true; + } + + /** + * Returns a hash code value for this envelope. + * + * @return A hash code value for this envelope. + */ + @Override + public int hashCode() { + int hashCode = m_description.hashCode(); + hashCode = NumberUtils.hash(hashCode, m_envelope.hashCode()); + if (!isEmpty() && m_attributes != null) { + for (int i = 0, n = (m_description.getTotalComponentCount() - 2) * 2; i < n; i++) { + hashCode = NumberUtils.hash(hashCode, m_attributes[i]); + } + } + return hashCode; + } + + /** + * Returns the X coordinate of the left corners of the envelope. + * + * @return The X coordinate of the left corners. + */ + public final double getXMin() { + return m_envelope.xmin; + } + + /** + * Returns the Y coordinate of the bottom corners of the envelope. + * + * @return The Y coordinate of the bottom corners. + */ + public final double getYMin() { + return m_envelope.ymin; + } + + /** + * Returns the X coordinate of the right corners of the envelope. + * + * @return The X coordinate of the right corners. + */ + public final double getXMax() { + return m_envelope.xmax; + } + + /** + * Returns the Y coordinate of the top corners of the envelope. + * + * @return The Y coordinate of the top corners. + */ + public final double getYMax() { + return m_envelope.ymax; + } + + /** + * Sets the left X coordinate. + * + * @param x The X coordinate of the left corner + */ + public void setXMin(double x) { + _touch(); + m_envelope.xmin = x; + } + + /** + * Sets the right X coordinate. + * + * @param x The X coordinate of the right corner. + */ + public void setXMax(double x) { + _touch(); + m_envelope.xmax = x; + } + + /** + * Sets the bottom Y coordinate. + * + * @param y the Y coordinate of the bottom corner. + */ + public void setYMin(double y) { + _touch(); + m_envelope.ymin = y; + } + + /** + * Sets the top Y coordinate. + * + * @param y The Y coordinate of the top corner. + */ + public void setYMax(double y) { + _touch(); + m_envelope.ymax = y; + } + + @Override + public Geometry getBoundary() { + return Boundary.calculate(this, null); + } + + @Override + public void replaceNaNs(int semantics, double value) { + addAttribute(semantics); + if (isEmpty()) + return; + + int ncomps = VertexDescription.getComponentCount(semantics); + for (int i = 0; i < ncomps; i++) { + Envelope1D interval = queryInterval(semantics, i); + if (interval.isEmpty()) { + interval.vmin = value; + interval.vmax = value; + setInterval(semantics, i, interval); + } + } + } + + /** + * The output of this method can be only used for debugging. It is subject to change without notice. + */ + @Override + public String toString() { + if (isEmpty()) + return "Envelope: []"; + + String s = "Envelope: [" + m_envelope.xmin + ", " + m_envelope.ymin + ", " + m_envelope.xmax + ", " + m_envelope.ymax + "]"; + return s; + } } diff --git a/src/main/java/com/esri/core/geometry/Envelope1D.java b/src/main/java/com/esri/core/geometry/Envelope1D.java index f81bada7..bd994a09 100644 --- a/src/main/java/com/esri/core/geometry/Envelope1D.java +++ b/src/main/java/com/esri/core/geometry/Envelope1D.java @@ -31,194 +31,194 @@ * A 1-dimensional interval. */ public final class Envelope1D implements Serializable { - private static final long serialVersionUID = 1L; - - public double vmin; - - public double vmax; - - public Envelope1D() { - - } - - public Envelope1D(double _vmin, double _vmax) { - setCoords(_vmin, _vmax); - } - - public Envelope1D(Envelope1D other) { - setCoords(other); - } - - public void setCoords(double _vmin, double _vmax) { - vmin = _vmin; - vmax = _vmax; - normalize(); - } - - public void setCoords(Envelope1D other) { - setCoords(other.vmin, other.vmax); - } - - public void normalize() { - if (NumberUtils.isNaN(vmin)) - return; - if (vmin > vmax) { - double v = vmin; - vmin = vmax; - vmax = v; - } - if (NumberUtils.isNaN(vmax))// vmax can be NAN - { - setEmpty(); - } - } - - public void setEmpty() { - vmin = NumberUtils.NaN(); - vmax = NumberUtils.NaN(); - } - - public boolean isEmpty() { - return NumberUtils.isNaN(vmin) || NumberUtils.isNaN(vmax); - } - - public void setInfinite() { - vmin = NumberUtils.negativeInf(); - vmax = NumberUtils.positiveInf(); - } - - public void merge(double v) { - if (isEmpty()) { - vmin = v; - vmax = v; - return; - } - - // no need to check for NaN, because all comparisons with NaN are false. - mergeNE(v); - } - - public void merge(Envelope1D other) { - if (other.isEmpty()) - return; - - if (isEmpty()) { - vmin = other.vmin; - vmax = other.vmax; - return; - } - - if (vmin > other.vmin) - vmin = other.vmin; - if (vmax < other.vmax) - vmax = other.vmax; - - if (vmin > vmax) - setEmpty(); - } - - public void mergeNE(double v) { - // Note, if v is NaN, vmin and vmax are unchanged - if (v < vmin) - vmin = v; - else if (v > vmax) - vmax = v; - } - - public boolean contains(double v) { - // If vmin is NaN, return false. No need to check for isEmpty. - return v >= vmin && v <= vmax; - } - - /** - * Returns True if the envelope contains the other envelope (boundary - * inclusive). Note: Will return false if either envelope is empty. - */ - public boolean contains(/* const */Envelope1D other) /* const */ { - return other.vmin >= vmin && other.vmax <= vmax; - } - - public void intersect(Envelope1D other) { - if (isEmpty() || other.isEmpty()) { - setEmpty(); - return; - } - - if (vmin < other.vmin) - vmin = other.vmin; - if (vmax > other.vmax) - vmax = other.vmax; - - if (vmin > vmax) - setEmpty(); - } - - public void inflate(double delta) { - if (isEmpty()) - return; - - vmin -= delta; - vmax += delta; - if (vmax < vmin) - setEmpty(); - } - - double _calculateToleranceFromEnvelope() { - if (isEmpty()) - return NumberUtils.doubleEps() * 100.0; // GEOMTERYX_EPSFACTOR - // 100.0; - double r = Math.abs(vmin) + Math.abs(vmax) + 1; - return r * NumberUtils.doubleEps() * 100.0; // GEOMTERYX_EPSFACTOR - // 100.0; - } - - void normalizeNoNaN_() { - if (vmin > vmax) { - double v = vmin; - vmin = vmax; - vmax = v; - } - } - - void setCoordsNoNaN_(double vmin_, double vmax_) { - vmin = vmin_; - vmax = vmax_; - normalizeNoNaN_(); - } - - public double snapClip(double v) /* const */ { - return NumberUtils.snap(v, vmin, vmax); - } - - public double getWidth() /* const */ { - return vmax - vmin; - } - - public double getCenter() /* const */ { - return 0.5 * (vmin + vmax); - } - - @Override - public boolean equals(Object _other) { - if (_other == this) - return true; - - if (!(_other instanceof Envelope1D)) - return false; - - Envelope1D other = (Envelope1D) _other; - if (isEmpty() && other.isEmpty()) - return true; - - if (vmin != other.vmin || vmax != other.vmax) - return false; - - return true; - } - - @Override - public int hashCode() { - return NumberUtils.hash(NumberUtils.hash(vmin), vmax); - } + private static final long serialVersionUID = 1L; + + public double vmin; + + public double vmax; + + public Envelope1D() { + + } + + public Envelope1D(double _vmin, double _vmax) { + setCoords(_vmin, _vmax); + } + + public Envelope1D(Envelope1D other) { + setCoords(other); + } + + public void setCoords(double _vmin, double _vmax) { + vmin = _vmin; + vmax = _vmax; + normalize(); + } + + public void setCoords(Envelope1D other) { + setCoords(other.vmin, other.vmax); + } + + public void normalize() { + if (NumberUtils.isNaN(vmin)) + return; + if (vmin > vmax) { + double v = vmin; + vmin = vmax; + vmax = v; + } + if (NumberUtils.isNaN(vmax))// vmax can be NAN + { + setEmpty(); + } + } + + public void setEmpty() { + vmin = NumberUtils.NaN(); + vmax = NumberUtils.NaN(); + } + + public boolean isEmpty() { + return NumberUtils.isNaN(vmin) || NumberUtils.isNaN(vmax); + } + + public void setInfinite() { + vmin = NumberUtils.negativeInf(); + vmax = NumberUtils.positiveInf(); + } + + public void merge(double v) { + if (isEmpty()) { + vmin = v; + vmax = v; + return; + } + + // no need to check for NaN, because all comparisons with NaN are false. + mergeNE(v); + } + + public void merge(Envelope1D other) { + if (other.isEmpty()) + return; + + if (isEmpty()) { + vmin = other.vmin; + vmax = other.vmax; + return; + } + + if (vmin > other.vmin) + vmin = other.vmin; + if (vmax < other.vmax) + vmax = other.vmax; + + if (vmin > vmax) + setEmpty(); + } + + public void mergeNE(double v) { + // Note, if v is NaN, vmin and vmax are unchanged + if (v < vmin) + vmin = v; + else if (v > vmax) + vmax = v; + } + + public boolean contains(double v) { + // If vmin is NaN, return false. No need to check for isEmpty. + return v >= vmin && v <= vmax; + } + + /** + * Returns True if the envelope contains the other envelope (boundary + * inclusive). Note: Will return false if either envelope is empty. + */ + public boolean contains(/* const */Envelope1D other) /* const */ { + return other.vmin >= vmin && other.vmax <= vmax; + } + + public void intersect(Envelope1D other) { + if (isEmpty() || other.isEmpty()) { + setEmpty(); + return; + } + + if (vmin < other.vmin) + vmin = other.vmin; + if (vmax > other.vmax) + vmax = other.vmax; + + if (vmin > vmax) + setEmpty(); + } + + public void inflate(double delta) { + if (isEmpty()) + return; + + vmin -= delta; + vmax += delta; + if (vmax < vmin) + setEmpty(); + } + + double _calculateToleranceFromEnvelope() { + if (isEmpty()) + return NumberUtils.doubleEps() * 100.0; // GEOMTERYX_EPSFACTOR + // 100.0; + double r = Math.abs(vmin) + Math.abs(vmax) + 1; + return r * NumberUtils.doubleEps() * 100.0; // GEOMTERYX_EPSFACTOR + // 100.0; + } + + void normalizeNoNaN_() { + if (vmin > vmax) { + double v = vmin; + vmin = vmax; + vmax = v; + } + } + + void setCoordsNoNaN_(double vmin_, double vmax_) { + vmin = vmin_; + vmax = vmax_; + normalizeNoNaN_(); + } + + public double snapClip(double v) /* const */ { + return NumberUtils.snap(v, vmin, vmax); + } + + public double getWidth() /* const */ { + return vmax - vmin; + } + + public double getCenter() /* const */ { + return 0.5 * (vmin + vmax); + } + + @Override + public boolean equals(Object _other) { + if (_other == this) + return true; + + if (!(_other instanceof Envelope1D)) + return false; + + Envelope1D other = (Envelope1D) _other; + if (isEmpty() && other.isEmpty()) + return true; + + if (vmin != other.vmin || vmax != other.vmax) + return false; + + return true; + } + + @Override + public int hashCode() { + return NumberUtils.hash(NumberUtils.hash(vmin), vmax); + } } diff --git a/src/main/java/com/esri/core/geometry/Envelope2D.java b/src/main/java/com/esri/core/geometry/Envelope2D.java index 1d6172f5..52e3a948 100644 --- a/src/main/java/com/esri/core/geometry/Envelope2D.java +++ b/src/main/java/com/esri/core/geometry/Envelope2D.java @@ -34,56 +34,55 @@ * An axis parallel 2-dimensional rectangle. */ public final class Envelope2D implements Serializable { - private static final long serialVersionUID = 1L; - - private final static int XLESSXMIN = 1; - // private final int XGREATERXMAX = 2; - private final static int YLESSYMIN = 4; - // private final int YGREATERYMAX = 8; - private final static int XMASK = 3; - private final static int YMASK = 12; - - public double xmin; - - public double ymin; - - public double xmax; - - public double ymax; - - public static Envelope2D construct(double _xmin, double _ymin, - double _xmax, double _ymax) { - Envelope2D env = new Envelope2D(); - env.xmin = _xmin; - env.ymin = _ymin; - env.xmax = _xmax; - env.ymax = _ymax; - return env; - } - - public static Envelope2D construct(Envelope2D other) { - Envelope2D env = new Envelope2D(); - env.setCoords(other); - return env; - } - - public Envelope2D() { - setEmpty(); - } - - public Envelope2D(double _xmin, double _ymin, double _xmax, double _ymax) { - xmin = _xmin; - ymin = _ymin; - xmax = _xmax; - ymax = _ymax; - } + private static final long serialVersionUID = 1L; + + private final static int XLESSXMIN = 1; + // private final int XGREATERXMAX = 2; + private final static int YLESSYMIN = 4; + // private final int YGREATERYMAX = 8; + private final static int XMASK = 3; + private final static int YMASK = 12; + + public double xmin; + + public double ymin; + + public double xmax; + + public double ymax; + + public static Envelope2D construct(double _xmin, double _ymin, + double _xmax, double _ymax) { + Envelope2D env = new Envelope2D(); + env.xmin = _xmin; + env.ymin = _ymin; + env.xmax = _xmax; + env.ymax = _ymax; + return env; + } + + public static Envelope2D construct(Envelope2D other) { + Envelope2D env = new Envelope2D(); + env.setCoords(other); + return env; + } + + public Envelope2D() { + setEmpty(); + } + + public Envelope2D(double _xmin, double _ymin, double _xmax, double _ymax) { + xmin = _xmin; + ymin = _ymin; + xmax = _xmax; + ymax = _ymax; + } public Envelope2D(Envelope2D other) { setCoords(other); } - public int estimateMemorySize() - { + public int estimateMemorySize() { return SIZE_OF_ENVELOPE2D; } @@ -94,43 +93,44 @@ public void setCoords(double _x, double _y) { ymax = _y; } - public void setCoords(double _xmin, double _ymin, double _xmax, double _ymax) { - xmin = _xmin; - ymin = _ymin; - xmax = _xmax; - ymax = _ymax; - normalize(); - } - - public void setCoords(Point2D center, double width, double height) { - xmin = center.x - width * 0.5; - xmax = xmin + width; - ymin = center.y - height * 0.5; - ymax = ymin + height; - normalize(); - } - - public void setCoords(Point2D pt) { - xmin = pt.x; - ymin = pt.y; - xmax = pt.x; - ymax = pt.y; - } - - public void setCoords(Envelope2D envSrc) { - setCoords(envSrc.xmin, envSrc.ymin, envSrc.xmax, envSrc.ymax); - } - - public Envelope2D getInflated(double dx, double dy) { - Envelope2D env = new Envelope2D(); - env.setCoords(this.xmin, this.ymin, this.xmax, this.ymax); - env.inflate(dx, dy); - return env; - } + public void setCoords(double _xmin, double _ymin, double _xmax, double _ymax) { + xmin = _xmin; + ymin = _ymin; + xmax = _xmax; + ymax = _ymax; + normalize(); + } + + public void setCoords(Point2D center, double width, double height) { + xmin = center.x - width * 0.5; + xmax = xmin + width; + ymin = center.y - height * 0.5; + ymax = ymin + height; + normalize(); + } + + public void setCoords(Point2D pt) { + xmin = pt.x; + ymin = pt.y; + xmax = pt.x; + ymax = pt.y; + } + + public void setCoords(Envelope2D envSrc) { + setCoords(envSrc.xmin, envSrc.ymin, envSrc.xmax, envSrc.ymax); + } + + public Envelope2D getInflated(double dx, double dy) { + Envelope2D env = new Envelope2D(); + env.setCoords(this.xmin, this.ymin, this.xmax, this.ymax); + env.inflate(dx, dy); + return env; + } /** * Sets the envelope from the array of points. The envelope will be set to * empty if the array is null. + * * @param points The points to set the envelope from. No element in the array can be null. */ public void setFromPoints(Point2D[] points) { @@ -139,66 +139,67 @@ public void setFromPoints(Point2D[] points) { return; } - Point2D pt = points[0]; - setCoords(pt.x, pt.y); - for (int i = 1; i < points.length; i++) { - Point2D pt2d = points[i]; - mergeNE(pt2d.x, pt2d.y); - } - } - - public void setEmpty() { - xmin = NumberUtils.TheNaN; - ymin = NumberUtils.TheNaN; - xmax = NumberUtils.TheNaN; - ymax = NumberUtils.TheNaN; - } - - public void setInfinite() { - xmin = NumberUtils.negativeInf(); - xmax = NumberUtils.positiveInf(); - ymin = NumberUtils.negativeInf(); - ymax = NumberUtils.positiveInf(); - } - - public boolean isEmpty() { - return NumberUtils.isNaN(xmin) || NumberUtils.isNaN(ymin) || NumberUtils.isNaN(xmax) || NumberUtils.isNaN(ymax); - } - - public void setCoords(Envelope1D xinterval, Envelope1D yinterval) { - if (xinterval.isEmpty() || yinterval.isEmpty()) { - setEmpty(); - return; - } - - xmin = xinterval.vmin; - xmax = xinterval.vmax; - ymin = yinterval.vmin; - ymax = yinterval.vmax; - } - - public void merge(double x, double y) { - if (isEmpty()) { - xmin = x; - ymin = y; - xmax = x; - ymax = y; - } else { - if (xmin > x) - xmin = x; - else if (xmax < x) - xmax = x; - - if (ymin > y) - ymin = y; - else if (ymax < y) - ymax = y; - } - } + Point2D pt = points[0]; + setCoords(pt.x, pt.y); + for (int i = 1; i < points.length; i++) { + Point2D pt2d = points[i]; + mergeNE(pt2d.x, pt2d.y); + } + } + + public void setEmpty() { + xmin = NumberUtils.TheNaN; + ymin = NumberUtils.TheNaN; + xmax = NumberUtils.TheNaN; + ymax = NumberUtils.TheNaN; + } + + public void setInfinite() { + xmin = NumberUtils.negativeInf(); + xmax = NumberUtils.positiveInf(); + ymin = NumberUtils.negativeInf(); + ymax = NumberUtils.positiveInf(); + } + + public boolean isEmpty() { + return NumberUtils.isNaN(xmin) || NumberUtils.isNaN(ymin) || NumberUtils.isNaN(xmax) || NumberUtils.isNaN(ymax); + } + + public void setCoords(Envelope1D xinterval, Envelope1D yinterval) { + if (xinterval.isEmpty() || yinterval.isEmpty()) { + setEmpty(); + return; + } + + xmin = xinterval.vmin; + xmax = xinterval.vmax; + ymin = yinterval.vmin; + ymax = yinterval.vmax; + } + + public void merge(double x, double y) { + if (isEmpty()) { + xmin = x; + ymin = y; + xmax = x; + ymax = y; + } else { + if (xmin > x) + xmin = x; + else if (xmax < x) + xmax = x; + + if (ymin > y) + ymin = y; + else if (ymax < y) + ymax = y; + } + } /** * Merges a point with this envelope without checking if the envelope is * empty. Use with care. + * * @param x The x coord of the point * @param y the y coord in the point */ @@ -208,59 +209,60 @@ public void mergeNE(double x, double y) { else if (xmax < x) xmax = x; - if (ymin > y) - ymin = y; - else if (ymax < y) - ymax = y; - } - - public void merge(Point2D pt) { - merge(pt.x, pt.y); - } - - public void merge(Point3D pt) { - merge(pt.x, pt.y); - } - - public void merge(Envelope2D other) { - if (other.isEmpty()) - return; - - merge(other.xmin, other.ymin); - merge(other.xmax, other.ymax); - } - - public void inflate(double dx, double dy) { - if (isEmpty()) - return; - xmin -= dx; - xmax += dx; - ymin -= dy; - ymax += dy; - if (xmin > xmax || ymin > ymax) - setEmpty(); - } - - public void scale(double f) { - if (f < 0.0) - setEmpty(); - - if (isEmpty()) - return; - - xmin *= f; - xmax *= f; - ymin *= f; - ymax *= f; - } - - public void zoom(double factorX, double factorY) { - if (!isEmpty()) - setCoords(getCenter(), factorX * getWidth(), factorY * getHeight()); - } + if (ymin > y) + ymin = y; + else if (ymax < y) + ymax = y; + } + + public void merge(Point2D pt) { + merge(pt.x, pt.y); + } + + public void merge(Point3D pt) { + merge(pt.x, pt.y); + } + + public void merge(Envelope2D other) { + if (other.isEmpty()) + return; + + merge(other.xmin, other.ymin); + merge(other.xmax, other.ymax); + } + + public void inflate(double dx, double dy) { + if (isEmpty()) + return; + xmin -= dx; + xmax += dx; + ymin -= dy; + ymax += dy; + if (xmin > xmax || ymin > ymax) + setEmpty(); + } + + public void scale(double f) { + if (f < 0.0) + setEmpty(); + + if (isEmpty()) + return; + + xmin *= f; + xmax *= f; + ymin *= f; + ymax *= f; + } + + public void zoom(double factorX, double factorY) { + if (!isEmpty()) + setCoords(getCenter(), factorX * getWidth(), factorY * getHeight()); + } /** * Checks if this envelope intersects the other. + * * @param other The other envelope. * @return True if this envelope intersects the other. */ @@ -270,14 +272,15 @@ public boolean isIntersecting(Envelope2D other) { return ((xmin <= other.xmin) ? xmax >= other.xmin : other.xmax >= xmin) && // check that x projections overlap ((ymin <= other.ymin) ? ymax >= other.ymin : other.ymax >= ymin); // check - // that - // y - // projections - // overlap + // that + // y + // projections + // overlap } /** * Checks if this envelope intersects the other assuming neither one is empty. + * * @param other The other envelope. * @return True if this envelope intersects the other. Assumes this and * other envelopes are not empty. @@ -286,14 +289,15 @@ public boolean isIntersectingNE(Envelope2D other) { return ((xmin <= other.xmin) ? xmax >= other.xmin : other.xmax >= xmin) && // check that x projections overlap ((ymin <= other.ymin) ? ymax >= other.ymin : other.ymax >= ymin); // check - // that - // y - // projections - // overlap + // that + // y + // projections + // overlap } /** * Checks if this envelope intersects the other. + * * @param xmin_ * @param ymin_ * @param xmax_ @@ -304,79 +308,81 @@ public boolean isIntersecting(double xmin_, double ymin_, double xmax_, double y // No need to check if empty, this will work for empty geoms too (IEEE // math) return ((xmin <= xmin_) ? xmax >= xmin_ : xmax_ >= xmin) && // check - // that x - // projections - // overlap + // that x + // projections + // overlap ((ymin <= ymin_) ? ymax >= ymin_ : ymax_ >= ymin); // check that - // y - // projections - // overlap + // y + // projections + // overlap } /** * Intersects this envelope with the other and stores result in this * envelope. + * * @param other The other envelope. * @return True if this envelope intersects the other, otherwise sets this - * envelope to empty state and returns False. + * envelope to empty state and returns False. */ public boolean intersect(Envelope2D other) { if (isEmpty() || other.isEmpty()) return false; - if (other.xmin > xmin) - xmin = other.xmin; - - if (other.xmax < xmax) - xmax = other.xmax; - - if (other.ymin > ymin) - ymin = other.ymin; - - if (other.ymax < ymax) - ymax = other.ymax; - - boolean bIntersecting = xmin <= xmax && ymin <= ymax; - - if (!bIntersecting) - setEmpty(); - - return bIntersecting; - } - - /** - * Queries a corner of the envelope. - * - * @param index Indicates a corner of the envelope. - *

- * 0 means lower left or (xmin, ymin) - *

- * 1 means upper left or (xmin, ymax) - *

- * 2 means upper right or (xmax, ymax) - *

- * 3 means lower right or (xmax, ymin) - * @return Point at a corner of the envelope. - */ - public Point2D queryCorner(int index) { - switch (index) { - case 0: - return Point2D.construct(xmin, ymin); - case 1: - return Point2D.construct(xmin, ymax); - case 2: - return Point2D.construct(xmax, ymax); - case 3: - return Point2D.construct(xmax, ymin); - default: - throw new IndexOutOfBoundsException(); - - } - } + if (other.xmin > xmin) + xmin = other.xmin; + + if (other.xmax < xmax) + xmax = other.xmax; + + if (other.ymin > ymin) + ymin = other.ymin; + + if (other.ymax < ymax) + ymax = other.ymax; + + boolean bIntersecting = xmin <= xmax && ymin <= ymax; + + if (!bIntersecting) + setEmpty(); + + return bIntersecting; + } + + /** + * Queries a corner of the envelope. + * + * @param index Indicates a corner of the envelope. + *

+ * 0 means lower left or (xmin, ymin) + *

+ * 1 means upper left or (xmin, ymax) + *

+ * 2 means upper right or (xmax, ymax) + *

+ * 3 means lower right or (xmax, ymin) + * @return Point at a corner of the envelope. + */ + public Point2D queryCorner(int index) { + switch (index) { + case 0: + return Point2D.construct(xmin, ymin); + case 1: + return Point2D.construct(xmin, ymax); + case 2: + return Point2D.construct(xmax, ymax); + case 3: + return Point2D.construct(xmax, ymin); + default: + throw new IndexOutOfBoundsException(); + + } + } /** * Queries corners into a given array. The array length must be at least * 4. Starts from the lower left corner and goes clockwise. + * * @param corners The array of four points. */ public void queryCorners(Point2D[] corners) { @@ -387,26 +393,27 @@ public void queryCorners(Point2D[] corners) { else corners[0] = new Point2D(xmin, ymin); - if (corners[1] != null) - corners[1].setCoords(xmin, ymax); - else - corners[1] = new Point2D(xmin, ymax); + if (corners[1] != null) + corners[1].setCoords(xmin, ymax); + else + corners[1] = new Point2D(xmin, ymax); - if (corners[2] != null) - corners[2].setCoords(xmax, ymax); - else - corners[2] = new Point2D(xmax, ymax); + if (corners[2] != null) + corners[2].setCoords(xmax, ymax); + else + corners[2] = new Point2D(xmax, ymax); - if (corners[3] != null) - corners[3].setCoords(xmax, ymin); - else - corners[3] = new Point2D(xmax, ymin); - } + if (corners[3] != null) + corners[3].setCoords(xmax, ymin); + else + corners[3] = new Point2D(xmax, ymin); + } /** * Queries corners into a given array in reversed order. The array length * must be at least 4. Starts from the lower left corner and goes * counterclockwise. + * * @param corners The array of four points. */ public void queryCornersReversed(Point2D[] corners) { @@ -417,98 +424,99 @@ public void queryCornersReversed(Point2D[] corners) { else corners[0] = new Point2D(xmin, ymin); - if (corners[1] != null) - corners[1].setCoords(xmax, ymin); - else - corners[1] = new Point2D(xmax, ymin); - - if (corners[2] != null) - corners[2].setCoords(xmax, ymax); - else - corners[2] = new Point2D(xmax, ymax); - - if (corners[3] != null) - corners[3].setCoords(xmin, ymax); - else - corners[3] = new Point2D(xmin, ymax); - } - - public double getArea() { - if (isEmpty()) - return 0; - return getWidth() * getHeight(); - } - - public double getLength() { - if (isEmpty()) - return 0; - return 2.0 * (getWidth() + getHeight()); - } - - public void setFromPoints(Point2D[] points, int count) { - if (count == 0) { - setEmpty(); - return; - } - xmin = points[0].x; - ymin = points[0].y; - xmax = xmin; - ymax = ymin; - for (int i = 1; i < count; i++) { - Point2D pt = points[i]; - if (pt.x < xmin) - xmin = pt.x; - else if (pt.x > xmax) - xmax = pt.x; - if (pt.y < ymin) - ymin = pt.y; - else if (pt.y > ymax) - ymax = pt.y; - } - } - - public void reaspect(double arWidth, double arHeight) { - if (isEmpty()) - return; - double newAspectRatio = arWidth / arHeight; - double widthHalf = getWidth() * 0.5; - double heightHalf = getHeight() * 0.5; - - double newWidthHalf = heightHalf * newAspectRatio; - if (widthHalf <= newWidthHalf) {// preserve height, increase width - double xc = getCenterX(); - xmin = xc - newWidthHalf; - xmax = xc + newWidthHalf; - } else {// preserve the width, increase height - double newHeightHalf = widthHalf / newAspectRatio; - double yc = getCenterY(); - ymin = yc - newHeightHalf; - ymax = yc + newHeightHalf; - } - - normalize(); - } - - public double getCenterX() { - double cx = (xmax + xmin) / 2d; - return cx; - } - - public double getCenterY() { - double cy = (ymax + ymin) / 2d; - return cy; - } - - public double getWidth() { - return xmax - xmin; - } - - public double getHeight() { - return ymax - ymin; - } + if (corners[1] != null) + corners[1].setCoords(xmax, ymin); + else + corners[1] = new Point2D(xmax, ymin); + + if (corners[2] != null) + corners[2].setCoords(xmax, ymax); + else + corners[2] = new Point2D(xmax, ymax); + + if (corners[3] != null) + corners[3].setCoords(xmin, ymax); + else + corners[3] = new Point2D(xmin, ymax); + } + + public double getArea() { + if (isEmpty()) + return 0; + return getWidth() * getHeight(); + } + + public double getLength() { + if (isEmpty()) + return 0; + return 2.0 * (getWidth() + getHeight()); + } + + public void setFromPoints(Point2D[] points, int count) { + if (count == 0) { + setEmpty(); + return; + } + xmin = points[0].x; + ymin = points[0].y; + xmax = xmin; + ymax = ymin; + for (int i = 1; i < count; i++) { + Point2D pt = points[i]; + if (pt.x < xmin) + xmin = pt.x; + else if (pt.x > xmax) + xmax = pt.x; + if (pt.y < ymin) + ymin = pt.y; + else if (pt.y > ymax) + ymax = pt.y; + } + } + + public void reaspect(double arWidth, double arHeight) { + if (isEmpty()) + return; + double newAspectRatio = arWidth / arHeight; + double widthHalf = getWidth() * 0.5; + double heightHalf = getHeight() * 0.5; + + double newWidthHalf = heightHalf * newAspectRatio; + if (widthHalf <= newWidthHalf) {// preserve height, increase width + double xc = getCenterX(); + xmin = xc - newWidthHalf; + xmax = xc + newWidthHalf; + } else {// preserve the width, increase height + double newHeightHalf = widthHalf / newAspectRatio; + double yc = getCenterY(); + ymin = yc - newHeightHalf; + ymax = yc + newHeightHalf; + } + + normalize(); + } + + public double getCenterX() { + double cx = (xmax + xmin) / 2d; + return cx; + } + + public double getCenterY() { + double cy = (ymax + ymin) / 2d; + return cy; + } + + public double getWidth() { + return xmax - xmin; + } + + public double getHeight() { + return ymax - ymin; + } /** * Moves the Envelope by given distance. + * * @param dx * @param dy */ @@ -521,149 +529,153 @@ public void move(double dx, double dy) { ymax += dy; } - public void centerAt(double x, double y) { - move(x - getCenterX(), y - getCenterY()); - } + public void centerAt(double x, double y) { + move(x - getCenterX(), y - getCenterY()); + } - void centerAt(Point2D pt) { - centerAt(pt.x, pt.y); - } + void centerAt(Point2D pt) { + centerAt(pt.x, pt.y); + } - public void offset(double dx, double dy) { - xmin += dx;// NaN remains NaN - xmax += dx; - ymin += dy; - ymax += dy; - } + public void offset(double dx, double dy) { + xmin += dx;// NaN remains NaN + xmax += dx; + ymin += dy; + ymax += dy; + } - public void normalize() { - if (isEmpty()) - return; + public void normalize() { + if (isEmpty()) + return; - double min = Math.min(xmin, xmax); - double max = Math.max(xmin, xmax); - xmin = min; - xmax = max; - min = Math.min(ymin, ymax); - max = Math.max(ymin, ymax); - ymin = min; - ymax = max; - } + double min = Math.min(xmin, xmax); + double max = Math.max(xmin, xmax); + xmin = min; + xmax = max; + min = Math.min(ymin, ymax); + max = Math.max(ymin, ymax); + ymin = min; + ymax = max; + } - public void queryLowerLeft(Point2D pt) { - pt.setCoords(xmin, ymin); - } + public void queryLowerLeft(Point2D pt) { + pt.setCoords(xmin, ymin); + } - public void queryLowerRight(Point2D pt) { - pt.setCoords(xmax, ymin); - } + public void queryLowerRight(Point2D pt) { + pt.setCoords(xmax, ymin); + } - public void queryUpperLeft(Point2D pt) { - pt.setCoords(xmin, ymax); - } + public void queryUpperLeft(Point2D pt) { + pt.setCoords(xmin, ymax); + } - public void queryUpperRight(Point2D pt) { - pt.setCoords(xmax, ymax); - } + public void queryUpperRight(Point2D pt) { + pt.setCoords(xmax, ymax); + } /** * Returns True if this envelope is valid (empty, or has xmin less or equal * to xmax, or ymin less or equal to ymax). + * * @return True if the envelope is valid. */ public boolean isValid() { return isEmpty() || (xmin <= xmax && ymin <= ymax); } - /** - * Gets the center point of the envelope. The Center Point occurs at: ((XMin - * + XMax) / 2, (YMin + YMax) / 2). - * - * @return the center point - */ - public Point2D getCenter() { - return new Point2D((xmax + xmin) / 2d, (ymax + ymin) / 2d); - } - - public void queryCenter(Point2D center) { - center.x = (xmax + xmin) / 2d; - center.y = (ymax + ymin) / 2d; - } - - public void centerAt(Point c) { - double cx = (xmax - xmin) / 2d; - double cy = (ymax - ymin) / 2d; - - xmin = c.getX() - cx; - xmax = c.getX() + cx; - ymin = c.getY() - cy; - ymax = c.getY() + cy; - } - - public Point2D getLowerLeft() { - return new Point2D(xmin, ymin); - } - - public Point2D getUpperLeft() { - return new Point2D(xmin, ymax); - } - - public Point2D getLowerRight() { - return new Point2D(xmax, ymin); - } - - public Point2D getUpperRight() { - return new Point2D(xmax, ymax); - } - - public boolean contains(Point p) { - return contains(p.getX(), p.getY()); - } - - public boolean contains(Point2D p) { - return contains(p.x, p.y); - } - - public boolean contains(double x, double y) { - // Note: This will return False, if envelope is empty, thus no need to - // call is_empty(). - return x >= xmin && x <= xmax && y >= ymin && y <= ymax; - } + /** + * Gets the center point of the envelope. The Center Point occurs at: ((XMin + * + XMax) / 2, (YMin + YMax) / 2). + * + * @return the center point + */ + public Point2D getCenter() { + return new Point2D((xmax + xmin) / 2d, (ymax + ymin) / 2d); + } + + public void queryCenter(Point2D center) { + center.x = (xmax + xmin) / 2d; + center.y = (ymax + ymin) / 2d; + } + + public void centerAt(Point c) { + double cx = (xmax - xmin) / 2d; + double cy = (ymax - ymin) / 2d; + + xmin = c.getX() - cx; + xmax = c.getX() + cx; + ymin = c.getY() - cy; + ymax = c.getY() + cy; + } + + public Point2D getLowerLeft() { + return new Point2D(xmin, ymin); + } + + public Point2D getUpperLeft() { + return new Point2D(xmin, ymax); + } + + public Point2D getLowerRight() { + return new Point2D(xmax, ymin); + } + + public Point2D getUpperRight() { + return new Point2D(xmax, ymax); + } + + public boolean contains(Point p) { + return contains(p.getX(), p.getY()); + } + + public boolean contains(Point2D p) { + return contains(p.x, p.y); + } + + public boolean contains(double x, double y) { + // Note: This will return False, if envelope is empty, thus no need to + // call is_empty(). + return x >= xmin && x <= xmax && y >= ymin && y <= ymax; + } /** * Returns True if the envelope contains the other envelope (boundary * inclusive). + * * @param other The other envelope. * @return True if this contains the other. */ public boolean contains(Envelope2D other) {// Note: Will return False, if - // either envelope is empty. + // either envelope is empty. return other.xmin >= xmin && other.xmax <= xmax && other.ymin >= ymin && other.ymax <= ymax; } /** * Returns True if the envelope contains the point (boundary exclusive). + * * @param x * @param y * @return True if this contains the point. - * */ + */ public boolean containsExclusive(double x, double y) { // Note: This will return False, if envelope is empty, thus no need to // call is_empty(). return x > xmin && x < xmax && y > ymin && y < ymax; } - /** - * Returns True if the envelope contains the point (boundary exclusive). - */ - public boolean containsExclusive(Point2D pt) { - return containsExclusive(pt.x, pt.y); - } + /** + * Returns True if the envelope contains the point (boundary exclusive). + */ + public boolean containsExclusive(Point2D pt) { + return containsExclusive(pt.x, pt.y); + } /** * Returns True if the envelope contains the other envelope (boundary * exclusive). + * * @param other The other envelope * @return True if this contains the other, boundary exclusive. */ @@ -674,271 +686,271 @@ boolean containsExclusive(Envelope2D other) { && other.ymax < ymax; } - @Override - public boolean equals(Object _other) { - if (_other == this) - return true; + @Override + public boolean equals(Object _other) { + if (_other == this) + return true; - if (!(_other instanceof Envelope2D)) - return false; + if (!(_other instanceof Envelope2D)) + return false; + + Envelope2D other = (Envelope2D) _other; + if (isEmpty() && other.isEmpty()) + return true; + + if (xmin != other.xmin || ymin != other.ymin || xmax != other.xmax + || ymax != other.ymax) + return false; + + return true; + } + + @Override + public int hashCode() { + + long bits = Double.doubleToLongBits(xmin); + int hc = (int) (bits ^ (bits >>> 32)); + + int hash = NumberUtils.hash(hc); + + bits = Double.doubleToLongBits(xmax); + hc = (int) (bits ^ (bits >>> 32)); + hash = NumberUtils.hash(hash, hc); + + bits = Double.doubleToLongBits(ymin); + hc = (int) (bits ^ (bits >>> 32)); + hash = NumberUtils.hash(hash, hc); + + bits = Double.doubleToLongBits(ymax); + hc = (int) (bits ^ (bits >>> 32)); + hash = NumberUtils.hash(hash, hc); + + return hash; + } + + Point2D _snapToBoundary(Point2D pt) { + Point2D p = new Point2D(); + p.setCoords(pt); + if (p._isNan()) + return p; + + if (isEmpty()) { + p._setNan(); + return p; + } + + if (p.x < xmin) + p.x = xmin; + else if (p.x > xmax) + p.x = xmax; + + if (p.y < ymin) + p.y = ymin; + else if (p.y > ymax) + p.y = ymax; + + if (!p.equals(pt)) + return p; + + // p is inside envelope + Point2D center = getCenter(); + double deltax = p.x < center.x ? p.x - xmin : xmax - p.x; + double deltay = p.y < center.y ? p.y - ymin : ymax - p.y; + + if (deltax < deltay) + p.x = p.x < center.x ? xmin : xmax; + else + p.y = p.y < center.y ? ymin : ymax; + + return p; + } - Envelope2D other = (Envelope2D) _other; - if (isEmpty() && other.isEmpty()) - return true; + // Calculates distance of point from lower left corner of envelope, + // moving clockwise along the envelope boundary. + // The input point is assumed to lie exactly on envelope boundary + // If this is not the case then a projection to the nearest position on the + // envelope boundary is performed. + // (If the user knows that the input point does most likely not lie on the + // boundary, + // it is more efficient to perform ProjectToBoundary before using this + // function). + double _boundaryDistance(Point2D pt) { + if (isEmpty()) + return NumberUtils.NaN(); - if (xmin != other.xmin || ymin != other.ymin || xmax != other.xmax - || ymax != other.ymax) - return false; + if (pt.x == xmin) + return pt.y - ymin; - return true; - } + double height = ymax - ymin; + double width = xmax - xmin; - @Override - public int hashCode() { + if (pt.y == ymax) + return height + pt.x - xmin; - long bits = Double.doubleToLongBits(xmin); - int hc = (int) (bits ^ (bits >>> 32)); + if (pt.x == xmax) + return height + width + ymax - pt.y; - int hash = NumberUtils.hash(hc); + if (pt.y == ymin) + return height * 2.0 + width + xmax - pt.x; - bits = Double.doubleToLongBits(xmax); - hc = (int) (bits ^ (bits >>> 32)); - hash = NumberUtils.hash(hash, hc); + return _boundaryDistance(_snapToBoundary(pt)); + } - bits = Double.doubleToLongBits(ymin); - hc = (int) (bits ^ (bits >>> 32)); - hash = NumberUtils.hash(hash, hc); + // returns 0,..3 depending on which side pt lies. + int _envelopeSide(Point2D pt) { - bits = Double.doubleToLongBits(ymax); - hc = (int) (bits ^ (bits >>> 32)); - hash = NumberUtils.hash(hash, hc); + if (isEmpty()) + return -1; - return hash; - } + double boundaryDist = _boundaryDistance(pt); + double height = ymax - ymin; + double width = xmax - xmin; - Point2D _snapToBoundary(Point2D pt) { - Point2D p = new Point2D(); - p.setCoords(pt); - if (p._isNan()) - return p; - - if (isEmpty()) { - p._setNan(); - return p; - } + if (boundaryDist < height) + return 0; - if (p.x < xmin) - p.x = xmin; - else if (p.x > xmax) - p.x = xmax; - - if (p.y < ymin) - p.y = ymin; - else if (p.y > ymax) - p.y = ymax; + if ((boundaryDist -= height) < width) + return 1; - if (!p.equals(pt)) - return p; + return boundaryDist - width < height ? 2 : 3; + } - // p is inside envelope - Point2D center = getCenter(); - double deltax = p.x < center.x ? p.x - xmin : xmax - p.x; - double deltay = p.y < center.y ? p.y - ymin : ymax - p.y; - - if (deltax < deltay) - p.x = p.x < center.x ? xmin : xmax; - else - p.y = p.y < center.y ? ymin : ymax; - - return p; - } - - // Calculates distance of point from lower left corner of envelope, - // moving clockwise along the envelope boundary. - // The input point is assumed to lie exactly on envelope boundary - // If this is not the case then a projection to the nearest position on the - // envelope boundary is performed. - // (If the user knows that the input point does most likely not lie on the - // boundary, - // it is more efficient to perform ProjectToBoundary before using this - // function). - double _boundaryDistance(Point2D pt) { - if (isEmpty()) - return NumberUtils.NaN(); - - if (pt.x == xmin) - return pt.y - ymin; - - double height = ymax - ymin; - double width = xmax - xmin; - - if (pt.y == ymax) - return height + pt.x - xmin; - - if (pt.x == xmax) - return height + width + ymax - pt.y; - - if (pt.y == ymin) - return height * 2.0 + width + xmax - pt.x; - - return _boundaryDistance(_snapToBoundary(pt)); - } - - // returns 0,..3 depending on which side pt lies. - int _envelopeSide(Point2D pt) { - - if (isEmpty()) - return -1; - - double boundaryDist = _boundaryDistance(pt); - double height = ymax - ymin; - double width = xmax - xmin; - - if (boundaryDist < height) - return 0; - - if ((boundaryDist -= height) < width) - return 1; - - return boundaryDist - width < height ? 2 : 3; - } - - double _calculateToleranceFromEnvelope() { - if (isEmpty()) - return NumberUtils.doubleEps() * 100.0; // GEOMTERYX_EPSFACTOR - // 100.0; - double r = Math.abs(xmin) + Math.abs(xmax) + Math.abs(ymin) - + Math.abs(ymax) + 1; - return r * NumberUtils.doubleEps() * 100.0; // GEOMTERYX_EPSFACTOR - // 100.0; - } - - public int clipLine(Point2D p1, Point2D p2) - // Modified Cohen-Sutherland Line-Clipping Algorithm - // returns: - // 0 - the segment is outside of the clipping window - // 1 - p1 was modified - // 2 - p2 was modified - // 3 - p1 and p2 were modified - // 4 - the segment is complitely inside of the clipping window - { - int c1 = _clipCode(p1), c2 = _clipCode(p2); - - if ((c1 & c2) != 0)// (c1 & c2) - return 0; - - if ((c1 | c2) == 0)// (!(c1 | c2)) - return 4; - - final int res = ((c1 != 0) ? 1 : 0) | ((c2 != 0) ? 2 : 0);// (c1 ? 1 : - // 0) | (c2 - // ? 2 : 0); - - do { - double dx = p2.x - p1.x, dy = p2.y - p1.y; - - boolean bDX = dx > dy; - - if (bDX) { - if ((c1 & XMASK) != 0)// (c1 & XMASK) - { - if ((c1 & XLESSXMIN) != 0)// (c1 & XLESSXMIN) - { - p1.y += dy * (xmin - p1.x) / dx; - p1.x = xmin; - } else { - p1.y += dy * (xmax - p1.x) / dx; - p1.x = xmax; - } - - c1 = _clipCode(p1); - } else if ((c2 & XMASK) != 0)// (c2 & XMASK) - { - if ((c2 & XLESSXMIN) != 0) { - p2.y += dy * (xmin - p2.x) / dx; - p2.x = xmin; - } else { - p2.y += dy * (xmax - p2.x) / dx; - p2.x = xmax; - } - - c2 = _clipCode(p2); - } else if (c1 != 0)// (c1) - { - if ((c1 & YLESSYMIN) != 0)// (c1 & YLESSYMIN) - { - p1.x += dx * (ymin - p1.y) / dy; - p1.y = ymin; - } else { - p1.x += dx * (ymax - p1.y) / dy; - p1.y = ymax; - } - - c1 = _clipCode(p1); - } else { - if ((c2 & YLESSYMIN) != 0)// (c2 & YLESSYMIN) - { - p2.x += dx * (ymin - p2.y) / dy; - p2.y = ymin; - } else { - p2.x += dx * (ymax - p2.y) / dy; - p2.y = ymax; - } - - c2 = _clipCode(p2); - } - } else { - if ((c1 & YMASK) != 0)// (c1 & YMASK) - { - if ((c1 & YLESSYMIN) != 0)// (c1 & YLESSYMIN) - { - p1.x += dx * (ymin - p1.y) / dy; - p1.y = ymin; - } else { - p1.x += dx * (ymax - p1.y) / dy; - p1.y = ymax; - } - - c1 = _clipCode(p1); - } else if ((c2 & YMASK) != 0)// (c2 & YMASK) - { - if ((c2 & YLESSYMIN) != 0) // (c2 & YLESSYMIN) - { - p2.x += dx * (ymin - p2.y) / dy; - p2.y = ymin; - } else { - p2.x += dx * (ymax - p2.y) / dy; - p2.y = ymax; - } - - c2 = _clipCode(p2); - } else if (c1 != 0)// (c1) - { - if ((c1 & XLESSXMIN) != 0)// (c1 & XLESSXMIN) - { - p1.y += dy * (xmin - p1.x) / dx; - p1.x = xmin; - } else { - p1.y += dy * (xmax - p1.x) / dx; - p1.x = xmax; - } - - c1 = _clipCode(p1); - } else { - if ((c2 & XLESSXMIN) != 0)// (c2 & XLESSXMIN) - { - p2.y += dy * (xmin - p2.x) / dx; - p2.x = xmin; - } else { - p2.y += dy * (xmax - p2.x) / dx; - p2.x = xmax; - } - - c2 = _clipCode(p2); - } + double _calculateToleranceFromEnvelope() { + if (isEmpty()) + return NumberUtils.doubleEps() * 100.0; // GEOMTERYX_EPSFACTOR + // 100.0; + double r = Math.abs(xmin) + Math.abs(xmax) + Math.abs(ymin) + + Math.abs(ymax) + 1; + return r * NumberUtils.doubleEps() * 100.0; // GEOMTERYX_EPSFACTOR + // 100.0; + } + + public int clipLine(Point2D p1, Point2D p2) + // Modified Cohen-Sutherland Line-Clipping Algorithm + // returns: + // 0 - the segment is outside of the clipping window + // 1 - p1 was modified + // 2 - p2 was modified + // 3 - p1 and p2 were modified + // 4 - the segment is complitely inside of the clipping window + { + int c1 = _clipCode(p1), c2 = _clipCode(p2); + + if ((c1 & c2) != 0)// (c1 & c2) + return 0; + + if ((c1 | c2) == 0)// (!(c1 | c2)) + return 4; + + final int res = ((c1 != 0) ? 1 : 0) | ((c2 != 0) ? 2 : 0);// (c1 ? 1 : + // 0) | (c2 + // ? 2 : 0); + + do { + double dx = p2.x - p1.x, dy = p2.y - p1.y; + + boolean bDX = dx > dy; + + if (bDX) { + if ((c1 & XMASK) != 0)// (c1 & XMASK) + { + if ((c1 & XLESSXMIN) != 0)// (c1 & XLESSXMIN) + { + p1.y += dy * (xmin - p1.x) / dx; + p1.x = xmin; + } else { + p1.y += dy * (xmax - p1.x) / dx; + p1.x = xmax; + } + + c1 = _clipCode(p1); + } else if ((c2 & XMASK) != 0)// (c2 & XMASK) + { + if ((c2 & XLESSXMIN) != 0) { + p2.y += dy * (xmin - p2.x) / dx; + p2.x = xmin; + } else { + p2.y += dy * (xmax - p2.x) / dx; + p2.x = xmax; + } + + c2 = _clipCode(p2); + } else if (c1 != 0)// (c1) + { + if ((c1 & YLESSYMIN) != 0)// (c1 & YLESSYMIN) + { + p1.x += dx * (ymin - p1.y) / dy; + p1.y = ymin; + } else { + p1.x += dx * (ymax - p1.y) / dy; + p1.y = ymax; + } + + c1 = _clipCode(p1); + } else { + if ((c2 & YLESSYMIN) != 0)// (c2 & YLESSYMIN) + { + p2.x += dx * (ymin - p2.y) / dy; + p2.y = ymin; + } else { + p2.x += dx * (ymax - p2.y) / dy; + p2.y = ymax; + } + + c2 = _clipCode(p2); + } + } else { + if ((c1 & YMASK) != 0)// (c1 & YMASK) + { + if ((c1 & YLESSYMIN) != 0)// (c1 & YLESSYMIN) + { + p1.x += dx * (ymin - p1.y) / dy; + p1.y = ymin; + } else { + p1.x += dx * (ymax - p1.y) / dy; + p1.y = ymax; + } + + c1 = _clipCode(p1); + } else if ((c2 & YMASK) != 0)// (c2 & YMASK) + { + if ((c2 & YLESSYMIN) != 0) // (c2 & YLESSYMIN) + { + p2.x += dx * (ymin - p2.y) / dy; + p2.y = ymin; + } else { + p2.x += dx * (ymax - p2.y) / dy; + p2.y = ymax; + } + + c2 = _clipCode(p2); + } else if (c1 != 0)// (c1) + { + if ((c1 & XLESSXMIN) != 0)// (c1 & XLESSXMIN) + { + p1.y += dy * (xmin - p1.x) / dx; + p1.x = xmin; + } else { + p1.y += dy * (xmax - p1.x) / dx; + p1.x = xmax; + } + + c1 = _clipCode(p1); + } else { + if ((c2 & XLESSXMIN) != 0)// (c2 & XLESSXMIN) + { + p2.y += dy * (xmin - p2.x) / dx; + p2.x = xmin; + } else { + p2.y += dy * (xmax - p2.x) / dx; + p2.x = xmax; + } + + c2 = _clipCode(p2); + } /* - * if (c1) //original code. Faster, but less robust numerically. + * if (c1) //original code. Faster, but less robust numerically. * ( //The Cohen-Sutherland Line-Clipping Algorithm) { if (c1 & * XLESSXMIN) { p1.y += dy * (xmin - p1.x) / dx; p1.x = xmin; } * else if (c1 & XGREATERXMAX) { p1.y += dy * (xmax - p1.x) / @@ -946,322 +958,322 @@ public int clipLine(Point2D p1, Point2D p2) * (ymin - p1.y) / dy; p1.y = ymin; } else if (c1 & * YGREATERYMAX) { p1.x += dx * (ymax - p1.y) / dy; p1.y = ymax; * } - * + * * c1 = _clipCode(p1, ClipRect); } else { if (c2 & XLESSXMIN) { * p2.y += dy * (xmin - p2.x) / dx; p2.x = xmin; } else if (c2 & * XGREATERXMAX) { p2.y += dy * (xmax - p2.x) / dx; p2.x = xmax; * } else if (c2 & YLESSYMIN) { p2.x += dx * (ymin - p2.y) / dy; * p2.y = ymin; } else if (c2 & YGREATERYMAX) { p2.x += dx * * (ymax - p2.y) / dy; p2.y = ymax; } - * + * * c2 = _clipCode(p2, ClipRect); } */ - } - - if ((c1 & c2) != 0)// (c1 & c2) - return 0; - - } while ((c1 | c2) != 0);// (c1 | c2); - - return res; - } - - int _clipCode(Point2D p)// returns a code from the Cohen-Sutherland (0000 is - // boundary inclusive) - { - int left = (p.x < xmin) ? 1 : 0; - int right = (p.x > xmax) ? 1 : 0; - int bottom = (p.y < ymin) ? 1 : 0; - int top = (p.y > ymax) ? 1 : 0; - return left | right << 1 | bottom << 2 | top << 3; - } - - // Clips and optionally extends line within envelope; modifies point 'from', - // 'to'. - // Algorithm: Liang-Barsky parametric line-clipping (Foley, vanDam, Feiner, - // Hughes, second edition, 117-124) - // lineExtension: 0 no line eExtension, 1 extend line at from point, 2 - // extend line at endpoint, 3 extend line at both ends - // boundaryDistances can be NULLPTR. - // returns: - // 0 - the segment is outside of the clipping window - // 1 - p1 was modified - // 2 - p2 was modified - // 3 - p1 and p2 were modified - // 4 - the segment is complitely inside of the clipping window - int clipLine(Point2D p0, Point2D p1, int lineExtension, double[] segParams, - double[] boundaryDistances) { - if (boundaryDistances != null) { - boundaryDistances[0] = -1.0; - boundaryDistances[1] = -1.0; - } - - double[] tOld = new double[2];// LOCALREFCLASS1(ArrayOf(double), int, - // tOld, 2); - int modified = 0; - - Point2D delta = new Point2D(p1.x - p0.x, p1.y - p0.y); - - if (delta.x == 0.0 && delta.y == 0.0) // input line degenerates to a - // point - { - segParams[0] = 0.0; - segParams[1] = 0.0; - return contains(p0) ? 4 : 0; - } - - segParams[0] = ((lineExtension & 1) != 0) ? NumberUtils.negativeInf() - : 0.0; - segParams[1] = ((lineExtension & 2) != 0) ? NumberUtils.positiveInf() - : 1.0; - tOld[0] = segParams[0]; - tOld[1] = segParams[1]; - - if (clipLineAuxiliary(delta.x, xmin - p0.x, segParams) - && clipLineAuxiliary(-delta.x, p0.x - xmax, segParams) - && clipLineAuxiliary(delta.y, ymin - p0.y, segParams) - && clipLineAuxiliary(-delta.y, p0.y - ymax, segParams)) { - if (segParams[1] < tOld[1]) { - p1.scaleAdd(segParams[1], delta, p0); - _snapToBoundary(p1); // needed for accuracy - modified |= 2; - - if (boundaryDistances != null) - boundaryDistances[1] = _boundaryDistance(p1); - } - if (segParams[0] > tOld[0]) { - p0.scaleAdd(segParams[0], delta, p0); - _snapToBoundary(p0); // needed for accuracy - modified |= 1; - - if (boundaryDistances != null) - boundaryDistances[0] = _boundaryDistance(p0); - } - } - - return modified; - } - - boolean clipLineAuxiliary(double denominator, double numerator, - double[] segParams) { - double t = numerator / denominator; - if (denominator > 0.0) { - if (t > segParams[1]) - return false; - - if (t > segParams[0]) { - segParams[0] = t; - return true; - } - } else if (denominator < 0.0) { - if (t < segParams[0]) - return false; - - if (t < segParams[1]) { - segParams[1] = t; - return true; - } - } else - return numerator <= 0.0; - - return true; - } - - /** - * Returns True, envelope is degenerate (Width or Height are less than - * tolerance). Note: this returns False for Empty envelope. - */ - public boolean isDegenerate(double tolerance) { - return !isEmpty() - && (getWidth() <= tolerance || getHeight() <= tolerance); - } - - Point2D _snapClip(Point2D pt)// clips the point if it is outside, then snaps - // it to the boundary. - { - double x = NumberUtils.snap(pt.x, xmin, xmax); - double y = NumberUtils.snap(pt.y, ymin, ymax); - return new Point2D(x, y); - } - - public boolean isPointOnBoundary(Point2D pt, double tolerance) { - return Math.abs(pt.x - xmin) <= tolerance - || Math.abs(pt.x - xmax) <= tolerance - || Math.abs(pt.y - ymin) <= tolerance - || Math.abs(pt.y - ymax) <= tolerance; - } + } + + if ((c1 & c2) != 0)// (c1 & c2) + return 0; + + } while ((c1 | c2) != 0);// (c1 | c2); + + return res; + } + + int _clipCode(Point2D p)// returns a code from the Cohen-Sutherland (0000 is + // boundary inclusive) + { + int left = (p.x < xmin) ? 1 : 0; + int right = (p.x > xmax) ? 1 : 0; + int bottom = (p.y < ymin) ? 1 : 0; + int top = (p.y > ymax) ? 1 : 0; + return left | right << 1 | bottom << 2 | top << 3; + } + + // Clips and optionally extends line within envelope; modifies point 'from', + // 'to'. + // Algorithm: Liang-Barsky parametric line-clipping (Foley, vanDam, Feiner, + // Hughes, second edition, 117-124) + // lineExtension: 0 no line eExtension, 1 extend line at from point, 2 + // extend line at endpoint, 3 extend line at both ends + // boundaryDistances can be NULLPTR. + // returns: + // 0 - the segment is outside of the clipping window + // 1 - p1 was modified + // 2 - p2 was modified + // 3 - p1 and p2 were modified + // 4 - the segment is complitely inside of the clipping window + int clipLine(Point2D p0, Point2D p1, int lineExtension, double[] segParams, + double[] boundaryDistances) { + if (boundaryDistances != null) { + boundaryDistances[0] = -1.0; + boundaryDistances[1] = -1.0; + } + + double[] tOld = new double[2];// LOCALREFCLASS1(ArrayOf(double), int, + // tOld, 2); + int modified = 0; + + Point2D delta = new Point2D(p1.x - p0.x, p1.y - p0.y); + + if (delta.x == 0.0 && delta.y == 0.0) // input line degenerates to a + // point + { + segParams[0] = 0.0; + segParams[1] = 0.0; + return contains(p0) ? 4 : 0; + } + + segParams[0] = ((lineExtension & 1) != 0) ? NumberUtils.negativeInf() + : 0.0; + segParams[1] = ((lineExtension & 2) != 0) ? NumberUtils.positiveInf() + : 1.0; + tOld[0] = segParams[0]; + tOld[1] = segParams[1]; + + if (clipLineAuxiliary(delta.x, xmin - p0.x, segParams) + && clipLineAuxiliary(-delta.x, p0.x - xmax, segParams) + && clipLineAuxiliary(delta.y, ymin - p0.y, segParams) + && clipLineAuxiliary(-delta.y, p0.y - ymax, segParams)) { + if (segParams[1] < tOld[1]) { + p1.scaleAdd(segParams[1], delta, p0); + _snapToBoundary(p1); // needed for accuracy + modified |= 2; + + if (boundaryDistances != null) + boundaryDistances[1] = _boundaryDistance(p1); + } + if (segParams[0] > tOld[0]) { + p0.scaleAdd(segParams[0], delta, p0); + _snapToBoundary(p0); // needed for accuracy + modified |= 1; + + if (boundaryDistances != null) + boundaryDistances[0] = _boundaryDistance(p0); + } + } + + return modified; + } + + boolean clipLineAuxiliary(double denominator, double numerator, + double[] segParams) { + double t = numerator / denominator; + if (denominator > 0.0) { + if (t > segParams[1]) + return false; + + if (t > segParams[0]) { + segParams[0] = t; + return true; + } + } else if (denominator < 0.0) { + if (t < segParams[0]) + return false; + + if (t < segParams[1]) { + segParams[1] = t; + return true; + } + } else + return numerator <= 0.0; + + return true; + } + + /** + * Returns True, envelope is degenerate (Width or Height are less than + * tolerance). Note: this returns False for Empty envelope. + */ + public boolean isDegenerate(double tolerance) { + return !isEmpty() + && (getWidth() <= tolerance || getHeight() <= tolerance); + } + + Point2D _snapClip(Point2D pt)// clips the point if it is outside, then snaps + // it to the boundary. + { + double x = NumberUtils.snap(pt.x, xmin, xmax); + double y = NumberUtils.snap(pt.y, ymin, ymax); + return new Point2D(x, y); + } + + public boolean isPointOnBoundary(Point2D pt, double tolerance) { + return Math.abs(pt.x - xmin) <= tolerance + || Math.abs(pt.x - xmax) <= tolerance + || Math.abs(pt.y - ymin) <= tolerance + || Math.abs(pt.y - ymax) <= tolerance; + } /** * Calculates minimum distance from this envelope to the other. * Returns 0 for empty envelopes. + * * @param other The other envelope. * @return Returns the distance */ - public double distance(Envelope2D other) - { + public double distance(Envelope2D other) { return Math.sqrt(sqrDistance(other)); } /** * Calculates minimum distance from this envelope to the point. * Returns 0 for empty envelopes. + * * @param pt2D The other point. * @return Returns the distance */ - public double distance(Point2D pt2D) - { + public double distance(Point2D pt2D) { return Math.sqrt(sqrDistance(pt2D)); } /** * Calculates minimum squared distance from this envelope to the other. * Returns 0 for empty envelopes. + * * @param other The other envelope. * @return Returns the squared distance */ - public double sqrDistance(Envelope2D other) - { + public double sqrDistance(Envelope2D other) { double dx = 0; double dy = 0; double nn; - nn = xmin - other.xmax; - if (nn > dx) - dx = nn; + nn = xmin - other.xmax; + if (nn > dx) + dx = nn; - nn = ymin - other.ymax; - if (nn > dy) - dy = nn; + nn = ymin - other.ymax; + if (nn > dy) + dy = nn; - nn = other.xmin - xmax; - if (nn > dx) - dx = nn; + nn = other.xmin - xmax; + if (nn > dx) + dx = nn; - nn = other.ymin - ymax; - if (nn > dy) - dy = nn; + nn = other.ymin - ymax; + if (nn > dy) + dy = nn; - return dx * dx + dy * dy; - } + return dx * dx + dy * dy; + } /** * Calculates minimum squared distance from this envelope to the other. * Returns 0 for empty envelopes. + * * @param xmin_ * @param ymin_ * @param xmax_ * @param ymax_ * @return Returns the squared distance. */ - public double sqrDistance(double xmin_, double ymin_, double xmax_, double ymax_) - { + public double sqrDistance(double xmin_, double ymin_, double xmax_, double ymax_) { double dx = 0; double dy = 0; double nn; - nn = xmin - xmax_; - if (nn > dx) - dx = nn; - - nn = ymin - ymax_; - if (nn > dy) - dy = nn; - - nn = xmin_ - xmax; - if (nn > dx) - dx = nn; - - nn = ymin_ - ymax; - if (nn > dy) - dy = nn; - - return dx * dx + dy * dy; - } - - /** - * Returns squared max distance between two bounding boxes. This is furthest distance between points on the two envelopes. - * - * @param other The bounding box to calculate the max distance two. - * @return Squared distance value. - */ - public double sqrMaxDistance(Envelope2D other) { - if (isEmpty() || other.isEmpty()) - return NumberUtils.TheNaN; - - double dist = 0; - Point2D[] points = new Point2D[4]; - queryCorners(points); - Point2D[] points_o = new Point2D[4]; - other.queryCorners(points_o); - for (int i = 0; i < 4; i++) { - for (int j = 0; j < 4; j++) { - double d = Point2D.sqrDistance(points[i], points_o[j]); - if (d > dist) { - dist = d; - } - } - } + nn = xmin - xmax_; + if (nn > dx) + dx = nn; + + nn = ymin - ymax_; + if (nn > dy) + dy = nn; + + nn = xmin_ - xmax; + if (nn > dx) + dx = nn; + + nn = ymin_ - ymax; + if (nn > dy) + dy = nn; + + return dx * dx + dy * dy; + } + + /** + * Returns squared max distance between two bounding boxes. This is furthest distance between points on the two envelopes. + * + * @param other The bounding box to calculate the max distance two. + * @return Squared distance value. + */ + public double sqrMaxDistance(Envelope2D other) { + if (isEmpty() || other.isEmpty()) + return NumberUtils.TheNaN; + + double dist = 0; + Point2D[] points = new Point2D[4]; + queryCorners(points); + Point2D[] points_o = new Point2D[4]; + other.queryCorners(points_o); + for (int i = 0; i < 4; i++) { + for (int j = 0; j < 4; j++) { + double d = Point2D.sqrDistance(points[i], points_o[j]); + if (d > dist) { + dist = d; + } + } + } return dist; } - + /** * Calculates minimum squared distance from this envelope to the point. * Returns 0 for empty envelopes. + * * @param pt2D The point. * @return Returns the squared distance */ - public double sqrDistance(Point2D pt2D) - { + public double sqrDistance(Point2D pt2D) { double dx = 0; double dy = 0; double nn; - nn = xmin - pt2D.x; - if (nn > dx) - dx = nn; - - nn = ymin - pt2D.y; - if (nn > dy) - dy = nn; - - nn = pt2D.x - xmax; - if (nn > dx) - dx = nn; - - nn = pt2D.y - ymax; - if (nn > dy) - dy = nn; - - return dx * dx + dy * dy; - } - - public void queryIntervalX(Envelope1D env1D) { - if (isEmpty()) { - env1D.setEmpty(); - } else { - env1D.setCoords(xmin, xmax); - } - } - - public void queryIntervalY(Envelope1D env1D) { - if (isEmpty()) { - env1D.setEmpty(); - } else { - env1D.setCoords(ymin, ymax); - } - } - - private void writeObject(java.io.ObjectOutputStream out) throws IOException { - out.defaultWriteObject(); - } - - private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException { - in.defaultReadObject(); - } - - private void readObjectNoData() throws ObjectStreamException { - setEmpty(); - } + nn = xmin - pt2D.x; + if (nn > dx) + dx = nn; + + nn = ymin - pt2D.y; + if (nn > dy) + dy = nn; + + nn = pt2D.x - xmax; + if (nn > dx) + dx = nn; + + nn = pt2D.y - ymax; + if (nn > dy) + dy = nn; + + return dx * dx + dy * dy; + } + + public void queryIntervalX(Envelope1D env1D) { + if (isEmpty()) { + env1D.setEmpty(); + } else { + env1D.setCoords(xmin, xmax); + } + } + + public void queryIntervalY(Envelope1D env1D) { + if (isEmpty()) { + env1D.setEmpty(); + } else { + env1D.setCoords(ymin, ymax); + } + } + + private void writeObject(java.io.ObjectOutputStream out) throws IOException { + out.defaultWriteObject(); + } + + private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException { + in.defaultReadObject(); + } + + private void readObjectNoData() throws ObjectStreamException { + setEmpty(); + } } diff --git a/src/main/java/com/esri/core/geometry/Envelope2DIntersectorImpl.java b/src/main/java/com/esri/core/geometry/Envelope2DIntersectorImpl.java index 2974be49..2941fcb6 100644 --- a/src/main/java/com/esri/core/geometry/Envelope2DIntersectorImpl.java +++ b/src/main/java/com/esri/core/geometry/Envelope2DIntersectorImpl.java @@ -26,898 +26,898 @@ import java.util.ArrayList; class Envelope2DIntersectorImpl { - /* - * Constructor for Envelope_2D_intersector. - */ - Envelope2DIntersectorImpl() { - m_function = -1; - m_tolerance = 0.0; - reset_(); - } - - void startConstruction() { - reset_(); - m_b_add_red_red = true; - - if (m_envelopes_red == null) { - m_elements_red = new AttributeStreamOfInt32(0); - m_envelopes_red = new ArrayList(0); - } else { - m_elements_red.resizePreserveCapacity(0); - m_envelopes_red.clear(); - } - } - - void addEnvelope(int element, Envelope2D envelope) { - if (!m_b_add_red_red) - throw new GeometryException("invalid call"); - - Envelope2D e = new Envelope2D(); - e.setCoords(envelope); - m_elements_red.add(element); - m_envelopes_red.add(e); - } - - void endConstruction() { - if (!m_b_add_red_red) - throw new GeometryException("invalid call"); - - m_b_add_red_red = false; - - if (m_envelopes_red != null && m_envelopes_red.size() > 0) { - m_function = State.initialize; - m_b_done = false; - } - } - - void startRedConstruction() { - reset_(); - m_b_add_red = true; - - if (m_envelopes_red == null) { - m_elements_red = new AttributeStreamOfInt32(0); - m_envelopes_red = new ArrayList(0); - } else { - m_elements_red.resizePreserveCapacity(0); - m_envelopes_red.clear(); - } - } - - void addRedEnvelope(int element, Envelope2D red_envelope) { - if (!m_b_add_red) - throw new GeometryException("invalid call"); - - Envelope2D e = new Envelope2D(); - e.setCoords(red_envelope); - m_elements_red.add(element); - m_envelopes_red.add(e); - } - - void endRedConstruction() { - if (!m_b_add_red) - throw new GeometryException("invalid call"); - - m_b_add_red = false; - - if (m_envelopes_red != null && m_envelopes_red.size() > 0 && m_envelopes_blue != null && m_envelopes_blue.size() > 0) { - if (m_function == -1) - m_function = State.initializeRedBlue; - else if (m_function == State.initializeBlue) - m_function = State.initializeRedBlue; - else if (m_function != State.initializeRedBlue) - m_function = State.initializeRed; - - m_b_done = false; - } - } - - void startBlueConstruction() { - reset_(); - m_b_add_blue = true; - - if (m_envelopes_blue == null) { - m_elements_blue = new AttributeStreamOfInt32(0); - m_envelopes_blue = new ArrayList(0); - } else { - m_elements_blue.resizePreserveCapacity(0); - m_envelopes_blue.clear(); - } - } - - void addBlueEnvelope(int element, Envelope2D blue_envelope) { - if (!m_b_add_blue) - throw new GeometryException("invalid call"); - - Envelope2D e = new Envelope2D(); - e.setCoords(blue_envelope); - m_elements_blue.add(element); - m_envelopes_blue.add(e); - } - - void endBlueConstruction() { - if (!m_b_add_blue) - throw new GeometryException("invalid call"); - - m_b_add_blue = false; - - if (m_envelopes_red != null && m_envelopes_red.size() > 0 && m_envelopes_blue != null && m_envelopes_blue.size() > 0) { - if (m_function == -1) - m_function = State.initializeRedBlue; - else if (m_function == State.initializeRed) - m_function = State.initializeRedBlue; - else if (m_function != State.initializeRedBlue) - m_function = State.initializeBlue; - - m_b_done = false; - } - } - - /* - * Moves the iterator to the next intersecting pair of envelopes.Returns - * true if an intersecting pair is found. You can call get_handle_a() and - * get_handle_b() to get the index of each envelope in the current - * intersection. Otherwise if false is returned, then are no more - * intersections (if at all). - */ - boolean next() { - if (m_b_done) - return false; - - boolean b_searching = true; - while (b_searching) { - switch (m_function) { - case State.initialize: - b_searching = initialize_(); - break; - case State.initializeRed: - b_searching = initializeRed_(); - break; - case State.initializeBlue: - b_searching = initializeBlue_(); - break; - case State.initializeRedBlue: - b_searching = initializeRedBlue_(); - break; - case State.sweep: - b_searching = sweep_(); - break; - case State.sweepBruteForce: - b_searching = sweepBruteForce_(); - break; - case State.sweepRedBlueBruteForce: - b_searching = sweepRedBlueBruteForce_(); - break; - case State.sweepRedBlue: - b_searching = sweepRedBlue_(); - break; - case State.sweepRed: - b_searching = sweepRed_(); - break; - case State.sweepBlue: - b_searching = sweepBlue_(); - break; - case State.iterate: - b_searching = iterate_(); - break; - case State.iterateRed: - b_searching = iterateRed_(); - break; - case State.iterateBlue: - b_searching = iterateBlue_(); - break; - case State.iterateBruteForce: - b_searching = iterateBruteForce_(); - break; - case State.iterateRedBlueBruteForce: - b_searching = iterateRedBlueBruteForce_(); - break; - case State.resetRed: - b_searching = resetRed_(); - break; - case State.resetBlue: - b_searching = resetBlue_(); - break; - default: - throw GeometryException.GeometryInternalError(); - } - } - - if (m_b_done) - return false; - - return true; - } - - /* - * Returns the index of the first envelope in the intersection. In the - * red/blue case, this will be an index to the red envelopes. - */ - int getHandleA() { - return m_envelope_handle_a; - } - - /* - * Returns the index of the second envelope in the intersection. In the - * red/blue case, this will be an index to the blue envelopes. - */ - int getHandleB() { - return m_envelope_handle_b; - } - - /* - * Sets the tolerance used for the intersection tests.\param tolerance The - * tolerance used to determine intersection. - */ - void setTolerance(double tolerance) { - m_tolerance = tolerance; - } - - /* - * Returns a reference to the envelope at the given handle. Use this for the red/red intersection case. - */ - Envelope2D getEnvelope(int handle) { - return m_envelopes_red.get(handle); - } - - /* - * Returns the user element associated with handle. Use this for the red/red intersection case. - */ - int getElement(int handle) { - return m_elements_red.read(handle); - } - - /* - * Returns a reference to the red envelope at handle_a. - */ - Envelope2D getRedEnvelope(int handle_a) { - return m_envelopes_red.get(handle_a); - } - - /* - * Returns a reference to the blue envelope at handle_b. - */ - Envelope2D getBlueEnvelope(int handle_b) { - return m_envelopes_blue.get(handle_b); - } - - /* - * Returns the user element associated with handle_a. - */ - int getRedElement(int handle_a) { - return m_elements_red.read(handle_a); - } - - /* - * Returns the user element associated with handle_b. - */ - int getBlueElement(int handle_b) { - return m_elements_blue.read(handle_b); - } - - private double m_tolerance; - private int m_sweep_index_red; - private int m_sweep_index_blue; - private int m_envelope_handle_a; - private int m_envelope_handle_b; - private IntervalTreeImpl m_interval_tree_red; - private IntervalTreeImpl m_interval_tree_blue; - private IntervalTreeImpl.IntervalTreeIteratorImpl m_iterator_red; - private IntervalTreeImpl.IntervalTreeIteratorImpl m_iterator_blue; - private Envelope2D m_envelope_helper = new Envelope2D(); - - private ArrayList m_envelopes_red; - private ArrayList m_envelopes_blue; - private AttributeStreamOfInt32 m_elements_red; - private AttributeStreamOfInt32 m_elements_blue; - - private AttributeStreamOfInt32 m_sorted_end_indices_red; - private AttributeStreamOfInt32 m_sorted_end_indices_blue; - - private int m_queued_list_red; - private int m_queued_list_blue; - private IndexMultiDCList m_queued_envelopes; - private AttributeStreamOfInt32 m_queued_indices_red; - private AttributeStreamOfInt32 m_queued_indices_blue; - private boolean m_b_add_red; - private boolean m_b_add_blue; - private boolean m_b_add_red_red; - private boolean m_b_done; - - private static boolean isTop_(int y_end_point_handle) { - return (y_end_point_handle & 0x1) == 1; - } - - private static boolean isBottom_(int y_end_point_handle) { - return (y_end_point_handle & 0x1) == 0; - } - - private void reset_() { - m_b_add_red = false; - m_b_add_blue = false; - m_b_add_red_red = false; - m_sweep_index_red = -1; - m_sweep_index_blue = -1; - m_queued_list_red = -1; - m_queued_list_blue = -1; - m_b_done = true; - } - - private boolean initialize_() { - m_envelope_handle_a = -1; - m_envelope_handle_b = -1; - - if (m_envelopes_red.size() < 10) { - m_sweep_index_red = m_envelopes_red.size(); - m_function = State.sweepBruteForce; - return true; - } - - if (m_interval_tree_red == null) { - m_interval_tree_red = new IntervalTreeImpl(true); - m_sorted_end_indices_red = new AttributeStreamOfInt32(0); - } - - m_interval_tree_red.addEnvelopesRef(m_envelopes_red); - - if (m_iterator_red == null) { - m_iterator_red = m_interval_tree_red.getIterator(); - } - - m_sorted_end_indices_red.reserve(2 * m_envelopes_red.size()); - m_sorted_end_indices_red.resize(0); - - for (int i = 0; i < 2 * m_envelopes_red.size(); i++) - m_sorted_end_indices_red.add(i); - - sortYEndIndices_(m_sorted_end_indices_red, 0, 2 * m_envelopes_red.size(), true); - - m_sweep_index_red = 2 * m_envelopes_red.size(); - - m_function = State.sweep; // overwrite initialize_ - - return true; - } - - private boolean initializeRed_() { - m_envelope_handle_a = -1; - m_envelope_handle_b = -1; - - if (m_envelopes_red.size() < 10 || m_envelopes_blue.size() < 10) { - m_sweep_index_red = m_envelopes_red.size(); - m_function = State.sweepRedBlueBruteForce; - return true; - } - - if (m_interval_tree_red == null) { - m_interval_tree_red = new IntervalTreeImpl(true); - m_sorted_end_indices_red = new AttributeStreamOfInt32(0); - } - - m_interval_tree_red.addEnvelopesRef(m_envelopes_red); - - if (m_iterator_red == null) { - m_iterator_red = m_interval_tree_red.getIterator(); - } - - m_sorted_end_indices_red.reserve(2 * m_envelopes_red.size()); - m_sorted_end_indices_red.resize(0); + /* + * Constructor for Envelope_2D_intersector. + */ + Envelope2DIntersectorImpl() { + m_function = -1; + m_tolerance = 0.0; + reset_(); + } + + void startConstruction() { + reset_(); + m_b_add_red_red = true; + + if (m_envelopes_red == null) { + m_elements_red = new AttributeStreamOfInt32(0); + m_envelopes_red = new ArrayList(0); + } else { + m_elements_red.resizePreserveCapacity(0); + m_envelopes_red.clear(); + } + } + + void addEnvelope(int element, Envelope2D envelope) { + if (!m_b_add_red_red) + throw new GeometryException("invalid call"); + + Envelope2D e = new Envelope2D(); + e.setCoords(envelope); + m_elements_red.add(element); + m_envelopes_red.add(e); + } + + void endConstruction() { + if (!m_b_add_red_red) + throw new GeometryException("invalid call"); + + m_b_add_red_red = false; + + if (m_envelopes_red != null && m_envelopes_red.size() > 0) { + m_function = State.initialize; + m_b_done = false; + } + } + + void startRedConstruction() { + reset_(); + m_b_add_red = true; + + if (m_envelopes_red == null) { + m_elements_red = new AttributeStreamOfInt32(0); + m_envelopes_red = new ArrayList(0); + } else { + m_elements_red.resizePreserveCapacity(0); + m_envelopes_red.clear(); + } + } + + void addRedEnvelope(int element, Envelope2D red_envelope) { + if (!m_b_add_red) + throw new GeometryException("invalid call"); + + Envelope2D e = new Envelope2D(); + e.setCoords(red_envelope); + m_elements_red.add(element); + m_envelopes_red.add(e); + } + + void endRedConstruction() { + if (!m_b_add_red) + throw new GeometryException("invalid call"); + + m_b_add_red = false; + + if (m_envelopes_red != null && m_envelopes_red.size() > 0 && m_envelopes_blue != null && m_envelopes_blue.size() > 0) { + if (m_function == -1) + m_function = State.initializeRedBlue; + else if (m_function == State.initializeBlue) + m_function = State.initializeRedBlue; + else if (m_function != State.initializeRedBlue) + m_function = State.initializeRed; + + m_b_done = false; + } + } + + void startBlueConstruction() { + reset_(); + m_b_add_blue = true; + + if (m_envelopes_blue == null) { + m_elements_blue = new AttributeStreamOfInt32(0); + m_envelopes_blue = new ArrayList(0); + } else { + m_elements_blue.resizePreserveCapacity(0); + m_envelopes_blue.clear(); + } + } + + void addBlueEnvelope(int element, Envelope2D blue_envelope) { + if (!m_b_add_blue) + throw new GeometryException("invalid call"); + + Envelope2D e = new Envelope2D(); + e.setCoords(blue_envelope); + m_elements_blue.add(element); + m_envelopes_blue.add(e); + } + + void endBlueConstruction() { + if (!m_b_add_blue) + throw new GeometryException("invalid call"); + + m_b_add_blue = false; + + if (m_envelopes_red != null && m_envelopes_red.size() > 0 && m_envelopes_blue != null && m_envelopes_blue.size() > 0) { + if (m_function == -1) + m_function = State.initializeRedBlue; + else if (m_function == State.initializeRed) + m_function = State.initializeRedBlue; + else if (m_function != State.initializeRedBlue) + m_function = State.initializeBlue; + + m_b_done = false; + } + } + + /* + * Moves the iterator to the next intersecting pair of envelopes.Returns + * true if an intersecting pair is found. You can call get_handle_a() and + * get_handle_b() to get the index of each envelope in the current + * intersection. Otherwise if false is returned, then are no more + * intersections (if at all). + */ + boolean next() { + if (m_b_done) + return false; + + boolean b_searching = true; + while (b_searching) { + switch (m_function) { + case State.initialize: + b_searching = initialize_(); + break; + case State.initializeRed: + b_searching = initializeRed_(); + break; + case State.initializeBlue: + b_searching = initializeBlue_(); + break; + case State.initializeRedBlue: + b_searching = initializeRedBlue_(); + break; + case State.sweep: + b_searching = sweep_(); + break; + case State.sweepBruteForce: + b_searching = sweepBruteForce_(); + break; + case State.sweepRedBlueBruteForce: + b_searching = sweepRedBlueBruteForce_(); + break; + case State.sweepRedBlue: + b_searching = sweepRedBlue_(); + break; + case State.sweepRed: + b_searching = sweepRed_(); + break; + case State.sweepBlue: + b_searching = sweepBlue_(); + break; + case State.iterate: + b_searching = iterate_(); + break; + case State.iterateRed: + b_searching = iterateRed_(); + break; + case State.iterateBlue: + b_searching = iterateBlue_(); + break; + case State.iterateBruteForce: + b_searching = iterateBruteForce_(); + break; + case State.iterateRedBlueBruteForce: + b_searching = iterateRedBlueBruteForce_(); + break; + case State.resetRed: + b_searching = resetRed_(); + break; + case State.resetBlue: + b_searching = resetBlue_(); + break; + default: + throw GeometryException.GeometryInternalError(); + } + } + + if (m_b_done) + return false; + + return true; + } + + /* + * Returns the index of the first envelope in the intersection. In the + * red/blue case, this will be an index to the red envelopes. + */ + int getHandleA() { + return m_envelope_handle_a; + } + + /* + * Returns the index of the second envelope in the intersection. In the + * red/blue case, this will be an index to the blue envelopes. + */ + int getHandleB() { + return m_envelope_handle_b; + } + + /* + * Sets the tolerance used for the intersection tests.\param tolerance The + * tolerance used to determine intersection. + */ + void setTolerance(double tolerance) { + m_tolerance = tolerance; + } + + /* + * Returns a reference to the envelope at the given handle. Use this for the red/red intersection case. + */ + Envelope2D getEnvelope(int handle) { + return m_envelopes_red.get(handle); + } + + /* + * Returns the user element associated with handle. Use this for the red/red intersection case. + */ + int getElement(int handle) { + return m_elements_red.read(handle); + } + + /* + * Returns a reference to the red envelope at handle_a. + */ + Envelope2D getRedEnvelope(int handle_a) { + return m_envelopes_red.get(handle_a); + } + + /* + * Returns a reference to the blue envelope at handle_b. + */ + Envelope2D getBlueEnvelope(int handle_b) { + return m_envelopes_blue.get(handle_b); + } + + /* + * Returns the user element associated with handle_a. + */ + int getRedElement(int handle_a) { + return m_elements_red.read(handle_a); + } + + /* + * Returns the user element associated with handle_b. + */ + int getBlueElement(int handle_b) { + return m_elements_blue.read(handle_b); + } + + private double m_tolerance; + private int m_sweep_index_red; + private int m_sweep_index_blue; + private int m_envelope_handle_a; + private int m_envelope_handle_b; + private IntervalTreeImpl m_interval_tree_red; + private IntervalTreeImpl m_interval_tree_blue; + private IntervalTreeImpl.IntervalTreeIteratorImpl m_iterator_red; + private IntervalTreeImpl.IntervalTreeIteratorImpl m_iterator_blue; + private Envelope2D m_envelope_helper = new Envelope2D(); + + private ArrayList m_envelopes_red; + private ArrayList m_envelopes_blue; + private AttributeStreamOfInt32 m_elements_red; + private AttributeStreamOfInt32 m_elements_blue; + + private AttributeStreamOfInt32 m_sorted_end_indices_red; + private AttributeStreamOfInt32 m_sorted_end_indices_blue; + + private int m_queued_list_red; + private int m_queued_list_blue; + private IndexMultiDCList m_queued_envelopes; + private AttributeStreamOfInt32 m_queued_indices_red; + private AttributeStreamOfInt32 m_queued_indices_blue; + private boolean m_b_add_red; + private boolean m_b_add_blue; + private boolean m_b_add_red_red; + private boolean m_b_done; + + private static boolean isTop_(int y_end_point_handle) { + return (y_end_point_handle & 0x1) == 1; + } + + private static boolean isBottom_(int y_end_point_handle) { + return (y_end_point_handle & 0x1) == 0; + } + + private void reset_() { + m_b_add_red = false; + m_b_add_blue = false; + m_b_add_red_red = false; + m_sweep_index_red = -1; + m_sweep_index_blue = -1; + m_queued_list_red = -1; + m_queued_list_blue = -1; + m_b_done = true; + } + + private boolean initialize_() { + m_envelope_handle_a = -1; + m_envelope_handle_b = -1; + + if (m_envelopes_red.size() < 10) { + m_sweep_index_red = m_envelopes_red.size(); + m_function = State.sweepBruteForce; + return true; + } + + if (m_interval_tree_red == null) { + m_interval_tree_red = new IntervalTreeImpl(true); + m_sorted_end_indices_red = new AttributeStreamOfInt32(0); + } + + m_interval_tree_red.addEnvelopesRef(m_envelopes_red); + + if (m_iterator_red == null) { + m_iterator_red = m_interval_tree_red.getIterator(); + } + + m_sorted_end_indices_red.reserve(2 * m_envelopes_red.size()); + m_sorted_end_indices_red.resize(0); + + for (int i = 0; i < 2 * m_envelopes_red.size(); i++) + m_sorted_end_indices_red.add(i); + + sortYEndIndices_(m_sorted_end_indices_red, 0, 2 * m_envelopes_red.size(), true); + + m_sweep_index_red = 2 * m_envelopes_red.size(); + + m_function = State.sweep; // overwrite initialize_ + + return true; + } + + private boolean initializeRed_() { + m_envelope_handle_a = -1; + m_envelope_handle_b = -1; + + if (m_envelopes_red.size() < 10 || m_envelopes_blue.size() < 10) { + m_sweep_index_red = m_envelopes_red.size(); + m_function = State.sweepRedBlueBruteForce; + return true; + } + + if (m_interval_tree_red == null) { + m_interval_tree_red = new IntervalTreeImpl(true); + m_sorted_end_indices_red = new AttributeStreamOfInt32(0); + } + + m_interval_tree_red.addEnvelopesRef(m_envelopes_red); + + if (m_iterator_red == null) { + m_iterator_red = m_interval_tree_red.getIterator(); + } + + m_sorted_end_indices_red.reserve(2 * m_envelopes_red.size()); + m_sorted_end_indices_red.resize(0); - for (int i = 0; i < 2 * m_envelopes_red.size(); i++) - m_sorted_end_indices_red.add(i); + for (int i = 0; i < 2 * m_envelopes_red.size(); i++) + m_sorted_end_indices_red.add(i); - sortYEndIndices_(m_sorted_end_indices_red, 0, m_sorted_end_indices_red.size(), true); - m_sweep_index_red = m_sorted_end_indices_red.size(); + sortYEndIndices_(m_sorted_end_indices_red, 0, m_sorted_end_indices_red.size(), true); + m_sweep_index_red = m_sorted_end_indices_red.size(); - if (m_queued_list_red != -1) { - m_queued_envelopes.deleteList(m_queued_list_red); - m_queued_indices_red.resize(0); - m_queued_list_red = -1; - } + if (m_queued_list_red != -1) { + m_queued_envelopes.deleteList(m_queued_list_red); + m_queued_indices_red.resize(0); + m_queued_list_red = -1; + } - m_function = State.sweepRedBlue; // overwrite initialize_ + m_function = State.sweepRedBlue; // overwrite initialize_ - return resetBlue_(); - } + return resetBlue_(); + } - private boolean initializeBlue_() { - m_envelope_handle_a = -1; - m_envelope_handle_b = -1; + private boolean initializeBlue_() { + m_envelope_handle_a = -1; + m_envelope_handle_b = -1; - if (m_envelopes_red.size() < 10 || m_envelopes_blue.size() < 10) { - m_sweep_index_red = m_envelopes_red.size(); - m_function = State.sweepRedBlueBruteForce; - return true; - } + if (m_envelopes_red.size() < 10 || m_envelopes_blue.size() < 10) { + m_sweep_index_red = m_envelopes_red.size(); + m_function = State.sweepRedBlueBruteForce; + return true; + } - if (m_interval_tree_blue == null) { - m_interval_tree_blue = new IntervalTreeImpl(true); - m_sorted_end_indices_blue = new AttributeStreamOfInt32(0); - } + if (m_interval_tree_blue == null) { + m_interval_tree_blue = new IntervalTreeImpl(true); + m_sorted_end_indices_blue = new AttributeStreamOfInt32(0); + } - m_interval_tree_blue.addEnvelopesRef(m_envelopes_blue); + m_interval_tree_blue.addEnvelopesRef(m_envelopes_blue); - if (m_iterator_blue == null) { - m_iterator_blue = m_interval_tree_blue.getIterator(); - } + if (m_iterator_blue == null) { + m_iterator_blue = m_interval_tree_blue.getIterator(); + } - m_sorted_end_indices_blue.reserve(2 * m_envelopes_blue.size()); - m_sorted_end_indices_blue.resize(0); + m_sorted_end_indices_blue.reserve(2 * m_envelopes_blue.size()); + m_sorted_end_indices_blue.resize(0); - for (int i = 0; i < 2 * m_envelopes_blue.size(); i++) - m_sorted_end_indices_blue.add(i); + for (int i = 0; i < 2 * m_envelopes_blue.size(); i++) + m_sorted_end_indices_blue.add(i); - sortYEndIndices_(m_sorted_end_indices_blue, 0, m_sorted_end_indices_blue.size(), false); - m_sweep_index_blue = m_sorted_end_indices_blue.size(); + sortYEndIndices_(m_sorted_end_indices_blue, 0, m_sorted_end_indices_blue.size(), false); + m_sweep_index_blue = m_sorted_end_indices_blue.size(); - if (m_queued_list_blue != -1) { - m_queued_envelopes.deleteList(m_queued_list_blue); - m_queued_indices_blue.resize(0); - m_queued_list_blue = -1; - } + if (m_queued_list_blue != -1) { + m_queued_envelopes.deleteList(m_queued_list_blue); + m_queued_indices_blue.resize(0); + m_queued_list_blue = -1; + } - m_function = State.sweepRedBlue; // overwrite initialize_ + m_function = State.sweepRedBlue; // overwrite initialize_ - return resetRed_(); - } + return resetRed_(); + } - private boolean initializeRedBlue_() { - m_envelope_handle_a = -1; - m_envelope_handle_b = -1; + private boolean initializeRedBlue_() { + m_envelope_handle_a = -1; + m_envelope_handle_b = -1; - if (m_envelopes_red.size() < 10 || m_envelopes_blue.size() < 10) { - m_sweep_index_red = m_envelopes_red.size(); - m_function = State.sweepRedBlueBruteForce; - return true; - } + if (m_envelopes_red.size() < 10 || m_envelopes_blue.size() < 10) { + m_sweep_index_red = m_envelopes_red.size(); + m_function = State.sweepRedBlueBruteForce; + return true; + } - if (m_interval_tree_red == null) { - m_interval_tree_red = new IntervalTreeImpl(true); - m_sorted_end_indices_red = new AttributeStreamOfInt32(0); - } + if (m_interval_tree_red == null) { + m_interval_tree_red = new IntervalTreeImpl(true); + m_sorted_end_indices_red = new AttributeStreamOfInt32(0); + } - if (m_interval_tree_blue == null) { - m_interval_tree_blue = new IntervalTreeImpl(true); - m_sorted_end_indices_blue = new AttributeStreamOfInt32(0); - } + if (m_interval_tree_blue == null) { + m_interval_tree_blue = new IntervalTreeImpl(true); + m_sorted_end_indices_blue = new AttributeStreamOfInt32(0); + } - m_interval_tree_red.addEnvelopesRef(m_envelopes_red); - m_interval_tree_blue.addEnvelopesRef(m_envelopes_blue); + m_interval_tree_red.addEnvelopesRef(m_envelopes_red); + m_interval_tree_blue.addEnvelopesRef(m_envelopes_blue); - if (m_iterator_red == null) { - m_iterator_red = m_interval_tree_red.getIterator(); - } + if (m_iterator_red == null) { + m_iterator_red = m_interval_tree_red.getIterator(); + } - if (m_iterator_blue == null) { - m_iterator_blue = m_interval_tree_blue.getIterator(); - } + if (m_iterator_blue == null) { + m_iterator_blue = m_interval_tree_blue.getIterator(); + } - m_sorted_end_indices_red.reserve(2 * m_envelopes_red.size()); - m_sorted_end_indices_blue.reserve(2 * m_envelopes_blue.size()); - m_sorted_end_indices_red.resize(0); - m_sorted_end_indices_blue.resize(0); + m_sorted_end_indices_red.reserve(2 * m_envelopes_red.size()); + m_sorted_end_indices_blue.reserve(2 * m_envelopes_blue.size()); + m_sorted_end_indices_red.resize(0); + m_sorted_end_indices_blue.resize(0); - for (int i = 0; i < 2 * m_envelopes_red.size(); i++) - m_sorted_end_indices_red.add(i); + for (int i = 0; i < 2 * m_envelopes_red.size(); i++) + m_sorted_end_indices_red.add(i); - for (int i = 0; i < 2 * m_envelopes_blue.size(); i++) - m_sorted_end_indices_blue.add(i); + for (int i = 0; i < 2 * m_envelopes_blue.size(); i++) + m_sorted_end_indices_blue.add(i); - sortYEndIndices_(m_sorted_end_indices_red, 0, m_sorted_end_indices_red.size(), true); - sortYEndIndices_(m_sorted_end_indices_blue, 0, m_sorted_end_indices_blue.size(), false); + sortYEndIndices_(m_sorted_end_indices_red, 0, m_sorted_end_indices_red.size(), true); + sortYEndIndices_(m_sorted_end_indices_blue, 0, m_sorted_end_indices_blue.size(), false); - m_sweep_index_red = m_sorted_end_indices_red.size(); - m_sweep_index_blue = m_sorted_end_indices_blue.size(); - - if (m_queued_list_red != -1) { - m_queued_envelopes.deleteList(m_queued_list_red); - m_queued_indices_red.resize(0); - m_queued_list_red = -1; - } - - if (m_queued_list_blue != -1) { - m_queued_envelopes.deleteList(m_queued_list_blue); - m_queued_indices_blue.resize(0); - m_queued_list_blue = -1; - } - - m_function = State.sweepRedBlue; // overwrite initialize_ - - return true; - } - - private boolean sweep_() { - int y_end_point_handle = m_sorted_end_indices_red.get(--m_sweep_index_red); - int envelope_handle = y_end_point_handle >> 1; - - if (isBottom_(y_end_point_handle)) { - m_interval_tree_red.remove(envelope_handle); - - if (m_sweep_index_red == 0) { - m_envelope_handle_a = -1; - m_envelope_handle_b = -1; - m_b_done = true; - return false; - } - - return true; - } - - m_iterator_red.resetIterator(m_envelopes_red.get(envelope_handle).xmin, m_envelopes_red.get(envelope_handle).xmax, m_tolerance); - m_envelope_handle_a = envelope_handle; - m_function = State.iterate; - - return true; - } - - private boolean sweepBruteForce_() {// this isn't really a sweep, it just walks along the array of red envelopes backward. - if (--m_sweep_index_red == -1) { - m_envelope_handle_a = -1; - m_envelope_handle_b = -1; - m_b_done = true; - return false; - } - - m_envelope_handle_a = m_sweep_index_red; - m_sweep_index_blue = m_sweep_index_red; - m_function = State.iterateBruteForce; - - return true; - } - - private boolean sweepRedBlueBruteForce_() {// this isn't really a sweep, it just walks along the array of red envelopes backward. - if (--m_sweep_index_red == -1) { - m_envelope_handle_a = -1; - m_envelope_handle_b = -1; - m_b_done = true; - return false; - } - - m_envelope_handle_a = m_sweep_index_red; - m_sweep_index_blue = m_envelopes_blue.size(); - m_function = State.iterateRedBlueBruteForce; - - return true; - } - - private boolean sweepRedBlue_() {// controls whether we want to sweep the red envelopes or sweep the blue envelopes - int y_end_point_handle_red = m_sorted_end_indices_red.get(m_sweep_index_red - 1); - int y_end_point_handle_blue = m_sorted_end_indices_blue.get(m_sweep_index_blue - 1); - - double y_red = getAdjustedValue_(y_end_point_handle_red, true); - double y_blue = getAdjustedValue_(y_end_point_handle_blue, false); - - if (y_red > y_blue) - return sweepRed_(); - if (y_red < y_blue) - return sweepBlue_(); - - if (isTop_(y_end_point_handle_red)) - return sweepRed_(); - if (isTop_(y_end_point_handle_blue)) - return sweepBlue_(); - - return sweepRed_(); // arbitrary. can call sweep_blue_ instead and would also work correctly - } - - private boolean sweepRed_() { - int y_end_point_handle_red = m_sorted_end_indices_red.get(--m_sweep_index_red); - int envelope_handle_red = y_end_point_handle_red >> 1; - - if (isBottom_(y_end_point_handle_red)) { - if (m_queued_list_red != -1 && m_queued_indices_red.get(envelope_handle_red) != -1) { - m_queued_envelopes.deleteElement(m_queued_list_red, m_queued_indices_red.get(envelope_handle_red)); - m_queued_indices_red.set(envelope_handle_red, -1); - } else - m_interval_tree_red.remove(envelope_handle_red); - - if (m_sweep_index_red == 0) { - m_envelope_handle_a = -1; - m_envelope_handle_b = -1; - m_b_done = true; - return false; - } - - return true; - } - - if (m_queued_list_blue != -1 && m_queued_envelopes.getListSize(m_queued_list_blue) > 0) { - int node = m_queued_envelopes.getFirst(m_queued_list_blue); - while (node != -1) { - int e = m_queued_envelopes.getData(node); - m_interval_tree_blue.insert(e); - m_queued_indices_blue.set(e, -1); - int next_node = m_queued_envelopes.getNext(node); - m_queued_envelopes.deleteElement(m_queued_list_blue, node); - node = next_node; - } - } - - if (m_interval_tree_blue.size() > 0) { - m_iterator_blue.resetIterator(m_envelopes_red.get(envelope_handle_red).xmin, m_envelopes_red.get(envelope_handle_red).xmax, m_tolerance); - m_envelope_handle_a = envelope_handle_red; - m_function = State.iterateBlue; - } else { - if (m_queued_list_red == -1) { - if (m_queued_envelopes == null) - m_queued_envelopes = new IndexMultiDCList(); - - m_queued_indices_red = new AttributeStreamOfInt32(0); - m_queued_indices_red.resize(m_envelopes_red.size(), -1); - m_queued_indices_red.setRange(-1, 0, m_envelopes_red.size()); - m_queued_list_red = m_queued_envelopes.createList(1); - } - - m_queued_indices_red.set(envelope_handle_red, m_queued_envelopes.addElement(m_queued_list_red, envelope_handle_red)); - m_function = State.sweepRedBlue; - } - - return true; - } - - private boolean sweepBlue_() { - int y_end_point_handle_blue = m_sorted_end_indices_blue.get(--m_sweep_index_blue); - int envelope_handle_blue = y_end_point_handle_blue >> 1; - - if (isBottom_(y_end_point_handle_blue)) { - if (m_queued_list_blue != -1 && m_queued_indices_blue.get(envelope_handle_blue) != -1) { - m_queued_envelopes.deleteElement(m_queued_list_blue, m_queued_indices_blue.get(envelope_handle_blue)); - m_queued_indices_blue.set(envelope_handle_blue, -1); - } else - m_interval_tree_blue.remove(envelope_handle_blue); - - if (m_sweep_index_blue == 0) { - m_envelope_handle_a = -1; - m_envelope_handle_b = -1; - m_b_done = true; - return false; - } - - return true; - } - - if (m_queued_list_red != -1 && m_queued_envelopes.getListSize(m_queued_list_red) > 0) { - int node = m_queued_envelopes.getFirst(m_queued_list_red); - while (node != -1) { - int e = m_queued_envelopes.getData(node); - m_interval_tree_red.insert(e); - m_queued_indices_red.set(e, -1); - int next_node = m_queued_envelopes.getNext(node); - m_queued_envelopes.deleteElement(m_queued_list_red, node); - node = next_node; - } - } - - if (m_interval_tree_red.size() > 0) { - m_iterator_red.resetIterator(m_envelopes_blue.get(envelope_handle_blue).xmin, m_envelopes_blue.get(envelope_handle_blue).xmax, m_tolerance); - m_envelope_handle_b = envelope_handle_blue; - m_function = State.iterateRed; - } else { - if (m_queued_list_blue == -1) { - if (m_queued_envelopes == null) - m_queued_envelopes = new IndexMultiDCList(); - - m_queued_indices_blue = new AttributeStreamOfInt32(0); - m_queued_indices_blue.resize(m_envelopes_blue.size(), -1); - m_queued_indices_blue.setRange(-1, 0, m_envelopes_blue.size()); - m_queued_list_blue = m_queued_envelopes.createList(0); - } - - m_queued_indices_blue.set(envelope_handle_blue, m_queued_envelopes.addElement(m_queued_list_blue, envelope_handle_blue)); - m_function = State.sweepRedBlue; - } - - return true; - } - - private boolean iterate_() { - m_envelope_handle_b = m_iterator_red.next(); - if (m_envelope_handle_b != -1) - return false; - - int envelope_handle = m_sorted_end_indices_red.get(m_sweep_index_red) >> 1; - m_interval_tree_red.insert(envelope_handle); - m_function = State.sweep; - - return true; - } - - private boolean iterateRed_() { - m_envelope_handle_a = m_iterator_red.next(); - if (m_envelope_handle_a != -1) - return false; - - m_envelope_handle_a = -1; - m_envelope_handle_b = -1; - - int envelope_handle_blue = m_sorted_end_indices_blue.get(m_sweep_index_blue) >> 1; - m_interval_tree_blue.insert(envelope_handle_blue); - m_function = State.sweepRedBlue; - - return true; - } - - private boolean iterateBlue_() { - m_envelope_handle_b = m_iterator_blue.next(); - if (m_envelope_handle_b != -1) - return false; - - int envelope_handle_red = m_sorted_end_indices_red.get(m_sweep_index_red) >> 1; - m_interval_tree_red.insert(envelope_handle_red); - m_function = State.sweepRedBlue; - - return true; - } - - private boolean iterateBruteForce_() { - if (--m_sweep_index_blue == -1) { - m_function = State.sweepBruteForce; - return true; - } - - m_envelope_helper.setCoords(m_envelopes_red.get(m_sweep_index_red)); - Envelope2D envelope_b = m_envelopes_red.get(m_sweep_index_blue); - - m_envelope_helper.inflate(m_tolerance, m_tolerance); - if (m_envelope_helper.isIntersecting(envelope_b)) { - m_envelope_handle_b = m_sweep_index_blue; - return false; - } - - return true; - } - - private boolean iterateRedBlueBruteForce_() { - if (--m_sweep_index_blue == -1) { - m_function = State.sweepRedBlueBruteForce; - return true; - } - - m_envelope_helper.setCoords(m_envelopes_red.get(m_sweep_index_red)); - Envelope2D envelope_b = m_envelopes_blue.get(m_sweep_index_blue); - - m_envelope_helper.inflate(m_tolerance, m_tolerance); - if (m_envelope_helper.isIntersecting(envelope_b)) { - m_envelope_handle_b = m_sweep_index_blue; - return false; - } - - return true; - } - - private boolean resetRed_() { - if (m_interval_tree_red == null) { - m_b_done = true; - return false; - } - - m_sweep_index_red = m_sorted_end_indices_red.size(); - - if (m_interval_tree_red.size() > 0) - m_interval_tree_red.reset(); - - if (m_queued_list_red != -1) { - m_queued_envelopes.deleteList(m_queued_list_red); - m_queued_indices_red.resize(0); - m_queued_list_red = -1; - } - - m_b_done = false; - return true; - } - - private boolean resetBlue_() { - if (m_interval_tree_blue == null) { - m_b_done = true; - return false; - } - - m_sweep_index_blue = m_sorted_end_indices_blue.size(); - - if (m_interval_tree_blue.size() > 0) - m_interval_tree_blue.reset(); - - if (m_queued_list_blue != -1) { - m_queued_envelopes.deleteList(m_queued_list_blue); - m_queued_indices_blue.resize(0); - m_queued_list_blue = -1; - } - - m_b_done = false; - return true; - } - - private int m_function; - - private interface State { - static final int initialize = 0; - static final int initializeRed = 1; - static final int initializeBlue = 2; - static final int initializeRedBlue = 3; - static final int sweep = 4; - static final int sweepBruteForce = 5; - static final int sweepRedBlueBruteForce = 6; - static final int sweepRedBlue = 7; - static final int sweepRed = 8; - static final int sweepBlue = 9; - static final int iterate = 10; - static final int iterateRed = 11; - static final int iterateBlue = 12; - static final int iterateBruteForce = 13; - static final int iterateRedBlueBruteForce = 14; - static final int resetRed = 15; - static final int resetBlue = 16; - } - - // *********** Helpers for Bucket sort************** - private BucketSort m_bucket_sort; - - private void sortYEndIndices_(AttributeStreamOfInt32 end_indices, int begin_, int end_, boolean b_red) { - if (m_bucket_sort == null) - m_bucket_sort = new BucketSort(); - - Envelope2DBucketSortHelper sorter = new Envelope2DBucketSortHelper(this, b_red); - m_bucket_sort.sort(end_indices, begin_, end_, sorter); - } - - private void sortYEndIndicesHelper_(AttributeStreamOfInt32 end_indices, int begin_, int end_, boolean b_red) { - end_indices.Sort(begin_, end_, new EndPointsComparer(this, b_red)); - } - - private double getAdjustedValue_(int e, boolean b_red) { - double dy = 0.5 * m_tolerance; - if (b_red) { - Envelope2D envelope_red = m_envelopes_red.get(e >> 1); - double y = (isBottom_(e) ? envelope_red.ymin - dy : envelope_red.ymax + dy); - return y; - } - - Envelope2D envelope_blue = m_envelopes_blue.get(e >> 1); - double y = (isBottom_(e) ? envelope_blue.ymin - dy : envelope_blue.ymax + dy); - return y; - } - - private static final class EndPointsComparer extends AttributeStreamOfInt32.IntComparator {// For user sort - - EndPointsComparer(Envelope2DIntersectorImpl intersector, boolean b_red) { - m_intersector = intersector; - m_b_red = b_red; - } - - @Override - public int compare(int e_1, int e_2) { - double y1 = m_intersector.getAdjustedValue_(e_1, m_b_red); - double y2 = m_intersector.getAdjustedValue_(e_2, m_b_red); - - if (y1 < y2 || (y1 == y2 && isBottom_(e_1) && isTop_(e_2))) - return -1; - - return 1; - } - - private Envelope2DIntersectorImpl m_intersector; - private boolean m_b_red; - } - - private static final class Envelope2DBucketSortHelper extends ClassicSort {// For - - // bucket - // sort - Envelope2DBucketSortHelper(Envelope2DIntersectorImpl intersector, boolean b_red) { - m_intersector = intersector; - m_b_red = b_red; - } - - @Override - public void userSort(int begin, int end, AttributeStreamOfInt32 indices) { - m_intersector.sortYEndIndicesHelper_(indices, begin, end, m_b_red); - } - - @Override - public double getValue(int index) { - return m_intersector.getAdjustedValue_(index, m_b_red); - } - - private Envelope2DIntersectorImpl m_intersector; - private boolean m_b_red; - } + m_sweep_index_red = m_sorted_end_indices_red.size(); + m_sweep_index_blue = m_sorted_end_indices_blue.size(); + + if (m_queued_list_red != -1) { + m_queued_envelopes.deleteList(m_queued_list_red); + m_queued_indices_red.resize(0); + m_queued_list_red = -1; + } + + if (m_queued_list_blue != -1) { + m_queued_envelopes.deleteList(m_queued_list_blue); + m_queued_indices_blue.resize(0); + m_queued_list_blue = -1; + } + + m_function = State.sweepRedBlue; // overwrite initialize_ + + return true; + } + + private boolean sweep_() { + int y_end_point_handle = m_sorted_end_indices_red.get(--m_sweep_index_red); + int envelope_handle = y_end_point_handle >> 1; + + if (isBottom_(y_end_point_handle)) { + m_interval_tree_red.remove(envelope_handle); + + if (m_sweep_index_red == 0) { + m_envelope_handle_a = -1; + m_envelope_handle_b = -1; + m_b_done = true; + return false; + } + + return true; + } + + m_iterator_red.resetIterator(m_envelopes_red.get(envelope_handle).xmin, m_envelopes_red.get(envelope_handle).xmax, m_tolerance); + m_envelope_handle_a = envelope_handle; + m_function = State.iterate; + + return true; + } + + private boolean sweepBruteForce_() {// this isn't really a sweep, it just walks along the array of red envelopes backward. + if (--m_sweep_index_red == -1) { + m_envelope_handle_a = -1; + m_envelope_handle_b = -1; + m_b_done = true; + return false; + } + + m_envelope_handle_a = m_sweep_index_red; + m_sweep_index_blue = m_sweep_index_red; + m_function = State.iterateBruteForce; + + return true; + } + + private boolean sweepRedBlueBruteForce_() {// this isn't really a sweep, it just walks along the array of red envelopes backward. + if (--m_sweep_index_red == -1) { + m_envelope_handle_a = -1; + m_envelope_handle_b = -1; + m_b_done = true; + return false; + } + + m_envelope_handle_a = m_sweep_index_red; + m_sweep_index_blue = m_envelopes_blue.size(); + m_function = State.iterateRedBlueBruteForce; + + return true; + } + + private boolean sweepRedBlue_() {// controls whether we want to sweep the red envelopes or sweep the blue envelopes + int y_end_point_handle_red = m_sorted_end_indices_red.get(m_sweep_index_red - 1); + int y_end_point_handle_blue = m_sorted_end_indices_blue.get(m_sweep_index_blue - 1); + + double y_red = getAdjustedValue_(y_end_point_handle_red, true); + double y_blue = getAdjustedValue_(y_end_point_handle_blue, false); + + if (y_red > y_blue) + return sweepRed_(); + if (y_red < y_blue) + return sweepBlue_(); + + if (isTop_(y_end_point_handle_red)) + return sweepRed_(); + if (isTop_(y_end_point_handle_blue)) + return sweepBlue_(); + + return sweepRed_(); // arbitrary. can call sweep_blue_ instead and would also work correctly + } + + private boolean sweepRed_() { + int y_end_point_handle_red = m_sorted_end_indices_red.get(--m_sweep_index_red); + int envelope_handle_red = y_end_point_handle_red >> 1; + + if (isBottom_(y_end_point_handle_red)) { + if (m_queued_list_red != -1 && m_queued_indices_red.get(envelope_handle_red) != -1) { + m_queued_envelopes.deleteElement(m_queued_list_red, m_queued_indices_red.get(envelope_handle_red)); + m_queued_indices_red.set(envelope_handle_red, -1); + } else + m_interval_tree_red.remove(envelope_handle_red); + + if (m_sweep_index_red == 0) { + m_envelope_handle_a = -1; + m_envelope_handle_b = -1; + m_b_done = true; + return false; + } + + return true; + } + + if (m_queued_list_blue != -1 && m_queued_envelopes.getListSize(m_queued_list_blue) > 0) { + int node = m_queued_envelopes.getFirst(m_queued_list_blue); + while (node != -1) { + int e = m_queued_envelopes.getData(node); + m_interval_tree_blue.insert(e); + m_queued_indices_blue.set(e, -1); + int next_node = m_queued_envelopes.getNext(node); + m_queued_envelopes.deleteElement(m_queued_list_blue, node); + node = next_node; + } + } + + if (m_interval_tree_blue.size() > 0) { + m_iterator_blue.resetIterator(m_envelopes_red.get(envelope_handle_red).xmin, m_envelopes_red.get(envelope_handle_red).xmax, m_tolerance); + m_envelope_handle_a = envelope_handle_red; + m_function = State.iterateBlue; + } else { + if (m_queued_list_red == -1) { + if (m_queued_envelopes == null) + m_queued_envelopes = new IndexMultiDCList(); + + m_queued_indices_red = new AttributeStreamOfInt32(0); + m_queued_indices_red.resize(m_envelopes_red.size(), -1); + m_queued_indices_red.setRange(-1, 0, m_envelopes_red.size()); + m_queued_list_red = m_queued_envelopes.createList(1); + } + + m_queued_indices_red.set(envelope_handle_red, m_queued_envelopes.addElement(m_queued_list_red, envelope_handle_red)); + m_function = State.sweepRedBlue; + } + + return true; + } + + private boolean sweepBlue_() { + int y_end_point_handle_blue = m_sorted_end_indices_blue.get(--m_sweep_index_blue); + int envelope_handle_blue = y_end_point_handle_blue >> 1; + + if (isBottom_(y_end_point_handle_blue)) { + if (m_queued_list_blue != -1 && m_queued_indices_blue.get(envelope_handle_blue) != -1) { + m_queued_envelopes.deleteElement(m_queued_list_blue, m_queued_indices_blue.get(envelope_handle_blue)); + m_queued_indices_blue.set(envelope_handle_blue, -1); + } else + m_interval_tree_blue.remove(envelope_handle_blue); + + if (m_sweep_index_blue == 0) { + m_envelope_handle_a = -1; + m_envelope_handle_b = -1; + m_b_done = true; + return false; + } + + return true; + } + + if (m_queued_list_red != -1 && m_queued_envelopes.getListSize(m_queued_list_red) > 0) { + int node = m_queued_envelopes.getFirst(m_queued_list_red); + while (node != -1) { + int e = m_queued_envelopes.getData(node); + m_interval_tree_red.insert(e); + m_queued_indices_red.set(e, -1); + int next_node = m_queued_envelopes.getNext(node); + m_queued_envelopes.deleteElement(m_queued_list_red, node); + node = next_node; + } + } + + if (m_interval_tree_red.size() > 0) { + m_iterator_red.resetIterator(m_envelopes_blue.get(envelope_handle_blue).xmin, m_envelopes_blue.get(envelope_handle_blue).xmax, m_tolerance); + m_envelope_handle_b = envelope_handle_blue; + m_function = State.iterateRed; + } else { + if (m_queued_list_blue == -1) { + if (m_queued_envelopes == null) + m_queued_envelopes = new IndexMultiDCList(); + + m_queued_indices_blue = new AttributeStreamOfInt32(0); + m_queued_indices_blue.resize(m_envelopes_blue.size(), -1); + m_queued_indices_blue.setRange(-1, 0, m_envelopes_blue.size()); + m_queued_list_blue = m_queued_envelopes.createList(0); + } + + m_queued_indices_blue.set(envelope_handle_blue, m_queued_envelopes.addElement(m_queued_list_blue, envelope_handle_blue)); + m_function = State.sweepRedBlue; + } + + return true; + } + + private boolean iterate_() { + m_envelope_handle_b = m_iterator_red.next(); + if (m_envelope_handle_b != -1) + return false; + + int envelope_handle = m_sorted_end_indices_red.get(m_sweep_index_red) >> 1; + m_interval_tree_red.insert(envelope_handle); + m_function = State.sweep; + + return true; + } + + private boolean iterateRed_() { + m_envelope_handle_a = m_iterator_red.next(); + if (m_envelope_handle_a != -1) + return false; + + m_envelope_handle_a = -1; + m_envelope_handle_b = -1; + + int envelope_handle_blue = m_sorted_end_indices_blue.get(m_sweep_index_blue) >> 1; + m_interval_tree_blue.insert(envelope_handle_blue); + m_function = State.sweepRedBlue; + + return true; + } + + private boolean iterateBlue_() { + m_envelope_handle_b = m_iterator_blue.next(); + if (m_envelope_handle_b != -1) + return false; + + int envelope_handle_red = m_sorted_end_indices_red.get(m_sweep_index_red) >> 1; + m_interval_tree_red.insert(envelope_handle_red); + m_function = State.sweepRedBlue; + + return true; + } + + private boolean iterateBruteForce_() { + if (--m_sweep_index_blue == -1) { + m_function = State.sweepBruteForce; + return true; + } + + m_envelope_helper.setCoords(m_envelopes_red.get(m_sweep_index_red)); + Envelope2D envelope_b = m_envelopes_red.get(m_sweep_index_blue); + + m_envelope_helper.inflate(m_tolerance, m_tolerance); + if (m_envelope_helper.isIntersecting(envelope_b)) { + m_envelope_handle_b = m_sweep_index_blue; + return false; + } + + return true; + } + + private boolean iterateRedBlueBruteForce_() { + if (--m_sweep_index_blue == -1) { + m_function = State.sweepRedBlueBruteForce; + return true; + } + + m_envelope_helper.setCoords(m_envelopes_red.get(m_sweep_index_red)); + Envelope2D envelope_b = m_envelopes_blue.get(m_sweep_index_blue); + + m_envelope_helper.inflate(m_tolerance, m_tolerance); + if (m_envelope_helper.isIntersecting(envelope_b)) { + m_envelope_handle_b = m_sweep_index_blue; + return false; + } + + return true; + } + + private boolean resetRed_() { + if (m_interval_tree_red == null) { + m_b_done = true; + return false; + } + + m_sweep_index_red = m_sorted_end_indices_red.size(); + + if (m_interval_tree_red.size() > 0) + m_interval_tree_red.reset(); + + if (m_queued_list_red != -1) { + m_queued_envelopes.deleteList(m_queued_list_red); + m_queued_indices_red.resize(0); + m_queued_list_red = -1; + } + + m_b_done = false; + return true; + } + + private boolean resetBlue_() { + if (m_interval_tree_blue == null) { + m_b_done = true; + return false; + } + + m_sweep_index_blue = m_sorted_end_indices_blue.size(); + + if (m_interval_tree_blue.size() > 0) + m_interval_tree_blue.reset(); + + if (m_queued_list_blue != -1) { + m_queued_envelopes.deleteList(m_queued_list_blue); + m_queued_indices_blue.resize(0); + m_queued_list_blue = -1; + } + + m_b_done = false; + return true; + } + + private int m_function; + + private interface State { + static final int initialize = 0; + static final int initializeRed = 1; + static final int initializeBlue = 2; + static final int initializeRedBlue = 3; + static final int sweep = 4; + static final int sweepBruteForce = 5; + static final int sweepRedBlueBruteForce = 6; + static final int sweepRedBlue = 7; + static final int sweepRed = 8; + static final int sweepBlue = 9; + static final int iterate = 10; + static final int iterateRed = 11; + static final int iterateBlue = 12; + static final int iterateBruteForce = 13; + static final int iterateRedBlueBruteForce = 14; + static final int resetRed = 15; + static final int resetBlue = 16; + } + + // *********** Helpers for Bucket sort************** + private BucketSort m_bucket_sort; + + private void sortYEndIndices_(AttributeStreamOfInt32 end_indices, int begin_, int end_, boolean b_red) { + if (m_bucket_sort == null) + m_bucket_sort = new BucketSort(); + + Envelope2DBucketSortHelper sorter = new Envelope2DBucketSortHelper(this, b_red); + m_bucket_sort.sort(end_indices, begin_, end_, sorter); + } + + private void sortYEndIndicesHelper_(AttributeStreamOfInt32 end_indices, int begin_, int end_, boolean b_red) { + end_indices.Sort(begin_, end_, new EndPointsComparer(this, b_red)); + } + + private double getAdjustedValue_(int e, boolean b_red) { + double dy = 0.5 * m_tolerance; + if (b_red) { + Envelope2D envelope_red = m_envelopes_red.get(e >> 1); + double y = (isBottom_(e) ? envelope_red.ymin - dy : envelope_red.ymax + dy); + return y; + } + + Envelope2D envelope_blue = m_envelopes_blue.get(e >> 1); + double y = (isBottom_(e) ? envelope_blue.ymin - dy : envelope_blue.ymax + dy); + return y; + } + + private static final class EndPointsComparer extends AttributeStreamOfInt32.IntComparator {// For user sort + + EndPointsComparer(Envelope2DIntersectorImpl intersector, boolean b_red) { + m_intersector = intersector; + m_b_red = b_red; + } + + @Override + public int compare(int e_1, int e_2) { + double y1 = m_intersector.getAdjustedValue_(e_1, m_b_red); + double y2 = m_intersector.getAdjustedValue_(e_2, m_b_red); + + if (y1 < y2 || (y1 == y2 && isBottom_(e_1) && isTop_(e_2))) + return -1; + + return 1; + } + + private Envelope2DIntersectorImpl m_intersector; + private boolean m_b_red; + } + + private static final class Envelope2DBucketSortHelper extends ClassicSort {// For + + // bucket + // sort + Envelope2DBucketSortHelper(Envelope2DIntersectorImpl intersector, boolean b_red) { + m_intersector = intersector; + m_b_red = b_red; + } + + @Override + public void userSort(int begin, int end, AttributeStreamOfInt32 indices) { + m_intersector.sortYEndIndicesHelper_(indices, begin, end, m_b_red); + } + + @Override + public double getValue(int index) { + return m_intersector.getAdjustedValue_(index, m_b_red); + } + + private Envelope2DIntersectorImpl m_intersector; + private boolean m_b_red; + } } diff --git a/src/main/java/com/esri/core/geometry/Envelope3D.java b/src/main/java/com/esri/core/geometry/Envelope3D.java index 8df3989d..589b8af2 100644 --- a/src/main/java/com/esri/core/geometry/Envelope3D.java +++ b/src/main/java/com/esri/core/geometry/Envelope3D.java @@ -31,336 +31,336 @@ * A class that represents axis parallel 3D rectangle. */ public final class Envelope3D implements Serializable { - private static final long serialVersionUID = 1L; - - public double xmin; - - public double ymin; - - public double zmin; - - public double xmax; - - public double ymax; - - public double zmax; - - public static Envelope3D construct(double _xmin, double _ymin, - double _zmin, double _xmax, double _ymax, double _zmax) { - Envelope3D env = new Envelope3D(_xmin, _ymin, _zmin, _xmax, _ymax, _zmax); - return env; - } - - public Envelope3D(double _xmin, double _ymin, double _zmin, double _xmax, double _ymax, double _zmax) { - setCoords(_xmin, _ymin, _zmin, _xmax, _ymax, _zmax); - } - - public Envelope3D() { - - } - - public Envelope3D(Envelope3D other) { - setCoords(other); - } - - - public void setInfinite() { - xmin = NumberUtils.negativeInf(); - xmax = NumberUtils.positiveInf(); - ymin = NumberUtils.negativeInf(); - ymax = NumberUtils.positiveInf(); - zmin = NumberUtils.negativeInf(); - zmax = NumberUtils.positiveInf(); - } - - public void setEmpty() { - xmin = NumberUtils.NaN(); - ymin = NumberUtils.NaN(); - zmin = NumberUtils.NaN(); - xmax = 0; - ymax = 0; - zmax = 0; - } - - public boolean isEmpty() { - return NumberUtils.isNaN(xmin); - } - - public void setEmptyZ() { - zmin = NumberUtils.NaN(); - } - - public boolean isEmptyZ() { - return NumberUtils.isNaN(zmin); - } - - public boolean hasEmptyDimension() { - return isEmpty() || isEmptyZ(); - } - - public void setCoords(double _xmin, double _ymin, double _zmin, - double _xmax, double _ymax, double _zmax) { - xmin = _xmin; - ymin = _ymin; - zmin = _zmin; - xmax = _xmax; - ymax = _ymax; - zmax = _zmax; - normalize(); - } - - public void setCoords(double _x, double _y, double _z) { - xmin = _x; - ymin = _y; - zmin = _z; - xmax = _x; - ymax = _y; - zmax = _z; - } - - public void setCoords(Point3D center, double width, double height, - double depth) { - xmin = center.x - width * 0.5; - xmax = xmin + width; - ymin = center.y - height * 0.5; - ymax = ymin + height; - zmin = center.z - depth * 0.5; - zmax = zmin + depth; - normalize(); - } - - public void setCoords(Envelope3D envSrc) { - - setCoords(envSrc.xmin, envSrc.ymin, envSrc.zmin, envSrc.xmax, envSrc.ymax, envSrc.zmax); - } - - public double getWidth() { - return xmax - xmin; - } - - public double getHeight() { - return ymax - ymin; - } - - public double getDepth() { - return zmax - zmin; - } - - public void move(Point3D vector) { - xmin += vector.x; - ymin += vector.y; - zmin += vector.z; - xmax += vector.x; - ymax += vector.y; - zmax += vector.z; - } - - public void normalize() { - if (isEmpty()) - return; - - double min = Math.min(xmin, xmax); - double max = Math.max(xmin, xmax); - xmin = min; - xmax = max; - min = Math.min(ymin, ymax); - max = Math.max(ymin, ymax); - ymin = min; - ymax = max; - min = Math.min(zmin, zmax); - max = Math.max(zmin, zmax); - zmin = min; - zmax = max; - } - - public void copyTo(Envelope2D env) { - env.xmin = xmin; - env.ymin = ymin; - env.xmax = xmax; - env.ymax = ymax; - } - - public void mergeNE(double x, double y, double z) { - if (xmin > x) - xmin = x; - else if (xmax < x) - xmax = x; - - if (ymin > y) - ymin = y; - else if (ymax < y) - ymax = y; - - if (zmin != NumberUtils.NaN()) { - if (zmin > z) - zmin = z; - else if (zmax < z) - zmax = z; - } else { - zmin = z; - zmax = z; - } - } - - public void merge(double x, double y, double z) { - if (isEmpty()) { - xmin = x; - ymin = y; - zmin = z; - xmax = x; - ymax = y; - zmax = z; - } else { - mergeNE(x, y, z); - } - } - - public void merge(Point3D pt) { - merge(pt.x, pt.y, pt.z); - } - - public void merge(Envelope3D other) { - if (other.isEmpty()) - return; - - merge(other.xmin, other.ymin, other.zmin); - merge(other.xmax, other.ymax, other.zmax); - } - - public void merge(double x1, double y1, double z1, double x2, double y2, - double z2) { - merge(x1, y1, z1); - merge(x2, y2, z2); - } - - public void inflate(double dx, double dy, double dz) { - if (isEmpty()) - return; - xmin -= dx; - xmax += dx; - ymin -= dy; - ymax += dy; - zmin -= dz; - zmax += dz; - if (xmin > xmax || ymin > ymax || zmin > zmax) - setEmpty(); - } - - /** - * Checks if this envelope intersects the other. - * - * @return True if this envelope intersects the other. - */ - public boolean isIntersecting(Envelope3D other) { - return !isEmpty() && !other.isEmpty() && ((xmin <= other.xmin) ? xmax >= other.xmin : other.xmax >= xmin) && // check that x projections overlap - ((ymin <= other.ymin) ? ymax >= other.ymin : other.ymax >= ymin) && // check that y projections overlap - ((zmin <= other.zmin) ? zmax >= other.zmin : other.zmax >= zmin); // check that z projections overlap - } - - /** - * Intersects this envelope with the other and stores result in this - * envelope. - * - * @return True if this envelope intersects the other, otherwise sets this - * envelope to empty state and returns False. - */ - public boolean intersect(Envelope3D other) { - if (isEmpty() || other.isEmpty()) - return false; - - if (other.xmin > xmin) - xmin = other.xmin; - - if (other.xmax < xmax) - xmax = other.xmax; - - if (other.ymin > ymin) - ymin = other.ymin; - - if (other.ymax < ymax) - ymax = other.ymax; - - if (other.zmin > zmin) - zmin = other.zmin; - - if (other.zmax < zmax) - zmax = other.zmax; - - boolean bIntersecting = xmin <= xmax && ymin <= ymax && zmin <= zmax; - - if (!bIntersecting) - setEmpty(); - - return bIntersecting; - } - - /** - * Returns True if the envelope contains the other envelope (boundary - * inclusive). - */ - public boolean contains(Envelope3D other) {// Note: Will return False, if either envelope is empty. - return other.xmin >= xmin && other.xmax <= xmax && other.ymin >= ymin && other.ymax <= ymax && other.zmin >= zmin && other.zmax <= zmax; - } - - @Override - public boolean equals(Object _other) { - if (_other == this) - return true; - - if (!(_other instanceof Envelope3D)) - return false; - - Envelope3D other = (Envelope3D) _other; - if (isEmpty() && other.isEmpty()) - return true; - - if (xmin != other.xmin || ymin != other.ymin || zmin != other.zmin || xmax != other.xmax || ymax != other.ymax || zmax != other.zmax) - return false; - - return true; - } - - public void construct(Envelope1D xinterval, Envelope1D yinterval, - Envelope1D zinterval) { - if (xinterval.isEmpty() || yinterval.isEmpty()) { - setEmpty(); - return; - } - - xmin = xinterval.vmin; - xmax = xinterval.vmax; - ymin = yinterval.vmin; - ymax = yinterval.vmax; - zmin = zinterval.vmin; - zmax = zinterval.vmax; - } - - public void queryCorners(Point3D[] corners) { - if ((corners == null) || (corners.length < 8)) - throw new IllegalArgumentException(); - - corners[0] = new Point3D(xmin, ymin, zmin); - corners[1] = new Point3D(xmin, ymax, zmin); - corners[2] = new Point3D(xmax, ymax, zmin); - corners[3] = new Point3D(xmax, ymin, zmin); - corners[4] = new Point3D(xmin, ymin, zmax); - corners[5] = new Point3D(xmin, ymax, zmax); - corners[6] = new Point3D(xmax, ymax, zmax); - corners[7] = new Point3D(xmax, ymin, zmax); - - } - - public void setFromPoints(Point3D[] points) { - if (points == null || points.length == 0) { - setEmpty(); - return; - } - - Point3D p = points[0]; - setCoords(p.x, p.y, p.z); - for (int i = 1; i < points.length; i++) { - Point3D pt = points[i]; - mergeNE(pt.x, pt.y, pt.z); - } - } + private static final long serialVersionUID = 1L; + + public double xmin; + + public double ymin; + + public double zmin; + + public double xmax; + + public double ymax; + + public double zmax; + + public static Envelope3D construct(double _xmin, double _ymin, + double _zmin, double _xmax, double _ymax, double _zmax) { + Envelope3D env = new Envelope3D(_xmin, _ymin, _zmin, _xmax, _ymax, _zmax); + return env; + } + + public Envelope3D(double _xmin, double _ymin, double _zmin, double _xmax, double _ymax, double _zmax) { + setCoords(_xmin, _ymin, _zmin, _xmax, _ymax, _zmax); + } + + public Envelope3D() { + + } + + public Envelope3D(Envelope3D other) { + setCoords(other); + } + + + public void setInfinite() { + xmin = NumberUtils.negativeInf(); + xmax = NumberUtils.positiveInf(); + ymin = NumberUtils.negativeInf(); + ymax = NumberUtils.positiveInf(); + zmin = NumberUtils.negativeInf(); + zmax = NumberUtils.positiveInf(); + } + + public void setEmpty() { + xmin = NumberUtils.NaN(); + ymin = NumberUtils.NaN(); + zmin = NumberUtils.NaN(); + xmax = 0; + ymax = 0; + zmax = 0; + } + + public boolean isEmpty() { + return NumberUtils.isNaN(xmin); + } + + public void setEmptyZ() { + zmin = NumberUtils.NaN(); + } + + public boolean isEmptyZ() { + return NumberUtils.isNaN(zmin); + } + + public boolean hasEmptyDimension() { + return isEmpty() || isEmptyZ(); + } + + public void setCoords(double _xmin, double _ymin, double _zmin, + double _xmax, double _ymax, double _zmax) { + xmin = _xmin; + ymin = _ymin; + zmin = _zmin; + xmax = _xmax; + ymax = _ymax; + zmax = _zmax; + normalize(); + } + + public void setCoords(double _x, double _y, double _z) { + xmin = _x; + ymin = _y; + zmin = _z; + xmax = _x; + ymax = _y; + zmax = _z; + } + + public void setCoords(Point3D center, double width, double height, + double depth) { + xmin = center.x - width * 0.5; + xmax = xmin + width; + ymin = center.y - height * 0.5; + ymax = ymin + height; + zmin = center.z - depth * 0.5; + zmax = zmin + depth; + normalize(); + } + + public void setCoords(Envelope3D envSrc) { + + setCoords(envSrc.xmin, envSrc.ymin, envSrc.zmin, envSrc.xmax, envSrc.ymax, envSrc.zmax); + } + + public double getWidth() { + return xmax - xmin; + } + + public double getHeight() { + return ymax - ymin; + } + + public double getDepth() { + return zmax - zmin; + } + + public void move(Point3D vector) { + xmin += vector.x; + ymin += vector.y; + zmin += vector.z; + xmax += vector.x; + ymax += vector.y; + zmax += vector.z; + } + + public void normalize() { + if (isEmpty()) + return; + + double min = Math.min(xmin, xmax); + double max = Math.max(xmin, xmax); + xmin = min; + xmax = max; + min = Math.min(ymin, ymax); + max = Math.max(ymin, ymax); + ymin = min; + ymax = max; + min = Math.min(zmin, zmax); + max = Math.max(zmin, zmax); + zmin = min; + zmax = max; + } + + public void copyTo(Envelope2D env) { + env.xmin = xmin; + env.ymin = ymin; + env.xmax = xmax; + env.ymax = ymax; + } + + public void mergeNE(double x, double y, double z) { + if (xmin > x) + xmin = x; + else if (xmax < x) + xmax = x; + + if (ymin > y) + ymin = y; + else if (ymax < y) + ymax = y; + + if (zmin != NumberUtils.NaN()) { + if (zmin > z) + zmin = z; + else if (zmax < z) + zmax = z; + } else { + zmin = z; + zmax = z; + } + } + + public void merge(double x, double y, double z) { + if (isEmpty()) { + xmin = x; + ymin = y; + zmin = z; + xmax = x; + ymax = y; + zmax = z; + } else { + mergeNE(x, y, z); + } + } + + public void merge(Point3D pt) { + merge(pt.x, pt.y, pt.z); + } + + public void merge(Envelope3D other) { + if (other.isEmpty()) + return; + + merge(other.xmin, other.ymin, other.zmin); + merge(other.xmax, other.ymax, other.zmax); + } + + public void merge(double x1, double y1, double z1, double x2, double y2, + double z2) { + merge(x1, y1, z1); + merge(x2, y2, z2); + } + + public void inflate(double dx, double dy, double dz) { + if (isEmpty()) + return; + xmin -= dx; + xmax += dx; + ymin -= dy; + ymax += dy; + zmin -= dz; + zmax += dz; + if (xmin > xmax || ymin > ymax || zmin > zmax) + setEmpty(); + } + + /** + * Checks if this envelope intersects the other. + * + * @return True if this envelope intersects the other. + */ + public boolean isIntersecting(Envelope3D other) { + return !isEmpty() && !other.isEmpty() && ((xmin <= other.xmin) ? xmax >= other.xmin : other.xmax >= xmin) && // check that x projections overlap + ((ymin <= other.ymin) ? ymax >= other.ymin : other.ymax >= ymin) && // check that y projections overlap + ((zmin <= other.zmin) ? zmax >= other.zmin : other.zmax >= zmin); // check that z projections overlap + } + + /** + * Intersects this envelope with the other and stores result in this + * envelope. + * + * @return True if this envelope intersects the other, otherwise sets this + * envelope to empty state and returns False. + */ + public boolean intersect(Envelope3D other) { + if (isEmpty() || other.isEmpty()) + return false; + + if (other.xmin > xmin) + xmin = other.xmin; + + if (other.xmax < xmax) + xmax = other.xmax; + + if (other.ymin > ymin) + ymin = other.ymin; + + if (other.ymax < ymax) + ymax = other.ymax; + + if (other.zmin > zmin) + zmin = other.zmin; + + if (other.zmax < zmax) + zmax = other.zmax; + + boolean bIntersecting = xmin <= xmax && ymin <= ymax && zmin <= zmax; + + if (!bIntersecting) + setEmpty(); + + return bIntersecting; + } + + /** + * Returns True if the envelope contains the other envelope (boundary + * inclusive). + */ + public boolean contains(Envelope3D other) {// Note: Will return False, if either envelope is empty. + return other.xmin >= xmin && other.xmax <= xmax && other.ymin >= ymin && other.ymax <= ymax && other.zmin >= zmin && other.zmax <= zmax; + } + + @Override + public boolean equals(Object _other) { + if (_other == this) + return true; + + if (!(_other instanceof Envelope3D)) + return false; + + Envelope3D other = (Envelope3D) _other; + if (isEmpty() && other.isEmpty()) + return true; + + if (xmin != other.xmin || ymin != other.ymin || zmin != other.zmin || xmax != other.xmax || ymax != other.ymax || zmax != other.zmax) + return false; + + return true; + } + + public void construct(Envelope1D xinterval, Envelope1D yinterval, + Envelope1D zinterval) { + if (xinterval.isEmpty() || yinterval.isEmpty()) { + setEmpty(); + return; + } + + xmin = xinterval.vmin; + xmax = xinterval.vmax; + ymin = yinterval.vmin; + ymax = yinterval.vmax; + zmin = zinterval.vmin; + zmax = zinterval.vmax; + } + + public void queryCorners(Point3D[] corners) { + if ((corners == null) || (corners.length < 8)) + throw new IllegalArgumentException(); + + corners[0] = new Point3D(xmin, ymin, zmin); + corners[1] = new Point3D(xmin, ymax, zmin); + corners[2] = new Point3D(xmax, ymax, zmin); + corners[3] = new Point3D(xmax, ymin, zmin); + corners[4] = new Point3D(xmin, ymin, zmax); + corners[5] = new Point3D(xmin, ymax, zmax); + corners[6] = new Point3D(xmax, ymax, zmax); + corners[7] = new Point3D(xmax, ymin, zmax); + + } + + public void setFromPoints(Point3D[] points) { + if (points == null || points.length == 0) { + setEmpty(); + return; + } + + Point3D p = points[0]; + setCoords(p.x, p.y, p.z); + for (int i = 1; i < points.length; i++) { + Point3D pt = points[i]; + mergeNE(pt.x, pt.y, pt.z); + } + } } diff --git a/src/main/java/com/esri/core/geometry/GeneralizeComparator.java b/src/main/java/com/esri/core/geometry/GeneralizeComparator.java index 6cf7e771..71b374d1 100644 --- a/src/main/java/com/esri/core/geometry/GeneralizeComparator.java +++ b/src/main/java/com/esri/core/geometry/GeneralizeComparator.java @@ -6,218 +6,218 @@ * Created by davidraleigh on 4/19/16. */ class GeneralizeComparator extends Treap.Comparator { - class EditShapeTriangle { - int m_prevVertexIndex; - int m_nextVertexIndex; - int m_vertexIndex; + class EditShapeTriangle { + int m_prevVertexIndex; + int m_nextVertexIndex; + int m_vertexIndex; - private Point2D m_point; - private Point2D m_prevPoint; - private Point2D m_nextPoint; - private double m_area; - private int m_orientation; + private Point2D m_point; + private Point2D m_prevPoint; + private Point2D m_nextPoint; + private double m_area; + private int m_orientation; - EditShapeTriangle() { + EditShapeTriangle() { - } + } - EditShapeTriangle(EditShape editShape, int iVertex) { - setTriangle(editShape, iVertex); - } + EditShapeTriangle(EditShape editShape, int iVertex) { + setTriangle(editShape, iVertex); + } - void setTriangle(EditShape editShape, int iVertex) { - int prevVertex = editShape.getPrevVertex(iVertex); - int nextVertex = editShape.getNextVertex(iVertex); - Point2D prevPoint = editShape.getXY(prevVertex); - Point2D currentPoint = editShape.getXY(iVertex); - Point2D nextPoint = editShape.getXY(nextVertex); + void setTriangle(EditShape editShape, int iVertex) { + int prevVertex = editShape.getPrevVertex(iVertex); + int nextVertex = editShape.getNextVertex(iVertex); + Point2D prevPoint = editShape.getXY(prevVertex); + Point2D currentPoint = editShape.getXY(iVertex); + Point2D nextPoint = editShape.getXY(nextVertex); - m_point = currentPoint; - m_prevPoint = prevPoint; - m_nextPoint = nextPoint; - m_vertexIndex = iVertex; - m_prevVertexIndex = prevVertex; - m_nextVertexIndex = nextVertex; - updateArea(); - updateOrientation(); - } - - - int getIndex() { - return m_vertexIndex; - } - - - double queryArea() { - return m_area; - } - - - int queryOrientation() { - return m_orientation; - } - - - void updateArea() { - m_area = m_prevPoint.calculateTriangleArea2D(m_point, m_nextPoint); - } - - - void updateOrientation() { - m_orientation = Point2D.orientationRobust(m_prevPoint, m_point, m_nextPoint); - } - } - - EditShape m_editShape; - int m_vertex_1 = -1; - int m_vertex_2 = -1; - int m_modulus_distribution = 0; - int m_subDivisions = 8; - GeneralizeType m_generalizeType; - - EditShapeTriangle m_temp_triangle_1 = null; - EditShapeTriangle m_temp_triangle_2 = null; - - ArrayList m_triangle_nodes_buffer; - ArrayList m_triangle_nodes_recycle; - ArrayList m_triangle_nodes_cache; - - GeneralizeComparator(EditShape editShape, GeneralizeType generalizeType) { - super(true); - m_editShape = editShape; - - m_generalizeType = generalizeType; - - m_triangle_nodes_buffer = new ArrayList(); - m_triangle_nodes_recycle = new ArrayList(); - m_triangle_nodes_cache = new ArrayList(); - - m_temp_triangle_1 = new EditShapeTriangle(); - m_temp_triangle_2 = new EditShapeTriangle(); - - m_modulus_distribution = 0; - - int s = Math.min(editShape.getTotalPointCount() * 3 / 2, (int) (67 /* SIMPLEDGE_CACHESIZE */)); - int cache_size = Math.min((int) 7, s); - - // TODO is this necessary or would a reserve call work? - for (int i = 0; i < cache_size; i++) { - m_triangle_nodes_cache.add(null); - } - } - - EditShapeTriangle createTriangle(int value) { - EditShapeTriangle triangle = new EditShapeTriangle(m_editShape, value); - return triangle; - } - - // Returns a cached edge for the given value. May return NULL. - EditShapeTriangle tryGetCachedTriangle_(int value) { - int ind = (value & NumberUtils.intMax()) % m_triangle_nodes_cache.size(); - EditShapeTriangle triangle = m_triangle_nodes_cache.get(ind); - if (triangle != null) { - if (triangle.m_vertexIndex == value) - return triangle; - else { - // int i = 0; - // cache collision - } - } - return null; - } - - // Removes cached edge from the cache for the given value. - void tryDeleteCachedTriangle_(int value) { - int ind = (value & NumberUtils.intMax()) % m_triangle_nodes_cache.size(); - EditShapeTriangle se = m_triangle_nodes_cache.get(ind); - if (se != null && se.m_vertexIndex == value) {// this value is cached - m_triangle_nodes_recycle.add(se); - m_triangle_nodes_cache.set(ind, null); - } else { - // The value has not been cached - } - } - - EditShapeTriangle tryCreateCachedTriangle_(int value) { - int ind = (value & NumberUtils.intMax()) % m_triangle_nodes_cache.size(); - EditShapeTriangle triangle = m_triangle_nodes_cache.get(ind); - if (triangle == null) { - if (m_triangle_nodes_recycle.isEmpty()) { - m_triangle_nodes_buffer.add(new EditShapeTriangle(m_editShape, value)); - triangle = m_triangle_nodes_buffer.get(m_triangle_nodes_buffer.size() - 1); - } else { - triangle = m_triangle_nodes_recycle.get(m_triangle_nodes_recycle.size() - 1); - m_triangle_nodes_recycle.remove(m_triangle_nodes_recycle.size() - 1); - triangle.setTriangle(m_editShape, value); - } - - m_triangle_nodes_cache.set(ind, triangle); - return triangle; - } else { - assert (triangle.getIndex() != value); - } - - return null; - } - - void setPathCount(int pathCount) { - if (pathCount < m_subDivisions * 2) - m_modulus_distribution = pathCount / 2; - else - m_modulus_distribution = pathCount / m_subDivisions; - if (m_modulus_distribution == 5) - m_modulus_distribution *= 2; - } - - @Override - public int compare(Treap treap, int left, int node) { - int right = treap.getElement(node); - - return compareTriangles(left, left, right, right); - } - - int compareTriangles(int leftElm, int left_vertex, int right_elm, int right_vertex) { - EditShapeTriangle triangleLeft = tryGetCachedTriangle_(leftElm); - if (triangleLeft == null) { - if (m_vertex_1 == left_vertex) { - triangleLeft = m_temp_triangle_1; - } else { - m_vertex_1 = left_vertex; - triangleLeft = tryCreateCachedTriangle_(leftElm); - if (triangleLeft == null) { - triangleLeft = m_temp_triangle_1; - m_temp_triangle_1.setTriangle(m_editShape, leftElm); - } - - } - } else { - m_vertex_1 = left_vertex; - } - - EditShapeTriangle triangleRight = tryGetCachedTriangle_(right_elm); - if (triangleRight == null) { - if (m_vertex_2 == right_vertex) { - triangleRight = m_temp_triangle_2; - } else { - m_vertex_2 = right_vertex; - triangleRight = tryCreateCachedTriangle_(right_elm); - if (triangleRight == null) { - triangleRight = m_temp_triangle_2; - m_temp_triangle_2.setTriangle(m_editShape, right_elm); - } - } - } else { - m_vertex_2 = right_vertex; - } - - return compare(triangleLeft, triangleRight); - } - - @Override - void onDelete(int elm) { + m_point = currentPoint; + m_prevPoint = prevPoint; + m_nextPoint = nextPoint; + m_vertexIndex = iVertex; + m_prevVertexIndex = prevVertex; + m_nextVertexIndex = nextVertex; + updateArea(); + updateOrientation(); + } + + + int getIndex() { + return m_vertexIndex; + } + + + double queryArea() { + return m_area; + } + + + int queryOrientation() { + return m_orientation; + } + + + void updateArea() { + m_area = m_prevPoint.calculateTriangleArea2D(m_point, m_nextPoint); + } + + + void updateOrientation() { + m_orientation = Point2D.orientationRobust(m_prevPoint, m_point, m_nextPoint); + } + } + + EditShape m_editShape; + int m_vertex_1 = -1; + int m_vertex_2 = -1; + int m_modulus_distribution = 0; + int m_subDivisions = 8; + GeneralizeType m_generalizeType; + + EditShapeTriangle m_temp_triangle_1 = null; + EditShapeTriangle m_temp_triangle_2 = null; + + ArrayList m_triangle_nodes_buffer; + ArrayList m_triangle_nodes_recycle; + ArrayList m_triangle_nodes_cache; + + GeneralizeComparator(EditShape editShape, GeneralizeType generalizeType) { + super(true); + m_editShape = editShape; + + m_generalizeType = generalizeType; + + m_triangle_nodes_buffer = new ArrayList(); + m_triangle_nodes_recycle = new ArrayList(); + m_triangle_nodes_cache = new ArrayList(); + + m_temp_triangle_1 = new EditShapeTriangle(); + m_temp_triangle_2 = new EditShapeTriangle(); + + m_modulus_distribution = 0; + + int s = Math.min(editShape.getTotalPointCount() * 3 / 2, (int) (67 /* SIMPLEDGE_CACHESIZE */)); + int cache_size = Math.min((int) 7, s); + + // TODO is this necessary or would a reserve call work? + for (int i = 0; i < cache_size; i++) { + m_triangle_nodes_cache.add(null); + } + } + + EditShapeTriangle createTriangle(int value) { + EditShapeTriangle triangle = new EditShapeTriangle(m_editShape, value); + return triangle; + } + + // Returns a cached edge for the given value. May return NULL. + EditShapeTriangle tryGetCachedTriangle_(int value) { + int ind = (value & NumberUtils.intMax()) % m_triangle_nodes_cache.size(); + EditShapeTriangle triangle = m_triangle_nodes_cache.get(ind); + if (triangle != null) { + if (triangle.m_vertexIndex == value) + return triangle; + else { + // int i = 0; + // cache collision + } + } + return null; + } + + // Removes cached edge from the cache for the given value. + void tryDeleteCachedTriangle_(int value) { + int ind = (value & NumberUtils.intMax()) % m_triangle_nodes_cache.size(); + EditShapeTriangle se = m_triangle_nodes_cache.get(ind); + if (se != null && se.m_vertexIndex == value) {// this value is cached + m_triangle_nodes_recycle.add(se); + m_triangle_nodes_cache.set(ind, null); + } else { + // The value has not been cached + } + } + + EditShapeTriangle tryCreateCachedTriangle_(int value) { + int ind = (value & NumberUtils.intMax()) % m_triangle_nodes_cache.size(); + EditShapeTriangle triangle = m_triangle_nodes_cache.get(ind); + if (triangle == null) { + if (m_triangle_nodes_recycle.isEmpty()) { + m_triangle_nodes_buffer.add(new EditShapeTriangle(m_editShape, value)); + triangle = m_triangle_nodes_buffer.get(m_triangle_nodes_buffer.size() - 1); + } else { + triangle = m_triangle_nodes_recycle.get(m_triangle_nodes_recycle.size() - 1); + m_triangle_nodes_recycle.remove(m_triangle_nodes_recycle.size() - 1); + triangle.setTriangle(m_editShape, value); + } + + m_triangle_nodes_cache.set(ind, triangle); + return triangle; + } else { + assert (triangle.getIndex() != value); + } + + return null; + } + + void setPathCount(int pathCount) { + if (pathCount < m_subDivisions * 2) + m_modulus_distribution = pathCount / 2; + else + m_modulus_distribution = pathCount / m_subDivisions; + if (m_modulus_distribution == 5) + m_modulus_distribution *= 2; + } + + @Override + public int compare(Treap treap, int left, int node) { + int right = treap.getElement(node); + + return compareTriangles(left, left, right, right); + } + + int compareTriangles(int leftElm, int left_vertex, int right_elm, int right_vertex) { + EditShapeTriangle triangleLeft = tryGetCachedTriangle_(leftElm); + if (triangleLeft == null) { + if (m_vertex_1 == left_vertex) { + triangleLeft = m_temp_triangle_1; + } else { + m_vertex_1 = left_vertex; + triangleLeft = tryCreateCachedTriangle_(leftElm); + if (triangleLeft == null) { + triangleLeft = m_temp_triangle_1; + m_temp_triangle_1.setTriangle(m_editShape, leftElm); + } + + } + } else { + m_vertex_1 = left_vertex; + } + + EditShapeTriangle triangleRight = tryGetCachedTriangle_(right_elm); + if (triangleRight == null) { + if (m_vertex_2 == right_vertex) { + triangleRight = m_temp_triangle_2; + } else { + m_vertex_2 = right_vertex; + triangleRight = tryCreateCachedTriangle_(right_elm); + if (triangleRight == null) { + triangleRight = m_temp_triangle_2; + m_temp_triangle_2.setTriangle(m_editShape, right_elm); + } + } + } else { + m_vertex_2 = right_vertex; + } + + return compare(triangleLeft, triangleRight); + } + + @Override + void onDelete(int elm) { //// EditShapeTriangle triangle = tryGetCachedTriangle_(elm); //// if (triangle == null) { //// triangle = tryCreateCachedTriangle_(elm); @@ -226,7 +226,7 @@ void onDelete(int elm) { //// int prevVertexIndex = triangle.m_prevVertexIndex; //// int nextVertexIndex = triangle.m_nextVertexIndex; - tryDeleteCachedTriangle_(elm); + tryDeleteCachedTriangle_(elm); //// EditShapeTriangle trianglePrev = tryGetCachedTriangle_(prevVertexIndex); //// if (trianglePrev == null) { @@ -236,7 +236,7 @@ void onDelete(int elm) { //// if (triangleNext == null) { //// triangleNext = tryCreateCachedTriangle_(nextVertexIndex); //// } - } + } // // @Override // void onSet(int oldelm) { @@ -253,74 +253,74 @@ void onDelete(int elm) { // tryDeleteCachedTriangle_(elm); // } - int compare(EditShapeTriangle tri1, EditShapeTriangle tri2) { - - if (m_generalizeType != GeneralizeType.Neither) { - // 1 for obtuse angle counter-clockwise, - // -1 for obtuse angle clockwise - // 0 for collinear - int orientation1 = tri1.queryOrientation(); - int orientation2 = tri2.queryOrientation(); - - if (m_generalizeType == GeneralizeType.ResultContainsOriginal) { - // if the result contains the original no vertices with a - // counter clockwise obtuse angle rotation (1) can be removed - if (orientation1 < 0 && orientation2 > 0) { - return 1; - } else if (orientation2 < 0 && orientation1 > 0) { - return -1; - } else if (orientation1 > 0 && orientation2 > 0) { - // Treap requires a unique definition of the positions in the case - // of deletions. no 0 returns allowed - - // for cases where there is a really even distribution of points this seperates the group - if (tri1.m_vertexIndex % m_modulus_distribution > tri1.m_vertexIndex % m_modulus_distribution) - return -1; - else if (tri1.m_vertexIndex % m_modulus_distribution < tri1.m_vertexIndex % m_modulus_distribution) - return 1; - else if (tri1.m_vertexIndex > tri2.m_vertexIndex) - return -1; - else if (tri1.m_vertexIndex < tri2.m_vertexIndex) - return 1; - return 0; - } - } else if (m_generalizeType == GeneralizeType.ResultWithinOriginal) { - if (orientation1 < 0 && orientation2 > 0) { - return -1; - } else if (orientation2 < 0 && orientation1 > 0) { - return 1; - } else if (orientation1 < 0 && orientation2 < 0) { - if (tri1.m_vertexIndex % m_modulus_distribution > tri1.m_vertexIndex % m_modulus_distribution) - return -1; - else if (tri1.m_vertexIndex % m_modulus_distribution < tri1.m_vertexIndex % m_modulus_distribution) - return 1; - else if (tri1.m_vertexIndex > tri2.m_vertexIndex) - return -1; - else if (tri1.m_vertexIndex < tri2.m_vertexIndex) - return 1; - return 0; - } - } - } - - // else if GeneralizeType.Neither - double area1 = tri1.queryArea(); - double area2 = tri2.queryArea(); - - if (area1 < area2) { - return -1; - } else if (area2 < area1) { - return 1; - } else if (tri1.m_vertexIndex % m_modulus_distribution > tri1.m_vertexIndex % m_modulus_distribution) - return -1; - else if (tri1.m_vertexIndex % m_modulus_distribution < tri1.m_vertexIndex % m_modulus_distribution) - return 1; - else if (tri1.m_vertexIndex > tri2.m_vertexIndex) { - return -1; - } else if (tri1.m_vertexIndex < tri2.m_vertexIndex) { - return 1; - } - - return 0; - } + int compare(EditShapeTriangle tri1, EditShapeTriangle tri2) { + + if (m_generalizeType != GeneralizeType.Neither) { + // 1 for obtuse angle counter-clockwise, + // -1 for obtuse angle clockwise + // 0 for collinear + int orientation1 = tri1.queryOrientation(); + int orientation2 = tri2.queryOrientation(); + + if (m_generalizeType == GeneralizeType.ResultContainsOriginal) { + // if the result contains the original no vertices with a + // counter clockwise obtuse angle rotation (1) can be removed + if (orientation1 < 0 && orientation2 > 0) { + return 1; + } else if (orientation2 < 0 && orientation1 > 0) { + return -1; + } else if (orientation1 > 0 && orientation2 > 0) { + // Treap requires a unique definition of the positions in the case + // of deletions. no 0 returns allowed + + // for cases where there is a really even distribution of points this seperates the group + if (tri1.m_vertexIndex % m_modulus_distribution > tri1.m_vertexIndex % m_modulus_distribution) + return -1; + else if (tri1.m_vertexIndex % m_modulus_distribution < tri1.m_vertexIndex % m_modulus_distribution) + return 1; + else if (tri1.m_vertexIndex > tri2.m_vertexIndex) + return -1; + else if (tri1.m_vertexIndex < tri2.m_vertexIndex) + return 1; + return 0; + } + } else if (m_generalizeType == GeneralizeType.ResultWithinOriginal) { + if (orientation1 < 0 && orientation2 > 0) { + return -1; + } else if (orientation2 < 0 && orientation1 > 0) { + return 1; + } else if (orientation1 < 0 && orientation2 < 0) { + if (tri1.m_vertexIndex % m_modulus_distribution > tri1.m_vertexIndex % m_modulus_distribution) + return -1; + else if (tri1.m_vertexIndex % m_modulus_distribution < tri1.m_vertexIndex % m_modulus_distribution) + return 1; + else if (tri1.m_vertexIndex > tri2.m_vertexIndex) + return -1; + else if (tri1.m_vertexIndex < tri2.m_vertexIndex) + return 1; + return 0; + } + } + } + + // else if GeneralizeType.Neither + double area1 = tri1.queryArea(); + double area2 = tri2.queryArea(); + + if (area1 < area2) { + return -1; + } else if (area2 < area1) { + return 1; + } else if (tri1.m_vertexIndex % m_modulus_distribution > tri1.m_vertexIndex % m_modulus_distribution) + return -1; + else if (tri1.m_vertexIndex % m_modulus_distribution < tri1.m_vertexIndex % m_modulus_distribution) + return 1; + else if (tri1.m_vertexIndex > tri2.m_vertexIndex) { + return -1; + } else if (tri1.m_vertexIndex < tri2.m_vertexIndex) { + return 1; + } + + return 0; + } } \ No newline at end of file diff --git a/src/main/java/com/esri/core/geometry/GeneralizeType.java b/src/main/java/com/esri/core/geometry/GeneralizeType.java index 92e5beb0..fab10743 100644 --- a/src/main/java/com/esri/core/geometry/GeneralizeType.java +++ b/src/main/java/com/esri/core/geometry/GeneralizeType.java @@ -4,7 +4,7 @@ * Created by davidraleigh on 4/17/16. */ public enum GeneralizeType { - ResultContainsOriginal, - ResultWithinOriginal, - Neither + ResultContainsOriginal, + ResultWithinOriginal, + Neither } diff --git a/src/main/java/com/esri/core/geometry/GenericGeometrySerializer.java b/src/main/java/com/esri/core/geometry/GenericGeometrySerializer.java index aa69e572..a276ccdd 100644 --- a/src/main/java/com/esri/core/geometry/GenericGeometrySerializer.java +++ b/src/main/java/com/esri/core/geometry/GenericGeometrySerializer.java @@ -29,66 +29,66 @@ //This is a writeReplace class for MultiPoint, Polyline, and Polygon public class GenericGeometrySerializer implements Serializable { - private static final long serialVersionUID = 1L; - int geometryType; - byte[] esriShape = null; - int simpleFlag = 0; - double tolerance = 0; - boolean[] ogcFlags = null; + private static final long serialVersionUID = 1L; + int geometryType; + byte[] esriShape = null; + int simpleFlag = 0; + double tolerance = 0; + boolean[] ogcFlags = null; - public Object readResolve() throws ObjectStreamException { - Geometry geometry = null; - try { - geometry = GeometryEngine.geometryFromEsriShape( - esriShape, Geometry.Type.intToType(geometryType)); + public Object readResolve() throws ObjectStreamException { + Geometry geometry = null; + try { + geometry = GeometryEngine.geometryFromEsriShape( + esriShape, Geometry.Type.intToType(geometryType)); - if (Geometry.isMultiVertex(geometryType)) { - MultiVertexGeometryImpl mvImpl = (MultiVertexGeometryImpl) geometry - ._getImpl(); - if (!geometry.isEmpty() - && Geometry.isMultiPath(geometryType)) { - MultiPathImpl mpImpl = (MultiPathImpl) geometry._getImpl(); - AttributeStreamOfInt8 pathFlags = mpImpl - .getPathFlagsStreamRef(); - for (int i = 0, n = mpImpl.getPathCount(); i < n; i++) { - if (ogcFlags[i]) - pathFlags.setBits(i, - (byte) PathFlags.enumOGCStartPolygon); - } - } - mvImpl.setIsSimple(simpleFlag, tolerance, false); - } - } catch (Exception ex) { - throw new InvalidObjectException("Cannot read geometry from stream"); - } - return geometry; - } + if (Geometry.isMultiVertex(geometryType)) { + MultiVertexGeometryImpl mvImpl = (MultiVertexGeometryImpl) geometry + ._getImpl(); + if (!geometry.isEmpty() + && Geometry.isMultiPath(geometryType)) { + MultiPathImpl mpImpl = (MultiPathImpl) geometry._getImpl(); + AttributeStreamOfInt8 pathFlags = mpImpl + .getPathFlagsStreamRef(); + for (int i = 0, n = mpImpl.getPathCount(); i < n; i++) { + if (ogcFlags[i]) + pathFlags.setBits(i, + (byte) PathFlags.enumOGCStartPolygon); + } + } + mvImpl.setIsSimple(simpleFlag, tolerance, false); + } + } catch (Exception ex) { + throw new InvalidObjectException("Cannot read geometry from stream"); + } + return geometry; + } - public void setGeometryByValue(Geometry geometry) - throws ObjectStreamException { - try { - esriShape = GeometryEngine - .geometryToEsriShape(geometry); - geometryType = geometry.getType().value(); - if (Geometry.isMultiVertex(geometryType)) { - MultiVertexGeometryImpl mvImpl = (MultiVertexGeometryImpl) geometry - ._getImpl(); - tolerance = mvImpl.m_simpleTolerance; - simpleFlag = mvImpl.getIsSimple(0); - if (!geometry.isEmpty() - && Geometry.isMultiPath(geometryType)) { - MultiPathImpl mpImpl = (MultiPathImpl) geometry._getImpl(); - ogcFlags = new boolean[mpImpl.getPathCount()]; - AttributeStreamOfInt8 pathFlags = mpImpl - .getPathFlagsStreamRef(); - for (int i = 0, n = mpImpl.getPathCount(); i < n; i++) { - ogcFlags[i] = (pathFlags.read(i) & (byte) PathFlags.enumOGCStartPolygon) != 0; - } - } + public void setGeometryByValue(Geometry geometry) + throws ObjectStreamException { + try { + esriShape = GeometryEngine + .geometryToEsriShape(geometry); + geometryType = geometry.getType().value(); + if (Geometry.isMultiVertex(geometryType)) { + MultiVertexGeometryImpl mvImpl = (MultiVertexGeometryImpl) geometry + ._getImpl(); + tolerance = mvImpl.m_simpleTolerance; + simpleFlag = mvImpl.getIsSimple(0); + if (!geometry.isEmpty() + && Geometry.isMultiPath(geometryType)) { + MultiPathImpl mpImpl = (MultiPathImpl) geometry._getImpl(); + ogcFlags = new boolean[mpImpl.getPathCount()]; + AttributeStreamOfInt8 pathFlags = mpImpl + .getPathFlagsStreamRef(); + for (int i = 0, n = mpImpl.getPathCount(); i < n; i++) { + ogcFlags[i] = (pathFlags.read(i) & (byte) PathFlags.enumOGCStartPolygon) != 0; + } + } - } - } catch (Exception ex) { - throw new InvalidObjectException("Cannot serialize this geometry"); - } - } + } + } catch (Exception ex) { + throw new InvalidObjectException("Cannot serialize this geometry"); + } + } } diff --git a/src/main/java/com/esri/core/geometry/GeoDist.java b/src/main/java/com/esri/core/geometry/GeoDist.java index 0d9b54c3..e75e12f5 100644 --- a/src/main/java/com/esri/core/geometry/GeoDist.java +++ b/src/main/java/com/esri/core/geometry/GeoDist.java @@ -25,60 +25,60 @@ package com.esri.core.geometry; final class GeoDist { - private static final double PE_PI = 3.14159265358979323846264; - private static final double PE_PI2 = 1.57079632679489661923132; - private static final double PE_2PI = 6.283185307179586476925287; - private static final double PE_EPS = 3.55271367880050092935562e-15; - private static final double RAD_TO_DEG = 180.0 / PE_PI; - private static final double DEG_TO_RAD = PE_PI / 180.0; - - /** - * Get the absolute value of a number - */ - static private double PE_ABS(double a) { - return (a < 0) ? -a : a; - } - - /** - * Assign the sign of the second number to the first - */ - static private double PE_SGN(double a, double b) { - return (b >= 0) ? PE_ABS(a) : -PE_ABS(a); - } - - /** - * Determine if two doubles are equal within a default tolerance - */ - static private boolean PE_EQ(double a, double b) { - return (a == b) - || PE_ABS(a - b) <= PE_EPS * (1 + (PE_ABS(a) + PE_ABS(b)) / 2); - } - - /** - * Determine if a double is within a given tolerance of zero - */ - static private boolean PE_ZERO(double a) { - return (a == 0.0) || (PE_ABS(a) <= PE_EPS); - } - - static private double lam_delta(double lam) { - double d = Math.IEEEremainder(lam, PE_2PI); - - return (PE_ABS(d) <= PE_PI) ? d : ((d < 0) ? d + PE_2PI : d - PE_2PI); - } - - static private void lam_phi_reduction(PeDouble p_lam, PeDouble p_phi) { - p_lam.val = lam_delta(p_lam.val); - p_phi.val = lam_delta(p_phi.val); - - if (PE_ABS(p_phi.val) > PE_PI2) { - p_lam.val = lam_delta(p_lam.val + PE_PI); - p_phi.val = PE_SGN(PE_PI, p_phi.val) - p_phi.val; - } - } - - static private double q90(double a, double e2) { - /* + private static final double PE_PI = 3.14159265358979323846264; + private static final double PE_PI2 = 1.57079632679489661923132; + private static final double PE_2PI = 6.283185307179586476925287; + private static final double PE_EPS = 3.55271367880050092935562e-15; + private static final double RAD_TO_DEG = 180.0 / PE_PI; + private static final double DEG_TO_RAD = PE_PI / 180.0; + + /** + * Get the absolute value of a number + */ + static private double PE_ABS(double a) { + return (a < 0) ? -a : a; + } + + /** + * Assign the sign of the second number to the first + */ + static private double PE_SGN(double a, double b) { + return (b >= 0) ? PE_ABS(a) : -PE_ABS(a); + } + + /** + * Determine if two doubles are equal within a default tolerance + */ + static private boolean PE_EQ(double a, double b) { + return (a == b) + || PE_ABS(a - b) <= PE_EPS * (1 + (PE_ABS(a) + PE_ABS(b)) / 2); + } + + /** + * Determine if a double is within a given tolerance of zero + */ + static private boolean PE_ZERO(double a) { + return (a == 0.0) || (PE_ABS(a) <= PE_EPS); + } + + static private double lam_delta(double lam) { + double d = Math.IEEEremainder(lam, PE_2PI); + + return (PE_ABS(d) <= PE_PI) ? d : ((d < 0) ? d + PE_2PI : d - PE_2PI); + } + + static private void lam_phi_reduction(PeDouble p_lam, PeDouble p_phi) { + p_lam.val = lam_delta(p_lam.val); + p_phi.val = lam_delta(p_phi.val); + + if (PE_ABS(p_phi.val) > PE_PI2) { + p_lam.val = lam_delta(p_lam.val + PE_PI); + p_phi.val = PE_SGN(PE_PI, p_phi.val) - p_phi.val; + } + } + + static private double q90(double a, double e2) { + /* * Rapp // Geometric Geodesy (Part I) // p. 39. Adams, O.S. // Latitude * Developments ... // pp. 122-127. Terms extended past n4 by David * Burrows, ESRI @@ -91,325 +91,325 @@ static private double q90(double a, double e2) { * 49/65536 n10 + ...)/(1.0 + n) */ - double t = Math.sqrt(1.0 - e2); - double n = (1.0 - t) / (1.0 + t); - double n2 = n * n; - - return a / (1.0 + n) - * (1.0 + n2 * (1.0 / 4.0 + n2 * (1.0 / 64.0 + n2 * (1.0 / 256.0)))) - * PE_PI2; - } - - // This should be a method within Envelop2D - public static void inflateEnv2D( - double a, - double e2, - Envelope2D env2D, - double dxMeters, - double dyMeters) { - if (env2D.isEmpty()) - return; - - double yMinAzimuth = dyMeters > 0 ? Math.PI : 0; - double yMaxAzimuth = dyMeters > 0 ? 0 : Math.PI; - - double xMinAzimuth = dxMeters > 0 ? 3 * Math.PI / 2 : Math.PI / 2; - double xMaxAzimuth = dxMeters > 0 ? Math.PI / 2 : 3 * Math.PI / 2; - - dxMeters = Math.abs(dxMeters); - dyMeters = Math.abs(dyMeters); - - PeDouble lamMin = new PeDouble(); - PeDouble phiMin = new PeDouble(); - PeDouble lamMax = new PeDouble(); - PeDouble phiMax = new PeDouble(); - PeDouble dummyVar = new PeDouble(); - - // TODO this probably needs improvement. Maybe a check to see which hemisphere and then whether to measure the - // x values at ymin or ymax and measure the y values at the xmin or xmax - // new ymin - geodesic_forward(a, e2, env2D.xmin * DEG_TO_RAD, env2D.ymin * DEG_TO_RAD, dyMeters, yMinAzimuth, dummyVar, phiMin); - // new xmin - geodesic_forward(a, e2, env2D.xmin * DEG_TO_RAD, env2D.ymax * DEG_TO_RAD, dxMeters, xMinAzimuth, lamMin, dummyVar); - // new yMAX - geodesic_forward(a, e2, env2D.xmax * DEG_TO_RAD, env2D.ymax * DEG_TO_RAD, dyMeters, yMaxAzimuth, dummyVar, phiMax); - // new xmax - geodesic_forward(a, e2, env2D.xmax * DEG_TO_RAD, env2D.ymin * DEG_TO_RAD, dxMeters, xMaxAzimuth, lamMax, dummyVar); - - env2D.ymax = phiMax.val * RAD_TO_DEG; - env2D.xmin = lamMin.val * RAD_TO_DEG; - env2D.ymin = phiMin.val * RAD_TO_DEG; - env2D.xmax = lamMax.val * RAD_TO_DEG; - - if (env2D.xmin > env2D.xmax || env2D.ymin > env2D.ymax) - env2D.setEmpty(); - } - - /** - * Gets the max geodetic width of an envelope - * - * @param a - * @param e2 - * @param env2D - * @return - */ - public static double getEnvWidth(double a, - double e2, - Envelope2D env2D) { - double rpu = Math.PI / 180.0; - PeDouble answer = new PeDouble(); - GeoDist.geodesic_distance_ngs( - a, - e2, - env2D.xmin * rpu, - env2D.ymin * rpu, - env2D.xmax * rpu, - env2D.ymin * rpu, - answer, null, null); - double lowerWidth = answer.val; - GeoDist.geodesic_distance_ngs( - a, - e2, - env2D.xmin * rpu, - env2D.ymax * rpu, - env2D.xmax * rpu, - env2D.ymax * rpu, - answer, null, null); - if (lowerWidth > answer.val) - return lowerWidth; - return answer.val; - } - - public static double getEnvHeight(double a, - double e2, - Envelope2D env2D) { - double rpu = Math.PI / 180.0; - PeDouble answer = new PeDouble(); - GeoDist.geodesic_distance_ngs( - a, - e2, - env2D.xmin * rpu, - env2D.ymin * rpu, - env2D.xmin * rpu, - env2D.ymax * rpu, - answer, null, null); - double leftHeight = answer.val; - GeoDist.geodesic_distance_ngs( - a, - e2, - env2D.xmax * rpu, - env2D.ymin * rpu, - env2D.xmax * rpu, - env2D.ymax * rpu, - answer, null, null); - if (leftHeight > answer.val) - return leftHeight; - return answer.val; - } - - public static Point2D getEnvCenter(Geometry geometry, SpatialReference spatialReference) { - Envelope2D inputEnvelope2D = new Envelope2D(); - - if (spatialReference == null) { - throw new GeometryException("Requires spatial reference"); - } - - if (spatialReference.getID() != 4326) { - OperatorProject operatorProject = (OperatorProject) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.Project); - // TODO this should be grabbing the GCS wkid from the input spatialreference instead of assuming 4326 - ProjectionTransformation projectionTransformation = new ProjectionTransformation(spatialReference, SpatialReference.create(4326)); - Geometry projectedGeom = operatorProject.execute(geometry, projectionTransformation, null); - projectedGeom.queryEnvelope2D(inputEnvelope2D); - } else { - geometry.queryEnvelope2D(inputEnvelope2D); - } - - // From GEOGRAPHIC Grab point - double a = spatialReference.getMajorAxis(); - double e2 = spatialReference.getEccentricitySquared(); - - Point2D ptCenter = new Point2D(); - GeoDist.getEnvCenter(a, e2, inputEnvelope2D, ptCenter); - - return ptCenter; - } - - public static void getEnvCenter(double a, - double e2, - Envelope2D env2D, - Point2D geodeticCenter) { - // Get minx miny, minx maxy midpoint - Point2D lowerRightUpperLeft = new Point2D(); - GeoDist.geodesicMidpoint(a, e2, env2D.getLowerRight(), env2D.getUpperLeft(), lowerRightUpperLeft); - - - // Get maxx miny, maxx maxy midpoint - Point2D upperRightLowerLeft = new Point2D(); - GeoDist.geodesicMidpoint(a, e2, env2D.getLowerLeft(), env2D.getUpperRight(), upperRightLowerLeft); - - // Get midpoint of midpoints - GeoDist.geodesicMidpoint(a, e2, lowerRightUpperLeft, upperRightLowerLeft, geodeticCenter); - } - - static public void geodesicMidpoint(double a, double e2, Point2D fromPoint, Point2D toPoint, Point2D outMidPoint) { - // TODO calculate distance - PeDouble azFromTo = new PeDouble(); - double distance = geodesicDistance(a, e2, fromPoint, toPoint, azFromTo, null); - - // TODO calculate midpoint - geodesicForward(a, e2, fromPoint, distance / 2f, azFromTo.val, outMidPoint); - } - - static public void geodesicForward(double a, - double e2, - Point2D fromPoint, - double distance, - double azimuth, - Point2D outToPoint) { - PeDouble lam2 = new PeDouble(); - PeDouble phi2 = new PeDouble(); - GeoDist.geodesic_forward( - a, - e2, - fromPoint.x * DEG_TO_RAD, - fromPoint.y * DEG_TO_RAD, - distance, - azimuth, - lam2, - phi2); - - outToPoint.x = lam2.val * RAD_TO_DEG; - outToPoint.y = phi2.val * RAD_TO_DEG; - } - - static public double geodesicDistance(double a, - double e2, - Point2D fromPoint, - Point2D toPoint, - PeDouble azFromTo, - PeDouble azToFrom) { + double t = Math.sqrt(1.0 - e2); + double n = (1.0 - t) / (1.0 + t); + double n2 = n * n; + + return a / (1.0 + n) + * (1.0 + n2 * (1.0 / 4.0 + n2 * (1.0 / 64.0 + n2 * (1.0 / 256.0)))) + * PE_PI2; + } + + // This should be a method within Envelop2D + public static void inflateEnv2D( + double a, + double e2, + Envelope2D env2D, + double dxMeters, + double dyMeters) { + if (env2D.isEmpty()) + return; + + double yMinAzimuth = dyMeters > 0 ? Math.PI : 0; + double yMaxAzimuth = dyMeters > 0 ? 0 : Math.PI; + + double xMinAzimuth = dxMeters > 0 ? 3 * Math.PI / 2 : Math.PI / 2; + double xMaxAzimuth = dxMeters > 0 ? Math.PI / 2 : 3 * Math.PI / 2; + + dxMeters = Math.abs(dxMeters); + dyMeters = Math.abs(dyMeters); + + PeDouble lamMin = new PeDouble(); + PeDouble phiMin = new PeDouble(); + PeDouble lamMax = new PeDouble(); + PeDouble phiMax = new PeDouble(); + PeDouble dummyVar = new PeDouble(); + + // TODO this probably needs improvement. Maybe a check to see which hemisphere and then whether to measure the + // x values at ymin or ymax and measure the y values at the xmin or xmax + // new ymin + geodesic_forward(a, e2, env2D.xmin * DEG_TO_RAD, env2D.ymin * DEG_TO_RAD, dyMeters, yMinAzimuth, dummyVar, phiMin); + // new xmin + geodesic_forward(a, e2, env2D.xmin * DEG_TO_RAD, env2D.ymax * DEG_TO_RAD, dxMeters, xMinAzimuth, lamMin, dummyVar); + // new yMAX + geodesic_forward(a, e2, env2D.xmax * DEG_TO_RAD, env2D.ymax * DEG_TO_RAD, dyMeters, yMaxAzimuth, dummyVar, phiMax); + // new xmax + geodesic_forward(a, e2, env2D.xmax * DEG_TO_RAD, env2D.ymin * DEG_TO_RAD, dxMeters, xMaxAzimuth, lamMax, dummyVar); + + env2D.ymax = phiMax.val * RAD_TO_DEG; + env2D.xmin = lamMin.val * RAD_TO_DEG; + env2D.ymin = phiMin.val * RAD_TO_DEG; + env2D.xmax = lamMax.val * RAD_TO_DEG; + + if (env2D.xmin > env2D.xmax || env2D.ymin > env2D.ymax) + env2D.setEmpty(); + } + + /** + * Gets the max geodetic width of an envelope + * + * @param a + * @param e2 + * @param env2D + * @return + */ + public static double getEnvWidth(double a, + double e2, + Envelope2D env2D) { + double rpu = Math.PI / 180.0; + PeDouble answer = new PeDouble(); + GeoDist.geodesic_distance_ngs( + a, + e2, + env2D.xmin * rpu, + env2D.ymin * rpu, + env2D.xmax * rpu, + env2D.ymin * rpu, + answer, null, null); + double lowerWidth = answer.val; + GeoDist.geodesic_distance_ngs( + a, + e2, + env2D.xmin * rpu, + env2D.ymax * rpu, + env2D.xmax * rpu, + env2D.ymax * rpu, + answer, null, null); + if (lowerWidth > answer.val) + return lowerWidth; + return answer.val; + } + + public static double getEnvHeight(double a, + double e2, + Envelope2D env2D) { + double rpu = Math.PI / 180.0; + PeDouble answer = new PeDouble(); + GeoDist.geodesic_distance_ngs( + a, + e2, + env2D.xmin * rpu, + env2D.ymin * rpu, + env2D.xmin * rpu, + env2D.ymax * rpu, + answer, null, null); + double leftHeight = answer.val; + GeoDist.geodesic_distance_ngs( + a, + e2, + env2D.xmax * rpu, + env2D.ymin * rpu, + env2D.xmax * rpu, + env2D.ymax * rpu, + answer, null, null); + if (leftHeight > answer.val) + return leftHeight; + return answer.val; + } + + public static Point2D getEnvCenter(Geometry geometry, SpatialReference spatialReference) { + Envelope2D inputEnvelope2D = new Envelope2D(); + + if (spatialReference == null) { + throw new GeometryException("Requires spatial reference"); + } + + if (spatialReference.getID() != 4326) { + OperatorProject operatorProject = (OperatorProject) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.Project); + // TODO this should be grabbing the GCS wkid from the input spatialreference instead of assuming 4326 + ProjectionTransformation projectionTransformation = new ProjectionTransformation(spatialReference, SpatialReference.create(4326)); + Geometry projectedGeom = operatorProject.execute(geometry, projectionTransformation, null); + projectedGeom.queryEnvelope2D(inputEnvelope2D); + } else { + geometry.queryEnvelope2D(inputEnvelope2D); + } + + // From GEOGRAPHIC Grab point + double a = spatialReference.getMajorAxis(); + double e2 = spatialReference.getEccentricitySquared(); + + Point2D ptCenter = new Point2D(); + GeoDist.getEnvCenter(a, e2, inputEnvelope2D, ptCenter); + + return ptCenter; + } + + public static void getEnvCenter(double a, + double e2, + Envelope2D env2D, + Point2D geodeticCenter) { + // Get minx miny, minx maxy midpoint + Point2D lowerRightUpperLeft = new Point2D(); + GeoDist.geodesicMidpoint(a, e2, env2D.getLowerRight(), env2D.getUpperLeft(), lowerRightUpperLeft); + + + // Get maxx miny, maxx maxy midpoint + Point2D upperRightLowerLeft = new Point2D(); + GeoDist.geodesicMidpoint(a, e2, env2D.getLowerLeft(), env2D.getUpperRight(), upperRightLowerLeft); + + // Get midpoint of midpoints + GeoDist.geodesicMidpoint(a, e2, lowerRightUpperLeft, upperRightLowerLeft, geodeticCenter); + } + + static public void geodesicMidpoint(double a, double e2, Point2D fromPoint, Point2D toPoint, Point2D outMidPoint) { + // TODO calculate distance + PeDouble azFromTo = new PeDouble(); + double distance = geodesicDistance(a, e2, fromPoint, toPoint, azFromTo, null); + + // TODO calculate midpoint + geodesicForward(a, e2, fromPoint, distance / 2f, azFromTo.val, outMidPoint); + } + + static public void geodesicForward(double a, + double e2, + Point2D fromPoint, + double distance, + double azimuth, + Point2D outToPoint) { + PeDouble lam2 = new PeDouble(); + PeDouble phi2 = new PeDouble(); + GeoDist.geodesic_forward( + a, + e2, + fromPoint.x * DEG_TO_RAD, + fromPoint.y * DEG_TO_RAD, + distance, + azimuth, + lam2, + phi2); + + outToPoint.x = lam2.val * RAD_TO_DEG; + outToPoint.y = phi2.val * RAD_TO_DEG; + } + + static public double geodesicDistance(double a, + double e2, + Point2D fromPoint, + Point2D toPoint, + PeDouble azFromTo, + PeDouble azToFrom) { // double a = 6378137.0; // radius of spheroid for WGS_1984 // double e2 = 0.0066943799901413165; // ellipticity for WGS_1984 - PeDouble answer = new PeDouble(); - GeoDist.geodesic_distance_ngs( - a, - e2, - fromPoint.x * DEG_TO_RAD, - fromPoint.y * DEG_TO_RAD, - toPoint.x * DEG_TO_RAD, - toPoint.y * DEG_TO_RAD, - answer, - azFromTo, - azToFrom); - return answer.val; - } - - // TODO replace geodesic_distance_ngs with this and get rid of PeDouble - static public InverseResult geodesicInverse(double a, - double e2, - Point2D fromPoint, - Point2D toPoint) { - PeDouble az12 = new PeDouble(); - PeDouble az21 = new PeDouble(); - PeDouble distance = new PeDouble(); - GeoDist.geodesic_distance_ngs( - a, - e2, - fromPoint.x * DEG_TO_RAD, - fromPoint.y * DEG_TO_RAD, - toPoint.x * DEG_TO_RAD, - toPoint.y * DEG_TO_RAD, - distance, - az12, - az21); - return new InverseResult(az12.val, az21.val, distance.val); - } - - static public void geodesic_forward(double a, - double e2, - double lam1, - double phi1, - double distance, - double az12, - PeDouble lam2, - PeDouble phi2) { - // http://www.fgg.uni-lj.si/~/mkuhar/Zalozba/Rapp_Geom_Geod_%20Vol_II.pdf - // it gets messy in here. This is the vicenty direct equations from Rapp, 1993 - // calculate A and B - // calc reduced latitude, beta1: (grabbed this from geodesic_distance_ngs) - double f = 1.0 - Math.sqrt(1.0 - e2); - double boa = 1.0 - f; - double beta1 = Math.atan(boa * Math.tan(phi1)); /* better reduced latitude */ - - //TODO might be buggy - double cosBeta1 = Math.cos(beta1); - // gets used in final results - double sinBeta1 = Math.sin(beta1); - - // Alpha1 in Rapp is az12, the azimuth of the direction traveled - double sinAlpha = cosBeta1 * Math.sin(az12); - double sinAlpha2 = sinAlpha * sinAlpha; - double cosAlpha2 = 1 - sinAlpha2; - double ePrime2 = e2 / (1 - e2); - double u2 = ePrime2 * cosAlpha2; - - double A = 1 + (u2 / 16384) * (4096 + u2 * (320 - 175 * u2)); - double B = (u2 / 1024) * (256 + u2 * (74 - 47 * u2)); - - // pre cooked for while loop - double B_4th = B / 4; - double B_6th = B / 6; - - double sigma1 = Math.atan(Math.tan(beta1) / Math.cos(az12)); - - double deltaSigma = 0; - double b = a * (1 - f); - double firstApprox = distance / (b * A); - double sigma = firstApprox; - double lastSigma = Double.MIN_VALUE; - - while (!PE_EQ(sigma, lastSigma)) { - lastSigma = sigma; - double cos2sigmaM = Math.cos(2 * sigma1 + sigma); - double cos2sigmaM2 = cos2sigmaM * cos2sigmaM; - double sinSigma2 = Math.sin(sigma) * Math.sin(sigma); - double cosSigmaGroup = Math.cos(sigma) * (-1 + 2 * cos2sigmaM2); - double cos2SigmaMGroup = cos2sigmaM * (-3 + 4 * sinSigma2) * (-3 + 4 * cos2sigmaM2); - deltaSigma = B * Math.sin(sigma) * (cos2sigmaM + B_4th * cosSigmaGroup - B_6th * cos2SigmaMGroup); - sigma = firstApprox + deltaSigma; - } - - // removing repetitive trig expressions - double cosSigma = Math.cos(sigma); - double sinSigma = Math.sin(sigma); - - double lambdaNumerator = sinSigma * Math.sin(az12); - double lambdaDenominator = cosBeta1 * cosSigma - sinBeta1 * sinSigma * Math.cos(az12); - double lambda = Math.atan2(lambdaNumerator, lambdaDenominator); - double C = (f / 16) * cosAlpha2 * (4 + f * (4 - 3 * cosAlpha2)); - - // from equation 184 in Rapp - double cos2sigmaM = Math.cos(2 * sigma1 + sigma); - double cos2sigmaM2 = cos2sigmaM * cos2sigmaM; - double squareBrackets_184 = cos2sigmaM + C * cosSigma * (-1 + 2 * cos2sigmaM2); - lam2.val = lam1 + lambda - (1 - C) * f * sinAlpha * (sigma + C * sinSigma * squareBrackets_184); - - double phiNumerator = sinBeta1 * cosSigma + cosBeta1 * sinSigma * Math.cos(az12); - double messyBlock = (sinBeta1 * sinSigma - cosBeta1 * cosSigma * Math.cos(az12)); - double messyBlock2 = messyBlock * messyBlock; - double phiDenominator = (1 - f) * Math.sqrt(sinAlpha2 + messyBlock2); - phi2.val = Math.atan2(phiNumerator, phiDenominator); - } - - static public void geodesic_distance_ngs(double a, - double e2, - double lam1, - double phi1, - double lam2, - double phi2, - PeDouble p_dist, - PeDouble p_az12, - PeDouble p_az21) { + PeDouble answer = new PeDouble(); + GeoDist.geodesic_distance_ngs( + a, + e2, + fromPoint.x * DEG_TO_RAD, + fromPoint.y * DEG_TO_RAD, + toPoint.x * DEG_TO_RAD, + toPoint.y * DEG_TO_RAD, + answer, + azFromTo, + azToFrom); + return answer.val; + } + + // TODO replace geodesic_distance_ngs with this and get rid of PeDouble + static public InverseResult geodesicInverse(double a, + double e2, + Point2D fromPoint, + Point2D toPoint) { + PeDouble az12 = new PeDouble(); + PeDouble az21 = new PeDouble(); + PeDouble distance = new PeDouble(); + GeoDist.geodesic_distance_ngs( + a, + e2, + fromPoint.x * DEG_TO_RAD, + fromPoint.y * DEG_TO_RAD, + toPoint.x * DEG_TO_RAD, + toPoint.y * DEG_TO_RAD, + distance, + az12, + az21); + return new InverseResult(az12.val, az21.val, distance.val); + } + + static public void geodesic_forward(double a, + double e2, + double lam1, + double phi1, + double distance, + double az12, + PeDouble lam2, + PeDouble phi2) { + // http://www.fgg.uni-lj.si/~/mkuhar/Zalozba/Rapp_Geom_Geod_%20Vol_II.pdf + // it gets messy in here. This is the vicenty direct equations from Rapp, 1993 + // calculate A and B + // calc reduced latitude, beta1: (grabbed this from geodesic_distance_ngs) + double f = 1.0 - Math.sqrt(1.0 - e2); + double boa = 1.0 - f; + double beta1 = Math.atan(boa * Math.tan(phi1)); /* better reduced latitude */ + + //TODO might be buggy + double cosBeta1 = Math.cos(beta1); + // gets used in final results + double sinBeta1 = Math.sin(beta1); + + // Alpha1 in Rapp is az12, the azimuth of the direction traveled + double sinAlpha = cosBeta1 * Math.sin(az12); + double sinAlpha2 = sinAlpha * sinAlpha; + double cosAlpha2 = 1 - sinAlpha2; + double ePrime2 = e2 / (1 - e2); + double u2 = ePrime2 * cosAlpha2; + + double A = 1 + (u2 / 16384) * (4096 + u2 * (320 - 175 * u2)); + double B = (u2 / 1024) * (256 + u2 * (74 - 47 * u2)); + + // pre cooked for while loop + double B_4th = B / 4; + double B_6th = B / 6; + + double sigma1 = Math.atan(Math.tan(beta1) / Math.cos(az12)); + + double deltaSigma = 0; + double b = a * (1 - f); + double firstApprox = distance / (b * A); + double sigma = firstApprox; + double lastSigma = Double.MIN_VALUE; + + while (!PE_EQ(sigma, lastSigma)) { + lastSigma = sigma; + double cos2sigmaM = Math.cos(2 * sigma1 + sigma); + double cos2sigmaM2 = cos2sigmaM * cos2sigmaM; + double sinSigma2 = Math.sin(sigma) * Math.sin(sigma); + double cosSigmaGroup = Math.cos(sigma) * (-1 + 2 * cos2sigmaM2); + double cos2SigmaMGroup = cos2sigmaM * (-3 + 4 * sinSigma2) * (-3 + 4 * cos2sigmaM2); + deltaSigma = B * Math.sin(sigma) * (cos2sigmaM + B_4th * cosSigmaGroup - B_6th * cos2SigmaMGroup); + sigma = firstApprox + deltaSigma; + } + + // removing repetitive trig expressions + double cosSigma = Math.cos(sigma); + double sinSigma = Math.sin(sigma); + + double lambdaNumerator = sinSigma * Math.sin(az12); + double lambdaDenominator = cosBeta1 * cosSigma - sinBeta1 * sinSigma * Math.cos(az12); + double lambda = Math.atan2(lambdaNumerator, lambdaDenominator); + double C = (f / 16) * cosAlpha2 * (4 + f * (4 - 3 * cosAlpha2)); + + // from equation 184 in Rapp + double cos2sigmaM = Math.cos(2 * sigma1 + sigma); + double cos2sigmaM2 = cos2sigmaM * cos2sigmaM; + double squareBrackets_184 = cos2sigmaM + C * cosSigma * (-1 + 2 * cos2sigmaM2); + lam2.val = lam1 + lambda - (1 - C) * f * sinAlpha * (sigma + C * sinSigma * squareBrackets_184); + + double phiNumerator = sinBeta1 * cosSigma + cosBeta1 * sinSigma * Math.cos(az12); + double messyBlock = (sinBeta1 * sinSigma - cosBeta1 * cosSigma * Math.cos(az12)); + double messyBlock2 = messyBlock * messyBlock; + double phiDenominator = (1 - f) * Math.sqrt(sinAlpha2 + messyBlock2); + phi2.val = Math.atan2(phiNumerator, phiDenominator); + } + + static public void geodesic_distance_ngs(double a, + double e2, + double lam1, + double phi1, + double lam2, + double phi2, + PeDouble p_dist, + PeDouble p_az12, + PeDouble p_az21) { /* Highly edited version (plus lots of additions) of NGS FORTRAN code */ /* @@ -419,354 +419,354 @@ static public void geodesic_distance_ngs(double a, * by thaddeus vincenty, 1975, 1976* removed back side solution option, * debugged, revised -- 2011may01 -- dgm* this version of code is * interim -- antipodal boundary needs work - * + * * * output (besides az12, az21, and dist):* These have been removed * from this esri version of the ngs code* it, iteration count* sigma, * spherical distance on auxiliary sphere* lam_sph, longitude difference * on auxiliary sphere* kind, solution flag: kind=1, long-line; kind=2, * antipodal - * - * + * + * * All references to Rapp are Part II */ - double tol = 1.0e-14; - double eps = 1.0e-15; + double tol = 1.0e-14; + double eps = 1.0e-15; - double boa = 0.0; - double dlam = 0.0; - double eta1 = 0.0, sin_eta1 = 0.0, cos_eta1 = 0.0; - double eta2 = 0.0, sin_eta2 = 0.0, cos_eta2 = 0.0; - double prev = 0.0, test = 0.0; - double sin_lam_sph = 0.0, cos_lam_sph = 0.0, temp = 0.0, sin_sigma = 0.0, cos_sigma = 0.0; - double sin_azeq = 0.0, cos2_azeq = 0.0, costm = 0.0, costm2 = 0.0, c = 0.0, d = 0.0; - double tem1 = 0.0, tem2 = 0.0, ep2 = 0.0, bige = 0.0, bigf = 0.0, biga = 0.0, bigb = 0.0, z = 0.0, dsigma = 0.0; - boolean q_continue_looping; + double boa = 0.0; + double dlam = 0.0; + double eta1 = 0.0, sin_eta1 = 0.0, cos_eta1 = 0.0; + double eta2 = 0.0, sin_eta2 = 0.0, cos_eta2 = 0.0; + double prev = 0.0, test = 0.0; + double sin_lam_sph = 0.0, cos_lam_sph = 0.0, temp = 0.0, sin_sigma = 0.0, cos_sigma = 0.0; + double sin_azeq = 0.0, cos2_azeq = 0.0, costm = 0.0, costm2 = 0.0, c = 0.0, d = 0.0; + double tem1 = 0.0, tem2 = 0.0, ep2 = 0.0, bige = 0.0, bigf = 0.0, biga = 0.0, bigb = 0.0, z = 0.0, dsigma = 0.0; + boolean q_continue_looping; - double f = 0.0; + double f = 0.0; - double az12 = 0.0, az21 = 0.0, dist = 0.0; - double sigma = 0.0, lam_sph = 0.0; - int it = 0, kind = 0; + double az12 = 0.0, az21 = 0.0, dist = 0.0; + double sigma = 0.0, lam_sph = 0.0; + int it = 0, kind = 0; - PeDouble lam = new PeDouble(); - PeDouble phi = new PeDouble(); + PeDouble lam = new PeDouble(); + PeDouble phi = new PeDouble(); /* Are there any values to calculate? */ - if (p_dist == null && p_az12 == null && p_az21 == null) { - return; - } + if (p_dist == null && p_az12 == null && p_az21 == null) { + return; + } /* Normalize point 1 and 2 */ - lam.val = lam1; - phi.val = phi1; - lam_phi_reduction(lam, phi); - lam1 = lam.val; - phi1 = phi.val; + lam.val = lam1; + phi.val = phi1; + lam_phi_reduction(lam, phi); + lam1 = lam.val; + phi1 = phi.val; - lam.val = lam2; - phi.val = phi2; - lam_phi_reduction(lam, phi); - lam2 = lam.val; - phi2 = phi.val; + lam.val = lam2; + phi.val = phi2; + lam_phi_reduction(lam, phi); + lam2 = lam.val; + phi2 = phi.val; - dlam = lam_delta(lam2 - lam1); /* longitude difference [-Pi, Pi] */ + dlam = lam_delta(lam2 - lam1); /* longitude difference [-Pi, Pi] */ - if (PE_EQ(phi1, phi2) && (PE_ZERO(dlam) || PE_EQ(PE_ABS(phi1), PE_PI2))) { + if (PE_EQ(phi1, phi2) && (PE_ZERO(dlam) || PE_EQ(PE_ABS(phi1), PE_PI2))) { /* Check that the points are not the same */ - if (p_dist != null) - p_dist.val = 0.0; - if (p_az12 != null) - p_az12.val = 0.0; - if (p_az21 != null) - p_az21.val = 0.0; - - return; - } else if (PE_EQ(phi1, -phi2)) { + if (p_dist != null) + p_dist.val = 0.0; + if (p_az12 != null) + p_az12.val = 0.0; + if (p_az21 != null) + p_az21.val = 0.0; + + return; + } else if (PE_EQ(phi1, -phi2)) { /* Check if they are perfectly antipodal */ - if (PE_EQ(PE_ABS(phi1), PE_PI2)) { + if (PE_EQ(PE_ABS(phi1), PE_PI2)) { /* Check if they are at opposite poles */ - if (p_dist != null) - p_dist.val = 2.0 * q90(a, e2); + if (p_dist != null) + p_dist.val = 2.0 * q90(a, e2); - if (p_az12 != null) - p_az12.val = phi1 > 0.0 ? lam_delta(PE_PI - lam_delta(lam2)) - : lam_delta(lam2); + if (p_az12 != null) + p_az12.val = phi1 > 0.0 ? lam_delta(PE_PI - lam_delta(lam2)) + : lam_delta(lam2); - if (p_az21 != null) - p_az21.val = phi1 > 0.0 ? lam_delta(lam2) : lam_delta(PE_PI - - lam_delta(lam2)); + if (p_az21 != null) + p_az21.val = phi1 > 0.0 ? lam_delta(lam2) : lam_delta(PE_PI + - lam_delta(lam2)); - return; - } else if (PE_EQ(PE_ABS(dlam), PE_PI)) { + return; + } else if (PE_EQ(PE_ABS(dlam), PE_PI)) { /* Other antipodal */ - if (p_dist != null) - p_dist.val = 2.0 * q90(a, e2); - if (p_az12 != null) - p_az12.val = 0.0; - if (p_az21 != null) - p_az21.val = 0.0; - return; - } - } - - if (PE_ZERO(e2)) /* Sphere */ { - double cos_phi1, cos_phi2; - double sin_phi1, sin_phi2; - - cos_phi1 = Math.cos(phi1); - cos_phi2 = Math.cos(phi2); - sin_phi1 = Math.sin(phi1); - sin_phi2 = Math.sin(phi2); - - if (p_dist != null) { - tem1 = Math.sin((phi2 - phi1) / 2.0); - tem2 = Math.sin(dlam / 2.0); - sigma = 2.0 * Math.asin(Math.sqrt(tem1 * tem1 + cos_phi1 - * cos_phi2 * tem2 * tem2)); - p_dist.val = sigma * a; - } - - if (p_az12 != null) { - if (PE_EQ(PE_ABS(phi1), PE_PI2)) /* Origin at N or S Pole */ { - p_az12.val = phi1 < 0.0 ? lam2 : lam_delta(PE_PI - lam2); - } else { - p_az12.val = Math.atan2(cos_phi2 * Math.sin(dlam), cos_phi1 - * sin_phi2 - sin_phi1 * cos_phi2 * Math.cos(dlam)); - } - } - - if (p_az21 != null) { - if (PE_EQ(PE_ABS(phi2), PE_PI2)) /* Destination at N or S Pole */ { - p_az21.val = phi2 < 0.0 ? lam1 : lam_delta(PE_PI - lam1); - } else { - p_az21.val = Math.atan2(cos_phi1 * Math.sin(dlam), sin_phi2 - * cos_phi1 * Math.cos(dlam) - cos_phi2 * sin_phi1); - p_az21.val = lam_delta(p_az21.val + PE_PI); - } - } - - return; - } - - f = 1.0 - Math.sqrt(1.0 - e2); - boa = 1.0 - f; - - eta1 = Math.atan(boa * Math.tan(phi1)); /* better reduced latitude */ - sin_eta1 = Math.sin(eta1); - cos_eta1 = Math.cos(eta1); - - eta2 = Math.atan(boa * Math.tan(phi2)); /* better reduced latitude */ - sin_eta2 = Math.sin(eta2); - cos_eta2 = Math.cos(eta2); - - prev = dlam; - test = dlam; - it = 0; - kind = 1; - lam_sph = dlam; /* v13 (Rapp ) */ + if (p_dist != null) + p_dist.val = 2.0 * q90(a, e2); + if (p_az12 != null) + p_az12.val = 0.0; + if (p_az21 != null) + p_az21.val = 0.0; + return; + } + } + + if (PE_ZERO(e2)) /* Sphere */ { + double cos_phi1, cos_phi2; + double sin_phi1, sin_phi2; + + cos_phi1 = Math.cos(phi1); + cos_phi2 = Math.cos(phi2); + sin_phi1 = Math.sin(phi1); + sin_phi2 = Math.sin(phi2); + + if (p_dist != null) { + tem1 = Math.sin((phi2 - phi1) / 2.0); + tem2 = Math.sin(dlam / 2.0); + sigma = 2.0 * Math.asin(Math.sqrt(tem1 * tem1 + cos_phi1 + * cos_phi2 * tem2 * tem2)); + p_dist.val = sigma * a; + } + + if (p_az12 != null) { + if (PE_EQ(PE_ABS(phi1), PE_PI2)) /* Origin at N or S Pole */ { + p_az12.val = phi1 < 0.0 ? lam2 : lam_delta(PE_PI - lam2); + } else { + p_az12.val = Math.atan2(cos_phi2 * Math.sin(dlam), cos_phi1 + * sin_phi2 - sin_phi1 * cos_phi2 * Math.cos(dlam)); + } + } + + if (p_az21 != null) { + if (PE_EQ(PE_ABS(phi2), PE_PI2)) /* Destination at N or S Pole */ { + p_az21.val = phi2 < 0.0 ? lam1 : lam_delta(PE_PI - lam1); + } else { + p_az21.val = Math.atan2(cos_phi1 * Math.sin(dlam), sin_phi2 + * cos_phi1 * Math.cos(dlam) - cos_phi2 * sin_phi1); + p_az21.val = lam_delta(p_az21.val + PE_PI); + } + } + + return; + } + + f = 1.0 - Math.sqrt(1.0 - e2); + boa = 1.0 - f; + + eta1 = Math.atan(boa * Math.tan(phi1)); /* better reduced latitude */ + sin_eta1 = Math.sin(eta1); + cos_eta1 = Math.cos(eta1); + + eta2 = Math.atan(boa * Math.tan(phi2)); /* better reduced latitude */ + sin_eta2 = Math.sin(eta2); + cos_eta2 = Math.cos(eta2); + + prev = dlam; + test = dlam; + it = 0; + kind = 1; + lam_sph = dlam; /* v13 (Rapp ) */ /* top of the long-line loop (kind = 1) */ - q_continue_looping = true; - while (q_continue_looping && it < 100) { - it = it + 1; + q_continue_looping = true; + while (q_continue_looping && it < 100) { + it = it + 1; - if (kind == 1) { - sin_lam_sph = Math.sin(lam_sph); + if (kind == 1) { + sin_lam_sph = Math.sin(lam_sph); /* * if ( PE_ABS(PE_PI - PE_ABS(dlam)) < 2.0e-11 ) sin_lam_sph = * 0.0 no--troublesome */ - cos_lam_sph = Math.cos(lam_sph); - tem1 = cos_eta2 * sin_lam_sph; - temp = cos_eta1 * sin_eta2 - sin_eta1 * cos_eta2 * cos_lam_sph; - sin_sigma = Math.sqrt(tem1 * tem1 + temp * temp); /* - * v14 (Rapp - * 1.87) - */ - cos_sigma = sin_eta1 * sin_eta2 + cos_eta1 * cos_eta2 - * cos_lam_sph; /* v15 (Rapp 1.88) */ - sigma = Math.atan2(sin_sigma, cos_sigma); /* (Rapp 1.89) */ - - if (PE_ABS(sin_sigma) < eps) /* avoid division by 0 */ { - sin_azeq = cos_eta1 * cos_eta2 * sin_lam_sph - / PE_SGN(eps, sin_sigma); - } else { - sin_azeq = cos_eta1 * cos_eta2 * sin_lam_sph / sin_sigma; + cos_lam_sph = Math.cos(lam_sph); + tem1 = cos_eta2 * sin_lam_sph; + temp = cos_eta1 * sin_eta2 - sin_eta1 * cos_eta2 * cos_lam_sph; + sin_sigma = Math.sqrt(tem1 * tem1 + temp * temp); /* + * v14 (Rapp + * 1.87) + */ + cos_sigma = sin_eta1 * sin_eta2 + cos_eta1 * cos_eta2 + * cos_lam_sph; /* v15 (Rapp 1.88) */ + sigma = Math.atan2(sin_sigma, cos_sigma); /* (Rapp 1.89) */ + + if (PE_ABS(sin_sigma) < eps) /* avoid division by 0 */ { + sin_azeq = cos_eta1 * cos_eta2 * sin_lam_sph + / PE_SGN(eps, sin_sigma); + } else { + sin_azeq = cos_eta1 * cos_eta2 * sin_lam_sph / sin_sigma; /* v17 (Rapp 1.90) */ - } + } - cos2_azeq = 1.0 - sin_azeq * sin_azeq; + cos2_azeq = 1.0 - sin_azeq * sin_azeq; - if (PE_ABS(cos2_azeq) < eps) /* avoid division by 0 */ { - costm = cos_sigma - 2.0 - * (sin_eta1 * sin_eta2 / PE_SGN(eps, cos2_azeq)); - } else { - costm = cos_sigma - 2.0 * (sin_eta1 * sin_eta2 / cos2_azeq); + if (PE_ABS(cos2_azeq) < eps) /* avoid division by 0 */ { + costm = cos_sigma - 2.0 + * (sin_eta1 * sin_eta2 / PE_SGN(eps, cos2_azeq)); + } else { + costm = cos_sigma - 2.0 * (sin_eta1 * sin_eta2 / cos2_azeq); /* v18 (Rapp 1.91) */ - } - costm2 = costm * costm; - c = ((-3.0 * cos2_azeq + 4.0) * f + 4.0) * cos2_azeq * f / 16.0; /* - * v10 - * ( - * Rapp - * 1.83 - * ) - */ - } + } + costm2 = costm * costm; + c = ((-3.0 * cos2_azeq + 4.0) * f + 4.0) * cos2_azeq * f / 16.0; /* + * v10 + * ( + * Rapp + * 1.83 + * ) + */ + } /* entry point of the antipodal loop (kind = 2) */ - d = (1.0 - c) - * f - * (sigma + c * sin_sigma - * (costm + cos_sigma * c * (2.0 * costm2 - 1.0))); + d = (1.0 - c) + * f + * (sigma + c * sin_sigma + * (costm + cos_sigma * c * (2.0 * costm2 - 1.0))); /* v11 (Rapp 1.84) */ - if (kind == 1) { - lam_sph = dlam + d * sin_azeq; - if (PE_ABS(lam_sph - test) < tol) { - q_continue_looping = false; - continue; - } - - if (PE_ABS(lam_sph) > PE_PI) { - kind = 2; - lam_sph = PE_PI; - if (dlam < 0.0) { - lam_sph = -lam_sph; - } - sin_azeq = 0.0; - cos2_azeq = 1.0; - test = 2.0; - prev = test; - - sigma = PE_PI - - PE_ABS(Math.atan(sin_eta1 / cos_eta1) - + Math.atan(sin_eta2 / cos_eta2)); - sin_sigma = Math.sin(sigma); - cos_sigma = Math.cos(sigma); - c = ((-3.0 * cos2_azeq + 4.0) * f + 4.0) * cos2_azeq * f - / 16.0; /* v10 (Rapp 1.83) */ - - if (PE_ABS(sin_azeq - prev) < tol) { - q_continue_looping = false; - continue; - } - if (PE_ABS(cos2_azeq) < eps) /* avoid division by 0 */ { - costm = cos_sigma - - 2.0 - * (sin_eta1 * sin_eta2 / PE_SGN(eps, cos2_azeq)); - } else { - costm = cos_sigma - 2.0 - * (sin_eta1 * sin_eta2 / cos2_azeq); + if (kind == 1) { + lam_sph = dlam + d * sin_azeq; + if (PE_ABS(lam_sph - test) < tol) { + q_continue_looping = false; + continue; + } + + if (PE_ABS(lam_sph) > PE_PI) { + kind = 2; + lam_sph = PE_PI; + if (dlam < 0.0) { + lam_sph = -lam_sph; + } + sin_azeq = 0.0; + cos2_azeq = 1.0; + test = 2.0; + prev = test; + + sigma = PE_PI + - PE_ABS(Math.atan(sin_eta1 / cos_eta1) + + Math.atan(sin_eta2 / cos_eta2)); + sin_sigma = Math.sin(sigma); + cos_sigma = Math.cos(sigma); + c = ((-3.0 * cos2_azeq + 4.0) * f + 4.0) * cos2_azeq * f + / 16.0; /* v10 (Rapp 1.83) */ + + if (PE_ABS(sin_azeq - prev) < tol) { + q_continue_looping = false; + continue; + } + if (PE_ABS(cos2_azeq) < eps) /* avoid division by 0 */ { + costm = cos_sigma + - 2.0 + * (sin_eta1 * sin_eta2 / PE_SGN(eps, cos2_azeq)); + } else { + costm = cos_sigma - 2.0 + * (sin_eta1 * sin_eta2 / cos2_azeq); /* v18 (Rapp 1.91) */ - } - costm2 = costm * costm; - continue; - } + } + costm2 = costm * costm; + continue; + } - if (((lam_sph - test) * (test - prev)) < 0.0 && it > 5) { + if (((lam_sph - test) * (test - prev)) < 0.0 && it > 5) { /* refined converge */ - lam_sph = (2.0 * lam_sph + 3.0 * test + prev) / 6.0; - } - prev = test; - test = lam_sph; - continue; - } else /* kind == 2 */ { - sin_azeq = (lam_sph - dlam) / d; - if (((sin_azeq - test) * (test - prev)) < 0.0 && it > 5) { + lam_sph = (2.0 * lam_sph + 3.0 * test + prev) / 6.0; + } + prev = test; + test = lam_sph; + continue; + } else /* kind == 2 */ { + sin_azeq = (lam_sph - dlam) / d; + if (((sin_azeq - test) * (test - prev)) < 0.0 && it > 5) { /* refined converge */ - sin_azeq = (2.0 * sin_azeq + 3.0 * test + prev) / 6.0; - } - prev = test; - test = sin_azeq; - cos2_azeq = 1.0 - sin_azeq * sin_azeq; - sin_lam_sph = sin_azeq * sin_sigma / (cos_eta1 * cos_eta2); - cos_lam_sph = -Math - .sqrt(PE_ABS(1.0 - sin_lam_sph * sin_lam_sph)); - lam_sph = Math.atan2(sin_lam_sph, cos_lam_sph); - tem1 = cos_eta2 * sin_lam_sph; - temp = cos_eta1 * sin_eta2 - sin_eta1 * cos_eta2 * cos_lam_sph; - sin_sigma = Math.sqrt(tem1 * tem1 + temp * temp); - cos_sigma = sin_eta1 * sin_eta2 + cos_eta1 * cos_eta2 - * cos_lam_sph; - sigma = Math.atan2(sin_sigma, cos_sigma); - c = ((-3.0 * cos2_azeq + 4.0) * f + 4.0) * cos2_azeq * f / 16.0; /* - * v10 - * ( - * Rapp - * 1.83 - * ) - */ - if (PE_ABS(sin_azeq - prev) < tol) { - q_continue_looping = false; - continue; - } - if (PE_ABS(cos2_azeq) < eps) /* avoid division by 0 */ { - costm = cos_sigma - 2.0 - * (sin_eta1 * sin_eta2 / PE_SGN(eps, cos2_azeq)); - } else { - costm = cos_sigma - 2.0 * (sin_eta1 * sin_eta2 / cos2_azeq); + sin_azeq = (2.0 * sin_azeq + 3.0 * test + prev) / 6.0; + } + prev = test; + test = sin_azeq; + cos2_azeq = 1.0 - sin_azeq * sin_azeq; + sin_lam_sph = sin_azeq * sin_sigma / (cos_eta1 * cos_eta2); + cos_lam_sph = -Math + .sqrt(PE_ABS(1.0 - sin_lam_sph * sin_lam_sph)); + lam_sph = Math.atan2(sin_lam_sph, cos_lam_sph); + tem1 = cos_eta2 * sin_lam_sph; + temp = cos_eta1 * sin_eta2 - sin_eta1 * cos_eta2 * cos_lam_sph; + sin_sigma = Math.sqrt(tem1 * tem1 + temp * temp); + cos_sigma = sin_eta1 * sin_eta2 + cos_eta1 * cos_eta2 + * cos_lam_sph; + sigma = Math.atan2(sin_sigma, cos_sigma); + c = ((-3.0 * cos2_azeq + 4.0) * f + 4.0) * cos2_azeq * f / 16.0; /* + * v10 + * ( + * Rapp + * 1.83 + * ) + */ + if (PE_ABS(sin_azeq - prev) < tol) { + q_continue_looping = false; + continue; + } + if (PE_ABS(cos2_azeq) < eps) /* avoid division by 0 */ { + costm = cos_sigma - 2.0 + * (sin_eta1 * sin_eta2 / PE_SGN(eps, cos2_azeq)); + } else { + costm = cos_sigma - 2.0 * (sin_eta1 * sin_eta2 / cos2_azeq); /* v18 (Rapp 1.91) */ - } - costm2 = costm * costm; - continue; - } - } /* End of while q_continue_looping */ + } + costm2 = costm * costm; + continue; + } + } /* End of while q_continue_looping */ /* Convergence */ - if (p_dist != null) { + if (p_dist != null) { /* * Helmert 1880 from Vincenty's * "Geodetic inverse solution between antipodal points" */ - ep2 = 1.0 / (boa * boa) - 1.0; - bige = Math.sqrt(1.0 + ep2 * cos2_azeq); /* 15 */ - bigf = (bige - 1.0) / (bige + 1.0); /* 16 */ - biga = (1.0 + bigf * bigf / 4.0) / (1.0 - bigf); /* 17 */ - bigb = bigf * (1.0 - 0.375 * bigf * bigf); /* 18 */ - z = bigb / 6.0 * costm * (-3.0 + 4.0 * sin_sigma * sin_sigma) - * (-3.0 + 4.0 * costm2); - dsigma = bigb - * sin_sigma - * (costm + bigb / 4.0 - * (cos_sigma * (-1.0 + 2.0 * costm2) - z)); /* 19 */ - dist = (boa * a) * biga * (sigma - dsigma); /* 20 */ - - p_dist.val = dist; - } - - if (p_az12 != null || p_az21 != null) { - if (kind == 2) /* antipodal */ { - az12 = sin_azeq / cos_eta1; - az21 = Math.sqrt(1.0 - az12 * az12); - if (temp < 0.0) { - az21 = -az21; - } - az12 = Math.atan2(az12, az21); - tem1 = -sin_azeq; - tem2 = sin_eta1 * sin_sigma - cos_eta1 * cos_sigma * az21; - az21 = Math.atan2(tem1, tem2); - } else /* long-line */ { - tem1 = cos_eta2 * sin_lam_sph; - tem2 = cos_eta1 * sin_eta2 - sin_eta1 * cos_eta2 * cos_lam_sph; - az12 = Math.atan2(tem1, tem2); - tem1 = -cos_eta1 * sin_lam_sph; - tem2 = sin_eta1 * cos_eta2 - cos_eta1 * sin_eta2 * cos_lam_sph; - az21 = Math.atan2(tem1, tem2); - } - - if (p_az12 != null) { - p_az12.val = lam_delta(az12); - } - if (p_az21 != null) { - p_az21.val = lam_delta(az21); - } - } - } + ep2 = 1.0 / (boa * boa) - 1.0; + bige = Math.sqrt(1.0 + ep2 * cos2_azeq); /* 15 */ + bigf = (bige - 1.0) / (bige + 1.0); /* 16 */ + biga = (1.0 + bigf * bigf / 4.0) / (1.0 - bigf); /* 17 */ + bigb = bigf * (1.0 - 0.375 * bigf * bigf); /* 18 */ + z = bigb / 6.0 * costm * (-3.0 + 4.0 * sin_sigma * sin_sigma) + * (-3.0 + 4.0 * costm2); + dsigma = bigb + * sin_sigma + * (costm + bigb / 4.0 + * (cos_sigma * (-1.0 + 2.0 * costm2) - z)); /* 19 */ + dist = (boa * a) * biga * (sigma - dsigma); /* 20 */ + + p_dist.val = dist; + } + + if (p_az12 != null || p_az21 != null) { + if (kind == 2) /* antipodal */ { + az12 = sin_azeq / cos_eta1; + az21 = Math.sqrt(1.0 - az12 * az12); + if (temp < 0.0) { + az21 = -az21; + } + az12 = Math.atan2(az12, az21); + tem1 = -sin_azeq; + tem2 = sin_eta1 * sin_sigma - cos_eta1 * cos_sigma * az21; + az21 = Math.atan2(tem1, tem2); + } else /* long-line */ { + tem1 = cos_eta2 * sin_lam_sph; + tem2 = cos_eta1 * sin_eta2 - sin_eta1 * cos_eta2 * cos_lam_sph; + az12 = Math.atan2(tem1, tem2); + tem1 = -cos_eta1 * sin_lam_sph; + tem2 = sin_eta1 * cos_eta2 - cos_eta1 * sin_eta2 * cos_lam_sph; + az21 = Math.atan2(tem1, tem2); + } + + if (p_az12 != null) { + p_az12.val = lam_delta(az12); + } + if (p_az21 != null) { + p_az21.val = lam_delta(az21); + } + } + } } diff --git a/src/main/java/com/esri/core/geometry/GeoJsonCrsTables.java b/src/main/java/com/esri/core/geometry/GeoJsonCrsTables.java index ee5fb963..b1633e2e 100644 --- a/src/main/java/com/esri/core/geometry/GeoJsonCrsTables.java +++ b/src/main/java/com/esri/core/geometry/GeoJsonCrsTables.java @@ -26,158 +26,158 @@ import java.util.Arrays; class GeoJsonCrsTables { - static int getWkidFromCrsShortForm(String crs_identifier) { - int last_colon = crs_identifier.lastIndexOf((int) ':'); // skip version + static int getWkidFromCrsShortForm(String crs_identifier) { + int last_colon = crs_identifier.lastIndexOf((int) ':'); // skip version - if (last_colon == -1) - return -1; + if (last_colon == -1) + return -1; - int code_start = last_colon + 1; - int wkid = getWkidFromCrsCode_(crs_identifier, code_start); - return wkid; - } + int code_start = last_colon + 1; + int wkid = getWkidFromCrsCode_(crs_identifier, code_start); + return wkid; + } - static int getWkidFromCrsName(String crs_identifier) { - int wkid = -1; + static int getWkidFromCrsName(String crs_identifier) { + int wkid = -1; - int last_colon = crs_identifier.lastIndexOf((int) ':'); // skip - // authority, - // version, and - // other things. - // Just try to - // get a wkid. - // This works - // for - // short/long - // form. + int last_colon = crs_identifier.lastIndexOf((int) ':'); // skip + // authority, + // version, and + // other things. + // Just try to + // get a wkid. + // This works + // for + // short/long + // form. - if (last_colon == -1) - return -1; + if (last_colon == -1) + return -1; - int code_start = last_colon + 1; - wkid = getWkidFromCrsCode_(crs_identifier, code_start); + int code_start = last_colon + 1; + wkid = getWkidFromCrsCode_(crs_identifier, code_start); - if (wkid != -1) - return wkid; + if (wkid != -1) + return wkid; - wkid = getWkidFromCrsOgcUrn(crs_identifier); // could be an OGC - // "preferred" urn - return wkid; - } + wkid = getWkidFromCrsOgcUrn(crs_identifier); // could be an OGC + // "preferred" urn + return wkid; + } - static int getWkidFromCrsOgcUrn(String crs_identifier) { - int wkid = -1; - if (crs_identifier.regionMatches(0, "urn:ogc:def:crs:OGC", 0, 19)) - wkid = getWkidFromCrsOgcUrn_(crs_identifier); + static int getWkidFromCrsOgcUrn(String crs_identifier) { + int wkid = -1; + if (crs_identifier.regionMatches(0, "urn:ogc:def:crs:OGC", 0, 19)) + wkid = getWkidFromCrsOgcUrn_(crs_identifier); - return wkid; - } + return wkid; + } - private static int getWkidFromCrsCode_(String crs_identifier, int code_start) { - assert (code_start > 0); + private static int getWkidFromCrsCode_(String crs_identifier, int code_start) { + assert (code_start > 0); - int wkid = -1; - int code_count = crs_identifier.length() - code_start; + int wkid = -1; + int code_count = crs_identifier.length() - code_start; - try { - wkid = Integer.parseInt(crs_identifier.substring(code_start, code_start + code_count)); - } catch (Exception e) { - } + try { + wkid = Integer.parseInt(crs_identifier.substring(code_start, code_start + code_count)); + } catch (Exception e) { + } - return (int) wkid; - } + return (int) wkid; + } - private static int getWkidFromCrsOgcUrn_(String crs_identifier) { - assert (crs_identifier.regionMatches(0, "urn:ogc:def:crs:OGC", 0, 19)); + private static int getWkidFromCrsOgcUrn_(String crs_identifier) { + assert (crs_identifier.regionMatches(0, "urn:ogc:def:crs:OGC", 0, 19)); - int last_colon = crs_identifier.lastIndexOf((int) ':'); // skip version + int last_colon = crs_identifier.lastIndexOf((int) ':'); // skip version - if (last_colon == -1) - return -1; + if (last_colon == -1) + return -1; - int ogc_code_start = last_colon + 1; - int ogc_code_count = crs_identifier.length() - ogc_code_start; + int ogc_code_start = last_colon + 1; + int ogc_code_count = crs_identifier.length() - ogc_code_start; - if (crs_identifier.regionMatches(ogc_code_start, "CRS84", 0, ogc_code_count)) - return 4326; + if (crs_identifier.regionMatches(ogc_code_start, "CRS84", 0, ogc_code_count)) + return 4326; - if (crs_identifier.regionMatches(ogc_code_start, "CRS83", 0, ogc_code_count)) - return 4269; + if (crs_identifier.regionMatches(ogc_code_start, "CRS83", 0, ogc_code_count)) + return 4269; - if (crs_identifier.regionMatches(ogc_code_start, "CRS27", 0, ogc_code_count)) - return 4267; + if (crs_identifier.regionMatches(ogc_code_start, "CRS27", 0, ogc_code_count)) + return 4267; - return -1; - } + return -1; + } - static int getWkidFromCrsHref(String crs_identifier) { - int sr_org_code_start = -1; + static int getWkidFromCrsHref(String crs_identifier) { + int sr_org_code_start = -1; - if (crs_identifier.regionMatches(0, "http://spatialreference.org/ref/epsg/", 0, 37)) - sr_org_code_start = 37; - else if (crs_identifier.regionMatches(0, "www.spatialreference.org/ref/epsg/", 0, 34)) - sr_org_code_start = 34; - else if (crs_identifier.regionMatches(0, "http://www.spatialreference.org/ref/epsg/", 0, 41)) - sr_org_code_start = 41; + if (crs_identifier.regionMatches(0, "http://spatialreference.org/ref/epsg/", 0, 37)) + sr_org_code_start = 37; + else if (crs_identifier.regionMatches(0, "www.spatialreference.org/ref/epsg/", 0, 34)) + sr_org_code_start = 34; + else if (crs_identifier.regionMatches(0, "http://www.spatialreference.org/ref/epsg/", 0, 41)) + sr_org_code_start = 41; - if (sr_org_code_start != -1) { - int sr_org_code_end = crs_identifier.indexOf('/', sr_org_code_start); + if (sr_org_code_start != -1) { + int sr_org_code_end = crs_identifier.indexOf('/', sr_org_code_start); - if (sr_org_code_end == -1) - return -1; + if (sr_org_code_end == -1) + return -1; - int count = sr_org_code_end - sr_org_code_start; - int wkid = -1; + int count = sr_org_code_end - sr_org_code_start; + int wkid = -1; - try { - wkid = Integer.parseInt(crs_identifier.substring(sr_org_code_start, sr_org_code_start + count)); - } catch (Exception e) { - } + try { + wkid = Integer.parseInt(crs_identifier.substring(sr_org_code_start, sr_org_code_start + count)); + } catch (Exception e) { + } - return wkid; - } + return wkid; + } - int open_gis_epsg_slash_end = -1; + int open_gis_epsg_slash_end = -1; - if (crs_identifier.regionMatches(0, "http://opengis.net/def/crs/EPSG/", 0, 32)) - open_gis_epsg_slash_end = 32; - else if (crs_identifier.regionMatches(0, "www.opengis.net/def/crs/EPSG/", 0, 29)) - open_gis_epsg_slash_end = 29; - else if (crs_identifier.regionMatches(0, "http://www.opengis.net/def/crs/EPSG/", 0, 36)) - open_gis_epsg_slash_end = 36; + if (crs_identifier.regionMatches(0, "http://opengis.net/def/crs/EPSG/", 0, 32)) + open_gis_epsg_slash_end = 32; + else if (crs_identifier.regionMatches(0, "www.opengis.net/def/crs/EPSG/", 0, 29)) + open_gis_epsg_slash_end = 29; + else if (crs_identifier.regionMatches(0, "http://www.opengis.net/def/crs/EPSG/", 0, 36)) + open_gis_epsg_slash_end = 36; - if (open_gis_epsg_slash_end != -1) { - int last_slash = crs_identifier.lastIndexOf('/'); // skip over the - // "0/" + if (open_gis_epsg_slash_end != -1) { + int last_slash = crs_identifier.lastIndexOf('/'); // skip over the + // "0/" - if (last_slash == -1) - return -1; + if (last_slash == -1) + return -1; - int open_gis_code_start = last_slash + 1; + int open_gis_code_start = last_slash + 1; - int count = crs_identifier.length() - open_gis_code_start; - int wkid = -1; + int count = crs_identifier.length() - open_gis_code_start; + int wkid = -1; - try { - wkid = Integer.parseInt(crs_identifier.substring(open_gis_code_start, open_gis_code_start + count)); - } catch (Exception e) { - } + try { + wkid = Integer.parseInt(crs_identifier.substring(open_gis_code_start, open_gis_code_start + count)); + } catch (Exception e) { + } - return wkid; - } + return wkid; + } - if (crs_identifier.compareToIgnoreCase("http://spatialreference.org/ref/sr-org/6928/ogcwkt/") == 0) - return 3857; + if (crs_identifier.compareToIgnoreCase("http://spatialreference.org/ref/sr-org/6928/ogcwkt/") == 0) + return 3857; - return -1; - } + return -1; + } - static String getWktFromCrsName(String crs_identifier) { - int last_colon = crs_identifier.lastIndexOf((int) ':'); // skip - // authority - int wkt_start = last_colon + 1; - int wkt_count = crs_identifier.length() - wkt_start; - String wkt = crs_identifier.substring(wkt_start, wkt_start + wkt_count); - return wkt; - } + static String getWktFromCrsName(String crs_identifier) { + int last_colon = crs_identifier.lastIndexOf((int) ':'); // skip + // authority + int wkt_start = last_colon + 1; + int wkt_count = crs_identifier.length() - wkt_start; + String wkt = crs_identifier.substring(wkt_start, wkt_start + wkt_count); + return wkt; + } } diff --git a/src/main/java/com/esri/core/geometry/GeoJsonExportFlags.java b/src/main/java/com/esri/core/geometry/GeoJsonExportFlags.java index 7866269e..fec55a03 100644 --- a/src/main/java/com/esri/core/geometry/GeoJsonExportFlags.java +++ b/src/main/java/com/esri/core/geometry/GeoJsonExportFlags.java @@ -24,31 +24,31 @@ package com.esri.core.geometry; public interface GeoJsonExportFlags { - public static final int geoJsonExportDefaults = 0; - /** - * Export MultiXXX geometries every time, by default it will export the minimum required type. - */ - public static final int geoJsonExportPreferMultiGeometry = 1; - public static final int geoJsonExportStripZs = 2; - public static final int geoJsonExportStripMs = 4; - public static final int geoJsonExportSkipCRS = 8; - public static final int geoJsonExportFailIfNotSimple = 16; - public static final int geoJsonExportPrecision16 = 0x02000; - public static final int geoJsonExportPrecision15 = 0x04000; - public static final int geoJsonExportPrecision14 = 0x06000; - public static final int geoJsonExportPrecision13 = 0x08000; - public static final int geoJsonExportPrecision12 = 0x0a000; - public static final int geoJsonExportPrecision11 = 0x0c000; - public static final int geoJsonExportPrecision10 = 0x0e000; - public static final int geoJsonExportPrecision9 = 0x10000; - public static final int geoJsonExportPrecision8 = 0x12000; - public static final int geoJsonExportPrecision7 = 0x14000; - public static final int geoJsonExportPrecision6 = 0x16000; - public static final int geoJsonExportPrecision5 = 0x18000; - public static final int geoJsonExportPrecision4 = 0x1a000; - public static final int geoJsonExportPrecision3 = 0x1c000; - public static final int geoJsonExportPrecision2 = 0x1e000; - public static final int geoJsonExportPrecision1 = 0x20000; - public static final int geoJsonExportPrecision0 = 0x22000; - public static final int geoJsonExportPrecisionFixedPoint = 0x40000; + public static final int geoJsonExportDefaults = 0; + /** + * Export MultiXXX geometries every time, by default it will export the minimum required type. + */ + public static final int geoJsonExportPreferMultiGeometry = 1; + public static final int geoJsonExportStripZs = 2; + public static final int geoJsonExportStripMs = 4; + public static final int geoJsonExportSkipCRS = 8; + public static final int geoJsonExportFailIfNotSimple = 16; + public static final int geoJsonExportPrecision16 = 0x02000; + public static final int geoJsonExportPrecision15 = 0x04000; + public static final int geoJsonExportPrecision14 = 0x06000; + public static final int geoJsonExportPrecision13 = 0x08000; + public static final int geoJsonExportPrecision12 = 0x0a000; + public static final int geoJsonExportPrecision11 = 0x0c000; + public static final int geoJsonExportPrecision10 = 0x0e000; + public static final int geoJsonExportPrecision9 = 0x10000; + public static final int geoJsonExportPrecision8 = 0x12000; + public static final int geoJsonExportPrecision7 = 0x14000; + public static final int geoJsonExportPrecision6 = 0x16000; + public static final int geoJsonExportPrecision5 = 0x18000; + public static final int geoJsonExportPrecision4 = 0x1a000; + public static final int geoJsonExportPrecision3 = 0x1c000; + public static final int geoJsonExportPrecision2 = 0x1e000; + public static final int geoJsonExportPrecision1 = 0x20000; + public static final int geoJsonExportPrecision0 = 0x22000; + public static final int geoJsonExportPrecisionFixedPoint = 0x40000; } diff --git a/src/main/java/com/esri/core/geometry/GeoJsonImportFlags.java b/src/main/java/com/esri/core/geometry/GeoJsonImportFlags.java index f59466f8..351b9045 100644 --- a/src/main/java/com/esri/core/geometry/GeoJsonImportFlags.java +++ b/src/main/java/com/esri/core/geometry/GeoJsonImportFlags.java @@ -24,16 +24,16 @@ package com.esri.core.geometry; public interface GeoJsonImportFlags { - public static final int geoJsonImportDefaults = 0; - @Deprecated - static final int geoJsonImportNonTrusted = 2; - /** - * If set, the import will skip CRS. - */ - public static final int geoJsonImportSkipCRS = 8; - /** - * If set, and the geojson does not have a spatial reference, the result geometry will not have one too, otherwise - * it'll assume WGS84. - */ - public static final int geoJsonImportNoWGS84Default = 16; + public static final int geoJsonImportDefaults = 0; + @Deprecated + static final int geoJsonImportNonTrusted = 2; + /** + * If set, the import will skip CRS. + */ + public static final int geoJsonImportSkipCRS = 8; + /** + * If set, and the geojson does not have a spatial reference, the result geometry will not have one too, otherwise + * it'll assume WGS84. + */ + public static final int geoJsonImportNoWGS84Default = 16; } diff --git a/src/main/java/com/esri/core/geometry/GeodesicBufferer.java b/src/main/java/com/esri/core/geometry/GeodesicBufferer.java index 41f22177..7132990f 100644 --- a/src/main/java/com/esri/core/geometry/GeodesicBufferer.java +++ b/src/main/java/com/esri/core/geometry/GeodesicBufferer.java @@ -6,289 +6,293 @@ * Created by davidraleigh on 2/20/16. */ class GeodesicBufferer { - /** - * Result is always a polygon. For non positive distance and non-areas - * returns an empty polygon. For points returns circles. - */ - static Geometry buffer(Geometry geometry, - double distance, - SpatialReference sr, - double densify_dist, - int max_vertex_in_complete_circle, - ProgressTracker progress_tracker) { - if (geometry == null) - throw new IllegalArgumentException(); - - if (densify_dist < 0) - throw new IllegalArgumentException(); - - if (geometry.isEmpty()) - return new Polygon(geometry.getDescription()); - - GeodesicBufferer geodesicBufferer = new GeodesicBufferer(progress_tracker); - - Envelope2D env2D = new Envelope2D(); - geometry.queryLooseEnvelope2D(env2D); - - geodesicBufferer.m_spatialReference = sr; - - if (distance > 0){ - // env2D.inflate(distance, distance); - GeoDist.inflateEnv2D( - geodesicBufferer.m_spatialReference.getMajorAxis(), - geodesicBufferer.m_spatialReference.getEccentricitySquared(), - env2D, - distance, - distance); - } - - geodesicBufferer.m_geometry = geometry; - geodesicBufferer.m_tolerance = InternalUtils.calculateToleranceFromGeometry(sr, env2D, true);// conservative to have same effect as simplify - geodesicBufferer.m_small_tolerance = InternalUtils.calculateToleranceFromGeometry(null, env2D, true);// conservative - // to have same effect as simplify - geodesicBufferer.m_distance = distance; - geodesicBufferer.m_original_geom_type = geometry.getType().value(); - if (max_vertex_in_complete_circle <= 0) { - max_vertex_in_complete_circle = 96;// 96 is the value used by SG. - // This is the number of - // vertices in the full circle. - } - - geodesicBufferer.m_abs_distance = Math.abs(geodesicBufferer.m_distance); - geodesicBufferer.m_abs_distance_reversed = geodesicBufferer.m_abs_distance != 0 ? 1.0 / geodesicBufferer.m_abs_distance : 0; - - if (NumberUtils.isNaN(densify_dist) || densify_dist == 0) { - densify_dist = geodesicBufferer.m_abs_distance * 1e-5; - } else if (densify_dist > geodesicBufferer.m_abs_distance * 0.5) { - //TODO put a break point here to see if this is ever a problem - // do not allow too large densify distance (the value will be adjusted anyway later) - densify_dist = geodesicBufferer.m_abs_distance * 0.5; - } - - if (max_vertex_in_complete_circle < 12) - max_vertex_in_complete_circle = 12; - - // TODO I don't know what max_dd is for. decimal degrees? - double max_dd = Math.abs(distance) * (1 - Math.cos(Math.PI / max_vertex_in_complete_circle)); - - if (max_dd > densify_dist) { - densify_dist = max_dd;// the densify distance has to agree with the - // max_vertex_in_complete_circle - } else { - double vertex_count = Math.PI / Math.acos(1.0 - densify_dist / Math.abs(distance)); - if (vertex_count < (double) max_vertex_in_complete_circle - 1.0) { - max_vertex_in_complete_circle = (int) vertex_count; - if (max_vertex_in_complete_circle < 12) { - max_vertex_in_complete_circle = 12; - densify_dist = Math.abs(distance) * (1 - Math.cos(Math.PI / max_vertex_in_complete_circle)); - } - } - } - - geodesicBufferer.m_densify_dist = densify_dist; - geodesicBufferer.m_max_vertex_in_complete_circle = max_vertex_in_complete_circle; - // when filtering close points we do not want the filter to distort - // generated buffer too much. - geodesicBufferer.m_filter_tolerance = Math.min(geodesicBufferer.m_small_tolerance, densify_dist * 0.25); - return geodesicBufferer.buffer_(); - } - - private Geometry m_geometry; - - protected static final class GeodesicBufferCommand { - protected interface Flags { - int enum_line = 1; - int enum_arc = 2; - int enum_dummy = 4; - int enum_concave_dip = 8; - int enum_connection = enum_arc | enum_line; - } - - protected Point2D m_from; - protected Point2D m_to; - protected Point2D m_center; - protected int m_next; - protected int m_prev; - protected int m_type; - - protected GeodesicBufferCommand(Point2D from, Point2D to, Point2D center, - int type, int next, int prev) { - m_from = new Point2D(); - m_to = new Point2D(); - m_center = new Point2D(); - m_from.setCoords(from); - m_to.setCoords(to); - m_center.setCoords(center); - m_type = type; - m_next = next; - m_prev = prev; - } - - protected GeodesicBufferCommand(Point2D from, Point2D to, int next, int prev, - String dummy) { - m_from = new Point2D(); - m_to = new Point2D(); - m_center = new Point2D(); - m_from.setCoords(from); - m_to.setCoords(to); - m_center.setNaN(); - m_type = 4; - m_next = next; - m_prev = prev; - } - } - - private ArrayList m_buffer_commands; - - private int m_original_geom_type; - private ProgressTracker m_progress_tracker; - private int m_max_vertex_in_complete_circle; - private SpatialReference m_spatialReference; - private double m_tolerance; - private double m_small_tolerance; - private double m_filter_tolerance; - private double m_densify_dist; - private double m_distance; - private double m_abs_distance; - private double m_abs_distance_reversed; - private double m_dA; - private boolean m_b_output_loops; - private boolean m_bfilter; -// private double m_a; + /** + * Result is always a polygon. For non positive distance and non-areas + * returns an empty polygon. For points returns circles. + */ + static Geometry buffer(Geometry geometry, + double distance, + SpatialReference sr, + double densify_dist, + int max_vertex_in_complete_circle, + ProgressTracker progress_tracker) { + if (geometry == null) + throw new IllegalArgumentException(); + + if (densify_dist < 0) + throw new IllegalArgumentException(); + + if (geometry.isEmpty()) + return new Polygon(geometry.getDescription()); + + GeodesicBufferer geodesicBufferer = new GeodesicBufferer(progress_tracker); + + Envelope2D env2D = new Envelope2D(); + geometry.queryLooseEnvelope2D(env2D); + + geodesicBufferer.m_spatialReference = sr; + + if (distance > 0) { + // env2D.inflate(distance, distance); + GeoDist.inflateEnv2D( + geodesicBufferer.m_spatialReference.getMajorAxis(), + geodesicBufferer.m_spatialReference.getEccentricitySquared(), + env2D, + distance, + distance); + } + + geodesicBufferer.m_geometry = geometry; + geodesicBufferer.m_tolerance = InternalUtils.calculateToleranceFromGeometry(sr, env2D, true);// conservative to have same effect as simplify + geodesicBufferer.m_small_tolerance = InternalUtils.calculateToleranceFromGeometry(null, env2D, true);// conservative + // to have same effect as simplify + geodesicBufferer.m_distance = distance; + geodesicBufferer.m_original_geom_type = geometry.getType().value(); + if (max_vertex_in_complete_circle <= 0) { + max_vertex_in_complete_circle = 96;// 96 is the value used by SG. + // This is the number of + // vertices in the full circle. + } + + geodesicBufferer.m_abs_distance = Math.abs(geodesicBufferer.m_distance); + geodesicBufferer.m_abs_distance_reversed = geodesicBufferer.m_abs_distance != 0 ? 1.0 / geodesicBufferer.m_abs_distance : 0; + + if (NumberUtils.isNaN(densify_dist) || densify_dist == 0) { + densify_dist = geodesicBufferer.m_abs_distance * 1e-5; + } else if (densify_dist > geodesicBufferer.m_abs_distance * 0.5) { + //TODO put a break point here to see if this is ever a problem + // do not allow too large densify distance (the value will be adjusted anyway later) + densify_dist = geodesicBufferer.m_abs_distance * 0.5; + } + + if (max_vertex_in_complete_circle < 12) + max_vertex_in_complete_circle = 12; + + // TODO I don't know what max_dd is for. decimal degrees? + double max_dd = Math.abs(distance) * (1 - Math.cos(Math.PI / max_vertex_in_complete_circle)); + + if (max_dd > densify_dist) { + densify_dist = max_dd;// the densify distance has to agree with the + // max_vertex_in_complete_circle + } else { + double vertex_count = Math.PI / Math.acos(1.0 - densify_dist / Math.abs(distance)); + if (vertex_count < (double) max_vertex_in_complete_circle - 1.0) { + max_vertex_in_complete_circle = (int) vertex_count; + if (max_vertex_in_complete_circle < 12) { + max_vertex_in_complete_circle = 12; + densify_dist = Math.abs(distance) * (1 - Math.cos(Math.PI / max_vertex_in_complete_circle)); + } + } + } + + geodesicBufferer.m_densify_dist = densify_dist; + geodesicBufferer.m_max_vertex_in_complete_circle = max_vertex_in_complete_circle; + // when filtering close points we do not want the filter to distort + // generated buffer too much. + geodesicBufferer.m_filter_tolerance = Math.min(geodesicBufferer.m_small_tolerance, densify_dist * 0.25); + return geodesicBufferer.buffer_(); + } + + private Geometry m_geometry; + + protected static final class GeodesicBufferCommand { + protected interface Flags { + int enum_line = 1; + int enum_arc = 2; + int enum_dummy = 4; + int enum_concave_dip = 8; + int enum_connection = enum_arc | enum_line; + } + + protected Point2D m_from; + protected Point2D m_to; + protected Point2D m_center; + protected int m_next; + protected int m_prev; + protected int m_type; + + protected GeodesicBufferCommand(Point2D from, Point2D to, Point2D center, + int type, int next, int prev) { + m_from = new Point2D(); + m_to = new Point2D(); + m_center = new Point2D(); + m_from.setCoords(from); + m_to.setCoords(to); + m_center.setCoords(center); + m_type = type; + m_next = next; + m_prev = prev; + } + + protected GeodesicBufferCommand(Point2D from, Point2D to, int next, int prev, + String dummy) { + m_from = new Point2D(); + m_to = new Point2D(); + m_center = new Point2D(); + m_from.setCoords(from); + m_to.setCoords(to); + m_center.setNaN(); + m_type = 4; + m_next = next; + m_prev = prev; + } + } + + private ArrayList m_buffer_commands; + + private int m_original_geom_type; + private ProgressTracker m_progress_tracker; + private int m_max_vertex_in_complete_circle; + private SpatialReference m_spatialReference; + private double m_tolerance; + private double m_small_tolerance; + private double m_filter_tolerance; + private double m_densify_dist; + private double m_distance; + private double m_abs_distance; + private double m_abs_distance_reversed; + private double m_dA; + private boolean m_b_output_loops; + private boolean m_bfilter; + // private double m_a; // private double m_e2; - private static final double RAD_TO_DEG = 180.0 / Math.PI; - private static final double DEG_TO_RAD = Math.PI / 180.0; - - // private ArrayList m_left_stack; - // private ArrayList m_middle_stack; - private Line m_helper_line_1; - private Line m_helper_line_2; - private Point2D[] m_helper_array; - private int m_progress_counter; - - private static final class GeometryCursorForMultiPoint extends GeometryCursor { - private int m_index; - private MultiPoint m_mp; - private SpatialReference m_spatialReference; - private double m_distance; - private double m_densify_dist; - private int m_max_vertex_in_complete_circle; - private ProgressTracker m_progress_tracker; - - GeometryCursorForMultiPoint(MultiPoint mp, - double distance, - SpatialReference sr, - double densify_dist, - int max_vertex_in_complete_circle, - ProgressTracker progress_tracker) { - m_index = 0; - m_mp = mp; - m_distance = distance; - m_spatialReference = sr; - m_densify_dist = densify_dist; - m_max_vertex_in_complete_circle = max_vertex_in_complete_circle; - m_progress_tracker = progress_tracker; - } - - @Override - public boolean hasNext() { return m_index < m_mp.getPointCount(); } - - @Override - public Geometry next() { - Point point = new Point(); - while (true) { - if (m_index == m_mp.getPointCount()) - return null; - - m_mp.getPointByVal(m_index, point); - m_index++; - if (point.isEmpty()) - continue; - break; - } - - return GeodesicBufferer.buffer( - point, - m_distance, - m_spatialReference, - m_densify_dist, - m_max_vertex_in_complete_circle, - m_progress_tracker); - } - - @Override - public long getGeometryID() { - return 0; - } - } - - private static final class GeometryCursorForPolyline extends GeometryCursor { - private GeodesicBufferer m_bufferer; - private int m_index; - private boolean m_bfilter; - - GeometryCursorForPolyline(GeodesicBufferer geodesicBufferer, boolean bfilter) { - m_bufferer = geodesicBufferer; - m_index = 0; - m_bfilter = bfilter; - } - - @Override - public boolean hasNext() { return m_index < ((MultiPath)m_bufferer.m_geometry).getPathCount(); } - - @Override - public Geometry next() { - MultiPathImpl mp = (MultiPathImpl) (m_bufferer.m_geometry._getImpl()); - // if there is a path left to retrieve - if (m_index < mp.getPathCount()) { - int ind = m_index; - m_index++; - - // TODO isClosedPathInXYPlane?! is the first point the same as the last? - if (!mp.isClosedPathInXYPlane(ind)) { - // TODO ??!! grab 2nd to last last point? - Point2D prev_end = mp.getXY(mp.getPathEnd(ind) - 1); - while (m_index < mp.getPathCount()) { - Point2D start = mp.getXY(mp.getPathStart(m_index)); - if (mp.isClosedPathInXYPlane(m_index)) - break; - if (start != prev_end) - break; - - prev_end = mp.getXY(mp.getPathEnd(m_index) - 1); - m_index++; - } - } - - - if (m_index - ind == 1) { - // if there is only one path - return m_bufferer.bufferPolylinePath_((Polyline) (m_bufferer.m_geometry), ind, m_bfilter); - } else { - Polyline tmp_polyline = new Polyline(m_bufferer.m_geometry.getDescription()); - tmp_polyline.addPath((Polyline) m_bufferer.m_geometry, ind, true); - for (int i = ind + 1; i < m_index; i++) { - ((MultiPathImpl) tmp_polyline._getImpl()).addSegmentsFromPath( - (MultiPathImpl) m_bufferer.m_geometry._getImpl(), - i, - 0, - mp.getSegmentCount(i), - false); - } - - Polygon res = m_bufferer.bufferPolylinePath_(tmp_polyline, 0, m_bfilter); - return res; - } - } - - return null; - } - - @Override - public long getGeometryID() { - return 0; - } - } + private static final double RAD_TO_DEG = 180.0 / Math.PI; + private static final double DEG_TO_RAD = Math.PI / 180.0; + + // private ArrayList m_left_stack; + // private ArrayList m_middle_stack; + private Line m_helper_line_1; + private Line m_helper_line_2; + private Point2D[] m_helper_array; + private int m_progress_counter; + + private static final class GeometryCursorForMultiPoint extends GeometryCursor { + private int m_index; + private MultiPoint m_mp; + private SpatialReference m_spatialReference; + private double m_distance; + private double m_densify_dist; + private int m_max_vertex_in_complete_circle; + private ProgressTracker m_progress_tracker; + + GeometryCursorForMultiPoint(MultiPoint mp, + double distance, + SpatialReference sr, + double densify_dist, + int max_vertex_in_complete_circle, + ProgressTracker progress_tracker) { + m_index = 0; + m_mp = mp; + m_distance = distance; + m_spatialReference = sr; + m_densify_dist = densify_dist; + m_max_vertex_in_complete_circle = max_vertex_in_complete_circle; + m_progress_tracker = progress_tracker; + } + + @Override + public boolean hasNext() { + return m_index < m_mp.getPointCount(); + } + + @Override + public Geometry next() { + Point point = new Point(); + while (true) { + if (m_index == m_mp.getPointCount()) + return null; + + m_mp.getPointByVal(m_index, point); + m_index++; + if (point.isEmpty()) + continue; + break; + } + + return GeodesicBufferer.buffer( + point, + m_distance, + m_spatialReference, + m_densify_dist, + m_max_vertex_in_complete_circle, + m_progress_tracker); + } + + @Override + public long getGeometryID() { + return 0; + } + } + + private static final class GeometryCursorForPolyline extends GeometryCursor { + private GeodesicBufferer m_bufferer; + private int m_index; + private boolean m_bfilter; + + GeometryCursorForPolyline(GeodesicBufferer geodesicBufferer, boolean bfilter) { + m_bufferer = geodesicBufferer; + m_index = 0; + m_bfilter = bfilter; + } + + @Override + public boolean hasNext() { + return m_index < ((MultiPath) m_bufferer.m_geometry).getPathCount(); + } + + @Override + public Geometry next() { + MultiPathImpl mp = (MultiPathImpl) (m_bufferer.m_geometry._getImpl()); + // if there is a path left to retrieve + if (m_index < mp.getPathCount()) { + int ind = m_index; + m_index++; + + // TODO isClosedPathInXYPlane?! is the first point the same as the last? + if (!mp.isClosedPathInXYPlane(ind)) { + // TODO ??!! grab 2nd to last last point? + Point2D prev_end = mp.getXY(mp.getPathEnd(ind) - 1); + while (m_index < mp.getPathCount()) { + Point2D start = mp.getXY(mp.getPathStart(m_index)); + if (mp.isClosedPathInXYPlane(m_index)) + break; + if (start != prev_end) + break; + + prev_end = mp.getXY(mp.getPathEnd(m_index) - 1); + m_index++; + } + } + + + if (m_index - ind == 1) { + // if there is only one path + return m_bufferer.bufferPolylinePath_((Polyline) (m_bufferer.m_geometry), ind, m_bfilter); + } else { + Polyline tmp_polyline = new Polyline(m_bufferer.m_geometry.getDescription()); + tmp_polyline.addPath((Polyline) m_bufferer.m_geometry, ind, true); + for (int i = ind + 1; i < m_index; i++) { + ((MultiPathImpl) tmp_polyline._getImpl()).addSegmentsFromPath( + (MultiPathImpl) m_bufferer.m_geometry._getImpl(), + i, + 0, + mp.getSegmentCount(i), + false); + } + + Polygon res = m_bufferer.bufferPolylinePath_(tmp_polyline, 0, m_bfilter); + return res; + } + } + + return null; + } + + @Override + public long getGeometryID() { + return 0; + } + } // private static final class GeometryCursorForPolygon extends GeometryCursor { // private GeodesicBufferer m_bufferer; @@ -334,135 +338,135 @@ public long getGeometryID() { // } // - private GeodesicBufferer(ProgressTracker progress_tracker) { - m_buffer_commands = new ArrayList(0); - m_progress_tracker = progress_tracker; - m_tolerance = 0; - m_small_tolerance = 0; - m_filter_tolerance = 0; - m_distance = 0; - m_original_geom_type = Geometry.GeometryType.Unknown; - m_abs_distance_reversed = 0; - m_abs_distance = 0; - m_densify_dist = -1; - m_dA = -1; - m_b_output_loops = true; - m_bfilter = true; + private GeodesicBufferer(ProgressTracker progress_tracker) { + m_buffer_commands = new ArrayList(0); + m_progress_tracker = progress_tracker; + m_tolerance = 0; + m_small_tolerance = 0; + m_filter_tolerance = 0; + m_distance = 0; + m_original_geom_type = Geometry.GeometryType.Unknown; + m_abs_distance_reversed = 0; + m_abs_distance = 0; + m_densify_dist = -1; + m_dA = -1; + m_b_output_loops = true; + m_bfilter = true; // m_a = 6378137.0; // radius of spheroid for WGS_1984 // m_e2 = 0.0066943799901413165; // ellipticity for WGS_1984 - } - - private Geometry buffer_() { - int gt = m_geometry.getType().value(); - if (Geometry.isSegment(gt)) {// convert segment to a polyline and repeat - // the call - Polyline polyline = new Polyline(m_geometry.getDescription()); - polyline.addSegment((Segment) (m_geometry), true); - m_geometry = polyline; - return buffer_(); - } - - if (m_distance <= m_tolerance) { - if (Geometry.isArea(gt)) { - //TODO add geodetic getWidth and getHeight for Envelope - if (m_distance <= 0) { - // if the geometry is area type, then the negative distance - // may produce a degenerate shape. Check for this and return - // empty geometry. - Envelope2D env = new Envelope2D(); - m_geometry.queryEnvelope2D(env); - - if (GeoDist.getEnvWidth(m_spatialReference.getMajorAxis(), m_spatialReference.getEccentricitySquared(), env) <= -m_distance * 2 || - GeoDist.getEnvHeight(m_spatialReference.getMajorAxis(), m_spatialReference.getEccentricitySquared(), env) <= m_distance * 2) { - return new Polygon(m_geometry.getDescription()); - } - } - } else { - return new Polygon(m_geometry.getDescription()); - // return an empty polygon for distance <= m_tolerance - // and any input other than polygon. - } - } - - // Complex cases: - switch (m_geometry.getType().value()) { - case Geometry.GeometryType.Point: - return bufferPoint_(); - case Geometry.GeometryType.MultiPoint: - return bufferMultiPoint_(); - case Geometry.GeometryType.Polyline: - return bufferPolyline_(); - case Geometry.GeometryType.Polygon: - return bufferPolygon_(); - case Geometry.GeometryType.Envelope: - return bufferEnvelope_(); - default: - throw GeometryException.GeometryInternalError(); - } - } - - private Geometry bufferPolyline_() { - if (isDegenerateGeometry_(m_geometry)) { - Point point = new Point(); - ((MultiVertexGeometry) m_geometry).getPointByVal(0, point); - Envelope2D env2D = new Envelope2D(); - m_geometry.queryEnvelope2D(env2D); - // TODO get center Geodesic - point.setXY(env2D.getCenter()); - return bufferPoint_(point); - } - - // TODO cannot use preparePolyline until there is a Geodetic Generalize - //m_geometry = preparePolyline_((Polyline) (m_geometry)); - - GeometryCursorForPolyline cursor = new GeometryCursorForPolyline(this, m_bfilter); - GeometryCursor union_cursor = ((OperatorUnion) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.Union)).execute( - cursor, - m_spatialReference, - m_progress_tracker); - Geometry result = union_cursor.next(); - return result; - } - - - private Geometry bufferPolygon_() { - if (m_distance == 0) - return m_geometry;// return input to the output. - - OperatorSimplify simplify = (OperatorSimplify) OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.Simplify); - - m_geometry = simplify.execute(m_geometry, null, false, - m_progress_tracker); - - Polygon poly = (Polygon) (m_geometry); - OperatorBoundary boundaryOp = (OperatorBoundary) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.Boundary); - SimpleGeometryCursor inputPolygonCursor = new SimpleGeometryCursor(m_geometry); - GeometryCursor boundaryLocalCursor = boundaryOp.execute(inputPolygonCursor, null); - OperatorGeodesicBuffer geodesicOp = (OperatorGeodesicBuffer) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.GeodesicBuffer); - double[] distances = new double[1]; - distances[0] = m_abs_distance; - GeometryCursor bufferedBoundaryCursor = geodesicOp.execute(boundaryLocalCursor, m_spatialReference, 0, distances, m_densify_dist, false, true, m_progress_tracker); - if (m_distance < 0) { - OperatorDifference differenceOp = (OperatorDifference) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.Difference); - SimpleGeometryCursor subtractee = new SimpleGeometryCursor(m_geometry); - GeometryCursor negativeBufferedGeom = differenceOp.execute(subtractee, bufferedBoundaryCursor, m_spatialReference, m_progress_tracker); + } + + private Geometry buffer_() { + int gt = m_geometry.getType().value(); + if (Geometry.isSegment(gt)) {// convert segment to a polyline and repeat + // the call + Polyline polyline = new Polyline(m_geometry.getDescription()); + polyline.addSegment((Segment) (m_geometry), true); + m_geometry = polyline; + return buffer_(); + } + + if (m_distance <= m_tolerance) { + if (Geometry.isArea(gt)) { + //TODO add geodetic getWidth and getHeight for Envelope + if (m_distance <= 0) { + // if the geometry is area type, then the negative distance + // may produce a degenerate shape. Check for this and return + // empty geometry. + Envelope2D env = new Envelope2D(); + m_geometry.queryEnvelope2D(env); + + if (GeoDist.getEnvWidth(m_spatialReference.getMajorAxis(), m_spatialReference.getEccentricitySquared(), env) <= -m_distance * 2 || + GeoDist.getEnvHeight(m_spatialReference.getMajorAxis(), m_spatialReference.getEccentricitySquared(), env) <= m_distance * 2) { + return new Polygon(m_geometry.getDescription()); + } + } + } else { + return new Polygon(m_geometry.getDescription()); + // return an empty polygon for distance <= m_tolerance + // and any input other than polygon. + } + } + + // Complex cases: + switch (m_geometry.getType().value()) { + case Geometry.GeometryType.Point: + return bufferPoint_(); + case Geometry.GeometryType.MultiPoint: + return bufferMultiPoint_(); + case Geometry.GeometryType.Polyline: + return bufferPolyline_(); + case Geometry.GeometryType.Polygon: + return bufferPolygon_(); + case Geometry.GeometryType.Envelope: + return bufferEnvelope_(); + default: + throw GeometryException.GeometryInternalError(); + } + } + + private Geometry bufferPolyline_() { + if (isDegenerateGeometry_(m_geometry)) { + Point point = new Point(); + ((MultiVertexGeometry) m_geometry).getPointByVal(0, point); + Envelope2D env2D = new Envelope2D(); + m_geometry.queryEnvelope2D(env2D); + // TODO get center Geodesic + point.setXY(env2D.getCenter()); + return bufferPoint_(point); + } + + // TODO cannot use preparePolyline until there is a Geodetic Generalize + //m_geometry = preparePolyline_((Polyline) (m_geometry)); + + GeometryCursorForPolyline cursor = new GeometryCursorForPolyline(this, m_bfilter); + GeometryCursor union_cursor = ((OperatorUnion) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.Union)).execute( + cursor, + m_spatialReference, + m_progress_tracker); + Geometry result = union_cursor.next(); + return result; + } + + + private Geometry bufferPolygon_() { + if (m_distance == 0) + return m_geometry;// return input to the output. + + OperatorSimplify simplify = (OperatorSimplify) OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Simplify); + + m_geometry = simplify.execute(m_geometry, null, false, + m_progress_tracker); + + Polygon poly = (Polygon) (m_geometry); + OperatorBoundary boundaryOp = (OperatorBoundary) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.Boundary); + SimpleGeometryCursor inputPolygonCursor = new SimpleGeometryCursor(m_geometry); + GeometryCursor boundaryLocalCursor = boundaryOp.execute(inputPolygonCursor, null); + OperatorGeodesicBuffer geodesicOp = (OperatorGeodesicBuffer) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.GeodesicBuffer); + double[] distances = new double[1]; + distances[0] = m_abs_distance; + GeometryCursor bufferedBoundaryCursor = geodesicOp.execute(boundaryLocalCursor, m_spatialReference, 0, distances, m_densify_dist, false, true, m_progress_tracker); + if (m_distance < 0) { + OperatorDifference differenceOp = (OperatorDifference) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.Difference); + SimpleGeometryCursor subtractee = new SimpleGeometryCursor(m_geometry); + GeometryCursor negativeBufferedGeom = differenceOp.execute(subtractee, bufferedBoundaryCursor, m_spatialReference, m_progress_tracker); // Polygon buffered_result = bufferPolygonImpl_(poly, 0, // poly.getPathCount()); // return simplify.execute(buffered_result, m_spatialReference, false, // m_progress_tracker); - return negativeBufferedGeom.next(); - } else { - if (isDegenerateGeometry_(m_geometry)) { - Point point = new Point(); - ((MultiVertexGeometry) m_geometry).getPointByVal(0, point); - Envelope2D env2D = new Envelope2D(); - m_geometry.queryEnvelope2D(env2D); - // TODO get center Geodesic - point.setXY(env2D.getCenter()); - return bufferPoint_(point); - } - return ((OperatorUnion) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.Union)).execute(bufferedBoundaryCursor.next(), m_geometry, m_spatialReference, null); + return negativeBufferedGeom.next(); + } else { + if (isDegenerateGeometry_(m_geometry)) { + Point point = new Point(); + ((MultiVertexGeometry) m_geometry).getPointByVal(0, point); + Envelope2D env2D = new Envelope2D(); + m_geometry.queryEnvelope2D(env2D); + // TODO get center Geodesic + point.setXY(env2D.getCenter()); + return bufferPoint_(point); + } + return ((OperatorUnion) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.Union)).execute(bufferedBoundaryCursor.next(), m_geometry, m_spatialReference, null); // // For the positive distance we need to process polygon in the parts // // such that each exterior ring with holes is processed separatelly. // GeometryCursorForPolygon cursor = new GeometryCursorForPolygon(this); @@ -471,8 +475,8 @@ private Geometry bufferPolygon_() { // cursor, m_spatialReference, m_progress_tracker); // Geometry result = union_cursor.next(); // return result; - } - } + } + } // // private Polygon bufferPolygonImpl_(Polygon input_geom, int ipath_begin, // int ipath_end) { @@ -670,55 +674,55 @@ private Geometry bufferPolygon_() { // } // - private Geometry bufferPoint_() { - return bufferPoint_((Point) (m_geometry)); - } - - private Geometry bufferPoint_(Point point) { - assert (m_distance > 0); - Polygon resultPolygon = new Polygon(m_geometry.getDescription()); - addCircle_((MultiPathImpl) resultPolygon._getImpl(), point); - return setStrongSimple_(resultPolygon); - } - - private Geometry bufferMultiPoint_() { - assert (m_distance > 0); - GeometryCursorForMultiPoint mpCursor = new GeometryCursorForMultiPoint( - (MultiPoint) m_geometry, - m_distance, - m_spatialReference, - m_densify_dist, - m_max_vertex_in_complete_circle, - m_progress_tracker); - // TODO is this union necessary??!??!??! - GeometryCursor c = ((OperatorUnion) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.Union)).execute( - mpCursor, - m_spatialReference, - m_progress_tracker); - return c.next(); - } - - private Geometry bufferEnvelope_() { - Polygon polygon = new Polygon(m_geometry.getDescription()); - if (m_distance <= 0) { - if (m_distance == 0) - polygon.addEnvelope((Envelope) (m_geometry), false); - else { - Envelope2D env = new Envelope2D(); - m_geometry.queryEnvelope2D(env); - GeoDist.inflateEnv2D(m_spatialReference.getMajorAxis(), m_spatialReference.getEccentricitySquared(), env, m_distance, m_distance); + private Geometry bufferPoint_() { + return bufferPoint_((Point) (m_geometry)); + } + + private Geometry bufferPoint_(Point point) { + assert (m_distance > 0); + Polygon resultPolygon = new Polygon(m_geometry.getDescription()); + addCircle_((MultiPathImpl) resultPolygon._getImpl(), point); + return setStrongSimple_(resultPolygon); + } + + private Geometry bufferMultiPoint_() { + assert (m_distance > 0); + GeometryCursorForMultiPoint mpCursor = new GeometryCursorForMultiPoint( + (MultiPoint) m_geometry, + m_distance, + m_spatialReference, + m_densify_dist, + m_max_vertex_in_complete_circle, + m_progress_tracker); + // TODO is this union necessary??!??!??! + GeometryCursor c = ((OperatorUnion) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.Union)).execute( + mpCursor, + m_spatialReference, + m_progress_tracker); + return c.next(); + } + + private Geometry bufferEnvelope_() { + Polygon polygon = new Polygon(m_geometry.getDescription()); + if (m_distance <= 0) { + if (m_distance == 0) + polygon.addEnvelope((Envelope) (m_geometry), false); + else { + Envelope2D env = new Envelope2D(); + m_geometry.queryEnvelope2D(env); + GeoDist.inflateEnv2D(m_spatialReference.getMajorAxis(), m_spatialReference.getEccentricitySquared(), env, m_distance, m_distance); // env.inflate(m_distance, m_distance); - polygon.addEnvelope(env, false); - } + polygon.addEnvelope(env, false); + } - return polygon;// nothing is easier than negative buffer on the envelope. - } + return polygon;// nothing is easier than negative buffer on the envelope. + } - polygon.addEnvelope((Envelope) (m_geometry), false); - m_geometry = polygon; - return bufferPolygon_(); - //return bufferConvexPath_(polygon, 0); - } + polygon.addEnvelope((Envelope) (m_geometry), false); + m_geometry = polygon; + return bufferPolygon_(); + //return bufferConvexPath_(polygon, 0); + } // // private Polygon bufferConvexPath_(MultiPath src, int ipath) { // generateCircleTemplate_(); @@ -773,106 +777,106 @@ private Geometry bufferEnvelope_() { // return setWeakSimple_(resultPolygon); // } - private Polygon bufferPolylinePath_(Polyline polyline, int ipath, boolean bfilter) { - assert (m_distance != 0); - //TODO, circle template doesn't work with Geodesics (unless all circles are on the same line of latitude) + private Polygon bufferPolylinePath_(Polyline polyline, int ipath, boolean bfilter) { + assert (m_distance != 0); + //TODO, circle template doesn't work with Geodesics (unless all circles are on the same line of latitude) // generateCircleTemplate_(); - MultiPath input_multi_path = polyline; - MultiPathImpl mp_impl = (MultiPathImpl) (input_multi_path._getImpl()); - - if (mp_impl.getPathSize(ipath) < 1) - return null; - - if (isDegeneratePath_(mp_impl, ipath) && m_distance > 0) {// if a path - // is degenerate (almost a point), then we can draw a circle instead - // of it as a buffer and nobody would notice :) - - Point point = new Point(); - mp_impl.getPointByVal(mp_impl.getPathStart(ipath), point); - Envelope2D env2D = new Envelope2D(); - mp_impl.queryPathEnvelope2D(ipath, env2D); - point.setXY(env2D.getCenter()); - return (Polygon) (bufferPoint_(point)); - } - - Polyline result_polyline = new Polyline(polyline.getDescription()); - //TODO what is this commented out code? - // result_polyline.reserve((m_circle_template.size() / 10 + 4) * - // mp_impl.getPathSize(ipath)); - - MultiPathImpl result_mp = (MultiPathImpl) result_polyline._getImpl(); - boolean b_closed = mp_impl.isClosedPathInXYPlane(ipath); - - if (b_closed) { - bufferClosedPath_(input_multi_path, ipath, result_mp, bfilter, 1); - bufferClosedPath_(input_multi_path, ipath, result_mp, bfilter, -1); - } else { - Polyline tmpPoly = new Polyline(input_multi_path.getDescription()); - tmpPoly.addPath(input_multi_path, ipath, false); - ((MultiPathImpl) tmpPoly._getImpl()).addSegmentsFromPath( - (MultiPathImpl) input_multi_path._getImpl(), - ipath, - 0, - input_multi_path.getSegmentCount(ipath), - false); - bufferClosedPath_(tmpPoly, 0, result_mp, bfilter, 1); + MultiPath input_multi_path = polyline; + MultiPathImpl mp_impl = (MultiPathImpl) (input_multi_path._getImpl()); + + if (mp_impl.getPathSize(ipath) < 1) + return null; + + if (isDegeneratePath_(mp_impl, ipath) && m_distance > 0) {// if a path + // is degenerate (almost a point), then we can draw a circle instead + // of it as a buffer and nobody would notice :) + + Point point = new Point(); + mp_impl.getPointByVal(mp_impl.getPathStart(ipath), point); + Envelope2D env2D = new Envelope2D(); + mp_impl.queryPathEnvelope2D(ipath, env2D); + point.setXY(env2D.getCenter()); + return (Polygon) (bufferPoint_(point)); + } + + Polyline result_polyline = new Polyline(polyline.getDescription()); + //TODO what is this commented out code? + // result_polyline.reserve((m_circle_template.size() / 10 + 4) * + // mp_impl.getPathSize(ipath)); + + MultiPathImpl result_mp = (MultiPathImpl) result_polyline._getImpl(); + boolean b_closed = mp_impl.isClosedPathInXYPlane(ipath); + + if (b_closed) { + bufferClosedPath_(input_multi_path, ipath, result_mp, bfilter, 1); + bufferClosedPath_(input_multi_path, ipath, result_mp, bfilter, -1); + } else { + Polyline tmpPoly = new Polyline(input_multi_path.getDescription()); + tmpPoly.addPath(input_multi_path, ipath, false); + ((MultiPathImpl) tmpPoly._getImpl()).addSegmentsFromPath( + (MultiPathImpl) input_multi_path._getImpl(), + ipath, + 0, + input_multi_path.getSegmentCount(ipath), + false); + bufferClosedPath_(tmpPoly, 0, result_mp, bfilter, 1); // // Operator_factory_local::SaveJSONToTextFileDbg("c:/temp/buffer_prepare.txt", // // *result_polyline, nullptr); - } - - return bufferCleanup_(result_polyline, false); - } - - // Planar and Geodesic are equivalent - private void progress_() { - m_progress_counter++; - if (m_progress_counter % 1024 == 0) { - if ((m_progress_tracker != null) - && !(m_progress_tracker.progress(-1, -1))) - throw new RuntimeException("user_canceled"); - } - } - - - private Polygon bufferCleanup_(MultiPath multi_path, boolean simplify_result) { - double tol = simplify_result ? m_tolerance : m_small_tolerance; - String words = GeometryEngine.geometryToWkt((Polyline) multi_path, 0); - Polygon resultPolygon = (Polygon) (TopologicalOperations - .planarSimplify(multi_path, tol, true, !simplify_result, - m_progress_tracker)); - assert (InternalUtils.isWeakSimple(resultPolygon, 0.0)); - return resultPolygon; - } - - //TODO prepare this for Geodesic - private int calcN_(int minN) { - if (m_densify_dist == 0) - return m_max_vertex_in_complete_circle; - - double r = m_densify_dist * Math.abs(m_abs_distance_reversed); - double cos_a = 1 - r; - double N; - if (cos_a < -1) - N = minN; - else - N = 2.0 * Math.PI / Math.acos(cos_a) + 0.5; - - if (N < minN) - N = minN; - else if (N > m_max_vertex_in_complete_circle) - N = m_max_vertex_in_complete_circle; - - return (int) N; - } - - private void addJoin_(MultiPathImpl dst, - Point2D center, - Point2D arcStartPt, - Point2D arcEndPt, - boolean bStartPath, - boolean bFinishAtToPt) { - addArc_(dst, center, arcStartPt, arcEndPt, bStartPath, bFinishAtToPt); + } + + return bufferCleanup_(result_polyline, false); + } + + // Planar and Geodesic are equivalent + private void progress_() { + m_progress_counter++; + if (m_progress_counter % 1024 == 0) { + if ((m_progress_tracker != null) + && !(m_progress_tracker.progress(-1, -1))) + throw new RuntimeException("user_canceled"); + } + } + + + private Polygon bufferCleanup_(MultiPath multi_path, boolean simplify_result) { + double tol = simplify_result ? m_tolerance : m_small_tolerance; + String words = GeometryEngine.geometryToWkt((Polyline) multi_path, 0); + Polygon resultPolygon = (Polygon) (TopologicalOperations + .planarSimplify(multi_path, tol, true, !simplify_result, + m_progress_tracker)); + assert (InternalUtils.isWeakSimple(resultPolygon, 0.0)); + return resultPolygon; + } + + //TODO prepare this for Geodesic + private int calcN_(int minN) { + if (m_densify_dist == 0) + return m_max_vertex_in_complete_circle; + + double r = m_densify_dist * Math.abs(m_abs_distance_reversed); + double cos_a = 1 - r; + double N; + if (cos_a < -1) + N = minN; + else + N = 2.0 * Math.PI / Math.acos(cos_a) + 0.5; + + if (N < minN) + N = minN; + else if (N > m_max_vertex_in_complete_circle) + N = m_max_vertex_in_complete_circle; + + return (int) N; + } + + private void addJoin_(MultiPathImpl dst, + Point2D center, + Point2D arcStartPt, + Point2D arcEndPt, + boolean bStartPath, + boolean bFinishAtToPt) { + addArc_(dst, center, arcStartPt, arcEndPt, bStartPath, bFinishAtToPt); // v_1.sub(fromPt, center); // v_1.scale(m_abs_distance_reversed); @@ -935,682 +939,682 @@ private void addJoin_(MultiPathImpl dst, // if (bFinishAtToPt) { // dst.lineTo(toPt); // } - } - - private int bufferClosedPath_(Geometry input_geom, - int ipath, - MultiPathImpl result_mp, - boolean bfilter, - int dir) { - // Use temporary polyline for the path buffering. - EditShape edit_shape = new EditShape(); - int geom = edit_shape.addPathFromMultiPath((MultiPath) input_geom, ipath, true); - - //TODO not sure what filtering does - edit_shape.filterClosePoints(m_filter_tolerance, false, false); - - if (edit_shape.getPointCount(geom) < 2) { - // Got degenerate output. - // Wither bail out or - // produce a circle. - if (dir < 0) - return 1;// negative direction produces nothing. - - MultiPath mpIn = (MultiPath) input_geom; - // Add a circle - Point pt = new Point(); - mpIn.getPointByVal(mpIn.getPathStart(ipath), pt); - addCircle_(result_mp, pt); - return 1; - } - - assert (edit_shape.getFirstPath(geom) != -1); - assert (edit_shape.getFirstVertex(edit_shape.getFirstPath(geom)) != -1); - - //TODO this won't work with Geodesic operations!!! It will ruin the results + } + + private int bufferClosedPath_(Geometry input_geom, + int ipath, + MultiPathImpl result_mp, + boolean bfilter, + int dir) { + // Use temporary polyline for the path buffering. + EditShape edit_shape = new EditShape(); + int geom = edit_shape.addPathFromMultiPath((MultiPath) input_geom, ipath, true); + + //TODO not sure what filtering does + edit_shape.filterClosePoints(m_filter_tolerance, false, false); + + if (edit_shape.getPointCount(geom) < 2) { + // Got degenerate output. + // Wither bail out or + // produce a circle. + if (dir < 0) + return 1;// negative direction produces nothing. + + MultiPath mpIn = (MultiPath) input_geom; + // Add a circle + Point pt = new Point(); + mpIn.getPointByVal(mpIn.getPathStart(ipath), pt); + addCircle_(result_mp, pt); + return 1; + } + + assert (edit_shape.getFirstPath(geom) != -1); + assert (edit_shape.getFirstVertex(edit_shape.getFirstPath(geom)) != -1); + + //TODO this won't work with Geodesic operations!!! It will ruin the results // Point2D origin = edit_shape.getXY(edit_shape.getFirstVertex(edit_shape.getFirstPath(geom))); // Transformation2D tr = new Transformation2D(); // tr.setShift(-origin.x, -origin.y); // // move the path to origin for better accuracy in calculations. // edit_shape.applyTransformation(tr); - //TODO prepare filter for geodesics - //if (bfilter) { - if (false) { - // try removing the noise that does not contribute to the buffer. - int res_filter = filterPath_(edit_shape, geom, dir, true); - assert (res_filter == 1); - // Operator_factory_local::SaveJSONToTextFileDbg("c:/temp/buffer_filter.txt", - // *edit_shape.get_geometry(geom), nullptr); - if (edit_shape.getPointCount(geom) < 2) {// got degenerate output. - // Wither bail out or - // produce a circle. - if (dir < 0) - return 1;// negative direction produces nothing. - - MultiPath mpIn = (MultiPath) input_geom; - // Add a circle - Point pt = new Point(); - mpIn.getPointByVal(mpIn.getPathStart(ipath), pt); - addCircle_(result_mp, pt); - return 1; - } - } - - m_buffer_commands.clear(); - int path = edit_shape.getFirstPath(geom); - int ivert = edit_shape.getFirstVertex(path); - int iprev = dir == 1 ? edit_shape.getPrevVertex(ivert) : edit_shape.getNextVertex(ivert); - int inext = dir == 1 ? edit_shape.getNextVertex(ivert) : edit_shape.getPrevVertex(ivert); - boolean b_first = true; - - // current point - Point2D pt_current = new Point2D(); - // next point - Point2D pt_after = new Point2D(); - // previous point - Point2D pt_before = new Point2D(); - - - Point2D pt_left_prev = new Point2D(); - Point2D pt = new Point2D(); - Point2D pt1 = new Point2D(); - - Point2D v_after = new Point2D(); - Point2D v_before = new Point2D(); + //TODO prepare filter for geodesics + //if (bfilter) { + if (false) { + // try removing the noise that does not contribute to the buffer. + int res_filter = filterPath_(edit_shape, geom, dir, true); + assert (res_filter == 1); + // Operator_factory_local::SaveJSONToTextFileDbg("c:/temp/buffer_filter.txt", + // *edit_shape.get_geometry(geom), nullptr); + if (edit_shape.getPointCount(geom) < 2) {// got degenerate output. + // Wither bail out or + // produce a circle. + if (dir < 0) + return 1;// negative direction produces nothing. + + MultiPath mpIn = (MultiPath) input_geom; + // Add a circle + Point pt = new Point(); + mpIn.getPointByVal(mpIn.getPathStart(ipath), pt); + addCircle_(result_mp, pt); + return 1; + } + } + + m_buffer_commands.clear(); + int path = edit_shape.getFirstPath(geom); + int ivert = edit_shape.getFirstVertex(path); + int iprev = dir == 1 ? edit_shape.getPrevVertex(ivert) : edit_shape.getNextVertex(ivert); + int inext = dir == 1 ? edit_shape.getNextVertex(ivert) : edit_shape.getPrevVertex(ivert); + boolean b_first = true; + + // current point + Point2D pt_current = new Point2D(); + // next point + Point2D pt_after = new Point2D(); + // previous point + Point2D pt_before = new Point2D(); + + + Point2D pt_left_prev = new Point2D(); + Point2D pt = new Point2D(); + Point2D pt1 = new Point2D(); + + Point2D v_after = new Point2D(); + Point2D v_before = new Point2D(); // Point2D v_left = new Point2D(); // Point2D v_left_prev = new Point2D(); - PeDouble az12 = new PeDouble(); - PeDouble lam2 = new PeDouble(); - PeDouble phi2 = new PeDouble(); - - double abs_d = m_abs_distance; - int ncount = edit_shape.getPathSize(path); - - // write out buffer commands as a set of arcs and line segments. - // if we'd convert this directly to a polygon and draw using winding - // fill rule, we'd get the buffered result. - for (int index = 0; index < ncount; index++) { - edit_shape.getXY(inext, pt_after); - - if (b_first) { - // grab the first point - edit_shape.getXY(ivert, pt_current); - // get the previous point (TODO if polygon?!?) - edit_shape.getXY(iprev, pt_before); - - // not sure is this is the right direction. might want before to current - GeoDist.geodesic_distance_ngs(m_spatialReference.getMajorAxis(), m_spatialReference.getEccentricitySquared(), pt_before.x * DEG_TO_RAD, pt_before.y * DEG_TO_RAD, pt_current.x * DEG_TO_RAD, pt_current.y * DEG_TO_RAD, null, az12, null); - // not sure if this is the correct rotation (maybe should be -Math.PI/2.0) - GeoDist.geodesic_forward(m_spatialReference.getMajorAxis(), m_spatialReference.getEccentricitySquared(), pt_current.x * DEG_TO_RAD, pt_current.y * DEG_TO_RAD, abs_d, az12.val - Math.PI / 2.0, lam2, phi2); - pt_left_prev.x = lam2.val * RAD_TO_DEG; - pt_left_prev.y = phi2.val * RAD_TO_DEG; - - // move v_before position as if pt_current was origin - v_before.sub(pt_current, pt_before); - // change v_before into unit vector - v_before.normalize(); + PeDouble az12 = new PeDouble(); + PeDouble lam2 = new PeDouble(); + PeDouble phi2 = new PeDouble(); + + double abs_d = m_abs_distance; + int ncount = edit_shape.getPathSize(path); + + // write out buffer commands as a set of arcs and line segments. + // if we'd convert this directly to a polygon and draw using winding + // fill rule, we'd get the buffered result. + for (int index = 0; index < ncount; index++) { + edit_shape.getXY(inext, pt_after); + + if (b_first) { + // grab the first point + edit_shape.getXY(ivert, pt_current); + // get the previous point (TODO if polygon?!?) + edit_shape.getXY(iprev, pt_before); + + // not sure is this is the right direction. might want before to current + GeoDist.geodesic_distance_ngs(m_spatialReference.getMajorAxis(), m_spatialReference.getEccentricitySquared(), pt_before.x * DEG_TO_RAD, pt_before.y * DEG_TO_RAD, pt_current.x * DEG_TO_RAD, pt_current.y * DEG_TO_RAD, null, az12, null); + // not sure if this is the correct rotation (maybe should be -Math.PI/2.0) + GeoDist.geodesic_forward(m_spatialReference.getMajorAxis(), m_spatialReference.getEccentricitySquared(), pt_current.x * DEG_TO_RAD, pt_current.y * DEG_TO_RAD, abs_d, az12.val - Math.PI / 2.0, lam2, phi2); + pt_left_prev.x = lam2.val * RAD_TO_DEG; + pt_left_prev.y = phi2.val * RAD_TO_DEG; + + // move v_before position as if pt_current was origin + v_before.sub(pt_current, pt_before); + // change v_before into unit vector + v_before.normalize(); // // create unit vector that is 90 degree counter-clockwise of v_before // v_left_prev.leftPerpendicular(v_before); // // scale the left perpendicular vector by the distance // v_left_prev.scale(abs_d); // // create the pt left previous by shifting the left perpendicular vector by the current point // pt_left_prev.add(v_left_prev, pt_current); - } - - // not sure is this is the right direction. might want before to current - GeoDist.geodesic_distance_ngs(m_spatialReference.getMajorAxis(), m_spatialReference.getEccentricitySquared(), pt_current.x * DEG_TO_RAD, pt_current.y * DEG_TO_RAD, pt_after.x * DEG_TO_RAD, pt_after.y * DEG_TO_RAD, null, az12, null); - // not sure if this is the correct rotation (maybe should be -Math.PI/2.0) - GeoDist.geodesic_forward(m_spatialReference.getMajorAxis(), m_spatialReference.getEccentricitySquared(), pt_current.x * DEG_TO_RAD, pt_current.y * DEG_TO_RAD, abs_d, az12.val - Math.PI / 2.0, lam2, phi2); - pt.x = lam2.val * RAD_TO_DEG; - pt.y = phi2.val * RAD_TO_DEG; - - // v_after is the vector of pt_after with the pt_center at origin - v_after.sub(pt_after, pt_current); - // v_after is normalized to be a unit vector - v_after.normalize(); + } + + // not sure is this is the right direction. might want before to current + GeoDist.geodesic_distance_ngs(m_spatialReference.getMajorAxis(), m_spatialReference.getEccentricitySquared(), pt_current.x * DEG_TO_RAD, pt_current.y * DEG_TO_RAD, pt_after.x * DEG_TO_RAD, pt_after.y * DEG_TO_RAD, null, az12, null); + // not sure if this is the correct rotation (maybe should be -Math.PI/2.0) + GeoDist.geodesic_forward(m_spatialReference.getMajorAxis(), m_spatialReference.getEccentricitySquared(), pt_current.x * DEG_TO_RAD, pt_current.y * DEG_TO_RAD, abs_d, az12.val - Math.PI / 2.0, lam2, phi2); + pt.x = lam2.val * RAD_TO_DEG; + pt.y = phi2.val * RAD_TO_DEG; + + // v_after is the vector of pt_after with the pt_center at origin + v_after.sub(pt_after, pt_current); + // v_after is normalized to be a unit vector + v_after.normalize(); // // v_left is a perpendicular to the left of v_after vector, centered at pt_current // v_left.leftPerpendicular(v_after); // // scale v_left by the buffer distance // v_left.scale(abs_d); // // shift the vector back relative to pt_current // pt.add(pt_current, v_left); - // Use these two calculations to determine if the angle is concave or convex - double cross = v_before.crossProduct(v_after); - double dot = v_before.dotProduct(v_after); - boolean bDoJoin = cross < 0 || (dot < 0 && cross == 0); - - if (bDoJoin) { - // create an arc - m_buffer_commands.add( - new GeodesicBufferCommand( - pt_left_prev, - pt, - pt_current, - GeodesicBufferCommand.Flags.enum_arc, - m_buffer_commands.size() + 1, - m_buffer_commands.size() - 1)); - } else if (!pt_left_prev.isEqual(pt)) { - // create straight edge? - m_buffer_commands.add( - new GeodesicBufferCommand( - pt_left_prev, - pt_current, - m_buffer_commands.size() + 1, - m_buffer_commands.size() - 1, - "dummy")); - m_buffer_commands.add( - new GeodesicBufferCommand( - pt_current, - pt, - m_buffer_commands.size() + 1, - m_buffer_commands.size() - 1, - "dummy")); - } - - GeoDist.geodesic_forward(m_spatialReference.getMajorAxis(), m_spatialReference.getEccentricitySquared(), pt_after.x * DEG_TO_RAD, pt_after.y * DEG_TO_RAD, abs_d, az12.val - Math.PI / 2.0, lam2, phi2); - pt1.x = lam2.val * RAD_TO_DEG; - pt1.y = phi2.val * RAD_TO_DEG; + // Use these two calculations to determine if the angle is concave or convex + double cross = v_before.crossProduct(v_after); + double dot = v_before.dotProduct(v_after); + boolean bDoJoin = cross < 0 || (dot < 0 && cross == 0); + + if (bDoJoin) { + // create an arc + m_buffer_commands.add( + new GeodesicBufferCommand( + pt_left_prev, + pt, + pt_current, + GeodesicBufferCommand.Flags.enum_arc, + m_buffer_commands.size() + 1, + m_buffer_commands.size() - 1)); + } else if (!pt_left_prev.isEqual(pt)) { + // create straight edge? + m_buffer_commands.add( + new GeodesicBufferCommand( + pt_left_prev, + pt_current, + m_buffer_commands.size() + 1, + m_buffer_commands.size() - 1, + "dummy")); + m_buffer_commands.add( + new GeodesicBufferCommand( + pt_current, + pt, + m_buffer_commands.size() + 1, + m_buffer_commands.size() - 1, + "dummy")); + } + + GeoDist.geodesic_forward(m_spatialReference.getMajorAxis(), m_spatialReference.getEccentricitySquared(), pt_after.x * DEG_TO_RAD, pt_after.y * DEG_TO_RAD, abs_d, az12.val - Math.PI / 2.0, lam2, phi2); + pt1.x = lam2.val * RAD_TO_DEG; + pt1.y = phi2.val * RAD_TO_DEG; // pt1.add(pt_after, v_left); - m_buffer_commands.add( - new GeodesicBufferCommand( - pt, - pt1, - pt_current, - GeodesicBufferCommand.Flags.enum_line, - m_buffer_commands.size() + 1, - m_buffer_commands.size() - 1)); - - pt_left_prev.setCoords(pt1); + m_buffer_commands.add( + new GeodesicBufferCommand( + pt, + pt1, + pt_current, + GeodesicBufferCommand.Flags.enum_line, + m_buffer_commands.size() + 1, + m_buffer_commands.size() - 1)); + + pt_left_prev.setCoords(pt1); // v_left_prev.setCoords(v_left); - pt_before.setCoords(pt_current); - pt_current.setCoords(pt_after); - v_before.setCoords(v_after); - iprev = ivert; - ivert = inext; - b_first = false; - inext = dir == 1 ? edit_shape.getNextVertex(ivert) : edit_shape.getPrevVertex(ivert); - } - - m_buffer_commands.get(m_buffer_commands.size() - 1).m_next = 0; - m_buffer_commands.get(0).m_prev = m_buffer_commands.size() - 1; - processBufferCommands_(result_mp); + pt_before.setCoords(pt_current); + pt_current.setCoords(pt_after); + v_before.setCoords(v_after); + iprev = ivert; + ivert = inext; + b_first = false; + inext = dir == 1 ? edit_shape.getNextVertex(ivert) : edit_shape.getPrevVertex(ivert); + } + + m_buffer_commands.get(m_buffer_commands.size() - 1).m_next = 0; + m_buffer_commands.get(0).m_prev = m_buffer_commands.size() - 1; + processBufferCommands_(result_mp); // tr.setShift(origin.x, origin.y);// move the path to improve precision. // result_mp.applyTransformation(tr, result_mp.getPathCount() - 1); - return 1; - } - - private void processBufferCommands_(MultiPathImpl result_mp) { - int ifirst_seg = cleanupBufferCommands_(); - boolean first = true; - int iseg_next = ifirst_seg + 1; - for (int iseg = ifirst_seg; iseg_next != ifirst_seg; iseg = iseg_next) { - GeodesicBufferCommand command = m_buffer_commands.get(iseg); - iseg_next = command.m_next != -1 ? command.m_next : (iseg + 1) % m_buffer_commands.size(); - - if (command.m_type == 0) - continue;// deleted segment - - if (first) { - result_mp.startPath(command.m_from); - first = false; - } - - if (command.m_type == GeodesicBufferCommand.Flags.enum_arc) {// arc - addJoin_(result_mp, command.m_center, command.m_from, command.m_to, false, true); - } else { - result_mp.lineTo(command.m_to); - } - first = false; - } + return 1; + } + + private void processBufferCommands_(MultiPathImpl result_mp) { + int ifirst_seg = cleanupBufferCommands_(); + boolean first = true; + int iseg_next = ifirst_seg + 1; + for (int iseg = ifirst_seg; iseg_next != ifirst_seg; iseg = iseg_next) { + GeodesicBufferCommand command = m_buffer_commands.get(iseg); + iseg_next = command.m_next != -1 ? command.m_next : (iseg + 1) % m_buffer_commands.size(); + + if (command.m_type == 0) + continue;// deleted segment + + if (first) { + result_mp.startPath(command.m_from); + first = false; + } + + if (command.m_type == GeodesicBufferCommand.Flags.enum_arc) {// arc + addJoin_(result_mp, command.m_center, command.m_from, command.m_to, false, true); + } else { + result_mp.lineTo(command.m_to); + } + first = false; + } // if (result_mp.getPoint(0).getX() != result_mp.getPoint(result_mp.getPointCount() - 1).getX() && // result_mp.getPoint(0).getY() != result_mp.getPoint(result_mp.getPointCount() - 1).getY()) // result_mp.lineTo(result_mp.getPoint(0)); - } - - //TODO this seems to be fine for Geodesic vs Planar. The intersect test might be a little off, but this should work? - private int cleanupBufferCommands_() { - // The purpose of this function is to remove as many self intersections - // from the buffered shape as possible. - // The buffer works without cleanup also, but slower. - - if (m_helper_array == null) - m_helper_array = new Point2D[9]; - - int istart = 0; - for (int iseg = 0, nseg = m_buffer_commands.size(); iseg < nseg; ) { - GeodesicBufferCommand command = m_buffer_commands.get(iseg); - if ((command.m_type & GeodesicBufferCommand.Flags.enum_connection) != 0) { - //TODO put a breakpoint. needs test case - istart = iseg; - break; - } - - iseg = command.m_next; - } - - int iseg_next = istart + 1; - for (int iseg = istart; iseg_next != istart; iseg = iseg_next) { - GeodesicBufferCommand command = m_buffer_commands.get(iseg); - iseg_next = command.m_next; - int count = 1; - GeodesicBufferCommand command_next = null; - while (iseg_next != iseg) {// find next segement - command_next = m_buffer_commands.get(iseg_next); - if ((command_next.m_type & GeodesicBufferCommand.Flags.enum_connection) != 0) - break; - - iseg_next = command_next.m_next; - count++; - } - - if (count == 1) { - // Next segment starts where this one ends. Skip this case as it - // is simple. - assert (command.m_to.isEqual(command_next.m_from, 0.01)); + } + + //TODO this seems to be fine for Geodesic vs Planar. The intersect test might be a little off, but this should work? + private int cleanupBufferCommands_() { + // The purpose of this function is to remove as many self intersections + // from the buffered shape as possible. + // The buffer works without cleanup also, but slower. + + if (m_helper_array == null) + m_helper_array = new Point2D[9]; + + int istart = 0; + for (int iseg = 0, nseg = m_buffer_commands.size(); iseg < nseg; ) { + GeodesicBufferCommand command = m_buffer_commands.get(iseg); + if ((command.m_type & GeodesicBufferCommand.Flags.enum_connection) != 0) { + //TODO put a breakpoint. needs test case + istart = iseg; + break; + } + + iseg = command.m_next; + } + + int iseg_next = istart + 1; + for (int iseg = istart; iseg_next != istart; iseg = iseg_next) { + GeodesicBufferCommand command = m_buffer_commands.get(iseg); + iseg_next = command.m_next; + int count = 1; + GeodesicBufferCommand command_next = null; + while (iseg_next != iseg) {// find next segement + command_next = m_buffer_commands.get(iseg_next); + if ((command_next.m_type & GeodesicBufferCommand.Flags.enum_connection) != 0) + break; + + iseg_next = command_next.m_next; + count++; + } + + if (count == 1) { + // Next segment starts where this one ends. Skip this case as it + // is simple. + assert (command.m_to.isEqual(command_next.m_from, 0.01)); // assert (command.m_to.isEqual(command_next.m_from)); - continue; - } - - if ((command.m_type & command_next.m_type) == GeodesicBufferCommand.Flags.enum_line) {// simplest - // cleanup - // - - // intersect - // lines - if (m_helper_line_1 == null) { - m_helper_line_1 = new Line(); - m_helper_line_2 = new Line(); - } - m_helper_line_1.setStartXY(command.m_from); - m_helper_line_1.setEndXY(command.m_to); - m_helper_line_2.setStartXY(command_next.m_from); - m_helper_line_2.setEndXY(command_next.m_to); - - int count_ = m_helper_line_1.intersect(m_helper_line_2, - m_helper_array, null, null, m_small_tolerance); - if (count_ == 1) { - command.m_to.setCoords(m_helper_array[0]); - command_next.m_from.setCoords(m_helper_array[0]); - command.m_next = iseg_next;// skip until iseg_next - command_next.m_prev = iseg; - } else if (count_ == 2) {// TODO: this case needs improvement - } - } - } - - return istart; - } - - private boolean isGap_(Point2D pt_before, Point2D pt_current, - Point2D pt_after) { - Point2D v_gap = new Point2D(); - v_gap.sub(pt_after, pt_before); - double gap_length = v_gap.length(); - double sqr_delta = m_abs_distance * m_abs_distance - gap_length - * gap_length * 0.25; - if (sqr_delta > 0) { - double delta = Math.sqrt(sqr_delta); - v_gap.normalize(); - v_gap.rightPerpendicular(); - Point2D p = new Point2D(); - p.sub(pt_current, pt_before); - double d = p.dotProduct(v_gap); - if (d + delta >= m_abs_distance) { - return true; - } - } - - return false; - } - - private int filterPath_(EditShape edit_shape, - int geom, - int dir, - boolean closed) { - // **********************!!!!!!!!!!!!!!!!!!!!!!!!!!!! - // return 1; - - boolean bConvex = true; - for (int pass = 0; pass < 1; pass++) { - boolean b_filtered = false; - int ipath = edit_shape.getFirstPath(geom); - int isize = edit_shape.getPathSize(ipath); - if (isize == 0) - return 0; - - int ncount = isize; - if (isize < 3) - return 1; - - if (closed && !edit_shape.isClosedPath(ipath))// the path is closed - // only virtually - { - ncount = isize - 1; - } - - assert (dir == 1 || dir == -1); - int ivert = edit_shape.getFirstVertex(ipath); - if (!closed) - edit_shape.getNextVertex(ivert); - - int iprev = dir > 0 ? edit_shape.getPrevVertex(ivert) : edit_shape.getNextVertex(ivert); - int inext = dir > 0 ? edit_shape.getNextVertex(ivert) : edit_shape.getPrevVertex(ivert); - int ibefore = iprev; - boolean reload = true; - - Point2D pt_current = new Point2D(), pt_after = new Point2D(), pt_before = new Point2D(), pt_before_before = new Point2D(), pt_middle = new Point2D(), pt_gap_last = new Point2D( - 0, 0); - Point2D v_after = new Point2D(), v_before = new Point2D(), v_gap = new Point2D(); - Point2D temp = new Point2D(); - double abs_d = m_abs_distance; - - // When the path is open we cannot process the first and the last - // vertices, so we process size - 2. - // When the path is closed, we can process all vertices. - int iter_count = closed ? ncount : isize - 2; - int gap_counter = 0; - for (int iter = 0; iter < iter_count; ) { - edit_shape.getXY(inext, pt_after); - - if (reload) { - edit_shape.getXY(ivert, pt_current); - edit_shape.getXY(iprev, pt_before); - ibefore = iprev; - } - - v_before.sub(pt_current, pt_before); - v_before.normalize(); - - v_after.sub(pt_after, pt_current); - v_after.normalize(); - - if (ibefore == inext) { - break; - } - - double cross = v_before.crossProduct(v_after); - double dot = v_before.dotProduct(v_after); - boolean bDoJoin = cross < 0 || (dot < 0 && cross == 0); - boolean b_write = true; - if (!bDoJoin) { - if (isGap_(pt_before, pt_current, pt_after)) { - pt_gap_last.setCoords(pt_after); - b_write = false; - ++gap_counter; - b_filtered = true; - } - - bConvex = false; - } - - if (b_write) { - if (gap_counter > 0) { - for (; ; ) {// re-test back - int ibefore_before = dir > 0 ? edit_shape - .getPrevVertex(ibefore) : edit_shape - .getNextVertex(ibefore); - if (ibefore_before == ivert) - break; - - edit_shape.getXY(ibefore_before, pt_before_before); - if (isGap_(pt_before_before, pt_before, pt_gap_last)) { - pt_before.setCoords(pt_before_before); - ibefore = ibefore_before; - b_write = false; - ++gap_counter; - continue; - } else { - if (ibefore_before != inext - && isGap_(pt_before_before, pt_before, - pt_after) - && isGap_(pt_before_before, pt_current, - pt_after)) {// now the current - // point is a part - // of the gap also. - // We retest it. - pt_before.setCoords(pt_before_before); - ibefore = ibefore_before; - b_write = false; - ++gap_counter; - } - } - break; - } - } - - if (!b_write) - continue;// retest forward - - if (gap_counter > 0) { - // remove all but one gap vertices. - int p = dir > 0 ? edit_shape.getPrevVertex(iprev) - : edit_shape.getNextVertex(iprev); - for (int i = 1; i < gap_counter; i++) { - int pp = dir > 0 ? edit_shape.getPrevVertex(p) - : edit_shape.getNextVertex(p); - edit_shape.removeVertex(p, true); - p = pp; - } - - v_gap.sub(pt_current, pt_before); - double gap_length = v_gap.length(); - double sqr_delta = abs_d * abs_d - gap_length - * gap_length * 0.25; - double delta = Math.sqrt(sqr_delta); - if (abs_d - delta > m_densify_dist * 0.5) { - pt_middle.add(pt_before, pt_current); - pt_middle.scale(0.5); - v_gap.normalize(); - v_gap.rightPerpendicular(); - temp.setCoords(v_gap); - temp.scale(abs_d - delta); - pt_middle.add(temp); - edit_shape.setXY(iprev, pt_middle); - } else { - // the gap is too short to be considered. Can close - // it with the straight segment; - edit_shape.removeVertex(iprev, true); - } - - gap_counter = 0; - } - - pt_before.setCoords(pt_current); - ibefore = ivert; - } - - pt_current.setCoords(pt_after); - iprev = ivert; - ivert = inext; - // reload = false; - inext = dir > 0 ? edit_shape.getNextVertex(ivert) : edit_shape - .getPrevVertex(ivert); - iter++; - reload = false; - } - - if (gap_counter > 0) { - int p = dir > 0 ? edit_shape.getPrevVertex(iprev) : edit_shape - .getNextVertex(iprev); - for (int i = 1; i < gap_counter; i++) { - int pp = dir > 0 ? edit_shape.getPrevVertex(p) : edit_shape - .getNextVertex(p); - edit_shape.removeVertex(p, true); - p = pp; - } - - pt_middle.add(pt_before, pt_current); - pt_middle.scale(0.5); - - v_gap.sub(pt_current, pt_before); - double gap_length = v_gap.length(); - double sqr_delta = abs_d * abs_d - gap_length * gap_length - * 0.25; - assert (sqr_delta > 0); - double delta = Math.sqrt(sqr_delta); - v_gap.normalize(); - v_gap.rightPerpendicular(); - temp.setCoords(v_gap); - temp.scale(abs_d - delta); - pt_middle.add(temp); - edit_shape.setXY(iprev, pt_middle); - } - - edit_shape.filterClosePoints(m_filter_tolerance, false, false); - - if (!b_filtered) - break; - } - - return 1; - } - - private boolean isDegeneratePath_(MultiPathImpl mp_impl, int ipath) { - if (mp_impl.getPathSize(ipath) == 1) - return true; - Envelope2D env = new Envelope2D(); - mp_impl.queryPathEnvelope2D(ipath, env); - return isDegenerateEnv2D(env); - } - - private boolean isDegenerateEnv2D(Envelope2D env2D) { - double width = GeoDist.getEnvWidth(m_spatialReference.getMajorAxis(), m_spatialReference.getEccentricitySquared(), env2D); - double height = GeoDist.getEnvHeight(m_spatialReference.getMajorAxis(), m_spatialReference.getEccentricitySquared(), env2D); - - if (Math.max(width, height) < m_densify_dist * 0.5) - return true; - - return false; - } - - private boolean isDegenerateGeometry_(Geometry geom) { - Envelope2D env2D = new Envelope2D(); - geom.queryEnvelope2D(env2D); - return isDegenerateEnv2D(env2D); - } - - private Polyline preparePolyline_(Polyline input_geom) { - // Generalize it firstly using 25% of the densification deviation as a - // criterion. - //TODO create geodetic Geodetic Generalize - Polyline generalized_polyline = (Polyline) ((OperatorGeneralize) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.Generalize)).execute( - input_geom, - m_densify_dist * 0.25, - false, - m_progress_tracker); - - int path_point_count = 0; - for (int i = 0, npath = generalized_polyline.getPathCount(); i < npath; i++) { - path_point_count = Math.max(generalized_polyline.getPathSize(i), path_point_count); - } - - if (path_point_count < 32) { - m_bfilter = false; - return generalized_polyline; - } else { - m_bfilter = true; - // If we apply a filter to the polyline, then we have to resolve all - // self intersections. - Polyline simple_polyline = (Polyline) (TopologicalOperations.planarSimplify( - generalized_polyline, - m_small_tolerance, - false, - true, - m_progress_tracker)); - // Operator_factory_local::SaveJSONToTextFileDbg("c:/temp/buffer_simplify.txt", simple_polyline, nullptr); - return simple_polyline; - } - } - - - private void addArc_(MultiPathImpl dst, - Point2D center, - Point2D arcStartPt, - Point2D arcEndPt, - boolean bStartPath, - boolean bFinishArcEndPt) { - // TODO move this logic into the constructor, eh? - int N = calcN_(4); - int real_size = ((N + 3) / 4) * 4; - double dA = (2 * Math.PI) / real_size; - - //TODO this might be good for memory allocations? - // result_mp.reserve(real_size * 4); - - // center point - double lamCenter = center.x * DEG_TO_RAD; - double phiCenter = center.y * DEG_TO_RAD; - - double startAzimuth = 0.0; - - if (arcStartPt != null && arcEndPt != null) { - PeDouble az12 = new PeDouble(); - GeoDist.geodesic_distance_ngs( - m_spatialReference.getMajorAxis(), m_spatialReference.getEccentricitySquared(), lamCenter, phiCenter, - arcStartPt.x * DEG_TO_RAD, arcStartPt.y * DEG_TO_RAD, - null, az12, null); - startAzimuth = az12.val; - - GeoDist.geodesic_distance_ngs( - m_spatialReference.getMajorAxis(), m_spatialReference.getEccentricitySquared(), lamCenter, phiCenter, - arcEndPt.x * DEG_TO_RAD, arcEndPt.y * DEG_TO_RAD, - null, az12, null); - double endAzimuth = az12.val; - - if (startAzimuth < 0) - startAzimuth = Math.PI * 2 + startAzimuth; - if (endAzimuth < 0) - endAzimuth = Math.PI * 2 + endAzimuth; - double angleDifference = endAzimuth - startAzimuth; - if (endAzimuth < startAzimuth) - angleDifference = 2 * Math.PI - startAzimuth + endAzimuth; - - double ratio = angleDifference / (2 * Math.PI); - real_size = (int) Math.floor(real_size * ratio); - // change the angle to be distributed about the real_size interval - dA = angleDifference / ((double) real_size); - } - - - PeDouble lam2 = new PeDouble(); - PeDouble phi2 = new PeDouble(); - - if (bStartPath) { - GeoDist.geodesic_forward(m_spatialReference.getMajorAxis(), m_spatialReference.getEccentricitySquared(), lamCenter, phiCenter, m_abs_distance, startAzimuth, lam2, phi2); - dst.startPath(lam2.val * RAD_TO_DEG, phi2.val * RAD_TO_DEG); - if (arcEndPt == null) - arcEndPt = new Point2D(lam2.val * RAD_TO_DEG, phi2.val * RAD_TO_DEG); - } - startAzimuth += dA; - - for (int i = 1; i < real_size; i++) { - GeoDist.geodesic_forward(m_spatialReference.getMajorAxis(), m_spatialReference.getEccentricitySquared(), lamCenter, phiCenter, m_abs_distance, startAzimuth, lam2, phi2); - dst.lineTo(lam2.val * RAD_TO_DEG, phi2.val * RAD_TO_DEG); - startAzimuth += dA; - } - - if (bFinishArcEndPt) { - dst.lineTo(arcEndPt); - } - } - - - private void addCircle_(MultiPathImpl dst, Point point) { - addArc_(dst, point.getXY(), null, null, true, true); - } - - // Planar and Geodesic are equivalent - private static Polygon setWeakSimple_(Polygon poly) { - ((MultiPathImpl) poly._getImpl()).setIsSimple(MultiVertexGeometryImpl.GeometryXSimple.Weak, 0.0, false); - return poly; - } - - // Planar and Geodesic are equivalent - private Polygon setStrongSimple_(Polygon poly) { - ((MultiPathImpl) poly._getImpl()).setIsSimple(MultiVertexGeometryImpl.GeometryXSimple.Strong, m_tolerance, false); - ((MultiPathImpl) poly._getImpl())._updateOGCFlags(); - return poly; - } + continue; + } + + if ((command.m_type & command_next.m_type) == GeodesicBufferCommand.Flags.enum_line) {// simplest + // cleanup + // - + // intersect + // lines + if (m_helper_line_1 == null) { + m_helper_line_1 = new Line(); + m_helper_line_2 = new Line(); + } + m_helper_line_1.setStartXY(command.m_from); + m_helper_line_1.setEndXY(command.m_to); + m_helper_line_2.setStartXY(command_next.m_from); + m_helper_line_2.setEndXY(command_next.m_to); + + int count_ = m_helper_line_1.intersect(m_helper_line_2, + m_helper_array, null, null, m_small_tolerance); + if (count_ == 1) { + command.m_to.setCoords(m_helper_array[0]); + command_next.m_from.setCoords(m_helper_array[0]); + command.m_next = iseg_next;// skip until iseg_next + command_next.m_prev = iseg; + } else if (count_ == 2) {// TODO: this case needs improvement + } + } + } + + return istart; + } + + private boolean isGap_(Point2D pt_before, Point2D pt_current, + Point2D pt_after) { + Point2D v_gap = new Point2D(); + v_gap.sub(pt_after, pt_before); + double gap_length = v_gap.length(); + double sqr_delta = m_abs_distance * m_abs_distance - gap_length + * gap_length * 0.25; + if (sqr_delta > 0) { + double delta = Math.sqrt(sqr_delta); + v_gap.normalize(); + v_gap.rightPerpendicular(); + Point2D p = new Point2D(); + p.sub(pt_current, pt_before); + double d = p.dotProduct(v_gap); + if (d + delta >= m_abs_distance) { + return true; + } + } + + return false; + } + + private int filterPath_(EditShape edit_shape, + int geom, + int dir, + boolean closed) { + // **********************!!!!!!!!!!!!!!!!!!!!!!!!!!!! + // return 1; + + boolean bConvex = true; + for (int pass = 0; pass < 1; pass++) { + boolean b_filtered = false; + int ipath = edit_shape.getFirstPath(geom); + int isize = edit_shape.getPathSize(ipath); + if (isize == 0) + return 0; + + int ncount = isize; + if (isize < 3) + return 1; + + if (closed && !edit_shape.isClosedPath(ipath))// the path is closed + // only virtually + { + ncount = isize - 1; + } + + assert (dir == 1 || dir == -1); + int ivert = edit_shape.getFirstVertex(ipath); + if (!closed) + edit_shape.getNextVertex(ivert); + + int iprev = dir > 0 ? edit_shape.getPrevVertex(ivert) : edit_shape.getNextVertex(ivert); + int inext = dir > 0 ? edit_shape.getNextVertex(ivert) : edit_shape.getPrevVertex(ivert); + int ibefore = iprev; + boolean reload = true; + + Point2D pt_current = new Point2D(), pt_after = new Point2D(), pt_before = new Point2D(), pt_before_before = new Point2D(), pt_middle = new Point2D(), pt_gap_last = new Point2D( + 0, 0); + Point2D v_after = new Point2D(), v_before = new Point2D(), v_gap = new Point2D(); + Point2D temp = new Point2D(); + double abs_d = m_abs_distance; + + // When the path is open we cannot process the first and the last + // vertices, so we process size - 2. + // When the path is closed, we can process all vertices. + int iter_count = closed ? ncount : isize - 2; + int gap_counter = 0; + for (int iter = 0; iter < iter_count; ) { + edit_shape.getXY(inext, pt_after); + + if (reload) { + edit_shape.getXY(ivert, pt_current); + edit_shape.getXY(iprev, pt_before); + ibefore = iprev; + } + + v_before.sub(pt_current, pt_before); + v_before.normalize(); + + v_after.sub(pt_after, pt_current); + v_after.normalize(); + + if (ibefore == inext) { + break; + } + + double cross = v_before.crossProduct(v_after); + double dot = v_before.dotProduct(v_after); + boolean bDoJoin = cross < 0 || (dot < 0 && cross == 0); + boolean b_write = true; + if (!bDoJoin) { + if (isGap_(pt_before, pt_current, pt_after)) { + pt_gap_last.setCoords(pt_after); + b_write = false; + ++gap_counter; + b_filtered = true; + } + + bConvex = false; + } + + if (b_write) { + if (gap_counter > 0) { + for (; ; ) {// re-test back + int ibefore_before = dir > 0 ? edit_shape + .getPrevVertex(ibefore) : edit_shape + .getNextVertex(ibefore); + if (ibefore_before == ivert) + break; + + edit_shape.getXY(ibefore_before, pt_before_before); + if (isGap_(pt_before_before, pt_before, pt_gap_last)) { + pt_before.setCoords(pt_before_before); + ibefore = ibefore_before; + b_write = false; + ++gap_counter; + continue; + } else { + if (ibefore_before != inext + && isGap_(pt_before_before, pt_before, + pt_after) + && isGap_(pt_before_before, pt_current, + pt_after)) {// now the current + // point is a part + // of the gap also. + // We retest it. + pt_before.setCoords(pt_before_before); + ibefore = ibefore_before; + b_write = false; + ++gap_counter; + } + } + break; + } + } + + if (!b_write) + continue;// retest forward + + if (gap_counter > 0) { + // remove all but one gap vertices. + int p = dir > 0 ? edit_shape.getPrevVertex(iprev) + : edit_shape.getNextVertex(iprev); + for (int i = 1; i < gap_counter; i++) { + int pp = dir > 0 ? edit_shape.getPrevVertex(p) + : edit_shape.getNextVertex(p); + edit_shape.removeVertex(p, true); + p = pp; + } + + v_gap.sub(pt_current, pt_before); + double gap_length = v_gap.length(); + double sqr_delta = abs_d * abs_d - gap_length + * gap_length * 0.25; + double delta = Math.sqrt(sqr_delta); + if (abs_d - delta > m_densify_dist * 0.5) { + pt_middle.add(pt_before, pt_current); + pt_middle.scale(0.5); + v_gap.normalize(); + v_gap.rightPerpendicular(); + temp.setCoords(v_gap); + temp.scale(abs_d - delta); + pt_middle.add(temp); + edit_shape.setXY(iprev, pt_middle); + } else { + // the gap is too short to be considered. Can close + // it with the straight segment; + edit_shape.removeVertex(iprev, true); + } + + gap_counter = 0; + } + + pt_before.setCoords(pt_current); + ibefore = ivert; + } + + pt_current.setCoords(pt_after); + iprev = ivert; + ivert = inext; + // reload = false; + inext = dir > 0 ? edit_shape.getNextVertex(ivert) : edit_shape + .getPrevVertex(ivert); + iter++; + reload = false; + } + + if (gap_counter > 0) { + int p = dir > 0 ? edit_shape.getPrevVertex(iprev) : edit_shape + .getNextVertex(iprev); + for (int i = 1; i < gap_counter; i++) { + int pp = dir > 0 ? edit_shape.getPrevVertex(p) : edit_shape + .getNextVertex(p); + edit_shape.removeVertex(p, true); + p = pp; + } + + pt_middle.add(pt_before, pt_current); + pt_middle.scale(0.5); + + v_gap.sub(pt_current, pt_before); + double gap_length = v_gap.length(); + double sqr_delta = abs_d * abs_d - gap_length * gap_length + * 0.25; + assert (sqr_delta > 0); + double delta = Math.sqrt(sqr_delta); + v_gap.normalize(); + v_gap.rightPerpendicular(); + temp.setCoords(v_gap); + temp.scale(abs_d - delta); + pt_middle.add(temp); + edit_shape.setXY(iprev, pt_middle); + } + + edit_shape.filterClosePoints(m_filter_tolerance, false, false); + + if (!b_filtered) + break; + } + + return 1; + } + + private boolean isDegeneratePath_(MultiPathImpl mp_impl, int ipath) { + if (mp_impl.getPathSize(ipath) == 1) + return true; + Envelope2D env = new Envelope2D(); + mp_impl.queryPathEnvelope2D(ipath, env); + return isDegenerateEnv2D(env); + } + + private boolean isDegenerateEnv2D(Envelope2D env2D) { + double width = GeoDist.getEnvWidth(m_spatialReference.getMajorAxis(), m_spatialReference.getEccentricitySquared(), env2D); + double height = GeoDist.getEnvHeight(m_spatialReference.getMajorAxis(), m_spatialReference.getEccentricitySquared(), env2D); + + if (Math.max(width, height) < m_densify_dist * 0.5) + return true; + + return false; + } + + private boolean isDegenerateGeometry_(Geometry geom) { + Envelope2D env2D = new Envelope2D(); + geom.queryEnvelope2D(env2D); + return isDegenerateEnv2D(env2D); + } + + private Polyline preparePolyline_(Polyline input_geom) { + // Generalize it firstly using 25% of the densification deviation as a + // criterion. + //TODO create geodetic Geodetic Generalize + Polyline generalized_polyline = (Polyline) ((OperatorGeneralize) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.Generalize)).execute( + input_geom, + m_densify_dist * 0.25, + false, + m_progress_tracker); + + int path_point_count = 0; + for (int i = 0, npath = generalized_polyline.getPathCount(); i < npath; i++) { + path_point_count = Math.max(generalized_polyline.getPathSize(i), path_point_count); + } + + if (path_point_count < 32) { + m_bfilter = false; + return generalized_polyline; + } else { + m_bfilter = true; + // If we apply a filter to the polyline, then we have to resolve all + // self intersections. + Polyline simple_polyline = (Polyline) (TopologicalOperations.planarSimplify( + generalized_polyline, + m_small_tolerance, + false, + true, + m_progress_tracker)); + // Operator_factory_local::SaveJSONToTextFileDbg("c:/temp/buffer_simplify.txt", simple_polyline, nullptr); + return simple_polyline; + } + } + + + private void addArc_(MultiPathImpl dst, + Point2D center, + Point2D arcStartPt, + Point2D arcEndPt, + boolean bStartPath, + boolean bFinishArcEndPt) { + // TODO move this logic into the constructor, eh? + int N = calcN_(4); + int real_size = ((N + 3) / 4) * 4; + double dA = (2 * Math.PI) / real_size; + + //TODO this might be good for memory allocations? + // result_mp.reserve(real_size * 4); + + // center point + double lamCenter = center.x * DEG_TO_RAD; + double phiCenter = center.y * DEG_TO_RAD; + + double startAzimuth = 0.0; + + if (arcStartPt != null && arcEndPt != null) { + PeDouble az12 = new PeDouble(); + GeoDist.geodesic_distance_ngs( + m_spatialReference.getMajorAxis(), m_spatialReference.getEccentricitySquared(), lamCenter, phiCenter, + arcStartPt.x * DEG_TO_RAD, arcStartPt.y * DEG_TO_RAD, + null, az12, null); + startAzimuth = az12.val; + + GeoDist.geodesic_distance_ngs( + m_spatialReference.getMajorAxis(), m_spatialReference.getEccentricitySquared(), lamCenter, phiCenter, + arcEndPt.x * DEG_TO_RAD, arcEndPt.y * DEG_TO_RAD, + null, az12, null); + double endAzimuth = az12.val; + + if (startAzimuth < 0) + startAzimuth = Math.PI * 2 + startAzimuth; + if (endAzimuth < 0) + endAzimuth = Math.PI * 2 + endAzimuth; + double angleDifference = endAzimuth - startAzimuth; + if (endAzimuth < startAzimuth) + angleDifference = 2 * Math.PI - startAzimuth + endAzimuth; + + double ratio = angleDifference / (2 * Math.PI); + real_size = (int) Math.floor(real_size * ratio); + // change the angle to be distributed about the real_size interval + dA = angleDifference / ((double) real_size); + } + + + PeDouble lam2 = new PeDouble(); + PeDouble phi2 = new PeDouble(); + + if (bStartPath) { + GeoDist.geodesic_forward(m_spatialReference.getMajorAxis(), m_spatialReference.getEccentricitySquared(), lamCenter, phiCenter, m_abs_distance, startAzimuth, lam2, phi2); + dst.startPath(lam2.val * RAD_TO_DEG, phi2.val * RAD_TO_DEG); + if (arcEndPt == null) + arcEndPt = new Point2D(lam2.val * RAD_TO_DEG, phi2.val * RAD_TO_DEG); + } + startAzimuth += dA; + + for (int i = 1; i < real_size; i++) { + GeoDist.geodesic_forward(m_spatialReference.getMajorAxis(), m_spatialReference.getEccentricitySquared(), lamCenter, phiCenter, m_abs_distance, startAzimuth, lam2, phi2); + dst.lineTo(lam2.val * RAD_TO_DEG, phi2.val * RAD_TO_DEG); + startAzimuth += dA; + } + + if (bFinishArcEndPt) { + dst.lineTo(arcEndPt); + } + } + + + private void addCircle_(MultiPathImpl dst, Point point) { + addArc_(dst, point.getXY(), null, null, true, true); + } + + // Planar and Geodesic are equivalent + private static Polygon setWeakSimple_(Polygon poly) { + ((MultiPathImpl) poly._getImpl()).setIsSimple(MultiVertexGeometryImpl.GeometryXSimple.Weak, 0.0, false); + return poly; + } + + // Planar and Geodesic are equivalent + private Polygon setStrongSimple_(Polygon poly) { + ((MultiPathImpl) poly._getImpl()).setIsSimple(MultiVertexGeometryImpl.GeometryXSimple.Strong, m_tolerance, false); + ((MultiPathImpl) poly._getImpl())._updateOGCFlags(); + return poly; + } } diff --git a/src/main/java/com/esri/core/geometry/GeodesicDensifier.java b/src/main/java/com/esri/core/geometry/GeodesicDensifier.java index 3ef2cd25..83750d0d 100644 --- a/src/main/java/com/esri/core/geometry/GeodesicDensifier.java +++ b/src/main/java/com/esri/core/geometry/GeodesicDensifier.java @@ -4,169 +4,169 @@ * Created by davidraleigh on 2/24/16. */ class GeodesicDensifier { - static Geometry densifyByLength(Geometry geom, SpatialReference sr, double maxLength, ProgressTracker progressTracker) { - if (geom.isEmpty() || geom.getDimension() < 1) - return geom; - - int geometryType = geom.getType().value(); - - GeodesicDensifier geodesicDensifier = new GeodesicDensifier(maxLength, sr, progressTracker); - // TODO implement IsMultiPath and remove Polygon and Polyline call to - // match Native - // if (Geometry.IsMultiPath(geometryType)) - if (geometryType == Geometry.GeometryType.Polygon) - return geodesicDensifier.densifyMultiPath((MultiPath) geom); - else if (Geometry.GeometryType.Polyline == geometryType) - return geodesicDensifier.densifyMultiPath((MultiPath) geom); - else if (Geometry.isSegment(geometryType)) - return geodesicDensifier.densifySegment((Segment) geom); - else if (geometryType == Geometry.GeometryType.Envelope) - return geodesicDensifier.densifyEnvelope((Envelope) geom); - else - // TODO fix geometry exception to match native implementation - throw GeometryException.GeometryInternalError();// GEOMTHROW(internal_error); - } - - GeodesicDensifier(double maxLength, SpatialReference sr, ProgressTracker progressTracker) { - m_startPoint = new Point(); - m_endPoint = new Point(); - m_maxLength = maxLength; - - // sr is used to define these: - m_a = sr.getMajorAxis(); // radius of spheroid for WGS_1984 - m_e2 = sr.getEccentricitySquared(); // ellipticity for WGS_1984 - m_rpu = Math.PI / 180.0; - m_dpu = 180.0 / Math.PI; - } - - private Point m_startPoint; - private Point m_endPoint; - private double m_maxLength; - private double m_a; - private double m_e2; - private double m_rpu; - private double m_dpu; - - - private double geodesicDistanceOnWGS84(Point2D startPt2D, Point2D endPt2D) { - m_startPoint.setXY(startPt2D); - m_endPoint.setXY(endPt2D); - return SpatialReferenceImpl.geodesicDistanceOnWGS84Impl(m_startPoint, m_endPoint); - } - - private double geodesicDistanceOnWGS84(Segment segment) { - m_startPoint.setXY(segment.getStartXY()); - m_endPoint.setXY(segment.getEndXY()); - return SpatialReferenceImpl.geodesicDistanceOnWGS84Impl(m_startPoint, m_endPoint); - } - - private Geometry densifySegment(Segment geom) { - double length = geodesicDistanceOnWGS84(geom); - if (length <= m_maxLength) - return geom; - - Polyline polyline = new Polyline(geom.getDescription()); - polyline.addSegment(geom, true); - return densifyMultiPath(polyline); - } - - private Geometry densifyEnvelope(Envelope geom) { - Polygon polygon = new Polygon(geom.getDescription()); - polygon.addEnvelope(geom, false); - - Envelope2D env2D = new Envelope2D(); - geom.queryEnvelope2D(env2D); - double wTop = geodesicDistanceOnWGS84(env2D.getUpperLeft(), env2D.getUpperRight()); - double wBottom = geodesicDistanceOnWGS84(env2D.getLowerLeft(), env2D.getLowerRight()); - double height = geodesicDistanceOnWGS84(env2D.getUpperLeft(), env2D.getLowerLeft());// height on right is same as left. meridians are geodesics - - if (wTop <= m_maxLength && wBottom <= m_maxLength && height <= m_maxLength) - return polygon; - - return densifyMultiPath(polygon); - } - - private Geometry densifyMultiPath(MultiPath geom) { - PeDouble distanceMeters = new PeDouble(); - PeDouble az12 = new PeDouble(); - PeDouble lam2 = new PeDouble(); - PeDouble phi2 = new PeDouble(); - - - MultiPath densifiedPoly = (MultiPath) geom.createInstance(); - SegmentIterator iter = geom.querySegmentIterator(); - while (iter.nextPath()) { - boolean bStartNewPath = true; - while (iter.hasNextSegment()) { - Segment seg = iter.nextSegment(); - if (seg.getType().value() != Geometry.GeometryType.Line) - throw new GeometryException("curve densify not implemented"); - - boolean bIsClosing = iter.isClosingSegment(); - - // also get the segment's azimuth - GeoDist.geodesic_distance_ngs( - m_a, - m_e2, - seg.getStartX() * m_rpu, - seg.getStartY() * m_rpu, - seg.getEndX() * m_rpu, - seg.getEndY() * m_rpu, - distanceMeters, - az12, - null); - - if (distanceMeters.val > m_maxLength) {// need to split - double dcount = Math.ceil(distanceMeters.val / m_maxLength); - double distInterval = distanceMeters.val / dcount; - - Point point = new Point(geom.getDescription());// LOCALREFCLASS1(Point, - // VertexDescription, - // point, - // geom.getDescription()); - if (bStartNewPath) { - bStartNewPath = false; - seg.queryStart(point); - densifiedPoly.startPath(point); - } - - int n = (int) dcount - 1; - double distanceAlongGeodesic = 0.0; - - for (int i = 0; i < n; i++) { - distanceAlongGeodesic += distInterval; - GeoDist.geodesic_forward( - m_a, - m_e2, - seg.getStartX() * m_rpu, - seg.getStartY() * m_rpu, - distanceAlongGeodesic, - az12.val, - lam2, - phi2); - - densifiedPoly.lineTo(lam2.val * m_dpu, phi2.val * m_dpu); - } - - if (!bIsClosing) { - seg.queryEnd(point); - densifiedPoly.lineTo(point); - } else { - densifiedPoly.closePathWithLine(); - } - - bStartNewPath = false; - } else { - if (!bIsClosing) - densifiedPoly.addSegment(seg, bStartNewPath); - else - densifiedPoly.closePathWithLine(); - - bStartNewPath = false; - } - } - } - - return densifiedPoly; - } + static Geometry densifyByLength(Geometry geom, SpatialReference sr, double maxLength, ProgressTracker progressTracker) { + if (geom.isEmpty() || geom.getDimension() < 1) + return geom; + + int geometryType = geom.getType().value(); + + GeodesicDensifier geodesicDensifier = new GeodesicDensifier(maxLength, sr, progressTracker); + // TODO implement IsMultiPath and remove Polygon and Polyline call to + // match Native + // if (Geometry.IsMultiPath(geometryType)) + if (geometryType == Geometry.GeometryType.Polygon) + return geodesicDensifier.densifyMultiPath((MultiPath) geom); + else if (Geometry.GeometryType.Polyline == geometryType) + return geodesicDensifier.densifyMultiPath((MultiPath) geom); + else if (Geometry.isSegment(geometryType)) + return geodesicDensifier.densifySegment((Segment) geom); + else if (geometryType == Geometry.GeometryType.Envelope) + return geodesicDensifier.densifyEnvelope((Envelope) geom); + else + // TODO fix geometry exception to match native implementation + throw GeometryException.GeometryInternalError();// GEOMTHROW(internal_error); + } + + GeodesicDensifier(double maxLength, SpatialReference sr, ProgressTracker progressTracker) { + m_startPoint = new Point(); + m_endPoint = new Point(); + m_maxLength = maxLength; + + // sr is used to define these: + m_a = sr.getMajorAxis(); // radius of spheroid for WGS_1984 + m_e2 = sr.getEccentricitySquared(); // ellipticity for WGS_1984 + m_rpu = Math.PI / 180.0; + m_dpu = 180.0 / Math.PI; + } + + private Point m_startPoint; + private Point m_endPoint; + private double m_maxLength; + private double m_a; + private double m_e2; + private double m_rpu; + private double m_dpu; + + + private double geodesicDistanceOnWGS84(Point2D startPt2D, Point2D endPt2D) { + m_startPoint.setXY(startPt2D); + m_endPoint.setXY(endPt2D); + return SpatialReferenceImpl.geodesicDistanceOnWGS84Impl(m_startPoint, m_endPoint); + } + + private double geodesicDistanceOnWGS84(Segment segment) { + m_startPoint.setXY(segment.getStartXY()); + m_endPoint.setXY(segment.getEndXY()); + return SpatialReferenceImpl.geodesicDistanceOnWGS84Impl(m_startPoint, m_endPoint); + } + + private Geometry densifySegment(Segment geom) { + double length = geodesicDistanceOnWGS84(geom); + if (length <= m_maxLength) + return geom; + + Polyline polyline = new Polyline(geom.getDescription()); + polyline.addSegment(geom, true); + return densifyMultiPath(polyline); + } + + private Geometry densifyEnvelope(Envelope geom) { + Polygon polygon = new Polygon(geom.getDescription()); + polygon.addEnvelope(geom, false); + + Envelope2D env2D = new Envelope2D(); + geom.queryEnvelope2D(env2D); + double wTop = geodesicDistanceOnWGS84(env2D.getUpperLeft(), env2D.getUpperRight()); + double wBottom = geodesicDistanceOnWGS84(env2D.getLowerLeft(), env2D.getLowerRight()); + double height = geodesicDistanceOnWGS84(env2D.getUpperLeft(), env2D.getLowerLeft());// height on right is same as left. meridians are geodesics + + if (wTop <= m_maxLength && wBottom <= m_maxLength && height <= m_maxLength) + return polygon; + + return densifyMultiPath(polygon); + } + + private Geometry densifyMultiPath(MultiPath geom) { + PeDouble distanceMeters = new PeDouble(); + PeDouble az12 = new PeDouble(); + PeDouble lam2 = new PeDouble(); + PeDouble phi2 = new PeDouble(); + + + MultiPath densifiedPoly = (MultiPath) geom.createInstance(); + SegmentIterator iter = geom.querySegmentIterator(); + while (iter.nextPath()) { + boolean bStartNewPath = true; + while (iter.hasNextSegment()) { + Segment seg = iter.nextSegment(); + if (seg.getType().value() != Geometry.GeometryType.Line) + throw new GeometryException("curve densify not implemented"); + + boolean bIsClosing = iter.isClosingSegment(); + + // also get the segment's azimuth + GeoDist.geodesic_distance_ngs( + m_a, + m_e2, + seg.getStartX() * m_rpu, + seg.getStartY() * m_rpu, + seg.getEndX() * m_rpu, + seg.getEndY() * m_rpu, + distanceMeters, + az12, + null); + + if (distanceMeters.val > m_maxLength) {// need to split + double dcount = Math.ceil(distanceMeters.val / m_maxLength); + double distInterval = distanceMeters.val / dcount; + + Point point = new Point(geom.getDescription());// LOCALREFCLASS1(Point, + // VertexDescription, + // point, + // geom.getDescription()); + if (bStartNewPath) { + bStartNewPath = false; + seg.queryStart(point); + densifiedPoly.startPath(point); + } + + int n = (int) dcount - 1; + double distanceAlongGeodesic = 0.0; + + for (int i = 0; i < n; i++) { + distanceAlongGeodesic += distInterval; + GeoDist.geodesic_forward( + m_a, + m_e2, + seg.getStartX() * m_rpu, + seg.getStartY() * m_rpu, + distanceAlongGeodesic, + az12.val, + lam2, + phi2); + + densifiedPoly.lineTo(lam2.val * m_dpu, phi2.val * m_dpu); + } + + if (!bIsClosing) { + seg.queryEnd(point); + densifiedPoly.lineTo(point); + } else { + densifiedPoly.closePathWithLine(); + } + + bStartNewPath = false; + } else { + if (!bIsClosing) + densifiedPoly.addSegment(seg, bStartNewPath); + else + densifiedPoly.closePathWithLine(); + + bStartNewPath = false; + } + } + } + + return densifiedPoly; + } } diff --git a/src/main/java/com/esri/core/geometry/GeodeticCurveType.java b/src/main/java/com/esri/core/geometry/GeodeticCurveType.java index 125d680f..f534ca29 100644 --- a/src/main/java/com/esri/core/geometry/GeodeticCurveType.java +++ b/src/main/java/com/esri/core/geometry/GeodeticCurveType.java @@ -27,25 +27,25 @@ * Values for use in Geodetic length and area calculations */ public interface GeodeticCurveType { - /** - * Shortest distance between two points on an ellipsoide - */ - int Geodesic = 0; - /** - * A line of constant bearing or azimuth. Also known as a rhmub line - */ - public final static int Loxodrome = 1; - /** - * The line on a spheroid defined along the intersection at the surface by a - * plane that passes through the center of the spheroid. When the spheroid - * flattening is equal to zero (sphere) then a Great Elliptic is a Great - * Circle - */ - public final static int GreatElliptic = 2; - public final static int NormalSection = 3; - /*The ShapePreserving type means the segments shapes are preserved in the spatial reference where they are defined. - *The behavior of the ShapePreserving type can be emulated by densifying the geometry with a small step, and then calling a geodetic method - *using Geodesic or GreatElliptic curve types. - */ - public final static int ShapePreserving = 4; + /** + * Shortest distance between two points on an ellipsoide + */ + int Geodesic = 0; + /** + * A line of constant bearing or azimuth. Also known as a rhmub line + */ + public final static int Loxodrome = 1; + /** + * The line on a spheroid defined along the intersection at the surface by a + * plane that passes through the center of the spheroid. When the spheroid + * flattening is equal to zero (sphere) then a Great Elliptic is a Great + * Circle + */ + public final static int GreatElliptic = 2; + public final static int NormalSection = 3; + /*The ShapePreserving type means the segments shapes are preserved in the spatial reference where they are defined. + *The behavior of the ShapePreserving type can be emulated by densifying the geometry with a small step, and then calling a geodetic method + *using Geodesic or GreatElliptic curve types. + */ + public final static int ShapePreserving = 4; } diff --git a/src/main/java/com/esri/core/geometry/Geometry.java b/src/main/java/com/esri/core/geometry/Geometry.java index f293749b..f4d0a864 100644 --- a/src/main/java/com/esri/core/geometry/Geometry.java +++ b/src/main/java/com/esri/core/geometry/Geometry.java @@ -35,118 +35,118 @@ * objects that define a spatial location and and associated geometric shape. */ public abstract class Geometry implements Serializable { - VertexDescription m_description; - volatile int m_touchFlag; - - Geometry() { - m_description = null; - m_touchFlag = 0; - } - - /** - * Geometry types - */ - static public interface GeometryType { - public final static int Unknown = 0; - public final static int Point = 1 + 0x20; // points - public final static int Line = 2 + 0x40 + 0x100; // lines, segment - final static int Bezier = 3 + 0x40 + 0x100; // lines, segment - final static int EllipticArc = 4 + 0x40 + 0x100; // lines, segment - public final static int Envelope = 5 + 0x40 + 0x80; // lines, areas - public final static int MultiPoint = 6 + 0x20 + 0x200; // points, - // multivertex - public final static int Polyline = 7 + 0x40 + 0x200 + 0x400; // lines, - // multivertex, - // multipath - public final static int Polygon = 8 + 0x40 + 0x80 + 0x200 + 0x400; - } - - /** - * The type of this geometry. - */ - static public enum Type { - /** - * Used to indicate that the geometry type is not known before executing - * a method. - */ - Unknown(GeometryType.Unknown), - /** - * The value representing a point as geometry type. - */ - - Point(GeometryType.Point), - /** - * The value representing a line as geometry type. - */ - - Line(GeometryType.Line), - /** - * The value representing an envelope as geometry type. - */ - - Envelope(GeometryType.Envelope), - /** - * The value representing a multipoint as geometry type. - */ - - MultiPoint(GeometryType.MultiPoint), - /** - * The value representing a polyline as geometry type. - */ - - Polyline(GeometryType.Polyline), - /** - * The value representing a polygon as geometry type. - */ - - Polygon(GeometryType.Polygon); - - private int enumValue; - - /** - * Returns the integer representation of the enumeration value. - */ - public int value() { - return enumValue; - } - - Type(int val) { - enumValue = val; - } - - static public Geometry.Type intToType(int geometryType) { - Geometry.Type[] v = Geometry.Type.values(); - for (int i = 0; i < v.length; i++) { - if (v[i].value() == geometryType) - return v[i]; - } - - throw new IllegalArgumentException(); - } - } - - /** - * Returns the geometry type. - * - * @return Returns the geometry type. - */ - public abstract Geometry.Type getType(); - - /** - * Returns the topological dimension of the geometry object based on the - * geometry's type. - *

- * Returns 0 for point and multipoint. - *

- * Returns 1 for lines and polylines. - *

- * Returns 2 for polygons and envelopes - *

- * Returns 3 for objects with volume - * - * @return Returns the integer value of the dimension of geometry. - */ - public abstract int getDimension(); + VertexDescription m_description; + volatile int m_touchFlag; + + Geometry() { + m_description = null; + m_touchFlag = 0; + } + + /** + * Geometry types + */ + static public interface GeometryType { + public final static int Unknown = 0; + public final static int Point = 1 + 0x20; // points + public final static int Line = 2 + 0x40 + 0x100; // lines, segment + final static int Bezier = 3 + 0x40 + 0x100; // lines, segment + final static int EllipticArc = 4 + 0x40 + 0x100; // lines, segment + public final static int Envelope = 5 + 0x40 + 0x80; // lines, areas + public final static int MultiPoint = 6 + 0x20 + 0x200; // points, + // multivertex + public final static int Polyline = 7 + 0x40 + 0x200 + 0x400; // lines, + // multivertex, + // multipath + public final static int Polygon = 8 + 0x40 + 0x80 + 0x200 + 0x400; + } + + /** + * The type of this geometry. + */ + static public enum Type { + /** + * Used to indicate that the geometry type is not known before executing + * a method. + */ + Unknown(GeometryType.Unknown), + /** + * The value representing a point as geometry type. + */ + + Point(GeometryType.Point), + /** + * The value representing a line as geometry type. + */ + + Line(GeometryType.Line), + /** + * The value representing an envelope as geometry type. + */ + + Envelope(GeometryType.Envelope), + /** + * The value representing a multipoint as geometry type. + */ + + MultiPoint(GeometryType.MultiPoint), + /** + * The value representing a polyline as geometry type. + */ + + Polyline(GeometryType.Polyline), + /** + * The value representing a polygon as geometry type. + */ + + Polygon(GeometryType.Polygon); + + private int enumValue; + + /** + * Returns the integer representation of the enumeration value. + */ + public int value() { + return enumValue; + } + + Type(int val) { + enumValue = val; + } + + static public Geometry.Type intToType(int geometryType) { + Geometry.Type[] v = Geometry.Type.values(); + for (int i = 0; i < v.length; i++) { + if (v[i].value() == geometryType) + return v[i]; + } + + throw new IllegalArgumentException(); + } + } + + /** + * Returns the geometry type. + * + * @return Returns the geometry type. + */ + public abstract Geometry.Type getType(); + + /** + * Returns the topological dimension of the geometry object based on the + * geometry's type. + *

+ * Returns 0 for point and multipoint. + *

+ * Returns 1 for lines and polylines. + *

+ * Returns 2 for polygons and envelopes + *

+ * Returns 3 for objects with volume + * + * @return Returns the integer value of the dimension of geometry. + */ + public abstract int getDimension(); /** * Returns an estimate of this object size in bytes. @@ -159,13 +159,13 @@ static public Geometry.Type intToType(int geometryType) { */ public abstract long estimateMemorySize(); - protected static long estimateMemorySize(double[] attributes) - { + protected static long estimateMemorySize(double[] attributes) { return attributes != null ? sizeOfDoubleArray(attributes.length) : 0; } /** * Returns the VertexDescription of this geometry. + * * @return VertexDescription */ public VertexDescription getDescription() { @@ -175,6 +175,7 @@ public VertexDescription getDescription() { /** * Assigns the new VertexDescription by adding or dropping attributes. The * Geometry will have the src description as a result. + * * @param src VertexDescription to assign. */ public void assignVertexDescription(VertexDescription src) { @@ -182,15 +183,16 @@ public void assignVertexDescription(VertexDescription src) { if (src == m_description) return; - _assignVertexDescriptionImpl(src); - } + _assignVertexDescriptionImpl(src); + } - protected abstract void _assignVertexDescriptionImpl(VertexDescription src); + protected abstract void _assignVertexDescriptionImpl(VertexDescription src); /** * Merges the new VertexDescription by adding missing attributes from the * src. The Geometry will have a union of the current and the src * descriptions. + * * @param src VertexDescription to merge. */ public void mergeVertexDescription(VertexDescription src) { @@ -198,16 +200,17 @@ public void mergeVertexDescription(VertexDescription src) { if (src == m_description) return; - // check if we need to do anything (if the src has same attributes) - VertexDescription newdescription = VertexDescriptionDesignerImpl.getMergedVertexDescription(m_description, src); - if (newdescription == m_description) - return; + // check if we need to do anything (if the src has same attributes) + VertexDescription newdescription = VertexDescriptionDesignerImpl.getMergedVertexDescription(m_description, src); + if (newdescription == m_description) + return; - _assignVertexDescriptionImpl(newdescription); - } + _assignVertexDescriptionImpl(newdescription); + } /** * A shortcut for getDescription().hasAttribute() + * * @param semantics The VertexDescription.Semantics to check. * @return Return true if the attribute is present. */ @@ -217,14 +220,14 @@ public boolean hasAttribute(int semantics) { /** * Adds a new attribute to the Geometry. - * + * * @param semantics The VertexDescription.Semantics to add. */ public void addAttribute(int semantics) { _touch(); if (m_description.hasAttribute(semantics)) return; - + VertexDescription newvd = VertexDescriptionDesignerImpl.getMergedVertexDescription(m_description, semantics); _assignVertexDescriptionImpl(newvd); } @@ -234,6 +237,7 @@ public void addAttribute(int semantics) { * equivalent to setting the attribute to the default value for each vertex, * However, it is faster and the result Geometry has smaller memory * footprint and smaller size when persisted. + * * @param semantics The VertexDescription.Semantics to drop. */ public void dropAttribute(int semantics) { @@ -241,44 +245,45 @@ public void dropAttribute(int semantics) { if (!m_description.hasAttribute(semantics)) return; - VertexDescription newvd = VertexDescriptionDesignerImpl.removeSemanticsFromVertexDescription(m_description, semantics); - _assignVertexDescriptionImpl(newvd); - } + VertexDescription newvd = VertexDescriptionDesignerImpl.removeSemanticsFromVertexDescription(m_description, semantics); + _assignVertexDescriptionImpl(newvd); + } - /** - * Drops all attributes from the Geometry with exception of POSITON. - */ - public void dropAllAttributes() { - assignVertexDescription(VertexDescriptionDesignerImpl - .getDefaultDescriptor2D()); - } + /** + * Drops all attributes from the Geometry with exception of POSITON. + */ + public void dropAllAttributes() { + assignVertexDescription(VertexDescriptionDesignerImpl + .getDefaultDescriptor2D()); + } /** * Returns the min and max attribute values at the ordinate of the Geometry. + * * @param semantics The semantics of the interval. - * @param ordinate The ordinate of the interval. + * @param ordinate The ordinate of the interval. * @return The interval. */ public abstract Envelope1D queryInterval(int semantics, int ordinate); - /** - * Returns the axis aligned bounding box of the geometry. - * - * @param env The envelope to return the result in. - */ - public abstract void queryEnvelope(Envelope env); + /** + * Returns the axis aligned bounding box of the geometry. + * + * @param env The envelope to return the result in. + */ + public abstract void queryEnvelope(Envelope env); /** * Returns tight bbox of the Geometry in X, Y plane. - * @param env - * The envelope to return the result in. + * + * @param env The envelope to return the result in. */ public abstract void queryEnvelope2D(Envelope2D env); /** * Returns tight bbox of the Geometry in 3D. - * @param env - * The envelope to return the result in. + * + * @param env The envelope to return the result in. */ abstract void queryEnvelope3D(Envelope3D env); @@ -286,8 +291,8 @@ public void dropAllAttributes() { * Returns the conservative bbox of the Geometry in X, Y plane. This is a * faster method than QueryEnvelope2D. However, the bbox could be larger * than the tight box. - * @param env - * The envelope to return the result in. + * + * @param env The envelope to return the result in. */ public void queryLooseEnvelope2D(Envelope2D env) { queryEnvelope2D(env); @@ -297,43 +302,44 @@ public void queryLooseEnvelope2D(Envelope2D env) { * Returns tight conservative box of the Geometry in 3D. This is a faster * method than the QueryEnvelope3D. However, the box could be larger than * the tight box. - * @param env - * The envelope to return the result in. + * + * @param env The envelope to return the result in. */ void queryLooseEnvelope3D(Envelope3D env) { queryEnvelope3D(env); } - /** - * IsEmpty returns TRUE when the Geometry object does not contain geometric - * information beyond its original initialization state. - * - * @return boolean Returns TRUE if this geometry is empty. - */ - public abstract boolean isEmpty(); + /** + * IsEmpty returns TRUE when the Geometry object does not contain geometric + * information beyond its original initialization state. + * + * @return boolean Returns TRUE if this geometry is empty. + */ + public abstract boolean isEmpty(); - /** - * Returns the geometry to its original initialization state by releasing - * all data referenced by the geometry. - */ - public abstract void setEmpty(); + /** + * Returns the geometry to its original initialization state by releasing + * all data referenced by the geometry. + */ + public abstract void setEmpty(); - /** - * Applies 2D affine transformation in XY plane. - * - * @param transform The affine transformation to be applied to this geometry. - */ - public abstract void applyTransformation(Transformation2D transform); + /** + * Applies 2D affine transformation in XY plane. + * + * @param transform The affine transformation to be applied to this geometry. + */ + public abstract void applyTransformation(Transformation2D transform); - /** - * Applies 3D affine transformation. Adds Z attribute if it is missing. - * - * @param transform The affine transformation to be applied to this geometry. - */ - abstract void applyTransformation(Transformation3D transform); + /** + * Applies 3D affine transformation. Adds Z attribute if it is missing. + * + * @param transform The affine transformation to be applied to this geometry. + */ + abstract void applyTransformation(Transformation3D transform); /** * Creates an instance of an empty geometry of the same type. + * * @return The new instance. */ public abstract Geometry createInstance(); @@ -341,196 +347,197 @@ void queryLooseEnvelope3D(Envelope3D env) { /** * Copies this geometry to another geometry of the same type. The result * geometry is an exact copy. + * * @param dst The geometry instance to copy to. - * @exception GeometryException - * invalid_argument if the geometry is of different type. + * @throws GeometryException invalid_argument if the geometry is of different type. */ public abstract void copyTo(Geometry dst); - /** - * Calculates the area of the geometry. If the spatial reference is a - * Geographic Coordinate System (WGS84) then the 2D area calculation is - * defined in angular units. - * - * @return A double value representing the 2D area of the geometry. - */ - public double calculateArea2D() { - return 0; - } - - /** - * Calculates the length of the geometry. If the spatial reference is a - * Geographic Coordinate System (a system where coordinates are defined - * using angular units such as longitude and latitude) then the 2D distance - * calculation is returned in angular units. In cases where length must be - * calculated on a Geographic Coordinate System consider the using the - * geodeticLength method on the {@link GeometryEngine} - * - * @return A double value representing the 2D length of the geometry. - */ - public double calculateLength2D() { - return 0; - } - - protected Object _getImpl() { - throw new RuntimeException("invalid call"); - } - - /** - * Adds the Z attribute to this Geometry - */ - void addZ() { - addAttribute(VertexDescription.Semantics.Z); - } - - /** - * Returns true if this Geometry has the Z attribute - * - * @return true if this Geometry has the Z attribute - */ - public boolean hasZ() { - return hasAttribute(VertexDescription.Semantics.Z); - } - - /** - * Adds the M attribute to this Geometry - */ - public void addM() { - addAttribute(VertexDescription.Semantics.M); - } - - /** - * Returns true if this Geometry has an M attribute - * - * @return true if this Geometry has an M attribute - */ - public boolean hasM() { - return hasAttribute(VertexDescription.Semantics.M); - } - - /** - * Adds the ID attribute to this Geometry - */ - public void addID() { - addAttribute(VertexDescription.Semantics.ID); - } - - /** - * Returns true if this Geometry has an ID attribute - * - * @return true if this Geometry has an ID attribute - */ - public boolean hasID() { - return hasAttribute(VertexDescription.Semantics.ID); - } - - /** - * Returns this geometry's dimension. - *

- * Returns 0 for point and multipoint. - *

- * Returns 1 for lines and polylines. - *

- * Returns 2 for polygons and envelopes - *

- * Returns 3 for objects with volume - * - * @param type The integer value from geometry enumeration. You can use the - * method {@link Type#value()} to get at the integer value. - * @return The integer dimension of this geometry. - */ - public static int getDimensionFromType(int type) { - return (((type & (0x40 | 0x80)) >> 6) + 1) >> 1; - } - - /** - * Indicates if the integer value of the enumeration is a point type - * (dimension 0). - * - * @param type The integer value from geometry enumeration. You can use the - * method {@link Type#value()} to get at the integer value. - * @return TRUE if the geometry is a point. - */ - public static boolean isPoint(int type) { - return (type & 0x20) != 0; - } - - /** - * Indicates if the integer value of the enumeration is linear (dimension - * 1). - * - * @param type The integer value from geometry enumeration. You can use the - * method {@link Type#value()} to get at the integer value. - * @return TRUE if the geometry is a line. - */ - public static boolean isLinear(int type) { - return (type & 0x40) != 0; - } - - /** - * Indicates if the integer value of the enumeration is an area (dimension - * 2). - * - * @param type The integer value from geometry enumeration. You can use the - * method {@link Type#value()} to get at the integer value. - * @return TRUE if the geometry is a polygon. - */ - public static boolean isArea(int type) { - return (type & 0x80) != 0; - } - - /** - * Indicates if the integer value of the enumeration is a segment. - * - * @param type The integer value from geometry enumeration. You can use the - * method {@link Type#value()} to get at the integer value. - * @return TRUE if the geometry is a segment. - */ - public static boolean isSegment(int type) { - return (type & 0x100) != 0; - } - - /** - * Indicates if the integer value of the enumeration is a multivertex (ie, - * multipoint, line, or area). - * - * @param type The integer value from geometry enumeration. You can use the - * method {@link Type#value()} to get at the integer value. - * @return TRUE if the geometry has multiple vertices. - */ - public static boolean isMultiVertex(int type) { - return (type & 0x200) != 0; - } - - /** - * Indicates if the integer value of the enumeration is a multipath (ie, - * line or area). - * - * @param type The integer value from geometry enumeration. You can use the - * method {@link Type#value()} to get at the integer value. - * @return TRUE if the geometry is a multipath. - */ - public static boolean isMultiPath(int type) { - return (type & 0x400) != 0; - } - - /** - * Creates a copy of the geometry. - * - * @return Returns a copy of this geometry. - */ - public Geometry copy() { - Geometry geom = createInstance(); - this.copyTo(geom); - return geom; - } + /** + * Calculates the area of the geometry. If the spatial reference is a + * Geographic Coordinate System (WGS84) then the 2D area calculation is + * defined in angular units. + * + * @return A double value representing the 2D area of the geometry. + */ + public double calculateArea2D() { + return 0; + } /** - * Returns boundary of this geometry. + * Calculates the length of the geometry. If the spatial reference is a + * Geographic Coordinate System (a system where coordinates are defined + * using angular units such as longitude and latitude) then the 2D distance + * calculation is returned in angular units. In cases where length must be + * calculated on a Geographic Coordinate System consider the using the + * geodeticLength method on the {@link GeometryEngine} + * + * @return A double value representing the 2D length of the geometry. + */ + public double calculateLength2D() { + return 0; + } + + protected Object _getImpl() { + throw new RuntimeException("invalid call"); + } + + /** + * Adds the Z attribute to this Geometry + */ + void addZ() { + addAttribute(VertexDescription.Semantics.Z); + } + + /** + * Returns true if this Geometry has the Z attribute + * + * @return true if this Geometry has the Z attribute + */ + public boolean hasZ() { + return hasAttribute(VertexDescription.Semantics.Z); + } + + /** + * Adds the M attribute to this Geometry + */ + public void addM() { + addAttribute(VertexDescription.Semantics.M); + } + + /** + * Returns true if this Geometry has an M attribute + * + * @return true if this Geometry has an M attribute + */ + public boolean hasM() { + return hasAttribute(VertexDescription.Semantics.M); + } + + /** + * Adds the ID attribute to this Geometry + */ + public void addID() { + addAttribute(VertexDescription.Semantics.ID); + } + + /** + * Returns true if this Geometry has an ID attribute + * + * @return true if this Geometry has an ID attribute + */ + public boolean hasID() { + return hasAttribute(VertexDescription.Semantics.ID); + } + + /** + * Returns this geometry's dimension. + *

+ * Returns 0 for point and multipoint. + *

+ * Returns 1 for lines and polylines. + *

+ * Returns 2 for polygons and envelopes + *

+ * Returns 3 for objects with volume * + * @param type The integer value from geometry enumeration. You can use the + * method {@link Type#value()} to get at the integer value. + * @return The integer dimension of this geometry. + */ + public static int getDimensionFromType(int type) { + return (((type & (0x40 | 0x80)) >> 6) + 1) >> 1; + } + + /** + * Indicates if the integer value of the enumeration is a point type + * (dimension 0). + * + * @param type The integer value from geometry enumeration. You can use the + * method {@link Type#value()} to get at the integer value. + * @return TRUE if the geometry is a point. + */ + public static boolean isPoint(int type) { + return (type & 0x20) != 0; + } + + /** + * Indicates if the integer value of the enumeration is linear (dimension + * 1). + * + * @param type The integer value from geometry enumeration. You can use the + * method {@link Type#value()} to get at the integer value. + * @return TRUE if the geometry is a line. + */ + public static boolean isLinear(int type) { + return (type & 0x40) != 0; + } + + /** + * Indicates if the integer value of the enumeration is an area (dimension + * 2). + * + * @param type The integer value from geometry enumeration. You can use the + * method {@link Type#value()} to get at the integer value. + * @return TRUE if the geometry is a polygon. + */ + public static boolean isArea(int type) { + return (type & 0x80) != 0; + } + + /** + * Indicates if the integer value of the enumeration is a segment. + * + * @param type The integer value from geometry enumeration. You can use the + * method {@link Type#value()} to get at the integer value. + * @return TRUE if the geometry is a segment. + */ + public static boolean isSegment(int type) { + return (type & 0x100) != 0; + } + + /** + * Indicates if the integer value of the enumeration is a multivertex (ie, + * multipoint, line, or area). + * + * @param type The integer value from geometry enumeration. You can use the + * method {@link Type#value()} to get at the integer value. + * @return TRUE if the geometry has multiple vertices. + */ + public static boolean isMultiVertex(int type) { + return (type & 0x200) != 0; + } + + /** + * Indicates if the integer value of the enumeration is a multipath (ie, + * line or area). + * + * @param type The integer value from geometry enumeration. You can use the + * method {@link Type#value()} to get at the integer value. + * @return TRUE if the geometry is a multipath. + */ + public static boolean isMultiPath(int type) { + return (type & 0x400) != 0; + } + + /** + * Creates a copy of the geometry. + * + * @return Returns a copy of this geometry. + */ + public Geometry copy() { + Geometry geom = createInstance(); + this.copyTo(geom); + return geom; + } + + /** + * Returns boundary of this geometry. + *

* Polygon and Envelope boundary is a Polyline. For Polyline and Line, the * boundary is a Multi_point consisting of path end points. For Multi_point and * Point null is returned. + * * @return The boundary geometry. */ public abstract Geometry getBoundary(); @@ -539,96 +546,98 @@ public Geometry copy() { * Replaces NaNs in the attribute with the given value. * If the geometry is not empty, it adds the attribute if geometry does not have it yet, and replaces the values. * If the geometry is empty, it adds the attribute and does not set any values. + * * @param semantics The semantics for which to replace the NaNs. - * @param value The value to replace NaNs with. + * @param value The value to replace NaNs with. */ public abstract void replaceNaNs(int semantics, double value); - static Geometry _clone(Geometry src) { - Geometry geom = src.createInstance(); - src.copyTo(geom); - return geom; - } - - /** - * The stateFlag value changes with changes applied to this geometry. This - * allows the user to keep track of the geometry's state. - * - * @return The state of the geometry. - */ - public int getStateFlag() { - m_touchFlag &= 0x7FFFFFFF; - return m_touchFlag; - } - - // Called whenever geometry changes - synchronized void _touch() { - if (m_touchFlag >= 0) { - m_touchFlag += 0x80000001; - } - } - - /** - * Describes the degree of acceleration of the geometry. - * Acceleration usually builds a raster and a quadtree. - */ - static public enum GeometryAccelerationDegree { - /** - * mild acceleration, takes least amount of memory. (64x64x2 bit raster) - */ - enumMild, - /** - * medium acceleration, takes more memory and takes more time to accelerate, but may work faster. - * (256x256x2 bit raster and a quad tree for segments) - */ - enumMedium, - /** - * high acceleration, takes even more memory and may take - * longest time to accelerate, but may work faster than the - * other two. - * (1024x1024x2 bit raster and a quad tree for segments) - */ - enumHot - } - - Object writeReplace() throws ObjectStreamException { - Type gt = getType(); - if (gt == Geometry.Type.Point) { - PtSrlzr pt = new PtSrlzr(); - pt.setGeometryByValue((Point) this); - return pt; - } else if (gt == Geometry.Type.Envelope) { - EnvSrlzr e = new EnvSrlzr(); - e.setGeometryByValue((Envelope) this); - return e; - } else if (gt == Geometry.Type.Line) { - LnSrlzr ln = new LnSrlzr(); - ln.setGeometryByValue((Line) this); - return ln; - } - - GenericGeometrySerializer geomSerializer = new GenericGeometrySerializer(); - geomSerializer.setGeometryByValue(this); - return geomSerializer; - } - - /** - * The output of this method can be only used for debugging. It is subject to change without notice. - */ - @Override - public String toString() { - String snippet = OperatorExportToWkt.local().execute(0, this, null); - if (snippet.length() > 200) { - return snippet.substring(0, 197) + "... (" + snippet.length() + " characters)"; - } else { - return snippet; - } - } + static Geometry _clone(Geometry src) { + Geometry geom = src.createInstance(); + src.copyTo(geom); + return geom; + } + + /** + * The stateFlag value changes with changes applied to this geometry. This + * allows the user to keep track of the geometry's state. + * + * @return The state of the geometry. + */ + public int getStateFlag() { + m_touchFlag &= 0x7FFFFFFF; + return m_touchFlag; + } + + // Called whenever geometry changes + synchronized void _touch() { + if (m_touchFlag >= 0) { + m_touchFlag += 0x80000001; + } + } + + /** + * Describes the degree of acceleration of the geometry. + * Acceleration usually builds a raster and a quadtree. + */ + static public enum GeometryAccelerationDegree { + /** + * mild acceleration, takes least amount of memory. (64x64x2 bit raster) + */ + enumMild, + /** + * medium acceleration, takes more memory and takes more time to accelerate, but may work faster. + * (256x256x2 bit raster and a quad tree for segments) + */ + enumMedium, + /** + * high acceleration, takes even more memory and may take + * longest time to accelerate, but may work faster than the + * other two. + * (1024x1024x2 bit raster and a quad tree for segments) + */ + enumHot + } + + Object writeReplace() throws ObjectStreamException { + Type gt = getType(); + if (gt == Geometry.Type.Point) { + PtSrlzr pt = new PtSrlzr(); + pt.setGeometryByValue((Point) this); + return pt; + } else if (gt == Geometry.Type.Envelope) { + EnvSrlzr e = new EnvSrlzr(); + e.setGeometryByValue((Envelope) this); + return e; + } else if (gt == Geometry.Type.Line) { + LnSrlzr ln = new LnSrlzr(); + ln.setGeometryByValue((Line) this); + return ln; + } + + GenericGeometrySerializer geomSerializer = new GenericGeometrySerializer(); + geomSerializer.setGeometryByValue(this); + return geomSerializer; + } + + /** + * The output of this method can be only used for debugging. It is subject to change without notice. + */ + @Override + public String toString() { + String snippet = OperatorExportToWkt.local().execute(0, this, null); + if (snippet.length() > 200) { + return snippet.substring(0, 197) + "... (" + snippet.length() + " characters)"; + } else { + return snippet; + } + } /** * Returns count of geometry vertices: 1 for Point, 4 for Envelope, * get_point_count for MultiVertexGeometry types, 2 for segment types Returns 0 * if geometry is empty. + * * @param geom The geometry to get the vertex count for. * @return The vertex count. */ @@ -637,32 +646,32 @@ public static int vertex_count(Geometry geom) { if (Geometry.isMultiVertex(gt.value())) return ((MultiVertexGeometry) geom).getPointCount(); - if (geom.isEmpty()) - return 0; - - if (gt == Geometry.Type.Envelope) - return 4; - - if (gt == Geometry.Type.Point) - return 1; - - if (Geometry.isSegment(gt.value())) - return 2; - - throw new GeometryException("missing type"); - } - - protected SimpleStateEnum getSimpleState() { - if (getType() != Type.Polygon && getType() != Type.Polyline && getType() != Type.MultiPoint) { - return SimpleStateEnum.STRONG_SIMPLE; - } else { - if (((MultiVertexGeometryImpl)this._getImpl())._hasDirtyFlag(MultiVertexGeometryImpl.DirtyFlags.IsStrongSimple)) { - return SimpleStateEnum.STRONG_SIMPLE; - } else if (((MultiVertexGeometryImpl)this._getImpl())._hasDirtyFlag(MultiVertexGeometryImpl.DirtyFlags.IsWeakSimple)) { - return SimpleStateEnum.WEAK_SIMPLE; - } - return SimpleStateEnum.SIMPLE_UNKNOWN; - } - } + if (geom.isEmpty()) + return 0; + + if (gt == Geometry.Type.Envelope) + return 4; + + if (gt == Geometry.Type.Point) + return 1; + + if (Geometry.isSegment(gt.value())) + return 2; + + throw new GeometryException("missing type"); + } + + protected SimpleStateEnum getSimpleState() { + if (getType() != Type.Polygon && getType() != Type.Polyline && getType() != Type.MultiPoint) { + return SimpleStateEnum.STRONG_SIMPLE; + } else { + if (((MultiVertexGeometryImpl) this._getImpl())._hasDirtyFlag(MultiVertexGeometryImpl.DirtyFlags.IsStrongSimple)) { + return SimpleStateEnum.STRONG_SIMPLE; + } else if (((MultiVertexGeometryImpl) this._getImpl())._hasDirtyFlag(MultiVertexGeometryImpl.DirtyFlags.IsWeakSimple)) { + return SimpleStateEnum.WEAK_SIMPLE; + } + return SimpleStateEnum.SIMPLE_UNKNOWN; + } + } } diff --git a/src/main/java/com/esri/core/geometry/GeometryAccelerators.java b/src/main/java/com/esri/core/geometry/GeometryAccelerators.java index 4aca3b1d..8077dd74 100644 --- a/src/main/java/com/esri/core/geometry/GeometryAccelerators.java +++ b/src/main/java/com/esri/core/geometry/GeometryAccelerators.java @@ -27,63 +27,63 @@ class GeometryAccelerators { - private RasterizedGeometry2D m_rasterizedGeometry; - private QuadTreeImpl m_quad_tree; - private QuadTreeImpl m_quad_tree_for_paths; + private RasterizedGeometry2D m_rasterizedGeometry; + private QuadTreeImpl m_quad_tree; + private QuadTreeImpl m_quad_tree_for_paths; - public RasterizedGeometry2D getRasterizedGeometry() { - return m_rasterizedGeometry; - } + public RasterizedGeometry2D getRasterizedGeometry() { + return m_rasterizedGeometry; + } - public QuadTreeImpl getQuadTree() { - return m_quad_tree; - } + public QuadTreeImpl getQuadTree() { + return m_quad_tree; + } - public QuadTreeImpl getQuadTreeForPaths() { - return m_quad_tree_for_paths; - } + public QuadTreeImpl getQuadTreeForPaths() { + return m_quad_tree_for_paths; + } - void _setRasterizedGeometry(RasterizedGeometry2D rg) { - m_rasterizedGeometry = rg; - } + void _setRasterizedGeometry(RasterizedGeometry2D rg) { + m_rasterizedGeometry = rg; + } - void _setQuadTree(QuadTreeImpl quad_tree) { - m_quad_tree = quad_tree; - } + void _setQuadTree(QuadTreeImpl quad_tree) { + m_quad_tree = quad_tree; + } - void _setQuadTreeForPaths(QuadTreeImpl quad_tree) { - m_quad_tree_for_paths = quad_tree; - } + void _setQuadTreeForPaths(QuadTreeImpl quad_tree) { + m_quad_tree_for_paths = quad_tree; + } - static boolean canUseRasterizedGeometry(Geometry geom) { - if (geom.isEmpty() - || !(geom.getType() == Geometry.Type.Polyline || geom.getType() == Geometry.Type.Polygon)) { - return false; - } + static boolean canUseRasterizedGeometry(Geometry geom) { + if (geom.isEmpty() + || !(geom.getType() == Geometry.Type.Polyline || geom.getType() == Geometry.Type.Polygon)) { + return false; + } - return true; - } + return true; + } - static boolean canUseQuadTree(Geometry geom) { - if (geom.isEmpty() - || !(geom.getType() == Geometry.Type.Polyline || geom.getType() == Geometry.Type.Polygon)) { - return false; - } + static boolean canUseQuadTree(Geometry geom) { + if (geom.isEmpty() + || !(geom.getType() == Geometry.Type.Polyline || geom.getType() == Geometry.Type.Polygon)) { + return false; + } - if (((MultiVertexGeometry) geom).getPointCount() < 20) { - return false; - } + if (((MultiVertexGeometry) geom).getPointCount() < 20) { + return false; + } - return true; - } + return true; + } - static boolean canUseQuadTreeForPaths(Geometry geom) { - if (geom.isEmpty() || !(geom.getType() == Geometry.Type.Polyline || geom.getType() == Geometry.Type.Polygon)) - return false; + static boolean canUseQuadTreeForPaths(Geometry geom) { + if (geom.isEmpty() || !(geom.getType() == Geometry.Type.Polyline || geom.getType() == Geometry.Type.Polygon)) + return false; - if (((MultiVertexGeometry) geom).getPointCount() < 20) - return false; + if (((MultiVertexGeometry) geom).getPointCount() < 20) + return false; - return true; - } + return true; + } } diff --git a/src/main/java/com/esri/core/geometry/GeometryCursor.java b/src/main/java/com/esri/core/geometry/GeometryCursor.java index ab127471..725fc08b 100644 --- a/src/main/java/com/esri/core/geometry/GeometryCursor.java +++ b/src/main/java/com/esri/core/geometry/GeometryCursor.java @@ -29,45 +29,49 @@ * An abstract Geometry Cursor class. */ public abstract class GeometryCursor implements Iterator { - // TODO add count - // TODO add extent - // TODO add spatial reference - GeometryCursor m_inputGeoms = null; + // TODO add count + // TODO add extent + // TODO add spatial reference + GeometryCursor m_inputGeoms = null; - /** - * Moves the cursor to the next Geometry. Returns null when reached the end. - * The behavior of the cursor is undefined after the method returns null. - */ - public abstract Geometry next(); + /** + * Moves the cursor to the next Geometry. Returns null when reached the end. + * The behavior of the cursor is undefined after the method returns null. + */ + public abstract Geometry next(); - /** - * Returns the ID of the current geometry. The ID is propagated across the operations (when possible). - *

- * Returns an ID associated with the current Geometry. The ID is passed along and is returned by some operators to preserve relationship between the input and output geometry classes. - * It is not always possible to preserve an ID during an operation. - */ - public long getGeometryID() { - return m_inputGeoms.getGeometryID(); - } + /** + * Returns the ID of the current geometry. The ID is propagated across the operations (when possible). + *

+ * Returns an ID associated with the current Geometry. The ID is passed along and is returned by some operators to preserve relationship between the input and output geometry classes. + * It is not always possible to preserve an ID during an operation. + */ + public long getGeometryID() { + return m_inputGeoms.getGeometryID(); + } - public SimpleStateEnum getSimpleState() { return m_inputGeoms.getSimpleState(); } + public SimpleStateEnum getSimpleState() { + return m_inputGeoms.getSimpleState(); + } - public String getFeatureID() { return m_inputGeoms.getFeatureID(); } + public String getFeatureID() { + return m_inputGeoms.getFeatureID(); + } - /** - * Executes a unit of work on the cursor. - * - * @return Returns true, if there is a geometry ready to be pulled using next(). - *

- * This method is to be used together with the tick() method on the ListeningGeometryCursor. - * Call tock() for each tick() on the ListeningGeometryCursor. - */ - public boolean tock() { - return true; - } + /** + * Executes a unit of work on the cursor. + * + * @return Returns true, if there is a geometry ready to be pulled using next(). + *

+ * This method is to be used together with the tick() method on the ListeningGeometryCursor. + * Call tock() for each tick() on the ListeningGeometryCursor. + */ + public boolean tock() { + return true; + } - public boolean hasNext() { - return m_inputGeoms != null && m_inputGeoms.hasNext(); - } + public boolean hasNext() { + return m_inputGeoms != null && m_inputGeoms.hasNext(); + } } diff --git a/src/main/java/com/esri/core/geometry/GeometryCursorAppend.java b/src/main/java/com/esri/core/geometry/GeometryCursorAppend.java index 2228ddf4..37965ed6 100644 --- a/src/main/java/com/esri/core/geometry/GeometryCursorAppend.java +++ b/src/main/java/com/esri/core/geometry/GeometryCursorAppend.java @@ -25,31 +25,33 @@ public class GeometryCursorAppend extends GeometryCursor { - private GeometryCursor m_cur1; - private GeometryCursor m_cur2; - private GeometryCursor m_cur; - - public GeometryCursorAppend(GeometryCursor cur1, GeometryCursor cur2) { - m_cur1 = cur1; - m_cur2 = cur2; - m_cur = m_cur1; - } - - @Override - public boolean hasNext() { return m_cur != null && m_cur.hasNext(); } - - @Override - public Geometry next() { - Geometry g = m_cur.next(); - if (g == null && m_cur != m_cur2) { - m_cur = m_cur2; - return m_cur.next(); - } - return g; - } - - @Override - public long getGeometryID() { - return m_cur.getGeometryID(); - } + private GeometryCursor m_cur1; + private GeometryCursor m_cur2; + private GeometryCursor m_cur; + + public GeometryCursorAppend(GeometryCursor cur1, GeometryCursor cur2) { + m_cur1 = cur1; + m_cur2 = cur2; + m_cur = m_cur1; + } + + @Override + public boolean hasNext() { + return m_cur != null && m_cur.hasNext(); + } + + @Override + public Geometry next() { + Geometry g = m_cur.next(); + if (g == null && m_cur != m_cur2) { + m_cur = m_cur2; + return m_cur.next(); + } + return g; + } + + @Override + public long getGeometryID() { + return m_cur.getGeometryID(); + } } diff --git a/src/main/java/com/esri/core/geometry/GeometryEngine.java b/src/main/java/com/esri/core/geometry/GeometryEngine.java index 188ac0eb..fc9855a3 100644 --- a/src/main/java/com/esri/core/geometry/GeometryEngine.java +++ b/src/main/java/com/esri/core/geometry/GeometryEngine.java @@ -39,801 +39,796 @@ */ public class GeometryEngine { - private static OperatorFactoryLocal factory = OperatorFactoryLocal - .getInstance(); - - - /** - * Imports the MapGeometry from its JSON representation. M and Z values are - * not imported from JSON representation. - *

- * See OperatorImportFromJson. - * - * @param json The JSON representation of the geometry (with spatial - * reference). - * @return The MapGeometry instance containing the imported geometry and its - * spatial reference. - */ - public static MapGeometry jsonToGeometry(JsonParser json) { - MapGeometry geom = OperatorImportFromJson.local().execute(Geometry.Type.Unknown, new JsonParserReader(json)); - return geom; - } + private static OperatorFactoryLocal factory = OperatorFactoryLocal + .getInstance(); + + + /** + * Imports the MapGeometry from its JSON representation. M and Z values are + * not imported from JSON representation. + *

+ * See OperatorImportFromJson. + * + * @param json The JSON representation of the geometry (with spatial + * reference). + * @return The MapGeometry instance containing the imported geometry and its + * spatial reference. + */ + public static MapGeometry jsonToGeometry(JsonParser json) { + MapGeometry geom = OperatorImportFromJson.local().execute(Geometry.Type.Unknown, new JsonParserReader(json)); + return geom; + } /** * Imports the MapGeometry from its JSON representation. M and Z values are * not imported from JSON representation. - * + *

* See OperatorImportFromJson. - * - * @param json - * The JSON representation of the geometry (with spatial - * reference). + * + * @param json The JSON representation of the geometry (with spatial + * reference). * @return The MapGeometry instance containing the imported geometry and its - * spatial reference. + * spatial reference. */ public static MapGeometry jsonToGeometry(JsonReader json) { MapGeometry geom = OperatorImportFromJson.local().execute(Geometry.Type.Unknown, json); return geom; } - + /** * Imports the MapGeometry from its JSON representation. M and Z values are * not imported from JSON representation. - * + *

* See OperatorImportFromJson. - * - * @param json - * The JSON representation of the geometry (with spatial - * reference). + * + * @param json The JSON representation of the geometry (with spatial + * reference). * @return The MapGeometry instance containing the imported geometry and its - * spatial reference. + * spatial reference. */ public static MapGeometry jsonToGeometry(String json) { MapGeometry geom = OperatorImportFromJson.local().execute(Geometry.Type.Unknown, json); return geom; } - + /** * Exports the specified geometry instance to it's JSON representation. - * + *

* See OperatorExportToJson. - * - * @see GeometryEngine#geometryToJson(SpatialReference spatialiReference, - * Geometry geometry) - * @param wkid - * The spatial reference Well Known ID to be used for the JSON - * representation. - * @param geometry - * The geometry to be exported to JSON. + * + * @param wkid The spatial reference Well Known ID to be used for the JSON + * representation. + * @param geometry The geometry to be exported to JSON. * @return The JSON representation of the specified Geometry. + * @see GeometryEngine#geometryToJson(SpatialReference spatialiReference, + * Geometry geometry) */ public static String geometryToJson(int wkid, Geometry geometry) { return GeometryEngine.geometryToJson( wkid > 0 ? SpatialReference.create(wkid) : null, geometry); } - /** - * Exports the specified geometry instance to it's JSON representation. M - * and Z values are not imported from JSON representation. - *

- * See OperatorExportToJson. - * - * @param spatialReference The spatial reference of associated object. - * @param geometry The geometry. - * @return The JSON representation of the specified geometry. - */ - public static String geometryToJson(SpatialReference spatialReference, - Geometry geometry) { - OperatorExportToJson exporter = (OperatorExportToJson) factory - .getOperator(Operator.Type.ExportToJson); + /** + * Exports the specified geometry instance to it's JSON representation. M + * and Z values are not imported from JSON representation. + *

+ * See OperatorExportToJson. + * + * @param spatialReference The spatial reference of associated object. + * @param geometry The geometry. + * @return The JSON representation of the specified geometry. + */ + public static String geometryToJson(SpatialReference spatialReference, + Geometry geometry) { + OperatorExportToJson exporter = (OperatorExportToJson) factory + .getOperator(Operator.Type.ExportToJson); - return exporter.execute(spatialReference, geometry); - } + return exporter.execute(spatialReference, geometry); + } - public static String geometryToGeoJson(Geometry geometry) { - OperatorExportToGeoJson exporter = (OperatorExportToGeoJson) factory - .getOperator(Operator.Type.ExportToGeoJson); + public static String geometryToGeoJson(Geometry geometry) { + OperatorExportToGeoJson exporter = (OperatorExportToGeoJson) factory + .getOperator(Operator.Type.ExportToGeoJson); - return exporter.execute(geometry); - } + return exporter.execute(geometry); + } /** * Imports the MapGeometry from its JSON representation. M and Z values are * not imported from JSON representation. - * + *

* See OperatorImportFromJson. - * - * @param json - * The JSON representation of the geometry (with spatial - * reference). + * + * @param json The JSON representation of the geometry (with spatial + * reference). * @return The MapGeometry instance containing the imported geometry and its - * spatial reference. + * spatial reference. */ public static MapGeometry geoJsonToGeometry(String json, int importFlags, Geometry.Type type) { MapGeometry geom = OperatorImportFromGeoJson.local().execute(importFlags, type, json, null); return geom; } - /** - * Exports the specified geometry instance to its GeoJSON representation. - *

- * See OperatorExportToGeoJson. - * - * @param wkid The spatial reference Well Known ID to be used for the GeoJSON - * representation. - * @param geometry The geometry to be exported to GeoJSON. - * @return The GeoJSON representation of the specified geometry. - * @see GeometryEngine#geometryToGeoJson(SpatialReference spatialReference, - * Geometry geometry) - */ - public static String geometryToGeoJson(int wkid, Geometry geometry) { - return GeometryEngine.geometryToGeoJson(wkid > 0 ? SpatialReference.create(wkid) : null, geometry); - } - - /** - * Exports the specified geometry instance to it's JSON representation. - *

- * See OperatorImportFromGeoJson. - * - * @param spatialReference The spatial reference of associated object. - * @param geometry The geometry. - * @return The GeoJSON representation of the specified geometry. - */ - public static String geometryToGeoJson(SpatialReference spatialReference, Geometry geometry) { - OperatorExportToGeoJson exporter = (OperatorExportToGeoJson) factory.getOperator(Operator.Type.ExportToGeoJson); - - return exporter.execute(spatialReference, geometry); - } - - /** - * Imports geometry from the ESRI shape file format. - *

- * See OperatorImportFromESRIShape. - * - * @param esriShapeBuffer The buffer containing geometry in the ESRI shape file format. - * @param geometryType The required type of the Geometry to be imported. Use - * Geometry.Type.Unknown if the geometry type needs to be - * determined from the buffer content. - * @return The geometry or null if the buffer contains null shape. - * @throws GeometryException when the geometryType is not Geometry.Type.Unknown and the - * buffer contains geometry that cannot be converted to the - * given geometryType. or the buffer is corrupt. Another - * exception possible is IllegalArgumentsException. - */ - public static Geometry geometryFromEsriShape(byte[] esriShapeBuffer, Geometry.Type geometryType) { - OperatorImportFromESRIShape op = (OperatorImportFromESRIShape) factory.getOperator(Operator.Type.ImportFromESRIShape); - return op.execute(ShapeImportFlags.ShapeImportNonTrusted, - geometryType, - ByteBuffer.wrap(esriShapeBuffer).order(ByteOrder.LITTLE_ENDIAN)); - } - - /** - * Exports geometry to the ESRI shape file format. - *

- * See OperatorExportToESRIShape. - * - * @param geometry The geometry to export. (null value is not allowed) - * @return Array containing the exported ESRI shape file. - */ - public static byte[] geometryToEsriShape(Geometry geometry) { - if (geometry == null) - throw new IllegalArgumentException(); - OperatorExportToESRIShape op = (OperatorExportToESRIShape) factory - .getOperator(Operator.Type.ExportToESRIShape); - return op.execute(0, geometry).array(); - } + /** + * Exports the specified geometry instance to its GeoJSON representation. + *

+ * See OperatorExportToGeoJson. + * + * @param wkid The spatial reference Well Known ID to be used for the GeoJSON + * representation. + * @param geometry The geometry to be exported to GeoJSON. + * @return The GeoJSON representation of the specified geometry. + * @see GeometryEngine#geometryToGeoJson(SpatialReference spatialReference, + * Geometry geometry) + */ + public static String geometryToGeoJson(int wkid, Geometry geometry) { + return GeometryEngine.geometryToGeoJson(wkid > 0 ? SpatialReference.create(wkid) : null, geometry); + } + + /** + * Exports the specified geometry instance to it's JSON representation. + *

+ * See OperatorImportFromGeoJson. + * + * @param spatialReference The spatial reference of associated object. + * @param geometry The geometry. + * @return The GeoJSON representation of the specified geometry. + */ + public static String geometryToGeoJson(SpatialReference spatialReference, Geometry geometry) { + OperatorExportToGeoJson exporter = (OperatorExportToGeoJson) factory.getOperator(Operator.Type.ExportToGeoJson); + + return exporter.execute(spatialReference, geometry); + } + + /** + * Imports geometry from the ESRI shape file format. + *

+ * See OperatorImportFromESRIShape. + * + * @param esriShapeBuffer The buffer containing geometry in the ESRI shape file format. + * @param geometryType The required type of the Geometry to be imported. Use + * Geometry.Type.Unknown if the geometry type needs to be + * determined from the buffer content. + * @return The geometry or null if the buffer contains null shape. + * @throws GeometryException when the geometryType is not Geometry.Type.Unknown and the + * buffer contains geometry that cannot be converted to the + * given geometryType. or the buffer is corrupt. Another + * exception possible is IllegalArgumentsException. + */ + public static Geometry geometryFromEsriShape(byte[] esriShapeBuffer, Geometry.Type geometryType) { + OperatorImportFromESRIShape op = (OperatorImportFromESRIShape) factory.getOperator(Operator.Type.ImportFromESRIShape); + return op.execute(ShapeImportFlags.ShapeImportNonTrusted, + geometryType, + ByteBuffer.wrap(esriShapeBuffer).order(ByteOrder.LITTLE_ENDIAN)); + } + + /** + * Exports geometry to the ESRI shape file format. + *

+ * See OperatorExportToESRIShape. + * + * @param geometry The geometry to export. (null value is not allowed) + * @return Array containing the exported ESRI shape file. + */ + public static byte[] geometryToEsriShape(Geometry geometry) { + if (geometry == null) + throw new IllegalArgumentException(); + OperatorExportToESRIShape op = (OperatorExportToESRIShape) factory + .getOperator(Operator.Type.ExportToESRIShape); + return op.execute(0, geometry).array(); + } /** * Imports a geometry from a WKT string. - * + *

* See OperatorImportFromWkt. - * - * @param wkt The string containing the geometry in WKT format. - * @param importFlags Use the {@link WktImportFlags} interface. + * + * @param wkt The string containing the geometry in WKT format. + * @param importFlags Use the {@link WktImportFlags} interface. * @param geometryType The required type of the Geometry to be imported. Use Geometry.Type.Unknown if the geometry type needs to be determined from the WKT context. * @return The geometry. - * @throws GeometryException when the geometryType is not Geometry.Type.Unknown and the WKT contains a geometry that cannot be converted to the given geometryType. + * @throws GeometryException when the geometryType is not Geometry.Type.Unknown and the WKT contains a geometry that cannot be converted to the given geometryType. * @throws IllegalArgumentException if an error is found while parsing the WKT string. */ public static Geometry geometryFromWkt(String wkt, int importFlags, - Geometry.Type geometryType) { + Geometry.Type geometryType) { OperatorImportFromWkt op = (OperatorImportFromWkt) factory .getOperator(Operator.Type.ImportFromWkt); return op.execute(importFlags, geometryType, wkt, null); } - /** - * Exports a geometry to a string in WKT format. - *

- * See OperatorExportToWkt. - * - * @param geometry The geometry to export. (null value is not allowed) - * @param exportFlags Use the {@link WktExportFlags} interface. - * @return A String containing the exported geometry in WKT format. - */ - public static String geometryToWkt(Geometry geometry, int exportFlags) { - OperatorExportToWkt op = (OperatorExportToWkt) factory - .getOperator(Operator.Type.ExportToWkt); - return op.execute(exportFlags, geometry, null); - } - - /** - * Constructs a new geometry by union an array of geometries. All inputs - * must be of the same type of geometries and share one spatial reference. - *

- * See OperatorUnion. - * - * @param geometries The geometries to union. - * @param spatialReference The spatial reference of the geometries. - * @return The geometry object representing the resultant union. - */ - public static Geometry union(Geometry[] geometries, SpatialReference spatialReference) { - OperatorUnion op = (OperatorUnion) factory.getOperator(Operator.Type.Union); - - SimpleGeometryCursor inputGeometries = new SimpleGeometryCursor(geometries); - GeometryCursor result = op.execute(inputGeometries, spatialReference, null); - return result.next(); - } - - /** - * Creates the difference of two geometries. The dimension of geometry2 has - * to be equal to or greater than that of geometry1. - *

- * See OperatorDifference. - * - * @param geometry1 The geometry being subtracted. - * @param substractor The geometry object to subtract from. - * @param spatialReference The spatial reference of the geometries. - * @return The geometry of the differences. - */ - public static Geometry difference(Geometry geometry1, Geometry substractor, - SpatialReference spatialReference) { - OperatorDifference op = (OperatorDifference) factory.getOperator(Operator.Type.Difference); - Geometry result = op.execute(geometry1, substractor, spatialReference,null); - return result; - } - - /** - * Creates the symmetric difference of two geometries. - *

- * See OperatorSymmetricDifference. - * - * @param leftGeometry is one of the Geometry instances in the XOR operation. - * @param rightGeometry is one of the Geometry instances in the XOR operation. - * @param spatialReference The spatial reference of the geometries. - * @return Returns the result of the symmetric difference. - */ - public static Geometry symmetricDifference(Geometry leftGeometry, - Geometry rightGeometry, SpatialReference spatialReference) { - OperatorSymmetricDifference op = (OperatorSymmetricDifference) factory - .getOperator(Operator.Type.SymmetricDifference); - Geometry result = op.execute(leftGeometry, rightGeometry, - spatialReference, null); - return result; - } - - /** - * Indicates if two geometries are equal. - *

- * See OperatorEquals. - * - * @param geometry1 Geometry. - * @param geometry2 Geometry. - * @param spatialReference The spatial reference of the geometries. - * @return TRUE if both geometry objects are equal. - */ - public static boolean equals(Geometry geometry1, Geometry geometry2, - SpatialReference spatialReference) { - OperatorEquals op = (OperatorEquals) factory - .getOperator(Operator.Type.Equals); - boolean result = op.execute(geometry1, geometry2, spatialReference, - null); - return result; - } - - /** - * See OperatorDisjoint. - */ - public static boolean disjoint(Geometry geometry1, Geometry geometry2, - SpatialReference spatialReference) { - OperatorDisjoint op = (OperatorDisjoint) factory - .getOperator(Operator.Type.Disjoint); - boolean result = op.execute(geometry1, geometry2, spatialReference, - null); - return result; - } - - /** - * Constructs the set-theoretic intersection between an array of geometries - * and another geometry. - *

- * See OperatorIntersection (also for dimension specific intersection). - * - * @param inputGeometries An array of geometry objects. - * @param geometry The geometry object. - * @return Any array of geometry objects showing the intersection. - */ - static Geometry[] intersect(Geometry[] inputGeometries, Geometry geometry, - SpatialReference spatialReference) { - OperatorIntersection op = (OperatorIntersection) factory - .getOperator(Operator.Type.Intersection); - SimpleGeometryCursor inputGeometriesCursor = new SimpleGeometryCursor( - inputGeometries); - SimpleGeometryCursor intersectorCursor = new SimpleGeometryCursor( - geometry); - GeometryCursor result = op.execute(inputGeometriesCursor, - intersectorCursor, spatialReference, null); - - ArrayList resultGeoms = new ArrayList(); - Geometry g; - while ((g = result.next()) != null) { - resultGeoms.add(g); - } - - Geometry[] resultarr = resultGeoms.toArray(new Geometry[0]); - return resultarr; - } - - /** - * Creates a geometry through intersection between two geometries. - *

- * See OperatorIntersection. - * - * @param geometry1 The first geometry. - * @param intersector The geometry to intersect the first geometry. - * @param spatialReference The spatial reference of the geometries. - * @return The geometry created through intersection. - */ - public static Geometry intersect(Geometry geometry1, Geometry intersector, - SpatialReference spatialReference) { - OperatorIntersection op = (OperatorIntersection) factory.getOperator(Operator.Type.Intersection); - Geometry result = op.execute(geometry1, intersector, spatialReference,null); - return result; - } - - /** - * Indicates if one geometry is within another geometry. - *

- * See OperatorWithin. - * - * @param geometry1 The base geometry that is tested for within relationship to - * the other geometry. - * @param geometry2 The comparison geometry that is tested for the contains - * relationship to the other geometry. - * @param spatialReference The spatial reference of the geometries. - * @return TRUE if the first geometry is within the other geometry. - */ - public static boolean within(Geometry geometry1, Geometry geometry2, - SpatialReference spatialReference) { - OperatorWithin op = (OperatorWithin) factory - .getOperator(Operator.Type.Within); - boolean result = op.execute(geometry1, geometry2, spatialReference, - null); - return result; - } - - /** - * Indicates if one geometry contains another geometry. - *

- * See OperatorContains. - * - * @param geometry1 The geometry that is tested for the contains relationship to - * the other geometry.. - * @param geometry2 The geometry that is tested for within relationship to the - * other geometry. - * @param spatialReference The spatial reference of the geometries. - * @return TRUE if geometry1 contains geometry2. - */ - public static boolean contains(Geometry geometry1, Geometry geometry2, - SpatialReference spatialReference) { - OperatorContains op = (OperatorContains) factory - .getOperator(Operator.Type.Contains); - boolean result = op.execute(geometry1, geometry2, spatialReference, - null); - return result; - } - - /** - * Indicates if one geometry crosses another geometry. - *

- * See OperatorCrosses. - * - * @param geometry1 The geometry to cross. - * @param geometry2 The geometry being crossed. - * @param spatialReference The spatial reference of the geometries. - * @return TRUE if geometry1 crosses geometry2. - */ - public static boolean crosses(Geometry geometry1, Geometry geometry2, - SpatialReference spatialReference) { - OperatorCrosses op = (OperatorCrosses) factory - .getOperator(Operator.Type.Crosses); - boolean result = op.execute(geometry1, geometry2, spatialReference, - null); - return result; - } - - /** - * Indicates if one geometry touches another geometry. - *

- * See OperatorTouches. - * - * @param geometry1 The geometry to touch. - * @param geometry2 The geometry to be touched. - * @param spatialReference The spatial reference of the geometries. - * @return TRUE if geometry1 touches geometry2. - */ - public static boolean touches(Geometry geometry1, Geometry geometry2, - SpatialReference spatialReference) { - OperatorTouches op = (OperatorTouches) factory - .getOperator(Operator.Type.Touches); - boolean result = op.execute(geometry1, geometry2, spatialReference, - null); - return result; - } - - /** - * Indicates if one geometry overlaps another geometry. - *

- * See OperatorOverlaps. - * - * @param geometry1 The geometry to overlap. - * @param geometry2 The geometry to be overlapped. - * @param spatialReference The spatial reference of the geometries. - * @return TRUE if geometry1 overlaps geometry2. - */ - public static boolean overlaps(Geometry geometry1, Geometry geometry2, - SpatialReference spatialReference) { - OperatorOverlaps op = (OperatorOverlaps) factory - .getOperator(Operator.Type.Overlaps); - boolean result = op.execute(geometry1, geometry2, spatialReference, - null); - return result; - } - - /** - * Indicates if the given relation holds for the two geometries. - *

- * See OperatorRelate. - * - * @param geometry1 The first geometry for the relation. - * @param geometry2 The second geometry for the relation. - * @param spatialReference The spatial reference of the geometries. - * @param relation The DE-9IM relation. - * @return TRUE if the given relation holds between geometry1 and geometry2. - */ - public static boolean relate(Geometry geometry1, Geometry geometry2, - SpatialReference spatialReference, String relation) { - OperatorRelate op = (OperatorRelate) factory - .getOperator(Operator.Type.Relate); - boolean result = op.execute(geometry1, geometry2, spatialReference, - relation, null); - return result; - } - - /** - * Calculates the 2D planar distance between two geometries. - *

- * See OperatorDistance. - * - * @param geometry1 Geometry. - * @param geometry2 Geometry. - * @param spatialReference The spatial reference of the geometries. This parameter is not - * used and can be null. - * @return The distance between the two geometries. - */ - public static double distance(Geometry geometry1, Geometry geometry2, - SpatialReference spatialReference) { - OperatorDistance op = (OperatorDistance) factory - .getOperator(Operator.Type.Distance); - double result = op.execute(geometry1, geometry2, null); - return result; - } - - /** - * Calculates the clipped geometry from a target geometry using an envelope. - *

- * See OperatorClip. - * - * @param geometry The geometry to be clipped. - * @param envelope The envelope used to clip. - * @param spatialReference The spatial reference of the geometries. - * @return The geometry created by clipping. - */ - public static Geometry clip(Geometry geometry, Envelope envelope, - SpatialReference spatialReference) { - OperatorClip op = (OperatorClip) factory - .getOperator(Operator.Type.Clip); - Geometry result = op.execute(geometry, Envelope2D.construct( - envelope.getXMin(), envelope.getYMin(), envelope.getXMax(), - envelope.getYMax()), spatialReference, null); - return result; - } - - /** - * Calculates the cut geometry from a target geometry using a polyline. For - * Polylines, all left cuts will be grouped together in the first Geometry, - * Right cuts and coincident cuts are grouped in the second Geometry, and - * each undefined cut, along with any uncut parts, are output as separate - * Polylines. For Polygons, all left cuts are grouped in the first Polygon, - * all right cuts are in the second Polygon, and each undefined cut, along - * with any left-over parts after cutting, are output as a separate Polygon. - * If there were no cuts then the array will be empty. An undefined cut will - * only be produced if a left cut or right cut was produced, and there was a - * part left over after cutting or a cut is bounded to the left and right of - * the cutter. - *

- * See OperatorCut. - * - * @param cuttee The geometry to be cut. - * @param cutter The polyline to cut the geometry. - * @param spatialReference The spatial reference of the geometries. - * @return An array of geometries created from cutting. - */ - public static Geometry[] cut(Geometry cuttee, Polyline cutter, - SpatialReference spatialReference) { - if (cuttee == null || cutter == null) - return null; - - OperatorCut op = (OperatorCut) factory.getOperator(Operator.Type.Cut); - GeometryCursor cursor = op.execute(true, cuttee, cutter, - spatialReference, null); - ArrayList cutsList = new ArrayList(); - - Geometry geometry; - while ((geometry = cursor.next()) != null) { - if (!geometry.isEmpty()) { - cutsList.add(geometry); - } - } - - return cutsList.toArray(new Geometry[0]); - } - - /** - * Calculates a buffer polygon for each geometry at each of the - * corresponding specified distances. It is assumed that all geometries have - * the same spatial reference. There is an option to union the - * returned geometries. - *

- * See OperatorBuffer. - * - * @param geometries An array of geometries to be buffered. - * @param spatialReference The spatial reference of the geometries. - * @param distances The corresponding distances for the input geometries to be buffered. - * @param toUnionResults TRUE if all geometries buffered at a given distance are to be unioned into a single polygon. - * @return The buffer of the geometries. - */ - public static Polygon[] buffer(Geometry[] geometries, - SpatialReference spatialReference, double[] distances, - boolean toUnionResults) { - // initially assume distances are in unit of spatial reference - double[] bufferDistances = distances; - - OperatorBuffer op = (OperatorBuffer) factory - .getOperator(Operator.Type.Buffer); - - if (toUnionResults) { - SimpleGeometryCursor inputGeometriesCursor = new SimpleGeometryCursor( - geometries); - GeometryCursor result = op.execute(inputGeometriesCursor, - spatialReference, bufferDistances, toUnionResults, null); - - ArrayList resultGeoms = new ArrayList(); - Geometry g; - while ((g = result.next()) != null) { - resultGeoms.add((Polygon) g); - } - Polygon[] buffers = resultGeoms.toArray(new Polygon[0]); - return buffers; - } else { - Polygon[] buffers = new Polygon[geometries.length]; - for (int i = 0; i < geometries.length; i++) { - buffers[i] = (Polygon) op.execute(geometries[i], - spatialReference, bufferDistances[i], null); - } - return buffers; - } - } - - public static Polygon geodesicBuffer(Geometry geometry, SpatialReference spatialReference, double d) { - OperatorGeodesicBuffer operatorGeodesicBuffer = (OperatorGeodesicBuffer)factory.getOperator(Operator.Type.GeodesicBuffer); - return (Polygon) operatorGeodesicBuffer.execute(geometry, spatialReference, 0, d, Double.NaN, false, null); - } - - public static Geometry project(Geometry geometry, SpatialReference spatialReferenceInput, SpatialReference spatialReferenceOutput) { - OperatorProject operatorProject = (OperatorProject) factory.getOperator(Operator.Type.Project); - ProjectionTransformation projectionTransformation = new ProjectionTransformation(spatialReferenceInput, spatialReferenceOutput); - return operatorProject.execute(geometry, projectionTransformation, null); - } - - /** - * Calculates a buffer polygon of the geometry as specified by the - * distance input. The buffer is implemented in the xy-plane. - *

- * See OperatorBuffer - * - * @param geometry Geometry to be buffered. - * @param spatialReference The spatial reference of the geometry. - * @param distance The specified distance for buffer. Same units as the spatial reference. - * @return The buffer polygon at the specified distances. - */ - public static Polygon buffer(Geometry geometry, - SpatialReference spatialReference, double distance) { - double bufferDistance = distance; - - OperatorBuffer op = (OperatorBuffer) factory - .getOperator(Operator.Type.Buffer); - Geometry result = op.execute(geometry, spatialReference, - bufferDistance, null); - return (Polygon) result; - } - - /** - * Calculates the convex hull geometry. - *

- * See OperatorConvexHull. - * - * @param geometry The input geometry. - * @return Returns the convex hull. - *

- * For a Point - returns the same point. For an Envelope - - * returns the same envelope. For a MultiPoint - If the point - * count is one, returns the same multipoint. If the point count - * is two, returns a polyline of the points. Otherwise computes - * and returns the convex hull polygon. For a Segment - returns a - * polyline consisting of the segment. For a Polyline - If - * consists of only one segment, returns the same polyline. - * Otherwise computes and returns the convex hull polygon. For a - * Polygon - If more than one path, or if the path isn't already - * convex, computes and returns the convex hull polygon. - * Otherwise returns the same polygon. - */ - public static Geometry convexHull(Geometry geometry) { - OperatorConvexHull op = (OperatorConvexHull) factory - .getOperator(Operator.Type.ConvexHull); - return op.execute(geometry, null); - } - - /** - * Calculates the convex hull. - *

- * See OperatorConvexHull - * - * @param geometries The input geometry array. - * @param b_merge Put true if you want the convex hull of all the geometries in - * the array combined. Put false if you want the convex hull of - * each geometry in the array individually. - * @return Returns an array of convex hulls. If b_merge is true, the result - * will be a one element array consisting of the merged convex hull. - */ - public static Geometry[] convexHull(Geometry[] geometries, boolean b_merge) { - OperatorConvexHull op = (OperatorConvexHull) factory - .getOperator(Operator.Type.ConvexHull); - SimpleGeometryCursor simple_cursor = new SimpleGeometryCursor( - geometries); - GeometryCursor cursor = op.execute(simple_cursor, b_merge, null); - - ArrayList resultGeoms = new ArrayList(); - Geometry g; - while ((g = cursor.next()) != null) { - resultGeoms.add(g); - } - - Geometry[] output = new Geometry[resultGeoms.size()]; - - for (int i = 0; i < resultGeoms.size(); i++) - output[i] = resultGeoms.get(i); - - return output; - } - - /** - * Finds the coordinate of the geometry which is closest to the specified - * point. - *

- * See OperatorProximity2D. - * - * @param inputPoint The point to find the nearest coordinate in the geometry for. - * @param geometry The geometry to consider. - * @return Proximity2DResult containing the nearest coordinate. - */ - public static Proximity2DResult getNearestCoordinate(Geometry geometry, - Point inputPoint, boolean bTestPolygonInterior) { - - OperatorProximity2D proximity = (OperatorProximity2D) factory - .getOperator(com.esri.core.geometry.Operator.Type.Proximity2D); - Proximity2DResult result = proximity.getNearestCoordinate(geometry, - inputPoint, bTestPolygonInterior); - return result; - } - - /** - * Finds nearest vertex on the geometry which is closed to the specified - * point. - *

- * See OperatorProximity2D. - * - * @param inputPoint The point to find the nearest vertex of the geometry for. - * @param geometry The geometry to consider. - * @return Proximity2DResult containing the nearest vertex. - */ - public static Proximity2DResult getNearestVertex(Geometry geometry, - Point inputPoint) { - OperatorProximity2D proximity = (OperatorProximity2D) factory - .getOperator(com.esri.core.geometry.Operator.Type.Proximity2D); - Proximity2DResult result = proximity.getNearestVertex(geometry, - inputPoint); - return result; - } - - /** - * Finds all vertices in the given distance from the specified point, sorted - * from the closest to the furthest. - *

- * See OperatorProximity2D. - * - * @param inputPoint The point to start from. - * @param geometry The geometry to consider. - * @param searchRadius The search radius. - * @param maxVertexCountToReturn The maximum number number of vertices to return. - * @return Proximity2DResult containing the array of nearest vertices. - */ - public static Proximity2DResult[] getNearestVertices(Geometry geometry, - Point inputPoint, double searchRadius, int maxVertexCountToReturn) { - OperatorProximity2D proximity = (OperatorProximity2D) factory - .getOperator(com.esri.core.geometry.Operator.Type.Proximity2D); - - Proximity2DResult[] results = proximity.getNearestVertices(geometry, - inputPoint, searchRadius, maxVertexCountToReturn); - - return results; - } - - /** - * Performs the simplify operation on the geometry. - *

- * See OperatorSimplify and See OperatorSimplifyOGC. - * - * @param geometry The geometry to be simplified. - * @param spatialReference The spatial reference of the geometry to be simplified. - * @return The simplified geometry. - */ - public static Geometry simplify(Geometry geometry, - SpatialReference spatialReference) { - OperatorSimplify op = (OperatorSimplify) factory - .getOperator(Operator.Type.Simplify); - Geometry result = op.execute(geometry, spatialReference, false, null); - return result; - } - - /** - * Checks if the Geometry is simple. - *

- * See OperatorSimplify. - * - * @param geometry The geometry to be checked. - * @param spatialReference The spatial reference of the geometry. - * @return TRUE if the geometry is simple. - */ - static boolean isSimple(Geometry geometry, SpatialReference spatialReference) { - OperatorSimplify op = (OperatorSimplify) factory - .getOperator(Operator.Type.Simplify); - boolean result = op.isSimpleAsFeature(geometry, spatialReference, null); - return result; - } - - /** - * A geodesic distance is the shortest distance between any two points on the earth's surface when the earth's - * surface is approximated by a spheroid. The function returns the shortest distance between two points on the - * WGS84 spheroid. - * - * @param ptFrom The "from" point: long, lat in degrees. - * @param ptTo The "to" point: long, lat in degrees. - * @return The geodesic distance between two points in meters. - */ - public static double geodesicDistanceOnWGS84(Point ptFrom, Point ptTo) { - return SpatialReferenceImpl.geodesicDistanceOnWGS84Impl(ptFrom, ptTo); - } + /** + * Exports a geometry to a string in WKT format. + *

+ * See OperatorExportToWkt. + * + * @param geometry The geometry to export. (null value is not allowed) + * @param exportFlags Use the {@link WktExportFlags} interface. + * @return A String containing the exported geometry in WKT format. + */ + public static String geometryToWkt(Geometry geometry, int exportFlags) { + OperatorExportToWkt op = (OperatorExportToWkt) factory + .getOperator(Operator.Type.ExportToWkt); + return op.execute(exportFlags, geometry, null); + } + + /** + * Constructs a new geometry by union an array of geometries. All inputs + * must be of the same type of geometries and share one spatial reference. + *

+ * See OperatorUnion. + * + * @param geometries The geometries to union. + * @param spatialReference The spatial reference of the geometries. + * @return The geometry object representing the resultant union. + */ + public static Geometry union(Geometry[] geometries, SpatialReference spatialReference) { + OperatorUnion op = (OperatorUnion) factory.getOperator(Operator.Type.Union); + + SimpleGeometryCursor inputGeometries = new SimpleGeometryCursor(geometries); + GeometryCursor result = op.execute(inputGeometries, spatialReference, null); + return result.next(); + } + + /** + * Creates the difference of two geometries. The dimension of geometry2 has + * to be equal to or greater than that of geometry1. + *

+ * See OperatorDifference. + * + * @param geometry1 The geometry being subtracted. + * @param substractor The geometry object to subtract from. + * @param spatialReference The spatial reference of the geometries. + * @return The geometry of the differences. + */ + public static Geometry difference(Geometry geometry1, Geometry substractor, + SpatialReference spatialReference) { + OperatorDifference op = (OperatorDifference) factory.getOperator(Operator.Type.Difference); + Geometry result = op.execute(geometry1, substractor, spatialReference, null); + return result; + } + + /** + * Creates the symmetric difference of two geometries. + *

+ * See OperatorSymmetricDifference. + * + * @param leftGeometry is one of the Geometry instances in the XOR operation. + * @param rightGeometry is one of the Geometry instances in the XOR operation. + * @param spatialReference The spatial reference of the geometries. + * @return Returns the result of the symmetric difference. + */ + public static Geometry symmetricDifference(Geometry leftGeometry, + Geometry rightGeometry, SpatialReference spatialReference) { + OperatorSymmetricDifference op = (OperatorSymmetricDifference) factory + .getOperator(Operator.Type.SymmetricDifference); + Geometry result = op.execute(leftGeometry, rightGeometry, + spatialReference, null); + return result; + } + + /** + * Indicates if two geometries are equal. + *

+ * See OperatorEquals. + * + * @param geometry1 Geometry. + * @param geometry2 Geometry. + * @param spatialReference The spatial reference of the geometries. + * @return TRUE if both geometry objects are equal. + */ + public static boolean equals(Geometry geometry1, Geometry geometry2, + SpatialReference spatialReference) { + OperatorEquals op = (OperatorEquals) factory + .getOperator(Operator.Type.Equals); + boolean result = op.execute(geometry1, geometry2, spatialReference, + null); + return result; + } + + /** + * See OperatorDisjoint. + */ + public static boolean disjoint(Geometry geometry1, Geometry geometry2, + SpatialReference spatialReference) { + OperatorDisjoint op = (OperatorDisjoint) factory + .getOperator(Operator.Type.Disjoint); + boolean result = op.execute(geometry1, geometry2, spatialReference, + null); + return result; + } + + /** + * Constructs the set-theoretic intersection between an array of geometries + * and another geometry. + *

+ * See OperatorIntersection (also for dimension specific intersection). + * + * @param inputGeometries An array of geometry objects. + * @param geometry The geometry object. + * @return Any array of geometry objects showing the intersection. + */ + static Geometry[] intersect(Geometry[] inputGeometries, Geometry geometry, + SpatialReference spatialReference) { + OperatorIntersection op = (OperatorIntersection) factory + .getOperator(Operator.Type.Intersection); + SimpleGeometryCursor inputGeometriesCursor = new SimpleGeometryCursor( + inputGeometries); + SimpleGeometryCursor intersectorCursor = new SimpleGeometryCursor( + geometry); + GeometryCursor result = op.execute(inputGeometriesCursor, + intersectorCursor, spatialReference, null); + + ArrayList resultGeoms = new ArrayList(); + Geometry g; + while ((g = result.next()) != null) { + resultGeoms.add(g); + } + + Geometry[] resultarr = resultGeoms.toArray(new Geometry[0]); + return resultarr; + } + + /** + * Creates a geometry through intersection between two geometries. + *

+ * See OperatorIntersection. + * + * @param geometry1 The first geometry. + * @param intersector The geometry to intersect the first geometry. + * @param spatialReference The spatial reference of the geometries. + * @return The geometry created through intersection. + */ + public static Geometry intersect(Geometry geometry1, Geometry intersector, + SpatialReference spatialReference) { + OperatorIntersection op = (OperatorIntersection) factory.getOperator(Operator.Type.Intersection); + Geometry result = op.execute(geometry1, intersector, spatialReference, null); + return result; + } + + /** + * Indicates if one geometry is within another geometry. + *

+ * See OperatorWithin. + * + * @param geometry1 The base geometry that is tested for within relationship to + * the other geometry. + * @param geometry2 The comparison geometry that is tested for the contains + * relationship to the other geometry. + * @param spatialReference The spatial reference of the geometries. + * @return TRUE if the first geometry is within the other geometry. + */ + public static boolean within(Geometry geometry1, Geometry geometry2, + SpatialReference spatialReference) { + OperatorWithin op = (OperatorWithin) factory + .getOperator(Operator.Type.Within); + boolean result = op.execute(geometry1, geometry2, spatialReference, + null); + return result; + } + + /** + * Indicates if one geometry contains another geometry. + *

+ * See OperatorContains. + * + * @param geometry1 The geometry that is tested for the contains relationship to + * the other geometry.. + * @param geometry2 The geometry that is tested for within relationship to the + * other geometry. + * @param spatialReference The spatial reference of the geometries. + * @return TRUE if geometry1 contains geometry2. + */ + public static boolean contains(Geometry geometry1, Geometry geometry2, + SpatialReference spatialReference) { + OperatorContains op = (OperatorContains) factory + .getOperator(Operator.Type.Contains); + boolean result = op.execute(geometry1, geometry2, spatialReference, + null); + return result; + } + + /** + * Indicates if one geometry crosses another geometry. + *

+ * See OperatorCrosses. + * + * @param geometry1 The geometry to cross. + * @param geometry2 The geometry being crossed. + * @param spatialReference The spatial reference of the geometries. + * @return TRUE if geometry1 crosses geometry2. + */ + public static boolean crosses(Geometry geometry1, Geometry geometry2, + SpatialReference spatialReference) { + OperatorCrosses op = (OperatorCrosses) factory + .getOperator(Operator.Type.Crosses); + boolean result = op.execute(geometry1, geometry2, spatialReference, + null); + return result; + } + + /** + * Indicates if one geometry touches another geometry. + *

+ * See OperatorTouches. + * + * @param geometry1 The geometry to touch. + * @param geometry2 The geometry to be touched. + * @param spatialReference The spatial reference of the geometries. + * @return TRUE if geometry1 touches geometry2. + */ + public static boolean touches(Geometry geometry1, Geometry geometry2, + SpatialReference spatialReference) { + OperatorTouches op = (OperatorTouches) factory + .getOperator(Operator.Type.Touches); + boolean result = op.execute(geometry1, geometry2, spatialReference, + null); + return result; + } + + /** + * Indicates if one geometry overlaps another geometry. + *

+ * See OperatorOverlaps. + * + * @param geometry1 The geometry to overlap. + * @param geometry2 The geometry to be overlapped. + * @param spatialReference The spatial reference of the geometries. + * @return TRUE if geometry1 overlaps geometry2. + */ + public static boolean overlaps(Geometry geometry1, Geometry geometry2, + SpatialReference spatialReference) { + OperatorOverlaps op = (OperatorOverlaps) factory + .getOperator(Operator.Type.Overlaps); + boolean result = op.execute(geometry1, geometry2, spatialReference, + null); + return result; + } + + /** + * Indicates if the given relation holds for the two geometries. + *

+ * See OperatorRelate. + * + * @param geometry1 The first geometry for the relation. + * @param geometry2 The second geometry for the relation. + * @param spatialReference The spatial reference of the geometries. + * @param relation The DE-9IM relation. + * @return TRUE if the given relation holds between geometry1 and geometry2. + */ + public static boolean relate(Geometry geometry1, Geometry geometry2, + SpatialReference spatialReference, String relation) { + OperatorRelate op = (OperatorRelate) factory + .getOperator(Operator.Type.Relate); + boolean result = op.execute(geometry1, geometry2, spatialReference, + relation, null); + return result; + } + + /** + * Calculates the 2D planar distance between two geometries. + *

+ * See OperatorDistance. + * + * @param geometry1 Geometry. + * @param geometry2 Geometry. + * @param spatialReference The spatial reference of the geometries. This parameter is not + * used and can be null. + * @return The distance between the two geometries. + */ + public static double distance(Geometry geometry1, Geometry geometry2, + SpatialReference spatialReference) { + OperatorDistance op = (OperatorDistance) factory + .getOperator(Operator.Type.Distance); + double result = op.execute(geometry1, geometry2, null); + return result; + } + + /** + * Calculates the clipped geometry from a target geometry using an envelope. + *

+ * See OperatorClip. + * + * @param geometry The geometry to be clipped. + * @param envelope The envelope used to clip. + * @param spatialReference The spatial reference of the geometries. + * @return The geometry created by clipping. + */ + public static Geometry clip(Geometry geometry, Envelope envelope, + SpatialReference spatialReference) { + OperatorClip op = (OperatorClip) factory + .getOperator(Operator.Type.Clip); + Geometry result = op.execute(geometry, Envelope2D.construct( + envelope.getXMin(), envelope.getYMin(), envelope.getXMax(), + envelope.getYMax()), spatialReference, null); + return result; + } + + /** + * Calculates the cut geometry from a target geometry using a polyline. For + * Polylines, all left cuts will be grouped together in the first Geometry, + * Right cuts and coincident cuts are grouped in the second Geometry, and + * each undefined cut, along with any uncut parts, are output as separate + * Polylines. For Polygons, all left cuts are grouped in the first Polygon, + * all right cuts are in the second Polygon, and each undefined cut, along + * with any left-over parts after cutting, are output as a separate Polygon. + * If there were no cuts then the array will be empty. An undefined cut will + * only be produced if a left cut or right cut was produced, and there was a + * part left over after cutting or a cut is bounded to the left and right of + * the cutter. + *

+ * See OperatorCut. + * + * @param cuttee The geometry to be cut. + * @param cutter The polyline to cut the geometry. + * @param spatialReference The spatial reference of the geometries. + * @return An array of geometries created from cutting. + */ + public static Geometry[] cut(Geometry cuttee, Polyline cutter, + SpatialReference spatialReference) { + if (cuttee == null || cutter == null) + return null; + + OperatorCut op = (OperatorCut) factory.getOperator(Operator.Type.Cut); + GeometryCursor cursor = op.execute(true, cuttee, cutter, + spatialReference, null); + ArrayList cutsList = new ArrayList(); + + Geometry geometry; + while ((geometry = cursor.next()) != null) { + if (!geometry.isEmpty()) { + cutsList.add(geometry); + } + } + + return cutsList.toArray(new Geometry[0]); + } + + /** + * Calculates a buffer polygon for each geometry at each of the + * corresponding specified distances. It is assumed that all geometries have + * the same spatial reference. There is an option to union the + * returned geometries. + *

+ * See OperatorBuffer. + * + * @param geometries An array of geometries to be buffered. + * @param spatialReference The spatial reference of the geometries. + * @param distances The corresponding distances for the input geometries to be buffered. + * @param toUnionResults TRUE if all geometries buffered at a given distance are to be unioned into a single polygon. + * @return The buffer of the geometries. + */ + public static Polygon[] buffer(Geometry[] geometries, + SpatialReference spatialReference, double[] distances, + boolean toUnionResults) { + // initially assume distances are in unit of spatial reference + double[] bufferDistances = distances; + + OperatorBuffer op = (OperatorBuffer) factory + .getOperator(Operator.Type.Buffer); + + if (toUnionResults) { + SimpleGeometryCursor inputGeometriesCursor = new SimpleGeometryCursor( + geometries); + GeometryCursor result = op.execute(inputGeometriesCursor, + spatialReference, bufferDistances, toUnionResults, null); + + ArrayList resultGeoms = new ArrayList(); + Geometry g; + while ((g = result.next()) != null) { + resultGeoms.add((Polygon) g); + } + Polygon[] buffers = resultGeoms.toArray(new Polygon[0]); + return buffers; + } else { + Polygon[] buffers = new Polygon[geometries.length]; + for (int i = 0; i < geometries.length; i++) { + buffers[i] = (Polygon) op.execute(geometries[i], + spatialReference, bufferDistances[i], null); + } + return buffers; + } + } + + public static Polygon geodesicBuffer(Geometry geometry, SpatialReference spatialReference, double d) { + OperatorGeodesicBuffer operatorGeodesicBuffer = (OperatorGeodesicBuffer) factory.getOperator(Operator.Type.GeodesicBuffer); + return (Polygon) operatorGeodesicBuffer.execute(geometry, spatialReference, 0, d, Double.NaN, false, null); + } + + public static Geometry project(Geometry geometry, SpatialReference spatialReferenceInput, SpatialReference spatialReferenceOutput) { + OperatorProject operatorProject = (OperatorProject) factory.getOperator(Operator.Type.Project); + ProjectionTransformation projectionTransformation = new ProjectionTransformation(spatialReferenceInput, spatialReferenceOutput); + return operatorProject.execute(geometry, projectionTransformation, null); + } + + /** + * Calculates a buffer polygon of the geometry as specified by the + * distance input. The buffer is implemented in the xy-plane. + *

+ * See OperatorBuffer + * + * @param geometry Geometry to be buffered. + * @param spatialReference The spatial reference of the geometry. + * @param distance The specified distance for buffer. Same units as the spatial reference. + * @return The buffer polygon at the specified distances. + */ + public static Polygon buffer(Geometry geometry, + SpatialReference spatialReference, double distance) { + double bufferDistance = distance; + + OperatorBuffer op = (OperatorBuffer) factory + .getOperator(Operator.Type.Buffer); + Geometry result = op.execute(geometry, spatialReference, + bufferDistance, null); + return (Polygon) result; + } + + /** + * Calculates the convex hull geometry. + *

+ * See OperatorConvexHull. + * + * @param geometry The input geometry. + * @return Returns the convex hull. + *

+ * For a Point - returns the same point. For an Envelope - + * returns the same envelope. For a MultiPoint - If the point + * count is one, returns the same multipoint. If the point count + * is two, returns a polyline of the points. Otherwise computes + * and returns the convex hull polygon. For a Segment - returns a + * polyline consisting of the segment. For a Polyline - If + * consists of only one segment, returns the same polyline. + * Otherwise computes and returns the convex hull polygon. For a + * Polygon - If more than one path, or if the path isn't already + * convex, computes and returns the convex hull polygon. + * Otherwise returns the same polygon. + */ + public static Geometry convexHull(Geometry geometry) { + OperatorConvexHull op = (OperatorConvexHull) factory + .getOperator(Operator.Type.ConvexHull); + return op.execute(geometry, null); + } + + /** + * Calculates the convex hull. + *

+ * See OperatorConvexHull + * + * @param geometries The input geometry array. + * @param b_merge Put true if you want the convex hull of all the geometries in + * the array combined. Put false if you want the convex hull of + * each geometry in the array individually. + * @return Returns an array of convex hulls. If b_merge is true, the result + * will be a one element array consisting of the merged convex hull. + */ + public static Geometry[] convexHull(Geometry[] geometries, boolean b_merge) { + OperatorConvexHull op = (OperatorConvexHull) factory + .getOperator(Operator.Type.ConvexHull); + SimpleGeometryCursor simple_cursor = new SimpleGeometryCursor( + geometries); + GeometryCursor cursor = op.execute(simple_cursor, b_merge, null); + + ArrayList resultGeoms = new ArrayList(); + Geometry g; + while ((g = cursor.next()) != null) { + resultGeoms.add(g); + } + + Geometry[] output = new Geometry[resultGeoms.size()]; + + for (int i = 0; i < resultGeoms.size(); i++) + output[i] = resultGeoms.get(i); + + return output; + } + + /** + * Finds the coordinate of the geometry which is closest to the specified + * point. + *

+ * See OperatorProximity2D. + * + * @param inputPoint The point to find the nearest coordinate in the geometry for. + * @param geometry The geometry to consider. + * @return Proximity2DResult containing the nearest coordinate. + */ + public static Proximity2DResult getNearestCoordinate(Geometry geometry, + Point inputPoint, boolean bTestPolygonInterior) { + + OperatorProximity2D proximity = (OperatorProximity2D) factory + .getOperator(com.esri.core.geometry.Operator.Type.Proximity2D); + Proximity2DResult result = proximity.getNearestCoordinate(geometry, + inputPoint, bTestPolygonInterior); + return result; + } + + /** + * Finds nearest vertex on the geometry which is closed to the specified + * point. + *

+ * See OperatorProximity2D. + * + * @param inputPoint The point to find the nearest vertex of the geometry for. + * @param geometry The geometry to consider. + * @return Proximity2DResult containing the nearest vertex. + */ + public static Proximity2DResult getNearestVertex(Geometry geometry, + Point inputPoint) { + OperatorProximity2D proximity = (OperatorProximity2D) factory + .getOperator(com.esri.core.geometry.Operator.Type.Proximity2D); + Proximity2DResult result = proximity.getNearestVertex(geometry, + inputPoint); + return result; + } + + /** + * Finds all vertices in the given distance from the specified point, sorted + * from the closest to the furthest. + *

+ * See OperatorProximity2D. + * + * @param inputPoint The point to start from. + * @param geometry The geometry to consider. + * @param searchRadius The search radius. + * @param maxVertexCountToReturn The maximum number number of vertices to return. + * @return Proximity2DResult containing the array of nearest vertices. + */ + public static Proximity2DResult[] getNearestVertices(Geometry geometry, + Point inputPoint, double searchRadius, int maxVertexCountToReturn) { + OperatorProximity2D proximity = (OperatorProximity2D) factory + .getOperator(com.esri.core.geometry.Operator.Type.Proximity2D); + + Proximity2DResult[] results = proximity.getNearestVertices(geometry, + inputPoint, searchRadius, maxVertexCountToReturn); + + return results; + } + + /** + * Performs the simplify operation on the geometry. + *

+ * See OperatorSimplify and See OperatorSimplifyOGC. + * + * @param geometry The geometry to be simplified. + * @param spatialReference The spatial reference of the geometry to be simplified. + * @return The simplified geometry. + */ + public static Geometry simplify(Geometry geometry, + SpatialReference spatialReference) { + OperatorSimplify op = (OperatorSimplify) factory + .getOperator(Operator.Type.Simplify); + Geometry result = op.execute(geometry, spatialReference, false, null); + return result; + } + + /** + * Checks if the Geometry is simple. + *

+ * See OperatorSimplify. + * + * @param geometry The geometry to be checked. + * @param spatialReference The spatial reference of the geometry. + * @return TRUE if the geometry is simple. + */ + static boolean isSimple(Geometry geometry, SpatialReference spatialReference) { + OperatorSimplify op = (OperatorSimplify) factory + .getOperator(Operator.Type.Simplify); + boolean result = op.isSimpleAsFeature(geometry, spatialReference, null); + return result; + } + + /** + * A geodesic distance is the shortest distance between any two points on the earth's surface when the earth's + * surface is approximated by a spheroid. The function returns the shortest distance between two points on the + * WGS84 spheroid. + * + * @param ptFrom The "from" point: long, lat in degrees. + * @param ptTo The "to" point: long, lat in degrees. + * @return The geodesic distance between two points in meters. + */ + public static double geodesicDistanceOnWGS84(Point ptFrom, Point ptTo) { + return SpatialReferenceImpl.geodesicDistanceOnWGS84Impl(ptFrom, ptTo); + } } diff --git a/src/main/java/com/esri/core/geometry/GeometryException.java b/src/main/java/com/esri/core/geometry/GeometryException.java index 27c454fe..75498a73 100644 --- a/src/main/java/com/esri/core/geometry/GeometryException.java +++ b/src/main/java/com/esri/core/geometry/GeometryException.java @@ -30,18 +30,18 @@ */ public class GeometryException extends RuntimeException { - private static final long serialVersionUID = 1L; - - /** - * Constructs a Geometry Exception with the given error string/message. - * - * @param str - The error string. - */ - public GeometryException(String str) { - super(str); - } - - static GeometryException GeometryInternalError() { - return new GeometryException("internal error"); - } + private static final long serialVersionUID = 1L; + + /** + * Constructs a Geometry Exception with the given error string/message. + * + * @param str - The error string. + */ + public GeometryException(String str) { + super(str); + } + + static GeometryException GeometryInternalError() { + return new GeometryException("internal error"); + } } diff --git a/src/main/java/com/esri/core/geometry/GeometrySerializer.java b/src/main/java/com/esri/core/geometry/GeometrySerializer.java index 70f983f4..247a6746 100644 --- a/src/main/java/com/esri/core/geometry/GeometrySerializer.java +++ b/src/main/java/com/esri/core/geometry/GeometrySerializer.java @@ -30,89 +30,89 @@ //Left here for backward compatibility. Use GenericGeometrySerializer instead @Deprecated final class GeometrySerializer implements Serializable { - private static final long serialVersionUID = 1L; + private static final long serialVersionUID = 1L; - static class BaseGeometryData implements Serializable { - Geometry.Type geometryType; - byte[] esriShape = null; - } + static class BaseGeometryData implements Serializable { + Geometry.Type geometryType; + byte[] esriShape = null; + } - static class MultiVertexData extends BaseGeometryData { - int simpleFlag = 0; - double tolerance = 0; - } + static class MultiVertexData extends BaseGeometryData { + int simpleFlag = 0; + double tolerance = 0; + } - static class MultiPathData extends MultiVertexData { - boolean[] ogcFlags = null; - } + static class MultiPathData extends MultiVertexData { + boolean[] ogcFlags = null; + } - BaseGeometryData geometryData; + BaseGeometryData geometryData; - Object readResolve() throws ObjectStreamException { - Geometry geometry = null; - try { - geometry = GeometryEngine.geometryFromEsriShape( - geometryData.esriShape, geometryData.geometryType); - if (Geometry.isMultiVertex(geometry.getType().value())) { - MultiVertexData mvd = (MultiVertexData) geometryData; - MultiVertexGeometryImpl mvImpl = (MultiVertexGeometryImpl) geometry - ._getImpl(); - if (!geometry.isEmpty() - && Geometry.isMultiPath(geometry.getType().value())) { - MultiPathData mpd = (MultiPathData) geometryData; - MultiPathImpl mpImpl = (MultiPathImpl) geometry._getImpl(); - AttributeStreamOfInt8 pathFlags = mpImpl - .getPathFlagsStreamRef(); - for (int i = 0, n = mpImpl.getPathCount(); i < n; i++) { - if (mpd.ogcFlags[i]) - pathFlags.setBits(i, - (byte) PathFlags.enumOGCStartPolygon); - } - } - mvImpl.setIsSimple(mvd.simpleFlag, mvd.tolerance, false); - } + Object readResolve() throws ObjectStreamException { + Geometry geometry = null; + try { + geometry = GeometryEngine.geometryFromEsriShape( + geometryData.esriShape, geometryData.geometryType); + if (Geometry.isMultiVertex(geometry.getType().value())) { + MultiVertexData mvd = (MultiVertexData) geometryData; + MultiVertexGeometryImpl mvImpl = (MultiVertexGeometryImpl) geometry + ._getImpl(); + if (!geometry.isEmpty() + && Geometry.isMultiPath(geometry.getType().value())) { + MultiPathData mpd = (MultiPathData) geometryData; + MultiPathImpl mpImpl = (MultiPathImpl) geometry._getImpl(); + AttributeStreamOfInt8 pathFlags = mpImpl + .getPathFlagsStreamRef(); + for (int i = 0, n = mpImpl.getPathCount(); i < n; i++) { + if (mpd.ogcFlags[i]) + pathFlags.setBits(i, + (byte) PathFlags.enumOGCStartPolygon); + } + } + mvImpl.setIsSimple(mvd.simpleFlag, mvd.tolerance, false); + } - } catch (Exception ex) { - throw new InvalidObjectException("Cannot read geometry from stream"); - } - return geometry; - } + } catch (Exception ex) { + throw new InvalidObjectException("Cannot read geometry from stream"); + } + return geometry; + } - public void setGeometryByValue(Geometry geometry) - throws ObjectStreamException { - try { - if (Geometry.isMultiPath(geometry.getType().value())) { - geometryData = new MultiPathData(); - } else if (Geometry.isMultiVertex(geometry.getType().value())) { - geometryData = new MultiVertexData(); - } else { - geometryData = new BaseGeometryData(); - } - geometryData.esriShape = GeometryEngine - .geometryToEsriShape(geometry); - geometryData.geometryType = geometry.getType(); - if (Geometry.isMultiVertex(geometryData.geometryType.value())) { - MultiVertexData mvd = (MultiVertexData) geometryData; - MultiVertexGeometryImpl mvImpl = (MultiVertexGeometryImpl) geometry - ._getImpl(); - mvd.tolerance = mvImpl.m_simpleTolerance; - mvd.simpleFlag = mvImpl.getIsSimple(0); - if (!geometry.isEmpty() - && Geometry.isMultiPath(geometryData.geometryType - .value())) { - MultiPathData mpd = (MultiPathData) geometryData; - MultiPathImpl mpImpl = (MultiPathImpl) geometry._getImpl(); - mpd.ogcFlags = new boolean[mpImpl.getPathCount()]; - AttributeStreamOfInt8 pathFlags = mpImpl - .getPathFlagsStreamRef(); - for (int i = 0, n = mpImpl.getPathCount(); i < n; i++) { - mpd.ogcFlags[i] = (pathFlags.read(i) & (byte) PathFlags.enumOGCStartPolygon) != 0; - } - } + public void setGeometryByValue(Geometry geometry) + throws ObjectStreamException { + try { + if (Geometry.isMultiPath(geometry.getType().value())) { + geometryData = new MultiPathData(); + } else if (Geometry.isMultiVertex(geometry.getType().value())) { + geometryData = new MultiVertexData(); + } else { + geometryData = new BaseGeometryData(); + } + geometryData.esriShape = GeometryEngine + .geometryToEsriShape(geometry); + geometryData.geometryType = geometry.getType(); + if (Geometry.isMultiVertex(geometryData.geometryType.value())) { + MultiVertexData mvd = (MultiVertexData) geometryData; + MultiVertexGeometryImpl mvImpl = (MultiVertexGeometryImpl) geometry + ._getImpl(); + mvd.tolerance = mvImpl.m_simpleTolerance; + mvd.simpleFlag = mvImpl.getIsSimple(0); + if (!geometry.isEmpty() + && Geometry.isMultiPath(geometryData.geometryType + .value())) { + MultiPathData mpd = (MultiPathData) geometryData; + MultiPathImpl mpImpl = (MultiPathImpl) geometry._getImpl(); + mpd.ogcFlags = new boolean[mpImpl.getPathCount()]; + AttributeStreamOfInt8 pathFlags = mpImpl + .getPathFlagsStreamRef(); + for (int i = 0, n = mpImpl.getPathCount(); i < n; i++) { + mpd.ogcFlags[i] = (pathFlags.read(i) & (byte) PathFlags.enumOGCStartPolygon) != 0; + } + } - } - } catch (Exception ex) { - throw new InvalidObjectException("Cannot serialize this geometry"); - } - } + } + } catch (Exception ex) { + throw new InvalidObjectException("Cannot serialize this geometry"); + } + } } diff --git a/src/main/java/com/esri/core/geometry/IndexHashTable.java b/src/main/java/com/esri/core/geometry/IndexHashTable.java index 8ef6bfce..d95c9af4 100644 --- a/src/main/java/com/esri/core/geometry/IndexHashTable.java +++ b/src/main/java/com/esri/core/geometry/IndexHashTable.java @@ -26,252 +26,252 @@ import java.util.Arrays; final class IndexHashTable { - // The hash function abstract class that user need to define to use the - // IndexHashTable. - public static abstract class HashFunction { - public abstract int getHash(int element); - - public abstract boolean equal(int element1, int element2); - - public abstract int getHash(Object elementDescriptor); - - public abstract boolean equal(Object elementDescriptor, int element); - } - - int m_random; - AttributeStreamOfInt32 m_hashBuckets; - int[] m_bit_filter; //this is aimed to speedup the find - //operation and allows to have less buckets. - IndexMultiList m_lists; - HashFunction m_hash; - - // Create hash table. size is the bin count in the table. The hashFunction - // is the function to use. - public IndexHashTable(int size, HashFunction hashFunction) { - m_hashBuckets = new AttributeStreamOfInt32(size, nullNode()); - m_lists = new IndexMultiList(); - m_hash = hashFunction; - m_bit_filter = new int[(size * 10 + 31) >> 5]; //10 times more bits than buckets - } - - public void reserveElements(int capacity) { - m_lists.reserveLists(Math.min(m_hashBuckets.size(), capacity)); - m_lists.reserveNodes(capacity); - } - - // Adds new element to the hash table. - public int addElement(int element, int hash) { - int bit_bucket = hash % (m_bit_filter.length << 5); - m_bit_filter[(bit_bucket >> 5)] |= (1 << (bit_bucket & 0x1F)); - int bucket = hash % m_hashBuckets.size(); - int list = m_hashBuckets.get(bucket); - if (list == -1) { - list = m_lists.createList(); - m_hashBuckets.set(bucket, list); - } - int node = m_lists.addElement(list, element); - return node; - } - - public int addElement(int element) { - int hash = m_hash.getHash(element); - int bit_bucket = hash % (m_bit_filter.length << 5); - m_bit_filter[(bit_bucket >> 5)] |= (1 << (bit_bucket & 0x1F)); - int bucket = hash % m_hashBuckets.size(); - int list = m_hashBuckets.get(bucket); - if (list == -1) { - list = m_lists.createList(); - m_hashBuckets.set(bucket, list); - } - int node = m_lists.addElement(list, element); - return node; - } - - public void deleteElement(int element, int hash) { - int bucket = hash % m_hashBuckets.size(); - int list = m_hashBuckets.get(bucket); - if (list == -1) - throw new IllegalArgumentException(); - - int ptr = m_lists.getFirst(list); - int prev = -1; - while (ptr != -1) { - int e = m_lists.getElement(ptr); - int nextptr = m_lists.getNext(ptr); - if (e == element) { - m_lists.deleteElement(list, prev, ptr); - if (m_lists.getFirst(list) == -1) { - m_lists.deleteList(list);// do not keep empty lists - m_hashBuckets.set(bucket, -1); - } - } else { - prev = ptr; - } - ptr = nextptr; - } - - } - - // Removes element from the hash table. - public void deleteElement(int element) { - int hash = m_hash.getHash(element); - int bucket = hash % m_hashBuckets.size(); - int list = m_hashBuckets.get(bucket); - if (list == -1) - throw new IllegalArgumentException(); - - int ptr = m_lists.getFirst(list); - int prev = -1; - while (ptr != -1) { - int e = m_lists.getElement(ptr); - int nextptr = m_lists.getNext(ptr); - if (e == element) { - m_lists.deleteElement(list, prev, ptr); - if (m_lists.getFirst(list) == -1) { - m_lists.deleteList(list);// do not keep empty lists - m_hashBuckets.set(bucket, -1); - } - } else { - prev = ptr; - } - ptr = nextptr; - } - - } - - // Returns the first node in the hash table bucket defined by the given - // hashValue. - public int getFirstInBucket(int hashValue) { - int bit_bucket = hashValue % (m_bit_filter.length << 5); - if ((m_bit_filter[(bit_bucket >> 5)] & (1 << (bit_bucket & 0x1F))) == 0) - return -1; - - int bucket = hashValue % m_hashBuckets.size(); - int list = m_hashBuckets.get(bucket); - if (list == -1) - return -1; - - return m_lists.getFirst(list); - - } - - // Returns next node in a bucket. Can be used together with GetFirstInBucket - // only. - public int getNextInBucket(int elementHandle) { - return m_lists.getNext(elementHandle); - } - - // Returns a node of the first element in the hash table, that is equal to - // the given one. - public int findNode(int element) { - int hash = m_hash.getHash(element); - int ptr = getFirstInBucket(hash); - while (ptr != -1) { - int e = m_lists.getElement(ptr); - if (m_hash.equal(e, element)) { - return ptr; - } - ptr = m_lists.getNext(ptr); - } - - return -1; - - } - - // Returns a node to the first element in the hash table, that is equal to - // the given element descriptor. - public int findNode(Object elementDescriptor) { - int hash = m_hash.getHash(elementDescriptor); - int ptr = getFirstInBucket(hash); - ; - while (ptr != -1) { - int e = m_lists.getElement(ptr); - if (m_hash.equal(elementDescriptor, e)) { - return ptr; - } - ptr = m_lists.getNext(ptr); - } - - return -1; - - } - - // Gets next equal node. - public int getNextNode(int elementHandle) { - int element = m_lists.getElement(elementHandle); - int ptr = m_lists.getNext(elementHandle); - while (ptr != -1) { - int e = m_lists.getElement(ptr); - if (m_hash.equal(e, element)) { - return ptr; - } - ptr = m_lists.getNext(ptr); - } - - return -1; - - } - - // Removes a node. - public void deleteNode(int node) { - int element = getElement(node); - int hash = m_hash.getHash(element); - int bucket = hash % m_hashBuckets.size(); - int list = m_hashBuckets.get(bucket); - if (list == -1) - throw new IllegalArgumentException(); - - int ptr = m_lists.getFirst(list); - int prev = -1; - while (ptr != -1) { - if (ptr == node) { - m_lists.deleteElement(list, prev, ptr); - if (m_lists.getFirst(list) == -1) { - m_lists.deleteList(list);// do not keep empty lists - m_hashBuckets.set(bucket, -1); - } - return; - } - prev = ptr; - ptr = m_lists.getNext(ptr); - } - - throw new IllegalArgumentException(); - - } - - // Returns a value of the element stored in the given node. - public int getElement(int elementHandle) { - return m_lists.getElement(elementHandle); - } - - // Returns any existing element from the hash table. Throws if the table is - // empty. - public int getAnyElement() { - return m_lists.getFirstElement(m_lists.getFirstList()); - } - - // Returns a node for any existing element from the hash table or NullNode - // if the table is empty. - public int getAnyNode() { - return m_lists.getFirst(m_lists.getFirstList()); - } - - public static int nullNode() { - return -1; - } - - // Removes all elements from the hash table. - public void clear() { - Arrays.fill(m_bit_filter, 0); - m_hashBuckets = new AttributeStreamOfInt32(m_hashBuckets.size(), - nullNode()); - m_lists.clear(); - - } - - // Returns the number of elements in the hash table - public int size() { - return m_lists.getNodeCount(); - } + // The hash function abstract class that user need to define to use the + // IndexHashTable. + public static abstract class HashFunction { + public abstract int getHash(int element); + + public abstract boolean equal(int element1, int element2); + + public abstract int getHash(Object elementDescriptor); + + public abstract boolean equal(Object elementDescriptor, int element); + } + + int m_random; + AttributeStreamOfInt32 m_hashBuckets; + int[] m_bit_filter; //this is aimed to speedup the find + //operation and allows to have less buckets. + IndexMultiList m_lists; + HashFunction m_hash; + + // Create hash table. size is the bin count in the table. The hashFunction + // is the function to use. + public IndexHashTable(int size, HashFunction hashFunction) { + m_hashBuckets = new AttributeStreamOfInt32(size, nullNode()); + m_lists = new IndexMultiList(); + m_hash = hashFunction; + m_bit_filter = new int[(size * 10 + 31) >> 5]; //10 times more bits than buckets + } + + public void reserveElements(int capacity) { + m_lists.reserveLists(Math.min(m_hashBuckets.size(), capacity)); + m_lists.reserveNodes(capacity); + } + + // Adds new element to the hash table. + public int addElement(int element, int hash) { + int bit_bucket = hash % (m_bit_filter.length << 5); + m_bit_filter[(bit_bucket >> 5)] |= (1 << (bit_bucket & 0x1F)); + int bucket = hash % m_hashBuckets.size(); + int list = m_hashBuckets.get(bucket); + if (list == -1) { + list = m_lists.createList(); + m_hashBuckets.set(bucket, list); + } + int node = m_lists.addElement(list, element); + return node; + } + + public int addElement(int element) { + int hash = m_hash.getHash(element); + int bit_bucket = hash % (m_bit_filter.length << 5); + m_bit_filter[(bit_bucket >> 5)] |= (1 << (bit_bucket & 0x1F)); + int bucket = hash % m_hashBuckets.size(); + int list = m_hashBuckets.get(bucket); + if (list == -1) { + list = m_lists.createList(); + m_hashBuckets.set(bucket, list); + } + int node = m_lists.addElement(list, element); + return node; + } + + public void deleteElement(int element, int hash) { + int bucket = hash % m_hashBuckets.size(); + int list = m_hashBuckets.get(bucket); + if (list == -1) + throw new IllegalArgumentException(); + + int ptr = m_lists.getFirst(list); + int prev = -1; + while (ptr != -1) { + int e = m_lists.getElement(ptr); + int nextptr = m_lists.getNext(ptr); + if (e == element) { + m_lists.deleteElement(list, prev, ptr); + if (m_lists.getFirst(list) == -1) { + m_lists.deleteList(list);// do not keep empty lists + m_hashBuckets.set(bucket, -1); + } + } else { + prev = ptr; + } + ptr = nextptr; + } + + } + + // Removes element from the hash table. + public void deleteElement(int element) { + int hash = m_hash.getHash(element); + int bucket = hash % m_hashBuckets.size(); + int list = m_hashBuckets.get(bucket); + if (list == -1) + throw new IllegalArgumentException(); + + int ptr = m_lists.getFirst(list); + int prev = -1; + while (ptr != -1) { + int e = m_lists.getElement(ptr); + int nextptr = m_lists.getNext(ptr); + if (e == element) { + m_lists.deleteElement(list, prev, ptr); + if (m_lists.getFirst(list) == -1) { + m_lists.deleteList(list);// do not keep empty lists + m_hashBuckets.set(bucket, -1); + } + } else { + prev = ptr; + } + ptr = nextptr; + } + + } + + // Returns the first node in the hash table bucket defined by the given + // hashValue. + public int getFirstInBucket(int hashValue) { + int bit_bucket = hashValue % (m_bit_filter.length << 5); + if ((m_bit_filter[(bit_bucket >> 5)] & (1 << (bit_bucket & 0x1F))) == 0) + return -1; + + int bucket = hashValue % m_hashBuckets.size(); + int list = m_hashBuckets.get(bucket); + if (list == -1) + return -1; + + return m_lists.getFirst(list); + + } + + // Returns next node in a bucket. Can be used together with GetFirstInBucket + // only. + public int getNextInBucket(int elementHandle) { + return m_lists.getNext(elementHandle); + } + + // Returns a node of the first element in the hash table, that is equal to + // the given one. + public int findNode(int element) { + int hash = m_hash.getHash(element); + int ptr = getFirstInBucket(hash); + while (ptr != -1) { + int e = m_lists.getElement(ptr); + if (m_hash.equal(e, element)) { + return ptr; + } + ptr = m_lists.getNext(ptr); + } + + return -1; + + } + + // Returns a node to the first element in the hash table, that is equal to + // the given element descriptor. + public int findNode(Object elementDescriptor) { + int hash = m_hash.getHash(elementDescriptor); + int ptr = getFirstInBucket(hash); + ; + while (ptr != -1) { + int e = m_lists.getElement(ptr); + if (m_hash.equal(elementDescriptor, e)) { + return ptr; + } + ptr = m_lists.getNext(ptr); + } + + return -1; + + } + + // Gets next equal node. + public int getNextNode(int elementHandle) { + int element = m_lists.getElement(elementHandle); + int ptr = m_lists.getNext(elementHandle); + while (ptr != -1) { + int e = m_lists.getElement(ptr); + if (m_hash.equal(e, element)) { + return ptr; + } + ptr = m_lists.getNext(ptr); + } + + return -1; + + } + + // Removes a node. + public void deleteNode(int node) { + int element = getElement(node); + int hash = m_hash.getHash(element); + int bucket = hash % m_hashBuckets.size(); + int list = m_hashBuckets.get(bucket); + if (list == -1) + throw new IllegalArgumentException(); + + int ptr = m_lists.getFirst(list); + int prev = -1; + while (ptr != -1) { + if (ptr == node) { + m_lists.deleteElement(list, prev, ptr); + if (m_lists.getFirst(list) == -1) { + m_lists.deleteList(list);// do not keep empty lists + m_hashBuckets.set(bucket, -1); + } + return; + } + prev = ptr; + ptr = m_lists.getNext(ptr); + } + + throw new IllegalArgumentException(); + + } + + // Returns a value of the element stored in the given node. + public int getElement(int elementHandle) { + return m_lists.getElement(elementHandle); + } + + // Returns any existing element from the hash table. Throws if the table is + // empty. + public int getAnyElement() { + return m_lists.getFirstElement(m_lists.getFirstList()); + } + + // Returns a node for any existing element from the hash table or NullNode + // if the table is empty. + public int getAnyNode() { + return m_lists.getFirst(m_lists.getFirstList()); + } + + public static int nullNode() { + return -1; + } + + // Removes all elements from the hash table. + public void clear() { + Arrays.fill(m_bit_filter, 0); + m_hashBuckets = new AttributeStreamOfInt32(m_hashBuckets.size(), + nullNode()); + m_lists.clear(); + + } + + // Returns the number of elements in the hash table + public int size() { + return m_lists.getNodeCount(); + } } diff --git a/src/main/java/com/esri/core/geometry/IndexMultiDCList.java b/src/main/java/com/esri/core/geometry/IndexMultiDCList.java index 4b4afeae..0e75999a 100644 --- a/src/main/java/com/esri/core/geometry/IndexMultiDCList.java +++ b/src/main/java/com/esri/core/geometry/IndexMultiDCList.java @@ -25,286 +25,286 @@ class IndexMultiDCList { - StridedIndexTypeCollection m_list_nodes; // stores lists and list elements. - // Each list element is Index, - // Prev, next. - StridedIndexTypeCollection m_lists; // stores lists. Each list is Head, - // Tail, PrevList, NextList, NodeCount, - // ListData. - int m_list_of_lists; - boolean m_b_store_list_index_with_node; - - void freeNode_(int node) { - m_list_nodes.deleteElement(node); - } - - int newNode_() { - int node = m_list_nodes.newElement(); - return node; - } - - void freeList_(int list) { - m_lists.deleteElement(list); - } - - int newList_() { - int list = m_lists.newElement(); - return list; - } - - void setPrev_(int node, int prev) { - m_list_nodes.setField(node, 1, prev); - } - - void setNext_(int node, int next) { - m_list_nodes.setField(node, 2, next); - } - - void setData_(int node, int data) { - m_list_nodes.setField(node, 0, data); - } - - void setList_(int node, int list) { - m_list_nodes.setField(node, 3, list); - } - - void setListSize_(int list, int newsize) { - m_lists.setField(list, 4, newsize); - } - - void setNextList_(int list, int next) { - m_lists.setField(list, 3, next); - } - - void setPrevList_(int list, int prev) { - m_lists.setField(list, 2, prev); - } - - // Same as Index_multi_dc_list(true). - IndexMultiDCList() { - m_list_nodes = new StridedIndexTypeCollection(3); - m_lists = new StridedIndexTypeCollection(6); - m_b_store_list_index_with_node = false; - m_list_of_lists = nullNode(); - } - - // When bStoreListIndexWithNode is true, the each node stores a pointer to - // the list. Otherwise it does not. - // The get_list() method cannot be used if bStoreListIndexWithNode is false. - IndexMultiDCList(boolean b_store_list_index_with_node) { - m_list_nodes = new StridedIndexTypeCollection(3); - m_lists = new StridedIndexTypeCollection(6); - m_b_store_list_index_with_node = false; - m_list_of_lists = nullNode(); - } - - // Creates new list and returns it's handle. - // listData is user's info associated with the list - int createList(int listData) { - int list = newList_(); - // m_lists.set_field(list, 0, null_node());//head - // m_lists.set_field(list, 1, null_node());//tail - // m_lists.set_field(list, 2, null_node());//prev list - m_lists.setField(list, 3, m_list_of_lists); // next list - m_lists.setField(list, 4, 0);// node count in the list - m_lists.setField(list, 5, listData); - if (m_list_of_lists != nullNode()) - setPrevList_(m_list_of_lists, list); - - m_list_of_lists = list; - return list; - } - - // Deletes a list and returns the index of the next list. - int deleteList(int list) { - clear(list); - int prevList = m_lists.getField(list, 2); - int nextList = m_lists.getField(list, 3); - if (prevList != nullNode()) - setNextList_(prevList, nextList); - else - m_list_of_lists = nextList; - - if (nextList != nullNode()) - setPrevList_(nextList, prevList); - - freeList_(list); - return nextList; - } - - // Reserves memory for the given number of lists. - void reserveLists(int listCount) { - m_lists.setCapacity(listCount); - } - - // returns user's data associated with the list - int getListData(int list) { - return m_lists.getField(list, 5); - } - - // returns the list associated with the node_index. Do not use if list is - // created with bStoreListIndexWithNode == false. - int getList(int node_index) { - assert (m_b_store_list_index_with_node); - return m_list_nodes.getField(node_index, 3); - } - - // sets the user data to the list - void setListData(int list, int data) { - m_lists.setField(list, 5, data); - } - - // Adds element to a given list. The element is added to the end. Returns - // the new - int addElement(int list, int data) { - return insertElement(list, -1, data); - } - - // Inserts a new node before the given one . - int insertElement(int list, int beforeNode, int data) { - int node = newNode_(); - int prev = -1; - if (beforeNode != nullNode()) { - prev = getPrev(beforeNode); - setPrev_(beforeNode, node); - } - - setNext_(node, beforeNode); - if (prev != nullNode()) - setNext_(prev, node); - - int head = m_lists.getField(list, 0); - - if (beforeNode == head) - m_lists.setField(list, 0, node); - if (beforeNode == nullNode()) { - int tail = m_lists.getField(list, 1); - setPrev_(node, tail); - if (tail != -1) - setNext_(tail, node); - - m_lists.setField(list, 1, node); - } - - setData(node, data); - setListSize_(list, getListSize(list) + 1); - - if (m_b_store_list_index_with_node) - setList_(node, list); - - return node; - } - - // Deletes a node from a list. Returns the next node after the deleted one. - int deleteElement(int list, int node) { - int prev = getPrev(node); - int next = getNext(node); - if (prev != nullNode()) - setNext_(prev, next); - else - m_lists.setField(list, 0, next);// change head - if (next != nullNode()) - setPrev_(next, prev); - else - m_lists.setField(list, 1, prev);// change tail - - freeNode_(node); - setListSize_(list, getListSize(list) - 1); - return next; - } - - // Reserves memory for the given number of nodes. - void reserveNodes(int nodeCount) { - m_list_nodes.setCapacity(nodeCount); - } - - // Returns the data from the given list node. - int getData(int node_index) { - return m_list_nodes.getField(node_index, 0); - } - - // Sets the data to the given list node. - void setData(int node_index, int element) { - m_list_nodes.setField(node_index, 0, element); - } - - // Returns index of next node for the give node. - int getNext(int node_index) { - return m_list_nodes.getField(node_index, 2); - } - - // Returns index of previous node for the give node. - int getPrev(int node_index) { - return m_list_nodes.getField(node_index, 1); - } - - // Returns the first node in the list - int getFirst(int list) { - return m_lists.getField(list, 0); - } - - // Returns the last node in the list - int getLast(int list) { - return m_lists.getField(list, 1); - } - - // Check if the node is Null (does not exist) - static int nullNode() { - return -1; - } - - // Clears all nodes and removes all lists. - void clear() { - for (int list = getFirstList(); list != -1; ) { - list = deleteList(list); - } - } - - // Clears all nodes from the list. - void clear(int list) { - int last = getLast(list); - while (last != nullNode()) { - int n = last; - last = getPrev(n); - freeNode_(n); - } - m_lists.setField(list, 0, -1); - m_lists.setField(list, 1, -1); - setListSize_(list, 0); - } - - // Returns True if the given list is empty. - boolean isEmpty(int list) { - return m_lists.getField(list, 0) == -1; - } - - // Returns True if the multilist is empty - boolean isEmpty() { - return m_list_nodes.size() == 0; - } - - // Returns node count in all lists - int getNodeCount() { - return m_list_nodes.size(); - } - - // returns the number of lists - int getListCount() { - return m_lists.size(); - } - - // Returns the node count in the given list - int getListSize(int list) { - return m_lists.getField(list, 4); - } - - // returns the first list - int getFirstList() { - return m_list_of_lists; - } - - // returns the next list - int getNextList(int list) { - return m_lists.getField(list, 3); - } + StridedIndexTypeCollection m_list_nodes; // stores lists and list elements. + // Each list element is Index, + // Prev, next. + StridedIndexTypeCollection m_lists; // stores lists. Each list is Head, + // Tail, PrevList, NextList, NodeCount, + // ListData. + int m_list_of_lists; + boolean m_b_store_list_index_with_node; + + void freeNode_(int node) { + m_list_nodes.deleteElement(node); + } + + int newNode_() { + int node = m_list_nodes.newElement(); + return node; + } + + void freeList_(int list) { + m_lists.deleteElement(list); + } + + int newList_() { + int list = m_lists.newElement(); + return list; + } + + void setPrev_(int node, int prev) { + m_list_nodes.setField(node, 1, prev); + } + + void setNext_(int node, int next) { + m_list_nodes.setField(node, 2, next); + } + + void setData_(int node, int data) { + m_list_nodes.setField(node, 0, data); + } + + void setList_(int node, int list) { + m_list_nodes.setField(node, 3, list); + } + + void setListSize_(int list, int newsize) { + m_lists.setField(list, 4, newsize); + } + + void setNextList_(int list, int next) { + m_lists.setField(list, 3, next); + } + + void setPrevList_(int list, int prev) { + m_lists.setField(list, 2, prev); + } + + // Same as Index_multi_dc_list(true). + IndexMultiDCList() { + m_list_nodes = new StridedIndexTypeCollection(3); + m_lists = new StridedIndexTypeCollection(6); + m_b_store_list_index_with_node = false; + m_list_of_lists = nullNode(); + } + + // When bStoreListIndexWithNode is true, the each node stores a pointer to + // the list. Otherwise it does not. + // The get_list() method cannot be used if bStoreListIndexWithNode is false. + IndexMultiDCList(boolean b_store_list_index_with_node) { + m_list_nodes = new StridedIndexTypeCollection(3); + m_lists = new StridedIndexTypeCollection(6); + m_b_store_list_index_with_node = false; + m_list_of_lists = nullNode(); + } + + // Creates new list and returns it's handle. + // listData is user's info associated with the list + int createList(int listData) { + int list = newList_(); + // m_lists.set_field(list, 0, null_node());//head + // m_lists.set_field(list, 1, null_node());//tail + // m_lists.set_field(list, 2, null_node());//prev list + m_lists.setField(list, 3, m_list_of_lists); // next list + m_lists.setField(list, 4, 0);// node count in the list + m_lists.setField(list, 5, listData); + if (m_list_of_lists != nullNode()) + setPrevList_(m_list_of_lists, list); + + m_list_of_lists = list; + return list; + } + + // Deletes a list and returns the index of the next list. + int deleteList(int list) { + clear(list); + int prevList = m_lists.getField(list, 2); + int nextList = m_lists.getField(list, 3); + if (prevList != nullNode()) + setNextList_(prevList, nextList); + else + m_list_of_lists = nextList; + + if (nextList != nullNode()) + setPrevList_(nextList, prevList); + + freeList_(list); + return nextList; + } + + // Reserves memory for the given number of lists. + void reserveLists(int listCount) { + m_lists.setCapacity(listCount); + } + + // returns user's data associated with the list + int getListData(int list) { + return m_lists.getField(list, 5); + } + + // returns the list associated with the node_index. Do not use if list is + // created with bStoreListIndexWithNode == false. + int getList(int node_index) { + assert (m_b_store_list_index_with_node); + return m_list_nodes.getField(node_index, 3); + } + + // sets the user data to the list + void setListData(int list, int data) { + m_lists.setField(list, 5, data); + } + + // Adds element to a given list. The element is added to the end. Returns + // the new + int addElement(int list, int data) { + return insertElement(list, -1, data); + } + + // Inserts a new node before the given one . + int insertElement(int list, int beforeNode, int data) { + int node = newNode_(); + int prev = -1; + if (beforeNode != nullNode()) { + prev = getPrev(beforeNode); + setPrev_(beforeNode, node); + } + + setNext_(node, beforeNode); + if (prev != nullNode()) + setNext_(prev, node); + + int head = m_lists.getField(list, 0); + + if (beforeNode == head) + m_lists.setField(list, 0, node); + if (beforeNode == nullNode()) { + int tail = m_lists.getField(list, 1); + setPrev_(node, tail); + if (tail != -1) + setNext_(tail, node); + + m_lists.setField(list, 1, node); + } + + setData(node, data); + setListSize_(list, getListSize(list) + 1); + + if (m_b_store_list_index_with_node) + setList_(node, list); + + return node; + } + + // Deletes a node from a list. Returns the next node after the deleted one. + int deleteElement(int list, int node) { + int prev = getPrev(node); + int next = getNext(node); + if (prev != nullNode()) + setNext_(prev, next); + else + m_lists.setField(list, 0, next);// change head + if (next != nullNode()) + setPrev_(next, prev); + else + m_lists.setField(list, 1, prev);// change tail + + freeNode_(node); + setListSize_(list, getListSize(list) - 1); + return next; + } + + // Reserves memory for the given number of nodes. + void reserveNodes(int nodeCount) { + m_list_nodes.setCapacity(nodeCount); + } + + // Returns the data from the given list node. + int getData(int node_index) { + return m_list_nodes.getField(node_index, 0); + } + + // Sets the data to the given list node. + void setData(int node_index, int element) { + m_list_nodes.setField(node_index, 0, element); + } + + // Returns index of next node for the give node. + int getNext(int node_index) { + return m_list_nodes.getField(node_index, 2); + } + + // Returns index of previous node for the give node. + int getPrev(int node_index) { + return m_list_nodes.getField(node_index, 1); + } + + // Returns the first node in the list + int getFirst(int list) { + return m_lists.getField(list, 0); + } + + // Returns the last node in the list + int getLast(int list) { + return m_lists.getField(list, 1); + } + + // Check if the node is Null (does not exist) + static int nullNode() { + return -1; + } + + // Clears all nodes and removes all lists. + void clear() { + for (int list = getFirstList(); list != -1; ) { + list = deleteList(list); + } + } + + // Clears all nodes from the list. + void clear(int list) { + int last = getLast(list); + while (last != nullNode()) { + int n = last; + last = getPrev(n); + freeNode_(n); + } + m_lists.setField(list, 0, -1); + m_lists.setField(list, 1, -1); + setListSize_(list, 0); + } + + // Returns True if the given list is empty. + boolean isEmpty(int list) { + return m_lists.getField(list, 0) == -1; + } + + // Returns True if the multilist is empty + boolean isEmpty() { + return m_list_nodes.size() == 0; + } + + // Returns node count in all lists + int getNodeCount() { + return m_list_nodes.size(); + } + + // returns the number of lists + int getListCount() { + return m_lists.size(); + } + + // Returns the node count in the given list + int getListSize(int list) { + return m_lists.getField(list, 4); + } + + // returns the first list + int getFirstList() { + return m_list_of_lists; + } + + // returns the next list + int getNextList(int list) { + return m_lists.getField(list, 3); + } } diff --git a/src/main/java/com/esri/core/geometry/IndexMultiList.java b/src/main/java/com/esri/core/geometry/IndexMultiList.java index 88c19f1c..5a881481 100644 --- a/src/main/java/com/esri/core/geometry/IndexMultiList.java +++ b/src/main/java/com/esri/core/geometry/IndexMultiList.java @@ -25,242 +25,242 @@ class IndexMultiList { - StridedIndexTypeCollection m_listNodes; // stores lists and list elements. - // Each list element is Index, next. - StridedIndexTypeCollection m_lists; // stores lists. Each list is Head, - // Tail, [PrevList, NextList]. - int m_list_of_lists; - boolean m_b_allow_navigation_between_lists;// when False, get_first_list, - // get_next_list return -1. - - void freeNode_(int node) { - m_listNodes.deleteElement(node); - } - - int newNode_() { - int node = m_listNodes.newElement(); - return node; - } - - void freeList_(int list) { - m_lists.deleteElement(list); - } - - int newList_() { - int list = m_lists.newElement(); - return list; - } - - // Same as Index_multi_list(true); - IndexMultiList() { - m_listNodes = new StridedIndexTypeCollection(2); - m_lists = new StridedIndexTypeCollection(4); - m_list_of_lists = nullNode(); - m_b_allow_navigation_between_lists = true; - } - - // When b_allow_navigation_between_lists is False, the get_first_list and - // get_next_list do not work. - // There will be two Index_type elements per list and two Index_type - // elements per list element - // When b_allow_navigation_between_lists is True, the get_first_list and - // get_next_list will work. - // There will be four Index_type elements per list and two Index_type - // elements per list element - IndexMultiList(boolean b_allow_navigation_between_lists) { - m_listNodes = new StridedIndexTypeCollection(2); - m_lists = new StridedIndexTypeCollection( - b_allow_navigation_between_lists ? 4 : 2); - m_list_of_lists = nullNode(); - m_b_allow_navigation_between_lists = b_allow_navigation_between_lists; - } - - // Creates new list and returns it's handle. - int createList() { - int node = newList_(); - if (m_b_allow_navigation_between_lists) { - m_lists.setField(node, 3, m_list_of_lists); - if (m_list_of_lists != nullNode()) - m_lists.setField(m_list_of_lists, 2, node); - m_list_of_lists = node; - } - - return node; - } - - // Deletes a list. - void deleteList(int list) { - int ptr = getFirst(list); - while (ptr != nullNode()) { - int p = ptr; - ptr = getNext(ptr); - freeNode_(p); - } - - if (m_b_allow_navigation_between_lists) { - int prevList = m_lists.getField(list, 2); - int nextList = m_lists.getField(list, 3); - if (prevList != nullNode()) - m_lists.setField(prevList, 3, nextList); - else - m_list_of_lists = nextList; - - if (nextList != nullNode()) - m_lists.setField(nextList, 2, prevList); - } - - freeList_(list); - } - - // Reserves memory for the given number of lists. - void reserveLists(int listCount) { - m_lists.setCapacity(listCount); - } - - // Adds element to a given list. The element is added to the end. Returns - // the new - int addElement(int list, int element) { - int head = m_lists.getField(list, 0); - int tail = m_lists.getField(list, 1); - int node = newNode_(); - if (tail != nullNode()) { - assert (head != nullNode()); - m_listNodes.setField(tail, 1, node); - m_lists.setField(list, 1, node); - } else {// empty list - assert (head == nullNode()); - m_lists.setField(list, 0, node); - m_lists.setField(list, 1, node); - } - - m_listNodes.setField(node, 0, element); - return node; - } - - // Reserves memory for the given number of nodes. - void reserveNodes(int nodeCount) { - m_listNodes.setCapacity(nodeCount); - } - - // Deletes a node from a list, given the previous node (previous node is - // required, because the list is singly connected). - void deleteElement(int list, int prevNode, int node) { - if (prevNode != nullNode()) { - assert (m_listNodes.getField(prevNode, 1) == node); - m_listNodes.setField(prevNode, 1, m_listNodes.getField(node, 1)); - if (m_lists.getField(list, 1) == node)// deleting a tail - { - m_lists.setField(list, 1, prevNode); - } - } else { - assert (m_lists.getField(list, 0) == node); - m_lists.setField(list, 0, m_listNodes.getField(node, 1)); - if (m_lists.getField(list, 1) == node) {// removing last element - assert (m_listNodes.getField(node, 1) == nullNode()); - m_lists.setField(list, 1, nullNode()); - } - } - freeNode_(node); - } - - // Concatenates list1 and list2. The nodes of list2 are added to the end of - // list1. The list2 index becomes invalid. - // Returns list1. - int concatenateLists(int list1, int list2) { - int tailNode1 = m_lists.getField(list1, 1); - int headNode2 = m_lists.getField(list2, 0); - if (headNode2 != nullNode())// do not concatenate empty lists - { - if (tailNode1 != nullNode()) { - // connect head of list2 to the tail of list1. - m_listNodes.setField(tailNode1, 1, headNode2); - // set the tail of the list1 to be the tail of list2. - m_lists.setField(list1, 1, m_lists.getField(list2, 1)); - } else {// list1 is empty, while list2 is not. - m_lists.setField(list1, 0, headNode2); - m_lists.setField(list1, 1, m_lists.getField(list2, 1)); - } - } - - if (m_b_allow_navigation_between_lists) { - int prevList = m_lists.getField(list2, 2); - int nextList = m_lists.getField(list2, 3); - if (prevList != nullNode()) - m_lists.setField(prevList, 3, nextList); - else - m_list_of_lists = nextList; - - if (nextList != nullNode()) - m_lists.setField(nextList, 2, prevList); - } - - freeList_(list2); - return list1; - } - - // Returns the data from the given list node. - int getElement(int node_index) { - return m_listNodes.getField(node_index, 0); - } - - // Sets the data to the given list node. - void setElement(int node_index, int element) { - m_listNodes.setField(node_index, 0, element); - } - - // Returns index of next node for the give node. - int getNext(int node_index) { - return m_listNodes.getField(node_index, 1); - } - - // Returns the first node in the least - int getFirst(int list) { - return m_lists.getField(list, 0); - } - - // Returns the element from the first node in the least. Equivalent to - // get_element(get_first(list)); - int getFirstElement(int list) { - int f = getFirst(list); - return getElement(f); - } - - // Check if the node is Null (does not exist) - static int nullNode() { - return -1; - } - - // Clears all nodes and removes all lists. Frees the memory. - void clear() { - m_listNodes.deleteAll(true); - m_lists.deleteAll(true); - m_list_of_lists = nullNode(); - } - - // Returns True if the given list is empty. - boolean isEmpty(int list) { - return m_lists.getField(list, 0) == nullNode(); - } - - boolean isEmpty() { - return m_listNodes.size() == 0; - } - - int getNodeCount() { - return m_listNodes.size(); - } - - int getListCount() { - return m_lists.size(); - } - - int getFirstList() { - assert (m_b_allow_navigation_between_lists); - return m_list_of_lists; - } - - int getNextList(int list) { - assert (m_b_allow_navigation_between_lists); - return m_lists.getField(list, 3); - } + StridedIndexTypeCollection m_listNodes; // stores lists and list elements. + // Each list element is Index, next. + StridedIndexTypeCollection m_lists; // stores lists. Each list is Head, + // Tail, [PrevList, NextList]. + int m_list_of_lists; + boolean m_b_allow_navigation_between_lists;// when False, get_first_list, + // get_next_list return -1. + + void freeNode_(int node) { + m_listNodes.deleteElement(node); + } + + int newNode_() { + int node = m_listNodes.newElement(); + return node; + } + + void freeList_(int list) { + m_lists.deleteElement(list); + } + + int newList_() { + int list = m_lists.newElement(); + return list; + } + + // Same as Index_multi_list(true); + IndexMultiList() { + m_listNodes = new StridedIndexTypeCollection(2); + m_lists = new StridedIndexTypeCollection(4); + m_list_of_lists = nullNode(); + m_b_allow_navigation_between_lists = true; + } + + // When b_allow_navigation_between_lists is False, the get_first_list and + // get_next_list do not work. + // There will be two Index_type elements per list and two Index_type + // elements per list element + // When b_allow_navigation_between_lists is True, the get_first_list and + // get_next_list will work. + // There will be four Index_type elements per list and two Index_type + // elements per list element + IndexMultiList(boolean b_allow_navigation_between_lists) { + m_listNodes = new StridedIndexTypeCollection(2); + m_lists = new StridedIndexTypeCollection( + b_allow_navigation_between_lists ? 4 : 2); + m_list_of_lists = nullNode(); + m_b_allow_navigation_between_lists = b_allow_navigation_between_lists; + } + + // Creates new list and returns it's handle. + int createList() { + int node = newList_(); + if (m_b_allow_navigation_between_lists) { + m_lists.setField(node, 3, m_list_of_lists); + if (m_list_of_lists != nullNode()) + m_lists.setField(m_list_of_lists, 2, node); + m_list_of_lists = node; + } + + return node; + } + + // Deletes a list. + void deleteList(int list) { + int ptr = getFirst(list); + while (ptr != nullNode()) { + int p = ptr; + ptr = getNext(ptr); + freeNode_(p); + } + + if (m_b_allow_navigation_between_lists) { + int prevList = m_lists.getField(list, 2); + int nextList = m_lists.getField(list, 3); + if (prevList != nullNode()) + m_lists.setField(prevList, 3, nextList); + else + m_list_of_lists = nextList; + + if (nextList != nullNode()) + m_lists.setField(nextList, 2, prevList); + } + + freeList_(list); + } + + // Reserves memory for the given number of lists. + void reserveLists(int listCount) { + m_lists.setCapacity(listCount); + } + + // Adds element to a given list. The element is added to the end. Returns + // the new + int addElement(int list, int element) { + int head = m_lists.getField(list, 0); + int tail = m_lists.getField(list, 1); + int node = newNode_(); + if (tail != nullNode()) { + assert (head != nullNode()); + m_listNodes.setField(tail, 1, node); + m_lists.setField(list, 1, node); + } else {// empty list + assert (head == nullNode()); + m_lists.setField(list, 0, node); + m_lists.setField(list, 1, node); + } + + m_listNodes.setField(node, 0, element); + return node; + } + + // Reserves memory for the given number of nodes. + void reserveNodes(int nodeCount) { + m_listNodes.setCapacity(nodeCount); + } + + // Deletes a node from a list, given the previous node (previous node is + // required, because the list is singly connected). + void deleteElement(int list, int prevNode, int node) { + if (prevNode != nullNode()) { + assert (m_listNodes.getField(prevNode, 1) == node); + m_listNodes.setField(prevNode, 1, m_listNodes.getField(node, 1)); + if (m_lists.getField(list, 1) == node)// deleting a tail + { + m_lists.setField(list, 1, prevNode); + } + } else { + assert (m_lists.getField(list, 0) == node); + m_lists.setField(list, 0, m_listNodes.getField(node, 1)); + if (m_lists.getField(list, 1) == node) {// removing last element + assert (m_listNodes.getField(node, 1) == nullNode()); + m_lists.setField(list, 1, nullNode()); + } + } + freeNode_(node); + } + + // Concatenates list1 and list2. The nodes of list2 are added to the end of + // list1. The list2 index becomes invalid. + // Returns list1. + int concatenateLists(int list1, int list2) { + int tailNode1 = m_lists.getField(list1, 1); + int headNode2 = m_lists.getField(list2, 0); + if (headNode2 != nullNode())// do not concatenate empty lists + { + if (tailNode1 != nullNode()) { + // connect head of list2 to the tail of list1. + m_listNodes.setField(tailNode1, 1, headNode2); + // set the tail of the list1 to be the tail of list2. + m_lists.setField(list1, 1, m_lists.getField(list2, 1)); + } else {// list1 is empty, while list2 is not. + m_lists.setField(list1, 0, headNode2); + m_lists.setField(list1, 1, m_lists.getField(list2, 1)); + } + } + + if (m_b_allow_navigation_between_lists) { + int prevList = m_lists.getField(list2, 2); + int nextList = m_lists.getField(list2, 3); + if (prevList != nullNode()) + m_lists.setField(prevList, 3, nextList); + else + m_list_of_lists = nextList; + + if (nextList != nullNode()) + m_lists.setField(nextList, 2, prevList); + } + + freeList_(list2); + return list1; + } + + // Returns the data from the given list node. + int getElement(int node_index) { + return m_listNodes.getField(node_index, 0); + } + + // Sets the data to the given list node. + void setElement(int node_index, int element) { + m_listNodes.setField(node_index, 0, element); + } + + // Returns index of next node for the give node. + int getNext(int node_index) { + return m_listNodes.getField(node_index, 1); + } + + // Returns the first node in the least + int getFirst(int list) { + return m_lists.getField(list, 0); + } + + // Returns the element from the first node in the least. Equivalent to + // get_element(get_first(list)); + int getFirstElement(int list) { + int f = getFirst(list); + return getElement(f); + } + + // Check if the node is Null (does not exist) + static int nullNode() { + return -1; + } + + // Clears all nodes and removes all lists. Frees the memory. + void clear() { + m_listNodes.deleteAll(true); + m_lists.deleteAll(true); + m_list_of_lists = nullNode(); + } + + // Returns True if the given list is empty. + boolean isEmpty(int list) { + return m_lists.getField(list, 0) == nullNode(); + } + + boolean isEmpty() { + return m_listNodes.size() == 0; + } + + int getNodeCount() { + return m_listNodes.size(); + } + + int getListCount() { + return m_lists.size(); + } + + int getFirstList() { + assert (m_b_allow_navigation_between_lists); + return m_list_of_lists; + } + + int getNextList(int list) { + assert (m_b_allow_navigation_between_lists); + return m_lists.getField(list, 3); + } } diff --git a/src/main/java/com/esri/core/geometry/InternalUtils.java b/src/main/java/com/esri/core/geometry/InternalUtils.java index f7219126..1ce3082f 100644 --- a/src/main/java/com/esri/core/geometry/InternalUtils.java +++ b/src/main/java/com/esri/core/geometry/InternalUtils.java @@ -28,538 +28,538 @@ final class InternalUtils { - // p0 and p1 have to be on left/right boundary of fullRange2D (since this - // fuction can be called recursively, p0 or p1 can also be fullRange2D - // corners) - static int addPointsToArray(Point2D p0In, Point2D p1In, - Point2D[] pointsArray, int idx, Envelope2D fullRange2D, - boolean clockwise, double densifyDist)// PointerOfArrayOf(Point2D) - // pointsArray, int idx, - // Envelope2D fullRange2D, - // boolean clockwise, double - // densifyDist) - { - Point2D p0 = new Point2D(); - p0.setCoords(p0In); - Point2D p1 = new Point2D(); - p1.setCoords(p1In); - fullRange2D._snapToBoundary(p0); - fullRange2D._snapToBoundary(p1); - // //_ASSERT((p0.x == fullRange2D.xmin || p0.x == fullRange2D.xmax) && - // (p1.x == fullRange2D.xmin || p1.x == fullRange2D.xmax)); - double boundDist0 = fullRange2D._boundaryDistance(p0); - double boundDist1 = fullRange2D._boundaryDistance(p1); - if (boundDist1 == 0.0) - boundDist1 = fullRange2D.getLength(); - - if ((p0.x == p1.x || p0.y == p1.y - && (p0.y == fullRange2D.ymin || p0.y == fullRange2D.ymax)) - && (boundDist1 > boundDist0) == clockwise) { - Point2D delta = new Point2D(); - delta.setCoords(p1.x - p0.x, p1.y - p0.y); - if (densifyDist != 0)// if (densifyDist) - { - long cPoints = (long) (delta._norm(0) / densifyDist); - if (cPoints > 0) // if (cPoints) - { - delta.scale(1.0 / (cPoints + 1)); - for (long i = 0; i < cPoints; i++) { - p0.add(delta); - pointsArray[idx++].setCoords(p0.x, p0.y);// ARRAYELEMENT(pointsArray, - // idx++).setCoords(p0.x, - // p0.y); - } - } - } - } else { - int side0 = fullRange2D._envelopeSide(p0); - int side1 = fullRange2D._envelopeSide(p1); - // create up to four corner points; the order depends on boolean - // clockwise - Point2D corner; - int deltaSide = clockwise ? 1 : 3; // 3 is equivalent to -1 - do { - side0 = (side0 + deltaSide) & 3; - corner = fullRange2D.queryCorner(side0); - if (densifyDist != 0)// if (densifyDist) - { - idx = addPointsToArray(p0, corner, pointsArray, idx, - fullRange2D, clockwise, densifyDist); - } - pointsArray[idx++].setCoords(corner.x, corner.y);// ARRAYELEMENT(pointsArray, - // idx++).setCoords(corner.x, - // corner.y); - p0 = corner; - } while ((side0 & 3) != side1); - - if (densifyDist != 0)// if (densifyDist) - idx = addPointsToArray(p0, p1, pointsArray, idx, fullRange2D, - clockwise, densifyDist); - } - - return idx; - } - - void shiftPath(MultiPath inputGeom, int iPath, double shift) { - MultiVertexGeometryImpl vertexGeometryImpl = (MultiVertexGeometryImpl) inputGeom - ._getImpl(); - AttributeStreamOfDbl xyStream = (AttributeStreamOfDbl) vertexGeometryImpl - .getAttributeStreamRef(VertexDescription.Semantics.POSITION); - - int i1 = inputGeom.getPathStart(iPath); - int i2 = inputGeom.getPathEnd(iPath); - Point2D pt = new Point2D(); - - while (i1 < i2) { - xyStream.read(i1, pt); - pt.x += shift; - xyStream.write(i1, pt); - i1++; - } - } - - static double calculateToleranceFromGeometry(SpatialReference sr, - Envelope2D env2D, boolean bConservative) { - double gtolerance = env2D._calculateToleranceFromEnvelope(); - double stolerance = sr != null ? sr - .getTolerance(VertexDescription.Semantics.POSITION) : 0; - if (bConservative) { - gtolerance *= 4; - stolerance *= 1.1; - } - return Math.max(stolerance, gtolerance); - } - - static double adjust_tolerance_for_TE_clustering(double tol) { - return 2.0 * Math.sqrt(2.0) * tol; - } - - static double adjust_tolerance_for_TE_cracking(double tol) { - return Math.sqrt(2.0) * tol; - } - - static double calculateToleranceFromGeometry(SpatialReference sr, - Geometry geometry, boolean bConservative) { - Envelope2D env2D = new Envelope2D(); - geometry.queryEnvelope2D(env2D); - return calculateToleranceFromGeometry(sr, env2D, bConservative); - } - - static double calculateZToleranceFromGeometry(SpatialReference sr, - Geometry geometry, boolean bConservative) { - Envelope1D env1D = geometry.queryInterval( - VertexDescription.Semantics.Z, 0); - double gtolerance = env1D._calculateToleranceFromEnvelope(); - double stolerance = sr != null ? sr - .getTolerance(VertexDescription.Semantics.Z) : 0; - if (bConservative) { - gtolerance *= 4; - stolerance *= 1.1; - } - return Math.max(stolerance, gtolerance); - } - - double calculateZToleranceFromGeometry(SpatialReference sr, - Geometry geometry) { - Envelope1D env1D = geometry.queryInterval( - VertexDescription.Semantics.Z, 0); - double tolerance = env1D._calculateToleranceFromEnvelope(); - return Math - .max(sr != null ? sr - .getTolerance(VertexDescription.Semantics.Z) : 0, - tolerance); - } - - public static Envelope2D getMergedExtent(Geometry geom1, Envelope2D env2) { - Envelope2D env1 = new Envelope2D(); - geom1.queryLooseEnvelope2D(env1); - env1.merge(env2); - return env1; - } - - public static Envelope2D getMergedExtent(Geometry geom1, Geometry geom2) { - Envelope2D env1 = new Envelope2D(); - geom1.queryLooseEnvelope2D(env1); - Envelope2D env2 = new Envelope2D(); - geom2.queryLooseEnvelope2D(env2); - env1.merge(env2); - return env1; - } - - public static Geometry createGeometry(int gt, VertexDescription vdIn) { - VertexDescription vd = vdIn; - if (vd == null) - vd = VertexDescriptionDesignerImpl.getDefaultDescriptor2D(); - switch (gt) { - case Geometry.GeometryType.Point: - return new Point(vd); - case Geometry.GeometryType.Line: - return new Line(vd); - // case enum_value2(Geometry, GeometryType, enumBezier): - // break; - // case enum_value2(Geometry, GeometryType, enumEllipticArc): - // break; - case Geometry.GeometryType.Envelope: - return new Envelope(vd); - case Geometry.GeometryType.MultiPoint: - return new MultiPoint(vd); - case Geometry.GeometryType.Polyline: - return new Polyline(vd); - case Geometry.GeometryType.Polygon: - return new Polygon(vd); - default: - throw new GeometryException("invalid argument."); - } - } - - static boolean isClockwiseRing(MultiPathImpl polygon, int iring) { - int high_point_index = polygon.getHighestPointIndex(iring); - int path_start = polygon.getPathStart(iring); - int path_end = polygon.getPathEnd(iring); - - Point2D q = polygon.getXY(high_point_index); - Point2D p, r; - - if (high_point_index == path_start) { - p = polygon.getXY(path_end - 1); - r = polygon.getXY(path_start + 1); - } else if (high_point_index == path_end - 1) { - p = polygon.getXY(high_point_index - 1); - r = polygon.getXY(path_start); - } else { - p = polygon.getXY(high_point_index - 1); - r = polygon.getXY(high_point_index + 1); - } - - int orientation = Point2D.orientationRobust(p, q, r); - - if (orientation == 0) - return polygon.calculateRingArea2D(iring) > 0.0; - - return orientation == -1; - } - - static QuadTreeImpl buildQuadTree(MultiPathImpl multipathImpl) { - Envelope2D extent = new Envelope2D(); - multipathImpl.queryLooseEnvelope2D(extent); - QuadTreeImpl quad_tree_impl = new QuadTreeImpl(extent, 8); - int hint_index = -1; - SegmentIteratorImpl seg_iter = multipathImpl.querySegmentIterator(); - Envelope2D boundingbox = new Envelope2D(); - boolean resized_extent = false; - - while (seg_iter.nextPath()) { - while (seg_iter.hasNextSegment()) { - Segment segment = seg_iter.nextSegment(); - int index = seg_iter.getStartPointIndex(); - segment.queryEnvelope2D(boundingbox); - hint_index = quad_tree_impl.insert(index, boundingbox, - hint_index); - - if (hint_index == -1) { - if (resized_extent) - throw GeometryException.GeometryInternalError(); - - // resize extent - multipathImpl.calculateEnvelope2D(extent, false); - resized_extent = true; - quad_tree_impl.reset(extent, 8); - seg_iter.resetToFirstPath(); - break; - } - } - } - - return quad_tree_impl; - } - - static QuadTreeImpl buildQuadTree(MultiPathImpl multipathImpl, - Envelope2D extentOfInterest) { - Envelope2D extent = new Envelope2D(); - multipathImpl.queryLooseEnvelope2D(extent); - QuadTreeImpl quad_tree_impl = new QuadTreeImpl(extent, 8); - int hint_index = -1; - Envelope2D boundingbox = new Envelope2D(); - SegmentIteratorImpl seg_iter = multipathImpl.querySegmentIterator(); - - boolean resized_extent = false; - while (seg_iter.nextPath()) { - while (seg_iter.hasNextSegment()) { - Segment segment = seg_iter.nextSegment(); - int index = seg_iter.getStartPointIndex(); - segment.queryEnvelope2D(boundingbox); - - if (boundingbox.isIntersecting(extentOfInterest)) { - hint_index = quad_tree_impl.insert(index, boundingbox, - hint_index); - - if (hint_index == -1) { - if (resized_extent) - throw GeometryException.GeometryInternalError(); - - // resize extent - multipathImpl.calculateEnvelope2D(extent, false); - resized_extent = true; - quad_tree_impl.reset(extent, 8); - seg_iter.resetToFirstPath(); - break; - } - } - } - } - - return quad_tree_impl; - } - - static QuadTreeImpl buildQuadTreeForPaths(MultiPathImpl multipathImpl) { - Envelope2D extent = new Envelope2D(); - multipathImpl.queryLooseEnvelope2D(extent); - if (extent.isEmpty()) - return null; - - QuadTreeImpl quad_tree_impl = new QuadTreeImpl(extent, 8); - int hint_index = -1; - Envelope2D boundingbox = new Envelope2D(); - - boolean resized_extent = false; - do { - for (int ipath = 0, npaths = multipathImpl.getPathCount(); ipath < npaths; ipath++) { - multipathImpl.queryPathEnvelope2D(ipath, boundingbox); - hint_index = quad_tree_impl.insert(ipath, boundingbox, hint_index); - - if (hint_index == -1) { - if (resized_extent) - throw GeometryException.GeometryInternalError(); - - //This is usually happens because esri shape buffer contains geometry extent which is slightly different from the true extent. - //Recalculate extent - multipathImpl.calculateEnvelope2D(extent, false); - resized_extent = true; - quad_tree_impl.reset(extent, 8); - break; //break the for loop - } else { - resized_extent = false; - } - } - - } while (resized_extent); - - return quad_tree_impl; - } - - static QuadTreeImpl buildQuadTree(MultiPointImpl multipointImpl) { - Envelope2D extent = new Envelope2D(); - multipointImpl.queryLooseEnvelope2D(extent); - QuadTreeImpl quad_tree_impl = new QuadTreeImpl(extent, 8); - - Point2D pt = new Point2D(); - Envelope2D boundingbox = new Envelope2D(); - boolean resized_extent = false; - for (int i = 0; i < multipointImpl.getPointCount(); i++) { - multipointImpl.getXY(i, pt); - boundingbox.setCoords(pt); - int element_handle = quad_tree_impl.insert(i, boundingbox); - - if (element_handle == -1) { - if (resized_extent) - throw GeometryException.GeometryInternalError(); - - // resize extent - multipointImpl.calculateEnvelope2D(extent, false); - resized_extent = true; - quad_tree_impl.reset(extent, 8); - i = -1; // resets the for-loop - continue; - } - } - - return quad_tree_impl; - } - - static QuadTreeImpl buildQuadTree(MultiPointImpl multipointImpl, - Envelope2D extentOfInterest) { - QuadTreeImpl quad_tree_impl = new QuadTreeImpl(extentOfInterest, 8); - Point2D pt = new Point2D(); - boolean resized_extent = false; - Envelope2D boundingbox = new Envelope2D(); - for (int i = 0; i < multipointImpl.getPointCount(); i++) { - multipointImpl.getXY(i, pt); - - if (!extentOfInterest.contains(pt)) - continue; - - boundingbox.setCoords(pt); - int element_handle = quad_tree_impl.insert(i, boundingbox); - - if (element_handle == -1) { - if (resized_extent) - throw GeometryException.GeometryInternalError(); - - // resize extent - resized_extent = true; - Envelope2D extent = new Envelope2D(); - multipointImpl.calculateEnvelope2D(extent, false); - quad_tree_impl.reset(extent, 8); - i = -1; // resets the for-loop - continue; - } - } - - return quad_tree_impl; - } - - static Envelope2DIntersectorImpl getEnvelope2DIntersector( - MultiPathImpl multipathImplA, MultiPathImpl multipathImplB, - double tolerance) { - Envelope2D env_a = new Envelope2D(), env_b = new Envelope2D(); - multipathImplA.queryLooseEnvelope2D(env_a); - multipathImplB.queryLooseEnvelope2D(env_b); - env_a.inflate(tolerance, tolerance); - env_b.inflate(tolerance, tolerance); - - Envelope2D envInter = new Envelope2D(); - envInter.setCoords(env_a); - envInter.intersect(env_b); - - SegmentIteratorImpl segIterA = multipathImplA.querySegmentIterator(); - SegmentIteratorImpl segIterB = multipathImplB.querySegmentIterator(); - - Envelope2DIntersectorImpl intersector = new Envelope2DIntersectorImpl(); - intersector.setTolerance(tolerance); - - boolean b_found_red = false; - intersector.startRedConstruction(); - while (segIterA.nextPath()) { - while (segIterA.hasNextSegment()) { - Segment segmentA = segIterA.nextSegment(); - segmentA.queryEnvelope2D(env_a); - - if (!env_a.isIntersecting(envInter)) - continue; - - b_found_red = true; - Envelope2D env = new Envelope2D(); - env.setCoords(env_a); - intersector.addRedEnvelope(segIterA.getStartPointIndex(), env); - } - } - intersector.endRedConstruction(); - - if (!b_found_red) - return null; - - boolean b_found_blue = false; - intersector.startBlueConstruction(); - while (segIterB.nextPath()) { - while (segIterB.hasNextSegment()) { - Segment segmentB = segIterB.nextSegment(); - segmentB.queryEnvelope2D(env_b); - - if (!env_b.isIntersecting(envInter)) - continue; - - b_found_blue = true; - Envelope2D env = new Envelope2D(); - env.setCoords(env_b); - intersector.addBlueEnvelope(segIterB.getStartPointIndex(), env); - } - } - intersector.endBlueConstruction(); - - if (!b_found_blue) - return null; - - return intersector; - } - - static Envelope2DIntersectorImpl getEnvelope2DIntersectorForParts( - MultiPathImpl multipathImplA, MultiPathImpl multipathImplB, - double tolerance, boolean bExteriorOnlyA, boolean bExteriorOnlyB) { - int type_a = multipathImplA.getType().value(); - int type_b = multipathImplB.getType().value(); - - Envelope2D env_a = new Envelope2D(), env_b = new Envelope2D(); - multipathImplA.queryLooseEnvelope2D(env_a); - multipathImplB.queryLooseEnvelope2D(env_b); - env_a.inflate(tolerance, tolerance); - env_b.inflate(tolerance, tolerance); - - Envelope2D envInter = new Envelope2D(); - envInter.setCoords(env_a); - envInter.intersect(env_b); - - Envelope2DIntersectorImpl intersector = new Envelope2DIntersectorImpl(); - intersector.setTolerance(tolerance); - - boolean b_found_red = false; - intersector.startRedConstruction(); - for (int ipath_a = 0, npaths = multipathImplA.getPathCount(); ipath_a < npaths; ipath_a++) { - if (bExteriorOnlyA && type_a == Geometry.GeometryType.Polygon && !multipathImplA.isExteriorRing(ipath_a)) - continue; - - multipathImplA.queryPathEnvelope2D(ipath_a, env_a); - - if (!env_a.isIntersecting(envInter)) - continue; - - b_found_red = true; - intersector.addRedEnvelope(ipath_a, env_a); - } - intersector.endRedConstruction(); - - if (!b_found_red) - return null; - - boolean b_found_blue = false; - intersector.startBlueConstruction(); - for (int ipath_b = 0, npaths = multipathImplB.getPathCount(); ipath_b < npaths; ipath_b++) { - if (bExteriorOnlyB && type_b == Geometry.GeometryType.Polygon && !multipathImplB.isExteriorRing(ipath_b)) - continue; - - multipathImplB.queryPathEnvelope2D(ipath_b, env_b); - - if (!env_b.isIntersecting(envInter)) - continue; - - b_found_blue = true; - intersector.addBlueEnvelope(ipath_b, env_b); - } - intersector.endBlueConstruction(); - - if (!b_found_blue) - return null; - - return intersector; - } - - static boolean isWeakSimple(MultiVertexGeometry geom, double tol) { - return ((MultiVertexGeometryImpl) geom._getImpl()).getIsSimple(tol) > 0; - } - - static QuadTree buildQuadTreeForOnePath(MultiPathImpl multipathImpl, int path) { - Envelope2D extent = new Envelope2D(); - multipathImpl.queryLoosePathEnvelope2D(path, extent); - QuadTree quad_tree = new QuadTree(extent, 8); - int hint_index = -1; - Envelope2D boundingbox = new Envelope2D(); - SegmentIteratorImpl seg_iter = multipathImpl.querySegmentIterator(); - - seg_iter.resetToPath(path); - if (seg_iter.nextPath()) { - while (seg_iter.hasNextSegment()) { - Segment segment = seg_iter.nextSegment(); - int index = seg_iter.getStartPointIndex(); - segment.queryLooseEnvelope2D(boundingbox); - hint_index = quad_tree.insert(index, boundingbox, hint_index); - - if (hint_index == -1) { - throw new GeometryException("internal error"); - } - } - } - - return quad_tree; - } + // p0 and p1 have to be on left/right boundary of fullRange2D (since this + // fuction can be called recursively, p0 or p1 can also be fullRange2D + // corners) + static int addPointsToArray(Point2D p0In, Point2D p1In, + Point2D[] pointsArray, int idx, Envelope2D fullRange2D, + boolean clockwise, double densifyDist)// PointerOfArrayOf(Point2D) + // pointsArray, int idx, + // Envelope2D fullRange2D, + // boolean clockwise, double + // densifyDist) + { + Point2D p0 = new Point2D(); + p0.setCoords(p0In); + Point2D p1 = new Point2D(); + p1.setCoords(p1In); + fullRange2D._snapToBoundary(p0); + fullRange2D._snapToBoundary(p1); + // //_ASSERT((p0.x == fullRange2D.xmin || p0.x == fullRange2D.xmax) && + // (p1.x == fullRange2D.xmin || p1.x == fullRange2D.xmax)); + double boundDist0 = fullRange2D._boundaryDistance(p0); + double boundDist1 = fullRange2D._boundaryDistance(p1); + if (boundDist1 == 0.0) + boundDist1 = fullRange2D.getLength(); + + if ((p0.x == p1.x || p0.y == p1.y + && (p0.y == fullRange2D.ymin || p0.y == fullRange2D.ymax)) + && (boundDist1 > boundDist0) == clockwise) { + Point2D delta = new Point2D(); + delta.setCoords(p1.x - p0.x, p1.y - p0.y); + if (densifyDist != 0)// if (densifyDist) + { + long cPoints = (long) (delta._norm(0) / densifyDist); + if (cPoints > 0) // if (cPoints) + { + delta.scale(1.0 / (cPoints + 1)); + for (long i = 0; i < cPoints; i++) { + p0.add(delta); + pointsArray[idx++].setCoords(p0.x, p0.y);// ARRAYELEMENT(pointsArray, + // idx++).setCoords(p0.x, + // p0.y); + } + } + } + } else { + int side0 = fullRange2D._envelopeSide(p0); + int side1 = fullRange2D._envelopeSide(p1); + // create up to four corner points; the order depends on boolean + // clockwise + Point2D corner; + int deltaSide = clockwise ? 1 : 3; // 3 is equivalent to -1 + do { + side0 = (side0 + deltaSide) & 3; + corner = fullRange2D.queryCorner(side0); + if (densifyDist != 0)// if (densifyDist) + { + idx = addPointsToArray(p0, corner, pointsArray, idx, + fullRange2D, clockwise, densifyDist); + } + pointsArray[idx++].setCoords(corner.x, corner.y);// ARRAYELEMENT(pointsArray, + // idx++).setCoords(corner.x, + // corner.y); + p0 = corner; + } while ((side0 & 3) != side1); + + if (densifyDist != 0)// if (densifyDist) + idx = addPointsToArray(p0, p1, pointsArray, idx, fullRange2D, + clockwise, densifyDist); + } + + return idx; + } + + void shiftPath(MultiPath inputGeom, int iPath, double shift) { + MultiVertexGeometryImpl vertexGeometryImpl = (MultiVertexGeometryImpl) inputGeom + ._getImpl(); + AttributeStreamOfDbl xyStream = (AttributeStreamOfDbl) vertexGeometryImpl + .getAttributeStreamRef(VertexDescription.Semantics.POSITION); + + int i1 = inputGeom.getPathStart(iPath); + int i2 = inputGeom.getPathEnd(iPath); + Point2D pt = new Point2D(); + + while (i1 < i2) { + xyStream.read(i1, pt); + pt.x += shift; + xyStream.write(i1, pt); + i1++; + } + } + + static double calculateToleranceFromGeometry(SpatialReference sr, + Envelope2D env2D, boolean bConservative) { + double gtolerance = env2D._calculateToleranceFromEnvelope(); + double stolerance = sr != null ? sr + .getTolerance(VertexDescription.Semantics.POSITION) : 0; + if (bConservative) { + gtolerance *= 4; + stolerance *= 1.1; + } + return Math.max(stolerance, gtolerance); + } + + static double adjust_tolerance_for_TE_clustering(double tol) { + return 2.0 * Math.sqrt(2.0) * tol; + } + + static double adjust_tolerance_for_TE_cracking(double tol) { + return Math.sqrt(2.0) * tol; + } + + static double calculateToleranceFromGeometry(SpatialReference sr, + Geometry geometry, boolean bConservative) { + Envelope2D env2D = new Envelope2D(); + geometry.queryEnvelope2D(env2D); + return calculateToleranceFromGeometry(sr, env2D, bConservative); + } + + static double calculateZToleranceFromGeometry(SpatialReference sr, + Geometry geometry, boolean bConservative) { + Envelope1D env1D = geometry.queryInterval( + VertexDescription.Semantics.Z, 0); + double gtolerance = env1D._calculateToleranceFromEnvelope(); + double stolerance = sr != null ? sr + .getTolerance(VertexDescription.Semantics.Z) : 0; + if (bConservative) { + gtolerance *= 4; + stolerance *= 1.1; + } + return Math.max(stolerance, gtolerance); + } + + double calculateZToleranceFromGeometry(SpatialReference sr, + Geometry geometry) { + Envelope1D env1D = geometry.queryInterval( + VertexDescription.Semantics.Z, 0); + double tolerance = env1D._calculateToleranceFromEnvelope(); + return Math + .max(sr != null ? sr + .getTolerance(VertexDescription.Semantics.Z) : 0, + tolerance); + } + + public static Envelope2D getMergedExtent(Geometry geom1, Envelope2D env2) { + Envelope2D env1 = new Envelope2D(); + geom1.queryLooseEnvelope2D(env1); + env1.merge(env2); + return env1; + } + + public static Envelope2D getMergedExtent(Geometry geom1, Geometry geom2) { + Envelope2D env1 = new Envelope2D(); + geom1.queryLooseEnvelope2D(env1); + Envelope2D env2 = new Envelope2D(); + geom2.queryLooseEnvelope2D(env2); + env1.merge(env2); + return env1; + } + + public static Geometry createGeometry(int gt, VertexDescription vdIn) { + VertexDescription vd = vdIn; + if (vd == null) + vd = VertexDescriptionDesignerImpl.getDefaultDescriptor2D(); + switch (gt) { + case Geometry.GeometryType.Point: + return new Point(vd); + case Geometry.GeometryType.Line: + return new Line(vd); + // case enum_value2(Geometry, GeometryType, enumBezier): + // break; + // case enum_value2(Geometry, GeometryType, enumEllipticArc): + // break; + case Geometry.GeometryType.Envelope: + return new Envelope(vd); + case Geometry.GeometryType.MultiPoint: + return new MultiPoint(vd); + case Geometry.GeometryType.Polyline: + return new Polyline(vd); + case Geometry.GeometryType.Polygon: + return new Polygon(vd); + default: + throw new GeometryException("invalid argument."); + } + } + + static boolean isClockwiseRing(MultiPathImpl polygon, int iring) { + int high_point_index = polygon.getHighestPointIndex(iring); + int path_start = polygon.getPathStart(iring); + int path_end = polygon.getPathEnd(iring); + + Point2D q = polygon.getXY(high_point_index); + Point2D p, r; + + if (high_point_index == path_start) { + p = polygon.getXY(path_end - 1); + r = polygon.getXY(path_start + 1); + } else if (high_point_index == path_end - 1) { + p = polygon.getXY(high_point_index - 1); + r = polygon.getXY(path_start); + } else { + p = polygon.getXY(high_point_index - 1); + r = polygon.getXY(high_point_index + 1); + } + + int orientation = Point2D.orientationRobust(p, q, r); + + if (orientation == 0) + return polygon.calculateRingArea2D(iring) > 0.0; + + return orientation == -1; + } + + static QuadTreeImpl buildQuadTree(MultiPathImpl multipathImpl) { + Envelope2D extent = new Envelope2D(); + multipathImpl.queryLooseEnvelope2D(extent); + QuadTreeImpl quad_tree_impl = new QuadTreeImpl(extent, 8); + int hint_index = -1; + SegmentIteratorImpl seg_iter = multipathImpl.querySegmentIterator(); + Envelope2D boundingbox = new Envelope2D(); + boolean resized_extent = false; + + while (seg_iter.nextPath()) { + while (seg_iter.hasNextSegment()) { + Segment segment = seg_iter.nextSegment(); + int index = seg_iter.getStartPointIndex(); + segment.queryEnvelope2D(boundingbox); + hint_index = quad_tree_impl.insert(index, boundingbox, + hint_index); + + if (hint_index == -1) { + if (resized_extent) + throw GeometryException.GeometryInternalError(); + + // resize extent + multipathImpl.calculateEnvelope2D(extent, false); + resized_extent = true; + quad_tree_impl.reset(extent, 8); + seg_iter.resetToFirstPath(); + break; + } + } + } + + return quad_tree_impl; + } + + static QuadTreeImpl buildQuadTree(MultiPathImpl multipathImpl, + Envelope2D extentOfInterest) { + Envelope2D extent = new Envelope2D(); + multipathImpl.queryLooseEnvelope2D(extent); + QuadTreeImpl quad_tree_impl = new QuadTreeImpl(extent, 8); + int hint_index = -1; + Envelope2D boundingbox = new Envelope2D(); + SegmentIteratorImpl seg_iter = multipathImpl.querySegmentIterator(); + + boolean resized_extent = false; + while (seg_iter.nextPath()) { + while (seg_iter.hasNextSegment()) { + Segment segment = seg_iter.nextSegment(); + int index = seg_iter.getStartPointIndex(); + segment.queryEnvelope2D(boundingbox); + + if (boundingbox.isIntersecting(extentOfInterest)) { + hint_index = quad_tree_impl.insert(index, boundingbox, + hint_index); + + if (hint_index == -1) { + if (resized_extent) + throw GeometryException.GeometryInternalError(); + + // resize extent + multipathImpl.calculateEnvelope2D(extent, false); + resized_extent = true; + quad_tree_impl.reset(extent, 8); + seg_iter.resetToFirstPath(); + break; + } + } + } + } + + return quad_tree_impl; + } + + static QuadTreeImpl buildQuadTreeForPaths(MultiPathImpl multipathImpl) { + Envelope2D extent = new Envelope2D(); + multipathImpl.queryLooseEnvelope2D(extent); + if (extent.isEmpty()) + return null; + + QuadTreeImpl quad_tree_impl = new QuadTreeImpl(extent, 8); + int hint_index = -1; + Envelope2D boundingbox = new Envelope2D(); + + boolean resized_extent = false; + do { + for (int ipath = 0, npaths = multipathImpl.getPathCount(); ipath < npaths; ipath++) { + multipathImpl.queryPathEnvelope2D(ipath, boundingbox); + hint_index = quad_tree_impl.insert(ipath, boundingbox, hint_index); + + if (hint_index == -1) { + if (resized_extent) + throw GeometryException.GeometryInternalError(); + + //This is usually happens because esri shape buffer contains geometry extent which is slightly different from the true extent. + //Recalculate extent + multipathImpl.calculateEnvelope2D(extent, false); + resized_extent = true; + quad_tree_impl.reset(extent, 8); + break; //break the for loop + } else { + resized_extent = false; + } + } + + } while (resized_extent); + + return quad_tree_impl; + } + + static QuadTreeImpl buildQuadTree(MultiPointImpl multipointImpl) { + Envelope2D extent = new Envelope2D(); + multipointImpl.queryLooseEnvelope2D(extent); + QuadTreeImpl quad_tree_impl = new QuadTreeImpl(extent, 8); + + Point2D pt = new Point2D(); + Envelope2D boundingbox = new Envelope2D(); + boolean resized_extent = false; + for (int i = 0; i < multipointImpl.getPointCount(); i++) { + multipointImpl.getXY(i, pt); + boundingbox.setCoords(pt); + int element_handle = quad_tree_impl.insert(i, boundingbox); + + if (element_handle == -1) { + if (resized_extent) + throw GeometryException.GeometryInternalError(); + + // resize extent + multipointImpl.calculateEnvelope2D(extent, false); + resized_extent = true; + quad_tree_impl.reset(extent, 8); + i = -1; // resets the for-loop + continue; + } + } + + return quad_tree_impl; + } + + static QuadTreeImpl buildQuadTree(MultiPointImpl multipointImpl, + Envelope2D extentOfInterest) { + QuadTreeImpl quad_tree_impl = new QuadTreeImpl(extentOfInterest, 8); + Point2D pt = new Point2D(); + boolean resized_extent = false; + Envelope2D boundingbox = new Envelope2D(); + for (int i = 0; i < multipointImpl.getPointCount(); i++) { + multipointImpl.getXY(i, pt); + + if (!extentOfInterest.contains(pt)) + continue; + + boundingbox.setCoords(pt); + int element_handle = quad_tree_impl.insert(i, boundingbox); + + if (element_handle == -1) { + if (resized_extent) + throw GeometryException.GeometryInternalError(); + + // resize extent + resized_extent = true; + Envelope2D extent = new Envelope2D(); + multipointImpl.calculateEnvelope2D(extent, false); + quad_tree_impl.reset(extent, 8); + i = -1; // resets the for-loop + continue; + } + } + + return quad_tree_impl; + } + + static Envelope2DIntersectorImpl getEnvelope2DIntersector( + MultiPathImpl multipathImplA, MultiPathImpl multipathImplB, + double tolerance) { + Envelope2D env_a = new Envelope2D(), env_b = new Envelope2D(); + multipathImplA.queryLooseEnvelope2D(env_a); + multipathImplB.queryLooseEnvelope2D(env_b); + env_a.inflate(tolerance, tolerance); + env_b.inflate(tolerance, tolerance); + + Envelope2D envInter = new Envelope2D(); + envInter.setCoords(env_a); + envInter.intersect(env_b); + + SegmentIteratorImpl segIterA = multipathImplA.querySegmentIterator(); + SegmentIteratorImpl segIterB = multipathImplB.querySegmentIterator(); + + Envelope2DIntersectorImpl intersector = new Envelope2DIntersectorImpl(); + intersector.setTolerance(tolerance); + + boolean b_found_red = false; + intersector.startRedConstruction(); + while (segIterA.nextPath()) { + while (segIterA.hasNextSegment()) { + Segment segmentA = segIterA.nextSegment(); + segmentA.queryEnvelope2D(env_a); + + if (!env_a.isIntersecting(envInter)) + continue; + + b_found_red = true; + Envelope2D env = new Envelope2D(); + env.setCoords(env_a); + intersector.addRedEnvelope(segIterA.getStartPointIndex(), env); + } + } + intersector.endRedConstruction(); + + if (!b_found_red) + return null; + + boolean b_found_blue = false; + intersector.startBlueConstruction(); + while (segIterB.nextPath()) { + while (segIterB.hasNextSegment()) { + Segment segmentB = segIterB.nextSegment(); + segmentB.queryEnvelope2D(env_b); + + if (!env_b.isIntersecting(envInter)) + continue; + + b_found_blue = true; + Envelope2D env = new Envelope2D(); + env.setCoords(env_b); + intersector.addBlueEnvelope(segIterB.getStartPointIndex(), env); + } + } + intersector.endBlueConstruction(); + + if (!b_found_blue) + return null; + + return intersector; + } + + static Envelope2DIntersectorImpl getEnvelope2DIntersectorForParts( + MultiPathImpl multipathImplA, MultiPathImpl multipathImplB, + double tolerance, boolean bExteriorOnlyA, boolean bExteriorOnlyB) { + int type_a = multipathImplA.getType().value(); + int type_b = multipathImplB.getType().value(); + + Envelope2D env_a = new Envelope2D(), env_b = new Envelope2D(); + multipathImplA.queryLooseEnvelope2D(env_a); + multipathImplB.queryLooseEnvelope2D(env_b); + env_a.inflate(tolerance, tolerance); + env_b.inflate(tolerance, tolerance); + + Envelope2D envInter = new Envelope2D(); + envInter.setCoords(env_a); + envInter.intersect(env_b); + + Envelope2DIntersectorImpl intersector = new Envelope2DIntersectorImpl(); + intersector.setTolerance(tolerance); + + boolean b_found_red = false; + intersector.startRedConstruction(); + for (int ipath_a = 0, npaths = multipathImplA.getPathCount(); ipath_a < npaths; ipath_a++) { + if (bExteriorOnlyA && type_a == Geometry.GeometryType.Polygon && !multipathImplA.isExteriorRing(ipath_a)) + continue; + + multipathImplA.queryPathEnvelope2D(ipath_a, env_a); + + if (!env_a.isIntersecting(envInter)) + continue; + + b_found_red = true; + intersector.addRedEnvelope(ipath_a, env_a); + } + intersector.endRedConstruction(); + + if (!b_found_red) + return null; + + boolean b_found_blue = false; + intersector.startBlueConstruction(); + for (int ipath_b = 0, npaths = multipathImplB.getPathCount(); ipath_b < npaths; ipath_b++) { + if (bExteriorOnlyB && type_b == Geometry.GeometryType.Polygon && !multipathImplB.isExteriorRing(ipath_b)) + continue; + + multipathImplB.queryPathEnvelope2D(ipath_b, env_b); + + if (!env_b.isIntersecting(envInter)) + continue; + + b_found_blue = true; + intersector.addBlueEnvelope(ipath_b, env_b); + } + intersector.endBlueConstruction(); + + if (!b_found_blue) + return null; + + return intersector; + } + + static boolean isWeakSimple(MultiVertexGeometry geom, double tol) { + return ((MultiVertexGeometryImpl) geom._getImpl()).getIsSimple(tol) > 0; + } + + static QuadTree buildQuadTreeForOnePath(MultiPathImpl multipathImpl, int path) { + Envelope2D extent = new Envelope2D(); + multipathImpl.queryLoosePathEnvelope2D(path, extent); + QuadTree quad_tree = new QuadTree(extent, 8); + int hint_index = -1; + Envelope2D boundingbox = new Envelope2D(); + SegmentIteratorImpl seg_iter = multipathImpl.querySegmentIterator(); + + seg_iter.resetToPath(path); + if (seg_iter.nextPath()) { + while (seg_iter.hasNextSegment()) { + Segment segment = seg_iter.nextSegment(); + int index = seg_iter.getStartPointIndex(); + segment.queryLooseEnvelope2D(boundingbox); + hint_index = quad_tree.insert(index, boundingbox, hint_index); + + if (hint_index == -1) { + throw new GeometryException("internal error"); + } + } + } + + return quad_tree; + } } diff --git a/src/main/java/com/esri/core/geometry/Interop.java b/src/main/java/com/esri/core/geometry/Interop.java index b90726f0..b47801c7 100644 --- a/src/main/java/com/esri/core/geometry/Interop.java +++ b/src/main/java/com/esri/core/geometry/Interop.java @@ -25,11 +25,11 @@ package com.esri.core.geometry; class Interop { - public static double translateFromAVNaN(double n) { - return (n < -1.0e38) ? NumberUtils.NaN() : n; - } + public static double translateFromAVNaN(double n) { + return (n < -1.0e38) ? NumberUtils.NaN() : n; + } - public static double translateToAVNaN(double n) { - return (NumberUtils.isNaN(n)) ? -Double.MAX_VALUE : n; - } + public static double translateToAVNaN(double n) { + return (NumberUtils.isNaN(n)) ? -Double.MAX_VALUE : n; + } } diff --git a/src/main/java/com/esri/core/geometry/IntervalTreeImpl.java b/src/main/java/com/esri/core/geometry/IntervalTreeImpl.java index cd1d2553..593a9e34 100644 --- a/src/main/java/com/esri/core/geometry/IntervalTreeImpl.java +++ b/src/main/java/com/esri/core/geometry/IntervalTreeImpl.java @@ -5,7 +5,7 @@ you may not use this file except in compliance with the License. You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 + http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, @@ -26,1131 +26,1131 @@ import java.util.ArrayList; final class IntervalTreeImpl { - private void sortEndIndices_(AttributeStreamOfInt32 end_indices, int begin_, int end_) { - IntervalTreeBucketSortHelper sorter = new IntervalTreeBucketSortHelper(this); - BucketSort bucket_sort = new BucketSort(); - bucket_sort.sort(end_indices, begin_, end_, sorter); - } - - private void sortEndIndicesHelper_(AttributeStreamOfInt32 end_indices, int begin_, int end_) { - end_indices.Sort(begin_, end_, new EndPointsComparer(this)); - } - - private double getValue_(int e) { - if (!m_b_envelopes_ref) { - Envelope1D interval = m_intervals.get(e >> 1); - double v = (isLeft_(e) ? interval.vmin : interval.vmax); - return v; - } - - Envelope2D interval = m_envelopes_ref.get(e >> 1); - double v = (isLeft_(e) ? interval.xmin : interval.xmax); - return v; - } - - private static final class EndPointsComparer extends AttributeStreamOfInt32.IntComparator { // For user sort - - EndPointsComparer(IntervalTreeImpl interval_tree) { - m_interval_tree = interval_tree; - } - - @Override - public int compare(int e_1, int e_2) { - double v_1 = m_interval_tree.getValue_(e_1); - double v_2 = m_interval_tree.getValue_(e_2); - - if (v_1 < v_2 || (v_1 == v_2 && isLeft_(e_1) && isRight_(e_2))) - return -1; - - return 1; - } - - private IntervalTreeImpl m_interval_tree; - } - - private class IntervalTreeBucketSortHelper extends ClassicSort { // For bucket sort - - IntervalTreeBucketSortHelper(IntervalTreeImpl interval_tree) { - m_interval_tree = interval_tree; - } - - @Override - public void userSort(int begin, int end, AttributeStreamOfInt32 indices) { - m_interval_tree.sortEndIndicesHelper_(indices, begin, end); - } - - @Override - public double getValue(int e) { - return m_interval_tree.getValue_(e); - } - - private IntervalTreeImpl m_interval_tree; - } - - IntervalTreeImpl(boolean b_offline_dynamic) { - m_b_envelopes_ref = false; - m_b_offline_dynamic = b_offline_dynamic; - m_b_constructing = false; - m_b_construction_ended = false; - } - - void addEnvelopesRef(ArrayList envelopes) { - reset_(true, true); - m_b_envelopes_ref = true; - m_envelopes_ref = envelopes; - - m_b_constructing = false; - m_b_construction_ended = true; - - if (!m_b_offline_dynamic) { - insertIntervalsStatic_(); - m_c_count = m_envelopes_ref.size(); - } - } - - void startConstruction() { - reset_(true, false); - } - - void addInterval(Envelope1D interval) { - if (!m_b_constructing) - throw new GeometryException("invalid call"); - - m_intervals.add(interval); - } - - void addInterval(double min, double max) { - if (!m_b_constructing) - throw new GeometryException("invald call"); - - m_intervals.add(new Envelope1D(min, max)); - } - - void endConstruction() { - if (!m_b_constructing) - throw new GeometryException("invalid call"); - - m_b_constructing = false; - m_b_construction_ended = true; - - if (!m_b_offline_dynamic) { - insertIntervalsStatic_(); - m_c_count = m_intervals.size(); - } - } - - /* - * Resets the Interval_tree_impl to an empty state, but maintains a handle - * on the current intervals. - */ - void reset() { - if (!m_b_offline_dynamic || !m_b_construction_ended) - throw new IllegalArgumentException("invalid call"); - - reset_(false, m_b_envelopes_ref); - } - - /** - * Returns the number of intervals stored in the Interval_tree_impl - */ - int size() { - return m_c_count; - } - - /** - * Gets an iterator on the Interval_tree_impl using the input Envelope_1D - * interval as the query. To reuse the existing iterator on the same - * Interval_tree_impl but with a new query, use the reset_iterator function - * on the Interval_tree_iterator_impl. \param query The Envelope_1D interval - * used for the query. \param tolerance The tolerance used for the - * intersection tests. - */ - IntervalTreeIteratorImpl getIterator(Envelope1D query, double tolerance) { - return new IntervalTreeImpl.IntervalTreeIteratorImpl(this, query, tolerance); - } - - /** - * Gets an iterator on the Interval_tree_impl using the input double as the - * stabbing query. To reuse the existing iterator on the same - * Interval_tree_impl but with a new query, use the reset_iterator function - * on the Interval_tree_iterator_impl. \param query The double used for the - * stabbing query. \param tolerance The tolerance used for the intersection - * tests. - */ - IntervalTreeIteratorImpl getIterator(double query, double tolerance) { - return new IntervalTreeImpl.IntervalTreeIteratorImpl(this, query, tolerance); - } - - /** - * Gets an iterator on the Interval_tree_impl. - */ - IntervalTreeIteratorImpl getIterator() { - return new IntervalTreeImpl.IntervalTreeIteratorImpl(this); - } - - private boolean m_b_envelopes_ref; - private boolean m_b_offline_dynamic; - private ArrayList m_intervals; - private ArrayList m_envelopes_ref; - private StridedIndexTypeCollection m_tertiary_nodes; // 5 elements for offline dynamic case, 4 elements for static case - private StridedIndexTypeCollection m_interval_nodes; // 3 elements - private AttributeStreamOfInt32 m_interval_handles; // for offline dynamic// case - private IndexMultiDCList m_secondary_lists; // for static case - private Treap m_secondary_treaps; // for off-line dynamic case - private AttributeStreamOfInt32 m_end_indices_unique; // for both offline dynamic and static cases - private int m_c_count; - private int m_root; - private boolean m_b_sort_intervals; - private boolean m_b_constructing; - private boolean m_b_construction_ended; - - /* m_tertiary_nodes - * 0: m_discriminant_index_1 - * 1: m_secondary - * 2: m_lptr - * 3: m_rptr - * 4: m_pptr - */ - - private void querySortedEndPointIndices_(AttributeStreamOfInt32 end_indices) { - int size = (!m_b_envelopes_ref ? m_intervals.size() : m_envelopes_ref.size()); - - for (int i = 0; i < 2 * size; i++) - end_indices.add(i); - - sortEndIndices_(end_indices, 0, 2 * size); - } - - private void querySortedDuplicatesRemoved_(AttributeStreamOfInt32 end_indices_sorted) { - // remove duplicates - - double prev = NumberUtils.TheNaN; - for (int i = 0; i < end_indices_sorted.size(); i++) { - int e = end_indices_sorted.get(i); - double v = getValue_(e); - - if (v != prev) { - m_end_indices_unique.add(e); - prev = v; - } - } - } - - void insert(int index) { - if (!m_b_offline_dynamic || !m_b_construction_ended) - throw new IllegalArgumentException("invalid call"); - - if (m_root == -1) { - - int size = (!m_b_envelopes_ref ? m_intervals.size() : m_envelopes_ref.size()); - - if (m_b_sort_intervals) { - // sort - AttributeStreamOfInt32 end_point_indices_sorted = new AttributeStreamOfInt32(0); - end_point_indices_sorted.reserve(2 * size); - querySortedEndPointIndices_(end_point_indices_sorted); - - // remove duplicates - m_end_indices_unique.resize(0); - querySortedDuplicatesRemoved_(end_point_indices_sorted); - m_interval_handles.resize(size, -1); - m_interval_handles.setRange(-1, 0, size); - m_b_sort_intervals = false; - } else { - m_interval_handles.setRange(-1, 0, size); - } - - m_root = createRoot_(); - } - - int interval_handle = insertIntervalEnd_(index << 1, m_root); - int secondary_handle = getSecondaryFromInterval_(interval_handle); - int right_end_handle = m_secondary_treaps.addElement((index << 1) + 1, secondary_handle); - setRightEnd_(interval_handle, right_end_handle); - m_interval_handles.set(index, interval_handle); - m_c_count++; - // assert(check_validation_()); - } - - private void insertIntervalsStatic_() { - int size = (!m_b_envelopes_ref ? m_intervals.size() : m_envelopes_ref.size()); - - assert (m_b_sort_intervals); - - // sort - AttributeStreamOfInt32 end_indices_sorted = new AttributeStreamOfInt32(0); - end_indices_sorted.reserve(2 * size); - querySortedEndPointIndices_(end_indices_sorted); - - // remove duplicates - m_end_indices_unique.resize(0); - querySortedDuplicatesRemoved_(end_indices_sorted); - - assert (m_tertiary_nodes.size() == 0); - m_interval_nodes.setCapacity(size); // one for each interval being inserted. each element contains a tertiary node, a left secondary node, and a right secondary node. - m_secondary_lists.reserveNodes(2 * size); // one for each end point of the original interval set (not the unique set) - - AttributeStreamOfInt32 interval_handles = (AttributeStreamOfInt32) AttributeStreamBase.createIndexStream(size); - interval_handles.setRange(-1, 0, size); - - m_root = createRoot_(); - - for (int i = 0; i < end_indices_sorted.size(); i++) { - int e = end_indices_sorted.get(i); - int interval_handle = interval_handles.get(e >> 1); - - if (interval_handle != -1) {// insert the right end point - assert (isRight_(e)); - int secondary_handle = getSecondaryFromInterval_(interval_handle); - setRightEnd_(interval_handle, m_secondary_lists.addElement(secondary_handle, e)); - } else {// insert the left end point - assert (isLeft_(e)); - interval_handle = insertIntervalEnd_(e, m_root); - interval_handles.set(e >> 1, interval_handle); - } - } - - assert (m_secondary_lists.getNodeCount() == 2 * size); - } - - private int createRoot_() { - int discriminant_index_1 = calculateDiscriminantIndex1_(0, m_end_indices_unique.size() - 1); - return createTertiaryNode_(discriminant_index_1); - } - - private int insertIntervalEnd_(int end_index, int root) { - assert (isLeft_(end_index)); - int pptr = -1; - int ptr = root; - int secondary_handle = -1; - int interval_handle = -1; - int il = 0, ir = m_end_indices_unique.size() - 1, im = 0; - int index = end_index >> 1; - double discriminant_pptr = NumberUtils.NaN(); - double discriminant_ptr = NumberUtils.NaN(); - boolean bSearching = true; - - double min = getMin_(index); - double max = getMax_(index); - - int discriminant_index_1 = -1; - - while (bSearching) { - im = il + (ir - il) / 2; - assert (il != ir || min == max); - discriminant_index_1 = calculateDiscriminantIndex1_(il, ir); - double discriminant = getDiscriminantFromIndex1_(discriminant_index_1); - assert (!NumberUtils.isNaN(discriminant)); - - if (max < discriminant) { - if (ptr != -1) { - if (discriminant_index_1 == getDiscriminantIndex1_(ptr)) { - assert (getDiscriminantFromIndex1_(discriminant_index_1) == getDiscriminant_(ptr)); - - pptr = ptr; - discriminant_pptr = discriminant; - ptr = getLPTR_(ptr); - - if (ptr != -1) - discriminant_ptr = getDiscriminant_(ptr); - else - discriminant_ptr = NumberUtils.NaN(); - } else if (discriminant_ptr > discriminant) { - int tertiary_handle = createTertiaryNode_(discriminant_index_1); - - if (discriminant < discriminant_pptr) - setLPTR_(pptr, tertiary_handle); - else - setRPTR_(pptr, tertiary_handle); - - setRPTR_(tertiary_handle, ptr); - - if (m_b_offline_dynamic) { - setPPTR_(tertiary_handle, pptr); - setPPTR_(ptr, tertiary_handle); - } - - pptr = tertiary_handle; - discriminant_pptr = discriminant; - ptr = -1; - discriminant_ptr = NumberUtils.NaN(); - } - } - - ir = im; - - continue; - } - - if (min > discriminant) { - if (ptr != -1) { - if (discriminant_index_1 == getDiscriminantIndex1_(ptr)) { - assert (getDiscriminantFromIndex1_(discriminant_index_1) == getDiscriminant_(ptr)); - - pptr = ptr; - discriminant_pptr = discriminant; - ptr = getRPTR_(ptr); - - if (ptr != -1) - discriminant_ptr = getDiscriminant_(ptr); - else - discriminant_ptr = NumberUtils.NaN(); - } else if (discriminant_ptr < discriminant) { - int tertiary_handle = createTertiaryNode_(discriminant_index_1); - - if (discriminant < discriminant_pptr) - setLPTR_(pptr, tertiary_handle); - else - setRPTR_(pptr, tertiary_handle); - - setLPTR_(tertiary_handle, ptr); - - if (m_b_offline_dynamic) { - setPPTR_(tertiary_handle, pptr); - setPPTR_(ptr, tertiary_handle); - } - - pptr = tertiary_handle; - discriminant_pptr = discriminant; - ptr = -1; - discriminant_ptr = NumberUtils.NaN(); - } - } - - il = im + 1; - - continue; - } - - int tertiary_handle = -1; - - if (ptr == -1 || discriminant_index_1 != getDiscriminantIndex1_(ptr)) { - tertiary_handle = createTertiaryNode_(discriminant_index_1); - } else { - tertiary_handle = ptr; - } - - secondary_handle = getSecondaryFromTertiary_(tertiary_handle); - - if (secondary_handle == -1) { - secondary_handle = createSecondary_(tertiary_handle); - setSecondaryToTertiary_(tertiary_handle, secondary_handle); - } - - int left_end_handle = addEndIndex_(secondary_handle, end_index); - interval_handle = createIntervalNode_(); - setSecondaryToInterval_(interval_handle, secondary_handle); - setLeftEnd_(interval_handle, left_end_handle); - - if (ptr == -1 || discriminant_index_1 != getDiscriminantIndex1_(ptr)) { - assert (tertiary_handle != -1); - assert (getLPTR_(tertiary_handle) == -1 && getRPTR_(tertiary_handle) == -1 && (!m_b_offline_dynamic || getPPTR_(tertiary_handle) == -1)); - - if (discriminant < discriminant_pptr) - setLPTR_(pptr, tertiary_handle); - else - setRPTR_(pptr, tertiary_handle); - - if (m_b_offline_dynamic) - setPPTR_(tertiary_handle, pptr); - - if (ptr != -1) { - if (discriminant_ptr < discriminant) - setLPTR_(tertiary_handle, ptr); - else - setRPTR_(tertiary_handle, ptr); - - if (m_b_offline_dynamic) - setPPTR_(ptr, tertiary_handle); - } - } - - bSearching = false; - break; - } - - return interval_handle; - } - - void remove(int index) { - if (!m_b_offline_dynamic || !m_b_construction_ended) - throw new GeometryException("invalid call"); - - int interval_handle = m_interval_handles.get(index); - - if (interval_handle == -1) - throw new GeometryException("the interval does not exist in the interval tree"); - - m_interval_handles.set(index, -1); - - assert (getSecondaryFromInterval_(interval_handle) != -1); - assert (getLeftEnd_(interval_handle) != -1); - assert (getRightEnd_(interval_handle) != -1); - - m_c_count--; - - int size; - int secondary_handle = getSecondaryFromInterval_(interval_handle); - int tertiary_handle = -1; - - tertiary_handle = m_secondary_treaps.getTreapData(secondary_handle); - m_secondary_treaps.deleteNode(getLeftEnd_(interval_handle), secondary_handle); - m_secondary_treaps.deleteNode(getRightEnd_(interval_handle), secondary_handle); - size = m_secondary_treaps.size(secondary_handle); - - if (size == 0) { - m_secondary_treaps.deleteTreap(secondary_handle); - setSecondaryToTertiary_(tertiary_handle, -1); - } - - m_interval_nodes.deleteElement(interval_handle); - int pptr = getPPTR_(tertiary_handle); - int lptr = getLPTR_(tertiary_handle); - int rptr = getRPTR_(tertiary_handle); - - int iterations = 0; - while (!(size > 0 || tertiary_handle == m_root || (lptr != -1 && rptr != -1))) { - assert (size == 0); - assert (lptr == -1 || rptr == -1); - assert (tertiary_handle != 0); - - if (tertiary_handle == getLPTR_(pptr)) { - if (lptr != -1) { - setLPTR_(pptr, lptr); - setPPTR_(lptr, pptr); - setLPTR_(tertiary_handle, -1); - setPPTR_(tertiary_handle, -1); - } else if (rptr != -1) { - setLPTR_(pptr, rptr); - setPPTR_(rptr, pptr); - setRPTR_(tertiary_handle, -1); - setPPTR_(tertiary_handle, -1); - } else { - setLPTR_(pptr, -1); - setPPTR_(tertiary_handle, -1); - } - } else { - if (lptr != -1) { - setRPTR_(pptr, lptr); - setPPTR_(lptr, pptr); - setLPTR_(tertiary_handle, -1); - setPPTR_(tertiary_handle, -1); - } else if (rptr != -1) { - setRPTR_(pptr, rptr); - setPPTR_(rptr, pptr); - setRPTR_(tertiary_handle, -1); - setPPTR_(tertiary_handle, -1); - } else { - setRPTR_(pptr, -1); - setPPTR_(tertiary_handle, -1); - } - } - - m_tertiary_nodes.deleteElement(tertiary_handle); - - iterations++; - tertiary_handle = pptr; - secondary_handle = getSecondaryFromTertiary_(tertiary_handle); - size = (secondary_handle != -1 ? m_secondary_treaps.size(secondary_handle) : 0); - lptr = getLPTR_(tertiary_handle); - rptr = getRPTR_(tertiary_handle); - pptr = getPPTR_(tertiary_handle); - } - - assert (iterations <= 2); - //assert(check_validation_()); - } - - private void reset_(boolean b_new_intervals, boolean b_envelopes_ref) { - if (b_new_intervals) { - m_b_envelopes_ref = false; - m_envelopes_ref = null; - - m_b_sort_intervals = true; - m_b_constructing = true; - m_b_construction_ended = false; - - if (m_end_indices_unique == null) - m_end_indices_unique = (AttributeStreamOfInt32) (AttributeStreamBase.createIndexStream(0)); - else - m_end_indices_unique.resize(0); - - if (!b_envelopes_ref) { - if (m_intervals == null) - m_intervals = new ArrayList(0); - else - m_intervals.clear(); - } else { - if (m_intervals != null) - m_intervals.clear(); - - m_b_envelopes_ref = true; - } - } else { - assert (m_b_offline_dynamic && m_b_construction_ended); - m_b_sort_intervals = false; - } - - if (m_b_offline_dynamic) { - if (m_interval_handles == null) { - m_interval_handles = (AttributeStreamOfInt32) (AttributeStreamBase.createIndexStream(0)); - m_secondary_treaps = new Treap(); - m_secondary_treaps.setComparator(new SecondaryComparator(this)); - } else { - m_secondary_treaps.clear(); - } - } else { - if (m_secondary_lists == null) - m_secondary_lists = new IndexMultiDCList(); - else - m_secondary_lists.clear(); - } - - if (m_tertiary_nodes == null) { - m_interval_nodes = new StridedIndexTypeCollection(3); - m_tertiary_nodes = new StridedIndexTypeCollection(m_b_offline_dynamic ? 5 : 4); - } else { - m_interval_nodes.deleteAll(false); - m_tertiary_nodes.deleteAll(false); - } - - m_root = -1; - m_c_count = 0; - } - - private double getDiscriminant_(int tertiary_handle) { - int discriminant_index_1 = getDiscriminantIndex1_(tertiary_handle); - return getDiscriminantFromIndex1_(discriminant_index_1); - } - - private double getDiscriminantFromIndex1_(int discriminant_index_1) { - if (discriminant_index_1 == -1) - return NumberUtils.NaN(); - - if (discriminant_index_1 > 0) { - int j = discriminant_index_1 - 2; - int e_1 = m_end_indices_unique.get(j); - int e_2 = m_end_indices_unique.get(j + 1); - - double v_1 = getValue_(e_1); - double v_2 = getValue_(e_2); - assert (v_1 < v_2); - - return 0.5 * (v_1 + v_2); - } - - int j = -discriminant_index_1 - 2; - assert (j >= 0); - int e = m_end_indices_unique.get(j); - double v = getValue_(e); - - return v; - } - - private int calculateDiscriminantIndex1_(int il, int ir) { - int discriminant_index_1; - - if (il < ir) { - int im = il + (ir - il) / 2; - discriminant_index_1 = im + 2; // positive discriminant means use average of im and im + 1 - } else { - discriminant_index_1 = -(il + 2); // negative discriminant just means use il (-(il + 2) will never be -1) - } - - return discriminant_index_1; - } - - static final class IntervalTreeIteratorImpl { - - private IntervalTreeImpl m_interval_tree; - private Envelope1D m_query = new Envelope1D(); - private int m_tertiary_handle; - private int m_next_tertiary_handle; - private int m_forked_handle; - private int m_current_end_handle; - private int m_next_end_handle; - private AttributeStreamOfInt32 m_tertiary_stack = new AttributeStreamOfInt32(0); - private int m_function_index; - private int[] m_function_stack = new int[2]; - - private interface State { - static final int initialize = 0; - static final int pIn = 1; - static final int pL = 2; - static final int pR = 3; - static final int pT = 4; - static final int right = 5; - static final int left = 6; - static final int all = 7; - } - - private int getNext_() { - if (!m_interval_tree.m_b_offline_dynamic) - return m_interval_tree.m_secondary_lists.getNext(m_current_end_handle); - - return m_interval_tree.m_secondary_treaps.getNext(m_current_end_handle); - } - - private int getPrev_() { - if (!m_interval_tree.m_b_offline_dynamic) - return m_interval_tree.m_secondary_lists.getPrev(m_current_end_handle); - - return m_interval_tree.m_secondary_treaps.getPrev(m_current_end_handle); - } - - private int getCurrentEndIndex_() { - if (!m_interval_tree.m_b_offline_dynamic) - return m_interval_tree.m_secondary_lists.getData(m_current_end_handle); - - return m_interval_tree.m_secondary_treaps.getElement(m_current_end_handle); - } - - int next() { - if (!m_interval_tree.m_b_construction_ended) - throw new GeometryException("invalid call"); - - if (m_function_index < 0) - return -1; - - boolean b_searching = true; - - while (b_searching) { - switch (m_function_stack[m_function_index]) { - case State.pIn: - b_searching = pIn_(); - break; - case State.pL: - b_searching = pL_(); - break; - case State.pR: - b_searching = pR_(); - break; - case State.pT: - b_searching = pT_(); - break; - case State.right: - b_searching = right_(); - break; - case State.left: - b_searching = left_(); - break; - case State.all: - b_searching = all_(); - break; - case State.initialize: - b_searching = initialize_(); - break; - default: - throw GeometryException.GeometryInternalError(); - } - } - - if (m_current_end_handle != -1) - return getCurrentEndIndex_() >> 1; - - return -1; - } - - private boolean initialize_() { - m_tertiary_handle = -1; - m_next_tertiary_handle = -1; - m_forked_handle = -1; - m_current_end_handle = -1; - - if (m_interval_tree.m_tertiary_nodes != null && m_interval_tree.m_tertiary_nodes.size() > 0) { - m_function_stack[0] = State.pIn; // overwrite initialize - m_next_tertiary_handle = m_interval_tree.m_root; - return true; - } - - m_function_index = -1; - return false; - } - - private boolean pIn_() { - m_tertiary_handle = m_next_tertiary_handle; - - if (m_tertiary_handle == -1) { - m_function_index = -1; - m_current_end_handle = -1; - return false; - } - - double discriminant = m_interval_tree.getDiscriminant_(m_tertiary_handle); - - if (m_query.vmax < discriminant) { - int secondary_handle = m_interval_tree.getSecondaryFromTertiary_(m_tertiary_handle); - m_next_tertiary_handle = m_interval_tree.getLPTR_(m_tertiary_handle); - - if (secondary_handle != -1) { - m_next_end_handle = m_interval_tree.getFirst_(secondary_handle); - m_function_stack[++m_function_index] = State.left; - } - - return true; - } - - if (discriminant < m_query.vmin) { - int secondary_handle = m_interval_tree.getSecondaryFromTertiary_(m_tertiary_handle); - m_next_tertiary_handle = m_interval_tree.getRPTR_(m_tertiary_handle); - - if (secondary_handle != -1) { - m_next_end_handle = m_interval_tree.getLast_(secondary_handle); - m_function_stack[++m_function_index] = State.right; - } - - return true; - } - - assert (m_query.contains(discriminant)); - - m_function_stack[m_function_index] = State.pL; // overwrite pIn - m_forked_handle = m_tertiary_handle; - int secondary_handle = m_interval_tree.getSecondaryFromTertiary_(m_tertiary_handle); - m_next_tertiary_handle = m_interval_tree.getLPTR_(m_tertiary_handle); - - if (secondary_handle != -1) { - m_next_end_handle = m_interval_tree.getFirst_(secondary_handle); - m_function_stack[++m_function_index] = State.all; - } - - return true; - } - - private boolean pL_() { - m_tertiary_handle = m_next_tertiary_handle; - - if (m_tertiary_handle == -1) { - m_function_stack[m_function_index] = State.pR; // overwrite pL - m_next_tertiary_handle = m_interval_tree.getRPTR_(m_forked_handle); - return true; - } + private void sortEndIndices_(AttributeStreamOfInt32 end_indices, int begin_, int end_) { + IntervalTreeBucketSortHelper sorter = new IntervalTreeBucketSortHelper(this); + BucketSort bucket_sort = new BucketSort(); + bucket_sort.sort(end_indices, begin_, end_, sorter); + } + + private void sortEndIndicesHelper_(AttributeStreamOfInt32 end_indices, int begin_, int end_) { + end_indices.Sort(begin_, end_, new EndPointsComparer(this)); + } + + private double getValue_(int e) { + if (!m_b_envelopes_ref) { + Envelope1D interval = m_intervals.get(e >> 1); + double v = (isLeft_(e) ? interval.vmin : interval.vmax); + return v; + } + + Envelope2D interval = m_envelopes_ref.get(e >> 1); + double v = (isLeft_(e) ? interval.xmin : interval.xmax); + return v; + } + + private static final class EndPointsComparer extends AttributeStreamOfInt32.IntComparator { // For user sort + + EndPointsComparer(IntervalTreeImpl interval_tree) { + m_interval_tree = interval_tree; + } + + @Override + public int compare(int e_1, int e_2) { + double v_1 = m_interval_tree.getValue_(e_1); + double v_2 = m_interval_tree.getValue_(e_2); + + if (v_1 < v_2 || (v_1 == v_2 && isLeft_(e_1) && isRight_(e_2))) + return -1; + + return 1; + } + + private IntervalTreeImpl m_interval_tree; + } + + private class IntervalTreeBucketSortHelper extends ClassicSort { // For bucket sort + + IntervalTreeBucketSortHelper(IntervalTreeImpl interval_tree) { + m_interval_tree = interval_tree; + } + + @Override + public void userSort(int begin, int end, AttributeStreamOfInt32 indices) { + m_interval_tree.sortEndIndicesHelper_(indices, begin, end); + } + + @Override + public double getValue(int e) { + return m_interval_tree.getValue_(e); + } + + private IntervalTreeImpl m_interval_tree; + } + + IntervalTreeImpl(boolean b_offline_dynamic) { + m_b_envelopes_ref = false; + m_b_offline_dynamic = b_offline_dynamic; + m_b_constructing = false; + m_b_construction_ended = false; + } + + void addEnvelopesRef(ArrayList envelopes) { + reset_(true, true); + m_b_envelopes_ref = true; + m_envelopes_ref = envelopes; + + m_b_constructing = false; + m_b_construction_ended = true; + + if (!m_b_offline_dynamic) { + insertIntervalsStatic_(); + m_c_count = m_envelopes_ref.size(); + } + } + + void startConstruction() { + reset_(true, false); + } + + void addInterval(Envelope1D interval) { + if (!m_b_constructing) + throw new GeometryException("invalid call"); + + m_intervals.add(interval); + } + + void addInterval(double min, double max) { + if (!m_b_constructing) + throw new GeometryException("invald call"); + + m_intervals.add(new Envelope1D(min, max)); + } + + void endConstruction() { + if (!m_b_constructing) + throw new GeometryException("invalid call"); + + m_b_constructing = false; + m_b_construction_ended = true; + + if (!m_b_offline_dynamic) { + insertIntervalsStatic_(); + m_c_count = m_intervals.size(); + } + } + + /* + * Resets the Interval_tree_impl to an empty state, but maintains a handle + * on the current intervals. + */ + void reset() { + if (!m_b_offline_dynamic || !m_b_construction_ended) + throw new IllegalArgumentException("invalid call"); + + reset_(false, m_b_envelopes_ref); + } + + /** + * Returns the number of intervals stored in the Interval_tree_impl + */ + int size() { + return m_c_count; + } + + /** + * Gets an iterator on the Interval_tree_impl using the input Envelope_1D + * interval as the query. To reuse the existing iterator on the same + * Interval_tree_impl but with a new query, use the reset_iterator function + * on the Interval_tree_iterator_impl. \param query The Envelope_1D interval + * used for the query. \param tolerance The tolerance used for the + * intersection tests. + */ + IntervalTreeIteratorImpl getIterator(Envelope1D query, double tolerance) { + return new IntervalTreeImpl.IntervalTreeIteratorImpl(this, query, tolerance); + } + + /** + * Gets an iterator on the Interval_tree_impl using the input double as the + * stabbing query. To reuse the existing iterator on the same + * Interval_tree_impl but with a new query, use the reset_iterator function + * on the Interval_tree_iterator_impl. \param query The double used for the + * stabbing query. \param tolerance The tolerance used for the intersection + * tests. + */ + IntervalTreeIteratorImpl getIterator(double query, double tolerance) { + return new IntervalTreeImpl.IntervalTreeIteratorImpl(this, query, tolerance); + } + + /** + * Gets an iterator on the Interval_tree_impl. + */ + IntervalTreeIteratorImpl getIterator() { + return new IntervalTreeImpl.IntervalTreeIteratorImpl(this); + } + + private boolean m_b_envelopes_ref; + private boolean m_b_offline_dynamic; + private ArrayList m_intervals; + private ArrayList m_envelopes_ref; + private StridedIndexTypeCollection m_tertiary_nodes; // 5 elements for offline dynamic case, 4 elements for static case + private StridedIndexTypeCollection m_interval_nodes; // 3 elements + private AttributeStreamOfInt32 m_interval_handles; // for offline dynamic// case + private IndexMultiDCList m_secondary_lists; // for static case + private Treap m_secondary_treaps; // for off-line dynamic case + private AttributeStreamOfInt32 m_end_indices_unique; // for both offline dynamic and static cases + private int m_c_count; + private int m_root; + private boolean m_b_sort_intervals; + private boolean m_b_constructing; + private boolean m_b_construction_ended; + + /* m_tertiary_nodes + * 0: m_discriminant_index_1 + * 1: m_secondary + * 2: m_lptr + * 3: m_rptr + * 4: m_pptr + */ + + private void querySortedEndPointIndices_(AttributeStreamOfInt32 end_indices) { + int size = (!m_b_envelopes_ref ? m_intervals.size() : m_envelopes_ref.size()); + + for (int i = 0; i < 2 * size; i++) + end_indices.add(i); + + sortEndIndices_(end_indices, 0, 2 * size); + } + + private void querySortedDuplicatesRemoved_(AttributeStreamOfInt32 end_indices_sorted) { + // remove duplicates + + double prev = NumberUtils.TheNaN; + for (int i = 0; i < end_indices_sorted.size(); i++) { + int e = end_indices_sorted.get(i); + double v = getValue_(e); + + if (v != prev) { + m_end_indices_unique.add(e); + prev = v; + } + } + } + + void insert(int index) { + if (!m_b_offline_dynamic || !m_b_construction_ended) + throw new IllegalArgumentException("invalid call"); + + if (m_root == -1) { + + int size = (!m_b_envelopes_ref ? m_intervals.size() : m_envelopes_ref.size()); + + if (m_b_sort_intervals) { + // sort + AttributeStreamOfInt32 end_point_indices_sorted = new AttributeStreamOfInt32(0); + end_point_indices_sorted.reserve(2 * size); + querySortedEndPointIndices_(end_point_indices_sorted); + + // remove duplicates + m_end_indices_unique.resize(0); + querySortedDuplicatesRemoved_(end_point_indices_sorted); + m_interval_handles.resize(size, -1); + m_interval_handles.setRange(-1, 0, size); + m_b_sort_intervals = false; + } else { + m_interval_handles.setRange(-1, 0, size); + } + + m_root = createRoot_(); + } + + int interval_handle = insertIntervalEnd_(index << 1, m_root); + int secondary_handle = getSecondaryFromInterval_(interval_handle); + int right_end_handle = m_secondary_treaps.addElement((index << 1) + 1, secondary_handle); + setRightEnd_(interval_handle, right_end_handle); + m_interval_handles.set(index, interval_handle); + m_c_count++; + // assert(check_validation_()); + } + + private void insertIntervalsStatic_() { + int size = (!m_b_envelopes_ref ? m_intervals.size() : m_envelopes_ref.size()); + + assert (m_b_sort_intervals); + + // sort + AttributeStreamOfInt32 end_indices_sorted = new AttributeStreamOfInt32(0); + end_indices_sorted.reserve(2 * size); + querySortedEndPointIndices_(end_indices_sorted); + + // remove duplicates + m_end_indices_unique.resize(0); + querySortedDuplicatesRemoved_(end_indices_sorted); + + assert (m_tertiary_nodes.size() == 0); + m_interval_nodes.setCapacity(size); // one for each interval being inserted. each element contains a tertiary node, a left secondary node, and a right secondary node. + m_secondary_lists.reserveNodes(2 * size); // one for each end point of the original interval set (not the unique set) + + AttributeStreamOfInt32 interval_handles = (AttributeStreamOfInt32) AttributeStreamBase.createIndexStream(size); + interval_handles.setRange(-1, 0, size); + + m_root = createRoot_(); + + for (int i = 0; i < end_indices_sorted.size(); i++) { + int e = end_indices_sorted.get(i); + int interval_handle = interval_handles.get(e >> 1); + + if (interval_handle != -1) {// insert the right end point + assert (isRight_(e)); + int secondary_handle = getSecondaryFromInterval_(interval_handle); + setRightEnd_(interval_handle, m_secondary_lists.addElement(secondary_handle, e)); + } else {// insert the left end point + assert (isLeft_(e)); + interval_handle = insertIntervalEnd_(e, m_root); + interval_handles.set(e >> 1, interval_handle); + } + } + + assert (m_secondary_lists.getNodeCount() == 2 * size); + } + + private int createRoot_() { + int discriminant_index_1 = calculateDiscriminantIndex1_(0, m_end_indices_unique.size() - 1); + return createTertiaryNode_(discriminant_index_1); + } + + private int insertIntervalEnd_(int end_index, int root) { + assert (isLeft_(end_index)); + int pptr = -1; + int ptr = root; + int secondary_handle = -1; + int interval_handle = -1; + int il = 0, ir = m_end_indices_unique.size() - 1, im = 0; + int index = end_index >> 1; + double discriminant_pptr = NumberUtils.NaN(); + double discriminant_ptr = NumberUtils.NaN(); + boolean bSearching = true; + + double min = getMin_(index); + double max = getMax_(index); + + int discriminant_index_1 = -1; + + while (bSearching) { + im = il + (ir - il) / 2; + assert (il != ir || min == max); + discriminant_index_1 = calculateDiscriminantIndex1_(il, ir); + double discriminant = getDiscriminantFromIndex1_(discriminant_index_1); + assert (!NumberUtils.isNaN(discriminant)); + + if (max < discriminant) { + if (ptr != -1) { + if (discriminant_index_1 == getDiscriminantIndex1_(ptr)) { + assert (getDiscriminantFromIndex1_(discriminant_index_1) == getDiscriminant_(ptr)); + + pptr = ptr; + discriminant_pptr = discriminant; + ptr = getLPTR_(ptr); + + if (ptr != -1) + discriminant_ptr = getDiscriminant_(ptr); + else + discriminant_ptr = NumberUtils.NaN(); + } else if (discriminant_ptr > discriminant) { + int tertiary_handle = createTertiaryNode_(discriminant_index_1); + + if (discriminant < discriminant_pptr) + setLPTR_(pptr, tertiary_handle); + else + setRPTR_(pptr, tertiary_handle); + + setRPTR_(tertiary_handle, ptr); + + if (m_b_offline_dynamic) { + setPPTR_(tertiary_handle, pptr); + setPPTR_(ptr, tertiary_handle); + } + + pptr = tertiary_handle; + discriminant_pptr = discriminant; + ptr = -1; + discriminant_ptr = NumberUtils.NaN(); + } + } + + ir = im; + + continue; + } + + if (min > discriminant) { + if (ptr != -1) { + if (discriminant_index_1 == getDiscriminantIndex1_(ptr)) { + assert (getDiscriminantFromIndex1_(discriminant_index_1) == getDiscriminant_(ptr)); + + pptr = ptr; + discriminant_pptr = discriminant; + ptr = getRPTR_(ptr); + + if (ptr != -1) + discriminant_ptr = getDiscriminant_(ptr); + else + discriminant_ptr = NumberUtils.NaN(); + } else if (discriminant_ptr < discriminant) { + int tertiary_handle = createTertiaryNode_(discriminant_index_1); + + if (discriminant < discriminant_pptr) + setLPTR_(pptr, tertiary_handle); + else + setRPTR_(pptr, tertiary_handle); + + setLPTR_(tertiary_handle, ptr); + + if (m_b_offline_dynamic) { + setPPTR_(tertiary_handle, pptr); + setPPTR_(ptr, tertiary_handle); + } + + pptr = tertiary_handle; + discriminant_pptr = discriminant; + ptr = -1; + discriminant_ptr = NumberUtils.NaN(); + } + } + + il = im + 1; + + continue; + } + + int tertiary_handle = -1; + + if (ptr == -1 || discriminant_index_1 != getDiscriminantIndex1_(ptr)) { + tertiary_handle = createTertiaryNode_(discriminant_index_1); + } else { + tertiary_handle = ptr; + } + + secondary_handle = getSecondaryFromTertiary_(tertiary_handle); + + if (secondary_handle == -1) { + secondary_handle = createSecondary_(tertiary_handle); + setSecondaryToTertiary_(tertiary_handle, secondary_handle); + } + + int left_end_handle = addEndIndex_(secondary_handle, end_index); + interval_handle = createIntervalNode_(); + setSecondaryToInterval_(interval_handle, secondary_handle); + setLeftEnd_(interval_handle, left_end_handle); + + if (ptr == -1 || discriminant_index_1 != getDiscriminantIndex1_(ptr)) { + assert (tertiary_handle != -1); + assert (getLPTR_(tertiary_handle) == -1 && getRPTR_(tertiary_handle) == -1 && (!m_b_offline_dynamic || getPPTR_(tertiary_handle) == -1)); + + if (discriminant < discriminant_pptr) + setLPTR_(pptr, tertiary_handle); + else + setRPTR_(pptr, tertiary_handle); + + if (m_b_offline_dynamic) + setPPTR_(tertiary_handle, pptr); + + if (ptr != -1) { + if (discriminant_ptr < discriminant) + setLPTR_(tertiary_handle, ptr); + else + setRPTR_(tertiary_handle, ptr); + + if (m_b_offline_dynamic) + setPPTR_(ptr, tertiary_handle); + } + } + + bSearching = false; + break; + } + + return interval_handle; + } + + void remove(int index) { + if (!m_b_offline_dynamic || !m_b_construction_ended) + throw new GeometryException("invalid call"); + + int interval_handle = m_interval_handles.get(index); + + if (interval_handle == -1) + throw new GeometryException("the interval does not exist in the interval tree"); + + m_interval_handles.set(index, -1); + + assert (getSecondaryFromInterval_(interval_handle) != -1); + assert (getLeftEnd_(interval_handle) != -1); + assert (getRightEnd_(interval_handle) != -1); + + m_c_count--; + + int size; + int secondary_handle = getSecondaryFromInterval_(interval_handle); + int tertiary_handle = -1; + + tertiary_handle = m_secondary_treaps.getTreapData(secondary_handle); + m_secondary_treaps.deleteNode(getLeftEnd_(interval_handle), secondary_handle); + m_secondary_treaps.deleteNode(getRightEnd_(interval_handle), secondary_handle); + size = m_secondary_treaps.size(secondary_handle); + + if (size == 0) { + m_secondary_treaps.deleteTreap(secondary_handle); + setSecondaryToTertiary_(tertiary_handle, -1); + } + + m_interval_nodes.deleteElement(interval_handle); + int pptr = getPPTR_(tertiary_handle); + int lptr = getLPTR_(tertiary_handle); + int rptr = getRPTR_(tertiary_handle); + + int iterations = 0; + while (!(size > 0 || tertiary_handle == m_root || (lptr != -1 && rptr != -1))) { + assert (size == 0); + assert (lptr == -1 || rptr == -1); + assert (tertiary_handle != 0); + + if (tertiary_handle == getLPTR_(pptr)) { + if (lptr != -1) { + setLPTR_(pptr, lptr); + setPPTR_(lptr, pptr); + setLPTR_(tertiary_handle, -1); + setPPTR_(tertiary_handle, -1); + } else if (rptr != -1) { + setLPTR_(pptr, rptr); + setPPTR_(rptr, pptr); + setRPTR_(tertiary_handle, -1); + setPPTR_(tertiary_handle, -1); + } else { + setLPTR_(pptr, -1); + setPPTR_(tertiary_handle, -1); + } + } else { + if (lptr != -1) { + setRPTR_(pptr, lptr); + setPPTR_(lptr, pptr); + setLPTR_(tertiary_handle, -1); + setPPTR_(tertiary_handle, -1); + } else if (rptr != -1) { + setRPTR_(pptr, rptr); + setPPTR_(rptr, pptr); + setRPTR_(tertiary_handle, -1); + setPPTR_(tertiary_handle, -1); + } else { + setRPTR_(pptr, -1); + setPPTR_(tertiary_handle, -1); + } + } + + m_tertiary_nodes.deleteElement(tertiary_handle); + + iterations++; + tertiary_handle = pptr; + secondary_handle = getSecondaryFromTertiary_(tertiary_handle); + size = (secondary_handle != -1 ? m_secondary_treaps.size(secondary_handle) : 0); + lptr = getLPTR_(tertiary_handle); + rptr = getRPTR_(tertiary_handle); + pptr = getPPTR_(tertiary_handle); + } + + assert (iterations <= 2); + //assert(check_validation_()); + } + + private void reset_(boolean b_new_intervals, boolean b_envelopes_ref) { + if (b_new_intervals) { + m_b_envelopes_ref = false; + m_envelopes_ref = null; + + m_b_sort_intervals = true; + m_b_constructing = true; + m_b_construction_ended = false; + + if (m_end_indices_unique == null) + m_end_indices_unique = (AttributeStreamOfInt32) (AttributeStreamBase.createIndexStream(0)); + else + m_end_indices_unique.resize(0); + + if (!b_envelopes_ref) { + if (m_intervals == null) + m_intervals = new ArrayList(0); + else + m_intervals.clear(); + } else { + if (m_intervals != null) + m_intervals.clear(); + + m_b_envelopes_ref = true; + } + } else { + assert (m_b_offline_dynamic && m_b_construction_ended); + m_b_sort_intervals = false; + } + + if (m_b_offline_dynamic) { + if (m_interval_handles == null) { + m_interval_handles = (AttributeStreamOfInt32) (AttributeStreamBase.createIndexStream(0)); + m_secondary_treaps = new Treap(); + m_secondary_treaps.setComparator(new SecondaryComparator(this)); + } else { + m_secondary_treaps.clear(); + } + } else { + if (m_secondary_lists == null) + m_secondary_lists = new IndexMultiDCList(); + else + m_secondary_lists.clear(); + } + + if (m_tertiary_nodes == null) { + m_interval_nodes = new StridedIndexTypeCollection(3); + m_tertiary_nodes = new StridedIndexTypeCollection(m_b_offline_dynamic ? 5 : 4); + } else { + m_interval_nodes.deleteAll(false); + m_tertiary_nodes.deleteAll(false); + } + + m_root = -1; + m_c_count = 0; + } + + private double getDiscriminant_(int tertiary_handle) { + int discriminant_index_1 = getDiscriminantIndex1_(tertiary_handle); + return getDiscriminantFromIndex1_(discriminant_index_1); + } + + private double getDiscriminantFromIndex1_(int discriminant_index_1) { + if (discriminant_index_1 == -1) + return NumberUtils.NaN(); + + if (discriminant_index_1 > 0) { + int j = discriminant_index_1 - 2; + int e_1 = m_end_indices_unique.get(j); + int e_2 = m_end_indices_unique.get(j + 1); + + double v_1 = getValue_(e_1); + double v_2 = getValue_(e_2); + assert (v_1 < v_2); + + return 0.5 * (v_1 + v_2); + } + + int j = -discriminant_index_1 - 2; + assert (j >= 0); + int e = m_end_indices_unique.get(j); + double v = getValue_(e); + + return v; + } + + private int calculateDiscriminantIndex1_(int il, int ir) { + int discriminant_index_1; + + if (il < ir) { + int im = il + (ir - il) / 2; + discriminant_index_1 = im + 2; // positive discriminant means use average of im and im + 1 + } else { + discriminant_index_1 = -(il + 2); // negative discriminant just means use il (-(il + 2) will never be -1) + } + + return discriminant_index_1; + } + + static final class IntervalTreeIteratorImpl { + + private IntervalTreeImpl m_interval_tree; + private Envelope1D m_query = new Envelope1D(); + private int m_tertiary_handle; + private int m_next_tertiary_handle; + private int m_forked_handle; + private int m_current_end_handle; + private int m_next_end_handle; + private AttributeStreamOfInt32 m_tertiary_stack = new AttributeStreamOfInt32(0); + private int m_function_index; + private int[] m_function_stack = new int[2]; + + private interface State { + static final int initialize = 0; + static final int pIn = 1; + static final int pL = 2; + static final int pR = 3; + static final int pT = 4; + static final int right = 5; + static final int left = 6; + static final int all = 7; + } + + private int getNext_() { + if (!m_interval_tree.m_b_offline_dynamic) + return m_interval_tree.m_secondary_lists.getNext(m_current_end_handle); + + return m_interval_tree.m_secondary_treaps.getNext(m_current_end_handle); + } + + private int getPrev_() { + if (!m_interval_tree.m_b_offline_dynamic) + return m_interval_tree.m_secondary_lists.getPrev(m_current_end_handle); + + return m_interval_tree.m_secondary_treaps.getPrev(m_current_end_handle); + } + + private int getCurrentEndIndex_() { + if (!m_interval_tree.m_b_offline_dynamic) + return m_interval_tree.m_secondary_lists.getData(m_current_end_handle); + + return m_interval_tree.m_secondary_treaps.getElement(m_current_end_handle); + } + + int next() { + if (!m_interval_tree.m_b_construction_ended) + throw new GeometryException("invalid call"); + + if (m_function_index < 0) + return -1; + + boolean b_searching = true; + + while (b_searching) { + switch (m_function_stack[m_function_index]) { + case State.pIn: + b_searching = pIn_(); + break; + case State.pL: + b_searching = pL_(); + break; + case State.pR: + b_searching = pR_(); + break; + case State.pT: + b_searching = pT_(); + break; + case State.right: + b_searching = right_(); + break; + case State.left: + b_searching = left_(); + break; + case State.all: + b_searching = all_(); + break; + case State.initialize: + b_searching = initialize_(); + break; + default: + throw GeometryException.GeometryInternalError(); + } + } + + if (m_current_end_handle != -1) + return getCurrentEndIndex_() >> 1; + + return -1; + } + + private boolean initialize_() { + m_tertiary_handle = -1; + m_next_tertiary_handle = -1; + m_forked_handle = -1; + m_current_end_handle = -1; + + if (m_interval_tree.m_tertiary_nodes != null && m_interval_tree.m_tertiary_nodes.size() > 0) { + m_function_stack[0] = State.pIn; // overwrite initialize + m_next_tertiary_handle = m_interval_tree.m_root; + return true; + } + + m_function_index = -1; + return false; + } + + private boolean pIn_() { + m_tertiary_handle = m_next_tertiary_handle; + + if (m_tertiary_handle == -1) { + m_function_index = -1; + m_current_end_handle = -1; + return false; + } + + double discriminant = m_interval_tree.getDiscriminant_(m_tertiary_handle); + + if (m_query.vmax < discriminant) { + int secondary_handle = m_interval_tree.getSecondaryFromTertiary_(m_tertiary_handle); + m_next_tertiary_handle = m_interval_tree.getLPTR_(m_tertiary_handle); + + if (secondary_handle != -1) { + m_next_end_handle = m_interval_tree.getFirst_(secondary_handle); + m_function_stack[++m_function_index] = State.left; + } + + return true; + } + + if (discriminant < m_query.vmin) { + int secondary_handle = m_interval_tree.getSecondaryFromTertiary_(m_tertiary_handle); + m_next_tertiary_handle = m_interval_tree.getRPTR_(m_tertiary_handle); + + if (secondary_handle != -1) { + m_next_end_handle = m_interval_tree.getLast_(secondary_handle); + m_function_stack[++m_function_index] = State.right; + } + + return true; + } + + assert (m_query.contains(discriminant)); + + m_function_stack[m_function_index] = State.pL; // overwrite pIn + m_forked_handle = m_tertiary_handle; + int secondary_handle = m_interval_tree.getSecondaryFromTertiary_(m_tertiary_handle); + m_next_tertiary_handle = m_interval_tree.getLPTR_(m_tertiary_handle); + + if (secondary_handle != -1) { + m_next_end_handle = m_interval_tree.getFirst_(secondary_handle); + m_function_stack[++m_function_index] = State.all; + } + + return true; + } + + private boolean pL_() { + m_tertiary_handle = m_next_tertiary_handle; + + if (m_tertiary_handle == -1) { + m_function_stack[m_function_index] = State.pR; // overwrite pL + m_next_tertiary_handle = m_interval_tree.getRPTR_(m_forked_handle); + return true; + } - double discriminant = m_interval_tree.getDiscriminant_(m_tertiary_handle); + double discriminant = m_interval_tree.getDiscriminant_(m_tertiary_handle); - if (discriminant < m_query.vmin) { - int secondary_handle = m_interval_tree.getSecondaryFromTertiary_(m_tertiary_handle); - m_next_tertiary_handle = m_interval_tree.getRPTR_(m_tertiary_handle); + if (discriminant < m_query.vmin) { + int secondary_handle = m_interval_tree.getSecondaryFromTertiary_(m_tertiary_handle); + m_next_tertiary_handle = m_interval_tree.getRPTR_(m_tertiary_handle); - if (secondary_handle != -1) { - m_next_end_handle = m_interval_tree.getLast_(secondary_handle); - m_function_stack[++m_function_index] = State.right; - } + if (secondary_handle != -1) { + m_next_end_handle = m_interval_tree.getLast_(secondary_handle); + m_function_stack[++m_function_index] = State.right; + } - return true; - } + return true; + } - assert (m_query.contains(discriminant)); + assert (m_query.contains(discriminant)); - int secondary_handle = m_interval_tree.getSecondaryFromTertiary_(m_tertiary_handle); - m_next_tertiary_handle = m_interval_tree.getLPTR_(m_tertiary_handle); + int secondary_handle = m_interval_tree.getSecondaryFromTertiary_(m_tertiary_handle); + m_next_tertiary_handle = m_interval_tree.getLPTR_(m_tertiary_handle); - if (secondary_handle != -1) { - m_next_end_handle = m_interval_tree.getFirst_(secondary_handle); - m_function_stack[++m_function_index] = State.all; - } + if (secondary_handle != -1) { + m_next_end_handle = m_interval_tree.getFirst_(secondary_handle); + m_function_stack[++m_function_index] = State.all; + } - int rptr = m_interval_tree.getRPTR_(m_tertiary_handle); + int rptr = m_interval_tree.getRPTR_(m_tertiary_handle); - if (rptr != -1) { - m_tertiary_stack.add(rptr); // we'll search this in the pT state - } + if (rptr != -1) { + m_tertiary_stack.add(rptr); // we'll search this in the pT state + } - return true; - } + return true; + } - private boolean pR_() { - m_tertiary_handle = m_next_tertiary_handle; + private boolean pR_() { + m_tertiary_handle = m_next_tertiary_handle; - if (m_tertiary_handle == -1) { - m_function_stack[m_function_index] = State.pT; // overwrite pR - return true; - } + if (m_tertiary_handle == -1) { + m_function_stack[m_function_index] = State.pT; // overwrite pR + return true; + } - double discriminant = m_interval_tree.getDiscriminant_(m_tertiary_handle); + double discriminant = m_interval_tree.getDiscriminant_(m_tertiary_handle); - if (m_query.vmax < discriminant) { - int secondary_handle = m_interval_tree.getSecondaryFromTertiary_(m_tertiary_handle); - m_next_tertiary_handle = m_interval_tree.getLPTR_(m_tertiary_handle); + if (m_query.vmax < discriminant) { + int secondary_handle = m_interval_tree.getSecondaryFromTertiary_(m_tertiary_handle); + m_next_tertiary_handle = m_interval_tree.getLPTR_(m_tertiary_handle); - if (secondary_handle != -1) { - m_next_end_handle = m_interval_tree.getFirst_(secondary_handle); - m_function_stack[++m_function_index] = State.left; - } + if (secondary_handle != -1) { + m_next_end_handle = m_interval_tree.getFirst_(secondary_handle); + m_function_stack[++m_function_index] = State.left; + } - return true; - } + return true; + } - assert (m_query.contains(discriminant)); + assert (m_query.contains(discriminant)); - int secondary_handle = m_interval_tree.getSecondaryFromTertiary_(m_tertiary_handle); + int secondary_handle = m_interval_tree.getSecondaryFromTertiary_(m_tertiary_handle); - m_next_tertiary_handle = m_interval_tree.getRPTR_(m_tertiary_handle); + m_next_tertiary_handle = m_interval_tree.getRPTR_(m_tertiary_handle); - if (secondary_handle != -1) { - m_next_end_handle = m_interval_tree.getFirst_(secondary_handle); - m_function_stack[++m_function_index] = State.all; - } + if (secondary_handle != -1) { + m_next_end_handle = m_interval_tree.getFirst_(secondary_handle); + m_function_stack[++m_function_index] = State.all; + } - int lptr = m_interval_tree.getLPTR_(m_tertiary_handle); + int lptr = m_interval_tree.getLPTR_(m_tertiary_handle); - if (lptr != -1) { - m_tertiary_stack.add(lptr); // we'll search this in the pT state - } + if (lptr != -1) { + m_tertiary_stack.add(lptr); // we'll search this in the pT state + } - return true; - } + return true; + } - private boolean pT_() { - if (m_tertiary_stack.size() == 0) { - m_function_index = -1; - m_current_end_handle = -1; - return false; - } + private boolean pT_() { + if (m_tertiary_stack.size() == 0) { + m_function_index = -1; + m_current_end_handle = -1; + return false; + } - m_tertiary_handle = m_tertiary_stack.get(m_tertiary_stack.size() - 1); - m_tertiary_stack.resize(m_tertiary_stack.size() - 1); + m_tertiary_handle = m_tertiary_stack.get(m_tertiary_stack.size() - 1); + m_tertiary_stack.resize(m_tertiary_stack.size() - 1); - int secondary_handle = m_interval_tree.getSecondaryFromTertiary_(m_tertiary_handle); + int secondary_handle = m_interval_tree.getSecondaryFromTertiary_(m_tertiary_handle); - if (secondary_handle != -1) { - m_next_end_handle = m_interval_tree.getFirst_(secondary_handle); - m_function_stack[++m_function_index] = State.all; - } - - if (m_interval_tree.getLPTR_(m_tertiary_handle) != -1) - m_tertiary_stack.add(m_interval_tree.getLPTR_(m_tertiary_handle)); - - if (m_interval_tree.getRPTR_(m_tertiary_handle) != -1) - m_tertiary_stack.add(m_interval_tree.getRPTR_(m_tertiary_handle)); - - return true; - } - - private boolean left_() { - m_current_end_handle = m_next_end_handle; - - if (m_current_end_handle != -1 && IntervalTreeImpl.isLeft_(getCurrentEndIndex_()) && m_interval_tree.getValue_(getCurrentEndIndex_()) <= m_query.vmax) { - m_next_end_handle = getNext_(); - return false; - } - - m_function_index--; - return true; - } - - private boolean right_() { - m_current_end_handle = m_next_end_handle; - - if (m_current_end_handle != -1 && IntervalTreeImpl.isRight_(getCurrentEndIndex_()) && m_interval_tree.getValue_(getCurrentEndIndex_()) >= m_query.vmin) { - m_next_end_handle = getPrev_(); - return false; - } - - m_function_index--; - return true; - } - - private boolean all_() { - m_current_end_handle = m_next_end_handle; - - if (m_current_end_handle != -1 && IntervalTreeImpl.isLeft_(getCurrentEndIndex_())) { - m_next_end_handle = getNext_(); - return false; - } - - m_function_index--; - return true; - } - - IntervalTreeIteratorImpl(IntervalTreeImpl interval_tree, Envelope1D query, double tolerance) { - m_interval_tree = interval_tree; - m_tertiary_stack.reserve(20); - resetIterator(query, tolerance); - } - - IntervalTreeIteratorImpl(IntervalTreeImpl interval_tree, double query, double tolerance) { - m_interval_tree = interval_tree; - m_tertiary_stack.reserve(20); - resetIterator(query, tolerance); - } - - IntervalTreeIteratorImpl(IntervalTreeImpl interval_tree) { - m_interval_tree = interval_tree; - m_tertiary_stack.reserve(20); - m_function_index = -1; - } - - void resetIterator(Envelope1D query, double tolerance) { - m_query.vmin = query.vmin - tolerance; - m_query.vmax = query.vmax + tolerance; - m_tertiary_stack.resize(0); - m_function_index = 0; - m_function_stack[0] = State.initialize; - } - - void resetIterator(double query_min, double query_max, double tolerance) { - m_query.vmin = query_min - tolerance; - m_query.vmax = query_max + tolerance; - m_tertiary_stack.resize(0); - m_function_index = 0; - m_function_stack[0] = State.initialize; - } - - void resetIterator(double query, double tolerance) { - m_query.vmin = query - tolerance; - m_query.vmax = query + tolerance; - m_tertiary_stack.resize(0); - m_function_index = 0; - m_function_stack[0] = State.initialize; - } - } - - private static final class SecondaryComparator extends Treap.Comparator { - SecondaryComparator(IntervalTreeImpl interval_tree) { - m_interval_tree = interval_tree; - } - - @Override - public int compare(Treap treap, int e_1, int node) { - int e_2 = treap.getElement(node); - double v_1 = m_interval_tree.getValue_(e_1); - double v_2 = m_interval_tree.getValue_(e_2); - - if (v_1 < v_2) - return -1; - if (v_1 == v_2) { - if (isLeft_(e_1) && isRight_(e_2)) - return -1; - if (isLeft_(e_2) && isRight_(e_1)) - return 1; - return 0; - } - return 1; - } - - private IntervalTreeImpl m_interval_tree; - } - - private int createTertiaryNode_(int discriminant_index_1) { - int tertiary_handle = m_tertiary_nodes.newElement(); - setDiscriminantIndex1_(tertiary_handle, discriminant_index_1); - return tertiary_handle; - } - - private int createSecondary_(int tertiary_handle) { - if (!m_b_offline_dynamic) - return m_secondary_lists.createList(tertiary_handle); - - return m_secondary_treaps.createTreap(tertiary_handle); - } - - private int createIntervalNode_() { - return m_interval_nodes.newElement(); - } - - private void setDiscriminantIndex1_(int tertiary_handle, int end_index) { - m_tertiary_nodes.setField(tertiary_handle, 0, end_index); - } - - private void setSecondaryToTertiary_(int tertiary_handle, int secondary_handle) { - m_tertiary_nodes.setField(tertiary_handle, 1, secondary_handle); - } - - private void setLPTR_(int tertiary_handle, int lptr) { - m_tertiary_nodes.setField(tertiary_handle, 2, lptr); - } - - private void setRPTR_(int tertiary_handle, int rptr) { - m_tertiary_nodes.setField(tertiary_handle, 3, rptr); - } - - private void setPPTR_(int tertiary_handle, int pptr) { - m_tertiary_nodes.setField(tertiary_handle, 4, pptr); - } - - private void setSecondaryToInterval_(int interval_handle, int secondary_handle) { - m_interval_nodes.setField(interval_handle, 0, secondary_handle); - } - - private int addEndIndex_(int secondary_handle, int end_index) { - int end_index_handle; - - if (!m_b_offline_dynamic) - end_index_handle = m_secondary_lists.addElement(secondary_handle, end_index); - else - end_index_handle = m_secondary_treaps.addElement(end_index, secondary_handle); - - return end_index_handle; - } - - private void setLeftEnd_(int interval_handle, int left_end_handle) { - m_interval_nodes.setField(interval_handle, 1, left_end_handle); - } - - private void setRightEnd_(int interval_handle, int right_end_handle) { - m_interval_nodes.setField(interval_handle, 2, right_end_handle); - } - - private int getDiscriminantIndex1_(int tertiary_handle) { - return m_tertiary_nodes.getField(tertiary_handle, 0); - } - - private int getSecondaryFromTertiary_(int tertiary_handle) { - return m_tertiary_nodes.getField(tertiary_handle, 1); - } - - private int getLPTR_(int tertiary_handle) { - return m_tertiary_nodes.getField(tertiary_handle, 2); - } - - private int getRPTR_(int tertiary_handle) { - return m_tertiary_nodes.getField(tertiary_handle, 3); - } - - private int getPPTR_(int tertiary_handle) { - return m_tertiary_nodes.getField(tertiary_handle, 4); - } - - private int getSecondaryFromInterval_(int interval_handle) { - return m_interval_nodes.getField(interval_handle, 0); - } - - private int getLeftEnd_(int interval_handle) { - return m_interval_nodes.getField(interval_handle, 1); - } - - private int getRightEnd_(int interval_handle) { - return m_interval_nodes.getField(interval_handle, 2); - } + if (secondary_handle != -1) { + m_next_end_handle = m_interval_tree.getFirst_(secondary_handle); + m_function_stack[++m_function_index] = State.all; + } + + if (m_interval_tree.getLPTR_(m_tertiary_handle) != -1) + m_tertiary_stack.add(m_interval_tree.getLPTR_(m_tertiary_handle)); + + if (m_interval_tree.getRPTR_(m_tertiary_handle) != -1) + m_tertiary_stack.add(m_interval_tree.getRPTR_(m_tertiary_handle)); + + return true; + } + + private boolean left_() { + m_current_end_handle = m_next_end_handle; + + if (m_current_end_handle != -1 && IntervalTreeImpl.isLeft_(getCurrentEndIndex_()) && m_interval_tree.getValue_(getCurrentEndIndex_()) <= m_query.vmax) { + m_next_end_handle = getNext_(); + return false; + } + + m_function_index--; + return true; + } + + private boolean right_() { + m_current_end_handle = m_next_end_handle; + + if (m_current_end_handle != -1 && IntervalTreeImpl.isRight_(getCurrentEndIndex_()) && m_interval_tree.getValue_(getCurrentEndIndex_()) >= m_query.vmin) { + m_next_end_handle = getPrev_(); + return false; + } + + m_function_index--; + return true; + } + + private boolean all_() { + m_current_end_handle = m_next_end_handle; + + if (m_current_end_handle != -1 && IntervalTreeImpl.isLeft_(getCurrentEndIndex_())) { + m_next_end_handle = getNext_(); + return false; + } + + m_function_index--; + return true; + } + + IntervalTreeIteratorImpl(IntervalTreeImpl interval_tree, Envelope1D query, double tolerance) { + m_interval_tree = interval_tree; + m_tertiary_stack.reserve(20); + resetIterator(query, tolerance); + } + + IntervalTreeIteratorImpl(IntervalTreeImpl interval_tree, double query, double tolerance) { + m_interval_tree = interval_tree; + m_tertiary_stack.reserve(20); + resetIterator(query, tolerance); + } + + IntervalTreeIteratorImpl(IntervalTreeImpl interval_tree) { + m_interval_tree = interval_tree; + m_tertiary_stack.reserve(20); + m_function_index = -1; + } + + void resetIterator(Envelope1D query, double tolerance) { + m_query.vmin = query.vmin - tolerance; + m_query.vmax = query.vmax + tolerance; + m_tertiary_stack.resize(0); + m_function_index = 0; + m_function_stack[0] = State.initialize; + } + + void resetIterator(double query_min, double query_max, double tolerance) { + m_query.vmin = query_min - tolerance; + m_query.vmax = query_max + tolerance; + m_tertiary_stack.resize(0); + m_function_index = 0; + m_function_stack[0] = State.initialize; + } + + void resetIterator(double query, double tolerance) { + m_query.vmin = query - tolerance; + m_query.vmax = query + tolerance; + m_tertiary_stack.resize(0); + m_function_index = 0; + m_function_stack[0] = State.initialize; + } + } + + private static final class SecondaryComparator extends Treap.Comparator { + SecondaryComparator(IntervalTreeImpl interval_tree) { + m_interval_tree = interval_tree; + } + + @Override + public int compare(Treap treap, int e_1, int node) { + int e_2 = treap.getElement(node); + double v_1 = m_interval_tree.getValue_(e_1); + double v_2 = m_interval_tree.getValue_(e_2); + + if (v_1 < v_2) + return -1; + if (v_1 == v_2) { + if (isLeft_(e_1) && isRight_(e_2)) + return -1; + if (isLeft_(e_2) && isRight_(e_1)) + return 1; + return 0; + } + return 1; + } + + private IntervalTreeImpl m_interval_tree; + } + + private int createTertiaryNode_(int discriminant_index_1) { + int tertiary_handle = m_tertiary_nodes.newElement(); + setDiscriminantIndex1_(tertiary_handle, discriminant_index_1); + return tertiary_handle; + } + + private int createSecondary_(int tertiary_handle) { + if (!m_b_offline_dynamic) + return m_secondary_lists.createList(tertiary_handle); + + return m_secondary_treaps.createTreap(tertiary_handle); + } + + private int createIntervalNode_() { + return m_interval_nodes.newElement(); + } + + private void setDiscriminantIndex1_(int tertiary_handle, int end_index) { + m_tertiary_nodes.setField(tertiary_handle, 0, end_index); + } + + private void setSecondaryToTertiary_(int tertiary_handle, int secondary_handle) { + m_tertiary_nodes.setField(tertiary_handle, 1, secondary_handle); + } + + private void setLPTR_(int tertiary_handle, int lptr) { + m_tertiary_nodes.setField(tertiary_handle, 2, lptr); + } + + private void setRPTR_(int tertiary_handle, int rptr) { + m_tertiary_nodes.setField(tertiary_handle, 3, rptr); + } + + private void setPPTR_(int tertiary_handle, int pptr) { + m_tertiary_nodes.setField(tertiary_handle, 4, pptr); + } + + private void setSecondaryToInterval_(int interval_handle, int secondary_handle) { + m_interval_nodes.setField(interval_handle, 0, secondary_handle); + } + + private int addEndIndex_(int secondary_handle, int end_index) { + int end_index_handle; + + if (!m_b_offline_dynamic) + end_index_handle = m_secondary_lists.addElement(secondary_handle, end_index); + else + end_index_handle = m_secondary_treaps.addElement(end_index, secondary_handle); + + return end_index_handle; + } + + private void setLeftEnd_(int interval_handle, int left_end_handle) { + m_interval_nodes.setField(interval_handle, 1, left_end_handle); + } + + private void setRightEnd_(int interval_handle, int right_end_handle) { + m_interval_nodes.setField(interval_handle, 2, right_end_handle); + } + + private int getDiscriminantIndex1_(int tertiary_handle) { + return m_tertiary_nodes.getField(tertiary_handle, 0); + } + + private int getSecondaryFromTertiary_(int tertiary_handle) { + return m_tertiary_nodes.getField(tertiary_handle, 1); + } + + private int getLPTR_(int tertiary_handle) { + return m_tertiary_nodes.getField(tertiary_handle, 2); + } + + private int getRPTR_(int tertiary_handle) { + return m_tertiary_nodes.getField(tertiary_handle, 3); + } + + private int getPPTR_(int tertiary_handle) { + return m_tertiary_nodes.getField(tertiary_handle, 4); + } + + private int getSecondaryFromInterval_(int interval_handle) { + return m_interval_nodes.getField(interval_handle, 0); + } + + private int getLeftEnd_(int interval_handle) { + return m_interval_nodes.getField(interval_handle, 1); + } + + private int getRightEnd_(int interval_handle) { + return m_interval_nodes.getField(interval_handle, 2); + } - private double getMin_(int i) { - return (!m_b_envelopes_ref ? m_intervals.get(i).vmin : m_envelopes_ref.get(i).xmin); - } + private double getMin_(int i) { + return (!m_b_envelopes_ref ? m_intervals.get(i).vmin : m_envelopes_ref.get(i).xmin); + } - private double getMax_(int i) { - return (!m_b_envelopes_ref ? m_intervals.get(i).vmax : m_envelopes_ref.get(i).xmax); - } + private double getMax_(int i) { + return (!m_b_envelopes_ref ? m_intervals.get(i).vmax : m_envelopes_ref.get(i).xmax); + } - private int getFirst_(int secondary_handle) { - if (!m_b_offline_dynamic) - return m_secondary_lists.getFirst(secondary_handle); + private int getFirst_(int secondary_handle) { + if (!m_b_offline_dynamic) + return m_secondary_lists.getFirst(secondary_handle); - return m_secondary_treaps.getFirst(secondary_handle); - } + return m_secondary_treaps.getFirst(secondary_handle); + } - private int getLast_(int secondary_handle) { - if (!m_b_offline_dynamic) - return m_secondary_lists.getLast(secondary_handle); + private int getLast_(int secondary_handle) { + if (!m_b_offline_dynamic) + return m_secondary_lists.getLast(secondary_handle); - return m_secondary_treaps.getLast(secondary_handle); - } + return m_secondary_treaps.getLast(secondary_handle); + } - private static boolean isLeft_(int end_index) { - return (end_index & 0x1) == 0; - } + private static boolean isLeft_(int end_index) { + return (end_index & 0x1) == 0; + } - private static boolean isRight_(int end_index) { - return (end_index & 0x1) == 1; - } + private static boolean isRight_(int end_index) { + return (end_index & 0x1) == 1; + } } diff --git a/src/main/java/com/esri/core/geometry/InverseResult.java b/src/main/java/com/esri/core/geometry/InverseResult.java index 760d3b52..4102d9b3 100644 --- a/src/main/java/com/esri/core/geometry/InverseResult.java +++ b/src/main/java/com/esri/core/geometry/InverseResult.java @@ -1,39 +1,39 @@ package com.esri.core.geometry; public class InverseResult { - double az12_rad = 0; - double az21_rad = 0; - double distance_m = 0; + double az12_rad = 0; + double az21_rad = 0; + double distance_m = 0; - public InverseResult(double az12_rad, double az21_rad, double distance_m) { - this.az12_rad = az12_rad; - this.az21_rad = az21_rad; - this.distance_m = distance_m; - } + public InverseResult(double az12_rad, double az21_rad, double distance_m) { + this.az12_rad = az12_rad; + this.az21_rad = az21_rad; + this.distance_m = distance_m; + } - public double getAz12_rad() { - return az12_rad; - } + public double getAz12_rad() { + return az12_rad; + } - public void setAz12_rad(double az12_rad) { - this.az12_rad = az12_rad; - } + public void setAz12_rad(double az12_rad) { + this.az12_rad = az12_rad; + } - public double getAz21_rad() { - return az21_rad; - } + public double getAz21_rad() { + return az21_rad; + } - public void setAz21_rad(double az21_rad) { - this.az21_rad = az21_rad; - } + public void setAz21_rad(double az21_rad) { + this.az21_rad = az21_rad; + } - public double getDistance_m() { - return distance_m; - } + public double getDistance_m() { + return distance_m; + } - public void setDistance_m(double distance_m) { - this.distance_m = distance_m; - } + public void setDistance_m(double distance_m) { + this.distance_m = distance_m; + } } diff --git a/src/main/java/com/esri/core/geometry/JSONUtils.java b/src/main/java/com/esri/core/geometry/JSONUtils.java index 7bbc51fd..14bcf142 100644 --- a/src/main/java/com/esri/core/geometry/JSONUtils.java +++ b/src/main/java/com/esri/core/geometry/JSONUtils.java @@ -25,23 +25,23 @@ final class JSONUtils { - static boolean isObjectStart(JsonReader parser) throws Exception { - return parser.currentToken() == null ? parser.nextToken() == JsonReader.Token.START_OBJECT - : parser.currentToken() == JsonReader.Token.START_OBJECT; - } - - static double readDouble(JsonReader parser) { - if (parser.currentToken() == JsonReader.Token.VALUE_NUMBER_FLOAT) - return parser.currentDoubleValue(); - else if (parser.currentToken() == JsonReader.Token.VALUE_NUMBER_INT) - return parser.currentIntValue(); - else if (parser.currentToken() == JsonReader.Token.VALUE_NULL) - return NumberUtils.NaN(); - else if (parser.currentToken() == JsonReader.Token.VALUE_STRING) - if (parser.currentString().equals("NaN")) - return NumberUtils.NaN(); - - throw new GeometryException("invalid parameter"); - } + static boolean isObjectStart(JsonReader parser) throws Exception { + return parser.currentToken() == null ? parser.nextToken() == JsonReader.Token.START_OBJECT + : parser.currentToken() == JsonReader.Token.START_OBJECT; + } + + static double readDouble(JsonReader parser) { + if (parser.currentToken() == JsonReader.Token.VALUE_NUMBER_FLOAT) + return parser.currentDoubleValue(); + else if (parser.currentToken() == JsonReader.Token.VALUE_NUMBER_INT) + return parser.currentIntValue(); + else if (parser.currentToken() == JsonReader.Token.VALUE_NULL) + return NumberUtils.NaN(); + else if (parser.currentToken() == JsonReader.Token.VALUE_STRING) + if (parser.currentString().equals("NaN")) + return NumberUtils.NaN(); + + throw new GeometryException("invalid parameter"); + } } diff --git a/src/main/java/com/esri/core/geometry/JsonCursor.java b/src/main/java/com/esri/core/geometry/JsonCursor.java index 9d0f7e8e..f4eced5c 100644 --- a/src/main/java/com/esri/core/geometry/JsonCursor.java +++ b/src/main/java/com/esri/core/geometry/JsonCursor.java @@ -28,19 +28,19 @@ */ public abstract class JsonCursor { - /** - * Moves the cursor to the next string. Returns null when reached the end. - */ - public abstract String next(); - - /** - * Returns the ID of the current geometry. The ID is propagated across the - * operations (when possible). - *

- * Returns an ID associated with the current Geometry. The ID is passed - * along and is returned by some operators to preserve relationship between - * the input and output geometry classes. It is not always possible to - * preserve an ID during an operation. - */ - public abstract int getID(); + /** + * Moves the cursor to the next string. Returns null when reached the end. + */ + public abstract String next(); + + /** + * Returns the ID of the current geometry. The ID is propagated across the + * operations (when possible). + *

+ * Returns an ID associated with the current Geometry. The ID is passed + * along and is returned by some operators to preserve relationship between + * the input and output geometry classes. It is not always possible to + * preserve an ID during an operation. + */ + public abstract int getID(); } diff --git a/src/main/java/com/esri/core/geometry/JsonGeometryException.java b/src/main/java/com/esri/core/geometry/JsonGeometryException.java index 75f27a9b..7981a27a 100644 --- a/src/main/java/com/esri/core/geometry/JsonGeometryException.java +++ b/src/main/java/com/esri/core/geometry/JsonGeometryException.java @@ -28,25 +28,25 @@ * A runtime exception raised when a JSON related exception occurs. */ public class JsonGeometryException extends GeometryException { - private static final long serialVersionUID = 1L; - - /** - * Constructs a Json Geometry Exception with the given error string/message. - * - * @param str - The error string. - */ - public JsonGeometryException(String str) { - super(str); - } - - /** - * Constructs a Json Geometry Exception with the given another exception. - * - * @param ex - The exception to copy the message from. - */ - public JsonGeometryException(Exception ex) { - super(ex.getMessage()); - } + private static final long serialVersionUID = 1L; + + /** + * Constructs a Json Geometry Exception with the given error string/message. + * + * @param str - The error string. + */ + public JsonGeometryException(String str) { + super(str); + } + + /** + * Constructs a Json Geometry Exception with the given another exception. + * + * @param ex - The exception to copy the message from. + */ + public JsonGeometryException(Exception ex) { + super(ex.getMessage()); + } } diff --git a/src/main/java/com/esri/core/geometry/JsonParserReader.java b/src/main/java/com/esri/core/geometry/JsonParserReader.java index ce8db6b6..cccef914 100644 --- a/src/main/java/com/esri/core/geometry/JsonParserReader.java +++ b/src/main/java/com/esri/core/geometry/JsonParserReader.java @@ -31,138 +31,138 @@ */ public class JsonParserReader implements JsonReader { - private JsonParser m_jsonParser; - - public JsonParserReader(JsonParser jsonParser) { - m_jsonParser = jsonParser; - } - - /** - * Creates a JsonReader for the string. - * The nextToken is called by this method. - */ - public static JsonReader createFromString(String str) { - try { - JsonFactory factory = new JsonFactory(); - JsonParser jsonParser = factory.createParser(str); - - jsonParser.nextToken(); - return new JsonParserReader(jsonParser); - } catch (Exception ex) { - throw new JsonGeometryException(ex.getMessage()); - } - } - - /** - * Creates a JsonReader for the string. - * The nextToken is not called by this method. - */ - public static JsonReader createFromStringNNT(String str) { - try { - JsonFactory factory = new JsonFactory(); - JsonParser jsonParser = factory.createParser(str); - - return new JsonParserReader(jsonParser); - } catch (Exception ex) { - throw new JsonGeometryException(ex.getMessage()); - } - } - - private static Token mapToken(JsonToken token) { - if (token == JsonToken.END_ARRAY) - return Token.END_ARRAY; - if (token == JsonToken.END_OBJECT) - return Token.END_OBJECT; - if (token == JsonToken.FIELD_NAME) - return Token.FIELD_NAME; - if (token == JsonToken.START_ARRAY) - return Token.START_ARRAY; - if (token == JsonToken.START_OBJECT) - return Token.START_OBJECT; - if (token == JsonToken.VALUE_FALSE) - return Token.VALUE_FALSE; - if (token == JsonToken.VALUE_NULL) - return Token.VALUE_NULL; - if (token == JsonToken.VALUE_NUMBER_FLOAT) - return Token.VALUE_NUMBER_FLOAT; - if (token == JsonToken.VALUE_NUMBER_INT) - return Token.VALUE_NUMBER_INT; - if (token == JsonToken.VALUE_STRING) - return Token.VALUE_STRING; - if (token == JsonToken.VALUE_TRUE) - return Token.VALUE_TRUE; - if (token == null) - return null; - - throw new JsonGeometryException("unexpected token"); - } - - @Override - public Token nextToken() throws JsonGeometryException { - try { - JsonToken token = m_jsonParser.nextToken(); - return mapToken(token); - } catch (Exception ex) { - throw new JsonGeometryException(ex); - } - } - - @Override - public Token currentToken() throws JsonGeometryException { - try { - return mapToken(m_jsonParser.getCurrentToken()); - } catch (Exception ex) { - throw new JsonGeometryException(ex); - } - } - - @Override - public void skipChildren() throws JsonGeometryException { - try { - m_jsonParser.skipChildren(); - } catch (Exception ex) { - throw new JsonGeometryException(ex); - } - - } - - @Override - public String currentString() throws JsonGeometryException { - try { - return m_jsonParser.getText(); - } catch (Exception ex) { - throw new JsonGeometryException(ex); - } - - } - - @Override - public double currentDoubleValue() throws JsonGeometryException { - try { - return m_jsonParser.getValueAsDouble(); - } catch (Exception ex) { - throw new JsonGeometryException(ex); - } - - } - - @Override - public int currentIntValue() throws JsonGeometryException { - try { - return m_jsonParser.getValueAsInt(); - } catch (Exception ex) { - throw new JsonGeometryException(ex); - } - } - - @Override - public boolean currentBooleanValue() { - Token t = currentToken(); - if (t == Token.VALUE_TRUE) - return true; - else if (t == Token.VALUE_FALSE) - return false; - throw new JsonGeometryException("Not a boolean"); - } + private JsonParser m_jsonParser; + + public JsonParserReader(JsonParser jsonParser) { + m_jsonParser = jsonParser; + } + + /** + * Creates a JsonReader for the string. + * The nextToken is called by this method. + */ + public static JsonReader createFromString(String str) { + try { + JsonFactory factory = new JsonFactory(); + JsonParser jsonParser = factory.createParser(str); + + jsonParser.nextToken(); + return new JsonParserReader(jsonParser); + } catch (Exception ex) { + throw new JsonGeometryException(ex.getMessage()); + } + } + + /** + * Creates a JsonReader for the string. + * The nextToken is not called by this method. + */ + public static JsonReader createFromStringNNT(String str) { + try { + JsonFactory factory = new JsonFactory(); + JsonParser jsonParser = factory.createParser(str); + + return new JsonParserReader(jsonParser); + } catch (Exception ex) { + throw new JsonGeometryException(ex.getMessage()); + } + } + + private static Token mapToken(JsonToken token) { + if (token == JsonToken.END_ARRAY) + return Token.END_ARRAY; + if (token == JsonToken.END_OBJECT) + return Token.END_OBJECT; + if (token == JsonToken.FIELD_NAME) + return Token.FIELD_NAME; + if (token == JsonToken.START_ARRAY) + return Token.START_ARRAY; + if (token == JsonToken.START_OBJECT) + return Token.START_OBJECT; + if (token == JsonToken.VALUE_FALSE) + return Token.VALUE_FALSE; + if (token == JsonToken.VALUE_NULL) + return Token.VALUE_NULL; + if (token == JsonToken.VALUE_NUMBER_FLOAT) + return Token.VALUE_NUMBER_FLOAT; + if (token == JsonToken.VALUE_NUMBER_INT) + return Token.VALUE_NUMBER_INT; + if (token == JsonToken.VALUE_STRING) + return Token.VALUE_STRING; + if (token == JsonToken.VALUE_TRUE) + return Token.VALUE_TRUE; + if (token == null) + return null; + + throw new JsonGeometryException("unexpected token"); + } + + @Override + public Token nextToken() throws JsonGeometryException { + try { + JsonToken token = m_jsonParser.nextToken(); + return mapToken(token); + } catch (Exception ex) { + throw new JsonGeometryException(ex); + } + } + + @Override + public Token currentToken() throws JsonGeometryException { + try { + return mapToken(m_jsonParser.getCurrentToken()); + } catch (Exception ex) { + throw new JsonGeometryException(ex); + } + } + + @Override + public void skipChildren() throws JsonGeometryException { + try { + m_jsonParser.skipChildren(); + } catch (Exception ex) { + throw new JsonGeometryException(ex); + } + + } + + @Override + public String currentString() throws JsonGeometryException { + try { + return m_jsonParser.getText(); + } catch (Exception ex) { + throw new JsonGeometryException(ex); + } + + } + + @Override + public double currentDoubleValue() throws JsonGeometryException { + try { + return m_jsonParser.getValueAsDouble(); + } catch (Exception ex) { + throw new JsonGeometryException(ex); + } + + } + + @Override + public int currentIntValue() throws JsonGeometryException { + try { + return m_jsonParser.getValueAsInt(); + } catch (Exception ex) { + throw new JsonGeometryException(ex); + } + } + + @Override + public boolean currentBooleanValue() { + Token t = currentToken(); + if (t == Token.VALUE_TRUE) + return true; + else if (t == Token.VALUE_FALSE) + return false; + throw new JsonGeometryException("Not a boolean"); + } } diff --git a/src/main/java/com/esri/core/geometry/JsonReader.java b/src/main/java/com/esri/core/geometry/JsonReader.java index 00dd3f12..8c1126e9 100644 --- a/src/main/java/com/esri/core/geometry/JsonReader.java +++ b/src/main/java/com/esri/core/geometry/JsonReader.java @@ -29,32 +29,32 @@ * See JsonParserReader for a concrete implementation around JsonParser. */ abstract public interface JsonReader { - public static enum Token { - END_ARRAY, - END_OBJECT, - FIELD_NAME, - START_ARRAY, - START_OBJECT, - VALUE_FALSE, - VALUE_NULL, - VALUE_NUMBER_FLOAT, - VALUE_NUMBER_INT, - VALUE_STRING, - VALUE_TRUE - } + public static enum Token { + END_ARRAY, + END_OBJECT, + FIELD_NAME, + START_ARRAY, + START_OBJECT, + VALUE_FALSE, + VALUE_NULL, + VALUE_NUMBER_FLOAT, + VALUE_NUMBER_INT, + VALUE_STRING, + VALUE_TRUE + } - abstract public Token nextToken() throws JsonGeometryException; + abstract public Token nextToken() throws JsonGeometryException; - abstract public Token currentToken() throws JsonGeometryException; + abstract public Token currentToken() throws JsonGeometryException; - abstract public void skipChildren() throws JsonGeometryException; + abstract public void skipChildren() throws JsonGeometryException; - abstract public String currentString() throws JsonGeometryException; + abstract public String currentString() throws JsonGeometryException; - abstract public double currentDoubleValue() throws JsonGeometryException; + abstract public double currentDoubleValue() throws JsonGeometryException; - abstract public int currentIntValue() throws JsonGeometryException; + abstract public int currentIntValue() throws JsonGeometryException; - abstract public boolean currentBooleanValue() throws JsonGeometryException; + abstract public boolean currentBooleanValue() throws JsonGeometryException; } diff --git a/src/main/java/com/esri/core/geometry/JsonReaderCursor.java b/src/main/java/com/esri/core/geometry/JsonReaderCursor.java index 240e0ce1..c36ebe11 100644 --- a/src/main/java/com/esri/core/geometry/JsonReaderCursor.java +++ b/src/main/java/com/esri/core/geometry/JsonReaderCursor.java @@ -46,26 +46,26 @@ */ abstract class JsonReaderCursor implements Iterator { - /** - * Moves the cursor to the next JsonReader. Returns null when reached the - * end. - */ - public abstract JsonReader next(); - - /** - * Returns the ID of the current geometry. The ID is propagated across the - * operations (when possible). - *

- * Returns an ID associated with the current Geometry. The ID is passed - * along and is returned by some operators to preserve relationship between - * the input and output geometry classes. It is not always possible to - * preserve an ID during an operation. - */ - public abstract int getID(); - - public abstract SimpleStateEnum getSimpleState(); - - public abstract String getFeatureID(); - - public abstract boolean hasNext(); + /** + * Moves the cursor to the next JsonReader. Returns null when reached the + * end. + */ + public abstract JsonReader next(); + + /** + * Returns the ID of the current geometry. The ID is propagated across the + * operations (when possible). + *

+ * Returns an ID associated with the current Geometry. The ID is passed + * along and is returned by some operators to preserve relationship between + * the input and output geometry classes. It is not always possible to + * preserve an ID during an operation. + */ + public abstract int getID(); + + public abstract SimpleStateEnum getSimpleState(); + + public abstract String getFeatureID(); + + public abstract boolean hasNext(); } diff --git a/src/main/java/com/esri/core/geometry/JsonStringWriter.java b/src/main/java/com/esri/core/geometry/JsonStringWriter.java index 4c551d25..36be1445 100644 --- a/src/main/java/com/esri/core/geometry/JsonStringWriter.java +++ b/src/main/java/com/esri/core/geometry/JsonStringWriter.java @@ -25,402 +25,402 @@ final class JsonStringWriter extends JsonWriter { - @Override - Object getJson() { - next_(Action.accept); - return m_jsonString.toString(); - } - - @Override - void startObject() { - next_(Action.addObject); - m_jsonString.append('{'); - m_functionStack.add(State.objectStart); - } - - @Override - void startArray() { - next_(Action.addArray); - m_jsonString.append('['); - m_functionStack.add(State.arrayStart); - } - - @Override - void endObject() { - next_(Action.popObject); - m_jsonString.append('}'); - } - - @Override - void endArray() { - next_(Action.popArray); - m_jsonString.append(']'); - } - - @Override - void addFieldName(String fieldName) { - next_(Action.addKey); - appendQuote_(fieldName); - } - - @Override - void addPairObject(String fieldName) { - next_(Action.addPair); - appendQuote_(fieldName); - m_jsonString.append(":"); - addValueObject_(); - } - - @Override - void addPairArray(String fieldName) { - next_(Action.addPair); - appendQuote_(fieldName); - m_jsonString.append(":"); - addValueArray_(); - } - - @Override - void addPairString(String fieldName, String v) { - next_(Action.addPair); - appendQuote_(fieldName); - m_jsonString.append(":"); - addValueString_(v); - } - - @Override - void addPairDouble(String fieldName, double v) { - next_(Action.addPair); - appendQuote_(fieldName); - m_jsonString.append(":"); - addValueDouble_(v); - } - - @Override - void addPairDouble(String fieldName, double v, int precision, boolean bFixedPoint) { - next_(Action.addPair); - appendQuote_(fieldName); - m_jsonString.append(":"); - addValueDouble_(v, precision, bFixedPoint); - } - - @Override - void addPairInt(String fieldName, int v) { - next_(Action.addPair); - appendQuote_(fieldName); - m_jsonString.append(":"); - addValueInt_(v); - } - - @Override - void addPairBoolean(String fieldName, boolean v) { - next_(Action.addPair); - appendQuote_(fieldName); - m_jsonString.append(":"); - addValueBoolean_(v); - } - - @Override - void addPairNull(String fieldName) { - next_(Action.addPair); - appendQuote_(fieldName); - m_jsonString.append(":"); - addValueNull_(); - } - - @Override - void addValueObject() { - next_(Action.addObject); - addValueObject_(); - } - - @Override - void addValueArray() { - next_(Action.addArray); - addValueArray_(); - } - - @Override - void addValueString(String v) { - next_(Action.addTerminal); - addValueString_(v); - } - - @Override - void addValueDouble(double v) { - next_(Action.addTerminal); - addValueDouble_(v); - } - - @Override - void addValueDouble(double v, int precision, boolean bFixedPoint) { - next_(Action.addTerminal); - addValueDouble_(v, precision, bFixedPoint); - } - - @Override - void addValueInt(int v) { - next_(Action.addTerminal); - addValueInt_(v); - } - - @Override - void addValueBoolean(boolean v) { - next_(Action.addTerminal); - addValueBoolean_(v); - } - - @Override - void addValueNull() { - next_(Action.addTerminal); - addValueNull_(); - } - - JsonStringWriter() { - m_jsonString = new StringBuilder(); - m_functionStack = new AttributeStreamOfInt32(0); - m_functionStack.add(State.accept); - m_functionStack.add(State.start); - } - - private StringBuilder m_jsonString; - private AttributeStreamOfInt32 m_functionStack; - - private void addValueObject_() { - m_jsonString.append('{'); - m_functionStack.add(State.objectStart); - } - - private void addValueArray_() { - m_jsonString.append('['); - m_functionStack.add(State.arrayStart); - } - - private void addValueString_(String v) { - appendQuote_(v); - } - - private void addValueDouble_(double v) { - if (NumberUtils.isNaN(v)) { - addValueNull_(); - return; - } - - StringUtils.appendDouble(v, 17, m_jsonString); - } - - private void addValueDouble_(double v, int precision, boolean bFixedPoint) { - if (NumberUtils.isNaN(v)) { - addValueNull_(); - return; - } - - if (bFixedPoint) - StringUtils.appendDoubleF(v, precision, m_jsonString); - else - StringUtils.appendDouble(v, precision, m_jsonString); - } - - private void addValueInt_(int v) { - m_jsonString.append(v); - } - - private void addValueBoolean_(boolean v) { - if (v) { - m_jsonString.append("true"); - } else { - m_jsonString.append("false"); - } - } - - private void addValueNull_() { - m_jsonString.append("null"); - } - - private void next_(int action) { - switch (m_functionStack.getLast()) { - case State.accept: - accept_(action); - break; - case State.start: - start_(action); - break; - case State.objectStart: - objectStart_(action); - break; - case State.arrayStart: - arrayStart_(action); - break; - case State.pairEnd: - pairEnd_(action); - break; - case State.elementEnd: - elementEnd_(action); - break; - case State.fieldNameEnd: - fieldNameEnd_(action); - break; - default: - throw new GeometryException("internal error"); - } - } - - private void accept_(int action) { - if (action != Action.accept) { - throw new GeometryException("invalid call"); - } - } - - private void start_(int action) { - if ((action & Action.addContainer) != 0) { - m_functionStack.removeLast(); - } else { - throw new GeometryException("invalid call"); - } - } - - private void objectStart_(int action) { - if (action != Action.popObject && action != Action.addPair && action != Action.addKey) - throw new GeometryException("invalid call"); - - m_functionStack.removeLast(); - - if (action == Action.addPair) { - m_functionStack.add(State.pairEnd); - } else if (action == Action.addKey) { - m_functionStack.add(State.pairEnd); - m_functionStack.add(State.fieldNameEnd); - } - } - - private void pairEnd_(int action) { - if (action == Action.addPair) { - m_jsonString.append(','); - } else if (action == Action.addKey) { - m_jsonString.append(','); - m_functionStack.add(State.fieldNameEnd); - } else if (action == Action.popObject) { - m_functionStack.removeLast(); - } else { - throw new GeometryException("invalid call"); - } - } - - private void arrayStart_(int action) { - if ((action & Action.addValue) == 0 && action != Action.popArray) - throw new GeometryException("invalid call"); - - m_functionStack.removeLast(); - - if ((action & Action.addValue) != 0) { - m_functionStack.add(State.elementEnd); - } - } - - private void elementEnd_(int action) { - if ((action & Action.addValue) != 0) { - m_jsonString.append(','); - } else if (action == Action.popArray) { - m_functionStack.removeLast(); - } else { - throw new GeometryException("invalid call"); - } - } - - private void fieldNameEnd_(int action) { - if ((action & Action.addValue) == 0) - throw new GeometryException("invalid call"); - - m_functionStack.removeLast(); - m_jsonString.append(':'); - } - - private void appendQuote_(String string) { - int count = 0; - int start = 0; - int end = string.length(); - - m_jsonString.append('"'); - - for (int i = 0; i < end; i++) { - switch (string.charAt(i)) { - case '"': - if (count > 0) { - m_jsonString.append(string, start, start + count); - count = 0; - } - m_jsonString.append("\\\""); - start = i + 1; - break; - case '\\': - if (count > 0) { - m_jsonString.append(string, start, start + count); - count = 0; - } - m_jsonString.append("\\\\"); - start = i + 1; - break; - case '/': - if (i > 0 && string.charAt(i - 1) == '<') { - if (count > 0) { - m_jsonString.append(string, start, start + count); - count = 0; - } - m_jsonString.append("\\/"); - start = i + 1; - } else { - count++; - } - break; - case '\b': - if (count > 0) { - m_jsonString.append(string, start, start + count); - count = 0; - } - m_jsonString.append("\\b"); - start = i + 1; - break; - case '\f': - if (count > 0) { - m_jsonString.append(string, start, start + count); - count = 0; - } - m_jsonString.append("\\f"); - start = i + 1; - break; - case '\n': - if (count > 0) { - m_jsonString.append(string, start, start + count); - count = 0; - } - m_jsonString.append("\\n"); - start = i + 1; - break; - case '\r': - if (count > 0) { - m_jsonString.append(string, start, start + count); - count = 0; - } - m_jsonString.append("\\r"); - start = i + 1; - break; - case '\t': - if (count > 0) { - m_jsonString.append(string, start, start + count); - count = 0; - } - m_jsonString.append("\\t"); - start = i + 1; - break; - default: - count++; - break; - } - } - - if (count > 0) { - m_jsonString.append(string, start, start + count); - } - - m_jsonString.append('"'); - } + @Override + Object getJson() { + next_(Action.accept); + return m_jsonString.toString(); + } + + @Override + void startObject() { + next_(Action.addObject); + m_jsonString.append('{'); + m_functionStack.add(State.objectStart); + } + + @Override + void startArray() { + next_(Action.addArray); + m_jsonString.append('['); + m_functionStack.add(State.arrayStart); + } + + @Override + void endObject() { + next_(Action.popObject); + m_jsonString.append('}'); + } + + @Override + void endArray() { + next_(Action.popArray); + m_jsonString.append(']'); + } + + @Override + void addFieldName(String fieldName) { + next_(Action.addKey); + appendQuote_(fieldName); + } + + @Override + void addPairObject(String fieldName) { + next_(Action.addPair); + appendQuote_(fieldName); + m_jsonString.append(":"); + addValueObject_(); + } + + @Override + void addPairArray(String fieldName) { + next_(Action.addPair); + appendQuote_(fieldName); + m_jsonString.append(":"); + addValueArray_(); + } + + @Override + void addPairString(String fieldName, String v) { + next_(Action.addPair); + appendQuote_(fieldName); + m_jsonString.append(":"); + addValueString_(v); + } + + @Override + void addPairDouble(String fieldName, double v) { + next_(Action.addPair); + appendQuote_(fieldName); + m_jsonString.append(":"); + addValueDouble_(v); + } + + @Override + void addPairDouble(String fieldName, double v, int precision, boolean bFixedPoint) { + next_(Action.addPair); + appendQuote_(fieldName); + m_jsonString.append(":"); + addValueDouble_(v, precision, bFixedPoint); + } + + @Override + void addPairInt(String fieldName, int v) { + next_(Action.addPair); + appendQuote_(fieldName); + m_jsonString.append(":"); + addValueInt_(v); + } + + @Override + void addPairBoolean(String fieldName, boolean v) { + next_(Action.addPair); + appendQuote_(fieldName); + m_jsonString.append(":"); + addValueBoolean_(v); + } + + @Override + void addPairNull(String fieldName) { + next_(Action.addPair); + appendQuote_(fieldName); + m_jsonString.append(":"); + addValueNull_(); + } + + @Override + void addValueObject() { + next_(Action.addObject); + addValueObject_(); + } + + @Override + void addValueArray() { + next_(Action.addArray); + addValueArray_(); + } + + @Override + void addValueString(String v) { + next_(Action.addTerminal); + addValueString_(v); + } + + @Override + void addValueDouble(double v) { + next_(Action.addTerminal); + addValueDouble_(v); + } + + @Override + void addValueDouble(double v, int precision, boolean bFixedPoint) { + next_(Action.addTerminal); + addValueDouble_(v, precision, bFixedPoint); + } + + @Override + void addValueInt(int v) { + next_(Action.addTerminal); + addValueInt_(v); + } + + @Override + void addValueBoolean(boolean v) { + next_(Action.addTerminal); + addValueBoolean_(v); + } + + @Override + void addValueNull() { + next_(Action.addTerminal); + addValueNull_(); + } + + JsonStringWriter() { + m_jsonString = new StringBuilder(); + m_functionStack = new AttributeStreamOfInt32(0); + m_functionStack.add(State.accept); + m_functionStack.add(State.start); + } + + private StringBuilder m_jsonString; + private AttributeStreamOfInt32 m_functionStack; + + private void addValueObject_() { + m_jsonString.append('{'); + m_functionStack.add(State.objectStart); + } + + private void addValueArray_() { + m_jsonString.append('['); + m_functionStack.add(State.arrayStart); + } + + private void addValueString_(String v) { + appendQuote_(v); + } + + private void addValueDouble_(double v) { + if (NumberUtils.isNaN(v)) { + addValueNull_(); + return; + } + + StringUtils.appendDouble(v, 17, m_jsonString); + } + + private void addValueDouble_(double v, int precision, boolean bFixedPoint) { + if (NumberUtils.isNaN(v)) { + addValueNull_(); + return; + } + + if (bFixedPoint) + StringUtils.appendDoubleF(v, precision, m_jsonString); + else + StringUtils.appendDouble(v, precision, m_jsonString); + } + + private void addValueInt_(int v) { + m_jsonString.append(v); + } + + private void addValueBoolean_(boolean v) { + if (v) { + m_jsonString.append("true"); + } else { + m_jsonString.append("false"); + } + } + + private void addValueNull_() { + m_jsonString.append("null"); + } + + private void next_(int action) { + switch (m_functionStack.getLast()) { + case State.accept: + accept_(action); + break; + case State.start: + start_(action); + break; + case State.objectStart: + objectStart_(action); + break; + case State.arrayStart: + arrayStart_(action); + break; + case State.pairEnd: + pairEnd_(action); + break; + case State.elementEnd: + elementEnd_(action); + break; + case State.fieldNameEnd: + fieldNameEnd_(action); + break; + default: + throw new GeometryException("internal error"); + } + } + + private void accept_(int action) { + if (action != Action.accept) { + throw new GeometryException("invalid call"); + } + } + + private void start_(int action) { + if ((action & Action.addContainer) != 0) { + m_functionStack.removeLast(); + } else { + throw new GeometryException("invalid call"); + } + } + + private void objectStart_(int action) { + if (action != Action.popObject && action != Action.addPair && action != Action.addKey) + throw new GeometryException("invalid call"); + + m_functionStack.removeLast(); + + if (action == Action.addPair) { + m_functionStack.add(State.pairEnd); + } else if (action == Action.addKey) { + m_functionStack.add(State.pairEnd); + m_functionStack.add(State.fieldNameEnd); + } + } + + private void pairEnd_(int action) { + if (action == Action.addPair) { + m_jsonString.append(','); + } else if (action == Action.addKey) { + m_jsonString.append(','); + m_functionStack.add(State.fieldNameEnd); + } else if (action == Action.popObject) { + m_functionStack.removeLast(); + } else { + throw new GeometryException("invalid call"); + } + } + + private void arrayStart_(int action) { + if ((action & Action.addValue) == 0 && action != Action.popArray) + throw new GeometryException("invalid call"); + + m_functionStack.removeLast(); + + if ((action & Action.addValue) != 0) { + m_functionStack.add(State.elementEnd); + } + } + + private void elementEnd_(int action) { + if ((action & Action.addValue) != 0) { + m_jsonString.append(','); + } else if (action == Action.popArray) { + m_functionStack.removeLast(); + } else { + throw new GeometryException("invalid call"); + } + } + + private void fieldNameEnd_(int action) { + if ((action & Action.addValue) == 0) + throw new GeometryException("invalid call"); + + m_functionStack.removeLast(); + m_jsonString.append(':'); + } + + private void appendQuote_(String string) { + int count = 0; + int start = 0; + int end = string.length(); + + m_jsonString.append('"'); + + for (int i = 0; i < end; i++) { + switch (string.charAt(i)) { + case '"': + if (count > 0) { + m_jsonString.append(string, start, start + count); + count = 0; + } + m_jsonString.append("\\\""); + start = i + 1; + break; + case '\\': + if (count > 0) { + m_jsonString.append(string, start, start + count); + count = 0; + } + m_jsonString.append("\\\\"); + start = i + 1; + break; + case '/': + if (i > 0 && string.charAt(i - 1) == '<') { + if (count > 0) { + m_jsonString.append(string, start, start + count); + count = 0; + } + m_jsonString.append("\\/"); + start = i + 1; + } else { + count++; + } + break; + case '\b': + if (count > 0) { + m_jsonString.append(string, start, start + count); + count = 0; + } + m_jsonString.append("\\b"); + start = i + 1; + break; + case '\f': + if (count > 0) { + m_jsonString.append(string, start, start + count); + count = 0; + } + m_jsonString.append("\\f"); + start = i + 1; + break; + case '\n': + if (count > 0) { + m_jsonString.append(string, start, start + count); + count = 0; + } + m_jsonString.append("\\n"); + start = i + 1; + break; + case '\r': + if (count > 0) { + m_jsonString.append(string, start, start + count); + count = 0; + } + m_jsonString.append("\\r"); + start = i + 1; + break; + case '\t': + if (count > 0) { + m_jsonString.append(string, start, start + count); + count = 0; + } + m_jsonString.append("\\t"); + start = i + 1; + break; + default: + count++; + break; + } + } + + if (count > 0) { + m_jsonString.append(string, start, start + count); + } + + m_jsonString.append('"'); + } } diff --git a/src/main/java/com/esri/core/geometry/JsonWriter.java b/src/main/java/com/esri/core/geometry/JsonWriter.java index 0013cfcc..095f50e1 100644 --- a/src/main/java/com/esri/core/geometry/JsonWriter.java +++ b/src/main/java/com/esri/core/geometry/JsonWriter.java @@ -25,72 +25,72 @@ abstract class JsonWriter { - abstract Object getJson(); + abstract Object getJson(); - abstract void startObject(); + abstract void startObject(); - abstract void startArray(); + abstract void startArray(); - abstract void endObject(); + abstract void endObject(); - abstract void endArray(); + abstract void endArray(); - abstract void addFieldName(String fieldName); + abstract void addFieldName(String fieldName); - abstract void addPairObject(String fieldName); + abstract void addPairObject(String fieldName); - abstract void addPairArray(String fieldName); + abstract void addPairArray(String fieldName); - abstract void addPairString(String fieldName, String v); + abstract void addPairString(String fieldName, String v); - abstract void addPairDouble(String fieldName, double v); + abstract void addPairDouble(String fieldName, double v); - abstract void addPairDouble(String fieldName, double v, int precision, boolean bFixedPoint); + abstract void addPairDouble(String fieldName, double v, int precision, boolean bFixedPoint); - abstract void addPairInt(String fieldName, int v); + abstract void addPairInt(String fieldName, int v); - abstract void addPairBoolean(String fieldName, boolean v); + abstract void addPairBoolean(String fieldName, boolean v); - abstract void addPairNull(String fieldName); + abstract void addPairNull(String fieldName); - abstract void addValueObject(); + abstract void addValueObject(); - abstract void addValueArray(); + abstract void addValueArray(); - abstract void addValueString(String v); + abstract void addValueString(String v); - abstract void addValueDouble(double v); + abstract void addValueDouble(double v); - abstract void addValueDouble(double v, int precision, boolean bFixedPoint); + abstract void addValueDouble(double v, int precision, boolean bFixedPoint); - abstract void addValueInt(int v); + abstract void addValueInt(int v); - abstract void addValueBoolean(boolean v); + abstract void addValueBoolean(boolean v); - abstract void addValueNull(); + abstract void addValueNull(); - protected interface Action { + protected interface Action { - static final int accept = 0; - static final int addObject = 1; - static final int addArray = 2; - static final int popObject = 4; - static final int popArray = 8; - static final int addKey = 16; - static final int addTerminal = 32; - static final int addPair = 64; - static final int addContainer = addObject | addArray; - static final int addValue = addContainer | addTerminal; - } + static final int accept = 0; + static final int addObject = 1; + static final int addArray = 2; + static final int popObject = 4; + static final int popArray = 8; + static final int addKey = 16; + static final int addTerminal = 32; + static final int addPair = 64; + static final int addContainer = addObject | addArray; + static final int addValue = addContainer | addTerminal; + } - protected interface State { + protected interface State { - static final int accept = 0; - static final int start = 1; - static final int objectStart = 2; - static final int arrayStart = 3; - static final int pairEnd = 4; - static final int elementEnd = 5; - static final int fieldNameEnd = 6; - } + static final int accept = 0; + static final int start = 1; + static final int objectStart = 2; + static final int arrayStart = 3; + static final int pairEnd = 4; + static final int elementEnd = 5; + static final int fieldNameEnd = 6; + } } diff --git a/src/main/java/com/esri/core/geometry/Line.java b/src/main/java/com/esri/core/geometry/Line.java index 4c717691..c84e03b7 100644 --- a/src/main/java/com/esri/core/geometry/Line.java +++ b/src/main/java/com/esri/core/geometry/Line.java @@ -36,14 +36,13 @@ */ public final class Line extends Segment implements Serializable { - @Override - public Geometry.Type getType() { - return Type.Line; - } + @Override + public Geometry.Type getType() { + return Type.Line; + } @Override - public long estimateMemorySize() - { + public long estimateMemorySize() { return SIZE_OF_LINE + estimateMemorySize(m_attributes); } @@ -54,966 +53,966 @@ public double calculateLength2D() { return Math.sqrt(dx * dx + dy * dy); } - @Override - boolean isDegenerate(double tolerance) { - double dx = m_xStart - m_xEnd; - double dy = m_yStart - m_yEnd; - return Math.sqrt(dx * dx + dy * dy) <= tolerance; - } - - /** - * Indicates if the line segment is a curve. - */ - @Override - public boolean isCurve() { - return false; - } - - @Override - Point2D _getTangent(double t) { - Point2D pt = new Point2D(); - pt.sub(getEndXY(), getStartXY()); - return pt; - } - - @Override - boolean _isDegenerate(double tolerance) { - return calculateLength2D() <= tolerance; - } - - // HEADER DEF - - // Cpp - - /** - * Creates a line segment. - */ - public Line() { - m_description = VertexDescriptionDesignerImpl.getDefaultDescriptor2D(); - } - - Line(VertexDescription vd) { - m_description = vd; - } - - public Line(double x1, double y1, double x2, double y2) { - m_description = VertexDescriptionDesignerImpl.getDefaultDescriptor2D(); - setStartXY(x1, y1); - setEndXY(x2, y2); - } - - @Override - public void queryEnvelope(Envelope env) { - env.setEmpty(); - env.assignVertexDescription(m_description); - Envelope2D env2D = new Envelope2D(); - queryEnvelope2D(env2D); - env.setEnvelope2D(env2D); - - for (int i = 1, n = m_description.getAttributeCount(); i < n; i++) { - int semantics = m_description.getSemantics(i); - for (int iord = 0, nord = VertexDescription - .getComponentCount(semantics); i < nord; i++) { - Envelope1D interval = queryInterval(semantics, iord); - env.setInterval(semantics, iord, interval); - } - } - } - - @Override - public void queryEnvelope2D(Envelope2D env) { - env.setCoords(m_xStart, m_yStart, m_xEnd, m_yEnd); - env.normalize(); - } - - @Override - void queryEnvelope3D(Envelope3D env) { - env.setEmpty(); - env.merge(m_xStart, m_yStart, _getAttributeAsDbl(0, Semantics.Z, 0)); - env.merge(m_xEnd, m_yEnd, _getAttributeAsDbl(1, Semantics.Z, 0)); - } - - @Override - public void applyTransformation(Transformation2D transform) { - _touch(); - Point2D pt = new Point2D(); - pt.x = m_xStart; - pt.y = m_yStart; - transform.transform(pt, pt); - m_xStart = pt.x; - m_yStart = pt.y; - pt.x = m_xEnd; - pt.y = m_yEnd; - transform.transform(pt, pt); - m_xEnd = pt.x; - m_yEnd = pt.y; - } - - @Override - void applyTransformation(Transformation3D transform) { - _touch(); - Point3D pt = new Point3D(); - pt.x = m_xStart; - pt.y = m_yStart; - pt.z = _getAttributeAsDbl(0, Semantics.Z, 0); - pt = transform.transform(pt); - m_xStart = pt.x; - m_yStart = pt.y; - _setAttribute(0, Semantics.Z, 0, pt.z); - pt.x = m_xEnd; - pt.y = m_yEnd; - pt.z = _getAttributeAsDbl(1, Semantics.Z, 0); - pt = transform.transform(pt); - m_xEnd = pt.x; - m_yEnd = pt.y; - _setAttribute(1, Semantics.Z, 0, pt.z); - } - - @Override - public Geometry createInstance() { - return new Line(m_description); - } - - @Override - double _calculateArea2DHelper(double xorg, double yorg) { - return ((m_xEnd - xorg) - (m_xStart - xorg)) - * ((m_yEnd - yorg) + (m_yStart - yorg)) * 0.5; - } - - @Override - double tToLength(double t) { - return t * calculateLength2D(); - } - - @Override - double lengthToT(double len) { - return len / calculateLength2D(); - } - - double getCoordX_(double t) { - // Must match query_coord_2D and vice verse - // Also match get_attribute_as_dbl - return MathUtils.lerp(m_xStart, m_xEnd, t); - } - - double getCoordY_(double t) { - // Must match query_coord_2D and vice verse - // Also match get_attribute_as_dbl - return MathUtils.lerp(m_yStart, m_yEnd, t); - } - - @Override - public void getCoord2D(double t, Point2D pt) { - // We want: - // 1. When t == 0, get exactly Start - // 2. When t == 1, get exactly End - // 3. When m_x_end == m_x_start, we want m_x_start exactly - // 4. When m_y_end == m_y_start, we want m_y_start exactly - MathUtils.lerp(m_xStart, m_yStart, m_xEnd, m_yEnd, t, pt); - } - - @Override - public Segment cut(double t1, double t2) { - SegmentBuffer segmentBuffer = new SegmentBuffer(); - cut(t1, t2, segmentBuffer); - return segmentBuffer.get(); - } - - @Override - void cut(double t1, double t2, SegmentBuffer subSegmentBuffer) { - if (subSegmentBuffer == null) - throw new IllegalArgumentException(); - - subSegmentBuffer.createLine();// Make sure buffer contains Line class. - Segment subSegment = subSegmentBuffer.get(); - subSegment.assignVertexDescription(m_description); - - Point2D point = new Point2D(); - getCoord2D(t1, point); - subSegment.setStartXY(point.x, point.y); - getCoord2D(t2, point); - subSegment.setEndXY(point.x, point.y); - - for (int iattr = 1, nattr = m_description.getAttributeCount(); iattr < nattr; iattr++) { - int semantics = m_description._getSemanticsImpl(iattr); - int ncomps = VertexDescription.getComponentCount(semantics); - - for (int ordinate = 0; ordinate < ncomps; ordinate++) { - double value1 = getAttributeAsDbl(t1, semantics, ordinate); - subSegment.setStartAttribute(semantics, ordinate, value1); - - double value2 = getAttributeAsDbl(t2, semantics, ordinate); - subSegment.setEndAttribute(semantics, ordinate, value2); - } - } - } - - @Override - public double getAttributeAsDbl(double t, int semantics, int ordinate) { - if (semantics == VertexDescription.Semantics.POSITION) - return ordinate == 0 ? getCoord2D(t).x : getCoord2D(t).y; - - int interpolation = VertexDescription.getInterpolation(semantics); - switch (interpolation) { - case VertexDescription.Interpolation.NONE: - if (t < 0.5) - return getStartAttributeAsDbl(semantics, ordinate); - else - return getEndAttributeAsDbl(semantics, ordinate); - case VertexDescription.Interpolation.LINEAR: { - double s = getStartAttributeAsDbl(semantics, ordinate); - double e = getEndAttributeAsDbl(semantics, ordinate); - return MathUtils.lerp(s, e, t); - } - case VertexDescription.Interpolation.ANGULAR: { - throw new GeometryException("not implemented"); - } - } - - throw GeometryException.GeometryInternalError(); - } - - @Override - public double getClosestCoordinate(Point2D inputPt, boolean bExtrapolate) { - double vx = m_xEnd - m_xStart; - double vy = m_yEnd - m_yStart; - double v2 = vx * vx + vy * vy; - if (v2 == 0) - return 0.5; - double rx = inputPt.x - m_xStart; - double ry = inputPt.y - m_yStart; - double t = (rx * vx + ry * vy) / v2; - if (!bExtrapolate) { - if (t < 0.0) - t = 0.0; - else if (t > 1.0) - t = 1.0; - } - - return t; - } - - @Override - public int intersectionWithAxis2D(boolean b_axis_x, double ordinate, - double[] result_ordinates, double[] parameters) { - if (b_axis_x) { - double a = (m_yEnd - m_yStart); - - if (a == 0) - return (ordinate == m_yEnd) ? -1 : 0; - - double t = (ordinate - m_yStart) / a; - - if (t < 0.0 || t > 1.0) - return 0; - - if (result_ordinates != null) - (result_ordinates)[0] = getCoordX_(t); - - if (parameters != null) - (parameters)[0] = t; - - return 1; - } else { - double a = (m_xEnd - m_xStart); - - if (a == 0) - return (ordinate == m_xEnd) ? -1 : 0; - - double t = (ordinate - m_xStart) / a; - - if (t < 0.0 || t > 1.0) - return 0; - - if (result_ordinates != null) - (result_ordinates)[0] = getCoordY_(t); - - if (parameters != null) - (parameters)[0] = t; - - return 1; - } - } - - // line segment can have 0 or 1 intersection interval with clipEnv2D. - // The function return 0 or 2 segParams (e.g. 0.0, 0.4; or 0.1, 0.9; or 0.6, - // 1.0; or 0.0, 1.0) - // segParams will be sorted in ascending order; the order of the - // envelopeDistances will correspond (i.e. the envelopeDistances may not be - // in ascending order); - // an envelopeDistance can be -1.0 if the corresponding endpoint is properly - // inside clipEnv2D. - int intersectionWithEnvelope2D(Envelope2D clipEnv2D, - boolean includeEnvBoundary, double[] segParams, - double[] envelopeDistances) { - Point2D p1 = getStartXY(); - Point2D p2 = getEndXY(); - - // includeEnvBoundary xxx ??? - - int modified = clipEnv2D.clipLine(p1, p2, 0, segParams, - envelopeDistances); - return modified != 0 ? 2 : 0; - - } - - @Override - double intersectionOfYMonotonicWithAxisX(double y, double x_parallel) { - double a = (m_yEnd - m_yStart); - - if (a == 0) - return (y == m_yEnd) ? x_parallel : NumberUtils.NaN(); - - double t = (y - m_yStart) / a; - assert (t >= 0 && t <= 1.0); - // double t_1 = 1.0 - t; - // assert(t + t_1 == 1.0); - double resx = getCoordX_(t); - if (t == 1.0) - resx = m_xEnd; - assert ((resx >= m_xStart && resx <= m_xEnd) || (resx <= m_xStart && resx >= m_xEnd)); - return resx; - } - - @Override - boolean _isIntersectingPoint(Point2D pt, double tolerance, - boolean bExcludeExactEndpoints) { - return _intersection(pt, tolerance, bExcludeExactEndpoints) >= 0;// must - // use - // same - // method - // that - // the - // intersection - // routine - // uses. - } - - /** - * Returns True if point and the segment intersect (not disjoint) for the - * given tolerance. - */ - @Override - public boolean isIntersecting(Point2D pt, double tolerance) { - return _isIntersectingPoint(pt, tolerance, false); - } - - void orientBottomUp_() { - if (m_yEnd < m_yStart || (m_yEnd == m_yStart && m_xEnd < m_xStart)) { - double x = m_xStart; - m_xStart = m_xEnd; - m_xEnd = x; - - double y = m_yStart; - m_yStart = m_yEnd; - m_yEnd = y; - for (int i = 0, n = m_description.getTotalComponentCount() - 2; i < n; i++) { - double a = m_attributes[i]; - m_attributes[i] = m_attributes[i + n]; - m_attributes[i + n] = a; - } - } - } - - // return -1 for the left side from the infinite line passing through thais - // Line, 1 for the right side of the line, 0 if on the line (in the bounds - // of the roundoff error) - int _side(Point2D pt) { - return _side(pt.x, pt.y); - } - - // return -1 for the left side from the infinite line passing through thais - // Line, 1 for the right side of the line, 0 if on the line (in the bounds - // of the roundoff error) - int _side(double ptX, double ptY) { - Point2D v1 = new Point2D(ptX, ptY); - v1.sub(getStartXY()); - Point2D v2 = new Point2D(); - v2.sub(getEndXY(), getStartXY()); - double cross = v2.crossProduct(v1); - double crossError = 4 * NumberUtils.doubleEps() - * (Math.abs(v2.x * v1.y) + Math.abs(v2.y * v1.x)); - return cross > crossError ? -1 : cross < -crossError ? 1 : 0; - } - - double _intersection(Point2D pt, double tolerance, - boolean bExcludeExactEndPoints) { - Point2D v = new Point2D(); - Point2D start = new Point2D(); - - // Test start point distance to pt. - start.setCoords(m_xStart, m_yStart); - v.sub(pt, start); - double vlength = v.length(); - double vLengthError = vlength * 3 * NumberUtils.doubleEps(); - if (vlength <= Math.max(tolerance, vLengthError)) { - assert (vlength != 0 || pt.isEqual(start));// probably never asserts - if (bExcludeExactEndPoints && vlength == 0) - return NumberUtils.TheNaN; - else - return 0; - } - - Point2D end2D = getEndXY(); - // Test end point distance to pt. - v.sub(pt, end2D); - vlength = v.length(); - vLengthError = vlength * 3 * NumberUtils.doubleEps(); - if (vlength <= Math.max(tolerance, vLengthError)) { - assert (vlength != 0 || pt.isEqual(end2D));// probably never asserts - if (bExcludeExactEndPoints && vlength == 0) - return NumberUtils.TheNaN; - else - return 1.0; - } - - // Find a distance from the line to pt. - v.setCoords(m_xEnd - m_xStart, m_yEnd - m_yStart); - double len = v.length(); - if (len > 0) { - double invertedLength = 1.0 / len; - v.scale(invertedLength); - Point2D relativePoint = new Point2D(); - relativePoint.sub(pt, start); - double projection = relativePoint.dotProduct(v); - double projectionError = 8 * relativePoint._dotProductAbs(v) - * NumberUtils.doubleEps();// See Error Estimation Rules In - // Borg.docx - v.leftPerpendicular();// get left normal to v - double distance = relativePoint.dotProduct(v); - double distanceError = 8 * relativePoint._dotProductAbs(v) - * NumberUtils.doubleEps();// See Error Estimation Rules In - // Borg.docx - - double perror = Math.max(tolerance, projectionError); - if (projection < -perror || projection > len + perror) - return NumberUtils.TheNaN; - - double merror = Math.max(tolerance, distanceError); - if (Math.abs(distance) <= merror) { - double t = projection * invertedLength; - t = NumberUtils.snap(t, 0.0, 1.0); - Point2D ptOnLine = new Point2D(); - getCoord2D(t, ptOnLine); - if (Point2D.distance(ptOnLine, pt) <= tolerance) { - if (t < 0.5) { - if (Point2D.distance(ptOnLine, start) <= tolerance)// the - // projected - // point - // is - // close - // to - // the - // start - // point. - // Need - // to - // return - // 0. - return 0; - } else if (Point2D.distance(ptOnLine, end2D) <= tolerance)// the - // projected - // point - // is - // close - // to - // the - // end - // point. - // Need - // to - // return - // 1.0. - return 1.0; - - return t; - } - } - } - - return NumberUtils.TheNaN; - } - - @Override - public boolean equals(Object other) { - if (other == null) - return false; - - if (other == this) - return true; - - if (other.getClass() != getClass()) - return false; - - return _equalsImpl((Segment) other); - } - - boolean equals(Line other) { - if (other == this) - return true; - - if (!(other instanceof Line)) - return false; - - return _equalsImpl((Segment) other); - } - - boolean _projectionIntersectHelper(Line other, Point2D v, boolean bStart) { - // v is the vector in the direction of this line == end - start. - double orgX = bStart ? m_xStart : m_xEnd; - double orgY = bStart ? m_yStart : m_yEnd; - Point2D m = new Point2D(); - m.x = other.getEndX() - orgX; - m.y = other.getEndY() - orgY; - double dot = v.dotProduct(m); - double dotError = 3 * NumberUtils.doubleEps() * v._dotProductAbs(m); - if (dot > dotError) { - m.x = other.getStartX() - orgX; - m.y = other.getStartY() - orgY; - double dot2 = v.dotProduct(m); - double dotError2 = 3 * NumberUtils.doubleEps() - * v._dotProductAbs(m); - return dot2 <= dotError2; - } - - return true; - } - - boolean _projectionIntersect(Line other) { - // This function returns true, if the "other"'s projection on "this" - Point2D v = new Point2D(); - v.x = m_xEnd - m_xStart; - v.y = m_yEnd - m_yStart; - if (!_projectionIntersectHelper(other, v, false)) - return false; // Both other.Start and other.End projections on - // "this" lie to the right of the this.End - - v.negate(); - if (!_projectionIntersectHelper(other, v, true)) - return false; // Both other.Start and other.End projections on - // "this" lie to the left of the this.End - - return true; - } - - // Tests if two lines intersect using projection of one line to another. - static boolean _isIntersectingHelper(Line line1, Line line2) { - int s11 = line1._side(line2.m_xStart, line2.m_yStart); - int s12 = line1._side(line2.m_xEnd, line2.m_yEnd); - if (s11 < 0 && s12 < 0 || s11 > 0 && s12 > 0) - return false;// no intersection. The line2 lies to one side of an - // infinite line passing through line1 - - int s21 = line2._side(line1.m_xStart, line1.m_yStart); - int s22 = line2._side(line1.m_xEnd, line1.m_yEnd); - if (s21 < 0 && s22 < 0 || s21 > 0 && s22 > 0) - return false;// no intersection.The line1 lies to one side of an - // infinite line passing through line2 - - double len1 = line1.calculateLength2D(); - double len2 = line2.calculateLength2D(); - if (len1 > len2) { - return line1._projectionIntersect(line2); - } else { - return line2._projectionIntersect(line1); - } - } - - static Point2D _intersectHelper1(Line line1, Line line2, double tolerance) { - Point2D result = new Point2D(NumberUtils.NaN(), NumberUtils.NaN()); - double k1x = line1.m_xEnd - line1.m_xStart; - double k1y = line1.m_yEnd - line1.m_yStart; - double k2x = line2.m_xEnd - line2.m_xStart; - double k2y = line2.m_yEnd - line2.m_yStart; - - double det = k2x * k1y - k1x * k2y; - if (det == 0) - return result; - - // estimate roundoff error for det: - double errdet = 4 * NumberUtils.doubleEps() - * (Math.abs(k2x * k1y) + Math.abs(k1x * k2y)); - - double bx = line2.m_xStart - line1.m_xStart; - double by = line2.m_yStart - line1.m_yStart; - - double a0 = (k2x * by - bx * k2y); - double a0error = 4 * NumberUtils.doubleEps() - * (Math.abs(k2x * by) + Math.abs(bx * k2y)); - double t0 = a0 / det; - double absdet = Math.abs(det); - double t0error = (a0error * absdet + errdet * Math.abs(a0)) - / (det * det) + NumberUtils.doubleEps() * Math.abs(t0); - if (t0 < -t0error || t0 > 1.0 + t0error) - return result; - - double a1 = (k1x * by - bx * k1y); - double a1error = 4 * NumberUtils.doubleEps() - * (Math.abs(k1x * by) + Math.abs(bx * k1y)); - double t1 = a1 / det; - double t1error = (a1error * absdet + errdet * Math.abs(a1)) - / (det * det) + NumberUtils.doubleEps() * Math.abs(t1); - - if (t1 < -t1error || t1 > 1.0 + t1error) - return result; - - double t0r = NumberUtils.snap(t0, 0.0, 1.0); - double t1r = NumberUtils.snap(t1, 0.0, 1.0); - Point2D pt0 = line1.getCoord2D(t0r); - Point2D pt1 = line2.getCoord2D(t1r); - Point2D pt = new Point2D(); - pt.sub(pt0, pt1); - if (pt.length() > tolerance) { - // Roundoff errors cause imprecise result. Try recalculate. - // 1. Use averaged point and recalculate the t values - // Point2D pt; - pt.add(pt0, pt1); - pt.scale(0.5); - t0r = line1.getClosestCoordinate(pt, false); - t1r = line2.getClosestCoordinate(pt, false); - Point2D pt01 = line1.getCoord2D(t0r); - Point2D pt11 = line2.getCoord2D(t1r); - pt01.sub(pt11); - if (pt01.length() > tolerance) { - // Seems to be no intersection here actually. Return NaNs - return result; - } - } - - result.setCoords(t0r, t1r); - return result; - } - - static int _isIntersectingLineLine(Line line1, Line line2, - double tolerance, boolean bExcludeExactEndpoints) { - // _ASSERT(line1 != line2); - // Check for the endpoints. - // The bExcludeExactEndpoints is True, means we care only about overlaps - // and real intersections, but do not care if the endpoints are exactly - // equal. - // bExcludeExactEndpoints is used in Cracking check test, because during - // cracking test all points are either coincident or further than the - // tolerance. - int counter = 0; - if (line1.m_xStart == line2.m_xStart - && line1.m_yStart == line2.m_yStart - || line1.m_xStart == line2.m_xEnd - && line1.m_yStart == line2.m_yEnd) { - counter++; - if (!bExcludeExactEndpoints) - return 1; - } - - if (line1.m_xEnd == line2.m_xStart && line1.m_yEnd == line2.m_yStart - || line1.m_xEnd == line2.m_xEnd && line1.m_yEnd == line2.m_yEnd) { - counter++; - if (counter == 2) - return 2; // counter == 2 means both endpoints coincide (Lines - // overlap). - if (!bExcludeExactEndpoints) - return 1; - } - - if (line2._isIntersectingPoint(line1.getStartXY(), tolerance, true)) - return 1;// return true; - if (line2._isIntersectingPoint(line1.getEndXY(), tolerance, true)) - return 1;// return true; - if (line1._isIntersectingPoint(line2.getStartXY(), tolerance, true)) - return 1;// return true; - if (line1._isIntersectingPoint(line2.getEndXY(), tolerance, true)) - return 1;// return true; - - if (bExcludeExactEndpoints && (counter != 0)) - return 0;// return false; - - return _isIntersectingHelper(line1, line2) == false ? 0 : 1; - } - - int _intersectLineLineExact(Line line1, Line line2, - Point2D[] intersectionPoints, double[] param1, double[] param2) { - int counter = 0; - if (line1.m_xStart == line2.m_xStart - && line1.m_yStart == line2.m_yStart) { - if (param1 != null)// if (param1) - param1[counter] = 0.0; - if (param2 != null)// if (param2) - param2[counter] = 0.0; - - if (intersectionPoints != null)// if (intersectionPoints) - intersectionPoints[counter] = Point2D.construct(line1.m_xStart, - line1.m_yStart); - - counter++; - } - - if (line1.m_xStart == line2.m_xEnd && line1.m_yStart == line2.m_yEnd) { - if (param1 != null)// if (param1) - param1[counter] = 0.0; - if (param2 != null)// if (param2) - param2[counter] = 1.0; - - if (intersectionPoints != null)// if (intersectionPoints) - intersectionPoints[counter] = Point2D.construct(line1.m_xStart, - line1.m_yStart); - - counter++; - } - - if (line1.m_xEnd == line2.m_xStart && line1.m_yEnd == line2.m_yStart) { - if (counter == 2) {// both segments a degenerate - if (param1 != null)// if (param1) - { - param1[0] = 0.0; - param1[1] = 1.0; - } - if (param2 != null)// if (param2) - { - param2[0] = 1.0; - } - - if (intersectionPoints != null)// if (intersectionPoints) - { - intersectionPoints[0] = Point2D.construct(line1.m_xEnd, - line1.m_yEnd); - intersectionPoints[1] = Point2D.construct(line1.m_xEnd, - line1.m_yEnd); - } - - return counter; - } - - if (param1 != null)// if (param1) - param1[counter] = 1.0; - if (param2 != null)// if (param2) - param2[counter] = 0.0; - - if (intersectionPoints != null)// if (intersectionPoints) - intersectionPoints[counter] = Point2D.construct(line1.m_xEnd, - line1.m_yEnd); - - counter++; - } - - if (line1.m_xEnd == line2.m_xEnd && line1.m_yEnd == line2.m_yEnd) { - if (counter == 2) {// both segments are degenerate - if (param1 != null)// if (param1) - { - param1[0] = 0.0; - param1[1] = 1.0; - } - if (param2 != null)// if (param2) - { - param2[0] = 1.0; - } - - if (intersectionPoints != null)// if (intersectionPoints) - { - intersectionPoints[0] = Point2D.construct(line1.m_xEnd, - line1.m_yEnd); - intersectionPoints[1] = Point2D.construct(line1.m_xEnd, - line1.m_yEnd); - } - - return counter; - } - - if (param1 != null)// if (param1) - param1[counter] = 1.0; - if (param2 != null)// if (param2) - param2[counter] = 1.0; - - if (intersectionPoints != null)// if (intersectionPoints) - intersectionPoints[counter] = Point2D.construct(line1.m_xEnd, - line1.m_yEnd); - counter++; - } - - return counter; - } - - static int _intersectLineLine(Line line1, Line line2, - Point2D[] intersectionPoints, double[] param1, double[] param2, - double tolerance) { - // _ASSERT(!param1 && !param2 || param1); - int counter = 0; - // Test the end points for exact coincidence. - double t11 = line1._intersection(line2.getStartXY(), tolerance, false); - double t12 = line1._intersection(line2.getEndXY(), tolerance, false); - double t21 = line2._intersection(line1.getStartXY(), tolerance, false); - double t22 = line2._intersection(line1.getEndXY(), tolerance, false); - - if (!NumberUtils.isNaN(t11)) { - if (param1 != null)// if (param1) - param1[counter] = t11; - if (param2 != null)// if (param2) - param2[counter] = 0; - - if (intersectionPoints != null)// if (intersectionPoints) - intersectionPoints[counter] = Point2D.construct(line2.m_xStart, - line2.m_yStart); - counter++; - } - - if (!NumberUtils.isNaN(t12)) { - if (param1 != null)// if (param1) - param1[counter] = t12; - if (param2 != null)// if (param2) - param2[counter] = 1.0; - - if (intersectionPoints != null)// if (intersectionPoints) - intersectionPoints[counter] = Point2D.construct(line2.m_xEnd, - line2.m_yEnd); - counter++; - } - - if (counter != 2 && !NumberUtils.isNaN(t21)) { - if (!(t11 == 0 && t21 == 0) && !(t12 == 0 && t21 == 1.0))// the "if" - // makes - // sure - // this - // has - // not - // been - // already - // calculated - { - if (param1 != null)// if (param1) - param1[counter] = 0; - if (param2 != null)// if (param2) - param2[counter] = t21; - - if (intersectionPoints != null)// if (intersectionPoints) - intersectionPoints[counter] = Point2D.construct( - line1.m_xStart, line1.m_yStart); - counter++; - } - } - - if (counter != 2 && !NumberUtils.isNaN(t22)) { - if (!(t11 == 1.0 && t22 == 0) && !(t12 == 1.0 && t22 == 1.0))// the - // "if" - // makes - // sure - // this - // has - // not - // been - // already - // calculated - { - if (param1 != null)// if (param1) - param1[counter] = 1.0; - if (param2 != null)// if (param2) - param2[counter] = t22; - - if (intersectionPoints != null)// if (intersectionPoints) - intersectionPoints[counter] = Point2D.construct( - line2.m_xEnd, line2.m_yEnd); - counter++; - } - } - - if (counter > 0) { - if (counter == 2 && param1 != null && param1[0] > param1[1]) {// make - // sure - // the - // intersection - // events - // are - // sorted - // along - // the - // line1 - // can't - // swap - // doulbes - // in - // java - // NumberUtils::Swap(param1[0], - // param1[1]); - double zeroParam1 = param1[0]; - param1[0] = param1[1]; - param1[1] = zeroParam1; - - if (param2 != null)// if (param2) - { - double zeroParam2 = param2[0]; - param2[0] = param2[1]; - param2[1] = zeroParam2;// NumberUtils::Swap(ARRAYELEMENT(param2, - // 0), ARRAYELEMENT(param2, 1)); - } - - if (intersectionPoints != null)// if (intersectionPoints) - { - Point2D tmp = new Point2D(intersectionPoints[0].x, - intersectionPoints[0].y); - intersectionPoints[0] = intersectionPoints[1]; - intersectionPoints[1] = tmp; - } - } - - return counter; - } - - Point2D params = _intersectHelper1(line1, line2, tolerance); - if (NumberUtils.isNaN(params.x)) - return 0; - - if (intersectionPoints != null)// if (intersectionPoints) - { - intersectionPoints[0] = line1.getCoord2D(params.x); - } - - if (param1 != null)// if (param1) - { - param1[0] = params.x; - } - - if (param2 != null)// if (param2) - { - param2[0] = params.y; - } - - return 1; - } - - @Override - public void replaceNaNs(int semantics, double value) { - addAttribute(semantics); - if (isEmpty()) - return; - - int ncomps = VertexDescription.getComponentCount(semantics); - for (int i = 0; i < ncomps; i++) { - double v = _getAttributeAsDbl(0, semantics, i); - if (Double.isNaN(v)) - _setAttribute(0, semantics, 0, value); - - v = _getAttributeAsDbl(1, semantics, i); - if (Double.isNaN(v)) - _setAttribute(1, semantics, 0, value); - } - } - - - @Override - int getYMonotonicParts(SegmentBuffer[] monotonicSegments) { - return 0; - } - - @Override - void _copyToImpl(Segment dst) { - // TODO Auto-generated method stub - - } - - /** - * The output of this method can be only used for debugging. It is subject to change without notice. - */ - @Override - public String toString() { - String s = "Line: [" + m_xStart + ", " + m_yStart + ", " + m_xEnd + ", " + m_yEnd + "]"; - return s; - } + @Override + boolean isDegenerate(double tolerance) { + double dx = m_xStart - m_xEnd; + double dy = m_yStart - m_yEnd; + return Math.sqrt(dx * dx + dy * dy) <= tolerance; + } + + /** + * Indicates if the line segment is a curve. + */ + @Override + public boolean isCurve() { + return false; + } + + @Override + Point2D _getTangent(double t) { + Point2D pt = new Point2D(); + pt.sub(getEndXY(), getStartXY()); + return pt; + } + + @Override + boolean _isDegenerate(double tolerance) { + return calculateLength2D() <= tolerance; + } + + // HEADER DEF + + // Cpp + + /** + * Creates a line segment. + */ + public Line() { + m_description = VertexDescriptionDesignerImpl.getDefaultDescriptor2D(); + } + + Line(VertexDescription vd) { + m_description = vd; + } + + public Line(double x1, double y1, double x2, double y2) { + m_description = VertexDescriptionDesignerImpl.getDefaultDescriptor2D(); + setStartXY(x1, y1); + setEndXY(x2, y2); + } + + @Override + public void queryEnvelope(Envelope env) { + env.setEmpty(); + env.assignVertexDescription(m_description); + Envelope2D env2D = new Envelope2D(); + queryEnvelope2D(env2D); + env.setEnvelope2D(env2D); + + for (int i = 1, n = m_description.getAttributeCount(); i < n; i++) { + int semantics = m_description.getSemantics(i); + for (int iord = 0, nord = VertexDescription + .getComponentCount(semantics); i < nord; i++) { + Envelope1D interval = queryInterval(semantics, iord); + env.setInterval(semantics, iord, interval); + } + } + } + + @Override + public void queryEnvelope2D(Envelope2D env) { + env.setCoords(m_xStart, m_yStart, m_xEnd, m_yEnd); + env.normalize(); + } + + @Override + void queryEnvelope3D(Envelope3D env) { + env.setEmpty(); + env.merge(m_xStart, m_yStart, _getAttributeAsDbl(0, Semantics.Z, 0)); + env.merge(m_xEnd, m_yEnd, _getAttributeAsDbl(1, Semantics.Z, 0)); + } + + @Override + public void applyTransformation(Transformation2D transform) { + _touch(); + Point2D pt = new Point2D(); + pt.x = m_xStart; + pt.y = m_yStart; + transform.transform(pt, pt); + m_xStart = pt.x; + m_yStart = pt.y; + pt.x = m_xEnd; + pt.y = m_yEnd; + transform.transform(pt, pt); + m_xEnd = pt.x; + m_yEnd = pt.y; + } + + @Override + void applyTransformation(Transformation3D transform) { + _touch(); + Point3D pt = new Point3D(); + pt.x = m_xStart; + pt.y = m_yStart; + pt.z = _getAttributeAsDbl(0, Semantics.Z, 0); + pt = transform.transform(pt); + m_xStart = pt.x; + m_yStart = pt.y; + _setAttribute(0, Semantics.Z, 0, pt.z); + pt.x = m_xEnd; + pt.y = m_yEnd; + pt.z = _getAttributeAsDbl(1, Semantics.Z, 0); + pt = transform.transform(pt); + m_xEnd = pt.x; + m_yEnd = pt.y; + _setAttribute(1, Semantics.Z, 0, pt.z); + } + + @Override + public Geometry createInstance() { + return new Line(m_description); + } + + @Override + double _calculateArea2DHelper(double xorg, double yorg) { + return ((m_xEnd - xorg) - (m_xStart - xorg)) + * ((m_yEnd - yorg) + (m_yStart - yorg)) * 0.5; + } + + @Override + double tToLength(double t) { + return t * calculateLength2D(); + } + + @Override + double lengthToT(double len) { + return len / calculateLength2D(); + } + + double getCoordX_(double t) { + // Must match query_coord_2D and vice verse + // Also match get_attribute_as_dbl + return MathUtils.lerp(m_xStart, m_xEnd, t); + } + + double getCoordY_(double t) { + // Must match query_coord_2D and vice verse + // Also match get_attribute_as_dbl + return MathUtils.lerp(m_yStart, m_yEnd, t); + } + + @Override + public void getCoord2D(double t, Point2D pt) { + // We want: + // 1. When t == 0, get exactly Start + // 2. When t == 1, get exactly End + // 3. When m_x_end == m_x_start, we want m_x_start exactly + // 4. When m_y_end == m_y_start, we want m_y_start exactly + MathUtils.lerp(m_xStart, m_yStart, m_xEnd, m_yEnd, t, pt); + } + + @Override + public Segment cut(double t1, double t2) { + SegmentBuffer segmentBuffer = new SegmentBuffer(); + cut(t1, t2, segmentBuffer); + return segmentBuffer.get(); + } + + @Override + void cut(double t1, double t2, SegmentBuffer subSegmentBuffer) { + if (subSegmentBuffer == null) + throw new IllegalArgumentException(); + + subSegmentBuffer.createLine();// Make sure buffer contains Line class. + Segment subSegment = subSegmentBuffer.get(); + subSegment.assignVertexDescription(m_description); + + Point2D point = new Point2D(); + getCoord2D(t1, point); + subSegment.setStartXY(point.x, point.y); + getCoord2D(t2, point); + subSegment.setEndXY(point.x, point.y); + + for (int iattr = 1, nattr = m_description.getAttributeCount(); iattr < nattr; iattr++) { + int semantics = m_description._getSemanticsImpl(iattr); + int ncomps = VertexDescription.getComponentCount(semantics); + + for (int ordinate = 0; ordinate < ncomps; ordinate++) { + double value1 = getAttributeAsDbl(t1, semantics, ordinate); + subSegment.setStartAttribute(semantics, ordinate, value1); + + double value2 = getAttributeAsDbl(t2, semantics, ordinate); + subSegment.setEndAttribute(semantics, ordinate, value2); + } + } + } + + @Override + public double getAttributeAsDbl(double t, int semantics, int ordinate) { + if (semantics == VertexDescription.Semantics.POSITION) + return ordinate == 0 ? getCoord2D(t).x : getCoord2D(t).y; + + int interpolation = VertexDescription.getInterpolation(semantics); + switch (interpolation) { + case VertexDescription.Interpolation.NONE: + if (t < 0.5) + return getStartAttributeAsDbl(semantics, ordinate); + else + return getEndAttributeAsDbl(semantics, ordinate); + case VertexDescription.Interpolation.LINEAR: { + double s = getStartAttributeAsDbl(semantics, ordinate); + double e = getEndAttributeAsDbl(semantics, ordinate); + return MathUtils.lerp(s, e, t); + } + case VertexDescription.Interpolation.ANGULAR: { + throw new GeometryException("not implemented"); + } + } + + throw GeometryException.GeometryInternalError(); + } + + @Override + public double getClosestCoordinate(Point2D inputPt, boolean bExtrapolate) { + double vx = m_xEnd - m_xStart; + double vy = m_yEnd - m_yStart; + double v2 = vx * vx + vy * vy; + if (v2 == 0) + return 0.5; + double rx = inputPt.x - m_xStart; + double ry = inputPt.y - m_yStart; + double t = (rx * vx + ry * vy) / v2; + if (!bExtrapolate) { + if (t < 0.0) + t = 0.0; + else if (t > 1.0) + t = 1.0; + } + + return t; + } + + @Override + public int intersectionWithAxis2D(boolean b_axis_x, double ordinate, + double[] result_ordinates, double[] parameters) { + if (b_axis_x) { + double a = (m_yEnd - m_yStart); + + if (a == 0) + return (ordinate == m_yEnd) ? -1 : 0; + + double t = (ordinate - m_yStart) / a; + + if (t < 0.0 || t > 1.0) + return 0; + + if (result_ordinates != null) + (result_ordinates)[0] = getCoordX_(t); + + if (parameters != null) + (parameters)[0] = t; + + return 1; + } else { + double a = (m_xEnd - m_xStart); + + if (a == 0) + return (ordinate == m_xEnd) ? -1 : 0; + + double t = (ordinate - m_xStart) / a; + + if (t < 0.0 || t > 1.0) + return 0; + + if (result_ordinates != null) + (result_ordinates)[0] = getCoordY_(t); + + if (parameters != null) + (parameters)[0] = t; + + return 1; + } + } + + // line segment can have 0 or 1 intersection interval with clipEnv2D. + // The function return 0 or 2 segParams (e.g. 0.0, 0.4; or 0.1, 0.9; or 0.6, + // 1.0; or 0.0, 1.0) + // segParams will be sorted in ascending order; the order of the + // envelopeDistances will correspond (i.e. the envelopeDistances may not be + // in ascending order); + // an envelopeDistance can be -1.0 if the corresponding endpoint is properly + // inside clipEnv2D. + int intersectionWithEnvelope2D(Envelope2D clipEnv2D, + boolean includeEnvBoundary, double[] segParams, + double[] envelopeDistances) { + Point2D p1 = getStartXY(); + Point2D p2 = getEndXY(); + + // includeEnvBoundary xxx ??? + + int modified = clipEnv2D.clipLine(p1, p2, 0, segParams, + envelopeDistances); + return modified != 0 ? 2 : 0; + + } + + @Override + double intersectionOfYMonotonicWithAxisX(double y, double x_parallel) { + double a = (m_yEnd - m_yStart); + + if (a == 0) + return (y == m_yEnd) ? x_parallel : NumberUtils.NaN(); + + double t = (y - m_yStart) / a; + assert (t >= 0 && t <= 1.0); + // double t_1 = 1.0 - t; + // assert(t + t_1 == 1.0); + double resx = getCoordX_(t); + if (t == 1.0) + resx = m_xEnd; + assert ((resx >= m_xStart && resx <= m_xEnd) || (resx <= m_xStart && resx >= m_xEnd)); + return resx; + } + + @Override + boolean _isIntersectingPoint(Point2D pt, double tolerance, + boolean bExcludeExactEndpoints) { + return _intersection(pt, tolerance, bExcludeExactEndpoints) >= 0;// must + // use + // same + // method + // that + // the + // intersection + // routine + // uses. + } + + /** + * Returns True if point and the segment intersect (not disjoint) for the + * given tolerance. + */ + @Override + public boolean isIntersecting(Point2D pt, double tolerance) { + return _isIntersectingPoint(pt, tolerance, false); + } + + void orientBottomUp_() { + if (m_yEnd < m_yStart || (m_yEnd == m_yStart && m_xEnd < m_xStart)) { + double x = m_xStart; + m_xStart = m_xEnd; + m_xEnd = x; + + double y = m_yStart; + m_yStart = m_yEnd; + m_yEnd = y; + for (int i = 0, n = m_description.getTotalComponentCount() - 2; i < n; i++) { + double a = m_attributes[i]; + m_attributes[i] = m_attributes[i + n]; + m_attributes[i + n] = a; + } + } + } + + // return -1 for the left side from the infinite line passing through thais + // Line, 1 for the right side of the line, 0 if on the line (in the bounds + // of the roundoff error) + int _side(Point2D pt) { + return _side(pt.x, pt.y); + } + + // return -1 for the left side from the infinite line passing through thais + // Line, 1 for the right side of the line, 0 if on the line (in the bounds + // of the roundoff error) + int _side(double ptX, double ptY) { + Point2D v1 = new Point2D(ptX, ptY); + v1.sub(getStartXY()); + Point2D v2 = new Point2D(); + v2.sub(getEndXY(), getStartXY()); + double cross = v2.crossProduct(v1); + double crossError = 4 * NumberUtils.doubleEps() + * (Math.abs(v2.x * v1.y) + Math.abs(v2.y * v1.x)); + return cross > crossError ? -1 : cross < -crossError ? 1 : 0; + } + + double _intersection(Point2D pt, double tolerance, + boolean bExcludeExactEndPoints) { + Point2D v = new Point2D(); + Point2D start = new Point2D(); + + // Test start point distance to pt. + start.setCoords(m_xStart, m_yStart); + v.sub(pt, start); + double vlength = v.length(); + double vLengthError = vlength * 3 * NumberUtils.doubleEps(); + if (vlength <= Math.max(tolerance, vLengthError)) { + assert (vlength != 0 || pt.isEqual(start));// probably never asserts + if (bExcludeExactEndPoints && vlength == 0) + return NumberUtils.TheNaN; + else + return 0; + } + + Point2D end2D = getEndXY(); + // Test end point distance to pt. + v.sub(pt, end2D); + vlength = v.length(); + vLengthError = vlength * 3 * NumberUtils.doubleEps(); + if (vlength <= Math.max(tolerance, vLengthError)) { + assert (vlength != 0 || pt.isEqual(end2D));// probably never asserts + if (bExcludeExactEndPoints && vlength == 0) + return NumberUtils.TheNaN; + else + return 1.0; + } + + // Find a distance from the line to pt. + v.setCoords(m_xEnd - m_xStart, m_yEnd - m_yStart); + double len = v.length(); + if (len > 0) { + double invertedLength = 1.0 / len; + v.scale(invertedLength); + Point2D relativePoint = new Point2D(); + relativePoint.sub(pt, start); + double projection = relativePoint.dotProduct(v); + double projectionError = 8 * relativePoint._dotProductAbs(v) + * NumberUtils.doubleEps();// See Error Estimation Rules In + // Borg.docx + v.leftPerpendicular();// get left normal to v + double distance = relativePoint.dotProduct(v); + double distanceError = 8 * relativePoint._dotProductAbs(v) + * NumberUtils.doubleEps();// See Error Estimation Rules In + // Borg.docx + + double perror = Math.max(tolerance, projectionError); + if (projection < -perror || projection > len + perror) + return NumberUtils.TheNaN; + + double merror = Math.max(tolerance, distanceError); + if (Math.abs(distance) <= merror) { + double t = projection * invertedLength; + t = NumberUtils.snap(t, 0.0, 1.0); + Point2D ptOnLine = new Point2D(); + getCoord2D(t, ptOnLine); + if (Point2D.distance(ptOnLine, pt) <= tolerance) { + if (t < 0.5) { + if (Point2D.distance(ptOnLine, start) <= tolerance)// the + // projected + // point + // is + // close + // to + // the + // start + // point. + // Need + // to + // return + // 0. + return 0; + } else if (Point2D.distance(ptOnLine, end2D) <= tolerance)// the + // projected + // point + // is + // close + // to + // the + // end + // point. + // Need + // to + // return + // 1.0. + return 1.0; + + return t; + } + } + } + + return NumberUtils.TheNaN; + } + + @Override + public boolean equals(Object other) { + if (other == null) + return false; + + if (other == this) + return true; + + if (other.getClass() != getClass()) + return false; + + return _equalsImpl((Segment) other); + } + + boolean equals(Line other) { + if (other == this) + return true; + + if (!(other instanceof Line)) + return false; + + return _equalsImpl((Segment) other); + } + + boolean _projectionIntersectHelper(Line other, Point2D v, boolean bStart) { + // v is the vector in the direction of this line == end - start. + double orgX = bStart ? m_xStart : m_xEnd; + double orgY = bStart ? m_yStart : m_yEnd; + Point2D m = new Point2D(); + m.x = other.getEndX() - orgX; + m.y = other.getEndY() - orgY; + double dot = v.dotProduct(m); + double dotError = 3 * NumberUtils.doubleEps() * v._dotProductAbs(m); + if (dot > dotError) { + m.x = other.getStartX() - orgX; + m.y = other.getStartY() - orgY; + double dot2 = v.dotProduct(m); + double dotError2 = 3 * NumberUtils.doubleEps() + * v._dotProductAbs(m); + return dot2 <= dotError2; + } + + return true; + } + + boolean _projectionIntersect(Line other) { + // This function returns true, if the "other"'s projection on "this" + Point2D v = new Point2D(); + v.x = m_xEnd - m_xStart; + v.y = m_yEnd - m_yStart; + if (!_projectionIntersectHelper(other, v, false)) + return false; // Both other.Start and other.End projections on + // "this" lie to the right of the this.End + + v.negate(); + if (!_projectionIntersectHelper(other, v, true)) + return false; // Both other.Start and other.End projections on + // "this" lie to the left of the this.End + + return true; + } + + // Tests if two lines intersect using projection of one line to another. + static boolean _isIntersectingHelper(Line line1, Line line2) { + int s11 = line1._side(line2.m_xStart, line2.m_yStart); + int s12 = line1._side(line2.m_xEnd, line2.m_yEnd); + if (s11 < 0 && s12 < 0 || s11 > 0 && s12 > 0) + return false;// no intersection. The line2 lies to one side of an + // infinite line passing through line1 + + int s21 = line2._side(line1.m_xStart, line1.m_yStart); + int s22 = line2._side(line1.m_xEnd, line1.m_yEnd); + if (s21 < 0 && s22 < 0 || s21 > 0 && s22 > 0) + return false;// no intersection.The line1 lies to one side of an + // infinite line passing through line2 + + double len1 = line1.calculateLength2D(); + double len2 = line2.calculateLength2D(); + if (len1 > len2) { + return line1._projectionIntersect(line2); + } else { + return line2._projectionIntersect(line1); + } + } + + static Point2D _intersectHelper1(Line line1, Line line2, double tolerance) { + Point2D result = new Point2D(NumberUtils.NaN(), NumberUtils.NaN()); + double k1x = line1.m_xEnd - line1.m_xStart; + double k1y = line1.m_yEnd - line1.m_yStart; + double k2x = line2.m_xEnd - line2.m_xStart; + double k2y = line2.m_yEnd - line2.m_yStart; + + double det = k2x * k1y - k1x * k2y; + if (det == 0) + return result; + + // estimate roundoff error for det: + double errdet = 4 * NumberUtils.doubleEps() + * (Math.abs(k2x * k1y) + Math.abs(k1x * k2y)); + + double bx = line2.m_xStart - line1.m_xStart; + double by = line2.m_yStart - line1.m_yStart; + + double a0 = (k2x * by - bx * k2y); + double a0error = 4 * NumberUtils.doubleEps() + * (Math.abs(k2x * by) + Math.abs(bx * k2y)); + double t0 = a0 / det; + double absdet = Math.abs(det); + double t0error = (a0error * absdet + errdet * Math.abs(a0)) + / (det * det) + NumberUtils.doubleEps() * Math.abs(t0); + if (t0 < -t0error || t0 > 1.0 + t0error) + return result; + + double a1 = (k1x * by - bx * k1y); + double a1error = 4 * NumberUtils.doubleEps() + * (Math.abs(k1x * by) + Math.abs(bx * k1y)); + double t1 = a1 / det; + double t1error = (a1error * absdet + errdet * Math.abs(a1)) + / (det * det) + NumberUtils.doubleEps() * Math.abs(t1); + + if (t1 < -t1error || t1 > 1.0 + t1error) + return result; + + double t0r = NumberUtils.snap(t0, 0.0, 1.0); + double t1r = NumberUtils.snap(t1, 0.0, 1.0); + Point2D pt0 = line1.getCoord2D(t0r); + Point2D pt1 = line2.getCoord2D(t1r); + Point2D pt = new Point2D(); + pt.sub(pt0, pt1); + if (pt.length() > tolerance) { + // Roundoff errors cause imprecise result. Try recalculate. + // 1. Use averaged point and recalculate the t values + // Point2D pt; + pt.add(pt0, pt1); + pt.scale(0.5); + t0r = line1.getClosestCoordinate(pt, false); + t1r = line2.getClosestCoordinate(pt, false); + Point2D pt01 = line1.getCoord2D(t0r); + Point2D pt11 = line2.getCoord2D(t1r); + pt01.sub(pt11); + if (pt01.length() > tolerance) { + // Seems to be no intersection here actually. Return NaNs + return result; + } + } + + result.setCoords(t0r, t1r); + return result; + } + + static int _isIntersectingLineLine(Line line1, Line line2, + double tolerance, boolean bExcludeExactEndpoints) { + // _ASSERT(line1 != line2); + // Check for the endpoints. + // The bExcludeExactEndpoints is True, means we care only about overlaps + // and real intersections, but do not care if the endpoints are exactly + // equal. + // bExcludeExactEndpoints is used in Cracking check test, because during + // cracking test all points are either coincident or further than the + // tolerance. + int counter = 0; + if (line1.m_xStart == line2.m_xStart + && line1.m_yStart == line2.m_yStart + || line1.m_xStart == line2.m_xEnd + && line1.m_yStart == line2.m_yEnd) { + counter++; + if (!bExcludeExactEndpoints) + return 1; + } + + if (line1.m_xEnd == line2.m_xStart && line1.m_yEnd == line2.m_yStart + || line1.m_xEnd == line2.m_xEnd && line1.m_yEnd == line2.m_yEnd) { + counter++; + if (counter == 2) + return 2; // counter == 2 means both endpoints coincide (Lines + // overlap). + if (!bExcludeExactEndpoints) + return 1; + } + + if (line2._isIntersectingPoint(line1.getStartXY(), tolerance, true)) + return 1;// return true; + if (line2._isIntersectingPoint(line1.getEndXY(), tolerance, true)) + return 1;// return true; + if (line1._isIntersectingPoint(line2.getStartXY(), tolerance, true)) + return 1;// return true; + if (line1._isIntersectingPoint(line2.getEndXY(), tolerance, true)) + return 1;// return true; + + if (bExcludeExactEndpoints && (counter != 0)) + return 0;// return false; + + return _isIntersectingHelper(line1, line2) == false ? 0 : 1; + } + + int _intersectLineLineExact(Line line1, Line line2, + Point2D[] intersectionPoints, double[] param1, double[] param2) { + int counter = 0; + if (line1.m_xStart == line2.m_xStart + && line1.m_yStart == line2.m_yStart) { + if (param1 != null)// if (param1) + param1[counter] = 0.0; + if (param2 != null)// if (param2) + param2[counter] = 0.0; + + if (intersectionPoints != null)// if (intersectionPoints) + intersectionPoints[counter] = Point2D.construct(line1.m_xStart, + line1.m_yStart); + + counter++; + } + + if (line1.m_xStart == line2.m_xEnd && line1.m_yStart == line2.m_yEnd) { + if (param1 != null)// if (param1) + param1[counter] = 0.0; + if (param2 != null)// if (param2) + param2[counter] = 1.0; + + if (intersectionPoints != null)// if (intersectionPoints) + intersectionPoints[counter] = Point2D.construct(line1.m_xStart, + line1.m_yStart); + + counter++; + } + + if (line1.m_xEnd == line2.m_xStart && line1.m_yEnd == line2.m_yStart) { + if (counter == 2) {// both segments a degenerate + if (param1 != null)// if (param1) + { + param1[0] = 0.0; + param1[1] = 1.0; + } + if (param2 != null)// if (param2) + { + param2[0] = 1.0; + } + + if (intersectionPoints != null)// if (intersectionPoints) + { + intersectionPoints[0] = Point2D.construct(line1.m_xEnd, + line1.m_yEnd); + intersectionPoints[1] = Point2D.construct(line1.m_xEnd, + line1.m_yEnd); + } + + return counter; + } + + if (param1 != null)// if (param1) + param1[counter] = 1.0; + if (param2 != null)// if (param2) + param2[counter] = 0.0; + + if (intersectionPoints != null)// if (intersectionPoints) + intersectionPoints[counter] = Point2D.construct(line1.m_xEnd, + line1.m_yEnd); + + counter++; + } + + if (line1.m_xEnd == line2.m_xEnd && line1.m_yEnd == line2.m_yEnd) { + if (counter == 2) {// both segments are degenerate + if (param1 != null)// if (param1) + { + param1[0] = 0.0; + param1[1] = 1.0; + } + if (param2 != null)// if (param2) + { + param2[0] = 1.0; + } + + if (intersectionPoints != null)// if (intersectionPoints) + { + intersectionPoints[0] = Point2D.construct(line1.m_xEnd, + line1.m_yEnd); + intersectionPoints[1] = Point2D.construct(line1.m_xEnd, + line1.m_yEnd); + } + + return counter; + } + + if (param1 != null)// if (param1) + param1[counter] = 1.0; + if (param2 != null)// if (param2) + param2[counter] = 1.0; + + if (intersectionPoints != null)// if (intersectionPoints) + intersectionPoints[counter] = Point2D.construct(line1.m_xEnd, + line1.m_yEnd); + counter++; + } + + return counter; + } + + static int _intersectLineLine(Line line1, Line line2, + Point2D[] intersectionPoints, double[] param1, double[] param2, + double tolerance) { + // _ASSERT(!param1 && !param2 || param1); + int counter = 0; + // Test the end points for exact coincidence. + double t11 = line1._intersection(line2.getStartXY(), tolerance, false); + double t12 = line1._intersection(line2.getEndXY(), tolerance, false); + double t21 = line2._intersection(line1.getStartXY(), tolerance, false); + double t22 = line2._intersection(line1.getEndXY(), tolerance, false); + + if (!NumberUtils.isNaN(t11)) { + if (param1 != null)// if (param1) + param1[counter] = t11; + if (param2 != null)// if (param2) + param2[counter] = 0; + + if (intersectionPoints != null)// if (intersectionPoints) + intersectionPoints[counter] = Point2D.construct(line2.m_xStart, + line2.m_yStart); + counter++; + } + + if (!NumberUtils.isNaN(t12)) { + if (param1 != null)// if (param1) + param1[counter] = t12; + if (param2 != null)// if (param2) + param2[counter] = 1.0; + + if (intersectionPoints != null)// if (intersectionPoints) + intersectionPoints[counter] = Point2D.construct(line2.m_xEnd, + line2.m_yEnd); + counter++; + } + + if (counter != 2 && !NumberUtils.isNaN(t21)) { + if (!(t11 == 0 && t21 == 0) && !(t12 == 0 && t21 == 1.0))// the "if" + // makes + // sure + // this + // has + // not + // been + // already + // calculated + { + if (param1 != null)// if (param1) + param1[counter] = 0; + if (param2 != null)// if (param2) + param2[counter] = t21; + + if (intersectionPoints != null)// if (intersectionPoints) + intersectionPoints[counter] = Point2D.construct( + line1.m_xStart, line1.m_yStart); + counter++; + } + } + + if (counter != 2 && !NumberUtils.isNaN(t22)) { + if (!(t11 == 1.0 && t22 == 0) && !(t12 == 1.0 && t22 == 1.0))// the + // "if" + // makes + // sure + // this + // has + // not + // been + // already + // calculated + { + if (param1 != null)// if (param1) + param1[counter] = 1.0; + if (param2 != null)// if (param2) + param2[counter] = t22; + + if (intersectionPoints != null)// if (intersectionPoints) + intersectionPoints[counter] = Point2D.construct( + line2.m_xEnd, line2.m_yEnd); + counter++; + } + } + + if (counter > 0) { + if (counter == 2 && param1 != null && param1[0] > param1[1]) {// make + // sure + // the + // intersection + // events + // are + // sorted + // along + // the + // line1 + // can't + // swap + // doulbes + // in + // java + // NumberUtils::Swap(param1[0], + // param1[1]); + double zeroParam1 = param1[0]; + param1[0] = param1[1]; + param1[1] = zeroParam1; + + if (param2 != null)// if (param2) + { + double zeroParam2 = param2[0]; + param2[0] = param2[1]; + param2[1] = zeroParam2;// NumberUtils::Swap(ARRAYELEMENT(param2, + // 0), ARRAYELEMENT(param2, 1)); + } + + if (intersectionPoints != null)// if (intersectionPoints) + { + Point2D tmp = new Point2D(intersectionPoints[0].x, + intersectionPoints[0].y); + intersectionPoints[0] = intersectionPoints[1]; + intersectionPoints[1] = tmp; + } + } + + return counter; + } + + Point2D params = _intersectHelper1(line1, line2, tolerance); + if (NumberUtils.isNaN(params.x)) + return 0; + + if (intersectionPoints != null)// if (intersectionPoints) + { + intersectionPoints[0] = line1.getCoord2D(params.x); + } + + if (param1 != null)// if (param1) + { + param1[0] = params.x; + } + + if (param2 != null)// if (param2) + { + param2[0] = params.y; + } + + return 1; + } + + @Override + public void replaceNaNs(int semantics, double value) { + addAttribute(semantics); + if (isEmpty()) + return; + + int ncomps = VertexDescription.getComponentCount(semantics); + for (int i = 0; i < ncomps; i++) { + double v = _getAttributeAsDbl(0, semantics, i); + if (Double.isNaN(v)) + _setAttribute(0, semantics, 0, value); + + v = _getAttributeAsDbl(1, semantics, i); + if (Double.isNaN(v)) + _setAttribute(1, semantics, 0, value); + } + } + + + @Override + int getYMonotonicParts(SegmentBuffer[] monotonicSegments) { + return 0; + } + + @Override + void _copyToImpl(Segment dst) { + // TODO Auto-generated method stub + + } + + /** + * The output of this method can be only used for debugging. It is subject to change without notice. + */ + @Override + public String toString() { + String s = "Line: [" + m_xStart + ", " + m_yStart + ", " + m_xEnd + ", " + m_yEnd + "]"; + return s; + } } diff --git a/src/main/java/com/esri/core/geometry/ListeningGeometryCursor.java b/src/main/java/com/esri/core/geometry/ListeningGeometryCursor.java index d4867194..5ab1297a 100644 --- a/src/main/java/com/esri/core/geometry/ListeningGeometryCursor.java +++ b/src/main/java/com/esri/core/geometry/ListeningGeometryCursor.java @@ -34,47 +34,51 @@ * but are coming in a stream. */ public final class ListeningGeometryCursor extends GeometryCursor { - // required for use with grpc streaming - // https://github.com/grpc/grpc-java/blob/06e9b8814787b5cdec10a60e0d9a2228feb8554d/examples/src/main/java/io/grpc/examples/routeguide/RouteGuideServer.java#L246 - private Queue m_geomList = new ConcurrentLinkedQueue(); + // required for use with grpc streaming + // https://github.com/grpc/grpc-java/blob/06e9b8814787b5cdec10a60e0d9a2228feb8554d/examples/src/main/java/io/grpc/examples/routeguide/RouteGuideServer.java#L246 + private Queue m_geomList = new ConcurrentLinkedQueue(); - private long m_index = -1; + private long m_index = -1; - public ListeningGeometryCursor() { - } + public ListeningGeometryCursor() { + } - @Override - public boolean hasNext() { return m_geomList != null && !m_geomList.isEmpty(); } + @Override + public boolean hasNext() { + return m_geomList != null && !m_geomList.isEmpty(); + } - @Override - public long getGeometryID() { - return m_index; - } + @Override + public long getGeometryID() { + return m_index; + } - @Override - public String getFeatureID() { return ""; } + @Override + public String getFeatureID() { + return ""; + } - @Override - public Geometry next() { - if (m_geomList != null && !m_geomList.isEmpty()) { - m_index++; - return m_geomList.poll(); - } + @Override + public Geometry next() { + if (m_geomList != null && !m_geomList.isEmpty()) { + m_index++; + return m_geomList.poll(); + } - m_geomList = null;//prevent the class from being used again - return null; - } + m_geomList = null;//prevent the class from being used again + return null; + } - /** - * Call this method to add geometry to the cursor. After this method is - * called, call immediately the tock() method on the GeometryCursor returned - * by the OperatorUnion (or OperatorConvexHull with b_merge == true). Call - * next() on the GeometryCursor returned by the OperatorUnion when done - * listening to incoming geometries to finish the union operation. - * - * @param geom The geometry to be pushed into the cursor. - */ - public void tick(Geometry geom) { - m_geomList.add(geom); - } + /** + * Call this method to add geometry to the cursor. After this method is + * called, call immediately the tock() method on the GeometryCursor returned + * by the OperatorUnion (or OperatorConvexHull with b_merge == true). Call + * next() on the GeometryCursor returned by the OperatorUnion when done + * listening to incoming geometries to finish the union operation. + * + * @param geom The geometry to be pushed into the cursor. + */ + public void tick(Geometry geom) { + m_geomList.add(geom); + } } diff --git a/src/main/java/com/esri/core/geometry/LnSrlzr.java b/src/main/java/com/esri/core/geometry/LnSrlzr.java index 25e37fd9..8e7a6eaa 100644 --- a/src/main/java/com/esri/core/geometry/LnSrlzr.java +++ b/src/main/java/com/esri/core/geometry/LnSrlzr.java @@ -29,65 +29,65 @@ //This is a writeReplace class for Lin public class LnSrlzr implements Serializable { - private static final long serialVersionUID = 1L; - double[] attribs; - int descriptionBitMask; + private static final long serialVersionUID = 1L; + double[] attribs; + int descriptionBitMask; - public Object readResolve() throws ObjectStreamException { - Line ln = null; - if (descriptionBitMask == -1) - return null; + public Object readResolve() throws ObjectStreamException { + Line ln = null; + if (descriptionBitMask == -1) + return null; - try { - VertexDescription vd = VertexDescriptionDesignerImpl - .getVertexDescription(descriptionBitMask); - ln = new Line(vd); - if (attribs != null) { - ln.setStartXY(attribs[0], attribs[1]); - ln.setEndXY(attribs[2], attribs[3]); - int index = 4; - for (int i = 1, n = vd.getAttributeCount(); i < n; i++) { - int semantics = vd.getSemantics(i); - int comps = VertexDescription.getComponentCount(semantics); - for (int ord = 0; ord < comps; ord++) { - ln.setStartAttribute(semantics, ord, attribs[index++]); - ln.setEndAttribute(semantics, ord, attribs[index++]); - } - } - } - } catch (Exception ex) { - throw new InvalidObjectException("Cannot read geometry from stream"); - } + try { + VertexDescription vd = VertexDescriptionDesignerImpl + .getVertexDescription(descriptionBitMask); + ln = new Line(vd); + if (attribs != null) { + ln.setStartXY(attribs[0], attribs[1]); + ln.setEndXY(attribs[2], attribs[3]); + int index = 4; + for (int i = 1, n = vd.getAttributeCount(); i < n; i++) { + int semantics = vd.getSemantics(i); + int comps = VertexDescription.getComponentCount(semantics); + for (int ord = 0; ord < comps; ord++) { + ln.setStartAttribute(semantics, ord, attribs[index++]); + ln.setEndAttribute(semantics, ord, attribs[index++]); + } + } + } + } catch (Exception ex) { + throw new InvalidObjectException("Cannot read geometry from stream"); + } - return ln; - } + return ln; + } - public void setGeometryByValue(Line ln) throws ObjectStreamException { - try { - attribs = null; - if (ln == null) { - descriptionBitMask = -1; - } + public void setGeometryByValue(Line ln) throws ObjectStreamException { + try { + attribs = null; + if (ln == null) { + descriptionBitMask = -1; + } - VertexDescription vd = ln.getDescription(); - descriptionBitMask = vd.m_semanticsBitArray; + VertexDescription vd = ln.getDescription(); + descriptionBitMask = vd.m_semanticsBitArray; - attribs = new double[vd.getTotalComponentCount() * 2]; - attribs[0] = ln.getStartX(); - attribs[1] = ln.getStartY(); - attribs[2] = ln.getEndX(); - attribs[3] = ln.getEndY(); - int index = 4; - for (int i = 1, n = vd.getAttributeCount(); i < n; i++) { - int semantics = vd.getSemantics(i); - int comps = VertexDescription.getComponentCount(semantics); - for (int ord = 0; ord < comps; ord++) { - attribs[index++] = ln.getStartAttributeAsDbl(semantics, ord); - attribs[index++] = ln.getEndAttributeAsDbl(semantics, ord); - } - } - } catch (Exception ex) { - throw new InvalidObjectException("Cannot serialize this geometry"); - } - } + attribs = new double[vd.getTotalComponentCount() * 2]; + attribs[0] = ln.getStartX(); + attribs[1] = ln.getStartY(); + attribs[2] = ln.getEndX(); + attribs[3] = ln.getEndY(); + int index = 4; + for (int i = 1, n = vd.getAttributeCount(); i < n; i++) { + int semantics = vd.getSemantics(i); + int comps = VertexDescription.getComponentCount(semantics); + for (int ord = 0; ord < comps; ord++) { + attribs[index++] = ln.getStartAttributeAsDbl(semantics, ord); + attribs[index++] = ln.getEndAttributeAsDbl(semantics, ord); + } + } + } catch (Exception ex) { + throw new InvalidObjectException("Cannot serialize this geometry"); + } + } } diff --git a/src/main/java/com/esri/core/geometry/MapGeometry.java b/src/main/java/com/esri/core/geometry/MapGeometry.java index 37efc4fe..b9f6be1f 100644 --- a/src/main/java/com/esri/core/geometry/MapGeometry.java +++ b/src/main/java/com/esri/core/geometry/MapGeometry.java @@ -35,126 +35,126 @@ * spatial reference defined for this geometry. */ public class MapGeometry implements Serializable { - private static final long serialVersionUID = 1L; - - Geometry m_geometry = null; - SpatialReference sr = null; - - /** - * Construct a MapGeometry instance using the specified geometry instance - * and its corresponding spatial reference. - * - * @param g The geometry to construct the new MapGeometry object. - * @param _sr The spatial reference of the geometry. - */ - public MapGeometry(Geometry g, SpatialReference _sr) { - m_geometry = g; - sr = _sr; - } - - /** - * Gets the only geometry without the spatial reference from the - * MapGeometry. - */ - public Geometry getGeometry() { - return m_geometry; - } - - /** - * Sets the geometry for this MapGeometry. - * - * @param geometry The geometry. - */ - - public void setGeometry(Geometry geometry) { - this.m_geometry = geometry; - } - - /** - * Sets the spatial reference for this MapGeometry. - * - * @param sr The spatial reference. - */ - public void setSpatialReference(SpatialReference sr) { - this.sr = sr; - } - - /** - * Gets the spatial reference for this MapGeometry. - */ - public SpatialReference getSpatialReference() { - return sr; - } - - /** - * The output of this method can be only used for debugging. It is subject to change without notice. - */ - @Override - public String toString() { - String snippet = OperatorExportToJson.local().execute(getSpatialReference(), getGeometry()); - if (snippet.length() > 200) { - return snippet.substring(0, 197) + "... (" + snippet.length() + " characters)"; - } else { - return snippet; - } - } - - @Override - public boolean equals(Object other) { - if (other == null) - return false; - - if (other == this) - return true; - - if (other.getClass() != getClass()) - return false; - - MapGeometry omg = (MapGeometry) other; - SpatialReference sr = getSpatialReference(); - Geometry g = getGeometry(); - SpatialReference osr = omg.getSpatialReference(); - Geometry og = omg.getGeometry(); - - if (sr != osr) { - if (sr == null || !sr.equals(osr)) - return false; - } - - if (g != og) { - if (g == null || !g.equals(og)) - return false; - } - - return true; - } - - /** - * Returns an estimate of this object size in bytes. - *

- * This estimate doesn't include the size of the {@link SpatialReference} object - * because instances of {@link SpatialReference} are expected to be shared among - * geometry objects. - * - * @return Returns an estimate of this object size in bytes. - */ - public long estimateMemorySize() { - long sz = SIZE_OF_MAPGEOMETRY; - if (m_geometry != null) - sz += m_geometry.estimateMemorySize(); - return sz; - } - - @Override - public int hashCode() { - SpatialReference sr = getSpatialReference(); - Geometry g = getGeometry(); - int hc = 0x2937912; - if (sr != null) - hc ^= sr.hashCode(); - if (g != null) - hc ^= g.hashCode(); - - return hc; - } + private static final long serialVersionUID = 1L; + + Geometry m_geometry = null; + SpatialReference sr = null; + + /** + * Construct a MapGeometry instance using the specified geometry instance + * and its corresponding spatial reference. + * + * @param g The geometry to construct the new MapGeometry object. + * @param _sr The spatial reference of the geometry. + */ + public MapGeometry(Geometry g, SpatialReference _sr) { + m_geometry = g; + sr = _sr; + } + + /** + * Gets the only geometry without the spatial reference from the + * MapGeometry. + */ + public Geometry getGeometry() { + return m_geometry; + } + + /** + * Sets the geometry for this MapGeometry. + * + * @param geometry The geometry. + */ + + public void setGeometry(Geometry geometry) { + this.m_geometry = geometry; + } + + /** + * Sets the spatial reference for this MapGeometry. + * + * @param sr The spatial reference. + */ + public void setSpatialReference(SpatialReference sr) { + this.sr = sr; + } + + /** + * Gets the spatial reference for this MapGeometry. + */ + public SpatialReference getSpatialReference() { + return sr; + } + + /** + * The output of this method can be only used for debugging. It is subject to change without notice. + */ + @Override + public String toString() { + String snippet = OperatorExportToJson.local().execute(getSpatialReference(), getGeometry()); + if (snippet.length() > 200) { + return snippet.substring(0, 197) + "... (" + snippet.length() + " characters)"; + } else { + return snippet; + } + } + + @Override + public boolean equals(Object other) { + if (other == null) + return false; + + if (other == this) + return true; + + if (other.getClass() != getClass()) + return false; + + MapGeometry omg = (MapGeometry) other; + SpatialReference sr = getSpatialReference(); + Geometry g = getGeometry(); + SpatialReference osr = omg.getSpatialReference(); + Geometry og = omg.getGeometry(); + + if (sr != osr) { + if (sr == null || !sr.equals(osr)) + return false; + } + + if (g != og) { + if (g == null || !g.equals(og)) + return false; + } + + return true; + } + + /** + * Returns an estimate of this object size in bytes. + *

+ * This estimate doesn't include the size of the {@link SpatialReference} object + * because instances of {@link SpatialReference} are expected to be shared among + * geometry objects. + * + * @return Returns an estimate of this object size in bytes. + */ + public long estimateMemorySize() { + long sz = SIZE_OF_MAPGEOMETRY; + if (m_geometry != null) + sz += m_geometry.estimateMemorySize(); + return sz; + } + + @Override + public int hashCode() { + SpatialReference sr = getSpatialReference(); + Geometry g = getGeometry(); + int hc = 0x2937912; + if (sr != null) + hc ^= sr.hashCode(); + if (g != null) + hc ^= g.hashCode(); + + return hc; + } } diff --git a/src/main/java/com/esri/core/geometry/MapGeometryCursor.java b/src/main/java/com/esri/core/geometry/MapGeometryCursor.java index 20923fc6..925474f8 100644 --- a/src/main/java/com/esri/core/geometry/MapGeometryCursor.java +++ b/src/main/java/com/esri/core/geometry/MapGeometryCursor.java @@ -30,26 +30,26 @@ */ public abstract class MapGeometryCursor implements Iterator { - /** - * Moves the cursor to the next ProjectedGeometry. Returns null when reached - * the end. - */ - public abstract MapGeometry next(); - - /** - * Returns the ID of the current geometry. The ID is propagated across the - * operations (when possible). - *

- * Returns an ID associated with the current Geometry. The ID is passed - * along and is returned by some operators to preserve relationship between - * the input and output geometry classes. It is not always possible to - * preserve an ID during an operation. - */ - public abstract long getGeometryID(); - - public abstract SimpleStateEnum getSimpleState(); - - public abstract String getFeatureID(); - - public abstract boolean hasNext(); + /** + * Moves the cursor to the next ProjectedGeometry. Returns null when reached + * the end. + */ + public abstract MapGeometry next(); + + /** + * Returns the ID of the current geometry. The ID is propagated across the + * operations (when possible). + *

+ * Returns an ID associated with the current Geometry. The ID is passed + * along and is returned by some operators to preserve relationship between + * the input and output geometry classes. It is not always possible to + * preserve an ID during an operation. + */ + public abstract long getGeometryID(); + + public abstract SimpleStateEnum getSimpleState(); + + public abstract String getFeatureID(); + + public abstract boolean hasNext(); } diff --git a/src/main/java/com/esri/core/geometry/MapOGCStructure.java b/src/main/java/com/esri/core/geometry/MapOGCStructure.java index ea2e8d4c..c4eb5241 100644 --- a/src/main/java/com/esri/core/geometry/MapOGCStructure.java +++ b/src/main/java/com/esri/core/geometry/MapOGCStructure.java @@ -24,6 +24,6 @@ package com.esri.core.geometry; public class MapOGCStructure { - public OGCStructure m_ogcStructure; - public SpatialReference m_spatialReference; + public OGCStructure m_ogcStructure; + public SpatialReference m_spatialReference; } diff --git a/src/main/java/com/esri/core/geometry/MathUtils.java b/src/main/java/com/esri/core/geometry/MathUtils.java index fa37aed2..2b41d433 100644 --- a/src/main/java/com/esri/core/geometry/MathUtils.java +++ b/src/main/java/com/esri/core/geometry/MathUtils.java @@ -25,211 +25,211 @@ package com.esri.core.geometry; final class MathUtils { - /** - * The implementation of the Kahan summation algorithm. Use to get better - * precision when adding a lot of values. - */ - static final class KahanSummator { - private double sum; // the accumulated sum - private double compensation; - private double startValue; // the Base (the class returns sum + - // startValue) - - /** - * initialize to the given start value. \param startValue_ The value to - * be added to the accumulated sum. - */ - KahanSummator(double startValue_) { - startValue = startValue_; - reset(); - } - - /** - * Resets the accumulated sum to zero. The getResult() returns - * startValue_ after this call. - */ - void reset() { - sum = 0; - compensation = 0; - } - - /** - * add a value. - */ - void add(double v) { - double y = v - compensation; - double t = sum + y; - double h = t - sum; - compensation = h - y; - sum = t; - } - - /** - * Subtracts a value. - */ - void sub(double v) { - add(-v); - } - - /** - * add another summator. - */ - void add(/* const */KahanSummator v) { - double y = (v.getResult() + v.compensation) - compensation; - double t = sum + y; - double h = t - sum; - compensation = h - y; - sum = t; - } - - /** - * Subtracts another summator. - */ - void sub(/* const */KahanSummator v) { - double y = -(v.getResult() - v.compensation) - compensation; - double t = sum + y; - double h = t - sum; - compensation = h - y; - sum = t; - } - - /** - * Returns current value of the sum. - */ - double getResult() /* const */ { - return startValue + sum; - } - - KahanSummator plusEquals(double v) { - add(v); - return this; - } - - KahanSummator minusEquals(double v) { - add(-v); - return this; - } - - KahanSummator plusEquals(/* const */KahanSummator v) { - add(v); - return this; - } - - KahanSummator minusEquals(/* const */KahanSummator v) { - sub(v); - return this; - } - } - - /** - * Returns one value with the sign of another (like copysign). - */ - static double copySign(double x, double y) { - return y >= 0.0 ? Math.abs(x) : -Math.abs(x); - } - - /** - * Calculates sign of the given value. Returns 0 if the value is equal to 0. - */ - static int sign(double value) { - return value < 0 ? -1 : (value > 0) ? 1 : 0; - } - - /** - * Rounds towards zero. - */ - static double truncate(double v) { - if (v >= 0) - return Math.floor(v); - else - return -Math.floor(-v); - } - - /** - * C fmod function. - */ - static double FMod(double x, double y) { - return x - truncate(x / y) * y; - } - - - /** - * Rounds double to the closest integer value. - */ - static double round(double v) { - return Math.floor(v + 0.5); - } - - static double sqr(double v) { - return v * v; - } - - /** - * Computes interpolation between two values, using the interpolation factor t. - * The interpolation formula is (end - start) * t + start. - * However, the computation ensures that t = 0 produces exactly start, and t = 1, produces exactly end. - * It also guarantees that for 0 <= t <= 1, the interpolated value v is between start and end. - */ - static double lerp(double start_, double end_, double t) { - // When end == start, we want result to be equal to start, for all t - // values. At the same time, when end != start, we want the result to be - // equal to start for t==0 and end for t == 1.0 - // The regular formula end_ * t + (1.0 - t) * start_, when end_ == - // start_, and t at 1/3, produces value different from start - double v; - if (t <= 0.5) - v = start_ + (end_ - start_) * t; - else - v = end_ - (end_ - start_) * (1.0 - t); - - assert (t < 0 || t > 1.0 || (v >= start_ && v <= end_) || (v <= start_ && v >= end_) || NumberUtils.isNaN(start_) || NumberUtils.isNaN(end_)); - return v; - } - - /** - * Computes interpolation between two values, using the interpolation factor t. - * The interpolation formula is (end - start) * t + start. - * However, the computation ensures that t = 0 produces exactly start, and t = 1, produces exactly end. - * It also guarantees that for 0 <= t <= 1, the interpolated value v is between start and end. - */ - static void lerp(Point2D start_, Point2D end_, double t, Point2D result) { - assert (start_ != result); - // When end == start, we want result to be equal to start, for all t - // values. At the same time, when end != start, we want the result to be - // equal to start for t==0 and end for t == 1.0 - // The regular formula end_ * t + (1.0 - t) * start_, when end_ == - // start_, and t at 1/3, produces value different from start - double rx, ry; - if (t <= 0.5) { - rx = start_.x + (end_.x - start_.x) * t; - ry = start_.y + (end_.y - start_.y) * t; - } else { - rx = end_.x - (end_.x - start_.x) * (1.0 - t); - ry = end_.y - (end_.y - start_.y) * (1.0 - t); - } - - assert (t < 0 || t > 1.0 || (rx >= start_.x && rx <= end_.x) || (rx <= start_.x && rx >= end_.x)); - assert (t < 0 || t > 1.0 || (ry >= start_.y && ry <= end_.y) || (ry <= start_.y && ry >= end_.y)); - result.x = rx; - result.y = ry; - } - - static void lerp(double start_x, double start_y, double end_x, double end_y, double t, Point2D result) { - // When end == start, we want result to be equal to start, for all t - // values. At the same time, when end != start, we want the result to be - // equal to start for t==0 and end for t == 1.0 - // The regular formula end_ * t + (1.0 - t) * start_, when end_ == - // start_, and t at 1/3, produces value different from start - if (t <= 0.5) { - result.x = start_x + (end_x - start_x) * t; - result.y = start_y + (end_y - start_y) * t; - } else { - result.x = end_x - (end_x - start_x) * (1.0 - t); - result.y = end_y - (end_y - start_y) * (1.0 - t); - } - - assert (t < 0 || t > 1.0 || (result.x >= start_x && result.x <= end_x) || (result.x <= start_x && result.x >= end_x)); - assert (t < 0 || t > 1.0 || (result.y >= start_y && result.y <= end_y) || (result.y <= start_y && result.y >= end_y)); - } + /** + * The implementation of the Kahan summation algorithm. Use to get better + * precision when adding a lot of values. + */ + static final class KahanSummator { + private double sum; // the accumulated sum + private double compensation; + private double startValue; // the Base (the class returns sum + + // startValue) + + /** + * initialize to the given start value. \param startValue_ The value to + * be added to the accumulated sum. + */ + KahanSummator(double startValue_) { + startValue = startValue_; + reset(); + } + + /** + * Resets the accumulated sum to zero. The getResult() returns + * startValue_ after this call. + */ + void reset() { + sum = 0; + compensation = 0; + } + + /** + * add a value. + */ + void add(double v) { + double y = v - compensation; + double t = sum + y; + double h = t - sum; + compensation = h - y; + sum = t; + } + + /** + * Subtracts a value. + */ + void sub(double v) { + add(-v); + } + + /** + * add another summator. + */ + void add(/* const */KahanSummator v) { + double y = (v.getResult() + v.compensation) - compensation; + double t = sum + y; + double h = t - sum; + compensation = h - y; + sum = t; + } + + /** + * Subtracts another summator. + */ + void sub(/* const */KahanSummator v) { + double y = -(v.getResult() - v.compensation) - compensation; + double t = sum + y; + double h = t - sum; + compensation = h - y; + sum = t; + } + + /** + * Returns current value of the sum. + */ + double getResult() /* const */ { + return startValue + sum; + } + + KahanSummator plusEquals(double v) { + add(v); + return this; + } + + KahanSummator minusEquals(double v) { + add(-v); + return this; + } + + KahanSummator plusEquals(/* const */KahanSummator v) { + add(v); + return this; + } + + KahanSummator minusEquals(/* const */KahanSummator v) { + sub(v); + return this; + } + } + + /** + * Returns one value with the sign of another (like copysign). + */ + static double copySign(double x, double y) { + return y >= 0.0 ? Math.abs(x) : -Math.abs(x); + } + + /** + * Calculates sign of the given value. Returns 0 if the value is equal to 0. + */ + static int sign(double value) { + return value < 0 ? -1 : (value > 0) ? 1 : 0; + } + + /** + * Rounds towards zero. + */ + static double truncate(double v) { + if (v >= 0) + return Math.floor(v); + else + return -Math.floor(-v); + } + + /** + * C fmod function. + */ + static double FMod(double x, double y) { + return x - truncate(x / y) * y; + } + + + /** + * Rounds double to the closest integer value. + */ + static double round(double v) { + return Math.floor(v + 0.5); + } + + static double sqr(double v) { + return v * v; + } + + /** + * Computes interpolation between two values, using the interpolation factor t. + * The interpolation formula is (end - start) * t + start. + * However, the computation ensures that t = 0 produces exactly start, and t = 1, produces exactly end. + * It also guarantees that for 0 <= t <= 1, the interpolated value v is between start and end. + */ + static double lerp(double start_, double end_, double t) { + // When end == start, we want result to be equal to start, for all t + // values. At the same time, when end != start, we want the result to be + // equal to start for t==0 and end for t == 1.0 + // The regular formula end_ * t + (1.0 - t) * start_, when end_ == + // start_, and t at 1/3, produces value different from start + double v; + if (t <= 0.5) + v = start_ + (end_ - start_) * t; + else + v = end_ - (end_ - start_) * (1.0 - t); + + assert (t < 0 || t > 1.0 || (v >= start_ && v <= end_) || (v <= start_ && v >= end_) || NumberUtils.isNaN(start_) || NumberUtils.isNaN(end_)); + return v; + } + + /** + * Computes interpolation between two values, using the interpolation factor t. + * The interpolation formula is (end - start) * t + start. + * However, the computation ensures that t = 0 produces exactly start, and t = 1, produces exactly end. + * It also guarantees that for 0 <= t <= 1, the interpolated value v is between start and end. + */ + static void lerp(Point2D start_, Point2D end_, double t, Point2D result) { + assert (start_ != result); + // When end == start, we want result to be equal to start, for all t + // values. At the same time, when end != start, we want the result to be + // equal to start for t==0 and end for t == 1.0 + // The regular formula end_ * t + (1.0 - t) * start_, when end_ == + // start_, and t at 1/3, produces value different from start + double rx, ry; + if (t <= 0.5) { + rx = start_.x + (end_.x - start_.x) * t; + ry = start_.y + (end_.y - start_.y) * t; + } else { + rx = end_.x - (end_.x - start_.x) * (1.0 - t); + ry = end_.y - (end_.y - start_.y) * (1.0 - t); + } + + assert (t < 0 || t > 1.0 || (rx >= start_.x && rx <= end_.x) || (rx <= start_.x && rx >= end_.x)); + assert (t < 0 || t > 1.0 || (ry >= start_.y && ry <= end_.y) || (ry <= start_.y && ry >= end_.y)); + result.x = rx; + result.y = ry; + } + + static void lerp(double start_x, double start_y, double end_x, double end_y, double t, Point2D result) { + // When end == start, we want result to be equal to start, for all t + // values. At the same time, when end != start, we want the result to be + // equal to start for t==0 and end for t == 1.0 + // The regular formula end_ * t + (1.0 - t) * start_, when end_ == + // start_, and t at 1/3, produces value different from start + if (t <= 0.5) { + result.x = start_x + (end_x - start_x) * t; + result.y = start_y + (end_y - start_y) * t; + } else { + result.x = end_x - (end_x - start_x) * (1.0 - t); + result.y = end_y - (end_y - start_y) * (1.0 - t); + } + + assert (t < 0 || t > 1.0 || (result.x >= start_x && result.x <= end_x) || (result.x <= start_x && result.x >= end_x)); + assert (t < 0 || t > 1.0 || (result.y >= start_y && result.y <= end_y) || (result.y <= start_y && result.y >= end_y)); + } } diff --git a/src/main/java/com/esri/core/geometry/MgrsConversionMode.java b/src/main/java/com/esri/core/geometry/MgrsConversionMode.java index cc47215e..7542f078 100644 --- a/src/main/java/com/esri/core/geometry/MgrsConversionMode.java +++ b/src/main/java/com/esri/core/geometry/MgrsConversionMode.java @@ -29,32 +29,32 @@ * notation and coordinates. */ public interface MgrsConversionMode { - /** - * Uses the spheroid to determine the military grid string. - */ - public static final int mgrsAutomatic = 0;// PE_MGRS_STYLE_AUTO - /** - * Treats all spheroids as new, like WGS 1984, when creating or reading a - * military grid string. The 180 longitude falls into zone 60. - */ - public static final int mgrsNewStyle = 0x100; // PE_MGRS_STYLE_NEW - /** - * Treats all spheroids as old, like Bessel 1841, when creating or reading a - * military grid string. The 180 longitude falls into zone 60. - */ - public static final int mgrsOldStyle = 0x200; // PE_MGRS_STYLE_OLD - /** - * Treats all spheroids as new, like WGS 1984, when creating or reading a - * military grid string. The 180 longitude falls into zone 01. - */ - public static final int mgrsNewWith180InZone01 = 0x1000 + 0x100; // PE_MGRS_180_ZONE_1_PLUS - // | - // PE_MGRS_STYLE_NEW - /** - * Treats all spheroids as old, like Bessel 1841, when creating or reading a - * military grid string. The 180 longitude falls into zone 01. - */ - public static final int mgrsOldWith180InZone01 = 0x1000 + 0x200; // PE_MGRS_180_ZONE_1_PLUS - // | - // PE_MGRS_STYLE_OLD + /** + * Uses the spheroid to determine the military grid string. + */ + public static final int mgrsAutomatic = 0;// PE_MGRS_STYLE_AUTO + /** + * Treats all spheroids as new, like WGS 1984, when creating or reading a + * military grid string. The 180 longitude falls into zone 60. + */ + public static final int mgrsNewStyle = 0x100; // PE_MGRS_STYLE_NEW + /** + * Treats all spheroids as old, like Bessel 1841, when creating or reading a + * military grid string. The 180 longitude falls into zone 60. + */ + public static final int mgrsOldStyle = 0x200; // PE_MGRS_STYLE_OLD + /** + * Treats all spheroids as new, like WGS 1984, when creating or reading a + * military grid string. The 180 longitude falls into zone 01. + */ + public static final int mgrsNewWith180InZone01 = 0x1000 + 0x100; // PE_MGRS_180_ZONE_1_PLUS + // | + // PE_MGRS_STYLE_NEW + /** + * Treats all spheroids as old, like Bessel 1841, when creating or reading a + * military grid string. The 180 longitude falls into zone 01. + */ + public static final int mgrsOldWith180InZone01 = 0x1000 + 0x200; // PE_MGRS_180_ZONE_1_PLUS + // | + // PE_MGRS_STYLE_OLD } diff --git a/src/main/java/com/esri/core/geometry/MultiPath.java b/src/main/java/com/esri/core/geometry/MultiPath.java index 8d7be36b..3f320725 100644 --- a/src/main/java/com/esri/core/geometry/MultiPath.java +++ b/src/main/java/com/esri/core/geometry/MultiPath.java @@ -30,687 +30,687 @@ * The MulitPath class is a base class for polygons and polylines. */ public abstract class MultiPath extends MultiVertexGeometry implements - Serializable { - MultiPathImpl m_impl; - - @Override - public VertexDescription getDescription() { - return m_impl.getDescription(); - } - - @Override - public void assignVertexDescription(VertexDescription src) { - m_impl.assignVertexDescription(src); - } - - @Override - public void mergeVertexDescription(VertexDescription src) { - m_impl.mergeVertexDescription(src); - } - - @Override - public void addAttribute(int semantics) { - m_impl.addAttribute(semantics); - } - - @Override - public void dropAttribute(int semantics) { - m_impl.dropAttribute(semantics); - } - - @Override - public void dropAllAttributes() { - m_impl.dropAllAttributes(); - } - - @Override - public int getPointCount() { - return m_impl.getPointCount(); - } - - @Override - public Point getPoint(int index) { - return m_impl.getPoint(index); - } - - @Override - public void setPoint(int index, Point point) { - m_impl.setPoint(index, point); - } - - @Override - public boolean isEmpty() { - return m_impl.isEmptyImpl(); - } - - @Override - public double calculateArea2D() { - return m_impl.calculateArea2D(); - } - - @Override - public double calculateLength2D() { - return m_impl.calculateLength2D(); - } - - public double calculatePathLength2D(int pathIndex) { - return m_impl.calculatePathLength2D(pathIndex); - } - - @Override - public double getAttributeAsDbl(int semantics, int index, int ordinate) { - return m_impl.getAttributeAsDbl(semantics, index, ordinate); - } - - @Override - public int getAttributeAsInt(int semantics, int index, int ordinate) { - return m_impl.getAttributeAsInt(semantics, index, ordinate); - } - - @Override - public void setAttribute(int semantics, int index, int ordinate, - double value) { - m_impl.setAttribute(semantics, index, ordinate, value); - } - - @Override - public void setAttribute(int semantics, int index, int ordinate, int value) { - m_impl.setAttribute(semantics, index, ordinate, value); - } - - @Override - public Point2D getXY(int index) { - return m_impl.getXY(index); - } - - @Override - public void getXY(int index, Point2D pt) { - m_impl.getXY(index, pt); - } - - @Override - public void setXY(int index, Point2D pt) { - m_impl.setXY(index, pt); - } - - @Override - Point3D getXYZ(int index) { - return m_impl.getXYZ(index); - } - - @Override - void setXYZ(int index, Point3D pt) { - m_impl.setXYZ(index, pt); - } - - @Override - public void queryEnvelope(Envelope env) { - m_impl.queryEnvelope(env); - } - - @Override - public void queryEnvelope2D(Envelope2D env) { - m_impl.queryEnvelope2D(env); - } - - public void queryPathEnvelope2D(int pathIndex, Envelope2D env) { - m_impl.queryPathEnvelope2D(pathIndex, env); - } - - @Override - void queryEnvelope3D(Envelope3D env) { - m_impl.queryEnvelope3D(env); - } - - public void queryLooseEnvelope(Envelope2D env) { - m_impl.queryLooseEnvelope2D(env); - } - - void queryLooseEnvelope(Envelope3D env) { - m_impl.queryLooseEnvelope3D(env); - } - - @Override - public Envelope1D queryInterval(int semantics, int ordinate) { - return m_impl.queryInterval(semantics, ordinate); - } - - @Override - public void copyTo(Geometry dst) { - if (getType() != dst.getType()) - throw new IllegalArgumentException(); - - m_impl.copyTo((Geometry) dst._getImpl()); - } - - @Override - public Geometry getBoundary() { - return m_impl.getBoundary(); - } - - @Override - public void queryCoordinates(Point2D[] dst) { - m_impl.queryCoordinates(dst); - } - - public void queryCoordinates(Point2D[] dst, int dstSize, int beginIndex, int endIndex) { - m_impl.queryCoordinates(dst, dstSize, beginIndex, endIndex); - } - - @Override - void queryCoordinates(Point3D[] dst) { - m_impl.queryCoordinates(dst); - } - - @Override - public void queryCoordinates(Point[] dst) { - m_impl.queryCoordinates(dst); - - } - - /** - * Returns TRUE if the multipath contains non-linear segments. - */ - boolean hasNonLinearSegments() { - return m_impl.hasNonLinearSegments(); - } - - /** - * Returns total segment count in the MultiPath. - */ - public int getSegmentCount() { - return m_impl.getSegmentCount(); - } - - /** - * Returns the segment count in the given multipath path. - * - * @param pathIndex The path to determine the segment. - * @return The segment of the multipath. - */ - public int getSegmentCount(int pathIndex) { - int segCount = getPathSize(pathIndex); - if (!isClosedPath(pathIndex)) - segCount--; - return segCount; - } - - /** - * Appends all paths from another multipath. - * - * @param src The multipath to append to this multipath. - * @param bReversePaths TRUE if the multipath is added should be added with its paths - * reversed. - */ - public void add(MultiPath src, boolean bReversePaths) { - m_impl.add((MultiPathImpl) src._getImpl(), bReversePaths); - } - - /** - * Copies a path from another multipath. - * - * @param src The multipath to copy from. - * @param srcPathIndex The index of the path in the the source MultiPath. - * @param bForward When FALSE, the points are inserted in reverse order. - */ - public void addPath(MultiPath src, int srcPathIndex, boolean bForward) { - m_impl.addPath((MultiPathImpl) src._getImpl(), srcPathIndex, bForward); - } - - /** - * Adds a new path to this multipath. - * - * @param points The array of points to add to this multipath. - * @param count The number of points added to the mulitpath. - * @param bForward When FALSE, the points are inserted in reverse order. - */ - void addPath(Point2D[] points, int count, boolean bForward) { - m_impl.addPath(points, count, bForward); - } - - /** - * Adds segments from a source multipath to this MultiPath. - * - * @param src The source MultiPath to add segments from. - * @param srcPathIndex The index of the path in the the source MultiPath. - * @param srcSegmentFrom The index of first segment in the path to start adding from. - * The value has to be between 0 and - * src.getSegmentCount(srcPathIndex) - 1. - * @param srcSegmentCount The number of segments to add. If 0, the function does - * nothing. - * @param bStartNewPath When true, a new path is added and segments are added to it. - * Otherwise the segments are added to the last path of this - * MultiPath. - *

- * If bStartNewPath false, the first point of the first source - * segment is not added. This is done to ensure proper connection - * to existing segments. When the source path is closed, and the - * closing segment is among those to be added, it is added also - * as a closing segment, not as a real segment. Use add_segment - * instead if you do not like that behavior. - *

- * This MultiPath obtains all missing attributes from the src - * MultiPath. - */ - public void addSegmentsFromPath(MultiPath src, int srcPathIndex, - int srcSegmentFrom, int srcSegmentCount, boolean bStartNewPath) { - m_impl.addSegmentsFromPath((MultiPathImpl) src._getImpl(), - srcPathIndex, srcSegmentFrom, srcSegmentCount, bStartNewPath); - } - - /** - * Adds a new segment to this multipath. - * - * @param segment The segment to be added to this mulitpath. - * @param bStartNewPath TRUE if a new path will be added. - */ - public void addSegment(Segment segment, boolean bStartNewPath) { - m_impl.addSegment(segment, bStartNewPath); - } - - /** - * Reverses the order of the vertices in each path. - */ - public void reverseAllPaths() { - m_impl.reverseAllPaths(); - } - - /** - * Reverses the order of vertices in the path. - * - * @param pathIndex The start index of the path to reverse the order. - */ - public void reversePath(int pathIndex) { - m_impl.reversePath(pathIndex); - } - - /** - * Removes the path at the given index. - * - * @param pathIndex The start index to remove the path. - */ - public void removePath(int pathIndex) { - m_impl.removePath(pathIndex); - } - - /** - * Inserts a path from another multipath. - * - * @param pathIndex The start index of the multipath to insert. - * @param src The multipath to insert into this multipath. Can be the same - * as the multipath being modified. - * @param srcPathIndex The start index to insert the path into the multipath. - * @param bForward When FALSE, the points are inserted in reverse order. - */ - public void insertPath(int pathIndex, MultiPath src, int srcPathIndex, - boolean bForward) { - m_impl.insertPath(pathIndex, (MultiPathImpl) src._getImpl(), - srcPathIndex, bForward); - } - - /** - * Inserts a path from an array of 2D Points. - * - * @param pathIndex The path index of the multipath to place the new path. - * @param points The array of points defining the new path. - * @param pointsOffset The offset into the array to start reading. - * @param count The number of points to insert into the new path. - * @param bForward When FALSE, the points are inserted in reverse order. - */ - void insertPath(int pathIndex, Point2D[] points, int pointsOffset, - int count, boolean bForward) { - m_impl.insertPath(pathIndex, points, pointsOffset, count, bForward); - } - - /** - * Inserts vertices from the given multipath into this multipath. All added - * vertices are connected by linear segments with each other and with the - * existing vertices. - * - * @param pathIndex The path index in this multipath to insert points to. Must - * correspond to an existing path. - * @param beforePointIndex The point index before all other vertices to insert in the - * given path of this multipath. This value must be between 0 and - * GetPathSize(pathIndex), or -1 to insert points at the end of - * the given path. - * @param src The source multipath. - * @param srcPathIndex The source path index to copy points from. - * @param srcPointIndexFrom The start point in the source path to start copying from. - * @param srcPointCount The count of points to add. - * @param bForward When FALSE, the points are inserted in reverse order. - */ - public void insertPoints(int pathIndex, int beforePointIndex, - MultiPath src, int srcPathIndex, int srcPointIndexFrom, - int srcPointCount, boolean bForward) { - m_impl.insertPoints(pathIndex, beforePointIndex, - (MultiPathImpl) src._getImpl(), srcPathIndex, - srcPointIndexFrom, srcPointCount, bForward); - } - - /** - * Inserts a part of a path from the given array. - * - * @param pathIndex The path index in this class to insert points to. Must - * correspond to an existing path. - * @param beforePointIndex The point index in the given path of this MultiPath before - * which the vertices need to be inserted. This value must be - * between 0 and GetPathSize(pathIndex), or -1 to insert points - * at the end of the given path. - * @param src The source array - * @param srcPointIndexFrom The start point in the source array to start copying from. - * @param srcPointCount The count of points to add. - * @param bForward When FALSE, the points are inserted in reverse order. - */ - void insertPoints(int pathIndex, int beforePointIndex, Point2D[] src, - int srcPointIndexFrom, int srcPointCount, boolean bForward) { - m_impl.insertPoints(pathIndex, beforePointIndex, src, - srcPointIndexFrom, srcPointCount, bForward); - } - - /** - * Inserts a point. - * - * @param pathIndex The path index in this class to insert the point to. Must - * correspond to an existing path. - * @param beforePointIndex The point index in the given path of this multipath. This - * value must be between 0 and GetPathSize(pathIndex), or -1 to - * insert the point at the end of the given path. - * @param pt The point to be inserted. - */ - void insertPoint(int pathIndex, int beforePointIndex, Point2D pt) { - m_impl.insertPoint(pathIndex, beforePointIndex, pt); - } - - /** - * Inserts a point. - * - * @param pathIndex The path index in this class to insert the point to. Must - * correspond to an existing path. - * @param beforePointIndex The point index in the given path of this multipath. This - * value must be between 0 and GetPathSize(pathIndex), or -1 to - * insert the point at the end of the given path. - * @param pt The point to be inserted. - */ - public void insertPoint(int pathIndex, int beforePointIndex, Point pt) { - m_impl.insertPoint(pathIndex, beforePointIndex, pt); - } - - /** - * Removes a point at a given index. - * - * @param pathIndex The path from whom to remove the point. - * @param pointIndex The index of the point to be removed. - */ - public void removePoint(int pathIndex, int pointIndex) { - m_impl.removePoint(pathIndex, pointIndex); - } - - /** - * Returns the number of paths in this multipath. - * - * @return The number of paths in this multipath. - */ - public int getPathCount() { - return m_impl.getPathCount(); - } - - /** - * Returns the number of vertices in a path. - * - * @param pathIndex The index of the path to return the number of vertices from. - * @return The number of vertices in a path. - */ - public int getPathSize(int pathIndex) { - return m_impl.getPathSize(pathIndex); - } - - /** - * Returns the start index of the path. - * - * @param pathIndex The index of the path to return the start index from. - * @return The start index of the path. - */ - public int getPathStart(int pathIndex) { - return m_impl.getPathStart(pathIndex); - } - - /** - * Returns the index immediately following the last index of the path. - * - * @param pathIndex The index of the path to return the end index from. - * @return Integer index after last index of path - */ - public int getPathEnd(int pathIndex) { - return m_impl.getPathEnd(pathIndex); - } - - /** - * Returns the path index from the point index. This is O(log N) operation. - * - * @param pointIndex The index of the point. - * @return The index of the path. - */ - public int getPathIndexFromPointIndex(int pointIndex) { - return m_impl.getPathIndexFromPointIndex(pointIndex); - } - - /** - * Starts a new path at given coordinates. - * - * @param x The X coordinate of the start point. - * @param y The Y coordinate of the start point. - */ - public void startPath(double x, double y) { - m_impl.startPath(x, y); - } - - void startPath(Point2D point) { - m_impl.startPath(point); - } - - void startPath(Point3D point) { - m_impl.startPath(point); - } - - /** - * Starts a new path at a point. - * - * @param point The point to start the path from. - */ - public void startPath(Point point) { - m_impl.startPath(point); - } - - /** - * Adds a line segment from the last point to the given end coordinates. - * - * @param x The X coordinate to the end point. - * @param y The Y coordinate to the end point. - */ - public void lineTo(double x, double y) { - m_impl.lineTo(x, y); - } - - void lineTo(Point2D endPoint) { - m_impl.lineTo(endPoint); - } - - void lineTo(Point3D endPoint) { - m_impl.lineTo(endPoint); - } - - /** - * Adds a Line Segment to the given end point. - * - * @param endPoint The end point to which the newly added line segment should - * point. - */ - public void lineTo(Point endPoint) { - m_impl.lineTo(endPoint); - } - - /** - * Adds a Cubic Bezier Segment to the current Path. The Bezier Segment - * connects the current last Point and the given endPoint. - */ - void bezierTo(Point2D controlPoint1, Point2D controlPoint2, Point2D endPoint) { - m_impl.bezierTo(controlPoint1, controlPoint2, endPoint); - } - - /** - * Closes the last path of this multipath with a line segment. The closing - * segment is a segment that connects the last and the first points of the - * path. This is a virtual segment. The first point is not duplicated to - * close the path. - *

- * Call this method only for polylines. For polygons this method is - * implicitly called for the Polygon class. - */ - public void closePathWithLine() { - m_impl.closePathWithLine(); - } - - /** - * Closes last path of the MultiPath with the Bezier Segment. - *

- * The start point of the Bezier is the last point of the path and the last - * point of the bezier is the first point of the path. - */ - void closePathWithBezier(Point2D controlPoint1, Point2D controlPoint2) { - m_impl.closePathWithBezier(controlPoint1, controlPoint2); - } - - /** - * Closes last path of the MultiPath with the Arc Segment. - */ - void closePathWithArc() { - throw new RuntimeException("not implemented"); - } - - /** - * Closes all open paths by adding an implicit line segment from the end - * point to the start point. Call this method only for polylines.For - * polygons this method is implicitly called for the Polygon class. - */ - public void closeAllPaths() { - m_impl.closeAllPaths(); - } - - /** - * Indicates if the given path is closed (represents a ring). A closed path - * has a virtual segment that connects the last and the first points of the - * path. The first point is not duplicated to close the path. Polygons - * always have all paths closed. - * - * @param pathIndex The index of the path to check to be closed. - * @return TRUE if the given path is closed (represents a Ring). - */ - public boolean isClosedPath(int pathIndex) { - return m_impl.isClosedPath(pathIndex); - } - - public boolean isClosedPathInXYPlane(int pathIndex) { - return m_impl.isClosedPathInXYPlane(pathIndex); - } - - /** - * Returns TRUE if the given path might have non-linear segments. - */ - boolean hasNonLinearSegments(int pathIndex) { - return m_impl.hasNonLinearSegments(pathIndex); - } - - /** - * Adds a rectangular closed Path to the MultiPathImpl. - * - * @param envSrc is the source rectangle. - * @param bReverse Creates reversed path. - */ - public void addEnvelope(Envelope2D envSrc, boolean bReverse) { - m_impl.addEnvelope(envSrc, bReverse); - } - - /** - * Adds a rectangular closed path to this multipath. - * - * @param envSrc Is the envelope to add to this mulitpath. - * @param bReverse Adds the path reversed (counter-clockwise). - */ - public void addEnvelope(Envelope envSrc, boolean bReverse) { - m_impl.addEnvelope(envSrc, bReverse); - } - - /** - * Returns a SegmentIterator that is set right before the beginning of the - * multipath. Calling nextPath() will set the iterator to the first path of - * this multipath. - * - * @return The SegmentIterator for this mulitpath. - */ - public SegmentIterator querySegmentIterator() { - return new SegmentIterator(m_impl.querySegmentIterator()); - } - - /** - * Returns a SegmentIterator that is set to a specific vertex of the - * MultiPath. The call to nextSegment() will return the segment that starts - * at the vertex. Calling PreviousSegment () will return the segment that - * starts at the previous vertex. - * - * @param startVertexIndex The start index of the SegementIterator. - * @return The SegmentIterator for this mulitpath at the specified vertex. - */ - public SegmentIterator querySegmentIteratorAtVertex(int startVertexIndex) { - return new SegmentIterator( - m_impl.querySegmentIteratorAtVertex(startVertexIndex)); - } - - @Override - public void setEmpty() { - m_impl.setEmpty(); - } - - @Override - public void applyTransformation(Transformation2D transform) { - m_impl.applyTransformation(transform); - } - - @Override - void applyTransformation(Transformation3D transform) { - m_impl.applyTransformation(transform); - } - - @Override - protected Object _getImpl() { - return m_impl; - } - - /** - * Returns the hash code for the multipath. - */ - @Override - public int hashCode() { - return m_impl.hashCode(); - } - - @Override - public void getPointByVal(int index, Point outPoint) { - m_impl.getPointByVal(index, outPoint); - } - - @Override - public void setPointByVal(int index, Point point) { - m_impl.setPointByVal(index, point); - } - - @Override - public int getStateFlag() { - return m_impl.getStateFlag(); - } - - @Override - public void replaceNaNs(int semantics, double value) { - m_impl.replaceNaNs(semantics, value); - } + Serializable { + MultiPathImpl m_impl; + + @Override + public VertexDescription getDescription() { + return m_impl.getDescription(); + } + + @Override + public void assignVertexDescription(VertexDescription src) { + m_impl.assignVertexDescription(src); + } + + @Override + public void mergeVertexDescription(VertexDescription src) { + m_impl.mergeVertexDescription(src); + } + + @Override + public void addAttribute(int semantics) { + m_impl.addAttribute(semantics); + } + + @Override + public void dropAttribute(int semantics) { + m_impl.dropAttribute(semantics); + } + + @Override + public void dropAllAttributes() { + m_impl.dropAllAttributes(); + } + + @Override + public int getPointCount() { + return m_impl.getPointCount(); + } + + @Override + public Point getPoint(int index) { + return m_impl.getPoint(index); + } + + @Override + public void setPoint(int index, Point point) { + m_impl.setPoint(index, point); + } + + @Override + public boolean isEmpty() { + return m_impl.isEmptyImpl(); + } + + @Override + public double calculateArea2D() { + return m_impl.calculateArea2D(); + } + + @Override + public double calculateLength2D() { + return m_impl.calculateLength2D(); + } + + public double calculatePathLength2D(int pathIndex) { + return m_impl.calculatePathLength2D(pathIndex); + } + + @Override + public double getAttributeAsDbl(int semantics, int index, int ordinate) { + return m_impl.getAttributeAsDbl(semantics, index, ordinate); + } + + @Override + public int getAttributeAsInt(int semantics, int index, int ordinate) { + return m_impl.getAttributeAsInt(semantics, index, ordinate); + } + + @Override + public void setAttribute(int semantics, int index, int ordinate, + double value) { + m_impl.setAttribute(semantics, index, ordinate, value); + } + + @Override + public void setAttribute(int semantics, int index, int ordinate, int value) { + m_impl.setAttribute(semantics, index, ordinate, value); + } + + @Override + public Point2D getXY(int index) { + return m_impl.getXY(index); + } + + @Override + public void getXY(int index, Point2D pt) { + m_impl.getXY(index, pt); + } + + @Override + public void setXY(int index, Point2D pt) { + m_impl.setXY(index, pt); + } + + @Override + Point3D getXYZ(int index) { + return m_impl.getXYZ(index); + } + + @Override + void setXYZ(int index, Point3D pt) { + m_impl.setXYZ(index, pt); + } + + @Override + public void queryEnvelope(Envelope env) { + m_impl.queryEnvelope(env); + } + + @Override + public void queryEnvelope2D(Envelope2D env) { + m_impl.queryEnvelope2D(env); + } + + public void queryPathEnvelope2D(int pathIndex, Envelope2D env) { + m_impl.queryPathEnvelope2D(pathIndex, env); + } + + @Override + void queryEnvelope3D(Envelope3D env) { + m_impl.queryEnvelope3D(env); + } + + public void queryLooseEnvelope(Envelope2D env) { + m_impl.queryLooseEnvelope2D(env); + } + + void queryLooseEnvelope(Envelope3D env) { + m_impl.queryLooseEnvelope3D(env); + } + + @Override + public Envelope1D queryInterval(int semantics, int ordinate) { + return m_impl.queryInterval(semantics, ordinate); + } + + @Override + public void copyTo(Geometry dst) { + if (getType() != dst.getType()) + throw new IllegalArgumentException(); + + m_impl.copyTo((Geometry) dst._getImpl()); + } + + @Override + public Geometry getBoundary() { + return m_impl.getBoundary(); + } + + @Override + public void queryCoordinates(Point2D[] dst) { + m_impl.queryCoordinates(dst); + } + + public void queryCoordinates(Point2D[] dst, int dstSize, int beginIndex, int endIndex) { + m_impl.queryCoordinates(dst, dstSize, beginIndex, endIndex); + } + + @Override + void queryCoordinates(Point3D[] dst) { + m_impl.queryCoordinates(dst); + } + + @Override + public void queryCoordinates(Point[] dst) { + m_impl.queryCoordinates(dst); + + } + + /** + * Returns TRUE if the multipath contains non-linear segments. + */ + boolean hasNonLinearSegments() { + return m_impl.hasNonLinearSegments(); + } + + /** + * Returns total segment count in the MultiPath. + */ + public int getSegmentCount() { + return m_impl.getSegmentCount(); + } + + /** + * Returns the segment count in the given multipath path. + * + * @param pathIndex The path to determine the segment. + * @return The segment of the multipath. + */ + public int getSegmentCount(int pathIndex) { + int segCount = getPathSize(pathIndex); + if (!isClosedPath(pathIndex)) + segCount--; + return segCount; + } + + /** + * Appends all paths from another multipath. + * + * @param src The multipath to append to this multipath. + * @param bReversePaths TRUE if the multipath is added should be added with its paths + * reversed. + */ + public void add(MultiPath src, boolean bReversePaths) { + m_impl.add((MultiPathImpl) src._getImpl(), bReversePaths); + } + + /** + * Copies a path from another multipath. + * + * @param src The multipath to copy from. + * @param srcPathIndex The index of the path in the the source MultiPath. + * @param bForward When FALSE, the points are inserted in reverse order. + */ + public void addPath(MultiPath src, int srcPathIndex, boolean bForward) { + m_impl.addPath((MultiPathImpl) src._getImpl(), srcPathIndex, bForward); + } + + /** + * Adds a new path to this multipath. + * + * @param points The array of points to add to this multipath. + * @param count The number of points added to the mulitpath. + * @param bForward When FALSE, the points are inserted in reverse order. + */ + void addPath(Point2D[] points, int count, boolean bForward) { + m_impl.addPath(points, count, bForward); + } + + /** + * Adds segments from a source multipath to this MultiPath. + * + * @param src The source MultiPath to add segments from. + * @param srcPathIndex The index of the path in the the source MultiPath. + * @param srcSegmentFrom The index of first segment in the path to start adding from. + * The value has to be between 0 and + * src.getSegmentCount(srcPathIndex) - 1. + * @param srcSegmentCount The number of segments to add. If 0, the function does + * nothing. + * @param bStartNewPath When true, a new path is added and segments are added to it. + * Otherwise the segments are added to the last path of this + * MultiPath. + *

+ * If bStartNewPath false, the first point of the first source + * segment is not added. This is done to ensure proper connection + * to existing segments. When the source path is closed, and the + * closing segment is among those to be added, it is added also + * as a closing segment, not as a real segment. Use add_segment + * instead if you do not like that behavior. + *

+ * This MultiPath obtains all missing attributes from the src + * MultiPath. + */ + public void addSegmentsFromPath(MultiPath src, int srcPathIndex, + int srcSegmentFrom, int srcSegmentCount, boolean bStartNewPath) { + m_impl.addSegmentsFromPath((MultiPathImpl) src._getImpl(), + srcPathIndex, srcSegmentFrom, srcSegmentCount, bStartNewPath); + } + + /** + * Adds a new segment to this multipath. + * + * @param segment The segment to be added to this mulitpath. + * @param bStartNewPath TRUE if a new path will be added. + */ + public void addSegment(Segment segment, boolean bStartNewPath) { + m_impl.addSegment(segment, bStartNewPath); + } + + /** + * Reverses the order of the vertices in each path. + */ + public void reverseAllPaths() { + m_impl.reverseAllPaths(); + } + + /** + * Reverses the order of vertices in the path. + * + * @param pathIndex The start index of the path to reverse the order. + */ + public void reversePath(int pathIndex) { + m_impl.reversePath(pathIndex); + } + + /** + * Removes the path at the given index. + * + * @param pathIndex The start index to remove the path. + */ + public void removePath(int pathIndex) { + m_impl.removePath(pathIndex); + } + + /** + * Inserts a path from another multipath. + * + * @param pathIndex The start index of the multipath to insert. + * @param src The multipath to insert into this multipath. Can be the same + * as the multipath being modified. + * @param srcPathIndex The start index to insert the path into the multipath. + * @param bForward When FALSE, the points are inserted in reverse order. + */ + public void insertPath(int pathIndex, MultiPath src, int srcPathIndex, + boolean bForward) { + m_impl.insertPath(pathIndex, (MultiPathImpl) src._getImpl(), + srcPathIndex, bForward); + } + + /** + * Inserts a path from an array of 2D Points. + * + * @param pathIndex The path index of the multipath to place the new path. + * @param points The array of points defining the new path. + * @param pointsOffset The offset into the array to start reading. + * @param count The number of points to insert into the new path. + * @param bForward When FALSE, the points are inserted in reverse order. + */ + void insertPath(int pathIndex, Point2D[] points, int pointsOffset, + int count, boolean bForward) { + m_impl.insertPath(pathIndex, points, pointsOffset, count, bForward); + } + + /** + * Inserts vertices from the given multipath into this multipath. All added + * vertices are connected by linear segments with each other and with the + * existing vertices. + * + * @param pathIndex The path index in this multipath to insert points to. Must + * correspond to an existing path. + * @param beforePointIndex The point index before all other vertices to insert in the + * given path of this multipath. This value must be between 0 and + * GetPathSize(pathIndex), or -1 to insert points at the end of + * the given path. + * @param src The source multipath. + * @param srcPathIndex The source path index to copy points from. + * @param srcPointIndexFrom The start point in the source path to start copying from. + * @param srcPointCount The count of points to add. + * @param bForward When FALSE, the points are inserted in reverse order. + */ + public void insertPoints(int pathIndex, int beforePointIndex, + MultiPath src, int srcPathIndex, int srcPointIndexFrom, + int srcPointCount, boolean bForward) { + m_impl.insertPoints(pathIndex, beforePointIndex, + (MultiPathImpl) src._getImpl(), srcPathIndex, + srcPointIndexFrom, srcPointCount, bForward); + } + + /** + * Inserts a part of a path from the given array. + * + * @param pathIndex The path index in this class to insert points to. Must + * correspond to an existing path. + * @param beforePointIndex The point index in the given path of this MultiPath before + * which the vertices need to be inserted. This value must be + * between 0 and GetPathSize(pathIndex), or -1 to insert points + * at the end of the given path. + * @param src The source array + * @param srcPointIndexFrom The start point in the source array to start copying from. + * @param srcPointCount The count of points to add. + * @param bForward When FALSE, the points are inserted in reverse order. + */ + void insertPoints(int pathIndex, int beforePointIndex, Point2D[] src, + int srcPointIndexFrom, int srcPointCount, boolean bForward) { + m_impl.insertPoints(pathIndex, beforePointIndex, src, + srcPointIndexFrom, srcPointCount, bForward); + } + + /** + * Inserts a point. + * + * @param pathIndex The path index in this class to insert the point to. Must + * correspond to an existing path. + * @param beforePointIndex The point index in the given path of this multipath. This + * value must be between 0 and GetPathSize(pathIndex), or -1 to + * insert the point at the end of the given path. + * @param pt The point to be inserted. + */ + void insertPoint(int pathIndex, int beforePointIndex, Point2D pt) { + m_impl.insertPoint(pathIndex, beforePointIndex, pt); + } + + /** + * Inserts a point. + * + * @param pathIndex The path index in this class to insert the point to. Must + * correspond to an existing path. + * @param beforePointIndex The point index in the given path of this multipath. This + * value must be between 0 and GetPathSize(pathIndex), or -1 to + * insert the point at the end of the given path. + * @param pt The point to be inserted. + */ + public void insertPoint(int pathIndex, int beforePointIndex, Point pt) { + m_impl.insertPoint(pathIndex, beforePointIndex, pt); + } + + /** + * Removes a point at a given index. + * + * @param pathIndex The path from whom to remove the point. + * @param pointIndex The index of the point to be removed. + */ + public void removePoint(int pathIndex, int pointIndex) { + m_impl.removePoint(pathIndex, pointIndex); + } + + /** + * Returns the number of paths in this multipath. + * + * @return The number of paths in this multipath. + */ + public int getPathCount() { + return m_impl.getPathCount(); + } + + /** + * Returns the number of vertices in a path. + * + * @param pathIndex The index of the path to return the number of vertices from. + * @return The number of vertices in a path. + */ + public int getPathSize(int pathIndex) { + return m_impl.getPathSize(pathIndex); + } + + /** + * Returns the start index of the path. + * + * @param pathIndex The index of the path to return the start index from. + * @return The start index of the path. + */ + public int getPathStart(int pathIndex) { + return m_impl.getPathStart(pathIndex); + } + + /** + * Returns the index immediately following the last index of the path. + * + * @param pathIndex The index of the path to return the end index from. + * @return Integer index after last index of path + */ + public int getPathEnd(int pathIndex) { + return m_impl.getPathEnd(pathIndex); + } + + /** + * Returns the path index from the point index. This is O(log N) operation. + * + * @param pointIndex The index of the point. + * @return The index of the path. + */ + public int getPathIndexFromPointIndex(int pointIndex) { + return m_impl.getPathIndexFromPointIndex(pointIndex); + } + + /** + * Starts a new path at given coordinates. + * + * @param x The X coordinate of the start point. + * @param y The Y coordinate of the start point. + */ + public void startPath(double x, double y) { + m_impl.startPath(x, y); + } + + void startPath(Point2D point) { + m_impl.startPath(point); + } + + void startPath(Point3D point) { + m_impl.startPath(point); + } + + /** + * Starts a new path at a point. + * + * @param point The point to start the path from. + */ + public void startPath(Point point) { + m_impl.startPath(point); + } + + /** + * Adds a line segment from the last point to the given end coordinates. + * + * @param x The X coordinate to the end point. + * @param y The Y coordinate to the end point. + */ + public void lineTo(double x, double y) { + m_impl.lineTo(x, y); + } + + void lineTo(Point2D endPoint) { + m_impl.lineTo(endPoint); + } + + void lineTo(Point3D endPoint) { + m_impl.lineTo(endPoint); + } + + /** + * Adds a Line Segment to the given end point. + * + * @param endPoint The end point to which the newly added line segment should + * point. + */ + public void lineTo(Point endPoint) { + m_impl.lineTo(endPoint); + } + + /** + * Adds a Cubic Bezier Segment to the current Path. The Bezier Segment + * connects the current last Point and the given endPoint. + */ + void bezierTo(Point2D controlPoint1, Point2D controlPoint2, Point2D endPoint) { + m_impl.bezierTo(controlPoint1, controlPoint2, endPoint); + } + + /** + * Closes the last path of this multipath with a line segment. The closing + * segment is a segment that connects the last and the first points of the + * path. This is a virtual segment. The first point is not duplicated to + * close the path. + *

+ * Call this method only for polylines. For polygons this method is + * implicitly called for the Polygon class. + */ + public void closePathWithLine() { + m_impl.closePathWithLine(); + } + + /** + * Closes last path of the MultiPath with the Bezier Segment. + *

+ * The start point of the Bezier is the last point of the path and the last + * point of the bezier is the first point of the path. + */ + void closePathWithBezier(Point2D controlPoint1, Point2D controlPoint2) { + m_impl.closePathWithBezier(controlPoint1, controlPoint2); + } + + /** + * Closes last path of the MultiPath with the Arc Segment. + */ + void closePathWithArc() { + throw new RuntimeException("not implemented"); + } + + /** + * Closes all open paths by adding an implicit line segment from the end + * point to the start point. Call this method only for polylines.For + * polygons this method is implicitly called for the Polygon class. + */ + public void closeAllPaths() { + m_impl.closeAllPaths(); + } + + /** + * Indicates if the given path is closed (represents a ring). A closed path + * has a virtual segment that connects the last and the first points of the + * path. The first point is not duplicated to close the path. Polygons + * always have all paths closed. + * + * @param pathIndex The index of the path to check to be closed. + * @return TRUE if the given path is closed (represents a Ring). + */ + public boolean isClosedPath(int pathIndex) { + return m_impl.isClosedPath(pathIndex); + } + + public boolean isClosedPathInXYPlane(int pathIndex) { + return m_impl.isClosedPathInXYPlane(pathIndex); + } + + /** + * Returns TRUE if the given path might have non-linear segments. + */ + boolean hasNonLinearSegments(int pathIndex) { + return m_impl.hasNonLinearSegments(pathIndex); + } + + /** + * Adds a rectangular closed Path to the MultiPathImpl. + * + * @param envSrc is the source rectangle. + * @param bReverse Creates reversed path. + */ + public void addEnvelope(Envelope2D envSrc, boolean bReverse) { + m_impl.addEnvelope(envSrc, bReverse); + } + + /** + * Adds a rectangular closed path to this multipath. + * + * @param envSrc Is the envelope to add to this mulitpath. + * @param bReverse Adds the path reversed (counter-clockwise). + */ + public void addEnvelope(Envelope envSrc, boolean bReverse) { + m_impl.addEnvelope(envSrc, bReverse); + } + + /** + * Returns a SegmentIterator that is set right before the beginning of the + * multipath. Calling nextPath() will set the iterator to the first path of + * this multipath. + * + * @return The SegmentIterator for this mulitpath. + */ + public SegmentIterator querySegmentIterator() { + return new SegmentIterator(m_impl.querySegmentIterator()); + } + + /** + * Returns a SegmentIterator that is set to a specific vertex of the + * MultiPath. The call to nextSegment() will return the segment that starts + * at the vertex. Calling PreviousSegment () will return the segment that + * starts at the previous vertex. + * + * @param startVertexIndex The start index of the SegementIterator. + * @return The SegmentIterator for this mulitpath at the specified vertex. + */ + public SegmentIterator querySegmentIteratorAtVertex(int startVertexIndex) { + return new SegmentIterator( + m_impl.querySegmentIteratorAtVertex(startVertexIndex)); + } + + @Override + public void setEmpty() { + m_impl.setEmpty(); + } + + @Override + public void applyTransformation(Transformation2D transform) { + m_impl.applyTransformation(transform); + } + + @Override + void applyTransformation(Transformation3D transform) { + m_impl.applyTransformation(transform); + } + + @Override + protected Object _getImpl() { + return m_impl; + } + + /** + * Returns the hash code for the multipath. + */ + @Override + public int hashCode() { + return m_impl.hashCode(); + } + + @Override + public void getPointByVal(int index, Point outPoint) { + m_impl.getPointByVal(index, outPoint); + } + + @Override + public void setPointByVal(int index, Point point) { + m_impl.setPointByVal(index, point); + } + + @Override + public int getStateFlag() { + return m_impl.getStateFlag(); + } + + @Override + public void replaceNaNs(int semantics, double value) { + m_impl.replaceNaNs(semantics, value); + } } diff --git a/src/main/java/com/esri/core/geometry/MultiPathImpl.java b/src/main/java/com/esri/core/geometry/MultiPathImpl.java index a1963aff..2e26317b 100644 --- a/src/main/java/com/esri/core/geometry/MultiPathImpl.java +++ b/src/main/java/com/esri/core/geometry/MultiPathImpl.java @@ -33,44 +33,43 @@ final class MultiPathImpl extends MultiVertexGeometryImpl { protected double m_cachedLength2D; protected double m_cachedArea2D; - protected AttributeStreamOfDbl m_cachedRingAreas2D; - protected boolean m_bPathStarted; - - // Contains starting points of the parts. The size is getPartCount() + 1. - // First element is 0, last element is equal to the getPointCount(). - protected AttributeStreamOfInt32 m_paths; - // same size as m_parts. Holds flags for each part (whether the part is - // closed, etc. See PathFlags) - protected AttributeStreamOfInt8 m_pathFlags; - // The segment flags. Size is getPointCount(). This is not a vertex - // attribute, because we may want to use indexed access later (via an index - // buffer). - // Can be NULL if the MultiPathImpl contains straight lines only. - protected AttributeStreamOfInt8 m_segmentFlags; - // An index into the m_segmentParams stream. Size is getPointCount(). Can be - // NULL if the MultiPathImpl contains straight lines only. - protected AttributeStreamOfInt32 m_segmentParamIndex; - protected AttributeStreamOfDbl m_segmentParams; - protected int m_curveParamwritePoint; - private int m_currentPathIndex; - private int m_fill_rule = Polygon.FillRule.enumFillRuleOddEven; - - static int[] _segmentParamSizes = {0, 0, 6, 0, 8, 0}; // None, Line, - // Bezier, XXX, Arc, - // XXX; + protected AttributeStreamOfDbl m_cachedRingAreas2D; + protected boolean m_bPathStarted; + + // Contains starting points of the parts. The size is getPartCount() + 1. + // First element is 0, last element is equal to the getPointCount(). + protected AttributeStreamOfInt32 m_paths; + // same size as m_parts. Holds flags for each part (whether the part is + // closed, etc. See PathFlags) + protected AttributeStreamOfInt8 m_pathFlags; + // The segment flags. Size is getPointCount(). This is not a vertex + // attribute, because we may want to use indexed access later (via an index + // buffer). + // Can be NULL if the MultiPathImpl contains straight lines only. + protected AttributeStreamOfInt8 m_segmentFlags; + // An index into the m_segmentParams stream. Size is getPointCount(). Can be + // NULL if the MultiPathImpl contains straight lines only. + protected AttributeStreamOfInt32 m_segmentParamIndex; + protected AttributeStreamOfDbl m_segmentParams; + protected int m_curveParamwritePoint; + private int m_currentPathIndex; + private int m_fill_rule = Polygon.FillRule.enumFillRuleOddEven; + + static int[] _segmentParamSizes = {0, 0, 6, 0, 8, 0}; // None, Line, + // Bezier, XXX, Arc, + // XXX; @Override - public long estimateMemorySize() - { + public long estimateMemorySize() { long size = SIZE_OF_MULTI_PATH_IMPL + - + (m_envelope != null ? m_envelope.estimateMemorySize() : 0) - + (m_moveToPoint != null ? m_moveToPoint.estimateMemorySize() : 0) - + (m_cachedRingAreas2D != null ? m_cachedRingAreas2D.estimateMemorySize() : 0) - + m_paths.estimateMemorySize() - + m_pathFlags.estimateMemorySize() - + (m_segmentFlags != null ? m_segmentFlags.estimateMemorySize() : 0) - + (m_segmentParamIndex != null ? m_segmentParamIndex.estimateMemorySize() : 0) - + (m_segmentParams != null ? m_segmentParams.estimateMemorySize() : 0); + +(m_envelope != null ? m_envelope.estimateMemorySize() : 0) + + (m_moveToPoint != null ? m_moveToPoint.estimateMemorySize() : 0) + + (m_cachedRingAreas2D != null ? m_cachedRingAreas2D.estimateMemorySize() : 0) + + m_paths.estimateMemorySize() + + m_pathFlags.estimateMemorySize() + + (m_segmentFlags != null ? m_segmentFlags.estimateMemorySize() : 0) + + (m_segmentParamIndex != null ? m_segmentParamIndex.estimateMemorySize() : 0) + + (m_segmentParams != null ? m_segmentParams.estimateMemorySize() : 0); if (m_vertexAttributes != null) { for (int i = 0; i < m_vertexAttributes.length; i++) { @@ -84,2535 +83,2535 @@ public boolean hasNonLinearSegments() { return m_curveParamwritePoint > 0; } - // / Cpp /// - // Reviewed vs. Native Jan 11, 2011 - public MultiPathImpl(boolean bPolygon) { - m_bPolygon = bPolygon; - - m_bPathStarted = false; - m_curveParamwritePoint = 0; - m_cachedLength2D = 0; - m_cachedArea2D = 0; - m_pointCount = 0; - m_description = VertexDescriptionDesignerImpl.getDefaultDescriptor2D(); - m_cachedRingAreas2D = null; - m_currentPathIndex = 0; - } - - // Reviewed vs. Native Jan 11, 2011 - public MultiPathImpl(boolean bPolygon, VertexDescription description) { - if (description == null) - throw new IllegalArgumentException(); - - m_bPolygon = bPolygon; - - m_bPathStarted = false; - m_curveParamwritePoint = 0; - m_cachedLength2D = 0; - m_cachedArea2D = 0; - m_pointCount = 0; - m_description = description; - m_cachedRingAreas2D = null; - m_currentPathIndex = 0; - } - - // Reviewed vs. Native Jan 11, 2011 - protected void _initPathStartPoint() { - _touch(); - if (m_moveToPoint == null) - m_moveToPoint = new Point(m_description); - else - m_moveToPoint.assignVertexDescription(m_description); - } - - // Reviewed vs. Native Jan 11, 2011 - - /** - * Starts a new Path at the Point. - */ - public void startPath(double x, double y) { - Point2D endPoint = new Point2D(); - endPoint.x = x; - endPoint.y = y; - startPath(endPoint); - } - - // Reviewed vs. Native Jan 11, 2011 - public void startPath(Point2D point) { - _initPathStartPoint(); - m_moveToPoint.setXY(point); - m_bPathStarted = true; - } - - // Reviewed vs. Native Jan 11, 2011 - public void startPath(Point3D point) { - _initPathStartPoint(); - m_moveToPoint.setXYZ(point); - assignVertexDescription(m_moveToPoint.getDescription()); - m_bPathStarted = true; - } - - // Reviewed vs. Native Jan 11, 2011 - public void startPath(Point point) { - if (point.isEmpty()) - throw new IllegalArgumentException();// throw new - // IllegalArgumentException(); - - mergeVertexDescription(point.getDescription()); - _initPathStartPoint(); - point.copyTo(m_moveToPoint); - - // TODO check MultiPathImpl.cpp comment - // "//the description will be merged later" - // assignVertexDescription(m_moveToPoint.getDescription()); - m_bPathStarted = true; - } - - // Reviewed vs. Native Jan 11, 2011 - protected void _beforeNewSegment(int resizeBy) { - // Called for each new segment being added. - if (m_bPathStarted) { - _initPathStartPoint();// make sure the m_movetoPoint exists and has - // right vertex description - - // The new path is started. Need to grow m_parts and m_pathFlags. - if (m_paths == null) { - m_paths = (AttributeStreamOfInt32) AttributeStreamBase - .createIndexStream(2); - m_paths.write(0, 0); - m_pathFlags = (AttributeStreamOfInt8) AttributeStreamBase - .createByteStream(2, (byte) 0); - } else { - // _ASSERT(m_parts.size() >= 2); - m_paths.resize(m_paths.size() + 1, 0); - m_pathFlags.resize(m_pathFlags.size() + 1, 0); - } - - if (m_bPolygon) { - // Mark the path as closed - m_pathFlags.write(m_pathFlags.size() - 2, - (byte) PathFlags.enumClosed); - } - - resizeBy++; // +1 for the StartPath point. - } - - int oldcount = m_pointCount; - m_paths.write(m_paths.size() - 1, m_pointCount + resizeBy); // The - // NotifyModified - // will - // update - // the - // m_pointCount - // with this - // value. - _resizeImpl(oldcount + resizeBy); - m_pathFlags.write(m_paths.size() - 1, (byte) 0); - - if (m_bPathStarted) { - setPointByVal(oldcount, m_moveToPoint);// setPoint(oldcount, - // m_moveToPoint); //finally - // set the start point to - // the geometry - m_bPathStarted = false; - } - } - - // Reviewed vs. Native Jan 11, 2011 - protected void _finishLineTo() { - } - - // Reviewed vs. Native Jan 11, 2011 - - /** - * adds a Line Segment from the last Point to the given endPoint. - */ - public void lineTo(double x, double y) { - _beforeNewSegment(1); - setXY(m_pointCount - 1, x, y); - _finishLineTo(); - // Point2D endPoint = new Point2D(); - // endPoint.x = x; endPoint.y = y; - // lineTo(endPoint); - } - - // Reviewed vs. Native Jan 11, 2011 - public void lineTo(Point2D endPoint) { - _beforeNewSegment(1); - setXY(m_pointCount - 1, endPoint); - _finishLineTo(); - } - - // Reviewed vs. Native Jan 11, 2011 - public void lineTo(Point3D endPoint) { - _beforeNewSegment(1); - setXYZ(m_pointCount - 1, endPoint); - _finishLineTo(); - } - - // Reviewed vs. Native Jan 11, 2011 - public void lineTo(Point endPoint) { - _beforeNewSegment(1); - setPointByVal(m_pointCount - 1, endPoint); - _finishLineTo(); - } - - // Reviewed vs. Native Jan 11, 2011 - protected void _initSegmentData(int sz) { - if (m_segmentParamIndex == null) { - m_segmentFlags = (AttributeStreamOfInt8) AttributeStreamBase - .createByteStream(m_pointCount, - (byte) SegmentFlags.enumLineSeg); - m_segmentParamIndex = (AttributeStreamOfInt32) AttributeStreamBase - .createIndexStream(m_pointCount, -1); - } - - int size = m_curveParamwritePoint + sz; - if (m_segmentParams == null) { - m_segmentParams = (AttributeStreamOfDbl) AttributeStreamBase - .createAttributeStreamWithPersistence( - VertexDescription.Persistence.enumDouble, size); - } else { - m_segmentParams.resize(size, 0); - } - } - - // Reviewed vs. Native Jan 11, 2011 - protected void _finishBezierTo() { - // _ASSERT(m_segmentFlags != null); - // _ASSERT(m_segmentParamIndex != null); - - m_segmentFlags.write(m_pointCount - 2, - (byte) SegmentFlags.enumBezierSeg); - } - - // Reviewed vs. Native Jan 11, 2011 - - /** - * adds a Cubic Bezier Segment to the current Path. The Bezier Segment - * connects the current last Point and the given endPoint. - */ - public void bezierTo(Point2D controlPoint1, Point2D controlPoint2, - Point2D endPoint) { - _beforeNewSegment(1); - setXY(m_pointCount - 1, endPoint); - double z; - _initSegmentData(6); - m_pathFlags.setBits(m_pathFlags.size() - 1, - (byte) PathFlags.enumHasNonlinearSegments); - m_segmentParamIndex.write(m_pointCount - 2, m_curveParamwritePoint); - m_curveParamwritePoint += 6; - int curveIndex = m_curveParamwritePoint; - m_segmentParams.write(curveIndex, controlPoint1.x); - m_segmentParams.write(curveIndex + 1, controlPoint1.y); - z = 0;// TODO: calculate me. - m_segmentParams.write(curveIndex + 2, z); - m_segmentParams.write(curveIndex + 3, controlPoint2.x); - m_segmentParams.write(curveIndex + 4, controlPoint2.y); - z = 0;// TODO: calculate me. - m_segmentParams.write(curveIndex + 5, z); - _finishBezierTo(); - } - - // Reviewed vs. Native Jan 11, 2011 - public void openPath(int pathIndex) { - _touch(); - if (m_bPolygon) - throw GeometryException.GeometryInternalError();// do not call this - // method on a - // polygon - - int pathCount = getPathCount(); - if (pathIndex > getPathCount()) - throw new IllegalArgumentException(); - - if (m_pathFlags == null) - throw GeometryException.GeometryInternalError(); - - m_pathFlags.clearBits(pathIndex, (byte) PathFlags.enumClosed); - } - - public void openPathAndDuplicateStartVertex(int pathIndex) { - _touch(); - if (m_bPolygon) - throw GeometryException.GeometryInternalError();// do not call this - // method on a - // polygon - - int pathCount = getPathCount(); - if (pathIndex > pathCount) - throw GeometryException.GeometryInternalError(); - - if (!isClosedPath(pathIndex)) - return;// do not open if open - - if (m_pathFlags == null)// if (!m_pathFlags) - throw GeometryException.GeometryInternalError(); - - int oldPointCount = m_pointCount; - int pathIndexStart = getPathStart(pathIndex); - int pathIndexEnd = getPathEnd(pathIndex); - _resizeImpl(m_pointCount + 1); // resize does not write into m_paths - // anymore! - _verifyAllStreams(); - for (int iattr = 0, nattr = m_description.getAttributeCount(); iattr < nattr; iattr++) { - if (m_vertexAttributes[iattr] != null)// if - // (m_vertexAttributes[iattr]) - { - int semantics = m_description._getSemanticsImpl(iattr); - int comp = VertexDescription.getComponentCount(semantics); - m_vertexAttributes[iattr].insertRange(comp * pathIndexEnd, - m_vertexAttributes[iattr], comp * pathIndexStart, comp, - true, 1, comp * oldPointCount); - } - } - - for (int ipath = pathCount; ipath > pathIndex; ipath--) { - int iend = m_paths.read(ipath); - m_paths.write(ipath, iend + 1); - } - - m_pathFlags.clearBits(pathIndex, (byte) PathFlags.enumClosed); - } - - // Reviewed vs. Native Jan 11, 2011 - // Major Changes on 16th of January - public void openAllPathsAndDuplicateStartVertex() { - _touch(); - if (m_bPolygon) - throw GeometryException.GeometryInternalError();// do not call this - // method on a - // polygon - - if (m_pathFlags == null)// if (!m_pathFlags) - throw GeometryException.GeometryInternalError(); - - _verifyAllStreams(); - - int closedPathCount = 0; - int pathCount = getPathCount(); - for (int i = 0; i < pathCount; i++) { - if (m_pathFlags.read(i) == (byte) PathFlags.enumClosed) { - closedPathCount++; - } - } - - for (int iattr = 0, nattr = m_description.getAttributeCount(); iattr < nattr; iattr++) { - if (m_vertexAttributes[iattr] != null) { - int semantics = m_description._getSemanticsImpl(iattr);// int - // semantics - // = - // m_description._getSemanticsImpl(iattr); - int comp = VertexDescription.getComponentCount(semantics); - int newSize = comp * (m_pointCount + closedPathCount); - m_vertexAttributes[iattr].resize(newSize); - - int offset = closedPathCount; - int ipath = pathCount; - for (int i = m_pointCount - 1; i >= 0; i--) { - if (i + 1 == m_paths.read(ipath)) { - ipath--; - if (m_pathFlags.read(ipath) == (byte) PathFlags.enumClosed) { - int istart = m_paths.read(ipath); - - for (int c = 0; c < comp; c++) { - double v = m_vertexAttributes[iattr] - .readAsDbl(comp * istart + c); - m_vertexAttributes[iattr].writeAsDbl(comp - * (offset + i) + c, v); - } - - if (--offset == 0) - break; - } - } - - for (int c = 0; c < comp; c++) { - double v = m_vertexAttributes[iattr].readAsDbl(comp * i - + c); - m_vertexAttributes[iattr].writeAsDbl(comp - * (offset + i) + c, v); - } - } - } - } - - int offset = closedPathCount; - for (int ipath = pathCount; ipath > 0; ipath--) { - int iend = m_paths.read(ipath); - m_paths.write(ipath, iend + offset); - - if (m_pathFlags.read(ipath - 1) == (byte) PathFlags.enumClosed) { - m_pathFlags.clearBits(ipath - 1, (byte) PathFlags.enumClosed); - - if (--offset == 0) { - break; - } - } - } - - m_pointCount += closedPathCount; - } - - void closePathWithLine(int path_index) { - // touch_(); - throwIfEmpty(); - - byte pf = m_pathFlags.read(path_index); - m_pathFlags.write(path_index, (byte) (pf | PathFlags.enumClosed)); - if (m_segmentFlags != null) { - int vindex = getPathEnd(path_index) - 1; - m_segmentFlags.write(vindex, (byte) SegmentFlags.enumLineSeg); - m_segmentParamIndex.write(vindex, -1); - } - } - - void closePathWithLine() { - throwIfEmpty(); - m_bPathStarted = false; - closePathWithLine(getPathCount() - 1); - } - - // Reviewed vs. Native Jan 11, 2011 - - /** - * Closes all open curves by adding an implicit line segment from the end - * point to the start point. - */ - public void closeAllPaths() { - _touch(); - if (m_bPolygon || isEmptyImpl()) - return; - - m_bPathStarted = false; - - for (int ipath = 0, npart = m_paths.size() - 1; ipath < npart; ipath++) { - if (isClosedPath(ipath)) - continue; - - byte pf = m_pathFlags.read(ipath); - m_pathFlags.write(ipath, (byte) (pf | PathFlags.enumClosed)); - // if (m_segmentFlags) - // { - // m_segmentFlags.write(m_pointCount - 1, - // (byte)SegmentFlags.LineSeg)); - // m_segmentParamIndex.write(m_pointCount - 1, -1); - // } - } - } - - // Reviewed vs. Native Jan 11, 2011 - - /** - * Returns the size of the segment data for the given segment type. - * - * @param flag is one of the segment flags from the SegmentFlags enum. - * @return the size of the segment params as the number of doubles. - */ - public static int getSegmentDataSize(byte flag) { - return _segmentParamSizes[flag]; - } - - // Reviewed vs. Native Jan 11, 2011 - - /** - * Closes last path of the MultiPathImpl with the Bezier Segment. - *

- * The start point of the Bezier is the last point of the path and the last - * point of the bezier is the first point of the path. - */ - public void closePathWithBezier(Point2D controlPoint1, Point2D controlPoint2) { - _touch(); - if (isEmptyImpl()) - throw new GeometryException( - "Invalid call. This operation cannot be performed on an empty geometry."); - - m_bPathStarted = false; - - int pathIndex = m_paths.size() - 2; - byte pf = m_pathFlags.read(pathIndex); - m_pathFlags - .write(pathIndex, - (byte) (pf | PathFlags.enumClosed | PathFlags.enumHasNonlinearSegments)); - _initSegmentData(6); - - byte oldType = m_segmentFlags - .read((byte) ((m_pointCount - 1) & SegmentFlags.enumSegmentMask)); - m_segmentFlags.write(m_pointCount - 1, - (byte) (SegmentFlags.enumBezierSeg)); - - int curveIndex = m_curveParamwritePoint; - if (getSegmentDataSize(oldType) < getSegmentDataSize((byte) SegmentFlags.enumBezierSeg)) { - m_segmentParamIndex.write(m_pointCount - 1, m_curveParamwritePoint); - m_curveParamwritePoint += 6; - } else { - // there was a closing bezier curve or an arc here. We can reuse the - // storage. - curveIndex = m_segmentParamIndex.read(m_pointCount - 1); - } - - double z; - m_segmentParams.write(curveIndex, controlPoint1.x); - m_segmentParams.write(curveIndex + 1, controlPoint1.y); - z = 0;// TODO: calculate me. - m_segmentParams.write(curveIndex + 2, z); - - m_segmentParams.write(curveIndex + 3, controlPoint2.x); - m_segmentParams.write(curveIndex + 4, controlPoint2.y); - z = 0;// TODO: calculate me. - m_segmentParams.write(curveIndex + 5, z); - } - - // Reviewed vs. Native Jan 11, 2011 - - /** - * Returns True if the given path is closed (represents a Ring). - */ - public boolean isClosedPath(int ipath) { - // Should we make a function called _UpdateClosedPathFlags and call it - // here? - return ((byte) (m_pathFlags.read(ipath) & PathFlags.enumClosed)) != 0; - } - - public boolean isClosedPathInXYPlane(int path_index) { - if (isClosedPath(path_index)) - return true; - int istart = getPathStart(path_index); - int iend = getPathEnd(path_index) - 1; - if (istart > iend) - return false; - Point2D ptS = getXY(istart); - Point2D ptE = getXY(iend); - return ptS.isEqual(ptE); - } - - // Reviewed vs. Native Jan 11, 2011 - - /** - * Returns True if the given path might have non-linear segments. - */ - public boolean hasNonLinearSegments(int ipath) { - // Should we make a function called _UpdateHasNonLinearSegmentsFlags and - // call it here? - return (m_pathFlags.read(ipath) & PathFlags.enumHasNonlinearSegments) != 0; - } - - // Reviewed vs. Native Jan 11, 2011 - public void addSegment(Segment segment, boolean bStartNewPath) { - mergeVertexDescription(segment.getDescription()); - if (segment.getType() == Type.Line) { - Point point = new Point(); - if (bStartNewPath || isEmpty()) { - segment.queryStart(point); - startPath(point); - } - - segment.queryEnd(point); - lineTo(point); - } else { - throw GeometryException.GeometryInternalError(); - } - } - - // Reviewed vs. Native Jan 11, 2011 - - /** - * adds a rectangular closed Path to the MultiPathImpl. - * - * @param envSrc is the source rectangle. - * @param bReverse Creates reversed path. - */ - public void addEnvelope(Envelope2D envSrc, boolean bReverse) { - boolean bWasEmpty = m_pointCount == 0; - - startPath(envSrc.xmin, envSrc.ymin); - if (bReverse) { - lineTo(envSrc.xmax, envSrc.ymin); - lineTo(envSrc.xmax, envSrc.ymax); - lineTo(envSrc.xmin, envSrc.ymax); - } else { - lineTo(envSrc.xmin, envSrc.ymax); - lineTo(envSrc.xmax, envSrc.ymax); - lineTo(envSrc.xmax, envSrc.ymin); - } - - closePathWithLine(); - m_bPathStarted = false; - - if (bWasEmpty && !bReverse) { - _setDirtyFlag(DirtyFlags.DirtyIsEnvelope, false);// now we no(sic?) - // the polypath - // is envelope - } - } - - // Reviewed vs. Native Jan 11, 2011 - - /** - * adds a rectangular closed Path to the MultiPathImpl. - * - * @param envSrc is the source rectangle. - * @param bReverse Creates reversed path. - */ - public void addEnvelope(Envelope envSrc, boolean bReverse) { - if (envSrc.isEmpty()) - return; - - boolean bWasEmpty = m_pointCount == 0; - Point pt = new Point(m_description);// getDescription()); - for (int i = 0, n = 4; i < n; i++) { - int j = bReverse ? n - i - 1 : i; - - envSrc.queryCornerByVal(j, pt); - if (i == 0) - startPath(pt); - else - lineTo(pt); - } - - closePathWithLine(); - m_bPathStarted = false; - - if (bWasEmpty && !bReverse) - _setDirtyFlag(DirtyFlags.DirtyIsEnvelope, false);// now we know the - // polypath is - // envelope - } - - // Reviewed vs. Native Jan 11, 2011 - public void add(MultiPathImpl src, boolean bReversePaths) { - for (int i = 0; i < src.getPathCount(); i++) - addPath(src, i, !bReversePaths); - } - - public void addPath(MultiPathImpl src, int srcPathIndex, boolean bForward) { - insertPath(-1, src, srcPathIndex, bForward); - } - - // Reviewed vs. Native Jan 11, 2011 Significant changes to last for loop - public void addPath(Point2D[] _points, int count, boolean bForward) { - insertPath(-1, _points, 0, count, bForward); - } - - public void addSegmentsFromPath(MultiPathImpl src, int src_path_index, - int src_segment_from, int src_segment_count, - boolean b_start_new_path) { - if (!b_start_new_path && getPathCount() == 0) - b_start_new_path = true; - - if (src_path_index < 0) - src_path_index = src.getPathCount() - 1; - - if (src_path_index >= src.getPathCount() || src_segment_from < 0 - || src_segment_count < 0 - || src_segment_count > src.getSegmentCount(src_path_index)) - throw new GeometryException("index out of bounds"); - - if (src_segment_count == 0) - return; - - boolean bIncludesClosingSegment = src.isClosedPath(src_path_index) - && src_segment_from + src_segment_count == src - .getSegmentCount(src_path_index); - - if (bIncludesClosingSegment && src_segment_count == 1) - return;// cannot add a closing segment alone. - - m_bPathStarted = false; - - mergeVertexDescription(src.getDescription()); - int src_point_count = src_segment_count; - int srcFromPoint = src.getPathStart(src_path_index) + src_segment_from - + 1; - if (b_start_new_path)// adding a new path. - { - src_point_count++;// add start point. - srcFromPoint--; - } - - if (bIncludesClosingSegment) { - src_point_count--; - } - - int oldPointCount = m_pointCount; - _resizeImpl(m_pointCount + src_point_count); - _verifyAllStreams(); - - if (b_start_new_path) { - if (src_point_count == 0) - return;// happens when adding a single closing segment to the - // new path - - m_paths.add(m_pointCount); - - byte flags = src.m_pathFlags.read(src_path_index); - flags &= ~(byte) PathFlags.enumCalcMask;// remove calculated flags - - if (m_bPolygon) - flags |= (byte) PathFlags.enumClosed; - - m_pathFlags.write(m_pathFlags.size() - 1, flags); - m_pathFlags.add((byte) 0); - } else { - m_paths.write(m_pathFlags.size() - 1, m_pointCount); - } - - // Index_type absoluteIndex = pathStart + before_point_index; - - for (int iattr = 0, nattr = m_description.getAttributeCount(); iattr < nattr; iattr++) { - int semantics = m_description.getSemantics(iattr); - int comp = VertexDescription.getComponentCount(semantics); - - int isrcAttr = src.m_description.getAttributeIndex(semantics); - if (isrcAttr < 0 || src.m_vertexAttributes[isrcAttr] == null) {// The - // source - // does - // not - // have - // the - // attribute. - // insert - // default - // value - double v = VertexDescription.getDefaultValue(semantics); - m_vertexAttributes[iattr].insertRange(comp * oldPointCount, v, - src_point_count * comp, comp * oldPointCount); - continue; - } - - // add vertices to the given stream - boolean b_forward = true; - m_vertexAttributes[iattr].insertRange(comp * oldPointCount, - src.m_vertexAttributes[isrcAttr], comp * srcFromPoint, - src_point_count * comp, b_forward, comp, comp - * oldPointCount); - } - - if (hasNonLinearSegments()) { - // TODO: implement me. For example as a while loop over all curves. - // Replace, calling ReplaceSegment - throw GeometryException.GeometryInternalError(); - // m_segment_flags->write_range((get_path_start(path_index) + - // before_point_index + src_point_count), (oldPointCount - - // get_path_start(path_index) - before_point_index), - // m_segment_flags, (get_path_start(path_index) + - // before_point_index), true, 1); - // m_segment_param_index->write_range((get_path_start(path_index) + - // before_point_index + src_point_count), (oldPointCount - - // get_path_start(path_index) - before_point_index), - // m_segment_param_index, (get_path_start(path_index) + - // before_point_index), true, 1); - // for (Index_type i = get_path_start(path_index) + - // before_point_index, n = get_path_start(path_index) + - // before_point_index + src_point_count; i < n; i++) - // { - // m_segment_flags->write(i, (int8_t)enum_value1(Segment_flags, - // enum_line_seg)); - // m_segment_param_index->write(i, -1); - // } - } - - if (src.hasNonLinearSegments(src_path_index)) { - // TODO: implement me. For example as a while loop over all curves. - // Replace, calling ReplaceSegment - throw GeometryException.GeometryInternalError(); - } - - notifyModified(DirtyFlags.DirtyCoordinates); - } - - // Reviewed vs. Native Jan 11, 2011 - public void reverseAllPaths() { - for (int i = 0, n = getPathCount(); i < n; i++) { - reversePath(i); - } - } - - // Reviewed vs. Native Jan 11, 2011 - public void reversePath(int pathIndex) { - _verifyAllStreams(); - int pathCount = getPathCount(); - if (pathIndex >= pathCount) - throw new IllegalArgumentException(); - - int reversedPathStart = getPathStart(pathIndex); - int reversedPathSize = getPathSize(pathIndex); - int offset = isClosedPath(pathIndex) ? 1 : 0; - - // TODO: a bug for the non linear segments here. - // There could be an issue here if someone explicity closes the path - // with the same start/end point. - for (int iattr = 0, nattr = m_description.getAttributeCount(); iattr < nattr; iattr++) { - if (m_vertexAttributes[iattr] != null) { - int semantics = m_description._getSemanticsImpl(iattr); - int comp = VertexDescription.getComponentCount(semantics); - m_vertexAttributes[iattr].reverseRange(comp - * (reversedPathStart + offset), comp - * (reversedPathSize - offset), comp); - } - } - - notifyModified(DirtyFlags.DirtyCoordinates); - } - - // Reviewed vs. Native Jan 11, 2011 - // TODO: Nonlinearsegments - public void removePath(int pathIndex) { - _verifyAllStreams(); - int pathCount = getPathCount(); - - if (pathIndex < 0) - pathIndex = pathCount - 1; - - if (pathIndex >= pathCount) - throw new IllegalArgumentException(); - - boolean bDirtyRingAreas2D = _hasDirtyFlag(DirtyFlags.DirtyRingAreas2D); - - int removedPathStart = getPathStart(pathIndex); - int removedPathSize = getPathSize(pathIndex); - - // Remove the attribute values for the path - for (int iattr = 0, nattr = m_description.getAttributeCount(); iattr < nattr; iattr++) { - if (m_vertexAttributes[iattr] != null) { - int semantics = m_description._getSemanticsImpl(iattr); - int comp = VertexDescription.getComponentCount(semantics); - m_vertexAttributes[iattr].eraseRange(comp * removedPathStart, - comp * removedPathSize, comp * m_pointCount); - } - } - - // Change the start of each path after the removed path - for (int i = pathIndex + 1; i <= pathCount; i++) { - int istart = m_paths.read(i); - m_paths.write(i - 1, istart - removedPathSize); - } - - if (m_pathFlags == null) { - for (int i = pathIndex + 1; i <= pathCount; i++) { - byte flags = m_pathFlags.read(i); - m_pathFlags.write(i - 1, flags); - } - } - - m_paths.resize(pathCount); - m_pathFlags.resize(pathCount); - m_pointCount -= removedPathSize; - m_reservedPointCount -= removedPathSize; - - notifyModified(DirtyFlags.DirtyCoordinates); - } - - // TODO: Nonlinearsegments - public void insertPath(int pathIndex, MultiPathImpl src, int srcPathIndex, - boolean bForward) { - if (src == this) - throw new IllegalArgumentException(); - - if (srcPathIndex >= src.getPathCount()) - throw new IllegalArgumentException(); - - int oldPathCount = getPathCount(); - if (pathIndex > oldPathCount) - throw new IllegalArgumentException(); - - if (pathIndex < 0) - pathIndex = oldPathCount; - - if (srcPathIndex < 0) - srcPathIndex = src.getPathCount() - 1; - - m_bPathStarted = false; - - mergeVertexDescription(src.m_description);// merge attributes from the - // source - - src._verifyAllStreams();// the source need to be correct. - - int srcPathIndexStart = src.getPathStart(srcPathIndex); - int srcPathSize = src.getPathSize(srcPathIndex); - int oldPointCount = m_pointCount; - int offset = src.isClosedPath(srcPathIndex) && !bForward ? 1 : 0; - - _resizeImpl(m_pointCount + srcPathSize); - _verifyAllStreams(); - int pathIndexStart = pathIndex < oldPathCount ? getPathStart(pathIndex) - : oldPointCount; - - // Copy all attribute values. - for (int iattr = 0, nattr = m_description.getAttributeCount(); iattr < nattr; iattr++) { - int semantics = m_description._getSemanticsImpl(iattr); - int isrcAttr = src.m_description.getAttributeIndex(semantics); - - int comp = VertexDescription.getComponentCount(semantics); - - if (isrcAttr >= 0 && src.m_vertexAttributes[isrcAttr] != null) { - if (offset != 0) - m_vertexAttributes[iattr].insertRange( - pathIndexStart * comp, - src.m_vertexAttributes[isrcAttr], comp - * srcPathIndexStart, comp, true, comp, comp - * oldPointCount); - m_vertexAttributes[iattr].insertRange((pathIndexStart + offset) - * comp, src.m_vertexAttributes[isrcAttr], comp - * (srcPathIndexStart + offset), comp - * (srcPathSize - offset), bForward, comp, comp - * (oldPointCount + offset)); - } else { - // Need to make room for the attributes, so we copy default - // values in - - double v = VertexDescription.getDefaultValue(semantics); - m_vertexAttributes[iattr].insertRange(pathIndexStart * comp, v, - comp * srcPathSize, comp * oldPointCount); - } - } - - int newPointCount = oldPointCount + srcPathSize; - m_paths.add(newPointCount); - - for (int ipath = oldPathCount; ipath >= pathIndex + 1; ipath--) { - int iend = m_paths.read(ipath - 1); - m_paths.write(ipath, iend + srcPathSize); - } - - // ========================== todo: NonLinearSegments ================= - if (src.hasNonLinearSegments(srcPathIndex)) { - - } - - m_pathFlags.add((byte) 0); - - // _ASSERT(m_pathFlags.size() == m_paths.size()); - - for (int ipath = oldPathCount - 1; ipath >= pathIndex + 1; ipath--) { - byte flags = m_pathFlags.read(ipath); - flags &= ~(byte) PathFlags.enumCalcMask;// remove calculated flags - m_pathFlags.write(ipath + 1, flags); - } - - AttributeStreamOfInt8 srcPathFlags = src.getPathFlagsStreamRef(); - byte flags = srcPathFlags.read(srcPathIndex); - flags &= ~(byte) PathFlags.enumCalcMask;// remove calculated flags - - if (m_bPolygon) - flags |= (byte) PathFlags.enumClosed; - - m_pathFlags.write(pathIndex, flags); - } - - public void insertPath(int pathIndex, Point2D[] points, int pointsOffset, - int count, boolean bForward) { - int oldPathCount = getPathCount(); - if (pathIndex > oldPathCount) - throw new IllegalArgumentException(); - - if (pathIndex < 0) - pathIndex = oldPathCount; - - m_bPathStarted = false; - - int oldPointCount = m_pointCount; - - // Copy all attribute values. - if (points != null) { - _resizeImpl(m_pointCount + count); - _verifyAllStreams(); - - int pathStart = pathIndex < oldPathCount ? getPathStart(pathIndex) - : oldPointCount; - - for (int iattr = 0, nattr = m_description.getAttributeCount(); iattr < nattr; iattr++) { - int semantics = m_description._getSemanticsImpl(iattr); - - if (semantics == VertexDescription.Semantics.POSITION) { - // copy range to make place for new vertices - m_vertexAttributes[iattr].writeRange( - 2 * (pathStart + count), - 2 * (oldPointCount - pathIndex), - m_vertexAttributes[iattr], 2 * pathStart, true, 2); - - AttributeStreamOfDbl position = (AttributeStreamOfDbl) (AttributeStreamBase) getAttributeStreamRef(semantics); - - int j = pathStart; - for (int i = 0; i < count; i++, j++) { - int index = (bForward ? pointsOffset + i : pointsOffset - + count - i - 1); - position.write(2 * j, points[index].x); - position.write(2 * j + 1, points[index].y); - } - } else { - // Need to make room for the attributes, so we copy default - // values in - - int comp = VertexDescription.getComponentCount(semantics); - double v = VertexDescription.getDefaultValue(semantics); - m_vertexAttributes[iattr].insertRange(pathStart * comp, v, - comp * count, comp * oldPointCount); - } - } - } else { - _verifyAllStreams(); - } - - m_paths.add(m_pointCount); - - for (int ipath = oldPathCount; ipath >= pathIndex + 1; ipath--) { - int iend = m_paths.read(ipath - 1); - m_paths.write(ipath, iend + count); - } - - m_pathFlags.add((byte) 0); - - // _ASSERT(m_pathFlags.size() == m_paths.size()); - - for (int ipath = oldPathCount - 1; ipath >= pathIndex + 1; ipath--) { - byte flags = m_pathFlags.read(ipath); - flags &= ~(byte) PathFlags.enumCalcMask;// remove calculated flags - m_pathFlags.write(ipath + 1, flags); - } - - if (m_bPolygon) - m_pathFlags.write(pathIndex, (byte) PathFlags.enumClosed); - } - - public void insertPoints(int pathIndex, int beforePointIndex, - MultiPathImpl src, int srcPathIndex, int srcPointIndexFrom, - int srcPointCount, boolean bForward) { - if (pathIndex < 0) - pathIndex = getPathCount(); - - if (srcPathIndex < 0) - srcPathIndex = src.getPathCount() - 1; - - if (pathIndex > getPathCount() || beforePointIndex >= 0 - && beforePointIndex > getPathSize(pathIndex) - || srcPathIndex >= src.getPathCount() - || srcPointCount > src.getPathSize(srcPathIndex)) - throw new GeometryException("index out of bounds"); - - if (srcPointCount == 0) - return; - - mergeVertexDescription(src.m_description); - - if (pathIndex == getPathCount())// adding a new path. - { - m_paths.add(m_pointCount); - - byte flags = src.m_pathFlags.read(srcPathIndex); - flags &= ~(byte) PathFlags.enumCalcMask;// remove calculated flags - - if (!m_bPolygon) - m_pathFlags.add(flags); - else - m_pathFlags.add((byte) (flags | PathFlags.enumClosed)); - } - - if (beforePointIndex < 0) - beforePointIndex = getPathSize(pathIndex); - - int oldPointCount = m_pointCount; - _resizeImpl(m_pointCount + srcPointCount); - _verifyAllStreams(); - src._verifyAllStreams(); - - int pathStart = getPathStart(pathIndex); - int absoluteIndex = pathStart + beforePointIndex; - - if (srcPointCount < 0) - srcPointCount = src.getPathSize(srcPathIndex); - - int srcPathStart = src.getPathStart(srcPathIndex); - int srcAbsoluteIndex = srcPathStart + srcPointCount; - - for (int iattr = 0, nattr = m_description.getAttributeCount(); iattr < nattr; iattr++) { - int semantics = m_description._getSemanticsImpl(iattr); - int comp = VertexDescription.getComponentCount(semantics); - - int isrcAttr = src.m_description.getAttributeIndex(semantics); - if (isrcAttr < 0 || src.m_vertexAttributes[isrcAttr] == null) // The - // source - // does - // not - // have - // the - // attribute. - { - double v = VertexDescription.getDefaultValue(semantics); - m_vertexAttributes[iattr].insertRange(comp * absoluteIndex, v, - srcAbsoluteIndex * comp, comp * oldPointCount); - continue; - } - - // add vertices to the given stream - m_vertexAttributes[iattr].insertRange(comp - * (pathStart + beforePointIndex), - src.m_vertexAttributes[isrcAttr], comp - * (srcPathStart + srcPointIndexFrom), srcPointCount - * comp, bForward, comp, comp * oldPointCount); - } - - if (hasNonLinearSegments()) {// TODO: probably a bug here when a new - // path is added. - m_segmentFlags.writeRange((getPathStart(pathIndex) - + beforePointIndex + srcPointCount), (oldPointCount - - getPathStart(pathIndex) - beforePointIndex), - m_segmentFlags, - (getPathStart(pathIndex) + beforePointIndex), true, 1); - m_segmentParamIndex.writeRange((getPathStart(pathIndex) - + beforePointIndex + srcPointCount), (oldPointCount - - getPathStart(pathIndex) - beforePointIndex), - m_segmentParamIndex, - (getPathStart(pathIndex) + beforePointIndex), true, 1); - for (int i = getPathStart(pathIndex) + beforePointIndex, n = getPathStart(pathIndex) - + beforePointIndex + srcPointCount; i < n; i++) { - m_segmentFlags.write(i, (byte) SegmentFlags.enumLineSeg); - m_segmentParamIndex.write(i, -1); - } - } - - if (src.hasNonLinearSegments(srcPathIndex)) { - // TODO: implement me. For example as a while loop over all curves. - // Replace, calling ReplaceSegment - throw GeometryException.GeometryInternalError(); - } - - for (int ipath = pathIndex + 1, npaths = getPathCount(); ipath <= npaths; ipath++) { - int num = m_paths.read(ipath); - m_paths.write(ipath, num + srcPointCount); - } - } - - public void insertPoints(int pathIndex, int beforePointIndex, - Point2D[] src, int srcPointIndexFrom, int srcPointCount, - boolean bForward) { - if (pathIndex < 0) - pathIndex = getPathCount(); - - if (pathIndex > getPathCount() - || beforePointIndex > getPathSize(pathIndex) - || srcPointIndexFrom < 0 || srcPointCount > src.length) - throw new GeometryException("index out of bounds"); - - if (srcPointCount == 0) - return; - - if (pathIndex == getPathCount())// adding a new path. - { - m_paths.add(m_pointCount); - - if (!m_bPolygon) - m_pathFlags.add((byte) 0); - else - m_pathFlags.add((byte) PathFlags.enumClosed); - } - - if (beforePointIndex < 0) - beforePointIndex = getPathSize(pathIndex); - - _verifyAllStreams(); - int oldPointCount = m_pointCount; - _resizeImpl(m_pointCount + srcPointCount); - _verifyAllStreams(); - for (int iattr = 0, nattr = m_description.getAttributeCount(); iattr < nattr; iattr++) { - int semantics = m_description._getSemanticsImpl(iattr); - int comp = VertexDescription.getComponentCount(semantics); - // copy range to make place for new vertices - m_vertexAttributes[iattr] - .writeRange( - comp - * (getPathStart(pathIndex) - + beforePointIndex + srcPointCount), - (oldPointCount - getPathStart(pathIndex) - beforePointIndex) - * comp, - m_vertexAttributes[iattr], - comp * (getPathStart(pathIndex) + beforePointIndex), - true, comp); - - if (iattr == 0) { - // add vertices to the given stream - ((AttributeStreamOfDbl) (AttributeStreamBase) m_vertexAttributes[iattr]) - .writeRange(comp - * (getPathStart(pathIndex) + beforePointIndex), - srcPointCount, src, srcPointIndexFrom, bForward); - } else { - double v = VertexDescription.getDefaultValue(semantics); - m_vertexAttributes[iattr].setRange(v, - (getPathStart(pathIndex) + beforePointIndex) * comp, - srcPointCount * comp); - } - } - - if (hasNonLinearSegments()) { - m_segmentFlags.writeRange((getPathStart(pathIndex) - + beforePointIndex + srcPointCount), (oldPointCount - - getPathStart(pathIndex) - beforePointIndex), - m_segmentFlags, - (getPathStart(pathIndex) + beforePointIndex), true, 1); - m_segmentParamIndex.writeRange((getPathStart(pathIndex) - + beforePointIndex + srcPointCount), (oldPointCount - - getPathStart(pathIndex) - beforePointIndex), - m_segmentParamIndex, - (getPathStart(pathIndex) + beforePointIndex), true, 1); - m_segmentFlags.setRange((byte) SegmentFlags.enumLineSeg, - getPathStart(pathIndex) + beforePointIndex, srcPointCount); - m_segmentParamIndex.setRange(-1, getPathStart(pathIndex) - + beforePointIndex, srcPointCount); - } - - for (int ipath = pathIndex + 1, npaths = getPathCount(); ipath <= npaths; ipath++) { - m_paths.write(ipath, m_paths.read(ipath) + srcPointCount); - } - } - - public void insertPoint(int pathIndex, int beforePointIndex, Point2D pt) { - int pathCount = getPathCount(); - - if (pathIndex < 0) - pathIndex = getPathCount(); - - if (pathIndex >= pathCount || beforePointIndex > getPathSize(pathIndex)) - throw new GeometryException("index out of bounds"); - - if (pathIndex == getPathCount())// adding a new path. - { - m_paths.add(m_pointCount); - - if (!m_bPolygon) - m_pathFlags.add((byte) 0); - else - m_pathFlags.add((byte) PathFlags.enumClosed); - } - - if (beforePointIndex < 0) - beforePointIndex = getPathSize(pathIndex); - - int oldPointCount = m_pointCount; - _resizeImpl(m_pointCount + 1); - _verifyAllStreams(); - - int pathStart = getPathStart(pathIndex); - - ((AttributeStreamOfDbl) (AttributeStreamBase) m_vertexAttributes[0]) - .insert(2 * (pathStart + beforePointIndex), pt, - 2 * oldPointCount); - - for (int iattr = 1, nattr = m_description.getAttributeCount(); iattr < nattr; iattr++) { - int semantics = m_description._getSemanticsImpl(iattr); - int comp = VertexDescription.getComponentCount(semantics); - - // Need to make room for the attribute, so we copy a default value - // in - double v = VertexDescription.getDefaultValue(semantics); - m_vertexAttributes[iattr].insertRange(comp - * (pathStart + beforePointIndex), v, comp, comp - * oldPointCount); - } - - for (int ipath = pathIndex + 1, npaths = pathCount; ipath <= npaths; ipath++) { - m_paths.write(ipath, m_paths.read(ipath) + 1); - } - } - - public void insertPoint(int pathIndex, int beforePointIndex, Point pt) { - int pathCount = getPathCount(); - - if (pathIndex < 0) - pathIndex = getPathCount(); - - if (pathIndex >= pathCount || beforePointIndex > getPathSize(pathIndex)) - throw new GeometryException("index out of bounds"); - - if (pathIndex == getPathCount())// adding a new path. - { - m_paths.add(m_pointCount); - - if (!m_bPolygon) - m_pathFlags.add((byte) 0); - else - m_pathFlags.add((byte) PathFlags.enumClosed); - } - - if (beforePointIndex < 0) - beforePointIndex = getPathSize(pathIndex); - - mergeVertexDescription(pt.getDescription()); - int oldPointCount = m_pointCount; - _resizeImpl(m_pointCount + 1); - _verifyAllStreams(); - - int pathStart = getPathStart(pathIndex); - - for (int iattr = 0, nattr = m_description.getAttributeCount(); iattr < nattr; iattr++) { - int semantics = m_description._getSemanticsImpl(iattr); - int comp = VertexDescription.getComponentCount(semantics); - - if (pt.hasAttribute(semantics)) { - m_vertexAttributes[iattr].insertAttributes(comp - * (pathStart + beforePointIndex), pt, semantics, comp - * oldPointCount); - } else { - // Need to make room for the attribute, so we copy a default - // value in - double v = VertexDescription.getDefaultValue(semantics); - m_vertexAttributes[iattr].insertRange(comp - * (pathStart + beforePointIndex), v, comp, comp - * oldPointCount); - } - } + // / Cpp /// + // Reviewed vs. Native Jan 11, 2011 + public MultiPathImpl(boolean bPolygon) { + m_bPolygon = bPolygon; + + m_bPathStarted = false; + m_curveParamwritePoint = 0; + m_cachedLength2D = 0; + m_cachedArea2D = 0; + m_pointCount = 0; + m_description = VertexDescriptionDesignerImpl.getDefaultDescriptor2D(); + m_cachedRingAreas2D = null; + m_currentPathIndex = 0; + } + + // Reviewed vs. Native Jan 11, 2011 + public MultiPathImpl(boolean bPolygon, VertexDescription description) { + if (description == null) + throw new IllegalArgumentException(); + + m_bPolygon = bPolygon; + + m_bPathStarted = false; + m_curveParamwritePoint = 0; + m_cachedLength2D = 0; + m_cachedArea2D = 0; + m_pointCount = 0; + m_description = description; + m_cachedRingAreas2D = null; + m_currentPathIndex = 0; + } + + // Reviewed vs. Native Jan 11, 2011 + protected void _initPathStartPoint() { + _touch(); + if (m_moveToPoint == null) + m_moveToPoint = new Point(m_description); + else + m_moveToPoint.assignVertexDescription(m_description); + } + + // Reviewed vs. Native Jan 11, 2011 + + /** + * Starts a new Path at the Point. + */ + public void startPath(double x, double y) { + Point2D endPoint = new Point2D(); + endPoint.x = x; + endPoint.y = y; + startPath(endPoint); + } + + // Reviewed vs. Native Jan 11, 2011 + public void startPath(Point2D point) { + _initPathStartPoint(); + m_moveToPoint.setXY(point); + m_bPathStarted = true; + } + + // Reviewed vs. Native Jan 11, 2011 + public void startPath(Point3D point) { + _initPathStartPoint(); + m_moveToPoint.setXYZ(point); + assignVertexDescription(m_moveToPoint.getDescription()); + m_bPathStarted = true; + } + + // Reviewed vs. Native Jan 11, 2011 + public void startPath(Point point) { + if (point.isEmpty()) + throw new IllegalArgumentException();// throw new + // IllegalArgumentException(); + + mergeVertexDescription(point.getDescription()); + _initPathStartPoint(); + point.copyTo(m_moveToPoint); + + // TODO check MultiPathImpl.cpp comment + // "//the description will be merged later" + // assignVertexDescription(m_moveToPoint.getDescription()); + m_bPathStarted = true; + } + + // Reviewed vs. Native Jan 11, 2011 + protected void _beforeNewSegment(int resizeBy) { + // Called for each new segment being added. + if (m_bPathStarted) { + _initPathStartPoint();// make sure the m_movetoPoint exists and has + // right vertex description + + // The new path is started. Need to grow m_parts and m_pathFlags. + if (m_paths == null) { + m_paths = (AttributeStreamOfInt32) AttributeStreamBase + .createIndexStream(2); + m_paths.write(0, 0); + m_pathFlags = (AttributeStreamOfInt8) AttributeStreamBase + .createByteStream(2, (byte) 0); + } else { + // _ASSERT(m_parts.size() >= 2); + m_paths.resize(m_paths.size() + 1, 0); + m_pathFlags.resize(m_pathFlags.size() + 1, 0); + } + + if (m_bPolygon) { + // Mark the path as closed + m_pathFlags.write(m_pathFlags.size() - 2, + (byte) PathFlags.enumClosed); + } + + resizeBy++; // +1 for the StartPath point. + } + + int oldcount = m_pointCount; + m_paths.write(m_paths.size() - 1, m_pointCount + resizeBy); // The + // NotifyModified + // will + // update + // the + // m_pointCount + // with this + // value. + _resizeImpl(oldcount + resizeBy); + m_pathFlags.write(m_paths.size() - 1, (byte) 0); + + if (m_bPathStarted) { + setPointByVal(oldcount, m_moveToPoint);// setPoint(oldcount, + // m_moveToPoint); //finally + // set the start point to + // the geometry + m_bPathStarted = false; + } + } + + // Reviewed vs. Native Jan 11, 2011 + protected void _finishLineTo() { + } + + // Reviewed vs. Native Jan 11, 2011 + + /** + * adds a Line Segment from the last Point to the given endPoint. + */ + public void lineTo(double x, double y) { + _beforeNewSegment(1); + setXY(m_pointCount - 1, x, y); + _finishLineTo(); + // Point2D endPoint = new Point2D(); + // endPoint.x = x; endPoint.y = y; + // lineTo(endPoint); + } + + // Reviewed vs. Native Jan 11, 2011 + public void lineTo(Point2D endPoint) { + _beforeNewSegment(1); + setXY(m_pointCount - 1, endPoint); + _finishLineTo(); + } + + // Reviewed vs. Native Jan 11, 2011 + public void lineTo(Point3D endPoint) { + _beforeNewSegment(1); + setXYZ(m_pointCount - 1, endPoint); + _finishLineTo(); + } + + // Reviewed vs. Native Jan 11, 2011 + public void lineTo(Point endPoint) { + _beforeNewSegment(1); + setPointByVal(m_pointCount - 1, endPoint); + _finishLineTo(); + } + + // Reviewed vs. Native Jan 11, 2011 + protected void _initSegmentData(int sz) { + if (m_segmentParamIndex == null) { + m_segmentFlags = (AttributeStreamOfInt8) AttributeStreamBase + .createByteStream(m_pointCount, + (byte) SegmentFlags.enumLineSeg); + m_segmentParamIndex = (AttributeStreamOfInt32) AttributeStreamBase + .createIndexStream(m_pointCount, -1); + } + + int size = m_curveParamwritePoint + sz; + if (m_segmentParams == null) { + m_segmentParams = (AttributeStreamOfDbl) AttributeStreamBase + .createAttributeStreamWithPersistence( + VertexDescription.Persistence.enumDouble, size); + } else { + m_segmentParams.resize(size, 0); + } + } + + // Reviewed vs. Native Jan 11, 2011 + protected void _finishBezierTo() { + // _ASSERT(m_segmentFlags != null); + // _ASSERT(m_segmentParamIndex != null); + + m_segmentFlags.write(m_pointCount - 2, + (byte) SegmentFlags.enumBezierSeg); + } + + // Reviewed vs. Native Jan 11, 2011 + + /** + * adds a Cubic Bezier Segment to the current Path. The Bezier Segment + * connects the current last Point and the given endPoint. + */ + public void bezierTo(Point2D controlPoint1, Point2D controlPoint2, + Point2D endPoint) { + _beforeNewSegment(1); + setXY(m_pointCount - 1, endPoint); + double z; + _initSegmentData(6); + m_pathFlags.setBits(m_pathFlags.size() - 1, + (byte) PathFlags.enumHasNonlinearSegments); + m_segmentParamIndex.write(m_pointCount - 2, m_curveParamwritePoint); + m_curveParamwritePoint += 6; + int curveIndex = m_curveParamwritePoint; + m_segmentParams.write(curveIndex, controlPoint1.x); + m_segmentParams.write(curveIndex + 1, controlPoint1.y); + z = 0;// TODO: calculate me. + m_segmentParams.write(curveIndex + 2, z); + m_segmentParams.write(curveIndex + 3, controlPoint2.x); + m_segmentParams.write(curveIndex + 4, controlPoint2.y); + z = 0;// TODO: calculate me. + m_segmentParams.write(curveIndex + 5, z); + _finishBezierTo(); + } + + // Reviewed vs. Native Jan 11, 2011 + public void openPath(int pathIndex) { + _touch(); + if (m_bPolygon) + throw GeometryException.GeometryInternalError();// do not call this + // method on a + // polygon + + int pathCount = getPathCount(); + if (pathIndex > getPathCount()) + throw new IllegalArgumentException(); + + if (m_pathFlags == null) + throw GeometryException.GeometryInternalError(); + + m_pathFlags.clearBits(pathIndex, (byte) PathFlags.enumClosed); + } + + public void openPathAndDuplicateStartVertex(int pathIndex) { + _touch(); + if (m_bPolygon) + throw GeometryException.GeometryInternalError();// do not call this + // method on a + // polygon + + int pathCount = getPathCount(); + if (pathIndex > pathCount) + throw GeometryException.GeometryInternalError(); + + if (!isClosedPath(pathIndex)) + return;// do not open if open + + if (m_pathFlags == null)// if (!m_pathFlags) + throw GeometryException.GeometryInternalError(); + + int oldPointCount = m_pointCount; + int pathIndexStart = getPathStart(pathIndex); + int pathIndexEnd = getPathEnd(pathIndex); + _resizeImpl(m_pointCount + 1); // resize does not write into m_paths + // anymore! + _verifyAllStreams(); + for (int iattr = 0, nattr = m_description.getAttributeCount(); iattr < nattr; iattr++) { + if (m_vertexAttributes[iattr] != null)// if + // (m_vertexAttributes[iattr]) + { + int semantics = m_description._getSemanticsImpl(iattr); + int comp = VertexDescription.getComponentCount(semantics); + m_vertexAttributes[iattr].insertRange(comp * pathIndexEnd, + m_vertexAttributes[iattr], comp * pathIndexStart, comp, + true, 1, comp * oldPointCount); + } + } + + for (int ipath = pathCount; ipath > pathIndex; ipath--) { + int iend = m_paths.read(ipath); + m_paths.write(ipath, iend + 1); + } + + m_pathFlags.clearBits(pathIndex, (byte) PathFlags.enumClosed); + } + + // Reviewed vs. Native Jan 11, 2011 + // Major Changes on 16th of January + public void openAllPathsAndDuplicateStartVertex() { + _touch(); + if (m_bPolygon) + throw GeometryException.GeometryInternalError();// do not call this + // method on a + // polygon + + if (m_pathFlags == null)// if (!m_pathFlags) + throw GeometryException.GeometryInternalError(); + + _verifyAllStreams(); + + int closedPathCount = 0; + int pathCount = getPathCount(); + for (int i = 0; i < pathCount; i++) { + if (m_pathFlags.read(i) == (byte) PathFlags.enumClosed) { + closedPathCount++; + } + } + + for (int iattr = 0, nattr = m_description.getAttributeCount(); iattr < nattr; iattr++) { + if (m_vertexAttributes[iattr] != null) { + int semantics = m_description._getSemanticsImpl(iattr);// int + // semantics + // = + // m_description._getSemanticsImpl(iattr); + int comp = VertexDescription.getComponentCount(semantics); + int newSize = comp * (m_pointCount + closedPathCount); + m_vertexAttributes[iattr].resize(newSize); + + int offset = closedPathCount; + int ipath = pathCount; + for (int i = m_pointCount - 1; i >= 0; i--) { + if (i + 1 == m_paths.read(ipath)) { + ipath--; + if (m_pathFlags.read(ipath) == (byte) PathFlags.enumClosed) { + int istart = m_paths.read(ipath); + + for (int c = 0; c < comp; c++) { + double v = m_vertexAttributes[iattr] + .readAsDbl(comp * istart + c); + m_vertexAttributes[iattr].writeAsDbl(comp + * (offset + i) + c, v); + } + + if (--offset == 0) + break; + } + } + + for (int c = 0; c < comp; c++) { + double v = m_vertexAttributes[iattr].readAsDbl(comp * i + + c); + m_vertexAttributes[iattr].writeAsDbl(comp + * (offset + i) + c, v); + } + } + } + } + + int offset = closedPathCount; + for (int ipath = pathCount; ipath > 0; ipath--) { + int iend = m_paths.read(ipath); + m_paths.write(ipath, iend + offset); + + if (m_pathFlags.read(ipath - 1) == (byte) PathFlags.enumClosed) { + m_pathFlags.clearBits(ipath - 1, (byte) PathFlags.enumClosed); + + if (--offset == 0) { + break; + } + } + } + + m_pointCount += closedPathCount; + } + + void closePathWithLine(int path_index) { + // touch_(); + throwIfEmpty(); + + byte pf = m_pathFlags.read(path_index); + m_pathFlags.write(path_index, (byte) (pf | PathFlags.enumClosed)); + if (m_segmentFlags != null) { + int vindex = getPathEnd(path_index) - 1; + m_segmentFlags.write(vindex, (byte) SegmentFlags.enumLineSeg); + m_segmentParamIndex.write(vindex, -1); + } + } + + void closePathWithLine() { + throwIfEmpty(); + m_bPathStarted = false; + closePathWithLine(getPathCount() - 1); + } + + // Reviewed vs. Native Jan 11, 2011 + + /** + * Closes all open curves by adding an implicit line segment from the end + * point to the start point. + */ + public void closeAllPaths() { + _touch(); + if (m_bPolygon || isEmptyImpl()) + return; + + m_bPathStarted = false; + + for (int ipath = 0, npart = m_paths.size() - 1; ipath < npart; ipath++) { + if (isClosedPath(ipath)) + continue; + + byte pf = m_pathFlags.read(ipath); + m_pathFlags.write(ipath, (byte) (pf | PathFlags.enumClosed)); + // if (m_segmentFlags) + // { + // m_segmentFlags.write(m_pointCount - 1, + // (byte)SegmentFlags.LineSeg)); + // m_segmentParamIndex.write(m_pointCount - 1, -1); + // } + } + } + + // Reviewed vs. Native Jan 11, 2011 + + /** + * Returns the size of the segment data for the given segment type. + * + * @param flag is one of the segment flags from the SegmentFlags enum. + * @return the size of the segment params as the number of doubles. + */ + public static int getSegmentDataSize(byte flag) { + return _segmentParamSizes[flag]; + } + + // Reviewed vs. Native Jan 11, 2011 + + /** + * Closes last path of the MultiPathImpl with the Bezier Segment. + *

+ * The start point of the Bezier is the last point of the path and the last + * point of the bezier is the first point of the path. + */ + public void closePathWithBezier(Point2D controlPoint1, Point2D controlPoint2) { + _touch(); + if (isEmptyImpl()) + throw new GeometryException( + "Invalid call. This operation cannot be performed on an empty geometry."); + + m_bPathStarted = false; + + int pathIndex = m_paths.size() - 2; + byte pf = m_pathFlags.read(pathIndex); + m_pathFlags + .write(pathIndex, + (byte) (pf | PathFlags.enumClosed | PathFlags.enumHasNonlinearSegments)); + _initSegmentData(6); + + byte oldType = m_segmentFlags + .read((byte) ((m_pointCount - 1) & SegmentFlags.enumSegmentMask)); + m_segmentFlags.write(m_pointCount - 1, + (byte) (SegmentFlags.enumBezierSeg)); + + int curveIndex = m_curveParamwritePoint; + if (getSegmentDataSize(oldType) < getSegmentDataSize((byte) SegmentFlags.enumBezierSeg)) { + m_segmentParamIndex.write(m_pointCount - 1, m_curveParamwritePoint); + m_curveParamwritePoint += 6; + } else { + // there was a closing bezier curve or an arc here. We can reuse the + // storage. + curveIndex = m_segmentParamIndex.read(m_pointCount - 1); + } + + double z; + m_segmentParams.write(curveIndex, controlPoint1.x); + m_segmentParams.write(curveIndex + 1, controlPoint1.y); + z = 0;// TODO: calculate me. + m_segmentParams.write(curveIndex + 2, z); + + m_segmentParams.write(curveIndex + 3, controlPoint2.x); + m_segmentParams.write(curveIndex + 4, controlPoint2.y); + z = 0;// TODO: calculate me. + m_segmentParams.write(curveIndex + 5, z); + } + + // Reviewed vs. Native Jan 11, 2011 + + /** + * Returns True if the given path is closed (represents a Ring). + */ + public boolean isClosedPath(int ipath) { + // Should we make a function called _UpdateClosedPathFlags and call it + // here? + return ((byte) (m_pathFlags.read(ipath) & PathFlags.enumClosed)) != 0; + } + + public boolean isClosedPathInXYPlane(int path_index) { + if (isClosedPath(path_index)) + return true; + int istart = getPathStart(path_index); + int iend = getPathEnd(path_index) - 1; + if (istart > iend) + return false; + Point2D ptS = getXY(istart); + Point2D ptE = getXY(iend); + return ptS.isEqual(ptE); + } + + // Reviewed vs. Native Jan 11, 2011 + + /** + * Returns True if the given path might have non-linear segments. + */ + public boolean hasNonLinearSegments(int ipath) { + // Should we make a function called _UpdateHasNonLinearSegmentsFlags and + // call it here? + return (m_pathFlags.read(ipath) & PathFlags.enumHasNonlinearSegments) != 0; + } + + // Reviewed vs. Native Jan 11, 2011 + public void addSegment(Segment segment, boolean bStartNewPath) { + mergeVertexDescription(segment.getDescription()); + if (segment.getType() == Type.Line) { + Point point = new Point(); + if (bStartNewPath || isEmpty()) { + segment.queryStart(point); + startPath(point); + } + + segment.queryEnd(point); + lineTo(point); + } else { + throw GeometryException.GeometryInternalError(); + } + } + + // Reviewed vs. Native Jan 11, 2011 + + /** + * adds a rectangular closed Path to the MultiPathImpl. + * + * @param envSrc is the source rectangle. + * @param bReverse Creates reversed path. + */ + public void addEnvelope(Envelope2D envSrc, boolean bReverse) { + boolean bWasEmpty = m_pointCount == 0; + + startPath(envSrc.xmin, envSrc.ymin); + if (bReverse) { + lineTo(envSrc.xmax, envSrc.ymin); + lineTo(envSrc.xmax, envSrc.ymax); + lineTo(envSrc.xmin, envSrc.ymax); + } else { + lineTo(envSrc.xmin, envSrc.ymax); + lineTo(envSrc.xmax, envSrc.ymax); + lineTo(envSrc.xmax, envSrc.ymin); + } + + closePathWithLine(); + m_bPathStarted = false; + + if (bWasEmpty && !bReverse) { + _setDirtyFlag(DirtyFlags.DirtyIsEnvelope, false);// now we no(sic?) + // the polypath + // is envelope + } + } + + // Reviewed vs. Native Jan 11, 2011 + + /** + * adds a rectangular closed Path to the MultiPathImpl. + * + * @param envSrc is the source rectangle. + * @param bReverse Creates reversed path. + */ + public void addEnvelope(Envelope envSrc, boolean bReverse) { + if (envSrc.isEmpty()) + return; + + boolean bWasEmpty = m_pointCount == 0; + Point pt = new Point(m_description);// getDescription()); + for (int i = 0, n = 4; i < n; i++) { + int j = bReverse ? n - i - 1 : i; + + envSrc.queryCornerByVal(j, pt); + if (i == 0) + startPath(pt); + else + lineTo(pt); + } + + closePathWithLine(); + m_bPathStarted = false; + + if (bWasEmpty && !bReverse) + _setDirtyFlag(DirtyFlags.DirtyIsEnvelope, false);// now we know the + // polypath is + // envelope + } + + // Reviewed vs. Native Jan 11, 2011 + public void add(MultiPathImpl src, boolean bReversePaths) { + for (int i = 0; i < src.getPathCount(); i++) + addPath(src, i, !bReversePaths); + } + + public void addPath(MultiPathImpl src, int srcPathIndex, boolean bForward) { + insertPath(-1, src, srcPathIndex, bForward); + } + + // Reviewed vs. Native Jan 11, 2011 Significant changes to last for loop + public void addPath(Point2D[] _points, int count, boolean bForward) { + insertPath(-1, _points, 0, count, bForward); + } + + public void addSegmentsFromPath(MultiPathImpl src, int src_path_index, + int src_segment_from, int src_segment_count, + boolean b_start_new_path) { + if (!b_start_new_path && getPathCount() == 0) + b_start_new_path = true; + + if (src_path_index < 0) + src_path_index = src.getPathCount() - 1; + + if (src_path_index >= src.getPathCount() || src_segment_from < 0 + || src_segment_count < 0 + || src_segment_count > src.getSegmentCount(src_path_index)) + throw new GeometryException("index out of bounds"); + + if (src_segment_count == 0) + return; + + boolean bIncludesClosingSegment = src.isClosedPath(src_path_index) + && src_segment_from + src_segment_count == src + .getSegmentCount(src_path_index); + + if (bIncludesClosingSegment && src_segment_count == 1) + return;// cannot add a closing segment alone. + + m_bPathStarted = false; + + mergeVertexDescription(src.getDescription()); + int src_point_count = src_segment_count; + int srcFromPoint = src.getPathStart(src_path_index) + src_segment_from + + 1; + if (b_start_new_path)// adding a new path. + { + src_point_count++;// add start point. + srcFromPoint--; + } + + if (bIncludesClosingSegment) { + src_point_count--; + } + + int oldPointCount = m_pointCount; + _resizeImpl(m_pointCount + src_point_count); + _verifyAllStreams(); + + if (b_start_new_path) { + if (src_point_count == 0) + return;// happens when adding a single closing segment to the + // new path + + m_paths.add(m_pointCount); + + byte flags = src.m_pathFlags.read(src_path_index); + flags &= ~(byte) PathFlags.enumCalcMask;// remove calculated flags + + if (m_bPolygon) + flags |= (byte) PathFlags.enumClosed; + + m_pathFlags.write(m_pathFlags.size() - 1, flags); + m_pathFlags.add((byte) 0); + } else { + m_paths.write(m_pathFlags.size() - 1, m_pointCount); + } + + // Index_type absoluteIndex = pathStart + before_point_index; + + for (int iattr = 0, nattr = m_description.getAttributeCount(); iattr < nattr; iattr++) { + int semantics = m_description.getSemantics(iattr); + int comp = VertexDescription.getComponentCount(semantics); + + int isrcAttr = src.m_description.getAttributeIndex(semantics); + if (isrcAttr < 0 || src.m_vertexAttributes[isrcAttr] == null) {// The + // source + // does + // not + // have + // the + // attribute. + // insert + // default + // value + double v = VertexDescription.getDefaultValue(semantics); + m_vertexAttributes[iattr].insertRange(comp * oldPointCount, v, + src_point_count * comp, comp * oldPointCount); + continue; + } + + // add vertices to the given stream + boolean b_forward = true; + m_vertexAttributes[iattr].insertRange(comp * oldPointCount, + src.m_vertexAttributes[isrcAttr], comp * srcFromPoint, + src_point_count * comp, b_forward, comp, comp + * oldPointCount); + } + + if (hasNonLinearSegments()) { + // TODO: implement me. For example as a while loop over all curves. + // Replace, calling ReplaceSegment + throw GeometryException.GeometryInternalError(); + // m_segment_flags->write_range((get_path_start(path_index) + + // before_point_index + src_point_count), (oldPointCount - + // get_path_start(path_index) - before_point_index), + // m_segment_flags, (get_path_start(path_index) + + // before_point_index), true, 1); + // m_segment_param_index->write_range((get_path_start(path_index) + + // before_point_index + src_point_count), (oldPointCount - + // get_path_start(path_index) - before_point_index), + // m_segment_param_index, (get_path_start(path_index) + + // before_point_index), true, 1); + // for (Index_type i = get_path_start(path_index) + + // before_point_index, n = get_path_start(path_index) + + // before_point_index + src_point_count; i < n; i++) + // { + // m_segment_flags->write(i, (int8_t)enum_value1(Segment_flags, + // enum_line_seg)); + // m_segment_param_index->write(i, -1); + // } + } + + if (src.hasNonLinearSegments(src_path_index)) { + // TODO: implement me. For example as a while loop over all curves. + // Replace, calling ReplaceSegment + throw GeometryException.GeometryInternalError(); + } + + notifyModified(DirtyFlags.DirtyCoordinates); + } + + // Reviewed vs. Native Jan 11, 2011 + public void reverseAllPaths() { + for (int i = 0, n = getPathCount(); i < n; i++) { + reversePath(i); + } + } + + // Reviewed vs. Native Jan 11, 2011 + public void reversePath(int pathIndex) { + _verifyAllStreams(); + int pathCount = getPathCount(); + if (pathIndex >= pathCount) + throw new IllegalArgumentException(); + + int reversedPathStart = getPathStart(pathIndex); + int reversedPathSize = getPathSize(pathIndex); + int offset = isClosedPath(pathIndex) ? 1 : 0; + + // TODO: a bug for the non linear segments here. + // There could be an issue here if someone explicity closes the path + // with the same start/end point. + for (int iattr = 0, nattr = m_description.getAttributeCount(); iattr < nattr; iattr++) { + if (m_vertexAttributes[iattr] != null) { + int semantics = m_description._getSemanticsImpl(iattr); + int comp = VertexDescription.getComponentCount(semantics); + m_vertexAttributes[iattr].reverseRange(comp + * (reversedPathStart + offset), comp + * (reversedPathSize - offset), comp); + } + } + + notifyModified(DirtyFlags.DirtyCoordinates); + } + + // Reviewed vs. Native Jan 11, 2011 + // TODO: Nonlinearsegments + public void removePath(int pathIndex) { + _verifyAllStreams(); + int pathCount = getPathCount(); + + if (pathIndex < 0) + pathIndex = pathCount - 1; + + if (pathIndex >= pathCount) + throw new IllegalArgumentException(); + + boolean bDirtyRingAreas2D = _hasDirtyFlag(DirtyFlags.DirtyRingAreas2D); + + int removedPathStart = getPathStart(pathIndex); + int removedPathSize = getPathSize(pathIndex); + + // Remove the attribute values for the path + for (int iattr = 0, nattr = m_description.getAttributeCount(); iattr < nattr; iattr++) { + if (m_vertexAttributes[iattr] != null) { + int semantics = m_description._getSemanticsImpl(iattr); + int comp = VertexDescription.getComponentCount(semantics); + m_vertexAttributes[iattr].eraseRange(comp * removedPathStart, + comp * removedPathSize, comp * m_pointCount); + } + } + + // Change the start of each path after the removed path + for (int i = pathIndex + 1; i <= pathCount; i++) { + int istart = m_paths.read(i); + m_paths.write(i - 1, istart - removedPathSize); + } + + if (m_pathFlags == null) { + for (int i = pathIndex + 1; i <= pathCount; i++) { + byte flags = m_pathFlags.read(i); + m_pathFlags.write(i - 1, flags); + } + } + + m_paths.resize(pathCount); + m_pathFlags.resize(pathCount); + m_pointCount -= removedPathSize; + m_reservedPointCount -= removedPathSize; + + notifyModified(DirtyFlags.DirtyCoordinates); + } + + // TODO: Nonlinearsegments + public void insertPath(int pathIndex, MultiPathImpl src, int srcPathIndex, + boolean bForward) { + if (src == this) + throw new IllegalArgumentException(); + + if (srcPathIndex >= src.getPathCount()) + throw new IllegalArgumentException(); + + int oldPathCount = getPathCount(); + if (pathIndex > oldPathCount) + throw new IllegalArgumentException(); + + if (pathIndex < 0) + pathIndex = oldPathCount; + + if (srcPathIndex < 0) + srcPathIndex = src.getPathCount() - 1; + + m_bPathStarted = false; + + mergeVertexDescription(src.m_description);// merge attributes from the + // source + + src._verifyAllStreams();// the source need to be correct. + + int srcPathIndexStart = src.getPathStart(srcPathIndex); + int srcPathSize = src.getPathSize(srcPathIndex); + int oldPointCount = m_pointCount; + int offset = src.isClosedPath(srcPathIndex) && !bForward ? 1 : 0; + + _resizeImpl(m_pointCount + srcPathSize); + _verifyAllStreams(); + int pathIndexStart = pathIndex < oldPathCount ? getPathStart(pathIndex) + : oldPointCount; + + // Copy all attribute values. + for (int iattr = 0, nattr = m_description.getAttributeCount(); iattr < nattr; iattr++) { + int semantics = m_description._getSemanticsImpl(iattr); + int isrcAttr = src.m_description.getAttributeIndex(semantics); + + int comp = VertexDescription.getComponentCount(semantics); + + if (isrcAttr >= 0 && src.m_vertexAttributes[isrcAttr] != null) { + if (offset != 0) + m_vertexAttributes[iattr].insertRange( + pathIndexStart * comp, + src.m_vertexAttributes[isrcAttr], comp + * srcPathIndexStart, comp, true, comp, comp + * oldPointCount); + m_vertexAttributes[iattr].insertRange((pathIndexStart + offset) + * comp, src.m_vertexAttributes[isrcAttr], comp + * (srcPathIndexStart + offset), comp + * (srcPathSize - offset), bForward, comp, comp + * (oldPointCount + offset)); + } else { + // Need to make room for the attributes, so we copy default + // values in + + double v = VertexDescription.getDefaultValue(semantics); + m_vertexAttributes[iattr].insertRange(pathIndexStart * comp, v, + comp * srcPathSize, comp * oldPointCount); + } + } + + int newPointCount = oldPointCount + srcPathSize; + m_paths.add(newPointCount); + + for (int ipath = oldPathCount; ipath >= pathIndex + 1; ipath--) { + int iend = m_paths.read(ipath - 1); + m_paths.write(ipath, iend + srcPathSize); + } + + // ========================== todo: NonLinearSegments ================= + if (src.hasNonLinearSegments(srcPathIndex)) { + + } + + m_pathFlags.add((byte) 0); + + // _ASSERT(m_pathFlags.size() == m_paths.size()); + + for (int ipath = oldPathCount - 1; ipath >= pathIndex + 1; ipath--) { + byte flags = m_pathFlags.read(ipath); + flags &= ~(byte) PathFlags.enumCalcMask;// remove calculated flags + m_pathFlags.write(ipath + 1, flags); + } + + AttributeStreamOfInt8 srcPathFlags = src.getPathFlagsStreamRef(); + byte flags = srcPathFlags.read(srcPathIndex); + flags &= ~(byte) PathFlags.enumCalcMask;// remove calculated flags + + if (m_bPolygon) + flags |= (byte) PathFlags.enumClosed; + + m_pathFlags.write(pathIndex, flags); + } + + public void insertPath(int pathIndex, Point2D[] points, int pointsOffset, + int count, boolean bForward) { + int oldPathCount = getPathCount(); + if (pathIndex > oldPathCount) + throw new IllegalArgumentException(); + + if (pathIndex < 0) + pathIndex = oldPathCount; + + m_bPathStarted = false; + + int oldPointCount = m_pointCount; + + // Copy all attribute values. + if (points != null) { + _resizeImpl(m_pointCount + count); + _verifyAllStreams(); + + int pathStart = pathIndex < oldPathCount ? getPathStart(pathIndex) + : oldPointCount; + + for (int iattr = 0, nattr = m_description.getAttributeCount(); iattr < nattr; iattr++) { + int semantics = m_description._getSemanticsImpl(iattr); + + if (semantics == VertexDescription.Semantics.POSITION) { + // copy range to make place for new vertices + m_vertexAttributes[iattr].writeRange( + 2 * (pathStart + count), + 2 * (oldPointCount - pathIndex), + m_vertexAttributes[iattr], 2 * pathStart, true, 2); + + AttributeStreamOfDbl position = (AttributeStreamOfDbl) (AttributeStreamBase) getAttributeStreamRef(semantics); + + int j = pathStart; + for (int i = 0; i < count; i++, j++) { + int index = (bForward ? pointsOffset + i : pointsOffset + + count - i - 1); + position.write(2 * j, points[index].x); + position.write(2 * j + 1, points[index].y); + } + } else { + // Need to make room for the attributes, so we copy default + // values in + + int comp = VertexDescription.getComponentCount(semantics); + double v = VertexDescription.getDefaultValue(semantics); + m_vertexAttributes[iattr].insertRange(pathStart * comp, v, + comp * count, comp * oldPointCount); + } + } + } else { + _verifyAllStreams(); + } + + m_paths.add(m_pointCount); + + for (int ipath = oldPathCount; ipath >= pathIndex + 1; ipath--) { + int iend = m_paths.read(ipath - 1); + m_paths.write(ipath, iend + count); + } + + m_pathFlags.add((byte) 0); + + // _ASSERT(m_pathFlags.size() == m_paths.size()); + + for (int ipath = oldPathCount - 1; ipath >= pathIndex + 1; ipath--) { + byte flags = m_pathFlags.read(ipath); + flags &= ~(byte) PathFlags.enumCalcMask;// remove calculated flags + m_pathFlags.write(ipath + 1, flags); + } + + if (m_bPolygon) + m_pathFlags.write(pathIndex, (byte) PathFlags.enumClosed); + } + + public void insertPoints(int pathIndex, int beforePointIndex, + MultiPathImpl src, int srcPathIndex, int srcPointIndexFrom, + int srcPointCount, boolean bForward) { + if (pathIndex < 0) + pathIndex = getPathCount(); + + if (srcPathIndex < 0) + srcPathIndex = src.getPathCount() - 1; + + if (pathIndex > getPathCount() || beforePointIndex >= 0 + && beforePointIndex > getPathSize(pathIndex) + || srcPathIndex >= src.getPathCount() + || srcPointCount > src.getPathSize(srcPathIndex)) + throw new GeometryException("index out of bounds"); + + if (srcPointCount == 0) + return; + + mergeVertexDescription(src.m_description); + + if (pathIndex == getPathCount())// adding a new path. + { + m_paths.add(m_pointCount); + + byte flags = src.m_pathFlags.read(srcPathIndex); + flags &= ~(byte) PathFlags.enumCalcMask;// remove calculated flags + + if (!m_bPolygon) + m_pathFlags.add(flags); + else + m_pathFlags.add((byte) (flags | PathFlags.enumClosed)); + } + + if (beforePointIndex < 0) + beforePointIndex = getPathSize(pathIndex); + + int oldPointCount = m_pointCount; + _resizeImpl(m_pointCount + srcPointCount); + _verifyAllStreams(); + src._verifyAllStreams(); + + int pathStart = getPathStart(pathIndex); + int absoluteIndex = pathStart + beforePointIndex; + + if (srcPointCount < 0) + srcPointCount = src.getPathSize(srcPathIndex); + + int srcPathStart = src.getPathStart(srcPathIndex); + int srcAbsoluteIndex = srcPathStart + srcPointCount; + + for (int iattr = 0, nattr = m_description.getAttributeCount(); iattr < nattr; iattr++) { + int semantics = m_description._getSemanticsImpl(iattr); + int comp = VertexDescription.getComponentCount(semantics); + + int isrcAttr = src.m_description.getAttributeIndex(semantics); + if (isrcAttr < 0 || src.m_vertexAttributes[isrcAttr] == null) // The + // source + // does + // not + // have + // the + // attribute. + { + double v = VertexDescription.getDefaultValue(semantics); + m_vertexAttributes[iattr].insertRange(comp * absoluteIndex, v, + srcAbsoluteIndex * comp, comp * oldPointCount); + continue; + } + + // add vertices to the given stream + m_vertexAttributes[iattr].insertRange(comp + * (pathStart + beforePointIndex), + src.m_vertexAttributes[isrcAttr], comp + * (srcPathStart + srcPointIndexFrom), srcPointCount + * comp, bForward, comp, comp * oldPointCount); + } + + if (hasNonLinearSegments()) {// TODO: probably a bug here when a new + // path is added. + m_segmentFlags.writeRange((getPathStart(pathIndex) + + beforePointIndex + srcPointCount), (oldPointCount + - getPathStart(pathIndex) - beforePointIndex), + m_segmentFlags, + (getPathStart(pathIndex) + beforePointIndex), true, 1); + m_segmentParamIndex.writeRange((getPathStart(pathIndex) + + beforePointIndex + srcPointCount), (oldPointCount + - getPathStart(pathIndex) - beforePointIndex), + m_segmentParamIndex, + (getPathStart(pathIndex) + beforePointIndex), true, 1); + for (int i = getPathStart(pathIndex) + beforePointIndex, n = getPathStart(pathIndex) + + beforePointIndex + srcPointCount; i < n; i++) { + m_segmentFlags.write(i, (byte) SegmentFlags.enumLineSeg); + m_segmentParamIndex.write(i, -1); + } + } + + if (src.hasNonLinearSegments(srcPathIndex)) { + // TODO: implement me. For example as a while loop over all curves. + // Replace, calling ReplaceSegment + throw GeometryException.GeometryInternalError(); + } + + for (int ipath = pathIndex + 1, npaths = getPathCount(); ipath <= npaths; ipath++) { + int num = m_paths.read(ipath); + m_paths.write(ipath, num + srcPointCount); + } + } + + public void insertPoints(int pathIndex, int beforePointIndex, + Point2D[] src, int srcPointIndexFrom, int srcPointCount, + boolean bForward) { + if (pathIndex < 0) + pathIndex = getPathCount(); + + if (pathIndex > getPathCount() + || beforePointIndex > getPathSize(pathIndex) + || srcPointIndexFrom < 0 || srcPointCount > src.length) + throw new GeometryException("index out of bounds"); + + if (srcPointCount == 0) + return; + + if (pathIndex == getPathCount())// adding a new path. + { + m_paths.add(m_pointCount); + + if (!m_bPolygon) + m_pathFlags.add((byte) 0); + else + m_pathFlags.add((byte) PathFlags.enumClosed); + } + + if (beforePointIndex < 0) + beforePointIndex = getPathSize(pathIndex); + + _verifyAllStreams(); + int oldPointCount = m_pointCount; + _resizeImpl(m_pointCount + srcPointCount); + _verifyAllStreams(); + for (int iattr = 0, nattr = m_description.getAttributeCount(); iattr < nattr; iattr++) { + int semantics = m_description._getSemanticsImpl(iattr); + int comp = VertexDescription.getComponentCount(semantics); + // copy range to make place for new vertices + m_vertexAttributes[iattr] + .writeRange( + comp + * (getPathStart(pathIndex) + + beforePointIndex + srcPointCount), + (oldPointCount - getPathStart(pathIndex) - beforePointIndex) + * comp, + m_vertexAttributes[iattr], + comp * (getPathStart(pathIndex) + beforePointIndex), + true, comp); + + if (iattr == 0) { + // add vertices to the given stream + ((AttributeStreamOfDbl) (AttributeStreamBase) m_vertexAttributes[iattr]) + .writeRange(comp + * (getPathStart(pathIndex) + beforePointIndex), + srcPointCount, src, srcPointIndexFrom, bForward); + } else { + double v = VertexDescription.getDefaultValue(semantics); + m_vertexAttributes[iattr].setRange(v, + (getPathStart(pathIndex) + beforePointIndex) * comp, + srcPointCount * comp); + } + } + + if (hasNonLinearSegments()) { + m_segmentFlags.writeRange((getPathStart(pathIndex) + + beforePointIndex + srcPointCount), (oldPointCount + - getPathStart(pathIndex) - beforePointIndex), + m_segmentFlags, + (getPathStart(pathIndex) + beforePointIndex), true, 1); + m_segmentParamIndex.writeRange((getPathStart(pathIndex) + + beforePointIndex + srcPointCount), (oldPointCount + - getPathStart(pathIndex) - beforePointIndex), + m_segmentParamIndex, + (getPathStart(pathIndex) + beforePointIndex), true, 1); + m_segmentFlags.setRange((byte) SegmentFlags.enumLineSeg, + getPathStart(pathIndex) + beforePointIndex, srcPointCount); + m_segmentParamIndex.setRange(-1, getPathStart(pathIndex) + + beforePointIndex, srcPointCount); + } + + for (int ipath = pathIndex + 1, npaths = getPathCount(); ipath <= npaths; ipath++) { + m_paths.write(ipath, m_paths.read(ipath) + srcPointCount); + } + } + + public void insertPoint(int pathIndex, int beforePointIndex, Point2D pt) { + int pathCount = getPathCount(); + + if (pathIndex < 0) + pathIndex = getPathCount(); + + if (pathIndex >= pathCount || beforePointIndex > getPathSize(pathIndex)) + throw new GeometryException("index out of bounds"); + + if (pathIndex == getPathCount())// adding a new path. + { + m_paths.add(m_pointCount); + + if (!m_bPolygon) + m_pathFlags.add((byte) 0); + else + m_pathFlags.add((byte) PathFlags.enumClosed); + } + + if (beforePointIndex < 0) + beforePointIndex = getPathSize(pathIndex); + + int oldPointCount = m_pointCount; + _resizeImpl(m_pointCount + 1); + _verifyAllStreams(); + + int pathStart = getPathStart(pathIndex); + + ((AttributeStreamOfDbl) (AttributeStreamBase) m_vertexAttributes[0]) + .insert(2 * (pathStart + beforePointIndex), pt, + 2 * oldPointCount); + + for (int iattr = 1, nattr = m_description.getAttributeCount(); iattr < nattr; iattr++) { + int semantics = m_description._getSemanticsImpl(iattr); + int comp = VertexDescription.getComponentCount(semantics); + + // Need to make room for the attribute, so we copy a default value + // in + double v = VertexDescription.getDefaultValue(semantics); + m_vertexAttributes[iattr].insertRange(comp + * (pathStart + beforePointIndex), v, comp, comp + * oldPointCount); + } + + for (int ipath = pathIndex + 1, npaths = pathCount; ipath <= npaths; ipath++) { + m_paths.write(ipath, m_paths.read(ipath) + 1); + } + } + + public void insertPoint(int pathIndex, int beforePointIndex, Point pt) { + int pathCount = getPathCount(); + + if (pathIndex < 0) + pathIndex = getPathCount(); + + if (pathIndex >= pathCount || beforePointIndex > getPathSize(pathIndex)) + throw new GeometryException("index out of bounds"); + + if (pathIndex == getPathCount())// adding a new path. + { + m_paths.add(m_pointCount); + + if (!m_bPolygon) + m_pathFlags.add((byte) 0); + else + m_pathFlags.add((byte) PathFlags.enumClosed); + } + + if (beforePointIndex < 0) + beforePointIndex = getPathSize(pathIndex); + + mergeVertexDescription(pt.getDescription()); + int oldPointCount = m_pointCount; + _resizeImpl(m_pointCount + 1); + _verifyAllStreams(); + + int pathStart = getPathStart(pathIndex); + + for (int iattr = 0, nattr = m_description.getAttributeCount(); iattr < nattr; iattr++) { + int semantics = m_description._getSemanticsImpl(iattr); + int comp = VertexDescription.getComponentCount(semantics); + + if (pt.hasAttribute(semantics)) { + m_vertexAttributes[iattr].insertAttributes(comp + * (pathStart + beforePointIndex), pt, semantics, comp + * oldPointCount); + } else { + // Need to make room for the attribute, so we copy a default + // value in + double v = VertexDescription.getDefaultValue(semantics); + m_vertexAttributes[iattr].insertRange(comp + * (pathStart + beforePointIndex), v, comp, comp + * oldPointCount); + } + } + + for (int ipath = pathIndex + 1, npaths = pathCount; ipath <= npaths; ipath++) { + m_paths.write(ipath, m_paths.read(ipath) + 1); + } + + notifyModified(DirtyFlags.DirtyCoordinates); + } + + public void removePoint(int pathIndex, int pointIndex) { + int pathCount = getPathCount(); + + if (pathIndex < 0) + pathIndex = pathCount - 1; - for (int ipath = pathIndex + 1, npaths = pathCount; ipath <= npaths; ipath++) { - m_paths.write(ipath, m_paths.read(ipath) + 1); - } + if (pathIndex >= pathCount || pointIndex >= getPathSize(pathIndex)) + throw new GeometryException("index out of bounds"); - notifyModified(DirtyFlags.DirtyCoordinates); - } + _verifyAllStreams(); - public void removePoint(int pathIndex, int pointIndex) { - int pathCount = getPathCount(); + int pathStart = getPathStart(pathIndex); - if (pathIndex < 0) - pathIndex = pathCount - 1; + if (pointIndex < 0) + pointIndex = getPathSize(pathIndex) - 1; - if (pathIndex >= pathCount || pointIndex >= getPathSize(pathIndex)) - throw new GeometryException("index out of bounds"); + int absoluteIndex = pathStart + pointIndex; - _verifyAllStreams(); + // Remove the attribute values for the path + for (int iattr = 0, nattr = m_description.getAttributeCount(); iattr < nattr; iattr++) { + if (m_vertexAttributes[iattr] != null) { + int semantics = m_description._getSemanticsImpl(iattr); + int comp = VertexDescription.getComponentCount(semantics); + m_vertexAttributes[iattr].eraseRange(comp * absoluteIndex, + comp, comp * m_pointCount); + } + } + + for (int ipath = pathCount; ipath >= pathIndex + 1; ipath--) { + int iend = m_paths.read(ipath); + m_paths.write(ipath, iend - 1); + } + + m_pointCount--; + m_reservedPointCount--; + notifyModified(DirtyFlags.DirtyCoordinates); + } + + public double calculatePathLength2D(int pathIndex) /* const */ { + SegmentIteratorImpl segIter = querySegmentIteratorAtVertex(getPathStart(pathIndex)); + + MathUtils.KahanSummator len = new MathUtils.KahanSummator(0); + while (segIter.hasNextSegment()) { + len.add(segIter.nextSegment().calculateLength2D()); + } + + return len.getResult(); + } + + double calculateSubLength2D(int from_path_index, int from_point_index, + int to_path_index, int to_point_index) { + int absolute_from_index = getPathStart(from_path_index) + + from_point_index; + int absolute_to_index = getPathStart(to_path_index) + to_point_index; + + if (absolute_to_index < absolute_from_index || absolute_from_index < 0 + || absolute_to_index > getPointCount() - 1) + throw new IllegalArgumentException(); + + SegmentIteratorImpl seg_iter = querySegmentIterator(); + + double sub_length = 0.0; + + seg_iter.resetToVertex(absolute_from_index); + + do { + while (seg_iter.hasNextSegment()) { + Segment segment = seg_iter.nextSegment(); + + if (seg_iter.getStartPointIndex() == absolute_to_index) + break; + + double segment_length = segment.calculateLength2D(); + sub_length += segment_length; + } + + if (seg_iter.getStartPointIndex() == absolute_to_index) + break; + + } while (seg_iter.nextPath()); + + return sub_length; + } + + double calculateSubLength2D(int path_index, int from_point_index, + int to_point_index) { + int absolute_from_index = getPathStart(path_index) + from_point_index; + int absolute_to_index = getPathStart(path_index) + to_point_index; + + if (absolute_from_index < 0 || absolute_to_index > getPointCount() - 1) + throw new IllegalArgumentException(); + + SegmentIteratorImpl seg_iter = querySegmentIterator(); + + if (absolute_from_index > absolute_to_index) { + if (!isClosedPath(path_index)) + throw new IllegalArgumentException( + "cannot iterate across an open path"); + + seg_iter.setCirculator(true); + } + + double prev_length = 0.0; + double sub_length = 0.0; + + seg_iter.resetToVertex(absolute_from_index); + + do { + assert (seg_iter.hasNextSegment()); + sub_length += prev_length; + Segment segment = seg_iter.nextSegment(); + prev_length = segment.calculateLength2D(); + + } while (seg_iter.getStartPointIndex() != absolute_to_index); + + return sub_length; + } + + @Override + public Geometry getBoundary() { + return Boundary.calculate(this, null); + } + + // TODO: Add code fore interpolation type (none and angular) + void interpolateAttributes(int from_path_index, int from_point_index, + int to_path_index, int to_point_index) { + for (int ipath = from_path_index; ipath < to_path_index - 1; ipath++) { + if (isClosedPath(ipath)) + throw new IllegalArgumentException( + "cannot interpolate across closed paths"); + } + + int nattr = m_description.getAttributeCount(); + + if (nattr == 1) + return; // only has position + + double sub_length = calculateSubLength2D(from_path_index, + from_point_index, to_path_index, to_point_index); + + if (sub_length == 0.0) + return; + + for (int iattr = 1; iattr < nattr; iattr++) { + int semantics = m_description.getSemantics(iattr); + + int interpolation = VertexDescription.getInterpolation(semantics); + if (interpolation == VertexDescription.Interpolation.ANGULAR) + continue; + + int components = VertexDescription.getComponentCount(semantics); + + for (int ordinate = 0; ordinate < components; ordinate++) + interpolateAttributes_(semantics, from_path_index, + from_point_index, to_path_index, to_point_index, + sub_length, ordinate); + } + } + + // TODO: Add code for interpolation type (none and angular) + void interpolateAttributesForSemantics(int semantics, int from_path_index, + int from_point_index, int to_path_index, int to_point_index) { + if (semantics == VertexDescription.Semantics.POSITION) + return; + + if (!hasAttribute(semantics)) + throw new IllegalArgumentException( + "does not have the given attribute"); + + int interpolation = VertexDescription.getInterpolation(semantics); + if (interpolation == VertexDescription.Interpolation.ANGULAR) + throw new IllegalArgumentException( + "not implemented for the given semantics"); + + for (int ipath = from_path_index; ipath < to_path_index - 1; ipath++) { + if (isClosedPath(ipath)) + throw new IllegalArgumentException( + "cannot interpolate across closed paths"); + } + + double sub_length = calculateSubLength2D(from_path_index, + from_point_index, to_path_index, to_point_index); + + if (sub_length == 0.0) + return; + + int components = VertexDescription.getComponentCount(semantics); + + for (int ordinate = 0; ordinate < components; ordinate++) + interpolateAttributes_(semantics, from_path_index, + from_point_index, to_path_index, to_point_index, + sub_length, ordinate); + } + + void interpolateAttributes(int path_index, int from_point_index, + int to_point_index) { + int nattr = m_description.getAttributeCount(); + + if (nattr == 1) + return; // only has position + + double sub_length = calculateSubLength2D(path_index, from_point_index, + to_point_index); + + if (sub_length == 0.0) + return; + + for (int iattr = 1; iattr < nattr; iattr++) { + int semantics = m_description.getSemantics(iattr); + + int interpolation = VertexDescription.getInterpolation(semantics); + if (interpolation == VertexDescription.Interpolation.ANGULAR) + continue; + + int components = VertexDescription.getComponentCount(semantics); + + for (int ordinate = 0; ordinate < components; ordinate++) + interpolateAttributes_(semantics, path_index, from_point_index, + to_point_index, sub_length, ordinate); + } + } + + void interpolateAttributesForSemantics(int semantics, int path_index, + int from_point_index, int to_point_index) { + if (semantics == VertexDescription.Semantics.POSITION) + return; + + if (!hasAttribute(semantics)) + throw new IllegalArgumentException( + "does not have the given attribute"); + + int interpolation = VertexDescription.getInterpolation(semantics); + if (interpolation == VertexDescription.Interpolation.ANGULAR) + throw new IllegalArgumentException( + "not implemented for the given semantics"); + + double sub_length = calculateSubLength2D(path_index, from_point_index, + to_point_index); + + if (sub_length == 0.0) + return; + + int components = VertexDescription.getComponentCount(semantics); + + for (int ordinate = 0; ordinate < components; ordinate++) + interpolateAttributes_(semantics, path_index, from_point_index, + to_point_index, sub_length, ordinate); + } + + // TODO: Add code fore interpolation type (none and angular) + void interpolateAttributes_(int semantics, int from_path_index, + int from_point_index, int to_path_index, int to_point_index, + double sub_length, int ordinate) { + SegmentIteratorImpl seg_iter = querySegmentIterator(); + + int absolute_from_index = getPathStart(from_path_index) + + from_point_index; + int absolute_to_index = getPathStart(to_path_index) + to_point_index; + + double from_attribute = getAttributeAsDbl(semantics, + absolute_from_index, ordinate); + double to_attribute = getAttributeAsDbl(semantics, absolute_to_index, + ordinate); + double interpolated_attribute = from_attribute; + double cumulative_length = 0.0; + + seg_iter.resetToVertex(absolute_from_index); + + do { + if (seg_iter.hasNextSegment()) { + seg_iter.nextSegment(); + + if (seg_iter.getStartPointIndex() == absolute_to_index) + return; + + setAttribute(semantics, seg_iter.getStartPointIndex(), + ordinate, interpolated_attribute); + + seg_iter.previousSegment(); + + do { + Segment segment = seg_iter.nextSegment(); + + if (seg_iter.getEndPointIndex() == absolute_to_index) + return; + + double segment_length = segment.calculateLength2D(); + cumulative_length += segment_length; + double t = cumulative_length / sub_length; + interpolated_attribute = MathUtils.lerp(from_attribute, to_attribute, t); + + if (!seg_iter.isClosingSegment()) + setAttribute(semantics, seg_iter.getEndPointIndex(), + ordinate, interpolated_attribute); + + } while (seg_iter.hasNextSegment()); + } + + } while (seg_iter.nextPath()); + } + + void interpolateAttributes_(int semantics, int path_index, + int from_point_index, int to_point_index, double sub_length, + int ordinate) { + assert (m_bPolygon); + SegmentIteratorImpl seg_iter = querySegmentIterator(); + + int absolute_from_index = getPathStart(path_index) + from_point_index; + int absolute_to_index = getPathStart(path_index) + to_point_index; + + if (absolute_to_index == absolute_from_index) + return; + + double from_attribute = getAttributeAsDbl(semantics, + absolute_from_index, ordinate); + double to_attribute = getAttributeAsDbl(semantics, absolute_to_index, + ordinate); + double cumulative_length = 0.0; + + seg_iter.resetToVertex(absolute_from_index); + seg_iter.setCirculator(true); + + double prev_interpolated_attribute = from_attribute; + + do { + Segment segment = seg_iter.nextSegment(); + setAttribute(semantics, seg_iter.getStartPointIndex(), ordinate, + prev_interpolated_attribute); + + double segment_length = segment.calculateLength2D(); + cumulative_length += segment_length; + double t = cumulative_length / sub_length; + prev_interpolated_attribute = MathUtils.lerp(from_attribute, to_attribute, t); + + } while (seg_iter.getEndPointIndex() != absolute_to_index); + } + + @Override + public void setEmpty() { + m_curveParamwritePoint = 0; + m_bPathStarted = false; + m_paths = null; + m_pathFlags = null; + m_segmentParamIndex = null; + m_segmentFlags = null; + m_segmentParams = null; + _setEmptyImpl(); + } + + @Override + public void applyTransformation(Transformation2D transform) { + applyTransformation(transform, -1); + } + + public void applyTransformation(Transformation2D transform, int pathIndex) { + if (isEmpty()) + return; + + if (transform.isIdentity()) + return; + + _verifyAllStreams(); + AttributeStreamOfDbl points = (AttributeStreamOfDbl) m_vertexAttributes[0]; + Point2D ptStart = new Point2D(); + Point2D ptControl = new Point2D(); + + boolean bHasNonLinear; + int fistIdx; + int lastIdx; + if (pathIndex < 0) { + bHasNonLinear = hasNonLinearSegments(); + fistIdx = 0; + lastIdx = m_pointCount; + } else { + bHasNonLinear = hasNonLinearSegments(pathIndex); + fistIdx = getPathStart(pathIndex); + lastIdx = getPathEnd(pathIndex); + } + + for (int ipoint = fistIdx; ipoint < lastIdx; ipoint++) { + ptStart.x = points.read(ipoint * 2); + ptStart.y = points.read(ipoint * 2 + 1); + + if (bHasNonLinear) { + int segIndex = m_segmentParamIndex.read(ipoint); + if (segIndex >= 0) { + int segmentType = (int) m_segmentFlags.read(ipoint); + int type = segmentType & SegmentFlags.enumSegmentMask; + switch (type) { + case SegmentFlags.enumBezierSeg: { + ptControl.x = m_segmentParams.read(segIndex); + ptControl.y = m_segmentParams.read(segIndex + 1); + transform.transform(ptControl, ptControl); + m_segmentParams.write(segIndex, ptControl.x); + m_segmentParams.write(segIndex + 1, ptControl.y); + + ptControl.x = m_segmentParams.read(segIndex + 3); + ptControl.y = m_segmentParams.read(segIndex + 4); + transform.transform(ptControl, ptControl); + m_segmentParams.write(segIndex + 3, ptControl.x); + m_segmentParams.write(segIndex + 4, ptControl.y); + } + break; + case SegmentFlags.enumArcSeg: + throw GeometryException.GeometryInternalError(); + + } + } + } + + transform.transform(ptStart, ptStart); + points.write(ipoint * 2, ptStart.x); + points.write(ipoint * 2 + 1, ptStart.y); + } + + notifyModified(DirtyFlags.DirtyCoordinates); + // REFACTOR: reset the exact envelope only and transform the loose + // envelope + } + + @Override + public void applyTransformation(Transformation3D transform) { + if (isEmpty()) + return; + + addAttribute(VertexDescription.Semantics.Z); + _verifyAllStreams(); + AttributeStreamOfDbl points = (AttributeStreamOfDbl) m_vertexAttributes[0]; + AttributeStreamOfDbl zs = (AttributeStreamOfDbl) m_vertexAttributes[1]; + Point3D ptStart = new Point3D(); + Point3D ptControl = new Point3D(); + boolean bHasNonLinear = hasNonLinearSegments(); + for (int ipoint = 0; ipoint < m_pointCount; ipoint++) { + ptStart.x = points.read(ipoint * 2); + ptStart.y = points.read(ipoint * 2 + 1); + ptStart.z = zs.read(ipoint); + + if (bHasNonLinear) { + int segIndex = m_segmentParamIndex.read(ipoint); + if (segIndex >= 0) { + int segmentType = (int) m_segmentFlags.read(ipoint); + int type = segmentType & (int) SegmentFlags.enumSegmentMask; + switch (type) { + case SegmentFlags.enumBezierSeg: { + ptControl.x = m_segmentParams.read(segIndex); + ptControl.y = m_segmentParams.read(segIndex + 1); + ptControl.z = m_segmentParams.read(segIndex + 2); + ptControl = transform.transform(ptControl); + m_segmentParams.write(segIndex, ptControl.x); + m_segmentParams.write(segIndex + 1, ptControl.y); + m_segmentParams.write(segIndex + 1, ptControl.z); + + ptControl.x = m_segmentParams.read(segIndex + 3); + ptControl.y = m_segmentParams.read(segIndex + 4); + ptControl.z = m_segmentParams.read(segIndex + 5); + ptControl = transform.transform(ptControl); + m_segmentParams.write(segIndex + 3, ptControl.x); + m_segmentParams.write(segIndex + 4, ptControl.y); + m_segmentParams.write(segIndex + 5, ptControl.z); + } + break; + case SegmentFlags.enumArcSeg: + throw GeometryException.GeometryInternalError(); + + } + } + } + + ptStart = transform.transform(ptStart); + points.write(ipoint * 2, ptStart.x); + points.write(ipoint * 2 + 1, ptStart.y); + zs.write(ipoint, ptStart.z); + } + + // REFACTOR: reset the exact envelope only and transform the loose + // envelope + + notifyModified(DirtyFlags.DirtyCoordinates); + } + + @Override + protected void _verifyStreamsImpl() { + if (m_paths == null) { + m_paths = (AttributeStreamOfInt32) AttributeStreamBase + .createIndexStream(1, 0); + m_pathFlags = (AttributeStreamOfInt8) AttributeStreamBase + .createByteStream(1, (byte) 0); + } + + if (m_segmentFlags != null) { + m_segmentFlags.resize(m_reservedPointCount, + (byte) SegmentFlags.enumLineSeg); + m_segmentParamIndex.resize(m_reservedPointCount, -1); + } + } + + @Override + void _copyToImpl(MultiVertexGeometryImpl dst) { + MultiPathImpl dstPoly = (MultiPathImpl) dst; + dstPoly.m_bPathStarted = false; + dstPoly.m_curveParamwritePoint = m_curveParamwritePoint; + dstPoly.m_fill_rule = m_fill_rule; + + if (m_paths != null) + dstPoly.m_paths = new AttributeStreamOfInt32(m_paths); + else + dstPoly.m_paths = null; + + if (m_pathFlags != null) + dstPoly.m_pathFlags = new AttributeStreamOfInt8(m_pathFlags); + else + dstPoly.m_pathFlags = null; + + if (m_segmentParamIndex != null) + dstPoly.m_segmentParamIndex = new AttributeStreamOfInt32( + m_segmentParamIndex); + else + dstPoly.m_segmentParamIndex = null; + + if (m_segmentFlags != null) + dstPoly.m_segmentFlags = new AttributeStreamOfInt8(m_segmentFlags); + else + dstPoly.m_segmentFlags = null; + + if (m_segmentParams != null) + dstPoly.m_segmentParams = new AttributeStreamOfDbl(m_segmentParams); + else + dstPoly.m_segmentParams = null; + + dstPoly.m_cachedLength2D = m_cachedLength2D; + dstPoly.m_cachedArea2D = m_cachedArea2D; + + if (!_hasDirtyFlag(DirtyFlags.DirtyRingAreas2D)) { + dstPoly.m_cachedRingAreas2D = (AttributeStreamOfDbl) m_cachedRingAreas2D; + } else + dstPoly.m_cachedRingAreas2D = null; + + } + + @Override + public double calculateLength2D() { + if (!_hasDirtyFlag(DirtyFlags.DirtyLength2D)) { + return m_cachedLength2D; + } + + SegmentIteratorImpl segIter = querySegmentIterator(); + MathUtils.KahanSummator len = new MathUtils.KahanSummator(0); + while (segIter.nextPath()) { + while (segIter.hasNextSegment()) { + len.add(segIter.nextSegment().calculateLength2D()); + } + } + + m_cachedLength2D = len.getResult(); + _setDirtyFlag(DirtyFlags.DirtyLength2D, false); + + return len.getResult(); + } + + @Override + public boolean equals(Object other) { + if (other == this) + return true; + + if (!(other instanceof MultiPathImpl)) + return false; + + if (!super.equals(other)) + return false; + + MultiPathImpl otherMultiPath = (MultiPathImpl) other; + + int pathCount = getPathCount(); + int pathCountOther = otherMultiPath.getPathCount(); + + if (pathCount != pathCountOther) + return false; + + if (pathCount > 0 && m_paths != null + && !m_paths.equals(otherMultiPath.m_paths, 0, pathCount + 1)) + return false; + + if (m_fill_rule != otherMultiPath.m_fill_rule) + return false; + + { + // Note: OGC flags do not participate in the equals operation by + // design. + // Because for the polygon pathFlags will have all enum_closed set, + // we do not need to compare this stream. Only for polyline. + // Polyline does not have OGC flags set. + if (!m_bPolygon) { + if (m_pathFlags != null + && !m_pathFlags.equals(otherMultiPath.m_pathFlags, 0, + pathCount)) + return false; + } + } + + return super.equals(other); + } + + /** + * Returns a SegmentIterator that set to a specific vertex of the + * MultiPathImpl. The call to NextSegment will return the segment that + * starts at the vertex. Call to PreviousSegment will return the segment + * that starts at the previous vertex. + */ + public SegmentIteratorImpl querySegmentIteratorAtVertex(int startVertexIndex) { + if (startVertexIndex < 0 || startVertexIndex >= getPointCount()) + throw new IndexOutOfBoundsException(); + + SegmentIteratorImpl iter = new SegmentIteratorImpl(this, + startVertexIndex); + return iter; + } + + // void QuerySegmentIterator(int fromVertex, SegmentIterator iterator); + public SegmentIteratorImpl querySegmentIterator() { + return new SegmentIteratorImpl(this); + } + + @Override + public void _updateXYImpl(boolean bExact) { + super._updateXYImpl(bExact); + boolean bHasCurves = hasNonLinearSegments(); + if (bHasCurves) { + SegmentIteratorImpl segIter = querySegmentIterator(); + while (segIter.nextPath()) { + while (segIter.hasNextSegment()) { + Segment curve = segIter.nextCurve(); + if (curve != null) { + Envelope2D env2D = new Envelope2D(); + curve.queryEnvelope2D(env2D); + m_envelope.merge(env2D); + } else + break; + } + } + } + } + + @Override + void calculateEnvelope2D(Envelope2D env, boolean bExact) { + super.calculateEnvelope2D(env, bExact); + boolean bHasCurves = hasNonLinearSegments(); + if (bHasCurves) { + SegmentIteratorImpl segIter = querySegmentIterator(); + while (segIter.nextPath()) { + while (segIter.hasNextSegment()) { + Segment curve = segIter.nextCurve(); + if (curve != null) { + Envelope2D env2D = new Envelope2D(); + curve.queryEnvelope2D(env2D); + env.merge(env2D); + } else + break; + } + } + } + } + + @Override + public void _notifyModifiedAllImpl() { + if (m_paths == null || m_paths.size() == 0)// if (m_paths == null || + // !m_paths.size()) + m_pointCount = 0; + else + m_pointCount = m_paths.read(m_paths.size() - 1); + } + + @Override + public double calculateArea2D() { + if (!m_bPolygon) + return 0.0; + + _updateRingAreas2D(); + + return m_cachedArea2D; + } + + /** + * Returns True if the ring is an exterior ring. Valid only for simple + * polygons. + */ + public boolean isExteriorRing(int ringIndex) { + if (!m_bPolygon) + return false; + + if (!_hasDirtyFlag(DirtyFlags.DirtyOGCFlags)) + return (m_pathFlags.read(ringIndex) & (byte) PathFlags.enumOGCStartPolygon) != 0; + + _updateRingAreas2D(); + return m_cachedRingAreas2D.read(ringIndex) > 0; + // Should we make a function called _UpdateHasNonLinearSegmentsFlags and + // call it here? + } + + public double calculateRingArea2D(int pathIndex) { + if (!m_bPolygon) + return 0.0; + + _updateRingAreas2D(); + + return m_cachedRingAreas2D.read(pathIndex); + } + + public void _updateRingAreas2D() { + if (_hasDirtyFlag(DirtyFlags.DirtyRingAreas2D)) { + int pathCount = getPathCount(); + + if (m_cachedRingAreas2D == null) + m_cachedRingAreas2D = new AttributeStreamOfDbl(pathCount); + else if (m_cachedRingAreas2D.size() != pathCount) + m_cachedRingAreas2D.resize(pathCount); + + MathUtils.KahanSummator totalArea = new MathUtils.KahanSummator(0); + MathUtils.KahanSummator pathArea = new MathUtils.KahanSummator(0); + Point2D pt = new Point2D(); + int ipath = 0; + SegmentIteratorImpl segIter = querySegmentIterator(); + while (segIter.nextPath()) { + pathArea.reset(); + getXY(getPathStart(segIter.getPathIndex()), pt);// get the area + // calculation + // origin to be + // the origin of + // the ring. + while (segIter.hasNextSegment()) { + pathArea.add(segIter.nextSegment()._calculateArea2DHelper( + pt.x, pt.y)); + } + + totalArea.add(pathArea.getResult()); + + int i = ipath++; + m_cachedRingAreas2D.write(i, pathArea.getResult()); + } + + m_cachedArea2D = totalArea.getResult(); + _setDirtyFlag(DirtyFlags.DirtyRingAreas2D, false); + } + } + + int getOGCPolygonCount() { + if (!m_bPolygon) + return 0; + + _updateOGCFlags(); - int pathStart = getPathStart(pathIndex); + int polygonCount = 0; + int partCount = getPathCount(); + for (int ipart = 0; ipart < partCount; ipart++) { + if (((int) m_pathFlags.read(ipart) & (int) PathFlags.enumOGCStartPolygon) != 0) + polygonCount++; + } - if (pointIndex < 0) - pointIndex = getPathSize(pathIndex) - 1; + return polygonCount; + } - int absoluteIndex = pathStart + pointIndex; + protected void _updateOGCFlags() { + if (_hasDirtyFlag(DirtyFlags.DirtyOGCFlags)) { + _updateRingAreas2D(); + + int pathCount = getPathCount(); + if (pathCount > 0 && (m_pathFlags == null || m_pathFlags.size() < pathCount)) + m_pathFlags = (AttributeStreamOfInt8) AttributeStreamBase + .createByteStream(pathCount + 1); + + int firstSign = 1; + for (int ipath = 0; ipath < pathCount; ipath++) { + double area = m_cachedRingAreas2D.read(ipath); + if (ipath == 0) + firstSign = area > 0 ? 1 : -1; + if (area * firstSign > 0.0) + m_pathFlags.setBits(ipath, + (byte) PathFlags.enumOGCStartPolygon); + else + m_pathFlags.clearBits(ipath, + (byte) PathFlags.enumOGCStartPolygon); + } + _setDirtyFlag(DirtyFlags.DirtyOGCFlags, false); + } + } - // Remove the attribute values for the path - for (int iattr = 0, nattr = m_description.getAttributeCount(); iattr < nattr; iattr++) { - if (m_vertexAttributes[iattr] != null) { - int semantics = m_description._getSemanticsImpl(iattr); - int comp = VertexDescription.getComponentCount(semantics); - m_vertexAttributes[iattr].eraseRange(comp * absoluteIndex, - comp, comp * m_pointCount); - } - } + public int getPathIndexFromPointIndex(int pointIndex) { + int positionHint = m_currentPathIndex;// in case of multithreading + // thiswould simply produce an + // invalid value + int pathCount = getPathCount(); + + // Try using the hint position first to get the path index. + if (positionHint >= 0 && positionHint < pathCount) { + if (pointIndex < getPathEnd(positionHint)) { + if (pointIndex >= getPathStart(positionHint)) + return positionHint; + positionHint--; + } else { + positionHint++; + } - for (int ipath = pathCount; ipath >= pathIndex + 1; ipath--) { - int iend = m_paths.read(ipath); - m_paths.write(ipath, iend - 1); - } + if (positionHint >= 0 && positionHint < pathCount) { + if (pointIndex >= getPathStart(positionHint) + && pointIndex < getPathEnd(positionHint)) { + m_currentPathIndex = positionHint; + return positionHint; + } + } + } - m_pointCount--; - m_reservedPointCount--; - notifyModified(DirtyFlags.DirtyCoordinates); - } + if (pathCount < 5) {// TODO: time the performance to choose when to use + // linear search. + for (int i = 0; i < pathCount; i++) { + if (pointIndex < getPathEnd(i)) { + m_currentPathIndex = i; + return i; + } + } + throw new GeometryException("corrupted geometry"); + } - public double calculatePathLength2D(int pathIndex) /* const */ { - SegmentIteratorImpl segIter = querySegmentIteratorAtVertex(getPathStart(pathIndex)); + // Do binary search: + int minPathIndex = 0; + int maxPathIndex = pathCount - 1; + while (maxPathIndex > minPathIndex) { + int mid = minPathIndex + ((maxPathIndex - minPathIndex) >> 1); + int pathStart = getPathStart(mid); + if (pointIndex < pathStart) + maxPathIndex = mid - 1; + else { + int pathEnd = getPathEnd(mid); + if (pointIndex >= pathEnd) + minPathIndex = mid + 1; + else { + m_currentPathIndex = mid; + return mid; + } + } + } - MathUtils.KahanSummator len = new MathUtils.KahanSummator(0); - while (segIter.hasNextSegment()) { - len.add(segIter.nextSegment().calculateLength2D()); - } + m_currentPathIndex = minPathIndex; + return minPathIndex; + } - return len.getResult(); - } + int getHighestPointIndex(int path_index) { + assert (path_index >= 0 && path_index < getPathCount()); - double calculateSubLength2D(int from_path_index, int from_point_index, - int to_path_index, int to_point_index) { - int absolute_from_index = getPathStart(from_path_index) - + from_point_index; - int absolute_to_index = getPathStart(to_path_index) + to_point_index; + AttributeStreamOfDbl position = (AttributeStreamOfDbl) (getAttributeStreamRef(VertexDescription.Semantics.POSITION)); + AttributeStreamOfInt32 paths = (AttributeStreamOfInt32) (getPathStreamRef()); - if (absolute_to_index < absolute_from_index || absolute_from_index < 0 - || absolute_to_index > getPointCount() - 1) - throw new IllegalArgumentException(); + int path_end = getPathEnd(path_index); + int path_start = getPathStart(path_index); + int max_index = -1; + Point2D max_point = new Point2D(), pt = new Point2D(); + max_point.y = NumberUtils.negativeInf(); + max_point.x = NumberUtils.negativeInf(); - SegmentIteratorImpl seg_iter = querySegmentIterator(); + for (int i = path_start + 0; i < path_end; i++) { + position.read(2 * i, pt); + if (max_point.compare(pt) == -1) { + max_index = i; + max_point.setCoords(pt); + } + } - double sub_length = 0.0; + return max_index; + } - seg_iter.resetToVertex(absolute_from_index); + /** + * Returns total segment count in the MultiPathImpl. + */ + public int getSegmentCount() { + int segCount = getPointCount(); + if (!m_bPolygon) { + segCount -= getPathCount(); + for (int i = 0, n = getPathCount(); i < n; i++) + if (isClosedPath(i)) + segCount++; + } - do { - while (seg_iter.hasNextSegment()) { - Segment segment = seg_iter.nextSegment(); + return segCount; + } - if (seg_iter.getStartPointIndex() == absolute_to_index) - break; + public int getSegmentCount(int path_index) { + int segCount = getPathSize(path_index); + if (!isClosedPath(path_index)) + segCount--; + return segCount; + } - double segment_length = segment.calculateLength2D(); - sub_length += segment_length; - } + // HEADER defintions + @Override + public Geometry createInstance() { + return new MultiPathImpl(m_bPolygon, getDescription()); + } - if (seg_iter.getStartPointIndex() == absolute_to_index) - break; + @Override + public int getDimension() { + return m_bPolygon ? 2 : 1; + } - } while (seg_iter.nextPath()); + @Override + public Geometry.Type getType() { + return m_bPolygon ? Type.Polygon : Type.Polyline; + } - return sub_length; - } + /** + * Returns True if the class is envelope. THis is not an exact method. Only + * addEnvelope makes this true. + */ + public boolean isEnvelope() { + return !_hasDirtyFlag(DirtyFlags.DirtyIsEnvelope); + } - double calculateSubLength2D(int path_index, int from_point_index, - int to_point_index) { - int absolute_from_index = getPathStart(path_index) + from_point_index; - int absolute_to_index = getPathStart(path_index) + to_point_index; + /** + * Returns a reference to the AttributeStream of MultiPathImpl parts + * (Paths). + *

+ * For the non empty MultiPathImpl, that stream contains start points of the + * MultiPathImpl curves. In addition, the last element is the total point + * count. The number of vertices in a given part is parts[i + 1] - parts[i]. + */ + public AttributeStreamOfInt32 getPathStreamRef() { + throwIfEmpty(); + return m_paths; + } - if (absolute_from_index < 0 || absolute_to_index > getPointCount() - 1) - throw new IllegalArgumentException(); + /** + * sets a reference to an AttributeStream of MultiPathImpl paths (Paths). + */ + public void setPathStreamRef(AttributeStreamOfInt32 paths) { + m_paths = paths; + notifyModified(DirtyFlags.DirtyAll); + } - SegmentIteratorImpl seg_iter = querySegmentIterator(); + /** + * Returns a reference to the AttributeStream of Segment flags (SegmentFlags + * flags). Can be NULL when no non-linear segments are present. + *

+ * Segment flags indicate what kind of segment originates (starts) on the + * given point. The last vertices of open Path parts has enumNone flag. + */ + public AttributeStreamOfInt8 getSegmentFlagsStreamRef() { + throwIfEmpty(); + return m_segmentFlags; + } - if (absolute_from_index > absolute_to_index) { - if (!isClosedPath(path_index)) - throw new IllegalArgumentException( - "cannot iterate across an open path"); + /** + * Returns a reference to the AttributeStream of Path flags (PathFlags + * flags). + *

+ * Each start point of a path has a flag set to indicate if the Path is open + * or closed. + */ + public AttributeStreamOfInt8 getPathFlagsStreamRef() { + throwIfEmpty(); + return m_pathFlags; + } - seg_iter.setCirculator(true); - } + /** + * sets a reference to an AttributeStream of Path flags (PathFlags flags). + */ + public void setPathFlagsStreamRef(AttributeStreamOfInt8 pathFlags) { + m_pathFlags = pathFlags; + notifyModified(DirtyFlags.DirtyAll); + } - double prev_length = 0.0; - double sub_length = 0.0; + public AttributeStreamOfInt32 getSegmentIndexStreamRef() { + throwIfEmpty(); + return m_segmentParamIndex; + } - seg_iter.resetToVertex(absolute_from_index); + public AttributeStreamOfDbl getSegmentDataStreamRef() { + throwIfEmpty(); + return m_segmentParams; + } - do { - assert (seg_iter.hasNextSegment()); - sub_length += prev_length; - Segment segment = seg_iter.nextSegment(); - prev_length = segment.calculateLength2D(); + public int getPathCount() { + return (m_paths != null) ? m_paths.size() - 1 : 0; + } - } while (seg_iter.getStartPointIndex() != absolute_to_index); + public int getPathEnd(int partIndex) { + return m_paths.read(partIndex + 1); + } - return sub_length; - } + public int getPathSize(int partIndex) { + return m_paths.read(partIndex + 1) - m_paths.read(partIndex); + } - @Override - public Geometry getBoundary() { - return Boundary.calculate(this, null); - } + public int getPathStart(int partIndex) { + return m_paths.read(partIndex); + } - // TODO: Add code fore interpolation type (none and angular) - void interpolateAttributes(int from_path_index, int from_point_index, - int to_path_index, int to_point_index) { - for (int ipath = from_path_index; ipath < to_path_index - 1; ipath++) { - if (isClosedPath(ipath)) - throw new IllegalArgumentException( - "cannot interpolate across closed paths"); - } + @Override + public Object _getImpl() { + return this; + } - int nattr = m_description.getAttributeCount(); + public void setDirtyOGCFlags(boolean bYesNo) { + _setDirtyFlag(DirtyFlags.DirtyOGCFlags, bYesNo); + } - if (nattr == 1) - return; // only has position + public boolean hasDirtyOGCStartFlags() { + return _hasDirtyFlag(DirtyFlags.DirtyOGCFlags); + } - double sub_length = calculateSubLength2D(from_path_index, - from_point_index, to_path_index, to_point_index); + public void setDirtyRingAreas2D(boolean bYesNo) { + _setDirtyFlag(DirtyFlags.DirtyRingAreas2D, bYesNo); + } - if (sub_length == 0.0) - return; + public boolean hasDirtyRingAreas2D() { + return _hasDirtyFlag(DirtyFlags.DirtyRingAreas2D); + } - for (int iattr = 1; iattr < nattr; iattr++) { - int semantics = m_description.getSemantics(iattr); + public void setRingAreasStreamRef(AttributeStreamOfDbl ringAreas) { + m_cachedRingAreas2D = ringAreas; + _setDirtyFlag(DirtyFlags.DirtyRingAreas2D, false); + } - int interpolation = VertexDescription.getInterpolation(semantics); - if (interpolation == VertexDescription.Interpolation.ANGULAR) - continue; + // HEADER defintions + + // // TODO check this against current implementation in native + // public void notifyModified(int flags) + // { + // if(flags == DirtyFlags.DirtyAll) + // { + // m_reservedPointCount = -1; + // _notifyModifiedAllImpl(); + // } + // m_flagsMask |= flags; + // _clearAccelerators(); + // + // + // // ROHIT's implementation + // // if (m_paths == null || 0 == m_paths.size()) + // // m_pointCount = 0; + // // else + // // m_pointCount = m_paths.read(m_paths.size() - 1); + // // + // // super.notifyModified(flags); + // } - int components = VertexDescription.getComponentCount(semantics); + @Override + public boolean _buildRasterizedGeometryAccelerator(double toleranceXY, + GeometryAccelerationDegree accelDegree) { + if (m_accelerators == null)// (!m_accelerators) + { + m_accelerators = new GeometryAccelerators(); + } - for (int ordinate = 0; ordinate < components; ordinate++) - interpolateAttributes_(semantics, from_path_index, - from_point_index, to_path_index, to_point_index, - sub_length, ordinate); - } - } + int rasterSize = RasterizedGeometry2D + .rasterSizeFromAccelerationDegree(accelDegree); + RasterizedGeometry2D rgeom = m_accelerators.getRasterizedGeometry(); + if (rgeom != null) { + if (rgeom.getToleranceXY() < toleranceXY + || rasterSize > rgeom.getRasterSize()) { + m_accelerators._setRasterizedGeometry(null); + } else + return true; + } - // TODO: Add code for interpolation type (none and angular) - void interpolateAttributesForSemantics(int semantics, int from_path_index, - int from_point_index, int to_path_index, int to_point_index) { - if (semantics == VertexDescription.Semantics.POSITION) - return; + rgeom = RasterizedGeometry2D.create(this, toleranceXY, rasterSize); + m_accelerators._setRasterizedGeometry(rgeom); + //rgeom.dbgSaveToBitmap("c:/temp/ddd.bmp"); + return true; + } - if (!hasAttribute(semantics)) - throw new IllegalArgumentException( - "does not have the given attribute"); + @Override + public int hashCode() { + int hashCode = super.hashCode(); - int interpolation = VertexDescription.getInterpolation(semantics); - if (interpolation == VertexDescription.Interpolation.ANGULAR) - throw new IllegalArgumentException( - "not implemented for the given semantics"); + if (!isEmptyImpl()) { + int pathCount = getPathCount(); - for (int ipath = from_path_index; ipath < to_path_index - 1; ipath++) { - if (isClosedPath(ipath)) - throw new IllegalArgumentException( - "cannot interpolate across closed paths"); - } + if (m_paths != null) + m_paths.calculateHashImpl(hashCode, 0, pathCount + 1); - double sub_length = calculateSubLength2D(from_path_index, - from_point_index, to_path_index, to_point_index); + if (m_pathFlags != null) + m_pathFlags.calculateHashImpl(hashCode, 0, pathCount); + } - if (sub_length == 0.0) - return; + return hashCode; + } - int components = VertexDescription.getComponentCount(semantics); + public byte getSegmentFlags(int ivertex) { + if (m_segmentFlags != null) + return m_segmentFlags.read(ivertex); + else + return (byte) SegmentFlags.enumLineSeg; + } - for (int ordinate = 0; ordinate < components; ordinate++) - interpolateAttributes_(semantics, from_path_index, - from_point_index, to_path_index, to_point_index, - sub_length, ordinate); - } + public void getSegment(int startVertexIndex, SegmentBuffer segBuffer, + boolean bStripAttributes) { + int ipath = getPathIndexFromPointIndex(startVertexIndex); + if (startVertexIndex == getPathEnd(ipath) - 1 && !isClosedPath(ipath)) + throw new GeometryException("index out of bounds"); + + _verifyAllStreams(); + AttributeStreamOfInt8 segFlagStream = getSegmentFlagsStreamRef(); + int segFlag = SegmentFlags.enumLineSeg; + if (segFlagStream != null) + segFlag = segFlagStream.read(startVertexIndex) + & SegmentFlags.enumSegmentMask; + + switch (segFlag) { + case SegmentFlags.enumLineSeg: + segBuffer.createLine(); + break; + case SegmentFlags.enumBezierSeg: + throw GeometryException.GeometryInternalError(); + case SegmentFlags.enumArcSeg: + throw GeometryException.GeometryInternalError(); + default: + throw GeometryException.GeometryInternalError(); + } - void interpolateAttributes(int path_index, int from_point_index, - int to_point_index) { - int nattr = m_description.getAttributeCount(); + Segment currentSegment = segBuffer.get(); + if (!bStripAttributes) + currentSegment.assignVertexDescription(m_description); + else + currentSegment + .assignVertexDescription(VertexDescriptionDesignerImpl + .getDefaultDescriptor2D()); + + int endVertexIndex; + if (startVertexIndex == getPathEnd(ipath) - 1 && isClosedPath(ipath)) { + endVertexIndex = getPathStart(ipath); + } else + endVertexIndex = startVertexIndex + 1; + + Point2D pt = new Point2D(); + getXY(startVertexIndex, pt); + currentSegment.setStartXY(pt); + getXY(endVertexIndex, pt); + currentSegment.setEndXY(pt); + + if (!bStripAttributes) { + for (int i = 1, nattr = m_description.getAttributeCount(); i < nattr; i++) { + int semantics = m_description._getSemanticsImpl(i); + int ncomp = VertexDescription.getComponentCount(semantics); + for (int ord = 0; ord < ncomp; ord++) { + double vs = getAttributeAsDbl(semantics, startVertexIndex, + ord); + currentSegment.setStartAttribute(semantics, ord, vs); + double ve = getAttributeAsDbl(semantics, endVertexIndex, + ord); + currentSegment.setEndAttribute(semantics, ord, ve); + } + } + } + } - if (nattr == 1) - return; // only has position + void queryPathEnvelope2D(int path_index, Envelope2D envelope) { + if (path_index >= getPathCount()) + throw new IllegalArgumentException(); - double sub_length = calculateSubLength2D(path_index, from_point_index, - to_point_index); + if (isEmpty()) { + envelope.setEmpty(); + return; + } - if (sub_length == 0.0) - return; + if (hasNonLinearSegments(path_index)) { + throw new GeometryException("not implemented"); + } else { + AttributeStreamOfDbl stream = (AttributeStreamOfDbl) getAttributeStreamRef(VertexDescription.Semantics.POSITION); + Point2D pt = new Point2D(); + Envelope2D env = new Envelope2D(); + env.setEmpty(); + for (int i = getPathStart(path_index), iend = getPathEnd(path_index); i < iend; i++) { + stream.read(2 * i, pt); + env.merge(pt); + } + envelope.setCoords(env); + } + } - for (int iattr = 1; iattr < nattr; iattr++) { - int semantics = m_description.getSemantics(iattr); + public void queryLoosePathEnvelope2D(int path_index, Envelope2D envelope) { + if (path_index >= getPathCount()) + throw new IllegalArgumentException(); - int interpolation = VertexDescription.getInterpolation(semantics); - if (interpolation == VertexDescription.Interpolation.ANGULAR) - continue; + if (isEmpty()) { + envelope.setEmpty(); + return; + } - int components = VertexDescription.getComponentCount(semantics); + if (hasNonLinearSegments(path_index)) { + throw new GeometryException("not implemented"); + } else { + AttributeStreamOfDbl stream = (AttributeStreamOfDbl) getAttributeStreamRef(VertexDescription.Semantics.POSITION); + Point2D pt = new Point2D(); + Envelope2D env = new Envelope2D(); + env.setEmpty(); + for (int i = getPathStart(path_index), iend = getPathEnd(path_index); i < iend; i++) { + stream.read(2 * i, pt); + env.merge(pt); + } + envelope.setCoords(env); + } + } - for (int ordinate = 0; ordinate < components; ordinate++) - interpolateAttributes_(semantics, path_index, from_point_index, - to_point_index, sub_length, ordinate); - } - } + @Override + public boolean _buildQuadTreeAccelerator(GeometryAccelerationDegree d) { + if (m_accelerators == null)// (!m_accelerators) + { + m_accelerators = new GeometryAccelerators(); + } - void interpolateAttributesForSemantics(int semantics, int path_index, - int from_point_index, int to_point_index) { - if (semantics == VertexDescription.Semantics.POSITION) - return; + if (d == GeometryAccelerationDegree.enumMild || getPointCount() < 16) + return false; - if (!hasAttribute(semantics)) - throw new IllegalArgumentException( - "does not have the given attribute"); + QuadTreeImpl quad_tree_impl = InternalUtils.buildQuadTree(this); + m_accelerators._setQuadTree(quad_tree_impl); - int interpolation = VertexDescription.getInterpolation(semantics); - if (interpolation == VertexDescription.Interpolation.ANGULAR) - throw new IllegalArgumentException( - "not implemented for the given semantics"); + return true; + } - double sub_length = calculateSubLength2D(path_index, from_point_index, - to_point_index); + boolean _buildQuadTreeForPathsAccelerator(GeometryAccelerationDegree degree) { + if (m_accelerators == null) { + m_accelerators = new GeometryAccelerators(); + } - if (sub_length == 0.0) - return; + // TODO: when less than two envelopes - no need to this. - int components = VertexDescription.getComponentCount(semantics); + if (m_accelerators.getQuadTreeForPaths() != null) + return true; - for (int ordinate = 0; ordinate < components; ordinate++) - interpolateAttributes_(semantics, path_index, from_point_index, - to_point_index, sub_length, ordinate); - } + m_accelerators._setQuadTreeForPaths(null); + QuadTreeImpl quad_tree_impl = InternalUtils.buildQuadTreeForPaths(this); + m_accelerators._setQuadTreeForPaths(quad_tree_impl); - // TODO: Add code fore interpolation type (none and angular) - void interpolateAttributes_(int semantics, int from_path_index, - int from_point_index, int to_path_index, int to_point_index, - double sub_length, int ordinate) { - SegmentIteratorImpl seg_iter = querySegmentIterator(); - - int absolute_from_index = getPathStart(from_path_index) - + from_point_index; - int absolute_to_index = getPathStart(to_path_index) + to_point_index; + return true; + } - double from_attribute = getAttributeAsDbl(semantics, - absolute_from_index, ordinate); - double to_attribute = getAttributeAsDbl(semantics, absolute_to_index, - ordinate); - double interpolated_attribute = from_attribute; - double cumulative_length = 0.0; + void setFillRule(int rule) { + assert (m_bPolygon); + m_fill_rule = rule; + } - seg_iter.resetToVertex(absolute_from_index); + int getFillRule() { + return m_fill_rule; + } - do { - if (seg_iter.hasNextSegment()) { - seg_iter.nextSegment(); - - if (seg_iter.getStartPointIndex() == absolute_to_index) - return; - - setAttribute(semantics, seg_iter.getStartPointIndex(), - ordinate, interpolated_attribute); - - seg_iter.previousSegment(); - - do { - Segment segment = seg_iter.nextSegment(); - - if (seg_iter.getEndPointIndex() == absolute_to_index) - return; - - double segment_length = segment.calculateLength2D(); - cumulative_length += segment_length; - double t = cumulative_length / sub_length; - interpolated_attribute = MathUtils.lerp(from_attribute, to_attribute, t); - - if (!seg_iter.isClosingSegment()) - setAttribute(semantics, seg_iter.getEndPointIndex(), - ordinate, interpolated_attribute); - - } while (seg_iter.hasNextSegment()); - } - - } while (seg_iter.nextPath()); - } - - void interpolateAttributes_(int semantics, int path_index, - int from_point_index, int to_point_index, double sub_length, - int ordinate) { - assert (m_bPolygon); - SegmentIteratorImpl seg_iter = querySegmentIterator(); - - int absolute_from_index = getPathStart(path_index) + from_point_index; - int absolute_to_index = getPathStart(path_index) + to_point_index; - - if (absolute_to_index == absolute_from_index) - return; - - double from_attribute = getAttributeAsDbl(semantics, - absolute_from_index, ordinate); - double to_attribute = getAttributeAsDbl(semantics, absolute_to_index, - ordinate); - double cumulative_length = 0.0; - - seg_iter.resetToVertex(absolute_from_index); - seg_iter.setCirculator(true); - - double prev_interpolated_attribute = from_attribute; - - do { - Segment segment = seg_iter.nextSegment(); - setAttribute(semantics, seg_iter.getStartPointIndex(), ordinate, - prev_interpolated_attribute); - - double segment_length = segment.calculateLength2D(); - cumulative_length += segment_length; - double t = cumulative_length / sub_length; - prev_interpolated_attribute = MathUtils.lerp(from_attribute, to_attribute, t); - - } while (seg_iter.getEndPointIndex() != absolute_to_index); - } - - @Override - public void setEmpty() { - m_curveParamwritePoint = 0; - m_bPathStarted = false; - m_paths = null; - m_pathFlags = null; - m_segmentParamIndex = null; - m_segmentFlags = null; - m_segmentParams = null; - _setEmptyImpl(); - } - - @Override - public void applyTransformation(Transformation2D transform) { - applyTransformation(transform, -1); - } - - public void applyTransformation(Transformation2D transform, int pathIndex) { - if (isEmpty()) - return; - - if (transform.isIdentity()) - return; - - _verifyAllStreams(); - AttributeStreamOfDbl points = (AttributeStreamOfDbl) m_vertexAttributes[0]; - Point2D ptStart = new Point2D(); - Point2D ptControl = new Point2D(); - - boolean bHasNonLinear; - int fistIdx; - int lastIdx; - if (pathIndex < 0) { - bHasNonLinear = hasNonLinearSegments(); - fistIdx = 0; - lastIdx = m_pointCount; - } else { - bHasNonLinear = hasNonLinearSegments(pathIndex); - fistIdx = getPathStart(pathIndex); - lastIdx = getPathEnd(pathIndex); - } - - for (int ipoint = fistIdx; ipoint < lastIdx; ipoint++) { - ptStart.x = points.read(ipoint * 2); - ptStart.y = points.read(ipoint * 2 + 1); - - if (bHasNonLinear) { - int segIndex = m_segmentParamIndex.read(ipoint); - if (segIndex >= 0) { - int segmentType = (int) m_segmentFlags.read(ipoint); - int type = segmentType & SegmentFlags.enumSegmentMask; - switch (type) { - case SegmentFlags.enumBezierSeg: { - ptControl.x = m_segmentParams.read(segIndex); - ptControl.y = m_segmentParams.read(segIndex + 1); - transform.transform(ptControl, ptControl); - m_segmentParams.write(segIndex, ptControl.x); - m_segmentParams.write(segIndex + 1, ptControl.y); - - ptControl.x = m_segmentParams.read(segIndex + 3); - ptControl.y = m_segmentParams.read(segIndex + 4); - transform.transform(ptControl, ptControl); - m_segmentParams.write(segIndex + 3, ptControl.x); - m_segmentParams.write(segIndex + 4, ptControl.y); - } - break; - case SegmentFlags.enumArcSeg: - throw GeometryException.GeometryInternalError(); - - } - } - } - - transform.transform(ptStart, ptStart); - points.write(ipoint * 2, ptStart.x); - points.write(ipoint * 2 + 1, ptStart.y); - } - - notifyModified(DirtyFlags.DirtyCoordinates); - // REFACTOR: reset the exact envelope only and transform the loose - // envelope - } - - @Override - public void applyTransformation(Transformation3D transform) { - if (isEmpty()) - return; - - addAttribute(VertexDescription.Semantics.Z); - _verifyAllStreams(); - AttributeStreamOfDbl points = (AttributeStreamOfDbl) m_vertexAttributes[0]; - AttributeStreamOfDbl zs = (AttributeStreamOfDbl) m_vertexAttributes[1]; - Point3D ptStart = new Point3D(); - Point3D ptControl = new Point3D(); - boolean bHasNonLinear = hasNonLinearSegments(); - for (int ipoint = 0; ipoint < m_pointCount; ipoint++) { - ptStart.x = points.read(ipoint * 2); - ptStart.y = points.read(ipoint * 2 + 1); - ptStart.z = zs.read(ipoint); - - if (bHasNonLinear) { - int segIndex = m_segmentParamIndex.read(ipoint); - if (segIndex >= 0) { - int segmentType = (int) m_segmentFlags.read(ipoint); - int type = segmentType & (int) SegmentFlags.enumSegmentMask; - switch (type) { - case SegmentFlags.enumBezierSeg: { - ptControl.x = m_segmentParams.read(segIndex); - ptControl.y = m_segmentParams.read(segIndex + 1); - ptControl.z = m_segmentParams.read(segIndex + 2); - ptControl = transform.transform(ptControl); - m_segmentParams.write(segIndex, ptControl.x); - m_segmentParams.write(segIndex + 1, ptControl.y); - m_segmentParams.write(segIndex + 1, ptControl.z); - - ptControl.x = m_segmentParams.read(segIndex + 3); - ptControl.y = m_segmentParams.read(segIndex + 4); - ptControl.z = m_segmentParams.read(segIndex + 5); - ptControl = transform.transform(ptControl); - m_segmentParams.write(segIndex + 3, ptControl.x); - m_segmentParams.write(segIndex + 4, ptControl.y); - m_segmentParams.write(segIndex + 5, ptControl.z); - } - break; - case SegmentFlags.enumArcSeg: - throw GeometryException.GeometryInternalError(); - - } - } - } - - ptStart = transform.transform(ptStart); - points.write(ipoint * 2, ptStart.x); - points.write(ipoint * 2 + 1, ptStart.y); - zs.write(ipoint, ptStart.z); - } - - // REFACTOR: reset the exact envelope only and transform the loose - // envelope - - notifyModified(DirtyFlags.DirtyCoordinates); - } - - @Override - protected void _verifyStreamsImpl() { - if (m_paths == null) { - m_paths = (AttributeStreamOfInt32) AttributeStreamBase - .createIndexStream(1, 0); - m_pathFlags = (AttributeStreamOfInt8) AttributeStreamBase - .createByteStream(1, (byte) 0); - } - - if (m_segmentFlags != null) { - m_segmentFlags.resize(m_reservedPointCount, - (byte) SegmentFlags.enumLineSeg); - m_segmentParamIndex.resize(m_reservedPointCount, -1); - } - } - - @Override - void _copyToImpl(MultiVertexGeometryImpl dst) { - MultiPathImpl dstPoly = (MultiPathImpl) dst; - dstPoly.m_bPathStarted = false; - dstPoly.m_curveParamwritePoint = m_curveParamwritePoint; - dstPoly.m_fill_rule = m_fill_rule; - - if (m_paths != null) - dstPoly.m_paths = new AttributeStreamOfInt32(m_paths); - else - dstPoly.m_paths = null; - - if (m_pathFlags != null) - dstPoly.m_pathFlags = new AttributeStreamOfInt8(m_pathFlags); - else - dstPoly.m_pathFlags = null; - - if (m_segmentParamIndex != null) - dstPoly.m_segmentParamIndex = new AttributeStreamOfInt32( - m_segmentParamIndex); - else - dstPoly.m_segmentParamIndex = null; - - if (m_segmentFlags != null) - dstPoly.m_segmentFlags = new AttributeStreamOfInt8(m_segmentFlags); - else - dstPoly.m_segmentFlags = null; - - if (m_segmentParams != null) - dstPoly.m_segmentParams = new AttributeStreamOfDbl(m_segmentParams); - else - dstPoly.m_segmentParams = null; - - dstPoly.m_cachedLength2D = m_cachedLength2D; - dstPoly.m_cachedArea2D = m_cachedArea2D; - - if (!_hasDirtyFlag(DirtyFlags.DirtyRingAreas2D)) { - dstPoly.m_cachedRingAreas2D = (AttributeStreamOfDbl) m_cachedRingAreas2D; - } else - dstPoly.m_cachedRingAreas2D = null; - - } - - @Override - public double calculateLength2D() { - if (!_hasDirtyFlag(DirtyFlags.DirtyLength2D)) { - return m_cachedLength2D; - } - - SegmentIteratorImpl segIter = querySegmentIterator(); - MathUtils.KahanSummator len = new MathUtils.KahanSummator(0); - while (segIter.nextPath()) { - while (segIter.hasNextSegment()) { - len.add(segIter.nextSegment().calculateLength2D()); - } - } - - m_cachedLength2D = len.getResult(); - _setDirtyFlag(DirtyFlags.DirtyLength2D, false); - - return len.getResult(); - } - - @Override - public boolean equals(Object other) { - if (other == this) - return true; - - if (!(other instanceof MultiPathImpl)) - return false; - - if (!super.equals(other)) - return false; - - MultiPathImpl otherMultiPath = (MultiPathImpl) other; - - int pathCount = getPathCount(); - int pathCountOther = otherMultiPath.getPathCount(); - - if (pathCount != pathCountOther) - return false; - - if (pathCount > 0 && m_paths != null - && !m_paths.equals(otherMultiPath.m_paths, 0, pathCount + 1)) - return false; - - if (m_fill_rule != otherMultiPath.m_fill_rule) - return false; - - { - // Note: OGC flags do not participate in the equals operation by - // design. - // Because for the polygon pathFlags will have all enum_closed set, - // we do not need to compare this stream. Only for polyline. - // Polyline does not have OGC flags set. - if (!m_bPolygon) { - if (m_pathFlags != null - && !m_pathFlags.equals(otherMultiPath.m_pathFlags, 0, - pathCount)) - return false; - } - } - - return super.equals(other); - } - - /** - * Returns a SegmentIterator that set to a specific vertex of the - * MultiPathImpl. The call to NextSegment will return the segment that - * starts at the vertex. Call to PreviousSegment will return the segment - * that starts at the previous vertex. - */ - public SegmentIteratorImpl querySegmentIteratorAtVertex(int startVertexIndex) { - if (startVertexIndex < 0 || startVertexIndex >= getPointCount()) - throw new IndexOutOfBoundsException(); - - SegmentIteratorImpl iter = new SegmentIteratorImpl(this, - startVertexIndex); - return iter; - } - - // void QuerySegmentIterator(int fromVertex, SegmentIterator iterator); - public SegmentIteratorImpl querySegmentIterator() { - return new SegmentIteratorImpl(this); - } - - @Override - public void _updateXYImpl(boolean bExact) { - super._updateXYImpl(bExact); - boolean bHasCurves = hasNonLinearSegments(); - if (bHasCurves) { - SegmentIteratorImpl segIter = querySegmentIterator(); - while (segIter.nextPath()) { - while (segIter.hasNextSegment()) { - Segment curve = segIter.nextCurve(); - if (curve != null) { - Envelope2D env2D = new Envelope2D(); - curve.queryEnvelope2D(env2D); - m_envelope.merge(env2D); - } else - break; - } - } - } - } - - @Override - void calculateEnvelope2D(Envelope2D env, boolean bExact) { - super.calculateEnvelope2D(env, bExact); - boolean bHasCurves = hasNonLinearSegments(); - if (bHasCurves) { - SegmentIteratorImpl segIter = querySegmentIterator(); - while (segIter.nextPath()) { - while (segIter.hasNextSegment()) { - Segment curve = segIter.nextCurve(); - if (curve != null) { - Envelope2D env2D = new Envelope2D(); - curve.queryEnvelope2D(env2D); - env.merge(env2D); - } else - break; - } - } - } - } - - @Override - public void _notifyModifiedAllImpl() { - if (m_paths == null || m_paths.size() == 0)// if (m_paths == null || - // !m_paths.size()) - m_pointCount = 0; - else - m_pointCount = m_paths.read(m_paths.size() - 1); - } - - @Override - public double calculateArea2D() { - if (!m_bPolygon) - return 0.0; - - _updateRingAreas2D(); - - return m_cachedArea2D; - } - - /** - * Returns True if the ring is an exterior ring. Valid only for simple - * polygons. - */ - public boolean isExteriorRing(int ringIndex) { - if (!m_bPolygon) - return false; - - if (!_hasDirtyFlag(DirtyFlags.DirtyOGCFlags)) - return (m_pathFlags.read(ringIndex) & (byte) PathFlags.enumOGCStartPolygon) != 0; - - _updateRingAreas2D(); - return m_cachedRingAreas2D.read(ringIndex) > 0; - // Should we make a function called _UpdateHasNonLinearSegmentsFlags and - // call it here? - } - - public double calculateRingArea2D(int pathIndex) { - if (!m_bPolygon) - return 0.0; - - _updateRingAreas2D(); - - return m_cachedRingAreas2D.read(pathIndex); - } - - public void _updateRingAreas2D() { - if (_hasDirtyFlag(DirtyFlags.DirtyRingAreas2D)) { - int pathCount = getPathCount(); - - if (m_cachedRingAreas2D == null) - m_cachedRingAreas2D = new AttributeStreamOfDbl(pathCount); - else if (m_cachedRingAreas2D.size() != pathCount) - m_cachedRingAreas2D.resize(pathCount); - - MathUtils.KahanSummator totalArea = new MathUtils.KahanSummator(0); - MathUtils.KahanSummator pathArea = new MathUtils.KahanSummator(0); - Point2D pt = new Point2D(); - int ipath = 0; - SegmentIteratorImpl segIter = querySegmentIterator(); - while (segIter.nextPath()) { - pathArea.reset(); - getXY(getPathStart(segIter.getPathIndex()), pt);// get the area - // calculation - // origin to be - // the origin of - // the ring. - while (segIter.hasNextSegment()) { - pathArea.add(segIter.nextSegment()._calculateArea2DHelper( - pt.x, pt.y)); - } - - totalArea.add(pathArea.getResult()); - - int i = ipath++; - m_cachedRingAreas2D.write(i, pathArea.getResult()); - } - - m_cachedArea2D = totalArea.getResult(); - _setDirtyFlag(DirtyFlags.DirtyRingAreas2D, false); - } - } - - int getOGCPolygonCount() { - if (!m_bPolygon) - return 0; - - _updateOGCFlags(); - - int polygonCount = 0; - int partCount = getPathCount(); - for (int ipart = 0; ipart < partCount; ipart++) { - if (((int) m_pathFlags.read(ipart) & (int) PathFlags.enumOGCStartPolygon) != 0) - polygonCount++; - } - - return polygonCount; - } - - protected void _updateOGCFlags() { - if (_hasDirtyFlag(DirtyFlags.DirtyOGCFlags)) { - _updateRingAreas2D(); - - int pathCount = getPathCount(); - if (pathCount > 0 && (m_pathFlags == null || m_pathFlags.size() < pathCount)) - m_pathFlags = (AttributeStreamOfInt8) AttributeStreamBase - .createByteStream(pathCount + 1); - - int firstSign = 1; - for (int ipath = 0; ipath < pathCount; ipath++) { - double area = m_cachedRingAreas2D.read(ipath); - if (ipath == 0) - firstSign = area > 0 ? 1 : -1; - if (area * firstSign > 0.0) - m_pathFlags.setBits(ipath, - (byte) PathFlags.enumOGCStartPolygon); - else - m_pathFlags.clearBits(ipath, - (byte) PathFlags.enumOGCStartPolygon); - } - _setDirtyFlag(DirtyFlags.DirtyOGCFlags, false); - } - } - - public int getPathIndexFromPointIndex(int pointIndex) { - int positionHint = m_currentPathIndex;// in case of multithreading - // thiswould simply produce an - // invalid value - int pathCount = getPathCount(); - - // Try using the hint position first to get the path index. - if (positionHint >= 0 && positionHint < pathCount) { - if (pointIndex < getPathEnd(positionHint)) { - if (pointIndex >= getPathStart(positionHint)) - return positionHint; - positionHint--; - } else { - positionHint++; - } - - if (positionHint >= 0 && positionHint < pathCount) { - if (pointIndex >= getPathStart(positionHint) - && pointIndex < getPathEnd(positionHint)) { - m_currentPathIndex = positionHint; - return positionHint; - } - } - } - - if (pathCount < 5) {// TODO: time the performance to choose when to use - // linear search. - for (int i = 0; i < pathCount; i++) { - if (pointIndex < getPathEnd(i)) { - m_currentPathIndex = i; - return i; - } - } - throw new GeometryException("corrupted geometry"); - } - - // Do binary search: - int minPathIndex = 0; - int maxPathIndex = pathCount - 1; - while (maxPathIndex > minPathIndex) { - int mid = minPathIndex + ((maxPathIndex - minPathIndex) >> 1); - int pathStart = getPathStart(mid); - if (pointIndex < pathStart) - maxPathIndex = mid - 1; - else { - int pathEnd = getPathEnd(mid); - if (pointIndex >= pathEnd) - minPathIndex = mid + 1; - else { - m_currentPathIndex = mid; - return mid; - } - } - } - - m_currentPathIndex = minPathIndex; - return minPathIndex; - } - - int getHighestPointIndex(int path_index) { - assert (path_index >= 0 && path_index < getPathCount()); - - AttributeStreamOfDbl position = (AttributeStreamOfDbl) (getAttributeStreamRef(VertexDescription.Semantics.POSITION)); - AttributeStreamOfInt32 paths = (AttributeStreamOfInt32) (getPathStreamRef()); - - int path_end = getPathEnd(path_index); - int path_start = getPathStart(path_index); - int max_index = -1; - Point2D max_point = new Point2D(), pt = new Point2D(); - max_point.y = NumberUtils.negativeInf(); - max_point.x = NumberUtils.negativeInf(); - - for (int i = path_start + 0; i < path_end; i++) { - position.read(2 * i, pt); - if (max_point.compare(pt) == -1) { - max_index = i; - max_point.setCoords(pt); - } - } - - return max_index; - } - - /** - * Returns total segment count in the MultiPathImpl. - */ - public int getSegmentCount() { - int segCount = getPointCount(); - if (!m_bPolygon) { - segCount -= getPathCount(); - for (int i = 0, n = getPathCount(); i < n; i++) - if (isClosedPath(i)) - segCount++; - } - - return segCount; - } - - public int getSegmentCount(int path_index) { - int segCount = getPathSize(path_index); - if (!isClosedPath(path_index)) - segCount--; - return segCount; - } - - // HEADER defintions - @Override - public Geometry createInstance() { - return new MultiPathImpl(m_bPolygon, getDescription()); - } - - @Override - public int getDimension() { - return m_bPolygon ? 2 : 1; - } - - @Override - public Geometry.Type getType() { - return m_bPolygon ? Type.Polygon : Type.Polyline; - } - - /** - * Returns True if the class is envelope. THis is not an exact method. Only - * addEnvelope makes this true. - */ - public boolean isEnvelope() { - return !_hasDirtyFlag(DirtyFlags.DirtyIsEnvelope); - } - - /** - * Returns a reference to the AttributeStream of MultiPathImpl parts - * (Paths). - *

- * For the non empty MultiPathImpl, that stream contains start points of the - * MultiPathImpl curves. In addition, the last element is the total point - * count. The number of vertices in a given part is parts[i + 1] - parts[i]. - */ - public AttributeStreamOfInt32 getPathStreamRef() { - throwIfEmpty(); - return m_paths; - } - - /** - * sets a reference to an AttributeStream of MultiPathImpl paths (Paths). - */ - public void setPathStreamRef(AttributeStreamOfInt32 paths) { - m_paths = paths; - notifyModified(DirtyFlags.DirtyAll); - } - - /** - * Returns a reference to the AttributeStream of Segment flags (SegmentFlags - * flags). Can be NULL when no non-linear segments are present. - *

- * Segment flags indicate what kind of segment originates (starts) on the - * given point. The last vertices of open Path parts has enumNone flag. - */ - public AttributeStreamOfInt8 getSegmentFlagsStreamRef() { - throwIfEmpty(); - return m_segmentFlags; - } - - /** - * Returns a reference to the AttributeStream of Path flags (PathFlags - * flags). - *

- * Each start point of a path has a flag set to indicate if the Path is open - * or closed. - */ - public AttributeStreamOfInt8 getPathFlagsStreamRef() { - throwIfEmpty(); - return m_pathFlags; - } - - /** - * sets a reference to an AttributeStream of Path flags (PathFlags flags). - */ - public void setPathFlagsStreamRef(AttributeStreamOfInt8 pathFlags) { - m_pathFlags = pathFlags; - notifyModified(DirtyFlags.DirtyAll); - } - - public AttributeStreamOfInt32 getSegmentIndexStreamRef() { - throwIfEmpty(); - return m_segmentParamIndex; - } - - public AttributeStreamOfDbl getSegmentDataStreamRef() { - throwIfEmpty(); - return m_segmentParams; - } - - public int getPathCount() { - return (m_paths != null) ? m_paths.size() - 1 : 0; - } - - public int getPathEnd(int partIndex) { - return m_paths.read(partIndex + 1); - } - - public int getPathSize(int partIndex) { - return m_paths.read(partIndex + 1) - m_paths.read(partIndex); - } - - public int getPathStart(int partIndex) { - return m_paths.read(partIndex); - } - - @Override - public Object _getImpl() { - return this; - } - - public void setDirtyOGCFlags(boolean bYesNo) { - _setDirtyFlag(DirtyFlags.DirtyOGCFlags, bYesNo); - } - - public boolean hasDirtyOGCStartFlags() { - return _hasDirtyFlag(DirtyFlags.DirtyOGCFlags); - } - - public void setDirtyRingAreas2D(boolean bYesNo) { - _setDirtyFlag(DirtyFlags.DirtyRingAreas2D, bYesNo); - } - - public boolean hasDirtyRingAreas2D() { - return _hasDirtyFlag(DirtyFlags.DirtyRingAreas2D); - } - - public void setRingAreasStreamRef(AttributeStreamOfDbl ringAreas) { - m_cachedRingAreas2D = ringAreas; - _setDirtyFlag(DirtyFlags.DirtyRingAreas2D, false); - } - - // HEADER defintions - - // // TODO check this against current implementation in native - // public void notifyModified(int flags) - // { - // if(flags == DirtyFlags.DirtyAll) - // { - // m_reservedPointCount = -1; - // _notifyModifiedAllImpl(); - // } - // m_flagsMask |= flags; - // _clearAccelerators(); - // - // - // // ROHIT's implementation - // // if (m_paths == null || 0 == m_paths.size()) - // // m_pointCount = 0; - // // else - // // m_pointCount = m_paths.read(m_paths.size() - 1); - // // - // // super.notifyModified(flags); - // } - - @Override - public boolean _buildRasterizedGeometryAccelerator(double toleranceXY, - GeometryAccelerationDegree accelDegree) { - if (m_accelerators == null)// (!m_accelerators) - { - m_accelerators = new GeometryAccelerators(); - } - - int rasterSize = RasterizedGeometry2D - .rasterSizeFromAccelerationDegree(accelDegree); - RasterizedGeometry2D rgeom = m_accelerators.getRasterizedGeometry(); - if (rgeom != null) { - if (rgeom.getToleranceXY() < toleranceXY - || rasterSize > rgeom.getRasterSize()) { - m_accelerators._setRasterizedGeometry(null); - } else - return true; - } - - rgeom = RasterizedGeometry2D.create(this, toleranceXY, rasterSize); - m_accelerators._setRasterizedGeometry(rgeom); - //rgeom.dbgSaveToBitmap("c:/temp/ddd.bmp"); - return true; - } - - @Override - public int hashCode() { - int hashCode = super.hashCode(); - - if (!isEmptyImpl()) { - int pathCount = getPathCount(); - - if (m_paths != null) - m_paths.calculateHashImpl(hashCode, 0, pathCount + 1); - - if (m_pathFlags != null) - m_pathFlags.calculateHashImpl(hashCode, 0, pathCount); - } - - return hashCode; - } - - public byte getSegmentFlags(int ivertex) { - if (m_segmentFlags != null) - return m_segmentFlags.read(ivertex); - else - return (byte) SegmentFlags.enumLineSeg; - } - - public void getSegment(int startVertexIndex, SegmentBuffer segBuffer, - boolean bStripAttributes) { - int ipath = getPathIndexFromPointIndex(startVertexIndex); - if (startVertexIndex == getPathEnd(ipath) - 1 && !isClosedPath(ipath)) - throw new GeometryException("index out of bounds"); - - _verifyAllStreams(); - AttributeStreamOfInt8 segFlagStream = getSegmentFlagsStreamRef(); - int segFlag = SegmentFlags.enumLineSeg; - if (segFlagStream != null) - segFlag = segFlagStream.read(startVertexIndex) - & SegmentFlags.enumSegmentMask; - - switch (segFlag) { - case SegmentFlags.enumLineSeg: - segBuffer.createLine(); - break; - case SegmentFlags.enumBezierSeg: - throw GeometryException.GeometryInternalError(); - case SegmentFlags.enumArcSeg: - throw GeometryException.GeometryInternalError(); - default: - throw GeometryException.GeometryInternalError(); - } - - Segment currentSegment = segBuffer.get(); - if (!bStripAttributes) - currentSegment.assignVertexDescription(m_description); - else - currentSegment - .assignVertexDescription(VertexDescriptionDesignerImpl - .getDefaultDescriptor2D()); - - int endVertexIndex; - if (startVertexIndex == getPathEnd(ipath) - 1 && isClosedPath(ipath)) { - endVertexIndex = getPathStart(ipath); - } else - endVertexIndex = startVertexIndex + 1; - - Point2D pt = new Point2D(); - getXY(startVertexIndex, pt); - currentSegment.setStartXY(pt); - getXY(endVertexIndex, pt); - currentSegment.setEndXY(pt); - - if (!bStripAttributes) { - for (int i = 1, nattr = m_description.getAttributeCount(); i < nattr; i++) { - int semantics = m_description._getSemanticsImpl(i); - int ncomp = VertexDescription.getComponentCount(semantics); - for (int ord = 0; ord < ncomp; ord++) { - double vs = getAttributeAsDbl(semantics, startVertexIndex, - ord); - currentSegment.setStartAttribute(semantics, ord, vs); - double ve = getAttributeAsDbl(semantics, endVertexIndex, - ord); - currentSegment.setEndAttribute(semantics, ord, ve); - } - } - } - } - - void queryPathEnvelope2D(int path_index, Envelope2D envelope) { - if (path_index >= getPathCount()) - throw new IllegalArgumentException(); - - if (isEmpty()) { - envelope.setEmpty(); - return; - } - - if (hasNonLinearSegments(path_index)) { - throw new GeometryException("not implemented"); - } else { - AttributeStreamOfDbl stream = (AttributeStreamOfDbl) getAttributeStreamRef(VertexDescription.Semantics.POSITION); - Point2D pt = new Point2D(); - Envelope2D env = new Envelope2D(); - env.setEmpty(); - for (int i = getPathStart(path_index), iend = getPathEnd(path_index); i < iend; i++) { - stream.read(2 * i, pt); - env.merge(pt); - } - envelope.setCoords(env); - } - } - - public void queryLoosePathEnvelope2D(int path_index, Envelope2D envelope) { - if (path_index >= getPathCount()) - throw new IllegalArgumentException(); - - if (isEmpty()) { - envelope.setEmpty(); - return; - } - - if (hasNonLinearSegments(path_index)) { - throw new GeometryException("not implemented"); - } else { - AttributeStreamOfDbl stream = (AttributeStreamOfDbl) getAttributeStreamRef(VertexDescription.Semantics.POSITION); - Point2D pt = new Point2D(); - Envelope2D env = new Envelope2D(); - env.setEmpty(); - for (int i = getPathStart(path_index), iend = getPathEnd(path_index); i < iend; i++) { - stream.read(2 * i, pt); - env.merge(pt); - } - envelope.setCoords(env); - } - } - - @Override - public boolean _buildQuadTreeAccelerator(GeometryAccelerationDegree d) { - if (m_accelerators == null)// (!m_accelerators) - { - m_accelerators = new GeometryAccelerators(); - } - - if (d == GeometryAccelerationDegree.enumMild || getPointCount() < 16) - return false; - - QuadTreeImpl quad_tree_impl = InternalUtils.buildQuadTree(this); - m_accelerators._setQuadTree(quad_tree_impl); - - return true; - } - - boolean _buildQuadTreeForPathsAccelerator(GeometryAccelerationDegree degree) { - if (m_accelerators == null) { - m_accelerators = new GeometryAccelerators(); - } - - // TODO: when less than two envelopes - no need to this. - - if (m_accelerators.getQuadTreeForPaths() != null) - return true; - - m_accelerators._setQuadTreeForPaths(null); - QuadTreeImpl quad_tree_impl = InternalUtils.buildQuadTreeForPaths(this); - m_accelerators._setQuadTreeForPaths(quad_tree_impl); - - return true; - } - - void setFillRule(int rule) { - assert (m_bPolygon); - m_fill_rule = rule; - } - - int getFillRule() { - return m_fill_rule; - } - - void clearDirtyOGCFlags() { - _setDirtyFlag(DirtyFlags.DirtyOGCFlags, false); - } + void clearDirtyOGCFlags() { + _setDirtyFlag(DirtyFlags.DirtyOGCFlags, false); + } } diff --git a/src/main/java/com/esri/core/geometry/MultiPoint.java b/src/main/java/com/esri/core/geometry/MultiPoint.java index 43f40642..bcdae5ac 100644 --- a/src/main/java/com/esri/core/geometry/MultiPoint.java +++ b/src/main/java/com/esri/core/geometry/MultiPoint.java @@ -35,221 +35,220 @@ * essential characteristic of the point set. */ public class MultiPoint extends MultiVertexGeometry implements - Serializable { - - private static final long serialVersionUID = 2L; - - private MultiPointImpl m_impl; - - /** - * Creates a new empty multipoint. - */ - public MultiPoint() { - m_impl = new MultiPointImpl(); - } - - public MultiPoint(VertexDescription description) { - m_impl = new MultiPointImpl(description); - } - - @Override - public double getAttributeAsDbl(int semantics, int index, int ordinate) { - return m_impl.getAttributeAsDbl(semantics, index, ordinate); - } - - @Override - public int getAttributeAsInt(int semantics, int index, int ordinate) { - return m_impl.getAttributeAsInt(semantics, index, ordinate); - } - - @Override - public Point getPoint(int index) { - return m_impl.getPoint(index); - } - - @Override - public int getPointCount() { - return m_impl.getPointCount(); - } - - @Override - public Point2D getXY(int index) { - return m_impl.getXY(index); - } - - @Override - public void getXY(int index, Point2D pt) { - m_impl.getXY(index, pt); - } - - @Override - Point3D getXYZ(int index) { - return m_impl.getXYZ(index); - } - - @Override - public void queryCoordinates(Point2D[] dst) { - m_impl.queryCoordinates(dst); - } - - @Override - public void queryCoordinates(Point[] dst) { - m_impl.queryCoordinates(dst); - } - - @Override - protected Object _getImpl() { - return m_impl; - } - - /** - * Adds a point multipoint. - * - * @param point The Point to be added to this multipoint. - */ - public void add(Point point) { - m_impl.add(point); - } - - /** - * Adds a point with the specified X, Y coordinates to this multipoint. - * - * @param x The new Point's X coordinate. - * @param y The new Point's Y coordinate. - */ - public void add(double x, double y) { - m_impl.add(x, y); - } - - /** - * Adds a point with the specified X, Y coordinates to this multipoint. - * - * @param pt the point to add - */ - public void add(Point2D pt) { - m_impl.add(pt.x, pt.y); - } - - /** - * Adds a 3DPoint with the specified X, Y, Z coordinates to this multipoint. - * - * @param x The new Point's X coordinate. - * @param y The new Point's Y coordinate. - * @param z The new Point's Z coordinate. - */ - void add(double x, double y, double z) { - m_impl.add(x, y, z); - } - - /** - * Appends points from another multipoint at the end of this multipoint. - * - * @param src The mulitpoint to append to this multipoint. - * @param srcFrom The start index in the source multipoint from which to start - * appending points. - * @param srcTo The end index in the source multipoint right after the last - * point to be appended. Use -1 to indicate the rest of the - * source multipoint. - */ - public void add(MultiVertexGeometry src, int srcFrom, int srcTo) { - m_impl.add((MultiVertexGeometryImpl) src._getImpl(), srcFrom, srcTo); - } - - void addPoints(Point2D[] points) { - m_impl.addPoints(points); - } - - void addPoints(Point[] points) { - m_impl.addPoints(points); - } - - /** - * Inserts a point to this multipoint. - * - * @param beforePointIndex The index right before the new point to insert. - * @param pt The point to insert. - */ - public void insertPoint(int beforePointIndex, Point pt) { - m_impl.insertPoint(beforePointIndex, pt); - } // inserts a point. The point is connected with Lines - - /** - * Removes a point from this multipoint. - * - * @param pointIndex The index of the point to be removed. - */ - public void removePoint(int pointIndex) { - m_impl.removePoint(pointIndex); - } - - /** - * Resizes the multipoint to have the given size. - * - * @param pointCount - The number of points in this multipoint. - */ - public void resize(int pointCount) { - m_impl.resize(pointCount); - } - - @Override - void queryCoordinates(Point3D[] dst) { - m_impl.queryCoordinates(dst); - } - - @Override - public void setAttribute(int semantics, int index, int ordinate, - double value) { - m_impl.setAttribute(semantics, index, ordinate, value); - } - - @Override - public void setAttribute(int semantics, int index, int ordinate, int value) { - m_impl.setAttribute(semantics, index, ordinate, value); - } - - @Override - public void setPoint(int index, Point pointSrc) { - m_impl.setPoint(index, pointSrc); - } - - @Override - public void setXY(int index, Point2D pt) { - m_impl.setXY(index, pt); - } - - @Override - void setXYZ(int index, Point3D pt) { - m_impl.setXYZ(index, pt); - } - - @Override - public void applyTransformation(Transformation2D transform) { - m_impl.applyTransformation(transform); - } - - @Override - void applyTransformation(Transformation3D transform) { - m_impl.applyTransformation(transform); - } - - @Override - public void copyTo(Geometry dst) { - m_impl.copyTo((Geometry) dst._getImpl()); - } - - @Override - public Geometry createInstance() { - return new MultiPoint(getDescription()); - } - - @Override - public int getDimension() { - return 0; - } - - @Override - public long estimateMemorySize() - { + Serializable { + + private static final long serialVersionUID = 2L; + + private MultiPointImpl m_impl; + + /** + * Creates a new empty multipoint. + */ + public MultiPoint() { + m_impl = new MultiPointImpl(); + } + + public MultiPoint(VertexDescription description) { + m_impl = new MultiPointImpl(description); + } + + @Override + public double getAttributeAsDbl(int semantics, int index, int ordinate) { + return m_impl.getAttributeAsDbl(semantics, index, ordinate); + } + + @Override + public int getAttributeAsInt(int semantics, int index, int ordinate) { + return m_impl.getAttributeAsInt(semantics, index, ordinate); + } + + @Override + public Point getPoint(int index) { + return m_impl.getPoint(index); + } + + @Override + public int getPointCount() { + return m_impl.getPointCount(); + } + + @Override + public Point2D getXY(int index) { + return m_impl.getXY(index); + } + + @Override + public void getXY(int index, Point2D pt) { + m_impl.getXY(index, pt); + } + + @Override + Point3D getXYZ(int index) { + return m_impl.getXYZ(index); + } + + @Override + public void queryCoordinates(Point2D[] dst) { + m_impl.queryCoordinates(dst); + } + + @Override + public void queryCoordinates(Point[] dst) { + m_impl.queryCoordinates(dst); + } + + @Override + protected Object _getImpl() { + return m_impl; + } + + /** + * Adds a point multipoint. + * + * @param point The Point to be added to this multipoint. + */ + public void add(Point point) { + m_impl.add(point); + } + + /** + * Adds a point with the specified X, Y coordinates to this multipoint. + * + * @param x The new Point's X coordinate. + * @param y The new Point's Y coordinate. + */ + public void add(double x, double y) { + m_impl.add(x, y); + } + + /** + * Adds a point with the specified X, Y coordinates to this multipoint. + * + * @param pt the point to add + */ + public void add(Point2D pt) { + m_impl.add(pt.x, pt.y); + } + + /** + * Adds a 3DPoint with the specified X, Y, Z coordinates to this multipoint. + * + * @param x The new Point's X coordinate. + * @param y The new Point's Y coordinate. + * @param z The new Point's Z coordinate. + */ + void add(double x, double y, double z) { + m_impl.add(x, y, z); + } + + /** + * Appends points from another multipoint at the end of this multipoint. + * + * @param src The mulitpoint to append to this multipoint. + * @param srcFrom The start index in the source multipoint from which to start + * appending points. + * @param srcTo The end index in the source multipoint right after the last + * point to be appended. Use -1 to indicate the rest of the + * source multipoint. + */ + public void add(MultiVertexGeometry src, int srcFrom, int srcTo) { + m_impl.add((MultiVertexGeometryImpl) src._getImpl(), srcFrom, srcTo); + } + + void addPoints(Point2D[] points) { + m_impl.addPoints(points); + } + + void addPoints(Point[] points) { + m_impl.addPoints(points); + } + + /** + * Inserts a point to this multipoint. + * + * @param beforePointIndex The index right before the new point to insert. + * @param pt The point to insert. + */ + public void insertPoint(int beforePointIndex, Point pt) { + m_impl.insertPoint(beforePointIndex, pt); + } // inserts a point. The point is connected with Lines + + /** + * Removes a point from this multipoint. + * + * @param pointIndex The index of the point to be removed. + */ + public void removePoint(int pointIndex) { + m_impl.removePoint(pointIndex); + } + + /** + * Resizes the multipoint to have the given size. + * + * @param pointCount - The number of points in this multipoint. + */ + public void resize(int pointCount) { + m_impl.resize(pointCount); + } + + @Override + void queryCoordinates(Point3D[] dst) { + m_impl.queryCoordinates(dst); + } + + @Override + public void setAttribute(int semantics, int index, int ordinate, + double value) { + m_impl.setAttribute(semantics, index, ordinate, value); + } + + @Override + public void setAttribute(int semantics, int index, int ordinate, int value) { + m_impl.setAttribute(semantics, index, ordinate, value); + } + + @Override + public void setPoint(int index, Point pointSrc) { + m_impl.setPoint(index, pointSrc); + } + + @Override + public void setXY(int index, Point2D pt) { + m_impl.setXY(index, pt); + } + + @Override + void setXYZ(int index, Point3D pt) { + m_impl.setXYZ(index, pt); + } + + @Override + public void applyTransformation(Transformation2D transform) { + m_impl.applyTransformation(transform); + } + + @Override + void applyTransformation(Transformation3D transform) { + m_impl.applyTransformation(transform); + } + + @Override + public void copyTo(Geometry dst) { + m_impl.copyTo((Geometry) dst._getImpl()); + } + + @Override + public Geometry createInstance() { + return new MultiPoint(getDescription()); + } + + @Override + public int getDimension() { + return 0; + } + + @Override + public long estimateMemorySize() { return SIZE_OF_MULTI_POINT + m_impl.estimateMemorySize(); } @@ -258,119 +257,119 @@ public Geometry.Type getType() { return Type.MultiPoint; } - @Override - public VertexDescription getDescription() { - return m_impl.getDescription(); - } - - @Override - public void addAttribute(int semantics) { - m_impl.addAttribute(semantics); - } - - @Override - public void assignVertexDescription(VertexDescription src) { - m_impl.assignVertexDescription(src); - } - - @Override - public void dropAllAttributes() { - m_impl.dropAllAttributes(); - } - - @Override - public void dropAttribute(int semantics) { - m_impl.dropAttribute(semantics); - } - - @Override - public void mergeVertexDescription(VertexDescription src) { - m_impl.mergeVertexDescription(src); - } - - @Override - public boolean isEmpty() { - return m_impl.isEmpty(); - } - - @Override - public void queryEnvelope(Envelope env) { - m_impl.queryEnvelope(env); - } - - @Override - public void queryEnvelope2D(Envelope2D env) { - m_impl.queryEnvelope2D(env); - } - - @Override - void queryEnvelope3D(Envelope3D env) { - m_impl.queryEnvelope3D(env); - } - - @Override - public Envelope1D queryInterval(int semantics, int ordinate) { - return m_impl.queryInterval(semantics, ordinate); - } - - @Override - public void setEmpty() { - m_impl.setEmpty(); - } - - /** - * Returns TRUE when this geometry has exactly same type, properties, and - * coordinates as the other geometry. - */ - @Override - public boolean equals(Object other) { - if (other == null) - return false; - - if (other == this) - return true; - - if (other.getClass() != getClass()) - return false; - - return m_impl.equals(((MultiPoint) other)._getImpl()); - } - - /** - * Returns a hash code value for this multipoint. - */ - @Override - public int hashCode() { - return m_impl.hashCode(); - } - - int queryCoordinates(Point2D[] dst, int dstSize, int beginIndex, - int endIndex) { - return m_impl.queryCoordinates(dst, dstSize, beginIndex, endIndex); - } - - @Override - public void getPointByVal(int index, Point outPoint) { - m_impl.getPointByVal(index, outPoint); - } - - @Override - public void setPointByVal(int index, Point pointSrc) { - m_impl.setPointByVal(index, pointSrc); - } - - @Override - public int getStateFlag() { - return m_impl.getStateFlag(); - } - - @Override - public Geometry getBoundary() { - return m_impl.getBoundary(); - } - - @Override - public void replaceNaNs(int semantics, double value) { - m_impl.replaceNaNs(semantics, value); - } + @Override + public VertexDescription getDescription() { + return m_impl.getDescription(); + } + + @Override + public void addAttribute(int semantics) { + m_impl.addAttribute(semantics); + } + + @Override + public void assignVertexDescription(VertexDescription src) { + m_impl.assignVertexDescription(src); + } + + @Override + public void dropAllAttributes() { + m_impl.dropAllAttributes(); + } + + @Override + public void dropAttribute(int semantics) { + m_impl.dropAttribute(semantics); + } + + @Override + public void mergeVertexDescription(VertexDescription src) { + m_impl.mergeVertexDescription(src); + } + + @Override + public boolean isEmpty() { + return m_impl.isEmpty(); + } + + @Override + public void queryEnvelope(Envelope env) { + m_impl.queryEnvelope(env); + } + + @Override + public void queryEnvelope2D(Envelope2D env) { + m_impl.queryEnvelope2D(env); + } + + @Override + void queryEnvelope3D(Envelope3D env) { + m_impl.queryEnvelope3D(env); + } + + @Override + public Envelope1D queryInterval(int semantics, int ordinate) { + return m_impl.queryInterval(semantics, ordinate); + } + + @Override + public void setEmpty() { + m_impl.setEmpty(); + } + + /** + * Returns TRUE when this geometry has exactly same type, properties, and + * coordinates as the other geometry. + */ + @Override + public boolean equals(Object other) { + if (other == null) + return false; + + if (other == this) + return true; + + if (other.getClass() != getClass()) + return false; + + return m_impl.equals(((MultiPoint) other)._getImpl()); + } + + /** + * Returns a hash code value for this multipoint. + */ + @Override + public int hashCode() { + return m_impl.hashCode(); + } + + int queryCoordinates(Point2D[] dst, int dstSize, int beginIndex, + int endIndex) { + return m_impl.queryCoordinates(dst, dstSize, beginIndex, endIndex); + } + + @Override + public void getPointByVal(int index, Point outPoint) { + m_impl.getPointByVal(index, outPoint); + } + + @Override + public void setPointByVal(int index, Point pointSrc) { + m_impl.setPointByVal(index, pointSrc); + } + + @Override + public int getStateFlag() { + return m_impl.getStateFlag(); + } + + @Override + public Geometry getBoundary() { + return m_impl.getBoundary(); + } + + @Override + public void replaceNaNs(int semantics, double value) { + m_impl.replaceNaNs(semantics, value); + } } diff --git a/src/main/java/com/esri/core/geometry/MultiPointImpl.java b/src/main/java/com/esri/core/geometry/MultiPointImpl.java index 0825b86e..4e401a8f 100644 --- a/src/main/java/com/esri/core/geometry/MultiPointImpl.java +++ b/src/main/java/com/esri/core/geometry/MultiPointImpl.java @@ -33,224 +33,223 @@ */ final class MultiPointImpl extends MultiVertexGeometryImpl { - public MultiPointImpl() { - super(); - m_description = VertexDescriptionDesignerImpl.getDefaultDescriptor2D(); - m_pointCount = 0; - } - - public MultiPointImpl(VertexDescription description) { - super(); - if (description == null) - throw new IllegalArgumentException(); - - m_description = description; - m_pointCount = 0; - } - - @Override - public Geometry createInstance() { - return new MultiPoint(m_description); - } - - /** - * Adds a Point to this MultiPoint. - */ - public void add(Point point) { - resize(m_pointCount + 1); - setPoint(m_pointCount - 1, point); - } - - /** - * Adds a Point to this MultiPoint with given x, y coordinates. - */ - public void add(double x, double y) { - resize(m_pointCount + 1); - Point2D pt = new Point2D(); - pt.setCoords(x, y); - setXY(m_pointCount - 1, pt); - } - - /** - * Adds a Point to this MultiPoint with given x, y, z coordinates. - */ - public void add(double x, double y, double z) { - resize(m_pointCount + 1); - Point3D pt = new Point3D(); - pt.setCoords(x, y, z); - setXYZ(m_pointCount - 1, pt); - } - - /** - * Appends points from another MultiVertexGeometryImpl at the end of this - * one. - * - * @param src The source MultiVertexGeometryImpl - */ - public void add(MultiVertexGeometryImpl src, int beginIndex, int endIndex) { - int endIndexC = endIndex < 0 ? src.getPointCount() : endIndex; - if (beginIndex < 0 || beginIndex > src.getPointCount() - || endIndexC < beginIndex) - throw new IllegalArgumentException(); - - if (beginIndex == endIndexC) - return; - - mergeVertexDescription(src.getDescription()); - int count = endIndexC - beginIndex; - int oldPointCount = m_pointCount; - resize(m_pointCount + count); - _verifyAllStreams(); - for (int iattrib = 0, nattrib = src.getDescription() - .getAttributeCount(); iattrib < nattrib; iattrib++) { - int semantics = src.getDescription()._getSemanticsImpl(iattrib); - int ncomps = VertexDescription.getComponentCount(semantics); - AttributeStreamBase stream = getAttributeStreamRef(semantics); - AttributeStreamBase srcStream = src - .getAttributeStreamRef(semantics); - stream.insertRange(oldPointCount * ncomps, srcStream, beginIndex - * ncomps, count * ncomps, true, 1, oldPointCount * ncomps); - } - } - - public void addPoints(Point2D[] points) { - int count = points.length; - int oldPointCount = m_pointCount; - resize(m_pointCount + count); - for (int i = 0; i < count; i++) - setXY(oldPointCount + i, points[i]); - } - - public void insertPoint(int beforePointIndex, Point pt) { - if (beforePointIndex > getPointCount()) - throw new GeometryException("index out of bounds"); - - if (beforePointIndex < 0) - beforePointIndex = getPointCount(); - - mergeVertexDescription(pt.getDescription()); - int oldPointCount = m_pointCount; - _resizeImpl(m_pointCount + 1); - _verifyAllStreams(); - - for (int iattr = 0, nattr = m_description.getAttributeCount(); iattr < nattr; iattr++) { - int semantics = m_description._getSemanticsImpl(iattr); - int comp = VertexDescription.getComponentCount(semantics); - - AttributeStreamBase stream = AttributeStreamBase - .createAttributeStreamWithSemantics(semantics, 1); - if (pt.hasAttribute(semantics)) { - m_vertexAttributes[iattr] - .insertAttributes(comp * beforePointIndex, pt, - semantics, comp * oldPointCount); - } else { - // Need to make room for the attribute, so we copy a default - // value in - - double v = VertexDescription.getDefaultValue(semantics); - m_vertexAttributes[iattr].insertRange(comp * beforePointIndex, - v, comp, comp * oldPointCount); - } - } - - notifyModified(DirtyFlags.DirtyCoordinates); - } - - void removePoint(int pointIndex) { - if (pointIndex < 0 || pointIndex >= getPointCount()) - throw new GeometryException("index out of bounds"); - - _verifyAllStreams(); - - // Remove the attribute value for the path - for (int iattr = 0, nattr = m_description.getAttributeCount(); iattr < nattr; iattr++) { - if (m_vertexAttributes[iattr] != null) { - int semantics = m_description._getSemanticsImpl(iattr); - int comp = VertexDescription.getComponentCount(semantics); - m_vertexAttributes[iattr].eraseRange(comp * pointIndex, comp, - comp * m_pointCount); - } - } - - m_pointCount--; - m_reservedPointCount--; - notifyModified(DirtyFlags.DirtyCoordinates); - } - - /** - * Resizes the MultiPoint to have the given size. - */ - public void resize(int pointCount) { - _resizeImpl(pointCount); - } - - @Override - void _copyToImpl(MultiVertexGeometryImpl mvg) { - } - - @Override - public void setEmpty() { - super._setEmptyImpl(); - } - - @Override - public void applyTransformation(Transformation2D transform) { - if (isEmpty()) - return; - - _verifyAllStreams(); - AttributeStreamOfDbl points = (AttributeStreamOfDbl) m_vertexAttributes[0]; - Point2D pt2 = new Point2D(); - - for (int ipoint = 0; ipoint < m_pointCount; ipoint++) { - pt2.x = points.read(ipoint * 2); - pt2.y = points.read(ipoint * 2 + 1); - - transform.transform(pt2, pt2); - points.write(ipoint * 2, pt2.x); - points.write(ipoint * 2 + 1, pt2.y); - } - - // REFACTOR: reset the exact envelope only and transform the loose - // envelope - notifyModified(DirtyFlags.DirtyCoordinates); - } - - @Override - void applyTransformation(Transformation3D transform) { - if (isEmpty()) - return; - - _verifyAllStreams(); - addAttribute(Semantics.Z); - _verifyAllStreams(); - AttributeStreamOfDbl points = (AttributeStreamOfDbl) m_vertexAttributes[0]; - AttributeStreamOfDbl zs = (AttributeStreamOfDbl) m_vertexAttributes[1]; - Point3D pt3 = new Point3D(); - for (int ipoint = 0; ipoint < m_pointCount; ipoint++) { - pt3.x = points.read(ipoint * 2); - pt3.y = points.read(ipoint * 2 + 1); - pt3.z = zs.read(ipoint); - - Point3D res = transform.transform(pt3); - points.write(ipoint * 2, res.x); - points.write(ipoint * 2 + 1, res.y); - zs.write(ipoint, res.z); - } - - // REFACTOR: reset the exact envelope only and transform the loose - // envelope - notifyModified(DirtyFlags.DirtyCoordinates); - } - - @Override - public int getDimension() { - return 0; - } + public MultiPointImpl() { + super(); + m_description = VertexDescriptionDesignerImpl.getDefaultDescriptor2D(); + m_pointCount = 0; + } + + public MultiPointImpl(VertexDescription description) { + super(); + if (description == null) + throw new IllegalArgumentException(); + + m_description = description; + m_pointCount = 0; + } + + @Override + public Geometry createInstance() { + return new MultiPoint(m_description); + } + + /** + * Adds a Point to this MultiPoint. + */ + public void add(Point point) { + resize(m_pointCount + 1); + setPoint(m_pointCount - 1, point); + } + + /** + * Adds a Point to this MultiPoint with given x, y coordinates. + */ + public void add(double x, double y) { + resize(m_pointCount + 1); + Point2D pt = new Point2D(); + pt.setCoords(x, y); + setXY(m_pointCount - 1, pt); + } + + /** + * Adds a Point to this MultiPoint with given x, y, z coordinates. + */ + public void add(double x, double y, double z) { + resize(m_pointCount + 1); + Point3D pt = new Point3D(); + pt.setCoords(x, y, z); + setXYZ(m_pointCount - 1, pt); + } + + /** + * Appends points from another MultiVertexGeometryImpl at the end of this + * one. + * + * @param src The source MultiVertexGeometryImpl + */ + public void add(MultiVertexGeometryImpl src, int beginIndex, int endIndex) { + int endIndexC = endIndex < 0 ? src.getPointCount() : endIndex; + if (beginIndex < 0 || beginIndex > src.getPointCount() + || endIndexC < beginIndex) + throw new IllegalArgumentException(); + + if (beginIndex == endIndexC) + return; + + mergeVertexDescription(src.getDescription()); + int count = endIndexC - beginIndex; + int oldPointCount = m_pointCount; + resize(m_pointCount + count); + _verifyAllStreams(); + for (int iattrib = 0, nattrib = src.getDescription() + .getAttributeCount(); iattrib < nattrib; iattrib++) { + int semantics = src.getDescription()._getSemanticsImpl(iattrib); + int ncomps = VertexDescription.getComponentCount(semantics); + AttributeStreamBase stream = getAttributeStreamRef(semantics); + AttributeStreamBase srcStream = src + .getAttributeStreamRef(semantics); + stream.insertRange(oldPointCount * ncomps, srcStream, beginIndex + * ncomps, count * ncomps, true, 1, oldPointCount * ncomps); + } + } + + public void addPoints(Point2D[] points) { + int count = points.length; + int oldPointCount = m_pointCount; + resize(m_pointCount + count); + for (int i = 0; i < count; i++) + setXY(oldPointCount + i, points[i]); + } + + public void insertPoint(int beforePointIndex, Point pt) { + if (beforePointIndex > getPointCount()) + throw new GeometryException("index out of bounds"); + + if (beforePointIndex < 0) + beforePointIndex = getPointCount(); + + mergeVertexDescription(pt.getDescription()); + int oldPointCount = m_pointCount; + _resizeImpl(m_pointCount + 1); + _verifyAllStreams(); + + for (int iattr = 0, nattr = m_description.getAttributeCount(); iattr < nattr; iattr++) { + int semantics = m_description._getSemanticsImpl(iattr); + int comp = VertexDescription.getComponentCount(semantics); + + AttributeStreamBase stream = AttributeStreamBase + .createAttributeStreamWithSemantics(semantics, 1); + if (pt.hasAttribute(semantics)) { + m_vertexAttributes[iattr] + .insertAttributes(comp * beforePointIndex, pt, + semantics, comp * oldPointCount); + } else { + // Need to make room for the attribute, so we copy a default + // value in + + double v = VertexDescription.getDefaultValue(semantics); + m_vertexAttributes[iattr].insertRange(comp * beforePointIndex, + v, comp, comp * oldPointCount); + } + } + + notifyModified(DirtyFlags.DirtyCoordinates); + } + + void removePoint(int pointIndex) { + if (pointIndex < 0 || pointIndex >= getPointCount()) + throw new GeometryException("index out of bounds"); + + _verifyAllStreams(); + + // Remove the attribute value for the path + for (int iattr = 0, nattr = m_description.getAttributeCount(); iattr < nattr; iattr++) { + if (m_vertexAttributes[iattr] != null) { + int semantics = m_description._getSemanticsImpl(iattr); + int comp = VertexDescription.getComponentCount(semantics); + m_vertexAttributes[iattr].eraseRange(comp * pointIndex, comp, + comp * m_pointCount); + } + } + + m_pointCount--; + m_reservedPointCount--; + notifyModified(DirtyFlags.DirtyCoordinates); + } + + /** + * Resizes the MultiPoint to have the given size. + */ + public void resize(int pointCount) { + _resizeImpl(pointCount); + } + + @Override + void _copyToImpl(MultiVertexGeometryImpl mvg) { + } + + @Override + public void setEmpty() { + super._setEmptyImpl(); + } + + @Override + public void applyTransformation(Transformation2D transform) { + if (isEmpty()) + return; + + _verifyAllStreams(); + AttributeStreamOfDbl points = (AttributeStreamOfDbl) m_vertexAttributes[0]; + Point2D pt2 = new Point2D(); + + for (int ipoint = 0; ipoint < m_pointCount; ipoint++) { + pt2.x = points.read(ipoint * 2); + pt2.y = points.read(ipoint * 2 + 1); + + transform.transform(pt2, pt2); + points.write(ipoint * 2, pt2.x); + points.write(ipoint * 2 + 1, pt2.y); + } + + // REFACTOR: reset the exact envelope only and transform the loose + // envelope + notifyModified(DirtyFlags.DirtyCoordinates); + } @Override - public long estimateMemorySize() - { + void applyTransformation(Transformation3D transform) { + if (isEmpty()) + return; + + _verifyAllStreams(); + addAttribute(Semantics.Z); + _verifyAllStreams(); + AttributeStreamOfDbl points = (AttributeStreamOfDbl) m_vertexAttributes[0]; + AttributeStreamOfDbl zs = (AttributeStreamOfDbl) m_vertexAttributes[1]; + Point3D pt3 = new Point3D(); + for (int ipoint = 0; ipoint < m_pointCount; ipoint++) { + pt3.x = points.read(ipoint * 2); + pt3.y = points.read(ipoint * 2 + 1); + pt3.z = zs.read(ipoint); + + Point3D res = transform.transform(pt3); + points.write(ipoint * 2, res.x); + points.write(ipoint * 2 + 1, res.y); + zs.write(ipoint, res.z); + } + + // REFACTOR: reset the exact envelope only and transform the loose + // envelope + notifyModified(DirtyFlags.DirtyCoordinates); + } + + @Override + public int getDimension() { + return 0; + } + + @Override + public long estimateMemorySize() { long size = SIZE_OF_MULTI_POINT_IMPL + (m_envelope != null ? m_envelope.estimateMemorySize() : 0); if (m_vertexAttributes != null) { @@ -266,100 +265,100 @@ public Geometry.Type getType() { return Type.MultiPoint; } - @Override - public double calculateArea2D() { - return 0; - } - - @Override - public double calculateLength2D() { - return 0; - } - - @Override - public Object _getImpl() { - return this; - } - - @Override - public boolean equals(Object other) { - if (other == this) - return true; - - if (!(other instanceof MultiPointImpl)) - return false; - - return super.equals(other); - } - - public void addPoints(Point[] points) { - int count = points.length; - // int oldPointCount = m_pointCount; - resize(m_pointCount + count); - for (int i = 0; i < count; i++) - setPoint(i, points[i]); - } - - public int queryCoordinates(Point2D[] dst, int dstSize, int beginIndex, - int endIndex) { - int endIndexC = endIndex < 0 ? m_pointCount : endIndex; - endIndexC = Math.min(endIndexC, beginIndex + dstSize); - - if (beginIndex < 0 || beginIndex >= m_pointCount - || endIndexC < beginIndex || dst.length != dstSize) - throw new IllegalArgumentException();// GEOMTHROW(invalid_argument); - - AttributeStreamOfDbl xy = (AttributeStreamOfDbl) getAttributeStreamRef(VertexDescription.Semantics.POSITION); - int pointCountToRead = endIndexC - beginIndex; - double[] dstArray = new double[pointCountToRead * 2]; - xy.readRange(2 * beginIndex, pointCountToRead * 2, dstArray, 0, true); - - for (int i = 0; i < pointCountToRead; i++) { - dst[i] = new Point2D(dstArray[i * 2], dstArray[i * 2 + 1]); - } - - return endIndexC; - } - - @Override - protected void _notifyModifiedAllImpl() { - // TODO Auto-generated method stub - - } - - @Override - protected void _verifyStreamsImpl() { - // TODO Auto-generated method stub - - } - - @Override - public boolean _buildRasterizedGeometryAccelerator(double toleranceXY, - GeometryAccelerationDegree accelDegree) { - // TODO Auto-generated method stub - return false; - } - - @Override - public boolean _buildQuadTreeAccelerator(GeometryAccelerationDegree accelDegree) { - // TODO Auto-generated method stub - return false; - } - - // @Override - // void _notifyModifiedAllImpl() { - // // TODO Auto-generated method stub - // - // } - - // @Override - // protected void _verifyStreamsImpl() { - // // TODO Auto-generated method stub - // - // } - - @Override - public Geometry getBoundary() { - return null; - } + @Override + public double calculateArea2D() { + return 0; + } + + @Override + public double calculateLength2D() { + return 0; + } + + @Override + public Object _getImpl() { + return this; + } + + @Override + public boolean equals(Object other) { + if (other == this) + return true; + + if (!(other instanceof MultiPointImpl)) + return false; + + return super.equals(other); + } + + public void addPoints(Point[] points) { + int count = points.length; + // int oldPointCount = m_pointCount; + resize(m_pointCount + count); + for (int i = 0; i < count; i++) + setPoint(i, points[i]); + } + + public int queryCoordinates(Point2D[] dst, int dstSize, int beginIndex, + int endIndex) { + int endIndexC = endIndex < 0 ? m_pointCount : endIndex; + endIndexC = Math.min(endIndexC, beginIndex + dstSize); + + if (beginIndex < 0 || beginIndex >= m_pointCount + || endIndexC < beginIndex || dst.length != dstSize) + throw new IllegalArgumentException();// GEOMTHROW(invalid_argument); + + AttributeStreamOfDbl xy = (AttributeStreamOfDbl) getAttributeStreamRef(VertexDescription.Semantics.POSITION); + int pointCountToRead = endIndexC - beginIndex; + double[] dstArray = new double[pointCountToRead * 2]; + xy.readRange(2 * beginIndex, pointCountToRead * 2, dstArray, 0, true); + + for (int i = 0; i < pointCountToRead; i++) { + dst[i] = new Point2D(dstArray[i * 2], dstArray[i * 2 + 1]); + } + + return endIndexC; + } + + @Override + protected void _notifyModifiedAllImpl() { + // TODO Auto-generated method stub + + } + + @Override + protected void _verifyStreamsImpl() { + // TODO Auto-generated method stub + + } + + @Override + public boolean _buildRasterizedGeometryAccelerator(double toleranceXY, + GeometryAccelerationDegree accelDegree) { + // TODO Auto-generated method stub + return false; + } + + @Override + public boolean _buildQuadTreeAccelerator(GeometryAccelerationDegree accelDegree) { + // TODO Auto-generated method stub + return false; + } + + // @Override + // void _notifyModifiedAllImpl() { + // // TODO Auto-generated method stub + // + // } + + // @Override + // protected void _verifyStreamsImpl() { + // // TODO Auto-generated method stub + // + // } + + @Override + public Geometry getBoundary() { + return null; + } } diff --git a/src/main/java/com/esri/core/geometry/MultiVertexGeometry.java b/src/main/java/com/esri/core/geometry/MultiVertexGeometry.java index 56dca8ce..d3a7b921 100644 --- a/src/main/java/com/esri/core/geometry/MultiVertexGeometry.java +++ b/src/main/java/com/esri/core/geometry/MultiVertexGeometry.java @@ -33,173 +33,173 @@ * There are as many arrays as there are attributes in the vertex. */ public abstract class MultiVertexGeometry extends Geometry implements - Serializable { - - @Override - protected void _assignVertexDescriptionImpl(VertexDescription newDescription) { - throw new GeometryException("invalid call"); - } - - /** - * Returns the total vertex count in this Geometry. - */ - public abstract int getPointCount(); - - /** - * Returns given vertex of the Geometry. - */ - public abstract Point getPoint(int index);// Java only - - /** - * Returns given vertex of the Geometry by value. - */ - public void getPoint(int index, Point ptOut) { - getPointByVal(index, ptOut); - } - - /** - * Sets the vertex at given index of the Geometry. - * - * @param index The index of the vertex being changed. - * @param pointSrc The Point instance to set given vertex attributes from. The - * pointSrc can not be empty.
- * The method throws if the pointSrc is not of the Point type.
- * The attributes, that are present in the pointSrc and missing - * in this Geometry, will be added to the Geometry.
- * The vertex attributes missing in the pointSrc but present in - * the Geometry will be set to the default values (see - * VertexDescription::GetDefaultValue). - */ - public abstract void setPoint(int index, Point pointSrc);// Java only - - /** - * Returns XY coordinates of the given vertex of the Geometry. - */ - public abstract Point2D getXY(int index); - - public abstract void getXY(int index, Point2D pt); - - /** - * Sets XY coordinates of the given vertex of the Geometry. All other - * attributes are unchanged. - */ - public abstract void setXY(int index, Point2D pt); - - /** - * Returns XYZ coordinates of the given vertex of the Geometry. If the - * Geometry has no Z's, the default value for Z is returned (0). - */ - abstract Point3D getXYZ(int index); - - /** - * Sets XYZ coordinates of the given vertex of the Geometry. If Z attribute - * is not present in this Geometry, it is added. All other attributes are - * unchanged. - */ - abstract void setXYZ(int index, Point3D pt); - - /** - * Returns XY coordinates as an array. - */ - public Point2D[] getCoordinates2D() { - Point2D[] arr = new Point2D[getPointCount()]; - queryCoordinates(arr); - return arr; - } - - /** - * Returns XYZ coordinates as an array. - */ - Point3D[] getCoordinates3D() { - Point3D[] arr = new Point3D[getPointCount()]; - queryCoordinates(arr); - return arr; - } - - public abstract void queryCoordinates(Point[] dst); - - /** - * Queries XY coordinates as an array. The array must be larg enough (See - * GetPointCount()). - */ - public abstract void queryCoordinates(Point2D[] dst); - - /** - * Queries XYZ coordinates as an array. The array must be larg enough (See - * GetPointCount()). - */ - abstract void queryCoordinates(Point3D[] dst); - - /** - * Returns value of the given vertex attribute as double. - * - * @param semantics The atribute semantics. - * @param index is the vertex index in the Geometry. - * @param ordinate is the ordinate of a vertex attribute (for example, y has - * ordinate of 1, because it is second ordinate of POSITION) - *

- * If attribute is not present, the default value is returned. - * See VertexDescription::GetDefaultValue() method. - */ - abstract double getAttributeAsDbl(int semantics, int index, - int ordinate); - - /** - * Returns value of the given vertex attribute as int. - * - * @param semantics The atribute semantics. - * @param index is the vertex index in the Geometry. - * @param ordinate is the ordinate of a vertex attribute (for example, y has - * ordinate of 1, because it is second ordinate of POSITION) - *

- * If attribute is not present, the default value is returned. - * See VertexDescription::GetDefaultValue() method. Avoid using - * this method on non-integer atributes. - */ - abstract int getAttributeAsInt(int semantics, int index, int ordinate); - - /** - * Sets the value of given attribute at given posisiotnsis. - * - * @param semantics The atribute semantics. - * @param index is the vertex index in the Geometry. - * @param ordinate is the ordinate of a vertex attribute (for example, y has - * ordinate of 1, because it is seond ordinate of POSITION) - * @param value is the value to set. as well as the number of components of - * the attribute. - *

- * If the attribute is not present in this Geometry, it is added. - */ - abstract void setAttribute(int semantics, int index, int ordinate, - double value); - - /** - * Same as above, but works with ints. Avoid using this method on - * non-integer atributes because some double attributes may have NaN default - * values (e.g. Ms) - */ - abstract void setAttribute(int semantics, int index, int ordinate, - int value); - - /** - * Returns given vertex of the Geometry. The outPoint will have same - * VertexDescription as this Geometry. - */ - public abstract void getPointByVal(int index, Point outPoint); - - /** - * Sets the vertex at given index of the Geometry. - * - * @param index The index of the vertex being changed. - * @param pointSrc The Point instance to set given vertex attributes from. The - * pointSrc can not be empty.
- * The method throws if the pointSrc is not of the Point type.
- * The attributes, that are present in the pointSrc and missing - * in this Geometry, will be added to the Geometry.
- * The vertex attributes missing in the pointSrc but present in - * the Geometry will be set to the default values (see - * VertexDescription::GetDefaultValue). - */ - public abstract void setPointByVal(int index, Point pointSrc); + Serializable { + + @Override + protected void _assignVertexDescriptionImpl(VertexDescription newDescription) { + throw new GeometryException("invalid call"); + } + + /** + * Returns the total vertex count in this Geometry. + */ + public abstract int getPointCount(); + + /** + * Returns given vertex of the Geometry. + */ + public abstract Point getPoint(int index);// Java only + + /** + * Returns given vertex of the Geometry by value. + */ + public void getPoint(int index, Point ptOut) { + getPointByVal(index, ptOut); + } + + /** + * Sets the vertex at given index of the Geometry. + * + * @param index The index of the vertex being changed. + * @param pointSrc The Point instance to set given vertex attributes from. The + * pointSrc can not be empty.
+ * The method throws if the pointSrc is not of the Point type.
+ * The attributes, that are present in the pointSrc and missing + * in this Geometry, will be added to the Geometry.
+ * The vertex attributes missing in the pointSrc but present in + * the Geometry will be set to the default values (see + * VertexDescription::GetDefaultValue). + */ + public abstract void setPoint(int index, Point pointSrc);// Java only + + /** + * Returns XY coordinates of the given vertex of the Geometry. + */ + public abstract Point2D getXY(int index); + + public abstract void getXY(int index, Point2D pt); + + /** + * Sets XY coordinates of the given vertex of the Geometry. All other + * attributes are unchanged. + */ + public abstract void setXY(int index, Point2D pt); + + /** + * Returns XYZ coordinates of the given vertex of the Geometry. If the + * Geometry has no Z's, the default value for Z is returned (0). + */ + abstract Point3D getXYZ(int index); + + /** + * Sets XYZ coordinates of the given vertex of the Geometry. If Z attribute + * is not present in this Geometry, it is added. All other attributes are + * unchanged. + */ + abstract void setXYZ(int index, Point3D pt); + + /** + * Returns XY coordinates as an array. + */ + public Point2D[] getCoordinates2D() { + Point2D[] arr = new Point2D[getPointCount()]; + queryCoordinates(arr); + return arr; + } + + /** + * Returns XYZ coordinates as an array. + */ + Point3D[] getCoordinates3D() { + Point3D[] arr = new Point3D[getPointCount()]; + queryCoordinates(arr); + return arr; + } + + public abstract void queryCoordinates(Point[] dst); + + /** + * Queries XY coordinates as an array. The array must be larg enough (See + * GetPointCount()). + */ + public abstract void queryCoordinates(Point2D[] dst); + + /** + * Queries XYZ coordinates as an array. The array must be larg enough (See + * GetPointCount()). + */ + abstract void queryCoordinates(Point3D[] dst); + + /** + * Returns value of the given vertex attribute as double. + * + * @param semantics The atribute semantics. + * @param index is the vertex index in the Geometry. + * @param ordinate is the ordinate of a vertex attribute (for example, y has + * ordinate of 1, because it is second ordinate of POSITION) + *

+ * If attribute is not present, the default value is returned. + * See VertexDescription::GetDefaultValue() method. + */ + abstract double getAttributeAsDbl(int semantics, int index, + int ordinate); + + /** + * Returns value of the given vertex attribute as int. + * + * @param semantics The atribute semantics. + * @param index is the vertex index in the Geometry. + * @param ordinate is the ordinate of a vertex attribute (for example, y has + * ordinate of 1, because it is second ordinate of POSITION) + *

+ * If attribute is not present, the default value is returned. + * See VertexDescription::GetDefaultValue() method. Avoid using + * this method on non-integer atributes. + */ + abstract int getAttributeAsInt(int semantics, int index, int ordinate); + + /** + * Sets the value of given attribute at given posisiotnsis. + * + * @param semantics The atribute semantics. + * @param index is the vertex index in the Geometry. + * @param ordinate is the ordinate of a vertex attribute (for example, y has + * ordinate of 1, because it is seond ordinate of POSITION) + * @param value is the value to set. as well as the number of components of + * the attribute. + *

+ * If the attribute is not present in this Geometry, it is added. + */ + abstract void setAttribute(int semantics, int index, int ordinate, + double value); + + /** + * Same as above, but works with ints. Avoid using this method on + * non-integer atributes because some double attributes may have NaN default + * values (e.g. Ms) + */ + abstract void setAttribute(int semantics, int index, int ordinate, + int value); + + /** + * Returns given vertex of the Geometry. The outPoint will have same + * VertexDescription as this Geometry. + */ + public abstract void getPointByVal(int index, Point outPoint); + + /** + * Sets the vertex at given index of the Geometry. + * + * @param index The index of the vertex being changed. + * @param pointSrc The Point instance to set given vertex attributes from. The + * pointSrc can not be empty.
+ * The method throws if the pointSrc is not of the Point type.
+ * The attributes, that are present in the pointSrc and missing + * in this Geometry, will be added to the Geometry.
+ * The vertex attributes missing in the pointSrc but present in + * the Geometry will be set to the default values (see + * VertexDescription::GetDefaultValue). + */ + public abstract void setPointByVal(int index, Point pointSrc); } diff --git a/src/main/java/com/esri/core/geometry/MultiVertexGeometryImpl.java b/src/main/java/com/esri/core/geometry/MultiVertexGeometryImpl.java index 8907df37..767ba6b3 100644 --- a/src/main/java/com/esri/core/geometry/MultiVertexGeometryImpl.java +++ b/src/main/java/com/esri/core/geometry/MultiVertexGeometryImpl.java @@ -38,1079 +38,1079 @@ */ abstract class MultiVertexGeometryImpl extends MultiVertexGeometry { - // HEADER DEFINED - public interface GeometryXSimple { - final int Unknown = -1; // not know if simple or not - final int Not = 0; // not simple - final int Weak = 1; // weak simple (no self intersections, ring - // orientation is correct, but ring order is not) - final int Strong = 2; // same as weak simple + OGC ring order. - } - - // TODO Remove? - - /** - * \internal CHildren implement this method to copy additional information - */ - abstract void _copyToImpl(MultiVertexGeometryImpl mvg); - - protected abstract void _notifyModifiedAllImpl(); - - /** - * \internal Called inside of the VerifyAllStreams to get a child class a - * chance to do additional verify. - */ - protected abstract void _verifyStreamsImpl(); - - public interface DirtyFlags { - public static final int DirtyIsKnownSimple = 1; // !<0 when IsWeakSimple - // flag is valid - public static final int IsWeakSimple = 2; // != m_pointCount) - // TODO - throw new GeometryException("index out of bounds"); - - // _ASSERT(!IsEmpty()); - // _ASSERT(m_vertexAttributes != null); - - _verifyAllStreams(); - - Point outPoint = dst; - outPoint.assignVertexDescription(m_description); - if (outPoint.isEmpty()) - outPoint._setToDefault(); - - for (int attributeIndex = 0; attributeIndex < m_description - .getAttributeCount(); attributeIndex++) { - // fix semantics - int semantics = m_description._getSemanticsImpl(attributeIndex); - - // VertexDescription.getComponentCount(semantics); - for (int icomp = 0, ncomp = VertexDescription - .getComponentCount(semantics); icomp < ncomp; icomp++) { - double v = m_vertexAttributes[attributeIndex].readAsDbl(ncomp - * index + icomp); - outPoint.setAttribute(semantics, icomp, v); - } - } - } - - @Override - public void setPointByVal(int index, Point src) { - if (index < 0 || index >= m_pointCount) - throw new GeometryException("index out of bounds"); - - Point point = src; - - if (src.isEmpty())// can not assign an empty point to a multipoint - // vertex - throw new IllegalArgumentException(); - - _verifyAllStreams();// verify all allocated streams are of necessary - // size. - VertexDescription vdin = point.getDescription(); - for (int attributeIndex = 0; attributeIndex < vdin.getAttributeCount(); attributeIndex++) { - int semantics = vdin._getSemanticsImpl(attributeIndex); - int ncomp = VertexDescription.getComponentCount(semantics); - for (int icomp = 0; icomp < ncomp; icomp++) { - double v = point.getAttributeAsDbl(semantics, icomp); - setAttribute(semantics, index, icomp, v); - } - } - } - - // Checked vs. Jan 11, 2011 - @Override - public Point2D getXY(int index) { - Point2D pt = new Point2D(); - getXY(index, pt); - return pt; - } - - @Override - public void getXY(int index, Point2D pt) { - if (index < 0 || index >= getPointCount()) - throw new IndexOutOfBoundsException(); - - _verifyAllStreams(); - // AttributeStreamOfDbl v = (AttributeStreamOfDbl) - // m_vertexAttributes[0]; - AttributeStreamOfDbl v = (AttributeStreamOfDbl) m_vertexAttributes[0]; - v.read(index * 2, pt); - } - - // Checked vs. Jan 11, 2011 - @Override - public void setXY(int index, Point2D pt) { - if (index < 0 || index >= m_pointCount) - // TODO exception - throw new IndexOutOfBoundsException(); - - _verifyAllStreams(); - // AttributeStreamOfDbl v = (AttributeStreamOfDbl) - // m_vertexAttributes[0]; - AttributeStreamOfDbl v = (AttributeStreamOfDbl) m_vertexAttributes[0]; - v.write(index * 2, pt); - notifyModified(DirtyFlags.DirtyCoordinates); - } - - // Checked vs. Jan 11, 2011 - public void setXY(int index, double x, double y) { - if (index < 0 || index >= m_pointCount) - // TODO exc - throw new IndexOutOfBoundsException(); - - _verifyAllStreams(); - // AttributeStreamOfDbl v = (AttributeStreamOfDbl) - // m_vertexAttributes[0]; - // TODO ask sergey about casts - AttributeStreamOfDbl v = (AttributeStreamOfDbl) m_vertexAttributes[0]; - v.write(index * 2, x); - v.write(index * 2 + 1, y); - notifyModified(DirtyFlags.DirtyCoordinates); - } - - // Checked vs. Jan 11, 2011 - @Override - public Point3D getXYZ(int index) { - if (index < 0 || index >= getPointCount()) - throw new IndexOutOfBoundsException(); - - _verifyAllStreams(); - AttributeStreamOfDbl v = (AttributeStreamOfDbl) m_vertexAttributes[0]; - Point3D pt = new Point3D(); - pt.x = v.read(index * 2); - pt.y = v.read(index * 2 + 1); - - // TODO check excluded if statement componenet - if (hasAttribute(Semantics.Z))// && (m_vertexAttributes[1] != null)) - pt.z = m_vertexAttributes[1].readAsDbl(index); - else - pt.z = VertexDescription.getDefaultValue(Semantics.Z); - - return pt; - } - - // Checked vs. Jan 11, 2011 - @Override - public void setXYZ(int index, Point3D pt) { - if (index < 0 || index >= getPointCount()) - throw new IndexOutOfBoundsException(); - - addAttribute(Semantics.Z); - - _verifyAllStreams(); - notifyModified(DirtyFlags.DirtyCoordinates); - AttributeStreamOfDbl v = (AttributeStreamOfDbl) m_vertexAttributes[0]; - v.write(index * 2, pt.x); - v.write(index * 2 + 1, pt.y); - m_vertexAttributes[1].writeAsDbl(index, pt.z); - } - - // Checked vs. Jan 11, 2011 - @Override - public double getAttributeAsDbl(int semantics, int offset, int ordinate) { - if (offset < 0 || offset >= m_pointCount) - throw new IndexOutOfBoundsException(); - - int ncomps = VertexDescription.getComponentCount(semantics); - if (ordinate >= ncomps) - throw new IndexOutOfBoundsException(); - - _verifyAllStreams(); - int attributeIndex = m_description.getAttributeIndex(semantics); - // TODO check if statement - if (attributeIndex >= 0)// && m_vertexAttributes[attributeIndex] != - // null) { - { - return m_vertexAttributes[attributeIndex].readAsDbl(offset * ncomps - + ordinate); - } - return VertexDescription.getDefaultValue(semantics); - } - - // Checked vs. Jan 11, 2011 - @Override - public int getAttributeAsInt(int semantics, int offset, int ordinate) { - return (int) getAttributeAsDbl(semantics, offset, ordinate); - } - - // Checked vs. Jan 11, 2011 - @Override - public void setAttribute(int semantics, int offset, int ordinate, - double value) { - if (offset < 0 || offset >= m_pointCount) - throw new IndexOutOfBoundsException(); - - int ncomps = VertexDescription.getComponentCount(semantics); - if (ordinate >= ncomps) - throw new IndexOutOfBoundsException(); - - addAttribute(semantics); - _verifyAllStreams(); - int attributeIndex = m_description.getAttributeIndex(semantics); - notifyModified(DirtyFlags.DirtyCoordinates); - m_vertexAttributes[attributeIndex].writeAsDbl(offset * ncomps - + ordinate, value); - } - - // Checked vs. Jan 11, 2011 - @Override - public void setAttribute(int semantics, int offset, int ordinate, int value) { - setAttribute(semantics, offset, ordinate, (double) value); - } - - public AttributeStreamBase getAttributeStreamRef(int semantics) { - throwIfEmpty(); - - addAttribute(semantics); - _verifyAllStreams(); - - int attributeIndex = m_description.getAttributeIndex(semantics); - return m_vertexAttributes[attributeIndex]; - } - - /** - * Sets a reference to the given AttributeStream of the Geometry. Once the - * buffer has been obtained, the vertices of the Geometry can be manipulated - * directly. The AttributeStream parameters are not checked for the size.
- * If the attribute is missing, it will be added.
- * Note, that this method does not change the vertex count in the Geometry.
- * The stream can have more elements, than the Geometry point count, but - * only necessary part will be saved when exporting to a ESRI shape or other - * format. @param semantics Semantics of the attribute to assign the stream - * to. @param stream The input AttributeStream that will be assigned by - * reference. If one changes the stream later through the reference, one has - * to call NotifyStreamChanged. \exception Throws invalid_argument exception - * if the input stream type does not match that of the semantics - * persistence. - */ - public void setAttributeStreamRef(int semantics, AttributeStreamBase stream) { - // int test1 = VertexDescription.getPersistence(semantics); - // int test2 = stream.getPersistence(); - - if ((stream != null) - && VertexDescription.getPersistence(semantics) != stream - .getPersistence())// input stream has wrong persistence - throw new IllegalArgumentException(); - - // Do not check for the stream size here to allow several streams to be - // attached before the point count is changed. - addAttribute(semantics); - int attributeIndex = m_description.getAttributeIndex(semantics); - if (m_vertexAttributes == null) - m_vertexAttributes = new AttributeStreamBase[m_description - .getAttributeCount()]; - - m_vertexAttributes[attributeIndex] = stream; - notifyModified(DirtyFlags.DirtyAll); - } - - @Override - protected void _assignVertexDescriptionImpl(VertexDescription newDescription) { - AttributeStreamBase[] newAttributes = null; - - if (m_vertexAttributes != null) { - int[] mapping = VertexDescriptionDesignerImpl.mapAttributes( - newDescription, m_description); - - newAttributes = new AttributeStreamBase[newDescription - .getAttributeCount()]; - - for (int i = 0, n = newDescription.getAttributeCount(); i < n; i++) { - if (mapping[i] != -1) { - int m = mapping[i]; - newAttributes[i] = m_vertexAttributes[m]; - } - - } - } else { - //if there are no streams we do not create them - } - - m_description = newDescription; - m_vertexAttributes = newAttributes; // late assignment to try to stay - m_reservedPointCount = -1;// we need to recreate the new attribute then - notifyModified(DirtyFlags.DirtyAll); - } - - // Checked vs. Jan 11, 2011 - protected void _updateEnvelope(Envelope2D env) { - _updateAllDirtyIntervals(true); - m_envelope.queryEnvelope2D(env); - } // note: overload for polylines/polygons with curves - - // Checked vs. Jan 11, 2011 - protected void _updateEnvelope(Envelope3D env) { - _updateAllDirtyIntervals(true); - m_envelope.queryEnvelope3D(env); - } // note: overload for polylines/polygons with curves - - // Checked vs. Jan 11, 2011 - protected void _updateLooseEnvelope(Envelope2D env) { - // TODO ROHIT has this set to true? - _updateAllDirtyIntervals(false); - m_envelope.queryEnvelope2D(env); - } // note: overload for polylines/polygons with curves - - // Checked vs. Jan 11, 2011 - - /** - * \internal Calculates loose envelope. Returns True if the calculation - * renders exact envelope. - */ - protected void _updateLooseEnvelope(Envelope3D env) { - // TODO ROHIT has this set to true? - _updateAllDirtyIntervals(false); - m_envelope.queryEnvelope3D(env); - } // note: overload for polylines/polygons with curves - - // Checked vs. Jan 11, 2011 - @Override - public void queryEnvelope(Envelope env) { - _updateAllDirtyIntervals(true); - m_envelope.copyTo(env); - } - - // TODO rename to remove 2D - // Checked vs. Jan 11, 2011 - @Override - public void queryEnvelope2D(Envelope2D env) { - _updateEnvelope(env); - } - - // Checked vs. Jan 11, 2011 - // TODO rename to remove 3D - @Override - public void queryEnvelope3D(Envelope3D env) { - _updateEnvelope(env); - } - - // Checked vs. Jan 11, 2011 - // TODO rename to remove 2D - @Override - public void queryLooseEnvelope2D(Envelope2D env) { - _updateLooseEnvelope(env); - } - - // Checked vs. Jan 11, 2011 - // TODO rename to remove 3D - @Override - public void queryLooseEnvelope3D(Envelope3D env) { - _updateLooseEnvelope(env); - } - - // Checked vs. Jan 11, 2011 - @Override - public Envelope1D queryInterval(int semantics, int ordinate) { - Envelope1D env = new Envelope1D(); - if (isEmptyImpl()) { - env.setEmpty(); - return env; - } - - _updateAllDirtyIntervals(true); - return m_envelope.queryInterval(semantics, ordinate); - } - - // Checked vs. Jan 11, 2011 - // TODO Rename to getHashCode - @Override - public int hashCode() { - int hashCode = m_description.hashCode(); - - if (!isEmptyImpl()) { - int pointCount = getPointCount(); - for (int i = 0, n = m_description.getAttributeCount(); i < n; i++) { - int components = VertexDescription - .getComponentCount(m_description._getSemanticsImpl(i)); - AttributeStreamBase stream = m_vertexAttributes[i]; - hashCode = stream.calculateHashImpl(hashCode, 0, pointCount - * components); - } - } - - return hashCode; - } - - // Checked vs. Jan 11, 2011 - @Override - public boolean equals(Object other) { - // Java checks - if (other == this) - return true; - - if (!(other instanceof MultiVertexGeometryImpl)) - return false; - - MultiVertexGeometryImpl otherMulti = (MultiVertexGeometryImpl) other; - - if (!(m_description.equals(otherMulti.m_description))) - return false; - - if (isEmptyImpl() != otherMulti.isEmptyImpl()) - return false; - - if (isEmptyImpl()) - return true; // both geometries are empty - - int pointCount = getPointCount(); - int pointCountOther = otherMulti.getPointCount(); - - if (pointCount != pointCountOther) - return false; - - for (int i = 0; i < m_description.getAttributeCount(); i++) { - int semantics = m_description.getSemantics(i); - - AttributeStreamBase stream = getAttributeStreamRef(semantics); - AttributeStreamBase streamOther = otherMulti - .getAttributeStreamRef(semantics); - - int components = VertexDescription.getComponentCount(semantics); - - if (!stream.equals(streamOther, 0, pointCount * components)) - return false; - } - - return true; - } - - // Checked vs. Jan 11, 2011 - - /** - * Sets the envelope of the Geometry. The Envelope description must match - * that of the Geometry. - */ - public void setEnvelope(Envelope env) { - if (!m_description.equals(env.getDescription())) - throw new IllegalArgumentException(); - - // m_envelope = (Envelope) env.clone(); - m_envelope = (Envelope) env.createInstance(); - env.copyTo(m_envelope); - _setDirtyFlag(DirtyFlags.DirtyIntervals, false); - } - - @Override - public void copyTo(Geometry dstGeom) { - MultiVertexGeometryImpl dst = (MultiVertexGeometryImpl) dstGeom; - if (dst.getType() != getType()) - throw new IllegalArgumentException(); - - _copyToUnsafe(dst); - } - - //Does not check geometry type. Used to copy Polygon to Polyline - void _copyToUnsafe(MultiVertexGeometryImpl dst) { - _verifyAllStreams(); - dst.m_description = m_description; - dst.m_vertexAttributes = null; - int nattrib = m_description.getAttributeCount(); - AttributeStreamBase[] cloneAttributes = null; - if (m_vertexAttributes != null) { - cloneAttributes = new AttributeStreamBase[nattrib]; - for (int i = 0; i < nattrib; i++) { - if (m_vertexAttributes[i] != null) { - int ncomps = VertexDescription.getComponentCount(m_description._getSemanticsImpl(i)); - cloneAttributes[i] = m_vertexAttributes[i].restrictedClone(getPointCount() * ncomps); - } - } - } - - if (m_envelope != null) { - dst.m_envelope = (Envelope) m_envelope.createInstance(); - m_envelope.copyTo(dst.m_envelope); - // dst.m_envelope = (Envelope) m_envelope.clone(); - } else - dst.m_envelope = null; - - dst.m_pointCount = m_pointCount; - dst.m_flagsMask = m_flagsMask; - dst.m_vertexAttributes = cloneAttributes; - - try { - _copyToImpl(dst); // copy child props - } catch (Exception ex) { - dst.setEmpty(); - throw new RuntimeException(ex); - } - } - - // Checked vs. Jan 11, 2011 - public boolean _attributeStreamIsAllocated(int semantics) { - throwIfEmpty(); - - int attributeIndex = m_description.getAttributeIndex(semantics); - - if (attributeIndex >= 0 && m_vertexAttributes[attributeIndex] != null) - return true; - - return false; - } - - // Checked vs. Jan 11, 2011 - void _setEmptyImpl() { - m_pointCount = 0; - m_reservedPointCount = -1; - m_vertexAttributes = null;// release it all streams. - notifyModified(DirtyFlags.DirtyAll); - } - - // Checked vs. Jan 11, 2011 - - /** - * Notifies the Geometry of changes made to the vertices so that it could - * reset cached structures. - */ - public void notifyModified(int flags) { - if (flags == DirtyFlags.DirtyAll) { - m_reservedPointCount = -1;// forget the reserved point number - _notifyModifiedAllImpl(); - } - m_flagsMask |= flags; - - _clearAccelerators(); - _touch(); - } - - // Checked vs. Jan 11, 2011 - - /** - * @param bExact True, when the exact envelope need to be calculated and false - * for the loose one. - */ - protected void _updateAllDirtyIntervals(boolean bExact) { - _verifyAllStreams(); - if (_hasDirtyFlag(DirtyFlags.DirtyIntervals)) { - if (null == m_envelope) - m_envelope = new Envelope(m_description); - else - m_envelope.assignVertexDescription(m_description); - - if (isEmpty()) { - m_envelope.setEmpty(); - return; - } - - _updateXYImpl(bExact);// efficient method for xy's - // now go through other attribues. - for (int attributeIndex = 1; attributeIndex < m_description - .getAttributeCount(); attributeIndex++) { - int semantics = m_description._getSemanticsImpl(attributeIndex); - int ncomps = VertexDescription.getComponentCount(semantics); - AttributeStreamBase stream = m_vertexAttributes[attributeIndex]; - for (int iord = 0; iord < ncomps; iord++) { - Envelope1D interval = new Envelope1D(); - interval.setEmpty(); - for (int i = 0; i < m_pointCount; i++) { - double value = stream.readAsDbl(i * ncomps + iord);// some - // optimization - // is - // possible - // if - // non-virtual - // method - // is - // used - interval.merge(value); - } - m_envelope.setInterval(semantics, iord, interval); - } - } - if (bExact) - _setDirtyFlag(DirtyFlags.DirtyIntervals, false); - } - } - - // Checked vs. Jan 11, 2011 - - /** - * \internal Updates x, y intervals. - */ - public void _updateXYImpl(boolean bExact) { - m_envelope.setEmpty(); - AttributeStreamOfDbl stream = (AttributeStreamOfDbl) m_vertexAttributes[0]; - Point2D pt = new Point2D(); - for (int i = 0; i < m_pointCount; i++) { - stream.read(2 * i, pt); - m_envelope.merge(pt); - } - } - - void calculateEnvelope2D(Envelope2D env, boolean bExact) { - env.setEmpty(); - AttributeStreamOfDbl stream = (AttributeStreamOfDbl) m_vertexAttributes[0]; - Point2D pt = new Point2D(); - for (int i = 0; i < m_pointCount; i++) { - stream.read(2 * i, pt); - env.merge(pt); - } - } - - // Checked vs. Jan 11, 2011 lots of changes - - /** - * \internal Verifies all streams (calls _VerifyStream for every attribute). - */ - protected void _verifyAllStreamsImpl() { - // This method checks that the streams are of correct size. - // It resizes the streams to ensure they are not shorter than - // m_PointCount - // _ASSERT(_HasDirtyFlag(enum_value1(DirtyFlags, - // DirtyVerifiedStreams))); - if (m_reservedPointCount < m_pointCount) // an optimization to skip this - // expensive loop when - // adding point by point - { - if (m_vertexAttributes == null) - m_vertexAttributes = new AttributeStreamBase[m_description - .getAttributeCount()]; - - m_reservedPointCount = NumberUtils.intMax(); - for (int attributeIndex = 0; attributeIndex < m_description - .getAttributeCount(); attributeIndex++) { - int semantics = m_description._getSemanticsImpl(attributeIndex); - if (m_vertexAttributes[attributeIndex] != null) { - int ncomp = VertexDescription.getComponentCount(semantics); - int size = m_vertexAttributes[attributeIndex].virtualSize() - / ncomp; - if (size < m_pointCount) { - size = (m_reservedPointCount > m_pointCount + 5) ? (m_pointCount * 5 + 3) / 4 - : m_pointCount;// reserve 25% more than user - // asks - m_vertexAttributes[attributeIndex].resize(size * ncomp, - VertexDescription.getDefaultValue(semantics)); - } - - if (size < m_reservedPointCount) - m_reservedPointCount = size; - } else { - m_vertexAttributes[attributeIndex] = AttributeStreamBase - .createAttributeStreamWithSemantics(semantics, - m_pointCount); - m_reservedPointCount = m_pointCount; - } - } - } - _verifyStreamsImpl(); - - _setDirtyFlag(DirtyFlags.DirtyVerifiedStreams, false); - } - - // Checked vs. Jan 11, 2011 - void _resizeImpl(int pointCount) { - if (pointCount < 0) - throw new IllegalArgumentException(); - - if (pointCount == m_pointCount) - return; - - m_pointCount = pointCount; - notifyModified(DirtyFlags.DirtyAllInternal); - } - - // Checked vs. Jan 11, 2011 - int queryCoordinates(Point2D[] dst, int dstSize, int beginIndex, - int endIndex) { - int endIndexC = endIndex < 0 ? m_pointCount : endIndex; - endIndexC = Math.min(endIndexC, beginIndex + dstSize); - - if (beginIndex < 0 || beginIndex >= m_pointCount - || endIndexC < beginIndex) - throw new IllegalArgumentException(); - - AttributeStreamOfDbl xy = (AttributeStreamOfDbl) getAttributeStreamRef(VertexDescription.Semantics.POSITION); - int j = 0; - double[] dstArray = new double[dst.length * 2]; - xy.readRange(2 * beginIndex, (endIndexC - beginIndex) * 2, dstArray, j, true); - - for (int i = 0; i < dst.length; i++) { - dst[i] = new Point2D(dstArray[i * 2], dstArray[i * 2 + 1]); - } - - // for (int i = beginIndex; i < endIndexC; i++, j++) - // { - // xy.read(2 * i, dst[j]); - // } - - return endIndexC; - } - - // Checked vs. Jan 11, 2011 - int QueryCoordinates(Point3D[] dst, int dstSize, int beginIndex, - int endIndex) { - int endIndexC = endIndex < 0 ? m_pointCount : endIndex; - endIndexC = Math.min(endIndexC, beginIndex + dstSize); - - if (beginIndex < 0 || beginIndex >= m_pointCount - || endIndexC < beginIndex) - // TODO replace geometry exc - throw new IllegalArgumentException(); - - AttributeStreamOfDbl xy = (AttributeStreamOfDbl) getAttributeStreamRef(VertexDescription.Semantics.POSITION); - AttributeStreamOfDbl z = null; - double v = VertexDescription - .getDefaultValue(VertexDescription.Semantics.Z); - boolean bHasZ = hasAttribute(VertexDescription.Semantics.Z); - if (bHasZ) - z = (AttributeStreamOfDbl) getAttributeStreamRef(VertexDescription.Semantics.Z); - int j = 0; - for (int i = beginIndex; i < endIndexC; i++, j++) { - dst[j].x = xy.read(2 * i); - dst[j].y = xy.read(2 * i + 1); - dst[j].z = bHasZ ? z.read(i) : v; - - dst[j] = getXYZ(i); - } - - return endIndexC; - } - - // Checked vs. Jan 11, 2011 - // -1 : DirtySimple is true (whether or not the MultiPath is Simple is - // unknown) - // 0 : DirtySimple is false and the MultiPath is not Weak Simple - // 1 : DirtySimple is false and the MultiPath is Weak Simple but not ring - // ordering may be invalid - // 2 : DirtySimple is false and the MultiPath is Strong Simple (Weak Simple - // and valid ring ordering) - public int getIsSimple(double tolerance) { - if (!_hasDirtyFlag(DirtyFlags.DirtyIsKnownSimple)) { - if (!_hasDirtyFlag(DirtyFlags.IsWeakSimple)) { - return 0; - } - if (m_simpleTolerance >= tolerance) { - if (!_hasDirtyFlag(DirtyFlags.DirtyOGCFlags)) - return 2; - - return 1; - } - - return -1; - } - return -1; - } - - void setIsSimple(int isSimpleRes, double tolerance, boolean ogc_known) { - m_simpleTolerance = tolerance; - if (isSimpleRes == GeometryXSimple.Unknown) { - _setDirtyFlag(DirtyFlags.DirtyIsKnownSimple, true); - _setDirtyFlag(DirtyFlags.DirtyOGCFlags, true); - return; - } - _setDirtyFlag(DirtyFlags.DirtyIsKnownSimple, false); - - if (!ogc_known) - _setDirtyFlag(DirtyFlags.DirtyOGCFlags, true); - - if (isSimpleRes == GeometryXSimple.Not) { - _setDirtyFlag(DirtyFlags.IsWeakSimple, false); - _setDirtyFlag(DirtyFlags.IsStrongSimple, false); - } else if (isSimpleRes == GeometryXSimple.Weak) { - _setDirtyFlag(DirtyFlags.IsWeakSimple, true); - _setDirtyFlag(DirtyFlags.IsStrongSimple, false); - } else if (isSimpleRes == GeometryXSimple.Strong) { - _setDirtyFlag(DirtyFlags.IsWeakSimple, true); - _setDirtyFlag(DirtyFlags.IsStrongSimple, true); - } else - throw GeometryException.GeometryInternalError();// what? - } - - double _getSimpleTolerance() { - return m_simpleTolerance; - } - - public GeometryAccelerators _getAccelerators() { - return m_accelerators; - } - - void _clearAccelerators() { - if (m_accelerators != null) - m_accelerators = null; - } - - void _interpolateTwoVertices(int vertex1, int vertex2, double f, - Point outPoint) { - if (vertex1 < 0 || vertex1 >= m_pointCount) - throw new GeometryException("index out of bounds."); - if (vertex2 < 0 || vertex2 >= m_pointCount) - throw new GeometryException("index out of bounds."); - - // _ASSERT(!IsEmpty()); - // _ASSERT(m_vertexAttributes != NULLPTR); - - _verifyAllStreams(); - - outPoint.assignVertexDescription(m_description); - if (outPoint.isEmpty()) - outPoint._setToDefault(); - - for (int attributeIndex = 0; attributeIndex < m_description - .getAttributeCount(); attributeIndex++) { - int semantics = m_description._getSemanticsImpl(attributeIndex); - for (int icomp = 0, ncomp = VertexDescription - .getComponentCount(semantics); icomp < ncomp; icomp++) { - double v1 = m_vertexAttributes[attributeIndex].readAsDbl(ncomp - * vertex1 + icomp); - double v2 = m_vertexAttributes[attributeIndex].readAsDbl(ncomp - * vertex2 + icomp); - outPoint.setAttribute(semantics, icomp, MathUtils.lerp(v1, v2, f)); - } - } - } - - double _getShortestDistance(int vertex1, int vertex2) { - Point2D pt = getXY(vertex1); - pt.sub(getXY(vertex2)); - return pt.length(); - } - - // ////////////////// METHODS To REMOVE /////////////////////// - @Override - public Point getPoint(int index) { - if (index < 0 || index >= m_pointCount) - throw new IndexOutOfBoundsException(); - - _verifyAllStreams(); - - Point outPoint = new Point(); - outPoint.assignVertexDescription(m_description); - if (outPoint.isEmpty()) - outPoint._setToDefault(); - - for (int attributeIndex = 0; attributeIndex < m_description - .getAttributeCount(); attributeIndex++) { - int semantics = m_description.getSemantics(attributeIndex); - for (int icomp = 0, ncomp = VertexDescription - .getComponentCount(semantics); icomp < ncomp; icomp++) { - double v = m_vertexAttributes[attributeIndex].readAsDbl(ncomp - * index + icomp); - outPoint.setAttribute(semantics, icomp, v); - } - } - return outPoint; - } - - @Override - public void setPoint(int index, Point src) { - if (index < 0 || index >= m_pointCount) - throw new IndexOutOfBoundsException(); - - Point point = src; - - if (src.isEmpty())// can not assign an empty point to a multipoint - // vertex - throw new IllegalArgumentException(); - - _verifyAllStreams();// verify all allocated streams are of necessary - // size. - VertexDescription vdin = point.getDescription(); - for (int attributeIndex = 0; attributeIndex < vdin.getAttributeCount(); attributeIndex++) { - int semantics = vdin.getSemantics(attributeIndex); - int ncomp = VertexDescription.getComponentCount(semantics); - for (int icomp = 0; icomp < ncomp; icomp++) { - double v = point.getAttributeAsDbl(semantics, icomp); - setAttribute(semantics, index, icomp, v); - } - } - } - - @Override - public void queryCoordinates(Point[] dst) { - int sz = m_pointCount; - if (dst.length < sz) - throw new IllegalArgumentException(); - - // TODO: refactor to a better AttributeAray call (ReadRange?) - for (int i = 0; i < sz; i++) { - dst[i] = getPoint(i); - } - } - - @Override - public void queryCoordinates(Point2D[] dst) { - int sz = m_pointCount; - if (dst.length < sz) - throw new IllegalArgumentException(); - - // TODO: refactor to a better AttributeAray call (ReadRange?) - for (int i = 0; i < sz; i++) { - dst[i] = getXY(i); - } - } - - @Override - public void queryCoordinates(Point3D[] dst) { - int sz = m_pointCount; - if (dst.length < sz) - throw new IllegalArgumentException(); - - // TODO: refactor to a better AttributeAray call (ReadRange?) - for (int i = 0; i < sz; i++) { - dst[i] = getXYZ(i); - } - } - - @Override - public void replaceNaNs(int semantics, double value) { - addAttribute(semantics); - if (isEmpty()) - return; - - boolean modified = false; - int ncomps = VertexDescription.getComponentCount(semantics); - for (int i = 0; i < ncomps; i++) { - AttributeStreamBase streamBase = getAttributeStreamRef(semantics); - if (streamBase instanceof AttributeStreamOfDbl) { - AttributeStreamOfDbl dblStream = (AttributeStreamOfDbl) streamBase; - for (int ivert = 0, n = m_pointCount * ncomps; ivert < n; ivert++) { - double v = dblStream.read(ivert); - if (Double.isNaN(v)) { - dblStream.write(ivert, value); - modified = true; - } - } - } else { - for (int ivert = 0, n = m_pointCount * ncomps; ivert < n; ivert++) { - double v = streamBase.readAsDbl(ivert); - if (Double.isNaN(v)) { - streamBase.writeAsDbl(ivert, value); - modified = true; - } - } - } - } - - if (modified) { - notifyModified(DirtyFlags.DirtyCoordinates); - } - } - - public abstract boolean _buildRasterizedGeometryAccelerator( - double toleranceXY, GeometryAccelerationDegree accelDegree); - - public abstract boolean _buildQuadTreeAccelerator( - GeometryAccelerationDegree d); - - @Override - public String toString() { - return "MultiVertexGeometryImpl"; - } + // HEADER DEFINED + public interface GeometryXSimple { + final int Unknown = -1; // not know if simple or not + final int Not = 0; // not simple + final int Weak = 1; // weak simple (no self intersections, ring + // orientation is correct, but ring order is not) + final int Strong = 2; // same as weak simple + OGC ring order. + } + + // TODO Remove? + + /** + * \internal CHildren implement this method to copy additional information + */ + abstract void _copyToImpl(MultiVertexGeometryImpl mvg); + + protected abstract void _notifyModifiedAllImpl(); + + /** + * \internal Called inside of the VerifyAllStreams to get a child class a + * chance to do additional verify. + */ + protected abstract void _verifyStreamsImpl(); + + public interface DirtyFlags { + public static final int DirtyIsKnownSimple = 1; // !<0 when IsWeakSimple + // flag is valid + public static final int IsWeakSimple = 2; // != m_pointCount) + // TODO + throw new GeometryException("index out of bounds"); + + // _ASSERT(!IsEmpty()); + // _ASSERT(m_vertexAttributes != null); + + _verifyAllStreams(); + + Point outPoint = dst; + outPoint.assignVertexDescription(m_description); + if (outPoint.isEmpty()) + outPoint._setToDefault(); + + for (int attributeIndex = 0; attributeIndex < m_description + .getAttributeCount(); attributeIndex++) { + // fix semantics + int semantics = m_description._getSemanticsImpl(attributeIndex); + + // VertexDescription.getComponentCount(semantics); + for (int icomp = 0, ncomp = VertexDescription + .getComponentCount(semantics); icomp < ncomp; icomp++) { + double v = m_vertexAttributes[attributeIndex].readAsDbl(ncomp + * index + icomp); + outPoint.setAttribute(semantics, icomp, v); + } + } + } + + @Override + public void setPointByVal(int index, Point src) { + if (index < 0 || index >= m_pointCount) + throw new GeometryException("index out of bounds"); + + Point point = src; + + if (src.isEmpty())// can not assign an empty point to a multipoint + // vertex + throw new IllegalArgumentException(); + + _verifyAllStreams();// verify all allocated streams are of necessary + // size. + VertexDescription vdin = point.getDescription(); + for (int attributeIndex = 0; attributeIndex < vdin.getAttributeCount(); attributeIndex++) { + int semantics = vdin._getSemanticsImpl(attributeIndex); + int ncomp = VertexDescription.getComponentCount(semantics); + for (int icomp = 0; icomp < ncomp; icomp++) { + double v = point.getAttributeAsDbl(semantics, icomp); + setAttribute(semantics, index, icomp, v); + } + } + } + + // Checked vs. Jan 11, 2011 + @Override + public Point2D getXY(int index) { + Point2D pt = new Point2D(); + getXY(index, pt); + return pt; + } + + @Override + public void getXY(int index, Point2D pt) { + if (index < 0 || index >= getPointCount()) + throw new IndexOutOfBoundsException(); + + _verifyAllStreams(); + // AttributeStreamOfDbl v = (AttributeStreamOfDbl) + // m_vertexAttributes[0]; + AttributeStreamOfDbl v = (AttributeStreamOfDbl) m_vertexAttributes[0]; + v.read(index * 2, pt); + } + + // Checked vs. Jan 11, 2011 + @Override + public void setXY(int index, Point2D pt) { + if (index < 0 || index >= m_pointCount) + // TODO exception + throw new IndexOutOfBoundsException(); + + _verifyAllStreams(); + // AttributeStreamOfDbl v = (AttributeStreamOfDbl) + // m_vertexAttributes[0]; + AttributeStreamOfDbl v = (AttributeStreamOfDbl) m_vertexAttributes[0]; + v.write(index * 2, pt); + notifyModified(DirtyFlags.DirtyCoordinates); + } + + // Checked vs. Jan 11, 2011 + public void setXY(int index, double x, double y) { + if (index < 0 || index >= m_pointCount) + // TODO exc + throw new IndexOutOfBoundsException(); + + _verifyAllStreams(); + // AttributeStreamOfDbl v = (AttributeStreamOfDbl) + // m_vertexAttributes[0]; + // TODO ask sergey about casts + AttributeStreamOfDbl v = (AttributeStreamOfDbl) m_vertexAttributes[0]; + v.write(index * 2, x); + v.write(index * 2 + 1, y); + notifyModified(DirtyFlags.DirtyCoordinates); + } + + // Checked vs. Jan 11, 2011 + @Override + public Point3D getXYZ(int index) { + if (index < 0 || index >= getPointCount()) + throw new IndexOutOfBoundsException(); + + _verifyAllStreams(); + AttributeStreamOfDbl v = (AttributeStreamOfDbl) m_vertexAttributes[0]; + Point3D pt = new Point3D(); + pt.x = v.read(index * 2); + pt.y = v.read(index * 2 + 1); + + // TODO check excluded if statement componenet + if (hasAttribute(Semantics.Z))// && (m_vertexAttributes[1] != null)) + pt.z = m_vertexAttributes[1].readAsDbl(index); + else + pt.z = VertexDescription.getDefaultValue(Semantics.Z); + + return pt; + } + + // Checked vs. Jan 11, 2011 + @Override + public void setXYZ(int index, Point3D pt) { + if (index < 0 || index >= getPointCount()) + throw new IndexOutOfBoundsException(); + + addAttribute(Semantics.Z); + + _verifyAllStreams(); + notifyModified(DirtyFlags.DirtyCoordinates); + AttributeStreamOfDbl v = (AttributeStreamOfDbl) m_vertexAttributes[0]; + v.write(index * 2, pt.x); + v.write(index * 2 + 1, pt.y); + m_vertexAttributes[1].writeAsDbl(index, pt.z); + } + + // Checked vs. Jan 11, 2011 + @Override + public double getAttributeAsDbl(int semantics, int offset, int ordinate) { + if (offset < 0 || offset >= m_pointCount) + throw new IndexOutOfBoundsException(); + + int ncomps = VertexDescription.getComponentCount(semantics); + if (ordinate >= ncomps) + throw new IndexOutOfBoundsException(); + + _verifyAllStreams(); + int attributeIndex = m_description.getAttributeIndex(semantics); + // TODO check if statement + if (attributeIndex >= 0)// && m_vertexAttributes[attributeIndex] != + // null) { + { + return m_vertexAttributes[attributeIndex].readAsDbl(offset * ncomps + + ordinate); + } + return VertexDescription.getDefaultValue(semantics); + } + + // Checked vs. Jan 11, 2011 + @Override + public int getAttributeAsInt(int semantics, int offset, int ordinate) { + return (int) getAttributeAsDbl(semantics, offset, ordinate); + } + + // Checked vs. Jan 11, 2011 + @Override + public void setAttribute(int semantics, int offset, int ordinate, + double value) { + if (offset < 0 || offset >= m_pointCount) + throw new IndexOutOfBoundsException(); + + int ncomps = VertexDescription.getComponentCount(semantics); + if (ordinate >= ncomps) + throw new IndexOutOfBoundsException(); + + addAttribute(semantics); + _verifyAllStreams(); + int attributeIndex = m_description.getAttributeIndex(semantics); + notifyModified(DirtyFlags.DirtyCoordinates); + m_vertexAttributes[attributeIndex].writeAsDbl(offset * ncomps + + ordinate, value); + } + + // Checked vs. Jan 11, 2011 + @Override + public void setAttribute(int semantics, int offset, int ordinate, int value) { + setAttribute(semantics, offset, ordinate, (double) value); + } + + public AttributeStreamBase getAttributeStreamRef(int semantics) { + throwIfEmpty(); + + addAttribute(semantics); + _verifyAllStreams(); + + int attributeIndex = m_description.getAttributeIndex(semantics); + return m_vertexAttributes[attributeIndex]; + } + + /** + * Sets a reference to the given AttributeStream of the Geometry. Once the + * buffer has been obtained, the vertices of the Geometry can be manipulated + * directly. The AttributeStream parameters are not checked for the size.
+ * If the attribute is missing, it will be added.
+ * Note, that this method does not change the vertex count in the Geometry.
+ * The stream can have more elements, than the Geometry point count, but + * only necessary part will be saved when exporting to a ESRI shape or other + * format. @param semantics Semantics of the attribute to assign the stream + * to. @param stream The input AttributeStream that will be assigned by + * reference. If one changes the stream later through the reference, one has + * to call NotifyStreamChanged. \exception Throws invalid_argument exception + * if the input stream type does not match that of the semantics + * persistence. + */ + public void setAttributeStreamRef(int semantics, AttributeStreamBase stream) { + // int test1 = VertexDescription.getPersistence(semantics); + // int test2 = stream.getPersistence(); + + if ((stream != null) + && VertexDescription.getPersistence(semantics) != stream + .getPersistence())// input stream has wrong persistence + throw new IllegalArgumentException(); + + // Do not check for the stream size here to allow several streams to be + // attached before the point count is changed. + addAttribute(semantics); + int attributeIndex = m_description.getAttributeIndex(semantics); + if (m_vertexAttributes == null) + m_vertexAttributes = new AttributeStreamBase[m_description + .getAttributeCount()]; + + m_vertexAttributes[attributeIndex] = stream; + notifyModified(DirtyFlags.DirtyAll); + } + + @Override + protected void _assignVertexDescriptionImpl(VertexDescription newDescription) { + AttributeStreamBase[] newAttributes = null; + + if (m_vertexAttributes != null) { + int[] mapping = VertexDescriptionDesignerImpl.mapAttributes( + newDescription, m_description); + + newAttributes = new AttributeStreamBase[newDescription + .getAttributeCount()]; + + for (int i = 0, n = newDescription.getAttributeCount(); i < n; i++) { + if (mapping[i] != -1) { + int m = mapping[i]; + newAttributes[i] = m_vertexAttributes[m]; + } + + } + } else { + //if there are no streams we do not create them + } + + m_description = newDescription; + m_vertexAttributes = newAttributes; // late assignment to try to stay + m_reservedPointCount = -1;// we need to recreate the new attribute then + notifyModified(DirtyFlags.DirtyAll); + } + + // Checked vs. Jan 11, 2011 + protected void _updateEnvelope(Envelope2D env) { + _updateAllDirtyIntervals(true); + m_envelope.queryEnvelope2D(env); + } // note: overload for polylines/polygons with curves + + // Checked vs. Jan 11, 2011 + protected void _updateEnvelope(Envelope3D env) { + _updateAllDirtyIntervals(true); + m_envelope.queryEnvelope3D(env); + } // note: overload for polylines/polygons with curves + + // Checked vs. Jan 11, 2011 + protected void _updateLooseEnvelope(Envelope2D env) { + // TODO ROHIT has this set to true? + _updateAllDirtyIntervals(false); + m_envelope.queryEnvelope2D(env); + } // note: overload for polylines/polygons with curves + + // Checked vs. Jan 11, 2011 + + /** + * \internal Calculates loose envelope. Returns True if the calculation + * renders exact envelope. + */ + protected void _updateLooseEnvelope(Envelope3D env) { + // TODO ROHIT has this set to true? + _updateAllDirtyIntervals(false); + m_envelope.queryEnvelope3D(env); + } // note: overload for polylines/polygons with curves + + // Checked vs. Jan 11, 2011 + @Override + public void queryEnvelope(Envelope env) { + _updateAllDirtyIntervals(true); + m_envelope.copyTo(env); + } + + // TODO rename to remove 2D + // Checked vs. Jan 11, 2011 + @Override + public void queryEnvelope2D(Envelope2D env) { + _updateEnvelope(env); + } + + // Checked vs. Jan 11, 2011 + // TODO rename to remove 3D + @Override + public void queryEnvelope3D(Envelope3D env) { + _updateEnvelope(env); + } + + // Checked vs. Jan 11, 2011 + // TODO rename to remove 2D + @Override + public void queryLooseEnvelope2D(Envelope2D env) { + _updateLooseEnvelope(env); + } + + // Checked vs. Jan 11, 2011 + // TODO rename to remove 3D + @Override + public void queryLooseEnvelope3D(Envelope3D env) { + _updateLooseEnvelope(env); + } + + // Checked vs. Jan 11, 2011 + @Override + public Envelope1D queryInterval(int semantics, int ordinate) { + Envelope1D env = new Envelope1D(); + if (isEmptyImpl()) { + env.setEmpty(); + return env; + } + + _updateAllDirtyIntervals(true); + return m_envelope.queryInterval(semantics, ordinate); + } + + // Checked vs. Jan 11, 2011 + // TODO Rename to getHashCode + @Override + public int hashCode() { + int hashCode = m_description.hashCode(); + + if (!isEmptyImpl()) { + int pointCount = getPointCount(); + for (int i = 0, n = m_description.getAttributeCount(); i < n; i++) { + int components = VertexDescription + .getComponentCount(m_description._getSemanticsImpl(i)); + AttributeStreamBase stream = m_vertexAttributes[i]; + hashCode = stream.calculateHashImpl(hashCode, 0, pointCount + * components); + } + } + + return hashCode; + } + + // Checked vs. Jan 11, 2011 + @Override + public boolean equals(Object other) { + // Java checks + if (other == this) + return true; + + if (!(other instanceof MultiVertexGeometryImpl)) + return false; + + MultiVertexGeometryImpl otherMulti = (MultiVertexGeometryImpl) other; + + if (!(m_description.equals(otherMulti.m_description))) + return false; + + if (isEmptyImpl() != otherMulti.isEmptyImpl()) + return false; + + if (isEmptyImpl()) + return true; // both geometries are empty + + int pointCount = getPointCount(); + int pointCountOther = otherMulti.getPointCount(); + + if (pointCount != pointCountOther) + return false; + + for (int i = 0; i < m_description.getAttributeCount(); i++) { + int semantics = m_description.getSemantics(i); + + AttributeStreamBase stream = getAttributeStreamRef(semantics); + AttributeStreamBase streamOther = otherMulti + .getAttributeStreamRef(semantics); + + int components = VertexDescription.getComponentCount(semantics); + + if (!stream.equals(streamOther, 0, pointCount * components)) + return false; + } + + return true; + } + + // Checked vs. Jan 11, 2011 + + /** + * Sets the envelope of the Geometry. The Envelope description must match + * that of the Geometry. + */ + public void setEnvelope(Envelope env) { + if (!m_description.equals(env.getDescription())) + throw new IllegalArgumentException(); + + // m_envelope = (Envelope) env.clone(); + m_envelope = (Envelope) env.createInstance(); + env.copyTo(m_envelope); + _setDirtyFlag(DirtyFlags.DirtyIntervals, false); + } + + @Override + public void copyTo(Geometry dstGeom) { + MultiVertexGeometryImpl dst = (MultiVertexGeometryImpl) dstGeom; + if (dst.getType() != getType()) + throw new IllegalArgumentException(); + + _copyToUnsafe(dst); + } + + //Does not check geometry type. Used to copy Polygon to Polyline + void _copyToUnsafe(MultiVertexGeometryImpl dst) { + _verifyAllStreams(); + dst.m_description = m_description; + dst.m_vertexAttributes = null; + int nattrib = m_description.getAttributeCount(); + AttributeStreamBase[] cloneAttributes = null; + if (m_vertexAttributes != null) { + cloneAttributes = new AttributeStreamBase[nattrib]; + for (int i = 0; i < nattrib; i++) { + if (m_vertexAttributes[i] != null) { + int ncomps = VertexDescription.getComponentCount(m_description._getSemanticsImpl(i)); + cloneAttributes[i] = m_vertexAttributes[i].restrictedClone(getPointCount() * ncomps); + } + } + } + + if (m_envelope != null) { + dst.m_envelope = (Envelope) m_envelope.createInstance(); + m_envelope.copyTo(dst.m_envelope); + // dst.m_envelope = (Envelope) m_envelope.clone(); + } else + dst.m_envelope = null; + + dst.m_pointCount = m_pointCount; + dst.m_flagsMask = m_flagsMask; + dst.m_vertexAttributes = cloneAttributes; + + try { + _copyToImpl(dst); // copy child props + } catch (Exception ex) { + dst.setEmpty(); + throw new RuntimeException(ex); + } + } + + // Checked vs. Jan 11, 2011 + public boolean _attributeStreamIsAllocated(int semantics) { + throwIfEmpty(); + + int attributeIndex = m_description.getAttributeIndex(semantics); + + if (attributeIndex >= 0 && m_vertexAttributes[attributeIndex] != null) + return true; + + return false; + } + + // Checked vs. Jan 11, 2011 + void _setEmptyImpl() { + m_pointCount = 0; + m_reservedPointCount = -1; + m_vertexAttributes = null;// release it all streams. + notifyModified(DirtyFlags.DirtyAll); + } + + // Checked vs. Jan 11, 2011 + + /** + * Notifies the Geometry of changes made to the vertices so that it could + * reset cached structures. + */ + public void notifyModified(int flags) { + if (flags == DirtyFlags.DirtyAll) { + m_reservedPointCount = -1;// forget the reserved point number + _notifyModifiedAllImpl(); + } + m_flagsMask |= flags; + + _clearAccelerators(); + _touch(); + } + + // Checked vs. Jan 11, 2011 + + /** + * @param bExact True, when the exact envelope need to be calculated and false + * for the loose one. + */ + protected void _updateAllDirtyIntervals(boolean bExact) { + _verifyAllStreams(); + if (_hasDirtyFlag(DirtyFlags.DirtyIntervals)) { + if (null == m_envelope) + m_envelope = new Envelope(m_description); + else + m_envelope.assignVertexDescription(m_description); + + if (isEmpty()) { + m_envelope.setEmpty(); + return; + } + + _updateXYImpl(bExact);// efficient method for xy's + // now go through other attribues. + for (int attributeIndex = 1; attributeIndex < m_description + .getAttributeCount(); attributeIndex++) { + int semantics = m_description._getSemanticsImpl(attributeIndex); + int ncomps = VertexDescription.getComponentCount(semantics); + AttributeStreamBase stream = m_vertexAttributes[attributeIndex]; + for (int iord = 0; iord < ncomps; iord++) { + Envelope1D interval = new Envelope1D(); + interval.setEmpty(); + for (int i = 0; i < m_pointCount; i++) { + double value = stream.readAsDbl(i * ncomps + iord);// some + // optimization + // is + // possible + // if + // non-virtual + // method + // is + // used + interval.merge(value); + } + m_envelope.setInterval(semantics, iord, interval); + } + } + if (bExact) + _setDirtyFlag(DirtyFlags.DirtyIntervals, false); + } + } + + // Checked vs. Jan 11, 2011 + + /** + * \internal Updates x, y intervals. + */ + public void _updateXYImpl(boolean bExact) { + m_envelope.setEmpty(); + AttributeStreamOfDbl stream = (AttributeStreamOfDbl) m_vertexAttributes[0]; + Point2D pt = new Point2D(); + for (int i = 0; i < m_pointCount; i++) { + stream.read(2 * i, pt); + m_envelope.merge(pt); + } + } + + void calculateEnvelope2D(Envelope2D env, boolean bExact) { + env.setEmpty(); + AttributeStreamOfDbl stream = (AttributeStreamOfDbl) m_vertexAttributes[0]; + Point2D pt = new Point2D(); + for (int i = 0; i < m_pointCount; i++) { + stream.read(2 * i, pt); + env.merge(pt); + } + } + + // Checked vs. Jan 11, 2011 lots of changes + + /** + * \internal Verifies all streams (calls _VerifyStream for every attribute). + */ + protected void _verifyAllStreamsImpl() { + // This method checks that the streams are of correct size. + // It resizes the streams to ensure they are not shorter than + // m_PointCount + // _ASSERT(_HasDirtyFlag(enum_value1(DirtyFlags, + // DirtyVerifiedStreams))); + if (m_reservedPointCount < m_pointCount) // an optimization to skip this + // expensive loop when + // adding point by point + { + if (m_vertexAttributes == null) + m_vertexAttributes = new AttributeStreamBase[m_description + .getAttributeCount()]; + + m_reservedPointCount = NumberUtils.intMax(); + for (int attributeIndex = 0; attributeIndex < m_description + .getAttributeCount(); attributeIndex++) { + int semantics = m_description._getSemanticsImpl(attributeIndex); + if (m_vertexAttributes[attributeIndex] != null) { + int ncomp = VertexDescription.getComponentCount(semantics); + int size = m_vertexAttributes[attributeIndex].virtualSize() + / ncomp; + if (size < m_pointCount) { + size = (m_reservedPointCount > m_pointCount + 5) ? (m_pointCount * 5 + 3) / 4 + : m_pointCount;// reserve 25% more than user + // asks + m_vertexAttributes[attributeIndex].resize(size * ncomp, + VertexDescription.getDefaultValue(semantics)); + } + + if (size < m_reservedPointCount) + m_reservedPointCount = size; + } else { + m_vertexAttributes[attributeIndex] = AttributeStreamBase + .createAttributeStreamWithSemantics(semantics, + m_pointCount); + m_reservedPointCount = m_pointCount; + } + } + } + _verifyStreamsImpl(); + + _setDirtyFlag(DirtyFlags.DirtyVerifiedStreams, false); + } + + // Checked vs. Jan 11, 2011 + void _resizeImpl(int pointCount) { + if (pointCount < 0) + throw new IllegalArgumentException(); + + if (pointCount == m_pointCount) + return; + + m_pointCount = pointCount; + notifyModified(DirtyFlags.DirtyAllInternal); + } + + // Checked vs. Jan 11, 2011 + int queryCoordinates(Point2D[] dst, int dstSize, int beginIndex, + int endIndex) { + int endIndexC = endIndex < 0 ? m_pointCount : endIndex; + endIndexC = Math.min(endIndexC, beginIndex + dstSize); + + if (beginIndex < 0 || beginIndex >= m_pointCount + || endIndexC < beginIndex) + throw new IllegalArgumentException(); + + AttributeStreamOfDbl xy = (AttributeStreamOfDbl) getAttributeStreamRef(VertexDescription.Semantics.POSITION); + int j = 0; + double[] dstArray = new double[dst.length * 2]; + xy.readRange(2 * beginIndex, (endIndexC - beginIndex) * 2, dstArray, j, true); + + for (int i = 0; i < dst.length; i++) { + dst[i] = new Point2D(dstArray[i * 2], dstArray[i * 2 + 1]); + } + + // for (int i = beginIndex; i < endIndexC; i++, j++) + // { + // xy.read(2 * i, dst[j]); + // } + + return endIndexC; + } + + // Checked vs. Jan 11, 2011 + int QueryCoordinates(Point3D[] dst, int dstSize, int beginIndex, + int endIndex) { + int endIndexC = endIndex < 0 ? m_pointCount : endIndex; + endIndexC = Math.min(endIndexC, beginIndex + dstSize); + + if (beginIndex < 0 || beginIndex >= m_pointCount + || endIndexC < beginIndex) + // TODO replace geometry exc + throw new IllegalArgumentException(); + + AttributeStreamOfDbl xy = (AttributeStreamOfDbl) getAttributeStreamRef(VertexDescription.Semantics.POSITION); + AttributeStreamOfDbl z = null; + double v = VertexDescription + .getDefaultValue(VertexDescription.Semantics.Z); + boolean bHasZ = hasAttribute(VertexDescription.Semantics.Z); + if (bHasZ) + z = (AttributeStreamOfDbl) getAttributeStreamRef(VertexDescription.Semantics.Z); + int j = 0; + for (int i = beginIndex; i < endIndexC; i++, j++) { + dst[j].x = xy.read(2 * i); + dst[j].y = xy.read(2 * i + 1); + dst[j].z = bHasZ ? z.read(i) : v; + + dst[j] = getXYZ(i); + } + + return endIndexC; + } + + // Checked vs. Jan 11, 2011 + // -1 : DirtySimple is true (whether or not the MultiPath is Simple is + // unknown) + // 0 : DirtySimple is false and the MultiPath is not Weak Simple + // 1 : DirtySimple is false and the MultiPath is Weak Simple but not ring + // ordering may be invalid + // 2 : DirtySimple is false and the MultiPath is Strong Simple (Weak Simple + // and valid ring ordering) + public int getIsSimple(double tolerance) { + if (!_hasDirtyFlag(DirtyFlags.DirtyIsKnownSimple)) { + if (!_hasDirtyFlag(DirtyFlags.IsWeakSimple)) { + return 0; + } + if (m_simpleTolerance >= tolerance) { + if (!_hasDirtyFlag(DirtyFlags.DirtyOGCFlags)) + return 2; + + return 1; + } + + return -1; + } + return -1; + } + + void setIsSimple(int isSimpleRes, double tolerance, boolean ogc_known) { + m_simpleTolerance = tolerance; + if (isSimpleRes == GeometryXSimple.Unknown) { + _setDirtyFlag(DirtyFlags.DirtyIsKnownSimple, true); + _setDirtyFlag(DirtyFlags.DirtyOGCFlags, true); + return; + } + _setDirtyFlag(DirtyFlags.DirtyIsKnownSimple, false); + + if (!ogc_known) + _setDirtyFlag(DirtyFlags.DirtyOGCFlags, true); + + if (isSimpleRes == GeometryXSimple.Not) { + _setDirtyFlag(DirtyFlags.IsWeakSimple, false); + _setDirtyFlag(DirtyFlags.IsStrongSimple, false); + } else if (isSimpleRes == GeometryXSimple.Weak) { + _setDirtyFlag(DirtyFlags.IsWeakSimple, true); + _setDirtyFlag(DirtyFlags.IsStrongSimple, false); + } else if (isSimpleRes == GeometryXSimple.Strong) { + _setDirtyFlag(DirtyFlags.IsWeakSimple, true); + _setDirtyFlag(DirtyFlags.IsStrongSimple, true); + } else + throw GeometryException.GeometryInternalError();// what? + } + + double _getSimpleTolerance() { + return m_simpleTolerance; + } + + public GeometryAccelerators _getAccelerators() { + return m_accelerators; + } + + void _clearAccelerators() { + if (m_accelerators != null) + m_accelerators = null; + } + + void _interpolateTwoVertices(int vertex1, int vertex2, double f, + Point outPoint) { + if (vertex1 < 0 || vertex1 >= m_pointCount) + throw new GeometryException("index out of bounds."); + if (vertex2 < 0 || vertex2 >= m_pointCount) + throw new GeometryException("index out of bounds."); + + // _ASSERT(!IsEmpty()); + // _ASSERT(m_vertexAttributes != NULLPTR); + + _verifyAllStreams(); + + outPoint.assignVertexDescription(m_description); + if (outPoint.isEmpty()) + outPoint._setToDefault(); + + for (int attributeIndex = 0; attributeIndex < m_description + .getAttributeCount(); attributeIndex++) { + int semantics = m_description._getSemanticsImpl(attributeIndex); + for (int icomp = 0, ncomp = VertexDescription + .getComponentCount(semantics); icomp < ncomp; icomp++) { + double v1 = m_vertexAttributes[attributeIndex].readAsDbl(ncomp + * vertex1 + icomp); + double v2 = m_vertexAttributes[attributeIndex].readAsDbl(ncomp + * vertex2 + icomp); + outPoint.setAttribute(semantics, icomp, MathUtils.lerp(v1, v2, f)); + } + } + } + + double _getShortestDistance(int vertex1, int vertex2) { + Point2D pt = getXY(vertex1); + pt.sub(getXY(vertex2)); + return pt.length(); + } + + // ////////////////// METHODS To REMOVE /////////////////////// + @Override + public Point getPoint(int index) { + if (index < 0 || index >= m_pointCount) + throw new IndexOutOfBoundsException(); + + _verifyAllStreams(); + + Point outPoint = new Point(); + outPoint.assignVertexDescription(m_description); + if (outPoint.isEmpty()) + outPoint._setToDefault(); + + for (int attributeIndex = 0; attributeIndex < m_description + .getAttributeCount(); attributeIndex++) { + int semantics = m_description.getSemantics(attributeIndex); + for (int icomp = 0, ncomp = VertexDescription + .getComponentCount(semantics); icomp < ncomp; icomp++) { + double v = m_vertexAttributes[attributeIndex].readAsDbl(ncomp + * index + icomp); + outPoint.setAttribute(semantics, icomp, v); + } + } + return outPoint; + } + + @Override + public void setPoint(int index, Point src) { + if (index < 0 || index >= m_pointCount) + throw new IndexOutOfBoundsException(); + + Point point = src; + + if (src.isEmpty())// can not assign an empty point to a multipoint + // vertex + throw new IllegalArgumentException(); + + _verifyAllStreams();// verify all allocated streams are of necessary + // size. + VertexDescription vdin = point.getDescription(); + for (int attributeIndex = 0; attributeIndex < vdin.getAttributeCount(); attributeIndex++) { + int semantics = vdin.getSemantics(attributeIndex); + int ncomp = VertexDescription.getComponentCount(semantics); + for (int icomp = 0; icomp < ncomp; icomp++) { + double v = point.getAttributeAsDbl(semantics, icomp); + setAttribute(semantics, index, icomp, v); + } + } + } + + @Override + public void queryCoordinates(Point[] dst) { + int sz = m_pointCount; + if (dst.length < sz) + throw new IllegalArgumentException(); + + // TODO: refactor to a better AttributeAray call (ReadRange?) + for (int i = 0; i < sz; i++) { + dst[i] = getPoint(i); + } + } + + @Override + public void queryCoordinates(Point2D[] dst) { + int sz = m_pointCount; + if (dst.length < sz) + throw new IllegalArgumentException(); + + // TODO: refactor to a better AttributeAray call (ReadRange?) + for (int i = 0; i < sz; i++) { + dst[i] = getXY(i); + } + } + + @Override + public void queryCoordinates(Point3D[] dst) { + int sz = m_pointCount; + if (dst.length < sz) + throw new IllegalArgumentException(); + + // TODO: refactor to a better AttributeAray call (ReadRange?) + for (int i = 0; i < sz; i++) { + dst[i] = getXYZ(i); + } + } + + @Override + public void replaceNaNs(int semantics, double value) { + addAttribute(semantics); + if (isEmpty()) + return; + + boolean modified = false; + int ncomps = VertexDescription.getComponentCount(semantics); + for (int i = 0; i < ncomps; i++) { + AttributeStreamBase streamBase = getAttributeStreamRef(semantics); + if (streamBase instanceof AttributeStreamOfDbl) { + AttributeStreamOfDbl dblStream = (AttributeStreamOfDbl) streamBase; + for (int ivert = 0, n = m_pointCount * ncomps; ivert < n; ivert++) { + double v = dblStream.read(ivert); + if (Double.isNaN(v)) { + dblStream.write(ivert, value); + modified = true; + } + } + } else { + for (int ivert = 0, n = m_pointCount * ncomps; ivert < n; ivert++) { + double v = streamBase.readAsDbl(ivert); + if (Double.isNaN(v)) { + streamBase.writeAsDbl(ivert, value); + modified = true; + } + } + } + } + + if (modified) { + notifyModified(DirtyFlags.DirtyCoordinates); + } + } + + public abstract boolean _buildRasterizedGeometryAccelerator( + double toleranceXY, GeometryAccelerationDegree accelDegree); + + public abstract boolean _buildQuadTreeAccelerator( + GeometryAccelerationDegree d); + + @Override + public String toString() { + return "MultiVertexGeometryImpl"; + } } diff --git a/src/main/java/com/esri/core/geometry/NonSimpleResult.java b/src/main/java/com/esri/core/geometry/NonSimpleResult.java index 47fa6a59..517a19e7 100644 --- a/src/main/java/com/esri/core/geometry/NonSimpleResult.java +++ b/src/main/java/com/esri/core/geometry/NonSimpleResult.java @@ -27,80 +27,80 @@ * The result of the IsSimpleXXX. */ public class NonSimpleResult { - public enum Reason { - /** - * This value is returned if the geometry "knows" through an internal state that it is non-simple. - * To make it determine the reason, use - * bForceTest == True. - */ - NotDetermined, - /** - * non-simple, because the structure is bad (0 size path, for example). - */ - Structure, - /** - * Non-simple, because there are degenerate segments. - */ - DegenerateSegments, - /** - * Non-simple, because not clustered properly, that is there are non-coincident vertices closer than tolerance. - */ - Clustering, - /** - * Non-simple, because not cracked properly (intersecting segments, overlaping segments) - */ - Cracking, - /** - * Non-simple, because there are crossovers (self intersections that are not cracking case). - */ - CrossOver, - /** - * Non-simple, because holes or exteriors have wrong orientation. - */ - RingOrientation, - /** - * The geometry is simple, but not strong-simple, because exteriors - * and holes are not in the correct order, and separation into sub polygons is not possible. - * Geometry needs to be resimplified with the bForceTest = true to fix this. - */ - RingOrder, - /** - * There is a self tangency or cross-over situation (strong simple, but not OGC simple) - * Only OperatorSimplifyOGC returns this. - */ - OGCPolylineSelfTangency, - /** - * There is a self tangency situation (strong simple, but not OGC simple) - * Only OperatorSimplifyOGC returns this. - */ - OGCPolygonSelfTangency, - /** - * Touching interioir rings make a disconnected point set from polygon interior - * (strong simple, but not OGC simple). - * Only OperatorSimplifyOGC returns this. - */ - OGCDisconnectedInterior - } + public enum Reason { + /** + * This value is returned if the geometry "knows" through an internal state that it is non-simple. + * To make it determine the reason, use + * bForceTest == True. + */ + NotDetermined, + /** + * non-simple, because the structure is bad (0 size path, for example). + */ + Structure, + /** + * Non-simple, because there are degenerate segments. + */ + DegenerateSegments, + /** + * Non-simple, because not clustered properly, that is there are non-coincident vertices closer than tolerance. + */ + Clustering, + /** + * Non-simple, because not cracked properly (intersecting segments, overlaping segments) + */ + Cracking, + /** + * Non-simple, because there are crossovers (self intersections that are not cracking case). + */ + CrossOver, + /** + * Non-simple, because holes or exteriors have wrong orientation. + */ + RingOrientation, + /** + * The geometry is simple, but not strong-simple, because exteriors + * and holes are not in the correct order, and separation into sub polygons is not possible. + * Geometry needs to be resimplified with the bForceTest = true to fix this. + */ + RingOrder, + /** + * There is a self tangency or cross-over situation (strong simple, but not OGC simple) + * Only OperatorSimplifyOGC returns this. + */ + OGCPolylineSelfTangency, + /** + * There is a self tangency situation (strong simple, but not OGC simple) + * Only OperatorSimplifyOGC returns this. + */ + OGCPolygonSelfTangency, + /** + * Touching interioir rings make a disconnected point set from polygon interior + * (strong simple, but not OGC simple). + * Only OperatorSimplifyOGC returns this. + */ + OGCDisconnectedInterior + } - public Reason m_reason; - public int m_vertexIndex1; - public int m_vertexIndex2; + public Reason m_reason; + public int m_vertexIndex1; + public int m_vertexIndex2; - public NonSimpleResult() { - m_reason = Reason.NotDetermined; - m_vertexIndex1 = -1; - m_vertexIndex2 = -1; - } + public NonSimpleResult() { + m_reason = Reason.NotDetermined; + m_vertexIndex1 = -1; + m_vertexIndex2 = -1; + } - void Assign(NonSimpleResult src) { - m_reason = src.m_reason; - m_vertexIndex1 = src.m_vertexIndex1; - m_vertexIndex2 = src.m_vertexIndex2; - } + void Assign(NonSimpleResult src) { + m_reason = src.m_reason; + m_vertexIndex1 = src.m_vertexIndex1; + m_vertexIndex2 = src.m_vertexIndex2; + } - NonSimpleResult(Reason reason, int index1, int index2) { - m_reason = reason; - m_vertexIndex1 = index1; - m_vertexIndex2 = index2; - } + NonSimpleResult(Reason reason, int index1, int index2) { + m_reason = reason; + m_vertexIndex1 = index1; + m_vertexIndex2 = index2; + } } diff --git a/src/main/java/com/esri/core/geometry/NumberUtils.java b/src/main/java/com/esri/core/geometry/NumberUtils.java index 0fc74aa5..aa704ad8 100644 --- a/src/main/java/com/esri/core/geometry/NumberUtils.java +++ b/src/main/java/com/esri/core/geometry/NumberUtils.java @@ -26,115 +26,115 @@ public class NumberUtils { - public static int snap(int v, int minv, int maxv) { - return v < minv ? minv : v > maxv ? maxv : v; - } - - public static long snap(long v, long minv, long maxv) { - return v < minv ? minv : v > maxv ? maxv : v; - } - - public static double snap(double v, double minv, double maxv) { - return v < minv ? minv : v > maxv ? maxv : v; - } - - static int sizeOf(double v) { - return 8; - } - - static int sizeOfDouble() { - return 8; - } - - static int sizeOf(int v) { - return 4; - } - - static int sizeOf(long v) { - return 8; - } - - static int sizeOf(byte v) { - return 1; - } - - static boolean isNaN(double d) { - return Double.isNaN(d); - } - - final static double TheNaN = Double.NaN; - - static double NaN() { - return Double.NaN; - } - - //combines two hash values - public static int hashCombine(int hash1, int hash2) { - return (hash1 * 31 + hash2) & 0x7FFFFFFF; - } - - //makes a hash out of an int - static int hash(int n) { - int hash = 5381; - hash = ((hash << 5) + hash) + (n & 0xFF); /* hash * 33 + c */ - hash = ((hash << 5) + hash) + ((n >> 8) & 0xFF); - hash = ((hash << 5) + hash) + ((n >> 16) & 0xFF); - hash = ((hash << 5) + hash) + ((n >> 24) & 0xFF); - hash &= 0x7FFFFFFF; - return hash; - } - - // //makes a hash out of an double - static int hash(double d) { - long bits = Double.doubleToLongBits(d); - int hc = (int) (bits ^ (bits >>> 32)); - return hash(hc); - } - - //adds an int to a hash value - static int hash(int hashIn, int n) { - int hash = ((hashIn << 5) + hashIn) + (n & 0xFF); /* hash * 33 + c */ - hash = ((hash << 5) + hash) + ((n >> 8) & 0xFF); - hash = ((hash << 5) + hash) + ((n >> 16) & 0xFF); - hash = ((hash << 5) + hash) + ((n >> 24) & 0xFF); - hash &= 0x7FFFFFFF; - return hash; - } - - //adds a double to a hash value - static int hash(int hash, double d) { - long bits = Double.doubleToLongBits(d); - int hc = (int) (bits ^ (bits >>> 32)); - return hash(hash, hc); - } - - static long doubleToInt64Bits(double d) { - return Double.doubleToLongBits(d); - } - - static double negativeInf() { - return Double.NEGATIVE_INFINITY; - } - - static double positiveInf() { - return Double.POSITIVE_INFINITY; - } - - static int intMax() { - return Integer.MAX_VALUE; - } - - static double doubleEps() { - return 2.2204460492503131e-016; - } - - static double doubleMax() { - return Double.MAX_VALUE; - } - - static int nextRand(int prevRand) { - return (1103515245 * prevRand + 12345) & intMax(); // according to Wiki, - // this is gcc's - } + public static int snap(int v, int minv, int maxv) { + return v < minv ? minv : v > maxv ? maxv : v; + } + + public static long snap(long v, long minv, long maxv) { + return v < minv ? minv : v > maxv ? maxv : v; + } + + public static double snap(double v, double minv, double maxv) { + return v < minv ? minv : v > maxv ? maxv : v; + } + + static int sizeOf(double v) { + return 8; + } + + static int sizeOfDouble() { + return 8; + } + + static int sizeOf(int v) { + return 4; + } + + static int sizeOf(long v) { + return 8; + } + + static int sizeOf(byte v) { + return 1; + } + + static boolean isNaN(double d) { + return Double.isNaN(d); + } + + final static double TheNaN = Double.NaN; + + static double NaN() { + return Double.NaN; + } + + //combines two hash values + public static int hashCombine(int hash1, int hash2) { + return (hash1 * 31 + hash2) & 0x7FFFFFFF; + } + + //makes a hash out of an int + static int hash(int n) { + int hash = 5381; + hash = ((hash << 5) + hash) + (n & 0xFF); /* hash * 33 + c */ + hash = ((hash << 5) + hash) + ((n >> 8) & 0xFF); + hash = ((hash << 5) + hash) + ((n >> 16) & 0xFF); + hash = ((hash << 5) + hash) + ((n >> 24) & 0xFF); + hash &= 0x7FFFFFFF; + return hash; + } + + // //makes a hash out of an double + static int hash(double d) { + long bits = Double.doubleToLongBits(d); + int hc = (int) (bits ^ (bits >>> 32)); + return hash(hc); + } + + //adds an int to a hash value + static int hash(int hashIn, int n) { + int hash = ((hashIn << 5) + hashIn) + (n & 0xFF); /* hash * 33 + c */ + hash = ((hash << 5) + hash) + ((n >> 8) & 0xFF); + hash = ((hash << 5) + hash) + ((n >> 16) & 0xFF); + hash = ((hash << 5) + hash) + ((n >> 24) & 0xFF); + hash &= 0x7FFFFFFF; + return hash; + } + + //adds a double to a hash value + static int hash(int hash, double d) { + long bits = Double.doubleToLongBits(d); + int hc = (int) (bits ^ (bits >>> 32)); + return hash(hash, hc); + } + + static long doubleToInt64Bits(double d) { + return Double.doubleToLongBits(d); + } + + static double negativeInf() { + return Double.NEGATIVE_INFINITY; + } + + static double positiveInf() { + return Double.POSITIVE_INFINITY; + } + + static int intMax() { + return Integer.MAX_VALUE; + } + + static double doubleEps() { + return 2.2204460492503131e-016; + } + + static double doubleMax() { + return Double.MAX_VALUE; + } + + static int nextRand(int prevRand) { + return (1103515245 * prevRand + 12345) & intMax(); // according to Wiki, + // this is gcc's + } } diff --git a/src/main/java/com/esri/core/geometry/OGCStructure.java b/src/main/java/com/esri/core/geometry/OGCStructure.java index b5002dde..9f0875b9 100644 --- a/src/main/java/com/esri/core/geometry/OGCStructure.java +++ b/src/main/java/com/esri/core/geometry/OGCStructure.java @@ -26,7 +26,7 @@ import java.util.List; public class OGCStructure { - public int m_type; - public List m_structures; - public Geometry m_geometry; + public int m_type; + public List m_structures; + public Geometry m_geometry; } diff --git a/src/main/java/com/esri/core/geometry/ObjectCacheTable.java b/src/main/java/com/esri/core/geometry/ObjectCacheTable.java index bddb924b..54b18abd 100644 --- a/src/main/java/com/esri/core/geometry/ObjectCacheTable.java +++ b/src/main/java/com/esri/core/geometry/ObjectCacheTable.java @@ -28,38 +28,38 @@ import java.util.Map; class ObjectCacheTable { - private Map m_hashTable = Collections - .synchronizedMap(new HashMap()); - private Object[] m_lru; - private boolean[] m_places; - private int m_index; + private Map m_hashTable = Collections + .synchronizedMap(new HashMap()); + private Object[] m_lru; + private boolean[] m_places; + private int m_index; - public ObjectCacheTable(int maxSize) { - m_lru = new Object[maxSize]; - m_places = new boolean[maxSize]; - m_index = 0; - for (int i = 0; i < maxSize; i++) - m_places[i] = false; - } + public ObjectCacheTable(int maxSize) { + m_lru = new Object[maxSize]; + m_places = new boolean[maxSize]; + m_index = 0; + for (int i = 0; i < maxSize; i++) + m_places[i] = false; + } - boolean contains(K key) { - return m_hashTable.containsKey(key); - } + boolean contains(K key) { + return m_hashTable.containsKey(key); + } - T get(K key) { - return m_hashTable.get(key); - } + T get(K key) { + return m_hashTable.get(key); + } - void add(K key, T value) { - if (m_places[m_index]) {// remove existing element from the cache - m_places[m_index] = false; - m_hashTable.remove(m_lru[m_index]); - } + void add(K key, T value) { + if (m_places[m_index]) {// remove existing element from the cache + m_places[m_index] = false; + m_hashTable.remove(m_lru[m_index]); + } - m_hashTable.put(key, value); - m_lru[m_index] = key; - m_places[m_index] = true; - m_index = (m_index + 1) % m_lru.length; - } + m_hashTable.put(key, value); + m_lru[m_index] = key; + m_places[m_index] = true; + m_index = (m_index + 1) % m_lru.length; + } } diff --git a/src/main/java/com/esri/core/geometry/Operator.java b/src/main/java/com/esri/core/geometry/Operator.java index 82fd9632..f6b5cdd7 100644 --- a/src/main/java/com/esri/core/geometry/Operator.java +++ b/src/main/java/com/esri/core/geometry/Operator.java @@ -29,108 +29,108 @@ * The base class for Geometry Operators. */ public abstract class Operator { - /** - * The operator type enum. - */ - public enum Type { -// TODO, if you change the order of this you will ruin the geometry service - Project, - ExportToJson, - ImportFromJson, - ExportToESRIShape, - ImportFromESRIShape, - Union, - Difference, - Proximity2D, - Relate, - Equals, - Disjoint, - Intersects, - Within, - Contains, - Crosses, - Touches, - Overlaps, - Buffer, - Distance, - Intersection, - Clip, - Cut, - DensifyByLength, - DensifyByAngle, - LabelPoint, - GeodesicBuffer, - GeodeticDensifyByLength, - ShapePreservingDensify, - GeodeticLength, - GeodeticArea, - Simplify, - SimplifyOGC, - Offset, - Generalize, - GeneralizeByArea, - ExportToWkb, - ImportFromWkb, - ExportToWkt, - ImportFromWkt, - ImportFromGeoJson, - ExportToGeoJson, - SymmetricDifference, - ConvexHull, - Boundary, - RandomPoints, - EnclosingCircle, - GeodeticInverse - } + /** + * The operator type enum. + */ + public enum Type { + // TODO, if you change the order of this you will ruin the geometry service + Project, + ExportToJson, + ImportFromJson, + ExportToESRIShape, + ImportFromESRIShape, + Union, + Difference, + Proximity2D, + Relate, + Equals, + Disjoint, + Intersects, + Within, + Contains, + Crosses, + Touches, + Overlaps, + Buffer, + Distance, + Intersection, + Clip, + Cut, + DensifyByLength, + DensifyByAngle, + LabelPoint, + GeodesicBuffer, + GeodeticDensifyByLength, + ShapePreservingDensify, + GeodeticLength, + GeodeticArea, + Simplify, + SimplifyOGC, + Offset, + Generalize, + GeneralizeByArea, + ExportToWkb, + ImportFromWkb, + ExportToWkt, + ImportFromWkt, + ImportFromGeoJson, + ExportToGeoJson, + SymmetricDifference, + ConvexHull, + Boundary, + RandomPoints, + EnclosingCircle, + GeodeticInverse + } - public abstract Type getType(); + public abstract Type getType(); - /** - * Processes Geometry to accelerate operations on it. The Geometry and it's - * copies remain accelerated until modified. The acceleration of Geometry - * can be a time consuming operation. The accelerated geometry also takes - * more memory. Some operators share the same accelerator, some require - * a different one. If the accelerator is built for the given parameters, - * the method returns immediately. - * - * @param geometry The geometry to be accelerated - * @param spatialReference The spatial reference of that geometry - * @param accelDegree The acceleration degree for geometry. - */ - public boolean accelerateGeometry(Geometry geometry, - SpatialReference spatialReference, - GeometryAccelerationDegree accelDegree) { - // Override at specific Operator level - return false; - } + /** + * Processes Geometry to accelerate operations on it. The Geometry and it's + * copies remain accelerated until modified. The acceleration of Geometry + * can be a time consuming operation. The accelerated geometry also takes + * more memory. Some operators share the same accelerator, some require + * a different one. If the accelerator is built for the given parameters, + * the method returns immediately. + * + * @param geometry The geometry to be accelerated + * @param spatialReference The spatial reference of that geometry + * @param accelDegree The acceleration degree for geometry. + */ + public boolean accelerateGeometry(Geometry geometry, + SpatialReference spatialReference, + GeometryAccelerationDegree accelDegree) { + // Override at specific Operator level + return false; + } - /** - * Returns true if the geometry can be accelerated. - * - * @param geometry - * @return true for geometries that can be accelerated, false for geometries - * that cannot - */ - public boolean canAccelerateGeometry(Geometry geometry) { - // Override at specific Operator level - return false; - } + /** + * Returns true if the geometry can be accelerated. + * + * @param geometry + * @return true for geometries that can be accelerated, false for geometries + * that cannot + */ + public boolean canAccelerateGeometry(Geometry geometry) { + // Override at specific Operator level + return false; + } - /** - * Removes accelerators from given geometry. - * - * @param geometry The geometry instance to remove accelerators from. - */ - public static void deaccelerateGeometry(Geometry geometry) { - Geometry.Type gt = geometry.getType(); - if (Geometry.isMultiVertex(gt.value())) { - GeometryAccelerators accel = ((MultiVertexGeometryImpl) geometry - ._getImpl())._getAccelerators(); - if (accel != null) { - accel._setRasterizedGeometry(null); - accel._setQuadTree(null); - } - } - } + /** + * Removes accelerators from given geometry. + * + * @param geometry The geometry instance to remove accelerators from. + */ + public static void deaccelerateGeometry(Geometry geometry) { + Geometry.Type gt = geometry.getType(); + if (Geometry.isMultiVertex(gt.value())) { + GeometryAccelerators accel = ((MultiVertexGeometryImpl) geometry + ._getImpl())._getAccelerators(); + if (accel != null) { + accel._setRasterizedGeometry(null); + accel._setQuadTree(null); + } + } + } } diff --git a/src/main/java/com/esri/core/geometry/OperatorBoundary.java b/src/main/java/com/esri/core/geometry/OperatorBoundary.java index 673949f8..ea0e8405 100644 --- a/src/main/java/com/esri/core/geometry/OperatorBoundary.java +++ b/src/main/java/com/esri/core/geometry/OperatorBoundary.java @@ -24,38 +24,38 @@ package com.esri.core.geometry; public abstract class OperatorBoundary extends Operator { - @Override - public Type getType() { - return Type.Boundary; - } - - /** - * Calculates the boundary geometry. - * - * @param geoms The input geometry cursor. - * @param progress_tracker The progress tracker, that allows to cancel the lengthy operation. - * @return Returns a cursor over boundaries for each geometry. - */ - abstract public GeometryCursor execute(GeometryCursor geoms, - ProgressTracker progress_tracker); - - /** - * Calculates the boundary. - * - * @param geom The input geometry. - * @param progress_tracker The progress tracker, that allows to cancel the lengthy operation. - * @return Returns the boundary. - *

- * For Point - returns an empty point. - * For Multi_point - returns an empty point. - * For Envelope - returns a polyline, that bounds the envelope. - * For Polyline - returns a multipoint, using OGC specification (includes path endpoints, using mod 2 rule). - * For Polygon - returns a polyline that bounds the polygon (adds all rings of the polygon to a polyline). - */ - abstract public Geometry execute(Geometry geom, - ProgressTracker progress_tracker); - - public static OperatorBoundary local() { - return (OperatorBoundary) OperatorFactoryLocal.getInstance().getOperator(Type.Boundary); - } + @Override + public Type getType() { + return Type.Boundary; + } + + /** + * Calculates the boundary geometry. + * + * @param geoms The input geometry cursor. + * @param progress_tracker The progress tracker, that allows to cancel the lengthy operation. + * @return Returns a cursor over boundaries for each geometry. + */ + abstract public GeometryCursor execute(GeometryCursor geoms, + ProgressTracker progress_tracker); + + /** + * Calculates the boundary. + * + * @param geom The input geometry. + * @param progress_tracker The progress tracker, that allows to cancel the lengthy operation. + * @return Returns the boundary. + *

+ * For Point - returns an empty point. + * For Multi_point - returns an empty point. + * For Envelope - returns a polyline, that bounds the envelope. + * For Polyline - returns a multipoint, using OGC specification (includes path endpoints, using mod 2 rule). + * For Polygon - returns a polyline that bounds the polygon (adds all rings of the polygon to a polyline). + */ + abstract public Geometry execute(Geometry geom, + ProgressTracker progress_tracker); + + public static OperatorBoundary local() { + return (OperatorBoundary) OperatorFactoryLocal.getInstance().getOperator(Type.Boundary); + } } diff --git a/src/main/java/com/esri/core/geometry/OperatorBoundaryLocal.java b/src/main/java/com/esri/core/geometry/OperatorBoundaryLocal.java index 56d31e11..97a62d8d 100644 --- a/src/main/java/com/esri/core/geometry/OperatorBoundaryLocal.java +++ b/src/main/java/com/esri/core/geometry/OperatorBoundaryLocal.java @@ -25,19 +25,19 @@ class OperatorBoundaryLocal extends OperatorBoundary { - @Override - public GeometryCursor execute(GeometryCursor geoms, - ProgressTracker progressTracker) { - // TODO Auto-generated method stub - return new OperatorBoundaryLocalCursor(geoms, progressTracker); - } - - @Override - public Geometry execute(Geometry geom, ProgressTracker progressTracker) { - // TODO Auto-generated method stub - GeometryCursor res = new OperatorBoundaryLocalCursor( - new SimpleGeometryCursor(geom), progressTracker); - return res.next(); - } + @Override + public GeometryCursor execute(GeometryCursor geoms, + ProgressTracker progressTracker) { + // TODO Auto-generated method stub + return new OperatorBoundaryLocalCursor(geoms, progressTracker); + } + + @Override + public Geometry execute(Geometry geom, ProgressTracker progressTracker) { + // TODO Auto-generated method stub + GeometryCursor res = new OperatorBoundaryLocalCursor( + new SimpleGeometryCursor(geom), progressTracker); + return res.next(); + } } diff --git a/src/main/java/com/esri/core/geometry/OperatorBoundaryLocalCursor.java b/src/main/java/com/esri/core/geometry/OperatorBoundaryLocalCursor.java index e2a8ff1c..7518058c 100644 --- a/src/main/java/com/esri/core/geometry/OperatorBoundaryLocalCursor.java +++ b/src/main/java/com/esri/core/geometry/OperatorBoundaryLocalCursor.java @@ -24,30 +24,30 @@ package com.esri.core.geometry; final class OperatorBoundaryLocalCursor extends GeometryCursor { - ProgressTracker m_progress_tracker; - - OperatorBoundaryLocalCursor(GeometryCursor inputGeoms, - ProgressTracker tracker) { - m_inputGeoms = inputGeoms; - m_progress_tracker = tracker; - } - - @Override - public Geometry next() { - if (hasNext()) { - return calculate_boundary(m_inputGeoms.next(), m_progress_tracker); - } - - return null; - } - - private static Geometry calculate_boundary(Geometry geom, - ProgressTracker progress_tracker) { - Geometry res = Boundary.calculate(geom, progress_tracker); - if (res == null) - return new Point(geom.getDescription());// cannot return null - else - return res; - } + ProgressTracker m_progress_tracker; + + OperatorBoundaryLocalCursor(GeometryCursor inputGeoms, + ProgressTracker tracker) { + m_inputGeoms = inputGeoms; + m_progress_tracker = tracker; + } + + @Override + public Geometry next() { + if (hasNext()) { + return calculate_boundary(m_inputGeoms.next(), m_progress_tracker); + } + + return null; + } + + private static Geometry calculate_boundary(Geometry geom, + ProgressTracker progress_tracker) { + Geometry res = Boundary.calculate(geom, progress_tracker); + if (res == null) + return new Point(geom.getDescription());// cannot return null + else + return res; + } } diff --git a/src/main/java/com/esri/core/geometry/OperatorBuffer.java b/src/main/java/com/esri/core/geometry/OperatorBuffer.java index cac9742b..b940420d 100644 --- a/src/main/java/com/esri/core/geometry/OperatorBuffer.java +++ b/src/main/java/com/esri/core/geometry/OperatorBuffer.java @@ -30,69 +30,69 @@ * Creates buffer polygons around geometries. */ public abstract class OperatorBuffer extends Operator { - @Override - public Type getType() { - return Type.Buffer; - } + @Override + public Type getType() { + return Type.Buffer; + } - /** - * Creates a buffer around the input geometries - * - * @param inputGeometries The geometries to buffer. - * @param sr The SpatialReference of the Geometries. - * @param distances The buffer distances for the Geometries. If the size of the distances array is less than the number of geometries in the inputGeometries, the last distance value is used for the rest of geometries. - * @param bUnion If True, the buffered geometries will be unioned, otherwise they wont be unioned. - */ - public abstract GeometryCursor execute(GeometryCursor inputGeometries, - SpatialReference sr, - double[] distances, - boolean bUnion, - ProgressTracker progressTracker); + /** + * Creates a buffer around the input geometries + * + * @param inputGeometries The geometries to buffer. + * @param sr The SpatialReference of the Geometries. + * @param distances The buffer distances for the Geometries. If the size of the distances array is less than the number of geometries in the inputGeometries, the last distance value is used for the rest of geometries. + * @param bUnion If True, the buffered geometries will be unioned, otherwise they wont be unioned. + */ + public abstract GeometryCursor execute(GeometryCursor inputGeometries, + SpatialReference sr, + double[] distances, + boolean bUnion, + ProgressTracker progressTracker); - /** - * Creates a buffer around the input geometry - * - * @param inputGeometry The geometry to buffer. - * @param sr The SpatialReference of the Geometry. - * @param distance The buffer distance for the Geometry. - */ - public abstract Geometry execute(Geometry inputGeometry, - SpatialReference sr, - double distance, - ProgressTracker progressTracker); + /** + * Creates a buffer around the input geometry + * + * @param inputGeometry The geometry to buffer. + * @param sr The SpatialReference of the Geometry. + * @param distance The buffer distance for the Geometry. + */ + public abstract Geometry execute(Geometry inputGeometry, + SpatialReference sr, + double distance, + ProgressTracker progressTracker); - /** - * Creates a buffer around the input geometries - * - * @param input_geometries The geometries to buffer. - * @param sr The Spatial_reference of the Geometries. It is used to obtain the tolerance. Can be null. - * @param distances The buffer distances for the Geometries. If the size of the distances array is less than the number of geometries in the input_geometries, the last distance value is used for the rest of geometries. - * @param max_deviation The max deviation of the result buffer from the true buffer in the units of the sr. - * When max_deviation is NaN or 0, it is replaced with 1e-5 * abs(distance). - * When max_deviation is larger than MIN = 0.5 * abs(distance), it is replaced with MIN. See below for more information. - * @param max_vertices_in_full_circle The maximum number of vertices in polygon produced from a buffered point. A value of 96 is used in methods that do not accept max_vertices_in_full_circle. - * If the value is less than MIN=12, it is set to MIN. See below for more information. - * @param b_union If True, the buffered geometries will be unioned, otherwise they wont be unioned. - * @param progress_tracker The progress tracker that allows to cancel the operation. Pass null if not needed. - *

- * The max_deviation and max_vertices_in_full_circle control the quality of round joins in the buffer. That is, the precision of the buffer is max_deviation unless - * the number of required vertices is too large. - * The max_vertices_in_full_circle controls how many vertices can be in each round join in the buffer. It is approximately equal to the number of vertices in the polygon around a - * buffered point. It has a priority over max_deviation. The max deviation is the distance from the result polygon to a true buffer. - * The real deviation is calculated as the max(max_deviation, abs(distance) * (1 - cos(PI / max_vertex_in_complete_circle))). - *

- * Note that max_deviation can be exceeded because geometry is generalized with 0.25 * real_deviation, also input segments closer than 0.25 * real_deviation are - * snapped to a point. - */ - public abstract GeometryCursor execute(GeometryCursor input_geometries, - SpatialReference sr, - double[] distances, - double max_deviation, - int max_vertices_in_full_circle, - boolean b_union, - ProgressTracker progress_tracker); + /** + * Creates a buffer around the input geometries + * + * @param input_geometries The geometries to buffer. + * @param sr The Spatial_reference of the Geometries. It is used to obtain the tolerance. Can be null. + * @param distances The buffer distances for the Geometries. If the size of the distances array is less than the number of geometries in the input_geometries, the last distance value is used for the rest of geometries. + * @param max_deviation The max deviation of the result buffer from the true buffer in the units of the sr. + * When max_deviation is NaN or 0, it is replaced with 1e-5 * abs(distance). + * When max_deviation is larger than MIN = 0.5 * abs(distance), it is replaced with MIN. See below for more information. + * @param max_vertices_in_full_circle The maximum number of vertices in polygon produced from a buffered point. A value of 96 is used in methods that do not accept max_vertices_in_full_circle. + * If the value is less than MIN=12, it is set to MIN. See below for more information. + * @param b_union If True, the buffered geometries will be unioned, otherwise they wont be unioned. + * @param progress_tracker The progress tracker that allows to cancel the operation. Pass null if not needed. + *

+ * The max_deviation and max_vertices_in_full_circle control the quality of round joins in the buffer. That is, the precision of the buffer is max_deviation unless + * the number of required vertices is too large. + * The max_vertices_in_full_circle controls how many vertices can be in each round join in the buffer. It is approximately equal to the number of vertices in the polygon around a + * buffered point. It has a priority over max_deviation. The max deviation is the distance from the result polygon to a true buffer. + * The real deviation is calculated as the max(max_deviation, abs(distance) * (1 - cos(PI / max_vertex_in_complete_circle))). + *

+ * Note that max_deviation can be exceeded because geometry is generalized with 0.25 * real_deviation, also input segments closer than 0.25 * real_deviation are + * snapped to a point. + */ + public abstract GeometryCursor execute(GeometryCursor input_geometries, + SpatialReference sr, + double[] distances, + double max_deviation, + int max_vertices_in_full_circle, + boolean b_union, + ProgressTracker progress_tracker); - public static OperatorBuffer local() { - return (OperatorBuffer) OperatorFactoryLocal.getInstance().getOperator(Type.Buffer); - } + public static OperatorBuffer local() { + return (OperatorBuffer) OperatorFactoryLocal.getInstance().getOperator(Type.Buffer); + } } diff --git a/src/main/java/com/esri/core/geometry/OperatorBufferCursor.java b/src/main/java/com/esri/core/geometry/OperatorBufferCursor.java index bcd2d707..d77a1546 100644 --- a/src/main/java/com/esri/core/geometry/OperatorBufferCursor.java +++ b/src/main/java/com/esri/core/geometry/OperatorBufferCursor.java @@ -25,53 +25,53 @@ package com.esri.core.geometry; class OperatorBufferCursor extends GeometryCursor { - Bufferer m_bufferer = new Bufferer(); - private SpatialReferenceImpl m_Spatial_reference; - private ProgressTracker m_progress_tracker; - private double[] m_distances; - private Envelope2D m_currentUnionEnvelope2D; - private boolean m_bUnion; - double m_max_deviation; - int m_max_vertices_in_full_circle; - private int m_dindex; + Bufferer m_bufferer = new Bufferer(); + private SpatialReferenceImpl m_Spatial_reference; + private ProgressTracker m_progress_tracker; + private double[] m_distances; + private Envelope2D m_currentUnionEnvelope2D; + private boolean m_bUnion; + double m_max_deviation; + int m_max_vertices_in_full_circle; + private int m_dindex; - OperatorBufferCursor(GeometryCursor inputGeoms, - SpatialReference sr, - double[] distances, - double max_deviation, - int max_vertices, - boolean b_union, - ProgressTracker progress_tracker) { - m_inputGeoms = inputGeoms; - m_max_deviation = max_deviation; - m_max_vertices_in_full_circle = max_vertices; - m_Spatial_reference = (SpatialReferenceImpl) (sr); - m_distances = distances; - m_bUnion = b_union; - m_currentUnionEnvelope2D = new Envelope2D(); - m_currentUnionEnvelope2D.setEmpty(); - m_dindex = -1; - m_progress_tracker = progress_tracker; - } + OperatorBufferCursor(GeometryCursor inputGeoms, + SpatialReference sr, + double[] distances, + double max_deviation, + int max_vertices, + boolean b_union, + ProgressTracker progress_tracker) { + m_inputGeoms = inputGeoms; + m_max_deviation = max_deviation; + m_max_vertices_in_full_circle = max_vertices; + m_Spatial_reference = (SpatialReferenceImpl) (sr); + m_distances = distances; + m_bUnion = b_union; + m_currentUnionEnvelope2D = new Envelope2D(); + m_currentUnionEnvelope2D.setEmpty(); + m_dindex = -1; + m_progress_tracker = progress_tracker; + } - @Override - public Geometry next() { - if (m_bUnion) { - OperatorBufferCursor bufferCursor = new OperatorBufferCursor(m_inputGeoms, - m_Spatial_reference, m_distances, m_max_deviation, m_max_vertices_in_full_circle, false, m_progress_tracker); - return ((OperatorUnion) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.Union)).execute(bufferCursor, m_Spatial_reference, m_progress_tracker).next(); - } else { - if (hasNext()) { - if (m_dindex + 1 < m_distances.length) - m_dindex++; + @Override + public Geometry next() { + if (m_bUnion) { + OperatorBufferCursor bufferCursor = new OperatorBufferCursor(m_inputGeoms, + m_Spatial_reference, m_distances, m_max_deviation, m_max_vertices_in_full_circle, false, m_progress_tracker); + return ((OperatorUnion) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.Union)).execute(bufferCursor, m_Spatial_reference, m_progress_tracker).next(); + } else { + if (hasNext()) { + if (m_dindex + 1 < m_distances.length) + m_dindex++; - return buffer(m_inputGeoms.next(), m_distances[m_dindex]); - } - return null; - } - } + return buffer(m_inputGeoms.next(), m_distances[m_dindex]); + } + return null; + } + } - Geometry buffer(Geometry geom, double distance) { - return m_bufferer.buffer(geom, distance, m_Spatial_reference, m_max_deviation, m_max_vertices_in_full_circle, m_progress_tracker); - } + Geometry buffer(Geometry geom, double distance) { + return m_bufferer.buffer(geom, distance, m_Spatial_reference, m_max_deviation, m_max_vertices_in_full_circle, m_progress_tracker); + } } diff --git a/src/main/java/com/esri/core/geometry/OperatorBufferLocal.java b/src/main/java/com/esri/core/geometry/OperatorBufferLocal.java index 7dddbcf1..78e281cc 100644 --- a/src/main/java/com/esri/core/geometry/OperatorBufferLocal.java +++ b/src/main/java/com/esri/core/geometry/OperatorBufferLocal.java @@ -26,42 +26,42 @@ class OperatorBufferLocal extends OperatorBuffer { - @Override - public GeometryCursor execute(GeometryCursor inputGeometries, - SpatialReference sr, - double[] distances, - boolean bUnion, - ProgressTracker progressTracker) { - return execute(inputGeometries, - sr, - distances, - NumberUtils.NaN(), - 96, - bUnion, - progressTracker); - } + @Override + public GeometryCursor execute(GeometryCursor inputGeometries, + SpatialReference sr, + double[] distances, + boolean bUnion, + ProgressTracker progressTracker) { + return execute(inputGeometries, + sr, + distances, + NumberUtils.NaN(), + 96, + bUnion, + progressTracker); + } - @Override - public Geometry execute(Geometry inputGeometry, - SpatialReference sr, - double distance, - ProgressTracker progressTracker) { - SimpleGeometryCursor inputCursor = new SimpleGeometryCursor(inputGeometry); - double[] distances = new double[1]; - distances[0] = distance; - GeometryCursor outputCursor = execute(inputCursor, sr, distances, false, progressTracker); + @Override + public Geometry execute(Geometry inputGeometry, + SpatialReference sr, + double distance, + ProgressTracker progressTracker) { + SimpleGeometryCursor inputCursor = new SimpleGeometryCursor(inputGeometry); + double[] distances = new double[1]; + distances[0] = distance; + GeometryCursor outputCursor = execute(inputCursor, sr, distances, false, progressTracker); - return outputCursor.next(); - } + return outputCursor.next(); + } - @Override - public GeometryCursor execute(GeometryCursor inputGeometries, - SpatialReference sr, - double[] distances, - double max_deviation, - int max_vertices_in_full_circle, - boolean b_union, - ProgressTracker progressTracker) { - return new OperatorBufferCursor(inputGeometries, sr, distances, max_deviation, max_vertices_in_full_circle, b_union, progressTracker); - } + @Override + public GeometryCursor execute(GeometryCursor inputGeometries, + SpatialReference sr, + double[] distances, + double max_deviation, + int max_vertices_in_full_circle, + boolean b_union, + ProgressTracker progressTracker) { + return new OperatorBufferCursor(inputGeometries, sr, distances, max_deviation, max_vertices_in_full_circle, b_union, progressTracker); + } } diff --git a/src/main/java/com/esri/core/geometry/OperatorClip.java b/src/main/java/com/esri/core/geometry/OperatorClip.java index adfc4018..a32312e3 100644 --- a/src/main/java/com/esri/core/geometry/OperatorClip.java +++ b/src/main/java/com/esri/core/geometry/OperatorClip.java @@ -30,25 +30,25 @@ * Clips geometries with Envelope2D. */ public abstract class OperatorClip extends Operator { - public Type getType() { - return Type.Clip; - } - - /** - * Performs the Clip operation on the geometry set. - */ - public abstract GeometryCursor execute(GeometryCursor geoms, - Envelope2D envelope, SpatialReference spatialRef, - ProgressTracker progressTracker); - - /** - * Performs the Clip operation on a single geometry. - */ - public abstract Geometry execute(Geometry geom, Envelope2D envelope, - SpatialReference spatialRef, ProgressTracker progressTracker); - - public static OperatorClip local() { - return (OperatorClip) OperatorFactoryLocal.getInstance().getOperator(Type.Clip); - } + public Type getType() { + return Type.Clip; + } + + /** + * Performs the Clip operation on the geometry set. + */ + public abstract GeometryCursor execute(GeometryCursor geoms, + Envelope2D envelope, SpatialReference spatialRef, + ProgressTracker progressTracker); + + /** + * Performs the Clip operation on a single geometry. + */ + public abstract Geometry execute(Geometry geom, Envelope2D envelope, + SpatialReference spatialRef, ProgressTracker progressTracker); + + public static OperatorClip local() { + return (OperatorClip) OperatorFactoryLocal.getInstance().getOperator(Type.Clip); + } } diff --git a/src/main/java/com/esri/core/geometry/OperatorClipCursor.java b/src/main/java/com/esri/core/geometry/OperatorClipCursor.java index 09f024a8..7904bd57 100644 --- a/src/main/java/com/esri/core/geometry/OperatorClipCursor.java +++ b/src/main/java/com/esri/core/geometry/OperatorClipCursor.java @@ -25,25 +25,25 @@ package com.esri.core.geometry; class OperatorClipCursor extends GeometryCursor { - private Envelope2D m_envelope; - double m_tolerance; - - OperatorClipCursor(GeometryCursor geoms, Envelope2D envelope, - SpatialReference spatial_ref, ProgressTracker progress_tracker) { - if (geoms == null) - throw new IllegalArgumentException(); - - m_envelope = envelope; - m_inputGeoms = geoms; - - m_tolerance = InternalUtils.calculateToleranceFromGeometry(spatial_ref, envelope, false); - } - - @Override - public Geometry next() { - if (hasNext()) { - return Clipper.clip(m_inputGeoms.next(), m_envelope, m_tolerance, 0.0); - } - return null; - } + private Envelope2D m_envelope; + double m_tolerance; + + OperatorClipCursor(GeometryCursor geoms, Envelope2D envelope, + SpatialReference spatial_ref, ProgressTracker progress_tracker) { + if (geoms == null) + throw new IllegalArgumentException(); + + m_envelope = envelope; + m_inputGeoms = geoms; + + m_tolerance = InternalUtils.calculateToleranceFromGeometry(spatial_ref, envelope, false); + } + + @Override + public Geometry next() { + if (hasNext()) { + return Clipper.clip(m_inputGeoms.next(), m_envelope, m_tolerance, 0.0); + } + return null; + } } diff --git a/src/main/java/com/esri/core/geometry/OperatorClipLocal.java b/src/main/java/com/esri/core/geometry/OperatorClipLocal.java index e641a8c9..4905eba2 100644 --- a/src/main/java/com/esri/core/geometry/OperatorClipLocal.java +++ b/src/main/java/com/esri/core/geometry/OperatorClipLocal.java @@ -26,19 +26,19 @@ class OperatorClipLocal extends OperatorClip { - @Override - public GeometryCursor execute(GeometryCursor geoms, Envelope2D envelope, - SpatialReference spatialRef, ProgressTracker progressTracker) { - return new OperatorClipCursor(geoms, envelope, spatialRef, progressTracker); - } - - @Override - public Geometry execute(Geometry geom, Envelope2D envelope, - SpatialReference spatialRef, ProgressTracker progressTracker) { - SimpleGeometryCursor inputCursor = new SimpleGeometryCursor(geom); - - GeometryCursor outputCursor = execute(inputCursor, envelope, spatialRef, progressTracker); - return outputCursor.next(); - } + @Override + public GeometryCursor execute(GeometryCursor geoms, Envelope2D envelope, + SpatialReference spatialRef, ProgressTracker progressTracker) { + return new OperatorClipCursor(geoms, envelope, spatialRef, progressTracker); + } + + @Override + public Geometry execute(Geometry geom, Envelope2D envelope, + SpatialReference spatialRef, ProgressTracker progressTracker) { + SimpleGeometryCursor inputCursor = new SimpleGeometryCursor(geom); + + GeometryCursor outputCursor = execute(inputCursor, envelope, spatialRef, progressTracker); + return outputCursor.next(); + } } diff --git a/src/main/java/com/esri/core/geometry/OperatorContains.java b/src/main/java/com/esri/core/geometry/OperatorContains.java index 62745640..bbc4cf27 100644 --- a/src/main/java/com/esri/core/geometry/OperatorContains.java +++ b/src/main/java/com/esri/core/geometry/OperatorContains.java @@ -30,13 +30,13 @@ * Relational operation Contains. */ public abstract class OperatorContains extends OperatorSimpleRelation { - @Override - public Type getType() { - return Type.Contains; - } - - public static OperatorContains local() { - return (OperatorContains) OperatorFactoryLocal.getInstance() - .getOperator(Type.Contains); - } + @Override + public Type getType() { + return Type.Contains; + } + + public static OperatorContains local() { + return (OperatorContains) OperatorFactoryLocal.getInstance() + .getOperator(Type.Contains); + } } diff --git a/src/main/java/com/esri/core/geometry/OperatorContainsLocal.java b/src/main/java/com/esri/core/geometry/OperatorContainsLocal.java index 9a61ef0f..592822e4 100644 --- a/src/main/java/com/esri/core/geometry/OperatorContainsLocal.java +++ b/src/main/java/com/esri/core/geometry/OperatorContainsLocal.java @@ -25,11 +25,11 @@ package com.esri.core.geometry; class OperatorContainsLocal extends OperatorContains { - @Override - public boolean execute(Geometry inputGeom1, Geometry inputGeom2, - SpatialReference sr, ProgressTracker progressTracker) { - return RelationalOperations.relate(inputGeom1, inputGeom2, sr, - RelationalOperations.Relation.contains, progressTracker); - } + @Override + public boolean execute(Geometry inputGeom1, Geometry inputGeom2, + SpatialReference sr, ProgressTracker progressTracker) { + return RelationalOperations.relate(inputGeom1, inputGeom2, sr, + RelationalOperations.Relation.contains, progressTracker); + } } diff --git a/src/main/java/com/esri/core/geometry/OperatorConvexHull.java b/src/main/java/com/esri/core/geometry/OperatorConvexHull.java index 9c02f531..a010b62b 100644 --- a/src/main/java/com/esri/core/geometry/OperatorConvexHull.java +++ b/src/main/java/com/esri/core/geometry/OperatorConvexHull.java @@ -29,51 +29,51 @@ * Creates the convex hull of the input geometry. */ public abstract class OperatorConvexHull extends Operator { - @Override - public Operator.Type getType() { - return Operator.Type.ConvexHull; - } + @Override + public Operator.Type getType() { + return Operator.Type.ConvexHull; + } - /** - * Calculates the convex hull. - * - * @param geoms The input geometry cursor. - * @param progress_tracker The progress tracker. Allows cancellation of a lengthy operation. - * @param b_merge Put true if you want the convex hull of all the geometries in the cursor combined. - * Put false if you want the convex hull of each geometry in the cursor individually. - * @return Returns a cursor over result convex hulls. - */ - abstract public GeometryCursor execute(GeometryCursor geoms, boolean b_merge, - ProgressTracker progress_tracker); + /** + * Calculates the convex hull. + * + * @param geoms The input geometry cursor. + * @param progress_tracker The progress tracker. Allows cancellation of a lengthy operation. + * @param b_merge Put true if you want the convex hull of all the geometries in the cursor combined. + * Put false if you want the convex hull of each geometry in the cursor individually. + * @return Returns a cursor over result convex hulls. + */ + abstract public GeometryCursor execute(GeometryCursor geoms, boolean b_merge, + ProgressTracker progress_tracker); - /** - * Calculates the convex hull geometry. - * - * @param geom The input geometry. - * @param progress_tracker The progress tracker. Allows cancellation of a lengthy operation. - * @return Returns the convex hull. - *

- * Point - Returns the same point. - * Envelope - returns the same envelope. - * MultiPoint - If the point count is one, returns the same multipoint. If the point count is two, returns a polyline of the points. Otherwise, computes and returns the convex hull polygon. - * Segment - Returns a polyline consisting of the segment. - * Polyline - If consists of only one segment, returns the same polyline. Otherwise, computes and returns the convex hull polygon. - * Polygon - If more than one path or if the path isn't already convex, computes and returns the convex hull polygon. Otherwise, returns the same polygon. - */ - abstract public Geometry execute(Geometry geom, - ProgressTracker progress_tracker); + /** + * Calculates the convex hull geometry. + * + * @param geom The input geometry. + * @param progress_tracker The progress tracker. Allows cancellation of a lengthy operation. + * @return Returns the convex hull. + *

+ * Point - Returns the same point. + * Envelope - returns the same envelope. + * MultiPoint - If the point count is one, returns the same multipoint. If the point count is two, returns a polyline of the points. Otherwise, computes and returns the convex hull polygon. + * Segment - Returns a polyline consisting of the segment. + * Polyline - If consists of only one segment, returns the same polyline. Otherwise, computes and returns the convex hull polygon. + * Polygon - If more than one path or if the path isn't already convex, computes and returns the convex hull polygon. Otherwise, returns the same polygon. + */ + abstract public Geometry execute(Geometry geom, + ProgressTracker progress_tracker); - /** - * Checks whether a Geometry is convex. - * - * @param geom The input geometry to test for convex. - * @param progress_tracker The progress tracker. - * @return Returns true if the geometry is convex. - */ - abstract public boolean isConvex(Geometry geom, - ProgressTracker progress_tracker); + /** + * Checks whether a Geometry is convex. + * + * @param geom The input geometry to test for convex. + * @param progress_tracker The progress tracker. + * @return Returns true if the geometry is convex. + */ + abstract public boolean isConvex(Geometry geom, + ProgressTracker progress_tracker); - public static OperatorConvexHull local() { - return (OperatorConvexHull) OperatorFactoryLocal.getInstance().getOperator(Type.ConvexHull); - } + public static OperatorConvexHull local() { + return (OperatorConvexHull) OperatorFactoryLocal.getInstance().getOperator(Type.ConvexHull); + } } diff --git a/src/main/java/com/esri/core/geometry/OperatorConvexHullCursor.java b/src/main/java/com/esri/core/geometry/OperatorConvexHullCursor.java index a91aa327..5667b4c5 100644 --- a/src/main/java/com/esri/core/geometry/OperatorConvexHullCursor.java +++ b/src/main/java/com/esri/core/geometry/OperatorConvexHullCursor.java @@ -24,184 +24,184 @@ package com.esri.core.geometry; class OperatorConvexHullCursor extends GeometryCursor { - private ProgressTracker m_progress_tracker; - private boolean m_b_merge; - private boolean m_b_done; - ConvexHull m_hull = new ConvexHull(); - - OperatorConvexHullCursor(boolean b_merge, GeometryCursor geoms, ProgressTracker progress_tracker) { - if (geoms == null) - throw new IllegalArgumentException(); - - m_b_merge = b_merge; - m_b_done = false; - m_inputGeoms = geoms; - m_progress_tracker = progress_tracker; - } - - @Override - public Geometry next() { - if (m_b_merge) { - if (!m_b_done) { - Geometry result = calculateConvexHullMerging_(m_inputGeoms, m_progress_tracker); - m_b_done = true; - return result; - } - - return null; - } - - if (!m_b_done) { - if (hasNext()) { - return calculateConvexHull_(m_inputGeoms.next(), m_progress_tracker); - } - - m_b_done = true; - } - - return null; - } - - private Geometry calculateConvexHullMerging_(GeometryCursor geoms, ProgressTracker progress_tracker) { - while (geoms.hasNext()) { - m_hull.addGeometry(geoms.next()); - } - - return m_hull.getBoundingGeometry(); - } - - @Override - public boolean tock() { - if (m_b_done) - return true; - - if (!m_b_merge) { - //Do not use tick/tock with the non-merging convex hull. - //Call tick/next instead, - //because tick pushes geometry into the cursor, and next performs a single convex hull on it. - throw new GeometryException("Invalid call for non merging convex hull."); - } - - Geometry geometry = m_inputGeoms.next(); - if (geometry != null) { - m_hull.addGeometry(geometry); - return true; - } else { - throw new GeometryException("Expects a non-null geometry."); - } - } - - static Geometry calculateConvexHull_(Geometry geom, ProgressTracker progress_tracker) { - if (geom.isEmpty()) - return geom.createInstance(); - - Geometry.Type type = geom.getType(); - - if (Geometry.isSegment(type.value())) {// Segments are always returned either as a Point or Polyline - Segment segment = (Segment) geom; - if (segment.getStartXY().equals(segment.getEndXY())) { - Point point = new Point(); - segment.queryStart(point); - return point; - } else { - Point pt = new Point(); - Polyline polyline = new Polyline(geom.getDescription()); - segment.queryStart(pt); - polyline.startPath(pt); - segment.queryEnd(pt); - polyline.lineTo(pt); - return polyline; - } - } else if (type == Geometry.Type.Envelope) { - Envelope envelope = (Envelope) geom; - Envelope2D env = new Envelope2D(); - envelope.queryEnvelope2D(env); - if (env.xmin == env.xmax && env.ymin == env.ymax) { - Point point = new Point(); - envelope.queryCornerByVal(0, point); - return point; - } else if (env.xmin == env.xmax || env.ymin == env.ymax) { - Point pt = new Point(); - Polyline polyline = new Polyline(geom.getDescription()); - envelope.queryCornerByVal(0, pt); - polyline.startPath(pt); - envelope.queryCornerByVal(1, pt); - polyline.lineTo(pt); - return polyline; - } else { - Polygon polygon = new Polygon(geom.getDescription()); - polygon.addEnvelope(envelope, false); - return polygon; - } - } - - if (isConvex_(geom, progress_tracker)) { - if (type == Geometry.Type.MultiPoint) {// Downgrade to a Point for simplistic output - MultiPoint multi_point = (MultiPoint) geom; - Point point = new Point(); - multi_point.getPointByVal(0, point); - return point; - } - - return geom; - } - - assert (Geometry.isMultiVertex(type.value())); - - Geometry convex_hull = ConvexHull.construct((MultiVertexGeometry) geom); - return convex_hull; - } - - static boolean isConvex_(Geometry geom, ProgressTracker progress_tracker) { - if (geom.isEmpty()) - return true; // vacuously true - - Geometry.Type type = geom.getType(); - - if (type == Geometry.Type.Point) - return true; // vacuously true - - if (type == Geometry.Type.Envelope) { - Envelope envelope = (Envelope) geom; - if (envelope.getXMin() == envelope.getXMax() || envelope.getYMin() == envelope.getYMax()) - return false; - - return true; - } - - if (MultiPath.isSegment(type.value())) { - Segment segment = (Segment) geom; - if (segment.getStartXY().equals(segment.getEndXY())) - return false; - - return true; // true, but we will upgrade to a Polyline for the ConvexHull operation - } - - if (type == Geometry.Type.MultiPoint) { - MultiPoint multi_point = (MultiPoint) geom; - - if (multi_point.getPointCount() == 1) - return true; // vacuously true, but we will downgrade to a Point for the ConvexHull operation - - return false; - } - - if (type == Geometry.Type.Polyline) { - Polyline polyline = (Polyline) geom; - - if (polyline.getPathCount() == 1 && polyline.getPointCount() == 2) { - if (!polyline.getXY(0).equals(polyline.getXY(1))) - return true; // vacuously true - } - - return false; // create convex hull - } - - Polygon polygon = (Polygon) geom; - - if (polygon.getPathCount() != 1 || polygon.getPointCount() < 3) - return false; - - return ConvexHull.isPathConvex(polygon, 0, progress_tracker); - } + private ProgressTracker m_progress_tracker; + private boolean m_b_merge; + private boolean m_b_done; + ConvexHull m_hull = new ConvexHull(); + + OperatorConvexHullCursor(boolean b_merge, GeometryCursor geoms, ProgressTracker progress_tracker) { + if (geoms == null) + throw new IllegalArgumentException(); + + m_b_merge = b_merge; + m_b_done = false; + m_inputGeoms = geoms; + m_progress_tracker = progress_tracker; + } + + @Override + public Geometry next() { + if (m_b_merge) { + if (!m_b_done) { + Geometry result = calculateConvexHullMerging_(m_inputGeoms, m_progress_tracker); + m_b_done = true; + return result; + } + + return null; + } + + if (!m_b_done) { + if (hasNext()) { + return calculateConvexHull_(m_inputGeoms.next(), m_progress_tracker); + } + + m_b_done = true; + } + + return null; + } + + private Geometry calculateConvexHullMerging_(GeometryCursor geoms, ProgressTracker progress_tracker) { + while (geoms.hasNext()) { + m_hull.addGeometry(geoms.next()); + } + + return m_hull.getBoundingGeometry(); + } + + @Override + public boolean tock() { + if (m_b_done) + return true; + + if (!m_b_merge) { + //Do not use tick/tock with the non-merging convex hull. + //Call tick/next instead, + //because tick pushes geometry into the cursor, and next performs a single convex hull on it. + throw new GeometryException("Invalid call for non merging convex hull."); + } + + Geometry geometry = m_inputGeoms.next(); + if (geometry != null) { + m_hull.addGeometry(geometry); + return true; + } else { + throw new GeometryException("Expects a non-null geometry."); + } + } + + static Geometry calculateConvexHull_(Geometry geom, ProgressTracker progress_tracker) { + if (geom.isEmpty()) + return geom.createInstance(); + + Geometry.Type type = geom.getType(); + + if (Geometry.isSegment(type.value())) {// Segments are always returned either as a Point or Polyline + Segment segment = (Segment) geom; + if (segment.getStartXY().equals(segment.getEndXY())) { + Point point = new Point(); + segment.queryStart(point); + return point; + } else { + Point pt = new Point(); + Polyline polyline = new Polyline(geom.getDescription()); + segment.queryStart(pt); + polyline.startPath(pt); + segment.queryEnd(pt); + polyline.lineTo(pt); + return polyline; + } + } else if (type == Geometry.Type.Envelope) { + Envelope envelope = (Envelope) geom; + Envelope2D env = new Envelope2D(); + envelope.queryEnvelope2D(env); + if (env.xmin == env.xmax && env.ymin == env.ymax) { + Point point = new Point(); + envelope.queryCornerByVal(0, point); + return point; + } else if (env.xmin == env.xmax || env.ymin == env.ymax) { + Point pt = new Point(); + Polyline polyline = new Polyline(geom.getDescription()); + envelope.queryCornerByVal(0, pt); + polyline.startPath(pt); + envelope.queryCornerByVal(1, pt); + polyline.lineTo(pt); + return polyline; + } else { + Polygon polygon = new Polygon(geom.getDescription()); + polygon.addEnvelope(envelope, false); + return polygon; + } + } + + if (isConvex_(geom, progress_tracker)) { + if (type == Geometry.Type.MultiPoint) {// Downgrade to a Point for simplistic output + MultiPoint multi_point = (MultiPoint) geom; + Point point = new Point(); + multi_point.getPointByVal(0, point); + return point; + } + + return geom; + } + + assert (Geometry.isMultiVertex(type.value())); + + Geometry convex_hull = ConvexHull.construct((MultiVertexGeometry) geom); + return convex_hull; + } + + static boolean isConvex_(Geometry geom, ProgressTracker progress_tracker) { + if (geom.isEmpty()) + return true; // vacuously true + + Geometry.Type type = geom.getType(); + + if (type == Geometry.Type.Point) + return true; // vacuously true + + if (type == Geometry.Type.Envelope) { + Envelope envelope = (Envelope) geom; + if (envelope.getXMin() == envelope.getXMax() || envelope.getYMin() == envelope.getYMax()) + return false; + + return true; + } + + if (MultiPath.isSegment(type.value())) { + Segment segment = (Segment) geom; + if (segment.getStartXY().equals(segment.getEndXY())) + return false; + + return true; // true, but we will upgrade to a Polyline for the ConvexHull operation + } + + if (type == Geometry.Type.MultiPoint) { + MultiPoint multi_point = (MultiPoint) geom; + + if (multi_point.getPointCount() == 1) + return true; // vacuously true, but we will downgrade to a Point for the ConvexHull operation + + return false; + } + + if (type == Geometry.Type.Polyline) { + Polyline polyline = (Polyline) geom; + + if (polyline.getPathCount() == 1 && polyline.getPointCount() == 2) { + if (!polyline.getXY(0).equals(polyline.getXY(1))) + return true; // vacuously true + } + + return false; // create convex hull + } + + Polygon polygon = (Polygon) geom; + + if (polygon.getPathCount() != 1 || polygon.getPointCount() < 3) + return false; + + return ConvexHull.isPathConvex(polygon, 0, progress_tracker); + } } diff --git a/src/main/java/com/esri/core/geometry/OperatorConvexHullLocal.java b/src/main/java/com/esri/core/geometry/OperatorConvexHullLocal.java index 5f02bb62..d9537b79 100644 --- a/src/main/java/com/esri/core/geometry/OperatorConvexHullLocal.java +++ b/src/main/java/com/esri/core/geometry/OperatorConvexHullLocal.java @@ -24,20 +24,20 @@ package com.esri.core.geometry; class OperatorConvexHullLocal extends OperatorConvexHull { - @Override - public GeometryCursor execute(GeometryCursor geoms, boolean b_merge, - ProgressTracker progress_tracker) { - return new OperatorConvexHullCursor(b_merge, geoms, progress_tracker); - } - - @Override - public Geometry execute(Geometry geometry, ProgressTracker progress_tracker) { - return OperatorConvexHullCursor.calculateConvexHull_(geometry, - progress_tracker); - } - - @Override - public boolean isConvex(Geometry geom, ProgressTracker progress_tracker) { - return OperatorConvexHullCursor.isConvex_(geom, progress_tracker); - } + @Override + public GeometryCursor execute(GeometryCursor geoms, boolean b_merge, + ProgressTracker progress_tracker) { + return new OperatorConvexHullCursor(b_merge, geoms, progress_tracker); + } + + @Override + public Geometry execute(Geometry geometry, ProgressTracker progress_tracker) { + return OperatorConvexHullCursor.calculateConvexHull_(geometry, + progress_tracker); + } + + @Override + public boolean isConvex(Geometry geom, ProgressTracker progress_tracker) { + return OperatorConvexHullCursor.isConvex_(geom, progress_tracker); + } } diff --git a/src/main/java/com/esri/core/geometry/OperatorCrosses.java b/src/main/java/com/esri/core/geometry/OperatorCrosses.java index 40bbed50..0780879a 100644 --- a/src/main/java/com/esri/core/geometry/OperatorCrosses.java +++ b/src/main/java/com/esri/core/geometry/OperatorCrosses.java @@ -30,14 +30,14 @@ * Relational operation Crosses. */ public abstract class OperatorCrosses extends OperatorSimpleRelation { - @Override - public Type getType() { - return Type.Crosses; - } - - public static OperatorCrosses local() { - return (OperatorCrosses) OperatorFactoryLocal.getInstance() - .getOperator(Type.Crosses); - } + @Override + public Type getType() { + return Type.Crosses; + } + + public static OperatorCrosses local() { + return (OperatorCrosses) OperatorFactoryLocal.getInstance() + .getOperator(Type.Crosses); + } } diff --git a/src/main/java/com/esri/core/geometry/OperatorCrossesLocal.java b/src/main/java/com/esri/core/geometry/OperatorCrossesLocal.java index b4a7341e..752ab1be 100644 --- a/src/main/java/com/esri/core/geometry/OperatorCrossesLocal.java +++ b/src/main/java/com/esri/core/geometry/OperatorCrossesLocal.java @@ -26,11 +26,11 @@ class OperatorCrossesLocal extends OperatorCrosses { - @Override - public boolean execute(Geometry inputGeom1, Geometry inputGeom2, - SpatialReference sr, ProgressTracker progressTracker) { - return RelationalOperations.relate(inputGeom1, inputGeom2, sr, - RelationalOperations.Relation.crosses, progressTracker); - } + @Override + public boolean execute(Geometry inputGeom1, Geometry inputGeom2, + SpatialReference sr, ProgressTracker progressTracker) { + return RelationalOperations.relate(inputGeom1, inputGeom2, sr, + RelationalOperations.Relation.crosses, progressTracker); + } } diff --git a/src/main/java/com/esri/core/geometry/OperatorCut.java b/src/main/java/com/esri/core/geometry/OperatorCut.java index bf2d73e1..2bb553a5 100644 --- a/src/main/java/com/esri/core/geometry/OperatorCut.java +++ b/src/main/java/com/esri/core/geometry/OperatorCut.java @@ -28,42 +28,42 @@ * Splits the target polyline or polygon where it is crossed by the cutter polyline. */ public abstract class OperatorCut extends Operator { - @Override - public Type getType() { - return Type.Cut; - } + @Override + public Type getType() { + return Type.Cut; + } - /** - * Performs the Cut operation on a geometry. - * - * @param bConsiderTouch Indicates whether we consider a touch event a cut. - * This only applies to polylines, but it's recommended to set this variable to True. - * @param cuttee The input geometry to be cut. - * @param cutter The polyline that will be used to divide the cuttee into - * pieces where it crosses the cutter. - * @return Returns a GeometryCursor of cut geometries. - * All left cuts will be grouped together in the first geometry. Right cuts and - * coincident cuts are grouped in the second geometry, and each undefined cut along - * with any uncut parts are output as separate geometries. If there were no cuts - * the cursor will return no geometry. If the left or right cut does not - * exist, the returned geometry will be empty for this type of cut. An - * undefined cut will only be produced if a left cut or right cut was - * produced and there was a part left over after cutting or a cut is - * bounded to the left and right of the cutter. - */ - public abstract GeometryCursor execute(boolean bConsiderTouch, - Geometry cuttee, - Polyline cutter, - SpatialReference spatialReference, - ProgressTracker progressTracker); + /** + * Performs the Cut operation on a geometry. + * + * @param bConsiderTouch Indicates whether we consider a touch event a cut. + * This only applies to polylines, but it's recommended to set this variable to True. + * @param cuttee The input geometry to be cut. + * @param cutter The polyline that will be used to divide the cuttee into + * pieces where it crosses the cutter. + * @return Returns a GeometryCursor of cut geometries. + * All left cuts will be grouped together in the first geometry. Right cuts and + * coincident cuts are grouped in the second geometry, and each undefined cut along + * with any uncut parts are output as separate geometries. If there were no cuts + * the cursor will return no geometry. If the left or right cut does not + * exist, the returned geometry will be empty for this type of cut. An + * undefined cut will only be produced if a left cut or right cut was + * produced and there was a part left over after cutting or a cut is + * bounded to the left and right of the cutter. + */ + public abstract GeometryCursor execute(boolean bConsiderTouch, + Geometry cuttee, + Polyline cutter, + SpatialReference spatialReference, + ProgressTracker progressTracker); - public abstract GeometryCursor execute(boolean bConsiderTouch, - GeometryCursor cuttees, - Polyline cutter, - SpatialReference spatialReference, - ProgressTracker progressTracker); + public abstract GeometryCursor execute(boolean bConsiderTouch, + GeometryCursor cuttees, + Polyline cutter, + SpatialReference spatialReference, + ProgressTracker progressTracker); - public static OperatorCut local() { - return (OperatorCut) OperatorFactoryLocal.getInstance().getOperator(Type.Cut); - } + public static OperatorCut local() { + return (OperatorCut) OperatorFactoryLocal.getInstance().getOperator(Type.Cut); + } } diff --git a/src/main/java/com/esri/core/geometry/OperatorCutCursor.java b/src/main/java/com/esri/core/geometry/OperatorCutCursor.java index 10f97b02..c9bedffa 100644 --- a/src/main/java/com/esri/core/geometry/OperatorCutCursor.java +++ b/src/main/java/com/esri/core/geometry/OperatorCutCursor.java @@ -31,186 +31,186 @@ import java.util.ArrayList; class OperatorCutCursor extends GeometryCursor { - boolean m_bConsiderTouch; - Geometry m_cuttee; - Polyline m_cutter; - double m_tolerance; - ProgressTracker m_progressTracker; - SpatialReference m_spatialReference; - int m_cutIndex; - ArrayList m_cuts = null; - boolean m_bFirstCall = false; - - OperatorCutCursor(boolean bConsiderTouch, - GeometryCursor cutteeCursor, - Polyline cutter, - SpatialReference spatialReference, - ProgressTracker progressTracker) { - - m_bConsiderTouch = bConsiderTouch; - m_inputGeoms = cutteeCursor; - m_cutter = cutter; - m_spatialReference = spatialReference; - m_cutIndex = -1; - m_progressTracker = progressTracker; - m_bFirstCall = true; - } - - @Override - public boolean hasNext() { - return m_inputGeoms != null && (m_inputGeoms.hasNext() || (m_bFirstCall || m_cutIndex + 1 < m_cuts.size())); - } - - private boolean hasNextRes() { - return m_bFirstCall || m_cutIndex + 1 < m_cuts.size(); - } - - @Override - public Geometry next() { - if (m_bFirstCall || (m_inputGeoms != null && m_inputGeoms.hasNext() && !(m_cutIndex + 1 < m_cuts.size()))) { - m_cuttee = m_inputGeoms.next(); - Envelope2D e = InternalUtils.getMergedExtent(m_cuttee, m_cutter); - m_tolerance = InternalUtils.calculateToleranceFromGeometry(m_spatialReference, e, true); - m_bFirstCall = true; - m_cutIndex = -1; - } - - if (hasNextRes()) { - m_bFirstCall = false; - generateCuts_(); - if (++m_cutIndex < m_cuts.size()) { - return m_cuts.get(m_cutIndex); - } - } - - return null; - } - - private void generateCuts_() { - if (m_cuts != null) - return; - - m_cuts = new ArrayList<>(); - - Geometry.Type type = m_cuttee.getType(); - switch (type.value()) { - case Geometry.GeometryType.Polyline: - generate_polyline_cuts_(); - break; - - case Geometry.GeometryType.Polygon: - generate_polygon_cuts_(); - break; - - default: - break; // warning fix - } - } - - private void generate_polyline_cuts_() { - MultiPath left = new Polyline(); - MultiPath right = new Polyline(); - MultiPath uncut = new Polyline(); - - m_cuts.add(left); - m_cuts.add(right); - - ArrayList cutPairs = new ArrayList( - 0); - Cutter.CutPolyline(m_bConsiderTouch, (Polyline) m_cuttee, m_cutter, - m_tolerance, cutPairs, null, m_progressTracker); - - for (int icut = 0; icut < cutPairs.size(); icut++) { - OperatorCutLocal.CutPair cutPair = cutPairs.get(icut); - if (cutPair.m_side == Side.Left) { - left.add((MultiPath) cutPair.m_geometry, false); - } else if (cutPair.m_side == Side.Right - || cutPair.m_side == Side.Coincident) { - right.add((MultiPath) cutPair.m_geometry, false); - } else if (cutPair.m_side == Side.Undefined) { - m_cuts.add((MultiPath) cutPair.m_geometry); - } else { - uncut.add((MultiPath) cutPair.m_geometry, false); - } - } - - if (!uncut.isEmpty() - && (!left.isEmpty() || !right.isEmpty() || m_cuts.size() >= 3)) - m_cuts.add(uncut); - - if (left.isEmpty() && right.isEmpty() && m_cuts.size() < 3) - m_cuts.clear(); // no cuts - } - - private void generate_polygon_cuts_() { - AttributeStreamOfInt32 cutHandles = new AttributeStreamOfInt32(0); - EditShape shape = new EditShape(); - int sideIndex = shape.createGeometryUserIndex(); - int cutteeHandle = shape.addGeometry(m_cuttee); - int cutterHandle = shape.addGeometry(m_cutter); - TopologicalOperations topoOp = new TopologicalOperations(); - try { - topoOp.setEditShapeCrackAndCluster(shape, m_tolerance, - m_progressTracker); - topoOp.cut(sideIndex, cutteeHandle, cutterHandle, cutHandles); - Polygon cutteeRemainder = (Polygon) shape.getGeometry(cutteeHandle); - - MultiPath left = new Polygon(); - MultiPath right = new Polygon(); - - m_cuts.clear(); - m_cuts.add(left); - m_cuts.add(right); - - for (int icutIndex = 0; icutIndex < cutHandles.size(); icutIndex++) { - Geometry cutGeometry; - { - // intersection - EditShape shapeIntersect = new EditShape(); - int geometryA = shapeIntersect.addGeometry(cutteeRemainder); - int geometryB = shapeIntersect.addGeometry(shape - .getGeometry(cutHandles.get(icutIndex))); - topoOp.setEditShape(shapeIntersect, m_progressTracker); - int intersectHandle = topoOp.intersection(geometryA, - geometryB); - cutGeometry = shapeIntersect.getGeometry(intersectHandle); - - if (cutGeometry.isEmpty()) - continue; - - int side = shape.getGeometryUserIndex( - cutHandles.get(icutIndex), sideIndex); - if (side == 2) - left.add((MultiPath) cutGeometry, false); - else if (side == 1) - right.add((MultiPath) cutGeometry, false); - else - m_cuts.add((MultiPath) cutGeometry); // Undefined - } - - { - // difference - EditShape shapeDifference = new EditShape(); - int geometryA = shapeDifference - .addGeometry(cutteeRemainder); - int geometryB = shapeDifference.addGeometry(shape - .getGeometry(cutHandles.get(icutIndex))); - topoOp.setEditShape(shapeDifference, m_progressTracker); - cutteeRemainder = (Polygon) shapeDifference - .getGeometry(topoOp - .difference(geometryA, geometryB)); - } - } - - if (!cutteeRemainder.isEmpty() && cutHandles.size() > 0) - m_cuts.add((MultiPath) cutteeRemainder); - - if (left.isEmpty() && right.isEmpty()) - m_cuts.clear(); // no cuts - - } finally { - topoOp.removeShape(); - } - } + boolean m_bConsiderTouch; + Geometry m_cuttee; + Polyline m_cutter; + double m_tolerance; + ProgressTracker m_progressTracker; + SpatialReference m_spatialReference; + int m_cutIndex; + ArrayList m_cuts = null; + boolean m_bFirstCall = false; + + OperatorCutCursor(boolean bConsiderTouch, + GeometryCursor cutteeCursor, + Polyline cutter, + SpatialReference spatialReference, + ProgressTracker progressTracker) { + + m_bConsiderTouch = bConsiderTouch; + m_inputGeoms = cutteeCursor; + m_cutter = cutter; + m_spatialReference = spatialReference; + m_cutIndex = -1; + m_progressTracker = progressTracker; + m_bFirstCall = true; + } + + @Override + public boolean hasNext() { + return m_inputGeoms != null && (m_inputGeoms.hasNext() || (m_bFirstCall || m_cutIndex + 1 < m_cuts.size())); + } + + private boolean hasNextRes() { + return m_bFirstCall || m_cutIndex + 1 < m_cuts.size(); + } + + @Override + public Geometry next() { + if (m_bFirstCall || (m_inputGeoms != null && m_inputGeoms.hasNext() && !(m_cutIndex + 1 < m_cuts.size()))) { + m_cuttee = m_inputGeoms.next(); + Envelope2D e = InternalUtils.getMergedExtent(m_cuttee, m_cutter); + m_tolerance = InternalUtils.calculateToleranceFromGeometry(m_spatialReference, e, true); + m_bFirstCall = true; + m_cutIndex = -1; + } + + if (hasNextRes()) { + m_bFirstCall = false; + generateCuts_(); + if (++m_cutIndex < m_cuts.size()) { + return m_cuts.get(m_cutIndex); + } + } + + return null; + } + + private void generateCuts_() { + if (m_cuts != null) + return; + + m_cuts = new ArrayList<>(); + + Geometry.Type type = m_cuttee.getType(); + switch (type.value()) { + case Geometry.GeometryType.Polyline: + generate_polyline_cuts_(); + break; + + case Geometry.GeometryType.Polygon: + generate_polygon_cuts_(); + break; + + default: + break; // warning fix + } + } + + private void generate_polyline_cuts_() { + MultiPath left = new Polyline(); + MultiPath right = new Polyline(); + MultiPath uncut = new Polyline(); + + m_cuts.add(left); + m_cuts.add(right); + + ArrayList cutPairs = new ArrayList( + 0); + Cutter.CutPolyline(m_bConsiderTouch, (Polyline) m_cuttee, m_cutter, + m_tolerance, cutPairs, null, m_progressTracker); + + for (int icut = 0; icut < cutPairs.size(); icut++) { + OperatorCutLocal.CutPair cutPair = cutPairs.get(icut); + if (cutPair.m_side == Side.Left) { + left.add((MultiPath) cutPair.m_geometry, false); + } else if (cutPair.m_side == Side.Right + || cutPair.m_side == Side.Coincident) { + right.add((MultiPath) cutPair.m_geometry, false); + } else if (cutPair.m_side == Side.Undefined) { + m_cuts.add((MultiPath) cutPair.m_geometry); + } else { + uncut.add((MultiPath) cutPair.m_geometry, false); + } + } + + if (!uncut.isEmpty() + && (!left.isEmpty() || !right.isEmpty() || m_cuts.size() >= 3)) + m_cuts.add(uncut); + + if (left.isEmpty() && right.isEmpty() && m_cuts.size() < 3) + m_cuts.clear(); // no cuts + } + + private void generate_polygon_cuts_() { + AttributeStreamOfInt32 cutHandles = new AttributeStreamOfInt32(0); + EditShape shape = new EditShape(); + int sideIndex = shape.createGeometryUserIndex(); + int cutteeHandle = shape.addGeometry(m_cuttee); + int cutterHandle = shape.addGeometry(m_cutter); + TopologicalOperations topoOp = new TopologicalOperations(); + try { + topoOp.setEditShapeCrackAndCluster(shape, m_tolerance, + m_progressTracker); + topoOp.cut(sideIndex, cutteeHandle, cutterHandle, cutHandles); + Polygon cutteeRemainder = (Polygon) shape.getGeometry(cutteeHandle); + + MultiPath left = new Polygon(); + MultiPath right = new Polygon(); + + m_cuts.clear(); + m_cuts.add(left); + m_cuts.add(right); + + for (int icutIndex = 0; icutIndex < cutHandles.size(); icutIndex++) { + Geometry cutGeometry; + { + // intersection + EditShape shapeIntersect = new EditShape(); + int geometryA = shapeIntersect.addGeometry(cutteeRemainder); + int geometryB = shapeIntersect.addGeometry(shape + .getGeometry(cutHandles.get(icutIndex))); + topoOp.setEditShape(shapeIntersect, m_progressTracker); + int intersectHandle = topoOp.intersection(geometryA, + geometryB); + cutGeometry = shapeIntersect.getGeometry(intersectHandle); + + if (cutGeometry.isEmpty()) + continue; + + int side = shape.getGeometryUserIndex( + cutHandles.get(icutIndex), sideIndex); + if (side == 2) + left.add((MultiPath) cutGeometry, false); + else if (side == 1) + right.add((MultiPath) cutGeometry, false); + else + m_cuts.add((MultiPath) cutGeometry); // Undefined + } + + { + // difference + EditShape shapeDifference = new EditShape(); + int geometryA = shapeDifference + .addGeometry(cutteeRemainder); + int geometryB = shapeDifference.addGeometry(shape + .getGeometry(cutHandles.get(icutIndex))); + topoOp.setEditShape(shapeDifference, m_progressTracker); + cutteeRemainder = (Polygon) shapeDifference + .getGeometry(topoOp + .difference(geometryA, geometryB)); + } + } + + if (!cutteeRemainder.isEmpty() && cutHandles.size() > 0) + m_cuts.add((MultiPath) cutteeRemainder); + + if (left.isEmpty() && right.isEmpty()) + m_cuts.clear(); // no cuts + + } finally { + topoOp.removeShape(); + } + } } diff --git a/src/main/java/com/esri/core/geometry/OperatorCutLocal.java b/src/main/java/com/esri/core/geometry/OperatorCutLocal.java index 374780cd..4ce3795a 100644 --- a/src/main/java/com/esri/core/geometry/OperatorCutLocal.java +++ b/src/main/java/com/esri/core/geometry/OperatorCutLocal.java @@ -25,69 +25,69 @@ package com.esri.core.geometry; class OperatorCutLocal extends OperatorCut { - public static interface Side { - public static final int Left = 0; - public static final int Right = 1; - public static final int Coincident = 2; - public static final int Undefined = 3; - public static final int Uncut = 4; - } + public static interface Side { + public static final int Left = 0; + public static final int Right = 1; + public static final int Coincident = 2; + public static final int Undefined = 3; + public static final int Uncut = 4; + } - public static class CutPair { - public CutPair(Geometry geometry, int side, int ipartCuttee, - int ivertexCuttee, double scalarCuttee, int sidePrev, - int ipartCutteePrev, int ivertexCutteePrev, - double scalarCutteePrev, int ipartCutter, int ivertexCutter, - double scalarCutter, int ipartCutterPrev, - int ivertexCutterPrev, double scalarCutterPrev) { - m_geometry = geometry; - m_side = side; - m_ipartCuttee = ipartCuttee; - m_ivertexCuttee = ivertexCuttee; - m_scalarCuttee = scalarCuttee; - m_sidePrev = sidePrev; - m_ipartCutteePrev = ipartCutteePrev; - m_ivertexCutteePrev = ivertexCutteePrev; - m_scalarCutteePrev = scalarCutteePrev; - m_ipartCutter = ipartCutter; - m_ivertexCutter = ivertexCutter; - m_scalarCutter = scalarCutter; - m_ipartCutterPrev = ipartCutterPrev; - m_ivertexCutterPrev = ivertexCutterPrev; - m_scalarCutterPrev = scalarCutterPrev; - } + public static class CutPair { + public CutPair(Geometry geometry, int side, int ipartCuttee, + int ivertexCuttee, double scalarCuttee, int sidePrev, + int ipartCutteePrev, int ivertexCutteePrev, + double scalarCutteePrev, int ipartCutter, int ivertexCutter, + double scalarCutter, int ipartCutterPrev, + int ivertexCutterPrev, double scalarCutterPrev) { + m_geometry = geometry; + m_side = side; + m_ipartCuttee = ipartCuttee; + m_ivertexCuttee = ivertexCuttee; + m_scalarCuttee = scalarCuttee; + m_sidePrev = sidePrev; + m_ipartCutteePrev = ipartCutteePrev; + m_ivertexCutteePrev = ivertexCutteePrev; + m_scalarCutteePrev = scalarCutteePrev; + m_ipartCutter = ipartCutter; + m_ivertexCutter = ivertexCutter; + m_scalarCutter = scalarCutter; + m_ipartCutterPrev = ipartCutterPrev; + m_ivertexCutterPrev = ivertexCutterPrev; + m_scalarCutterPrev = scalarCutterPrev; + } - Geometry m_geometry; - int m_side; - int m_ipartCuttee; - int m_ivertexCuttee; - double m_scalarCuttee; - int m_sidePrev; - int m_ipartCutteePrev; - int m_ivertexCutteePrev; - double m_scalarCutteePrev; - int m_ipartCutter; - int m_ivertexCutter; - double m_scalarCutter; - int m_ipartCutterPrev; - int m_ivertexCutterPrev; - double m_scalarCutterPrev; - } + Geometry m_geometry; + int m_side; + int m_ipartCuttee; + int m_ivertexCuttee; + double m_scalarCuttee; + int m_sidePrev; + int m_ipartCutteePrev; + int m_ivertexCutteePrev; + double m_scalarCutteePrev; + int m_ipartCutter; + int m_ivertexCutter; + double m_scalarCutter; + int m_ipartCutterPrev; + int m_ivertexCutterPrev; + double m_scalarCutterPrev; + } - ; + ; - @Override - public GeometryCursor execute(boolean bConsiderTouch, Geometry cuttee, - Polyline cutter, SpatialReference spatialReference, - ProgressTracker progressTracker) { - SimpleGeometryCursor simpleGeometryCursor = new SimpleGeometryCursor(cuttee); - return new OperatorCutCursor(bConsiderTouch, simpleGeometryCursor, cutter, - spatialReference, progressTracker); - } + @Override + public GeometryCursor execute(boolean bConsiderTouch, Geometry cuttee, + Polyline cutter, SpatialReference spatialReference, + ProgressTracker progressTracker) { + SimpleGeometryCursor simpleGeometryCursor = new SimpleGeometryCursor(cuttee); + return new OperatorCutCursor(bConsiderTouch, simpleGeometryCursor, cutter, + spatialReference, progressTracker); + } - @Override - public GeometryCursor execute(boolean bConsiderTouch, GeometryCursor cuttees, Polyline cutter, SpatialReference spatialReference, ProgressTracker progressTracker) { - return new OperatorCutCursor(bConsiderTouch, cuttees, cutter, - spatialReference, progressTracker); - } + @Override + public GeometryCursor execute(boolean bConsiderTouch, GeometryCursor cuttees, Polyline cutter, SpatialReference spatialReference, ProgressTracker progressTracker) { + return new OperatorCutCursor(bConsiderTouch, cuttees, cutter, + spatialReference, progressTracker); + } } diff --git a/src/main/java/com/esri/core/geometry/OperatorDensifyByLength.java b/src/main/java/com/esri/core/geometry/OperatorDensifyByLength.java index a64111f2..ff37e27c 100644 --- a/src/main/java/com/esri/core/geometry/OperatorDensifyByLength.java +++ b/src/main/java/com/esri/core/geometry/OperatorDensifyByLength.java @@ -31,50 +31,46 @@ * given threshold value. */ public abstract class OperatorDensifyByLength extends Operator { - @Override - public Type getType() { - return Type.DensifyByLength; - } + @Override + public Type getType() { + return Type.DensifyByLength; + } /** * Performs the Densify operation on the geometry set. - * - * @param inputGeometries - * The geometries to be densified. - * @param maxLength - * The maximum segment length allowed. Must be a positive value. - * Curves are densified to straight segments using the - * maxSegmentLength. Curves are split into shorter subcurves such - * that the length of subcurves is shorter than maxSegmentLength. - * After that the curves are replaced with straight segments. + * + * @param inputGeometries The geometries to be densified. + * @param maxLength The maximum segment length allowed. Must be a positive value. + * Curves are densified to straight segments using the + * maxSegmentLength. Curves are split into shorter subcurves such + * that the length of subcurves is shorter than maxSegmentLength. + * After that the curves are replaced with straight segments. * @param progressTracker * @return Returns the densified geometries (It does nothing to geometries - * with dim < 1, but simply passes them along). + * with dim < 1, but simply passes them along). */ public abstract GeometryCursor execute(GeometryCursor inputGeometries, - double maxLength, ProgressTracker progressTracker); + double maxLength, ProgressTracker progressTracker); - /** - * Performs the Densify operation on the geometry set. - * - * @param inputGeometry - * The geometry to be densified. - * @param maxLength - * The maximum segment length allowed. Must be a positive value. - * Curves are densified to straight segments using the - * maxSegmentLength. Curves are split into shorter subcurves such - * that the length of subcurves is shorter than maxSegmentLength. - * After that the curves are replaced with straight segments. - * @param progressTracker - * @return Returns the densified geometry. (It does nothing to geometries - * with dim < 1, but simply passes them along). - */ - public abstract Geometry execute(Geometry inputGeometry, double maxLength, - ProgressTracker progressTracker); + /** + * Performs the Densify operation on the geometry set. + * + * @param inputGeometry The geometry to be densified. + * @param maxLength The maximum segment length allowed. Must be a positive value. + * Curves are densified to straight segments using the + * maxSegmentLength. Curves are split into shorter subcurves such + * that the length of subcurves is shorter than maxSegmentLength. + * After that the curves are replaced with straight segments. + * @param progressTracker + * @return Returns the densified geometry. (It does nothing to geometries + * with dim < 1, but simply passes them along). + */ + public abstract Geometry execute(Geometry inputGeometry, double maxLength, + ProgressTracker progressTracker); - public static OperatorDensifyByLength local() { - return (OperatorDensifyByLength) OperatorFactoryLocal.getInstance() - .getOperator(Type.DensifyByLength); - } + public static OperatorDensifyByLength local() { + return (OperatorDensifyByLength) OperatorFactoryLocal.getInstance() + .getOperator(Type.DensifyByLength); + } } diff --git a/src/main/java/com/esri/core/geometry/OperatorDensifyByLengthCursor.java b/src/main/java/com/esri/core/geometry/OperatorDensifyByLengthCursor.java index e70439bd..22a1e151 100644 --- a/src/main/java/com/esri/core/geometry/OperatorDensifyByLengthCursor.java +++ b/src/main/java/com/esri/core/geometry/OperatorDensifyByLengthCursor.java @@ -25,124 +25,124 @@ package com.esri.core.geometry; class OperatorDensifyByLengthCursor extends GeometryCursor { - double m_maxLength; - - public OperatorDensifyByLengthCursor(GeometryCursor inputGeoms, - double maxLength, ProgressTracker progressTracker) { - m_inputGeoms = inputGeoms; - m_maxLength = maxLength; - } - - @Override - public Geometry next() { - if (hasNext()) { - return densifyByLength(m_inputGeoms.next()); - } - return null; - } - - private Geometry densifyByLength(Geometry geom) { - if (geom.isEmpty() || geom.getDimension() < 1) - return geom; - - int geometryType = geom.getType().value(); - - // TODO implement IsMultiPath and remove Polygon and Polyline call to - // match Native - // if (Geometry.IsMultiPath(geometryType)) - if (geometryType == Geometry.GeometryType.Polygon) - return densifyMultiPath((MultiPath) geom); - else if (Geometry.GeometryType.Polyline == geometryType) - return densifyMultiPath((MultiPath) geom); - else if (Geometry.isSegment(geometryType)) - return densifySegment((Segment) geom); - else if (geometryType == Geometry.GeometryType.Envelope) - return densifyEnvelope((Envelope) geom); - else - // TODO fix geometry exception to match native implementation - throw GeometryException.GeometryInternalError();// GEOMTHROW(internal_error); - - // unreachable in java - // return null; - } - - private Geometry densifySegment(Segment geom) { - double length = geom.calculateLength2D(); - if (length <= m_maxLength) - return (Geometry) geom; - - Polyline polyline = new Polyline(geom.getDescription()); - polyline.addSegment(geom, true); - return densifyMultiPath((MultiPath) polyline); - } - - private Geometry densifyEnvelope(Envelope geom) { - Polygon polygon = new Polygon(geom.getDescription()); - polygon.addEnvelope(geom, false); - - Envelope2D env2D = new Envelope2D(); - geom.queryEnvelope2D(env2D); - double w = env2D.getWidth(); - double h = env2D.getHeight(); - if (w <= m_maxLength && h <= m_maxLength) - return (Geometry) polygon; - - return densifyMultiPath((MultiPath) polygon); - } - - private Geometry densifyMultiPath(MultiPath geom) { - MultiPath densifiedPoly = (MultiPath) geom.createInstance(); - SegmentIterator iter = geom.querySegmentIterator(); - while (iter.nextPath()) { - boolean bStartNewPath = true; - while (iter.hasNextSegment()) { - Segment seg = iter.nextSegment(); - if (seg.getType().value() != Geometry.GeometryType.Line) - throw new GeometryException("not implemented"); - - boolean bIsClosing = iter.isClosingSegment(); - - double len = seg.calculateLength2D(); - if (len > m_maxLength) {// need to split - double dcount = Math.ceil(len / m_maxLength); - - Point point = new Point(geom.getDescription());// LOCALREFCLASS1(Point, - // VertexDescription, - // point, - // geom.getDescription()); - if (bStartNewPath) { - bStartNewPath = false; - seg.queryStart(point); - densifiedPoly.startPath(point); - } - double dt = 1.0 / dcount; - double t = dt; - for (int i = 0, n = (int) dcount - 1; i < n; i++) { - seg.queryCoord(t, point); - densifiedPoly.lineTo(point); - t += dt; - } - - if (!bIsClosing) { - seg.queryEnd(point); - densifiedPoly.lineTo(point); - } else { - densifiedPoly.closePathWithLine(); - } - - bStartNewPath = false; - } else { - if (!bIsClosing) - densifiedPoly.addSegment(seg, bStartNewPath); - else - densifiedPoly.closePathWithLine(); - - bStartNewPath = false; - } - } - } - - return densifiedPoly; - } + double m_maxLength; + + public OperatorDensifyByLengthCursor(GeometryCursor inputGeoms, + double maxLength, ProgressTracker progressTracker) { + m_inputGeoms = inputGeoms; + m_maxLength = maxLength; + } + + @Override + public Geometry next() { + if (hasNext()) { + return densifyByLength(m_inputGeoms.next()); + } + return null; + } + + private Geometry densifyByLength(Geometry geom) { + if (geom.isEmpty() || geom.getDimension() < 1) + return geom; + + int geometryType = geom.getType().value(); + + // TODO implement IsMultiPath and remove Polygon and Polyline call to + // match Native + // if (Geometry.IsMultiPath(geometryType)) + if (geometryType == Geometry.GeometryType.Polygon) + return densifyMultiPath((MultiPath) geom); + else if (Geometry.GeometryType.Polyline == geometryType) + return densifyMultiPath((MultiPath) geom); + else if (Geometry.isSegment(geometryType)) + return densifySegment((Segment) geom); + else if (geometryType == Geometry.GeometryType.Envelope) + return densifyEnvelope((Envelope) geom); + else + // TODO fix geometry exception to match native implementation + throw GeometryException.GeometryInternalError();// GEOMTHROW(internal_error); + + // unreachable in java + // return null; + } + + private Geometry densifySegment(Segment geom) { + double length = geom.calculateLength2D(); + if (length <= m_maxLength) + return (Geometry) geom; + + Polyline polyline = new Polyline(geom.getDescription()); + polyline.addSegment(geom, true); + return densifyMultiPath((MultiPath) polyline); + } + + private Geometry densifyEnvelope(Envelope geom) { + Polygon polygon = new Polygon(geom.getDescription()); + polygon.addEnvelope(geom, false); + + Envelope2D env2D = new Envelope2D(); + geom.queryEnvelope2D(env2D); + double w = env2D.getWidth(); + double h = env2D.getHeight(); + if (w <= m_maxLength && h <= m_maxLength) + return (Geometry) polygon; + + return densifyMultiPath((MultiPath) polygon); + } + + private Geometry densifyMultiPath(MultiPath geom) { + MultiPath densifiedPoly = (MultiPath) geom.createInstance(); + SegmentIterator iter = geom.querySegmentIterator(); + while (iter.nextPath()) { + boolean bStartNewPath = true; + while (iter.hasNextSegment()) { + Segment seg = iter.nextSegment(); + if (seg.getType().value() != Geometry.GeometryType.Line) + throw new GeometryException("not implemented"); + + boolean bIsClosing = iter.isClosingSegment(); + + double len = seg.calculateLength2D(); + if (len > m_maxLength) {// need to split + double dcount = Math.ceil(len / m_maxLength); + + Point point = new Point(geom.getDescription());// LOCALREFCLASS1(Point, + // VertexDescription, + // point, + // geom.getDescription()); + if (bStartNewPath) { + bStartNewPath = false; + seg.queryStart(point); + densifiedPoly.startPath(point); + } + double dt = 1.0 / dcount; + double t = dt; + for (int i = 0, n = (int) dcount - 1; i < n; i++) { + seg.queryCoord(t, point); + densifiedPoly.lineTo(point); + t += dt; + } + + if (!bIsClosing) { + seg.queryEnd(point); + densifiedPoly.lineTo(point); + } else { + densifiedPoly.closePathWithLine(); + } + + bStartNewPath = false; + } else { + if (!bIsClosing) + densifiedPoly.addSegment(seg, bStartNewPath); + else + densifiedPoly.closePathWithLine(); + + bStartNewPath = false; + } + } + } + + return densifiedPoly; + } } diff --git a/src/main/java/com/esri/core/geometry/OperatorDensifyByLengthLocal.java b/src/main/java/com/esri/core/geometry/OperatorDensifyByLengthLocal.java index 083edfd0..22350f6b 100644 --- a/src/main/java/com/esri/core/geometry/OperatorDensifyByLengthLocal.java +++ b/src/main/java/com/esri/core/geometry/OperatorDensifyByLengthLocal.java @@ -25,26 +25,26 @@ class OperatorDensifyByLengthLocal extends OperatorDensifyByLength { - @Override - public GeometryCursor execute( - GeometryCursor inputGeometries, - double maxLength, - ProgressTracker progressTracker) { - if (maxLength <= 0) - // TODO fix geometry exception to match native implementation - throw new IllegalArgumentException();// GEOMTHROW(invalid_argument); - - return new OperatorDensifyByLengthCursor(inputGeometries, maxLength, - progressTracker); - } - - @Override - public Geometry execute(Geometry inputGeometry, double maxLength, - ProgressTracker progressTracker) { - SimpleGeometryCursor inputCursor = new SimpleGeometryCursor( - inputGeometry); - GeometryCursor outputCursor = execute(inputCursor, maxLength, - progressTracker); - return outputCursor.next(); - } + @Override + public GeometryCursor execute( + GeometryCursor inputGeometries, + double maxLength, + ProgressTracker progressTracker) { + if (maxLength <= 0) + // TODO fix geometry exception to match native implementation + throw new IllegalArgumentException();// GEOMTHROW(invalid_argument); + + return new OperatorDensifyByLengthCursor(inputGeometries, maxLength, + progressTracker); + } + + @Override + public Geometry execute(Geometry inputGeometry, double maxLength, + ProgressTracker progressTracker) { + SimpleGeometryCursor inputCursor = new SimpleGeometryCursor( + inputGeometry); + GeometryCursor outputCursor = execute(inputCursor, maxLength, + progressTracker); + return outputCursor.next(); + } } diff --git a/src/main/java/com/esri/core/geometry/OperatorDifference.java b/src/main/java/com/esri/core/geometry/OperatorDifference.java index ce71cb72..e22fb2a8 100644 --- a/src/main/java/com/esri/core/geometry/OperatorDifference.java +++ b/src/main/java/com/esri/core/geometry/OperatorDifference.java @@ -31,41 +31,41 @@ */ public abstract class OperatorDifference extends Operator implements CombineOperator { - @Override - public Type getType() { - return Type.Difference; - } + @Override + public Type getType() { + return Type.Difference; + } - /** - * Performs the Topological Difference operation on the geometry set. - * - * @param inputGeometries is the set of Geometry instances to be subtracted by the - * subtractor - * @param subtractor is the Geometry being subtracted. - * @return Returns the result of the subtraction. - *

- * The operator subtracts subtractor from every geometry in - * inputGeometries. - */ - public abstract GeometryCursor execute(GeometryCursor inputGeometries, - GeometryCursor subtractor, SpatialReference sr, - ProgressTracker progressTracker); + /** + * Performs the Topological Difference operation on the geometry set. + * + * @param inputGeometries is the set of Geometry instances to be subtracted by the + * subtractor + * @param subtractor is the Geometry being subtracted. + * @return Returns the result of the subtraction. + *

+ * The operator subtracts subtractor from every geometry in + * inputGeometries. + */ + public abstract GeometryCursor execute(GeometryCursor inputGeometries, + GeometryCursor subtractor, SpatialReference sr, + ProgressTracker progressTracker); - /** - * Performs the Topological Difference operation on the two geometries. - * - * @param inputGeometry is the Geometry instance on the left hand side of the - * subtraction. - * @param subtractor is the Geometry on the right hand side being subtracted. - * @return Returns the result of subtraction. - */ - public abstract Geometry execute(Geometry inputGeometry, - Geometry subtractor, SpatialReference sr, - ProgressTracker progressTracker); + /** + * Performs the Topological Difference operation on the two geometries. + * + * @param inputGeometry is the Geometry instance on the left hand side of the + * subtraction. + * @param subtractor is the Geometry on the right hand side being subtracted. + * @return Returns the result of subtraction. + */ + public abstract Geometry execute(Geometry inputGeometry, + Geometry subtractor, SpatialReference sr, + ProgressTracker progressTracker); - public static OperatorDifference local() { - return (OperatorDifference) OperatorFactoryLocal.getInstance() - .getOperator(Type.Difference); - } + public static OperatorDifference local() { + return (OperatorDifference) OperatorFactoryLocal.getInstance() + .getOperator(Type.Difference); + } } diff --git a/src/main/java/com/esri/core/geometry/OperatorDifferenceCursor.java b/src/main/java/com/esri/core/geometry/OperatorDifferenceCursor.java index a71dffbc..a75bb6dd 100644 --- a/src/main/java/com/esri/core/geometry/OperatorDifferenceCursor.java +++ b/src/main/java/com/esri/core/geometry/OperatorDifferenceCursor.java @@ -25,33 +25,33 @@ package com.esri.core.geometry; class OperatorDifferenceCursor extends GeometryCursor { - ProgressTracker m_progress_tracker; - SpatialReference m_Spatial_reference; - Geometry m_geomSubtractor; - boolean m_bEmpty; - - OperatorDifferenceCursor(GeometryCursor inputGeoms, - GeometryCursor geomSubtractor, SpatialReference sr, - ProgressTracker progress_tracker) { - m_bEmpty = (geomSubtractor == null); - m_inputGeoms = inputGeoms; - m_Spatial_reference = sr; - m_geomSubtractor = geomSubtractor.next(); - m_progress_tracker = progress_tracker; - } - - @Override - public Geometry next() { - if (m_bEmpty) - return null; - - if (hasNext()) { - return OperatorDifferenceLocal.difference( - m_inputGeoms.next(), - m_geomSubtractor, - m_Spatial_reference, - m_progress_tracker); - } - return null; - } + ProgressTracker m_progress_tracker; + SpatialReference m_Spatial_reference; + Geometry m_geomSubtractor; + boolean m_bEmpty; + + OperatorDifferenceCursor(GeometryCursor inputGeoms, + GeometryCursor geomSubtractor, SpatialReference sr, + ProgressTracker progress_tracker) { + m_bEmpty = (geomSubtractor == null); + m_inputGeoms = inputGeoms; + m_Spatial_reference = sr; + m_geomSubtractor = geomSubtractor.next(); + m_progress_tracker = progress_tracker; + } + + @Override + public Geometry next() { + if (m_bEmpty) + return null; + + if (hasNext()) { + return OperatorDifferenceLocal.difference( + m_inputGeoms.next(), + m_geomSubtractor, + m_Spatial_reference, + m_progress_tracker); + } + return null; + } } diff --git a/src/main/java/com/esri/core/geometry/OperatorDifferenceLocal.java b/src/main/java/com/esri/core/geometry/OperatorDifferenceLocal.java index cd1b3b3f..6e39ef4a 100644 --- a/src/main/java/com/esri/core/geometry/OperatorDifferenceLocal.java +++ b/src/main/java/com/esri/core/geometry/OperatorDifferenceLocal.java @@ -26,367 +26,367 @@ class OperatorDifferenceLocal extends OperatorDifference { - @Override - public GeometryCursor execute(GeometryCursor inputGeometries, - GeometryCursor subtractor, SpatialReference sr, - ProgressTracker progressTracker) { - return new OperatorDifferenceCursor(inputGeometries, subtractor, sr, - progressTracker); - } - - @Override - public Geometry execute(Geometry inputGeometry, Geometry subtractor, - SpatialReference sr, ProgressTracker progressTracker) { - SimpleGeometryCursor inputGeomCurs = new SimpleGeometryCursor( - inputGeometry); - SimpleGeometryCursor subractorCurs = new SimpleGeometryCursor( - subtractor); - GeometryCursor geometryCursor = execute(inputGeomCurs, subractorCurs, - sr, progressTracker); - - return geometryCursor.next(); - } - - static Geometry difference(Geometry geometry_a, Geometry geometry_b, - SpatialReference spatial_reference, ProgressTracker progress_tracker) { - if (geometry_a.isEmpty() || geometry_b.isEmpty()) - return geometry_a; - - int dimension_a = geometry_a.getDimension(); - int dimension_b = geometry_b.getDimension(); - - if (dimension_a > dimension_b) - return geometry_a; - - int type_a = geometry_a.getType().value(); - int type_b = geometry_b.getType().value(); - - Envelope2D env_a = new Envelope2D(), env_b = new Envelope2D(), env_merged = new Envelope2D(); - geometry_a.queryEnvelope2D(env_a); - geometry_b.queryEnvelope2D(env_b); - env_merged.setCoords(env_a); - env_merged.merge(env_b); - - double tolerance = InternalUtils.calculateToleranceFromGeometry( - spatial_reference, env_merged, false); - double tolerance_cluster = tolerance * Math.sqrt(2.0) * 1.00001; - - Envelope2D env_a_inflated = new Envelope2D(); - env_a_inflated.setCoords(env_a); - env_a_inflated.inflate(tolerance_cluster, tolerance_cluster); // inflate - // by - // cluster - // tolerance - - if (!env_a_inflated.isIntersecting(env_b)) - return geometry_a; - - if (dimension_a == 1 && dimension_b == 2) - return polylineMinusArea_(geometry_a, geometry_b, type_b, - spatial_reference, progress_tracker); - - if (type_a == Geometry.GeometryType.Point) { - Geometry geometry_b_; - if (MultiPath.isSegment(type_b)) { - geometry_b_ = new Polyline(geometry_b.getDescription()); - ((Polyline) (geometry_b_)).addSegment((Segment) (geometry_b), - true); - } else { - geometry_b_ = geometry_b; - } - switch (type_b) { - case Geometry.GeometryType.Polygon: - return pointMinusPolygon_((Point) (geometry_a), - (Polygon) (geometry_b_), tolerance, progress_tracker); - case Geometry.GeometryType.Polyline: - return pointMinusPolyline_((Point) (geometry_a), - (Polyline) (geometry_b_), tolerance, progress_tracker); - case Geometry.GeometryType.MultiPoint: - return pointMinusMultiPoint_((Point) (geometry_a), - (MultiPoint) (geometry_b_), tolerance, progress_tracker); - case Geometry.GeometryType.Envelope: - return pointMinusEnvelope_((Point) (geometry_a), - (Envelope) (geometry_b_), tolerance, progress_tracker); - case Geometry.GeometryType.Point: - return pointMinusPoint_((Point) (geometry_a), - (Point) (geometry_b_), tolerance, progress_tracker); - default: - throw new IllegalArgumentException(); - } - } else if (type_a == Geometry.GeometryType.MultiPoint) { - switch (type_b) { - case Geometry.GeometryType.Polygon: - return multiPointMinusPolygon_((MultiPoint) (geometry_a), - (Polygon) (geometry_b), tolerance, progress_tracker); - case Geometry.GeometryType.Envelope: - return multiPointMinusEnvelope_((MultiPoint) (geometry_a), - (Envelope) (geometry_b), tolerance, progress_tracker); - case Geometry.GeometryType.Point: - return multiPointMinusPoint_((MultiPoint) (geometry_a), - (Point) (geometry_b), tolerance, progress_tracker); - default: - break; - } - } - return TopologicalOperations.difference(geometry_a, geometry_b, - spatial_reference, progress_tracker); - } - - // these are special implementations, all others delegate to the topo-graph. - static Geometry pointMinusPolygon_(Point point, Polygon polygon, - double tolerance, ProgressTracker progress_tracker) { - PolygonUtils.PiPResult result = PolygonUtils.isPointInPolygon2D( - polygon, point, tolerance); - - if (result == PolygonUtils.PiPResult.PiPOutside) - return point; - - return point.createInstance(); - } - - static Geometry pointMinusPolyline_(Point point, Polyline polyline, - double tolerance, ProgressTracker progress_tracker) { - Point2D pt = point.getXY(); - SegmentIterator seg_iter = polyline.querySegmentIterator(); - - double tolerance_cluster = tolerance * Math.sqrt(2.0) * 1.00001; - double tolerance_cluster_sq = tolerance_cluster * tolerance_cluster; - Envelope2D env = new Envelope2D(); - - while (seg_iter.nextPath()) { - while (seg_iter.hasNextSegment()) { - Segment segment = seg_iter.nextSegment(); - - segment.queryEnvelope2D(env); - env.inflate(tolerance_cluster, tolerance_cluster); - - if (!env.contains(pt)) - continue; - - if (segment.isIntersecting(pt, tolerance)) - return point.createInstance(); - - // check segment end points to the cluster tolerance - Point2D end_point = segment.getStartXY(); - - if (Point2D.sqrDistance(pt, end_point) <= tolerance_cluster_sq) - return point.createInstance(); - - end_point = segment.getEndXY(); - - if (Point2D.sqrDistance(pt, end_point) <= tolerance_cluster_sq) - return point.createInstance(); - } - } - - return point; - } - - static Geometry pointMinusMultiPoint_(Point point, MultiPoint multi_point, - double tolerance, ProgressTracker progress_tracker) { - MultiPointImpl multipointImpl = (MultiPointImpl) (multi_point - ._getImpl()); - AttributeStreamOfDbl position = (AttributeStreamOfDbl) multipointImpl - .getAttributeStreamRef(VertexDescription.Semantics.POSITION); - int point_count = multi_point.getPointCount(); - Point2D point2D = point.getXY(); - Point2D pt = new Point2D(); - - double tolerance_cluster = tolerance * Math.sqrt(2.0) * 1.00001; - double tolerance_cluster_sq = tolerance_cluster * tolerance_cluster; - - for (int i = 0; i < point_count; i++) { - position.read(2 * i, pt); - double sqr_dist = Point2D.sqrDistance(pt, point2D); - if (sqr_dist <= tolerance_cluster_sq) - return point.createInstance();// return an empty point. - } - - return point;// return the input point - } - - static Geometry pointMinusEnvelope_(Point point, Envelope envelope, - double tolerance, ProgressTracker progress_tracker) { - Envelope2D env = new Envelope2D(); - envelope.queryEnvelope2D(env); - env.inflate(tolerance, tolerance); - - Point2D pt = point.getXY(); + @Override + public GeometryCursor execute(GeometryCursor inputGeometries, + GeometryCursor subtractor, SpatialReference sr, + ProgressTracker progressTracker) { + return new OperatorDifferenceCursor(inputGeometries, subtractor, sr, + progressTracker); + } + + @Override + public Geometry execute(Geometry inputGeometry, Geometry subtractor, + SpatialReference sr, ProgressTracker progressTracker) { + SimpleGeometryCursor inputGeomCurs = new SimpleGeometryCursor( + inputGeometry); + SimpleGeometryCursor subractorCurs = new SimpleGeometryCursor( + subtractor); + GeometryCursor geometryCursor = execute(inputGeomCurs, subractorCurs, + sr, progressTracker); + + return geometryCursor.next(); + } + + static Geometry difference(Geometry geometry_a, Geometry geometry_b, + SpatialReference spatial_reference, ProgressTracker progress_tracker) { + if (geometry_a.isEmpty() || geometry_b.isEmpty()) + return geometry_a; + + int dimension_a = geometry_a.getDimension(); + int dimension_b = geometry_b.getDimension(); + + if (dimension_a > dimension_b) + return geometry_a; + + int type_a = geometry_a.getType().value(); + int type_b = geometry_b.getType().value(); + + Envelope2D env_a = new Envelope2D(), env_b = new Envelope2D(), env_merged = new Envelope2D(); + geometry_a.queryEnvelope2D(env_a); + geometry_b.queryEnvelope2D(env_b); + env_merged.setCoords(env_a); + env_merged.merge(env_b); + + double tolerance = InternalUtils.calculateToleranceFromGeometry( + spatial_reference, env_merged, false); + double tolerance_cluster = tolerance * Math.sqrt(2.0) * 1.00001; + + Envelope2D env_a_inflated = new Envelope2D(); + env_a_inflated.setCoords(env_a); + env_a_inflated.inflate(tolerance_cluster, tolerance_cluster); // inflate + // by + // cluster + // tolerance + + if (!env_a_inflated.isIntersecting(env_b)) + return geometry_a; + + if (dimension_a == 1 && dimension_b == 2) + return polylineMinusArea_(geometry_a, geometry_b, type_b, + spatial_reference, progress_tracker); + + if (type_a == Geometry.GeometryType.Point) { + Geometry geometry_b_; + if (MultiPath.isSegment(type_b)) { + geometry_b_ = new Polyline(geometry_b.getDescription()); + ((Polyline) (geometry_b_)).addSegment((Segment) (geometry_b), + true); + } else { + geometry_b_ = geometry_b; + } + switch (type_b) { + case Geometry.GeometryType.Polygon: + return pointMinusPolygon_((Point) (geometry_a), + (Polygon) (geometry_b_), tolerance, progress_tracker); + case Geometry.GeometryType.Polyline: + return pointMinusPolyline_((Point) (geometry_a), + (Polyline) (geometry_b_), tolerance, progress_tracker); + case Geometry.GeometryType.MultiPoint: + return pointMinusMultiPoint_((Point) (geometry_a), + (MultiPoint) (geometry_b_), tolerance, progress_tracker); + case Geometry.GeometryType.Envelope: + return pointMinusEnvelope_((Point) (geometry_a), + (Envelope) (geometry_b_), tolerance, progress_tracker); + case Geometry.GeometryType.Point: + return pointMinusPoint_((Point) (geometry_a), + (Point) (geometry_b_), tolerance, progress_tracker); + default: + throw new IllegalArgumentException(); + } + } else if (type_a == Geometry.GeometryType.MultiPoint) { + switch (type_b) { + case Geometry.GeometryType.Polygon: + return multiPointMinusPolygon_((MultiPoint) (geometry_a), + (Polygon) (geometry_b), tolerance, progress_tracker); + case Geometry.GeometryType.Envelope: + return multiPointMinusEnvelope_((MultiPoint) (geometry_a), + (Envelope) (geometry_b), tolerance, progress_tracker); + case Geometry.GeometryType.Point: + return multiPointMinusPoint_((MultiPoint) (geometry_a), + (Point) (geometry_b), tolerance, progress_tracker); + default: + break; + } + } + return TopologicalOperations.difference(geometry_a, geometry_b, + spatial_reference, progress_tracker); + } + + // these are special implementations, all others delegate to the topo-graph. + static Geometry pointMinusPolygon_(Point point, Polygon polygon, + double tolerance, ProgressTracker progress_tracker) { + PolygonUtils.PiPResult result = PolygonUtils.isPointInPolygon2D( + polygon, point, tolerance); + + if (result == PolygonUtils.PiPResult.PiPOutside) + return point; + + return point.createInstance(); + } + + static Geometry pointMinusPolyline_(Point point, Polyline polyline, + double tolerance, ProgressTracker progress_tracker) { + Point2D pt = point.getXY(); + SegmentIterator seg_iter = polyline.querySegmentIterator(); + + double tolerance_cluster = tolerance * Math.sqrt(2.0) * 1.00001; + double tolerance_cluster_sq = tolerance_cluster * tolerance_cluster; + Envelope2D env = new Envelope2D(); + + while (seg_iter.nextPath()) { + while (seg_iter.hasNextSegment()) { + Segment segment = seg_iter.nextSegment(); + + segment.queryEnvelope2D(env); + env.inflate(tolerance_cluster, tolerance_cluster); + + if (!env.contains(pt)) + continue; + + if (segment.isIntersecting(pt, tolerance)) + return point.createInstance(); + + // check segment end points to the cluster tolerance + Point2D end_point = segment.getStartXY(); + + if (Point2D.sqrDistance(pt, end_point) <= tolerance_cluster_sq) + return point.createInstance(); + + end_point = segment.getEndXY(); + + if (Point2D.sqrDistance(pt, end_point) <= tolerance_cluster_sq) + return point.createInstance(); + } + } + + return point; + } + + static Geometry pointMinusMultiPoint_(Point point, MultiPoint multi_point, + double tolerance, ProgressTracker progress_tracker) { + MultiPointImpl multipointImpl = (MultiPointImpl) (multi_point + ._getImpl()); + AttributeStreamOfDbl position = (AttributeStreamOfDbl) multipointImpl + .getAttributeStreamRef(VertexDescription.Semantics.POSITION); + int point_count = multi_point.getPointCount(); + Point2D point2D = point.getXY(); + Point2D pt = new Point2D(); + + double tolerance_cluster = tolerance * Math.sqrt(2.0) * 1.00001; + double tolerance_cluster_sq = tolerance_cluster * tolerance_cluster; + + for (int i = 0; i < point_count; i++) { + position.read(2 * i, pt); + double sqr_dist = Point2D.sqrDistance(pt, point2D); + if (sqr_dist <= tolerance_cluster_sq) + return point.createInstance();// return an empty point. + } + + return point;// return the input point + } + + static Geometry pointMinusEnvelope_(Point point, Envelope envelope, + double tolerance, ProgressTracker progress_tracker) { + Envelope2D env = new Envelope2D(); + envelope.queryEnvelope2D(env); + env.inflate(tolerance, tolerance); + + Point2D pt = point.getXY(); - if (!env.contains(pt)) - return point; + if (!env.contains(pt)) + return point; - return point.createInstance(); - } + return point.createInstance(); + } - static Geometry pointMinusPoint_(Point point_a, Point point_b, - double tolerance, ProgressTracker progress_tracker) { - double tolerance_cluster = tolerance * Math.sqrt(2.0) * 1.00001; - double tolerance_cluster_sq = tolerance_cluster * tolerance_cluster; + static Geometry pointMinusPoint_(Point point_a, Point point_b, + double tolerance, ProgressTracker progress_tracker) { + double tolerance_cluster = tolerance * Math.sqrt(2.0) * 1.00001; + double tolerance_cluster_sq = tolerance_cluster * tolerance_cluster; - Point2D pt_a = point_a.getXY(); - Point2D pt_b = point_b.getXY(); + Point2D pt_a = point_a.getXY(); + Point2D pt_b = point_b.getXY(); - if (Point2D.sqrDistance(pt_a, pt_b) <= tolerance_cluster_sq) - return point_a.createInstance(); // return empty point + if (Point2D.sqrDistance(pt_a, pt_b) <= tolerance_cluster_sq) + return point_a.createInstance(); // return empty point - return point_a; - } + return point_a; + } - static Geometry multiPointMinusPolygon_(MultiPoint multi_point, - Polygon polygon, double tolerance, ProgressTracker progress_tracker) { - Envelope2D env = new Envelope2D(); - polygon.queryEnvelope2D(env); - env.inflate(tolerance, tolerance); + static Geometry multiPointMinusPolygon_(MultiPoint multi_point, + Polygon polygon, double tolerance, ProgressTracker progress_tracker) { + Envelope2D env = new Envelope2D(); + polygon.queryEnvelope2D(env); + env.inflate(tolerance, tolerance); - int point_count = multi_point.getPointCount(); + int point_count = multi_point.getPointCount(); - boolean b_found_covered = false; - boolean[] covered = new boolean[point_count]; - for (int i = 0; i < point_count; i++) - covered[i] = false; + boolean b_found_covered = false; + boolean[] covered = new boolean[point_count]; + for (int i = 0; i < point_count; i++) + covered[i] = false; - Point2D pt = new Point2D(); + Point2D pt = new Point2D(); - for (int i = 0; i < point_count; i++) { - multi_point.getXY(i, pt); + for (int i = 0; i < point_count; i++) { + multi_point.getXY(i, pt); - if (!env.contains(pt)) - continue; + if (!env.contains(pt)) + continue; - PolygonUtils.PiPResult result = PolygonUtils.isPointInPolygon2D( - polygon, pt, tolerance); + PolygonUtils.PiPResult result = PolygonUtils.isPointInPolygon2D( + polygon, pt, tolerance); - if (result == PolygonUtils.PiPResult.PiPOutside) - continue; + if (result == PolygonUtils.PiPResult.PiPOutside) + continue; - b_found_covered = true; - covered[i] = true; - } + b_found_covered = true; + covered[i] = true; + } - if (!b_found_covered) - return multi_point; + if (!b_found_covered) + return multi_point; - MultiPoint new_multipoint = (MultiPoint) multi_point.createInstance(); + MultiPoint new_multipoint = (MultiPoint) multi_point.createInstance(); - for (int i = 0; i < point_count; i++) { - if (!covered[i]) - new_multipoint.add(multi_point, i, i + 1); - } + for (int i = 0; i < point_count; i++) { + if (!covered[i]) + new_multipoint.add(multi_point, i, i + 1); + } - return new_multipoint; - } + return new_multipoint; + } - static Geometry multiPointMinusEnvelope_(MultiPoint multi_point, - Envelope envelope, double tolerance, - ProgressTracker progress_tracker) { - Envelope2D env = new Envelope2D(); - envelope.queryEnvelope2D(env); - env.inflate(tolerance, tolerance); + static Geometry multiPointMinusEnvelope_(MultiPoint multi_point, + Envelope envelope, double tolerance, + ProgressTracker progress_tracker) { + Envelope2D env = new Envelope2D(); + envelope.queryEnvelope2D(env); + env.inflate(tolerance, tolerance); - int point_count = multi_point.getPointCount(); + int point_count = multi_point.getPointCount(); - boolean b_found_covered = false; - boolean[] covered = new boolean[point_count]; - for (int i = 0; i < point_count; i++) - covered[i] = false; + boolean b_found_covered = false; + boolean[] covered = new boolean[point_count]; + for (int i = 0; i < point_count; i++) + covered[i] = false; - Point2D pt = new Point2D(); + Point2D pt = new Point2D(); - for (int i = 0; i < point_count; i++) { - multi_point.getXY(i, pt); + for (int i = 0; i < point_count; i++) { + multi_point.getXY(i, pt); - if (!env.contains(pt)) - continue; + if (!env.contains(pt)) + continue; - b_found_covered = true; - covered[i] = true; - } + b_found_covered = true; + covered[i] = true; + } - if (!b_found_covered) - return multi_point; + if (!b_found_covered) + return multi_point; - MultiPoint new_multipoint = (MultiPoint) multi_point.createInstance(); + MultiPoint new_multipoint = (MultiPoint) multi_point.createInstance(); - for (int i = 0; i < point_count; i++) { - if (!covered[i]) - new_multipoint.add(multi_point, i, i + 1); - } + for (int i = 0; i < point_count; i++) { + if (!covered[i]) + new_multipoint.add(multi_point, i, i + 1); + } - return new_multipoint; - } + return new_multipoint; + } - static Geometry multiPointMinusPoint_(MultiPoint multi_point, Point point, - double tolerance, ProgressTracker progress_tracker) { - MultiPointImpl multipointImpl = (MultiPointImpl) (multi_point - ._getImpl()); - AttributeStreamOfDbl position = (AttributeStreamOfDbl) (multipointImpl - .getAttributeStreamRef(VertexDescription.Semantics.POSITION)); - int point_count = multi_point.getPointCount(); - Point2D point2D = point.getXY(); - Point2D pt = new Point2D(); + static Geometry multiPointMinusPoint_(MultiPoint multi_point, Point point, + double tolerance, ProgressTracker progress_tracker) { + MultiPointImpl multipointImpl = (MultiPointImpl) (multi_point + ._getImpl()); + AttributeStreamOfDbl position = (AttributeStreamOfDbl) (multipointImpl + .getAttributeStreamRef(VertexDescription.Semantics.POSITION)); + int point_count = multi_point.getPointCount(); + Point2D point2D = point.getXY(); + Point2D pt = new Point2D(); - boolean b_found_covered = false; - boolean[] covered = new boolean[point_count]; - for (int i = 0; i < point_count; i++) - covered[i] = false; + boolean b_found_covered = false; + boolean[] covered = new boolean[point_count]; + for (int i = 0; i < point_count; i++) + covered[i] = false; - double tolerance_cluster = tolerance * Math.sqrt(2.0) * 1.00001; - double tolerance_cluster_sq = tolerance_cluster * tolerance_cluster; + double tolerance_cluster = tolerance * Math.sqrt(2.0) * 1.00001; + double tolerance_cluster_sq = tolerance_cluster * tolerance_cluster; - for (int i = 0; i < point_count; i++) { - position.read(2 * i, pt); + for (int i = 0; i < point_count; i++) { + position.read(2 * i, pt); - double sqr_dist = Point2D.sqrDistance(pt, point2D); + double sqr_dist = Point2D.sqrDistance(pt, point2D); - if (sqr_dist <= tolerance_cluster_sq) { - b_found_covered = true; - covered[i] = true; - } - } + if (sqr_dist <= tolerance_cluster_sq) { + b_found_covered = true; + covered[i] = true; + } + } - if (!b_found_covered) - return multi_point; + if (!b_found_covered) + return multi_point; - MultiPoint new_multipoint = (MultiPoint) (multi_point.createInstance()); + MultiPoint new_multipoint = (MultiPoint) (multi_point.createInstance()); - for (int i = 0; i < point_count; i++) { - if (!covered[i]) - new_multipoint.add(multi_point, i, i + 1); - } - - return new_multipoint; - } - - static Geometry polylineMinusArea_(Geometry geometry, Geometry area, - int area_type, SpatialReference sr, ProgressTracker progress_tracker) { - // construct the complement of the Polygon (or Envelope) - Envelope envelope = new Envelope(); - geometry.queryEnvelope(envelope); - Envelope2D env_2D = new Envelope2D(); - area.queryEnvelope2D(env_2D); - envelope.merge(env_2D); - double dw = 0.1 * envelope.getWidth(); - double dh = 0.1 * envelope.getHeight(); - envelope.inflate(dw, dh); - - Polygon complement = new Polygon(); - complement.addEnvelope(envelope, false); - - MultiPathImpl complementImpl = (MultiPathImpl) (complement._getImpl()); - - if (area_type == Geometry.GeometryType.Polygon) { - MultiPathImpl polygonImpl = (MultiPathImpl) (area._getImpl()); - complementImpl.add(polygonImpl, true); - } else - complementImpl.addEnvelope((Envelope) (area), true); - - OperatorFactoryLocal projEnv = OperatorFactoryLocal.getInstance(); - OperatorIntersection operatorIntersection = (OperatorIntersection) projEnv - .getOperator(Operator.Type.Intersection); - Geometry difference = operatorIntersection.execute(geometry, - complement, sr, progress_tracker); - return difference; - } + for (int i = 0; i < point_count; i++) { + if (!covered[i]) + new_multipoint.add(multi_point, i, i + 1); + } + + return new_multipoint; + } + + static Geometry polylineMinusArea_(Geometry geometry, Geometry area, + int area_type, SpatialReference sr, ProgressTracker progress_tracker) { + // construct the complement of the Polygon (or Envelope) + Envelope envelope = new Envelope(); + geometry.queryEnvelope(envelope); + Envelope2D env_2D = new Envelope2D(); + area.queryEnvelope2D(env_2D); + envelope.merge(env_2D); + double dw = 0.1 * envelope.getWidth(); + double dh = 0.1 * envelope.getHeight(); + envelope.inflate(dw, dh); + + Polygon complement = new Polygon(); + complement.addEnvelope(envelope, false); + + MultiPathImpl complementImpl = (MultiPathImpl) (complement._getImpl()); + + if (area_type == Geometry.GeometryType.Polygon) { + MultiPathImpl polygonImpl = (MultiPathImpl) (area._getImpl()); + complementImpl.add(polygonImpl, true); + } else + complementImpl.addEnvelope((Envelope) (area), true); + + OperatorFactoryLocal projEnv = OperatorFactoryLocal.getInstance(); + OperatorIntersection operatorIntersection = (OperatorIntersection) projEnv + .getOperator(Operator.Type.Intersection); + Geometry difference = operatorIntersection.execute(geometry, + complement, sr, progress_tracker); + return difference; + } } diff --git a/src/main/java/com/esri/core/geometry/OperatorDisjoint.java b/src/main/java/com/esri/core/geometry/OperatorDisjoint.java index 6c703f5f..9404759f 100644 --- a/src/main/java/com/esri/core/geometry/OperatorDisjoint.java +++ b/src/main/java/com/esri/core/geometry/OperatorDisjoint.java @@ -31,14 +31,14 @@ */ public abstract class OperatorDisjoint extends OperatorSimpleRelation { - @Override - public Type getType() { - return Type.Disjoint; - } - - public static OperatorDisjoint local() { - return (OperatorDisjoint) OperatorFactoryLocal.getInstance() - .getOperator(Type.Disjoint); - } + @Override + public Type getType() { + return Type.Disjoint; + } + + public static OperatorDisjoint local() { + return (OperatorDisjoint) OperatorFactoryLocal.getInstance() + .getOperator(Type.Disjoint); + } } diff --git a/src/main/java/com/esri/core/geometry/OperatorDisjointLocal.java b/src/main/java/com/esri/core/geometry/OperatorDisjointLocal.java index 3229d70e..6b2ab840 100644 --- a/src/main/java/com/esri/core/geometry/OperatorDisjointLocal.java +++ b/src/main/java/com/esri/core/geometry/OperatorDisjointLocal.java @@ -25,11 +25,11 @@ package com.esri.core.geometry; class OperatorDisjointLocal extends OperatorDisjoint { - @Override - public boolean execute(Geometry inputGeom1, Geometry inputGeom2, - SpatialReference sr, ProgressTracker progressTracker) { - return RelationalOperations.relate(inputGeom1, inputGeom2, sr, - RelationalOperations.Relation.disjoint, progressTracker); - } + @Override + public boolean execute(Geometry inputGeom1, Geometry inputGeom2, + SpatialReference sr, ProgressTracker progressTracker) { + return RelationalOperations.relate(inputGeom1, inputGeom2, sr, + RelationalOperations.Relation.disjoint, progressTracker); + } } diff --git a/src/main/java/com/esri/core/geometry/OperatorDistance.java b/src/main/java/com/esri/core/geometry/OperatorDistance.java index 56b70976..6141f6f8 100644 --- a/src/main/java/com/esri/core/geometry/OperatorDistance.java +++ b/src/main/java/com/esri/core/geometry/OperatorDistance.java @@ -31,20 +31,20 @@ */ public abstract class OperatorDistance extends Operator { - @Override - public Type getType() { - return Type.Distance; - } - - /** - * Calculates distance between two geometries. - */ - public abstract double execute(Geometry geom1, Geometry geom2, - ProgressTracker progressTracker); - - public static OperatorDistance local() { - return (OperatorDistance) OperatorFactoryLocal.getInstance() - .getOperator(Type.Distance); - } + @Override + public Type getType() { + return Type.Distance; + } + + /** + * Calculates distance between two geometries. + */ + public abstract double execute(Geometry geom1, Geometry geom2, + ProgressTracker progressTracker); + + public static OperatorDistance local() { + return (OperatorDistance) OperatorFactoryLocal.getInstance() + .getOperator(Type.Distance); + } } diff --git a/src/main/java/com/esri/core/geometry/OperatorDistanceLocal.java b/src/main/java/com/esri/core/geometry/OperatorDistanceLocal.java index 2c2d5880..ca3b305c 100644 --- a/src/main/java/com/esri/core/geometry/OperatorDistanceLocal.java +++ b/src/main/java/com/esri/core/geometry/OperatorDistanceLocal.java @@ -26,417 +26,417 @@ class OperatorDistanceLocal extends OperatorDistance { - /** - * Performs the Distance operation on two geometries - * - * @return Returns a double. - */ - @Override - public double execute(Geometry geom1, Geometry geom2, - ProgressTracker progressTracker) { - if (null == geom1 || null == geom2) { - throw new IllegalArgumentException(); - } - - Geometry geometryA = geom1; - Geometry geometryB = geom2; - - if (geometryA.isEmpty() || geometryB.isEmpty()) - return NumberUtils.TheNaN; - - Polygon polygonA; - Polygon polygonB; - MultiPoint multiPointA; - MultiPoint multiPointB; - - // if geometryA is an envelope use a polygon instead (if geom1 was - // folded, then geometryA will already be a polygon) - // if geometryA is a point use a multipoint instead - Geometry.Type gtA = geometryA.getType(); - Geometry.Type gtB = geometryB.getType(); - if (gtA == Geometry.Type.Point) { - if (gtB == Geometry.Type.Point) { - return Point2D.distance(((Point) geometryA).getXY(), ((Point) geometryB).getXY()); - } else if (gtB == Geometry.Type.Envelope) { - Envelope2D envB = new Envelope2D(); - geometryB.queryEnvelope2D(envB); - return envB.distance(((Point) geometryA).getXY()); - } - - multiPointA = new MultiPoint(); - multiPointA.add((Point) geometryA); - geometryA = multiPointA; - } else if (gtA == Geometry.Type.Envelope) { - if (gtB == Geometry.Type.Envelope) { - Envelope2D envA = new Envelope2D(); - geometryA.queryEnvelope2D(envA); - Envelope2D envB = new Envelope2D(); - geometryB.queryEnvelope2D(envB); - return envB.distance(envA); - } - polygonA = new Polygon(); - polygonA.addEnvelope((Envelope) geometryA, false); - geometryA = polygonA; - } - - // if geom_2 is an envelope use a polygon instead - // if geom_2 is a point use a multipoint instead - if (gtB == Geometry.Type.Point) { - multiPointB = new MultiPoint(); - multiPointB.add((Point) geometryB); - geometryB = multiPointB; - } else if (gtB == Geometry.Type.Envelope) { - polygonB = new Polygon(); - polygonB.addEnvelope((Envelope) geometryB, false); - geometryB = polygonB; - } - - DistanceCalculator distanceCalculator = new DistanceCalculator( - progressTracker); - double distance = distanceCalculator.calculate(geometryA, geometryB); - return distance; - } - - // Implementation of distance algorithm. - class DistanceCalculator { - private ProgressTracker m_progressTracker; - private Envelope2D m_env2DgeometryA; - private Envelope2D m_env2DgeometryB; - - private void swapEnvelopes_() { - double temp; - // swap xmin - temp = m_env2DgeometryA.xmin; - m_env2DgeometryA.xmin = m_env2DgeometryB.xmin; - m_env2DgeometryB.xmin = temp; - // swap xmax - temp = m_env2DgeometryA.xmax; - m_env2DgeometryA.xmax = m_env2DgeometryB.xmax; - m_env2DgeometryB.xmax = temp; - // swap ymin - temp = m_env2DgeometryA.ymin; - m_env2DgeometryA.ymin = m_env2DgeometryB.ymin; - m_env2DgeometryB.ymin = temp; - // swap ymax - temp = m_env2DgeometryA.ymax; - m_env2DgeometryA.ymax = m_env2DgeometryB.ymax; - m_env2DgeometryB.ymax = temp; - } - - private double executeBruteForce_(/* const */Geometry geometryA, /* const */ - Geometry geometryB) { - if ((m_progressTracker != null) - && !(m_progressTracker.progress(-1, -1))) - throw new RuntimeException("user_canceled"); - - boolean geometriesAreDisjoint = !m_env2DgeometryA - .isIntersecting(m_env2DgeometryB); - if (Geometry.isMultiPath(geometryA.getType().value()) - && Geometry.isMultiPath(geometryB.getType().value())) { // MultiPath - // vs. - // MultiPath - // choose - // the - // multipath - // with - // more - // points - // to - // be - // geometryA, - // this - // way - // more - // of - // geometryA - // segments - // can - // be - // disqualified - // more - // quickly - // by - // testing - // segmentA - // envelope - // vs. - // geometryB - // envelope - if (((MultiPath) geometryA).getPointCount() > ((MultiPath) geometryB) - .getPointCount()) - return bruteForceMultiPathMultiPath_((MultiPath) geometryA, - (MultiPath) geometryB, geometriesAreDisjoint); - swapEnvelopes_(); - double answer = bruteForceMultiPathMultiPath_( - (MultiPath) geometryB, (MultiPath) geometryA, - geometriesAreDisjoint); - swapEnvelopes_(); - return answer; - } else if (geometryA.getType() == Geometry.Type.MultiPoint - && Geometry.isMultiPath(geometryB.getType().value())) { // MultiPoint - // vs. - // MultiPath - swapEnvelopes_(); - double answer = bruteForceMultiPathMultiPoint_( - (MultiPath) geometryB, (MultiPoint) geometryA, - geometriesAreDisjoint); - swapEnvelopes_(); - return answer; - } else if (geometryB.getType() == Geometry.Type.MultiPoint - && Geometry.isMultiPath(geometryA.getType().value())) { // MultiPath - // vs. - // MultiPoint - return bruteForceMultiPathMultiPoint_((MultiPath) geometryA, - (MultiPoint) geometryB, geometriesAreDisjoint); - } else if (geometryA.getType() == Geometry.Type.MultiPoint - && geometryB.getType() == Geometry.Type.MultiPoint) { // MultiPoint - // vs. - // MultiPoint - // choose - // the - // multipoint - // with - // more - // vertices - // to - // be - // the - // "geometryA", - // this - // way - // more - // points - // can - // be - // potentially - // excluded - // by - // envelope - // distance - // tests. - if (((MultiPoint) geometryA).getPointCount() > ((MultiPoint) geometryB) - .getPointCount()) - return bruteForceMultiPointMultiPoint_( - (MultiPoint) geometryA, (MultiPoint) geometryB, - geometriesAreDisjoint); - swapEnvelopes_(); - double answer = bruteForceMultiPointMultiPoint_( - (MultiPoint) geometryB, (MultiPoint) geometryA, - geometriesAreDisjoint); - swapEnvelopes_(); - return answer; - } - return 0.0; - } - - private double bruteForceMultiPathMultiPath_( - /* const */MultiPath geometryA, /* const */MultiPath geometryB, - boolean geometriesAreDisjoint) { - // It may be beneficial to have the geometry with less vertices - // always be geometryA. - SegmentIterator segIterA = geometryA.querySegmentIterator(); - SegmentIterator segIterB = geometryB.querySegmentIterator(); - Envelope2D env2DSegmentA = new Envelope2D(); - Envelope2D env2DSegmentB = new Envelope2D(); - - double minSqrDistance = NumberUtils.doubleMax(); - - if (!geometriesAreDisjoint) { - // Geometries might be non-disjoint. Check if they intersect - // using point-in-polygon tests - if (weakIntersectionTest_(geometryA, geometryB, segIterA, - segIterB)) - return 0.0; - } - - // if geometries are known disjoint, don't bother to do any tests - // for polygon containment - - // nested while-loop insanity - while (segIterA.nextPath()) { - while (segIterA.hasNextSegment()) { + /** + * Performs the Distance operation on two geometries + * + * @return Returns a double. + */ + @Override + public double execute(Geometry geom1, Geometry geom2, + ProgressTracker progressTracker) { + if (null == geom1 || null == geom2) { + throw new IllegalArgumentException(); + } + + Geometry geometryA = geom1; + Geometry geometryB = geom2; + + if (geometryA.isEmpty() || geometryB.isEmpty()) + return NumberUtils.TheNaN; + + Polygon polygonA; + Polygon polygonB; + MultiPoint multiPointA; + MultiPoint multiPointB; + + // if geometryA is an envelope use a polygon instead (if geom1 was + // folded, then geometryA will already be a polygon) + // if geometryA is a point use a multipoint instead + Geometry.Type gtA = geometryA.getType(); + Geometry.Type gtB = geometryB.getType(); + if (gtA == Geometry.Type.Point) { + if (gtB == Geometry.Type.Point) { + return Point2D.distance(((Point) geometryA).getXY(), ((Point) geometryB).getXY()); + } else if (gtB == Geometry.Type.Envelope) { + Envelope2D envB = new Envelope2D(); + geometryB.queryEnvelope2D(envB); + return envB.distance(((Point) geometryA).getXY()); + } + + multiPointA = new MultiPoint(); + multiPointA.add((Point) geometryA); + geometryA = multiPointA; + } else if (gtA == Geometry.Type.Envelope) { + if (gtB == Geometry.Type.Envelope) { + Envelope2D envA = new Envelope2D(); + geometryA.queryEnvelope2D(envA); + Envelope2D envB = new Envelope2D(); + geometryB.queryEnvelope2D(envB); + return envB.distance(envA); + } + polygonA = new Polygon(); + polygonA.addEnvelope((Envelope) geometryA, false); + geometryA = polygonA; + } + + // if geom_2 is an envelope use a polygon instead + // if geom_2 is a point use a multipoint instead + if (gtB == Geometry.Type.Point) { + multiPointB = new MultiPoint(); + multiPointB.add((Point) geometryB); + geometryB = multiPointB; + } else if (gtB == Geometry.Type.Envelope) { + polygonB = new Polygon(); + polygonB.addEnvelope((Envelope) geometryB, false); + geometryB = polygonB; + } + + DistanceCalculator distanceCalculator = new DistanceCalculator( + progressTracker); + double distance = distanceCalculator.calculate(geometryA, geometryB); + return distance; + } + + // Implementation of distance algorithm. + class DistanceCalculator { + private ProgressTracker m_progressTracker; + private Envelope2D m_env2DgeometryA; + private Envelope2D m_env2DgeometryB; + + private void swapEnvelopes_() { + double temp; + // swap xmin + temp = m_env2DgeometryA.xmin; + m_env2DgeometryA.xmin = m_env2DgeometryB.xmin; + m_env2DgeometryB.xmin = temp; + // swap xmax + temp = m_env2DgeometryA.xmax; + m_env2DgeometryA.xmax = m_env2DgeometryB.xmax; + m_env2DgeometryB.xmax = temp; + // swap ymin + temp = m_env2DgeometryA.ymin; + m_env2DgeometryA.ymin = m_env2DgeometryB.ymin; + m_env2DgeometryB.ymin = temp; + // swap ymax + temp = m_env2DgeometryA.ymax; + m_env2DgeometryA.ymax = m_env2DgeometryB.ymax; + m_env2DgeometryB.ymax = temp; + } + + private double executeBruteForce_(/* const */Geometry geometryA, /* const */ + Geometry geometryB) { + if ((m_progressTracker != null) + && !(m_progressTracker.progress(-1, -1))) + throw new RuntimeException("user_canceled"); + + boolean geometriesAreDisjoint = !m_env2DgeometryA + .isIntersecting(m_env2DgeometryB); + if (Geometry.isMultiPath(geometryA.getType().value()) + && Geometry.isMultiPath(geometryB.getType().value())) { // MultiPath + // vs. + // MultiPath + // choose + // the + // multipath + // with + // more + // points + // to + // be + // geometryA, + // this + // way + // more + // of + // geometryA + // segments + // can + // be + // disqualified + // more + // quickly + // by + // testing + // segmentA + // envelope + // vs. + // geometryB + // envelope + if (((MultiPath) geometryA).getPointCount() > ((MultiPath) geometryB) + .getPointCount()) + return bruteForceMultiPathMultiPath_((MultiPath) geometryA, + (MultiPath) geometryB, geometriesAreDisjoint); + swapEnvelopes_(); + double answer = bruteForceMultiPathMultiPath_( + (MultiPath) geometryB, (MultiPath) geometryA, + geometriesAreDisjoint); + swapEnvelopes_(); + return answer; + } else if (geometryA.getType() == Geometry.Type.MultiPoint + && Geometry.isMultiPath(geometryB.getType().value())) { // MultiPoint + // vs. + // MultiPath + swapEnvelopes_(); + double answer = bruteForceMultiPathMultiPoint_( + (MultiPath) geometryB, (MultiPoint) geometryA, + geometriesAreDisjoint); + swapEnvelopes_(); + return answer; + } else if (geometryB.getType() == Geometry.Type.MultiPoint + && Geometry.isMultiPath(geometryA.getType().value())) { // MultiPath + // vs. + // MultiPoint + return bruteForceMultiPathMultiPoint_((MultiPath) geometryA, + (MultiPoint) geometryB, geometriesAreDisjoint); + } else if (geometryA.getType() == Geometry.Type.MultiPoint + && geometryB.getType() == Geometry.Type.MultiPoint) { // MultiPoint + // vs. + // MultiPoint + // choose + // the + // multipoint + // with + // more + // vertices + // to + // be + // the + // "geometryA", + // this + // way + // more + // points + // can + // be + // potentially + // excluded + // by + // envelope + // distance + // tests. + if (((MultiPoint) geometryA).getPointCount() > ((MultiPoint) geometryB) + .getPointCount()) + return bruteForceMultiPointMultiPoint_( + (MultiPoint) geometryA, (MultiPoint) geometryB, + geometriesAreDisjoint); + swapEnvelopes_(); + double answer = bruteForceMultiPointMultiPoint_( + (MultiPoint) geometryB, (MultiPoint) geometryA, + geometriesAreDisjoint); + swapEnvelopes_(); + return answer; + } + return 0.0; + } + + private double bruteForceMultiPathMultiPath_( + /* const */MultiPath geometryA, /* const */MultiPath geometryB, + boolean geometriesAreDisjoint) { + // It may be beneficial to have the geometry with less vertices + // always be geometryA. + SegmentIterator segIterA = geometryA.querySegmentIterator(); + SegmentIterator segIterB = geometryB.querySegmentIterator(); + Envelope2D env2DSegmentA = new Envelope2D(); + Envelope2D env2DSegmentB = new Envelope2D(); + + double minSqrDistance = NumberUtils.doubleMax(); + + if (!geometriesAreDisjoint) { + // Geometries might be non-disjoint. Check if they intersect + // using point-in-polygon tests + if (weakIntersectionTest_(geometryA, geometryB, segIterA, + segIterB)) + return 0.0; + } + + // if geometries are known disjoint, don't bother to do any tests + // for polygon containment + + // nested while-loop insanity + while (segIterA.nextPath()) { + while (segIterA.hasNextSegment()) { /* const */ - Segment segmentA = segIterA.nextSegment(); - segmentA.queryEnvelope2D(env2DSegmentA); - if (env2DSegmentA.sqrDistance(m_env2DgeometryB) > minSqrDistance) - continue; + Segment segmentA = segIterA.nextSegment(); + segmentA.queryEnvelope2D(env2DSegmentA); + if (env2DSegmentA.sqrDistance(m_env2DgeometryB) > minSqrDistance) + continue; - while (segIterB.nextPath()) { - while (segIterB.hasNextSegment()) { + while (segIterB.nextPath()) { + while (segIterB.hasNextSegment()) { /* const */ - Segment segmentB = segIterB - .nextSegment(); - segmentB.queryEnvelope2D(env2DSegmentB); - if (env2DSegmentA.sqrDistance(env2DSegmentB) < minSqrDistance) { - // get distance between segments - double sqrDistance = segmentA.distance( - segmentB, geometriesAreDisjoint); - sqrDistance *= sqrDistance; - if (sqrDistance < minSqrDistance) { - if (sqrDistance == 0.0) - return 0.0; - - minSqrDistance = sqrDistance; - } - } - } - } - segIterB.resetToFirstPath(); - } - } - - return Math.sqrt(minSqrDistance); - } - - private double bruteForceMultiPathMultiPoint_( - /* const */MultiPath geometryA, /* const */ - MultiPoint geometryB, boolean geometriesAreDisjoint) { - SegmentIterator segIterA = geometryA.querySegmentIterator(); - - Envelope2D env2DSegmentA = new Envelope2D(); - - double minSqrDistance = NumberUtils.doubleMax(); - - Point2D inputPoint = new Point2D(); - double t = -1; - double sqrDistance = minSqrDistance; + Segment segmentB = segIterB + .nextSegment(); + segmentB.queryEnvelope2D(env2DSegmentB); + if (env2DSegmentA.sqrDistance(env2DSegmentB) < minSqrDistance) { + // get distance between segments + double sqrDistance = segmentA.distance( + segmentB, geometriesAreDisjoint); + sqrDistance *= sqrDistance; + if (sqrDistance < minSqrDistance) { + if (sqrDistance == 0.0) + return 0.0; + + minSqrDistance = sqrDistance; + } + } + } + } + segIterB.resetToFirstPath(); + } + } + + return Math.sqrt(minSqrDistance); + } + + private double bruteForceMultiPathMultiPoint_( + /* const */MultiPath geometryA, /* const */ + MultiPoint geometryB, boolean geometriesAreDisjoint) { + SegmentIterator segIterA = geometryA.querySegmentIterator(); + + Envelope2D env2DSegmentA = new Envelope2D(); + + double minSqrDistance = NumberUtils.doubleMax(); + + Point2D inputPoint = new Point2D(); + double t = -1; + double sqrDistance = minSqrDistance; /* const */ - MultiPointImpl multiPointImplB = (MultiPointImpl) geometryB - ._getImpl(); - int pointCountB = multiPointImplB.getPointCount(); - boolean bDoPiPTest = !geometriesAreDisjoint - && (geometryA.getType() == Geometry.Type.Polygon); - - while (segIterA.nextPath()) { - while (segIterA.hasNextSegment()) { + MultiPointImpl multiPointImplB = (MultiPointImpl) geometryB + ._getImpl(); + int pointCountB = multiPointImplB.getPointCount(); + boolean bDoPiPTest = !geometriesAreDisjoint + && (geometryA.getType() == Geometry.Type.Polygon); + + while (segIterA.nextPath()) { + while (segIterA.hasNextSegment()) { /* const */ - Segment segmentA = segIterA.nextSegment(); - segmentA.queryEnvelope2D(env2DSegmentA); - // if multipointB has only 1 vertex then it is faster to not - // test for - // env2DSegmentA.distance(env2DgeometryB) - if (pointCountB > 1 - && env2DSegmentA.sqrDistance(m_env2DgeometryB) > minSqrDistance) - continue; - - for (int i = 0; i < pointCountB; i++) { - multiPointImplB.getXY(i, inputPoint); - if (bDoPiPTest) { - // Test for polygon containment. This takes the - // place of a more general intersection test at the - // beginning of the operator - if (PolygonUtils.isPointInPolygon2D( - (Polygon) geometryA, inputPoint, 0) != PolygonUtils.PiPResult.PiPOutside) - return 0.0; - } - - t = segmentA.getClosestCoordinate(inputPoint, false); - inputPoint.sub(segmentA.getCoord2D(t)); - sqrDistance = inputPoint.sqrLength(); - if (sqrDistance < minSqrDistance) { - if (sqrDistance == 0.0) - return 0.0; - - minSqrDistance = sqrDistance; - } - } - - // No need to do point-in-polygon anymore (if it is a - // polygon vs polyline) - bDoPiPTest = false; - } - } - return Math.sqrt(minSqrDistance); - } - - private double bruteForceMultiPointMultiPoint_( - /* const */MultiPoint geometryA, /* const */ - MultiPoint geometryB, boolean geometriesAreDisjoint) { - double minSqrDistance = NumberUtils.doubleMax(); - - Point2D pointA = new Point2D(); - Point2D pointB = new Point2D(); - - double sqrDistance = minSqrDistance; + Segment segmentA = segIterA.nextSegment(); + segmentA.queryEnvelope2D(env2DSegmentA); + // if multipointB has only 1 vertex then it is faster to not + // test for + // env2DSegmentA.distance(env2DgeometryB) + if (pointCountB > 1 + && env2DSegmentA.sqrDistance(m_env2DgeometryB) > minSqrDistance) + continue; + + for (int i = 0; i < pointCountB; i++) { + multiPointImplB.getXY(i, inputPoint); + if (bDoPiPTest) { + // Test for polygon containment. This takes the + // place of a more general intersection test at the + // beginning of the operator + if (PolygonUtils.isPointInPolygon2D( + (Polygon) geometryA, inputPoint, 0) != PolygonUtils.PiPResult.PiPOutside) + return 0.0; + } + + t = segmentA.getClosestCoordinate(inputPoint, false); + inputPoint.sub(segmentA.getCoord2D(t)); + sqrDistance = inputPoint.sqrLength(); + if (sqrDistance < minSqrDistance) { + if (sqrDistance == 0.0) + return 0.0; + + minSqrDistance = sqrDistance; + } + } + + // No need to do point-in-polygon anymore (if it is a + // polygon vs polyline) + bDoPiPTest = false; + } + } + return Math.sqrt(minSqrDistance); + } + + private double bruteForceMultiPointMultiPoint_( + /* const */MultiPoint geometryA, /* const */ + MultiPoint geometryB, boolean geometriesAreDisjoint) { + double minSqrDistance = NumberUtils.doubleMax(); + + Point2D pointA = new Point2D(); + Point2D pointB = new Point2D(); + + double sqrDistance = minSqrDistance; /* const */ - MultiPointImpl multiPointImplA = (/* const */MultiPointImpl) geometryA - ._getImpl(); + MultiPointImpl multiPointImplA = (/* const */MultiPointImpl) geometryA + ._getImpl(); /* const */ - MultiPointImpl multiPointImplB = (/* const */MultiPointImpl) geometryB - ._getImpl(); - int pointCountA = multiPointImplA.getPointCount(); - int pointCountB = multiPointImplB.getPointCount(); - for (int i = 0; i < pointCountA; i++) { - multiPointImplA.getXY(i, pointA); - - if (pointCountB > 1 - && m_env2DgeometryB.sqrDistance(pointA) > minSqrDistance) - continue; - - for (int j = 0; j < pointCountB; j++) { - multiPointImplB.getXY(j, pointB); - sqrDistance = Point2D.sqrDistance(pointA, pointB); - if (sqrDistance < minSqrDistance) { - if (sqrDistance == 0.0) - return 0.0; - - minSqrDistance = sqrDistance; - } - } - } - - return Math.sqrt(minSqrDistance); - } - - // resets Iterators if they are used. - private boolean weakIntersectionTest_(/* const */Geometry geometryA, /* const */ - Geometry geometryB, SegmentIterator segIterA, SegmentIterator segIterB) { - if (geometryA.getType() == Geometry.Type.Polygon) { - // test PolygonA vs. first segment of each of geometryB's paths - while (segIterB.nextPath()) { - if (segIterB.hasNextSegment()) { + MultiPointImpl multiPointImplB = (/* const */MultiPointImpl) geometryB + ._getImpl(); + int pointCountA = multiPointImplA.getPointCount(); + int pointCountB = multiPointImplB.getPointCount(); + for (int i = 0; i < pointCountA; i++) { + multiPointImplA.getXY(i, pointA); + + if (pointCountB > 1 + && m_env2DgeometryB.sqrDistance(pointA) > minSqrDistance) + continue; + + for (int j = 0; j < pointCountB; j++) { + multiPointImplB.getXY(j, pointB); + sqrDistance = Point2D.sqrDistance(pointA, pointB); + if (sqrDistance < minSqrDistance) { + if (sqrDistance == 0.0) + return 0.0; + + minSqrDistance = sqrDistance; + } + } + } + + return Math.sqrt(minSqrDistance); + } + + // resets Iterators if they are used. + private boolean weakIntersectionTest_(/* const */Geometry geometryA, /* const */ + Geometry geometryB, SegmentIterator segIterA, SegmentIterator segIterB) { + if (geometryA.getType() == Geometry.Type.Polygon) { + // test PolygonA vs. first segment of each of geometryB's paths + while (segIterB.nextPath()) { + if (segIterB.hasNextSegment()) { /* const */ - Segment segmentB = segIterB.nextSegment(); - if (PolygonUtils.isPointInPolygon2D( - (Polygon) geometryA, segmentB.getEndXY(), 0) != PolygonUtils.PiPResult.PiPOutside) - return true; - } - } - segIterB.resetToFirstPath(); - } - - if (geometryB.getType() == Geometry.Type.Polygon) { - // test PolygonB vs. first segment of each of geometryA's paths - while (segIterA.nextPath()) { - if (segIterA.hasNextSegment()) { + Segment segmentB = segIterB.nextSegment(); + if (PolygonUtils.isPointInPolygon2D( + (Polygon) geometryA, segmentB.getEndXY(), 0) != PolygonUtils.PiPResult.PiPOutside) + return true; + } + } + segIterB.resetToFirstPath(); + } + + if (geometryB.getType() == Geometry.Type.Polygon) { + // test PolygonB vs. first segment of each of geometryA's paths + while (segIterA.nextPath()) { + if (segIterA.hasNextSegment()) { /* const */ - Segment segmentA = segIterA.nextSegment(); - if (PolygonUtils.isPointInPolygon2D( - (Polygon) geometryB, segmentA.getEndXY(), 0) != PolygonUtils.PiPResult.PiPOutside) - return true; - } - } - segIterA.resetToFirstPath(); - } - return false; - } - - DistanceCalculator(ProgressTracker progressTracker) { - m_progressTracker = progressTracker; - m_env2DgeometryA = new Envelope2D(); - m_env2DgeometryA.setEmpty(); - m_env2DgeometryB = new Envelope2D(); - m_env2DgeometryB.setEmpty(); - } - - double calculate(/* const */Geometry geometryA, /* const */ - Geometry geometryB) { - if (geometryA.isEmpty() || geometryB.isEmpty()) - return NumberUtils.TheNaN; - - geometryA.queryEnvelope2D(m_env2DgeometryA); - geometryB.queryEnvelope2D(m_env2DgeometryB); - return executeBruteForce_(geometryA, geometryB); - } - } + Segment segmentA = segIterA.nextSegment(); + if (PolygonUtils.isPointInPolygon2D( + (Polygon) geometryB, segmentA.getEndXY(), 0) != PolygonUtils.PiPResult.PiPOutside) + return true; + } + } + segIterA.resetToFirstPath(); + } + return false; + } + + DistanceCalculator(ProgressTracker progressTracker) { + m_progressTracker = progressTracker; + m_env2DgeometryA = new Envelope2D(); + m_env2DgeometryA.setEmpty(); + m_env2DgeometryB = new Envelope2D(); + m_env2DgeometryB.setEmpty(); + } + + double calculate(/* const */Geometry geometryA, /* const */ + Geometry geometryB) { + if (geometryA.isEmpty() || geometryB.isEmpty()) + return NumberUtils.TheNaN; + + geometryA.queryEnvelope2D(m_env2DgeometryA); + geometryB.queryEnvelope2D(m_env2DgeometryB); + return executeBruteForce_(geometryA, geometryB); + } + } } diff --git a/src/main/java/com/esri/core/geometry/OperatorEnclosingCircle.java b/src/main/java/com/esri/core/geometry/OperatorEnclosingCircle.java index db4fe249..40276e8b 100644 --- a/src/main/java/com/esri/core/geometry/OperatorEnclosingCircle.java +++ b/src/main/java/com/esri/core/geometry/OperatorEnclosingCircle.java @@ -1,22 +1,22 @@ package com.esri.core.geometry; public abstract class OperatorEnclosingCircle extends Operator { - @Override - public Type getType() { - return Type.EnclosingCircle; - } + @Override + public Type getType() { + return Type.EnclosingCircle; + } - public abstract GeometryCursor execute(GeometryCursor geoms, SpatialReference spatialReference, ProgressTracker progressTracker); + public abstract GeometryCursor execute(GeometryCursor geoms, SpatialReference spatialReference, ProgressTracker progressTracker); - /** - * Performs the Generalize operation on a single geometry. Point and - * multipoint geometries are left unchanged. An envelope is converted to a - * polygon. - */ - public abstract Geometry execute(Geometry geom, SpatialReference spatialReference, ProgressTracker progressTracker); + /** + * Performs the Generalize operation on a single geometry. Point and + * multipoint geometries are left unchanged. An envelope is converted to a + * polygon. + */ + public abstract Geometry execute(Geometry geom, SpatialReference spatialReference, ProgressTracker progressTracker); - public static OperatorEnclosingCircle local() { - return (OperatorEnclosingCircle) OperatorFactoryLocal.getInstance().getOperator(Type.EnclosingCircle); - } + public static OperatorEnclosingCircle local() { + return (OperatorEnclosingCircle) OperatorFactoryLocal.getInstance().getOperator(Type.EnclosingCircle); + } } diff --git a/src/main/java/com/esri/core/geometry/OperatorEnclosingCircleCursor.java b/src/main/java/com/esri/core/geometry/OperatorEnclosingCircleCursor.java index 7368d38c..38611a42 100644 --- a/src/main/java/com/esri/core/geometry/OperatorEnclosingCircleCursor.java +++ b/src/main/java/com/esri/core/geometry/OperatorEnclosingCircleCursor.java @@ -6,30 +6,30 @@ import java.util.stream.IntStream; public class OperatorEnclosingCircleCursor extends GeometryCursor { - SpatialReference m_spatialReference; - ProgressTracker m_progressTracker; - - public OperatorEnclosingCircleCursor(GeometryCursor geoms, SpatialReference spatialReference, ProgressTracker progressTracker) { - m_inputGeoms = geoms; - m_spatialReference = spatialReference; - m_progressTracker = progressTracker; - } - - @Override - public Geometry next() { - if (hasNext()) - return getCircle(m_inputGeoms.next()); - - return null; - } - - protected Geometry getCircle(Geometry geometry) { - if (geometry.getType() == Geometry.Type.Point || ((MultiVertexGeometry)geometry).getPointCount() <= 1) - return geometry; - - EnclosingCircler enclosingCircler = new EnclosingCircler(geometry, m_spatialReference, m_progressTracker); - return enclosingCircler.search(); - } + SpatialReference m_spatialReference; + ProgressTracker m_progressTracker; + + public OperatorEnclosingCircleCursor(GeometryCursor geoms, SpatialReference spatialReference, ProgressTracker progressTracker) { + m_inputGeoms = geoms; + m_spatialReference = spatialReference; + m_progressTracker = progressTracker; + } + + @Override + public Geometry next() { + if (hasNext()) + return getCircle(m_inputGeoms.next()); + + return null; + } + + protected Geometry getCircle(Geometry geometry) { + if (geometry.getType() == Geometry.Type.Point || ((MultiVertexGeometry) geometry).getPointCount() <= 1) + return geometry; + + EnclosingCircler enclosingCircler = new EnclosingCircler(geometry, m_spatialReference, m_progressTracker); + return enclosingCircler.search(); + } } diff --git a/src/main/java/com/esri/core/geometry/OperatorEnclosingCircleLocal.java b/src/main/java/com/esri/core/geometry/OperatorEnclosingCircleLocal.java index b05d680b..4e63bfe0 100644 --- a/src/main/java/com/esri/core/geometry/OperatorEnclosingCircleLocal.java +++ b/src/main/java/com/esri/core/geometry/OperatorEnclosingCircleLocal.java @@ -1,14 +1,14 @@ package com.esri.core.geometry; public class OperatorEnclosingCircleLocal extends OperatorEnclosingCircle { - @Override - public GeometryCursor execute(GeometryCursor geoms, SpatialReference spatialReference, ProgressTracker progressTracker) { - return new OperatorEnclosingCircleCursor(geoms, spatialReference, progressTracker); - } + @Override + public GeometryCursor execute(GeometryCursor geoms, SpatialReference spatialReference, ProgressTracker progressTracker) { + return new OperatorEnclosingCircleCursor(geoms, spatialReference, progressTracker); + } - @Override - public Geometry execute(Geometry geom, SpatialReference spatialReference, ProgressTracker progressTracker) { - OperatorEnclosingCircleCursor operatorEnclosingCircleCursor = new OperatorEnclosingCircleCursor(new SimpleGeometryCursor(geom), spatialReference, progressTracker); - return operatorEnclosingCircleCursor.next(); - } + @Override + public Geometry execute(Geometry geom, SpatialReference spatialReference, ProgressTracker progressTracker) { + OperatorEnclosingCircleCursor operatorEnclosingCircleCursor = new OperatorEnclosingCircleCursor(new SimpleGeometryCursor(geom), spatialReference, progressTracker); + return operatorEnclosingCircleCursor.next(); + } } diff --git a/src/main/java/com/esri/core/geometry/OperatorEquals.java b/src/main/java/com/esri/core/geometry/OperatorEquals.java index 1286052f..2fb6d3ba 100644 --- a/src/main/java/com/esri/core/geometry/OperatorEquals.java +++ b/src/main/java/com/esri/core/geometry/OperatorEquals.java @@ -30,14 +30,14 @@ * Relational operation Equals. */ public abstract class OperatorEquals extends OperatorSimpleRelation { - @Override - public Type getType() { - return Type.Equals; - } - - public static OperatorEquals local() { - return (OperatorEquals) OperatorFactoryLocal.getInstance().getOperator( - Type.Equals); - } + @Override + public Type getType() { + return Type.Equals; + } + + public static OperatorEquals local() { + return (OperatorEquals) OperatorFactoryLocal.getInstance().getOperator( + Type.Equals); + } } diff --git a/src/main/java/com/esri/core/geometry/OperatorEqualsLocal.java b/src/main/java/com/esri/core/geometry/OperatorEqualsLocal.java index c0dbc7f8..945b0514 100644 --- a/src/main/java/com/esri/core/geometry/OperatorEqualsLocal.java +++ b/src/main/java/com/esri/core/geometry/OperatorEqualsLocal.java @@ -25,11 +25,11 @@ package com.esri.core.geometry; class OperatorEqualsLocal extends OperatorEquals { - @Override - public boolean execute(Geometry inputGeom1, Geometry inputGeom2, - SpatialReference sr, ProgressTracker progressTracker) { - return RelationalOperations.relate(inputGeom1, inputGeom2, sr, - RelationalOperations.Relation.equals, progressTracker); - } + @Override + public boolean execute(Geometry inputGeom1, Geometry inputGeom2, + SpatialReference sr, ProgressTracker progressTracker) { + return RelationalOperations.relate(inputGeom1, inputGeom2, sr, + RelationalOperations.Relation.equals, progressTracker); + } } diff --git a/src/main/java/com/esri/core/geometry/OperatorExportToESRIShape.java b/src/main/java/com/esri/core/geometry/OperatorExportToESRIShape.java index 5842a046..9eb4051e 100644 --- a/src/main/java/com/esri/core/geometry/OperatorExportToESRIShape.java +++ b/src/main/java/com/esri/core/geometry/OperatorExportToESRIShape.java @@ -32,38 +32,38 @@ * Export to ESRI shape format. */ public abstract class OperatorExportToESRIShape extends Operator { - @Override - public Type getType() { - return Type.ExportToESRIShape; - } + @Override + public Type getType() { + return Type.ExportToESRIShape; + } - /** - * Performs the ExportToESRIShape operation - * - * @return Returns a ByteBufferCursor. - */ - public abstract ByteBufferCursor execute(int exportFlags, GeometryCursor geometryCursor); + /** + * Performs the ExportToESRIShape operation + * + * @return Returns a ByteBufferCursor. + */ + public abstract ByteBufferCursor execute(int exportFlags, GeometryCursor geometryCursor); - /** - * Performs the ExportToESRIShape operation. - * - * @param exportFlags Use the {@link ShapeExportFlags} interface. - * @param geometry The Geometry being exported. - * @return Returns a ByteBuffer object containing the Geometry in ESRIShape format. - */ - public abstract ByteBuffer execute(int exportFlags, Geometry geometry); + /** + * Performs the ExportToESRIShape operation. + * + * @param exportFlags Use the {@link ShapeExportFlags} interface. + * @param geometry The Geometry being exported. + * @return Returns a ByteBuffer object containing the Geometry in ESRIShape format. + */ + public abstract ByteBuffer execute(int exportFlags, Geometry geometry); - /** - * Performs the ExportToESRIShape operation. - * - * @param exportFlags Use the {@link ShapeExportFlags} interface. - * @param geometry The Geometry being exported. - * @param shapeBuffer The ByteBuffer to contain the exported Geometry in ESRIShape format. - * @return If the input buffer is null, then the size needed for the buffer is returned. Otherwise the number of bytes written to the buffer is returned. - */ - public abstract int execute(int exportFlags, Geometry geometry, ByteBuffer shapeBuffer); + /** + * Performs the ExportToESRIShape operation. + * + * @param exportFlags Use the {@link ShapeExportFlags} interface. + * @param geometry The Geometry being exported. + * @param shapeBuffer The ByteBuffer to contain the exported Geometry in ESRIShape format. + * @return If the input buffer is null, then the size needed for the buffer is returned. Otherwise the number of bytes written to the buffer is returned. + */ + public abstract int execute(int exportFlags, Geometry geometry, ByteBuffer shapeBuffer); - public static OperatorExportToESRIShape local() { - return (OperatorExportToESRIShape) OperatorFactoryLocal.getInstance().getOperator(Type.ExportToESRIShape); - } + public static OperatorExportToESRIShape local() { + return (OperatorExportToESRIShape) OperatorFactoryLocal.getInstance().getOperator(Type.ExportToESRIShape); + } } diff --git a/src/main/java/com/esri/core/geometry/OperatorExportToESRIShapeCursor.java b/src/main/java/com/esri/core/geometry/OperatorExportToESRIShapeCursor.java index 360f3830..fa364be9 100644 --- a/src/main/java/com/esri/core/geometry/OperatorExportToESRIShapeCursor.java +++ b/src/main/java/com/esri/core/geometry/OperatorExportToESRIShapeCursor.java @@ -30,885 +30,887 @@ import java.nio.ByteOrder; public class OperatorExportToESRIShapeCursor extends ByteBufferCursor { - private GeometryCursor m_geometryCursor; - private int m_exportFlags; - private ByteBuffer m_shapeBuffer; - private SimpleStateEnum simpleStateEnum = SimpleStateEnum.SIMPLE_UNKNOWN; - - public OperatorExportToESRIShapeCursor(int exportFlags, GeometryCursor geometryCursor) { - if (geometryCursor == null) - throw new GeometryException("invalid argument"); - - m_exportFlags = exportFlags; - m_geometryCursor = geometryCursor; - m_shapeBuffer = null; - } - - @Override - public long getByteBufferID() { - return m_geometryCursor.getGeometryID(); - } - - @Override - public SimpleStateEnum getSimpleState() { - return simpleStateEnum; - } - - @Override - public String getFeatureID() { - return m_geometryCursor.getFeatureID(); - } - - @Override - public boolean hasNext() { return m_geometryCursor != null && m_geometryCursor.hasNext(); } - - @Override - public ByteBuffer next() { - Geometry geometry; - if (hasNext()) { - geometry = m_geometryCursor.next(); - simpleStateEnum = geometry.getSimpleState(); - int size = exportToESRIShape(m_exportFlags, geometry, null); - if (m_shapeBuffer == null || size > m_shapeBuffer.capacity()) - m_shapeBuffer = ByteBuffer.allocate(size).order( - ByteOrder.LITTLE_ENDIAN); - exportToESRIShape(m_exportFlags, geometry, m_shapeBuffer); - return m_shapeBuffer; - } - return null; - } - - static int exportToESRIShape(int exportFlags, Geometry geometry, - ByteBuffer shapeBuffer) { - if (geometry == null) { - if (shapeBuffer != null) - shapeBuffer.putInt(0, ShapeType.ShapeNull); - - return 4; - } - - int type = geometry.getType().value(); - switch (type) { - case Geometry.GeometryType.Polygon: - return exportMultiPathToESRIShape(true, exportFlags, - (MultiPath) geometry, shapeBuffer); - case Geometry.GeometryType.Polyline: - return exportMultiPathToESRIShape(false, exportFlags, - (MultiPath) geometry, shapeBuffer); - case Geometry.GeometryType.MultiPoint: - return exportMultiPointToESRIShape(exportFlags, - (MultiPoint) geometry, shapeBuffer); - case Geometry.GeometryType.Point: - return exportPointToESRIShape(exportFlags, (Point) geometry, - shapeBuffer); - case Geometry.GeometryType.Envelope: - return exportEnvelopeToESRIShape(exportFlags, (Envelope) geometry, - shapeBuffer); - default: { - throw GeometryException.GeometryInternalError(); - // return -1; - } - } - } - - private static int exportEnvelopeToESRIShape(int exportFlags, - Envelope envelope, ByteBuffer shapeBuffer) { - boolean bExportZs = envelope.hasAttribute(Semantics.Z) - && (exportFlags & ShapeExportFlags.ShapeExportStripZs) == 0; - boolean bExportMs = envelope.hasAttribute(Semantics.M) - && (exportFlags & ShapeExportFlags.ShapeExportStripMs) == 0; - boolean bExportIDs = envelope.hasAttribute(Semantics.ID) - && (exportFlags & ShapeExportFlags.ShapeExportStripIDs) == 0; - boolean bArcViewNaNs = (exportFlags & ShapeExportFlags.ShapeExportTrueNaNs) == 0; - - boolean bEmpty = envelope.isEmpty(); - int partCount = bEmpty ? 0 : 1; - int pointCount = bEmpty ? 0 : 5; - - int size = (4 /* type */) + (4 * 8 /* envelope */) + (4 /* part count */) - + (4 /* point count */) + (partCount * 4 /* start indices */) - + pointCount * 2 * 8 /* xy coordinates */; - - if (bExportZs) - size += (2 * 8 /* min max */) + (pointCount * 8 /* zs */); - if (bExportMs) - size += (2 * 8 /* min max */) + (pointCount * 8 /* ms */); - if (bExportIDs) - size += (pointCount * 4 /* ids */); - - if (shapeBuffer == null) - return size; - else if (shapeBuffer.capacity() < size) - throw new GeometryException("buffer is too small"); - - int type; - - // Determine the shape type - if (!bExportZs && !bExportMs) { - if (bExportIDs) - type = ShapeType.ShapeGeneralPolygon - | ShapeModifiers.ShapeHasIDs; - else - type = ShapeType.ShapePolygon; - } else if (bExportZs && !bExportMs) { - if (bExportIDs) - type = ShapeType.ShapeGeneralPolygon - | ShapeModifiers.ShapeHasZs - | ShapeModifiers.ShapeHasIDs; - else - type = ShapeType.ShapePolygonZ; - } else if (bExportMs && !bExportZs) { - if (bExportIDs) - type = ShapeType.ShapeGeneralPolygon - | ShapeModifiers.ShapeHasMs - | ShapeModifiers.ShapeHasIDs; - else - type = ShapeType.ShapePolygonM; - } else { - if (bExportIDs) - type = ShapeType.ShapeGeneralPolygon - | ShapeModifiers.ShapeHasZs | ShapeModifiers.ShapeHasMs - | ShapeModifiers.ShapeHasIDs; - else - type = ShapeType.ShapePolygonZM; - } - - int offset = 0; - - // write type - shapeBuffer.putInt(offset, type); - offset += 4; - - // write Envelope - Envelope2D env = new Envelope2D(); - envelope.queryEnvelope2D(env); // calls _VerifyAllStreams - shapeBuffer.putDouble(offset, env.xmin); - offset += 8; - shapeBuffer.putDouble(offset, env.ymin); - offset += 8; - shapeBuffer.putDouble(offset, env.xmax); - offset += 8; - shapeBuffer.putDouble(offset, env.ymax); - offset += 8; - - // write part count - shapeBuffer.putInt(offset, partCount); - offset += 4; - - // write pointCount - shapeBuffer.putInt(offset, pointCount); - offset += 4; - - if (!bEmpty) { - // write start index - shapeBuffer.putInt(offset, 0); - offset += 4; - - // write xy coordinates - shapeBuffer.putDouble(offset, env.xmin); - offset += 8; - shapeBuffer.putDouble(offset, env.ymin); - offset += 8; - - shapeBuffer.putDouble(offset, env.xmin); - offset += 8; - shapeBuffer.putDouble(offset, env.ymax); - offset += 8; - - shapeBuffer.putDouble(offset, env.xmax); - offset += 8; - shapeBuffer.putDouble(offset, env.ymax); - offset += 8; - - shapeBuffer.putDouble(offset, env.xmax); - offset += 8; - shapeBuffer.putDouble(offset, env.ymin); - offset += 8; - - shapeBuffer.putDouble(offset, env.xmin); - offset += 8; - shapeBuffer.putDouble(offset, env.ymin); - offset += 8; - } - // write Zs - if (bExportZs) { - Envelope1D zInterval; - zInterval = envelope.queryInterval(Semantics.Z, 0); - - double zmin = bArcViewNaNs ? Interop - .translateToAVNaN(zInterval.vmin) : zInterval.vmin; - double zmax = bArcViewNaNs ? Interop - .translateToAVNaN(zInterval.vmax) : zInterval.vmax; - - // write min max values - shapeBuffer.putDouble(offset, zmin); - offset += 8; - shapeBuffer.putDouble(offset, zmax); - offset += 8; - - if (!bEmpty) { - // write arbitrary z values - shapeBuffer.putDouble(offset, zmin); - offset += 8; - shapeBuffer.putDouble(offset, zmax); - offset += 8; - shapeBuffer.putDouble(offset, zmin); - offset += 8; - shapeBuffer.putDouble(offset, zmax); - offset += 8; - shapeBuffer.putDouble(offset, zmin); - offset += 8; - } - } - // write Ms - if (bExportMs) { - Envelope1D mInterval; - mInterval = envelope.queryInterval(Semantics.M, 0); - - double mmin = bArcViewNaNs ? Interop - .translateToAVNaN(mInterval.vmin) : mInterval.vmin; - double mmax = bArcViewNaNs ? Interop - .translateToAVNaN(mInterval.vmax) : mInterval.vmax; - - // write min max values - shapeBuffer.putDouble(offset, mmin); - offset += 8; - shapeBuffer.putDouble(offset, mmax); - offset += 8; - - if (!bEmpty) { - // write arbitrary m values - shapeBuffer.putDouble(offset, mmin); - offset += 8; - shapeBuffer.putDouble(offset, mmax); - offset += 8; - shapeBuffer.putDouble(offset, mmin); - offset += 8; - shapeBuffer.putDouble(offset, mmax); - offset += 8; - shapeBuffer.putDouble(offset, mmin); - offset += 8; - } - } - - // write IDs - if (bExportIDs && !bEmpty) { - Envelope1D idInterval; - idInterval = envelope.queryInterval(Semantics.ID, 0); - - int idmin = (int) idInterval.vmin; - int idmax = (int) idInterval.vmax; - - // write arbitrary id values - shapeBuffer.putInt(offset, idmin); - offset += 4; - shapeBuffer.putInt(offset, idmax); - offset += 4; - shapeBuffer.putInt(offset, idmin); - offset += 4; - shapeBuffer.putInt(offset, idmax); - offset += 4; - shapeBuffer.putInt(offset, idmin); - offset += 4; - } - - return offset; - } - - private static int exportPointToESRIShape(int exportFlags, Point point, - ByteBuffer shapeBuffer) { - boolean bExportZ = point.hasAttribute(Semantics.Z) - && (exportFlags & ShapeExportFlags.ShapeExportStripZs) == 0; - boolean bExportM = point.hasAttribute(Semantics.M) - && (exportFlags & ShapeExportFlags.ShapeExportStripMs) == 0; - boolean bExportID = point.hasAttribute(Semantics.ID) - && (exportFlags & ShapeExportFlags.ShapeExportStripIDs) == 0; - boolean bArcViewNaNs = (exportFlags & ShapeExportFlags.ShapeExportTrueNaNs) == 0; - - int size = (4 /* type */) + (2 * 8 /* xy coordinate */); - - if (bExportZ) - size += 8; - if (bExportM) - size += 8; - if (bExportID) - size += 4; - - if (shapeBuffer == null) - return size; - else if (shapeBuffer.capacity() < size) - throw new GeometryException("buffer is too small"); - - int type; - - // Determine the shape type - if (!bExportZ && !bExportM) { - if (bExportID) - type = ShapeType.ShapeGeneralPoint | ShapeModifiers.ShapeHasIDs; - else - type = ShapeType.ShapePoint; - } else if (bExportZ && !bExportM) { - if (bExportID) - type = ShapeType.ShapeGeneralPoint | ShapeModifiers.ShapeHasZs - | ShapeModifiers.ShapeHasIDs; - else - type = ShapeType.ShapePointZ; - } else if (bExportM && !bExportZ) { - if (bExportID) - type = ShapeType.ShapeGeneralPoint | ShapeModifiers.ShapeHasMs - | ShapeModifiers.ShapeHasIDs; - else - type = ShapeType.ShapePointM; - } else { - if (bExportID) - type = ShapeType.ShapeGeneralPoint | ShapeModifiers.ShapeHasZs - | ShapeModifiers.ShapeHasMs - | ShapeModifiers.ShapeHasIDs; - else - type = ShapeType.ShapePointZM; - } - - int offset = 0; - - // write type - - shapeBuffer.putInt(offset, type); - offset += 4; - - boolean bEmpty = point.isEmpty(); - - // write xy - double x = !bEmpty ? point.getX() : NumberUtils.NaN(); - double y = !bEmpty ? point.getY() : NumberUtils.NaN(); - shapeBuffer.putDouble(offset, - bArcViewNaNs ? Interop.translateToAVNaN(x) : x); - offset += 8; - shapeBuffer.putDouble(offset, - bArcViewNaNs ? Interop.translateToAVNaN(y) : y); - offset += 8; - - // write Z - if (bExportZ) { - double z = !bEmpty ? point.getZ() : NumberUtils.NaN(); - shapeBuffer.putDouble(offset, - bArcViewNaNs ? Interop.translateToAVNaN(z) : z); - offset += 8; - } - - // WriteM - if (bExportM) { - double m = !bEmpty ? point.getM() : NumberUtils.NaN(); - shapeBuffer.putDouble(offset, - bArcViewNaNs ? Interop.translateToAVNaN(m) : m); - offset += 8; - } - - // write ID - if (bExportID) { - int id = !bEmpty ? point.getID() : 0; - shapeBuffer.putInt(offset, id); - offset += 4; - } - - return offset; - } - - private static int exportMultiPointToESRIShape(int exportFlags, - MultiPoint multipoint, ByteBuffer shapeBuffer) { - MultiPointImpl multipointImpl = (MultiPointImpl) multipoint._getImpl(); - boolean bExportZs = multipointImpl.hasAttribute(Semantics.Z) - && (exportFlags & ShapeExportFlags.ShapeExportStripZs) == 0; - boolean bExportMs = multipointImpl.hasAttribute(Semantics.M) - && (exportFlags & ShapeExportFlags.ShapeExportStripMs) == 0; - boolean bExportIDs = multipointImpl.hasAttribute(Semantics.ID) - && (exportFlags & ShapeExportFlags.ShapeExportStripIDs) == 0; - boolean bArcViewNaNs = (exportFlags & ShapeExportFlags.ShapeExportTrueNaNs) == 0; - - int pointCount = multipointImpl.getPointCount(); - - int size = (4 /* type */) + (4 * 8 /* envelope */) + (4 /* point count */) - + (pointCount * 2 * 8 /* xy coordinates */); - - if (bExportZs) - size += (2 * 8 /* min max */) + (pointCount * 8 /* zs */); - if (bExportMs) - size += (2 * 8 /* min max */) + (pointCount * 8 /* ms */); - if (bExportIDs) - size += pointCount * 4 /* ids */; - - if (size >= NumberUtils.intMax()) - throw new GeometryException("invalid call"); - - if (shapeBuffer == null) - return size; - else if (shapeBuffer.capacity() < size) - throw new GeometryException("buffer is too small"); - - int type; - - // Determine the shape type - if (!bExportZs && !bExportMs) { - if (bExportIDs) - type = ShapeType.ShapeGeneralMultiPoint - | ShapeModifiers.ShapeHasIDs; - else - type = ShapeType.ShapeMultiPoint; - } else if (bExportZs && !bExportMs) { - if (bExportIDs) - type = ShapeType.ShapeGeneralMultiPoint - | ShapeModifiers.ShapeHasZs - | ShapeModifiers.ShapeHasIDs; - else - type = ShapeType.ShapeMultiPointZ; - } else if (bExportMs && !bExportZs) { - if (bExportIDs) - type = ShapeType.ShapeGeneralMultiPoint - | ShapeModifiers.ShapeHasMs - | ShapeModifiers.ShapeHasIDs; - else - type = ShapeType.ShapeMultiPointM; - } else { - if (bExportIDs) - type = ShapeType.ShapeGeneralMultiPoint - | ShapeModifiers.ShapeHasZs | ShapeModifiers.ShapeHasMs - | ShapeModifiers.ShapeHasIDs; - else - type = ShapeType.ShapeMultiPointZM; - } - - // write type - int offset = 0; - - shapeBuffer.putInt(offset, type); - offset += 4; - - // write Envelope - Envelope2D env = new Envelope2D(); - multipointImpl.queryEnvelope2D(env); // calls _VerifyAllStreams - shapeBuffer.putDouble(offset, env.xmin); - offset += 8; - shapeBuffer.putDouble(offset, env.ymin); - offset += 8; - shapeBuffer.putDouble(offset, env.xmax); - offset += 8; - shapeBuffer.putDouble(offset, env.ymax); - offset += 8; - - // write point count - shapeBuffer.putInt(offset, pointCount); - offset += 4; - - if (pointCount > 0) { - // write xy coordinates - AttributeStreamBase positionStream = multipointImpl - .getAttributeStreamRef(Semantics.POSITION); - AttributeStreamOfDbl position = (AttributeStreamOfDbl) positionStream; - for (int i = 0; i < pointCount; i++) { - double x = position.read(2 * i); - double y = position.read(2 * i + 1); - shapeBuffer.putDouble(offset, x); - offset += 8; - shapeBuffer.putDouble(offset, y); - offset += 8; - } - } - - // write Zs - if (bExportZs) { - Envelope1D zInterval = multipointImpl.queryInterval(Semantics.Z, 0); - shapeBuffer.putDouble(offset, - bArcViewNaNs ? Interop.translateToAVNaN(zInterval.vmin) - : zInterval.vmin); - offset += 8; - shapeBuffer.putDouble(offset, - bArcViewNaNs ? Interop.translateToAVNaN(zInterval.vmax) - : zInterval.vmax); - offset += 8; - - if (pointCount > 0) { - if (multipointImpl._attributeStreamIsAllocated(Semantics.Z)) { - AttributeStreamOfDbl zs = (AttributeStreamOfDbl) multipointImpl - .getAttributeStreamRef(Semantics.Z); - for (int i = 0; i < pointCount; i++) { - double z = zs.read(i); - shapeBuffer.putDouble(offset, - bArcViewNaNs ? Interop.translateToAVNaN(z) : z); - offset += 8; - } - } else { - double z = VertexDescription.getDefaultValue(Semantics.Z); - - if (bArcViewNaNs) - z = Interop.translateToAVNaN(z); - - // Can we write a function that writes all these values at - // once instead of doing a for loop? - for (int i = 0; i < pointCount; i++) - shapeBuffer.putDouble(offset, z); - offset += 8; - } - } - } - - // write Ms - if (bExportMs) { - Envelope1D mInterval = multipointImpl.queryInterval(Semantics.M, 0); - shapeBuffer.putDouble(offset, - bArcViewNaNs ? Interop.translateToAVNaN(mInterval.vmin) - : mInterval.vmin); - offset += 8; - shapeBuffer.putDouble(offset, - bArcViewNaNs ? Interop.translateToAVNaN(mInterval.vmax) - : mInterval.vmax); - offset += 8; - - if (pointCount > 0) { - if (multipointImpl._attributeStreamIsAllocated(Semantics.M)) { - AttributeStreamOfDbl ms = (AttributeStreamOfDbl) multipointImpl - .getAttributeStreamRef(Semantics.M); - for (int i = 0; i < pointCount; i++) { - double m = ms.read(i); - shapeBuffer.putDouble(offset, - bArcViewNaNs ? Interop.translateToAVNaN(m) : m); - offset += 8; - } - } else { - double m = VertexDescription.getDefaultValue(Semantics.M); - - if (bArcViewNaNs) - m = Interop.translateToAVNaN(m); - - for (int i = 0; i < pointCount; i++) - shapeBuffer.putDouble(offset, m); - offset += 8; - } - } - } - - // write IDs - if (bExportIDs) { - if (pointCount > 0) { - if (multipointImpl._attributeStreamIsAllocated(Semantics.ID)) { - AttributeStreamOfInt32 ids = (AttributeStreamOfInt32) multipointImpl - .getAttributeStreamRef(Semantics.ID); - for (int i = 0; i < pointCount; i++) { - int id = ids.read(i); - shapeBuffer.putInt(offset, id); - offset += 4; - } - } else { - int id = (int) VertexDescription - .getDefaultValue(Semantics.ID); - for (int i = 0; i < pointCount; i++) - shapeBuffer.putInt(offset, id); - offset += 4; - } - } - } - - return offset; - } - - private static int exportMultiPathToESRIShape(boolean bPolygon, - int exportFlags, MultiPath multipath, ByteBuffer shapeBuffer) { - MultiPathImpl multipathImpl = (MultiPathImpl) multipath._getImpl(); - - boolean bExportZs = multipathImpl.hasAttribute(Semantics.Z) - && (exportFlags & ShapeExportFlags.ShapeExportStripZs) == 0; - boolean bExportMs = multipathImpl.hasAttribute(Semantics.M) - && (exportFlags & ShapeExportFlags.ShapeExportStripMs) == 0; - boolean bExportIDs = multipathImpl.hasAttribute(Semantics.ID) - && (exportFlags & ShapeExportFlags.ShapeExportStripIDs) == 0; - boolean bHasCurves = multipathImpl.hasNonLinearSegments(); - boolean bArcViewNaNs = (exportFlags & ShapeExportFlags.ShapeExportTrueNaNs) == 0; - - int partCount = multipathImpl.getPathCount(); - int pointCount = multipathImpl.getPointCount(); - - if (!bPolygon) { - for (int ipart = 0; ipart < partCount; ipart++) - if (multipath.isClosedPath(ipart)) - pointCount++; - } else - pointCount += partCount; - - int size = (4 /* type */) + (4 * 8 /* envelope */) + (4 /* part count */) - + (4 /* point count */) + (partCount * 4 /* start indices */) - + pointCount * 2 * 8 /* xy coordinates */; - - if (bExportZs) - size += (2 * 8 /* min max */) + (pointCount * 8 /* zs */); - if (bExportMs) - size += (2 * 8 /* min max */) + (pointCount * 8 /* ms */); - if (bExportIDs) - size += pointCount * 4 /* ids */; - if (bHasCurves) { - // to-do: curves - } - - if (size >= NumberUtils.intMax()) - throw new GeometryException("invalid call"); - - if (shapeBuffer == null) - return size; - else if (shapeBuffer.capacity() < size) - throw new GeometryException("buffer is too small"); - - int offset = 0; - - // Determine the shape type - int type; - if (!bExportZs && !bExportMs) { - if (bExportIDs || bHasCurves) { - type = bPolygon ? ShapeType.ShapeGeneralPolygon - : ShapeType.ShapeGeneralPolyline; - if (bExportIDs) - type |= ShapeModifiers.ShapeHasIDs; - if (bHasCurves) - type |= ShapeModifiers.ShapeHasCurves; - } else - type = bPolygon ? ShapeType.ShapePolygon - : ShapeType.ShapePolyline; - } else if (bExportZs && !bExportMs) { - if (bExportIDs || bHasCurves) { - type = bPolygon ? ShapeType.ShapeGeneralPolygon - : ShapeType.ShapeGeneralPolyline; - type |= ShapeModifiers.ShapeHasZs; - if (bExportIDs) - type |= ShapeModifiers.ShapeHasIDs; - if (bHasCurves) - type |= ShapeModifiers.ShapeHasCurves; - } else - type = bPolygon ? ShapeType.ShapePolygonZ - : ShapeType.ShapePolylineZ; - } else if (bExportMs && !bExportZs) { - if (bExportIDs || bHasCurves) { - type = bPolygon ? ShapeType.ShapeGeneralPolygon - : ShapeType.ShapeGeneralPolyline; - type |= ShapeModifiers.ShapeHasMs; - if (bExportIDs) - type |= ShapeModifiers.ShapeHasIDs; - if (bHasCurves) - type |= ShapeModifiers.ShapeHasCurves; - } else - type = bPolygon ? ShapeType.ShapePolygonM - : ShapeType.ShapePolylineM; - } else { - if (bExportIDs || bHasCurves) { - type = bPolygon ? ShapeType.ShapeGeneralPolygon - : ShapeType.ShapeGeneralPolyline; - type |= ShapeModifiers.ShapeHasZs | ShapeModifiers.ShapeHasMs; - if (bExportIDs) - type |= ShapeModifiers.ShapeHasIDs; - if (bHasCurves) - type |= ShapeModifiers.ShapeHasCurves; - } else - type = bPolygon ? ShapeType.ShapePolygonZM - : ShapeType.ShapePolylineZM; - } - - // write type - shapeBuffer.putInt(offset, type); - offset += 4; - - // write Envelope - Envelope2D env = new Envelope2D(); - multipathImpl.queryEnvelope2D(env); // calls _VerifyAllStreams - shapeBuffer.putDouble(offset, env.xmin); - offset += 8; - shapeBuffer.putDouble(offset, env.ymin); - offset += 8; - shapeBuffer.putDouble(offset, env.xmax); - offset += 8; - shapeBuffer.putDouble(offset, env.ymax); - offset += 8; - - // write part count - shapeBuffer.putInt(offset, partCount); - offset += 4; // to-do: return error if larger than 2^32 - 1 - - // write pointCount - shapeBuffer.putInt(offset, pointCount); - offset += 4; - - // write start indices for each part - int pointIndexDelta = 0; - for (int ipart = 0; ipart < partCount; ipart++) { - int istart = multipathImpl.getPathStart(ipart) + pointIndexDelta; - shapeBuffer.putInt(offset, istart); - offset += 4; - if (bPolygon || multipathImpl.isClosedPath(ipart)) - pointIndexDelta++; - } - - if (pointCount > 0) { - // write xy coordinates - AttributeStreamBase positionStream = multipathImpl - .getAttributeStreamRef(Semantics.POSITION); - AttributeStreamOfDbl position = (AttributeStreamOfDbl) positionStream; - - for (int ipart = 0; ipart < partCount; ipart++) { - int partStart = multipathImpl.getPathStart(ipart); - int partEnd = multipathImpl.getPathEnd(ipart); - for (int i = partStart; i < partEnd; i++) { - double x = position.read(2 * i); - double y = position.read(2 * i + 1); - - shapeBuffer.putDouble(offset, x); - offset += 8; - shapeBuffer.putDouble(offset, y); - offset += 8; - } - - // If the part is closed, then we need to duplicate the start - // point - if (bPolygon || multipathImpl.isClosedPath(ipart)) { - double x = position.read(2 * partStart); - double y = position.read(2 * partStart + 1); - - shapeBuffer.putDouble(offset, x); - offset += 8; - shapeBuffer.putDouble(offset, y); - offset += 8; - } - } - } - - // write Zs - if (bExportZs) { - Envelope1D zInterval = multipathImpl.queryInterval(Semantics.Z, 0); - shapeBuffer.putDouble(offset, - bArcViewNaNs ? Interop.translateToAVNaN(zInterval.vmin) - : zInterval.vmin); - offset += 8; - shapeBuffer.putDouble(offset, - bArcViewNaNs ? Interop.translateToAVNaN(zInterval.vmax) - : zInterval.vmax); - offset += 8; - - if (pointCount > 0) { - if (multipathImpl._attributeStreamIsAllocated(Semantics.Z)) { - AttributeStreamOfDbl zs = (AttributeStreamOfDbl) multipathImpl - .getAttributeStreamRef(Semantics.Z); - for (int ipart = 0; ipart < partCount; ipart++) { - int partStart = multipathImpl.getPathStart(ipart); - int partEnd = multipathImpl.getPathEnd(ipart); - for (int i = partStart; i < partEnd; i++) { - double z = zs.read(i); - shapeBuffer.putDouble(offset, - bArcViewNaNs ? Interop.translateToAVNaN(z) - : z); - offset += 8; - } - - // If the part is closed, then we need to duplicate the - // start z - if (bPolygon || multipathImpl.isClosedPath(ipart)) { - double z = zs.read(partStart); - shapeBuffer.putDouble(offset, z); - offset += 8; - } - } - } else { - double z = VertexDescription.getDefaultValue(Semantics.Z); - - if (bArcViewNaNs) - z = Interop.translateToAVNaN(z); - - for (int i = 0; i < pointCount; i++) - shapeBuffer.putDouble(offset, z); - offset += 8; - } - } - } - - // write Ms - if (bExportMs) { - Envelope1D mInterval = multipathImpl.queryInterval(Semantics.M, 0); - shapeBuffer.putDouble(offset, - bArcViewNaNs ? Interop.translateToAVNaN(mInterval.vmin) - : mInterval.vmin); - offset += 8; - shapeBuffer.putDouble(offset, - bArcViewNaNs ? Interop.translateToAVNaN(mInterval.vmax) - : mInterval.vmax); - offset += 8; - - if (pointCount > 0) { - if (multipathImpl._attributeStreamIsAllocated(Semantics.M)) { - AttributeStreamOfDbl ms = (AttributeStreamOfDbl) multipathImpl - .getAttributeStreamRef(Semantics.M); - for (int ipart = 0; ipart < partCount; ipart++) { - int partStart = multipathImpl.getPathStart(ipart); - int partEnd = multipathImpl.getPathEnd(ipart); - for (int i = partStart; i < partEnd; i++) { - double m = ms.read(i); - shapeBuffer.putDouble(offset, - bArcViewNaNs ? Interop.translateToAVNaN(m) - : m); - offset += 8; - } - - // If the part is closed, then we need to duplicate the - // start m - if (bPolygon || multipathImpl.isClosedPath(ipart)) { - double m = ms.read(partStart); - shapeBuffer.putDouble(offset, m); - offset += 8; - } - } - } else { - double m = VertexDescription.getDefaultValue(Semantics.M); - - if (bArcViewNaNs) - m = Interop.translateToAVNaN(m); - - for (int i = 0; i < pointCount; i++) - shapeBuffer.putDouble(offset, m); - offset += 8; - } - } - } - - // write Curves - if (bHasCurves) { - // to-do: We'll finish this later - } - - // write IDs - if (bExportIDs) { - if (pointCount > 0) { - if (multipathImpl._attributeStreamIsAllocated(Semantics.ID)) { - AttributeStreamOfInt32 ids = (AttributeStreamOfInt32) multipathImpl - .getAttributeStreamRef(Semantics.ID); - for (int ipart = 0; ipart < partCount; ipart++) { - int partStart = multipathImpl.getPathStart(ipart); - int partEnd = multipathImpl.getPathEnd(ipart); - for (int i = partStart; i < partEnd; i++) { - int id = ids.read(i); - shapeBuffer.putInt(offset, id); - offset += 4; - } - - // If the part is closed, then we need to duplicate the - // start id - if (bPolygon || multipathImpl.isClosedPath(ipart)) { - int id = ids.read(partStart); - shapeBuffer.putInt(offset, id); - offset += 4; - } - } - } else { - int id = (int) VertexDescription - .getDefaultValue(Semantics.ID); - for (int i = 0; i < pointCount; i++) - shapeBuffer.putInt(offset, id); - offset += 4; - } - } - } - - return offset; - } + private GeometryCursor m_geometryCursor; + private int m_exportFlags; + private ByteBuffer m_shapeBuffer; + private SimpleStateEnum simpleStateEnum = SimpleStateEnum.SIMPLE_UNKNOWN; + + public OperatorExportToESRIShapeCursor(int exportFlags, GeometryCursor geometryCursor) { + if (geometryCursor == null) + throw new GeometryException("invalid argument"); + + m_exportFlags = exportFlags; + m_geometryCursor = geometryCursor; + m_shapeBuffer = null; + } + + @Override + public long getByteBufferID() { + return m_geometryCursor.getGeometryID(); + } + + @Override + public SimpleStateEnum getSimpleState() { + return simpleStateEnum; + } + + @Override + public String getFeatureID() { + return m_geometryCursor.getFeatureID(); + } + + @Override + public boolean hasNext() { + return m_geometryCursor != null && m_geometryCursor.hasNext(); + } + + @Override + public ByteBuffer next() { + Geometry geometry; + if (hasNext()) { + geometry = m_geometryCursor.next(); + simpleStateEnum = geometry.getSimpleState(); + int size = exportToESRIShape(m_exportFlags, geometry, null); + if (m_shapeBuffer == null || size > m_shapeBuffer.capacity()) + m_shapeBuffer = ByteBuffer.allocate(size).order( + ByteOrder.LITTLE_ENDIAN); + exportToESRIShape(m_exportFlags, geometry, m_shapeBuffer); + return m_shapeBuffer; + } + return null; + } + + static int exportToESRIShape(int exportFlags, Geometry geometry, + ByteBuffer shapeBuffer) { + if (geometry == null) { + if (shapeBuffer != null) + shapeBuffer.putInt(0, ShapeType.ShapeNull); + + return 4; + } + + int type = geometry.getType().value(); + switch (type) { + case Geometry.GeometryType.Polygon: + return exportMultiPathToESRIShape(true, exportFlags, + (MultiPath) geometry, shapeBuffer); + case Geometry.GeometryType.Polyline: + return exportMultiPathToESRIShape(false, exportFlags, + (MultiPath) geometry, shapeBuffer); + case Geometry.GeometryType.MultiPoint: + return exportMultiPointToESRIShape(exportFlags, + (MultiPoint) geometry, shapeBuffer); + case Geometry.GeometryType.Point: + return exportPointToESRIShape(exportFlags, (Point) geometry, + shapeBuffer); + case Geometry.GeometryType.Envelope: + return exportEnvelopeToESRIShape(exportFlags, (Envelope) geometry, + shapeBuffer); + default: { + throw GeometryException.GeometryInternalError(); + // return -1; + } + } + } + + private static int exportEnvelopeToESRIShape(int exportFlags, + Envelope envelope, ByteBuffer shapeBuffer) { + boolean bExportZs = envelope.hasAttribute(Semantics.Z) + && (exportFlags & ShapeExportFlags.ShapeExportStripZs) == 0; + boolean bExportMs = envelope.hasAttribute(Semantics.M) + && (exportFlags & ShapeExportFlags.ShapeExportStripMs) == 0; + boolean bExportIDs = envelope.hasAttribute(Semantics.ID) + && (exportFlags & ShapeExportFlags.ShapeExportStripIDs) == 0; + boolean bArcViewNaNs = (exportFlags & ShapeExportFlags.ShapeExportTrueNaNs) == 0; + + boolean bEmpty = envelope.isEmpty(); + int partCount = bEmpty ? 0 : 1; + int pointCount = bEmpty ? 0 : 5; + + int size = (4 /* type */) + (4 * 8 /* envelope */) + (4 /* part count */) + + (4 /* point count */) + (partCount * 4 /* start indices */) + + pointCount * 2 * 8 /* xy coordinates */; + + if (bExportZs) + size += (2 * 8 /* min max */) + (pointCount * 8 /* zs */); + if (bExportMs) + size += (2 * 8 /* min max */) + (pointCount * 8 /* ms */); + if (bExportIDs) + size += (pointCount * 4 /* ids */); + + if (shapeBuffer == null) + return size; + else if (shapeBuffer.capacity() < size) + throw new GeometryException("buffer is too small"); + + int type; + + // Determine the shape type + if (!bExportZs && !bExportMs) { + if (bExportIDs) + type = ShapeType.ShapeGeneralPolygon + | ShapeModifiers.ShapeHasIDs; + else + type = ShapeType.ShapePolygon; + } else if (bExportZs && !bExportMs) { + if (bExportIDs) + type = ShapeType.ShapeGeneralPolygon + | ShapeModifiers.ShapeHasZs + | ShapeModifiers.ShapeHasIDs; + else + type = ShapeType.ShapePolygonZ; + } else if (bExportMs && !bExportZs) { + if (bExportIDs) + type = ShapeType.ShapeGeneralPolygon + | ShapeModifiers.ShapeHasMs + | ShapeModifiers.ShapeHasIDs; + else + type = ShapeType.ShapePolygonM; + } else { + if (bExportIDs) + type = ShapeType.ShapeGeneralPolygon + | ShapeModifiers.ShapeHasZs | ShapeModifiers.ShapeHasMs + | ShapeModifiers.ShapeHasIDs; + else + type = ShapeType.ShapePolygonZM; + } + + int offset = 0; + + // write type + shapeBuffer.putInt(offset, type); + offset += 4; + + // write Envelope + Envelope2D env = new Envelope2D(); + envelope.queryEnvelope2D(env); // calls _VerifyAllStreams + shapeBuffer.putDouble(offset, env.xmin); + offset += 8; + shapeBuffer.putDouble(offset, env.ymin); + offset += 8; + shapeBuffer.putDouble(offset, env.xmax); + offset += 8; + shapeBuffer.putDouble(offset, env.ymax); + offset += 8; + + // write part count + shapeBuffer.putInt(offset, partCount); + offset += 4; + + // write pointCount + shapeBuffer.putInt(offset, pointCount); + offset += 4; + + if (!bEmpty) { + // write start index + shapeBuffer.putInt(offset, 0); + offset += 4; + + // write xy coordinates + shapeBuffer.putDouble(offset, env.xmin); + offset += 8; + shapeBuffer.putDouble(offset, env.ymin); + offset += 8; + + shapeBuffer.putDouble(offset, env.xmin); + offset += 8; + shapeBuffer.putDouble(offset, env.ymax); + offset += 8; + + shapeBuffer.putDouble(offset, env.xmax); + offset += 8; + shapeBuffer.putDouble(offset, env.ymax); + offset += 8; + + shapeBuffer.putDouble(offset, env.xmax); + offset += 8; + shapeBuffer.putDouble(offset, env.ymin); + offset += 8; + + shapeBuffer.putDouble(offset, env.xmin); + offset += 8; + shapeBuffer.putDouble(offset, env.ymin); + offset += 8; + } + // write Zs + if (bExportZs) { + Envelope1D zInterval; + zInterval = envelope.queryInterval(Semantics.Z, 0); + + double zmin = bArcViewNaNs ? Interop + .translateToAVNaN(zInterval.vmin) : zInterval.vmin; + double zmax = bArcViewNaNs ? Interop + .translateToAVNaN(zInterval.vmax) : zInterval.vmax; + + // write min max values + shapeBuffer.putDouble(offset, zmin); + offset += 8; + shapeBuffer.putDouble(offset, zmax); + offset += 8; + + if (!bEmpty) { + // write arbitrary z values + shapeBuffer.putDouble(offset, zmin); + offset += 8; + shapeBuffer.putDouble(offset, zmax); + offset += 8; + shapeBuffer.putDouble(offset, zmin); + offset += 8; + shapeBuffer.putDouble(offset, zmax); + offset += 8; + shapeBuffer.putDouble(offset, zmin); + offset += 8; + } + } + // write Ms + if (bExportMs) { + Envelope1D mInterval; + mInterval = envelope.queryInterval(Semantics.M, 0); + + double mmin = bArcViewNaNs ? Interop + .translateToAVNaN(mInterval.vmin) : mInterval.vmin; + double mmax = bArcViewNaNs ? Interop + .translateToAVNaN(mInterval.vmax) : mInterval.vmax; + + // write min max values + shapeBuffer.putDouble(offset, mmin); + offset += 8; + shapeBuffer.putDouble(offset, mmax); + offset += 8; + + if (!bEmpty) { + // write arbitrary m values + shapeBuffer.putDouble(offset, mmin); + offset += 8; + shapeBuffer.putDouble(offset, mmax); + offset += 8; + shapeBuffer.putDouble(offset, mmin); + offset += 8; + shapeBuffer.putDouble(offset, mmax); + offset += 8; + shapeBuffer.putDouble(offset, mmin); + offset += 8; + } + } + + // write IDs + if (bExportIDs && !bEmpty) { + Envelope1D idInterval; + idInterval = envelope.queryInterval(Semantics.ID, 0); + + int idmin = (int) idInterval.vmin; + int idmax = (int) idInterval.vmax; + + // write arbitrary id values + shapeBuffer.putInt(offset, idmin); + offset += 4; + shapeBuffer.putInt(offset, idmax); + offset += 4; + shapeBuffer.putInt(offset, idmin); + offset += 4; + shapeBuffer.putInt(offset, idmax); + offset += 4; + shapeBuffer.putInt(offset, idmin); + offset += 4; + } + + return offset; + } + + private static int exportPointToESRIShape(int exportFlags, Point point, + ByteBuffer shapeBuffer) { + boolean bExportZ = point.hasAttribute(Semantics.Z) + && (exportFlags & ShapeExportFlags.ShapeExportStripZs) == 0; + boolean bExportM = point.hasAttribute(Semantics.M) + && (exportFlags & ShapeExportFlags.ShapeExportStripMs) == 0; + boolean bExportID = point.hasAttribute(Semantics.ID) + && (exportFlags & ShapeExportFlags.ShapeExportStripIDs) == 0; + boolean bArcViewNaNs = (exportFlags & ShapeExportFlags.ShapeExportTrueNaNs) == 0; + + int size = (4 /* type */) + (2 * 8 /* xy coordinate */); + + if (bExportZ) + size += 8; + if (bExportM) + size += 8; + if (bExportID) + size += 4; + + if (shapeBuffer == null) + return size; + else if (shapeBuffer.capacity() < size) + throw new GeometryException("buffer is too small"); + + int type; + + // Determine the shape type + if (!bExportZ && !bExportM) { + if (bExportID) + type = ShapeType.ShapeGeneralPoint | ShapeModifiers.ShapeHasIDs; + else + type = ShapeType.ShapePoint; + } else if (bExportZ && !bExportM) { + if (bExportID) + type = ShapeType.ShapeGeneralPoint | ShapeModifiers.ShapeHasZs + | ShapeModifiers.ShapeHasIDs; + else + type = ShapeType.ShapePointZ; + } else if (bExportM && !bExportZ) { + if (bExportID) + type = ShapeType.ShapeGeneralPoint | ShapeModifiers.ShapeHasMs + | ShapeModifiers.ShapeHasIDs; + else + type = ShapeType.ShapePointM; + } else { + if (bExportID) + type = ShapeType.ShapeGeneralPoint | ShapeModifiers.ShapeHasZs + | ShapeModifiers.ShapeHasMs + | ShapeModifiers.ShapeHasIDs; + else + type = ShapeType.ShapePointZM; + } + + int offset = 0; + + // write type + + shapeBuffer.putInt(offset, type); + offset += 4; + + boolean bEmpty = point.isEmpty(); + + // write xy + double x = !bEmpty ? point.getX() : NumberUtils.NaN(); + double y = !bEmpty ? point.getY() : NumberUtils.NaN(); + shapeBuffer.putDouble(offset, + bArcViewNaNs ? Interop.translateToAVNaN(x) : x); + offset += 8; + shapeBuffer.putDouble(offset, + bArcViewNaNs ? Interop.translateToAVNaN(y) : y); + offset += 8; + + // write Z + if (bExportZ) { + double z = !bEmpty ? point.getZ() : NumberUtils.NaN(); + shapeBuffer.putDouble(offset, + bArcViewNaNs ? Interop.translateToAVNaN(z) : z); + offset += 8; + } + + // WriteM + if (bExportM) { + double m = !bEmpty ? point.getM() : NumberUtils.NaN(); + shapeBuffer.putDouble(offset, + bArcViewNaNs ? Interop.translateToAVNaN(m) : m); + offset += 8; + } + + // write ID + if (bExportID) { + int id = !bEmpty ? point.getID() : 0; + shapeBuffer.putInt(offset, id); + offset += 4; + } + + return offset; + } + + private static int exportMultiPointToESRIShape(int exportFlags, + MultiPoint multipoint, ByteBuffer shapeBuffer) { + MultiPointImpl multipointImpl = (MultiPointImpl) multipoint._getImpl(); + boolean bExportZs = multipointImpl.hasAttribute(Semantics.Z) + && (exportFlags & ShapeExportFlags.ShapeExportStripZs) == 0; + boolean bExportMs = multipointImpl.hasAttribute(Semantics.M) + && (exportFlags & ShapeExportFlags.ShapeExportStripMs) == 0; + boolean bExportIDs = multipointImpl.hasAttribute(Semantics.ID) + && (exportFlags & ShapeExportFlags.ShapeExportStripIDs) == 0; + boolean bArcViewNaNs = (exportFlags & ShapeExportFlags.ShapeExportTrueNaNs) == 0; + + int pointCount = multipointImpl.getPointCount(); + + int size = (4 /* type */) + (4 * 8 /* envelope */) + (4 /* point count */) + + (pointCount * 2 * 8 /* xy coordinates */); + + if (bExportZs) + size += (2 * 8 /* min max */) + (pointCount * 8 /* zs */); + if (bExportMs) + size += (2 * 8 /* min max */) + (pointCount * 8 /* ms */); + if (bExportIDs) + size += pointCount * 4 /* ids */; + + if (size >= NumberUtils.intMax()) + throw new GeometryException("invalid call"); + + if (shapeBuffer == null) + return size; + else if (shapeBuffer.capacity() < size) + throw new GeometryException("buffer is too small"); + + int type; + + // Determine the shape type + if (!bExportZs && !bExportMs) { + if (bExportIDs) + type = ShapeType.ShapeGeneralMultiPoint + | ShapeModifiers.ShapeHasIDs; + else + type = ShapeType.ShapeMultiPoint; + } else if (bExportZs && !bExportMs) { + if (bExportIDs) + type = ShapeType.ShapeGeneralMultiPoint + | ShapeModifiers.ShapeHasZs + | ShapeModifiers.ShapeHasIDs; + else + type = ShapeType.ShapeMultiPointZ; + } else if (bExportMs && !bExportZs) { + if (bExportIDs) + type = ShapeType.ShapeGeneralMultiPoint + | ShapeModifiers.ShapeHasMs + | ShapeModifiers.ShapeHasIDs; + else + type = ShapeType.ShapeMultiPointM; + } else { + if (bExportIDs) + type = ShapeType.ShapeGeneralMultiPoint + | ShapeModifiers.ShapeHasZs | ShapeModifiers.ShapeHasMs + | ShapeModifiers.ShapeHasIDs; + else + type = ShapeType.ShapeMultiPointZM; + } + + // write type + int offset = 0; + + shapeBuffer.putInt(offset, type); + offset += 4; + + // write Envelope + Envelope2D env = new Envelope2D(); + multipointImpl.queryEnvelope2D(env); // calls _VerifyAllStreams + shapeBuffer.putDouble(offset, env.xmin); + offset += 8; + shapeBuffer.putDouble(offset, env.ymin); + offset += 8; + shapeBuffer.putDouble(offset, env.xmax); + offset += 8; + shapeBuffer.putDouble(offset, env.ymax); + offset += 8; + + // write point count + shapeBuffer.putInt(offset, pointCount); + offset += 4; + + if (pointCount > 0) { + // write xy coordinates + AttributeStreamBase positionStream = multipointImpl + .getAttributeStreamRef(Semantics.POSITION); + AttributeStreamOfDbl position = (AttributeStreamOfDbl) positionStream; + for (int i = 0; i < pointCount; i++) { + double x = position.read(2 * i); + double y = position.read(2 * i + 1); + shapeBuffer.putDouble(offset, x); + offset += 8; + shapeBuffer.putDouble(offset, y); + offset += 8; + } + } + + // write Zs + if (bExportZs) { + Envelope1D zInterval = multipointImpl.queryInterval(Semantics.Z, 0); + shapeBuffer.putDouble(offset, + bArcViewNaNs ? Interop.translateToAVNaN(zInterval.vmin) + : zInterval.vmin); + offset += 8; + shapeBuffer.putDouble(offset, + bArcViewNaNs ? Interop.translateToAVNaN(zInterval.vmax) + : zInterval.vmax); + offset += 8; + + if (pointCount > 0) { + if (multipointImpl._attributeStreamIsAllocated(Semantics.Z)) { + AttributeStreamOfDbl zs = (AttributeStreamOfDbl) multipointImpl + .getAttributeStreamRef(Semantics.Z); + for (int i = 0; i < pointCount; i++) { + double z = zs.read(i); + shapeBuffer.putDouble(offset, + bArcViewNaNs ? Interop.translateToAVNaN(z) : z); + offset += 8; + } + } else { + double z = VertexDescription.getDefaultValue(Semantics.Z); + + if (bArcViewNaNs) + z = Interop.translateToAVNaN(z); + + // Can we write a function that writes all these values at + // once instead of doing a for loop? + for (int i = 0; i < pointCount; i++) + shapeBuffer.putDouble(offset, z); + offset += 8; + } + } + } + + // write Ms + if (bExportMs) { + Envelope1D mInterval = multipointImpl.queryInterval(Semantics.M, 0); + shapeBuffer.putDouble(offset, + bArcViewNaNs ? Interop.translateToAVNaN(mInterval.vmin) + : mInterval.vmin); + offset += 8; + shapeBuffer.putDouble(offset, + bArcViewNaNs ? Interop.translateToAVNaN(mInterval.vmax) + : mInterval.vmax); + offset += 8; + + if (pointCount > 0) { + if (multipointImpl._attributeStreamIsAllocated(Semantics.M)) { + AttributeStreamOfDbl ms = (AttributeStreamOfDbl) multipointImpl + .getAttributeStreamRef(Semantics.M); + for (int i = 0; i < pointCount; i++) { + double m = ms.read(i); + shapeBuffer.putDouble(offset, + bArcViewNaNs ? Interop.translateToAVNaN(m) : m); + offset += 8; + } + } else { + double m = VertexDescription.getDefaultValue(Semantics.M); + + if (bArcViewNaNs) + m = Interop.translateToAVNaN(m); + + for (int i = 0; i < pointCount; i++) + shapeBuffer.putDouble(offset, m); + offset += 8; + } + } + } + + // write IDs + if (bExportIDs) { + if (pointCount > 0) { + if (multipointImpl._attributeStreamIsAllocated(Semantics.ID)) { + AttributeStreamOfInt32 ids = (AttributeStreamOfInt32) multipointImpl + .getAttributeStreamRef(Semantics.ID); + for (int i = 0; i < pointCount; i++) { + int id = ids.read(i); + shapeBuffer.putInt(offset, id); + offset += 4; + } + } else { + int id = (int) VertexDescription + .getDefaultValue(Semantics.ID); + for (int i = 0; i < pointCount; i++) + shapeBuffer.putInt(offset, id); + offset += 4; + } + } + } + + return offset; + } + + private static int exportMultiPathToESRIShape(boolean bPolygon, + int exportFlags, MultiPath multipath, ByteBuffer shapeBuffer) { + MultiPathImpl multipathImpl = (MultiPathImpl) multipath._getImpl(); + + boolean bExportZs = multipathImpl.hasAttribute(Semantics.Z) + && (exportFlags & ShapeExportFlags.ShapeExportStripZs) == 0; + boolean bExportMs = multipathImpl.hasAttribute(Semantics.M) + && (exportFlags & ShapeExportFlags.ShapeExportStripMs) == 0; + boolean bExportIDs = multipathImpl.hasAttribute(Semantics.ID) + && (exportFlags & ShapeExportFlags.ShapeExportStripIDs) == 0; + boolean bHasCurves = multipathImpl.hasNonLinearSegments(); + boolean bArcViewNaNs = (exportFlags & ShapeExportFlags.ShapeExportTrueNaNs) == 0; + + int partCount = multipathImpl.getPathCount(); + int pointCount = multipathImpl.getPointCount(); + + if (!bPolygon) { + for (int ipart = 0; ipart < partCount; ipart++) + if (multipath.isClosedPath(ipart)) + pointCount++; + } else + pointCount += partCount; + + int size = (4 /* type */) + (4 * 8 /* envelope */) + (4 /* part count */) + + (4 /* point count */) + (partCount * 4 /* start indices */) + + pointCount * 2 * 8 /* xy coordinates */; + + if (bExportZs) + size += (2 * 8 /* min max */) + (pointCount * 8 /* zs */); + if (bExportMs) + size += (2 * 8 /* min max */) + (pointCount * 8 /* ms */); + if (bExportIDs) + size += pointCount * 4 /* ids */; + if (bHasCurves) { + // to-do: curves + } + + if (size >= NumberUtils.intMax()) + throw new GeometryException("invalid call"); + + if (shapeBuffer == null) + return size; + else if (shapeBuffer.capacity() < size) + throw new GeometryException("buffer is too small"); + + int offset = 0; + + // Determine the shape type + int type; + if (!bExportZs && !bExportMs) { + if (bExportIDs || bHasCurves) { + type = bPolygon ? ShapeType.ShapeGeneralPolygon + : ShapeType.ShapeGeneralPolyline; + if (bExportIDs) + type |= ShapeModifiers.ShapeHasIDs; + if (bHasCurves) + type |= ShapeModifiers.ShapeHasCurves; + } else + type = bPolygon ? ShapeType.ShapePolygon + : ShapeType.ShapePolyline; + } else if (bExportZs && !bExportMs) { + if (bExportIDs || bHasCurves) { + type = bPolygon ? ShapeType.ShapeGeneralPolygon + : ShapeType.ShapeGeneralPolyline; + type |= ShapeModifiers.ShapeHasZs; + if (bExportIDs) + type |= ShapeModifiers.ShapeHasIDs; + if (bHasCurves) + type |= ShapeModifiers.ShapeHasCurves; + } else + type = bPolygon ? ShapeType.ShapePolygonZ + : ShapeType.ShapePolylineZ; + } else if (bExportMs && !bExportZs) { + if (bExportIDs || bHasCurves) { + type = bPolygon ? ShapeType.ShapeGeneralPolygon + : ShapeType.ShapeGeneralPolyline; + type |= ShapeModifiers.ShapeHasMs; + if (bExportIDs) + type |= ShapeModifiers.ShapeHasIDs; + if (bHasCurves) + type |= ShapeModifiers.ShapeHasCurves; + } else + type = bPolygon ? ShapeType.ShapePolygonM + : ShapeType.ShapePolylineM; + } else { + if (bExportIDs || bHasCurves) { + type = bPolygon ? ShapeType.ShapeGeneralPolygon + : ShapeType.ShapeGeneralPolyline; + type |= ShapeModifiers.ShapeHasZs | ShapeModifiers.ShapeHasMs; + if (bExportIDs) + type |= ShapeModifiers.ShapeHasIDs; + if (bHasCurves) + type |= ShapeModifiers.ShapeHasCurves; + } else + type = bPolygon ? ShapeType.ShapePolygonZM + : ShapeType.ShapePolylineZM; + } + + // write type + shapeBuffer.putInt(offset, type); + offset += 4; + + // write Envelope + Envelope2D env = new Envelope2D(); + multipathImpl.queryEnvelope2D(env); // calls _VerifyAllStreams + shapeBuffer.putDouble(offset, env.xmin); + offset += 8; + shapeBuffer.putDouble(offset, env.ymin); + offset += 8; + shapeBuffer.putDouble(offset, env.xmax); + offset += 8; + shapeBuffer.putDouble(offset, env.ymax); + offset += 8; + + // write part count + shapeBuffer.putInt(offset, partCount); + offset += 4; // to-do: return error if larger than 2^32 - 1 + + // write pointCount + shapeBuffer.putInt(offset, pointCount); + offset += 4; + + // write start indices for each part + int pointIndexDelta = 0; + for (int ipart = 0; ipart < partCount; ipart++) { + int istart = multipathImpl.getPathStart(ipart) + pointIndexDelta; + shapeBuffer.putInt(offset, istart); + offset += 4; + if (bPolygon || multipathImpl.isClosedPath(ipart)) + pointIndexDelta++; + } + + if (pointCount > 0) { + // write xy coordinates + AttributeStreamBase positionStream = multipathImpl + .getAttributeStreamRef(Semantics.POSITION); + AttributeStreamOfDbl position = (AttributeStreamOfDbl) positionStream; + + for (int ipart = 0; ipart < partCount; ipart++) { + int partStart = multipathImpl.getPathStart(ipart); + int partEnd = multipathImpl.getPathEnd(ipart); + for (int i = partStart; i < partEnd; i++) { + double x = position.read(2 * i); + double y = position.read(2 * i + 1); + + shapeBuffer.putDouble(offset, x); + offset += 8; + shapeBuffer.putDouble(offset, y); + offset += 8; + } + + // If the part is closed, then we need to duplicate the start + // point + if (bPolygon || multipathImpl.isClosedPath(ipart)) { + double x = position.read(2 * partStart); + double y = position.read(2 * partStart + 1); + + shapeBuffer.putDouble(offset, x); + offset += 8; + shapeBuffer.putDouble(offset, y); + offset += 8; + } + } + } + + // write Zs + if (bExportZs) { + Envelope1D zInterval = multipathImpl.queryInterval(Semantics.Z, 0); + shapeBuffer.putDouble(offset, + bArcViewNaNs ? Interop.translateToAVNaN(zInterval.vmin) + : zInterval.vmin); + offset += 8; + shapeBuffer.putDouble(offset, + bArcViewNaNs ? Interop.translateToAVNaN(zInterval.vmax) + : zInterval.vmax); + offset += 8; + + if (pointCount > 0) { + if (multipathImpl._attributeStreamIsAllocated(Semantics.Z)) { + AttributeStreamOfDbl zs = (AttributeStreamOfDbl) multipathImpl + .getAttributeStreamRef(Semantics.Z); + for (int ipart = 0; ipart < partCount; ipart++) { + int partStart = multipathImpl.getPathStart(ipart); + int partEnd = multipathImpl.getPathEnd(ipart); + for (int i = partStart; i < partEnd; i++) { + double z = zs.read(i); + shapeBuffer.putDouble(offset, + bArcViewNaNs ? Interop.translateToAVNaN(z) + : z); + offset += 8; + } + + // If the part is closed, then we need to duplicate the + // start z + if (bPolygon || multipathImpl.isClosedPath(ipart)) { + double z = zs.read(partStart); + shapeBuffer.putDouble(offset, z); + offset += 8; + } + } + } else { + double z = VertexDescription.getDefaultValue(Semantics.Z); + + if (bArcViewNaNs) + z = Interop.translateToAVNaN(z); + + for (int i = 0; i < pointCount; i++) + shapeBuffer.putDouble(offset, z); + offset += 8; + } + } + } + + // write Ms + if (bExportMs) { + Envelope1D mInterval = multipathImpl.queryInterval(Semantics.M, 0); + shapeBuffer.putDouble(offset, + bArcViewNaNs ? Interop.translateToAVNaN(mInterval.vmin) + : mInterval.vmin); + offset += 8; + shapeBuffer.putDouble(offset, + bArcViewNaNs ? Interop.translateToAVNaN(mInterval.vmax) + : mInterval.vmax); + offset += 8; + + if (pointCount > 0) { + if (multipathImpl._attributeStreamIsAllocated(Semantics.M)) { + AttributeStreamOfDbl ms = (AttributeStreamOfDbl) multipathImpl + .getAttributeStreamRef(Semantics.M); + for (int ipart = 0; ipart < partCount; ipart++) { + int partStart = multipathImpl.getPathStart(ipart); + int partEnd = multipathImpl.getPathEnd(ipart); + for (int i = partStart; i < partEnd; i++) { + double m = ms.read(i); + shapeBuffer.putDouble(offset, + bArcViewNaNs ? Interop.translateToAVNaN(m) + : m); + offset += 8; + } + + // If the part is closed, then we need to duplicate the + // start m + if (bPolygon || multipathImpl.isClosedPath(ipart)) { + double m = ms.read(partStart); + shapeBuffer.putDouble(offset, m); + offset += 8; + } + } + } else { + double m = VertexDescription.getDefaultValue(Semantics.M); + + if (bArcViewNaNs) + m = Interop.translateToAVNaN(m); + + for (int i = 0; i < pointCount; i++) + shapeBuffer.putDouble(offset, m); + offset += 8; + } + } + } + + // write Curves + if (bHasCurves) { + // to-do: We'll finish this later + } + + // write IDs + if (bExportIDs) { + if (pointCount > 0) { + if (multipathImpl._attributeStreamIsAllocated(Semantics.ID)) { + AttributeStreamOfInt32 ids = (AttributeStreamOfInt32) multipathImpl + .getAttributeStreamRef(Semantics.ID); + for (int ipart = 0; ipart < partCount; ipart++) { + int partStart = multipathImpl.getPathStart(ipart); + int partEnd = multipathImpl.getPathEnd(ipart); + for (int i = partStart; i < partEnd; i++) { + int id = ids.read(i); + shapeBuffer.putInt(offset, id); + offset += 4; + } + + // If the part is closed, then we need to duplicate the + // start id + if (bPolygon || multipathImpl.isClosedPath(ipart)) { + int id = ids.read(partStart); + shapeBuffer.putInt(offset, id); + offset += 4; + } + } + } else { + int id = (int) VertexDescription + .getDefaultValue(Semantics.ID); + for (int i = 0; i < pointCount; i++) + shapeBuffer.putInt(offset, id); + offset += 4; + } + } + } + + return offset; + } } diff --git a/src/main/java/com/esri/core/geometry/OperatorExportToESRIShapeLocal.java b/src/main/java/com/esri/core/geometry/OperatorExportToESRIShapeLocal.java index ce3c7b4c..071a9428 100644 --- a/src/main/java/com/esri/core/geometry/OperatorExportToESRIShapeLocal.java +++ b/src/main/java/com/esri/core/geometry/OperatorExportToESRIShapeLocal.java @@ -32,23 +32,23 @@ */ class OperatorExportToESRIShapeLocal extends OperatorExportToESRIShape { - @Override - public ByteBufferCursor execute(int exportFlags, GeometryCursor geometryCursor) { - return new OperatorExportToESRIShapeCursor(exportFlags, geometryCursor); - } - - @Override - public ByteBuffer execute(int exportFlags, Geometry geometry) { - ByteBuffer shapeBuffer = null; - int size = OperatorExportToESRIShapeCursor.exportToESRIShape(exportFlags, geometry, shapeBuffer); - shapeBuffer = ByteBuffer.allocate(size).order(ByteOrder.LITTLE_ENDIAN); - OperatorExportToESRIShapeCursor.exportToESRIShape(exportFlags, geometry, shapeBuffer); - return shapeBuffer; - } - - @Override - public int execute(int exportFlags, Geometry geometry, ByteBuffer shapeBuffer) { - shapeBuffer.order(ByteOrder.LITTLE_ENDIAN); - return OperatorExportToESRIShapeCursor.exportToESRIShape(exportFlags, geometry, shapeBuffer); - } + @Override + public ByteBufferCursor execute(int exportFlags, GeometryCursor geometryCursor) { + return new OperatorExportToESRIShapeCursor(exportFlags, geometryCursor); + } + + @Override + public ByteBuffer execute(int exportFlags, Geometry geometry) { + ByteBuffer shapeBuffer = null; + int size = OperatorExportToESRIShapeCursor.exportToESRIShape(exportFlags, geometry, shapeBuffer); + shapeBuffer = ByteBuffer.allocate(size).order(ByteOrder.LITTLE_ENDIAN); + OperatorExportToESRIShapeCursor.exportToESRIShape(exportFlags, geometry, shapeBuffer); + return shapeBuffer; + } + + @Override + public int execute(int exportFlags, Geometry geometry, ByteBuffer shapeBuffer) { + shapeBuffer.order(ByteOrder.LITTLE_ENDIAN); + return OperatorExportToESRIShapeCursor.exportToESRIShape(exportFlags, geometry, shapeBuffer); + } } diff --git a/src/main/java/com/esri/core/geometry/OperatorExportToGeoJson.java b/src/main/java/com/esri/core/geometry/OperatorExportToGeoJson.java index 656070de..52af8dfe 100644 --- a/src/main/java/com/esri/core/geometry/OperatorExportToGeoJson.java +++ b/src/main/java/com/esri/core/geometry/OperatorExportToGeoJson.java @@ -28,57 +28,57 @@ * Export to GeoJson format. */ public abstract class OperatorExportToGeoJson extends Operator { - @Override - public Type getType() { - return Type.ExportToGeoJson; - } + @Override + public Type getType() { + return Type.ExportToGeoJson; + } - /** - * Performs the ExportToGeoJson operation - * - * @param spatialReference The SpatialReference of the Geometry. Will be written as "crs":null if the spatialReference is null. - * @param geometryCursor The cursor of geometries to write as GeoJson. - * @return Returns a JsonCursor. - */ - public abstract StringCursor execute(SpatialReference spatialReference, GeometryCursor geometryCursor); + /** + * Performs the ExportToGeoJson operation + * + * @param spatialReference The SpatialReference of the Geometry. Will be written as "crs":null if the spatialReference is null. + * @param geometryCursor The cursor of geometries to write as GeoJson. + * @return Returns a JsonCursor. + */ + public abstract StringCursor execute(SpatialReference spatialReference, GeometryCursor geometryCursor); - /** - * Performs the ExportToGeoJson operation - * - * @param spatialReference The SpatialReference of the Geometry. Will be written as "crs":null if the spatialReference is null. - * @param geometry The Geometry to write as GeoJson. - * @return Returns a string in GeoJson format. - */ - public abstract String execute(SpatialReference spatialReference, Geometry geometry); + /** + * Performs the ExportToGeoJson operation + * + * @param spatialReference The SpatialReference of the Geometry. Will be written as "crs":null if the spatialReference is null. + * @param geometry The Geometry to write as GeoJson. + * @return Returns a string in GeoJson format. + */ + public abstract String execute(SpatialReference spatialReference, Geometry geometry); - /** - * Performs the ExportToGeoJson operation - * - * @param exportFlags Use the {@link GeoJsonExportFlags} interface. - * @param spatialReference The SpatialReference of the Geometry. Will be written as "crs":null if the spatialReference is null. - * @param geometry The Geometry to write as GeoJson. - * @return Returns a string in GeoJson format. - */ - public abstract String execute(int exportFlags, SpatialReference spatialReference, Geometry geometry); + /** + * Performs the ExportToGeoJson operation + * + * @param exportFlags Use the {@link GeoJsonExportFlags} interface. + * @param spatialReference The SpatialReference of the Geometry. Will be written as "crs":null if the spatialReference is null. + * @param geometry The Geometry to write as GeoJson. + * @return Returns a string in GeoJson format. + */ + public abstract String execute(int exportFlags, SpatialReference spatialReference, Geometry geometry); - /** - * Performs the ExportToGeoJson operation. Will not write out a spatial reference or crs tag. Assumes the geometry is in wgs84. - * - * @param geometry The Geometry to write as GeoJson. - * @return Returns a string in GeoJson format. - */ - public abstract String execute(Geometry geometry); + /** + * Performs the ExportToGeoJson operation. Will not write out a spatial reference or crs tag. Assumes the geometry is in wgs84. + * + * @param geometry The Geometry to write as GeoJson. + * @return Returns a string in GeoJson format. + */ + public abstract String execute(Geometry geometry); - /** - * Performs the ExportToGeoJson operation on a spatial reference. - * - * @param export_flags The flags used for the export. - * @param spatial_reference The spatial reference being exported. Cannot be null. - * @return Returns the crs value object. - */ - public abstract String exportSpatialReference(int export_flags, SpatialReference spatial_reference); + /** + * Performs the ExportToGeoJson operation on a spatial reference. + * + * @param export_flags The flags used for the export. + * @param spatial_reference The spatial reference being exported. Cannot be null. + * @return Returns the crs value object. + */ + public abstract String exportSpatialReference(int export_flags, SpatialReference spatial_reference); - public static OperatorExportToGeoJson local() { - return (OperatorExportToGeoJson) OperatorFactoryLocal.getInstance().getOperator(Type.ExportToGeoJson); - } + public static OperatorExportToGeoJson local() { + return (OperatorExportToGeoJson) OperatorFactoryLocal.getInstance().getOperator(Type.ExportToGeoJson); + } } diff --git a/src/main/java/com/esri/core/geometry/OperatorExportToGeoJsonCursor.java b/src/main/java/com/esri/core/geometry/OperatorExportToGeoJsonCursor.java index b5713159..33c1e926 100644 --- a/src/main/java/com/esri/core/geometry/OperatorExportToGeoJsonCursor.java +++ b/src/main/java/com/esri/core/geometry/OperatorExportToGeoJsonCursor.java @@ -48,768 +48,770 @@ import com.esri.core.geometry.VertexDescription.Semantics; public class OperatorExportToGeoJsonCursor extends StringCursor { - private GeometryCursor m_geometryCursor; - private SpatialReference m_spatialReference; - private int m_export_flags; - private SimpleStateEnum simpleStateEnum = SimpleStateEnum.SIMPLE_UNKNOWN; - - public OperatorExportToGeoJsonCursor(int export_flags, SpatialReference spatialReference, - GeometryCursor geometryCursor) { - if (geometryCursor == null) - throw new IllegalArgumentException(); - - m_export_flags = export_flags; - m_spatialReference = spatialReference; - m_geometryCursor = geometryCursor; - } + private GeometryCursor m_geometryCursor; + private SpatialReference m_spatialReference; + private int m_export_flags; + private SimpleStateEnum simpleStateEnum = SimpleStateEnum.SIMPLE_UNKNOWN; + + public OperatorExportToGeoJsonCursor(int export_flags, SpatialReference spatialReference, + GeometryCursor geometryCursor) { + if (geometryCursor == null) + throw new IllegalArgumentException(); + + m_export_flags = export_flags; + m_spatialReference = spatialReference; + m_geometryCursor = geometryCursor; + } + + @Override + public boolean hasNext() { + return m_geometryCursor != null && m_geometryCursor.hasNext(); + } + + @Override + public long getID() { + return m_geometryCursor.getGeometryID(); + } - @Override - public boolean hasNext() { return m_geometryCursor != null && m_geometryCursor.hasNext(); } + @Override + public SimpleStateEnum getSimpleState() { + return simpleStateEnum; + } - @Override - public long getID() { - return m_geometryCursor.getGeometryID(); - } + @Override + public String getFeatureID() { + return m_geometryCursor.getFeatureID(); + } - @Override - public SimpleStateEnum getSimpleState() { - return simpleStateEnum; - } + @Override + public String next() { + Geometry geometry; + if (hasNext()) { + geometry = m_geometryCursor.next(); + simpleStateEnum = geometry.getSimpleState(); + return exportToGeoJson(m_export_flags, geometry, m_spatialReference); + } + return null; + } - @Override - public String getFeatureID() { - return m_geometryCursor.getFeatureID(); - } + // Mirrors wkt + static String exportToGeoJson(int export_flags, Geometry geometry, SpatialReference spatial_reference) { - @Override - public String next() { - Geometry geometry; - if (hasNext()) { - geometry = m_geometryCursor.next(); - simpleStateEnum = geometry.getSimpleState(); - return exportToGeoJson(m_export_flags, geometry, m_spatialReference); - } - return null; - } + if (geometry == null) + throw new IllegalArgumentException(""); - // Mirrors wkt - static String exportToGeoJson(int export_flags, Geometry geometry, SpatialReference spatial_reference) { + JsonWriter json_writer = new JsonStringWriter(); - if (geometry == null) - throw new IllegalArgumentException(""); + json_writer.startObject(); - JsonWriter json_writer = new JsonStringWriter(); + exportGeometryToGeoJson_(export_flags, geometry, json_writer); - json_writer.startObject(); + if ((export_flags & GeoJsonExportFlags.geoJsonExportSkipCRS) == 0) { + json_writer.addFieldName("crs"); + exportSpatialReference(export_flags, spatial_reference, json_writer); + } - exportGeometryToGeoJson_(export_flags, geometry, json_writer); + json_writer.endObject(); - if ((export_flags & GeoJsonExportFlags.geoJsonExportSkipCRS) == 0) { - json_writer.addFieldName("crs"); - exportSpatialReference(export_flags, spatial_reference, json_writer); - } + return (String) json_writer.getJson(); + } - json_writer.endObject(); + static String exportSpatialReference(int export_flags, SpatialReference spatial_reference) { + if (spatial_reference == null || (export_flags & GeoJsonExportFlags.geoJsonExportSkipCRS) != 0) + throw new IllegalArgumentException(""); + + JsonWriter json_writer = new JsonStringWriter(); + exportSpatialReference(export_flags, spatial_reference, json_writer); - return (String) json_writer.getJson(); - } + return (String) json_writer.getJson(); + } + + private static void exportGeometryToGeoJson_(int export_flags, Geometry geometry, JsonWriter json_writer) { + int type = geometry.getType().value(); + switch (type) { + case Geometry.GeometryType.Polygon: + exportPolygonToGeoJson_(export_flags, (Polygon) geometry, json_writer); + return; + + case Geometry.GeometryType.Polyline: + exportPolylineToGeoJson_(export_flags, (Polyline) geometry, json_writer); + return; + + case Geometry.GeometryType.MultiPoint: + exportMultiPointToGeoJson_(export_flags, (MultiPoint) geometry, json_writer); + return; + + case Geometry.GeometryType.Point: + exportPointToGeoJson_(export_flags, (Point) geometry, json_writer); + return; + + case Geometry.GeometryType.Envelope: + exportEnvelopeToGeoJson_(export_flags, (Envelope) geometry, + json_writer); + return; + + default: + throw new RuntimeException("not implemented for this geometry type"); + } + } + + private static void exportSpatialReference(int export_flags, SpatialReference spatial_reference, + JsonWriter json_writer) { + if (spatial_reference != null) { + int wkid = spatial_reference.getLatestID(); + + if (wkid <= 0) + throw new GeometryException("invalid call"); + + json_writer.startObject(); + + json_writer.addFieldName("type"); + + json_writer.addValueString("name"); + + json_writer.addFieldName("properties"); + json_writer.startObject(); + + json_writer.addFieldName("name"); + + String authority = ((SpatialReferenceImpl) spatial_reference).getAuthority(); + authority = authority.toUpperCase(); + StringBuilder crs_identifier = new StringBuilder(authority); + crs_identifier.append(':'); + crs_identifier.append(wkid); + json_writer.addValueString(crs_identifier.toString()); + + json_writer.endObject(); + + json_writer.endObject(); + } else { + json_writer.addValueNull(); + } + } + + // Mirrors wkt + private static void exportPolygonToGeoJson_(int export_flags, Polygon polygon, JsonWriter json_writer) { + MultiPathImpl polygon_impl = (MultiPathImpl) (polygon._getImpl()); + + if ((export_flags & GeoJsonExportFlags.geoJsonExportFailIfNotSimple) != 0) { + int simple = polygon_impl.getIsSimple(0.0); + + if (simple != MultiPathImpl.GeometryXSimple.Strong) + throw new GeometryException("corrupted geometry"); + } + + int point_count = polygon.getPointCount(); + int polygon_count = polygon_impl.getOGCPolygonCount(); + + if (point_count > 0 && polygon_count == 0) + throw new GeometryException("corrupted geometry"); + + int precision = 17 - (31 & (export_flags >> 13)); + boolean bFixedPoint = (GeoJsonExportFlags.geoJsonExportPrecisionFixedPoint & export_flags) != 0; + boolean b_export_zs = polygon_impl.hasAttribute(VertexDescription.Semantics.Z) + && (export_flags & GeoJsonExportFlags.geoJsonExportStripZs) == 0; + boolean b_export_ms = polygon_impl.hasAttribute(VertexDescription.Semantics.M) + && (export_flags & GeoJsonExportFlags.geoJsonExportStripMs) == 0; + + if (!b_export_zs && b_export_ms) + throw new IllegalArgumentException("invalid argument"); + + int path_count = 0; + AttributeStreamOfDbl position = null; + AttributeStreamOfDbl zs = null; + AttributeStreamOfDbl ms = null; + AttributeStreamOfInt8 path_flags = null; + AttributeStreamOfInt32 paths = null; + + if (point_count > 0) { + position = (AttributeStreamOfDbl) polygon_impl.getAttributeStreamRef(Semantics.POSITION); + path_flags = polygon_impl.getPathFlagsStreamRef(); + paths = polygon_impl.getPathStreamRef(); + path_count = polygon_impl.getPathCount(); + + if (b_export_zs) { + if (polygon_impl._attributeStreamIsAllocated(Semantics.Z)) + zs = (AttributeStreamOfDbl) polygon_impl.getAttributeStreamRef(Semantics.Z); + } + + if (b_export_ms) { + if (polygon_impl._attributeStreamIsAllocated(Semantics.M)) + ms = (AttributeStreamOfDbl) polygon_impl.getAttributeStreamRef(Semantics.M); + } + } + + if ((export_flags & GeoJsonExportFlags.geoJsonExportPreferMultiGeometry) == 0 && polygon_count <= 1) + polygonTaggedText_(precision, bFixedPoint, b_export_zs, b_export_ms, zs, ms, position, paths, path_count, + json_writer); + else + multiPolygonTaggedText_(precision, bFixedPoint, b_export_zs, b_export_ms, zs, ms, position, path_flags, + paths, polygon_count, path_count, json_writer); + } + + // Mirrors wkt + private static void exportPolylineToGeoJson_(int export_flags, Polyline polyline, JsonWriter json_writer) { + MultiPathImpl polyline_impl = (MultiPathImpl) polyline._getImpl(); + + int point_count = polyline_impl.getPointCount(); + int path_count = polyline_impl.getPathCount(); + + if (point_count > 0 && path_count == 0) + throw new GeometryException("corrupted geometry"); + + int precision = 17 - (31 & (export_flags >> 13)); + boolean bFixedPoint = (GeoJsonExportFlags.geoJsonExportPrecisionFixedPoint & export_flags) != 0; + boolean b_export_zs = polyline_impl.hasAttribute(VertexDescription.Semantics.Z) + && (export_flags & GeoJsonExportFlags.geoJsonExportStripZs) == 0; + boolean b_export_ms = polyline_impl.hasAttribute(VertexDescription.Semantics.M) + && (export_flags & GeoJsonExportFlags.geoJsonExportStripMs) == 0; + + if (!b_export_zs && b_export_ms) + throw new IllegalArgumentException("invalid argument"); + + AttributeStreamOfDbl position = null; + AttributeStreamOfDbl zs = null; + AttributeStreamOfDbl ms = null; + AttributeStreamOfInt8 path_flags = null; + AttributeStreamOfInt32 paths = null; + + if (point_count > 0) { + position = (AttributeStreamOfDbl) polyline_impl.getAttributeStreamRef(Semantics.POSITION); + path_flags = polyline_impl.getPathFlagsStreamRef(); + paths = polyline_impl.getPathStreamRef(); + + if (b_export_zs) { + if (polyline_impl._attributeStreamIsAllocated(Semantics.Z)) + zs = (AttributeStreamOfDbl) polyline_impl.getAttributeStreamRef(Semantics.Z); + } + + if (b_export_ms) { + if (polyline_impl._attributeStreamIsAllocated(Semantics.M)) + ms = (AttributeStreamOfDbl) polyline_impl.getAttributeStreamRef(Semantics.M); + } + } + + if ((export_flags & GeoJsonExportFlags.geoJsonExportPreferMultiGeometry) == 0 && path_count <= 1) + lineStringTaggedText_(precision, bFixedPoint, b_export_zs, b_export_ms, zs, ms, position, path_flags, paths, + json_writer); + else + multiLineStringTaggedText_(precision, bFixedPoint, b_export_zs, b_export_ms, zs, ms, position, path_flags, + paths, path_count, json_writer); + } + + // Mirrors wkt + private static void exportMultiPointToGeoJson_(int export_flags, MultiPoint multipoint, JsonWriter json_writer) { + MultiPointImpl multipoint_impl = (MultiPointImpl) multipoint._getImpl(); + + int point_count = multipoint_impl.getPointCount(); + + int precision = 17 - (31 & (export_flags >> 13)); + boolean bFixedPoint = (GeoJsonExportFlags.geoJsonExportPrecisionFixedPoint & export_flags) != 0; + boolean b_export_zs = multipoint_impl.hasAttribute(VertexDescription.Semantics.Z) + && (export_flags & GeoJsonExportFlags.geoJsonExportStripZs) == 0; + boolean b_export_ms = multipoint_impl.hasAttribute(VertexDescription.Semantics.M) + && (export_flags & GeoJsonExportFlags.geoJsonExportStripMs) == 0; + + if (!b_export_zs && b_export_ms) + throw new IllegalArgumentException("invalid argument"); + + AttributeStreamOfDbl position = null; + AttributeStreamOfDbl zs = null; + AttributeStreamOfDbl ms = null; + + if (point_count > 0) { + position = (AttributeStreamOfDbl) multipoint_impl.getAttributeStreamRef(Semantics.POSITION); + + if (b_export_zs) { + if (multipoint_impl._attributeStreamIsAllocated(Semantics.Z)) + zs = (AttributeStreamOfDbl) multipoint_impl.getAttributeStreamRef(Semantics.Z); + } + + if (b_export_ms) { + if (multipoint_impl._attributeStreamIsAllocated(Semantics.M)) + ms = (AttributeStreamOfDbl) multipoint_impl.getAttributeStreamRef(Semantics.M); + } + } + + multiPointTaggedText_(precision, bFixedPoint, b_export_zs, b_export_ms, zs, ms, position, point_count, + json_writer); + } + + // Mirrors wkt + private static void exportPointToGeoJson_(int export_flags, Point point, JsonWriter json_writer) { + int precision = 17 - (31 & (export_flags >> 13)); + boolean bFixedPoint = (GeoJsonExportFlags.geoJsonExportPrecisionFixedPoint & export_flags) != 0; + boolean b_export_zs = point.hasAttribute(VertexDescription.Semantics.Z) + && (export_flags & GeoJsonExportFlags.geoJsonExportStripZs) == 0; + boolean b_export_ms = point.hasAttribute(VertexDescription.Semantics.M) + && (export_flags & GeoJsonExportFlags.geoJsonExportStripMs) == 0; + + if (!b_export_zs && b_export_ms) + throw new IllegalArgumentException("invalid argument"); + + double x = NumberUtils.NaN(); + double y = NumberUtils.NaN(); + double z = NumberUtils.NaN(); + double m = NumberUtils.NaN(); + + if (!point.isEmpty()) { + x = point.getX(); + y = point.getY(); + + if (b_export_zs) + z = point.getZ(); + + if (b_export_ms) + m = point.getM(); + } + + if ((export_flags & GeoJsonExportFlags.geoJsonExportPreferMultiGeometry) == 0) + pointTaggedText_(precision, bFixedPoint, b_export_zs, b_export_ms, x, y, z, m, json_writer); + else + multiPointTaggedTextFromPoint_(precision, bFixedPoint, b_export_zs, b_export_ms, x, y, z, m, json_writer); + } + + // Mirrors wkt + private static void exportEnvelopeToGeoJson_(int export_flags, Envelope envelope, JsonWriter json_writer) { + int precision = 17 - (31 & (export_flags >> 13)); + boolean bFixedPoint = (GeoJsonExportFlags.geoJsonExportPrecisionFixedPoint & export_flags) != 0; + boolean b_export_zs = envelope.hasAttribute(VertexDescription.Semantics.Z) + && (export_flags & GeoJsonExportFlags.geoJsonExportStripZs) == 0; + boolean b_export_ms = envelope.hasAttribute(VertexDescription.Semantics.M) + && (export_flags & GeoJsonExportFlags.geoJsonExportStripMs) == 0; + + if (!b_export_zs && b_export_ms) + throw new IllegalArgumentException("invalid argument"); + + double xmin = NumberUtils.NaN(); + double ymin = NumberUtils.NaN(); + double xmax = NumberUtils.NaN(); + double ymax = NumberUtils.NaN(); + double zmin = NumberUtils.NaN(); + double zmax = NumberUtils.NaN(); + double mmin = NumberUtils.NaN(); + double mmax = NumberUtils.NaN(); + + if (!envelope.isEmpty()) { + xmin = envelope.getXMin(); + ymin = envelope.getYMin(); + xmax = envelope.getXMax(); + ymax = envelope.getYMax(); + + Envelope1D interval; + + if (b_export_zs) { + interval = envelope.queryInterval(Semantics.Z, 0); + zmin = interval.vmin; + zmax = interval.vmax; + } + + if (b_export_ms) { + interval = envelope.queryInterval(Semantics.M, 0); + mmin = interval.vmin; + mmax = interval.vmax; + } + } + + if ((export_flags & GeoJsonExportFlags.geoJsonExportPreferMultiGeometry) == 0) + polygonTaggedTextFromEnvelope_(precision, bFixedPoint, b_export_zs, b_export_ms, xmin, ymin, xmax, ymax, + zmin, zmax, mmin, mmax, json_writer); + else + multiPolygonTaggedTextFromEnvelope_(precision, bFixedPoint, b_export_zs, b_export_ms, xmin, ymin, xmax, + ymax, zmin, zmax, mmin, mmax, json_writer); + } + + // Mirrors wkt + private static void multiPolygonTaggedText_(int precision, boolean bFixedPoint, boolean b_export_zs, + boolean b_export_ms, AttributeStreamOfDbl zs, AttributeStreamOfDbl ms, AttributeStreamOfDbl position, + AttributeStreamOfInt8 path_flags, AttributeStreamOfInt32 paths, int polygon_count, int path_count, + JsonWriter json_writer) { + json_writer.addFieldName("type"); + json_writer.addValueString("MultiPolygon"); + + json_writer.addFieldName("coordinates"); + + if (position == null) { + json_writer.startArray(); + json_writer.endArray(); + return; + } + + json_writer.startArray(); + + multiPolygonText_(precision, bFixedPoint, b_export_zs, b_export_ms, zs, ms, position, path_flags, paths, + polygon_count, path_count, json_writer); + + json_writer.endArray(); + } + + // Mirrors wkt + private static void multiPolygonTaggedTextFromEnvelope_(int precision, boolean bFixedPoint, boolean b_export_zs, + boolean b_export_ms, double xmin, double ymin, double xmax, double ymax, double zmin, double zmax, + double mmin, double mmax, JsonWriter json_writer) { + json_writer.addFieldName("type"); + json_writer.addValueString("MultiPolygon"); + + json_writer.addFieldName("coordinates"); + + if (NumberUtils.isNaN(xmin)) { + json_writer.startArray(); + json_writer.endArray(); + return; + } + + json_writer.startArray(); + + writeEnvelopeAsGeoJsonPolygon_(precision, bFixedPoint, b_export_zs, b_export_ms, xmin, ymin, xmax, ymax, zmin, + zmax, mmin, mmax, json_writer); + + json_writer.endArray(); + } + + // Mirrors wkt + private static void multiLineStringTaggedText_(int precision, boolean bFixedPoint, boolean b_export_zs, + boolean b_export_ms, AttributeStreamOfDbl zs, AttributeStreamOfDbl ms, AttributeStreamOfDbl position, + AttributeStreamOfInt8 path_flags, AttributeStreamOfInt32 paths, int path_count, JsonWriter json_writer) { + json_writer.addFieldName("type"); + json_writer.addValueString("MultiLineString"); + + json_writer.addFieldName("coordinates"); + + if (position == null) { + json_writer.startArray(); + json_writer.endArray(); + return; + } + + json_writer.startArray(); + + multiLineStringText_(precision, bFixedPoint, b_export_zs, b_export_ms, zs, ms, position, path_flags, paths, + path_count, json_writer); + + json_writer.endArray(); + } + + // Mirrors wkt + private static void multiPointTaggedText_(int precision, boolean bFixedPoint, boolean b_export_zs, + boolean b_export_ms, AttributeStreamOfDbl zs, AttributeStreamOfDbl ms, AttributeStreamOfDbl position, + int point_count, JsonWriter json_writer) { + json_writer.addFieldName("type"); + json_writer.addValueString("MultiPoint"); + + json_writer.addFieldName("coordinates"); + + if (position == null) { + json_writer.startArray(); + json_writer.endArray(); + return; + } + + lineStringText_(false, false, precision, bFixedPoint, b_export_zs, b_export_ms, zs, ms, position, 0, + point_count, json_writer); + } + + // Mirrors wkt + private static void multiPointTaggedTextFromPoint_(int precision, boolean bFixedPoint, boolean b_export_zs, + boolean b_export_ms, double x, double y, double z, double m, JsonWriter json_writer) { + json_writer.addFieldName("type"); + json_writer.addValueString("MultiPoint"); + + json_writer.addFieldName("coordinates"); + + if (NumberUtils.isNaN(x)) { + json_writer.startArray(); + json_writer.endArray(); + return; + } + + json_writer.startArray(); - static String exportSpatialReference(int export_flags, SpatialReference spatial_reference) { - if (spatial_reference == null || (export_flags & GeoJsonExportFlags.geoJsonExportSkipCRS) != 0) - throw new IllegalArgumentException(""); + pointText_(precision, bFixedPoint, b_export_zs, b_export_ms, x, y, z, m, json_writer); - JsonWriter json_writer = new JsonStringWriter(); - exportSpatialReference(export_flags, spatial_reference, json_writer); + json_writer.endArray(); + } - return (String) json_writer.getJson(); - } - - private static void exportGeometryToGeoJson_(int export_flags, Geometry geometry, JsonWriter json_writer) { - int type = geometry.getType().value(); - switch (type) { - case Geometry.GeometryType.Polygon: - exportPolygonToGeoJson_(export_flags, (Polygon) geometry, json_writer); - return; - - case Geometry.GeometryType.Polyline: - exportPolylineToGeoJson_(export_flags, (Polyline) geometry, json_writer); - return; - - case Geometry.GeometryType.MultiPoint: - exportMultiPointToGeoJson_(export_flags, (MultiPoint) geometry, json_writer); - return; - - case Geometry.GeometryType.Point: - exportPointToGeoJson_(export_flags, (Point) geometry, json_writer); - return; - - case Geometry.GeometryType.Envelope: - exportEnvelopeToGeoJson_(export_flags, (Envelope) geometry, - json_writer); - return; - - default: - throw new RuntimeException("not implemented for this geometry type"); - } - } - - private static void exportSpatialReference(int export_flags, SpatialReference spatial_reference, - JsonWriter json_writer) { - if (spatial_reference != null) { - int wkid = spatial_reference.getLatestID(); - - if (wkid <= 0) - throw new GeometryException("invalid call"); - - json_writer.startObject(); - - json_writer.addFieldName("type"); - - json_writer.addValueString("name"); - - json_writer.addFieldName("properties"); - json_writer.startObject(); - - json_writer.addFieldName("name"); - - String authority = ((SpatialReferenceImpl) spatial_reference).getAuthority(); - authority = authority.toUpperCase(); - StringBuilder crs_identifier = new StringBuilder(authority); - crs_identifier.append(':'); - crs_identifier.append(wkid); - json_writer.addValueString(crs_identifier.toString()); - - json_writer.endObject(); - - json_writer.endObject(); - } else { - json_writer.addValueNull(); - } - } - - // Mirrors wkt - private static void exportPolygonToGeoJson_(int export_flags, Polygon polygon, JsonWriter json_writer) { - MultiPathImpl polygon_impl = (MultiPathImpl) (polygon._getImpl()); - - if ((export_flags & GeoJsonExportFlags.geoJsonExportFailIfNotSimple) != 0) { - int simple = polygon_impl.getIsSimple(0.0); - - if (simple != MultiPathImpl.GeometryXSimple.Strong) - throw new GeometryException("corrupted geometry"); - } - - int point_count = polygon.getPointCount(); - int polygon_count = polygon_impl.getOGCPolygonCount(); - - if (point_count > 0 && polygon_count == 0) - throw new GeometryException("corrupted geometry"); - - int precision = 17 - (31 & (export_flags >> 13)); - boolean bFixedPoint = (GeoJsonExportFlags.geoJsonExportPrecisionFixedPoint & export_flags) != 0; - boolean b_export_zs = polygon_impl.hasAttribute(VertexDescription.Semantics.Z) - && (export_flags & GeoJsonExportFlags.geoJsonExportStripZs) == 0; - boolean b_export_ms = polygon_impl.hasAttribute(VertexDescription.Semantics.M) - && (export_flags & GeoJsonExportFlags.geoJsonExportStripMs) == 0; - - if (!b_export_zs && b_export_ms) - throw new IllegalArgumentException("invalid argument"); - - int path_count = 0; - AttributeStreamOfDbl position = null; - AttributeStreamOfDbl zs = null; - AttributeStreamOfDbl ms = null; - AttributeStreamOfInt8 path_flags = null; - AttributeStreamOfInt32 paths = null; - - if (point_count > 0) { - position = (AttributeStreamOfDbl) polygon_impl.getAttributeStreamRef(Semantics.POSITION); - path_flags = polygon_impl.getPathFlagsStreamRef(); - paths = polygon_impl.getPathStreamRef(); - path_count = polygon_impl.getPathCount(); - - if (b_export_zs) { - if (polygon_impl._attributeStreamIsAllocated(Semantics.Z)) - zs = (AttributeStreamOfDbl) polygon_impl.getAttributeStreamRef(Semantics.Z); - } - - if (b_export_ms) { - if (polygon_impl._attributeStreamIsAllocated(Semantics.M)) - ms = (AttributeStreamOfDbl) polygon_impl.getAttributeStreamRef(Semantics.M); - } - } - - if ((export_flags & GeoJsonExportFlags.geoJsonExportPreferMultiGeometry) == 0 && polygon_count <= 1) - polygonTaggedText_(precision, bFixedPoint, b_export_zs, b_export_ms, zs, ms, position, paths, path_count, - json_writer); - else - multiPolygonTaggedText_(precision, bFixedPoint, b_export_zs, b_export_ms, zs, ms, position, path_flags, - paths, polygon_count, path_count, json_writer); - } - - // Mirrors wkt - private static void exportPolylineToGeoJson_(int export_flags, Polyline polyline, JsonWriter json_writer) { - MultiPathImpl polyline_impl = (MultiPathImpl) polyline._getImpl(); - - int point_count = polyline_impl.getPointCount(); - int path_count = polyline_impl.getPathCount(); - - if (point_count > 0 && path_count == 0) - throw new GeometryException("corrupted geometry"); - - int precision = 17 - (31 & (export_flags >> 13)); - boolean bFixedPoint = (GeoJsonExportFlags.geoJsonExportPrecisionFixedPoint & export_flags) != 0; - boolean b_export_zs = polyline_impl.hasAttribute(VertexDescription.Semantics.Z) - && (export_flags & GeoJsonExportFlags.geoJsonExportStripZs) == 0; - boolean b_export_ms = polyline_impl.hasAttribute(VertexDescription.Semantics.M) - && (export_flags & GeoJsonExportFlags.geoJsonExportStripMs) == 0; - - if (!b_export_zs && b_export_ms) - throw new IllegalArgumentException("invalid argument"); - - AttributeStreamOfDbl position = null; - AttributeStreamOfDbl zs = null; - AttributeStreamOfDbl ms = null; - AttributeStreamOfInt8 path_flags = null; - AttributeStreamOfInt32 paths = null; - - if (point_count > 0) { - position = (AttributeStreamOfDbl) polyline_impl.getAttributeStreamRef(Semantics.POSITION); - path_flags = polyline_impl.getPathFlagsStreamRef(); - paths = polyline_impl.getPathStreamRef(); - - if (b_export_zs) { - if (polyline_impl._attributeStreamIsAllocated(Semantics.Z)) - zs = (AttributeStreamOfDbl) polyline_impl.getAttributeStreamRef(Semantics.Z); - } - - if (b_export_ms) { - if (polyline_impl._attributeStreamIsAllocated(Semantics.M)) - ms = (AttributeStreamOfDbl) polyline_impl.getAttributeStreamRef(Semantics.M); - } - } - - if ((export_flags & GeoJsonExportFlags.geoJsonExportPreferMultiGeometry) == 0 && path_count <= 1) - lineStringTaggedText_(precision, bFixedPoint, b_export_zs, b_export_ms, zs, ms, position, path_flags, paths, - json_writer); - else - multiLineStringTaggedText_(precision, bFixedPoint, b_export_zs, b_export_ms, zs, ms, position, path_flags, - paths, path_count, json_writer); - } - - // Mirrors wkt - private static void exportMultiPointToGeoJson_(int export_flags, MultiPoint multipoint, JsonWriter json_writer) { - MultiPointImpl multipoint_impl = (MultiPointImpl) multipoint._getImpl(); - - int point_count = multipoint_impl.getPointCount(); - - int precision = 17 - (31 & (export_flags >> 13)); - boolean bFixedPoint = (GeoJsonExportFlags.geoJsonExportPrecisionFixedPoint & export_flags) != 0; - boolean b_export_zs = multipoint_impl.hasAttribute(VertexDescription.Semantics.Z) - && (export_flags & GeoJsonExportFlags.geoJsonExportStripZs) == 0; - boolean b_export_ms = multipoint_impl.hasAttribute(VertexDescription.Semantics.M) - && (export_flags & GeoJsonExportFlags.geoJsonExportStripMs) == 0; - - if (!b_export_zs && b_export_ms) - throw new IllegalArgumentException("invalid argument"); - - AttributeStreamOfDbl position = null; - AttributeStreamOfDbl zs = null; - AttributeStreamOfDbl ms = null; - - if (point_count > 0) { - position = (AttributeStreamOfDbl) multipoint_impl.getAttributeStreamRef(Semantics.POSITION); - - if (b_export_zs) { - if (multipoint_impl._attributeStreamIsAllocated(Semantics.Z)) - zs = (AttributeStreamOfDbl) multipoint_impl.getAttributeStreamRef(Semantics.Z); - } - - if (b_export_ms) { - if (multipoint_impl._attributeStreamIsAllocated(Semantics.M)) - ms = (AttributeStreamOfDbl) multipoint_impl.getAttributeStreamRef(Semantics.M); - } - } - - multiPointTaggedText_(precision, bFixedPoint, b_export_zs, b_export_ms, zs, ms, position, point_count, - json_writer); - } - - // Mirrors wkt - private static void exportPointToGeoJson_(int export_flags, Point point, JsonWriter json_writer) { - int precision = 17 - (31 & (export_flags >> 13)); - boolean bFixedPoint = (GeoJsonExportFlags.geoJsonExportPrecisionFixedPoint & export_flags) != 0; - boolean b_export_zs = point.hasAttribute(VertexDescription.Semantics.Z) - && (export_flags & GeoJsonExportFlags.geoJsonExportStripZs) == 0; - boolean b_export_ms = point.hasAttribute(VertexDescription.Semantics.M) - && (export_flags & GeoJsonExportFlags.geoJsonExportStripMs) == 0; - - if (!b_export_zs && b_export_ms) - throw new IllegalArgumentException("invalid argument"); - - double x = NumberUtils.NaN(); - double y = NumberUtils.NaN(); - double z = NumberUtils.NaN(); - double m = NumberUtils.NaN(); - - if (!point.isEmpty()) { - x = point.getX(); - y = point.getY(); - - if (b_export_zs) - z = point.getZ(); - - if (b_export_ms) - m = point.getM(); - } - - if ((export_flags & GeoJsonExportFlags.geoJsonExportPreferMultiGeometry) == 0) - pointTaggedText_(precision, bFixedPoint, b_export_zs, b_export_ms, x, y, z, m, json_writer); - else - multiPointTaggedTextFromPoint_(precision, bFixedPoint, b_export_zs, b_export_ms, x, y, z, m, json_writer); - } - - // Mirrors wkt - private static void exportEnvelopeToGeoJson_(int export_flags, Envelope envelope, JsonWriter json_writer) { - int precision = 17 - (31 & (export_flags >> 13)); - boolean bFixedPoint = (GeoJsonExportFlags.geoJsonExportPrecisionFixedPoint & export_flags) != 0; - boolean b_export_zs = envelope.hasAttribute(VertexDescription.Semantics.Z) - && (export_flags & GeoJsonExportFlags.geoJsonExportStripZs) == 0; - boolean b_export_ms = envelope.hasAttribute(VertexDescription.Semantics.M) - && (export_flags & GeoJsonExportFlags.geoJsonExportStripMs) == 0; - - if (!b_export_zs && b_export_ms) - throw new IllegalArgumentException("invalid argument"); - - double xmin = NumberUtils.NaN(); - double ymin = NumberUtils.NaN(); - double xmax = NumberUtils.NaN(); - double ymax = NumberUtils.NaN(); - double zmin = NumberUtils.NaN(); - double zmax = NumberUtils.NaN(); - double mmin = NumberUtils.NaN(); - double mmax = NumberUtils.NaN(); - - if (!envelope.isEmpty()) { - xmin = envelope.getXMin(); - ymin = envelope.getYMin(); - xmax = envelope.getXMax(); - ymax = envelope.getYMax(); - - Envelope1D interval; - - if (b_export_zs) { - interval = envelope.queryInterval(Semantics.Z, 0); - zmin = interval.vmin; - zmax = interval.vmax; - } - - if (b_export_ms) { - interval = envelope.queryInterval(Semantics.M, 0); - mmin = interval.vmin; - mmax = interval.vmax; - } - } - - if ((export_flags & GeoJsonExportFlags.geoJsonExportPreferMultiGeometry) == 0) - polygonTaggedTextFromEnvelope_(precision, bFixedPoint, b_export_zs, b_export_ms, xmin, ymin, xmax, ymax, - zmin, zmax, mmin, mmax, json_writer); - else - multiPolygonTaggedTextFromEnvelope_(precision, bFixedPoint, b_export_zs, b_export_ms, xmin, ymin, xmax, - ymax, zmin, zmax, mmin, mmax, json_writer); - } - - // Mirrors wkt - private static void multiPolygonTaggedText_(int precision, boolean bFixedPoint, boolean b_export_zs, - boolean b_export_ms, AttributeStreamOfDbl zs, AttributeStreamOfDbl ms, AttributeStreamOfDbl position, - AttributeStreamOfInt8 path_flags, AttributeStreamOfInt32 paths, int polygon_count, int path_count, - JsonWriter json_writer) { - json_writer.addFieldName("type"); - json_writer.addValueString("MultiPolygon"); - - json_writer.addFieldName("coordinates"); - - if (position == null) { - json_writer.startArray(); - json_writer.endArray(); - return; - } - - json_writer.startArray(); - - multiPolygonText_(precision, bFixedPoint, b_export_zs, b_export_ms, zs, ms, position, path_flags, paths, - polygon_count, path_count, json_writer); - - json_writer.endArray(); - } - - // Mirrors wkt - private static void multiPolygonTaggedTextFromEnvelope_(int precision, boolean bFixedPoint, boolean b_export_zs, - boolean b_export_ms, double xmin, double ymin, double xmax, double ymax, double zmin, double zmax, - double mmin, double mmax, JsonWriter json_writer) { - json_writer.addFieldName("type"); - json_writer.addValueString("MultiPolygon"); - - json_writer.addFieldName("coordinates"); - - if (NumberUtils.isNaN(xmin)) { - json_writer.startArray(); - json_writer.endArray(); - return; - } - - json_writer.startArray(); - - writeEnvelopeAsGeoJsonPolygon_(precision, bFixedPoint, b_export_zs, b_export_ms, xmin, ymin, xmax, ymax, zmin, - zmax, mmin, mmax, json_writer); - - json_writer.endArray(); - } - - // Mirrors wkt - private static void multiLineStringTaggedText_(int precision, boolean bFixedPoint, boolean b_export_zs, - boolean b_export_ms, AttributeStreamOfDbl zs, AttributeStreamOfDbl ms, AttributeStreamOfDbl position, - AttributeStreamOfInt8 path_flags, AttributeStreamOfInt32 paths, int path_count, JsonWriter json_writer) { - json_writer.addFieldName("type"); - json_writer.addValueString("MultiLineString"); - - json_writer.addFieldName("coordinates"); - - if (position == null) { - json_writer.startArray(); - json_writer.endArray(); - return; - } - - json_writer.startArray(); - - multiLineStringText_(precision, bFixedPoint, b_export_zs, b_export_ms, zs, ms, position, path_flags, paths, - path_count, json_writer); - - json_writer.endArray(); - } - - // Mirrors wkt - private static void multiPointTaggedText_(int precision, boolean bFixedPoint, boolean b_export_zs, - boolean b_export_ms, AttributeStreamOfDbl zs, AttributeStreamOfDbl ms, AttributeStreamOfDbl position, - int point_count, JsonWriter json_writer) { - json_writer.addFieldName("type"); - json_writer.addValueString("MultiPoint"); - - json_writer.addFieldName("coordinates"); - - if (position == null) { - json_writer.startArray(); - json_writer.endArray(); - return; - } - - lineStringText_(false, false, precision, bFixedPoint, b_export_zs, b_export_ms, zs, ms, position, 0, - point_count, json_writer); - } - - // Mirrors wkt - private static void multiPointTaggedTextFromPoint_(int precision, boolean bFixedPoint, boolean b_export_zs, - boolean b_export_ms, double x, double y, double z, double m, JsonWriter json_writer) { - json_writer.addFieldName("type"); - json_writer.addValueString("MultiPoint"); - - json_writer.addFieldName("coordinates"); - - if (NumberUtils.isNaN(x)) { - json_writer.startArray(); - json_writer.endArray(); - return; - } - - json_writer.startArray(); + // Mirrors wkt + private static void polygonTaggedText_(int precision, boolean bFixedPoint, boolean b_export_zs, boolean b_export_ms, + AttributeStreamOfDbl zs, AttributeStreamOfDbl ms, AttributeStreamOfDbl position, + AttributeStreamOfInt32 paths, int path_count, JsonWriter json_writer) { + json_writer.addFieldName("type"); + json_writer.addValueString("Polygon"); + + json_writer.addFieldName("coordinates"); + + if (position == null) { + json_writer.startArray(); + json_writer.endArray(); + return; + } + + polygonText_(precision, bFixedPoint, b_export_zs, b_export_ms, zs, ms, position, paths, 0, path_count, + json_writer); + } + + // Mirrors wkt + private static void polygonTaggedTextFromEnvelope_(int precision, boolean bFixedPoint, boolean b_export_zs, + boolean b_export_ms, double xmin, double ymin, double xmax, double ymax, double zmin, double zmax, + double mmin, double mmax, JsonWriter json_writer) { + json_writer.addFieldName("type"); + json_writer.addValueString("Polygon"); + + json_writer.addFieldName("coordinates"); + + if (NumberUtils.isNaN(xmin)) { + json_writer.startArray(); + json_writer.endArray(); + return; + } + + writeEnvelopeAsGeoJsonPolygon_(precision, bFixedPoint, b_export_zs, b_export_ms, xmin, ymin, xmax, ymax, zmin, + zmax, mmin, mmax, json_writer); + } + + // Mirrors wkt + private static void lineStringTaggedText_(int precision, boolean bFixedPoint, boolean b_export_zs, + boolean b_export_ms, AttributeStreamOfDbl zs, AttributeStreamOfDbl ms, AttributeStreamOfDbl position, + AttributeStreamOfInt8 path_flags, AttributeStreamOfInt32 paths, JsonWriter json_writer) { + json_writer.addFieldName("type"); + json_writer.addValueString("LineString"); + + json_writer.addFieldName("coordinates"); + + if (position == null) { + json_writer.startArray(); + json_writer.endArray(); + return; + } + + boolean b_closed = ((path_flags.read(0) & PathFlags.enumClosed) != 0); + + lineStringText_(false, b_closed, precision, bFixedPoint, b_export_zs, b_export_ms, zs, ms, position, 0, + paths.read(1), json_writer); + } + + // Mirrors wkt + private static void pointTaggedText_(int precision, boolean bFixedPoint, boolean b_export_zs, boolean b_export_ms, + double x, double y, double z, double m, JsonWriter json_writer) { + json_writer.addFieldName("type"); + json_writer.addValueString("Point"); + + json_writer.addFieldName("coordinates"); + + if (NumberUtils.isNaN(x)) { + json_writer.startArray(); + json_writer.endArray(); + + return; + } + + pointText_(precision, bFixedPoint, b_export_zs, b_export_ms, x, y, z, m, json_writer); + } + + // Mirrors wkt + private static void multiPolygonText_(int precision, boolean bFixedPoint, boolean b_export_zs, boolean b_export_ms, + AttributeStreamOfDbl zs, AttributeStreamOfDbl ms, AttributeStreamOfDbl position, + AttributeStreamOfInt8 path_flags, AttributeStreamOfInt32 paths, int polygon_count, int path_count, + JsonWriter json_writer) { + int polygon_start = 0; + int polygon_end = 1; + + while (polygon_end < path_count && ((int) path_flags.read(polygon_end) & PathFlags.enumOGCStartPolygon) == 0) + polygon_end++; + + polygonText_(precision, bFixedPoint, b_export_zs, b_export_ms, zs, ms, position, paths, polygon_start, + polygon_end, json_writer); + + for (int ipolygon = 1; ipolygon < polygon_count; ipolygon++) { + polygon_start = polygon_end; + polygon_end++; + + while (polygon_end < path_count + && ((int) path_flags.read(polygon_end) & PathFlags.enumOGCStartPolygon) == 0) + polygon_end++; + + polygonText_(precision, bFixedPoint, b_export_zs, b_export_ms, zs, ms, position, paths, polygon_start, + polygon_end, json_writer); + } + } + + // Mirrors wkt + private static void multiLineStringText_(int precision, boolean bFixedPoint, boolean b_export_zs, + boolean b_export_ms, AttributeStreamOfDbl zs, AttributeStreamOfDbl ms, AttributeStreamOfDbl position, + AttributeStreamOfInt8 path_flags, AttributeStreamOfInt32 paths, int path_count, JsonWriter json_writer) { + boolean b_closed = ((path_flags.read(0) & PathFlags.enumClosed) != 0); + + lineStringText_(false, b_closed, precision, bFixedPoint, b_export_zs, b_export_ms, zs, ms, position, 0, + paths.read(1), json_writer); + + for (int path = 1; path < path_count; path++) { + b_closed = ((path_flags.read(path) & PathFlags.enumClosed) != 0); + + int istart = paths.read(path); + int iend = paths.read(path + 1); + lineStringText_(false, b_closed, precision, bFixedPoint, b_export_zs, b_export_ms, zs, ms, position, istart, + iend, json_writer); + } + } + + // Mirrors wkt + private static void polygonText_(int precision, boolean bFixedPoint, boolean b_export_zs, boolean b_export_ms, + AttributeStreamOfDbl zs, AttributeStreamOfDbl ms, AttributeStreamOfDbl position, + AttributeStreamOfInt32 paths, int polygon_start, int polygon_end, JsonWriter json_writer) { + json_writer.startArray(); + + int istart = paths.read(polygon_start); + int iend = paths.read(polygon_start + 1); + lineStringText_(true, true, precision, bFixedPoint, b_export_zs, b_export_ms, zs, ms, position, istart, iend, + json_writer); - pointText_(precision, bFixedPoint, b_export_zs, b_export_ms, x, y, z, m, json_writer); + for (int path = polygon_start + 1; path < polygon_end; path++) { + istart = paths.read(path); + iend = paths.read(path + 1); + lineStringText_(true, true, precision, bFixedPoint, b_export_zs, b_export_ms, zs, ms, position, istart, + iend, json_writer); + } - json_writer.endArray(); - } + json_writer.endArray(); + } - // Mirrors wkt - private static void polygonTaggedText_(int precision, boolean bFixedPoint, boolean b_export_zs, boolean b_export_ms, - AttributeStreamOfDbl zs, AttributeStreamOfDbl ms, AttributeStreamOfDbl position, - AttributeStreamOfInt32 paths, int path_count, JsonWriter json_writer) { - json_writer.addFieldName("type"); - json_writer.addValueString("Polygon"); - - json_writer.addFieldName("coordinates"); - - if (position == null) { - json_writer.startArray(); - json_writer.endArray(); - return; - } - - polygonText_(precision, bFixedPoint, b_export_zs, b_export_ms, zs, ms, position, paths, 0, path_count, - json_writer); - } - - // Mirrors wkt - private static void polygonTaggedTextFromEnvelope_(int precision, boolean bFixedPoint, boolean b_export_zs, - boolean b_export_ms, double xmin, double ymin, double xmax, double ymax, double zmin, double zmax, - double mmin, double mmax, JsonWriter json_writer) { - json_writer.addFieldName("type"); - json_writer.addValueString("Polygon"); - - json_writer.addFieldName("coordinates"); - - if (NumberUtils.isNaN(xmin)) { - json_writer.startArray(); - json_writer.endArray(); - return; - } - - writeEnvelopeAsGeoJsonPolygon_(precision, bFixedPoint, b_export_zs, b_export_ms, xmin, ymin, xmax, ymax, zmin, - zmax, mmin, mmax, json_writer); - } - - // Mirrors wkt - private static void lineStringTaggedText_(int precision, boolean bFixedPoint, boolean b_export_zs, - boolean b_export_ms, AttributeStreamOfDbl zs, AttributeStreamOfDbl ms, AttributeStreamOfDbl position, - AttributeStreamOfInt8 path_flags, AttributeStreamOfInt32 paths, JsonWriter json_writer) { - json_writer.addFieldName("type"); - json_writer.addValueString("LineString"); - - json_writer.addFieldName("coordinates"); - - if (position == null) { - json_writer.startArray(); - json_writer.endArray(); - return; - } - - boolean b_closed = ((path_flags.read(0) & PathFlags.enumClosed) != 0); - - lineStringText_(false, b_closed, precision, bFixedPoint, b_export_zs, b_export_ms, zs, ms, position, 0, - paths.read(1), json_writer); - } - - // Mirrors wkt - private static void pointTaggedText_(int precision, boolean bFixedPoint, boolean b_export_zs, boolean b_export_ms, - double x, double y, double z, double m, JsonWriter json_writer) { - json_writer.addFieldName("type"); - json_writer.addValueString("Point"); - - json_writer.addFieldName("coordinates"); - - if (NumberUtils.isNaN(x)) { - json_writer.startArray(); - json_writer.endArray(); - - return; - } - - pointText_(precision, bFixedPoint, b_export_zs, b_export_ms, x, y, z, m, json_writer); - } - - // Mirrors wkt - private static void multiPolygonText_(int precision, boolean bFixedPoint, boolean b_export_zs, boolean b_export_ms, - AttributeStreamOfDbl zs, AttributeStreamOfDbl ms, AttributeStreamOfDbl position, - AttributeStreamOfInt8 path_flags, AttributeStreamOfInt32 paths, int polygon_count, int path_count, - JsonWriter json_writer) { - int polygon_start = 0; - int polygon_end = 1; - - while (polygon_end < path_count && ((int) path_flags.read(polygon_end) & PathFlags.enumOGCStartPolygon) == 0) - polygon_end++; - - polygonText_(precision, bFixedPoint, b_export_zs, b_export_ms, zs, ms, position, paths, polygon_start, - polygon_end, json_writer); - - for (int ipolygon = 1; ipolygon < polygon_count; ipolygon++) { - polygon_start = polygon_end; - polygon_end++; - - while (polygon_end < path_count - && ((int) path_flags.read(polygon_end) & PathFlags.enumOGCStartPolygon) == 0) - polygon_end++; - - polygonText_(precision, bFixedPoint, b_export_zs, b_export_ms, zs, ms, position, paths, polygon_start, - polygon_end, json_writer); - } - } - - // Mirrors wkt - private static void multiLineStringText_(int precision, boolean bFixedPoint, boolean b_export_zs, - boolean b_export_ms, AttributeStreamOfDbl zs, AttributeStreamOfDbl ms, AttributeStreamOfDbl position, - AttributeStreamOfInt8 path_flags, AttributeStreamOfInt32 paths, int path_count, JsonWriter json_writer) { - boolean b_closed = ((path_flags.read(0) & PathFlags.enumClosed) != 0); - - lineStringText_(false, b_closed, precision, bFixedPoint, b_export_zs, b_export_ms, zs, ms, position, 0, - paths.read(1), json_writer); - - for (int path = 1; path < path_count; path++) { - b_closed = ((path_flags.read(path) & PathFlags.enumClosed) != 0); - - int istart = paths.read(path); - int iend = paths.read(path + 1); - lineStringText_(false, b_closed, precision, bFixedPoint, b_export_zs, b_export_ms, zs, ms, position, istart, - iend, json_writer); - } - } - - // Mirrors wkt - private static void polygonText_(int precision, boolean bFixedPoint, boolean b_export_zs, boolean b_export_ms, - AttributeStreamOfDbl zs, AttributeStreamOfDbl ms, AttributeStreamOfDbl position, - AttributeStreamOfInt32 paths, int polygon_start, int polygon_end, JsonWriter json_writer) { - json_writer.startArray(); - - int istart = paths.read(polygon_start); - int iend = paths.read(polygon_start + 1); - lineStringText_(true, true, precision, bFixedPoint, b_export_zs, b_export_ms, zs, ms, position, istart, iend, - json_writer); + // Mirrors wkt + private static void lineStringText_(boolean bRing, boolean b_closed, int precision, boolean bFixedPoint, + boolean b_export_zs, boolean b_export_ms, AttributeStreamOfDbl zs, AttributeStreamOfDbl ms, + AttributeStreamOfDbl position, int istart, int iend, JsonWriter json_writer) { + if (istart == iend) { + json_writer.startArray(); + json_writer.endArray(); + return; + } - for (int path = polygon_start + 1; path < polygon_end; path++) { - istart = paths.read(path); - iend = paths.read(path + 1); - lineStringText_(true, true, precision, bFixedPoint, b_export_zs, b_export_ms, zs, ms, position, istart, - iend, json_writer); - } + json_writer.startArray(); - json_writer.endArray(); - } + if (bRing) { + pointText_(precision, bFixedPoint, b_export_zs, b_export_ms, zs, ms, position, istart, json_writer); - // Mirrors wkt - private static void lineStringText_(boolean bRing, boolean b_closed, int precision, boolean bFixedPoint, - boolean b_export_zs, boolean b_export_ms, AttributeStreamOfDbl zs, AttributeStreamOfDbl ms, - AttributeStreamOfDbl position, int istart, int iend, JsonWriter json_writer) { - if (istart == iend) { - json_writer.startArray(); - json_writer.endArray(); - return; - } + for (int point = iend - 1; point >= istart + 1; point--) + pointText_(precision, bFixedPoint, b_export_zs, b_export_ms, zs, ms, position, point, json_writer); - json_writer.startArray(); + pointText_(precision, bFixedPoint, b_export_zs, b_export_ms, zs, ms, position, istart, json_writer); + } else { + for (int point = istart; point < iend - 1; point++) + pointText_(precision, bFixedPoint, b_export_zs, b_export_ms, zs, ms, position, point, json_writer); - if (bRing) { - pointText_(precision, bFixedPoint, b_export_zs, b_export_ms, zs, ms, position, istart, json_writer); + pointText_(precision, bFixedPoint, b_export_zs, b_export_ms, zs, ms, position, iend - 1, json_writer); - for (int point = iend - 1; point >= istart + 1; point--) - pointText_(precision, bFixedPoint, b_export_zs, b_export_ms, zs, ms, position, point, json_writer); + if (b_closed) + pointText_(precision, bFixedPoint, b_export_zs, b_export_ms, zs, ms, position, istart, json_writer); + } - pointText_(precision, bFixedPoint, b_export_zs, b_export_ms, zs, ms, position, istart, json_writer); - } else { - for (int point = istart; point < iend - 1; point++) - pointText_(precision, bFixedPoint, b_export_zs, b_export_ms, zs, ms, position, point, json_writer); + json_writer.endArray(); + } - pointText_(precision, bFixedPoint, b_export_zs, b_export_ms, zs, ms, position, iend - 1, json_writer); + // Mirrors wkt + private static int pointText_(int precision, boolean bFixedPoint, boolean b_export_zs, boolean b_export_ms, + double x, double y, double z, double m, JsonWriter json_writer) { - if (b_closed) - pointText_(precision, bFixedPoint, b_export_zs, b_export_ms, zs, ms, position, istart, json_writer); - } + json_writer.startArray(); - json_writer.endArray(); - } + json_writer.addValueDouble(x, precision, bFixedPoint); + json_writer.addValueDouble(y, precision, bFixedPoint); - // Mirrors wkt - private static int pointText_(int precision, boolean bFixedPoint, boolean b_export_zs, boolean b_export_ms, - double x, double y, double z, double m, JsonWriter json_writer) { + if (b_export_zs) + json_writer.addValueDouble(z, precision, bFixedPoint); - json_writer.startArray(); + if (b_export_ms) + json_writer.addValueDouble(m, precision, bFixedPoint); - json_writer.addValueDouble(x, precision, bFixedPoint); - json_writer.addValueDouble(y, precision, bFixedPoint); + json_writer.endArray(); - if (b_export_zs) - json_writer.addValueDouble(z, precision, bFixedPoint); + return 1; + } - if (b_export_ms) - json_writer.addValueDouble(m, precision, bFixedPoint); + // Mirrors wkt + private static void pointText_(int precision, boolean bFixedPoint, boolean b_export_zs, boolean b_export_ms, + AttributeStreamOfDbl zs, AttributeStreamOfDbl ms, AttributeStreamOfDbl position, int point, + JsonWriter json_writer) { + double x = position.readAsDbl(2 * point); + double y = position.readAsDbl(2 * point + 1); + double z = NumberUtils.NaN(); + double m = NumberUtils.NaN(); - json_writer.endArray(); + if (b_export_zs) + z = (zs != null ? zs.readAsDbl(point) : VertexDescription.getDefaultValue(Semantics.Z)); - return 1; - } + if (b_export_ms) + m = (ms != null ? ms.readAsDbl(point) : VertexDescription.getDefaultValue(Semantics.M)); - // Mirrors wkt - private static void pointText_(int precision, boolean bFixedPoint, boolean b_export_zs, boolean b_export_ms, - AttributeStreamOfDbl zs, AttributeStreamOfDbl ms, AttributeStreamOfDbl position, int point, - JsonWriter json_writer) { - double x = position.readAsDbl(2 * point); - double y = position.readAsDbl(2 * point + 1); - double z = NumberUtils.NaN(); - double m = NumberUtils.NaN(); + pointText_(precision, bFixedPoint, b_export_zs, b_export_ms, x, y, z, m, json_writer); + } - if (b_export_zs) - z = (zs != null ? zs.readAsDbl(point) : VertexDescription.getDefaultValue(Semantics.Z)); + // Mirrors wkt + private static void writeEnvelopeAsGeoJsonPolygon_(int precision, boolean bFixedPoint, boolean b_export_zs, + boolean b_export_ms, double xmin, double ymin, double xmax, double ymax, double zmin, double zmax, + double mmin, double mmax, JsonWriter json_writer) { + json_writer.startArray(); + json_writer.startArray(); - if (b_export_ms) - m = (ms != null ? ms.readAsDbl(point) : VertexDescription.getDefaultValue(Semantics.M)); + json_writer.startArray(); + json_writer.addValueDouble(xmin, precision, bFixedPoint); + json_writer.addValueDouble(ymin, precision, bFixedPoint); - pointText_(precision, bFixedPoint, b_export_zs, b_export_ms, x, y, z, m, json_writer); - } + if (b_export_zs) + json_writer.addValueDouble(zmin, precision, bFixedPoint); - // Mirrors wkt - private static void writeEnvelopeAsGeoJsonPolygon_(int precision, boolean bFixedPoint, boolean b_export_zs, - boolean b_export_ms, double xmin, double ymin, double xmax, double ymax, double zmin, double zmax, - double mmin, double mmax, JsonWriter json_writer) { - json_writer.startArray(); - json_writer.startArray(); + if (b_export_ms) + json_writer.addValueDouble(mmin, precision, bFixedPoint); - json_writer.startArray(); - json_writer.addValueDouble(xmin, precision, bFixedPoint); - json_writer.addValueDouble(ymin, precision, bFixedPoint); + json_writer.endArray(); - if (b_export_zs) - json_writer.addValueDouble(zmin, precision, bFixedPoint); + json_writer.startArray(); + json_writer.addValueDouble(xmax, precision, bFixedPoint); + json_writer.addValueDouble(ymin, precision, bFixedPoint); - if (b_export_ms) - json_writer.addValueDouble(mmin, precision, bFixedPoint); + if (b_export_zs) + json_writer.addValueDouble(zmax, precision, bFixedPoint); - json_writer.endArray(); + if (b_export_ms) + json_writer.addValueDouble(mmax, precision, bFixedPoint); - json_writer.startArray(); - json_writer.addValueDouble(xmax, precision, bFixedPoint); - json_writer.addValueDouble(ymin, precision, bFixedPoint); + json_writer.endArray(); - if (b_export_zs) - json_writer.addValueDouble(zmax, precision, bFixedPoint); + json_writer.startArray(); + json_writer.addValueDouble(xmax, precision, bFixedPoint); + json_writer.addValueDouble(ymax, precision, bFixedPoint); - if (b_export_ms) - json_writer.addValueDouble(mmax, precision, bFixedPoint); + if (b_export_zs) + json_writer.addValueDouble(zmin, precision, bFixedPoint); - json_writer.endArray(); + if (b_export_ms) + json_writer.addValueDouble(mmin, precision, bFixedPoint); - json_writer.startArray(); - json_writer.addValueDouble(xmax, precision, bFixedPoint); - json_writer.addValueDouble(ymax, precision, bFixedPoint); + json_writer.endArray(); - if (b_export_zs) - json_writer.addValueDouble(zmin, precision, bFixedPoint); + json_writer.startArray(); + json_writer.addValueDouble(xmin, precision, bFixedPoint); + json_writer.addValueDouble(ymax, precision, bFixedPoint); - if (b_export_ms) - json_writer.addValueDouble(mmin, precision, bFixedPoint); + if (b_export_zs) + json_writer.addValueDouble(zmax, precision, bFixedPoint); - json_writer.endArray(); + if (b_export_ms) + json_writer.addValueDouble(mmax, precision, bFixedPoint); - json_writer.startArray(); - json_writer.addValueDouble(xmin, precision, bFixedPoint); - json_writer.addValueDouble(ymax, precision, bFixedPoint); + json_writer.endArray(); - if (b_export_zs) - json_writer.addValueDouble(zmax, precision, bFixedPoint); + json_writer.startArray(); + json_writer.addValueDouble(xmin, precision, bFixedPoint); + json_writer.addValueDouble(ymin, precision, bFixedPoint); - if (b_export_ms) - json_writer.addValueDouble(mmax, precision, bFixedPoint); + if (b_export_zs) + json_writer.addValueDouble(zmin, precision, bFixedPoint); - json_writer.endArray(); + if (b_export_ms) + json_writer.addValueDouble(mmin, precision, bFixedPoint); - json_writer.startArray(); - json_writer.addValueDouble(xmin, precision, bFixedPoint); - json_writer.addValueDouble(ymin, precision, bFixedPoint); + json_writer.endArray(); - if (b_export_zs) - json_writer.addValueDouble(zmin, precision, bFixedPoint); - - if (b_export_ms) - json_writer.addValueDouble(mmin, precision, bFixedPoint); - - json_writer.endArray(); - - json_writer.endArray(); - json_writer.endArray(); - } + json_writer.endArray(); + json_writer.endArray(); + } } diff --git a/src/main/java/com/esri/core/geometry/OperatorExportToGeoJsonLocal.java b/src/main/java/com/esri/core/geometry/OperatorExportToGeoJsonLocal.java index 30bd7ad2..9412cd83 100644 --- a/src/main/java/com/esri/core/geometry/OperatorExportToGeoJsonLocal.java +++ b/src/main/java/com/esri/core/geometry/OperatorExportToGeoJsonLocal.java @@ -23,28 +23,28 @@ package com.esri.core.geometry; class OperatorExportToGeoJsonLocal extends OperatorExportToGeoJson { - @Override - public StringCursor execute(SpatialReference spatialReference, GeometryCursor geometryCursor) { - return new OperatorExportToGeoJsonCursor(GeoJsonExportFlags.geoJsonExportDefaults, spatialReference, geometryCursor); - } - - @Override - public String execute(SpatialReference spatialReference, Geometry geometry) { - return OperatorExportToGeoJsonCursor.exportToGeoJson(GeoJsonExportFlags.geoJsonExportDefaults, geometry, spatialReference); - } - - @Override - public String execute(int exportFlags, SpatialReference spatialReference, Geometry geometry) { - return OperatorExportToGeoJsonCursor.exportToGeoJson(exportFlags, geometry, spatialReference); - } - - @Override - public String execute(Geometry geometry) { - return OperatorExportToGeoJsonCursor.exportToGeoJson(GeoJsonExportFlags.geoJsonExportSkipCRS, geometry, null); - } - - @Override - public String exportSpatialReference(int export_flags, SpatialReference spatial_reference) { - return OperatorExportToGeoJsonCursor.exportSpatialReference(export_flags, spatial_reference); - } + @Override + public StringCursor execute(SpatialReference spatialReference, GeometryCursor geometryCursor) { + return new OperatorExportToGeoJsonCursor(GeoJsonExportFlags.geoJsonExportDefaults, spatialReference, geometryCursor); + } + + @Override + public String execute(SpatialReference spatialReference, Geometry geometry) { + return OperatorExportToGeoJsonCursor.exportToGeoJson(GeoJsonExportFlags.geoJsonExportDefaults, geometry, spatialReference); + } + + @Override + public String execute(int exportFlags, SpatialReference spatialReference, Geometry geometry) { + return OperatorExportToGeoJsonCursor.exportToGeoJson(exportFlags, geometry, spatialReference); + } + + @Override + public String execute(Geometry geometry) { + return OperatorExportToGeoJsonCursor.exportToGeoJson(GeoJsonExportFlags.geoJsonExportSkipCRS, geometry, null); + } + + @Override + public String exportSpatialReference(int export_flags, SpatialReference spatial_reference) { + return OperatorExportToGeoJsonCursor.exportSpatialReference(export_flags, spatial_reference); + } } diff --git a/src/main/java/com/esri/core/geometry/OperatorExportToJson.java b/src/main/java/com/esri/core/geometry/OperatorExportToJson.java index 63cf5958..2bb814c0 100644 --- a/src/main/java/com/esri/core/geometry/OperatorExportToJson.java +++ b/src/main/java/com/esri/core/geometry/OperatorExportToJson.java @@ -32,37 +32,37 @@ */ public abstract class OperatorExportToJson extends Operator { - @Override - public Type getType() { - return Type.ExportToJson; - } + @Override + public Type getType() { + return Type.ExportToJson; + } - /** - * Performs the ExportToJson operation - * - * @return Returns a StringCursor. - */ - public abstract StringCursor execute(SpatialReference spatialReference, - GeometryCursor geometryCursor); + /** + * Performs the ExportToJson operation + * + * @return Returns a StringCursor. + */ + public abstract StringCursor execute(SpatialReference spatialReference, + GeometryCursor geometryCursor); - /** - * Performs the ExportToJson operation - * - * @return Returns a String. - */ - public abstract String execute(SpatialReference spatialReference, - Geometry geometry); + /** + * Performs the ExportToJson operation + * + * @return Returns a String. + */ + public abstract String execute(SpatialReference spatialReference, + Geometry geometry); - /** - * Performs the ExportToJson operation - * - * @return Returns a String. - */ - public abstract String execute(SpatialReference spatialReference, - Geometry geometry, Map exportProperties); + /** + * Performs the ExportToJson operation + * + * @return Returns a String. + */ + public abstract String execute(SpatialReference spatialReference, + Geometry geometry, Map exportProperties); - public static OperatorExportToJson local() { - return (OperatorExportToJson) OperatorFactoryLocal.getInstance() - .getOperator(Type.ExportToJson); - } + public static OperatorExportToJson local() { + return (OperatorExportToJson) OperatorFactoryLocal.getInstance() + .getOperator(Type.ExportToJson); + } } diff --git a/src/main/java/com/esri/core/geometry/OperatorExportToJsonCursor.java b/src/main/java/com/esri/core/geometry/OperatorExportToJsonCursor.java index 04b98896..7d7ae145 100644 --- a/src/main/java/com/esri/core/geometry/OperatorExportToJsonCursor.java +++ b/src/main/java/com/esri/core/geometry/OperatorExportToJsonCursor.java @@ -29,442 +29,446 @@ public class OperatorExportToJsonCursor extends StringCursor { - private GeometryCursor m_geometryCursor; - private SpatialReference m_spatialReference; - private SimpleStateEnum simpleStateEnum = SimpleStateEnum.SIMPLE_UNKNOWN; - - - public OperatorExportToJsonCursor(SpatialReference spatialReference, GeometryCursor geometryCursor) { - if (geometryCursor == null) { - throw new IllegalArgumentException(); - } - - m_geometryCursor = geometryCursor; - m_spatialReference = spatialReference; - } - - @Override - public boolean hasNext() { return m_geometryCursor != null && m_geometryCursor.hasNext(); } - - @Override - public long getID() { - return m_geometryCursor.getGeometryID(); - } - - @Override - public SimpleStateEnum getSimpleState() { return simpleStateEnum; } - - @Override - public String getFeatureID() { - return m_geometryCursor.getFeatureID(); - } - - @Override - public String next() { - Geometry geometry; - if (hasNext()) { - geometry = m_geometryCursor.next(); - simpleStateEnum = geometry.getSimpleState(); - return exportToString(geometry, m_spatialReference, null); - } - return null; - } - - static String exportToString(Geometry geometry, SpatialReference spatialReference, Map exportProperties) { - JsonWriter jsonWriter = new JsonStringWriter(); - exportToJson_(geometry, spatialReference, jsonWriter, exportProperties); - return (String) jsonWriter.getJson(); - } - - private static void exportToJson_(Geometry geometry, SpatialReference spatialReference, JsonWriter jsonWriter, Map exportProperties) { - try { - int type = geometry.getType().value(); - switch (type) { - case Geometry.GeometryType.Point: - exportPointToJson((Point) geometry, spatialReference, jsonWriter, exportProperties); - break; - - case Geometry.GeometryType.MultiPoint: - exportMultiPointToJson((MultiPoint) geometry, spatialReference, jsonWriter, exportProperties); - break; - - case Geometry.GeometryType.Polyline: - exportPolylineToJson((Polyline) geometry, spatialReference, jsonWriter, exportProperties); - break; - - case Geometry.GeometryType.Polygon: - exportPolygonToJson((Polygon) geometry, spatialReference, jsonWriter, exportProperties); - break; - - case Geometry.GeometryType.Envelope: - exportEnvelopeToJson((Envelope) geometry, spatialReference, jsonWriter, exportProperties); - break; - - default: - throw new RuntimeException("not implemented for this geometry type"); - } - - } catch (Exception e) { - } - - } - - private static void exportPolygonToJson(Polygon pp, SpatialReference spatialReference, JsonWriter jsonWriter, Map exportProperties) { - exportPolypathToJson(pp, "rings", spatialReference, jsonWriter, exportProperties); - } - - private static void exportPolylineToJson(Polyline pp, SpatialReference spatialReference, JsonWriter jsonWriter, Map exportProperties) { - exportPolypathToJson(pp, "paths", spatialReference, jsonWriter, exportProperties); - } - - private static void exportPolypathToJson(MultiPath pp, String name, SpatialReference spatialReference, JsonWriter jsonWriter, Map exportProperties) { - boolean bExportZs = pp.hasAttribute(Semantics.Z); - boolean bExportMs = pp.hasAttribute(Semantics.M); - - boolean bPositionAsF = false; - int decimals = 17; - - if (exportProperties != null) { - Object numberOfDecimalsXY = exportProperties.get("numberOfDecimalsXY"); - if (numberOfDecimalsXY != null && numberOfDecimalsXY instanceof Number) { - bPositionAsF = true; - decimals = ((Number) numberOfDecimalsXY).intValue(); - } - } - - jsonWriter.startObject(); - - if (bExportZs) { - jsonWriter.addPairBoolean("hasZ", true); - } - - if (bExportMs) { - jsonWriter.addPairBoolean("hasM", true); - } - - jsonWriter.addPairArray(name); - - if (!pp.isEmpty()) { - int n = pp.getPathCount(); // rings or paths - - MultiPathImpl mpImpl = (MultiPathImpl) pp._getImpl();// get impl for - // faster - // access - AttributeStreamOfDbl zs = null; - AttributeStreamOfDbl ms = null; - - if (bExportZs) { - zs = (AttributeStreamOfDbl) mpImpl.getAttributeStreamRef(Semantics.Z); - } - - if (bExportMs) { - ms = (AttributeStreamOfDbl) mpImpl.getAttributeStreamRef(Semantics.M); - } - - boolean bPolygon = pp instanceof Polygon; - Point2D pt = new Point2D(); - - for (int i = 0; i < n; i++) { - jsonWriter.addValueArray(); - int startindex = pp.getPathStart(i); - int numVertices = pp.getPathSize(i); - double startx = 0.0, starty = 0.0, startz = NumberUtils.NaN(), startm = NumberUtils.NaN(); - double z = NumberUtils.NaN(), m = NumberUtils.NaN(); - boolean bClosed = pp.isClosedPath(i); - for (int j = startindex; j < startindex + numVertices; j++) { - pp.getXY(j, pt); - - jsonWriter.addValueArray(); - - if (bPositionAsF) { - jsonWriter.addValueDouble(pt.x, decimals, true); - jsonWriter.addValueDouble(pt.y, decimals, true); - } else { - jsonWriter.addValueDouble(pt.x); - jsonWriter.addValueDouble(pt.y); - } - - if (bExportZs) { - z = zs.get(j); - jsonWriter.addValueDouble(z); - } - - if (bExportMs) { - m = ms.get(j); - jsonWriter.addValueDouble(m); - } - - if (j == startindex && bClosed) { - startx = pt.x; - starty = pt.y; - startz = z; - startm = m; - } - - jsonWriter.endArray(); - } - - // Close the Path/Ring by writing the Point at the start index - if (bClosed && (startx != pt.x || starty != pt.y || (bExportZs && !(NumberUtils.isNaN(startz) && NumberUtils.isNaN(z)) && startz != z) || (bExportMs && !(NumberUtils.isNaN(startm) && NumberUtils.isNaN(m)) && startm != m))) { - pp.getXY(startindex, pt); - // getPoint(startindex); - jsonWriter.addValueArray(); - - if (bPositionAsF) { - jsonWriter.addValueDouble(pt.x, decimals, true); - jsonWriter.addValueDouble(pt.y, decimals, true); - } else { - jsonWriter.addValueDouble(pt.x); - jsonWriter.addValueDouble(pt.y); - } - - if (bExportZs) { - z = zs.get(startindex); - jsonWriter.addValueDouble(z); - } - - if (bExportMs) { - m = ms.get(startindex); - jsonWriter.addValueDouble(m); - } - - jsonWriter.endArray(); - } - - jsonWriter.endArray(); - } - } - - jsonWriter.endArray(); - - if (spatialReference != null) { - writeSR(spatialReference, jsonWriter); - } - - jsonWriter.endObject(); - } - - private static void exportMultiPointToJson(MultiPoint mpt, SpatialReference spatialReference, JsonWriter jsonWriter, Map exportProperties) { - boolean bExportZs = mpt.hasAttribute(Semantics.Z); - boolean bExportMs = mpt.hasAttribute(Semantics.M); - - boolean bPositionAsF = false; - int decimals = 17; - - if (exportProperties != null) { - Object numberOfDecimalsXY = exportProperties.get("numberOfDecimalsXY"); - if (numberOfDecimalsXY != null && numberOfDecimalsXY instanceof Number) { - bPositionAsF = true; - decimals = ((Number) numberOfDecimalsXY).intValue(); - } - } - - jsonWriter.startObject(); - - if (bExportZs) { - jsonWriter.addPairBoolean("hasZ", true); - } - - if (bExportMs) { - jsonWriter.addPairBoolean("hasM", true); - } - - jsonWriter.addPairArray("points"); - - if (!mpt.isEmpty()) { - MultiPointImpl mpImpl = (MultiPointImpl) mpt._getImpl();// get impl - // for - // faster - // access - AttributeStreamOfDbl zs = null; - AttributeStreamOfDbl ms = null; - - if (bExportZs) { - zs = (AttributeStreamOfDbl) mpImpl.getAttributeStreamRef(Semantics.Z); - } - - if (bExportMs) { - ms = (AttributeStreamOfDbl) mpImpl.getAttributeStreamRef(Semantics.M); - } - - Point2D pt = new Point2D(); - int n = mpt.getPointCount(); - for (int i = 0; i < n; i++) { - mpt.getXY(i, pt); - - jsonWriter.addValueArray(); - - if (bPositionAsF) { - jsonWriter.addValueDouble(pt.x, decimals, true); - jsonWriter.addValueDouble(pt.y, decimals, true); - } else { - jsonWriter.addValueDouble(pt.x); - jsonWriter.addValueDouble(pt.y); - } - - if (bExportZs) { - double z = zs.get(i); - jsonWriter.addValueDouble(z); - } - - if (bExportMs) { - double m = ms.get(i); - jsonWriter.addValueDouble(m); - } - - jsonWriter.endArray(); - } - } - - jsonWriter.endArray(); - - if (spatialReference != null) { - writeSR(spatialReference, jsonWriter); - } - - jsonWriter.endObject(); - } - - private static void exportPointToJson(Point pt, SpatialReference spatialReference, JsonWriter jsonWriter, Map exportProperties) { - boolean bExportZs = pt.hasAttribute(Semantics.Z); - boolean bExportMs = pt.hasAttribute(Semantics.M); - - boolean bPositionAsF = false; - int decimals = 17; - - if (exportProperties != null) { - Object numberOfDecimalsXY = exportProperties.get("numberOfDecimalsXY"); - if (numberOfDecimalsXY != null && numberOfDecimalsXY instanceof Number) { - bPositionAsF = true; - decimals = ((Number) numberOfDecimalsXY).intValue(); - } - } - - jsonWriter.startObject(); - - if (pt.isEmpty()) { - jsonWriter.addPairNull("x"); - jsonWriter.addPairNull("y"); - - if (bExportZs) { - jsonWriter.addPairNull("z"); - } - - if (bExportMs) { - jsonWriter.addPairNull("m"); - } - } else { - - if (bPositionAsF) { - jsonWriter.addPairDouble("x", pt.getX(), decimals, true); - jsonWriter.addPairDouble("y", pt.getY(), decimals, true); - } else { - jsonWriter.addPairDouble("x", pt.getX()); - jsonWriter.addPairDouble("y", pt.getY()); - } - - if (bExportZs) { - jsonWriter.addPairDouble("z", pt.getZ()); - } - - if (bExportMs) { - jsonWriter.addPairDouble("m", pt.getM()); - } - } - - if (spatialReference != null) { - writeSR(spatialReference, jsonWriter); - } - - jsonWriter.endObject(); - } - - private static void exportEnvelopeToJson(Envelope env, SpatialReference spatialReference, JsonWriter jsonWriter, Map exportProperties) { - boolean bExportZs = env.hasAttribute(Semantics.Z); - boolean bExportMs = env.hasAttribute(Semantics.M); - - boolean bPositionAsF = false; - int decimals = 17; - - if (exportProperties != null) { - Object numberOfDecimalsXY = exportProperties.get("numberOfDecimalsXY"); - if (numberOfDecimalsXY != null && numberOfDecimalsXY instanceof Number) { - bPositionAsF = true; - decimals = ((Number) numberOfDecimalsXY).intValue(); - } - } - - jsonWriter.startObject(); - - if (env.isEmpty()) { - jsonWriter.addPairNull("xmin"); - jsonWriter.addPairNull("ymin"); - jsonWriter.addPairNull("xmax"); - jsonWriter.addPairNull("ymax"); - - if (bExportZs) { - jsonWriter.addPairNull("zmin"); - jsonWriter.addPairNull("zmax"); - } - - if (bExportMs) { - jsonWriter.addPairNull("mmin"); - jsonWriter.addPairNull("mmax"); - } - } else { - - if (bPositionAsF) { - jsonWriter.addPairDouble("xmin", env.getXMin(), decimals, true); - jsonWriter.addPairDouble("ymin", env.getYMin(), decimals, true); - jsonWriter.addPairDouble("xmax", env.getXMax(), decimals, true); - jsonWriter.addPairDouble("ymax", env.getYMax(), decimals, true); - } else { - jsonWriter.addPairDouble("xmin", env.getXMin()); - jsonWriter.addPairDouble("ymin", env.getYMin()); - jsonWriter.addPairDouble("xmax", env.getXMax()); - jsonWriter.addPairDouble("ymax", env.getYMax()); - } - - if (bExportZs) { - Envelope1D z = env.queryInterval(Semantics.Z, 0); - jsonWriter.addPairDouble("zmin", z.vmin); - jsonWriter.addPairDouble("zmax", z.vmax); - } - - if (bExportMs) { - Envelope1D m = env.queryInterval(Semantics.M, 0); - jsonWriter.addPairDouble("mmin", m.vmin); - jsonWriter.addPairDouble("mmax", m.vmax); - } - } - - if (spatialReference != null) { - writeSR(spatialReference, jsonWriter); - } - - jsonWriter.endObject(); - } - - private static void writeSR(SpatialReference spatialReference, JsonWriter jsonWriter) { - int wkid = spatialReference.getOldID(); - if (wkid > 0) { - jsonWriter.addPairObject("spatialReference"); - - jsonWriter.addPairInt("wkid", wkid); - - int latest_wkid = spatialReference.getLatestID(); - if (latest_wkid > 0 && latest_wkid != wkid) { - jsonWriter.addPairInt("latestWkid", latest_wkid); - } - - jsonWriter.endObject(); - } else { - String wkt = spatialReference.getText(); - if (wkt != null) { - jsonWriter.addPairObject("spatialReference"); - jsonWriter.addPairString("wkt", wkt); - jsonWriter.endObject(); - } - } - } + private GeometryCursor m_geometryCursor; + private SpatialReference m_spatialReference; + private SimpleStateEnum simpleStateEnum = SimpleStateEnum.SIMPLE_UNKNOWN; + + + public OperatorExportToJsonCursor(SpatialReference spatialReference, GeometryCursor geometryCursor) { + if (geometryCursor == null) { + throw new IllegalArgumentException(); + } + + m_geometryCursor = geometryCursor; + m_spatialReference = spatialReference; + } + + @Override + public boolean hasNext() { + return m_geometryCursor != null && m_geometryCursor.hasNext(); + } + + @Override + public long getID() { + return m_geometryCursor.getGeometryID(); + } + + @Override + public SimpleStateEnum getSimpleState() { + return simpleStateEnum; + } + + @Override + public String getFeatureID() { + return m_geometryCursor.getFeatureID(); + } + + @Override + public String next() { + Geometry geometry; + if (hasNext()) { + geometry = m_geometryCursor.next(); + simpleStateEnum = geometry.getSimpleState(); + return exportToString(geometry, m_spatialReference, null); + } + return null; + } + + static String exportToString(Geometry geometry, SpatialReference spatialReference, Map exportProperties) { + JsonWriter jsonWriter = new JsonStringWriter(); + exportToJson_(geometry, spatialReference, jsonWriter, exportProperties); + return (String) jsonWriter.getJson(); + } + + private static void exportToJson_(Geometry geometry, SpatialReference spatialReference, JsonWriter jsonWriter, Map exportProperties) { + try { + int type = geometry.getType().value(); + switch (type) { + case Geometry.GeometryType.Point: + exportPointToJson((Point) geometry, spatialReference, jsonWriter, exportProperties); + break; + + case Geometry.GeometryType.MultiPoint: + exportMultiPointToJson((MultiPoint) geometry, spatialReference, jsonWriter, exportProperties); + break; + + case Geometry.GeometryType.Polyline: + exportPolylineToJson((Polyline) geometry, spatialReference, jsonWriter, exportProperties); + break; + + case Geometry.GeometryType.Polygon: + exportPolygonToJson((Polygon) geometry, spatialReference, jsonWriter, exportProperties); + break; + + case Geometry.GeometryType.Envelope: + exportEnvelopeToJson((Envelope) geometry, spatialReference, jsonWriter, exportProperties); + break; + + default: + throw new RuntimeException("not implemented for this geometry type"); + } + + } catch (Exception e) { + } + + } + + private static void exportPolygonToJson(Polygon pp, SpatialReference spatialReference, JsonWriter jsonWriter, Map exportProperties) { + exportPolypathToJson(pp, "rings", spatialReference, jsonWriter, exportProperties); + } + + private static void exportPolylineToJson(Polyline pp, SpatialReference spatialReference, JsonWriter jsonWriter, Map exportProperties) { + exportPolypathToJson(pp, "paths", spatialReference, jsonWriter, exportProperties); + } + + private static void exportPolypathToJson(MultiPath pp, String name, SpatialReference spatialReference, JsonWriter jsonWriter, Map exportProperties) { + boolean bExportZs = pp.hasAttribute(Semantics.Z); + boolean bExportMs = pp.hasAttribute(Semantics.M); + + boolean bPositionAsF = false; + int decimals = 17; + + if (exportProperties != null) { + Object numberOfDecimalsXY = exportProperties.get("numberOfDecimalsXY"); + if (numberOfDecimalsXY != null && numberOfDecimalsXY instanceof Number) { + bPositionAsF = true; + decimals = ((Number) numberOfDecimalsXY).intValue(); + } + } + + jsonWriter.startObject(); + + if (bExportZs) { + jsonWriter.addPairBoolean("hasZ", true); + } + + if (bExportMs) { + jsonWriter.addPairBoolean("hasM", true); + } + + jsonWriter.addPairArray(name); + + if (!pp.isEmpty()) { + int n = pp.getPathCount(); // rings or paths + + MultiPathImpl mpImpl = (MultiPathImpl) pp._getImpl();// get impl for + // faster + // access + AttributeStreamOfDbl zs = null; + AttributeStreamOfDbl ms = null; + + if (bExportZs) { + zs = (AttributeStreamOfDbl) mpImpl.getAttributeStreamRef(Semantics.Z); + } + + if (bExportMs) { + ms = (AttributeStreamOfDbl) mpImpl.getAttributeStreamRef(Semantics.M); + } + + boolean bPolygon = pp instanceof Polygon; + Point2D pt = new Point2D(); + + for (int i = 0; i < n; i++) { + jsonWriter.addValueArray(); + int startindex = pp.getPathStart(i); + int numVertices = pp.getPathSize(i); + double startx = 0.0, starty = 0.0, startz = NumberUtils.NaN(), startm = NumberUtils.NaN(); + double z = NumberUtils.NaN(), m = NumberUtils.NaN(); + boolean bClosed = pp.isClosedPath(i); + for (int j = startindex; j < startindex + numVertices; j++) { + pp.getXY(j, pt); + + jsonWriter.addValueArray(); + + if (bPositionAsF) { + jsonWriter.addValueDouble(pt.x, decimals, true); + jsonWriter.addValueDouble(pt.y, decimals, true); + } else { + jsonWriter.addValueDouble(pt.x); + jsonWriter.addValueDouble(pt.y); + } + + if (bExportZs) { + z = zs.get(j); + jsonWriter.addValueDouble(z); + } + + if (bExportMs) { + m = ms.get(j); + jsonWriter.addValueDouble(m); + } + + if (j == startindex && bClosed) { + startx = pt.x; + starty = pt.y; + startz = z; + startm = m; + } + + jsonWriter.endArray(); + } + + // Close the Path/Ring by writing the Point at the start index + if (bClosed && (startx != pt.x || starty != pt.y || (bExportZs && !(NumberUtils.isNaN(startz) && NumberUtils.isNaN(z)) && startz != z) || (bExportMs && !(NumberUtils.isNaN(startm) && NumberUtils.isNaN(m)) && startm != m))) { + pp.getXY(startindex, pt); + // getPoint(startindex); + jsonWriter.addValueArray(); + + if (bPositionAsF) { + jsonWriter.addValueDouble(pt.x, decimals, true); + jsonWriter.addValueDouble(pt.y, decimals, true); + } else { + jsonWriter.addValueDouble(pt.x); + jsonWriter.addValueDouble(pt.y); + } + + if (bExportZs) { + z = zs.get(startindex); + jsonWriter.addValueDouble(z); + } + + if (bExportMs) { + m = ms.get(startindex); + jsonWriter.addValueDouble(m); + } + + jsonWriter.endArray(); + } + + jsonWriter.endArray(); + } + } + + jsonWriter.endArray(); + + if (spatialReference != null) { + writeSR(spatialReference, jsonWriter); + } + + jsonWriter.endObject(); + } + + private static void exportMultiPointToJson(MultiPoint mpt, SpatialReference spatialReference, JsonWriter jsonWriter, Map exportProperties) { + boolean bExportZs = mpt.hasAttribute(Semantics.Z); + boolean bExportMs = mpt.hasAttribute(Semantics.M); + + boolean bPositionAsF = false; + int decimals = 17; + + if (exportProperties != null) { + Object numberOfDecimalsXY = exportProperties.get("numberOfDecimalsXY"); + if (numberOfDecimalsXY != null && numberOfDecimalsXY instanceof Number) { + bPositionAsF = true; + decimals = ((Number) numberOfDecimalsXY).intValue(); + } + } + + jsonWriter.startObject(); + + if (bExportZs) { + jsonWriter.addPairBoolean("hasZ", true); + } + + if (bExportMs) { + jsonWriter.addPairBoolean("hasM", true); + } + + jsonWriter.addPairArray("points"); + + if (!mpt.isEmpty()) { + MultiPointImpl mpImpl = (MultiPointImpl) mpt._getImpl();// get impl + // for + // faster + // access + AttributeStreamOfDbl zs = null; + AttributeStreamOfDbl ms = null; + + if (bExportZs) { + zs = (AttributeStreamOfDbl) mpImpl.getAttributeStreamRef(Semantics.Z); + } + + if (bExportMs) { + ms = (AttributeStreamOfDbl) mpImpl.getAttributeStreamRef(Semantics.M); + } + + Point2D pt = new Point2D(); + int n = mpt.getPointCount(); + for (int i = 0; i < n; i++) { + mpt.getXY(i, pt); + + jsonWriter.addValueArray(); + + if (bPositionAsF) { + jsonWriter.addValueDouble(pt.x, decimals, true); + jsonWriter.addValueDouble(pt.y, decimals, true); + } else { + jsonWriter.addValueDouble(pt.x); + jsonWriter.addValueDouble(pt.y); + } + + if (bExportZs) { + double z = zs.get(i); + jsonWriter.addValueDouble(z); + } + + if (bExportMs) { + double m = ms.get(i); + jsonWriter.addValueDouble(m); + } + + jsonWriter.endArray(); + } + } + + jsonWriter.endArray(); + + if (spatialReference != null) { + writeSR(spatialReference, jsonWriter); + } + + jsonWriter.endObject(); + } + + private static void exportPointToJson(Point pt, SpatialReference spatialReference, JsonWriter jsonWriter, Map exportProperties) { + boolean bExportZs = pt.hasAttribute(Semantics.Z); + boolean bExportMs = pt.hasAttribute(Semantics.M); + + boolean bPositionAsF = false; + int decimals = 17; + + if (exportProperties != null) { + Object numberOfDecimalsXY = exportProperties.get("numberOfDecimalsXY"); + if (numberOfDecimalsXY != null && numberOfDecimalsXY instanceof Number) { + bPositionAsF = true; + decimals = ((Number) numberOfDecimalsXY).intValue(); + } + } + + jsonWriter.startObject(); + + if (pt.isEmpty()) { + jsonWriter.addPairNull("x"); + jsonWriter.addPairNull("y"); + + if (bExportZs) { + jsonWriter.addPairNull("z"); + } + + if (bExportMs) { + jsonWriter.addPairNull("m"); + } + } else { + + if (bPositionAsF) { + jsonWriter.addPairDouble("x", pt.getX(), decimals, true); + jsonWriter.addPairDouble("y", pt.getY(), decimals, true); + } else { + jsonWriter.addPairDouble("x", pt.getX()); + jsonWriter.addPairDouble("y", pt.getY()); + } + + if (bExportZs) { + jsonWriter.addPairDouble("z", pt.getZ()); + } + + if (bExportMs) { + jsonWriter.addPairDouble("m", pt.getM()); + } + } + + if (spatialReference != null) { + writeSR(spatialReference, jsonWriter); + } + + jsonWriter.endObject(); + } + + private static void exportEnvelopeToJson(Envelope env, SpatialReference spatialReference, JsonWriter jsonWriter, Map exportProperties) { + boolean bExportZs = env.hasAttribute(Semantics.Z); + boolean bExportMs = env.hasAttribute(Semantics.M); + + boolean bPositionAsF = false; + int decimals = 17; + + if (exportProperties != null) { + Object numberOfDecimalsXY = exportProperties.get("numberOfDecimalsXY"); + if (numberOfDecimalsXY != null && numberOfDecimalsXY instanceof Number) { + bPositionAsF = true; + decimals = ((Number) numberOfDecimalsXY).intValue(); + } + } + + jsonWriter.startObject(); + + if (env.isEmpty()) { + jsonWriter.addPairNull("xmin"); + jsonWriter.addPairNull("ymin"); + jsonWriter.addPairNull("xmax"); + jsonWriter.addPairNull("ymax"); + + if (bExportZs) { + jsonWriter.addPairNull("zmin"); + jsonWriter.addPairNull("zmax"); + } + + if (bExportMs) { + jsonWriter.addPairNull("mmin"); + jsonWriter.addPairNull("mmax"); + } + } else { + + if (bPositionAsF) { + jsonWriter.addPairDouble("xmin", env.getXMin(), decimals, true); + jsonWriter.addPairDouble("ymin", env.getYMin(), decimals, true); + jsonWriter.addPairDouble("xmax", env.getXMax(), decimals, true); + jsonWriter.addPairDouble("ymax", env.getYMax(), decimals, true); + } else { + jsonWriter.addPairDouble("xmin", env.getXMin()); + jsonWriter.addPairDouble("ymin", env.getYMin()); + jsonWriter.addPairDouble("xmax", env.getXMax()); + jsonWriter.addPairDouble("ymax", env.getYMax()); + } + + if (bExportZs) { + Envelope1D z = env.queryInterval(Semantics.Z, 0); + jsonWriter.addPairDouble("zmin", z.vmin); + jsonWriter.addPairDouble("zmax", z.vmax); + } + + if (bExportMs) { + Envelope1D m = env.queryInterval(Semantics.M, 0); + jsonWriter.addPairDouble("mmin", m.vmin); + jsonWriter.addPairDouble("mmax", m.vmax); + } + } + + if (spatialReference != null) { + writeSR(spatialReference, jsonWriter); + } + + jsonWriter.endObject(); + } + + private static void writeSR(SpatialReference spatialReference, JsonWriter jsonWriter) { + int wkid = spatialReference.getOldID(); + if (wkid > 0) { + jsonWriter.addPairObject("spatialReference"); + + jsonWriter.addPairInt("wkid", wkid); + + int latest_wkid = spatialReference.getLatestID(); + if (latest_wkid > 0 && latest_wkid != wkid) { + jsonWriter.addPairInt("latestWkid", latest_wkid); + } + + jsonWriter.endObject(); + } else { + String wkt = spatialReference.getText(); + if (wkt != null) { + jsonWriter.addPairObject("spatialReference"); + jsonWriter.addPairString("wkt", wkt); + jsonWriter.endObject(); + } + } + } } diff --git a/src/main/java/com/esri/core/geometry/OperatorExportToJsonLocal.java b/src/main/java/com/esri/core/geometry/OperatorExportToJsonLocal.java index 90c7880c..5446bf9b 100644 --- a/src/main/java/com/esri/core/geometry/OperatorExportToJsonLocal.java +++ b/src/main/java/com/esri/core/geometry/OperatorExportToJsonLocal.java @@ -27,22 +27,22 @@ class OperatorExportToJsonLocal extends OperatorExportToJson { - @Override - public StringCursor execute(SpatialReference spatialReference, - GeometryCursor geometryCursor) { - return new OperatorExportToJsonCursor(spatialReference, geometryCursor); - } - - @Override - public String execute(SpatialReference spatialReference, Geometry geometry) { - SimpleGeometryCursor gc = new SimpleGeometryCursor(geometry); - StringCursor cursor = new OperatorExportToJsonCursor(spatialReference, gc); - return cursor.next(); - } - - @Override - public String execute(SpatialReference spatialReference, - Geometry geometry, Map exportProperties) { - return OperatorExportToJsonCursor.exportToString(geometry, spatialReference, exportProperties); - } + @Override + public StringCursor execute(SpatialReference spatialReference, + GeometryCursor geometryCursor) { + return new OperatorExportToJsonCursor(spatialReference, geometryCursor); + } + + @Override + public String execute(SpatialReference spatialReference, Geometry geometry) { + SimpleGeometryCursor gc = new SimpleGeometryCursor(geometry); + StringCursor cursor = new OperatorExportToJsonCursor(spatialReference, gc); + return cursor.next(); + } + + @Override + public String execute(SpatialReference spatialReference, + Geometry geometry, Map exportProperties) { + return OperatorExportToJsonCursor.exportToString(geometry, spatialReference, exportProperties); + } } diff --git a/src/main/java/com/esri/core/geometry/OperatorExportToWkb.java b/src/main/java/com/esri/core/geometry/OperatorExportToWkb.java index 67b1d2a6..86b6d48d 100644 --- a/src/main/java/com/esri/core/geometry/OperatorExportToWkb.java +++ b/src/main/java/com/esri/core/geometry/OperatorExportToWkb.java @@ -32,41 +32,41 @@ * Export to WKB format. */ public abstract class OperatorExportToWkb extends Operator { - @Override - public Type getType() { - return Type.ExportToWkb; - } + @Override + public Type getType() { + return Type.ExportToWkb; + } - /** - * Performs the ExportToWKB operation on cursor of geometries - * - * @param exportFlags This should probably be removed - * @param geometryCursor - * @return - */ - public abstract ByteBufferCursor execute(int exportFlags, GeometryCursor geometryCursor); + /** + * Performs the ExportToWKB operation on cursor of geometries + * + * @param exportFlags This should probably be removed + * @param geometryCursor + * @return + */ + public abstract ByteBufferCursor execute(int exportFlags, GeometryCursor geometryCursor); - /** - * Performs the ExportToWKB operation. - * - * @param exportFlags Use the {@link WkbExportFlags} interface. - * @param geometry The Geometry being exported. - * @return Returns a ByteBuffer object containing the Geometry in WKB format - */ - public abstract ByteBuffer execute(int exportFlags, Geometry geometry, ProgressTracker progressTracker); + /** + * Performs the ExportToWKB operation. + * + * @param exportFlags Use the {@link WkbExportFlags} interface. + * @param geometry The Geometry being exported. + * @return Returns a ByteBuffer object containing the Geometry in WKB format + */ + public abstract ByteBuffer execute(int exportFlags, Geometry geometry, ProgressTracker progressTracker); - /** - * Performs the ExportToWKB operation. - * - * @param exportFlags Use the {@link WkbExportFlags} interface. - * @param geometry The Geometry being exported. - * @param wkbBuffer The ByteBuffer to contain the exported Geometry in WKB format. - * @return If the input buffer is null, then the size needed for the buffer is returned. Otherwise the number of bytes written to the buffer is returned. - */ - public abstract int execute(int exportFlags, Geometry geometry, ByteBuffer wkbBuffer, ProgressTracker progressTracker); + /** + * Performs the ExportToWKB operation. + * + * @param exportFlags Use the {@link WkbExportFlags} interface. + * @param geometry The Geometry being exported. + * @param wkbBuffer The ByteBuffer to contain the exported Geometry in WKB format. + * @return If the input buffer is null, then the size needed for the buffer is returned. Otherwise the number of bytes written to the buffer is returned. + */ + public abstract int execute(int exportFlags, Geometry geometry, ByteBuffer wkbBuffer, ProgressTracker progressTracker); - public static OperatorExportToWkb local() { - return (OperatorExportToWkb) OperatorFactoryLocal.getInstance().getOperator(Type.ExportToWkb); - } + public static OperatorExportToWkb local() { + return (OperatorExportToWkb) OperatorFactoryLocal.getInstance().getOperator(Type.ExportToWkb); + } } diff --git a/src/main/java/com/esri/core/geometry/OperatorExportToWkbCursor.java b/src/main/java/com/esri/core/geometry/OperatorExportToWkbCursor.java index 019f85b2..ca207237 100644 --- a/src/main/java/com/esri/core/geometry/OperatorExportToWkbCursor.java +++ b/src/main/java/com/esri/core/geometry/OperatorExportToWkbCursor.java @@ -4,49 +4,49 @@ import java.nio.ByteOrder; public class OperatorExportToWkbCursor extends ByteBufferCursor { - private GeometryCursor m_geometryCursor; - private int m_exportFlags; - private SimpleStateEnum simpleStateEnum = SimpleStateEnum.SIMPLE_UNKNOWN; - - public OperatorExportToWkbCursor(int exportFlags, GeometryCursor geometryCursor) { - if (geometryCursor == null) - throw new GeometryException("invalid argument"); - - m_exportFlags = exportFlags; - m_geometryCursor = geometryCursor; - } - - @Override - public boolean hasNext() { - return m_geometryCursor != null && m_geometryCursor.hasNext(); - } - - @Override - public ByteBuffer next() { - Geometry geometry; - if (hasNext()) { - geometry = m_geometryCursor.next(); - simpleStateEnum = geometry.getSimpleState(); - int size = OperatorExportToWkbLocal.exportToWKB(m_exportFlags, geometry, null); - ByteBuffer wkbBuffer = ByteBuffer.allocate(size).order(ByteOrder.nativeOrder()); - OperatorExportToWkbLocal.exportToWKB(m_exportFlags, geometry, wkbBuffer); - return wkbBuffer; - } - return null; - } - - @Override - public long getByteBufferID() { - return m_geometryCursor.getGeometryID(); - } - - @Override - public SimpleStateEnum getSimpleState() { - return simpleStateEnum; - } - - @Override - public String getFeatureID() { - return m_geometryCursor.getFeatureID(); - } + private GeometryCursor m_geometryCursor; + private int m_exportFlags; + private SimpleStateEnum simpleStateEnum = SimpleStateEnum.SIMPLE_UNKNOWN; + + public OperatorExportToWkbCursor(int exportFlags, GeometryCursor geometryCursor) { + if (geometryCursor == null) + throw new GeometryException("invalid argument"); + + m_exportFlags = exportFlags; + m_geometryCursor = geometryCursor; + } + + @Override + public boolean hasNext() { + return m_geometryCursor != null && m_geometryCursor.hasNext(); + } + + @Override + public ByteBuffer next() { + Geometry geometry; + if (hasNext()) { + geometry = m_geometryCursor.next(); + simpleStateEnum = geometry.getSimpleState(); + int size = OperatorExportToWkbLocal.exportToWKB(m_exportFlags, geometry, null); + ByteBuffer wkbBuffer = ByteBuffer.allocate(size).order(ByteOrder.nativeOrder()); + OperatorExportToWkbLocal.exportToWKB(m_exportFlags, geometry, wkbBuffer); + return wkbBuffer; + } + return null; + } + + @Override + public long getByteBufferID() { + return m_geometryCursor.getGeometryID(); + } + + @Override + public SimpleStateEnum getSimpleState() { + return simpleStateEnum; + } + + @Override + public String getFeatureID() { + return m_geometryCursor.getFeatureID(); + } } diff --git a/src/main/java/com/esri/core/geometry/OperatorExportToWkbLocal.java b/src/main/java/com/esri/core/geometry/OperatorExportToWkbLocal.java index 51c62f39..ddce628b 100644 --- a/src/main/java/com/esri/core/geometry/OperatorExportToWkbLocal.java +++ b/src/main/java/com/esri/core/geometry/OperatorExportToWkbLocal.java @@ -29,1224 +29,1224 @@ class OperatorExportToWkbLocal extends OperatorExportToWkb { - @Override - public ByteBufferCursor execute(int exportFlags, GeometryCursor geometryCursor) { - return new OperatorExportToWkbCursor(exportFlags, geometryCursor); - } - - @Override - public ByteBuffer execute(int exportFlags, Geometry geometry, ProgressTracker progressTracker) { - int size = exportToWKB(exportFlags, geometry, null); - ByteBuffer wkbBuffer = ByteBuffer.allocate(size).order(ByteOrder.nativeOrder()); - exportToWKB(exportFlags, geometry, wkbBuffer); - return wkbBuffer; - } - - @Override - public int execute(int exportFlags, Geometry geometry, - ByteBuffer wkbBuffer, ProgressTracker progressTracker) { - return exportToWKB(exportFlags, geometry, wkbBuffer); - } - - protected static int exportToWKB(int exportFlags, Geometry geometry, - ByteBuffer wkbBuffer) { - if (geometry == null) - return 0; - - int type = geometry.getType().value(); - switch (type) { - case Geometry.GeometryType.Polygon: - if ((exportFlags & WkbExportFlags.wkbExportLineString) != 0 - || (exportFlags & WkbExportFlags.wkbExportMultiLineString) != 0 - || (exportFlags & WkbExportFlags.wkbExportPoint) != 0 - || (exportFlags & WkbExportFlags.wkbExportMultiPoint) != 0) - throw new GeometryException("invalid argument"); - - return exportPolygonToWKB(exportFlags, (Polygon) geometry, - wkbBuffer); - case Geometry.GeometryType.Polyline: - if ((exportFlags & WkbExportFlags.wkbExportPolygon) != 0 - || (exportFlags & WkbExportFlags.wkbExportMultiPolygon) != 0 - || (exportFlags & WkbExportFlags.wkbExportPoint) != 0 - || (exportFlags & WkbExportFlags.wkbExportMultiPoint) != 0) - throw new GeometryException("invalid argument"); - return exportPolylineToWKB(exportFlags, (Polyline) geometry, - wkbBuffer); - - case Geometry.GeometryType.MultiPoint: - if ((exportFlags & WkbExportFlags.wkbExportLineString) != 0 - || (exportFlags & WkbExportFlags.wkbExportMultiLineString) != 0 - || (exportFlags & WkbExportFlags.wkbExportPolygon) != 0 - || (exportFlags & WkbExportFlags.wkbExportMultiPolygon) != 0) - throw new GeometryException("invalid argument"); - return exportMultiPointToWKB(exportFlags, (MultiPoint) geometry, - wkbBuffer); - - case Geometry.GeometryType.Point: - if ((exportFlags & WkbExportFlags.wkbExportLineString) != 0 - || (exportFlags & WkbExportFlags.wkbExportMultiLineString) != 0 - || (exportFlags & WkbExportFlags.wkbExportPolygon) != 0 - || (exportFlags & WkbExportFlags.wkbExportMultiPolygon) != 0) - throw new GeometryException("invalid argument"); - return exportPointToWKB(exportFlags, (Point) geometry, wkbBuffer); - - case Geometry.GeometryType.Envelope: - if ((exportFlags & WkbExportFlags.wkbExportLineString) != 0 - || (exportFlags & WkbExportFlags.wkbExportMultiLineString) != 0 - || (exportFlags & WkbExportFlags.wkbExportPoint) != 0 - || (exportFlags & WkbExportFlags.wkbExportMultiPoint) != 0) - throw new GeometryException("invalid argument"); - return exportEnvelopeToWKB(exportFlags, (Envelope) geometry, - wkbBuffer); - - default: { - throw GeometryException.GeometryInternalError(); - // return -1; - } - } - } - - private static int exportPolygonToWKB(int exportFlags, Polygon _polygon, - ByteBuffer wkbBuffer) { - MultiPathImpl polygon = (MultiPathImpl) _polygon._getImpl(); - - if ((exportFlags & (int) WkbExportFlags.wkbExportFailIfNotSimple) != 0) { - int simple = polygon.getIsSimple(0.0); - - if (simple != MultiVertexGeometryImpl.GeometryXSimple.Strong) - throw new GeometryException("non simple geometry"); - } - - boolean bExportZs = polygon.hasAttribute(VertexDescription.Semantics.Z) - && (exportFlags & (int) WkbExportFlags.wkbExportStripZs) == 0; - boolean bExportMs = polygon.hasAttribute(VertexDescription.Semantics.M) - && (exportFlags & (int) WkbExportFlags.wkbExportStripMs) == 0; - - int polygonCount = polygon.getOGCPolygonCount(); - if ((exportFlags & (int) WkbExportFlags.wkbExportPolygon) != 0 - && polygonCount > 1) - throw new IllegalArgumentException(); - - int partCount = polygon.getPathCount(); - int point_count = polygon.getPointCount(); - point_count += partCount; // add 1 point per part - - if (point_count > 0 && polygonCount == 0) - throw new GeometryException("corrupted geometry"); - - // In the WKB_export_defaults case, polygons gets exported as a - // WKB_multi_polygon. - - // get size for buffer - int size = 0; - if ((exportFlags & (int) WkbExportFlags.wkbExportPolygon) == 0 - || polygonCount == 0) - size += 1 /* byte order */ + 4 /* wkbType */ + 4 /* numPolygons */; - - size += polygonCount - * (1 /* byte order */ + 4 /* wkbType */ + 4/* numRings */) - + partCount * (4 /* num_points */) + point_count * (2 * 8 /* - * xy - * coordinates - */); - - if (bExportZs) - size += (point_count * 8 /* zs */); - if (bExportMs) - size += (point_count * 8 /* ms */); - - if (size >= NumberUtils.intMax()) - throw new GeometryException("invalid call"); - - if (wkbBuffer == null) - return (int) size; - else if (wkbBuffer.capacity() < size) - throw new GeometryException("buffer is too small"); - - int offset = 0; - - byte byteOrder = (byte) (wkbBuffer.order() == ByteOrder.LITTLE_ENDIAN ? WkbByteOrder.wkbNDR - : WkbByteOrder.wkbXDR); - - // Determine the wkb type - int type; - if (!bExportZs && !bExportMs) { - type = WkbGeometryType.wkbPolygon; - - if ((exportFlags & WktExportFlags.wktExportPolygon) == 0) { - wkbBuffer.put(offset, byteOrder); - offset += 1; - wkbBuffer.putInt(offset, WkbGeometryType.wkbMultiPolygon); - offset += 4; - wkbBuffer.putInt(offset, polygonCount); - offset += 4; - } else if (polygonCount == 0) { - wkbBuffer.put(offset, byteOrder); - offset += 1; - wkbBuffer.putInt(offset, WkbGeometryType.wkbPolygon); - offset += 4; - wkbBuffer.putInt(offset, 0); - offset += 4; - } - } else if (bExportZs && !bExportMs) { - type = WkbGeometryType.wkbPolygonZ; - - if ((exportFlags & WkbExportFlags.wkbExportPolygon) == 0) { - wkbBuffer.put(offset, byteOrder); - offset += 1; - wkbBuffer.putInt(offset, WkbGeometryType.wkbMultiPolygonZ); - offset += 4; - wkbBuffer.putInt(offset, polygonCount); - offset += 4; - } else if (polygonCount == 0) { - wkbBuffer.put(offset, byteOrder); - offset += 1; - wkbBuffer.putInt(offset, WkbGeometryType.wkbPolygonZ); - offset += 4; - wkbBuffer.putInt(offset, 0); - offset += 4; - } - } else if (bExportMs && !bExportZs) { - type = WkbGeometryType.wkbPolygonM; - - if ((exportFlags & WkbExportFlags.wkbExportPolygon) == 0) { - wkbBuffer.put(offset, byteOrder); - offset += 1; - wkbBuffer.putInt(offset, WkbGeometryType.wkbMultiPolygonM); - offset += 4; - wkbBuffer.putInt(offset, (int) polygonCount); - offset += 4; - } else if (polygonCount == 0) { - wkbBuffer.put(offset, byteOrder); - offset += 1; - wkbBuffer.putInt(offset, WkbGeometryType.wkbPolygonM); - offset += 4; - wkbBuffer.putInt(offset, 0); - offset += 4; - } - } else { - type = WkbGeometryType.wkbPolygonZM; - - if ((exportFlags & WkbExportFlags.wkbExportPolygon) == 0) { - wkbBuffer.put(offset, byteOrder); - offset += 1; - wkbBuffer.putInt(offset, WkbGeometryType.wkbMultiPolygonZM); - offset += 4; - wkbBuffer.putInt(offset, polygonCount); - offset += 4; - } else if (polygonCount == 0) { - wkbBuffer.put(offset, byteOrder); - offset += 1; - wkbBuffer.putInt(offset, WkbGeometryType.wkbPolygonZM); - offset += 4; - wkbBuffer.putInt(offset, 0); - offset += 4; - } - } - - if (polygonCount == 0) - return offset; - - AttributeStreamOfDbl position = (AttributeStreamOfDbl) (polygon - .getAttributeStreamRef(VertexDescription.Semantics.POSITION)); - - AttributeStreamOfDbl zs = null; - if (bExportZs) { - if (polygon - ._attributeStreamIsAllocated(VertexDescription.Semantics.Z)) - zs = (AttributeStreamOfDbl) (polygon - .getAttributeStreamRef(VertexDescription.Semantics.Z)); - } - - AttributeStreamOfDbl ms = null; - if (bExportMs) { - if (polygon - ._attributeStreamIsAllocated(VertexDescription.Semantics.M)) - ms = (AttributeStreamOfDbl) (polygon - .getAttributeStreamRef(VertexDescription.Semantics.M)); - } - - int ipartend = 0; - int ipolygonend = 0; - - for (int ipolygon = 0; ipolygon < (int) polygonCount; ipolygon++) { - // write byte order - wkbBuffer.put(offset, byteOrder); - offset += 1; - - // write type - wkbBuffer.putInt(offset, type); - offset += 4; - - // get partcount for the ith polygon - AttributeStreamOfInt8 pathFlags = polygon.getPathFlagsStreamRef(); - - int ipolygonstart = ipolygonend; - ipolygonend++; - - while (ipolygonend < partCount - && (pathFlags.read(ipolygonend) & PathFlags.enumOGCStartPolygon) == 0) - ipolygonend++; - - // write numRings - wkbBuffer.putInt(offset, ipolygonend - ipolygonstart); - offset += 4; - - for (int ipart = ipolygonstart; ipart < ipolygonend; ipart++) { - // get num_points - int ipartstart = ipartend; - ipartend = (int) polygon.getPathEnd(ipart); - - // write num_points - wkbBuffer.putInt(offset, ipartend - ipartstart + 1); - offset += 4; - - // duplicate the start point - double x = position.read(2 * ipartstart); - double y = position.read(2 * ipartstart + 1); - - wkbBuffer.putDouble(offset, x); - offset += 8; - wkbBuffer.putDouble(offset, y); - offset += 8; - - if (bExportZs) { - double z; - if (zs != null) - z = zs.read(ipartstart); - else - z = VertexDescription - .getDefaultValue(VertexDescription.Semantics.Z); - - wkbBuffer.putDouble(offset, z); - offset += 8; - } - - if (bExportMs) { - double m; - if (ms != null) - m = ms.read(ipartstart); - else - m = VertexDescription - .getDefaultValue(VertexDescription.Semantics.M); - - wkbBuffer.putDouble(offset, m); - offset += 8; - } - - // We must write to the buffer backwards - ogc polygon format is - // opposite of shapefile format - for (int i = ipartend - 1; i >= ipartstart; i--) { - x = position.read(2 * i); - y = position.read(2 * i + 1); - - wkbBuffer.putDouble(offset, x); - offset += 8; - wkbBuffer.putDouble(offset, y); - offset += 8; - - if (bExportZs) { - double z; - if (zs != null) - z = zs.read(i); - else - z = VertexDescription - .getDefaultValue(VertexDescription.Semantics.Z); - - wkbBuffer.putDouble(offset, z); - offset += 8; - } - - if (bExportMs) { - double m; - if (ms != null) - m = ms.read(i); - else - m = VertexDescription - .getDefaultValue(VertexDescription.Semantics.M); - - wkbBuffer.putDouble(offset, m); - offset += 8; - } - } - } - } - - return offset; - } - - private static int exportPolylineToWKB(int exportFlags, Polyline _polyline, - ByteBuffer wkbBuffer) { - MultiPathImpl polyline = (MultiPathImpl) _polyline._getImpl(); - - if ((exportFlags & WkbExportFlags.wkbExportFailIfNotSimple) != 0) { - int simple = polyline.getIsSimple(0.0); - - if (simple < 1) - throw new GeometryException("corrupted geometry"); - } - - boolean bExportZs = polyline - .hasAttribute(VertexDescription.Semantics.Z) - && (exportFlags & WkbExportFlags.wkbExportStripZs) == 0; - boolean bExportMs = polyline - .hasAttribute(VertexDescription.Semantics.M) - && (exportFlags & WkbExportFlags.wkbExportStripMs) == 0; - - int partCount = polyline.getPathCount(); - if ((exportFlags & WkbExportFlags.wkbExportLineString) != 0 - && partCount > 1) - throw new IllegalArgumentException(); - - int point_count = polyline.getPointCount(); - - for (int ipart = 0; ipart < partCount; ipart++) - if (polyline.isClosedPath(ipart)) - point_count++; - - // In the WKB_export_defaults case, polylines gets exported as a - // WKB_multi_line_string - - // get size for buffer - int size = 0; - if ((exportFlags & WkbExportFlags.wkbExportLineString) == 0 - || partCount == 0) - size += 1 /* byte order */ + 4 /* wkbType */ + 4 /* numLineStrings */; - - size += partCount - * (1 /* byte order */ + 4 /* wkbType */ + 4/* num_points */) - + point_count * (2 * 8 /* xy coordinates */); - - if (bExportZs) - size += (point_count * 8 /* zs */); - if (bExportMs) - size += (point_count * 8 /* ms */); - - if (size >= NumberUtils.intMax()) - throw new GeometryException("invalid call"); - - if (wkbBuffer == null) - return (int) size; - else if (wkbBuffer.capacity() < (int) size) - throw new GeometryException("buffer is too small"); - - int offset = 0; - - byte byteOrder = (byte) (wkbBuffer.order() == ByteOrder.LITTLE_ENDIAN ? WkbByteOrder.wkbNDR - : WkbByteOrder.wkbXDR); - - // Determine the wkb type - int type; - if (!bExportZs && !bExportMs) { - type = WkbGeometryType.wkbLineString; - - if ((exportFlags & WkbExportFlags.wkbExportLineString) == 0) { - wkbBuffer.put(offset, byteOrder); - offset += 1; - wkbBuffer.putInt(offset, WkbGeometryType.wkbMultiLineString); - offset += 4; - wkbBuffer.putInt(offset, (int) partCount); - offset += 4; - } else if (partCount == 0) { - wkbBuffer.put(offset, byteOrder); - offset += 1; - wkbBuffer.putInt(offset, WkbGeometryType.wkbLineString); - offset += 4; - wkbBuffer.putInt(offset, 0); - offset += 4; - } - } else if (bExportZs && !bExportMs) { - type = WkbGeometryType.wkbLineStringZ; - - if ((exportFlags & WkbExportFlags.wkbExportLineString) == 0) { - wkbBuffer.put(offset, byteOrder); - offset += 1; - wkbBuffer.putInt(offset, WkbGeometryType.wkbMultiLineStringZ); - offset += 4; - wkbBuffer.putInt(offset, (int) partCount); - offset += 4; - } else if (partCount == 0) { - wkbBuffer.put(offset, byteOrder); - offset += 1; - wkbBuffer.putInt(offset, WkbGeometryType.wkbLineStringZ); - offset += 4; - wkbBuffer.putInt(offset, 0); - offset += 4; - } - } else if (bExportMs && !bExportZs) { - type = WkbGeometryType.wkbLineStringM; - - if ((exportFlags & WkbExportFlags.wkbExportLineString) == 0) { - wkbBuffer.put(offset, byteOrder); - offset += 1; - wkbBuffer.putInt(offset, WkbGeometryType.wkbMultiLineStringM); - offset += 4; - wkbBuffer.putInt(offset, (int) partCount); - offset += 4; - } else if (partCount == 0) { - wkbBuffer.put(offset, byteOrder); - offset += 1; - wkbBuffer.putInt(offset, WkbGeometryType.wkbLineStringM); - offset += 4; - wkbBuffer.putInt(offset, 0); - offset += 4; - } - } else { - type = WkbGeometryType.wkbLineStringZM; - - if ((exportFlags & WkbExportFlags.wkbExportLineString) == 0) { - wkbBuffer.put(offset, byteOrder); - offset += 1; - wkbBuffer.putInt(offset, WkbGeometryType.wkbMultiLineStringZM); - offset += 4; - wkbBuffer.putInt(offset, partCount); - offset += 4; - } else if (partCount == 0) { - wkbBuffer.put(offset, byteOrder); - offset += 1; - wkbBuffer.putInt(offset, WkbGeometryType.wkbLineStringZM); - offset += 4; - wkbBuffer.putInt(offset, 0); - offset += 4; - } - } - - if (partCount == 0) - return offset; - - AttributeStreamOfDbl position = (AttributeStreamOfDbl) (polyline - .getAttributeStreamRef(VertexDescription.Semantics.POSITION)); - - AttributeStreamOfDbl zs = null; - if (bExportZs) { - if (polyline - ._attributeStreamIsAllocated(VertexDescription.Semantics.Z)) - zs = (AttributeStreamOfDbl) (polyline - .getAttributeStreamRef(VertexDescription.Semantics.Z)); - } - - AttributeStreamOfDbl ms = null; - if (bExportMs) { - if (polyline - ._attributeStreamIsAllocated(VertexDescription.Semantics.M)) - ms = (AttributeStreamOfDbl) (polyline - .getAttributeStreamRef(VertexDescription.Semantics.M)); - } - - int ipartend = 0; - for (int ipart = 0; ipart < (int) partCount; ipart++) { - // write byte order - wkbBuffer.put(offset, byteOrder); - offset += 1; - - // write type - wkbBuffer.putInt(offset, type); - offset += 4; - - // get start and end indices - int ipartstart = ipartend; - ipartend = (int) polyline.getPathEnd(ipart); - - // write num_points - int num_points = ipartend - ipartstart; - if (polyline.isClosedPath(ipart)) - num_points++; - - wkbBuffer.putInt(offset, num_points); - offset += 4; - - // write points - for (int i = ipartstart; i < ipartend; i++) { - double x = position.read(2 * i); - double y = position.read(2 * i + 1); - - wkbBuffer.putDouble(offset, x); - offset += 8; - wkbBuffer.putDouble(offset, y); - offset += 8; - - if (bExportZs) { - double z; - if (zs != null) - z = zs.read(i); - else - z = VertexDescription - .getDefaultValue(VertexDescription.Semantics.Z); - - wkbBuffer.putDouble(offset, z); - offset += 8; - } - - if (bExportMs) { - double m; - if (ms != null) - m = ms.read(i); - else - m = VertexDescription - .getDefaultValue(VertexDescription.Semantics.M); - - wkbBuffer.putDouble(offset, m); - offset += 8; - } - } - - // duplicate the start point if the Polyline is closed - if (polyline.isClosedPath(ipart)) { - double x = position.read(2 * ipartstart); - double y = position.read(2 * ipartstart + 1); - - wkbBuffer.putDouble(offset, x); - offset += 8; - wkbBuffer.putDouble(offset, y); - offset += 8; - - if (bExportZs) { - double z; - if (zs != null) - z = zs.read(ipartstart); - else - z = VertexDescription - .getDefaultValue(VertexDescription.Semantics.Z); - - wkbBuffer.putDouble(offset, z); - offset += 8; - } - - if (bExportMs) { - double m; - if (ms != null) - m = ms.read(ipartstart); - else - m = VertexDescription - .getDefaultValue(VertexDescription.Semantics.M); - - wkbBuffer.putDouble(offset, m); - offset += 8; - } - } - } - - return offset; - } - - private static int exportMultiPointToWKB(int exportFlags, - MultiPoint _multipoint, ByteBuffer wkbBuffer) { - MultiPointImpl multipoint = (MultiPointImpl) _multipoint._getImpl(); - - boolean bExportZs = multipoint - .hasAttribute(VertexDescription.Semantics.Z) - && (exportFlags & WkbExportFlags.wkbExportStripZs) == 0; - boolean bExportMs = multipoint - .hasAttribute(VertexDescription.Semantics.M) - && (exportFlags & WkbExportFlags.wkbExportStripMs) == 0; - - int point_count = multipoint.getPointCount(); - if ((exportFlags & WkbExportFlags.wkbExportPoint) != 0 - && point_count > 1) - throw new IllegalArgumentException(); - - // get size for buffer - int size; - if ((exportFlags & WkbExportFlags.wkbExportPoint) == 0) { - size = 1 /* byte order */ + 4 /* wkbType */ + 4 /* num_points */ - + point_count - * (1 /* byte order */ + 4 /* wkbType */ + 2 * 8 /* - * xy - * coordinates - */); - - if (bExportZs) - size += (point_count * 8 /* zs */); - if (bExportMs) - size += (point_count * 8 /* ms */); - } else { - size = 1 /* byte order */ + 4 /* wkbType */ + 2 * 8 /* xy coordinates */; - - if (bExportZs) - size += 8 /* z */; - if (bExportMs) - size += 8 /* m */; - } - - if (size >= NumberUtils.intMax()) - throw new GeometryException("invalid call"); - - if (wkbBuffer == null) - return (int) size; - else if (wkbBuffer.capacity() < (int) size) - throw new GeometryException("buffer is too small"); - - int offset = 0; - - byte byteOrder = (byte) (wkbBuffer.order() == ByteOrder.LITTLE_ENDIAN ? WkbByteOrder.wkbNDR - : WkbByteOrder.wkbXDR); - - // Determine the wkb type - int type; - if (!bExportZs && !bExportMs) { - type = WkbGeometryType.wkbPoint; - - if ((exportFlags & WkbExportFlags.wkbExportPoint) == 0) { - wkbBuffer.put(offset, byteOrder); - offset += 1; - wkbBuffer.putInt(offset, WkbGeometryType.wkbMultiPoint); - offset += 4; - wkbBuffer.putInt(offset, (int) point_count); - offset += 4; - } else if (point_count == 0) { - wkbBuffer.put(offset, byteOrder); - offset += 1; - wkbBuffer.putInt(offset, type); - offset += 4; - wkbBuffer.putDouble(offset, NumberUtils.TheNaN); - offset += 8; - wkbBuffer.putDouble(offset, NumberUtils.TheNaN); - offset += 8; - } - } else if (bExportZs && !bExportMs) { - type = WkbGeometryType.wkbPointZ; - - if ((exportFlags & WkbExportFlags.wkbExportPoint) == 0) { - wkbBuffer.put(offset, byteOrder); - offset += 1; - wkbBuffer.putInt(offset, WkbGeometryType.wkbMultiPointZ); - offset += 4; - wkbBuffer.putInt(offset, (int) point_count); - offset += 4; - } else if (point_count == 0) { - wkbBuffer.put(offset, byteOrder); - offset += 1; - wkbBuffer.putInt(offset, type); - offset += 4; - wkbBuffer.putDouble(offset, NumberUtils.TheNaN); - offset += 8; - wkbBuffer.putDouble(offset, NumberUtils.TheNaN); - offset += 8; - wkbBuffer.putDouble(offset, NumberUtils.TheNaN); - offset += 8; - } - } else if (bExportMs && !bExportZs) { - type = WkbGeometryType.wkbPointM; - - if ((exportFlags & WkbExportFlags.wkbExportPoint) == 0) { - wkbBuffer.put(offset, byteOrder); - offset += 1; - wkbBuffer.putInt(offset, WkbGeometryType.wkbMultiPointM); - offset += 4; - wkbBuffer.putInt(offset, (int) point_count); - offset += 4; - } else if (point_count == 0) { - wkbBuffer.put(offset, byteOrder); - offset += 1; - wkbBuffer.putInt(offset, type); - offset += 4; - wkbBuffer.putDouble(offset, NumberUtils.TheNaN); - offset += 8; - wkbBuffer.putDouble(offset, NumberUtils.TheNaN); - offset += 8; - wkbBuffer.putDouble(offset, NumberUtils.TheNaN); - offset += 8; - } - } else { - type = WkbGeometryType.wkbPointZM; - - if ((exportFlags & WkbExportFlags.wkbExportPoint) == 0) { - wkbBuffer.put(offset, byteOrder); - offset += 1; - wkbBuffer.putInt(offset, WkbGeometryType.wkbMultiPolygonZM); - offset += 4; - wkbBuffer.putInt(offset, point_count); - offset += 4; - } else if (point_count == 0) { - wkbBuffer.put(offset, byteOrder); - offset += 1; - wkbBuffer.putInt(offset, type); - offset += 4; - wkbBuffer.putDouble(offset, NumberUtils.TheNaN); - offset += 8; - wkbBuffer.putDouble(offset, NumberUtils.TheNaN); - offset += 8; - wkbBuffer.putDouble(offset, NumberUtils.TheNaN); - offset += 8; - wkbBuffer.putDouble(offset, NumberUtils.TheNaN); - offset += 8; - } - } - - if (point_count == 0) - return offset; - - AttributeStreamOfDbl position = (AttributeStreamOfDbl) (multipoint - .getAttributeStreamRef(VertexDescription.Semantics.POSITION)); - - AttributeStreamOfDbl zs = null; - if (bExportZs) { - if (multipoint - ._attributeStreamIsAllocated(VertexDescription.Semantics.Z)) - zs = (AttributeStreamOfDbl) (multipoint - .getAttributeStreamRef(VertexDescription.Semantics.Z)); - } - - AttributeStreamOfDbl ms = null; - if (bExportMs) { - if (multipoint - ._attributeStreamIsAllocated(VertexDescription.Semantics.M)) - ms = (AttributeStreamOfDbl) (multipoint - .getAttributeStreamRef(VertexDescription.Semantics.M)); - } - - for (int i = 0; i < (int) point_count; i++) { - // write byte order - wkbBuffer.put(offset, byteOrder); - offset += 1; - - // write type - wkbBuffer.putInt(offset, type); - offset += 4; - - // write xy coordinates - double x = position.read(2 * i); - double y = position.read(2 * i + 1); - - wkbBuffer.putDouble(offset, x); - offset += 8; - wkbBuffer.putDouble(offset, y); - offset += 8; - - // write Z - if (bExportZs) { - double z; - if (zs != null) - z = zs.read(i); - else - z = VertexDescription - .getDefaultValue(VertexDescription.Semantics.Z); - - wkbBuffer.putDouble(offset, z); - offset += 8; - } - - // write M - if (bExportMs) { - double m; - if (ms != null) - m = ms.read(i); - else - m = VertexDescription - .getDefaultValue(VertexDescription.Semantics.M); - - wkbBuffer.putDouble(offset, m); - offset += 8; - } - } - - return offset; - } - - private static int exportPointToWKB(int exportFlags, Point point, - ByteBuffer wkbBuffer) { - boolean bExportZs = point.hasAttribute(VertexDescription.Semantics.Z) - && (exportFlags & WkbExportFlags.wkbExportStripZs) == 0; - boolean bExportMs = point.hasAttribute(VertexDescription.Semantics.M) - && (exportFlags & WkbExportFlags.wkbExportStripMs) == 0; - - boolean bEmpty = point.isEmpty(); - int point_count = bEmpty ? 0 : 1; - - // get size for buffer - int size; - if ((exportFlags & WkbExportFlags.wkbExportMultiPoint) != 0) { - size = 1 /* byte order */ + 4 /* wkbType */ + 4 /* num_points */ - + point_count - * (1 /* byte order */ + 4 /* wkbType */ + 2 * 8 /* - * xy - * coordinates - */); - - if (bExportZs) - size += (point_count * 8 /* zs */); - if (bExportMs) - size += (point_count * 8 /* ms */); - } else { - size = 1 /* byte order */ + 4 /* wkbType */ + 2 * 8 /* xy coordinates */; - - if (bExportZs) - size += 8 /* z */; - if (bExportMs) - size += 8 /* m */; - } - - if (size >= NumberUtils.intMax()) - throw new GeometryException("invalid call"); - - if (wkbBuffer == null) - return size; - else if (wkbBuffer.capacity() < size) - throw new GeometryException("buffer is too small"); - - int offset = 0; - - byte byteOrder = (byte) (wkbBuffer.order() == ByteOrder.LITTLE_ENDIAN ? WkbByteOrder.wkbNDR - : WkbByteOrder.wkbXDR); - - // Determine the wkb type - int type; - if (!bExportZs && !bExportMs) { - type = WkbGeometryType.wkbPoint; - - if ((exportFlags & WkbExportFlags.wkbExportMultiPoint) != 0) { - wkbBuffer.put(offset, byteOrder); - offset += 1; - wkbBuffer.putInt(offset, WkbGeometryType.wkbMultiPoint); - offset += 4; - wkbBuffer.putInt(offset, (int) point_count); - offset += 4; - } else if (point_count == 0) { - wkbBuffer.put(offset, byteOrder); - offset += 1; - wkbBuffer.putInt(offset, type); - offset += 4; - wkbBuffer.putDouble(offset, NumberUtils.TheNaN); - offset += 8; - wkbBuffer.putDouble(offset, NumberUtils.TheNaN); - offset += 8; - } - } else if (bExportZs && !bExportMs) { - type = WkbGeometryType.wkbPointZ; - - if ((exportFlags & WkbExportFlags.wkbExportMultiPoint) != 0) { - wkbBuffer.put(offset, byteOrder); - offset += 1; - wkbBuffer.putInt(offset, WkbGeometryType.wkbMultiPointZ); - offset += 4; - wkbBuffer.putInt(offset, (int) point_count); - offset += 4; - } else if (point_count == 0) { - wkbBuffer.put(offset, byteOrder); - offset += 1; - wkbBuffer.putInt(offset, type); - offset += 4; - wkbBuffer.putDouble(offset, NumberUtils.TheNaN); - offset += 8; - wkbBuffer.putDouble(offset, NumberUtils.TheNaN); - offset += 8; - wkbBuffer.putDouble(offset, NumberUtils.TheNaN); - offset += 8; - } - } else if (bExportMs && !bExportZs) { - type = WkbGeometryType.wkbPointM; - - if ((exportFlags & WkbExportFlags.wkbExportMultiPoint) != 0) { - wkbBuffer.put(offset, byteOrder); - offset += 1; - wkbBuffer.putInt(offset, WkbGeometryType.wkbMultiPointM); - offset += 4; - wkbBuffer.putInt(offset, (int) point_count); - offset += 4; - } else if (point_count == 0) { - wkbBuffer.put(offset, byteOrder); - offset += 1; - wkbBuffer.putInt(offset, type); - offset += 4; - wkbBuffer.putDouble(offset, NumberUtils.TheNaN); - offset += 8; - wkbBuffer.putDouble(offset, NumberUtils.TheNaN); - offset += 8; - wkbBuffer.putDouble(offset, NumberUtils.TheNaN); - offset += 8; - } - } else { - type = WkbGeometryType.wkbPointZM; - - if ((exportFlags & WkbExportFlags.wkbExportMultiPoint) != 0) { - wkbBuffer.put(offset, byteOrder); - offset += 1; - wkbBuffer.putInt(offset, WkbGeometryType.wkbMultiPointZM); - offset += 4; - wkbBuffer.putInt(offset, (int) point_count); - offset += 4; - } else if (point_count == 0) { - wkbBuffer.put(offset, byteOrder); - offset += 1; - wkbBuffer.putInt(offset, type); - offset += 4; - wkbBuffer.putDouble(offset, NumberUtils.TheNaN); - offset += 8; - wkbBuffer.putDouble(offset, NumberUtils.TheNaN); - offset += 8; - wkbBuffer.putDouble(offset, NumberUtils.TheNaN); - offset += 8; - wkbBuffer.putDouble(offset, NumberUtils.TheNaN); - offset += 8; - } - } - - if (point_count == 0) - return offset; - - // write byte order - wkbBuffer.put(offset, byteOrder); - offset += 1; - - // write type - wkbBuffer.putInt(offset, type); - offset += 4; - - // write xy coordinate - double x = point.getX(); - double y = point.getY(); - wkbBuffer.putDouble(offset, x); - offset += 8; - wkbBuffer.putDouble(offset, y); - offset += 8; - - // write Z - if (bExportZs) { - double z = point.getZ(); - wkbBuffer.putDouble(offset, z); - offset += 8; - } - - // write M - if (bExportMs) { - double m = point.getM(); - wkbBuffer.putDouble(offset, m); - offset += 8; - } - - return offset; - } - - private static int exportEnvelopeToWKB(int exportFlags, Envelope envelope, - ByteBuffer wkbBuffer) { - boolean bExportZs = envelope - .hasAttribute(VertexDescription.Semantics.Z) - && (exportFlags & WkbExportFlags.wkbExportStripZs) == 0; - boolean bExportMs = envelope - .hasAttribute(VertexDescription.Semantics.M) - && (exportFlags & WkbExportFlags.wkbExportStripMs) == 0; - boolean bEmpty = envelope.isEmpty(); - - int partCount = bEmpty ? 0 : 1; - int point_count = bEmpty ? 0 : 5; - - // Envelope by default is exported as a WKB_polygon - - // get size for buffer - int size = 0; - if ((exportFlags & WkbExportFlags.wkbExportMultiPolygon) != 0 - || partCount == 0) - size += 1 /* byte order */ + 4 /* wkbType */ + 4 /* numPolygons */; - - size += partCount - * (1 /* byte order */ + 4 /* wkbType */ + 4/* numRings */) - + partCount * (4 /* num_points */) + point_count * (2 * 8 /* - * xy - * coordinates - */); - - if (bExportZs) - size += (point_count * 8 /* zs */); - if (bExportMs) - size += (point_count * 8 /* ms */); - - if (size >= NumberUtils.intMax()) - throw new GeometryException("invalid call"); - - if (wkbBuffer == null) - return size; - else if (wkbBuffer.capacity() < size) - throw new GeometryException("buffer is too small"); - - int offset = 0; - - byte byteOrder = (byte) (wkbBuffer.order() == ByteOrder.LITTLE_ENDIAN ? WkbByteOrder.wkbNDR - : WkbByteOrder.wkbXDR); - - // Determine the wkb type - int type; - if (!bExportZs && !bExportMs) { - type = WkbGeometryType.wkbPolygon; - - if ((exportFlags & WkbExportFlags.wkbExportMultiPolygon) != 0) { - wkbBuffer.put(offset, byteOrder); - offset += 1; - wkbBuffer.putInt(offset, WkbGeometryType.wkbMultiPolygon); - offset += 4; - wkbBuffer.putInt(offset, (int) partCount); - offset += 4; - } else if (partCount == 0) { - wkbBuffer.put(offset, byteOrder); - offset += 1; - wkbBuffer.putInt(offset, WkbGeometryType.wkbPolygon); - offset += 4; - wkbBuffer.putInt(offset, 0); - offset += 4; - } - } else if (bExportZs && !bExportMs) { - type = WkbGeometryType.wkbPolygonZ; - - if ((exportFlags & WkbExportFlags.wkbExportPolygon) != 0) { - wkbBuffer.put(offset, byteOrder); - offset += 1; - wkbBuffer.putInt(offset, WkbGeometryType.wkbMultiPolygonZ); - offset += 4; - wkbBuffer.putInt(offset, partCount); - offset += 4; - } else if (partCount == 0) { - wkbBuffer.put(offset, byteOrder); - offset += 1; - wkbBuffer.putInt(offset, WkbGeometryType.wkbPolygonZ); - offset += 4; - wkbBuffer.putInt(offset, 0); - offset += 4; - } - } else if (bExportMs && !bExportZs) { - type = WkbGeometryType.wkbPolygonM; - - if ((exportFlags & WkbExportFlags.wkbExportMultiPolygon) != 0) { - wkbBuffer.put(offset, byteOrder); - offset += 1; - wkbBuffer.putInt(offset, WkbGeometryType.wkbMultiPolygonM); - offset += 4; - wkbBuffer.putInt(offset, partCount); - offset += 4; - } else if (partCount == 0) { - wkbBuffer.put(offset, byteOrder); - offset += 1; - wkbBuffer.putInt(offset, WkbGeometryType.wkbPolygonM); - offset += 4; - wkbBuffer.putInt(offset, 0); - offset += 4; - } - } else { - type = WkbGeometryType.wkbPolygonZM; - - if ((exportFlags & WkbExportFlags.wkbExportMultiPolygon) != 0) { - wkbBuffer.put(offset, byteOrder); - offset += 1; - wkbBuffer.putInt(offset, WkbGeometryType.wkbMultiPolygonZM); - offset += 4; - wkbBuffer.putInt(offset, partCount); - offset += 4; - } else if (partCount == 0) { - wkbBuffer.put(offset, byteOrder); - offset += 1; - wkbBuffer.putInt(offset, WkbGeometryType.wkbPolygonZM); - offset += 4; - wkbBuffer.putInt(offset, 0); - offset += 4; - } - } - - if (partCount == 0) - return offset; - - // write byte order - wkbBuffer.put(offset, byteOrder); - offset += 1; - - // write type - wkbBuffer.putInt(offset, type); - offset += 4; - - // write numRings - wkbBuffer.putInt(offset, 1); - offset += 4; - - // write num_points - wkbBuffer.putInt(offset, 5); - offset += 4; - - Envelope2D env = new Envelope2D(); - envelope.queryEnvelope2D(env); - - Envelope1D z_interval = null; - if (bExportZs) - z_interval = envelope.queryInterval(VertexDescription.Semantics.Z, - 0); - - Envelope1D mInterval = null; - if (bExportMs) - mInterval = envelope - .queryInterval(VertexDescription.Semantics.M, 0); - - wkbBuffer.putDouble(offset, env.xmin); - offset += 8; - wkbBuffer.putDouble(offset, env.ymin); - offset += 8; - - if (bExportZs) { - wkbBuffer.putDouble(offset, z_interval.vmin); - offset += 8; - } - - if (bExportMs) { - wkbBuffer.putDouble(offset, mInterval.vmin); - offset += 8; - } - - wkbBuffer.putDouble(offset, env.xmax); - offset += 8; - wkbBuffer.putDouble(offset, env.ymin); - offset += 8; - - if (bExportZs) { - wkbBuffer.putDouble(offset, z_interval.vmax); - offset += 8; - } - - if (bExportMs) { - wkbBuffer.putDouble(offset, mInterval.vmax); - offset += 8; - } - - wkbBuffer.putDouble(offset, env.xmax); - offset += 8; - wkbBuffer.putDouble(offset, env.ymax); - offset += 8; - - if (bExportZs) { - wkbBuffer.putDouble(offset, z_interval.vmin); - offset += 8; - } - - if (bExportMs) { - wkbBuffer.putDouble(offset, mInterval.vmin); - offset += 8; - } - - wkbBuffer.putDouble(offset, env.xmin); - offset += 8; - wkbBuffer.putDouble(offset, env.ymax); - offset += 8; - - if (bExportZs) { - wkbBuffer.putDouble(offset, z_interval.vmax); - offset += 8; - } - - if (bExportMs) { - wkbBuffer.putDouble(offset, mInterval.vmax); - offset += 8; - } - - wkbBuffer.putDouble(offset, env.xmin); - offset += 8; - wkbBuffer.putDouble(offset, env.ymin); - offset += 8; - - if (bExportZs) { - wkbBuffer.putDouble(offset, z_interval.vmin); - offset += 8; - } - - if (bExportMs) { - wkbBuffer.putDouble(offset, mInterval.vmin); - offset += 8; - } - - return offset; - } + @Override + public ByteBufferCursor execute(int exportFlags, GeometryCursor geometryCursor) { + return new OperatorExportToWkbCursor(exportFlags, geometryCursor); + } + + @Override + public ByteBuffer execute(int exportFlags, Geometry geometry, ProgressTracker progressTracker) { + int size = exportToWKB(exportFlags, geometry, null); + ByteBuffer wkbBuffer = ByteBuffer.allocate(size).order(ByteOrder.nativeOrder()); + exportToWKB(exportFlags, geometry, wkbBuffer); + return wkbBuffer; + } + + @Override + public int execute(int exportFlags, Geometry geometry, + ByteBuffer wkbBuffer, ProgressTracker progressTracker) { + return exportToWKB(exportFlags, geometry, wkbBuffer); + } + + protected static int exportToWKB(int exportFlags, Geometry geometry, + ByteBuffer wkbBuffer) { + if (geometry == null) + return 0; + + int type = geometry.getType().value(); + switch (type) { + case Geometry.GeometryType.Polygon: + if ((exportFlags & WkbExportFlags.wkbExportLineString) != 0 + || (exportFlags & WkbExportFlags.wkbExportMultiLineString) != 0 + || (exportFlags & WkbExportFlags.wkbExportPoint) != 0 + || (exportFlags & WkbExportFlags.wkbExportMultiPoint) != 0) + throw new GeometryException("invalid argument"); + + return exportPolygonToWKB(exportFlags, (Polygon) geometry, + wkbBuffer); + case Geometry.GeometryType.Polyline: + if ((exportFlags & WkbExportFlags.wkbExportPolygon) != 0 + || (exportFlags & WkbExportFlags.wkbExportMultiPolygon) != 0 + || (exportFlags & WkbExportFlags.wkbExportPoint) != 0 + || (exportFlags & WkbExportFlags.wkbExportMultiPoint) != 0) + throw new GeometryException("invalid argument"); + return exportPolylineToWKB(exportFlags, (Polyline) geometry, + wkbBuffer); + + case Geometry.GeometryType.MultiPoint: + if ((exportFlags & WkbExportFlags.wkbExportLineString) != 0 + || (exportFlags & WkbExportFlags.wkbExportMultiLineString) != 0 + || (exportFlags & WkbExportFlags.wkbExportPolygon) != 0 + || (exportFlags & WkbExportFlags.wkbExportMultiPolygon) != 0) + throw new GeometryException("invalid argument"); + return exportMultiPointToWKB(exportFlags, (MultiPoint) geometry, + wkbBuffer); + + case Geometry.GeometryType.Point: + if ((exportFlags & WkbExportFlags.wkbExportLineString) != 0 + || (exportFlags & WkbExportFlags.wkbExportMultiLineString) != 0 + || (exportFlags & WkbExportFlags.wkbExportPolygon) != 0 + || (exportFlags & WkbExportFlags.wkbExportMultiPolygon) != 0) + throw new GeometryException("invalid argument"); + return exportPointToWKB(exportFlags, (Point) geometry, wkbBuffer); + + case Geometry.GeometryType.Envelope: + if ((exportFlags & WkbExportFlags.wkbExportLineString) != 0 + || (exportFlags & WkbExportFlags.wkbExportMultiLineString) != 0 + || (exportFlags & WkbExportFlags.wkbExportPoint) != 0 + || (exportFlags & WkbExportFlags.wkbExportMultiPoint) != 0) + throw new GeometryException("invalid argument"); + return exportEnvelopeToWKB(exportFlags, (Envelope) geometry, + wkbBuffer); + + default: { + throw GeometryException.GeometryInternalError(); + // return -1; + } + } + } + + private static int exportPolygonToWKB(int exportFlags, Polygon _polygon, + ByteBuffer wkbBuffer) { + MultiPathImpl polygon = (MultiPathImpl) _polygon._getImpl(); + + if ((exportFlags & (int) WkbExportFlags.wkbExportFailIfNotSimple) != 0) { + int simple = polygon.getIsSimple(0.0); + + if (simple != MultiVertexGeometryImpl.GeometryXSimple.Strong) + throw new GeometryException("non simple geometry"); + } + + boolean bExportZs = polygon.hasAttribute(VertexDescription.Semantics.Z) + && (exportFlags & (int) WkbExportFlags.wkbExportStripZs) == 0; + boolean bExportMs = polygon.hasAttribute(VertexDescription.Semantics.M) + && (exportFlags & (int) WkbExportFlags.wkbExportStripMs) == 0; + + int polygonCount = polygon.getOGCPolygonCount(); + if ((exportFlags & (int) WkbExportFlags.wkbExportPolygon) != 0 + && polygonCount > 1) + throw new IllegalArgumentException(); + + int partCount = polygon.getPathCount(); + int point_count = polygon.getPointCount(); + point_count += partCount; // add 1 point per part + + if (point_count > 0 && polygonCount == 0) + throw new GeometryException("corrupted geometry"); + + // In the WKB_export_defaults case, polygons gets exported as a + // WKB_multi_polygon. + + // get size for buffer + int size = 0; + if ((exportFlags & (int) WkbExportFlags.wkbExportPolygon) == 0 + || polygonCount == 0) + size += 1 /* byte order */ + 4 /* wkbType */ + 4 /* numPolygons */; + + size += polygonCount + * (1 /* byte order */ + 4 /* wkbType */ + 4/* numRings */) + + partCount * (4 /* num_points */) + point_count * (2 * 8 /* + * xy + * coordinates + */); + + if (bExportZs) + size += (point_count * 8 /* zs */); + if (bExportMs) + size += (point_count * 8 /* ms */); + + if (size >= NumberUtils.intMax()) + throw new GeometryException("invalid call"); + + if (wkbBuffer == null) + return (int) size; + else if (wkbBuffer.capacity() < size) + throw new GeometryException("buffer is too small"); + + int offset = 0; + + byte byteOrder = (byte) (wkbBuffer.order() == ByteOrder.LITTLE_ENDIAN ? WkbByteOrder.wkbNDR + : WkbByteOrder.wkbXDR); + + // Determine the wkb type + int type; + if (!bExportZs && !bExportMs) { + type = WkbGeometryType.wkbPolygon; + + if ((exportFlags & WktExportFlags.wktExportPolygon) == 0) { + wkbBuffer.put(offset, byteOrder); + offset += 1; + wkbBuffer.putInt(offset, WkbGeometryType.wkbMultiPolygon); + offset += 4; + wkbBuffer.putInt(offset, polygonCount); + offset += 4; + } else if (polygonCount == 0) { + wkbBuffer.put(offset, byteOrder); + offset += 1; + wkbBuffer.putInt(offset, WkbGeometryType.wkbPolygon); + offset += 4; + wkbBuffer.putInt(offset, 0); + offset += 4; + } + } else if (bExportZs && !bExportMs) { + type = WkbGeometryType.wkbPolygonZ; + + if ((exportFlags & WkbExportFlags.wkbExportPolygon) == 0) { + wkbBuffer.put(offset, byteOrder); + offset += 1; + wkbBuffer.putInt(offset, WkbGeometryType.wkbMultiPolygonZ); + offset += 4; + wkbBuffer.putInt(offset, polygonCount); + offset += 4; + } else if (polygonCount == 0) { + wkbBuffer.put(offset, byteOrder); + offset += 1; + wkbBuffer.putInt(offset, WkbGeometryType.wkbPolygonZ); + offset += 4; + wkbBuffer.putInt(offset, 0); + offset += 4; + } + } else if (bExportMs && !bExportZs) { + type = WkbGeometryType.wkbPolygonM; + + if ((exportFlags & WkbExportFlags.wkbExportPolygon) == 0) { + wkbBuffer.put(offset, byteOrder); + offset += 1; + wkbBuffer.putInt(offset, WkbGeometryType.wkbMultiPolygonM); + offset += 4; + wkbBuffer.putInt(offset, (int) polygonCount); + offset += 4; + } else if (polygonCount == 0) { + wkbBuffer.put(offset, byteOrder); + offset += 1; + wkbBuffer.putInt(offset, WkbGeometryType.wkbPolygonM); + offset += 4; + wkbBuffer.putInt(offset, 0); + offset += 4; + } + } else { + type = WkbGeometryType.wkbPolygonZM; + + if ((exportFlags & WkbExportFlags.wkbExportPolygon) == 0) { + wkbBuffer.put(offset, byteOrder); + offset += 1; + wkbBuffer.putInt(offset, WkbGeometryType.wkbMultiPolygonZM); + offset += 4; + wkbBuffer.putInt(offset, polygonCount); + offset += 4; + } else if (polygonCount == 0) { + wkbBuffer.put(offset, byteOrder); + offset += 1; + wkbBuffer.putInt(offset, WkbGeometryType.wkbPolygonZM); + offset += 4; + wkbBuffer.putInt(offset, 0); + offset += 4; + } + } + + if (polygonCount == 0) + return offset; + + AttributeStreamOfDbl position = (AttributeStreamOfDbl) (polygon + .getAttributeStreamRef(VertexDescription.Semantics.POSITION)); + + AttributeStreamOfDbl zs = null; + if (bExportZs) { + if (polygon + ._attributeStreamIsAllocated(VertexDescription.Semantics.Z)) + zs = (AttributeStreamOfDbl) (polygon + .getAttributeStreamRef(VertexDescription.Semantics.Z)); + } + + AttributeStreamOfDbl ms = null; + if (bExportMs) { + if (polygon + ._attributeStreamIsAllocated(VertexDescription.Semantics.M)) + ms = (AttributeStreamOfDbl) (polygon + .getAttributeStreamRef(VertexDescription.Semantics.M)); + } + + int ipartend = 0; + int ipolygonend = 0; + + for (int ipolygon = 0; ipolygon < (int) polygonCount; ipolygon++) { + // write byte order + wkbBuffer.put(offset, byteOrder); + offset += 1; + + // write type + wkbBuffer.putInt(offset, type); + offset += 4; + + // get partcount for the ith polygon + AttributeStreamOfInt8 pathFlags = polygon.getPathFlagsStreamRef(); + + int ipolygonstart = ipolygonend; + ipolygonend++; + + while (ipolygonend < partCount + && (pathFlags.read(ipolygonend) & PathFlags.enumOGCStartPolygon) == 0) + ipolygonend++; + + // write numRings + wkbBuffer.putInt(offset, ipolygonend - ipolygonstart); + offset += 4; + + for (int ipart = ipolygonstart; ipart < ipolygonend; ipart++) { + // get num_points + int ipartstart = ipartend; + ipartend = (int) polygon.getPathEnd(ipart); + + // write num_points + wkbBuffer.putInt(offset, ipartend - ipartstart + 1); + offset += 4; + + // duplicate the start point + double x = position.read(2 * ipartstart); + double y = position.read(2 * ipartstart + 1); + + wkbBuffer.putDouble(offset, x); + offset += 8; + wkbBuffer.putDouble(offset, y); + offset += 8; + + if (bExportZs) { + double z; + if (zs != null) + z = zs.read(ipartstart); + else + z = VertexDescription + .getDefaultValue(VertexDescription.Semantics.Z); + + wkbBuffer.putDouble(offset, z); + offset += 8; + } + + if (bExportMs) { + double m; + if (ms != null) + m = ms.read(ipartstart); + else + m = VertexDescription + .getDefaultValue(VertexDescription.Semantics.M); + + wkbBuffer.putDouble(offset, m); + offset += 8; + } + + // We must write to the buffer backwards - ogc polygon format is + // opposite of shapefile format + for (int i = ipartend - 1; i >= ipartstart; i--) { + x = position.read(2 * i); + y = position.read(2 * i + 1); + + wkbBuffer.putDouble(offset, x); + offset += 8; + wkbBuffer.putDouble(offset, y); + offset += 8; + + if (bExportZs) { + double z; + if (zs != null) + z = zs.read(i); + else + z = VertexDescription + .getDefaultValue(VertexDescription.Semantics.Z); + + wkbBuffer.putDouble(offset, z); + offset += 8; + } + + if (bExportMs) { + double m; + if (ms != null) + m = ms.read(i); + else + m = VertexDescription + .getDefaultValue(VertexDescription.Semantics.M); + + wkbBuffer.putDouble(offset, m); + offset += 8; + } + } + } + } + + return offset; + } + + private static int exportPolylineToWKB(int exportFlags, Polyline _polyline, + ByteBuffer wkbBuffer) { + MultiPathImpl polyline = (MultiPathImpl) _polyline._getImpl(); + + if ((exportFlags & WkbExportFlags.wkbExportFailIfNotSimple) != 0) { + int simple = polyline.getIsSimple(0.0); + + if (simple < 1) + throw new GeometryException("corrupted geometry"); + } + + boolean bExportZs = polyline + .hasAttribute(VertexDescription.Semantics.Z) + && (exportFlags & WkbExportFlags.wkbExportStripZs) == 0; + boolean bExportMs = polyline + .hasAttribute(VertexDescription.Semantics.M) + && (exportFlags & WkbExportFlags.wkbExportStripMs) == 0; + + int partCount = polyline.getPathCount(); + if ((exportFlags & WkbExportFlags.wkbExportLineString) != 0 + && partCount > 1) + throw new IllegalArgumentException(); + + int point_count = polyline.getPointCount(); + + for (int ipart = 0; ipart < partCount; ipart++) + if (polyline.isClosedPath(ipart)) + point_count++; + + // In the WKB_export_defaults case, polylines gets exported as a + // WKB_multi_line_string + + // get size for buffer + int size = 0; + if ((exportFlags & WkbExportFlags.wkbExportLineString) == 0 + || partCount == 0) + size += 1 /* byte order */ + 4 /* wkbType */ + 4 /* numLineStrings */; + + size += partCount + * (1 /* byte order */ + 4 /* wkbType */ + 4/* num_points */) + + point_count * (2 * 8 /* xy coordinates */); + + if (bExportZs) + size += (point_count * 8 /* zs */); + if (bExportMs) + size += (point_count * 8 /* ms */); + + if (size >= NumberUtils.intMax()) + throw new GeometryException("invalid call"); + + if (wkbBuffer == null) + return (int) size; + else if (wkbBuffer.capacity() < (int) size) + throw new GeometryException("buffer is too small"); + + int offset = 0; + + byte byteOrder = (byte) (wkbBuffer.order() == ByteOrder.LITTLE_ENDIAN ? WkbByteOrder.wkbNDR + : WkbByteOrder.wkbXDR); + + // Determine the wkb type + int type; + if (!bExportZs && !bExportMs) { + type = WkbGeometryType.wkbLineString; + + if ((exportFlags & WkbExportFlags.wkbExportLineString) == 0) { + wkbBuffer.put(offset, byteOrder); + offset += 1; + wkbBuffer.putInt(offset, WkbGeometryType.wkbMultiLineString); + offset += 4; + wkbBuffer.putInt(offset, (int) partCount); + offset += 4; + } else if (partCount == 0) { + wkbBuffer.put(offset, byteOrder); + offset += 1; + wkbBuffer.putInt(offset, WkbGeometryType.wkbLineString); + offset += 4; + wkbBuffer.putInt(offset, 0); + offset += 4; + } + } else if (bExportZs && !bExportMs) { + type = WkbGeometryType.wkbLineStringZ; + + if ((exportFlags & WkbExportFlags.wkbExportLineString) == 0) { + wkbBuffer.put(offset, byteOrder); + offset += 1; + wkbBuffer.putInt(offset, WkbGeometryType.wkbMultiLineStringZ); + offset += 4; + wkbBuffer.putInt(offset, (int) partCount); + offset += 4; + } else if (partCount == 0) { + wkbBuffer.put(offset, byteOrder); + offset += 1; + wkbBuffer.putInt(offset, WkbGeometryType.wkbLineStringZ); + offset += 4; + wkbBuffer.putInt(offset, 0); + offset += 4; + } + } else if (bExportMs && !bExportZs) { + type = WkbGeometryType.wkbLineStringM; + + if ((exportFlags & WkbExportFlags.wkbExportLineString) == 0) { + wkbBuffer.put(offset, byteOrder); + offset += 1; + wkbBuffer.putInt(offset, WkbGeometryType.wkbMultiLineStringM); + offset += 4; + wkbBuffer.putInt(offset, (int) partCount); + offset += 4; + } else if (partCount == 0) { + wkbBuffer.put(offset, byteOrder); + offset += 1; + wkbBuffer.putInt(offset, WkbGeometryType.wkbLineStringM); + offset += 4; + wkbBuffer.putInt(offset, 0); + offset += 4; + } + } else { + type = WkbGeometryType.wkbLineStringZM; + + if ((exportFlags & WkbExportFlags.wkbExportLineString) == 0) { + wkbBuffer.put(offset, byteOrder); + offset += 1; + wkbBuffer.putInt(offset, WkbGeometryType.wkbMultiLineStringZM); + offset += 4; + wkbBuffer.putInt(offset, partCount); + offset += 4; + } else if (partCount == 0) { + wkbBuffer.put(offset, byteOrder); + offset += 1; + wkbBuffer.putInt(offset, WkbGeometryType.wkbLineStringZM); + offset += 4; + wkbBuffer.putInt(offset, 0); + offset += 4; + } + } + + if (partCount == 0) + return offset; + + AttributeStreamOfDbl position = (AttributeStreamOfDbl) (polyline + .getAttributeStreamRef(VertexDescription.Semantics.POSITION)); + + AttributeStreamOfDbl zs = null; + if (bExportZs) { + if (polyline + ._attributeStreamIsAllocated(VertexDescription.Semantics.Z)) + zs = (AttributeStreamOfDbl) (polyline + .getAttributeStreamRef(VertexDescription.Semantics.Z)); + } + + AttributeStreamOfDbl ms = null; + if (bExportMs) { + if (polyline + ._attributeStreamIsAllocated(VertexDescription.Semantics.M)) + ms = (AttributeStreamOfDbl) (polyline + .getAttributeStreamRef(VertexDescription.Semantics.M)); + } + + int ipartend = 0; + for (int ipart = 0; ipart < (int) partCount; ipart++) { + // write byte order + wkbBuffer.put(offset, byteOrder); + offset += 1; + + // write type + wkbBuffer.putInt(offset, type); + offset += 4; + + // get start and end indices + int ipartstart = ipartend; + ipartend = (int) polyline.getPathEnd(ipart); + + // write num_points + int num_points = ipartend - ipartstart; + if (polyline.isClosedPath(ipart)) + num_points++; + + wkbBuffer.putInt(offset, num_points); + offset += 4; + + // write points + for (int i = ipartstart; i < ipartend; i++) { + double x = position.read(2 * i); + double y = position.read(2 * i + 1); + + wkbBuffer.putDouble(offset, x); + offset += 8; + wkbBuffer.putDouble(offset, y); + offset += 8; + + if (bExportZs) { + double z; + if (zs != null) + z = zs.read(i); + else + z = VertexDescription + .getDefaultValue(VertexDescription.Semantics.Z); + + wkbBuffer.putDouble(offset, z); + offset += 8; + } + + if (bExportMs) { + double m; + if (ms != null) + m = ms.read(i); + else + m = VertexDescription + .getDefaultValue(VertexDescription.Semantics.M); + + wkbBuffer.putDouble(offset, m); + offset += 8; + } + } + + // duplicate the start point if the Polyline is closed + if (polyline.isClosedPath(ipart)) { + double x = position.read(2 * ipartstart); + double y = position.read(2 * ipartstart + 1); + + wkbBuffer.putDouble(offset, x); + offset += 8; + wkbBuffer.putDouble(offset, y); + offset += 8; + + if (bExportZs) { + double z; + if (zs != null) + z = zs.read(ipartstart); + else + z = VertexDescription + .getDefaultValue(VertexDescription.Semantics.Z); + + wkbBuffer.putDouble(offset, z); + offset += 8; + } + + if (bExportMs) { + double m; + if (ms != null) + m = ms.read(ipartstart); + else + m = VertexDescription + .getDefaultValue(VertexDescription.Semantics.M); + + wkbBuffer.putDouble(offset, m); + offset += 8; + } + } + } + + return offset; + } + + private static int exportMultiPointToWKB(int exportFlags, + MultiPoint _multipoint, ByteBuffer wkbBuffer) { + MultiPointImpl multipoint = (MultiPointImpl) _multipoint._getImpl(); + + boolean bExportZs = multipoint + .hasAttribute(VertexDescription.Semantics.Z) + && (exportFlags & WkbExportFlags.wkbExportStripZs) == 0; + boolean bExportMs = multipoint + .hasAttribute(VertexDescription.Semantics.M) + && (exportFlags & WkbExportFlags.wkbExportStripMs) == 0; + + int point_count = multipoint.getPointCount(); + if ((exportFlags & WkbExportFlags.wkbExportPoint) != 0 + && point_count > 1) + throw new IllegalArgumentException(); + + // get size for buffer + int size; + if ((exportFlags & WkbExportFlags.wkbExportPoint) == 0) { + size = 1 /* byte order */ + 4 /* wkbType */ + 4 /* num_points */ + + point_count + * (1 /* byte order */ + 4 /* wkbType */ + 2 * 8 /* + * xy + * coordinates + */); + + if (bExportZs) + size += (point_count * 8 /* zs */); + if (bExportMs) + size += (point_count * 8 /* ms */); + } else { + size = 1 /* byte order */ + 4 /* wkbType */ + 2 * 8 /* xy coordinates */; + + if (bExportZs) + size += 8 /* z */; + if (bExportMs) + size += 8 /* m */; + } + + if (size >= NumberUtils.intMax()) + throw new GeometryException("invalid call"); + + if (wkbBuffer == null) + return (int) size; + else if (wkbBuffer.capacity() < (int) size) + throw new GeometryException("buffer is too small"); + + int offset = 0; + + byte byteOrder = (byte) (wkbBuffer.order() == ByteOrder.LITTLE_ENDIAN ? WkbByteOrder.wkbNDR + : WkbByteOrder.wkbXDR); + + // Determine the wkb type + int type; + if (!bExportZs && !bExportMs) { + type = WkbGeometryType.wkbPoint; + + if ((exportFlags & WkbExportFlags.wkbExportPoint) == 0) { + wkbBuffer.put(offset, byteOrder); + offset += 1; + wkbBuffer.putInt(offset, WkbGeometryType.wkbMultiPoint); + offset += 4; + wkbBuffer.putInt(offset, (int) point_count); + offset += 4; + } else if (point_count == 0) { + wkbBuffer.put(offset, byteOrder); + offset += 1; + wkbBuffer.putInt(offset, type); + offset += 4; + wkbBuffer.putDouble(offset, NumberUtils.TheNaN); + offset += 8; + wkbBuffer.putDouble(offset, NumberUtils.TheNaN); + offset += 8; + } + } else if (bExportZs && !bExportMs) { + type = WkbGeometryType.wkbPointZ; + + if ((exportFlags & WkbExportFlags.wkbExportPoint) == 0) { + wkbBuffer.put(offset, byteOrder); + offset += 1; + wkbBuffer.putInt(offset, WkbGeometryType.wkbMultiPointZ); + offset += 4; + wkbBuffer.putInt(offset, (int) point_count); + offset += 4; + } else if (point_count == 0) { + wkbBuffer.put(offset, byteOrder); + offset += 1; + wkbBuffer.putInt(offset, type); + offset += 4; + wkbBuffer.putDouble(offset, NumberUtils.TheNaN); + offset += 8; + wkbBuffer.putDouble(offset, NumberUtils.TheNaN); + offset += 8; + wkbBuffer.putDouble(offset, NumberUtils.TheNaN); + offset += 8; + } + } else if (bExportMs && !bExportZs) { + type = WkbGeometryType.wkbPointM; + + if ((exportFlags & WkbExportFlags.wkbExportPoint) == 0) { + wkbBuffer.put(offset, byteOrder); + offset += 1; + wkbBuffer.putInt(offset, WkbGeometryType.wkbMultiPointM); + offset += 4; + wkbBuffer.putInt(offset, (int) point_count); + offset += 4; + } else if (point_count == 0) { + wkbBuffer.put(offset, byteOrder); + offset += 1; + wkbBuffer.putInt(offset, type); + offset += 4; + wkbBuffer.putDouble(offset, NumberUtils.TheNaN); + offset += 8; + wkbBuffer.putDouble(offset, NumberUtils.TheNaN); + offset += 8; + wkbBuffer.putDouble(offset, NumberUtils.TheNaN); + offset += 8; + } + } else { + type = WkbGeometryType.wkbPointZM; + + if ((exportFlags & WkbExportFlags.wkbExportPoint) == 0) { + wkbBuffer.put(offset, byteOrder); + offset += 1; + wkbBuffer.putInt(offset, WkbGeometryType.wkbMultiPolygonZM); + offset += 4; + wkbBuffer.putInt(offset, point_count); + offset += 4; + } else if (point_count == 0) { + wkbBuffer.put(offset, byteOrder); + offset += 1; + wkbBuffer.putInt(offset, type); + offset += 4; + wkbBuffer.putDouble(offset, NumberUtils.TheNaN); + offset += 8; + wkbBuffer.putDouble(offset, NumberUtils.TheNaN); + offset += 8; + wkbBuffer.putDouble(offset, NumberUtils.TheNaN); + offset += 8; + wkbBuffer.putDouble(offset, NumberUtils.TheNaN); + offset += 8; + } + } + + if (point_count == 0) + return offset; + + AttributeStreamOfDbl position = (AttributeStreamOfDbl) (multipoint + .getAttributeStreamRef(VertexDescription.Semantics.POSITION)); + + AttributeStreamOfDbl zs = null; + if (bExportZs) { + if (multipoint + ._attributeStreamIsAllocated(VertexDescription.Semantics.Z)) + zs = (AttributeStreamOfDbl) (multipoint + .getAttributeStreamRef(VertexDescription.Semantics.Z)); + } + + AttributeStreamOfDbl ms = null; + if (bExportMs) { + if (multipoint + ._attributeStreamIsAllocated(VertexDescription.Semantics.M)) + ms = (AttributeStreamOfDbl) (multipoint + .getAttributeStreamRef(VertexDescription.Semantics.M)); + } + + for (int i = 0; i < (int) point_count; i++) { + // write byte order + wkbBuffer.put(offset, byteOrder); + offset += 1; + + // write type + wkbBuffer.putInt(offset, type); + offset += 4; + + // write xy coordinates + double x = position.read(2 * i); + double y = position.read(2 * i + 1); + + wkbBuffer.putDouble(offset, x); + offset += 8; + wkbBuffer.putDouble(offset, y); + offset += 8; + + // write Z + if (bExportZs) { + double z; + if (zs != null) + z = zs.read(i); + else + z = VertexDescription + .getDefaultValue(VertexDescription.Semantics.Z); + + wkbBuffer.putDouble(offset, z); + offset += 8; + } + + // write M + if (bExportMs) { + double m; + if (ms != null) + m = ms.read(i); + else + m = VertexDescription + .getDefaultValue(VertexDescription.Semantics.M); + + wkbBuffer.putDouble(offset, m); + offset += 8; + } + } + + return offset; + } + + private static int exportPointToWKB(int exportFlags, Point point, + ByteBuffer wkbBuffer) { + boolean bExportZs = point.hasAttribute(VertexDescription.Semantics.Z) + && (exportFlags & WkbExportFlags.wkbExportStripZs) == 0; + boolean bExportMs = point.hasAttribute(VertexDescription.Semantics.M) + && (exportFlags & WkbExportFlags.wkbExportStripMs) == 0; + + boolean bEmpty = point.isEmpty(); + int point_count = bEmpty ? 0 : 1; + + // get size for buffer + int size; + if ((exportFlags & WkbExportFlags.wkbExportMultiPoint) != 0) { + size = 1 /* byte order */ + 4 /* wkbType */ + 4 /* num_points */ + + point_count + * (1 /* byte order */ + 4 /* wkbType */ + 2 * 8 /* + * xy + * coordinates + */); + + if (bExportZs) + size += (point_count * 8 /* zs */); + if (bExportMs) + size += (point_count * 8 /* ms */); + } else { + size = 1 /* byte order */ + 4 /* wkbType */ + 2 * 8 /* xy coordinates */; + + if (bExportZs) + size += 8 /* z */; + if (bExportMs) + size += 8 /* m */; + } + + if (size >= NumberUtils.intMax()) + throw new GeometryException("invalid call"); + + if (wkbBuffer == null) + return size; + else if (wkbBuffer.capacity() < size) + throw new GeometryException("buffer is too small"); + + int offset = 0; + + byte byteOrder = (byte) (wkbBuffer.order() == ByteOrder.LITTLE_ENDIAN ? WkbByteOrder.wkbNDR + : WkbByteOrder.wkbXDR); + + // Determine the wkb type + int type; + if (!bExportZs && !bExportMs) { + type = WkbGeometryType.wkbPoint; + + if ((exportFlags & WkbExportFlags.wkbExportMultiPoint) != 0) { + wkbBuffer.put(offset, byteOrder); + offset += 1; + wkbBuffer.putInt(offset, WkbGeometryType.wkbMultiPoint); + offset += 4; + wkbBuffer.putInt(offset, (int) point_count); + offset += 4; + } else if (point_count == 0) { + wkbBuffer.put(offset, byteOrder); + offset += 1; + wkbBuffer.putInt(offset, type); + offset += 4; + wkbBuffer.putDouble(offset, NumberUtils.TheNaN); + offset += 8; + wkbBuffer.putDouble(offset, NumberUtils.TheNaN); + offset += 8; + } + } else if (bExportZs && !bExportMs) { + type = WkbGeometryType.wkbPointZ; + + if ((exportFlags & WkbExportFlags.wkbExportMultiPoint) != 0) { + wkbBuffer.put(offset, byteOrder); + offset += 1; + wkbBuffer.putInt(offset, WkbGeometryType.wkbMultiPointZ); + offset += 4; + wkbBuffer.putInt(offset, (int) point_count); + offset += 4; + } else if (point_count == 0) { + wkbBuffer.put(offset, byteOrder); + offset += 1; + wkbBuffer.putInt(offset, type); + offset += 4; + wkbBuffer.putDouble(offset, NumberUtils.TheNaN); + offset += 8; + wkbBuffer.putDouble(offset, NumberUtils.TheNaN); + offset += 8; + wkbBuffer.putDouble(offset, NumberUtils.TheNaN); + offset += 8; + } + } else if (bExportMs && !bExportZs) { + type = WkbGeometryType.wkbPointM; + + if ((exportFlags & WkbExportFlags.wkbExportMultiPoint) != 0) { + wkbBuffer.put(offset, byteOrder); + offset += 1; + wkbBuffer.putInt(offset, WkbGeometryType.wkbMultiPointM); + offset += 4; + wkbBuffer.putInt(offset, (int) point_count); + offset += 4; + } else if (point_count == 0) { + wkbBuffer.put(offset, byteOrder); + offset += 1; + wkbBuffer.putInt(offset, type); + offset += 4; + wkbBuffer.putDouble(offset, NumberUtils.TheNaN); + offset += 8; + wkbBuffer.putDouble(offset, NumberUtils.TheNaN); + offset += 8; + wkbBuffer.putDouble(offset, NumberUtils.TheNaN); + offset += 8; + } + } else { + type = WkbGeometryType.wkbPointZM; + + if ((exportFlags & WkbExportFlags.wkbExportMultiPoint) != 0) { + wkbBuffer.put(offset, byteOrder); + offset += 1; + wkbBuffer.putInt(offset, WkbGeometryType.wkbMultiPointZM); + offset += 4; + wkbBuffer.putInt(offset, (int) point_count); + offset += 4; + } else if (point_count == 0) { + wkbBuffer.put(offset, byteOrder); + offset += 1; + wkbBuffer.putInt(offset, type); + offset += 4; + wkbBuffer.putDouble(offset, NumberUtils.TheNaN); + offset += 8; + wkbBuffer.putDouble(offset, NumberUtils.TheNaN); + offset += 8; + wkbBuffer.putDouble(offset, NumberUtils.TheNaN); + offset += 8; + wkbBuffer.putDouble(offset, NumberUtils.TheNaN); + offset += 8; + } + } + + if (point_count == 0) + return offset; + + // write byte order + wkbBuffer.put(offset, byteOrder); + offset += 1; + + // write type + wkbBuffer.putInt(offset, type); + offset += 4; + + // write xy coordinate + double x = point.getX(); + double y = point.getY(); + wkbBuffer.putDouble(offset, x); + offset += 8; + wkbBuffer.putDouble(offset, y); + offset += 8; + + // write Z + if (bExportZs) { + double z = point.getZ(); + wkbBuffer.putDouble(offset, z); + offset += 8; + } + + // write M + if (bExportMs) { + double m = point.getM(); + wkbBuffer.putDouble(offset, m); + offset += 8; + } + + return offset; + } + + private static int exportEnvelopeToWKB(int exportFlags, Envelope envelope, + ByteBuffer wkbBuffer) { + boolean bExportZs = envelope + .hasAttribute(VertexDescription.Semantics.Z) + && (exportFlags & WkbExportFlags.wkbExportStripZs) == 0; + boolean bExportMs = envelope + .hasAttribute(VertexDescription.Semantics.M) + && (exportFlags & WkbExportFlags.wkbExportStripMs) == 0; + boolean bEmpty = envelope.isEmpty(); + + int partCount = bEmpty ? 0 : 1; + int point_count = bEmpty ? 0 : 5; + + // Envelope by default is exported as a WKB_polygon + + // get size for buffer + int size = 0; + if ((exportFlags & WkbExportFlags.wkbExportMultiPolygon) != 0 + || partCount == 0) + size += 1 /* byte order */ + 4 /* wkbType */ + 4 /* numPolygons */; + + size += partCount + * (1 /* byte order */ + 4 /* wkbType */ + 4/* numRings */) + + partCount * (4 /* num_points */) + point_count * (2 * 8 /* + * xy + * coordinates + */); + + if (bExportZs) + size += (point_count * 8 /* zs */); + if (bExportMs) + size += (point_count * 8 /* ms */); + + if (size >= NumberUtils.intMax()) + throw new GeometryException("invalid call"); + + if (wkbBuffer == null) + return size; + else if (wkbBuffer.capacity() < size) + throw new GeometryException("buffer is too small"); + + int offset = 0; + + byte byteOrder = (byte) (wkbBuffer.order() == ByteOrder.LITTLE_ENDIAN ? WkbByteOrder.wkbNDR + : WkbByteOrder.wkbXDR); + + // Determine the wkb type + int type; + if (!bExportZs && !bExportMs) { + type = WkbGeometryType.wkbPolygon; + + if ((exportFlags & WkbExportFlags.wkbExportMultiPolygon) != 0) { + wkbBuffer.put(offset, byteOrder); + offset += 1; + wkbBuffer.putInt(offset, WkbGeometryType.wkbMultiPolygon); + offset += 4; + wkbBuffer.putInt(offset, (int) partCount); + offset += 4; + } else if (partCount == 0) { + wkbBuffer.put(offset, byteOrder); + offset += 1; + wkbBuffer.putInt(offset, WkbGeometryType.wkbPolygon); + offset += 4; + wkbBuffer.putInt(offset, 0); + offset += 4; + } + } else if (bExportZs && !bExportMs) { + type = WkbGeometryType.wkbPolygonZ; + + if ((exportFlags & WkbExportFlags.wkbExportPolygon) != 0) { + wkbBuffer.put(offset, byteOrder); + offset += 1; + wkbBuffer.putInt(offset, WkbGeometryType.wkbMultiPolygonZ); + offset += 4; + wkbBuffer.putInt(offset, partCount); + offset += 4; + } else if (partCount == 0) { + wkbBuffer.put(offset, byteOrder); + offset += 1; + wkbBuffer.putInt(offset, WkbGeometryType.wkbPolygonZ); + offset += 4; + wkbBuffer.putInt(offset, 0); + offset += 4; + } + } else if (bExportMs && !bExportZs) { + type = WkbGeometryType.wkbPolygonM; + + if ((exportFlags & WkbExportFlags.wkbExportMultiPolygon) != 0) { + wkbBuffer.put(offset, byteOrder); + offset += 1; + wkbBuffer.putInt(offset, WkbGeometryType.wkbMultiPolygonM); + offset += 4; + wkbBuffer.putInt(offset, partCount); + offset += 4; + } else if (partCount == 0) { + wkbBuffer.put(offset, byteOrder); + offset += 1; + wkbBuffer.putInt(offset, WkbGeometryType.wkbPolygonM); + offset += 4; + wkbBuffer.putInt(offset, 0); + offset += 4; + } + } else { + type = WkbGeometryType.wkbPolygonZM; + + if ((exportFlags & WkbExportFlags.wkbExportMultiPolygon) != 0) { + wkbBuffer.put(offset, byteOrder); + offset += 1; + wkbBuffer.putInt(offset, WkbGeometryType.wkbMultiPolygonZM); + offset += 4; + wkbBuffer.putInt(offset, partCount); + offset += 4; + } else if (partCount == 0) { + wkbBuffer.put(offset, byteOrder); + offset += 1; + wkbBuffer.putInt(offset, WkbGeometryType.wkbPolygonZM); + offset += 4; + wkbBuffer.putInt(offset, 0); + offset += 4; + } + } + + if (partCount == 0) + return offset; + + // write byte order + wkbBuffer.put(offset, byteOrder); + offset += 1; + + // write type + wkbBuffer.putInt(offset, type); + offset += 4; + + // write numRings + wkbBuffer.putInt(offset, 1); + offset += 4; + + // write num_points + wkbBuffer.putInt(offset, 5); + offset += 4; + + Envelope2D env = new Envelope2D(); + envelope.queryEnvelope2D(env); + + Envelope1D z_interval = null; + if (bExportZs) + z_interval = envelope.queryInterval(VertexDescription.Semantics.Z, + 0); + + Envelope1D mInterval = null; + if (bExportMs) + mInterval = envelope + .queryInterval(VertexDescription.Semantics.M, 0); + + wkbBuffer.putDouble(offset, env.xmin); + offset += 8; + wkbBuffer.putDouble(offset, env.ymin); + offset += 8; + + if (bExportZs) { + wkbBuffer.putDouble(offset, z_interval.vmin); + offset += 8; + } + + if (bExportMs) { + wkbBuffer.putDouble(offset, mInterval.vmin); + offset += 8; + } + + wkbBuffer.putDouble(offset, env.xmax); + offset += 8; + wkbBuffer.putDouble(offset, env.ymin); + offset += 8; + + if (bExportZs) { + wkbBuffer.putDouble(offset, z_interval.vmax); + offset += 8; + } + + if (bExportMs) { + wkbBuffer.putDouble(offset, mInterval.vmax); + offset += 8; + } + + wkbBuffer.putDouble(offset, env.xmax); + offset += 8; + wkbBuffer.putDouble(offset, env.ymax); + offset += 8; + + if (bExportZs) { + wkbBuffer.putDouble(offset, z_interval.vmin); + offset += 8; + } + + if (bExportMs) { + wkbBuffer.putDouble(offset, mInterval.vmin); + offset += 8; + } + + wkbBuffer.putDouble(offset, env.xmin); + offset += 8; + wkbBuffer.putDouble(offset, env.ymax); + offset += 8; + + if (bExportZs) { + wkbBuffer.putDouble(offset, z_interval.vmax); + offset += 8; + } + + if (bExportMs) { + wkbBuffer.putDouble(offset, mInterval.vmax); + offset += 8; + } + + wkbBuffer.putDouble(offset, env.xmin); + offset += 8; + wkbBuffer.putDouble(offset, env.ymin); + offset += 8; + + if (bExportZs) { + wkbBuffer.putDouble(offset, z_interval.vmin); + offset += 8; + } + + if (bExportMs) { + wkbBuffer.putDouble(offset, mInterval.vmin); + offset += 8; + } + + return offset; + } } diff --git a/src/main/java/com/esri/core/geometry/OperatorExportToWkt.java b/src/main/java/com/esri/core/geometry/OperatorExportToWkt.java index 41447a26..a773e48b 100644 --- a/src/main/java/com/esri/core/geometry/OperatorExportToWkt.java +++ b/src/main/java/com/esri/core/geometry/OperatorExportToWkt.java @@ -25,22 +25,22 @@ public abstract class OperatorExportToWkt extends Operator { - @Override - public Type getType() { - return Type.ExportToWkt; - } - - public abstract StringCursor execute(int exportFlags, - GeometryCursor geometryCursor, - ProgressTracker progressTracker); - - public abstract String execute(int exportFlags, - Geometry geometry, - ProgressTracker progress_tracker); - - public static OperatorExportToWkt local() { - return (OperatorExportToWkt) OperatorFactoryLocal.getInstance() - .getOperator(Type.ExportToWkt); - } + @Override + public Type getType() { + return Type.ExportToWkt; + } + + public abstract StringCursor execute(int exportFlags, + GeometryCursor geometryCursor, + ProgressTracker progressTracker); + + public abstract String execute(int exportFlags, + Geometry geometry, + ProgressTracker progress_tracker); + + public static OperatorExportToWkt local() { + return (OperatorExportToWkt) OperatorFactoryLocal.getInstance() + .getOperator(Type.ExportToWkt); + } } diff --git a/src/main/java/com/esri/core/geometry/OperatorExportToWktCursor.java b/src/main/java/com/esri/core/geometry/OperatorExportToWktCursor.java index 693dc87f..c434ea39 100644 --- a/src/main/java/com/esri/core/geometry/OperatorExportToWktCursor.java +++ b/src/main/java/com/esri/core/geometry/OperatorExportToWktCursor.java @@ -1,44 +1,48 @@ package com.esri.core.geometry; public class OperatorExportToWktCursor extends StringCursor { - private GeometryCursor m_geometryCursor; - private int m_export_flags; - private SimpleStateEnum simpleStateEnum = SimpleStateEnum.SIMPLE_UNKNOWN; - - public OperatorExportToWktCursor(int exportFlags, GeometryCursor geometryCursor, ProgressTracker progressTracker) { - if (geometryCursor == null) - throw new GeometryException("invalid argument"); - - m_export_flags = exportFlags; - m_geometryCursor = geometryCursor; - } - - @Override - public String next() { - Geometry geometry; - if (hasNext()) { - geometry = m_geometryCursor.next(); - simpleStateEnum = geometry.getSimpleState(); - StringBuilder stringBuilder = new StringBuilder(); - OperatorExportToWktLocal.exportToWkt(m_export_flags, geometry, stringBuilder); - return stringBuilder.toString(); - } - return null; - } - - @Override - public boolean hasNext() { return m_geometryCursor != null && m_geometryCursor.hasNext(); } - - @Override - public long getID() { - return m_geometryCursor.getGeometryID(); - } - - @Override - public SimpleStateEnum getSimpleState() { - return simpleStateEnum; - } - - @Override - public String getFeatureID() { return m_geometryCursor.getFeatureID(); } + private GeometryCursor m_geometryCursor; + private int m_export_flags; + private SimpleStateEnum simpleStateEnum = SimpleStateEnum.SIMPLE_UNKNOWN; + + public OperatorExportToWktCursor(int exportFlags, GeometryCursor geometryCursor, ProgressTracker progressTracker) { + if (geometryCursor == null) + throw new GeometryException("invalid argument"); + + m_export_flags = exportFlags; + m_geometryCursor = geometryCursor; + } + + @Override + public String next() { + Geometry geometry; + if (hasNext()) { + geometry = m_geometryCursor.next(); + simpleStateEnum = geometry.getSimpleState(); + StringBuilder stringBuilder = new StringBuilder(); + OperatorExportToWktLocal.exportToWkt(m_export_flags, geometry, stringBuilder); + return stringBuilder.toString(); + } + return null; + } + + @Override + public boolean hasNext() { + return m_geometryCursor != null && m_geometryCursor.hasNext(); + } + + @Override + public long getID() { + return m_geometryCursor.getGeometryID(); + } + + @Override + public SimpleStateEnum getSimpleState() { + return simpleStateEnum; + } + + @Override + public String getFeatureID() { + return m_geometryCursor.getFeatureID(); + } } diff --git a/src/main/java/com/esri/core/geometry/OperatorExportToWktLocal.java b/src/main/java/com/esri/core/geometry/OperatorExportToWktLocal.java index 4a86f938..725346ef 100644 --- a/src/main/java/com/esri/core/geometry/OperatorExportToWktLocal.java +++ b/src/main/java/com/esri/core/geometry/OperatorExportToWktLocal.java @@ -24,869 +24,869 @@ package com.esri.core.geometry; class OperatorExportToWktLocal extends OperatorExportToWkt { - @Override - public StringCursor execute(int exportFlags, GeometryCursor geometryCursor, ProgressTracker progressTracker) { - return new OperatorExportToWktCursor(exportFlags, geometryCursor, progressTracker); - } - - @Override - public String execute(int export_flags, Geometry geometry, - ProgressTracker progress_tracker) { - StringBuilder string = new StringBuilder(); - exportToWkt(export_flags, geometry, string); - - return string.toString(); - } - - static void exportToWkt(int export_flags, Geometry geometry, - StringBuilder string) { - int type = geometry.getType().value(); - switch (type) { - case Geometry.GeometryType.Polygon: - if ((export_flags & WktExportFlags.wktExportLineString) != 0 - || (export_flags & WktExportFlags.wktExportMultiLineString) != 0 - || (export_flags & WktExportFlags.wktExportPoint) != 0 - || (export_flags & WktExportFlags.wktExportMultiPoint) != 0) - throw new IllegalArgumentException("Cannot export a Polygon as (Multi)LineString/(Multi)Point : " + export_flags); - - exportPolygonToWkt(export_flags, (Polygon) geometry, string); - return; - - case Geometry.GeometryType.Polyline: - if ((export_flags & WktExportFlags.wktExportPolygon) != 0 - || (export_flags & WktExportFlags.wktExportMultiPolygon) != 0 - || (export_flags & WktExportFlags.wktExportPoint) != 0 - || (export_flags & WktExportFlags.wktExportMultiPoint) != 0) - throw new IllegalArgumentException("Cannot export a Polyline as (Multi)Polygon/(Multi)Point : " + export_flags); - - exportPolylineToWkt(export_flags, (Polyline) geometry, string); - return; - - case Geometry.GeometryType.MultiPoint: - if ((export_flags & WktExportFlags.wktExportLineString) != 0 - || (export_flags & WktExportFlags.wktExportMultiLineString) != 0 - || (export_flags & WktExportFlags.wktExportPolygon) != 0 - || (export_flags & WktExportFlags.wktExportMultiPolygon) != 0) - throw new IllegalArgumentException("Cannot export a MultiPoint as (Multi)LineString/(Multi)Polygon: " + export_flags); - - exportMultiPointToWkt(export_flags, (MultiPoint) geometry, string); - return; - - case Geometry.GeometryType.Point: - if ((export_flags & WktExportFlags.wktExportLineString) != 0 - || (export_flags & WktExportFlags.wktExportMultiLineString) != 0 - || (export_flags & WktExportFlags.wktExportPolygon) != 0 - || (export_flags & WktExportFlags.wktExportMultiPolygon) != 0) - throw new IllegalArgumentException("Cannot export a Point as (Multi)LineString/(Multi)Polygon: " + export_flags); - - exportPointToWkt(export_flags, (Point) geometry, string); - return; - - case Geometry.GeometryType.Envelope: - if ((export_flags & WktExportFlags.wktExportLineString) != 0 - || (export_flags & WktExportFlags.wktExportMultiLineString) != 0 - || (export_flags & WktExportFlags.wktExportPoint) != 0 - || (export_flags & WktExportFlags.wktExportMultiPoint) != 0) - throw new IllegalArgumentException("Cannot export an Envelope as (Multi)LineString/(Multi)Point: " + export_flags); - - exportEnvelopeToWkt(export_flags, (Envelope) geometry, string); - return; - - default: { - throw GeometryException.GeometryInternalError(); - } - } - } - - static void exportPolygonToWkt(int export_flags, Polygon polygon, - StringBuilder string) { - MultiPathImpl polygon_impl = (MultiPathImpl) polygon._getImpl(); - - if ((export_flags & WktExportFlags.wktExportFailIfNotSimple) != 0) { - int simple = polygon_impl.getIsSimple(0.0); - - if (simple != MultiPathImpl.GeometryXSimple.Strong) - throw new GeometryException("corrupted geometry"); - } - - int point_count = polygon.getPointCount(); - int polygon_count = polygon_impl.getOGCPolygonCount(); - - if (point_count > 0 && polygon_count == 0) - throw new GeometryException("corrupted geometry"); - - int precision = 17 - (7 & (export_flags >> 13)); - boolean b_export_zs = polygon_impl - .hasAttribute(VertexDescription.Semantics.Z) - && (export_flags & WktExportFlags.wktExportStripZs) == 0; - boolean b_export_ms = polygon_impl - .hasAttribute(VertexDescription.Semantics.M) - && (export_flags & WktExportFlags.wktExportStripMs) == 0; - - int path_count = 0; - AttributeStreamOfDbl position = null; - AttributeStreamOfDbl zs = null; - AttributeStreamOfDbl ms = null; - AttributeStreamOfInt8 path_flags = null; - AttributeStreamOfInt32 paths = null; - - if (point_count > 0) { - position = (AttributeStreamOfDbl) (polygon_impl - .getAttributeStreamRef(VertexDescription.Semantics.POSITION)); - path_flags = polygon_impl.getPathFlagsStreamRef(); - paths = polygon_impl.getPathStreamRef(); - path_count = polygon_impl.getPathCount(); - - if (b_export_zs) { - if (polygon_impl - ._attributeStreamIsAllocated(VertexDescription.Semantics.Z)) - zs = (AttributeStreamOfDbl) polygon_impl - .getAttributeStreamRef(VertexDescription.Semantics.Z); - } - - if (b_export_ms) { - if (polygon_impl - ._attributeStreamIsAllocated(VertexDescription.Semantics.M)) - ms = (AttributeStreamOfDbl) polygon_impl - .getAttributeStreamRef(VertexDescription.Semantics.M); - } - } - - if ((export_flags & WktExportFlags.wktExportPolygon) != 0) { - if (polygon_count > 1) - throw new IllegalArgumentException("Cannot export a Polygon with specified export flags: " + export_flags); - - polygonTaggedText_(precision, b_export_zs, b_export_ms, zs, ms, - position, path_flags, paths, path_count, string); - } else { - multiPolygonTaggedText_(precision, b_export_zs, b_export_ms, zs, - ms, position, path_flags, paths, polygon_count, path_count, - string); - } - } - - static void exportPolylineToWkt(int export_flags, Polyline polyline, - StringBuilder string) { - MultiPathImpl polyline_impl = (MultiPathImpl) polyline._getImpl(); - - int point_count = polyline_impl.getPointCount(); - int path_count = polyline_impl.getPathCount(); - - if (point_count > 0 && path_count == 0) - throw new GeometryException("corrupted geometry"); - - int precision = 17 - (7 & (export_flags >> 13)); - boolean b_export_zs = polyline_impl - .hasAttribute(VertexDescription.Semantics.Z) - && (export_flags & WktExportFlags.wktExportStripZs) == 0; - boolean b_export_ms = polyline_impl - .hasAttribute(VertexDescription.Semantics.M) - && (export_flags & WktExportFlags.wktExportStripMs) == 0; - - AttributeStreamOfDbl position = null; - AttributeStreamOfDbl zs = null; - AttributeStreamOfDbl ms = null; - AttributeStreamOfInt8 path_flags = null; - AttributeStreamOfInt32 paths = null; - - if (point_count > 0) { - position = (AttributeStreamOfDbl) polyline_impl - .getAttributeStreamRef(VertexDescription.Semantics.POSITION); - path_flags = polyline_impl.getPathFlagsStreamRef(); - paths = polyline_impl.getPathStreamRef(); - - if (b_export_zs) { - if (polyline_impl - ._attributeStreamIsAllocated(VertexDescription.Semantics.Z)) - zs = (AttributeStreamOfDbl) (polyline_impl - .getAttributeStreamRef(VertexDescription.Semantics.Z)); - } - - if (b_export_ms) { - if (polyline_impl - ._attributeStreamIsAllocated(VertexDescription.Semantics.M)) - ms = (AttributeStreamOfDbl) (polyline_impl - .getAttributeStreamRef(VertexDescription.Semantics.M)); - } - } - - if ((export_flags & WktExportFlags.wktExportLineString) != 0) { - if (path_count > 1) - throw new IllegalArgumentException("Cannot export a LineString with specified export flags: " + export_flags); - - lineStringTaggedText_(precision, b_export_zs, b_export_ms, zs, ms, - position, path_flags, paths, string); - } else { - multiLineStringTaggedText_(precision, b_export_zs, b_export_ms, zs, - ms, position, path_flags, paths, path_count, string); - } - } - - static void exportMultiPointToWkt(int export_flags, MultiPoint multipoint, - StringBuilder string) { - MultiPointImpl multipoint_impl = (MultiPointImpl) multipoint._getImpl(); - - int point_count = multipoint_impl.getPointCount(); - - int precision = 17 - (7 & (export_flags >> 13)); - boolean b_export_zs = multipoint_impl - .hasAttribute(VertexDescription.Semantics.Z) - && (export_flags & WktExportFlags.wktExportStripZs) == 0; - boolean b_export_ms = multipoint_impl - .hasAttribute(VertexDescription.Semantics.M) - && (export_flags & WktExportFlags.wktExportStripMs) == 0; - - AttributeStreamOfDbl position = null; - AttributeStreamOfDbl zs = null; - AttributeStreamOfDbl ms = null; - - if (point_count > 0) { - position = (AttributeStreamOfDbl) (multipoint_impl - .getAttributeStreamRef(VertexDescription.Semantics.POSITION)); - - if (b_export_zs) { - if (multipoint_impl - ._attributeStreamIsAllocated(VertexDescription.Semantics.Z)) - zs = (AttributeStreamOfDbl) (multipoint_impl - .getAttributeStreamRef(VertexDescription.Semantics.Z)); - } - - if (b_export_ms) { - if (multipoint_impl - ._attributeStreamIsAllocated(VertexDescription.Semantics.M)) - ms = (AttributeStreamOfDbl) (multipoint_impl - .getAttributeStreamRef(VertexDescription.Semantics.M)); - } - } - - if ((export_flags & WktExportFlags.wktExportPoint) != 0) { - if (point_count > 1) - throw new IllegalArgumentException("Cannot export a Point with specified export flags: " + export_flags); - - pointTaggedTextFromMultiPoint_(precision, b_export_zs, b_export_ms, - zs, ms, position, string); - } else { - multiPointTaggedText_(precision, b_export_zs, b_export_ms, zs, ms, - position, point_count, string); - } - } - - static void exportPointToWkt(int export_flags, Point point, - StringBuilder string) { - int precision = 17 - (7 & (export_flags >> 13)); - boolean b_export_zs = point.hasAttribute(VertexDescription.Semantics.Z) - && (export_flags & WktExportFlags.wktExportStripZs) == 0; - boolean b_export_ms = point.hasAttribute(VertexDescription.Semantics.M) - && (export_flags & WktExportFlags.wktExportStripMs) == 0; - - double x = NumberUtils.TheNaN; - double y = NumberUtils.TheNaN; - double z = NumberUtils.TheNaN; - double m = NumberUtils.TheNaN; - - if (!point.isEmpty()) { - x = point.getX(); - y = point.getY(); - - if (b_export_zs) - z = point.getZ(); - - if (b_export_ms) - m = point.getM(); - } - - if ((export_flags & WktExportFlags.wktExportMultiPoint) != 0) { - multiPointTaggedTextFromPoint_(precision, b_export_zs, b_export_ms, - x, y, z, m, string); - } else { - pointTaggedText_(precision, b_export_zs, b_export_ms, x, y, z, m, - string); - } - } - - static void exportEnvelopeToWkt(int export_flags, Envelope envelope, - StringBuilder string) { - int precision = 17 - (7 & (export_flags >> 13)); - boolean b_export_zs = envelope - .hasAttribute(VertexDescription.Semantics.Z) - && (export_flags & WktExportFlags.wktExportStripZs) == 0; - boolean b_export_ms = envelope - .hasAttribute(VertexDescription.Semantics.M) - && (export_flags & WktExportFlags.wktExportStripMs) == 0; - - double xmin = NumberUtils.TheNaN; - double ymin = NumberUtils.TheNaN; - double xmax = NumberUtils.TheNaN; - double ymax = NumberUtils.TheNaN; - double zmin = NumberUtils.TheNaN; - double zmax = NumberUtils.TheNaN; - double mmin = NumberUtils.TheNaN; - double mmax = NumberUtils.TheNaN; - Envelope1D interval; - - if (!envelope.isEmpty()) { - xmin = envelope.getXMin(); - ymin = envelope.getYMin(); - xmax = envelope.getXMax(); - ymax = envelope.getYMax(); - - if (b_export_zs) { - interval = envelope.queryInterval( - VertexDescription.Semantics.Z, 0); - zmin = interval.vmin; - zmax = interval.vmax; - } - - if (b_export_ms) { - interval = envelope.queryInterval( - VertexDescription.Semantics.M, 0); - mmin = interval.vmin; - mmax = interval.vmax; - } - } - - if ((export_flags & WktExportFlags.wktExportMultiPolygon) != 0) { - multiPolygonTaggedTextFromEnvelope_(precision, b_export_zs, - b_export_ms, xmin, ymin, xmax, ymax, zmin, zmax, mmin, - mmax, string); - } else { - polygonTaggedTextFromEnvelope_(precision, b_export_zs, b_export_ms, - xmin, ymin, xmax, ymax, zmin, zmax, mmin, mmax, string); - } - } - - static void multiPolygonTaggedText_(int precision, boolean b_export_zs, - boolean b_export_ms, AttributeStreamOfDbl zs, - AttributeStreamOfDbl ms, AttributeStreamOfDbl position, - AttributeStreamOfInt8 path_flags, AttributeStreamOfInt32 paths, - int polygon_count, int path_count, StringBuilder string) { - string.append("MULTIPOLYGON "); - - if (b_export_zs && b_export_ms) - string.append("ZM "); - else if (b_export_zs && !b_export_ms) - string.append("Z "); - else if (!b_export_zs && b_export_ms) - string.append("M "); - - if (position == null) { - string.append("EMPTY"); - return; - } - - string.append('('); - - multiPolygonText_(precision, b_export_zs, b_export_ms, zs, ms, - position, path_flags, paths, polygon_count, path_count, string); - - string.append(')'); - } - - static void multiPolygonTaggedTextFromEnvelope_(int precision, - boolean b_export_zs, boolean b_export_ms, double xmin, double ymin, - double xmax, double ymax, double zmin, double zmax, double mmin, - double mmax, StringBuilder string) { - string.append("MULTIPOLYGON "); - - if (b_export_zs && b_export_ms) - string.append("ZM "); - else if (b_export_zs && !b_export_ms) - string.append("Z "); - else if (!b_export_zs && b_export_ms) - string.append("M "); - - if (NumberUtils.isNaN(xmin)) { - string.append("EMPTY"); - return; - } - - string.append('('); - - writeEnvelopeAsWktPolygon_(precision, b_export_zs, b_export_ms, xmin, - ymin, xmax, ymax, zmin, zmax, mmin, mmax, string); - - string.append(')'); - } - - static void multiLineStringTaggedText_(int precision, boolean b_export_zs, - boolean b_export_ms, AttributeStreamOfDbl zs, - AttributeStreamOfDbl ms, AttributeStreamOfDbl position, - AttributeStreamOfInt8 path_flags, AttributeStreamOfInt32 paths, - int path_count, StringBuilder string) { - string.append("MULTILINESTRING "); - - if (b_export_zs && b_export_ms) - string.append("ZM "); - else if (b_export_zs && !b_export_ms) - string.append("Z "); - else if (!b_export_zs && b_export_ms) - string.append("M "); - - if (position == null) { - string.append("EMPTY"); - return; - } - - string.append('('); - - multiLineStringText_(precision, b_export_zs, b_export_ms, zs, ms, - position, path_flags, paths, path_count, string); - - string.append(')'); - } - - static void multiPointTaggedText_(int precision, boolean b_export_zs, - boolean b_export_ms, AttributeStreamOfDbl zs, - AttributeStreamOfDbl ms, AttributeStreamOfDbl position, - int point_count, StringBuilder string) { - string.append("MULTIPOINT "); - - if (b_export_zs && b_export_ms) - string.append("ZM "); - else if (b_export_zs && !b_export_ms) - string.append("Z "); - else if (!b_export_zs && b_export_ms) - string.append("M "); - - if (position == null) { - string.append("EMPTY"); - return; - } - - string.append('('); - - multiPointText_(precision, b_export_zs, b_export_ms, zs, ms, position, - point_count, string); - - string.append(')'); - } - - static void multiPointTaggedTextFromPoint_(int precision, - boolean b_export_zs, boolean b_export_ms, double x, double y, - double z, double m, StringBuilder string) { - string.append("MULTIPOINT "); - - if (b_export_zs && b_export_ms) - string.append("ZM "); - else if (b_export_zs && !b_export_ms) - string.append("Z "); - else if (!b_export_zs && b_export_ms) - string.append("M "); - - if (NumberUtils.isNaN(x)) { - string.append("EMPTY"); - return; - } - - string.append('('); - - pointText_(precision, b_export_zs, b_export_ms, x, y, z, m, string); - - string.append(')'); - } - - static void polygonTaggedText_(int precision, boolean b_export_zs, - boolean b_export_ms, AttributeStreamOfDbl zs, - AttributeStreamOfDbl ms, AttributeStreamOfDbl position, - AttributeStreamOfInt8 path_flags, AttributeStreamOfInt32 paths, - int path_count, StringBuilder string) { - string.append("POLYGON "); - - if (b_export_zs && b_export_ms) - string.append("ZM "); - else if (b_export_zs && !b_export_ms) - string.append("Z "); - else if (!b_export_zs && b_export_ms) - string.append("M "); - - if (position == null) { - string.append("EMPTY"); - return; - } - - polygonText_(precision, b_export_zs, b_export_ms, zs, ms, position, - path_flags, paths, 0, path_count, string); - } - - static void polygonTaggedTextFromEnvelope_(int precision, - boolean b_export_zs, boolean b_export_ms, double xmin, double ymin, - double xmax, double ymax, double zmin, double zmax, double mmin, - double mmax, StringBuilder string) { - string.append("POLYGON "); - - if (b_export_zs && b_export_ms) - string.append("ZM "); - else if (b_export_zs && !b_export_ms) - string.append("Z "); - else if (!b_export_zs && b_export_ms) - string.append("M "); - - if (NumberUtils.isNaN(xmin)) { - string.append("EMPTY"); - return; - } - - writeEnvelopeAsWktPolygon_(precision, b_export_zs, b_export_ms, xmin, - ymin, xmax, ymax, zmin, zmax, mmin, mmax, string); - } - - static void lineStringTaggedText_(int precision, boolean b_export_zs, - boolean b_export_ms, AttributeStreamOfDbl zs, - AttributeStreamOfDbl ms, AttributeStreamOfDbl position, - AttributeStreamOfInt8 path_flags, AttributeStreamOfInt32 paths, - StringBuilder string) { - string.append("LINESTRING "); - - if (b_export_zs && b_export_ms) - string.append("ZM "); - else if (b_export_zs && !b_export_ms) - string.append("Z "); - else if (!b_export_zs && b_export_ms) - string.append("M "); - - if (position == null) { - string.append("EMPTY"); - return; - } - - boolean b_closed = ((path_flags.read(0) & PathFlags.enumClosed) != 0); - - lineStringText_(false, b_closed, precision, b_export_zs, b_export_ms, - zs, ms, position, paths, 0, string); - } - - static void pointTaggedText_(int precision, boolean b_export_zs, - boolean b_export_ms, double x, double y, double z, double m, - StringBuilder string) { - string.append("POINT "); - - if (b_export_zs && b_export_ms) - string.append("ZM "); - else if (b_export_zs && !b_export_ms) - string.append("Z "); - else if (!b_export_zs && b_export_ms) - string.append("M "); - - if (NumberUtils.isNaN(x)) { - string.append("EMPTY"); - return; - } - - pointText_(precision, b_export_zs, b_export_ms, x, y, z, m, string); - } - - static void pointTaggedTextFromMultiPoint_(int precision, - boolean b_export_zs, boolean b_export_ms, AttributeStreamOfDbl zs, - AttributeStreamOfDbl ms, AttributeStreamOfDbl position, - StringBuilder string) { - string.append("POINT "); - - if (b_export_zs && b_export_ms) - string.append("ZM "); - else if (b_export_zs && !b_export_ms) - string.append("Z "); - else if (!b_export_zs && b_export_ms) - string.append("M "); - - if (position == null) { - string.append("EMPTY"); - return; - } - - pointText_(precision, b_export_zs, b_export_ms, zs, ms, position, 0, - string); - } - - static void multiPolygonText_(int precision, boolean b_export_zs, - boolean b_export_ms, AttributeStreamOfDbl zs, - AttributeStreamOfDbl ms, AttributeStreamOfDbl position, - AttributeStreamOfInt8 path_flags, AttributeStreamOfInt32 paths, - int polygon_count, int path_count, StringBuilder string) { - int polygon_start = 0; - int polygon_end = 1; - - while (polygon_end < path_count - && (path_flags.read(polygon_end) & PathFlags.enumOGCStartPolygon) == 0) - polygon_end++; - - polygonText_(precision, b_export_zs, b_export_ms, zs, ms, position, - path_flags, paths, polygon_start, polygon_end, string); - - for (int ipolygon = 1; ipolygon < polygon_count; ipolygon++) { - polygon_start = polygon_end; - polygon_end++; - - while (polygon_end < path_count - && (path_flags.read(polygon_end) & PathFlags.enumOGCStartPolygon) == 0) - polygon_end++; - - string.append(", "); - polygonText_(precision, b_export_zs, b_export_ms, zs, ms, position, - path_flags, paths, polygon_start, polygon_end, string); - } - } - - static void multiLineStringText_(int precision, boolean b_export_zs, - boolean b_export_ms, AttributeStreamOfDbl zs, - AttributeStreamOfDbl ms, AttributeStreamOfDbl position, - AttributeStreamOfInt8 path_flags, AttributeStreamOfInt32 paths, - int path_count, StringBuilder string) { - boolean b_closed = ((path_flags.read(0) & PathFlags.enumClosed) != 0); - - lineStringText_(false, b_closed, precision, b_export_zs, b_export_ms, - zs, ms, position, paths, 0, string); - - for (int path = 1; path < path_count; path++) { - string.append(", "); - - b_closed = ((path_flags.read(path) & PathFlags.enumClosed) != 0); - - lineStringText_(false, b_closed, precision, b_export_zs, - b_export_ms, zs, ms, position, paths, path, string); - } - } - - static void multiPointText_(int precision, boolean b_export_zs, - boolean b_export_ms, AttributeStreamOfDbl zs, - AttributeStreamOfDbl ms, AttributeStreamOfDbl position, - int point_count, StringBuilder string) { - pointText_(precision, b_export_zs, b_export_ms, zs, ms, position, 0, - string); - - for (int point = 1; point < point_count; point++) { - string.append(", "); - pointText_(precision, b_export_zs, b_export_ms, zs, ms, position, - point, string); - } - } - - static void polygonText_(int precision, boolean b_export_zs, - boolean b_export_ms, AttributeStreamOfDbl zs, - AttributeStreamOfDbl ms, AttributeStreamOfDbl position, - AttributeStreamOfInt8 path_flags, AttributeStreamOfInt32 paths, - int polygon_start, int polygon_end, StringBuilder string) { - string.append('('); - - lineStringText_(true, true, precision, b_export_zs, b_export_ms, zs, - ms, position, paths, polygon_start, string); - - for (int path = polygon_start + 1; path < polygon_end; path++) { - string.append(", "); - lineStringText_(true, true, precision, b_export_zs, b_export_ms, - zs, ms, position, paths, path, string); - } - - string.append(')'); - } - - static void lineStringText_(boolean bRing, boolean b_closed, int precision, - boolean b_export_zs, boolean b_export_ms, AttributeStreamOfDbl zs, - AttributeStreamOfDbl ms, AttributeStreamOfDbl position, - AttributeStreamOfInt32 paths, int path, StringBuilder string) { - int istart = paths.read(path); - int iend = paths.read(path + 1); - - if (istart == iend) { - string.append("EMPTY"); - return; - } - - string.append('('); - - if (bRing) { - point_(precision, b_export_zs, b_export_ms, zs, ms, position, - istart, string); - string.append(", "); - - for (int point = iend - 1; point >= istart + 1; point--) { - point_(precision, b_export_zs, b_export_ms, zs, ms, position, - point, string); - string.append(", "); - } - - point_(precision, b_export_zs, b_export_ms, zs, ms, position, - istart, string); - } else { - for (int point = istart; point < iend - 1; point++) { - point_(precision, b_export_zs, b_export_ms, zs, ms, position, - point, string); - string.append(", "); - } - - point_(precision, b_export_zs, b_export_ms, zs, ms, position, - iend - 1, string); - - if (b_closed) { - string.append(", "); - point_(precision, b_export_zs, b_export_ms, zs, ms, position, - istart, string); - } - } - - string.append(')'); - } - - static int pointText_(int precision, boolean b_export_zs, - boolean b_export_ms, double x, double y, double z, double m, - StringBuilder string) { - string.append('('); - point_(precision, b_export_zs, b_export_ms, x, y, z, m, string); - string.append(')'); - - return 1; - } - - static void pointText_(int precision, boolean b_export_zs, - boolean b_export_ms, AttributeStreamOfDbl zs, - AttributeStreamOfDbl ms, AttributeStreamOfDbl position, int point, - StringBuilder string) { - double x = position.read(2 * point); - double y = position.read(2 * point + 1); - double z = NumberUtils.TheNaN; - double m = NumberUtils.TheNaN; - - if (b_export_zs) - z = (zs != null ? zs.read(point) : VertexDescription - .getDefaultValue(VertexDescription.Semantics.Z)); - - if (b_export_ms) - m = (ms != null ? ms.read(point) : VertexDescription - .getDefaultValue(VertexDescription.Semantics.M)); - - pointText_(precision, b_export_zs, b_export_ms, x, y, z, m, string); - } - - static void point_(int precision, boolean b_export_zs, boolean b_export_ms, - double x, double y, double z, double m, StringBuilder string) { - writeSignedNumericLiteral_(x, precision, string); - string.append(' '); - writeSignedNumericLiteral_(y, precision, string); - - if (b_export_zs) { - string.append(' '); - writeSignedNumericLiteral_(z, precision, string); - } - - if (b_export_ms) { - string.append(' '); - writeSignedNumericLiteral_(m, precision, string); - } - } - - static void point_(int precision, boolean b_export_zs, boolean b_export_ms, - AttributeStreamOfDbl zs, AttributeStreamOfDbl ms, - AttributeStreamOfDbl position, int point, StringBuilder string) { - double x = position.read(2 * point); - double y = position.read(2 * point + 1); - double z = NumberUtils.TheNaN; - double m = NumberUtils.TheNaN; - - if (b_export_zs) - z = (zs != null ? zs.read(point) : VertexDescription - .getDefaultValue(VertexDescription.Semantics.Z)); - - if (b_export_ms) - m = (ms != null ? ms.read(point) : VertexDescription - .getDefaultValue(VertexDescription.Semantics.M)); - - point_(precision, b_export_zs, b_export_ms, x, y, z, m, string); - } - - static boolean writeSignedNumericLiteral_(double v, int precision, - StringBuilder string) { - if (NumberUtils.isNaN(v)) { - string.append("NAN"); - return false; - } - - StringUtils.appendDouble(v, precision, string); - return true; - } - - static void writeEnvelopeAsWktPolygon_(int precision, boolean b_export_zs, - boolean b_export_ms, double xmin, double ymin, double xmax, - double ymax, double zmin, double zmax, double mmin, double mmax, - StringBuilder string) { - string.append("(("); - - writeSignedNumericLiteral_(xmin, precision, string); - string.append(' '); - writeSignedNumericLiteral_(ymin, precision, string); - - if (b_export_zs) { - string.append(' '); - writeSignedNumericLiteral_(zmin, precision, string); - } - - if (b_export_ms) { - string.append(' '); - writeSignedNumericLiteral_(mmin, precision, string); - } - - string.append(", "); - - writeSignedNumericLiteral_(xmax, precision, string); - string.append(' '); - writeSignedNumericLiteral_(ymin, precision, string); - - if (b_export_zs) { - string.append(' '); - writeSignedNumericLiteral_(zmax, precision, string); - } - - if (b_export_ms) { - string.append(' '); - writeSignedNumericLiteral_(mmax, precision, string); - } - - string.append(", "); - - writeSignedNumericLiteral_(xmax, precision, string); - string.append(' '); - writeSignedNumericLiteral_(ymax, precision, string); - - if (b_export_zs) { - string.append(' '); - writeSignedNumericLiteral_(zmin, precision, string); - } - - if (b_export_ms) { - string.append(' '); - writeSignedNumericLiteral_(mmin, precision, string); - } - - string.append(", "); - - writeSignedNumericLiteral_(xmin, precision, string); - string.append(' '); - writeSignedNumericLiteral_(ymax, precision, string); - - if (b_export_zs) { - string.append(' '); - writeSignedNumericLiteral_(zmax, precision, string); - } - - if (b_export_ms) { - string.append(' '); - writeSignedNumericLiteral_(mmax, precision, string); - } - - string.append(", "); - - writeSignedNumericLiteral_(xmin, precision, string); - string.append(' '); - writeSignedNumericLiteral_(ymin, precision, string); - - if (b_export_zs) { - string.append(' '); - writeSignedNumericLiteral_(zmin, precision, string); - } - - if (b_export_ms) { - string.append(' '); - writeSignedNumericLiteral_(mmin, precision, string); - } - - string.append("))"); - } + @Override + public StringCursor execute(int exportFlags, GeometryCursor geometryCursor, ProgressTracker progressTracker) { + return new OperatorExportToWktCursor(exportFlags, geometryCursor, progressTracker); + } + + @Override + public String execute(int export_flags, Geometry geometry, + ProgressTracker progress_tracker) { + StringBuilder string = new StringBuilder(); + exportToWkt(export_flags, geometry, string); + + return string.toString(); + } + + static void exportToWkt(int export_flags, Geometry geometry, + StringBuilder string) { + int type = geometry.getType().value(); + switch (type) { + case Geometry.GeometryType.Polygon: + if ((export_flags & WktExportFlags.wktExportLineString) != 0 + || (export_flags & WktExportFlags.wktExportMultiLineString) != 0 + || (export_flags & WktExportFlags.wktExportPoint) != 0 + || (export_flags & WktExportFlags.wktExportMultiPoint) != 0) + throw new IllegalArgumentException("Cannot export a Polygon as (Multi)LineString/(Multi)Point : " + export_flags); + + exportPolygonToWkt(export_flags, (Polygon) geometry, string); + return; + + case Geometry.GeometryType.Polyline: + if ((export_flags & WktExportFlags.wktExportPolygon) != 0 + || (export_flags & WktExportFlags.wktExportMultiPolygon) != 0 + || (export_flags & WktExportFlags.wktExportPoint) != 0 + || (export_flags & WktExportFlags.wktExportMultiPoint) != 0) + throw new IllegalArgumentException("Cannot export a Polyline as (Multi)Polygon/(Multi)Point : " + export_flags); + + exportPolylineToWkt(export_flags, (Polyline) geometry, string); + return; + + case Geometry.GeometryType.MultiPoint: + if ((export_flags & WktExportFlags.wktExportLineString) != 0 + || (export_flags & WktExportFlags.wktExportMultiLineString) != 0 + || (export_flags & WktExportFlags.wktExportPolygon) != 0 + || (export_flags & WktExportFlags.wktExportMultiPolygon) != 0) + throw new IllegalArgumentException("Cannot export a MultiPoint as (Multi)LineString/(Multi)Polygon: " + export_flags); + + exportMultiPointToWkt(export_flags, (MultiPoint) geometry, string); + return; + + case Geometry.GeometryType.Point: + if ((export_flags & WktExportFlags.wktExportLineString) != 0 + || (export_flags & WktExportFlags.wktExportMultiLineString) != 0 + || (export_flags & WktExportFlags.wktExportPolygon) != 0 + || (export_flags & WktExportFlags.wktExportMultiPolygon) != 0) + throw new IllegalArgumentException("Cannot export a Point as (Multi)LineString/(Multi)Polygon: " + export_flags); + + exportPointToWkt(export_flags, (Point) geometry, string); + return; + + case Geometry.GeometryType.Envelope: + if ((export_flags & WktExportFlags.wktExportLineString) != 0 + || (export_flags & WktExportFlags.wktExportMultiLineString) != 0 + || (export_flags & WktExportFlags.wktExportPoint) != 0 + || (export_flags & WktExportFlags.wktExportMultiPoint) != 0) + throw new IllegalArgumentException("Cannot export an Envelope as (Multi)LineString/(Multi)Point: " + export_flags); + + exportEnvelopeToWkt(export_flags, (Envelope) geometry, string); + return; + + default: { + throw GeometryException.GeometryInternalError(); + } + } + } + + static void exportPolygonToWkt(int export_flags, Polygon polygon, + StringBuilder string) { + MultiPathImpl polygon_impl = (MultiPathImpl) polygon._getImpl(); + + if ((export_flags & WktExportFlags.wktExportFailIfNotSimple) != 0) { + int simple = polygon_impl.getIsSimple(0.0); + + if (simple != MultiPathImpl.GeometryXSimple.Strong) + throw new GeometryException("corrupted geometry"); + } + + int point_count = polygon.getPointCount(); + int polygon_count = polygon_impl.getOGCPolygonCount(); + + if (point_count > 0 && polygon_count == 0) + throw new GeometryException("corrupted geometry"); + + int precision = 17 - (7 & (export_flags >> 13)); + boolean b_export_zs = polygon_impl + .hasAttribute(VertexDescription.Semantics.Z) + && (export_flags & WktExportFlags.wktExportStripZs) == 0; + boolean b_export_ms = polygon_impl + .hasAttribute(VertexDescription.Semantics.M) + && (export_flags & WktExportFlags.wktExportStripMs) == 0; + + int path_count = 0; + AttributeStreamOfDbl position = null; + AttributeStreamOfDbl zs = null; + AttributeStreamOfDbl ms = null; + AttributeStreamOfInt8 path_flags = null; + AttributeStreamOfInt32 paths = null; + + if (point_count > 0) { + position = (AttributeStreamOfDbl) (polygon_impl + .getAttributeStreamRef(VertexDescription.Semantics.POSITION)); + path_flags = polygon_impl.getPathFlagsStreamRef(); + paths = polygon_impl.getPathStreamRef(); + path_count = polygon_impl.getPathCount(); + + if (b_export_zs) { + if (polygon_impl + ._attributeStreamIsAllocated(VertexDescription.Semantics.Z)) + zs = (AttributeStreamOfDbl) polygon_impl + .getAttributeStreamRef(VertexDescription.Semantics.Z); + } + + if (b_export_ms) { + if (polygon_impl + ._attributeStreamIsAllocated(VertexDescription.Semantics.M)) + ms = (AttributeStreamOfDbl) polygon_impl + .getAttributeStreamRef(VertexDescription.Semantics.M); + } + } + + if ((export_flags & WktExportFlags.wktExportPolygon) != 0) { + if (polygon_count > 1) + throw new IllegalArgumentException("Cannot export a Polygon with specified export flags: " + export_flags); + + polygonTaggedText_(precision, b_export_zs, b_export_ms, zs, ms, + position, path_flags, paths, path_count, string); + } else { + multiPolygonTaggedText_(precision, b_export_zs, b_export_ms, zs, + ms, position, path_flags, paths, polygon_count, path_count, + string); + } + } + + static void exportPolylineToWkt(int export_flags, Polyline polyline, + StringBuilder string) { + MultiPathImpl polyline_impl = (MultiPathImpl) polyline._getImpl(); + + int point_count = polyline_impl.getPointCount(); + int path_count = polyline_impl.getPathCount(); + + if (point_count > 0 && path_count == 0) + throw new GeometryException("corrupted geometry"); + + int precision = 17 - (7 & (export_flags >> 13)); + boolean b_export_zs = polyline_impl + .hasAttribute(VertexDescription.Semantics.Z) + && (export_flags & WktExportFlags.wktExportStripZs) == 0; + boolean b_export_ms = polyline_impl + .hasAttribute(VertexDescription.Semantics.M) + && (export_flags & WktExportFlags.wktExportStripMs) == 0; + + AttributeStreamOfDbl position = null; + AttributeStreamOfDbl zs = null; + AttributeStreamOfDbl ms = null; + AttributeStreamOfInt8 path_flags = null; + AttributeStreamOfInt32 paths = null; + + if (point_count > 0) { + position = (AttributeStreamOfDbl) polyline_impl + .getAttributeStreamRef(VertexDescription.Semantics.POSITION); + path_flags = polyline_impl.getPathFlagsStreamRef(); + paths = polyline_impl.getPathStreamRef(); + + if (b_export_zs) { + if (polyline_impl + ._attributeStreamIsAllocated(VertexDescription.Semantics.Z)) + zs = (AttributeStreamOfDbl) (polyline_impl + .getAttributeStreamRef(VertexDescription.Semantics.Z)); + } + + if (b_export_ms) { + if (polyline_impl + ._attributeStreamIsAllocated(VertexDescription.Semantics.M)) + ms = (AttributeStreamOfDbl) (polyline_impl + .getAttributeStreamRef(VertexDescription.Semantics.M)); + } + } + + if ((export_flags & WktExportFlags.wktExportLineString) != 0) { + if (path_count > 1) + throw new IllegalArgumentException("Cannot export a LineString with specified export flags: " + export_flags); + + lineStringTaggedText_(precision, b_export_zs, b_export_ms, zs, ms, + position, path_flags, paths, string); + } else { + multiLineStringTaggedText_(precision, b_export_zs, b_export_ms, zs, + ms, position, path_flags, paths, path_count, string); + } + } + + static void exportMultiPointToWkt(int export_flags, MultiPoint multipoint, + StringBuilder string) { + MultiPointImpl multipoint_impl = (MultiPointImpl) multipoint._getImpl(); + + int point_count = multipoint_impl.getPointCount(); + + int precision = 17 - (7 & (export_flags >> 13)); + boolean b_export_zs = multipoint_impl + .hasAttribute(VertexDescription.Semantics.Z) + && (export_flags & WktExportFlags.wktExportStripZs) == 0; + boolean b_export_ms = multipoint_impl + .hasAttribute(VertexDescription.Semantics.M) + && (export_flags & WktExportFlags.wktExportStripMs) == 0; + + AttributeStreamOfDbl position = null; + AttributeStreamOfDbl zs = null; + AttributeStreamOfDbl ms = null; + + if (point_count > 0) { + position = (AttributeStreamOfDbl) (multipoint_impl + .getAttributeStreamRef(VertexDescription.Semantics.POSITION)); + + if (b_export_zs) { + if (multipoint_impl + ._attributeStreamIsAllocated(VertexDescription.Semantics.Z)) + zs = (AttributeStreamOfDbl) (multipoint_impl + .getAttributeStreamRef(VertexDescription.Semantics.Z)); + } + + if (b_export_ms) { + if (multipoint_impl + ._attributeStreamIsAllocated(VertexDescription.Semantics.M)) + ms = (AttributeStreamOfDbl) (multipoint_impl + .getAttributeStreamRef(VertexDescription.Semantics.M)); + } + } + + if ((export_flags & WktExportFlags.wktExportPoint) != 0) { + if (point_count > 1) + throw new IllegalArgumentException("Cannot export a Point with specified export flags: " + export_flags); + + pointTaggedTextFromMultiPoint_(precision, b_export_zs, b_export_ms, + zs, ms, position, string); + } else { + multiPointTaggedText_(precision, b_export_zs, b_export_ms, zs, ms, + position, point_count, string); + } + } + + static void exportPointToWkt(int export_flags, Point point, + StringBuilder string) { + int precision = 17 - (7 & (export_flags >> 13)); + boolean b_export_zs = point.hasAttribute(VertexDescription.Semantics.Z) + && (export_flags & WktExportFlags.wktExportStripZs) == 0; + boolean b_export_ms = point.hasAttribute(VertexDescription.Semantics.M) + && (export_flags & WktExportFlags.wktExportStripMs) == 0; + + double x = NumberUtils.TheNaN; + double y = NumberUtils.TheNaN; + double z = NumberUtils.TheNaN; + double m = NumberUtils.TheNaN; + + if (!point.isEmpty()) { + x = point.getX(); + y = point.getY(); + + if (b_export_zs) + z = point.getZ(); + + if (b_export_ms) + m = point.getM(); + } + + if ((export_flags & WktExportFlags.wktExportMultiPoint) != 0) { + multiPointTaggedTextFromPoint_(precision, b_export_zs, b_export_ms, + x, y, z, m, string); + } else { + pointTaggedText_(precision, b_export_zs, b_export_ms, x, y, z, m, + string); + } + } + + static void exportEnvelopeToWkt(int export_flags, Envelope envelope, + StringBuilder string) { + int precision = 17 - (7 & (export_flags >> 13)); + boolean b_export_zs = envelope + .hasAttribute(VertexDescription.Semantics.Z) + && (export_flags & WktExportFlags.wktExportStripZs) == 0; + boolean b_export_ms = envelope + .hasAttribute(VertexDescription.Semantics.M) + && (export_flags & WktExportFlags.wktExportStripMs) == 0; + + double xmin = NumberUtils.TheNaN; + double ymin = NumberUtils.TheNaN; + double xmax = NumberUtils.TheNaN; + double ymax = NumberUtils.TheNaN; + double zmin = NumberUtils.TheNaN; + double zmax = NumberUtils.TheNaN; + double mmin = NumberUtils.TheNaN; + double mmax = NumberUtils.TheNaN; + Envelope1D interval; + + if (!envelope.isEmpty()) { + xmin = envelope.getXMin(); + ymin = envelope.getYMin(); + xmax = envelope.getXMax(); + ymax = envelope.getYMax(); + + if (b_export_zs) { + interval = envelope.queryInterval( + VertexDescription.Semantics.Z, 0); + zmin = interval.vmin; + zmax = interval.vmax; + } + + if (b_export_ms) { + interval = envelope.queryInterval( + VertexDescription.Semantics.M, 0); + mmin = interval.vmin; + mmax = interval.vmax; + } + } + + if ((export_flags & WktExportFlags.wktExportMultiPolygon) != 0) { + multiPolygonTaggedTextFromEnvelope_(precision, b_export_zs, + b_export_ms, xmin, ymin, xmax, ymax, zmin, zmax, mmin, + mmax, string); + } else { + polygonTaggedTextFromEnvelope_(precision, b_export_zs, b_export_ms, + xmin, ymin, xmax, ymax, zmin, zmax, mmin, mmax, string); + } + } + + static void multiPolygonTaggedText_(int precision, boolean b_export_zs, + boolean b_export_ms, AttributeStreamOfDbl zs, + AttributeStreamOfDbl ms, AttributeStreamOfDbl position, + AttributeStreamOfInt8 path_flags, AttributeStreamOfInt32 paths, + int polygon_count, int path_count, StringBuilder string) { + string.append("MULTIPOLYGON "); + + if (b_export_zs && b_export_ms) + string.append("ZM "); + else if (b_export_zs && !b_export_ms) + string.append("Z "); + else if (!b_export_zs && b_export_ms) + string.append("M "); + + if (position == null) { + string.append("EMPTY"); + return; + } + + string.append('('); + + multiPolygonText_(precision, b_export_zs, b_export_ms, zs, ms, + position, path_flags, paths, polygon_count, path_count, string); + + string.append(')'); + } + + static void multiPolygonTaggedTextFromEnvelope_(int precision, + boolean b_export_zs, boolean b_export_ms, double xmin, double ymin, + double xmax, double ymax, double zmin, double zmax, double mmin, + double mmax, StringBuilder string) { + string.append("MULTIPOLYGON "); + + if (b_export_zs && b_export_ms) + string.append("ZM "); + else if (b_export_zs && !b_export_ms) + string.append("Z "); + else if (!b_export_zs && b_export_ms) + string.append("M "); + + if (NumberUtils.isNaN(xmin)) { + string.append("EMPTY"); + return; + } + + string.append('('); + + writeEnvelopeAsWktPolygon_(precision, b_export_zs, b_export_ms, xmin, + ymin, xmax, ymax, zmin, zmax, mmin, mmax, string); + + string.append(')'); + } + + static void multiLineStringTaggedText_(int precision, boolean b_export_zs, + boolean b_export_ms, AttributeStreamOfDbl zs, + AttributeStreamOfDbl ms, AttributeStreamOfDbl position, + AttributeStreamOfInt8 path_flags, AttributeStreamOfInt32 paths, + int path_count, StringBuilder string) { + string.append("MULTILINESTRING "); + + if (b_export_zs && b_export_ms) + string.append("ZM "); + else if (b_export_zs && !b_export_ms) + string.append("Z "); + else if (!b_export_zs && b_export_ms) + string.append("M "); + + if (position == null) { + string.append("EMPTY"); + return; + } + + string.append('('); + + multiLineStringText_(precision, b_export_zs, b_export_ms, zs, ms, + position, path_flags, paths, path_count, string); + + string.append(')'); + } + + static void multiPointTaggedText_(int precision, boolean b_export_zs, + boolean b_export_ms, AttributeStreamOfDbl zs, + AttributeStreamOfDbl ms, AttributeStreamOfDbl position, + int point_count, StringBuilder string) { + string.append("MULTIPOINT "); + + if (b_export_zs && b_export_ms) + string.append("ZM "); + else if (b_export_zs && !b_export_ms) + string.append("Z "); + else if (!b_export_zs && b_export_ms) + string.append("M "); + + if (position == null) { + string.append("EMPTY"); + return; + } + + string.append('('); + + multiPointText_(precision, b_export_zs, b_export_ms, zs, ms, position, + point_count, string); + + string.append(')'); + } + + static void multiPointTaggedTextFromPoint_(int precision, + boolean b_export_zs, boolean b_export_ms, double x, double y, + double z, double m, StringBuilder string) { + string.append("MULTIPOINT "); + + if (b_export_zs && b_export_ms) + string.append("ZM "); + else if (b_export_zs && !b_export_ms) + string.append("Z "); + else if (!b_export_zs && b_export_ms) + string.append("M "); + + if (NumberUtils.isNaN(x)) { + string.append("EMPTY"); + return; + } + + string.append('('); + + pointText_(precision, b_export_zs, b_export_ms, x, y, z, m, string); + + string.append(')'); + } + + static void polygonTaggedText_(int precision, boolean b_export_zs, + boolean b_export_ms, AttributeStreamOfDbl zs, + AttributeStreamOfDbl ms, AttributeStreamOfDbl position, + AttributeStreamOfInt8 path_flags, AttributeStreamOfInt32 paths, + int path_count, StringBuilder string) { + string.append("POLYGON "); + + if (b_export_zs && b_export_ms) + string.append("ZM "); + else if (b_export_zs && !b_export_ms) + string.append("Z "); + else if (!b_export_zs && b_export_ms) + string.append("M "); + + if (position == null) { + string.append("EMPTY"); + return; + } + + polygonText_(precision, b_export_zs, b_export_ms, zs, ms, position, + path_flags, paths, 0, path_count, string); + } + + static void polygonTaggedTextFromEnvelope_(int precision, + boolean b_export_zs, boolean b_export_ms, double xmin, double ymin, + double xmax, double ymax, double zmin, double zmax, double mmin, + double mmax, StringBuilder string) { + string.append("POLYGON "); + + if (b_export_zs && b_export_ms) + string.append("ZM "); + else if (b_export_zs && !b_export_ms) + string.append("Z "); + else if (!b_export_zs && b_export_ms) + string.append("M "); + + if (NumberUtils.isNaN(xmin)) { + string.append("EMPTY"); + return; + } + + writeEnvelopeAsWktPolygon_(precision, b_export_zs, b_export_ms, xmin, + ymin, xmax, ymax, zmin, zmax, mmin, mmax, string); + } + + static void lineStringTaggedText_(int precision, boolean b_export_zs, + boolean b_export_ms, AttributeStreamOfDbl zs, + AttributeStreamOfDbl ms, AttributeStreamOfDbl position, + AttributeStreamOfInt8 path_flags, AttributeStreamOfInt32 paths, + StringBuilder string) { + string.append("LINESTRING "); + + if (b_export_zs && b_export_ms) + string.append("ZM "); + else if (b_export_zs && !b_export_ms) + string.append("Z "); + else if (!b_export_zs && b_export_ms) + string.append("M "); + + if (position == null) { + string.append("EMPTY"); + return; + } + + boolean b_closed = ((path_flags.read(0) & PathFlags.enumClosed) != 0); + + lineStringText_(false, b_closed, precision, b_export_zs, b_export_ms, + zs, ms, position, paths, 0, string); + } + + static void pointTaggedText_(int precision, boolean b_export_zs, + boolean b_export_ms, double x, double y, double z, double m, + StringBuilder string) { + string.append("POINT "); + + if (b_export_zs && b_export_ms) + string.append("ZM "); + else if (b_export_zs && !b_export_ms) + string.append("Z "); + else if (!b_export_zs && b_export_ms) + string.append("M "); + + if (NumberUtils.isNaN(x)) { + string.append("EMPTY"); + return; + } + + pointText_(precision, b_export_zs, b_export_ms, x, y, z, m, string); + } + + static void pointTaggedTextFromMultiPoint_(int precision, + boolean b_export_zs, boolean b_export_ms, AttributeStreamOfDbl zs, + AttributeStreamOfDbl ms, AttributeStreamOfDbl position, + StringBuilder string) { + string.append("POINT "); + + if (b_export_zs && b_export_ms) + string.append("ZM "); + else if (b_export_zs && !b_export_ms) + string.append("Z "); + else if (!b_export_zs && b_export_ms) + string.append("M "); + + if (position == null) { + string.append("EMPTY"); + return; + } + + pointText_(precision, b_export_zs, b_export_ms, zs, ms, position, 0, + string); + } + + static void multiPolygonText_(int precision, boolean b_export_zs, + boolean b_export_ms, AttributeStreamOfDbl zs, + AttributeStreamOfDbl ms, AttributeStreamOfDbl position, + AttributeStreamOfInt8 path_flags, AttributeStreamOfInt32 paths, + int polygon_count, int path_count, StringBuilder string) { + int polygon_start = 0; + int polygon_end = 1; + + while (polygon_end < path_count + && (path_flags.read(polygon_end) & PathFlags.enumOGCStartPolygon) == 0) + polygon_end++; + + polygonText_(precision, b_export_zs, b_export_ms, zs, ms, position, + path_flags, paths, polygon_start, polygon_end, string); + + for (int ipolygon = 1; ipolygon < polygon_count; ipolygon++) { + polygon_start = polygon_end; + polygon_end++; + + while (polygon_end < path_count + && (path_flags.read(polygon_end) & PathFlags.enumOGCStartPolygon) == 0) + polygon_end++; + + string.append(", "); + polygonText_(precision, b_export_zs, b_export_ms, zs, ms, position, + path_flags, paths, polygon_start, polygon_end, string); + } + } + + static void multiLineStringText_(int precision, boolean b_export_zs, + boolean b_export_ms, AttributeStreamOfDbl zs, + AttributeStreamOfDbl ms, AttributeStreamOfDbl position, + AttributeStreamOfInt8 path_flags, AttributeStreamOfInt32 paths, + int path_count, StringBuilder string) { + boolean b_closed = ((path_flags.read(0) & PathFlags.enumClosed) != 0); + + lineStringText_(false, b_closed, precision, b_export_zs, b_export_ms, + zs, ms, position, paths, 0, string); + + for (int path = 1; path < path_count; path++) { + string.append(", "); + + b_closed = ((path_flags.read(path) & PathFlags.enumClosed) != 0); + + lineStringText_(false, b_closed, precision, b_export_zs, + b_export_ms, zs, ms, position, paths, path, string); + } + } + + static void multiPointText_(int precision, boolean b_export_zs, + boolean b_export_ms, AttributeStreamOfDbl zs, + AttributeStreamOfDbl ms, AttributeStreamOfDbl position, + int point_count, StringBuilder string) { + pointText_(precision, b_export_zs, b_export_ms, zs, ms, position, 0, + string); + + for (int point = 1; point < point_count; point++) { + string.append(", "); + pointText_(precision, b_export_zs, b_export_ms, zs, ms, position, + point, string); + } + } + + static void polygonText_(int precision, boolean b_export_zs, + boolean b_export_ms, AttributeStreamOfDbl zs, + AttributeStreamOfDbl ms, AttributeStreamOfDbl position, + AttributeStreamOfInt8 path_flags, AttributeStreamOfInt32 paths, + int polygon_start, int polygon_end, StringBuilder string) { + string.append('('); + + lineStringText_(true, true, precision, b_export_zs, b_export_ms, zs, + ms, position, paths, polygon_start, string); + + for (int path = polygon_start + 1; path < polygon_end; path++) { + string.append(", "); + lineStringText_(true, true, precision, b_export_zs, b_export_ms, + zs, ms, position, paths, path, string); + } + + string.append(')'); + } + + static void lineStringText_(boolean bRing, boolean b_closed, int precision, + boolean b_export_zs, boolean b_export_ms, AttributeStreamOfDbl zs, + AttributeStreamOfDbl ms, AttributeStreamOfDbl position, + AttributeStreamOfInt32 paths, int path, StringBuilder string) { + int istart = paths.read(path); + int iend = paths.read(path + 1); + + if (istart == iend) { + string.append("EMPTY"); + return; + } + + string.append('('); + + if (bRing) { + point_(precision, b_export_zs, b_export_ms, zs, ms, position, + istart, string); + string.append(", "); + + for (int point = iend - 1; point >= istart + 1; point--) { + point_(precision, b_export_zs, b_export_ms, zs, ms, position, + point, string); + string.append(", "); + } + + point_(precision, b_export_zs, b_export_ms, zs, ms, position, + istart, string); + } else { + for (int point = istart; point < iend - 1; point++) { + point_(precision, b_export_zs, b_export_ms, zs, ms, position, + point, string); + string.append(", "); + } + + point_(precision, b_export_zs, b_export_ms, zs, ms, position, + iend - 1, string); + + if (b_closed) { + string.append(", "); + point_(precision, b_export_zs, b_export_ms, zs, ms, position, + istart, string); + } + } + + string.append(')'); + } + + static int pointText_(int precision, boolean b_export_zs, + boolean b_export_ms, double x, double y, double z, double m, + StringBuilder string) { + string.append('('); + point_(precision, b_export_zs, b_export_ms, x, y, z, m, string); + string.append(')'); + + return 1; + } + + static void pointText_(int precision, boolean b_export_zs, + boolean b_export_ms, AttributeStreamOfDbl zs, + AttributeStreamOfDbl ms, AttributeStreamOfDbl position, int point, + StringBuilder string) { + double x = position.read(2 * point); + double y = position.read(2 * point + 1); + double z = NumberUtils.TheNaN; + double m = NumberUtils.TheNaN; + + if (b_export_zs) + z = (zs != null ? zs.read(point) : VertexDescription + .getDefaultValue(VertexDescription.Semantics.Z)); + + if (b_export_ms) + m = (ms != null ? ms.read(point) : VertexDescription + .getDefaultValue(VertexDescription.Semantics.M)); + + pointText_(precision, b_export_zs, b_export_ms, x, y, z, m, string); + } + + static void point_(int precision, boolean b_export_zs, boolean b_export_ms, + double x, double y, double z, double m, StringBuilder string) { + writeSignedNumericLiteral_(x, precision, string); + string.append(' '); + writeSignedNumericLiteral_(y, precision, string); + + if (b_export_zs) { + string.append(' '); + writeSignedNumericLiteral_(z, precision, string); + } + + if (b_export_ms) { + string.append(' '); + writeSignedNumericLiteral_(m, precision, string); + } + } + + static void point_(int precision, boolean b_export_zs, boolean b_export_ms, + AttributeStreamOfDbl zs, AttributeStreamOfDbl ms, + AttributeStreamOfDbl position, int point, StringBuilder string) { + double x = position.read(2 * point); + double y = position.read(2 * point + 1); + double z = NumberUtils.TheNaN; + double m = NumberUtils.TheNaN; + + if (b_export_zs) + z = (zs != null ? zs.read(point) : VertexDescription + .getDefaultValue(VertexDescription.Semantics.Z)); + + if (b_export_ms) + m = (ms != null ? ms.read(point) : VertexDescription + .getDefaultValue(VertexDescription.Semantics.M)); + + point_(precision, b_export_zs, b_export_ms, x, y, z, m, string); + } + + static boolean writeSignedNumericLiteral_(double v, int precision, + StringBuilder string) { + if (NumberUtils.isNaN(v)) { + string.append("NAN"); + return false; + } + + StringUtils.appendDouble(v, precision, string); + return true; + } + + static void writeEnvelopeAsWktPolygon_(int precision, boolean b_export_zs, + boolean b_export_ms, double xmin, double ymin, double xmax, + double ymax, double zmin, double zmax, double mmin, double mmax, + StringBuilder string) { + string.append("(("); + + writeSignedNumericLiteral_(xmin, precision, string); + string.append(' '); + writeSignedNumericLiteral_(ymin, precision, string); + + if (b_export_zs) { + string.append(' '); + writeSignedNumericLiteral_(zmin, precision, string); + } + + if (b_export_ms) { + string.append(' '); + writeSignedNumericLiteral_(mmin, precision, string); + } + + string.append(", "); + + writeSignedNumericLiteral_(xmax, precision, string); + string.append(' '); + writeSignedNumericLiteral_(ymin, precision, string); + + if (b_export_zs) { + string.append(' '); + writeSignedNumericLiteral_(zmax, precision, string); + } + + if (b_export_ms) { + string.append(' '); + writeSignedNumericLiteral_(mmax, precision, string); + } + + string.append(", "); + + writeSignedNumericLiteral_(xmax, precision, string); + string.append(' '); + writeSignedNumericLiteral_(ymax, precision, string); + + if (b_export_zs) { + string.append(' '); + writeSignedNumericLiteral_(zmin, precision, string); + } + + if (b_export_ms) { + string.append(' '); + writeSignedNumericLiteral_(mmin, precision, string); + } + + string.append(", "); + + writeSignedNumericLiteral_(xmin, precision, string); + string.append(' '); + writeSignedNumericLiteral_(ymax, precision, string); + + if (b_export_zs) { + string.append(' '); + writeSignedNumericLiteral_(zmax, precision, string); + } + + if (b_export_ms) { + string.append(' '); + writeSignedNumericLiteral_(mmax, precision, string); + } + + string.append(", "); + + writeSignedNumericLiteral_(xmin, precision, string); + string.append(' '); + writeSignedNumericLiteral_(ymin, precision, string); + + if (b_export_zs) { + string.append(' '); + writeSignedNumericLiteral_(zmin, precision, string); + } + + if (b_export_ms) { + string.append(' '); + writeSignedNumericLiteral_(mmin, precision, string); + } + + string.append("))"); + } } diff --git a/src/main/java/com/esri/core/geometry/OperatorFactory.java b/src/main/java/com/esri/core/geometry/OperatorFactory.java index 673a9af0..745c5b97 100644 --- a/src/main/java/com/esri/core/geometry/OperatorFactory.java +++ b/src/main/java/com/esri/core/geometry/OperatorFactory.java @@ -31,13 +31,13 @@ * An abstract class that represent the basic OperatorFactory interface. */ public abstract class OperatorFactory { - /** - * Returns True if the given operator exists. The type is one of the Operator::Type values or a user defined value. - */ - public abstract boolean isOperatorSupported(Type type); - - /** - * Returns an operator of the given type. Throws an exception if the operator is not supported. - */ - public abstract Operator getOperator(Type type); + /** + * Returns True if the given operator exists. The type is one of the Operator::Type values or a user defined value. + */ + public abstract boolean isOperatorSupported(Type type); + + /** + * Returns an operator of the given type. Throws an exception if the operator is not supported. + */ + public abstract Operator getOperator(Type type); } diff --git a/src/main/java/com/esri/core/geometry/OperatorFactoryLocal.java b/src/main/java/com/esri/core/geometry/OperatorFactoryLocal.java index b7678ffb..6cc5c4fc 100644 --- a/src/main/java/com/esri/core/geometry/OperatorFactoryLocal.java +++ b/src/main/java/com/esri/core/geometry/OperatorFactoryLocal.java @@ -42,238 +42,238 @@ * An abstract class that represent the basic OperatorFactory interface. */ public class OperatorFactoryLocal extends OperatorFactory { - private static final OperatorFactoryLocal INSTANCE = new OperatorFactoryLocal(); + private static final OperatorFactoryLocal INSTANCE = new OperatorFactoryLocal(); - private static final HashMap st_supportedOperators = new HashMap(); + private static final HashMap st_supportedOperators = new HashMap(); - static { - // Register all implemented operator allocators in the dictionary + static { + // Register all implemented operator allocators in the dictionary - st_supportedOperators.put(Type.Project, new OperatorProjectLocal()); - st_supportedOperators.put(Type.ExportToJson, new OperatorExportToJsonLocal()); - st_supportedOperators.put(Type.ImportFromJson, new OperatorImportFromJsonLocal()); + st_supportedOperators.put(Type.Project, new OperatorProjectLocal()); + st_supportedOperators.put(Type.ExportToJson, new OperatorExportToJsonLocal()); + st_supportedOperators.put(Type.ImportFromJson, new OperatorImportFromJsonLocal()); // st_supportedOperators.put(Type.ImportMapGeometryFromJson, new OperatorImportFromJsonLocal()); - st_supportedOperators.put(Type.ExportToESRIShape, new OperatorExportToESRIShapeLocal()); - st_supportedOperators.put(Type.ImportFromESRIShape, new OperatorImportFromESRIShapeLocal()); - - st_supportedOperators.put(Type.Proximity2D, new OperatorProximity2DLocal()); - st_supportedOperators.put(Type.DensifyByLength, new OperatorDensifyByLengthLocal()); - - st_supportedOperators.put(Type.Relate, new OperatorRelateLocal()); - st_supportedOperators.put(Type.Equals, new OperatorEqualsLocal()); - st_supportedOperators.put(Type.Disjoint, new OperatorDisjointLocal()); - - st_supportedOperators.put(Type.Intersects, new OperatorIntersectsLocal()); - st_supportedOperators.put(Type.Within, new OperatorWithinLocal()); - st_supportedOperators.put(Type.Contains, new OperatorContainsLocal()); - st_supportedOperators.put(Type.Crosses, new OperatorCrossesLocal()); - st_supportedOperators.put(Type.Touches, new OperatorTouchesLocal()); - st_supportedOperators.put(Type.Overlaps, new OperatorOverlapsLocal()); - - st_supportedOperators.put(Type.SimplifyOGC, new OperatorSimplifyLocalOGC()); - st_supportedOperators.put(Type.Simplify, new OperatorSimplifyLocal()); - st_supportedOperators.put(Type.Offset, new OperatorOffsetLocal()); - - st_supportedOperators.put(Type.GeodeticDensifyByLength, new OperatorGeodeticDensifyLocal()); - - st_supportedOperators.put(Type.ShapePreservingDensify, new OperatorShapePreservingDensifyLocal()); - - st_supportedOperators.put(Type.GeodesicBuffer, new OperatorGeodesicBufferLocal()); - - st_supportedOperators.put(Type.GeodeticLength, new OperatorGeodeticLengthLocal()); - st_supportedOperators.put(Type.GeodeticArea, new OperatorGeodeticAreaLocal()); - - st_supportedOperators.put(Type.Buffer, new OperatorBufferLocal()); - st_supportedOperators.put(Type.Distance, new OperatorDistanceLocal()); - st_supportedOperators.put(Type.Intersection, new OperatorIntersectionLocal()); - st_supportedOperators.put(Type.Difference, new OperatorDifferenceLocal()); - st_supportedOperators.put(Type.SymmetricDifference, new OperatorSymmetricDifferenceLocal()); - st_supportedOperators.put(Type.Clip, new OperatorClipLocal()); - st_supportedOperators.put(Type.Cut, new OperatorCutLocal()); - st_supportedOperators.put(Type.ExportToWkb, new OperatorExportToWkbLocal()); - st_supportedOperators.put(Type.ImportFromWkb, new OperatorImportFromWkbLocal()); - st_supportedOperators.put(Type.ExportToWkt, new OperatorExportToWktLocal()); - st_supportedOperators.put(Type.ImportFromWkt, new OperatorImportFromWktLocal()); - st_supportedOperators.put(Type.ImportFromGeoJson, new OperatorImportFromGeoJsonLocal()); - st_supportedOperators.put(Type.ExportToGeoJson, new OperatorExportToGeoJsonLocal()); - st_supportedOperators.put(Type.Union, new OperatorUnionLocal()); - st_supportedOperators.put(Type.GeneralizeByArea, new OperatorGeneralizeByAreaLocal()); - st_supportedOperators.put(Type.Generalize, new OperatorGeneralizeLocal()); - st_supportedOperators.put(Type.ConvexHull, new OperatorConvexHullLocal()); - st_supportedOperators.put(Type.Boundary, new OperatorBoundaryLocal()); - - st_supportedOperators.put(Type.RandomPoints, new OperatorRandomPointsLocal()); - st_supportedOperators.put(Type.EnclosingCircle, new OperatorEnclosingCircleLocal()); - st_supportedOperators.put(Type.GeodeticInverse, new OperatorGeodeticInverseLocal()); - - // LabelPoint, - not ported - - } - - private OperatorFactoryLocal() { - - } - - - /** - * Returns a reference to the singleton. - */ - public static OperatorFactoryLocal getInstance() { - return INSTANCE; - } - - @Override - public Operator getOperator(Type type) { - if (st_supportedOperators.containsKey(type)) { - return st_supportedOperators.get(type); - } else { - throw new IllegalArgumentException(); - } - } - - @Override - public boolean isOperatorSupported(Operator.Type type) { - return st_supportedOperators.containsKey(type); - } - - public static void saveJSONToTextFileDbg(String file_name, - Geometry geometry, - SpatialReference spatial_ref) { - if (file_name == null) { - throw new IllegalArgumentException(); - } - - OperatorFactoryLocal engine = OperatorFactoryLocal.getInstance(); - OperatorExportToJson exporterJSON = (OperatorExportToJson) engine - .getOperator(Operator.Type.ExportToJson); - String jsonString = exporterJSON.execute(spatial_ref, geometry); - - try { - FileOutputStream outfile = new FileOutputStream(file_name); - PrintStream p = new PrintStream(outfile); - p.print(jsonString); - p.close(); - } catch (Exception ex) { - } - } - - public static MapGeometry loadGeometryFromJSONFileDbg(String file_name) { - if (file_name == null) { - throw new IllegalArgumentException(); - } - - String jsonString = null; - try { - FileInputStream stream = new FileInputStream(file_name); - Reader reader = new BufferedReader(new InputStreamReader(stream)); - StringBuilder builder = new StringBuilder(); - char[] buffer = new char[8192]; - int read; - while ((read = reader.read(buffer, 0, buffer.length)) > 0) { - builder.append(buffer, 0, read); - } - stream.close(); - - jsonString = builder.toString(); - } catch (Exception ex) { - } - - MapGeometry mapGeom = OperatorImportFromJson.local().execute(Geometry.Type.Unknown, jsonString); - return mapGeom; - } - - public static MapGeometry loadGeometryFromJSONStringDbg(String json) { - if (json == null) { - throw new IllegalArgumentException(); - } - - MapGeometry mapGeom = null; - try { - mapGeom = OperatorImportFromJson.local().execute(Geometry.Type.Unknown, json); - } catch (Exception e) { - throw new IllegalArgumentException(e.toString()); - } - return mapGeom; - } - - public static Geometry loadGeometryFromEsriShapeDbg(String file_name) { - if (file_name == null) { - throw new IllegalArgumentException(); - } - - try { - FileInputStream stream = new FileInputStream(file_name); - FileChannel fchan = stream.getChannel(); - ByteBuffer bb = ByteBuffer.allocate((int) fchan.size()); - fchan.read(bb); - bb.order(ByteOrder.LITTLE_ENDIAN); - Geometry g = OperatorImportFromESRIShape.local().execute(0, - Geometry.Type.Unknown, bb); - fchan.close(); - stream.close(); - return g; - } catch (Exception ex) { - throw new IllegalArgumentException(); - } - } - - public static void saveGeometryToEsriShapeDbg(String file_name, Geometry geometry) { - if (file_name == null) { - throw new IllegalArgumentException(); - } - - try { - ByteBuffer bb = OperatorExportToESRIShape.local().execute(0, geometry); - FileOutputStream outfile = new FileOutputStream(file_name); - FileChannel fchan = outfile.getChannel(); - fchan.write(bb); - fchan.close(); - outfile.close(); - } catch (Exception ex) { - throw new IllegalArgumentException(); - } - } - - public static void saveToWKTFileDbg(String file_name, - Geometry geometry, SpatialReference spatial_ref) { - if (file_name == null) { - throw new IllegalArgumentException(); - } - - String jsonString = OperatorExportToWkt.local().execute(0, geometry, null); - - try { - FileOutputStream outfile = new FileOutputStream(file_name); - PrintStream p = new PrintStream(outfile); - p.print(jsonString); - p.close(); - } catch (Exception ex) { - } - } - - public static Geometry loadGeometryFromWKTFileDbg(String file_name) { - if (file_name == null) { - throw new IllegalArgumentException(); - } - - String s = null; - try { - FileInputStream stream = new FileInputStream(file_name); - Reader reader = new BufferedReader(new InputStreamReader(stream)); - StringBuilder builder = new StringBuilder(); - char[] buffer = new char[8192]; - int read; - while ((read = reader.read(buffer, 0, buffer.length)) > 0) { - builder.append(buffer, 0, read); - } - stream.close(); - - s = builder.toString(); - } catch (Exception ex) { - } - - return OperatorImportFromWkt.local().execute(0, Geometry.Type.Unknown, s, null); - } + st_supportedOperators.put(Type.ExportToESRIShape, new OperatorExportToESRIShapeLocal()); + st_supportedOperators.put(Type.ImportFromESRIShape, new OperatorImportFromESRIShapeLocal()); + + st_supportedOperators.put(Type.Proximity2D, new OperatorProximity2DLocal()); + st_supportedOperators.put(Type.DensifyByLength, new OperatorDensifyByLengthLocal()); + + st_supportedOperators.put(Type.Relate, new OperatorRelateLocal()); + st_supportedOperators.put(Type.Equals, new OperatorEqualsLocal()); + st_supportedOperators.put(Type.Disjoint, new OperatorDisjointLocal()); + + st_supportedOperators.put(Type.Intersects, new OperatorIntersectsLocal()); + st_supportedOperators.put(Type.Within, new OperatorWithinLocal()); + st_supportedOperators.put(Type.Contains, new OperatorContainsLocal()); + st_supportedOperators.put(Type.Crosses, new OperatorCrossesLocal()); + st_supportedOperators.put(Type.Touches, new OperatorTouchesLocal()); + st_supportedOperators.put(Type.Overlaps, new OperatorOverlapsLocal()); + + st_supportedOperators.put(Type.SimplifyOGC, new OperatorSimplifyLocalOGC()); + st_supportedOperators.put(Type.Simplify, new OperatorSimplifyLocal()); + st_supportedOperators.put(Type.Offset, new OperatorOffsetLocal()); + + st_supportedOperators.put(Type.GeodeticDensifyByLength, new OperatorGeodeticDensifyLocal()); + + st_supportedOperators.put(Type.ShapePreservingDensify, new OperatorShapePreservingDensifyLocal()); + + st_supportedOperators.put(Type.GeodesicBuffer, new OperatorGeodesicBufferLocal()); + + st_supportedOperators.put(Type.GeodeticLength, new OperatorGeodeticLengthLocal()); + st_supportedOperators.put(Type.GeodeticArea, new OperatorGeodeticAreaLocal()); + + st_supportedOperators.put(Type.Buffer, new OperatorBufferLocal()); + st_supportedOperators.put(Type.Distance, new OperatorDistanceLocal()); + st_supportedOperators.put(Type.Intersection, new OperatorIntersectionLocal()); + st_supportedOperators.put(Type.Difference, new OperatorDifferenceLocal()); + st_supportedOperators.put(Type.SymmetricDifference, new OperatorSymmetricDifferenceLocal()); + st_supportedOperators.put(Type.Clip, new OperatorClipLocal()); + st_supportedOperators.put(Type.Cut, new OperatorCutLocal()); + st_supportedOperators.put(Type.ExportToWkb, new OperatorExportToWkbLocal()); + st_supportedOperators.put(Type.ImportFromWkb, new OperatorImportFromWkbLocal()); + st_supportedOperators.put(Type.ExportToWkt, new OperatorExportToWktLocal()); + st_supportedOperators.put(Type.ImportFromWkt, new OperatorImportFromWktLocal()); + st_supportedOperators.put(Type.ImportFromGeoJson, new OperatorImportFromGeoJsonLocal()); + st_supportedOperators.put(Type.ExportToGeoJson, new OperatorExportToGeoJsonLocal()); + st_supportedOperators.put(Type.Union, new OperatorUnionLocal()); + st_supportedOperators.put(Type.GeneralizeByArea, new OperatorGeneralizeByAreaLocal()); + st_supportedOperators.put(Type.Generalize, new OperatorGeneralizeLocal()); + st_supportedOperators.put(Type.ConvexHull, new OperatorConvexHullLocal()); + st_supportedOperators.put(Type.Boundary, new OperatorBoundaryLocal()); + + st_supportedOperators.put(Type.RandomPoints, new OperatorRandomPointsLocal()); + st_supportedOperators.put(Type.EnclosingCircle, new OperatorEnclosingCircleLocal()); + st_supportedOperators.put(Type.GeodeticInverse, new OperatorGeodeticInverseLocal()); + + // LabelPoint, - not ported + + } + + private OperatorFactoryLocal() { + + } + + + /** + * Returns a reference to the singleton. + */ + public static OperatorFactoryLocal getInstance() { + return INSTANCE; + } + + @Override + public Operator getOperator(Type type) { + if (st_supportedOperators.containsKey(type)) { + return st_supportedOperators.get(type); + } else { + throw new IllegalArgumentException(); + } + } + + @Override + public boolean isOperatorSupported(Operator.Type type) { + return st_supportedOperators.containsKey(type); + } + + public static void saveJSONToTextFileDbg(String file_name, + Geometry geometry, + SpatialReference spatial_ref) { + if (file_name == null) { + throw new IllegalArgumentException(); + } + + OperatorFactoryLocal engine = OperatorFactoryLocal.getInstance(); + OperatorExportToJson exporterJSON = (OperatorExportToJson) engine + .getOperator(Operator.Type.ExportToJson); + String jsonString = exporterJSON.execute(spatial_ref, geometry); + + try { + FileOutputStream outfile = new FileOutputStream(file_name); + PrintStream p = new PrintStream(outfile); + p.print(jsonString); + p.close(); + } catch (Exception ex) { + } + } + + public static MapGeometry loadGeometryFromJSONFileDbg(String file_name) { + if (file_name == null) { + throw new IllegalArgumentException(); + } + + String jsonString = null; + try { + FileInputStream stream = new FileInputStream(file_name); + Reader reader = new BufferedReader(new InputStreamReader(stream)); + StringBuilder builder = new StringBuilder(); + char[] buffer = new char[8192]; + int read; + while ((read = reader.read(buffer, 0, buffer.length)) > 0) { + builder.append(buffer, 0, read); + } + stream.close(); + + jsonString = builder.toString(); + } catch (Exception ex) { + } + + MapGeometry mapGeom = OperatorImportFromJson.local().execute(Geometry.Type.Unknown, jsonString); + return mapGeom; + } + + public static MapGeometry loadGeometryFromJSONStringDbg(String json) { + if (json == null) { + throw new IllegalArgumentException(); + } + + MapGeometry mapGeom = null; + try { + mapGeom = OperatorImportFromJson.local().execute(Geometry.Type.Unknown, json); + } catch (Exception e) { + throw new IllegalArgumentException(e.toString()); + } + return mapGeom; + } + + public static Geometry loadGeometryFromEsriShapeDbg(String file_name) { + if (file_name == null) { + throw new IllegalArgumentException(); + } + + try { + FileInputStream stream = new FileInputStream(file_name); + FileChannel fchan = stream.getChannel(); + ByteBuffer bb = ByteBuffer.allocate((int) fchan.size()); + fchan.read(bb); + bb.order(ByteOrder.LITTLE_ENDIAN); + Geometry g = OperatorImportFromESRIShape.local().execute(0, + Geometry.Type.Unknown, bb); + fchan.close(); + stream.close(); + return g; + } catch (Exception ex) { + throw new IllegalArgumentException(); + } + } + + public static void saveGeometryToEsriShapeDbg(String file_name, Geometry geometry) { + if (file_name == null) { + throw new IllegalArgumentException(); + } + + try { + ByteBuffer bb = OperatorExportToESRIShape.local().execute(0, geometry); + FileOutputStream outfile = new FileOutputStream(file_name); + FileChannel fchan = outfile.getChannel(); + fchan.write(bb); + fchan.close(); + outfile.close(); + } catch (Exception ex) { + throw new IllegalArgumentException(); + } + } + + public static void saveToWKTFileDbg(String file_name, + Geometry geometry, SpatialReference spatial_ref) { + if (file_name == null) { + throw new IllegalArgumentException(); + } + + String jsonString = OperatorExportToWkt.local().execute(0, geometry, null); + + try { + FileOutputStream outfile = new FileOutputStream(file_name); + PrintStream p = new PrintStream(outfile); + p.print(jsonString); + p.close(); + } catch (Exception ex) { + } + } + + public static Geometry loadGeometryFromWKTFileDbg(String file_name) { + if (file_name == null) { + throw new IllegalArgumentException(); + } + + String s = null; + try { + FileInputStream stream = new FileInputStream(file_name); + Reader reader = new BufferedReader(new InputStreamReader(stream)); + StringBuilder builder = new StringBuilder(); + char[] buffer = new char[8192]; + int read; + while ((read = reader.read(buffer, 0, buffer.length)) > 0) { + builder.append(buffer, 0, read); + } + stream.close(); + + s = builder.toString(); + } catch (Exception ex) { + } + + return OperatorImportFromWkt.local().execute(0, Geometry.Type.Unknown, s, null); + } } diff --git a/src/main/java/com/esri/core/geometry/OperatorGeneralize.java b/src/main/java/com/esri/core/geometry/OperatorGeneralize.java index d3a23fcb..15528321 100644 --- a/src/main/java/com/esri/core/geometry/OperatorGeneralize.java +++ b/src/main/java/com/esri/core/geometry/OperatorGeneralize.java @@ -27,30 +27,30 @@ * Generalizes geometries using Douglas-Peucker algorithm. */ public abstract class OperatorGeneralize extends Operator { - @Override - public Type getType() { - return Type.Generalize; - } - - /** - * Performs the Generalize operation on a geometry set. Point and - * multipoint geometries are left unchanged. An envelope is converted to a - * polygon. - */ - public abstract GeometryCursor execute(GeometryCursor geoms, - double maxDeviation, boolean bRemoveDegenerateParts, - ProgressTracker progressTracker); - - /** - * Performs the Generalize operation on a single geometry. Point and - * multipoint geometries are left unchanged. An envelope is converted to a - * polygon. - */ - public abstract Geometry execute(Geometry geom, double maxDeviation, - boolean bRemoveDegenerateParts, ProgressTracker progressTracker); - - public static OperatorGeneralize local() { - return (OperatorGeneralize) OperatorFactoryLocal.getInstance().getOperator(Type.Generalize); - } + @Override + public Type getType() { + return Type.Generalize; + } + + /** + * Performs the Generalize operation on a geometry set. Point and + * multipoint geometries are left unchanged. An envelope is converted to a + * polygon. + */ + public abstract GeometryCursor execute(GeometryCursor geoms, + double maxDeviation, boolean bRemoveDegenerateParts, + ProgressTracker progressTracker); + + /** + * Performs the Generalize operation on a single geometry. Point and + * multipoint geometries are left unchanged. An envelope is converted to a + * polygon. + */ + public abstract Geometry execute(Geometry geom, double maxDeviation, + boolean bRemoveDegenerateParts, ProgressTracker progressTracker); + + public static OperatorGeneralize local() { + return (OperatorGeneralize) OperatorFactoryLocal.getInstance().getOperator(Type.Generalize); + } } diff --git a/src/main/java/com/esri/core/geometry/OperatorGeneralizeByArea.java b/src/main/java/com/esri/core/geometry/OperatorGeneralizeByArea.java index e5a38acc..f34db8b4 100644 --- a/src/main/java/com/esri/core/geometry/OperatorGeneralizeByArea.java +++ b/src/main/java/com/esri/core/geometry/OperatorGeneralizeByArea.java @@ -5,60 +5,60 @@ * Generalizes geometries using Visvalingam algorithm */ public abstract class OperatorGeneralizeByArea extends Operator { - @Override - public Operator.Type getType() { - return Operator.Type.GeneralizeByArea; - } + @Override + public Operator.Type getType() { + return Operator.Type.GeneralizeByArea; + } - /** - * Performs the Generalize operation on a geometry set. Point and - * multipoint geometries are left unchanged. An envelope is converted to a - * polygon. - */ - public abstract GeometryCursor execute(GeometryCursor geoms, - double percentReduction, - boolean bRemoveDegenerateParts, - GeneralizeType generalizeType, - SpatialReference spatialReference, - ProgressTracker progressTracker); + /** + * Performs the Generalize operation on a geometry set. Point and + * multipoint geometries are left unchanged. An envelope is converted to a + * polygon. + */ + public abstract GeometryCursor execute(GeometryCursor geoms, + double percentReduction, + boolean bRemoveDegenerateParts, + GeneralizeType generalizeType, + SpatialReference spatialReference, + ProgressTracker progressTracker); - /** - * Performs the Generalize operation on a geometry set. Point and - * multipoint geometries are left unchanged. An envelope is converted to a - * polygon. - */ - public abstract GeometryCursor execute(GeometryCursor geoms, - boolean bRemoveDegenerateParts, - int maxPointCount, - GeneralizeType generalizeType, - SpatialReference spatialReference, - ProgressTracker progressTracker); + /** + * Performs the Generalize operation on a geometry set. Point and + * multipoint geometries are left unchanged. An envelope is converted to a + * polygon. + */ + public abstract GeometryCursor execute(GeometryCursor geoms, + boolean bRemoveDegenerateParts, + int maxPointCount, + GeneralizeType generalizeType, + SpatialReference spatialReference, + ProgressTracker progressTracker); - /** - * Performs the Generalize operation on a single geometry. Point and - * multipoint geometries are left unchanged. An envelope is converted to a - * polygon. - */ - public abstract Geometry execute(Geometry geom, - double percentReduction, - boolean bRemoveDegenerateParts, - GeneralizeType generalizeType, - SpatialReference spatialReference, - ProgressTracker progressTracker); + /** + * Performs the Generalize operation on a single geometry. Point and + * multipoint geometries are left unchanged. An envelope is converted to a + * polygon. + */ + public abstract Geometry execute(Geometry geom, + double percentReduction, + boolean bRemoveDegenerateParts, + GeneralizeType generalizeType, + SpatialReference spatialReference, + ProgressTracker progressTracker); - /** - * Performs the Generalize operation on a single geometry. Point and - * multipoint geometries are left unchanged. An envelope is converted to a - * polygon. - */ - public abstract Geometry execute(Geometry geom, - boolean bRemoveDegenerateParts, - int maxPointCount, - GeneralizeType generalizeType, - SpatialReference spatialReference, - ProgressTracker progressTracker); + /** + * Performs the Generalize operation on a single geometry. Point and + * multipoint geometries are left unchanged. An envelope is converted to a + * polygon. + */ + public abstract Geometry execute(Geometry geom, + boolean bRemoveDegenerateParts, + int maxPointCount, + GeneralizeType generalizeType, + SpatialReference spatialReference, + ProgressTracker progressTracker); - public static OperatorGeneralizeByArea local() { - return (OperatorGeneralizeByArea) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.GeneralizeByArea); - } + public static OperatorGeneralizeByArea local() { + return (OperatorGeneralizeByArea) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.GeneralizeByArea); + } } diff --git a/src/main/java/com/esri/core/geometry/OperatorGeneralizeByAreaCursor.java b/src/main/java/com/esri/core/geometry/OperatorGeneralizeByAreaCursor.java index 5411b2f6..94e56e53 100644 --- a/src/main/java/com/esri/core/geometry/OperatorGeneralizeByAreaCursor.java +++ b/src/main/java/com/esri/core/geometry/OperatorGeneralizeByAreaCursor.java @@ -8,151 +8,151 @@ * Created by davidraleigh on 4/17/16. */ public class OperatorGeneralizeByAreaCursor extends GeometryCursor { - ProgressTracker m_progressTracker; - boolean m_bRemoveDegenerateParts; - GeneralizeType m_generalizeType; - double m_percentReduction; - SpatialReference m_spatialReference; - int m_maxPointCount; - - public OperatorGeneralizeByAreaCursor(GeometryCursor geoms, - double percentReduction, - boolean bRemoveDegenerateParts, - GeneralizeType generalizeType, - SpatialReference spatialReference, - ProgressTracker progressTracker) { - m_inputGeoms = geoms; - m_progressTracker = progressTracker; - m_bRemoveDegenerateParts = bRemoveDegenerateParts; - m_generalizeType = generalizeType; - m_percentReduction = percentReduction; - m_spatialReference = spatialReference; - - m_maxPointCount = 0; - } - - public OperatorGeneralizeByAreaCursor(GeometryCursor geoms, - boolean bRemoveDegenerateParts, - int maxPointCount, - GeneralizeType generalizeType, - SpatialReference spatialReference, - ProgressTracker progressTracker) { - m_inputGeoms = geoms; - m_progressTracker = progressTracker; - m_bRemoveDegenerateParts = bRemoveDegenerateParts; - m_generalizeType = generalizeType; - m_spatialReference = spatialReference; - - m_maxPointCount = maxPointCount; - } - - @Override - public Geometry next() { - if (hasNext()) - return GeneralizeArea(m_inputGeoms.next()); - - return null; - } - - private Geometry GeneralizeArea(Geometry geom) { - Geometry.Type gt = geom.getType(); - - if (Geometry.isPoint(gt.value())) - return geom; - - if (gt == Geometry.Type.Envelope) { - Polygon poly = new Polygon(geom.getDescription()); - poly.addEnvelope((Envelope) geom, false); - return GeneralizeArea(poly); - } - - if (geom.isEmpty()) - return geom; - - if (m_maxPointCount > 0) { - int pointCount = ((MultiVertexGeometry)geom).getPointCount(); - m_percentReduction = 100 - 100.0 * ((double)m_maxPointCount) / ((double)pointCount); - } - - EditShape editShape = new EditShape(); - editShape.addGeometry(geom); - - GeneralizeAreaPath(editShape); - - // TODO this simplify is a cheat. maybe there's a better way for making sure our geometry isn't screwed up. - return GeometryEngine.simplify(editShape.getGeometry(editShape.getFirstGeometry()), m_spatialReference); - } - - - private void GeneralizeAreaPath(EditShape editShape) { - - Treap treap = new Treap(); - GeneralizeComparator areaComparator = new GeneralizeComparator(editShape, m_generalizeType); - treap.disableBalancing(); - treap.setComparator(areaComparator); - - // TODO fix this. path removal stuff. It's a messy solution to the whole treap cleanup problem - - - for (int iGeometry = editShape.getFirstGeometry(); iGeometry != -1; iGeometry = editShape.getNextGeometry(iGeometry)) { - for (int iPath = editShape.getFirstPath(iGeometry); iPath != -1; iPath = editShape.getNextPath(iPath)) { - int n = editShape.getPathSize(iPath); - treap.setCapacity(n); - int ptCountToRemove = (int)Math.ceil(n * m_percentReduction / 100.0); - - // if there are points that will remain after removals, then first create the treap - int iVertex = editShape.getFirstVertex(iPath); - areaComparator.setPathCount(n * 5); - for (int i = 0; i < n; iVertex = editShape.getNextVertex(iVertex), i++) { - treap.addElement(iVertex, -1); - } - - - while (0 < ptCountToRemove-- && treap.size(-1) > 0) { - - int vertexNode = treap.getFirst(-1); - int vertexElm = treap.getElement(vertexNode); - - GeneralizeComparator.EditShapeTriangle triangle = areaComparator.tryGetCachedTriangle_(vertexElm); - if (triangle == null) { - triangle = areaComparator.tryCreateCachedTriangle_(vertexElm); - if (triangle == null) { - triangle = areaComparator.createTriangle(vertexElm); - } - } - - if ((m_generalizeType == GeneralizeType.ResultContainsOriginal && triangle.queryOrientation() < 0) || - (m_generalizeType == GeneralizeType.ResultWithinOriginal && triangle.queryOrientation() > 0)) { - break; - } - - - if (treap.size(-1) == 1) { - treap.deleteNode(vertexNode, -1); - editShape.removeVertex(vertexElm, false); - } else { - int prevElement = triangle.m_prevVertexIndex; - int nextElement = triangle.m_nextVertexIndex; - - int prevNodeIndex = treap.search(prevElement, -1); - int nextNodeIndex = treap.search(nextElement, -1); + ProgressTracker m_progressTracker; + boolean m_bRemoveDegenerateParts; + GeneralizeType m_generalizeType; + double m_percentReduction; + SpatialReference m_spatialReference; + int m_maxPointCount; + + public OperatorGeneralizeByAreaCursor(GeometryCursor geoms, + double percentReduction, + boolean bRemoveDegenerateParts, + GeneralizeType generalizeType, + SpatialReference spatialReference, + ProgressTracker progressTracker) { + m_inputGeoms = geoms; + m_progressTracker = progressTracker; + m_bRemoveDegenerateParts = bRemoveDegenerateParts; + m_generalizeType = generalizeType; + m_percentReduction = percentReduction; + m_spatialReference = spatialReference; + + m_maxPointCount = 0; + } + + public OperatorGeneralizeByAreaCursor(GeometryCursor geoms, + boolean bRemoveDegenerateParts, + int maxPointCount, + GeneralizeType generalizeType, + SpatialReference spatialReference, + ProgressTracker progressTracker) { + m_inputGeoms = geoms; + m_progressTracker = progressTracker; + m_bRemoveDegenerateParts = bRemoveDegenerateParts; + m_generalizeType = generalizeType; + m_spatialReference = spatialReference; + + m_maxPointCount = maxPointCount; + } + + @Override + public Geometry next() { + if (hasNext()) + return GeneralizeArea(m_inputGeoms.next()); + + return null; + } + + private Geometry GeneralizeArea(Geometry geom) { + Geometry.Type gt = geom.getType(); + + if (Geometry.isPoint(gt.value())) + return geom; + + if (gt == Geometry.Type.Envelope) { + Polygon poly = new Polygon(geom.getDescription()); + poly.addEnvelope((Envelope) geom, false); + return GeneralizeArea(poly); + } + + if (geom.isEmpty()) + return geom; + + if (m_maxPointCount > 0) { + int pointCount = ((MultiVertexGeometry) geom).getPointCount(); + m_percentReduction = 100 - 100.0 * ((double) m_maxPointCount) / ((double) pointCount); + } + + EditShape editShape = new EditShape(); + editShape.addGeometry(geom); + + GeneralizeAreaPath(editShape); + + // TODO this simplify is a cheat. maybe there's a better way for making sure our geometry isn't screwed up. + return GeometryEngine.simplify(editShape.getGeometry(editShape.getFirstGeometry()), m_spatialReference); + } + + + private void GeneralizeAreaPath(EditShape editShape) { + + Treap treap = new Treap(); + GeneralizeComparator areaComparator = new GeneralizeComparator(editShape, m_generalizeType); + treap.disableBalancing(); + treap.setComparator(areaComparator); + + // TODO fix this. path removal stuff. It's a messy solution to the whole treap cleanup problem + + + for (int iGeometry = editShape.getFirstGeometry(); iGeometry != -1; iGeometry = editShape.getNextGeometry(iGeometry)) { + for (int iPath = editShape.getFirstPath(iGeometry); iPath != -1; iPath = editShape.getNextPath(iPath)) { + int n = editShape.getPathSize(iPath); + treap.setCapacity(n); + int ptCountToRemove = (int) Math.ceil(n * m_percentReduction / 100.0); + + // if there are points that will remain after removals, then first create the treap + int iVertex = editShape.getFirstVertex(iPath); + areaComparator.setPathCount(n * 5); + for (int i = 0; i < n; iVertex = editShape.getNextVertex(iVertex), i++) { + treap.addElement(iVertex, -1); + } + + + while (0 < ptCountToRemove-- && treap.size(-1) > 0) { + + int vertexNode = treap.getFirst(-1); + int vertexElm = treap.getElement(vertexNode); + + GeneralizeComparator.EditShapeTriangle triangle = areaComparator.tryGetCachedTriangle_(vertexElm); + if (triangle == null) { + triangle = areaComparator.tryCreateCachedTriangle_(vertexElm); + if (triangle == null) { + triangle = areaComparator.createTriangle(vertexElm); + } + } + + if ((m_generalizeType == GeneralizeType.ResultContainsOriginal && triangle.queryOrientation() < 0) || + (m_generalizeType == GeneralizeType.ResultWithinOriginal && triangle.queryOrientation() > 0)) { + break; + } + + + if (treap.size(-1) == 1) { + treap.deleteNode(vertexNode, -1); + editShape.removeVertex(vertexElm, false); + } else { + int prevElement = triangle.m_prevVertexIndex; + int nextElement = triangle.m_nextVertexIndex; + + int prevNodeIndex = treap.search(prevElement, -1); + int nextNodeIndex = treap.search(nextElement, -1); - if (prevNodeIndex > -1) - treap.deleteNode(prevNodeIndex, -1); - if (nextNodeIndex > -1) - treap.deleteNode(nextNodeIndex, -1); + if (prevNodeIndex > -1) + treap.deleteNode(prevNodeIndex, -1); + if (nextNodeIndex > -1) + treap.deleteNode(nextNodeIndex, -1); - treap.deleteNode(vertexNode, -1); - editShape.removeVertex(vertexElm, false); + treap.deleteNode(vertexNode, -1); + editShape.removeVertex(vertexElm, false); - if (prevNodeIndex > -1) - treap.addElement(prevElement, -1); - if (nextNodeIndex > -1) - treap.addElement(nextElement, -1); - } - } - treap.clear(); - } - } - } + if (prevNodeIndex > -1) + treap.addElement(prevElement, -1); + if (nextNodeIndex > -1) + treap.addElement(nextElement, -1); + } + } + treap.clear(); + } + } + } } diff --git a/src/main/java/com/esri/core/geometry/OperatorGeneralizeByAreaLocal.java b/src/main/java/com/esri/core/geometry/OperatorGeneralizeByAreaLocal.java index ede6f969..7056ac85 100644 --- a/src/main/java/com/esri/core/geometry/OperatorGeneralizeByAreaLocal.java +++ b/src/main/java/com/esri/core/geometry/OperatorGeneralizeByAreaLocal.java @@ -5,75 +5,75 @@ */ final public class OperatorGeneralizeByAreaLocal extends OperatorGeneralizeByArea { - @Override - public GeometryCursor execute(GeometryCursor geoms, - double percentReduction, - boolean bRemoveDegenerateParts, - GeneralizeType generalizeType, - SpatialReference spatialReference, - ProgressTracker progressTracker) { + @Override + public GeometryCursor execute(GeometryCursor geoms, + double percentReduction, + boolean bRemoveDegenerateParts, + GeneralizeType generalizeType, + SpatialReference spatialReference, + ProgressTracker progressTracker) { - return new OperatorGeneralizeByAreaCursor(geoms, - percentReduction, - bRemoveDegenerateParts, - generalizeType, - spatialReference, - progressTracker); - } + return new OperatorGeneralizeByAreaCursor(geoms, + percentReduction, + bRemoveDegenerateParts, + generalizeType, + spatialReference, + progressTracker); + } - @Override - public GeometryCursor execute(GeometryCursor geoms, - boolean bRemoveDegenerateParts, - int maxPointCount, - GeneralizeType generalizeType, - SpatialReference spatialReference, - ProgressTracker progressTracker) { - return new OperatorGeneralizeByAreaCursor(geoms, - bRemoveDegenerateParts, - maxPointCount, - generalizeType, - spatialReference, - progressTracker); - } + @Override + public GeometryCursor execute(GeometryCursor geoms, + boolean bRemoveDegenerateParts, + int maxPointCount, + GeneralizeType generalizeType, + SpatialReference spatialReference, + ProgressTracker progressTracker) { + return new OperatorGeneralizeByAreaCursor(geoms, + bRemoveDegenerateParts, + maxPointCount, + generalizeType, + spatialReference, + progressTracker); + } - @Override - public Geometry execute(Geometry geom, - double percentReduction, - boolean bRemoveDegenerateParts, - GeneralizeType generalizeType, - SpatialReference spatialReference, - ProgressTracker progressTracker) { + @Override + public Geometry execute(Geometry geom, + double percentReduction, + boolean bRemoveDegenerateParts, + GeneralizeType generalizeType, + SpatialReference spatialReference, + ProgressTracker progressTracker) { - SimpleGeometryCursor inputGeomCurs = new SimpleGeometryCursor(geom); + SimpleGeometryCursor inputGeomCurs = new SimpleGeometryCursor(geom); - GeometryCursor geometryCursor = execute(inputGeomCurs, - percentReduction, - bRemoveDegenerateParts, - generalizeType, - spatialReference, - progressTracker); + GeometryCursor geometryCursor = execute(inputGeomCurs, + percentReduction, + bRemoveDegenerateParts, + generalizeType, + spatialReference, + progressTracker); - return geometryCursor.next(); - } + return geometryCursor.next(); + } - @Override - public Geometry execute(Geometry geom, - boolean bRemoveDegenerateParts, - int maxPointCount, - GeneralizeType generalizeType, - SpatialReference spatialReference, - ProgressTracker progressTracker) { + @Override + public Geometry execute(Geometry geom, + boolean bRemoveDegenerateParts, + int maxPointCount, + GeneralizeType generalizeType, + SpatialReference spatialReference, + ProgressTracker progressTracker) { - SimpleGeometryCursor inputGeomCurs = new SimpleGeometryCursor(geom); + SimpleGeometryCursor inputGeomCurs = new SimpleGeometryCursor(geom); - GeometryCursor geometryCursor = execute(inputGeomCurs, - bRemoveDegenerateParts, - maxPointCount, - generalizeType, - spatialReference, - progressTracker); + GeometryCursor geometryCursor = execute(inputGeomCurs, + bRemoveDegenerateParts, + maxPointCount, + generalizeType, + spatialReference, + progressTracker); - return geometryCursor.next(); + return geometryCursor.next(); - } + } } diff --git a/src/main/java/com/esri/core/geometry/OperatorGeneralizeCursor.java b/src/main/java/com/esri/core/geometry/OperatorGeneralizeCursor.java index 5f03a87b..e08900ba 100644 --- a/src/main/java/com/esri/core/geometry/OperatorGeneralizeCursor.java +++ b/src/main/java/com/esri/core/geometry/OperatorGeneralizeCursor.java @@ -24,144 +24,144 @@ package com.esri.core.geometry; final class OperatorGeneralizeCursor extends GeometryCursor { - ProgressTracker m_progressTracker; - double m_maxDeviation; - boolean m_bRemoveDegenerateParts; - - public OperatorGeneralizeCursor(GeometryCursor geoms, double maxDeviation, - boolean bRemoveDegenerateParts, ProgressTracker progressTracker) { - m_inputGeoms = geoms; - m_maxDeviation = maxDeviation; - m_progressTracker = progressTracker; - m_bRemoveDegenerateParts = bRemoveDegenerateParts; - } - - @Override - public Geometry next() { - // TODO Auto-generated method stub - Geometry geom = m_inputGeoms.next(); - if (geom == null) - return null; - return Generalize(geom); - } - - private Geometry Generalize(Geometry geom) { - Geometry.Type gt = geom.getType(); - if (Geometry.isPoint(gt.value())) - return geom; - if (gt == Geometry.Type.Envelope) { - Polygon poly = new Polygon(geom.getDescription()); - poly.addEnvelope((Envelope) geom, false); - return Generalize(poly); - } - if (geom.isEmpty()) - return geom; - MultiPath mp = (MultiPath) geom; - MultiPath dstmp = (MultiPath) geom.createInstance(); - Line line = new Line(); - for (int ipath = 0, npath = mp.getPathCount(); ipath < npath; ipath++) { - GeneralizePath((MultiPathImpl) mp._getImpl(), ipath, - (MultiPathImpl) dstmp._getImpl(), line); - } - - return dstmp; - } - - private void GeneralizePath(MultiPathImpl mpsrc, int ipath, - MultiPathImpl mpdst, Line lineHelper) { - if (mpsrc.getPathSize(ipath) < 2) - return; - int start = mpsrc.getPathStart(ipath); - int end = mpsrc.getPathEnd(ipath) - 1; - AttributeStreamOfDbl xy = (AttributeStreamOfDbl) mpsrc - .getAttributeStreamRef(VertexDescription.Semantics.POSITION); - boolean bClosed = mpsrc.isClosedPath(ipath); - - AttributeStreamOfInt32 stack = new AttributeStreamOfInt32(0); - stack.reserve(mpsrc.getPathSize(ipath) + 1); - AttributeStreamOfInt32 resultStack = new AttributeStreamOfInt32(0); - resultStack.reserve(mpsrc.getPathSize(ipath) + 1); - stack.add(bClosed ? start : end); - stack.add(start); - Point2D pt = new Point2D(); - while (stack.size() > 1) { - int i1 = stack.getLast(); - stack.removeLast(); - int i2 = stack.getLast(); - mpsrc.getXY(i1, pt); - lineHelper.setStartXY(pt); - mpsrc.getXY(i2, pt); - lineHelper.setEndXY(pt); - int mid = FindGreatestDistance(lineHelper, pt, xy, i1, i2, end); - if (mid >= 0) { - stack.add(mid); - stack.add(i1); - } else { - resultStack.add(i1); - } - } - - if (!bClosed) - resultStack.add(stack.get(0)); - - int rs_size = resultStack.size(); - int path_size = mpsrc.getPathSize(ipath); - if (rs_size == path_size && rs_size == stack.size()) { - mpdst.addPath(mpsrc, ipath, true); - } else { - if (resultStack.size() > 0) { - if (m_bRemoveDegenerateParts && resultStack.size() <= 2) { - if (bClosed || resultStack.size() == 1) - return; - - double d = Point2D.distance( - mpsrc.getXY(resultStack.get(0)), - mpsrc.getXY(resultStack.get(1))); - if (d <= m_maxDeviation) - return; - } - - Point point = new Point(); - for (int i = 0, n = resultStack.size(); i < n; i++) { - mpsrc.getPointByVal(resultStack.get(i), point); - if (i == 0) - mpdst.startPath(point); - else - mpdst.lineTo(point); - } - - if (bClosed) { - for (int i = resultStack.size(); i < 3; i++) - mpdst.lineTo(point); - - mpdst.closePathWithLine(); - } - } - } - } - - private int FindGreatestDistance(Line line, Point2D ptHelper, - AttributeStreamOfDbl xy, int start, int end, int pathEnd) { - int to = end - 1; - if (end <= start) {// closed path case. end is equal to the path start. - to = pathEnd; - } - int mid = -1; - double maxd = -1.0; - for (int i = start + 1; i <= to; i++) { - xy.read(2 * i, ptHelper); - double x1 = ptHelper.x; - double y1 = ptHelper.y; - double t = line.getClosestCoordinate(ptHelper, false); - line.getCoord2D(t, ptHelper); - ptHelper.x -= x1; - ptHelper.y -= y1; - double dist = ptHelper.length(); - if (dist > m_maxDeviation && dist > maxd) { - mid = i; - maxd = dist; - } - } - return mid; - } + ProgressTracker m_progressTracker; + double m_maxDeviation; + boolean m_bRemoveDegenerateParts; + + public OperatorGeneralizeCursor(GeometryCursor geoms, double maxDeviation, + boolean bRemoveDegenerateParts, ProgressTracker progressTracker) { + m_inputGeoms = geoms; + m_maxDeviation = maxDeviation; + m_progressTracker = progressTracker; + m_bRemoveDegenerateParts = bRemoveDegenerateParts; + } + + @Override + public Geometry next() { + // TODO Auto-generated method stub + Geometry geom = m_inputGeoms.next(); + if (geom == null) + return null; + return Generalize(geom); + } + + private Geometry Generalize(Geometry geom) { + Geometry.Type gt = geom.getType(); + if (Geometry.isPoint(gt.value())) + return geom; + if (gt == Geometry.Type.Envelope) { + Polygon poly = new Polygon(geom.getDescription()); + poly.addEnvelope((Envelope) geom, false); + return Generalize(poly); + } + if (geom.isEmpty()) + return geom; + MultiPath mp = (MultiPath) geom; + MultiPath dstmp = (MultiPath) geom.createInstance(); + Line line = new Line(); + for (int ipath = 0, npath = mp.getPathCount(); ipath < npath; ipath++) { + GeneralizePath((MultiPathImpl) mp._getImpl(), ipath, + (MultiPathImpl) dstmp._getImpl(), line); + } + + return dstmp; + } + + private void GeneralizePath(MultiPathImpl mpsrc, int ipath, + MultiPathImpl mpdst, Line lineHelper) { + if (mpsrc.getPathSize(ipath) < 2) + return; + int start = mpsrc.getPathStart(ipath); + int end = mpsrc.getPathEnd(ipath) - 1; + AttributeStreamOfDbl xy = (AttributeStreamOfDbl) mpsrc + .getAttributeStreamRef(VertexDescription.Semantics.POSITION); + boolean bClosed = mpsrc.isClosedPath(ipath); + + AttributeStreamOfInt32 stack = new AttributeStreamOfInt32(0); + stack.reserve(mpsrc.getPathSize(ipath) + 1); + AttributeStreamOfInt32 resultStack = new AttributeStreamOfInt32(0); + resultStack.reserve(mpsrc.getPathSize(ipath) + 1); + stack.add(bClosed ? start : end); + stack.add(start); + Point2D pt = new Point2D(); + while (stack.size() > 1) { + int i1 = stack.getLast(); + stack.removeLast(); + int i2 = stack.getLast(); + mpsrc.getXY(i1, pt); + lineHelper.setStartXY(pt); + mpsrc.getXY(i2, pt); + lineHelper.setEndXY(pt); + int mid = FindGreatestDistance(lineHelper, pt, xy, i1, i2, end); + if (mid >= 0) { + stack.add(mid); + stack.add(i1); + } else { + resultStack.add(i1); + } + } + + if (!bClosed) + resultStack.add(stack.get(0)); + + int rs_size = resultStack.size(); + int path_size = mpsrc.getPathSize(ipath); + if (rs_size == path_size && rs_size == stack.size()) { + mpdst.addPath(mpsrc, ipath, true); + } else { + if (resultStack.size() > 0) { + if (m_bRemoveDegenerateParts && resultStack.size() <= 2) { + if (bClosed || resultStack.size() == 1) + return; + + double d = Point2D.distance( + mpsrc.getXY(resultStack.get(0)), + mpsrc.getXY(resultStack.get(1))); + if (d <= m_maxDeviation) + return; + } + + Point point = new Point(); + for (int i = 0, n = resultStack.size(); i < n; i++) { + mpsrc.getPointByVal(resultStack.get(i), point); + if (i == 0) + mpdst.startPath(point); + else + mpdst.lineTo(point); + } + + if (bClosed) { + for (int i = resultStack.size(); i < 3; i++) + mpdst.lineTo(point); + + mpdst.closePathWithLine(); + } + } + } + } + + private int FindGreatestDistance(Line line, Point2D ptHelper, + AttributeStreamOfDbl xy, int start, int end, int pathEnd) { + int to = end - 1; + if (end <= start) {// closed path case. end is equal to the path start. + to = pathEnd; + } + int mid = -1; + double maxd = -1.0; + for (int i = start + 1; i <= to; i++) { + xy.read(2 * i, ptHelper); + double x1 = ptHelper.x; + double y1 = ptHelper.y; + double t = line.getClosestCoordinate(ptHelper, false); + line.getCoord2D(t, ptHelper); + ptHelper.x -= x1; + ptHelper.y -= y1; + double dist = ptHelper.length(); + if (dist > m_maxDeviation && dist > maxd) { + mid = i; + maxd = dist; + } + } + return mid; + } } diff --git a/src/main/java/com/esri/core/geometry/OperatorGeneralizeLocal.java b/src/main/java/com/esri/core/geometry/OperatorGeneralizeLocal.java index 912bb2da..e0392625 100644 --- a/src/main/java/com/esri/core/geometry/OperatorGeneralizeLocal.java +++ b/src/main/java/com/esri/core/geometry/OperatorGeneralizeLocal.java @@ -25,23 +25,23 @@ final class OperatorGeneralizeLocal extends OperatorGeneralize { - @Override - public GeometryCursor execute(GeometryCursor geoms, double maxDeviation, - boolean bRemoveDegenerateParts, ProgressTracker progressTracker) { - // TODO Auto-generated method stub - - return new OperatorGeneralizeCursor(geoms, maxDeviation, - bRemoveDegenerateParts, progressTracker); - } - - @Override - public Geometry execute(Geometry geom, double maxDeviation, - boolean bRemoveDegenerateParts, ProgressTracker progressTracker) { - SimpleGeometryCursor inputGeomCurs = new SimpleGeometryCursor(geom); - GeometryCursor geometryCursor = execute(inputGeomCurs, maxDeviation, - bRemoveDegenerateParts, progressTracker); - - return geometryCursor.next(); - } + @Override + public GeometryCursor execute(GeometryCursor geoms, double maxDeviation, + boolean bRemoveDegenerateParts, ProgressTracker progressTracker) { + // TODO Auto-generated method stub + + return new OperatorGeneralizeCursor(geoms, maxDeviation, + bRemoveDegenerateParts, progressTracker); + } + + @Override + public Geometry execute(Geometry geom, double maxDeviation, + boolean bRemoveDegenerateParts, ProgressTracker progressTracker) { + SimpleGeometryCursor inputGeomCurs = new SimpleGeometryCursor(geom); + GeometryCursor geometryCursor = execute(inputGeomCurs, maxDeviation, + bRemoveDegenerateParts, progressTracker); + + return geometryCursor.next(); + } } diff --git a/src/main/java/com/esri/core/geometry/OperatorGeodesicBuffer.java b/src/main/java/com/esri/core/geometry/OperatorGeodesicBuffer.java index 15fd7b38..2473d2c7 100644 --- a/src/main/java/com/esri/core/geometry/OperatorGeodesicBuffer.java +++ b/src/main/java/com/esri/core/geometry/OperatorGeodesicBuffer.java @@ -25,61 +25,61 @@ public abstract class OperatorGeodesicBuffer extends Operator { - @Override - public Operator.Type getType() { - return Operator.Type.GeodesicBuffer; - } + @Override + public Operator.Type getType() { + return Operator.Type.GeodesicBuffer; + } - /** - * Creates a geodesic buffer around the input geometries - * - * @param inputGeometries The geometries to buffer. - * @param sr The Spatial_reference of the Geometries. - * @param curveType The geodetic curve type of the segments. If the curve_type is Geodetic_curve::shape_preserving, then the segments are densified in the projection where they are defined before - * buffering. - * @param distancesMeters The buffer distances in meters for the Geometries. If the size of the distances array is less than the number of geometries in the input_geometries, the last distance value - * is used for the rest of geometries. - * @param maxDeviationMeters The deviation offset to use for convergence. The geodesic arcs of the resulting buffer will be closer than the max deviation of the true buffer. Pass in NaN to use the - * default deviation. - * @param bReserved Must be false. Reserved for future development. Will throw an exception if not false. - * @param bUnion If True, the buffered geometries will be unioned, otherwise they wont be unioned. - * @param progressTracker Can be null. Allows to cancel lengthy operation. - * @return Geometry cursor over result buffers. - */ - public abstract GeometryCursor execute( - GeometryCursor inputGeometries, - SpatialReference sr, - int curveType, - double[] distancesMeters, - double maxDeviationMeters, - boolean bReserved, - boolean bUnion, - ProgressTracker progressTracker); + /** + * Creates a geodesic buffer around the input geometries + * + * @param inputGeometries The geometries to buffer. + * @param sr The Spatial_reference of the Geometries. + * @param curveType The geodetic curve type of the segments. If the curve_type is Geodetic_curve::shape_preserving, then the segments are densified in the projection where they are defined before + * buffering. + * @param distancesMeters The buffer distances in meters for the Geometries. If the size of the distances array is less than the number of geometries in the input_geometries, the last distance value + * is used for the rest of geometries. + * @param maxDeviationMeters The deviation offset to use for convergence. The geodesic arcs of the resulting buffer will be closer than the max deviation of the true buffer. Pass in NaN to use the + * default deviation. + * @param bReserved Must be false. Reserved for future development. Will throw an exception if not false. + * @param bUnion If True, the buffered geometries will be unioned, otherwise they wont be unioned. + * @param progressTracker Can be null. Allows to cancel lengthy operation. + * @return Geometry cursor over result buffers. + */ + public abstract GeometryCursor execute( + GeometryCursor inputGeometries, + SpatialReference sr, + int curveType, + double[] distancesMeters, + double maxDeviationMeters, + boolean bReserved, + boolean bUnion, + ProgressTracker progressTracker); - /** - * Creates a geodesic buffer around the input geometry - * - * @param inputGeometry The geometry to buffer. - * @param sr The Spatial_reference of the Geometry. - * @param curveType The geodetic curve type of the segments. If the curve_type is Geodetic_curve::shape_preserving, then the segments are densified in the projection where they are defined before - * buffering. - * @param distanceMeters The buffer distance in meters for the Geometry. - * @param maxDeviationMeters The deviation offset to use for convergence. The geodesic arcs of the resulting buffer will be closer than the max deviation of the true buffer. Pass in NaN to use the - * default deviation. - * @param bReserved Must be false. Reserved for future development. Will throw an exception if not false. - * @param progressTracker Can be null. Allows to cancel lengthy operation. - * @return Returns result buffer. - */ - public abstract Geometry execute( - Geometry inputGeometry, - SpatialReference sr, - int curveType, - double distanceMeters, - double maxDeviationMeters, - boolean bReserved, - ProgressTracker progressTracker); + /** + * Creates a geodesic buffer around the input geometry + * + * @param inputGeometry The geometry to buffer. + * @param sr The Spatial_reference of the Geometry. + * @param curveType The geodetic curve type of the segments. If the curve_type is Geodetic_curve::shape_preserving, then the segments are densified in the projection where they are defined before + * buffering. + * @param distanceMeters The buffer distance in meters for the Geometry. + * @param maxDeviationMeters The deviation offset to use for convergence. The geodesic arcs of the resulting buffer will be closer than the max deviation of the true buffer. Pass in NaN to use the + * default deviation. + * @param bReserved Must be false. Reserved for future development. Will throw an exception if not false. + * @param progressTracker Can be null. Allows to cancel lengthy operation. + * @return Returns result buffer. + */ + public abstract Geometry execute( + Geometry inputGeometry, + SpatialReference sr, + int curveType, + double distanceMeters, + double maxDeviationMeters, + boolean bReserved, + ProgressTracker progressTracker); - public static OperatorGeodesicBuffer local() { - return (OperatorGeodesicBuffer) OperatorFactoryLocal.getInstance().getOperator(Type.GeodesicBuffer); - } + public static OperatorGeodesicBuffer local() { + return (OperatorGeodesicBuffer) OperatorFactoryLocal.getInstance().getOperator(Type.GeodesicBuffer); + } } diff --git a/src/main/java/com/esri/core/geometry/OperatorGeodesicBufferCursor.java b/src/main/java/com/esri/core/geometry/OperatorGeodesicBufferCursor.java index a6f6bce3..0a7650f7 100644 --- a/src/main/java/com/esri/core/geometry/OperatorGeodesicBufferCursor.java +++ b/src/main/java/com/esri/core/geometry/OperatorGeodesicBufferCursor.java @@ -4,54 +4,54 @@ * Created by davidraleigh on 2/20/16. */ public class OperatorGeodesicBufferCursor extends GeometryCursor { - private SpatialReferenceImpl m_spatialReference; - private ProgressTracker m_progressTracker; - private double[] m_distances; - private double m_maxDeviation; - private Envelope2D m_currentUnionEnvelope2D; - private boolean m_bUnion; - - private int m_dindex; - - // GeometryCursor inputGeometries, SpatialReference sr, int curveType, double[] distancesMeters, double maxDeviationMeters, boolean bReserved, boolean bUnion, ProgressTracker progressTracker - OperatorGeodesicBufferCursor(GeometryCursor inputGeoms, - SpatialReference sr, - double[] distances, - double maxDeviation, - boolean bReserved, - boolean b_union, - ProgressTracker progressTracker) { - m_inputGeoms = inputGeoms; - m_spatialReference = (SpatialReferenceImpl) sr; - m_distances = distances; - m_maxDeviation = maxDeviation; - m_bUnion = b_union; - m_currentUnionEnvelope2D = new Envelope2D(); - m_currentUnionEnvelope2D.setEmpty(); - m_dindex = -1; - m_progressTracker = progressTracker; - } - - - @Override - public Geometry next() { - if (m_bUnion) { - OperatorGeodesicBufferCursor cursor = new OperatorGeodesicBufferCursor(m_inputGeoms, m_spatialReference, m_distances, m_maxDeviation, false, false, m_progressTracker); - return ((OperatorUnion) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.Union)).execute(cursor, m_spatialReference, m_progressTracker).next(); - } - - if (hasNext()) { - if (m_dindex + 1 < m_distances.length) - m_dindex++; - - return geodesicBuffer(m_inputGeoms.next(), m_distances[m_dindex]); - } - - return null; - } - - // virtual bool IsRecycling() OVERRIDE { return false; } - Geometry geodesicBuffer(Geometry geom, double distance) { - return GeodesicBufferer.buffer(geom, distance, m_spatialReference, m_maxDeviation, 96, m_progressTracker); - } + private SpatialReferenceImpl m_spatialReference; + private ProgressTracker m_progressTracker; + private double[] m_distances; + private double m_maxDeviation; + private Envelope2D m_currentUnionEnvelope2D; + private boolean m_bUnion; + + private int m_dindex; + + // GeometryCursor inputGeometries, SpatialReference sr, int curveType, double[] distancesMeters, double maxDeviationMeters, boolean bReserved, boolean bUnion, ProgressTracker progressTracker + OperatorGeodesicBufferCursor(GeometryCursor inputGeoms, + SpatialReference sr, + double[] distances, + double maxDeviation, + boolean bReserved, + boolean b_union, + ProgressTracker progressTracker) { + m_inputGeoms = inputGeoms; + m_spatialReference = (SpatialReferenceImpl) sr; + m_distances = distances; + m_maxDeviation = maxDeviation; + m_bUnion = b_union; + m_currentUnionEnvelope2D = new Envelope2D(); + m_currentUnionEnvelope2D.setEmpty(); + m_dindex = -1; + m_progressTracker = progressTracker; + } + + + @Override + public Geometry next() { + if (m_bUnion) { + OperatorGeodesicBufferCursor cursor = new OperatorGeodesicBufferCursor(m_inputGeoms, m_spatialReference, m_distances, m_maxDeviation, false, false, m_progressTracker); + return ((OperatorUnion) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.Union)).execute(cursor, m_spatialReference, m_progressTracker).next(); + } + + if (hasNext()) { + if (m_dindex + 1 < m_distances.length) + m_dindex++; + + return geodesicBuffer(m_inputGeoms.next(), m_distances[m_dindex]); + } + + return null; + } + + // virtual bool IsRecycling() OVERRIDE { return false; } + Geometry geodesicBuffer(Geometry geom, double distance) { + return GeodesicBufferer.buffer(geom, distance, m_spatialReference, m_maxDeviation, 96, m_progressTracker); + } } diff --git a/src/main/java/com/esri/core/geometry/OperatorGeodesicBufferLocal.java b/src/main/java/com/esri/core/geometry/OperatorGeodesicBufferLocal.java index 8f2a0bf8..1d20ced4 100644 --- a/src/main/java/com/esri/core/geometry/OperatorGeodesicBufferLocal.java +++ b/src/main/java/com/esri/core/geometry/OperatorGeodesicBufferLocal.java @@ -27,50 +27,50 @@ //This is a stub class OperatorGeodesicBufferLocal extends OperatorGeodesicBuffer { - @Override - public GeometryCursor execute(GeometryCursor inputGeometries, - SpatialReference sr, - int curveType, - double[] distancesMeters, - double maxDeviationMeters, - boolean bReserved, - boolean bUnion, - ProgressTracker progressTracker) { - SpatialReference gcs = SpatialReference.create(4326); - if (sr.getCoordinateSystemType() != SpatialReference.CoordinateSystemType.GEOGRAPHIC) { - // TODO assigning to WGS 84, but should grab GCS from projection - ProjectionTransformation projectionTransformation = new ProjectionTransformation(sr, gcs); - inputGeometries = new OperatorProjectCursor(inputGeometries, projectionTransformation, progressTracker); - } else { - gcs = sr; - } - - inputGeometries = new OperatorGeodesicBufferCursor(inputGeometries, gcs, distancesMeters, maxDeviationMeters, bReserved, bUnion, progressTracker); - - if (sr.getCoordinateSystemType() != SpatialReference.CoordinateSystemType.GEOGRAPHIC) { - ProjectionTransformation projectionTransformation = new ProjectionTransformation(gcs, sr); - inputGeometries = new OperatorProjectCursor(inputGeometries, projectionTransformation, progressTracker); - } - - return inputGeometries; - } - - @Override - public Geometry execute(Geometry inputGeometry, - SpatialReference sr, - int curveType, - double distanceMeters, - double maxDeviationMeters, - boolean bReserved, - ProgressTracker progressTracker) { - - SimpleGeometryCursor inputCursor = new SimpleGeometryCursor(inputGeometry); - - double[] distances = new double[1]; - distances[0] = distanceMeters; - - GeometryCursor outputCursor = execute(inputCursor, sr, curveType, distances, maxDeviationMeters, false, false, progressTracker); - - return outputCursor.next(); - } + @Override + public GeometryCursor execute(GeometryCursor inputGeometries, + SpatialReference sr, + int curveType, + double[] distancesMeters, + double maxDeviationMeters, + boolean bReserved, + boolean bUnion, + ProgressTracker progressTracker) { + SpatialReference gcs = SpatialReference.create(4326); + if (sr.getCoordinateSystemType() != SpatialReference.CoordinateSystemType.GEOGRAPHIC) { + // TODO assigning to WGS 84, but should grab GCS from projection + ProjectionTransformation projectionTransformation = new ProjectionTransformation(sr, gcs); + inputGeometries = new OperatorProjectCursor(inputGeometries, projectionTransformation, progressTracker); + } else { + gcs = sr; + } + + inputGeometries = new OperatorGeodesicBufferCursor(inputGeometries, gcs, distancesMeters, maxDeviationMeters, bReserved, bUnion, progressTracker); + + if (sr.getCoordinateSystemType() != SpatialReference.CoordinateSystemType.GEOGRAPHIC) { + ProjectionTransformation projectionTransformation = new ProjectionTransformation(gcs, sr); + inputGeometries = new OperatorProjectCursor(inputGeometries, projectionTransformation, progressTracker); + } + + return inputGeometries; + } + + @Override + public Geometry execute(Geometry inputGeometry, + SpatialReference sr, + int curveType, + double distanceMeters, + double maxDeviationMeters, + boolean bReserved, + ProgressTracker progressTracker) { + + SimpleGeometryCursor inputCursor = new SimpleGeometryCursor(inputGeometry); + + double[] distances = new double[1]; + distances[0] = distanceMeters; + + GeometryCursor outputCursor = execute(inputCursor, sr, curveType, distances, maxDeviationMeters, false, false, progressTracker); + + return outputCursor.next(); + } } diff --git a/src/main/java/com/esri/core/geometry/OperatorGeodeticArea.java b/src/main/java/com/esri/core/geometry/OperatorGeodeticArea.java index d953ad69..bdf603e0 100644 --- a/src/main/java/com/esri/core/geometry/OperatorGeodeticArea.java +++ b/src/main/java/com/esri/core/geometry/OperatorGeodeticArea.java @@ -29,41 +29,41 @@ */ public abstract class OperatorGeodeticArea extends Operator { - @Override - public Type getType() { - return Type.GeodeticArea; - } + @Override + public Type getType() { + return Type.GeodeticArea; + } - /** - * Calculates the geodetic area of each geometry in the geometry cursor. - * - * @param geoms The geometry cursor to be iterated over to perform the - * Geodetic Area calculation. - * @param sr The SpatialReference of the geometries. - * @param geodeticCurveType Use the {@link GeodeticCurveType} interface to choose the - * interpretation of a line connecting two points. - * @param progressTracker - * @return Returns an array of the geodetic areas of the geometries. - */ - public abstract double[] execute(GeometryCursor geoms, SpatialReference sr, - int geodeticCurveType, ProgressTracker progressTracker); + /** + * Calculates the geodetic area of each geometry in the geometry cursor. + * + * @param geoms The geometry cursor to be iterated over to perform the + * Geodetic Area calculation. + * @param sr The SpatialReference of the geometries. + * @param geodeticCurveType Use the {@link GeodeticCurveType} interface to choose the + * interpretation of a line connecting two points. + * @param progressTracker + * @return Returns an array of the geodetic areas of the geometries. + */ + public abstract double[] execute(GeometryCursor geoms, SpatialReference sr, + int geodeticCurveType, ProgressTracker progressTracker); - /** - * Calculates the geodetic area of the input Geometry. - * - * @param geom The input Geometry for the geodetic area calculation. - * @param sr The SpatialReference of the Geometry. - * @param geodeticCurveType Use the {@link GeodeticCurveType} interface to choose the - * interpretation of a line connecting two points. - * @param progressTracker - * @return Returns the geodetic area of the Geometry. - */ - public abstract double execute(Geometry geom, SpatialReference sr, - int geodeticCurveType, ProgressTracker progressTracker); + /** + * Calculates the geodetic area of the input Geometry. + * + * @param geom The input Geometry for the geodetic area calculation. + * @param sr The SpatialReference of the Geometry. + * @param geodeticCurveType Use the {@link GeodeticCurveType} interface to choose the + * interpretation of a line connecting two points. + * @param progressTracker + * @return Returns the geodetic area of the Geometry. + */ + public abstract double execute(Geometry geom, SpatialReference sr, + int geodeticCurveType, ProgressTracker progressTracker); - public static OperatorGeodeticArea local() { - return (OperatorGeodeticArea) OperatorFactoryLocal.getInstance() - .getOperator(Type.GeodeticArea); - } + public static OperatorGeodeticArea local() { + return (OperatorGeodeticArea) OperatorFactoryLocal.getInstance() + .getOperator(Type.GeodeticArea); + } } diff --git a/src/main/java/com/esri/core/geometry/OperatorGeodeticAreaLocal.java b/src/main/java/com/esri/core/geometry/OperatorGeodeticAreaLocal.java index 4ee72ae7..556d43de 100644 --- a/src/main/java/com/esri/core/geometry/OperatorGeodeticAreaLocal.java +++ b/src/main/java/com/esri/core/geometry/OperatorGeodeticAreaLocal.java @@ -28,22 +28,22 @@ //This is a stub class OperatorGeodeticAreaLocal extends OperatorGeodeticArea { - @Override - public double[] execute(GeometryCursor geoms, SpatialReference sr, - int geodeticCurveType, ProgressTracker progressTracker) { - ArrayList areas = new ArrayList<>(); - while (geoms.hasNext()) { - areas.add(execute(geoms.next(), sr, geodeticCurveType, progressTracker)); - } - return areas.stream().mapToDouble(d -> d).toArray(); - } - - @Override - public double execute(Geometry geom, SpatialReference sr, - int geodeticCurveType, ProgressTracker progressTracker) { - if (geodeticCurveType != GeodeticCurveType.Geodesic) { - throw new GeometryException("Only implemented for Geodesic"); - } - return ((OperatorProject)OperatorFactoryLocal.getInstance().getOperator(Type.Project)).execute(geom, ProjectionTransformation.getEqualArea(geom, sr), null).calculateArea2D(); - } + @Override + public double[] execute(GeometryCursor geoms, SpatialReference sr, + int geodeticCurveType, ProgressTracker progressTracker) { + ArrayList areas = new ArrayList<>(); + while (geoms.hasNext()) { + areas.add(execute(geoms.next(), sr, geodeticCurveType, progressTracker)); + } + return areas.stream().mapToDouble(d -> d).toArray(); + } + + @Override + public double execute(Geometry geom, SpatialReference sr, + int geodeticCurveType, ProgressTracker progressTracker) { + if (geodeticCurveType != GeodeticCurveType.Geodesic) { + throw new GeometryException("Only implemented for Geodesic"); + } + return ((OperatorProject) OperatorFactoryLocal.getInstance().getOperator(Type.Project)).execute(geom, ProjectionTransformation.getEqualArea(geom, sr), null).calculateArea2D(); + } } diff --git a/src/main/java/com/esri/core/geometry/OperatorGeodeticDensifyByLength.java b/src/main/java/com/esri/core/geometry/OperatorGeodeticDensifyByLength.java index 9ed6a8d5..0b798975 100644 --- a/src/main/java/com/esri/core/geometry/OperatorGeodeticDensifyByLength.java +++ b/src/main/java/com/esri/core/geometry/OperatorGeodeticDensifyByLength.java @@ -30,40 +30,40 @@ */ public abstract class OperatorGeodeticDensifyByLength extends Operator { - @Override - public Type getType() { - return Type.GeodeticDensifyByLength; - } + @Override + public Type getType() { + return Type.GeodeticDensifyByLength; + } - /** - * Densifies input geometries. Attributes are interpolated along the scalar t-values of the input segments obtained from the length ratios along the densified segments. - * - * @param geoms The geometries to be densified. - * @param sr The SpatialReference of the Geometry. - * @param maxSegmentLengthMeters The maximum segment length (in meters) allowed. Must be a positive value. - * @param curveType The interpretation of a line connecting two points. - * @return Returns the densified geometries (It does nothing to geometries with dim less than 1, but simply passes them along). - *

- * Note the behavior is not determined for any geodetic curve segments that connect two poles, or for loxodrome segments that connect to any pole. - */ - public abstract GeometryCursor execute( - GeometryCursor geoms, - SpatialReference sr, - double maxSegmentLengthMeters, - int curveType, - ProgressTracker progressTracker); + /** + * Densifies input geometries. Attributes are interpolated along the scalar t-values of the input segments obtained from the length ratios along the densified segments. + * + * @param geoms The geometries to be densified. + * @param sr The SpatialReference of the Geometry. + * @param maxSegmentLengthMeters The maximum segment length (in meters) allowed. Must be a positive value. + * @param curveType The interpretation of a line connecting two points. + * @return Returns the densified geometries (It does nothing to geometries with dim less than 1, but simply passes them along). + *

+ * Note the behavior is not determined for any geodetic curve segments that connect two poles, or for loxodrome segments that connect to any pole. + */ + public abstract GeometryCursor execute( + GeometryCursor geoms, + SpatialReference sr, + double maxSegmentLengthMeters, + int curveType, + ProgressTracker progressTracker); - /** - * Same as above, but works with a single geometry. - */ - public abstract Geometry execute( - Geometry geom, - SpatialReference sr, - double maxSegmentLengthMeters, - int curveType, - ProgressTracker progressTracker); + /** + * Same as above, but works with a single geometry. + */ + public abstract Geometry execute( + Geometry geom, + SpatialReference sr, + double maxSegmentLengthMeters, + int curveType, + ProgressTracker progressTracker); - public static OperatorGeodeticDensifyByLength local() { - return (OperatorGeodeticDensifyByLength) OperatorFactoryLocal.getInstance().getOperator(Type.GeodeticDensifyByLength); - } + public static OperatorGeodeticDensifyByLength local() { + return (OperatorGeodeticDensifyByLength) OperatorFactoryLocal.getInstance().getOperator(Type.GeodeticDensifyByLength); + } } diff --git a/src/main/java/com/esri/core/geometry/OperatorGeodeticDensifyCursor.java b/src/main/java/com/esri/core/geometry/OperatorGeodeticDensifyCursor.java index b767a1fe..b2eb6a27 100644 --- a/src/main/java/com/esri/core/geometry/OperatorGeodeticDensifyCursor.java +++ b/src/main/java/com/esri/core/geometry/OperatorGeodeticDensifyCursor.java @@ -4,29 +4,29 @@ * Created by davidraleigh on 2/21/16. */ public class OperatorGeodeticDensifyCursor extends GeometryCursor { - SpatialReferenceImpl m_spatialReference; - double m_maxLength; - Point m_startPoint; - Point m_endPoint; - ProgressTracker m_progressTracker; + SpatialReferenceImpl m_spatialReference; + double m_maxLength; + Point m_startPoint; + Point m_endPoint; + ProgressTracker m_progressTracker; - public OperatorGeodeticDensifyCursor(GeometryCursor inputGeoms1, - SpatialReference spatialReference, - double maxLength, - ProgressTracker progressTracker) { - m_inputGeoms = inputGeoms1; - m_maxLength = maxLength; - m_spatialReference = (SpatialReferenceImpl) spatialReference; - m_startPoint = new Point(); - m_endPoint = new Point(); - m_progressTracker = progressTracker; - } + public OperatorGeodeticDensifyCursor(GeometryCursor inputGeoms1, + SpatialReference spatialReference, + double maxLength, + ProgressTracker progressTracker) { + m_inputGeoms = inputGeoms1; + m_maxLength = maxLength; + m_spatialReference = (SpatialReferenceImpl) spatialReference; + m_startPoint = new Point(); + m_endPoint = new Point(); + m_progressTracker = progressTracker; + } - @Override - public Geometry next() { - if (hasNext()) - return GeodesicDensifier.densifyByLength(m_inputGeoms.next(), m_spatialReference, m_maxLength, m_progressTracker); + @Override + public Geometry next() { + if (hasNext()) + return GeodesicDensifier.densifyByLength(m_inputGeoms.next(), m_spatialReference, m_maxLength, m_progressTracker); - return null; - } + return null; + } } diff --git a/src/main/java/com/esri/core/geometry/OperatorGeodeticDensifyLocal.java b/src/main/java/com/esri/core/geometry/OperatorGeodeticDensifyLocal.java index da1ff5dd..1518885e 100644 --- a/src/main/java/com/esri/core/geometry/OperatorGeodeticDensifyLocal.java +++ b/src/main/java/com/esri/core/geometry/OperatorGeodeticDensifyLocal.java @@ -27,27 +27,27 @@ //This is a stub class OperatorGeodeticDensifyLocal extends OperatorGeodeticDensifyByLength { - @Override - public GeometryCursor execute(GeometryCursor geoms, - SpatialReference sr, - double maxSegmentLengthMeters, - int curveType, - ProgressTracker progressTracker) { - if (maxSegmentLengthMeters <= 0) - // TODO fix geometry exception to match native implementation - throw new GeometryException("max segment length must be positive and greater than 0");// GEOMTHROW(invalid_argument); - - return new OperatorGeodeticDensifyCursor(geoms, sr, maxSegmentLengthMeters, progressTracker); - } - - @Override - public Geometry execute(Geometry geom, - SpatialReference sr, - double maxSegmentLengthMeters, - int curveType, - ProgressTracker progressTracker) { - SimpleGeometryCursor inputCursor = new SimpleGeometryCursor(geom); - GeometryCursor outputCursor = execute(inputCursor, sr, maxSegmentLengthMeters, curveType, progressTracker); - return outputCursor.next(); - } + @Override + public GeometryCursor execute(GeometryCursor geoms, + SpatialReference sr, + double maxSegmentLengthMeters, + int curveType, + ProgressTracker progressTracker) { + if (maxSegmentLengthMeters <= 0) + // TODO fix geometry exception to match native implementation + throw new GeometryException("max segment length must be positive and greater than 0");// GEOMTHROW(invalid_argument); + + return new OperatorGeodeticDensifyCursor(geoms, sr, maxSegmentLengthMeters, progressTracker); + } + + @Override + public Geometry execute(Geometry geom, + SpatialReference sr, + double maxSegmentLengthMeters, + int curveType, + ProgressTracker progressTracker) { + SimpleGeometryCursor inputCursor = new SimpleGeometryCursor(geom); + GeometryCursor outputCursor = execute(inputCursor, sr, maxSegmentLengthMeters, curveType, progressTracker); + return outputCursor.next(); + } } diff --git a/src/main/java/com/esri/core/geometry/OperatorGeodeticInverse.java b/src/main/java/com/esri/core/geometry/OperatorGeodeticInverse.java index f5a03234..757a5e2e 100644 --- a/src/main/java/com/esri/core/geometry/OperatorGeodeticInverse.java +++ b/src/main/java/com/esri/core/geometry/OperatorGeodeticInverse.java @@ -29,31 +29,32 @@ */ public abstract class OperatorGeodeticInverse extends Operator { - @Override - public Type getType() { - return Type.GeodeticInverse; - } - - /** - * calculate the ngs forward equations for distance and azimuth calculations - * @param geom1 point1 (for now only supports point, could support centroids in future) - * @param geom2 point2 (for now only supports point, could support centroids in future) - * @param sr1 spatial reference of point one (this is the ellipsoid that the calculation will use) - * @param sr2 spatial reference of point two - * @param geodeticCurveType for now only ellipsoid geodesic - * @param progressTracker not used - * @return forward results of azimuth from 1 to 2, azimuth from 2 to 1, and the distance - */ - public abstract InverseResult execute(Geometry geom1, - Geometry geom2, - SpatialReference sr1, - SpatialReference sr2, - int geodeticCurveType, - ProgressTracker progressTracker); - - public static OperatorGeodeticInverse local() { - return (OperatorGeodeticInverse) OperatorFactoryLocal.getInstance() - .getOperator(Type.GeodeticInverse); - } + @Override + public Type getType() { + return Type.GeodeticInverse; + } + + /** + * calculate the ngs forward equations for distance and azimuth calculations + * + * @param geom1 point1 (for now only supports point, could support centroids in future) + * @param geom2 point2 (for now only supports point, could support centroids in future) + * @param sr1 spatial reference of point one (this is the ellipsoid that the calculation will use) + * @param sr2 spatial reference of point two + * @param geodeticCurveType for now only ellipsoid geodesic + * @param progressTracker not used + * @return forward results of azimuth from 1 to 2, azimuth from 2 to 1, and the distance + */ + public abstract InverseResult execute(Geometry geom1, + Geometry geom2, + SpatialReference sr1, + SpatialReference sr2, + int geodeticCurveType, + ProgressTracker progressTracker); + + public static OperatorGeodeticInverse local() { + return (OperatorGeodeticInverse) OperatorFactoryLocal.getInstance() + .getOperator(Type.GeodeticInverse); + } } diff --git a/src/main/java/com/esri/core/geometry/OperatorGeodeticInverseLocal.java b/src/main/java/com/esri/core/geometry/OperatorGeodeticInverseLocal.java index b8a4ea40..d52f81a8 100644 --- a/src/main/java/com/esri/core/geometry/OperatorGeodeticInverseLocal.java +++ b/src/main/java/com/esri/core/geometry/OperatorGeodeticInverseLocal.java @@ -26,35 +26,35 @@ //This is a stub class OperatorGeodeticInverseLocal extends OperatorGeodeticInverse { - @Override - public InverseResult execute(Geometry geom1, Geometry geom2, SpatialReference sr1, SpatialReference sr2, int geodeticCurveType, ProgressTracker progressTracker) { - if (geodeticCurveType != GeodeticCurveType.Geodesic) { - throw new GeometryException("only Geodesic implemented"); - } - if (geom1.getType() != Geometry.Type.Point || geom2.getType() != Geometry.Type.Point) { - throw new GeometryException("only implemented for points"); - } - - double a, e2; - Point projected1; - ProjectionTransformation projectionTransformation; - if (sr1.getCoordinateSystemType() != SpatialReference.CoordinateSystemType.GEOGRAPHIC) { - SpatialReference wgs84 = SpatialReference.create(4326); - projectionTransformation = new ProjectionTransformation(sr1, wgs84); - sr1 = wgs84; - projected1 = (Point)OperatorProject.local().execute(geom1, projectionTransformation, progressTracker); - a = wgs84.getMajorAxis(); - e2 = wgs84.getEccentricitySquared(); - } else { - projected1 = (Point)geom1; - a = sr1.getMajorAxis(); - e2 = sr1.getEccentricitySquared(); - } - - // TODO it'd be great if this was reusable so we didn't have this mem allocaiton - projectionTransformation = new ProjectionTransformation(sr2, sr1); - Point projected2 = (Point)OperatorProject.local().execute(geom2, projectionTransformation, progressTracker); - - return GeoDist.geodesicInverse(a, e2, projected1.getXY(), projected2.getXY()); - } + @Override + public InverseResult execute(Geometry geom1, Geometry geom2, SpatialReference sr1, SpatialReference sr2, int geodeticCurveType, ProgressTracker progressTracker) { + if (geodeticCurveType != GeodeticCurveType.Geodesic) { + throw new GeometryException("only Geodesic implemented"); + } + if (geom1.getType() != Geometry.Type.Point || geom2.getType() != Geometry.Type.Point) { + throw new GeometryException("only implemented for points"); + } + + double a, e2; + Point projected1; + ProjectionTransformation projectionTransformation; + if (sr1.getCoordinateSystemType() != SpatialReference.CoordinateSystemType.GEOGRAPHIC) { + SpatialReference wgs84 = SpatialReference.create(4326); + projectionTransformation = new ProjectionTransformation(sr1, wgs84); + sr1 = wgs84; + projected1 = (Point) OperatorProject.local().execute(geom1, projectionTransformation, progressTracker); + a = wgs84.getMajorAxis(); + e2 = wgs84.getEccentricitySquared(); + } else { + projected1 = (Point) geom1; + a = sr1.getMajorAxis(); + e2 = sr1.getEccentricitySquared(); + } + + // TODO it'd be great if this was reusable so we didn't have this mem allocaiton + projectionTransformation = new ProjectionTransformation(sr2, sr1); + Point projected2 = (Point) OperatorProject.local().execute(geom2, projectionTransformation, progressTracker); + + return GeoDist.geodesicInverse(a, e2, projected1.getXY(), projected2.getXY()); + } } diff --git a/src/main/java/com/esri/core/geometry/OperatorGeodeticLength.java b/src/main/java/com/esri/core/geometry/OperatorGeodeticLength.java index 665c7227..a58a4fb1 100644 --- a/src/main/java/com/esri/core/geometry/OperatorGeodeticLength.java +++ b/src/main/java/com/esri/core/geometry/OperatorGeodeticLength.java @@ -29,27 +29,27 @@ */ public abstract class OperatorGeodeticLength extends Operator { - @Override - public Type getType() { - return Operator.Type.GeodeticLength; - } - - /** - * Calculates the geodetic length of the input Geometry. - * - * @param geom The input Geometry for the geodetic length calculation. - * @param sr The SpatialReference of the Geometry. - * @param geodeticCurveType Use the {@link GeodeticCurveType} interface to choose the - * interpretation of a line connecting two points. - * @param progressTracker - * @return Returns the geoetic length of the Geometry. - */ - public abstract double execute(Geometry geom, SpatialReference sr, - int geodeticCurveType, ProgressTracker progressTracker); - - public static OperatorGeodeticLength local() { - return (OperatorGeodeticLength) OperatorFactoryLocal.getInstance() - .getOperator(Type.GeodeticLength); - } + @Override + public Type getType() { + return Operator.Type.GeodeticLength; + } + + /** + * Calculates the geodetic length of the input Geometry. + * + * @param geom The input Geometry for the geodetic length calculation. + * @param sr The SpatialReference of the Geometry. + * @param geodeticCurveType Use the {@link GeodeticCurveType} interface to choose the + * interpretation of a line connecting two points. + * @param progressTracker + * @return Returns the geoetic length of the Geometry. + */ + public abstract double execute(Geometry geom, SpatialReference sr, + int geodeticCurveType, ProgressTracker progressTracker); + + public static OperatorGeodeticLength local() { + return (OperatorGeodeticLength) OperatorFactoryLocal.getInstance() + .getOperator(Type.GeodeticLength); + } } diff --git a/src/main/java/com/esri/core/geometry/OperatorGeodeticLengthLocal.java b/src/main/java/com/esri/core/geometry/OperatorGeodeticLengthLocal.java index 822c311d..e1ba8048 100644 --- a/src/main/java/com/esri/core/geometry/OperatorGeodeticLengthLocal.java +++ b/src/main/java/com/esri/core/geometry/OperatorGeodeticLengthLocal.java @@ -26,43 +26,43 @@ //This is a stub class OperatorGeodeticLengthLocal extends OperatorGeodeticLength { - @Override - public double execute(Geometry geom, - SpatialReference sr, - int geodeticCurveType, - ProgressTracker progressTracker) { - if (geodeticCurveType != GeodeticCurveType.Geodesic) { - throw new GeometryException("only Geodesic implemented"); - } - if (geom.getType() == Geometry.Type.MultiPoint || geom.getType() == Geometry.Type.Point) { - return 0; - } + @Override + public double execute(Geometry geom, + SpatialReference sr, + int geodeticCurveType, + ProgressTracker progressTracker) { + if (geodeticCurveType != GeodeticCurveType.Geodesic) { + throw new GeometryException("only Geodesic implemented"); + } + if (geom.getType() == Geometry.Type.MultiPoint || geom.getType() == Geometry.Type.Point) { + return 0; + } - SegmentIteratorImpl segIter; - double a, e2; - if (sr.getCoordinateSystemType() != SpatialReference.CoordinateSystemType.GEOGRAPHIC) { - SpatialReference wgs84 = SpatialReference.create(4326); - ProjectionTransformation projectionTransformation = new ProjectionTransformation(sr, wgs84); - Geometry projected = OperatorProject.local().execute(geom, projectionTransformation, progressTracker); - segIter = ((MultiPathImpl)projected._getImpl()).querySegmentIterator(); - a = wgs84.getMajorAxis(); - e2 = wgs84.getEccentricitySquared(); - } else { - segIter = ((MultiPathImpl)geom._getImpl()).querySegmentIterator(); - a = sr.getMajorAxis(); - e2 = sr.getEccentricitySquared(); - } + SegmentIteratorImpl segIter; + double a, e2; + if (sr.getCoordinateSystemType() != SpatialReference.CoordinateSystemType.GEOGRAPHIC) { + SpatialReference wgs84 = SpatialReference.create(4326); + ProjectionTransformation projectionTransformation = new ProjectionTransformation(sr, wgs84); + Geometry projected = OperatorProject.local().execute(geom, projectionTransformation, progressTracker); + segIter = ((MultiPathImpl) projected._getImpl()).querySegmentIterator(); + a = wgs84.getMajorAxis(); + e2 = wgs84.getEccentricitySquared(); + } else { + segIter = ((MultiPathImpl) geom._getImpl()).querySegmentIterator(); + a = sr.getMajorAxis(); + e2 = sr.getEccentricitySquared(); + } - MathUtils.KahanSummator len = new MathUtils.KahanSummator(0); + MathUtils.KahanSummator len = new MathUtils.KahanSummator(0); - while (segIter.nextPath()) { - while (segIter.hasNextSegment()) { - Segment segment = segIter.nextSegment(); - double dist = GeoDist.geodesicDistance(a, e2, segment.getStartXY(), segment.getEndXY(), null, null); - len.add(dist); - } - } + while (segIter.nextPath()) { + while (segIter.hasNextSegment()) { + Segment segment = segIter.nextSegment(); + double dist = GeoDist.geodesicDistance(a, e2, segment.getStartXY(), segment.getEndXY(), null, null); + len.add(dist); + } + } - return len.getResult(); - } + return len.getResult(); + } } diff --git a/src/main/java/com/esri/core/geometry/OperatorImportFromESRIShape.java b/src/main/java/com/esri/core/geometry/OperatorImportFromESRIShape.java index 469359c4..f41e4554 100644 --- a/src/main/java/com/esri/core/geometry/OperatorImportFromESRIShape.java +++ b/src/main/java/com/esri/core/geometry/OperatorImportFromESRIShape.java @@ -32,41 +32,41 @@ * Import from ESRI shape format. */ public abstract class OperatorImportFromESRIShape extends Operator { - @Override - public Type getType() { - return Type.ImportFromESRIShape; - } + @Override + public Type getType() { + return Type.ImportFromESRIShape; + } - /** - * Performs the ImportFromESRIShape operation on a stream of shape buffers - * - * @param importFlags Use the {@link ShapeImportFlags} interface. The default is 0, which means geometry comes - * from a trusted source and is topologically simple. - * If the geometry comes from non-trusted source (that is it can be non-simple), pass ShapeImportNonTrusted. - * @param type The geometry type that you want to import. Use the {@link Geometry.Type} enum. It can be - * Geometry.Type.Unknown if the type of geometry has to be - * figured out from the shape buffer. - * @param shapeBuffers The cursor over shape buffers that hold the Geometries in ESRIShape format. - * @return Returns a GeometryCursor. - */ - public abstract GeometryCursor execute(int importFlags, Geometry.Type type, ByteBufferCursor shapeBuffers); + /** + * Performs the ImportFromESRIShape operation on a stream of shape buffers + * + * @param importFlags Use the {@link ShapeImportFlags} interface. The default is 0, which means geometry comes + * from a trusted source and is topologically simple. + * If the geometry comes from non-trusted source (that is it can be non-simple), pass ShapeImportNonTrusted. + * @param type The geometry type that you want to import. Use the {@link Geometry.Type} enum. It can be + * Geometry.Type.Unknown if the type of geometry has to be + * figured out from the shape buffer. + * @param shapeBuffers The cursor over shape buffers that hold the Geometries in ESRIShape format. + * @return Returns a GeometryCursor. + */ + public abstract GeometryCursor execute(int importFlags, Geometry.Type type, ByteBufferCursor shapeBuffers); - /** - * Performs the ImportFromESRIShape operation. - * - * @param importFlags Use the {@link ShapeImportFlags} interface. The default is 0, which means geometry - * comes from a trusted source and is topologically simple. - * If the geometry comes from non-trusted source (that is it can be non-simple), pass ShapeImportNonTrusted. - * @param type The geometry type that you want to import. Use the {@link Geometry.Type} enum. It can be - * Geometry.Type.Unknown if the type of geometry has to be - * figured out from the shape buffer. - * @param shapeBuffer The buffer holding the Geometry in ESRIShape format. - * @return Returns the imported Geometry. - */ - public abstract Geometry execute(int importFlags, Geometry.Type type, ByteBuffer shapeBuffer); + /** + * Performs the ImportFromESRIShape operation. + * + * @param importFlags Use the {@link ShapeImportFlags} interface. The default is 0, which means geometry + * comes from a trusted source and is topologically simple. + * If the geometry comes from non-trusted source (that is it can be non-simple), pass ShapeImportNonTrusted. + * @param type The geometry type that you want to import. Use the {@link Geometry.Type} enum. It can be + * Geometry.Type.Unknown if the type of geometry has to be + * figured out from the shape buffer. + * @param shapeBuffer The buffer holding the Geometry in ESRIShape format. + * @return Returns the imported Geometry. + */ + public abstract Geometry execute(int importFlags, Geometry.Type type, ByteBuffer shapeBuffer); - public static OperatorImportFromESRIShape local() { - return (OperatorImportFromESRIShape) OperatorFactoryLocal.getInstance().getOperator(Type.ImportFromESRIShape); - } + public static OperatorImportFromESRIShape local() { + return (OperatorImportFromESRIShape) OperatorFactoryLocal.getInstance().getOperator(Type.ImportFromESRIShape); + } } diff --git a/src/main/java/com/esri/core/geometry/OperatorImportFromESRIShapeCursor.java b/src/main/java/com/esri/core/geometry/OperatorImportFromESRIShapeCursor.java index 2176eeb9..ce7e0cd9 100644 --- a/src/main/java/com/esri/core/geometry/OperatorImportFromESRIShapeCursor.java +++ b/src/main/java/com/esri/core/geometry/OperatorImportFromESRIShapeCursor.java @@ -32,989 +32,993 @@ public class OperatorImportFromESRIShapeCursor extends GeometryCursor { - ByteBufferCursor m_inputShapeBuffers; - int m_importFlags; - int m_type; - - public OperatorImportFromESRIShapeCursor(int importFlags, int type, ByteBufferCursor shapeBuffers) { - if (shapeBuffers == null) - throw new GeometryException("invalid argument"); - - m_importFlags = importFlags; - m_type = type; - m_inputShapeBuffers = shapeBuffers; - } - - @Override - public boolean hasNext() { return m_inputShapeBuffers != null && m_inputShapeBuffers.hasNext(); } - - @Override - public Geometry next() { - ByteBuffer shapeBuffer = m_inputShapeBuffers.next(); - if (shapeBuffer != null) { - return importFromESRIShape(shapeBuffer); - } - return null; - } - - @Override - public SimpleStateEnum getSimpleState() { - return m_inputShapeBuffers.getSimpleState(); - } - - @Override - public String getFeatureID() { return m_inputShapeBuffers.getFeatureID(); } - - @Override - public long getGeometryID() { - return m_inputShapeBuffers.getByteBufferID(); - } - - private Geometry importFromESRIShape(ByteBuffer shapeBuffer) { - ByteOrder initialOrder = shapeBuffer.order(); - shapeBuffer.order(ByteOrder.LITTLE_ENDIAN); - - try { - // read type - int shapetype = shapeBuffer.getInt(0); - - // Extract general type and modifiers - int generaltype; - int modifiers; - switch (shapetype & ShapeModifiers.ShapeBasicTypeMask) { - // Polygon - case ShapeType.ShapePolygon: - generaltype = ShapeType.ShapeGeneralPolygon; - modifiers = 0; - break; - case ShapeType.ShapePolygonZM: - generaltype = ShapeType.ShapeGeneralPolygon; - modifiers = ShapeModifiers.ShapeHasZs | ShapeModifiers.ShapeHasMs; - break; - case ShapeType.ShapePolygonM: - generaltype = ShapeType.ShapeGeneralPolygon; - modifiers = ShapeModifiers.ShapeHasMs; - break; - case ShapeType.ShapePolygonZ: - generaltype = ShapeType.ShapeGeneralPolygon; - modifiers = ShapeModifiers.ShapeHasZs; - break; - case ShapeType.ShapeGeneralPolygon: - generaltype = ShapeType.ShapeGeneralPolygon; - modifiers = shapetype & ShapeModifiers.ShapeModifierMask; - break; - - // Polyline - case ShapeType.ShapePolyline: - generaltype = ShapeType.ShapeGeneralPolyline; - modifiers = 0; - break; - case ShapeType.ShapePolylineZM: - generaltype = ShapeType.ShapeGeneralPolyline; - modifiers = ShapeModifiers.ShapeHasZs - | (int) ShapeModifiers.ShapeHasMs; - break; - case ShapeType.ShapePolylineM: - generaltype = ShapeType.ShapeGeneralPolyline; - modifiers = ShapeModifiers.ShapeHasMs; - break; - case ShapeType.ShapePolylineZ: - generaltype = ShapeType.ShapeGeneralPolyline; - modifiers = ShapeModifiers.ShapeHasZs; - break; - case ShapeType.ShapeGeneralPolyline: - generaltype = ShapeType.ShapeGeneralPolyline; - modifiers = shapetype & ShapeModifiers.ShapeModifierMask; - break; - - // MultiPoint - case ShapeType.ShapeMultiPoint: - generaltype = ShapeType.ShapeGeneralMultiPoint; - modifiers = 0; - break; - case ShapeType.ShapeMultiPointZM: - generaltype = ShapeType.ShapeGeneralMultiPoint; - modifiers = (int) ShapeModifiers.ShapeHasZs - | (int) ShapeModifiers.ShapeHasMs; - break; - case ShapeType.ShapeMultiPointM: - generaltype = ShapeType.ShapeGeneralMultiPoint; - modifiers = ShapeModifiers.ShapeHasMs; - break; - case ShapeType.ShapeMultiPointZ: - generaltype = ShapeType.ShapeGeneralMultiPoint; - modifiers = ShapeModifiers.ShapeHasZs; - break; - case ShapeType.ShapeGeneralMultiPoint: - generaltype = ShapeType.ShapeGeneralMultiPoint; - modifiers = shapetype & ShapeModifiers.ShapeModifierMask; - break; - - // Point - case ShapeType.ShapePoint: - generaltype = ShapeType.ShapeGeneralPoint; - modifiers = 0; - break; - case ShapeType.ShapePointZM: - generaltype = ShapeType.ShapeGeneralPoint; - modifiers = ShapeModifiers.ShapeHasZs - | (int) ShapeModifiers.ShapeHasMs; - break; - case ShapeType.ShapePointM: - generaltype = ShapeType.ShapeGeneralPoint; - modifiers = ShapeModifiers.ShapeHasMs; - break; - case ShapeType.ShapePointZ: - generaltype = ShapeType.ShapeGeneralPoint; - modifiers = ShapeModifiers.ShapeHasZs; - break; - case ShapeType.ShapeGeneralPoint: - generaltype = ShapeType.ShapeGeneralPoint; - modifiers = shapetype & ShapeModifiers.ShapeModifierMask; - break; - - // Null Geometry - case ShapeType.ShapeNull: - return null; - - default: - throw new GeometryException("invalid shape type"); - } - - switch (generaltype) { - case ShapeType.ShapeGeneralPolygon: - if (m_type != Geometry.GeometryType.Polygon - && m_type != Geometry.GeometryType.Unknown - && m_type != Geometry.GeometryType.Envelope) - throw new GeometryException("invalid shape type"); - return importFromESRIShapeMultiPath(true, modifiers, - shapeBuffer); - - case ShapeType.ShapeGeneralPolyline: - if (m_type != Geometry.GeometryType.Polyline - && m_type != Geometry.GeometryType.Unknown - && m_type != Geometry.GeometryType.Envelope) - throw new GeometryException("invalid shape type"); - return importFromESRIShapeMultiPath(false, modifiers, - shapeBuffer); - - case ShapeType.ShapeGeneralMultiPoint: - if (m_type != Geometry.GeometryType.MultiPoint - && m_type != Geometry.GeometryType.Unknown - && m_type != Geometry.GeometryType.Envelope) - throw new GeometryException("invalid shape type"); - return importFromESRIShapeMultiPoint(modifiers, shapeBuffer); - - case ShapeType.ShapeGeneralPoint: - if (m_type != Geometry.GeometryType.Point - && m_type != Geometry.GeometryType.MultiPoint - && m_type != Geometry.GeometryType.Unknown - && m_type != Geometry.GeometryType.Envelope) - throw new GeometryException("invalid shape type"); - return importFromESRIShapePoint(modifiers, shapeBuffer); - } - - return null; - } finally { - shapeBuffer.order(initialOrder); - } - } - - private Geometry importFromESRIShapeMultiPath(boolean bPolygon, - int modifiers, ByteBuffer shapeBuffer) { - int offset = 4; - - boolean bZs = (modifiers & (int) ShapeModifiers.ShapeHasZs) != 0; - boolean bMs = (modifiers & (int) ShapeModifiers.ShapeHasMs) != 0; - boolean bIDs = (modifiers & (int) ShapeModifiers.ShapeHasIDs) != 0; - - boolean bHasAttributes = bZs || bMs || bIDs; - boolean bHasBadRings = false; - - // read Envelope - double xmin = shapeBuffer.getDouble(offset); - offset += 8; - double ymin = shapeBuffer.getDouble(offset); - offset += 8; - double xmax = shapeBuffer.getDouble(offset); - offset += 8; - double ymax = shapeBuffer.getDouble(offset); - offset += 8; - - // read part count - int originalPartCount = shapeBuffer.getInt(offset); - offset += 4; - int partCount = 0; - - // read point count - int pointCount = shapeBuffer.getInt(offset); - offset += 4; - - AttributeStreamOfDbl position = null; - AttributeStreamOfDbl zs = null; - AttributeStreamOfDbl ms = null; - AttributeStreamOfInt32 ids = null; - AttributeStreamOfInt32 parts = null; - AttributeStreamOfInt8 pathFlags = null; - - Envelope bbox = null; - MultiPath multipath = null; - MultiPathImpl multipathImpl = null; - if (m_type == Geometry.GeometryType.Polygon - || m_type == Geometry.GeometryType.Polyline - || m_type == Geometry.GeometryType.Unknown) { - if (bPolygon) - multipath = new Polygon(); - else - multipath = new Polyline(); - - multipathImpl = (MultiPathImpl) multipath._getImpl(); - - if (pointCount > 0) { - bbox = new Envelope(); - bbox.setCoords(xmin, ymin, xmax, ymax); - parts = (AttributeStreamOfInt32) AttributeStreamBase - .createIndexStream(originalPartCount + 1); - - int previstart = -1; - int lastCount = 0; - for (int i = 0; i < originalPartCount; i++) { - int istart = shapeBuffer.getInt(offset); - offset += 4; - lastCount = istart; - if (previstart > istart || istart < 0)// check that the part - // indices in the - // buffer are not - // corrupted - throw new GeometryException("corrupted geometry"); - - if (istart != previstart) { - parts.write(partCount, istart); - previstart = istart; - partCount++; - } - } - - parts.resize(partCount + 1); - if (pointCount < lastCount)// check that the point count in the - // buffer is not corrupted - throw new GeometryException("corrupted geometry"); - - parts.write(partCount, pointCount); - pathFlags = (AttributeStreamOfInt8) AttributeStreamBase - .createByteStream(parts.size(), (byte) 0); - - // Create empty position stream - position = (AttributeStreamOfDbl) AttributeStreamBase - .createAttributeStreamWithSemantics(Semantics.POSITION, - pointCount); - - int startpart = parts.read(0); - // read xy coordinates - int xyindex = 0; - for (int ipart = 0; ipart < partCount; ipart++) { - int endpartActual = parts.read(ipart + 1); - // for polygons we read one point less, then analyze if the - // polygon is closed. - int endpart = (bPolygon) ? endpartActual - 1 - : endpartActual; - - double startx = shapeBuffer.getDouble(offset); - offset += 8; - double starty = shapeBuffer.getDouble(offset); - offset += 8; - position.write(2 * xyindex, startx); - position.write(2 * xyindex + 1, starty); - xyindex++; - - for (int i = startpart + 1; i < endpart; i++) { - double x = shapeBuffer.getDouble(offset); - offset += 8; - double y = shapeBuffer.getDouble(offset); - offset += 8; - position.write(2 * xyindex, x); - position.write(2 * xyindex + 1, y); - xyindex++; - } - - if (endpart - startpart < 2) {// a part with only one point - multipathImpl.setIsSimple(GeometryXSimple.Unknown, 0.0, - false); - } - - if (bPolygon) {// read the last point of the part to decide - // if we need to close the polygon - if (startpart == endpart) {// a part with only one point - parts.write(ipart + 1, xyindex); - } else { - double x = shapeBuffer.getDouble(offset); - offset += 8; - double y = shapeBuffer.getDouble(offset); - offset += 8; - - if (x != startx || y != starty) {// bad polygon. The - // last point is - // not the same - // as the last - // one. We need - // to add it so - // that we do - // not loose it. - position.write(2 * xyindex, x); - position.write(2 * xyindex + 1, y); - xyindex++; - multipathImpl.setIsSimple( - GeometryXSimple.Unknown, 0.0, false); - bHasBadRings = true; - // write part count to indicate we need to - // account for one extra point - // The count will be fixed after the attributes - // are processed. So we write negative only when - // there are attributes. - parts.write(ipart + 1, - bHasAttributes ? -xyindex : xyindex); - } else - parts.write(ipart + 1, xyindex); - } - - pathFlags.setBits(ipart, (byte) PathFlags.enumClosed); - } - - startpart = endpartActual; - } - - if (bZs) - bbox.addAttribute(Semantics.Z); - - if (bMs) - bbox.addAttribute(Semantics.M); - - if (bIDs) - bbox.addAttribute(Semantics.ID); - } - } else { - bbox = new Envelope(); - - if (bZs) - bbox.addAttribute(Semantics.Z); - - if (bMs) - bbox.addAttribute(Semantics.M); - - if (bIDs) - bbox.addAttribute(Semantics.ID); - - if (pointCount > 0) { - bbox.setCoords(xmin, ymin, xmax, ymax); - offset += pointCount * 16 + originalPartCount * 4; - } else - return (Geometry) bbox; - } - - // read Zs - if (bZs) { - if (pointCount > 0) { - double zmin = Interop.translateFromAVNaN(shapeBuffer - .getDouble(offset)); - offset += 8; - double zmax = Interop.translateFromAVNaN(shapeBuffer - .getDouble(offset)); - offset += 8; - - Envelope1D env = new Envelope1D(); - env.setCoords(zmin, zmax); - bbox.setInterval(Semantics.Z, 0, env); - - if (m_type == Geometry.GeometryType.Polygon - || m_type == Geometry.GeometryType.Polyline - || m_type == Geometry.GeometryType.Unknown) { - zs = (AttributeStreamOfDbl) AttributeStreamBase - .createAttributeStreamWithSemantics(Semantics.Z, - pointCount); - - boolean bCreate = false; - int startpart = parts.read(0); - for (int ipart = 0; ipart < partCount; ipart++) { - int endpartActual = parts.read(ipart + 1); - int endpart = Math.abs(endpartActual); - - double startz = Interop.translateFromAVNaN(shapeBuffer - .getDouble(offset)); - offset += 8; - zs.write(startpart, startz); - if (!VertexDescription.isDefaultValue(Semantics.Z, - startz)) - bCreate = true; - - for (int i = startpart + 1; i < endpart; i++) { - double z = Interop.translateFromAVNaN(shapeBuffer - .getDouble(offset)); - offset += 8; - zs.write(i, z); - if (!VertexDescription.isDefaultValue(Semantics.Z, - z)) - bCreate = true; - } - - if (bPolygon && endpartActual > 0) { - offset += 8; - } - - startpart = endpart; - } - - if (!bCreate) - zs = null; - } else - offset += pointCount * 8; - } - - if (m_type == Geometry.GeometryType.Polygon - || m_type == Geometry.GeometryType.Polyline - || m_type == Geometry.GeometryType.Unknown) - multipathImpl.setAttributeStreamRef(Semantics.Z, zs); - } - - // read Ms - if (bMs) { - if (pointCount > 0) { - double mmin = Interop.translateFromAVNaN(shapeBuffer - .getDouble(offset)); - offset += 8; - double mmax = Interop.translateFromAVNaN(shapeBuffer - .getDouble(offset)); - offset += 8; - - Envelope1D env = new Envelope1D(); - env.setCoords(mmin, mmax); - bbox.setInterval(Semantics.M, 0, env); - - if (m_type == Geometry.GeometryType.Polygon - || m_type == Geometry.GeometryType.Polyline - || m_type == Geometry.GeometryType.Unknown) { - ms = (AttributeStreamOfDbl) AttributeStreamBase - .createAttributeStreamWithSemantics(Semantics.M, - pointCount); - - boolean bCreate = false; - int startpart = parts.read(0); - for (int ipart = 0; ipart < partCount; ipart++) { - int endpartActual = parts.read(ipart + 1); - int endpart = Math.abs(endpartActual); - - double startm = Interop.translateFromAVNaN(shapeBuffer - .getDouble(offset)); - offset += 8; - ms.write(startpart, startm); - if (!VertexDescription.isDefaultValue(Semantics.M, - startm)) - bCreate = true; - - for (int i = startpart + 1; i < endpart; i++) { - double m = Interop.translateFromAVNaN(shapeBuffer - .getDouble(offset)); - offset += 8; - ms.write(i, m); - if (!VertexDescription.isDefaultValue(Semantics.M, - m)) - bCreate = true; - } - - if (bPolygon && endpartActual > 0) { - offset += 8; - } - - startpart = endpart; - } - - if (!bCreate) - ms = null; - } else - offset += pointCount * 8; - } - - if (m_type == Geometry.GeometryType.Polygon - || m_type == Geometry.GeometryType.Polyline - || m_type == Geometry.GeometryType.Unknown) - multipathImpl.setAttributeStreamRef(Semantics.M, ms); - } - - // read IDs - if (bIDs) { - if (pointCount > 0) { - double idmin = NumberUtils.doubleMax(); - double idmax = -NumberUtils.doubleMax(); - - if (m_type == Geometry.GeometryType.Polygon - || m_type == Geometry.GeometryType.Polyline - || m_type == Geometry.GeometryType.Unknown) { - ids = (AttributeStreamOfInt32) AttributeStreamBase - .createAttributeStreamWithSemantics(Semantics.ID, - pointCount); - - boolean bCreate = false; - int startpart = parts.read(0); - for (int ipart = 0; ipart < partCount; ipart++) { - int endpartActual = parts.read(ipart + 1); - int endpart = Math.abs(endpartActual); - - int startid = shapeBuffer.getInt(offset); - offset += 4; - ids.write(startpart, startid); - if (!VertexDescription.isDefaultValue(Semantics.ID, - startid)) - bCreate = true; - - for (int i = startpart + 1; i < endpart; i++) { - int id = shapeBuffer.getInt(offset); - offset += 4; - ids.write(i, id); - if (!bCreate - && !VertexDescription.isDefaultValue( - Semantics.ID, id)) - bCreate = true; - - if (idmin > id) - idmin = id; - else if (idmax < id) - idmax = id; - } - - if (bPolygon && endpartActual > 0) { - offset += 4; - } - - startpart = endpart; - } - - if (!bCreate) - ids = null; - } else { - for (int i = 0; i < pointCount; i++) { - int id = shapeBuffer.getInt(offset); - offset += 4; - - if (idmin > id) - idmin = id; - else if (idmax < id) - idmax = id; - } - } - - Envelope1D env = new Envelope1D(); - env.setCoords(idmin, idmax); - bbox.setInterval(Semantics.ID, 0, env); - } - - if (m_type == Geometry.GeometryType.Polygon - || m_type == Geometry.GeometryType.Polyline - || m_type == Geometry.GeometryType.Unknown) - multipathImpl.setAttributeStreamRef(Semantics.ID, ids); - } - - if (bHasBadRings && bHasAttributes) {// revert our hack for bad polygons - for (int ipart = 1; ipart < partCount + 1; ipart++) { - int v = parts.read(ipart); - if (v < 0) - parts.write(ipart, -v); - } - } - - if (m_type == Geometry.GeometryType.Envelope) - return (Geometry) bbox; - - if (pointCount > 0) { - multipathImpl.setPathStreamRef(parts); - multipathImpl.setPathFlagsStreamRef(pathFlags); - multipathImpl.setAttributeStreamRef(Semantics.POSITION, position); - multipathImpl.setEnvelope(bbox); - } - - if ((m_importFlags & ShapeImportFlags.ShapeImportNonTrusted) == 0) - multipathImpl.setIsSimple(GeometryXSimple.Weak, 0.0, false);// We - // use - // tolerance - // of 0. - // What - // should - // we - // instead? - - return (Geometry) multipath; - } - - private Geometry importFromESRIShapeMultiPoint(int modifiers, - ByteBuffer shapeBuffer) { - int offset = 4; - - boolean bZs = (modifiers & (int) ShapeModifiers.ShapeHasZs) != 0; - boolean bMs = (modifiers & (int) ShapeModifiers.ShapeHasMs) != 0; - boolean bIDs = (modifiers & modifiers & (int) ShapeModifiers.ShapeHasIDs) != 0; - - double xmin = shapeBuffer.getDouble(offset); - offset += 8; - double ymin = shapeBuffer.getDouble(offset); - offset += 8; - double xmax = shapeBuffer.getDouble(offset); - offset += 8; - double ymax = shapeBuffer.getDouble(offset); - offset += 8; - - int cPoints = shapeBuffer.getInt(offset); - offset += 4; - - AttributeStreamOfDbl position = null; - AttributeStreamOfDbl zs = null; - AttributeStreamOfDbl ms = null; - AttributeStreamOfInt32 ids = null; - - Envelope bbox = null; - MultiPoint multipoint = null; - MultiPointImpl multipointImpl = null; - - if (m_type == Geometry.GeometryType.MultiPoint - || m_type == Geometry.GeometryType.Unknown) { - multipoint = new MultiPoint(); - multipointImpl = (MultiPointImpl) multipoint._getImpl(); - - if (cPoints > 0) { - bbox = new Envelope(); - multipointImpl.resize(cPoints); - position = (AttributeStreamOfDbl) AttributeStreamBase - .createAttributeStreamWithSemantics(Semantics.POSITION, - cPoints); - - for (int i = 0; i < cPoints; i++) { - double x = shapeBuffer.getDouble(offset); - offset += 8; - double y = shapeBuffer.getDouble(offset); - offset += 8; - position.write(2 * i, x); - position.write(2 * i + 1, y); - } - - multipointImpl.resize(cPoints); - bbox.setCoords(xmin, ymin, xmax, ymax); - - if (bZs) - bbox.addAttribute(Semantics.Z); - - if (bMs) - bbox.addAttribute(Semantics.M); - - if (bIDs) - bbox.addAttribute(Semantics.ID); - } - } else { - bbox = new Envelope(); - - if (bZs) - bbox.addAttribute(Semantics.Z); - - if (bMs) - bbox.addAttribute(Semantics.M); - - if (bIDs) - bbox.addAttribute(Semantics.ID); - - if (cPoints > 0) { - bbox.setCoords(xmin, ymin, xmax, ymax); - offset += cPoints * 16; - } else - return (Geometry) bbox; - } - - if (bZs) { - if (cPoints > 0) { - double zmin = Interop.translateFromAVNaN(shapeBuffer - .getDouble(offset)); - offset += 8; - double zmax = Interop.translateFromAVNaN(shapeBuffer - .getDouble(offset)); - offset += 8; - - Envelope1D env = new Envelope1D(); - env.setCoords(zmin, zmax); - bbox.setInterval(Semantics.Z, 0, env); - - if (m_type == Geometry.GeometryType.MultiPoint - || m_type == Geometry.GeometryType.Unknown) { - zs = (AttributeStreamOfDbl) AttributeStreamBase - .createAttributeStreamWithSemantics(Semantics.Z, - cPoints); - - boolean bCreate = false; - for (int i = 0; i < cPoints; i++) { - double value = Interop.translateFromAVNaN(shapeBuffer - .getDouble(offset)); - offset += 8; - zs.write(i, value); - if (!VertexDescription.isDefaultValue(Semantics.Z, - value)) - bCreate = true; - } - - if (!bCreate) - zs = null; - } else - offset += cPoints * 8; - } - - if (m_type == Geometry.GeometryType.MultiPoint - || m_type == Geometry.GeometryType.Unknown) - multipointImpl.setAttributeStreamRef(Semantics.Z, zs); - } - - if (bMs) { - if (cPoints > 0) { - double mmin = Interop.translateFromAVNaN(shapeBuffer - .getDouble(offset)); - offset += 8; - double mmax = Interop.translateFromAVNaN(shapeBuffer - .getDouble(offset)); - offset += 8; - - Envelope1D env = new Envelope1D(); - env.setCoords(mmin, mmax); - bbox.setInterval(Semantics.M, 0, env); - if (m_type == Geometry.GeometryType.MultiPoint - || m_type == Geometry.GeometryType.Unknown) { - ms = (AttributeStreamOfDbl) AttributeStreamBase - .createAttributeStreamWithSemantics(Semantics.M, - cPoints); - - boolean bCreate = false; - for (int i = 0; i < cPoints; i++) { - double value = Interop.translateFromAVNaN(shapeBuffer - .getDouble(offset)); - offset += 8; - ms.write(i, value); - if (!VertexDescription.isDefaultValue(Semantics.M, - value)) - bCreate = true; - } - - if (!bCreate) - ms = null; - } else - offset += cPoints * 8; - } - - if (m_type == Geometry.GeometryType.MultiPoint - || m_type == Geometry.GeometryType.Unknown) - multipointImpl.setAttributeStreamRef(Semantics.M, ms); - } - - if (bIDs) { - if (cPoints > 0) { - double idmin = NumberUtils.doubleMax(); - double idmax = -NumberUtils.doubleMax(); - - if (m_type == Geometry.GeometryType.MultiPoint - || m_type == Geometry.GeometryType.Unknown) { - ids = (AttributeStreamOfInt32) AttributeStreamBase - .createAttributeStreamWithSemantics(Semantics.ID, - cPoints); - - boolean bCreate = false; - for (int i = 0; i < cPoints; i++) { - int value = shapeBuffer.getInt(offset); - offset += 4; - ids.write(i, value); - if (!VertexDescription.isDefaultValue(Semantics.ID, - value)) - bCreate = true; - - if (idmin > value) - idmin = value; - else if (idmax < value) - idmax = value; - } - - if (!bCreate) - ids = null; - } else { - for (int i = 0; i < cPoints; i++) { - int id = shapeBuffer.getInt(offset); - offset += 4; - - if (idmin > id) - idmin = id; - else if (idmax < id) - idmax = id; - } - } - - Envelope1D env = new Envelope1D(); - env.setCoords(idmin, idmax); - bbox.setInterval(Semantics.ID, 0, env); - } - - if (m_type == Geometry.GeometryType.MultiPoint - || m_type == Geometry.GeometryType.Unknown) - multipointImpl.setAttributeStreamRef(Semantics.ID, ids); - } - - if (m_type == Geometry.GeometryType.Envelope) - return (Geometry) bbox; - - if (cPoints > 0) { - multipointImpl.setAttributeStreamRef(Semantics.POSITION, position); - multipointImpl.setEnvelope(bbox); - } - - return (Geometry) multipoint; - } - - private Geometry importFromESRIShapePoint(int modifiers, - ByteBuffer shapeBuffer) { - int offset = 4; - - boolean bZs = (modifiers & (int) ShapeModifiers.ShapeHasZs) != 0; - boolean bMs = (modifiers & (int) ShapeModifiers.ShapeHasMs) != 0; - boolean bIDs = (modifiers & modifiers & (int) ShapeModifiers.ShapeHasIDs) != 0; - - // read XY - double x = shapeBuffer.getDouble(offset); - offset += 8; - double y = shapeBuffer.getDouble(offset); - offset += 8; - - boolean bEmpty = NumberUtils.isNaN(x); - - double z = NumberUtils.NaN(); - if (bZs) { - z = Interop.translateFromAVNaN(shapeBuffer.getDouble(offset)); - offset += 8; - } - - double m = NumberUtils.NaN(); - if (bMs) { - m = Interop.translateFromAVNaN(shapeBuffer.getDouble(offset)); - offset += 8; - } - - int id = -1; - if (bIDs) { - id = shapeBuffer.getInt(offset); - offset += 4; - } - - if (m_type == Geometry.GeometryType.MultiPoint) { - MultiPoint newmultipoint = new MultiPoint(); - MultiPointImpl multipointImpl = (MultiPointImpl) newmultipoint - ._getImpl(); - - if (!bEmpty) { - AttributeStreamBase newPositionStream = AttributeStreamBase - .createAttributeStreamWithSemantics(Semantics.POSITION, - 1); - AttributeStreamOfDbl position = (AttributeStreamOfDbl) newPositionStream; - position.write(0, x); - position.write(1, y); - - multipointImpl.setAttributeStreamRef(Semantics.POSITION, - newPositionStream); - multipointImpl.resize(1); - } - - if (bZs) { - multipointImpl.addAttribute(Semantics.Z); - if (!bEmpty - && !VertexDescription.isDefaultValue(Semantics.Z, z)) { - AttributeStreamBase newZStream = AttributeStreamBase - .createAttributeStreamWithSemantics(Semantics.Z, 1); - newZStream.writeAsDbl(0, z); - multipointImpl.setAttributeStreamRef(Semantics.Z, - newZStream); - } - } - - if (bMs) { - multipointImpl.addAttribute(Semantics.M); - if (!bEmpty - && !VertexDescription.isDefaultValue(Semantics.M, m)) { - AttributeStreamBase newMStream = AttributeStreamBase - .createAttributeStreamWithSemantics(Semantics.M, 1); - newMStream.writeAsDbl(0, m); - multipointImpl.setAttributeStreamRef(Semantics.M, - newMStream); - } - } - - if (bIDs) { - multipointImpl.addAttribute(Semantics.ID); - if (!bEmpty - && !VertexDescription.isDefaultValue(Semantics.ID, id)) { - AttributeStreamBase newIDStream = AttributeStreamBase - .createAttributeStreamWithSemantics(Semantics.ID, 1); - newIDStream.writeAsInt(0, id); - multipointImpl.setAttributeStreamRef(Semantics.ID, - newIDStream); - } - } - - return (Geometry) newmultipoint; - } else if (m_type == Geometry.GeometryType.Envelope) { - Envelope envelope = new Envelope(); - envelope.setCoords(x, y, x, y); - - if (bZs) { - Envelope1D interval = new Envelope1D(); - interval.vmin = z; - interval.vmax = z; - envelope.addAttribute(Semantics.Z); - envelope.setInterval(Semantics.Z, 0, interval); - } - - if (bMs) { - Envelope1D interval = new Envelope1D(); - interval.vmin = m; - interval.vmax = m; - envelope.addAttribute(Semantics.M); - envelope.setInterval(Semantics.M, 0, interval); - } - - if (bIDs) { - Envelope1D interval = new Envelope1D(); - interval.vmin = id; - interval.vmax = id; - envelope.addAttribute(Semantics.ID); - envelope.setInterval(Semantics.ID, 0, interval); - } - - return (Geometry) envelope; - } - - Point point = new Point(); - - if (!bEmpty) { - point.setX(Interop.translateFromAVNaN(x)); - point.setY(Interop.translateFromAVNaN(y)); - } - - // read Z - if (bZs) { - point.addAttribute(Semantics.Z); - if (!bEmpty) - point.setZ(Interop.translateFromAVNaN(z)); - } - - // read M - if (bMs) { - point.addAttribute(Semantics.M); - if (!bEmpty) - point.setM(Interop.translateFromAVNaN(m)); - } - - // read ID - if (bIDs) { - point.addAttribute(Semantics.ID); - if (!bEmpty) - point.setID(id); - } - - return (Geometry) point; - } + ByteBufferCursor m_inputShapeBuffers; + int m_importFlags; + int m_type; + + public OperatorImportFromESRIShapeCursor(int importFlags, int type, ByteBufferCursor shapeBuffers) { + if (shapeBuffers == null) + throw new GeometryException("invalid argument"); + + m_importFlags = importFlags; + m_type = type; + m_inputShapeBuffers = shapeBuffers; + } + + @Override + public boolean hasNext() { + return m_inputShapeBuffers != null && m_inputShapeBuffers.hasNext(); + } + + @Override + public Geometry next() { + ByteBuffer shapeBuffer = m_inputShapeBuffers.next(); + if (shapeBuffer != null) { + return importFromESRIShape(shapeBuffer); + } + return null; + } + + @Override + public SimpleStateEnum getSimpleState() { + return m_inputShapeBuffers.getSimpleState(); + } + + @Override + public String getFeatureID() { + return m_inputShapeBuffers.getFeatureID(); + } + + @Override + public long getGeometryID() { + return m_inputShapeBuffers.getByteBufferID(); + } + + private Geometry importFromESRIShape(ByteBuffer shapeBuffer) { + ByteOrder initialOrder = shapeBuffer.order(); + shapeBuffer.order(ByteOrder.LITTLE_ENDIAN); + + try { + // read type + int shapetype = shapeBuffer.getInt(0); + + // Extract general type and modifiers + int generaltype; + int modifiers; + switch (shapetype & ShapeModifiers.ShapeBasicTypeMask) { + // Polygon + case ShapeType.ShapePolygon: + generaltype = ShapeType.ShapeGeneralPolygon; + modifiers = 0; + break; + case ShapeType.ShapePolygonZM: + generaltype = ShapeType.ShapeGeneralPolygon; + modifiers = ShapeModifiers.ShapeHasZs | ShapeModifiers.ShapeHasMs; + break; + case ShapeType.ShapePolygonM: + generaltype = ShapeType.ShapeGeneralPolygon; + modifiers = ShapeModifiers.ShapeHasMs; + break; + case ShapeType.ShapePolygonZ: + generaltype = ShapeType.ShapeGeneralPolygon; + modifiers = ShapeModifiers.ShapeHasZs; + break; + case ShapeType.ShapeGeneralPolygon: + generaltype = ShapeType.ShapeGeneralPolygon; + modifiers = shapetype & ShapeModifiers.ShapeModifierMask; + break; + + // Polyline + case ShapeType.ShapePolyline: + generaltype = ShapeType.ShapeGeneralPolyline; + modifiers = 0; + break; + case ShapeType.ShapePolylineZM: + generaltype = ShapeType.ShapeGeneralPolyline; + modifiers = ShapeModifiers.ShapeHasZs + | (int) ShapeModifiers.ShapeHasMs; + break; + case ShapeType.ShapePolylineM: + generaltype = ShapeType.ShapeGeneralPolyline; + modifiers = ShapeModifiers.ShapeHasMs; + break; + case ShapeType.ShapePolylineZ: + generaltype = ShapeType.ShapeGeneralPolyline; + modifiers = ShapeModifiers.ShapeHasZs; + break; + case ShapeType.ShapeGeneralPolyline: + generaltype = ShapeType.ShapeGeneralPolyline; + modifiers = shapetype & ShapeModifiers.ShapeModifierMask; + break; + + // MultiPoint + case ShapeType.ShapeMultiPoint: + generaltype = ShapeType.ShapeGeneralMultiPoint; + modifiers = 0; + break; + case ShapeType.ShapeMultiPointZM: + generaltype = ShapeType.ShapeGeneralMultiPoint; + modifiers = (int) ShapeModifiers.ShapeHasZs + | (int) ShapeModifiers.ShapeHasMs; + break; + case ShapeType.ShapeMultiPointM: + generaltype = ShapeType.ShapeGeneralMultiPoint; + modifiers = ShapeModifiers.ShapeHasMs; + break; + case ShapeType.ShapeMultiPointZ: + generaltype = ShapeType.ShapeGeneralMultiPoint; + modifiers = ShapeModifiers.ShapeHasZs; + break; + case ShapeType.ShapeGeneralMultiPoint: + generaltype = ShapeType.ShapeGeneralMultiPoint; + modifiers = shapetype & ShapeModifiers.ShapeModifierMask; + break; + + // Point + case ShapeType.ShapePoint: + generaltype = ShapeType.ShapeGeneralPoint; + modifiers = 0; + break; + case ShapeType.ShapePointZM: + generaltype = ShapeType.ShapeGeneralPoint; + modifiers = ShapeModifiers.ShapeHasZs + | (int) ShapeModifiers.ShapeHasMs; + break; + case ShapeType.ShapePointM: + generaltype = ShapeType.ShapeGeneralPoint; + modifiers = ShapeModifiers.ShapeHasMs; + break; + case ShapeType.ShapePointZ: + generaltype = ShapeType.ShapeGeneralPoint; + modifiers = ShapeModifiers.ShapeHasZs; + break; + case ShapeType.ShapeGeneralPoint: + generaltype = ShapeType.ShapeGeneralPoint; + modifiers = shapetype & ShapeModifiers.ShapeModifierMask; + break; + + // Null Geometry + case ShapeType.ShapeNull: + return null; + + default: + throw new GeometryException("invalid shape type"); + } + + switch (generaltype) { + case ShapeType.ShapeGeneralPolygon: + if (m_type != Geometry.GeometryType.Polygon + && m_type != Geometry.GeometryType.Unknown + && m_type != Geometry.GeometryType.Envelope) + throw new GeometryException("invalid shape type"); + return importFromESRIShapeMultiPath(true, modifiers, + shapeBuffer); + + case ShapeType.ShapeGeneralPolyline: + if (m_type != Geometry.GeometryType.Polyline + && m_type != Geometry.GeometryType.Unknown + && m_type != Geometry.GeometryType.Envelope) + throw new GeometryException("invalid shape type"); + return importFromESRIShapeMultiPath(false, modifiers, + shapeBuffer); + + case ShapeType.ShapeGeneralMultiPoint: + if (m_type != Geometry.GeometryType.MultiPoint + && m_type != Geometry.GeometryType.Unknown + && m_type != Geometry.GeometryType.Envelope) + throw new GeometryException("invalid shape type"); + return importFromESRIShapeMultiPoint(modifiers, shapeBuffer); + + case ShapeType.ShapeGeneralPoint: + if (m_type != Geometry.GeometryType.Point + && m_type != Geometry.GeometryType.MultiPoint + && m_type != Geometry.GeometryType.Unknown + && m_type != Geometry.GeometryType.Envelope) + throw new GeometryException("invalid shape type"); + return importFromESRIShapePoint(modifiers, shapeBuffer); + } + + return null; + } finally { + shapeBuffer.order(initialOrder); + } + } + + private Geometry importFromESRIShapeMultiPath(boolean bPolygon, + int modifiers, ByteBuffer shapeBuffer) { + int offset = 4; + + boolean bZs = (modifiers & (int) ShapeModifiers.ShapeHasZs) != 0; + boolean bMs = (modifiers & (int) ShapeModifiers.ShapeHasMs) != 0; + boolean bIDs = (modifiers & (int) ShapeModifiers.ShapeHasIDs) != 0; + + boolean bHasAttributes = bZs || bMs || bIDs; + boolean bHasBadRings = false; + + // read Envelope + double xmin = shapeBuffer.getDouble(offset); + offset += 8; + double ymin = shapeBuffer.getDouble(offset); + offset += 8; + double xmax = shapeBuffer.getDouble(offset); + offset += 8; + double ymax = shapeBuffer.getDouble(offset); + offset += 8; + + // read part count + int originalPartCount = shapeBuffer.getInt(offset); + offset += 4; + int partCount = 0; + + // read point count + int pointCount = shapeBuffer.getInt(offset); + offset += 4; + + AttributeStreamOfDbl position = null; + AttributeStreamOfDbl zs = null; + AttributeStreamOfDbl ms = null; + AttributeStreamOfInt32 ids = null; + AttributeStreamOfInt32 parts = null; + AttributeStreamOfInt8 pathFlags = null; + + Envelope bbox = null; + MultiPath multipath = null; + MultiPathImpl multipathImpl = null; + if (m_type == Geometry.GeometryType.Polygon + || m_type == Geometry.GeometryType.Polyline + || m_type == Geometry.GeometryType.Unknown) { + if (bPolygon) + multipath = new Polygon(); + else + multipath = new Polyline(); + + multipathImpl = (MultiPathImpl) multipath._getImpl(); + + if (pointCount > 0) { + bbox = new Envelope(); + bbox.setCoords(xmin, ymin, xmax, ymax); + parts = (AttributeStreamOfInt32) AttributeStreamBase + .createIndexStream(originalPartCount + 1); + + int previstart = -1; + int lastCount = 0; + for (int i = 0; i < originalPartCount; i++) { + int istart = shapeBuffer.getInt(offset); + offset += 4; + lastCount = istart; + if (previstart > istart || istart < 0)// check that the part + // indices in the + // buffer are not + // corrupted + throw new GeometryException("corrupted geometry"); + + if (istart != previstart) { + parts.write(partCount, istart); + previstart = istart; + partCount++; + } + } + + parts.resize(partCount + 1); + if (pointCount < lastCount)// check that the point count in the + // buffer is not corrupted + throw new GeometryException("corrupted geometry"); + + parts.write(partCount, pointCount); + pathFlags = (AttributeStreamOfInt8) AttributeStreamBase + .createByteStream(parts.size(), (byte) 0); + + // Create empty position stream + position = (AttributeStreamOfDbl) AttributeStreamBase + .createAttributeStreamWithSemantics(Semantics.POSITION, + pointCount); + + int startpart = parts.read(0); + // read xy coordinates + int xyindex = 0; + for (int ipart = 0; ipart < partCount; ipart++) { + int endpartActual = parts.read(ipart + 1); + // for polygons we read one point less, then analyze if the + // polygon is closed. + int endpart = (bPolygon) ? endpartActual - 1 + : endpartActual; + + double startx = shapeBuffer.getDouble(offset); + offset += 8; + double starty = shapeBuffer.getDouble(offset); + offset += 8; + position.write(2 * xyindex, startx); + position.write(2 * xyindex + 1, starty); + xyindex++; + + for (int i = startpart + 1; i < endpart; i++) { + double x = shapeBuffer.getDouble(offset); + offset += 8; + double y = shapeBuffer.getDouble(offset); + offset += 8; + position.write(2 * xyindex, x); + position.write(2 * xyindex + 1, y); + xyindex++; + } + + if (endpart - startpart < 2) {// a part with only one point + multipathImpl.setIsSimple(GeometryXSimple.Unknown, 0.0, + false); + } + + if (bPolygon) {// read the last point of the part to decide + // if we need to close the polygon + if (startpart == endpart) {// a part with only one point + parts.write(ipart + 1, xyindex); + } else { + double x = shapeBuffer.getDouble(offset); + offset += 8; + double y = shapeBuffer.getDouble(offset); + offset += 8; + + if (x != startx || y != starty) {// bad polygon. The + // last point is + // not the same + // as the last + // one. We need + // to add it so + // that we do + // not loose it. + position.write(2 * xyindex, x); + position.write(2 * xyindex + 1, y); + xyindex++; + multipathImpl.setIsSimple( + GeometryXSimple.Unknown, 0.0, false); + bHasBadRings = true; + // write part count to indicate we need to + // account for one extra point + // The count will be fixed after the attributes + // are processed. So we write negative only when + // there are attributes. + parts.write(ipart + 1, + bHasAttributes ? -xyindex : xyindex); + } else + parts.write(ipart + 1, xyindex); + } + + pathFlags.setBits(ipart, (byte) PathFlags.enumClosed); + } + + startpart = endpartActual; + } + + if (bZs) + bbox.addAttribute(Semantics.Z); + + if (bMs) + bbox.addAttribute(Semantics.M); + + if (bIDs) + bbox.addAttribute(Semantics.ID); + } + } else { + bbox = new Envelope(); + + if (bZs) + bbox.addAttribute(Semantics.Z); + + if (bMs) + bbox.addAttribute(Semantics.M); + + if (bIDs) + bbox.addAttribute(Semantics.ID); + + if (pointCount > 0) { + bbox.setCoords(xmin, ymin, xmax, ymax); + offset += pointCount * 16 + originalPartCount * 4; + } else + return (Geometry) bbox; + } + + // read Zs + if (bZs) { + if (pointCount > 0) { + double zmin = Interop.translateFromAVNaN(shapeBuffer + .getDouble(offset)); + offset += 8; + double zmax = Interop.translateFromAVNaN(shapeBuffer + .getDouble(offset)); + offset += 8; + + Envelope1D env = new Envelope1D(); + env.setCoords(zmin, zmax); + bbox.setInterval(Semantics.Z, 0, env); + + if (m_type == Geometry.GeometryType.Polygon + || m_type == Geometry.GeometryType.Polyline + || m_type == Geometry.GeometryType.Unknown) { + zs = (AttributeStreamOfDbl) AttributeStreamBase + .createAttributeStreamWithSemantics(Semantics.Z, + pointCount); + + boolean bCreate = false; + int startpart = parts.read(0); + for (int ipart = 0; ipart < partCount; ipart++) { + int endpartActual = parts.read(ipart + 1); + int endpart = Math.abs(endpartActual); + + double startz = Interop.translateFromAVNaN(shapeBuffer + .getDouble(offset)); + offset += 8; + zs.write(startpart, startz); + if (!VertexDescription.isDefaultValue(Semantics.Z, + startz)) + bCreate = true; + + for (int i = startpart + 1; i < endpart; i++) { + double z = Interop.translateFromAVNaN(shapeBuffer + .getDouble(offset)); + offset += 8; + zs.write(i, z); + if (!VertexDescription.isDefaultValue(Semantics.Z, + z)) + bCreate = true; + } + + if (bPolygon && endpartActual > 0) { + offset += 8; + } + + startpart = endpart; + } + + if (!bCreate) + zs = null; + } else + offset += pointCount * 8; + } + + if (m_type == Geometry.GeometryType.Polygon + || m_type == Geometry.GeometryType.Polyline + || m_type == Geometry.GeometryType.Unknown) + multipathImpl.setAttributeStreamRef(Semantics.Z, zs); + } + + // read Ms + if (bMs) { + if (pointCount > 0) { + double mmin = Interop.translateFromAVNaN(shapeBuffer + .getDouble(offset)); + offset += 8; + double mmax = Interop.translateFromAVNaN(shapeBuffer + .getDouble(offset)); + offset += 8; + + Envelope1D env = new Envelope1D(); + env.setCoords(mmin, mmax); + bbox.setInterval(Semantics.M, 0, env); + + if (m_type == Geometry.GeometryType.Polygon + || m_type == Geometry.GeometryType.Polyline + || m_type == Geometry.GeometryType.Unknown) { + ms = (AttributeStreamOfDbl) AttributeStreamBase + .createAttributeStreamWithSemantics(Semantics.M, + pointCount); + + boolean bCreate = false; + int startpart = parts.read(0); + for (int ipart = 0; ipart < partCount; ipart++) { + int endpartActual = parts.read(ipart + 1); + int endpart = Math.abs(endpartActual); + + double startm = Interop.translateFromAVNaN(shapeBuffer + .getDouble(offset)); + offset += 8; + ms.write(startpart, startm); + if (!VertexDescription.isDefaultValue(Semantics.M, + startm)) + bCreate = true; + + for (int i = startpart + 1; i < endpart; i++) { + double m = Interop.translateFromAVNaN(shapeBuffer + .getDouble(offset)); + offset += 8; + ms.write(i, m); + if (!VertexDescription.isDefaultValue(Semantics.M, + m)) + bCreate = true; + } + + if (bPolygon && endpartActual > 0) { + offset += 8; + } + + startpart = endpart; + } + + if (!bCreate) + ms = null; + } else + offset += pointCount * 8; + } + + if (m_type == Geometry.GeometryType.Polygon + || m_type == Geometry.GeometryType.Polyline + || m_type == Geometry.GeometryType.Unknown) + multipathImpl.setAttributeStreamRef(Semantics.M, ms); + } + + // read IDs + if (bIDs) { + if (pointCount > 0) { + double idmin = NumberUtils.doubleMax(); + double idmax = -NumberUtils.doubleMax(); + + if (m_type == Geometry.GeometryType.Polygon + || m_type == Geometry.GeometryType.Polyline + || m_type == Geometry.GeometryType.Unknown) { + ids = (AttributeStreamOfInt32) AttributeStreamBase + .createAttributeStreamWithSemantics(Semantics.ID, + pointCount); + + boolean bCreate = false; + int startpart = parts.read(0); + for (int ipart = 0; ipart < partCount; ipart++) { + int endpartActual = parts.read(ipart + 1); + int endpart = Math.abs(endpartActual); + + int startid = shapeBuffer.getInt(offset); + offset += 4; + ids.write(startpart, startid); + if (!VertexDescription.isDefaultValue(Semantics.ID, + startid)) + bCreate = true; + + for (int i = startpart + 1; i < endpart; i++) { + int id = shapeBuffer.getInt(offset); + offset += 4; + ids.write(i, id); + if (!bCreate + && !VertexDescription.isDefaultValue( + Semantics.ID, id)) + bCreate = true; + + if (idmin > id) + idmin = id; + else if (idmax < id) + idmax = id; + } + + if (bPolygon && endpartActual > 0) { + offset += 4; + } + + startpart = endpart; + } + + if (!bCreate) + ids = null; + } else { + for (int i = 0; i < pointCount; i++) { + int id = shapeBuffer.getInt(offset); + offset += 4; + + if (idmin > id) + idmin = id; + else if (idmax < id) + idmax = id; + } + } + + Envelope1D env = new Envelope1D(); + env.setCoords(idmin, idmax); + bbox.setInterval(Semantics.ID, 0, env); + } + + if (m_type == Geometry.GeometryType.Polygon + || m_type == Geometry.GeometryType.Polyline + || m_type == Geometry.GeometryType.Unknown) + multipathImpl.setAttributeStreamRef(Semantics.ID, ids); + } + + if (bHasBadRings && bHasAttributes) {// revert our hack for bad polygons + for (int ipart = 1; ipart < partCount + 1; ipart++) { + int v = parts.read(ipart); + if (v < 0) + parts.write(ipart, -v); + } + } + + if (m_type == Geometry.GeometryType.Envelope) + return (Geometry) bbox; + + if (pointCount > 0) { + multipathImpl.setPathStreamRef(parts); + multipathImpl.setPathFlagsStreamRef(pathFlags); + multipathImpl.setAttributeStreamRef(Semantics.POSITION, position); + multipathImpl.setEnvelope(bbox); + } + + if ((m_importFlags & ShapeImportFlags.ShapeImportNonTrusted) == 0) + multipathImpl.setIsSimple(GeometryXSimple.Weak, 0.0, false);// We + // use + // tolerance + // of 0. + // What + // should + // we + // instead? + + return (Geometry) multipath; + } + + private Geometry importFromESRIShapeMultiPoint(int modifiers, + ByteBuffer shapeBuffer) { + int offset = 4; + + boolean bZs = (modifiers & (int) ShapeModifiers.ShapeHasZs) != 0; + boolean bMs = (modifiers & (int) ShapeModifiers.ShapeHasMs) != 0; + boolean bIDs = (modifiers & modifiers & (int) ShapeModifiers.ShapeHasIDs) != 0; + + double xmin = shapeBuffer.getDouble(offset); + offset += 8; + double ymin = shapeBuffer.getDouble(offset); + offset += 8; + double xmax = shapeBuffer.getDouble(offset); + offset += 8; + double ymax = shapeBuffer.getDouble(offset); + offset += 8; + + int cPoints = shapeBuffer.getInt(offset); + offset += 4; + + AttributeStreamOfDbl position = null; + AttributeStreamOfDbl zs = null; + AttributeStreamOfDbl ms = null; + AttributeStreamOfInt32 ids = null; + + Envelope bbox = null; + MultiPoint multipoint = null; + MultiPointImpl multipointImpl = null; + + if (m_type == Geometry.GeometryType.MultiPoint + || m_type == Geometry.GeometryType.Unknown) { + multipoint = new MultiPoint(); + multipointImpl = (MultiPointImpl) multipoint._getImpl(); + + if (cPoints > 0) { + bbox = new Envelope(); + multipointImpl.resize(cPoints); + position = (AttributeStreamOfDbl) AttributeStreamBase + .createAttributeStreamWithSemantics(Semantics.POSITION, + cPoints); + + for (int i = 0; i < cPoints; i++) { + double x = shapeBuffer.getDouble(offset); + offset += 8; + double y = shapeBuffer.getDouble(offset); + offset += 8; + position.write(2 * i, x); + position.write(2 * i + 1, y); + } + + multipointImpl.resize(cPoints); + bbox.setCoords(xmin, ymin, xmax, ymax); + + if (bZs) + bbox.addAttribute(Semantics.Z); + + if (bMs) + bbox.addAttribute(Semantics.M); + + if (bIDs) + bbox.addAttribute(Semantics.ID); + } + } else { + bbox = new Envelope(); + + if (bZs) + bbox.addAttribute(Semantics.Z); + + if (bMs) + bbox.addAttribute(Semantics.M); + + if (bIDs) + bbox.addAttribute(Semantics.ID); + + if (cPoints > 0) { + bbox.setCoords(xmin, ymin, xmax, ymax); + offset += cPoints * 16; + } else + return (Geometry) bbox; + } + + if (bZs) { + if (cPoints > 0) { + double zmin = Interop.translateFromAVNaN(shapeBuffer + .getDouble(offset)); + offset += 8; + double zmax = Interop.translateFromAVNaN(shapeBuffer + .getDouble(offset)); + offset += 8; + + Envelope1D env = new Envelope1D(); + env.setCoords(zmin, zmax); + bbox.setInterval(Semantics.Z, 0, env); + + if (m_type == Geometry.GeometryType.MultiPoint + || m_type == Geometry.GeometryType.Unknown) { + zs = (AttributeStreamOfDbl) AttributeStreamBase + .createAttributeStreamWithSemantics(Semantics.Z, + cPoints); + + boolean bCreate = false; + for (int i = 0; i < cPoints; i++) { + double value = Interop.translateFromAVNaN(shapeBuffer + .getDouble(offset)); + offset += 8; + zs.write(i, value); + if (!VertexDescription.isDefaultValue(Semantics.Z, + value)) + bCreate = true; + } + + if (!bCreate) + zs = null; + } else + offset += cPoints * 8; + } + + if (m_type == Geometry.GeometryType.MultiPoint + || m_type == Geometry.GeometryType.Unknown) + multipointImpl.setAttributeStreamRef(Semantics.Z, zs); + } + + if (bMs) { + if (cPoints > 0) { + double mmin = Interop.translateFromAVNaN(shapeBuffer + .getDouble(offset)); + offset += 8; + double mmax = Interop.translateFromAVNaN(shapeBuffer + .getDouble(offset)); + offset += 8; + + Envelope1D env = new Envelope1D(); + env.setCoords(mmin, mmax); + bbox.setInterval(Semantics.M, 0, env); + if (m_type == Geometry.GeometryType.MultiPoint + || m_type == Geometry.GeometryType.Unknown) { + ms = (AttributeStreamOfDbl) AttributeStreamBase + .createAttributeStreamWithSemantics(Semantics.M, + cPoints); + + boolean bCreate = false; + for (int i = 0; i < cPoints; i++) { + double value = Interop.translateFromAVNaN(shapeBuffer + .getDouble(offset)); + offset += 8; + ms.write(i, value); + if (!VertexDescription.isDefaultValue(Semantics.M, + value)) + bCreate = true; + } + + if (!bCreate) + ms = null; + } else + offset += cPoints * 8; + } + + if (m_type == Geometry.GeometryType.MultiPoint + || m_type == Geometry.GeometryType.Unknown) + multipointImpl.setAttributeStreamRef(Semantics.M, ms); + } + + if (bIDs) { + if (cPoints > 0) { + double idmin = NumberUtils.doubleMax(); + double idmax = -NumberUtils.doubleMax(); + + if (m_type == Geometry.GeometryType.MultiPoint + || m_type == Geometry.GeometryType.Unknown) { + ids = (AttributeStreamOfInt32) AttributeStreamBase + .createAttributeStreamWithSemantics(Semantics.ID, + cPoints); + + boolean bCreate = false; + for (int i = 0; i < cPoints; i++) { + int value = shapeBuffer.getInt(offset); + offset += 4; + ids.write(i, value); + if (!VertexDescription.isDefaultValue(Semantics.ID, + value)) + bCreate = true; + + if (idmin > value) + idmin = value; + else if (idmax < value) + idmax = value; + } + + if (!bCreate) + ids = null; + } else { + for (int i = 0; i < cPoints; i++) { + int id = shapeBuffer.getInt(offset); + offset += 4; + + if (idmin > id) + idmin = id; + else if (idmax < id) + idmax = id; + } + } + + Envelope1D env = new Envelope1D(); + env.setCoords(idmin, idmax); + bbox.setInterval(Semantics.ID, 0, env); + } + + if (m_type == Geometry.GeometryType.MultiPoint + || m_type == Geometry.GeometryType.Unknown) + multipointImpl.setAttributeStreamRef(Semantics.ID, ids); + } + + if (m_type == Geometry.GeometryType.Envelope) + return (Geometry) bbox; + + if (cPoints > 0) { + multipointImpl.setAttributeStreamRef(Semantics.POSITION, position); + multipointImpl.setEnvelope(bbox); + } + + return (Geometry) multipoint; + } + + private Geometry importFromESRIShapePoint(int modifiers, + ByteBuffer shapeBuffer) { + int offset = 4; + + boolean bZs = (modifiers & (int) ShapeModifiers.ShapeHasZs) != 0; + boolean bMs = (modifiers & (int) ShapeModifiers.ShapeHasMs) != 0; + boolean bIDs = (modifiers & modifiers & (int) ShapeModifiers.ShapeHasIDs) != 0; + + // read XY + double x = shapeBuffer.getDouble(offset); + offset += 8; + double y = shapeBuffer.getDouble(offset); + offset += 8; + + boolean bEmpty = NumberUtils.isNaN(x); + + double z = NumberUtils.NaN(); + if (bZs) { + z = Interop.translateFromAVNaN(shapeBuffer.getDouble(offset)); + offset += 8; + } + + double m = NumberUtils.NaN(); + if (bMs) { + m = Interop.translateFromAVNaN(shapeBuffer.getDouble(offset)); + offset += 8; + } + + int id = -1; + if (bIDs) { + id = shapeBuffer.getInt(offset); + offset += 4; + } + + if (m_type == Geometry.GeometryType.MultiPoint) { + MultiPoint newmultipoint = new MultiPoint(); + MultiPointImpl multipointImpl = (MultiPointImpl) newmultipoint + ._getImpl(); + + if (!bEmpty) { + AttributeStreamBase newPositionStream = AttributeStreamBase + .createAttributeStreamWithSemantics(Semantics.POSITION, + 1); + AttributeStreamOfDbl position = (AttributeStreamOfDbl) newPositionStream; + position.write(0, x); + position.write(1, y); + + multipointImpl.setAttributeStreamRef(Semantics.POSITION, + newPositionStream); + multipointImpl.resize(1); + } + + if (bZs) { + multipointImpl.addAttribute(Semantics.Z); + if (!bEmpty + && !VertexDescription.isDefaultValue(Semantics.Z, z)) { + AttributeStreamBase newZStream = AttributeStreamBase + .createAttributeStreamWithSemantics(Semantics.Z, 1); + newZStream.writeAsDbl(0, z); + multipointImpl.setAttributeStreamRef(Semantics.Z, + newZStream); + } + } + + if (bMs) { + multipointImpl.addAttribute(Semantics.M); + if (!bEmpty + && !VertexDescription.isDefaultValue(Semantics.M, m)) { + AttributeStreamBase newMStream = AttributeStreamBase + .createAttributeStreamWithSemantics(Semantics.M, 1); + newMStream.writeAsDbl(0, m); + multipointImpl.setAttributeStreamRef(Semantics.M, + newMStream); + } + } + + if (bIDs) { + multipointImpl.addAttribute(Semantics.ID); + if (!bEmpty + && !VertexDescription.isDefaultValue(Semantics.ID, id)) { + AttributeStreamBase newIDStream = AttributeStreamBase + .createAttributeStreamWithSemantics(Semantics.ID, 1); + newIDStream.writeAsInt(0, id); + multipointImpl.setAttributeStreamRef(Semantics.ID, + newIDStream); + } + } + + return (Geometry) newmultipoint; + } else if (m_type == Geometry.GeometryType.Envelope) { + Envelope envelope = new Envelope(); + envelope.setCoords(x, y, x, y); + + if (bZs) { + Envelope1D interval = new Envelope1D(); + interval.vmin = z; + interval.vmax = z; + envelope.addAttribute(Semantics.Z); + envelope.setInterval(Semantics.Z, 0, interval); + } + + if (bMs) { + Envelope1D interval = new Envelope1D(); + interval.vmin = m; + interval.vmax = m; + envelope.addAttribute(Semantics.M); + envelope.setInterval(Semantics.M, 0, interval); + } + + if (bIDs) { + Envelope1D interval = new Envelope1D(); + interval.vmin = id; + interval.vmax = id; + envelope.addAttribute(Semantics.ID); + envelope.setInterval(Semantics.ID, 0, interval); + } + + return (Geometry) envelope; + } + + Point point = new Point(); + + if (!bEmpty) { + point.setX(Interop.translateFromAVNaN(x)); + point.setY(Interop.translateFromAVNaN(y)); + } + + // read Z + if (bZs) { + point.addAttribute(Semantics.Z); + if (!bEmpty) + point.setZ(Interop.translateFromAVNaN(z)); + } + + // read M + if (bMs) { + point.addAttribute(Semantics.M); + if (!bEmpty) + point.setM(Interop.translateFromAVNaN(m)); + } + + // read ID + if (bIDs) { + point.addAttribute(Semantics.ID); + if (!bEmpty) + point.setID(id); + } + + return (Geometry) point; + } } diff --git a/src/main/java/com/esri/core/geometry/OperatorImportFromESRIShapeLocal.java b/src/main/java/com/esri/core/geometry/OperatorImportFromESRIShapeLocal.java index 496e0605..ed7f959c 100644 --- a/src/main/java/com/esri/core/geometry/OperatorImportFromESRIShapeLocal.java +++ b/src/main/java/com/esri/core/geometry/OperatorImportFromESRIShapeLocal.java @@ -31,16 +31,16 @@ */ class OperatorImportFromESRIShapeLocal extends OperatorImportFromESRIShape { - @Override - public GeometryCursor execute(int importFlags, Geometry.Type type, ByteBufferCursor shapeBuffers) { - return new OperatorImportFromESRIShapeCursor(importFlags, type.value(), shapeBuffers); - } - - @Override - public Geometry execute(int importFlags, Geometry.Type type, ByteBuffer shapeBuffer) { - SimpleByteBufferCursor byteBufferCursor = new SimpleByteBufferCursor(shapeBuffer); - GeometryCursor geometryCursor = execute(importFlags, type, byteBufferCursor); - - return geometryCursor.next(); - } + @Override + public GeometryCursor execute(int importFlags, Geometry.Type type, ByteBufferCursor shapeBuffers) { + return new OperatorImportFromESRIShapeCursor(importFlags, type.value(), shapeBuffers); + } + + @Override + public Geometry execute(int importFlags, Geometry.Type type, ByteBuffer shapeBuffer) { + SimpleByteBufferCursor byteBufferCursor = new SimpleByteBufferCursor(shapeBuffer); + GeometryCursor geometryCursor = execute(importFlags, type, byteBufferCursor); + + return geometryCursor.next(); + } } diff --git a/src/main/java/com/esri/core/geometry/OperatorImportFromGeoJson.java b/src/main/java/com/esri/core/geometry/OperatorImportFromGeoJson.java index 2eb37f61..d40a2373 100644 --- a/src/main/java/com/esri/core/geometry/OperatorImportFromGeoJson.java +++ b/src/main/java/com/esri/core/geometry/OperatorImportFromGeoJson.java @@ -25,48 +25,48 @@ public abstract class OperatorImportFromGeoJson extends Operator { - @Override - public Type getType() { - return Type.ImportFromGeoJson; - } + @Override + public Type getType() { + return Type.ImportFromGeoJson; + } - // TODO, this was never implemented. Maybe remove? + // TODO, this was never implemented. Maybe remove? // abstract MapGeometryCursor execute(int import_flags, String geoJsonString, ProgressTracker progressTracker); - abstract MapGeometryCursor execute(int import_flags, StringCursor stringCursor, ProgressTracker progressTracker); + abstract MapGeometryCursor execute(int import_flags, StringCursor stringCursor, ProgressTracker progressTracker); - /** - * Performs the ImportFromGeoJson operation. - * - * @param type Use the {@link Geometry.Type} enum. - * @param jsonObject The JSONObject holding the geometry and spatial reference. - * @return Returns the imported MapGeometry. - * @throws JsonGeometryException - */ - public abstract MapGeometry execute(int importFlags, Geometry.Type type, JsonReader jsonReader, ProgressTracker progressTracker); + /** + * Performs the ImportFromGeoJson operation. + * + * @param type Use the {@link Geometry.Type} enum. + * @param jsonObject The JSONObject holding the geometry and spatial reference. + * @return Returns the imported MapGeometry. + * @throws JsonGeometryException + */ + public abstract MapGeometry execute(int importFlags, Geometry.Type type, JsonReader jsonReader, ProgressTracker progressTracker); - /** - * Deprecated, use version without import_flags. - *

- * Performs the ImportFromGeoJson operation. - * - * @param import_flags Use the {@link GeoJsonImportFlags} interface. - * @param type Use the {@link Geometry.Type} enum. - * @param geoJsonString The string holding the Geometry in geoJson format. - * @return Returns the imported MapGeometry. - */ - public abstract MapGeometry execute(int import_flags, Geometry.Type type, String geoJsonString, ProgressTracker progress_tracker); + /** + * Deprecated, use version without import_flags. + *

+ * Performs the ImportFromGeoJson operation. + * + * @param import_flags Use the {@link GeoJsonImportFlags} interface. + * @param type Use the {@link Geometry.Type} enum. + * @param geoJsonString The string holding the Geometry in geoJson format. + * @return Returns the imported MapGeometry. + */ + public abstract MapGeometry execute(int import_flags, Geometry.Type type, String geoJsonString, ProgressTracker progress_tracker); - /** - * Performs the ImportFromGeoJson operation. - * - * @param import_flags Use the {@link GeoJsonImportFlags} interface. - * @param geoJsonString The string holding the Geometry in geoJson format. - * @return Returns the imported MapOGCStructure. - */ - public abstract MapOGCStructure executeOGC(int import_flags, String geoJsonString, ProgressTracker progress_tracker); + /** + * Performs the ImportFromGeoJson operation. + * + * @param import_flags Use the {@link GeoJsonImportFlags} interface. + * @param geoJsonString The string holding the Geometry in geoJson format. + * @return Returns the imported MapOGCStructure. + */ + public abstract MapOGCStructure executeOGC(int import_flags, String geoJsonString, ProgressTracker progress_tracker); - public static OperatorImportFromGeoJson local() { - return (OperatorImportFromGeoJson) OperatorFactoryLocal.getInstance().getOperator(Type.ImportFromGeoJson); - } + public static OperatorImportFromGeoJson local() { + return (OperatorImportFromGeoJson) OperatorFactoryLocal.getInstance().getOperator(Type.ImportFromGeoJson); + } } diff --git a/src/main/java/com/esri/core/geometry/OperatorImportFromGeoJsonCursor.java b/src/main/java/com/esri/core/geometry/OperatorImportFromGeoJsonCursor.java index 17b4de98..152c7b4d 100644 --- a/src/main/java/com/esri/core/geometry/OperatorImportFromGeoJsonCursor.java +++ b/src/main/java/com/esri/core/geometry/OperatorImportFromGeoJsonCursor.java @@ -1,11 +1,11 @@ package com.esri.core.geometry; public class OperatorImportFromGeoJsonCursor extends MapGeometryCursor { - StringCursor m_jsonStringCursor; - int m_import_flags; - int m_count; + StringCursor m_jsonStringCursor; + int m_import_flags; + int m_count; - // TODO, this was never implemented. Maybe remove? + // TODO, this was never implemented. Maybe remove? // public OperatorImportFromGeoJsonCursor(int import_flags, String geoJsonString, ProgressTracker progressTracker) { // m_geoJsonString = geoJsonString; // m_import_flags = import_flags; @@ -13,35 +13,39 @@ public class OperatorImportFromGeoJsonCursor extends MapGeometryCursor { // m_count = 1; // } - public OperatorImportFromGeoJsonCursor(int import_flags, StringCursor stringCursor, ProgressTracker progressTracker) { - m_jsonStringCursor = stringCursor; - m_import_flags = import_flags; - m_count = 1; - } - - @Override - public MapGeometry next() { - String nextString; - if ((nextString = m_jsonStringCursor.next()) != null) { - JsonReader jsonReader = JsonParserReader.createFromString(nextString); - return OperatorImportFromGeoJsonLocal.OperatorImportFromGeoJsonHelper.importFromGeoJson(m_import_flags, Geometry.Type.Unknown, jsonReader, null, false); - } - return null; - } - - @Override - public SimpleStateEnum getSimpleState() { return m_jsonStringCursor.getSimpleState(); } - - @Override - public String getFeatureID() { return m_jsonStringCursor.getFeatureID(); } - - @Override - public long getGeometryID() { - return m_jsonStringCursor.getID(); - } - - @Override - public boolean hasNext() { - return m_jsonStringCursor != null && m_jsonStringCursor.hasNext(); - } + public OperatorImportFromGeoJsonCursor(int import_flags, StringCursor stringCursor, ProgressTracker progressTracker) { + m_jsonStringCursor = stringCursor; + m_import_flags = import_flags; + m_count = 1; + } + + @Override + public MapGeometry next() { + String nextString; + if ((nextString = m_jsonStringCursor.next()) != null) { + JsonReader jsonReader = JsonParserReader.createFromString(nextString); + return OperatorImportFromGeoJsonLocal.OperatorImportFromGeoJsonHelper.importFromGeoJson(m_import_flags, Geometry.Type.Unknown, jsonReader, null, false); + } + return null; + } + + @Override + public SimpleStateEnum getSimpleState() { + return m_jsonStringCursor.getSimpleState(); + } + + @Override + public String getFeatureID() { + return m_jsonStringCursor.getFeatureID(); + } + + @Override + public long getGeometryID() { + return m_jsonStringCursor.getID(); + } + + @Override + public boolean hasNext() { + return m_jsonStringCursor != null && m_jsonStringCursor.hasNext(); + } } diff --git a/src/main/java/com/esri/core/geometry/OperatorImportFromGeoJsonLocal.java b/src/main/java/com/esri/core/geometry/OperatorImportFromGeoJsonLocal.java index 2e3c78aa..4df7676b 100644 --- a/src/main/java/com/esri/core/geometry/OperatorImportFromGeoJsonLocal.java +++ b/src/main/java/com/esri/core/geometry/OperatorImportFromGeoJsonLocal.java @@ -28,1305 +28,1305 @@ import java.util.ArrayList; class OperatorImportFromGeoJsonLocal extends OperatorImportFromGeoJson { - static enum GeoJsonType { - Point, LineString, Polygon, MultiPoint, MultiLineString, MultiPolygon, GeometryCollection; - - static GeoJsonType fromGeoJsonValue(int v) { - return GeoJsonType.values()[v - 1]; - } - - public int geogsjonvalue() { - return ordinal() + 1; - } - } - - static interface GeoJsonValues { - public final static int Point = GeoJsonType.Point.geogsjonvalue(); - public final static int LineString = GeoJsonType.LineString.geogsjonvalue(); - public final static int Polygon = GeoJsonType.Polygon.geogsjonvalue(); - public final static int MultiPoint = GeoJsonType.MultiPoint.geogsjonvalue(); - public final static int MultiLineString = GeoJsonType.MultiLineString.geogsjonvalue(); - public final static int MultiPolygon = GeoJsonType.MultiPolygon.geogsjonvalue(); - public final static int GeometryCollection = GeoJsonType.GeometryCollection.geogsjonvalue(); - } - - - // TODO, this was never implemented. Maybe remove? + static enum GeoJsonType { + Point, LineString, Polygon, MultiPoint, MultiLineString, MultiPolygon, GeometryCollection; + + static GeoJsonType fromGeoJsonValue(int v) { + return GeoJsonType.values()[v - 1]; + } + + public int geogsjonvalue() { + return ordinal() + 1; + } + } + + static interface GeoJsonValues { + public final static int Point = GeoJsonType.Point.geogsjonvalue(); + public final static int LineString = GeoJsonType.LineString.geogsjonvalue(); + public final static int Polygon = GeoJsonType.Polygon.geogsjonvalue(); + public final static int MultiPoint = GeoJsonType.MultiPoint.geogsjonvalue(); + public final static int MultiLineString = GeoJsonType.MultiLineString.geogsjonvalue(); + public final static int MultiPolygon = GeoJsonType.MultiPolygon.geogsjonvalue(); + public final static int GeometryCollection = GeoJsonType.GeometryCollection.geogsjonvalue(); + } + + + // TODO, this was never implemented. Maybe remove? // @Override // MapGeometryCursor execute(int import_flags, String geoJsonString, ProgressTracker progressTracker) { // return new OperatorImportFromGeoJsonCursor(import_flags, geoJsonString, progressTracker); // } - @Override - MapGeometryCursor execute(int import_flags, StringCursor stringCursor, ProgressTracker progressTracker) { - return new OperatorImportFromGeoJsonCursor(import_flags, stringCursor, progressTracker); - } - - @Override - public MapGeometry execute(int importFlags, Geometry.Type type, - String geoJsonString, ProgressTracker progressTracker) - throws JsonGeometryException { - MapGeometry map_geometry = OperatorImportFromGeoJsonHelper - .importFromGeoJson(importFlags, type, JsonParserReader.createFromString(geoJsonString), progressTracker, false); - return map_geometry; - } - - @Override - public MapGeometry execute(int importFlags, Geometry.Type type, - JsonReader jsonReader, ProgressTracker progressTracker) - throws JsonGeometryException { - if (jsonReader == null) - return null; - - return OperatorImportFromGeoJsonHelper.importFromGeoJson(importFlags, - type, jsonReader, progressTracker, false); - } - - static final class OperatorImportFromGeoJsonHelper { - - private AttributeStreamOfDbl m_position; - private AttributeStreamOfDbl m_zs; - private AttributeStreamOfDbl m_ms; - private AttributeStreamOfInt32 m_paths; - private AttributeStreamOfInt8 m_path_flags; - private Point m_point; // special case for Points - private boolean m_b_has_zs; - private boolean m_b_has_ms; - private boolean m_b_has_zs_known; - private boolean m_b_has_ms_known; - private int m_num_embeddings; - - int m_ogcType; - - OperatorImportFromGeoJsonHelper() { - m_position = null; - m_zs = null; - m_ms = null; - m_paths = null; - m_path_flags = null; - m_point = null; - m_b_has_zs = false; - m_b_has_ms = false; - m_b_has_zs_known = false; - m_b_has_ms_known = false; - m_num_embeddings = 0; - m_ogcType = 0; - } - - static MapGeometry importFromGeoJson(int importFlags, - Geometry.Type type, JsonReader json_iterator, - ProgressTracker progress_tracker, boolean skip_coordinates) - throws JsonGeometryException { - OperatorImportFromGeoJsonHelper geo_json_helper = new OperatorImportFromGeoJsonHelper(); - MapOGCStructure ms = geo_json_helper.importFromGeoJsonImpl( - importFlags, type, json_iterator, progress_tracker, - skip_coordinates, 0); - - if (geo_json_helper.m_ogcType == GeoJsonValues.GeometryCollection && !skip_coordinates) - throw new JsonGeometryException("parsing error"); - - return new MapGeometry(ms.m_ogcStructure.m_geometry, - ms.m_spatialReference); - } - - static MapOGCStructure importFromGeoJson(int importFlags, - Geometry.Type type, JsonReader json_iterator, - ProgressTracker progress_tracker, boolean skip_coordinates, - int recursion) throws JsonGeometryException { - OperatorImportFromGeoJsonHelper geo_json_helper = new OperatorImportFromGeoJsonHelper(); - MapOGCStructure ms = geo_json_helper.importFromGeoJsonImpl( - importFlags, type, json_iterator, progress_tracker, - skip_coordinates, recursion); - - if (geo_json_helper.m_ogcType == GeoJsonValues.GeometryCollection && !skip_coordinates) - throw new JsonGeometryException("parsing error"); - - return ms; - } - - MapOGCStructure importFromGeoJsonImpl(int importFlags, - Geometry.Type type, JsonReader json_iterator, - ProgressTracker progress_tracker, boolean skip_coordinates, - int recursion) throws JsonGeometryException { - OperatorImportFromGeoJsonHelper geo_json_helper = this; - boolean b_type_found = false; - boolean b_coordinates_found = false; - boolean b_crs_found = false; - boolean b_crsURN_found = false; - boolean b_geometry_collection = false; - boolean b_geometries_found = false; - GeoJsonType geo_json_type = null; - - Geometry geometry = null; - SpatialReference spatial_reference = null; - - JsonReader.Token current_token; - String field_name = null; - MapOGCStructure ms = new MapOGCStructure(); - - while ((current_token = json_iterator.nextToken()) != JsonReader.Token.END_OBJECT) { - field_name = json_iterator.currentString(); - - if (field_name.equals("type")) { - if (b_type_found) { - throw new JsonGeometryException("parsing error"); - } - - b_type_found = true; - current_token = json_iterator.nextToken(); - - if (current_token != JsonReader.Token.VALUE_STRING) { - throw new JsonGeometryException("parsing error"); - } - - String s = json_iterator.currentString(); - try { - geo_json_type = GeoJsonType.valueOf(s); - } catch (Exception ex) { - throw new JsonGeometryException(s); - } - - if (geo_json_type == GeoJsonType.GeometryCollection) { - if (type != Geometry.Type.Unknown) - throw new JsonGeometryException("parsing error"); - - b_geometry_collection = true; - } - } else if (field_name.equals("geometries")) { - b_geometries_found = true; - if (type != Geometry.Type.Unknown) - throw new JsonGeometryException("parsing error"); - - if (recursion > 10) { - throw new JsonGeometryException("deep geojson"); - } - - if (skip_coordinates) { - json_iterator.skipChildren(); - } else { - current_token = json_iterator.nextToken(); - - ms.m_ogcStructure = new OGCStructure(); - ms.m_ogcStructure.m_type = GeoJsonValues.GeometryCollection; - ms.m_ogcStructure.m_structures = new ArrayList( - 0); - - if (current_token == JsonReader.Token.START_ARRAY) { - current_token = json_iterator.nextToken(); - while (current_token != JsonReader.Token.END_ARRAY) { - MapOGCStructure child = importFromGeoJson( - importFlags - | GeoJsonImportFlags.geoJsonImportSkipCRS, - type, json_iterator, - progress_tracker, false, - recursion + 1); - ms.m_ogcStructure.m_structures - .add(child.m_ogcStructure); - - current_token = json_iterator.nextToken(); - } - } else if (current_token != JsonReader.Token.VALUE_NULL) { - throw new JsonGeometryException("parsing error"); - } - } - } else if (field_name.equals("coordinates")) { - - if (b_coordinates_found) { - throw new JsonGeometryException("parsing error"); - } - - b_coordinates_found = true; - current_token = json_iterator.nextToken(); - - if (skip_coordinates) { - json_iterator.skipChildren(); - } else {// According to the spec, the value of the - // coordinates must be an array. However, I do an - // extra check for null too. - if (current_token != JsonReader.Token.VALUE_NULL) { - if (current_token != JsonReader.Token.START_ARRAY) { - throw new JsonGeometryException("parsing error"); - } - - geo_json_helper.import_coordinates_(json_iterator, - progress_tracker); - } - } - } else if (field_name.equals("crs")) { - if (b_crs_found || b_crsURN_found) { - throw new JsonGeometryException("parsing error"); - } - - b_crs_found = true; - current_token = json_iterator.nextToken(); - - if ((importFlags & GeoJsonImportFlags.geoJsonImportSkipCRS) == 0) - spatial_reference = importSpatialReferenceFromCrs( - json_iterator, progress_tracker); - else - json_iterator.skipChildren(); - } else if (field_name.equals("crsURN")) { - if (b_crs_found || b_crsURN_found) { - throw new JsonGeometryException("parsing error"); - } - - b_crsURN_found = true; - current_token = json_iterator.nextToken(); - - spatial_reference = importSpatialReferenceFromCrsUrn_( - json_iterator, progress_tracker); - } else { - json_iterator.nextToken(); - json_iterator.skipChildren(); - } - } - - // According to the spec, a GeoJSON object must have both a type and - // a coordinates array - if (!b_type_found || (!b_geometry_collection && !b_coordinates_found && !skip_coordinates)) { - throw new JsonGeometryException("parsing error"); - } - - if ((!b_geometry_collection && b_geometries_found) || (b_geometry_collection && !b_geometries_found)) { - throw new JsonGeometryException("parsing error");//found "geometries" but did not see "GeometryCollection" - } - - - if (!skip_coordinates && !b_geometry_collection) { - geometry = geo_json_helper.createGeometry_(geo_json_type, - type.value()); - - ms.m_ogcStructure = new OGCStructure(); - ms.m_ogcStructure.m_type = m_ogcType; - ms.m_ogcStructure.m_geometry = geometry; - } - - if (!b_crs_found - && !b_crsURN_found - && ((importFlags & GeoJsonImportFlags.geoJsonImportSkipCRS) == 0) - && ((importFlags & GeoJsonImportFlags.geoJsonImportNoWGS84Default) == 0)) { - spatial_reference = SpatialReference.create(4326); // the spec - // gives a - // default - // of 4326 - // if no crs - // is given - } - - ms.m_spatialReference = spatial_reference; - return ms; - } - - // We have to import the coordinates in the most general way possible to - // not assume the type of geometry we're parsing. - // JSON allows for unordered objects, so it's possible that the - // coordinates array can come before the type tag when parsing - // sequentially, otherwise - // we would have to parse using a JSON_object, which would be easier, - // but not as space/time efficient. So this function blindly imports the - // coordinates - // into the attribute stream(s), and will later assign them to a - // geometry after the type tag is found. - private void import_coordinates_(JsonReader json_iterator, - ProgressTracker progress_tracker) throws JsonGeometryException { - assert (json_iterator.currentToken() == JsonReader.Token.START_ARRAY); - - int coordinates_level_lower = 1; - int coordinates_level_upper = 4; - - json_iterator.nextToken(); - - while (json_iterator.currentToken() != JsonReader.Token.END_ARRAY) { - if (isDouble_(json_iterator)) { - if (coordinates_level_upper > 1) { - coordinates_level_upper = 1; - } - } else if (json_iterator.currentToken() == JsonReader.Token.START_ARRAY) { - if (coordinates_level_lower < 2) { - coordinates_level_lower = 2; - } - } else { - throw new JsonGeometryException("parsing error"); - } - - if (coordinates_level_lower > coordinates_level_upper) { - throw new IllegalArgumentException("invalid argument"); - } - - if (coordinates_level_lower == coordinates_level_upper - && coordinates_level_lower == 1) {// special - // code - // for - // Points - readCoordinateAsPoint_(json_iterator); - } else { - boolean b_add_path_level_3 = true; - boolean b_polygon_start_level_4 = true; - - assert (json_iterator.currentToken() == JsonReader.Token.START_ARRAY); - json_iterator.nextToken(); - - while (json_iterator.currentToken() != JsonReader.Token.END_ARRAY) { - if (isDouble_(json_iterator)) { - if (coordinates_level_upper > 2) { - coordinates_level_upper = 2; - } - } else if (json_iterator.currentToken() == JsonReader.Token.START_ARRAY) { - if (coordinates_level_lower < 3) { - coordinates_level_lower = 3; - } - } else { - throw new JsonGeometryException("parsing error"); - } - - if (coordinates_level_lower > coordinates_level_upper) { - throw new JsonGeometryException("parsing error"); - } - - if (coordinates_level_lower == coordinates_level_upper - && coordinates_level_lower == 2) {// LineString - // or - // MultiPoint - addCoordinate_(json_iterator); - } else { - boolean b_add_path_level_4 = true; - - assert (json_iterator.currentToken() == JsonReader.Token.START_ARRAY); - json_iterator.nextToken(); - - while (json_iterator.currentToken() != JsonReader.Token.END_ARRAY) { - if (isDouble_(json_iterator)) { - if (coordinates_level_upper > 3) { - coordinates_level_upper = 3; - } - } else if (json_iterator.currentToken() == JsonReader.Token.START_ARRAY) { - if (coordinates_level_lower < 4) { - coordinates_level_lower = 4; - } - } else { - throw new JsonGeometryException("parsing error"); - } - - if (coordinates_level_lower > coordinates_level_upper) { - throw new JsonGeometryException("parsing error"); - } - - if (coordinates_level_lower == coordinates_level_upper - && coordinates_level_lower == 3) {// Polygon - // or - // MultiLineString - if (b_add_path_level_3) { - addPath_(); - b_add_path_level_3 = false; - } - - addCoordinate_(json_iterator); - } else { - assert (json_iterator.currentToken() == JsonReader.Token.START_ARRAY); - json_iterator.nextToken(); - - if (json_iterator.currentToken() != JsonReader.Token.END_ARRAY) { - if (!isDouble_(json_iterator)) { - throw new JsonGeometryException("parsing error"); - } - - assert (coordinates_level_lower == coordinates_level_upper && coordinates_level_lower == 4); - // MultiPolygon - - if (b_add_path_level_4) { - addPath_(); - addPathFlag_(b_polygon_start_level_4); - b_add_path_level_4 = false; - b_polygon_start_level_4 = false; - } - - addCoordinate_(json_iterator); - } - - json_iterator.nextToken(); - } - } - - json_iterator.nextToken(); - } - } - - json_iterator.nextToken(); - } - } - - if (m_paths != null) { - m_paths.add(m_position.size() / 2); // add final path size - } - if (m_path_flags != null) { - m_path_flags.add((byte) 0); // to match the paths size - } - - m_num_embeddings = coordinates_level_lower; - } - - private void readCoordinateAsPoint_(JsonReader json_iterator) - throws JsonGeometryException { - assert (isDouble_(json_iterator)); - - m_point = new Point(); - - double x = readDouble_(json_iterator); - json_iterator.nextToken(); - double y = readDouble_(json_iterator); - json_iterator.nextToken(); - - if (NumberUtils.isNaN(y)) { - x = NumberUtils.NaN(); - } - - m_point.setXY(x, y); - - if (isDouble_(json_iterator)) { - double z = readDouble_(json_iterator); - json_iterator.nextToken(); - m_point.setZ(z); - } - - if (isDouble_(json_iterator)) { - double m = readDouble_(json_iterator); - json_iterator.nextToken(); - m_point.setM(m); - } - - if (json_iterator.currentToken() != JsonReader.Token.END_ARRAY) { - throw new JsonGeometryException("parsing error"); - } - } - - private void addCoordinate_(JsonReader json_iterator) - throws JsonGeometryException { - assert (isDouble_(json_iterator)); - - if (m_position == null) { - m_position = (AttributeStreamOfDbl) AttributeStreamBase - .createDoubleStream(0); - } - - double x = readDouble_(json_iterator); - json_iterator.nextToken(); - double y = readDouble_(json_iterator); - json_iterator.nextToken(); - - int size = m_position.size(); - - m_position.add(x); - m_position.add(y); - - if (isDouble_(json_iterator)) { - if (!m_b_has_zs_known) { - m_b_has_zs_known = true; - m_b_has_zs = true; - m_zs = (AttributeStreamOfDbl) AttributeStreamBase - .createDoubleStream(0); - } else { - if (!m_b_has_zs) { - m_zs = (AttributeStreamOfDbl) AttributeStreamBase - .createDoubleStream(size >> 1, - VertexDescription - .getDefaultValue(Semantics.Z)); - m_b_has_zs = true; - } - } - - double z = readDouble_(json_iterator); - json_iterator.nextToken(); - m_zs.add(z); - } else { - if (!m_b_has_zs_known) { - m_b_has_zs_known = true; - m_b_has_zs = false; - } else { - if (m_b_has_zs) { - m_zs.add(VertexDescription.getDefaultValue(Semantics.Z)); - } - } - } - - if (isDouble_(json_iterator)) { - if (!m_b_has_ms_known) { - m_b_has_ms_known = true; - m_b_has_ms = true; - m_ms = (AttributeStreamOfDbl) AttributeStreamBase - .createDoubleStream(0); - } else { - if (!m_b_has_ms) { - m_ms = (AttributeStreamOfDbl) AttributeStreamBase - .createDoubleStream(size >> 1, - VertexDescription - .getDefaultValue(Semantics.M)); - m_b_has_ms = true; - } - } - - double m = readDouble_(json_iterator); - json_iterator.nextToken(); - m_ms.add(m); - } else { - if (!m_b_has_ms_known) { - m_b_has_ms_known = true; - m_b_has_ms = false; - } else { - if (m_b_has_ms) { - m_zs.add(VertexDescription.getDefaultValue(Semantics.M)); - } - } - } - - if (json_iterator.currentToken() != JsonReader.Token.END_ARRAY) { - throw new JsonGeometryException("parsing error"); - } - } - - private void addPath_() { - if (m_paths == null) { - m_paths = (AttributeStreamOfInt32) AttributeStreamBase - .createIndexStream(0); - } - - if (m_position == null) { - m_paths.add(0); - } else { - m_paths.add(m_position.size() / 2); - } - } - - private void addPathFlag_(boolean b_polygon_start) { - if (m_path_flags == null) { - m_path_flags = (AttributeStreamOfInt8) AttributeStreamBase - .createByteStream(0); - } - - if (b_polygon_start) { - m_path_flags - .add((byte) (PathFlags.enumClosed | PathFlags.enumOGCStartPolygon)); - } else { - m_path_flags.add((byte) PathFlags.enumClosed); - } - } - - private double readDouble_(JsonReader json_iterator) - throws JsonGeometryException { - JsonReader.Token current_token = json_iterator.currentToken(); - if (current_token == JsonReader.Token.VALUE_NULL - || (current_token == JsonReader.Token.VALUE_STRING && json_iterator - .currentString().equals("NaN"))) { - return NumberUtils.NaN(); - } else { - return json_iterator.currentDoubleValue(); - } - } - - private boolean isDouble_(JsonReader json_iterator) - throws JsonGeometryException { - JsonReader.Token current_token = json_iterator.currentToken(); - - if (current_token == JsonReader.Token.VALUE_NUMBER_FLOAT) { - return true; - } - - if (current_token == JsonReader.Token.VALUE_NUMBER_INT) { - return true; - } - - if (current_token == JsonReader.Token.VALUE_NULL - || (current_token == JsonReader.Token.VALUE_STRING && json_iterator - .currentString().equals("NaN"))) { - return true; - } - - return false; - } - - //does not accept GeometryCollection - private Geometry createGeometry_(GeoJsonType geo_json_type, int type) - throws JsonGeometryException { - Geometry geometry; - - if (type != Geometry.GeometryType.Unknown) { - switch (type) { - case Geometry.GeometryType.Polygon: - if (geo_json_type != GeoJsonType.MultiPolygon - && geo_json_type != GeoJsonType.Polygon) { - throw new GeometryException("invalid shape type"); - } - break; - case Geometry.GeometryType.Polyline: - if (geo_json_type != GeoJsonType.MultiLineString - && geo_json_type != GeoJsonType.LineString) { - throw new GeometryException("invalid shape type"); - } - break; - case Geometry.GeometryType.MultiPoint: - if (geo_json_type != GeoJsonType.MultiPoint) { - throw new GeometryException("invalid shape type"); - } - break; - case Geometry.GeometryType.Point: - if (geo_json_type != GeoJsonType.Point) { - throw new GeometryException("invalid shape type"); - } - break; - default: - throw new GeometryException("invalid shape type"); - } - } - - m_ogcType = geo_json_type.geogsjonvalue(); - if (geo_json_type == GeoJsonType.GeometryCollection) - throw new IllegalArgumentException("invalid argument"); - - if (m_position == null && m_point == null) { - switch (geo_json_type) { - case Point: { - if (m_num_embeddings > 1) { - throw new JsonGeometryException("parsing error"); - } - - geometry = new Point(); - break; - } - case MultiPoint: { - if (m_num_embeddings > 2) { - throw new JsonGeometryException("parsing error"); - } - - geometry = new MultiPoint(); - break; - } - case LineString: { - if (m_num_embeddings > 2) { - throw new JsonGeometryException("parsing error"); - } - - geometry = new Polyline(); - break; - } - case MultiLineString: { - if (m_num_embeddings > 3) { - throw new JsonGeometryException("parsing error"); - } - - geometry = new Polyline(); - break; - } - case Polygon: { - if (m_num_embeddings > 3) { - throw new JsonGeometryException("parsing error"); - } - - geometry = new Polygon(); - break; - } - case MultiPolygon: { - assert (m_num_embeddings <= 4); - geometry = new Polygon(); - break; - } - default: - throw new JsonGeometryException("parsing error"); - } - } else if (m_num_embeddings == 1) { - if (geo_json_type != GeoJsonType.Point) { - throw new JsonGeometryException("parsing error"); - } - - assert (m_point != null); - geometry = m_point; - } else if (m_num_embeddings == 2) { - if (geo_json_type == GeoJsonType.MultiPoint) { - geometry = createMultiPointFromStreams_(); - } else if (geo_json_type == GeoJsonType.LineString) { - geometry = createPolylineFromStreams_(); - } else { - throw new JsonGeometryException("parsing error"); - } - } else if (m_num_embeddings == 3) { - if (geo_json_type == GeoJsonType.Polygon) { - geometry = createPolygonFromStreams_(); - } else if (geo_json_type == GeoJsonType.MultiLineString) { - geometry = createPolylineFromStreams_(); - } else { - throw new JsonGeometryException("parsing error"); - } - } else { - if (geo_json_type != GeoJsonType.MultiPolygon) { - throw new JsonGeometryException("parsing error"); - } - - geometry = createPolygonFromStreams_(); - } - - return geometry; - } - - private Geometry createPolygonFromStreams_() { - assert (m_position != null); - assert (m_paths != null); - assert ((m_num_embeddings == 3 && m_path_flags == null) || (m_num_embeddings == 4 && m_path_flags != null)); - - Polygon polygon = new Polygon(); - MultiPathImpl multi_path_impl = (MultiPathImpl) polygon._getImpl(); - - checkPathPointCountsForMultiPath_(true); - multi_path_impl.setAttributeStreamRef(Semantics.POSITION, - m_position); - - if (m_b_has_zs) { - assert (m_zs != null); - multi_path_impl.setAttributeStreamRef(Semantics.Z, m_zs); - } - - if (m_b_has_ms) { - assert (m_ms != null); - multi_path_impl.setAttributeStreamRef(Semantics.M, m_ms); - } - - if (m_path_flags == null) { - m_path_flags = (AttributeStreamOfInt8) AttributeStreamBase - .createByteStream(m_paths.size(), (byte) 0); - m_path_flags - .setBits( - 0, - (byte) (PathFlags.enumClosed | PathFlags.enumOGCStartPolygon)); - - for (int i = 1; i < m_path_flags.size() - 1; i++) { - m_path_flags.setBits(i, (byte) PathFlags.enumClosed); - } - } - - multi_path_impl.setPathStreamRef(m_paths); - multi_path_impl.setPathFlagsStreamRef(m_path_flags); - multi_path_impl - .notifyModified(MultiVertexGeometryImpl.DirtyFlags.DirtyAll); - - AttributeStreamOfInt8 path_flags_clone = new AttributeStreamOfInt8( - m_path_flags); - - for (int i = 0; i < path_flags_clone.size() - 1; i++) { - assert ((path_flags_clone.read(i) & PathFlags.enumClosed) != 0); - assert ((m_path_flags.read(i) & PathFlags.enumClosed) != 0); - - if ((path_flags_clone.read(i) & PathFlags.enumOGCStartPolygon) != 0) {// Should - // be - // clockwise - if (!InternalUtils.isClockwiseRing(multi_path_impl, i)) { - multi_path_impl.reversePath(i); // make clockwise - } - } else {// Should be counter-clockwise - if (InternalUtils.isClockwiseRing(multi_path_impl, i)) { - multi_path_impl.reversePath(i); // make - // counter-clockwise - } - } - } - - multi_path_impl.setPathFlagsStreamRef(path_flags_clone); - multi_path_impl.clearDirtyOGCFlags(); - - return polygon; - } - - private Geometry createPolylineFromStreams_() { - assert (m_position != null); - assert ((m_num_embeddings == 2 && m_paths == null) || (m_num_embeddings == 3 && m_paths != null)); - assert (m_path_flags == null); - - Polyline polyline = new Polyline(); - MultiPathImpl multi_path_impl = (MultiPathImpl) polyline._getImpl(); - - if (m_paths == null) { - m_paths = (AttributeStreamOfInt32) AttributeStreamBase - .createIndexStream(0); - m_paths.add(0); - m_paths.add(m_position.size() / 2); - } - - checkPathPointCountsForMultiPath_(false); - multi_path_impl.setAttributeStreamRef(Semantics.POSITION, - m_position); - - if (m_b_has_zs) { - assert (m_zs != null); - multi_path_impl.setAttributeStreamRef(Semantics.Z, m_zs); - } - - if (m_b_has_ms) { - assert (m_ms != null); - multi_path_impl.setAttributeStreamRef(Semantics.M, m_ms); - } - - m_path_flags = (AttributeStreamOfInt8) AttributeStreamBase - .createByteStream(m_paths.size(), (byte) 0); - - multi_path_impl.setPathStreamRef(m_paths); - multi_path_impl.setPathFlagsStreamRef(m_path_flags); - multi_path_impl - .notifyModified(MultiVertexGeometryImpl.DirtyFlags.DirtyAll); - - return polyline; - } - - private Geometry createMultiPointFromStreams_() { - assert (m_position != null); - assert (m_paths == null); - assert (m_path_flags == null); - - MultiPoint multi_point = new MultiPoint(); - MultiPointImpl multi_point_impl = (MultiPointImpl) multi_point - ._getImpl(); - multi_point_impl.setAttributeStreamRef(Semantics.POSITION, - m_position); - - if (m_b_has_zs) { - assert (m_zs != null); - multi_point_impl.setAttributeStreamRef(Semantics.Z, m_zs); - } - - if (m_b_has_ms) { - assert (m_ms != null); - multi_point_impl.setAttributeStreamRef(Semantics.M, m_ms); - } - - multi_point_impl.resize(m_position.size() / 2); - multi_point_impl.notifyModified(MultiVertexGeometryImpl.DirtyFlags.DirtyAll); - return multi_point; - } - - private void checkPathPointCountsForMultiPath_(boolean b_is_polygon) { - Point2D pt1 = new Point2D(), pt2 = new Point2D(); - double z1 = 0.0, z2 = 0.0, m1 = 0.0, m2 = 0.0; - int path_count = m_paths.size() - 1; - int guess_adjustment = 0; - - if (b_is_polygon) {// Polygon - guess_adjustment = path_count; // may remove up to path_count - // number of points - } else {// Polyline - for (int path = 0; path < path_count; path++) { - int path_size = m_paths.read(path + 1) - m_paths.read(path); - - if (path_size == 1) { - guess_adjustment--; // will add a point for each path - // containing only 1 point - } - } - - if (guess_adjustment == 0) { - return; // all paths are okay - } - } - - AttributeStreamOfDbl adjusted_position = (AttributeStreamOfDbl) AttributeStreamBase - .createDoubleStream(m_position.size() - guess_adjustment); - AttributeStreamOfInt32 adjusted_paths = (AttributeStreamOfInt32) AttributeStreamBase - .createIndexStream(m_paths.size()); - AttributeStreamOfDbl adjusted_zs = null; - AttributeStreamOfDbl adjusted_ms = null; - - if (m_b_has_zs) { - adjusted_zs = (AttributeStreamOfDbl) AttributeStreamBase - .createDoubleStream(m_zs.size() - guess_adjustment); - } - - if (m_b_has_ms) { - adjusted_ms = (AttributeStreamOfDbl) AttributeStreamBase - .createDoubleStream(m_ms.size() - guess_adjustment); - } - - int adjusted_start = 0; - adjusted_paths.write(0, 0); - - for (int path = 0; path < path_count; path++) { - int path_start = m_paths.read(path); - int path_end = m_paths.read(path + 1); - int path_size = path_end - path_start; - assert (path_size != 0); // we should not have added empty parts - // on import - - if (path_size == 1) { - insertIntoAdjustedStreams_(adjusted_position, adjusted_zs, - adjusted_ms, adjusted_start, path_start, path_size); - insertIntoAdjustedStreams_(adjusted_position, adjusted_zs, - adjusted_ms, adjusted_start + 1, path_start, - path_size); - adjusted_start += 2; - } else if (path_size >= 3 && b_is_polygon) { - m_position.read(path_start * 2, pt1); - m_position.read((path_end - 1) * 2, pt2); - - if (m_b_has_zs) { - z1 = m_zs.readAsDbl(path_start); - z2 = m_zs.readAsDbl(path_end - 1); - } - - if (m_b_has_ms) { - m1 = m_ms.readAsDbl(path_start); - m2 = m_ms.readAsDbl(path_end - 1); - } - - if (pt1.equals(pt2) - && (NumberUtils.isNaN(z1) && NumberUtils.isNaN(z2) || z1 == z2) - && (NumberUtils.isNaN(m1) && NumberUtils.isNaN(m2) || m1 == m2)) { - insertIntoAdjustedStreams_(adjusted_position, - adjusted_zs, adjusted_ms, adjusted_start, - path_start, path_size - 1); - adjusted_start += path_size - 1; - } else { - insertIntoAdjustedStreams_(adjusted_position, - adjusted_zs, adjusted_ms, adjusted_start, - path_start, path_size); - adjusted_start += path_size; - } - } else { - insertIntoAdjustedStreams_(adjusted_position, adjusted_zs, - adjusted_ms, adjusted_start, path_start, path_size); - adjusted_start += path_size; - } - adjusted_paths.write(path + 1, adjusted_start); - } - - m_position = adjusted_position; - m_paths = adjusted_paths; - m_zs = adjusted_zs; - m_ms = adjusted_ms; - } - - private void insertIntoAdjustedStreams_( - AttributeStreamOfDbl adjusted_position, - AttributeStreamOfDbl adjusted_zs, - AttributeStreamOfDbl adjusted_ms, int adjusted_start, - int path_start, int count) { - adjusted_position.insertRange(adjusted_start * 2, m_position, - path_start * 2, count * 2, true, 2, adjusted_start * 2); - - if (m_b_has_zs) { - adjusted_zs.insertRange(adjusted_start, m_zs, path_start, - count, true, 1, adjusted_start); - } - - if (m_b_has_ms) { - adjusted_ms.insertRange(adjusted_start, m_ms, path_start, - count, true, 1, adjusted_start); - } - } - - static SpatialReference importSpatialReferenceFromCrs( - JsonReader json_iterator, ProgressTracker progress_tracker) - throws JsonGeometryException { - // According to the spec, a null crs corresponds to no spatial - // reference - if (json_iterator.currentToken() == JsonReader.Token.VALUE_NULL) { - return null; - } - - if (json_iterator.currentToken() == JsonReader.Token.VALUE_STRING) {// see - // http://wiki.geojson.org/RFC-001 - // (this - // is - // deprecated, - // but - // there - // may - // be - // data - // with - // this - // format) - - String crs_short_form = json_iterator.currentString(); - int wkid = GeoJsonCrsTables - .getWkidFromCrsShortForm(crs_short_form); - - if (wkid == -1) { - throw new GeometryException("not implemented"); - } - - SpatialReference spatial_reference = null; - - try { - spatial_reference = SpatialReference.create(wkid); - } catch (Exception e) { - } - - return spatial_reference; - } - - if (json_iterator.currentToken() != JsonReader.Token.START_OBJECT) { - throw new JsonGeometryException("parsing error"); - } - - // This is to support all cases of crs identifiers I've seen. Some - // may be rare or are legacy formats, but all are simple to - // accomodate. - boolean b_found_type = false; - boolean b_found_properties = false; - boolean b_found_properties_name = false; - boolean b_found_properties_href = false; - boolean b_found_properties_urn = false; - boolean b_found_properties_url = false; - boolean b_found_properties_code = false; - boolean b_found_esriwkt = false; - String crs_field = null; - String properties_field = null; - String crs_identifier_name = null; - String crs_identifier_urn = null; - String crs_identifier_href = null; - String crs_identifier_url = null; - String esriwkt = null; - int crs_identifier_code = -1; - JsonReader.Token current_token; - - while (json_iterator.nextToken() != JsonReader.Token.END_OBJECT) { - crs_field = json_iterator.currentString(); - - if (crs_field.equals("type")) { - if (b_found_type) { - throw new JsonGeometryException("parsing error"); - } - - b_found_type = true; - - current_token = json_iterator.nextToken(); - - if (current_token != JsonReader.Token.VALUE_STRING) { - throw new JsonGeometryException("parsing error"); - } - - //type = json_iterator.currentString(); - } else if (crs_field.equals("properties")) { - if (b_found_properties) { - throw new JsonGeometryException("parsing error"); - } - - b_found_properties = true; - - current_token = json_iterator.nextToken(); - - if (current_token != JsonReader.Token.START_OBJECT) { - throw new JsonGeometryException("parsing error"); - } - - while (json_iterator.nextToken() != JsonReader.Token.END_OBJECT) { - properties_field = json_iterator.currentString(); - - if (properties_field.equals("name")) { - if (b_found_properties_name) { - throw new JsonGeometryException("parsing error"); - } - - b_found_properties_name = true; - crs_identifier_name = getCrsIdentifier_(json_iterator); - } else if (properties_field.equals("href")) { - if (b_found_properties_href) { - throw new JsonGeometryException("parsing error"); - } - - b_found_properties_href = true; - crs_identifier_href = getCrsIdentifier_(json_iterator); - } else if (properties_field.equals("urn")) { - if (b_found_properties_urn) { - throw new JsonGeometryException("parsing error"); - } - - b_found_properties_urn = true; - crs_identifier_urn = getCrsIdentifier_(json_iterator); - } else if (properties_field.equals("url")) { - if (b_found_properties_url) { - throw new JsonGeometryException("parsing error"); - } - - b_found_properties_url = true; - crs_identifier_url = getCrsIdentifier_(json_iterator); - } else if (properties_field.equals("code")) { - if (b_found_properties_code) { - throw new JsonGeometryException("parsing error"); - } - - b_found_properties_code = true; - - current_token = json_iterator.nextToken(); - - if (current_token != JsonReader.Token.VALUE_NUMBER_INT) { - throw new JsonGeometryException("parsing error"); - } - - crs_identifier_code = json_iterator - .currentIntValue(); - } else { - json_iterator.nextToken(); - json_iterator.skipChildren(); - } - } - } else if (crs_field.equals("esriwkt")) { - if (b_found_esriwkt) { - throw new JsonGeometryException("parsing error"); - } - - b_found_esriwkt = true; - - current_token = json_iterator.nextToken(); - - if (current_token != JsonReader.Token.VALUE_STRING) { - throw new JsonGeometryException("parsing error"); - } - - esriwkt = json_iterator.currentString(); - } else { - json_iterator.nextToken(); - json_iterator.skipChildren(); - } - } - - if ((!b_found_type || !b_found_properties) && !b_found_esriwkt) { - throw new JsonGeometryException("parsing error"); - } - - int wkid = -1; - - if (b_found_properties_name) { - wkid = GeoJsonCrsTables.getWkidFromCrsName(crs_identifier_name); // see - // http://wiki.geojson.org/GeoJSON_draft_version_6 - // (most - // common) - } else if (b_found_properties_href) { - wkid = GeoJsonCrsTables.getWkidFromCrsHref(crs_identifier_href); // see - // http://wiki.geojson.org/GeoJSON_draft_version_6 - // (somewhat - // common) - } else if (b_found_properties_urn) { - wkid = GeoJsonCrsTables - .getWkidFromCrsOgcUrn(crs_identifier_urn); // see - // http://wiki.geojson.org/GeoJSON_draft_version_5 - // (rare) - } else if (b_found_properties_url) { - wkid = GeoJsonCrsTables.getWkidFromCrsHref(crs_identifier_url); // see - // http://wiki.geojson.org/GeoJSON_draft_version_5 - // (rare) - } else if (b_found_properties_code) { - wkid = crs_identifier_code; // see - // http://wiki.geojson.org/GeoJSON_draft_version_5 - // (rare) - } else if (!b_found_esriwkt) { - throw new JsonGeometryException("parsing error"); - } - - if (wkid < 0 && !b_found_esriwkt && !b_found_properties_name) { - throw new JsonGeometryException("parsing error"); - } - - SpatialReference spatial_reference = null; - - if (wkid > 0) { - try { - spatial_reference = SpatialReference.create(wkid); - } catch (Exception e) { - } - } - - if (spatial_reference == null) { - try { - if (b_found_esriwkt) {// I exported crs wkt strings like - // this - spatial_reference = SpatialReference.create(esriwkt); - } else if (b_found_properties_name) {// AGOL exported crs - // wkt strings like - // this where the - // crs identifier of - // the properties - // name is like - // "ESRI:" - String potential_wkt = GeoJsonCrsTables - .getWktFromCrsName(crs_identifier_name); - spatial_reference = SpatialReference - .create(potential_wkt); - } - } catch (Exception e) { - } - } - - return spatial_reference; - } - - // see http://geojsonwg.github.io/draft-geojson/draft.html - static SpatialReference importSpatialReferenceFromCrsUrn_( - JsonReader json_iterator, ProgressTracker progress_tracker) - throws JsonGeometryException { - // According to the spec, a null crs corresponds to no spatial - // reference - if (json_iterator.currentToken() == JsonReader.Token.VALUE_NULL) { - return null; - } - - if (json_iterator.currentToken() != JsonReader.Token.VALUE_STRING) { - throw new JsonGeometryException("parsing error"); - } - - String crs_identifier_urn = json_iterator.currentString(); - - int wkid = GeoJsonCrsTables.getWkidFromCrsName(crs_identifier_urn); // This - // will - // check - // for - // short - // form - // name, - // as - // well - // as - // long - // form - // URNs - - if (wkid == -1) { - throw new GeometryException("not implemented"); - } - - SpatialReference spatial_reference = SpatialReference.create(wkid); - - return spatial_reference; - } - - private static String getCrsIdentifier_(JsonReader json_iterator) - throws JsonGeometryException { - JsonReader.Token current_token = json_iterator.nextToken(); - - if (current_token != JsonReader.Token.VALUE_STRING) { - throw new JsonGeometryException("parsing error"); - } - - return json_iterator.currentString(); - } - - } - - @Override - public MapOGCStructure executeOGC(int import_flags, String geoJsonString, - ProgressTracker progress_tracker) throws JsonGeometryException { - return executeOGC(import_flags, JsonParserReader.createFromString(geoJsonString), - progress_tracker); - } - - public MapOGCStructure executeOGC(int import_flags, - JsonReader json_iterator, ProgressTracker progress_tracker) - throws JsonGeometryException { - MapOGCStructure mapOGCStructure = OperatorImportFromGeoJsonHelper.importFromGeoJson( - import_flags, Geometry.Type.Unknown, json_iterator, - progress_tracker, false, 0); - - //This is to restore legacy behavior when we always return a geometry collection of one element. - MapOGCStructure res = new MapOGCStructure(); - res.m_ogcStructure = new OGCStructure(); - res.m_ogcStructure.m_type = 0; - res.m_ogcStructure.m_structures = new ArrayList(); - res.m_ogcStructure.m_structures.add(mapOGCStructure.m_ogcStructure); - res.m_spatialReference = mapOGCStructure.m_spatialReference; - return res; - } + @Override + MapGeometryCursor execute(int import_flags, StringCursor stringCursor, ProgressTracker progressTracker) { + return new OperatorImportFromGeoJsonCursor(import_flags, stringCursor, progressTracker); + } + + @Override + public MapGeometry execute(int importFlags, Geometry.Type type, + String geoJsonString, ProgressTracker progressTracker) + throws JsonGeometryException { + MapGeometry map_geometry = OperatorImportFromGeoJsonHelper + .importFromGeoJson(importFlags, type, JsonParserReader.createFromString(geoJsonString), progressTracker, false); + return map_geometry; + } + + @Override + public MapGeometry execute(int importFlags, Geometry.Type type, + JsonReader jsonReader, ProgressTracker progressTracker) + throws JsonGeometryException { + if (jsonReader == null) + return null; + + return OperatorImportFromGeoJsonHelper.importFromGeoJson(importFlags, + type, jsonReader, progressTracker, false); + } + + static final class OperatorImportFromGeoJsonHelper { + + private AttributeStreamOfDbl m_position; + private AttributeStreamOfDbl m_zs; + private AttributeStreamOfDbl m_ms; + private AttributeStreamOfInt32 m_paths; + private AttributeStreamOfInt8 m_path_flags; + private Point m_point; // special case for Points + private boolean m_b_has_zs; + private boolean m_b_has_ms; + private boolean m_b_has_zs_known; + private boolean m_b_has_ms_known; + private int m_num_embeddings; + + int m_ogcType; + + OperatorImportFromGeoJsonHelper() { + m_position = null; + m_zs = null; + m_ms = null; + m_paths = null; + m_path_flags = null; + m_point = null; + m_b_has_zs = false; + m_b_has_ms = false; + m_b_has_zs_known = false; + m_b_has_ms_known = false; + m_num_embeddings = 0; + m_ogcType = 0; + } + + static MapGeometry importFromGeoJson(int importFlags, + Geometry.Type type, JsonReader json_iterator, + ProgressTracker progress_tracker, boolean skip_coordinates) + throws JsonGeometryException { + OperatorImportFromGeoJsonHelper geo_json_helper = new OperatorImportFromGeoJsonHelper(); + MapOGCStructure ms = geo_json_helper.importFromGeoJsonImpl( + importFlags, type, json_iterator, progress_tracker, + skip_coordinates, 0); + + if (geo_json_helper.m_ogcType == GeoJsonValues.GeometryCollection && !skip_coordinates) + throw new JsonGeometryException("parsing error"); + + return new MapGeometry(ms.m_ogcStructure.m_geometry, + ms.m_spatialReference); + } + + static MapOGCStructure importFromGeoJson(int importFlags, + Geometry.Type type, JsonReader json_iterator, + ProgressTracker progress_tracker, boolean skip_coordinates, + int recursion) throws JsonGeometryException { + OperatorImportFromGeoJsonHelper geo_json_helper = new OperatorImportFromGeoJsonHelper(); + MapOGCStructure ms = geo_json_helper.importFromGeoJsonImpl( + importFlags, type, json_iterator, progress_tracker, + skip_coordinates, recursion); + + if (geo_json_helper.m_ogcType == GeoJsonValues.GeometryCollection && !skip_coordinates) + throw new JsonGeometryException("parsing error"); + + return ms; + } + + MapOGCStructure importFromGeoJsonImpl(int importFlags, + Geometry.Type type, JsonReader json_iterator, + ProgressTracker progress_tracker, boolean skip_coordinates, + int recursion) throws JsonGeometryException { + OperatorImportFromGeoJsonHelper geo_json_helper = this; + boolean b_type_found = false; + boolean b_coordinates_found = false; + boolean b_crs_found = false; + boolean b_crsURN_found = false; + boolean b_geometry_collection = false; + boolean b_geometries_found = false; + GeoJsonType geo_json_type = null; + + Geometry geometry = null; + SpatialReference spatial_reference = null; + + JsonReader.Token current_token; + String field_name = null; + MapOGCStructure ms = new MapOGCStructure(); + + while ((current_token = json_iterator.nextToken()) != JsonReader.Token.END_OBJECT) { + field_name = json_iterator.currentString(); + + if (field_name.equals("type")) { + if (b_type_found) { + throw new JsonGeometryException("parsing error"); + } + + b_type_found = true; + current_token = json_iterator.nextToken(); + + if (current_token != JsonReader.Token.VALUE_STRING) { + throw new JsonGeometryException("parsing error"); + } + + String s = json_iterator.currentString(); + try { + geo_json_type = GeoJsonType.valueOf(s); + } catch (Exception ex) { + throw new JsonGeometryException(s); + } + + if (geo_json_type == GeoJsonType.GeometryCollection) { + if (type != Geometry.Type.Unknown) + throw new JsonGeometryException("parsing error"); + + b_geometry_collection = true; + } + } else if (field_name.equals("geometries")) { + b_geometries_found = true; + if (type != Geometry.Type.Unknown) + throw new JsonGeometryException("parsing error"); + + if (recursion > 10) { + throw new JsonGeometryException("deep geojson"); + } + + if (skip_coordinates) { + json_iterator.skipChildren(); + } else { + current_token = json_iterator.nextToken(); + + ms.m_ogcStructure = new OGCStructure(); + ms.m_ogcStructure.m_type = GeoJsonValues.GeometryCollection; + ms.m_ogcStructure.m_structures = new ArrayList( + 0); + + if (current_token == JsonReader.Token.START_ARRAY) { + current_token = json_iterator.nextToken(); + while (current_token != JsonReader.Token.END_ARRAY) { + MapOGCStructure child = importFromGeoJson( + importFlags + | GeoJsonImportFlags.geoJsonImportSkipCRS, + type, json_iterator, + progress_tracker, false, + recursion + 1); + ms.m_ogcStructure.m_structures + .add(child.m_ogcStructure); + + current_token = json_iterator.nextToken(); + } + } else if (current_token != JsonReader.Token.VALUE_NULL) { + throw new JsonGeometryException("parsing error"); + } + } + } else if (field_name.equals("coordinates")) { + + if (b_coordinates_found) { + throw new JsonGeometryException("parsing error"); + } + + b_coordinates_found = true; + current_token = json_iterator.nextToken(); + + if (skip_coordinates) { + json_iterator.skipChildren(); + } else {// According to the spec, the value of the + // coordinates must be an array. However, I do an + // extra check for null too. + if (current_token != JsonReader.Token.VALUE_NULL) { + if (current_token != JsonReader.Token.START_ARRAY) { + throw new JsonGeometryException("parsing error"); + } + + geo_json_helper.import_coordinates_(json_iterator, + progress_tracker); + } + } + } else if (field_name.equals("crs")) { + if (b_crs_found || b_crsURN_found) { + throw new JsonGeometryException("parsing error"); + } + + b_crs_found = true; + current_token = json_iterator.nextToken(); + + if ((importFlags & GeoJsonImportFlags.geoJsonImportSkipCRS) == 0) + spatial_reference = importSpatialReferenceFromCrs( + json_iterator, progress_tracker); + else + json_iterator.skipChildren(); + } else if (field_name.equals("crsURN")) { + if (b_crs_found || b_crsURN_found) { + throw new JsonGeometryException("parsing error"); + } + + b_crsURN_found = true; + current_token = json_iterator.nextToken(); + + spatial_reference = importSpatialReferenceFromCrsUrn_( + json_iterator, progress_tracker); + } else { + json_iterator.nextToken(); + json_iterator.skipChildren(); + } + } + + // According to the spec, a GeoJSON object must have both a type and + // a coordinates array + if (!b_type_found || (!b_geometry_collection && !b_coordinates_found && !skip_coordinates)) { + throw new JsonGeometryException("parsing error"); + } + + if ((!b_geometry_collection && b_geometries_found) || (b_geometry_collection && !b_geometries_found)) { + throw new JsonGeometryException("parsing error");//found "geometries" but did not see "GeometryCollection" + } + + + if (!skip_coordinates && !b_geometry_collection) { + geometry = geo_json_helper.createGeometry_(geo_json_type, + type.value()); + + ms.m_ogcStructure = new OGCStructure(); + ms.m_ogcStructure.m_type = m_ogcType; + ms.m_ogcStructure.m_geometry = geometry; + } + + if (!b_crs_found + && !b_crsURN_found + && ((importFlags & GeoJsonImportFlags.geoJsonImportSkipCRS) == 0) + && ((importFlags & GeoJsonImportFlags.geoJsonImportNoWGS84Default) == 0)) { + spatial_reference = SpatialReference.create(4326); // the spec + // gives a + // default + // of 4326 + // if no crs + // is given + } + + ms.m_spatialReference = spatial_reference; + return ms; + } + + // We have to import the coordinates in the most general way possible to + // not assume the type of geometry we're parsing. + // JSON allows for unordered objects, so it's possible that the + // coordinates array can come before the type tag when parsing + // sequentially, otherwise + // we would have to parse using a JSON_object, which would be easier, + // but not as space/time efficient. So this function blindly imports the + // coordinates + // into the attribute stream(s), and will later assign them to a + // geometry after the type tag is found. + private void import_coordinates_(JsonReader json_iterator, + ProgressTracker progress_tracker) throws JsonGeometryException { + assert (json_iterator.currentToken() == JsonReader.Token.START_ARRAY); + + int coordinates_level_lower = 1; + int coordinates_level_upper = 4; + + json_iterator.nextToken(); + + while (json_iterator.currentToken() != JsonReader.Token.END_ARRAY) { + if (isDouble_(json_iterator)) { + if (coordinates_level_upper > 1) { + coordinates_level_upper = 1; + } + } else if (json_iterator.currentToken() == JsonReader.Token.START_ARRAY) { + if (coordinates_level_lower < 2) { + coordinates_level_lower = 2; + } + } else { + throw new JsonGeometryException("parsing error"); + } + + if (coordinates_level_lower > coordinates_level_upper) { + throw new IllegalArgumentException("invalid argument"); + } + + if (coordinates_level_lower == coordinates_level_upper + && coordinates_level_lower == 1) {// special + // code + // for + // Points + readCoordinateAsPoint_(json_iterator); + } else { + boolean b_add_path_level_3 = true; + boolean b_polygon_start_level_4 = true; + + assert (json_iterator.currentToken() == JsonReader.Token.START_ARRAY); + json_iterator.nextToken(); + + while (json_iterator.currentToken() != JsonReader.Token.END_ARRAY) { + if (isDouble_(json_iterator)) { + if (coordinates_level_upper > 2) { + coordinates_level_upper = 2; + } + } else if (json_iterator.currentToken() == JsonReader.Token.START_ARRAY) { + if (coordinates_level_lower < 3) { + coordinates_level_lower = 3; + } + } else { + throw new JsonGeometryException("parsing error"); + } + + if (coordinates_level_lower > coordinates_level_upper) { + throw new JsonGeometryException("parsing error"); + } + + if (coordinates_level_lower == coordinates_level_upper + && coordinates_level_lower == 2) {// LineString + // or + // MultiPoint + addCoordinate_(json_iterator); + } else { + boolean b_add_path_level_4 = true; + + assert (json_iterator.currentToken() == JsonReader.Token.START_ARRAY); + json_iterator.nextToken(); + + while (json_iterator.currentToken() != JsonReader.Token.END_ARRAY) { + if (isDouble_(json_iterator)) { + if (coordinates_level_upper > 3) { + coordinates_level_upper = 3; + } + } else if (json_iterator.currentToken() == JsonReader.Token.START_ARRAY) { + if (coordinates_level_lower < 4) { + coordinates_level_lower = 4; + } + } else { + throw new JsonGeometryException("parsing error"); + } + + if (coordinates_level_lower > coordinates_level_upper) { + throw new JsonGeometryException("parsing error"); + } + + if (coordinates_level_lower == coordinates_level_upper + && coordinates_level_lower == 3) {// Polygon + // or + // MultiLineString + if (b_add_path_level_3) { + addPath_(); + b_add_path_level_3 = false; + } + + addCoordinate_(json_iterator); + } else { + assert (json_iterator.currentToken() == JsonReader.Token.START_ARRAY); + json_iterator.nextToken(); + + if (json_iterator.currentToken() != JsonReader.Token.END_ARRAY) { + if (!isDouble_(json_iterator)) { + throw new JsonGeometryException("parsing error"); + } + + assert (coordinates_level_lower == coordinates_level_upper && coordinates_level_lower == 4); + // MultiPolygon + + if (b_add_path_level_4) { + addPath_(); + addPathFlag_(b_polygon_start_level_4); + b_add_path_level_4 = false; + b_polygon_start_level_4 = false; + } + + addCoordinate_(json_iterator); + } + + json_iterator.nextToken(); + } + } + + json_iterator.nextToken(); + } + } + + json_iterator.nextToken(); + } + } + + if (m_paths != null) { + m_paths.add(m_position.size() / 2); // add final path size + } + if (m_path_flags != null) { + m_path_flags.add((byte) 0); // to match the paths size + } + + m_num_embeddings = coordinates_level_lower; + } + + private void readCoordinateAsPoint_(JsonReader json_iterator) + throws JsonGeometryException { + assert (isDouble_(json_iterator)); + + m_point = new Point(); + + double x = readDouble_(json_iterator); + json_iterator.nextToken(); + double y = readDouble_(json_iterator); + json_iterator.nextToken(); + + if (NumberUtils.isNaN(y)) { + x = NumberUtils.NaN(); + } + + m_point.setXY(x, y); + + if (isDouble_(json_iterator)) { + double z = readDouble_(json_iterator); + json_iterator.nextToken(); + m_point.setZ(z); + } + + if (isDouble_(json_iterator)) { + double m = readDouble_(json_iterator); + json_iterator.nextToken(); + m_point.setM(m); + } + + if (json_iterator.currentToken() != JsonReader.Token.END_ARRAY) { + throw new JsonGeometryException("parsing error"); + } + } + + private void addCoordinate_(JsonReader json_iterator) + throws JsonGeometryException { + assert (isDouble_(json_iterator)); + + if (m_position == null) { + m_position = (AttributeStreamOfDbl) AttributeStreamBase + .createDoubleStream(0); + } + + double x = readDouble_(json_iterator); + json_iterator.nextToken(); + double y = readDouble_(json_iterator); + json_iterator.nextToken(); + + int size = m_position.size(); + + m_position.add(x); + m_position.add(y); + + if (isDouble_(json_iterator)) { + if (!m_b_has_zs_known) { + m_b_has_zs_known = true; + m_b_has_zs = true; + m_zs = (AttributeStreamOfDbl) AttributeStreamBase + .createDoubleStream(0); + } else { + if (!m_b_has_zs) { + m_zs = (AttributeStreamOfDbl) AttributeStreamBase + .createDoubleStream(size >> 1, + VertexDescription + .getDefaultValue(Semantics.Z)); + m_b_has_zs = true; + } + } + + double z = readDouble_(json_iterator); + json_iterator.nextToken(); + m_zs.add(z); + } else { + if (!m_b_has_zs_known) { + m_b_has_zs_known = true; + m_b_has_zs = false; + } else { + if (m_b_has_zs) { + m_zs.add(VertexDescription.getDefaultValue(Semantics.Z)); + } + } + } + + if (isDouble_(json_iterator)) { + if (!m_b_has_ms_known) { + m_b_has_ms_known = true; + m_b_has_ms = true; + m_ms = (AttributeStreamOfDbl) AttributeStreamBase + .createDoubleStream(0); + } else { + if (!m_b_has_ms) { + m_ms = (AttributeStreamOfDbl) AttributeStreamBase + .createDoubleStream(size >> 1, + VertexDescription + .getDefaultValue(Semantics.M)); + m_b_has_ms = true; + } + } + + double m = readDouble_(json_iterator); + json_iterator.nextToken(); + m_ms.add(m); + } else { + if (!m_b_has_ms_known) { + m_b_has_ms_known = true; + m_b_has_ms = false; + } else { + if (m_b_has_ms) { + m_zs.add(VertexDescription.getDefaultValue(Semantics.M)); + } + } + } + + if (json_iterator.currentToken() != JsonReader.Token.END_ARRAY) { + throw new JsonGeometryException("parsing error"); + } + } + + private void addPath_() { + if (m_paths == null) { + m_paths = (AttributeStreamOfInt32) AttributeStreamBase + .createIndexStream(0); + } + + if (m_position == null) { + m_paths.add(0); + } else { + m_paths.add(m_position.size() / 2); + } + } + + private void addPathFlag_(boolean b_polygon_start) { + if (m_path_flags == null) { + m_path_flags = (AttributeStreamOfInt8) AttributeStreamBase + .createByteStream(0); + } + + if (b_polygon_start) { + m_path_flags + .add((byte) (PathFlags.enumClosed | PathFlags.enumOGCStartPolygon)); + } else { + m_path_flags.add((byte) PathFlags.enumClosed); + } + } + + private double readDouble_(JsonReader json_iterator) + throws JsonGeometryException { + JsonReader.Token current_token = json_iterator.currentToken(); + if (current_token == JsonReader.Token.VALUE_NULL + || (current_token == JsonReader.Token.VALUE_STRING && json_iterator + .currentString().equals("NaN"))) { + return NumberUtils.NaN(); + } else { + return json_iterator.currentDoubleValue(); + } + } + + private boolean isDouble_(JsonReader json_iterator) + throws JsonGeometryException { + JsonReader.Token current_token = json_iterator.currentToken(); + + if (current_token == JsonReader.Token.VALUE_NUMBER_FLOAT) { + return true; + } + + if (current_token == JsonReader.Token.VALUE_NUMBER_INT) { + return true; + } + + if (current_token == JsonReader.Token.VALUE_NULL + || (current_token == JsonReader.Token.VALUE_STRING && json_iterator + .currentString().equals("NaN"))) { + return true; + } + + return false; + } + + //does not accept GeometryCollection + private Geometry createGeometry_(GeoJsonType geo_json_type, int type) + throws JsonGeometryException { + Geometry geometry; + + if (type != Geometry.GeometryType.Unknown) { + switch (type) { + case Geometry.GeometryType.Polygon: + if (geo_json_type != GeoJsonType.MultiPolygon + && geo_json_type != GeoJsonType.Polygon) { + throw new GeometryException("invalid shape type"); + } + break; + case Geometry.GeometryType.Polyline: + if (geo_json_type != GeoJsonType.MultiLineString + && geo_json_type != GeoJsonType.LineString) { + throw new GeometryException("invalid shape type"); + } + break; + case Geometry.GeometryType.MultiPoint: + if (geo_json_type != GeoJsonType.MultiPoint) { + throw new GeometryException("invalid shape type"); + } + break; + case Geometry.GeometryType.Point: + if (geo_json_type != GeoJsonType.Point) { + throw new GeometryException("invalid shape type"); + } + break; + default: + throw new GeometryException("invalid shape type"); + } + } + + m_ogcType = geo_json_type.geogsjonvalue(); + if (geo_json_type == GeoJsonType.GeometryCollection) + throw new IllegalArgumentException("invalid argument"); + + if (m_position == null && m_point == null) { + switch (geo_json_type) { + case Point: { + if (m_num_embeddings > 1) { + throw new JsonGeometryException("parsing error"); + } + + geometry = new Point(); + break; + } + case MultiPoint: { + if (m_num_embeddings > 2) { + throw new JsonGeometryException("parsing error"); + } + + geometry = new MultiPoint(); + break; + } + case LineString: { + if (m_num_embeddings > 2) { + throw new JsonGeometryException("parsing error"); + } + + geometry = new Polyline(); + break; + } + case MultiLineString: { + if (m_num_embeddings > 3) { + throw new JsonGeometryException("parsing error"); + } + + geometry = new Polyline(); + break; + } + case Polygon: { + if (m_num_embeddings > 3) { + throw new JsonGeometryException("parsing error"); + } + + geometry = new Polygon(); + break; + } + case MultiPolygon: { + assert (m_num_embeddings <= 4); + geometry = new Polygon(); + break; + } + default: + throw new JsonGeometryException("parsing error"); + } + } else if (m_num_embeddings == 1) { + if (geo_json_type != GeoJsonType.Point) { + throw new JsonGeometryException("parsing error"); + } + + assert (m_point != null); + geometry = m_point; + } else if (m_num_embeddings == 2) { + if (geo_json_type == GeoJsonType.MultiPoint) { + geometry = createMultiPointFromStreams_(); + } else if (geo_json_type == GeoJsonType.LineString) { + geometry = createPolylineFromStreams_(); + } else { + throw new JsonGeometryException("parsing error"); + } + } else if (m_num_embeddings == 3) { + if (geo_json_type == GeoJsonType.Polygon) { + geometry = createPolygonFromStreams_(); + } else if (geo_json_type == GeoJsonType.MultiLineString) { + geometry = createPolylineFromStreams_(); + } else { + throw new JsonGeometryException("parsing error"); + } + } else { + if (geo_json_type != GeoJsonType.MultiPolygon) { + throw new JsonGeometryException("parsing error"); + } + + geometry = createPolygonFromStreams_(); + } + + return geometry; + } + + private Geometry createPolygonFromStreams_() { + assert (m_position != null); + assert (m_paths != null); + assert ((m_num_embeddings == 3 && m_path_flags == null) || (m_num_embeddings == 4 && m_path_flags != null)); + + Polygon polygon = new Polygon(); + MultiPathImpl multi_path_impl = (MultiPathImpl) polygon._getImpl(); + + checkPathPointCountsForMultiPath_(true); + multi_path_impl.setAttributeStreamRef(Semantics.POSITION, + m_position); + + if (m_b_has_zs) { + assert (m_zs != null); + multi_path_impl.setAttributeStreamRef(Semantics.Z, m_zs); + } + + if (m_b_has_ms) { + assert (m_ms != null); + multi_path_impl.setAttributeStreamRef(Semantics.M, m_ms); + } + + if (m_path_flags == null) { + m_path_flags = (AttributeStreamOfInt8) AttributeStreamBase + .createByteStream(m_paths.size(), (byte) 0); + m_path_flags + .setBits( + 0, + (byte) (PathFlags.enumClosed | PathFlags.enumOGCStartPolygon)); + + for (int i = 1; i < m_path_flags.size() - 1; i++) { + m_path_flags.setBits(i, (byte) PathFlags.enumClosed); + } + } + + multi_path_impl.setPathStreamRef(m_paths); + multi_path_impl.setPathFlagsStreamRef(m_path_flags); + multi_path_impl + .notifyModified(MultiVertexGeometryImpl.DirtyFlags.DirtyAll); + + AttributeStreamOfInt8 path_flags_clone = new AttributeStreamOfInt8( + m_path_flags); + + for (int i = 0; i < path_flags_clone.size() - 1; i++) { + assert ((path_flags_clone.read(i) & PathFlags.enumClosed) != 0); + assert ((m_path_flags.read(i) & PathFlags.enumClosed) != 0); + + if ((path_flags_clone.read(i) & PathFlags.enumOGCStartPolygon) != 0) {// Should + // be + // clockwise + if (!InternalUtils.isClockwiseRing(multi_path_impl, i)) { + multi_path_impl.reversePath(i); // make clockwise + } + } else {// Should be counter-clockwise + if (InternalUtils.isClockwiseRing(multi_path_impl, i)) { + multi_path_impl.reversePath(i); // make + // counter-clockwise + } + } + } + + multi_path_impl.setPathFlagsStreamRef(path_flags_clone); + multi_path_impl.clearDirtyOGCFlags(); + + return polygon; + } + + private Geometry createPolylineFromStreams_() { + assert (m_position != null); + assert ((m_num_embeddings == 2 && m_paths == null) || (m_num_embeddings == 3 && m_paths != null)); + assert (m_path_flags == null); + + Polyline polyline = new Polyline(); + MultiPathImpl multi_path_impl = (MultiPathImpl) polyline._getImpl(); + + if (m_paths == null) { + m_paths = (AttributeStreamOfInt32) AttributeStreamBase + .createIndexStream(0); + m_paths.add(0); + m_paths.add(m_position.size() / 2); + } + + checkPathPointCountsForMultiPath_(false); + multi_path_impl.setAttributeStreamRef(Semantics.POSITION, + m_position); + + if (m_b_has_zs) { + assert (m_zs != null); + multi_path_impl.setAttributeStreamRef(Semantics.Z, m_zs); + } + + if (m_b_has_ms) { + assert (m_ms != null); + multi_path_impl.setAttributeStreamRef(Semantics.M, m_ms); + } + + m_path_flags = (AttributeStreamOfInt8) AttributeStreamBase + .createByteStream(m_paths.size(), (byte) 0); + + multi_path_impl.setPathStreamRef(m_paths); + multi_path_impl.setPathFlagsStreamRef(m_path_flags); + multi_path_impl + .notifyModified(MultiVertexGeometryImpl.DirtyFlags.DirtyAll); + + return polyline; + } + + private Geometry createMultiPointFromStreams_() { + assert (m_position != null); + assert (m_paths == null); + assert (m_path_flags == null); + + MultiPoint multi_point = new MultiPoint(); + MultiPointImpl multi_point_impl = (MultiPointImpl) multi_point + ._getImpl(); + multi_point_impl.setAttributeStreamRef(Semantics.POSITION, + m_position); + + if (m_b_has_zs) { + assert (m_zs != null); + multi_point_impl.setAttributeStreamRef(Semantics.Z, m_zs); + } + + if (m_b_has_ms) { + assert (m_ms != null); + multi_point_impl.setAttributeStreamRef(Semantics.M, m_ms); + } + + multi_point_impl.resize(m_position.size() / 2); + multi_point_impl.notifyModified(MultiVertexGeometryImpl.DirtyFlags.DirtyAll); + return multi_point; + } + + private void checkPathPointCountsForMultiPath_(boolean b_is_polygon) { + Point2D pt1 = new Point2D(), pt2 = new Point2D(); + double z1 = 0.0, z2 = 0.0, m1 = 0.0, m2 = 0.0; + int path_count = m_paths.size() - 1; + int guess_adjustment = 0; + + if (b_is_polygon) {// Polygon + guess_adjustment = path_count; // may remove up to path_count + // number of points + } else {// Polyline + for (int path = 0; path < path_count; path++) { + int path_size = m_paths.read(path + 1) - m_paths.read(path); + + if (path_size == 1) { + guess_adjustment--; // will add a point for each path + // containing only 1 point + } + } + + if (guess_adjustment == 0) { + return; // all paths are okay + } + } + + AttributeStreamOfDbl adjusted_position = (AttributeStreamOfDbl) AttributeStreamBase + .createDoubleStream(m_position.size() - guess_adjustment); + AttributeStreamOfInt32 adjusted_paths = (AttributeStreamOfInt32) AttributeStreamBase + .createIndexStream(m_paths.size()); + AttributeStreamOfDbl adjusted_zs = null; + AttributeStreamOfDbl adjusted_ms = null; + + if (m_b_has_zs) { + adjusted_zs = (AttributeStreamOfDbl) AttributeStreamBase + .createDoubleStream(m_zs.size() - guess_adjustment); + } + + if (m_b_has_ms) { + adjusted_ms = (AttributeStreamOfDbl) AttributeStreamBase + .createDoubleStream(m_ms.size() - guess_adjustment); + } + + int adjusted_start = 0; + adjusted_paths.write(0, 0); + + for (int path = 0; path < path_count; path++) { + int path_start = m_paths.read(path); + int path_end = m_paths.read(path + 1); + int path_size = path_end - path_start; + assert (path_size != 0); // we should not have added empty parts + // on import + + if (path_size == 1) { + insertIntoAdjustedStreams_(adjusted_position, adjusted_zs, + adjusted_ms, adjusted_start, path_start, path_size); + insertIntoAdjustedStreams_(adjusted_position, adjusted_zs, + adjusted_ms, adjusted_start + 1, path_start, + path_size); + adjusted_start += 2; + } else if (path_size >= 3 && b_is_polygon) { + m_position.read(path_start * 2, pt1); + m_position.read((path_end - 1) * 2, pt2); + + if (m_b_has_zs) { + z1 = m_zs.readAsDbl(path_start); + z2 = m_zs.readAsDbl(path_end - 1); + } + + if (m_b_has_ms) { + m1 = m_ms.readAsDbl(path_start); + m2 = m_ms.readAsDbl(path_end - 1); + } + + if (pt1.equals(pt2) + && (NumberUtils.isNaN(z1) && NumberUtils.isNaN(z2) || z1 == z2) + && (NumberUtils.isNaN(m1) && NumberUtils.isNaN(m2) || m1 == m2)) { + insertIntoAdjustedStreams_(adjusted_position, + adjusted_zs, adjusted_ms, adjusted_start, + path_start, path_size - 1); + adjusted_start += path_size - 1; + } else { + insertIntoAdjustedStreams_(adjusted_position, + adjusted_zs, adjusted_ms, adjusted_start, + path_start, path_size); + adjusted_start += path_size; + } + } else { + insertIntoAdjustedStreams_(adjusted_position, adjusted_zs, + adjusted_ms, adjusted_start, path_start, path_size); + adjusted_start += path_size; + } + adjusted_paths.write(path + 1, adjusted_start); + } + + m_position = adjusted_position; + m_paths = adjusted_paths; + m_zs = adjusted_zs; + m_ms = adjusted_ms; + } + + private void insertIntoAdjustedStreams_( + AttributeStreamOfDbl adjusted_position, + AttributeStreamOfDbl adjusted_zs, + AttributeStreamOfDbl adjusted_ms, int adjusted_start, + int path_start, int count) { + adjusted_position.insertRange(adjusted_start * 2, m_position, + path_start * 2, count * 2, true, 2, adjusted_start * 2); + + if (m_b_has_zs) { + adjusted_zs.insertRange(adjusted_start, m_zs, path_start, + count, true, 1, adjusted_start); + } + + if (m_b_has_ms) { + adjusted_ms.insertRange(adjusted_start, m_ms, path_start, + count, true, 1, adjusted_start); + } + } + + static SpatialReference importSpatialReferenceFromCrs( + JsonReader json_iterator, ProgressTracker progress_tracker) + throws JsonGeometryException { + // According to the spec, a null crs corresponds to no spatial + // reference + if (json_iterator.currentToken() == JsonReader.Token.VALUE_NULL) { + return null; + } + + if (json_iterator.currentToken() == JsonReader.Token.VALUE_STRING) {// see + // http://wiki.geojson.org/RFC-001 + // (this + // is + // deprecated, + // but + // there + // may + // be + // data + // with + // this + // format) + + String crs_short_form = json_iterator.currentString(); + int wkid = GeoJsonCrsTables + .getWkidFromCrsShortForm(crs_short_form); + + if (wkid == -1) { + throw new GeometryException("not implemented"); + } + + SpatialReference spatial_reference = null; + + try { + spatial_reference = SpatialReference.create(wkid); + } catch (Exception e) { + } + + return spatial_reference; + } + + if (json_iterator.currentToken() != JsonReader.Token.START_OBJECT) { + throw new JsonGeometryException("parsing error"); + } + + // This is to support all cases of crs identifiers I've seen. Some + // may be rare or are legacy formats, but all are simple to + // accomodate. + boolean b_found_type = false; + boolean b_found_properties = false; + boolean b_found_properties_name = false; + boolean b_found_properties_href = false; + boolean b_found_properties_urn = false; + boolean b_found_properties_url = false; + boolean b_found_properties_code = false; + boolean b_found_esriwkt = false; + String crs_field = null; + String properties_field = null; + String crs_identifier_name = null; + String crs_identifier_urn = null; + String crs_identifier_href = null; + String crs_identifier_url = null; + String esriwkt = null; + int crs_identifier_code = -1; + JsonReader.Token current_token; + + while (json_iterator.nextToken() != JsonReader.Token.END_OBJECT) { + crs_field = json_iterator.currentString(); + + if (crs_field.equals("type")) { + if (b_found_type) { + throw new JsonGeometryException("parsing error"); + } + + b_found_type = true; + + current_token = json_iterator.nextToken(); + + if (current_token != JsonReader.Token.VALUE_STRING) { + throw new JsonGeometryException("parsing error"); + } + + //type = json_iterator.currentString(); + } else if (crs_field.equals("properties")) { + if (b_found_properties) { + throw new JsonGeometryException("parsing error"); + } + + b_found_properties = true; + + current_token = json_iterator.nextToken(); + + if (current_token != JsonReader.Token.START_OBJECT) { + throw new JsonGeometryException("parsing error"); + } + + while (json_iterator.nextToken() != JsonReader.Token.END_OBJECT) { + properties_field = json_iterator.currentString(); + + if (properties_field.equals("name")) { + if (b_found_properties_name) { + throw new JsonGeometryException("parsing error"); + } + + b_found_properties_name = true; + crs_identifier_name = getCrsIdentifier_(json_iterator); + } else if (properties_field.equals("href")) { + if (b_found_properties_href) { + throw new JsonGeometryException("parsing error"); + } + + b_found_properties_href = true; + crs_identifier_href = getCrsIdentifier_(json_iterator); + } else if (properties_field.equals("urn")) { + if (b_found_properties_urn) { + throw new JsonGeometryException("parsing error"); + } + + b_found_properties_urn = true; + crs_identifier_urn = getCrsIdentifier_(json_iterator); + } else if (properties_field.equals("url")) { + if (b_found_properties_url) { + throw new JsonGeometryException("parsing error"); + } + + b_found_properties_url = true; + crs_identifier_url = getCrsIdentifier_(json_iterator); + } else if (properties_field.equals("code")) { + if (b_found_properties_code) { + throw new JsonGeometryException("parsing error"); + } + + b_found_properties_code = true; + + current_token = json_iterator.nextToken(); + + if (current_token != JsonReader.Token.VALUE_NUMBER_INT) { + throw new JsonGeometryException("parsing error"); + } + + crs_identifier_code = json_iterator + .currentIntValue(); + } else { + json_iterator.nextToken(); + json_iterator.skipChildren(); + } + } + } else if (crs_field.equals("esriwkt")) { + if (b_found_esriwkt) { + throw new JsonGeometryException("parsing error"); + } + + b_found_esriwkt = true; + + current_token = json_iterator.nextToken(); + + if (current_token != JsonReader.Token.VALUE_STRING) { + throw new JsonGeometryException("parsing error"); + } + + esriwkt = json_iterator.currentString(); + } else { + json_iterator.nextToken(); + json_iterator.skipChildren(); + } + } + + if ((!b_found_type || !b_found_properties) && !b_found_esriwkt) { + throw new JsonGeometryException("parsing error"); + } + + int wkid = -1; + + if (b_found_properties_name) { + wkid = GeoJsonCrsTables.getWkidFromCrsName(crs_identifier_name); // see + // http://wiki.geojson.org/GeoJSON_draft_version_6 + // (most + // common) + } else if (b_found_properties_href) { + wkid = GeoJsonCrsTables.getWkidFromCrsHref(crs_identifier_href); // see + // http://wiki.geojson.org/GeoJSON_draft_version_6 + // (somewhat + // common) + } else if (b_found_properties_urn) { + wkid = GeoJsonCrsTables + .getWkidFromCrsOgcUrn(crs_identifier_urn); // see + // http://wiki.geojson.org/GeoJSON_draft_version_5 + // (rare) + } else if (b_found_properties_url) { + wkid = GeoJsonCrsTables.getWkidFromCrsHref(crs_identifier_url); // see + // http://wiki.geojson.org/GeoJSON_draft_version_5 + // (rare) + } else if (b_found_properties_code) { + wkid = crs_identifier_code; // see + // http://wiki.geojson.org/GeoJSON_draft_version_5 + // (rare) + } else if (!b_found_esriwkt) { + throw new JsonGeometryException("parsing error"); + } + + if (wkid < 0 && !b_found_esriwkt && !b_found_properties_name) { + throw new JsonGeometryException("parsing error"); + } + + SpatialReference spatial_reference = null; + + if (wkid > 0) { + try { + spatial_reference = SpatialReference.create(wkid); + } catch (Exception e) { + } + } + + if (spatial_reference == null) { + try { + if (b_found_esriwkt) {// I exported crs wkt strings like + // this + spatial_reference = SpatialReference.create(esriwkt); + } else if (b_found_properties_name) {// AGOL exported crs + // wkt strings like + // this where the + // crs identifier of + // the properties + // name is like + // "ESRI:" + String potential_wkt = GeoJsonCrsTables + .getWktFromCrsName(crs_identifier_name); + spatial_reference = SpatialReference + .create(potential_wkt); + } + } catch (Exception e) { + } + } + + return spatial_reference; + } + + // see http://geojsonwg.github.io/draft-geojson/draft.html + static SpatialReference importSpatialReferenceFromCrsUrn_( + JsonReader json_iterator, ProgressTracker progress_tracker) + throws JsonGeometryException { + // According to the spec, a null crs corresponds to no spatial + // reference + if (json_iterator.currentToken() == JsonReader.Token.VALUE_NULL) { + return null; + } + + if (json_iterator.currentToken() != JsonReader.Token.VALUE_STRING) { + throw new JsonGeometryException("parsing error"); + } + + String crs_identifier_urn = json_iterator.currentString(); + + int wkid = GeoJsonCrsTables.getWkidFromCrsName(crs_identifier_urn); // This + // will + // check + // for + // short + // form + // name, + // as + // well + // as + // long + // form + // URNs + + if (wkid == -1) { + throw new GeometryException("not implemented"); + } + + SpatialReference spatial_reference = SpatialReference.create(wkid); + + return spatial_reference; + } + + private static String getCrsIdentifier_(JsonReader json_iterator) + throws JsonGeometryException { + JsonReader.Token current_token = json_iterator.nextToken(); + + if (current_token != JsonReader.Token.VALUE_STRING) { + throw new JsonGeometryException("parsing error"); + } + + return json_iterator.currentString(); + } + + } + + @Override + public MapOGCStructure executeOGC(int import_flags, String geoJsonString, + ProgressTracker progress_tracker) throws JsonGeometryException { + return executeOGC(import_flags, JsonParserReader.createFromString(geoJsonString), + progress_tracker); + } + + public MapOGCStructure executeOGC(int import_flags, + JsonReader json_iterator, ProgressTracker progress_tracker) + throws JsonGeometryException { + MapOGCStructure mapOGCStructure = OperatorImportFromGeoJsonHelper.importFromGeoJson( + import_flags, Geometry.Type.Unknown, json_iterator, + progress_tracker, false, 0); + + //This is to restore legacy behavior when we always return a geometry collection of one element. + MapOGCStructure res = new MapOGCStructure(); + res.m_ogcStructure = new OGCStructure(); + res.m_ogcStructure.m_type = 0; + res.m_ogcStructure.m_structures = new ArrayList(); + res.m_ogcStructure.m_structures.add(mapOGCStructure.m_ogcStructure); + res.m_spatialReference = mapOGCStructure.m_spatialReference; + return res; + } } diff --git a/src/main/java/com/esri/core/geometry/OperatorImportFromJson.java b/src/main/java/com/esri/core/geometry/OperatorImportFromJson.java index 7d5e0c25..dd5565fb 100644 --- a/src/main/java/com/esri/core/geometry/OperatorImportFromJson.java +++ b/src/main/java/com/esri/core/geometry/OperatorImportFromJson.java @@ -28,36 +28,36 @@ * Import from JSON format. */ public abstract class OperatorImportFromJson extends Operator { - @Override - public Type getType() { - return Type.ImportFromJson; - } - - /** - * Performs the ImportFromJson operation on a number of Json Strings - * - * @return Returns a MapGeometryCursor. - */ - abstract MapGeometryCursor execute(Geometry.Type type, - JsonReaderCursor jsonReaderCursor); - - /** - * Performs the ImportFromJson operation on a single Json string - * - * @return Returns a MapGeometry. - */ - public abstract MapGeometry execute(Geometry.Type type, - JsonReader jsonReader); - - /** - * Performs the ImportFromJson operation on a single Json string - * - * @return Returns a MapGeometry. - */ - public abstract MapGeometry execute(Geometry.Type type, String string); - - public static OperatorImportFromJson local() { - return (OperatorImportFromJson) OperatorFactoryLocal.getInstance().getOperator(Type.ImportFromJson); - } + @Override + public Type getType() { + return Type.ImportFromJson; + } + + /** + * Performs the ImportFromJson operation on a number of Json Strings + * + * @return Returns a MapGeometryCursor. + */ + abstract MapGeometryCursor execute(Geometry.Type type, + JsonReaderCursor jsonReaderCursor); + + /** + * Performs the ImportFromJson operation on a single Json string + * + * @return Returns a MapGeometry. + */ + public abstract MapGeometry execute(Geometry.Type type, + JsonReader jsonReader); + + /** + * Performs the ImportFromJson operation on a single Json string + * + * @return Returns a MapGeometry. + */ + public abstract MapGeometry execute(Geometry.Type type, String string); + + public static OperatorImportFromJson local() { + return (OperatorImportFromJson) OperatorFactoryLocal.getInstance().getOperator(Type.ImportFromJson); + } } diff --git a/src/main/java/com/esri/core/geometry/OperatorImportFromJsonCursor.java b/src/main/java/com/esri/core/geometry/OperatorImportFromJsonCursor.java index c51fd6ef..22801a36 100644 --- a/src/main/java/com/esri/core/geometry/OperatorImportFromJsonCursor.java +++ b/src/main/java/com/esri/core/geometry/OperatorImportFromJsonCursor.java @@ -28,538 +28,540 @@ import com.esri.core.geometry.VertexDescription.Semantics; public class OperatorImportFromJsonCursor extends MapGeometryCursor { - JsonReaderCursor m_inputJsonParsers; - - int m_type; - - long m_index; - - public OperatorImportFromJsonCursor(int type, JsonReaderCursor jsonParsers) { - m_index = -1; - if (jsonParsers == null) - throw new IllegalArgumentException(); - - m_type = type; - m_inputJsonParsers = jsonParsers; - } - - @Override - public long getGeometryID() { - return m_index; - } - - @Override - public SimpleStateEnum getSimpleState() { - return m_inputJsonParsers.getSimpleState(); - } - - @Override - public String getFeatureID() { return m_inputJsonParsers.getFeatureID(); } - - @Override - public boolean hasNext() { - return m_inputJsonParsers.hasNext(); - } - - @Override - public MapGeometry next() { - JsonReader jsonParser; - if ((jsonParser = m_inputJsonParsers.next()) != null) { - m_index = m_inputJsonParsers.getID(); - return importFromJsonParser(m_type, jsonParser); - } - return null; - } - - static MapGeometry importFromJsonParser(int gt, JsonReader parser) { - MapGeometry mp; - - try { - if (!JSONUtils.isObjectStart(parser)) - return null; - - boolean bFoundSpatial_reference = false; - boolean bFoundHasZ = false; - boolean bFoundHasM = false; - boolean bFoundPolygon = false; - boolean bFoundPolyline = false; - boolean bFoundMultiPoint = false; - boolean bFoundX = false; - boolean bFoundY = false; - boolean bFoundZ = false; - boolean bFoundM = false; - boolean bFoundXMin = false; - boolean bFoundYMin = false; - boolean bFoundXMax = false; - boolean bFoundYMax = false; - boolean bFoundZMin = false; - boolean bFoundZMax = false; - boolean bFoundMMin = false; - boolean bFoundMMax = false; - double x = NumberUtils.NaN(); - double y = NumberUtils.NaN(); - double z = NumberUtils.NaN(); - double m = NumberUtils.NaN(); - double xmin = NumberUtils.NaN(); - double ymin = NumberUtils.NaN(); - double xmax = NumberUtils.NaN(); - double ymax = NumberUtils.NaN(); - double zmin = NumberUtils.NaN(); - double zmax = NumberUtils.NaN(); - double mmin = NumberUtils.NaN(); - double mmax = NumberUtils.NaN(); - boolean bHasZ = false; - boolean bHasM = false; - AttributeStreamOfDbl as = (AttributeStreamOfDbl) AttributeStreamBase - .createDoubleStream(0); - AttributeStreamOfDbl bs = (AttributeStreamOfDbl) AttributeStreamBase - .createDoubleStream(0); - - Geometry geometry = null; - SpatialReference spatial_reference = null; - - while (parser.nextToken() != JsonReader.Token.END_OBJECT) { - String name = parser.currentString(); - parser.nextToken(); - - if (!bFoundSpatial_reference && name.equals("spatialReference")) { - bFoundSpatial_reference = true; - - if (parser.currentToken() == JsonReader.Token.START_OBJECT) { - spatial_reference = SpatialReference.fromJson(parser); - } else { - if (parser.currentToken() != JsonReader.Token.VALUE_NULL) - throw new GeometryException( - "failed to parse spatial reference: object or null is expected"); - } - } else if (!bFoundHasZ && name.equals("hasZ")) { - bFoundHasZ = true; - bHasZ = (parser.currentToken() == JsonReader.Token.VALUE_TRUE); - } else if (!bFoundHasM && name.equals("hasM")) { - bFoundHasM = true; - bHasM = (parser.currentToken() == JsonReader.Token.VALUE_TRUE); - } else if (!bFoundPolygon - && name.equals("rings") - && (gt == Geometry.GeometryType.Unknown || gt == Geometry.GeometryType.Polygon)) { - bFoundPolygon = true; - geometry = importFromJsonMultiPath(true, parser, as, bs); - continue; - } else if (!bFoundPolyline - && name.equals("paths") - && (gt == Geometry.GeometryType.Unknown || gt == Geometry.GeometryType.Polyline)) { - bFoundPolyline = true; - geometry = importFromJsonMultiPath(false, parser, as, bs); - continue; - } else if (!bFoundMultiPoint - && name.equals("points") - && (gt == Geometry.GeometryType.Unknown || gt == Geometry.GeometryType.MultiPoint)) { - bFoundMultiPoint = true; - geometry = importFromJsonMultiPoint(parser, as, bs); - continue; - } else if (!bFoundX - && name.equals("x") - && (gt == Geometry.GeometryType.Unknown || gt == Geometry.GeometryType.Point)) { - bFoundX = true; - x = readDouble(parser); - } else if (!bFoundY - && name.equals("y") - && (gt == Geometry.GeometryType.Unknown || gt == Geometry.GeometryType.Point)) { - bFoundY = true; - y = readDouble(parser); - } else if (!bFoundZ - && name.equals("z") - && (gt == Geometry.GeometryType.Unknown || gt == Geometry.GeometryType.Point)) { - bFoundZ = true; - z = readDouble(parser); - } else if (!bFoundM - && name.equals("m") - && (gt == Geometry.GeometryType.Unknown || gt == Geometry.GeometryType.Point)) { - bFoundM = true; - m = readDouble(parser); - } - if (!bFoundXMin - && name.equals("xmin") - && (gt == Geometry.GeometryType.Unknown || gt == Geometry.GeometryType.Envelope)) { - bFoundXMin = true; - xmin = readDouble(parser); - } else if (!bFoundYMin - && name.equals("ymin") - && (gt == Geometry.GeometryType.Unknown || gt == Geometry.GeometryType.Envelope)) { - bFoundYMin = true; - ymin = readDouble(parser); - } else if (!bFoundMMin - && name.equals("mmin") - && (gt == Geometry.GeometryType.Unknown || gt == Geometry.GeometryType.Envelope)) { - bFoundMMin = true; - mmin = readDouble(parser); - } else if (!bFoundZMin - && name.equals("zmin") - && (gt == Geometry.GeometryType.Unknown || gt == Geometry.GeometryType.Envelope)) { - bFoundZMin = true; - zmin = readDouble(parser); - } else if (!bFoundXMax - && name.equals("xmax") - && (gt == Geometry.GeometryType.Unknown || gt == Geometry.GeometryType.Envelope)) { - bFoundXMax = true; - xmax = readDouble(parser); - } else if (!bFoundYMax - && name.equals("ymax") - && (gt == Geometry.GeometryType.Unknown || gt == Geometry.GeometryType.Envelope)) { - bFoundYMax = true; - ymax = readDouble(parser); - } else if (!bFoundMMax - && name.equals("mmax") - && (gt == Geometry.GeometryType.Unknown || gt == Geometry.GeometryType.Envelope)) { - bFoundMMax = true; - mmax = readDouble(parser); - } else if (!bFoundZMax - && name.equals("zmax") - && (gt == Geometry.GeometryType.Unknown || gt == Geometry.GeometryType.Envelope)) { - bFoundZMax = true; - zmax = readDouble(parser); - } else { - windup(parser); - } - } - - if (bFoundPolygon || bFoundPolyline || bFoundMultiPoint) { - assert (geometry != null); - MultiVertexGeometryImpl mvImpl = (MultiVertexGeometryImpl) geometry - ._getImpl(); - - AttributeStreamBase zs = null; - AttributeStreamBase ms = null; - - if (bHasZ) { - geometry.addAttribute(Semantics.Z); - zs = as; - } - if (bHasM) { - geometry.addAttribute(Semantics.M); - ms = !bHasZ ? as : bs; - } - - if (bHasZ && zs != null) { - mvImpl.setAttributeStreamRef(Semantics.Z, zs); - } - - if (bHasM && ms != null) { - mvImpl.setAttributeStreamRef(Semantics.M, ms); - } - - mvImpl.notifyModified(DirtyFlags.DirtyAll); - } else if (bFoundX || bFoundY || bFoundY || bFoundZ) { - if (NumberUtils.isNaN(y)) - x = NumberUtils.NaN(); - - Point p = new Point(x, y); - - if (bFoundZ) - p.setZ(z); - - if (bFoundM) - p.setM(m); - - geometry = p; - } else if (bFoundXMin || bFoundYMin || bFoundXMax || bFoundYMax - || bFoundZMin || bFoundZMax || bFoundMMin || bFoundMMax) { - if (NumberUtils.isNaN(ymin) || NumberUtils.isNaN(xmax) - || NumberUtils.isNaN(ymax)) - xmin = NumberUtils.NaN(); - - Envelope e = new Envelope(xmin, ymin, xmax, ymax); - - if (bFoundZMin && bFoundZMax) - e.setInterval(Semantics.Z, 0, zmin, zmax); - - if (bFoundMMin && bFoundMMax) - e.setInterval(Semantics.M, 0, mmin, mmax); - - geometry = e; - } - - mp = new MapGeometry(geometry, spatial_reference); - - } catch (Exception e) { - return null; - } - - return mp; - } - - public static MapGeometry fromJsonToUnknown(JsonReader parser) - throws Exception { - - return importFromJsonParser(Geometry.GeometryType.Unknown, parser); - } - - public static MapGeometry fromJsonToEnvelope(JsonReader parser) - throws Exception { - return importFromJsonParser(Geometry.GeometryType.Envelope, parser); - } - - public static MapGeometry fromJsonToPoint(JsonReader parser) - throws Exception { - return importFromJsonParser(Geometry.GeometryType.Point, parser); - } - - public static MapGeometry fromJsonToPolygon(JsonReader parser) - throws Exception { - return importFromJsonParser(Geometry.GeometryType.Polygon, parser); - } - - public static MapGeometry fromJsonToPolyline(JsonReader parser) - throws Exception { - return importFromJsonParser(Geometry.GeometryType.Polyline, parser); - } - - public static MapGeometry fromJsonToMultiPoint(JsonReader parser) - throws Exception { - return importFromJsonParser(Geometry.GeometryType.MultiPoint, parser); - } - - private static void windup(JsonReader parser) { - parser.skipChildren(); - } - - private static double readDouble(JsonReader parser) { - if (parser.currentToken() == JsonReader.Token.VALUE_NULL - || parser.currentToken() == JsonReader.Token.VALUE_STRING - && parser.currentString().equals("NaN")) - return NumberUtils.NaN(); - else - return parser.currentDoubleValue(); - } - - private static Geometry importFromJsonMultiPoint(JsonReader parser, - AttributeStreamOfDbl as, AttributeStreamOfDbl bs) throws Exception { - if (parser.currentToken() != JsonReader.Token.START_ARRAY) - throw new GeometryException( - "failed to parse multipoint: array of vertices is expected"); - - int point_count = 0; - MultiPoint multipoint; - - multipoint = new MultiPoint(); - - AttributeStreamOfDbl position = (AttributeStreamOfDbl) (AttributeStreamBase - .createDoubleStream(2, 0)); - - // At start of rings - int sz; - double[] buf = new double[4]; - while (parser.nextToken() != JsonReader.Token.END_ARRAY) { - if (parser.currentToken() != JsonReader.Token.START_ARRAY) - throw new GeometryException( - "failed to parse multipoint: array is expected, multipoint vertices consist of arrays of cooridinates"); - - sz = 0; - while (parser.nextToken() != JsonReader.Token.END_ARRAY) { - buf[sz++] = readDouble(parser); - } - - if (sz < 2) - throw new GeometryException( - "failed to parse multipoint: each vertex array has to have at least 2 elements"); - - if (position.size() == 2 * point_count) { - int c = point_count * 3; - if (c % 2 != 0) - c++;// have to be even - position.resize(c); - } - - position.write(2 * point_count, buf[0]); - position.write(2 * point_count + 1, buf[1]); - - if (as.size() == point_count) { - int c = (point_count * 3) / 2; - if (c < 4) - c = 4; - else if (c < 16) - c = 16; - - as.resize(c); - } - - if (sz > 2) { - as.write(point_count, buf[2]); - } else - as.write(point_count, NumberUtils.NaN()); - - if (bs.size() == point_count) { - int c = (point_count * 3) / 2; - if (c < 4) - c = 4; - else if (c < 16) - c = 16; - - bs.resize(c); - } - - if (sz > 3) { - bs.write(point_count, buf[3]); - } else - bs.write(point_count, NumberUtils.NaN()); - - point_count++; - } - - if (point_count != 0) { - MultiPointImpl mp_impl = (MultiPointImpl) multipoint._getImpl(); - mp_impl.resize(point_count); - mp_impl.setAttributeStreamRef(Semantics.POSITION, position); - } - return multipoint; - } - - private static Geometry importFromJsonMultiPath(boolean b_polygon, - JsonReader parser, AttributeStreamOfDbl as, AttributeStreamOfDbl bs) - throws Exception { - if (parser.currentToken() != JsonReader.Token.START_ARRAY) - throw new GeometryException( - "failed to parse multipath: array of array of vertices is expected"); - - MultiPath multipath; - - if (b_polygon) - multipath = new Polygon(); - else - multipath = new Polyline(); - - AttributeStreamOfInt32 parts = (AttributeStreamOfInt32) AttributeStreamBase - .createIndexStream(0); - AttributeStreamOfDbl position = (AttributeStreamOfDbl) AttributeStreamBase - .createDoubleStream(2, 0); - AttributeStreamOfInt8 pathFlags = (AttributeStreamOfInt8) AttributeStreamBase - .createByteStream(0); - - // set up min max variables - double[] buf = new double[4]; - double[] start = new double[4]; - - int point_count = 0; - int path_count = 0; - byte pathFlag = b_polygon ? (byte) PathFlags.enumClosed : 0; - int requiredSize = b_polygon ? 3 : 2; - - // At start of rings - while (parser.nextToken() != JsonReader.Token.END_ARRAY) { - if (parser.currentToken() != JsonReader.Token.START_ARRAY) - throw new GeometryException( - "failed to parse multipath: ring/path array is expected"); - - int pathPointCount = 0; - boolean b_first = true; - int sz = 0; - int szstart = 0; - - parser.nextToken(); - while (parser.currentToken() != JsonReader.Token.END_ARRAY) { - if (parser.currentToken() != JsonReader.Token.START_ARRAY) - throw new GeometryException( - "failed to parse multipath: array is expected, rings/paths vertices consist of arrays of cooridinates"); - - sz = 0; - while (parser.nextToken() != JsonReader.Token.END_ARRAY) { - buf[sz++] = readDouble(parser); - } - - if (sz < 2) - throw new GeometryException( - "failed to parse multipath: each vertex array has to have at least 2 elements"); - - parser.nextToken(); - - do { - if (position.size() == point_count * 2) { - int c = point_count * 3; - - if (c % 2 != 0) - c++;// have to be even - if (c < 8) - c = 8; - else if (c < 32) - c = 32; - - position.resize(c); - } - - position.write(2 * point_count, buf[0]); - position.write(2 * point_count + 1, buf[1]); - - if (as.size() == point_count) { - int c = (point_count * 3) / 2;// have to be even - if (c < 4) - c = 4; - else if (c < 16) - c = 16; - as.resize(c); - } - - if (sz > 2) { - as.write(point_count, buf[2]); - } else - as.write(point_count, NumberUtils.NaN()); - - if (bs.size() == point_count) { - int c = (point_count * 3) / 2;// have to be even - if (c < 4) - c = 4; - else if (c < 16) - c = 16; - bs.resize(c); - } - - if (sz > 3) { - bs.write(point_count, buf[3]); - } else - bs.write(point_count, NumberUtils.NaN()); - - if (b_first) { - path_count++; - parts.add(point_count); - pathFlags.add(pathFlag); - b_first = false; - szstart = sz; - start[0] = buf[0]; - start[1] = buf[1]; - start[2] = buf[2]; - start[3] = buf[3]; - } - point_count++; - pathPointCount++; - } while (pathPointCount < requiredSize - && parser.currentToken() == JsonReader.Token.END_ARRAY); - } - - if (b_polygon && pathPointCount > requiredSize && sz == szstart - && start[0] == buf[0] && start[1] == buf[1] - && start[2] == buf[2] && start[3] == buf[3]) { - // remove the end point that is equal to the start point. - point_count--; - pathPointCount--; - } - - if (pathPointCount == 0) - continue;// skip empty paths - } - - if (point_count != 0) { - parts.resize(path_count); - pathFlags.resize(path_count); - - if (point_count > 0) { - parts.add(point_count); - pathFlags.add((byte) 0); - } - - MultiPathImpl mp_impl = (MultiPathImpl) multipath._getImpl(); - mp_impl.setAttributeStreamRef(Semantics.POSITION, position); - mp_impl.setPathFlagsStreamRef(pathFlags); - mp_impl.setPathStreamRef(parts); - } - return multipath; - } + JsonReaderCursor m_inputJsonParsers; + + int m_type; + + long m_index; + + public OperatorImportFromJsonCursor(int type, JsonReaderCursor jsonParsers) { + m_index = -1; + if (jsonParsers == null) + throw new IllegalArgumentException(); + + m_type = type; + m_inputJsonParsers = jsonParsers; + } + + @Override + public long getGeometryID() { + return m_index; + } + + @Override + public SimpleStateEnum getSimpleState() { + return m_inputJsonParsers.getSimpleState(); + } + + @Override + public String getFeatureID() { + return m_inputJsonParsers.getFeatureID(); + } + + @Override + public boolean hasNext() { + return m_inputJsonParsers.hasNext(); + } + + @Override + public MapGeometry next() { + JsonReader jsonParser; + if ((jsonParser = m_inputJsonParsers.next()) != null) { + m_index = m_inputJsonParsers.getID(); + return importFromJsonParser(m_type, jsonParser); + } + return null; + } + + static MapGeometry importFromJsonParser(int gt, JsonReader parser) { + MapGeometry mp; + + try { + if (!JSONUtils.isObjectStart(parser)) + return null; + + boolean bFoundSpatial_reference = false; + boolean bFoundHasZ = false; + boolean bFoundHasM = false; + boolean bFoundPolygon = false; + boolean bFoundPolyline = false; + boolean bFoundMultiPoint = false; + boolean bFoundX = false; + boolean bFoundY = false; + boolean bFoundZ = false; + boolean bFoundM = false; + boolean bFoundXMin = false; + boolean bFoundYMin = false; + boolean bFoundXMax = false; + boolean bFoundYMax = false; + boolean bFoundZMin = false; + boolean bFoundZMax = false; + boolean bFoundMMin = false; + boolean bFoundMMax = false; + double x = NumberUtils.NaN(); + double y = NumberUtils.NaN(); + double z = NumberUtils.NaN(); + double m = NumberUtils.NaN(); + double xmin = NumberUtils.NaN(); + double ymin = NumberUtils.NaN(); + double xmax = NumberUtils.NaN(); + double ymax = NumberUtils.NaN(); + double zmin = NumberUtils.NaN(); + double zmax = NumberUtils.NaN(); + double mmin = NumberUtils.NaN(); + double mmax = NumberUtils.NaN(); + boolean bHasZ = false; + boolean bHasM = false; + AttributeStreamOfDbl as = (AttributeStreamOfDbl) AttributeStreamBase + .createDoubleStream(0); + AttributeStreamOfDbl bs = (AttributeStreamOfDbl) AttributeStreamBase + .createDoubleStream(0); + + Geometry geometry = null; + SpatialReference spatial_reference = null; + + while (parser.nextToken() != JsonReader.Token.END_OBJECT) { + String name = parser.currentString(); + parser.nextToken(); + + if (!bFoundSpatial_reference && name.equals("spatialReference")) { + bFoundSpatial_reference = true; + + if (parser.currentToken() == JsonReader.Token.START_OBJECT) { + spatial_reference = SpatialReference.fromJson(parser); + } else { + if (parser.currentToken() != JsonReader.Token.VALUE_NULL) + throw new GeometryException( + "failed to parse spatial reference: object or null is expected"); + } + } else if (!bFoundHasZ && name.equals("hasZ")) { + bFoundHasZ = true; + bHasZ = (parser.currentToken() == JsonReader.Token.VALUE_TRUE); + } else if (!bFoundHasM && name.equals("hasM")) { + bFoundHasM = true; + bHasM = (parser.currentToken() == JsonReader.Token.VALUE_TRUE); + } else if (!bFoundPolygon + && name.equals("rings") + && (gt == Geometry.GeometryType.Unknown || gt == Geometry.GeometryType.Polygon)) { + bFoundPolygon = true; + geometry = importFromJsonMultiPath(true, parser, as, bs); + continue; + } else if (!bFoundPolyline + && name.equals("paths") + && (gt == Geometry.GeometryType.Unknown || gt == Geometry.GeometryType.Polyline)) { + bFoundPolyline = true; + geometry = importFromJsonMultiPath(false, parser, as, bs); + continue; + } else if (!bFoundMultiPoint + && name.equals("points") + && (gt == Geometry.GeometryType.Unknown || gt == Geometry.GeometryType.MultiPoint)) { + bFoundMultiPoint = true; + geometry = importFromJsonMultiPoint(parser, as, bs); + continue; + } else if (!bFoundX + && name.equals("x") + && (gt == Geometry.GeometryType.Unknown || gt == Geometry.GeometryType.Point)) { + bFoundX = true; + x = readDouble(parser); + } else if (!bFoundY + && name.equals("y") + && (gt == Geometry.GeometryType.Unknown || gt == Geometry.GeometryType.Point)) { + bFoundY = true; + y = readDouble(parser); + } else if (!bFoundZ + && name.equals("z") + && (gt == Geometry.GeometryType.Unknown || gt == Geometry.GeometryType.Point)) { + bFoundZ = true; + z = readDouble(parser); + } else if (!bFoundM + && name.equals("m") + && (gt == Geometry.GeometryType.Unknown || gt == Geometry.GeometryType.Point)) { + bFoundM = true; + m = readDouble(parser); + } + if (!bFoundXMin + && name.equals("xmin") + && (gt == Geometry.GeometryType.Unknown || gt == Geometry.GeometryType.Envelope)) { + bFoundXMin = true; + xmin = readDouble(parser); + } else if (!bFoundYMin + && name.equals("ymin") + && (gt == Geometry.GeometryType.Unknown || gt == Geometry.GeometryType.Envelope)) { + bFoundYMin = true; + ymin = readDouble(parser); + } else if (!bFoundMMin + && name.equals("mmin") + && (gt == Geometry.GeometryType.Unknown || gt == Geometry.GeometryType.Envelope)) { + bFoundMMin = true; + mmin = readDouble(parser); + } else if (!bFoundZMin + && name.equals("zmin") + && (gt == Geometry.GeometryType.Unknown || gt == Geometry.GeometryType.Envelope)) { + bFoundZMin = true; + zmin = readDouble(parser); + } else if (!bFoundXMax + && name.equals("xmax") + && (gt == Geometry.GeometryType.Unknown || gt == Geometry.GeometryType.Envelope)) { + bFoundXMax = true; + xmax = readDouble(parser); + } else if (!bFoundYMax + && name.equals("ymax") + && (gt == Geometry.GeometryType.Unknown || gt == Geometry.GeometryType.Envelope)) { + bFoundYMax = true; + ymax = readDouble(parser); + } else if (!bFoundMMax + && name.equals("mmax") + && (gt == Geometry.GeometryType.Unknown || gt == Geometry.GeometryType.Envelope)) { + bFoundMMax = true; + mmax = readDouble(parser); + } else if (!bFoundZMax + && name.equals("zmax") + && (gt == Geometry.GeometryType.Unknown || gt == Geometry.GeometryType.Envelope)) { + bFoundZMax = true; + zmax = readDouble(parser); + } else { + windup(parser); + } + } + + if (bFoundPolygon || bFoundPolyline || bFoundMultiPoint) { + assert (geometry != null); + MultiVertexGeometryImpl mvImpl = (MultiVertexGeometryImpl) geometry + ._getImpl(); + + AttributeStreamBase zs = null; + AttributeStreamBase ms = null; + + if (bHasZ) { + geometry.addAttribute(Semantics.Z); + zs = as; + } + if (bHasM) { + geometry.addAttribute(Semantics.M); + ms = !bHasZ ? as : bs; + } + + if (bHasZ && zs != null) { + mvImpl.setAttributeStreamRef(Semantics.Z, zs); + } + + if (bHasM && ms != null) { + mvImpl.setAttributeStreamRef(Semantics.M, ms); + } + + mvImpl.notifyModified(DirtyFlags.DirtyAll); + } else if (bFoundX || bFoundY || bFoundY || bFoundZ) { + if (NumberUtils.isNaN(y)) + x = NumberUtils.NaN(); + + Point p = new Point(x, y); + + if (bFoundZ) + p.setZ(z); + + if (bFoundM) + p.setM(m); + + geometry = p; + } else if (bFoundXMin || bFoundYMin || bFoundXMax || bFoundYMax + || bFoundZMin || bFoundZMax || bFoundMMin || bFoundMMax) { + if (NumberUtils.isNaN(ymin) || NumberUtils.isNaN(xmax) + || NumberUtils.isNaN(ymax)) + xmin = NumberUtils.NaN(); + + Envelope e = new Envelope(xmin, ymin, xmax, ymax); + + if (bFoundZMin && bFoundZMax) + e.setInterval(Semantics.Z, 0, zmin, zmax); + + if (bFoundMMin && bFoundMMax) + e.setInterval(Semantics.M, 0, mmin, mmax); + + geometry = e; + } + + mp = new MapGeometry(geometry, spatial_reference); + + } catch (Exception e) { + return null; + } + + return mp; + } + + public static MapGeometry fromJsonToUnknown(JsonReader parser) + throws Exception { + + return importFromJsonParser(Geometry.GeometryType.Unknown, parser); + } + + public static MapGeometry fromJsonToEnvelope(JsonReader parser) + throws Exception { + return importFromJsonParser(Geometry.GeometryType.Envelope, parser); + } + + public static MapGeometry fromJsonToPoint(JsonReader parser) + throws Exception { + return importFromJsonParser(Geometry.GeometryType.Point, parser); + } + + public static MapGeometry fromJsonToPolygon(JsonReader parser) + throws Exception { + return importFromJsonParser(Geometry.GeometryType.Polygon, parser); + } + + public static MapGeometry fromJsonToPolyline(JsonReader parser) + throws Exception { + return importFromJsonParser(Geometry.GeometryType.Polyline, parser); + } + + public static MapGeometry fromJsonToMultiPoint(JsonReader parser) + throws Exception { + return importFromJsonParser(Geometry.GeometryType.MultiPoint, parser); + } + + private static void windup(JsonReader parser) { + parser.skipChildren(); + } + + private static double readDouble(JsonReader parser) { + if (parser.currentToken() == JsonReader.Token.VALUE_NULL + || parser.currentToken() == JsonReader.Token.VALUE_STRING + && parser.currentString().equals("NaN")) + return NumberUtils.NaN(); + else + return parser.currentDoubleValue(); + } + + private static Geometry importFromJsonMultiPoint(JsonReader parser, + AttributeStreamOfDbl as, AttributeStreamOfDbl bs) throws Exception { + if (parser.currentToken() != JsonReader.Token.START_ARRAY) + throw new GeometryException( + "failed to parse multipoint: array of vertices is expected"); + + int point_count = 0; + MultiPoint multipoint; + + multipoint = new MultiPoint(); + + AttributeStreamOfDbl position = (AttributeStreamOfDbl) (AttributeStreamBase + .createDoubleStream(2, 0)); + + // At start of rings + int sz; + double[] buf = new double[4]; + while (parser.nextToken() != JsonReader.Token.END_ARRAY) { + if (parser.currentToken() != JsonReader.Token.START_ARRAY) + throw new GeometryException( + "failed to parse multipoint: array is expected, multipoint vertices consist of arrays of cooridinates"); + + sz = 0; + while (parser.nextToken() != JsonReader.Token.END_ARRAY) { + buf[sz++] = readDouble(parser); + } + + if (sz < 2) + throw new GeometryException( + "failed to parse multipoint: each vertex array has to have at least 2 elements"); + + if (position.size() == 2 * point_count) { + int c = point_count * 3; + if (c % 2 != 0) + c++;// have to be even + position.resize(c); + } + + position.write(2 * point_count, buf[0]); + position.write(2 * point_count + 1, buf[1]); + + if (as.size() == point_count) { + int c = (point_count * 3) / 2; + if (c < 4) + c = 4; + else if (c < 16) + c = 16; + + as.resize(c); + } + + if (sz > 2) { + as.write(point_count, buf[2]); + } else + as.write(point_count, NumberUtils.NaN()); + + if (bs.size() == point_count) { + int c = (point_count * 3) / 2; + if (c < 4) + c = 4; + else if (c < 16) + c = 16; + + bs.resize(c); + } + + if (sz > 3) { + bs.write(point_count, buf[3]); + } else + bs.write(point_count, NumberUtils.NaN()); + + point_count++; + } + + if (point_count != 0) { + MultiPointImpl mp_impl = (MultiPointImpl) multipoint._getImpl(); + mp_impl.resize(point_count); + mp_impl.setAttributeStreamRef(Semantics.POSITION, position); + } + return multipoint; + } + + private static Geometry importFromJsonMultiPath(boolean b_polygon, + JsonReader parser, AttributeStreamOfDbl as, AttributeStreamOfDbl bs) + throws Exception { + if (parser.currentToken() != JsonReader.Token.START_ARRAY) + throw new GeometryException( + "failed to parse multipath: array of array of vertices is expected"); + + MultiPath multipath; + + if (b_polygon) + multipath = new Polygon(); + else + multipath = new Polyline(); + + AttributeStreamOfInt32 parts = (AttributeStreamOfInt32) AttributeStreamBase + .createIndexStream(0); + AttributeStreamOfDbl position = (AttributeStreamOfDbl) AttributeStreamBase + .createDoubleStream(2, 0); + AttributeStreamOfInt8 pathFlags = (AttributeStreamOfInt8) AttributeStreamBase + .createByteStream(0); + + // set up min max variables + double[] buf = new double[4]; + double[] start = new double[4]; + + int point_count = 0; + int path_count = 0; + byte pathFlag = b_polygon ? (byte) PathFlags.enumClosed : 0; + int requiredSize = b_polygon ? 3 : 2; + + // At start of rings + while (parser.nextToken() != JsonReader.Token.END_ARRAY) { + if (parser.currentToken() != JsonReader.Token.START_ARRAY) + throw new GeometryException( + "failed to parse multipath: ring/path array is expected"); + + int pathPointCount = 0; + boolean b_first = true; + int sz = 0; + int szstart = 0; + + parser.nextToken(); + while (parser.currentToken() != JsonReader.Token.END_ARRAY) { + if (parser.currentToken() != JsonReader.Token.START_ARRAY) + throw new GeometryException( + "failed to parse multipath: array is expected, rings/paths vertices consist of arrays of cooridinates"); + + sz = 0; + while (parser.nextToken() != JsonReader.Token.END_ARRAY) { + buf[sz++] = readDouble(parser); + } + + if (sz < 2) + throw new GeometryException( + "failed to parse multipath: each vertex array has to have at least 2 elements"); + + parser.nextToken(); + + do { + if (position.size() == point_count * 2) { + int c = point_count * 3; + + if (c % 2 != 0) + c++;// have to be even + if (c < 8) + c = 8; + else if (c < 32) + c = 32; + + position.resize(c); + } + + position.write(2 * point_count, buf[0]); + position.write(2 * point_count + 1, buf[1]); + + if (as.size() == point_count) { + int c = (point_count * 3) / 2;// have to be even + if (c < 4) + c = 4; + else if (c < 16) + c = 16; + as.resize(c); + } + + if (sz > 2) { + as.write(point_count, buf[2]); + } else + as.write(point_count, NumberUtils.NaN()); + + if (bs.size() == point_count) { + int c = (point_count * 3) / 2;// have to be even + if (c < 4) + c = 4; + else if (c < 16) + c = 16; + bs.resize(c); + } + + if (sz > 3) { + bs.write(point_count, buf[3]); + } else + bs.write(point_count, NumberUtils.NaN()); + + if (b_first) { + path_count++; + parts.add(point_count); + pathFlags.add(pathFlag); + b_first = false; + szstart = sz; + start[0] = buf[0]; + start[1] = buf[1]; + start[2] = buf[2]; + start[3] = buf[3]; + } + point_count++; + pathPointCount++; + } while (pathPointCount < requiredSize + && parser.currentToken() == JsonReader.Token.END_ARRAY); + } + + if (b_polygon && pathPointCount > requiredSize && sz == szstart + && start[0] == buf[0] && start[1] == buf[1] + && start[2] == buf[2] && start[3] == buf[3]) { + // remove the end point that is equal to the start point. + point_count--; + pathPointCount--; + } + + if (pathPointCount == 0) + continue;// skip empty paths + } + + if (point_count != 0) { + parts.resize(path_count); + pathFlags.resize(path_count); + + if (point_count > 0) { + parts.add(point_count); + pathFlags.add((byte) 0); + } + + MultiPathImpl mp_impl = (MultiPathImpl) multipath._getImpl(); + mp_impl.setAttributeStreamRef(Semantics.POSITION, position); + mp_impl.setPathFlagsStreamRef(pathFlags); + mp_impl.setPathStreamRef(parts); + } + return multipath; + } } diff --git a/src/main/java/com/esri/core/geometry/OperatorImportFromJsonLocal.java b/src/main/java/com/esri/core/geometry/OperatorImportFromJsonLocal.java index bb7cb107..5c515e0f 100644 --- a/src/main/java/com/esri/core/geometry/OperatorImportFromJsonLocal.java +++ b/src/main/java/com/esri/core/geometry/OperatorImportFromJsonLocal.java @@ -25,19 +25,19 @@ class OperatorImportFromJsonLocal extends OperatorImportFromJson { - @Override - public MapGeometryCursor execute(Geometry.Type type, - JsonReaderCursor jsonParserCursor) { - return new OperatorImportFromJsonCursor(type.value(), jsonParserCursor); - } - - @Override - public MapGeometry execute(Geometry.Type type, JsonReader jsonParser) { - return OperatorImportFromJsonCursor.importFromJsonParser(type.value(), jsonParser); - } - - @Override - public MapGeometry execute(Geometry.Type type, String string) { - return execute(type, JsonParserReader.createFromString(string)); - } + @Override + public MapGeometryCursor execute(Geometry.Type type, + JsonReaderCursor jsonParserCursor) { + return new OperatorImportFromJsonCursor(type.value(), jsonParserCursor); + } + + @Override + public MapGeometry execute(Geometry.Type type, JsonReader jsonParser) { + return OperatorImportFromJsonCursor.importFromJsonParser(type.value(), jsonParser); + } + + @Override + public MapGeometry execute(Geometry.Type type, String string) { + return execute(type, JsonParserReader.createFromString(string)); + } } diff --git a/src/main/java/com/esri/core/geometry/OperatorImportFromWkb.java b/src/main/java/com/esri/core/geometry/OperatorImportFromWkb.java index 40d4ab99..6d20a2d5 100644 --- a/src/main/java/com/esri/core/geometry/OperatorImportFromWkb.java +++ b/src/main/java/com/esri/core/geometry/OperatorImportFromWkb.java @@ -33,48 +33,48 @@ */ public abstract class OperatorImportFromWkb extends Operator { - @Override - public Type getType() { - return Type.ImportFromWkb; - } - - - /** - * @param importFlags Use the {@link ShapeImportFlags} interface. The default is 0, which means geometry comes - * from a trusted source and is topologically simple. - * @param wkbBuffers The cursor over wkb buffers that hold the Geometries in wkb format. - * @param progressTracker - * @return Returns a GeometryCursor. - */ - public abstract GeometryCursor execute(int importFlags, ByteBufferCursor wkbBuffers, ProgressTracker progressTracker); - - /** - * Performs the ImportFromWKB operation. - * - * @param importFlags Use the {@link WkbImportFlags} interface. - * @param type Use the {@link Geometry.Type} enum. - * @param wkbBuffer The buffer holding the Geometry in wkb format. - * @return Returns the imported Geometry. - */ - public abstract Geometry execute(int importFlags, - Geometry.Type type, - ByteBuffer wkbBuffer, - ProgressTracker progress_tracker); - - - /** - * Performs the ImportFromWkb operation. - * - * @param importFlags Use the {@link WkbImportFlags} interface. - * @param wkbBuffer The buffer holding the Geometry in wkb format. - * @return Returns the imported OGCStructure. - */ - public abstract OGCStructure executeOGC(int importFlags, - ByteBuffer wkbBuffer, - ProgressTracker progress_tracker); - - public static OperatorImportFromWkb local() { - return (OperatorImportFromWkb) OperatorFactoryLocal.getInstance().getOperator(Type.ImportFromWkb); - } + @Override + public Type getType() { + return Type.ImportFromWkb; + } + + + /** + * @param importFlags Use the {@link ShapeImportFlags} interface. The default is 0, which means geometry comes + * from a trusted source and is topologically simple. + * @param wkbBuffers The cursor over wkb buffers that hold the Geometries in wkb format. + * @param progressTracker + * @return Returns a GeometryCursor. + */ + public abstract GeometryCursor execute(int importFlags, ByteBufferCursor wkbBuffers, ProgressTracker progressTracker); + + /** + * Performs the ImportFromWKB operation. + * + * @param importFlags Use the {@link WkbImportFlags} interface. + * @param type Use the {@link Geometry.Type} enum. + * @param wkbBuffer The buffer holding the Geometry in wkb format. + * @return Returns the imported Geometry. + */ + public abstract Geometry execute(int importFlags, + Geometry.Type type, + ByteBuffer wkbBuffer, + ProgressTracker progress_tracker); + + + /** + * Performs the ImportFromWkb operation. + * + * @param importFlags Use the {@link WkbImportFlags} interface. + * @param wkbBuffer The buffer holding the Geometry in wkb format. + * @return Returns the imported OGCStructure. + */ + public abstract OGCStructure executeOGC(int importFlags, + ByteBuffer wkbBuffer, + ProgressTracker progress_tracker); + + public static OperatorImportFromWkb local() { + return (OperatorImportFromWkb) OperatorFactoryLocal.getInstance().getOperator(Type.ImportFromWkb); + } } diff --git a/src/main/java/com/esri/core/geometry/OperatorImportFromWkbCursor.java b/src/main/java/com/esri/core/geometry/OperatorImportFromWkbCursor.java index 65b0c726..48debc90 100644 --- a/src/main/java/com/esri/core/geometry/OperatorImportFromWkbCursor.java +++ b/src/main/java/com/esri/core/geometry/OperatorImportFromWkbCursor.java @@ -1,40 +1,46 @@ package com.esri.core.geometry; public class OperatorImportFromWkbCursor extends GeometryCursor { - private ByteBufferCursor m_inputWkbBuffers; - private int m_importFlags; - - public OperatorImportFromWkbCursor(int importFlags, ByteBufferCursor wkbBuffers) { - if (wkbBuffers == null) - throw new GeometryException("invalid argument"); - - m_importFlags = importFlags; - m_inputWkbBuffers = wkbBuffers; - } - - @Override - public boolean hasNext() { return m_inputWkbBuffers != null && m_inputWkbBuffers.hasNext(); } - - @Override - public Geometry next() { - if (hasNext()) { - return OperatorImportFromWkbLocal.local().execute( - m_importFlags, - Geometry.Type.Unknown, - m_inputWkbBuffers.next(), - null); - } - return null; - } - - @Override - public long getGeometryID() { - return m_inputWkbBuffers.getByteBufferID(); - } - - @Override - public SimpleStateEnum getSimpleState() { return m_inputWkbBuffers.getSimpleState(); } - - @Override - public String getFeatureID() { return m_inputWkbBuffers.getFeatureID(); } + private ByteBufferCursor m_inputWkbBuffers; + private int m_importFlags; + + public OperatorImportFromWkbCursor(int importFlags, ByteBufferCursor wkbBuffers) { + if (wkbBuffers == null) + throw new GeometryException("invalid argument"); + + m_importFlags = importFlags; + m_inputWkbBuffers = wkbBuffers; + } + + @Override + public boolean hasNext() { + return m_inputWkbBuffers != null && m_inputWkbBuffers.hasNext(); + } + + @Override + public Geometry next() { + if (hasNext()) { + return OperatorImportFromWkbLocal.local().execute( + m_importFlags, + Geometry.Type.Unknown, + m_inputWkbBuffers.next(), + null); + } + return null; + } + + @Override + public long getGeometryID() { + return m_inputWkbBuffers.getByteBufferID(); + } + + @Override + public SimpleStateEnum getSimpleState() { + return m_inputWkbBuffers.getSimpleState(); + } + + @Override + public String getFeatureID() { + return m_inputWkbBuffers.getFeatureID(); + } } diff --git a/src/main/java/com/esri/core/geometry/OperatorImportFromWkbLocal.java b/src/main/java/com/esri/core/geometry/OperatorImportFromWkbLocal.java index 226ffe1b..181c73fe 100644 --- a/src/main/java/com/esri/core/geometry/OperatorImportFromWkbLocal.java +++ b/src/main/java/com/esri/core/geometry/OperatorImportFromWkbLocal.java @@ -33,1031 +33,1031 @@ */ class OperatorImportFromWkbLocal extends OperatorImportFromWkb { - static final class WkbHelper { - WkbHelper(ByteBuffer buffer) { - wkbBuffer = buffer; - adjustment = 0; - } + static final class WkbHelper { + WkbHelper(ByteBuffer buffer) { + wkbBuffer = buffer; + adjustment = 0; + } - int getInt(int offset) { - return wkbBuffer.getInt(adjustment + offset); - } + int getInt(int offset) { + return wkbBuffer.getInt(adjustment + offset); + } - double getDouble(int offset) { - return wkbBuffer.getDouble(adjustment + offset); - } - - ByteBuffer wkbBuffer; - int adjustment; - } - - - @Override - public GeometryCursor execute(int importFlags, ByteBufferCursor wkbBuffers, ProgressTracker progressTracker) { - return new OperatorImportFromWkbCursor(importFlags, wkbBuffers); - } - - @Override - public Geometry execute(int importFlags, Geometry.Type type, ByteBuffer wkbBuffer, ProgressTracker progress_tracker) { - - ByteOrder initialOrder = wkbBuffer.order(); - - // read byte ordering - int byteOrder = wkbBuffer.get(0); - - if (byteOrder == WkbByteOrder.wkbNDR) - wkbBuffer.order(ByteOrder.LITTLE_ENDIAN); - else - wkbBuffer.order(ByteOrder.BIG_ENDIAN); - - WkbHelper wkbHelper = new WkbHelper(wkbBuffer); - - try { - return importFromWkb(importFlags, type, wkbHelper); - } finally { - wkbBuffer.order(initialOrder); - } - } - - @Override - public OGCStructure executeOGC(int importFlags, ByteBuffer wkbBuffer, ProgressTracker progress_tracker) { - - ByteOrder initialOrder = wkbBuffer.order(); - - // read byte ordering - int byteOrder = wkbBuffer.get(0); - - if (byteOrder == WkbByteOrder.wkbNDR) - wkbBuffer.order(ByteOrder.LITTLE_ENDIAN); - else - wkbBuffer.order(ByteOrder.BIG_ENDIAN); - - ArrayList stack = new ArrayList(0); - AttributeStreamOfInt32 numGeometries = new AttributeStreamOfInt32(0); - AttributeStreamOfInt32 indices = new AttributeStreamOfInt32(0); - WkbHelper wkbHelper = new WkbHelper(wkbBuffer); - - OGCStructure root = new OGCStructure(); - root.m_structures = new ArrayList(0); - stack.add(root); // add dummy root - numGeometries.add(1); - indices.add(0); - - boolean bCheckConsistentAttributes = false; - boolean bHasZs = false; - boolean bHasMs = false; - - try { - - while (!stack.isEmpty()) { - - if (indices.getLast() == numGeometries.getLast()) { - stack.remove(stack.size() - 1); - indices.removeLast(); - numGeometries.removeLast(); - continue; - } - - OGCStructure last = stack.get(stack.size() - 1); - indices.write(indices.size() - 1, indices.getLast() + 1); - Geometry geometry; - - int wkbType = wkbHelper.getInt(1); - int ogcType; - - // strip away attributes from type identifier - - if (wkbType > 3000) { - ogcType = wkbType - 3000; - - if (bCheckConsistentAttributes) { - if (!bHasZs || !bHasMs) - throw new IllegalArgumentException(); - } else { - bHasZs = true; - bHasMs = true; - bCheckConsistentAttributes = true; - } - } else if (wkbType > 2000) { - ogcType = wkbType - 2000; - - if (bCheckConsistentAttributes) { - if (bHasZs || !bHasMs) - throw new IllegalArgumentException(); - } else { - bHasZs = false; - bHasMs = true; - bCheckConsistentAttributes = true; - } - } else if (wkbType > 1000) { - ogcType = wkbType - 1000; - - if (bCheckConsistentAttributes) { - if (!bHasZs || bHasMs) - throw new IllegalArgumentException(); - } else { - bHasZs = true; - bHasMs = false; - bCheckConsistentAttributes = true; - } - } else { - ogcType = wkbType; - - if (bCheckConsistentAttributes) { - if (bHasZs || bHasMs) - throw new IllegalArgumentException(); - } else { - bHasZs = false; - bHasMs = false; - bCheckConsistentAttributes = true; - } - } - if (ogcType == 7) { - int count = wkbHelper.getInt(5); - wkbHelper.adjustment += 9; - - OGCStructure next = new OGCStructure(); - next.m_type = ogcType; - next.m_structures = new ArrayList(0); - last.m_structures.add(next); - stack.add(next); - indices.add(0); - numGeometries.add(count); - } else { - geometry = importFromWkb(importFlags, - Geometry.Type.Unknown, wkbHelper); - OGCStructure leaf = new OGCStructure(); - leaf.m_type = ogcType; - leaf.m_geometry = geometry; - last.m_structures.add(leaf); - } - } - } finally { - wkbBuffer.order(initialOrder); - } - - return root; - } - - private static Geometry importFromWkb(int importFlags, Geometry.Type type, - WkbHelper wkbHelper) { - - // read type - int wkbType = wkbHelper.getInt(1); - - switch (wkbType) { - case WkbGeometryType.wkbPolygon: - if (type.value() != Geometry.GeometryType.Polygon - && type.value() != Geometry.GeometryType.Unknown) - throw new GeometryException("invalid shape type"); - return importFromWkbPolygon(false, importFlags, false, false, - wkbHelper); - - case WkbGeometryType.wkbPolygonM: - if (type.value() != Geometry.GeometryType.Polygon - && type.value() != Geometry.GeometryType.Unknown) - throw new GeometryException("invalid shape type"); - return importFromWkbPolygon(false, importFlags, false, true, - wkbHelper); - - case WkbGeometryType.wkbPolygonZ: - if (type.value() != Geometry.GeometryType.Polygon - && type.value() != Geometry.GeometryType.Unknown) - throw new GeometryException("invalid shape type"); - return importFromWkbPolygon(false, importFlags, true, false, - wkbHelper); - - case WkbGeometryType.wkbPolygonZM: - if (type.value() != Geometry.GeometryType.Polygon - && type.value() != Geometry.GeometryType.Unknown) - throw new GeometryException("invalid shape type"); - return importFromWkbPolygon(false, importFlags, true, true, - wkbHelper); - - case WkbGeometryType.wkbMultiPolygon: - if (type.value() != Geometry.GeometryType.Polygon - && type.value() != Geometry.GeometryType.Unknown) - throw new GeometryException("invalid shape type"); - return importFromWkbPolygon(true, importFlags, false, false, - wkbHelper); - - case WkbGeometryType.wkbMultiPolygonM: - if (type.value() != Geometry.GeometryType.Polygon - && type.value() != Geometry.GeometryType.Unknown) - throw new GeometryException("invalid shape type"); - return importFromWkbPolygon(true, importFlags, false, true, - wkbHelper); - - case WkbGeometryType.wkbMultiPolygonZ: - if (type.value() != Geometry.GeometryType.Polygon - && type.value() != Geometry.GeometryType.Unknown) - throw new GeometryException("invalid shape type"); - return importFromWkbPolygon(true, importFlags, true, false, - wkbHelper); - - case WkbGeometryType.wkbMultiPolygonZM: - if (type.value() != Geometry.GeometryType.Polygon - && type.value() != Geometry.GeometryType.Unknown) - throw new GeometryException("invalid shape type"); - return importFromWkbPolygon(true, importFlags, true, true, - wkbHelper); - - case WkbGeometryType.wkbLineString: - if (type.value() != Geometry.GeometryType.Polyline - && type.value() != Geometry.GeometryType.Unknown) - throw new GeometryException("invalid shape type"); - return importFromWkbPolyline(false, importFlags, false, false, - wkbHelper); - - case WkbGeometryType.wkbLineStringM: - if (type.value() != Geometry.GeometryType.Polyline - && type.value() != Geometry.GeometryType.Unknown) - throw new GeometryException("invalid shape type"); - return importFromWkbPolyline(false, importFlags, false, true, - wkbHelper); - - case WkbGeometryType.wkbLineStringZ: - if (type.value() != Geometry.GeometryType.Polyline - && type.value() != Geometry.GeometryType.Unknown) - throw new GeometryException("invalid shape type"); - return importFromWkbPolyline(false, importFlags, true, false, - wkbHelper); - - case WkbGeometryType.wkbLineStringZM: - if (type.value() != Geometry.GeometryType.Polyline - && type.value() != Geometry.GeometryType.Unknown) - throw new GeometryException("invalid shape type"); - return importFromWkbPolyline(false, importFlags, true, true, - wkbHelper); - - case WkbGeometryType.wkbMultiLineString: - if (type.value() != Geometry.GeometryType.Polyline - && type.value() != Geometry.GeometryType.Unknown) - throw new GeometryException("invalid shape type"); - return importFromWkbPolyline(true, importFlags, false, false, - wkbHelper); - - case WkbGeometryType.wkbMultiLineStringM: - if (type.value() != Geometry.GeometryType.Polyline - && type.value() != Geometry.GeometryType.Unknown) - throw new GeometryException("invalid shape type"); - return importFromWkbPolyline(true, importFlags, false, true, - wkbHelper); - - case WkbGeometryType.wkbMultiLineStringZ: - if (type.value() != Geometry.GeometryType.Polyline - && type.value() != Geometry.GeometryType.Unknown) - throw new GeometryException("invalid shape type"); - return importFromWkbPolyline(true, importFlags, true, false, - wkbHelper); - - case WkbGeometryType.wkbMultiLineStringZM: - if (type.value() != Geometry.GeometryType.Polyline - && type.value() != Geometry.GeometryType.Unknown) - throw new GeometryException("invalid shape type"); - return importFromWkbPolyline(true, importFlags, true, true, - wkbHelper); - - case WkbGeometryType.wkbMultiPoint: - if (type.value() != Geometry.GeometryType.MultiPoint - && type.value() != Geometry.GeometryType.Unknown) - throw new GeometryException("invalid shape type"); - return importFromWkbMultiPoint(importFlags, false, false, wkbHelper); - - case WkbGeometryType.wkbMultiPointM: - if (type.value() != Geometry.GeometryType.MultiPoint - && type.value() != Geometry.GeometryType.Unknown) - throw new GeometryException("invalid shape type"); - return importFromWkbMultiPoint(importFlags, false, true, wkbHelper); - - case WkbGeometryType.wkbMultiPointZ: - if (type.value() != Geometry.GeometryType.MultiPoint - && type.value() != Geometry.GeometryType.Unknown) - throw new GeometryException("invalid shape type"); - return importFromWkbMultiPoint(importFlags, true, false, wkbHelper); - - case WkbGeometryType.wkbMultiPointZM: - if (type.value() != Geometry.GeometryType.MultiPoint - && type.value() != Geometry.GeometryType.Unknown) - throw new GeometryException("invalid shape type"); - return importFromWkbMultiPoint(importFlags, true, true, wkbHelper); - - case WkbGeometryType.wkbPoint: - if (type.value() != Geometry.GeometryType.Point - && type.value() != Geometry.GeometryType.Unknown) - throw new GeometryException("invalid shape type"); - return importFromWkbPoint(importFlags, false, false, wkbHelper); - - case WkbGeometryType.wkbPointM: - if (type.value() != Geometry.GeometryType.Point - && type.value() != Geometry.GeometryType.Unknown) - throw new GeometryException("invalid shape type"); - return importFromWkbPoint(importFlags, false, true, wkbHelper); - - case WkbGeometryType.wkbPointZ: - if (type.value() != Geometry.GeometryType.Point - && type.value() != Geometry.GeometryType.Unknown) - throw new GeometryException("invalid shape type"); - return importFromWkbPoint(importFlags, true, false, wkbHelper); - - case WkbGeometryType.wkbPointZM: - if (type.value() != Geometry.GeometryType.Point - && type.value() != Geometry.GeometryType.Unknown) - throw new GeometryException("invalid shape type"); - return importFromWkbPoint(importFlags, true, true, wkbHelper); - - default: - throw new GeometryException("invalid shape type"); - } - } - - private static Geometry importFromWkbPolygon(boolean bMultiPolygon, - int importFlags, boolean bZs, boolean bMs, WkbHelper wkbHelper) { - int offset; - int polygonCount; - - if (bMultiPolygon) { - polygonCount = wkbHelper.getInt(5); - offset = 9; - } else { - polygonCount = 1; - offset = 0; - } - - // Find total point count and part count - int point_count = 0; - int partCount = 0; - int tempOffset = offset; - for (int ipolygon = 0; ipolygon < polygonCount; ipolygon++) { - tempOffset += 5; // skip redundant byte order and type fields - int ipartcount = wkbHelper.getInt(tempOffset); - tempOffset += 4; - - for (int ipart = 0; ipart < ipartcount; ipart++) { - int ipointcount = wkbHelper.getInt(tempOffset); - tempOffset += 4; - - // If ipointcount == 0, then we have an empty part - if (ipointcount == 0) - continue; - - if (ipointcount <= 2) { - tempOffset += ipointcount * 2 * 8; - - if (bZs) - tempOffset += ipointcount * 8; - - if (bMs) - tempOffset += ipointcount * 8; - - if (ipointcount == 1) - point_count += ipointcount + 1; - else - point_count += ipointcount; - - partCount++; - - continue; - } - - double startx = wkbHelper.getDouble(tempOffset); - tempOffset += 8; - double starty = wkbHelper.getDouble(tempOffset); - tempOffset += 8; - double startz = NumberUtils.TheNaN; - double startm = NumberUtils.TheNaN; - - if (bZs) { - startz = wkbHelper.getDouble(tempOffset); - tempOffset += 8; - } - - if (bMs) { - startm = wkbHelper.getDouble(tempOffset); - tempOffset += 8; - } - - tempOffset += (ipointcount - 2) * 2 * 8; - - if (bZs) - tempOffset += (ipointcount - 2) * 8; - - if (bMs) - tempOffset += (ipointcount - 2) * 8; - - double endx = wkbHelper.getDouble(tempOffset); - tempOffset += 8; - double endy = wkbHelper.getDouble(tempOffset); - tempOffset += 8; - double endz = NumberUtils.TheNaN; - double endm = NumberUtils.TheNaN; - - if (bZs) { - endz = wkbHelper.getDouble(tempOffset); - tempOffset += 8; - } - - if (bMs) { - endm = wkbHelper.getDouble(tempOffset); - tempOffset += 8; - } - - if ((startx == endx || (NumberUtils.isNaN(startx) && NumberUtils - .isNaN(endx))) - && (starty == endy || (NumberUtils.isNaN(starty) && NumberUtils - .isNaN(endy))) - && (!bZs || startz == endz || (NumberUtils - .isNaN(startz) && NumberUtils.isNaN(endz))) - && (!bMs || startm == endm || (NumberUtils - .isNaN(startm) && NumberUtils.isNaN(endm)))) { - point_count += ipointcount - 1; - } else { - point_count += ipointcount; - } - - partCount++; - } - } - - AttributeStreamOfDbl position = null; - AttributeStreamOfDbl zs = null; - AttributeStreamOfDbl ms = null; - AttributeStreamOfInt32 parts = null; - AttributeStreamOfInt8 pathFlags = null; - - Geometry newPolygon; - MultiPathImpl polygon; - - newPolygon = new Polygon(); - polygon = (MultiPathImpl) newPolygon._getImpl(); - - if (bZs) - polygon.addAttribute(VertexDescription.Semantics.Z); - - if (bMs) - polygon.addAttribute(VertexDescription.Semantics.M); - - if (point_count > 0) { - parts = (AttributeStreamOfInt32) (AttributeStreamBase - .createIndexStream(partCount + 1, 0)); - pathFlags = (AttributeStreamOfInt8) (AttributeStreamBase - .createByteStream(parts.size(), (byte) PathFlags.enumClosed)); - position = (AttributeStreamOfDbl) (AttributeStreamBase - .createAttributeStreamWithSemantics( - VertexDescription.Semantics.POSITION, point_count)); - - if (bZs) - zs = (AttributeStreamOfDbl) (AttributeStreamBase - .createAttributeStreamWithSemantics( - VertexDescription.Semantics.Z, point_count)); - - if (bMs) - ms = (AttributeStreamOfDbl) (AttributeStreamBase - .createAttributeStreamWithSemantics( - VertexDescription.Semantics.M, point_count)); - } - - boolean bCreateMs = false, bCreateZs = false; - int ipartend = 0; - int ipolygonend = 0; - int part_index = 0; - - // read Coordinates - for (int ipolygon = 0; ipolygon < polygonCount; ipolygon++) { - offset += 5; // skip redundant byte order and type fields - int ipartcount = wkbHelper.getInt(offset); - offset += 4; - int ipolygonstart = ipolygonend; - ipolygonend = ipolygonstart + ipartcount; - - for (int ipart = ipolygonstart; ipart < ipolygonend; ipart++) { - int ipointcount = wkbHelper.getInt(offset); - offset += 4; - - if (ipointcount == 0) - continue; - - int ipartstart = ipartend; - ipartend += ipointcount; - boolean bSkipLastPoint = true; - - if (ipointcount == 1) { - ipartstart++; - ipartend++; - bSkipLastPoint = false; - } else if (ipointcount == 2) { - bSkipLastPoint = false; - } else { - // Check if start point is equal to end point - - tempOffset = offset; - - double startx = wkbHelper.getDouble(tempOffset); - tempOffset += 8; - double starty = wkbHelper.getDouble(tempOffset); - tempOffset += 8; - double startz = NumberUtils.TheNaN; - double startm = NumberUtils.TheNaN; - - if (bZs) { - startz = wkbHelper.getDouble(tempOffset); - tempOffset += 8; - } - - if (bMs) { - startm = wkbHelper.getDouble(tempOffset); - tempOffset += 8; - } - - tempOffset += (ipointcount - 2) * 2 * 8; - - if (bZs) - tempOffset += (ipointcount - 2) * 8; - - if (bMs) - tempOffset += (ipointcount - 2) * 8; - - double endx = wkbHelper.getDouble(tempOffset); - tempOffset += 8; - double endy = wkbHelper.getDouble(tempOffset); - tempOffset += 8; - double endz = NumberUtils.TheNaN; - double endm = NumberUtils.TheNaN; - - if (bZs) { - endz = wkbHelper.getDouble(tempOffset); - tempOffset += 8; - } - - if (bMs) { - endm = wkbHelper.getDouble(tempOffset); - tempOffset += 8; - } - - if ((startx == endx || (NumberUtils.isNaN(startx) && NumberUtils - .isNaN(endx))) - && (starty == endy || (NumberUtils.isNaN(starty) && NumberUtils - .isNaN(endy))) - && (!bZs || startz == endz || (NumberUtils - .isNaN(startz) && NumberUtils.isNaN(endz))) - && (!bMs || startm == endm || (NumberUtils - .isNaN(startm) && NumberUtils.isNaN(endm)))) - ipartend--; - else - bSkipLastPoint = false; - } - - if (ipart == ipolygonstart) - pathFlags.setBits(ipart, - (byte) PathFlags.enumOGCStartPolygon); - - parts.write(++part_index, ipartend); - - // We must write from the buffer backwards - ogc polygon - // format is opposite of shapefile format - for (int i = ipartstart; i < ipartend; i++) { - double x = wkbHelper.getDouble(offset); - offset += 8; - double y = wkbHelper.getDouble(offset); - offset += 8; - - position.write(2 * i, x); - position.write(2 * i + 1, y); - - if (bZs) { - double z = wkbHelper.getDouble(offset); - offset += 8; - - zs.write(i, z); - if (!VertexDescription.isDefaultValue( - VertexDescription.Semantics.Z, z)) - bCreateZs = true; - } - - if (bMs) { - double m = wkbHelper.getDouble(offset); - offset += 8; - - ms.write(i, m); - if (!VertexDescription.isDefaultValue( - VertexDescription.Semantics.M, m)) - bCreateMs = true; - } - } - - if (bSkipLastPoint) { - offset += 2 * 8; - - if (bZs) - offset += 8; - - if (bMs) - offset += 8; - } else if (ipointcount == 1) { - double x = position.read(2 * ipartstart); - double y = position.read(2 * ipartstart + 1); - position.write(2 * (ipartstart - 1), x); - position.write(2 * (ipartstart - 1) + 1, y); - - if (bZs) { - double z = zs.read(ipartstart); - zs.write(ipartstart - 1, z); - } - - if (bMs) { - double m = ms.read(ipartstart); - ms.write(ipartstart - 1, m); - } - } - } - } - - // set envelopes and assign AttributeStreams - - if (point_count > 0) { - polygon.setPathStreamRef(parts); // sets m_parts - polygon.setPathFlagsStreamRef(pathFlags); - polygon.setAttributeStreamRef(VertexDescription.Semantics.POSITION, - position); - - if (bZs) { - if (!bCreateZs) - zs = null; - - polygon.setAttributeStreamRef(VertexDescription.Semantics.Z, zs); - } - - if (bMs) { - if (!bCreateMs) - ms = null; - - polygon.setAttributeStreamRef(VertexDescription.Semantics.M, ms); - } - - polygon.notifyModified(MultiPathImpl.DirtyFlags.DirtyAll); - - AttributeStreamOfInt8 path_flags_clone = new AttributeStreamOfInt8( - pathFlags); - - for (int i = 0; i < path_flags_clone.size() - 1; i++) { - if (((int) path_flags_clone.read(i) & (int) PathFlags.enumOGCStartPolygon) != 0) {// Should - // be - // clockwise - if (!InternalUtils.isClockwiseRing(polygon, i)) - polygon.reversePath(i); // make clockwise - } else {// Should be counter-clockwise - if (InternalUtils.isClockwiseRing(polygon, i)) - polygon.reversePath(i); // make counter-clockwise - } - } - - polygon.setPathFlagsStreamRef(path_flags_clone); - } - - if ((importFlags & (int) WkbImportFlags.wkbImportNonTrusted) == 0) - polygon.setIsSimple(MultiVertexGeometryImpl.GeometryXSimple.Weak, - 0.0, false); - - polygon.setDirtyOGCFlags(false); - wkbHelper.adjustment += offset; - - return newPolygon; - } - - private static Geometry importFromWkbPolyline(boolean bMultiPolyline, - int importFlags, boolean bZs, boolean bMs, WkbHelper wkbHelper) { - int offset; - int originalPartCount; - - if (bMultiPolyline) { - originalPartCount = wkbHelper.getInt(5); - offset = 9; - } else { - originalPartCount = 1; - offset = 0; - } - - // Find total point count and part count - int point_count = 0; - int partCount = 0; - int tempOffset = offset; - for (int ipart = 0; ipart < originalPartCount; ipart++) { - tempOffset += 5; // skip redundant byte order and type fields - int ipointcount = wkbHelper.getInt(tempOffset); - tempOffset += 4; - - // If ipointcount == 0, then we have an empty part - if (ipointcount == 0) - continue; - - point_count += ipointcount; - partCount++; - - if (ipointcount == 1) - point_count++; - - tempOffset += ipointcount * 2 * 8; - - if (bZs) - tempOffset += ipointcount * 8; - - if (bMs) - tempOffset += ipointcount * 8; - } - - AttributeStreamOfDbl position = null; - AttributeStreamOfDbl zs = null; - AttributeStreamOfDbl ms = null; - AttributeStreamOfInt32 parts = null; - AttributeStreamOfInt8 pathFlags = null; - - Polyline newpolyline; - MultiPathImpl polyline; - - newpolyline = new Polyline(); - polyline = (MultiPathImpl) newpolyline._getImpl(); - - if (bZs) - polyline.addAttribute(VertexDescription.Semantics.Z); - - if (bMs) - polyline.addAttribute(VertexDescription.Semantics.M); - - if (point_count > 0) { - parts = (AttributeStreamOfInt32) (AttributeStreamBase - .createIndexStream(partCount + 1, 0)); - pathFlags = (AttributeStreamOfInt8) (AttributeStreamBase - .createByteStream(parts.size(), (byte) 0)); - position = (AttributeStreamOfDbl) (AttributeStreamBase - .createAttributeStreamWithSemantics( - VertexDescription.Semantics.POSITION, point_count)); - - if (bZs) - zs = (AttributeStreamOfDbl) (AttributeStreamBase - .createAttributeStreamWithSemantics( - VertexDescription.Semantics.Z, point_count)); - - if (bMs) - ms = (AttributeStreamOfDbl) (AttributeStreamBase - .createAttributeStreamWithSemantics( - VertexDescription.Semantics.M, point_count)); - } - - boolean bCreateMs = false, bCreateZs = false; - int ipartend = 0; - int part_index = 0; - - // read Coordinates - for (int ipart = 0; ipart < originalPartCount; ipart++) { - offset += 5; // skip redundant byte order and type fields - - int ipointcount = wkbHelper.getInt(offset); - offset += 4; - - if (ipointcount == 0) - continue; - - int ipartstart = ipartend; - ipartend = ipartstart + ipointcount; - - if (ipointcount == 1) { - ipartstart++; - ipartend++; - } - - parts.write(++part_index, ipartend); - - for (int i = ipartstart; i < ipartend; i++) { - double x = wkbHelper.getDouble(offset); - offset += 8; - double y = wkbHelper.getDouble(offset); - offset += 8; - - position.write(2 * i, x); - position.write(2 * i + 1, y); - - if (bZs) { - double z = wkbHelper.getDouble(offset); - offset += 8; - - zs.write(i, z); - if (!VertexDescription.isDefaultValue( - VertexDescription.Semantics.Z, z)) - bCreateZs = true; - } - - if (bMs) { - double m = wkbHelper.getDouble(offset); - offset += 8; - - ms.write(i, m); - if (!VertexDescription.isDefaultValue( - VertexDescription.Semantics.M, m)) - bCreateMs = true; - } - } - - if (ipointcount == 1) { - double x = position.read(2 * ipartstart); - double y = position.read(2 * ipartstart + 1); - position.write(2 * (ipartstart - 1), x); - position.write(2 * (ipartstart - 1) + 1, y); - - if (bZs) { - double z = zs.read(ipartstart); - zs.write(ipartstart - 1, z); - } - - if (bMs) { - double m = ms.read(ipartstart); - ms.write(ipartstart - 1, m); - } - } - } - - // set envelopes and assign AttributeStreams - - if (point_count > 0) { - polyline.setPathStreamRef(parts); // sets m_parts - polyline.setPathFlagsStreamRef(pathFlags); - polyline.setAttributeStreamRef( - VertexDescription.Semantics.POSITION, position); - - if (bZs) { - if (!bCreateZs) - zs = null; - - polyline.setAttributeStreamRef(VertexDescription.Semantics.Z, - zs); - } - - if (bMs) { - if (!bCreateMs) - ms = null; - - polyline.setAttributeStreamRef(VertexDescription.Semantics.M, - ms); - } - - polyline.notifyModified(MultiPathImpl.DirtyFlags.DirtyAll); - } - - wkbHelper.adjustment += offset; - - return newpolyline; - } - - private static Geometry importFromWkbMultiPoint(int importFlags, - boolean bZs, boolean bMs, WkbHelper wkbHelper) { - int offset = 5; // skip byte order and type - - // set point count - int point_count = wkbHelper.getInt(offset); - offset += 4; - - AttributeStreamOfDbl position = null; - AttributeStreamOfDbl zs = null; - AttributeStreamOfDbl ms = null; - - MultiPoint newmultipoint; - MultiPointImpl multipoint; - - newmultipoint = new MultiPoint(); - multipoint = (MultiPointImpl) newmultipoint._getImpl(); - - if (bZs) - multipoint.addAttribute(VertexDescription.Semantics.Z); - - if (bMs) - multipoint.addAttribute(VertexDescription.Semantics.M); - - if (point_count > 0) { - position = (AttributeStreamOfDbl) (AttributeStreamBase - .createAttributeStreamWithSemantics( - VertexDescription.Semantics.POSITION, point_count)); - - if (bZs) - zs = (AttributeStreamOfDbl) (AttributeStreamBase - .createAttributeStreamWithSemantics( - VertexDescription.Semantics.Z, point_count)); - - if (bMs) - ms = (AttributeStreamOfDbl) (AttributeStreamBase - .createAttributeStreamWithSemantics( - VertexDescription.Semantics.M, point_count)); - } - - boolean bCreateMs = false, bCreateZs = false; - for (int i = 0; i < point_count; i++) { - offset += 5; // skip redundant byte order and type fields - - // read xy coordinates - double x = wkbHelper.getDouble(offset); - offset += 8; - double y = wkbHelper.getDouble(offset); - offset += 8; - - position.write(2 * i, x); - position.write(2 * i + 1, y); - - if (bZs) { - double z = wkbHelper.getDouble(offset); - offset += 8; - - zs.write(i, z); - if (!VertexDescription.isDefaultValue( - VertexDescription.Semantics.Z, z)) - bCreateZs = true; - } - - if (bMs) { - double m = wkbHelper.getDouble(offset); - offset += 8; - - ms.write(i, m); - if (!VertexDescription.isDefaultValue( - VertexDescription.Semantics.M, m)) - bCreateMs = true; - } - } - - // set envelopes and assign AttributeStreams - - if (point_count > 0) { - multipoint.resize(point_count); - multipoint.setAttributeStreamRef( - VertexDescription.Semantics.POSITION, position); - - if (bZs) { - if (!bCreateZs) - zs = null; - - multipoint.setAttributeStreamRef(VertexDescription.Semantics.Z, - zs); - } - - if (bMs) { - if (!bCreateMs) - ms = null; - - multipoint.setAttributeStreamRef(VertexDescription.Semantics.M, - ms); - } - - multipoint.notifyModified(MultiPointImpl.DirtyFlags.DirtyAll); - } - - wkbHelper.adjustment += offset; - - return newmultipoint; - } - - private static Geometry importFromWkbPoint(int importFlags, boolean bZs, - boolean bMs, WkbHelper wkbHelper) { - int offset = 5; // skip byte order and type - - // set xy coordinate - double x = wkbHelper.getDouble(offset); - offset += 8; - double y = wkbHelper.getDouble(offset); - offset += 8; - - double z = NumberUtils.TheNaN; - if (bZs) { - z = wkbHelper.getDouble(offset); - offset += 8; - } - - double m = NumberUtils.TheNaN; - if (bMs) { - m = wkbHelper.getDouble(offset); - offset += 8; - } - - boolean bEmpty = NumberUtils.isNaN(x); - Point point = new Point(); - - if (!bEmpty) { - point.setX(x); - point.setY(y); - } - - // set Z - if (bZs) { - point.addAttribute(VertexDescription.Semantics.Z); - if (!bEmpty) - point.setZ(z); - } - - // set M - if (bMs) { - point.addAttribute(VertexDescription.Semantics.M); - if (!bEmpty) - point.setM(m); - } - - wkbHelper.adjustment += offset; - - return point; - } + double getDouble(int offset) { + return wkbBuffer.getDouble(adjustment + offset); + } + + ByteBuffer wkbBuffer; + int adjustment; + } + + + @Override + public GeometryCursor execute(int importFlags, ByteBufferCursor wkbBuffers, ProgressTracker progressTracker) { + return new OperatorImportFromWkbCursor(importFlags, wkbBuffers); + } + + @Override + public Geometry execute(int importFlags, Geometry.Type type, ByteBuffer wkbBuffer, ProgressTracker progress_tracker) { + + ByteOrder initialOrder = wkbBuffer.order(); + + // read byte ordering + int byteOrder = wkbBuffer.get(0); + + if (byteOrder == WkbByteOrder.wkbNDR) + wkbBuffer.order(ByteOrder.LITTLE_ENDIAN); + else + wkbBuffer.order(ByteOrder.BIG_ENDIAN); + + WkbHelper wkbHelper = new WkbHelper(wkbBuffer); + + try { + return importFromWkb(importFlags, type, wkbHelper); + } finally { + wkbBuffer.order(initialOrder); + } + } + + @Override + public OGCStructure executeOGC(int importFlags, ByteBuffer wkbBuffer, ProgressTracker progress_tracker) { + + ByteOrder initialOrder = wkbBuffer.order(); + + // read byte ordering + int byteOrder = wkbBuffer.get(0); + + if (byteOrder == WkbByteOrder.wkbNDR) + wkbBuffer.order(ByteOrder.LITTLE_ENDIAN); + else + wkbBuffer.order(ByteOrder.BIG_ENDIAN); + + ArrayList stack = new ArrayList(0); + AttributeStreamOfInt32 numGeometries = new AttributeStreamOfInt32(0); + AttributeStreamOfInt32 indices = new AttributeStreamOfInt32(0); + WkbHelper wkbHelper = new WkbHelper(wkbBuffer); + + OGCStructure root = new OGCStructure(); + root.m_structures = new ArrayList(0); + stack.add(root); // add dummy root + numGeometries.add(1); + indices.add(0); + + boolean bCheckConsistentAttributes = false; + boolean bHasZs = false; + boolean bHasMs = false; + + try { + + while (!stack.isEmpty()) { + + if (indices.getLast() == numGeometries.getLast()) { + stack.remove(stack.size() - 1); + indices.removeLast(); + numGeometries.removeLast(); + continue; + } + + OGCStructure last = stack.get(stack.size() - 1); + indices.write(indices.size() - 1, indices.getLast() + 1); + Geometry geometry; + + int wkbType = wkbHelper.getInt(1); + int ogcType; + + // strip away attributes from type identifier + + if (wkbType > 3000) { + ogcType = wkbType - 3000; + + if (bCheckConsistentAttributes) { + if (!bHasZs || !bHasMs) + throw new IllegalArgumentException(); + } else { + bHasZs = true; + bHasMs = true; + bCheckConsistentAttributes = true; + } + } else if (wkbType > 2000) { + ogcType = wkbType - 2000; + + if (bCheckConsistentAttributes) { + if (bHasZs || !bHasMs) + throw new IllegalArgumentException(); + } else { + bHasZs = false; + bHasMs = true; + bCheckConsistentAttributes = true; + } + } else if (wkbType > 1000) { + ogcType = wkbType - 1000; + + if (bCheckConsistentAttributes) { + if (!bHasZs || bHasMs) + throw new IllegalArgumentException(); + } else { + bHasZs = true; + bHasMs = false; + bCheckConsistentAttributes = true; + } + } else { + ogcType = wkbType; + + if (bCheckConsistentAttributes) { + if (bHasZs || bHasMs) + throw new IllegalArgumentException(); + } else { + bHasZs = false; + bHasMs = false; + bCheckConsistentAttributes = true; + } + } + if (ogcType == 7) { + int count = wkbHelper.getInt(5); + wkbHelper.adjustment += 9; + + OGCStructure next = new OGCStructure(); + next.m_type = ogcType; + next.m_structures = new ArrayList(0); + last.m_structures.add(next); + stack.add(next); + indices.add(0); + numGeometries.add(count); + } else { + geometry = importFromWkb(importFlags, + Geometry.Type.Unknown, wkbHelper); + OGCStructure leaf = new OGCStructure(); + leaf.m_type = ogcType; + leaf.m_geometry = geometry; + last.m_structures.add(leaf); + } + } + } finally { + wkbBuffer.order(initialOrder); + } + + return root; + } + + private static Geometry importFromWkb(int importFlags, Geometry.Type type, + WkbHelper wkbHelper) { + + // read type + int wkbType = wkbHelper.getInt(1); + + switch (wkbType) { + case WkbGeometryType.wkbPolygon: + if (type.value() != Geometry.GeometryType.Polygon + && type.value() != Geometry.GeometryType.Unknown) + throw new GeometryException("invalid shape type"); + return importFromWkbPolygon(false, importFlags, false, false, + wkbHelper); + + case WkbGeometryType.wkbPolygonM: + if (type.value() != Geometry.GeometryType.Polygon + && type.value() != Geometry.GeometryType.Unknown) + throw new GeometryException("invalid shape type"); + return importFromWkbPolygon(false, importFlags, false, true, + wkbHelper); + + case WkbGeometryType.wkbPolygonZ: + if (type.value() != Geometry.GeometryType.Polygon + && type.value() != Geometry.GeometryType.Unknown) + throw new GeometryException("invalid shape type"); + return importFromWkbPolygon(false, importFlags, true, false, + wkbHelper); + + case WkbGeometryType.wkbPolygonZM: + if (type.value() != Geometry.GeometryType.Polygon + && type.value() != Geometry.GeometryType.Unknown) + throw new GeometryException("invalid shape type"); + return importFromWkbPolygon(false, importFlags, true, true, + wkbHelper); + + case WkbGeometryType.wkbMultiPolygon: + if (type.value() != Geometry.GeometryType.Polygon + && type.value() != Geometry.GeometryType.Unknown) + throw new GeometryException("invalid shape type"); + return importFromWkbPolygon(true, importFlags, false, false, + wkbHelper); + + case WkbGeometryType.wkbMultiPolygonM: + if (type.value() != Geometry.GeometryType.Polygon + && type.value() != Geometry.GeometryType.Unknown) + throw new GeometryException("invalid shape type"); + return importFromWkbPolygon(true, importFlags, false, true, + wkbHelper); + + case WkbGeometryType.wkbMultiPolygonZ: + if (type.value() != Geometry.GeometryType.Polygon + && type.value() != Geometry.GeometryType.Unknown) + throw new GeometryException("invalid shape type"); + return importFromWkbPolygon(true, importFlags, true, false, + wkbHelper); + + case WkbGeometryType.wkbMultiPolygonZM: + if (type.value() != Geometry.GeometryType.Polygon + && type.value() != Geometry.GeometryType.Unknown) + throw new GeometryException("invalid shape type"); + return importFromWkbPolygon(true, importFlags, true, true, + wkbHelper); + + case WkbGeometryType.wkbLineString: + if (type.value() != Geometry.GeometryType.Polyline + && type.value() != Geometry.GeometryType.Unknown) + throw new GeometryException("invalid shape type"); + return importFromWkbPolyline(false, importFlags, false, false, + wkbHelper); + + case WkbGeometryType.wkbLineStringM: + if (type.value() != Geometry.GeometryType.Polyline + && type.value() != Geometry.GeometryType.Unknown) + throw new GeometryException("invalid shape type"); + return importFromWkbPolyline(false, importFlags, false, true, + wkbHelper); + + case WkbGeometryType.wkbLineStringZ: + if (type.value() != Geometry.GeometryType.Polyline + && type.value() != Geometry.GeometryType.Unknown) + throw new GeometryException("invalid shape type"); + return importFromWkbPolyline(false, importFlags, true, false, + wkbHelper); + + case WkbGeometryType.wkbLineStringZM: + if (type.value() != Geometry.GeometryType.Polyline + && type.value() != Geometry.GeometryType.Unknown) + throw new GeometryException("invalid shape type"); + return importFromWkbPolyline(false, importFlags, true, true, + wkbHelper); + + case WkbGeometryType.wkbMultiLineString: + if (type.value() != Geometry.GeometryType.Polyline + && type.value() != Geometry.GeometryType.Unknown) + throw new GeometryException("invalid shape type"); + return importFromWkbPolyline(true, importFlags, false, false, + wkbHelper); + + case WkbGeometryType.wkbMultiLineStringM: + if (type.value() != Geometry.GeometryType.Polyline + && type.value() != Geometry.GeometryType.Unknown) + throw new GeometryException("invalid shape type"); + return importFromWkbPolyline(true, importFlags, false, true, + wkbHelper); + + case WkbGeometryType.wkbMultiLineStringZ: + if (type.value() != Geometry.GeometryType.Polyline + && type.value() != Geometry.GeometryType.Unknown) + throw new GeometryException("invalid shape type"); + return importFromWkbPolyline(true, importFlags, true, false, + wkbHelper); + + case WkbGeometryType.wkbMultiLineStringZM: + if (type.value() != Geometry.GeometryType.Polyline + && type.value() != Geometry.GeometryType.Unknown) + throw new GeometryException("invalid shape type"); + return importFromWkbPolyline(true, importFlags, true, true, + wkbHelper); + + case WkbGeometryType.wkbMultiPoint: + if (type.value() != Geometry.GeometryType.MultiPoint + && type.value() != Geometry.GeometryType.Unknown) + throw new GeometryException("invalid shape type"); + return importFromWkbMultiPoint(importFlags, false, false, wkbHelper); + + case WkbGeometryType.wkbMultiPointM: + if (type.value() != Geometry.GeometryType.MultiPoint + && type.value() != Geometry.GeometryType.Unknown) + throw new GeometryException("invalid shape type"); + return importFromWkbMultiPoint(importFlags, false, true, wkbHelper); + + case WkbGeometryType.wkbMultiPointZ: + if (type.value() != Geometry.GeometryType.MultiPoint + && type.value() != Geometry.GeometryType.Unknown) + throw new GeometryException("invalid shape type"); + return importFromWkbMultiPoint(importFlags, true, false, wkbHelper); + + case WkbGeometryType.wkbMultiPointZM: + if (type.value() != Geometry.GeometryType.MultiPoint + && type.value() != Geometry.GeometryType.Unknown) + throw new GeometryException("invalid shape type"); + return importFromWkbMultiPoint(importFlags, true, true, wkbHelper); + + case WkbGeometryType.wkbPoint: + if (type.value() != Geometry.GeometryType.Point + && type.value() != Geometry.GeometryType.Unknown) + throw new GeometryException("invalid shape type"); + return importFromWkbPoint(importFlags, false, false, wkbHelper); + + case WkbGeometryType.wkbPointM: + if (type.value() != Geometry.GeometryType.Point + && type.value() != Geometry.GeometryType.Unknown) + throw new GeometryException("invalid shape type"); + return importFromWkbPoint(importFlags, false, true, wkbHelper); + + case WkbGeometryType.wkbPointZ: + if (type.value() != Geometry.GeometryType.Point + && type.value() != Geometry.GeometryType.Unknown) + throw new GeometryException("invalid shape type"); + return importFromWkbPoint(importFlags, true, false, wkbHelper); + + case WkbGeometryType.wkbPointZM: + if (type.value() != Geometry.GeometryType.Point + && type.value() != Geometry.GeometryType.Unknown) + throw new GeometryException("invalid shape type"); + return importFromWkbPoint(importFlags, true, true, wkbHelper); + + default: + throw new GeometryException("invalid shape type"); + } + } + + private static Geometry importFromWkbPolygon(boolean bMultiPolygon, + int importFlags, boolean bZs, boolean bMs, WkbHelper wkbHelper) { + int offset; + int polygonCount; + + if (bMultiPolygon) { + polygonCount = wkbHelper.getInt(5); + offset = 9; + } else { + polygonCount = 1; + offset = 0; + } + + // Find total point count and part count + int point_count = 0; + int partCount = 0; + int tempOffset = offset; + for (int ipolygon = 0; ipolygon < polygonCount; ipolygon++) { + tempOffset += 5; // skip redundant byte order and type fields + int ipartcount = wkbHelper.getInt(tempOffset); + tempOffset += 4; + + for (int ipart = 0; ipart < ipartcount; ipart++) { + int ipointcount = wkbHelper.getInt(tempOffset); + tempOffset += 4; + + // If ipointcount == 0, then we have an empty part + if (ipointcount == 0) + continue; + + if (ipointcount <= 2) { + tempOffset += ipointcount * 2 * 8; + + if (bZs) + tempOffset += ipointcount * 8; + + if (bMs) + tempOffset += ipointcount * 8; + + if (ipointcount == 1) + point_count += ipointcount + 1; + else + point_count += ipointcount; + + partCount++; + + continue; + } + + double startx = wkbHelper.getDouble(tempOffset); + tempOffset += 8; + double starty = wkbHelper.getDouble(tempOffset); + tempOffset += 8; + double startz = NumberUtils.TheNaN; + double startm = NumberUtils.TheNaN; + + if (bZs) { + startz = wkbHelper.getDouble(tempOffset); + tempOffset += 8; + } + + if (bMs) { + startm = wkbHelper.getDouble(tempOffset); + tempOffset += 8; + } + + tempOffset += (ipointcount - 2) * 2 * 8; + + if (bZs) + tempOffset += (ipointcount - 2) * 8; + + if (bMs) + tempOffset += (ipointcount - 2) * 8; + + double endx = wkbHelper.getDouble(tempOffset); + tempOffset += 8; + double endy = wkbHelper.getDouble(tempOffset); + tempOffset += 8; + double endz = NumberUtils.TheNaN; + double endm = NumberUtils.TheNaN; + + if (bZs) { + endz = wkbHelper.getDouble(tempOffset); + tempOffset += 8; + } + + if (bMs) { + endm = wkbHelper.getDouble(tempOffset); + tempOffset += 8; + } + + if ((startx == endx || (NumberUtils.isNaN(startx) && NumberUtils + .isNaN(endx))) + && (starty == endy || (NumberUtils.isNaN(starty) && NumberUtils + .isNaN(endy))) + && (!bZs || startz == endz || (NumberUtils + .isNaN(startz) && NumberUtils.isNaN(endz))) + && (!bMs || startm == endm || (NumberUtils + .isNaN(startm) && NumberUtils.isNaN(endm)))) { + point_count += ipointcount - 1; + } else { + point_count += ipointcount; + } + + partCount++; + } + } + + AttributeStreamOfDbl position = null; + AttributeStreamOfDbl zs = null; + AttributeStreamOfDbl ms = null; + AttributeStreamOfInt32 parts = null; + AttributeStreamOfInt8 pathFlags = null; + + Geometry newPolygon; + MultiPathImpl polygon; + + newPolygon = new Polygon(); + polygon = (MultiPathImpl) newPolygon._getImpl(); + + if (bZs) + polygon.addAttribute(VertexDescription.Semantics.Z); + + if (bMs) + polygon.addAttribute(VertexDescription.Semantics.M); + + if (point_count > 0) { + parts = (AttributeStreamOfInt32) (AttributeStreamBase + .createIndexStream(partCount + 1, 0)); + pathFlags = (AttributeStreamOfInt8) (AttributeStreamBase + .createByteStream(parts.size(), (byte) PathFlags.enumClosed)); + position = (AttributeStreamOfDbl) (AttributeStreamBase + .createAttributeStreamWithSemantics( + VertexDescription.Semantics.POSITION, point_count)); + + if (bZs) + zs = (AttributeStreamOfDbl) (AttributeStreamBase + .createAttributeStreamWithSemantics( + VertexDescription.Semantics.Z, point_count)); + + if (bMs) + ms = (AttributeStreamOfDbl) (AttributeStreamBase + .createAttributeStreamWithSemantics( + VertexDescription.Semantics.M, point_count)); + } + + boolean bCreateMs = false, bCreateZs = false; + int ipartend = 0; + int ipolygonend = 0; + int part_index = 0; + + // read Coordinates + for (int ipolygon = 0; ipolygon < polygonCount; ipolygon++) { + offset += 5; // skip redundant byte order and type fields + int ipartcount = wkbHelper.getInt(offset); + offset += 4; + int ipolygonstart = ipolygonend; + ipolygonend = ipolygonstart + ipartcount; + + for (int ipart = ipolygonstart; ipart < ipolygonend; ipart++) { + int ipointcount = wkbHelper.getInt(offset); + offset += 4; + + if (ipointcount == 0) + continue; + + int ipartstart = ipartend; + ipartend += ipointcount; + boolean bSkipLastPoint = true; + + if (ipointcount == 1) { + ipartstart++; + ipartend++; + bSkipLastPoint = false; + } else if (ipointcount == 2) { + bSkipLastPoint = false; + } else { + // Check if start point is equal to end point + + tempOffset = offset; + + double startx = wkbHelper.getDouble(tempOffset); + tempOffset += 8; + double starty = wkbHelper.getDouble(tempOffset); + tempOffset += 8; + double startz = NumberUtils.TheNaN; + double startm = NumberUtils.TheNaN; + + if (bZs) { + startz = wkbHelper.getDouble(tempOffset); + tempOffset += 8; + } + + if (bMs) { + startm = wkbHelper.getDouble(tempOffset); + tempOffset += 8; + } + + tempOffset += (ipointcount - 2) * 2 * 8; + + if (bZs) + tempOffset += (ipointcount - 2) * 8; + + if (bMs) + tempOffset += (ipointcount - 2) * 8; + + double endx = wkbHelper.getDouble(tempOffset); + tempOffset += 8; + double endy = wkbHelper.getDouble(tempOffset); + tempOffset += 8; + double endz = NumberUtils.TheNaN; + double endm = NumberUtils.TheNaN; + + if (bZs) { + endz = wkbHelper.getDouble(tempOffset); + tempOffset += 8; + } + + if (bMs) { + endm = wkbHelper.getDouble(tempOffset); + tempOffset += 8; + } + + if ((startx == endx || (NumberUtils.isNaN(startx) && NumberUtils + .isNaN(endx))) + && (starty == endy || (NumberUtils.isNaN(starty) && NumberUtils + .isNaN(endy))) + && (!bZs || startz == endz || (NumberUtils + .isNaN(startz) && NumberUtils.isNaN(endz))) + && (!bMs || startm == endm || (NumberUtils + .isNaN(startm) && NumberUtils.isNaN(endm)))) + ipartend--; + else + bSkipLastPoint = false; + } + + if (ipart == ipolygonstart) + pathFlags.setBits(ipart, + (byte) PathFlags.enumOGCStartPolygon); + + parts.write(++part_index, ipartend); + + // We must write from the buffer backwards - ogc polygon + // format is opposite of shapefile format + for (int i = ipartstart; i < ipartend; i++) { + double x = wkbHelper.getDouble(offset); + offset += 8; + double y = wkbHelper.getDouble(offset); + offset += 8; + + position.write(2 * i, x); + position.write(2 * i + 1, y); + + if (bZs) { + double z = wkbHelper.getDouble(offset); + offset += 8; + + zs.write(i, z); + if (!VertexDescription.isDefaultValue( + VertexDescription.Semantics.Z, z)) + bCreateZs = true; + } + + if (bMs) { + double m = wkbHelper.getDouble(offset); + offset += 8; + + ms.write(i, m); + if (!VertexDescription.isDefaultValue( + VertexDescription.Semantics.M, m)) + bCreateMs = true; + } + } + + if (bSkipLastPoint) { + offset += 2 * 8; + + if (bZs) + offset += 8; + + if (bMs) + offset += 8; + } else if (ipointcount == 1) { + double x = position.read(2 * ipartstart); + double y = position.read(2 * ipartstart + 1); + position.write(2 * (ipartstart - 1), x); + position.write(2 * (ipartstart - 1) + 1, y); + + if (bZs) { + double z = zs.read(ipartstart); + zs.write(ipartstart - 1, z); + } + + if (bMs) { + double m = ms.read(ipartstart); + ms.write(ipartstart - 1, m); + } + } + } + } + + // set envelopes and assign AttributeStreams + + if (point_count > 0) { + polygon.setPathStreamRef(parts); // sets m_parts + polygon.setPathFlagsStreamRef(pathFlags); + polygon.setAttributeStreamRef(VertexDescription.Semantics.POSITION, + position); + + if (bZs) { + if (!bCreateZs) + zs = null; + + polygon.setAttributeStreamRef(VertexDescription.Semantics.Z, zs); + } + + if (bMs) { + if (!bCreateMs) + ms = null; + + polygon.setAttributeStreamRef(VertexDescription.Semantics.M, ms); + } + + polygon.notifyModified(MultiPathImpl.DirtyFlags.DirtyAll); + + AttributeStreamOfInt8 path_flags_clone = new AttributeStreamOfInt8( + pathFlags); + + for (int i = 0; i < path_flags_clone.size() - 1; i++) { + if (((int) path_flags_clone.read(i) & (int) PathFlags.enumOGCStartPolygon) != 0) {// Should + // be + // clockwise + if (!InternalUtils.isClockwiseRing(polygon, i)) + polygon.reversePath(i); // make clockwise + } else {// Should be counter-clockwise + if (InternalUtils.isClockwiseRing(polygon, i)) + polygon.reversePath(i); // make counter-clockwise + } + } + + polygon.setPathFlagsStreamRef(path_flags_clone); + } + + if ((importFlags & (int) WkbImportFlags.wkbImportNonTrusted) == 0) + polygon.setIsSimple(MultiVertexGeometryImpl.GeometryXSimple.Weak, + 0.0, false); + + polygon.setDirtyOGCFlags(false); + wkbHelper.adjustment += offset; + + return newPolygon; + } + + private static Geometry importFromWkbPolyline(boolean bMultiPolyline, + int importFlags, boolean bZs, boolean bMs, WkbHelper wkbHelper) { + int offset; + int originalPartCount; + + if (bMultiPolyline) { + originalPartCount = wkbHelper.getInt(5); + offset = 9; + } else { + originalPartCount = 1; + offset = 0; + } + + // Find total point count and part count + int point_count = 0; + int partCount = 0; + int tempOffset = offset; + for (int ipart = 0; ipart < originalPartCount; ipart++) { + tempOffset += 5; // skip redundant byte order and type fields + int ipointcount = wkbHelper.getInt(tempOffset); + tempOffset += 4; + + // If ipointcount == 0, then we have an empty part + if (ipointcount == 0) + continue; + + point_count += ipointcount; + partCount++; + + if (ipointcount == 1) + point_count++; + + tempOffset += ipointcount * 2 * 8; + + if (bZs) + tempOffset += ipointcount * 8; + + if (bMs) + tempOffset += ipointcount * 8; + } + + AttributeStreamOfDbl position = null; + AttributeStreamOfDbl zs = null; + AttributeStreamOfDbl ms = null; + AttributeStreamOfInt32 parts = null; + AttributeStreamOfInt8 pathFlags = null; + + Polyline newpolyline; + MultiPathImpl polyline; + + newpolyline = new Polyline(); + polyline = (MultiPathImpl) newpolyline._getImpl(); + + if (bZs) + polyline.addAttribute(VertexDescription.Semantics.Z); + + if (bMs) + polyline.addAttribute(VertexDescription.Semantics.M); + + if (point_count > 0) { + parts = (AttributeStreamOfInt32) (AttributeStreamBase + .createIndexStream(partCount + 1, 0)); + pathFlags = (AttributeStreamOfInt8) (AttributeStreamBase + .createByteStream(parts.size(), (byte) 0)); + position = (AttributeStreamOfDbl) (AttributeStreamBase + .createAttributeStreamWithSemantics( + VertexDescription.Semantics.POSITION, point_count)); + + if (bZs) + zs = (AttributeStreamOfDbl) (AttributeStreamBase + .createAttributeStreamWithSemantics( + VertexDescription.Semantics.Z, point_count)); + + if (bMs) + ms = (AttributeStreamOfDbl) (AttributeStreamBase + .createAttributeStreamWithSemantics( + VertexDescription.Semantics.M, point_count)); + } + + boolean bCreateMs = false, bCreateZs = false; + int ipartend = 0; + int part_index = 0; + + // read Coordinates + for (int ipart = 0; ipart < originalPartCount; ipart++) { + offset += 5; // skip redundant byte order and type fields + + int ipointcount = wkbHelper.getInt(offset); + offset += 4; + + if (ipointcount == 0) + continue; + + int ipartstart = ipartend; + ipartend = ipartstart + ipointcount; + + if (ipointcount == 1) { + ipartstart++; + ipartend++; + } + + parts.write(++part_index, ipartend); + + for (int i = ipartstart; i < ipartend; i++) { + double x = wkbHelper.getDouble(offset); + offset += 8; + double y = wkbHelper.getDouble(offset); + offset += 8; + + position.write(2 * i, x); + position.write(2 * i + 1, y); + + if (bZs) { + double z = wkbHelper.getDouble(offset); + offset += 8; + + zs.write(i, z); + if (!VertexDescription.isDefaultValue( + VertexDescription.Semantics.Z, z)) + bCreateZs = true; + } + + if (bMs) { + double m = wkbHelper.getDouble(offset); + offset += 8; + + ms.write(i, m); + if (!VertexDescription.isDefaultValue( + VertexDescription.Semantics.M, m)) + bCreateMs = true; + } + } + + if (ipointcount == 1) { + double x = position.read(2 * ipartstart); + double y = position.read(2 * ipartstart + 1); + position.write(2 * (ipartstart - 1), x); + position.write(2 * (ipartstart - 1) + 1, y); + + if (bZs) { + double z = zs.read(ipartstart); + zs.write(ipartstart - 1, z); + } + + if (bMs) { + double m = ms.read(ipartstart); + ms.write(ipartstart - 1, m); + } + } + } + + // set envelopes and assign AttributeStreams + + if (point_count > 0) { + polyline.setPathStreamRef(parts); // sets m_parts + polyline.setPathFlagsStreamRef(pathFlags); + polyline.setAttributeStreamRef( + VertexDescription.Semantics.POSITION, position); + + if (bZs) { + if (!bCreateZs) + zs = null; + + polyline.setAttributeStreamRef(VertexDescription.Semantics.Z, + zs); + } + + if (bMs) { + if (!bCreateMs) + ms = null; + + polyline.setAttributeStreamRef(VertexDescription.Semantics.M, + ms); + } + + polyline.notifyModified(MultiPathImpl.DirtyFlags.DirtyAll); + } + + wkbHelper.adjustment += offset; + + return newpolyline; + } + + private static Geometry importFromWkbMultiPoint(int importFlags, + boolean bZs, boolean bMs, WkbHelper wkbHelper) { + int offset = 5; // skip byte order and type + + // set point count + int point_count = wkbHelper.getInt(offset); + offset += 4; + + AttributeStreamOfDbl position = null; + AttributeStreamOfDbl zs = null; + AttributeStreamOfDbl ms = null; + + MultiPoint newmultipoint; + MultiPointImpl multipoint; + + newmultipoint = new MultiPoint(); + multipoint = (MultiPointImpl) newmultipoint._getImpl(); + + if (bZs) + multipoint.addAttribute(VertexDescription.Semantics.Z); + + if (bMs) + multipoint.addAttribute(VertexDescription.Semantics.M); + + if (point_count > 0) { + position = (AttributeStreamOfDbl) (AttributeStreamBase + .createAttributeStreamWithSemantics( + VertexDescription.Semantics.POSITION, point_count)); + + if (bZs) + zs = (AttributeStreamOfDbl) (AttributeStreamBase + .createAttributeStreamWithSemantics( + VertexDescription.Semantics.Z, point_count)); + + if (bMs) + ms = (AttributeStreamOfDbl) (AttributeStreamBase + .createAttributeStreamWithSemantics( + VertexDescription.Semantics.M, point_count)); + } + + boolean bCreateMs = false, bCreateZs = false; + for (int i = 0; i < point_count; i++) { + offset += 5; // skip redundant byte order and type fields + + // read xy coordinates + double x = wkbHelper.getDouble(offset); + offset += 8; + double y = wkbHelper.getDouble(offset); + offset += 8; + + position.write(2 * i, x); + position.write(2 * i + 1, y); + + if (bZs) { + double z = wkbHelper.getDouble(offset); + offset += 8; + + zs.write(i, z); + if (!VertexDescription.isDefaultValue( + VertexDescription.Semantics.Z, z)) + bCreateZs = true; + } + + if (bMs) { + double m = wkbHelper.getDouble(offset); + offset += 8; + + ms.write(i, m); + if (!VertexDescription.isDefaultValue( + VertexDescription.Semantics.M, m)) + bCreateMs = true; + } + } + + // set envelopes and assign AttributeStreams + + if (point_count > 0) { + multipoint.resize(point_count); + multipoint.setAttributeStreamRef( + VertexDescription.Semantics.POSITION, position); + + if (bZs) { + if (!bCreateZs) + zs = null; + + multipoint.setAttributeStreamRef(VertexDescription.Semantics.Z, + zs); + } + + if (bMs) { + if (!bCreateMs) + ms = null; + + multipoint.setAttributeStreamRef(VertexDescription.Semantics.M, + ms); + } + + multipoint.notifyModified(MultiPointImpl.DirtyFlags.DirtyAll); + } + + wkbHelper.adjustment += offset; + + return newmultipoint; + } + + private static Geometry importFromWkbPoint(int importFlags, boolean bZs, + boolean bMs, WkbHelper wkbHelper) { + int offset = 5; // skip byte order and type + + // set xy coordinate + double x = wkbHelper.getDouble(offset); + offset += 8; + double y = wkbHelper.getDouble(offset); + offset += 8; + + double z = NumberUtils.TheNaN; + if (bZs) { + z = wkbHelper.getDouble(offset); + offset += 8; + } + + double m = NumberUtils.TheNaN; + if (bMs) { + m = wkbHelper.getDouble(offset); + offset += 8; + } + + boolean bEmpty = NumberUtils.isNaN(x); + Point point = new Point(); + + if (!bEmpty) { + point.setX(x); + point.setY(y); + } + + // set Z + if (bZs) { + point.addAttribute(VertexDescription.Semantics.Z); + if (!bEmpty) + point.setZ(z); + } + + // set M + if (bMs) { + point.addAttribute(VertexDescription.Semantics.M); + if (!bEmpty) + point.setM(m); + } + + wkbHelper.adjustment += offset; + + return point; + } } diff --git a/src/main/java/com/esri/core/geometry/OperatorImportFromWkt.java b/src/main/java/com/esri/core/geometry/OperatorImportFromWkt.java index 62e68255..fa4c3705 100644 --- a/src/main/java/com/esri/core/geometry/OperatorImportFromWkt.java +++ b/src/main/java/com/esri/core/geometry/OperatorImportFromWkt.java @@ -24,41 +24,41 @@ package com.esri.core.geometry; public abstract class OperatorImportFromWkt extends Operator { - @Override - public Type getType() { - return Type.ImportFromWkb; - } + @Override + public Type getType() { + return Type.ImportFromWkb; + } - public abstract GeometryCursor execute(int import_flags, - SimpleStringCursor wkt_stringCursor, - ProgressTracker progress_tracker); + public abstract GeometryCursor execute(int import_flags, + SimpleStringCursor wkt_stringCursor, + ProgressTracker progress_tracker); - /** - * Performs the ImportFromWkt operation. - * - * @param import_flags Use the {@link WktImportFlags} interface. - * @param type Use the {@link Geometry.Type} enum. - * @param wkt_string The string holding the Geometry in wkt format. - * @return Returns the imported Geometry. - */ - public abstract Geometry execute(int import_flags, - Geometry.Type type, - String wkt_string, - ProgressTracker progress_tracker); + /** + * Performs the ImportFromWkt operation. + * + * @param import_flags Use the {@link WktImportFlags} interface. + * @param type Use the {@link Geometry.Type} enum. + * @param wkt_string The string holding the Geometry in wkt format. + * @return Returns the imported Geometry. + */ + public abstract Geometry execute(int import_flags, + Geometry.Type type, + String wkt_string, + ProgressTracker progress_tracker); - /** - * Performs the ImportFromWkt operation. - * - * @param import_flags Use the {@link WktImportFlags} interface. - * @param wkt_string The string holding the Geometry in wkt format. - * @return Returns the imported OGCStructure. - */ - public abstract OGCStructure executeOGC(int import_flags, - String wkt_string, ProgressTracker progress_tracker); + /** + * Performs the ImportFromWkt operation. + * + * @param import_flags Use the {@link WktImportFlags} interface. + * @param wkt_string The string holding the Geometry in wkt format. + * @return Returns the imported OGCStructure. + */ + public abstract OGCStructure executeOGC(int import_flags, + String wkt_string, ProgressTracker progress_tracker); - public static OperatorImportFromWkt local() { - return (OperatorImportFromWkt) OperatorFactoryLocal.getInstance() - .getOperator(Type.ImportFromWkt); - } + public static OperatorImportFromWkt local() { + return (OperatorImportFromWkt) OperatorFactoryLocal.getInstance() + .getOperator(Type.ImportFromWkt); + } } diff --git a/src/main/java/com/esri/core/geometry/OperatorImportFromWktCursor.java b/src/main/java/com/esri/core/geometry/OperatorImportFromWktCursor.java index 0c3b3f53..9866db0a 100644 --- a/src/main/java/com/esri/core/geometry/OperatorImportFromWktCursor.java +++ b/src/main/java/com/esri/core/geometry/OperatorImportFromWktCursor.java @@ -1,40 +1,46 @@ package com.esri.core.geometry; public class OperatorImportFromWktCursor extends GeometryCursor { - private StringCursor m_wktStringCursor; - private int m_importFlags; - - public OperatorImportFromWktCursor(int import_flags, StringCursor stringCursor) { - if (stringCursor == null) - throw new GeometryException("invalid argument"); - - m_importFlags = import_flags; - m_wktStringCursor = stringCursor; - } - - @Override - public boolean hasNext() { return m_wktStringCursor != null && m_wktStringCursor.hasNext(); } - - @Override - public Geometry next() { - if (hasNext()) { - return OperatorImportFromWkt.local().execute( - m_importFlags, - Geometry.Type.Unknown, - m_wktStringCursor.next(), - null); - } - return null; - } - - @Override - public long getGeometryID() { - return m_wktStringCursor.getID(); - } - - @Override - public SimpleStateEnum getSimpleState() { return m_wktStringCursor.getSimpleState(); } - - @Override - public String getFeatureID() { return m_wktStringCursor.getFeatureID(); } + private StringCursor m_wktStringCursor; + private int m_importFlags; + + public OperatorImportFromWktCursor(int import_flags, StringCursor stringCursor) { + if (stringCursor == null) + throw new GeometryException("invalid argument"); + + m_importFlags = import_flags; + m_wktStringCursor = stringCursor; + } + + @Override + public boolean hasNext() { + return m_wktStringCursor != null && m_wktStringCursor.hasNext(); + } + + @Override + public Geometry next() { + if (hasNext()) { + return OperatorImportFromWkt.local().execute( + m_importFlags, + Geometry.Type.Unknown, + m_wktStringCursor.next(), + null); + } + return null; + } + + @Override + public long getGeometryID() { + return m_wktStringCursor.getID(); + } + + @Override + public SimpleStateEnum getSimpleState() { + return m_wktStringCursor.getSimpleState(); + } + + @Override + public String getFeatureID() { + return m_wktStringCursor.getFeatureID(); + } } diff --git a/src/main/java/com/esri/core/geometry/OperatorImportFromWktLocal.java b/src/main/java/com/esri/core/geometry/OperatorImportFromWktLocal.java index 65b30c63..d2d67bd7 100644 --- a/src/main/java/com/esri/core/geometry/OperatorImportFromWktLocal.java +++ b/src/main/java/com/esri/core/geometry/OperatorImportFromWktLocal.java @@ -28,643 +28,643 @@ class OperatorImportFromWktLocal extends OperatorImportFromWkt { - @Override - public GeometryCursor execute(int import_flags, SimpleStringCursor wkt_stringCursor, ProgressTracker progress_tracker) { - return new OperatorImportFromWktCursor(import_flags, wkt_stringCursor); - } - - - @Override - public Geometry execute(int import_flags, Geometry.Type type, - String wkt_string, ProgressTracker progress_tracker) { - WktParser wkt_parser = new WktParser(wkt_string); - int current_token = wkt_parser.nextToken(); - return importFromWkt(import_flags, type, wkt_parser); - } - - @Override - public OGCStructure executeOGC(int import_flags, String wkt_string, - ProgressTracker progress_tracker) { - ArrayList stack = new ArrayList(0); - WktParser wkt_parser = new WktParser(wkt_string); - - OGCStructure root = new OGCStructure(); - root.m_structures = new ArrayList(0); - stack.add(root); // add dummy root - - while (wkt_parser.nextToken() != WktParser.WktToken.not_available) { - int current_token = wkt_parser.currentToken(); - - if (current_token == WktParser.WktToken.right_paren) { - stack.remove(stack.size() - 1); - continue; - } - - int ogc_type = current_token; - OGCStructure last = stack.get(stack.size() - 1); - - if (current_token == WktParser.WktToken.geometrycollection) { - current_token = wkt_parser.nextToken(); - - if (current_token == WktParser.WktToken.attribute_z - || current_token == WktParser.WktToken.attribute_m - || current_token == WktParser.WktToken.attribute_zm) - wkt_parser.nextToken(); - - OGCStructure next = new OGCStructure(); - next.m_type = ogc_type; - next.m_structures = new ArrayList(0); - last.m_structures.add(next); - - if (current_token != WktParser.WktToken.empty) - stack.add(next); - continue; - } - - Geometry geometry = importFromWkt(import_flags, - Geometry.Type.Unknown, wkt_parser); - - OGCStructure leaf = new OGCStructure(); - leaf.m_type = ogc_type; - leaf.m_geometry = geometry; - last.m_structures.add(leaf); - } - - return root; - } - - static Geometry importFromWkt(int import_flags, Geometry.Type type, - WktParser wkt_parser) { - int current_token = wkt_parser.currentToken(); - - switch (current_token) { - case WktParser.WktToken.multipolygon: - if (type != Geometry.Type.Polygon && type != Geometry.Type.Unknown) - throw new IllegalArgumentException("invalid shapetype"); - return polygonTaggedText(true, import_flags, wkt_parser); - - case WktParser.WktToken.multilinestring: - if (type != Geometry.Type.Polyline && type != Geometry.Type.Unknown) - throw new IllegalArgumentException("invalid shapetype"); - return lineStringTaggedText(true, import_flags, wkt_parser); - - case WktParser.WktToken.multipoint: - if (type != Geometry.Type.MultiPoint - && type != Geometry.Type.Unknown) - throw new IllegalArgumentException("invalid shapetype"); - return multiPointTaggedText(import_flags, wkt_parser); - - case WktParser.WktToken.polygon: - if (type != Geometry.Type.Polygon && type != Geometry.Type.Unknown) - throw new IllegalArgumentException("invalid shapetype"); - return polygonTaggedText(false, import_flags, wkt_parser); - - case WktParser.WktToken.linestring: - if (type != Geometry.Type.Polyline && type != Geometry.Type.Unknown) - throw new IllegalArgumentException("invalid shapetype"); - return lineStringTaggedText(false, import_flags, wkt_parser); - - case WktParser.WktToken.point: - if (type != Geometry.Type.Point && type != Geometry.Type.Unknown) - throw new IllegalArgumentException("invalid shapetype"); - return pointTaggedText(import_flags, wkt_parser); - - default: - break; // warning fix - } - - return null; - } - - static Geometry polygonTaggedText(boolean b_multi_polygon, - int import_flags, WktParser wkt_parser) { - MultiPath multi_path; - MultiPathImpl multi_path_impl; - AttributeStreamOfDbl zs = null; - AttributeStreamOfDbl ms = null; - AttributeStreamOfDbl position; - AttributeStreamOfInt32 paths; - AttributeStreamOfInt8 path_flags; - - position = (AttributeStreamOfDbl) AttributeStreamBase - .createDoubleStream(0); - paths = (AttributeStreamOfInt32) AttributeStreamBase.createIndexStream( - 1, 0); - path_flags = (AttributeStreamOfInt8) AttributeStreamBase - .createByteStream(1, (byte) 0); - - multi_path = new Polygon(); - multi_path_impl = (MultiPathImpl) multi_path._getImpl(); - - int current_token = wkt_parser.nextToken(); - - if (current_token == WktParser.WktToken.attribute_z) { - zs = (AttributeStreamOfDbl) AttributeStreamBase.createDoubleStream( - 0, NumberUtils.TheNaN); - multi_path_impl.addAttribute(VertexDescription.Semantics.Z); - wkt_parser.nextToken(); - } else if (current_token == WktParser.WktToken.attribute_m) { - ms = (AttributeStreamOfDbl) AttributeStreamBase.createDoubleStream( - 0, NumberUtils.TheNaN); - multi_path_impl.addAttribute(VertexDescription.Semantics.M); - wkt_parser.nextToken(); - } else if (current_token == WktParser.WktToken.attribute_zm) { - zs = (AttributeStreamOfDbl) AttributeStreamBase.createDoubleStream( - 0, NumberUtils.TheNaN); - ms = (AttributeStreamOfDbl) AttributeStreamBase.createDoubleStream( - 0, NumberUtils.TheNaN); - multi_path_impl.addAttribute(VertexDescription.Semantics.Z); - multi_path_impl.addAttribute(VertexDescription.Semantics.M); - wkt_parser.nextToken(); - } - - int point_count; - - if (b_multi_polygon) - point_count = multiPolygonText(zs, ms, position, paths, path_flags, - wkt_parser); - else - point_count = polygonText(zs, ms, position, paths, path_flags, 0, - wkt_parser); - - if (point_count != 0) { - assert (2 * point_count == position.size()); - multi_path_impl.setAttributeStreamRef( - VertexDescription.Semantics.POSITION, position); - multi_path_impl.setPathStreamRef(paths); - multi_path_impl.setPathFlagsStreamRef(path_flags); - - if (zs != null) - multi_path_impl.setAttributeStreamRef( - VertexDescription.Semantics.Z, zs); - - if (ms != null) - multi_path_impl.setAttributeStreamRef( - VertexDescription.Semantics.M, ms); - - multi_path_impl.notifyModified(MultiPathImpl.DirtyFlags.DirtyAll); - - AttributeStreamOfInt8 path_flags_clone = new AttributeStreamOfInt8( - path_flags); - - for (int i = 0; i < path_flags_clone.size() - 1; i++) { - if (((int) path_flags_clone.read(i) & (int) PathFlags.enumOGCStartPolygon) != 0) {// Should - // be - // clockwise - if (!InternalUtils.isClockwiseRing(multi_path_impl, i)) - multi_path_impl.reversePath(i); // make clockwise - } else {// Should be counter-clockwise - if (InternalUtils.isClockwiseRing(multi_path_impl, i)) - multi_path_impl.reversePath(i); // make - // counter-clockwise - } - } - - multi_path_impl.setPathFlagsStreamRef(path_flags_clone); - } - - if ((import_flags & (int) WktImportFlags.wktImportNonTrusted) == 0) - multi_path_impl.setIsSimple(MultiPathImpl.GeometryXSimple.Weak, - 0.0, false); - - multi_path_impl.setDirtyOGCFlags(false); - - return multi_path; - } - - static Geometry lineStringTaggedText(boolean b_multi_linestring, - int import_flags, WktParser wkt_parser) { - MultiPath multi_path; - MultiPathImpl multi_path_impl; - AttributeStreamOfDbl zs = null; - AttributeStreamOfDbl ms = null; - AttributeStreamOfDbl position; - AttributeStreamOfInt32 paths; - AttributeStreamOfInt8 path_flags; - - position = (AttributeStreamOfDbl) AttributeStreamBase - .createDoubleStream(0); - paths = (AttributeStreamOfInt32) AttributeStreamBase.createIndexStream( - 1, 0); - path_flags = (AttributeStreamOfInt8) AttributeStreamBase - .createByteStream(1, (byte) 0); - - multi_path = new Polyline(); - multi_path_impl = (MultiPathImpl) multi_path._getImpl(); - - int current_token = wkt_parser.nextToken(); - - if (current_token == WktParser.WktToken.attribute_z) { - zs = (AttributeStreamOfDbl) AttributeStreamBase.createDoubleStream( - 0, NumberUtils.TheNaN); - multi_path_impl.addAttribute(VertexDescription.Semantics.Z); - wkt_parser.nextToken(); - } else if (current_token == WktParser.WktToken.attribute_m) { - ms = (AttributeStreamOfDbl) AttributeStreamBase.createDoubleStream( - 0, NumberUtils.TheNaN); - multi_path_impl.addAttribute(VertexDescription.Semantics.M); - wkt_parser.nextToken(); - } else if (current_token == WktParser.WktToken.attribute_zm) { - zs = (AttributeStreamOfDbl) AttributeStreamBase.createDoubleStream( - 0, NumberUtils.TheNaN); - ms = (AttributeStreamOfDbl) AttributeStreamBase.createDoubleStream( - 0, NumberUtils.TheNaN); - multi_path_impl.addAttribute(VertexDescription.Semantics.Z); - multi_path_impl.addAttribute(VertexDescription.Semantics.M); - wkt_parser.nextToken(); - } - - int point_count; - - if (b_multi_linestring) - point_count = multiLineStringText(zs, ms, position, paths, - path_flags, wkt_parser); - else - point_count = lineStringText(false, zs, ms, position, paths, - path_flags, wkt_parser); - - if (point_count != 0) { - assert (2 * point_count == position.size()); - multi_path_impl.setAttributeStreamRef( - VertexDescription.Semantics.POSITION, position); - multi_path_impl.setPathStreamRef(paths); - multi_path_impl.setPathFlagsStreamRef(path_flags); - - if (zs != null) - multi_path_impl.setAttributeStreamRef( - VertexDescription.Semantics.Z, zs); - - if (ms != null) - multi_path_impl.setAttributeStreamRef( - VertexDescription.Semantics.M, ms); - - multi_path_impl.notifyModified(MultiPathImpl.DirtyFlags.DirtyAll); - } - - return multi_path; - } - - static Geometry multiPointTaggedText(int import_flags, WktParser wkt_parser) { - MultiPoint multi_point; - MultiPointImpl multi_point_impl; - AttributeStreamOfDbl zs = null; - AttributeStreamOfDbl ms = null; - AttributeStreamOfDbl position; - - position = (AttributeStreamOfDbl) AttributeStreamBase - .createDoubleStream(0); - - multi_point = new MultiPoint(); - multi_point_impl = (MultiPointImpl) multi_point._getImpl(); - - int current_token = wkt_parser.nextToken(); - - if (current_token == WktParser.WktToken.attribute_z) { - zs = (AttributeStreamOfDbl) AttributeStreamBase.createDoubleStream( - 0, NumberUtils.TheNaN); - multi_point_impl.addAttribute(VertexDescription.Semantics.Z); - wkt_parser.nextToken(); - } else if (current_token == WktParser.WktToken.attribute_m) { - ms = (AttributeStreamOfDbl) AttributeStreamBase.createDoubleStream( - 0, NumberUtils.TheNaN); - multi_point_impl.addAttribute(VertexDescription.Semantics.M); - wkt_parser.nextToken(); - } else if (current_token == WktParser.WktToken.attribute_zm) { - zs = (AttributeStreamOfDbl) AttributeStreamBase.createDoubleStream( - 0, NumberUtils.TheNaN); - ms = (AttributeStreamOfDbl) AttributeStreamBase.createDoubleStream( - 0, NumberUtils.TheNaN); - multi_point_impl.addAttribute(VertexDescription.Semantics.Z); - multi_point_impl.addAttribute(VertexDescription.Semantics.M); - wkt_parser.nextToken(); - } - - int point_count = multiPointText(zs, ms, position, wkt_parser); - - if (point_count != 0) { - assert (2 * point_count == position.size()); - multi_point_impl.resize(point_count); - multi_point_impl.setAttributeStreamRef( - VertexDescription.Semantics.POSITION, position); - - if (zs != null) - multi_point_impl.setAttributeStreamRef( - VertexDescription.Semantics.Z, zs); - - if (ms != null) - multi_point_impl.setAttributeStreamRef( - VertexDescription.Semantics.M, ms); - - multi_point_impl.notifyModified(MultiPointImpl.DirtyFlags.DirtyAll); - } - - return multi_point; - } - - static Geometry pointTaggedText(int import_flags, WktParser wkt_parser) { - Point point = new Point(); - - int current_token = wkt_parser.nextToken(); - - if (current_token == WktParser.WktToken.attribute_z) { - point.addAttribute(VertexDescription.Semantics.Z); - wkt_parser.nextToken(); - } else if (current_token == WktParser.WktToken.attribute_m) { - point.addAttribute(VertexDescription.Semantics.M); - wkt_parser.nextToken(); - } else if (current_token == WktParser.WktToken.attribute_zm) { - point.addAttribute(VertexDescription.Semantics.Z); - point.addAttribute(VertexDescription.Semantics.M); - wkt_parser.nextToken(); - } - // At start of PointText + @Override + public GeometryCursor execute(int import_flags, SimpleStringCursor wkt_stringCursor, ProgressTracker progress_tracker) { + return new OperatorImportFromWktCursor(import_flags, wkt_stringCursor); + } + + + @Override + public Geometry execute(int import_flags, Geometry.Type type, + String wkt_string, ProgressTracker progress_tracker) { + WktParser wkt_parser = new WktParser(wkt_string); + int current_token = wkt_parser.nextToken(); + return importFromWkt(import_flags, type, wkt_parser); + } + + @Override + public OGCStructure executeOGC(int import_flags, String wkt_string, + ProgressTracker progress_tracker) { + ArrayList stack = new ArrayList(0); + WktParser wkt_parser = new WktParser(wkt_string); + + OGCStructure root = new OGCStructure(); + root.m_structures = new ArrayList(0); + stack.add(root); // add dummy root + + while (wkt_parser.nextToken() != WktParser.WktToken.not_available) { + int current_token = wkt_parser.currentToken(); + + if (current_token == WktParser.WktToken.right_paren) { + stack.remove(stack.size() - 1); + continue; + } + + int ogc_type = current_token; + OGCStructure last = stack.get(stack.size() - 1); + + if (current_token == WktParser.WktToken.geometrycollection) { + current_token = wkt_parser.nextToken(); + + if (current_token == WktParser.WktToken.attribute_z + || current_token == WktParser.WktToken.attribute_m + || current_token == WktParser.WktToken.attribute_zm) + wkt_parser.nextToken(); + + OGCStructure next = new OGCStructure(); + next.m_type = ogc_type; + next.m_structures = new ArrayList(0); + last.m_structures.add(next); + + if (current_token != WktParser.WktToken.empty) + stack.add(next); + continue; + } + + Geometry geometry = importFromWkt(import_flags, + Geometry.Type.Unknown, wkt_parser); + + OGCStructure leaf = new OGCStructure(); + leaf.m_type = ogc_type; + leaf.m_geometry = geometry; + last.m_structures.add(leaf); + } + + return root; + } + + static Geometry importFromWkt(int import_flags, Geometry.Type type, + WktParser wkt_parser) { + int current_token = wkt_parser.currentToken(); + + switch (current_token) { + case WktParser.WktToken.multipolygon: + if (type != Geometry.Type.Polygon && type != Geometry.Type.Unknown) + throw new IllegalArgumentException("invalid shapetype"); + return polygonTaggedText(true, import_flags, wkt_parser); + + case WktParser.WktToken.multilinestring: + if (type != Geometry.Type.Polyline && type != Geometry.Type.Unknown) + throw new IllegalArgumentException("invalid shapetype"); + return lineStringTaggedText(true, import_flags, wkt_parser); + + case WktParser.WktToken.multipoint: + if (type != Geometry.Type.MultiPoint + && type != Geometry.Type.Unknown) + throw new IllegalArgumentException("invalid shapetype"); + return multiPointTaggedText(import_flags, wkt_parser); + + case WktParser.WktToken.polygon: + if (type != Geometry.Type.Polygon && type != Geometry.Type.Unknown) + throw new IllegalArgumentException("invalid shapetype"); + return polygonTaggedText(false, import_flags, wkt_parser); + + case WktParser.WktToken.linestring: + if (type != Geometry.Type.Polyline && type != Geometry.Type.Unknown) + throw new IllegalArgumentException("invalid shapetype"); + return lineStringTaggedText(false, import_flags, wkt_parser); + + case WktParser.WktToken.point: + if (type != Geometry.Type.Point && type != Geometry.Type.Unknown) + throw new IllegalArgumentException("invalid shapetype"); + return pointTaggedText(import_flags, wkt_parser); + + default: + break; // warning fix + } + + return null; + } + + static Geometry polygonTaggedText(boolean b_multi_polygon, + int import_flags, WktParser wkt_parser) { + MultiPath multi_path; + MultiPathImpl multi_path_impl; + AttributeStreamOfDbl zs = null; + AttributeStreamOfDbl ms = null; + AttributeStreamOfDbl position; + AttributeStreamOfInt32 paths; + AttributeStreamOfInt8 path_flags; + + position = (AttributeStreamOfDbl) AttributeStreamBase + .createDoubleStream(0); + paths = (AttributeStreamOfInt32) AttributeStreamBase.createIndexStream( + 1, 0); + path_flags = (AttributeStreamOfInt8) AttributeStreamBase + .createByteStream(1, (byte) 0); + + multi_path = new Polygon(); + multi_path_impl = (MultiPathImpl) multi_path._getImpl(); + + int current_token = wkt_parser.nextToken(); + + if (current_token == WktParser.WktToken.attribute_z) { + zs = (AttributeStreamOfDbl) AttributeStreamBase.createDoubleStream( + 0, NumberUtils.TheNaN); + multi_path_impl.addAttribute(VertexDescription.Semantics.Z); + wkt_parser.nextToken(); + } else if (current_token == WktParser.WktToken.attribute_m) { + ms = (AttributeStreamOfDbl) AttributeStreamBase.createDoubleStream( + 0, NumberUtils.TheNaN); + multi_path_impl.addAttribute(VertexDescription.Semantics.M); + wkt_parser.nextToken(); + } else if (current_token == WktParser.WktToken.attribute_zm) { + zs = (AttributeStreamOfDbl) AttributeStreamBase.createDoubleStream( + 0, NumberUtils.TheNaN); + ms = (AttributeStreamOfDbl) AttributeStreamBase.createDoubleStream( + 0, NumberUtils.TheNaN); + multi_path_impl.addAttribute(VertexDescription.Semantics.Z); + multi_path_impl.addAttribute(VertexDescription.Semantics.M); + wkt_parser.nextToken(); + } + + int point_count; + + if (b_multi_polygon) + point_count = multiPolygonText(zs, ms, position, paths, path_flags, + wkt_parser); + else + point_count = polygonText(zs, ms, position, paths, path_flags, 0, + wkt_parser); + + if (point_count != 0) { + assert (2 * point_count == position.size()); + multi_path_impl.setAttributeStreamRef( + VertexDescription.Semantics.POSITION, position); + multi_path_impl.setPathStreamRef(paths); + multi_path_impl.setPathFlagsStreamRef(path_flags); + + if (zs != null) + multi_path_impl.setAttributeStreamRef( + VertexDescription.Semantics.Z, zs); + + if (ms != null) + multi_path_impl.setAttributeStreamRef( + VertexDescription.Semantics.M, ms); + + multi_path_impl.notifyModified(MultiPathImpl.DirtyFlags.DirtyAll); + + AttributeStreamOfInt8 path_flags_clone = new AttributeStreamOfInt8( + path_flags); + + for (int i = 0; i < path_flags_clone.size() - 1; i++) { + if (((int) path_flags_clone.read(i) & (int) PathFlags.enumOGCStartPolygon) != 0) {// Should + // be + // clockwise + if (!InternalUtils.isClockwiseRing(multi_path_impl, i)) + multi_path_impl.reversePath(i); // make clockwise + } else {// Should be counter-clockwise + if (InternalUtils.isClockwiseRing(multi_path_impl, i)) + multi_path_impl.reversePath(i); // make + // counter-clockwise + } + } + + multi_path_impl.setPathFlagsStreamRef(path_flags_clone); + } + + if ((import_flags & (int) WktImportFlags.wktImportNonTrusted) == 0) + multi_path_impl.setIsSimple(MultiPathImpl.GeometryXSimple.Weak, + 0.0, false); + + multi_path_impl.setDirtyOGCFlags(false); + + return multi_path; + } + + static Geometry lineStringTaggedText(boolean b_multi_linestring, + int import_flags, WktParser wkt_parser) { + MultiPath multi_path; + MultiPathImpl multi_path_impl; + AttributeStreamOfDbl zs = null; + AttributeStreamOfDbl ms = null; + AttributeStreamOfDbl position; + AttributeStreamOfInt32 paths; + AttributeStreamOfInt8 path_flags; + + position = (AttributeStreamOfDbl) AttributeStreamBase + .createDoubleStream(0); + paths = (AttributeStreamOfInt32) AttributeStreamBase.createIndexStream( + 1, 0); + path_flags = (AttributeStreamOfInt8) AttributeStreamBase + .createByteStream(1, (byte) 0); + + multi_path = new Polyline(); + multi_path_impl = (MultiPathImpl) multi_path._getImpl(); + + int current_token = wkt_parser.nextToken(); + + if (current_token == WktParser.WktToken.attribute_z) { + zs = (AttributeStreamOfDbl) AttributeStreamBase.createDoubleStream( + 0, NumberUtils.TheNaN); + multi_path_impl.addAttribute(VertexDescription.Semantics.Z); + wkt_parser.nextToken(); + } else if (current_token == WktParser.WktToken.attribute_m) { + ms = (AttributeStreamOfDbl) AttributeStreamBase.createDoubleStream( + 0, NumberUtils.TheNaN); + multi_path_impl.addAttribute(VertexDescription.Semantics.M); + wkt_parser.nextToken(); + } else if (current_token == WktParser.WktToken.attribute_zm) { + zs = (AttributeStreamOfDbl) AttributeStreamBase.createDoubleStream( + 0, NumberUtils.TheNaN); + ms = (AttributeStreamOfDbl) AttributeStreamBase.createDoubleStream( + 0, NumberUtils.TheNaN); + multi_path_impl.addAttribute(VertexDescription.Semantics.Z); + multi_path_impl.addAttribute(VertexDescription.Semantics.M); + wkt_parser.nextToken(); + } + + int point_count; + + if (b_multi_linestring) + point_count = multiLineStringText(zs, ms, position, paths, + path_flags, wkt_parser); + else + point_count = lineStringText(false, zs, ms, position, paths, + path_flags, wkt_parser); + + if (point_count != 0) { + assert (2 * point_count == position.size()); + multi_path_impl.setAttributeStreamRef( + VertexDescription.Semantics.POSITION, position); + multi_path_impl.setPathStreamRef(paths); + multi_path_impl.setPathFlagsStreamRef(path_flags); + + if (zs != null) + multi_path_impl.setAttributeStreamRef( + VertexDescription.Semantics.Z, zs); + + if (ms != null) + multi_path_impl.setAttributeStreamRef( + VertexDescription.Semantics.M, ms); + + multi_path_impl.notifyModified(MultiPathImpl.DirtyFlags.DirtyAll); + } + + return multi_path; + } + + static Geometry multiPointTaggedText(int import_flags, WktParser wkt_parser) { + MultiPoint multi_point; + MultiPointImpl multi_point_impl; + AttributeStreamOfDbl zs = null; + AttributeStreamOfDbl ms = null; + AttributeStreamOfDbl position; + + position = (AttributeStreamOfDbl) AttributeStreamBase + .createDoubleStream(0); + + multi_point = new MultiPoint(); + multi_point_impl = (MultiPointImpl) multi_point._getImpl(); + + int current_token = wkt_parser.nextToken(); + + if (current_token == WktParser.WktToken.attribute_z) { + zs = (AttributeStreamOfDbl) AttributeStreamBase.createDoubleStream( + 0, NumberUtils.TheNaN); + multi_point_impl.addAttribute(VertexDescription.Semantics.Z); + wkt_parser.nextToken(); + } else if (current_token == WktParser.WktToken.attribute_m) { + ms = (AttributeStreamOfDbl) AttributeStreamBase.createDoubleStream( + 0, NumberUtils.TheNaN); + multi_point_impl.addAttribute(VertexDescription.Semantics.M); + wkt_parser.nextToken(); + } else if (current_token == WktParser.WktToken.attribute_zm) { + zs = (AttributeStreamOfDbl) AttributeStreamBase.createDoubleStream( + 0, NumberUtils.TheNaN); + ms = (AttributeStreamOfDbl) AttributeStreamBase.createDoubleStream( + 0, NumberUtils.TheNaN); + multi_point_impl.addAttribute(VertexDescription.Semantics.Z); + multi_point_impl.addAttribute(VertexDescription.Semantics.M); + wkt_parser.nextToken(); + } + + int point_count = multiPointText(zs, ms, position, wkt_parser); + + if (point_count != 0) { + assert (2 * point_count == position.size()); + multi_point_impl.resize(point_count); + multi_point_impl.setAttributeStreamRef( + VertexDescription.Semantics.POSITION, position); + + if (zs != null) + multi_point_impl.setAttributeStreamRef( + VertexDescription.Semantics.Z, zs); + + if (ms != null) + multi_point_impl.setAttributeStreamRef( + VertexDescription.Semantics.M, ms); + + multi_point_impl.notifyModified(MultiPointImpl.DirtyFlags.DirtyAll); + } + + return multi_point; + } + + static Geometry pointTaggedText(int import_flags, WktParser wkt_parser) { + Point point = new Point(); + + int current_token = wkt_parser.nextToken(); + + if (current_token == WktParser.WktToken.attribute_z) { + point.addAttribute(VertexDescription.Semantics.Z); + wkt_parser.nextToken(); + } else if (current_token == WktParser.WktToken.attribute_m) { + point.addAttribute(VertexDescription.Semantics.M); + wkt_parser.nextToken(); + } else if (current_token == WktParser.WktToken.attribute_zm) { + point.addAttribute(VertexDescription.Semantics.Z); + point.addAttribute(VertexDescription.Semantics.M); + wkt_parser.nextToken(); + } + // At start of PointText - current_token = wkt_parser.currentToken(); + current_token = wkt_parser.currentToken(); - if (current_token != WktParser.WktToken.empty) { - wkt_parser.nextToken(); + if (current_token != WktParser.WktToken.empty) { + wkt_parser.nextToken(); - double x = wkt_parser.currentNumericLiteral(); - wkt_parser.nextToken(); + double x = wkt_parser.currentNumericLiteral(); + wkt_parser.nextToken(); - double y = wkt_parser.currentNumericLiteral(); - wkt_parser.nextToken(); + double y = wkt_parser.currentNumericLiteral(); + wkt_parser.nextToken(); - point.setXY(x, y); + point.setXY(x, y); - if (wkt_parser.hasZs()) { - double z = wkt_parser.currentNumericLiteral(); - wkt_parser.nextToken(); - point.setZ(z); - } + if (wkt_parser.hasZs()) { + double z = wkt_parser.currentNumericLiteral(); + wkt_parser.nextToken(); + point.setZ(z); + } - if (wkt_parser.hasMs()) { - double m = wkt_parser.currentNumericLiteral(); - wkt_parser.nextToken(); - point.setM(m); - } - } + if (wkt_parser.hasMs()) { + double m = wkt_parser.currentNumericLiteral(); + wkt_parser.nextToken(); + point.setM(m); + } + } - return point; - } + return point; + } - static int multiPolygonText(AttributeStreamOfDbl zs, - AttributeStreamOfDbl ms, AttributeStreamOfDbl position, - AttributeStreamOfInt32 paths, AttributeStreamOfInt8 path_flags, - WktParser wkt_parser) { - // At start of MultiPolygonText + static int multiPolygonText(AttributeStreamOfDbl zs, + AttributeStreamOfDbl ms, AttributeStreamOfDbl position, + AttributeStreamOfInt32 paths, AttributeStreamOfInt8 path_flags, + WktParser wkt_parser) { + // At start of MultiPolygonText - int current_token = wkt_parser.currentToken(); + int current_token = wkt_parser.currentToken(); - int total_point_count = 0; + int total_point_count = 0; - if (current_token == WktParser.WktToken.empty) - return total_point_count; + if (current_token == WktParser.WktToken.empty) + return total_point_count; - current_token = wkt_parser.nextToken(); + current_token = wkt_parser.nextToken(); - while (current_token != WktParser.WktToken.right_paren) { - // At start of PolygonText + while (current_token != WktParser.WktToken.right_paren) { + // At start of PolygonText - total_point_count = polygonText(zs, ms, position, paths, - path_flags, total_point_count, wkt_parser); - current_token = wkt_parser.nextToken(); - } + total_point_count = polygonText(zs, ms, position, paths, + path_flags, total_point_count, wkt_parser); + current_token = wkt_parser.nextToken(); + } - return total_point_count; - } + return total_point_count; + } - static int multiLineStringText(AttributeStreamOfDbl zs, - AttributeStreamOfDbl ms, AttributeStreamOfDbl position, - AttributeStreamOfInt32 paths, AttributeStreamOfInt8 path_flags, - WktParser wkt_parser) { - // At start of MultiLineStringText + static int multiLineStringText(AttributeStreamOfDbl zs, + AttributeStreamOfDbl ms, AttributeStreamOfDbl position, + AttributeStreamOfInt32 paths, AttributeStreamOfInt8 path_flags, + WktParser wkt_parser) { + // At start of MultiLineStringText - int current_token = wkt_parser.currentToken(); + int current_token = wkt_parser.currentToken(); - int total_point_count = 0; + int total_point_count = 0; - if (current_token == WktParser.WktToken.empty) - return total_point_count; + if (current_token == WktParser.WktToken.empty) + return total_point_count; - current_token = wkt_parser.nextToken(); + current_token = wkt_parser.nextToken(); - while (current_token != WktParser.WktToken.right_paren) { - // At start of LineStringText + while (current_token != WktParser.WktToken.right_paren) { + // At start of LineStringText - int point_count = lineStringText(false, zs, ms, position, paths, - path_flags, wkt_parser); - total_point_count += point_count; + int point_count = lineStringText(false, zs, ms, position, paths, + path_flags, wkt_parser); + total_point_count += point_count; - current_token = wkt_parser.nextToken(); - } + current_token = wkt_parser.nextToken(); + } - return total_point_count; - } + return total_point_count; + } - static int multiPointText(AttributeStreamOfDbl zs, AttributeStreamOfDbl ms, - AttributeStreamOfDbl position, WktParser wkt_parser) { - // At start of MultiPointText + static int multiPointText(AttributeStreamOfDbl zs, AttributeStreamOfDbl ms, + AttributeStreamOfDbl position, WktParser wkt_parser) { + // At start of MultiPointText - int current_token = wkt_parser.currentToken(); + int current_token = wkt_parser.currentToken(); - int point_count = 0; + int point_count = 0; - if (current_token == WktParser.WktToken.empty) - return point_count; + if (current_token == WktParser.WktToken.empty) + return point_count; - current_token = wkt_parser.nextToken(); + current_token = wkt_parser.nextToken(); - while (current_token != WktParser.WktToken.right_paren) { - // At start of PointText + while (current_token != WktParser.WktToken.right_paren) { + // At start of PointText - point_count += pointText(zs, ms, position, wkt_parser); + point_count += pointText(zs, ms, position, wkt_parser); - if (current_token == WktParser.WktToken.left_paren - || current_token == WktParser.WktToken.empty) - current_token = wkt_parser.nextToken(); // ogc standard - else - current_token = wkt_parser.currentToken(); // not ogc standard. - // treat as - // linestring - } + if (current_token == WktParser.WktToken.left_paren + || current_token == WktParser.WktToken.empty) + current_token = wkt_parser.nextToken(); // ogc standard + else + current_token = wkt_parser.currentToken(); // not ogc standard. + // treat as + // linestring + } - return point_count; - } + return point_count; + } - static int polygonText(AttributeStreamOfDbl zs, AttributeStreamOfDbl ms, - AttributeStreamOfDbl position, AttributeStreamOfInt32 paths, - AttributeStreamOfInt8 path_flags, int total_point_count, - WktParser wkt_parser) { - // At start of PolygonText + static int polygonText(AttributeStreamOfDbl zs, AttributeStreamOfDbl ms, + AttributeStreamOfDbl position, AttributeStreamOfInt32 paths, + AttributeStreamOfInt8 path_flags, int total_point_count, + WktParser wkt_parser) { + // At start of PolygonText - int current_token = wkt_parser.currentToken(); + int current_token = wkt_parser.currentToken(); - if (current_token == WktParser.WktToken.empty) - return total_point_count; + if (current_token == WktParser.WktToken.empty) + return total_point_count; - boolean b_first_line_string = true; + boolean b_first_line_string = true; - current_token = wkt_parser.nextToken(); + current_token = wkt_parser.nextToken(); - while (current_token != WktParser.WktToken.right_paren) { - // At start of LineStringText + while (current_token != WktParser.WktToken.right_paren) { + // At start of LineStringText - int point_count = lineStringText(true, zs, ms, position, paths, - path_flags, wkt_parser); + int point_count = lineStringText(true, zs, ms, position, paths, + path_flags, wkt_parser); - if (point_count != 0) { - if (b_first_line_string) { - b_first_line_string = false; - path_flags.setBits(path_flags.size() - 2, - (byte) PathFlags.enumOGCStartPolygon); - } + if (point_count != 0) { + if (b_first_line_string) { + b_first_line_string = false; + path_flags.setBits(path_flags.size() - 2, + (byte) PathFlags.enumOGCStartPolygon); + } - path_flags.setBits(path_flags.size() - 2, - (byte) PathFlags.enumClosed); - total_point_count += point_count; - } + path_flags.setBits(path_flags.size() - 2, + (byte) PathFlags.enumClosed); + total_point_count += point_count; + } - current_token = wkt_parser.nextToken(); - } + current_token = wkt_parser.nextToken(); + } - return total_point_count; - } + return total_point_count; + } - static int lineStringText(boolean b_ring, AttributeStreamOfDbl zs, - AttributeStreamOfDbl ms, AttributeStreamOfDbl position, - AttributeStreamOfInt32 paths, AttributeStreamOfInt8 path_flags, - WktParser wkt_parser) { - // At start of LineStringText + static int lineStringText(boolean b_ring, AttributeStreamOfDbl zs, + AttributeStreamOfDbl ms, AttributeStreamOfDbl position, + AttributeStreamOfInt32 paths, AttributeStreamOfInt8 path_flags, + WktParser wkt_parser) { + // At start of LineStringText - int current_token = wkt_parser.currentToken(); + int current_token = wkt_parser.currentToken(); - int point_count = 0; + int point_count = 0; - if (current_token == WktParser.WktToken.empty) - return point_count; + if (current_token == WktParser.WktToken.empty) + return point_count; - boolean b_start_path = true; - double startx = NumberUtils.TheNaN; - double starty = NumberUtils.TheNaN; - double startz = NumberUtils.TheNaN; - double startm = NumberUtils.TheNaN; + boolean b_start_path = true; + double startx = NumberUtils.TheNaN; + double starty = NumberUtils.TheNaN; + double startz = NumberUtils.TheNaN; + double startm = NumberUtils.TheNaN; - current_token = wkt_parser.nextToken(); + current_token = wkt_parser.nextToken(); - while (current_token != WktParser.WktToken.right_paren) { - // At start of x + while (current_token != WktParser.WktToken.right_paren) { + // At start of x - double x = wkt_parser.currentNumericLiteral(); - wkt_parser.nextToken(); + double x = wkt_parser.currentNumericLiteral(); + wkt_parser.nextToken(); - double y = wkt_parser.currentNumericLiteral(); - wkt_parser.nextToken(); + double y = wkt_parser.currentNumericLiteral(); + wkt_parser.nextToken(); - double z = NumberUtils.TheNaN, m = NumberUtils.TheNaN; + double z = NumberUtils.TheNaN, m = NumberUtils.TheNaN; - if (wkt_parser.hasZs()) { - z = wkt_parser.currentNumericLiteral(); - wkt_parser.nextToken(); - } + if (wkt_parser.hasZs()) { + z = wkt_parser.currentNumericLiteral(); + wkt_parser.nextToken(); + } - if (wkt_parser.hasMs()) { - m = wkt_parser.currentNumericLiteral(); - wkt_parser.nextToken(); - } + if (wkt_parser.hasMs()) { + m = wkt_parser.currentNumericLiteral(); + wkt_parser.nextToken(); + } - current_token = wkt_parser.currentToken(); - boolean b_add_point = true; + current_token = wkt_parser.currentToken(); + boolean b_add_point = true; - if (b_ring && point_count >= 2 - && current_token == WktParser.WktToken.right_paren) { - // If the last point in the ring is not equal to the start - // point, then let's add it. + if (b_ring && point_count >= 2 + && current_token == WktParser.WktToken.right_paren) { + // If the last point in the ring is not equal to the start + // point, then let's add it. - if ((startx == x || (NumberUtils.isNaN(startx) && NumberUtils - .isNaN(x))) - && (starty == y || (NumberUtils.isNaN(starty) && NumberUtils - .isNaN(y))) - && (!wkt_parser.hasZs() || startz == z || (NumberUtils - .isNaN(startz) && NumberUtils.isNaN(z))) - && (!wkt_parser.hasMs() || startm == m || (NumberUtils - .isNaN(startm) && NumberUtils.isNaN(m)))) - b_add_point = false; - } + if ((startx == x || (NumberUtils.isNaN(startx) && NumberUtils + .isNaN(x))) + && (starty == y || (NumberUtils.isNaN(starty) && NumberUtils + .isNaN(y))) + && (!wkt_parser.hasZs() || startz == z || (NumberUtils + .isNaN(startz) && NumberUtils.isNaN(z))) + && (!wkt_parser.hasMs() || startm == m || (NumberUtils + .isNaN(startm) && NumberUtils.isNaN(m)))) + b_add_point = false; + } - if (b_add_point) { - if (b_start_path) { - b_start_path = false; - startx = x; - starty = y; - startz = z; - startm = m; - } + if (b_add_point) { + if (b_start_path) { + b_start_path = false; + startx = x; + starty = y; + startz = z; + startm = m; + } - point_count++; - addToStreams(zs, ms, position, x, y, z, m); - } - } + point_count++; + addToStreams(zs, ms, position, x, y, z, m); + } + } - if (point_count == 1) { - point_count++; - addToStreams(zs, ms, position, startx, starty, startz, startm); - } + if (point_count == 1) { + point_count++; + addToStreams(zs, ms, position, startx, starty, startz, startm); + } - paths.add(position.size() / 2); - path_flags.add((byte) 0); + paths.add(position.size() / 2); + path_flags.add((byte) 0); - return point_count; - } + return point_count; + } - static int pointText(AttributeStreamOfDbl zs, AttributeStreamOfDbl ms, - AttributeStreamOfDbl position, WktParser wkt_parser) { - // At start of PointText + static int pointText(AttributeStreamOfDbl zs, AttributeStreamOfDbl ms, + AttributeStreamOfDbl position, WktParser wkt_parser) { + // At start of PointText - int current_token = wkt_parser.currentToken(); + int current_token = wkt_parser.currentToken(); - if (current_token == WktParser.WktToken.empty) - return 0; + if (current_token == WktParser.WktToken.empty) + return 0; - if (current_token == WktParser.WktToken.left_paren) - wkt_parser.nextToken(); // ogc standard + if (current_token == WktParser.WktToken.left_paren) + wkt_parser.nextToken(); // ogc standard - // At start of x + // At start of x - double x = wkt_parser.currentNumericLiteral(); - wkt_parser.nextToken(); + double x = wkt_parser.currentNumericLiteral(); + wkt_parser.nextToken(); - double y = wkt_parser.currentNumericLiteral(); - wkt_parser.nextToken(); + double y = wkt_parser.currentNumericLiteral(); + wkt_parser.nextToken(); - double z = NumberUtils.TheNaN; - double m = NumberUtils.TheNaN; + double z = NumberUtils.TheNaN; + double m = NumberUtils.TheNaN; - if (zs != null) { - z = wkt_parser.currentNumericLiteral(); - wkt_parser.nextToken(); - } + if (zs != null) { + z = wkt_parser.currentNumericLiteral(); + wkt_parser.nextToken(); + } - if (ms != null) { - m = wkt_parser.currentNumericLiteral(); - wkt_parser.nextToken(); - } + if (ms != null) { + m = wkt_parser.currentNumericLiteral(); + wkt_parser.nextToken(); + } - addToStreams(zs, ms, position, x, y, z, m); + addToStreams(zs, ms, position, x, y, z, m); - return 1; - } + return 1; + } - static void addToStreams(AttributeStreamOfDbl zs, AttributeStreamOfDbl ms, - AttributeStreamOfDbl position, double x, double y, double z, - double m) { - position.add(x); - position.add(y); + static void addToStreams(AttributeStreamOfDbl zs, AttributeStreamOfDbl ms, + AttributeStreamOfDbl position, double x, double y, double z, + double m) { + position.add(x); + position.add(y); - if (zs != null) - zs.add(z); - - if (ms != null) - ms.add(m); - } + if (zs != null) + zs.add(z); + + if (ms != null) + ms.add(m); + } } diff --git a/src/main/java/com/esri/core/geometry/OperatorInternalRelationUtils.java b/src/main/java/com/esri/core/geometry/OperatorInternalRelationUtils.java index ea3c475a..899f7745 100644 --- a/src/main/java/com/esri/core/geometry/OperatorInternalRelationUtils.java +++ b/src/main/java/com/esri/core/geometry/OperatorInternalRelationUtils.java @@ -27,707 +27,707 @@ class OperatorInternalRelationUtils { - interface Relation { - public final int Unknown = 0; - public final int Contains = 1; - public final int Within = 2; - public final int Equals = 3; // == Within | Contains tests both within - // and contains - public final int Disjoint = 4; - public final int Touches = 8; - public final int Crosses = 16; - public final int Overlaps = 32; - - public final int NoThisRelation = 64; // returned when the relation is - // not satisified - public final int Intersects = 0x40000000;// this means not_disjoint. - // Used for early bailout - public final int IntersectsOrDisjoint = Intersects | Disjoint; - } - - public static int quickTest2D(Geometry geomA, Geometry geomB, - double tolerance, int testType) { - if (geomB.isEmpty() || geomA.isEmpty()) - return (int) Relation.Disjoint; - - int geomAtype = geomA.getType().value(); - int geomBtype = geomB.getType().value(); - - // We do not support segments directly for now. Convert to Polyline - Polyline autoPolyA; - if (Geometry.isSegment(geomAtype)) { - autoPolyA = new Polyline(geomA.getDescription()); - geomA = (Geometry) autoPolyA; - autoPolyA.addSegment((Segment) geomA, true); - } - - Polyline autoPolyB; - if (Geometry.isSegment(geomBtype)) { - autoPolyB = new Polyline(geomB.getDescription()); - geomB = (Geometry) autoPolyB; - autoPolyB.addSegment((Segment) geomB, true); - } - - // Now process GeometryxGeometry case by case - switch (geomAtype) { - case Geometry.GeometryType.Point: { - switch (geomBtype) { - case Geometry.GeometryType.Point: - return quickTest2DPointPoint((Point) geomA, (Point) geomB, - tolerance); - case Geometry.GeometryType.Envelope: - return reverseResult(quickTest2DEnvelopePoint((Envelope) geomB, - (Point) geomA, tolerance)); - case Geometry.GeometryType.MultiPoint: - return reverseResult(quickTest2DMultiPointPoint( - (MultiPoint) geomB, (Point) geomA, tolerance)); - case Geometry.GeometryType.Polyline: - return reverseResult(quickTest2DPolylinePoint((Polyline) geomB, - (Point) geomA, tolerance, testType)); - case Geometry.GeometryType.Polygon: - return reverseResult(quickTest2DPolygonPoint((Polygon) geomB, - (Point) geomA, tolerance)); - } - throw GeometryException.GeometryInternalError();// GEOMTHROW(internal_error);//what - // else? - } - case Geometry.GeometryType.Envelope: { - switch (geomBtype) { - case Geometry.GeometryType.Point: - return quickTest2DEnvelopePoint((Envelope) geomA, - (Point) geomB, tolerance); - case Geometry.GeometryType.Envelope: - return quickTest2DEnvelopeEnvelope((Envelope) geomA, - (Envelope) geomB, tolerance); - case Geometry.GeometryType.MultiPoint: - return reverseResult(quickTest2DMultiPointEnvelope( - (MultiPoint) geomB, (Envelope) geomA, tolerance, - testType)); - case Geometry.GeometryType.Polyline: - return reverseResult(quickTest2DPolylineEnvelope( - (Polyline) geomB, (Envelope) geomA, tolerance)); - case Geometry.GeometryType.Polygon: - return reverseResult(quickTest2DPolygonEnvelope( - (Polygon) geomB, (Envelope) geomA, tolerance)); - } - throw GeometryException.GeometryInternalError();// GEOMTHROW(internal_error);//what - // else? - } - case Geometry.GeometryType.MultiPoint: { - switch (geomBtype) { - case Geometry.GeometryType.Point: - return quickTest2DMultiPointPoint((MultiPoint) geomA, - (Point) geomB, tolerance); - case Geometry.GeometryType.Envelope: - return quickTest2DMultiPointEnvelope((MultiPoint) geomA, - (Envelope) geomB, tolerance, testType); - case Geometry.GeometryType.MultiPoint: - return quickTest2DMultiPointMultiPoint((MultiPoint) geomA, - (MultiPoint) geomB, tolerance, testType); - case Geometry.GeometryType.Polyline: - return reverseResult(quickTest2DPolylineMultiPoint( - (Polyline) geomB, (MultiPoint) geomA, tolerance)); - case Geometry.GeometryType.Polygon: - return reverseResult(quickTest2DPolygonMultiPoint( - (Polygon) geomB, (MultiPoint) geomA, tolerance)); - } - throw GeometryException.GeometryInternalError();// GEOMTHROW(internal_error);//what - // else? - } - case Geometry.GeometryType.Polyline: { - switch (geomBtype) { - case Geometry.GeometryType.Point: - return quickTest2DPolylinePoint((Polyline) geomA, - (Point) geomB, tolerance, testType); - case Geometry.GeometryType.Envelope: - return quickTest2DPolylineEnvelope((Polyline) geomA, - (Envelope) geomB, tolerance); - case Geometry.GeometryType.MultiPoint: - return quickTest2DPolylineMultiPoint((Polyline) geomA, - (MultiPoint) geomB, tolerance); - case Geometry.GeometryType.Polyline: - return quickTest2DPolylinePolyline((Polyline) geomA, - (Polyline) geomB, tolerance); - case Geometry.GeometryType.Polygon: - return reverseResult(quickTest2DPolygonPolyline( - (Polygon) geomB, (Polyline) geomA, tolerance)); - } - throw GeometryException.GeometryInternalError();// GEOMTHROW(internal_error);//what - // else? - } - case Geometry.GeometryType.Polygon: { - switch (geomBtype) { - case Geometry.GeometryType.Point: - return quickTest2DPolygonPoint((Polygon) geomA, (Point) geomB, - tolerance); - case Geometry.GeometryType.Envelope: - return quickTest2DPolygonEnvelope((Polygon) geomA, - (Envelope) geomB, tolerance); - case Geometry.GeometryType.MultiPoint: - return quickTest2DPolygonMultiPoint((Polygon) geomA, - (MultiPoint) geomB, tolerance); - case Geometry.GeometryType.Polyline: - return quickTest2DPolygonPolyline((Polygon) geomA, - (Polyline) geomB, tolerance); - case Geometry.GeometryType.Polygon: - return quickTest2DPolygonPolygon((Polygon) geomA, - (Polygon) geomB, tolerance); - } - throw GeometryException.GeometryInternalError();// GEOMTHROW(internal_error);//what - // else? - } - - default: - throw GeometryException.GeometryInternalError();// GEOMTHROW(internal_error);//what - // else? - // return 0; - } - } - - private static int quickTest2DPointPoint(Point geomA, Point geomB, - double tolerance) { - Point2D ptA = geomA.getXY(); - Point2D ptB = geomB.getXY(); - return quickTest2DPointPoint(ptA, ptB, tolerance); - } - - private static int quickTest2DPointPoint(Point2D ptA, Point2D ptB, - double tolerance) { - ptA.sub(ptB); - double len = ptA.sqrLength();// Should we test against 2*tol or tol? - if (len <= tolerance * tolerance)// Two points are equal if they are not - // Disjoint. We consider a point to - // be a disk of radius tolerance. - // Any intersection of two disks - // produces same disk. - return (int) Relation.Within | (int) Relation.Contains;// ==Equals - - return (int) Relation.Disjoint; - } - - private static int quickTest2DEnvelopePoint(Envelope geomA, Point geomB, - double tolerance) { - Envelope2D geomAEnv = new Envelope2D(); - geomA.queryEnvelope2D(geomAEnv); - Point2D ptB; - ptB = geomB.getXY(); - return quickTest2DEnvelopePoint(geomAEnv, ptB, tolerance); - } - - private static int quickTest2DEnvelopePoint(Envelope2D geomAEnv, - Point2D ptB, double tolerance) { - Envelope2D envAMinus = geomAEnv; - envAMinus.inflate(-tolerance, -tolerance); - if (envAMinus.contains(ptB)) - return (int) Relation.Contains;// clementini's contains - Envelope2D envAPlus = geomAEnv; - envAPlus.inflate(tolerance, tolerance); - if (envAPlus.contains(ptB)) - return (int) Relation.Touches;// clementini's touches - - return (int) Relation.Disjoint;// clementini's disjoint - } - - private static int quickTest2DEnvelopePoint(Envelope2D envAPlus, - Envelope2D envAMinus, Point2D ptB, double tolerance) { - if (envAMinus.contains(ptB)) - return (int) Relation.Contains;// clementini's contains - if (envAPlus.contains(ptB)) - return (int) Relation.Touches;// clementini's touches - - return (int) Relation.Disjoint;// clementini's disjoint - } - - private static int quickTest2DEnvelopeEnvelope(Envelope geomA, - Envelope geomB, double tolerance) { - Envelope2D geomAEnv = new Envelope2D(); - geomA.queryEnvelope2D(geomAEnv); - Envelope2D geomBEnv = new Envelope2D(); - geomB.queryEnvelope2D(geomBEnv); - return quickTest2DEnvelopeEnvelope(geomAEnv, geomBEnv, tolerance); - } - - private static int quickTest2DEnvelopeEnvelope(Envelope2D geomAEnv, - Envelope2D geomBEnv, double tolerance) { - // firstly check for contains and within to give a chance degenerate - // envelopes to work. - // otherwise, if there are two degenerate envelopes that are equal, - // Touch relation may occur. - int res = 0; - if (geomAEnv.contains(geomBEnv)) - res |= (int) Relation.Contains; - - if (geomBEnv.contains(geomAEnv)) - res |= (int) Relation.Within; - - if (res != 0) - return res; - - Envelope2D envAMinus = geomAEnv; - envAMinus.inflate(-tolerance, -tolerance);// Envelope A interior - Envelope2D envBMinus = geomBEnv; - envBMinus.inflate(-tolerance, -tolerance);// Envelope B interior - if (envAMinus.isIntersecting(envBMinus)) { - Envelope2D envAPlus = geomAEnv; - envAPlus.inflate(tolerance, tolerance);// Envelope A interior plus - // boundary - res = envAPlus.contains(geomBEnv) ? (int) Relation.Contains : 0; - Envelope2D envBPlus = geomBEnv; - envBPlus.inflate(tolerance, tolerance);// Envelope A interior plus - // boundary - res |= envBPlus.contains(geomAEnv) ? (int) Relation.Within : 0; - if (res != 0) - return res; - - return (int) Relation.Overlaps; // Clementini's Overlap - } else { - Envelope2D envAPlus = geomAEnv; - envAPlus.inflate(tolerance, tolerance);// Envelope A interior plus - // boundary - Envelope2D envBPlus = geomBEnv; - envBPlus.inflate(tolerance, tolerance);// Envelope A interior plus - // boundary - if (envAPlus.isIntersecting(envBPlus)) { - return (int) Relation.Touches; // Clementini Touch - } else { - return (int) Relation.Disjoint; // Clementini Disjoint - } - } - } - - private static int quickTest2DMultiPointPoint(MultiPoint geomA, - Point geomB, double tolerance) { - Point2D ptB; - ptB = geomB.getXY(); - return quickTest2DMultiPointPoint(geomA, ptB, tolerance); - } - - private static int quickTest2DMultiPointPoint(MultiPoint geomA, - Point2D ptB, double tolerance) { - // TODO: Add Geometry accelerator. (RasterizedGeometry + kd-tree or - // alike) - for (int i = 0, n = geomA.getPointCount(); i < n; i++) { - Point2D ptA; - ptA = geomA.getXY(i); - int res = quickTest2DPointPoint(ptA, ptB, tolerance); - if (res != (int) Relation.Disjoint) { - if ((res & (int) Relation.Within) != 0 && n != 1) { - // _ASSERT(res & (int)Relation.Contains); - return (int) Relation.Contains; - } - - return res; - } - } - - return (int) Relation.Disjoint; - } - - private static int quickTest2DMultiPointEnvelope(MultiPoint geomA, - Envelope geomB, double tolerance, int testType) { - Envelope2D geomBEnv = new Envelope2D(); - geomB.queryEnvelope2D(geomBEnv); - return quickTest2DMultiPointEnvelope(geomA, geomBEnv, tolerance, - testType); - } - - private static int quickTest2DMultiPointEnvelope(MultiPoint geomA, - Envelope2D geomBEnv, double tolerance, int testType) { - // Add early bailout for disjoint test. - Envelope2D envBMinus = geomBEnv; - envBMinus.inflate(-tolerance, -tolerance); - Envelope2D envBPlus = geomBEnv; - envBPlus.inflate(tolerance, tolerance); - int dres = 0; - for (int i = 0, n = geomA.getPointCount(); i < n; i++) { - Point2D ptA; - ptA = geomA.getXY(i); - int res = reverseResult(quickTest2DEnvelopePoint(envBPlus, - envBMinus, ptA, tolerance)); - if (res != (int) Relation.Disjoint) { - dres |= res; - if (testType == (int) Relation.Disjoint) - return (int) Relation.Intersects; - } - } - - if (dres == 0) - return (int) Relation.Disjoint; - - if (dres == (int) Relation.Within) - return dres; - - return (int) Relation.Overlaps; - } - - private static int quickTest2DMultiPointMultiPoint(MultiPoint geomA, - MultiPoint geomB, double tolerance, int testType) { - int counter = 0; - for (int ib = 0, nb = geomB.getPointCount(); ib < nb; ib++) { - Point2D ptB; - ptB = geomB.getXY(ib); - int res = quickTest2DMultiPointPoint(geomA, ptB, tolerance); - if (res != (int) Relation.Disjoint) { - counter++; - if (testType == (int) Relation.Disjoint) - return (int) Relation.Intersects; - } - } - - if (counter > 0) { - if (counter == geomB.getPointCount())// every point from B is within - // A. Means the A contains B - { - if (testType == (int) Relation.Equals) {// This is slow. - // Refactor. - int res = quickTest2DMultiPointMultiPoint(geomB, geomA, - tolerance, (int) Relation.Contains); - return res == (int) Relation.Contains ? (int) Relation.Equals - : (int) Relation.Unknown; - } - return (int) Relation.Contains; - } else { - return (int) Relation.Overlaps; - } - } - - return 0; - } - - private static int quickTest2DPolylinePoint(Polyline geomA, Point geomB, - double tolerance, int testType) { - Point2D ptB; - ptB = geomB.getXY(); - return quickTest2DPolylinePoint(geomA, ptB, tolerance, testType); - } - - private static int quickTest2DMVPointRasterOnly(MultiVertexGeometry geomA, - Point2D ptB, double tolerance) { - // Use rasterized Geometry: - RasterizedGeometry2D rgeomA = null; - MultiVertexGeometryImpl mpImpl = (MultiVertexGeometryImpl) geomA - ._getImpl(); - GeometryAccelerators gaccel = mpImpl._getAccelerators(); - if (gaccel != null) { - rgeomA = gaccel.getRasterizedGeometry(); - } - - if (rgeomA != null) { - RasterizedGeometry2D.HitType hitres = rgeomA.queryPointInGeometry( - ptB.x, ptB.y); - if (hitres == RasterizedGeometry2D.HitType.Outside) - return (int) Relation.Disjoint; - - if (hitres == RasterizedGeometry2D.HitType.Inside) - return (int) Relation.Contains; - } else - return -1; - - return 0; - } - - private static int quickTest2DPolylinePoint(Polyline geomA, Point2D ptB, - double tolerance, int testType) { - int mask = Relation.Touches | Relation.Contains | Relation.Within - | Relation.Disjoint | Relation.Intersects; - - if ((testType & mask) == 0) - return Relation.NoThisRelation; - - int res = quickTest2DMVPointRasterOnly(geomA, ptB, tolerance); - if (res > 0) - return res; - - // Go through the segments: - double toleranceSqr = tolerance * tolerance; - MultiPathImpl mpImpl = (MultiPathImpl) geomA._getImpl(); - SegmentIteratorImpl iter = mpImpl.querySegmentIterator(); - while (iter.nextPath()) { - int pathIndex = iter.getPathIndex(); - if (!geomA.isClosedPath(pathIndex)) { - int pathSize = geomA.getPathSize(pathIndex); - int pathStart = geomA.getPathStart(pathIndex); - if (pathSize == 0) - continue; - - if (Point2D.sqrDistance(geomA.getXY(pathStart), ptB) <= toleranceSqr - || (pathSize > 1 && Point2D.sqrDistance( - geomA.getXY(pathStart + pathSize - 1), ptB) <= toleranceSqr)) { - return (int) Relation.Touches; - } - } - - if (testType != Relation.Touches) { - while (iter.hasNextSegment()) { - Segment segment = iter.nextSegment(); - double t = segment.getClosestCoordinate(ptB, false); - Point2D pt = segment.getCoord2D(t); - if (Point2D.sqrDistance(pt, ptB) <= toleranceSqr) { - if ((testType & Relation.IntersectsOrDisjoint) != 0) { - return Relation.Intersects; - } - - return (int) Relation.Contains; - } - } - } - } - - return (testType & Relation.IntersectsOrDisjoint) != 0 ? Relation.Disjoint - : Relation.NoThisRelation; - } - - private static int quickTest2DPolylineEnvelope(Polyline geomA, - Envelope geomB, double tolerance) { - Envelope2D geomBEnv = new Envelope2D(); - geomB.queryEnvelope2D(geomBEnv); - return quickTest2DPolylineEnvelope(geomA, geomBEnv, tolerance); - } - - private static int quickTest2DPolylineEnvelope(Polyline geomA, - Envelope2D geomBEnv, double tolerance) { - int res = quickTest2DMVEnvelopeRasterOnly(geomA, geomBEnv, tolerance); - if (res > 0) - return res; - - // TODO: implement me - return 0; - } - - private static int quickTest2DMVEnvelopeRasterOnly( - MultiVertexGeometry geomA, Envelope2D geomBEnv, double tolerance) { - // Use rasterized Geometry only: - RasterizedGeometry2D rgeomA; - MultiVertexGeometryImpl mpImpl = (MultiVertexGeometryImpl) geomA - ._getImpl(); - GeometryAccelerators gaccel = mpImpl._getAccelerators(); - if (gaccel != null) { - rgeomA = gaccel.getRasterizedGeometry(); - } else - return -1; - - if (rgeomA != null) { - HitType hitres = rgeomA.queryEnvelopeInGeometry(geomBEnv); - if (hitres == RasterizedGeometry2D.HitType.Outside) - return (int) Relation.Disjoint; - - if (hitres == RasterizedGeometry2D.HitType.Inside) - return (int) Relation.Contains; - } else - return -1; - - return 0; - } - - private static int quickTest2DPolylineMultiPoint(Polyline geomA, - MultiPoint geomB, double tolerance) { - Envelope2D geomBEnv = new Envelope2D(); - geomB.queryEnvelope2D(geomBEnv); - int res = quickTest2DMVEnvelopeRasterOnly(geomA, geomBEnv, tolerance); - if (res > 0) - return res; - - // TODO: implement me - return 0; - } - - private static int quickTest2DMVMVRasterOnly(MultiVertexGeometry geomA, - MultiVertexGeometry geomB, double tolerance) { - Envelope2D geomBEnv = new Envelope2D(); - geomB.queryEnvelope2D(geomBEnv); - int res = quickTest2DMVEnvelopeRasterOnly(geomA, geomBEnv, tolerance); - if (res > 0) - return res; - - if (res == -1) { - Envelope2D geomAEnv = new Envelope2D(); - geomA.queryEnvelope2D(geomAEnv); - res = quickTest2DMVEnvelopeRasterOnly(geomB, geomAEnv, tolerance); - if (res > 0) - return reverseResult(res); - } - - // TODO: implement me - return 0; - } - - private static int quickTest2DPolylinePolyline(Polyline geomA, - Polyline geomB, double tolerance) { - int res = quickTest2DMVMVRasterOnly(geomA, geomB, tolerance); - if (res > 0) - return res; - - // TODO: implement me - return 0; - } - - private static int quickTest2DPolygonPoint(Polygon geomA, Point geomB, - double tolerance) { - Point2D ptB; - ptB = geomB.getXY(); - return quickTest2DPolygonPoint(geomA, ptB, tolerance); - } - - private static int quickTest2DPolygonPoint(Polygon geomA, Point2D ptB, - double tolerance) { - PolygonUtils.PiPResult pipres = PolygonUtils.isPointInPolygon2D(geomA, - ptB, tolerance);// this method uses the accelerator if available - if (pipres == PolygonUtils.PiPResult.PiPOutside) - return (int) Relation.Disjoint;// clementini's disjoint - - if (pipres == PolygonUtils.PiPResult.PiPInside) - return (int) Relation.Contains;// clementini's contains - - if (pipres == PolygonUtils.PiPResult.PiPBoundary) - return (int) Relation.Touches;// clementini's touches - - throw GeometryException.GeometryInternalError();// GEOMTHROW(internal_error); - // //what else - // return 0; - } - - private static int quickTest2DPolygonEnvelope(Polygon geomA, - Envelope geomB, double tolerance) { - Envelope2D geomBEnv = new Envelope2D(); - geomB.queryEnvelope2D(geomBEnv); - return quickTest2DPolygonEnvelope(geomA, geomBEnv, tolerance); - } - - private static int quickTest2DPolygonEnvelope(Polygon geomA, - Envelope2D geomBEnv, double tolerance) { - int res = quickTest2DMVEnvelopeRasterOnly(geomA, geomBEnv, tolerance); - if (res > 0) - return res; - - // TODO: implement me - return 0; - } - - private static int quickTest2DPolygonMultiPoint(Polygon geomA, - MultiPoint geomB, double tolerance) { - int res = quickTest2DMVMVRasterOnly(geomA, geomB, tolerance); - if (res > 0) - return res; - - // TODO: implement me - return 0; - } - - private static int quickTest2DPolygonPolyline(Polygon geomA, - Polyline geomB, double tolerance) { - int res = quickTest2DMVMVRasterOnly(geomA, geomB, tolerance); - if (res > 0) - return res; - - // TODO: implement me - return 0; - } - - private static int quickTest2DPolygonPolygon(Polygon geomA, Polygon geomB, - double tolerance) { - int res = quickTest2DMVMVRasterOnly(geomA, geomB, tolerance); - if (res > 0) - return res; - - // TODO: implement me - return 0; - } - - public static int quickTest2D_Accelerated_DisjointOrContains( - Geometry geomA, Geometry geomB, double tolerance) { - int gtA = geomA.getType().value(); - int gtB = geomB.getType().value(); - GeometryAccelerators accel; - boolean endWhileStatement = false; - do { - if (Geometry.isMultiVertex(gtA)) { - MultiVertexGeometryImpl impl = (MultiVertexGeometryImpl) geomA - ._getImpl(); - accel = impl._getAccelerators(); - if (accel != null) { - RasterizedGeometry2D rgeom = accel.getRasterizedGeometry(); - if (rgeom != null) { - if (gtB == Geometry.GeometryType.Point) { - Point2D ptB = ((Point) geomB).getXY(); - HitType hit = rgeom.queryPointInGeometry(ptB.x, - ptB.y); - if (hit == RasterizedGeometry2D.HitType.Inside) { - return (int) Relation.Contains; - } else if (hit == RasterizedGeometry2D.HitType.Outside) { - return (int) Relation.Disjoint; - } - - break; - } - Envelope2D envB = new Envelope2D(); - geomB.queryEnvelope2D(envB); - RasterizedGeometry2D.HitType hit = rgeom - .queryEnvelopeInGeometry(envB); - if (hit == RasterizedGeometry2D.HitType.Inside) { - return (int) Relation.Contains; - } else if (hit == RasterizedGeometry2D.HitType.Outside) { - return (int) Relation.Disjoint; - } - - break; - } - } - } - } while (endWhileStatement); - - accel = null; - do { - if (Geometry.isMultiVertex(gtB)) { - MultiVertexGeometryImpl impl = (MultiVertexGeometryImpl) geomB - ._getImpl(); - accel = impl._getAccelerators(); - if (accel != null) { - RasterizedGeometry2D rgeom = accel.getRasterizedGeometry(); - if (rgeom != null) { - if (gtA == Geometry.GeometryType.Point) { - Point2D ptA = ((Point) geomA).getXY(); - RasterizedGeometry2D.HitType hit = rgeom - .queryPointInGeometry(ptA.x, ptA.y); - if (hit == RasterizedGeometry2D.HitType.Inside) { - return (int) Relation.Within; - } else if (hit == RasterizedGeometry2D.HitType.Outside) { - return (int) Relation.Disjoint; - } - - break; - } - - Envelope2D envA = new Envelope2D(); - geomA.queryEnvelope2D(envA); - RasterizedGeometry2D.HitType hit = rgeom - .queryEnvelopeInGeometry(envA); - if (hit == RasterizedGeometry2D.HitType.Inside) { - return (int) Relation.Within; - } else if (hit == RasterizedGeometry2D.HitType.Outside) { - return (int) Relation.Disjoint; - } - - break; - } - } - } - } while (endWhileStatement); - - return 0; - } - - private static int reverseResult(int resIn) { - int res = resIn; - if ((res & (int) Relation.Contains) != 0) { - res &= ~(int) Relation.Contains; - res |= (int) Relation.Within; - } - if ((res & (int) Relation.Within) != 0) { - res &= ~(int) Relation.Within; - res |= (int) Relation.Contains; - } - - return res; - } + interface Relation { + public final int Unknown = 0; + public final int Contains = 1; + public final int Within = 2; + public final int Equals = 3; // == Within | Contains tests both within + // and contains + public final int Disjoint = 4; + public final int Touches = 8; + public final int Crosses = 16; + public final int Overlaps = 32; + + public final int NoThisRelation = 64; // returned when the relation is + // not satisified + public final int Intersects = 0x40000000;// this means not_disjoint. + // Used for early bailout + public final int IntersectsOrDisjoint = Intersects | Disjoint; + } + + public static int quickTest2D(Geometry geomA, Geometry geomB, + double tolerance, int testType) { + if (geomB.isEmpty() || geomA.isEmpty()) + return (int) Relation.Disjoint; + + int geomAtype = geomA.getType().value(); + int geomBtype = geomB.getType().value(); + + // We do not support segments directly for now. Convert to Polyline + Polyline autoPolyA; + if (Geometry.isSegment(geomAtype)) { + autoPolyA = new Polyline(geomA.getDescription()); + geomA = (Geometry) autoPolyA; + autoPolyA.addSegment((Segment) geomA, true); + } + + Polyline autoPolyB; + if (Geometry.isSegment(geomBtype)) { + autoPolyB = new Polyline(geomB.getDescription()); + geomB = (Geometry) autoPolyB; + autoPolyB.addSegment((Segment) geomB, true); + } + + // Now process GeometryxGeometry case by case + switch (geomAtype) { + case Geometry.GeometryType.Point: { + switch (geomBtype) { + case Geometry.GeometryType.Point: + return quickTest2DPointPoint((Point) geomA, (Point) geomB, + tolerance); + case Geometry.GeometryType.Envelope: + return reverseResult(quickTest2DEnvelopePoint((Envelope) geomB, + (Point) geomA, tolerance)); + case Geometry.GeometryType.MultiPoint: + return reverseResult(quickTest2DMultiPointPoint( + (MultiPoint) geomB, (Point) geomA, tolerance)); + case Geometry.GeometryType.Polyline: + return reverseResult(quickTest2DPolylinePoint((Polyline) geomB, + (Point) geomA, tolerance, testType)); + case Geometry.GeometryType.Polygon: + return reverseResult(quickTest2DPolygonPoint((Polygon) geomB, + (Point) geomA, tolerance)); + } + throw GeometryException.GeometryInternalError();// GEOMTHROW(internal_error);//what + // else? + } + case Geometry.GeometryType.Envelope: { + switch (geomBtype) { + case Geometry.GeometryType.Point: + return quickTest2DEnvelopePoint((Envelope) geomA, + (Point) geomB, tolerance); + case Geometry.GeometryType.Envelope: + return quickTest2DEnvelopeEnvelope((Envelope) geomA, + (Envelope) geomB, tolerance); + case Geometry.GeometryType.MultiPoint: + return reverseResult(quickTest2DMultiPointEnvelope( + (MultiPoint) geomB, (Envelope) geomA, tolerance, + testType)); + case Geometry.GeometryType.Polyline: + return reverseResult(quickTest2DPolylineEnvelope( + (Polyline) geomB, (Envelope) geomA, tolerance)); + case Geometry.GeometryType.Polygon: + return reverseResult(quickTest2DPolygonEnvelope( + (Polygon) geomB, (Envelope) geomA, tolerance)); + } + throw GeometryException.GeometryInternalError();// GEOMTHROW(internal_error);//what + // else? + } + case Geometry.GeometryType.MultiPoint: { + switch (geomBtype) { + case Geometry.GeometryType.Point: + return quickTest2DMultiPointPoint((MultiPoint) geomA, + (Point) geomB, tolerance); + case Geometry.GeometryType.Envelope: + return quickTest2DMultiPointEnvelope((MultiPoint) geomA, + (Envelope) geomB, tolerance, testType); + case Geometry.GeometryType.MultiPoint: + return quickTest2DMultiPointMultiPoint((MultiPoint) geomA, + (MultiPoint) geomB, tolerance, testType); + case Geometry.GeometryType.Polyline: + return reverseResult(quickTest2DPolylineMultiPoint( + (Polyline) geomB, (MultiPoint) geomA, tolerance)); + case Geometry.GeometryType.Polygon: + return reverseResult(quickTest2DPolygonMultiPoint( + (Polygon) geomB, (MultiPoint) geomA, tolerance)); + } + throw GeometryException.GeometryInternalError();// GEOMTHROW(internal_error);//what + // else? + } + case Geometry.GeometryType.Polyline: { + switch (geomBtype) { + case Geometry.GeometryType.Point: + return quickTest2DPolylinePoint((Polyline) geomA, + (Point) geomB, tolerance, testType); + case Geometry.GeometryType.Envelope: + return quickTest2DPolylineEnvelope((Polyline) geomA, + (Envelope) geomB, tolerance); + case Geometry.GeometryType.MultiPoint: + return quickTest2DPolylineMultiPoint((Polyline) geomA, + (MultiPoint) geomB, tolerance); + case Geometry.GeometryType.Polyline: + return quickTest2DPolylinePolyline((Polyline) geomA, + (Polyline) geomB, tolerance); + case Geometry.GeometryType.Polygon: + return reverseResult(quickTest2DPolygonPolyline( + (Polygon) geomB, (Polyline) geomA, tolerance)); + } + throw GeometryException.GeometryInternalError();// GEOMTHROW(internal_error);//what + // else? + } + case Geometry.GeometryType.Polygon: { + switch (geomBtype) { + case Geometry.GeometryType.Point: + return quickTest2DPolygonPoint((Polygon) geomA, (Point) geomB, + tolerance); + case Geometry.GeometryType.Envelope: + return quickTest2DPolygonEnvelope((Polygon) geomA, + (Envelope) geomB, tolerance); + case Geometry.GeometryType.MultiPoint: + return quickTest2DPolygonMultiPoint((Polygon) geomA, + (MultiPoint) geomB, tolerance); + case Geometry.GeometryType.Polyline: + return quickTest2DPolygonPolyline((Polygon) geomA, + (Polyline) geomB, tolerance); + case Geometry.GeometryType.Polygon: + return quickTest2DPolygonPolygon((Polygon) geomA, + (Polygon) geomB, tolerance); + } + throw GeometryException.GeometryInternalError();// GEOMTHROW(internal_error);//what + // else? + } + + default: + throw GeometryException.GeometryInternalError();// GEOMTHROW(internal_error);//what + // else? + // return 0; + } + } + + private static int quickTest2DPointPoint(Point geomA, Point geomB, + double tolerance) { + Point2D ptA = geomA.getXY(); + Point2D ptB = geomB.getXY(); + return quickTest2DPointPoint(ptA, ptB, tolerance); + } + + private static int quickTest2DPointPoint(Point2D ptA, Point2D ptB, + double tolerance) { + ptA.sub(ptB); + double len = ptA.sqrLength();// Should we test against 2*tol or tol? + if (len <= tolerance * tolerance)// Two points are equal if they are not + // Disjoint. We consider a point to + // be a disk of radius tolerance. + // Any intersection of two disks + // produces same disk. + return (int) Relation.Within | (int) Relation.Contains;// ==Equals + + return (int) Relation.Disjoint; + } + + private static int quickTest2DEnvelopePoint(Envelope geomA, Point geomB, + double tolerance) { + Envelope2D geomAEnv = new Envelope2D(); + geomA.queryEnvelope2D(geomAEnv); + Point2D ptB; + ptB = geomB.getXY(); + return quickTest2DEnvelopePoint(geomAEnv, ptB, tolerance); + } + + private static int quickTest2DEnvelopePoint(Envelope2D geomAEnv, + Point2D ptB, double tolerance) { + Envelope2D envAMinus = geomAEnv; + envAMinus.inflate(-tolerance, -tolerance); + if (envAMinus.contains(ptB)) + return (int) Relation.Contains;// clementini's contains + Envelope2D envAPlus = geomAEnv; + envAPlus.inflate(tolerance, tolerance); + if (envAPlus.contains(ptB)) + return (int) Relation.Touches;// clementini's touches + + return (int) Relation.Disjoint;// clementini's disjoint + } + + private static int quickTest2DEnvelopePoint(Envelope2D envAPlus, + Envelope2D envAMinus, Point2D ptB, double tolerance) { + if (envAMinus.contains(ptB)) + return (int) Relation.Contains;// clementini's contains + if (envAPlus.contains(ptB)) + return (int) Relation.Touches;// clementini's touches + + return (int) Relation.Disjoint;// clementini's disjoint + } + + private static int quickTest2DEnvelopeEnvelope(Envelope geomA, + Envelope geomB, double tolerance) { + Envelope2D geomAEnv = new Envelope2D(); + geomA.queryEnvelope2D(geomAEnv); + Envelope2D geomBEnv = new Envelope2D(); + geomB.queryEnvelope2D(geomBEnv); + return quickTest2DEnvelopeEnvelope(geomAEnv, geomBEnv, tolerance); + } + + private static int quickTest2DEnvelopeEnvelope(Envelope2D geomAEnv, + Envelope2D geomBEnv, double tolerance) { + // firstly check for contains and within to give a chance degenerate + // envelopes to work. + // otherwise, if there are two degenerate envelopes that are equal, + // Touch relation may occur. + int res = 0; + if (geomAEnv.contains(geomBEnv)) + res |= (int) Relation.Contains; + + if (geomBEnv.contains(geomAEnv)) + res |= (int) Relation.Within; + + if (res != 0) + return res; + + Envelope2D envAMinus = geomAEnv; + envAMinus.inflate(-tolerance, -tolerance);// Envelope A interior + Envelope2D envBMinus = geomBEnv; + envBMinus.inflate(-tolerance, -tolerance);// Envelope B interior + if (envAMinus.isIntersecting(envBMinus)) { + Envelope2D envAPlus = geomAEnv; + envAPlus.inflate(tolerance, tolerance);// Envelope A interior plus + // boundary + res = envAPlus.contains(geomBEnv) ? (int) Relation.Contains : 0; + Envelope2D envBPlus = geomBEnv; + envBPlus.inflate(tolerance, tolerance);// Envelope A interior plus + // boundary + res |= envBPlus.contains(geomAEnv) ? (int) Relation.Within : 0; + if (res != 0) + return res; + + return (int) Relation.Overlaps; // Clementini's Overlap + } else { + Envelope2D envAPlus = geomAEnv; + envAPlus.inflate(tolerance, tolerance);// Envelope A interior plus + // boundary + Envelope2D envBPlus = geomBEnv; + envBPlus.inflate(tolerance, tolerance);// Envelope A interior plus + // boundary + if (envAPlus.isIntersecting(envBPlus)) { + return (int) Relation.Touches; // Clementini Touch + } else { + return (int) Relation.Disjoint; // Clementini Disjoint + } + } + } + + private static int quickTest2DMultiPointPoint(MultiPoint geomA, + Point geomB, double tolerance) { + Point2D ptB; + ptB = geomB.getXY(); + return quickTest2DMultiPointPoint(geomA, ptB, tolerance); + } + + private static int quickTest2DMultiPointPoint(MultiPoint geomA, + Point2D ptB, double tolerance) { + // TODO: Add Geometry accelerator. (RasterizedGeometry + kd-tree or + // alike) + for (int i = 0, n = geomA.getPointCount(); i < n; i++) { + Point2D ptA; + ptA = geomA.getXY(i); + int res = quickTest2DPointPoint(ptA, ptB, tolerance); + if (res != (int) Relation.Disjoint) { + if ((res & (int) Relation.Within) != 0 && n != 1) { + // _ASSERT(res & (int)Relation.Contains); + return (int) Relation.Contains; + } + + return res; + } + } + + return (int) Relation.Disjoint; + } + + private static int quickTest2DMultiPointEnvelope(MultiPoint geomA, + Envelope geomB, double tolerance, int testType) { + Envelope2D geomBEnv = new Envelope2D(); + geomB.queryEnvelope2D(geomBEnv); + return quickTest2DMultiPointEnvelope(geomA, geomBEnv, tolerance, + testType); + } + + private static int quickTest2DMultiPointEnvelope(MultiPoint geomA, + Envelope2D geomBEnv, double tolerance, int testType) { + // Add early bailout for disjoint test. + Envelope2D envBMinus = geomBEnv; + envBMinus.inflate(-tolerance, -tolerance); + Envelope2D envBPlus = geomBEnv; + envBPlus.inflate(tolerance, tolerance); + int dres = 0; + for (int i = 0, n = geomA.getPointCount(); i < n; i++) { + Point2D ptA; + ptA = geomA.getXY(i); + int res = reverseResult(quickTest2DEnvelopePoint(envBPlus, + envBMinus, ptA, tolerance)); + if (res != (int) Relation.Disjoint) { + dres |= res; + if (testType == (int) Relation.Disjoint) + return (int) Relation.Intersects; + } + } + + if (dres == 0) + return (int) Relation.Disjoint; + + if (dres == (int) Relation.Within) + return dres; + + return (int) Relation.Overlaps; + } + + private static int quickTest2DMultiPointMultiPoint(MultiPoint geomA, + MultiPoint geomB, double tolerance, int testType) { + int counter = 0; + for (int ib = 0, nb = geomB.getPointCount(); ib < nb; ib++) { + Point2D ptB; + ptB = geomB.getXY(ib); + int res = quickTest2DMultiPointPoint(geomA, ptB, tolerance); + if (res != (int) Relation.Disjoint) { + counter++; + if (testType == (int) Relation.Disjoint) + return (int) Relation.Intersects; + } + } + + if (counter > 0) { + if (counter == geomB.getPointCount())// every point from B is within + // A. Means the A contains B + { + if (testType == (int) Relation.Equals) {// This is slow. + // Refactor. + int res = quickTest2DMultiPointMultiPoint(geomB, geomA, + tolerance, (int) Relation.Contains); + return res == (int) Relation.Contains ? (int) Relation.Equals + : (int) Relation.Unknown; + } + return (int) Relation.Contains; + } else { + return (int) Relation.Overlaps; + } + } + + return 0; + } + + private static int quickTest2DPolylinePoint(Polyline geomA, Point geomB, + double tolerance, int testType) { + Point2D ptB; + ptB = geomB.getXY(); + return quickTest2DPolylinePoint(geomA, ptB, tolerance, testType); + } + + private static int quickTest2DMVPointRasterOnly(MultiVertexGeometry geomA, + Point2D ptB, double tolerance) { + // Use rasterized Geometry: + RasterizedGeometry2D rgeomA = null; + MultiVertexGeometryImpl mpImpl = (MultiVertexGeometryImpl) geomA + ._getImpl(); + GeometryAccelerators gaccel = mpImpl._getAccelerators(); + if (gaccel != null) { + rgeomA = gaccel.getRasterizedGeometry(); + } + + if (rgeomA != null) { + RasterizedGeometry2D.HitType hitres = rgeomA.queryPointInGeometry( + ptB.x, ptB.y); + if (hitres == RasterizedGeometry2D.HitType.Outside) + return (int) Relation.Disjoint; + + if (hitres == RasterizedGeometry2D.HitType.Inside) + return (int) Relation.Contains; + } else + return -1; + + return 0; + } + + private static int quickTest2DPolylinePoint(Polyline geomA, Point2D ptB, + double tolerance, int testType) { + int mask = Relation.Touches | Relation.Contains | Relation.Within + | Relation.Disjoint | Relation.Intersects; + + if ((testType & mask) == 0) + return Relation.NoThisRelation; + + int res = quickTest2DMVPointRasterOnly(geomA, ptB, tolerance); + if (res > 0) + return res; + + // Go through the segments: + double toleranceSqr = tolerance * tolerance; + MultiPathImpl mpImpl = (MultiPathImpl) geomA._getImpl(); + SegmentIteratorImpl iter = mpImpl.querySegmentIterator(); + while (iter.nextPath()) { + int pathIndex = iter.getPathIndex(); + if (!geomA.isClosedPath(pathIndex)) { + int pathSize = geomA.getPathSize(pathIndex); + int pathStart = geomA.getPathStart(pathIndex); + if (pathSize == 0) + continue; + + if (Point2D.sqrDistance(geomA.getXY(pathStart), ptB) <= toleranceSqr + || (pathSize > 1 && Point2D.sqrDistance( + geomA.getXY(pathStart + pathSize - 1), ptB) <= toleranceSqr)) { + return (int) Relation.Touches; + } + } + + if (testType != Relation.Touches) { + while (iter.hasNextSegment()) { + Segment segment = iter.nextSegment(); + double t = segment.getClosestCoordinate(ptB, false); + Point2D pt = segment.getCoord2D(t); + if (Point2D.sqrDistance(pt, ptB) <= toleranceSqr) { + if ((testType & Relation.IntersectsOrDisjoint) != 0) { + return Relation.Intersects; + } + + return (int) Relation.Contains; + } + } + } + } + + return (testType & Relation.IntersectsOrDisjoint) != 0 ? Relation.Disjoint + : Relation.NoThisRelation; + } + + private static int quickTest2DPolylineEnvelope(Polyline geomA, + Envelope geomB, double tolerance) { + Envelope2D geomBEnv = new Envelope2D(); + geomB.queryEnvelope2D(geomBEnv); + return quickTest2DPolylineEnvelope(geomA, geomBEnv, tolerance); + } + + private static int quickTest2DPolylineEnvelope(Polyline geomA, + Envelope2D geomBEnv, double tolerance) { + int res = quickTest2DMVEnvelopeRasterOnly(geomA, geomBEnv, tolerance); + if (res > 0) + return res; + + // TODO: implement me + return 0; + } + + private static int quickTest2DMVEnvelopeRasterOnly( + MultiVertexGeometry geomA, Envelope2D geomBEnv, double tolerance) { + // Use rasterized Geometry only: + RasterizedGeometry2D rgeomA; + MultiVertexGeometryImpl mpImpl = (MultiVertexGeometryImpl) geomA + ._getImpl(); + GeometryAccelerators gaccel = mpImpl._getAccelerators(); + if (gaccel != null) { + rgeomA = gaccel.getRasterizedGeometry(); + } else + return -1; + + if (rgeomA != null) { + HitType hitres = rgeomA.queryEnvelopeInGeometry(geomBEnv); + if (hitres == RasterizedGeometry2D.HitType.Outside) + return (int) Relation.Disjoint; + + if (hitres == RasterizedGeometry2D.HitType.Inside) + return (int) Relation.Contains; + } else + return -1; + + return 0; + } + + private static int quickTest2DPolylineMultiPoint(Polyline geomA, + MultiPoint geomB, double tolerance) { + Envelope2D geomBEnv = new Envelope2D(); + geomB.queryEnvelope2D(geomBEnv); + int res = quickTest2DMVEnvelopeRasterOnly(geomA, geomBEnv, tolerance); + if (res > 0) + return res; + + // TODO: implement me + return 0; + } + + private static int quickTest2DMVMVRasterOnly(MultiVertexGeometry geomA, + MultiVertexGeometry geomB, double tolerance) { + Envelope2D geomBEnv = new Envelope2D(); + geomB.queryEnvelope2D(geomBEnv); + int res = quickTest2DMVEnvelopeRasterOnly(geomA, geomBEnv, tolerance); + if (res > 0) + return res; + + if (res == -1) { + Envelope2D geomAEnv = new Envelope2D(); + geomA.queryEnvelope2D(geomAEnv); + res = quickTest2DMVEnvelopeRasterOnly(geomB, geomAEnv, tolerance); + if (res > 0) + return reverseResult(res); + } + + // TODO: implement me + return 0; + } + + private static int quickTest2DPolylinePolyline(Polyline geomA, + Polyline geomB, double tolerance) { + int res = quickTest2DMVMVRasterOnly(geomA, geomB, tolerance); + if (res > 0) + return res; + + // TODO: implement me + return 0; + } + + private static int quickTest2DPolygonPoint(Polygon geomA, Point geomB, + double tolerance) { + Point2D ptB; + ptB = geomB.getXY(); + return quickTest2DPolygonPoint(geomA, ptB, tolerance); + } + + private static int quickTest2DPolygonPoint(Polygon geomA, Point2D ptB, + double tolerance) { + PolygonUtils.PiPResult pipres = PolygonUtils.isPointInPolygon2D(geomA, + ptB, tolerance);// this method uses the accelerator if available + if (pipres == PolygonUtils.PiPResult.PiPOutside) + return (int) Relation.Disjoint;// clementini's disjoint + + if (pipres == PolygonUtils.PiPResult.PiPInside) + return (int) Relation.Contains;// clementini's contains + + if (pipres == PolygonUtils.PiPResult.PiPBoundary) + return (int) Relation.Touches;// clementini's touches + + throw GeometryException.GeometryInternalError();// GEOMTHROW(internal_error); + // //what else + // return 0; + } + + private static int quickTest2DPolygonEnvelope(Polygon geomA, + Envelope geomB, double tolerance) { + Envelope2D geomBEnv = new Envelope2D(); + geomB.queryEnvelope2D(geomBEnv); + return quickTest2DPolygonEnvelope(geomA, geomBEnv, tolerance); + } + + private static int quickTest2DPolygonEnvelope(Polygon geomA, + Envelope2D geomBEnv, double tolerance) { + int res = quickTest2DMVEnvelopeRasterOnly(geomA, geomBEnv, tolerance); + if (res > 0) + return res; + + // TODO: implement me + return 0; + } + + private static int quickTest2DPolygonMultiPoint(Polygon geomA, + MultiPoint geomB, double tolerance) { + int res = quickTest2DMVMVRasterOnly(geomA, geomB, tolerance); + if (res > 0) + return res; + + // TODO: implement me + return 0; + } + + private static int quickTest2DPolygonPolyline(Polygon geomA, + Polyline geomB, double tolerance) { + int res = quickTest2DMVMVRasterOnly(geomA, geomB, tolerance); + if (res > 0) + return res; + + // TODO: implement me + return 0; + } + + private static int quickTest2DPolygonPolygon(Polygon geomA, Polygon geomB, + double tolerance) { + int res = quickTest2DMVMVRasterOnly(geomA, geomB, tolerance); + if (res > 0) + return res; + + // TODO: implement me + return 0; + } + + public static int quickTest2D_Accelerated_DisjointOrContains( + Geometry geomA, Geometry geomB, double tolerance) { + int gtA = geomA.getType().value(); + int gtB = geomB.getType().value(); + GeometryAccelerators accel; + boolean endWhileStatement = false; + do { + if (Geometry.isMultiVertex(gtA)) { + MultiVertexGeometryImpl impl = (MultiVertexGeometryImpl) geomA + ._getImpl(); + accel = impl._getAccelerators(); + if (accel != null) { + RasterizedGeometry2D rgeom = accel.getRasterizedGeometry(); + if (rgeom != null) { + if (gtB == Geometry.GeometryType.Point) { + Point2D ptB = ((Point) geomB).getXY(); + HitType hit = rgeom.queryPointInGeometry(ptB.x, + ptB.y); + if (hit == RasterizedGeometry2D.HitType.Inside) { + return (int) Relation.Contains; + } else if (hit == RasterizedGeometry2D.HitType.Outside) { + return (int) Relation.Disjoint; + } + + break; + } + Envelope2D envB = new Envelope2D(); + geomB.queryEnvelope2D(envB); + RasterizedGeometry2D.HitType hit = rgeom + .queryEnvelopeInGeometry(envB); + if (hit == RasterizedGeometry2D.HitType.Inside) { + return (int) Relation.Contains; + } else if (hit == RasterizedGeometry2D.HitType.Outside) { + return (int) Relation.Disjoint; + } + + break; + } + } + } + } while (endWhileStatement); + + accel = null; + do { + if (Geometry.isMultiVertex(gtB)) { + MultiVertexGeometryImpl impl = (MultiVertexGeometryImpl) geomB + ._getImpl(); + accel = impl._getAccelerators(); + if (accel != null) { + RasterizedGeometry2D rgeom = accel.getRasterizedGeometry(); + if (rgeom != null) { + if (gtA == Geometry.GeometryType.Point) { + Point2D ptA = ((Point) geomA).getXY(); + RasterizedGeometry2D.HitType hit = rgeom + .queryPointInGeometry(ptA.x, ptA.y); + if (hit == RasterizedGeometry2D.HitType.Inside) { + return (int) Relation.Within; + } else if (hit == RasterizedGeometry2D.HitType.Outside) { + return (int) Relation.Disjoint; + } + + break; + } + + Envelope2D envA = new Envelope2D(); + geomA.queryEnvelope2D(envA); + RasterizedGeometry2D.HitType hit = rgeom + .queryEnvelopeInGeometry(envA); + if (hit == RasterizedGeometry2D.HitType.Inside) { + return (int) Relation.Within; + } else if (hit == RasterizedGeometry2D.HitType.Outside) { + return (int) Relation.Disjoint; + } + + break; + } + } + } + } while (endWhileStatement); + + return 0; + } + + private static int reverseResult(int resIn) { + int res = resIn; + if ((res & (int) Relation.Contains) != 0) { + res &= ~(int) Relation.Contains; + res |= (int) Relation.Within; + } + if ((res & (int) Relation.Within) != 0) { + res &= ~(int) Relation.Within; + res |= (int) Relation.Contains; + } + + return res; + } } diff --git a/src/main/java/com/esri/core/geometry/OperatorIntersection.java b/src/main/java/com/esri/core/geometry/OperatorIntersection.java index 7e151db5..e820025e 100644 --- a/src/main/java/com/esri/core/geometry/OperatorIntersection.java +++ b/src/main/java/com/esri/core/geometry/OperatorIntersection.java @@ -28,71 +28,73 @@ * Intersection of geometries by a given geometry. */ public abstract class OperatorIntersection extends Operator implements CombineOperator { - @Override - public Type getType() { - return Type.Intersection; - } + @Override + public Type getType() { + return Type.Intersection; + } - /** - * Performs the Topological Intersection operation on the geometry set. - * - * @param inputGeometries is the set of Geometry instances to be intersected by the intersector. - * @param intersector is the intersector Geometry. - *

- * The operator intersects every geometry in the inputGeometries with the first geometry of the intersector and returns the result. - */ - public abstract GeometryCursor execute(GeometryCursor inputGeometries, - GeometryCursor intersector, SpatialReference sr, - ProgressTracker progressTracker); + /** + * Performs the Topological Intersection operation on the geometry set. + * + * @param inputGeometries is the set of Geometry instances to be intersected by the intersector. + * @param intersector is the intersector Geometry. + *

+ * The operator intersects every geometry in the inputGeometries with the first geometry of the intersector and returns the result. + */ + public abstract GeometryCursor execute(GeometryCursor inputGeometries, + GeometryCursor intersector, SpatialReference sr, + ProgressTracker progressTracker); - /** - *Performs the Topological intersection operation on the geometry set. - *@param input_geometries is the set of Geometry instances to be intersected by the intersector. - *@param intersector is the intersector Geometry. Only single intersector is used, therefore, the intersector.next() is called only once. - *@param sr The spatial reference is used to get tolerance value. Can be null, then the tolerance is not used and the operation is performed with - *a small tolerance value just enough to make the operation robust. - *@param progress_tracker Allows to cancel the operation. Can be null. - *@param dimensionMask The dimension of the intersection. The value is either -1, or a bitmask mask of values (1 << dim). - *The value of -1 means the lower dimension in the intersecting pair. - *This is a fastest option when intersecting polygons with polygons or polylines. - *The bitmask of values (1 << dim), where dim is the desired dimension value, is used to indicate - *what dimensions of geometry one wants to be returned. For example, to return - *multipoints and lines only, pass (1 << 0) | (1 << 1), which is equivalen to 1 | 2, or 3. - *@return Returns the cursor of the intersection result. The cursors' getGeometryID method returns the current ID of the input geometry - *being processed. When dimensionMask is a bitmask, there will be n result geometries per one input geometry returned, where n is the number - *of bits set in the bitmask. For example, if the dimensionMask is 5, there will be two geometries per one input geometry. - * - *The operator intersects every geometry in the input_geometries with the first geometry of the intersector and returns the result. - * - *Note, when the dimensionMask is -1, then for each intersected pair of geometries, - *the result has the lower of dimentions of the two geometries. That is, the dimension of the Polyline/Polyline intersection - *is always 1 (that is, for polylines it never returns crossing points, but the overlaps only). - *If dimensionMask is 7, the operation will return any possible intersections. - */ - public abstract GeometryCursor execute(GeometryCursor input_geometries, - GeometryCursor intersector, SpatialReference sr, - ProgressTracker progress_tracker, int dimensionMask); + /** + * Performs the Topological intersection operation on the geometry set. + * + * @param input_geometries is the set of Geometry instances to be intersected by the intersector. + * @param intersector is the intersector Geometry. Only single intersector is used, therefore, the intersector.next() is called only once. + * @param sr The spatial reference is used to get tolerance value. Can be null, then the tolerance is not used and the operation is performed with + * a small tolerance value just enough to make the operation robust. + * @param progress_tracker Allows to cancel the operation. Can be null. + * @param dimensionMask The dimension of the intersection. The value is either -1, or a bitmask mask of values (1 << dim). + * The value of -1 means the lower dimension in the intersecting pair. + * This is a fastest option when intersecting polygons with polygons or polylines. + * The bitmask of values (1 << dim), where dim is the desired dimension value, is used to indicate + * what dimensions of geometry one wants to be returned. For example, to return + * multipoints and lines only, pass (1 << 0) | (1 << 1), which is equivalen to 1 | 2, or 3. + * @return Returns the cursor of the intersection result. The cursors' getGeometryID method returns the current ID of the input geometry + * being processed. When dimensionMask is a bitmask, there will be n result geometries per one input geometry returned, where n is the number + * of bits set in the bitmask. For example, if the dimensionMask is 5, there will be two geometries per one input geometry. + *

+ * The operator intersects every geometry in the input_geometries with the first geometry of the intersector and returns the result. + *

+ * Note, when the dimensionMask is -1, then for each intersected pair of geometries, + * the result has the lower of dimentions of the two geometries. That is, the dimension of the Polyline/Polyline intersection + * is always 1 (that is, for polylines it never returns crossing points, but the overlaps only). + * If dimensionMask is 7, the operation will return any possible intersections. + */ + public abstract GeometryCursor execute(GeometryCursor input_geometries, + GeometryCursor intersector, SpatialReference sr, + ProgressTracker progress_tracker, int dimensionMask); - /** - *Performs the Topological Intersection operation on the geometry. - *The result has the lower of dimentions of the two geometries. That is, the dimension of the - *Polyline/Polyline intersection is always 1 (that is, for polylines it never returns crossing - *points, but the overlaps only). - *The call is equivalent to calling the overloaded method using cursors: - *execute(new SimpleGeometryCursor(input_geometry), new SimpleGeometryCursor(intersector), sr, progress_tracker, mask).next(); - *where mask can be either -1 or min(1 << input_geometry.getDimension(), 1 << intersector.getDimension()); - *@param inputGeometry is the Geometry instance to be intersected by the intersector. - *@param intersector is the intersector Geometry. - *@param sr The spatial reference to get the tolerance value from. Can be null, then the tolerance is calculated from the input geometries. - *@return Returns the intersected Geometry. - */ - public abstract Geometry execute(Geometry inputGeometry, - Geometry intersector, SpatialReference sr, - ProgressTracker progressTracker); + /** + * Performs the Topological Intersection operation on the geometry. + * The result has the lower of dimentions of the two geometries. That is, the dimension of the + * Polyline/Polyline intersection is always 1 (that is, for polylines it never returns crossing + * points, but the overlaps only). + * The call is equivalent to calling the overloaded method using cursors: + * execute(new SimpleGeometryCursor(input_geometry), new SimpleGeometryCursor(intersector), sr, progress_tracker, mask).next(); + * where mask can be either -1 or min(1 << input_geometry.getDimension(), 1 << intersector.getDimension()); + * + * @param inputGeometry is the Geometry instance to be intersected by the intersector. + * @param intersector is the intersector Geometry. + * @param sr The spatial reference to get the tolerance value from. Can be null, then the tolerance is calculated from the input geometries. + * @return Returns the intersected Geometry. + */ + public abstract Geometry execute(Geometry inputGeometry, + Geometry intersector, SpatialReference sr, + ProgressTracker progressTracker); - public static OperatorIntersection local() { - return (OperatorIntersection) OperatorFactoryLocal.getInstance() - .getOperator(Type.Intersection); - } + public static OperatorIntersection local() { + return (OperatorIntersection) OperatorFactoryLocal.getInstance() + .getOperator(Type.Intersection); + } } diff --git a/src/main/java/com/esri/core/geometry/OperatorIntersectionCursor.java b/src/main/java/com/esri/core/geometry/OperatorIntersectionCursor.java index 223f36f9..5e23bc82 100644 --- a/src/main/java/com/esri/core/geometry/OperatorIntersectionCursor.java +++ b/src/main/java/com/esri/core/geometry/OperatorIntersectionCursor.java @@ -26,775 +26,775 @@ // TODO test this public class OperatorIntersectionCursor extends GeometryCursor { - GeometryCursor m_smallCursor; - ProgressTracker m_progress_tracker; - SpatialReference m_spatial_reference; - Geometry m_geomIntersector; - Geometry m_geomIntersectorEmptyGeom;// holds empty geometry of intersector - // type. - int m_geomIntersectorType; - int m_currentGeomType; - int m_dimensionMask; - boolean m_bEmpty; - - OperatorIntersectionCursor(GeometryCursor inputGeoms, - GeometryCursor geomIntersector, - SpatialReference sr, - ProgressTracker progress_tracker, - int dimensionMask) { - m_bEmpty = geomIntersector == null; - m_inputGeoms = inputGeoms; - m_spatial_reference = sr; - m_geomIntersector = geomIntersector.next(); - m_geomIntersectorType = m_geomIntersector.getType().value(); - m_currentGeomType = Geometry.Type.Unknown.value(); - m_progress_tracker = progress_tracker; - m_dimensionMask = dimensionMask; - if (m_dimensionMask != -1 - && (m_dimensionMask <= 0 || m_dimensionMask > 7)) - throw new IllegalArgumentException("bad dimension mask");// dimension - // mask - // can - // be - // -1, - // for - // the - // default - // behavior, - // or a - // value - // between - // 1 and - // 7. - } - - @Override - public Geometry next() { - if (m_bEmpty) - return null; - - Geometry geom; - if (m_smallCursor != null && m_smallCursor.hasNext()) {// when dimension mask is used, we produce a - geom = m_smallCursor.next(); - if (geom != null) - return geom; - else - m_smallCursor = null;// done with the small cursor - } - - if (m_inputGeoms.hasNext()) { - geom = m_inputGeoms.next(); - if (m_dimensionMask == -1) { - Geometry resGeom = intersect(geom); - assert (resGeom != null); - return resGeom; - } else { - m_smallCursor = intersectEx(geom); - Geometry resGeom = m_smallCursor.next(); - assert (resGeom != null); - return resGeom; - } - } - return null; - } - - Geometry intersect(Geometry input_geom) { - Geometry dst_geom = tryNativeImplementation_(input_geom); - if (dst_geom != null) - return dst_geom; - - Envelope2D commonExtent = InternalUtils.getMergedExtent( - m_geomIntersector, input_geom); - - // return Topological_operations::intersection(input_geom, - // m_geomIntersector, m_spatial_reference, m_progress_tracker); - // Preprocess geometries to be clipped to the extent of intersection to - // get rid of extra segments. - double t = InternalUtils.calculateToleranceFromGeometry(m_spatial_reference, commonExtent, true); - Envelope2D env = new Envelope2D(); - m_geomIntersector.queryEnvelope2D(env); - Envelope2D env1 = new Envelope2D(); - input_geom.queryEnvelope2D(env1); - env.inflate(2.0 * t, 2.0 * t); - env.intersect(env1); - assert (!env.isEmpty()); - env.inflate(100 * t, 100 * t); - double tol = 0; - Geometry clippedIntersector = Clipper.clip(m_geomIntersector, env, tol, - 0.0); - Geometry clippedInputGeom = Clipper.clip(input_geom, env, tol, 0.0); - // perform the clip - return TopologicalOperations.intersection(clippedInputGeom, - clippedIntersector, m_spatial_reference, m_progress_tracker); - } - - // Parses the input vector to ensure the out result contains only geometries - // as indicated with the dimensionMask - GeometryCursor prepareVector_(VertexDescription descr, int dimensionMask, - Geometry[] res_vec) { - int inext = 0; - if ((dimensionMask & 1) != 0) { - if (res_vec[0] == null) - res_vec[0] = new MultiPoint(descr); - inext++; - } else { - for (int i = 0; i < res_vec.length - 1; i++) - res_vec[i] = res_vec[i + 1]; - } - - if ((dimensionMask & 2) != 0) { - if (res_vec[inext] == null) - res_vec[inext] = new Polyline(descr); - inext++; - } else { - for (int i = inext; i < res_vec.length - 1; i++) - res_vec[i] = res_vec[i + 1]; - } - - if ((dimensionMask & 4) != 0) { - if (res_vec[inext] == null) - res_vec[inext] = new Polygon(descr); - inext++; - } else { - for (int i = inext; i < res_vec.length - 1; i++) - res_vec[i] = res_vec[i + 1]; - } - - if (inext != 3) { - Geometry[] r = new Geometry[inext]; - for (int i = 0; i < inext; i++) - r[i] = res_vec[i]; - - return new SimpleGeometryCursor(r); - } else { - return new SimpleGeometryCursor(res_vec); - } - } - - GeometryCursor intersectEx(Geometry input_geom) { - assert (m_dimensionMask != -1); - Geometry dst_geom = tryNativeImplementation_(input_geom); - if (dst_geom != null) { - Geometry[] res_vec = new Geometry[3]; - res_vec[dst_geom.getDimension()] = dst_geom; - return prepareVector_(input_geom.getDescription(), m_dimensionMask, - res_vec); - } - - Envelope2D commonExtent = InternalUtils.getMergedExtent( - m_geomIntersector, input_geom); - double t = InternalUtils.calculateToleranceFromGeometry( - m_spatial_reference, commonExtent, true); - - // Preprocess geometries to be clipped to the extent of intersection to - // get rid of extra segments. - - Envelope2D env = new Envelope2D(); - m_geomIntersector.queryEnvelope2D(env); - env.inflate(2 * t, 2 * t); - Envelope2D env1 = new Envelope2D(); - input_geom.queryEnvelope2D(env1); - env.intersect(env1); - assert (!env.isEmpty()); - env.inflate(100 * t, 100 * t); - double tol = 0; - Geometry clippedIntersector = Clipper.clip(m_geomIntersector, env, tol, - 0.0); - Geometry clippedInputGeom = Clipper.clip(input_geom, env, tol, 0.0); - // perform the clip - Geometry[] res_vec; - res_vec = TopologicalOperations.intersectionEx(clippedInputGeom, - clippedIntersector, m_spatial_reference, m_progress_tracker); - return prepareVector_(input_geom.getDescription(), m_dimensionMask, - res_vec); - } - - Geometry tryNativeImplementation_(Geometry input_geom) { - // A note on attributes: - // 1. The geometry with lower dimension wins in regard to the - // attributes. - // 2. If the dimensions are the same, the input_geometry attributes win. - // 3. The exception to the 2. is when the input is an Envelope, and the - // intersector is a polygon, then the intersector wins. - - // A note on the tolerance: - // This operator performs a simple intersection operation. Should it use - // the tolerance? - // Example: Point is intersected by the envelope. - // If it is slightly outside of the envelope, should we still return it - // if it is closer than the tolerance? - // Should we do crack and cluster and snap the point coordinates to the - // envelope boundary? - // - // Consider floating point arithmetics approach. When you compare - // doubles, you should use an epsilon (equals means ::fabs(a - b) < - // eps), however when you add/subtract, etc them, you do not use - // epsilon. - // Shouldn't we do same here? Relational operators use tolerance, but - // the action operators don't. - - Envelope2D mergedExtent = InternalUtils.getMergedExtent(input_geom, - m_geomIntersector); - double tolerance = InternalUtils.calculateToleranceFromGeometry( - m_spatial_reference, mergedExtent, false); - - int gtInput = input_geom.getType().value(); - boolean bInputEmpty = input_geom.isEmpty(); - boolean bGeomIntersectorEmpty = m_geomIntersector.isEmpty(); - boolean bResultIsEmpty = bInputEmpty || bGeomIntersectorEmpty; - if (!bResultIsEmpty) {// test envelopes - Envelope2D env2D1 = new Envelope2D(); - input_geom.queryEnvelope2D(env2D1); - Envelope2D env2D2 = new Envelope2D(); - m_geomIntersector.queryEnvelope2D(env2D2); - env2D2.inflate(2.0 * tolerance, 2.0 * tolerance); - bResultIsEmpty = !env2D1.isIntersecting(env2D2); - } - - if (!bResultIsEmpty) {// try accelerated test - int res = OperatorInternalRelationUtils - .quickTest2D_Accelerated_DisjointOrContains( - m_geomIntersector, input_geom, tolerance); - if (res == OperatorInternalRelationUtils.Relation.Disjoint) {// disjoint - bResultIsEmpty = true; - } else if ((res & OperatorInternalRelationUtils.Relation.Within) != 0) {// intersector - // is - // within - // the - // input_geom - // TODO: - // assign - // input_geom - // attributes - // first - return m_geomIntersector; - } else if ((res & OperatorInternalRelationUtils.Relation.Contains) != 0) {// intersector - // contains - // input_geom - return input_geom; - } - } - - if (bResultIsEmpty) {// When one geometry or both are empty, we need to - // return an empty geometry. - // Here we do that end also ensure the type is - // correct. - // That is the lower dimension need to be - // returned. Also, for Point vs Multi_point, an - // empty Point need to be returned. - int dim1 = Geometry.getDimensionFromType(gtInput); - int dim2 = Geometry.getDimensionFromType(m_geomIntersectorType); - if (dim1 < dim2) - return returnEmpty_(input_geom, bInputEmpty); - else if (dim1 > dim2) - return returnEmptyIntersector_(); - else if (dim1 == 0) { - if (gtInput == Geometry.GeometryType.MultiPoint - && m_geomIntersectorType == Geometry.GeometryType.Point) {// point - // vs - // Multi_point - // need - // special - // treatment - // to - // ensure - // Point - // is - // returned - // always. - return returnEmptyIntersector_(); - } else - // Both input and intersector have same gtype, or input is - // Point. - return returnEmpty_(input_geom, bInputEmpty); - } else - return returnEmpty_(input_geom, bInputEmpty); - } - - // Note: No empty geometries after this point! - - // Warning: Do not try clip for polylines and polygons. - - // Try clip of Envelope with Envelope. - if ((m_dimensionMask == -1 || m_dimensionMask == (1 << 2)) - && gtInput == Geometry.GeometryType.Envelope - && m_geomIntersectorType == Geometry.GeometryType.Envelope) { - Envelope env1 = (Envelope) input_geom; - Envelope env2 = (Envelope) m_geomIntersector; - Envelope2D env2D_1 = new Envelope2D(); - env1.queryEnvelope2D(env2D_1); - Envelope2D env2D_2 = new Envelope2D(); - env2.queryEnvelope2D(env2D_2); - env2D_1.intersect(env2D_2); - Envelope result_env = new Envelope(); - env1.copyTo(result_env); - result_env.setEnvelope2D(env2D_1); - return result_env; - } - - // Use clip for Point and Multi_point with Envelope - if ((gtInput == Geometry.GeometryType.Envelope && Geometry - .getDimensionFromType(m_geomIntersectorType) == 0) - || (m_geomIntersectorType == Geometry.GeometryType.Envelope && Geometry - .getDimensionFromType(gtInput) == 0)) { - Envelope env = gtInput == Geometry.GeometryType.Envelope ? (Envelope) input_geom - : (Envelope) m_geomIntersector; - Geometry other = gtInput == Geometry.GeometryType.Envelope ? m_geomIntersector - : input_geom; - Envelope2D env_2D = new Envelope2D(); - env.queryEnvelope2D(env_2D); - return Clipper.clip(other, env_2D, tolerance, 0); - } - - if ((Geometry.getDimensionFromType(gtInput) == 0 && Geometry - .getDimensionFromType(m_geomIntersectorType) > 0) - || (Geometry.getDimensionFromType(gtInput) > 0 && Geometry - .getDimensionFromType(m_geomIntersectorType) == 0)) {// multipoint - // intersection - double tolerance1 = InternalUtils.calculateToleranceFromGeometry( - m_spatial_reference, input_geom, false); - if (gtInput == Geometry.GeometryType.MultiPoint) - return TopologicalOperations.intersection( - (MultiPoint) input_geom, m_geomIntersector, tolerance1); - if (gtInput == Geometry.GeometryType.Point) - return TopologicalOperations.intersection((Point) input_geom, - m_geomIntersector, tolerance1); - if (m_geomIntersectorType == Geometry.GeometryType.MultiPoint) - return TopologicalOperations.intersection( - (MultiPoint) m_geomIntersector, input_geom, tolerance1); - if (m_geomIntersectorType == Geometry.GeometryType.Point) - return TopologicalOperations.intersection( - (Point) m_geomIntersector, input_geom, tolerance1); - throw GeometryException.GeometryInternalError(); - } - - // Try Polyline vs Polygon - if ((m_dimensionMask == -1 || m_dimensionMask == (1 << 1)) - && (gtInput == Geometry.GeometryType.Polyline) - && (m_geomIntersectorType == Geometry.GeometryType.Polygon)) { - return tryFastIntersectPolylinePolygon_((Polyline) (input_geom), - (Polygon) (m_geomIntersector)); - } - - // Try Polygon vs Polyline - if ((m_dimensionMask == -1 || m_dimensionMask == (1 << 1)) - && (gtInput == Geometry.GeometryType.Polygon) - && (m_geomIntersectorType == Geometry.GeometryType.Polyline)) { - return tryFastIntersectPolylinePolygon_( - (Polyline) (m_geomIntersector), (Polygon) (input_geom)); - } - - return null; - } - - Geometry tryFastIntersectPolylinePolygon_(Polyline polyline, Polygon polygon) { - MultiPathImpl polylineImpl = (MultiPathImpl) polyline._getImpl(); - MultiPathImpl polygonImpl = (MultiPathImpl) polygon._getImpl(); - - double tolerance = InternalUtils.calculateToleranceFromGeometry( - m_spatial_reference, polygon, false); - Envelope2D clipEnvelope = new Envelope2D(); - { - polygonImpl.queryEnvelope2D(clipEnvelope); - Envelope2D env1 = new Envelope2D(); - polylineImpl.queryEnvelope2D(env1); - env1.inflate(2.0 * tolerance, 2.0 * tolerance); - clipEnvelope.intersect(env1); - assert (!clipEnvelope.isEmpty()); - } - - clipEnvelope.inflate(10 * tolerance, 10 * tolerance); - - if (true) { - double tol = 0; - Geometry clippedPolyline = Clipper.clip(polyline, clipEnvelope, - tol, 0.0); - polyline = (Polyline) clippedPolyline; - polylineImpl = (MultiPathImpl) polyline._getImpl(); - } - - AttributeStreamOfInt32 clipResult = new AttributeStreamOfInt32(0); - int unresolvedSegments = -1; - GeometryAccelerators accel = polygonImpl._getAccelerators(); - if (accel != null) { - RasterizedGeometry2D rgeom = accel.getRasterizedGeometry(); - if (rgeom != null) { - unresolvedSegments = 0; - clipResult.reserve(polylineImpl.getPointCount() - + polylineImpl.getPathCount()); - Envelope2D seg_env = new Envelope2D(); - SegmentIteratorImpl iter = polylineImpl.querySegmentIterator(); - while (iter.nextPath()) { - while (iter.hasNextSegment()) { - Segment seg = iter.nextSegment(); - seg.queryEnvelope2D(seg_env); - RasterizedGeometry2D.HitType hit = rgeom - .queryEnvelopeInGeometry(seg_env); - if (hit == RasterizedGeometry2D.HitType.Inside) { - clipResult.add(1); - } else if (hit == RasterizedGeometry2D.HitType.Outside) { - clipResult.add(0); - } else { - clipResult.add(-1); - unresolvedSegments++; - } - } - } - } - } - - if (polygon.getPointCount() > 5) { - double tol = 0; - Geometry clippedPolygon = Clipper.clip(polygon, clipEnvelope, tol, - 0.0); - - polygon = (Polygon) clippedPolygon; - polygonImpl = (MultiPathImpl) polygon._getImpl(); - accel = polygonImpl._getAccelerators();//update accelerators - } - - if (unresolvedSegments < 0) { - unresolvedSegments = polylineImpl.getSegmentCount(); - } - - // Some heuristics to decide if it makes sense to go with fast intersect - // vs going with the regular planesweep. - double totalPoints = (double) (polylineImpl.getPointCount() + polygonImpl - .getPointCount()); - double thisAlgorithmComplexity = ((double) unresolvedSegments * polygonImpl - .getPointCount());// assume the worst case. - double planesweepComplexity = Math.log(totalPoints) * totalPoints; - double empiricConstantFactorPlaneSweep = 4; - if (thisAlgorithmComplexity > planesweepComplexity - * empiricConstantFactorPlaneSweep) { - // Based on the number of input points, we deduced that the - // plansweep performance should be better than the brute force - // performance. - return null; // resort to planesweep if quadtree does not help - } - - QuadTreeImpl polygonQuadTree = null; - SegmentIteratorImpl polygonIter = polygonImpl.querySegmentIterator(); - // Some logic to decide if it makes sense to build a quadtree on the - // polygon segments - if (accel != null && accel.getQuadTree() != null) - polygonQuadTree = accel.getQuadTree(); - - if (polygonQuadTree == null && polygonImpl.getPointCount() > 20) { - polygonQuadTree = InternalUtils.buildQuadTree(polygonImpl); - } - - Polyline result_polyline = (Polyline) polyline.createInstance(); - MultiPathImpl resultPolylineImpl = (MultiPathImpl) result_polyline - ._getImpl(); - QuadTreeImpl.QuadTreeIteratorImpl qIter = null; - SegmentIteratorImpl polylineIter = polylineImpl.querySegmentIterator(); - double[] params = new double[9]; - AttributeStreamOfDbl intersections = new AttributeStreamOfDbl(0); - SegmentBuffer segmentBuffer = new SegmentBuffer(); - int start_index = -1; - int inCount = 0; - int segIndex = 0; - boolean bOptimized = clipResult.size() > 0; - - // The algorithm is like that: - // Loop through all the segments of the polyline. - // For each polyline segment, intersect it with each of the polygon - // segments. - // If no intersections found then, - // If the polyline segment is completely inside, it is added to the - // result polyline. - // If it is outside, it is thrown out. - // If it intersects, then cut the polyline segment to pieces and test - // each part of the intersected result. - // The cut pieces will either have one point inside, or one point - // outside, or the middle point inside/outside. - // - int polylinePathIndex = -1; - - while (polylineIter.nextPath()) { - polylinePathIndex = polylineIter.getPathIndex(); - int stateNewPath = 0; - int stateAddSegment = 1; - int stateManySegments = 2; - int stateManySegmentsContinuePath = 2; - int stateManySegmentsNewPath = 3; - int state = stateNewPath; - start_index = -1; - inCount = 0; - - while (polylineIter.hasNextSegment()) { - int clipStatus = bOptimized ? (int) clipResult.get(segIndex) - : -1; - segIndex++; - Segment polylineSeg = polylineIter.nextSegment(); - if (clipStatus < 0) { - assert (clipStatus == -1); - // Analyse polyline segment for intersection with the - // polygon. - if (polygonQuadTree != null) { - if (qIter == null) { - qIter = polygonQuadTree.getIterator(polylineSeg, - tolerance); - } else { - qIter.resetIterator(polylineSeg, tolerance); - } - - int path_index = -1; - for (int ind = qIter.next(); ind != -1; ind = qIter - .next()) { - polygonIter.resetToVertex(polygonQuadTree - .getElement(ind)); // path_index - path_index = polygonIter.getPathIndex(); - Segment polygonSeg = polygonIter.nextSegment(); - // intersect polylineSeg and polygonSeg. - int count = polylineSeg.intersect(polygonSeg, null, - params, null, tolerance); - for (int i = 0; i < count; i++) - intersections.add(params[i]); - } - } else {// no quadtree built - polygonIter.resetToFirstPath(); - while (polygonIter.nextPath()) { - while (polygonIter.hasNextSegment()) { - Segment polygonSeg = polygonIter.nextSegment(); - // intersect polylineSeg and polygonSeg. - int count = polylineSeg.intersect(polygonSeg, - null, params, null, tolerance); - for (int i = 0; i < count; i++) - intersections.add(params[i]); - } - } - } - - if (intersections.size() > 0) {// intersections detected. - intersections.sort(0, intersections.size()); // std::sort(intersections.begin(), - // intersections.end()); - - double t0 = 0; - intersections.add(1.0); - int status = -1; - for (int i = 0, n = intersections.size(); i < n; i++) { - double t = intersections.get(i); - if (t == t0) { - continue; - } - boolean bWholeSegment = false; - Segment resSeg; - if (t0 != 0 || t != 1.0) { - polylineSeg.cut(t0, t, segmentBuffer); - resSeg = segmentBuffer.get(); - } else { - resSeg = polylineSeg; - bWholeSegment = true; - } - - if (state >= stateManySegments) { - resultPolylineImpl.addSegmentsFromPath( - polylineImpl, polylinePathIndex, - start_index, inCount, - state == stateManySegmentsNewPath); - if (analyseClipSegment_(polygon, - resSeg.getStartXY(), tolerance) != 1) { - if (analyseClipSegment_(polygon, resSeg, - tolerance) != 1) { - return null; //someting went wrong we'll falback to slower but robust planesweep code. - } - } - - resultPolylineImpl.addSegment(resSeg, false); - state = stateAddSegment; - inCount = 0; - } else { - status = analyseClipSegment_(polygon, resSeg, - tolerance); - switch (status) { - case 1: - if (!bWholeSegment) { - resultPolylineImpl.addSegment(resSeg, - state == stateNewPath); - state = stateAddSegment; - } else { - if (state < stateManySegments) { - start_index = polylineIter - .getStartPointIndex() - - polylineImpl - .getPathStart(polylinePathIndex); - inCount = 1; - - if (state == stateNewPath) - state = stateManySegmentsNewPath; - else { - assert (state == stateAddSegment); - state = stateManySegmentsContinuePath; - } - } else - inCount++; - } - - break; - case 0: - state = stateNewPath; - start_index = -1; - inCount = 0; - break; - default: - return null;// may happen if a segment - // coincides with the border. - } - } - - t0 = t; - } - } else { - clipStatus = analyseClipSegment_(polygon, - polylineSeg.getStartXY(), tolerance);// simple - // case - // no - // intersection. - // Both - // points - // must - // be - // inside. - if (clipStatus < 0) { - assert (clipStatus >= 0); - return null;// something goes wrong, resort to - // planesweep - } - - assert (analyseClipSegment_(polygon, - polylineSeg.getEndXY(), tolerance) == clipStatus); - if (clipStatus == 1) {// the whole segment inside - if (state < stateManySegments) { - assert (inCount == 0); - start_index = polylineIter.getStartPointIndex() - - polylineImpl - .getPathStart(polylinePathIndex); - if (state == stateNewPath) - state = stateManySegmentsNewPath; - else { - assert (state == stateAddSegment); - state = stateManySegmentsContinuePath; - } - } - - inCount++; - } else { - assert (state < stateManySegments); - start_index = -1; - inCount = 0; - } - } - - intersections.clear(false); - } else {// clip status is determined by other means - if (clipStatus == 0) {// outside - assert (analyseClipSegment_(polygon, polylineSeg, - tolerance) == 0); - assert (start_index < 0); - assert (inCount == 0); - continue; - } - - if (clipStatus == 1) { - assert (analyseClipSegment_(polygon, polylineSeg, - tolerance) == 1); - if (state == stateNewPath) { - state = stateManySegmentsNewPath; - start_index = polylineIter.getStartPointIndex() - - polylineImpl - .getPathStart(polylinePathIndex); - } else if (state == stateAddSegment) { - state = stateManySegmentsContinuePath; - start_index = polylineIter.getStartPointIndex() - - polylineImpl - .getPathStart(polylinePathIndex); - } else - assert (state >= stateManySegments); - - inCount++; - continue; - } - } - } - - if (state >= stateManySegments) { - resultPolylineImpl.addSegmentsFromPath(polylineImpl, - polylinePathIndex, start_index, inCount, - state == stateManySegmentsNewPath); - start_index = -1; - } - } - - return result_polyline; - } - - int analyseClipSegment_(Polygon polygon, Point2D pt, double tol) { - int v = PointInPolygonHelper.isPointInPolygon(polygon, pt, tol); - return v; - } - - int analyseClipSegment_(Polygon polygon, Segment seg, double tol) { - Point2D pt_1 = seg.getStartXY(); - Point2D pt_2 = seg.getEndXY(); - int v_1 = PointInPolygonHelper.isPointInPolygon(polygon, pt_1, tol); - int v_2 = PointInPolygonHelper.isPointInPolygon(polygon, pt_2, tol); - if ((v_1 == 1 && v_2 == 0) || (v_1 == 0 && v_2 == 1)) { - // Operator_factory_local::SaveJSONToTextFileDbg("c:/temp/badPointInPolygon.json", - // polygon, m_spatial_reference); - assert (false);// if happens - return -1;// something went wrong. One point is inside, the other is - // outside. Should not happen. We'll resort to - // planesweep. - } - if (v_1 == 0 || v_2 == 0) - return 0; - if (v_1 == 1 || v_2 == 1) - return 1; - - Point2D midPt = new Point2D(); - midPt.add(pt_1, pt_2); - midPt.scale(0.5);// calculate midpoint - int v = PointInPolygonHelper.isPointInPolygon(polygon, midPt, tol); - if (v == 0) { - return 0; - } - - if (v == 1) { - return 1; - } - - return -1; - } - - Geometry normalizeIntersectionOutput(Geometry geom, int GT_1, int GT_2) { - if (GT_1 == Geometry.GeometryType.Point - || GT_2 == Geometry.GeometryType.Point) { - assert (geom.getType().value() == Geometry.GeometryType.Point); - } - if (GT_1 == Geometry.GeometryType.MultiPoint) { - if (geom.getType().value() == Geometry.GeometryType.Point) { - MultiPoint mp = new MultiPoint(geom.getDescription()); - if (!geom.isEmpty()) - mp.add((Point) geom); - return mp; - } - } - - return geom; - } - - static Geometry returnEmpty_(Geometry geom, boolean bEmpty) { - return bEmpty ? geom : geom.createInstance(); - } - - Geometry returnEmptyIntersector_() { - if (m_geomIntersectorEmptyGeom == null) - m_geomIntersectorEmptyGeom = m_geomIntersector.createInstance(); - - return m_geomIntersectorEmptyGeom; - } - - // virtual boolean IsRecycling() OVERRIDE { return false; } + GeometryCursor m_smallCursor; + ProgressTracker m_progress_tracker; + SpatialReference m_spatial_reference; + Geometry m_geomIntersector; + Geometry m_geomIntersectorEmptyGeom;// holds empty geometry of intersector + // type. + int m_geomIntersectorType; + int m_currentGeomType; + int m_dimensionMask; + boolean m_bEmpty; + + OperatorIntersectionCursor(GeometryCursor inputGeoms, + GeometryCursor geomIntersector, + SpatialReference sr, + ProgressTracker progress_tracker, + int dimensionMask) { + m_bEmpty = geomIntersector == null; + m_inputGeoms = inputGeoms; + m_spatial_reference = sr; + m_geomIntersector = geomIntersector.next(); + m_geomIntersectorType = m_geomIntersector.getType().value(); + m_currentGeomType = Geometry.Type.Unknown.value(); + m_progress_tracker = progress_tracker; + m_dimensionMask = dimensionMask; + if (m_dimensionMask != -1 + && (m_dimensionMask <= 0 || m_dimensionMask > 7)) + throw new IllegalArgumentException("bad dimension mask");// dimension + // mask + // can + // be + // -1, + // for + // the + // default + // behavior, + // or a + // value + // between + // 1 and + // 7. + } + + @Override + public Geometry next() { + if (m_bEmpty) + return null; + + Geometry geom; + if (m_smallCursor != null && m_smallCursor.hasNext()) {// when dimension mask is used, we produce a + geom = m_smallCursor.next(); + if (geom != null) + return geom; + else + m_smallCursor = null;// done with the small cursor + } + + if (m_inputGeoms.hasNext()) { + geom = m_inputGeoms.next(); + if (m_dimensionMask == -1) { + Geometry resGeom = intersect(geom); + assert (resGeom != null); + return resGeom; + } else { + m_smallCursor = intersectEx(geom); + Geometry resGeom = m_smallCursor.next(); + assert (resGeom != null); + return resGeom; + } + } + return null; + } + + Geometry intersect(Geometry input_geom) { + Geometry dst_geom = tryNativeImplementation_(input_geom); + if (dst_geom != null) + return dst_geom; + + Envelope2D commonExtent = InternalUtils.getMergedExtent( + m_geomIntersector, input_geom); + + // return Topological_operations::intersection(input_geom, + // m_geomIntersector, m_spatial_reference, m_progress_tracker); + // Preprocess geometries to be clipped to the extent of intersection to + // get rid of extra segments. + double t = InternalUtils.calculateToleranceFromGeometry(m_spatial_reference, commonExtent, true); + Envelope2D env = new Envelope2D(); + m_geomIntersector.queryEnvelope2D(env); + Envelope2D env1 = new Envelope2D(); + input_geom.queryEnvelope2D(env1); + env.inflate(2.0 * t, 2.0 * t); + env.intersect(env1); + assert (!env.isEmpty()); + env.inflate(100 * t, 100 * t); + double tol = 0; + Geometry clippedIntersector = Clipper.clip(m_geomIntersector, env, tol, + 0.0); + Geometry clippedInputGeom = Clipper.clip(input_geom, env, tol, 0.0); + // perform the clip + return TopologicalOperations.intersection(clippedInputGeom, + clippedIntersector, m_spatial_reference, m_progress_tracker); + } + + // Parses the input vector to ensure the out result contains only geometries + // as indicated with the dimensionMask + GeometryCursor prepareVector_(VertexDescription descr, int dimensionMask, + Geometry[] res_vec) { + int inext = 0; + if ((dimensionMask & 1) != 0) { + if (res_vec[0] == null) + res_vec[0] = new MultiPoint(descr); + inext++; + } else { + for (int i = 0; i < res_vec.length - 1; i++) + res_vec[i] = res_vec[i + 1]; + } + + if ((dimensionMask & 2) != 0) { + if (res_vec[inext] == null) + res_vec[inext] = new Polyline(descr); + inext++; + } else { + for (int i = inext; i < res_vec.length - 1; i++) + res_vec[i] = res_vec[i + 1]; + } + + if ((dimensionMask & 4) != 0) { + if (res_vec[inext] == null) + res_vec[inext] = new Polygon(descr); + inext++; + } else { + for (int i = inext; i < res_vec.length - 1; i++) + res_vec[i] = res_vec[i + 1]; + } + + if (inext != 3) { + Geometry[] r = new Geometry[inext]; + for (int i = 0; i < inext; i++) + r[i] = res_vec[i]; + + return new SimpleGeometryCursor(r); + } else { + return new SimpleGeometryCursor(res_vec); + } + } + + GeometryCursor intersectEx(Geometry input_geom) { + assert (m_dimensionMask != -1); + Geometry dst_geom = tryNativeImplementation_(input_geom); + if (dst_geom != null) { + Geometry[] res_vec = new Geometry[3]; + res_vec[dst_geom.getDimension()] = dst_geom; + return prepareVector_(input_geom.getDescription(), m_dimensionMask, + res_vec); + } + + Envelope2D commonExtent = InternalUtils.getMergedExtent( + m_geomIntersector, input_geom); + double t = InternalUtils.calculateToleranceFromGeometry( + m_spatial_reference, commonExtent, true); + + // Preprocess geometries to be clipped to the extent of intersection to + // get rid of extra segments. + + Envelope2D env = new Envelope2D(); + m_geomIntersector.queryEnvelope2D(env); + env.inflate(2 * t, 2 * t); + Envelope2D env1 = new Envelope2D(); + input_geom.queryEnvelope2D(env1); + env.intersect(env1); + assert (!env.isEmpty()); + env.inflate(100 * t, 100 * t); + double tol = 0; + Geometry clippedIntersector = Clipper.clip(m_geomIntersector, env, tol, + 0.0); + Geometry clippedInputGeom = Clipper.clip(input_geom, env, tol, 0.0); + // perform the clip + Geometry[] res_vec; + res_vec = TopologicalOperations.intersectionEx(clippedInputGeom, + clippedIntersector, m_spatial_reference, m_progress_tracker); + return prepareVector_(input_geom.getDescription(), m_dimensionMask, + res_vec); + } + + Geometry tryNativeImplementation_(Geometry input_geom) { + // A note on attributes: + // 1. The geometry with lower dimension wins in regard to the + // attributes. + // 2. If the dimensions are the same, the input_geometry attributes win. + // 3. The exception to the 2. is when the input is an Envelope, and the + // intersector is a polygon, then the intersector wins. + + // A note on the tolerance: + // This operator performs a simple intersection operation. Should it use + // the tolerance? + // Example: Point is intersected by the envelope. + // If it is slightly outside of the envelope, should we still return it + // if it is closer than the tolerance? + // Should we do crack and cluster and snap the point coordinates to the + // envelope boundary? + // + // Consider floating point arithmetics approach. When you compare + // doubles, you should use an epsilon (equals means ::fabs(a - b) < + // eps), however when you add/subtract, etc them, you do not use + // epsilon. + // Shouldn't we do same here? Relational operators use tolerance, but + // the action operators don't. + + Envelope2D mergedExtent = InternalUtils.getMergedExtent(input_geom, + m_geomIntersector); + double tolerance = InternalUtils.calculateToleranceFromGeometry( + m_spatial_reference, mergedExtent, false); + + int gtInput = input_geom.getType().value(); + boolean bInputEmpty = input_geom.isEmpty(); + boolean bGeomIntersectorEmpty = m_geomIntersector.isEmpty(); + boolean bResultIsEmpty = bInputEmpty || bGeomIntersectorEmpty; + if (!bResultIsEmpty) {// test envelopes + Envelope2D env2D1 = new Envelope2D(); + input_geom.queryEnvelope2D(env2D1); + Envelope2D env2D2 = new Envelope2D(); + m_geomIntersector.queryEnvelope2D(env2D2); + env2D2.inflate(2.0 * tolerance, 2.0 * tolerance); + bResultIsEmpty = !env2D1.isIntersecting(env2D2); + } + + if (!bResultIsEmpty) {// try accelerated test + int res = OperatorInternalRelationUtils + .quickTest2D_Accelerated_DisjointOrContains( + m_geomIntersector, input_geom, tolerance); + if (res == OperatorInternalRelationUtils.Relation.Disjoint) {// disjoint + bResultIsEmpty = true; + } else if ((res & OperatorInternalRelationUtils.Relation.Within) != 0) {// intersector + // is + // within + // the + // input_geom + // TODO: + // assign + // input_geom + // attributes + // first + return m_geomIntersector; + } else if ((res & OperatorInternalRelationUtils.Relation.Contains) != 0) {// intersector + // contains + // input_geom + return input_geom; + } + } + + if (bResultIsEmpty) {// When one geometry or both are empty, we need to + // return an empty geometry. + // Here we do that end also ensure the type is + // correct. + // That is the lower dimension need to be + // returned. Also, for Point vs Multi_point, an + // empty Point need to be returned. + int dim1 = Geometry.getDimensionFromType(gtInput); + int dim2 = Geometry.getDimensionFromType(m_geomIntersectorType); + if (dim1 < dim2) + return returnEmpty_(input_geom, bInputEmpty); + else if (dim1 > dim2) + return returnEmptyIntersector_(); + else if (dim1 == 0) { + if (gtInput == Geometry.GeometryType.MultiPoint + && m_geomIntersectorType == Geometry.GeometryType.Point) {// point + // vs + // Multi_point + // need + // special + // treatment + // to + // ensure + // Point + // is + // returned + // always. + return returnEmptyIntersector_(); + } else + // Both input and intersector have same gtype, or input is + // Point. + return returnEmpty_(input_geom, bInputEmpty); + } else + return returnEmpty_(input_geom, bInputEmpty); + } + + // Note: No empty geometries after this point! + + // Warning: Do not try clip for polylines and polygons. + + // Try clip of Envelope with Envelope. + if ((m_dimensionMask == -1 || m_dimensionMask == (1 << 2)) + && gtInput == Geometry.GeometryType.Envelope + && m_geomIntersectorType == Geometry.GeometryType.Envelope) { + Envelope env1 = (Envelope) input_geom; + Envelope env2 = (Envelope) m_geomIntersector; + Envelope2D env2D_1 = new Envelope2D(); + env1.queryEnvelope2D(env2D_1); + Envelope2D env2D_2 = new Envelope2D(); + env2.queryEnvelope2D(env2D_2); + env2D_1.intersect(env2D_2); + Envelope result_env = new Envelope(); + env1.copyTo(result_env); + result_env.setEnvelope2D(env2D_1); + return result_env; + } + + // Use clip for Point and Multi_point with Envelope + if ((gtInput == Geometry.GeometryType.Envelope && Geometry + .getDimensionFromType(m_geomIntersectorType) == 0) + || (m_geomIntersectorType == Geometry.GeometryType.Envelope && Geometry + .getDimensionFromType(gtInput) == 0)) { + Envelope env = gtInput == Geometry.GeometryType.Envelope ? (Envelope) input_geom + : (Envelope) m_geomIntersector; + Geometry other = gtInput == Geometry.GeometryType.Envelope ? m_geomIntersector + : input_geom; + Envelope2D env_2D = new Envelope2D(); + env.queryEnvelope2D(env_2D); + return Clipper.clip(other, env_2D, tolerance, 0); + } + + if ((Geometry.getDimensionFromType(gtInput) == 0 && Geometry + .getDimensionFromType(m_geomIntersectorType) > 0) + || (Geometry.getDimensionFromType(gtInput) > 0 && Geometry + .getDimensionFromType(m_geomIntersectorType) == 0)) {// multipoint + // intersection + double tolerance1 = InternalUtils.calculateToleranceFromGeometry( + m_spatial_reference, input_geom, false); + if (gtInput == Geometry.GeometryType.MultiPoint) + return TopologicalOperations.intersection( + (MultiPoint) input_geom, m_geomIntersector, tolerance1); + if (gtInput == Geometry.GeometryType.Point) + return TopologicalOperations.intersection((Point) input_geom, + m_geomIntersector, tolerance1); + if (m_geomIntersectorType == Geometry.GeometryType.MultiPoint) + return TopologicalOperations.intersection( + (MultiPoint) m_geomIntersector, input_geom, tolerance1); + if (m_geomIntersectorType == Geometry.GeometryType.Point) + return TopologicalOperations.intersection( + (Point) m_geomIntersector, input_geom, tolerance1); + throw GeometryException.GeometryInternalError(); + } + + // Try Polyline vs Polygon + if ((m_dimensionMask == -1 || m_dimensionMask == (1 << 1)) + && (gtInput == Geometry.GeometryType.Polyline) + && (m_geomIntersectorType == Geometry.GeometryType.Polygon)) { + return tryFastIntersectPolylinePolygon_((Polyline) (input_geom), + (Polygon) (m_geomIntersector)); + } + + // Try Polygon vs Polyline + if ((m_dimensionMask == -1 || m_dimensionMask == (1 << 1)) + && (gtInput == Geometry.GeometryType.Polygon) + && (m_geomIntersectorType == Geometry.GeometryType.Polyline)) { + return tryFastIntersectPolylinePolygon_( + (Polyline) (m_geomIntersector), (Polygon) (input_geom)); + } + + return null; + } + + Geometry tryFastIntersectPolylinePolygon_(Polyline polyline, Polygon polygon) { + MultiPathImpl polylineImpl = (MultiPathImpl) polyline._getImpl(); + MultiPathImpl polygonImpl = (MultiPathImpl) polygon._getImpl(); + + double tolerance = InternalUtils.calculateToleranceFromGeometry( + m_spatial_reference, polygon, false); + Envelope2D clipEnvelope = new Envelope2D(); + { + polygonImpl.queryEnvelope2D(clipEnvelope); + Envelope2D env1 = new Envelope2D(); + polylineImpl.queryEnvelope2D(env1); + env1.inflate(2.0 * tolerance, 2.0 * tolerance); + clipEnvelope.intersect(env1); + assert (!clipEnvelope.isEmpty()); + } + + clipEnvelope.inflate(10 * tolerance, 10 * tolerance); + + if (true) { + double tol = 0; + Geometry clippedPolyline = Clipper.clip(polyline, clipEnvelope, + tol, 0.0); + polyline = (Polyline) clippedPolyline; + polylineImpl = (MultiPathImpl) polyline._getImpl(); + } + + AttributeStreamOfInt32 clipResult = new AttributeStreamOfInt32(0); + int unresolvedSegments = -1; + GeometryAccelerators accel = polygonImpl._getAccelerators(); + if (accel != null) { + RasterizedGeometry2D rgeom = accel.getRasterizedGeometry(); + if (rgeom != null) { + unresolvedSegments = 0; + clipResult.reserve(polylineImpl.getPointCount() + + polylineImpl.getPathCount()); + Envelope2D seg_env = new Envelope2D(); + SegmentIteratorImpl iter = polylineImpl.querySegmentIterator(); + while (iter.nextPath()) { + while (iter.hasNextSegment()) { + Segment seg = iter.nextSegment(); + seg.queryEnvelope2D(seg_env); + RasterizedGeometry2D.HitType hit = rgeom + .queryEnvelopeInGeometry(seg_env); + if (hit == RasterizedGeometry2D.HitType.Inside) { + clipResult.add(1); + } else if (hit == RasterizedGeometry2D.HitType.Outside) { + clipResult.add(0); + } else { + clipResult.add(-1); + unresolvedSegments++; + } + } + } + } + } + + if (polygon.getPointCount() > 5) { + double tol = 0; + Geometry clippedPolygon = Clipper.clip(polygon, clipEnvelope, tol, + 0.0); + + polygon = (Polygon) clippedPolygon; + polygonImpl = (MultiPathImpl) polygon._getImpl(); + accel = polygonImpl._getAccelerators();//update accelerators + } + + if (unresolvedSegments < 0) { + unresolvedSegments = polylineImpl.getSegmentCount(); + } + + // Some heuristics to decide if it makes sense to go with fast intersect + // vs going with the regular planesweep. + double totalPoints = (double) (polylineImpl.getPointCount() + polygonImpl + .getPointCount()); + double thisAlgorithmComplexity = ((double) unresolvedSegments * polygonImpl + .getPointCount());// assume the worst case. + double planesweepComplexity = Math.log(totalPoints) * totalPoints; + double empiricConstantFactorPlaneSweep = 4; + if (thisAlgorithmComplexity > planesweepComplexity + * empiricConstantFactorPlaneSweep) { + // Based on the number of input points, we deduced that the + // plansweep performance should be better than the brute force + // performance. + return null; // resort to planesweep if quadtree does not help + } + + QuadTreeImpl polygonQuadTree = null; + SegmentIteratorImpl polygonIter = polygonImpl.querySegmentIterator(); + // Some logic to decide if it makes sense to build a quadtree on the + // polygon segments + if (accel != null && accel.getQuadTree() != null) + polygonQuadTree = accel.getQuadTree(); + + if (polygonQuadTree == null && polygonImpl.getPointCount() > 20) { + polygonQuadTree = InternalUtils.buildQuadTree(polygonImpl); + } + + Polyline result_polyline = (Polyline) polyline.createInstance(); + MultiPathImpl resultPolylineImpl = (MultiPathImpl) result_polyline + ._getImpl(); + QuadTreeImpl.QuadTreeIteratorImpl qIter = null; + SegmentIteratorImpl polylineIter = polylineImpl.querySegmentIterator(); + double[] params = new double[9]; + AttributeStreamOfDbl intersections = new AttributeStreamOfDbl(0); + SegmentBuffer segmentBuffer = new SegmentBuffer(); + int start_index = -1; + int inCount = 0; + int segIndex = 0; + boolean bOptimized = clipResult.size() > 0; + + // The algorithm is like that: + // Loop through all the segments of the polyline. + // For each polyline segment, intersect it with each of the polygon + // segments. + // If no intersections found then, + // If the polyline segment is completely inside, it is added to the + // result polyline. + // If it is outside, it is thrown out. + // If it intersects, then cut the polyline segment to pieces and test + // each part of the intersected result. + // The cut pieces will either have one point inside, or one point + // outside, or the middle point inside/outside. + // + int polylinePathIndex = -1; + + while (polylineIter.nextPath()) { + polylinePathIndex = polylineIter.getPathIndex(); + int stateNewPath = 0; + int stateAddSegment = 1; + int stateManySegments = 2; + int stateManySegmentsContinuePath = 2; + int stateManySegmentsNewPath = 3; + int state = stateNewPath; + start_index = -1; + inCount = 0; + + while (polylineIter.hasNextSegment()) { + int clipStatus = bOptimized ? (int) clipResult.get(segIndex) + : -1; + segIndex++; + Segment polylineSeg = polylineIter.nextSegment(); + if (clipStatus < 0) { + assert (clipStatus == -1); + // Analyse polyline segment for intersection with the + // polygon. + if (polygonQuadTree != null) { + if (qIter == null) { + qIter = polygonQuadTree.getIterator(polylineSeg, + tolerance); + } else { + qIter.resetIterator(polylineSeg, tolerance); + } + + int path_index = -1; + for (int ind = qIter.next(); ind != -1; ind = qIter + .next()) { + polygonIter.resetToVertex(polygonQuadTree + .getElement(ind)); // path_index + path_index = polygonIter.getPathIndex(); + Segment polygonSeg = polygonIter.nextSegment(); + // intersect polylineSeg and polygonSeg. + int count = polylineSeg.intersect(polygonSeg, null, + params, null, tolerance); + for (int i = 0; i < count; i++) + intersections.add(params[i]); + } + } else {// no quadtree built + polygonIter.resetToFirstPath(); + while (polygonIter.nextPath()) { + while (polygonIter.hasNextSegment()) { + Segment polygonSeg = polygonIter.nextSegment(); + // intersect polylineSeg and polygonSeg. + int count = polylineSeg.intersect(polygonSeg, + null, params, null, tolerance); + for (int i = 0; i < count; i++) + intersections.add(params[i]); + } + } + } + + if (intersections.size() > 0) {// intersections detected. + intersections.sort(0, intersections.size()); // std::sort(intersections.begin(), + // intersections.end()); + + double t0 = 0; + intersections.add(1.0); + int status = -1; + for (int i = 0, n = intersections.size(); i < n; i++) { + double t = intersections.get(i); + if (t == t0) { + continue; + } + boolean bWholeSegment = false; + Segment resSeg; + if (t0 != 0 || t != 1.0) { + polylineSeg.cut(t0, t, segmentBuffer); + resSeg = segmentBuffer.get(); + } else { + resSeg = polylineSeg; + bWholeSegment = true; + } + + if (state >= stateManySegments) { + resultPolylineImpl.addSegmentsFromPath( + polylineImpl, polylinePathIndex, + start_index, inCount, + state == stateManySegmentsNewPath); + if (analyseClipSegment_(polygon, + resSeg.getStartXY(), tolerance) != 1) { + if (analyseClipSegment_(polygon, resSeg, + tolerance) != 1) { + return null; //someting went wrong we'll falback to slower but robust planesweep code. + } + } + + resultPolylineImpl.addSegment(resSeg, false); + state = stateAddSegment; + inCount = 0; + } else { + status = analyseClipSegment_(polygon, resSeg, + tolerance); + switch (status) { + case 1: + if (!bWholeSegment) { + resultPolylineImpl.addSegment(resSeg, + state == stateNewPath); + state = stateAddSegment; + } else { + if (state < stateManySegments) { + start_index = polylineIter + .getStartPointIndex() + - polylineImpl + .getPathStart(polylinePathIndex); + inCount = 1; + + if (state == stateNewPath) + state = stateManySegmentsNewPath; + else { + assert (state == stateAddSegment); + state = stateManySegmentsContinuePath; + } + } else + inCount++; + } + + break; + case 0: + state = stateNewPath; + start_index = -1; + inCount = 0; + break; + default: + return null;// may happen if a segment + // coincides with the border. + } + } + + t0 = t; + } + } else { + clipStatus = analyseClipSegment_(polygon, + polylineSeg.getStartXY(), tolerance);// simple + // case + // no + // intersection. + // Both + // points + // must + // be + // inside. + if (clipStatus < 0) { + assert (clipStatus >= 0); + return null;// something goes wrong, resort to + // planesweep + } + + assert (analyseClipSegment_(polygon, + polylineSeg.getEndXY(), tolerance) == clipStatus); + if (clipStatus == 1) {// the whole segment inside + if (state < stateManySegments) { + assert (inCount == 0); + start_index = polylineIter.getStartPointIndex() + - polylineImpl + .getPathStart(polylinePathIndex); + if (state == stateNewPath) + state = stateManySegmentsNewPath; + else { + assert (state == stateAddSegment); + state = stateManySegmentsContinuePath; + } + } + + inCount++; + } else { + assert (state < stateManySegments); + start_index = -1; + inCount = 0; + } + } + + intersections.clear(false); + } else {// clip status is determined by other means + if (clipStatus == 0) {// outside + assert (analyseClipSegment_(polygon, polylineSeg, + tolerance) == 0); + assert (start_index < 0); + assert (inCount == 0); + continue; + } + + if (clipStatus == 1) { + assert (analyseClipSegment_(polygon, polylineSeg, + tolerance) == 1); + if (state == stateNewPath) { + state = stateManySegmentsNewPath; + start_index = polylineIter.getStartPointIndex() + - polylineImpl + .getPathStart(polylinePathIndex); + } else if (state == stateAddSegment) { + state = stateManySegmentsContinuePath; + start_index = polylineIter.getStartPointIndex() + - polylineImpl + .getPathStart(polylinePathIndex); + } else + assert (state >= stateManySegments); + + inCount++; + continue; + } + } + } + + if (state >= stateManySegments) { + resultPolylineImpl.addSegmentsFromPath(polylineImpl, + polylinePathIndex, start_index, inCount, + state == stateManySegmentsNewPath); + start_index = -1; + } + } + + return result_polyline; + } + + int analyseClipSegment_(Polygon polygon, Point2D pt, double tol) { + int v = PointInPolygonHelper.isPointInPolygon(polygon, pt, tol); + return v; + } + + int analyseClipSegment_(Polygon polygon, Segment seg, double tol) { + Point2D pt_1 = seg.getStartXY(); + Point2D pt_2 = seg.getEndXY(); + int v_1 = PointInPolygonHelper.isPointInPolygon(polygon, pt_1, tol); + int v_2 = PointInPolygonHelper.isPointInPolygon(polygon, pt_2, tol); + if ((v_1 == 1 && v_2 == 0) || (v_1 == 0 && v_2 == 1)) { + // Operator_factory_local::SaveJSONToTextFileDbg("c:/temp/badPointInPolygon.json", + // polygon, m_spatial_reference); + assert (false);// if happens + return -1;// something went wrong. One point is inside, the other is + // outside. Should not happen. We'll resort to + // planesweep. + } + if (v_1 == 0 || v_2 == 0) + return 0; + if (v_1 == 1 || v_2 == 1) + return 1; + + Point2D midPt = new Point2D(); + midPt.add(pt_1, pt_2); + midPt.scale(0.5);// calculate midpoint + int v = PointInPolygonHelper.isPointInPolygon(polygon, midPt, tol); + if (v == 0) { + return 0; + } + + if (v == 1) { + return 1; + } + + return -1; + } + + Geometry normalizeIntersectionOutput(Geometry geom, int GT_1, int GT_2) { + if (GT_1 == Geometry.GeometryType.Point + || GT_2 == Geometry.GeometryType.Point) { + assert (geom.getType().value() == Geometry.GeometryType.Point); + } + if (GT_1 == Geometry.GeometryType.MultiPoint) { + if (geom.getType().value() == Geometry.GeometryType.Point) { + MultiPoint mp = new MultiPoint(geom.getDescription()); + if (!geom.isEmpty()) + mp.add((Point) geom); + return mp; + } + } + + return geom; + } + + static Geometry returnEmpty_(Geometry geom, boolean bEmpty) { + return bEmpty ? geom : geom.createInstance(); + } + + Geometry returnEmptyIntersector_() { + if (m_geomIntersectorEmptyGeom == null) + m_geomIntersectorEmptyGeom = m_geomIntersector.createInstance(); + + return m_geomIntersectorEmptyGeom; + } + + // virtual boolean IsRecycling() OVERRIDE { return false; } } diff --git a/src/main/java/com/esri/core/geometry/OperatorIntersectionLocal.java b/src/main/java/com/esri/core/geometry/OperatorIntersectionLocal.java index 03d50c31..f2ce196b 100644 --- a/src/main/java/com/esri/core/geometry/OperatorIntersectionLocal.java +++ b/src/main/java/com/esri/core/geometry/OperatorIntersectionLocal.java @@ -28,54 +28,54 @@ class OperatorIntersectionLocal extends OperatorIntersection { - @Override - public GeometryCursor execute(GeometryCursor inputGeometries, - GeometryCursor intersector, SpatialReference sr, - ProgressTracker progressTracker) { - - return new OperatorIntersectionCursor(inputGeometries, intersector, sr, - progressTracker, -1); - } - - @Override - public GeometryCursor execute(GeometryCursor input_geometries, - GeometryCursor intersector, SpatialReference sr, - ProgressTracker progress_tracker, int dimensionMask) { - return new OperatorIntersectionCursor(input_geometries, intersector, - sr, progress_tracker, dimensionMask); - } - - @Override - public Geometry execute(Geometry inputGeometry, Geometry intersector, - SpatialReference sr, ProgressTracker progressTracker) { - SimpleGeometryCursor inputGeomCurs = new SimpleGeometryCursor( - inputGeometry); - SimpleGeometryCursor intersectorCurs = new SimpleGeometryCursor( - intersector); - GeometryCursor geometryCursor = execute(inputGeomCurs, intersectorCurs, - sr, progressTracker); - - return geometryCursor.next(); - } - - @Override - public boolean accelerateGeometry(Geometry geometry, - SpatialReference spatialReference, - GeometryAccelerationDegree accelDegree) { - if (!canAccelerateGeometry(geometry)) - return false; - - double tol = InternalUtils.calculateToleranceFromGeometry(spatialReference, geometry, false); - boolean accelerated = ((MultiVertexGeometryImpl) geometry._getImpl()) - ._buildQuadTreeAccelerator(accelDegree); - accelerated |= ((MultiVertexGeometryImpl) geometry._getImpl()) - ._buildRasterizedGeometryAccelerator(tol, accelDegree); - return accelerated; - } - - @Override - public boolean canAccelerateGeometry(Geometry geometry) { - return RasterizedGeometry2D.canUseAccelerator(geometry); - } + @Override + public GeometryCursor execute(GeometryCursor inputGeometries, + GeometryCursor intersector, SpatialReference sr, + ProgressTracker progressTracker) { + + return new OperatorIntersectionCursor(inputGeometries, intersector, sr, + progressTracker, -1); + } + + @Override + public GeometryCursor execute(GeometryCursor input_geometries, + GeometryCursor intersector, SpatialReference sr, + ProgressTracker progress_tracker, int dimensionMask) { + return new OperatorIntersectionCursor(input_geometries, intersector, + sr, progress_tracker, dimensionMask); + } + + @Override + public Geometry execute(Geometry inputGeometry, Geometry intersector, + SpatialReference sr, ProgressTracker progressTracker) { + SimpleGeometryCursor inputGeomCurs = new SimpleGeometryCursor( + inputGeometry); + SimpleGeometryCursor intersectorCurs = new SimpleGeometryCursor( + intersector); + GeometryCursor geometryCursor = execute(inputGeomCurs, intersectorCurs, + sr, progressTracker); + + return geometryCursor.next(); + } + + @Override + public boolean accelerateGeometry(Geometry geometry, + SpatialReference spatialReference, + GeometryAccelerationDegree accelDegree) { + if (!canAccelerateGeometry(geometry)) + return false; + + double tol = InternalUtils.calculateToleranceFromGeometry(spatialReference, geometry, false); + boolean accelerated = ((MultiVertexGeometryImpl) geometry._getImpl()) + ._buildQuadTreeAccelerator(accelDegree); + accelerated |= ((MultiVertexGeometryImpl) geometry._getImpl()) + ._buildRasterizedGeometryAccelerator(tol, accelDegree); + return accelerated; + } + + @Override + public boolean canAccelerateGeometry(Geometry geometry) { + return RasterizedGeometry2D.canUseAccelerator(geometry); + } } diff --git a/src/main/java/com/esri/core/geometry/OperatorIntersects.java b/src/main/java/com/esri/core/geometry/OperatorIntersects.java index e42ce750..ace6c277 100644 --- a/src/main/java/com/esri/core/geometry/OperatorIntersects.java +++ b/src/main/java/com/esri/core/geometry/OperatorIntersects.java @@ -25,14 +25,14 @@ package com.esri.core.geometry; public abstract class OperatorIntersects extends OperatorSimpleRelation { - @Override - public Type getType() { - return Type.Intersects; - } - - public static OperatorIntersects local() { - return (OperatorIntersects) OperatorFactoryLocal.getInstance() - .getOperator(Type.Intersects); - } + @Override + public Type getType() { + return Type.Intersects; + } + + public static OperatorIntersects local() { + return (OperatorIntersects) OperatorFactoryLocal.getInstance() + .getOperator(Type.Intersects); + } } diff --git a/src/main/java/com/esri/core/geometry/OperatorIntersectsLocal.java b/src/main/java/com/esri/core/geometry/OperatorIntersectsLocal.java index cf9c358b..d4b47d76 100644 --- a/src/main/java/com/esri/core/geometry/OperatorIntersectsLocal.java +++ b/src/main/java/com/esri/core/geometry/OperatorIntersectsLocal.java @@ -26,13 +26,13 @@ class OperatorIntersectsLocal extends OperatorIntersects { - OperatorDisjoint m_disjoint = (OperatorDisjoint) OperatorFactoryLocal - .getInstance().getOperator(Type.Disjoint); - - @Override - public boolean execute(Geometry inputGeom1, Geometry inputGeom2, - SpatialReference sr, ProgressTracker progressTracker) { - return !m_disjoint.execute(inputGeom1, inputGeom2, sr, progressTracker); - } + OperatorDisjoint m_disjoint = (OperatorDisjoint) OperatorFactoryLocal + .getInstance().getOperator(Type.Disjoint); + + @Override + public boolean execute(Geometry inputGeom1, Geometry inputGeom2, + SpatialReference sr, ProgressTracker progressTracker) { + return !m_disjoint.execute(inputGeom1, inputGeom2, sr, progressTracker); + } } diff --git a/src/main/java/com/esri/core/geometry/OperatorOffset.java b/src/main/java/com/esri/core/geometry/OperatorOffset.java index 68b1c1b8..214ee811 100644 --- a/src/main/java/com/esri/core/geometry/OperatorOffset.java +++ b/src/main/java/com/esri/core/geometry/OperatorOffset.java @@ -26,31 +26,32 @@ import com.esri.core.geometry.Operator.Type; public abstract class OperatorOffset extends Operator { - @Override - public Operator.Type getType() { - return Operator.Type.Offset; - } + @Override + public Operator.Type getType() { + return Operator.Type.Offset; + } - /** - * Join types for the offset operation. Updated to match grpc int values - */ - public enum JoinType { - Round(0), Bevel(1), Miter(2), Square(3); - private final int value; - JoinType(int value) { - this.value = value; - } + /** + * Join types for the offset operation. Updated to match grpc int values + */ + public enum JoinType { + Round(0), Bevel(1), Miter(2), Square(3); + private final int value; + + JoinType(int value) { + this.value = value; + } - public int getValue() { - return this.value; - } - } + public int getValue() { + return this.value; + } + } - ; + ; /** * Creates offset version of the input geometries. - * + *

* The offset operation creates a geometry that is a constant distance from * an input polyline or polygon. It is similar to buffering, but produces a * one sided result. If offsetDistance greater than 0, then the offset geometry is @@ -61,35 +62,29 @@ public int getValue() { * multiplied by the offset distance and the result determines how far a * mitered offset intersection can be from the input curve before it is * beveled. - * - * @param inputGeometries - * The geometries to calculate offset for. Point and MultiPoint - * are not supported. - * @param sr - * The SpatialReference of the Geometries. - * @param distance - * The offset distance for the Geometries. - * @param joins - * The join type of the offset geometry. - * @param bevelRatio - * The ratio used to produce a bevel join instead of a miter join - * (used only when joins is Miter) - * @param flattenError - * The maximum distance of the resulting segments compared to the - * true circular arc (used only when joins is Round). If - * flattenError is 0, tolerance value is used. Also, the - * algorithm never produces more than around 180 vertices for - * each round join. + * + * @param inputGeometries The geometries to calculate offset for. Point and MultiPoint + * are not supported. + * @param sr The SpatialReference of the Geometries. + * @param distance The offset distance for the Geometries. + * @param joins The join type of the offset geometry. + * @param bevelRatio The ratio used to produce a bevel join instead of a miter join + * (used only when joins is Miter) + * @param flattenError The maximum distance of the resulting segments compared to the + * true circular arc (used only when joins is Round). If + * flattenError is 0, tolerance value is used. Also, the + * algorithm never produces more than around 180 vertices for + * each round join. * @return Returns the result of the offset operation. */ public abstract GeometryCursor execute(GeometryCursor inputGeometries, - SpatialReference sr, double distance, JoinType joins, - double bevelRatio, double flattenError, - ProgressTracker progressTracker); + SpatialReference sr, double distance, JoinType joins, + double bevelRatio, double flattenError, + ProgressTracker progressTracker); /** * Creates offset version of the input geometry. - * + *

* The offset operation creates a geometry that is a constant distance from * an input polyline or polygon. It is similar to buffering, but produces a * one sided result. If offsetDistance greater than 0, then the offset geometry is @@ -100,35 +95,29 @@ public abstract GeometryCursor execute(GeometryCursor inputGeometries, * multiplied by the offset distance and the result determines how far a * mitered offset intersection can be from the input curve before it is * beveled. - * - * @param inputGeometry - * The geometry to calculate offset for. Point and MultiPoint are - * not supported. - * @param sr - * The SpatialReference of the Geometries. - * @param distance - * The offset distance for the Geometries. - * @param joins - * The join type of the offset geometry. - * @param bevelRatio - * The ratio used to produce a bevel join instead of a miter join - * (used only when joins is Miter) - * @param flattenError - * The maximum distance of the resulting segments compared to the - * true circular arc (used only when joins is Round). If - * flattenError is 0, tolerance value is used. Also, the - * algorithm never produces more than around 180 vetices for each - * round join. + * + * @param inputGeometry The geometry to calculate offset for. Point and MultiPoint are + * not supported. + * @param sr The SpatialReference of the Geometries. + * @param distance The offset distance for the Geometries. + * @param joins The join type of the offset geometry. + * @param bevelRatio The ratio used to produce a bevel join instead of a miter join + * (used only when joins is Miter) + * @param flattenError The maximum distance of the resulting segments compared to the + * true circular arc (used only when joins is Round). If + * flattenError is 0, tolerance value is used. Also, the + * algorithm never produces more than around 180 vetices for each + * round join. * @return Returns the result of the offset operation. */ public abstract Geometry execute(Geometry inputGeometry, - SpatialReference sr, double distance, JoinType joins, - double bevelRatio, double flattenError, - ProgressTracker progressTracker); + SpatialReference sr, double distance, JoinType joins, + double bevelRatio, double flattenError, + ProgressTracker progressTracker); - public static OperatorOffset local() { - return (OperatorOffset) OperatorFactoryLocal.getInstance().getOperator( - Type.Offset); - } + public static OperatorOffset local() { + return (OperatorOffset) OperatorFactoryLocal.getInstance().getOperator( + Type.Offset); + } } diff --git a/src/main/java/com/esri/core/geometry/OperatorOffsetCursor.java b/src/main/java/com/esri/core/geometry/OperatorOffsetCursor.java index e6ece9f0..303af8f2 100644 --- a/src/main/java/com/esri/core/geometry/OperatorOffsetCursor.java +++ b/src/main/java/com/esri/core/geometry/OperatorOffsetCursor.java @@ -25,45 +25,45 @@ // TODO test this, never been public before public class OperatorOffsetCursor extends GeometryCursor { - SpatialReferenceImpl m_spatialReference; - ProgressTracker m_progressTracker; - double m_distance; - double m_miterLimit; - OperatorOffset.JoinType m_joins; - double m_flattenError; + SpatialReferenceImpl m_spatialReference; + ProgressTracker m_progressTracker; + double m_distance; + double m_miterLimit; + OperatorOffset.JoinType m_joins; + double m_flattenError; - OperatorOffsetCursor(GeometryCursor inputGeometries, - SpatialReference sr, - double distance, OperatorOffset.JoinType joins, - double bevelRatio, - double flattenError, - ProgressTracker progressTracker) { - m_inputGeoms = inputGeometries; - m_spatialReference = (SpatialReferenceImpl) sr; - m_distance = distance; - m_joins = joins; - m_miterLimit = bevelRatio; - m_flattenError = flattenError; - m_progressTracker = progressTracker; - } + OperatorOffsetCursor(GeometryCursor inputGeometries, + SpatialReference sr, + double distance, OperatorOffset.JoinType joins, + double bevelRatio, + double flattenError, + ProgressTracker progressTracker) { + m_inputGeoms = inputGeometries; + m_spatialReference = (SpatialReferenceImpl) sr; + m_distance = distance; + m_joins = joins; + m_miterLimit = bevelRatio; + m_flattenError = flattenError; + m_progressTracker = progressTracker; + } - @Override - public Geometry next() { - if (hasNext()) - return Offset(m_inputGeoms.next()); + @Override + public Geometry next() { + if (hasNext()) + return Offset(m_inputGeoms.next()); - return null; - } + return null; + } - Geometry Offset(Geometry geom) { - double tolerance; - if (m_flattenError <= 0) - tolerance = InternalUtils.calculateToleranceFromGeometry( - m_spatialReference, geom, false); - else - tolerance = m_flattenError; - return ConstructOffset.execute(geom, m_distance, m_joins, m_miterLimit, - tolerance, m_progressTracker); - } + Geometry Offset(Geometry geom) { + double tolerance; + if (m_flattenError <= 0) + tolerance = InternalUtils.calculateToleranceFromGeometry( + m_spatialReference, geom, false); + else + tolerance = m_flattenError; + return ConstructOffset.execute(geom, m_distance, m_joins, m_miterLimit, + tolerance, m_progressTracker); + } } diff --git a/src/main/java/com/esri/core/geometry/OperatorOffsetLocal.java b/src/main/java/com/esri/core/geometry/OperatorOffsetLocal.java index 5381b120..894f2849 100644 --- a/src/main/java/com/esri/core/geometry/OperatorOffsetLocal.java +++ b/src/main/java/com/esri/core/geometry/OperatorOffsetLocal.java @@ -25,24 +25,24 @@ class OperatorOffsetLocal extends OperatorOffset { - @Override - public GeometryCursor execute(GeometryCursor inputGeometries, - SpatialReference sr, double distance, JoinType joins, - double bevelRatio, double flattenError, - ProgressTracker progressTracker) { - return new OperatorOffsetCursor(inputGeometries, sr, distance, joins, - bevelRatio, flattenError, progressTracker); - } - - @Override - public Geometry execute(Geometry inputGeometry, SpatialReference sr, - double distance, JoinType joins, double bevelRatio, - double flattenError, ProgressTracker progressTracker) { - SimpleGeometryCursor inputCursor = new SimpleGeometryCursor( - inputGeometry); - GeometryCursor outCursor = execute(inputCursor, sr, distance, joins, - bevelRatio, flattenError, progressTracker); - return outCursor.next(); - } + @Override + public GeometryCursor execute(GeometryCursor inputGeometries, + SpatialReference sr, double distance, JoinType joins, + double bevelRatio, double flattenError, + ProgressTracker progressTracker) { + return new OperatorOffsetCursor(inputGeometries, sr, distance, joins, + bevelRatio, flattenError, progressTracker); + } + + @Override + public Geometry execute(Geometry inputGeometry, SpatialReference sr, + double distance, JoinType joins, double bevelRatio, + double flattenError, ProgressTracker progressTracker) { + SimpleGeometryCursor inputCursor = new SimpleGeometryCursor( + inputGeometry); + GeometryCursor outCursor = execute(inputCursor, sr, distance, joins, + bevelRatio, flattenError, progressTracker); + return outCursor.next(); + } } diff --git a/src/main/java/com/esri/core/geometry/OperatorOverlaps.java b/src/main/java/com/esri/core/geometry/OperatorOverlaps.java index ca2b4905..c7b3b207 100644 --- a/src/main/java/com/esri/core/geometry/OperatorOverlaps.java +++ b/src/main/java/com/esri/core/geometry/OperatorOverlaps.java @@ -26,14 +26,14 @@ import com.esri.core.geometry.Operator.Type; public abstract class OperatorOverlaps extends OperatorSimpleRelation { - @Override - public Type getType() { - return Type.Equals; - } - - public static OperatorOverlaps local() { - return (OperatorOverlaps) OperatorFactoryLocal.getInstance() - .getOperator(Type.Overlaps); - } + @Override + public Type getType() { + return Type.Equals; + } + + public static OperatorOverlaps local() { + return (OperatorOverlaps) OperatorFactoryLocal.getInstance() + .getOperator(Type.Overlaps); + } } diff --git a/src/main/java/com/esri/core/geometry/OperatorOverlapsLocal.java b/src/main/java/com/esri/core/geometry/OperatorOverlapsLocal.java index 1196e6a7..33a81c5e 100644 --- a/src/main/java/com/esri/core/geometry/OperatorOverlapsLocal.java +++ b/src/main/java/com/esri/core/geometry/OperatorOverlapsLocal.java @@ -24,10 +24,10 @@ package com.esri.core.geometry; class OperatorOverlapsLocal extends OperatorOverlaps { - @Override - public boolean execute(Geometry inputGeom1, Geometry inputGeom2, - SpatialReference sr, ProgressTracker progressTracker) { - return RelationalOperations.relate(inputGeom1, inputGeom2, sr, - RelationalOperations.Relation.overlaps, progressTracker); - } + @Override + public boolean execute(Geometry inputGeom1, Geometry inputGeom2, + SpatialReference sr, ProgressTracker progressTracker) { + return RelationalOperations.relate(inputGeom1, inputGeom2, sr, + RelationalOperations.Relation.overlaps, progressTracker); + } } diff --git a/src/main/java/com/esri/core/geometry/OperatorProject.java b/src/main/java/com/esri/core/geometry/OperatorProject.java index 3b9cc296..9f5fe8b2 100644 --- a/src/main/java/com/esri/core/geometry/OperatorProject.java +++ b/src/main/java/com/esri/core/geometry/OperatorProject.java @@ -28,135 +28,135 @@ */ public abstract class OperatorProject extends Operator { - @Override - public Type getType() { - return Type.Project; - } - - /** - * Performs the Project operation on a geometry cursor - * - * @return Returns a GeometryCursor. - */ - public abstract GeometryCursor execute(GeometryCursor inputGeoms, - ProjectionTransformation projection, - ProgressTracker progressTracker); - - /** - * Performs the Project operation on a single geometry instance - * - * @return Returns the Geometry after projection - */ - public abstract Geometry execute(Geometry geometry, - ProjectionTransformation projection, - ProgressTracker progressTracker); - - /** - * Transforms an array of points. Returns the number of points transformed. - */ - public abstract int transform(ProjectionTransformation transform, - Point[] coordsSrc, - int length, - Point[] coordsDst) throws org.proj4.PJException; - - /** - * Transforms an array of 2D points and returns it. The points are stored in - * an interleaved array (x0, y0, x1, y1, x2, y2, ...). - * - * @param transform ProjectionTransformation - * @param coordsSrc source coordinates to project. - * @param pointCount the point count in the coordSrc. THere has to be at least - * pointCount * 2 elements in the coordsSrc array. - * @param bHasZ does the coordSrc array include z values - * @return projected coordinates in the interleaved form. - */ - public abstract double[] transform(ProjectionTransformation transform, - double[] coordsSrc, - int pointCount, - boolean bHasZ) throws org.proj4.PJException; - - /** - * Folds a geometry into the 360 degree range of the associated spatial reference. If the spatial reference be a 'pannable' PROJECTED or GEOGRAPHIC. For other spatial types, the function throws an invalid - * argument exception. A pannable PROJECTED it a Rectangular PROJECTED where the x coordinate range is equivalent to a 360 degree range on the defining geographic Coordinate System(GEOGRAPHIC). If the spatial - * reference is a GEOGRAPHIC then it is always pannable(default 360 range for spatial reference in GEOGRAPHIC coordinates is -180 to 180) - *

- * If the geometry is an Envelope fold_into_360_range returns a polygon, unless the Envelope is empty, in which case the empty envelope is returned. The result geometry will be completely inside of - * the coordinate system extent. The folding happens where geometry intersects the min or max meridian of the spatial reference and when geometry is completely outside of the min-max meridian range. - * Folding does not preserve geodetic area or length. Folding does not preserve perimeter of a polygon. - * - * @param geom The geometry to be folded. - * @param pannableSR The pannable Spatial Reference. - * @return Folded geometry. - */ - public static Geometry foldInto360Range(Geometry geom, SpatialReference pannableSR) { - Envelope2D envelope2D = new Envelope2D(); - geom.queryEnvelope2D(envelope2D); - if (envelope2D.xmax <= 180.0 && envelope2D.xmin >= -180.0) { - return geom; - } - - // clip by -180 and 180 - MultiPath foldedGeometry = null; - - if (geom.getType() != Geometry.Type.Polyline && geom.getType() != Geometry.Type.Polygon) - return geom; - - MultiPathImpl multiPath = (MultiPathImpl)geom._getImpl(); - if (geom.getType() == Geometry.Type.Polygon) { - foldedGeometry = new Polygon(multiPath.m_description); - } else if (geom.getType() == Geometry.Type.Polyline) { - foldedGeometry = new Polyline(multiPath.m_description); - } - - // TODO this should be a static class member - Polyline cuttee1 = new Polyline(); - cuttee1.startPath(-180, 90); - cuttee1.lineTo(-180, -90); - cuttee1.startPath(180, 90); - cuttee1.lineTo(180, -90); - Geometry[] parts = GeometryEngine.cut(geom, cuttee1, pannableSR); - - if (parts.length == 0) { - parts = new Geometry[] {geom}; - } - - for (Geometry geometryPart : parts) { - geometryPart.queryEnvelope2D(envelope2D); - // TODO this only accounts for geometries with lat lon rang of -540 to 540 - if (envelope2D.xmin < -180) { - // add 180 to all vertices in geometry - // TODO this should be a static class member - Transformation2D transformation2D = new Transformation2D(); - transformation2D.xd = 360; - geometryPart.applyTransformation(transformation2D); - } - if (envelope2D.xmax > 180) { - // TODO this should be a static class member - Transformation2D transformation2D = new Transformation2D(); - transformation2D.xd = -360; - geometryPart.applyTransformation(transformation2D); - } - foldedGeometry.add((MultiPath) geometryPart, false); - } - - return foldedGeometry; - } - - /** - * Same as fold_into_360_range. The difference is that this function preserves geodetic area of polygons and geodetic length of polylines. It does not preserve regular area and length or perimeter - * of polygons. Also, this function might change tangent of the lines at the points of folding. - *

- * If the geometry is an Envelope fold_into_360_range returns a polygon, unless the Envelope is empty, in which case the empty envelope is returned. The result geometry will be completely inside of - * the coordinate system extent. The folding happens where geometry intersects the min or max meridian of the spatial reference and when geometry is completely outside of the min-max meridian range. - * - * @param geom The geometry to be folded. - * @param pannableSR The pannable Spatial Reference. - * @param curveType The type of geodetic curve to use to produce vertices at the points of folding. \return Folded geometry. - */ - public abstract Geometry foldInto360RangeGeodetic(Geometry geom, SpatialReference pannableSR, int curveType); - - public static OperatorProject local() { - return (OperatorProject) OperatorFactoryLocal.getInstance().getOperator(Type.Project); - } + @Override + public Type getType() { + return Type.Project; + } + + /** + * Performs the Project operation on a geometry cursor + * + * @return Returns a GeometryCursor. + */ + public abstract GeometryCursor execute(GeometryCursor inputGeoms, + ProjectionTransformation projection, + ProgressTracker progressTracker); + + /** + * Performs the Project operation on a single geometry instance + * + * @return Returns the Geometry after projection + */ + public abstract Geometry execute(Geometry geometry, + ProjectionTransformation projection, + ProgressTracker progressTracker); + + /** + * Transforms an array of points. Returns the number of points transformed. + */ + public abstract int transform(ProjectionTransformation transform, + Point[] coordsSrc, + int length, + Point[] coordsDst) throws org.proj4.PJException; + + /** + * Transforms an array of 2D points and returns it. The points are stored in + * an interleaved array (x0, y0, x1, y1, x2, y2, ...). + * + * @param transform ProjectionTransformation + * @param coordsSrc source coordinates to project. + * @param pointCount the point count in the coordSrc. THere has to be at least + * pointCount * 2 elements in the coordsSrc array. + * @param bHasZ does the coordSrc array include z values + * @return projected coordinates in the interleaved form. + */ + public abstract double[] transform(ProjectionTransformation transform, + double[] coordsSrc, + int pointCount, + boolean bHasZ) throws org.proj4.PJException; + + /** + * Folds a geometry into the 360 degree range of the associated spatial reference. If the spatial reference be a 'pannable' PROJECTED or GEOGRAPHIC. For other spatial types, the function throws an invalid + * argument exception. A pannable PROJECTED it a Rectangular PROJECTED where the x coordinate range is equivalent to a 360 degree range on the defining geographic Coordinate System(GEOGRAPHIC). If the spatial + * reference is a GEOGRAPHIC then it is always pannable(default 360 range for spatial reference in GEOGRAPHIC coordinates is -180 to 180) + *

+ * If the geometry is an Envelope fold_into_360_range returns a polygon, unless the Envelope is empty, in which case the empty envelope is returned. The result geometry will be completely inside of + * the coordinate system extent. The folding happens where geometry intersects the min or max meridian of the spatial reference and when geometry is completely outside of the min-max meridian range. + * Folding does not preserve geodetic area or length. Folding does not preserve perimeter of a polygon. + * + * @param geom The geometry to be folded. + * @param pannableSR The pannable Spatial Reference. + * @return Folded geometry. + */ + public static Geometry foldInto360Range(Geometry geom, SpatialReference pannableSR) { + Envelope2D envelope2D = new Envelope2D(); + geom.queryEnvelope2D(envelope2D); + if (envelope2D.xmax <= 180.0 && envelope2D.xmin >= -180.0) { + return geom; + } + + // clip by -180 and 180 + MultiPath foldedGeometry = null; + + if (geom.getType() != Geometry.Type.Polyline && geom.getType() != Geometry.Type.Polygon) + return geom; + + MultiPathImpl multiPath = (MultiPathImpl) geom._getImpl(); + if (geom.getType() == Geometry.Type.Polygon) { + foldedGeometry = new Polygon(multiPath.m_description); + } else if (geom.getType() == Geometry.Type.Polyline) { + foldedGeometry = new Polyline(multiPath.m_description); + } + + // TODO this should be a static class member + Polyline cuttee1 = new Polyline(); + cuttee1.startPath(-180, 90); + cuttee1.lineTo(-180, -90); + cuttee1.startPath(180, 90); + cuttee1.lineTo(180, -90); + Geometry[] parts = GeometryEngine.cut(geom, cuttee1, pannableSR); + + if (parts.length == 0) { + parts = new Geometry[]{geom}; + } + + for (Geometry geometryPart : parts) { + geometryPart.queryEnvelope2D(envelope2D); + // TODO this only accounts for geometries with lat lon rang of -540 to 540 + if (envelope2D.xmin < -180) { + // add 180 to all vertices in geometry + // TODO this should be a static class member + Transformation2D transformation2D = new Transformation2D(); + transformation2D.xd = 360; + geometryPart.applyTransformation(transformation2D); + } + if (envelope2D.xmax > 180) { + // TODO this should be a static class member + Transformation2D transformation2D = new Transformation2D(); + transformation2D.xd = -360; + geometryPart.applyTransformation(transformation2D); + } + foldedGeometry.add((MultiPath) geometryPart, false); + } + + return foldedGeometry; + } + + /** + * Same as fold_into_360_range. The difference is that this function preserves geodetic area of polygons and geodetic length of polylines. It does not preserve regular area and length or perimeter + * of polygons. Also, this function might change tangent of the lines at the points of folding. + *

+ * If the geometry is an Envelope fold_into_360_range returns a polygon, unless the Envelope is empty, in which case the empty envelope is returned. The result geometry will be completely inside of + * the coordinate system extent. The folding happens where geometry intersects the min or max meridian of the spatial reference and when geometry is completely outside of the min-max meridian range. + * + * @param geom The geometry to be folded. + * @param pannableSR The pannable Spatial Reference. + * @param curveType The type of geodetic curve to use to produce vertices at the points of folding. \return Folded geometry. + */ + public abstract Geometry foldInto360RangeGeodetic(Geometry geom, SpatialReference pannableSR, int curveType); + + public static OperatorProject local() { + return (OperatorProject) OperatorFactoryLocal.getInstance().getOperator(Type.Project); + } } diff --git a/src/main/java/com/esri/core/geometry/OperatorProjectCursor.java b/src/main/java/com/esri/core/geometry/OperatorProjectCursor.java index 995c8a28..0a4724d3 100644 --- a/src/main/java/com/esri/core/geometry/OperatorProjectCursor.java +++ b/src/main/java/com/esri/core/geometry/OperatorProjectCursor.java @@ -4,24 +4,24 @@ * Created by davidraleigh on 5/12/17. */ public class OperatorProjectCursor extends GeometryCursor { - ProjectionTransformation m_projectionTransformation; - ProgressTracker m_progressTracker; + ProjectionTransformation m_projectionTransformation; + ProgressTracker m_progressTracker; - OperatorProjectCursor( - GeometryCursor inputGeoms, - ProjectionTransformation projectionTransformation, - ProgressTracker progressTracker) { - m_inputGeoms = inputGeoms; - m_projectionTransformation = projectionTransformation; - m_progressTracker = progressTracker; - } + OperatorProjectCursor( + GeometryCursor inputGeoms, + ProjectionTransformation projectionTransformation, + ProgressTracker progressTracker) { + m_inputGeoms = inputGeoms; + m_projectionTransformation = projectionTransformation; + m_progressTracker = progressTracker; + } - @Override - public Geometry next() { - if (m_inputGeoms.hasNext()) { - Geometry geometry = m_inputGeoms.next(); - return Projecter.project(geometry, m_projectionTransformation, m_progressTracker); - } - return null; - } + @Override + public Geometry next() { + if (m_inputGeoms.hasNext()) { + Geometry geometry = m_inputGeoms.next(); + return Projecter.project(geometry, m_projectionTransformation, m_progressTracker); + } + return null; + } } diff --git a/src/main/java/com/esri/core/geometry/OperatorProjectLocal.java b/src/main/java/com/esri/core/geometry/OperatorProjectLocal.java index 49386c12..62b442aa 100644 --- a/src/main/java/com/esri/core/geometry/OperatorProjectLocal.java +++ b/src/main/java/com/esri/core/geometry/OperatorProjectLocal.java @@ -31,37 +31,37 @@ //This is a stub class OperatorProjectLocal extends OperatorProject { - @Override - public GeometryCursor execute(GeometryCursor inputGeoms, - ProjectionTransformation transform, - ProgressTracker progressTracker) { - return new OperatorProjectCursor(inputGeoms, transform, progressTracker); - } + @Override + public GeometryCursor execute(GeometryCursor inputGeoms, + ProjectionTransformation transform, + ProgressTracker progressTracker) { + return new OperatorProjectCursor(inputGeoms, transform, progressTracker); + } - public Geometry execute(Geometry inputGeom, - ProjectionTransformation transform, - ProgressTracker progressTracker) { - return execute(new SimpleGeometryCursor(inputGeom), transform, progressTracker).next(); - } + public Geometry execute(Geometry inputGeom, + ProjectionTransformation transform, + ProgressTracker progressTracker) { + return execute(new SimpleGeometryCursor(inputGeom), transform, progressTracker).next(); + } - @Override - public int transform(ProjectionTransformation transform, - Point[] pointsIn, - int count, - Point[] pointsOut) throws org.proj4.PJException { - return Projecter.transform(transform, pointsIn, count, pointsOut); - } + @Override + public int transform(ProjectionTransformation transform, + Point[] pointsIn, + int count, + Point[] pointsOut) throws org.proj4.PJException { + return Projecter.transform(transform, pointsIn, count, pointsOut); + } - public double[] transform(ProjectionTransformation transform, - double[] coordsSrc, - int pointCount, - boolean bHasZ) throws org.proj4.PJException { - return Projecter.transform(transform, coordsSrc, bHasZ); - } + public double[] transform(ProjectionTransformation transform, + double[] coordsSrc, + int pointCount, + boolean bHasZ) throws org.proj4.PJException { + return Projecter.transform(transform, coordsSrc, bHasZ); + } - @Override - public Geometry foldInto360RangeGeodetic(Geometry _geom, SpatialReference pannableSR, int curveType) { - return _geom; - } + @Override + public Geometry foldInto360RangeGeodetic(Geometry _geom, SpatialReference pannableSR, int curveType) { + return _geom; + } } diff --git a/src/main/java/com/esri/core/geometry/OperatorProximity2D.java b/src/main/java/com/esri/core/geometry/OperatorProximity2D.java index 6b40d72f..bb7e19e1 100644 --- a/src/main/java/com/esri/core/geometry/OperatorProximity2D.java +++ b/src/main/java/com/esri/core/geometry/OperatorProximity2D.java @@ -30,70 +30,70 @@ * Finds closest vertices of the Geometry. */ public abstract class OperatorProximity2D extends Operator { - @Override - public Type getType() { - return Type.Proximity2D; - } + @Override + public Type getType() { + return Type.Proximity2D; + } - /** - * Returns the nearest coordinate on the Geometry to the given input point. - * - * @param geom The input Geometry. - * @param inputPoint The query point. - * @param bTestPolygonInterior When true and geom is a polygon, the function will test if the input_point is inside of the polygon. Points that are - * inside of the polygon have zero distance to the polygon. When false, the function will not check if the point is inside of the polygon, - * but only determine proximity to the boundary. - * @param bCalculateLeftRightSide The function will calculate left/right side of polylines or polygons when the parameter is True. - * \return Returns the result of proximity calculation. See Proximity_2D_result. - */ - public abstract Proximity2DResult getNearestCoordinate(Geometry geom, - Point inputPoint, boolean bTestPolygonInterior, - boolean bCalculateLeftRightSide); + /** + * Returns the nearest coordinate on the Geometry to the given input point. + * + * @param geom The input Geometry. + * @param inputPoint The query point. + * @param bTestPolygonInterior When true and geom is a polygon, the function will test if the input_point is inside of the polygon. Points that are + * inside of the polygon have zero distance to the polygon. When false, the function will not check if the point is inside of the polygon, + * but only determine proximity to the boundary. + * @param bCalculateLeftRightSide The function will calculate left/right side of polylines or polygons when the parameter is True. + * \return Returns the result of proximity calculation. See Proximity_2D_result. + */ + public abstract Proximity2DResult getNearestCoordinate(Geometry geom, + Point inputPoint, boolean bTestPolygonInterior, + boolean bCalculateLeftRightSide); - /** - * Returns the nearest coordinate on the Geometry to the given input point. - * - * @param geom The input Geometry. - * @param inputPoint The query point. - * @param bTestPolygonInterior When true and geom is a polygon, the function will test if the input_point is inside of the polygon. Points that are - * inside of the polygon have zero distance to the polygon. When false, the function will not check if the point is inside of the polygon, - * but only determine proximity to the boundary. - * \return Returns the result of proximity calculation. See Proximity_2D_result. - */ - public abstract Proximity2DResult getNearestCoordinate(Geometry geom, - Point inputPoint, boolean bTestPolygonInterior); + /** + * Returns the nearest coordinate on the Geometry to the given input point. + * + * @param geom The input Geometry. + * @param inputPoint The query point. + * @param bTestPolygonInterior When true and geom is a polygon, the function will test if the input_point is inside of the polygon. Points that are + * inside of the polygon have zero distance to the polygon. When false, the function will not check if the point is inside of the polygon, + * but only determine proximity to the boundary. + * \return Returns the result of proximity calculation. See Proximity_2D_result. + */ + public abstract Proximity2DResult getNearestCoordinate(Geometry geom, + Point inputPoint, boolean bTestPolygonInterior); - /** - * Returns the nearest vertex of the Geometry to the given input point. - */ - public abstract Proximity2DResult getNearestVertex(Geometry geom, - Point inputPoint); + /** + * Returns the nearest vertex of the Geometry to the given input point. + */ + public abstract Proximity2DResult getNearestVertex(Geometry geom, + Point inputPoint); - /** - * Returns vertices of the Geometry that are closer to the given point than - * the given radius. - * - * @param geom The input Geometry. - * @param inputPoint The query point. - * @param searchRadius The maximum distance to the query point of the vertices. - * @param maxVertexCountToReturn The maximum vertex count to return. The function returns no - * more than this number of vertices. - * @return The array of vertices that are in the given search radius to the - * point. The array is sorted by distance to the queryPoint with the - * closest point first. When there are more than the - * maxVertexCountToReturn vertices to return, it returns the closest - * vertices. The array will be empty when geom is empty. - */ - public abstract Proximity2DResult[] getNearestVertices(Geometry geom, - Point inputPoint, double searchRadius, int maxVertexCountToReturn); + /** + * Returns vertices of the Geometry that are closer to the given point than + * the given radius. + * + * @param geom The input Geometry. + * @param inputPoint The query point. + * @param searchRadius The maximum distance to the query point of the vertices. + * @param maxVertexCountToReturn The maximum vertex count to return. The function returns no + * more than this number of vertices. + * @return The array of vertices that are in the given search radius to the + * point. The array is sorted by distance to the queryPoint with the + * closest point first. When there are more than the + * maxVertexCountToReturn vertices to return, it returns the closest + * vertices. The array will be empty when geom is empty. + */ + public abstract Proximity2DResult[] getNearestVertices(Geometry geom, + Point inputPoint, double searchRadius, int maxVertexCountToReturn); - public static OperatorProximity2D local() { - return (OperatorProximity2D) OperatorFactoryLocal.getInstance() - .getOperator(Type.Proximity2D); - } + public static OperatorProximity2D local() { + return (OperatorProximity2D) OperatorFactoryLocal.getInstance() + .getOperator(Type.Proximity2D); + } - interface ProxResultInfo { - static final int rightSide = 0x1; - } + interface ProxResultInfo { + static final int rightSide = 0x1; + } } diff --git a/src/main/java/com/esri/core/geometry/OperatorProximity2DLocal.java b/src/main/java/com/esri/core/geometry/OperatorProximity2DLocal.java index 7b6969d7..832ad712 100644 --- a/src/main/java/com/esri/core/geometry/OperatorProximity2DLocal.java +++ b/src/main/java/com/esri/core/geometry/OperatorProximity2DLocal.java @@ -31,511 +31,511 @@ class OperatorProximity2DLocal extends OperatorProximity2D { - class Side_helper { - int m_i1; - int m_i2; - boolean m_bRight1; - boolean m_bRight2; - - void reset() { - m_i1 = -1; - m_i2 = -1; - m_bRight1 = false; - m_bRight2 = false; - } - - int find_non_degenerate(SegmentIterator segIter, int vertexIndex, - int pathIndex) { - segIter.resetToVertex(vertexIndex, pathIndex); - - while (segIter.hasNextSegment()) { - Segment segment = segIter.nextSegment(); - double length = segment.calculateLength2D(); - - if (length != 0.0) - return segIter.getStartPointIndex(); - } - - segIter.resetToVertex(vertexIndex, pathIndex); - - while (segIter.hasPreviousSegment()) { - Segment segment = segIter.previousSegment(); - double length = segment.calculateLength2D(); - - if (length != 0) - return segIter.getStartPointIndex(); - } - - return -1; - } - - int find_prev_non_degenerate(SegmentIterator segIter, int index) { - segIter.resetToVertex(index, -1); - - while (segIter.hasPreviousSegment()) { - Segment segment = segIter.previousSegment(); - double length = segment.calculateLength2D(); - - if (length != 0) - return segIter.getStartPointIndex(); - } - - return -1; - } - - int find_next_non_degenerate(SegmentIterator segIter, int index) { - segIter.resetToVertex(index, -1); - segIter.nextSegment(); - - while (segIter.hasNextSegment()) { - Segment segment = segIter.nextSegment(); - double length = segment.calculateLength2D(); - - if (length != 0) - return segIter.getStartPointIndex(); - } - - return -1; - } - - void find_analysis_pair_from_index(Point2D inputPoint, - SegmentIterator segIter, int vertexIndex, int pathIndex) { - m_i1 = find_non_degenerate(segIter, vertexIndex, pathIndex); - - if (m_i1 != -1) { - segIter.resetToVertex(m_i1, -1); - Segment segment1 = segIter.nextSegment(); - double t1 = segment1.getClosestCoordinate(inputPoint, false); - Point2D p1 = segment1.getCoord2D(t1); - double d1 = Point2D.sqrDistance(p1, inputPoint); - Point2D pq = new Point2D(); - pq.setCoords(p1); - pq.sub(segment1.getStartXY()); - Point2D pr = new Point2D(); - pr.setCoords(inputPoint); - pr.sub(segment1.getStartXY()); - m_bRight1 = (pq.crossProduct(pr) < 0); - - m_i2 = find_next_non_degenerate(segIter, m_i1); - if (m_i2 != -1) { - segIter.resetToVertex(m_i2, -1); - Segment segment2 = segIter.nextSegment(); - double t2 = segment2 - .getClosestCoordinate(inputPoint, false); - Point2D p2 = segment2.getCoord2D(t2); - double d2 = Point2D.sqrDistance(p2, inputPoint); - - if (d2 > d1) { - m_i2 = -1; - } else { - pq.setCoords(p2); - pq.sub(segment2.getStartXY()); - pr.setCoords(inputPoint); - pr.sub(segment2.getStartXY()); - m_bRight2 = (pq.crossProduct(pr) < 0); - } - } - - if (m_i2 == -1) { - m_i2 = find_prev_non_degenerate(segIter, m_i1); - if (m_i2 != -1) { - segIter.resetToVertex(m_i2, -1); - Segment segment2 = segIter.nextSegment(); - double t2 = segment2.getClosestCoordinate(inputPoint, - false); - Point2D p2 = segment2.getCoord2D(t2); - double d2 = Point2D.sqrDistance(p2, inputPoint); - - if (d2 > d1) - m_i2 = -1; - else { - pq.setCoords(p2); - pq.sub(segment2.getStartXY()); - pr.setCoords(inputPoint); - pr.sub(segment2.getStartXY()); - m_bRight2 = (pq.crossProduct(pr) < 0); - - int itemp = m_i1; - m_i1 = m_i2; - m_i2 = itemp; - - boolean btemp = m_bRight1; - m_bRight1 = m_bRight2; - m_bRight2 = btemp; - } - } - } - } - } - - // Try to find two segements that are not degenerate - boolean calc_side(Point2D inputPoint, boolean bRight, - MultiPath multipath, int vertexIndex, int pathIndex) { - SegmentIterator segIter = multipath.querySegmentIterator(); - - find_analysis_pair_from_index(inputPoint, segIter, vertexIndex, - pathIndex); - - if (m_i1 != -1 && m_i2 == -1) {// could not find a pair of segments - return m_bRight1; - } - - if (m_i1 != -1 && m_i2 != -1) { - if (m_bRight1 == m_bRight2) - return m_bRight1;// no conflicting result for the side - else { - // the conflicting result, that we are trying to resolve, - // happens in the obtuse (outer) side of the turn only. - segIter.resetToVertex(m_i1, -1); - Segment segment1 = segIter.nextSegment(); - Point2D tang1 = segment1._getTangent(1.0); - - segIter.resetToVertex(m_i2, -1); - Segment segment2 = segIter.nextSegment(); - Point2D tang2 = segment2._getTangent(0.0); - - double cross = tang1.crossProduct(tang2); - - if (cross >= 0) // the obtuse angle is on the right side - { - return true; - } else // the obtuse angle is on the right side - { - return false; - } - } - } else { - assert (m_i1 == -1 && m_i2 == -1); - return bRight;// could not resolve the side. So just return the - // old value. - } - } - } - - @Override - public Proximity2DResult getNearestCoordinate(Geometry geom, - Point inputPoint, boolean bTestPolygonInterior) { - - return getNearestCoordinate(geom, inputPoint, bTestPolygonInterior, - false); - } - - @Override - public Proximity2DResult getNearestCoordinate(Geometry geom, - Point inputPoint, boolean bTestPolygonInterior, - boolean bCalculateLeftRightSide) { - if (geom.isEmpty()) - return new Proximity2DResult(); - - Point2D inputPoint2D = inputPoint.getXY(); - - Geometry proxmityTestGeom = geom; - int gt = geom.getType().value(); - - if (gt == Geometry.GeometryType.Envelope) { - Polygon polygon = new Polygon(); - polygon.addEnvelope((Envelope) geom, false); - proxmityTestGeom = polygon; - gt = Geometry.GeometryType.Polygon; - } - switch (gt) { - case Geometry.GeometryType.Point: - return pointGetNearestVertex((Point) proxmityTestGeom, inputPoint2D); - case Geometry.GeometryType.MultiPoint: - return multiVertexGetNearestVertex( - (MultiVertexGeometry) proxmityTestGeom, inputPoint2D); - case Geometry.GeometryType.Polyline: - case Geometry.GeometryType.Polygon: - return multiPathGetNearestCoordinate((MultiPath) proxmityTestGeom, - inputPoint2D, bTestPolygonInterior, bCalculateLeftRightSide); - default: { - throw new GeometryException("not implemented"); - } - } - } - - @Override - public Proximity2DResult getNearestVertex(Geometry geom, Point inputPoint) { - if (geom.isEmpty()) - return new Proximity2DResult(); - - Point2D inputPoint2D = inputPoint.getXY(); - - Geometry proxmityTestGeom = geom; - int gt = geom.getType().value(); - - if (gt == Geometry.GeometryType.Envelope) { - Polygon polygon = new Polygon(); - polygon.addEnvelope((Envelope) geom, false); - proxmityTestGeom = polygon; - gt = Geometry.GeometryType.Polygon; - } - switch (gt) { - case Geometry.GeometryType.Point: - return pointGetNearestVertex((Point) proxmityTestGeom, inputPoint2D); - case Geometry.GeometryType.MultiPoint: - case Geometry.GeometryType.Polyline: - case Geometry.GeometryType.Polygon: - return multiVertexGetNearestVertex( - (MultiVertexGeometry) proxmityTestGeom, inputPoint2D); - default: { - throw new GeometryException("not implemented"); - } - } - } - - @Override - public Proximity2DResult[] getNearestVertices(Geometry geom, - Point inputPoint, double searchRadius, int maxVertexCountToReturn) { - if (maxVertexCountToReturn < 0) - throw new IllegalArgumentException(); - - if (geom.isEmpty()) - return new Proximity2DResult[]{}; - - Point2D inputPoint2D = inputPoint.getXY(); - - Geometry proxmityTestGeom = geom; - int gt = geom.getType().value(); - - if (gt == Geometry.GeometryType.Envelope) { - Polygon polygon = new Polygon(); - polygon.addEnvelope((Envelope) geom, false); - proxmityTestGeom = polygon; - gt = Geometry.GeometryType.Polygon; - } - switch (gt) { - case Geometry.GeometryType.Point: - return pointGetNearestVertices((Point) proxmityTestGeom, - inputPoint2D, searchRadius, maxVertexCountToReturn); - case Geometry.GeometryType.MultiPoint: - case Geometry.GeometryType.Polyline: - case Geometry.GeometryType.Polygon: - return multiVertexGetNearestVertices( - (MultiVertexGeometry) proxmityTestGeom, inputPoint2D, - searchRadius, maxVertexCountToReturn); - default: { - throw new GeometryException("not implemented"); - } - } - } - - Proximity2DResult multiPathGetNearestCoordinate(MultiPath geom, - Point2D inputPoint, boolean bTestPolygonInterior, - boolean bCalculateLeftRightSide) { - if (geom.getType() == Geometry.Type.Polygon && bTestPolygonInterior) { - Envelope2D env = new Envelope2D(); - geom.queryEnvelope2D(env); - double tolerance = InternalUtils.calculateToleranceFromGeometry( - null, env, false); - - PolygonUtils.PiPResult pipResult; - - if (bCalculateLeftRightSide) - pipResult = PolygonUtils.isPointInPolygon2D((Polygon) geom, - inputPoint, 0.0); - else - pipResult = PolygonUtils.isPointInPolygon2D((Polygon) geom, - inputPoint, tolerance); - - if (pipResult != PolygonUtils.PiPResult.PiPOutside) { - Proximity2DResult result = new Proximity2DResult(inputPoint, 0, - 0.0); - - if (bCalculateLeftRightSide) - result.setRightSide(true); - - return result; - } - } - - SegmentIterator segIter = geom.querySegmentIterator(); - - Point2D closest = new Point2D(); - int closestVertexIndex = -1; - int closestPathIndex = -1; - double closestDistanceSq = NumberUtils.doubleMax(); - boolean bRight = false; - int num_candidates = 0; - - while (segIter.nextPath()) { - while (segIter.hasNextSegment()) { - Segment segment = segIter.nextSegment(); - double t = segment.getClosestCoordinate(inputPoint, false); - - Point2D point = segment.getCoord2D(t); - - double distanceSq = Point2D.sqrDistance(point, inputPoint); - if (distanceSq < closestDistanceSq) { - num_candidates = 1; - closest = point; - closestVertexIndex = segIter.getStartPointIndex(); - closestPathIndex = segIter.getPathIndex(); - closestDistanceSq = distanceSq; - } else if (distanceSq == closestDistanceSq) { - num_candidates++; - } - } - } - - Proximity2DResult result = new Proximity2DResult(closest, - closestVertexIndex, Math.sqrt(closestDistanceSq)); - - if (bCalculateLeftRightSide) { - segIter.resetToVertex(closestVertexIndex, closestPathIndex); - Segment segment = segIter.nextSegment(); - bRight = (Point2D.orientationRobust(inputPoint, - segment.getStartXY(), segment.getEndXY()) < 0); - - if (num_candidates > 1) { - Side_helper sideHelper = new Side_helper(); - sideHelper.reset(); - bRight = sideHelper.calc_side(inputPoint, bRight, geom, - closestVertexIndex, closestPathIndex); - } - - result.setRightSide(bRight); - } - - return result; - } - - Proximity2DResult pointGetNearestVertex(Point geom, Point2D input_point) { - Point2D pt = geom.getXY(); - double distance = Point2D.distance(pt, input_point); - return new Proximity2DResult(pt, 0, distance); - } - - Proximity2DResult multiVertexGetNearestVertex(MultiVertexGeometry geom, - Point2D inputPoint) { - MultiVertexGeometryImpl mpImpl = (MultiVertexGeometryImpl) geom - ._getImpl(); - AttributeStreamOfDbl position = (AttributeStreamOfDbl) mpImpl - .getAttributeStreamRef((Semantics.POSITION)); - int pointCount = geom.getPointCount(); - - int closestIndex = 0; - double closestx = 0.0; - double closesty = 0.0; - double closestDistanceSq = NumberUtils.doubleMax(); - for (int i = 0; i < pointCount; i++) { - Point2D pt = new Point2D(); - position.read(2 * i, pt); - - double distanceSq = Point2D.sqrDistance(pt, inputPoint); - if (distanceSq < closestDistanceSq) { - closestx = pt.x; - closesty = pt.y; - closestIndex = i; - closestDistanceSq = distanceSq; - } - } - - Proximity2DResult result = new Proximity2DResult(); - result._setParams(closestx, closesty, closestIndex, - Math.sqrt(closestDistanceSq)); - - return result; - } - - Proximity2DResult[] pointGetNearestVertices(Point geom, Point2D inputPoint, - double searchRadius, int maxVertexCountToReturn) { - Proximity2DResult[] resultArray; - - if (maxVertexCountToReturn == 0) { - resultArray = new Proximity2DResult[]{}; - return resultArray; - } - - double searchRadiusSq = searchRadius * searchRadius; - Point2D pt = geom.getXY(); - - double distanceSq = Point2D.sqrDistance(pt, inputPoint); - if (distanceSq <= searchRadiusSq) { - resultArray = new Proximity2DResult[1]; - - Proximity2DResult result = new Proximity2DResult(); - result._setParams(pt.x, pt.y, 0, Math.sqrt(distanceSq)); - resultArray[0] = result; - } else { - resultArray = new Proximity2DResult[0]; - } - - return resultArray; - } - - Proximity2DResult[] multiVertexGetNearestVertices(MultiVertexGeometry geom, - Point2D inputPoint, double searchRadius, int maxVertexCountToReturn) { - Proximity2DResult[] resultArray; - - if (maxVertexCountToReturn == 0) { - resultArray = new Proximity2DResult[0]; - return resultArray; - } - - MultiVertexGeometryImpl mpImpl = (MultiVertexGeometryImpl) geom - ._getImpl(); - AttributeStreamOfDbl position = (AttributeStreamOfDbl) mpImpl - .getAttributeStreamRef((Semantics.POSITION)); - int pointCount = geom.getPointCount(); - - ArrayList v = new ArrayList( - maxVertexCountToReturn); - - int count = 0; - double searchRadiusSq = searchRadius * searchRadius; - for (int i = 0; i < pointCount; i++) { - double x = position.read(2 * i); - double y = position.read(2 * i + 1); - - double xDiff = inputPoint.x - x; - double yDiff = inputPoint.y - y; - - double distanceSq = xDiff * xDiff + yDiff * yDiff; - if (distanceSq <= searchRadiusSq) { - Proximity2DResult result = new Proximity2DResult(); - result._setParams(x, y, i, Math.sqrt(distanceSq)); - - count++; - v.add(result); - - } - } - - int vsize = v.size(); - Collections.sort(v, new Proximity2DResultComparator()); - - if (maxVertexCountToReturn >= vsize) - return v.toArray(new Proximity2DResult[0]); - return v.subList(0, maxVertexCountToReturn).toArray( - new Proximity2DResult[0]); - - } + class Side_helper { + int m_i1; + int m_i2; + boolean m_bRight1; + boolean m_bRight2; + + void reset() { + m_i1 = -1; + m_i2 = -1; + m_bRight1 = false; + m_bRight2 = false; + } + + int find_non_degenerate(SegmentIterator segIter, int vertexIndex, + int pathIndex) { + segIter.resetToVertex(vertexIndex, pathIndex); + + while (segIter.hasNextSegment()) { + Segment segment = segIter.nextSegment(); + double length = segment.calculateLength2D(); + + if (length != 0.0) + return segIter.getStartPointIndex(); + } + + segIter.resetToVertex(vertexIndex, pathIndex); + + while (segIter.hasPreviousSegment()) { + Segment segment = segIter.previousSegment(); + double length = segment.calculateLength2D(); + + if (length != 0) + return segIter.getStartPointIndex(); + } + + return -1; + } + + int find_prev_non_degenerate(SegmentIterator segIter, int index) { + segIter.resetToVertex(index, -1); + + while (segIter.hasPreviousSegment()) { + Segment segment = segIter.previousSegment(); + double length = segment.calculateLength2D(); + + if (length != 0) + return segIter.getStartPointIndex(); + } + + return -1; + } + + int find_next_non_degenerate(SegmentIterator segIter, int index) { + segIter.resetToVertex(index, -1); + segIter.nextSegment(); + + while (segIter.hasNextSegment()) { + Segment segment = segIter.nextSegment(); + double length = segment.calculateLength2D(); + + if (length != 0) + return segIter.getStartPointIndex(); + } + + return -1; + } + + void find_analysis_pair_from_index(Point2D inputPoint, + SegmentIterator segIter, int vertexIndex, int pathIndex) { + m_i1 = find_non_degenerate(segIter, vertexIndex, pathIndex); + + if (m_i1 != -1) { + segIter.resetToVertex(m_i1, -1); + Segment segment1 = segIter.nextSegment(); + double t1 = segment1.getClosestCoordinate(inputPoint, false); + Point2D p1 = segment1.getCoord2D(t1); + double d1 = Point2D.sqrDistance(p1, inputPoint); + Point2D pq = new Point2D(); + pq.setCoords(p1); + pq.sub(segment1.getStartXY()); + Point2D pr = new Point2D(); + pr.setCoords(inputPoint); + pr.sub(segment1.getStartXY()); + m_bRight1 = (pq.crossProduct(pr) < 0); + + m_i2 = find_next_non_degenerate(segIter, m_i1); + if (m_i2 != -1) { + segIter.resetToVertex(m_i2, -1); + Segment segment2 = segIter.nextSegment(); + double t2 = segment2 + .getClosestCoordinate(inputPoint, false); + Point2D p2 = segment2.getCoord2D(t2); + double d2 = Point2D.sqrDistance(p2, inputPoint); + + if (d2 > d1) { + m_i2 = -1; + } else { + pq.setCoords(p2); + pq.sub(segment2.getStartXY()); + pr.setCoords(inputPoint); + pr.sub(segment2.getStartXY()); + m_bRight2 = (pq.crossProduct(pr) < 0); + } + } + + if (m_i2 == -1) { + m_i2 = find_prev_non_degenerate(segIter, m_i1); + if (m_i2 != -1) { + segIter.resetToVertex(m_i2, -1); + Segment segment2 = segIter.nextSegment(); + double t2 = segment2.getClosestCoordinate(inputPoint, + false); + Point2D p2 = segment2.getCoord2D(t2); + double d2 = Point2D.sqrDistance(p2, inputPoint); + + if (d2 > d1) + m_i2 = -1; + else { + pq.setCoords(p2); + pq.sub(segment2.getStartXY()); + pr.setCoords(inputPoint); + pr.sub(segment2.getStartXY()); + m_bRight2 = (pq.crossProduct(pr) < 0); + + int itemp = m_i1; + m_i1 = m_i2; + m_i2 = itemp; + + boolean btemp = m_bRight1; + m_bRight1 = m_bRight2; + m_bRight2 = btemp; + } + } + } + } + } + + // Try to find two segements that are not degenerate + boolean calc_side(Point2D inputPoint, boolean bRight, + MultiPath multipath, int vertexIndex, int pathIndex) { + SegmentIterator segIter = multipath.querySegmentIterator(); + + find_analysis_pair_from_index(inputPoint, segIter, vertexIndex, + pathIndex); + + if (m_i1 != -1 && m_i2 == -1) {// could not find a pair of segments + return m_bRight1; + } + + if (m_i1 != -1 && m_i2 != -1) { + if (m_bRight1 == m_bRight2) + return m_bRight1;// no conflicting result for the side + else { + // the conflicting result, that we are trying to resolve, + // happens in the obtuse (outer) side of the turn only. + segIter.resetToVertex(m_i1, -1); + Segment segment1 = segIter.nextSegment(); + Point2D tang1 = segment1._getTangent(1.0); + + segIter.resetToVertex(m_i2, -1); + Segment segment2 = segIter.nextSegment(); + Point2D tang2 = segment2._getTangent(0.0); + + double cross = tang1.crossProduct(tang2); + + if (cross >= 0) // the obtuse angle is on the right side + { + return true; + } else // the obtuse angle is on the right side + { + return false; + } + } + } else { + assert (m_i1 == -1 && m_i2 == -1); + return bRight;// could not resolve the side. So just return the + // old value. + } + } + } + + @Override + public Proximity2DResult getNearestCoordinate(Geometry geom, + Point inputPoint, boolean bTestPolygonInterior) { + + return getNearestCoordinate(geom, inputPoint, bTestPolygonInterior, + false); + } + + @Override + public Proximity2DResult getNearestCoordinate(Geometry geom, + Point inputPoint, boolean bTestPolygonInterior, + boolean bCalculateLeftRightSide) { + if (geom.isEmpty()) + return new Proximity2DResult(); + + Point2D inputPoint2D = inputPoint.getXY(); + + Geometry proxmityTestGeom = geom; + int gt = geom.getType().value(); + + if (gt == Geometry.GeometryType.Envelope) { + Polygon polygon = new Polygon(); + polygon.addEnvelope((Envelope) geom, false); + proxmityTestGeom = polygon; + gt = Geometry.GeometryType.Polygon; + } + switch (gt) { + case Geometry.GeometryType.Point: + return pointGetNearestVertex((Point) proxmityTestGeom, inputPoint2D); + case Geometry.GeometryType.MultiPoint: + return multiVertexGetNearestVertex( + (MultiVertexGeometry) proxmityTestGeom, inputPoint2D); + case Geometry.GeometryType.Polyline: + case Geometry.GeometryType.Polygon: + return multiPathGetNearestCoordinate((MultiPath) proxmityTestGeom, + inputPoint2D, bTestPolygonInterior, bCalculateLeftRightSide); + default: { + throw new GeometryException("not implemented"); + } + } + } + + @Override + public Proximity2DResult getNearestVertex(Geometry geom, Point inputPoint) { + if (geom.isEmpty()) + return new Proximity2DResult(); + + Point2D inputPoint2D = inputPoint.getXY(); + + Geometry proxmityTestGeom = geom; + int gt = geom.getType().value(); + + if (gt == Geometry.GeometryType.Envelope) { + Polygon polygon = new Polygon(); + polygon.addEnvelope((Envelope) geom, false); + proxmityTestGeom = polygon; + gt = Geometry.GeometryType.Polygon; + } + switch (gt) { + case Geometry.GeometryType.Point: + return pointGetNearestVertex((Point) proxmityTestGeom, inputPoint2D); + case Geometry.GeometryType.MultiPoint: + case Geometry.GeometryType.Polyline: + case Geometry.GeometryType.Polygon: + return multiVertexGetNearestVertex( + (MultiVertexGeometry) proxmityTestGeom, inputPoint2D); + default: { + throw new GeometryException("not implemented"); + } + } + } + + @Override + public Proximity2DResult[] getNearestVertices(Geometry geom, + Point inputPoint, double searchRadius, int maxVertexCountToReturn) { + if (maxVertexCountToReturn < 0) + throw new IllegalArgumentException(); + + if (geom.isEmpty()) + return new Proximity2DResult[]{}; + + Point2D inputPoint2D = inputPoint.getXY(); + + Geometry proxmityTestGeom = geom; + int gt = geom.getType().value(); + + if (gt == Geometry.GeometryType.Envelope) { + Polygon polygon = new Polygon(); + polygon.addEnvelope((Envelope) geom, false); + proxmityTestGeom = polygon; + gt = Geometry.GeometryType.Polygon; + } + switch (gt) { + case Geometry.GeometryType.Point: + return pointGetNearestVertices((Point) proxmityTestGeom, + inputPoint2D, searchRadius, maxVertexCountToReturn); + case Geometry.GeometryType.MultiPoint: + case Geometry.GeometryType.Polyline: + case Geometry.GeometryType.Polygon: + return multiVertexGetNearestVertices( + (MultiVertexGeometry) proxmityTestGeom, inputPoint2D, + searchRadius, maxVertexCountToReturn); + default: { + throw new GeometryException("not implemented"); + } + } + } + + Proximity2DResult multiPathGetNearestCoordinate(MultiPath geom, + Point2D inputPoint, boolean bTestPolygonInterior, + boolean bCalculateLeftRightSide) { + if (geom.getType() == Geometry.Type.Polygon && bTestPolygonInterior) { + Envelope2D env = new Envelope2D(); + geom.queryEnvelope2D(env); + double tolerance = InternalUtils.calculateToleranceFromGeometry( + null, env, false); + + PolygonUtils.PiPResult pipResult; + + if (bCalculateLeftRightSide) + pipResult = PolygonUtils.isPointInPolygon2D((Polygon) geom, + inputPoint, 0.0); + else + pipResult = PolygonUtils.isPointInPolygon2D((Polygon) geom, + inputPoint, tolerance); + + if (pipResult != PolygonUtils.PiPResult.PiPOutside) { + Proximity2DResult result = new Proximity2DResult(inputPoint, 0, + 0.0); + + if (bCalculateLeftRightSide) + result.setRightSide(true); + + return result; + } + } + + SegmentIterator segIter = geom.querySegmentIterator(); + + Point2D closest = new Point2D(); + int closestVertexIndex = -1; + int closestPathIndex = -1; + double closestDistanceSq = NumberUtils.doubleMax(); + boolean bRight = false; + int num_candidates = 0; + + while (segIter.nextPath()) { + while (segIter.hasNextSegment()) { + Segment segment = segIter.nextSegment(); + double t = segment.getClosestCoordinate(inputPoint, false); + + Point2D point = segment.getCoord2D(t); + + double distanceSq = Point2D.sqrDistance(point, inputPoint); + if (distanceSq < closestDistanceSq) { + num_candidates = 1; + closest = point; + closestVertexIndex = segIter.getStartPointIndex(); + closestPathIndex = segIter.getPathIndex(); + closestDistanceSq = distanceSq; + } else if (distanceSq == closestDistanceSq) { + num_candidates++; + } + } + } + + Proximity2DResult result = new Proximity2DResult(closest, + closestVertexIndex, Math.sqrt(closestDistanceSq)); + + if (bCalculateLeftRightSide) { + segIter.resetToVertex(closestVertexIndex, closestPathIndex); + Segment segment = segIter.nextSegment(); + bRight = (Point2D.orientationRobust(inputPoint, + segment.getStartXY(), segment.getEndXY()) < 0); + + if (num_candidates > 1) { + Side_helper sideHelper = new Side_helper(); + sideHelper.reset(); + bRight = sideHelper.calc_side(inputPoint, bRight, geom, + closestVertexIndex, closestPathIndex); + } + + result.setRightSide(bRight); + } + + return result; + } + + Proximity2DResult pointGetNearestVertex(Point geom, Point2D input_point) { + Point2D pt = geom.getXY(); + double distance = Point2D.distance(pt, input_point); + return new Proximity2DResult(pt, 0, distance); + } + + Proximity2DResult multiVertexGetNearestVertex(MultiVertexGeometry geom, + Point2D inputPoint) { + MultiVertexGeometryImpl mpImpl = (MultiVertexGeometryImpl) geom + ._getImpl(); + AttributeStreamOfDbl position = (AttributeStreamOfDbl) mpImpl + .getAttributeStreamRef((Semantics.POSITION)); + int pointCount = geom.getPointCount(); + + int closestIndex = 0; + double closestx = 0.0; + double closesty = 0.0; + double closestDistanceSq = NumberUtils.doubleMax(); + for (int i = 0; i < pointCount; i++) { + Point2D pt = new Point2D(); + position.read(2 * i, pt); + + double distanceSq = Point2D.sqrDistance(pt, inputPoint); + if (distanceSq < closestDistanceSq) { + closestx = pt.x; + closesty = pt.y; + closestIndex = i; + closestDistanceSq = distanceSq; + } + } + + Proximity2DResult result = new Proximity2DResult(); + result._setParams(closestx, closesty, closestIndex, + Math.sqrt(closestDistanceSq)); + + return result; + } + + Proximity2DResult[] pointGetNearestVertices(Point geom, Point2D inputPoint, + double searchRadius, int maxVertexCountToReturn) { + Proximity2DResult[] resultArray; + + if (maxVertexCountToReturn == 0) { + resultArray = new Proximity2DResult[]{}; + return resultArray; + } + + double searchRadiusSq = searchRadius * searchRadius; + Point2D pt = geom.getXY(); + + double distanceSq = Point2D.sqrDistance(pt, inputPoint); + if (distanceSq <= searchRadiusSq) { + resultArray = new Proximity2DResult[1]; + + Proximity2DResult result = new Proximity2DResult(); + result._setParams(pt.x, pt.y, 0, Math.sqrt(distanceSq)); + resultArray[0] = result; + } else { + resultArray = new Proximity2DResult[0]; + } + + return resultArray; + } + + Proximity2DResult[] multiVertexGetNearestVertices(MultiVertexGeometry geom, + Point2D inputPoint, double searchRadius, int maxVertexCountToReturn) { + Proximity2DResult[] resultArray; + + if (maxVertexCountToReturn == 0) { + resultArray = new Proximity2DResult[0]; + return resultArray; + } + + MultiVertexGeometryImpl mpImpl = (MultiVertexGeometryImpl) geom + ._getImpl(); + AttributeStreamOfDbl position = (AttributeStreamOfDbl) mpImpl + .getAttributeStreamRef((Semantics.POSITION)); + int pointCount = geom.getPointCount(); + + ArrayList v = new ArrayList( + maxVertexCountToReturn); + + int count = 0; + double searchRadiusSq = searchRadius * searchRadius; + for (int i = 0; i < pointCount; i++) { + double x = position.read(2 * i); + double y = position.read(2 * i + 1); + + double xDiff = inputPoint.x - x; + double yDiff = inputPoint.y - y; + + double distanceSq = xDiff * xDiff + yDiff * yDiff; + if (distanceSq <= searchRadiusSq) { + Proximity2DResult result = new Proximity2DResult(); + result._setParams(x, y, i, Math.sqrt(distanceSq)); + + count++; + v.add(result); + + } + } + + int vsize = v.size(); + Collections.sort(v, new Proximity2DResultComparator()); + + if (maxVertexCountToReturn >= vsize) + return v.toArray(new Proximity2DResult[0]); + return v.subList(0, maxVertexCountToReturn).toArray( + new Proximity2DResult[0]); + + } /* - * if (distanceSq <= searchRadiusSq) { if (count >= maxVertexCountToReturn + + * if (distanceSq <= searchRadiusSq) { if (count >= maxVertexCountToReturn + * 1) { count++; double frontDistance = v.get(0).getDistance(); if * (frontDistance * frontDistance <= distanceSq) continue; } - * + * * Proximity2DResult result = new Proximity2DResult(); result._setParams(x, * y, i, Math.sqrt(distanceSq)); - * + * * count++; - * + * * if (count <= maxVertexCountToReturn) { v.add(result); } // else // { // * if (count == maxVertexCountToReturn + 1) // MAKEHEAP(v, * Proximity2DResult, Proximity2DResult::_Compare); // // PUSHHEAP(v, * result, Proximity2DResult, Proximity2DResult::_Compare); // POPHEAP(v, * Proximity2DResult, Proximity2DResult::_Compare); // } } } - * + * * int vsize = v.size(); Collections.sort(v, new * Proximity2DResultComparator()); - * + * * // SORTDYNAMICARRAY(v, Proximity2DResult, 0, vsize, * Proximity2DResult::_Compare); resultArray = new Proximity2DResult[vsize]; * for (int i = 0; i < vsize; i++) { resultArray[i] = * (Proximity2DResult)v.get(i); } - * + * * return resultArray; } */ } diff --git a/src/main/java/com/esri/core/geometry/OperatorRandomPoints.java b/src/main/java/com/esri/core/geometry/OperatorRandomPoints.java index 5dc0841e..5e72aac2 100644 --- a/src/main/java/com/esri/core/geometry/OperatorRandomPoints.java +++ b/src/main/java/com/esri/core/geometry/OperatorRandomPoints.java @@ -5,27 +5,27 @@ package com.esri.core.geometry; public abstract class OperatorRandomPoints extends Operator { - @Override - public Operator.Type getType() { - return Type.RandomPoints; - } + @Override + public Operator.Type getType() { + return Type.RandomPoints; + } - public abstract GeometryCursor execute( - GeometryCursor inputPolygons, - double[] pointsPerSquareKm, - long seed, - SpatialReference sr, - ProgressTracker progressTracker); + public abstract GeometryCursor execute( + GeometryCursor inputPolygons, + double[] pointsPerSquareKm, + long seed, + SpatialReference sr, + ProgressTracker progressTracker); - public abstract Geometry execute( - Geometry inputPolygon, - double pointsPerSquareKm, - long seed, - SpatialReference sr, - ProgressTracker progressTracker); + public abstract Geometry execute( + Geometry inputPolygon, + double pointsPerSquareKm, + long seed, + SpatialReference sr, + ProgressTracker progressTracker); - public static OperatorRandomPoints local() { - return (OperatorRandomPoints) OperatorFactoryLocal.getInstance().getOperator(Type.RandomPoints); - } + public static OperatorRandomPoints local() { + return (OperatorRandomPoints) OperatorFactoryLocal.getInstance().getOperator(Type.RandomPoints); + } } diff --git a/src/main/java/com/esri/core/geometry/OperatorRandomPointsCursor.java b/src/main/java/com/esri/core/geometry/OperatorRandomPointsCursor.java index 8f73dea2..65a8cf22 100644 --- a/src/main/java/com/esri/core/geometry/OperatorRandomPointsCursor.java +++ b/src/main/java/com/esri/core/geometry/OperatorRandomPointsCursor.java @@ -8,47 +8,47 @@ * Created by davidraleigh on 5/10/17. */ public class OperatorRandomPointsCursor extends GeometryCursor { - private double[] m_pointsPerSquareKm; - private SpatialReferenceImpl m_spatialReference; - private ProgressTracker m_progressTracker; - private Random m_numberGenerator; - private long m_seed; + private double[] m_pointsPerSquareKm; + private SpatialReferenceImpl m_spatialReference; + private ProgressTracker m_progressTracker; + private Random m_numberGenerator; + private long m_seed; - private int m_PPSKmindex; + private int m_PPSKmindex; - public OperatorRandomPointsCursor(GeometryCursor inputGeoms, - double[] pointsPerSquareKm, - long seed, - SpatialReference sr, - ProgressTracker pr) { - m_inputGeoms = inputGeoms; - m_spatialReference = (SpatialReferenceImpl) sr; - m_pointsPerSquareKm = pointsPerSquareKm; - // TODO, for distributed case geometries will be done in different order each time, - // that is why the random number generator is started over with the same seed - // for each object in the cursor - m_seed = seed; - m_numberGenerator = new Random(seed); - } + public OperatorRandomPointsCursor(GeometryCursor inputGeoms, + double[] pointsPerSquareKm, + long seed, + SpatialReference sr, + ProgressTracker pr) { + m_inputGeoms = inputGeoms; + m_spatialReference = (SpatialReferenceImpl) sr; + m_pointsPerSquareKm = pointsPerSquareKm; + // TODO, for distributed case geometries will be done in different order each time, + // that is why the random number generator is started over with the same seed + // for each object in the cursor + m_seed = seed; + m_numberGenerator = new Random(seed); + } - @Override - public Geometry next() { - if (hasNext()) { - if (m_PPSKmindex + 1 < m_pointsPerSquareKm.length) - m_PPSKmindex++; + @Override + public Geometry next() { + if (hasNext()) { + if (m_PPSKmindex + 1 < m_pointsPerSquareKm.length) + m_PPSKmindex++; - m_numberGenerator.setSeed(m_seed); - try { - return RandomPointMaker.generate( - m_inputGeoms.next(), - m_pointsPerSquareKm[m_PPSKmindex], - m_numberGenerator, - m_spatialReference, - m_progressTracker); - } catch (PJException e) { - throw new GeometryException(e.getMessage()); - } - } - return null; - } + m_numberGenerator.setSeed(m_seed); + try { + return RandomPointMaker.generate( + m_inputGeoms.next(), + m_pointsPerSquareKm[m_PPSKmindex], + m_numberGenerator, + m_spatialReference, + m_progressTracker); + } catch (PJException e) { + throw new GeometryException(e.getMessage()); + } + } + return null; + } } diff --git a/src/main/java/com/esri/core/geometry/OperatorRandomPointsLocal.java b/src/main/java/com/esri/core/geometry/OperatorRandomPointsLocal.java index c3ae8cef..42da2350 100644 --- a/src/main/java/com/esri/core/geometry/OperatorRandomPointsLocal.java +++ b/src/main/java/com/esri/core/geometry/OperatorRandomPointsLocal.java @@ -4,26 +4,26 @@ * Created by davidraleigh on 5/10/17. */ public class OperatorRandomPointsLocal extends OperatorRandomPoints { - @Override - public GeometryCursor execute( - GeometryCursor inputPolygons, - double[] pointsPerSquareKm, - long seed, - SpatialReference sr, - ProgressTracker progressTracker) { - GeometryCursor randomPointsCursor = new OperatorRandomPointsCursor(inputPolygons, pointsPerSquareKm, seed, sr, progressTracker); - return randomPointsCursor; - } + @Override + public GeometryCursor execute( + GeometryCursor inputPolygons, + double[] pointsPerSquareKm, + long seed, + SpatialReference sr, + ProgressTracker progressTracker) { + GeometryCursor randomPointsCursor = new OperatorRandomPointsCursor(inputPolygons, pointsPerSquareKm, seed, sr, progressTracker); + return randomPointsCursor; + } - @Override - public Geometry execute( - Geometry inputPolygon, - double pointsPerSquareKm, - long seed, - SpatialReference sr, - ProgressTracker progressTracker) { - double[] perSqrKM = {pointsPerSquareKm}; - GeometryCursor res = execute(new SimpleGeometryCursor(inputPolygon), perSqrKM, seed, sr, progressTracker); - return res.next(); - } + @Override + public Geometry execute( + Geometry inputPolygon, + double pointsPerSquareKm, + long seed, + SpatialReference sr, + ProgressTracker progressTracker) { + double[] perSqrKM = {pointsPerSquareKm}; + GeometryCursor res = execute(new SimpleGeometryCursor(inputPolygon), perSqrKM, seed, sr, progressTracker); + return res.next(); + } } diff --git a/src/main/java/com/esri/core/geometry/OperatorRelate.java b/src/main/java/com/esri/core/geometry/OperatorRelate.java index 3cd3399d..f06d516b 100644 --- a/src/main/java/com/esri/core/geometry/OperatorRelate.java +++ b/src/main/java/com/esri/core/geometry/OperatorRelate.java @@ -31,40 +31,40 @@ * Performs the Relation operation between two geometries using the DE-9IM matrix encoded as a string. */ public abstract class OperatorRelate extends Operator { - @Override - public Type getType() { - return Type.Relate; - } + @Override + public Type getType() { + return Type.Relate; + } - /** - * Performs the Relation operation between two geometries using the DE-9IM matrix encoded as a string. - * - * @param inputGeom1 The first geometry in the relation. - * @param inputGeom2 The second geometry in the relation. - * @param sr The spatial reference of the geometries. - * @param de_9im_string The DE-9IM matrix relation encoded as a string. - * @return Returns True if the relation holds, False otherwise. - */ - public abstract boolean execute(Geometry inputGeom1, Geometry inputGeom2, - SpatialReference sr, String de_9im_string, ProgressTracker progressTracker); + /** + * Performs the Relation operation between two geometries using the DE-9IM matrix encoded as a string. + * + * @param inputGeom1 The first geometry in the relation. + * @param inputGeom2 The second geometry in the relation. + * @param sr The spatial reference of the geometries. + * @param de_9im_string The DE-9IM matrix relation encoded as a string. + * @return Returns True if the relation holds, False otherwise. + */ + public abstract boolean execute(Geometry inputGeom1, Geometry inputGeom2, + SpatialReference sr, String de_9im_string, ProgressTracker progressTracker); - public static OperatorRelate local() { - return (OperatorRelate) OperatorFactoryLocal.getInstance().getOperator( - Type.Relate); - } + public static OperatorRelate local() { + return (OperatorRelate) OperatorFactoryLocal.getInstance().getOperator( + Type.Relate); + } - @Override - public boolean canAccelerateGeometry(Geometry geometry) { - return RelationalOperations.Accelerate_helper - .can_accelerate_geometry(geometry); - } + @Override + public boolean canAccelerateGeometry(Geometry geometry) { + return RelationalOperations.Accelerate_helper + .can_accelerate_geometry(geometry); + } - @Override - public boolean accelerateGeometry(Geometry geometry, - SpatialReference spatialReference, - GeometryAccelerationDegree accelDegree) { - return RelationalOperations.Accelerate_helper.accelerate_geometry( - geometry, spatialReference, accelDegree); - } + @Override + public boolean accelerateGeometry(Geometry geometry, + SpatialReference spatialReference, + GeometryAccelerationDegree accelDegree) { + return RelationalOperations.Accelerate_helper.accelerate_geometry( + geometry, spatialReference, accelDegree); + } } diff --git a/src/main/java/com/esri/core/geometry/OperatorRelateLocal.java b/src/main/java/com/esri/core/geometry/OperatorRelateLocal.java index 59227b73..f40b915d 100644 --- a/src/main/java/com/esri/core/geometry/OperatorRelateLocal.java +++ b/src/main/java/com/esri/core/geometry/OperatorRelateLocal.java @@ -26,11 +26,11 @@ class OperatorRelateLocal extends OperatorRelate { - @Override - public boolean execute(Geometry inputGeom1, Geometry inputGeom2, - SpatialReference sr, String scl, ProgressTracker progress_tracker) { - return RelationalOperationsMatrix.relate(inputGeom1, inputGeom2, sr, - scl, progress_tracker); - } + @Override + public boolean execute(Geometry inputGeom1, Geometry inputGeom2, + SpatialReference sr, String scl, ProgressTracker progress_tracker) { + return RelationalOperationsMatrix.relate(inputGeom1, inputGeom2, sr, + scl, progress_tracker); + } } diff --git a/src/main/java/com/esri/core/geometry/OperatorShapePreservingDensify.java b/src/main/java/com/esri/core/geometry/OperatorShapePreservingDensify.java index 685ab7d9..c9de6126 100644 --- a/src/main/java/com/esri/core/geometry/OperatorShapePreservingDensify.java +++ b/src/main/java/com/esri/core/geometry/OperatorShapePreservingDensify.java @@ -29,43 +29,43 @@ */ abstract class OperatorShapePreservingDensify extends Operator { - @Override - public Type getType() { - return Type.ShapePreservingDensify; - } + @Override + public Type getType() { + return Type.ShapePreservingDensify; + } - /** - * Performs the Shape Preserving Densify operation on the geometry set. Attributes are interpolated along the scalar t-values of the input segments obtained from the length ratios along the - * densified segments. - * - * @param geoms The geometries to be densified. - * @param sr The spatial reference of the geometries. - * @param maxLengthMeters The maximum segment length allowed. Must be a positive value to be used. Pass zero or NaN to disable densification by length. - * @param maxDeviationMeters The maximum deviation. Must be a positive value to be used. Pass zero or NaN to disable densification by deviation. - * @param reserved Must be 0 or NaN. Reserved for future use. Throws and exception if not NaN or 0. - * @return Returns the densified geometries (It does nothing to geometries with dim less than 1, but simply passes them along). - *

- * The operation always starts from the lowest point on the segment, thus guaranteeing that topologically equal segments are always densified exactly the same. - */ - public abstract GeometryCursor execute(GeometryCursor geoms, SpatialReference sr, double maxLengthMeters, double maxDeviationMeters, double reserved, ProgressTracker progressTracker); + /** + * Performs the Shape Preserving Densify operation on the geometry set. Attributes are interpolated along the scalar t-values of the input segments obtained from the length ratios along the + * densified segments. + * + * @param geoms The geometries to be densified. + * @param sr The spatial reference of the geometries. + * @param maxLengthMeters The maximum segment length allowed. Must be a positive value to be used. Pass zero or NaN to disable densification by length. + * @param maxDeviationMeters The maximum deviation. Must be a positive value to be used. Pass zero or NaN to disable densification by deviation. + * @param reserved Must be 0 or NaN. Reserved for future use. Throws and exception if not NaN or 0. + * @return Returns the densified geometries (It does nothing to geometries with dim less than 1, but simply passes them along). + *

+ * The operation always starts from the lowest point on the segment, thus guaranteeing that topologically equal segments are always densified exactly the same. + */ + public abstract GeometryCursor execute(GeometryCursor geoms, SpatialReference sr, double maxLengthMeters, double maxDeviationMeters, double reserved, ProgressTracker progressTracker); - /** - * Performs the Shape Preserving Densify operation on the geometry. Attributes are interpolated along the scalar t-values of the input segments obtained from the length ratios along the densified - * segments. - * - * @param geom The geometry to be densified. - * @param sr The spatial reference of the geometry. - * @param maxLengthMeters The maximum segment length allowed. Must be a positive value to be used. Pass zero or NaN to disable densification by length. - * @param maxDeviationMeters The maximum deviation. Must be a positive value to be used. Pass zero or NaN to disable densification by deviation. - * @param reserved Must be 0 or NaN. Reserved for future use. Throws and exception if not NaN or 0. - * @return Returns the densified geometries (It does nothing to geometries with dim less than 1, but simply passes them along). - *

- * The operation always starts from the lowest point on the segment, thus guaranteeing that topologically equal segments are always densified exactly the same. - */ - public abstract Geometry execute(Geometry geom, SpatialReference sr, double maxLengthMeters, double maxDeviationMeters, double reserved, ProgressTracker progressTracker); + /** + * Performs the Shape Preserving Densify operation on the geometry. Attributes are interpolated along the scalar t-values of the input segments obtained from the length ratios along the densified + * segments. + * + * @param geom The geometry to be densified. + * @param sr The spatial reference of the geometry. + * @param maxLengthMeters The maximum segment length allowed. Must be a positive value to be used. Pass zero or NaN to disable densification by length. + * @param maxDeviationMeters The maximum deviation. Must be a positive value to be used. Pass zero or NaN to disable densification by deviation. + * @param reserved Must be 0 or NaN. Reserved for future use. Throws and exception if not NaN or 0. + * @return Returns the densified geometries (It does nothing to geometries with dim less than 1, but simply passes them along). + *

+ * The operation always starts from the lowest point on the segment, thus guaranteeing that topologically equal segments are always densified exactly the same. + */ + public abstract Geometry execute(Geometry geom, SpatialReference sr, double maxLengthMeters, double maxDeviationMeters, double reserved, ProgressTracker progressTracker); - public static OperatorShapePreservingDensify local() { - return (OperatorShapePreservingDensify) OperatorFactoryLocal.getInstance() - .getOperator(Type.ShapePreservingDensify); - } + public static OperatorShapePreservingDensify local() { + return (OperatorShapePreservingDensify) OperatorFactoryLocal.getInstance() + .getOperator(Type.ShapePreservingDensify); + } } diff --git a/src/main/java/com/esri/core/geometry/OperatorShapePreservingDensifyLocal.java b/src/main/java/com/esri/core/geometry/OperatorShapePreservingDensifyLocal.java index 2a7d4128..50a9874b 100644 --- a/src/main/java/com/esri/core/geometry/OperatorShapePreservingDensifyLocal.java +++ b/src/main/java/com/esri/core/geometry/OperatorShapePreservingDensifyLocal.java @@ -26,19 +26,19 @@ //This is a stub class OperatorShapePreservingDensifyLocal extends - OperatorShapePreservingDensify { - - @Override - public GeometryCursor execute(GeometryCursor geoms, SpatialReference sr, - double maxLengthMeters, double maxDeviationMeters, double reserved, - ProgressTracker progressTracker) { - throw new GeometryException("not implemented"); - } - - @Override - public Geometry execute(Geometry geom, SpatialReference sr, - double maxLengthMeters, double maxDeviationMeters, double reserved, - ProgressTracker progressTracker) { - throw new GeometryException("not implemented"); - } + OperatorShapePreservingDensify { + + @Override + public GeometryCursor execute(GeometryCursor geoms, SpatialReference sr, + double maxLengthMeters, double maxDeviationMeters, double reserved, + ProgressTracker progressTracker) { + throw new GeometryException("not implemented"); + } + + @Override + public Geometry execute(Geometry geom, SpatialReference sr, + double maxLengthMeters, double maxDeviationMeters, double reserved, + ProgressTracker progressTracker) { + throw new GeometryException("not implemented"); + } } diff --git a/src/main/java/com/esri/core/geometry/OperatorSimpleRelation.java b/src/main/java/com/esri/core/geometry/OperatorSimpleRelation.java index feb6a6b1..ff24f76d 100644 --- a/src/main/java/com/esri/core/geometry/OperatorSimpleRelation.java +++ b/src/main/java/com/esri/core/geometry/OperatorSimpleRelation.java @@ -33,42 +33,42 @@ */ public abstract class OperatorSimpleRelation extends Operator { - /** - * Performs the given relation operation between two geometries. - * - * @return Returns True if the relation holds, False otherwise. - */ - public abstract boolean execute(Geometry inputGeom1, - Geometry inputGeom2, - SpatialReference sr, - ProgressTracker progressTracker); + /** + * Performs the given relation operation between two geometries. + * + * @return Returns True if the relation holds, False otherwise. + */ + public abstract boolean execute(Geometry inputGeom1, + Geometry inputGeom2, + SpatialReference sr, + ProgressTracker progressTracker); - public HashMap execute(Geometry inputGeom1, - GeometryCursor geometryCursor2, - SpatialReference sr, - ProgressTracker progressTracker) { - HashMap hashMap = new HashMap<>(); - Geometry inputGeom2; - while ((inputGeom2 = geometryCursor2.next()) != null) { - Long index = geometryCursor2.getGeometryID(); - if ((progressTracker != null) && !(progressTracker.progress(-1, -1))) - throw new RuntimeException("user_canceled"); - hashMap.put(index, execute(inputGeom1, inputGeom2, sr, progressTracker)); - } - return hashMap; - } + public HashMap execute(Geometry inputGeom1, + GeometryCursor geometryCursor2, + SpatialReference sr, + ProgressTracker progressTracker) { + HashMap hashMap = new HashMap<>(); + Geometry inputGeom2; + while ((inputGeom2 = geometryCursor2.next()) != null) { + Long index = geometryCursor2.getGeometryID(); + if ((progressTracker != null) && !(progressTracker.progress(-1, -1))) + throw new RuntimeException("user_canceled"); + hashMap.put(index, execute(inputGeom1, inputGeom2, sr, progressTracker)); + } + return hashMap; + } - @Override - public boolean canAccelerateGeometry(Geometry geometry) { - return RelationalOperations.Accelerate_helper - .can_accelerate_geometry(geometry); - } + @Override + public boolean canAccelerateGeometry(Geometry geometry) { + return RelationalOperations.Accelerate_helper + .can_accelerate_geometry(geometry); + } - @Override - public boolean accelerateGeometry(Geometry geometry, - SpatialReference spatialReference, - GeometryAccelerationDegree accelDegree) { - return RelationalOperations.Accelerate_helper.accelerate_geometry( - geometry, spatialReference, accelDegree); - } + @Override + public boolean accelerateGeometry(Geometry geometry, + SpatialReference spatialReference, + GeometryAccelerationDegree accelDegree) { + return RelationalOperations.Accelerate_helper.accelerate_geometry( + geometry, spatialReference, accelDegree); + } } diff --git a/src/main/java/com/esri/core/geometry/OperatorSimplify.java b/src/main/java/com/esri/core/geometry/OperatorSimplify.java index 400f1e9b..dbba5391 100644 --- a/src/main/java/com/esri/core/geometry/OperatorSimplify.java +++ b/src/main/java/com/esri/core/geometry/OperatorSimplify.java @@ -39,72 +39,72 @@ * See also OperatorSimplifyOGC. */ public abstract class OperatorSimplify extends Operator { - @Override - public Operator.Type getType() { - return Operator.Type.Simplify; - } + @Override + public Operator.Type getType() { + return Operator.Type.Simplify; + } - /** - * Tests if the Geometry is simple. - * - * @param geom The Geometry to be tested. - * @param spatialRef Spatial reference from which the tolerance is obtained. Can be null, then a - * very small tolerance value is derived from the geometry bounds. - * @param bForceTest When True, the Geometry will be tested regardless of the internal IsKnownSimple flag. - * @param result if not null, will contain the results of the check. - * @param progressTracker Allows cancellation of a long operation. Can be null. - **/ - public abstract boolean isSimpleAsFeature(Geometry geom, - SpatialReference spatialRef, - boolean bForceTest, - NonSimpleResult result, - ProgressTracker progressTracker); + /** + * Tests if the Geometry is simple. + * + * @param geom The Geometry to be tested. + * @param spatialRef Spatial reference from which the tolerance is obtained. Can be null, then a + * very small tolerance value is derived from the geometry bounds. + * @param bForceTest When True, the Geometry will be tested regardless of the internal IsKnownSimple flag. + * @param result if not null, will contain the results of the check. + * @param progressTracker Allows cancellation of a long operation. Can be null. + **/ + public abstract boolean isSimpleAsFeature(Geometry geom, + SpatialReference spatialRef, + boolean bForceTest, + NonSimpleResult result, + ProgressTracker progressTracker); - /** - * Tests if the Geometry is simple (second call will use a cached IsKnownSimple flag and immediately return). - * - * @param geom The Geometry to be tested. - * @param spatialRef Spatial reference from which the tolerance is obtained. Can be null, then a - * very small tolerance value is derived from the geometry bounds. - * @param progressTracker Allows cancellation of a long operation. Can be null. - */ - public boolean isSimpleAsFeature(Geometry geom, SpatialReference spatialRef, ProgressTracker progressTracker) { - return isSimpleAsFeature(geom, spatialRef, false, null, progressTracker); - } + /** + * Tests if the Geometry is simple (second call will use a cached IsKnownSimple flag and immediately return). + * + * @param geom The Geometry to be tested. + * @param spatialRef Spatial reference from which the tolerance is obtained. Can be null, then a + * very small tolerance value is derived from the geometry bounds. + * @param progressTracker Allows cancellation of a long operation. Can be null. + */ + public boolean isSimpleAsFeature(Geometry geom, SpatialReference spatialRef, ProgressTracker progressTracker) { + return isSimpleAsFeature(geom, spatialRef, false, null, progressTracker); + } - /** - * Performs the Simplify operation on the geometry cursor. - * - * @param geoms Geometries to simplify. - * @param sr Spatial reference from which the tolerance is obtained. When null, the tolerance - * will be derived individually for each geometry from its bounds. - * @param bForceSimplify When True, the Geometry will be simplified regardless of the internal IsKnownSimple flag. - * @param progressTracker Allows cancellation of a long operation. Can be null. - * @return Returns a GeometryCursor of simplified geometries. - *

- * The isSimpleAsFeature returns true after this method. - */ - public abstract GeometryCursor execute(GeometryCursor geoms, - SpatialReference sr, boolean bForceSimplify, - ProgressTracker progressTracker); + /** + * Performs the Simplify operation on the geometry cursor. + * + * @param geoms Geometries to simplify. + * @param sr Spatial reference from which the tolerance is obtained. When null, the tolerance + * will be derived individually for each geometry from its bounds. + * @param bForceSimplify When True, the Geometry will be simplified regardless of the internal IsKnownSimple flag. + * @param progressTracker Allows cancellation of a long operation. Can be null. + * @return Returns a GeometryCursor of simplified geometries. + *

+ * The isSimpleAsFeature returns true after this method. + */ + public abstract GeometryCursor execute(GeometryCursor geoms, + SpatialReference sr, boolean bForceSimplify, + ProgressTracker progressTracker); - /** - * Performs the Simplify operation on the geometry. - * - * @param geom Geometry to simplify. - * @param sr Spatial reference from which the tolerance is obtained. When null, the tolerance - * will be derived individually for each geometry from its bounds. - * @param bForceSimplify When True, the Geometry will be simplified regardless of the internal IsKnownSimple flag. - * @param progressTracker Allows cancellation of a long operation. Can be null. - * @return Returns a simple geometry. - *

- * The isSimpleAsFeature returns true after this method. - */ - public abstract Geometry execute(Geometry geom, SpatialReference sr, - boolean bForceSimplify, ProgressTracker progressTracker); + /** + * Performs the Simplify operation on the geometry. + * + * @param geom Geometry to simplify. + * @param sr Spatial reference from which the tolerance is obtained. When null, the tolerance + * will be derived individually for each geometry from its bounds. + * @param bForceSimplify When True, the Geometry will be simplified regardless of the internal IsKnownSimple flag. + * @param progressTracker Allows cancellation of a long operation. Can be null. + * @return Returns a simple geometry. + *

+ * The isSimpleAsFeature returns true after this method. + */ + public abstract Geometry execute(Geometry geom, SpatialReference sr, + boolean bForceSimplify, ProgressTracker progressTracker); - public static OperatorSimplify local() { - return (OperatorSimplify) OperatorFactoryLocal.getInstance() - .getOperator(Type.Simplify); - } + public static OperatorSimplify local() { + return (OperatorSimplify) OperatorFactoryLocal.getInstance() + .getOperator(Type.Simplify); + } } diff --git a/src/main/java/com/esri/core/geometry/OperatorSimplifyCursor.java b/src/main/java/com/esri/core/geometry/OperatorSimplifyCursor.java index 362154a5..a2cb146d 100644 --- a/src/main/java/com/esri/core/geometry/OperatorSimplifyCursor.java +++ b/src/main/java/com/esri/core/geometry/OperatorSimplifyCursor.java @@ -24,42 +24,42 @@ package com.esri.core.geometry; class OperatorSimplifyCursor extends GeometryCursor { - SpatialReference m_spatialReference; - ProgressTracker m_progressTracker; - boolean m_bForceSimplify; + SpatialReference m_spatialReference; + ProgressTracker m_progressTracker; + boolean m_bForceSimplify; - // Reviewed vs. Feb 8 2011 - OperatorSimplifyCursor(GeometryCursor geoms, SpatialReference spatialRef, - boolean bForceSimplify, ProgressTracker progressTracker) { - if (geoms == null) - throw new IllegalArgumentException(); + // Reviewed vs. Feb 8 2011 + OperatorSimplifyCursor(GeometryCursor geoms, SpatialReference spatialRef, + boolean bForceSimplify, ProgressTracker progressTracker) { + if (geoms == null) + throw new IllegalArgumentException(); - m_progressTracker = progressTracker; - m_bForceSimplify = bForceSimplify; - m_inputGeoms = geoms; + m_progressTracker = progressTracker; + m_bForceSimplify = bForceSimplify; + m_inputGeoms = geoms; - m_spatialReference = spatialRef; - } + m_spatialReference = spatialRef; + } - // Reviewed vs. Feb 8 2011 - @Override - public Geometry next() { - if (hasNext()) { - if ((m_progressTracker != null) && !(m_progressTracker.progress(-1, -1))) - throw new RuntimeException("user_canceled"); - return simplify(m_inputGeoms.next()); - } - return null; - } + // Reviewed vs. Feb 8 2011 + @Override + public Geometry next() { + if (hasNext()) { + if ((m_progressTracker != null) && !(m_progressTracker.progress(-1, -1))) + throw new RuntimeException("user_canceled"); + return simplify(m_inputGeoms.next()); + } + return null; + } - // Reviewed vs. Feb 8 2011 - Geometry simplify(Geometry geometry) { - if (geometry == null) - throw new IllegalArgumentException(); + // Reviewed vs. Feb 8 2011 + Geometry simplify(Geometry geometry) { + if (geometry == null) + throw new IllegalArgumentException(); - // Geometry.Type type = geometry.getType(); + // Geometry.Type type = geometry.getType(); - return OperatorSimplifyLocalHelper.simplifyAsFeature(geometry, - m_spatialReference, m_bForceSimplify, m_progressTracker); - } + return OperatorSimplifyLocalHelper.simplifyAsFeature(geometry, + m_spatialReference, m_bForceSimplify, m_progressTracker); + } } diff --git a/src/main/java/com/esri/core/geometry/OperatorSimplifyCursorOGC.java b/src/main/java/com/esri/core/geometry/OperatorSimplifyCursorOGC.java index e1d5e0af..55690dc6 100644 --- a/src/main/java/com/esri/core/geometry/OperatorSimplifyCursorOGC.java +++ b/src/main/java/com/esri/core/geometry/OperatorSimplifyCursorOGC.java @@ -24,39 +24,39 @@ package com.esri.core.geometry; class OperatorSimplifyCursorOGC extends GeometryCursor { - SpatialReference m_spatialReference; - ProgressTracker m_progressTracker; - boolean m_bForceSimplify; - - OperatorSimplifyCursorOGC(GeometryCursor geoms, - SpatialReference spatialRef, boolean bForceSimplify, - ProgressTracker progressTracker) { - if (geoms == null) - throw new IllegalArgumentException(); - - m_progressTracker = progressTracker; - m_bForceSimplify = bForceSimplify; - - m_inputGeoms = geoms; - - m_spatialReference = spatialRef; - } - - @Override - public Geometry next() { - if (hasNext()) { - if ((m_progressTracker != null) && !(m_progressTracker.progress(-1, -1))) - throw new RuntimeException("user_canceled"); - return simplify(m_inputGeoms.next()); - } - return null; - } - - Geometry simplify(Geometry geometry) { - if (geometry == null) - throw new IllegalArgumentException(); - - return OperatorSimplifyLocalHelper.simplifyOGC(geometry, - m_spatialReference, m_bForceSimplify, m_progressTracker); - } + SpatialReference m_spatialReference; + ProgressTracker m_progressTracker; + boolean m_bForceSimplify; + + OperatorSimplifyCursorOGC(GeometryCursor geoms, + SpatialReference spatialRef, boolean bForceSimplify, + ProgressTracker progressTracker) { + if (geoms == null) + throw new IllegalArgumentException(); + + m_progressTracker = progressTracker; + m_bForceSimplify = bForceSimplify; + + m_inputGeoms = geoms; + + m_spatialReference = spatialRef; + } + + @Override + public Geometry next() { + if (hasNext()) { + if ((m_progressTracker != null) && !(m_progressTracker.progress(-1, -1))) + throw new RuntimeException("user_canceled"); + return simplify(m_inputGeoms.next()); + } + return null; + } + + Geometry simplify(Geometry geometry) { + if (geometry == null) + throw new IllegalArgumentException(); + + return OperatorSimplifyLocalHelper.simplifyOGC(geometry, + m_spatialReference, m_bForceSimplify, m_progressTracker); + } } diff --git a/src/main/java/com/esri/core/geometry/OperatorSimplifyLocal.java b/src/main/java/com/esri/core/geometry/OperatorSimplifyLocal.java index a1eea89c..c18690ad 100644 --- a/src/main/java/com/esri/core/geometry/OperatorSimplifyLocal.java +++ b/src/main/java/com/esri/core/geometry/OperatorSimplifyLocal.java @@ -25,35 +25,35 @@ class OperatorSimplifyLocal extends OperatorSimplify { - // Reviewed vs. Feb 8 2011 - @Override - public GeometryCursor execute(GeometryCursor geoms, - SpatialReference spatialRef, - boolean bForceSimplify, - ProgressTracker progressTracker) { - return new OperatorSimplifyCursor(geoms, spatialRef, bForceSimplify,progressTracker); - } - - // Reviewed vs. Feb 8 2011 - @Override - public boolean isSimpleAsFeature(Geometry geom, - SpatialReference spatialRef, - boolean bForceTest, - NonSimpleResult result, - ProgressTracker progressTracker) { - int res = OperatorSimplifyLocalHelper.isSimpleAsFeature(geom, spatialRef, bForceTest, result, progressTracker); - return res > 0; - } - - // Reviewed vs. Feb 8 2011 - @Override - public Geometry execute(Geometry geom, - SpatialReference spatialRef, - boolean bForceSimplify, - ProgressTracker progressTracker) { - SimpleGeometryCursor inputCursor = new SimpleGeometryCursor(geom); - GeometryCursor outputCursor = execute(inputCursor, spatialRef,bForceSimplify, progressTracker); - - return outputCursor.next(); - } + // Reviewed vs. Feb 8 2011 + @Override + public GeometryCursor execute(GeometryCursor geoms, + SpatialReference spatialRef, + boolean bForceSimplify, + ProgressTracker progressTracker) { + return new OperatorSimplifyCursor(geoms, spatialRef, bForceSimplify, progressTracker); + } + + // Reviewed vs. Feb 8 2011 + @Override + public boolean isSimpleAsFeature(Geometry geom, + SpatialReference spatialRef, + boolean bForceTest, + NonSimpleResult result, + ProgressTracker progressTracker) { + int res = OperatorSimplifyLocalHelper.isSimpleAsFeature(geom, spatialRef, bForceTest, result, progressTracker); + return res > 0; + } + + // Reviewed vs. Feb 8 2011 + @Override + public Geometry execute(Geometry geom, + SpatialReference spatialRef, + boolean bForceSimplify, + ProgressTracker progressTracker) { + SimpleGeometryCursor inputCursor = new SimpleGeometryCursor(geom); + GeometryCursor outputCursor = execute(inputCursor, spatialRef, bForceSimplify, progressTracker); + + return outputCursor.next(); + } } diff --git a/src/main/java/com/esri/core/geometry/OperatorSimplifyLocalHelper.java b/src/main/java/com/esri/core/geometry/OperatorSimplifyLocalHelper.java index b6707af3..795c86ac 100644 --- a/src/main/java/com/esri/core/geometry/OperatorSimplifyLocalHelper.java +++ b/src/main/java/com/esri/core/geometry/OperatorSimplifyLocalHelper.java @@ -31,2204 +31,2204 @@ import com.esri.core.geometry.MultiVertexGeometryImpl.GeometryXSimple; class OperatorSimplifyLocalHelper { - private static final class Edge { - Edge() { - m_flags = 0; - // m_segment.createInstance(); - } - - Segment m_segment; - int m_vertexIndex; - int m_pathIndex; - int m_flags; - - void setReversed(boolean bYesNo) { - m_flags &= (~1); - m_flags = m_flags | (bYesNo ? 1 : 0); - } - - // The value returned by GetReversed is interpreted differently in - // checkSelfIntersections_ and checkValidRingOrientation_ - boolean getReversed() /* const */ { - return (m_flags & 1) != 0; - } - - int getRightSide() /* const */ { - return getReversed() ? 0 : 1; // 0 means there should be an - // emptiness on the right side of - // the edge, 1 means there is - // interior - } - } - - private final VertexDescription m_description; - private Geometry m_geometry; - private SpatialReferenceImpl m_sr; - private int m_dbgCounter; // debugging counter(for breakpoints) - private double m_toleranceIsSimple; - private double m_toleranceSimplify; - // private double m_toleranceCluster; //cluster tolerance needs to be - // sqrt(2) times larger than the tolerance of the other simplify processes. - private int m_knownSimpleResult; - private int m_attributeCount; - - private ArrayList m_edges; - private AttributeStreamOfInt32 m_FreeEdges; - private ArrayList m_lineEdgesRecycle; - private AttributeStreamOfInt32 m_newEdges; - private SegmentIteratorImpl m_recycledSegIter; - private IndexMultiDCList m_crossOverHelperList; - private AttributeStreamOfInt32 m_paths_for_OGC_tests; - - private ProgressTracker m_progressTracker; - - private Treap m_AET; - private AttributeStreamOfInt32 m_xyToNode1; // for each vertex, contains -1, - // or the edge node. - private AttributeStreamOfInt32 m_xyToNode2; // for each vertex, contains -1, - // or the edge node. - private AttributeStreamOfInt32 m_pathOrientations; // 0 if undefined, -1 for - // counterclockwise, 1 - // for clockwise. - private AttributeStreamOfInt32 m_pathParentage; - private int m_unknownOrientationPathCount; - private double m_yScanline; - - private AttributeStreamOfDbl m_xy; - private AttributeStreamOfInt32 m_pairs; - private AttributeStreamOfInt32 m_pairIndices; - - private EditShape m_editShape; - private boolean m_bOGCRestrictions; - private boolean m_bPlanarSimplify; - - private int isSimplePlanarImpl_() { - m_bPlanarSimplify = true; - if (Geometry.isMultiPath(m_geometry.getType().value())) { - if (!checkStructure_()) // check structure of geometry(no zero - // length paths, etc) - return 0; - - if (!checkDegenerateSegments_(false)) // check for degenerate - // segments(only 2D,no zs or - // other attributes) - return 0; - } - - if (!checkClustering_()) // check clustering(points are either - // coincident,or further than tolerance) - return 0; - - if (!Geometry.isMultiPath(m_geometry.getType().value())) - return 2; // multipoint is simple - - if (!checkCracking_()) // check that there are no self intersections and - // overlaps among segments. - return 0; - - if (m_geometry.getType() == Geometry.Type.Polyline) { - if (!checkSelfIntersectionsPolylinePlanar_()) - return 0; - - return 2; // polyline is simple - } - - if (!checkSelfIntersections_()) // check that there are no other self - // intersections (for the cases of - // several segments connect in a point) - return 0; - - // check that every hole is counterclockwise, and every exterior is - // clockwise. - // for the strong simple also check that exterior rings are followed by - // the interior rings. - return checkValidRingOrientation_(); - } - - private boolean testToleranceDistance_(int xyindex1, int xyindex2) { - double x1 = m_xy.read(2 * xyindex1); - double y1 = m_xy.read(2 * xyindex1 + 1); - double x2 = m_xy.read(2 * xyindex2); - double y2 = m_xy.read(2 * xyindex2 + 1); - boolean b = !Clusterer.isClusterCandidate_(x1, y1, x2, y2, - m_toleranceIsSimple * m_toleranceIsSimple); - if (!b) { - if (m_geometry.getDimension() == 0) - return false; - - return (x1 == x2 && y1 == y2); // points either coincide or - // further,than the tolerance - } - - return b; - } - - private boolean checkStructure_() { - MultiPathImpl multiPathImpl = (MultiPathImpl) m_geometry._getImpl(); - int minsize = multiPathImpl.m_bPolygon ? 3 : 2; - for (int ipath = 0, npath = multiPathImpl.getPathCount(); ipath < npath; ipath++) { - if (multiPathImpl.getPathSize(ipath) < minsize) { - m_nonSimpleResult = new NonSimpleResult(NonSimpleResult.Reason.Structure, ipath, 0); - return false; - } - } - - return true; - } - - private boolean checkDegenerateSegments_(boolean bTestZs) { - MultiPathImpl multiPathImpl = (MultiPathImpl) m_geometry._getImpl(); - SegmentIteratorImpl segIter = multiPathImpl.querySegmentIterator(); - // Envelope2D env2D; - boolean bHasZ = multiPathImpl - .hasAttribute(VertexDescription.Semantics.Z); - double ztolerance = !bHasZ ? 0 : InternalUtils - .calculateZToleranceFromGeometry(m_sr, multiPathImpl, false); - while (segIter.nextPath()) { - while (segIter.hasNextSegment()) { - /* const */ - Segment seg = segIter.nextSegment(); - double length = seg.calculateLength2D(); - if (length > m_toleranceIsSimple) - continue; - - if (bTestZs && bHasZ) { - double z0 = seg.getStartAttributeAsDbl( - VertexDescription.Semantics.Z, 0); - double z1 = seg.getStartAttributeAsDbl( - VertexDescription.Semantics.Z, 0); - if (Math.abs(z1 - z0) > ztolerance) - continue; - } - - m_nonSimpleResult = new NonSimpleResult( - NonSimpleResult.Reason.DegenerateSegments, - segIter.getStartPointIndex(), -1); - return false; - } - } - - return true; - } - - private boolean checkClustering_() { - MultiVertexGeometryImpl multiVertexImpl = (MultiVertexGeometryImpl) m_geometry - ._getImpl(); - - MultiPathImpl multiPathImpl = null; - if (Geometry.isMultiPath(m_geometry.getType().value())) - multiPathImpl = (MultiPathImpl) m_geometry._getImpl(); - - boolean get_paths = (m_bPlanarSimplify || m_bOGCRestrictions) - && multiPathImpl != null; - - int pointCount = multiVertexImpl.getPointCount(); - m_xy = (AttributeStreamOfDbl) multiVertexImpl - .getAttributeStreamRef(VertexDescription.Semantics.POSITION); - m_pairs = new AttributeStreamOfInt32(0); - m_pairs.reserve(pointCount * 2); - m_pairIndices = new AttributeStreamOfInt32(0); - m_pairIndices.reserve(pointCount * 2); - if (get_paths) { - if (m_paths_for_OGC_tests == null) - m_paths_for_OGC_tests = new AttributeStreamOfInt32(0); - m_paths_for_OGC_tests.reserve(pointCount); - } - int ipath = 0; - for (int i = 0; i < pointCount; i++) { - m_pairs.add(2 * i); // y - tol(BOTTOM) - m_pairs.add(2 * i + 1); // y + tol(TOP) - m_pairIndices.add(2 * i); - m_pairIndices.add(2 * i + 1); - if (get_paths) { - while (i >= multiPathImpl.getPathEnd(ipath)) - ipath++; - - m_paths_for_OGC_tests.add(ipath); - } - - } - - BucketSort sorter = new BucketSort(); - sorter.sort(m_pairIndices, 0, 2 * pointCount, new IndexSorter(this, - get_paths)); - - m_AET.clear(); - m_AET.setComparator(new ClusterTestComparator(this)); - m_AET.setCapacity(pointCount); - for (int index = 0, n = pointCount * 2; index < n; index++) { - int pairIndex = m_pairIndices.get(index); - int pair = m_pairs.get(pairIndex); - int xyindex = pair >> 1; // k = 2n or 2n + 1 represent a vertical - // segment for the same vertex. - // Therefore, k / 2 represents a vertex - // index - // Points need to be either exactly equal or further than 2 * - // tolerance apart. - if ((pair & 1) == 0) {// bottom element - int aetNode = m_AET.addElement(xyindex, -1); - // add it to the AET,end test it against its left and right - // neighbours. - int leftneighbour = m_AET.getPrev(aetNode); - if (leftneighbour != Treap.nullNode() - && !testToleranceDistance_( - m_AET.getElement(leftneighbour), xyindex)) { - m_nonSimpleResult = new NonSimpleResult( - NonSimpleResult.Reason.Clustering, xyindex, - m_AET.getElement(leftneighbour)); - return false; - } - int rightneighbour = m_AET.getNext(aetNode); - if (rightneighbour != Treap.nullNode() - && !testToleranceDistance_( - m_AET.getElement(rightneighbour), xyindex)) { - m_nonSimpleResult = new NonSimpleResult( - NonSimpleResult.Reason.Clustering, xyindex, - m_AET.getElement(rightneighbour)); - return false; - } - } else { // top - // get left and right neighbours, and remove the element - // from AET. Then test the neighbours with the - // tolerance. - int aetNode = m_AET.search(xyindex, -1); - int leftneighbour = m_AET.getPrev(aetNode); - int rightneighbour = m_AET.getNext(aetNode); - m_AET.deleteNode(aetNode, -1); - if (leftneighbour != Treap.nullNode() - && rightneighbour != Treap.nullNode() - && !testToleranceDistance_( - m_AET.getElement(leftneighbour), - m_AET.getElement(rightneighbour))) { - m_nonSimpleResult = new NonSimpleResult( - NonSimpleResult.Reason.Clustering, - m_AET.getElement(leftneighbour), - m_AET.getElement(rightneighbour)); - return false; - } - } - } - - return true; - } - - private boolean checkCracking_() { - MultiVertexGeometryImpl multiVertexImpl = (MultiVertexGeometryImpl) m_geometry - ._getImpl(); - int pointCount = multiVertexImpl.getPointCount(); - if (pointCount < 10)// use brute force for smaller polygons - { - return checkCrackingBrute_(); - } else { - return checkCrackingPlanesweep_(); - } - } - - private boolean checkCrackingPlanesweep_() // cracker,that uses planesweep - // algorithm. - { - EditShape editShape = new EditShape(); - editShape.addGeometry(m_geometry); - NonSimpleResult result = new NonSimpleResult(); - boolean bNonSimple = Cracker.needsCracking(false, editShape, - m_toleranceIsSimple, result, m_progressTracker); - if (bNonSimple) { - result.m_vertexIndex1 = editShape - .getVertexIndex(result.m_vertexIndex1); - result.m_vertexIndex2 = editShape - .getVertexIndex(result.m_vertexIndex2); - m_nonSimpleResult.Assign(result); - return false; - } else - return true; - } - - private boolean checkCrackingBrute_() // cracker, that uses brute force (a - // double loop) to find segment - // intersections. - { - MultiPathImpl multiPathImpl = (MultiPathImpl) m_geometry._getImpl(); - // Implementation without a QuadTreeImpl accelerator - SegmentIteratorImpl segIter1 = multiPathImpl.querySegmentIterator(); - SegmentIteratorImpl segIter2 = multiPathImpl.querySegmentIterator(); - // Envelope2D env2D; - while (segIter1.nextPath()) { - while (segIter1.hasNextSegment()) { + private static final class Edge { + Edge() { + m_flags = 0; + // m_segment.createInstance(); + } + + Segment m_segment; + int m_vertexIndex; + int m_pathIndex; + int m_flags; + + void setReversed(boolean bYesNo) { + m_flags &= (~1); + m_flags = m_flags | (bYesNo ? 1 : 0); + } + + // The value returned by GetReversed is interpreted differently in + // checkSelfIntersections_ and checkValidRingOrientation_ + boolean getReversed() /* const */ { + return (m_flags & 1) != 0; + } + + int getRightSide() /* const */ { + return getReversed() ? 0 : 1; // 0 means there should be an + // emptiness on the right side of + // the edge, 1 means there is + // interior + } + } + + private final VertexDescription m_description; + private Geometry m_geometry; + private SpatialReferenceImpl m_sr; + private int m_dbgCounter; // debugging counter(for breakpoints) + private double m_toleranceIsSimple; + private double m_toleranceSimplify; + // private double m_toleranceCluster; //cluster tolerance needs to be + // sqrt(2) times larger than the tolerance of the other simplify processes. + private int m_knownSimpleResult; + private int m_attributeCount; + + private ArrayList m_edges; + private AttributeStreamOfInt32 m_FreeEdges; + private ArrayList m_lineEdgesRecycle; + private AttributeStreamOfInt32 m_newEdges; + private SegmentIteratorImpl m_recycledSegIter; + private IndexMultiDCList m_crossOverHelperList; + private AttributeStreamOfInt32 m_paths_for_OGC_tests; + + private ProgressTracker m_progressTracker; + + private Treap m_AET; + private AttributeStreamOfInt32 m_xyToNode1; // for each vertex, contains -1, + // or the edge node. + private AttributeStreamOfInt32 m_xyToNode2; // for each vertex, contains -1, + // or the edge node. + private AttributeStreamOfInt32 m_pathOrientations; // 0 if undefined, -1 for + // counterclockwise, 1 + // for clockwise. + private AttributeStreamOfInt32 m_pathParentage; + private int m_unknownOrientationPathCount; + private double m_yScanline; + + private AttributeStreamOfDbl m_xy; + private AttributeStreamOfInt32 m_pairs; + private AttributeStreamOfInt32 m_pairIndices; + + private EditShape m_editShape; + private boolean m_bOGCRestrictions; + private boolean m_bPlanarSimplify; + + private int isSimplePlanarImpl_() { + m_bPlanarSimplify = true; + if (Geometry.isMultiPath(m_geometry.getType().value())) { + if (!checkStructure_()) // check structure of geometry(no zero + // length paths, etc) + return 0; + + if (!checkDegenerateSegments_(false)) // check for degenerate + // segments(only 2D,no zs or + // other attributes) + return 0; + } + + if (!checkClustering_()) // check clustering(points are either + // coincident,or further than tolerance) + return 0; + + if (!Geometry.isMultiPath(m_geometry.getType().value())) + return 2; // multipoint is simple + + if (!checkCracking_()) // check that there are no self intersections and + // overlaps among segments. + return 0; + + if (m_geometry.getType() == Geometry.Type.Polyline) { + if (!checkSelfIntersectionsPolylinePlanar_()) + return 0; + + return 2; // polyline is simple + } + + if (!checkSelfIntersections_()) // check that there are no other self + // intersections (for the cases of + // several segments connect in a point) + return 0; + + // check that every hole is counterclockwise, and every exterior is + // clockwise. + // for the strong simple also check that exterior rings are followed by + // the interior rings. + return checkValidRingOrientation_(); + } + + private boolean testToleranceDistance_(int xyindex1, int xyindex2) { + double x1 = m_xy.read(2 * xyindex1); + double y1 = m_xy.read(2 * xyindex1 + 1); + double x2 = m_xy.read(2 * xyindex2); + double y2 = m_xy.read(2 * xyindex2 + 1); + boolean b = !Clusterer.isClusterCandidate_(x1, y1, x2, y2, + m_toleranceIsSimple * m_toleranceIsSimple); + if (!b) { + if (m_geometry.getDimension() == 0) + return false; + + return (x1 == x2 && y1 == y2); // points either coincide or + // further,than the tolerance + } + + return b; + } + + private boolean checkStructure_() { + MultiPathImpl multiPathImpl = (MultiPathImpl) m_geometry._getImpl(); + int minsize = multiPathImpl.m_bPolygon ? 3 : 2; + for (int ipath = 0, npath = multiPathImpl.getPathCount(); ipath < npath; ipath++) { + if (multiPathImpl.getPathSize(ipath) < minsize) { + m_nonSimpleResult = new NonSimpleResult(NonSimpleResult.Reason.Structure, ipath, 0); + return false; + } + } + + return true; + } + + private boolean checkDegenerateSegments_(boolean bTestZs) { + MultiPathImpl multiPathImpl = (MultiPathImpl) m_geometry._getImpl(); + SegmentIteratorImpl segIter = multiPathImpl.querySegmentIterator(); + // Envelope2D env2D; + boolean bHasZ = multiPathImpl + .hasAttribute(VertexDescription.Semantics.Z); + double ztolerance = !bHasZ ? 0 : InternalUtils + .calculateZToleranceFromGeometry(m_sr, multiPathImpl, false); + while (segIter.nextPath()) { + while (segIter.hasNextSegment()) { /* const */ - Segment seg1 = segIter1.nextSegment(); - if (!segIter1.isLastSegmentInPath() || !segIter1.isLastPath()) { - segIter2.resetTo(segIter1); - do { - while (segIter2.hasNextSegment()) { + Segment seg = segIter.nextSegment(); + double length = seg.calculateLength2D(); + if (length > m_toleranceIsSimple) + continue; + + if (bTestZs && bHasZ) { + double z0 = seg.getStartAttributeAsDbl( + VertexDescription.Semantics.Z, 0); + double z1 = seg.getStartAttributeAsDbl( + VertexDescription.Semantics.Z, 0); + if (Math.abs(z1 - z0) > ztolerance) + continue; + } + + m_nonSimpleResult = new NonSimpleResult( + NonSimpleResult.Reason.DegenerateSegments, + segIter.getStartPointIndex(), -1); + return false; + } + } + + return true; + } + + private boolean checkClustering_() { + MultiVertexGeometryImpl multiVertexImpl = (MultiVertexGeometryImpl) m_geometry + ._getImpl(); + + MultiPathImpl multiPathImpl = null; + if (Geometry.isMultiPath(m_geometry.getType().value())) + multiPathImpl = (MultiPathImpl) m_geometry._getImpl(); + + boolean get_paths = (m_bPlanarSimplify || m_bOGCRestrictions) + && multiPathImpl != null; + + int pointCount = multiVertexImpl.getPointCount(); + m_xy = (AttributeStreamOfDbl) multiVertexImpl + .getAttributeStreamRef(VertexDescription.Semantics.POSITION); + m_pairs = new AttributeStreamOfInt32(0); + m_pairs.reserve(pointCount * 2); + m_pairIndices = new AttributeStreamOfInt32(0); + m_pairIndices.reserve(pointCount * 2); + if (get_paths) { + if (m_paths_for_OGC_tests == null) + m_paths_for_OGC_tests = new AttributeStreamOfInt32(0); + m_paths_for_OGC_tests.reserve(pointCount); + } + int ipath = 0; + for (int i = 0; i < pointCount; i++) { + m_pairs.add(2 * i); // y - tol(BOTTOM) + m_pairs.add(2 * i + 1); // y + tol(TOP) + m_pairIndices.add(2 * i); + m_pairIndices.add(2 * i + 1); + if (get_paths) { + while (i >= multiPathImpl.getPathEnd(ipath)) + ipath++; + + m_paths_for_OGC_tests.add(ipath); + } + + } + + BucketSort sorter = new BucketSort(); + sorter.sort(m_pairIndices, 0, 2 * pointCount, new IndexSorter(this, + get_paths)); + + m_AET.clear(); + m_AET.setComparator(new ClusterTestComparator(this)); + m_AET.setCapacity(pointCount); + for (int index = 0, n = pointCount * 2; index < n; index++) { + int pairIndex = m_pairIndices.get(index); + int pair = m_pairs.get(pairIndex); + int xyindex = pair >> 1; // k = 2n or 2n + 1 represent a vertical + // segment for the same vertex. + // Therefore, k / 2 represents a vertex + // index + // Points need to be either exactly equal or further than 2 * + // tolerance apart. + if ((pair & 1) == 0) {// bottom element + int aetNode = m_AET.addElement(xyindex, -1); + // add it to the AET,end test it against its left and right + // neighbours. + int leftneighbour = m_AET.getPrev(aetNode); + if (leftneighbour != Treap.nullNode() + && !testToleranceDistance_( + m_AET.getElement(leftneighbour), xyindex)) { + m_nonSimpleResult = new NonSimpleResult( + NonSimpleResult.Reason.Clustering, xyindex, + m_AET.getElement(leftneighbour)); + return false; + } + int rightneighbour = m_AET.getNext(aetNode); + if (rightneighbour != Treap.nullNode() + && !testToleranceDistance_( + m_AET.getElement(rightneighbour), xyindex)) { + m_nonSimpleResult = new NonSimpleResult( + NonSimpleResult.Reason.Clustering, xyindex, + m_AET.getElement(rightneighbour)); + return false; + } + } else { // top + // get left and right neighbours, and remove the element + // from AET. Then test the neighbours with the + // tolerance. + int aetNode = m_AET.search(xyindex, -1); + int leftneighbour = m_AET.getPrev(aetNode); + int rightneighbour = m_AET.getNext(aetNode); + m_AET.deleteNode(aetNode, -1); + if (leftneighbour != Treap.nullNode() + && rightneighbour != Treap.nullNode() + && !testToleranceDistance_( + m_AET.getElement(leftneighbour), + m_AET.getElement(rightneighbour))) { + m_nonSimpleResult = new NonSimpleResult( + NonSimpleResult.Reason.Clustering, + m_AET.getElement(leftneighbour), + m_AET.getElement(rightneighbour)); + return false; + } + } + } + + return true; + } + + private boolean checkCracking_() { + MultiVertexGeometryImpl multiVertexImpl = (MultiVertexGeometryImpl) m_geometry + ._getImpl(); + int pointCount = multiVertexImpl.getPointCount(); + if (pointCount < 10)// use brute force for smaller polygons + { + return checkCrackingBrute_(); + } else { + return checkCrackingPlanesweep_(); + } + } + + private boolean checkCrackingPlanesweep_() // cracker,that uses planesweep + // algorithm. + { + EditShape editShape = new EditShape(); + editShape.addGeometry(m_geometry); + NonSimpleResult result = new NonSimpleResult(); + boolean bNonSimple = Cracker.needsCracking(false, editShape, + m_toleranceIsSimple, result, m_progressTracker); + if (bNonSimple) { + result.m_vertexIndex1 = editShape + .getVertexIndex(result.m_vertexIndex1); + result.m_vertexIndex2 = editShape + .getVertexIndex(result.m_vertexIndex2); + m_nonSimpleResult.Assign(result); + return false; + } else + return true; + } + + private boolean checkCrackingBrute_() // cracker, that uses brute force (a + // double loop) to find segment + // intersections. + { + MultiPathImpl multiPathImpl = (MultiPathImpl) m_geometry._getImpl(); + // Implementation without a QuadTreeImpl accelerator + SegmentIteratorImpl segIter1 = multiPathImpl.querySegmentIterator(); + SegmentIteratorImpl segIter2 = multiPathImpl.querySegmentIterator(); + // Envelope2D env2D; + while (segIter1.nextPath()) { + while (segIter1.hasNextSegment()) { + /* const */ + Segment seg1 = segIter1.nextSegment(); + if (!segIter1.isLastSegmentInPath() || !segIter1.isLastPath()) { + segIter2.resetTo(segIter1); + do { + while (segIter2.hasNextSegment()) { /* const */ - Segment seg2 = segIter2.nextSegment(); - int res = seg1._isIntersecting(seg2, - m_toleranceIsSimple, true); - if (res != 0) { - NonSimpleResult.Reason reason = res == 2 ? NonSimpleResult.Reason.CrossOver - : NonSimpleResult.Reason.Cracking; - m_nonSimpleResult = new NonSimpleResult(reason, - segIter1.getStartPointIndex(), - segIter2.getStartPointIndex()); - return false; - } - } - } while (segIter2.nextPath()); - } - } - } - return true; - } - - private boolean checkSelfIntersections_() { - MultiPathImpl multiPathImpl = (MultiPathImpl) m_geometry._getImpl(); - m_edges.clear(); - m_edges.ensureCapacity(20);// we reuse the edges while going through a - // polygon. - m_lineEdgesRecycle.clear(); - m_lineEdgesRecycle.ensureCapacity(20);// we reuse the edges while going - // through a polygon. - - m_recycledSegIter = multiPathImpl.querySegmentIterator(); - m_recycledSegIter.setCirculator(true); - - AttributeStreamOfInt32 bunch = new AttributeStreamOfInt32(0);// stores - // coincident - // vertices - bunch.reserve(10); - int pointCount = multiPathImpl.getPointCount(); - double xprev = NumberUtils.TheNaN; - double yprev = 0; - // We already have a sorted list of vertices from clustering check. - for (int index = 0, n = pointCount * 2; index < n; index++) { - int pairIndex = m_pairIndices.get(index); - int pair = m_pairs.get(pairIndex); - if ((pair & 1) != 0) - continue; // m_pairs array is redundant. See checkClustering_. - - int xyindex = pair >> 1; - - double x = m_xy.read(2 * xyindex); - double y = m_xy.read(2 * xyindex + 1); - if (bunch.size() != 0) { - if (x != xprev || y != yprev) { - if (!processBunchForSelfIntersectionTest_(bunch)) - return false; - if (bunch != null) - bunch.clear(false); - } - } - - bunch.add(xyindex); - xprev = x; - yprev = y; - } - - assert (bunch.size() > 0);// cannot be empty - - if (!processBunchForSelfIntersectionTest_(bunch)) - return false; - - return true; - } - - static final class Vertex_info { - double x, y; - int ipath; - int ivertex; - boolean boundary; - } - - ; - - static final class Vertex_info_pl { - double x; - double y; - int ipath; - int ivertex; - boolean boundary; - boolean end_point; - } - - ; - - boolean checkSelfIntersectionsPolylinePlanar_() { - MultiPathImpl multiPathImpl = (MultiPathImpl) m_geometry._getImpl(); - - boolean closedPaths[] = new boolean[multiPathImpl.getPathCount()]; - for (int ipath = 0, npaths = multiPathImpl.getPathCount(); ipath < npaths; ipath++) { - closedPaths[ipath] = multiPathImpl.isClosedPathInXYPlane(ipath); - } - - Vertex_info_pl vi_prev = new Vertex_info_pl(); - boolean is_closed_path; - int path_start; - int path_last; - - Point2D pt = new Point2D(); - - {// scope - int pairIndex = m_pairIndices.get(0); - int pair = m_pairs.get(pairIndex); - int xyindex = pair >> 1; - m_xy.read(2 * xyindex, pt); - int ipath = m_paths_for_OGC_tests.get(xyindex); - is_closed_path = closedPaths[ipath]; - path_start = multiPathImpl.getPathStart(ipath); - path_last = multiPathImpl.getPathEnd(ipath) - 1; - vi_prev.end_point = (xyindex == path_start) - || (xyindex == path_last); - if (m_bOGCRestrictions) - vi_prev.boundary = !is_closed_path && vi_prev.end_point; - else - // for regular planar simplify, only the end points are allowed - // to coincide - vi_prev.boundary = vi_prev.end_point; - vi_prev.ipath = ipath; - vi_prev.x = pt.x; - vi_prev.y = pt.y; - vi_prev.ivertex = xyindex; - } - - Vertex_info_pl vi = new Vertex_info_pl(); - - for (int index = 1, n = m_pairIndices.size(); index < n; index++) { - int pairIndex = m_pairIndices.get(index); - int pair = m_pairs.get(pairIndex); - if ((pair & 1) != 0) - continue; - - int xyindex = pair >> 1; - m_xy.read(2 * xyindex, pt); - int ipath = m_paths_for_OGC_tests.get(xyindex); - if (ipath != vi_prev.ipath) { - is_closed_path = closedPaths[ipath]; - path_start = multiPathImpl.getPathStart(ipath); - path_last = multiPathImpl.getPathEnd(ipath) - 1; - } - boolean boundary; - boolean end_point = (xyindex == path_start) - || (xyindex == path_last); - if (m_bOGCRestrictions) - boundary = !is_closed_path && vi_prev.end_point; - else - // for regular planar simplify, only the end points are allowed - // to coincide - boundary = vi_prev.end_point; - - vi.x = pt.x; - vi.y = pt.y; - vi.ipath = ipath; - vi.ivertex = xyindex; - vi.boundary = boundary; - vi.end_point = end_point; - - if (vi.x == vi_prev.x && vi.y == vi_prev.y) { - if (m_bOGCRestrictions) { - if (!vi.boundary || !vi_prev.boundary) { - if ((vi.ipath != vi_prev.ipath) - || (!vi.end_point && !vi_prev.end_point))// check - // that - // this - // is - // not - // the - // endpoints - // of - // a - // closed - // path - { - // one of coincident vertices is not on the boundary - // this is either Non_simple_result::cross_over or - // Non_simple_result::ogc_self_tangency. - // too expensive to distinguish between the two. - m_nonSimpleResult = new NonSimpleResult( - NonSimpleResult.Reason.OGCPolylineSelfTangency, - vi.ivertex, vi_prev.ivertex); - return false;// common point not on the boundary - } - } - } else { - if (!vi.end_point || !vi_prev.end_point) {// one of - // coincident - // vertices is - // not an - // endpoint - m_nonSimpleResult = new NonSimpleResult( - NonSimpleResult.Reason.CrossOver, vi.ivertex, - vi_prev.ivertex); - return false;// common point not on the boundary - } - } - } - - Vertex_info_pl tmp = vi_prev; - vi_prev = vi; - vi = tmp; - } - - return true; - } - - final static class Vertex_info_pg { - double x; - double y; - int ipath; - int ivertex; - int ipolygon; - - Vertex_info_pg(double x_, double y_, int ipath_, int xyindex_, - int polygon_) { - x = x_; - y = y_; - ipath = ipath_; - ivertex = xyindex_; - ipolygon = polygon_; - } - - boolean is_equal(Vertex_info_pg other) { - return x == other.x && y == other.y && ipath == other.ipath - && ivertex == other.ivertex && ipolygon == other.ipolygon; - } - } - - ; - - boolean check_self_intersections_polygons_OGC_() { - MultiPathImpl multiPathImpl = (MultiPathImpl) (m_geometry._getImpl()); - // OGC MultiPolygon is simple when each Polygon is simple and Polygons a - // allowed only touch at finite number of vertices. - // OGC Polygon is simple if it consist of simple LinearRings. - // LinearRings cannot cross. - // Any two LinearRings of a OGC Polygon are allowed to touch at single - // vertex only. - // The OGC Polygon interior has to be a connected set. - - // At this point we assume that the ring order has to be correct (holes - // follow corresponding exterior ring). - // No Rings cross. Exterior rings can only touch at finite number of - // vertices. - - // Fill a mapping of ring to - int[] ring_to_polygon = new int[multiPathImpl.getPathCount()]; - int exteriors = -1; - boolean has_holes = false; - for (int ipath = 0, n = multiPathImpl.getPathCount(); ipath < n; ipath++) { - if (multiPathImpl.isExteriorRing(ipath)) { - has_holes = false; - exteriors++; - if (ipath < n - 1) { - if (!multiPathImpl.isExteriorRing(ipath + 1)) - has_holes = true; - } - } - - // For OGC polygons with no holes, store -1. - // For polygons with holes, store polygon index for each ring. - ring_to_polygon[ipath] = has_holes ? exteriors : -1; - } - - // Use already sorted m_pairIndices - Vertex_info_pg vi_prev = null; - Point2D pt = new Point2D(); - {// scope - int pairIndex = m_pairIndices.get(0); - int pair = m_pairs.get(pairIndex); - int xyindex = pair >> 1; - m_xy.read(2 * xyindex, pt); - int ipath = m_paths_for_OGC_tests.get(xyindex); - vi_prev = new Vertex_info_pg(pt.x, pt.y, ipath, xyindex, - ring_to_polygon[ipath]); - } - - ArrayList intersections = new ArrayList( - multiPathImpl.getPathCount() * 2); - for (int index = 1, n = m_pairIndices.size(); index < n; index++) { - int pairIndex = m_pairIndices.get(index); - int pair = m_pairs.get(pairIndex); - if ((pair & 1) != 0) - continue; - int xyindex = pair >> 1; - m_xy.read(2 * xyindex, pt); - int ipath = m_paths_for_OGC_tests.get(xyindex); - Vertex_info_pg vi = new Vertex_info_pg(pt.x, pt.y, ipath, xyindex, - ring_to_polygon[ipath]); - - if (vi.x == vi_prev.x && vi.y == vi_prev.y) { - if (vi.ipath == vi_prev.ipath) {// the ring has self tangency - m_nonSimpleResult = new NonSimpleResult( - NonSimpleResult.Reason.OGCPolygonSelfTangency, - vi.ivertex, vi_prev.ivertex); - return false; - } else if (ring_to_polygon[vi.ipath] >= 0 - && ring_to_polygon[vi.ipath] == ring_to_polygon[vi_prev.ipath]) {// only - // add - // rings - // from - // polygons - // with - // holes. - // Only - // interested - // in - // touching - // rings - // that - // belong - // to - // the - // same - // polygon - if (intersections.size() == 0 - || intersections.get(intersections.size() - 1) != vi_prev) - intersections.add(vi_prev); - intersections.add(vi); - } - } - - vi_prev = vi; - } - - if (intersections.size() == 0) - return true; - - // Find disconnected interior cases (OGC spec: Interior of polygon has - // to be a closed set) - - // Note: Now we'll reuse ring_to_polygon for different purpose - to - // store mapping from the rings to the graph nodes. - - IndexMultiDCList graph = new IndexMultiDCList(true); - Arrays.fill(ring_to_polygon, -1); - int vnode_index = -1; - Point2D prev = new Point2D(); - prev.setNaN(); - for (int i = 0, n = intersections.size(); i < n; i++) { - Vertex_info_pg cur = intersections.get(i); - if (cur.x != prev.x || cur.y != prev.y) { - vnode_index = graph.createList(0); - prev.x = cur.x; - prev.y = cur.y; - } - - int rnode_index = ring_to_polygon[cur.ipath]; - if (rnode_index == -1) { - rnode_index = graph.createList(2); - ring_to_polygon[cur.ipath] = rnode_index; - } - graph.addElement(rnode_index, vnode_index); // add to rnode - // adjacency list the - // current vnode - graph.addElement(vnode_index, rnode_index); // add to vnode - // adjacency list the - // rnode - } - - AttributeStreamOfInt32 depth_first_stack = new AttributeStreamOfInt32(0); - depth_first_stack.reserve(10); - - for (int node = graph.getFirstList(); node != -1; node = graph - .getNextList(node)) { - int ncolor = graph.getListData(node); - if ((ncolor & 1) != 0 || (ncolor & 2) == 0) - continue;// already visited or this is a vnode (we do not want - // to start from vnode). - - int bad_rnode = -1; - depth_first_stack.add(node); - depth_first_stack.add(-1);// parent - while (depth_first_stack.size() > 0) { - int cur_node_parent = depth_first_stack.getLast(); - depth_first_stack.removeLast(); - int cur_node = depth_first_stack.getLast(); - depth_first_stack.removeLast(); - int color = graph.getListData(cur_node); - if ((color & 1) != 0) { - // already visited this node. This means we found a loop. - if ((color & 2) == 0) {// closing on vnode - bad_rnode = cur_node_parent; - } else - bad_rnode = cur_node; - - // assert(bad_rnode != -1); - break; - } - - graph.setListData(cur_node, color | 1); - for (int adjacent_node = graph.getFirst(cur_node); adjacent_node != -1; adjacent_node = graph - .getNext(adjacent_node)) { - int adjacent_node_data = graph.getData(adjacent_node); - if (adjacent_node_data == cur_node_parent) - continue;// avoid going back to where we just came from - depth_first_stack.add(adjacent_node_data); - depth_first_stack.add(cur_node);// push cur_node as parent - // of adjacent_node - } - } - - if (bad_rnode != -1) { - int bad_ring_index = -1; - for (int i = 0, n = ring_to_polygon.length; i < n; i++) - if (ring_to_polygon[i] == bad_rnode) { - bad_ring_index = i; - break; - } - - // bad_ring_index is any ring in a problematic chain of touching - // rings. - // When chain of touching rings form a loop, the result is a - // disconnected interior, - // which is non-simple for OGC spec. - m_nonSimpleResult = new NonSimpleResult( - NonSimpleResult.Reason.OGCDisconnectedInterior, - bad_ring_index, -1); - return false; - } - } - - return true; - } - - private int checkValidRingOrientation_() { - MultiPathImpl multiPathImpl = (MultiPathImpl) m_geometry._getImpl(); - double totalArea = multiPathImpl.calculateArea2D(); - if (totalArea <= 0) { - m_nonSimpleResult = new NonSimpleResult( - NonSimpleResult.Reason.RingOrientation, - multiPathImpl.getPathCount() == 1 ? 1 : -1, -1); - return 0; - } - - if (multiPathImpl.getPathCount() == 1) {// optimization for a single - // polygon - if (m_bOGCRestrictions) { - if (!check_self_intersections_polygons_OGC_()) - return 0; - } - - return 2; - } - - // 1.Go through all vertices in the sorted order. - // 2.For each vertex,insert any non-horizontal segment that has the - // vertex as low point(there can be max two segments) - m_pathOrientations = new AttributeStreamOfInt32( - multiPathImpl.getPathCount(), 0); - - m_pathParentage = new AttributeStreamOfInt32( - multiPathImpl.getPathCount(), -1); - - int parent_ring = -1; - double exteriorArea = 0; - for (int ipath = 0, n = multiPathImpl.getPathCount(); ipath < n; ipath++) { - double area = multiPathImpl.calculateRingArea2D(ipath); - m_pathOrientations.write(ipath, area < 0 ? 0 : 256); // 8th bit - // is - // existing - // orientation - if (area > 0) { - parent_ring = ipath; - exteriorArea = area; - } else if (area == 0) { - m_nonSimpleResult = new NonSimpleResult( - NonSimpleResult.Reason.RingOrientation, ipath, -1); - return 0; - } else { - // area < 0: this is a hole. - // We write the parent exterior - // ring for it (assumed to be first previous exterior ring) - if (parent_ring < 0 || exteriorArea < Math.abs(area)) { - // The first ring is a hole - this is a wrong ring ordering. - // Or the hole's area is bigger than the previous exterior - // area - this means ring order is broken, - // because holes are always smaller. This is not the only - // condition when ring order is broken though. - m_nonSimpleResult = new NonSimpleResult( - NonSimpleResult.Reason.RingOrder, ipath, -1); - if (m_bOGCRestrictions) - return 0; - } - m_pathParentage.write(ipath, parent_ring); - } - } - - m_unknownOrientationPathCount = multiPathImpl.getPathCount(); - m_newEdges = new AttributeStreamOfInt32(0); - m_newEdges.reserve(10); - - int pointCount = multiPathImpl.getPointCount(); - m_yScanline = NumberUtils.TheNaN; - AttributeStreamOfInt32 bunch = new AttributeStreamOfInt32(0); // stores - // coincident - // vertices - bunch.reserve(10); - // Each vertex has two edges attached.These two arrays map vertices to - // edges as nodes in the m_AET - m_xyToNode1 = new AttributeStreamOfInt32(pointCount, Treap.nullNode()); - m_xyToNode2 = new AttributeStreamOfInt32(pointCount, Treap.nullNode()); - if (m_FreeEdges != null) - m_FreeEdges.clear(false); - else - m_FreeEdges = new AttributeStreamOfInt32(0); - m_FreeEdges.reserve(10); - - m_AET.clear(); - m_AET.setComparator(new RingOrientationTestComparator(this)); - for (int index = 0, n = pointCount * 2; m_unknownOrientationPathCount > 0 - && index < n; index++) { - int pairIndex = m_pairIndices.get(index); - int pair = m_pairs.get(pairIndex); - if ((pair & 1) != 0) - continue;// m_pairs array is redundant.See checkClustering_. - - int xyindex = pair >> 1; - double y = m_xy.read(2 * xyindex + 1); - - if (y != m_yScanline && bunch.size() != 0) { - if (!processBunchForRingOrientationTest_(bunch)) { - // m_nonSimpleResult is set in the - // processBunchForRingOrientationTest_ - return 0; - } - if (bunch != null) - bunch.clear(false); - } - - bunch.add(xyindex);// all vertices that have same y are added to the - // bunch - m_yScanline = y; - } - - if (m_unknownOrientationPathCount > 0 - && !processBunchForRingOrientationTest_(bunch)) { - // m_nonSimpleResult is set in the - // processBunchForRingOrientationTest_ - return 0; - } - - if (m_bOGCRestrictions) { - if (m_nonSimpleResult.m_reason != NonSimpleResult.Reason.NotDetermined) - return 0;// cannot proceed with OGC verification if the ring - // order is broken (cannot decide polygons then). - - if (!check_self_intersections_polygons_OGC_()) - return 0; - - return 2;// everything is good - } else { - if (m_nonSimpleResult.m_reason == NonSimpleResult.Reason.NotDetermined) - return 2;// everything is good - - // weak simple - return 1; - } - } - - private boolean processBunchForSelfIntersectionTest_( - AttributeStreamOfInt32 bunch) { - assert (bunch.size() > 0); - if (bunch.size() == 1) - return true; - - assert (m_edges.size() == 0); - - // Bunch contains vertices that have exactly same x and y. - // We populate m_edges array with the edges that originate in the - // vertices of the bunch. - for (int i = 0, n = bunch.size(); i < n; i++) { - int xyindex = bunch.get(i); - m_recycledSegIter.resetToVertex(xyindex);// the iterator is - // circular. + Segment seg2 = segIter2.nextSegment(); + int res = seg1._isIntersecting(seg2, + m_toleranceIsSimple, true); + if (res != 0) { + NonSimpleResult.Reason reason = res == 2 ? NonSimpleResult.Reason.CrossOver + : NonSimpleResult.Reason.Cracking; + m_nonSimpleResult = new NonSimpleResult(reason, + segIter1.getStartPointIndex(), + segIter2.getStartPointIndex()); + return false; + } + } + } while (segIter2.nextPath()); + } + } + } + return true; + } + + private boolean checkSelfIntersections_() { + MultiPathImpl multiPathImpl = (MultiPathImpl) m_geometry._getImpl(); + m_edges.clear(); + m_edges.ensureCapacity(20);// we reuse the edges while going through a + // polygon. + m_lineEdgesRecycle.clear(); + m_lineEdgesRecycle.ensureCapacity(20);// we reuse the edges while going + // through a polygon. + + m_recycledSegIter = multiPathImpl.querySegmentIterator(); + m_recycledSegIter.setCirculator(true); + + AttributeStreamOfInt32 bunch = new AttributeStreamOfInt32(0);// stores + // coincident + // vertices + bunch.reserve(10); + int pointCount = multiPathImpl.getPointCount(); + double xprev = NumberUtils.TheNaN; + double yprev = 0; + // We already have a sorted list of vertices from clustering check. + for (int index = 0, n = pointCount * 2; index < n; index++) { + int pairIndex = m_pairIndices.get(index); + int pair = m_pairs.get(pairIndex); + if ((pair & 1) != 0) + continue; // m_pairs array is redundant. See checkClustering_. + + int xyindex = pair >> 1; + + double x = m_xy.read(2 * xyindex); + double y = m_xy.read(2 * xyindex + 1); + if (bunch.size() != 0) { + if (x != xprev || y != yprev) { + if (!processBunchForSelfIntersectionTest_(bunch)) + return false; + if (bunch != null) + bunch.clear(false); + } + } + + bunch.add(xyindex); + xprev = x; + yprev = y; + } + + assert (bunch.size() > 0);// cannot be empty + + if (!processBunchForSelfIntersectionTest_(bunch)) + return false; + + return true; + } + + static final class Vertex_info { + double x, y; + int ipath; + int ivertex; + boolean boundary; + } + + ; + + static final class Vertex_info_pl { + double x; + double y; + int ipath; + int ivertex; + boolean boundary; + boolean end_point; + } + + ; + + boolean checkSelfIntersectionsPolylinePlanar_() { + MultiPathImpl multiPathImpl = (MultiPathImpl) m_geometry._getImpl(); + + boolean closedPaths[] = new boolean[multiPathImpl.getPathCount()]; + for (int ipath = 0, npaths = multiPathImpl.getPathCount(); ipath < npaths; ipath++) { + closedPaths[ipath] = multiPathImpl.isClosedPathInXYPlane(ipath); + } + + Vertex_info_pl vi_prev = new Vertex_info_pl(); + boolean is_closed_path; + int path_start; + int path_last; + + Point2D pt = new Point2D(); + + {// scope + int pairIndex = m_pairIndices.get(0); + int pair = m_pairs.get(pairIndex); + int xyindex = pair >> 1; + m_xy.read(2 * xyindex, pt); + int ipath = m_paths_for_OGC_tests.get(xyindex); + is_closed_path = closedPaths[ipath]; + path_start = multiPathImpl.getPathStart(ipath); + path_last = multiPathImpl.getPathEnd(ipath) - 1; + vi_prev.end_point = (xyindex == path_start) + || (xyindex == path_last); + if (m_bOGCRestrictions) + vi_prev.boundary = !is_closed_path && vi_prev.end_point; + else + // for regular planar simplify, only the end points are allowed + // to coincide + vi_prev.boundary = vi_prev.end_point; + vi_prev.ipath = ipath; + vi_prev.x = pt.x; + vi_prev.y = pt.y; + vi_prev.ivertex = xyindex; + } + + Vertex_info_pl vi = new Vertex_info_pl(); + + for (int index = 1, n = m_pairIndices.size(); index < n; index++) { + int pairIndex = m_pairIndices.get(index); + int pair = m_pairs.get(pairIndex); + if ((pair & 1) != 0) + continue; + + int xyindex = pair >> 1; + m_xy.read(2 * xyindex, pt); + int ipath = m_paths_for_OGC_tests.get(xyindex); + if (ipath != vi_prev.ipath) { + is_closed_path = closedPaths[ipath]; + path_start = multiPathImpl.getPathStart(ipath); + path_last = multiPathImpl.getPathEnd(ipath) - 1; + } + boolean boundary; + boolean end_point = (xyindex == path_start) + || (xyindex == path_last); + if (m_bOGCRestrictions) + boundary = !is_closed_path && vi_prev.end_point; + else + // for regular planar simplify, only the end points are allowed + // to coincide + boundary = vi_prev.end_point; + + vi.x = pt.x; + vi.y = pt.y; + vi.ipath = ipath; + vi.ivertex = xyindex; + vi.boundary = boundary; + vi.end_point = end_point; + + if (vi.x == vi_prev.x && vi.y == vi_prev.y) { + if (m_bOGCRestrictions) { + if (!vi.boundary || !vi_prev.boundary) { + if ((vi.ipath != vi_prev.ipath) + || (!vi.end_point && !vi_prev.end_point))// check + // that + // this + // is + // not + // the + // endpoints + // of + // a + // closed + // path + { + // one of coincident vertices is not on the boundary + // this is either Non_simple_result::cross_over or + // Non_simple_result::ogc_self_tangency. + // too expensive to distinguish between the two. + m_nonSimpleResult = new NonSimpleResult( + NonSimpleResult.Reason.OGCPolylineSelfTangency, + vi.ivertex, vi_prev.ivertex); + return false;// common point not on the boundary + } + } + } else { + if (!vi.end_point || !vi_prev.end_point) {// one of + // coincident + // vertices is + // not an + // endpoint + m_nonSimpleResult = new NonSimpleResult( + NonSimpleResult.Reason.CrossOver, vi.ivertex, + vi_prev.ivertex); + return false;// common point not on the boundary + } + } + } + + Vertex_info_pl tmp = vi_prev; + vi_prev = vi; + vi = tmp; + } + + return true; + } + + final static class Vertex_info_pg { + double x; + double y; + int ipath; + int ivertex; + int ipolygon; + + Vertex_info_pg(double x_, double y_, int ipath_, int xyindex_, + int polygon_) { + x = x_; + y = y_; + ipath = ipath_; + ivertex = xyindex_; + ipolygon = polygon_; + } + + boolean is_equal(Vertex_info_pg other) { + return x == other.x && y == other.y && ipath == other.ipath + && ivertex == other.ivertex && ipolygon == other.ipolygon; + } + } + + ; + + boolean check_self_intersections_polygons_OGC_() { + MultiPathImpl multiPathImpl = (MultiPathImpl) (m_geometry._getImpl()); + // OGC MultiPolygon is simple when each Polygon is simple and Polygons a + // allowed only touch at finite number of vertices. + // OGC Polygon is simple if it consist of simple LinearRings. + // LinearRings cannot cross. + // Any two LinearRings of a OGC Polygon are allowed to touch at single + // vertex only. + // The OGC Polygon interior has to be a connected set. + + // At this point we assume that the ring order has to be correct (holes + // follow corresponding exterior ring). + // No Rings cross. Exterior rings can only touch at finite number of + // vertices. + + // Fill a mapping of ring to + int[] ring_to_polygon = new int[multiPathImpl.getPathCount()]; + int exteriors = -1; + boolean has_holes = false; + for (int ipath = 0, n = multiPathImpl.getPathCount(); ipath < n; ipath++) { + if (multiPathImpl.isExteriorRing(ipath)) { + has_holes = false; + exteriors++; + if (ipath < n - 1) { + if (!multiPathImpl.isExteriorRing(ipath + 1)) + has_holes = true; + } + } + + // For OGC polygons with no holes, store -1. + // For polygons with holes, store polygon index for each ring. + ring_to_polygon[ipath] = has_holes ? exteriors : -1; + } + + // Use already sorted m_pairIndices + Vertex_info_pg vi_prev = null; + Point2D pt = new Point2D(); + {// scope + int pairIndex = m_pairIndices.get(0); + int pair = m_pairs.get(pairIndex); + int xyindex = pair >> 1; + m_xy.read(2 * xyindex, pt); + int ipath = m_paths_for_OGC_tests.get(xyindex); + vi_prev = new Vertex_info_pg(pt.x, pt.y, ipath, xyindex, + ring_to_polygon[ipath]); + } + + ArrayList intersections = new ArrayList( + multiPathImpl.getPathCount() * 2); + for (int index = 1, n = m_pairIndices.size(); index < n; index++) { + int pairIndex = m_pairIndices.get(index); + int pair = m_pairs.get(pairIndex); + if ((pair & 1) != 0) + continue; + int xyindex = pair >> 1; + m_xy.read(2 * xyindex, pt); + int ipath = m_paths_for_OGC_tests.get(xyindex); + Vertex_info_pg vi = new Vertex_info_pg(pt.x, pt.y, ipath, xyindex, + ring_to_polygon[ipath]); + + if (vi.x == vi_prev.x && vi.y == vi_prev.y) { + if (vi.ipath == vi_prev.ipath) {// the ring has self tangency + m_nonSimpleResult = new NonSimpleResult( + NonSimpleResult.Reason.OGCPolygonSelfTangency, + vi.ivertex, vi_prev.ivertex); + return false; + } else if (ring_to_polygon[vi.ipath] >= 0 + && ring_to_polygon[vi.ipath] == ring_to_polygon[vi_prev.ipath]) {// only + // add + // rings + // from + // polygons + // with + // holes. + // Only + // interested + // in + // touching + // rings + // that + // belong + // to + // the + // same + // polygon + if (intersections.size() == 0 + || intersections.get(intersections.size() - 1) != vi_prev) + intersections.add(vi_prev); + intersections.add(vi); + } + } + + vi_prev = vi; + } + + if (intersections.size() == 0) + return true; + + // Find disconnected interior cases (OGC spec: Interior of polygon has + // to be a closed set) + + // Note: Now we'll reuse ring_to_polygon for different purpose - to + // store mapping from the rings to the graph nodes. + + IndexMultiDCList graph = new IndexMultiDCList(true); + Arrays.fill(ring_to_polygon, -1); + int vnode_index = -1; + Point2D prev = new Point2D(); + prev.setNaN(); + for (int i = 0, n = intersections.size(); i < n; i++) { + Vertex_info_pg cur = intersections.get(i); + if (cur.x != prev.x || cur.y != prev.y) { + vnode_index = graph.createList(0); + prev.x = cur.x; + prev.y = cur.y; + } + + int rnode_index = ring_to_polygon[cur.ipath]; + if (rnode_index == -1) { + rnode_index = graph.createList(2); + ring_to_polygon[cur.ipath] = rnode_index; + } + graph.addElement(rnode_index, vnode_index); // add to rnode + // adjacency list the + // current vnode + graph.addElement(vnode_index, rnode_index); // add to vnode + // adjacency list the + // rnode + } + + AttributeStreamOfInt32 depth_first_stack = new AttributeStreamOfInt32(0); + depth_first_stack.reserve(10); + + for (int node = graph.getFirstList(); node != -1; node = graph + .getNextList(node)) { + int ncolor = graph.getListData(node); + if ((ncolor & 1) != 0 || (ncolor & 2) == 0) + continue;// already visited or this is a vnode (we do not want + // to start from vnode). + + int bad_rnode = -1; + depth_first_stack.add(node); + depth_first_stack.add(-1);// parent + while (depth_first_stack.size() > 0) { + int cur_node_parent = depth_first_stack.getLast(); + depth_first_stack.removeLast(); + int cur_node = depth_first_stack.getLast(); + depth_first_stack.removeLast(); + int color = graph.getListData(cur_node); + if ((color & 1) != 0) { + // already visited this node. This means we found a loop. + if ((color & 2) == 0) {// closing on vnode + bad_rnode = cur_node_parent; + } else + bad_rnode = cur_node; + + // assert(bad_rnode != -1); + break; + } + + graph.setListData(cur_node, color | 1); + for (int adjacent_node = graph.getFirst(cur_node); adjacent_node != -1; adjacent_node = graph + .getNext(adjacent_node)) { + int adjacent_node_data = graph.getData(adjacent_node); + if (adjacent_node_data == cur_node_parent) + continue;// avoid going back to where we just came from + depth_first_stack.add(adjacent_node_data); + depth_first_stack.add(cur_node);// push cur_node as parent + // of adjacent_node + } + } + + if (bad_rnode != -1) { + int bad_ring_index = -1; + for (int i = 0, n = ring_to_polygon.length; i < n; i++) + if (ring_to_polygon[i] == bad_rnode) { + bad_ring_index = i; + break; + } + + // bad_ring_index is any ring in a problematic chain of touching + // rings. + // When chain of touching rings form a loop, the result is a + // disconnected interior, + // which is non-simple for OGC spec. + m_nonSimpleResult = new NonSimpleResult( + NonSimpleResult.Reason.OGCDisconnectedInterior, + bad_ring_index, -1); + return false; + } + } + + return true; + } + + private int checkValidRingOrientation_() { + MultiPathImpl multiPathImpl = (MultiPathImpl) m_geometry._getImpl(); + double totalArea = multiPathImpl.calculateArea2D(); + if (totalArea <= 0) { + m_nonSimpleResult = new NonSimpleResult( + NonSimpleResult.Reason.RingOrientation, + multiPathImpl.getPathCount() == 1 ? 1 : -1, -1); + return 0; + } + + if (multiPathImpl.getPathCount() == 1) {// optimization for a single + // polygon + if (m_bOGCRestrictions) { + if (!check_self_intersections_polygons_OGC_()) + return 0; + } + + return 2; + } + + // 1.Go through all vertices in the sorted order. + // 2.For each vertex,insert any non-horizontal segment that has the + // vertex as low point(there can be max two segments) + m_pathOrientations = new AttributeStreamOfInt32( + multiPathImpl.getPathCount(), 0); + + m_pathParentage = new AttributeStreamOfInt32( + multiPathImpl.getPathCount(), -1); + + int parent_ring = -1; + double exteriorArea = 0; + for (int ipath = 0, n = multiPathImpl.getPathCount(); ipath < n; ipath++) { + double area = multiPathImpl.calculateRingArea2D(ipath); + m_pathOrientations.write(ipath, area < 0 ? 0 : 256); // 8th bit + // is + // existing + // orientation + if (area > 0) { + parent_ring = ipath; + exteriorArea = area; + } else if (area == 0) { + m_nonSimpleResult = new NonSimpleResult( + NonSimpleResult.Reason.RingOrientation, ipath, -1); + return 0; + } else { + // area < 0: this is a hole. + // We write the parent exterior + // ring for it (assumed to be first previous exterior ring) + if (parent_ring < 0 || exteriorArea < Math.abs(area)) { + // The first ring is a hole - this is a wrong ring ordering. + // Or the hole's area is bigger than the previous exterior + // area - this means ring order is broken, + // because holes are always smaller. This is not the only + // condition when ring order is broken though. + m_nonSimpleResult = new NonSimpleResult( + NonSimpleResult.Reason.RingOrder, ipath, -1); + if (m_bOGCRestrictions) + return 0; + } + m_pathParentage.write(ipath, parent_ring); + } + } + + m_unknownOrientationPathCount = multiPathImpl.getPathCount(); + m_newEdges = new AttributeStreamOfInt32(0); + m_newEdges.reserve(10); + + int pointCount = multiPathImpl.getPointCount(); + m_yScanline = NumberUtils.TheNaN; + AttributeStreamOfInt32 bunch = new AttributeStreamOfInt32(0); // stores + // coincident + // vertices + bunch.reserve(10); + // Each vertex has two edges attached.These two arrays map vertices to + // edges as nodes in the m_AET + m_xyToNode1 = new AttributeStreamOfInt32(pointCount, Treap.nullNode()); + m_xyToNode2 = new AttributeStreamOfInt32(pointCount, Treap.nullNode()); + if (m_FreeEdges != null) + m_FreeEdges.clear(false); + else + m_FreeEdges = new AttributeStreamOfInt32(0); + m_FreeEdges.reserve(10); + + m_AET.clear(); + m_AET.setComparator(new RingOrientationTestComparator(this)); + for (int index = 0, n = pointCount * 2; m_unknownOrientationPathCount > 0 + && index < n; index++) { + int pairIndex = m_pairIndices.get(index); + int pair = m_pairs.get(pairIndex); + if ((pair & 1) != 0) + continue;// m_pairs array is redundant.See checkClustering_. + + int xyindex = pair >> 1; + double y = m_xy.read(2 * xyindex + 1); + + if (y != m_yScanline && bunch.size() != 0) { + if (!processBunchForRingOrientationTest_(bunch)) { + // m_nonSimpleResult is set in the + // processBunchForRingOrientationTest_ + return 0; + } + if (bunch != null) + bunch.clear(false); + } + + bunch.add(xyindex);// all vertices that have same y are added to the + // bunch + m_yScanline = y; + } + + if (m_unknownOrientationPathCount > 0 + && !processBunchForRingOrientationTest_(bunch)) { + // m_nonSimpleResult is set in the + // processBunchForRingOrientationTest_ + return 0; + } + + if (m_bOGCRestrictions) { + if (m_nonSimpleResult.m_reason != NonSimpleResult.Reason.NotDetermined) + return 0;// cannot proceed with OGC verification if the ring + // order is broken (cannot decide polygons then). + + if (!check_self_intersections_polygons_OGC_()) + return 0; + + return 2;// everything is good + } else { + if (m_nonSimpleResult.m_reason == NonSimpleResult.Reason.NotDetermined) + return 2;// everything is good + + // weak simple + return 1; + } + } + + private boolean processBunchForSelfIntersectionTest_( + AttributeStreamOfInt32 bunch) { + assert (bunch.size() > 0); + if (bunch.size() == 1) + return true; + + assert (m_edges.size() == 0); + + // Bunch contains vertices that have exactly same x and y. + // We populate m_edges array with the edges that originate in the + // vertices of the bunch. + for (int i = 0, n = bunch.size(); i < n; i++) { + int xyindex = bunch.get(i); + m_recycledSegIter.resetToVertex(xyindex);// the iterator is + // circular. /* const */ - Segment seg1 = m_recycledSegIter.previousSegment(); - m_edges.add(createEdge_(seg1, xyindex, - m_recycledSegIter.getPathIndex(), true)); - m_recycledSegIter.nextSegment();// Need to skip one,because of the - // previousSegment call - // before (otherwise will get same segment again) + Segment seg1 = m_recycledSegIter.previousSegment(); + m_edges.add(createEdge_(seg1, xyindex, + m_recycledSegIter.getPathIndex(), true)); + m_recycledSegIter.nextSegment();// Need to skip one,because of the + // previousSegment call + // before (otherwise will get same segment again) /* const */ - Segment seg2 = m_recycledSegIter.nextSegment(); - m_edges.add(createEdge_(seg2, xyindex, - m_recycledSegIter.getPathIndex(), false)); - } - - assert ((m_edges.size() & 1) == 0); // even size - - // Analyze the bunch edges for self intersections(the edges touch at the - // end points only at this stage of IsSimple) - // 1.sort the edges by angle between edge and the unit vector along axis - // x,using the cross product sign.Precondition:no overlaps occur at this - // stage. - - Collections.sort(m_edges, new EdgeComparerForSelfIntersection(this)); - - // 2.Analyze the bunch.There can be no edges between edges that share - // same vertex coordinates. - // We populate a doubly linked list with the edge indices and iterate - // over this list getting rid of the neighbouring pairs of vertices. - // The process is similar to peeling an onion. - // If the list becomes empty,there are no crossovers,otherwise,the - // geometry has cross-over. - int list = m_crossOverHelperList.getFirstList(); - - if (list == -1) - list = m_crossOverHelperList.createList(0); - - m_crossOverHelperList.reserveNodes(m_edges.size()); - - for (int i = 0, n = m_edges.size(); i < n; i++) { - m_crossOverHelperList.addElement(list, i); - } - - // Peel the onion - boolean bContinue = true; - int i1 = -1; - int i2 = -1; - while (bContinue) { - bContinue = false; - int listnode = m_crossOverHelperList.getFirst(list); - if (listnode == -1) - break; - - int nextnode = m_crossOverHelperList.getNext(listnode); - while (nextnode != -1) { - int edgeindex1 = m_crossOverHelperList.getData(listnode); - int edgeindex2 = m_crossOverHelperList.getData(nextnode); - i1 = m_edges.get(edgeindex1).m_vertexIndex; - i2 = m_edges.get(edgeindex2).m_vertexIndex; - if (i1 == i2) { - bContinue = true; - m_crossOverHelperList.deleteElement(list, listnode); - listnode = m_crossOverHelperList.getPrev(nextnode); - nextnode = m_crossOverHelperList.deleteElement(list, - nextnode); - if (nextnode == -1 || listnode == -1) - break; - else - continue; - } - listnode = nextnode; - nextnode = m_crossOverHelperList.getNext(listnode); - } - } - - int listSize = m_crossOverHelperList.getListSize(list); - m_crossOverHelperList.clear(list); - if (listSize > 0) { - // There is self-intersection here. - m_nonSimpleResult = new NonSimpleResult( - NonSimpleResult.Reason.CrossOver, i1, i2); - return false; - } - - // Recycle the bunch to save on object creation - for (int i = 0, n = bunch.size(); i < n; i++) { - recycleEdge_(m_edges.get(i)); - } - - m_edges.clear(); - return true; - } - - private boolean processBunchForRingOrientationTest_( - AttributeStreamOfInt32 bunch) { - m_dbgCounter++; - assert (bunch.size() > 0); - - // remove nodes that go out of scope - for (int i = 0, n = bunch.size(); i < n; i++) { - int xyindex = bunch.get(i); - int aetNode = m_xyToNode1.read(xyindex); - if (aetNode != Treap.nullNode()) {// We found that there is an edge - // in AET, attached to the - // xyindex vertex. This edge - // goes out of scope. Delete it - // from AET. - int edgeIndex = m_AET.getElement(aetNode); - m_FreeEdges.add(edgeIndex); - m_AET.deleteNode(aetNode, -1); - recycleEdge_(m_edges.get(edgeIndex)); - m_edges.set(edgeIndex, null); - m_xyToNode1.write(xyindex, Treap.nullNode()); - } - - aetNode = m_xyToNode2.read(xyindex); - if (aetNode != Treap.nullNode()) {// We found that there is an edge - // in AET, attached to the - // xyindex vertex. This edge - // goes out of scope. Delete it - // from AET. - int edgeIndex = m_AET.getElement(aetNode); - m_FreeEdges.add(edgeIndex); - m_AET.deleteNode(aetNode, -1); - recycleEdge_(m_edges.get(edgeIndex)); - m_edges.set(edgeIndex, null); - m_xyToNode2.write(xyindex, Treap.nullNode()); - } - } - - // add new edges to AET - for (int i = 0, n = bunch.size(); i < n; i++) { - int xyindex = bunch.get(i); - m_recycledSegIter.resetToVertex(xyindex);// the iterator is - // circular. - Segment seg1 = m_recycledSegIter.previousSegment();// this - // segment - // has - // end - // point - // at - // xyindex - if (seg1.getStartY() > seg1.getEndY())// do not allow horizontal - // segments in here - { - // get the top vertex index.We use it to determine what segments - // to get rid of. - int edgeTopIndex = m_recycledSegIter.getStartPointIndex(); - Edge edge = createEdge_(seg1, xyindex, - m_recycledSegIter.getPathIndex(), true); - int edgeIndex; - if (m_FreeEdges.size() > 0) { - edgeIndex = m_FreeEdges.getLast(); - m_FreeEdges.removeLast(); - m_edges.set(edgeIndex, edge); - } else { - edgeIndex = m_edges.size(); - m_edges.add(edge); - } - - int aetNode = m_AET.addElement(edgeIndex, -1); - // Remember AET nodes in the vertex to AET node maps. - if (m_xyToNode1.read(edgeTopIndex) == Treap.nullNode()) - m_xyToNode1.write(edgeTopIndex, aetNode); - else { - assert (m_xyToNode2.read(edgeTopIndex) == Treap.nullNode()); - m_xyToNode2.write(edgeTopIndex, aetNode); - } - - // If this edge belongs to a path that has not have direction - // figured out yet, - // add it to m_newEdges for post processing - if ((m_pathOrientations.read(m_recycledSegIter.getPathIndex()) & 3) == 0) - m_newEdges.add(aetNode); - } - - m_recycledSegIter.nextSegment();// Need to skip one,because of the - // previousSegment call - // before(otherwise will get same - // segment again) - // seg1 is invalid now - Segment seg2 = m_recycledSegIter.nextSegment(); - // start has to be lower than end for this one - if (seg2.getStartY() < seg2.getEndY())// do not allow horizontal - // segments in here - { - // get the top vertex index.We use it to determine what segments - // to get rid of. - int edgeTopIndex = m_recycledSegIter.getEndPointIndex(); - Edge edge = createEdge_(seg2, xyindex, - m_recycledSegIter.getPathIndex(), false); - int edgeIndex; - if (m_FreeEdges.size() > 0) { - edgeIndex = m_FreeEdges.getLast(); - m_FreeEdges.removeLast(); - m_edges.set(edgeIndex, edge); - } else { - edgeIndex = m_edges.size(); - m_edges.add(edge); - } - - int aetNode = m_AET.addElement(edgeIndex, -1); - if (m_xyToNode1.read(edgeTopIndex) == Treap.nullNode()) - m_xyToNode1.write(edgeTopIndex, aetNode); - else { - assert (m_xyToNode2.read(edgeTopIndex) == Treap.nullNode()); - m_xyToNode2.write(edgeTopIndex, aetNode); - } - - // If this edge belongs to a path that has not have direction - // figured out yet, - // add it to m_newEdges for post processing - if ((m_pathOrientations.read(m_recycledSegIter.getPathIndex()) & 3) == 0) - m_newEdges.add(aetNode); - } - } - - for (int i = 0, n = m_newEdges.size(); i < n - && m_unknownOrientationPathCount > 0; i++) { - int aetNode = m_newEdges.get(i); - int edgeIndexInitial = m_AET.getElement(aetNode); - Edge edgeInitial = m_edges.get(edgeIndexInitial); - int pathIndexInitial = edgeInitial.m_pathIndex; - int directionInitial = m_pathOrientations.read(pathIndexInitial); - if ((directionInitial & 3) == 0) { - int prevExteriorPath = -1; - int node = m_AET.getPrev(aetNode); - int prevNode = aetNode; - int oddEven = 0; - {// scope - int edgeIndex = -1; - Edge edge = null; - int pathIndex = -1; - int dir = 0; - // find the leftmost edge for which the ring orientation is - // known - while (node != Treap.nullNode()) { - edgeIndex = m_AET.getElement(node); - edge = m_edges.get(edgeIndex); - pathIndex = edge.m_pathIndex; - dir = m_pathOrientations.read(pathIndex); - if ((dir & 3) != 0) - break; - - prevNode = node; - node = m_AET.getPrev(node); - } - - if (node == Treap.nullNode()) {// if no edges have ring - // orientation known, then - // start - // from the left most and it - // has - // to be exterior ring. - oddEven = 1; - node = prevNode; - } else { - if ((dir & 3) == 1) { - prevExteriorPath = pathIndex; - } else { - prevExteriorPath = m_pathParentage.read(pathIndex); - } - - oddEven = (edge.getRightSide() != 0) ? 0 : 1; - node = m_AET.getNext(node); - } - } - - do { - int edgeIndex = m_AET.getElement(node); - Edge edge = m_edges.get(edgeIndex); - int pathIndex = edge.m_pathIndex; - int direction = m_pathOrientations.read(pathIndex); - if ((direction & 3) == 0) { - if (oddEven != edge.getRightSide()) { - m_nonSimpleResult = new NonSimpleResult( - NonSimpleResult.Reason.RingOrientation, - pathIndex, -1); - return false;// wrong ring orientation - } - - int dir = (oddEven != 0 && !edge.getReversed()) ? 1 : 2; - direction = (direction & 0xfc) | dir; - m_pathOrientations.write(pathIndex, dir); - if (dir == 2 - && m_nonSimpleResult.m_reason == NonSimpleResult.Reason.NotDetermined) { - // check that this hole has a correct parent - // exterior ring. - int parent = m_pathParentage.read(pathIndex); - if (parent != prevExteriorPath) { - m_nonSimpleResult = new NonSimpleResult( - NonSimpleResult.Reason.RingOrder, - pathIndex, -1); - if (m_bOGCRestrictions) - return false; - } - } - - m_unknownOrientationPathCount--; - if (m_unknownOrientationPathCount == 0)// if(!m_unknownOrientationPathCount) - return true; - } - - if ((direction & 3) == 1) { - prevExteriorPath = pathIndex; - } - - prevNode = node; - node = m_AET.getNext(node); - oddEven = oddEven != 0 ? 0 : 1; - } while (prevNode != aetNode); - } - } - - if (m_newEdges != null) - m_newEdges.clear(false); - else - m_newEdges = new AttributeStreamOfInt32(0); - return true; - } - - private Edge createEdge_(/* const */Segment seg, int xyindex, int pathIndex, - boolean bReversed) { - Edge edge; - Geometry.Type gt = seg.getType(); - if (gt == Geometry.Type.Line) { - edge = createEdgeLine_(seg); - } else { - throw GeometryException.GeometryInternalError(); // implement - // recycling for - // curves - } - edge.m_vertexIndex = xyindex; - edge.m_pathIndex = pathIndex; - edge.m_flags = 0; - edge.setReversed(bReversed); - - return edge; - } - - private Edge createEdgeLine_(/* const */Segment seg) { - Edge edge = null; - if (m_lineEdgesRecycle.size() > 0) { - int indexLast = m_lineEdgesRecycle.size() - 1; - edge = m_lineEdgesRecycle.get(indexLast); - m_lineEdgesRecycle.remove(indexLast); - seg.copyTo(edge.m_segment); - } else { - edge = new Edge(); - edge.m_segment = (Segment) Segment._clone(seg); - } - - return edge; - } - - private void recycleEdge_(/* const */Edge edge) { - Geometry.Type gt = edge.m_segment.getType(); - if (gt == Geometry.Type.Line) { - m_lineEdgesRecycle.add(edge); - } - } - - private static final class ClusterTestComparator extends Treap.Comparator { - OperatorSimplifyLocalHelper m_helper; - - ClusterTestComparator(OperatorSimplifyLocalHelper helper) { - m_helper = helper; - } - - @Override - int compare(/* const */Treap treap, int xy1, int node) { - int xy2 = treap.getElement(node); - double x1 = m_helper.m_xy.read(2 * xy1); - double x2 = m_helper.m_xy.read(2 * xy2); - double dx = x1 - x2; - return dx < 0 ? -1 : (dx > 0 ? 1 : 0); - } - } - - private static final class RingOrientationTestComparator extends - Treap.Comparator { - private OperatorSimplifyLocalHelper m_helper; - - RingOrientationTestComparator(OperatorSimplifyLocalHelper helper) { - m_helper = helper; - } - - @Override - int compare(/* const */Treap treap, int left, int node) { - int right = treap.getElement(node); - Edge edge1 = m_helper.m_edges.get(left); - Edge edge2 = m_helper.m_edges.get(right); - boolean bEdge1Reversed = edge1.getReversed(); - boolean bEdge2Reversed = edge2.getReversed(); - - double x1 = edge1.m_segment.intersectionOfYMonotonicWithAxisX( - m_helper.m_yScanline, 0); - double x2 = edge2.m_segment.intersectionOfYMonotonicWithAxisX( - m_helper.m_yScanline, 0); - - if (x1 == x2) { - // apparently these edges originate from same vertex and the - // scanline is on the vertex.move scanline a little. - double y1 = bEdge1Reversed ? edge1.m_segment.getStartY() - : edge1.m_segment.getEndY(); - double y2 = bEdge2Reversed ? edge2.m_segment.getStartY() - : edge2.m_segment.getEndY(); - double miny = Math.min(y1, y2); - double y = (miny - m_helper.m_yScanline) * 0.5 - + m_helper.m_yScanline; - if (y == m_helper.m_yScanline) { - // assert(0); //ST: not a bug. just curious to see this - // happens. - y = miny; // apparently, one of the segments is almost - // horizontal line. - } - x1 = edge1.m_segment.intersectionOfYMonotonicWithAxisX(y, 0); - x2 = edge2.m_segment.intersectionOfYMonotonicWithAxisX(y, 0); - assert (x1 != x2); - } - - return x1 < x2 ? -1 : (x1 > x2 ? 1 : 0); - } - } - - int multiPointIsSimpleAsFeature_() { - MultiVertexGeometryImpl multiVertexImpl = (MultiVertexGeometryImpl) m_geometry - ._getImpl(); - // sort lexicographically: by y,then by x, then by other attributes in - // the order. - // Go through the sorted list and make sure no points coincide exactly - // (no tolerance is taken into account). - int pointCount = multiVertexImpl.getPointCount(); - - AttributeStreamOfInt32 indices = new AttributeStreamOfInt32(0); - - for (int i = 0; i < pointCount; i++) { - indices.add(i); - } - - indices.Sort(0, pointCount, new MultiPointVertexComparer(this)); - - for (int i = 1; i < pointCount; i++) { - if (compareVerticesMultiPoint_(indices.get(i - 1), indices.get(i)) == 0) { - m_nonSimpleResult = new NonSimpleResult( - NonSimpleResult.Reason.Clustering, indices.get(i - 1), - indices.get(i)); - return 0;// points are coincident-simplify. - } - } - - return 2; - } - - int polylineIsSimpleAsFeature_() { - if (!checkStructure_()) - return 0; - // Non planar IsSimple. - // Go through all line segments and make sure no line segments are - // degenerate. - // Degenerate segment is the one which has its length shorter than - // tolerance or Z projection shorter than z tolerance. - return checkDegenerateSegments_(true) ? 2 : 0; - } - - int polygonIsSimpleAsFeature_() { - return isSimplePlanarImpl_(); - } - - MultiPoint multiPointSimplifyAsFeature_() { - MultiVertexGeometryImpl multiVertexImpl = (MultiVertexGeometryImpl) m_geometry - ._getImpl(); - // sort lexicographically:by y,then by x,then by other attributes in the - // order. - int pointCount = multiVertexImpl.getPointCount(); - assert (pointCount > 0); - - AttributeStreamOfInt32 indices = new AttributeStreamOfInt32(0); - - for (int i = 0; i < pointCount; i++) { - indices.add(i); - } - - indices.Sort(0, pointCount, new MultiPointVertexComparer2(this)); - - // Mark vertices that are unique - boolean[] indicesOut = new boolean[pointCount]; - - indicesOut[indices.get(0)] = true; - - for (int i = 1; i < pointCount; i++) { - int ind1 = indices.get(i - 1); - int ind2 = indices.get(i); - if (compareVerticesMultiPoint_(ind1, ind2) == 0) { - indicesOut[ind2] = false; - continue; - } - - indicesOut[ind2] = true; - } - - // get rid of non-unique vertices. - // We preserve the order of MultiPoint vertices.Among duplicate - // vertices,those that have - // higher index are deleted. - MultiPoint dst = (MultiPoint) m_geometry.createInstance(); - MultiPoint src = (MultiPoint) m_geometry; - int istart = 0; - int iend = 1; - for (int i = 0; i < pointCount; i++) { - if (indicesOut[i]) - iend = i + 1; - else { - if (istart < iend) { - dst.add(src, istart, iend); - } - - istart = i + 1; - } - } - - if (istart < iend) { - dst.add(src, istart, iend); - } - - ((MultiVertexGeometryImpl) dst._getImpl()).setIsSimple( - GeometryXSimple.Strong, m_toleranceSimplify, false); - return dst; - } - - Polyline polylineSimplifyAsFeature_() { - // Non planar simplify. - // Go through all line segments and make sure no line segments are - // degenerate. - // Degenerate segment is the one which has its length shorter than - // tolerance or Z projection shorter than z tolerance. - // The algorithm processes each path symmetrically from each end to - // ensure the result of simplify does not depend on the direction of the - // path. - - MultiPathImpl multiPathImpl = (MultiPathImpl) m_geometry._getImpl(); - SegmentIteratorImpl segIterFwd = multiPathImpl.querySegmentIterator(); - SegmentIteratorImpl segIterBwd = multiPathImpl.querySegmentIterator(); - Polyline dst = (Polyline) m_geometry.createInstance(); - Polyline src = (Polyline) m_geometry; - // Envelope2D env2D; - boolean bHasZ = multiPathImpl - .hasAttribute(VertexDescription.Semantics.Z); - double ztolerance = !bHasZ ? 0.0 : InternalUtils - .calculateZToleranceFromGeometry(m_sr, multiPathImpl, true); - AttributeStreamOfInt32 fwdStack = new AttributeStreamOfInt32(0); - AttributeStreamOfInt32 bwdStack = new AttributeStreamOfInt32(0); - fwdStack.reserve(multiPathImpl.getPointCount() / 2 + 1); - bwdStack.reserve(multiPathImpl.getPointCount() / 2 + 1); - while (segIterFwd.nextPath()) { - segIterBwd.nextPath(); - if (multiPathImpl.getPathSize(segIterFwd.getPathIndex()) < 2) - continue; - - segIterBwd.resetToLastSegment(); - double lengthFwd = 0; - double lengthBwd = 0; - boolean bFirst = true; - while (segIterFwd.hasNextSegment()) { - assert (segIterBwd.hasPreviousSegment()); + Segment seg2 = m_recycledSegIter.nextSegment(); + m_edges.add(createEdge_(seg2, xyindex, + m_recycledSegIter.getPathIndex(), false)); + } + + assert ((m_edges.size() & 1) == 0); // even size + + // Analyze the bunch edges for self intersections(the edges touch at the + // end points only at this stage of IsSimple) + // 1.sort the edges by angle between edge and the unit vector along axis + // x,using the cross product sign.Precondition:no overlaps occur at this + // stage. + + Collections.sort(m_edges, new EdgeComparerForSelfIntersection(this)); + + // 2.Analyze the bunch.There can be no edges between edges that share + // same vertex coordinates. + // We populate a doubly linked list with the edge indices and iterate + // over this list getting rid of the neighbouring pairs of vertices. + // The process is similar to peeling an onion. + // If the list becomes empty,there are no crossovers,otherwise,the + // geometry has cross-over. + int list = m_crossOverHelperList.getFirstList(); + + if (list == -1) + list = m_crossOverHelperList.createList(0); + + m_crossOverHelperList.reserveNodes(m_edges.size()); + + for (int i = 0, n = m_edges.size(); i < n; i++) { + m_crossOverHelperList.addElement(list, i); + } + + // Peel the onion + boolean bContinue = true; + int i1 = -1; + int i2 = -1; + while (bContinue) { + bContinue = false; + int listnode = m_crossOverHelperList.getFirst(list); + if (listnode == -1) + break; + + int nextnode = m_crossOverHelperList.getNext(listnode); + while (nextnode != -1) { + int edgeindex1 = m_crossOverHelperList.getData(listnode); + int edgeindex2 = m_crossOverHelperList.getData(nextnode); + i1 = m_edges.get(edgeindex1).m_vertexIndex; + i2 = m_edges.get(edgeindex2).m_vertexIndex; + if (i1 == i2) { + bContinue = true; + m_crossOverHelperList.deleteElement(list, listnode); + listnode = m_crossOverHelperList.getPrev(nextnode); + nextnode = m_crossOverHelperList.deleteElement(list, + nextnode); + if (nextnode == -1 || listnode == -1) + break; + else + continue; + } + listnode = nextnode; + nextnode = m_crossOverHelperList.getNext(listnode); + } + } + + int listSize = m_crossOverHelperList.getListSize(list); + m_crossOverHelperList.clear(list); + if (listSize > 0) { + // There is self-intersection here. + m_nonSimpleResult = new NonSimpleResult( + NonSimpleResult.Reason.CrossOver, i1, i2); + return false; + } + + // Recycle the bunch to save on object creation + for (int i = 0, n = bunch.size(); i < n; i++) { + recycleEdge_(m_edges.get(i)); + } + + m_edges.clear(); + return true; + } + + private boolean processBunchForRingOrientationTest_( + AttributeStreamOfInt32 bunch) { + m_dbgCounter++; + assert (bunch.size() > 0); + + // remove nodes that go out of scope + for (int i = 0, n = bunch.size(); i < n; i++) { + int xyindex = bunch.get(i); + int aetNode = m_xyToNode1.read(xyindex); + if (aetNode != Treap.nullNode()) {// We found that there is an edge + // in AET, attached to the + // xyindex vertex. This edge + // goes out of scope. Delete it + // from AET. + int edgeIndex = m_AET.getElement(aetNode); + m_FreeEdges.add(edgeIndex); + m_AET.deleteNode(aetNode, -1); + recycleEdge_(m_edges.get(edgeIndex)); + m_edges.set(edgeIndex, null); + m_xyToNode1.write(xyindex, Treap.nullNode()); + } + + aetNode = m_xyToNode2.read(xyindex); + if (aetNode != Treap.nullNode()) {// We found that there is an edge + // in AET, attached to the + // xyindex vertex. This edge + // goes out of scope. Delete it + // from AET. + int edgeIndex = m_AET.getElement(aetNode); + m_FreeEdges.add(edgeIndex); + m_AET.deleteNode(aetNode, -1); + recycleEdge_(m_edges.get(edgeIndex)); + m_edges.set(edgeIndex, null); + m_xyToNode2.write(xyindex, Treap.nullNode()); + } + } + + // add new edges to AET + for (int i = 0, n = bunch.size(); i < n; i++) { + int xyindex = bunch.get(i); + m_recycledSegIter.resetToVertex(xyindex);// the iterator is + // circular. + Segment seg1 = m_recycledSegIter.previousSegment();// this + // segment + // has + // end + // point + // at + // xyindex + if (seg1.getStartY() > seg1.getEndY())// do not allow horizontal + // segments in here + { + // get the top vertex index.We use it to determine what segments + // to get rid of. + int edgeTopIndex = m_recycledSegIter.getStartPointIndex(); + Edge edge = createEdge_(seg1, xyindex, + m_recycledSegIter.getPathIndex(), true); + int edgeIndex; + if (m_FreeEdges.size() > 0) { + edgeIndex = m_FreeEdges.getLast(); + m_FreeEdges.removeLast(); + m_edges.set(edgeIndex, edge); + } else { + edgeIndex = m_edges.size(); + m_edges.add(edge); + } + + int aetNode = m_AET.addElement(edgeIndex, -1); + // Remember AET nodes in the vertex to AET node maps. + if (m_xyToNode1.read(edgeTopIndex) == Treap.nullNode()) + m_xyToNode1.write(edgeTopIndex, aetNode); + else { + assert (m_xyToNode2.read(edgeTopIndex) == Treap.nullNode()); + m_xyToNode2.write(edgeTopIndex, aetNode); + } + + // If this edge belongs to a path that has not have direction + // figured out yet, + // add it to m_newEdges for post processing + if ((m_pathOrientations.read(m_recycledSegIter.getPathIndex()) & 3) == 0) + m_newEdges.add(aetNode); + } + + m_recycledSegIter.nextSegment();// Need to skip one,because of the + // previousSegment call + // before(otherwise will get same + // segment again) + // seg1 is invalid now + Segment seg2 = m_recycledSegIter.nextSegment(); + // start has to be lower than end for this one + if (seg2.getStartY() < seg2.getEndY())// do not allow horizontal + // segments in here + { + // get the top vertex index.We use it to determine what segments + // to get rid of. + int edgeTopIndex = m_recycledSegIter.getEndPointIndex(); + Edge edge = createEdge_(seg2, xyindex, + m_recycledSegIter.getPathIndex(), false); + int edgeIndex; + if (m_FreeEdges.size() > 0) { + edgeIndex = m_FreeEdges.getLast(); + m_FreeEdges.removeLast(); + m_edges.set(edgeIndex, edge); + } else { + edgeIndex = m_edges.size(); + m_edges.add(edge); + } + + int aetNode = m_AET.addElement(edgeIndex, -1); + if (m_xyToNode1.read(edgeTopIndex) == Treap.nullNode()) + m_xyToNode1.write(edgeTopIndex, aetNode); + else { + assert (m_xyToNode2.read(edgeTopIndex) == Treap.nullNode()); + m_xyToNode2.write(edgeTopIndex, aetNode); + } + + // If this edge belongs to a path that has not have direction + // figured out yet, + // add it to m_newEdges for post processing + if ((m_pathOrientations.read(m_recycledSegIter.getPathIndex()) & 3) == 0) + m_newEdges.add(aetNode); + } + } + + for (int i = 0, n = m_newEdges.size(); i < n + && m_unknownOrientationPathCount > 0; i++) { + int aetNode = m_newEdges.get(i); + int edgeIndexInitial = m_AET.getElement(aetNode); + Edge edgeInitial = m_edges.get(edgeIndexInitial); + int pathIndexInitial = edgeInitial.m_pathIndex; + int directionInitial = m_pathOrientations.read(pathIndexInitial); + if ((directionInitial & 3) == 0) { + int prevExteriorPath = -1; + int node = m_AET.getPrev(aetNode); + int prevNode = aetNode; + int oddEven = 0; + {// scope + int edgeIndex = -1; + Edge edge = null; + int pathIndex = -1; + int dir = 0; + // find the leftmost edge for which the ring orientation is + // known + while (node != Treap.nullNode()) { + edgeIndex = m_AET.getElement(node); + edge = m_edges.get(edgeIndex); + pathIndex = edge.m_pathIndex; + dir = m_pathOrientations.read(pathIndex); + if ((dir & 3) != 0) + break; + + prevNode = node; + node = m_AET.getPrev(node); + } + + if (node == Treap.nullNode()) {// if no edges have ring + // orientation known, then + // start + // from the left most and it + // has + // to be exterior ring. + oddEven = 1; + node = prevNode; + } else { + if ((dir & 3) == 1) { + prevExteriorPath = pathIndex; + } else { + prevExteriorPath = m_pathParentage.read(pathIndex); + } + + oddEven = (edge.getRightSide() != 0) ? 0 : 1; + node = m_AET.getNext(node); + } + } + + do { + int edgeIndex = m_AET.getElement(node); + Edge edge = m_edges.get(edgeIndex); + int pathIndex = edge.m_pathIndex; + int direction = m_pathOrientations.read(pathIndex); + if ((direction & 3) == 0) { + if (oddEven != edge.getRightSide()) { + m_nonSimpleResult = new NonSimpleResult( + NonSimpleResult.Reason.RingOrientation, + pathIndex, -1); + return false;// wrong ring orientation + } + + int dir = (oddEven != 0 && !edge.getReversed()) ? 1 : 2; + direction = (direction & 0xfc) | dir; + m_pathOrientations.write(pathIndex, dir); + if (dir == 2 + && m_nonSimpleResult.m_reason == NonSimpleResult.Reason.NotDetermined) { + // check that this hole has a correct parent + // exterior ring. + int parent = m_pathParentage.read(pathIndex); + if (parent != prevExteriorPath) { + m_nonSimpleResult = new NonSimpleResult( + NonSimpleResult.Reason.RingOrder, + pathIndex, -1); + if (m_bOGCRestrictions) + return false; + } + } + + m_unknownOrientationPathCount--; + if (m_unknownOrientationPathCount == 0)// if(!m_unknownOrientationPathCount) + return true; + } + + if ((direction & 3) == 1) { + prevExteriorPath = pathIndex; + } + + prevNode = node; + node = m_AET.getNext(node); + oddEven = oddEven != 0 ? 0 : 1; + } while (prevNode != aetNode); + } + } + + if (m_newEdges != null) + m_newEdges.clear(false); + else + m_newEdges = new AttributeStreamOfInt32(0); + return true; + } + + private Edge createEdge_(/* const */Segment seg, int xyindex, int pathIndex, + boolean bReversed) { + Edge edge; + Geometry.Type gt = seg.getType(); + if (gt == Geometry.Type.Line) { + edge = createEdgeLine_(seg); + } else { + throw GeometryException.GeometryInternalError(); // implement + // recycling for + // curves + } + edge.m_vertexIndex = xyindex; + edge.m_pathIndex = pathIndex; + edge.m_flags = 0; + edge.setReversed(bReversed); + + return edge; + } + + private Edge createEdgeLine_(/* const */Segment seg) { + Edge edge = null; + if (m_lineEdgesRecycle.size() > 0) { + int indexLast = m_lineEdgesRecycle.size() - 1; + edge = m_lineEdgesRecycle.get(indexLast); + m_lineEdgesRecycle.remove(indexLast); + seg.copyTo(edge.m_segment); + } else { + edge = new Edge(); + edge.m_segment = (Segment) Segment._clone(seg); + } + + return edge; + } + + private void recycleEdge_(/* const */Edge edge) { + Geometry.Type gt = edge.m_segment.getType(); + if (gt == Geometry.Type.Line) { + m_lineEdgesRecycle.add(edge); + } + } + + private static final class ClusterTestComparator extends Treap.Comparator { + OperatorSimplifyLocalHelper m_helper; + + ClusterTestComparator(OperatorSimplifyLocalHelper helper) { + m_helper = helper; + } + + @Override + int compare(/* const */Treap treap, int xy1, int node) { + int xy2 = treap.getElement(node); + double x1 = m_helper.m_xy.read(2 * xy1); + double x2 = m_helper.m_xy.read(2 * xy2); + double dx = x1 - x2; + return dx < 0 ? -1 : (dx > 0 ? 1 : 0); + } + } + + private static final class RingOrientationTestComparator extends + Treap.Comparator { + private OperatorSimplifyLocalHelper m_helper; + + RingOrientationTestComparator(OperatorSimplifyLocalHelper helper) { + m_helper = helper; + } + + @Override + int compare(/* const */Treap treap, int left, int node) { + int right = treap.getElement(node); + Edge edge1 = m_helper.m_edges.get(left); + Edge edge2 = m_helper.m_edges.get(right); + boolean bEdge1Reversed = edge1.getReversed(); + boolean bEdge2Reversed = edge2.getReversed(); + + double x1 = edge1.m_segment.intersectionOfYMonotonicWithAxisX( + m_helper.m_yScanline, 0); + double x2 = edge2.m_segment.intersectionOfYMonotonicWithAxisX( + m_helper.m_yScanline, 0); + + if (x1 == x2) { + // apparently these edges originate from same vertex and the + // scanline is on the vertex.move scanline a little. + double y1 = bEdge1Reversed ? edge1.m_segment.getStartY() + : edge1.m_segment.getEndY(); + double y2 = bEdge2Reversed ? edge2.m_segment.getStartY() + : edge2.m_segment.getEndY(); + double miny = Math.min(y1, y2); + double y = (miny - m_helper.m_yScanline) * 0.5 + + m_helper.m_yScanline; + if (y == m_helper.m_yScanline) { + // assert(0); //ST: not a bug. just curious to see this + // happens. + y = miny; // apparently, one of the segments is almost + // horizontal line. + } + x1 = edge1.m_segment.intersectionOfYMonotonicWithAxisX(y, 0); + x2 = edge2.m_segment.intersectionOfYMonotonicWithAxisX(y, 0); + assert (x1 != x2); + } + + return x1 < x2 ? -1 : (x1 > x2 ? 1 : 0); + } + } + + int multiPointIsSimpleAsFeature_() { + MultiVertexGeometryImpl multiVertexImpl = (MultiVertexGeometryImpl) m_geometry + ._getImpl(); + // sort lexicographically: by y,then by x, then by other attributes in + // the order. + // Go through the sorted list and make sure no points coincide exactly + // (no tolerance is taken into account). + int pointCount = multiVertexImpl.getPointCount(); + + AttributeStreamOfInt32 indices = new AttributeStreamOfInt32(0); + + for (int i = 0; i < pointCount; i++) { + indices.add(i); + } + + indices.Sort(0, pointCount, new MultiPointVertexComparer(this)); + + for (int i = 1; i < pointCount; i++) { + if (compareVerticesMultiPoint_(indices.get(i - 1), indices.get(i)) == 0) { + m_nonSimpleResult = new NonSimpleResult( + NonSimpleResult.Reason.Clustering, indices.get(i - 1), + indices.get(i)); + return 0;// points are coincident-simplify. + } + } + + return 2; + } + + int polylineIsSimpleAsFeature_() { + if (!checkStructure_()) + return 0; + // Non planar IsSimple. + // Go through all line segments and make sure no line segments are + // degenerate. + // Degenerate segment is the one which has its length shorter than + // tolerance or Z projection shorter than z tolerance. + return checkDegenerateSegments_(true) ? 2 : 0; + } + + int polygonIsSimpleAsFeature_() { + return isSimplePlanarImpl_(); + } + + MultiPoint multiPointSimplifyAsFeature_() { + MultiVertexGeometryImpl multiVertexImpl = (MultiVertexGeometryImpl) m_geometry + ._getImpl(); + // sort lexicographically:by y,then by x,then by other attributes in the + // order. + int pointCount = multiVertexImpl.getPointCount(); + assert (pointCount > 0); + + AttributeStreamOfInt32 indices = new AttributeStreamOfInt32(0); + + for (int i = 0; i < pointCount; i++) { + indices.add(i); + } + + indices.Sort(0, pointCount, new MultiPointVertexComparer2(this)); + + // Mark vertices that are unique + boolean[] indicesOut = new boolean[pointCount]; + + indicesOut[indices.get(0)] = true; + + for (int i = 1; i < pointCount; i++) { + int ind1 = indices.get(i - 1); + int ind2 = indices.get(i); + if (compareVerticesMultiPoint_(ind1, ind2) == 0) { + indicesOut[ind2] = false; + continue; + } + + indicesOut[ind2] = true; + } + + // get rid of non-unique vertices. + // We preserve the order of MultiPoint vertices.Among duplicate + // vertices,those that have + // higher index are deleted. + MultiPoint dst = (MultiPoint) m_geometry.createInstance(); + MultiPoint src = (MultiPoint) m_geometry; + int istart = 0; + int iend = 1; + for (int i = 0; i < pointCount; i++) { + if (indicesOut[i]) + iend = i + 1; + else { + if (istart < iend) { + dst.add(src, istart, iend); + } + + istart = i + 1; + } + } + + if (istart < iend) { + dst.add(src, istart, iend); + } + + ((MultiVertexGeometryImpl) dst._getImpl()).setIsSimple( + GeometryXSimple.Strong, m_toleranceSimplify, false); + return dst; + } + + Polyline polylineSimplifyAsFeature_() { + // Non planar simplify. + // Go through all line segments and make sure no line segments are + // degenerate. + // Degenerate segment is the one which has its length shorter than + // tolerance or Z projection shorter than z tolerance. + // The algorithm processes each path symmetrically from each end to + // ensure the result of simplify does not depend on the direction of the + // path. + + MultiPathImpl multiPathImpl = (MultiPathImpl) m_geometry._getImpl(); + SegmentIteratorImpl segIterFwd = multiPathImpl.querySegmentIterator(); + SegmentIteratorImpl segIterBwd = multiPathImpl.querySegmentIterator(); + Polyline dst = (Polyline) m_geometry.createInstance(); + Polyline src = (Polyline) m_geometry; + // Envelope2D env2D; + boolean bHasZ = multiPathImpl + .hasAttribute(VertexDescription.Semantics.Z); + double ztolerance = !bHasZ ? 0.0 : InternalUtils + .calculateZToleranceFromGeometry(m_sr, multiPathImpl, true); + AttributeStreamOfInt32 fwdStack = new AttributeStreamOfInt32(0); + AttributeStreamOfInt32 bwdStack = new AttributeStreamOfInt32(0); + fwdStack.reserve(multiPathImpl.getPointCount() / 2 + 1); + bwdStack.reserve(multiPathImpl.getPointCount() / 2 + 1); + while (segIterFwd.nextPath()) { + segIterBwd.nextPath(); + if (multiPathImpl.getPathSize(segIterFwd.getPathIndex()) < 2) + continue; + + segIterBwd.resetToLastSegment(); + double lengthFwd = 0; + double lengthBwd = 0; + boolean bFirst = true; + while (segIterFwd.hasNextSegment()) { + assert (segIterBwd.hasPreviousSegment()); /* const */ - Segment segFwd = segIterFwd.nextSegment(); + Segment segFwd = segIterFwd.nextSegment(); /* const */ - Segment segBwd = segIterBwd.previousSegment(); - - int idx1 = segIterFwd.getStartPointIndex(); - int idx2 = segIterBwd.getStartPointIndex(); - if (idx1 > idx2) - break; - - if (bFirst) { - // add the very first and the very last point indices - fwdStack.add(segIterFwd.getStartPointIndex());// first goes - // to - // fwdStack - bwdStack.add(segIterBwd.getEndPointIndex());// last goes to - // bwdStack - bFirst = false; - } - - { - int index0 = fwdStack.getLast(); - int index1 = segIterFwd.getEndPointIndex(); - if (index1 - index0 > 1) { - Point2D pt = new Point2D(); - pt.sub(multiPathImpl.getXY(index0), - multiPathImpl.getXY(index1)); - lengthFwd = pt.length(); - } else { - lengthFwd = segFwd.calculateLength2D(); - } - } - - { - int index0 = bwdStack.getLast(); - int index1 = segIterBwd.getStartPointIndex(); - if (index1 - index0 > 1) { - Point2D pt = new Point2D(); - pt.sub(multiPathImpl.getXY(index0), - multiPathImpl.getXY(index1)); - lengthBwd = pt.length(); - } else { - lengthBwd = segBwd.calculateLength2D(); - } - } - - if (lengthFwd > m_toleranceSimplify) { - fwdStack.add(segIterFwd.getEndPointIndex()); - lengthFwd = 0; - } else { - if (bHasZ) { - double z0 = multiPathImpl.getAttributeAsDbl( - VertexDescription.Semantics.Z, - fwdStack.getLast(), 0); - double z1 = segFwd.getEndAttributeAsDbl( - VertexDescription.Semantics.Z, 0); - if (Math.abs(z1 - z0) > ztolerance) { - fwdStack.add(segIterFwd.getEndPointIndex()); - lengthFwd = 0; - } - } - } - - if (lengthBwd > m_toleranceSimplify) { - bwdStack.add(segIterBwd.getStartPointIndex()); - lengthBwd = 0; - } else { - if (bHasZ) { - double z0 = multiPathImpl.getAttributeAsDbl( - VertexDescription.Semantics.Z, - bwdStack.getLast(), 0); - double z1 = segBwd.getEndAttributeAsDbl( - VertexDescription.Semantics.Z, 0); - if (Math.abs(z1 - z0) > ztolerance) { - bwdStack.add(segIterBwd.getStartPointIndex()); - lengthBwd = 0; - } - } - } - } - - // assert(fwdStack.getLast() <= bwdStack.getLast()); - if (fwdStack.getLast() < bwdStack.getLast()) { - // There is degenerate segment in the middle. Remove. - // If the path degenerate, this will make fwdStack.size() + - // bwdStack.size() < 2. - if (fwdStack.size() > bwdStack.size()) - fwdStack.removeLast(); - else - bwdStack.removeLast(); - } else if (fwdStack.getLast() == bwdStack.getLast()) { - bwdStack.removeLast(); - } else { - assert (fwdStack.getLast() - bwdStack.getLast() == 1); - bwdStack.removeLast(); - bwdStack.removeLast(); - } - - if (bwdStack.size() + fwdStack.size() >= 2) { - // Completely ignore the curves for now. - Point point = new Point(); - for (int i = 0, n = fwdStack.size(); i < n; i++) { - src.getPointByVal(fwdStack.get(i), point); - if (i == 0) - dst.startPath(point); - else - dst.lineTo(point); - } - - // int prevIdx = fwdStack.getLast(); - for (int i = bwdStack.size() - 1; i > 0; i--) { - src.getPointByVal(bwdStack.get(i), point); - dst.lineTo(point); - } - - if (src.isClosedPath(segIterFwd.getPathIndex())) { - dst.closePathWithLine(); - } else { - if (bwdStack.size() > 0) { - src.getPointByVal(bwdStack.get(0), point); - dst.lineTo(point); - } - } - } else { - // degenerate path won't be added - } - - if (fwdStack != null) - fwdStack.clear(false); - if (bwdStack != null) - bwdStack.clear(false); - } - - ((MultiVertexGeometryImpl) dst._getImpl()).setIsSimple( - GeometryXSimple.Strong, m_toleranceSimplify, false); - return dst; - } - - Polygon polygonSimplifyAsFeature_() { - return (Polygon) simplifyPlanar_(); - } - - MultiVertexGeometry simplifyPlanar_() { - // do clustering/cracking loop - // if (false) - // { - // ((MultiPathImpl)m_geometry._getImpl()).saveToTextFileDbg("c:/temp/_simplifyDbg0.txt"); - // } - - if (m_geometry.getType() == Geometry.Type.Polygon) { - if (((Polygon) m_geometry).getFillRule() == Polygon.FillRule.enumFillRuleWinding) { - // when the fill rule is winding, we need to call a special - // method. - return TopologicalOperations.planarSimplify( - (MultiVertexGeometry) m_geometry, m_toleranceSimplify, - true, false, m_progressTracker); - } - } - - m_editShape = new EditShape(); - m_editShape.addGeometry(m_geometry); - - if (m_editShape.getTotalPointCount() != 0) { - assert (m_knownSimpleResult != GeometryXSimple.Strong); - if (m_knownSimpleResult != GeometryXSimple.Weak) { - CrackAndCluster.execute(m_editShape, m_toleranceSimplify, - m_progressTracker, true); - } - - if (m_geometry.getType().equals(Geometry.Type.Polygon)) { - Simplificator.execute(m_editShape, m_editShape.getFirstGeometry(), - m_knownSimpleResult, false, m_progressTracker); - } - } - - m_geometry = m_editShape.getGeometry(m_editShape.getFirstGeometry()); // extract - // the - // result - // of - // simplify - - if (m_geometry.getType().equals(Geometry.Type.Polygon)) { - ((MultiPathImpl) m_geometry._getImpl())._updateOGCFlags(); - ((Polygon) m_geometry).setFillRule(Polygon.FillRule.enumFillRuleOddEven); - } - - // We have simplified the geometry using the given tolerance. Now mark - // the geometry as strong simple, - // So that the next call will not have to repeat these steps. - - ((MultiVertexGeometryImpl) m_geometry._getImpl()).setIsSimple( - GeometryXSimple.Strong, m_toleranceSimplify, false); - - return (MultiVertexGeometry) (m_geometry); - } - - NonSimpleResult m_nonSimpleResult; - - OperatorSimplifyLocalHelper(Geometry geometry, - SpatialReference spatialReference, int knownSimpleResult, - ProgressTracker progressTracker, boolean bOGCRestrictions) { - - m_description = geometry.getDescription(); - m_geometry = geometry; - m_sr = (SpatialReferenceImpl) spatialReference; - m_dbgCounter = 0; - m_toleranceIsSimple = InternalUtils.calculateToleranceFromGeometry( - m_sr, geometry, false); - m_toleranceSimplify = InternalUtils.calculateToleranceFromGeometry( - m_sr, geometry, true); - // m_toleranceCluster = m_toleranceSimplify * Math.sqrt(2.0) * 1.00001; - m_knownSimpleResult = knownSimpleResult; - m_attributeCount = m_description.getAttributeCount(); - m_edges = new ArrayList(); - m_lineEdgesRecycle = new ArrayList(); - m_crossOverHelperList = new IndexMultiDCList(); - m_AET = new Treap(); - m_nonSimpleResult = new NonSimpleResult(); - m_bOGCRestrictions = bOGCRestrictions; - m_bPlanarSimplify = m_bOGCRestrictions; - } - - // Returns 0 non-simple, 1 weak simple, 2 strong simple - - /** - * The code is executed in the 2D plane only.Attributes are ignored. - * MultiPoint-check for clustering. Polyline -check for clustering and - * cracking. Polygon -check for clustering,cracking,absence of - * self-intersections,and correct ring ordering. - */ - static protected int isSimplePlanar(/* const */Geometry geometry, /* const */ - SpatialReference spatialReference, boolean bForce, - ProgressTracker progressTracker) { - assert (false); // this code is not called yet. - if (geometry.isEmpty()) - return 1; - Geometry.Type gt = geometry.getType(); - if (gt == Geometry.Type.Point) - return 1; - else if (gt == Geometry.Type.Envelope) { - Envelope2D env2D = new Envelope2D(); - geometry.queryEnvelope2D(env2D); - boolean bReturnValue = !env2D.isDegenerate(InternalUtils - .calculateToleranceFromGeometry(spatialReference, geometry, - false)); - return bReturnValue ? 1 : 0; - } else if (Geometry.isSegment(gt.value())) { - throw GeometryException.GeometryInternalError(); - // return seg.IsSimple(m_tolerance); - } else if (!Geometry.isMultiVertex(gt.value())) { - throw GeometryException.GeometryInternalError();// What else? - } - - double tolerance = InternalUtils.calculateToleranceFromGeometry( - spatialReference, geometry, false); - - double geomTolerance = 0; - int isSimple = ((MultiVertexGeometryImpl) geometry._getImpl()) - .getIsSimple(tolerance); - int knownSimpleResult = bForce ? -1 : isSimple; - // TODO: need to distinguish KnownSimple between SimpleAsFeature and - // SimplePlanar. The SimplePlanar implies SimpleAsFeature. - if (knownSimpleResult != -1) - return knownSimpleResult; - - if (knownSimpleResult == GeometryXSimple.Weak) { - assert (tolerance <= geomTolerance); - tolerance = geomTolerance;// OVERRIDE the tolerance. - } - - OperatorSimplifyLocalHelper helper = new OperatorSimplifyLocalHelper( - geometry, spatialReference, knownSimpleResult, progressTracker, - false); - knownSimpleResult = helper.isSimplePlanarImpl_(); - ((MultiVertexGeometryImpl) geometry._getImpl()).setIsSimple( - knownSimpleResult, tolerance, false); - return knownSimpleResult; - } - - /** - * Checks if Geometry is simple for storing in DB: - *

- * MultiPoint:check that no points coincide.tolerance is ignored. - * Polyline:ensure there no segments degenerate segments. Polygon:Same as - * IsSimplePlanar. - */ - static protected int isSimpleAsFeature(/* const */Geometry geometry, /* const */ - SpatialReference spatialReference, boolean bForce, NonSimpleResult result, - ProgressTracker progressTracker) { - if (result != null) { - result.m_reason = NonSimpleResult.Reason.NotDetermined; - result.m_vertexIndex1 = -1; - result.m_vertexIndex2 = -1; - } - if (geometry.isEmpty()) - return 1; - Geometry.Type gt = geometry.getType(); - if (gt == Geometry.Type.Point) - return 1; - - double tolerance = InternalUtils.calculateToleranceFromGeometry( - spatialReference, geometry, false); - if (gt == Geometry.Type.Envelope) { + Segment segBwd = segIterBwd.previousSegment(); + + int idx1 = segIterFwd.getStartPointIndex(); + int idx2 = segIterBwd.getStartPointIndex(); + if (idx1 > idx2) + break; + + if (bFirst) { + // add the very first and the very last point indices + fwdStack.add(segIterFwd.getStartPointIndex());// first goes + // to + // fwdStack + bwdStack.add(segIterBwd.getEndPointIndex());// last goes to + // bwdStack + bFirst = false; + } + + { + int index0 = fwdStack.getLast(); + int index1 = segIterFwd.getEndPointIndex(); + if (index1 - index0 > 1) { + Point2D pt = new Point2D(); + pt.sub(multiPathImpl.getXY(index0), + multiPathImpl.getXY(index1)); + lengthFwd = pt.length(); + } else { + lengthFwd = segFwd.calculateLength2D(); + } + } + + { + int index0 = bwdStack.getLast(); + int index1 = segIterBwd.getStartPointIndex(); + if (index1 - index0 > 1) { + Point2D pt = new Point2D(); + pt.sub(multiPathImpl.getXY(index0), + multiPathImpl.getXY(index1)); + lengthBwd = pt.length(); + } else { + lengthBwd = segBwd.calculateLength2D(); + } + } + + if (lengthFwd > m_toleranceSimplify) { + fwdStack.add(segIterFwd.getEndPointIndex()); + lengthFwd = 0; + } else { + if (bHasZ) { + double z0 = multiPathImpl.getAttributeAsDbl( + VertexDescription.Semantics.Z, + fwdStack.getLast(), 0); + double z1 = segFwd.getEndAttributeAsDbl( + VertexDescription.Semantics.Z, 0); + if (Math.abs(z1 - z0) > ztolerance) { + fwdStack.add(segIterFwd.getEndPointIndex()); + lengthFwd = 0; + } + } + } + + if (lengthBwd > m_toleranceSimplify) { + bwdStack.add(segIterBwd.getStartPointIndex()); + lengthBwd = 0; + } else { + if (bHasZ) { + double z0 = multiPathImpl.getAttributeAsDbl( + VertexDescription.Semantics.Z, + bwdStack.getLast(), 0); + double z1 = segBwd.getEndAttributeAsDbl( + VertexDescription.Semantics.Z, 0); + if (Math.abs(z1 - z0) > ztolerance) { + bwdStack.add(segIterBwd.getStartPointIndex()); + lengthBwd = 0; + } + } + } + } + + // assert(fwdStack.getLast() <= bwdStack.getLast()); + if (fwdStack.getLast() < bwdStack.getLast()) { + // There is degenerate segment in the middle. Remove. + // If the path degenerate, this will make fwdStack.size() + + // bwdStack.size() < 2. + if (fwdStack.size() > bwdStack.size()) + fwdStack.removeLast(); + else + bwdStack.removeLast(); + } else if (fwdStack.getLast() == bwdStack.getLast()) { + bwdStack.removeLast(); + } else { + assert (fwdStack.getLast() - bwdStack.getLast() == 1); + bwdStack.removeLast(); + bwdStack.removeLast(); + } + + if (bwdStack.size() + fwdStack.size() >= 2) { + // Completely ignore the curves for now. + Point point = new Point(); + for (int i = 0, n = fwdStack.size(); i < n; i++) { + src.getPointByVal(fwdStack.get(i), point); + if (i == 0) + dst.startPath(point); + else + dst.lineTo(point); + } + + // int prevIdx = fwdStack.getLast(); + for (int i = bwdStack.size() - 1; i > 0; i--) { + src.getPointByVal(bwdStack.get(i), point); + dst.lineTo(point); + } + + if (src.isClosedPath(segIterFwd.getPathIndex())) { + dst.closePathWithLine(); + } else { + if (bwdStack.size() > 0) { + src.getPointByVal(bwdStack.get(0), point); + dst.lineTo(point); + } + } + } else { + // degenerate path won't be added + } + + if (fwdStack != null) + fwdStack.clear(false); + if (bwdStack != null) + bwdStack.clear(false); + } + + ((MultiVertexGeometryImpl) dst._getImpl()).setIsSimple( + GeometryXSimple.Strong, m_toleranceSimplify, false); + return dst; + } + + Polygon polygonSimplifyAsFeature_() { + return (Polygon) simplifyPlanar_(); + } + + MultiVertexGeometry simplifyPlanar_() { + // do clustering/cracking loop + // if (false) + // { + // ((MultiPathImpl)m_geometry._getImpl()).saveToTextFileDbg("c:/temp/_simplifyDbg0.txt"); + // } + + if (m_geometry.getType() == Geometry.Type.Polygon) { + if (((Polygon) m_geometry).getFillRule() == Polygon.FillRule.enumFillRuleWinding) { + // when the fill rule is winding, we need to call a special + // method. + return TopologicalOperations.planarSimplify( + (MultiVertexGeometry) m_geometry, m_toleranceSimplify, + true, false, m_progressTracker); + } + } + + m_editShape = new EditShape(); + m_editShape.addGeometry(m_geometry); + + if (m_editShape.getTotalPointCount() != 0) { + assert (m_knownSimpleResult != GeometryXSimple.Strong); + if (m_knownSimpleResult != GeometryXSimple.Weak) { + CrackAndCluster.execute(m_editShape, m_toleranceSimplify, + m_progressTracker, true); + } + + if (m_geometry.getType().equals(Geometry.Type.Polygon)) { + Simplificator.execute(m_editShape, m_editShape.getFirstGeometry(), + m_knownSimpleResult, false, m_progressTracker); + } + } + + m_geometry = m_editShape.getGeometry(m_editShape.getFirstGeometry()); // extract + // the + // result + // of + // simplify + + if (m_geometry.getType().equals(Geometry.Type.Polygon)) { + ((MultiPathImpl) m_geometry._getImpl())._updateOGCFlags(); + ((Polygon) m_geometry).setFillRule(Polygon.FillRule.enumFillRuleOddEven); + } + + // We have simplified the geometry using the given tolerance. Now mark + // the geometry as strong simple, + // So that the next call will not have to repeat these steps. + + ((MultiVertexGeometryImpl) m_geometry._getImpl()).setIsSimple( + GeometryXSimple.Strong, m_toleranceSimplify, false); + + return (MultiVertexGeometry) (m_geometry); + } + + NonSimpleResult m_nonSimpleResult; + + OperatorSimplifyLocalHelper(Geometry geometry, + SpatialReference spatialReference, int knownSimpleResult, + ProgressTracker progressTracker, boolean bOGCRestrictions) { + + m_description = geometry.getDescription(); + m_geometry = geometry; + m_sr = (SpatialReferenceImpl) spatialReference; + m_dbgCounter = 0; + m_toleranceIsSimple = InternalUtils.calculateToleranceFromGeometry( + m_sr, geometry, false); + m_toleranceSimplify = InternalUtils.calculateToleranceFromGeometry( + m_sr, geometry, true); + // m_toleranceCluster = m_toleranceSimplify * Math.sqrt(2.0) * 1.00001; + m_knownSimpleResult = knownSimpleResult; + m_attributeCount = m_description.getAttributeCount(); + m_edges = new ArrayList(); + m_lineEdgesRecycle = new ArrayList(); + m_crossOverHelperList = new IndexMultiDCList(); + m_AET = new Treap(); + m_nonSimpleResult = new NonSimpleResult(); + m_bOGCRestrictions = bOGCRestrictions; + m_bPlanarSimplify = m_bOGCRestrictions; + } + + // Returns 0 non-simple, 1 weak simple, 2 strong simple + + /** + * The code is executed in the 2D plane only.Attributes are ignored. + * MultiPoint-check for clustering. Polyline -check for clustering and + * cracking. Polygon -check for clustering,cracking,absence of + * self-intersections,and correct ring ordering. + */ + static protected int isSimplePlanar(/* const */Geometry geometry, /* const */ + SpatialReference spatialReference, boolean bForce, + ProgressTracker progressTracker) { + assert (false); // this code is not called yet. + if (geometry.isEmpty()) + return 1; + Geometry.Type gt = geometry.getType(); + if (gt == Geometry.Type.Point) + return 1; + else if (gt == Geometry.Type.Envelope) { + Envelope2D env2D = new Envelope2D(); + geometry.queryEnvelope2D(env2D); + boolean bReturnValue = !env2D.isDegenerate(InternalUtils + .calculateToleranceFromGeometry(spatialReference, geometry, + false)); + return bReturnValue ? 1 : 0; + } else if (Geometry.isSegment(gt.value())) { + throw GeometryException.GeometryInternalError(); + // return seg.IsSimple(m_tolerance); + } else if (!Geometry.isMultiVertex(gt.value())) { + throw GeometryException.GeometryInternalError();// What else? + } + + double tolerance = InternalUtils.calculateToleranceFromGeometry( + spatialReference, geometry, false); + + double geomTolerance = 0; + int isSimple = ((MultiVertexGeometryImpl) geometry._getImpl()) + .getIsSimple(tolerance); + int knownSimpleResult = bForce ? -1 : isSimple; + // TODO: need to distinguish KnownSimple between SimpleAsFeature and + // SimplePlanar. The SimplePlanar implies SimpleAsFeature. + if (knownSimpleResult != -1) + return knownSimpleResult; + + if (knownSimpleResult == GeometryXSimple.Weak) { + assert (tolerance <= geomTolerance); + tolerance = geomTolerance;// OVERRIDE the tolerance. + } + + OperatorSimplifyLocalHelper helper = new OperatorSimplifyLocalHelper( + geometry, spatialReference, knownSimpleResult, progressTracker, + false); + knownSimpleResult = helper.isSimplePlanarImpl_(); + ((MultiVertexGeometryImpl) geometry._getImpl()).setIsSimple( + knownSimpleResult, tolerance, false); + return knownSimpleResult; + } + + /** + * Checks if Geometry is simple for storing in DB: + *

+ * MultiPoint:check that no points coincide.tolerance is ignored. + * Polyline:ensure there no segments degenerate segments. Polygon:Same as + * IsSimplePlanar. + */ + static protected int isSimpleAsFeature(/* const */Geometry geometry, /* const */ + SpatialReference spatialReference, boolean bForce, NonSimpleResult result, + ProgressTracker progressTracker) { + if (result != null) { + result.m_reason = NonSimpleResult.Reason.NotDetermined; + result.m_vertexIndex1 = -1; + result.m_vertexIndex2 = -1; + } + if (geometry.isEmpty()) + return 1; + Geometry.Type gt = geometry.getType(); + if (gt == Geometry.Type.Point) + return 1; + + double tolerance = InternalUtils.calculateToleranceFromGeometry( + spatialReference, geometry, false); + if (gt == Geometry.Type.Envelope) { /* const */ - Envelope env = (Envelope) geometry; - Envelope2D env2D = new Envelope2D(); - env.queryEnvelope2D(env2D); - if (env2D.isDegenerate(tolerance)) { - if (result != null) { - result.m_reason = NonSimpleResult.Reason.DegenerateSegments; - result.m_vertexIndex1 = -1; - result.m_vertexIndex2 = -1; - } - return 0; - } - return 1; - } else if (Geometry.isSegment(gt.value())) { + Envelope env = (Envelope) geometry; + Envelope2D env2D = new Envelope2D(); + env.queryEnvelope2D(env2D); + if (env2D.isDegenerate(tolerance)) { + if (result != null) { + result.m_reason = NonSimpleResult.Reason.DegenerateSegments; + result.m_vertexIndex1 = -1; + result.m_vertexIndex2 = -1; + } + return 0; + } + return 1; + } else if (Geometry.isSegment(gt.value())) { /* const */ - Segment seg = (Segment) geometry; - Polyline polyline = new Polyline(seg.getDescription()); - polyline.addSegment(seg, true); - return isSimpleAsFeature(polyline, spatialReference, bForce, - result, progressTracker); - } - - // double geomTolerance = 0; - int isSimple = ((MultiVertexGeometryImpl) geometry._getImpl()) - .getIsSimple(tolerance); - int knownSimpleResult = bForce ? -1 : isSimple; - // TODO: need to distinguish KnownSimple between SimpleAsFeature and - // SimplePlanar. - // From the first sight it seems the SimplePlanar implies - // SimpleAsFeature. - if (knownSimpleResult != -1) - return knownSimpleResult; - - OperatorSimplifyLocalHelper helper = new OperatorSimplifyLocalHelper( - geometry, spatialReference, knownSimpleResult, progressTracker, - false); - - if (gt == Geometry.Type.MultiPoint) { - knownSimpleResult = helper.multiPointIsSimpleAsFeature_(); - } else if (gt == Geometry.Type.Polyline) { - knownSimpleResult = helper.polylineIsSimpleAsFeature_(); - } else if (gt == Geometry.Type.Polygon) { - knownSimpleResult = helper.polygonIsSimpleAsFeature_(); - } else { - throw GeometryException.GeometryInternalError();// what else? - } - - ((MultiVertexGeometryImpl) (geometry._getImpl())).setIsSimple( - knownSimpleResult, tolerance, false); - if (result != null && knownSimpleResult == 0) - result.Assign(helper.m_nonSimpleResult); - return knownSimpleResult; - } - - static int isSimpleOGC(/* const */Geometry geometry, /* const */ - SpatialReference spatialReference, boolean bForce, NonSimpleResult result, - ProgressTracker progressTracker) { - if (result != null) { - result.m_reason = NonSimpleResult.Reason.NotDetermined; - result.m_vertexIndex1 = -1; - result.m_vertexIndex2 = -1; - } - if (geometry.isEmpty()) - return 1; - Geometry.Type gt = geometry.getType(); - if (gt == Geometry.Type.Point) - return 1; - - double tolerance = InternalUtils.calculateToleranceFromGeometry( - spatialReference, geometry, false); - if (gt == Geometry.Type.Envelope) { + Segment seg = (Segment) geometry; + Polyline polyline = new Polyline(seg.getDescription()); + polyline.addSegment(seg, true); + return isSimpleAsFeature(polyline, spatialReference, bForce, + result, progressTracker); + } + + // double geomTolerance = 0; + int isSimple = ((MultiVertexGeometryImpl) geometry._getImpl()) + .getIsSimple(tolerance); + int knownSimpleResult = bForce ? -1 : isSimple; + // TODO: need to distinguish KnownSimple between SimpleAsFeature and + // SimplePlanar. + // From the first sight it seems the SimplePlanar implies + // SimpleAsFeature. + if (knownSimpleResult != -1) + return knownSimpleResult; + + OperatorSimplifyLocalHelper helper = new OperatorSimplifyLocalHelper( + geometry, spatialReference, knownSimpleResult, progressTracker, + false); + + if (gt == Geometry.Type.MultiPoint) { + knownSimpleResult = helper.multiPointIsSimpleAsFeature_(); + } else if (gt == Geometry.Type.Polyline) { + knownSimpleResult = helper.polylineIsSimpleAsFeature_(); + } else if (gt == Geometry.Type.Polygon) { + knownSimpleResult = helper.polygonIsSimpleAsFeature_(); + } else { + throw GeometryException.GeometryInternalError();// what else? + } + + ((MultiVertexGeometryImpl) (geometry._getImpl())).setIsSimple( + knownSimpleResult, tolerance, false); + if (result != null && knownSimpleResult == 0) + result.Assign(helper.m_nonSimpleResult); + return knownSimpleResult; + } + + static int isSimpleOGC(/* const */Geometry geometry, /* const */ + SpatialReference spatialReference, boolean bForce, NonSimpleResult result, + ProgressTracker progressTracker) { + if (result != null) { + result.m_reason = NonSimpleResult.Reason.NotDetermined; + result.m_vertexIndex1 = -1; + result.m_vertexIndex2 = -1; + } + if (geometry.isEmpty()) + return 1; + Geometry.Type gt = geometry.getType(); + if (gt == Geometry.Type.Point) + return 1; + + double tolerance = InternalUtils.calculateToleranceFromGeometry( + spatialReference, geometry, false); + if (gt == Geometry.Type.Envelope) { /* const */ - Envelope env = (Envelope) geometry; - Envelope2D env2D = new Envelope2D(); - env.queryEnvelope2D(env2D); - if (env2D.isDegenerate(tolerance)) { - if (result != null) { - result.m_reason = NonSimpleResult.Reason.DegenerateSegments; - result.m_vertexIndex1 = -1; - result.m_vertexIndex2 = -1; - } - return 0; - } - return 1; - } else if (Geometry.isSegment(gt.value())) { + Envelope env = (Envelope) geometry; + Envelope2D env2D = new Envelope2D(); + env.queryEnvelope2D(env2D); + if (env2D.isDegenerate(tolerance)) { + if (result != null) { + result.m_reason = NonSimpleResult.Reason.DegenerateSegments; + result.m_vertexIndex1 = -1; + result.m_vertexIndex2 = -1; + } + return 0; + } + return 1; + } else if (Geometry.isSegment(gt.value())) { /* const */ - Segment seg = (Segment) geometry; - Polyline polyline = new Polyline(seg.getDescription()); - polyline.addSegment(seg, true); - return isSimpleAsFeature(polyline, spatialReference, bForce, - result, progressTracker); - } - - int knownSimpleResult = -1; - - OperatorSimplifyLocalHelper helper = new OperatorSimplifyLocalHelper( - geometry, spatialReference, knownSimpleResult, progressTracker, - true); - - if (gt == Geometry.Type.MultiPoint || gt == Geometry.Type.Polyline - || gt == Geometry.Type.Polygon) { - knownSimpleResult = helper.isSimplePlanarImpl_(); - } else { - throw GeometryException.GeometryInternalError();// what else? - } - - if (result != null) - result.Assign(helper.m_nonSimpleResult); - - return knownSimpleResult; - } - - /** - * Simplifies geometries for storing in DB: - *

- * MultiPoint:check that no points coincide.tolerance is ignored. - * Polyline:ensure there no segments degenerate segments. Polygon:cracks and - * clusters using cluster tolerance and resolves all self intersections, - * orients rings properly and arranges the rings in the OGC order. - *

- * Returns simplified geometry. - */ - static protected Geometry simplifyAsFeature(/* const */Geometry geometry, /* const */ - SpatialReference spatialReference, boolean bForce, - ProgressTracker progressTracker) { - if (geometry.isEmpty()) - return geometry; - Geometry.Type gt = geometry.getType(); - if (gt == Geometry.Type.Point) - return geometry; - - double tolerance = InternalUtils.calculateToleranceFromGeometry( - spatialReference, geometry, false); - if (gt == Geometry.Type.Envelope) { - Envelope env = (Envelope) geometry; - Envelope2D env2D = new Envelope2D(); - env.queryEnvelope2D(env2D); - if (env2D.isDegenerate(tolerance)) { - return (Geometry) (env.createInstance()); // return empty - // geometry - } - return geometry; - } else if (Geometry.isSegment(gt.value())) { - Segment seg = (Segment) geometry; - Polyline polyline = new Polyline(seg.getDescription()); - polyline.addSegment(seg, true); - return simplifyAsFeature(polyline, spatialReference, bForce, - progressTracker); - } - - double geomTolerance = 0; - int isSimple = ((MultiVertexGeometryImpl) geometry._getImpl()) - .getIsSimple(tolerance); - int knownSimpleResult = bForce ? GeometryXSimple.Unknown : isSimple; - - // TODO: need to distinguish KnownSimple between SimpleAsFeature and - // SimplePlanar. - // From the first sight it seems the SimplePlanar implies - // SimpleAsFeature. - if (knownSimpleResult == GeometryXSimple.Strong) { - if (gt == Geometry.Type.Polygon && ((Polygon) geometry).getFillRule() != Polygon.FillRule.enumFillRuleOddEven) { - Geometry res = geometry.copy(); - ((Polygon) res).setFillRule(Polygon.FillRule.enumFillRuleOddEven);//standardize on odd_even fill rule - return res; - } - - return geometry; - } - - OperatorSimplifyLocalHelper helper = new OperatorSimplifyLocalHelper( - geometry, spatialReference, knownSimpleResult, progressTracker, - false); - - Geometry result; - - if (gt == Geometry.Type.MultiPoint) { - result = (Geometry) (helper.multiPointSimplifyAsFeature_()); - } else if (gt == Geometry.Type.Polyline) { - result = (Geometry) (helper.polylineSimplifyAsFeature_()); - } else if (gt == Geometry.Type.Polygon) { - result = (Geometry) (helper.polygonSimplifyAsFeature_()); - } else { - throw GeometryException.GeometryInternalError(); // what else? - } - - return result; - } - - /** - * Simplifies geometries for storing in OGC format: - *

- * MultiPoint:check that no points coincide.tolerance is ignored. - * Polyline:ensure there no segments degenerate segments. Polygon:cracks and - * clusters using cluster tolerance and resolves all self intersections, - * orients rings properly and arranges the rings in the OGC order. - *

- * Returns simplified geometry. - */ - static Geometry simplifyOGC(/* const */Geometry geometry, /* const */ - SpatialReference spatialReference, boolean bForce, - ProgressTracker progressTracker) { - if (geometry.isEmpty()) - return geometry; - Geometry.Type gt = geometry.getType(); - if (gt == Geometry.Type.Point) - return geometry; - - double tolerance = InternalUtils.calculateToleranceFromGeometry( - spatialReference, geometry, false); - if (gt == Geometry.Type.Envelope) { - Envelope env = (Envelope) geometry; - Envelope2D env2D = new Envelope2D(); - env.queryEnvelope2D(env2D); - if (env2D.isDegenerate(tolerance)) { - return (Geometry) (env.createInstance()); // return empty - // geometry - } - return geometry; - } else if (Geometry.isSegment(gt.value())) { - Segment seg = (Segment) geometry; - Polyline polyline = new Polyline(seg.getDescription()); - polyline.addSegment(seg, true); - return simplifyOGC(polyline, spatialReference, bForce, - progressTracker); - } - - if (!Geometry.isMultiVertex(gt.value())) { - throw new GeometryException("OGC simplify is not implemented for this geometry type" + gt); - } - - MultiVertexGeometry result = TopologicalOperations.simplifyOGC( - (MultiVertexGeometry) geometry, tolerance, false, progressTracker); - - return result; - } - - private int compareVertices_(int i1, int i2, boolean get_paths) { - if (i1 == i2) - return 0; - - int pair1 = m_pairs.get(i1); - int pair2 = m_pairs.get(i2); - int xy1 = pair1 >> 1; - int xy2 = pair2 >> 1; - Point2D pt1 = new Point2D(); - Point2D pt2 = new Point2D(); - m_xy.read(2 * xy1, pt1); - pt1.y += (((pair1 & 1) != 0) ? m_toleranceIsSimple - : -m_toleranceIsSimple); - m_xy.read(2 * xy2, pt2); - pt2.y += (((pair2 & 1) != 0) ? m_toleranceIsSimple - : -m_toleranceIsSimple); - int res = pt1.compare(pt2); - if (res == 0 && get_paths) { - int di = m_paths_for_OGC_tests.get(xy1) - - m_paths_for_OGC_tests.get(xy2); - return di < 0 ? -1 : di > 0 ? 1 : 0; - } - return res; - } - - private static final class VertexComparer extends - AttributeStreamOfInt32.IntComparator { - OperatorSimplifyLocalHelper parent; - boolean get_paths; - - VertexComparer(OperatorSimplifyLocalHelper parent_, boolean get_paths_) { - parent = parent_; - get_paths = get_paths_; - } - - @Override - public int compare(int i1, int i2) { - return parent.compareVertices_(i1, i2, get_paths); - } - } - - private static final class IndexSorter extends ClassicSort { - OperatorSimplifyLocalHelper parent; - private boolean get_paths; - private Point2D pt1_dummy = new Point2D(); - - IndexSorter(OperatorSimplifyLocalHelper parent_, boolean get_paths_) { - parent = parent_; - get_paths = get_paths_; - } - - @Override - public void userSort(int begin, int end, AttributeStreamOfInt32 indices) { - indices.Sort(begin, end, new VertexComparer(parent, get_paths)); - } - - @Override - public double getValue(int index) /* const */ { - int pair = parent.m_pairs.get(index); - int xy1 = pair >> 1; - parent.m_xy.read(2 * xy1, pt1_dummy); - double y = pt1_dummy.y - + (((pair & 1) != 0) ? parent.m_toleranceIsSimple - : -parent.m_toleranceIsSimple); - return y; - } - } - - private int compareVerticesMultiPoint_(int i1, int i2) { - if (i1 == i2) - return 0; - MultiVertexGeometryImpl multiVertexImpl = (MultiVertexGeometryImpl) m_geometry - ._getImpl(); - Point2D pt1 = multiVertexImpl.getXY(i1); - Point2D pt2 = multiVertexImpl.getXY(i2); - - if (pt1.x < pt2.x) - return -1; - if (pt1.x > pt2.x) - return 1; - if (pt1.y < pt2.y) - return -1; - if (pt1.y > pt2.y) - return 1; - - for (int attrib = 1; attrib < m_attributeCount; attrib++) { - int semantics = m_description.getSemantics(attrib); - int nords = VertexDescription.getComponentCount(semantics); - for (int ord = 0; ord < nords; ord++) { - double v1 = multiVertexImpl.getAttributeAsDbl(semantics, i1, - ord); - double v2 = multiVertexImpl.getAttributeAsDbl(semantics, i2, - ord); - if (v1 < v2) - return -1; - if (v1 > v2) - return 1; - } - } - - return 0; - } - - private int compareVerticesMultiPoint2_(int i1, int i2) { - int res = compareVerticesMultiPoint_(i1, i2); - if (res == 0) - return i1 < i2 ? -1 : 1; - else - return res; - } - - private static final class EdgeComparerForSelfIntersection implements - Comparator { - OperatorSimplifyLocalHelper parent; - - EdgeComparerForSelfIntersection(OperatorSimplifyLocalHelper parent_) { - parent = parent_; - } - - // Recall that the total ordering [<] induced by compare satisfies e1 - // [<] e2 if and only if compare(e1, e2) < 0. - - @Override - public int compare(Edge e1, Edge e2) { - return parent.edgeAngleCompare_(e1, e2); - } - } - - private static final class MultiPointVertexComparer extends - AttributeStreamOfInt32.IntComparator { - OperatorSimplifyLocalHelper parent; - - MultiPointVertexComparer(OperatorSimplifyLocalHelper parent_) { - parent = parent_; - } - - @Override - public int compare(int i1, int i2) { - return parent.compareVerticesMultiPoint_(i1, i2); - } - } - - private static final class MultiPointVertexComparer2 extends - AttributeStreamOfInt32.IntComparator { - OperatorSimplifyLocalHelper parent; - - MultiPointVertexComparer2(OperatorSimplifyLocalHelper parent_) { - parent = parent_; - } - - @Override - public int compare(int i1, int i2) { - return parent.compareVerticesMultiPoint2_(i1, i2); - } - } - - // compares angles between two edges - private int edgeAngleCompare_(/* const */Edge edge1, /* const */Edge edge2) { - if (edge1.equals(edge2)) - return 0; - - Point2D v1 = edge1.m_segment._getTangent(edge1.getReversed() ? 1.0 - : 0.0); - if (edge1.getReversed()) - v1.negate(); - Point2D v2 = edge2.m_segment._getTangent(edge2.getReversed() ? 1.0 - : 0.0); - if (edge2.getReversed()) - v2.negate(); - - int q1 = v1._getQuarter(); - int q2 = v2._getQuarter(); - - if (q2 == q1) { - double cross = v1.crossProduct(v2); - double crossError = 4 * NumberUtils.doubleEps() - * (Math.abs(v2.x * v1.y) + Math.abs(v2.y * v1.x)); - if (Math.abs(cross) <= crossError) { - cross--; // To avoid warning of "this line has no effect" from - // cross = cross. - cross++; - } - assert (Math.abs(cross) > crossError); - return cross < 0 ? 1 : (cross > 0 ? -1 : 0); - } else { - return q1 < q2 ? -1 : 1; - } - } + Segment seg = (Segment) geometry; + Polyline polyline = new Polyline(seg.getDescription()); + polyline.addSegment(seg, true); + return isSimpleAsFeature(polyline, spatialReference, bForce, + result, progressTracker); + } + + int knownSimpleResult = -1; + + OperatorSimplifyLocalHelper helper = new OperatorSimplifyLocalHelper( + geometry, spatialReference, knownSimpleResult, progressTracker, + true); + + if (gt == Geometry.Type.MultiPoint || gt == Geometry.Type.Polyline + || gt == Geometry.Type.Polygon) { + knownSimpleResult = helper.isSimplePlanarImpl_(); + } else { + throw GeometryException.GeometryInternalError();// what else? + } + + if (result != null) + result.Assign(helper.m_nonSimpleResult); + + return knownSimpleResult; + } + + /** + * Simplifies geometries for storing in DB: + *

+ * MultiPoint:check that no points coincide.tolerance is ignored. + * Polyline:ensure there no segments degenerate segments. Polygon:cracks and + * clusters using cluster tolerance and resolves all self intersections, + * orients rings properly and arranges the rings in the OGC order. + *

+ * Returns simplified geometry. + */ + static protected Geometry simplifyAsFeature(/* const */Geometry geometry, /* const */ + SpatialReference spatialReference, boolean bForce, + ProgressTracker progressTracker) { + if (geometry.isEmpty()) + return geometry; + Geometry.Type gt = geometry.getType(); + if (gt == Geometry.Type.Point) + return geometry; + + double tolerance = InternalUtils.calculateToleranceFromGeometry( + spatialReference, geometry, false); + if (gt == Geometry.Type.Envelope) { + Envelope env = (Envelope) geometry; + Envelope2D env2D = new Envelope2D(); + env.queryEnvelope2D(env2D); + if (env2D.isDegenerate(tolerance)) { + return (Geometry) (env.createInstance()); // return empty + // geometry + } + return geometry; + } else if (Geometry.isSegment(gt.value())) { + Segment seg = (Segment) geometry; + Polyline polyline = new Polyline(seg.getDescription()); + polyline.addSegment(seg, true); + return simplifyAsFeature(polyline, spatialReference, bForce, + progressTracker); + } + + double geomTolerance = 0; + int isSimple = ((MultiVertexGeometryImpl) geometry._getImpl()) + .getIsSimple(tolerance); + int knownSimpleResult = bForce ? GeometryXSimple.Unknown : isSimple; + + // TODO: need to distinguish KnownSimple between SimpleAsFeature and + // SimplePlanar. + // From the first sight it seems the SimplePlanar implies + // SimpleAsFeature. + if (knownSimpleResult == GeometryXSimple.Strong) { + if (gt == Geometry.Type.Polygon && ((Polygon) geometry).getFillRule() != Polygon.FillRule.enumFillRuleOddEven) { + Geometry res = geometry.copy(); + ((Polygon) res).setFillRule(Polygon.FillRule.enumFillRuleOddEven);//standardize on odd_even fill rule + return res; + } + + return geometry; + } + + OperatorSimplifyLocalHelper helper = new OperatorSimplifyLocalHelper( + geometry, spatialReference, knownSimpleResult, progressTracker, + false); + + Geometry result; + + if (gt == Geometry.Type.MultiPoint) { + result = (Geometry) (helper.multiPointSimplifyAsFeature_()); + } else if (gt == Geometry.Type.Polyline) { + result = (Geometry) (helper.polylineSimplifyAsFeature_()); + } else if (gt == Geometry.Type.Polygon) { + result = (Geometry) (helper.polygonSimplifyAsFeature_()); + } else { + throw GeometryException.GeometryInternalError(); // what else? + } + + return result; + } + + /** + * Simplifies geometries for storing in OGC format: + *

+ * MultiPoint:check that no points coincide.tolerance is ignored. + * Polyline:ensure there no segments degenerate segments. Polygon:cracks and + * clusters using cluster tolerance and resolves all self intersections, + * orients rings properly and arranges the rings in the OGC order. + *

+ * Returns simplified geometry. + */ + static Geometry simplifyOGC(/* const */Geometry geometry, /* const */ + SpatialReference spatialReference, boolean bForce, + ProgressTracker progressTracker) { + if (geometry.isEmpty()) + return geometry; + Geometry.Type gt = geometry.getType(); + if (gt == Geometry.Type.Point) + return geometry; + + double tolerance = InternalUtils.calculateToleranceFromGeometry( + spatialReference, geometry, false); + if (gt == Geometry.Type.Envelope) { + Envelope env = (Envelope) geometry; + Envelope2D env2D = new Envelope2D(); + env.queryEnvelope2D(env2D); + if (env2D.isDegenerate(tolerance)) { + return (Geometry) (env.createInstance()); // return empty + // geometry + } + return geometry; + } else if (Geometry.isSegment(gt.value())) { + Segment seg = (Segment) geometry; + Polyline polyline = new Polyline(seg.getDescription()); + polyline.addSegment(seg, true); + return simplifyOGC(polyline, spatialReference, bForce, + progressTracker); + } + + if (!Geometry.isMultiVertex(gt.value())) { + throw new GeometryException("OGC simplify is not implemented for this geometry type" + gt); + } + + MultiVertexGeometry result = TopologicalOperations.simplifyOGC( + (MultiVertexGeometry) geometry, tolerance, false, progressTracker); + + return result; + } + + private int compareVertices_(int i1, int i2, boolean get_paths) { + if (i1 == i2) + return 0; + + int pair1 = m_pairs.get(i1); + int pair2 = m_pairs.get(i2); + int xy1 = pair1 >> 1; + int xy2 = pair2 >> 1; + Point2D pt1 = new Point2D(); + Point2D pt2 = new Point2D(); + m_xy.read(2 * xy1, pt1); + pt1.y += (((pair1 & 1) != 0) ? m_toleranceIsSimple + : -m_toleranceIsSimple); + m_xy.read(2 * xy2, pt2); + pt2.y += (((pair2 & 1) != 0) ? m_toleranceIsSimple + : -m_toleranceIsSimple); + int res = pt1.compare(pt2); + if (res == 0 && get_paths) { + int di = m_paths_for_OGC_tests.get(xy1) + - m_paths_for_OGC_tests.get(xy2); + return di < 0 ? -1 : di > 0 ? 1 : 0; + } + return res; + } + + private static final class VertexComparer extends + AttributeStreamOfInt32.IntComparator { + OperatorSimplifyLocalHelper parent; + boolean get_paths; + + VertexComparer(OperatorSimplifyLocalHelper parent_, boolean get_paths_) { + parent = parent_; + get_paths = get_paths_; + } + + @Override + public int compare(int i1, int i2) { + return parent.compareVertices_(i1, i2, get_paths); + } + } + + private static final class IndexSorter extends ClassicSort { + OperatorSimplifyLocalHelper parent; + private boolean get_paths; + private Point2D pt1_dummy = new Point2D(); + + IndexSorter(OperatorSimplifyLocalHelper parent_, boolean get_paths_) { + parent = parent_; + get_paths = get_paths_; + } + + @Override + public void userSort(int begin, int end, AttributeStreamOfInt32 indices) { + indices.Sort(begin, end, new VertexComparer(parent, get_paths)); + } + + @Override + public double getValue(int index) /* const */ { + int pair = parent.m_pairs.get(index); + int xy1 = pair >> 1; + parent.m_xy.read(2 * xy1, pt1_dummy); + double y = pt1_dummy.y + + (((pair & 1) != 0) ? parent.m_toleranceIsSimple + : -parent.m_toleranceIsSimple); + return y; + } + } + + private int compareVerticesMultiPoint_(int i1, int i2) { + if (i1 == i2) + return 0; + MultiVertexGeometryImpl multiVertexImpl = (MultiVertexGeometryImpl) m_geometry + ._getImpl(); + Point2D pt1 = multiVertexImpl.getXY(i1); + Point2D pt2 = multiVertexImpl.getXY(i2); + + if (pt1.x < pt2.x) + return -1; + if (pt1.x > pt2.x) + return 1; + if (pt1.y < pt2.y) + return -1; + if (pt1.y > pt2.y) + return 1; + + for (int attrib = 1; attrib < m_attributeCount; attrib++) { + int semantics = m_description.getSemantics(attrib); + int nords = VertexDescription.getComponentCount(semantics); + for (int ord = 0; ord < nords; ord++) { + double v1 = multiVertexImpl.getAttributeAsDbl(semantics, i1, + ord); + double v2 = multiVertexImpl.getAttributeAsDbl(semantics, i2, + ord); + if (v1 < v2) + return -1; + if (v1 > v2) + return 1; + } + } + + return 0; + } + + private int compareVerticesMultiPoint2_(int i1, int i2) { + int res = compareVerticesMultiPoint_(i1, i2); + if (res == 0) + return i1 < i2 ? -1 : 1; + else + return res; + } + + private static final class EdgeComparerForSelfIntersection implements + Comparator { + OperatorSimplifyLocalHelper parent; + + EdgeComparerForSelfIntersection(OperatorSimplifyLocalHelper parent_) { + parent = parent_; + } + + // Recall that the total ordering [<] induced by compare satisfies e1 + // [<] e2 if and only if compare(e1, e2) < 0. + + @Override + public int compare(Edge e1, Edge e2) { + return parent.edgeAngleCompare_(e1, e2); + } + } + + private static final class MultiPointVertexComparer extends + AttributeStreamOfInt32.IntComparator { + OperatorSimplifyLocalHelper parent; + + MultiPointVertexComparer(OperatorSimplifyLocalHelper parent_) { + parent = parent_; + } + + @Override + public int compare(int i1, int i2) { + return parent.compareVerticesMultiPoint_(i1, i2); + } + } + + private static final class MultiPointVertexComparer2 extends + AttributeStreamOfInt32.IntComparator { + OperatorSimplifyLocalHelper parent; + + MultiPointVertexComparer2(OperatorSimplifyLocalHelper parent_) { + parent = parent_; + } + + @Override + public int compare(int i1, int i2) { + return parent.compareVerticesMultiPoint2_(i1, i2); + } + } + + // compares angles between two edges + private int edgeAngleCompare_(/* const */Edge edge1, /* const */Edge edge2) { + if (edge1.equals(edge2)) + return 0; + + Point2D v1 = edge1.m_segment._getTangent(edge1.getReversed() ? 1.0 + : 0.0); + if (edge1.getReversed()) + v1.negate(); + Point2D v2 = edge2.m_segment._getTangent(edge2.getReversed() ? 1.0 + : 0.0); + if (edge2.getReversed()) + v2.negate(); + + int q1 = v1._getQuarter(); + int q2 = v2._getQuarter(); + + if (q2 == q1) { + double cross = v1.crossProduct(v2); + double crossError = 4 * NumberUtils.doubleEps() + * (Math.abs(v2.x * v1.y) + Math.abs(v2.y * v1.x)); + if (Math.abs(cross) <= crossError) { + cross--; // To avoid warning of "this line has no effect" from + // cross = cross. + cross++; + } + assert (Math.abs(cross) > crossError); + return cross < 0 ? 1 : (cross > 0 ? -1 : 0); + } else { + return q1 < q2 ? -1 : 1; + } + } }; diff --git a/src/main/java/com/esri/core/geometry/OperatorSimplifyLocalOGC.java b/src/main/java/com/esri/core/geometry/OperatorSimplifyLocalOGC.java index 1cdf3acf..f1f367fb 100644 --- a/src/main/java/com/esri/core/geometry/OperatorSimplifyLocalOGC.java +++ b/src/main/java/com/esri/core/geometry/OperatorSimplifyLocalOGC.java @@ -25,30 +25,30 @@ class OperatorSimplifyLocalOGC extends OperatorSimplifyOGC { - @Override - public GeometryCursor execute(GeometryCursor geoms, - SpatialReference spatialRef, boolean bForceSimplify, - ProgressTracker progressTracker) { - return new OperatorSimplifyCursorOGC(geoms, spatialRef, bForceSimplify, - progressTracker); - } - - @Override - public boolean isSimpleOGC(Geometry geom, SpatialReference spatialRef, - boolean bForceTest, NonSimpleResult result, - ProgressTracker progressTracker) { - int res = OperatorSimplifyLocalHelper.isSimpleOGC(geom, spatialRef, - bForceTest, result, progressTracker); - return res > 0; - } - - @Override - public Geometry execute(Geometry geom, SpatialReference spatialRef, - boolean bForceSimplify, ProgressTracker progressTracker) { - SimpleGeometryCursor inputCursor = new SimpleGeometryCursor(geom); - GeometryCursor outputCursor = execute(inputCursor, spatialRef, - bForceSimplify, progressTracker); - - return outputCursor.next(); - } + @Override + public GeometryCursor execute(GeometryCursor geoms, + SpatialReference spatialRef, boolean bForceSimplify, + ProgressTracker progressTracker) { + return new OperatorSimplifyCursorOGC(geoms, spatialRef, bForceSimplify, + progressTracker); + } + + @Override + public boolean isSimpleOGC(Geometry geom, SpatialReference spatialRef, + boolean bForceTest, NonSimpleResult result, + ProgressTracker progressTracker) { + int res = OperatorSimplifyLocalHelper.isSimpleOGC(geom, spatialRef, + bForceTest, result, progressTracker); + return res > 0; + } + + @Override + public Geometry execute(Geometry geom, SpatialReference spatialRef, + boolean bForceSimplify, ProgressTracker progressTracker) { + SimpleGeometryCursor inputCursor = new SimpleGeometryCursor(geom); + GeometryCursor outputCursor = execute(inputCursor, spatialRef, + bForceSimplify, progressTracker); + + return outputCursor.next(); + } } diff --git a/src/main/java/com/esri/core/geometry/OperatorSimplifyOGC.java b/src/main/java/com/esri/core/geometry/OperatorSimplifyOGC.java index 140a9259..478ef745 100644 --- a/src/main/java/com/esri/core/geometry/OperatorSimplifyOGC.java +++ b/src/main/java/com/esri/core/geometry/OperatorSimplifyOGC.java @@ -29,63 +29,63 @@ * Uses tolerance to determine equal vertices or points of intersection. */ public abstract class OperatorSimplifyOGC extends Operator { - @Override - public Operator.Type getType() { - return Operator.Type.SimplifyOGC; - } + @Override + public Operator.Type getType() { + return Operator.Type.SimplifyOGC; + } - /** - * Tests if the Geometry is simple for OGC specification. - * - * @param geom The Geometry to be tested. - * @param spatialRef Spatial reference to obtain the tolerance from. When null, the tolerance - * will be derived individually from geometry bounds. - * @param bForceTest When True, the Geometry will be tested regardless of the IsKnownSimple flag. - * @param progressTracker Allows cancellation of a long operation. Can be null. - *

- * Note: As other methods in the OperatorSimplifyOGC, this method uses tolerance from the spatial reference. - * Points that are within the tolerance are considered equal. - *

- * When this method returns true, the OperatorSimplify.isSimpleAsFeature will return true also (this does not necessary happen the other way around). - */ - public abstract boolean isSimpleOGC(Geometry geom, - SpatialReference spatialRef, boolean bForceTest, - NonSimpleResult result, ProgressTracker progressTracker); + /** + * Tests if the Geometry is simple for OGC specification. + * + * @param geom The Geometry to be tested. + * @param spatialRef Spatial reference to obtain the tolerance from. When null, the tolerance + * will be derived individually from geometry bounds. + * @param bForceTest When True, the Geometry will be tested regardless of the IsKnownSimple flag. + * @param progressTracker Allows cancellation of a long operation. Can be null. + *

+ * Note: As other methods in the OperatorSimplifyOGC, this method uses tolerance from the spatial reference. + * Points that are within the tolerance are considered equal. + *

+ * When this method returns true, the OperatorSimplify.isSimpleAsFeature will return true also (this does not necessary happen the other way around). + */ + public abstract boolean isSimpleOGC(Geometry geom, + SpatialReference spatialRef, boolean bForceTest, + NonSimpleResult result, ProgressTracker progressTracker); - /** - * Processes geometry cursor to ensure its geometries are simple for OGC specification. - * - * @param geoms Geometries to be simplified. - * @param sr Spatial reference to obtain the tolerance from. When null, the tolerance - * will be derived individually for each geometry from its bounds. - * @param bForceSimplify When True, the Geometry will be simplified regardless of the internal IsKnownSimple flag. - * @param progressTracker Allows cancellation of a long operation. Can be null. - * @return Returns a GeometryCursor of simplified geometries. - *

- * The isSimpleOGC returns true after this call. - */ - public abstract GeometryCursor execute(GeometryCursor geoms, - SpatialReference sr, boolean bForceSimplify, - ProgressTracker progressTracker); + /** + * Processes geometry cursor to ensure its geometries are simple for OGC specification. + * + * @param geoms Geometries to be simplified. + * @param sr Spatial reference to obtain the tolerance from. When null, the tolerance + * will be derived individually for each geometry from its bounds. + * @param bForceSimplify When True, the Geometry will be simplified regardless of the internal IsKnownSimple flag. + * @param progressTracker Allows cancellation of a long operation. Can be null. + * @return Returns a GeometryCursor of simplified geometries. + *

+ * The isSimpleOGC returns true after this call. + */ + public abstract GeometryCursor execute(GeometryCursor geoms, + SpatialReference sr, boolean bForceSimplify, + ProgressTracker progressTracker); - /** - * Processes geometry to ensure it is simple for OGC specification. - * - * @param geom The geometry to be simplified. - * @param sr Spatial reference to obtain the tolerance from. When null, the tolerance - * will be derived individually from geometry bounds. - * @param bForceSimplify When True, the Geometry will be simplified regardless of the internal IsKnownSimple flag. - * @param progressTracker Allows cancellation of a long operation. Can be null. - * @return Returns a simple Geometry that should be visually equivalent to the input geometry. - *

- * The isSimpleOGC returns true after this call. - */ - public abstract Geometry execute(Geometry geom, SpatialReference sr, - boolean bForceSimplify, ProgressTracker progressTracker); + /** + * Processes geometry to ensure it is simple for OGC specification. + * + * @param geom The geometry to be simplified. + * @param sr Spatial reference to obtain the tolerance from. When null, the tolerance + * will be derived individually from geometry bounds. + * @param bForceSimplify When True, the Geometry will be simplified regardless of the internal IsKnownSimple flag. + * @param progressTracker Allows cancellation of a long operation. Can be null. + * @return Returns a simple Geometry that should be visually equivalent to the input geometry. + *

+ * The isSimpleOGC returns true after this call. + */ + public abstract Geometry execute(Geometry geom, SpatialReference sr, + boolean bForceSimplify, ProgressTracker progressTracker); - public static OperatorSimplifyOGC local() { - return (OperatorSimplifyOGC) OperatorFactoryLocal.getInstance() - .getOperator(Type.SimplifyOGC); - } + public static OperatorSimplifyOGC local() { + return (OperatorSimplifyOGC) OperatorFactoryLocal.getInstance() + .getOperator(Type.SimplifyOGC); + } } diff --git a/src/main/java/com/esri/core/geometry/OperatorSymmetricDifference.java b/src/main/java/com/esri/core/geometry/OperatorSymmetricDifference.java index f31b7178..f8a17117 100644 --- a/src/main/java/com/esri/core/geometry/OperatorSymmetricDifference.java +++ b/src/main/java/com/esri/core/geometry/OperatorSymmetricDifference.java @@ -29,38 +29,38 @@ * Symmetric difference (XOR) operation between geometries. */ public abstract class OperatorSymmetricDifference extends Operator implements CombineOperator { - @Override - public Type getType() { - return Type.Difference; - } + @Override + public Type getType() { + return Type.Difference; + } - /** - * Performs the Symmetric Difference operation on the geometry set. - * - * @param inputGeometries is the set of Geometry instances to be XOR'd by rightGeometry. - * @param rightGeometry is the Geometry being XOR'd with the inputGeometies. - * @return Returns the result of the symmetric difference. - *

- * The operator XOR's every geometry in inputGeometries with rightGeometry. - */ - public abstract GeometryCursor execute(GeometryCursor inputGeometries, - GeometryCursor rightGeometry, SpatialReference sr, - ProgressTracker progressTracker); + /** + * Performs the Symmetric Difference operation on the geometry set. + * + * @param inputGeometries is the set of Geometry instances to be XOR'd by rightGeometry. + * @param rightGeometry is the Geometry being XOR'd with the inputGeometies. + * @return Returns the result of the symmetric difference. + *

+ * The operator XOR's every geometry in inputGeometries with rightGeometry. + */ + public abstract GeometryCursor execute(GeometryCursor inputGeometries, + GeometryCursor rightGeometry, SpatialReference sr, + ProgressTracker progressTracker); - /** - * Performs the Symmetric Difference operation on the two geometries. - * - * @param leftGeometry is one of the Geometry instances in the XOR operation. - * @param rightGeometry is one of the Geometry instances in the XOR operation. - * @return Returns the result of the symmetric difference. - */ - public abstract Geometry execute(Geometry leftGeometry, - Geometry rightGeometry, SpatialReference sr, - ProgressTracker progressTracker); + /** + * Performs the Symmetric Difference operation on the two geometries. + * + * @param leftGeometry is one of the Geometry instances in the XOR operation. + * @param rightGeometry is one of the Geometry instances in the XOR operation. + * @return Returns the result of the symmetric difference. + */ + public abstract Geometry execute(Geometry leftGeometry, + Geometry rightGeometry, SpatialReference sr, + ProgressTracker progressTracker); - public static OperatorSymmetricDifference local() { - return (OperatorSymmetricDifference) OperatorFactoryLocal.getInstance() - .getOperator(Type.SymmetricDifference); - } + public static OperatorSymmetricDifference local() { + return (OperatorSymmetricDifference) OperatorFactoryLocal.getInstance() + .getOperator(Type.SymmetricDifference); + } } diff --git a/src/main/java/com/esri/core/geometry/OperatorSymmetricDifferenceCursor.java b/src/main/java/com/esri/core/geometry/OperatorSymmetricDifferenceCursor.java index 647ca21f..302648db 100644 --- a/src/main/java/com/esri/core/geometry/OperatorSymmetricDifferenceCursor.java +++ b/src/main/java/com/esri/core/geometry/OperatorSymmetricDifferenceCursor.java @@ -24,34 +24,34 @@ package com.esri.core.geometry; public class OperatorSymmetricDifferenceCursor extends GeometryCursor { - ProgressTracker m_progress_tracker; - SpatialReference m_spatial_reference; - Geometry m_rightGeom; - boolean m_bEmpty; - - OperatorSymmetricDifferenceCursor(GeometryCursor inputGeoms, - GeometryCursor rightGeom, SpatialReference sr, - ProgressTracker progress_tracker) { - m_bEmpty = rightGeom == null; - m_inputGeoms = inputGeoms; - m_spatial_reference = sr; - m_rightGeom = rightGeom.next(); - m_progress_tracker = progress_tracker; - } - - @Override - public Geometry next() { - if (m_bEmpty) - return null; - - if (hasNext()) { - return OperatorSymmetricDifferenceLocal.symmetricDifference( - m_inputGeoms.next(), - m_rightGeom, - m_spatial_reference, - m_progress_tracker); - } - - return null; - } + ProgressTracker m_progress_tracker; + SpatialReference m_spatial_reference; + Geometry m_rightGeom; + boolean m_bEmpty; + + OperatorSymmetricDifferenceCursor(GeometryCursor inputGeoms, + GeometryCursor rightGeom, SpatialReference sr, + ProgressTracker progress_tracker) { + m_bEmpty = rightGeom == null; + m_inputGeoms = inputGeoms; + m_spatial_reference = sr; + m_rightGeom = rightGeom.next(); + m_progress_tracker = progress_tracker; + } + + @Override + public Geometry next() { + if (m_bEmpty) + return null; + + if (hasNext()) { + return OperatorSymmetricDifferenceLocal.symmetricDifference( + m_inputGeoms.next(), + m_rightGeom, + m_spatial_reference, + m_progress_tracker); + } + + return null; + } } diff --git a/src/main/java/com/esri/core/geometry/OperatorSymmetricDifferenceLocal.java b/src/main/java/com/esri/core/geometry/OperatorSymmetricDifferenceLocal.java index 0fb12613..0a6a0374 100644 --- a/src/main/java/com/esri/core/geometry/OperatorSymmetricDifferenceLocal.java +++ b/src/main/java/com/esri/core/geometry/OperatorSymmetricDifferenceLocal.java @@ -25,144 +25,144 @@ class OperatorSymmetricDifferenceLocal extends OperatorSymmetricDifference { - @Override - public GeometryCursor execute(GeometryCursor inputGeometries, - GeometryCursor rightGeometry, SpatialReference sr, - ProgressTracker progressTracker) { - return new OperatorSymmetricDifferenceCursor(inputGeometries, - rightGeometry, sr, progressTracker); - } - - @Override - public Geometry execute(Geometry leftGeometry, Geometry rightGeometry, - SpatialReference sr, ProgressTracker progressTracker) { - SimpleGeometryCursor leftGeomCurs = new SimpleGeometryCursor( - leftGeometry); - SimpleGeometryCursor rightGeomCurs = new SimpleGeometryCursor( - rightGeometry); - GeometryCursor geometryCursor = execute(leftGeomCurs, rightGeomCurs, - sr, progressTracker); - return geometryCursor.next(); - } - - static Geometry symmetricDifference(Geometry geometry_a, - Geometry geometry_b, SpatialReference spatial_reference, - ProgressTracker progress_tracker) { - int dim_a = geometry_a.getDimension(); - int dim_b = geometry_b.getDimension(); - - if (geometry_a.isEmpty() && geometry_b.isEmpty()) - return dim_a > dim_b ? geometry_a : geometry_b; - - if (geometry_a.isEmpty()) - return geometry_b; - if (geometry_b.isEmpty()) - return geometry_a; - - Envelope2D env_a = new Envelope2D(), env_b = new Envelope2D(), env_merged = new Envelope2D(); - geometry_a.queryEnvelope2D(env_a); - geometry_b.queryEnvelope2D(env_b); - env_merged.setCoords(env_a); - env_merged.merge(env_b); - - double tolerance = InternalUtils.calculateToleranceFromGeometry( - spatial_reference, env_merged, false); - - int type_a = geometry_a.getType().value(); - int type_b = geometry_b.getType().value(); - - if (type_a == Geometry.GeometryType.Point - && type_b == Geometry.GeometryType.Point) - return pointSymDiffPoint_((Point) (geometry_a), - (Point) (geometry_b), tolerance, progress_tracker); - - if (type_a != type_b) { - if (dim_a > 0 || dim_b > 0) - return dim_a > dim_b ? geometry_a : geometry_b; - - // Multi_point/Point case - - if (type_a == Geometry.GeometryType.MultiPoint) - return multiPointSymDiffPoint_((MultiPoint) (geometry_a), - (Point) (geometry_b), tolerance, progress_tracker); - - return multiPointSymDiffPoint_((MultiPoint) (geometry_b), - (Point) (geometry_a), tolerance, progress_tracker); - } - - return TopologicalOperations.symmetricDifference(geometry_a, - geometry_b, spatial_reference, progress_tracker); - } - - static Geometry pointSymDiffPoint_(Point point_a, Point point_b, - double tolerance, ProgressTracker progress_tracker) { - double tolerance_cluster = tolerance * Math.sqrt(2.0) * 1.00001; - double tolerance_cluster_sq = tolerance_cluster * tolerance_cluster; - - Point2D pt_a = point_a.getXY(); - Point2D pt_b = point_b.getXY(); - - MultiPoint multi_point = new MultiPoint(point_a.getDescription()); - - if (Point2D.sqrDistance(pt_a, pt_b) > tolerance_cluster_sq) { - multi_point.add(point_a); - multi_point.add(point_b); - } - - return multi_point; - } - - static Geometry multiPointSymDiffPoint_(MultiPoint multi_point, - Point point, double tolerance, ProgressTracker progress_tracker) { - MultiPointImpl multipointImpl = (MultiPointImpl) (multi_point - ._getImpl()); - AttributeStreamOfDbl position = (AttributeStreamOfDbl) (multipointImpl - .getAttributeStreamRef(VertexDescription.Semantics.POSITION)); - int point_count = multi_point.getPointCount(); - Point2D point2D = point.getXY(); - - MultiPoint new_multipoint = (MultiPoint) (multi_point.createInstance()); - double tolerance_cluster = tolerance * Math.sqrt(2.0) * 1.00001; - - Envelope2D env = new Envelope2D(); - multi_point.queryEnvelope2D(env); - env.inflate(tolerance_cluster, tolerance_cluster); - - if (env.contains(point2D)) { - double tolerance_cluster_sq = tolerance_cluster * tolerance_cluster; - - boolean b_found_covered = false; - boolean[] covered = new boolean[point_count]; - for (int i = 0; i < point_count; i++) - covered[i] = false; - - for (int i = 0; i < point_count; i++) { - double x = position.read(2 * i); - double y = position.read(2 * i + 1); - - double dx = x - point2D.x; - double dy = y - point2D.y; - - if (dx * dx + dy * dy <= tolerance_cluster_sq) { - b_found_covered = true; - covered[i] = true; - } - } - - if (!b_found_covered) { - new_multipoint.add(multi_point, 0, point_count); - new_multipoint.add(point); - } else { - for (int i = 0; i < point_count; i++) { - if (!covered[i]) - new_multipoint.add(multi_point, i, i + 1); - } - } - } else { - new_multipoint.add(multi_point, 0, point_count); - new_multipoint.add(point); - } - - return new_multipoint; - } + @Override + public GeometryCursor execute(GeometryCursor inputGeometries, + GeometryCursor rightGeometry, SpatialReference sr, + ProgressTracker progressTracker) { + return new OperatorSymmetricDifferenceCursor(inputGeometries, + rightGeometry, sr, progressTracker); + } + + @Override + public Geometry execute(Geometry leftGeometry, Geometry rightGeometry, + SpatialReference sr, ProgressTracker progressTracker) { + SimpleGeometryCursor leftGeomCurs = new SimpleGeometryCursor( + leftGeometry); + SimpleGeometryCursor rightGeomCurs = new SimpleGeometryCursor( + rightGeometry); + GeometryCursor geometryCursor = execute(leftGeomCurs, rightGeomCurs, + sr, progressTracker); + return geometryCursor.next(); + } + + static Geometry symmetricDifference(Geometry geometry_a, + Geometry geometry_b, SpatialReference spatial_reference, + ProgressTracker progress_tracker) { + int dim_a = geometry_a.getDimension(); + int dim_b = geometry_b.getDimension(); + + if (geometry_a.isEmpty() && geometry_b.isEmpty()) + return dim_a > dim_b ? geometry_a : geometry_b; + + if (geometry_a.isEmpty()) + return geometry_b; + if (geometry_b.isEmpty()) + return geometry_a; + + Envelope2D env_a = new Envelope2D(), env_b = new Envelope2D(), env_merged = new Envelope2D(); + geometry_a.queryEnvelope2D(env_a); + geometry_b.queryEnvelope2D(env_b); + env_merged.setCoords(env_a); + env_merged.merge(env_b); + + double tolerance = InternalUtils.calculateToleranceFromGeometry( + spatial_reference, env_merged, false); + + int type_a = geometry_a.getType().value(); + int type_b = geometry_b.getType().value(); + + if (type_a == Geometry.GeometryType.Point + && type_b == Geometry.GeometryType.Point) + return pointSymDiffPoint_((Point) (geometry_a), + (Point) (geometry_b), tolerance, progress_tracker); + + if (type_a != type_b) { + if (dim_a > 0 || dim_b > 0) + return dim_a > dim_b ? geometry_a : geometry_b; + + // Multi_point/Point case + + if (type_a == Geometry.GeometryType.MultiPoint) + return multiPointSymDiffPoint_((MultiPoint) (geometry_a), + (Point) (geometry_b), tolerance, progress_tracker); + + return multiPointSymDiffPoint_((MultiPoint) (geometry_b), + (Point) (geometry_a), tolerance, progress_tracker); + } + + return TopologicalOperations.symmetricDifference(geometry_a, + geometry_b, spatial_reference, progress_tracker); + } + + static Geometry pointSymDiffPoint_(Point point_a, Point point_b, + double tolerance, ProgressTracker progress_tracker) { + double tolerance_cluster = tolerance * Math.sqrt(2.0) * 1.00001; + double tolerance_cluster_sq = tolerance_cluster * tolerance_cluster; + + Point2D pt_a = point_a.getXY(); + Point2D pt_b = point_b.getXY(); + + MultiPoint multi_point = new MultiPoint(point_a.getDescription()); + + if (Point2D.sqrDistance(pt_a, pt_b) > tolerance_cluster_sq) { + multi_point.add(point_a); + multi_point.add(point_b); + } + + return multi_point; + } + + static Geometry multiPointSymDiffPoint_(MultiPoint multi_point, + Point point, double tolerance, ProgressTracker progress_tracker) { + MultiPointImpl multipointImpl = (MultiPointImpl) (multi_point + ._getImpl()); + AttributeStreamOfDbl position = (AttributeStreamOfDbl) (multipointImpl + .getAttributeStreamRef(VertexDescription.Semantics.POSITION)); + int point_count = multi_point.getPointCount(); + Point2D point2D = point.getXY(); + + MultiPoint new_multipoint = (MultiPoint) (multi_point.createInstance()); + double tolerance_cluster = tolerance * Math.sqrt(2.0) * 1.00001; + + Envelope2D env = new Envelope2D(); + multi_point.queryEnvelope2D(env); + env.inflate(tolerance_cluster, tolerance_cluster); + + if (env.contains(point2D)) { + double tolerance_cluster_sq = tolerance_cluster * tolerance_cluster; + + boolean b_found_covered = false; + boolean[] covered = new boolean[point_count]; + for (int i = 0; i < point_count; i++) + covered[i] = false; + + for (int i = 0; i < point_count; i++) { + double x = position.read(2 * i); + double y = position.read(2 * i + 1); + + double dx = x - point2D.x; + double dy = y - point2D.y; + + if (dx * dx + dy * dy <= tolerance_cluster_sq) { + b_found_covered = true; + covered[i] = true; + } + } + + if (!b_found_covered) { + new_multipoint.add(multi_point, 0, point_count); + new_multipoint.add(point); + } else { + for (int i = 0; i < point_count; i++) { + if (!covered[i]) + new_multipoint.add(multi_point, i, i + 1); + } + } + } else { + new_multipoint.add(multi_point, 0, point_count); + new_multipoint.add(point); + } + + return new_multipoint; + } } diff --git a/src/main/java/com/esri/core/geometry/OperatorTouches.java b/src/main/java/com/esri/core/geometry/OperatorTouches.java index a4c07d81..172f4f28 100644 --- a/src/main/java/com/esri/core/geometry/OperatorTouches.java +++ b/src/main/java/com/esri/core/geometry/OperatorTouches.java @@ -30,14 +30,14 @@ * Relational operation Touches. */ public abstract class OperatorTouches extends OperatorSimpleRelation { - @Override - public Type getType() { - return Type.Touches; - } - - public static OperatorTouches local() { - return (OperatorTouches) OperatorFactoryLocal.getInstance() - .getOperator(Type.Touches); - } + @Override + public Type getType() { + return Type.Touches; + } + + public static OperatorTouches local() { + return (OperatorTouches) OperatorFactoryLocal.getInstance() + .getOperator(Type.Touches); + } } diff --git a/src/main/java/com/esri/core/geometry/OperatorTouchesLocal.java b/src/main/java/com/esri/core/geometry/OperatorTouchesLocal.java index ac5b852d..2e2227f0 100644 --- a/src/main/java/com/esri/core/geometry/OperatorTouchesLocal.java +++ b/src/main/java/com/esri/core/geometry/OperatorTouchesLocal.java @@ -25,11 +25,11 @@ package com.esri.core.geometry; class OperatorTouchesLocal extends OperatorTouches { - @Override - public boolean execute(Geometry inputGeom1, Geometry inputGeom2, - SpatialReference sr, ProgressTracker progressTracker) { - return RelationalOperations.relate(inputGeom1, inputGeom2, sr, - RelationalOperations.Relation.touches, progressTracker); - } + @Override + public boolean execute(Geometry inputGeom1, Geometry inputGeom2, + SpatialReference sr, ProgressTracker progressTracker) { + return RelationalOperations.relate(inputGeom1, inputGeom2, sr, + RelationalOperations.Relation.touches, progressTracker); + } } diff --git a/src/main/java/com/esri/core/geometry/OperatorUnion.java b/src/main/java/com/esri/core/geometry/OperatorUnion.java index d83c6887..4de6a2cf 100644 --- a/src/main/java/com/esri/core/geometry/OperatorUnion.java +++ b/src/main/java/com/esri/core/geometry/OperatorUnion.java @@ -30,30 +30,30 @@ * Union of geometries. */ public abstract class OperatorUnion extends Operator implements CombineOperator { - @Override - public Type getType() { - return Type.Union; - } - - /** - * Performs the Topological Union operation on the geometry set. - * - * @param inputGeometries is the set of Geometry instances to be unioned. - */ - public abstract GeometryCursor execute(GeometryCursor inputGeometries, - SpatialReference sr, ProgressTracker progressTracker); - - /** - * Performs the Topological Union operation on two geometries. - * - * @param geom1 and geom2 are the geometry instances to be unioned. - */ - public abstract Geometry execute(Geometry geom1, Geometry geom2, - SpatialReference sr, ProgressTracker progressTracker); - - public static OperatorUnion local() { - return (OperatorUnion) OperatorFactoryLocal.getInstance().getOperator( - Type.Union); - } + @Override + public Type getType() { + return Type.Union; + } + + /** + * Performs the Topological Union operation on the geometry set. + * + * @param inputGeometries is the set of Geometry instances to be unioned. + */ + public abstract GeometryCursor execute(GeometryCursor inputGeometries, + SpatialReference sr, ProgressTracker progressTracker); + + /** + * Performs the Topological Union operation on two geometries. + * + * @param geom1 and geom2 are the geometry instances to be unioned. + */ + public abstract Geometry execute(Geometry geom1, Geometry geom2, + SpatialReference sr, ProgressTracker progressTracker); + + public static OperatorUnion local() { + return (OperatorUnion) OperatorFactoryLocal.getInstance().getOperator( + Type.Union); + } } diff --git a/src/main/java/com/esri/core/geometry/OperatorUnionCursor.java b/src/main/java/com/esri/core/geometry/OperatorUnionCursor.java index f18fa633..dfc66bcc 100644 --- a/src/main/java/com/esri/core/geometry/OperatorUnionCursor.java +++ b/src/main/java/com/esri/core/geometry/OperatorUnionCursor.java @@ -30,264 +30,264 @@ import java.util.TreeMap; public final class OperatorUnionCursor extends GeometryCursor { - private ProgressTracker m_progress_tracker; - private SpatialReferenceImpl m_spatial_reference; - private boolean m_b_done = false; - private boolean[] m_had_geometry = new boolean[4]; - private int[] m_dim_geom_counts = new int[4]; - private boolean m_b_union_all_dimensions = false; - private int m_max_dimension = -1; - private int m_added_geoms = 0; - private int m_current_dim = -1; - - private final static class Geom_pair { - void init() { - geom = null; - vertex_count = -1; - unioned = false; - } - - Geometry geom; - int vertex_count; - boolean unioned;//true if geometry is a result of union operation - } - - final static class Bin_type //bin array and the total vertex count in the bin - { - int bin_vertex_count = 0; - ArrayList geometries = new ArrayList(); - - void add_pair(Geom_pair geom) { - bin_vertex_count += geom.vertex_count; - geometries.add(geom); - } - - void pop_pair() { - bin_vertex_count -= geometries.get(geometries.size() - 1).vertex_count; - geometries.remove(geometries.size() - 1); - } - - Geom_pair back_pair() { - return geometries.get(geometries.size() - 1); - } - - int geom_count() { - return geometries.size(); - } - } - - ArrayList> m_union_bins = new ArrayList>();//for each dimension there is a list of bins sorted by level - - OperatorUnionCursor(GeometryCursor inputGeoms, SpatialReference sr, - ProgressTracker progress_tracker) { - m_inputGeoms = inputGeoms; - m_spatial_reference = (SpatialReferenceImpl) (sr); - m_progress_tracker = progress_tracker; - } - - private Geometry get_result_geometry(int dim) { - assert (m_dim_geom_counts[dim] > 0); - java.util.TreeMap map = m_union_bins.get(dim); - Map.Entry e = map.firstEntry(); - Bin_type bin = e.getValue(); - - Geometry resG; - resG = bin.back_pair().geom; - boolean unioned = bin.back_pair().unioned; - map.remove(e.getKey()); - - if (unioned) { - resG = OperatorSimplify.local().execute(resG, m_spatial_reference, - false, m_progress_tracker); - if (dim == 0 && resG.getType() == Geometry.Type.Point) {// must - // return - // multipoint - // for - // points - MultiPoint mp = new MultiPoint(resG.getDescription()); - if (!resG.isEmpty()) - mp.add((Point) resG); - - resG = mp; - } - } - - return resG; - } - - @Override - public Geometry next() { - if (m_b_done && m_current_dim == m_max_dimension) - return null; - - while (!step_()) { - } - - if (m_max_dimension == -1) - return null;// empty input cursor - - if (m_b_union_all_dimensions) { - m_current_dim++; - while (true) { - if (m_current_dim > m_max_dimension || m_current_dim < 0) - throw GeometryException.GeometryInternalError(); - - if (m_had_geometry[m_current_dim]) - break; - } - - return get_result_geometry(m_current_dim); - } else { - assert (m_max_dimension >= 0); - m_current_dim = m_max_dimension; - return get_result_geometry(m_max_dimension); - } - } - - private boolean step_() { - if (m_b_done) - return true; - - Geometry geom = null; - if (m_inputGeoms != null) { - geom = m_inputGeoms.next(); - if (geom == null) { - m_b_done = true; - } - } - - ProgressTracker.checkAndThrow(m_progress_tracker); - - if (geom != null) { - int dim = geom.getDimension(); - m_had_geometry[dim] = true; - if (dim >= m_max_dimension && !m_b_union_all_dimensions) { - add_geom(dim, false, geom); - if (dim > m_max_dimension && !m_b_union_all_dimensions) { - //this geometry has higher dimension than the previously processed one - //Therefore we delete all lower dimensions (unless m_b_union_all_dimensions is true). - remove_all_bins_with_lower_dimension(dim); - } - } else { - //this geometry is skipped - } - } else { - //geom is null. do nothing - } - - if (m_added_geoms > 0) { - for (int dim = 0; dim <= m_max_dimension; dim++) { - while (m_dim_geom_counts[dim] > 1) { - ArrayList batch_to_union = collect_geometries_to_union(dim); - boolean serial_execution = true; - if (serial_execution) { - if (batch_to_union.size() != 0) { - Geometry geomRes = TopologicalOperations - .dissolveDirty(batch_to_union, - m_spatial_reference, - m_progress_tracker); - add_geom(dim, true, geomRes); - } else { - break; - } - } - } - } - } - - return m_b_done; - } - - ArrayList collect_geometries_to_union(int dim) { - ArrayList batch_to_union = new ArrayList(); - ArrayList> entriesToRemove = new ArrayList>(); - Set> set = m_union_bins.get(dim) - .entrySet(); - for (Map.Entry e : set) { - //int level = e.getKey(); - Bin_type bin = e.getValue(); - - final int binVertexThreshold = 10000; - - if (m_b_done - || (bin.bin_vertex_count > binVertexThreshold && bin - .geom_count() > 1)) { - m_dim_geom_counts[dim] -= bin.geom_count(); - m_added_geoms -= bin.geom_count(); - while (bin.geometries.size() > 0) { - // empty geometries will be unioned too. - batch_to_union.add(bin.back_pair().geom); - bin.pop_pair(); - } - - entriesToRemove.add(e); - } - } - - set.removeAll(entriesToRemove); - return batch_to_union; - } - - private void remove_all_bins_with_lower_dimension(int dim) { - // this geometry has higher dimension than the previously processed one - for (int i = 0; i < dim; i++) { - m_union_bins.get(i).clear(); - m_added_geoms -= m_dim_geom_counts[i]; - m_dim_geom_counts[i] = 0; - } - } - - private void add_geom(int dimension, boolean unioned, Geometry geom) { - Geom_pair pair = new Geom_pair(); - pair.init(); - pair.geom = geom; - int sz = get_vertex_count_(geom); - pair.vertex_count = sz; - int level = get_level_(sz); - if (dimension + 1 > (int) m_union_bins.size()) { - for (int i = 0, n = Math.max(2, dimension + 1); i < n; i++) { - m_union_bins.add(new TreeMap()); - } - } - - Bin_type bin = m_union_bins.get(dimension).get(level);//return null if level is abscent - if (bin == null) { - bin = new Bin_type(); - m_union_bins.get(dimension).put(level, bin); - } - - pair.unioned = unioned; - bin.add_pair(pair); - - // Update global cursor state - m_dim_geom_counts[dimension]++; - m_added_geoms++; - m_max_dimension = Math.max(m_max_dimension, dimension); - } - - private static int get_level_(int sz) {// calculates logarithm of sz to base - // 4. - return sz > 0 ? (int) (Math.log((double) sz) / Math.log(4.0) + 0.5) - : (int) 0; - } - - private static int get_vertex_count_(Geometry geom) { - int gt = geom.getType().value(); - if (Geometry.isMultiVertex(gt)) { - return ((MultiVertexGeometry) geom).getPointCount(); - } else if (gt == Geometry.GeometryType.Point) { - return 1; - } else if (gt == Geometry.GeometryType.Envelope) { - return 4; - } else if (Geometry.isSegment(gt)) { - return 2; - } else { - throw GeometryException.GeometryInternalError(); - } - } - - @Override - public boolean tock() { - return step_(); - } + private ProgressTracker m_progress_tracker; + private SpatialReferenceImpl m_spatial_reference; + private boolean m_b_done = false; + private boolean[] m_had_geometry = new boolean[4]; + private int[] m_dim_geom_counts = new int[4]; + private boolean m_b_union_all_dimensions = false; + private int m_max_dimension = -1; + private int m_added_geoms = 0; + private int m_current_dim = -1; + + private final static class Geom_pair { + void init() { + geom = null; + vertex_count = -1; + unioned = false; + } + + Geometry geom; + int vertex_count; + boolean unioned;//true if geometry is a result of union operation + } + + final static class Bin_type //bin array and the total vertex count in the bin + { + int bin_vertex_count = 0; + ArrayList geometries = new ArrayList(); + + void add_pair(Geom_pair geom) { + bin_vertex_count += geom.vertex_count; + geometries.add(geom); + } + + void pop_pair() { + bin_vertex_count -= geometries.get(geometries.size() - 1).vertex_count; + geometries.remove(geometries.size() - 1); + } + + Geom_pair back_pair() { + return geometries.get(geometries.size() - 1); + } + + int geom_count() { + return geometries.size(); + } + } + + ArrayList> m_union_bins = new ArrayList>();//for each dimension there is a list of bins sorted by level + + OperatorUnionCursor(GeometryCursor inputGeoms, SpatialReference sr, + ProgressTracker progress_tracker) { + m_inputGeoms = inputGeoms; + m_spatial_reference = (SpatialReferenceImpl) (sr); + m_progress_tracker = progress_tracker; + } + + private Geometry get_result_geometry(int dim) { + assert (m_dim_geom_counts[dim] > 0); + java.util.TreeMap map = m_union_bins.get(dim); + Map.Entry e = map.firstEntry(); + Bin_type bin = e.getValue(); + + Geometry resG; + resG = bin.back_pair().geom; + boolean unioned = bin.back_pair().unioned; + map.remove(e.getKey()); + + if (unioned) { + resG = OperatorSimplify.local().execute(resG, m_spatial_reference, + false, m_progress_tracker); + if (dim == 0 && resG.getType() == Geometry.Type.Point) {// must + // return + // multipoint + // for + // points + MultiPoint mp = new MultiPoint(resG.getDescription()); + if (!resG.isEmpty()) + mp.add((Point) resG); + + resG = mp; + } + } + + return resG; + } + + @Override + public Geometry next() { + if (m_b_done && m_current_dim == m_max_dimension) + return null; + + while (!step_()) { + } + + if (m_max_dimension == -1) + return null;// empty input cursor + + if (m_b_union_all_dimensions) { + m_current_dim++; + while (true) { + if (m_current_dim > m_max_dimension || m_current_dim < 0) + throw GeometryException.GeometryInternalError(); + + if (m_had_geometry[m_current_dim]) + break; + } + + return get_result_geometry(m_current_dim); + } else { + assert (m_max_dimension >= 0); + m_current_dim = m_max_dimension; + return get_result_geometry(m_max_dimension); + } + } + + private boolean step_() { + if (m_b_done) + return true; + + Geometry geom = null; + if (m_inputGeoms != null) { + geom = m_inputGeoms.next(); + if (geom == null) { + m_b_done = true; + } + } + + ProgressTracker.checkAndThrow(m_progress_tracker); + + if (geom != null) { + int dim = geom.getDimension(); + m_had_geometry[dim] = true; + if (dim >= m_max_dimension && !m_b_union_all_dimensions) { + add_geom(dim, false, geom); + if (dim > m_max_dimension && !m_b_union_all_dimensions) { + //this geometry has higher dimension than the previously processed one + //Therefore we delete all lower dimensions (unless m_b_union_all_dimensions is true). + remove_all_bins_with_lower_dimension(dim); + } + } else { + //this geometry is skipped + } + } else { + //geom is null. do nothing + } + + if (m_added_geoms > 0) { + for (int dim = 0; dim <= m_max_dimension; dim++) { + while (m_dim_geom_counts[dim] > 1) { + ArrayList batch_to_union = collect_geometries_to_union(dim); + boolean serial_execution = true; + if (serial_execution) { + if (batch_to_union.size() != 0) { + Geometry geomRes = TopologicalOperations + .dissolveDirty(batch_to_union, + m_spatial_reference, + m_progress_tracker); + add_geom(dim, true, geomRes); + } else { + break; + } + } + } + } + } + + return m_b_done; + } + + ArrayList collect_geometries_to_union(int dim) { + ArrayList batch_to_union = new ArrayList(); + ArrayList> entriesToRemove = new ArrayList>(); + Set> set = m_union_bins.get(dim) + .entrySet(); + for (Map.Entry e : set) { + //int level = e.getKey(); + Bin_type bin = e.getValue(); + + final int binVertexThreshold = 10000; + + if (m_b_done + || (bin.bin_vertex_count > binVertexThreshold && bin + .geom_count() > 1)) { + m_dim_geom_counts[dim] -= bin.geom_count(); + m_added_geoms -= bin.geom_count(); + while (bin.geometries.size() > 0) { + // empty geometries will be unioned too. + batch_to_union.add(bin.back_pair().geom); + bin.pop_pair(); + } + + entriesToRemove.add(e); + } + } + + set.removeAll(entriesToRemove); + return batch_to_union; + } + + private void remove_all_bins_with_lower_dimension(int dim) { + // this geometry has higher dimension than the previously processed one + for (int i = 0; i < dim; i++) { + m_union_bins.get(i).clear(); + m_added_geoms -= m_dim_geom_counts[i]; + m_dim_geom_counts[i] = 0; + } + } + + private void add_geom(int dimension, boolean unioned, Geometry geom) { + Geom_pair pair = new Geom_pair(); + pair.init(); + pair.geom = geom; + int sz = get_vertex_count_(geom); + pair.vertex_count = sz; + int level = get_level_(sz); + if (dimension + 1 > (int) m_union_bins.size()) { + for (int i = 0, n = Math.max(2, dimension + 1); i < n; i++) { + m_union_bins.add(new TreeMap()); + } + } + + Bin_type bin = m_union_bins.get(dimension).get(level);//return null if level is abscent + if (bin == null) { + bin = new Bin_type(); + m_union_bins.get(dimension).put(level, bin); + } + + pair.unioned = unioned; + bin.add_pair(pair); + + // Update global cursor state + m_dim_geom_counts[dimension]++; + m_added_geoms++; + m_max_dimension = Math.max(m_max_dimension, dimension); + } + + private static int get_level_(int sz) {// calculates logarithm of sz to base + // 4. + return sz > 0 ? (int) (Math.log((double) sz) / Math.log(4.0) + 0.5) + : (int) 0; + } + + private static int get_vertex_count_(Geometry geom) { + int gt = geom.getType().value(); + if (Geometry.isMultiVertex(gt)) { + return ((MultiVertexGeometry) geom).getPointCount(); + } else if (gt == Geometry.GeometryType.Point) { + return 1; + } else if (gt == Geometry.GeometryType.Envelope) { + return 4; + } else if (Geometry.isSegment(gt)) { + return 2; + } else { + throw GeometryException.GeometryInternalError(); + } + } + + @Override + public boolean tock() { + return step_(); + } } diff --git a/src/main/java/com/esri/core/geometry/OperatorUnionLocal.java b/src/main/java/com/esri/core/geometry/OperatorUnionLocal.java index c8805f28..516875aa 100644 --- a/src/main/java/com/esri/core/geometry/OperatorUnionLocal.java +++ b/src/main/java/com/esri/core/geometry/OperatorUnionLocal.java @@ -28,21 +28,22 @@ class OperatorUnionLocal extends OperatorUnion { - @Override - public GeometryCursor execute(GeometryCursor inputGeometries, - SpatialReference sr, ProgressTracker progressTracker) { - return new OperatorUnionCursor(inputGeometries, sr, progressTracker); - } - - @Override - public Geometry execute(Geometry geom1, Geometry geom2, - SpatialReference sr, ProgressTracker progressTracker) { - ArrayDeque geometryArrayDeque = new ArrayDeque<>(); - geometryArrayDeque.add(geom1);geometryArrayDeque.add(geom2); - SimpleGeometryCursor inputGeometries = new SimpleGeometryCursor(geometryArrayDeque); - GeometryCursor outputCursor = execute(inputGeometries, sr, progressTracker); - - return outputCursor.next(); - } + @Override + public GeometryCursor execute(GeometryCursor inputGeometries, + SpatialReference sr, ProgressTracker progressTracker) { + return new OperatorUnionCursor(inputGeometries, sr, progressTracker); + } + + @Override + public Geometry execute(Geometry geom1, Geometry geom2, + SpatialReference sr, ProgressTracker progressTracker) { + ArrayDeque geometryArrayDeque = new ArrayDeque<>(); + geometryArrayDeque.add(geom1); + geometryArrayDeque.add(geom2); + SimpleGeometryCursor inputGeometries = new SimpleGeometryCursor(geometryArrayDeque); + GeometryCursor outputCursor = execute(inputGeometries, sr, progressTracker); + + return outputCursor.next(); + } } diff --git a/src/main/java/com/esri/core/geometry/OperatorWithin.java b/src/main/java/com/esri/core/geometry/OperatorWithin.java index 8817c533..e1780359 100644 --- a/src/main/java/com/esri/core/geometry/OperatorWithin.java +++ b/src/main/java/com/esri/core/geometry/OperatorWithin.java @@ -30,14 +30,14 @@ * Relational operation Within. */ public abstract class OperatorWithin extends OperatorSimpleRelation { - @Override - public Type getType() { - return Type.Within; - } - - public static OperatorWithin local() { - return (OperatorWithin) OperatorFactoryLocal.getInstance().getOperator( - Type.Within); - } + @Override + public Type getType() { + return Type.Within; + } + + public static OperatorWithin local() { + return (OperatorWithin) OperatorFactoryLocal.getInstance().getOperator( + Type.Within); + } } diff --git a/src/main/java/com/esri/core/geometry/OperatorWithinLocal.java b/src/main/java/com/esri/core/geometry/OperatorWithinLocal.java index 635910b3..b1b8ace3 100644 --- a/src/main/java/com/esri/core/geometry/OperatorWithinLocal.java +++ b/src/main/java/com/esri/core/geometry/OperatorWithinLocal.java @@ -26,11 +26,11 @@ class OperatorWithinLocal extends OperatorWithin { - @Override - public boolean execute(Geometry inputGeom1, Geometry inputGeom2, - SpatialReference sr, ProgressTracker progressTracker) { - return RelationalOperations.relate(inputGeom1, inputGeom2, sr, - RelationalOperations.Relation.within, progressTracker); - } + @Override + public boolean execute(Geometry inputGeom1, Geometry inputGeom2, + SpatialReference sr, ProgressTracker progressTracker) { + return RelationalOperations.relate(inputGeom1, inputGeom2, sr, + RelationalOperations.Relation.within, progressTracker); + } } diff --git a/src/main/java/com/esri/core/geometry/PairwiseIntersectorImpl.java b/src/main/java/com/esri/core/geometry/PairwiseIntersectorImpl.java index d02cc4fa..857f16c4 100644 --- a/src/main/java/com/esri/core/geometry/PairwiseIntersectorImpl.java +++ b/src/main/java/com/esri/core/geometry/PairwiseIntersectorImpl.java @@ -24,226 +24,226 @@ package com.esri.core.geometry; class PairwiseIntersectorImpl { - // Quad_tree - private MultiPathImpl m_multi_path_impl_a; - private MultiPathImpl m_multi_path_impl_b; - private boolean m_b_paths; - private boolean m_b_quad_tree; - private boolean m_b_done; - private boolean m_b_swap_elements; - private double m_tolerance; - private int m_path_index; - private int m_element_handle; - private Envelope2D m_paths_query = new Envelope2D(); // only used for m_b_paths == true case - private QuadTreeImpl m_quad_tree; - private QuadTreeImpl.QuadTreeIteratorImpl m_qt_iter; - private SegmentIteratorImpl m_seg_iter; - - // Envelope_2D_intersector - private Envelope2DIntersectorImpl m_intersector; - - private int m_function; - - private interface State { - static final int nextPath = 0; - static final int nextSegment = 1; - static final int iterate = 2; - } - - PairwiseIntersectorImpl(MultiPathImpl multi_path_impl_a, MultiPathImpl multi_path_impl_b, double tolerance, boolean b_paths) { - m_multi_path_impl_a = multi_path_impl_a; - m_multi_path_impl_b = multi_path_impl_b; - - m_b_paths = b_paths; - m_path_index = -1; - - m_b_quad_tree = false; - - GeometryAccelerators geometry_accelerators_a = multi_path_impl_a._getAccelerators(); - - if (geometry_accelerators_a != null) { - QuadTreeImpl qtree_a = (!b_paths ? geometry_accelerators_a.getQuadTree() : geometry_accelerators_a.getQuadTreeForPaths()); - - if (qtree_a != null) { - m_b_done = false; - m_tolerance = tolerance; - m_quad_tree = qtree_a; - m_qt_iter = m_quad_tree.getIterator(); - m_b_quad_tree = true; - m_b_swap_elements = true; - m_function = State.nextPath; - - if (!b_paths) - m_seg_iter = multi_path_impl_b.querySegmentIterator(); - else - m_path_index = multi_path_impl_b.getPathCount(); // we will iterate backwards until we hit -1 - } - } - - if (!m_b_quad_tree) { - GeometryAccelerators geometry_accelerators_b = multi_path_impl_b._getAccelerators(); - - if (geometry_accelerators_b != null) { - QuadTreeImpl qtree_b = (!b_paths ? geometry_accelerators_b.getQuadTree() : geometry_accelerators_b.getQuadTreeForPaths()); - - if (qtree_b != null) { - m_b_done = false; - m_tolerance = tolerance; - m_quad_tree = qtree_b; - m_qt_iter = m_quad_tree.getIterator(); - m_b_quad_tree = true; - m_b_swap_elements = false; - m_function = State.nextPath; - - if (!b_paths) - m_seg_iter = multi_path_impl_a.querySegmentIterator(); - else - m_path_index = multi_path_impl_a.getPathCount(); // we will iterate backwards until we hit -1 - } - } - } - - if (!m_b_quad_tree) { - if (!b_paths) { - m_intersector = InternalUtils.getEnvelope2DIntersector(multi_path_impl_a, multi_path_impl_b, tolerance); - } else { - boolean b_simple_a = multi_path_impl_a.getIsSimple(0.0) >= 1; - boolean b_simple_b = multi_path_impl_b.getIsSimple(0.0) >= 1; - m_intersector = InternalUtils.getEnvelope2DIntersectorForParts(multi_path_impl_a, multi_path_impl_b, tolerance, b_simple_a, b_simple_b); - } - } - } - - boolean next() { - if (m_b_quad_tree) { - if (m_b_done) - return false; - - boolean b_searching = true; - while (b_searching) { - switch (m_function) { - case State.nextPath: - b_searching = nextPath_(); - break; - case State.nextSegment: - b_searching = nextSegment_(); - break; - case State.iterate: - b_searching = iterate_(); - break; - default: - throw GeometryException.GeometryInternalError(); - } - } - - if (m_b_done) - return false; - - return true; - } - - if (m_intersector == null) - return false; - - return m_intersector.next(); - } - - int getRedElement() { - if (m_b_quad_tree) { - if (!m_b_swap_elements) - return (!m_b_paths ? m_seg_iter.getStartPointIndex() : m_path_index); - - return m_quad_tree.getElement(m_element_handle); - } - - return m_intersector.getRedElement(m_intersector.getHandleA()); - } - - int getBlueElement() { - if (m_b_quad_tree) { - if (m_b_swap_elements) - return (!m_b_paths ? m_seg_iter.getStartPointIndex() : m_path_index); - - return m_quad_tree.getElement(m_element_handle); - } - - return m_intersector.getBlueElement(m_intersector.getHandleB()); - } - - Envelope2D getRedEnvelope() { - if (!m_b_paths) - throw GeometryException.GeometryInternalError(); - - if (m_b_quad_tree) { - if (!m_b_swap_elements) - return m_paths_query; - - return m_quad_tree.getElementExtent(m_element_handle); - } - - return m_intersector.getRedEnvelope(m_intersector.getHandleA()); - } - - Envelope2D getBlueEnvelope() { - if (!m_b_paths) - throw GeometryException.GeometryInternalError(); - - if (m_b_quad_tree) { - if (m_b_swap_elements) - return m_paths_query; - - return m_quad_tree.getElementExtent(m_element_handle); - } - - return m_intersector.getBlueEnvelope(m_intersector.getHandleB()); - } - - boolean nextPath_() { - if (!m_b_paths) { - if (!m_seg_iter.nextPath()) { - m_b_done = true; - return false; - } - - m_function = State.nextSegment; - return true; - } - - if (--m_path_index == -1) { - m_b_done = true; - return false; - } - - if (m_b_swap_elements) - m_multi_path_impl_b.queryPathEnvelope2D(m_path_index, m_paths_query); - else - m_multi_path_impl_a.queryPathEnvelope2D(m_path_index, m_paths_query); - - m_qt_iter.resetIterator(m_paths_query, m_tolerance); - m_function = State.iterate; - return true; - } - - boolean nextSegment_() { - if (!m_seg_iter.hasNextSegment()) { - m_function = State.nextPath; - return true; - } - - Segment segment = m_seg_iter.nextSegment(); - m_qt_iter.resetIterator(segment, m_tolerance); - m_function = State.iterate; - return true; - } + // Quad_tree + private MultiPathImpl m_multi_path_impl_a; + private MultiPathImpl m_multi_path_impl_b; + private boolean m_b_paths; + private boolean m_b_quad_tree; + private boolean m_b_done; + private boolean m_b_swap_elements; + private double m_tolerance; + private int m_path_index; + private int m_element_handle; + private Envelope2D m_paths_query = new Envelope2D(); // only used for m_b_paths == true case + private QuadTreeImpl m_quad_tree; + private QuadTreeImpl.QuadTreeIteratorImpl m_qt_iter; + private SegmentIteratorImpl m_seg_iter; + + // Envelope_2D_intersector + private Envelope2DIntersectorImpl m_intersector; + + private int m_function; + + private interface State { + static final int nextPath = 0; + static final int nextSegment = 1; + static final int iterate = 2; + } + + PairwiseIntersectorImpl(MultiPathImpl multi_path_impl_a, MultiPathImpl multi_path_impl_b, double tolerance, boolean b_paths) { + m_multi_path_impl_a = multi_path_impl_a; + m_multi_path_impl_b = multi_path_impl_b; + + m_b_paths = b_paths; + m_path_index = -1; + + m_b_quad_tree = false; + + GeometryAccelerators geometry_accelerators_a = multi_path_impl_a._getAccelerators(); + + if (geometry_accelerators_a != null) { + QuadTreeImpl qtree_a = (!b_paths ? geometry_accelerators_a.getQuadTree() : geometry_accelerators_a.getQuadTreeForPaths()); + + if (qtree_a != null) { + m_b_done = false; + m_tolerance = tolerance; + m_quad_tree = qtree_a; + m_qt_iter = m_quad_tree.getIterator(); + m_b_quad_tree = true; + m_b_swap_elements = true; + m_function = State.nextPath; + + if (!b_paths) + m_seg_iter = multi_path_impl_b.querySegmentIterator(); + else + m_path_index = multi_path_impl_b.getPathCount(); // we will iterate backwards until we hit -1 + } + } + + if (!m_b_quad_tree) { + GeometryAccelerators geometry_accelerators_b = multi_path_impl_b._getAccelerators(); + + if (geometry_accelerators_b != null) { + QuadTreeImpl qtree_b = (!b_paths ? geometry_accelerators_b.getQuadTree() : geometry_accelerators_b.getQuadTreeForPaths()); + + if (qtree_b != null) { + m_b_done = false; + m_tolerance = tolerance; + m_quad_tree = qtree_b; + m_qt_iter = m_quad_tree.getIterator(); + m_b_quad_tree = true; + m_b_swap_elements = false; + m_function = State.nextPath; + + if (!b_paths) + m_seg_iter = multi_path_impl_a.querySegmentIterator(); + else + m_path_index = multi_path_impl_a.getPathCount(); // we will iterate backwards until we hit -1 + } + } + } + + if (!m_b_quad_tree) { + if (!b_paths) { + m_intersector = InternalUtils.getEnvelope2DIntersector(multi_path_impl_a, multi_path_impl_b, tolerance); + } else { + boolean b_simple_a = multi_path_impl_a.getIsSimple(0.0) >= 1; + boolean b_simple_b = multi_path_impl_b.getIsSimple(0.0) >= 1; + m_intersector = InternalUtils.getEnvelope2DIntersectorForParts(multi_path_impl_a, multi_path_impl_b, tolerance, b_simple_a, b_simple_b); + } + } + } + + boolean next() { + if (m_b_quad_tree) { + if (m_b_done) + return false; + + boolean b_searching = true; + while (b_searching) { + switch (m_function) { + case State.nextPath: + b_searching = nextPath_(); + break; + case State.nextSegment: + b_searching = nextSegment_(); + break; + case State.iterate: + b_searching = iterate_(); + break; + default: + throw GeometryException.GeometryInternalError(); + } + } + + if (m_b_done) + return false; + + return true; + } + + if (m_intersector == null) + return false; + + return m_intersector.next(); + } + + int getRedElement() { + if (m_b_quad_tree) { + if (!m_b_swap_elements) + return (!m_b_paths ? m_seg_iter.getStartPointIndex() : m_path_index); + + return m_quad_tree.getElement(m_element_handle); + } + + return m_intersector.getRedElement(m_intersector.getHandleA()); + } + + int getBlueElement() { + if (m_b_quad_tree) { + if (m_b_swap_elements) + return (!m_b_paths ? m_seg_iter.getStartPointIndex() : m_path_index); + + return m_quad_tree.getElement(m_element_handle); + } + + return m_intersector.getBlueElement(m_intersector.getHandleB()); + } + + Envelope2D getRedEnvelope() { + if (!m_b_paths) + throw GeometryException.GeometryInternalError(); + + if (m_b_quad_tree) { + if (!m_b_swap_elements) + return m_paths_query; + + return m_quad_tree.getElementExtent(m_element_handle); + } + + return m_intersector.getRedEnvelope(m_intersector.getHandleA()); + } + + Envelope2D getBlueEnvelope() { + if (!m_b_paths) + throw GeometryException.GeometryInternalError(); + + if (m_b_quad_tree) { + if (m_b_swap_elements) + return m_paths_query; + + return m_quad_tree.getElementExtent(m_element_handle); + } + + return m_intersector.getBlueEnvelope(m_intersector.getHandleB()); + } + + boolean nextPath_() { + if (!m_b_paths) { + if (!m_seg_iter.nextPath()) { + m_b_done = true; + return false; + } + + m_function = State.nextSegment; + return true; + } + + if (--m_path_index == -1) { + m_b_done = true; + return false; + } + + if (m_b_swap_elements) + m_multi_path_impl_b.queryPathEnvelope2D(m_path_index, m_paths_query); + else + m_multi_path_impl_a.queryPathEnvelope2D(m_path_index, m_paths_query); + + m_qt_iter.resetIterator(m_paths_query, m_tolerance); + m_function = State.iterate; + return true; + } + + boolean nextSegment_() { + if (!m_seg_iter.hasNextSegment()) { + m_function = State.nextPath; + return true; + } + + Segment segment = m_seg_iter.nextSegment(); + m_qt_iter.resetIterator(segment, m_tolerance); + m_function = State.iterate; + return true; + } - boolean iterate_() { - m_element_handle = m_qt_iter.next(); + boolean iterate_() { + m_element_handle = m_qt_iter.next(); - if (m_element_handle == -1) { - m_function = (!m_b_paths ? State.nextSegment : State.nextPath); - return true; - } + if (m_element_handle == -1) { + m_function = (!m_b_paths ? State.nextSegment : State.nextPath); + return true; + } - return false; - } + return false; + } } diff --git a/src/main/java/com/esri/core/geometry/PathFlags.java b/src/main/java/com/esri/core/geometry/PathFlags.java index 296db3ea..4e899128 100644 --- a/src/main/java/com/esri/core/geometry/PathFlags.java +++ b/src/main/java/com/esri/core/geometry/PathFlags.java @@ -25,16 +25,16 @@ package com.esri.core.geometry; interface PathFlags { - public static final int enumClosed = 1; - public static final int enumHasNonlinearSegments = 2;// set when the given - // part has - // non-linear - // segments - public static final int enumOGCStartPolygon = 4;// set at the start of a - // Polygon when viewed as an - // OGC MultiPolygon - public static final int enumCalcMask = 4;// mask of flags that are obtained - // by calculation and depend on - // the order of MultiPath parts. + public static final int enumClosed = 1; + public static final int enumHasNonlinearSegments = 2;// set when the given + // part has + // non-linear + // segments + public static final int enumOGCStartPolygon = 4;// set at the start of a + // Polygon when viewed as an + // OGC MultiPolygon + public static final int enumCalcMask = 4;// mask of flags that are obtained + // by calculation and depend on + // the order of MultiPath parts. } diff --git a/src/main/java/com/esri/core/geometry/PeDouble.java b/src/main/java/com/esri/core/geometry/PeDouble.java index 9595af29..fd33af49 100644 --- a/src/main/java/com/esri/core/geometry/PeDouble.java +++ b/src/main/java/com/esri/core/geometry/PeDouble.java @@ -25,13 +25,13 @@ package com.esri.core.geometry; final class PeDouble { - double val; + double val; - PeDouble() { - val = 0.0; - } + PeDouble() { + val = 0.0; + } - PeDouble(double v) { - val = v; - } + PeDouble(double v) { + val = v; + } } diff --git a/src/main/java/com/esri/core/geometry/PlaneSweepCrackerHelper.java b/src/main/java/com/esri/core/geometry/PlaneSweepCrackerHelper.java index f295ea77..d286aac4 100644 --- a/src/main/java/com/esri/core/geometry/PlaneSweepCrackerHelper.java +++ b/src/main/java/com/esri/core/geometry/PlaneSweepCrackerHelper.java @@ -28,1506 +28,1506 @@ package com.esri.core.geometry; final class PlaneSweepCrackerHelper { - PlaneSweepCrackerHelper() { - m_edges = new StridedIndexTypeCollection(8); - m_clusters = new StridedIndexTypeCollection(5); - m_cluster_vertices = new IndexMultiList(); - m_edge_vertices = new IndexMultiList(); - m_complications = false; - m_sweep_point = new Point2D(); - m_sweep_point.setCoords(0, 0); - m_tolerance = 0; - m_vertex_cluster_index = -1; - m_b_cracked = false; - m_shape = null; - - m_event_q = new Treap(); - m_sweep_structure = new Treap(); - m_edges_to_insert_in_sweep_structure = new AttributeStreamOfInt32(0); - m_segment_intersector = new SegmentIntersector(); - m_temp_edge_buffer = new AttributeStreamOfInt32(0); - m_modified_clusters = new AttributeStreamOfInt32(0); - m_helper_point = new Point(); - } - - // For use in Cluster/Cracker loop - boolean sweep(EditShape shape, double tolerance) { - Transformation2D transform = new Transformation2D(); - transform.setSwapCoordinates(); - shape.applyTransformation(transform);// swap coordinates for the sweep - // along x - setEditShape_(shape); - m_b_cracked = false; - m_tolerance = tolerance; - m_tolerance_sqr = tolerance * tolerance; - - boolean b_cracked = sweepImpl_(); - shape.applyTransformation(transform); - if (!b_cracked) { - fillEventQueuePass2(); - b_cracked |= sweepImpl_(); - } - - if (m_vertex_cluster_index != -1) { - m_shape.removeUserIndex(m_vertex_cluster_index); - m_vertex_cluster_index = -1; - } - - m_shape = null; - return m_b_cracked; - } - - // Does one pass sweep vertically - boolean sweepVertical(EditShape shape, double tolerance) { - setEditShape_(shape); - m_b_cracked = false; - m_tolerance = tolerance; - m_tolerance_sqr = tolerance * tolerance; - m_complications = false; - boolean bresult = sweepImpl_(); - if (!m_complications) { - int filtered = shape.filterClosePoints(tolerance, true, false); - m_complications = filtered == 1; - bresult |= filtered == 1; - } - - if (m_vertex_cluster_index != -1) { - m_shape.removeUserIndex(m_vertex_cluster_index); - m_vertex_cluster_index = -1; - } - - m_shape = null; - return bresult; - } - - boolean hadCompications() { - return m_complications; - } - - private EditShape m_shape; - private StridedIndexTypeCollection m_edges; - private StridedIndexTypeCollection m_clusters; - private IndexMultiList m_cluster_vertices; - private IndexMultiList m_edge_vertices; - private Point m_helper_point; - - private Treap m_event_q; - private Treap m_sweep_structure; - - boolean m_complications; - - static final class SimplifySweepComparator extends SweepComparator { - PlaneSweepCrackerHelper m_parent; - - SimplifySweepComparator(PlaneSweepCrackerHelper parent) { - super(parent.m_shape, parent.m_tolerance, false); - m_parent = parent; - } - - @Override - int compare(Treap treap, int elm, int node) { - // Compares two segments on a sweep line passing through m_sweep_y, - // m_sweep_x. - if (m_b_intersection_detected) - return -1; - - int vertex_list_left = m_parent.getEdgeOriginVertices(elm); - int left = m_parent.m_edge_vertices - .getFirstElement(vertex_list_left); - - int right_elm = treap.getElement(node); - assert (m_parent.getEdgeSweepNode(right_elm) == node); - int vertex_list_right = m_parent.getEdgeOriginVertices(right_elm); - int right = m_parent.m_edge_vertices - .getFirstElement(vertex_list_right); - - m_current_node = node; - return compareSegments(elm, left, right_elm, right); - } - } - - ; - - static final class SimplifySweepMonikerComparator extends - SweepMonkierComparator { - PlaneSweepCrackerHelper m_parent; - - SimplifySweepMonikerComparator(PlaneSweepCrackerHelper parent) { - super(parent.m_shape, parent.m_tolerance); - m_parent = parent; - } - - @Override - int compare(Treap treap, int node) { - // Compares two segments on a sweep line passing through m_sweep_y, - // m_sweep_x. - if (m_b_intersection_detected) - return -1; - - int elm = treap.getElement(node); - int vertexList = m_parent.getEdgeOriginVertices(elm); - int vertex = m_parent.m_edge_vertices.getFirstElement(vertexList); - - m_current_node = node; - return compareVertex_(treap, node, vertex); - } - } - - ; - - SimplifySweepComparator m_sweep_comparator; - - AttributeStreamOfInt32 m_temp_edge_buffer; - AttributeStreamOfInt32 m_modified_clusters; - AttributeStreamOfInt32 m_edges_to_insert_in_sweep_structure; - - int m_prev_neighbour; - int m_next_neighbour; - boolean m_b_continuing_segment_chain_optimization;// set to true, when the - // cluster has two edges - // attached, one is - // below and another - // above the sweep line - - SegmentIntersector m_segment_intersector; - - Line m_line_1; - Line m_line_2; - - Point2D m_sweep_point; - double m_tolerance; - double m_tolerance_sqr; - - int m_sweep_point_cluster; - int m_vertex_cluster_index; - - boolean m_b_cracked; - boolean m_b_sweep_point_cluster_was_modified;// set to true if the - // coordinates of the - // cluster, where the sweep - // line was, has been - // changed. - - int getEdgeCluster(int edge, int end) { - assert (end == 0 || end == 1); - return m_edges.getField(edge, 0 + end); - } - - void setEdgeCluster_(int edge, int end, int cluster) { - assert (end == 0 || end == 1); - m_edges.setField(edge, 0 + end, cluster); - } - - // Edge may have several origin vertices, when there are two or more equal - // segements in that edge - // We have to store edge origin separately from the cluster vertices, - // because cluster can have several different edges started on it. - int getEdgeOriginVertices(int edge) { - return m_edges.getField(edge, 2); - } - - void setEdgeOriginVertices_(int edge, int vertices) { - m_edges.setField(edge, 2, vertices); - } - - int getNextEdgeEx(int edge, int end) { - assert (end == 0 || end == 1); - return m_edges.getField(edge, 3 + end); - } - - void setNextEdgeEx_(int edge, int end, int next_edge) { - assert (end == 0 || end == 1); - m_edges.setField(edge, 3 + end, next_edge); - } - - // int get_prev_edge_ex(int edge, int end) - // { - // assert(end == 0 || end == 1); - // return m_edges.get_field(edge, 5 + end); - // } - // void set_prev_edge_ex_(int edge, int end, int prevEdge) - // { - // assert(end == 0 || end == 1); - // m_edges.set_field(edge, 5 + end, prevEdge); - // } - - int getEdgeSweepNode(int edge) { - return m_edges.getField(edge, 7); - } - - void setEdgeSweepNode_(int edge, int sweepNode) { - m_edges.setField(edge, 7, sweepNode); - } - - int getNextEdge(int edge, int cluster) { - int end = getEdgeEnd(edge, cluster); - assert (end == 0 || end == 1); - return m_edges.getField(edge, 3 + end); - } - - void setNextEdge_(int edge, int cluster, int next_edge) { - int end = getEdgeEnd(edge, cluster); - assert (end == 0 || end == 1); - m_edges.setField(edge, 3 + end, next_edge); - } - - int getPrevEdge(int edge, int cluster) { - int end = getEdgeEnd(edge, cluster); - assert (end == 0 || end == 1); - return m_edges.getField(edge, 5 + end); - } - - void setPrevEdge_(int edge, int cluster, int prevEdge) { - int end = getEdgeEnd(edge, cluster); - assert (end == 0 || end == 1); - m_edges.setField(edge, 5 + end, prevEdge); - } - - int getClusterVertices(int cluster) { - return m_clusters.getField(cluster, 0); - } - - void setClusterVertices_(int cluster, int vertices) { - m_clusters.setField(cluster, 0, vertices); - } - - int getClusterVertexIndex(int cluster) { - return m_clusters.getField(cluster, 4); - } - - void setClusterVertexIndex_(int cluster, int vindex) { - m_clusters.setField(cluster, 4, vindex); - } - - int getClusterSweepEdgeList(int cluster) { - return m_clusters.getField(cluster, 2); - } - - void setClusterSweepEdgeList_(int cluster, int sweep_edges) { - m_clusters.setField(cluster, 2, sweep_edges); - } - - int getClusterFirstEdge(int cluster) { - return m_clusters.getField(cluster, 1); - } - - void setClusterFirstEdge_(int cluster, int first_edge) { - m_clusters.setField(cluster, 1, first_edge); - } - - int getClusterEventQNode(int cluster) { - return m_clusters.getField(cluster, 3); - } - - void setClusterEventQNode_(int cluster, int node) { - m_clusters.setField(cluster, 3, node); - } - - int newCluster_(int vertex) { - int cluster = m_clusters.newElement(); - int vertexList = m_cluster_vertices.createList(); - setClusterVertices_(cluster, vertexList); - if (vertex != -1) { - m_cluster_vertices.addElement(vertexList, vertex); - assert (m_shape.getUserIndex(vertex, m_vertex_cluster_index) == -1); - m_shape.setUserIndex(vertex, m_vertex_cluster_index, cluster); - setClusterVertexIndex_(cluster, m_shape.getVertexIndex(vertex)); - } else { - setClusterVertexIndex_(cluster, -1); - } - - return cluster; - } - - void deleteCluster_(int cluster) { - m_clusters.deleteElement(cluster); - } - - void addVertexToCluster_(int cluster, int vertex) { - assert (m_shape.getUserIndex(vertex, m_vertex_cluster_index) == -1); - int vertexList = getClusterVertices(cluster); - m_cluster_vertices.addElement(vertexList, vertex); - m_shape.setUserIndex(vertex, m_vertex_cluster_index, cluster); - } - - // Creates a new unattached edge with the given origin. - int newEdge_(int origin_vertex) { - int edge = m_edges.newElement(); - int edgeVertices = m_edge_vertices.createList(); - setEdgeOriginVertices_(edge, edgeVertices); - if (origin_vertex != -1) - m_edge_vertices.addElement(edgeVertices, origin_vertex); - - return edge; - } - - void addVertexToEdge_(int edge, int vertex) { - int vertexList = getEdgeOriginVertices(edge); - m_edge_vertices.addElement(vertexList, vertex); - } - - void deleteEdge_(int edge) { - m_edges.deleteElement(edge); - int ind = m_edges_to_insert_in_sweep_structure.findElement(edge); - if (ind >= 0) - m_edges_to_insert_in_sweep_structure.popElement(ind); - } - - void addEdgeToCluster(int edge, int cluster) { - if (getEdgeCluster(edge, 0) == -1) { - assert (getEdgeCluster(edge, 1) != cluster); - setEdgeCluster_(edge, 0, cluster); - } else if (getEdgeCluster(edge, 1) == -1) { - assert (getEdgeCluster(edge, 0) != cluster); - setEdgeCluster_(edge, 1, cluster); - } else - throw GeometryException.GeometryInternalError(); - - addEdgeToClusterImpl_(edge, cluster);// simply adds the edge to the list - // of cluster edges. - } - - void addEdgeToClusterImpl_(int edge, int cluster) { - int first_edge = getClusterFirstEdge(cluster); - if (first_edge != -1) { - int next = getNextEdge(first_edge, cluster); - setPrevEdge_(next, cluster, edge); - setNextEdge_(edge, cluster, next); - setNextEdge_(first_edge, cluster, edge); - setPrevEdge_(edge, cluster, first_edge); - } else { - setPrevEdge_(edge, cluster, edge);// point to itself - setNextEdge_(edge, cluster, edge); - setClusterFirstEdge_(cluster, edge); - } - } - - int getEdgeEnd(int edge, int cluster) { - if (getEdgeCluster(edge, 0) == cluster) { - assert (getEdgeCluster(edge, 1) != cluster); - return 0; - } else { - assert (getEdgeCluster(edge, 1) == cluster); - return 1; - } - } - - // Merges two coincident clusters into one. The cluster2 becomes invalid. - void mergeClusters_(int cluster_1, int cluster2) { - // dbg_check_cluster_(cluster_1); - // dbg_check_cluster_(cluster2); - int eventQnode = getClusterEventQNode(cluster2); - if (eventQnode != -1) { - m_event_q.deleteNode(eventQnode, -1); - setClusterEventQNode_(cluster2, -1); - } - - int firstEdge1 = getClusterFirstEdge(cluster_1); - int firstEdge2 = getClusterFirstEdge(cluster2); - - if (firstEdge2 != -1) {// scope - int edge2 = firstEdge2; - int lastEdge = firstEdge2; - boolean bForceContinue = false; - // Delete edges that connect cluster_1 and cluster2. - do { - // dbg_check_edge_(edge2); - bForceContinue = false; - // assert(!StridedIndexTypeCollection.isValidElement(getEdgeSweepNode(edge2))); - int end = getEdgeEnd(edge2, cluster2); - int nextEdge2 = getNextEdgeEx(edge2, end); - if (getEdgeCluster(edge2, (end + 1) & 1) == cluster_1) { // Snapping - // clusters - // that - // are - // connected - // with - // an - // edge - // Delete - // the - // edge. - disconnectEdge_(edge2); - int edgeOrigins2 = getEdgeOriginVertices(edge2); - m_edge_vertices.deleteList(edgeOrigins2); - deleteEdge_(edge2); - if (edge2 == nextEdge2) {// deleted last edge connecting to - // the cluster2 (all connections - // are degenerate) - firstEdge2 = -1; - break; - } - if (firstEdge2 == edge2) { - firstEdge2 = getClusterFirstEdge(cluster2); - lastEdge = nextEdge2; - bForceContinue = true; - } - } else { - assert (edge2 != getClusterFirstEdge(cluster_1)); - } - edge2 = nextEdge2; - } while (edge2 != lastEdge || bForceContinue); - - if (firstEdge2 != -1) { - // set the cluster to the edge ends - do { - int end = getEdgeEnd(edge2, cluster2); - int nextEdge2 = getNextEdgeEx(edge2, end); - assert (edge2 != getClusterFirstEdge(cluster_1)); - setEdgeCluster_(edge2, end, cluster_1); - edge2 = nextEdge2; - } while (edge2 != lastEdge); - - firstEdge1 = getClusterFirstEdge(cluster_1); - if (firstEdge1 != -1) { - int next1 = getNextEdge(firstEdge1, cluster_1); - int next2 = getNextEdge(firstEdge2, cluster_1); - if (next1 == firstEdge1) { - setClusterFirstEdge_(cluster_1, firstEdge2); - addEdgeToClusterImpl_(firstEdge1, cluster_1); - setClusterFirstEdge_(cluster_1, firstEdge1); - } else if (next2 == firstEdge2) { - addEdgeToClusterImpl_(firstEdge2, cluster_1); - } - - setNextEdge_(firstEdge2, cluster_1, next1); - setPrevEdge_(next1, cluster_1, firstEdge2); - setNextEdge_(firstEdge1, cluster_1, next2); - setPrevEdge_(next2, cluster_1, firstEdge1); - } else { - setClusterFirstEdge_(cluster_1, firstEdge2); - } - } - } - - int vertices1 = getClusterVertices(cluster_1); - int vertices2 = getClusterVertices(cluster2); - // Update cluster info on vertices. - for (int vh = m_cluster_vertices.getFirst(vertices2); vh != -1; vh = m_cluster_vertices - .getNext(vh)) { - int v = m_cluster_vertices.getElement(vh); - m_shape.setUserIndex(v, m_vertex_cluster_index, cluster_1); - } - m_cluster_vertices.concatenateLists(vertices1, vertices2); - deleteCluster_(cluster2); - // dbg_check_cluster_(cluster_1); - } - - // Merges two coincident edges into one. The edge2 becomes invalid. - void mergeEdges_(int edge1, int edge2) { - // dbg_check_edge_(edge1); - int cluster_1 = getEdgeCluster(edge1, 0); - int cluster2 = getEdgeCluster(edge1, 1); - int cluster21 = getEdgeCluster(edge2, 0); - int cluster22 = getEdgeCluster(edge2, 1); - - int originVertices1 = getEdgeOriginVertices(edge1); - int originVertices2 = getEdgeOriginVertices(edge2); - m_edge_vertices.concatenateLists(originVertices1, originVertices2); - if (edge2 == getClusterFirstEdge(cluster_1)) - setClusterFirstEdge_(cluster_1, edge1); - if (edge2 == getClusterFirstEdge(cluster2)) - setClusterFirstEdge_(cluster2, edge1); - - disconnectEdge_(edge2);// disconnects the edge2 from the clusters. - deleteEdge_(edge2); - - if (!((cluster_1 == cluster21 && cluster2 == cluster22) || (cluster2 == cluster21 && cluster_1 == cluster22))) { - // Merged edges have different clusters (clusters have not yet been - // merged) - // merge clusters before merging the edges - getClusterXY(cluster_1, pt_1); - getClusterXY(cluster21, pt_2); - if (pt_1.isEqual(pt_2)) { - if (cluster_1 != cluster21) { - mergeClusters_(cluster_1, cluster21); - assert (!m_modified_clusters.hasElement(cluster21)); - } - if (cluster2 != cluster22) { - mergeClusters_(cluster2, cluster22); - assert (!m_modified_clusters.hasElement(cluster22)); - } - } else { - if (cluster2 != cluster21) { - mergeClusters_(cluster2, cluster21); - assert (!m_modified_clusters.hasElement(cluster21)); - } - if (cluster_1 != cluster22) { - mergeClusters_(cluster_1, cluster22); - assert (!m_modified_clusters.hasElement(cluster22)); - } - } - } else { - // Merged edges have equal clusters. - } - // dbg_check_edge_(edge1); - } - - // Disconnects the edge from its clusters. - void disconnectEdge_(int edge) { - int cluster_1 = getEdgeCluster(edge, 0); - int cluster2 = getEdgeCluster(edge, 1); - disconnectEdgeFromCluster_(edge, cluster_1); - disconnectEdgeFromCluster_(edge, cluster2); - } - - // Disconnects the edge from a cluster it is connected to. - void disconnectEdgeFromCluster_(int edge, int cluster) { - int next = getNextEdge(edge, cluster); - assert (getPrevEdge(next, cluster) == edge); - int prev = getPrevEdge(edge, cluster); - assert (getNextEdge(prev, cluster) == edge); - int first_edge = getClusterFirstEdge(cluster); - if (next != edge) { - setNextEdge_(prev, cluster, next); - setPrevEdge_(next, cluster, prev); - if (first_edge == edge) - setClusterFirstEdge_(cluster, next); - } else - setClusterFirstEdge_(cluster, -1); - } - - void applyIntersectorToEditShape_(int edgeOrigins, - SegmentIntersector intersector, int intersector_index) { - // Split Edit_shape segments and produce new vertices. Modify - // coordinates as necessary. No vertices are deleted. - int vertexHandle = m_edge_vertices.getFirst(edgeOrigins); - int first_vertex = m_edge_vertices.getElement(vertexHandle); - - int cluster_1 = getClusterFromVertex(first_vertex); - int cluster2 = getClusterFromVertex(m_shape.getNextVertex(first_vertex)); - boolean bComplexCase = cluster_1 == cluster2; - assert (!bComplexCase);// if it ever asserts there will be a bug. Should - // be a case of a curve that forms a loop. - - m_shape.splitSegment_(first_vertex, intersector, intersector_index, - true); - for (vertexHandle = m_edge_vertices.getNext(vertexHandle); vertexHandle != -1; vertexHandle = m_edge_vertices - .getNext(vertexHandle)) { - int vertex = m_edge_vertices.getElement(vertexHandle); - boolean b_forward = getClusterFromVertex(vertex) == cluster_1; - assert ((b_forward && getClusterFromVertex(m_shape - .getNextVertex(vertex)) == cluster2) || (getClusterFromVertex(vertex) == cluster2 && getClusterFromVertex(m_shape - .getNextVertex(vertex)) == cluster_1)); - m_shape.splitSegment_(vertex, intersector, intersector_index, - b_forward); - } - - // Now apply the updated coordinates to all vertices in the cluster_1 - // and cluster2. - Point2D pt_0; - Point2D pt_1; - pt_0 = intersector.getResultSegment(intersector_index, 0).getStartXY(); - pt_1 = intersector.getResultSegment(intersector_index, - intersector.getResultSegmentCount(intersector_index) - 1) - .getEndXY(); - updateClusterXY(cluster_1, pt_0); - updateClusterXY(cluster2, pt_1); - } - - void createEdgesAndClustersFromSplitEdge_(int edge1, - SegmentIntersector intersector, int intersector_index) { - // dbg_check_new_edges_array_(); - // The method uses m_temp_edge_buffer for temporary storage and clears - // it at the end. - int edgeOrigins1 = getEdgeOriginVertices(edge1); - - // create new edges and clusters - // Note that edge1 is disconnected from its clusters already (the - // cluster's edge list does not contain it). - int cluster_1 = getEdgeCluster(edge1, 0); - int cluster2 = getEdgeCluster(edge1, 1); - int prevEdge = newEdge_(-1); - m_edges_to_insert_in_sweep_structure.add(prevEdge); - int c_3 = StridedIndexTypeCollection.impossibleIndex3(); - setEdgeSweepNode_(prevEdge, c_3);// mark that its in - // m_edges_to_insert_in_sweep_structure - m_temp_edge_buffer.add(prevEdge); - addEdgeToCluster(prevEdge, cluster_1); - for (int i = 1, n = intersector - .getResultSegmentCount(intersector_index); i < n; i++) {// each - // iteration - // adds - // new - // Cluster - // and - // Edge. - int newCluster = newCluster_(-1); - m_modified_clusters.add(newCluster); - m_temp_edge_buffer.add(newCluster); - addEdgeToCluster(prevEdge, newCluster); - int newEdge = newEdge_(-1); - m_edges_to_insert_in_sweep_structure.add(newEdge); - setEdgeSweepNode_(newEdge, c_3);// mark that its in - // m_edges_to_insert_in_sweep_structure - m_temp_edge_buffer.add(newEdge); - addEdgeToCluster(newEdge, newCluster); - prevEdge = newEdge; - } - addEdgeToCluster(prevEdge, cluster2); - // set the Edit_shape vertices to the new clusters and edges. - for (int vertexHandle = m_edge_vertices.getFirst(edgeOrigins1); vertexHandle != -1; vertexHandle = m_edge_vertices - .getNext(vertexHandle)) { - int vertex = m_edge_vertices.getElement(vertexHandle); - int cluster = getClusterFromVertex(vertex); - if (cluster == cluster_1) {// connecting from cluster_1 to cluster2 - int i = 0; - do { - if (i > 0) { - int c = m_temp_edge_buffer.get(i - 1); - addVertexToCluster_(c, vertex); - if (getClusterVertexIndex(c) == -1) - setClusterVertexIndex_(c, - m_shape.getVertexIndex(vertex)); - } - - int edge = m_temp_edge_buffer.get(i); - i += 2; - addVertexToEdge_(edge, vertex); - vertex = m_shape.getNextVertex(vertex); - } while (i < m_temp_edge_buffer.size()); - assert (getClusterFromVertex(vertex) == cluster2); - } else {// connecting from cluster2 to cluster_1 - assert (cluster == cluster2); - int i = m_temp_edge_buffer.size() - 1; - do { - if (i < m_temp_edge_buffer.size() - 2) { - int c = m_temp_edge_buffer.get(i + 1); - addVertexToCluster_(c, vertex); - if (getClusterVertexIndex(c) < 0) - setClusterVertexIndex_(c, - m_shape.getVertexIndex(vertex)); - } - - assert (i % 2 == 0); - int edge = m_temp_edge_buffer.get(i); - i -= 2; - addVertexToEdge_(edge, vertex); - vertex = m_shape.getNextVertex(vertex); - } while (i >= 0); - assert (getClusterFromVertex(vertex) == cluster_1); - } - } - - // #ifdef _DEBUG_TOPO - // for (int i = 0, j = 0, n = - // intersector->get_result_segment_count(intersector_index); i < n; i++, - // j+=2) - // { - // int edge = m_temp_edge_buffer.get(j); - // dbg_check_edge_(edge); - // } - // #endif - - m_temp_edge_buffer.clear(false); - // dbg_check_new_edges_array_(); - } - - int getVertexFromClusterIndex(int cluster) { - int vertexList = getClusterVertices(cluster); - int vertex = m_cluster_vertices.getFirstElement(vertexList); - return vertex; - } - - int getClusterFromVertex(int vertex) { - return m_shape.getUserIndex(vertex, m_vertex_cluster_index); - } - - static final class QComparator extends Treap.Comparator { - EditShape m_shape; - Point2D pt_1 = new Point2D(); - Point2D pt_2 = new Point2D(); - - QComparator(EditShape shape) { - m_shape = shape; - } - - @Override - int compare(Treap treap, int vertex, int node) { - m_shape.getXY(vertex, pt_1); - int v_2 = treap.getElement(node); - m_shape.getXY(v_2, pt_2); - return pt_1.compare(pt_2); - } - } - - static final class QMonikerComparator extends Treap.MonikerComparator { - EditShape m_shape; - Point2D m_point = new Point2D(); - Point2D m_pt = new Point2D(); - - QMonikerComparator(EditShape shape) { - m_shape = shape; - } - - void setPoint(Point2D pt) { - m_point.setCoords(pt); - } - - @Override - int compare(Treap treap, int node) { - int v = treap.getElement(node); - m_shape.getXY(v, m_pt); - return m_point.compare(m_pt); - } - } - - ; - - void processSplitHelper1_(int index, int edge, - SegmentIntersector intersector) { - int clusterStart = getEdgeCluster(edge, 0); - Point2D ptClusterStart = new Point2D(); - getClusterXY(clusterStart, ptClusterStart); - Point2D ptClusterEnd = new Point2D(); - int clusterEnd = getEdgeCluster(edge, 1); - getClusterXY(clusterEnd, ptClusterEnd); - - // Collect all edges that are affected by the split and that are in the - // sweep structure. - int count = intersector.getResultSegmentCount(index); - Segment seg = intersector.getResultSegment(index, 0); - Point2D newStart = new Point2D(); - seg.getStartXY(newStart); - - if (!ptClusterStart.isEqual(newStart)) { - if (!m_complications) { - int res1 = ptClusterStart.compare(m_sweep_point); - int res2 = newStart.compare(m_sweep_point); - if (res1 * res2 < 0) { - m_complications = true;// point is not yet have been processed - // but moved before the sweep point, - // this will require - // repeating the cracking step and the sweep_vertical cannot - // help here - } - } - - // This cluster's position needs to be changed - getAffectedEdges(clusterStart, m_temp_edge_buffer); - m_modified_clusters.add(clusterStart); - } - - if (!m_complications && count > 1) { - int dir = ptClusterStart.compare(ptClusterEnd); - Point2D midPoint = seg.getEndXY(); - if (ptClusterStart.compare(midPoint) != dir - || midPoint.compare(ptClusterEnd) != dir) {// split segment - // midpoint is - // above the - // sweep line. - // Therefore the - // part of the - // segment - m_complications = true; - } else { - if (midPoint.compare(m_sweep_point) < 0) { - // midpoint moved below sweepline. - m_complications = true; - } - } - } - - seg = intersector.getResultSegment(index, count - 1); - Point2D newEnd = seg.getEndXY(); - if (!ptClusterEnd.isEqual(newEnd)) { - if (!m_complications) { - int res1 = ptClusterEnd.compare(m_sweep_point); - int res2 = newEnd.compare(m_sweep_point); - if (res1 * res2 < 0) { - m_complications = true;// point is not yet have been processed - // but moved before the sweep point. - } - } - // This cluster's position needs to be changed - getAffectedEdges(clusterEnd, m_temp_edge_buffer); - m_modified_clusters.add(clusterEnd); - } - - m_temp_edge_buffer.add(edge); - // Delete all nodes from the sweep structure that are affected by the - // change. - for (int i = 0, n = m_temp_edge_buffer.size(); i < n; i++) { - int e = m_temp_edge_buffer.get(i); - int sweepNode = getEdgeSweepNode(e); - if (StridedIndexTypeCollection.isValidElement(sweepNode)) { - m_sweep_structure.deleteNode(sweepNode, -1); - setEdgeSweepNode_(e, -1); - } - - int c_3 = StridedIndexTypeCollection.impossibleIndex3(); - if (e != edge && getEdgeSweepNode(e) != c_3)// c_3 means the edge is - // already in the - // m_edges_to_insert_in_sweep_structure - { - m_edges_to_insert_in_sweep_structure.add(e); - setEdgeSweepNode_(e, c_3); - } - } - m_temp_edge_buffer.clear(false); - } - - boolean checkAndFixIntersection_(int leftSweepNode, int rightSweepNode) { - int leftEdge = m_sweep_structure.getElement(leftSweepNode); - m_sweep_comparator.compare(m_sweep_structure, leftEdge, rightSweepNode); - if (m_sweep_comparator.intersectionDetected()) { - m_sweep_comparator.clearIntersectionDetectedFlag(); - fixIntersection_(leftSweepNode, rightSweepNode); - return true; - } - - return false; - } - - void fixIntersection_(int left, int right) { - m_b_cracked = true; - int edge1 = m_sweep_structure.getElement(left); - int edge2 = m_sweep_structure.getElement(right); - assert (edge1 != edge2); - Segment seg_1; - Segment seg_2; - int vertexList1 = getEdgeOriginVertices(edge1); - int origin1 = m_edge_vertices.getFirstElement(vertexList1); - int vertexList2 = getEdgeOriginVertices(edge2); - int origin2 = m_edge_vertices.getFirstElement(vertexList2); - seg_1 = m_shape.getSegment(origin1); - if (seg_1 == null) { - if (m_line_1 == null) - m_line_1 = new Line(); - m_shape.queryLineConnector(origin1, m_line_1); - seg_1 = m_line_1; - } - - seg_2 = m_shape.getSegment(origin2); - if (seg_2 == null) { - if (m_line_2 == null) - m_line_2 = new Line(); - m_shape.queryLineConnector(origin2, m_line_2); - seg_2 = m_line_2; - } - - // #ifdef _DEBUG_CRACKING_REPORT - // { - // Point_2D pt11, pt12, pt21, pt22; - // pt11 = seg_1->get_start_xy(); - // pt12 = seg_1->get_end_xy(); - // pt21 = seg_2->get_start_xy(); - // pt22 = seg_2->get_end_xy(); - // DEBUGPRINTF(L"Intersecting %d (%0.4f, %0.4f - %0.4f, %0.4f) and %d (%0.4f, %0.4f - %0.4f, %0.4f)\n", - // edge1, pt11.x, pt11.y, pt12.x, pt12.y, edge2, pt21.x, pt21.y, pt22.x, - // pt22.y); - // } - // #endif - - m_segment_intersector.pushSegment(seg_1); - m_segment_intersector.pushSegment(seg_2); - if (m_segment_intersector.intersect(m_tolerance, true)) - m_complications = true; - - - splitEdge_(edge1, edge2, -1, m_segment_intersector); - m_segment_intersector.clear(); - } - - void fixIntersectionPointSegment_(int cluster, int node) { - m_b_cracked = true; - int edge1 = m_sweep_structure.getElement(node); - Segment seg_1; - int vertexList1 = getEdgeOriginVertices(edge1); - int origin1 = m_edge_vertices.getFirstElement(vertexList1); - seg_1 = m_shape.getSegment(origin1); - if (seg_1 == null) { - if (m_line_1 == null) - m_line_1 = new Line(); - m_shape.queryLineConnector(origin1, m_line_1); - seg_1 = m_line_1; - } - - int clusterVertex = getClusterFirstVertex(cluster); - m_segment_intersector.pushSegment(seg_1); - - m_shape.queryPoint(clusterVertex, m_helper_point); - m_segment_intersector.intersect(m_tolerance, m_helper_point, 0, 1.0, - true); - - splitEdge_(edge1, -1, cluster, m_segment_intersector); - - m_segment_intersector.clear(); - } - - void insertNewEdges_() { - if (m_edges_to_insert_in_sweep_structure.size() == 0) - return; - - while (m_edges_to_insert_in_sweep_structure.size() != 0) { - if (m_edges_to_insert_in_sweep_structure.size() > Math.max( - (int) 100, m_shape.getTotalPointCount())) { - assert (false); - m_edges_to_insert_in_sweep_structure.clear(false); - m_complications = true; - break;// something strange going on here. bail out, forget about - // these edges and continue with sweep line. We'll - // iterate on the data one more time. - } - - int edge = m_edges_to_insert_in_sweep_structure.getLast(); - m_edges_to_insert_in_sweep_structure.removeLast(); - - assert (getEdgeSweepNode(edge) == StridedIndexTypeCollection - .impossibleIndex3()); - setEdgeSweepNode_(edge, -1); - int terminatingCluster = isEdgeOnSweepLine_(edge); - if (terminatingCluster != -1) { - insertNewEdgeToSweepStructure_(edge, terminatingCluster); - } - m_b_continuing_segment_chain_optimization = false; - } - } - - boolean insertNewEdgeToSweepStructure_(int edge, int terminatingCluster) { - assert (getEdgeSweepNode(edge) == -1); - int newEdgeNode; - if (m_b_continuing_segment_chain_optimization) { - newEdgeNode = m_sweep_structure.addElementAtPosition( - m_prev_neighbour, m_next_neighbour, edge, true, true, -1); - m_b_continuing_segment_chain_optimization = false; - } else { - newEdgeNode = m_sweep_structure.addUniqueElement(edge, -1); - } - - if (newEdgeNode == -1) {// a coinciding edge. - int existingNode = m_sweep_structure.getDuplicateElement(-1); - int existingEdge = m_sweep_structure.getElement(existingNode); - mergeEdges_(existingEdge, edge); - return false; - } - - // Remember the sweep structure node in the edge. - setEdgeSweepNode_(edge, newEdgeNode); - - if (m_sweep_comparator.intersectionDetected()) { - // The edge has been inserted into the sweep structure and an - // intersection has beebn found. The edge will be split and removed. - m_sweep_comparator.clearIntersectionDetectedFlag(); - int intersectionNode = m_sweep_comparator.getLastComparedNode(); - fixIntersection_(intersectionNode, newEdgeNode); - return true; - } else { - // The edge has been inserted into the sweep structure without - // problems (it does not intersect its neighbours) - } - - return false; - } - - Point2D pt_1 = new Point2D(); - Point2D pt_2 = new Point2D(); - - int isEdgeOnSweepLine_(int edge) { - int cluster_1 = getEdgeCluster(edge, 0); - int cluster2 = getEdgeCluster(edge, 1); - getClusterXY(cluster_1, pt_1); - getClusterXY(cluster2, pt_2); - if (Point2D.sqrDistance(pt_1, pt_2) <= m_tolerance_sqr) {// avoid - // degenerate - // segments - m_complications = true; - return -1; - } - int cmp1 = pt_1.compare(m_sweep_point); - int cmp2 = pt_2.compare(m_sweep_point); - if (cmp1 <= 0 && cmp2 > 0) { - return cluster2; - } - - if (cmp2 <= 0 && cmp1 > 0) { - return cluster_1; - } - - return -1; - } - - // void set_edit_shape(Edit_shape* shape); - // Fills the event queue and merges coincident clusters. - void fillEventQueue() { - AttributeStreamOfInt32 event_q = new AttributeStreamOfInt32(0); - event_q.reserve(m_shape.getTotalPointCount());// temporary structure to - // sort and find - // clusters - EditShape.VertexIterator iter = m_shape.queryVertexIterator(); - for (int vert = iter.next(); vert != -1; vert = iter.next()) { - if (m_shape.getUserIndex(vert, m_vertex_cluster_index) != -1) - event_q.add(vert); - } - - // Now we can merge coincident clusters and form the envent structure. - - // sort vertices lexicographically. - m_shape.sortVerticesSimpleByY_(event_q, 0, event_q.size()); - - // The m_event_q is the event structure for the planesweep algorithm. - // We could use any data structure that allows log(n) insertion and - // deletion in the sorted order and - // allow to iterate through in the sorted order. - - m_event_q.clear(); - // Populate the event structure - m_event_q.setCapacity(event_q.size()); - { - // The comparator is used to sort vertices by the m_event_q - m_event_q.setComparator(new QComparator(m_shape)); - } - - // create the vertex clusters and fill the event structure m_event_q. - // Because most vertices are expected to be non clustered, we create - // clusters only for actual clusters to save some memory. - Point2D cluster_pt = new Point2D(); - cluster_pt.setNaN(); - int cluster = -1; - Point2D pt = new Point2D(); - for (int index = 0, nvertex = event_q.size(); index < nvertex; index++) { - int vertex = event_q.get(index); - m_shape.getXY(vertex, pt); - if (pt.isEqual(cluster_pt)) { - int vertexCluster = m_shape.getUserIndex(vertex, - m_vertex_cluster_index); - mergeClusters_(cluster, vertexCluster); - continue; - } - - cluster = getClusterFromVertex(vertex); - // add a vertex to the event queue - m_shape.getXY(vertex, cluster_pt); - int eventQnode = m_event_q.addBiggestElement(vertex, -1); // this - // method - // does - // not - // call - // comparator's - // compare, - // assuming - // sorted - // order. - setClusterEventQNode_(cluster, eventQnode); - } - } - - void fillEventQueuePass2() { - AttributeStreamOfInt32 event_q = new AttributeStreamOfInt32(0); - event_q.reserve(m_shape.getTotalPointCount());// temporary structure to - // sort and find - // clusters - for (int node = m_event_q.getFirst(-1); node != -1; node = m_event_q - .getNext(node)) { - int v = m_event_q.getElement(node); - event_q.add(v); - } - - assert (event_q.size() == m_event_q.size(-1)); - - m_event_q.clear(); - - // sort vertices lexicographically. - m_shape.sortVerticesSimpleByY_(event_q, 0, event_q.size()); - - for (int index = 0, nvertex = event_q.size(); index < nvertex; index++) { - int vertex = event_q.get(index); - int cluster = getClusterFromVertex(vertex); - int eventQnode = m_event_q.addBiggestElement(vertex, -1); // this - // method - // does - // not - // call - // comparator's - // compare, - // assuming - // sorted - // order. - setClusterEventQNode_(cluster, eventQnode); - } - } - - // Returns edges already in the sweep structure that are affected by the - // change of cluster coordinate. - void getAffectedEdges(int cluster, AttributeStreamOfInt32 edges) { - int first_edge = getClusterFirstEdge(cluster); - if (first_edge == -1) - return; - - int edge = first_edge; - do { - int sweepNode = getEdgeSweepNode(edge); - if (StridedIndexTypeCollection.isValidElement(sweepNode)) { - edges.add(edge); - } - edge = getNextEdge(edge, cluster); - } while (edge != first_edge); - } - - // Updates all vertices of the cluster to new coordinate - void updateClusterXY(int cluster, Point2D pt) { - int vertexList = getClusterVertices(cluster); - for (int vh = m_cluster_vertices.getFirst(vertexList); vh != -1; vh = m_cluster_vertices - .getNext(vh)) { - int vertex = m_cluster_vertices.getElement(vh); - m_shape.setXY(vertex, pt); - } - } - - // Modifies the given edges given the intersector class and the result - // index. - // The function updates the the event structure and puts new edges into the - // m_edges_to_insert_in_sweep_structure. - void splitEdge_(int edge1, int edge2, int intersectionCluster, - SegmentIntersector intersector) { - - disconnectEdge_(edge1);// disconnects the edge from the clusters. The - // edge still remembers the clusters. - if (edge2 != -1) - disconnectEdge_(edge2);// disconnects the edge from the clusters. - // The edge still remembers the clusters. - - // Collect all edges that are affected when the clusters change position - // due to snapping - // The edges are collected in m_edges_to_insert_in_sweep_structure. - // Collect the modified clusters in m_modified_clusters. - processSplitHelper1_(0, edge1, intersector); - if (edge2 != -1) - processSplitHelper1_(1, edge2, intersector); - - if (intersectionCluster != -1) { - intersector.getResultPoint().getXY(pt_1); - getClusterXY(intersectionCluster, pt_2); - if (!pt_2.isEqual(pt_1)) - m_modified_clusters.add(intersectionCluster); - } - - // remove modified clusters from the event queue. We'll reincert them - // later - for (int i = 0, n = m_modified_clusters.size(); i < n; i++) { - int cluster = m_modified_clusters.get(i); - int eventQnode = getClusterEventQNode(cluster); - if (eventQnode != -1) { - m_event_q.deleteNode(eventQnode, -1); - setClusterEventQNode_(cluster, -1); - } - } - - int edgeOrigins1 = getEdgeOriginVertices(edge1); - int edgeOrigins2 = (edge2 != -1) ? getEdgeOriginVertices(edge2) : -1; - - // Adjust the vertex coordinates and split the segments in the the edit - // shape. - applyIntersectorToEditShape_(edgeOrigins1, intersector, 0); - if (edge2 != -1) - applyIntersectorToEditShape_(edgeOrigins2, intersector, 1); - - // Produce clusters, and new edges. The new edges are added to - // m_edges_to_insert_in_sweep_structure. - createEdgesAndClustersFromSplitEdge_(edge1, intersector, 0); - if (edge2 != -1) - createEdgesAndClustersFromSplitEdge_(edge2, intersector, 1); - - m_edge_vertices.deleteList(edgeOrigins1); - deleteEdge_(edge1); - - if (edge2 != -1) { - m_edge_vertices.deleteList(edgeOrigins2); - deleteEdge_(edge2); - } - - // insert clusters into the event queue and the edges into the sweep - // structure. - for (int i = 0, n = m_modified_clusters.size(); i < n; i++) { - int cluster = m_modified_clusters.get(i); - if (cluster == m_sweep_point_cluster) - m_b_sweep_point_cluster_was_modified = true; - - int eventQnode = getClusterEventQNode(cluster); - if (eventQnode == -1) { - int vertex = getClusterFirstVertex(cluster); - assert (getClusterFromVertex(vertex) == cluster); - - eventQnode = m_event_q.addUniqueElement(vertex, -1);// O(logN) - // operation - if (eventQnode == -1) {// the cluster is coinciding with another - // one. merge. - int existingNode = m_event_q.getDuplicateElement(-1); - int v = m_event_q.getElement(existingNode); - assert (m_shape.isEqualXY(vertex, v)); - int existingCluster = getClusterFromVertex(v); - mergeClusters_(existingCluster, cluster); - } else { - setClusterEventQNode_(cluster, eventQnode); - } - } else { - // if already inserted (probably impossible) case - } - } - - m_modified_clusters.clear(false); - } - - // Returns a cluster's xy. - void getClusterXY(int cluster, Point2D ptOut) { - int vindex = getClusterVertexIndex(cluster); - m_shape.getXYWithIndex(vindex, ptOut); - } - - int getClusterFirstVertex(int cluster) { - int vertexList = getClusterVertices(cluster); - int vertex = m_cluster_vertices.getFirstElement(vertexList); - return vertex; - } - - boolean sweepImpl_() { - m_b_sweep_point_cluster_was_modified = false; - m_sweep_point_cluster = -1; - if (m_sweep_comparator == null) { - m_sweep_structure.disableBalancing(); - m_sweep_comparator = new SimplifySweepComparator(this); - m_sweep_structure.setComparator(m_sweep_comparator); - } - - AttributeStreamOfInt32 edgesToDelete = new AttributeStreamOfInt32(0); - SimplifySweepMonikerComparator sweepMoniker = null; - QMonikerComparator moniker = null; - - int iterationCounter = 0; - m_prev_neighbour = -1; - m_next_neighbour = -1; - m_b_continuing_segment_chain_optimization = false; - - int c_2 = StridedIndexTypeCollection.impossibleIndex2(); - int c_3 = StridedIndexTypeCollection.impossibleIndex3(); - assert (c_2 != c_3); - - for (int eventQnode = m_event_q.getFirst(-1); eventQnode != -1; ) { - iterationCounter++; - m_b_continuing_segment_chain_optimization = false; - - int vertex = m_event_q.getElement(eventQnode); - m_sweep_point_cluster = getClusterFromVertex(vertex); - m_shape.getXY(vertex, m_sweep_point); - - m_sweep_comparator.setSweepY(m_sweep_point.y, m_sweep_point.x);// move - // the - // sweep - // line - - boolean bDisconnectedCluster = false; - {// scope - int first_edge = getClusterFirstEdge(m_sweep_point_cluster); - bDisconnectedCluster = first_edge == -1; - if (!bDisconnectedCluster) { - int edge = first_edge; - do { - int sweepNode = getEdgeSweepNode(edge); - if (sweepNode == -1) { - m_edges_to_insert_in_sweep_structure.add(edge); - setEdgeSweepNode_(edge, c_3);// mark that its in - // m_edges_to_insert_in_sweep_structure - } else if (sweepNode != c_3) { - assert (StridedIndexTypeCollection.isValidElement(sweepNode)); - edgesToDelete.add(sweepNode); - } - edge = getNextEdge(edge, m_sweep_point_cluster); - } while (edge != first_edge); - } - } - - // st_counter_insertions_peaks += edgesToDelete.size() == 0 && - // m_edges_to_insert_in_sweep_structure.size() > 0; - // First step is to delete the edges that terminate in the - // cluster. - // During that step we also determine the left and right neighbors - // of the deleted bunch and then check if those left and right - // intersect or not. - if (edgesToDelete.size() > 0) { - m_b_continuing_segment_chain_optimization = (edgesToDelete - .size() == 1 && m_edges_to_insert_in_sweep_structure - .size() == 1); - - // Mark nodes that need to be deleted by setting c_2 to the - // edge's sweep node member. - for (int i = 0, n = edgesToDelete.size(); i < n; i++) { - int edge = m_sweep_structure.getElement(edgesToDelete - .get(i)); - setEdgeSweepNode_(edge, c_2); - } - - int left = c_2; - int right = c_2; - // Determine left and right nodes for the bunch of nodes we are - // deleting. - for (int i = 0, n = edgesToDelete.size(); i < n; i++) { - int sweepNode = edgesToDelete.get(i); - if (left == c_2) { - int localleft = m_sweep_structure.getPrev(sweepNode); - if (localleft != -1) { - int edge = m_sweep_structure.getElement(localleft); - int node = getEdgeSweepNode(edge); - if (node != c_2) - left = localleft; - } else - left = -1; - } - - if (right == c_2) { - int localright = m_sweep_structure.getNext(sweepNode); - if (localright != -1) { - int edge = m_sweep_structure.getElement(localright); - int node = getEdgeSweepNode(edge); - if (node != c_2) - right = localright; - } else - right = -1; - } - - if (left != c_2 && right != c_2) - break; - } - - assert (left != c_2 && right != c_2); - // Now delete the bunch. - for (int i = 0, n = edgesToDelete.size(); i < n; i++) { - int sweepNode = edgesToDelete.get(i); - int edge = m_sweep_structure.getElement(sweepNode); - m_sweep_structure.deleteNode(sweepNode, -1); - setEdgeSweepNode_(edge, -1); - } - - edgesToDelete.clear(false); - - m_prev_neighbour = left != -1 ? left : -1; - m_next_neighbour = right != -1 ? right : -1; - - // Now check if the left and right we found intersect or not. - if (left != -1 && right != -1) { - if (!m_b_continuing_segment_chain_optimization) { - boolean bIntersected = checkAndFixIntersection_(left, - right); - } - } else { - if ((left == -1) && (right == -1)) - m_b_continuing_segment_chain_optimization = false; - } - } else { - // edgesToDelete.size() == 0 - nothing to delete here. This is a - // cluster which has all edges directed up or a disconnected - // cluster. - - if (bDisconnectedCluster) {// check standalone cluster (point or - // multipoint) if it cracks an edge. - if (sweepMoniker == null) - sweepMoniker = new SimplifySweepMonikerComparator(this); - - sweepMoniker.setPoint(m_sweep_point); - m_sweep_structure.searchUpperBound(sweepMoniker, -1); - if (sweepMoniker.intersectionDetected()) { - sweepMoniker.clearIntersectionDetectedFlag(); - fixIntersectionPointSegment_(m_sweep_point_cluster, - sweepMoniker.getCurrentNode()); - } - } - } - - // Now insert edges that start at the cluster and go up - insertNewEdges_(); - - if (m_b_sweep_point_cluster_was_modified) { - m_b_sweep_point_cluster_was_modified = false; - if (moniker == null) - moniker = new QMonikerComparator(m_shape); - moniker.setPoint(m_sweep_point); - eventQnode = m_event_q.searchUpperBound(moniker, -1); - } else - eventQnode = m_event_q.getNext(eventQnode); - } - - return m_b_cracked; - } - - void setEditShape_(EditShape shape) { - // Populate the cluster and edge structures. - m_shape = shape; - m_vertex_cluster_index = m_shape.createUserIndex(); - - m_edges.setCapacity(shape.getTotalPointCount() + 32); - - m_clusters.setCapacity(shape.getTotalPointCount()); - - m_cluster_vertices.reserveLists(shape.getTotalPointCount()); - m_cluster_vertices.reserveNodes(shape.getTotalPointCount()); - - m_edge_vertices.reserveLists(shape.getTotalPointCount() + 32); - m_edge_vertices.reserveNodes(shape.getTotalPointCount() + 32); - - for (int geometry = m_shape.getFirstGeometry(); geometry != -1; geometry = m_shape - .getNextGeometry(geometry)) { - boolean bMultiPath = Geometry.isMultiPath(m_shape - .getGeometryType(geometry)); - - if (!bMultiPath) {// for multipoints do not add edges. - assert (m_shape.getGeometryType(geometry) == Geometry.GeometryType.MultiPoint); - - for (int path = m_shape.getFirstPath(geometry); path != -1; path = m_shape - .getNextPath(path)) { - int vertex = m_shape.getFirstVertex(path); - for (int i = 0, n = m_shape.getPathSize(path); i < n; i++) { - // int cluster - newCluster_(vertex); - vertex = m_shape.getNextVertex(vertex); - } - } - continue; - } - - for (int path = m_shape.getFirstPath(geometry); path != -1; path = m_shape - .getNextPath(path)) { - int path_size = m_shape.getPathSize(path); - assert (path_size > 1); - int first_vertex = m_shape.getFirstVertex(path); - - // first------------------ - int firstCluster = newCluster_(first_vertex); - int first_edge = newEdge_(first_vertex); - addEdgeToCluster(first_edge, firstCluster); - int prevEdge = first_edge; - int vertex = m_shape.getNextVertex(first_vertex); - for (int index = 0, n = path_size - 2; index < n; index++) { - int nextvertex = m_shape.getNextVertex(vertex); - // ------------x------------ - int cluster = newCluster_(vertex); - addEdgeToCluster(prevEdge, cluster); - int newEdge = newEdge_(vertex); - addEdgeToCluster(newEdge, cluster); - prevEdge = newEdge; - vertex = nextvertex; - } - - // ------------------lastx - if (m_shape.isClosedPath(path)) { - int cluster = newCluster_(vertex); - addEdgeToCluster(prevEdge, cluster); - // close the path - // lastx------------------firstx - int newEdge = newEdge_(vertex); - addEdgeToCluster(newEdge, cluster); - addEdgeToCluster(newEdge, firstCluster); - } else { - // ------------------lastx - int cluster = newCluster_(vertex); - addEdgeToCluster(prevEdge, cluster); - } - - } - } - - fillEventQueue(); - - // int perPoint = estimate_memory_size() / - // m_shape.get_total_point_count(); - // perPoint = 0; - } + PlaneSweepCrackerHelper() { + m_edges = new StridedIndexTypeCollection(8); + m_clusters = new StridedIndexTypeCollection(5); + m_cluster_vertices = new IndexMultiList(); + m_edge_vertices = new IndexMultiList(); + m_complications = false; + m_sweep_point = new Point2D(); + m_sweep_point.setCoords(0, 0); + m_tolerance = 0; + m_vertex_cluster_index = -1; + m_b_cracked = false; + m_shape = null; + + m_event_q = new Treap(); + m_sweep_structure = new Treap(); + m_edges_to_insert_in_sweep_structure = new AttributeStreamOfInt32(0); + m_segment_intersector = new SegmentIntersector(); + m_temp_edge_buffer = new AttributeStreamOfInt32(0); + m_modified_clusters = new AttributeStreamOfInt32(0); + m_helper_point = new Point(); + } + + // For use in Cluster/Cracker loop + boolean sweep(EditShape shape, double tolerance) { + Transformation2D transform = new Transformation2D(); + transform.setSwapCoordinates(); + shape.applyTransformation(transform);// swap coordinates for the sweep + // along x + setEditShape_(shape); + m_b_cracked = false; + m_tolerance = tolerance; + m_tolerance_sqr = tolerance * tolerance; + + boolean b_cracked = sweepImpl_(); + shape.applyTransformation(transform); + if (!b_cracked) { + fillEventQueuePass2(); + b_cracked |= sweepImpl_(); + } + + if (m_vertex_cluster_index != -1) { + m_shape.removeUserIndex(m_vertex_cluster_index); + m_vertex_cluster_index = -1; + } + + m_shape = null; + return m_b_cracked; + } + + // Does one pass sweep vertically + boolean sweepVertical(EditShape shape, double tolerance) { + setEditShape_(shape); + m_b_cracked = false; + m_tolerance = tolerance; + m_tolerance_sqr = tolerance * tolerance; + m_complications = false; + boolean bresult = sweepImpl_(); + if (!m_complications) { + int filtered = shape.filterClosePoints(tolerance, true, false); + m_complications = filtered == 1; + bresult |= filtered == 1; + } + + if (m_vertex_cluster_index != -1) { + m_shape.removeUserIndex(m_vertex_cluster_index); + m_vertex_cluster_index = -1; + } + + m_shape = null; + return bresult; + } + + boolean hadCompications() { + return m_complications; + } + + private EditShape m_shape; + private StridedIndexTypeCollection m_edges; + private StridedIndexTypeCollection m_clusters; + private IndexMultiList m_cluster_vertices; + private IndexMultiList m_edge_vertices; + private Point m_helper_point; + + private Treap m_event_q; + private Treap m_sweep_structure; + + boolean m_complications; + + static final class SimplifySweepComparator extends SweepComparator { + PlaneSweepCrackerHelper m_parent; + + SimplifySweepComparator(PlaneSweepCrackerHelper parent) { + super(parent.m_shape, parent.m_tolerance, false); + m_parent = parent; + } + + @Override + int compare(Treap treap, int elm, int node) { + // Compares two segments on a sweep line passing through m_sweep_y, + // m_sweep_x. + if (m_b_intersection_detected) + return -1; + + int vertex_list_left = m_parent.getEdgeOriginVertices(elm); + int left = m_parent.m_edge_vertices + .getFirstElement(vertex_list_left); + + int right_elm = treap.getElement(node); + assert (m_parent.getEdgeSweepNode(right_elm) == node); + int vertex_list_right = m_parent.getEdgeOriginVertices(right_elm); + int right = m_parent.m_edge_vertices + .getFirstElement(vertex_list_right); + + m_current_node = node; + return compareSegments(elm, left, right_elm, right); + } + } + + ; + + static final class SimplifySweepMonikerComparator extends + SweepMonkierComparator { + PlaneSweepCrackerHelper m_parent; + + SimplifySweepMonikerComparator(PlaneSweepCrackerHelper parent) { + super(parent.m_shape, parent.m_tolerance); + m_parent = parent; + } + + @Override + int compare(Treap treap, int node) { + // Compares two segments on a sweep line passing through m_sweep_y, + // m_sweep_x. + if (m_b_intersection_detected) + return -1; + + int elm = treap.getElement(node); + int vertexList = m_parent.getEdgeOriginVertices(elm); + int vertex = m_parent.m_edge_vertices.getFirstElement(vertexList); + + m_current_node = node; + return compareVertex_(treap, node, vertex); + } + } + + ; + + SimplifySweepComparator m_sweep_comparator; + + AttributeStreamOfInt32 m_temp_edge_buffer; + AttributeStreamOfInt32 m_modified_clusters; + AttributeStreamOfInt32 m_edges_to_insert_in_sweep_structure; + + int m_prev_neighbour; + int m_next_neighbour; + boolean m_b_continuing_segment_chain_optimization;// set to true, when the + // cluster has two edges + // attached, one is + // below and another + // above the sweep line + + SegmentIntersector m_segment_intersector; + + Line m_line_1; + Line m_line_2; + + Point2D m_sweep_point; + double m_tolerance; + double m_tolerance_sqr; + + int m_sweep_point_cluster; + int m_vertex_cluster_index; + + boolean m_b_cracked; + boolean m_b_sweep_point_cluster_was_modified;// set to true if the + // coordinates of the + // cluster, where the sweep + // line was, has been + // changed. + + int getEdgeCluster(int edge, int end) { + assert (end == 0 || end == 1); + return m_edges.getField(edge, 0 + end); + } + + void setEdgeCluster_(int edge, int end, int cluster) { + assert (end == 0 || end == 1); + m_edges.setField(edge, 0 + end, cluster); + } + + // Edge may have several origin vertices, when there are two or more equal + // segements in that edge + // We have to store edge origin separately from the cluster vertices, + // because cluster can have several different edges started on it. + int getEdgeOriginVertices(int edge) { + return m_edges.getField(edge, 2); + } + + void setEdgeOriginVertices_(int edge, int vertices) { + m_edges.setField(edge, 2, vertices); + } + + int getNextEdgeEx(int edge, int end) { + assert (end == 0 || end == 1); + return m_edges.getField(edge, 3 + end); + } + + void setNextEdgeEx_(int edge, int end, int next_edge) { + assert (end == 0 || end == 1); + m_edges.setField(edge, 3 + end, next_edge); + } + + // int get_prev_edge_ex(int edge, int end) + // { + // assert(end == 0 || end == 1); + // return m_edges.get_field(edge, 5 + end); + // } + // void set_prev_edge_ex_(int edge, int end, int prevEdge) + // { + // assert(end == 0 || end == 1); + // m_edges.set_field(edge, 5 + end, prevEdge); + // } + + int getEdgeSweepNode(int edge) { + return m_edges.getField(edge, 7); + } + + void setEdgeSweepNode_(int edge, int sweepNode) { + m_edges.setField(edge, 7, sweepNode); + } + + int getNextEdge(int edge, int cluster) { + int end = getEdgeEnd(edge, cluster); + assert (end == 0 || end == 1); + return m_edges.getField(edge, 3 + end); + } + + void setNextEdge_(int edge, int cluster, int next_edge) { + int end = getEdgeEnd(edge, cluster); + assert (end == 0 || end == 1); + m_edges.setField(edge, 3 + end, next_edge); + } + + int getPrevEdge(int edge, int cluster) { + int end = getEdgeEnd(edge, cluster); + assert (end == 0 || end == 1); + return m_edges.getField(edge, 5 + end); + } + + void setPrevEdge_(int edge, int cluster, int prevEdge) { + int end = getEdgeEnd(edge, cluster); + assert (end == 0 || end == 1); + m_edges.setField(edge, 5 + end, prevEdge); + } + + int getClusterVertices(int cluster) { + return m_clusters.getField(cluster, 0); + } + + void setClusterVertices_(int cluster, int vertices) { + m_clusters.setField(cluster, 0, vertices); + } + + int getClusterVertexIndex(int cluster) { + return m_clusters.getField(cluster, 4); + } + + void setClusterVertexIndex_(int cluster, int vindex) { + m_clusters.setField(cluster, 4, vindex); + } + + int getClusterSweepEdgeList(int cluster) { + return m_clusters.getField(cluster, 2); + } + + void setClusterSweepEdgeList_(int cluster, int sweep_edges) { + m_clusters.setField(cluster, 2, sweep_edges); + } + + int getClusterFirstEdge(int cluster) { + return m_clusters.getField(cluster, 1); + } + + void setClusterFirstEdge_(int cluster, int first_edge) { + m_clusters.setField(cluster, 1, first_edge); + } + + int getClusterEventQNode(int cluster) { + return m_clusters.getField(cluster, 3); + } + + void setClusterEventQNode_(int cluster, int node) { + m_clusters.setField(cluster, 3, node); + } + + int newCluster_(int vertex) { + int cluster = m_clusters.newElement(); + int vertexList = m_cluster_vertices.createList(); + setClusterVertices_(cluster, vertexList); + if (vertex != -1) { + m_cluster_vertices.addElement(vertexList, vertex); + assert (m_shape.getUserIndex(vertex, m_vertex_cluster_index) == -1); + m_shape.setUserIndex(vertex, m_vertex_cluster_index, cluster); + setClusterVertexIndex_(cluster, m_shape.getVertexIndex(vertex)); + } else { + setClusterVertexIndex_(cluster, -1); + } + + return cluster; + } + + void deleteCluster_(int cluster) { + m_clusters.deleteElement(cluster); + } + + void addVertexToCluster_(int cluster, int vertex) { + assert (m_shape.getUserIndex(vertex, m_vertex_cluster_index) == -1); + int vertexList = getClusterVertices(cluster); + m_cluster_vertices.addElement(vertexList, vertex); + m_shape.setUserIndex(vertex, m_vertex_cluster_index, cluster); + } + + // Creates a new unattached edge with the given origin. + int newEdge_(int origin_vertex) { + int edge = m_edges.newElement(); + int edgeVertices = m_edge_vertices.createList(); + setEdgeOriginVertices_(edge, edgeVertices); + if (origin_vertex != -1) + m_edge_vertices.addElement(edgeVertices, origin_vertex); + + return edge; + } + + void addVertexToEdge_(int edge, int vertex) { + int vertexList = getEdgeOriginVertices(edge); + m_edge_vertices.addElement(vertexList, vertex); + } + + void deleteEdge_(int edge) { + m_edges.deleteElement(edge); + int ind = m_edges_to_insert_in_sweep_structure.findElement(edge); + if (ind >= 0) + m_edges_to_insert_in_sweep_structure.popElement(ind); + } + + void addEdgeToCluster(int edge, int cluster) { + if (getEdgeCluster(edge, 0) == -1) { + assert (getEdgeCluster(edge, 1) != cluster); + setEdgeCluster_(edge, 0, cluster); + } else if (getEdgeCluster(edge, 1) == -1) { + assert (getEdgeCluster(edge, 0) != cluster); + setEdgeCluster_(edge, 1, cluster); + } else + throw GeometryException.GeometryInternalError(); + + addEdgeToClusterImpl_(edge, cluster);// simply adds the edge to the list + // of cluster edges. + } + + void addEdgeToClusterImpl_(int edge, int cluster) { + int first_edge = getClusterFirstEdge(cluster); + if (first_edge != -1) { + int next = getNextEdge(first_edge, cluster); + setPrevEdge_(next, cluster, edge); + setNextEdge_(edge, cluster, next); + setNextEdge_(first_edge, cluster, edge); + setPrevEdge_(edge, cluster, first_edge); + } else { + setPrevEdge_(edge, cluster, edge);// point to itself + setNextEdge_(edge, cluster, edge); + setClusterFirstEdge_(cluster, edge); + } + } + + int getEdgeEnd(int edge, int cluster) { + if (getEdgeCluster(edge, 0) == cluster) { + assert (getEdgeCluster(edge, 1) != cluster); + return 0; + } else { + assert (getEdgeCluster(edge, 1) == cluster); + return 1; + } + } + + // Merges two coincident clusters into one. The cluster2 becomes invalid. + void mergeClusters_(int cluster_1, int cluster2) { + // dbg_check_cluster_(cluster_1); + // dbg_check_cluster_(cluster2); + int eventQnode = getClusterEventQNode(cluster2); + if (eventQnode != -1) { + m_event_q.deleteNode(eventQnode, -1); + setClusterEventQNode_(cluster2, -1); + } + + int firstEdge1 = getClusterFirstEdge(cluster_1); + int firstEdge2 = getClusterFirstEdge(cluster2); + + if (firstEdge2 != -1) {// scope + int edge2 = firstEdge2; + int lastEdge = firstEdge2; + boolean bForceContinue = false; + // Delete edges that connect cluster_1 and cluster2. + do { + // dbg_check_edge_(edge2); + bForceContinue = false; + // assert(!StridedIndexTypeCollection.isValidElement(getEdgeSweepNode(edge2))); + int end = getEdgeEnd(edge2, cluster2); + int nextEdge2 = getNextEdgeEx(edge2, end); + if (getEdgeCluster(edge2, (end + 1) & 1) == cluster_1) { // Snapping + // clusters + // that + // are + // connected + // with + // an + // edge + // Delete + // the + // edge. + disconnectEdge_(edge2); + int edgeOrigins2 = getEdgeOriginVertices(edge2); + m_edge_vertices.deleteList(edgeOrigins2); + deleteEdge_(edge2); + if (edge2 == nextEdge2) {// deleted last edge connecting to + // the cluster2 (all connections + // are degenerate) + firstEdge2 = -1; + break; + } + if (firstEdge2 == edge2) { + firstEdge2 = getClusterFirstEdge(cluster2); + lastEdge = nextEdge2; + bForceContinue = true; + } + } else { + assert (edge2 != getClusterFirstEdge(cluster_1)); + } + edge2 = nextEdge2; + } while (edge2 != lastEdge || bForceContinue); + + if (firstEdge2 != -1) { + // set the cluster to the edge ends + do { + int end = getEdgeEnd(edge2, cluster2); + int nextEdge2 = getNextEdgeEx(edge2, end); + assert (edge2 != getClusterFirstEdge(cluster_1)); + setEdgeCluster_(edge2, end, cluster_1); + edge2 = nextEdge2; + } while (edge2 != lastEdge); + + firstEdge1 = getClusterFirstEdge(cluster_1); + if (firstEdge1 != -1) { + int next1 = getNextEdge(firstEdge1, cluster_1); + int next2 = getNextEdge(firstEdge2, cluster_1); + if (next1 == firstEdge1) { + setClusterFirstEdge_(cluster_1, firstEdge2); + addEdgeToClusterImpl_(firstEdge1, cluster_1); + setClusterFirstEdge_(cluster_1, firstEdge1); + } else if (next2 == firstEdge2) { + addEdgeToClusterImpl_(firstEdge2, cluster_1); + } + + setNextEdge_(firstEdge2, cluster_1, next1); + setPrevEdge_(next1, cluster_1, firstEdge2); + setNextEdge_(firstEdge1, cluster_1, next2); + setPrevEdge_(next2, cluster_1, firstEdge1); + } else { + setClusterFirstEdge_(cluster_1, firstEdge2); + } + } + } + + int vertices1 = getClusterVertices(cluster_1); + int vertices2 = getClusterVertices(cluster2); + // Update cluster info on vertices. + for (int vh = m_cluster_vertices.getFirst(vertices2); vh != -1; vh = m_cluster_vertices + .getNext(vh)) { + int v = m_cluster_vertices.getElement(vh); + m_shape.setUserIndex(v, m_vertex_cluster_index, cluster_1); + } + m_cluster_vertices.concatenateLists(vertices1, vertices2); + deleteCluster_(cluster2); + // dbg_check_cluster_(cluster_1); + } + + // Merges two coincident edges into one. The edge2 becomes invalid. + void mergeEdges_(int edge1, int edge2) { + // dbg_check_edge_(edge1); + int cluster_1 = getEdgeCluster(edge1, 0); + int cluster2 = getEdgeCluster(edge1, 1); + int cluster21 = getEdgeCluster(edge2, 0); + int cluster22 = getEdgeCluster(edge2, 1); + + int originVertices1 = getEdgeOriginVertices(edge1); + int originVertices2 = getEdgeOriginVertices(edge2); + m_edge_vertices.concatenateLists(originVertices1, originVertices2); + if (edge2 == getClusterFirstEdge(cluster_1)) + setClusterFirstEdge_(cluster_1, edge1); + if (edge2 == getClusterFirstEdge(cluster2)) + setClusterFirstEdge_(cluster2, edge1); + + disconnectEdge_(edge2);// disconnects the edge2 from the clusters. + deleteEdge_(edge2); + + if (!((cluster_1 == cluster21 && cluster2 == cluster22) || (cluster2 == cluster21 && cluster_1 == cluster22))) { + // Merged edges have different clusters (clusters have not yet been + // merged) + // merge clusters before merging the edges + getClusterXY(cluster_1, pt_1); + getClusterXY(cluster21, pt_2); + if (pt_1.isEqual(pt_2)) { + if (cluster_1 != cluster21) { + mergeClusters_(cluster_1, cluster21); + assert (!m_modified_clusters.hasElement(cluster21)); + } + if (cluster2 != cluster22) { + mergeClusters_(cluster2, cluster22); + assert (!m_modified_clusters.hasElement(cluster22)); + } + } else { + if (cluster2 != cluster21) { + mergeClusters_(cluster2, cluster21); + assert (!m_modified_clusters.hasElement(cluster21)); + } + if (cluster_1 != cluster22) { + mergeClusters_(cluster_1, cluster22); + assert (!m_modified_clusters.hasElement(cluster22)); + } + } + } else { + // Merged edges have equal clusters. + } + // dbg_check_edge_(edge1); + } + + // Disconnects the edge from its clusters. + void disconnectEdge_(int edge) { + int cluster_1 = getEdgeCluster(edge, 0); + int cluster2 = getEdgeCluster(edge, 1); + disconnectEdgeFromCluster_(edge, cluster_1); + disconnectEdgeFromCluster_(edge, cluster2); + } + + // Disconnects the edge from a cluster it is connected to. + void disconnectEdgeFromCluster_(int edge, int cluster) { + int next = getNextEdge(edge, cluster); + assert (getPrevEdge(next, cluster) == edge); + int prev = getPrevEdge(edge, cluster); + assert (getNextEdge(prev, cluster) == edge); + int first_edge = getClusterFirstEdge(cluster); + if (next != edge) { + setNextEdge_(prev, cluster, next); + setPrevEdge_(next, cluster, prev); + if (first_edge == edge) + setClusterFirstEdge_(cluster, next); + } else + setClusterFirstEdge_(cluster, -1); + } + + void applyIntersectorToEditShape_(int edgeOrigins, + SegmentIntersector intersector, int intersector_index) { + // Split Edit_shape segments and produce new vertices. Modify + // coordinates as necessary. No vertices are deleted. + int vertexHandle = m_edge_vertices.getFirst(edgeOrigins); + int first_vertex = m_edge_vertices.getElement(vertexHandle); + + int cluster_1 = getClusterFromVertex(first_vertex); + int cluster2 = getClusterFromVertex(m_shape.getNextVertex(first_vertex)); + boolean bComplexCase = cluster_1 == cluster2; + assert (!bComplexCase);// if it ever asserts there will be a bug. Should + // be a case of a curve that forms a loop. + + m_shape.splitSegment_(first_vertex, intersector, intersector_index, + true); + for (vertexHandle = m_edge_vertices.getNext(vertexHandle); vertexHandle != -1; vertexHandle = m_edge_vertices + .getNext(vertexHandle)) { + int vertex = m_edge_vertices.getElement(vertexHandle); + boolean b_forward = getClusterFromVertex(vertex) == cluster_1; + assert ((b_forward && getClusterFromVertex(m_shape + .getNextVertex(vertex)) == cluster2) || (getClusterFromVertex(vertex) == cluster2 && getClusterFromVertex(m_shape + .getNextVertex(vertex)) == cluster_1)); + m_shape.splitSegment_(vertex, intersector, intersector_index, + b_forward); + } + + // Now apply the updated coordinates to all vertices in the cluster_1 + // and cluster2. + Point2D pt_0; + Point2D pt_1; + pt_0 = intersector.getResultSegment(intersector_index, 0).getStartXY(); + pt_1 = intersector.getResultSegment(intersector_index, + intersector.getResultSegmentCount(intersector_index) - 1) + .getEndXY(); + updateClusterXY(cluster_1, pt_0); + updateClusterXY(cluster2, pt_1); + } + + void createEdgesAndClustersFromSplitEdge_(int edge1, + SegmentIntersector intersector, int intersector_index) { + // dbg_check_new_edges_array_(); + // The method uses m_temp_edge_buffer for temporary storage and clears + // it at the end. + int edgeOrigins1 = getEdgeOriginVertices(edge1); + + // create new edges and clusters + // Note that edge1 is disconnected from its clusters already (the + // cluster's edge list does not contain it). + int cluster_1 = getEdgeCluster(edge1, 0); + int cluster2 = getEdgeCluster(edge1, 1); + int prevEdge = newEdge_(-1); + m_edges_to_insert_in_sweep_structure.add(prevEdge); + int c_3 = StridedIndexTypeCollection.impossibleIndex3(); + setEdgeSweepNode_(prevEdge, c_3);// mark that its in + // m_edges_to_insert_in_sweep_structure + m_temp_edge_buffer.add(prevEdge); + addEdgeToCluster(prevEdge, cluster_1); + for (int i = 1, n = intersector + .getResultSegmentCount(intersector_index); i < n; i++) {// each + // iteration + // adds + // new + // Cluster + // and + // Edge. + int newCluster = newCluster_(-1); + m_modified_clusters.add(newCluster); + m_temp_edge_buffer.add(newCluster); + addEdgeToCluster(prevEdge, newCluster); + int newEdge = newEdge_(-1); + m_edges_to_insert_in_sweep_structure.add(newEdge); + setEdgeSweepNode_(newEdge, c_3);// mark that its in + // m_edges_to_insert_in_sweep_structure + m_temp_edge_buffer.add(newEdge); + addEdgeToCluster(newEdge, newCluster); + prevEdge = newEdge; + } + addEdgeToCluster(prevEdge, cluster2); + // set the Edit_shape vertices to the new clusters and edges. + for (int vertexHandle = m_edge_vertices.getFirst(edgeOrigins1); vertexHandle != -1; vertexHandle = m_edge_vertices + .getNext(vertexHandle)) { + int vertex = m_edge_vertices.getElement(vertexHandle); + int cluster = getClusterFromVertex(vertex); + if (cluster == cluster_1) {// connecting from cluster_1 to cluster2 + int i = 0; + do { + if (i > 0) { + int c = m_temp_edge_buffer.get(i - 1); + addVertexToCluster_(c, vertex); + if (getClusterVertexIndex(c) == -1) + setClusterVertexIndex_(c, + m_shape.getVertexIndex(vertex)); + } + + int edge = m_temp_edge_buffer.get(i); + i += 2; + addVertexToEdge_(edge, vertex); + vertex = m_shape.getNextVertex(vertex); + } while (i < m_temp_edge_buffer.size()); + assert (getClusterFromVertex(vertex) == cluster2); + } else {// connecting from cluster2 to cluster_1 + assert (cluster == cluster2); + int i = m_temp_edge_buffer.size() - 1; + do { + if (i < m_temp_edge_buffer.size() - 2) { + int c = m_temp_edge_buffer.get(i + 1); + addVertexToCluster_(c, vertex); + if (getClusterVertexIndex(c) < 0) + setClusterVertexIndex_(c, + m_shape.getVertexIndex(vertex)); + } + + assert (i % 2 == 0); + int edge = m_temp_edge_buffer.get(i); + i -= 2; + addVertexToEdge_(edge, vertex); + vertex = m_shape.getNextVertex(vertex); + } while (i >= 0); + assert (getClusterFromVertex(vertex) == cluster_1); + } + } + + // #ifdef _DEBUG_TOPO + // for (int i = 0, j = 0, n = + // intersector->get_result_segment_count(intersector_index); i < n; i++, + // j+=2) + // { + // int edge = m_temp_edge_buffer.get(j); + // dbg_check_edge_(edge); + // } + // #endif + + m_temp_edge_buffer.clear(false); + // dbg_check_new_edges_array_(); + } + + int getVertexFromClusterIndex(int cluster) { + int vertexList = getClusterVertices(cluster); + int vertex = m_cluster_vertices.getFirstElement(vertexList); + return vertex; + } + + int getClusterFromVertex(int vertex) { + return m_shape.getUserIndex(vertex, m_vertex_cluster_index); + } + + static final class QComparator extends Treap.Comparator { + EditShape m_shape; + Point2D pt_1 = new Point2D(); + Point2D pt_2 = new Point2D(); + + QComparator(EditShape shape) { + m_shape = shape; + } + + @Override + int compare(Treap treap, int vertex, int node) { + m_shape.getXY(vertex, pt_1); + int v_2 = treap.getElement(node); + m_shape.getXY(v_2, pt_2); + return pt_1.compare(pt_2); + } + } + + static final class QMonikerComparator extends Treap.MonikerComparator { + EditShape m_shape; + Point2D m_point = new Point2D(); + Point2D m_pt = new Point2D(); + + QMonikerComparator(EditShape shape) { + m_shape = shape; + } + + void setPoint(Point2D pt) { + m_point.setCoords(pt); + } + + @Override + int compare(Treap treap, int node) { + int v = treap.getElement(node); + m_shape.getXY(v, m_pt); + return m_point.compare(m_pt); + } + } + + ; + + void processSplitHelper1_(int index, int edge, + SegmentIntersector intersector) { + int clusterStart = getEdgeCluster(edge, 0); + Point2D ptClusterStart = new Point2D(); + getClusterXY(clusterStart, ptClusterStart); + Point2D ptClusterEnd = new Point2D(); + int clusterEnd = getEdgeCluster(edge, 1); + getClusterXY(clusterEnd, ptClusterEnd); + + // Collect all edges that are affected by the split and that are in the + // sweep structure. + int count = intersector.getResultSegmentCount(index); + Segment seg = intersector.getResultSegment(index, 0); + Point2D newStart = new Point2D(); + seg.getStartXY(newStart); + + if (!ptClusterStart.isEqual(newStart)) { + if (!m_complications) { + int res1 = ptClusterStart.compare(m_sweep_point); + int res2 = newStart.compare(m_sweep_point); + if (res1 * res2 < 0) { + m_complications = true;// point is not yet have been processed + // but moved before the sweep point, + // this will require + // repeating the cracking step and the sweep_vertical cannot + // help here + } + } + + // This cluster's position needs to be changed + getAffectedEdges(clusterStart, m_temp_edge_buffer); + m_modified_clusters.add(clusterStart); + } + + if (!m_complications && count > 1) { + int dir = ptClusterStart.compare(ptClusterEnd); + Point2D midPoint = seg.getEndXY(); + if (ptClusterStart.compare(midPoint) != dir + || midPoint.compare(ptClusterEnd) != dir) {// split segment + // midpoint is + // above the + // sweep line. + // Therefore the + // part of the + // segment + m_complications = true; + } else { + if (midPoint.compare(m_sweep_point) < 0) { + // midpoint moved below sweepline. + m_complications = true; + } + } + } + + seg = intersector.getResultSegment(index, count - 1); + Point2D newEnd = seg.getEndXY(); + if (!ptClusterEnd.isEqual(newEnd)) { + if (!m_complications) { + int res1 = ptClusterEnd.compare(m_sweep_point); + int res2 = newEnd.compare(m_sweep_point); + if (res1 * res2 < 0) { + m_complications = true;// point is not yet have been processed + // but moved before the sweep point. + } + } + // This cluster's position needs to be changed + getAffectedEdges(clusterEnd, m_temp_edge_buffer); + m_modified_clusters.add(clusterEnd); + } + + m_temp_edge_buffer.add(edge); + // Delete all nodes from the sweep structure that are affected by the + // change. + for (int i = 0, n = m_temp_edge_buffer.size(); i < n; i++) { + int e = m_temp_edge_buffer.get(i); + int sweepNode = getEdgeSweepNode(e); + if (StridedIndexTypeCollection.isValidElement(sweepNode)) { + m_sweep_structure.deleteNode(sweepNode, -1); + setEdgeSweepNode_(e, -1); + } + + int c_3 = StridedIndexTypeCollection.impossibleIndex3(); + if (e != edge && getEdgeSweepNode(e) != c_3)// c_3 means the edge is + // already in the + // m_edges_to_insert_in_sweep_structure + { + m_edges_to_insert_in_sweep_structure.add(e); + setEdgeSweepNode_(e, c_3); + } + } + m_temp_edge_buffer.clear(false); + } + + boolean checkAndFixIntersection_(int leftSweepNode, int rightSweepNode) { + int leftEdge = m_sweep_structure.getElement(leftSweepNode); + m_sweep_comparator.compare(m_sweep_structure, leftEdge, rightSweepNode); + if (m_sweep_comparator.intersectionDetected()) { + m_sweep_comparator.clearIntersectionDetectedFlag(); + fixIntersection_(leftSweepNode, rightSweepNode); + return true; + } + + return false; + } + + void fixIntersection_(int left, int right) { + m_b_cracked = true; + int edge1 = m_sweep_structure.getElement(left); + int edge2 = m_sweep_structure.getElement(right); + assert (edge1 != edge2); + Segment seg_1; + Segment seg_2; + int vertexList1 = getEdgeOriginVertices(edge1); + int origin1 = m_edge_vertices.getFirstElement(vertexList1); + int vertexList2 = getEdgeOriginVertices(edge2); + int origin2 = m_edge_vertices.getFirstElement(vertexList2); + seg_1 = m_shape.getSegment(origin1); + if (seg_1 == null) { + if (m_line_1 == null) + m_line_1 = new Line(); + m_shape.queryLineConnector(origin1, m_line_1); + seg_1 = m_line_1; + } + + seg_2 = m_shape.getSegment(origin2); + if (seg_2 == null) { + if (m_line_2 == null) + m_line_2 = new Line(); + m_shape.queryLineConnector(origin2, m_line_2); + seg_2 = m_line_2; + } + + // #ifdef _DEBUG_CRACKING_REPORT + // { + // Point_2D pt11, pt12, pt21, pt22; + // pt11 = seg_1->get_start_xy(); + // pt12 = seg_1->get_end_xy(); + // pt21 = seg_2->get_start_xy(); + // pt22 = seg_2->get_end_xy(); + // DEBUGPRINTF(L"Intersecting %d (%0.4f, %0.4f - %0.4f, %0.4f) and %d (%0.4f, %0.4f - %0.4f, %0.4f)\n", + // edge1, pt11.x, pt11.y, pt12.x, pt12.y, edge2, pt21.x, pt21.y, pt22.x, + // pt22.y); + // } + // #endif + + m_segment_intersector.pushSegment(seg_1); + m_segment_intersector.pushSegment(seg_2); + if (m_segment_intersector.intersect(m_tolerance, true)) + m_complications = true; + + + splitEdge_(edge1, edge2, -1, m_segment_intersector); + m_segment_intersector.clear(); + } + + void fixIntersectionPointSegment_(int cluster, int node) { + m_b_cracked = true; + int edge1 = m_sweep_structure.getElement(node); + Segment seg_1; + int vertexList1 = getEdgeOriginVertices(edge1); + int origin1 = m_edge_vertices.getFirstElement(vertexList1); + seg_1 = m_shape.getSegment(origin1); + if (seg_1 == null) { + if (m_line_1 == null) + m_line_1 = new Line(); + m_shape.queryLineConnector(origin1, m_line_1); + seg_1 = m_line_1; + } + + int clusterVertex = getClusterFirstVertex(cluster); + m_segment_intersector.pushSegment(seg_1); + + m_shape.queryPoint(clusterVertex, m_helper_point); + m_segment_intersector.intersect(m_tolerance, m_helper_point, 0, 1.0, + true); + + splitEdge_(edge1, -1, cluster, m_segment_intersector); + + m_segment_intersector.clear(); + } + + void insertNewEdges_() { + if (m_edges_to_insert_in_sweep_structure.size() == 0) + return; + + while (m_edges_to_insert_in_sweep_structure.size() != 0) { + if (m_edges_to_insert_in_sweep_structure.size() > Math.max( + (int) 100, m_shape.getTotalPointCount())) { + assert (false); + m_edges_to_insert_in_sweep_structure.clear(false); + m_complications = true; + break;// something strange going on here. bail out, forget about + // these edges and continue with sweep line. We'll + // iterate on the data one more time. + } + + int edge = m_edges_to_insert_in_sweep_structure.getLast(); + m_edges_to_insert_in_sweep_structure.removeLast(); + + assert (getEdgeSweepNode(edge) == StridedIndexTypeCollection + .impossibleIndex3()); + setEdgeSweepNode_(edge, -1); + int terminatingCluster = isEdgeOnSweepLine_(edge); + if (terminatingCluster != -1) { + insertNewEdgeToSweepStructure_(edge, terminatingCluster); + } + m_b_continuing_segment_chain_optimization = false; + } + } + + boolean insertNewEdgeToSweepStructure_(int edge, int terminatingCluster) { + assert (getEdgeSweepNode(edge) == -1); + int newEdgeNode; + if (m_b_continuing_segment_chain_optimization) { + newEdgeNode = m_sweep_structure.addElementAtPosition( + m_prev_neighbour, m_next_neighbour, edge, true, true, -1); + m_b_continuing_segment_chain_optimization = false; + } else { + newEdgeNode = m_sweep_structure.addUniqueElement(edge, -1); + } + + if (newEdgeNode == -1) {// a coinciding edge. + int existingNode = m_sweep_structure.getDuplicateElement(-1); + int existingEdge = m_sweep_structure.getElement(existingNode); + mergeEdges_(existingEdge, edge); + return false; + } + + // Remember the sweep structure node in the edge. + setEdgeSweepNode_(edge, newEdgeNode); + + if (m_sweep_comparator.intersectionDetected()) { + // The edge has been inserted into the sweep structure and an + // intersection has beebn found. The edge will be split and removed. + m_sweep_comparator.clearIntersectionDetectedFlag(); + int intersectionNode = m_sweep_comparator.getLastComparedNode(); + fixIntersection_(intersectionNode, newEdgeNode); + return true; + } else { + // The edge has been inserted into the sweep structure without + // problems (it does not intersect its neighbours) + } + + return false; + } + + Point2D pt_1 = new Point2D(); + Point2D pt_2 = new Point2D(); + + int isEdgeOnSweepLine_(int edge) { + int cluster_1 = getEdgeCluster(edge, 0); + int cluster2 = getEdgeCluster(edge, 1); + getClusterXY(cluster_1, pt_1); + getClusterXY(cluster2, pt_2); + if (Point2D.sqrDistance(pt_1, pt_2) <= m_tolerance_sqr) {// avoid + // degenerate + // segments + m_complications = true; + return -1; + } + int cmp1 = pt_1.compare(m_sweep_point); + int cmp2 = pt_2.compare(m_sweep_point); + if (cmp1 <= 0 && cmp2 > 0) { + return cluster2; + } + + if (cmp2 <= 0 && cmp1 > 0) { + return cluster_1; + } + + return -1; + } + + // void set_edit_shape(Edit_shape* shape); + // Fills the event queue and merges coincident clusters. + void fillEventQueue() { + AttributeStreamOfInt32 event_q = new AttributeStreamOfInt32(0); + event_q.reserve(m_shape.getTotalPointCount());// temporary structure to + // sort and find + // clusters + EditShape.VertexIterator iter = m_shape.queryVertexIterator(); + for (int vert = iter.next(); vert != -1; vert = iter.next()) { + if (m_shape.getUserIndex(vert, m_vertex_cluster_index) != -1) + event_q.add(vert); + } + + // Now we can merge coincident clusters and form the envent structure. + + // sort vertices lexicographically. + m_shape.sortVerticesSimpleByY_(event_q, 0, event_q.size()); + + // The m_event_q is the event structure for the planesweep algorithm. + // We could use any data structure that allows log(n) insertion and + // deletion in the sorted order and + // allow to iterate through in the sorted order. + + m_event_q.clear(); + // Populate the event structure + m_event_q.setCapacity(event_q.size()); + { + // The comparator is used to sort vertices by the m_event_q + m_event_q.setComparator(new QComparator(m_shape)); + } + + // create the vertex clusters and fill the event structure m_event_q. + // Because most vertices are expected to be non clustered, we create + // clusters only for actual clusters to save some memory. + Point2D cluster_pt = new Point2D(); + cluster_pt.setNaN(); + int cluster = -1; + Point2D pt = new Point2D(); + for (int index = 0, nvertex = event_q.size(); index < nvertex; index++) { + int vertex = event_q.get(index); + m_shape.getXY(vertex, pt); + if (pt.isEqual(cluster_pt)) { + int vertexCluster = m_shape.getUserIndex(vertex, + m_vertex_cluster_index); + mergeClusters_(cluster, vertexCluster); + continue; + } + + cluster = getClusterFromVertex(vertex); + // add a vertex to the event queue + m_shape.getXY(vertex, cluster_pt); + int eventQnode = m_event_q.addBiggestElement(vertex, -1); // this + // method + // does + // not + // call + // comparator's + // compare, + // assuming + // sorted + // order. + setClusterEventQNode_(cluster, eventQnode); + } + } + + void fillEventQueuePass2() { + AttributeStreamOfInt32 event_q = new AttributeStreamOfInt32(0); + event_q.reserve(m_shape.getTotalPointCount());// temporary structure to + // sort and find + // clusters + for (int node = m_event_q.getFirst(-1); node != -1; node = m_event_q + .getNext(node)) { + int v = m_event_q.getElement(node); + event_q.add(v); + } + + assert (event_q.size() == m_event_q.size(-1)); + + m_event_q.clear(); + + // sort vertices lexicographically. + m_shape.sortVerticesSimpleByY_(event_q, 0, event_q.size()); + + for (int index = 0, nvertex = event_q.size(); index < nvertex; index++) { + int vertex = event_q.get(index); + int cluster = getClusterFromVertex(vertex); + int eventQnode = m_event_q.addBiggestElement(vertex, -1); // this + // method + // does + // not + // call + // comparator's + // compare, + // assuming + // sorted + // order. + setClusterEventQNode_(cluster, eventQnode); + } + } + + // Returns edges already in the sweep structure that are affected by the + // change of cluster coordinate. + void getAffectedEdges(int cluster, AttributeStreamOfInt32 edges) { + int first_edge = getClusterFirstEdge(cluster); + if (first_edge == -1) + return; + + int edge = first_edge; + do { + int sweepNode = getEdgeSweepNode(edge); + if (StridedIndexTypeCollection.isValidElement(sweepNode)) { + edges.add(edge); + } + edge = getNextEdge(edge, cluster); + } while (edge != first_edge); + } + + // Updates all vertices of the cluster to new coordinate + void updateClusterXY(int cluster, Point2D pt) { + int vertexList = getClusterVertices(cluster); + for (int vh = m_cluster_vertices.getFirst(vertexList); vh != -1; vh = m_cluster_vertices + .getNext(vh)) { + int vertex = m_cluster_vertices.getElement(vh); + m_shape.setXY(vertex, pt); + } + } + + // Modifies the given edges given the intersector class and the result + // index. + // The function updates the the event structure and puts new edges into the + // m_edges_to_insert_in_sweep_structure. + void splitEdge_(int edge1, int edge2, int intersectionCluster, + SegmentIntersector intersector) { + + disconnectEdge_(edge1);// disconnects the edge from the clusters. The + // edge still remembers the clusters. + if (edge2 != -1) + disconnectEdge_(edge2);// disconnects the edge from the clusters. + // The edge still remembers the clusters. + + // Collect all edges that are affected when the clusters change position + // due to snapping + // The edges are collected in m_edges_to_insert_in_sweep_structure. + // Collect the modified clusters in m_modified_clusters. + processSplitHelper1_(0, edge1, intersector); + if (edge2 != -1) + processSplitHelper1_(1, edge2, intersector); + + if (intersectionCluster != -1) { + intersector.getResultPoint().getXY(pt_1); + getClusterXY(intersectionCluster, pt_2); + if (!pt_2.isEqual(pt_1)) + m_modified_clusters.add(intersectionCluster); + } + + // remove modified clusters from the event queue. We'll reincert them + // later + for (int i = 0, n = m_modified_clusters.size(); i < n; i++) { + int cluster = m_modified_clusters.get(i); + int eventQnode = getClusterEventQNode(cluster); + if (eventQnode != -1) { + m_event_q.deleteNode(eventQnode, -1); + setClusterEventQNode_(cluster, -1); + } + } + + int edgeOrigins1 = getEdgeOriginVertices(edge1); + int edgeOrigins2 = (edge2 != -1) ? getEdgeOriginVertices(edge2) : -1; + + // Adjust the vertex coordinates and split the segments in the the edit + // shape. + applyIntersectorToEditShape_(edgeOrigins1, intersector, 0); + if (edge2 != -1) + applyIntersectorToEditShape_(edgeOrigins2, intersector, 1); + + // Produce clusters, and new edges. The new edges are added to + // m_edges_to_insert_in_sweep_structure. + createEdgesAndClustersFromSplitEdge_(edge1, intersector, 0); + if (edge2 != -1) + createEdgesAndClustersFromSplitEdge_(edge2, intersector, 1); + + m_edge_vertices.deleteList(edgeOrigins1); + deleteEdge_(edge1); + + if (edge2 != -1) { + m_edge_vertices.deleteList(edgeOrigins2); + deleteEdge_(edge2); + } + + // insert clusters into the event queue and the edges into the sweep + // structure. + for (int i = 0, n = m_modified_clusters.size(); i < n; i++) { + int cluster = m_modified_clusters.get(i); + if (cluster == m_sweep_point_cluster) + m_b_sweep_point_cluster_was_modified = true; + + int eventQnode = getClusterEventQNode(cluster); + if (eventQnode == -1) { + int vertex = getClusterFirstVertex(cluster); + assert (getClusterFromVertex(vertex) == cluster); + + eventQnode = m_event_q.addUniqueElement(vertex, -1);// O(logN) + // operation + if (eventQnode == -1) {// the cluster is coinciding with another + // one. merge. + int existingNode = m_event_q.getDuplicateElement(-1); + int v = m_event_q.getElement(existingNode); + assert (m_shape.isEqualXY(vertex, v)); + int existingCluster = getClusterFromVertex(v); + mergeClusters_(existingCluster, cluster); + } else { + setClusterEventQNode_(cluster, eventQnode); + } + } else { + // if already inserted (probably impossible) case + } + } + + m_modified_clusters.clear(false); + } + + // Returns a cluster's xy. + void getClusterXY(int cluster, Point2D ptOut) { + int vindex = getClusterVertexIndex(cluster); + m_shape.getXYWithIndex(vindex, ptOut); + } + + int getClusterFirstVertex(int cluster) { + int vertexList = getClusterVertices(cluster); + int vertex = m_cluster_vertices.getFirstElement(vertexList); + return vertex; + } + + boolean sweepImpl_() { + m_b_sweep_point_cluster_was_modified = false; + m_sweep_point_cluster = -1; + if (m_sweep_comparator == null) { + m_sweep_structure.disableBalancing(); + m_sweep_comparator = new SimplifySweepComparator(this); + m_sweep_structure.setComparator(m_sweep_comparator); + } + + AttributeStreamOfInt32 edgesToDelete = new AttributeStreamOfInt32(0); + SimplifySweepMonikerComparator sweepMoniker = null; + QMonikerComparator moniker = null; + + int iterationCounter = 0; + m_prev_neighbour = -1; + m_next_neighbour = -1; + m_b_continuing_segment_chain_optimization = false; + + int c_2 = StridedIndexTypeCollection.impossibleIndex2(); + int c_3 = StridedIndexTypeCollection.impossibleIndex3(); + assert (c_2 != c_3); + + for (int eventQnode = m_event_q.getFirst(-1); eventQnode != -1; ) { + iterationCounter++; + m_b_continuing_segment_chain_optimization = false; + + int vertex = m_event_q.getElement(eventQnode); + m_sweep_point_cluster = getClusterFromVertex(vertex); + m_shape.getXY(vertex, m_sweep_point); + + m_sweep_comparator.setSweepY(m_sweep_point.y, m_sweep_point.x);// move + // the + // sweep + // line + + boolean bDisconnectedCluster = false; + {// scope + int first_edge = getClusterFirstEdge(m_sweep_point_cluster); + bDisconnectedCluster = first_edge == -1; + if (!bDisconnectedCluster) { + int edge = first_edge; + do { + int sweepNode = getEdgeSweepNode(edge); + if (sweepNode == -1) { + m_edges_to_insert_in_sweep_structure.add(edge); + setEdgeSweepNode_(edge, c_3);// mark that its in + // m_edges_to_insert_in_sweep_structure + } else if (sweepNode != c_3) { + assert (StridedIndexTypeCollection.isValidElement(sweepNode)); + edgesToDelete.add(sweepNode); + } + edge = getNextEdge(edge, m_sweep_point_cluster); + } while (edge != first_edge); + } + } + + // st_counter_insertions_peaks += edgesToDelete.size() == 0 && + // m_edges_to_insert_in_sweep_structure.size() > 0; + // First step is to delete the edges that terminate in the + // cluster. + // During that step we also determine the left and right neighbors + // of the deleted bunch and then check if those left and right + // intersect or not. + if (edgesToDelete.size() > 0) { + m_b_continuing_segment_chain_optimization = (edgesToDelete + .size() == 1 && m_edges_to_insert_in_sweep_structure + .size() == 1); + + // Mark nodes that need to be deleted by setting c_2 to the + // edge's sweep node member. + for (int i = 0, n = edgesToDelete.size(); i < n; i++) { + int edge = m_sweep_structure.getElement(edgesToDelete + .get(i)); + setEdgeSweepNode_(edge, c_2); + } + + int left = c_2; + int right = c_2; + // Determine left and right nodes for the bunch of nodes we are + // deleting. + for (int i = 0, n = edgesToDelete.size(); i < n; i++) { + int sweepNode = edgesToDelete.get(i); + if (left == c_2) { + int localleft = m_sweep_structure.getPrev(sweepNode); + if (localleft != -1) { + int edge = m_sweep_structure.getElement(localleft); + int node = getEdgeSweepNode(edge); + if (node != c_2) + left = localleft; + } else + left = -1; + } + + if (right == c_2) { + int localright = m_sweep_structure.getNext(sweepNode); + if (localright != -1) { + int edge = m_sweep_structure.getElement(localright); + int node = getEdgeSweepNode(edge); + if (node != c_2) + right = localright; + } else + right = -1; + } + + if (left != c_2 && right != c_2) + break; + } + + assert (left != c_2 && right != c_2); + // Now delete the bunch. + for (int i = 0, n = edgesToDelete.size(); i < n; i++) { + int sweepNode = edgesToDelete.get(i); + int edge = m_sweep_structure.getElement(sweepNode); + m_sweep_structure.deleteNode(sweepNode, -1); + setEdgeSweepNode_(edge, -1); + } + + edgesToDelete.clear(false); + + m_prev_neighbour = left != -1 ? left : -1; + m_next_neighbour = right != -1 ? right : -1; + + // Now check if the left and right we found intersect or not. + if (left != -1 && right != -1) { + if (!m_b_continuing_segment_chain_optimization) { + boolean bIntersected = checkAndFixIntersection_(left, + right); + } + } else { + if ((left == -1) && (right == -1)) + m_b_continuing_segment_chain_optimization = false; + } + } else { + // edgesToDelete.size() == 0 - nothing to delete here. This is a + // cluster which has all edges directed up or a disconnected + // cluster. + + if (bDisconnectedCluster) {// check standalone cluster (point or + // multipoint) if it cracks an edge. + if (sweepMoniker == null) + sweepMoniker = new SimplifySweepMonikerComparator(this); + + sweepMoniker.setPoint(m_sweep_point); + m_sweep_structure.searchUpperBound(sweepMoniker, -1); + if (sweepMoniker.intersectionDetected()) { + sweepMoniker.clearIntersectionDetectedFlag(); + fixIntersectionPointSegment_(m_sweep_point_cluster, + sweepMoniker.getCurrentNode()); + } + } + } + + // Now insert edges that start at the cluster and go up + insertNewEdges_(); + + if (m_b_sweep_point_cluster_was_modified) { + m_b_sweep_point_cluster_was_modified = false; + if (moniker == null) + moniker = new QMonikerComparator(m_shape); + moniker.setPoint(m_sweep_point); + eventQnode = m_event_q.searchUpperBound(moniker, -1); + } else + eventQnode = m_event_q.getNext(eventQnode); + } + + return m_b_cracked; + } + + void setEditShape_(EditShape shape) { + // Populate the cluster and edge structures. + m_shape = shape; + m_vertex_cluster_index = m_shape.createUserIndex(); + + m_edges.setCapacity(shape.getTotalPointCount() + 32); + + m_clusters.setCapacity(shape.getTotalPointCount()); + + m_cluster_vertices.reserveLists(shape.getTotalPointCount()); + m_cluster_vertices.reserveNodes(shape.getTotalPointCount()); + + m_edge_vertices.reserveLists(shape.getTotalPointCount() + 32); + m_edge_vertices.reserveNodes(shape.getTotalPointCount() + 32); + + for (int geometry = m_shape.getFirstGeometry(); geometry != -1; geometry = m_shape + .getNextGeometry(geometry)) { + boolean bMultiPath = Geometry.isMultiPath(m_shape + .getGeometryType(geometry)); + + if (!bMultiPath) {// for multipoints do not add edges. + assert (m_shape.getGeometryType(geometry) == Geometry.GeometryType.MultiPoint); + + for (int path = m_shape.getFirstPath(geometry); path != -1; path = m_shape + .getNextPath(path)) { + int vertex = m_shape.getFirstVertex(path); + for (int i = 0, n = m_shape.getPathSize(path); i < n; i++) { + // int cluster + newCluster_(vertex); + vertex = m_shape.getNextVertex(vertex); + } + } + continue; + } + + for (int path = m_shape.getFirstPath(geometry); path != -1; path = m_shape + .getNextPath(path)) { + int path_size = m_shape.getPathSize(path); + assert (path_size > 1); + int first_vertex = m_shape.getFirstVertex(path); + + // first------------------ + int firstCluster = newCluster_(first_vertex); + int first_edge = newEdge_(first_vertex); + addEdgeToCluster(first_edge, firstCluster); + int prevEdge = first_edge; + int vertex = m_shape.getNextVertex(first_vertex); + for (int index = 0, n = path_size - 2; index < n; index++) { + int nextvertex = m_shape.getNextVertex(vertex); + // ------------x------------ + int cluster = newCluster_(vertex); + addEdgeToCluster(prevEdge, cluster); + int newEdge = newEdge_(vertex); + addEdgeToCluster(newEdge, cluster); + prevEdge = newEdge; + vertex = nextvertex; + } + + // ------------------lastx + if (m_shape.isClosedPath(path)) { + int cluster = newCluster_(vertex); + addEdgeToCluster(prevEdge, cluster); + // close the path + // lastx------------------firstx + int newEdge = newEdge_(vertex); + addEdgeToCluster(newEdge, cluster); + addEdgeToCluster(newEdge, firstCluster); + } else { + // ------------------lastx + int cluster = newCluster_(vertex); + addEdgeToCluster(prevEdge, cluster); + } + + } + } + + fillEventQueue(); + + // int perPoint = estimate_memory_size() / + // m_shape.get_total_point_count(); + // perPoint = 0; + } } diff --git a/src/main/java/com/esri/core/geometry/Point.java b/src/main/java/com/esri/core/geometry/Point.java index 83d7d074..51336f69 100644 --- a/src/main/java/com/esri/core/geometry/Point.java +++ b/src/main/java/com/esri/core/geometry/Point.java @@ -36,326 +36,325 @@ * Systems, the X coordinate is the longitude and the Y is the latitude. */ public class Point extends Geometry implements Serializable { - //We are using writeReplace instead. - //private static final long serialVersionUID = 2L; - - double[] m_attributes; // use doubles to store everything (long are bitcast) - - /** - * Creates an empty 2D point. - */ - public Point() { - m_description = VertexDescriptionDesignerImpl.getDefaultDescriptor2D(); - } - - public Point(VertexDescription vd) { - if (vd == null) - throw new IllegalArgumentException(); - m_description = vd; - } - - /** - * Creates a 2D Point with specified X and Y coordinates. In case of - * Geographic Coordinate Systems, the X coordinate is the longitude and the - * Y is the latitude. - * - * @param x The X coordinate of the new 2D point. - * @param y The Y coordinate of the new 2D point. - */ - public Point(double x, double y) { - m_description = VertexDescriptionDesignerImpl.getDefaultDescriptor2D(); - setXY(x, y); - } - - public Point(Point2D pt) { - m_description = VertexDescriptionDesignerImpl.getDefaultDescriptor2D(); - setXY(pt); - } - - /** - * Creates a 3D point with specified X, Y and Z coordinates. In case of - * Geographic Coordinate Systems, the X coordinate is the longitude and the - * Y is the latitude. - * - * @param x The X coordinate of the new 3D point. - * @param y The Y coordinate of the new 3D point. - * @param z The Z coordinate of the new 3D point. - */ - public Point(double x, double y, double z) { - m_description = VertexDescriptionDesignerImpl.getDefaultDescriptor2D(); - Point3D pt = new Point3D(); - pt.setCoords(x, y, z); - setXYZ(pt); - - } - - /** - * Returns XY coordinates of this point. - */ - public final Point2D getXY() { - if (isEmptyImpl()) - throw new GeometryException( - "This operation should not be performed on an empty geometry."); - - Point2D pt = new Point2D(); - pt.setCoords(m_attributes[0], m_attributes[1]); - return pt; - } - - /** - * Returns XY coordinates of this point. - */ - public final void getXY(Point2D pt) { - if (isEmptyImpl()) - throw new GeometryException( - "This operation should not be performed on an empty geometry."); - - pt.setCoords(m_attributes[0], m_attributes[1]); - } - - /** - * Sets the XY coordinates of this point. param pt The point to create the X - * and Y coordinate from. - */ - public final void setXY(Point2D pt) { - _touch(); - setXY(pt.x, pt.y); - } - - /** - * Returns XYZ coordinates of the point. Z will be set to 0 if Z is missing. - */ - public Point3D getXYZ() { - if (isEmptyImpl()) - throw new GeometryException( - "This operation should not be performed on an empty geometry."); - - Point3D pt = new Point3D(); - pt.x = m_attributes[0]; - pt.y = m_attributes[1]; - if (m_description.hasZ()) - pt.z = m_attributes[2]; - else - pt.z = VertexDescription.getDefaultValue(Semantics.Z); - - return pt; - } - - /** - * Sets the XYZ coordinates of this point. - * - * @param pt The point to create the XYZ coordinate from. - */ - public void setXYZ(Point3D pt) { - _touch(); - boolean bHasZ = hasAttribute(Semantics.Z); - if (!bHasZ && !VertexDescription.isDefaultValue(Semantics.Z, pt.z)) {// add - // Z - // only - // if - // pt.z - // is - // not - // a - // default - // value. - addAttribute(Semantics.Z); - bHasZ = true; - } - - if (m_attributes == null) - _setToDefault(); - - m_attributes[0] = pt.x; - m_attributes[1] = pt.y; - if (bHasZ) - m_attributes[2] = pt.z; - } - - /** - * Returns the X coordinate of the point. - */ - public final double getX() { - if (isEmptyImpl()) - throw new GeometryException( - "This operation should not be performed on an empty geometry."); - - return m_attributes[0]; - } - - /** - * Sets the X coordinate of the point. - * - * @param x The X coordinate to be set for this point. - */ - public void setX(double x) { - setAttribute(Semantics.POSITION, 0, x); - } - - /** - * Returns the Y coordinate of this point. - */ - public final double getY() { - if (isEmptyImpl()) - throw new GeometryException( - "This operation should not be performed on an empty geometry."); - - return m_attributes[1]; - } - - /** - * Sets the Y coordinate of this point. - * - * @param y The Y coordinate to be set for this point. - */ - public void setY(double y) { - setAttribute(Semantics.POSITION, 1, y); - } - - /** - * Returns the Z coordinate of this point. - */ - public double getZ() { - return getAttributeAsDbl(Semantics.Z, 0); - } - - /** - * Sets the Z coordinate of this point. - * - * @param z The Z coordinate to be set for this point. - */ - public void setZ(double z) { - setAttribute(Semantics.Z, 0, z); - } - - /** - * Returns the attribute M of this point. - */ - public double getM() { - return getAttributeAsDbl(Semantics.M, 0); - } - - /** - * Sets the M coordinate of this point. - * - * @param m The M coordinate to be set for this point. - */ - public void setM(double m) { - setAttribute(Semantics.M, 0, m); - } - - /** - * Returns the ID of this point. - */ - public int getID() { - return getAttributeAsInt(Semantics.ID, 0); - } - - /** - * Sets the ID of this point. - * - * @param id The ID of this point. - */ - public void setID(int id) { - setAttribute(Semantics.ID, 0, id); - } - - /** - * Returns value of the given vertex attribute's ordinate. - * - * @param semantics The attribute semantics. - * @param ordinate The attribute's ordinate. For example, the Y coordinate of the - * NORMAL has ordinate of 1. - * @return The ordinate as double value. - */ - public double getAttributeAsDbl(int semantics, int ordinate) { - if (isEmptyImpl()) - throw new GeometryException( - "This operation was performed on an Empty Geometry."); - - int ncomps = VertexDescription.getComponentCount(semantics); - if (ordinate >= ncomps) - throw new IndexOutOfBoundsException(); - - int attributeIndex = m_description.getAttributeIndex(semantics); - if (attributeIndex >= 0) - return m_attributes[m_description - ._getPointAttributeOffset(attributeIndex) + ordinate]; - else - return VertexDescription.getDefaultValue(semantics); - } - - /** - * Returns value of the given vertex attribute's ordinate. The ordinate is - * always 0 because integer attributes always have one component. - * - * @param semantics The attribute semantics. - * @param ordinate The attribute's ordinate. For example, the y coordinate of the - * NORMAL has ordinate of 1. - * @return The ordinate value truncated to a 32 bit integer value. - */ - public int getAttributeAsInt(int semantics, int ordinate) { - if (isEmptyImpl()) - throw new GeometryException( - "This operation was performed on an Empty Geometry."); - - int ncomps = VertexDescription.getComponentCount(semantics); - if (ordinate >= ncomps) - throw new IndexOutOfBoundsException(); - - int attributeIndex = m_description.getAttributeIndex(semantics); - if (attributeIndex >= 0) - return (int) m_attributes[m_description - ._getPointAttributeOffset(attributeIndex) + ordinate]; - else - return (int) VertexDescription.getDefaultValue(semantics); - } - - /** - * Sets the value of the attribute. - * - * @param semantics The attribute semantics. - * @param ordinate The ordinate of the attribute. - * @param value Is the array to write values to. The attribute type and the - * number of elements must match the persistence type, as well as - * the number of components of the attribute. - */ - public void setAttribute(int semantics, int ordinate, double value) { - _touch(); - int ncomps = VertexDescription.getComponentCount(semantics); - if (ncomps < ordinate) - throw new IndexOutOfBoundsException(); - - int attributeIndex = m_description.getAttributeIndex(semantics); - if (attributeIndex < 0) { - addAttribute(semantics); - attributeIndex = m_description.getAttributeIndex(semantics); - } - - if (m_attributes == null) - _setToDefault(); - - m_attributes[m_description._getPointAttributeOffset(attributeIndex) - + ordinate] = value; - } - - public void setAttribute(int semantics, int ordinate, int value) { - setAttribute(semantics, ordinate, (double) value); - } - - @Override - public Geometry.Type getType() { - return Type.Point; - } - - @Override - public int getDimension() { - return 0; - } + //We are using writeReplace instead. + //private static final long serialVersionUID = 2L; + + double[] m_attributes; // use doubles to store everything (long are bitcast) + + /** + * Creates an empty 2D point. + */ + public Point() { + m_description = VertexDescriptionDesignerImpl.getDefaultDescriptor2D(); + } + + public Point(VertexDescription vd) { + if (vd == null) + throw new IllegalArgumentException(); + m_description = vd; + } + + /** + * Creates a 2D Point with specified X and Y coordinates. In case of + * Geographic Coordinate Systems, the X coordinate is the longitude and the + * Y is the latitude. + * + * @param x The X coordinate of the new 2D point. + * @param y The Y coordinate of the new 2D point. + */ + public Point(double x, double y) { + m_description = VertexDescriptionDesignerImpl.getDefaultDescriptor2D(); + setXY(x, y); + } + + public Point(Point2D pt) { + m_description = VertexDescriptionDesignerImpl.getDefaultDescriptor2D(); + setXY(pt); + } + + /** + * Creates a 3D point with specified X, Y and Z coordinates. In case of + * Geographic Coordinate Systems, the X coordinate is the longitude and the + * Y is the latitude. + * + * @param x The X coordinate of the new 3D point. + * @param y The Y coordinate of the new 3D point. + * @param z The Z coordinate of the new 3D point. + */ + public Point(double x, double y, double z) { + m_description = VertexDescriptionDesignerImpl.getDefaultDescriptor2D(); + Point3D pt = new Point3D(); + pt.setCoords(x, y, z); + setXYZ(pt); + + } + + /** + * Returns XY coordinates of this point. + */ + public final Point2D getXY() { + if (isEmptyImpl()) + throw new GeometryException( + "This operation should not be performed on an empty geometry."); + + Point2D pt = new Point2D(); + pt.setCoords(m_attributes[0], m_attributes[1]); + return pt; + } + + /** + * Returns XY coordinates of this point. + */ + public final void getXY(Point2D pt) { + if (isEmptyImpl()) + throw new GeometryException( + "This operation should not be performed on an empty geometry."); + + pt.setCoords(m_attributes[0], m_attributes[1]); + } + + /** + * Sets the XY coordinates of this point. param pt The point to create the X + * and Y coordinate from. + */ + public final void setXY(Point2D pt) { + _touch(); + setXY(pt.x, pt.y); + } + + /** + * Returns XYZ coordinates of the point. Z will be set to 0 if Z is missing. + */ + public Point3D getXYZ() { + if (isEmptyImpl()) + throw new GeometryException( + "This operation should not be performed on an empty geometry."); + + Point3D pt = new Point3D(); + pt.x = m_attributes[0]; + pt.y = m_attributes[1]; + if (m_description.hasZ()) + pt.z = m_attributes[2]; + else + pt.z = VertexDescription.getDefaultValue(Semantics.Z); + + return pt; + } + + /** + * Sets the XYZ coordinates of this point. + * + * @param pt The point to create the XYZ coordinate from. + */ + public void setXYZ(Point3D pt) { + _touch(); + boolean bHasZ = hasAttribute(Semantics.Z); + if (!bHasZ && !VertexDescription.isDefaultValue(Semantics.Z, pt.z)) {// add + // Z + // only + // if + // pt.z + // is + // not + // a + // default + // value. + addAttribute(Semantics.Z); + bHasZ = true; + } + + if (m_attributes == null) + _setToDefault(); + + m_attributes[0] = pt.x; + m_attributes[1] = pt.y; + if (bHasZ) + m_attributes[2] = pt.z; + } + + /** + * Returns the X coordinate of the point. + */ + public final double getX() { + if (isEmptyImpl()) + throw new GeometryException( + "This operation should not be performed on an empty geometry."); + + return m_attributes[0]; + } + + /** + * Sets the X coordinate of the point. + * + * @param x The X coordinate to be set for this point. + */ + public void setX(double x) { + setAttribute(Semantics.POSITION, 0, x); + } + + /** + * Returns the Y coordinate of this point. + */ + public final double getY() { + if (isEmptyImpl()) + throw new GeometryException( + "This operation should not be performed on an empty geometry."); + + return m_attributes[1]; + } + + /** + * Sets the Y coordinate of this point. + * + * @param y The Y coordinate to be set for this point. + */ + public void setY(double y) { + setAttribute(Semantics.POSITION, 1, y); + } + + /** + * Returns the Z coordinate of this point. + */ + public double getZ() { + return getAttributeAsDbl(Semantics.Z, 0); + } + + /** + * Sets the Z coordinate of this point. + * + * @param z The Z coordinate to be set for this point. + */ + public void setZ(double z) { + setAttribute(Semantics.Z, 0, z); + } + + /** + * Returns the attribute M of this point. + */ + public double getM() { + return getAttributeAsDbl(Semantics.M, 0); + } + + /** + * Sets the M coordinate of this point. + * + * @param m The M coordinate to be set for this point. + */ + public void setM(double m) { + setAttribute(Semantics.M, 0, m); + } + + /** + * Returns the ID of this point. + */ + public int getID() { + return getAttributeAsInt(Semantics.ID, 0); + } + + /** + * Sets the ID of this point. + * + * @param id The ID of this point. + */ + public void setID(int id) { + setAttribute(Semantics.ID, 0, id); + } + + /** + * Returns value of the given vertex attribute's ordinate. + * + * @param semantics The attribute semantics. + * @param ordinate The attribute's ordinate. For example, the Y coordinate of the + * NORMAL has ordinate of 1. + * @return The ordinate as double value. + */ + public double getAttributeAsDbl(int semantics, int ordinate) { + if (isEmptyImpl()) + throw new GeometryException( + "This operation was performed on an Empty Geometry."); + + int ncomps = VertexDescription.getComponentCount(semantics); + if (ordinate >= ncomps) + throw new IndexOutOfBoundsException(); + + int attributeIndex = m_description.getAttributeIndex(semantics); + if (attributeIndex >= 0) + return m_attributes[m_description + ._getPointAttributeOffset(attributeIndex) + ordinate]; + else + return VertexDescription.getDefaultValue(semantics); + } + + /** + * Returns value of the given vertex attribute's ordinate. The ordinate is + * always 0 because integer attributes always have one component. + * + * @param semantics The attribute semantics. + * @param ordinate The attribute's ordinate. For example, the y coordinate of the + * NORMAL has ordinate of 1. + * @return The ordinate value truncated to a 32 bit integer value. + */ + public int getAttributeAsInt(int semantics, int ordinate) { + if (isEmptyImpl()) + throw new GeometryException( + "This operation was performed on an Empty Geometry."); + + int ncomps = VertexDescription.getComponentCount(semantics); + if (ordinate >= ncomps) + throw new IndexOutOfBoundsException(); + + int attributeIndex = m_description.getAttributeIndex(semantics); + if (attributeIndex >= 0) + return (int) m_attributes[m_description + ._getPointAttributeOffset(attributeIndex) + ordinate]; + else + return (int) VertexDescription.getDefaultValue(semantics); + } + + /** + * Sets the value of the attribute. + * + * @param semantics The attribute semantics. + * @param ordinate The ordinate of the attribute. + * @param value Is the array to write values to. The attribute type and the + * number of elements must match the persistence type, as well as + * the number of components of the attribute. + */ + public void setAttribute(int semantics, int ordinate, double value) { + _touch(); + int ncomps = VertexDescription.getComponentCount(semantics); + if (ncomps < ordinate) + throw new IndexOutOfBoundsException(); + + int attributeIndex = m_description.getAttributeIndex(semantics); + if (attributeIndex < 0) { + addAttribute(semantics); + attributeIndex = m_description.getAttributeIndex(semantics); + } + + if (m_attributes == null) + _setToDefault(); + + m_attributes[m_description._getPointAttributeOffset(attributeIndex) + + ordinate] = value; + } + + public void setAttribute(int semantics, int ordinate, int value) { + setAttribute(semantics, ordinate, (double) value); + } @Override - public long estimateMemorySize() - { + public Geometry.Type getType() { + return Type.Point; + } + + @Override + public int getDimension() { + return 0; + } + + @Override + public long estimateMemorySize() { return SIZE_OF_POINT + estimateMemorySize(m_attributes); } @@ -368,239 +367,239 @@ public void setEmpty() { } } - @Override - protected void _assignVertexDescriptionImpl(VertexDescription newDescription) { - if (m_attributes == null) { - m_description = newDescription; - return; - } - - int[] mapping = VertexDescriptionDesignerImpl.mapAttributes(newDescription, m_description); - - double[] newAttributes = new double[newDescription.getTotalComponentCount()]; - - int j = 0; - for (int i = 0, n = newDescription.getAttributeCount(); i < n; i++) { - int semantics = newDescription.getSemantics(i); - int nords = VertexDescription.getComponentCount(semantics); - if (mapping[i] == -1) { - double d = VertexDescription.getDefaultValue(semantics); - for (int ord = 0; ord < nords; ord++) { - newAttributes[j] = d; - j++; - } - } else { - int m = mapping[i]; - int offset = m_description._getPointAttributeOffset(m); - for (int ord = 0; ord < nords; ord++) { - newAttributes[j] = m_attributes[offset]; - j++; - offset++; - } - } - - } - - m_attributes = newAttributes; - m_description = newDescription; - } - - /** - * Sets the Point to a default, non-empty state. - */ - void _setToDefault() { - resizeAttributes(m_description.getTotalComponentCount()); - Point.attributeCopy(m_description._getDefaultPointAttributes(), - m_attributes, m_description.getTotalComponentCount()); - m_attributes[0] = NumberUtils.NaN(); - m_attributes[1] = NumberUtils.NaN(); - } - - @Override - public void applyTransformation(Transformation2D transform) { - if (isEmptyImpl()) - return; - - Point2D pt = getXY(); - transform.transform(pt, pt); - setXY(pt); - } - - @Override - void applyTransformation(Transformation3D transform) { - if (isEmptyImpl()) - return; - - addAttribute(Semantics.Z); - Point3D pt = getXYZ(); - setXYZ(transform.transform(pt)); - } - - @Override - public void copyTo(Geometry dst) { - if (dst.getType() != Type.Point) - throw new IllegalArgumentException(); - - Point pointDst = (Point) dst; - dst._touch(); - - if (m_attributes == null) { - pointDst.setEmpty(); - pointDst.m_attributes = null; - pointDst.assignVertexDescription(m_description); - } else { - pointDst.assignVertexDescription(m_description); - pointDst.resizeAttributes(m_description.getTotalComponentCount()); - attributeCopy(m_attributes, pointDst.m_attributes, - m_description.getTotalComponentCount()); - } - } - - @Override - public Geometry createInstance() { - Point point = new Point(m_description); - return point; - } - - @Override - public boolean isEmpty() { - return isEmptyImpl(); - } - - final boolean isEmptyImpl() { - return ((m_attributes == null) || NumberUtils.isNaN(m_attributes[0]) || NumberUtils - .isNaN(m_attributes[1])); - } - - @Override - public void queryEnvelope(Envelope env) { - env.setEmpty(); - if (m_description != env.m_description) - env.assignVertexDescription(m_description); - env.merge(this); - } - - @Override - public void queryEnvelope2D(Envelope2D env) { - - if (isEmptyImpl()) { - env.setEmpty(); - return; - } - - env.xmin = m_attributes[0]; - env.ymin = m_attributes[1]; - env.xmax = m_attributes[0]; - env.ymax = m_attributes[1]; - } - - @Override - void queryEnvelope3D(Envelope3D env) { - if (isEmptyImpl()) { - env.setEmpty(); - return; - } - - Point3D pt = getXYZ(); - env.xmin = pt.x; - env.ymin = pt.y; - env.zmin = pt.z; - env.xmax = pt.x; - env.ymax = pt.y; - env.zmax = pt.z; - } - - @Override - public Envelope1D queryInterval(int semantics, int ordinate) { - Envelope1D env = new Envelope1D(); - if (isEmptyImpl()) { - env.setEmpty(); - return env; - } - - double s = getAttributeAsDbl(semantics, ordinate); - env.vmin = s; - env.vmax = s; - return env; - } - - private void resizeAttributes(int newSize) { - if (m_attributes == null) { - m_attributes = new double[newSize]; - } else if (m_attributes.length < newSize) { - double[] newbuffer = new double[newSize]; - System.arraycopy(m_attributes, 0, newbuffer, 0, m_attributes.length); - m_attributes = newbuffer; - } - } - - static void attributeCopy(double[] src, double[] dst, int count) { - if (count > 0) - System.arraycopy(src, 0, dst, 0, count); - } - - /** - * Set the X and Y coordinate of the point. - * - * @param x X coordinate of the point. - * @param y Y coordinate of the point. - */ - public void setXY(double x, double y) { - _touch(); - - if (m_attributes == null) - _setToDefault(); - - m_attributes[0] = x; - m_attributes[1] = y; - } - - /** - * Returns TRUE when this geometry has exactly same type, properties, and - * coordinates as the other geometry. - */ - @Override - public boolean equals(Object _other) { - if (_other == this) - return true; - - if (!(_other instanceof Point)) - return false; - - Point otherPt = (Point) _other; - - if (m_description != otherPt.m_description) - return false; - - if (isEmptyImpl()) - if (otherPt.isEmptyImpl()) - return true; - else - return false; - - for (int i = 0, n = m_description.getTotalComponentCount(); i < n; i++) - if (m_attributes[i] != otherPt.m_attributes[i]) - return false; - - return true; - } - - /** - * Returns the hash code for the point. - */ - - @Override - public int hashCode() { - int hashCode = m_description.hashCode(); - if (!isEmptyImpl()) { - for (int i = 0, n = m_description.getTotalComponentCount(); i < n; i++) { - long bits = Double.doubleToLongBits(m_attributes[i]); - int hc = (int) (bits ^ (bits >>> 32)); - hashCode = NumberUtils.hash(hashCode, hc); - } - } - return hashCode; - } + @Override + protected void _assignVertexDescriptionImpl(VertexDescription newDescription) { + if (m_attributes == null) { + m_description = newDescription; + return; + } + + int[] mapping = VertexDescriptionDesignerImpl.mapAttributes(newDescription, m_description); + + double[] newAttributes = new double[newDescription.getTotalComponentCount()]; + + int j = 0; + for (int i = 0, n = newDescription.getAttributeCount(); i < n; i++) { + int semantics = newDescription.getSemantics(i); + int nords = VertexDescription.getComponentCount(semantics); + if (mapping[i] == -1) { + double d = VertexDescription.getDefaultValue(semantics); + for (int ord = 0; ord < nords; ord++) { + newAttributes[j] = d; + j++; + } + } else { + int m = mapping[i]; + int offset = m_description._getPointAttributeOffset(m); + for (int ord = 0; ord < nords; ord++) { + newAttributes[j] = m_attributes[offset]; + j++; + offset++; + } + } + + } + + m_attributes = newAttributes; + m_description = newDescription; + } + + /** + * Sets the Point to a default, non-empty state. + */ + void _setToDefault() { + resizeAttributes(m_description.getTotalComponentCount()); + Point.attributeCopy(m_description._getDefaultPointAttributes(), + m_attributes, m_description.getTotalComponentCount()); + m_attributes[0] = NumberUtils.NaN(); + m_attributes[1] = NumberUtils.NaN(); + } + + @Override + public void applyTransformation(Transformation2D transform) { + if (isEmptyImpl()) + return; + + Point2D pt = getXY(); + transform.transform(pt, pt); + setXY(pt); + } + + @Override + void applyTransformation(Transformation3D transform) { + if (isEmptyImpl()) + return; + + addAttribute(Semantics.Z); + Point3D pt = getXYZ(); + setXYZ(transform.transform(pt)); + } + + @Override + public void copyTo(Geometry dst) { + if (dst.getType() != Type.Point) + throw new IllegalArgumentException(); + + Point pointDst = (Point) dst; + dst._touch(); + + if (m_attributes == null) { + pointDst.setEmpty(); + pointDst.m_attributes = null; + pointDst.assignVertexDescription(m_description); + } else { + pointDst.assignVertexDescription(m_description); + pointDst.resizeAttributes(m_description.getTotalComponentCount()); + attributeCopy(m_attributes, pointDst.m_attributes, + m_description.getTotalComponentCount()); + } + } + + @Override + public Geometry createInstance() { + Point point = new Point(m_description); + return point; + } + + @Override + public boolean isEmpty() { + return isEmptyImpl(); + } + + final boolean isEmptyImpl() { + return ((m_attributes == null) || NumberUtils.isNaN(m_attributes[0]) || NumberUtils + .isNaN(m_attributes[1])); + } + + @Override + public void queryEnvelope(Envelope env) { + env.setEmpty(); + if (m_description != env.m_description) + env.assignVertexDescription(m_description); + env.merge(this); + } + + @Override + public void queryEnvelope2D(Envelope2D env) { + + if (isEmptyImpl()) { + env.setEmpty(); + return; + } + + env.xmin = m_attributes[0]; + env.ymin = m_attributes[1]; + env.xmax = m_attributes[0]; + env.ymax = m_attributes[1]; + } + + @Override + void queryEnvelope3D(Envelope3D env) { + if (isEmptyImpl()) { + env.setEmpty(); + return; + } + + Point3D pt = getXYZ(); + env.xmin = pt.x; + env.ymin = pt.y; + env.zmin = pt.z; + env.xmax = pt.x; + env.ymax = pt.y; + env.zmax = pt.z; + } + + @Override + public Envelope1D queryInterval(int semantics, int ordinate) { + Envelope1D env = new Envelope1D(); + if (isEmptyImpl()) { + env.setEmpty(); + return env; + } + + double s = getAttributeAsDbl(semantics, ordinate); + env.vmin = s; + env.vmax = s; + return env; + } + + private void resizeAttributes(int newSize) { + if (m_attributes == null) { + m_attributes = new double[newSize]; + } else if (m_attributes.length < newSize) { + double[] newbuffer = new double[newSize]; + System.arraycopy(m_attributes, 0, newbuffer, 0, m_attributes.length); + m_attributes = newbuffer; + } + } + + static void attributeCopy(double[] src, double[] dst, int count) { + if (count > 0) + System.arraycopy(src, 0, dst, 0, count); + } + + /** + * Set the X and Y coordinate of the point. + * + * @param x X coordinate of the point. + * @param y Y coordinate of the point. + */ + public void setXY(double x, double y) { + _touch(); + + if (m_attributes == null) + _setToDefault(); + + m_attributes[0] = x; + m_attributes[1] = y; + } + + /** + * Returns TRUE when this geometry has exactly same type, properties, and + * coordinates as the other geometry. + */ + @Override + public boolean equals(Object _other) { + if (_other == this) + return true; + + if (!(_other instanceof Point)) + return false; + + Point otherPt = (Point) _other; + + if (m_description != otherPt.m_description) + return false; + + if (isEmptyImpl()) + if (otherPt.isEmptyImpl()) + return true; + else + return false; + + for (int i = 0, n = m_description.getTotalComponentCount(); i < n; i++) + if (m_attributes[i] != otherPt.m_attributes[i]) + return false; + + return true; + } + + /** + * Returns the hash code for the point. + */ + + @Override + public int hashCode() { + int hashCode = m_description.hashCode(); + if (!isEmptyImpl()) { + for (int i = 0, n = m_description.getTotalComponentCount(); i < n; i++) { + long bits = Double.doubleToLongBits(m_attributes[i]); + int hc = (int) (bits ^ (bits >>> 32)); + hashCode = NumberUtils.hash(hashCode, hc); + } + } + return hashCode; + } @Override public Geometry getBoundary() { diff --git a/src/main/java/com/esri/core/geometry/Point2D.java b/src/main/java/com/esri/core/geometry/Point2D.java index b20e81d0..a02790fe 100644 --- a/src/main/java/com/esri/core/geometry/Point2D.java +++ b/src/main/java/com/esri/core/geometry/Point2D.java @@ -33,780 +33,781 @@ * Basic 2D point class. Contains only two double fields. */ public final class Point2D implements Serializable { - private static final long serialVersionUID = 1L; - - public double x; - public double y; - - public Point2D() { - } - - public Point2D(double x, double y) { - this.x = x; - this.y = y; - } - - public Point2D(Point2D other) { - setCoords(other); - } - - public static Point2D construct(double x, double y) { - return new Point2D(x, y); - } - - public void setCoords(double x, double y) { - this.x = x; - this.y = y; - } - - public void setCoords(Point2D other) { - x = other.x; - y = other.y; - } - - public boolean isEqual(Point2D other) { - return x == other.x && y == other.y; - } - - public boolean isEqual(double x_, double y_) { - return x == x_ && y == y_; - } - - public boolean isEqual(Point2D other, double tol) { - return (Math.abs(x - other.x) <= tol) && (Math.abs(y - other.y) <= tol); - } - - public boolean equals(Point2D other) { - return x == other.x && y == other.y; - } - - @Override - public boolean equals(Object other) { - if (other == this) - return true; - - if (!(other instanceof Point2D)) - return false; - - Point2D v = (Point2D) other; - - return x == v.x && y == v.y; - } - - public void sub(Point2D other) { - x -= other.x; - y -= other.y; - } - - public void sub(Point2D p1, Point2D p2) { - x = p1.x - p2.x; - y = p1.y - p2.y; - } - - public void add(Point2D other) { - x += other.x; - y += other.y; - } - - public void add(Point2D p1, Point2D p2) { - x = p1.x + p2.x; - y = p1.y + p2.y; - } - - public void negate() { - x = -x; - y = -y; - } - - public void negate(Point2D other) { - x = -other.x; - y = -other.y; - } - - public void interpolate(Point2D other, double alpha) { - MathUtils.lerp(this, other, alpha, this); - } - - public void interpolate(Point2D p1, Point2D p2, double alpha) { - MathUtils.lerp(p1, p2, alpha, this); - } - - /** - * Calculates this = this * f + shift - * - * @param f - * @param shift - */ - public void scaleAdd(double f, Point2D shift) { - x = x * f + shift.x; - y = y * f + shift.y; - } - - /** - * Calculates this = other * f + shift - * - * @param f - * @param other - * @param shift - */ - public void scaleAdd(double f, Point2D other, Point2D shift) { - x = other.x * f + shift.x; - y = other.y * f + shift.y; - } - - public void scale(double f, Point2D other) { - x = f * other.x; - y = f * other.y; - } - - public void scale(double f) { - x *= f; - y *= f; - } - - /** - * Compares two vertices lexicographically by y. - */ - public int compare(Point2D other) { - return y < other.y ? -1 : (y > other.y ? 1 : (x < other.x ? -1 - : (x > other.x ? 1 : 0))); - } - - /** - * Compares two vertices lexicographically by x. - */ - int compareX(Point2D other) { - return x < other.x ? -1 : (x > other.x ? 1 : (y < other.y ? -1 - : (y > other.y ? 1 : 0))); - } - - public void normalize(Point2D other) { - double len = other.length(); - if (len == 0) { - x = 1.0; - y = 0.0; - } else { - x = other.x / len; - y = other.y / len; - } - } - - public void normalize() { - double len = length(); - if (len == 0)// (!len) - { - x = 1.0; - y = 0.0; - } - x /= len; - y /= len; - } - - public double length() { - return Math.sqrt(x * x + y * y); - } - - public double sqrLength() { - return x * x + y * y; - } - - public static double distance(Point2D pt1, Point2D pt2) { - return Math.sqrt(sqrDistance(pt1, pt2)); - } - - public double dotProduct(Point2D other) { - return x * other.x + y * other.y; - } - - double _dotProductAbs(Point2D other) { - return Math.abs(x * other.x) + Math.abs(y * other.y); - } - - public double crossProduct(Point2D other) { - return x * other.y - y * other.x; - } - - public void rotateDirect(double Cos, double Sin) // corresponds to the - // Transformation2D.SetRotate(cos, - // sin).Transform(pt) - { - double xx = x * Cos - y * Sin; - double yy = x * Sin + y * Cos; - x = xx; - y = yy; - } - - public void rotateReverse(double Cos, double Sin) { - double xx = x * Cos + y * Sin; - double yy = -x * Sin + y * Cos; - x = xx; - y = yy; - } - - /** - * 90 degree rotation, anticlockwise. Equivalent to RotateDirect(cos(pi/2), - * sin(pi/2)). - */ - public void leftPerpendicular() { - double xx = x; - x = -y; - y = xx; - } - - /** - * 90 degree rotation, anticlockwise. Equivalent to RotateDirect(cos(pi/2), - * sin(pi/2)). - */ - public void leftPerpendicular(Point2D pt) { - x = -pt.y; - y = pt.x; - } - - /** - * 270 degree rotation, anticlockwise. Equivalent to - * RotateDirect(-cos(pi/2), sin(-pi/2)). - */ - public void rightPerpendicular() { - double xx = x; - x = y; - y = -xx; - } - - /** - * 270 degree rotation, anticlockwise. Equivalent to - * RotateDirect(-cos(pi/2), sin(-pi/2)). - */ - public void rightPerpendicular(Point2D pt) { - x = pt.y; - y = -pt.x; - } - - void _setNan() { - x = NumberUtils.NaN(); - y = NumberUtils.NaN(); - } - - boolean _isNan() { - return NumberUtils.isNaN(x) || NumberUtils.isNaN(y); - } - - // calculates which quarter of xy plane the vector lies in. First quater is - // between vectors (1,0) and (0, 1), second between (0, 1) and (-1, 0), etc. - // Angle intervals corresponding to quarters: 1 : [0 : 90); 2 : [90 : 180); - // 3 : [180 : 270); 4 : [270 : 360) - final int _getQuarter() { - if (x > 0) { - if (y >= 0) - return 1; // x > 0 && y <= 0 - else - return 4; // y < 0 && x > 0. Should be x >= 0 && y < 0. The x == - // 0 case is processed later. - } else { - if (y > 0) - return 2; // x <= 0 && y > 0 - else - return x == 0 ? 4 : 3; // 3: x < 0 && y <= 0. The case x == 0 && - // y <= 0 is attribute to the case 4. - // The point x==0 and y==0 is a bug, but - // will be assigned to 4. - } - } - - /** - * Calculates which quarter of XY plane the vector lies in. First quarter is - * between vectors (1,0) and (0, 1), second between (0, 1) and (-1, 0), etc. - * The quarters are numbered counterclockwise. - * Angle intervals corresponding to quarters: 1 : [0 : 90); 2 : [90 : 180); - * 3 : [180 : 270); 4 : [270 : 360) - */ - public int getQuarter() { - return _getQuarter(); - } - - // Assume vector v1 and v2 have same origin. The function compares the - // vectors by angle from the x axis to the vector in the counter clockwise - // direction. - // > > - // \ / - // V3 \ / V1 - // \ - // \ - // >V2 - // _compareVectors(V1, V2) == -1. - // _compareVectors(V1, V3) == -1 - // _compareVectors(V2, V3) == 1 - // - final static int _compareVectors(Point2D v1, Point2D v2) { - int q1 = v1._getQuarter(); - int q2 = v2._getQuarter(); - - if (q2 == q1) { - double cross = v1.crossProduct(v2); - return cross < 0 ? 1 : (cross > 0 ? -1 : 0); - } else - return q1 < q2 ? -1 : 1; - } - - /** - * Assume vector v1 and v2 have same origin. The function compares the - * vectors by angle in the counter clockwise direction from the axis X. - *

- * For example, V1 makes 30 degree angle counterclockwise from horizontal x axis - * V2, makes 270, V3 makes 90, then - * compareVectors(V1, V2) == -1. - * compareVectors(V1, V3) == -1. - * compareVectors(V2, V3) == 1. - * - * @return Returns 1 if v1 is less than v2, 0 if equal, and 1 if greater. - */ - public static int compareVectors(Point2D v1, Point2D v2) { - return _compareVectors(v1, v2); - } - - static class CompareVectors implements Comparator { - @Override - public int compare(Point2D v1, Point2D v2) { - return _compareVectors((Point2D) v1, (Point2D) v2); - } - } - - public static double sqrDistance(Point2D pt1, Point2D pt2) { - double dx = pt1.x - pt2.x; - double dy = pt1.y - pt2.y; - return dx * dx + dy * dy; - } - - @Override - public String toString() { - return "(" + x + " , " + y + ")"; - } - - public void setNaN() { - x = NumberUtils.NaN(); - y = NumberUtils.NaN(); - } - - public boolean isNaN() { - return NumberUtils.isNaN(x) || NumberUtils.isNaN(y); - } - - // metric = 1: Manhattan metric - // 2: Euclidian metric (default) - // 0: used for L-infinite (max(fabs(x), fabs(y)) - // for predefined metrics, use the DistanceMetricEnum defined in WKSPoint.h - double _norm(int metric) { - if (metric < 0 || _isNan()) - return NumberUtils.NaN(); - - switch (metric) { - case 0: // L-infinite - return Math.abs(x) >= Math.abs(y) ? Math.abs(x) : Math.abs(y); - - case 1: // L1 or Manhattan metric - return Math.abs(x) + Math.abs(y); - - case 2: // L2 or Euclidean metric - return Math.sqrt(x * x + y * y); - - default: - return Math - .pow(Math.pow(x, (double) metric) - + Math.pow(y, (double) metric), - 1.0 / (double) metric); - } - } - - /** - * returns signed distance of point from infinite line represented by - * pt_1...pt_2. The returned distance is positive if this point lies on the - * right-hand side of the line, negative otherwise. If the two input points - * are equal, the (positive) distance of this point to p_1 is returned. - */ - double offset(/* const */Point2D pt1, /* const */Point2D pt2) { - double newDistance = distance(pt1, pt2); - Point2D p = construct(x, y); - if (newDistance == 0.0) - return distance(p, pt1); - - // get vectors relative to pt_1 - Point2D p2 = new Point2D(); - p2.setCoords(pt2); - p2.sub(pt1); - p.sub(pt1); - - double cross = p.crossProduct(p2); - return cross / newDistance; - } - - /** - * Calculates the orientation of the triangle formed by p->q->r. Returns 1 - * for counter-clockwise, -1 for clockwise, and 0 for collinear. May use - * high precision arithmetics for some special degenerate cases. - */ - public static int orientationRobust(Point2D p, Point2D q, Point2D r) { - ECoordinate det_ec = new ECoordinate(); - det_ec.set(q.x); - det_ec.sub(p.x); - - ECoordinate rp_y_ec = new ECoordinate(); - rp_y_ec.set(r.y); - rp_y_ec.sub(p.y); - - ECoordinate qp_y_ec = new ECoordinate(); - qp_y_ec.set(q.y); - qp_y_ec.sub(p.y); - - ECoordinate rp_x_ec = new ECoordinate(); - rp_x_ec.set(r.x); - rp_x_ec.sub(p.x); - - det_ec.mul(rp_y_ec); - qp_y_ec.mul(rp_x_ec); - det_ec.sub(qp_y_ec); - - if (!det_ec.isFuzzyZero()) { - double det_ec_value = det_ec.value(); - - if (det_ec_value < 0.0) - return -1; - - if (det_ec_value > 0.0) - return 1; - - return 0; - } - - // Need extended precision - - BigDecimal det_mp = new BigDecimal(q.x); - BigDecimal px_mp = new BigDecimal(p.x); - BigDecimal py_mp = new BigDecimal(p.y); - det_mp = det_mp.subtract(px_mp); - - BigDecimal rp_y_mp = new BigDecimal(r.y); - rp_y_mp = rp_y_mp.subtract(py_mp); - - BigDecimal qp_y_mp = new BigDecimal(q.y); - qp_y_mp = qp_y_mp.subtract(py_mp); - - BigDecimal rp_x_mp = new BigDecimal(r.x); - rp_x_mp = rp_x_mp.subtract(px_mp); - - det_mp = det_mp.multiply(rp_y_mp); - qp_y_mp = qp_y_mp.multiply(rp_x_mp); - det_mp = det_mp.subtract(qp_y_mp); - - return det_mp.signum(); - } - - private static int inCircleRobustMP_(Point2D p, Point2D q, Point2D r, Point2D s) { - BigDecimal sx_mp = new BigDecimal(s.x), sy_mp = new BigDecimal(s.y); - - BigDecimal psx_mp = new BigDecimal(p.x), psy_mp = new BigDecimal(p.y); - psx_mp = psx_mp.subtract(sx_mp); - psy_mp = psy_mp.subtract(sy_mp); - - BigDecimal qsx_mp = new BigDecimal(q.x), qsy_mp = new BigDecimal(q.y); - qsx_mp = qsx_mp.subtract(sx_mp); - qsy_mp = qsy_mp.subtract(sy_mp); - - BigDecimal rsx_mp = new BigDecimal(r.x), rsy_mp = new BigDecimal(r.y); - rsx_mp = rsx_mp.subtract(sx_mp); - rsy_mp = rsy_mp.subtract(sy_mp); - - BigDecimal pq_det_mp = psx_mp.multiply(qsy_mp).subtract(psy_mp.multiply(qsx_mp)); - BigDecimal qr_det_mp = qsx_mp.multiply(rsy_mp).subtract(qsy_mp.multiply(rsx_mp)); - BigDecimal pr_det_mp = psx_mp.multiply(rsy_mp).subtract(psy_mp.multiply(rsx_mp)); - - BigDecimal p_parab_mp = psx_mp.multiply(psx_mp).add(psy_mp.multiply(psy_mp)); - BigDecimal q_parab_mp = qsx_mp.multiply(qsx_mp).add(qsy_mp.multiply(qsy_mp)); - BigDecimal r_parab_mp = rsx_mp.multiply(rsx_mp).add(rsy_mp.multiply(rsy_mp)); - - BigDecimal det_mp = (p_parab_mp.multiply(qr_det_mp).subtract(q_parab_mp.multiply(pr_det_mp))) - .add(r_parab_mp.multiply(pq_det_mp)); - - return det_mp.signum(); - } - - /** - * Calculates if the point s is inside of the circumcircle inscribed by the clockwise oriented triangle p-q-r. - * Returns 1 for outside, -1 for inside, and 0 for cocircular. - * Note that the convention used here differs from what is commonly found in literature, which can define the relation - * in terms of a counter-clockwise oriented circle, and this flips the sign (think of the signed volume of the tetrahedron). - * May use high precision arithmetics for some special cases. - */ - static int inCircleRobust(Point2D p, Point2D q, Point2D r, Point2D s) { - ECoordinate psx_ec = new ECoordinate(), psy_ec = new ECoordinate(); - psx_ec.set(p.x); - psx_ec.sub(s.x); - psy_ec.set(p.y); - psy_ec.sub(s.y); - - ECoordinate qsx_ec = new ECoordinate(), qsy_ec = new ECoordinate(); - qsx_ec.set(q.x); - qsx_ec.sub(s.x); - qsy_ec.set(q.y); - qsy_ec.sub(s.y); - - ECoordinate rsx_ec = new ECoordinate(), rsy_ec = new ECoordinate(); - rsx_ec.set(r.x); - rsx_ec.sub(s.x); - rsy_ec.set(r.y); - rsy_ec.sub(s.y); - - ECoordinate psx_ec_qsy_ec = new ECoordinate(); - psx_ec_qsy_ec.set(psx_ec); - psx_ec_qsy_ec.mul(qsy_ec); - ECoordinate psy_ec_qsx_ec = new ECoordinate(); - psy_ec_qsx_ec.set(psy_ec); - psy_ec_qsx_ec.mul(qsx_ec); - ECoordinate qsx_ec_rsy_ec = new ECoordinate(); - qsx_ec_rsy_ec.set(qsx_ec); - qsx_ec_rsy_ec.mul(rsy_ec); - ECoordinate qsy_ec_rsx_ec = new ECoordinate(); - qsy_ec_rsx_ec.set(qsy_ec); - qsy_ec_rsx_ec.mul(rsx_ec); - ECoordinate psx_ec_rsy_ec = new ECoordinate(); - psx_ec_rsy_ec.set(psx_ec); - psx_ec_rsy_ec.mul(rsy_ec); - ECoordinate psy_ec_rsx_ec = new ECoordinate(); - psy_ec_rsx_ec.set(psy_ec); - psy_ec_rsx_ec.mul(rsx_ec); - - ECoordinate pq_det_ec = new ECoordinate(); - pq_det_ec.set(psx_ec_qsy_ec); - pq_det_ec.sub(psy_ec_qsx_ec); - ECoordinate qr_det_ec = new ECoordinate(); - qr_det_ec.set(qsx_ec_rsy_ec); - qr_det_ec.sub(qsy_ec_rsx_ec); - ECoordinate pr_det_ec = new ECoordinate(); - pr_det_ec.set(psx_ec_rsy_ec); - pr_det_ec.sub(psy_ec_rsx_ec); - - ECoordinate psx_ec_psx_ec = new ECoordinate(); - psx_ec_psx_ec.set(psx_ec); - psx_ec_psx_ec.mul(psx_ec); - ECoordinate psy_ec_psy_ec = new ECoordinate(); - psy_ec_psy_ec.set(psy_ec); - psy_ec_psy_ec.mul(psy_ec); - ECoordinate qsx_ec_qsx_ec = new ECoordinate(); - qsx_ec_qsx_ec.set(qsx_ec); - qsx_ec_qsx_ec.mul(qsx_ec); - ECoordinate qsy_ec_qsy_ec = new ECoordinate(); - qsy_ec_qsy_ec.set(qsy_ec); - qsy_ec_qsy_ec.mul(qsy_ec); - ECoordinate rsx_ec_rsx_ec = new ECoordinate(); - rsx_ec_rsx_ec.set(rsx_ec); - rsx_ec_rsx_ec.mul(rsx_ec); - ECoordinate rsy_ec_rsy_ec = new ECoordinate(); - rsy_ec_rsy_ec.set(rsy_ec); - rsy_ec_rsy_ec.mul(rsy_ec); - - ECoordinate p_parab_ec = new ECoordinate(); - p_parab_ec.set(psx_ec_psx_ec); - p_parab_ec.add(psy_ec_psy_ec); - ECoordinate q_parab_ec = new ECoordinate(); - q_parab_ec.set(qsx_ec_qsx_ec); - q_parab_ec.add(qsy_ec_qsy_ec); - ECoordinate r_parab_ec = new ECoordinate(); - r_parab_ec.set(rsx_ec_rsx_ec); - r_parab_ec.add(rsy_ec_rsy_ec); - - p_parab_ec.mul(qr_det_ec); - q_parab_ec.mul(pr_det_ec); - r_parab_ec.mul(pq_det_ec); - - ECoordinate det_ec = new ECoordinate(); - det_ec.set(p_parab_ec); - det_ec.sub(q_parab_ec); - det_ec.add(r_parab_ec); - - if (!det_ec.isFuzzyZero()) { - double det_ec_value = det_ec.value(); - - if (det_ec_value < 0.0) - return -1; - - if (det_ec_value > 0.0) - return 1; - - return 0; - } - - return inCircleRobustMP_(p, q, r, s); - } - - private static Point2D calculateCenterFromThreePointsHelperMP_(Point2D from, Point2D mid_point, Point2D to) { - assert (!mid_point.isEqual(to) && !mid_point.isEqual(from) && !from.isEqual(to)); - BigDecimal mx = new BigDecimal(mid_point.x); - mx = mx.subtract(new BigDecimal(from.x)); - BigDecimal my = new BigDecimal(mid_point.y); - my = my.subtract(new BigDecimal(from.y)); - BigDecimal tx = new BigDecimal(to.x); - tx = tx.subtract(new BigDecimal(from.x)); - BigDecimal ty = new BigDecimal(to.y); - ty = ty.subtract(new BigDecimal(from.y)); - - BigDecimal d = mx.multiply(ty); - BigDecimal tmp = my.multiply(tx); - d = d.subtract(tmp); - - if (d.signum() == 0) { - return Point2D.construct(NumberUtils.NaN(), NumberUtils.NaN()); - } - - d = d.multiply(new BigDecimal(2.0)); - - BigDecimal mx2 = mx.multiply(mx); - BigDecimal my2 = my.multiply(my); - BigDecimal m_norm2 = mx2.add(my2); - BigDecimal tx2 = tx.multiply(tx); - BigDecimal ty2 = ty.multiply(ty); - BigDecimal t_norm2 = tx2.add(ty2); - - BigDecimal xo = my.multiply(t_norm2); - tmp = ty.multiply(m_norm2); - xo = xo.subtract(tmp); - xo = xo.divide(d, BigDecimal.ROUND_HALF_EVEN); - - BigDecimal yo = mx.multiply(t_norm2); - tmp = tx.multiply(m_norm2); - yo = yo.subtract(tmp); - yo = yo.divide(d, BigDecimal.ROUND_HALF_EVEN); - - Point2D center = Point2D.construct(from.x - xo.doubleValue(), from.y + yo.doubleValue()); - return center; - } - - private static Point2D calculateCenterFromThreePointsHelper_(Point2D from, Point2D mid_point, Point2D to) { - assert (!mid_point.isEqual(to) && !mid_point.isEqual(from) && !from.isEqual(to)); - ECoordinate mx = new ECoordinate(mid_point.x); - mx.sub(from.x); - ECoordinate my = new ECoordinate(mid_point.y); - my.sub(from.y); - ECoordinate tx = new ECoordinate(to.x); - tx.sub(from.x); - ECoordinate ty = new ECoordinate(to.y); - ty.sub(from.y); - - ECoordinate d = new ECoordinate(mx); - d.mul(ty); - ECoordinate tmp = new ECoordinate(my); - tmp.mul(tx); - d.sub(tmp); - - if (d.value() == 0.0) { - return Point2D.construct(NumberUtils.NaN(), NumberUtils.NaN()); - } - - d.mul(2.0); - - ECoordinate mx2 = new ECoordinate(mx); - mx2.mul(mx); - ECoordinate my2 = new ECoordinate(my); - my2.mul(my); - ECoordinate m_norm2 = new ECoordinate(mx2); - m_norm2.add(my2); - ECoordinate tx2 = new ECoordinate(tx); - tx2.mul(tx); - ECoordinate ty2 = new ECoordinate(ty); - ty2.mul(ty); - ECoordinate t_norm2 = new ECoordinate(tx2); - t_norm2.add(ty2); - - ECoordinate xo = new ECoordinate(my); - xo.mul(t_norm2); - tmp = new ECoordinate(ty); - tmp.mul(m_norm2); - xo.sub(tmp); - xo.div(d); - - ECoordinate yo = new ECoordinate(mx); - yo.mul(t_norm2); - tmp = new ECoordinate(tx); - tmp.mul(m_norm2); - yo.sub(tmp); - yo.div(d); - - Point2D center = Point2D.construct(from.x - xo.value(), from.y + yo.value()); - double r1 = Point2D.construct(from.x - center.x, from.y - center.y).length(); - double r2 = Point2D.construct(mid_point.x - center.x, mid_point.y - center.y).length(); - double r3 = Point2D.construct(to.x - center.x, to.y - center.y).length(); - double base = r1 + Math.abs(from.x) + Math.abs(mid_point.x) + Math.abs(to.x) + Math.abs(from.y) - + Math.abs(mid_point.y) + Math.abs(to.y); - - double tol = 1e-15; - if ((Math.abs(r1 - r2) <= base * tol && Math.abs(r1 - r3) <= base * tol)) - return center;//returns center value for MP_value type or when calculated radius value for from - center, mid - center, and to - center are very close. - - return Point2D.construct(NumberUtils.NaN(), NumberUtils.NaN()); - } - - /** - * Calculate the center of a circle, whose perimeter contains the points from, mid_point and to - * @param from - * @param mid_point - * @param to - * @return - */ - static Point2D calculateCircleCenterFromThreePoints(Point2D from, Point2D mid_point, Point2D to) { - if (from.isEqual(to) || from.isEqual(mid_point) || to.isEqual(mid_point)) { - return new Point2D(NumberUtils.NaN(), NumberUtils.NaN()); - } - - Point2D pt = calculateCenterFromThreePointsHelper_(from, mid_point, to); //use error tracking calculations - if (pt.isNaN()) - return calculateCenterFromThreePointsHelperMP_(from, mid_point, to); //use precise calculations - else { - return pt; - } - } - - @Override - public int hashCode() { - return NumberUtils.hash(NumberUtils.hash(x), y); - } - - public double calculateTriangleArea2D(Point2D pt1, Point2D pt2) { - double a = Point2D.distance(pt1, pt2); - double b = Point2D.distance(pt1, this); - double c = Point2D.distance(pt2, this); - double temp; - - - if (a < b) { - // set a >= b - temp = b; - b = a; - a = temp; - } - - if (c > a) { - // set a >= c - temp = c; - c = a; - a = temp; - } - - if (c > b) { - temp = c; - c = b; - b = temp; - } - - // First sort a, b, c so that a ≥ b ≥ c ; this can be done at the cost of at most three comparisons. - // If c-(a-b) < 0 then the data are not side-lengths of a real triangle - if (c - (a - b) < 0) - return 0.0; - - //double result = 0.5 * Math.abs((x - pt2.x)*(pt1.y - y) - (x - pt1.x)*(pt2.y - y)); - double result = Math.sqrt((a + (b + c)) * (c - (a - b)) * (c + (a - b)) * (a + (b - c))) / 4.0; - return result; - - } - - double getAxis(int ordinate) { - assert (ordinate == 0 || ordinate == 1); - return (ordinate == 0 ? x : y); - } + private static final long serialVersionUID = 1L; + + public double x; + public double y; + + public Point2D() { + } + + public Point2D(double x, double y) { + this.x = x; + this.y = y; + } + + public Point2D(Point2D other) { + setCoords(other); + } + + public static Point2D construct(double x, double y) { + return new Point2D(x, y); + } + + public void setCoords(double x, double y) { + this.x = x; + this.y = y; + } + + public void setCoords(Point2D other) { + x = other.x; + y = other.y; + } + + public boolean isEqual(Point2D other) { + return x == other.x && y == other.y; + } + + public boolean isEqual(double x_, double y_) { + return x == x_ && y == y_; + } + + public boolean isEqual(Point2D other, double tol) { + return (Math.abs(x - other.x) <= tol) && (Math.abs(y - other.y) <= tol); + } + + public boolean equals(Point2D other) { + return x == other.x && y == other.y; + } + + @Override + public boolean equals(Object other) { + if (other == this) + return true; + + if (!(other instanceof Point2D)) + return false; + + Point2D v = (Point2D) other; + + return x == v.x && y == v.y; + } + + public void sub(Point2D other) { + x -= other.x; + y -= other.y; + } + + public void sub(Point2D p1, Point2D p2) { + x = p1.x - p2.x; + y = p1.y - p2.y; + } + + public void add(Point2D other) { + x += other.x; + y += other.y; + } + + public void add(Point2D p1, Point2D p2) { + x = p1.x + p2.x; + y = p1.y + p2.y; + } + + public void negate() { + x = -x; + y = -y; + } + + public void negate(Point2D other) { + x = -other.x; + y = -other.y; + } + + public void interpolate(Point2D other, double alpha) { + MathUtils.lerp(this, other, alpha, this); + } + + public void interpolate(Point2D p1, Point2D p2, double alpha) { + MathUtils.lerp(p1, p2, alpha, this); + } + + /** + * Calculates this = this * f + shift + * + * @param f + * @param shift + */ + public void scaleAdd(double f, Point2D shift) { + x = x * f + shift.x; + y = y * f + shift.y; + } + + /** + * Calculates this = other * f + shift + * + * @param f + * @param other + * @param shift + */ + public void scaleAdd(double f, Point2D other, Point2D shift) { + x = other.x * f + shift.x; + y = other.y * f + shift.y; + } + + public void scale(double f, Point2D other) { + x = f * other.x; + y = f * other.y; + } + + public void scale(double f) { + x *= f; + y *= f; + } + + /** + * Compares two vertices lexicographically by y. + */ + public int compare(Point2D other) { + return y < other.y ? -1 : (y > other.y ? 1 : (x < other.x ? -1 + : (x > other.x ? 1 : 0))); + } + + /** + * Compares two vertices lexicographically by x. + */ + int compareX(Point2D other) { + return x < other.x ? -1 : (x > other.x ? 1 : (y < other.y ? -1 + : (y > other.y ? 1 : 0))); + } + + public void normalize(Point2D other) { + double len = other.length(); + if (len == 0) { + x = 1.0; + y = 0.0; + } else { + x = other.x / len; + y = other.y / len; + } + } + + public void normalize() { + double len = length(); + if (len == 0)// (!len) + { + x = 1.0; + y = 0.0; + } + x /= len; + y /= len; + } + + public double length() { + return Math.sqrt(x * x + y * y); + } + + public double sqrLength() { + return x * x + y * y; + } + + public static double distance(Point2D pt1, Point2D pt2) { + return Math.sqrt(sqrDistance(pt1, pt2)); + } + + public double dotProduct(Point2D other) { + return x * other.x + y * other.y; + } + + double _dotProductAbs(Point2D other) { + return Math.abs(x * other.x) + Math.abs(y * other.y); + } + + public double crossProduct(Point2D other) { + return x * other.y - y * other.x; + } + + public void rotateDirect(double Cos, double Sin) // corresponds to the + // Transformation2D.SetRotate(cos, + // sin).Transform(pt) + { + double xx = x * Cos - y * Sin; + double yy = x * Sin + y * Cos; + x = xx; + y = yy; + } + + public void rotateReverse(double Cos, double Sin) { + double xx = x * Cos + y * Sin; + double yy = -x * Sin + y * Cos; + x = xx; + y = yy; + } + + /** + * 90 degree rotation, anticlockwise. Equivalent to RotateDirect(cos(pi/2), + * sin(pi/2)). + */ + public void leftPerpendicular() { + double xx = x; + x = -y; + y = xx; + } + + /** + * 90 degree rotation, anticlockwise. Equivalent to RotateDirect(cos(pi/2), + * sin(pi/2)). + */ + public void leftPerpendicular(Point2D pt) { + x = -pt.y; + y = pt.x; + } + + /** + * 270 degree rotation, anticlockwise. Equivalent to + * RotateDirect(-cos(pi/2), sin(-pi/2)). + */ + public void rightPerpendicular() { + double xx = x; + x = y; + y = -xx; + } + + /** + * 270 degree rotation, anticlockwise. Equivalent to + * RotateDirect(-cos(pi/2), sin(-pi/2)). + */ + public void rightPerpendicular(Point2D pt) { + x = pt.y; + y = -pt.x; + } + + void _setNan() { + x = NumberUtils.NaN(); + y = NumberUtils.NaN(); + } + + boolean _isNan() { + return NumberUtils.isNaN(x) || NumberUtils.isNaN(y); + } + + // calculates which quarter of xy plane the vector lies in. First quater is + // between vectors (1,0) and (0, 1), second between (0, 1) and (-1, 0), etc. + // Angle intervals corresponding to quarters: 1 : [0 : 90); 2 : [90 : 180); + // 3 : [180 : 270); 4 : [270 : 360) + final int _getQuarter() { + if (x > 0) { + if (y >= 0) + return 1; // x > 0 && y <= 0 + else + return 4; // y < 0 && x > 0. Should be x >= 0 && y < 0. The x == + // 0 case is processed later. + } else { + if (y > 0) + return 2; // x <= 0 && y > 0 + else + return x == 0 ? 4 : 3; // 3: x < 0 && y <= 0. The case x == 0 && + // y <= 0 is attribute to the case 4. + // The point x==0 and y==0 is a bug, but + // will be assigned to 4. + } + } + + /** + * Calculates which quarter of XY plane the vector lies in. First quarter is + * between vectors (1,0) and (0, 1), second between (0, 1) and (-1, 0), etc. + * The quarters are numbered counterclockwise. + * Angle intervals corresponding to quarters: 1 : [0 : 90); 2 : [90 : 180); + * 3 : [180 : 270); 4 : [270 : 360) + */ + public int getQuarter() { + return _getQuarter(); + } + + // Assume vector v1 and v2 have same origin. The function compares the + // vectors by angle from the x axis to the vector in the counter clockwise + // direction. + // > > + // \ / + // V3 \ / V1 + // \ + // \ + // >V2 + // _compareVectors(V1, V2) == -1. + // _compareVectors(V1, V3) == -1 + // _compareVectors(V2, V3) == 1 + // + final static int _compareVectors(Point2D v1, Point2D v2) { + int q1 = v1._getQuarter(); + int q2 = v2._getQuarter(); + + if (q2 == q1) { + double cross = v1.crossProduct(v2); + return cross < 0 ? 1 : (cross > 0 ? -1 : 0); + } else + return q1 < q2 ? -1 : 1; + } + + /** + * Assume vector v1 and v2 have same origin. The function compares the + * vectors by angle in the counter clockwise direction from the axis X. + *

+ * For example, V1 makes 30 degree angle counterclockwise from horizontal x axis + * V2, makes 270, V3 makes 90, then + * compareVectors(V1, V2) == -1. + * compareVectors(V1, V3) == -1. + * compareVectors(V2, V3) == 1. + * + * @return Returns 1 if v1 is less than v2, 0 if equal, and 1 if greater. + */ + public static int compareVectors(Point2D v1, Point2D v2) { + return _compareVectors(v1, v2); + } + + static class CompareVectors implements Comparator { + @Override + public int compare(Point2D v1, Point2D v2) { + return _compareVectors((Point2D) v1, (Point2D) v2); + } + } + + public static double sqrDistance(Point2D pt1, Point2D pt2) { + double dx = pt1.x - pt2.x; + double dy = pt1.y - pt2.y; + return dx * dx + dy * dy; + } + + @Override + public String toString() { + return "(" + x + " , " + y + ")"; + } + + public void setNaN() { + x = NumberUtils.NaN(); + y = NumberUtils.NaN(); + } + + public boolean isNaN() { + return NumberUtils.isNaN(x) || NumberUtils.isNaN(y); + } + + // metric = 1: Manhattan metric + // 2: Euclidian metric (default) + // 0: used for L-infinite (max(fabs(x), fabs(y)) + // for predefined metrics, use the DistanceMetricEnum defined in WKSPoint.h + double _norm(int metric) { + if (metric < 0 || _isNan()) + return NumberUtils.NaN(); + + switch (metric) { + case 0: // L-infinite + return Math.abs(x) >= Math.abs(y) ? Math.abs(x) : Math.abs(y); + + case 1: // L1 or Manhattan metric + return Math.abs(x) + Math.abs(y); + + case 2: // L2 or Euclidean metric + return Math.sqrt(x * x + y * y); + + default: + return Math + .pow(Math.pow(x, (double) metric) + + Math.pow(y, (double) metric), + 1.0 / (double) metric); + } + } + + /** + * returns signed distance of point from infinite line represented by + * pt_1...pt_2. The returned distance is positive if this point lies on the + * right-hand side of the line, negative otherwise. If the two input points + * are equal, the (positive) distance of this point to p_1 is returned. + */ + double offset(/* const */Point2D pt1, /* const */Point2D pt2) { + double newDistance = distance(pt1, pt2); + Point2D p = construct(x, y); + if (newDistance == 0.0) + return distance(p, pt1); + + // get vectors relative to pt_1 + Point2D p2 = new Point2D(); + p2.setCoords(pt2); + p2.sub(pt1); + p.sub(pt1); + + double cross = p.crossProduct(p2); + return cross / newDistance; + } + + /** + * Calculates the orientation of the triangle formed by p->q->r. Returns 1 + * for counter-clockwise, -1 for clockwise, and 0 for collinear. May use + * high precision arithmetics for some special degenerate cases. + */ + public static int orientationRobust(Point2D p, Point2D q, Point2D r) { + ECoordinate det_ec = new ECoordinate(); + det_ec.set(q.x); + det_ec.sub(p.x); + + ECoordinate rp_y_ec = new ECoordinate(); + rp_y_ec.set(r.y); + rp_y_ec.sub(p.y); + + ECoordinate qp_y_ec = new ECoordinate(); + qp_y_ec.set(q.y); + qp_y_ec.sub(p.y); + + ECoordinate rp_x_ec = new ECoordinate(); + rp_x_ec.set(r.x); + rp_x_ec.sub(p.x); + + det_ec.mul(rp_y_ec); + qp_y_ec.mul(rp_x_ec); + det_ec.sub(qp_y_ec); + + if (!det_ec.isFuzzyZero()) { + double det_ec_value = det_ec.value(); + + if (det_ec_value < 0.0) + return -1; + + if (det_ec_value > 0.0) + return 1; + + return 0; + } + + // Need extended precision + + BigDecimal det_mp = new BigDecimal(q.x); + BigDecimal px_mp = new BigDecimal(p.x); + BigDecimal py_mp = new BigDecimal(p.y); + det_mp = det_mp.subtract(px_mp); + + BigDecimal rp_y_mp = new BigDecimal(r.y); + rp_y_mp = rp_y_mp.subtract(py_mp); + + BigDecimal qp_y_mp = new BigDecimal(q.y); + qp_y_mp = qp_y_mp.subtract(py_mp); + + BigDecimal rp_x_mp = new BigDecimal(r.x); + rp_x_mp = rp_x_mp.subtract(px_mp); + + det_mp = det_mp.multiply(rp_y_mp); + qp_y_mp = qp_y_mp.multiply(rp_x_mp); + det_mp = det_mp.subtract(qp_y_mp); + + return det_mp.signum(); + } + + private static int inCircleRobustMP_(Point2D p, Point2D q, Point2D r, Point2D s) { + BigDecimal sx_mp = new BigDecimal(s.x), sy_mp = new BigDecimal(s.y); + + BigDecimal psx_mp = new BigDecimal(p.x), psy_mp = new BigDecimal(p.y); + psx_mp = psx_mp.subtract(sx_mp); + psy_mp = psy_mp.subtract(sy_mp); + + BigDecimal qsx_mp = new BigDecimal(q.x), qsy_mp = new BigDecimal(q.y); + qsx_mp = qsx_mp.subtract(sx_mp); + qsy_mp = qsy_mp.subtract(sy_mp); + + BigDecimal rsx_mp = new BigDecimal(r.x), rsy_mp = new BigDecimal(r.y); + rsx_mp = rsx_mp.subtract(sx_mp); + rsy_mp = rsy_mp.subtract(sy_mp); + + BigDecimal pq_det_mp = psx_mp.multiply(qsy_mp).subtract(psy_mp.multiply(qsx_mp)); + BigDecimal qr_det_mp = qsx_mp.multiply(rsy_mp).subtract(qsy_mp.multiply(rsx_mp)); + BigDecimal pr_det_mp = psx_mp.multiply(rsy_mp).subtract(psy_mp.multiply(rsx_mp)); + + BigDecimal p_parab_mp = psx_mp.multiply(psx_mp).add(psy_mp.multiply(psy_mp)); + BigDecimal q_parab_mp = qsx_mp.multiply(qsx_mp).add(qsy_mp.multiply(qsy_mp)); + BigDecimal r_parab_mp = rsx_mp.multiply(rsx_mp).add(rsy_mp.multiply(rsy_mp)); + + BigDecimal det_mp = (p_parab_mp.multiply(qr_det_mp).subtract(q_parab_mp.multiply(pr_det_mp))) + .add(r_parab_mp.multiply(pq_det_mp)); + + return det_mp.signum(); + } + + /** + * Calculates if the point s is inside of the circumcircle inscribed by the clockwise oriented triangle p-q-r. + * Returns 1 for outside, -1 for inside, and 0 for cocircular. + * Note that the convention used here differs from what is commonly found in literature, which can define the relation + * in terms of a counter-clockwise oriented circle, and this flips the sign (think of the signed volume of the tetrahedron). + * May use high precision arithmetics for some special cases. + */ + static int inCircleRobust(Point2D p, Point2D q, Point2D r, Point2D s) { + ECoordinate psx_ec = new ECoordinate(), psy_ec = new ECoordinate(); + psx_ec.set(p.x); + psx_ec.sub(s.x); + psy_ec.set(p.y); + psy_ec.sub(s.y); + + ECoordinate qsx_ec = new ECoordinate(), qsy_ec = new ECoordinate(); + qsx_ec.set(q.x); + qsx_ec.sub(s.x); + qsy_ec.set(q.y); + qsy_ec.sub(s.y); + + ECoordinate rsx_ec = new ECoordinate(), rsy_ec = new ECoordinate(); + rsx_ec.set(r.x); + rsx_ec.sub(s.x); + rsy_ec.set(r.y); + rsy_ec.sub(s.y); + + ECoordinate psx_ec_qsy_ec = new ECoordinate(); + psx_ec_qsy_ec.set(psx_ec); + psx_ec_qsy_ec.mul(qsy_ec); + ECoordinate psy_ec_qsx_ec = new ECoordinate(); + psy_ec_qsx_ec.set(psy_ec); + psy_ec_qsx_ec.mul(qsx_ec); + ECoordinate qsx_ec_rsy_ec = new ECoordinate(); + qsx_ec_rsy_ec.set(qsx_ec); + qsx_ec_rsy_ec.mul(rsy_ec); + ECoordinate qsy_ec_rsx_ec = new ECoordinate(); + qsy_ec_rsx_ec.set(qsy_ec); + qsy_ec_rsx_ec.mul(rsx_ec); + ECoordinate psx_ec_rsy_ec = new ECoordinate(); + psx_ec_rsy_ec.set(psx_ec); + psx_ec_rsy_ec.mul(rsy_ec); + ECoordinate psy_ec_rsx_ec = new ECoordinate(); + psy_ec_rsx_ec.set(psy_ec); + psy_ec_rsx_ec.mul(rsx_ec); + + ECoordinate pq_det_ec = new ECoordinate(); + pq_det_ec.set(psx_ec_qsy_ec); + pq_det_ec.sub(psy_ec_qsx_ec); + ECoordinate qr_det_ec = new ECoordinate(); + qr_det_ec.set(qsx_ec_rsy_ec); + qr_det_ec.sub(qsy_ec_rsx_ec); + ECoordinate pr_det_ec = new ECoordinate(); + pr_det_ec.set(psx_ec_rsy_ec); + pr_det_ec.sub(psy_ec_rsx_ec); + + ECoordinate psx_ec_psx_ec = new ECoordinate(); + psx_ec_psx_ec.set(psx_ec); + psx_ec_psx_ec.mul(psx_ec); + ECoordinate psy_ec_psy_ec = new ECoordinate(); + psy_ec_psy_ec.set(psy_ec); + psy_ec_psy_ec.mul(psy_ec); + ECoordinate qsx_ec_qsx_ec = new ECoordinate(); + qsx_ec_qsx_ec.set(qsx_ec); + qsx_ec_qsx_ec.mul(qsx_ec); + ECoordinate qsy_ec_qsy_ec = new ECoordinate(); + qsy_ec_qsy_ec.set(qsy_ec); + qsy_ec_qsy_ec.mul(qsy_ec); + ECoordinate rsx_ec_rsx_ec = new ECoordinate(); + rsx_ec_rsx_ec.set(rsx_ec); + rsx_ec_rsx_ec.mul(rsx_ec); + ECoordinate rsy_ec_rsy_ec = new ECoordinate(); + rsy_ec_rsy_ec.set(rsy_ec); + rsy_ec_rsy_ec.mul(rsy_ec); + + ECoordinate p_parab_ec = new ECoordinate(); + p_parab_ec.set(psx_ec_psx_ec); + p_parab_ec.add(psy_ec_psy_ec); + ECoordinate q_parab_ec = new ECoordinate(); + q_parab_ec.set(qsx_ec_qsx_ec); + q_parab_ec.add(qsy_ec_qsy_ec); + ECoordinate r_parab_ec = new ECoordinate(); + r_parab_ec.set(rsx_ec_rsx_ec); + r_parab_ec.add(rsy_ec_rsy_ec); + + p_parab_ec.mul(qr_det_ec); + q_parab_ec.mul(pr_det_ec); + r_parab_ec.mul(pq_det_ec); + + ECoordinate det_ec = new ECoordinate(); + det_ec.set(p_parab_ec); + det_ec.sub(q_parab_ec); + det_ec.add(r_parab_ec); + + if (!det_ec.isFuzzyZero()) { + double det_ec_value = det_ec.value(); + + if (det_ec_value < 0.0) + return -1; + + if (det_ec_value > 0.0) + return 1; + + return 0; + } + + return inCircleRobustMP_(p, q, r, s); + } + + private static Point2D calculateCenterFromThreePointsHelperMP_(Point2D from, Point2D mid_point, Point2D to) { + assert (!mid_point.isEqual(to) && !mid_point.isEqual(from) && !from.isEqual(to)); + BigDecimal mx = new BigDecimal(mid_point.x); + mx = mx.subtract(new BigDecimal(from.x)); + BigDecimal my = new BigDecimal(mid_point.y); + my = my.subtract(new BigDecimal(from.y)); + BigDecimal tx = new BigDecimal(to.x); + tx = tx.subtract(new BigDecimal(from.x)); + BigDecimal ty = new BigDecimal(to.y); + ty = ty.subtract(new BigDecimal(from.y)); + + BigDecimal d = mx.multiply(ty); + BigDecimal tmp = my.multiply(tx); + d = d.subtract(tmp); + + if (d.signum() == 0) { + return Point2D.construct(NumberUtils.NaN(), NumberUtils.NaN()); + } + + d = d.multiply(new BigDecimal(2.0)); + + BigDecimal mx2 = mx.multiply(mx); + BigDecimal my2 = my.multiply(my); + BigDecimal m_norm2 = mx2.add(my2); + BigDecimal tx2 = tx.multiply(tx); + BigDecimal ty2 = ty.multiply(ty); + BigDecimal t_norm2 = tx2.add(ty2); + + BigDecimal xo = my.multiply(t_norm2); + tmp = ty.multiply(m_norm2); + xo = xo.subtract(tmp); + xo = xo.divide(d, BigDecimal.ROUND_HALF_EVEN); + + BigDecimal yo = mx.multiply(t_norm2); + tmp = tx.multiply(m_norm2); + yo = yo.subtract(tmp); + yo = yo.divide(d, BigDecimal.ROUND_HALF_EVEN); + + Point2D center = Point2D.construct(from.x - xo.doubleValue(), from.y + yo.doubleValue()); + return center; + } + + private static Point2D calculateCenterFromThreePointsHelper_(Point2D from, Point2D mid_point, Point2D to) { + assert (!mid_point.isEqual(to) && !mid_point.isEqual(from) && !from.isEqual(to)); + ECoordinate mx = new ECoordinate(mid_point.x); + mx.sub(from.x); + ECoordinate my = new ECoordinate(mid_point.y); + my.sub(from.y); + ECoordinate tx = new ECoordinate(to.x); + tx.sub(from.x); + ECoordinate ty = new ECoordinate(to.y); + ty.sub(from.y); + + ECoordinate d = new ECoordinate(mx); + d.mul(ty); + ECoordinate tmp = new ECoordinate(my); + tmp.mul(tx); + d.sub(tmp); + + if (d.value() == 0.0) { + return Point2D.construct(NumberUtils.NaN(), NumberUtils.NaN()); + } + + d.mul(2.0); + + ECoordinate mx2 = new ECoordinate(mx); + mx2.mul(mx); + ECoordinate my2 = new ECoordinate(my); + my2.mul(my); + ECoordinate m_norm2 = new ECoordinate(mx2); + m_norm2.add(my2); + ECoordinate tx2 = new ECoordinate(tx); + tx2.mul(tx); + ECoordinate ty2 = new ECoordinate(ty); + ty2.mul(ty); + ECoordinate t_norm2 = new ECoordinate(tx2); + t_norm2.add(ty2); + + ECoordinate xo = new ECoordinate(my); + xo.mul(t_norm2); + tmp = new ECoordinate(ty); + tmp.mul(m_norm2); + xo.sub(tmp); + xo.div(d); + + ECoordinate yo = new ECoordinate(mx); + yo.mul(t_norm2); + tmp = new ECoordinate(tx); + tmp.mul(m_norm2); + yo.sub(tmp); + yo.div(d); + + Point2D center = Point2D.construct(from.x - xo.value(), from.y + yo.value()); + double r1 = Point2D.construct(from.x - center.x, from.y - center.y).length(); + double r2 = Point2D.construct(mid_point.x - center.x, mid_point.y - center.y).length(); + double r3 = Point2D.construct(to.x - center.x, to.y - center.y).length(); + double base = r1 + Math.abs(from.x) + Math.abs(mid_point.x) + Math.abs(to.x) + Math.abs(from.y) + + Math.abs(mid_point.y) + Math.abs(to.y); + + double tol = 1e-15; + if ((Math.abs(r1 - r2) <= base * tol && Math.abs(r1 - r3) <= base * tol)) + return center;//returns center value for MP_value type or when calculated radius value for from - center, mid - center, and to - center are very close. + + return Point2D.construct(NumberUtils.NaN(), NumberUtils.NaN()); + } + + /** + * Calculate the center of a circle, whose perimeter contains the points from, mid_point and to + * + * @param from + * @param mid_point + * @param to + * @return + */ + static Point2D calculateCircleCenterFromThreePoints(Point2D from, Point2D mid_point, Point2D to) { + if (from.isEqual(to) || from.isEqual(mid_point) || to.isEqual(mid_point)) { + return new Point2D(NumberUtils.NaN(), NumberUtils.NaN()); + } + + Point2D pt = calculateCenterFromThreePointsHelper_(from, mid_point, to); //use error tracking calculations + if (pt.isNaN()) + return calculateCenterFromThreePointsHelperMP_(from, mid_point, to); //use precise calculations + else { + return pt; + } + } + + @Override + public int hashCode() { + return NumberUtils.hash(NumberUtils.hash(x), y); + } + + public double calculateTriangleArea2D(Point2D pt1, Point2D pt2) { + double a = Point2D.distance(pt1, pt2); + double b = Point2D.distance(pt1, this); + double c = Point2D.distance(pt2, this); + double temp; + + + if (a < b) { + // set a >= b + temp = b; + b = a; + a = temp; + } + + if (c > a) { + // set a >= c + temp = c; + c = a; + a = temp; + } + + if (c > b) { + temp = c; + c = b; + b = temp; + } + + // First sort a, b, c so that a ≥ b ≥ c ; this can be done at the cost of at most three comparisons. + // If c-(a-b) < 0 then the data are not side-lengths of a real triangle + if (c - (a - b) < 0) + return 0.0; + + //double result = 0.5 * Math.abs((x - pt2.x)*(pt1.y - y) - (x - pt1.x)*(pt2.y - y)); + double result = Math.sqrt((a + (b + c)) * (c - (a - b)) * (c + (a - b)) * (a + (b - c))) / 4.0; + return result; + + } + + double getAxis(int ordinate) { + assert (ordinate == 0 || ordinate == 1); + return (ordinate == 0 ? x : y); + } } diff --git a/src/main/java/com/esri/core/geometry/Point3D.java b/src/main/java/com/esri/core/geometry/Point3D.java index 123a51ac..b76a5b5a 100644 --- a/src/main/java/com/esri/core/geometry/Point3D.java +++ b/src/main/java/com/esri/core/geometry/Point3D.java @@ -31,102 +31,102 @@ * Basic 3D point class. */ public final class Point3D implements Serializable { - private static final long serialVersionUID = 1L; - - public double x; - public double y; - public double z; - - public Point3D() { - } - - public Point3D(Point3D other) { - setCoords(other); - } - - public Point3D(double x, double y, double z) { - setCoords(x, y, z); - } - - public static Point3D construct(double x, double y, double z) { - Point3D pt = new Point3D(); - pt.setCoords(x, y, z); - return pt; - } - - public void setCoords(double x, double y, double z) { - this.x = x; - this.y = y; - this.z = z; - } - - public void setCoords(Point3D other) { - setCoords(other.x, other.y, other.z); - } - - public void setZero() { - x = 0.0; - y = 0.0; - z = 0.0; - } - - public void normalize() { - double len = length(); - if (len == 0) { - x = 1.0; - y = 0.0; - z = 0.0; - } else { - x /= len; - y /= len; - z /= len; - } - } - - public double dotProduct(Point3D other) { - return x * other.x + y * other.y + z * other.z; - } - - public double sqrLength() { - return x * x + y * y + z * z; - } - - public double length() { - return Math.sqrt(x * x + y * y + z * z); - } - - public void sub(Point3D other) { - x -= other.x; - y -= other.y; - z -= other.z; - } - - public void sub(Point3D p1, Point3D p2) { - x = p1.x - p2.x; - y = p1.y - p2.y; - z = p1.z - p2.z; - } - - public void scale(double f, Point3D other) { - x = f * other.x; - y = f * other.y; - z = f * other.z; - } - - public void mul(double factor) { - x *= factor; - y *= factor; - z *= factor; - } - - void _setNan() { - x = NumberUtils.NaN(); - y = NumberUtils.NaN(); - z = NumberUtils.NaN(); - } - - boolean _isNan() { - return NumberUtils.isNaN(x) || NumberUtils.isNaN(y) || NumberUtils.isNaN(z); - } + private static final long serialVersionUID = 1L; + + public double x; + public double y; + public double z; + + public Point3D() { + } + + public Point3D(Point3D other) { + setCoords(other); + } + + public Point3D(double x, double y, double z) { + setCoords(x, y, z); + } + + public static Point3D construct(double x, double y, double z) { + Point3D pt = new Point3D(); + pt.setCoords(x, y, z); + return pt; + } + + public void setCoords(double x, double y, double z) { + this.x = x; + this.y = y; + this.z = z; + } + + public void setCoords(Point3D other) { + setCoords(other.x, other.y, other.z); + } + + public void setZero() { + x = 0.0; + y = 0.0; + z = 0.0; + } + + public void normalize() { + double len = length(); + if (len == 0) { + x = 1.0; + y = 0.0; + z = 0.0; + } else { + x /= len; + y /= len; + z /= len; + } + } + + public double dotProduct(Point3D other) { + return x * other.x + y * other.y + z * other.z; + } + + public double sqrLength() { + return x * x + y * y + z * z; + } + + public double length() { + return Math.sqrt(x * x + y * y + z * z); + } + + public void sub(Point3D other) { + x -= other.x; + y -= other.y; + z -= other.z; + } + + public void sub(Point3D p1, Point3D p2) { + x = p1.x - p2.x; + y = p1.y - p2.y; + z = p1.z - p2.z; + } + + public void scale(double f, Point3D other) { + x = f * other.x; + y = f * other.y; + z = f * other.z; + } + + public void mul(double factor) { + x *= factor; + y *= factor; + z *= factor; + } + + void _setNan() { + x = NumberUtils.NaN(); + y = NumberUtils.NaN(); + z = NumberUtils.NaN(); + } + + boolean _isNan() { + return NumberUtils.isNaN(x) || NumberUtils.isNaN(y) || NumberUtils.isNaN(z); + } } diff --git a/src/main/java/com/esri/core/geometry/PointInPolygonHelper.java b/src/main/java/com/esri/core/geometry/PointInPolygonHelper.java index 0f3dd9da..863ba992 100644 --- a/src/main/java/com/esri/core/geometry/PointInPolygonHelper.java +++ b/src/main/java/com/esri/core/geometry/PointInPolygonHelper.java @@ -26,410 +26,410 @@ final class PointInPolygonHelper { - private Point2D m_inputPoint; - private int m_windnum; - private SegmentBuffer[] m_monotoneParts = null; - private double[] m_xOrds = null; - private double m_tolerance; - private double m_toleranceSqr; - private double m_miny; - private double m_maxy; - private boolean m_bAlternate; - private boolean m_bTestBorder; - private boolean m_bBreak; - private boolean m_bPointInAnyOuterRingTest; - - private int result() { - return m_windnum != 0 ? 1 : 0; - } - - private boolean _testBorder(Segment seg) { - double t = seg.getClosestCoordinate(m_inputPoint, false); - Point2D pt = seg.getCoord2D(t); - if (Point2D.sqrDistance(pt, m_inputPoint) <= m_toleranceSqr) { - return true; - } - return false; - } - - private void doOne(Segment seg) { - if (!m_bTestBorder) { - // test if point is on the boundary - if (m_bAlternate && m_inputPoint.isEqual(seg.getStartXY()) - || m_inputPoint.isEqual(seg.getEndXY())) {// test if the - // point - // coincides - // with a vertex - m_bBreak = true; - return; - } - } - - if (seg.getStartY() == m_inputPoint.y - && seg.getStartY() == seg.getEndY()) {// skip horizontal - // segments. test if the - // point lies on a - // horizontal segment - if (m_bAlternate && !m_bTestBorder) { - double minx = Math.min(seg.getStartX(), seg.getEndX()); - double maxx = Math.max(seg.getStartX(), seg.getEndX()); - if (m_inputPoint.x > minx && m_inputPoint.x < maxx) - m_bBreak = true; - } - - return;// skip horizontal segments - } - - boolean bToTheRight = false; - double maxx = Math.max(seg.getStartX(), seg.getEndX()); - if (m_inputPoint.x > maxx) { - bToTheRight = true; - } else { - if (m_inputPoint.x >= Math.min(seg.getStartX(), seg.getEndX())) { - int n = seg.intersectionWithAxis2D(true, m_inputPoint.y, - m_xOrds, null); - bToTheRight = n > 0 && m_xOrds[0] <= m_inputPoint.x; - } - } - - if (bToTheRight) { - // to prevent double counting, when the ray crosses a vertex, count - // only the segments that are below the ray. - if (m_inputPoint.y == seg.getStartXY().y) { - if (m_inputPoint.y < seg.getEndXY().y) - return; - } else if (m_inputPoint.y == seg.getEndXY().y) { - if (m_inputPoint.y < seg.getStartXY().y) - return; - } - - if (m_bAlternate) - m_windnum ^= 1; - else - m_windnum += (seg.getStartXY().y > seg.getEndXY().y) ? 1 : -1; - } - } - - public PointInPolygonHelper(boolean bFillRule_Alternate, - Point2D inputPoint, double tolerance) { - // //_ASSERT(tolerance >= 0); - m_inputPoint = inputPoint; - m_miny = inputPoint.y - tolerance; - m_maxy = inputPoint.y + tolerance; - m_windnum = 0; - m_bAlternate = bFillRule_Alternate; - m_tolerance = tolerance; - m_toleranceSqr = tolerance * tolerance; - m_bTestBorder = tolerance != 0;// - m_bBreak = false; - } - - private boolean processSegment(Segment segment) { - Envelope1D yrange = segment.queryInterval( - (int) VertexDescription.Semantics.POSITION, 1); - if (yrange.vmin > m_maxy || yrange.vmax < m_miny) { - return false; - } - - if (m_bTestBorder && _testBorder(segment)) - return true; - - if (yrange.vmin > m_inputPoint.y || yrange.vmax < m_inputPoint.y) { - return false; - } - - if (m_monotoneParts == null) - m_monotoneParts = new SegmentBuffer[5]; - if (m_xOrds == null) - m_xOrds = new double[3]; - - int nparts = segment.getYMonotonicParts(m_monotoneParts); - if (nparts > 0) {// the segment is a curve and has been broken in - // ymonotone parts - for (int i = 0; i < nparts; i++) { - Segment part = m_monotoneParts[i].get(); - doOne(part); - if (m_bBreak) - return true; - } - } else {// the segment is a line or it is y monotone curve - doOne(segment); - if (m_bBreak) - return true; - } - - return false; - } - - private static int _isPointInPolygonInternal(Polygon inputPolygon, - Point2D inputPoint, double tolerance) { - - boolean bAltenate = inputPolygon.getFillRule() == Polygon.FillRule.enumFillRuleOddEven; - PointInPolygonHelper helper = new PointInPolygonHelper(bAltenate, - inputPoint, tolerance); - MultiPathImpl mpImpl = (MultiPathImpl) inputPolygon._getImpl(); - SegmentIteratorImpl iter = mpImpl.querySegmentIterator(); - while (iter.nextPath()) { - while (iter.hasNextSegment()) { - Segment segment = iter.nextSegment(); - if (helper.processSegment(segment)) - return -1; // point on boundary - } - } - - return helper.result(); - } - - private static int _isPointInPolygonInternalWithQuadTree( - Polygon inputPolygon, QuadTreeImpl quadTree, Point2D inputPoint, - double tolerance) { - Envelope2D envPoly = new Envelope2D(); - inputPolygon.queryLooseEnvelope(envPoly); - envPoly.inflate(tolerance, tolerance); - - boolean bAltenate = inputPolygon.getFillRule() == Polygon.FillRule.enumFillRuleOddEven; - PointInPolygonHelper helper = new PointInPolygonHelper(bAltenate, - inputPoint, tolerance); - - MultiPathImpl mpImpl = (MultiPathImpl) inputPolygon._getImpl(); - SegmentIteratorImpl iter = mpImpl.querySegmentIterator(); - Envelope2D queryEnv = new Envelope2D(); - queryEnv.setCoords(envPoly); - queryEnv.xmax = inputPoint.x + tolerance;// no need to query segments to - // the right of the point. - // Only segments to the left - // matter. - queryEnv.ymin = inputPoint.y - tolerance; - queryEnv.ymax = inputPoint.y + tolerance; - QuadTreeImpl.QuadTreeIteratorImpl qiter = quadTree.getIterator( - queryEnv, tolerance); - for (int qhandle = qiter.next(); qhandle != -1; qhandle = qiter.next()) { - iter.resetToVertex(quadTree.getElement(qhandle)); - if (iter.hasNextSegment()) { - Segment segment = iter.nextSegment(); - if (helper.processSegment(segment)) - return -1; // point on boundary - } - } - - return helper.result(); - } - - public static int isPointInPolygon(Polygon inputPolygon, - Point2D inputPoint, double tolerance) { - if (inputPolygon.isEmpty()) - return 0; - - Envelope2D env = new Envelope2D(); - inputPolygon.queryLooseEnvelope(env); - env.inflate(tolerance, tolerance); - if (!env.contains(inputPoint)) - return 0; - - MultiPathImpl mpImpl = (MultiPathImpl) inputPolygon._getImpl(); - GeometryAccelerators accel = mpImpl._getAccelerators(); - if (accel != null) { - // geometry has spatial indices built. Try using them. - RasterizedGeometry2D rgeom = accel.getRasterizedGeometry(); - if (rgeom != null) { - RasterizedGeometry2D.HitType hit = rgeom.queryPointInGeometry( - inputPoint.x, inputPoint.y); - if (hit == RasterizedGeometry2D.HitType.Inside) - return 1; - else if (hit == RasterizedGeometry2D.HitType.Outside) - return 0; - } - - QuadTreeImpl qtree = accel.getQuadTree(); - if (qtree != null) { - return _isPointInPolygonInternalWithQuadTree(inputPolygon, - qtree, inputPoint, tolerance); - } - } - - return _isPointInPolygonInternal(inputPolygon, inputPoint, tolerance); - } - - static int isPointInPolygon(Polygon inputPolygon, double inputPointXVal, - double inputPointYVal, double tolerance) { - if (inputPolygon.isEmpty()) - return 0; - - Envelope2D env = new Envelope2D(); - inputPolygon.queryLooseEnvelope(env); - env.inflate(tolerance, tolerance); - if (!env.contains(inputPointXVal, inputPointYVal)) - return 0; - - MultiPathImpl mpImpl = (MultiPathImpl) inputPolygon._getImpl(); - GeometryAccelerators accel = mpImpl._getAccelerators(); - if (accel != null) { - RasterizedGeometry2D rgeom = accel.getRasterizedGeometry(); - if (rgeom != null) { - RasterizedGeometry2D.HitType hit = rgeom.queryPointInGeometry( - inputPointXVal, inputPointYVal); - if (hit == RasterizedGeometry2D.HitType.Inside) - return 1; - else if (hit == RasterizedGeometry2D.HitType.Outside) - return 0; - } - } - - return _isPointInPolygonInternal(inputPolygon, new Point2D( - inputPointXVal, inputPointYVal), tolerance); - } - - public static int isPointInRing(MultiPathImpl inputPolygonImpl, int iRing, - Point2D inputPoint, double tolerance, QuadTree quadTree) { - Envelope2D env = new Envelope2D(); - inputPolygonImpl.queryLooseEnvelope2D(env); - env.inflate(tolerance, tolerance); - if (!env.contains(inputPoint)) - return 0; - - boolean bAltenate = true; - PointInPolygonHelper helper = new PointInPolygonHelper(bAltenate, - inputPoint, tolerance); - - if (quadTree != null) { - Envelope2D queryEnv = new Envelope2D(); - queryEnv.setCoords(env); - queryEnv.xmax = inputPoint.x + tolerance;// no need to query - // segments to - // the right of the - // point. - // Only segments to the - // left - // matter. - queryEnv.ymin = inputPoint.y - tolerance; - queryEnv.ymax = inputPoint.y + tolerance; - SegmentIteratorImpl iter = inputPolygonImpl.querySegmentIterator(); - QuadTree.QuadTreeIterator qiter = quadTree.getIterator(queryEnv, - tolerance); - - for (int qhandle = qiter.next(); qhandle != -1; qhandle = qiter - .next()) { - iter.resetToVertex(quadTree.getElement(qhandle), iRing); - if (iter.hasNextSegment()) { - if (iter.getPathIndex() != iRing) - continue; - - Segment segment = iter.nextSegment(); - if (helper.processSegment(segment)) - return -1; // point on boundary - } - } - - return helper.result(); - } else { - SegmentIteratorImpl iter = inputPolygonImpl.querySegmentIterator(); - iter.resetToPath(iRing); - - if (iter.nextPath()) { - while (iter.hasNextSegment()) { - Segment segment = iter.nextSegment(); - if (helper.processSegment(segment)) - return -1; // point on boundary - } - } - - return helper.result(); - } - } - - public static int isPointInPolygon(Polygon inputPolygon, Point inputPoint, - double tolerance) { - if (inputPoint.isEmpty()) - return 0; - - return isPointInPolygon(inputPolygon, inputPoint.getXY(), tolerance); - } - - public static int isPointInAnyOuterRing(Polygon inputPolygon, - Point2D inputPoint, double tolerance) { - Envelope2D env = new Envelope2D(); - inputPolygon.queryLooseEnvelope(env); - env.inflate(tolerance, tolerance); - if (!env.contains(inputPoint)) - return 0; - - // Note: - // Wolfgang had noted that this could be optimized if the exterior rings - // have positive area: - // Only test the positive rings and bail out immediately when in a - // positive ring. - // The worst case complexity is still O(n), but on average for polygons - // with holes, that would be faster. - // However, that method would not work if polygon is reversed, while the - // one here works fine same as PointInPolygon. - - boolean bAltenate = false;// use winding in this test - PointInPolygonHelper helper = new PointInPolygonHelper(bAltenate, - inputPoint, tolerance); - MultiPathImpl mpImpl = (MultiPathImpl) inputPolygon._getImpl(); - SegmentIteratorImpl iter = mpImpl.querySegmentIterator(); - while (iter.nextPath()) { - double ringArea = mpImpl.calculateRingArea2D(iter.getPathIndex()); - boolean bIsHole = ringArea < 0; - if (!bIsHole) { - helper.m_windnum = 0; - while (iter.hasNextSegment()) { - Segment segment = iter.nextSegment(); - if (helper.processSegment(segment)) - return -1; // point on boundary - } - - if (helper.m_windnum != 0) - return 1; - } - } - - return helper.result(); - } - - // Tests if Ring1 is inside Ring2. - // We assume here that the Polygon is Weak Simple. That is if one point of - // Ring1 is found to be inside of Ring2, then - // we assume that all of Ring1 is inside Ring2. - static boolean _isRingInRing2D(MultiPath polygon, int iRing1, int iRing2, - double tolerance, QuadTree quadTree) { - MultiPathImpl polygonImpl = (MultiPathImpl) polygon._getImpl(); - SegmentIteratorImpl segIter = polygonImpl.querySegmentIterator(); - segIter.resetToPath(iRing1); - if (!segIter.nextPath() || !segIter.hasNextSegment()) - throw new GeometryException("corrupted geometry"); - - int res = 2; - - while (res == 2 && segIter.hasNextSegment()) { - Segment segment = segIter.nextSegment(); - Point2D point = segment.getCoord2D(0.5); - res = PointInPolygonHelper.isPointInRing(polygonImpl, iRing2, - point, tolerance, quadTree); - } - - if (res == 2) - throw GeometryException.GeometryInternalError(); - if (res == 1) - return true; - - return false; - } - - static boolean quadTreeWillHelp(Polygon polygon, int c_queries) { - int n = polygon.getPointCount(); - - if (n < 16) - return false; - - double c_build_quad_tree = 2.0; // what's a good constant? - double c_query_quad_tree = 1.0; // what's a good constant? - double c_point_in_polygon_brute_force = 1.0; // what's a good constant? - - double c_quad_tree = c_build_quad_tree * n + c_query_quad_tree * (Math.log((double) n) / Math.log(2.0)) * c_queries; - double c_brute_force = c_point_in_polygon_brute_force * n * c_queries; - - return c_quad_tree < c_brute_force; - } + private Point2D m_inputPoint; + private int m_windnum; + private SegmentBuffer[] m_monotoneParts = null; + private double[] m_xOrds = null; + private double m_tolerance; + private double m_toleranceSqr; + private double m_miny; + private double m_maxy; + private boolean m_bAlternate; + private boolean m_bTestBorder; + private boolean m_bBreak; + private boolean m_bPointInAnyOuterRingTest; + + private int result() { + return m_windnum != 0 ? 1 : 0; + } + + private boolean _testBorder(Segment seg) { + double t = seg.getClosestCoordinate(m_inputPoint, false); + Point2D pt = seg.getCoord2D(t); + if (Point2D.sqrDistance(pt, m_inputPoint) <= m_toleranceSqr) { + return true; + } + return false; + } + + private void doOne(Segment seg) { + if (!m_bTestBorder) { + // test if point is on the boundary + if (m_bAlternate && m_inputPoint.isEqual(seg.getStartXY()) + || m_inputPoint.isEqual(seg.getEndXY())) {// test if the + // point + // coincides + // with a vertex + m_bBreak = true; + return; + } + } + + if (seg.getStartY() == m_inputPoint.y + && seg.getStartY() == seg.getEndY()) {// skip horizontal + // segments. test if the + // point lies on a + // horizontal segment + if (m_bAlternate && !m_bTestBorder) { + double minx = Math.min(seg.getStartX(), seg.getEndX()); + double maxx = Math.max(seg.getStartX(), seg.getEndX()); + if (m_inputPoint.x > minx && m_inputPoint.x < maxx) + m_bBreak = true; + } + + return;// skip horizontal segments + } + + boolean bToTheRight = false; + double maxx = Math.max(seg.getStartX(), seg.getEndX()); + if (m_inputPoint.x > maxx) { + bToTheRight = true; + } else { + if (m_inputPoint.x >= Math.min(seg.getStartX(), seg.getEndX())) { + int n = seg.intersectionWithAxis2D(true, m_inputPoint.y, + m_xOrds, null); + bToTheRight = n > 0 && m_xOrds[0] <= m_inputPoint.x; + } + } + + if (bToTheRight) { + // to prevent double counting, when the ray crosses a vertex, count + // only the segments that are below the ray. + if (m_inputPoint.y == seg.getStartXY().y) { + if (m_inputPoint.y < seg.getEndXY().y) + return; + } else if (m_inputPoint.y == seg.getEndXY().y) { + if (m_inputPoint.y < seg.getStartXY().y) + return; + } + + if (m_bAlternate) + m_windnum ^= 1; + else + m_windnum += (seg.getStartXY().y > seg.getEndXY().y) ? 1 : -1; + } + } + + public PointInPolygonHelper(boolean bFillRule_Alternate, + Point2D inputPoint, double tolerance) { + // //_ASSERT(tolerance >= 0); + m_inputPoint = inputPoint; + m_miny = inputPoint.y - tolerance; + m_maxy = inputPoint.y + tolerance; + m_windnum = 0; + m_bAlternate = bFillRule_Alternate; + m_tolerance = tolerance; + m_toleranceSqr = tolerance * tolerance; + m_bTestBorder = tolerance != 0;// + m_bBreak = false; + } + + private boolean processSegment(Segment segment) { + Envelope1D yrange = segment.queryInterval( + (int) VertexDescription.Semantics.POSITION, 1); + if (yrange.vmin > m_maxy || yrange.vmax < m_miny) { + return false; + } + + if (m_bTestBorder && _testBorder(segment)) + return true; + + if (yrange.vmin > m_inputPoint.y || yrange.vmax < m_inputPoint.y) { + return false; + } + + if (m_monotoneParts == null) + m_monotoneParts = new SegmentBuffer[5]; + if (m_xOrds == null) + m_xOrds = new double[3]; + + int nparts = segment.getYMonotonicParts(m_monotoneParts); + if (nparts > 0) {// the segment is a curve and has been broken in + // ymonotone parts + for (int i = 0; i < nparts; i++) { + Segment part = m_monotoneParts[i].get(); + doOne(part); + if (m_bBreak) + return true; + } + } else {// the segment is a line or it is y monotone curve + doOne(segment); + if (m_bBreak) + return true; + } + + return false; + } + + private static int _isPointInPolygonInternal(Polygon inputPolygon, + Point2D inputPoint, double tolerance) { + + boolean bAltenate = inputPolygon.getFillRule() == Polygon.FillRule.enumFillRuleOddEven; + PointInPolygonHelper helper = new PointInPolygonHelper(bAltenate, + inputPoint, tolerance); + MultiPathImpl mpImpl = (MultiPathImpl) inputPolygon._getImpl(); + SegmentIteratorImpl iter = mpImpl.querySegmentIterator(); + while (iter.nextPath()) { + while (iter.hasNextSegment()) { + Segment segment = iter.nextSegment(); + if (helper.processSegment(segment)) + return -1; // point on boundary + } + } + + return helper.result(); + } + + private static int _isPointInPolygonInternalWithQuadTree( + Polygon inputPolygon, QuadTreeImpl quadTree, Point2D inputPoint, + double tolerance) { + Envelope2D envPoly = new Envelope2D(); + inputPolygon.queryLooseEnvelope(envPoly); + envPoly.inflate(tolerance, tolerance); + + boolean bAltenate = inputPolygon.getFillRule() == Polygon.FillRule.enumFillRuleOddEven; + PointInPolygonHelper helper = new PointInPolygonHelper(bAltenate, + inputPoint, tolerance); + + MultiPathImpl mpImpl = (MultiPathImpl) inputPolygon._getImpl(); + SegmentIteratorImpl iter = mpImpl.querySegmentIterator(); + Envelope2D queryEnv = new Envelope2D(); + queryEnv.setCoords(envPoly); + queryEnv.xmax = inputPoint.x + tolerance;// no need to query segments to + // the right of the point. + // Only segments to the left + // matter. + queryEnv.ymin = inputPoint.y - tolerance; + queryEnv.ymax = inputPoint.y + tolerance; + QuadTreeImpl.QuadTreeIteratorImpl qiter = quadTree.getIterator( + queryEnv, tolerance); + for (int qhandle = qiter.next(); qhandle != -1; qhandle = qiter.next()) { + iter.resetToVertex(quadTree.getElement(qhandle)); + if (iter.hasNextSegment()) { + Segment segment = iter.nextSegment(); + if (helper.processSegment(segment)) + return -1; // point on boundary + } + } + + return helper.result(); + } + + public static int isPointInPolygon(Polygon inputPolygon, + Point2D inputPoint, double tolerance) { + if (inputPolygon.isEmpty()) + return 0; + + Envelope2D env = new Envelope2D(); + inputPolygon.queryLooseEnvelope(env); + env.inflate(tolerance, tolerance); + if (!env.contains(inputPoint)) + return 0; + + MultiPathImpl mpImpl = (MultiPathImpl) inputPolygon._getImpl(); + GeometryAccelerators accel = mpImpl._getAccelerators(); + if (accel != null) { + // geometry has spatial indices built. Try using them. + RasterizedGeometry2D rgeom = accel.getRasterizedGeometry(); + if (rgeom != null) { + RasterizedGeometry2D.HitType hit = rgeom.queryPointInGeometry( + inputPoint.x, inputPoint.y); + if (hit == RasterizedGeometry2D.HitType.Inside) + return 1; + else if (hit == RasterizedGeometry2D.HitType.Outside) + return 0; + } + + QuadTreeImpl qtree = accel.getQuadTree(); + if (qtree != null) { + return _isPointInPolygonInternalWithQuadTree(inputPolygon, + qtree, inputPoint, tolerance); + } + } + + return _isPointInPolygonInternal(inputPolygon, inputPoint, tolerance); + } + + static int isPointInPolygon(Polygon inputPolygon, double inputPointXVal, + double inputPointYVal, double tolerance) { + if (inputPolygon.isEmpty()) + return 0; + + Envelope2D env = new Envelope2D(); + inputPolygon.queryLooseEnvelope(env); + env.inflate(tolerance, tolerance); + if (!env.contains(inputPointXVal, inputPointYVal)) + return 0; + + MultiPathImpl mpImpl = (MultiPathImpl) inputPolygon._getImpl(); + GeometryAccelerators accel = mpImpl._getAccelerators(); + if (accel != null) { + RasterizedGeometry2D rgeom = accel.getRasterizedGeometry(); + if (rgeom != null) { + RasterizedGeometry2D.HitType hit = rgeom.queryPointInGeometry( + inputPointXVal, inputPointYVal); + if (hit == RasterizedGeometry2D.HitType.Inside) + return 1; + else if (hit == RasterizedGeometry2D.HitType.Outside) + return 0; + } + } + + return _isPointInPolygonInternal(inputPolygon, new Point2D( + inputPointXVal, inputPointYVal), tolerance); + } + + public static int isPointInRing(MultiPathImpl inputPolygonImpl, int iRing, + Point2D inputPoint, double tolerance, QuadTree quadTree) { + Envelope2D env = new Envelope2D(); + inputPolygonImpl.queryLooseEnvelope2D(env); + env.inflate(tolerance, tolerance); + if (!env.contains(inputPoint)) + return 0; + + boolean bAltenate = true; + PointInPolygonHelper helper = new PointInPolygonHelper(bAltenate, + inputPoint, tolerance); + + if (quadTree != null) { + Envelope2D queryEnv = new Envelope2D(); + queryEnv.setCoords(env); + queryEnv.xmax = inputPoint.x + tolerance;// no need to query + // segments to + // the right of the + // point. + // Only segments to the + // left + // matter. + queryEnv.ymin = inputPoint.y - tolerance; + queryEnv.ymax = inputPoint.y + tolerance; + SegmentIteratorImpl iter = inputPolygonImpl.querySegmentIterator(); + QuadTree.QuadTreeIterator qiter = quadTree.getIterator(queryEnv, + tolerance); + + for (int qhandle = qiter.next(); qhandle != -1; qhandle = qiter + .next()) { + iter.resetToVertex(quadTree.getElement(qhandle), iRing); + if (iter.hasNextSegment()) { + if (iter.getPathIndex() != iRing) + continue; + + Segment segment = iter.nextSegment(); + if (helper.processSegment(segment)) + return -1; // point on boundary + } + } + + return helper.result(); + } else { + SegmentIteratorImpl iter = inputPolygonImpl.querySegmentIterator(); + iter.resetToPath(iRing); + + if (iter.nextPath()) { + while (iter.hasNextSegment()) { + Segment segment = iter.nextSegment(); + if (helper.processSegment(segment)) + return -1; // point on boundary + } + } + + return helper.result(); + } + } + + public static int isPointInPolygon(Polygon inputPolygon, Point inputPoint, + double tolerance) { + if (inputPoint.isEmpty()) + return 0; + + return isPointInPolygon(inputPolygon, inputPoint.getXY(), tolerance); + } + + public static int isPointInAnyOuterRing(Polygon inputPolygon, + Point2D inputPoint, double tolerance) { + Envelope2D env = new Envelope2D(); + inputPolygon.queryLooseEnvelope(env); + env.inflate(tolerance, tolerance); + if (!env.contains(inputPoint)) + return 0; + + // Note: + // Wolfgang had noted that this could be optimized if the exterior rings + // have positive area: + // Only test the positive rings and bail out immediately when in a + // positive ring. + // The worst case complexity is still O(n), but on average for polygons + // with holes, that would be faster. + // However, that method would not work if polygon is reversed, while the + // one here works fine same as PointInPolygon. + + boolean bAltenate = false;// use winding in this test + PointInPolygonHelper helper = new PointInPolygonHelper(bAltenate, + inputPoint, tolerance); + MultiPathImpl mpImpl = (MultiPathImpl) inputPolygon._getImpl(); + SegmentIteratorImpl iter = mpImpl.querySegmentIterator(); + while (iter.nextPath()) { + double ringArea = mpImpl.calculateRingArea2D(iter.getPathIndex()); + boolean bIsHole = ringArea < 0; + if (!bIsHole) { + helper.m_windnum = 0; + while (iter.hasNextSegment()) { + Segment segment = iter.nextSegment(); + if (helper.processSegment(segment)) + return -1; // point on boundary + } + + if (helper.m_windnum != 0) + return 1; + } + } + + return helper.result(); + } + + // Tests if Ring1 is inside Ring2. + // We assume here that the Polygon is Weak Simple. That is if one point of + // Ring1 is found to be inside of Ring2, then + // we assume that all of Ring1 is inside Ring2. + static boolean _isRingInRing2D(MultiPath polygon, int iRing1, int iRing2, + double tolerance, QuadTree quadTree) { + MultiPathImpl polygonImpl = (MultiPathImpl) polygon._getImpl(); + SegmentIteratorImpl segIter = polygonImpl.querySegmentIterator(); + segIter.resetToPath(iRing1); + if (!segIter.nextPath() || !segIter.hasNextSegment()) + throw new GeometryException("corrupted geometry"); + + int res = 2; + + while (res == 2 && segIter.hasNextSegment()) { + Segment segment = segIter.nextSegment(); + Point2D point = segment.getCoord2D(0.5); + res = PointInPolygonHelper.isPointInRing(polygonImpl, iRing2, + point, tolerance, quadTree); + } + + if (res == 2) + throw GeometryException.GeometryInternalError(); + if (res == 1) + return true; + + return false; + } + + static boolean quadTreeWillHelp(Polygon polygon, int c_queries) { + int n = polygon.getPointCount(); + + if (n < 16) + return false; + + double c_build_quad_tree = 2.0; // what's a good constant? + double c_query_quad_tree = 1.0; // what's a good constant? + double c_point_in_polygon_brute_force = 1.0; // what's a good constant? + + double c_quad_tree = c_build_quad_tree * n + c_query_quad_tree * (Math.log((double) n) / Math.log(2.0)) * c_queries; + double c_brute_force = c_point_in_polygon_brute_force * n * c_queries; + + return c_quad_tree < c_brute_force; + } } diff --git a/src/main/java/com/esri/core/geometry/Polygon.java b/src/main/java/com/esri/core/geometry/Polygon.java index 44b920df..d37e92dd 100644 --- a/src/main/java/com/esri/core/geometry/Polygon.java +++ b/src/main/java/com/esri/core/geometry/Polygon.java @@ -34,34 +34,34 @@ */ public class Polygon extends MultiPath implements Serializable { private static final long serialVersionUID = 2L;// TODO:remove as we use - // writeReplace and - // GeometrySerializer - - /** - * Creates a polygon. - */ - public Polygon() { - m_impl = new MultiPathImpl(true); - } - - public Polygon(VertexDescription vd) { - m_impl = new MultiPathImpl(true, vd); - } - - @Override - public Geometry createInstance() { - return new Polygon(getDescription()); - } - - @Override - public int getDimension() { - return 2; - } - - @Override - public Geometry.Type getType() { - return Type.Polygon; - } + // writeReplace and + // GeometrySerializer + + /** + * Creates a polygon. + */ + public Polygon() { + m_impl = new MultiPathImpl(true); + } + + public Polygon(VertexDescription vd) { + m_impl = new MultiPathImpl(true, vd); + } + + @Override + public Geometry createInstance() { + return new Polygon(getDescription()); + } + + @Override + public int getDimension() { + return 2; + } + + @Override + public Geometry.Type getType() { + return Type.Polygon; + } @Override public long estimateMemorySize() { @@ -70,78 +70,77 @@ public long estimateMemorySize() { /** * Calculates the ring area for this ring. - * - * @param ringIndex - * The index of this ring. + * + * @param ringIndex The index of this ring. * @return The ring area for this ring. */ public double calculateRingArea2D(int ringIndex) { return m_impl.calculateRingArea2D(ringIndex); } - /** - * Returns TRUE if the ring is an exterior ring. Valid only for simple - * polygons. - */ - public boolean isExteriorRing(int partIndex) { - return m_impl.isExteriorRing(partIndex); - } - - /** - * Returns TRUE when this geometry has exactly same type, properties, and - * coordinates as the other geometry. - */ - @Override - public boolean equals(Object other) { - if (other == null) - return false; - - if (other == this) - return true; - - if (other.getClass() != getClass()) - return false; - - return m_impl.equals(((Polygon) other)._getImpl()); - } - - /** - * Returns a hash code value for this polygon. - */ - - @Override - public int hashCode() { - return m_impl.hashCode(); - } - - /** - * Sets a new vertex for the polygon. - * - * @param i The index of the new vertex. - * @param x The X coordinate for the new vertex. - * @param y The Y coordinate for the new vertex. - */ - public void setXY(int i, double x, double y) { - m_impl.setXY(i, x, y); - - } - - public void interpolateAttributes(int path_index, int from_point_index, - int to_point_index) { - m_impl.interpolateAttributes(path_index, from_point_index, - to_point_index); - } - - public void interpolateAttributes(int semantics, int path_index, - int from_point_index, int to_point_index) { - m_impl.interpolateAttributesForSemantics(semantics, path_index, - from_point_index, to_point_index); - } + /** + * Returns TRUE if the ring is an exterior ring. Valid only for simple + * polygons. + */ + public boolean isExteriorRing(int partIndex) { + return m_impl.isExteriorRing(partIndex); + } + + /** + * Returns TRUE when this geometry has exactly same type, properties, and + * coordinates as the other geometry. + */ + @Override + public boolean equals(Object other) { + if (other == null) + return false; + + if (other == this) + return true; + + if (other.getClass() != getClass()) + return false; + + return m_impl.equals(((Polygon) other)._getImpl()); + } + + /** + * Returns a hash code value for this polygon. + */ + + @Override + public int hashCode() { + return m_impl.hashCode(); + } + + /** + * Sets a new vertex for the polygon. + * + * @param i The index of the new vertex. + * @param x The X coordinate for the new vertex. + * @param y The Y coordinate for the new vertex. + */ + public void setXY(int i, double x, double y) { + m_impl.setXY(i, x, y); + + } + + public void interpolateAttributes(int path_index, int from_point_index, + int to_point_index) { + m_impl.interpolateAttributes(path_index, from_point_index, + to_point_index); + } + + public void interpolateAttributes(int semantics, int path_index, + int from_point_index, int to_point_index) { + m_impl.interpolateAttributesForSemantics(semantics, path_index, + from_point_index, to_point_index); + } public int getExteriorRingCount() { return m_impl.getOGCPolygonCount(); } - + public interface FillRule { /** * odd-even fill rule. This is the default value. A point is in the polygon @@ -157,7 +156,9 @@ public interface FillRule { * crosses segments directed down, then the winding number is equal to N-M. */ public final static int enumFillRuleWinding = 1; - }; + } + + ; /** * Fill rule for the polygon that defines the interior of the self intersecting diff --git a/src/main/java/com/esri/core/geometry/PolygonUtils.java b/src/main/java/com/esri/core/geometry/PolygonUtils.java index f858e7f2..5d581564 100644 --- a/src/main/java/com/esri/core/geometry/PolygonUtils.java +++ b/src/main/java/com/esri/core/geometry/PolygonUtils.java @@ -26,306 +26,306 @@ final class PolygonUtils { - public enum PiPResult { - PiPOutside, PiPInside, PiPBoundary - } - - ; - - // enum_class PiPResult { PiPOutside = 0, PiPInside = 1, PiPBoundary = 2}; - - /** - * Tests if Point is inside the Polygon. Returns PiPOutside if not in - * polygon, PiPInside if in the polygon, PiPBoundary is if on the border. It - * tests border only if the tolerance is greater than 0, otherwise PiPBoundary cannot - * be returned. Note: If the tolerance is not 0, the test is more expensive - * because it calculates closest distance from a point to each segment. - *

- * O(n) complexity, where n is the number of polygon segments. - */ - public static PiPResult isPointInPolygon2D(Polygon polygon, - Point inputPoint, double tolerance) { - int res = PointInPolygonHelper.isPointInPolygon(polygon, inputPoint, - tolerance); - if (res == 0) - return PiPResult.PiPOutside; - if (res == 1) - return PiPResult.PiPInside; - - return PiPResult.PiPBoundary; - } - - public static PiPResult isPointInPolygon2D(Polygon polygon, - Point2D inputPoint, double tolerance) { - int res = PointInPolygonHelper.isPointInPolygon(polygon, inputPoint, - tolerance); - if (res == 0) - return PiPResult.PiPOutside; - if (res == 1) - return PiPResult.PiPInside; - - return PiPResult.PiPBoundary; - } - - static PiPResult isPointInPolygon2D(Polygon polygon, double inputPointXVal, - double inputPointYVal, double tolerance) { - int res = PointInPolygonHelper.isPointInPolygon(polygon, - inputPointXVal, inputPointYVal, tolerance); - if (res == 0) - return PiPResult.PiPOutside; - if (res == 1) - return PiPResult.PiPInside; - - return PiPResult.PiPBoundary; - } - - /** - * Tests if Point is inside the Polygon's ring. Returns PiPOutside if not in - * ring, PiPInside if in the ring, PiPBoundary is if on the border. It tests - * border only if the tolerance is greater than 0, otherwise PiPBoundary cannot be - * returned. Note: If the tolerance is not 0, the test is more expensive - * because it calculates closest distance from a point to each segment. - *

- * O(n) complexity, where n is the number of ring segments. - */ - public static PiPResult isPointInRing2D(Polygon polygon, int iRing, - Point2D inputPoint, double tolerance) { - MultiPathImpl polygonImpl = (MultiPathImpl) polygon._getImpl(); - int res = PointInPolygonHelper.isPointInRing(polygonImpl, iRing, - inputPoint, tolerance, null); - if (res == 0) - return PiPResult.PiPOutside; - if (res == 1) - return PiPResult.PiPInside; - - // return PiPResult.PiPBoundary; - return PiPResult.PiPInside; // we do not return PiPBoundary. Overwise, - // we would have to do more complex - // calculations to differentiat between - // internal and external boundaries. - } - - /** - * Tests if Point is inside of the any outer ring of a Polygon. Returns - * PiPOutside if not in any outer ring, PiPInside if in the any outer ring, - * or on the boundary. PiPBoundary is never returned. Note: If the tolerance - * is not 0, the test is more expensive because it calculates closest - * distance from a point to each segment. - *

- * O(n) complexity, where n is the number of polygon segments. - */ - public static PiPResult isPointInAnyOuterRing(Polygon polygon, - Point2D inputPoint, double tolerance) { - int res = PointInPolygonHelper.isPointInAnyOuterRing(polygon, - inputPoint, tolerance); - if (res == 0) - return PiPResult.PiPOutside; - if (res == 1) - return PiPResult.PiPInside; - - // return PiPResult.PiPBoundary; - return PiPResult.PiPInside; // we do not return PiPBoundary. Overwise, - // we would have to do more complex - // calculations to differentiat between - // internal and external boundaries. - } - - /** - * Tests point is inside the Polygon for an array of points. Returns - * PiPOutside if not in polygon, PiPInside if in the polygon, PiPBoundary is - * if on the border. It tests border only if the tolerance is greater than 0, otherwise - * PiPBoundary cannot be returned. Note: If the tolerance is not 0, the test - * is more expensive. - *

- * O(n*m) complexity, where n is the number of polygon segments, m is the - * number of input points. - */ - public static void testPointsInPolygon2D(Polygon polygon, - Point2D[] inputPoints, int count, double tolerance, - PiPResult[] testResults) { - if (inputPoints.length < count || testResults.length < count) - throw new IllegalArgumentException();// GEOMTHROW(invalid_argument); - - for (int i = 0; i < count; i++) - testResults[i] = isPointInPolygon2D(polygon, inputPoints[i], - tolerance); - } - - static void testPointsInPolygon2D(Polygon polygon, double[] xyStreamBuffer, - int pointCount, double tolerance, PiPResult[] testResults) { - if (xyStreamBuffer.length / 2 < pointCount - || testResults.length < pointCount) - throw new IllegalArgumentException();// GEOMTHROW(invalid_argument); - - for (int i = 0; i < pointCount; i++) - testResults[i] = isPointInPolygon2D(polygon, xyStreamBuffer[i * 2], - xyStreamBuffer[i * 2 + 1], tolerance); - } - - /** - * Tests point is inside an Area Geometry (Envelope, Polygon) for an array - * of points. Returns PiPOutside if not in area, PiPInside if in the area, - * PiPBoundary is if on the border. It tests border only if the tolerance is - * greater than 0, otherwise PiPBoundary cannot be returned. Note: If the tolerance is - * not 0, the test is more expensive. - *

- * O(n*m) complexity, where n is the number of polygon segments, m is the - * number of input points. - */ - public static void testPointsInArea2D(Geometry polygon, - Point2D[] inputPoints, int count, double tolerance, - PiPResult[] testResults) { - if (polygon.getType() == Geometry.Type.Polygon) - testPointsInPolygon2D((Polygon) polygon, inputPoints, count, - tolerance, testResults); - else if (polygon.getType() == Geometry.Type.Envelope) { - Envelope2D env2D = new Envelope2D(); - ((Envelope) polygon).queryEnvelope2D(env2D); - _testPointsInEnvelope2D(env2D, inputPoints, count, tolerance, - testResults); - } else - throw new GeometryException("invalid_call");// GEOMTHROW(invalid_call); - } - - public static void testPointsInArea2D(Geometry polygon, - double[] xyStreamBuffer, int count, double tolerance, - PiPResult[] testResults) { - if (polygon.getType() == Geometry.Type.Polygon) - testPointsInPolygon2D((Polygon) polygon, xyStreamBuffer, count, - tolerance, testResults); - else if (polygon.getType() == Geometry.Type.Envelope) { - Envelope2D env2D = new Envelope2D(); - ((Envelope) polygon).queryEnvelope2D(env2D); - _testPointsInEnvelope2D(env2D, xyStreamBuffer, count, tolerance, - testResults); - } else - throw new GeometryException("invalid_call");// GEOMTHROW(invalid_call); - } - - private static void _testPointsInEnvelope2D(Envelope2D env2D, - Point2D[] inputPoints, int count, double tolerance, - PiPResult[] testResults) { - if (inputPoints.length < count || testResults.length < count) - throw new IllegalArgumentException(); - - if (env2D.isEmpty()) { - for (int i = 0; i < count; i++) - testResults[i] = PiPResult.PiPOutside; - return; - } - - Envelope2D envIn = env2D; // note for java port - assignement by value - envIn.inflate(-tolerance * 0.5, -tolerance * 0.5); - Envelope2D envOut = env2D;// note for java port - assignement by value - envOut.inflate(tolerance * 0.5, tolerance * 0.5); - for (int i = 0; i < count; i++) { - if (envIn.contains(inputPoints[i])) - testResults[i] = PiPResult.PiPInside; - else if (!envOut.contains(inputPoints[i])) - testResults[i] = PiPResult.PiPOutside; - else - testResults[i] = PiPResult.PiPBoundary; - } - } - - private static void _testPointsInEnvelope2D(Envelope2D env2D, - double[] xyStreamBuffer, int pointCount, double tolerance, - PiPResult[] testResults) { - if (xyStreamBuffer.length / 2 < pointCount - || testResults.length < pointCount) - throw new IllegalArgumentException(); - - if (env2D.isEmpty()) { - for (int i = 0; i < pointCount; i++) - testResults[i] = PiPResult.PiPOutside; - return; - } - - Envelope2D envIn = env2D; // note for java port - assignement by value - envIn.inflate(-tolerance * 0.5, -tolerance * 0.5); - Envelope2D envOut = env2D;// note for java port - assignement by value - envOut.inflate(tolerance * 0.5, tolerance * 0.5); - for (int i = 0; i < pointCount; i++) { - if (envIn - .contains(xyStreamBuffer[i * 2], xyStreamBuffer[i * 2 + 1])) - testResults[i] = PiPResult.PiPInside; - else if (!envIn.contains(xyStreamBuffer[i * 2], - xyStreamBuffer[i * 2 + 1])) - testResults[i] = PiPResult.PiPOutside; - else - testResults[i] = PiPResult.PiPBoundary; - } - } - - static void testPointsOnSegment_(Segment seg, Point2D[] input_points, - int count, double tolerance, PolygonUtils.PiPResult[] test_results) { - for (int i = 0; i < count; i++) { - if (seg.isIntersecting(input_points[i], tolerance)) - test_results[i] = PiPResult.PiPBoundary; - else - test_results[i] = PiPResult.PiPOutside; - } - } - - static void testPointsOnPolyline2D_(Polyline poly, Point2D[] input_points, - int count, double tolerance, PolygonUtils.PiPResult[] test_results) { - MultiPathImpl mp_impl = (MultiPathImpl) poly._getImpl(); - GeometryAccelerators accel = mp_impl._getAccelerators(); - RasterizedGeometry2D rgeom = null; - if (accel != null) { - rgeom = accel.getRasterizedGeometry(); - } - - int pointsLeft = count; - for (int i = 0; i < count; i++) { - test_results[i] = PiPResult.PiPInside;// set to impossible value - - if (rgeom != null) { - Point2D input_point = input_points[i]; - RasterizedGeometry2D.HitType hit = rgeom.queryPointInGeometry( - input_point.x, input_point.y); - if (hit == RasterizedGeometry2D.HitType.Outside) { - test_results[i] = PiPResult.PiPOutside; - pointsLeft--; - } - } - } - - if (pointsLeft != 0) { - SegmentIteratorImpl iter = mp_impl.querySegmentIterator(); - while (iter.nextPath() && pointsLeft != 0) { - while (iter.hasNextSegment() && pointsLeft != 0) { - Segment segment = iter.nextSegment(); - for (int i = 0; i < count && pointsLeft != 0; i++) { - if (test_results[i] == PiPResult.PiPInside) { - if (segment.isIntersecting(input_points[i], - tolerance)) { - test_results[i] = PiPResult.PiPBoundary; - pointsLeft--; - } - } - } - } - } - } - - for (int i = 0; i < count; i++) { - if (test_results[i] == PiPResult.PiPInside) - test_results[i] = PiPResult.PiPOutside; - } - } - - static void testPointsOnLine2D(Geometry line, Point2D[] input_points, - int count, double tolerance, PolygonUtils.PiPResult[] test_results) { - Geometry.Type gt = line.getType(); - if (gt == Geometry.Type.Polyline) - testPointsOnPolyline2D_((Polyline) line, input_points, count, - tolerance, test_results); - else if (Geometry.isSegment(gt.value())) { - testPointsOnSegment_((Segment) line, input_points, count, - tolerance, test_results); - } else - throw new GeometryException("Invalid call."); - } + public enum PiPResult { + PiPOutside, PiPInside, PiPBoundary + } + + ; + + // enum_class PiPResult { PiPOutside = 0, PiPInside = 1, PiPBoundary = 2}; + + /** + * Tests if Point is inside the Polygon. Returns PiPOutside if not in + * polygon, PiPInside if in the polygon, PiPBoundary is if on the border. It + * tests border only if the tolerance is greater than 0, otherwise PiPBoundary cannot + * be returned. Note: If the tolerance is not 0, the test is more expensive + * because it calculates closest distance from a point to each segment. + *

+ * O(n) complexity, where n is the number of polygon segments. + */ + public static PiPResult isPointInPolygon2D(Polygon polygon, + Point inputPoint, double tolerance) { + int res = PointInPolygonHelper.isPointInPolygon(polygon, inputPoint, + tolerance); + if (res == 0) + return PiPResult.PiPOutside; + if (res == 1) + return PiPResult.PiPInside; + + return PiPResult.PiPBoundary; + } + + public static PiPResult isPointInPolygon2D(Polygon polygon, + Point2D inputPoint, double tolerance) { + int res = PointInPolygonHelper.isPointInPolygon(polygon, inputPoint, + tolerance); + if (res == 0) + return PiPResult.PiPOutside; + if (res == 1) + return PiPResult.PiPInside; + + return PiPResult.PiPBoundary; + } + + static PiPResult isPointInPolygon2D(Polygon polygon, double inputPointXVal, + double inputPointYVal, double tolerance) { + int res = PointInPolygonHelper.isPointInPolygon(polygon, + inputPointXVal, inputPointYVal, tolerance); + if (res == 0) + return PiPResult.PiPOutside; + if (res == 1) + return PiPResult.PiPInside; + + return PiPResult.PiPBoundary; + } + + /** + * Tests if Point is inside the Polygon's ring. Returns PiPOutside if not in + * ring, PiPInside if in the ring, PiPBoundary is if on the border. It tests + * border only if the tolerance is greater than 0, otherwise PiPBoundary cannot be + * returned. Note: If the tolerance is not 0, the test is more expensive + * because it calculates closest distance from a point to each segment. + *

+ * O(n) complexity, where n is the number of ring segments. + */ + public static PiPResult isPointInRing2D(Polygon polygon, int iRing, + Point2D inputPoint, double tolerance) { + MultiPathImpl polygonImpl = (MultiPathImpl) polygon._getImpl(); + int res = PointInPolygonHelper.isPointInRing(polygonImpl, iRing, + inputPoint, tolerance, null); + if (res == 0) + return PiPResult.PiPOutside; + if (res == 1) + return PiPResult.PiPInside; + + // return PiPResult.PiPBoundary; + return PiPResult.PiPInside; // we do not return PiPBoundary. Overwise, + // we would have to do more complex + // calculations to differentiat between + // internal and external boundaries. + } + + /** + * Tests if Point is inside of the any outer ring of a Polygon. Returns + * PiPOutside if not in any outer ring, PiPInside if in the any outer ring, + * or on the boundary. PiPBoundary is never returned. Note: If the tolerance + * is not 0, the test is more expensive because it calculates closest + * distance from a point to each segment. + *

+ * O(n) complexity, where n is the number of polygon segments. + */ + public static PiPResult isPointInAnyOuterRing(Polygon polygon, + Point2D inputPoint, double tolerance) { + int res = PointInPolygonHelper.isPointInAnyOuterRing(polygon, + inputPoint, tolerance); + if (res == 0) + return PiPResult.PiPOutside; + if (res == 1) + return PiPResult.PiPInside; + + // return PiPResult.PiPBoundary; + return PiPResult.PiPInside; // we do not return PiPBoundary. Overwise, + // we would have to do more complex + // calculations to differentiat between + // internal and external boundaries. + } + + /** + * Tests point is inside the Polygon for an array of points. Returns + * PiPOutside if not in polygon, PiPInside if in the polygon, PiPBoundary is + * if on the border. It tests border only if the tolerance is greater than 0, otherwise + * PiPBoundary cannot be returned. Note: If the tolerance is not 0, the test + * is more expensive. + *

+ * O(n*m) complexity, where n is the number of polygon segments, m is the + * number of input points. + */ + public static void testPointsInPolygon2D(Polygon polygon, + Point2D[] inputPoints, int count, double tolerance, + PiPResult[] testResults) { + if (inputPoints.length < count || testResults.length < count) + throw new IllegalArgumentException();// GEOMTHROW(invalid_argument); + + for (int i = 0; i < count; i++) + testResults[i] = isPointInPolygon2D(polygon, inputPoints[i], + tolerance); + } + + static void testPointsInPolygon2D(Polygon polygon, double[] xyStreamBuffer, + int pointCount, double tolerance, PiPResult[] testResults) { + if (xyStreamBuffer.length / 2 < pointCount + || testResults.length < pointCount) + throw new IllegalArgumentException();// GEOMTHROW(invalid_argument); + + for (int i = 0; i < pointCount; i++) + testResults[i] = isPointInPolygon2D(polygon, xyStreamBuffer[i * 2], + xyStreamBuffer[i * 2 + 1], tolerance); + } + + /** + * Tests point is inside an Area Geometry (Envelope, Polygon) for an array + * of points. Returns PiPOutside if not in area, PiPInside if in the area, + * PiPBoundary is if on the border. It tests border only if the tolerance is + * greater than 0, otherwise PiPBoundary cannot be returned. Note: If the tolerance is + * not 0, the test is more expensive. + *

+ * O(n*m) complexity, where n is the number of polygon segments, m is the + * number of input points. + */ + public static void testPointsInArea2D(Geometry polygon, + Point2D[] inputPoints, int count, double tolerance, + PiPResult[] testResults) { + if (polygon.getType() == Geometry.Type.Polygon) + testPointsInPolygon2D((Polygon) polygon, inputPoints, count, + tolerance, testResults); + else if (polygon.getType() == Geometry.Type.Envelope) { + Envelope2D env2D = new Envelope2D(); + ((Envelope) polygon).queryEnvelope2D(env2D); + _testPointsInEnvelope2D(env2D, inputPoints, count, tolerance, + testResults); + } else + throw new GeometryException("invalid_call");// GEOMTHROW(invalid_call); + } + + public static void testPointsInArea2D(Geometry polygon, + double[] xyStreamBuffer, int count, double tolerance, + PiPResult[] testResults) { + if (polygon.getType() == Geometry.Type.Polygon) + testPointsInPolygon2D((Polygon) polygon, xyStreamBuffer, count, + tolerance, testResults); + else if (polygon.getType() == Geometry.Type.Envelope) { + Envelope2D env2D = new Envelope2D(); + ((Envelope) polygon).queryEnvelope2D(env2D); + _testPointsInEnvelope2D(env2D, xyStreamBuffer, count, tolerance, + testResults); + } else + throw new GeometryException("invalid_call");// GEOMTHROW(invalid_call); + } + + private static void _testPointsInEnvelope2D(Envelope2D env2D, + Point2D[] inputPoints, int count, double tolerance, + PiPResult[] testResults) { + if (inputPoints.length < count || testResults.length < count) + throw new IllegalArgumentException(); + + if (env2D.isEmpty()) { + for (int i = 0; i < count; i++) + testResults[i] = PiPResult.PiPOutside; + return; + } + + Envelope2D envIn = env2D; // note for java port - assignement by value + envIn.inflate(-tolerance * 0.5, -tolerance * 0.5); + Envelope2D envOut = env2D;// note for java port - assignement by value + envOut.inflate(tolerance * 0.5, tolerance * 0.5); + for (int i = 0; i < count; i++) { + if (envIn.contains(inputPoints[i])) + testResults[i] = PiPResult.PiPInside; + else if (!envOut.contains(inputPoints[i])) + testResults[i] = PiPResult.PiPOutside; + else + testResults[i] = PiPResult.PiPBoundary; + } + } + + private static void _testPointsInEnvelope2D(Envelope2D env2D, + double[] xyStreamBuffer, int pointCount, double tolerance, + PiPResult[] testResults) { + if (xyStreamBuffer.length / 2 < pointCount + || testResults.length < pointCount) + throw new IllegalArgumentException(); + + if (env2D.isEmpty()) { + for (int i = 0; i < pointCount; i++) + testResults[i] = PiPResult.PiPOutside; + return; + } + + Envelope2D envIn = env2D; // note for java port - assignement by value + envIn.inflate(-tolerance * 0.5, -tolerance * 0.5); + Envelope2D envOut = env2D;// note for java port - assignement by value + envOut.inflate(tolerance * 0.5, tolerance * 0.5); + for (int i = 0; i < pointCount; i++) { + if (envIn + .contains(xyStreamBuffer[i * 2], xyStreamBuffer[i * 2 + 1])) + testResults[i] = PiPResult.PiPInside; + else if (!envIn.contains(xyStreamBuffer[i * 2], + xyStreamBuffer[i * 2 + 1])) + testResults[i] = PiPResult.PiPOutside; + else + testResults[i] = PiPResult.PiPBoundary; + } + } + + static void testPointsOnSegment_(Segment seg, Point2D[] input_points, + int count, double tolerance, PolygonUtils.PiPResult[] test_results) { + for (int i = 0; i < count; i++) { + if (seg.isIntersecting(input_points[i], tolerance)) + test_results[i] = PiPResult.PiPBoundary; + else + test_results[i] = PiPResult.PiPOutside; + } + } + + static void testPointsOnPolyline2D_(Polyline poly, Point2D[] input_points, + int count, double tolerance, PolygonUtils.PiPResult[] test_results) { + MultiPathImpl mp_impl = (MultiPathImpl) poly._getImpl(); + GeometryAccelerators accel = mp_impl._getAccelerators(); + RasterizedGeometry2D rgeom = null; + if (accel != null) { + rgeom = accel.getRasterizedGeometry(); + } + + int pointsLeft = count; + for (int i = 0; i < count; i++) { + test_results[i] = PiPResult.PiPInside;// set to impossible value + + if (rgeom != null) { + Point2D input_point = input_points[i]; + RasterizedGeometry2D.HitType hit = rgeom.queryPointInGeometry( + input_point.x, input_point.y); + if (hit == RasterizedGeometry2D.HitType.Outside) { + test_results[i] = PiPResult.PiPOutside; + pointsLeft--; + } + } + } + + if (pointsLeft != 0) { + SegmentIteratorImpl iter = mp_impl.querySegmentIterator(); + while (iter.nextPath() && pointsLeft != 0) { + while (iter.hasNextSegment() && pointsLeft != 0) { + Segment segment = iter.nextSegment(); + for (int i = 0; i < count && pointsLeft != 0; i++) { + if (test_results[i] == PiPResult.PiPInside) { + if (segment.isIntersecting(input_points[i], + tolerance)) { + test_results[i] = PiPResult.PiPBoundary; + pointsLeft--; + } + } + } + } + } + } + + for (int i = 0; i < count; i++) { + if (test_results[i] == PiPResult.PiPInside) + test_results[i] = PiPResult.PiPOutside; + } + } + + static void testPointsOnLine2D(Geometry line, Point2D[] input_points, + int count, double tolerance, PolygonUtils.PiPResult[] test_results) { + Geometry.Type gt = line.getType(); + if (gt == Geometry.Type.Polyline) + testPointsOnPolyline2D_((Polyline) line, input_points, count, + tolerance, test_results); + else if (Geometry.isSegment(gt.value())) { + testPointsOnSegment_((Segment) line, input_points, count, + tolerance, test_results); + } else + throw new GeometryException("Invalid call."); + } } diff --git a/src/main/java/com/esri/core/geometry/Polyline.java b/src/main/java/com/esri/core/geometry/Polyline.java index d6bfe51d..faa3fc6f 100644 --- a/src/main/java/com/esri/core/geometry/Polyline.java +++ b/src/main/java/com/esri/core/geometry/Polyline.java @@ -34,44 +34,44 @@ */ public class Polyline extends MultiPath implements Serializable { - private static final long serialVersionUID = 2L;// TODO:remove as we use - // writeReplace and - // GeometrySerializer - - /** - * Creates an empty polyline. - */ - public Polyline() { - m_impl = new MultiPathImpl(false); - } - - public Polyline(VertexDescription vd) { - m_impl = new MultiPathImpl(false, vd); - } - - /** - * Creates a polyline with one line segment. - */ - public Polyline(Point start, Point end) { - m_impl = new MultiPathImpl(false, start.getDescription()); - startPath(start); - lineTo(end); - } - - @Override - public Geometry createInstance() { - return new Polyline(getDescription()); - } - - @Override - public int getDimension() { - return 1; - } - - @Override - public Geometry.Type getType() { - return Type.Polyline; - } + private static final long serialVersionUID = 2L;// TODO:remove as we use + // writeReplace and + // GeometrySerializer + + /** + * Creates an empty polyline. + */ + public Polyline() { + m_impl = new MultiPathImpl(false); + } + + public Polyline(VertexDescription vd) { + m_impl = new MultiPathImpl(false, vd); + } + + /** + * Creates a polyline with one line segment. + */ + public Polyline(Point start, Point end) { + m_impl = new MultiPathImpl(false, start.getDescription()); + startPath(start); + lineTo(end); + } + + @Override + public Geometry createInstance() { + return new Polyline(getDescription()); + } + + @Override + public int getDimension() { + return 1; + } + + @Override + public Geometry.Type getType() { + return Type.Polyline; + } @Override public long estimateMemorySize() { @@ -87,38 +87,38 @@ public boolean equals(Object other) { if (other == null) return false; - if (other == this) - return true; + if (other == this) + return true; - if (other.getClass() != getClass()) - return false; + if (other.getClass() != getClass()) + return false; - return m_impl.equals(((Polyline) other)._getImpl()); - } + return m_impl.equals(((Polyline) other)._getImpl()); + } - /** - * Returns the hash code for the polyline. - */ + /** + * Returns the hash code for the polyline. + */ - @Override - public int hashCode() { - return m_impl.hashCode(); - } + @Override + public int hashCode() { + return m_impl.hashCode(); + } - @Override - public void addSegment(Segment segment, boolean bStartNewPath) { - m_impl.addSegment(segment, bStartNewPath); - } + @Override + public void addSegment(Segment segment, boolean bStartNewPath) { + m_impl.addSegment(segment, bStartNewPath); + } - public void interpolateAttributes(int from_path_index, - int from_point_index, int to_path_index, int to_point_index) { - m_impl.interpolateAttributes(from_path_index, from_point_index, - to_path_index, to_point_index); - } + public void interpolateAttributes(int from_path_index, + int from_point_index, int to_path_index, int to_point_index) { + m_impl.interpolateAttributes(from_path_index, from_point_index, + to_path_index, to_point_index); + } - public void interpolateAttributes(int semantics, int from_path_index, - int from_point_index, int to_path_index, int to_point_index) { - m_impl.interpolateAttributesForSemantics(semantics, from_path_index, - from_point_index, to_path_index, to_point_index); - } + public void interpolateAttributes(int semantics, int from_path_index, + int from_point_index, int to_path_index, int to_point_index) { + m_impl.interpolateAttributesForSemantics(semantics, from_path_index, + from_point_index, to_path_index, to_point_index); + } } diff --git a/src/main/java/com/esri/core/geometry/ProgressTracker.java b/src/main/java/com/esri/core/geometry/ProgressTracker.java index 9f1b9f1f..94c77578 100644 --- a/src/main/java/com/esri/core/geometry/ProgressTracker.java +++ b/src/main/java/com/esri/core/geometry/ProgressTracker.java @@ -27,22 +27,22 @@ * A callback to provide progress and cancel tracking mechanism for lengthy operation. */ public abstract class ProgressTracker { - /** - * Periodically called by a lengthy operation to check if the caller requested to cancel. - * - * @param step The current step of the operation. - * @param totalExpectedSteps is the number of steps the operation is expects to complete its task. - * @return true, if the operation can continue. Returns False, when the operation has to terminate due to a user cancelation. - */ - public abstract boolean progress(int step, int totalExpectedSteps); + /** + * Periodically called by a lengthy operation to check if the caller requested to cancel. + * + * @param step The current step of the operation. + * @param totalExpectedSteps is the number of steps the operation is expects to complete its task. + * @return true, if the operation can continue. Returns False, when the operation has to terminate due to a user cancelation. + */ + public abstract boolean progress(int step, int totalExpectedSteps); - /** - * Checks the tracker and throws UserCancelException if tracker is not null and progress returns false - * - * @param tracker can be null, then the method does nothing. - */ - public static void checkAndThrow(ProgressTracker tracker) { - if (tracker != null && !tracker.progress(-1, -1)) - throw new UserCancelException(); - } + /** + * Checks the tracker and throws UserCancelException if tracker is not null and progress returns false + * + * @param tracker can be null, then the method does nothing. + */ + public static void checkAndThrow(ProgressTracker tracker) { + if (tracker != null && !tracker.progress(-1, -1)) + throw new UserCancelException(); + } } diff --git a/src/main/java/com/esri/core/geometry/Projecter.java b/src/main/java/com/esri/core/geometry/Projecter.java index 06f42657..bcb80b30 100644 --- a/src/main/java/com/esri/core/geometry/Projecter.java +++ b/src/main/java/com/esri/core/geometry/Projecter.java @@ -6,151 +6,151 @@ * Created by davidraleigh on 5/12/17. */ class Projecter { - static { - System.loadLibrary("proj"); - } - - public static int transform(ProjectionTransformation projectionTransformation, Point[] pointsIn, - int count, Point[] pointsOut) throws org.proj4.PJException { - if (pointsIn[0].hasZ()) - return __transform3D(projectionTransformation, pointsIn, count, pointsOut); - return __transform2D(projectionTransformation, pointsIn, count, pointsOut); - } - - private static int __transform2D(ProjectionTransformation projectionTransformation, - Point[] pointsIn, - int count, - Point[] pointsOut) throws org.proj4.PJException { - double[] coordsIn = new double[pointsIn.length * 2]; - for (int i = 0; i < pointsIn.length; i++) { - coordsIn[i * 2] = pointsIn[i].getX(); - coordsIn[i * 2 + 1] = pointsIn[i].getY(); - } - - Projecter.transform(projectionTransformation, coordsIn, false); - for (int i = 0; i < pointsOut.length; i++) { - pointsOut[i].setX(coordsIn[i * 2]); - pointsOut[i].setY(coordsIn[i * 2 + 1]); - } - - return 0; - } - - private static int __transform3D(ProjectionTransformation projectionTransformation, - Point[] pointsIn, - int count, - Point[] pointsOut) throws org.proj4.PJException { - double[] coordsIn = new double[pointsIn.length * 3]; - for (int i = 0; i < pointsIn.length; i++) { - coordsIn[i * 2] = pointsIn[i].getX(); - coordsIn[i * 2 + 1] = pointsIn[i].getY(); - coordsIn[i * 2 + 2] = pointsIn[i].getZ(); - } - - Projecter.transform(projectionTransformation, coordsIn, true); - for (int i = 0; i < pointsOut.length; i++) { - pointsOut[i].setX(coordsIn[i * 2]); - pointsOut[i].setY(coordsIn[i * 2 + 1]); - pointsOut[i].setZ(coordsIn[i * 2 + 2]); - } - - return 0; - } - - public static double[] transform(ProjectionTransformation projectionTransformation, - double[] coordsSrc, - boolean hasZ) throws org.proj4.PJException { - int n = 2; - if (hasZ) - n = 3; - - projectionTransformation.getFromProj().transform(projectionTransformation.getToProj(), n, coordsSrc, 0, coordsSrc.length / n); - return coordsSrc; - } - - static Geometry project(Geometry geometry, - ProjectionTransformation projectionTransformation, - ProgressTracker progressTracker) { - if (geometry.isEmpty()) { - return geometry; - } - - if (projectionTransformation.m_fromSpatialReference == null || projectionTransformation.m_toSpatialReference == null) { - throw new GeometryException("From and To Spatial references required to Project Geometry"); - } - // TODO check that all project methods no longer use 'new Geometry' - // TODO maybe push copy down to each geometry type? Envelope shouldn't create copy, right? - // TODO is clipping creating a new cloned geometry? Should there should be a check so that there aren't too many unnecessary clones - Geometry result = geometry.copy(); - try { - switch (geometry.getType()) { - case Unknown: - break; - case Point: - result = projectPoint(result, projectionTransformation, progressTracker); - break; - case Line: - break; - case Envelope: - result = projectEnvelope(result, projectionTransformation, progressTracker); - break; - case MultiPoint: - result = projectMultiPoint(result, projectionTransformation, progressTracker); - break; - case Polyline: - result = projectPolyline(result, projectionTransformation, progressTracker); - break; - case Polygon: - result = projectPolygon(result, projectionTransformation, progressTracker); - break; - } - } catch (PJException e) { - throw new GeometryException(String.format("Proj4 projection exception:\n{}\n{}", e.getLocalizedMessage(), e.getStackTrace())); - } - - return result; - } - - static Geometry clipGeometry(Geometry geometry, ProjectionTransformation projectionTransformation, ProgressTracker progressTracker) { - // TODO implement a real horizon - if (projectionTransformation.m_fromSpatialReference.getCoordinateSystemType() == SpatialReference.CoordinateSystemType.GEOGRAPHIC) { - // Fold Geometries into a space that ranges from -180 - 180 - Geometry folded = OperatorProjectLocal.foldInto360Range(geometry, projectionTransformation.m_fromSpatialReference); - - // Cut geometries at horizon - - // union rings - return folded; - } - - return geometry; - } - - static Geometry projectPoint(Geometry geometry, - ProjectionTransformation projectionTransformation, - ProgressTracker progressTracker) throws org.proj4.PJException { - geometry = clipGeometry(geometry, projectionTransformation, progressTracker); - Point outpoint = new Point(); - // TODO clean this idea up - Point[] outputs = {outpoint}; - Point[] inputs = {(Point) geometry}; - transform(projectionTransformation, inputs, 1, outputs); - // TODO setDirtyFlag? - return outputs[0]; - } - - static Geometry projectMultiPoint(Geometry geometry, - ProjectionTransformation projectionTransformation, - ProgressTracker progressTracker) throws org.proj4.PJException { - MultiPoint multiPoint = (MultiPoint) clipGeometry(geometry, projectionTransformation, progressTracker); - - int pointCount = multiPoint.getPointCount(); - MultiVertexGeometryImpl multiVertexGeometry = (MultiVertexGeometryImpl) multiPoint._getImpl(); - - AttributeStreamOfDbl xyPositions = (AttributeStreamOfDbl) multiVertexGeometry.getAttributeStreamRef(0); - // TODO check that there isn't a way for grabbing xyzPositions - transform(projectionTransformation, xyPositions.m_buffer, false); - multiVertexGeometry._setDirtyFlag(DirtyFlags.dirtyAll, true); + static { + System.loadLibrary("proj"); + } + + public static int transform(ProjectionTransformation projectionTransformation, Point[] pointsIn, + int count, Point[] pointsOut) throws org.proj4.PJException { + if (pointsIn[0].hasZ()) + return __transform3D(projectionTransformation, pointsIn, count, pointsOut); + return __transform2D(projectionTransformation, pointsIn, count, pointsOut); + } + + private static int __transform2D(ProjectionTransformation projectionTransformation, + Point[] pointsIn, + int count, + Point[] pointsOut) throws org.proj4.PJException { + double[] coordsIn = new double[pointsIn.length * 2]; + for (int i = 0; i < pointsIn.length; i++) { + coordsIn[i * 2] = pointsIn[i].getX(); + coordsIn[i * 2 + 1] = pointsIn[i].getY(); + } + + Projecter.transform(projectionTransformation, coordsIn, false); + for (int i = 0; i < pointsOut.length; i++) { + pointsOut[i].setX(coordsIn[i * 2]); + pointsOut[i].setY(coordsIn[i * 2 + 1]); + } + + return 0; + } + + private static int __transform3D(ProjectionTransformation projectionTransformation, + Point[] pointsIn, + int count, + Point[] pointsOut) throws org.proj4.PJException { + double[] coordsIn = new double[pointsIn.length * 3]; + for (int i = 0; i < pointsIn.length; i++) { + coordsIn[i * 2] = pointsIn[i].getX(); + coordsIn[i * 2 + 1] = pointsIn[i].getY(); + coordsIn[i * 2 + 2] = pointsIn[i].getZ(); + } + + Projecter.transform(projectionTransformation, coordsIn, true); + for (int i = 0; i < pointsOut.length; i++) { + pointsOut[i].setX(coordsIn[i * 2]); + pointsOut[i].setY(coordsIn[i * 2 + 1]); + pointsOut[i].setZ(coordsIn[i * 2 + 2]); + } + + return 0; + } + + public static double[] transform(ProjectionTransformation projectionTransformation, + double[] coordsSrc, + boolean hasZ) throws org.proj4.PJException { + int n = 2; + if (hasZ) + n = 3; + + projectionTransformation.getFromProj().transform(projectionTransformation.getToProj(), n, coordsSrc, 0, coordsSrc.length / n); + return coordsSrc; + } + + static Geometry project(Geometry geometry, + ProjectionTransformation projectionTransformation, + ProgressTracker progressTracker) { + if (geometry.isEmpty()) { + return geometry; + } + + if (projectionTransformation.m_fromSpatialReference == null || projectionTransformation.m_toSpatialReference == null) { + throw new GeometryException("From and To Spatial references required to Project Geometry"); + } + // TODO check that all project methods no longer use 'new Geometry' + // TODO maybe push copy down to each geometry type? Envelope shouldn't create copy, right? + // TODO is clipping creating a new cloned geometry? Should there should be a check so that there aren't too many unnecessary clones + Geometry result = geometry.copy(); + try { + switch (geometry.getType()) { + case Unknown: + break; + case Point: + result = projectPoint(result, projectionTransformation, progressTracker); + break; + case Line: + break; + case Envelope: + result = projectEnvelope(result, projectionTransformation, progressTracker); + break; + case MultiPoint: + result = projectMultiPoint(result, projectionTransformation, progressTracker); + break; + case Polyline: + result = projectPolyline(result, projectionTransformation, progressTracker); + break; + case Polygon: + result = projectPolygon(result, projectionTransformation, progressTracker); + break; + } + } catch (PJException e) { + throw new GeometryException(String.format("Proj4 projection exception:\n{}\n{}", e.getLocalizedMessage(), e.getStackTrace())); + } + + return result; + } + + static Geometry clipGeometry(Geometry geometry, ProjectionTransformation projectionTransformation, ProgressTracker progressTracker) { + // TODO implement a real horizon + if (projectionTransformation.m_fromSpatialReference.getCoordinateSystemType() == SpatialReference.CoordinateSystemType.GEOGRAPHIC) { + // Fold Geometries into a space that ranges from -180 - 180 + Geometry folded = OperatorProjectLocal.foldInto360Range(geometry, projectionTransformation.m_fromSpatialReference); + + // Cut geometries at horizon + + // union rings + return folded; + } + + return geometry; + } + + static Geometry projectPoint(Geometry geometry, + ProjectionTransformation projectionTransformation, + ProgressTracker progressTracker) throws org.proj4.PJException { + geometry = clipGeometry(geometry, projectionTransformation, progressTracker); + Point outpoint = new Point(); + // TODO clean this idea up + Point[] outputs = {outpoint}; + Point[] inputs = {(Point) geometry}; + transform(projectionTransformation, inputs, 1, outputs); + // TODO setDirtyFlag? + return outputs[0]; + } + + static Geometry projectMultiPoint(Geometry geometry, + ProjectionTransformation projectionTransformation, + ProgressTracker progressTracker) throws org.proj4.PJException { + MultiPoint multiPoint = (MultiPoint) clipGeometry(geometry, projectionTransformation, progressTracker); + + int pointCount = multiPoint.getPointCount(); + MultiVertexGeometryImpl multiVertexGeometry = (MultiVertexGeometryImpl) multiPoint._getImpl(); + + AttributeStreamOfDbl xyPositions = (AttributeStreamOfDbl) multiVertexGeometry.getAttributeStreamRef(0); + // TODO check that there isn't a way for grabbing xyzPositions + transform(projectionTransformation, xyPositions.m_buffer, false); + multiVertexGeometry._setDirtyFlag(DirtyFlags.dirtyAll, true); // AttributeStreamOfDbl attributeStreamOfDbl = new AttributeStreamOfDbl(pointCount * 2); // attributeStreamOfDbl.writeRange(0, pointCount * 2, output, 0, true); // @@ -160,23 +160,23 @@ static Geometry projectMultiPoint(Geometry geometry, // multiVertexGeometryOut.setAttributeStreamRef(0, attributeStreamOfDbl); // multiVertexGeometryOut._resizeImpl(pointCount); // multiPointOut.resize(pointCount); - multiVertexGeometry._updateAllDirtyIntervals(true); + multiVertexGeometry._updateAllDirtyIntervals(true); - return multiPoint; - } + return multiPoint; + } - static Geometry projectPolyline(Geometry geometry, - ProjectionTransformation projectionTransformation, - ProgressTracker progressTracker) throws org.proj4.PJException { - Polyline polyline = (Polyline) clipGeometry(geometry, projectionTransformation, progressTracker); + static Geometry projectPolyline(Geometry geometry, + ProjectionTransformation projectionTransformation, + ProgressTracker progressTracker) throws org.proj4.PJException { + Polyline polyline = (Polyline) clipGeometry(geometry, projectionTransformation, progressTracker); - int pointCount = polyline.getPointCount(); - MultiVertexGeometryImpl multiVertexGeometry = (MultiVertexGeometryImpl) polyline._getImpl(); + int pointCount = polyline.getPointCount(); + MultiVertexGeometryImpl multiVertexGeometry = (MultiVertexGeometryImpl) polyline._getImpl(); - AttributeStreamOfDbl xyPositions = (AttributeStreamOfDbl) multiVertexGeometry.getAttributeStreamRef(0); - // TODO check that there isn't a way for grabbing xyzPositions - transform(projectionTransformation, xyPositions.m_buffer, false); - multiVertexGeometry._setDirtyFlag(DirtyFlags.dirtyAll, true); + AttributeStreamOfDbl xyPositions = (AttributeStreamOfDbl) multiVertexGeometry.getAttributeStreamRef(0); + // TODO check that there isn't a way for grabbing xyzPositions + transform(projectionTransformation, xyPositions.m_buffer, false); + multiVertexGeometry._setDirtyFlag(DirtyFlags.dirtyAll, true); // AttributeStreamOfDbl attributeStreamOfDbl = new AttributeStreamOfDbl(pointCount * 2); // attributeStreamOfDbl.writeRange(0, pointCount * 2, output, 0, true); @@ -185,22 +185,22 @@ static Geometry projectPolyline(Geometry geometry, // // multiVertexGeometryOut.setAttributeStreamRef(0, attributeStreamOfDbl); // multiVertexGeometryOut._resizeImpl(pointCount); - multiVertexGeometry._updateAllDirtyIntervals(true); + multiVertexGeometry._updateAllDirtyIntervals(true); - return polyline; - } + return polyline; + } - static Geometry projectPolygon(Geometry geometry, - ProjectionTransformation projectionTransformation, - ProgressTracker progressTracker) throws org.proj4.PJException { - Polygon polygon = (Polygon) clipGeometry(geometry, projectionTransformation, progressTracker); + static Geometry projectPolygon(Geometry geometry, + ProjectionTransformation projectionTransformation, + ProgressTracker progressTracker) throws org.proj4.PJException { + Polygon polygon = (Polygon) clipGeometry(geometry, projectionTransformation, progressTracker); - MultiVertexGeometryImpl multiVertexGeometry = (MultiVertexGeometryImpl) polygon._getImpl(); + MultiVertexGeometryImpl multiVertexGeometry = (MultiVertexGeometryImpl) polygon._getImpl(); - AttributeStreamOfDbl xyPositions = (AttributeStreamOfDbl) multiVertexGeometry.getAttributeStreamRef(0); - // TODO check that there isn't a way for grabbing xyzPositions - transform(projectionTransformation, xyPositions.m_buffer, false); - multiVertexGeometry._setDirtyFlag(DirtyFlags.dirtyAll, true); + AttributeStreamOfDbl xyPositions = (AttributeStreamOfDbl) multiVertexGeometry.getAttributeStreamRef(0); + // TODO check that there isn't a way for grabbing xyzPositions + transform(projectionTransformation, xyPositions.m_buffer, false); + multiVertexGeometry._setDirtyFlag(DirtyFlags.dirtyAll, true); // AttributeStreamOfDbl attributeStreamOfDbl = new AttributeStreamOfDbl(pointCount * 2); // attributeStreamOfDbl.writeRange(0, pointCount * 2, output, 0, true); // @@ -209,18 +209,18 @@ static Geometry projectPolygon(Geometry geometry, // // multiVertexGeometryOut.setAttributeStreamRef(0, attributeStreamOfDbl); // multiVertexGeometryOut._resizeImpl(pointCount); - multiVertexGeometry._updateAllDirtyIntervals(true); - return polygon; - } - - static Geometry projectEnvelope(Geometry geometry, - ProjectionTransformation projectionTransformation, - ProgressTracker progressTracker) throws org.proj4.PJException { - Envelope envelope = (Envelope) geometry; - // TODO how to properly copy envelope into polygon - Polygon polygon = new Polygon(); - polygon.addEnvelope(envelope, false); - - return projectPolygon(polygon, projectionTransformation, progressTracker); - } + multiVertexGeometry._updateAllDirtyIntervals(true); + return polygon; + } + + static Geometry projectEnvelope(Geometry geometry, + ProjectionTransformation projectionTransformation, + ProgressTracker progressTracker) throws org.proj4.PJException { + Envelope envelope = (Envelope) geometry; + // TODO how to properly copy envelope into polygon + Polygon polygon = new Polygon(); + polygon.addEnvelope(envelope, false); + + return projectPolygon(polygon, projectionTransformation, progressTracker); + } } diff --git a/src/main/java/com/esri/core/geometry/ProjectionTransformation.java b/src/main/java/com/esri/core/geometry/ProjectionTransformation.java index 625723eb..7c946b44 100644 --- a/src/main/java/com/esri/core/geometry/ProjectionTransformation.java +++ b/src/main/java/com/esri/core/geometry/ProjectionTransformation.java @@ -28,31 +28,36 @@ //This is a stub public class ProjectionTransformation { - SpatialReference m_fromSpatialReference; - SpatialReference m_toSpatialReference; - // TODO maybe cache the PJ objects? - - public ProjectionTransformation(SpatialReference fromSpatialReference, SpatialReference toSpatialReference) { - m_fromSpatialReference = fromSpatialReference; - m_toSpatialReference = toSpatialReference; - } - - public ProjectionTransformation getReverse() { - return new ProjectionTransformation(m_toSpatialReference, m_fromSpatialReference); - } - - PJ getFromProj() { - return ((SpatialReferenceImpl)m_fromSpatialReference).getPJ(); - } - - public SpatialReference getFrom() { return m_fromSpatialReference; } - public SpatialReference getTo() { return m_toSpatialReference; } - - PJ getToProj() { - return ((SpatialReferenceImpl)m_toSpatialReference).getPJ(); - } - - public static ProjectionTransformation getEqualArea(Geometry geometry, SpatialReference spatialReference) { - return new ProjectionTransformation(spatialReference, SpatialReference.createEqualArea(geometry, spatialReference)); - } + SpatialReference m_fromSpatialReference; + SpatialReference m_toSpatialReference; + // TODO maybe cache the PJ objects? + + public ProjectionTransformation(SpatialReference fromSpatialReference, SpatialReference toSpatialReference) { + m_fromSpatialReference = fromSpatialReference; + m_toSpatialReference = toSpatialReference; + } + + public ProjectionTransformation getReverse() { + return new ProjectionTransformation(m_toSpatialReference, m_fromSpatialReference); + } + + PJ getFromProj() { + return ((SpatialReferenceImpl) m_fromSpatialReference).getPJ(); + } + + public SpatialReference getFrom() { + return m_fromSpatialReference; + } + + public SpatialReference getTo() { + return m_toSpatialReference; + } + + PJ getToProj() { + return ((SpatialReferenceImpl) m_toSpatialReference).getPJ(); + } + + public static ProjectionTransformation getEqualArea(Geometry geometry, SpatialReference spatialReference) { + return new ProjectionTransformation(spatialReference, SpatialReference.createEqualArea(geometry, spatialReference)); + } } diff --git a/src/main/java/com/esri/core/geometry/Proximity2DResult.java b/src/main/java/com/esri/core/geometry/Proximity2DResult.java index 92c348cd..27a84a92 100644 --- a/src/main/java/com/esri/core/geometry/Proximity2DResult.java +++ b/src/main/java/com/esri/core/geometry/Proximity2DResult.java @@ -29,104 +29,104 @@ * the distance from a given point to the nearest point on another geometry. */ public class Proximity2DResult { - Point2D m_coordinate = new Point2D(); - int m_vertexIndex; - double m_distance; - int m_info; - - /** - * Sets the right_side info to true or false. - * - * @param bRight Whether the nearest coordinate is to the right or left of the - * geometry. - */ - public void setRightSide(boolean bRight) { - if (bRight) - m_info |= (int) OperatorProximity2D.ProxResultInfo.rightSide; - else - m_info &= ~(int) OperatorProximity2D.ProxResultInfo.rightSide; - } - - /** - * Returns TRUE if the Proximity2DResult is empty. This only happens if the - * Geometry passed to the Proximity operator is empty. - */ - public boolean isEmpty() { - return m_vertexIndex < 0; - } - - /** - * Returns the closest coordinate for - * OperatorProximity2D.getNearestCoordinate or the vertex coordinates for - * the OperatorProximity2D.getNearestVertex and - * OperatorProximity2D.getNearestVertices. - */ - public Point getCoordinate() { - if (isEmpty()) - throw new GeometryException("invalid call"); - - return new Point(m_coordinate.x, m_coordinate.y); - } - - /** - * Returns the vertex index. For OperatorProximity2D.getNearestCoordinate - * the behavior is: When the input is a polygon or an envelope and the - * bTestPolygonInterior is true, the value is zero. When the input is a - * polygon or an Envelope and the bTestPolygonInterior is false, the value - * is the start vertex index of a segment with the closest coordinate. When - * the input is a polyline, the value is the start vertex index of a segment - * with the closest coordinate. When the input is a point, the value is 0. - * When the input is a multipoint, the value is the closest vertex. - */ - public int getVertexIndex() { - if (isEmpty()) - throw new GeometryException("invalid call"); - - return m_vertexIndex; - } - - /** - * Returns the distance to the closest vertex or coordinate. - */ - public double getDistance() { - if (isEmpty()) - throw new GeometryException("invalid call"); - - return m_distance; - } - - /** - * Returns true if the closest coordinate is to the right of the MultiPath. - */ - public boolean isRightSide() { - return (m_info & (int) OperatorProximity2D.ProxResultInfo.rightSide) != 0; - } - - void _setParams(double x, double y, int vertexIndex, double distance) { - m_coordinate.x = x; - m_coordinate.y = y; - m_vertexIndex = vertexIndex; - m_distance = distance; - } - - // static int _compare(Proximity2DResult v1, Proximity2DResult v2) - // { - // if (v1.m_distance < v2.m_distance) - // return -1; - // if (v1.m_distance == v2.m_distance) - // return 0; - // - // return 1; - // } - - Proximity2DResult() { - m_vertexIndex = -1; - } - - Proximity2DResult(Point2D coordinate, int vertexIndex, double distance) { - m_coordinate.setCoords(coordinate); - m_vertexIndex = vertexIndex; - m_distance = distance; - m_info = 0; - } + Point2D m_coordinate = new Point2D(); + int m_vertexIndex; + double m_distance; + int m_info; + + /** + * Sets the right_side info to true or false. + * + * @param bRight Whether the nearest coordinate is to the right or left of the + * geometry. + */ + public void setRightSide(boolean bRight) { + if (bRight) + m_info |= (int) OperatorProximity2D.ProxResultInfo.rightSide; + else + m_info &= ~(int) OperatorProximity2D.ProxResultInfo.rightSide; + } + + /** + * Returns TRUE if the Proximity2DResult is empty. This only happens if the + * Geometry passed to the Proximity operator is empty. + */ + public boolean isEmpty() { + return m_vertexIndex < 0; + } + + /** + * Returns the closest coordinate for + * OperatorProximity2D.getNearestCoordinate or the vertex coordinates for + * the OperatorProximity2D.getNearestVertex and + * OperatorProximity2D.getNearestVertices. + */ + public Point getCoordinate() { + if (isEmpty()) + throw new GeometryException("invalid call"); + + return new Point(m_coordinate.x, m_coordinate.y); + } + + /** + * Returns the vertex index. For OperatorProximity2D.getNearestCoordinate + * the behavior is: When the input is a polygon or an envelope and the + * bTestPolygonInterior is true, the value is zero. When the input is a + * polygon or an Envelope and the bTestPolygonInterior is false, the value + * is the start vertex index of a segment with the closest coordinate. When + * the input is a polyline, the value is the start vertex index of a segment + * with the closest coordinate. When the input is a point, the value is 0. + * When the input is a multipoint, the value is the closest vertex. + */ + public int getVertexIndex() { + if (isEmpty()) + throw new GeometryException("invalid call"); + + return m_vertexIndex; + } + + /** + * Returns the distance to the closest vertex or coordinate. + */ + public double getDistance() { + if (isEmpty()) + throw new GeometryException("invalid call"); + + return m_distance; + } + + /** + * Returns true if the closest coordinate is to the right of the MultiPath. + */ + public boolean isRightSide() { + return (m_info & (int) OperatorProximity2D.ProxResultInfo.rightSide) != 0; + } + + void _setParams(double x, double y, int vertexIndex, double distance) { + m_coordinate.x = x; + m_coordinate.y = y; + m_vertexIndex = vertexIndex; + m_distance = distance; + } + + // static int _compare(Proximity2DResult v1, Proximity2DResult v2) + // { + // if (v1.m_distance < v2.m_distance) + // return -1; + // if (v1.m_distance == v2.m_distance) + // return 0; + // + // return 1; + // } + + Proximity2DResult() { + m_vertexIndex = -1; + } + + Proximity2DResult(Point2D coordinate, int vertexIndex, double distance) { + m_coordinate.setCoords(coordinate); + m_vertexIndex = vertexIndex; + m_distance = distance; + m_info = 0; + } } diff --git a/src/main/java/com/esri/core/geometry/Proximity2DResultComparator.java b/src/main/java/com/esri/core/geometry/Proximity2DResultComparator.java index c8c35828..0404aa80 100644 --- a/src/main/java/com/esri/core/geometry/Proximity2DResultComparator.java +++ b/src/main/java/com/esri/core/geometry/Proximity2DResultComparator.java @@ -28,13 +28,13 @@ class Proximity2DResultComparator implements Comparator { - public int compare(Proximity2DResult v1, Proximity2DResult v2) { - if (v1.m_distance < v2.m_distance) - return -1; - if (v1.m_distance == v2.m_distance) - return 0; - - return 1; - } + public int compare(Proximity2DResult v1, Proximity2DResult v2) { + if (v1.m_distance < v2.m_distance) + return -1; + if (v1.m_distance == v2.m_distance) + return 0; + + return 1; + } } diff --git a/src/main/java/com/esri/core/geometry/PtSrlzr.java b/src/main/java/com/esri/core/geometry/PtSrlzr.java index 5d9435f4..68dd1aae 100644 --- a/src/main/java/com/esri/core/geometry/PtSrlzr.java +++ b/src/main/java/com/esri/core/geometry/PtSrlzr.java @@ -29,60 +29,60 @@ //This is a writeReplace class for Point public class PtSrlzr implements Serializable { - private static final long serialVersionUID = 1L; - double[] attribs; - int descriptionBitMask; + private static final long serialVersionUID = 1L; + double[] attribs; + int descriptionBitMask; - public Object readResolve() throws ObjectStreamException { - Point point = null; - try { - VertexDescription vd = VertexDescriptionDesignerImpl - .getVertexDescription(descriptionBitMask); - point = new Point(vd); - if (attribs != null) { - point.setXY(attribs[0], attribs[1]); - int index = 2; - for (int i = 1, n = vd.getAttributeCount(); i < n; i++) { - int semantics = vd.getSemantics(i); - int comps = VertexDescription.getComponentCount(semantics); - for (int ord = 0; ord < comps; ord++) { - point.setAttribute(semantics, ord, attribs[index++]); - } - } - } - } catch (Exception ex) { - throw new InvalidObjectException("Cannot read geometry from stream"); - } + public Object readResolve() throws ObjectStreamException { + Point point = null; + try { + VertexDescription vd = VertexDescriptionDesignerImpl + .getVertexDescription(descriptionBitMask); + point = new Point(vd); + if (attribs != null) { + point.setXY(attribs[0], attribs[1]); + int index = 2; + for (int i = 1, n = vd.getAttributeCount(); i < n; i++) { + int semantics = vd.getSemantics(i); + int comps = VertexDescription.getComponentCount(semantics); + for (int ord = 0; ord < comps; ord++) { + point.setAttribute(semantics, ord, attribs[index++]); + } + } + } + } catch (Exception ex) { + throw new InvalidObjectException("Cannot read geometry from stream"); + } - return point; - } + return point; + } - public void setGeometryByValue(Point point) throws ObjectStreamException { - try { - attribs = null; - if (point == null) { - descriptionBitMask = 1; - } + public void setGeometryByValue(Point point) throws ObjectStreamException { + try { + attribs = null; + if (point == null) { + descriptionBitMask = 1; + } - VertexDescription vd = point.getDescription(); - descriptionBitMask = vd.m_semanticsBitArray; - if (point.isEmpty()) { - return; - } + VertexDescription vd = point.getDescription(); + descriptionBitMask = vd.m_semanticsBitArray; + if (point.isEmpty()) { + return; + } - attribs = new double[vd.getTotalComponentCount()]; - attribs[0] = point.getX(); - attribs[1] = point.getY(); - int index = 2; - for (int i = 1, n = vd.getAttributeCount(); i < n; i++) { - int semantics = vd.getSemantics(i); - int comps = VertexDescription.getComponentCount(semantics); - for (int ord = 0; ord < comps; ord++) { - attribs[index++] = point.getAttributeAsDbl(semantics, ord); - } - } - } catch (Exception ex) { - throw new InvalidObjectException("Cannot serialize this geometry"); - } - } + attribs = new double[vd.getTotalComponentCount()]; + attribs[0] = point.getX(); + attribs[1] = point.getY(); + int index = 2; + for (int i = 1, n = vd.getAttributeCount(); i < n; i++) { + int semantics = vd.getSemantics(i); + int comps = VertexDescription.getComponentCount(semantics); + for (int ord = 0; ord < comps; ord++) { + attribs[index++] = point.getAttributeAsDbl(semantics, ord); + } + } + } catch (Exception ex) { + throw new InvalidObjectException("Cannot serialize this geometry"); + } + } } diff --git a/src/main/java/com/esri/core/geometry/QuadTree.java b/src/main/java/com/esri/core/geometry/QuadTree.java index e236826b..32d81c3c 100644 --- a/src/main/java/com/esri/core/geometry/QuadTree.java +++ b/src/main/java/com/esri/core/geometry/QuadTree.java @@ -26,315 +26,315 @@ package com.esri.core.geometry; public class QuadTree { - public static final class QuadTreeIterator { - /** - * Resets the iterator to an starting state on the QuadTree. If the - * input Geometry is a Line segment, then the query will be the segment. - * Otherwise the query will be the Envelope2D bounding the Geometry. - * \param query The Geometry used for the query. - * \param tolerance The tolerance used for the intersection tests. - */ - public void resetIterator(Geometry query, double tolerance) { - if (!m_b_sorted) - ((QuadTreeImpl.QuadTreeIteratorImpl) m_impl).resetIterator(query, tolerance); - else - ((QuadTreeImpl.QuadTreeSortedIteratorImpl) m_impl).resetIterator(query, tolerance); - } - - /** - * Resets the iterator to a starting state on the QuadTree using the - * input Envelope2D as the query. - * \param query The Envelope2D used for the query. - * \param tolerance The tolerance used for the intersection - * tests. - */ - public void resetIterator(Envelope2D query, double tolerance) { - if (!m_b_sorted) - ((QuadTreeImpl.QuadTreeIteratorImpl) m_impl).resetIterator(query, tolerance); - else - ((QuadTreeImpl.QuadTreeSortedIteratorImpl) m_impl).resetIterator(query, tolerance); - } - - /** - * Moves the iterator to the next Element_handle and returns the - * Element_handle. - */ - public int next() { - if (!m_b_sorted) - return ((QuadTreeImpl.QuadTreeIteratorImpl) m_impl).next(); - else - return ((QuadTreeImpl.QuadTreeSortedIteratorImpl) m_impl).next(); - } - - /** - * Returns a void* to the impl class. - */ - Object getImpl_() { - return m_impl; - } - - // Creates an iterator on the input QuadTreeImpl. The query will be - // the Envelope2D bounding the input Geometry. - private QuadTreeIterator(Object obj, boolean bSorted) { - - m_impl = obj; - m_b_sorted = bSorted; - } - - private Object m_impl; - private boolean m_b_sorted; - } - - /** - * Creates a QuadTree with the root having the extent of the input - * Envelope2D, and height of the input height, where the root starts at height 0. - * \param extent The extent of the QuadTree. - * \param height The max height of the QuadTree. - */ - public QuadTree(Envelope2D extent, int height) { - m_impl = new QuadTreeImpl(extent, height); - } - - /** - * Creates a QuadTree with the root having the extent of the input Envelope2D, and height of the input height, where the root starts at height 0. - * \param extent The extent of the QuadTreeImpl. - * \param height The max height of the QuadTreeImpl. - * \param bStoreDuplicates Put true to place elements deeper into the quad tree at intesecting quads, duplicates will be stored. Put false to only place elements into quads that can contain it.. - */ - public QuadTree(Envelope2D extent, int height, boolean bStoreDuplicates) { - m_impl = new QuadTreeImpl(extent, height, bStoreDuplicates); - } - - /** - * Inserts the element and bounding_box into the QuadTree. Note that a copy - * will me made of the input bounding_box. Note that this will invalidate - * any active iterator on the QuadTree. Returns an Element_handle - * corresponding to the element and bounding_box. - * \param element The element of the Geometry to be inserted. - * \param bounding_box The bounding_box of - * the Geometry to be inserted. - */ - public int insert(int element, Envelope2D boundingBox) { - return m_impl.insert(element, boundingBox); - } - - /** - * Inserts the element and bounding_box into the QuadTree at the given - * quad_handle. Note that a copy will me made of the input bounding_box. - * Note that this will invalidate any active iterator on the QuadTree. - * Returns an Element_handle corresponding to the element and bounding_box. - * \param element The element of the Geometry to be inserted. - * \param bounding_box The bounding_box of the Geometry to be inserted. - * \param hint_index A handle used as a hint where to place the element. This can - * be a handle obtained from a previous insertion and is useful on data - * having strong locality such as segments of a Polygon. - */ - public int insert(int element, Envelope2D boundingBox, int hintIndex) { - return m_impl.insert(element, boundingBox, hintIndex); - } - - /** - * Removes the element and bounding_box at the given element_handle. Note - * that this will invalidate any active iterator on the QuadTree. - * \param element_handle The handle corresponding to the element and bounding_box - * to be removed. - */ - public void removeElement(int elementHandle) { - m_impl.removeElement(elementHandle); - } - - /** - * Returns the element at the given element_handle. - * \param element_handle The handle corresponding to the element to be retrieved. - */ - public int getElement(int elementHandle) { - return m_impl.getElement(elementHandle); - } - - /** - * Returns the element extent at the given element_handle. - * \param element_handle The handle corresponding to the element extent to be retrieved. - */ - public Envelope2D getElementExtent(int elementHandle) { - return m_impl.getElementExtent(elementHandle); - } - - /** - * Returns the extent of all elements in the quad tree. - */ - public Envelope2D getDataExtent() { - return m_impl.getDataExtent(); - } - - /** - * Returns the extent of the quad tree. - */ - public Envelope2D getQuadTreeExtent() { - return m_impl.getQuadTreeExtent(); - } - - /** - * Returns the number of elements in the subtree rooted at the given quad_handle. - * \param quad_handle The handle corresponding to the quad. - */ - public int getSubTreeElementCount(int quadHandle) { - return m_impl.getSubTreeElementCount(quadHandle); - } - - /** - * Returns the number of elements contained in the subtree rooted at the given quad_handle. - * \param quad_handle The handle corresponding to the quad. - */ - public int getContainedSubTreeElementCount(int quadHandle) { - return m_impl.getContainedSubTreeElementCount(quadHandle); - } - - /** - * Returns the number of elements in the quad tree that intersect the qiven query. Some elements may be duplicated if the quad tree stores duplicates. - * \param query The Envelope2D used for the query. - * \param tolerance The tolerance used for the intersection tests. - * \param max_count If the intersection count becomes greater than or equal to the max_count, then max_count is returned. - */ - public int getIntersectionCount(Envelope2D query, double tolerance, int maxCount) { - return m_impl.getIntersectionCount(query, tolerance, maxCount); - } - - /** - * Returns true if the quad tree has data intersecting the given query. - * \param query The Envelope2D used for the query. - * \param tolerance The tolerance used for the intersection tests. - */ - public boolean hasData(Envelope2D query, double tolerance) { - return m_impl.hasData(query, tolerance); - } - - /** - * Returns the height of the quad at the given quad_handle. \param - * quad_handle The handle corresponding to the quad. - */ - public int getHeight(int quadHandle) { - return m_impl.getHeight(quadHandle); - } - - /** - * Returns the max height the quad tree can grow to. - */ - public int getMaxHeight() { - return m_impl.getMaxHeight(); - } - - /** - * Returns the extent of the quad at the given quad_handle. - * \param quad_handle The handle corresponding to the quad. - */ - public Envelope2D getExtent(int quadHandle) { - return m_impl.getExtent(quadHandle); - } - - /** - * Returns the Quad_handle of the quad containing the given element_handle. - * \param element_handle The handle corresponding to the element. - */ - public int getQuad(int elementHandle) { - return m_impl.getQuad(elementHandle); - } - - /** - * Returns the number of elements in the QuadTree. - */ - public int getElementCount() { - return m_impl.getElementCount(); - } - - /** - * Gets an iterator on the QuadTree. The query will be the Envelope2D that - * bounds the input Geometry. To reuse the existing iterator on the same - * QuadTree but with a new query, use the reset_iterator function on the - * QuadTree_iterator. - * \param query The Geometry used for the query. If the - * Geometry is a Line segment, then the query will be the segment. Otherwise - * the query will be the Envelope2D bounding the Geometry. - * \param tolerance The tolerance used for the intersection tests. - */ - public QuadTreeIterator getIterator(Geometry query, double tolerance) { - QuadTreeImpl.QuadTreeIteratorImpl iterator = m_impl.getIterator(query, tolerance); - return new QuadTreeIterator(iterator, false); - } - - /** - * Gets an iterator on the QuadTree using the input Envelope2D as the - * query. To reuse the existing iterator on the same QuadTree but with a - * new query, use the reset_iterator function on the QuadTree_iterator. - * \param query The Envelope2D used for the query. - * \param tolerance The tolerance used for the intersection tests. - */ - public QuadTreeIterator getIterator(Envelope2D query, double tolerance) { - QuadTreeImpl.QuadTreeIteratorImpl iterator = m_impl.getIterator(query, tolerance); - return new QuadTreeIterator(iterator, false); - } - - /** - * Gets an iterator on the QuadTree. - */ - public QuadTreeIterator getIterator() { - QuadTreeImpl.QuadTreeIteratorImpl iterator = m_impl.getIterator(); - return new QuadTreeIterator(iterator, false); - } - - /** - * Gets an iterator on the QuadTree. The query will be the Envelope2D that bounds the input Geometry. - * To reuse the existing iterator on the same QuadTree but with a new query, use the reset_iterator function on the QuadTree_iterator. - * \param query The Geometry used for the query. If the Geometry is a Line segment, then the query will be the segment. Otherwise the query will be the Envelope2D bounding the Geometry. - * \param tolerance The tolerance used for the intersection tests. - * \param bSorted Put true to iterate the quad tree in the order of the Element_types. - */ - public QuadTreeIterator getIterator(Geometry query, double tolerance, boolean bSorted) { - if (!bSorted) { - QuadTreeImpl.QuadTreeIteratorImpl iterator = m_impl.getIterator(query, tolerance); - return new QuadTreeIterator(iterator, false); - } else { - QuadTreeImpl.QuadTreeSortedIteratorImpl iterator = m_impl.getSortedIterator(query, tolerance); - return new QuadTreeIterator(iterator, true); - } - } - - /** - * Gets an iterator on the QuadTree using the input Envelope2D as the query. - * To reuse the existing iterator on the same QuadTree but with a new query, use the reset_iterator function on the QuadTree_iterator. - * \param query The Envelope2D used for the query. - * \param tolerance The tolerance used for the intersection tests. - * \param bSorted Put true to iterate the quad tree in the order of the Element_types. - */ - public QuadTreeIterator getIterator(Envelope2D query, double tolerance, boolean bSorted) { - if (!bSorted) { - QuadTreeImpl.QuadTreeIteratorImpl iterator = m_impl.getIterator(query, tolerance); - return new QuadTreeIterator(iterator, false); - } else { - QuadTreeImpl.QuadTreeSortedIteratorImpl iterator = m_impl.getSortedIterator(query, tolerance); - return new QuadTreeIterator(iterator, true); - } - } - - /** - * Gets an iterator on the QuadTree. - * \param bSorted Put true to iterate the quad tree in the order of the Element_types. - */ - public QuadTreeIterator getIterator(boolean bSorted) { - if (!bSorted) { - QuadTreeImpl.QuadTreeIteratorImpl iterator = m_impl.getIterator(); - return new QuadTreeIterator(iterator, false); - } else { - QuadTreeImpl.QuadTreeSortedIteratorImpl iterator = m_impl.getSortedIterator(); - return new QuadTreeIterator(iterator, true); - } - } - - /** - * Returns a void* to the impl class. - */ - Object getImpl_() { - return m_impl; - } - - private QuadTreeImpl m_impl; + public static final class QuadTreeIterator { + /** + * Resets the iterator to an starting state on the QuadTree. If the + * input Geometry is a Line segment, then the query will be the segment. + * Otherwise the query will be the Envelope2D bounding the Geometry. + * \param query The Geometry used for the query. + * \param tolerance The tolerance used for the intersection tests. + */ + public void resetIterator(Geometry query, double tolerance) { + if (!m_b_sorted) + ((QuadTreeImpl.QuadTreeIteratorImpl) m_impl).resetIterator(query, tolerance); + else + ((QuadTreeImpl.QuadTreeSortedIteratorImpl) m_impl).resetIterator(query, tolerance); + } + + /** + * Resets the iterator to a starting state on the QuadTree using the + * input Envelope2D as the query. + * \param query The Envelope2D used for the query. + * \param tolerance The tolerance used for the intersection + * tests. + */ + public void resetIterator(Envelope2D query, double tolerance) { + if (!m_b_sorted) + ((QuadTreeImpl.QuadTreeIteratorImpl) m_impl).resetIterator(query, tolerance); + else + ((QuadTreeImpl.QuadTreeSortedIteratorImpl) m_impl).resetIterator(query, tolerance); + } + + /** + * Moves the iterator to the next Element_handle and returns the + * Element_handle. + */ + public int next() { + if (!m_b_sorted) + return ((QuadTreeImpl.QuadTreeIteratorImpl) m_impl).next(); + else + return ((QuadTreeImpl.QuadTreeSortedIteratorImpl) m_impl).next(); + } + + /** + * Returns a void* to the impl class. + */ + Object getImpl_() { + return m_impl; + } + + // Creates an iterator on the input QuadTreeImpl. The query will be + // the Envelope2D bounding the input Geometry. + private QuadTreeIterator(Object obj, boolean bSorted) { + + m_impl = obj; + m_b_sorted = bSorted; + } + + private Object m_impl; + private boolean m_b_sorted; + } + + /** + * Creates a QuadTree with the root having the extent of the input + * Envelope2D, and height of the input height, where the root starts at height 0. + * \param extent The extent of the QuadTree. + * \param height The max height of the QuadTree. + */ + public QuadTree(Envelope2D extent, int height) { + m_impl = new QuadTreeImpl(extent, height); + } + + /** + * Creates a QuadTree with the root having the extent of the input Envelope2D, and height of the input height, where the root starts at height 0. + * \param extent The extent of the QuadTreeImpl. + * \param height The max height of the QuadTreeImpl. + * \param bStoreDuplicates Put true to place elements deeper into the quad tree at intesecting quads, duplicates will be stored. Put false to only place elements into quads that can contain it.. + */ + public QuadTree(Envelope2D extent, int height, boolean bStoreDuplicates) { + m_impl = new QuadTreeImpl(extent, height, bStoreDuplicates); + } + + /** + * Inserts the element and bounding_box into the QuadTree. Note that a copy + * will me made of the input bounding_box. Note that this will invalidate + * any active iterator on the QuadTree. Returns an Element_handle + * corresponding to the element and bounding_box. + * \param element The element of the Geometry to be inserted. + * \param bounding_box The bounding_box of + * the Geometry to be inserted. + */ + public int insert(int element, Envelope2D boundingBox) { + return m_impl.insert(element, boundingBox); + } + + /** + * Inserts the element and bounding_box into the QuadTree at the given + * quad_handle. Note that a copy will me made of the input bounding_box. + * Note that this will invalidate any active iterator on the QuadTree. + * Returns an Element_handle corresponding to the element and bounding_box. + * \param element The element of the Geometry to be inserted. + * \param bounding_box The bounding_box of the Geometry to be inserted. + * \param hint_index A handle used as a hint where to place the element. This can + * be a handle obtained from a previous insertion and is useful on data + * having strong locality such as segments of a Polygon. + */ + public int insert(int element, Envelope2D boundingBox, int hintIndex) { + return m_impl.insert(element, boundingBox, hintIndex); + } + + /** + * Removes the element and bounding_box at the given element_handle. Note + * that this will invalidate any active iterator on the QuadTree. + * \param element_handle The handle corresponding to the element and bounding_box + * to be removed. + */ + public void removeElement(int elementHandle) { + m_impl.removeElement(elementHandle); + } + + /** + * Returns the element at the given element_handle. + * \param element_handle The handle corresponding to the element to be retrieved. + */ + public int getElement(int elementHandle) { + return m_impl.getElement(elementHandle); + } + + /** + * Returns the element extent at the given element_handle. + * \param element_handle The handle corresponding to the element extent to be retrieved. + */ + public Envelope2D getElementExtent(int elementHandle) { + return m_impl.getElementExtent(elementHandle); + } + + /** + * Returns the extent of all elements in the quad tree. + */ + public Envelope2D getDataExtent() { + return m_impl.getDataExtent(); + } + + /** + * Returns the extent of the quad tree. + */ + public Envelope2D getQuadTreeExtent() { + return m_impl.getQuadTreeExtent(); + } + + /** + * Returns the number of elements in the subtree rooted at the given quad_handle. + * \param quad_handle The handle corresponding to the quad. + */ + public int getSubTreeElementCount(int quadHandle) { + return m_impl.getSubTreeElementCount(quadHandle); + } + + /** + * Returns the number of elements contained in the subtree rooted at the given quad_handle. + * \param quad_handle The handle corresponding to the quad. + */ + public int getContainedSubTreeElementCount(int quadHandle) { + return m_impl.getContainedSubTreeElementCount(quadHandle); + } + + /** + * Returns the number of elements in the quad tree that intersect the qiven query. Some elements may be duplicated if the quad tree stores duplicates. + * \param query The Envelope2D used for the query. + * \param tolerance The tolerance used for the intersection tests. + * \param max_count If the intersection count becomes greater than or equal to the max_count, then max_count is returned. + */ + public int getIntersectionCount(Envelope2D query, double tolerance, int maxCount) { + return m_impl.getIntersectionCount(query, tolerance, maxCount); + } + + /** + * Returns true if the quad tree has data intersecting the given query. + * \param query The Envelope2D used for the query. + * \param tolerance The tolerance used for the intersection tests. + */ + public boolean hasData(Envelope2D query, double tolerance) { + return m_impl.hasData(query, tolerance); + } + + /** + * Returns the height of the quad at the given quad_handle. \param + * quad_handle The handle corresponding to the quad. + */ + public int getHeight(int quadHandle) { + return m_impl.getHeight(quadHandle); + } + + /** + * Returns the max height the quad tree can grow to. + */ + public int getMaxHeight() { + return m_impl.getMaxHeight(); + } + + /** + * Returns the extent of the quad at the given quad_handle. + * \param quad_handle The handle corresponding to the quad. + */ + public Envelope2D getExtent(int quadHandle) { + return m_impl.getExtent(quadHandle); + } + + /** + * Returns the Quad_handle of the quad containing the given element_handle. + * \param element_handle The handle corresponding to the element. + */ + public int getQuad(int elementHandle) { + return m_impl.getQuad(elementHandle); + } + + /** + * Returns the number of elements in the QuadTree. + */ + public int getElementCount() { + return m_impl.getElementCount(); + } + + /** + * Gets an iterator on the QuadTree. The query will be the Envelope2D that + * bounds the input Geometry. To reuse the existing iterator on the same + * QuadTree but with a new query, use the reset_iterator function on the + * QuadTree_iterator. + * \param query The Geometry used for the query. If the + * Geometry is a Line segment, then the query will be the segment. Otherwise + * the query will be the Envelope2D bounding the Geometry. + * \param tolerance The tolerance used for the intersection tests. + */ + public QuadTreeIterator getIterator(Geometry query, double tolerance) { + QuadTreeImpl.QuadTreeIteratorImpl iterator = m_impl.getIterator(query, tolerance); + return new QuadTreeIterator(iterator, false); + } + + /** + * Gets an iterator on the QuadTree using the input Envelope2D as the + * query. To reuse the existing iterator on the same QuadTree but with a + * new query, use the reset_iterator function on the QuadTree_iterator. + * \param query The Envelope2D used for the query. + * \param tolerance The tolerance used for the intersection tests. + */ + public QuadTreeIterator getIterator(Envelope2D query, double tolerance) { + QuadTreeImpl.QuadTreeIteratorImpl iterator = m_impl.getIterator(query, tolerance); + return new QuadTreeIterator(iterator, false); + } + + /** + * Gets an iterator on the QuadTree. + */ + public QuadTreeIterator getIterator() { + QuadTreeImpl.QuadTreeIteratorImpl iterator = m_impl.getIterator(); + return new QuadTreeIterator(iterator, false); + } + + /** + * Gets an iterator on the QuadTree. The query will be the Envelope2D that bounds the input Geometry. + * To reuse the existing iterator on the same QuadTree but with a new query, use the reset_iterator function on the QuadTree_iterator. + * \param query The Geometry used for the query. If the Geometry is a Line segment, then the query will be the segment. Otherwise the query will be the Envelope2D bounding the Geometry. + * \param tolerance The tolerance used for the intersection tests. + * \param bSorted Put true to iterate the quad tree in the order of the Element_types. + */ + public QuadTreeIterator getIterator(Geometry query, double tolerance, boolean bSorted) { + if (!bSorted) { + QuadTreeImpl.QuadTreeIteratorImpl iterator = m_impl.getIterator(query, tolerance); + return new QuadTreeIterator(iterator, false); + } else { + QuadTreeImpl.QuadTreeSortedIteratorImpl iterator = m_impl.getSortedIterator(query, tolerance); + return new QuadTreeIterator(iterator, true); + } + } + + /** + * Gets an iterator on the QuadTree using the input Envelope2D as the query. + * To reuse the existing iterator on the same QuadTree but with a new query, use the reset_iterator function on the QuadTree_iterator. + * \param query The Envelope2D used for the query. + * \param tolerance The tolerance used for the intersection tests. + * \param bSorted Put true to iterate the quad tree in the order of the Element_types. + */ + public QuadTreeIterator getIterator(Envelope2D query, double tolerance, boolean bSorted) { + if (!bSorted) { + QuadTreeImpl.QuadTreeIteratorImpl iterator = m_impl.getIterator(query, tolerance); + return new QuadTreeIterator(iterator, false); + } else { + QuadTreeImpl.QuadTreeSortedIteratorImpl iterator = m_impl.getSortedIterator(query, tolerance); + return new QuadTreeIterator(iterator, true); + } + } + + /** + * Gets an iterator on the QuadTree. + * \param bSorted Put true to iterate the quad tree in the order of the Element_types. + */ + public QuadTreeIterator getIterator(boolean bSorted) { + if (!bSorted) { + QuadTreeImpl.QuadTreeIteratorImpl iterator = m_impl.getIterator(); + return new QuadTreeIterator(iterator, false); + } else { + QuadTreeImpl.QuadTreeSortedIteratorImpl iterator = m_impl.getSortedIterator(); + return new QuadTreeIterator(iterator, true); + } + } + + /** + * Returns a void* to the impl class. + */ + Object getImpl_() { + return m_impl; + } + + private QuadTreeImpl m_impl; } diff --git a/src/main/java/com/esri/core/geometry/QuadTreeImpl.java b/src/main/java/com/esri/core/geometry/QuadTreeImpl.java index 092f6bbe..714ab4cb 100644 --- a/src/main/java/com/esri/core/geometry/QuadTreeImpl.java +++ b/src/main/java/com/esri/core/geometry/QuadTreeImpl.java @@ -26,1272 +26,1272 @@ import java.util.ArrayList; class QuadTreeImpl { - static final class QuadTreeIteratorImpl { - /** - * Resets the iterator to an starting state on the Quad_tree_impl. If - * the input Geometry is a Line segment, then the query will be the - * segment. Otherwise the query will be the Envelope_2D bounding the - * Geometry. \param query The Geometry used for the query. \param - * tolerance The tolerance used for the intersection tests. \param - * tolerance The tolerance used for the intersection tests. - */ - void resetIterator(Geometry query, double tolerance) { - m_quads_stack.resize(0); - m_extents_stack.clear(); - m_current_element_handle = -1; - query.queryLooseEnvelope2D(m_query_box); - m_query_box.inflate(tolerance, tolerance); - - if (m_quad_tree.m_root != -1 && m_query_box.isIntersecting(m_quad_tree.m_extent)) { - int type = query.getType().value(); - m_b_linear = Geometry.isSegment(type); - - if (m_b_linear) { - Segment segment = (Segment) query; - m_query_start = segment.getStartXY(); - m_query_end = segment.getEndXY(); - m_tolerance = tolerance; - } else { - m_tolerance = NumberUtils.NaN(); // we don't need it - } - - m_quads_stack.add(m_quad_tree.m_root); - m_extents_stack.add(m_quad_tree.m_extent); - m_next_element_handle = m_quad_tree.get_first_element_(m_quad_tree.m_root); - } else - m_next_element_handle = -1; - } - - /** - * Resets the iterator to a starting state on the Quad_tree_impl using - * the input Envelope_2D as the query. \param query The Envelope_2D used - * for the query. \param tolerance The tolerance used for the - * intersection tests. - */ - void resetIterator(Envelope2D query, double tolerance) { - m_quads_stack.resize(0); - m_extents_stack.clear(); - m_current_element_handle = -1; - m_query_box.setCoords(query); - m_query_box.inflate(tolerance, tolerance); - m_tolerance = NumberUtils.NaN(); // we don't need it - - if (m_quad_tree.m_root != -1 && m_query_box.isIntersecting(m_quad_tree.m_extent)) { - m_quads_stack.add(m_quad_tree.m_root); - m_extents_stack.add(m_quad_tree.m_extent); - m_next_element_handle = m_quad_tree.get_first_element_(m_quad_tree.m_root); - m_b_linear = false; - } else - m_next_element_handle = -1; - } - - /** - * Moves the iterator to the next int and returns the int. - */ - int next() { - // If the node stack is empty, then we've exhausted our search - - if (m_quads_stack.size() == 0) - return -1; - - m_current_element_handle = m_next_element_handle; - - Point2D start = null; - Point2D end = null; - Envelope2D bounding_box; - Envelope2D extent_inf = null; - Envelope2D[] child_extents = null; - - if (m_b_linear) { - start = new Point2D(); - end = new Point2D(); - extent_inf = new Envelope2D(); - } - - boolean b_found_hit = false; - while (!b_found_hit) { - while (m_current_element_handle != -1) { - int current_data_handle = m_quad_tree.get_data_(m_current_element_handle); - bounding_box = m_quad_tree.get_bounding_box_value_(current_data_handle); - - if (bounding_box.isIntersecting(m_query_box)) { - if (m_b_linear) { - start.setCoords(m_query_start); - end.setCoords(m_query_end); - extent_inf.setCoords(bounding_box); - - extent_inf.inflate(m_tolerance, m_tolerance); - if (extent_inf.clipLine(start, end) > 0) { - b_found_hit = true; - break; - } - } else { - b_found_hit = true; - break; - } - } - - // get next element_handle - m_current_element_handle = m_quad_tree.get_next_element_(m_current_element_handle); - } - - // If m_current_element_handle equals -1, then we've exhausted our search in the current quadtree node - if (m_current_element_handle == -1) { - // get the last node from the stack and add the children whose extent intersects m_query_box - int current_quad = m_quads_stack.getLast(); - Envelope2D current_extent = m_extents_stack.get(m_extents_stack.size() - 1); - - if (child_extents == null) { - child_extents = new Envelope2D[4]; - child_extents[0] = new Envelope2D(); - child_extents[1] = new Envelope2D(); - child_extents[2] = new Envelope2D(); - child_extents[3] = new Envelope2D(); - } - - set_child_extents_(current_extent, child_extents); - m_quads_stack.removeLast(); - m_extents_stack.remove(m_extents_stack.size() - 1); - - for (int quadrant = 0; quadrant < 4; quadrant++) { - int child_handle = m_quad_tree.get_child_(current_quad, quadrant); - - if (child_handle != -1 && m_quad_tree.getSubTreeElementCount(child_handle) > 0) { - if (child_extents[quadrant].isIntersecting(m_query_box)) { - if (m_b_linear) { - start.setCoords(m_query_start); - end.setCoords(m_query_end); - - extent_inf.setCoords(child_extents[quadrant]); - extent_inf.inflate(m_tolerance, m_tolerance); - if (extent_inf.clipLine(start, end) > 0) { - Envelope2D child_extent = new Envelope2D(); - child_extent.setCoords(child_extents[quadrant]); - m_quads_stack.add(child_handle); - m_extents_stack.add(child_extent); - } - } else { - Envelope2D child_extent = new Envelope2D(); - child_extent.setCoords(child_extents[quadrant]); - m_quads_stack.add(child_handle); - m_extents_stack.add(child_extent); - } - } - } - } - - assert (m_quads_stack.size() <= 4 * (m_quad_tree.m_height - 1)); - - if (m_quads_stack.size() == 0) - return -1; - - m_current_element_handle = m_quad_tree.get_first_element_(m_quads_stack.get(m_quads_stack.size() - 1)); - } - } - - // We did not exhaust our search in the current node, so we return - // the element at m_current_element_handle in m_element_nodes - - m_next_element_handle = m_quad_tree.get_next_element_(m_current_element_handle); - return m_current_element_handle; - } - - // Creates an iterator on the input Quad_tree_impl. The query will be - // the Envelope_2D bounding the input Geometry. - QuadTreeIteratorImpl(QuadTreeImpl quad_tree_impl, Geometry query, double tolerance) { - m_quad_tree = quad_tree_impl; - m_query_box = new Envelope2D(); - m_quads_stack = new AttributeStreamOfInt32(0); - m_extents_stack = new ArrayList(0); - resetIterator(query, tolerance); - } - - // Creates an iterator on the input Quad_tree_impl using the input - // Envelope_2D as the query. - QuadTreeIteratorImpl(QuadTreeImpl quad_tree_impl, Envelope2D query, double tolerance) { - m_quad_tree = quad_tree_impl; - m_query_box = new Envelope2D(); - m_quads_stack = new AttributeStreamOfInt32(0); - m_extents_stack = new ArrayList(0); - resetIterator(query, tolerance); - } - - // Creates an iterator on the input Quad_tree_impl. - QuadTreeIteratorImpl(QuadTreeImpl quad_tree_impl) { - m_quad_tree = quad_tree_impl; - m_query_box = new Envelope2D(); - m_quads_stack = new AttributeStreamOfInt32(0); - m_extents_stack = new ArrayList(0); - } - - private boolean m_b_linear; - private Point2D m_query_start; - private Point2D m_query_end; - private Envelope2D m_query_box; - private double m_tolerance; - private int m_current_element_handle; - private int m_next_element_handle; - private QuadTreeImpl m_quad_tree; - private AttributeStreamOfInt32 m_quads_stack; - private ArrayList m_extents_stack; // this won't grow bigger than 4 * (m_quad_tree->m_height - 1) - } - - static final class QuadTreeSortedIteratorImpl { - /** - * Resets the iterator to a starting state on the Quad_tree_impl. If the input Geometry is a Line segment, then the query will be the segment. Otherwise the query will be the Envelope_2D bounding the Geometry. - * \param query The Geometry used for the query. - * \param tolerance The tolerance used for the intersection tests. - * \param tolerance The tolerance used for the intersection tests. - */ - void resetIterator(Geometry query, double tolerance) { - m_quad_tree_iterator_impl.resetIterator(query, tolerance); - m_sorted_handles.resize(0); - m_index = -1; - } - - /** - * Resets the iterator to a starting state on the Quad_tree_impl using the input Envelope_2D as the query. - * \param query The Envelope_2D used for the query. - * \param tolerance The tolerance used for the intersection tests. - */ - void resetIterator(Envelope2D query, double tolerance) { - m_quad_tree_iterator_impl.resetIterator(query, tolerance); - m_sorted_handles.resize(0); - m_index = -1; - } - - /** - * Moves the iterator to the next Element_handle and returns the Element_handle. - */ - int next() { - if (m_index == -1) { - int element_handle = -1; - while ((element_handle = m_quad_tree_iterator_impl.next()) != -1) - m_sorted_handles.add(element_handle); - - m_bucket_sort.sort(m_sorted_handles, 0, m_sorted_handles.size(), new Sorter(m_quad_tree_iterator_impl.m_quad_tree)); - } - - if (m_index == m_sorted_handles.size() - 1) - return -1; - - m_index++; - return m_sorted_handles.get(m_index); - } - - //Creates a sorted iterator on the input Quad_tree_iterator_impl - QuadTreeSortedIteratorImpl(QuadTreeIteratorImpl quad_tree_iterator_impl) { - m_bucket_sort = new BucketSort(); - m_sorted_handles = new AttributeStreamOfInt32(0); - m_quad_tree_iterator_impl = quad_tree_iterator_impl; - m_index = -1; - } - - private class Sorter extends ClassicSort { - public Sorter(QuadTreeImpl quad_tree) { - m_quad_tree = quad_tree; - } - - @Override - public void userSort(int begin, int end, AttributeStreamOfInt32 indices) { - indices.sort(begin, end); - } - - @Override - public double getValue(int e) { - return m_quad_tree.getElement(e); - } - - private QuadTreeImpl m_quad_tree; - } - - private BucketSort m_bucket_sort; - private AttributeStreamOfInt32 m_sorted_handles; - private QuadTreeIteratorImpl m_quad_tree_iterator_impl; - int m_index; - } - - /** - * Creates a Quad_tree_impl with the root having the extent of the input Envelope_2D, and height of the input height, where the root starts at height 0. - * \param extent The extent of the Quad_tree_impl. - * \param height The max height of the Quad_tree_impl. - */ - QuadTreeImpl(Envelope2D extent, int height) { - m_quad_tree_nodes = new StridedIndexTypeCollection(10); - m_element_nodes = new StridedIndexTypeCollection(4); - m_data = new ArrayList(0); - m_free_data = new AttributeStreamOfInt32(0); - m_b_store_duplicates = false; - - m_extent = new Envelope2D(); - m_data_extent = new Envelope2D(); - - reset_(extent, height); - } - - /** - * Creates a Quad_tree_impl with the root having the extent of the input Envelope_2D, and height of the input height, where the root starts at height 0. - * \param extent The extent of the Quad_tree_impl. - * \param height The max height of the Quad_tree_impl. - * \param b_store_duplicates Put true to place elements deeper into the quad tree at intesecting quads, duplicates will be stored. Put false to only place elements into quads that can contain it. - */ - QuadTreeImpl(Envelope2D extent, int height, boolean b_store_duplicates) { - m_quad_tree_nodes = (b_store_duplicates ? new StridedIndexTypeCollection(11) : new StridedIndexTypeCollection(10)); - m_element_nodes = new StridedIndexTypeCollection(4); - m_data = new ArrayList(0); - m_free_data = new AttributeStreamOfInt32(0); - m_b_store_duplicates = b_store_duplicates; - - m_extent = new Envelope2D(); - m_data_extent = new Envelope2D(); - - reset_(extent, height); - } - - /** - * Resets the Quad_tree_impl to the given extent and height. - * \param extent The extent of the Quad_tree_impl. - * \param height The max height of the Quad_tree_impl. - */ - void reset(Envelope2D extent, int height) { - m_quad_tree_nodes.deleteAll(false); - m_element_nodes.deleteAll(false); - m_data.clear(); - m_free_data.clear(false); - reset_(extent, height); - } - - /** - * Inserts the element and bounding_box into the Quad_tree_impl. - * Note that this will invalidate any active iterator on the Quad_tree_impl. - * Returns an Element_handle corresponding to the element and bounding_box. - * \param element The element of the Geometry to be inserted. - * \param bounding_box The bounding_box of the Geometry to be inserted. - */ - int insert(int element, Envelope2D bounding_box) { - if (m_root == -1) - create_root_(); - - if (m_b_store_duplicates) { - int success = insert_duplicates_(element, bounding_box, 0, m_extent, m_root, false, -1); - - if (success != -1) { - if (m_data_extent.isEmpty()) - m_data_extent.setCoords(bounding_box); - else - m_data_extent.merge(bounding_box); - } - - return success; - } - - int element_handle = insert_(element, bounding_box, 0, m_extent, m_root, false, -1); - - if (element_handle != -1) { - if (m_data_extent.isEmpty()) - m_data_extent.setCoords(bounding_box); - else - m_data_extent.merge(bounding_box); - } - - return element_handle; - } - - /** - * Inserts the element and bounding_box into the Quad_tree_impl at the given quad_handle. - * Note that this will invalidate any active iterator on the Quad_tree_impl. - * Returns an Element_handle corresponding to the element and bounding_box. - * \param element The element of the Geometry to be inserted. - * \param bounding_box The bounding_box of the Geometry to be inserted. - * \param hint_index A handle used as a hint where to place the element. This can be a handle obtained from a previous insertion and is useful on data having strong locality such as segments of a Polygon. - */ - int insert(int element, Envelope2D bounding_box, int hint_index) { - if (m_root == -1) - create_root_(); - - if (m_b_store_duplicates) { - int success = insert_duplicates_(element, bounding_box, 0, m_extent, m_root, false, -1); - - if (success != -1) { - if (m_data_extent.isEmpty()) - m_data_extent.setCoords(bounding_box); - else - m_data_extent.merge(bounding_box); - } - return success; - } - - int quad_handle; - - if (hint_index == -1) - quad_handle = m_root; - else - quad_handle = get_quad_(hint_index); - - int quad_height = getHeight(quad_handle); - Envelope2D quad_extent = getExtent(quad_handle); - - int element_handle = insert_(element, bounding_box, quad_height, quad_extent, quad_handle, false, -1); - - if (element_handle != -1) { - if (m_data_extent.isEmpty()) - m_data_extent.setCoords(bounding_box); - else - m_data_extent.merge(bounding_box); - } - - return element_handle; - } - - /** - * Removes the element and bounding_box at the given element_handle. - * Note that this will invalidate any active iterator on the Quad_tree_impl. - * \param element_handle The handle corresponding to the element and bounding_box to be removed. - */ - void removeElement(int element_handle) { - if (m_b_store_duplicates) - throw new GeometryException("invalid call"); - - int quad_handle = get_quad_(element_handle); - disconnect_element_handle_(element_handle); - free_element_and_box_node_(element_handle); - - int q = quad_handle; - - while (q != -1) { - set_sub_tree_element_count_(q, get_sub_tree_element_count_(q) - 1); - int parent = get_parent_(q); - - if (get_sub_tree_element_count_(q) == 0) { - assert (get_local_element_count_(q) == 0); - - if (q != m_root) { - int quadrant = get_quadrant_(q); - m_quad_tree_nodes.deleteElement(q); - set_child_(parent, quadrant, -1); - } - } - - q = parent; - } - } - - /** - * Returns the element at the given element_handle. - * \param element_handle The handle corresponding to the element to be retrieved. - */ - int getElement(int element_handle) { - return get_element_value_(get_data_(element_handle)); - } - - /** - * Returns the ith unique element. - * \param i The index corresponding to the ith unique element. - */ - int getElementAtIndex(int i) { - return m_data.get(i).element; - } - - /** - * Returns the element extent at the given element_handle. - * \param element_handle The handle corresponding to the element extent to be retrieved. - */ - Envelope2D getElementExtent(int element_handle) { - int data_handle = get_data_(element_handle); - return get_bounding_box_value_(data_handle); - } - - /** - * Returns the extent of the ith unique element. - * \param i The index corresponding to the ith unique element. - */ - Envelope2D getElementExtentAtIndex(int i) { - return m_data.get(i).box; - } - - /** - * Returns the extent of all elements in the quad tree. - */ - Envelope2D getDataExtent() { - return m_data_extent; - } - - /** - * Returns the extent of the quad tree. - */ - Envelope2D getQuadTreeExtent() { - return m_extent; - } - - /** - * Returns the height of the quad at the given quad_handle. - * \param quad_handle The handle corresponding to the quad. - */ - int getHeight(int quad_handle) { - return get_height_(quad_handle); - } - - int getMaxHeight() { - return m_height; - } - - /** - * Returns the extent of the quad at the given quad_handle. - * \param quad_handle The handle corresponding to the quad. - */ - Envelope2D getExtent(int quad_handle) { - Envelope2D quad_extent = new Envelope2D(); - quad_extent.setCoords(m_extent); - - if (quad_handle == m_root) - return quad_extent; - - AttributeStreamOfInt32 quadrants = new AttributeStreamOfInt32(0); - - int q = quad_handle; - - do { - quadrants.add(get_quadrant_(q)); - q = get_parent_(q); - - } while (q != m_root); - - int sz = quadrants.size(); - assert (sz == getHeight(quad_handle)); - - for (int i = 0; i < sz; i++) { - int child = quadrants.getLast(); - quadrants.removeLast(); - - if (child == 0) {//northeast - quad_extent.xmin = 0.5 * (quad_extent.xmin + quad_extent.xmax); - quad_extent.ymin = 0.5 * (quad_extent.ymin + quad_extent.ymax); - } else if (child == 1) {//northwest - quad_extent.xmax = 0.5 * (quad_extent.xmin + quad_extent.xmax); - quad_extent.ymin = 0.5 * (quad_extent.ymin + quad_extent.ymax); - } else if (child == 2) {//southwest - quad_extent.xmax = 0.5 * (quad_extent.xmin + quad_extent.xmax); - quad_extent.ymax = 0.5 * (quad_extent.ymin + quad_extent.ymax); - } else {//southeast - quad_extent.xmin = 0.5 * (quad_extent.xmin + quad_extent.xmax); - quad_extent.ymax = 0.5 * (quad_extent.ymin + quad_extent.ymax); - } - } - - return quad_extent; - } - - /** - * Returns the Quad_handle of the quad containing the given element_handle. - * \param element_handle The handle corresponding to the element. - */ - int getQuad(int element_handle) { - return get_quad_(element_handle); - } - - /** - * Returns the number of elements in the Quad_tree_impl. - */ - int getElementCount() { - if (m_root == -1) - return 0; - - assert (get_sub_tree_element_count_(m_root) == m_data.size()); - return get_sub_tree_element_count_(m_root); - } - - /** - * Returns the number of elements in the subtree rooted at the given quad_handle. - * \param quad_handle The handle corresponding to the quad. - */ - int getSubTreeElementCount(int quad_handle) { - return get_sub_tree_element_count_(quad_handle); - } - - /** - * Returns the number of elements contained in the subtree rooted at the given quad_handle. - * \param quad_handle The handle corresponding to the quad. - */ - int getContainedSubTreeElementCount(int quad_handle) { - if (!m_b_store_duplicates) - return get_sub_tree_element_count_(quad_handle); - - return get_contained_sub_tree_element_count_(quad_handle); - } - - /** - * Returns the number of elements in the quad tree that intersect the qiven query. Some elements may be duplicated if the quad tree stores duplicates. - * \param query The Envelope_2D used for the query. - * \param tolerance The tolerance used for the intersection tests. - * \param max_count If the intersection count becomes greater than or equal to the max_count, then max_count is returned. - */ - int getIntersectionCount(Envelope2D query, double tolerance, int max_count) { - if (m_root == -1) - return 0; - - Envelope2D query_inflated = new Envelope2D(); - query_inflated.setCoords(query); - query_inflated.inflate(tolerance, tolerance); - - AttributeStreamOfInt32 quads_stack = new AttributeStreamOfInt32(0); - ArrayList extents_stack = new ArrayList(0); - quads_stack.add(m_root); - extents_stack.add(new Envelope2D(m_extent.xmin, m_extent.ymin, m_extent.xmax, m_extent.ymax)); - - Envelope2D[] child_extents = new Envelope2D[4]; - child_extents[0] = new Envelope2D(); - child_extents[1] = new Envelope2D(); - child_extents[2] = new Envelope2D(); - child_extents[3] = new Envelope2D(); - - Envelope2D current_extent = new Envelope2D(); - - int intersection_count = 0; - - while (quads_stack.size() > 0) { - boolean b_subdivide = false; - - int current_quad_handle = quads_stack.getLast(); - current_extent.setCoords(extents_stack.get(extents_stack.size() - 1)); - - quads_stack.removeLast(); - extents_stack.remove(extents_stack.size() - 1); - - - if (query_inflated.contains(current_extent)) { - intersection_count += getSubTreeElementCount(current_quad_handle); - - if (max_count > 0 && intersection_count >= max_count) - return max_count; - } else { - if (query_inflated.isIntersecting(current_extent)) { - for (int element_handle = get_first_element_(current_quad_handle); element_handle != -1; element_handle = get_next_element_(element_handle)) { - int data_handle = get_data_(element_handle); - Envelope2D env = get_bounding_box_value_(data_handle); - - if (env.isIntersecting(query_inflated)) { - intersection_count++; - - if (max_count > 0 && intersection_count >= max_count) - return max_count; - } - } - - b_subdivide = getHeight(current_quad_handle) + 1 <= m_height; - } - } - - if (b_subdivide) { - set_child_extents_(current_extent, child_extents); - - for (int i = 0; i < 4; i++) { - int child_handle = get_child_(current_quad_handle, i); - - if (child_handle != -1 && getSubTreeElementCount(child_handle) > 0) { - boolean b_is_intersecting = query_inflated.isIntersecting(child_extents[i]); - - if (b_is_intersecting) { - quads_stack.add(child_handle); - extents_stack.add(new Envelope2D(child_extents[i].xmin, child_extents[i].ymin, child_extents[i].xmax, child_extents[i].ymax)); - } - } - } - } - } - - return intersection_count; - } - - /** - * Returns true if the quad tree has data intersecting the given query. - * \param query The Envelope_2D used for the query. - * \param tolerance The tolerance used for the intersection tests. - */ - boolean hasData(Envelope2D query, double tolerance) { - int count = getIntersectionCount(query, tolerance, 1); - return count >= 1; - } - - /** - * Gets an iterator on the Quad_tree_impl. The query will be the Envelope_2D - * that bounds the input Geometry. To reuse the existing iterator on the - * same Quad_tree_impl but with a new query, use the reset_iterator function - * on the Quad_tree_iterator_impl. \param query The Geometry used for the - * query. If the Geometry is a Line segment, then the query will be the - * segment. Otherwise the query will be the Envelope_2D bounding the - * Geometry. \param tolerance The tolerance used for the intersection tests. - */ - QuadTreeIteratorImpl getIterator(Geometry query, double tolerance) { - return new QuadTreeIteratorImpl(this, query, tolerance); - } - - /** - * Gets an iterator on the Quad_tree_impl using the input Envelope_2D as the - * query. To reuse the existing iterator on the same Quad_tree_impl but with - * a new query, use the reset_iterator function on the - * Quad_tree_iterator_impl. \param query The Envelope_2D used for the query. - * \param tolerance The tolerance used for the intersection tests. - */ - QuadTreeIteratorImpl getIterator(Envelope2D query, double tolerance) { - return new QuadTreeIteratorImpl(this, query, tolerance); - } - - /** - * Gets an iterator on the Quad_tree. - */ - QuadTreeIteratorImpl getIterator() { - return new QuadTreeIteratorImpl(this); - } - - /** - * Gets a sorted iterator on the Quad_tree_impl. The Element_handles will be returned in increasing order of their corresponding Element_types. - * The query will be the Envelope_2D that bounds the input Geometry. - * To reuse the existing iterator on the same Quad_tree_impl but with a new query, use the reset_iterator function on the Quad_tree_sorted_iterator_impl. - * \param query The Geometry used for the query. If the Geometry is a Line segment, then the query will be the segment. Otherwise the query will be the Envelope_2D bounding the Geometry. - * \param tolerance The tolerance used for the intersection tests. - */ - QuadTreeSortedIteratorImpl getSortedIterator(Geometry query, double tolerance) { - return new QuadTreeSortedIteratorImpl(getIterator(query, tolerance)); - } - - /** - * Gets a sorted iterator on the Quad_tree_impl using the input Envelope_2D as the query. The Element_handles will be returned in increasing order of their corresponding Element_types. - * To reuse the existing iterator on the same Quad_tree_impl but with a new query, use the reset_iterator function on the Quad_tree_iterator_impl. - * \param query The Envelope_2D used for the query. - * \param tolerance The tolerance used for the intersection tests. - */ - QuadTreeSortedIteratorImpl getSortedIterator(Envelope2D query, double tolerance) { - return new QuadTreeSortedIteratorImpl(getIterator(query, tolerance)); - } - - /** - * Gets a sorted iterator on the Quad_tree. The Element_handles will be returned in increasing order of their corresponding Element_types - */ - QuadTreeSortedIteratorImpl getSortedIterator() { - return new QuadTreeSortedIteratorImpl(getIterator()); - } - - private void reset_(Envelope2D extent, int height) { - if (height < 0 || height > 127) - throw new IllegalArgumentException("invalid height"); - - m_height = height; - m_extent.setCoords(extent); - m_root = m_quad_tree_nodes.newElement(); - m_data_extent.setEmpty(); - m_root = -1; - } - - private int insert_(int element, Envelope2D bounding_box, int height, Envelope2D quad_extent, int quad_handle, boolean b_flushing, int flushed_element_handle) { - if (!quad_extent.contains(bounding_box)) { - assert (!b_flushing); - - if (height == 0) - return -1; - - return insert_(element, bounding_box, 0, m_extent, m_root, b_flushing, flushed_element_handle); - } - - if (!b_flushing) { - for (int q = quad_handle; q != -1; q = get_parent_(q)) - set_sub_tree_element_count_(q, get_sub_tree_element_count_(q) + 1); - } - - Envelope2D current_extent = new Envelope2D(); - current_extent.setCoords(quad_extent); - - int current_quad_handle = quad_handle; - - Envelope2D[] child_extents = new Envelope2D[4]; - child_extents[0] = new Envelope2D(); - child_extents[1] = new Envelope2D(); - child_extents[2] = new Envelope2D(); - child_extents[3] = new Envelope2D(); - - int current_height; - for (current_height = height; current_height < m_height && can_push_down_(current_quad_handle); current_height++) { - set_child_extents_(current_extent, child_extents); - - boolean b_contains = false; - - for (int i = 0; i < 4; i++) { - if (child_extents[i].contains(bounding_box)) { - b_contains = true; - - int child_handle = get_child_(current_quad_handle, i); - if (child_handle == -1) - child_handle = create_child_(current_quad_handle, i); - - set_sub_tree_element_count_(child_handle, get_sub_tree_element_count_(child_handle) + 1); - - current_quad_handle = child_handle; - current_extent.setCoords(child_extents[i]); - break; - } - } - - if (!b_contains) - break; - } - - return insert_at_quad_(element, bounding_box, current_height, current_extent, current_quad_handle, b_flushing, quad_handle, flushed_element_handle, -1); - } - - private int insert_duplicates_(int element, Envelope2D bounding_box, int height, Envelope2D quad_extent, int quad_handle, boolean b_flushing, int flushed_element_handle) { - assert (b_flushing || m_root == quad_handle); - - if (!b_flushing) // If b_flushing is true, then the sub tree element counts are already accounted for since the element already lies in the current incoming quad - { - if (!quad_extent.contains(bounding_box)) - return -1; - - set_sub_tree_element_count_(quad_handle, get_sub_tree_element_count_(quad_handle) + 1); - set_contained_sub_tree_element_count_(quad_handle, get_contained_sub_tree_element_count_(quad_handle) + 1); - } - - double bounding_box_max_dim = Math.max(bounding_box.getWidth(), bounding_box.getHeight()); - - int element_handle = -1; - AttributeStreamOfInt32 quads_stack = new AttributeStreamOfInt32(0); - ArrayList extents_stack = new ArrayList(0); - AttributeStreamOfInt32 heights_stack = new AttributeStreamOfInt32(0); - quads_stack.add(quad_handle); - extents_stack.add(new Envelope2D(quad_extent.xmin, quad_extent.ymin, quad_extent.xmax, quad_extent.ymax)); - heights_stack.add(height); - - Envelope2D[] child_extents = new Envelope2D[4]; - child_extents[0] = new Envelope2D(); - child_extents[1] = new Envelope2D(); - child_extents[2] = new Envelope2D(); - child_extents[3] = new Envelope2D(); - - Envelope2D current_extent = new Envelope2D(); - - while (quads_stack.size() > 0) { - boolean b_subdivide = false; - - int current_quad_handle = quads_stack.getLast(); - current_extent.setCoords(extents_stack.get(extents_stack.size() - 1)); - int current_height = heights_stack.getLast(); - - quads_stack.removeLast(); - extents_stack.remove(extents_stack.size() - 1); - heights_stack.removeLast(); - - if (current_height + 1 < m_height && can_push_down_(current_quad_handle)) { - double current_extent_max_dim = Math.max(current_extent.getWidth(), current_extent.getHeight()); - - if (bounding_box_max_dim <= current_extent_max_dim / 2.0) - b_subdivide = true; - } + static final class QuadTreeIteratorImpl { + /** + * Resets the iterator to an starting state on the Quad_tree_impl. If + * the input Geometry is a Line segment, then the query will be the + * segment. Otherwise the query will be the Envelope_2D bounding the + * Geometry. \param query The Geometry used for the query. \param + * tolerance The tolerance used for the intersection tests. \param + * tolerance The tolerance used for the intersection tests. + */ + void resetIterator(Geometry query, double tolerance) { + m_quads_stack.resize(0); + m_extents_stack.clear(); + m_current_element_handle = -1; + query.queryLooseEnvelope2D(m_query_box); + m_query_box.inflate(tolerance, tolerance); + + if (m_quad_tree.m_root != -1 && m_query_box.isIntersecting(m_quad_tree.m_extent)) { + int type = query.getType().value(); + m_b_linear = Geometry.isSegment(type); + + if (m_b_linear) { + Segment segment = (Segment) query; + m_query_start = segment.getStartXY(); + m_query_end = segment.getEndXY(); + m_tolerance = tolerance; + } else { + m_tolerance = NumberUtils.NaN(); // we don't need it + } + + m_quads_stack.add(m_quad_tree.m_root); + m_extents_stack.add(m_quad_tree.m_extent); + m_next_element_handle = m_quad_tree.get_first_element_(m_quad_tree.m_root); + } else + m_next_element_handle = -1; + } + + /** + * Resets the iterator to a starting state on the Quad_tree_impl using + * the input Envelope_2D as the query. \param query The Envelope_2D used + * for the query. \param tolerance The tolerance used for the + * intersection tests. + */ + void resetIterator(Envelope2D query, double tolerance) { + m_quads_stack.resize(0); + m_extents_stack.clear(); + m_current_element_handle = -1; + m_query_box.setCoords(query); + m_query_box.inflate(tolerance, tolerance); + m_tolerance = NumberUtils.NaN(); // we don't need it + + if (m_quad_tree.m_root != -1 && m_query_box.isIntersecting(m_quad_tree.m_extent)) { + m_quads_stack.add(m_quad_tree.m_root); + m_extents_stack.add(m_quad_tree.m_extent); + m_next_element_handle = m_quad_tree.get_first_element_(m_quad_tree.m_root); + m_b_linear = false; + } else + m_next_element_handle = -1; + } + + /** + * Moves the iterator to the next int and returns the int. + */ + int next() { + // If the node stack is empty, then we've exhausted our search + + if (m_quads_stack.size() == 0) + return -1; + + m_current_element_handle = m_next_element_handle; + + Point2D start = null; + Point2D end = null; + Envelope2D bounding_box; + Envelope2D extent_inf = null; + Envelope2D[] child_extents = null; + + if (m_b_linear) { + start = new Point2D(); + end = new Point2D(); + extent_inf = new Envelope2D(); + } + + boolean b_found_hit = false; + while (!b_found_hit) { + while (m_current_element_handle != -1) { + int current_data_handle = m_quad_tree.get_data_(m_current_element_handle); + bounding_box = m_quad_tree.get_bounding_box_value_(current_data_handle); + + if (bounding_box.isIntersecting(m_query_box)) { + if (m_b_linear) { + start.setCoords(m_query_start); + end.setCoords(m_query_end); + extent_inf.setCoords(bounding_box); + + extent_inf.inflate(m_tolerance, m_tolerance); + if (extent_inf.clipLine(start, end) > 0) { + b_found_hit = true; + break; + } + } else { + b_found_hit = true; + break; + } + } + + // get next element_handle + m_current_element_handle = m_quad_tree.get_next_element_(m_current_element_handle); + } + + // If m_current_element_handle equals -1, then we've exhausted our search in the current quadtree node + if (m_current_element_handle == -1) { + // get the last node from the stack and add the children whose extent intersects m_query_box + int current_quad = m_quads_stack.getLast(); + Envelope2D current_extent = m_extents_stack.get(m_extents_stack.size() - 1); + + if (child_extents == null) { + child_extents = new Envelope2D[4]; + child_extents[0] = new Envelope2D(); + child_extents[1] = new Envelope2D(); + child_extents[2] = new Envelope2D(); + child_extents[3] = new Envelope2D(); + } + + set_child_extents_(current_extent, child_extents); + m_quads_stack.removeLast(); + m_extents_stack.remove(m_extents_stack.size() - 1); + + for (int quadrant = 0; quadrant < 4; quadrant++) { + int child_handle = m_quad_tree.get_child_(current_quad, quadrant); + + if (child_handle != -1 && m_quad_tree.getSubTreeElementCount(child_handle) > 0) { + if (child_extents[quadrant].isIntersecting(m_query_box)) { + if (m_b_linear) { + start.setCoords(m_query_start); + end.setCoords(m_query_end); + + extent_inf.setCoords(child_extents[quadrant]); + extent_inf.inflate(m_tolerance, m_tolerance); + if (extent_inf.clipLine(start, end) > 0) { + Envelope2D child_extent = new Envelope2D(); + child_extent.setCoords(child_extents[quadrant]); + m_quads_stack.add(child_handle); + m_extents_stack.add(child_extent); + } + } else { + Envelope2D child_extent = new Envelope2D(); + child_extent.setCoords(child_extents[quadrant]); + m_quads_stack.add(child_handle); + m_extents_stack.add(child_extent); + } + } + } + } + + assert (m_quads_stack.size() <= 4 * (m_quad_tree.m_height - 1)); + + if (m_quads_stack.size() == 0) + return -1; + + m_current_element_handle = m_quad_tree.get_first_element_(m_quads_stack.get(m_quads_stack.size() - 1)); + } + } + + // We did not exhaust our search in the current node, so we return + // the element at m_current_element_handle in m_element_nodes + + m_next_element_handle = m_quad_tree.get_next_element_(m_current_element_handle); + return m_current_element_handle; + } + + // Creates an iterator on the input Quad_tree_impl. The query will be + // the Envelope_2D bounding the input Geometry. + QuadTreeIteratorImpl(QuadTreeImpl quad_tree_impl, Geometry query, double tolerance) { + m_quad_tree = quad_tree_impl; + m_query_box = new Envelope2D(); + m_quads_stack = new AttributeStreamOfInt32(0); + m_extents_stack = new ArrayList(0); + resetIterator(query, tolerance); + } + + // Creates an iterator on the input Quad_tree_impl using the input + // Envelope_2D as the query. + QuadTreeIteratorImpl(QuadTreeImpl quad_tree_impl, Envelope2D query, double tolerance) { + m_quad_tree = quad_tree_impl; + m_query_box = new Envelope2D(); + m_quads_stack = new AttributeStreamOfInt32(0); + m_extents_stack = new ArrayList(0); + resetIterator(query, tolerance); + } + + // Creates an iterator on the input Quad_tree_impl. + QuadTreeIteratorImpl(QuadTreeImpl quad_tree_impl) { + m_quad_tree = quad_tree_impl; + m_query_box = new Envelope2D(); + m_quads_stack = new AttributeStreamOfInt32(0); + m_extents_stack = new ArrayList(0); + } + + private boolean m_b_linear; + private Point2D m_query_start; + private Point2D m_query_end; + private Envelope2D m_query_box; + private double m_tolerance; + private int m_current_element_handle; + private int m_next_element_handle; + private QuadTreeImpl m_quad_tree; + private AttributeStreamOfInt32 m_quads_stack; + private ArrayList m_extents_stack; // this won't grow bigger than 4 * (m_quad_tree->m_height - 1) + } + + static final class QuadTreeSortedIteratorImpl { + /** + * Resets the iterator to a starting state on the Quad_tree_impl. If the input Geometry is a Line segment, then the query will be the segment. Otherwise the query will be the Envelope_2D bounding the Geometry. + * \param query The Geometry used for the query. + * \param tolerance The tolerance used for the intersection tests. + * \param tolerance The tolerance used for the intersection tests. + */ + void resetIterator(Geometry query, double tolerance) { + m_quad_tree_iterator_impl.resetIterator(query, tolerance); + m_sorted_handles.resize(0); + m_index = -1; + } + + /** + * Resets the iterator to a starting state on the Quad_tree_impl using the input Envelope_2D as the query. + * \param query The Envelope_2D used for the query. + * \param tolerance The tolerance used for the intersection tests. + */ + void resetIterator(Envelope2D query, double tolerance) { + m_quad_tree_iterator_impl.resetIterator(query, tolerance); + m_sorted_handles.resize(0); + m_index = -1; + } + + /** + * Moves the iterator to the next Element_handle and returns the Element_handle. + */ + int next() { + if (m_index == -1) { + int element_handle = -1; + while ((element_handle = m_quad_tree_iterator_impl.next()) != -1) + m_sorted_handles.add(element_handle); + + m_bucket_sort.sort(m_sorted_handles, 0, m_sorted_handles.size(), new Sorter(m_quad_tree_iterator_impl.m_quad_tree)); + } + + if (m_index == m_sorted_handles.size() - 1) + return -1; + + m_index++; + return m_sorted_handles.get(m_index); + } + + //Creates a sorted iterator on the input Quad_tree_iterator_impl + QuadTreeSortedIteratorImpl(QuadTreeIteratorImpl quad_tree_iterator_impl) { + m_bucket_sort = new BucketSort(); + m_sorted_handles = new AttributeStreamOfInt32(0); + m_quad_tree_iterator_impl = quad_tree_iterator_impl; + m_index = -1; + } + + private class Sorter extends ClassicSort { + public Sorter(QuadTreeImpl quad_tree) { + m_quad_tree = quad_tree; + } + + @Override + public void userSort(int begin, int end, AttributeStreamOfInt32 indices) { + indices.sort(begin, end); + } + + @Override + public double getValue(int e) { + return m_quad_tree.getElement(e); + } + + private QuadTreeImpl m_quad_tree; + } + + private BucketSort m_bucket_sort; + private AttributeStreamOfInt32 m_sorted_handles; + private QuadTreeIteratorImpl m_quad_tree_iterator_impl; + int m_index; + } + + /** + * Creates a Quad_tree_impl with the root having the extent of the input Envelope_2D, and height of the input height, where the root starts at height 0. + * \param extent The extent of the Quad_tree_impl. + * \param height The max height of the Quad_tree_impl. + */ + QuadTreeImpl(Envelope2D extent, int height) { + m_quad_tree_nodes = new StridedIndexTypeCollection(10); + m_element_nodes = new StridedIndexTypeCollection(4); + m_data = new ArrayList(0); + m_free_data = new AttributeStreamOfInt32(0); + m_b_store_duplicates = false; + + m_extent = new Envelope2D(); + m_data_extent = new Envelope2D(); + + reset_(extent, height); + } + + /** + * Creates a Quad_tree_impl with the root having the extent of the input Envelope_2D, and height of the input height, where the root starts at height 0. + * \param extent The extent of the Quad_tree_impl. + * \param height The max height of the Quad_tree_impl. + * \param b_store_duplicates Put true to place elements deeper into the quad tree at intesecting quads, duplicates will be stored. Put false to only place elements into quads that can contain it. + */ + QuadTreeImpl(Envelope2D extent, int height, boolean b_store_duplicates) { + m_quad_tree_nodes = (b_store_duplicates ? new StridedIndexTypeCollection(11) : new StridedIndexTypeCollection(10)); + m_element_nodes = new StridedIndexTypeCollection(4); + m_data = new ArrayList(0); + m_free_data = new AttributeStreamOfInt32(0); + m_b_store_duplicates = b_store_duplicates; + + m_extent = new Envelope2D(); + m_data_extent = new Envelope2D(); + + reset_(extent, height); + } + + /** + * Resets the Quad_tree_impl to the given extent and height. + * \param extent The extent of the Quad_tree_impl. + * \param height The max height of the Quad_tree_impl. + */ + void reset(Envelope2D extent, int height) { + m_quad_tree_nodes.deleteAll(false); + m_element_nodes.deleteAll(false); + m_data.clear(); + m_free_data.clear(false); + reset_(extent, height); + } + + /** + * Inserts the element and bounding_box into the Quad_tree_impl. + * Note that this will invalidate any active iterator on the Quad_tree_impl. + * Returns an Element_handle corresponding to the element and bounding_box. + * \param element The element of the Geometry to be inserted. + * \param bounding_box The bounding_box of the Geometry to be inserted. + */ + int insert(int element, Envelope2D bounding_box) { + if (m_root == -1) + create_root_(); + + if (m_b_store_duplicates) { + int success = insert_duplicates_(element, bounding_box, 0, m_extent, m_root, false, -1); + + if (success != -1) { + if (m_data_extent.isEmpty()) + m_data_extent.setCoords(bounding_box); + else + m_data_extent.merge(bounding_box); + } + + return success; + } + + int element_handle = insert_(element, bounding_box, 0, m_extent, m_root, false, -1); + + if (element_handle != -1) { + if (m_data_extent.isEmpty()) + m_data_extent.setCoords(bounding_box); + else + m_data_extent.merge(bounding_box); + } + + return element_handle; + } + + /** + * Inserts the element and bounding_box into the Quad_tree_impl at the given quad_handle. + * Note that this will invalidate any active iterator on the Quad_tree_impl. + * Returns an Element_handle corresponding to the element and bounding_box. + * \param element The element of the Geometry to be inserted. + * \param bounding_box The bounding_box of the Geometry to be inserted. + * \param hint_index A handle used as a hint where to place the element. This can be a handle obtained from a previous insertion and is useful on data having strong locality such as segments of a Polygon. + */ + int insert(int element, Envelope2D bounding_box, int hint_index) { + if (m_root == -1) + create_root_(); + + if (m_b_store_duplicates) { + int success = insert_duplicates_(element, bounding_box, 0, m_extent, m_root, false, -1); + + if (success != -1) { + if (m_data_extent.isEmpty()) + m_data_extent.setCoords(bounding_box); + else + m_data_extent.merge(bounding_box); + } + return success; + } + + int quad_handle; + + if (hint_index == -1) + quad_handle = m_root; + else + quad_handle = get_quad_(hint_index); + + int quad_height = getHeight(quad_handle); + Envelope2D quad_extent = getExtent(quad_handle); + + int element_handle = insert_(element, bounding_box, quad_height, quad_extent, quad_handle, false, -1); + + if (element_handle != -1) { + if (m_data_extent.isEmpty()) + m_data_extent.setCoords(bounding_box); + else + m_data_extent.merge(bounding_box); + } + + return element_handle; + } + + /** + * Removes the element and bounding_box at the given element_handle. + * Note that this will invalidate any active iterator on the Quad_tree_impl. + * \param element_handle The handle corresponding to the element and bounding_box to be removed. + */ + void removeElement(int element_handle) { + if (m_b_store_duplicates) + throw new GeometryException("invalid call"); + + int quad_handle = get_quad_(element_handle); + disconnect_element_handle_(element_handle); + free_element_and_box_node_(element_handle); + + int q = quad_handle; + + while (q != -1) { + set_sub_tree_element_count_(q, get_sub_tree_element_count_(q) - 1); + int parent = get_parent_(q); + + if (get_sub_tree_element_count_(q) == 0) { + assert (get_local_element_count_(q) == 0); + + if (q != m_root) { + int quadrant = get_quadrant_(q); + m_quad_tree_nodes.deleteElement(q); + set_child_(parent, quadrant, -1); + } + } + + q = parent; + } + } + + /** + * Returns the element at the given element_handle. + * \param element_handle The handle corresponding to the element to be retrieved. + */ + int getElement(int element_handle) { + return get_element_value_(get_data_(element_handle)); + } + + /** + * Returns the ith unique element. + * \param i The index corresponding to the ith unique element. + */ + int getElementAtIndex(int i) { + return m_data.get(i).element; + } + + /** + * Returns the element extent at the given element_handle. + * \param element_handle The handle corresponding to the element extent to be retrieved. + */ + Envelope2D getElementExtent(int element_handle) { + int data_handle = get_data_(element_handle); + return get_bounding_box_value_(data_handle); + } + + /** + * Returns the extent of the ith unique element. + * \param i The index corresponding to the ith unique element. + */ + Envelope2D getElementExtentAtIndex(int i) { + return m_data.get(i).box; + } + + /** + * Returns the extent of all elements in the quad tree. + */ + Envelope2D getDataExtent() { + return m_data_extent; + } + + /** + * Returns the extent of the quad tree. + */ + Envelope2D getQuadTreeExtent() { + return m_extent; + } + + /** + * Returns the height of the quad at the given quad_handle. + * \param quad_handle The handle corresponding to the quad. + */ + int getHeight(int quad_handle) { + return get_height_(quad_handle); + } + + int getMaxHeight() { + return m_height; + } + + /** + * Returns the extent of the quad at the given quad_handle. + * \param quad_handle The handle corresponding to the quad. + */ + Envelope2D getExtent(int quad_handle) { + Envelope2D quad_extent = new Envelope2D(); + quad_extent.setCoords(m_extent); + + if (quad_handle == m_root) + return quad_extent; + + AttributeStreamOfInt32 quadrants = new AttributeStreamOfInt32(0); + + int q = quad_handle; + + do { + quadrants.add(get_quadrant_(q)); + q = get_parent_(q); + + } while (q != m_root); + + int sz = quadrants.size(); + assert (sz == getHeight(quad_handle)); + + for (int i = 0; i < sz; i++) { + int child = quadrants.getLast(); + quadrants.removeLast(); + + if (child == 0) {//northeast + quad_extent.xmin = 0.5 * (quad_extent.xmin + quad_extent.xmax); + quad_extent.ymin = 0.5 * (quad_extent.ymin + quad_extent.ymax); + } else if (child == 1) {//northwest + quad_extent.xmax = 0.5 * (quad_extent.xmin + quad_extent.xmax); + quad_extent.ymin = 0.5 * (quad_extent.ymin + quad_extent.ymax); + } else if (child == 2) {//southwest + quad_extent.xmax = 0.5 * (quad_extent.xmin + quad_extent.xmax); + quad_extent.ymax = 0.5 * (quad_extent.ymin + quad_extent.ymax); + } else {//southeast + quad_extent.xmin = 0.5 * (quad_extent.xmin + quad_extent.xmax); + quad_extent.ymax = 0.5 * (quad_extent.ymin + quad_extent.ymax); + } + } + + return quad_extent; + } + + /** + * Returns the Quad_handle of the quad containing the given element_handle. + * \param element_handle The handle corresponding to the element. + */ + int getQuad(int element_handle) { + return get_quad_(element_handle); + } + + /** + * Returns the number of elements in the Quad_tree_impl. + */ + int getElementCount() { + if (m_root == -1) + return 0; + + assert (get_sub_tree_element_count_(m_root) == m_data.size()); + return get_sub_tree_element_count_(m_root); + } + + /** + * Returns the number of elements in the subtree rooted at the given quad_handle. + * \param quad_handle The handle corresponding to the quad. + */ + int getSubTreeElementCount(int quad_handle) { + return get_sub_tree_element_count_(quad_handle); + } + + /** + * Returns the number of elements contained in the subtree rooted at the given quad_handle. + * \param quad_handle The handle corresponding to the quad. + */ + int getContainedSubTreeElementCount(int quad_handle) { + if (!m_b_store_duplicates) + return get_sub_tree_element_count_(quad_handle); + + return get_contained_sub_tree_element_count_(quad_handle); + } + + /** + * Returns the number of elements in the quad tree that intersect the qiven query. Some elements may be duplicated if the quad tree stores duplicates. + * \param query The Envelope_2D used for the query. + * \param tolerance The tolerance used for the intersection tests. + * \param max_count If the intersection count becomes greater than or equal to the max_count, then max_count is returned. + */ + int getIntersectionCount(Envelope2D query, double tolerance, int max_count) { + if (m_root == -1) + return 0; + + Envelope2D query_inflated = new Envelope2D(); + query_inflated.setCoords(query); + query_inflated.inflate(tolerance, tolerance); + + AttributeStreamOfInt32 quads_stack = new AttributeStreamOfInt32(0); + ArrayList extents_stack = new ArrayList(0); + quads_stack.add(m_root); + extents_stack.add(new Envelope2D(m_extent.xmin, m_extent.ymin, m_extent.xmax, m_extent.ymax)); + + Envelope2D[] child_extents = new Envelope2D[4]; + child_extents[0] = new Envelope2D(); + child_extents[1] = new Envelope2D(); + child_extents[2] = new Envelope2D(); + child_extents[3] = new Envelope2D(); + + Envelope2D current_extent = new Envelope2D(); + + int intersection_count = 0; + + while (quads_stack.size() > 0) { + boolean b_subdivide = false; + + int current_quad_handle = quads_stack.getLast(); + current_extent.setCoords(extents_stack.get(extents_stack.size() - 1)); + + quads_stack.removeLast(); + extents_stack.remove(extents_stack.size() - 1); + + + if (query_inflated.contains(current_extent)) { + intersection_count += getSubTreeElementCount(current_quad_handle); + + if (max_count > 0 && intersection_count >= max_count) + return max_count; + } else { + if (query_inflated.isIntersecting(current_extent)) { + for (int element_handle = get_first_element_(current_quad_handle); element_handle != -1; element_handle = get_next_element_(element_handle)) { + int data_handle = get_data_(element_handle); + Envelope2D env = get_bounding_box_value_(data_handle); + + if (env.isIntersecting(query_inflated)) { + intersection_count++; + + if (max_count > 0 && intersection_count >= max_count) + return max_count; + } + } + + b_subdivide = getHeight(current_quad_handle) + 1 <= m_height; + } + } + + if (b_subdivide) { + set_child_extents_(current_extent, child_extents); + + for (int i = 0; i < 4; i++) { + int child_handle = get_child_(current_quad_handle, i); + + if (child_handle != -1 && getSubTreeElementCount(child_handle) > 0) { + boolean b_is_intersecting = query_inflated.isIntersecting(child_extents[i]); + + if (b_is_intersecting) { + quads_stack.add(child_handle); + extents_stack.add(new Envelope2D(child_extents[i].xmin, child_extents[i].ymin, child_extents[i].xmax, child_extents[i].ymax)); + } + } + } + } + } + + return intersection_count; + } + + /** + * Returns true if the quad tree has data intersecting the given query. + * \param query The Envelope_2D used for the query. + * \param tolerance The tolerance used for the intersection tests. + */ + boolean hasData(Envelope2D query, double tolerance) { + int count = getIntersectionCount(query, tolerance, 1); + return count >= 1; + } + + /** + * Gets an iterator on the Quad_tree_impl. The query will be the Envelope_2D + * that bounds the input Geometry. To reuse the existing iterator on the + * same Quad_tree_impl but with a new query, use the reset_iterator function + * on the Quad_tree_iterator_impl. \param query The Geometry used for the + * query. If the Geometry is a Line segment, then the query will be the + * segment. Otherwise the query will be the Envelope_2D bounding the + * Geometry. \param tolerance The tolerance used for the intersection tests. + */ + QuadTreeIteratorImpl getIterator(Geometry query, double tolerance) { + return new QuadTreeIteratorImpl(this, query, tolerance); + } + + /** + * Gets an iterator on the Quad_tree_impl using the input Envelope_2D as the + * query. To reuse the existing iterator on the same Quad_tree_impl but with + * a new query, use the reset_iterator function on the + * Quad_tree_iterator_impl. \param query The Envelope_2D used for the query. + * \param tolerance The tolerance used for the intersection tests. + */ + QuadTreeIteratorImpl getIterator(Envelope2D query, double tolerance) { + return new QuadTreeIteratorImpl(this, query, tolerance); + } + + /** + * Gets an iterator on the Quad_tree. + */ + QuadTreeIteratorImpl getIterator() { + return new QuadTreeIteratorImpl(this); + } + + /** + * Gets a sorted iterator on the Quad_tree_impl. The Element_handles will be returned in increasing order of their corresponding Element_types. + * The query will be the Envelope_2D that bounds the input Geometry. + * To reuse the existing iterator on the same Quad_tree_impl but with a new query, use the reset_iterator function on the Quad_tree_sorted_iterator_impl. + * \param query The Geometry used for the query. If the Geometry is a Line segment, then the query will be the segment. Otherwise the query will be the Envelope_2D bounding the Geometry. + * \param tolerance The tolerance used for the intersection tests. + */ + QuadTreeSortedIteratorImpl getSortedIterator(Geometry query, double tolerance) { + return new QuadTreeSortedIteratorImpl(getIterator(query, tolerance)); + } + + /** + * Gets a sorted iterator on the Quad_tree_impl using the input Envelope_2D as the query. The Element_handles will be returned in increasing order of their corresponding Element_types. + * To reuse the existing iterator on the same Quad_tree_impl but with a new query, use the reset_iterator function on the Quad_tree_iterator_impl. + * \param query The Envelope_2D used for the query. + * \param tolerance The tolerance used for the intersection tests. + */ + QuadTreeSortedIteratorImpl getSortedIterator(Envelope2D query, double tolerance) { + return new QuadTreeSortedIteratorImpl(getIterator(query, tolerance)); + } + + /** + * Gets a sorted iterator on the Quad_tree. The Element_handles will be returned in increasing order of their corresponding Element_types + */ + QuadTreeSortedIteratorImpl getSortedIterator() { + return new QuadTreeSortedIteratorImpl(getIterator()); + } + + private void reset_(Envelope2D extent, int height) { + if (height < 0 || height > 127) + throw new IllegalArgumentException("invalid height"); + + m_height = height; + m_extent.setCoords(extent); + m_root = m_quad_tree_nodes.newElement(); + m_data_extent.setEmpty(); + m_root = -1; + } + + private int insert_(int element, Envelope2D bounding_box, int height, Envelope2D quad_extent, int quad_handle, boolean b_flushing, int flushed_element_handle) { + if (!quad_extent.contains(bounding_box)) { + assert (!b_flushing); + + if (height == 0) + return -1; + + return insert_(element, bounding_box, 0, m_extent, m_root, b_flushing, flushed_element_handle); + } + + if (!b_flushing) { + for (int q = quad_handle; q != -1; q = get_parent_(q)) + set_sub_tree_element_count_(q, get_sub_tree_element_count_(q) + 1); + } + + Envelope2D current_extent = new Envelope2D(); + current_extent.setCoords(quad_extent); + + int current_quad_handle = quad_handle; + + Envelope2D[] child_extents = new Envelope2D[4]; + child_extents[0] = new Envelope2D(); + child_extents[1] = new Envelope2D(); + child_extents[2] = new Envelope2D(); + child_extents[3] = new Envelope2D(); + + int current_height; + for (current_height = height; current_height < m_height && can_push_down_(current_quad_handle); current_height++) { + set_child_extents_(current_extent, child_extents); + + boolean b_contains = false; + + for (int i = 0; i < 4; i++) { + if (child_extents[i].contains(bounding_box)) { + b_contains = true; + + int child_handle = get_child_(current_quad_handle, i); + if (child_handle == -1) + child_handle = create_child_(current_quad_handle, i); + + set_sub_tree_element_count_(child_handle, get_sub_tree_element_count_(child_handle) + 1); + + current_quad_handle = child_handle; + current_extent.setCoords(child_extents[i]); + break; + } + } + + if (!b_contains) + break; + } + + return insert_at_quad_(element, bounding_box, current_height, current_extent, current_quad_handle, b_flushing, quad_handle, flushed_element_handle, -1); + } + + private int insert_duplicates_(int element, Envelope2D bounding_box, int height, Envelope2D quad_extent, int quad_handle, boolean b_flushing, int flushed_element_handle) { + assert (b_flushing || m_root == quad_handle); + + if (!b_flushing) // If b_flushing is true, then the sub tree element counts are already accounted for since the element already lies in the current incoming quad + { + if (!quad_extent.contains(bounding_box)) + return -1; + + set_sub_tree_element_count_(quad_handle, get_sub_tree_element_count_(quad_handle) + 1); + set_contained_sub_tree_element_count_(quad_handle, get_contained_sub_tree_element_count_(quad_handle) + 1); + } + + double bounding_box_max_dim = Math.max(bounding_box.getWidth(), bounding_box.getHeight()); + + int element_handle = -1; + AttributeStreamOfInt32 quads_stack = new AttributeStreamOfInt32(0); + ArrayList extents_stack = new ArrayList(0); + AttributeStreamOfInt32 heights_stack = new AttributeStreamOfInt32(0); + quads_stack.add(quad_handle); + extents_stack.add(new Envelope2D(quad_extent.xmin, quad_extent.ymin, quad_extent.xmax, quad_extent.ymax)); + heights_stack.add(height); + + Envelope2D[] child_extents = new Envelope2D[4]; + child_extents[0] = new Envelope2D(); + child_extents[1] = new Envelope2D(); + child_extents[2] = new Envelope2D(); + child_extents[3] = new Envelope2D(); + + Envelope2D current_extent = new Envelope2D(); + + while (quads_stack.size() > 0) { + boolean b_subdivide = false; + + int current_quad_handle = quads_stack.getLast(); + current_extent.setCoords(extents_stack.get(extents_stack.size() - 1)); + int current_height = heights_stack.getLast(); + + quads_stack.removeLast(); + extents_stack.remove(extents_stack.size() - 1); + heights_stack.removeLast(); + + if (current_height + 1 < m_height && can_push_down_(current_quad_handle)) { + double current_extent_max_dim = Math.max(current_extent.getWidth(), current_extent.getHeight()); + + if (bounding_box_max_dim <= current_extent_max_dim / 2.0) + b_subdivide = true; + } - if (b_subdivide) { - set_child_extents_(current_extent, child_extents); - - boolean b_contains = false; - - for (int i = 0; i < 4; i++) { - b_contains = child_extents[i].contains(bounding_box); - - if (b_contains) { - int child_handle = get_child_(current_quad_handle, i); - if (child_handle == -1) - child_handle = create_child_(current_quad_handle, i); - - quads_stack.add(child_handle); - extents_stack.add(new Envelope2D(child_extents[i].xmin, child_extents[i].ymin, child_extents[i].xmax, child_extents[i].ymax)); - heights_stack.add(current_height + 1); - - set_sub_tree_element_count_(child_handle, get_sub_tree_element_count_(child_handle) + 1); - set_contained_sub_tree_element_count_(child_handle, get_contained_sub_tree_element_count_(child_handle) + 1); - break; - } - } - - if (!b_contains) { - for (int i = 0; i < 4; i++) { - boolean b_intersects = child_extents[i].isIntersecting(bounding_box); - - if (b_intersects) { - int child_handle = get_child_(current_quad_handle, i); - if (child_handle == -1) - child_handle = create_child_(current_quad_handle, i); - - quads_stack.add(child_handle); - extents_stack.add(new Envelope2D(child_extents[i].xmin, child_extents[i].ymin, child_extents[i].xmax, child_extents[i].ymax)); - heights_stack.add(current_height + 1); - - set_sub_tree_element_count_(child_handle, get_sub_tree_element_count_(child_handle) + 1); - } - } - } - } else { - element_handle = insert_at_quad_(element, bounding_box, current_height, current_extent, current_quad_handle, b_flushing, quad_handle, flushed_element_handle, element_handle); - b_flushing = false; // flushing is false after the first inserted element has been flushed down, all subsequent inserts will be new - } - } - - return 0; - } - - private int insert_at_quad_(int element, Envelope2D bounding_box, int current_height, Envelope2D current_extent, int current_quad_handle, boolean b_flushing, int quad_handle, int flushed_element_handle, int duplicate_element_handle) { - // If the bounding box is not contained in any of the current_node's children, or if the current_height is m_height, then insert the element and - // bounding box into the current_node - - int head_element_handle = get_first_element_(current_quad_handle); - int tail_element_handle = get_last_element_(current_quad_handle); - int element_handle = -1; - - if (b_flushing) { - assert (flushed_element_handle != -1); - - if (current_quad_handle == quad_handle) - return flushed_element_handle; - - disconnect_element_handle_(flushed_element_handle); // Take it out of the incoming quad_handle, and place in current_quad_handle - element_handle = flushed_element_handle; - } else { - if (duplicate_element_handle == -1) { - element_handle = create_element_(); - set_data_values_(get_data_(element_handle), element, bounding_box); - } else { - assert (m_b_store_duplicates); - element_handle = create_element_from_duplicate_(duplicate_element_handle); - } - } - - assert (!b_flushing || element_handle == flushed_element_handle); - - set_quad_(element_handle, current_quad_handle); // set parent quad (needed for removal of element) - - // assign the prev pointer of the new tail to point at the old tail (tail_element_handle) - // assign the next pointer of the old tail to point at the new tail (next_element_handle) - if (tail_element_handle != -1) { - set_prev_element_(element_handle, tail_element_handle); - set_next_element_(tail_element_handle, element_handle); - } else { - assert (head_element_handle == -1); - set_first_element_(current_quad_handle, element_handle); - } - - // assign the new tail - set_last_element_(current_quad_handle, element_handle); - - set_local_element_count_(current_quad_handle, get_local_element_count_(current_quad_handle) + 1); - - if (can_flush_(current_quad_handle)) - flush_(current_height, current_extent, current_quad_handle); - - return element_handle; - } - - private static void set_child_extents_(Envelope2D current_extent, Envelope2D[] child_extents) { - double x_mid = 0.5 * (current_extent.xmin + current_extent.xmax); - double y_mid = 0.5 * (current_extent.ymin + current_extent.ymax); - - child_extents[0].setCoords(x_mid, y_mid, current_extent.xmax, current_extent.ymax); // northeast - child_extents[1].setCoords(current_extent.xmin, y_mid, x_mid, current_extent.ymax); // northwest - child_extents[2].setCoords(current_extent.xmin, current_extent.ymin, x_mid, y_mid); // southwest - child_extents[3].setCoords(x_mid, current_extent.ymin, current_extent.xmax, y_mid); // southeast - } - - private void disconnect_element_handle_(int element_handle) { - assert (element_handle != -1); - int quad_handle = get_quad_(element_handle); - int head_element_handle = get_first_element_(quad_handle); - int tail_element_handle = get_last_element_(quad_handle); - int prev_element_handle = get_prev_element_(element_handle); - int next_element_handle = get_next_element_(element_handle); - assert (head_element_handle != -1 && tail_element_handle != -1); - - if (head_element_handle == element_handle) { - if (next_element_handle != -1) - set_prev_element_(next_element_handle, -1); - else { - assert (head_element_handle == tail_element_handle); - assert (get_local_element_count_(quad_handle) == 1); - set_last_element_(quad_handle, -1); - } - - set_first_element_(quad_handle, next_element_handle); - } else if (tail_element_handle == element_handle) { - assert (prev_element_handle != -1); - assert (get_local_element_count_(quad_handle) >= 2); - set_next_element_(prev_element_handle, -1); - set_last_element_(quad_handle, prev_element_handle); - } else { - assert (next_element_handle != -1 && prev_element_handle != -1); - assert (get_local_element_count_(quad_handle) >= 3); - set_prev_element_(next_element_handle, prev_element_handle); - set_next_element_(prev_element_handle, next_element_handle); - } - - set_prev_element_(element_handle, -1); - set_next_element_(element_handle, -1); - - set_local_element_count_(quad_handle, get_local_element_count_(quad_handle) - 1); - assert (get_local_element_count_(quad_handle) >= 0); - } - - private boolean can_flush_(int quad_handle) { - return get_local_element_count_(quad_handle) == m_flushing_count && !has_children_(quad_handle); - } - - private void flush_(int height, Envelope2D extent, int quad_handle) { - int element; - Envelope2D bounding_box = new Envelope2D(); - - assert (quad_handle != -1); - - int element_handle = get_first_element_(quad_handle), next_handle = -1; - int data_handle = -1; - assert (element_handle != -1); - - do { - data_handle = get_data_(element_handle); - element = get_element_value_(data_handle); - bounding_box.setCoords(get_bounding_box_value_(data_handle)); - - next_handle = get_next_element_(element_handle); - - if (!m_b_store_duplicates) - insert_(element, bounding_box, height, extent, quad_handle, true, element_handle); - else - insert_duplicates_(element, bounding_box, height, extent, quad_handle, true, element_handle); - - element_handle = next_handle; - - } while (element_handle != -1); - } - - private boolean can_push_down_(int quad_handle) { - return get_local_element_count_(quad_handle) >= m_flushing_count || has_children_(quad_handle); - } - - private boolean has_children_(int parent) { - return get_child_(parent, 0) != -1 || get_child_(parent, 1) != -1 || get_child_(parent, 2) != -1 || get_child_(parent, 3) != -1; - } - - private int create_child_(int parent, int quadrant) { - int child = m_quad_tree_nodes.newElement(); - set_child_(parent, quadrant, child); - set_sub_tree_element_count_(child, 0); - set_local_element_count_(child, 0); - set_parent_(child, parent); - set_height_and_quadrant_(child, get_height_(parent) + 1, quadrant); - - if (m_b_store_duplicates) - set_contained_sub_tree_element_count_(child, 0); - - return child; - } - - private void create_root_() { - m_root = m_quad_tree_nodes.newElement(); - set_sub_tree_element_count_(m_root, 0); - set_local_element_count_(m_root, 0); - set_height_and_quadrant_(m_root, 0, 0); - - if (m_b_store_duplicates) - set_contained_sub_tree_element_count_(m_root, 0); - } - - private int create_element_() { - int element_handle = m_element_nodes.newElement(); - int data_handle; - - if (m_free_data.size() > 0) { - data_handle = m_free_data.get(m_free_data.size() - 1); - m_free_data.removeLast(); - } else { - data_handle = m_data.size(); - m_data.add(null); - } - - set_data_(element_handle, data_handle); - return element_handle; - } - - private int create_element_from_duplicate_(int duplicate_element_handle) { - int element_handle = m_element_nodes.newElement(); - int data_handle = get_data_(duplicate_element_handle); - set_data_(element_handle, data_handle); - return element_handle; - } - - private void free_element_and_box_node_(int element_handle) { - int data_handle = get_data_(element_handle); - m_free_data.add(data_handle); - m_element_nodes.deleteElement(element_handle); - } - - private int get_child_(int quad_handle, int quadrant) { - return m_quad_tree_nodes.getField(quad_handle, quadrant); - } - - private void set_child_(int parent, int quadrant, int child) { - m_quad_tree_nodes.setField(parent, quadrant, child); - } - - private int get_first_element_(int quad_handle) { - return m_quad_tree_nodes.getField(quad_handle, 4); - } - - private void set_first_element_(int quad_handle, int head) { - m_quad_tree_nodes.setField(quad_handle, 4, head); - } - - private int get_last_element_(int quad_handle) { - return m_quad_tree_nodes.getField(quad_handle, 5); - } - - private void set_last_element_(int quad_handle, int tail) { - m_quad_tree_nodes.setField(quad_handle, 5, tail); - } - - - private int get_quadrant_(int quad_handle) { - int height_quadrant_hybrid = m_quad_tree_nodes.getField(quad_handle, 6); - int quadrant = height_quadrant_hybrid & m_quadrant_mask; - return quadrant; - } - - private int get_height_(int quad_handle) { - int height_quadrant_hybrid = m_quad_tree_nodes.getField(quad_handle, 6); - int height = height_quadrant_hybrid >> m_height_bit_shift; - return height; - } - - private void set_height_and_quadrant_(int quad_handle, int height, int quadrant) { - assert (quadrant >= 0 && quadrant <= 3); - int height_quadrant_hybrid = (int) ((height << m_height_bit_shift) | quadrant); - m_quad_tree_nodes.setField(quad_handle, 6, height_quadrant_hybrid); - } - - private int get_local_element_count_(int quad_handle) { - return m_quad_tree_nodes.getField(quad_handle, 7); - } - - private void set_local_element_count_(int quad_handle, int count) { - m_quad_tree_nodes.setField(quad_handle, 7, count); - } - - private int get_sub_tree_element_count_(int quad_handle) { - return m_quad_tree_nodes.getField(quad_handle, 8); - } - - private void set_sub_tree_element_count_(int quad_handle, int count) { - m_quad_tree_nodes.setField(quad_handle, 8, count); - } - - private int get_parent_(int child) { - return m_quad_tree_nodes.getField(child, 9); - } - - private void set_parent_(int child, int parent) { - m_quad_tree_nodes.setField(child, 9, parent); - } - - private int get_contained_sub_tree_element_count_(int quad_handle) { - return m_quad_tree_nodes.getField(quad_handle, 10); - } - - private void set_contained_sub_tree_element_count_(int quad_handle, int count) { - m_quad_tree_nodes.setField(quad_handle, 10, count); - } - - private int get_data_(int element_handle) { - return m_element_nodes.getField(element_handle, 0); - } - - private void set_data_(int element_handle, int data_handle) { - m_element_nodes.setField(element_handle, 0, data_handle); - } - - private int get_prev_element_(int element_handle) { - return m_element_nodes.getField(element_handle, 1); - } - - private int get_next_element_(int element_handle) { - return m_element_nodes.getField(element_handle, 2); - } - - private void set_prev_element_(int element_handle, int prev_handle) { - m_element_nodes.setField(element_handle, 1, prev_handle); - } - - private void set_next_element_(int element_handle, int next_handle) { - m_element_nodes.setField(element_handle, 2, next_handle); - } - - private int get_quad_(int element_handle) { - return m_element_nodes.getField(element_handle, 3); - } - - private void set_quad_(int element_handle, int parent) { - m_element_nodes.setField(element_handle, 3, parent); - } - - private int get_element_value_(int data_handle) { - return m_data.get(data_handle).element; - } - - private Envelope2D get_bounding_box_value_(int data_handle) { - return m_data.get(data_handle).box; - } - - private void set_data_values_(int data_handle, int element, Envelope2D bounding_box) { - m_data.set(data_handle, new Data(element, bounding_box)); - } - - private Envelope2D m_extent; - private Envelope2D m_data_extent; - private StridedIndexTypeCollection m_quad_tree_nodes; - private StridedIndexTypeCollection m_element_nodes; - private ArrayList m_data; - private AttributeStreamOfInt32 m_free_data; - private int m_root; - private int m_height; - private boolean m_b_store_duplicates; - - private int m_quadrant_mask = 3; - private int m_height_bit_shift = 2; - private int m_flushing_count = 5; - - static final class Data { - int element; - Envelope2D box; - - Data(int element_, Envelope2D box_) { - element = element_; - box = new Envelope2D(); - box.setCoords(box_); - } - } - - /* m_quad_tree_nodes - * 0: m_north_east_child - * 1: m_north_west_child - * 2: m_south_west_child - * 3: m_south_east_child - * 4: m_head_element - * 5: m_tail_element - * 6: m_quadrant_and_height - * 7: m_local_element_count - * 8: m_sub_tree_element_count - * 9: m_parent_quad - * 10: m_height - */ - - /* m_element_nodes - * 0: m_data_handle - * 1: m_prev - * 2: m_next - * 3: m_parent_quad - */ - - /* m_data - * element - * box - */ + if (b_subdivide) { + set_child_extents_(current_extent, child_extents); + + boolean b_contains = false; + + for (int i = 0; i < 4; i++) { + b_contains = child_extents[i].contains(bounding_box); + + if (b_contains) { + int child_handle = get_child_(current_quad_handle, i); + if (child_handle == -1) + child_handle = create_child_(current_quad_handle, i); + + quads_stack.add(child_handle); + extents_stack.add(new Envelope2D(child_extents[i].xmin, child_extents[i].ymin, child_extents[i].xmax, child_extents[i].ymax)); + heights_stack.add(current_height + 1); + + set_sub_tree_element_count_(child_handle, get_sub_tree_element_count_(child_handle) + 1); + set_contained_sub_tree_element_count_(child_handle, get_contained_sub_tree_element_count_(child_handle) + 1); + break; + } + } + + if (!b_contains) { + for (int i = 0; i < 4; i++) { + boolean b_intersects = child_extents[i].isIntersecting(bounding_box); + + if (b_intersects) { + int child_handle = get_child_(current_quad_handle, i); + if (child_handle == -1) + child_handle = create_child_(current_quad_handle, i); + + quads_stack.add(child_handle); + extents_stack.add(new Envelope2D(child_extents[i].xmin, child_extents[i].ymin, child_extents[i].xmax, child_extents[i].ymax)); + heights_stack.add(current_height + 1); + + set_sub_tree_element_count_(child_handle, get_sub_tree_element_count_(child_handle) + 1); + } + } + } + } else { + element_handle = insert_at_quad_(element, bounding_box, current_height, current_extent, current_quad_handle, b_flushing, quad_handle, flushed_element_handle, element_handle); + b_flushing = false; // flushing is false after the first inserted element has been flushed down, all subsequent inserts will be new + } + } + + return 0; + } + + private int insert_at_quad_(int element, Envelope2D bounding_box, int current_height, Envelope2D current_extent, int current_quad_handle, boolean b_flushing, int quad_handle, int flushed_element_handle, int duplicate_element_handle) { + // If the bounding box is not contained in any of the current_node's children, or if the current_height is m_height, then insert the element and + // bounding box into the current_node + + int head_element_handle = get_first_element_(current_quad_handle); + int tail_element_handle = get_last_element_(current_quad_handle); + int element_handle = -1; + + if (b_flushing) { + assert (flushed_element_handle != -1); + + if (current_quad_handle == quad_handle) + return flushed_element_handle; + + disconnect_element_handle_(flushed_element_handle); // Take it out of the incoming quad_handle, and place in current_quad_handle + element_handle = flushed_element_handle; + } else { + if (duplicate_element_handle == -1) { + element_handle = create_element_(); + set_data_values_(get_data_(element_handle), element, bounding_box); + } else { + assert (m_b_store_duplicates); + element_handle = create_element_from_duplicate_(duplicate_element_handle); + } + } + + assert (!b_flushing || element_handle == flushed_element_handle); + + set_quad_(element_handle, current_quad_handle); // set parent quad (needed for removal of element) + + // assign the prev pointer of the new tail to point at the old tail (tail_element_handle) + // assign the next pointer of the old tail to point at the new tail (next_element_handle) + if (tail_element_handle != -1) { + set_prev_element_(element_handle, tail_element_handle); + set_next_element_(tail_element_handle, element_handle); + } else { + assert (head_element_handle == -1); + set_first_element_(current_quad_handle, element_handle); + } + + // assign the new tail + set_last_element_(current_quad_handle, element_handle); + + set_local_element_count_(current_quad_handle, get_local_element_count_(current_quad_handle) + 1); + + if (can_flush_(current_quad_handle)) + flush_(current_height, current_extent, current_quad_handle); + + return element_handle; + } + + private static void set_child_extents_(Envelope2D current_extent, Envelope2D[] child_extents) { + double x_mid = 0.5 * (current_extent.xmin + current_extent.xmax); + double y_mid = 0.5 * (current_extent.ymin + current_extent.ymax); + + child_extents[0].setCoords(x_mid, y_mid, current_extent.xmax, current_extent.ymax); // northeast + child_extents[1].setCoords(current_extent.xmin, y_mid, x_mid, current_extent.ymax); // northwest + child_extents[2].setCoords(current_extent.xmin, current_extent.ymin, x_mid, y_mid); // southwest + child_extents[3].setCoords(x_mid, current_extent.ymin, current_extent.xmax, y_mid); // southeast + } + + private void disconnect_element_handle_(int element_handle) { + assert (element_handle != -1); + int quad_handle = get_quad_(element_handle); + int head_element_handle = get_first_element_(quad_handle); + int tail_element_handle = get_last_element_(quad_handle); + int prev_element_handle = get_prev_element_(element_handle); + int next_element_handle = get_next_element_(element_handle); + assert (head_element_handle != -1 && tail_element_handle != -1); + + if (head_element_handle == element_handle) { + if (next_element_handle != -1) + set_prev_element_(next_element_handle, -1); + else { + assert (head_element_handle == tail_element_handle); + assert (get_local_element_count_(quad_handle) == 1); + set_last_element_(quad_handle, -1); + } + + set_first_element_(quad_handle, next_element_handle); + } else if (tail_element_handle == element_handle) { + assert (prev_element_handle != -1); + assert (get_local_element_count_(quad_handle) >= 2); + set_next_element_(prev_element_handle, -1); + set_last_element_(quad_handle, prev_element_handle); + } else { + assert (next_element_handle != -1 && prev_element_handle != -1); + assert (get_local_element_count_(quad_handle) >= 3); + set_prev_element_(next_element_handle, prev_element_handle); + set_next_element_(prev_element_handle, next_element_handle); + } + + set_prev_element_(element_handle, -1); + set_next_element_(element_handle, -1); + + set_local_element_count_(quad_handle, get_local_element_count_(quad_handle) - 1); + assert (get_local_element_count_(quad_handle) >= 0); + } + + private boolean can_flush_(int quad_handle) { + return get_local_element_count_(quad_handle) == m_flushing_count && !has_children_(quad_handle); + } + + private void flush_(int height, Envelope2D extent, int quad_handle) { + int element; + Envelope2D bounding_box = new Envelope2D(); + + assert (quad_handle != -1); + + int element_handle = get_first_element_(quad_handle), next_handle = -1; + int data_handle = -1; + assert (element_handle != -1); + + do { + data_handle = get_data_(element_handle); + element = get_element_value_(data_handle); + bounding_box.setCoords(get_bounding_box_value_(data_handle)); + + next_handle = get_next_element_(element_handle); + + if (!m_b_store_duplicates) + insert_(element, bounding_box, height, extent, quad_handle, true, element_handle); + else + insert_duplicates_(element, bounding_box, height, extent, quad_handle, true, element_handle); + + element_handle = next_handle; + + } while (element_handle != -1); + } + + private boolean can_push_down_(int quad_handle) { + return get_local_element_count_(quad_handle) >= m_flushing_count || has_children_(quad_handle); + } + + private boolean has_children_(int parent) { + return get_child_(parent, 0) != -1 || get_child_(parent, 1) != -1 || get_child_(parent, 2) != -1 || get_child_(parent, 3) != -1; + } + + private int create_child_(int parent, int quadrant) { + int child = m_quad_tree_nodes.newElement(); + set_child_(parent, quadrant, child); + set_sub_tree_element_count_(child, 0); + set_local_element_count_(child, 0); + set_parent_(child, parent); + set_height_and_quadrant_(child, get_height_(parent) + 1, quadrant); + + if (m_b_store_duplicates) + set_contained_sub_tree_element_count_(child, 0); + + return child; + } + + private void create_root_() { + m_root = m_quad_tree_nodes.newElement(); + set_sub_tree_element_count_(m_root, 0); + set_local_element_count_(m_root, 0); + set_height_and_quadrant_(m_root, 0, 0); + + if (m_b_store_duplicates) + set_contained_sub_tree_element_count_(m_root, 0); + } + + private int create_element_() { + int element_handle = m_element_nodes.newElement(); + int data_handle; + + if (m_free_data.size() > 0) { + data_handle = m_free_data.get(m_free_data.size() - 1); + m_free_data.removeLast(); + } else { + data_handle = m_data.size(); + m_data.add(null); + } + + set_data_(element_handle, data_handle); + return element_handle; + } + + private int create_element_from_duplicate_(int duplicate_element_handle) { + int element_handle = m_element_nodes.newElement(); + int data_handle = get_data_(duplicate_element_handle); + set_data_(element_handle, data_handle); + return element_handle; + } + + private void free_element_and_box_node_(int element_handle) { + int data_handle = get_data_(element_handle); + m_free_data.add(data_handle); + m_element_nodes.deleteElement(element_handle); + } + + private int get_child_(int quad_handle, int quadrant) { + return m_quad_tree_nodes.getField(quad_handle, quadrant); + } + + private void set_child_(int parent, int quadrant, int child) { + m_quad_tree_nodes.setField(parent, quadrant, child); + } + + private int get_first_element_(int quad_handle) { + return m_quad_tree_nodes.getField(quad_handle, 4); + } + + private void set_first_element_(int quad_handle, int head) { + m_quad_tree_nodes.setField(quad_handle, 4, head); + } + + private int get_last_element_(int quad_handle) { + return m_quad_tree_nodes.getField(quad_handle, 5); + } + + private void set_last_element_(int quad_handle, int tail) { + m_quad_tree_nodes.setField(quad_handle, 5, tail); + } + + + private int get_quadrant_(int quad_handle) { + int height_quadrant_hybrid = m_quad_tree_nodes.getField(quad_handle, 6); + int quadrant = height_quadrant_hybrid & m_quadrant_mask; + return quadrant; + } + + private int get_height_(int quad_handle) { + int height_quadrant_hybrid = m_quad_tree_nodes.getField(quad_handle, 6); + int height = height_quadrant_hybrid >> m_height_bit_shift; + return height; + } + + private void set_height_and_quadrant_(int quad_handle, int height, int quadrant) { + assert (quadrant >= 0 && quadrant <= 3); + int height_quadrant_hybrid = (int) ((height << m_height_bit_shift) | quadrant); + m_quad_tree_nodes.setField(quad_handle, 6, height_quadrant_hybrid); + } + + private int get_local_element_count_(int quad_handle) { + return m_quad_tree_nodes.getField(quad_handle, 7); + } + + private void set_local_element_count_(int quad_handle, int count) { + m_quad_tree_nodes.setField(quad_handle, 7, count); + } + + private int get_sub_tree_element_count_(int quad_handle) { + return m_quad_tree_nodes.getField(quad_handle, 8); + } + + private void set_sub_tree_element_count_(int quad_handle, int count) { + m_quad_tree_nodes.setField(quad_handle, 8, count); + } + + private int get_parent_(int child) { + return m_quad_tree_nodes.getField(child, 9); + } + + private void set_parent_(int child, int parent) { + m_quad_tree_nodes.setField(child, 9, parent); + } + + private int get_contained_sub_tree_element_count_(int quad_handle) { + return m_quad_tree_nodes.getField(quad_handle, 10); + } + + private void set_contained_sub_tree_element_count_(int quad_handle, int count) { + m_quad_tree_nodes.setField(quad_handle, 10, count); + } + + private int get_data_(int element_handle) { + return m_element_nodes.getField(element_handle, 0); + } + + private void set_data_(int element_handle, int data_handle) { + m_element_nodes.setField(element_handle, 0, data_handle); + } + + private int get_prev_element_(int element_handle) { + return m_element_nodes.getField(element_handle, 1); + } + + private int get_next_element_(int element_handle) { + return m_element_nodes.getField(element_handle, 2); + } + + private void set_prev_element_(int element_handle, int prev_handle) { + m_element_nodes.setField(element_handle, 1, prev_handle); + } + + private void set_next_element_(int element_handle, int next_handle) { + m_element_nodes.setField(element_handle, 2, next_handle); + } + + private int get_quad_(int element_handle) { + return m_element_nodes.getField(element_handle, 3); + } + + private void set_quad_(int element_handle, int parent) { + m_element_nodes.setField(element_handle, 3, parent); + } + + private int get_element_value_(int data_handle) { + return m_data.get(data_handle).element; + } + + private Envelope2D get_bounding_box_value_(int data_handle) { + return m_data.get(data_handle).box; + } + + private void set_data_values_(int data_handle, int element, Envelope2D bounding_box) { + m_data.set(data_handle, new Data(element, bounding_box)); + } + + private Envelope2D m_extent; + private Envelope2D m_data_extent; + private StridedIndexTypeCollection m_quad_tree_nodes; + private StridedIndexTypeCollection m_element_nodes; + private ArrayList m_data; + private AttributeStreamOfInt32 m_free_data; + private int m_root; + private int m_height; + private boolean m_b_store_duplicates; + + private int m_quadrant_mask = 3; + private int m_height_bit_shift = 2; + private int m_flushing_count = 5; + + static final class Data { + int element; + Envelope2D box; + + Data(int element_, Envelope2D box_) { + element = element_; + box = new Envelope2D(); + box.setCoords(box_); + } + } + + /* m_quad_tree_nodes + * 0: m_north_east_child + * 1: m_north_west_child + * 2: m_south_west_child + * 3: m_south_east_child + * 4: m_head_element + * 5: m_tail_element + * 6: m_quadrant_and_height + * 7: m_local_element_count + * 8: m_sub_tree_element_count + * 9: m_parent_quad + * 10: m_height + */ + + /* m_element_nodes + * 0: m_data_handle + * 1: m_prev + * 2: m_next + * 3: m_parent_quad + */ + + /* m_data + * element + * box + */ } diff --git a/src/main/java/com/esri/core/geometry/RandomPointMaker.java b/src/main/java/com/esri/core/geometry/RandomPointMaker.java index 1e6aec76..da4ec1a5 100644 --- a/src/main/java/com/esri/core/geometry/RandomPointMaker.java +++ b/src/main/java/com/esri/core/geometry/RandomPointMaker.java @@ -10,109 +10,109 @@ class RandomPointMaker { - /** - * This assumes an equal area projection - * - * @param geometry - * @param pointsPerSquareKm - * @param sr - * @param progressTracker - * @return - */ - static Geometry generate(Geometry geometry, - double pointsPerSquareKm, - Random numberGenerator, - SpatialReference sr, - ProgressTracker progressTracker) throws PJException { - if (geometry.getType() != Geometry.Type.Polygon && geometry.getType() != Geometry.Type.Envelope) - throw new GeometryException("Geometry input must be of type Polygon or Envelope"); - - if (sr == null || sr.isLocal()) - throw new GeometryException("Spatial reference must be defined and must have unit definition"); - - Polygon polygon = null; - if (geometry.getType() == Geometry.Type.Envelope) { - polygon = new Polygon(); - polygon.addEnvelope((Envelope) geometry, false); - } else { - polygon = (Polygon) geometry; - } - - // TODO should iterate over paths - // TODO iterator should check for containment. If a path is contained within another, random points shouldn't - // be generated for that contained path - // Ask Aaron if paths are written to attribute stream such that paths contained come after container paths - return __makeRandomPoints(polygon, pointsPerSquareKm, numberGenerator, sr, progressTracker); - } - - // TODO input should be multiplath - static Geometry __makeRandomPoints(Polygon polygon, - double pointsPerSquareKm, - Random numberGenerator, - SpatialReference sr, - ProgressTracker progressTracker) throws PJException { - // TODO for each ring project, in order to prevent from creating an excess of points if two parts of a polygon are on opposite sides of the globe. - - ProjectionTransformation forwardProjectionTransformation = ProjectionTransformation.getEqualArea(polygon, sr); - - Envelope env = new Envelope(); - polygon.queryEnvelope(env); - // Project bounding coordinates to equal area - // equalAreaEnvelopeGeom must be a geometry/polygon because projection of envelope will almost certainly - // or skew geometry - // TODO, maybe it would be computationally cheaper or more accurate to project input polygon instead of it's envelope - Geometry equalAreaEnvelopeGeom = OperatorProject.local().execute(env, forwardProjectionTransformation, progressTracker); - - Envelope2D equalAreaEnvelope = new Envelope2D(); - // envelope of projected envelope - equalAreaEnvelopeGeom.queryEnvelope2D(equalAreaEnvelope); - - double areaKm = equalAreaEnvelope.getArea() / (1000.0 * 1000.0); - double pointCountNotCast = Math.ceil(areaKm * pointsPerSquareKm); - //http://stackoverflow.com/questions/3038392/do-java-arrays-have-a-maximum-size - if (pointCountNotCast * 2 > Integer.MAX_VALUE - 8) { - throw new GeometryException("Random Point count outside of available"); - } - int pointCount = (int) pointCountNotCast; - - // TODO if the area of the envelope is more than twice that of the initial polygon, maybe a raster creation - // of random multipoints would be required...? - - double[] xy = new double[pointCount * 2]; - - double xdiff = equalAreaEnvelope.xmax - equalAreaEnvelope.xmin; - double ydiff = equalAreaEnvelope.ymax - equalAreaEnvelope.ymin; - for (int i = 0; i < pointCount * 2; i++) { - if (i % 2 == 0) // x val - xy[i] = numberGenerator.nextDouble() * xdiff + equalAreaEnvelope.xmin; - else // y val - xy[i] = numberGenerator.nextDouble() * ydiff + equalAreaEnvelope.ymin; - } - - // Create Multipoint from vertices - MultiPoint multiPoint = new MultiPoint(); - MultiVertexGeometryImpl multiVertexGeometry = (MultiVertexGeometryImpl) multiPoint._getImpl(); - AttributeStreamOfDbl attributeStreamOfDbl = new AttributeStreamOfDbl(pointCount * 2); - - // TODO it would be better if we could just std::move the array. - attributeStreamOfDbl.writeRangeMove(xy); - multiVertexGeometry.setAttributeStreamRef(0, attributeStreamOfDbl); - //multiVertexGeometry._resizeImpl(pointCount); - multiPoint.resize(pointCount); - multiVertexGeometry._setDirtyFlag(DirtyFlags.dirtyAll, true); - - ProjectionTransformation backProjectionTransformation = forwardProjectionTransformation.getReverse(); - // project inplace instead of projecting a copy using OperatorProject::execute - Projecter.projectMultiPoint(multiPoint, backProjectionTransformation, progressTracker); - - - // TODO project multipoint back to input spatial reference (it is necessary to do it here, - // because if we projected the above array, then we wouldn't benefit from clipping - - // Intersect by input geometry - // TODO reduce densify distance? - Geometry intersector = OperatorGeodeticDensifyByLength.local().execute(polygon, sr, areaKm, GeodeticCurveType.Geodesic, null); + /** + * This assumes an equal area projection + * + * @param geometry + * @param pointsPerSquareKm + * @param sr + * @param progressTracker + * @return + */ + static Geometry generate(Geometry geometry, + double pointsPerSquareKm, + Random numberGenerator, + SpatialReference sr, + ProgressTracker progressTracker) throws PJException { + if (geometry.getType() != Geometry.Type.Polygon && geometry.getType() != Geometry.Type.Envelope) + throw new GeometryException("Geometry input must be of type Polygon or Envelope"); + + if (sr == null || sr.isLocal()) + throw new GeometryException("Spatial reference must be defined and must have unit definition"); + + Polygon polygon = null; + if (geometry.getType() == Geometry.Type.Envelope) { + polygon = new Polygon(); + polygon.addEnvelope((Envelope) geometry, false); + } else { + polygon = (Polygon) geometry; + } + + // TODO should iterate over paths + // TODO iterator should check for containment. If a path is contained within another, random points shouldn't + // be generated for that contained path + // Ask Aaron if paths are written to attribute stream such that paths contained come after container paths + return __makeRandomPoints(polygon, pointsPerSquareKm, numberGenerator, sr, progressTracker); + } + + // TODO input should be multiplath + static Geometry __makeRandomPoints(Polygon polygon, + double pointsPerSquareKm, + Random numberGenerator, + SpatialReference sr, + ProgressTracker progressTracker) throws PJException { + // TODO for each ring project, in order to prevent from creating an excess of points if two parts of a polygon are on opposite sides of the globe. + + ProjectionTransformation forwardProjectionTransformation = ProjectionTransformation.getEqualArea(polygon, sr); + + Envelope env = new Envelope(); + polygon.queryEnvelope(env); + // Project bounding coordinates to equal area + // equalAreaEnvelopeGeom must be a geometry/polygon because projection of envelope will almost certainly + // or skew geometry + // TODO, maybe it would be computationally cheaper or more accurate to project input polygon instead of it's envelope + Geometry equalAreaEnvelopeGeom = OperatorProject.local().execute(env, forwardProjectionTransformation, progressTracker); + + Envelope2D equalAreaEnvelope = new Envelope2D(); + // envelope of projected envelope + equalAreaEnvelopeGeom.queryEnvelope2D(equalAreaEnvelope); + + double areaKm = equalAreaEnvelope.getArea() / (1000.0 * 1000.0); + double pointCountNotCast = Math.ceil(areaKm * pointsPerSquareKm); + //http://stackoverflow.com/questions/3038392/do-java-arrays-have-a-maximum-size + if (pointCountNotCast * 2 > Integer.MAX_VALUE - 8) { + throw new GeometryException("Random Point count outside of available"); + } + int pointCount = (int) pointCountNotCast; + + // TODO if the area of the envelope is more than twice that of the initial polygon, maybe a raster creation + // of random multipoints would be required...? + + double[] xy = new double[pointCount * 2]; + + double xdiff = equalAreaEnvelope.xmax - equalAreaEnvelope.xmin; + double ydiff = equalAreaEnvelope.ymax - equalAreaEnvelope.ymin; + for (int i = 0; i < pointCount * 2; i++) { + if (i % 2 == 0) // x val + xy[i] = numberGenerator.nextDouble() * xdiff + equalAreaEnvelope.xmin; + else // y val + xy[i] = numberGenerator.nextDouble() * ydiff + equalAreaEnvelope.ymin; + } + + // Create Multipoint from vertices + MultiPoint multiPoint = new MultiPoint(); + MultiVertexGeometryImpl multiVertexGeometry = (MultiVertexGeometryImpl) multiPoint._getImpl(); + AttributeStreamOfDbl attributeStreamOfDbl = new AttributeStreamOfDbl(pointCount * 2); + + // TODO it would be better if we could just std::move the array. + attributeStreamOfDbl.writeRangeMove(xy); + multiVertexGeometry.setAttributeStreamRef(0, attributeStreamOfDbl); + //multiVertexGeometry._resizeImpl(pointCount); + multiPoint.resize(pointCount); + multiVertexGeometry._setDirtyFlag(DirtyFlags.dirtyAll, true); + + ProjectionTransformation backProjectionTransformation = forwardProjectionTransformation.getReverse(); + // project inplace instead of projecting a copy using OperatorProject::execute + Projecter.projectMultiPoint(multiPoint, backProjectionTransformation, progressTracker); + + + // TODO project multipoint back to input spatial reference (it is necessary to do it here, + // because if we projected the above array, then we wouldn't benefit from clipping + + // Intersect by input geometry + // TODO reduce densify distance? + Geometry intersector = OperatorGeodeticDensifyByLength.local().execute(polygon, sr, areaKm, GeodeticCurveType.Geodesic, null); // double geodeticDensify = 1 - return GeometryEngine.intersect(multiPoint, intersector, sr); - } + return GeometryEngine.intersect(multiPoint, intersector, sr); + } } diff --git a/src/main/java/com/esri/core/geometry/RasterizedGeometry2D.java b/src/main/java/com/esri/core/geometry/RasterizedGeometry2D.java index 86e1a5b4..e78c3c37 100644 --- a/src/main/java/com/esri/core/geometry/RasterizedGeometry2D.java +++ b/src/main/java/com/esri/core/geometry/RasterizedGeometry2D.java @@ -27,112 +27,112 @@ public abstract class RasterizedGeometry2D { - public enum HitType { - Outside(0), // the test geometry is well outside the geometry bounds - Inside(1), // the test geometry is well inside the geomety bounds - Border(2); // the test geometry is close to the bounds or intersects the - // bounds - - int enumVal; - - private HitType(int val) { - enumVal = val; - } - - ; - } - - /** - * Test a point against the RasterizedGeometry - */ - public abstract HitType queryPointInGeometry(double x, double y); - - /** - * Test an envelope against the RasterizedGeometry. - */ - public abstract HitType queryEnvelopeInGeometry(Envelope2D env); - - /** - * Creates a rasterized geometry from a given Geometry. - * - * @param geom The input geometry to rasterize. It has to be a MultiVertexGeometry instance. - * @param toleranceXY The tolerance of the rasterization. Raster pixels that are - * closer than given tolerance to the Geometry will be set. - * @param rasterSizeBytes The max size of the raster in bytes. The raster has size of - * rasterSize x rasterSize. Polygons are rasterized into 2 bpp - * (bits per pixel) rasters while other geometries are rasterized - * into 1 bpp rasters. 32x32 pixel raster for a polygon would - * take 256 bytes of memory - */ - public static RasterizedGeometry2D create(Geometry geom, - double toleranceXY, int rasterSizeBytes) { - if (!canUseAccelerator(geom)) - throw new IllegalArgumentException(); - - RasterizedGeometry2DImpl gc = RasterizedGeometry2DImpl.createImpl(geom, - toleranceXY, rasterSizeBytes); - return (RasterizedGeometry2D) gc; - } - - static RasterizedGeometry2D create(MultiVertexGeometryImpl geom, - double toleranceXY, int rasterSizeBytes) { - if (!canUseAccelerator(geom)) - throw new IllegalArgumentException(); - - RasterizedGeometry2DImpl gc = RasterizedGeometry2DImpl.createImpl(geom, - toleranceXY, rasterSizeBytes); - return (RasterizedGeometry2D) gc; - - } - - public static int rasterSizeFromAccelerationDegree( - GeometryAccelerationDegree accelDegree) { - int value = 0; - switch (accelDegree) { - case enumMild: - value = 64 * 64 * 2 / 8;// 1k - break; - case enumMedium: - value = 256 * 256 * 2 / 8;// 16k - break; - case enumHot: - value = 1024 * 1024 * 2 / 8;// 256k - break; - default: - throw GeometryException.GeometryInternalError(); - } - - return value; - } - - /** - * Checks whether the RasterizedGeometry2D accelerator can be used with the - * given geometry. - */ - static boolean canUseAccelerator(Geometry geom) { - if (geom.isEmpty() - || !(geom.getType() == Geometry.Type.Polyline || geom.getType() == Geometry.Type.Polygon)) - return false; - - return true; - } - - /** - * Returns the tolerance for which the rasterized Geometry has been built. - */ - public abstract double getToleranceXY(); - - /** - * Returns raster size in bytes - */ - public abstract int getRasterSize(); - - /** - * Dumps the raster to a bmp file for debug purposes. - * - * @param fileName - * @return true if success, false otherwise. - */ - public abstract boolean dbgSaveToBitmap(String fileName); + public enum HitType { + Outside(0), // the test geometry is well outside the geometry bounds + Inside(1), // the test geometry is well inside the geomety bounds + Border(2); // the test geometry is close to the bounds or intersects the + // bounds + + int enumVal; + + private HitType(int val) { + enumVal = val; + } + + ; + } + + /** + * Test a point against the RasterizedGeometry + */ + public abstract HitType queryPointInGeometry(double x, double y); + + /** + * Test an envelope against the RasterizedGeometry. + */ + public abstract HitType queryEnvelopeInGeometry(Envelope2D env); + + /** + * Creates a rasterized geometry from a given Geometry. + * + * @param geom The input geometry to rasterize. It has to be a MultiVertexGeometry instance. + * @param toleranceXY The tolerance of the rasterization. Raster pixels that are + * closer than given tolerance to the Geometry will be set. + * @param rasterSizeBytes The max size of the raster in bytes. The raster has size of + * rasterSize x rasterSize. Polygons are rasterized into 2 bpp + * (bits per pixel) rasters while other geometries are rasterized + * into 1 bpp rasters. 32x32 pixel raster for a polygon would + * take 256 bytes of memory + */ + public static RasterizedGeometry2D create(Geometry geom, + double toleranceXY, int rasterSizeBytes) { + if (!canUseAccelerator(geom)) + throw new IllegalArgumentException(); + + RasterizedGeometry2DImpl gc = RasterizedGeometry2DImpl.createImpl(geom, + toleranceXY, rasterSizeBytes); + return (RasterizedGeometry2D) gc; + } + + static RasterizedGeometry2D create(MultiVertexGeometryImpl geom, + double toleranceXY, int rasterSizeBytes) { + if (!canUseAccelerator(geom)) + throw new IllegalArgumentException(); + + RasterizedGeometry2DImpl gc = RasterizedGeometry2DImpl.createImpl(geom, + toleranceXY, rasterSizeBytes); + return (RasterizedGeometry2D) gc; + + } + + public static int rasterSizeFromAccelerationDegree( + GeometryAccelerationDegree accelDegree) { + int value = 0; + switch (accelDegree) { + case enumMild: + value = 64 * 64 * 2 / 8;// 1k + break; + case enumMedium: + value = 256 * 256 * 2 / 8;// 16k + break; + case enumHot: + value = 1024 * 1024 * 2 / 8;// 256k + break; + default: + throw GeometryException.GeometryInternalError(); + } + + return value; + } + + /** + * Checks whether the RasterizedGeometry2D accelerator can be used with the + * given geometry. + */ + static boolean canUseAccelerator(Geometry geom) { + if (geom.isEmpty() + || !(geom.getType() == Geometry.Type.Polyline || geom.getType() == Geometry.Type.Polygon)) + return false; + + return true; + } + + /** + * Returns the tolerance for which the rasterized Geometry has been built. + */ + public abstract double getToleranceXY(); + + /** + * Returns raster size in bytes + */ + public abstract int getRasterSize(); + + /** + * Dumps the raster to a bmp file for debug purposes. + * + * @param fileName + * @return true if success, false otherwise. + */ + public abstract boolean dbgSaveToBitmap(String fileName); } diff --git a/src/main/java/com/esri/core/geometry/RasterizedGeometry2DImpl.java b/src/main/java/com/esri/core/geometry/RasterizedGeometry2DImpl.java index fb8179e8..6faf0dac 100644 --- a/src/main/java/com/esri/core/geometry/RasterizedGeometry2DImpl.java +++ b/src/main/java/com/esri/core/geometry/RasterizedGeometry2DImpl.java @@ -38,525 +38,525 @@ import com.esri.core.geometry.SimpleRasterizer; final class RasterizedGeometry2DImpl extends RasterizedGeometry2D { - int[] m_bitmap; - int m_scanLineSize; - int m_width; - double m_dx; - double m_dy; - double m_x0; - double m_y0; - double m_toleranceXY; - double m_stroke_half_widthX_pix; - double m_stroke_half_widthY_pix; - double m_stroke_half_width; - - Envelope2D m_geomEnv;// envelope of the raster in world coordinates - Transformation2D m_transform; - int m_dbgTestCount; - SimpleRasterizer m_rasterizer; - ScanCallbackImpl m_callback; - - class ScanCallbackImpl implements SimpleRasterizer.ScanCallback { - int[] m_bitmap; - int m_scanlineWidth; - int m_color; - - public ScanCallbackImpl(int[] bitmap, int scanlineWidth) { - m_scanlineWidth = scanlineWidth; - m_bitmap = bitmap; - } - - public void setColor(SimpleRasterizer rasterizer, int color) { - if (m_color != color) - rasterizer.flush(); - - m_color = color;// set new color - } - - @Override - public void drawScan(int[] scans, int scanCount3) { - for (int i = 0; i < scanCount3; ) { - int x0 = scans[i++]; - int x1 = scans[i++]; - int y = scans[i++]; - - int scanlineStart = y * m_scanlineWidth; - for (int xx = x0; xx < x1; xx++) { - m_bitmap[scanlineStart + (xx >> 4)] |= m_color << ((xx & 15) * 2);// 2 - // bit - // per - // color - } - } - } - } - - void fillMultiPath(SimpleRasterizer rasterizer, Transformation2D trans, MultiPathImpl polygon, boolean isWinding) { - SegmentIteratorImpl segIter = polygon.querySegmentIterator(); - Point2D p1 = new Point2D(); - Point2D p2 = new Point2D(); - while (segIter.nextPath()) { - while (segIter.hasNextSegment()) { - Segment seg = segIter.nextSegment(); - if (seg.getType() != Geometry.Type.Line) - throw GeometryException.GeometryInternalError(); // TODO: - // densify - // the - // segment - // here - trans.transform(seg.getStartXY(), p1); - trans.transform(seg.getEndXY(), p2); - m_rasterizer.addEdge(p1.x, p1.y, p2.x, p2.y); - } - } - - m_rasterizer.renderEdges(isWinding ? SimpleRasterizer.WINDING : SimpleRasterizer.EVEN_ODD); - } - - void fillPoints(SimpleRasterizer rasterizer, MultiPointImpl geom, double stroke_half_width) { - throw GeometryException.GeometryInternalError(); - } - - void fillConvexPolygon(SimpleRasterizer rasterizer, Point2D[] fan, int len) { - for (int i = 1, n = len; i < n; i++) { - rasterizer.addEdge(fan[i - 1].x, fan[i - 1].y, fan[i].x, fan[i].y); - } - rasterizer.addEdge(fan[len - 1].x, fan[len - 1].y, fan[0].x, fan[0].y); - m_rasterizer.renderEdges(SimpleRasterizer.EVEN_ODD); - } - - void fillEnvelope(SimpleRasterizer rasterizer, Envelope2D envIn) { - rasterizer.fillEnvelope(envIn); - } - - void strokeDrawPolyPath(SimpleRasterizer rasterizer, - MultiPathImpl polyPath, double tol) { - - Point2D[] fan = new Point2D[4]; - for (int i = 0; i < fan.length; i++) - fan[i] = new Point2D(); - - SegmentIteratorImpl segIter = polyPath.querySegmentIterator(); - double strokeHalfWidth = m_transform.transform(tol) + 1.5; - double shortSegment = 0.25; - Point2D vec = new Point2D(); - Point2D vecA = new Point2D(); - Point2D vecB = new Point2D(); - - Point2D ptStart = new Point2D(); - Point2D ptEnd = new Point2D(); - Point2D prev_start = new Point2D(); - Point2D prev_end = new Point2D(); - double[] helper_xy_10_elm = new double[10]; - Envelope2D segEnv = new Envelope2D(); - Point2D ptOld = new Point2D(); - while (segIter.nextPath()) { - boolean hasFan = false; - boolean first = true; - ptOld.setCoords(0, 0); - while (segIter.hasNextSegment()) { - Segment seg = segIter.nextSegment(); - ptStart.x = seg.getStartX(); - ptStart.y = seg.getStartY(); - ptEnd.x = seg.getEndX(); - ptEnd.y = seg.getEndY(); - segEnv.setEmpty(); - segEnv.merge(ptStart.x, ptStart.y); - segEnv.mergeNE(ptEnd.x, ptEnd.y); - if (!m_geomEnv.isIntersectingNE(segEnv)) { - if (hasFan) { - rasterizer.startAddingEdges(); - rasterizer.addSegmentStroke(prev_start.x, prev_start.y, - prev_end.x, prev_end.y, strokeHalfWidth, false, - helper_xy_10_elm); - rasterizer.renderEdges(SimpleRasterizer.EVEN_ODD); - hasFan = false; - } - - first = true; - continue; - } - - m_transform.transform(ptEnd, ptEnd); - - if (first) { - m_transform.transform(ptStart, ptStart); - ptOld.setCoords(ptStart); - first = false; - } else { - ptStart.setCoords(ptOld); - } - - prev_start.setCoords(ptStart); - prev_end.setCoords(ptEnd); - - rasterizer.startAddingEdges(); - hasFan = !rasterizer.addSegmentStroke(prev_start.x, - prev_start.y, prev_end.x, prev_end.y, strokeHalfWidth, - true, helper_xy_10_elm); - rasterizer.renderEdges(SimpleRasterizer.EVEN_ODD); - if (!hasFan) - ptOld.setCoords(prev_end); - } - - if (hasFan) { - rasterizer.startAddingEdges(); - hasFan = !rasterizer.addSegmentStroke(prev_start.x, - prev_start.y, prev_end.x, prev_end.y, strokeHalfWidth, - false, helper_xy_10_elm); - rasterizer.renderEdges(SimpleRasterizer.EVEN_ODD); - } - } - } - - int worldToPixX(double x) { - return (int) (x * m_dx + m_x0); - } - - int worldToPixY(double y) { - return (int) (y * m_dy + m_y0); - } - - RasterizedGeometry2DImpl(Geometry geom, double toleranceXY, - int rasterSizeBytes) { - // //_ASSERT(CanUseAccelerator(geom)); - init((MultiVertexGeometryImpl) geom._getImpl(), toleranceXY, - rasterSizeBytes); - } - - static RasterizedGeometry2DImpl createImpl(Geometry geom, - double toleranceXY, int rasterSizeBytes) { - RasterizedGeometry2DImpl rgImpl = new RasterizedGeometry2DImpl(geom, - toleranceXY, rasterSizeBytes); - - return rgImpl; - } - - private RasterizedGeometry2DImpl(MultiVertexGeometryImpl geom, - double toleranceXY, int rasterSizeBytes) { - init(geom, toleranceXY, rasterSizeBytes); - } - - static RasterizedGeometry2DImpl createImpl(MultiVertexGeometryImpl geom, - double toleranceXY, int rasterSizeBytes) { - RasterizedGeometry2DImpl rgImpl = new RasterizedGeometry2DImpl(geom, - toleranceXY, rasterSizeBytes); - return rgImpl; - } - - void init(MultiVertexGeometryImpl geom, double toleranceXY, - int rasterSizeBytes) { - // _ASSERT(CanUseAccelerator(geom)); - m_width = Math.max((int) (Math.sqrt(rasterSizeBytes) * 2 + 0.5), 64); - m_scanLineSize = (m_width * 2 + 31) / 32; // 2 bits per pixel - m_geomEnv = new Envelope2D(); - - m_toleranceXY = toleranceXY; - - // calculate bitmap size - int size = 0; - int width = m_width; - int scanLineSize = m_scanLineSize; - while (width >= 8) { - size += width * scanLineSize; - width /= 2; - scanLineSize = (width * 2 + 31) / 32; - } - - // allocate the bitmap, that contains the base and the mip-levels - m_bitmap = new int[size]; - for (int i = 0; i < size; i++) - m_bitmap[i] = 0; - - m_rasterizer = new SimpleRasterizer(); - ScanCallbackImpl callback = new ScanCallbackImpl(m_bitmap, - m_scanLineSize); - m_callback = callback; - m_rasterizer.setup(m_width, m_width, callback); - geom.queryEnvelope2D(m_geomEnv); - if (m_geomEnv.getWidth() > m_width * m_geomEnv.getHeight() - || m_geomEnv.getHeight() > m_geomEnv.getWidth() * m_width) { - // the geometry is thin and the rasterizer is not needed. - } - m_geomEnv.inflate(toleranceXY, toleranceXY); - Envelope2D worldEnv = new Envelope2D(); - - Envelope2D pixEnv = Envelope2D - .construct(1, 1, m_width - 2, m_width - 2); - - double minWidth = toleranceXY * pixEnv.getWidth(); // min width is such - // that the size of - // one pixel is - // equal to the - // tolerance - double minHeight = toleranceXY * pixEnv.getHeight(); - - worldEnv.setCoords(m_geomEnv.getCenter(), - Math.max(minWidth, m_geomEnv.getWidth()), - Math.max(minHeight, m_geomEnv.getHeight())); - - m_stroke_half_widthX_pix = worldEnv.getWidth() / pixEnv.getWidth(); - m_stroke_half_widthY_pix = worldEnv.getHeight() / pixEnv.getHeight(); - - // The stroke half width. Later it will be inflated to account for - // pixels size. - m_stroke_half_width = m_toleranceXY; - - m_transform = new Transformation2D(); - m_transform.initializeFromRect(worldEnv, pixEnv);// geom to pixels - - Transformation2D identityTransform = new Transformation2D(); - - switch (geom.getType().value()) { - case Geometry.GeometryType.MultiPoint: - callback.setColor(m_rasterizer, 2); - fillPoints(m_rasterizer, (MultiPointImpl) geom, m_stroke_half_width); - break; - case Geometry.GeometryType.Polyline: - callback.setColor(m_rasterizer, 2); - strokeDrawPolyPath(m_rasterizer, (MultiPathImpl) geom._getImpl(), - m_stroke_half_width); - break; - case Geometry.GeometryType.Polygon: { - boolean isWinding = false;// NOTE: change when winding is supported - callback.setColor(m_rasterizer, 1); - fillMultiPath(m_rasterizer, m_transform, (MultiPathImpl) geom, isWinding); - callback.setColor(m_rasterizer, 2); - strokeDrawPolyPath(m_rasterizer, (MultiPathImpl) geom._getImpl(), - m_stroke_half_width); - } - break; - } - - m_dx = m_transform.xx; - m_dy = m_transform.yy; - m_x0 = m_transform.xd; - m_y0 = m_transform.yd; - buildLevels(); - //dbgSaveToBitmap("c:/temp/_dbg.bmp"); - } - - boolean tryRenderAsSmallEnvelope_(Envelope2D env) { - if (!env.isIntersecting(m_geomEnv)) - return true; - - Envelope2D envPix = new Envelope2D(); - envPix.setCoords(env); - m_transform.transform(env); - double strokeHalfWidthPixX = m_stroke_half_widthX_pix; - double strokeHalfWidthPixY = m_stroke_half_widthY_pix; - if (envPix.getWidth() > 2 * strokeHalfWidthPixX + 1 - || envPix.getHeight() > 2 * strokeHalfWidthPixY + 1) - return false; - - // This envelope is too narrow/small, so that it can be just drawn as a - // rectangle using only boundary color. - - envPix.inflate(strokeHalfWidthPixX, strokeHalfWidthPixY); - envPix.xmax += 1.0; - envPix.ymax += 1.0;// take into account that it does not draw right and - // bottom edges. - - m_callback.setColor(m_rasterizer, 2); - fillEnvelope(m_rasterizer, envPix); - return true; - } - - void buildLevels() { - m_rasterizer.flush(); - int iStart = 0; - int iStartNext = m_width * m_scanLineSize; - int width = m_width; - int widthNext = m_width / 2; - int scanLineSize = m_scanLineSize; - int scanLineSizeNext = (widthNext * 2 + 31) / 32; - while (width > 8) { - for (int iy = 0; iy < widthNext; iy++) { - int iysrc1 = iy * 2; - int iysrc2 = iy * 2 + 1; - for (int ix = 0; ix < widthNext; ix++) { - int ixsrc1 = ix * 2; - int ixsrc2 = ix * 2 + 1; - int divix1 = ixsrc1 >> 4; - int modix1 = (ixsrc1 & 15) * 2; - int divix2 = ixsrc2 >> 4; - int modix2 = (ixsrc2 & 15) * 2; - int res = (m_bitmap[iStart + scanLineSize * iysrc1 + divix1] >> modix1) & 3; - res |= (m_bitmap[iStart + scanLineSize * iysrc1 + divix2] >> modix2) & 3; - res |= (m_bitmap[iStart + scanLineSize * iysrc2 + divix1] >> modix1) & 3; - res |= (m_bitmap[iStart + scanLineSize * iysrc2 + divix2] >> modix2) & 3; - int divixDst = ix >> 4; - int modixDst = (ix & 15) * 2; - m_bitmap[iStartNext + scanLineSizeNext * iy + divixDst] |= res << modixDst; - } - } - - width = widthNext; - scanLineSize = scanLineSizeNext; - iStart = iStartNext; - widthNext = width / 2; - scanLineSizeNext = (widthNext * 2 + 31) / 32; - iStartNext = iStart + scanLineSize * width; - } - } - - @Override - public HitType queryPointInGeometry(double x, double y) { - if (!m_geomEnv.contains(x, y)) - return HitType.Outside; - - int ix = worldToPixX(x); - int iy = worldToPixY(y); - if (ix < 0 || ix >= m_width || iy < 0 || iy >= m_width) - return HitType.Outside; - int divix = ix >> 4; - int modix = (ix & 15) * 2; - int res = (m_bitmap[m_scanLineSize * iy + divix] >> modix) & 3; - if (res == 0) - return HitType.Outside; - else if (res == 1) - return HitType.Inside; - else - return HitType.Border; - } - - @Override - public HitType queryEnvelopeInGeometry(Envelope2D env) { - if (!env.intersect(m_geomEnv)) - return HitType.Outside; - - int ixmin = worldToPixX(env.xmin); - int ixmax = worldToPixX(env.xmax); - int iymin = worldToPixY(env.ymin); - int iymax = worldToPixY(env.ymax); - if (ixmin < 0) - ixmin = 0; - if (iymin < 0) - iymin = 0; - if (ixmax >= m_width) - ixmax = m_width - 1; - if (iymax >= m_width) - iymax = m_width - 1; - - if (ixmin > ixmax || iymin > iymax) - return HitType.Outside; - - int area = Math.max(ixmax - ixmin, 1) * Math.max(iymax - iymin, 1); - int iStart = 0; - int scanLineSize = m_scanLineSize; - int width = m_width; - int res = 0; - while (true) { - if (area < 32 || width < 16) { - for (int iy = iymin; iy <= iymax; iy++) { - for (int ix = ixmin; ix <= ixmax; ix++) { - int divix = ix >> 4; - int modix = (ix & 15) * 2; - res = (m_bitmap[iStart + scanLineSize * iy + divix] >> modix) & 3; // read - // two - // bit - // color. - if (res > 1) - return HitType.Border; - } - } - - if (res == 0) - return HitType.Outside; - else if (res == 1) - return HitType.Inside; - } - - iStart += scanLineSize * width; - width /= 2; - scanLineSize = (width * 2 + 31) / 32; - ixmin /= 2; - iymin /= 2; - ixmax /= 2; - iymax /= 2; - area = Math.max(ixmax - ixmin, 1) * Math.max(iymax - iymin, 1); - } - } - - @Override - public double getToleranceXY() { - return m_toleranceXY; - } - - @Override - public int getRasterSize() { - return m_width * m_scanLineSize; - } - - @Override - public boolean dbgSaveToBitmap(String fileName) { - try { - FileOutputStream outfile = new FileOutputStream(fileName); - - int height = m_width; - int width = m_width; - int sz = 14 + 40 + 4 * m_width * height; - // Write the BITMAPFILEHEADER - ByteBuffer byteBuffer = ByteBuffer.allocate(sz); - byteBuffer.order(ByteOrder.LITTLE_ENDIAN); - // byteBuffer.put((byte) 'M'); - byteBuffer.put((byte) 66); - byteBuffer.put((byte) 77); - // fwrite("BM", 1, 2, f); //bfType - byteBuffer.putInt(sz); - // fwrite(&sz, 1, 4, f);//bfSize - short zero16 = 0; - byteBuffer.putShort(zero16); - // fwrite(&zero16, 1, 2, f);//bfReserved1 - byteBuffer.putShort(zero16); - // fwrite(&zero16, 1, 2, f);//bfReserved2 - int offset = 14 + 40; - byteBuffer.putInt(offset); - // fwrite(&offset, 1, 4, f);//bfOffBits - - // Write the BITMAPINFOHEADER - int biSize = 40; - int biWidth = width; - int biHeight = -height; - short biPlanes = 1; - short biBitCount = 32; - int biCompression = 0; - int biSizeImage = 4 * width * height; - int biXPelsPerMeter = 0; - int biYPelsPerMeter = 0; - int biClrUsed = 0; - int biClrImportant = 0; - byteBuffer.putInt(biSize); - byteBuffer.putInt(biWidth); - byteBuffer.putInt(biHeight); - byteBuffer.putShort(biPlanes); - byteBuffer.putShort(biBitCount); - byteBuffer.putInt(biCompression); - byteBuffer.putInt(biSizeImage); - byteBuffer.putInt(biXPelsPerMeter); - byteBuffer.putInt(biYPelsPerMeter); - byteBuffer.putInt(biClrUsed); - byteBuffer.putInt(biClrImportant); - - int colors[] = {0xFFFFFFFF, 0xFF000000, 0xFFFF0000, 0xFF00FF00}; - // int32_t* rgb4 = (int32_t*)malloc(biSizeImage); - for (int y = 0; y < height; y++) { - int scanlineIn = y * ((width * 2 + 31) / 32); - int scanlineOut = offset + width * y; - - for (int x = 0; x < width; x++) { - int res = (m_bitmap[scanlineIn + (x >> 4)] >> ((x & 15) * 2)) & 3; - byteBuffer.putInt(colors[res]); - } - } - - byte[] b = byteBuffer.array(); - outfile.write(b); - outfile.close(); - return true; - } catch (IOException ex) { - return false; - - } - } + int[] m_bitmap; + int m_scanLineSize; + int m_width; + double m_dx; + double m_dy; + double m_x0; + double m_y0; + double m_toleranceXY; + double m_stroke_half_widthX_pix; + double m_stroke_half_widthY_pix; + double m_stroke_half_width; + + Envelope2D m_geomEnv;// envelope of the raster in world coordinates + Transformation2D m_transform; + int m_dbgTestCount; + SimpleRasterizer m_rasterizer; + ScanCallbackImpl m_callback; + + class ScanCallbackImpl implements SimpleRasterizer.ScanCallback { + int[] m_bitmap; + int m_scanlineWidth; + int m_color; + + public ScanCallbackImpl(int[] bitmap, int scanlineWidth) { + m_scanlineWidth = scanlineWidth; + m_bitmap = bitmap; + } + + public void setColor(SimpleRasterizer rasterizer, int color) { + if (m_color != color) + rasterizer.flush(); + + m_color = color;// set new color + } + + @Override + public void drawScan(int[] scans, int scanCount3) { + for (int i = 0; i < scanCount3; ) { + int x0 = scans[i++]; + int x1 = scans[i++]; + int y = scans[i++]; + + int scanlineStart = y * m_scanlineWidth; + for (int xx = x0; xx < x1; xx++) { + m_bitmap[scanlineStart + (xx >> 4)] |= m_color << ((xx & 15) * 2);// 2 + // bit + // per + // color + } + } + } + } + + void fillMultiPath(SimpleRasterizer rasterizer, Transformation2D trans, MultiPathImpl polygon, boolean isWinding) { + SegmentIteratorImpl segIter = polygon.querySegmentIterator(); + Point2D p1 = new Point2D(); + Point2D p2 = new Point2D(); + while (segIter.nextPath()) { + while (segIter.hasNextSegment()) { + Segment seg = segIter.nextSegment(); + if (seg.getType() != Geometry.Type.Line) + throw GeometryException.GeometryInternalError(); // TODO: + // densify + // the + // segment + // here + trans.transform(seg.getStartXY(), p1); + trans.transform(seg.getEndXY(), p2); + m_rasterizer.addEdge(p1.x, p1.y, p2.x, p2.y); + } + } + + m_rasterizer.renderEdges(isWinding ? SimpleRasterizer.WINDING : SimpleRasterizer.EVEN_ODD); + } + + void fillPoints(SimpleRasterizer rasterizer, MultiPointImpl geom, double stroke_half_width) { + throw GeometryException.GeometryInternalError(); + } + + void fillConvexPolygon(SimpleRasterizer rasterizer, Point2D[] fan, int len) { + for (int i = 1, n = len; i < n; i++) { + rasterizer.addEdge(fan[i - 1].x, fan[i - 1].y, fan[i].x, fan[i].y); + } + rasterizer.addEdge(fan[len - 1].x, fan[len - 1].y, fan[0].x, fan[0].y); + m_rasterizer.renderEdges(SimpleRasterizer.EVEN_ODD); + } + + void fillEnvelope(SimpleRasterizer rasterizer, Envelope2D envIn) { + rasterizer.fillEnvelope(envIn); + } + + void strokeDrawPolyPath(SimpleRasterizer rasterizer, + MultiPathImpl polyPath, double tol) { + + Point2D[] fan = new Point2D[4]; + for (int i = 0; i < fan.length; i++) + fan[i] = new Point2D(); + + SegmentIteratorImpl segIter = polyPath.querySegmentIterator(); + double strokeHalfWidth = m_transform.transform(tol) + 1.5; + double shortSegment = 0.25; + Point2D vec = new Point2D(); + Point2D vecA = new Point2D(); + Point2D vecB = new Point2D(); + + Point2D ptStart = new Point2D(); + Point2D ptEnd = new Point2D(); + Point2D prev_start = new Point2D(); + Point2D prev_end = new Point2D(); + double[] helper_xy_10_elm = new double[10]; + Envelope2D segEnv = new Envelope2D(); + Point2D ptOld = new Point2D(); + while (segIter.nextPath()) { + boolean hasFan = false; + boolean first = true; + ptOld.setCoords(0, 0); + while (segIter.hasNextSegment()) { + Segment seg = segIter.nextSegment(); + ptStart.x = seg.getStartX(); + ptStart.y = seg.getStartY(); + ptEnd.x = seg.getEndX(); + ptEnd.y = seg.getEndY(); + segEnv.setEmpty(); + segEnv.merge(ptStart.x, ptStart.y); + segEnv.mergeNE(ptEnd.x, ptEnd.y); + if (!m_geomEnv.isIntersectingNE(segEnv)) { + if (hasFan) { + rasterizer.startAddingEdges(); + rasterizer.addSegmentStroke(prev_start.x, prev_start.y, + prev_end.x, prev_end.y, strokeHalfWidth, false, + helper_xy_10_elm); + rasterizer.renderEdges(SimpleRasterizer.EVEN_ODD); + hasFan = false; + } + + first = true; + continue; + } + + m_transform.transform(ptEnd, ptEnd); + + if (first) { + m_transform.transform(ptStart, ptStart); + ptOld.setCoords(ptStart); + first = false; + } else { + ptStart.setCoords(ptOld); + } + + prev_start.setCoords(ptStart); + prev_end.setCoords(ptEnd); + + rasterizer.startAddingEdges(); + hasFan = !rasterizer.addSegmentStroke(prev_start.x, + prev_start.y, prev_end.x, prev_end.y, strokeHalfWidth, + true, helper_xy_10_elm); + rasterizer.renderEdges(SimpleRasterizer.EVEN_ODD); + if (!hasFan) + ptOld.setCoords(prev_end); + } + + if (hasFan) { + rasterizer.startAddingEdges(); + hasFan = !rasterizer.addSegmentStroke(prev_start.x, + prev_start.y, prev_end.x, prev_end.y, strokeHalfWidth, + false, helper_xy_10_elm); + rasterizer.renderEdges(SimpleRasterizer.EVEN_ODD); + } + } + } + + int worldToPixX(double x) { + return (int) (x * m_dx + m_x0); + } + + int worldToPixY(double y) { + return (int) (y * m_dy + m_y0); + } + + RasterizedGeometry2DImpl(Geometry geom, double toleranceXY, + int rasterSizeBytes) { + // //_ASSERT(CanUseAccelerator(geom)); + init((MultiVertexGeometryImpl) geom._getImpl(), toleranceXY, + rasterSizeBytes); + } + + static RasterizedGeometry2DImpl createImpl(Geometry geom, + double toleranceXY, int rasterSizeBytes) { + RasterizedGeometry2DImpl rgImpl = new RasterizedGeometry2DImpl(geom, + toleranceXY, rasterSizeBytes); + + return rgImpl; + } + + private RasterizedGeometry2DImpl(MultiVertexGeometryImpl geom, + double toleranceXY, int rasterSizeBytes) { + init(geom, toleranceXY, rasterSizeBytes); + } + + static RasterizedGeometry2DImpl createImpl(MultiVertexGeometryImpl geom, + double toleranceXY, int rasterSizeBytes) { + RasterizedGeometry2DImpl rgImpl = new RasterizedGeometry2DImpl(geom, + toleranceXY, rasterSizeBytes); + return rgImpl; + } + + void init(MultiVertexGeometryImpl geom, double toleranceXY, + int rasterSizeBytes) { + // _ASSERT(CanUseAccelerator(geom)); + m_width = Math.max((int) (Math.sqrt(rasterSizeBytes) * 2 + 0.5), 64); + m_scanLineSize = (m_width * 2 + 31) / 32; // 2 bits per pixel + m_geomEnv = new Envelope2D(); + + m_toleranceXY = toleranceXY; + + // calculate bitmap size + int size = 0; + int width = m_width; + int scanLineSize = m_scanLineSize; + while (width >= 8) { + size += width * scanLineSize; + width /= 2; + scanLineSize = (width * 2 + 31) / 32; + } + + // allocate the bitmap, that contains the base and the mip-levels + m_bitmap = new int[size]; + for (int i = 0; i < size; i++) + m_bitmap[i] = 0; + + m_rasterizer = new SimpleRasterizer(); + ScanCallbackImpl callback = new ScanCallbackImpl(m_bitmap, + m_scanLineSize); + m_callback = callback; + m_rasterizer.setup(m_width, m_width, callback); + geom.queryEnvelope2D(m_geomEnv); + if (m_geomEnv.getWidth() > m_width * m_geomEnv.getHeight() + || m_geomEnv.getHeight() > m_geomEnv.getWidth() * m_width) { + // the geometry is thin and the rasterizer is not needed. + } + m_geomEnv.inflate(toleranceXY, toleranceXY); + Envelope2D worldEnv = new Envelope2D(); + + Envelope2D pixEnv = Envelope2D + .construct(1, 1, m_width - 2, m_width - 2); + + double minWidth = toleranceXY * pixEnv.getWidth(); // min width is such + // that the size of + // one pixel is + // equal to the + // tolerance + double minHeight = toleranceXY * pixEnv.getHeight(); + + worldEnv.setCoords(m_geomEnv.getCenter(), + Math.max(minWidth, m_geomEnv.getWidth()), + Math.max(minHeight, m_geomEnv.getHeight())); + + m_stroke_half_widthX_pix = worldEnv.getWidth() / pixEnv.getWidth(); + m_stroke_half_widthY_pix = worldEnv.getHeight() / pixEnv.getHeight(); + + // The stroke half width. Later it will be inflated to account for + // pixels size. + m_stroke_half_width = m_toleranceXY; + + m_transform = new Transformation2D(); + m_transform.initializeFromRect(worldEnv, pixEnv);// geom to pixels + + Transformation2D identityTransform = new Transformation2D(); + + switch (geom.getType().value()) { + case Geometry.GeometryType.MultiPoint: + callback.setColor(m_rasterizer, 2); + fillPoints(m_rasterizer, (MultiPointImpl) geom, m_stroke_half_width); + break; + case Geometry.GeometryType.Polyline: + callback.setColor(m_rasterizer, 2); + strokeDrawPolyPath(m_rasterizer, (MultiPathImpl) geom._getImpl(), + m_stroke_half_width); + break; + case Geometry.GeometryType.Polygon: { + boolean isWinding = false;// NOTE: change when winding is supported + callback.setColor(m_rasterizer, 1); + fillMultiPath(m_rasterizer, m_transform, (MultiPathImpl) geom, isWinding); + callback.setColor(m_rasterizer, 2); + strokeDrawPolyPath(m_rasterizer, (MultiPathImpl) geom._getImpl(), + m_stroke_half_width); + } + break; + } + + m_dx = m_transform.xx; + m_dy = m_transform.yy; + m_x0 = m_transform.xd; + m_y0 = m_transform.yd; + buildLevels(); + //dbgSaveToBitmap("c:/temp/_dbg.bmp"); + } + + boolean tryRenderAsSmallEnvelope_(Envelope2D env) { + if (!env.isIntersecting(m_geomEnv)) + return true; + + Envelope2D envPix = new Envelope2D(); + envPix.setCoords(env); + m_transform.transform(env); + double strokeHalfWidthPixX = m_stroke_half_widthX_pix; + double strokeHalfWidthPixY = m_stroke_half_widthY_pix; + if (envPix.getWidth() > 2 * strokeHalfWidthPixX + 1 + || envPix.getHeight() > 2 * strokeHalfWidthPixY + 1) + return false; + + // This envelope is too narrow/small, so that it can be just drawn as a + // rectangle using only boundary color. + + envPix.inflate(strokeHalfWidthPixX, strokeHalfWidthPixY); + envPix.xmax += 1.0; + envPix.ymax += 1.0;// take into account that it does not draw right and + // bottom edges. + + m_callback.setColor(m_rasterizer, 2); + fillEnvelope(m_rasterizer, envPix); + return true; + } + + void buildLevels() { + m_rasterizer.flush(); + int iStart = 0; + int iStartNext = m_width * m_scanLineSize; + int width = m_width; + int widthNext = m_width / 2; + int scanLineSize = m_scanLineSize; + int scanLineSizeNext = (widthNext * 2 + 31) / 32; + while (width > 8) { + for (int iy = 0; iy < widthNext; iy++) { + int iysrc1 = iy * 2; + int iysrc2 = iy * 2 + 1; + for (int ix = 0; ix < widthNext; ix++) { + int ixsrc1 = ix * 2; + int ixsrc2 = ix * 2 + 1; + int divix1 = ixsrc1 >> 4; + int modix1 = (ixsrc1 & 15) * 2; + int divix2 = ixsrc2 >> 4; + int modix2 = (ixsrc2 & 15) * 2; + int res = (m_bitmap[iStart + scanLineSize * iysrc1 + divix1] >> modix1) & 3; + res |= (m_bitmap[iStart + scanLineSize * iysrc1 + divix2] >> modix2) & 3; + res |= (m_bitmap[iStart + scanLineSize * iysrc2 + divix1] >> modix1) & 3; + res |= (m_bitmap[iStart + scanLineSize * iysrc2 + divix2] >> modix2) & 3; + int divixDst = ix >> 4; + int modixDst = (ix & 15) * 2; + m_bitmap[iStartNext + scanLineSizeNext * iy + divixDst] |= res << modixDst; + } + } + + width = widthNext; + scanLineSize = scanLineSizeNext; + iStart = iStartNext; + widthNext = width / 2; + scanLineSizeNext = (widthNext * 2 + 31) / 32; + iStartNext = iStart + scanLineSize * width; + } + } + + @Override + public HitType queryPointInGeometry(double x, double y) { + if (!m_geomEnv.contains(x, y)) + return HitType.Outside; + + int ix = worldToPixX(x); + int iy = worldToPixY(y); + if (ix < 0 || ix >= m_width || iy < 0 || iy >= m_width) + return HitType.Outside; + int divix = ix >> 4; + int modix = (ix & 15) * 2; + int res = (m_bitmap[m_scanLineSize * iy + divix] >> modix) & 3; + if (res == 0) + return HitType.Outside; + else if (res == 1) + return HitType.Inside; + else + return HitType.Border; + } + + @Override + public HitType queryEnvelopeInGeometry(Envelope2D env) { + if (!env.intersect(m_geomEnv)) + return HitType.Outside; + + int ixmin = worldToPixX(env.xmin); + int ixmax = worldToPixX(env.xmax); + int iymin = worldToPixY(env.ymin); + int iymax = worldToPixY(env.ymax); + if (ixmin < 0) + ixmin = 0; + if (iymin < 0) + iymin = 0; + if (ixmax >= m_width) + ixmax = m_width - 1; + if (iymax >= m_width) + iymax = m_width - 1; + + if (ixmin > ixmax || iymin > iymax) + return HitType.Outside; + + int area = Math.max(ixmax - ixmin, 1) * Math.max(iymax - iymin, 1); + int iStart = 0; + int scanLineSize = m_scanLineSize; + int width = m_width; + int res = 0; + while (true) { + if (area < 32 || width < 16) { + for (int iy = iymin; iy <= iymax; iy++) { + for (int ix = ixmin; ix <= ixmax; ix++) { + int divix = ix >> 4; + int modix = (ix & 15) * 2; + res = (m_bitmap[iStart + scanLineSize * iy + divix] >> modix) & 3; // read + // two + // bit + // color. + if (res > 1) + return HitType.Border; + } + } + + if (res == 0) + return HitType.Outside; + else if (res == 1) + return HitType.Inside; + } + + iStart += scanLineSize * width; + width /= 2; + scanLineSize = (width * 2 + 31) / 32; + ixmin /= 2; + iymin /= 2; + ixmax /= 2; + iymax /= 2; + area = Math.max(ixmax - ixmin, 1) * Math.max(iymax - iymin, 1); + } + } + + @Override + public double getToleranceXY() { + return m_toleranceXY; + } + + @Override + public int getRasterSize() { + return m_width * m_scanLineSize; + } + + @Override + public boolean dbgSaveToBitmap(String fileName) { + try { + FileOutputStream outfile = new FileOutputStream(fileName); + + int height = m_width; + int width = m_width; + int sz = 14 + 40 + 4 * m_width * height; + // Write the BITMAPFILEHEADER + ByteBuffer byteBuffer = ByteBuffer.allocate(sz); + byteBuffer.order(ByteOrder.LITTLE_ENDIAN); + // byteBuffer.put((byte) 'M'); + byteBuffer.put((byte) 66); + byteBuffer.put((byte) 77); + // fwrite("BM", 1, 2, f); //bfType + byteBuffer.putInt(sz); + // fwrite(&sz, 1, 4, f);//bfSize + short zero16 = 0; + byteBuffer.putShort(zero16); + // fwrite(&zero16, 1, 2, f);//bfReserved1 + byteBuffer.putShort(zero16); + // fwrite(&zero16, 1, 2, f);//bfReserved2 + int offset = 14 + 40; + byteBuffer.putInt(offset); + // fwrite(&offset, 1, 4, f);//bfOffBits + + // Write the BITMAPINFOHEADER + int biSize = 40; + int biWidth = width; + int biHeight = -height; + short biPlanes = 1; + short biBitCount = 32; + int biCompression = 0; + int biSizeImage = 4 * width * height; + int biXPelsPerMeter = 0; + int biYPelsPerMeter = 0; + int biClrUsed = 0; + int biClrImportant = 0; + byteBuffer.putInt(biSize); + byteBuffer.putInt(biWidth); + byteBuffer.putInt(biHeight); + byteBuffer.putShort(biPlanes); + byteBuffer.putShort(biBitCount); + byteBuffer.putInt(biCompression); + byteBuffer.putInt(biSizeImage); + byteBuffer.putInt(biXPelsPerMeter); + byteBuffer.putInt(biYPelsPerMeter); + byteBuffer.putInt(biClrUsed); + byteBuffer.putInt(biClrImportant); + + int colors[] = {0xFFFFFFFF, 0xFF000000, 0xFFFF0000, 0xFF00FF00}; + // int32_t* rgb4 = (int32_t*)malloc(biSizeImage); + for (int y = 0; y < height; y++) { + int scanlineIn = y * ((width * 2 + 31) / 32); + int scanlineOut = offset + width * y; + + for (int x = 0; x < width; x++) { + int res = (m_bitmap[scanlineIn + (x >> 4)] >> ((x & 15) * 2)) & 3; + byteBuffer.putInt(colors[res]); + } + } + + byte[] b = byteBuffer.array(); + outfile.write(b); + outfile.close(); + return true; + } catch (IOException ex) { + return false; + + } + } } diff --git a/src/main/java/com/esri/core/geometry/RelationalOperations.java b/src/main/java/com/esri/core/geometry/RelationalOperations.java index c36e78dc..d95c9d1d 100644 --- a/src/main/java/com/esri/core/geometry/RelationalOperations.java +++ b/src/main/java/com/esri/core/geometry/RelationalOperations.java @@ -26,5208 +26,5208 @@ import java.util.ArrayList; class RelationalOperations { - interface Relation { - static final int contains = 1; - static final int within = 2; - static final int equals = 3; - static final int disjoint = 4; - static final int touches = 8; - static final int crosses = 16; - static final int overlaps = 32; - - static final int unknown = 0; - static final int intersects = 0x40000000; - } - - static boolean relate(Geometry geometry_a, Geometry geometry_b, - SpatialReference sr, int relation, ProgressTracker progress_tracker) { - int type_a = geometry_a.getType().value(); - int type_b = geometry_b.getType().value(); - - // Give preference to the Point vs Envelope, Envelope vs Envelope and - // Point vs Point realtions: - if (type_a == Geometry.GeometryType.Envelope) { - if (type_b == Geometry.GeometryType.Envelope) { - return relate((Envelope) geometry_a, (Envelope) geometry_b, sr, - relation, progress_tracker); - } else if (type_b == Geometry.GeometryType.Point) { - if (relation == Relation.within) - relation = Relation.contains; - else if (relation == Relation.contains) - relation = Relation.within; - - return relate((Point) geometry_b, (Envelope) geometry_a, sr, - relation, progress_tracker); - } else { - // proceed below - } - } else if (type_a == Geometry.GeometryType.Point) { - if (type_b == Geometry.GeometryType.Envelope) { - return relate((Point) geometry_a, (Envelope) geometry_b, sr, - relation, progress_tracker); - } else if (type_b == Geometry.GeometryType.Point) { - return relate((Point) geometry_a, (Point) geometry_b, sr, - relation, progress_tracker); - } else { - // proceed below - } - } else { - // proceed below - } - - if (geometry_a.isEmpty() || geometry_b.isEmpty()) { - if (relation == Relation.disjoint) - return true; // Always true - - return false; // Always false - } - - Envelope2D env1 = new Envelope2D(); - geometry_a.queryEnvelope2D(env1); - Envelope2D env2 = new Envelope2D(); - geometry_b.queryEnvelope2D(env2); - - Envelope2D envMerged = new Envelope2D(); - envMerged.setCoords(env1); - envMerged.merge(env2); - double tolerance = InternalUtils.calculateToleranceFromGeometry(sr, - envMerged, false); - - if (envelopeDisjointEnvelope_(env1, env2, tolerance, progress_tracker)) { - if (relation == Relation.disjoint) - return true; - - return false; - } - - boolean bRelation = false; - - Geometry _geometry_a; - Geometry _geometry_b; - Polyline polyline_a, polyline_b; - - if (MultiPath.isSegment(type_a)) { - polyline_a = new Polyline(geometry_a.getDescription()); - polyline_a.addSegment((Segment) geometry_a, true); - _geometry_a = polyline_a; - type_a = Geometry.GeometryType.Polyline; - } else { - _geometry_a = geometry_a; - } - - if (MultiPath.isSegment(type_b)) { - polyline_b = new Polyline(geometry_b.getDescription()); - polyline_b.addSegment((Segment) geometry_b, true); - _geometry_b = polyline_b; - type_b = Geometry.GeometryType.Polyline; - } else { - _geometry_b = geometry_b; - } - - if (type_a != Geometry.GeometryType.Envelope - && type_b != Geometry.GeometryType.Envelope) { - if (_geometry_a.getDimension() < _geometry_b.getDimension() - || (type_a == Geometry.GeometryType.Point && type_b == Geometry.GeometryType.MultiPoint)) {// we - // will - // switch - // the - // order - // of - // the - // geometries - // below. - if (relation == Relation.within) - relation = Relation.contains; - else if (relation == Relation.contains) - relation = Relation.within; - } - } else { - if (type_a != Geometry.GeometryType.Polygon - && type_b != Geometry.GeometryType.Envelope) { // we will - // switch - // the order - // of the - // geometries - // below. - if (relation == Relation.within) - relation = Relation.contains; - else if (relation == Relation.contains) - relation = Relation.within; - } - } - - switch (type_a) { - case Geometry.GeometryType.Polygon: - switch (type_b) { - case Geometry.GeometryType.Polygon: - bRelation = polygonRelatePolygon_((Polygon) (_geometry_a), - (Polygon) (_geometry_b), tolerance, relation, - progress_tracker); - break; - - case Geometry.GeometryType.Polyline: - bRelation = polygonRelatePolyline_((Polygon) (_geometry_a), - (Polyline) (_geometry_b), tolerance, relation, - progress_tracker); - break; - - case Geometry.GeometryType.Point: - bRelation = polygonRelatePoint_((Polygon) (_geometry_a), - (Point) (_geometry_b), tolerance, relation, - progress_tracker); - break; - - case Geometry.GeometryType.MultiPoint: - bRelation = polygonRelateMultiPoint_((Polygon) (_geometry_a), - (MultiPoint) (_geometry_b), tolerance, relation, - progress_tracker); - break; - - case Geometry.GeometryType.Envelope: - bRelation = polygonRelateEnvelope_((Polygon) (_geometry_a), - (Envelope) (_geometry_b), tolerance, relation, - progress_tracker); - break; - - default: - break; // warning fix - } - break; - - case Geometry.GeometryType.Polyline: - switch (type_b) { - case Geometry.GeometryType.Polygon: - bRelation = polygonRelatePolyline_((Polygon) (_geometry_b), - (Polyline) (_geometry_a), tolerance, relation, - progress_tracker); - break; - - case Geometry.GeometryType.Polyline: - bRelation = polylineRelatePolyline_((Polyline) (_geometry_a), - (Polyline) (_geometry_b), tolerance, relation, - progress_tracker); - break; - - case Geometry.GeometryType.Point: - bRelation = polylineRelatePoint_((Polyline) (_geometry_a), - (Point) (_geometry_b), tolerance, relation, - progress_tracker); - break; - - case Geometry.GeometryType.MultiPoint: - bRelation = polylineRelateMultiPoint_((Polyline) (_geometry_a), - (MultiPoint) (_geometry_b), tolerance, relation, - progress_tracker); - break; - - case Geometry.GeometryType.Envelope: - bRelation = polylineRelateEnvelope_((Polyline) (_geometry_a), - (Envelope) (_geometry_b), tolerance, relation, - progress_tracker); - break; - - default: - break; // warning fix - } - break; - - case Geometry.GeometryType.Point: - switch (type_b) { - case Geometry.GeometryType.Polygon: - bRelation = polygonRelatePoint_((Polygon) (_geometry_b), - (Point) (_geometry_a), tolerance, relation, - progress_tracker); - break; - - case Geometry.GeometryType.Polyline: - bRelation = polylineRelatePoint_((Polyline) (_geometry_b), - (Point) (_geometry_a), tolerance, relation, - progress_tracker); - break; - - case Geometry.GeometryType.MultiPoint: - bRelation = multiPointRelatePoint_((MultiPoint) (_geometry_b), - (Point) (_geometry_a), tolerance, relation, - progress_tracker); - break; - - default: - break; // warning fix - } - break; - - case Geometry.GeometryType.MultiPoint: - switch (type_b) { - case Geometry.GeometryType.Polygon: - bRelation = polygonRelateMultiPoint_((Polygon) (_geometry_b), - (MultiPoint) (_geometry_a), tolerance, relation, - progress_tracker); - break; - - case Geometry.GeometryType.Polyline: - bRelation = polylineRelateMultiPoint_((Polyline) (_geometry_b), - (MultiPoint) (_geometry_a), tolerance, relation, - progress_tracker); - break; - - case Geometry.GeometryType.MultiPoint: - bRelation = multiPointRelateMultiPoint_( - (MultiPoint) (_geometry_a), (MultiPoint) (_geometry_b), - tolerance, relation, progress_tracker); - break; - - case Geometry.GeometryType.Point: - bRelation = multiPointRelatePoint_((MultiPoint) (_geometry_a), - (Point) (_geometry_b), tolerance, relation, - progress_tracker); - break; - - case Geometry.GeometryType.Envelope: - bRelation = multiPointRelateEnvelope_( - (MultiPoint) (_geometry_a), (Envelope) (_geometry_b), - tolerance, relation, progress_tracker); - break; - - default: - break; // warning fix - } - break; - - case Geometry.GeometryType.Envelope: - switch (type_b) { - case Geometry.GeometryType.Polygon: - bRelation = polygonRelateEnvelope_((Polygon) (_geometry_b), - (Envelope) (_geometry_a), tolerance, relation, - progress_tracker); - break; - - case Geometry.GeometryType.Polyline: - bRelation = polylineRelateEnvelope_((Polyline) (_geometry_b), - (Envelope) (_geometry_a), tolerance, relation, - progress_tracker); - break; - - case Geometry.GeometryType.MultiPoint: - bRelation = multiPointRelateEnvelope_( - (MultiPoint) (_geometry_b), (Envelope) (_geometry_a), - tolerance, relation, progress_tracker); - break; - - default: - break; // warning fix - } - break; - - default: - break; // warning fix - } - - return bRelation; - } - - // Computes the necessary 9 intersection relationships of boundary, - // interior, and exterior of envelope_a vs envelope_b for the given - // relation. - private static boolean relate(Envelope envelope_a, Envelope envelope_b, - SpatialReference sr, int relation, ProgressTracker progress_tracker) { - if (envelope_a.isEmpty() || envelope_b.isEmpty()) { - if (relation == Relation.disjoint) - return true; // Always true - - return false; // Always false - } - - Envelope2D env_a = new Envelope2D(), env_b = new Envelope2D(), env_merged = new Envelope2D(); - envelope_a.queryEnvelope2D(env_a); - envelope_b.queryEnvelope2D(env_b); - env_merged.setCoords(env_a); - env_merged.merge(env_b); - - double tolerance = InternalUtils.calculateToleranceFromGeometry(sr, - env_merged, false); - - switch (relation) { - case Relation.disjoint: - return envelopeDisjointEnvelope_(env_a, env_b, tolerance, - progress_tracker); - - case Relation.within: - return envelopeContainsEnvelope_(env_b, env_a, tolerance, - progress_tracker); - - case Relation.contains: - return envelopeContainsEnvelope_(env_a, env_b, tolerance, - progress_tracker); - - case Relation.equals: - return envelopeEqualsEnvelope_(env_a, env_b, tolerance, - progress_tracker); - - case Relation.touches: - return envelopeTouchesEnvelope_(env_a, env_b, tolerance, - progress_tracker); - - case Relation.overlaps: - return envelopeOverlapsEnvelope_(env_a, env_b, tolerance, - progress_tracker); - - case Relation.crosses: - return envelopeCrossesEnvelope_(env_a, env_b, tolerance, - progress_tracker); - - default: - break; // warning fix - } - - return false; - } - - // Computes the necessary 9 intersection relationships of boundary, - // interior, and exterior of point_a vs envelope_b for the given relation. - private static boolean relate(Point point_a, Envelope envelope_b, - SpatialReference sr, int relation, ProgressTracker progress_tracker) { - if (point_a.isEmpty() || envelope_b.isEmpty()) { - if (relation == Relation.disjoint) - return true; // Always true - - return false; // Always false - } - - Point2D pt_a = point_a.getXY(); - Envelope2D env_b = new Envelope2D(), env_merged = new Envelope2D(); - envelope_b.queryEnvelope2D(env_b); - env_merged.setCoords(pt_a); - env_merged.merge(env_b); - - double tolerance = InternalUtils.calculateToleranceFromGeometry(sr, - env_merged, false); - - switch (relation) { - case Relation.disjoint: - return pointDisjointEnvelope_(pt_a, env_b, tolerance, - progress_tracker); - - case Relation.within: - return pointWithinEnvelope_(pt_a, env_b, tolerance, - progress_tracker); - - case Relation.contains: - return pointContainsEnvelope_(pt_a, env_b, tolerance, - progress_tracker); - - case Relation.equals: - return pointEqualsEnvelope_(pt_a, env_b, tolerance, - progress_tracker); - - case Relation.touches: - return pointTouchesEnvelope_(pt_a, env_b, tolerance, - progress_tracker); - - default: - break; // warning fix - } - - return false; - } - - // Computes the necessary 9 intersection relationships of boundary, - // interior, and exterior of point_a vs point_b for the given relation. - private static boolean relate(Point point_a, Point point_b, - SpatialReference sr, int relation, ProgressTracker progress_tracker) { - if (point_a.isEmpty() || point_b.isEmpty()) { - if (relation == Relation.disjoint) - return true; // Always true - - return false; // Always false - } - - Point2D pt_a = point_a.getXY(); - Point2D pt_b = point_b.getXY(); - Envelope2D env_merged = new Envelope2D(); - env_merged.setCoords(pt_a); - env_merged.merge(pt_b); - - double tolerance = InternalUtils.calculateToleranceFromGeometry(sr, - env_merged, false); - - switch (relation) { - case Relation.disjoint: - return pointDisjointPoint_(pt_a, pt_b, tolerance, progress_tracker); - - case Relation.within: - return pointContainsPoint_(pt_b, pt_a, tolerance, progress_tracker); - - case Relation.contains: - return pointContainsPoint_(pt_a, pt_b, tolerance, progress_tracker); - - case Relation.equals: - return pointEqualsPoint_(pt_a, pt_b, tolerance, progress_tracker); - - default: - break; // warning fix - } - - return false; - } - - // Returns true if the relation holds. - private static boolean polygonRelatePolygon_(Polygon polygon_a, - Polygon polygon_b, double tolerance, int relation, - ProgressTracker progress_tracker) { - switch (relation) { - case Relation.disjoint: - return polygonDisjointPolygon_(polygon_a, polygon_b, tolerance, - progress_tracker); - - case Relation.within: - return polygonContainsPolygon_(polygon_b, polygon_a, tolerance, - progress_tracker); - - case Relation.contains: - return polygonContainsPolygon_(polygon_a, polygon_b, tolerance, - progress_tracker); - - case Relation.equals: - return polygonEqualsPolygon_(polygon_a, polygon_b, tolerance, - progress_tracker); - - case Relation.touches: - return polygonTouchesPolygon_(polygon_a, polygon_b, tolerance, - progress_tracker); - - case Relation.overlaps: - return polygonOverlapsPolygon_(polygon_a, polygon_b, tolerance, - progress_tracker); - - default: - break; // warning fix - } - - return false; - } - - // Returns true if the relation holds. - private static boolean polygonRelatePolyline_(Polygon polygon_a, - Polyline polyline_b, double tolerance, int relation, - ProgressTracker progress_tracker) { - switch (relation) { - case Relation.disjoint: - return polygonDisjointPolyline_(polygon_a, polyline_b, tolerance, - progress_tracker); - - case Relation.contains: - return polygonContainsPolyline_(polygon_a, polyline_b, tolerance, - progress_tracker); - - case Relation.touches: - return polygonTouchesPolyline_(polygon_a, polyline_b, tolerance, - progress_tracker); - - case Relation.crosses: - return polygonCrossesPolyline_(polygon_a, polyline_b, tolerance, - progress_tracker); - - default: - break; // warning fix - } - - return false; - } - - // Returns true if the relation holds. - private static boolean polygonRelatePoint_(Polygon polygon_a, - Point point_b, double tolerance, int relation, - ProgressTracker progress_tracker) { - switch (relation) { - case Relation.disjoint: - return polygonDisjointPoint_(polygon_a, point_b, tolerance, - progress_tracker); - - case Relation.contains: - return polygonContainsPoint_(polygon_a, point_b, tolerance, - progress_tracker); - - case Relation.touches: - return polygonTouchesPoint_(polygon_a, point_b, tolerance, - progress_tracker); - - default: - break; // warning fix - } - - return false; - } - - // Returns true if the relation holds - private static boolean polygonRelateMultiPoint_(Polygon polygon_a, - MultiPoint multipoint_b, double tolerance, int relation, - ProgressTracker progress_tracker) { - switch (relation) { - case Relation.disjoint: - return polygonDisjointMultiPoint_(polygon_a, multipoint_b, - tolerance, true, progress_tracker); - - case Relation.contains: - return polygonContainsMultiPoint_(polygon_a, multipoint_b, - tolerance, progress_tracker); - - case Relation.touches: - return polygonTouchesMultiPoint_(polygon_a, multipoint_b, - tolerance, progress_tracker); - - case Relation.crosses: - return polygonCrossesMultiPoint_(polygon_a, multipoint_b, - tolerance, progress_tracker); - - default: - break; // warning fix - } - - return false; - } - - // Returns true if the relation holds - private static boolean polygonRelateEnvelope_(Polygon polygon_a, - Envelope envelope_b, double tolerance, int relation, - ProgressTracker progress_tracker) { - if (polygonDisjointEnvelope_(polygon_a, envelope_b, tolerance, - progress_tracker)) { - if (relation == Relation.disjoint) - return true; - - return false; - } else if (relation == Relation.disjoint) { - return false; - } - - switch (relation) { - case Relation.within: - return polygonWithinEnvelope_(polygon_a, envelope_b, tolerance, - progress_tracker); - - case Relation.contains: - return polygonContainsEnvelope_(polygon_a, envelope_b, tolerance, - progress_tracker); - - case Relation.equals: - return polygonEqualsEnvelope_(polygon_a, envelope_b, tolerance, - progress_tracker); - - case Relation.touches: - return polygonTouchesEnvelope_(polygon_a, envelope_b, tolerance, - progress_tracker); - - case Relation.overlaps: - return polygonOverlapsEnvelope_(polygon_a, envelope_b, tolerance, - progress_tracker); - - case Relation.crosses: - return polygonCrossesEnvelope_(polygon_a, envelope_b, tolerance, - progress_tracker); - - default: - break; // warning fix - } - - return false; - } - - // Returns true if the relation holds. - private static boolean polylineRelatePolyline_(Polyline polyline_a, - Polyline polyline_b, double tolerance, int relation, - ProgressTracker progress_tracker) { - switch (relation) { - case Relation.disjoint: - return polylineDisjointPolyline_(polyline_a, polyline_b, tolerance, - progress_tracker); - - case Relation.within: - return polylineContainsPolyline_(polyline_b, polyline_a, tolerance, - progress_tracker); - - case Relation.contains: - return polylineContainsPolyline_(polyline_a, polyline_b, tolerance, - progress_tracker); - - case Relation.equals: - return polylineEqualsPolyline_(polyline_a, polyline_b, tolerance, - progress_tracker); - - case Relation.touches: - return polylineTouchesPolyline_(polyline_a, polyline_b, tolerance, - progress_tracker); - - case Relation.overlaps: - return polylineOverlapsPolyline_(polyline_a, polyline_b, tolerance, - progress_tracker); - - case Relation.crosses: - return polylineCrossesPolyline_(polyline_a, polyline_b, tolerance, - progress_tracker); - - default: - break; // warning fix - } - - return false; - } - - // Returns true if the relation holds. - private static boolean polylineRelatePoint_(Polyline polyline_a, - Point point_b, double tolerance, int relation, - ProgressTracker progress_tracker) { - switch (relation) { - case Relation.disjoint: - return polylineDisjointPoint_(polyline_a, point_b, tolerance, - progress_tracker); - - case Relation.contains: - return polylineContainsPoint_(polyline_a, point_b, tolerance, - progress_tracker); - - case Relation.touches: - return polylineTouchesPoint_(polyline_a, point_b, tolerance, - progress_tracker); - - default: - break; // warning fix - } - - return false; - } - - // Returns true if the relation holds. - private static boolean polylineRelateMultiPoint_(Polyline polyline_a, - MultiPoint multipoint_b, double tolerance, int relation, - ProgressTracker progress_tracker) { - switch (relation) { - case Relation.disjoint: - return polylineDisjointMultiPoint_(polyline_a, multipoint_b, - tolerance, progress_tracker); - - case Relation.contains: - return polylineContainsMultiPoint_(polyline_a, multipoint_b, - tolerance, progress_tracker); - - case Relation.touches: - return polylineTouchesMultiPoint_(polyline_a, multipoint_b, - tolerance, progress_tracker); - - case Relation.crosses: - return polylineCrossesMultiPoint_(polyline_a, multipoint_b, - tolerance, progress_tracker); - - default: - break; // warning fix - } - - return false; - } - - // Returns true if the relation holds. - private static boolean polylineRelateEnvelope_(Polyline polyline_a, - Envelope envelope_b, double tolerance, int relation, - ProgressTracker progress_tracker) { - if (polylineDisjointEnvelope_(polyline_a, envelope_b, tolerance, - progress_tracker)) { - if (relation == Relation.disjoint) - return true; - - return false; - } else if (relation == Relation.disjoint) { - return false; - } - - switch (relation) { - case Relation.within: - return polylineWithinEnvelope_(polyline_a, envelope_b, tolerance, - progress_tracker); - - case Relation.contains: - return polylineContainsEnvelope_(polyline_a, envelope_b, tolerance, - progress_tracker); - - case Relation.equals: - return polylineEqualsEnvelope_(polyline_a, envelope_b, tolerance, - progress_tracker); - - case Relation.touches: - return polylineTouchesEnvelope_(polyline_a, envelope_b, tolerance, - progress_tracker); - - case Relation.overlaps: - return polylineOverlapsEnvelope_(polyline_a, envelope_b, tolerance, - progress_tracker); - - case Relation.crosses: - return polylineCrossesEnvelope_(polyline_a, envelope_b, tolerance, - progress_tracker); - - default: - break; // warning fix - } - - return false; - } - - // Returns true if the relation holds. - private static boolean multiPointRelateMultiPoint_(MultiPoint multipoint_a, - MultiPoint multipoint_b, double tolerance, int relation, - ProgressTracker progress_tracker) { - switch (relation) { - case Relation.disjoint: - return multiPointDisjointMultiPoint_(multipoint_a, multipoint_b, - tolerance, progress_tracker); - - case Relation.within: - return multiPointContainsMultiPoint_(multipoint_b, multipoint_a, - tolerance, progress_tracker); - - case Relation.contains: - return multiPointContainsMultiPoint_(multipoint_a, multipoint_b, - tolerance, progress_tracker); - - case Relation.equals: - return multiPointEqualsMultiPoint_(multipoint_a, multipoint_b, - tolerance, progress_tracker); - - case Relation.overlaps: - return multiPointOverlapsMultiPoint_(multipoint_a, multipoint_b, - tolerance, progress_tracker); - - default: - break; // warning fix - } - - return false; - } - - // Returns true if the relation holds. - private static boolean multiPointRelatePoint_(MultiPoint multipoint_a, - Point point_b, double tolerance, int relation, - ProgressTracker progress_tracker) { - switch (relation) { - case Relation.disjoint: - return multiPointDisjointPoint_(multipoint_a, point_b, tolerance, - progress_tracker); - - case Relation.within: - return multiPointWithinPoint_(multipoint_a, point_b, tolerance, - progress_tracker); - - case Relation.contains: - return multiPointContainsPoint_(multipoint_a, point_b, tolerance, - progress_tracker); - - case Relation.equals: - return multiPointEqualsPoint_(multipoint_a, point_b, tolerance, - progress_tracker); - - default: - break; // warning fix - } - - return false; - } - - // Returns true if the relation holds. - private static boolean multiPointRelateEnvelope_(MultiPoint multipoint_a, - Envelope envelope_b, double tolerance, int relation, - ProgressTracker progress_tracker) { - switch (relation) { - case Relation.disjoint: - return multiPointDisjointEnvelope_(multipoint_a, envelope_b, - tolerance, progress_tracker); - - case Relation.within: - return multiPointWithinEnvelope_(multipoint_a, envelope_b, - tolerance, progress_tracker); - - case Relation.contains: - return multiPointContainsEnvelope_(multipoint_a, envelope_b, - tolerance, progress_tracker); - - case Relation.equals: - return multiPointEqualsEnvelope_(multipoint_a, envelope_b, - tolerance, progress_tracker); - - case Relation.touches: - return multiPointTouchesEnvelope_(multipoint_a, envelope_b, - tolerance, progress_tracker); - - case Relation.crosses: - return multiPointCrossesEnvelope_(multipoint_a, envelope_b, - tolerance, progress_tracker); - - default: - break; // warning fix - } - - return false; - } - - // Returns true if polygon_a equals polygon_b. - private static boolean polygonEqualsPolygon_(Polygon polygon_a, - Polygon polygon_b, double tolerance, - ProgressTracker progress_tracker) { - Envelope2D env_a = new Envelope2D(), env_b = new Envelope2D(); - polygon_a.queryEnvelope2D(env_a); - polygon_b.queryEnvelope2D(env_b); - - // Quick envelope rejection test for false equality. - if (!envelopeEqualsEnvelope_(env_a, env_b, tolerance, progress_tracker)) - return false; - - // Quick rasterize test to see whether the the geometries are disjoint, - // or if one is contained in the other. - int relation = tryRasterizedContainsOrDisjoint_(polygon_a, polygon_b, - tolerance, false); - - if (relation == Relation.disjoint || relation == Relation.contains - || relation == Relation.within) - return false; - - // Quick point equality check for true equality. This just checks if all - // the points in each ring are the same (within a tolerance) and in the - // same order - if (multiPathExactlyEqualsMultiPath_(polygon_a, polygon_b, tolerance, - progress_tracker)) - return true; - - double length_a = polygon_a.calculateLength2D(); - double length_b = polygon_b.calculateLength2D(); - int max_vertices = Math.max(polygon_a.getPointCount(), - polygon_b.getPointCount()); - - if (Math.abs(length_a - length_b) > max_vertices * 4.0 * tolerance) - return false; - - return linearPathEqualsLinearPath_(polygon_a, polygon_b, tolerance, true); - } - - // Returns true if polygon_a is disjoint from polygon_b. - private static boolean polygonDisjointPolygon_(Polygon polygon_a, - Polygon polygon_b, double tolerance, - ProgressTracker progress_tracker) { - // Quick rasterize test to see whether the the geometries are disjoint, - // or if one is contained in the other. - int relation = tryRasterizedContainsOrDisjoint_(polygon_a, polygon_b, - tolerance, true); - - if (relation == Relation.disjoint) - return true; - - if (relation == Relation.contains || relation == Relation.within - || relation == Relation.intersects) - return false; - - return polygonDisjointMultiPath_(polygon_a, polygon_b, tolerance, - progress_tracker); - } - - // Returns true if polygon_a touches polygon_b. - private static boolean polygonTouchesPolygon_(Polygon polygon_a, - Polygon polygon_b, double tolerance, - ProgressTracker progress_tracker) { - // Quick rasterize test to see whether the the geometries are disjoint, - // or if one is contained in the other. - int relation = tryRasterizedContainsOrDisjoint_(polygon_a, polygon_b, - tolerance, false); - - if (relation == Relation.disjoint || relation == Relation.contains - || relation == Relation.within) - return false; - - return polygonTouchesPolygonImpl_(polygon_a, polygon_b, tolerance, null); - } - - // Returns true if polygon_a overlaps polygon_b. - private static boolean polygonOverlapsPolygon_(Polygon polygon_a, - Polygon polygon_b, double tolerance, - ProgressTracker progress_tracker) { - // Quick rasterize test to see whether the the geometries are disjoint, - // or if one is contained in the other. - int relation = tryRasterizedContainsOrDisjoint_(polygon_a, polygon_b, - tolerance, false); - - if (relation == Relation.disjoint || relation == Relation.contains - || relation == Relation.within) - return false; - - return polygonOverlapsPolygonImpl_(polygon_a, polygon_b, tolerance, - progress_tracker); - } - - // Returns true if polygon_a contains polygon_b. - private static boolean polygonContainsPolygon_(Polygon polygon_a, - Polygon polygon_b, double tolerance, - ProgressTracker progress_tracker) { - Envelope2D env_a = new Envelope2D(), env_b = new Envelope2D(); - polygon_a.queryEnvelope2D(env_a); - polygon_b.queryEnvelope2D(env_b); - - // Quick envelope rejection test for false equality. - if (!envelopeInfContainsEnvelope_(env_a, env_b, tolerance)) - return false; - - // Quick rasterize test to see whether the the geometries are disjoint, - // or if one is contained in the other. - int relation = tryRasterizedContainsOrDisjoint_(polygon_a, polygon_b, - tolerance, false); - - if (relation == Relation.disjoint || relation == Relation.within) - return false; - - if (relation == Relation.contains) - return true; - - return polygonContainsPolygonImpl_(polygon_a, polygon_b, tolerance, - progress_tracker); - } - - // Returns true if polygon_a is disjoint from polyline_b. - private static boolean polygonDisjointPolyline_(Polygon polygon_a, - Polyline polyline_b, double tolerance, - ProgressTracker progress_tracker) { - // Quick rasterize test to see whether the the geometries are disjoint, - // or if one is contained in the other. - int relation = tryRasterizedContainsOrDisjoint_(polygon_a, polyline_b, - tolerance, true); - - if (relation == Relation.disjoint) - return true; - - if (relation == Relation.contains || relation == Relation.intersects) - return false; - - return polygonDisjointMultiPath_(polygon_a, polyline_b, tolerance, - progress_tracker); - } - - // Returns true if polygon_a touches polyline_b. - private static boolean polygonTouchesPolyline_(Polygon polygon_a, - Polyline polyline_b, double tolerance, - ProgressTracker progress_tracker) { - // Quick rasterize test to see whether the the geometries are disjoint, - // or if one is contained in the other. - int relation = tryRasterizedContainsOrDisjoint_(polygon_a, polyline_b, - tolerance, false); - - if (relation == Relation.disjoint || relation == Relation.contains) - return false; - - return polygonTouchesPolylineImpl_(polygon_a, polyline_b, tolerance, - progress_tracker); - } - - // Returns true if polygon_a crosses polyline_b. - private static boolean polygonCrossesPolyline_(Polygon polygon_a, - Polyline polyline_b, double tolerance, - ProgressTracker progress_tracker) { - // Quick rasterize test to see whether the the geometries are disjoint, - // or if one is contained in the other. - int relation = tryRasterizedContainsOrDisjoint_(polygon_a, polyline_b, - tolerance, false); - - if (relation == Relation.disjoint || relation == Relation.contains) - return false; - - return polygonCrossesPolylineImpl_(polygon_a, polyline_b, tolerance, - null); - } - - // Returns true if polygon_a contains polyline_b. - private static boolean polygonContainsPolyline_(Polygon polygon_a, - Polyline polyline_b, double tolerance, - ProgressTracker progress_tracker) { - Envelope2D env_a = new Envelope2D(), env_b = new Envelope2D(); - polygon_a.queryEnvelope2D(env_a); - polyline_b.queryEnvelope2D(env_b); - - // Quick envelope rejection test for false equality. - if (!envelopeInfContainsEnvelope_(env_a, env_b, tolerance)) - return false; - - // Quick rasterize test to see whether the the geometries are disjoint, - // or if one is contained in the other. - int relation = tryRasterizedContainsOrDisjoint_(polygon_a, polyline_b, - tolerance, false); - - if (relation == Relation.disjoint) - return false; - - if (relation == Relation.contains) - return true; - - return polygonContainsPolylineImpl_(polygon_a, polyline_b, tolerance, - progress_tracker); - } - - // Returns true if polygon_a is disjoint from point_b. - private static boolean polygonDisjointPoint_(Polygon polygon_a, - Point point_b, double tolerance, ProgressTracker progress_tracker) { - PolygonUtils.PiPResult result = PolygonUtils.isPointInPolygon2D( - polygon_a, point_b, tolerance); - - if (result == PolygonUtils.PiPResult.PiPOutside) - return true; - - return false; - } - - // Returns true of polygon_a touches point_b. - private static boolean polygonTouchesPoint_(Polygon polygon_a, - Point point_b, double tolerance, ProgressTracker progress_tracker) { - Point2D pt_b = point_b.getXY(); - return polygonTouchesPointImpl_(polygon_a, pt_b, tolerance, null); - } - - // Returns true if polygon_a contains point_b. - private static boolean polygonContainsPoint_(Polygon polygon_a, - Point point_b, double tolerance, ProgressTracker progress_tracker) { - Point2D pt_b = point_b.getXY(); - return polygonContainsPointImpl_(polygon_a, pt_b, tolerance, - progress_tracker); - } - - // Returns true if polygon_a is disjoint from multipoint_b. - private static boolean polygonDisjointMultiPoint_(Polygon polygon_a, - MultiPoint multipoint_b, double tolerance, - boolean bIncludeBoundaryA, ProgressTracker progress_tracker) { - // Quick rasterize test to see whether the the geometries are disjoint, - // or if one is contained in the other. - int relation = tryRasterizedContainsOrDisjoint_(polygon_a, - multipoint_b, tolerance, false); - - if (relation == Relation.disjoint) - return true; - - if (relation == Relation.contains) - return false; - - Envelope2D env_a_inflated = new Envelope2D(); - polygon_a.queryEnvelope2D(env_a_inflated); - env_a_inflated.inflate(tolerance, tolerance); - Point2D ptB = new Point2D(); - - for (int i = 0; i < multipoint_b.getPointCount(); i++) { - multipoint_b.getXY(i, ptB); - - if (!env_a_inflated.contains(ptB)) - continue; - - PolygonUtils.PiPResult result = PolygonUtils.isPointInPolygon2D( - polygon_a, ptB, tolerance); - - if (result == PolygonUtils.PiPResult.PiPInside - || (bIncludeBoundaryA && result == PolygonUtils.PiPResult.PiPBoundary)) - return false; - } - - return true; - } - - // Returns true if polygon_a touches multipoint_b. - private static boolean polygonTouchesMultiPoint_(Polygon polygon_a, - MultiPoint multipoint_b, double tolerance, - ProgressTracker progress_tracker) { - // Quick rasterize test to see whether the the geometries are disjoint, - // or if one is contained in the other. - int relation = tryRasterizedContainsOrDisjoint_(polygon_a, - multipoint_b, tolerance, false); - - if (relation == Relation.disjoint || relation == Relation.contains) - return false; - - Envelope2D env_a_inflated = new Envelope2D(); - polygon_a.queryEnvelope2D(env_a_inflated); - env_a_inflated.inflate(tolerance, tolerance); - - Point2D ptB; - boolean b_boundary = false; - - MultiPathImpl polygon_a_impl = (MultiPathImpl) polygon_a._getImpl(); - - Polygon pa = null; - Polygon p_polygon_a = polygon_a; - - boolean b_checked_polygon_a_quad_tree = false; - - for (int i = 0; i < multipoint_b.getPointCount(); i++) { - ptB = multipoint_b.getXY(i); - - if (env_a_inflated.contains(ptB)) { - - PolygonUtils.PiPResult result = PolygonUtils.isPointInPolygon2D(p_polygon_a, ptB, tolerance); - - if (result == PolygonUtils.PiPResult.PiPBoundary) - b_boundary = true; - else if (result == PolygonUtils.PiPResult.PiPInside) - return false; - } - - if (!b_checked_polygon_a_quad_tree) { - if (PointInPolygonHelper.quadTreeWillHelp(polygon_a, multipoint_b.getPointCount() - 1) && (polygon_a_impl._getAccelerators() == null || polygon_a_impl._getAccelerators().getQuadTree() == null)) { - pa = new Polygon(); - polygon_a.copyTo(pa); - ((MultiPathImpl) pa._getImpl())._buildQuadTreeAccelerator(Geometry.GeometryAccelerationDegree.enumMedium); - p_polygon_a = pa; - } else { - p_polygon_a = polygon_a; - } - - b_checked_polygon_a_quad_tree = true; - } - } - - if (b_boundary) - return true; - - return false; - } + interface Relation { + static final int contains = 1; + static final int within = 2; + static final int equals = 3; + static final int disjoint = 4; + static final int touches = 8; + static final int crosses = 16; + static final int overlaps = 32; + + static final int unknown = 0; + static final int intersects = 0x40000000; + } + + static boolean relate(Geometry geometry_a, Geometry geometry_b, + SpatialReference sr, int relation, ProgressTracker progress_tracker) { + int type_a = geometry_a.getType().value(); + int type_b = geometry_b.getType().value(); + + // Give preference to the Point vs Envelope, Envelope vs Envelope and + // Point vs Point realtions: + if (type_a == Geometry.GeometryType.Envelope) { + if (type_b == Geometry.GeometryType.Envelope) { + return relate((Envelope) geometry_a, (Envelope) geometry_b, sr, + relation, progress_tracker); + } else if (type_b == Geometry.GeometryType.Point) { + if (relation == Relation.within) + relation = Relation.contains; + else if (relation == Relation.contains) + relation = Relation.within; + + return relate((Point) geometry_b, (Envelope) geometry_a, sr, + relation, progress_tracker); + } else { + // proceed below + } + } else if (type_a == Geometry.GeometryType.Point) { + if (type_b == Geometry.GeometryType.Envelope) { + return relate((Point) geometry_a, (Envelope) geometry_b, sr, + relation, progress_tracker); + } else if (type_b == Geometry.GeometryType.Point) { + return relate((Point) geometry_a, (Point) geometry_b, sr, + relation, progress_tracker); + } else { + // proceed below + } + } else { + // proceed below + } + + if (geometry_a.isEmpty() || geometry_b.isEmpty()) { + if (relation == Relation.disjoint) + return true; // Always true + + return false; // Always false + } + + Envelope2D env1 = new Envelope2D(); + geometry_a.queryEnvelope2D(env1); + Envelope2D env2 = new Envelope2D(); + geometry_b.queryEnvelope2D(env2); + + Envelope2D envMerged = new Envelope2D(); + envMerged.setCoords(env1); + envMerged.merge(env2); + double tolerance = InternalUtils.calculateToleranceFromGeometry(sr, + envMerged, false); + + if (envelopeDisjointEnvelope_(env1, env2, tolerance, progress_tracker)) { + if (relation == Relation.disjoint) + return true; + + return false; + } + + boolean bRelation = false; + + Geometry _geometry_a; + Geometry _geometry_b; + Polyline polyline_a, polyline_b; + + if (MultiPath.isSegment(type_a)) { + polyline_a = new Polyline(geometry_a.getDescription()); + polyline_a.addSegment((Segment) geometry_a, true); + _geometry_a = polyline_a; + type_a = Geometry.GeometryType.Polyline; + } else { + _geometry_a = geometry_a; + } + + if (MultiPath.isSegment(type_b)) { + polyline_b = new Polyline(geometry_b.getDescription()); + polyline_b.addSegment((Segment) geometry_b, true); + _geometry_b = polyline_b; + type_b = Geometry.GeometryType.Polyline; + } else { + _geometry_b = geometry_b; + } + + if (type_a != Geometry.GeometryType.Envelope + && type_b != Geometry.GeometryType.Envelope) { + if (_geometry_a.getDimension() < _geometry_b.getDimension() + || (type_a == Geometry.GeometryType.Point && type_b == Geometry.GeometryType.MultiPoint)) {// we + // will + // switch + // the + // order + // of + // the + // geometries + // below. + if (relation == Relation.within) + relation = Relation.contains; + else if (relation == Relation.contains) + relation = Relation.within; + } + } else { + if (type_a != Geometry.GeometryType.Polygon + && type_b != Geometry.GeometryType.Envelope) { // we will + // switch + // the order + // of the + // geometries + // below. + if (relation == Relation.within) + relation = Relation.contains; + else if (relation == Relation.contains) + relation = Relation.within; + } + } + + switch (type_a) { + case Geometry.GeometryType.Polygon: + switch (type_b) { + case Geometry.GeometryType.Polygon: + bRelation = polygonRelatePolygon_((Polygon) (_geometry_a), + (Polygon) (_geometry_b), tolerance, relation, + progress_tracker); + break; + + case Geometry.GeometryType.Polyline: + bRelation = polygonRelatePolyline_((Polygon) (_geometry_a), + (Polyline) (_geometry_b), tolerance, relation, + progress_tracker); + break; + + case Geometry.GeometryType.Point: + bRelation = polygonRelatePoint_((Polygon) (_geometry_a), + (Point) (_geometry_b), tolerance, relation, + progress_tracker); + break; + + case Geometry.GeometryType.MultiPoint: + bRelation = polygonRelateMultiPoint_((Polygon) (_geometry_a), + (MultiPoint) (_geometry_b), tolerance, relation, + progress_tracker); + break; + + case Geometry.GeometryType.Envelope: + bRelation = polygonRelateEnvelope_((Polygon) (_geometry_a), + (Envelope) (_geometry_b), tolerance, relation, + progress_tracker); + break; + + default: + break; // warning fix + } + break; + + case Geometry.GeometryType.Polyline: + switch (type_b) { + case Geometry.GeometryType.Polygon: + bRelation = polygonRelatePolyline_((Polygon) (_geometry_b), + (Polyline) (_geometry_a), tolerance, relation, + progress_tracker); + break; + + case Geometry.GeometryType.Polyline: + bRelation = polylineRelatePolyline_((Polyline) (_geometry_a), + (Polyline) (_geometry_b), tolerance, relation, + progress_tracker); + break; + + case Geometry.GeometryType.Point: + bRelation = polylineRelatePoint_((Polyline) (_geometry_a), + (Point) (_geometry_b), tolerance, relation, + progress_tracker); + break; + + case Geometry.GeometryType.MultiPoint: + bRelation = polylineRelateMultiPoint_((Polyline) (_geometry_a), + (MultiPoint) (_geometry_b), tolerance, relation, + progress_tracker); + break; + + case Geometry.GeometryType.Envelope: + bRelation = polylineRelateEnvelope_((Polyline) (_geometry_a), + (Envelope) (_geometry_b), tolerance, relation, + progress_tracker); + break; + + default: + break; // warning fix + } + break; + + case Geometry.GeometryType.Point: + switch (type_b) { + case Geometry.GeometryType.Polygon: + bRelation = polygonRelatePoint_((Polygon) (_geometry_b), + (Point) (_geometry_a), tolerance, relation, + progress_tracker); + break; + + case Geometry.GeometryType.Polyline: + bRelation = polylineRelatePoint_((Polyline) (_geometry_b), + (Point) (_geometry_a), tolerance, relation, + progress_tracker); + break; + + case Geometry.GeometryType.MultiPoint: + bRelation = multiPointRelatePoint_((MultiPoint) (_geometry_b), + (Point) (_geometry_a), tolerance, relation, + progress_tracker); + break; + + default: + break; // warning fix + } + break; + + case Geometry.GeometryType.MultiPoint: + switch (type_b) { + case Geometry.GeometryType.Polygon: + bRelation = polygonRelateMultiPoint_((Polygon) (_geometry_b), + (MultiPoint) (_geometry_a), tolerance, relation, + progress_tracker); + break; + + case Geometry.GeometryType.Polyline: + bRelation = polylineRelateMultiPoint_((Polyline) (_geometry_b), + (MultiPoint) (_geometry_a), tolerance, relation, + progress_tracker); + break; + + case Geometry.GeometryType.MultiPoint: + bRelation = multiPointRelateMultiPoint_( + (MultiPoint) (_geometry_a), (MultiPoint) (_geometry_b), + tolerance, relation, progress_tracker); + break; + + case Geometry.GeometryType.Point: + bRelation = multiPointRelatePoint_((MultiPoint) (_geometry_a), + (Point) (_geometry_b), tolerance, relation, + progress_tracker); + break; + + case Geometry.GeometryType.Envelope: + bRelation = multiPointRelateEnvelope_( + (MultiPoint) (_geometry_a), (Envelope) (_geometry_b), + tolerance, relation, progress_tracker); + break; + + default: + break; // warning fix + } + break; + + case Geometry.GeometryType.Envelope: + switch (type_b) { + case Geometry.GeometryType.Polygon: + bRelation = polygonRelateEnvelope_((Polygon) (_geometry_b), + (Envelope) (_geometry_a), tolerance, relation, + progress_tracker); + break; + + case Geometry.GeometryType.Polyline: + bRelation = polylineRelateEnvelope_((Polyline) (_geometry_b), + (Envelope) (_geometry_a), tolerance, relation, + progress_tracker); + break; + + case Geometry.GeometryType.MultiPoint: + bRelation = multiPointRelateEnvelope_( + (MultiPoint) (_geometry_b), (Envelope) (_geometry_a), + tolerance, relation, progress_tracker); + break; + + default: + break; // warning fix + } + break; + + default: + break; // warning fix + } + + return bRelation; + } + + // Computes the necessary 9 intersection relationships of boundary, + // interior, and exterior of envelope_a vs envelope_b for the given + // relation. + private static boolean relate(Envelope envelope_a, Envelope envelope_b, + SpatialReference sr, int relation, ProgressTracker progress_tracker) { + if (envelope_a.isEmpty() || envelope_b.isEmpty()) { + if (relation == Relation.disjoint) + return true; // Always true + + return false; // Always false + } + + Envelope2D env_a = new Envelope2D(), env_b = new Envelope2D(), env_merged = new Envelope2D(); + envelope_a.queryEnvelope2D(env_a); + envelope_b.queryEnvelope2D(env_b); + env_merged.setCoords(env_a); + env_merged.merge(env_b); + + double tolerance = InternalUtils.calculateToleranceFromGeometry(sr, + env_merged, false); + + switch (relation) { + case Relation.disjoint: + return envelopeDisjointEnvelope_(env_a, env_b, tolerance, + progress_tracker); + + case Relation.within: + return envelopeContainsEnvelope_(env_b, env_a, tolerance, + progress_tracker); + + case Relation.contains: + return envelopeContainsEnvelope_(env_a, env_b, tolerance, + progress_tracker); + + case Relation.equals: + return envelopeEqualsEnvelope_(env_a, env_b, tolerance, + progress_tracker); + + case Relation.touches: + return envelopeTouchesEnvelope_(env_a, env_b, tolerance, + progress_tracker); + + case Relation.overlaps: + return envelopeOverlapsEnvelope_(env_a, env_b, tolerance, + progress_tracker); + + case Relation.crosses: + return envelopeCrossesEnvelope_(env_a, env_b, tolerance, + progress_tracker); + + default: + break; // warning fix + } + + return false; + } + + // Computes the necessary 9 intersection relationships of boundary, + // interior, and exterior of point_a vs envelope_b for the given relation. + private static boolean relate(Point point_a, Envelope envelope_b, + SpatialReference sr, int relation, ProgressTracker progress_tracker) { + if (point_a.isEmpty() || envelope_b.isEmpty()) { + if (relation == Relation.disjoint) + return true; // Always true + + return false; // Always false + } + + Point2D pt_a = point_a.getXY(); + Envelope2D env_b = new Envelope2D(), env_merged = new Envelope2D(); + envelope_b.queryEnvelope2D(env_b); + env_merged.setCoords(pt_a); + env_merged.merge(env_b); + + double tolerance = InternalUtils.calculateToleranceFromGeometry(sr, + env_merged, false); + + switch (relation) { + case Relation.disjoint: + return pointDisjointEnvelope_(pt_a, env_b, tolerance, + progress_tracker); + + case Relation.within: + return pointWithinEnvelope_(pt_a, env_b, tolerance, + progress_tracker); + + case Relation.contains: + return pointContainsEnvelope_(pt_a, env_b, tolerance, + progress_tracker); + + case Relation.equals: + return pointEqualsEnvelope_(pt_a, env_b, tolerance, + progress_tracker); + + case Relation.touches: + return pointTouchesEnvelope_(pt_a, env_b, tolerance, + progress_tracker); + + default: + break; // warning fix + } + + return false; + } + + // Computes the necessary 9 intersection relationships of boundary, + // interior, and exterior of point_a vs point_b for the given relation. + private static boolean relate(Point point_a, Point point_b, + SpatialReference sr, int relation, ProgressTracker progress_tracker) { + if (point_a.isEmpty() || point_b.isEmpty()) { + if (relation == Relation.disjoint) + return true; // Always true + + return false; // Always false + } + + Point2D pt_a = point_a.getXY(); + Point2D pt_b = point_b.getXY(); + Envelope2D env_merged = new Envelope2D(); + env_merged.setCoords(pt_a); + env_merged.merge(pt_b); + + double tolerance = InternalUtils.calculateToleranceFromGeometry(sr, + env_merged, false); + + switch (relation) { + case Relation.disjoint: + return pointDisjointPoint_(pt_a, pt_b, tolerance, progress_tracker); + + case Relation.within: + return pointContainsPoint_(pt_b, pt_a, tolerance, progress_tracker); + + case Relation.contains: + return pointContainsPoint_(pt_a, pt_b, tolerance, progress_tracker); + + case Relation.equals: + return pointEqualsPoint_(pt_a, pt_b, tolerance, progress_tracker); + + default: + break; // warning fix + } + + return false; + } + + // Returns true if the relation holds. + private static boolean polygonRelatePolygon_(Polygon polygon_a, + Polygon polygon_b, double tolerance, int relation, + ProgressTracker progress_tracker) { + switch (relation) { + case Relation.disjoint: + return polygonDisjointPolygon_(polygon_a, polygon_b, tolerance, + progress_tracker); + + case Relation.within: + return polygonContainsPolygon_(polygon_b, polygon_a, tolerance, + progress_tracker); + + case Relation.contains: + return polygonContainsPolygon_(polygon_a, polygon_b, tolerance, + progress_tracker); + + case Relation.equals: + return polygonEqualsPolygon_(polygon_a, polygon_b, tolerance, + progress_tracker); + + case Relation.touches: + return polygonTouchesPolygon_(polygon_a, polygon_b, tolerance, + progress_tracker); + + case Relation.overlaps: + return polygonOverlapsPolygon_(polygon_a, polygon_b, tolerance, + progress_tracker); + + default: + break; // warning fix + } + + return false; + } + + // Returns true if the relation holds. + private static boolean polygonRelatePolyline_(Polygon polygon_a, + Polyline polyline_b, double tolerance, int relation, + ProgressTracker progress_tracker) { + switch (relation) { + case Relation.disjoint: + return polygonDisjointPolyline_(polygon_a, polyline_b, tolerance, + progress_tracker); + + case Relation.contains: + return polygonContainsPolyline_(polygon_a, polyline_b, tolerance, + progress_tracker); + + case Relation.touches: + return polygonTouchesPolyline_(polygon_a, polyline_b, tolerance, + progress_tracker); + + case Relation.crosses: + return polygonCrossesPolyline_(polygon_a, polyline_b, tolerance, + progress_tracker); + + default: + break; // warning fix + } + + return false; + } + + // Returns true if the relation holds. + private static boolean polygonRelatePoint_(Polygon polygon_a, + Point point_b, double tolerance, int relation, + ProgressTracker progress_tracker) { + switch (relation) { + case Relation.disjoint: + return polygonDisjointPoint_(polygon_a, point_b, tolerance, + progress_tracker); + + case Relation.contains: + return polygonContainsPoint_(polygon_a, point_b, tolerance, + progress_tracker); + + case Relation.touches: + return polygonTouchesPoint_(polygon_a, point_b, tolerance, + progress_tracker); + + default: + break; // warning fix + } + + return false; + } + + // Returns true if the relation holds + private static boolean polygonRelateMultiPoint_(Polygon polygon_a, + MultiPoint multipoint_b, double tolerance, int relation, + ProgressTracker progress_tracker) { + switch (relation) { + case Relation.disjoint: + return polygonDisjointMultiPoint_(polygon_a, multipoint_b, + tolerance, true, progress_tracker); + + case Relation.contains: + return polygonContainsMultiPoint_(polygon_a, multipoint_b, + tolerance, progress_tracker); + + case Relation.touches: + return polygonTouchesMultiPoint_(polygon_a, multipoint_b, + tolerance, progress_tracker); + + case Relation.crosses: + return polygonCrossesMultiPoint_(polygon_a, multipoint_b, + tolerance, progress_tracker); + + default: + break; // warning fix + } + + return false; + } + + // Returns true if the relation holds + private static boolean polygonRelateEnvelope_(Polygon polygon_a, + Envelope envelope_b, double tolerance, int relation, + ProgressTracker progress_tracker) { + if (polygonDisjointEnvelope_(polygon_a, envelope_b, tolerance, + progress_tracker)) { + if (relation == Relation.disjoint) + return true; + + return false; + } else if (relation == Relation.disjoint) { + return false; + } + + switch (relation) { + case Relation.within: + return polygonWithinEnvelope_(polygon_a, envelope_b, tolerance, + progress_tracker); + + case Relation.contains: + return polygonContainsEnvelope_(polygon_a, envelope_b, tolerance, + progress_tracker); + + case Relation.equals: + return polygonEqualsEnvelope_(polygon_a, envelope_b, tolerance, + progress_tracker); + + case Relation.touches: + return polygonTouchesEnvelope_(polygon_a, envelope_b, tolerance, + progress_tracker); + + case Relation.overlaps: + return polygonOverlapsEnvelope_(polygon_a, envelope_b, tolerance, + progress_tracker); + + case Relation.crosses: + return polygonCrossesEnvelope_(polygon_a, envelope_b, tolerance, + progress_tracker); + + default: + break; // warning fix + } + + return false; + } + + // Returns true if the relation holds. + private static boolean polylineRelatePolyline_(Polyline polyline_a, + Polyline polyline_b, double tolerance, int relation, + ProgressTracker progress_tracker) { + switch (relation) { + case Relation.disjoint: + return polylineDisjointPolyline_(polyline_a, polyline_b, tolerance, + progress_tracker); + + case Relation.within: + return polylineContainsPolyline_(polyline_b, polyline_a, tolerance, + progress_tracker); + + case Relation.contains: + return polylineContainsPolyline_(polyline_a, polyline_b, tolerance, + progress_tracker); + + case Relation.equals: + return polylineEqualsPolyline_(polyline_a, polyline_b, tolerance, + progress_tracker); + + case Relation.touches: + return polylineTouchesPolyline_(polyline_a, polyline_b, tolerance, + progress_tracker); + + case Relation.overlaps: + return polylineOverlapsPolyline_(polyline_a, polyline_b, tolerance, + progress_tracker); + + case Relation.crosses: + return polylineCrossesPolyline_(polyline_a, polyline_b, tolerance, + progress_tracker); + + default: + break; // warning fix + } + + return false; + } + + // Returns true if the relation holds. + private static boolean polylineRelatePoint_(Polyline polyline_a, + Point point_b, double tolerance, int relation, + ProgressTracker progress_tracker) { + switch (relation) { + case Relation.disjoint: + return polylineDisjointPoint_(polyline_a, point_b, tolerance, + progress_tracker); + + case Relation.contains: + return polylineContainsPoint_(polyline_a, point_b, tolerance, + progress_tracker); + + case Relation.touches: + return polylineTouchesPoint_(polyline_a, point_b, tolerance, + progress_tracker); + + default: + break; // warning fix + } + + return false; + } + + // Returns true if the relation holds. + private static boolean polylineRelateMultiPoint_(Polyline polyline_a, + MultiPoint multipoint_b, double tolerance, int relation, + ProgressTracker progress_tracker) { + switch (relation) { + case Relation.disjoint: + return polylineDisjointMultiPoint_(polyline_a, multipoint_b, + tolerance, progress_tracker); + + case Relation.contains: + return polylineContainsMultiPoint_(polyline_a, multipoint_b, + tolerance, progress_tracker); + + case Relation.touches: + return polylineTouchesMultiPoint_(polyline_a, multipoint_b, + tolerance, progress_tracker); + + case Relation.crosses: + return polylineCrossesMultiPoint_(polyline_a, multipoint_b, + tolerance, progress_tracker); + + default: + break; // warning fix + } + + return false; + } + + // Returns true if the relation holds. + private static boolean polylineRelateEnvelope_(Polyline polyline_a, + Envelope envelope_b, double tolerance, int relation, + ProgressTracker progress_tracker) { + if (polylineDisjointEnvelope_(polyline_a, envelope_b, tolerance, + progress_tracker)) { + if (relation == Relation.disjoint) + return true; + + return false; + } else if (relation == Relation.disjoint) { + return false; + } + + switch (relation) { + case Relation.within: + return polylineWithinEnvelope_(polyline_a, envelope_b, tolerance, + progress_tracker); + + case Relation.contains: + return polylineContainsEnvelope_(polyline_a, envelope_b, tolerance, + progress_tracker); + + case Relation.equals: + return polylineEqualsEnvelope_(polyline_a, envelope_b, tolerance, + progress_tracker); + + case Relation.touches: + return polylineTouchesEnvelope_(polyline_a, envelope_b, tolerance, + progress_tracker); + + case Relation.overlaps: + return polylineOverlapsEnvelope_(polyline_a, envelope_b, tolerance, + progress_tracker); + + case Relation.crosses: + return polylineCrossesEnvelope_(polyline_a, envelope_b, tolerance, + progress_tracker); + + default: + break; // warning fix + } + + return false; + } + + // Returns true if the relation holds. + private static boolean multiPointRelateMultiPoint_(MultiPoint multipoint_a, + MultiPoint multipoint_b, double tolerance, int relation, + ProgressTracker progress_tracker) { + switch (relation) { + case Relation.disjoint: + return multiPointDisjointMultiPoint_(multipoint_a, multipoint_b, + tolerance, progress_tracker); + + case Relation.within: + return multiPointContainsMultiPoint_(multipoint_b, multipoint_a, + tolerance, progress_tracker); + + case Relation.contains: + return multiPointContainsMultiPoint_(multipoint_a, multipoint_b, + tolerance, progress_tracker); + + case Relation.equals: + return multiPointEqualsMultiPoint_(multipoint_a, multipoint_b, + tolerance, progress_tracker); + + case Relation.overlaps: + return multiPointOverlapsMultiPoint_(multipoint_a, multipoint_b, + tolerance, progress_tracker); + + default: + break; // warning fix + } + + return false; + } + + // Returns true if the relation holds. + private static boolean multiPointRelatePoint_(MultiPoint multipoint_a, + Point point_b, double tolerance, int relation, + ProgressTracker progress_tracker) { + switch (relation) { + case Relation.disjoint: + return multiPointDisjointPoint_(multipoint_a, point_b, tolerance, + progress_tracker); + + case Relation.within: + return multiPointWithinPoint_(multipoint_a, point_b, tolerance, + progress_tracker); + + case Relation.contains: + return multiPointContainsPoint_(multipoint_a, point_b, tolerance, + progress_tracker); + + case Relation.equals: + return multiPointEqualsPoint_(multipoint_a, point_b, tolerance, + progress_tracker); + + default: + break; // warning fix + } + + return false; + } + + // Returns true if the relation holds. + private static boolean multiPointRelateEnvelope_(MultiPoint multipoint_a, + Envelope envelope_b, double tolerance, int relation, + ProgressTracker progress_tracker) { + switch (relation) { + case Relation.disjoint: + return multiPointDisjointEnvelope_(multipoint_a, envelope_b, + tolerance, progress_tracker); + + case Relation.within: + return multiPointWithinEnvelope_(multipoint_a, envelope_b, + tolerance, progress_tracker); + + case Relation.contains: + return multiPointContainsEnvelope_(multipoint_a, envelope_b, + tolerance, progress_tracker); + + case Relation.equals: + return multiPointEqualsEnvelope_(multipoint_a, envelope_b, + tolerance, progress_tracker); + + case Relation.touches: + return multiPointTouchesEnvelope_(multipoint_a, envelope_b, + tolerance, progress_tracker); + + case Relation.crosses: + return multiPointCrossesEnvelope_(multipoint_a, envelope_b, + tolerance, progress_tracker); + + default: + break; // warning fix + } + + return false; + } + + // Returns true if polygon_a equals polygon_b. + private static boolean polygonEqualsPolygon_(Polygon polygon_a, + Polygon polygon_b, double tolerance, + ProgressTracker progress_tracker) { + Envelope2D env_a = new Envelope2D(), env_b = new Envelope2D(); + polygon_a.queryEnvelope2D(env_a); + polygon_b.queryEnvelope2D(env_b); + + // Quick envelope rejection test for false equality. + if (!envelopeEqualsEnvelope_(env_a, env_b, tolerance, progress_tracker)) + return false; + + // Quick rasterize test to see whether the the geometries are disjoint, + // or if one is contained in the other. + int relation = tryRasterizedContainsOrDisjoint_(polygon_a, polygon_b, + tolerance, false); + + if (relation == Relation.disjoint || relation == Relation.contains + || relation == Relation.within) + return false; + + // Quick point equality check for true equality. This just checks if all + // the points in each ring are the same (within a tolerance) and in the + // same order + if (multiPathExactlyEqualsMultiPath_(polygon_a, polygon_b, tolerance, + progress_tracker)) + return true; + + double length_a = polygon_a.calculateLength2D(); + double length_b = polygon_b.calculateLength2D(); + int max_vertices = Math.max(polygon_a.getPointCount(), + polygon_b.getPointCount()); + + if (Math.abs(length_a - length_b) > max_vertices * 4.0 * tolerance) + return false; + + return linearPathEqualsLinearPath_(polygon_a, polygon_b, tolerance, true); + } + + // Returns true if polygon_a is disjoint from polygon_b. + private static boolean polygonDisjointPolygon_(Polygon polygon_a, + Polygon polygon_b, double tolerance, + ProgressTracker progress_tracker) { + // Quick rasterize test to see whether the the geometries are disjoint, + // or if one is contained in the other. + int relation = tryRasterizedContainsOrDisjoint_(polygon_a, polygon_b, + tolerance, true); + + if (relation == Relation.disjoint) + return true; + + if (relation == Relation.contains || relation == Relation.within + || relation == Relation.intersects) + return false; + + return polygonDisjointMultiPath_(polygon_a, polygon_b, tolerance, + progress_tracker); + } + + // Returns true if polygon_a touches polygon_b. + private static boolean polygonTouchesPolygon_(Polygon polygon_a, + Polygon polygon_b, double tolerance, + ProgressTracker progress_tracker) { + // Quick rasterize test to see whether the the geometries are disjoint, + // or if one is contained in the other. + int relation = tryRasterizedContainsOrDisjoint_(polygon_a, polygon_b, + tolerance, false); + + if (relation == Relation.disjoint || relation == Relation.contains + || relation == Relation.within) + return false; + + return polygonTouchesPolygonImpl_(polygon_a, polygon_b, tolerance, null); + } + + // Returns true if polygon_a overlaps polygon_b. + private static boolean polygonOverlapsPolygon_(Polygon polygon_a, + Polygon polygon_b, double tolerance, + ProgressTracker progress_tracker) { + // Quick rasterize test to see whether the the geometries are disjoint, + // or if one is contained in the other. + int relation = tryRasterizedContainsOrDisjoint_(polygon_a, polygon_b, + tolerance, false); + + if (relation == Relation.disjoint || relation == Relation.contains + || relation == Relation.within) + return false; + + return polygonOverlapsPolygonImpl_(polygon_a, polygon_b, tolerance, + progress_tracker); + } + + // Returns true if polygon_a contains polygon_b. + private static boolean polygonContainsPolygon_(Polygon polygon_a, + Polygon polygon_b, double tolerance, + ProgressTracker progress_tracker) { + Envelope2D env_a = new Envelope2D(), env_b = new Envelope2D(); + polygon_a.queryEnvelope2D(env_a); + polygon_b.queryEnvelope2D(env_b); + + // Quick envelope rejection test for false equality. + if (!envelopeInfContainsEnvelope_(env_a, env_b, tolerance)) + return false; + + // Quick rasterize test to see whether the the geometries are disjoint, + // or if one is contained in the other. + int relation = tryRasterizedContainsOrDisjoint_(polygon_a, polygon_b, + tolerance, false); + + if (relation == Relation.disjoint || relation == Relation.within) + return false; + + if (relation == Relation.contains) + return true; + + return polygonContainsPolygonImpl_(polygon_a, polygon_b, tolerance, + progress_tracker); + } + + // Returns true if polygon_a is disjoint from polyline_b. + private static boolean polygonDisjointPolyline_(Polygon polygon_a, + Polyline polyline_b, double tolerance, + ProgressTracker progress_tracker) { + // Quick rasterize test to see whether the the geometries are disjoint, + // or if one is contained in the other. + int relation = tryRasterizedContainsOrDisjoint_(polygon_a, polyline_b, + tolerance, true); + + if (relation == Relation.disjoint) + return true; + + if (relation == Relation.contains || relation == Relation.intersects) + return false; + + return polygonDisjointMultiPath_(polygon_a, polyline_b, tolerance, + progress_tracker); + } + + // Returns true if polygon_a touches polyline_b. + private static boolean polygonTouchesPolyline_(Polygon polygon_a, + Polyline polyline_b, double tolerance, + ProgressTracker progress_tracker) { + // Quick rasterize test to see whether the the geometries are disjoint, + // or if one is contained in the other. + int relation = tryRasterizedContainsOrDisjoint_(polygon_a, polyline_b, + tolerance, false); + + if (relation == Relation.disjoint || relation == Relation.contains) + return false; + + return polygonTouchesPolylineImpl_(polygon_a, polyline_b, tolerance, + progress_tracker); + } + + // Returns true if polygon_a crosses polyline_b. + private static boolean polygonCrossesPolyline_(Polygon polygon_a, + Polyline polyline_b, double tolerance, + ProgressTracker progress_tracker) { + // Quick rasterize test to see whether the the geometries are disjoint, + // or if one is contained in the other. + int relation = tryRasterizedContainsOrDisjoint_(polygon_a, polyline_b, + tolerance, false); + + if (relation == Relation.disjoint || relation == Relation.contains) + return false; + + return polygonCrossesPolylineImpl_(polygon_a, polyline_b, tolerance, + null); + } + + // Returns true if polygon_a contains polyline_b. + private static boolean polygonContainsPolyline_(Polygon polygon_a, + Polyline polyline_b, double tolerance, + ProgressTracker progress_tracker) { + Envelope2D env_a = new Envelope2D(), env_b = new Envelope2D(); + polygon_a.queryEnvelope2D(env_a); + polyline_b.queryEnvelope2D(env_b); + + // Quick envelope rejection test for false equality. + if (!envelopeInfContainsEnvelope_(env_a, env_b, tolerance)) + return false; + + // Quick rasterize test to see whether the the geometries are disjoint, + // or if one is contained in the other. + int relation = tryRasterizedContainsOrDisjoint_(polygon_a, polyline_b, + tolerance, false); + + if (relation == Relation.disjoint) + return false; + + if (relation == Relation.contains) + return true; + + return polygonContainsPolylineImpl_(polygon_a, polyline_b, tolerance, + progress_tracker); + } + + // Returns true if polygon_a is disjoint from point_b. + private static boolean polygonDisjointPoint_(Polygon polygon_a, + Point point_b, double tolerance, ProgressTracker progress_tracker) { + PolygonUtils.PiPResult result = PolygonUtils.isPointInPolygon2D( + polygon_a, point_b, tolerance); + + if (result == PolygonUtils.PiPResult.PiPOutside) + return true; + + return false; + } + + // Returns true of polygon_a touches point_b. + private static boolean polygonTouchesPoint_(Polygon polygon_a, + Point point_b, double tolerance, ProgressTracker progress_tracker) { + Point2D pt_b = point_b.getXY(); + return polygonTouchesPointImpl_(polygon_a, pt_b, tolerance, null); + } + + // Returns true if polygon_a contains point_b. + private static boolean polygonContainsPoint_(Polygon polygon_a, + Point point_b, double tolerance, ProgressTracker progress_tracker) { + Point2D pt_b = point_b.getXY(); + return polygonContainsPointImpl_(polygon_a, pt_b, tolerance, + progress_tracker); + } + + // Returns true if polygon_a is disjoint from multipoint_b. + private static boolean polygonDisjointMultiPoint_(Polygon polygon_a, + MultiPoint multipoint_b, double tolerance, + boolean bIncludeBoundaryA, ProgressTracker progress_tracker) { + // Quick rasterize test to see whether the the geometries are disjoint, + // or if one is contained in the other. + int relation = tryRasterizedContainsOrDisjoint_(polygon_a, + multipoint_b, tolerance, false); + + if (relation == Relation.disjoint) + return true; + + if (relation == Relation.contains) + return false; + + Envelope2D env_a_inflated = new Envelope2D(); + polygon_a.queryEnvelope2D(env_a_inflated); + env_a_inflated.inflate(tolerance, tolerance); + Point2D ptB = new Point2D(); + + for (int i = 0; i < multipoint_b.getPointCount(); i++) { + multipoint_b.getXY(i, ptB); + + if (!env_a_inflated.contains(ptB)) + continue; + + PolygonUtils.PiPResult result = PolygonUtils.isPointInPolygon2D( + polygon_a, ptB, tolerance); + + if (result == PolygonUtils.PiPResult.PiPInside + || (bIncludeBoundaryA && result == PolygonUtils.PiPResult.PiPBoundary)) + return false; + } + + return true; + } + + // Returns true if polygon_a touches multipoint_b. + private static boolean polygonTouchesMultiPoint_(Polygon polygon_a, + MultiPoint multipoint_b, double tolerance, + ProgressTracker progress_tracker) { + // Quick rasterize test to see whether the the geometries are disjoint, + // or if one is contained in the other. + int relation = tryRasterizedContainsOrDisjoint_(polygon_a, + multipoint_b, tolerance, false); + + if (relation == Relation.disjoint || relation == Relation.contains) + return false; + + Envelope2D env_a_inflated = new Envelope2D(); + polygon_a.queryEnvelope2D(env_a_inflated); + env_a_inflated.inflate(tolerance, tolerance); + + Point2D ptB; + boolean b_boundary = false; + + MultiPathImpl polygon_a_impl = (MultiPathImpl) polygon_a._getImpl(); + + Polygon pa = null; + Polygon p_polygon_a = polygon_a; + + boolean b_checked_polygon_a_quad_tree = false; + + for (int i = 0; i < multipoint_b.getPointCount(); i++) { + ptB = multipoint_b.getXY(i); + + if (env_a_inflated.contains(ptB)) { + + PolygonUtils.PiPResult result = PolygonUtils.isPointInPolygon2D(p_polygon_a, ptB, tolerance); + + if (result == PolygonUtils.PiPResult.PiPBoundary) + b_boundary = true; + else if (result == PolygonUtils.PiPResult.PiPInside) + return false; + } + + if (!b_checked_polygon_a_quad_tree) { + if (PointInPolygonHelper.quadTreeWillHelp(polygon_a, multipoint_b.getPointCount() - 1) && (polygon_a_impl._getAccelerators() == null || polygon_a_impl._getAccelerators().getQuadTree() == null)) { + pa = new Polygon(); + polygon_a.copyTo(pa); + ((MultiPathImpl) pa._getImpl())._buildQuadTreeAccelerator(Geometry.GeometryAccelerationDegree.enumMedium); + p_polygon_a = pa; + } else { + p_polygon_a = polygon_a; + } + + b_checked_polygon_a_quad_tree = true; + } + } + + if (b_boundary) + return true; + + return false; + } - // Returns true if polygon_a crosses multipoint_b. - private static boolean polygonCrossesMultiPoint_(Polygon polygon_a, - MultiPoint multipoint_b, double tolerance, - ProgressTracker progress_tracker) { - // Quick rasterize test to see whether the the geometries are disjoint, - // or if one is contained in the other. - int relation = tryRasterizedContainsOrDisjoint_(polygon_a, - multipoint_b, tolerance, false); + // Returns true if polygon_a crosses multipoint_b. + private static boolean polygonCrossesMultiPoint_(Polygon polygon_a, + MultiPoint multipoint_b, double tolerance, + ProgressTracker progress_tracker) { + // Quick rasterize test to see whether the the geometries are disjoint, + // or if one is contained in the other. + int relation = tryRasterizedContainsOrDisjoint_(polygon_a, + multipoint_b, tolerance, false); - if (relation == Relation.disjoint || relation == Relation.contains) - return false; + if (relation == Relation.disjoint || relation == Relation.contains) + return false; - Envelope2D env_a = new Envelope2D(), env_a_inflated = new Envelope2D(), env_b = new Envelope2D(); - polygon_a.queryEnvelope2D(env_a); - multipoint_b.queryEnvelope2D(env_b); - env_a_inflated.setCoords(env_a); - env_a_inflated.inflate(tolerance, tolerance); + Envelope2D env_a = new Envelope2D(), env_a_inflated = new Envelope2D(), env_b = new Envelope2D(); + polygon_a.queryEnvelope2D(env_a); + multipoint_b.queryEnvelope2D(env_b); + env_a_inflated.setCoords(env_a); + env_a_inflated.inflate(tolerance, tolerance); - boolean b_interior = false, b_exterior = false; + boolean b_interior = false, b_exterior = false; - Point2D pt_b; + Point2D pt_b; - MultiPathImpl polygon_a_impl = (MultiPathImpl) polygon_a._getImpl(); + MultiPathImpl polygon_a_impl = (MultiPathImpl) polygon_a._getImpl(); - Polygon pa = null; - Polygon p_polygon_a = polygon_a; + Polygon pa = null; + Polygon p_polygon_a = polygon_a; - boolean b_checked_polygon_a_quad_tree = false; + boolean b_checked_polygon_a_quad_tree = false; - for (int i = 0; i < multipoint_b.getPointCount(); i++) { - pt_b = multipoint_b.getXY(i); - - if (!env_a_inflated.contains(pt_b)) { - b_exterior = true; - } else { - PolygonUtils.PiPResult result = PolygonUtils.isPointInPolygon2D(p_polygon_a, pt_b, tolerance); - - if (result == PolygonUtils.PiPResult.PiPOutside) - b_exterior = true; - else if (result == PolygonUtils.PiPResult.PiPInside) - b_interior = true; - } - - if (b_interior && b_exterior) - return true; + for (int i = 0; i < multipoint_b.getPointCount(); i++) { + pt_b = multipoint_b.getXY(i); + + if (!env_a_inflated.contains(pt_b)) { + b_exterior = true; + } else { + PolygonUtils.PiPResult result = PolygonUtils.isPointInPolygon2D(p_polygon_a, pt_b, tolerance); + + if (result == PolygonUtils.PiPResult.PiPOutside) + b_exterior = true; + else if (result == PolygonUtils.PiPResult.PiPInside) + b_interior = true; + } + + if (b_interior && b_exterior) + return true; - if (!b_checked_polygon_a_quad_tree) { - if (PointInPolygonHelper.quadTreeWillHelp(polygon_a, multipoint_b.getPointCount() - 1) && (polygon_a_impl._getAccelerators() == null || polygon_a_impl._getAccelerators().getQuadTree() == null)) { - pa = new Polygon(); - polygon_a.copyTo(pa); - ((MultiPathImpl) pa._getImpl())._buildQuadTreeAccelerator(Geometry.GeometryAccelerationDegree.enumMedium); - p_polygon_a = pa; - } else { - p_polygon_a = polygon_a; - } - - b_checked_polygon_a_quad_tree = true; - } - } - - return false; - } - - // Returns true if polygon_a contains multipoint_b. - private static boolean polygonContainsMultiPoint_(Polygon polygon_a, - MultiPoint multipoint_b, double tolerance, - ProgressTracker progress_tracker) { - Envelope2D env_a = new Envelope2D(), env_b = new Envelope2D(); - polygon_a.queryEnvelope2D(env_a); - multipoint_b.queryEnvelope2D(env_b); - - // Quick envelope rejection test for false equality. - if (!envelopeInfContainsEnvelope_(env_a, env_b, tolerance)) - return false; - - // Quick rasterize test to see whether the the geometries are disjoint, - // or if one is contained in the other. - int relation = tryRasterizedContainsOrDisjoint_(polygon_a, - multipoint_b, tolerance, false); - - if (relation == Relation.disjoint) - return false; - - if (relation == Relation.contains) - return true; - - boolean b_interior = false; - Point2D ptB; - - MultiPathImpl polygon_a_impl = (MultiPathImpl) polygon_a._getImpl(); - - Polygon pa = null; - Polygon p_polygon_a = polygon_a; - - boolean b_checked_polygon_a_quad_tree = false; - - for (int i = 0; i < multipoint_b.getPointCount(); i++) { - ptB = multipoint_b.getXY(i); - - if (!env_a.contains(ptB)) - return false; - - PolygonUtils.PiPResult result = PolygonUtils.isPointInPolygon2D(p_polygon_a, ptB, tolerance); - - if (result == PolygonUtils.PiPResult.PiPInside) - b_interior = true; - else if (result == PolygonUtils.PiPResult.PiPOutside) - return false; - - if (!b_checked_polygon_a_quad_tree) { - if (PointInPolygonHelper.quadTreeWillHelp(polygon_a, multipoint_b.getPointCount() - 1) && (polygon_a_impl._getAccelerators() == null || polygon_a_impl._getAccelerators().getQuadTree() == null)) { - pa = new Polygon(); - polygon_a.copyTo(pa); - ((MultiPathImpl) pa._getImpl())._buildQuadTreeAccelerator(Geometry.GeometryAccelerationDegree.enumMedium); - p_polygon_a = pa; - } else { - p_polygon_a = polygon_a; - } - - b_checked_polygon_a_quad_tree = true; - } - } - - return b_interior; - } - - // Returns true if polygon_a equals envelope_b. - private static boolean polygonEqualsEnvelope_(Polygon polygon_a, - Envelope envelope_b, double tolerance, - ProgressTracker progress_tracker) { - Envelope2D env_a = new Envelope2D(), env_b = new Envelope2D(); - polygon_a.queryEnvelope2D(env_a); - envelope_b.queryEnvelope2D(env_b); - - // Quick envelope rejection test for false equality. - // This check will correctly handle degenerate envelope cases (i.e. - // degenerate to point or line) - if (!envelopeEqualsEnvelope_(env_a, env_b, tolerance, progress_tracker)) - return false; - - Polygon polygon_b = new Polygon(); - polygon_b.addEnvelope(envelope_b, false); - - return linearPathEqualsLinearPath_(polygon_a, polygon_b, tolerance, true); - } - - // Returns true if polygon_a is disjoint from envelope_b. - private static boolean polygonDisjointEnvelope_(Polygon polygon_a, - Envelope envelope_b, double tolerance, - ProgressTracker progress_tracker) { - // Quick rasterize test to see whether the the geometries are disjoint, - // or if one is contained in the other. - int relation = tryRasterizedContainsOrDisjoint_(polygon_a, envelope_b, - tolerance, false); - - if (relation == Relation.disjoint) - return true; - - if (relation == Relation.contains || relation == Relation.within) - return false; - - Envelope2D env_a = new Envelope2D(), env_b = new Envelope2D(); - polygon_a.queryEnvelope2D(env_a); - envelope_b.queryEnvelope2D(env_b); - - if (envelopeInfContainsEnvelope_(env_b, env_a, tolerance)) - return false; - - PolygonUtils.PiPResult pipres; - Point2D pt_b = new Point2D(); - env_b.queryLowerLeft(pt_b); - pipres = PolygonUtils.isPointInPolygon2D(polygon_a, pt_b, tolerance); - if (pipres != PolygonUtils.PiPResult.PiPOutside) - return false; - - env_b.queryLowerRight(pt_b); - pipres = PolygonUtils.isPointInPolygon2D(polygon_a, pt_b, tolerance); - if (pipres != PolygonUtils.PiPResult.PiPOutside) - return false; - - env_b.queryUpperRight(pt_b); - pipres = PolygonUtils.isPointInPolygon2D(polygon_a, pt_b, tolerance); - if (pipres != PolygonUtils.PiPResult.PiPOutside) - return false; - - env_b.queryUpperLeft(pt_b); - pipres = PolygonUtils.isPointInPolygon2D(polygon_a, pt_b, tolerance); - if (pipres != PolygonUtils.PiPResult.PiPOutside) - return false; - - MultiPathImpl mimpl_a = (MultiPathImpl) polygon_a._getImpl(); - AttributeStreamOfDbl pos = (AttributeStreamOfDbl) (mimpl_a - .getAttributeStreamRef(VertexDescription.Semantics.POSITION)); - - Envelope2D env_b_inflated = new Envelope2D(); - env_b_inflated.setCoords(env_b); - env_b_inflated.inflate(tolerance, tolerance); - for (int ptIndex = 0, n = mimpl_a.getPointCount(); ptIndex < n; ptIndex++) { - double x = pos.read(2 * ptIndex); - double y = pos.read(2 * ptIndex + 1); - if (env_b_inflated.contains(x, y)) - return false; - } - - return !linearPathIntersectsEnvelope_(polygon_a, env_b, tolerance, - progress_tracker); - } - - // Returns true if polygon_a touches envelope_b. - private static boolean polygonTouchesEnvelope_(Polygon polygon_a, - Envelope envelope_b, double tolerance, - ProgressTracker progress_tracker) { - // Quick rasterize test to see whether the the geometries are disjoint, - // or if one is contained in the other. - int relation = tryRasterizedContainsOrDisjoint_(polygon_a, envelope_b, - tolerance, false); - - if (relation == Relation.disjoint || relation == Relation.contains - || relation == Relation.within) - return false; - - Envelope2D env_a = new Envelope2D(), env_b = new Envelope2D(); - polygon_a.queryEnvelope2D(env_a); - envelope_b.queryEnvelope2D(env_b); - - if (envelopeInfContainsEnvelope_(env_b, env_a, tolerance)) - return false; - - if (env_b.getWidth() <= tolerance && env_b.getHeight() <= tolerance) {// treat - // as - // point - Point2D pt_b = envelope_b.getCenterXY(); - return polygonTouchesPointImpl_(polygon_a, pt_b, tolerance, - progress_tracker); - } - - if (env_b.getWidth() <= tolerance || env_b.getHeight() <= tolerance) {// treat - // as - // polyline - Polyline polyline_b = new Polyline(); - Point p = new Point(); - envelope_b.queryCornerByVal(0, p); - polyline_b.startPath(p); - envelope_b.queryCornerByVal(2, p); - polyline_b.lineTo(p); - return polygonTouchesPolylineImpl_(polygon_a, polyline_b, - tolerance, progress_tracker); - } - - // treat as polygon - Polygon polygon_b = new Polygon(); - polygon_b.addEnvelope(envelope_b, false); - return polygonTouchesPolygonImpl_(polygon_a, polygon_b, tolerance, - progress_tracker); - } - - // Returns true if polygon_a overlaps envelope_b. - private static boolean polygonOverlapsEnvelope_(Polygon polygon_a, - Envelope envelope_b, double tolerance, - ProgressTracker progress_tracker) { - // Quick rasterize test to see whether the the geometries are disjoint, - // or if one is contained in the other. - int relation = tryRasterizedContainsOrDisjoint_(polygon_a, envelope_b, - tolerance, false); - - if (relation == Relation.disjoint || relation == Relation.contains - || relation == Relation.within) - return false; - - Envelope2D env_a = new Envelope2D(), env_b = new Envelope2D(); - polygon_a.queryEnvelope2D(env_a); - envelope_b.queryEnvelope2D(env_b); - - if (envelopeInfContainsEnvelope_(env_b, env_a, tolerance)) - return false; - - if (env_b.getWidth() <= tolerance || env_b.getHeight() <= tolerance) - return false; // has no interior - - Polygon polygon_b = new Polygon(); - polygon_b.addEnvelope(envelope_b, false); - return polygonOverlapsPolygonImpl_(polygon_a, polygon_b, tolerance, - progress_tracker); - } - - // Returns true if polygon_a is within envelope_b - private static boolean polygonWithinEnvelope_(Polygon polygon_a, - Envelope envelope_b, double tolerance, - ProgressTracker progress_tracker) { - Envelope2D env_a = new Envelope2D(), env_b = new Envelope2D(); - polygon_a.queryEnvelope2D(env_a); - envelope_b.queryEnvelope2D(env_b); - return envelopeInfContainsEnvelope_(env_b, env_a, tolerance); - } - - // Returns true if polygon_a contains envelope_b. - private static boolean polygonContainsEnvelope_(Polygon polygon_a, - Envelope envelope_b, double tolerance, - ProgressTracker progress_tracker) { - // Quick envelope rejection test - - Envelope2D env_a = new Envelope2D(), env_b = new Envelope2D(); - polygon_a.queryEnvelope2D(env_a); - envelope_b.queryEnvelope2D(env_b); - - if (!envelopeInfContainsEnvelope_(env_a, env_b, tolerance)) - return false; - - // Quick rasterize test to see whether the the geometries are disjoint, - // or if one is contained in the other. - int relation = tryRasterizedContainsOrDisjoint_(polygon_a, envelope_b, - tolerance, false); - - if (relation == Relation.disjoint || relation == Relation.within) - return false; - - if (relation == Relation.contains) - return true; - - if (env_b.getWidth() <= tolerance && env_b.getHeight() <= tolerance) {// treat - // as - // point - Point2D pt_b = envelope_b.getCenterXY(); - return polygonContainsPointImpl_(polygon_a, pt_b, tolerance, - progress_tracker); - } - - if (env_b.getWidth() <= tolerance || env_b.getHeight() <= tolerance) {// treat - // as - // polyline - Polyline polyline_b = new Polyline(); - Point p = new Point(); - envelope_b.queryCornerByVal(0, p); - polyline_b.startPath(p); - envelope_b.queryCornerByVal(2, p); - polyline_b.lineTo(p); - return polygonContainsPolylineImpl_(polygon_a, polyline_b, - tolerance, null); - } - - // treat as polygon - Polygon polygon_b = new Polygon(); - polygon_b.addEnvelope(envelope_b, false); - return polygonContainsPolygonImpl_(polygon_a, polygon_b, tolerance, - null); - } - - // Returns true if polygon_a crosses envelope_b. - private static boolean polygonCrossesEnvelope_(Polygon polygon_a, - Envelope envelope_b, double tolerance, - ProgressTracker progress_tracker) { - Envelope2D env_a = new Envelope2D(), env_b = new Envelope2D(); - polygon_a.queryEnvelope2D(env_a); - envelope_b.queryEnvelope2D(env_b); - - if (envelopeInfContainsEnvelope_(env_b, env_a, tolerance)) - return false; - - if (env_b.getHeight() > tolerance && env_b.getWidth() > tolerance) - return false; // when treated as an area, areas cannot cross areas. - - if (env_b.getHeight() <= tolerance && env_b.getWidth() <= tolerance) - return false; // when treated as a point, areas cannot cross points. - - // Treat as polyline - Polyline polyline_b = new Polyline(); - Point p = new Point(); - envelope_b.queryCornerByVal(0, p); - polyline_b.startPath(p); - envelope_b.queryCornerByVal(2, p); - polyline_b.lineTo(p); - return polygonCrossesPolylineImpl_(polygon_a, polyline_b, tolerance, - progress_tracker); - } - - // Returns true if polyline_a equals polyline_b. - private static boolean polylineEqualsPolyline_(Polyline polyline_a, - Polyline polyline_b, double tolerance, - ProgressTracker progress_tracker) { - Envelope2D env_a = new Envelope2D(), env_b = new Envelope2D(); - polyline_a.queryEnvelope2D(env_a); - polyline_b.queryEnvelope2D(env_b); - - // Quick envelope rejection test for false equality. - if (!envelopeEqualsEnvelope_(env_a, env_b, tolerance, progress_tracker)) - return false; - - // Quick rasterize test to see whether the the geometries are disjoint. - if (tryRasterizedContainsOrDisjoint_(polyline_a, polyline_b, tolerance, - false) == Relation.disjoint) - return false; - - // Quick point equality check for true equality. This just checks if all - // the points in each ring are the same (within a tolerance) and in the - // same order - if (multiPathExactlyEqualsMultiPath_(polyline_a, polyline_b, tolerance, - progress_tracker)) - return true; - - return linearPathEqualsLinearPath_(polyline_a, polyline_b, tolerance, false); - } - - // Returns true if polyline_a is disjoint from polyline_b. - private static boolean polylineDisjointPolyline_(Polyline polyline_a, - Polyline polyline_b, double tolerance, - ProgressTracker progress_tracker) { - // Quick rasterize test to see whether the the geometries are disjoint. - if (tryRasterizedContainsOrDisjoint_(polyline_a, polyline_b, tolerance, - false) == Relation.disjoint) - return true; - - MultiPathImpl multi_path_impl_a = (MultiPathImpl) polyline_a._getImpl(); - MultiPathImpl multi_path_impl_b = (MultiPathImpl) polyline_b._getImpl(); - - PairwiseIntersectorImpl intersector_paths = new PairwiseIntersectorImpl(multi_path_impl_a, multi_path_impl_b, tolerance, true); - - if (!intersector_paths.next()) - return false; - - return !linearPathIntersectsLinearPath_(polyline_a, polyline_b, - tolerance); - } - - // Returns true if polyline_a touches polyline_b. - private static boolean polylineTouchesPolyline_(Polyline polyline_a, - Polyline polyline_b, double tolerance, - ProgressTracker progress_tracker) { - // Quick rasterize test to see whether the the geometries are disjoint. - if (tryRasterizedContainsOrDisjoint_(polyline_a, polyline_b, tolerance, - false) == Relation.disjoint) - return false; - - AttributeStreamOfDbl intersections = new AttributeStreamOfDbl(0); - - int dim = linearPathIntersectsLinearPathMaxDim_(polyline_a, polyline_b, - tolerance, intersections); - - if (dim != 0) - return false; - - MultiPoint intersection = new MultiPoint(); - - for (int i = 0; i < intersections.size(); i += 2) { - double x = intersections.read(i); - double y = intersections.read(i + 1); - intersection.add(x, y); - } - - MultiPoint boundary_a_b = (MultiPoint) (polyline_a.getBoundary()); - MultiPoint boundary_b = (MultiPoint) (polyline_b.getBoundary()); - - boundary_a_b.add(boundary_b, 0, boundary_b.getPointCount()); - - return multiPointContainsMultiPointBrute_(boundary_a_b, intersection, - tolerance); - } - - // Returns true if polyline_a crosses polyline_b. - private static boolean polylineCrossesPolyline_(Polyline polyline_a, - Polyline polyline_b, double tolerance, - ProgressTracker progress_tracker) { - // Quick rasterize test to see whether the the geometries are disjoint. - if (tryRasterizedContainsOrDisjoint_(polyline_a, polyline_b, tolerance, - false) == Relation.disjoint) - return false; - - AttributeStreamOfDbl intersections = new AttributeStreamOfDbl(0); - - int dim = linearPathIntersectsLinearPathMaxDim_(polyline_a, polyline_b, - tolerance, intersections); - - if (dim != 0) - return false; - - MultiPoint intersection = new MultiPoint(); - - for (int i = 0; i < intersections.size(); i += 2) { - double x = intersections.read(i); - double y = intersections.read(i + 1); - intersection.add(x, y); - } - - MultiPoint boundary_a_b = (MultiPoint) (polyline_a.getBoundary()); - MultiPoint boundary_b = (MultiPoint) (polyline_b.getBoundary()); - - boundary_a_b.add(boundary_b, 0, boundary_b.getPointCount()); - - return !multiPointContainsMultiPointBrute_(boundary_a_b, intersection, - tolerance); - } - - // Returns true if polyline_a overlaps polyline_b. - private static boolean polylineOverlapsPolyline_(Polyline polyline_a, - Polyline polyline_b, double tolerance, - ProgressTracker progress_tracker) { - // Quick rasterize test to see whether the the geometries are disjoint. - if (tryRasterizedContainsOrDisjoint_(polyline_a, polyline_b, tolerance, - false) == Relation.disjoint) - return false; - - return linearPathOverlapsLinearPath_(polyline_a, polyline_b, tolerance); - } - - // Returns true if polyline_a contains polyline_b. - private static boolean polylineContainsPolyline_(Polyline polyline_a, - Polyline polyline_b, double tolerance, - ProgressTracker progress_tracker) { - Envelope2D env_a = new Envelope2D(), env_b = new Envelope2D(); - polyline_a.queryEnvelope2D(env_a); - polyline_b.queryEnvelope2D(env_b); - - // Quick envelope rejection test for false equality. - if (!envelopeInfContainsEnvelope_(env_a, env_b, tolerance)) - return false; - - // Quick rasterize test to see whether the the geometries are disjoint. - if (tryRasterizedContainsOrDisjoint_(polyline_a, polyline_b, tolerance, - false) == Relation.disjoint) - return false; - - return linearPathWithinLinearPath_(polyline_b, polyline_a, tolerance, false); - } - - // Returns true if polyline_a is disjoint from point_b. - private static boolean polylineDisjointPoint_(Polyline polyline_a, - Point point_b, double tolerance, ProgressTracker progress_tracker) { - // Quick rasterize test to see whether the the geometries are disjoint. - if (tryRasterizedContainsOrDisjoint_(polyline_a, point_b, tolerance, - false) == Relation.disjoint) - return true; - - Point2D pt_b = point_b.getXY(); - return !linearPathIntersectsPoint_(polyline_a, pt_b, tolerance); - } - - // Returns true if polyline_a touches point_b. - private static boolean polylineTouchesPoint_(Polyline polyline_a, - Point point_b, double tolerance, ProgressTracker progress_tracker) { - // Quick rasterize test to see whether the the geometries are disjoint. - if (tryRasterizedContainsOrDisjoint_(polyline_a, point_b, tolerance, - false) == Relation.disjoint) - return false; - - Point2D pt_b = point_b.getXY(); - return linearPathTouchesPointImpl_(polyline_a, pt_b, tolerance); - } - - // Returns true of polyline_a contains point_b. - private static boolean polylineContainsPoint_(Polyline polyline_a, - Point point_b, double tolerance, ProgressTracker progress_tracker) { - // Quick rasterize test to see whether the the geometries are disjoint. - if (tryRasterizedContainsOrDisjoint_(polyline_a, point_b, tolerance, - false) == Relation.disjoint) - return false; - - Point2D pt_b = point_b.getXY(); - return linearPathContainsPoint_(polyline_a, pt_b, tolerance); - } - - // Returns true if polyline_a is disjoint from multipoint_b. - private static boolean polylineDisjointMultiPoint_(Polyline polyline_a, - MultiPoint multipoint_b, double tolerance, - ProgressTracker progress_tracker) { - // Quick rasterize test to see whether the the geometries are disjoint. - if (tryRasterizedContainsOrDisjoint_(polyline_a, multipoint_b, - tolerance, false) == Relation.disjoint) - return true; - - return !linearPathIntersectsMultiPoint_(polyline_a, multipoint_b, - tolerance, false); - } - - // Returns true if polyline_a touches multipoint_b. - private static boolean polylineTouchesMultiPoint_(Polyline polyline_a, - MultiPoint multipoint_b, double tolerance, - ProgressTracker progress_tracker) { - // Quick rasterize test to see whether the the geometries are disjoint. - if (tryRasterizedContainsOrDisjoint_(polyline_a, multipoint_b, - tolerance, false) == Relation.disjoint) { - return false; - } - - SegmentIteratorImpl segIterA = ((MultiPathImpl) polyline_a._getImpl()) - .querySegmentIterator(); - - Envelope2D env_a = new Envelope2D(); - Envelope2D env_b = new Envelope2D(); - Envelope2D envInter = new Envelope2D(); - polyline_a.queryEnvelope2D(env_a); - multipoint_b.queryEnvelope2D(env_b); - env_a.inflate(tolerance, tolerance); - env_b.inflate(tolerance, tolerance); - envInter.setCoords(env_a); - envInter.intersect(env_b); - - QuadTreeImpl qtA = null; - QuadTreeImpl quadTreeA = null; - QuadTreeImpl quadTreePathsA = null; - - GeometryAccelerators accel = ((MultiPathImpl) (polyline_a._getImpl())) - ._getAccelerators(); - - if (accel != null) { - quadTreeA = accel.getQuadTree(); - quadTreePathsA = accel.getQuadTreeForPaths(); - if (quadTreeA == null) { - qtA = InternalUtils.buildQuadTree( - (MultiPathImpl) polyline_a._getImpl(), envInter); - quadTreeA = qtA; - } - } else { - qtA = InternalUtils.buildQuadTree( - (MultiPathImpl) polyline_a._getImpl(), envInter); - quadTreeA = qtA; - } - - QuadTreeImpl.QuadTreeIteratorImpl qtIterA = quadTreeA.getIterator(); - - QuadTreeImpl.QuadTreeIteratorImpl qtIterPathsA = null; - if (quadTreePathsA != null) - qtIterPathsA = quadTreePathsA.getIterator(); - - Point2D ptB = new Point2D(), closest = new Point2D(); - boolean b_intersects = false; - double toleranceSq = tolerance * tolerance; - - AttributeStreamOfInt8 intersects = new AttributeStreamOfInt8( - multipoint_b.getPointCount()); - for (int i = 0; i < multipoint_b.getPointCount(); i++) { - intersects.write(i, (byte) 0); - } - - for (int i = 0; i < multipoint_b.getPointCount(); i++) { - multipoint_b.getXY(i, ptB); - - if (!envInter.contains(ptB)) { - continue; - } - - env_b.setCoords(ptB.x, ptB.y, ptB.x, ptB.y); - - if (qtIterPathsA != null) { - qtIterPathsA.resetIterator(env_b, tolerance); - - if (qtIterPathsA.next() == -1) - continue; - } - - qtIterA.resetIterator(env_b, tolerance); - - for (int elementHandleA = qtIterA.next(); elementHandleA != -1; elementHandleA = qtIterA - .next()) { - int vertex_a = quadTreeA.getElement(elementHandleA); - segIterA.resetToVertex(vertex_a); - - Segment segmentA = segIterA.nextSegment(); - double t = segmentA.getClosestCoordinate(ptB, false); - segmentA.getCoord2D(t, closest); - - if (Point2D.sqrDistance(ptB, closest) <= toleranceSq) { - intersects.write(i, (byte) 1); - b_intersects = true; - break; - } - } - } - - if (!b_intersects) { - return false; - } - - MultiPoint boundary_a = (MultiPoint) (polyline_a.getBoundary()); - MultiPoint multipoint_b_inter = new MultiPoint(); - Point2D pt = new Point2D(); - - for (int i = 0; i < multipoint_b.getPointCount(); i++) { - if (intersects.read(i) == 0) { - continue; - } - - multipoint_b.getXY(i, pt); - multipoint_b_inter.add(pt.x, pt.y); - } - - return multiPointContainsMultiPointBrute_(boundary_a, - multipoint_b_inter, tolerance); - } - - // Returns true if polyline_a crosses multipoint_b. - private static boolean polylineCrossesMultiPoint_(Polyline polyline_a, - MultiPoint multipoint_b, double tolerance, - ProgressTracker progress_tracker) { - // Quick rasterize test to see whether the the geometries are disjoint. - if (tryRasterizedContainsOrDisjoint_(polyline_a, multipoint_b, - tolerance, false) == Relation.disjoint) { - return false; - } - - SegmentIteratorImpl segIterA = ((MultiPathImpl) polyline_a._getImpl()) - .querySegmentIterator(); - - Envelope2D env_a = new Envelope2D(); - Envelope2D env_b = new Envelope2D(); - Envelope2D envInter = new Envelope2D(); - polyline_a.queryEnvelope2D(env_a); - multipoint_b.queryEnvelope2D(env_b); - env_a.inflate(tolerance, tolerance); - env_b.inflate(tolerance, tolerance); - envInter.setCoords(env_a); - envInter.intersect(env_b); - - QuadTreeImpl qtA = null; - QuadTreeImpl quadTreeA = null; - QuadTreeImpl quadTreePathsA = null; - - GeometryAccelerators accel = ((MultiPathImpl) (polyline_a._getImpl())) - ._getAccelerators(); - - if (accel != null) { - quadTreeA = accel.getQuadTree(); - quadTreePathsA = accel.getQuadTreeForPaths(); - if (quadTreeA == null) { - qtA = InternalUtils.buildQuadTree( - (MultiPathImpl) polyline_a._getImpl(), envInter); - quadTreeA = qtA; - } - } else { - qtA = InternalUtils.buildQuadTree( - (MultiPathImpl) polyline_a._getImpl(), envInter); - quadTreeA = qtA; - } - - QuadTreeImpl.QuadTreeIteratorImpl qtIterA = quadTreeA.getIterator(); - - QuadTreeImpl.QuadTreeIteratorImpl qtIterPathsA = null; - if (quadTreePathsA != null) - qtIterPathsA = quadTreePathsA.getIterator(); - - Point2D ptB = new Point2D(), closest = new Point2D(); - boolean b_intersects = false; - boolean b_exterior_found = false; - double toleranceSq = tolerance * tolerance; - - AttributeStreamOfInt8 intersects = new AttributeStreamOfInt8( - multipoint_b.getPointCount()); - for (int i = 0; i < multipoint_b.getPointCount(); i++) { - intersects.write(i, (byte) 0); - } - - for (int i = 0; i < multipoint_b.getPointCount(); i++) { - multipoint_b.getXY(i, ptB); - - if (!envInter.contains(ptB)) { - b_exterior_found = true; - continue; - } - - env_b.setCoords(ptB.x, ptB.y, ptB.x, ptB.y); - - if (qtIterPathsA != null) { - qtIterPathsA.resetIterator(env_b, tolerance); - - if (qtIterPathsA.next() == -1) { - b_exterior_found = true; - continue; - } - } - - qtIterA.resetIterator(env_b, tolerance); - - boolean b_covered = false; - - for (int elementHandleA = qtIterA.next(); elementHandleA != -1; elementHandleA = qtIterA - .next()) { - int vertex_a = quadTreeA.getElement(elementHandleA); - segIterA.resetToVertex(vertex_a); - - Segment segmentA = segIterA.nextSegment(); - double t = segmentA.getClosestCoordinate(ptB, false); - segmentA.getCoord2D(t, closest); - - if (Point2D.sqrDistance(ptB, closest) <= toleranceSq) { - intersects.write(i, (byte) 1); - b_intersects = true; - b_covered = true; - break; - } - } - - if (!b_covered) { - b_exterior_found = true; - } - } - - if (!b_intersects || !b_exterior_found) { - return false; - } - - MultiPoint boundary_a = (MultiPoint) (polyline_a.getBoundary()); - MultiPoint multipoint_b_inter = new MultiPoint(); - Point2D pt = new Point2D(); - - for (int i = 0; i < multipoint_b.getPointCount(); i++) { - if (intersects.read(i) == 0) { - continue; - } - - multipoint_b.getXY(i, pt); - multipoint_b_inter.add(pt.x, pt.y); - } - - return !multiPointContainsMultiPointBrute_(boundary_a, - multipoint_b_inter, tolerance); - } - - // Returns true if polyline_a contains multipoint_b. - private static boolean polylineContainsMultiPoint_(Polyline polyline_a, - MultiPoint multipoint_b, double tolerance, - ProgressTracker progress_tracker) { - Envelope2D env_a = new Envelope2D(), env_b = new Envelope2D(); - polyline_a.queryEnvelope2D(env_a); - multipoint_b.queryEnvelope2D(env_b); - - if (!envelopeInfContainsEnvelope_(env_a, env_b, tolerance)) - return false; - - // Quick rasterize test to see whether the the geometries are disjoint. - if (tryRasterizedContainsOrDisjoint_(polyline_a, multipoint_b, - tolerance, false) == Relation.disjoint) - return false; - - if (!linearPathIntersectsMultiPoint_(polyline_a, multipoint_b, - tolerance, true)) - return false; - - MultiPoint boundary_a = (MultiPoint) (polyline_a.getBoundary()); - return !multiPointIntersectsMultiPoint_(boundary_a, multipoint_b, - tolerance, progress_tracker); - } - - // Returns true if polyline_a equals envelope_b. - private static boolean polylineEqualsEnvelope_(Polyline polyline_a, - Envelope envelope_b, double tolerance, - ProgressTracker progress_tracker) { - Envelope2D env_a = new Envelope2D(), env_b = new Envelope2D(); - polyline_a.queryEnvelope2D(env_a); - envelope_b.queryEnvelope2D(env_b); - - if (env_b.getHeight() > tolerance && env_b.getWidth() > tolerance) - return false; // area cannot equal a line - - return envelopeEqualsEnvelope_(env_a, env_b, tolerance, - progress_tracker); - } - - // Returns true if polyline_a is disjoint from envelope_b. - private static boolean polylineDisjointEnvelope_(Polyline polyline_a, - Envelope envelope_b, double tolerance, - ProgressTracker progress_tracker) { - Envelope2D env_a = new Envelope2D(), env_b = new Envelope2D(); - polyline_a.queryEnvelope2D(env_a); - envelope_b.queryEnvelope2D(env_b); - - if (envelopeInfContainsEnvelope_(env_b, env_a, tolerance)) - return false; - - return !linearPathIntersectsEnvelope_(polyline_a, env_b, tolerance, - progress_tracker); - } - - // Returns true if polyline_a touches envelope_b. - private static boolean polylineTouchesEnvelope_(Polyline polyline_a, - Envelope envelope_b, double tolerance, - ProgressTracker progress_tracker) { - Envelope2D env_a = new Envelope2D(), env_b = new Envelope2D(); - polyline_a.queryEnvelope2D(env_a); - envelope_b.queryEnvelope2D(env_b); - - if (env_b.getHeight() <= tolerance && env_b.getWidth() <= tolerance) {// Treat - // as - // point - Point2D pt_b = envelope_b.getCenterXY(); - return linearPathTouchesPointImpl_(polyline_a, pt_b, tolerance); - } - - if (env_b.getHeight() <= tolerance || env_b.getWidth() <= tolerance) {// Treat - // as - // polyline - Polyline polyline_b = new Polyline(); - Point p = new Point(); - envelope_b.queryCornerByVal(0, p); - polyline_b.startPath(p); - envelope_b.queryCornerByVal(2, p); - polyline_b.lineTo(p); - return polylineTouchesPolyline_(polyline_a, polyline_b, tolerance, - progress_tracker); - } - - // Treat env_b as area - - SegmentIterator seg_iter_a = polyline_a.querySegmentIterator(); - Envelope2D env_b_deflated = new Envelope2D(), env_b_inflated = new Envelope2D(); - env_b_deflated.setCoords(env_b); - env_b_inflated.setCoords(env_b); - env_b_deflated.inflate(-tolerance, -tolerance); - env_b_inflated.inflate(tolerance, tolerance); - - boolean b_boundary = false; - Envelope2D env_segment_a = new Envelope2D(); - Envelope2D env_inter = new Envelope2D(); - - while (seg_iter_a.nextPath()) { - while (seg_iter_a.hasNextSegment()) { - Segment segment_a = seg_iter_a.nextSegment(); - segment_a.queryEnvelope2D(env_segment_a); - - env_inter.setCoords(env_b_deflated); - env_inter.intersect(env_segment_a); - - if (!env_inter.isEmpty() - && (env_inter.getHeight() > tolerance || env_inter - .getWidth() > tolerance)) - return false; // consider segment within - - env_inter.setCoords(env_b_inflated); - env_inter.intersect(env_segment_a); - - if (!env_inter.isEmpty()) - b_boundary = true; - } - } - - return b_boundary; - } - - // Returns true if polyline_a overlaps envelope_b. - private static boolean polylineOverlapsEnvelope_(Polyline polyline_a, - Envelope envelope_b, double tolerance, - ProgressTracker progress_tracker) { - Envelope2D env_a = new Envelope2D(), env_b = new Envelope2D(); - polyline_a.queryEnvelope2D(env_a); - envelope_b.queryEnvelope2D(env_b); - - if (envelopeInfContainsEnvelope_(env_a, env_b, tolerance) - || envelopeInfContainsEnvelope_(env_b, env_a, tolerance)) - return false; - - if (envelopeInfContainsEnvelope_(env_b, env_a, tolerance)) - return false; - - if (env_b.getHeight() > tolerance && env_b.getWidth() > tolerance) - return false; // lines cannot overlap areas - - if (env_b.getHeight() <= tolerance && env_b.getWidth() <= tolerance) - return false; // lines cannot overlap points - - // Treat as polyline - Polyline polyline_b = new Polyline(); - Point p = new Point(); - envelope_b.queryCornerByVal(0, p); - polyline_b.startPath(p); - envelope_b.queryCornerByVal(2, p); - polyline_b.lineTo(p); - return linearPathOverlapsLinearPath_(polyline_a, polyline_b, tolerance); - } - - // Returns true if polyline_a is within envelope_b. - private static boolean polylineWithinEnvelope_(Polyline polyline_a, - Envelope envelope_b, double tolerance, - ProgressTracker progress_tracker) { - Envelope2D env_a = new Envelope2D(), env_b = new Envelope2D(); - polyline_a.queryEnvelope2D(env_a); - envelope_b.queryEnvelope2D(env_b); - - if (!envelopeInfContainsEnvelope_(env_b, env_a, tolerance)) - return false; - - if (env_b.getHeight() <= tolerance && env_b.getWidth() <= tolerance) - return false; - - if (env_b.getHeight() <= tolerance || env_b.getWidth() <= tolerance) - return envelopeInfContainsEnvelope_(env_b, env_a, tolerance); - - SegmentIterator seg_iter_a = polyline_a.querySegmentIterator(); - Envelope2D env_b_deflated = new Envelope2D(); - env_b_deflated.setCoords(env_b); - env_b_deflated.inflate(-tolerance, -tolerance); - - boolean b_interior = false; - Envelope2D env_segment_a = new Envelope2D(); - Envelope2D env_inter = new Envelope2D(); - - while (seg_iter_a.nextPath()) { - while (seg_iter_a.hasNextSegment()) { - Segment segment_a = seg_iter_a.nextSegment(); - segment_a.queryEnvelope2D(env_segment_a); - - if (env_b_deflated.containsExclusive(env_segment_a)) { - b_interior = true; - continue; - } - - env_inter.setCoords(env_b_deflated); - env_inter.intersect(env_segment_a); - - if (!env_inter.isEmpty() - && (env_inter.getHeight() > tolerance || env_inter - .getWidth() > tolerance)) - b_interior = true; - } - } - - return b_interior; - } - - // Returns true if polyline_a contains envelope_b. - private static boolean polylineContainsEnvelope_(Polyline polyline_a, - Envelope envelope_b, double tolerance, - ProgressTracker progress_tracker) { - Envelope2D env_a = new Envelope2D(), env_b = new Envelope2D(); - envelope_b.queryEnvelope2D(env_b); - polyline_a.queryEnvelope2D(env_a); - - if (!envelopeInfContainsEnvelope_(env_a, env_b, tolerance)) - return false; - - if (env_b.getHeight() > tolerance && env_b.getWidth() > tolerance) - return false; // when treated as an area, lines cannot contain - // areas. - - if (env_b.getHeight() <= tolerance && env_b.getWidth() <= tolerance) {// Treat - // as - // point - Point2D pt_b = envelope_b.getCenterXY(); - return linearPathContainsPoint_(polyline_a, pt_b, tolerance); - } - - // Treat as polyline - Polyline polyline_b = new Polyline(); - Point p = new Point(); - envelope_b.queryCornerByVal(0, p); - polyline_b.startPath(p); - envelope_b.queryCornerByVal(2, p); - polyline_b.lineTo(p); - return linearPathWithinLinearPath_(polyline_b, polyline_a, tolerance, false); - } - - // Returns true if polyline_a crosses envelope_b. - private static boolean polylineCrossesEnvelope_(Polyline polyline_a, - Envelope envelope_b, double tolerance, - ProgressTracker progress_tracker) { - Envelope2D env_a = new Envelope2D(), env_b = new Envelope2D(); - polyline_a.queryEnvelope2D(env_a); - envelope_b.queryEnvelope2D(env_b); - - if (envelopeInfContainsEnvelope_(env_b, env_a, tolerance)) - return false; - - if (env_b.getHeight() <= tolerance && env_b.getWidth() <= tolerance) - return false; // when treated as a point, lines cannot cross points. - - if (env_b.getHeight() <= tolerance || env_b.getWidth() <= tolerance) {// Treat - // as - // polyline - Polyline polyline_b = new Polyline(); - Point p = new Point(); - envelope_b.queryCornerByVal(0, p); - polyline_b.startPath(p); - envelope_b.queryCornerByVal(2, p); - polyline_b.lineTo(p); - return polylineCrossesPolyline_(polyline_a, polyline_b, tolerance, - progress_tracker); - } - - // Treat env_b as area - - SegmentIterator seg_iter_a = polyline_a.querySegmentIterator(); - Envelope2D env_b_inflated = new Envelope2D(), env_b_deflated = new Envelope2D(); - env_b_deflated.setCoords(env_b); - env_b_inflated.setCoords(env_b); - env_b_deflated.inflate(-tolerance, -tolerance); - env_b_inflated.inflate(tolerance, tolerance); - - boolean b_interior = false, b_exterior = false; - Envelope2D env_segment_a = new Envelope2D(); - Envelope2D env_inter = new Envelope2D(); - - while (seg_iter_a.nextPath()) { - while (seg_iter_a.hasNextSegment()) { - Segment segment_a = seg_iter_a.nextSegment(); - segment_a.queryEnvelope2D(env_segment_a); - - if (!b_exterior) { - if (!env_b_inflated.contains(env_segment_a)) - b_exterior = true; - } - - if (!b_interior) { - env_inter.setCoords(env_b_deflated); - env_inter.intersect(env_segment_a); - - if (!env_inter.isEmpty() - && (env_inter.getHeight() > tolerance || env_inter - .getWidth() > tolerance)) - b_interior = true; - } - - if (b_interior && b_exterior) - return true; - } - } - - return false; - } - - // Returns true if multipoint_a equals multipoint_b. - private static boolean multiPointEqualsMultiPoint_(MultiPoint multipoint_a, - MultiPoint multipoint_b, double tolerance, - ProgressTracker progress_tracker) { - Envelope2D env_a = new Envelope2D(), env_b = new Envelope2D(); - multipoint_a.queryEnvelope2D(env_a); - multipoint_b.queryEnvelope2D(env_b); - - if (!envelopeEqualsEnvelope_(env_a, env_b, tolerance, progress_tracker)) - return false; - - if (multiPointExactlyEqualsMultiPoint_(multipoint_a, multipoint_b, - tolerance, progress_tracker)) - return true; - - return multiPointCoverageMultiPoint_(multipoint_a, multipoint_b, - tolerance, false, true, false, progress_tracker); - } - - // Returns true if multipoint_a is disjoint from multipoint_b. - private static boolean multiPointDisjointMultiPoint_( - MultiPoint multipoint_a, MultiPoint multipoint_b, double tolerance, - ProgressTracker progress_tracker) { - return !multiPointIntersectsMultiPoint_(multipoint_a, multipoint_b, - tolerance, progress_tracker); - } - - // Returns true if multipoint_a overlaps multipoint_b. - private static boolean multiPointOverlapsMultiPoint_( - MultiPoint multipoint_a, MultiPoint multipoint_b, double tolerance, - ProgressTracker progress_tracker) { - return multiPointCoverageMultiPoint_(multipoint_a, multipoint_b, - tolerance, false, false, true, progress_tracker); - } - - // Returns true if multipoint_a contains multipoint_b. - private static boolean multiPointContainsMultiPoint_( - MultiPoint multipoint_a, MultiPoint multipoint_b, double tolerance, - ProgressTracker progress_tracker) { - Envelope2D env_a = new Envelope2D(), env_b = new Envelope2D(); - multipoint_a.queryEnvelope2D(env_a); - multipoint_b.queryEnvelope2D(env_b); - - if (!envelopeInfContainsEnvelope_(env_a, env_b, tolerance)) - return false; - - return multiPointCoverageMultiPoint_(multipoint_b, multipoint_a, - tolerance, true, false, false, progress_tracker); - } - - private static boolean multiPointContainsMultiPointBrute_( - MultiPoint multipoint_a, MultiPoint multipoint_b, double tolerance) { - double tolerance_sq = tolerance * tolerance; - Point2D pt_a = new Point2D(); - Point2D pt_b = new Point2D(); - - for (int i = 0; i < multipoint_b.getPointCount(); i++) { - multipoint_b.getXY(i, pt_b); - boolean b_contained = false; - - for (int j = 0; j < multipoint_a.getPointCount(); j++) { - multipoint_a.getXY(j, pt_a); - - if (Point2D.sqrDistance(pt_a, pt_b) <= tolerance_sq) { - b_contained = true; - break; - } - } - - if (!b_contained) - return false; - } - - return true; - } - - // Returns true if multipoint_a equals point_b. - static boolean multiPointEqualsPoint_(MultiPoint multipoint_a, - Point point_b, double tolerance, ProgressTracker progress_tracker) { - Envelope2D env_a = new Envelope2D(), env_b = new Envelope2D(); - multipoint_a.queryEnvelope2D(env_a); - point_b.queryEnvelope2D(env_b); - return envelopeEqualsEnvelope_(env_a, env_b, tolerance, - progress_tracker); - } - - // Returns true if multipoint_a is disjoint from point_b. - private static boolean multiPointDisjointPoint_(MultiPoint multipoint_a, - Point point_b, double tolerance, ProgressTracker progress_tracker) { - Point2D pt_b = point_b.getXY(); - return multiPointDisjointPointImpl_(multipoint_a, pt_b, tolerance, - progress_tracker); - } - - // Returns true if multipoint_a is within point_b. - private static boolean multiPointWithinPoint_(MultiPoint multipoint_a, - Point point_b, double tolerance, ProgressTracker progress_tracker) { - return multiPointEqualsPoint_(multipoint_a, point_b, tolerance, - progress_tracker); - } - - // Returns true if multipoint_a contains point_b. - private static boolean multiPointContainsPoint_(MultiPoint multipoint_a, - Point point_b, double tolerance, ProgressTracker progress_tracker) { - return !multiPointDisjointPoint_(multipoint_a, point_b, tolerance, - progress_tracker); - } - - // Returns true if multipoint_a equals envelope_b. - private static boolean multiPointEqualsEnvelope_(MultiPoint multipoint_a, - Envelope envelope_b, double tolerance, - ProgressTracker progress_tracker) { - Envelope2D env_a = new Envelope2D(), env_b = new Envelope2D(); - multipoint_a.queryEnvelope2D(env_a); - envelope_b.queryEnvelope2D(env_b); - - if (env_b.getHeight() > tolerance || env_b.getWidth() > tolerance) - return false; - - // only true if all the points of the multi_point degenerate to a point - // equal to the envelope - return envelopeEqualsEnvelope_(env_a, env_b, tolerance, - progress_tracker); - } - - // Returns true if multipoint_a is disjoint from envelope_b. - private static boolean multiPointDisjointEnvelope_(MultiPoint multipoint_a, - Envelope envelope_b, double tolerance, - ProgressTracker progress_tracker) { - Envelope2D env_a = new Envelope2D(), env_b = new Envelope2D(); - multipoint_a.queryEnvelope2D(env_a); - envelope_b.queryEnvelope2D(env_b); - - if (envelopeInfContainsEnvelope_(env_b, env_a, tolerance)) - return false; - - Envelope2D env_b_inflated = new Envelope2D(); - env_b_inflated.setCoords(env_b); - env_b_inflated.inflate(tolerance, tolerance); - Point2D pt_a = new Point2D(); - - for (int i = 0; i < multipoint_a.getPointCount(); i++) { - multipoint_a.getXY(i, pt_a); - - if (!env_b_inflated.contains(pt_a)) - continue; - - return false; - } - - return true; - } - - // Returns true if multipoint_a touches envelope_b. - private static boolean multiPointTouchesEnvelope_(MultiPoint multipoint_a, - Envelope envelope_b, double tolerance, - ProgressTracker progress_tracker) { - Envelope2D env_b = new Envelope2D(), env_b_inflated = new Envelope2D(), env_b_deflated = new Envelope2D(); - envelope_b.queryEnvelope2D(env_b); - - if (env_b.getHeight() <= tolerance && env_b.getWidth() <= tolerance) - return false; // there are no boundaries to intersect - - if (env_b.getHeight() <= tolerance || env_b.getWidth() <= tolerance) {// treat - // as - // line - - Point2D pt_a = new Point2D(); - boolean b_boundary = false; - - env_b_inflated.setCoords(env_b); - env_b_deflated.setCoords(env_b); - env_b_inflated.inflate(tolerance, tolerance); - if (env_b.getHeight() > tolerance) - env_b_deflated.inflate(0, -tolerance); - else - env_b_deflated.inflate(-tolerance, 0); - - for (int i = 0; i < multipoint_a.getPointCount(); i++) { - multipoint_a.getXY(i, pt_a); - - if (!env_b_inflated.contains(pt_a)) - continue; - - if (env_b.getHeight() > tolerance) { - if (pt_a.y > env_b_deflated.ymin - && pt_a.y < env_b_deflated.ymax) - return false; - - b_boundary = true; - } else { - if (pt_a.x > env_b_deflated.xmin - && pt_a.x < env_b_deflated.xmax) - return false; - - b_boundary = true; - } - } - - return b_boundary; - } - - // treat as area - env_b_inflated.setCoords(env_b); - env_b_deflated.setCoords(env_b); - env_b_inflated.inflate(tolerance, tolerance); - env_b_deflated.inflate(-tolerance, -tolerance); + if (!b_checked_polygon_a_quad_tree) { + if (PointInPolygonHelper.quadTreeWillHelp(polygon_a, multipoint_b.getPointCount() - 1) && (polygon_a_impl._getAccelerators() == null || polygon_a_impl._getAccelerators().getQuadTree() == null)) { + pa = new Polygon(); + polygon_a.copyTo(pa); + ((MultiPathImpl) pa._getImpl())._buildQuadTreeAccelerator(Geometry.GeometryAccelerationDegree.enumMedium); + p_polygon_a = pa; + } else { + p_polygon_a = polygon_a; + } + + b_checked_polygon_a_quad_tree = true; + } + } + + return false; + } + + // Returns true if polygon_a contains multipoint_b. + private static boolean polygonContainsMultiPoint_(Polygon polygon_a, + MultiPoint multipoint_b, double tolerance, + ProgressTracker progress_tracker) { + Envelope2D env_a = new Envelope2D(), env_b = new Envelope2D(); + polygon_a.queryEnvelope2D(env_a); + multipoint_b.queryEnvelope2D(env_b); + + // Quick envelope rejection test for false equality. + if (!envelopeInfContainsEnvelope_(env_a, env_b, tolerance)) + return false; + + // Quick rasterize test to see whether the the geometries are disjoint, + // or if one is contained in the other. + int relation = tryRasterizedContainsOrDisjoint_(polygon_a, + multipoint_b, tolerance, false); + + if (relation == Relation.disjoint) + return false; + + if (relation == Relation.contains) + return true; + + boolean b_interior = false; + Point2D ptB; + + MultiPathImpl polygon_a_impl = (MultiPathImpl) polygon_a._getImpl(); + + Polygon pa = null; + Polygon p_polygon_a = polygon_a; + + boolean b_checked_polygon_a_quad_tree = false; + + for (int i = 0; i < multipoint_b.getPointCount(); i++) { + ptB = multipoint_b.getXY(i); + + if (!env_a.contains(ptB)) + return false; + + PolygonUtils.PiPResult result = PolygonUtils.isPointInPolygon2D(p_polygon_a, ptB, tolerance); + + if (result == PolygonUtils.PiPResult.PiPInside) + b_interior = true; + else if (result == PolygonUtils.PiPResult.PiPOutside) + return false; + + if (!b_checked_polygon_a_quad_tree) { + if (PointInPolygonHelper.quadTreeWillHelp(polygon_a, multipoint_b.getPointCount() - 1) && (polygon_a_impl._getAccelerators() == null || polygon_a_impl._getAccelerators().getQuadTree() == null)) { + pa = new Polygon(); + polygon_a.copyTo(pa); + ((MultiPathImpl) pa._getImpl())._buildQuadTreeAccelerator(Geometry.GeometryAccelerationDegree.enumMedium); + p_polygon_a = pa; + } else { + p_polygon_a = polygon_a; + } + + b_checked_polygon_a_quad_tree = true; + } + } + + return b_interior; + } + + // Returns true if polygon_a equals envelope_b. + private static boolean polygonEqualsEnvelope_(Polygon polygon_a, + Envelope envelope_b, double tolerance, + ProgressTracker progress_tracker) { + Envelope2D env_a = new Envelope2D(), env_b = new Envelope2D(); + polygon_a.queryEnvelope2D(env_a); + envelope_b.queryEnvelope2D(env_b); + + // Quick envelope rejection test for false equality. + // This check will correctly handle degenerate envelope cases (i.e. + // degenerate to point or line) + if (!envelopeEqualsEnvelope_(env_a, env_b, tolerance, progress_tracker)) + return false; + + Polygon polygon_b = new Polygon(); + polygon_b.addEnvelope(envelope_b, false); + + return linearPathEqualsLinearPath_(polygon_a, polygon_b, tolerance, true); + } + + // Returns true if polygon_a is disjoint from envelope_b. + private static boolean polygonDisjointEnvelope_(Polygon polygon_a, + Envelope envelope_b, double tolerance, + ProgressTracker progress_tracker) { + // Quick rasterize test to see whether the the geometries are disjoint, + // or if one is contained in the other. + int relation = tryRasterizedContainsOrDisjoint_(polygon_a, envelope_b, + tolerance, false); + + if (relation == Relation.disjoint) + return true; + + if (relation == Relation.contains || relation == Relation.within) + return false; + + Envelope2D env_a = new Envelope2D(), env_b = new Envelope2D(); + polygon_a.queryEnvelope2D(env_a); + envelope_b.queryEnvelope2D(env_b); + + if (envelopeInfContainsEnvelope_(env_b, env_a, tolerance)) + return false; + + PolygonUtils.PiPResult pipres; + Point2D pt_b = new Point2D(); + env_b.queryLowerLeft(pt_b); + pipres = PolygonUtils.isPointInPolygon2D(polygon_a, pt_b, tolerance); + if (pipres != PolygonUtils.PiPResult.PiPOutside) + return false; + + env_b.queryLowerRight(pt_b); + pipres = PolygonUtils.isPointInPolygon2D(polygon_a, pt_b, tolerance); + if (pipres != PolygonUtils.PiPResult.PiPOutside) + return false; + + env_b.queryUpperRight(pt_b); + pipres = PolygonUtils.isPointInPolygon2D(polygon_a, pt_b, tolerance); + if (pipres != PolygonUtils.PiPResult.PiPOutside) + return false; + + env_b.queryUpperLeft(pt_b); + pipres = PolygonUtils.isPointInPolygon2D(polygon_a, pt_b, tolerance); + if (pipres != PolygonUtils.PiPResult.PiPOutside) + return false; + + MultiPathImpl mimpl_a = (MultiPathImpl) polygon_a._getImpl(); + AttributeStreamOfDbl pos = (AttributeStreamOfDbl) (mimpl_a + .getAttributeStreamRef(VertexDescription.Semantics.POSITION)); + + Envelope2D env_b_inflated = new Envelope2D(); + env_b_inflated.setCoords(env_b); + env_b_inflated.inflate(tolerance, tolerance); + for (int ptIndex = 0, n = mimpl_a.getPointCount(); ptIndex < n; ptIndex++) { + double x = pos.read(2 * ptIndex); + double y = pos.read(2 * ptIndex + 1); + if (env_b_inflated.contains(x, y)) + return false; + } + + return !linearPathIntersectsEnvelope_(polygon_a, env_b, tolerance, + progress_tracker); + } + + // Returns true if polygon_a touches envelope_b. + private static boolean polygonTouchesEnvelope_(Polygon polygon_a, + Envelope envelope_b, double tolerance, + ProgressTracker progress_tracker) { + // Quick rasterize test to see whether the the geometries are disjoint, + // or if one is contained in the other. + int relation = tryRasterizedContainsOrDisjoint_(polygon_a, envelope_b, + tolerance, false); + + if (relation == Relation.disjoint || relation == Relation.contains + || relation == Relation.within) + return false; + + Envelope2D env_a = new Envelope2D(), env_b = new Envelope2D(); + polygon_a.queryEnvelope2D(env_a); + envelope_b.queryEnvelope2D(env_b); + + if (envelopeInfContainsEnvelope_(env_b, env_a, tolerance)) + return false; + + if (env_b.getWidth() <= tolerance && env_b.getHeight() <= tolerance) {// treat + // as + // point + Point2D pt_b = envelope_b.getCenterXY(); + return polygonTouchesPointImpl_(polygon_a, pt_b, tolerance, + progress_tracker); + } + + if (env_b.getWidth() <= tolerance || env_b.getHeight() <= tolerance) {// treat + // as + // polyline + Polyline polyline_b = new Polyline(); + Point p = new Point(); + envelope_b.queryCornerByVal(0, p); + polyline_b.startPath(p); + envelope_b.queryCornerByVal(2, p); + polyline_b.lineTo(p); + return polygonTouchesPolylineImpl_(polygon_a, polyline_b, + tolerance, progress_tracker); + } + + // treat as polygon + Polygon polygon_b = new Polygon(); + polygon_b.addEnvelope(envelope_b, false); + return polygonTouchesPolygonImpl_(polygon_a, polygon_b, tolerance, + progress_tracker); + } + + // Returns true if polygon_a overlaps envelope_b. + private static boolean polygonOverlapsEnvelope_(Polygon polygon_a, + Envelope envelope_b, double tolerance, + ProgressTracker progress_tracker) { + // Quick rasterize test to see whether the the geometries are disjoint, + // or if one is contained in the other. + int relation = tryRasterizedContainsOrDisjoint_(polygon_a, envelope_b, + tolerance, false); + + if (relation == Relation.disjoint || relation == Relation.contains + || relation == Relation.within) + return false; + + Envelope2D env_a = new Envelope2D(), env_b = new Envelope2D(); + polygon_a.queryEnvelope2D(env_a); + envelope_b.queryEnvelope2D(env_b); + + if (envelopeInfContainsEnvelope_(env_b, env_a, tolerance)) + return false; + + if (env_b.getWidth() <= tolerance || env_b.getHeight() <= tolerance) + return false; // has no interior + + Polygon polygon_b = new Polygon(); + polygon_b.addEnvelope(envelope_b, false); + return polygonOverlapsPolygonImpl_(polygon_a, polygon_b, tolerance, + progress_tracker); + } + + // Returns true if polygon_a is within envelope_b + private static boolean polygonWithinEnvelope_(Polygon polygon_a, + Envelope envelope_b, double tolerance, + ProgressTracker progress_tracker) { + Envelope2D env_a = new Envelope2D(), env_b = new Envelope2D(); + polygon_a.queryEnvelope2D(env_a); + envelope_b.queryEnvelope2D(env_b); + return envelopeInfContainsEnvelope_(env_b, env_a, tolerance); + } + + // Returns true if polygon_a contains envelope_b. + private static boolean polygonContainsEnvelope_(Polygon polygon_a, + Envelope envelope_b, double tolerance, + ProgressTracker progress_tracker) { + // Quick envelope rejection test + + Envelope2D env_a = new Envelope2D(), env_b = new Envelope2D(); + polygon_a.queryEnvelope2D(env_a); + envelope_b.queryEnvelope2D(env_b); + + if (!envelopeInfContainsEnvelope_(env_a, env_b, tolerance)) + return false; + + // Quick rasterize test to see whether the the geometries are disjoint, + // or if one is contained in the other. + int relation = tryRasterizedContainsOrDisjoint_(polygon_a, envelope_b, + tolerance, false); + + if (relation == Relation.disjoint || relation == Relation.within) + return false; + + if (relation == Relation.contains) + return true; + + if (env_b.getWidth() <= tolerance && env_b.getHeight() <= tolerance) {// treat + // as + // point + Point2D pt_b = envelope_b.getCenterXY(); + return polygonContainsPointImpl_(polygon_a, pt_b, tolerance, + progress_tracker); + } + + if (env_b.getWidth() <= tolerance || env_b.getHeight() <= tolerance) {// treat + // as + // polyline + Polyline polyline_b = new Polyline(); + Point p = new Point(); + envelope_b.queryCornerByVal(0, p); + polyline_b.startPath(p); + envelope_b.queryCornerByVal(2, p); + polyline_b.lineTo(p); + return polygonContainsPolylineImpl_(polygon_a, polyline_b, + tolerance, null); + } + + // treat as polygon + Polygon polygon_b = new Polygon(); + polygon_b.addEnvelope(envelope_b, false); + return polygonContainsPolygonImpl_(polygon_a, polygon_b, tolerance, + null); + } + + // Returns true if polygon_a crosses envelope_b. + private static boolean polygonCrossesEnvelope_(Polygon polygon_a, + Envelope envelope_b, double tolerance, + ProgressTracker progress_tracker) { + Envelope2D env_a = new Envelope2D(), env_b = new Envelope2D(); + polygon_a.queryEnvelope2D(env_a); + envelope_b.queryEnvelope2D(env_b); + + if (envelopeInfContainsEnvelope_(env_b, env_a, tolerance)) + return false; + + if (env_b.getHeight() > tolerance && env_b.getWidth() > tolerance) + return false; // when treated as an area, areas cannot cross areas. + + if (env_b.getHeight() <= tolerance && env_b.getWidth() <= tolerance) + return false; // when treated as a point, areas cannot cross points. + + // Treat as polyline + Polyline polyline_b = new Polyline(); + Point p = new Point(); + envelope_b.queryCornerByVal(0, p); + polyline_b.startPath(p); + envelope_b.queryCornerByVal(2, p); + polyline_b.lineTo(p); + return polygonCrossesPolylineImpl_(polygon_a, polyline_b, tolerance, + progress_tracker); + } + + // Returns true if polyline_a equals polyline_b. + private static boolean polylineEqualsPolyline_(Polyline polyline_a, + Polyline polyline_b, double tolerance, + ProgressTracker progress_tracker) { + Envelope2D env_a = new Envelope2D(), env_b = new Envelope2D(); + polyline_a.queryEnvelope2D(env_a); + polyline_b.queryEnvelope2D(env_b); + + // Quick envelope rejection test for false equality. + if (!envelopeEqualsEnvelope_(env_a, env_b, tolerance, progress_tracker)) + return false; + + // Quick rasterize test to see whether the the geometries are disjoint. + if (tryRasterizedContainsOrDisjoint_(polyline_a, polyline_b, tolerance, + false) == Relation.disjoint) + return false; + + // Quick point equality check for true equality. This just checks if all + // the points in each ring are the same (within a tolerance) and in the + // same order + if (multiPathExactlyEqualsMultiPath_(polyline_a, polyline_b, tolerance, + progress_tracker)) + return true; + + return linearPathEqualsLinearPath_(polyline_a, polyline_b, tolerance, false); + } + + // Returns true if polyline_a is disjoint from polyline_b. + private static boolean polylineDisjointPolyline_(Polyline polyline_a, + Polyline polyline_b, double tolerance, + ProgressTracker progress_tracker) { + // Quick rasterize test to see whether the the geometries are disjoint. + if (tryRasterizedContainsOrDisjoint_(polyline_a, polyline_b, tolerance, + false) == Relation.disjoint) + return true; + + MultiPathImpl multi_path_impl_a = (MultiPathImpl) polyline_a._getImpl(); + MultiPathImpl multi_path_impl_b = (MultiPathImpl) polyline_b._getImpl(); + + PairwiseIntersectorImpl intersector_paths = new PairwiseIntersectorImpl(multi_path_impl_a, multi_path_impl_b, tolerance, true); + + if (!intersector_paths.next()) + return false; + + return !linearPathIntersectsLinearPath_(polyline_a, polyline_b, + tolerance); + } + + // Returns true if polyline_a touches polyline_b. + private static boolean polylineTouchesPolyline_(Polyline polyline_a, + Polyline polyline_b, double tolerance, + ProgressTracker progress_tracker) { + // Quick rasterize test to see whether the the geometries are disjoint. + if (tryRasterizedContainsOrDisjoint_(polyline_a, polyline_b, tolerance, + false) == Relation.disjoint) + return false; + + AttributeStreamOfDbl intersections = new AttributeStreamOfDbl(0); + + int dim = linearPathIntersectsLinearPathMaxDim_(polyline_a, polyline_b, + tolerance, intersections); + + if (dim != 0) + return false; + + MultiPoint intersection = new MultiPoint(); + + for (int i = 0; i < intersections.size(); i += 2) { + double x = intersections.read(i); + double y = intersections.read(i + 1); + intersection.add(x, y); + } + + MultiPoint boundary_a_b = (MultiPoint) (polyline_a.getBoundary()); + MultiPoint boundary_b = (MultiPoint) (polyline_b.getBoundary()); + + boundary_a_b.add(boundary_b, 0, boundary_b.getPointCount()); + + return multiPointContainsMultiPointBrute_(boundary_a_b, intersection, + tolerance); + } + + // Returns true if polyline_a crosses polyline_b. + private static boolean polylineCrossesPolyline_(Polyline polyline_a, + Polyline polyline_b, double tolerance, + ProgressTracker progress_tracker) { + // Quick rasterize test to see whether the the geometries are disjoint. + if (tryRasterizedContainsOrDisjoint_(polyline_a, polyline_b, tolerance, + false) == Relation.disjoint) + return false; + + AttributeStreamOfDbl intersections = new AttributeStreamOfDbl(0); + + int dim = linearPathIntersectsLinearPathMaxDim_(polyline_a, polyline_b, + tolerance, intersections); + + if (dim != 0) + return false; + + MultiPoint intersection = new MultiPoint(); + + for (int i = 0; i < intersections.size(); i += 2) { + double x = intersections.read(i); + double y = intersections.read(i + 1); + intersection.add(x, y); + } + + MultiPoint boundary_a_b = (MultiPoint) (polyline_a.getBoundary()); + MultiPoint boundary_b = (MultiPoint) (polyline_b.getBoundary()); + + boundary_a_b.add(boundary_b, 0, boundary_b.getPointCount()); + + return !multiPointContainsMultiPointBrute_(boundary_a_b, intersection, + tolerance); + } + + // Returns true if polyline_a overlaps polyline_b. + private static boolean polylineOverlapsPolyline_(Polyline polyline_a, + Polyline polyline_b, double tolerance, + ProgressTracker progress_tracker) { + // Quick rasterize test to see whether the the geometries are disjoint. + if (tryRasterizedContainsOrDisjoint_(polyline_a, polyline_b, tolerance, + false) == Relation.disjoint) + return false; + + return linearPathOverlapsLinearPath_(polyline_a, polyline_b, tolerance); + } + + // Returns true if polyline_a contains polyline_b. + private static boolean polylineContainsPolyline_(Polyline polyline_a, + Polyline polyline_b, double tolerance, + ProgressTracker progress_tracker) { + Envelope2D env_a = new Envelope2D(), env_b = new Envelope2D(); + polyline_a.queryEnvelope2D(env_a); + polyline_b.queryEnvelope2D(env_b); + + // Quick envelope rejection test for false equality. + if (!envelopeInfContainsEnvelope_(env_a, env_b, tolerance)) + return false; + + // Quick rasterize test to see whether the the geometries are disjoint. + if (tryRasterizedContainsOrDisjoint_(polyline_a, polyline_b, tolerance, + false) == Relation.disjoint) + return false; + + return linearPathWithinLinearPath_(polyline_b, polyline_a, tolerance, false); + } + + // Returns true if polyline_a is disjoint from point_b. + private static boolean polylineDisjointPoint_(Polyline polyline_a, + Point point_b, double tolerance, ProgressTracker progress_tracker) { + // Quick rasterize test to see whether the the geometries are disjoint. + if (tryRasterizedContainsOrDisjoint_(polyline_a, point_b, tolerance, + false) == Relation.disjoint) + return true; + + Point2D pt_b = point_b.getXY(); + return !linearPathIntersectsPoint_(polyline_a, pt_b, tolerance); + } + + // Returns true if polyline_a touches point_b. + private static boolean polylineTouchesPoint_(Polyline polyline_a, + Point point_b, double tolerance, ProgressTracker progress_tracker) { + // Quick rasterize test to see whether the the geometries are disjoint. + if (tryRasterizedContainsOrDisjoint_(polyline_a, point_b, tolerance, + false) == Relation.disjoint) + return false; + + Point2D pt_b = point_b.getXY(); + return linearPathTouchesPointImpl_(polyline_a, pt_b, tolerance); + } + + // Returns true of polyline_a contains point_b. + private static boolean polylineContainsPoint_(Polyline polyline_a, + Point point_b, double tolerance, ProgressTracker progress_tracker) { + // Quick rasterize test to see whether the the geometries are disjoint. + if (tryRasterizedContainsOrDisjoint_(polyline_a, point_b, tolerance, + false) == Relation.disjoint) + return false; + + Point2D pt_b = point_b.getXY(); + return linearPathContainsPoint_(polyline_a, pt_b, tolerance); + } + + // Returns true if polyline_a is disjoint from multipoint_b. + private static boolean polylineDisjointMultiPoint_(Polyline polyline_a, + MultiPoint multipoint_b, double tolerance, + ProgressTracker progress_tracker) { + // Quick rasterize test to see whether the the geometries are disjoint. + if (tryRasterizedContainsOrDisjoint_(polyline_a, multipoint_b, + tolerance, false) == Relation.disjoint) + return true; + + return !linearPathIntersectsMultiPoint_(polyline_a, multipoint_b, + tolerance, false); + } + + // Returns true if polyline_a touches multipoint_b. + private static boolean polylineTouchesMultiPoint_(Polyline polyline_a, + MultiPoint multipoint_b, double tolerance, + ProgressTracker progress_tracker) { + // Quick rasterize test to see whether the the geometries are disjoint. + if (tryRasterizedContainsOrDisjoint_(polyline_a, multipoint_b, + tolerance, false) == Relation.disjoint) { + return false; + } + + SegmentIteratorImpl segIterA = ((MultiPathImpl) polyline_a._getImpl()) + .querySegmentIterator(); + + Envelope2D env_a = new Envelope2D(); + Envelope2D env_b = new Envelope2D(); + Envelope2D envInter = new Envelope2D(); + polyline_a.queryEnvelope2D(env_a); + multipoint_b.queryEnvelope2D(env_b); + env_a.inflate(tolerance, tolerance); + env_b.inflate(tolerance, tolerance); + envInter.setCoords(env_a); + envInter.intersect(env_b); + + QuadTreeImpl qtA = null; + QuadTreeImpl quadTreeA = null; + QuadTreeImpl quadTreePathsA = null; + + GeometryAccelerators accel = ((MultiPathImpl) (polyline_a._getImpl())) + ._getAccelerators(); + + if (accel != null) { + quadTreeA = accel.getQuadTree(); + quadTreePathsA = accel.getQuadTreeForPaths(); + if (quadTreeA == null) { + qtA = InternalUtils.buildQuadTree( + (MultiPathImpl) polyline_a._getImpl(), envInter); + quadTreeA = qtA; + } + } else { + qtA = InternalUtils.buildQuadTree( + (MultiPathImpl) polyline_a._getImpl(), envInter); + quadTreeA = qtA; + } + + QuadTreeImpl.QuadTreeIteratorImpl qtIterA = quadTreeA.getIterator(); + + QuadTreeImpl.QuadTreeIteratorImpl qtIterPathsA = null; + if (quadTreePathsA != null) + qtIterPathsA = quadTreePathsA.getIterator(); + + Point2D ptB = new Point2D(), closest = new Point2D(); + boolean b_intersects = false; + double toleranceSq = tolerance * tolerance; + + AttributeStreamOfInt8 intersects = new AttributeStreamOfInt8( + multipoint_b.getPointCount()); + for (int i = 0; i < multipoint_b.getPointCount(); i++) { + intersects.write(i, (byte) 0); + } + + for (int i = 0; i < multipoint_b.getPointCount(); i++) { + multipoint_b.getXY(i, ptB); + + if (!envInter.contains(ptB)) { + continue; + } + + env_b.setCoords(ptB.x, ptB.y, ptB.x, ptB.y); + + if (qtIterPathsA != null) { + qtIterPathsA.resetIterator(env_b, tolerance); + + if (qtIterPathsA.next() == -1) + continue; + } + + qtIterA.resetIterator(env_b, tolerance); + + for (int elementHandleA = qtIterA.next(); elementHandleA != -1; elementHandleA = qtIterA + .next()) { + int vertex_a = quadTreeA.getElement(elementHandleA); + segIterA.resetToVertex(vertex_a); + + Segment segmentA = segIterA.nextSegment(); + double t = segmentA.getClosestCoordinate(ptB, false); + segmentA.getCoord2D(t, closest); + + if (Point2D.sqrDistance(ptB, closest) <= toleranceSq) { + intersects.write(i, (byte) 1); + b_intersects = true; + break; + } + } + } + + if (!b_intersects) { + return false; + } + + MultiPoint boundary_a = (MultiPoint) (polyline_a.getBoundary()); + MultiPoint multipoint_b_inter = new MultiPoint(); + Point2D pt = new Point2D(); + + for (int i = 0; i < multipoint_b.getPointCount(); i++) { + if (intersects.read(i) == 0) { + continue; + } + + multipoint_b.getXY(i, pt); + multipoint_b_inter.add(pt.x, pt.y); + } + + return multiPointContainsMultiPointBrute_(boundary_a, + multipoint_b_inter, tolerance); + } + + // Returns true if polyline_a crosses multipoint_b. + private static boolean polylineCrossesMultiPoint_(Polyline polyline_a, + MultiPoint multipoint_b, double tolerance, + ProgressTracker progress_tracker) { + // Quick rasterize test to see whether the the geometries are disjoint. + if (tryRasterizedContainsOrDisjoint_(polyline_a, multipoint_b, + tolerance, false) == Relation.disjoint) { + return false; + } + + SegmentIteratorImpl segIterA = ((MultiPathImpl) polyline_a._getImpl()) + .querySegmentIterator(); + + Envelope2D env_a = new Envelope2D(); + Envelope2D env_b = new Envelope2D(); + Envelope2D envInter = new Envelope2D(); + polyline_a.queryEnvelope2D(env_a); + multipoint_b.queryEnvelope2D(env_b); + env_a.inflate(tolerance, tolerance); + env_b.inflate(tolerance, tolerance); + envInter.setCoords(env_a); + envInter.intersect(env_b); + + QuadTreeImpl qtA = null; + QuadTreeImpl quadTreeA = null; + QuadTreeImpl quadTreePathsA = null; + + GeometryAccelerators accel = ((MultiPathImpl) (polyline_a._getImpl())) + ._getAccelerators(); + + if (accel != null) { + quadTreeA = accel.getQuadTree(); + quadTreePathsA = accel.getQuadTreeForPaths(); + if (quadTreeA == null) { + qtA = InternalUtils.buildQuadTree( + (MultiPathImpl) polyline_a._getImpl(), envInter); + quadTreeA = qtA; + } + } else { + qtA = InternalUtils.buildQuadTree( + (MultiPathImpl) polyline_a._getImpl(), envInter); + quadTreeA = qtA; + } + + QuadTreeImpl.QuadTreeIteratorImpl qtIterA = quadTreeA.getIterator(); + + QuadTreeImpl.QuadTreeIteratorImpl qtIterPathsA = null; + if (quadTreePathsA != null) + qtIterPathsA = quadTreePathsA.getIterator(); + + Point2D ptB = new Point2D(), closest = new Point2D(); + boolean b_intersects = false; + boolean b_exterior_found = false; + double toleranceSq = tolerance * tolerance; + + AttributeStreamOfInt8 intersects = new AttributeStreamOfInt8( + multipoint_b.getPointCount()); + for (int i = 0; i < multipoint_b.getPointCount(); i++) { + intersects.write(i, (byte) 0); + } + + for (int i = 0; i < multipoint_b.getPointCount(); i++) { + multipoint_b.getXY(i, ptB); + + if (!envInter.contains(ptB)) { + b_exterior_found = true; + continue; + } + + env_b.setCoords(ptB.x, ptB.y, ptB.x, ptB.y); + + if (qtIterPathsA != null) { + qtIterPathsA.resetIterator(env_b, tolerance); + + if (qtIterPathsA.next() == -1) { + b_exterior_found = true; + continue; + } + } + + qtIterA.resetIterator(env_b, tolerance); + + boolean b_covered = false; + + for (int elementHandleA = qtIterA.next(); elementHandleA != -1; elementHandleA = qtIterA + .next()) { + int vertex_a = quadTreeA.getElement(elementHandleA); + segIterA.resetToVertex(vertex_a); + + Segment segmentA = segIterA.nextSegment(); + double t = segmentA.getClosestCoordinate(ptB, false); + segmentA.getCoord2D(t, closest); + + if (Point2D.sqrDistance(ptB, closest) <= toleranceSq) { + intersects.write(i, (byte) 1); + b_intersects = true; + b_covered = true; + break; + } + } + + if (!b_covered) { + b_exterior_found = true; + } + } + + if (!b_intersects || !b_exterior_found) { + return false; + } + + MultiPoint boundary_a = (MultiPoint) (polyline_a.getBoundary()); + MultiPoint multipoint_b_inter = new MultiPoint(); + Point2D pt = new Point2D(); + + for (int i = 0; i < multipoint_b.getPointCount(); i++) { + if (intersects.read(i) == 0) { + continue; + } + + multipoint_b.getXY(i, pt); + multipoint_b_inter.add(pt.x, pt.y); + } + + return !multiPointContainsMultiPointBrute_(boundary_a, + multipoint_b_inter, tolerance); + } + + // Returns true if polyline_a contains multipoint_b. + private static boolean polylineContainsMultiPoint_(Polyline polyline_a, + MultiPoint multipoint_b, double tolerance, + ProgressTracker progress_tracker) { + Envelope2D env_a = new Envelope2D(), env_b = new Envelope2D(); + polyline_a.queryEnvelope2D(env_a); + multipoint_b.queryEnvelope2D(env_b); + + if (!envelopeInfContainsEnvelope_(env_a, env_b, tolerance)) + return false; + + // Quick rasterize test to see whether the the geometries are disjoint. + if (tryRasterizedContainsOrDisjoint_(polyline_a, multipoint_b, + tolerance, false) == Relation.disjoint) + return false; + + if (!linearPathIntersectsMultiPoint_(polyline_a, multipoint_b, + tolerance, true)) + return false; + + MultiPoint boundary_a = (MultiPoint) (polyline_a.getBoundary()); + return !multiPointIntersectsMultiPoint_(boundary_a, multipoint_b, + tolerance, progress_tracker); + } + + // Returns true if polyline_a equals envelope_b. + private static boolean polylineEqualsEnvelope_(Polyline polyline_a, + Envelope envelope_b, double tolerance, + ProgressTracker progress_tracker) { + Envelope2D env_a = new Envelope2D(), env_b = new Envelope2D(); + polyline_a.queryEnvelope2D(env_a); + envelope_b.queryEnvelope2D(env_b); + + if (env_b.getHeight() > tolerance && env_b.getWidth() > tolerance) + return false; // area cannot equal a line + + return envelopeEqualsEnvelope_(env_a, env_b, tolerance, + progress_tracker); + } + + // Returns true if polyline_a is disjoint from envelope_b. + private static boolean polylineDisjointEnvelope_(Polyline polyline_a, + Envelope envelope_b, double tolerance, + ProgressTracker progress_tracker) { + Envelope2D env_a = new Envelope2D(), env_b = new Envelope2D(); + polyline_a.queryEnvelope2D(env_a); + envelope_b.queryEnvelope2D(env_b); + + if (envelopeInfContainsEnvelope_(env_b, env_a, tolerance)) + return false; + + return !linearPathIntersectsEnvelope_(polyline_a, env_b, tolerance, + progress_tracker); + } + + // Returns true if polyline_a touches envelope_b. + private static boolean polylineTouchesEnvelope_(Polyline polyline_a, + Envelope envelope_b, double tolerance, + ProgressTracker progress_tracker) { + Envelope2D env_a = new Envelope2D(), env_b = new Envelope2D(); + polyline_a.queryEnvelope2D(env_a); + envelope_b.queryEnvelope2D(env_b); + + if (env_b.getHeight() <= tolerance && env_b.getWidth() <= tolerance) {// Treat + // as + // point + Point2D pt_b = envelope_b.getCenterXY(); + return linearPathTouchesPointImpl_(polyline_a, pt_b, tolerance); + } + + if (env_b.getHeight() <= tolerance || env_b.getWidth() <= tolerance) {// Treat + // as + // polyline + Polyline polyline_b = new Polyline(); + Point p = new Point(); + envelope_b.queryCornerByVal(0, p); + polyline_b.startPath(p); + envelope_b.queryCornerByVal(2, p); + polyline_b.lineTo(p); + return polylineTouchesPolyline_(polyline_a, polyline_b, tolerance, + progress_tracker); + } + + // Treat env_b as area + + SegmentIterator seg_iter_a = polyline_a.querySegmentIterator(); + Envelope2D env_b_deflated = new Envelope2D(), env_b_inflated = new Envelope2D(); + env_b_deflated.setCoords(env_b); + env_b_inflated.setCoords(env_b); + env_b_deflated.inflate(-tolerance, -tolerance); + env_b_inflated.inflate(tolerance, tolerance); + + boolean b_boundary = false; + Envelope2D env_segment_a = new Envelope2D(); + Envelope2D env_inter = new Envelope2D(); + + while (seg_iter_a.nextPath()) { + while (seg_iter_a.hasNextSegment()) { + Segment segment_a = seg_iter_a.nextSegment(); + segment_a.queryEnvelope2D(env_segment_a); + + env_inter.setCoords(env_b_deflated); + env_inter.intersect(env_segment_a); + + if (!env_inter.isEmpty() + && (env_inter.getHeight() > tolerance || env_inter + .getWidth() > tolerance)) + return false; // consider segment within + + env_inter.setCoords(env_b_inflated); + env_inter.intersect(env_segment_a); + + if (!env_inter.isEmpty()) + b_boundary = true; + } + } + + return b_boundary; + } + + // Returns true if polyline_a overlaps envelope_b. + private static boolean polylineOverlapsEnvelope_(Polyline polyline_a, + Envelope envelope_b, double tolerance, + ProgressTracker progress_tracker) { + Envelope2D env_a = new Envelope2D(), env_b = new Envelope2D(); + polyline_a.queryEnvelope2D(env_a); + envelope_b.queryEnvelope2D(env_b); + + if (envelopeInfContainsEnvelope_(env_a, env_b, tolerance) + || envelopeInfContainsEnvelope_(env_b, env_a, tolerance)) + return false; + + if (envelopeInfContainsEnvelope_(env_b, env_a, tolerance)) + return false; + + if (env_b.getHeight() > tolerance && env_b.getWidth() > tolerance) + return false; // lines cannot overlap areas + + if (env_b.getHeight() <= tolerance && env_b.getWidth() <= tolerance) + return false; // lines cannot overlap points + + // Treat as polyline + Polyline polyline_b = new Polyline(); + Point p = new Point(); + envelope_b.queryCornerByVal(0, p); + polyline_b.startPath(p); + envelope_b.queryCornerByVal(2, p); + polyline_b.lineTo(p); + return linearPathOverlapsLinearPath_(polyline_a, polyline_b, tolerance); + } + + // Returns true if polyline_a is within envelope_b. + private static boolean polylineWithinEnvelope_(Polyline polyline_a, + Envelope envelope_b, double tolerance, + ProgressTracker progress_tracker) { + Envelope2D env_a = new Envelope2D(), env_b = new Envelope2D(); + polyline_a.queryEnvelope2D(env_a); + envelope_b.queryEnvelope2D(env_b); + + if (!envelopeInfContainsEnvelope_(env_b, env_a, tolerance)) + return false; + + if (env_b.getHeight() <= tolerance && env_b.getWidth() <= tolerance) + return false; + + if (env_b.getHeight() <= tolerance || env_b.getWidth() <= tolerance) + return envelopeInfContainsEnvelope_(env_b, env_a, tolerance); + + SegmentIterator seg_iter_a = polyline_a.querySegmentIterator(); + Envelope2D env_b_deflated = new Envelope2D(); + env_b_deflated.setCoords(env_b); + env_b_deflated.inflate(-tolerance, -tolerance); + + boolean b_interior = false; + Envelope2D env_segment_a = new Envelope2D(); + Envelope2D env_inter = new Envelope2D(); + + while (seg_iter_a.nextPath()) { + while (seg_iter_a.hasNextSegment()) { + Segment segment_a = seg_iter_a.nextSegment(); + segment_a.queryEnvelope2D(env_segment_a); + + if (env_b_deflated.containsExclusive(env_segment_a)) { + b_interior = true; + continue; + } + + env_inter.setCoords(env_b_deflated); + env_inter.intersect(env_segment_a); + + if (!env_inter.isEmpty() + && (env_inter.getHeight() > tolerance || env_inter + .getWidth() > tolerance)) + b_interior = true; + } + } + + return b_interior; + } + + // Returns true if polyline_a contains envelope_b. + private static boolean polylineContainsEnvelope_(Polyline polyline_a, + Envelope envelope_b, double tolerance, + ProgressTracker progress_tracker) { + Envelope2D env_a = new Envelope2D(), env_b = new Envelope2D(); + envelope_b.queryEnvelope2D(env_b); + polyline_a.queryEnvelope2D(env_a); + + if (!envelopeInfContainsEnvelope_(env_a, env_b, tolerance)) + return false; + + if (env_b.getHeight() > tolerance && env_b.getWidth() > tolerance) + return false; // when treated as an area, lines cannot contain + // areas. + + if (env_b.getHeight() <= tolerance && env_b.getWidth() <= tolerance) {// Treat + // as + // point + Point2D pt_b = envelope_b.getCenterXY(); + return linearPathContainsPoint_(polyline_a, pt_b, tolerance); + } + + // Treat as polyline + Polyline polyline_b = new Polyline(); + Point p = new Point(); + envelope_b.queryCornerByVal(0, p); + polyline_b.startPath(p); + envelope_b.queryCornerByVal(2, p); + polyline_b.lineTo(p); + return linearPathWithinLinearPath_(polyline_b, polyline_a, tolerance, false); + } + + // Returns true if polyline_a crosses envelope_b. + private static boolean polylineCrossesEnvelope_(Polyline polyline_a, + Envelope envelope_b, double tolerance, + ProgressTracker progress_tracker) { + Envelope2D env_a = new Envelope2D(), env_b = new Envelope2D(); + polyline_a.queryEnvelope2D(env_a); + envelope_b.queryEnvelope2D(env_b); + + if (envelopeInfContainsEnvelope_(env_b, env_a, tolerance)) + return false; + + if (env_b.getHeight() <= tolerance && env_b.getWidth() <= tolerance) + return false; // when treated as a point, lines cannot cross points. + + if (env_b.getHeight() <= tolerance || env_b.getWidth() <= tolerance) {// Treat + // as + // polyline + Polyline polyline_b = new Polyline(); + Point p = new Point(); + envelope_b.queryCornerByVal(0, p); + polyline_b.startPath(p); + envelope_b.queryCornerByVal(2, p); + polyline_b.lineTo(p); + return polylineCrossesPolyline_(polyline_a, polyline_b, tolerance, + progress_tracker); + } + + // Treat env_b as area + + SegmentIterator seg_iter_a = polyline_a.querySegmentIterator(); + Envelope2D env_b_inflated = new Envelope2D(), env_b_deflated = new Envelope2D(); + env_b_deflated.setCoords(env_b); + env_b_inflated.setCoords(env_b); + env_b_deflated.inflate(-tolerance, -tolerance); + env_b_inflated.inflate(tolerance, tolerance); + + boolean b_interior = false, b_exterior = false; + Envelope2D env_segment_a = new Envelope2D(); + Envelope2D env_inter = new Envelope2D(); + + while (seg_iter_a.nextPath()) { + while (seg_iter_a.hasNextSegment()) { + Segment segment_a = seg_iter_a.nextSegment(); + segment_a.queryEnvelope2D(env_segment_a); + + if (!b_exterior) { + if (!env_b_inflated.contains(env_segment_a)) + b_exterior = true; + } + + if (!b_interior) { + env_inter.setCoords(env_b_deflated); + env_inter.intersect(env_segment_a); + + if (!env_inter.isEmpty() + && (env_inter.getHeight() > tolerance || env_inter + .getWidth() > tolerance)) + b_interior = true; + } + + if (b_interior && b_exterior) + return true; + } + } + + return false; + } + + // Returns true if multipoint_a equals multipoint_b. + private static boolean multiPointEqualsMultiPoint_(MultiPoint multipoint_a, + MultiPoint multipoint_b, double tolerance, + ProgressTracker progress_tracker) { + Envelope2D env_a = new Envelope2D(), env_b = new Envelope2D(); + multipoint_a.queryEnvelope2D(env_a); + multipoint_b.queryEnvelope2D(env_b); + + if (!envelopeEqualsEnvelope_(env_a, env_b, tolerance, progress_tracker)) + return false; + + if (multiPointExactlyEqualsMultiPoint_(multipoint_a, multipoint_b, + tolerance, progress_tracker)) + return true; + + return multiPointCoverageMultiPoint_(multipoint_a, multipoint_b, + tolerance, false, true, false, progress_tracker); + } + + // Returns true if multipoint_a is disjoint from multipoint_b. + private static boolean multiPointDisjointMultiPoint_( + MultiPoint multipoint_a, MultiPoint multipoint_b, double tolerance, + ProgressTracker progress_tracker) { + return !multiPointIntersectsMultiPoint_(multipoint_a, multipoint_b, + tolerance, progress_tracker); + } + + // Returns true if multipoint_a overlaps multipoint_b. + private static boolean multiPointOverlapsMultiPoint_( + MultiPoint multipoint_a, MultiPoint multipoint_b, double tolerance, + ProgressTracker progress_tracker) { + return multiPointCoverageMultiPoint_(multipoint_a, multipoint_b, + tolerance, false, false, true, progress_tracker); + } + + // Returns true if multipoint_a contains multipoint_b. + private static boolean multiPointContainsMultiPoint_( + MultiPoint multipoint_a, MultiPoint multipoint_b, double tolerance, + ProgressTracker progress_tracker) { + Envelope2D env_a = new Envelope2D(), env_b = new Envelope2D(); + multipoint_a.queryEnvelope2D(env_a); + multipoint_b.queryEnvelope2D(env_b); + + if (!envelopeInfContainsEnvelope_(env_a, env_b, tolerance)) + return false; + + return multiPointCoverageMultiPoint_(multipoint_b, multipoint_a, + tolerance, true, false, false, progress_tracker); + } + + private static boolean multiPointContainsMultiPointBrute_( + MultiPoint multipoint_a, MultiPoint multipoint_b, double tolerance) { + double tolerance_sq = tolerance * tolerance; + Point2D pt_a = new Point2D(); + Point2D pt_b = new Point2D(); + + for (int i = 0; i < multipoint_b.getPointCount(); i++) { + multipoint_b.getXY(i, pt_b); + boolean b_contained = false; + + for (int j = 0; j < multipoint_a.getPointCount(); j++) { + multipoint_a.getXY(j, pt_a); + + if (Point2D.sqrDistance(pt_a, pt_b) <= tolerance_sq) { + b_contained = true; + break; + } + } + + if (!b_contained) + return false; + } + + return true; + } + + // Returns true if multipoint_a equals point_b. + static boolean multiPointEqualsPoint_(MultiPoint multipoint_a, + Point point_b, double tolerance, ProgressTracker progress_tracker) { + Envelope2D env_a = new Envelope2D(), env_b = new Envelope2D(); + multipoint_a.queryEnvelope2D(env_a); + point_b.queryEnvelope2D(env_b); + return envelopeEqualsEnvelope_(env_a, env_b, tolerance, + progress_tracker); + } + + // Returns true if multipoint_a is disjoint from point_b. + private static boolean multiPointDisjointPoint_(MultiPoint multipoint_a, + Point point_b, double tolerance, ProgressTracker progress_tracker) { + Point2D pt_b = point_b.getXY(); + return multiPointDisjointPointImpl_(multipoint_a, pt_b, tolerance, + progress_tracker); + } + + // Returns true if multipoint_a is within point_b. + private static boolean multiPointWithinPoint_(MultiPoint multipoint_a, + Point point_b, double tolerance, ProgressTracker progress_tracker) { + return multiPointEqualsPoint_(multipoint_a, point_b, tolerance, + progress_tracker); + } + + // Returns true if multipoint_a contains point_b. + private static boolean multiPointContainsPoint_(MultiPoint multipoint_a, + Point point_b, double tolerance, ProgressTracker progress_tracker) { + return !multiPointDisjointPoint_(multipoint_a, point_b, tolerance, + progress_tracker); + } + + // Returns true if multipoint_a equals envelope_b. + private static boolean multiPointEqualsEnvelope_(MultiPoint multipoint_a, + Envelope envelope_b, double tolerance, + ProgressTracker progress_tracker) { + Envelope2D env_a = new Envelope2D(), env_b = new Envelope2D(); + multipoint_a.queryEnvelope2D(env_a); + envelope_b.queryEnvelope2D(env_b); + + if (env_b.getHeight() > tolerance || env_b.getWidth() > tolerance) + return false; + + // only true if all the points of the multi_point degenerate to a point + // equal to the envelope + return envelopeEqualsEnvelope_(env_a, env_b, tolerance, + progress_tracker); + } + + // Returns true if multipoint_a is disjoint from envelope_b. + private static boolean multiPointDisjointEnvelope_(MultiPoint multipoint_a, + Envelope envelope_b, double tolerance, + ProgressTracker progress_tracker) { + Envelope2D env_a = new Envelope2D(), env_b = new Envelope2D(); + multipoint_a.queryEnvelope2D(env_a); + envelope_b.queryEnvelope2D(env_b); + + if (envelopeInfContainsEnvelope_(env_b, env_a, tolerance)) + return false; + + Envelope2D env_b_inflated = new Envelope2D(); + env_b_inflated.setCoords(env_b); + env_b_inflated.inflate(tolerance, tolerance); + Point2D pt_a = new Point2D(); + + for (int i = 0; i < multipoint_a.getPointCount(); i++) { + multipoint_a.getXY(i, pt_a); + + if (!env_b_inflated.contains(pt_a)) + continue; + + return false; + } + + return true; + } + + // Returns true if multipoint_a touches envelope_b. + private static boolean multiPointTouchesEnvelope_(MultiPoint multipoint_a, + Envelope envelope_b, double tolerance, + ProgressTracker progress_tracker) { + Envelope2D env_b = new Envelope2D(), env_b_inflated = new Envelope2D(), env_b_deflated = new Envelope2D(); + envelope_b.queryEnvelope2D(env_b); + + if (env_b.getHeight() <= tolerance && env_b.getWidth() <= tolerance) + return false; // there are no boundaries to intersect + + if (env_b.getHeight() <= tolerance || env_b.getWidth() <= tolerance) {// treat + // as + // line + + Point2D pt_a = new Point2D(); + boolean b_boundary = false; + + env_b_inflated.setCoords(env_b); + env_b_deflated.setCoords(env_b); + env_b_inflated.inflate(tolerance, tolerance); + if (env_b.getHeight() > tolerance) + env_b_deflated.inflate(0, -tolerance); + else + env_b_deflated.inflate(-tolerance, 0); + + for (int i = 0; i < multipoint_a.getPointCount(); i++) { + multipoint_a.getXY(i, pt_a); + + if (!env_b_inflated.contains(pt_a)) + continue; + + if (env_b.getHeight() > tolerance) { + if (pt_a.y > env_b_deflated.ymin + && pt_a.y < env_b_deflated.ymax) + return false; + + b_boundary = true; + } else { + if (pt_a.x > env_b_deflated.xmin + && pt_a.x < env_b_deflated.xmax) + return false; + + b_boundary = true; + } + } + + return b_boundary; + } + + // treat as area + env_b_inflated.setCoords(env_b); + env_b_deflated.setCoords(env_b); + env_b_inflated.inflate(tolerance, tolerance); + env_b_deflated.inflate(-tolerance, -tolerance); - Point2D pt_a = new Point2D(); - boolean b_boundary = false; - - for (int i = 0; i < multipoint_a.getPointCount(); i++) { - multipoint_a.getXY(i, pt_a); - - if (!env_b_inflated.contains(pt_a)) - continue; - - if (env_b_deflated.containsExclusive(pt_a)) - return false; - - b_boundary = true; - } + Point2D pt_a = new Point2D(); + boolean b_boundary = false; + + for (int i = 0; i < multipoint_a.getPointCount(); i++) { + multipoint_a.getXY(i, pt_a); + + if (!env_b_inflated.contains(pt_a)) + continue; + + if (env_b_deflated.containsExclusive(pt_a)) + return false; + + b_boundary = true; + } - return b_boundary; - } + return b_boundary; + } - // Returns true if multipoint_a is within envelope_b. - private static boolean multiPointWithinEnvelope_(MultiPoint multipoint_a, - Envelope envelope_b, double tolerance, - ProgressTracker progress_tracker) { - Envelope2D env_a = new Envelope2D(), env_b = new Envelope2D(); - multipoint_a.queryEnvelope2D(env_a); - envelope_b.queryEnvelope2D(env_b); + // Returns true if multipoint_a is within envelope_b. + private static boolean multiPointWithinEnvelope_(MultiPoint multipoint_a, + Envelope envelope_b, double tolerance, + ProgressTracker progress_tracker) { + Envelope2D env_a = new Envelope2D(), env_b = new Envelope2D(); + multipoint_a.queryEnvelope2D(env_a); + envelope_b.queryEnvelope2D(env_b); - if (!envelopeInfContainsEnvelope_(env_b, env_a, tolerance)) - return false; + if (!envelopeInfContainsEnvelope_(env_b, env_a, tolerance)) + return false; - if (env_b.getHeight() <= tolerance && env_b.getWidth() <= tolerance) - return envelopeEqualsEnvelope_(env_a, env_b, tolerance, - progress_tracker); // treat as point + if (env_b.getHeight() <= tolerance && env_b.getWidth() <= tolerance) + return envelopeEqualsEnvelope_(env_a, env_b, tolerance, + progress_tracker); // treat as point - if (env_b.getHeight() <= tolerance || env_b.getWidth() <= tolerance) {// treat - // as - // line - - boolean b_interior = false; + if (env_b.getHeight() <= tolerance || env_b.getWidth() <= tolerance) {// treat + // as + // line + + boolean b_interior = false; - Envelope2D env_b_deflated = new Envelope2D(), env_b_inflated = new Envelope2D(); - env_b_deflated.setCoords(env_b); - env_b_inflated.setCoords(env_b); - - if (env_b.getHeight() > tolerance) - env_b_deflated.inflate(0, -tolerance); - else - env_b_deflated.inflate(-tolerance, 0); - - env_b_inflated.inflate(tolerance, tolerance); + Envelope2D env_b_deflated = new Envelope2D(), env_b_inflated = new Envelope2D(); + env_b_deflated.setCoords(env_b); + env_b_inflated.setCoords(env_b); + + if (env_b.getHeight() > tolerance) + env_b_deflated.inflate(0, -tolerance); + else + env_b_deflated.inflate(-tolerance, 0); + + env_b_inflated.inflate(tolerance, tolerance); - Point2D pt_a = new Point2D(); + Point2D pt_a = new Point2D(); - for (int i = 0; i < multipoint_a.getPointCount(); i++) { - multipoint_a.getXY(i, pt_a); + for (int i = 0; i < multipoint_a.getPointCount(); i++) { + multipoint_a.getXY(i, pt_a); - if (!env_b_inflated.contains(pt_a)) - return false; - - if (env_b.getHeight() > tolerance) { - if (pt_a.y > env_b_deflated.ymin - && pt_a.y < env_b_deflated.ymax) - b_interior = true; - } else { - if (pt_a.x > env_b_deflated.xmin - && pt_a.x < env_b_deflated.xmax) - b_interior = true; - } - } - - return b_interior; - } - - // treat as area - - boolean b_interior = false; - - Envelope2D env_b_deflated = new Envelope2D(), env_b_inflated = new Envelope2D(); - env_b_deflated.setCoords(env_b); - env_b_inflated.setCoords(env_b); - env_b_deflated.inflate(-tolerance, -tolerance); - env_b_inflated.inflate(tolerance, tolerance); - - Point2D pt_a = new Point2D(); - - // we loop to find a proper interior intersection (i.e. something inside - // instead of just on the boundary) - for (int i = 0; i < multipoint_a.getPointCount(); i++) { - multipoint_a.getXY(i, pt_a); + if (!env_b_inflated.contains(pt_a)) + return false; + + if (env_b.getHeight() > tolerance) { + if (pt_a.y > env_b_deflated.ymin + && pt_a.y < env_b_deflated.ymax) + b_interior = true; + } else { + if (pt_a.x > env_b_deflated.xmin + && pt_a.x < env_b_deflated.xmax) + b_interior = true; + } + } + + return b_interior; + } + + // treat as area + + boolean b_interior = false; + + Envelope2D env_b_deflated = new Envelope2D(), env_b_inflated = new Envelope2D(); + env_b_deflated.setCoords(env_b); + env_b_inflated.setCoords(env_b); + env_b_deflated.inflate(-tolerance, -tolerance); + env_b_inflated.inflate(tolerance, tolerance); + + Point2D pt_a = new Point2D(); + + // we loop to find a proper interior intersection (i.e. something inside + // instead of just on the boundary) + for (int i = 0; i < multipoint_a.getPointCount(); i++) { + multipoint_a.getXY(i, pt_a); - if (!env_b_inflated.contains(pt_a)) - return false; + if (!env_b_inflated.contains(pt_a)) + return false; - if (env_b_deflated.containsExclusive(pt_a)) - b_interior = true; - } + if (env_b_deflated.containsExclusive(pt_a)) + b_interior = true; + } - return b_interior; - } + return b_interior; + } - // Returns true if multipoint_a contains envelope_b. - private static boolean multiPointContainsEnvelope_(MultiPoint multipoint_a, - Envelope envelope_b, double tolerance, - ProgressTracker progress_tracker) { - Envelope2D env_a = new Envelope2D(), env_b = new Envelope2D(); - multipoint_a.queryEnvelope2D(env_a); - envelope_b.queryEnvelope2D(env_b); - - if (!envelopeInfContainsEnvelope_(env_a, env_b, tolerance)) - return false; - - if (env_b.getHeight() > tolerance || env_b.getWidth() > tolerance) - return false; - - Point2D pt_b = envelope_b.getCenterXY(); - return !multiPointDisjointPointImpl_(multipoint_a, pt_b, tolerance, - progress_tracker); - } - - // Returns true if multipoint_a crosses envelope_b. - static boolean multiPointCrossesEnvelope_(MultiPoint multipoint_a, - Envelope envelope_b, double tolerance, - ProgressTracker progress_tracker) { - Envelope2D env_a = new Envelope2D(), env_b = new Envelope2D(); - multipoint_a.queryEnvelope2D(env_a); - envelope_b.queryEnvelope2D(env_b); + // Returns true if multipoint_a contains envelope_b. + private static boolean multiPointContainsEnvelope_(MultiPoint multipoint_a, + Envelope envelope_b, double tolerance, + ProgressTracker progress_tracker) { + Envelope2D env_a = new Envelope2D(), env_b = new Envelope2D(); + multipoint_a.queryEnvelope2D(env_a); + envelope_b.queryEnvelope2D(env_b); + + if (!envelopeInfContainsEnvelope_(env_a, env_b, tolerance)) + return false; + + if (env_b.getHeight() > tolerance || env_b.getWidth() > tolerance) + return false; + + Point2D pt_b = envelope_b.getCenterXY(); + return !multiPointDisjointPointImpl_(multipoint_a, pt_b, tolerance, + progress_tracker); + } + + // Returns true if multipoint_a crosses envelope_b. + static boolean multiPointCrossesEnvelope_(MultiPoint multipoint_a, + Envelope envelope_b, double tolerance, + ProgressTracker progress_tracker) { + Envelope2D env_a = new Envelope2D(), env_b = new Envelope2D(); + multipoint_a.queryEnvelope2D(env_a); + envelope_b.queryEnvelope2D(env_b); - if (envelopeInfContainsEnvelope_(env_b, env_a, tolerance)) - return false; - - if (env_b.getHeight() <= tolerance && env_b.getWidth() <= tolerance) - return false; - - if (env_b.getHeight() <= tolerance || env_b.getWidth() <= tolerance) {// treat - // as - // line - Envelope2D env_b_deflated = new Envelope2D(); - Envelope2D env_b_inflated = new Envelope2D(); - env_b_deflated.setCoords(env_b); - - if (env_b.getHeight() > tolerance) - env_b_deflated.inflate(0, -tolerance); - else - env_b_deflated.inflate(-tolerance, 0); - - env_b_inflated.setCoords(env_b); - env_b_inflated.inflate(tolerance, tolerance); - - Point2D pt_a = new Point2D(); - boolean b_interior = false, b_exterior = false; - - for (int i = 0; i < multipoint_a.getPointCount(); i++) { - multipoint_a.getXY(i, pt_a); - - if (!b_interior) { - if (env_b.getHeight() > tolerance) { - if (pt_a.y > env_b_deflated.ymin - && pt_a.y < env_b_deflated.ymax) - b_interior = true; - } else { - if (pt_a.x > env_b_deflated.xmin - && pt_a.x < env_b_deflated.xmax) - b_interior = true; - } - } - - if (!b_exterior && !env_b_inflated.contains(pt_a)) - b_exterior = true; - - if (b_interior && b_exterior) - return true; - } - - return false; - } - - Envelope2D env_b_deflated = new Envelope2D(), env_b_inflated = new Envelope2D(); - env_b_deflated.setCoords(env_b); - env_b_deflated.inflate(-tolerance, -tolerance); - assert (!env_b_deflated.isEmpty()); - - env_b_inflated.setCoords(env_b); - env_b_inflated.inflate(tolerance, tolerance); - - Point2D pt_a = new Point2D(); - boolean b_interior = false, b_exterior = false; - - for (int i = 0; i < multipoint_a.getPointCount(); i++) { - multipoint_a.getXY(i, pt_a); - - if (!b_interior && env_b_deflated.containsExclusive(pt_a)) - b_interior = true; - - if (!b_exterior && !env_b_inflated.contains(pt_a)) - b_exterior = true; - - if (b_interior && b_exterior) - return true; - } - - return false; - } - - // Returns true if pt_a equals pt_b. - private static boolean pointEqualsPoint_(Point2D pt_a, Point2D pt_b, - double tolerance, ProgressTracker progress_tracker) { - if (Point2D.sqrDistance(pt_a, pt_b) <= tolerance * tolerance) - return true; - - return false; - } - - // Returns true if pt_a is disjoint from pt_b. - private static boolean pointDisjointPoint_(Point2D pt_a, Point2D pt_b, - double tolerance, ProgressTracker progress_tracker) { - if (Point2D.sqrDistance(pt_a, pt_b) > tolerance * tolerance) - return true; - - return false; - } - - // Returns true if pt_a contains pt_b. - private static boolean pointContainsPoint_(Point2D pt_a, Point2D pt_b, - double tolerance, ProgressTracker progress_tracker) { - return pointEqualsPoint_(pt_a, pt_b, tolerance, progress_tracker); - } - - // Returns true if pt_a equals enve_b. - private static boolean pointEqualsEnvelope_(Point2D pt_a, Envelope2D env_b, - double tolerance, ProgressTracker progress_tracker) { - Envelope2D env_a = new Envelope2D(); - env_a.setCoords(pt_a); - return envelopeEqualsEnvelope_(env_a, env_b, tolerance, - progress_tracker); - } - - // Returns true if pt_a is disjoint from env_b. - static boolean pointDisjointEnvelope_(Point2D pt_a, Envelope2D env_b, - double tolerance, ProgressTracker progress_tracker) { - Envelope2D env_b_inflated = new Envelope2D(); - env_b_inflated.setCoords(env_b); - env_b_inflated.inflate(tolerance, tolerance); - return !env_b_inflated.contains(pt_a); - } - - // Returns true if pt_a touches env_b. - private static boolean pointTouchesEnvelope_(Point2D pt_a, - Envelope2D env_b, double tolerance, ProgressTracker progress_tracker) { - if (env_b.getHeight() <= tolerance && env_b.getWidth() <= tolerance) - return false; // when treates as a point, points cannot touch points - - Envelope2D env_b_inflated = new Envelope2D(), env_b_deflated = new Envelope2D(); - - env_b_inflated.setCoords(env_b); - env_b_inflated.inflate(tolerance, tolerance); - - if (!env_b_inflated.contains(pt_a)) - return false; - - if (env_b.getHeight() <= tolerance || env_b.getWidth() <= tolerance) { - env_b_deflated.setCoords(env_b); - - if (env_b.getHeight() > tolerance) - env_b_deflated.inflate(0, -tolerance); - else - env_b_deflated.inflate(-tolerance, 0); - - if (env_b.getHeight() > tolerance) { - if (pt_a.y > env_b_deflated.ymin - && pt_a.y < env_b_deflated.ymax) - return false; - } else { - if (pt_a.x > env_b_deflated.xmin - && pt_a.x < env_b_deflated.xmax) - return false; - } - - return true; - } - - env_b_deflated.setCoords(env_b); - env_b_deflated.inflate(-tolerance, -tolerance); - - if (env_b_deflated.containsExclusive(pt_a)) - return false; - - return true; - } - - // Returns true if pt_a is within env_b. - private static boolean pointWithinEnvelope_(Point2D pt_a, Envelope2D env_b, - double tolerance, ProgressTracker progress_tracker) { - if (env_b.getHeight() <= tolerance && env_b.getWidth() <= tolerance) { - // assert(env_b_inflated.contains(pt_a)); // should contain if we - // got to here (i.e. not disjoint) - return true; - } - - if (env_b.getHeight() <= tolerance || env_b.getWidth() <= tolerance) {// treat - // as - // line - Envelope2D env_b_deflated = new Envelope2D(); - env_b_deflated.setCoords(env_b); - - if (env_b.getHeight() > tolerance) - env_b_deflated.inflate(0, -tolerance); - else - env_b_deflated.inflate(-tolerance, 0); - - boolean b_interior = false; - - if (env_b.getHeight() > tolerance) { - if (pt_a.y > env_b_deflated.ymin - && pt_a.y < env_b_deflated.ymax) - b_interior = true; - } else { - if (pt_a.x > env_b_deflated.xmin - && pt_a.x < env_b_deflated.xmax) - b_interior = true; - } - - return b_interior; - } - - // treat as area - - Envelope2D env_b_deflated = new Envelope2D(); - env_b_deflated.setCoords(env_b); - env_b_deflated.inflate(-tolerance, -tolerance); - return env_b_deflated.containsExclusive(pt_a); - } - - // Returns true if pt_a contains env_b. - private static boolean pointContainsEnvelope_(Point2D pt_a, - Envelope2D env_b, double tolerance, ProgressTracker progress_tracker) { - return pointEqualsEnvelope_(pt_a, env_b, tolerance, progress_tracker); - } - - // Returns true if env_a equals env_b. - private static boolean envelopeEqualsEnvelope_(Envelope2D env_a, - Envelope2D env_b, double tolerance, ProgressTracker progress_tracker) { - return envelopeInfContainsEnvelope_(env_a, env_b, tolerance) - && envelopeInfContainsEnvelope_(env_b, env_a, tolerance); - } - - // Returns true if env_a is disjoint from env_b. - static boolean envelopeDisjointEnvelope_(Envelope2D env_a, - Envelope2D env_b, double tolerance, ProgressTracker progress_tracker) { - Envelope2D env_b_inflated = new Envelope2D(); - env_b_inflated.setCoords(env_b); - env_b_inflated.inflate(tolerance, tolerance); - return !env_a.isIntersecting(env_b_inflated); - } - - // Returns true if env_a touches env_b. - private static boolean envelopeTouchesEnvelope_(Envelope2D env_a, - Envelope2D env_b, double tolerance, ProgressTracker progress_tracker) { - if (env_a.getHeight() <= tolerance && env_a.getWidth() <= tolerance) {// treat - // env_a - // as - // point - Point2D pt_a = env_a.getCenter(); - return pointTouchesEnvelope_(pt_a, env_b, tolerance, - progress_tracker); - } - - if (env_b.getHeight() <= tolerance && env_b.getWidth() <= tolerance) {// treat - // env_b - // as - // point - Point2D pt_b = env_b.getCenter(); - return pointTouchesEnvelope_(pt_b, env_a, tolerance, - progress_tracker); - } - - Envelope2D _env_a; - Envelope2D _env_b; - - if (env_a.getHeight() > tolerance - && env_a.getWidth() > tolerance - && (env_b.getHeight() <= tolerance || env_b.getWidth() <= tolerance)) { - // swap a and b - _env_a = env_b; - _env_b = env_a; - } else { - _env_a = env_a; - _env_b = env_b; - } - - if (_env_a.getHeight() <= tolerance || _env_a.getWidth() <= tolerance) {// treat - // env_a - // as - // line - - if (_env_b.getHeight() <= tolerance - || _env_b.getWidth() <= tolerance) {// treat env_b as line - - Line line_a = new Line(), line_b = new Line(); - double[] scalars_a = new double[2]; - double[] scalars_b = new double[2]; - Point2D pt = new Point2D(); - _env_a.queryLowerLeft(pt); - line_a.setStartXY(pt); - _env_a.queryUpperRight(pt); - line_a.setEndXY(pt); - _env_b.queryLowerLeft(pt); - line_b.setStartXY(pt); - _env_b.queryUpperRight(pt); - line_b.setEndXY(pt); - - line_a.intersect(line_b, null, scalars_a, scalars_b, tolerance); - int count = line_a.intersect(line_b, null, null, null, - tolerance); - - if (count != 1) - return false; - - return scalars_a[0] == 0.0 || scalars_a[1] == 1.0 - || scalars_b[0] == 0.0 || scalars_b[1] == 1.0; - } - - // treat env_b as area - - Envelope2D env_b_deflated = new Envelope2D(), env_inter = new Envelope2D(); - env_b_deflated.setCoords(_env_b); - env_b_deflated.inflate(-tolerance, -tolerance); - env_inter.setCoords(env_b_deflated); - env_inter.intersect(_env_a); - - if (!env_inter.isEmpty() - && (env_inter.getHeight() > tolerance || env_inter - .getWidth() > tolerance)) - return false; - - assert (!envelopeDisjointEnvelope_(_env_a, _env_b, tolerance, - progress_tracker)); - return true; // we already know they intersect within a tolerance - } - - Envelope2D env_inter = new Envelope2D(); - env_inter.setCoords(_env_a); - env_inter.intersect(_env_b); - - if (!env_inter.isEmpty() && env_inter.getHeight() > tolerance - && env_inter.getWidth() > tolerance) - return false; - - return true; // we already know they intersect within a tolerance - } - - // Returns true if env_a overlaps env_b. - private static boolean envelopeOverlapsEnvelope_(Envelope2D env_a, - Envelope2D env_b, double tolerance, ProgressTracker progress_tracker) { - if (envelopeInfContainsEnvelope_(env_a, env_b, tolerance) - || envelopeInfContainsEnvelope_(env_b, env_a, tolerance)) - return false; - - if (env_a.getHeight() <= tolerance && env_a.getWidth() <= tolerance) - return false; // points cannot overlap - - if (env_b.getHeight() <= tolerance && env_b.getWidth() <= tolerance) - return false; // points cannot overlap - - if (env_a.getHeight() <= tolerance || env_a.getWidth() <= tolerance) {// treat - // env_a - // as - // a - // line - - if (env_b.getHeight() > tolerance && env_b.getWidth() > tolerance) - return false; // lines cannot overlap areas - - // treat both as lines - - Line line_a = new Line(), line_b = new Line(); - double[] scalars_a = new double[2]; - double[] scalars_b = new double[2]; - Point2D pt = new Point2D(); - env_a.queryLowerLeft(pt); - line_a.setStartXY(pt); - env_a.queryUpperRight(pt); - line_a.setEndXY(pt); - env_b.queryLowerLeft(pt); - line_b.setStartXY(pt); - env_b.queryUpperRight(pt); - line_b.setEndXY(pt); - - line_a.intersect(line_b, null, scalars_a, scalars_b, tolerance); - int count = line_a.intersect(line_b, null, null, null, tolerance); - - if (count != 2) - return false; - - return (scalars_a[0] > 0.0 || scalars_a[1] < 1.0) - && (scalars_b[0] > 0.0 || scalars_b[1] < 1.0); - } - - if (env_b.getHeight() <= tolerance || env_b.getWidth() <= tolerance) - return false; // lines cannot overlap areas - - // treat both as areas - - Envelope2D env_inter = new Envelope2D(); - env_inter.setCoords(env_a); - env_inter.intersect(env_b); - - if (env_inter.isEmpty()) - return false; - - if (env_inter.getHeight() <= tolerance - || env_inter.getWidth() <= tolerance) - return false; // not an area - - assert (!envelopeInfContainsEnvelope_(env_inter, env_a, tolerance) && !envelopeInfContainsEnvelope_( - env_inter, env_b, tolerance)); - - return true; - } - - // Returns true if env_a contains env_b. - private static boolean envelopeContainsEnvelope_(Envelope2D env_a, - Envelope2D env_b, double tolerance, ProgressTracker progress_tracker) { - if (!envelopeInfContainsEnvelope_(env_a, env_b, tolerance)) - return false; - - if (env_a.getHeight() <= tolerance && env_a.getWidth() <= tolerance) { - Point2D pt_a = env_a.getCenter(); - return pointWithinEnvelope_(pt_a, env_b, tolerance, - progress_tracker); - } - - if (env_b.getHeight() <= tolerance && env_b.getWidth() <= tolerance) { - Point2D pt_b = env_b.getCenter(); - return pointWithinEnvelope_(pt_b, env_a, tolerance, - progress_tracker); - } - - if (env_a.getHeight() <= tolerance || env_a.getWidth() <= tolerance) - return envelopeInfContainsEnvelope_(env_a, env_b, tolerance); // treat - // env_b - // as - // line - - // treat env_a as area - - if (env_b.getHeight() <= tolerance || env_b.getWidth() <= tolerance) {// treat - // env_b - // as - // line - - Envelope2D env_a_deflated = new Envelope2D(); - env_a_deflated.setCoords(env_a); - env_a_deflated.inflate(-tolerance, -tolerance); - - if (env_a_deflated.containsExclusive(env_b)) - return true; - - Envelope2D env_inter = new Envelope2D(); - env_inter.setCoords(env_a_deflated); - env_inter.intersect(env_b); - - if (env_inter.isEmpty() - || (env_inter.getHeight() <= tolerance && env_inter - .getWidth() <= tolerance)) - return false; - - return true; - } - - return envelopeInfContainsEnvelope_(env_a, env_b, tolerance); - } - - // Returns true if env_a crosses env_b. - private static boolean envelopeCrossesEnvelope_(Envelope2D env_a, - Envelope2D env_b, double tolerance, ProgressTracker progress_tracker) { - if (envelopeInfContainsEnvelope_(env_a, env_b, tolerance) - || envelopeInfContainsEnvelope_(env_b, env_a, tolerance)) - return false; - - if (env_a.getHeight() <= tolerance && env_a.getWidth() <= tolerance) - return false; // points cannot cross - - if (env_b.getHeight() <= tolerance && env_b.getWidth() <= tolerance) - return false; // points cannot cross - - if (env_b.getHeight() > tolerance && env_b.getWidth() > tolerance) { - if (env_a.getHeight() > tolerance && env_a.getWidth() > tolerance) - return false; // areas cannot cross - } - - Envelope2D _env_a; - Envelope2D _env_b; - - if (env_a.getHeight() > tolerance && env_a.getWidth() > tolerance) { - // swap b and a - _env_a = env_b; - _env_b = env_a; - } else { - _env_a = env_a; - _env_b = env_b; - } - - if (_env_b.getHeight() > tolerance && _env_b.getWidth() > tolerance) {// treat - // env_b - // as - // an - // area - // (env_a - // as - // a - // line); - - Envelope2D env_inter = new Envelope2D(), env_b_deflated = new Envelope2D(); - env_b_deflated.setCoords(_env_b); - env_b_deflated.inflate(-tolerance, -tolerance); - env_inter.setCoords(env_b_deflated); - env_inter.intersect(_env_a); - - if (env_inter.isEmpty()) - return false; - - if (env_inter.getHeight() <= tolerance - && env_inter.getWidth() <= tolerance) - return false; // not a line - - assert (!envelopeInfContainsEnvelope_(env_inter, _env_a, tolerance)); - return true; - } - - // treat both as lines - - Line line_a = new Line(), line_b = new Line(); - double[] scalars_a = new double[2]; - double[] scalars_b = new double[2]; - Point2D pt = new Point2D(); - _env_a.queryLowerLeft(pt); - line_a.setStartXY(pt); - _env_a.queryUpperRight(pt); - line_a.setEndXY(pt); - _env_b.queryLowerLeft(pt); - line_b.setStartXY(pt); - _env_b.queryUpperRight(pt); - line_b.setEndXY(pt); - - line_a.intersect(line_b, null, scalars_a, scalars_b, tolerance); - int count = line_a.intersect(line_b, null, null, null, tolerance); - - if (count != 1) - return false; - - return scalars_a[0] > 0.0 && scalars_a[1] < 1.0 && scalars_b[0] > 0.0 - && scalars_b[1] < 1.0; - } - - // Returns true if polygon_a is disjoint from multipath_b. - private static boolean polygonDisjointMultiPath_(Polygon polygon_a, - MultiPath multipath_b, double tolerance, - ProgressTracker progress_tracker) { - Point2D pt_a, pt_b; - Envelope2D env_a_inf = new Envelope2D(), env_b_inf = new Envelope2D(); - - MultiPathImpl multi_path_impl_a = (MultiPathImpl) polygon_a._getImpl(); - MultiPathImpl multi_path_impl_b = (MultiPathImpl) multipath_b._getImpl(); - - PairwiseIntersectorImpl intersector = new PairwiseIntersectorImpl(multi_path_impl_a, multi_path_impl_b, tolerance, true); - - if (!intersector.next()) - return true; // no rings intersect - - boolean b_intersects = linearPathIntersectsLinearPath_(polygon_a, multipath_b, tolerance); - - if (b_intersects) - return false; - - Polygon pa = null; - Polygon p_polygon_a = polygon_a; - - Polygon pb = null; - Polygon p_polygon_b = null; - - if (multipath_b.getType().value() == Geometry.GeometryType.Polygon) - p_polygon_b = (Polygon) multipath_b; - - boolean b_checked_polygon_a_quad_tree = false; - boolean b_checked_polygon_b_quad_tree = false; - - do { - int path_a = intersector.getRedElement(); - int path_b = intersector.getBlueElement(); - - pt_b = multipath_b.getXY(multipath_b.getPathStart(path_b)); - env_a_inf.setCoords(intersector.getRedEnvelope()); - env_a_inf.inflate(tolerance, tolerance); - - if (env_a_inf.contains(pt_b)) { - PolygonUtils.PiPResult result = PolygonUtils.isPointInPolygon2D(p_polygon_a, pt_b, 0.0); - - if (result != PolygonUtils.PiPResult.PiPOutside) - return false; - } - - if (multipath_b.getType().value() == Geometry.GeometryType.Polygon) { - pt_a = polygon_a.getXY(polygon_a.getPathStart(path_a)); - env_b_inf.setCoords(intersector.getBlueEnvelope()); - env_b_inf.inflate(tolerance, tolerance); - - if (env_b_inf.contains(pt_a)) { - PolygonUtils.PiPResult result = PolygonUtils.isPointInPolygon2D(p_polygon_b, pt_a, 0.0); - - if (result != PolygonUtils.PiPResult.PiPOutside) - return false; - } - } - - if (!b_checked_polygon_a_quad_tree) { - if (PointInPolygonHelper.quadTreeWillHelp(polygon_a, multipath_b.getPathCount() - 1) && (multi_path_impl_a._getAccelerators() == null || multi_path_impl_a._getAccelerators().getQuadTree() == null)) { - pa = new Polygon(); - polygon_a.copyTo(pa); - ((MultiPathImpl) pa._getImpl())._buildQuadTreeAccelerator(Geometry.GeometryAccelerationDegree.enumMedium); - p_polygon_a = pa; - } else { - p_polygon_a = polygon_a; - } - - b_checked_polygon_a_quad_tree = true; - } - - if (multipath_b.getType().value() == Geometry.GeometryType.Polygon) { - if (!b_checked_polygon_b_quad_tree) { - Polygon polygon_b = (Polygon) multipath_b; - if (PointInPolygonHelper.quadTreeWillHelp(polygon_b, polygon_a.getPathCount() - 1) && (multi_path_impl_b._getAccelerators() == null || multi_path_impl_b._getAccelerators().getQuadTree() == null)) { - pb = new Polygon(); - polygon_b.copyTo(pb); - ((MultiPathImpl) pb._getImpl())._buildQuadTreeAccelerator(Geometry.GeometryAccelerationDegree.enumMedium); - p_polygon_b = pb; - } else { - p_polygon_b = (Polygon) multipath_b; - } - - b_checked_polygon_b_quad_tree = true; - } - } - - } while (intersector.next()); - - return true; - } - - // Returns true if env_a inflated contains env_b. - private static boolean envelopeInfContainsEnvelope_(Envelope2D env_a, - Envelope2D env_b, double tolerance) { - Envelope2D env_a_inflated = new Envelope2D(); - env_a_inflated.setCoords(env_a); - env_a_inflated.inflate(tolerance, tolerance); - return env_a_inflated.contains(env_b); - } - - // Returns true if a coordinate of envelope A is outside of envelope B. - private static boolean interiorEnvExteriorEnv_(Envelope2D env_a, - Envelope2D env_b, double tolerance) { - Envelope2D envBInflated = new Envelope2D(); - envBInflated.setCoords(env_b); - envBInflated.inflate(tolerance, tolerance); - Point2D pt = new Point2D(); - - env_a.queryLowerLeft(pt); - if (!envBInflated.contains(pt)) - return true; - - env_a.queryLowerRight(pt); - if (!envBInflated.contains(pt)) - return true; - - env_a.queryUpperLeft(pt); - if (!envBInflated.contains(pt)) - return true; - - env_a.queryUpperRight(pt); - if (!envBInflated.contains(pt)) - return true; - - assert (envBInflated.contains(env_a)); - return false; - } - - // Returns true if the points in each path of multipathA are the same as - // those in multipathB, within a tolerance, and in the same order. - private static boolean multiPathExactlyEqualsMultiPath_( - MultiPath multipathA, MultiPath multipathB, double tolerance, - ProgressTracker progress_tracker) { - if (multipathA.getPathCount() != multipathB.getPathCount() - || multipathA.getPointCount() != multipathB.getPointCount()) - return false; - - Point2D ptA = new Point2D(), ptB = new Point2D(); - boolean bAllPointsEqual = true; - double tolerance_sq = tolerance * tolerance; - - for (int ipath = 0; ipath < multipathA.getPathCount(); ipath++) { - if (multipathA.getPathEnd(ipath) != multipathB.getPathEnd(ipath)) { - bAllPointsEqual = false; - break; - } - - for (int i = multipathA.getPathStart(ipath); i < multipathB - .getPathEnd(ipath); i++) { - multipathA.getXY(i, ptA); - multipathB.getXY(i, ptB); - - if (Point2D.sqrDistance(ptA, ptB) > tolerance_sq) { - bAllPointsEqual = false; - break; - } - } - - if (!bAllPointsEqual) - break; - } - - if (!bAllPointsEqual) - return false; - - return true; - } - - // Returns true if the points of multipoint_a are the same as those in - // multipoint_b, within a tolerance, and in the same order. - private static boolean multiPointExactlyEqualsMultiPoint_( - MultiPoint multipoint_a, MultiPoint multipoint_b, double tolerance, - ProgressTracker progress_tracker) { - if (multipoint_a.getPointCount() != multipoint_b.getPointCount()) - return false; - - Point2D ptA = new Point2D(), ptB = new Point2D(); - boolean bAllPointsEqual = true; - double tolerance_sq = tolerance * tolerance; - - for (int i = 0; i < multipoint_a.getPointCount(); i++) { - multipoint_a.getXY(i, ptA); - multipoint_b.getXY(i, ptB); - - if (Point2D.sqrDistance(ptA, ptB) > tolerance_sq) { - bAllPointsEqual = false; - break; - } - } - - if (!bAllPointsEqual) - return false; - - return true; - } - - // By default this will perform the within operation if bEquals is false. - // Otherwise it will do equals. - private static boolean multiPointCoverageMultiPoint_( - MultiPoint _multipointA, MultiPoint _multipointB, double tolerance, - boolean bPerformWithin, boolean bPerformEquals, - boolean bPerformOverlaps, ProgressTracker progress_tracker) { - boolean bPerformContains = false; - MultiPoint multipoint_a; - MultiPoint multipoint_b; - - if (_multipointA.getPointCount() > _multipointB.getPointCount()) { - if (bPerformWithin) { - bPerformWithin = false; - bPerformContains = true; - } - - multipoint_a = _multipointB; - multipoint_b = _multipointA; - } else { - multipoint_a = _multipointA; - multipoint_b = _multipointB; - } - - AttributeStreamOfInt8 contained = null; - - if (bPerformEquals || bPerformOverlaps || bPerformContains) { - contained = new AttributeStreamOfInt8(multipoint_b.getPointCount()); - - for (int i = 0; i < multipoint_b.getPointCount(); i++) - contained.write(i, (byte) 0); - } - - Envelope2D env_a = new Envelope2D(); - Envelope2D env_b = new Envelope2D(); - Envelope2D envInter = new Envelope2D(); - multipoint_a.queryEnvelope2D(env_a); - multipoint_b.queryEnvelope2D(env_b); - env_a.inflate(tolerance, tolerance); - env_b.inflate(tolerance, tolerance); - envInter.setCoords(env_a); - envInter.intersect(env_b); - - Point2D ptA = new Point2D(); - Point2D ptB = new Point2D(); - - boolean bWithin = true; // starts off true by default - - QuadTreeImpl quadTreeB = InternalUtils.buildQuadTree( - (MultiPointImpl) (multipoint_b._getImpl()), envInter); - QuadTreeImpl.QuadTreeIteratorImpl qtIterB = quadTreeB.getIterator(); - double tolerance_sq = tolerance * tolerance; - - for (int vertex_a = 0; vertex_a < multipoint_a.getPointCount(); vertex_a++) { - multipoint_a.getXY(vertex_a, ptA); - - if (!envInter.contains(ptA)) { - if (bPerformEquals || bPerformWithin) - return false; - else { - bWithin = false; - continue; - } - } - - boolean bPtACovered = false; - env_a.setCoords(ptA.x, ptA.y, ptA.x, ptA.y); - qtIterB.resetIterator(env_a, tolerance); - for (int elementHandleB = qtIterB.next(); elementHandleB != -1; elementHandleB = qtIterB - .next()) { - int vertex_b = quadTreeB.getElement(elementHandleB); - multipoint_b.getXY(vertex_b, ptB); - - if (Point2D.sqrDistance(ptA, ptB) <= tolerance_sq) { - if (bPerformEquals || bPerformOverlaps || bPerformContains) - contained.write(vertex_b, (byte) 1); - - bPtACovered = true; - - if (bPerformWithin) - break; - } - } - - if (!bPtACovered) { - bWithin = false; - - if (bPerformEquals || bPerformWithin) - return false; - } - } - - if (bPerformOverlaps && bWithin) - return false; - - if (bPerformWithin) - return true; - - for (int i = 0; i < multipoint_b.getPointCount(); i++) { - if (contained.read(i) == 1) { - if (bPerformOverlaps) - return true; - } else { - if (bPerformEquals || bPerformContains) - return false; - } - } - - if (bPerformOverlaps) - return false; - - return true; - } - - // Returns true if multipoint_a intersects multipoint_b. - private static boolean multiPointIntersectsMultiPoint_( - MultiPoint _multipointA, MultiPoint _multipointB, double tolerance, - ProgressTracker progress_tracker) { - MultiPoint multipoint_a; - MultiPoint multipoint_b; - - if (_multipointA.getPointCount() > _multipointB.getPointCount()) { - multipoint_a = _multipointB; - multipoint_b = _multipointA; - } else { - multipoint_a = _multipointA; - multipoint_b = _multipointB; - } - - Envelope2D env_a = new Envelope2D(); - Envelope2D env_b = new Envelope2D(); - Envelope2D envInter = new Envelope2D(); - multipoint_a.queryEnvelope2D(env_a); - multipoint_b.queryEnvelope2D(env_b); - env_a.inflate(tolerance, tolerance); - env_b.inflate(tolerance, tolerance); - envInter.setCoords(env_a); - envInter.intersect(env_b); - - Point2D ptA = new Point2D(); - Point2D ptB = new Point2D(); - double tolerance_sq = tolerance * tolerance; - - QuadTreeImpl quadTreeB = InternalUtils.buildQuadTree( - (MultiPointImpl) (multipoint_b._getImpl()), envInter); - QuadTreeImpl.QuadTreeIteratorImpl qtIterB = quadTreeB.getIterator(); - - for (int vertex_a = 0; vertex_a < multipoint_a.getPointCount(); vertex_a++) { - multipoint_a.getXY(vertex_a, ptA); - - if (!envInter.contains(ptA)) - continue; - - env_a.setCoords(ptA.x, ptA.y, ptA.x, ptA.y); - qtIterB.resetIterator(env_a, tolerance); - - for (int elementHandleB = qtIterB.next(); elementHandleB != -1; elementHandleB = qtIterB - .next()) { - int vertex_b = quadTreeB.getElement(elementHandleB); - multipoint_b.getXY(vertex_b, ptB); - - if (Point2D.sqrDistance(ptA, ptB) <= tolerance_sq) - return true; - } - } - - return false; - } - - // Returns true if multipathA equals multipathB. - private static boolean linearPathEqualsLinearPath_(MultiPath multipathA, - MultiPath multipathB, double tolerance, boolean bEnforceOrientation) { - return linearPathWithinLinearPath_(multipathA, multipathB, tolerance, bEnforceOrientation) - && linearPathWithinLinearPath_(multipathB, multipathA, - tolerance, bEnforceOrientation); - } - - // Returns true if the segments of multipathA are within the segments of - // multipathB. - private static boolean linearPathWithinLinearPath_(MultiPath multipathA, - MultiPath multipathB, double tolerance, boolean bEnforceOrientation) { - boolean bWithin = true; - double[] scalarsA = new double[2]; - double[] scalarsB = new double[2]; - - int ievent = 0; - AttributeStreamOfInt32 eventIndices = new AttributeStreamOfInt32(0); - RelationalOperations relOps = new RelationalOperations(); - OverlapComparer overlapComparer = new OverlapComparer(relOps); - OverlapEvent overlapEvent; - - Envelope2D env_a = new Envelope2D(); - Envelope2D env_b = new Envelope2D(); - Envelope2D envInter = new Envelope2D(); - multipathA.queryEnvelope2D(env_a); - multipathB.queryEnvelope2D(env_b); - env_a.inflate(tolerance, tolerance); - env_b.inflate(tolerance, tolerance); - envInter.setCoords(env_a); - envInter.intersect(env_b); - - SegmentIteratorImpl segIterA = ((MultiPathImpl) multipathA._getImpl()) - .querySegmentIterator(); - SegmentIteratorImpl segIterB = ((MultiPathImpl) multipathB._getImpl()) - .querySegmentIterator(); - - QuadTreeImpl qtB = null; - QuadTreeImpl quadTreeB = null; - QuadTreeImpl quadTreePathsB = null; - - GeometryAccelerators accel = ((MultiPathImpl) multipathB._getImpl()) - ._getAccelerators(); - - if (accel != null) { - quadTreeB = accel.getQuadTree(); - quadTreePathsB = accel.getQuadTreeForPaths(); - if (quadTreeB == null) { - qtB = InternalUtils.buildQuadTree( - (MultiPathImpl) multipathB._getImpl(), envInter); - quadTreeB = qtB; - } - } else { - qtB = InternalUtils.buildQuadTree( - (MultiPathImpl) multipathB._getImpl(), envInter); - quadTreeB = qtB; - } - - QuadTreeImpl.QuadTreeIteratorImpl qtIterB = quadTreeB.getIterator(); - - QuadTreeImpl.QuadTreeIteratorImpl qtIterPathsB = null; - if (quadTreePathsB != null) - qtIterPathsB = quadTreePathsB.getIterator(); - - while (segIterA.nextPath()) { - while (segIterA.hasNextSegment()) { - boolean bStringOfSegmentAsCovered = false; - - Segment segmentA = segIterA.nextSegment(); - segmentA.queryEnvelope2D(env_a); - - if (!env_a.isIntersecting(envInter)) { - return false; // bWithin = false - } - - if (qtIterPathsB != null) { - qtIterPathsB.resetIterator(env_a, tolerance); - - if (qtIterPathsB.next() == -1) { - bWithin = false; - return false; - } - } - - double lengthA = segmentA.calculateLength2D(); - - qtIterB.resetIterator(segmentA, tolerance); - - for (int elementHandleB = qtIterB.next(); elementHandleB != -1; elementHandleB = qtIterB - .next()) { - int vertex_b = quadTreeB.getElement(elementHandleB); - segIterB.resetToVertex(vertex_b); - Segment segmentB = segIterB.nextSegment(); - - int result = segmentA.intersect(segmentB, null, scalarsA, - scalarsB, tolerance); - - if (result == 2 && (!bEnforceOrientation || scalarsB[0] <= scalarsB[1])) { - double scalar_a_0 = scalarsA[0]; - double scalar_a_1 = scalarsA[1]; - double scalar_b_0 = scalarsB[0]; - double scalar_b_1 = scalarsB[1]; - - // Performance enhancement for nice cases where - // localization occurs. Increment segIterA as far as we - // can while the current segmentA is covered. - if (scalar_a_0 * lengthA <= tolerance - && (1.0 - scalar_a_1) * lengthA <= tolerance) { - bStringOfSegmentAsCovered = true; - - ievent = 0; - eventIndices.resize(0); - relOps.m_overlap_events.clear(); - - int ivertex_a = segIterA.getStartPointIndex(); - boolean bSegmentACovered = true; - - while (bSegmentACovered) {// keep going while the - // current segmentA is - // covered. - if (segIterA.hasNextSegment()) { - segmentA = segIterA.nextSegment(); - lengthA = segmentA.calculateLength2D(); - - result = segmentA.intersect(segmentB, null, - scalarsA, scalarsB, tolerance); - - if (result == 2 && (!bEnforceOrientation || scalarsB[0] <= scalarsB[1])) { - scalar_a_0 = scalarsA[0]; - scalar_a_1 = scalarsA[1]; - - if (scalar_a_0 * lengthA <= tolerance - && (1.0 - scalar_a_1) * lengthA <= tolerance) { - ivertex_a = segIterA - .getStartPointIndex(); - continue; - } - } - - if (segIterB.hasNextSegment()) { - segmentB = segIterB.nextSegment(); - result = segmentA.intersect(segmentB, - null, scalarsA, scalarsB, - tolerance); - - if (result == 2 && (!bEnforceOrientation || scalarsB[0] <= scalarsB[1])) { - scalar_a_0 = scalarsA[0]; - scalar_a_1 = scalarsA[1]; - - if (scalar_a_0 * lengthA <= tolerance - && (1.0 - scalar_a_1) - * lengthA <= tolerance) { - ivertex_a = segIterA - .getStartPointIndex(); - continue; - } - } - } - } - - bSegmentACovered = false; - } - - if (ivertex_a != segIterA.getStartPointIndex()) { - segIterA.resetToVertex(ivertex_a); - segIterA.nextSegment(); - } - - break; - } else { - int ivertex_a = segIterA.getStartPointIndex(); - int ipath_a = segIterA.getPathIndex(); - int ivertex_b = segIterB.getStartPointIndex(); - int ipath_b = segIterB.getPathIndex(); - - overlapEvent = OverlapEvent.construct(ivertex_a, - ipath_a, scalar_a_0, scalar_a_1, ivertex_b, - ipath_b, scalar_b_0, scalar_b_1); - relOps.m_overlap_events.add(overlapEvent); - eventIndices.add(eventIndices.size()); - } - } - } - - if (bStringOfSegmentAsCovered) { - continue; // no need to check that segmentA is covered - } - if (ievent == relOps.m_overlap_events.size()) { - return false; // bWithin = false - } - - if (eventIndices.size() - ievent > 1) { - eventIndices.Sort(ievent, eventIndices.size(), - overlapComparer); - } - - double lastScalar = 0.0; - - for (int i = ievent; i < relOps.m_overlap_events.size(); i++) { - overlapEvent = relOps.m_overlap_events.get(eventIndices - .get(i)); - - if (overlapEvent.m_scalar_a_0 < lastScalar - && overlapEvent.m_scalar_a_1 < lastScalar) { - continue; - } - - if (lengthA * (overlapEvent.m_scalar_a_0 - lastScalar) > tolerance) { - return false; // bWithin = false - } else { - lastScalar = overlapEvent.m_scalar_a_1; - - if (lengthA * (1.0 - lastScalar) <= tolerance - || lastScalar == 1.0) { - break; - } - } - } - - if (lengthA * (1.0 - lastScalar) > tolerance) { - return false; // bWithin = false - } - - ievent = 0; - eventIndices.resize(0); - relOps.m_overlap_events.clear(); - } - } - - return bWithin; - } - - // Returns true if the segments of multipathA overlap the segments of - // multipathB. - private static boolean linearPathOverlapsLinearPath_(MultiPath multipathA, - MultiPath multipathB, double tolerance) { - int dim = linearPathIntersectsLinearPathMaxDim_(multipathA, multipathB, - tolerance, null); - - if (dim < 1) - return false; - - Envelope2D env_a = new Envelope2D(), env_b = new Envelope2D(); - multipathA.queryEnvelope2D(env_a); - multipathB.queryEnvelope2D(env_b); - - boolean bIntAExtB = interiorEnvExteriorEnv_(env_a, env_b, tolerance); - boolean bIntBExtA = interiorEnvExteriorEnv_(env_b, env_a, tolerance); - - if (bIntAExtB && bIntBExtA) - return true; - - if (bIntAExtB && !bIntBExtA) - return !linearPathWithinLinearPath_(multipathB, multipathA, - tolerance, false); - - if (bIntBExtA && !bIntAExtB) - return !linearPathWithinLinearPath_(multipathA, multipathB, - tolerance, false); - - return !linearPathWithinLinearPath_(multipathA, multipathB, tolerance, false) - && !linearPathWithinLinearPath_(multipathB, multipathA, - tolerance, false); - } - - // Returns true the dimension of intersection of _multipathA and - // _multipathB. - static int linearPathIntersectsLinearPathMaxDim_(MultiPath _multipathA, - MultiPath _multipathB, double tolerance, - AttributeStreamOfDbl intersections) { - MultiPath multipathA; - MultiPath multipathB; - - if (_multipathA.getSegmentCount() > _multipathB.getSegmentCount()) { - multipathA = _multipathB; - multipathB = _multipathA; - } else { - multipathA = _multipathA; - multipathB = _multipathB; - } - - SegmentIteratorImpl segIterA = ((MultiPathImpl) multipathA._getImpl()) - .querySegmentIterator(); - SegmentIteratorImpl segIterB = ((MultiPathImpl) multipathB._getImpl()) - .querySegmentIterator(); - double[] scalarsA = new double[2]; - double[] scalarsB = new double[2]; - - int dim = -1; - - int ievent = 0; - double overlapLength; - AttributeStreamOfInt32 eventIndices = new AttributeStreamOfInt32(0); - RelationalOperations relOps = new RelationalOperations(); - OverlapComparer overlapComparer = new OverlapComparer(relOps); - OverlapEvent overlapEvent; - - Envelope2D env_a = new Envelope2D(); - Envelope2D env_b = new Envelope2D(); - Envelope2D envInter = new Envelope2D(); - multipathA.queryEnvelope2D(env_a); - multipathB.queryEnvelope2D(env_b); - env_a.inflate(tolerance, tolerance); - env_b.inflate(tolerance, tolerance); - envInter.setCoords(env_a); - envInter.intersect(env_b); - - Point2D int_point = null; - - if (intersections != null) { - int_point = new Point2D(); - } - - QuadTreeImpl qtB = null; - QuadTreeImpl quadTreeB = null; - QuadTreeImpl quadTreePathsB = null; - - GeometryAccelerators accel = ((MultiPathImpl) multipathB._getImpl()) - ._getAccelerators(); - - if (accel != null) { - quadTreeB = accel.getQuadTree(); - quadTreePathsB = accel.getQuadTreeForPaths(); - if (quadTreeB == null) { - qtB = InternalUtils.buildQuadTree( - (MultiPathImpl) multipathB._getImpl(), envInter); - quadTreeB = qtB; - } - } else { - qtB = InternalUtils.buildQuadTree( - (MultiPathImpl) multipathB._getImpl(), envInter); - quadTreeB = qtB; - } - - QuadTreeImpl.QuadTreeIteratorImpl qtIterB = quadTreeB.getIterator(); - - QuadTreeImpl.QuadTreeIteratorImpl qtIterPathsB = null; - if (quadTreePathsB != null) - qtIterPathsB = quadTreePathsB.getIterator(); - - while (segIterA.nextPath()) { - overlapLength = 0.0; - - while (segIterA.hasNextSegment()) { - Segment segmentA = segIterA.nextSegment(); - segmentA.queryEnvelope2D(env_a); - - if (!env_a.isIntersecting(envInter)) { - continue; - } - - if (qtIterPathsB != null) { - qtIterPathsB.resetIterator(env_a, tolerance); - - if (qtIterPathsB.next() == -1) - continue; - } - - double lengthA = segmentA.calculateLength2D(); - - qtIterB.resetIterator(segmentA, tolerance); - - for (int elementHandleB = qtIterB.next(); elementHandleB != -1; elementHandleB = qtIterB - .next()) { - int vertex_b = quadTreeB.getElement(elementHandleB); - segIterB.resetToVertex(vertex_b); - - Segment segmentB = segIterB.nextSegment(); - double lengthB = segmentB.calculateLength2D(); - - int result = segmentA.intersect(segmentB, null, scalarsA, - scalarsB, tolerance); - - if (result > 0) { - double scalar_a_0 = scalarsA[0]; - double scalar_b_0 = scalarsB[0]; - double scalar_a_1 = (result == 2 ? scalarsA[1] - : NumberUtils.TheNaN); - double scalar_b_1 = (result == 2 ? scalarsB[1] - : NumberUtils.TheNaN); - - if (result == 2) { - if (lengthA * (scalar_a_1 - scalar_a_0) > tolerance) { - dim = 1; - return dim; - } - - // Quick neighbor check - double length = lengthA * (scalar_a_1 - scalar_a_0); - - if (segIterB.hasNextSegment()) { - segmentB = segIterB.nextSegment(); - result = segmentA.intersect(segmentB, null, - scalarsA, null, tolerance); - - if (result == 2) { - double nextScalarA0 = scalarsA[0]; - double nextScalarA1 = scalarsA[1]; - - double lengthNext = lengthA - * (nextScalarA1 - nextScalarA0); - - if (length + lengthNext > tolerance) { - dim = 1; - return dim; - } - } - - segIterB.resetToVertex(vertex_b); - segIterB.nextSegment(); - } - - if (!segIterB.isFirstSegmentInPath()) { - segIterB.previousSegment(); - segmentB = segIterB.previousSegment(); - result = segmentA.intersect(segmentB, null, - scalarsA, null, tolerance); - - if (result == 2) { - double nextScalarA0 = scalarsA[0]; - double nextScalarA1 = scalarsA[1]; - - double lengthPrevious = lengthA - * (nextScalarA1 - nextScalarA0); - - if (length + lengthPrevious > tolerance) { - dim = 1; - return dim; - } - } - - segIterB.resetToVertex(vertex_b); - segIterB.nextSegment(); - } - - if (segIterA.hasNextSegment()) { - int vertex_a = segIterA.getStartPointIndex(); - segmentA = segIterA.nextSegment(); - result = segmentA.intersect(segmentB, null, - scalarsA, null, tolerance); - - if (result == 2) { - double nextScalarA0 = scalarsA[0]; - double nextScalarA1 = scalarsA[1]; - - double lengthNext = lengthA - * (nextScalarA1 - nextScalarA0); - - if (length + lengthNext > tolerance) { - dim = 1; - return dim; - } - } - - segIterA.resetToVertex(vertex_a); - segIterA.nextSegment(); - } - - if (!segIterA.isFirstSegmentInPath()) { - int vertex_a = segIterA.getStartPointIndex(); - segIterA.previousSegment(); - segmentA = segIterA.previousSegment(); - result = segmentA.intersect(segmentB, null, - scalarsA, null, tolerance); - - if (result == 2) { - double nextScalarA0 = scalarsA[0]; - double nextScalarA1 = scalarsA[1]; - - double lengthPrevious = lengthB - * (nextScalarA1 - nextScalarA0); - - if (length + lengthPrevious > tolerance) { - dim = 1; - return dim; - } - } - - segIterA.resetToVertex(vertex_a); - segIterA.nextSegment(); - } - - int ivertex_a = segIterA.getStartPointIndex(); - int ipath_a = segIterA.getPathIndex(); - int ivertex_b = segIterB.getStartPointIndex(); - int ipath_b = segIterB.getPathIndex(); - - overlapEvent = OverlapEvent.construct(ivertex_a, - ipath_a, scalar_a_0, scalar_a_1, ivertex_b, - ipath_b, scalar_b_0, scalar_b_1); - relOps.m_overlap_events.add(overlapEvent); - eventIndices.add(eventIndices.size()); - } - - dim = 0; - - if (intersections != null) { - segmentA.getCoord2D(scalar_a_0, int_point); - intersections.add(int_point.x); - intersections.add(int_point.y); - } - } - } - - if (ievent < relOps.m_overlap_events.size()) { - eventIndices.Sort(ievent, eventIndices.size(), - overlapComparer); - - double lastScalar = 0.0; - int lastPath = relOps.m_overlap_events.get(eventIndices - .get(ievent)).m_ipath_a; - - for (int i = ievent; i < relOps.m_overlap_events.size(); i++) { - overlapEvent = relOps.m_overlap_events.get(eventIndices - .get(i)); - - if (overlapEvent.m_scalar_a_0 < lastScalar - && overlapEvent.m_scalar_a_1 < lastScalar) { - continue; - } - - if (lengthA * (overlapEvent.m_scalar_a_0 - lastScalar) > tolerance) { - overlapLength = lengthA - * (overlapEvent.m_scalar_a_1 - overlapEvent.m_scalar_a_0); // reset - lastScalar = overlapEvent.m_scalar_a_1; - lastPath = overlapEvent.m_ipath_a; - } else { - if (overlapEvent.m_ipath_a != lastPath) { - overlapLength = lengthA - * (overlapEvent.m_scalar_a_1 - overlapEvent.m_scalar_a_0); // reset - lastPath = overlapEvent.m_ipath_a; - } else { - overlapLength += lengthA - * (overlapEvent.m_scalar_a_1 - overlapEvent.m_scalar_a_0); // accumulate - } - if (overlapLength > tolerance) { - dim = 1; - return dim; - } - - lastScalar = overlapEvent.m_scalar_a_1; - - if (lastScalar == 1.0) { - break; - } - } - } - - if (lengthA * (1.0 - lastScalar) > tolerance) { - overlapLength = 0.0; // reset - } - ievent = 0; - eventIndices.resize(0); - relOps.m_overlap_events.clear(); - } - } - } - - return dim; - } - - // Returns true if the line segments of _multipathA intersect the line - // segments of _multipathB. - private static boolean linearPathIntersectsLinearPath_( - MultiPath multipathA, MultiPath multipathB, double tolerance) { - MultiPathImpl multi_path_impl_a = (MultiPathImpl) multipathA._getImpl(); - MultiPathImpl multi_path_impl_b = (MultiPathImpl) multipathB._getImpl(); - - SegmentIteratorImpl segIterA = multi_path_impl_a.querySegmentIterator(); - SegmentIteratorImpl segIterB = multi_path_impl_b.querySegmentIterator(); - - PairwiseIntersectorImpl intersector = new PairwiseIntersectorImpl(multi_path_impl_a, multi_path_impl_b, tolerance, false); - - while (intersector.next()) { - int vertex_a = intersector.getRedElement(); - int vertex_b = intersector.getBlueElement(); - - segIterA.resetToVertex(vertex_a); - segIterB.resetToVertex(vertex_b); - Segment segmentA = segIterA.nextSegment(); - Segment segmentB = segIterB.nextSegment(); - - int result = segmentB.intersect(segmentA, null, null, null, - tolerance); - - if (result > 0) { - return true; - } - } - - return false; - } - - // Returns true if the relation intersects, crosses, or contains holds - // between multipathA and multipoint_b. multipathA is put in the - // Quad_tree_impl. - private static boolean linearPathIntersectsMultiPoint_( - MultiPath multipathA, MultiPoint multipoint_b, double tolerance, - boolean b_intersects_all) { - SegmentIteratorImpl segIterA = ((MultiPathImpl) multipathA._getImpl()) - .querySegmentIterator(); - - boolean bContained = true; - boolean bInteriorHitFound = false; - - Envelope2D env_a = new Envelope2D(); - Envelope2D env_b = new Envelope2D(); - Envelope2D envInter = new Envelope2D(); - multipathA.queryEnvelope2D(env_a); - multipoint_b.queryEnvelope2D(env_b); - env_a.inflate(tolerance, tolerance); - - if (!env_a.contains(env_b)) { - bContained = false; - } - - env_b.inflate(tolerance, tolerance); - envInter.setCoords(env_a); - envInter.intersect(env_b); - - QuadTreeImpl qtA = null; - QuadTreeImpl quadTreeA = null; - QuadTreeImpl quadTreePathsA = null; - - GeometryAccelerators accel = ((MultiPathImpl) multipathA._getImpl()) - ._getAccelerators(); - - if (accel != null) { - quadTreeA = accel.getQuadTree(); - if (quadTreeA == null) { - qtA = InternalUtils.buildQuadTree( - (MultiPathImpl) multipathA._getImpl(), envInter); - quadTreeA = qtA; - } - } else { - qtA = InternalUtils.buildQuadTree( - (MultiPathImpl) multipathA._getImpl(), envInter); - quadTreeA = qtA; - } - - QuadTreeImpl.QuadTreeIteratorImpl qtIterA = quadTreeA.getIterator(); - - QuadTreeImpl.QuadTreeIteratorImpl qtIterPathsA = null; - if (quadTreePathsA != null) - qtIterPathsA = quadTreePathsA.getIterator(); - - Point2D ptB = new Point2D(), closest = new Point2D(); - boolean b_intersects = false; - double toleranceSq = tolerance * tolerance; - - for (int i = 0; i < multipoint_b.getPointCount(); i++) { - multipoint_b.getXY(i, ptB); - - if (!envInter.contains(ptB)) { - continue; - } - - env_b.setCoords(ptB.x, ptB.y, ptB.x, ptB.y); - - if (qtIterPathsA != null) { - qtIterPathsA.resetIterator(env_b, tolerance); - - if (qtIterPathsA.next() == -1) - continue; - } - - qtIterA.resetIterator(env_b, tolerance); - - boolean b_covered = false; - - for (int elementHandleA = qtIterA.next(); elementHandleA != -1; elementHandleA = qtIterA - .next()) { - int vertex_a = quadTreeA.getElement(elementHandleA); - segIterA.resetToVertex(vertex_a); - Segment segmentA = segIterA.nextSegment(); - - double t = segmentA.getClosestCoordinate(ptB, false); - segmentA.getCoord2D(t, closest); - - if (Point2D.sqrDistance(closest, ptB) <= toleranceSq) { - b_covered = true; - break; - } - } - - if (b_intersects_all) { - if (!b_covered) { - return false; - } - } else { - if (b_covered) { - return true; - } - } - } - - if (b_intersects_all) { - return true; - } - - return false; - } - - // Returns true if a segment of multipathA intersects point_b. - static boolean linearPathIntersectsPoint_(MultiPath multipathA, - Point2D ptB, double tolerance) { - Point2D closest = new Point2D(); - double toleranceSq = tolerance * tolerance; - SegmentIteratorImpl segIterA = ((MultiPathImpl) multipathA._getImpl()) - .querySegmentIterator(); - - GeometryAccelerators accel = ((MultiPathImpl) multipathA._getImpl()) - ._getAccelerators(); - - if (accel != null) { - QuadTreeImpl quadTreeA = accel.getQuadTree(); - if (quadTreeA != null) { - Envelope2D env_b = new Envelope2D(); - env_b.setCoords(ptB); - - QuadTreeImpl.QuadTreeIteratorImpl qt_iter = quadTreeA - .getIterator(env_b, tolerance); - - for (int e = qt_iter.next(); e != -1; e = qt_iter.next()) { - segIterA.resetToVertex(quadTreeA.getElement(e)); - - if (segIterA.hasNextSegment()) { - Segment segmentA = segIterA.nextSegment(); - - double t = segmentA.getClosestCoordinate(ptB, false); - segmentA.getCoord2D(t, closest); - - if (Point2D.sqrDistance(ptB, closest) <= toleranceSq) { - return true; - } - } - } - - return false; - } - } - Envelope2D env_a = new Envelope2D(); - - while (segIterA.nextPath()) { - while (segIterA.hasNextSegment()) { - Segment segmentA = segIterA.nextSegment(); - segmentA.queryEnvelope2D(env_a); - env_a.inflate(tolerance, tolerance); - - if (!env_a.contains(ptB)) { - continue; - } - - double t = segmentA.getClosestCoordinate(ptB, false); - segmentA.getCoord2D(t, closest); - - if (Point2D.sqrDistance(ptB, closest) <= toleranceSq) { - return true; - } - } - } - - return false; - } - - private static boolean linearPathContainsPoint_(MultiPath multipathA, - Point2D pt_b, double tolerance) { - return linearPathIntersectsPoint_(multipathA, pt_b, tolerance) - && !linearPathTouchesPointImpl_(multipathA, pt_b, tolerance); - } - - private static boolean linearPathTouchesPointImpl_(MultiPath multipathA, - Point2D ptB, double tolerance) { - MultiPoint boundary = (MultiPoint) (multipathA.getBoundary()); - return !multiPointDisjointPointImpl_(boundary, ptB, tolerance, null); - } - - // Returns true if the segments of multipathA intersects env_b - private static boolean linearPathIntersectsEnvelope_(MultiPath multipath_a, - Envelope2D env_b, double tolerance, ProgressTracker progress_tracker) { - if (!multipath_a.hasNonLinearSegments()) { - Envelope2D env_b_inflated = new Envelope2D(); - env_b_inflated.setCoords(env_b); - env_b_inflated.inflate(tolerance, tolerance); - MultiPathImpl mimpl_a = (MultiPathImpl) multipath_a._getImpl(); - AttributeStreamOfDbl xy = (AttributeStreamOfDbl) (mimpl_a - .getAttributeStreamRef(VertexDescription.Semantics.POSITION)); - Point2D pt = new Point2D(); - Point2D pt_prev = new Point2D(); - Point2D pt_1 = new Point2D(); - Point2D pt_2 = new Point2D(); - for (int ipath = 0, npath = mimpl_a.getPathCount(); ipath < npath; ipath++) { - boolean b_first = true; - for (int i = mimpl_a.getPathStart(ipath), n = mimpl_a - .getPathEnd(ipath); i < n; i++) { - if (b_first) { - xy.read(2 * i, pt_prev); - b_first = false; - continue; - } - - xy.read(2 * i, pt); - pt_1.setCoords(pt_prev); - pt_2.setCoords(pt); - if (env_b_inflated.clipLine(pt_1, pt_2) != 0) - return true; - - pt_prev.setCoords(pt); - } - } - } else { - Line line_1 = new Line(env_b.xmin, env_b.ymin, env_b.xmin, - env_b.ymax); - Line line_2 = new Line(env_b.xmin, env_b.ymax, env_b.xmax, - env_b.ymax); - Line line3 = new Line(env_b.xmax, env_b.ymax, env_b.xmax, - env_b.ymin); - Line line4 = new Line(env_b.xmax, env_b.ymin, env_b.xmin, - env_b.ymin); - SegmentIterator iter = multipath_a.querySegmentIterator(); - while (iter.nextPath()) { - while (iter.hasNextSegment()) { - Segment polySeg = iter.nextSegment(); - if (polySeg.isIntersecting(line_1, tolerance)) - return true; - - if (polySeg.isIntersecting(line_2, tolerance)) - return true; - - if (polySeg.isIntersecting(line3, tolerance)) - return true; - - if (polySeg.isIntersecting(line4, tolerance)) - return true; - } - } - } - - return false; - } - - // Returns contains, disjoint, or within if the relationship can be - // determined from the rasterized tests. - // When bExtraTestForIntersects is true performs extra tests and can return - // "intersects". - static int tryRasterizedContainsOrDisjoint_(Geometry geom_a, - Geometry geom_b, double tolerance, boolean bExtraTestForIntersects) { - int gtA = geom_a.getType().value(); - int gtB = geom_b.getType().value(); - do { - if (Geometry.isMultiVertex(gtA)) { - MultiVertexGeometryImpl impl = (MultiVertexGeometryImpl) geom_a - ._getImpl(); - GeometryAccelerators accel = impl._getAccelerators(); - if (accel != null) { - RasterizedGeometry2D rgeom = accel.getRasterizedGeometry(); - if (rgeom != null) { - if (gtB == Geometry.GeometryType.Point) { - Point2D ptB = ((Point) geom_b).getXY(); - RasterizedGeometry2D.HitType hit = rgeom - .queryPointInGeometry(ptB.x, ptB.y); - if (hit == RasterizedGeometry2D.HitType.Inside) { - return Relation.contains; - } else if (hit == RasterizedGeometry2D.HitType.Outside) { - return Relation.disjoint; - } - break; - } - Envelope2D env_b = new Envelope2D(); - geom_b.queryEnvelope2D(env_b); - RasterizedGeometry2D.HitType hit = rgeom - .queryEnvelopeInGeometry(env_b); - if (hit == RasterizedGeometry2D.HitType.Inside) { - return Relation.contains; - } else if (hit == RasterizedGeometry2D.HitType.Outside) { - return Relation.disjoint; - } else if (bExtraTestForIntersects - && Geometry.isMultiVertex(gtB)) { - if (checkVerticesForIntersection_( - (MultiVertexGeometryImpl) geom_b._getImpl(), - rgeom)) { - return Relation.intersects; - } - } - - break; - } - } - } - } while (false); - - do { - if (Geometry.isMultiVertex(gtB)) { - MultiVertexGeometryImpl impl = (MultiVertexGeometryImpl) geom_b - ._getImpl(); - GeometryAccelerators accel = impl._getAccelerators(); - if (accel != null) { - RasterizedGeometry2D rgeom = accel.getRasterizedGeometry(); - if (rgeom != null) { - if (gtA == Geometry.GeometryType.Point) { - Point2D ptA = ((Point) geom_a).getXY(); - RasterizedGeometry2D.HitType hit = rgeom - .queryPointInGeometry(ptA.x, ptA.y); - if (hit == RasterizedGeometry2D.HitType.Inside) { - return Relation.within; - } else if (hit == RasterizedGeometry2D.HitType.Outside) { - return Relation.disjoint; - } - break; - } - - Envelope2D env_a = new Envelope2D(); - geom_a.queryEnvelope2D(env_a); - RasterizedGeometry2D.HitType hit = rgeom - .queryEnvelopeInGeometry(env_a); - if (hit == RasterizedGeometry2D.HitType.Inside) { - return Relation.within; - } else if (hit == RasterizedGeometry2D.HitType.Outside) { - return Relation.disjoint; - } else if (bExtraTestForIntersects - && Geometry.isMultiVertex(gtA)) { - if (checkVerticesForIntersection_( - (MultiVertexGeometryImpl) geom_a._getImpl(), - rgeom)) { - return Relation.intersects; - } - } - - break; - } - } - } - } while (false); - - return Relation.unknown; - } - - // Returns true if intersects and false if nothing can be determined. - private static boolean checkVerticesForIntersection_( - MultiVertexGeometryImpl geom, RasterizedGeometry2D rgeom) { - // Do a quick raster test for each point. If any point is inside, then - // there is an intersection. - int pointCount = geom.getPointCount(); - Point2D pt = new Point2D(); - for (int ipoint = 0; ipoint < pointCount; ipoint++) { - geom.getXY(ipoint, pt); - RasterizedGeometry2D.HitType hit = rgeom.queryPointInGeometry(pt.x, - pt.y); - if (hit == RasterizedGeometry2D.HitType.Inside) { - return true; - } - } - - return false; - } - - private static boolean polygonTouchesPolygonImpl_(Polygon polygon_a, - Polygon polygon_b, double tolerance, ProgressTracker progressTracker) { - MultiPathImpl polygon_impl_a = (MultiPathImpl) polygon_a._getImpl(); - MultiPathImpl polygon_impl_b = (MultiPathImpl) polygon_b._getImpl(); - - // double geom_tolerance; - boolean b_geometries_simple = polygon_impl_a.getIsSimple(0.0) >= 1 - && polygon_impl_b.getIsSimple(0.0) >= 1; - - SegmentIteratorImpl segIterA = polygon_impl_a.querySegmentIterator(); - SegmentIteratorImpl segIterB = polygon_impl_b.querySegmentIterator(); - double[] scalarsA = new double[2]; - double[] scalarsB = new double[2]; - - PairwiseIntersectorImpl intersector = new PairwiseIntersectorImpl( - polygon_impl_a, polygon_impl_b, tolerance, false); - - boolean b_boundaries_intersect = false; - - while (intersector.next()) { - int vertex_a = intersector.getRedElement(); - int vertex_b = intersector.getBlueElement(); - - segIterA.resetToVertex(vertex_a); - segIterB.resetToVertex(vertex_b); - Segment segmentA = segIterA.nextSegment(); - Segment segmentB = segIterB.nextSegment(); - - int result = segmentB.intersect(segmentA, null, scalarsB, scalarsA, - tolerance); - - if (result == 2) { - double scalar_a_0 = scalarsA[0]; - double scalar_a_1 = scalarsA[1]; - double length_a = segmentA.calculateLength2D(); - - if (b_geometries_simple - && (scalar_a_1 - scalar_a_0) * length_a > tolerance) { - // If the line segments overlap along the same direction, - // then we have an Interior-Interior intersection - return false; - } - - b_boundaries_intersect = true; - } else if (result != 0) { - double scalar_a_0 = scalarsA[0]; - double scalar_b_0 = scalarsB[0]; - - if (scalar_a_0 > 0.0 && scalar_a_0 < 1.0 && scalar_b_0 > 0.0 - && scalar_b_0 < 1.0) { - return false; - } - - b_boundaries_intersect = true; - } - } - - if (!b_boundaries_intersect) { - return false; - } - - Envelope2D env_a = new Envelope2D(), env_b = new Envelope2D(), envInter = new Envelope2D(); - polygon_a.queryEnvelope2D(env_a); - polygon_b.queryEnvelope2D(env_b); - env_a.inflate(1000.0 * tolerance, 1000.0 * tolerance); - env_b.inflate(1000.0 * tolerance, 1000.0 * tolerance); - envInter.setCoords(env_a); - envInter.intersect(env_b); - - Polygon _polygonA; - Polygon _polygonB; - - if (polygon_a.getPointCount() > 10) { - _polygonA = (Polygon) (Clipper.clip(polygon_a, envInter, tolerance, - 0.0)); - if (_polygonA.isEmpty()) { - return false; - } - } else { - _polygonA = polygon_a; - } - - if (polygon_b.getPointCount() > 10) { - _polygonB = (Polygon) (Clipper.clip(polygon_b, envInter, tolerance, - 0.0)); - if (_polygonB.isEmpty()) { - return false; - } - } else { - _polygonB = polygon_b; - } - - // We just need to determine whether interior_interior is false - String scl = "F********"; - boolean bRelation = RelationalOperationsMatrix.polygonRelatePolygon_( - _polygonA, _polygonB, tolerance, scl, progressTracker); - - return bRelation; - } - - private static boolean polygonOverlapsPolygonImpl_(Polygon polygon_a, - Polygon polygon_b, double tolerance, ProgressTracker progressTracker) { - MultiPathImpl polygon_impl_a = (MultiPathImpl) polygon_a._getImpl(); - MultiPathImpl polygon_impl_b = (MultiPathImpl) polygon_b._getImpl(); - - // double geom_tolerance; - boolean b_geometries_simple = polygon_impl_a.getIsSimple(0.0) >= 1 - && polygon_impl_b.getIsSimple(0.0) >= 1; - - Envelope2D env_a = new Envelope2D(), env_b = new Envelope2D(), envInter = new Envelope2D(); - polygon_a.queryEnvelope2D(env_a); - polygon_b.queryEnvelope2D(env_b); - - boolean bInteriorIntersectionKnown = false; - - boolean bIntAExtB = interiorEnvExteriorEnv_(env_a, env_b, tolerance); - boolean bExtAIntB = interiorEnvExteriorEnv_(env_b, env_a, tolerance); - - SegmentIteratorImpl segIterA = polygon_impl_a.querySegmentIterator(); - SegmentIteratorImpl segIterB = polygon_impl_b.querySegmentIterator(); - double[] scalarsA = new double[2]; - double[] scalarsB = new double[2]; - - PairwiseIntersectorImpl intersector = new PairwiseIntersectorImpl( - polygon_impl_a, polygon_impl_b, tolerance, false); - - while (intersector.next()) { - int vertex_a = intersector.getRedElement(); - int vertex_b = intersector.getBlueElement(); - - segIterA.resetToVertex(vertex_a); - segIterB.resetToVertex(vertex_b); - Segment segmentA = segIterA.nextSegment(); - Segment segmentB = segIterB.nextSegment(); - - int result = segmentB.intersect(segmentA, null, scalarsB, scalarsA, - tolerance); - - if (result == 2) { - double scalar_a_0 = scalarsA[0]; - double scalar_a_1 = scalarsA[1]; - double length_a = segmentA.calculateLength2D(); - - if (b_geometries_simple - && (scalar_a_1 - scalar_a_0) * length_a > tolerance) { - // When the line segments intersect along the same - // direction, then we have an interior-interior intersection - bInteriorIntersectionKnown = true; - - if (bIntAExtB && bExtAIntB) { - return true; - } - } - } else if (result != 0) { - double scalar_a_0 = scalarsA[0]; - double scalar_b_0 = scalarsB[0]; - - if (scalar_a_0 > 0.0 && scalar_a_0 < 1.0 && scalar_b_0 > 0.0 - && scalar_b_0 < 1.0) { - return true; - } - } - } - - Envelope2D envAInflated = new Envelope2D(), envBInflated = new Envelope2D(); - envAInflated.setCoords(env_a); - envAInflated.inflate(1000.0 * tolerance, 1000.0 * tolerance); - envBInflated.setCoords(env_b); - envBInflated.inflate(1000.0 * tolerance, 1000.0 * tolerance); - - envInter.setCoords(envAInflated); - envInter.intersect(envBInflated); - - Polygon _polygonA; - Polygon _polygonB; - StringBuilder scl = new StringBuilder(); - - if (!bInteriorIntersectionKnown) { - scl.append("T*"); - } else { - scl.append("**"); - } - - if (bIntAExtB) { - if (polygon_b.getPointCount() > 10) { - _polygonB = (Polygon) (Clipper.clip(polygon_b, envInter, - tolerance, 0.0)); - if (_polygonB.isEmpty()) { - return false; - } - } else { - _polygonB = polygon_b; - } - - scl.append("****"); - } else { - _polygonB = polygon_b; - scl.append("T***"); - } - - if (bExtAIntB) { - if (polygon_a.getPointCount() > 10) { - _polygonA = (Polygon) (Clipper.clip(polygon_a, envInter, - tolerance, 0.0)); - if (_polygonA.isEmpty()) { - return false; - } - } else { - _polygonA = polygon_a; - } - - scl.append("***"); - } else { - _polygonA = polygon_a; - scl.append("T**"); - } - - boolean bRelation = RelationalOperationsMatrix.polygonRelatePolygon_( - _polygonA, _polygonB, tolerance, scl.toString(), - progressTracker); - return bRelation; - } - - private static boolean polygonContainsPolygonImpl_(Polygon polygon_a, - Polygon polygon_b, double tolerance, ProgressTracker progressTracker) { - boolean[] b_result_known = new boolean[1]; - b_result_known[0] = false; - boolean res = polygonContainsMultiPath_(polygon_a, polygon_b, tolerance, b_result_known, progressTracker); - - if (b_result_known[0]) - return res; - - // We can clip polygon_a to the extent of polyline_b - - Envelope2D envBInflated = new Envelope2D(); - polygon_b.queryEnvelope2D(envBInflated); - envBInflated.inflate(1000.0 * tolerance, 1000.0 * tolerance); - - Polygon _polygonA = null; - - if (polygon_a.getPointCount() > 10) { - _polygonA = (Polygon) Clipper.clip(polygon_a, envBInflated, tolerance, 0.0); - if (_polygonA.isEmpty()) - return false; - } else { - _polygonA = polygon_a; - } - - boolean bContains = RelationalOperationsMatrix.polygonContainsPolygon_(_polygonA, polygon_b, tolerance, progressTracker); - return bContains; - } - - private static boolean polygonContainsMultiPath_(Polygon polygon_a, MultiPath multi_path_b, double tolerance, boolean[] b_result_known, ProgressTracker progress_tracker) { - b_result_known[0] = false; - - MultiPathImpl polygon_impl_a = (MultiPathImpl) polygon_a._getImpl(); - MultiPathImpl multi_path_impl_b = (MultiPathImpl) multi_path_b._getImpl(); - - SegmentIteratorImpl segIterA = polygon_impl_a.querySegmentIterator(); - SegmentIteratorImpl segIterB = multi_path_impl_b.querySegmentIterator(); - double[] scalarsA = new double[2]; - double[] scalarsB = new double[2]; - - PairwiseIntersectorImpl intersector = new PairwiseIntersectorImpl(polygon_impl_a, multi_path_impl_b, tolerance, false); - boolean b_boundaries_intersect = false; - - while (intersector.next()) { - int vertex_a = intersector.getRedElement(); - int vertex_b = intersector.getBlueElement(); - - segIterA.resetToVertex(vertex_a, -1); - segIterB.resetToVertex(vertex_b, -1); - Segment segmentA = segIterA.nextSegment(); - Segment segmentB = segIterB.nextSegment(); - - int result = segmentB.intersect(segmentA, null, scalarsB, scalarsA, tolerance); - - if (result != 0) { - b_boundaries_intersect = true; - if (result == 1) { - double scalar_a_0 = scalarsA[0]; - double scalar_b_0 = scalarsB[0]; - - if (scalar_a_0 > 0.0 && scalar_a_0 < 1.0 && scalar_b_0 > 0.0 && scalar_b_0 < 1.0) { - b_result_known[0] = true; - return false; - } - } - } - } - - if (!b_boundaries_intersect) { - b_result_known[0] = true; - - //boundaries do not intersect - - Envelope2D env_a_inflated = new Envelope2D(); - polygon_a.queryEnvelope2D(env_a_inflated); - env_a_inflated.inflate(tolerance, tolerance); - - Polygon pa = null; - Polygon p_polygon_a = polygon_a; - - boolean b_checked_polygon_a_quad_tree = false; - - Envelope2D path_env_b = new Envelope2D(); - - for (int ipath = 0, npath = multi_path_b.getPathCount(); ipath < npath; ipath++) { - if (multi_path_b.getPathSize(ipath) > 0) { - multi_path_b.queryPathEnvelope2D(ipath, path_env_b); - - if (env_a_inflated.isIntersecting(path_env_b)) { - Point2D anyPoint = multi_path_b.getXY(multi_path_b.getPathStart(ipath)); - int res = PointInPolygonHelper.isPointInPolygon(p_polygon_a, anyPoint, 0); - if (res == 0) - return false; - } else { - return false; - } - - if (!b_checked_polygon_a_quad_tree) { - if (PointInPolygonHelper.quadTreeWillHelp(polygon_a, multi_path_b.getPathCount() - 1) && (polygon_impl_a._getAccelerators() == null || polygon_impl_a._getAccelerators().getQuadTree() == null)) { - pa = new Polygon(); - polygon_a.copyTo(pa); - ((MultiPathImpl) pa._getImpl())._buildQuadTreeAccelerator(Geometry.GeometryAccelerationDegree.enumMedium); - p_polygon_a = pa; - } else { - p_polygon_a = polygon_a; - } - - b_checked_polygon_a_quad_tree = true; - } - } - } - - if (polygon_a.getPathCount() == 1 || multi_path_b.getType().value() == Geometry.GeometryType.Polyline) - return true; //boundaries do not intersect. all paths of b are inside of a - - // Polygon A has multiple rings, and Multi_path B is a polygon. - - Polygon polygon_b = (Polygon) multi_path_b; - - Envelope2D env_b_inflated = new Envelope2D(); - polygon_b.queryEnvelope2D(env_b_inflated); - env_b_inflated.inflate(tolerance, tolerance); - - Polygon pb = null; - Polygon p_polygon_b = polygon_b; - - boolean b_checked_polygon_b_quad_tree = false; - - Envelope2D path_env_a = new Envelope2D(); - - for (int ipath = 0, npath = polygon_a.getPathCount(); ipath < npath; ipath++) { - if (polygon_a.getPathSize(ipath) > 0) { - polygon_a.queryPathEnvelope2D(ipath, path_env_a); - - if (env_b_inflated.isIntersecting(path_env_a)) { - Point2D anyPoint = polygon_a.getXY(polygon_a.getPathStart(ipath)); - int res = PointInPolygonHelper.isPointInPolygon(p_polygon_b, anyPoint, 0); - if (res == 1) - return false; - } - - if (!b_checked_polygon_b_quad_tree) { - if (PointInPolygonHelper.quadTreeWillHelp(polygon_b, polygon_a.getPathCount() - 1) && (multi_path_impl_b._getAccelerators() == null || multi_path_impl_b._getAccelerators().getQuadTree() == null)) { - pb = new Polygon(); - polygon_b.copyTo(pb); - ((MultiPathImpl) pb._getImpl())._buildQuadTreeAccelerator(Geometry.GeometryAccelerationDegree.enumMedium); - p_polygon_b = pb; - } else { - p_polygon_b = polygon_b; - } - - b_checked_polygon_b_quad_tree = true; - } - } - } - - return true; - } - - return false; - } - - private static boolean polygonTouchesPolylineImpl_(Polygon polygon_a, - Polyline polyline_b, double tolerance, - ProgressTracker progressTracker) { - MultiPathImpl polygon_impl_a = (MultiPathImpl) polygon_a._getImpl(); - MultiPathImpl polyline_impl_b = (MultiPathImpl) polyline_b._getImpl(); - - SegmentIteratorImpl segIterA = polygon_impl_a.querySegmentIterator(); - SegmentIteratorImpl segIterB = polyline_impl_b.querySegmentIterator(); - double[] scalarsA = new double[2]; - double[] scalarsB = new double[2]; - - PairwiseIntersectorImpl intersector = new PairwiseIntersectorImpl( - polygon_impl_a, polyline_impl_b, tolerance, false); - - boolean b_boundaries_intersect = false; - - while (intersector.next()) { - int vertex_a = intersector.getRedElement(); - int vertex_b = intersector.getBlueElement(); - - segIterA.resetToVertex(vertex_a); - segIterB.resetToVertex(vertex_b); - Segment segmentA = segIterA.nextSegment(); - Segment segmentB = segIterB.nextSegment(); - - int result = segmentB.intersect(segmentA, null, scalarsB, scalarsA, - tolerance); - - if (result == 2) { - b_boundaries_intersect = true; - } else if (result != 0) { - double scalar_a_0 = scalarsA[0]; - double scalar_b_0 = scalarsB[0]; - - if (scalar_a_0 > 0.0 && scalar_a_0 < 1.0 && scalar_b_0 > 0.0 - && scalar_b_0 < 1.0) { - return false; - } - - b_boundaries_intersect = true; - } - } - - if (!b_boundaries_intersect) { - return false; - } - - Envelope2D env_a = new Envelope2D(), env_b = new Envelope2D(), envInter = new Envelope2D(); - polygon_a.queryEnvelope2D(env_a); - polyline_b.queryEnvelope2D(env_b); - env_a.inflate(1000.0 * tolerance, 1000.0 * tolerance); - env_b.inflate(1000.0 * tolerance, 1000.0 * tolerance); - envInter.setCoords(env_a); - envInter.intersect(env_b); - - Polygon _polygonA; - Polyline _polylineB; - - if (polygon_a.getPointCount() > 10) { - _polygonA = (Polygon) (Clipper.clip(polygon_a, envInter, tolerance, - 0.0)); - if (_polygonA.isEmpty()) { - return false; - } - } else { - _polygonA = polygon_a; - } - - if (polyline_b.getPointCount() > 10) { - _polylineB = (Polyline) Clipper.clip(polyline_b, envInter, - tolerance, 0.0); - if (_polylineB.isEmpty()) { - return false; - } - } else { - _polylineB = polyline_b; - } - - // We just need to determine that interior_interior is false - String scl = "F********"; - boolean bRelation = RelationalOperationsMatrix.polygonRelatePolyline_( - _polygonA, _polylineB, tolerance, scl, progressTracker); - - return bRelation; - } - - private static boolean polygonCrossesPolylineImpl_(Polygon polygon_a, - Polyline polyline_b, double tolerance, - ProgressTracker progressTracker) { - MultiPathImpl polygon_impl_a = (MultiPathImpl) polygon_a._getImpl(); - MultiPathImpl polyline_impl_b = (MultiPathImpl) polyline_b._getImpl(); - - SegmentIteratorImpl segIterA = polygon_impl_a.querySegmentIterator(); - SegmentIteratorImpl segIterB = polyline_impl_b.querySegmentIterator(); - double[] scalarsA = new double[2]; - double[] scalarsB = new double[2]; - - PairwiseIntersectorImpl intersector = new PairwiseIntersectorImpl( - polygon_impl_a, polyline_impl_b, tolerance, false); - - boolean b_boundaries_intersect = false; - - while (intersector.next()) { - int vertex_a = intersector.getRedElement(); - int vertex_b = intersector.getBlueElement(); - - segIterA.resetToVertex(vertex_a); - segIterB.resetToVertex(vertex_b); - Segment segmentA = segIterA.nextSegment(); - Segment segmentB = segIterB.nextSegment(); - - int result = segmentB.intersect(segmentA, null, scalarsB, scalarsA, - tolerance); - - if (result == 2) { - b_boundaries_intersect = true; - } else if (result != 0) { - double scalar_a_0 = scalarsA[0]; - double scalar_b_0 = scalarsB[0]; - - if (scalar_a_0 > 0.0 && scalar_a_0 < 1.0 && scalar_b_0 > 0.0 - && scalar_b_0 < 1.0) { - return true; - } - - b_boundaries_intersect = true; - } - } - - if (!b_boundaries_intersect) { - return false; - } - - Envelope2D env_a = new Envelope2D(), env_b = new Envelope2D(), envAInflated = new Envelope2D(), envBInflated = new Envelope2D(), envInter = new Envelope2D(); - polygon_a.queryEnvelope2D(env_a); - polyline_b.queryEnvelope2D(env_b); - - if (interiorEnvExteriorEnv_(env_b, env_a, tolerance)) { - envAInflated.setCoords(env_a); - envAInflated.inflate(1000.0 * tolerance, 1000.0 * tolerance); - envBInflated.setCoords(env_b); - envBInflated.inflate(1000.0 * tolerance, 1000.0 * tolerance); - envInter.setCoords(envAInflated); - envInter.intersect(envBInflated); - - Polygon _polygonA; - Polyline _polylineB; - - if (polygon_a.getPointCount() > 10) { - _polygonA = (Polygon) (Clipper.clip(polygon_a, envInter, - tolerance, 0.0)); - if (_polygonA.isEmpty()) { - return false; - } - } else { - _polygonA = polygon_a; - } - - if (polyline_b.getPointCount() > 10) { - _polylineB = (Polyline) (Clipper.clip(polyline_b, envInter, - tolerance, 0.0)); - if (_polylineB.isEmpty()) { - return false; - } - } else { - _polylineB = polyline_b; - } - - String scl = "T********"; - boolean bRelation = RelationalOperationsMatrix - .polygonRelatePolyline_(_polygonA, _polylineB, tolerance, - scl, progressTracker); - return bRelation; - } - - String scl = "T*****T**"; - boolean bRelation = RelationalOperationsMatrix.polygonRelatePolyline_( - polygon_a, polyline_b, tolerance, scl, progressTracker); - - return bRelation; - } - - private static boolean polygonContainsPolylineImpl_(Polygon polygon_a, - Polyline polyline_b, double tolerance, - ProgressTracker progress_tracker) { - boolean[] b_result_known = new boolean[1]; - b_result_known[0] = false; - boolean res = polygonContainsMultiPath_(polygon_a, polyline_b, tolerance, b_result_known, progress_tracker); - - if (b_result_known[0]) - return res; - - // We can clip polygon_a to the extent of polyline_b - - Envelope2D envBInflated = new Envelope2D(); - polyline_b.queryEnvelope2D(envBInflated); - envBInflated.inflate(1000.0 * tolerance, 1000.0 * tolerance); - - Polygon _polygonA = null; - - if (polygon_a.getPointCount() > 10) { - _polygonA = (Polygon) Clipper.clip(polygon_a, envBInflated, tolerance, 0.0); - if (_polygonA.isEmpty()) - return false; - } else { - _polygonA = polygon_a; - } - - boolean bContains = RelationalOperationsMatrix.polygonContainsPolyline_(_polygonA, polyline_b, tolerance, progress_tracker); - return bContains; - } - - private static boolean polygonContainsPointImpl_(Polygon polygon_a, - Point2D pt_b, double tolerance, ProgressTracker progressTracker) { - PolygonUtils.PiPResult result = PolygonUtils.isPointInPolygon2D( - polygon_a, pt_b, tolerance); - - if (result == PolygonUtils.PiPResult.PiPInside) - return true; - - return false; - } - - private static boolean polygonTouchesPointImpl_(Polygon polygon_a, - Point2D pt_b, double tolerance, ProgressTracker progressTracker) { - PolygonUtils.PiPResult result = PolygonUtils.isPointInPolygon2D( - polygon_a, pt_b, tolerance); - - if (result == PolygonUtils.PiPResult.PiPBoundary) - return true; - - return false; - } - - static boolean multiPointDisjointPointImpl_(MultiPoint multipoint_a, - Point2D pt_b, double tolerance, ProgressTracker progressTracker) { - Point2D pt_a = new Point2D(); - double tolerance_sq = tolerance * tolerance; - - for (int i = 0; i < multipoint_a.getPointCount(); i++) { - multipoint_a.getXY(i, pt_a); - - if (Point2D.sqrDistance(pt_a, pt_b) <= tolerance_sq) - return false; - } - - return true; - } - - private static final class OverlapEvent { - int m_ivertex_a; - int m_ipath_a; - double m_scalar_a_0; - double m_scalar_a_1; - int m_ivertex_b; - int m_ipath_b; - double m_scalar_b_0; - double m_scalar_b_1; - - static OverlapEvent construct(int ivertex_a, int ipath_a, - double scalar_a_0, double scalar_a_1, int ivertex_b, - int ipath_b, double scalar_b_0, double scalar_b_1) { - OverlapEvent overlapEvent = new OverlapEvent(); - overlapEvent.m_ivertex_a = ivertex_a; - overlapEvent.m_ipath_a = ipath_a; - overlapEvent.m_scalar_a_0 = scalar_a_0; - overlapEvent.m_scalar_a_1 = scalar_a_1; - overlapEvent.m_ivertex_b = ivertex_b; - overlapEvent.m_ipath_b = ipath_b; - overlapEvent.m_scalar_b_0 = scalar_b_0; - overlapEvent.m_scalar_b_1 = scalar_b_1; - return overlapEvent; - } - } - - ArrayList m_overlap_events; - - private RelationalOperations() { - m_overlap_events = new ArrayList(); - } - - private static class OverlapComparer extends - AttributeStreamOfInt32.IntComparator { - OverlapComparer(RelationalOperations rel_ops) { - m_rel_ops = rel_ops; - } - - @Override - public int compare(int o_1, int o_2) { - return m_rel_ops.compareOverlapEvents_(o_1, o_2); - } - - private RelationalOperations m_rel_ops; - } - - int compareOverlapEvents_(int o_1, int o_2) { - OverlapEvent overlapEvent1 = m_overlap_events.get(o_1); - OverlapEvent overlapEvent2 = m_overlap_events.get(o_2); - - if (overlapEvent1.m_ipath_a < overlapEvent2.m_ipath_a) - return -1; - - if (overlapEvent1.m_ipath_a == overlapEvent2.m_ipath_a) { - if (overlapEvent1.m_ivertex_a < overlapEvent2.m_ivertex_a) - return -1; - - if (overlapEvent1.m_ivertex_a == overlapEvent2.m_ivertex_a) { - if (overlapEvent1.m_scalar_a_0 < overlapEvent2.m_scalar_a_0) - return -1; - - if (overlapEvent1.m_scalar_a_0 == overlapEvent2.m_scalar_a_0) { - if (overlapEvent1.m_scalar_a_1 < overlapEvent2.m_scalar_a_1) - return -1; - - if (overlapEvent1.m_scalar_a_1 == overlapEvent2.m_scalar_a_1) { - if (overlapEvent1.m_ivertex_b < overlapEvent2.m_ivertex_b) - return -1; - } - } - } - } - - return 1; - } - - static final class Accelerate_helper { - static boolean accelerate_geometry(Geometry geometry, - SpatialReference sr, - Geometry.GeometryAccelerationDegree accel_degree) { - if (!can_accelerate_geometry(geometry)) - return false; - - double tol = InternalUtils.calculateToleranceFromGeometry(sr, - geometry, false); - boolean bAccelerated = false; - if (GeometryAccelerators.canUseRasterizedGeometry(geometry)) - bAccelerated |= ((MultiVertexGeometryImpl) geometry._getImpl()) - ._buildRasterizedGeometryAccelerator(tol, accel_degree); - - Geometry.Type type = geometry.getType(); - if ((type == Geometry.Type.Polygon || type == Geometry.Type.Polyline) - && GeometryAccelerators.canUseQuadTree(geometry) - && accel_degree != Geometry.GeometryAccelerationDegree.enumMild) - bAccelerated |= ((MultiVertexGeometryImpl) geometry._getImpl()) - ._buildQuadTreeAccelerator(accel_degree); - - if ((type == Geometry.Type.Polygon || type == Geometry.Type.Polyline) - && GeometryAccelerators.canUseQuadTreeForPaths(geometry) - && accel_degree != Geometry.GeometryAccelerationDegree.enumMild) - bAccelerated |= ((MultiPathImpl) geometry._getImpl()) - ._buildQuadTreeForPathsAccelerator(accel_degree); - - return bAccelerated; - } - - static boolean can_accelerate_geometry(Geometry geometry) { - return GeometryAccelerators.canUseRasterizedGeometry(geometry) - || GeometryAccelerators.canUseQuadTree(geometry) || GeometryAccelerators.canUseQuadTreeForPaths(geometry); - } - } + if (envelopeInfContainsEnvelope_(env_b, env_a, tolerance)) + return false; + + if (env_b.getHeight() <= tolerance && env_b.getWidth() <= tolerance) + return false; + + if (env_b.getHeight() <= tolerance || env_b.getWidth() <= tolerance) {// treat + // as + // line + Envelope2D env_b_deflated = new Envelope2D(); + Envelope2D env_b_inflated = new Envelope2D(); + env_b_deflated.setCoords(env_b); + + if (env_b.getHeight() > tolerance) + env_b_deflated.inflate(0, -tolerance); + else + env_b_deflated.inflate(-tolerance, 0); + + env_b_inflated.setCoords(env_b); + env_b_inflated.inflate(tolerance, tolerance); + + Point2D pt_a = new Point2D(); + boolean b_interior = false, b_exterior = false; + + for (int i = 0; i < multipoint_a.getPointCount(); i++) { + multipoint_a.getXY(i, pt_a); + + if (!b_interior) { + if (env_b.getHeight() > tolerance) { + if (pt_a.y > env_b_deflated.ymin + && pt_a.y < env_b_deflated.ymax) + b_interior = true; + } else { + if (pt_a.x > env_b_deflated.xmin + && pt_a.x < env_b_deflated.xmax) + b_interior = true; + } + } + + if (!b_exterior && !env_b_inflated.contains(pt_a)) + b_exterior = true; + + if (b_interior && b_exterior) + return true; + } + + return false; + } + + Envelope2D env_b_deflated = new Envelope2D(), env_b_inflated = new Envelope2D(); + env_b_deflated.setCoords(env_b); + env_b_deflated.inflate(-tolerance, -tolerance); + assert (!env_b_deflated.isEmpty()); + + env_b_inflated.setCoords(env_b); + env_b_inflated.inflate(tolerance, tolerance); + + Point2D pt_a = new Point2D(); + boolean b_interior = false, b_exterior = false; + + for (int i = 0; i < multipoint_a.getPointCount(); i++) { + multipoint_a.getXY(i, pt_a); + + if (!b_interior && env_b_deflated.containsExclusive(pt_a)) + b_interior = true; + + if (!b_exterior && !env_b_inflated.contains(pt_a)) + b_exterior = true; + + if (b_interior && b_exterior) + return true; + } + + return false; + } + + // Returns true if pt_a equals pt_b. + private static boolean pointEqualsPoint_(Point2D pt_a, Point2D pt_b, + double tolerance, ProgressTracker progress_tracker) { + if (Point2D.sqrDistance(pt_a, pt_b) <= tolerance * tolerance) + return true; + + return false; + } + + // Returns true if pt_a is disjoint from pt_b. + private static boolean pointDisjointPoint_(Point2D pt_a, Point2D pt_b, + double tolerance, ProgressTracker progress_tracker) { + if (Point2D.sqrDistance(pt_a, pt_b) > tolerance * tolerance) + return true; + + return false; + } + + // Returns true if pt_a contains pt_b. + private static boolean pointContainsPoint_(Point2D pt_a, Point2D pt_b, + double tolerance, ProgressTracker progress_tracker) { + return pointEqualsPoint_(pt_a, pt_b, tolerance, progress_tracker); + } + + // Returns true if pt_a equals enve_b. + private static boolean pointEqualsEnvelope_(Point2D pt_a, Envelope2D env_b, + double tolerance, ProgressTracker progress_tracker) { + Envelope2D env_a = new Envelope2D(); + env_a.setCoords(pt_a); + return envelopeEqualsEnvelope_(env_a, env_b, tolerance, + progress_tracker); + } + + // Returns true if pt_a is disjoint from env_b. + static boolean pointDisjointEnvelope_(Point2D pt_a, Envelope2D env_b, + double tolerance, ProgressTracker progress_tracker) { + Envelope2D env_b_inflated = new Envelope2D(); + env_b_inflated.setCoords(env_b); + env_b_inflated.inflate(tolerance, tolerance); + return !env_b_inflated.contains(pt_a); + } + + // Returns true if pt_a touches env_b. + private static boolean pointTouchesEnvelope_(Point2D pt_a, + Envelope2D env_b, double tolerance, ProgressTracker progress_tracker) { + if (env_b.getHeight() <= tolerance && env_b.getWidth() <= tolerance) + return false; // when treates as a point, points cannot touch points + + Envelope2D env_b_inflated = new Envelope2D(), env_b_deflated = new Envelope2D(); + + env_b_inflated.setCoords(env_b); + env_b_inflated.inflate(tolerance, tolerance); + + if (!env_b_inflated.contains(pt_a)) + return false; + + if (env_b.getHeight() <= tolerance || env_b.getWidth() <= tolerance) { + env_b_deflated.setCoords(env_b); + + if (env_b.getHeight() > tolerance) + env_b_deflated.inflate(0, -tolerance); + else + env_b_deflated.inflate(-tolerance, 0); + + if (env_b.getHeight() > tolerance) { + if (pt_a.y > env_b_deflated.ymin + && pt_a.y < env_b_deflated.ymax) + return false; + } else { + if (pt_a.x > env_b_deflated.xmin + && pt_a.x < env_b_deflated.xmax) + return false; + } + + return true; + } + + env_b_deflated.setCoords(env_b); + env_b_deflated.inflate(-tolerance, -tolerance); + + if (env_b_deflated.containsExclusive(pt_a)) + return false; + + return true; + } + + // Returns true if pt_a is within env_b. + private static boolean pointWithinEnvelope_(Point2D pt_a, Envelope2D env_b, + double tolerance, ProgressTracker progress_tracker) { + if (env_b.getHeight() <= tolerance && env_b.getWidth() <= tolerance) { + // assert(env_b_inflated.contains(pt_a)); // should contain if we + // got to here (i.e. not disjoint) + return true; + } + + if (env_b.getHeight() <= tolerance || env_b.getWidth() <= tolerance) {// treat + // as + // line + Envelope2D env_b_deflated = new Envelope2D(); + env_b_deflated.setCoords(env_b); + + if (env_b.getHeight() > tolerance) + env_b_deflated.inflate(0, -tolerance); + else + env_b_deflated.inflate(-tolerance, 0); + + boolean b_interior = false; + + if (env_b.getHeight() > tolerance) { + if (pt_a.y > env_b_deflated.ymin + && pt_a.y < env_b_deflated.ymax) + b_interior = true; + } else { + if (pt_a.x > env_b_deflated.xmin + && pt_a.x < env_b_deflated.xmax) + b_interior = true; + } + + return b_interior; + } + + // treat as area + + Envelope2D env_b_deflated = new Envelope2D(); + env_b_deflated.setCoords(env_b); + env_b_deflated.inflate(-tolerance, -tolerance); + return env_b_deflated.containsExclusive(pt_a); + } + + // Returns true if pt_a contains env_b. + private static boolean pointContainsEnvelope_(Point2D pt_a, + Envelope2D env_b, double tolerance, ProgressTracker progress_tracker) { + return pointEqualsEnvelope_(pt_a, env_b, tolerance, progress_tracker); + } + + // Returns true if env_a equals env_b. + private static boolean envelopeEqualsEnvelope_(Envelope2D env_a, + Envelope2D env_b, double tolerance, ProgressTracker progress_tracker) { + return envelopeInfContainsEnvelope_(env_a, env_b, tolerance) + && envelopeInfContainsEnvelope_(env_b, env_a, tolerance); + } + + // Returns true if env_a is disjoint from env_b. + static boolean envelopeDisjointEnvelope_(Envelope2D env_a, + Envelope2D env_b, double tolerance, ProgressTracker progress_tracker) { + Envelope2D env_b_inflated = new Envelope2D(); + env_b_inflated.setCoords(env_b); + env_b_inflated.inflate(tolerance, tolerance); + return !env_a.isIntersecting(env_b_inflated); + } + + // Returns true if env_a touches env_b. + private static boolean envelopeTouchesEnvelope_(Envelope2D env_a, + Envelope2D env_b, double tolerance, ProgressTracker progress_tracker) { + if (env_a.getHeight() <= tolerance && env_a.getWidth() <= tolerance) {// treat + // env_a + // as + // point + Point2D pt_a = env_a.getCenter(); + return pointTouchesEnvelope_(pt_a, env_b, tolerance, + progress_tracker); + } + + if (env_b.getHeight() <= tolerance && env_b.getWidth() <= tolerance) {// treat + // env_b + // as + // point + Point2D pt_b = env_b.getCenter(); + return pointTouchesEnvelope_(pt_b, env_a, tolerance, + progress_tracker); + } + + Envelope2D _env_a; + Envelope2D _env_b; + + if (env_a.getHeight() > tolerance + && env_a.getWidth() > tolerance + && (env_b.getHeight() <= tolerance || env_b.getWidth() <= tolerance)) { + // swap a and b + _env_a = env_b; + _env_b = env_a; + } else { + _env_a = env_a; + _env_b = env_b; + } + + if (_env_a.getHeight() <= tolerance || _env_a.getWidth() <= tolerance) {// treat + // env_a + // as + // line + + if (_env_b.getHeight() <= tolerance + || _env_b.getWidth() <= tolerance) {// treat env_b as line + + Line line_a = new Line(), line_b = new Line(); + double[] scalars_a = new double[2]; + double[] scalars_b = new double[2]; + Point2D pt = new Point2D(); + _env_a.queryLowerLeft(pt); + line_a.setStartXY(pt); + _env_a.queryUpperRight(pt); + line_a.setEndXY(pt); + _env_b.queryLowerLeft(pt); + line_b.setStartXY(pt); + _env_b.queryUpperRight(pt); + line_b.setEndXY(pt); + + line_a.intersect(line_b, null, scalars_a, scalars_b, tolerance); + int count = line_a.intersect(line_b, null, null, null, + tolerance); + + if (count != 1) + return false; + + return scalars_a[0] == 0.0 || scalars_a[1] == 1.0 + || scalars_b[0] == 0.0 || scalars_b[1] == 1.0; + } + + // treat env_b as area + + Envelope2D env_b_deflated = new Envelope2D(), env_inter = new Envelope2D(); + env_b_deflated.setCoords(_env_b); + env_b_deflated.inflate(-tolerance, -tolerance); + env_inter.setCoords(env_b_deflated); + env_inter.intersect(_env_a); + + if (!env_inter.isEmpty() + && (env_inter.getHeight() > tolerance || env_inter + .getWidth() > tolerance)) + return false; + + assert (!envelopeDisjointEnvelope_(_env_a, _env_b, tolerance, + progress_tracker)); + return true; // we already know they intersect within a tolerance + } + + Envelope2D env_inter = new Envelope2D(); + env_inter.setCoords(_env_a); + env_inter.intersect(_env_b); + + if (!env_inter.isEmpty() && env_inter.getHeight() > tolerance + && env_inter.getWidth() > tolerance) + return false; + + return true; // we already know they intersect within a tolerance + } + + // Returns true if env_a overlaps env_b. + private static boolean envelopeOverlapsEnvelope_(Envelope2D env_a, + Envelope2D env_b, double tolerance, ProgressTracker progress_tracker) { + if (envelopeInfContainsEnvelope_(env_a, env_b, tolerance) + || envelopeInfContainsEnvelope_(env_b, env_a, tolerance)) + return false; + + if (env_a.getHeight() <= tolerance && env_a.getWidth() <= tolerance) + return false; // points cannot overlap + + if (env_b.getHeight() <= tolerance && env_b.getWidth() <= tolerance) + return false; // points cannot overlap + + if (env_a.getHeight() <= tolerance || env_a.getWidth() <= tolerance) {// treat + // env_a + // as + // a + // line + + if (env_b.getHeight() > tolerance && env_b.getWidth() > tolerance) + return false; // lines cannot overlap areas + + // treat both as lines + + Line line_a = new Line(), line_b = new Line(); + double[] scalars_a = new double[2]; + double[] scalars_b = new double[2]; + Point2D pt = new Point2D(); + env_a.queryLowerLeft(pt); + line_a.setStartXY(pt); + env_a.queryUpperRight(pt); + line_a.setEndXY(pt); + env_b.queryLowerLeft(pt); + line_b.setStartXY(pt); + env_b.queryUpperRight(pt); + line_b.setEndXY(pt); + + line_a.intersect(line_b, null, scalars_a, scalars_b, tolerance); + int count = line_a.intersect(line_b, null, null, null, tolerance); + + if (count != 2) + return false; + + return (scalars_a[0] > 0.0 || scalars_a[1] < 1.0) + && (scalars_b[0] > 0.0 || scalars_b[1] < 1.0); + } + + if (env_b.getHeight() <= tolerance || env_b.getWidth() <= tolerance) + return false; // lines cannot overlap areas + + // treat both as areas + + Envelope2D env_inter = new Envelope2D(); + env_inter.setCoords(env_a); + env_inter.intersect(env_b); + + if (env_inter.isEmpty()) + return false; + + if (env_inter.getHeight() <= tolerance + || env_inter.getWidth() <= tolerance) + return false; // not an area + + assert (!envelopeInfContainsEnvelope_(env_inter, env_a, tolerance) && !envelopeInfContainsEnvelope_( + env_inter, env_b, tolerance)); + + return true; + } + + // Returns true if env_a contains env_b. + private static boolean envelopeContainsEnvelope_(Envelope2D env_a, + Envelope2D env_b, double tolerance, ProgressTracker progress_tracker) { + if (!envelopeInfContainsEnvelope_(env_a, env_b, tolerance)) + return false; + + if (env_a.getHeight() <= tolerance && env_a.getWidth() <= tolerance) { + Point2D pt_a = env_a.getCenter(); + return pointWithinEnvelope_(pt_a, env_b, tolerance, + progress_tracker); + } + + if (env_b.getHeight() <= tolerance && env_b.getWidth() <= tolerance) { + Point2D pt_b = env_b.getCenter(); + return pointWithinEnvelope_(pt_b, env_a, tolerance, + progress_tracker); + } + + if (env_a.getHeight() <= tolerance || env_a.getWidth() <= tolerance) + return envelopeInfContainsEnvelope_(env_a, env_b, tolerance); // treat + // env_b + // as + // line + + // treat env_a as area + + if (env_b.getHeight() <= tolerance || env_b.getWidth() <= tolerance) {// treat + // env_b + // as + // line + + Envelope2D env_a_deflated = new Envelope2D(); + env_a_deflated.setCoords(env_a); + env_a_deflated.inflate(-tolerance, -tolerance); + + if (env_a_deflated.containsExclusive(env_b)) + return true; + + Envelope2D env_inter = new Envelope2D(); + env_inter.setCoords(env_a_deflated); + env_inter.intersect(env_b); + + if (env_inter.isEmpty() + || (env_inter.getHeight() <= tolerance && env_inter + .getWidth() <= tolerance)) + return false; + + return true; + } + + return envelopeInfContainsEnvelope_(env_a, env_b, tolerance); + } + + // Returns true if env_a crosses env_b. + private static boolean envelopeCrossesEnvelope_(Envelope2D env_a, + Envelope2D env_b, double tolerance, ProgressTracker progress_tracker) { + if (envelopeInfContainsEnvelope_(env_a, env_b, tolerance) + || envelopeInfContainsEnvelope_(env_b, env_a, tolerance)) + return false; + + if (env_a.getHeight() <= tolerance && env_a.getWidth() <= tolerance) + return false; // points cannot cross + + if (env_b.getHeight() <= tolerance && env_b.getWidth() <= tolerance) + return false; // points cannot cross + + if (env_b.getHeight() > tolerance && env_b.getWidth() > tolerance) { + if (env_a.getHeight() > tolerance && env_a.getWidth() > tolerance) + return false; // areas cannot cross + } + + Envelope2D _env_a; + Envelope2D _env_b; + + if (env_a.getHeight() > tolerance && env_a.getWidth() > tolerance) { + // swap b and a + _env_a = env_b; + _env_b = env_a; + } else { + _env_a = env_a; + _env_b = env_b; + } + + if (_env_b.getHeight() > tolerance && _env_b.getWidth() > tolerance) {// treat + // env_b + // as + // an + // area + // (env_a + // as + // a + // line); + + Envelope2D env_inter = new Envelope2D(), env_b_deflated = new Envelope2D(); + env_b_deflated.setCoords(_env_b); + env_b_deflated.inflate(-tolerance, -tolerance); + env_inter.setCoords(env_b_deflated); + env_inter.intersect(_env_a); + + if (env_inter.isEmpty()) + return false; + + if (env_inter.getHeight() <= tolerance + && env_inter.getWidth() <= tolerance) + return false; // not a line + + assert (!envelopeInfContainsEnvelope_(env_inter, _env_a, tolerance)); + return true; + } + + // treat both as lines + + Line line_a = new Line(), line_b = new Line(); + double[] scalars_a = new double[2]; + double[] scalars_b = new double[2]; + Point2D pt = new Point2D(); + _env_a.queryLowerLeft(pt); + line_a.setStartXY(pt); + _env_a.queryUpperRight(pt); + line_a.setEndXY(pt); + _env_b.queryLowerLeft(pt); + line_b.setStartXY(pt); + _env_b.queryUpperRight(pt); + line_b.setEndXY(pt); + + line_a.intersect(line_b, null, scalars_a, scalars_b, tolerance); + int count = line_a.intersect(line_b, null, null, null, tolerance); + + if (count != 1) + return false; + + return scalars_a[0] > 0.0 && scalars_a[1] < 1.0 && scalars_b[0] > 0.0 + && scalars_b[1] < 1.0; + } + + // Returns true if polygon_a is disjoint from multipath_b. + private static boolean polygonDisjointMultiPath_(Polygon polygon_a, + MultiPath multipath_b, double tolerance, + ProgressTracker progress_tracker) { + Point2D pt_a, pt_b; + Envelope2D env_a_inf = new Envelope2D(), env_b_inf = new Envelope2D(); + + MultiPathImpl multi_path_impl_a = (MultiPathImpl) polygon_a._getImpl(); + MultiPathImpl multi_path_impl_b = (MultiPathImpl) multipath_b._getImpl(); + + PairwiseIntersectorImpl intersector = new PairwiseIntersectorImpl(multi_path_impl_a, multi_path_impl_b, tolerance, true); + + if (!intersector.next()) + return true; // no rings intersect + + boolean b_intersects = linearPathIntersectsLinearPath_(polygon_a, multipath_b, tolerance); + + if (b_intersects) + return false; + + Polygon pa = null; + Polygon p_polygon_a = polygon_a; + + Polygon pb = null; + Polygon p_polygon_b = null; + + if (multipath_b.getType().value() == Geometry.GeometryType.Polygon) + p_polygon_b = (Polygon) multipath_b; + + boolean b_checked_polygon_a_quad_tree = false; + boolean b_checked_polygon_b_quad_tree = false; + + do { + int path_a = intersector.getRedElement(); + int path_b = intersector.getBlueElement(); + + pt_b = multipath_b.getXY(multipath_b.getPathStart(path_b)); + env_a_inf.setCoords(intersector.getRedEnvelope()); + env_a_inf.inflate(tolerance, tolerance); + + if (env_a_inf.contains(pt_b)) { + PolygonUtils.PiPResult result = PolygonUtils.isPointInPolygon2D(p_polygon_a, pt_b, 0.0); + + if (result != PolygonUtils.PiPResult.PiPOutside) + return false; + } + + if (multipath_b.getType().value() == Geometry.GeometryType.Polygon) { + pt_a = polygon_a.getXY(polygon_a.getPathStart(path_a)); + env_b_inf.setCoords(intersector.getBlueEnvelope()); + env_b_inf.inflate(tolerance, tolerance); + + if (env_b_inf.contains(pt_a)) { + PolygonUtils.PiPResult result = PolygonUtils.isPointInPolygon2D(p_polygon_b, pt_a, 0.0); + + if (result != PolygonUtils.PiPResult.PiPOutside) + return false; + } + } + + if (!b_checked_polygon_a_quad_tree) { + if (PointInPolygonHelper.quadTreeWillHelp(polygon_a, multipath_b.getPathCount() - 1) && (multi_path_impl_a._getAccelerators() == null || multi_path_impl_a._getAccelerators().getQuadTree() == null)) { + pa = new Polygon(); + polygon_a.copyTo(pa); + ((MultiPathImpl) pa._getImpl())._buildQuadTreeAccelerator(Geometry.GeometryAccelerationDegree.enumMedium); + p_polygon_a = pa; + } else { + p_polygon_a = polygon_a; + } + + b_checked_polygon_a_quad_tree = true; + } + + if (multipath_b.getType().value() == Geometry.GeometryType.Polygon) { + if (!b_checked_polygon_b_quad_tree) { + Polygon polygon_b = (Polygon) multipath_b; + if (PointInPolygonHelper.quadTreeWillHelp(polygon_b, polygon_a.getPathCount() - 1) && (multi_path_impl_b._getAccelerators() == null || multi_path_impl_b._getAccelerators().getQuadTree() == null)) { + pb = new Polygon(); + polygon_b.copyTo(pb); + ((MultiPathImpl) pb._getImpl())._buildQuadTreeAccelerator(Geometry.GeometryAccelerationDegree.enumMedium); + p_polygon_b = pb; + } else { + p_polygon_b = (Polygon) multipath_b; + } + + b_checked_polygon_b_quad_tree = true; + } + } + + } while (intersector.next()); + + return true; + } + + // Returns true if env_a inflated contains env_b. + private static boolean envelopeInfContainsEnvelope_(Envelope2D env_a, + Envelope2D env_b, double tolerance) { + Envelope2D env_a_inflated = new Envelope2D(); + env_a_inflated.setCoords(env_a); + env_a_inflated.inflate(tolerance, tolerance); + return env_a_inflated.contains(env_b); + } + + // Returns true if a coordinate of envelope A is outside of envelope B. + private static boolean interiorEnvExteriorEnv_(Envelope2D env_a, + Envelope2D env_b, double tolerance) { + Envelope2D envBInflated = new Envelope2D(); + envBInflated.setCoords(env_b); + envBInflated.inflate(tolerance, tolerance); + Point2D pt = new Point2D(); + + env_a.queryLowerLeft(pt); + if (!envBInflated.contains(pt)) + return true; + + env_a.queryLowerRight(pt); + if (!envBInflated.contains(pt)) + return true; + + env_a.queryUpperLeft(pt); + if (!envBInflated.contains(pt)) + return true; + + env_a.queryUpperRight(pt); + if (!envBInflated.contains(pt)) + return true; + + assert (envBInflated.contains(env_a)); + return false; + } + + // Returns true if the points in each path of multipathA are the same as + // those in multipathB, within a tolerance, and in the same order. + private static boolean multiPathExactlyEqualsMultiPath_( + MultiPath multipathA, MultiPath multipathB, double tolerance, + ProgressTracker progress_tracker) { + if (multipathA.getPathCount() != multipathB.getPathCount() + || multipathA.getPointCount() != multipathB.getPointCount()) + return false; + + Point2D ptA = new Point2D(), ptB = new Point2D(); + boolean bAllPointsEqual = true; + double tolerance_sq = tolerance * tolerance; + + for (int ipath = 0; ipath < multipathA.getPathCount(); ipath++) { + if (multipathA.getPathEnd(ipath) != multipathB.getPathEnd(ipath)) { + bAllPointsEqual = false; + break; + } + + for (int i = multipathA.getPathStart(ipath); i < multipathB + .getPathEnd(ipath); i++) { + multipathA.getXY(i, ptA); + multipathB.getXY(i, ptB); + + if (Point2D.sqrDistance(ptA, ptB) > tolerance_sq) { + bAllPointsEqual = false; + break; + } + } + + if (!bAllPointsEqual) + break; + } + + if (!bAllPointsEqual) + return false; + + return true; + } + + // Returns true if the points of multipoint_a are the same as those in + // multipoint_b, within a tolerance, and in the same order. + private static boolean multiPointExactlyEqualsMultiPoint_( + MultiPoint multipoint_a, MultiPoint multipoint_b, double tolerance, + ProgressTracker progress_tracker) { + if (multipoint_a.getPointCount() != multipoint_b.getPointCount()) + return false; + + Point2D ptA = new Point2D(), ptB = new Point2D(); + boolean bAllPointsEqual = true; + double tolerance_sq = tolerance * tolerance; + + for (int i = 0; i < multipoint_a.getPointCount(); i++) { + multipoint_a.getXY(i, ptA); + multipoint_b.getXY(i, ptB); + + if (Point2D.sqrDistance(ptA, ptB) > tolerance_sq) { + bAllPointsEqual = false; + break; + } + } + + if (!bAllPointsEqual) + return false; + + return true; + } + + // By default this will perform the within operation if bEquals is false. + // Otherwise it will do equals. + private static boolean multiPointCoverageMultiPoint_( + MultiPoint _multipointA, MultiPoint _multipointB, double tolerance, + boolean bPerformWithin, boolean bPerformEquals, + boolean bPerformOverlaps, ProgressTracker progress_tracker) { + boolean bPerformContains = false; + MultiPoint multipoint_a; + MultiPoint multipoint_b; + + if (_multipointA.getPointCount() > _multipointB.getPointCount()) { + if (bPerformWithin) { + bPerformWithin = false; + bPerformContains = true; + } + + multipoint_a = _multipointB; + multipoint_b = _multipointA; + } else { + multipoint_a = _multipointA; + multipoint_b = _multipointB; + } + + AttributeStreamOfInt8 contained = null; + + if (bPerformEquals || bPerformOverlaps || bPerformContains) { + contained = new AttributeStreamOfInt8(multipoint_b.getPointCount()); + + for (int i = 0; i < multipoint_b.getPointCount(); i++) + contained.write(i, (byte) 0); + } + + Envelope2D env_a = new Envelope2D(); + Envelope2D env_b = new Envelope2D(); + Envelope2D envInter = new Envelope2D(); + multipoint_a.queryEnvelope2D(env_a); + multipoint_b.queryEnvelope2D(env_b); + env_a.inflate(tolerance, tolerance); + env_b.inflate(tolerance, tolerance); + envInter.setCoords(env_a); + envInter.intersect(env_b); + + Point2D ptA = new Point2D(); + Point2D ptB = new Point2D(); + + boolean bWithin = true; // starts off true by default + + QuadTreeImpl quadTreeB = InternalUtils.buildQuadTree( + (MultiPointImpl) (multipoint_b._getImpl()), envInter); + QuadTreeImpl.QuadTreeIteratorImpl qtIterB = quadTreeB.getIterator(); + double tolerance_sq = tolerance * tolerance; + + for (int vertex_a = 0; vertex_a < multipoint_a.getPointCount(); vertex_a++) { + multipoint_a.getXY(vertex_a, ptA); + + if (!envInter.contains(ptA)) { + if (bPerformEquals || bPerformWithin) + return false; + else { + bWithin = false; + continue; + } + } + + boolean bPtACovered = false; + env_a.setCoords(ptA.x, ptA.y, ptA.x, ptA.y); + qtIterB.resetIterator(env_a, tolerance); + for (int elementHandleB = qtIterB.next(); elementHandleB != -1; elementHandleB = qtIterB + .next()) { + int vertex_b = quadTreeB.getElement(elementHandleB); + multipoint_b.getXY(vertex_b, ptB); + + if (Point2D.sqrDistance(ptA, ptB) <= tolerance_sq) { + if (bPerformEquals || bPerformOverlaps || bPerformContains) + contained.write(vertex_b, (byte) 1); + + bPtACovered = true; + + if (bPerformWithin) + break; + } + } + + if (!bPtACovered) { + bWithin = false; + + if (bPerformEquals || bPerformWithin) + return false; + } + } + + if (bPerformOverlaps && bWithin) + return false; + + if (bPerformWithin) + return true; + + for (int i = 0; i < multipoint_b.getPointCount(); i++) { + if (contained.read(i) == 1) { + if (bPerformOverlaps) + return true; + } else { + if (bPerformEquals || bPerformContains) + return false; + } + } + + if (bPerformOverlaps) + return false; + + return true; + } + + // Returns true if multipoint_a intersects multipoint_b. + private static boolean multiPointIntersectsMultiPoint_( + MultiPoint _multipointA, MultiPoint _multipointB, double tolerance, + ProgressTracker progress_tracker) { + MultiPoint multipoint_a; + MultiPoint multipoint_b; + + if (_multipointA.getPointCount() > _multipointB.getPointCount()) { + multipoint_a = _multipointB; + multipoint_b = _multipointA; + } else { + multipoint_a = _multipointA; + multipoint_b = _multipointB; + } + + Envelope2D env_a = new Envelope2D(); + Envelope2D env_b = new Envelope2D(); + Envelope2D envInter = new Envelope2D(); + multipoint_a.queryEnvelope2D(env_a); + multipoint_b.queryEnvelope2D(env_b); + env_a.inflate(tolerance, tolerance); + env_b.inflate(tolerance, tolerance); + envInter.setCoords(env_a); + envInter.intersect(env_b); + + Point2D ptA = new Point2D(); + Point2D ptB = new Point2D(); + double tolerance_sq = tolerance * tolerance; + + QuadTreeImpl quadTreeB = InternalUtils.buildQuadTree( + (MultiPointImpl) (multipoint_b._getImpl()), envInter); + QuadTreeImpl.QuadTreeIteratorImpl qtIterB = quadTreeB.getIterator(); + + for (int vertex_a = 0; vertex_a < multipoint_a.getPointCount(); vertex_a++) { + multipoint_a.getXY(vertex_a, ptA); + + if (!envInter.contains(ptA)) + continue; + + env_a.setCoords(ptA.x, ptA.y, ptA.x, ptA.y); + qtIterB.resetIterator(env_a, tolerance); + + for (int elementHandleB = qtIterB.next(); elementHandleB != -1; elementHandleB = qtIterB + .next()) { + int vertex_b = quadTreeB.getElement(elementHandleB); + multipoint_b.getXY(vertex_b, ptB); + + if (Point2D.sqrDistance(ptA, ptB) <= tolerance_sq) + return true; + } + } + + return false; + } + + // Returns true if multipathA equals multipathB. + private static boolean linearPathEqualsLinearPath_(MultiPath multipathA, + MultiPath multipathB, double tolerance, boolean bEnforceOrientation) { + return linearPathWithinLinearPath_(multipathA, multipathB, tolerance, bEnforceOrientation) + && linearPathWithinLinearPath_(multipathB, multipathA, + tolerance, bEnforceOrientation); + } + + // Returns true if the segments of multipathA are within the segments of + // multipathB. + private static boolean linearPathWithinLinearPath_(MultiPath multipathA, + MultiPath multipathB, double tolerance, boolean bEnforceOrientation) { + boolean bWithin = true; + double[] scalarsA = new double[2]; + double[] scalarsB = new double[2]; + + int ievent = 0; + AttributeStreamOfInt32 eventIndices = new AttributeStreamOfInt32(0); + RelationalOperations relOps = new RelationalOperations(); + OverlapComparer overlapComparer = new OverlapComparer(relOps); + OverlapEvent overlapEvent; + + Envelope2D env_a = new Envelope2D(); + Envelope2D env_b = new Envelope2D(); + Envelope2D envInter = new Envelope2D(); + multipathA.queryEnvelope2D(env_a); + multipathB.queryEnvelope2D(env_b); + env_a.inflate(tolerance, tolerance); + env_b.inflate(tolerance, tolerance); + envInter.setCoords(env_a); + envInter.intersect(env_b); + + SegmentIteratorImpl segIterA = ((MultiPathImpl) multipathA._getImpl()) + .querySegmentIterator(); + SegmentIteratorImpl segIterB = ((MultiPathImpl) multipathB._getImpl()) + .querySegmentIterator(); + + QuadTreeImpl qtB = null; + QuadTreeImpl quadTreeB = null; + QuadTreeImpl quadTreePathsB = null; + + GeometryAccelerators accel = ((MultiPathImpl) multipathB._getImpl()) + ._getAccelerators(); + + if (accel != null) { + quadTreeB = accel.getQuadTree(); + quadTreePathsB = accel.getQuadTreeForPaths(); + if (quadTreeB == null) { + qtB = InternalUtils.buildQuadTree( + (MultiPathImpl) multipathB._getImpl(), envInter); + quadTreeB = qtB; + } + } else { + qtB = InternalUtils.buildQuadTree( + (MultiPathImpl) multipathB._getImpl(), envInter); + quadTreeB = qtB; + } + + QuadTreeImpl.QuadTreeIteratorImpl qtIterB = quadTreeB.getIterator(); + + QuadTreeImpl.QuadTreeIteratorImpl qtIterPathsB = null; + if (quadTreePathsB != null) + qtIterPathsB = quadTreePathsB.getIterator(); + + while (segIterA.nextPath()) { + while (segIterA.hasNextSegment()) { + boolean bStringOfSegmentAsCovered = false; + + Segment segmentA = segIterA.nextSegment(); + segmentA.queryEnvelope2D(env_a); + + if (!env_a.isIntersecting(envInter)) { + return false; // bWithin = false + } + + if (qtIterPathsB != null) { + qtIterPathsB.resetIterator(env_a, tolerance); + + if (qtIterPathsB.next() == -1) { + bWithin = false; + return false; + } + } + + double lengthA = segmentA.calculateLength2D(); + + qtIterB.resetIterator(segmentA, tolerance); + + for (int elementHandleB = qtIterB.next(); elementHandleB != -1; elementHandleB = qtIterB + .next()) { + int vertex_b = quadTreeB.getElement(elementHandleB); + segIterB.resetToVertex(vertex_b); + Segment segmentB = segIterB.nextSegment(); + + int result = segmentA.intersect(segmentB, null, scalarsA, + scalarsB, tolerance); + + if (result == 2 && (!bEnforceOrientation || scalarsB[0] <= scalarsB[1])) { + double scalar_a_0 = scalarsA[0]; + double scalar_a_1 = scalarsA[1]; + double scalar_b_0 = scalarsB[0]; + double scalar_b_1 = scalarsB[1]; + + // Performance enhancement for nice cases where + // localization occurs. Increment segIterA as far as we + // can while the current segmentA is covered. + if (scalar_a_0 * lengthA <= tolerance + && (1.0 - scalar_a_1) * lengthA <= tolerance) { + bStringOfSegmentAsCovered = true; + + ievent = 0; + eventIndices.resize(0); + relOps.m_overlap_events.clear(); + + int ivertex_a = segIterA.getStartPointIndex(); + boolean bSegmentACovered = true; + + while (bSegmentACovered) {// keep going while the + // current segmentA is + // covered. + if (segIterA.hasNextSegment()) { + segmentA = segIterA.nextSegment(); + lengthA = segmentA.calculateLength2D(); + + result = segmentA.intersect(segmentB, null, + scalarsA, scalarsB, tolerance); + + if (result == 2 && (!bEnforceOrientation || scalarsB[0] <= scalarsB[1])) { + scalar_a_0 = scalarsA[0]; + scalar_a_1 = scalarsA[1]; + + if (scalar_a_0 * lengthA <= tolerance + && (1.0 - scalar_a_1) * lengthA <= tolerance) { + ivertex_a = segIterA + .getStartPointIndex(); + continue; + } + } + + if (segIterB.hasNextSegment()) { + segmentB = segIterB.nextSegment(); + result = segmentA.intersect(segmentB, + null, scalarsA, scalarsB, + tolerance); + + if (result == 2 && (!bEnforceOrientation || scalarsB[0] <= scalarsB[1])) { + scalar_a_0 = scalarsA[0]; + scalar_a_1 = scalarsA[1]; + + if (scalar_a_0 * lengthA <= tolerance + && (1.0 - scalar_a_1) + * lengthA <= tolerance) { + ivertex_a = segIterA + .getStartPointIndex(); + continue; + } + } + } + } + + bSegmentACovered = false; + } + + if (ivertex_a != segIterA.getStartPointIndex()) { + segIterA.resetToVertex(ivertex_a); + segIterA.nextSegment(); + } + + break; + } else { + int ivertex_a = segIterA.getStartPointIndex(); + int ipath_a = segIterA.getPathIndex(); + int ivertex_b = segIterB.getStartPointIndex(); + int ipath_b = segIterB.getPathIndex(); + + overlapEvent = OverlapEvent.construct(ivertex_a, + ipath_a, scalar_a_0, scalar_a_1, ivertex_b, + ipath_b, scalar_b_0, scalar_b_1); + relOps.m_overlap_events.add(overlapEvent); + eventIndices.add(eventIndices.size()); + } + } + } + + if (bStringOfSegmentAsCovered) { + continue; // no need to check that segmentA is covered + } + if (ievent == relOps.m_overlap_events.size()) { + return false; // bWithin = false + } + + if (eventIndices.size() - ievent > 1) { + eventIndices.Sort(ievent, eventIndices.size(), + overlapComparer); + } + + double lastScalar = 0.0; + + for (int i = ievent; i < relOps.m_overlap_events.size(); i++) { + overlapEvent = relOps.m_overlap_events.get(eventIndices + .get(i)); + + if (overlapEvent.m_scalar_a_0 < lastScalar + && overlapEvent.m_scalar_a_1 < lastScalar) { + continue; + } + + if (lengthA * (overlapEvent.m_scalar_a_0 - lastScalar) > tolerance) { + return false; // bWithin = false + } else { + lastScalar = overlapEvent.m_scalar_a_1; + + if (lengthA * (1.0 - lastScalar) <= tolerance + || lastScalar == 1.0) { + break; + } + } + } + + if (lengthA * (1.0 - lastScalar) > tolerance) { + return false; // bWithin = false + } + + ievent = 0; + eventIndices.resize(0); + relOps.m_overlap_events.clear(); + } + } + + return bWithin; + } + + // Returns true if the segments of multipathA overlap the segments of + // multipathB. + private static boolean linearPathOverlapsLinearPath_(MultiPath multipathA, + MultiPath multipathB, double tolerance) { + int dim = linearPathIntersectsLinearPathMaxDim_(multipathA, multipathB, + tolerance, null); + + if (dim < 1) + return false; + + Envelope2D env_a = new Envelope2D(), env_b = new Envelope2D(); + multipathA.queryEnvelope2D(env_a); + multipathB.queryEnvelope2D(env_b); + + boolean bIntAExtB = interiorEnvExteriorEnv_(env_a, env_b, tolerance); + boolean bIntBExtA = interiorEnvExteriorEnv_(env_b, env_a, tolerance); + + if (bIntAExtB && bIntBExtA) + return true; + + if (bIntAExtB && !bIntBExtA) + return !linearPathWithinLinearPath_(multipathB, multipathA, + tolerance, false); + + if (bIntBExtA && !bIntAExtB) + return !linearPathWithinLinearPath_(multipathA, multipathB, + tolerance, false); + + return !linearPathWithinLinearPath_(multipathA, multipathB, tolerance, false) + && !linearPathWithinLinearPath_(multipathB, multipathA, + tolerance, false); + } + + // Returns true the dimension of intersection of _multipathA and + // _multipathB. + static int linearPathIntersectsLinearPathMaxDim_(MultiPath _multipathA, + MultiPath _multipathB, double tolerance, + AttributeStreamOfDbl intersections) { + MultiPath multipathA; + MultiPath multipathB; + + if (_multipathA.getSegmentCount() > _multipathB.getSegmentCount()) { + multipathA = _multipathB; + multipathB = _multipathA; + } else { + multipathA = _multipathA; + multipathB = _multipathB; + } + + SegmentIteratorImpl segIterA = ((MultiPathImpl) multipathA._getImpl()) + .querySegmentIterator(); + SegmentIteratorImpl segIterB = ((MultiPathImpl) multipathB._getImpl()) + .querySegmentIterator(); + double[] scalarsA = new double[2]; + double[] scalarsB = new double[2]; + + int dim = -1; + + int ievent = 0; + double overlapLength; + AttributeStreamOfInt32 eventIndices = new AttributeStreamOfInt32(0); + RelationalOperations relOps = new RelationalOperations(); + OverlapComparer overlapComparer = new OverlapComparer(relOps); + OverlapEvent overlapEvent; + + Envelope2D env_a = new Envelope2D(); + Envelope2D env_b = new Envelope2D(); + Envelope2D envInter = new Envelope2D(); + multipathA.queryEnvelope2D(env_a); + multipathB.queryEnvelope2D(env_b); + env_a.inflate(tolerance, tolerance); + env_b.inflate(tolerance, tolerance); + envInter.setCoords(env_a); + envInter.intersect(env_b); + + Point2D int_point = null; + + if (intersections != null) { + int_point = new Point2D(); + } + + QuadTreeImpl qtB = null; + QuadTreeImpl quadTreeB = null; + QuadTreeImpl quadTreePathsB = null; + + GeometryAccelerators accel = ((MultiPathImpl) multipathB._getImpl()) + ._getAccelerators(); + + if (accel != null) { + quadTreeB = accel.getQuadTree(); + quadTreePathsB = accel.getQuadTreeForPaths(); + if (quadTreeB == null) { + qtB = InternalUtils.buildQuadTree( + (MultiPathImpl) multipathB._getImpl(), envInter); + quadTreeB = qtB; + } + } else { + qtB = InternalUtils.buildQuadTree( + (MultiPathImpl) multipathB._getImpl(), envInter); + quadTreeB = qtB; + } + + QuadTreeImpl.QuadTreeIteratorImpl qtIterB = quadTreeB.getIterator(); + + QuadTreeImpl.QuadTreeIteratorImpl qtIterPathsB = null; + if (quadTreePathsB != null) + qtIterPathsB = quadTreePathsB.getIterator(); + + while (segIterA.nextPath()) { + overlapLength = 0.0; + + while (segIterA.hasNextSegment()) { + Segment segmentA = segIterA.nextSegment(); + segmentA.queryEnvelope2D(env_a); + + if (!env_a.isIntersecting(envInter)) { + continue; + } + + if (qtIterPathsB != null) { + qtIterPathsB.resetIterator(env_a, tolerance); + + if (qtIterPathsB.next() == -1) + continue; + } + + double lengthA = segmentA.calculateLength2D(); + + qtIterB.resetIterator(segmentA, tolerance); + + for (int elementHandleB = qtIterB.next(); elementHandleB != -1; elementHandleB = qtIterB + .next()) { + int vertex_b = quadTreeB.getElement(elementHandleB); + segIterB.resetToVertex(vertex_b); + + Segment segmentB = segIterB.nextSegment(); + double lengthB = segmentB.calculateLength2D(); + + int result = segmentA.intersect(segmentB, null, scalarsA, + scalarsB, tolerance); + + if (result > 0) { + double scalar_a_0 = scalarsA[0]; + double scalar_b_0 = scalarsB[0]; + double scalar_a_1 = (result == 2 ? scalarsA[1] + : NumberUtils.TheNaN); + double scalar_b_1 = (result == 2 ? scalarsB[1] + : NumberUtils.TheNaN); + + if (result == 2) { + if (lengthA * (scalar_a_1 - scalar_a_0) > tolerance) { + dim = 1; + return dim; + } + + // Quick neighbor check + double length = lengthA * (scalar_a_1 - scalar_a_0); + + if (segIterB.hasNextSegment()) { + segmentB = segIterB.nextSegment(); + result = segmentA.intersect(segmentB, null, + scalarsA, null, tolerance); + + if (result == 2) { + double nextScalarA0 = scalarsA[0]; + double nextScalarA1 = scalarsA[1]; + + double lengthNext = lengthA + * (nextScalarA1 - nextScalarA0); + + if (length + lengthNext > tolerance) { + dim = 1; + return dim; + } + } + + segIterB.resetToVertex(vertex_b); + segIterB.nextSegment(); + } + + if (!segIterB.isFirstSegmentInPath()) { + segIterB.previousSegment(); + segmentB = segIterB.previousSegment(); + result = segmentA.intersect(segmentB, null, + scalarsA, null, tolerance); + + if (result == 2) { + double nextScalarA0 = scalarsA[0]; + double nextScalarA1 = scalarsA[1]; + + double lengthPrevious = lengthA + * (nextScalarA1 - nextScalarA0); + + if (length + lengthPrevious > tolerance) { + dim = 1; + return dim; + } + } + + segIterB.resetToVertex(vertex_b); + segIterB.nextSegment(); + } + + if (segIterA.hasNextSegment()) { + int vertex_a = segIterA.getStartPointIndex(); + segmentA = segIterA.nextSegment(); + result = segmentA.intersect(segmentB, null, + scalarsA, null, tolerance); + + if (result == 2) { + double nextScalarA0 = scalarsA[0]; + double nextScalarA1 = scalarsA[1]; + + double lengthNext = lengthA + * (nextScalarA1 - nextScalarA0); + + if (length + lengthNext > tolerance) { + dim = 1; + return dim; + } + } + + segIterA.resetToVertex(vertex_a); + segIterA.nextSegment(); + } + + if (!segIterA.isFirstSegmentInPath()) { + int vertex_a = segIterA.getStartPointIndex(); + segIterA.previousSegment(); + segmentA = segIterA.previousSegment(); + result = segmentA.intersect(segmentB, null, + scalarsA, null, tolerance); + + if (result == 2) { + double nextScalarA0 = scalarsA[0]; + double nextScalarA1 = scalarsA[1]; + + double lengthPrevious = lengthB + * (nextScalarA1 - nextScalarA0); + + if (length + lengthPrevious > tolerance) { + dim = 1; + return dim; + } + } + + segIterA.resetToVertex(vertex_a); + segIterA.nextSegment(); + } + + int ivertex_a = segIterA.getStartPointIndex(); + int ipath_a = segIterA.getPathIndex(); + int ivertex_b = segIterB.getStartPointIndex(); + int ipath_b = segIterB.getPathIndex(); + + overlapEvent = OverlapEvent.construct(ivertex_a, + ipath_a, scalar_a_0, scalar_a_1, ivertex_b, + ipath_b, scalar_b_0, scalar_b_1); + relOps.m_overlap_events.add(overlapEvent); + eventIndices.add(eventIndices.size()); + } + + dim = 0; + + if (intersections != null) { + segmentA.getCoord2D(scalar_a_0, int_point); + intersections.add(int_point.x); + intersections.add(int_point.y); + } + } + } + + if (ievent < relOps.m_overlap_events.size()) { + eventIndices.Sort(ievent, eventIndices.size(), + overlapComparer); + + double lastScalar = 0.0; + int lastPath = relOps.m_overlap_events.get(eventIndices + .get(ievent)).m_ipath_a; + + for (int i = ievent; i < relOps.m_overlap_events.size(); i++) { + overlapEvent = relOps.m_overlap_events.get(eventIndices + .get(i)); + + if (overlapEvent.m_scalar_a_0 < lastScalar + && overlapEvent.m_scalar_a_1 < lastScalar) { + continue; + } + + if (lengthA * (overlapEvent.m_scalar_a_0 - lastScalar) > tolerance) { + overlapLength = lengthA + * (overlapEvent.m_scalar_a_1 - overlapEvent.m_scalar_a_0); // reset + lastScalar = overlapEvent.m_scalar_a_1; + lastPath = overlapEvent.m_ipath_a; + } else { + if (overlapEvent.m_ipath_a != lastPath) { + overlapLength = lengthA + * (overlapEvent.m_scalar_a_1 - overlapEvent.m_scalar_a_0); // reset + lastPath = overlapEvent.m_ipath_a; + } else { + overlapLength += lengthA + * (overlapEvent.m_scalar_a_1 - overlapEvent.m_scalar_a_0); // accumulate + } + if (overlapLength > tolerance) { + dim = 1; + return dim; + } + + lastScalar = overlapEvent.m_scalar_a_1; + + if (lastScalar == 1.0) { + break; + } + } + } + + if (lengthA * (1.0 - lastScalar) > tolerance) { + overlapLength = 0.0; // reset + } + ievent = 0; + eventIndices.resize(0); + relOps.m_overlap_events.clear(); + } + } + } + + return dim; + } + + // Returns true if the line segments of _multipathA intersect the line + // segments of _multipathB. + private static boolean linearPathIntersectsLinearPath_( + MultiPath multipathA, MultiPath multipathB, double tolerance) { + MultiPathImpl multi_path_impl_a = (MultiPathImpl) multipathA._getImpl(); + MultiPathImpl multi_path_impl_b = (MultiPathImpl) multipathB._getImpl(); + + SegmentIteratorImpl segIterA = multi_path_impl_a.querySegmentIterator(); + SegmentIteratorImpl segIterB = multi_path_impl_b.querySegmentIterator(); + + PairwiseIntersectorImpl intersector = new PairwiseIntersectorImpl(multi_path_impl_a, multi_path_impl_b, tolerance, false); + + while (intersector.next()) { + int vertex_a = intersector.getRedElement(); + int vertex_b = intersector.getBlueElement(); + + segIterA.resetToVertex(vertex_a); + segIterB.resetToVertex(vertex_b); + Segment segmentA = segIterA.nextSegment(); + Segment segmentB = segIterB.nextSegment(); + + int result = segmentB.intersect(segmentA, null, null, null, + tolerance); + + if (result > 0) { + return true; + } + } + + return false; + } + + // Returns true if the relation intersects, crosses, or contains holds + // between multipathA and multipoint_b. multipathA is put in the + // Quad_tree_impl. + private static boolean linearPathIntersectsMultiPoint_( + MultiPath multipathA, MultiPoint multipoint_b, double tolerance, + boolean b_intersects_all) { + SegmentIteratorImpl segIterA = ((MultiPathImpl) multipathA._getImpl()) + .querySegmentIterator(); + + boolean bContained = true; + boolean bInteriorHitFound = false; + + Envelope2D env_a = new Envelope2D(); + Envelope2D env_b = new Envelope2D(); + Envelope2D envInter = new Envelope2D(); + multipathA.queryEnvelope2D(env_a); + multipoint_b.queryEnvelope2D(env_b); + env_a.inflate(tolerance, tolerance); + + if (!env_a.contains(env_b)) { + bContained = false; + } + + env_b.inflate(tolerance, tolerance); + envInter.setCoords(env_a); + envInter.intersect(env_b); + + QuadTreeImpl qtA = null; + QuadTreeImpl quadTreeA = null; + QuadTreeImpl quadTreePathsA = null; + + GeometryAccelerators accel = ((MultiPathImpl) multipathA._getImpl()) + ._getAccelerators(); + + if (accel != null) { + quadTreeA = accel.getQuadTree(); + if (quadTreeA == null) { + qtA = InternalUtils.buildQuadTree( + (MultiPathImpl) multipathA._getImpl(), envInter); + quadTreeA = qtA; + } + } else { + qtA = InternalUtils.buildQuadTree( + (MultiPathImpl) multipathA._getImpl(), envInter); + quadTreeA = qtA; + } + + QuadTreeImpl.QuadTreeIteratorImpl qtIterA = quadTreeA.getIterator(); + + QuadTreeImpl.QuadTreeIteratorImpl qtIterPathsA = null; + if (quadTreePathsA != null) + qtIterPathsA = quadTreePathsA.getIterator(); + + Point2D ptB = new Point2D(), closest = new Point2D(); + boolean b_intersects = false; + double toleranceSq = tolerance * tolerance; + + for (int i = 0; i < multipoint_b.getPointCount(); i++) { + multipoint_b.getXY(i, ptB); + + if (!envInter.contains(ptB)) { + continue; + } + + env_b.setCoords(ptB.x, ptB.y, ptB.x, ptB.y); + + if (qtIterPathsA != null) { + qtIterPathsA.resetIterator(env_b, tolerance); + + if (qtIterPathsA.next() == -1) + continue; + } + + qtIterA.resetIterator(env_b, tolerance); + + boolean b_covered = false; + + for (int elementHandleA = qtIterA.next(); elementHandleA != -1; elementHandleA = qtIterA + .next()) { + int vertex_a = quadTreeA.getElement(elementHandleA); + segIterA.resetToVertex(vertex_a); + Segment segmentA = segIterA.nextSegment(); + + double t = segmentA.getClosestCoordinate(ptB, false); + segmentA.getCoord2D(t, closest); + + if (Point2D.sqrDistance(closest, ptB) <= toleranceSq) { + b_covered = true; + break; + } + } + + if (b_intersects_all) { + if (!b_covered) { + return false; + } + } else { + if (b_covered) { + return true; + } + } + } + + if (b_intersects_all) { + return true; + } + + return false; + } + + // Returns true if a segment of multipathA intersects point_b. + static boolean linearPathIntersectsPoint_(MultiPath multipathA, + Point2D ptB, double tolerance) { + Point2D closest = new Point2D(); + double toleranceSq = tolerance * tolerance; + SegmentIteratorImpl segIterA = ((MultiPathImpl) multipathA._getImpl()) + .querySegmentIterator(); + + GeometryAccelerators accel = ((MultiPathImpl) multipathA._getImpl()) + ._getAccelerators(); + + if (accel != null) { + QuadTreeImpl quadTreeA = accel.getQuadTree(); + if (quadTreeA != null) { + Envelope2D env_b = new Envelope2D(); + env_b.setCoords(ptB); + + QuadTreeImpl.QuadTreeIteratorImpl qt_iter = quadTreeA + .getIterator(env_b, tolerance); + + for (int e = qt_iter.next(); e != -1; e = qt_iter.next()) { + segIterA.resetToVertex(quadTreeA.getElement(e)); + + if (segIterA.hasNextSegment()) { + Segment segmentA = segIterA.nextSegment(); + + double t = segmentA.getClosestCoordinate(ptB, false); + segmentA.getCoord2D(t, closest); + + if (Point2D.sqrDistance(ptB, closest) <= toleranceSq) { + return true; + } + } + } + + return false; + } + } + Envelope2D env_a = new Envelope2D(); + + while (segIterA.nextPath()) { + while (segIterA.hasNextSegment()) { + Segment segmentA = segIterA.nextSegment(); + segmentA.queryEnvelope2D(env_a); + env_a.inflate(tolerance, tolerance); + + if (!env_a.contains(ptB)) { + continue; + } + + double t = segmentA.getClosestCoordinate(ptB, false); + segmentA.getCoord2D(t, closest); + + if (Point2D.sqrDistance(ptB, closest) <= toleranceSq) { + return true; + } + } + } + + return false; + } + + private static boolean linearPathContainsPoint_(MultiPath multipathA, + Point2D pt_b, double tolerance) { + return linearPathIntersectsPoint_(multipathA, pt_b, tolerance) + && !linearPathTouchesPointImpl_(multipathA, pt_b, tolerance); + } + + private static boolean linearPathTouchesPointImpl_(MultiPath multipathA, + Point2D ptB, double tolerance) { + MultiPoint boundary = (MultiPoint) (multipathA.getBoundary()); + return !multiPointDisjointPointImpl_(boundary, ptB, tolerance, null); + } + + // Returns true if the segments of multipathA intersects env_b + private static boolean linearPathIntersectsEnvelope_(MultiPath multipath_a, + Envelope2D env_b, double tolerance, ProgressTracker progress_tracker) { + if (!multipath_a.hasNonLinearSegments()) { + Envelope2D env_b_inflated = new Envelope2D(); + env_b_inflated.setCoords(env_b); + env_b_inflated.inflate(tolerance, tolerance); + MultiPathImpl mimpl_a = (MultiPathImpl) multipath_a._getImpl(); + AttributeStreamOfDbl xy = (AttributeStreamOfDbl) (mimpl_a + .getAttributeStreamRef(VertexDescription.Semantics.POSITION)); + Point2D pt = new Point2D(); + Point2D pt_prev = new Point2D(); + Point2D pt_1 = new Point2D(); + Point2D pt_2 = new Point2D(); + for (int ipath = 0, npath = mimpl_a.getPathCount(); ipath < npath; ipath++) { + boolean b_first = true; + for (int i = mimpl_a.getPathStart(ipath), n = mimpl_a + .getPathEnd(ipath); i < n; i++) { + if (b_first) { + xy.read(2 * i, pt_prev); + b_first = false; + continue; + } + + xy.read(2 * i, pt); + pt_1.setCoords(pt_prev); + pt_2.setCoords(pt); + if (env_b_inflated.clipLine(pt_1, pt_2) != 0) + return true; + + pt_prev.setCoords(pt); + } + } + } else { + Line line_1 = new Line(env_b.xmin, env_b.ymin, env_b.xmin, + env_b.ymax); + Line line_2 = new Line(env_b.xmin, env_b.ymax, env_b.xmax, + env_b.ymax); + Line line3 = new Line(env_b.xmax, env_b.ymax, env_b.xmax, + env_b.ymin); + Line line4 = new Line(env_b.xmax, env_b.ymin, env_b.xmin, + env_b.ymin); + SegmentIterator iter = multipath_a.querySegmentIterator(); + while (iter.nextPath()) { + while (iter.hasNextSegment()) { + Segment polySeg = iter.nextSegment(); + if (polySeg.isIntersecting(line_1, tolerance)) + return true; + + if (polySeg.isIntersecting(line_2, tolerance)) + return true; + + if (polySeg.isIntersecting(line3, tolerance)) + return true; + + if (polySeg.isIntersecting(line4, tolerance)) + return true; + } + } + } + + return false; + } + + // Returns contains, disjoint, or within if the relationship can be + // determined from the rasterized tests. + // When bExtraTestForIntersects is true performs extra tests and can return + // "intersects". + static int tryRasterizedContainsOrDisjoint_(Geometry geom_a, + Geometry geom_b, double tolerance, boolean bExtraTestForIntersects) { + int gtA = geom_a.getType().value(); + int gtB = geom_b.getType().value(); + do { + if (Geometry.isMultiVertex(gtA)) { + MultiVertexGeometryImpl impl = (MultiVertexGeometryImpl) geom_a + ._getImpl(); + GeometryAccelerators accel = impl._getAccelerators(); + if (accel != null) { + RasterizedGeometry2D rgeom = accel.getRasterizedGeometry(); + if (rgeom != null) { + if (gtB == Geometry.GeometryType.Point) { + Point2D ptB = ((Point) geom_b).getXY(); + RasterizedGeometry2D.HitType hit = rgeom + .queryPointInGeometry(ptB.x, ptB.y); + if (hit == RasterizedGeometry2D.HitType.Inside) { + return Relation.contains; + } else if (hit == RasterizedGeometry2D.HitType.Outside) { + return Relation.disjoint; + } + break; + } + Envelope2D env_b = new Envelope2D(); + geom_b.queryEnvelope2D(env_b); + RasterizedGeometry2D.HitType hit = rgeom + .queryEnvelopeInGeometry(env_b); + if (hit == RasterizedGeometry2D.HitType.Inside) { + return Relation.contains; + } else if (hit == RasterizedGeometry2D.HitType.Outside) { + return Relation.disjoint; + } else if (bExtraTestForIntersects + && Geometry.isMultiVertex(gtB)) { + if (checkVerticesForIntersection_( + (MultiVertexGeometryImpl) geom_b._getImpl(), + rgeom)) { + return Relation.intersects; + } + } + + break; + } + } + } + } while (false); + + do { + if (Geometry.isMultiVertex(gtB)) { + MultiVertexGeometryImpl impl = (MultiVertexGeometryImpl) geom_b + ._getImpl(); + GeometryAccelerators accel = impl._getAccelerators(); + if (accel != null) { + RasterizedGeometry2D rgeom = accel.getRasterizedGeometry(); + if (rgeom != null) { + if (gtA == Geometry.GeometryType.Point) { + Point2D ptA = ((Point) geom_a).getXY(); + RasterizedGeometry2D.HitType hit = rgeom + .queryPointInGeometry(ptA.x, ptA.y); + if (hit == RasterizedGeometry2D.HitType.Inside) { + return Relation.within; + } else if (hit == RasterizedGeometry2D.HitType.Outside) { + return Relation.disjoint; + } + break; + } + + Envelope2D env_a = new Envelope2D(); + geom_a.queryEnvelope2D(env_a); + RasterizedGeometry2D.HitType hit = rgeom + .queryEnvelopeInGeometry(env_a); + if (hit == RasterizedGeometry2D.HitType.Inside) { + return Relation.within; + } else if (hit == RasterizedGeometry2D.HitType.Outside) { + return Relation.disjoint; + } else if (bExtraTestForIntersects + && Geometry.isMultiVertex(gtA)) { + if (checkVerticesForIntersection_( + (MultiVertexGeometryImpl) geom_a._getImpl(), + rgeom)) { + return Relation.intersects; + } + } + + break; + } + } + } + } while (false); + + return Relation.unknown; + } + + // Returns true if intersects and false if nothing can be determined. + private static boolean checkVerticesForIntersection_( + MultiVertexGeometryImpl geom, RasterizedGeometry2D rgeom) { + // Do a quick raster test for each point. If any point is inside, then + // there is an intersection. + int pointCount = geom.getPointCount(); + Point2D pt = new Point2D(); + for (int ipoint = 0; ipoint < pointCount; ipoint++) { + geom.getXY(ipoint, pt); + RasterizedGeometry2D.HitType hit = rgeom.queryPointInGeometry(pt.x, + pt.y); + if (hit == RasterizedGeometry2D.HitType.Inside) { + return true; + } + } + + return false; + } + + private static boolean polygonTouchesPolygonImpl_(Polygon polygon_a, + Polygon polygon_b, double tolerance, ProgressTracker progressTracker) { + MultiPathImpl polygon_impl_a = (MultiPathImpl) polygon_a._getImpl(); + MultiPathImpl polygon_impl_b = (MultiPathImpl) polygon_b._getImpl(); + + // double geom_tolerance; + boolean b_geometries_simple = polygon_impl_a.getIsSimple(0.0) >= 1 + && polygon_impl_b.getIsSimple(0.0) >= 1; + + SegmentIteratorImpl segIterA = polygon_impl_a.querySegmentIterator(); + SegmentIteratorImpl segIterB = polygon_impl_b.querySegmentIterator(); + double[] scalarsA = new double[2]; + double[] scalarsB = new double[2]; + + PairwiseIntersectorImpl intersector = new PairwiseIntersectorImpl( + polygon_impl_a, polygon_impl_b, tolerance, false); + + boolean b_boundaries_intersect = false; + + while (intersector.next()) { + int vertex_a = intersector.getRedElement(); + int vertex_b = intersector.getBlueElement(); + + segIterA.resetToVertex(vertex_a); + segIterB.resetToVertex(vertex_b); + Segment segmentA = segIterA.nextSegment(); + Segment segmentB = segIterB.nextSegment(); + + int result = segmentB.intersect(segmentA, null, scalarsB, scalarsA, + tolerance); + + if (result == 2) { + double scalar_a_0 = scalarsA[0]; + double scalar_a_1 = scalarsA[1]; + double length_a = segmentA.calculateLength2D(); + + if (b_geometries_simple + && (scalar_a_1 - scalar_a_0) * length_a > tolerance) { + // If the line segments overlap along the same direction, + // then we have an Interior-Interior intersection + return false; + } + + b_boundaries_intersect = true; + } else if (result != 0) { + double scalar_a_0 = scalarsA[0]; + double scalar_b_0 = scalarsB[0]; + + if (scalar_a_0 > 0.0 && scalar_a_0 < 1.0 && scalar_b_0 > 0.0 + && scalar_b_0 < 1.0) { + return false; + } + + b_boundaries_intersect = true; + } + } + + if (!b_boundaries_intersect) { + return false; + } + + Envelope2D env_a = new Envelope2D(), env_b = new Envelope2D(), envInter = new Envelope2D(); + polygon_a.queryEnvelope2D(env_a); + polygon_b.queryEnvelope2D(env_b); + env_a.inflate(1000.0 * tolerance, 1000.0 * tolerance); + env_b.inflate(1000.0 * tolerance, 1000.0 * tolerance); + envInter.setCoords(env_a); + envInter.intersect(env_b); + + Polygon _polygonA; + Polygon _polygonB; + + if (polygon_a.getPointCount() > 10) { + _polygonA = (Polygon) (Clipper.clip(polygon_a, envInter, tolerance, + 0.0)); + if (_polygonA.isEmpty()) { + return false; + } + } else { + _polygonA = polygon_a; + } + + if (polygon_b.getPointCount() > 10) { + _polygonB = (Polygon) (Clipper.clip(polygon_b, envInter, tolerance, + 0.0)); + if (_polygonB.isEmpty()) { + return false; + } + } else { + _polygonB = polygon_b; + } + + // We just need to determine whether interior_interior is false + String scl = "F********"; + boolean bRelation = RelationalOperationsMatrix.polygonRelatePolygon_( + _polygonA, _polygonB, tolerance, scl, progressTracker); + + return bRelation; + } + + private static boolean polygonOverlapsPolygonImpl_(Polygon polygon_a, + Polygon polygon_b, double tolerance, ProgressTracker progressTracker) { + MultiPathImpl polygon_impl_a = (MultiPathImpl) polygon_a._getImpl(); + MultiPathImpl polygon_impl_b = (MultiPathImpl) polygon_b._getImpl(); + + // double geom_tolerance; + boolean b_geometries_simple = polygon_impl_a.getIsSimple(0.0) >= 1 + && polygon_impl_b.getIsSimple(0.0) >= 1; + + Envelope2D env_a = new Envelope2D(), env_b = new Envelope2D(), envInter = new Envelope2D(); + polygon_a.queryEnvelope2D(env_a); + polygon_b.queryEnvelope2D(env_b); + + boolean bInteriorIntersectionKnown = false; + + boolean bIntAExtB = interiorEnvExteriorEnv_(env_a, env_b, tolerance); + boolean bExtAIntB = interiorEnvExteriorEnv_(env_b, env_a, tolerance); + + SegmentIteratorImpl segIterA = polygon_impl_a.querySegmentIterator(); + SegmentIteratorImpl segIterB = polygon_impl_b.querySegmentIterator(); + double[] scalarsA = new double[2]; + double[] scalarsB = new double[2]; + + PairwiseIntersectorImpl intersector = new PairwiseIntersectorImpl( + polygon_impl_a, polygon_impl_b, tolerance, false); + + while (intersector.next()) { + int vertex_a = intersector.getRedElement(); + int vertex_b = intersector.getBlueElement(); + + segIterA.resetToVertex(vertex_a); + segIterB.resetToVertex(vertex_b); + Segment segmentA = segIterA.nextSegment(); + Segment segmentB = segIterB.nextSegment(); + + int result = segmentB.intersect(segmentA, null, scalarsB, scalarsA, + tolerance); + + if (result == 2) { + double scalar_a_0 = scalarsA[0]; + double scalar_a_1 = scalarsA[1]; + double length_a = segmentA.calculateLength2D(); + + if (b_geometries_simple + && (scalar_a_1 - scalar_a_0) * length_a > tolerance) { + // When the line segments intersect along the same + // direction, then we have an interior-interior intersection + bInteriorIntersectionKnown = true; + + if (bIntAExtB && bExtAIntB) { + return true; + } + } + } else if (result != 0) { + double scalar_a_0 = scalarsA[0]; + double scalar_b_0 = scalarsB[0]; + + if (scalar_a_0 > 0.0 && scalar_a_0 < 1.0 && scalar_b_0 > 0.0 + && scalar_b_0 < 1.0) { + return true; + } + } + } + + Envelope2D envAInflated = new Envelope2D(), envBInflated = new Envelope2D(); + envAInflated.setCoords(env_a); + envAInflated.inflate(1000.0 * tolerance, 1000.0 * tolerance); + envBInflated.setCoords(env_b); + envBInflated.inflate(1000.0 * tolerance, 1000.0 * tolerance); + + envInter.setCoords(envAInflated); + envInter.intersect(envBInflated); + + Polygon _polygonA; + Polygon _polygonB; + StringBuilder scl = new StringBuilder(); + + if (!bInteriorIntersectionKnown) { + scl.append("T*"); + } else { + scl.append("**"); + } + + if (bIntAExtB) { + if (polygon_b.getPointCount() > 10) { + _polygonB = (Polygon) (Clipper.clip(polygon_b, envInter, + tolerance, 0.0)); + if (_polygonB.isEmpty()) { + return false; + } + } else { + _polygonB = polygon_b; + } + + scl.append("****"); + } else { + _polygonB = polygon_b; + scl.append("T***"); + } + + if (bExtAIntB) { + if (polygon_a.getPointCount() > 10) { + _polygonA = (Polygon) (Clipper.clip(polygon_a, envInter, + tolerance, 0.0)); + if (_polygonA.isEmpty()) { + return false; + } + } else { + _polygonA = polygon_a; + } + + scl.append("***"); + } else { + _polygonA = polygon_a; + scl.append("T**"); + } + + boolean bRelation = RelationalOperationsMatrix.polygonRelatePolygon_( + _polygonA, _polygonB, tolerance, scl.toString(), + progressTracker); + return bRelation; + } + + private static boolean polygonContainsPolygonImpl_(Polygon polygon_a, + Polygon polygon_b, double tolerance, ProgressTracker progressTracker) { + boolean[] b_result_known = new boolean[1]; + b_result_known[0] = false; + boolean res = polygonContainsMultiPath_(polygon_a, polygon_b, tolerance, b_result_known, progressTracker); + + if (b_result_known[0]) + return res; + + // We can clip polygon_a to the extent of polyline_b + + Envelope2D envBInflated = new Envelope2D(); + polygon_b.queryEnvelope2D(envBInflated); + envBInflated.inflate(1000.0 * tolerance, 1000.0 * tolerance); + + Polygon _polygonA = null; + + if (polygon_a.getPointCount() > 10) { + _polygonA = (Polygon) Clipper.clip(polygon_a, envBInflated, tolerance, 0.0); + if (_polygonA.isEmpty()) + return false; + } else { + _polygonA = polygon_a; + } + + boolean bContains = RelationalOperationsMatrix.polygonContainsPolygon_(_polygonA, polygon_b, tolerance, progressTracker); + return bContains; + } + + private static boolean polygonContainsMultiPath_(Polygon polygon_a, MultiPath multi_path_b, double tolerance, boolean[] b_result_known, ProgressTracker progress_tracker) { + b_result_known[0] = false; + + MultiPathImpl polygon_impl_a = (MultiPathImpl) polygon_a._getImpl(); + MultiPathImpl multi_path_impl_b = (MultiPathImpl) multi_path_b._getImpl(); + + SegmentIteratorImpl segIterA = polygon_impl_a.querySegmentIterator(); + SegmentIteratorImpl segIterB = multi_path_impl_b.querySegmentIterator(); + double[] scalarsA = new double[2]; + double[] scalarsB = new double[2]; + + PairwiseIntersectorImpl intersector = new PairwiseIntersectorImpl(polygon_impl_a, multi_path_impl_b, tolerance, false); + boolean b_boundaries_intersect = false; + + while (intersector.next()) { + int vertex_a = intersector.getRedElement(); + int vertex_b = intersector.getBlueElement(); + + segIterA.resetToVertex(vertex_a, -1); + segIterB.resetToVertex(vertex_b, -1); + Segment segmentA = segIterA.nextSegment(); + Segment segmentB = segIterB.nextSegment(); + + int result = segmentB.intersect(segmentA, null, scalarsB, scalarsA, tolerance); + + if (result != 0) { + b_boundaries_intersect = true; + if (result == 1) { + double scalar_a_0 = scalarsA[0]; + double scalar_b_0 = scalarsB[0]; + + if (scalar_a_0 > 0.0 && scalar_a_0 < 1.0 && scalar_b_0 > 0.0 && scalar_b_0 < 1.0) { + b_result_known[0] = true; + return false; + } + } + } + } + + if (!b_boundaries_intersect) { + b_result_known[0] = true; + + //boundaries do not intersect + + Envelope2D env_a_inflated = new Envelope2D(); + polygon_a.queryEnvelope2D(env_a_inflated); + env_a_inflated.inflate(tolerance, tolerance); + + Polygon pa = null; + Polygon p_polygon_a = polygon_a; + + boolean b_checked_polygon_a_quad_tree = false; + + Envelope2D path_env_b = new Envelope2D(); + + for (int ipath = 0, npath = multi_path_b.getPathCount(); ipath < npath; ipath++) { + if (multi_path_b.getPathSize(ipath) > 0) { + multi_path_b.queryPathEnvelope2D(ipath, path_env_b); + + if (env_a_inflated.isIntersecting(path_env_b)) { + Point2D anyPoint = multi_path_b.getXY(multi_path_b.getPathStart(ipath)); + int res = PointInPolygonHelper.isPointInPolygon(p_polygon_a, anyPoint, 0); + if (res == 0) + return false; + } else { + return false; + } + + if (!b_checked_polygon_a_quad_tree) { + if (PointInPolygonHelper.quadTreeWillHelp(polygon_a, multi_path_b.getPathCount() - 1) && (polygon_impl_a._getAccelerators() == null || polygon_impl_a._getAccelerators().getQuadTree() == null)) { + pa = new Polygon(); + polygon_a.copyTo(pa); + ((MultiPathImpl) pa._getImpl())._buildQuadTreeAccelerator(Geometry.GeometryAccelerationDegree.enumMedium); + p_polygon_a = pa; + } else { + p_polygon_a = polygon_a; + } + + b_checked_polygon_a_quad_tree = true; + } + } + } + + if (polygon_a.getPathCount() == 1 || multi_path_b.getType().value() == Geometry.GeometryType.Polyline) + return true; //boundaries do not intersect. all paths of b are inside of a + + // Polygon A has multiple rings, and Multi_path B is a polygon. + + Polygon polygon_b = (Polygon) multi_path_b; + + Envelope2D env_b_inflated = new Envelope2D(); + polygon_b.queryEnvelope2D(env_b_inflated); + env_b_inflated.inflate(tolerance, tolerance); + + Polygon pb = null; + Polygon p_polygon_b = polygon_b; + + boolean b_checked_polygon_b_quad_tree = false; + + Envelope2D path_env_a = new Envelope2D(); + + for (int ipath = 0, npath = polygon_a.getPathCount(); ipath < npath; ipath++) { + if (polygon_a.getPathSize(ipath) > 0) { + polygon_a.queryPathEnvelope2D(ipath, path_env_a); + + if (env_b_inflated.isIntersecting(path_env_a)) { + Point2D anyPoint = polygon_a.getXY(polygon_a.getPathStart(ipath)); + int res = PointInPolygonHelper.isPointInPolygon(p_polygon_b, anyPoint, 0); + if (res == 1) + return false; + } + + if (!b_checked_polygon_b_quad_tree) { + if (PointInPolygonHelper.quadTreeWillHelp(polygon_b, polygon_a.getPathCount() - 1) && (multi_path_impl_b._getAccelerators() == null || multi_path_impl_b._getAccelerators().getQuadTree() == null)) { + pb = new Polygon(); + polygon_b.copyTo(pb); + ((MultiPathImpl) pb._getImpl())._buildQuadTreeAccelerator(Geometry.GeometryAccelerationDegree.enumMedium); + p_polygon_b = pb; + } else { + p_polygon_b = polygon_b; + } + + b_checked_polygon_b_quad_tree = true; + } + } + } + + return true; + } + + return false; + } + + private static boolean polygonTouchesPolylineImpl_(Polygon polygon_a, + Polyline polyline_b, double tolerance, + ProgressTracker progressTracker) { + MultiPathImpl polygon_impl_a = (MultiPathImpl) polygon_a._getImpl(); + MultiPathImpl polyline_impl_b = (MultiPathImpl) polyline_b._getImpl(); + + SegmentIteratorImpl segIterA = polygon_impl_a.querySegmentIterator(); + SegmentIteratorImpl segIterB = polyline_impl_b.querySegmentIterator(); + double[] scalarsA = new double[2]; + double[] scalarsB = new double[2]; + + PairwiseIntersectorImpl intersector = new PairwiseIntersectorImpl( + polygon_impl_a, polyline_impl_b, tolerance, false); + + boolean b_boundaries_intersect = false; + + while (intersector.next()) { + int vertex_a = intersector.getRedElement(); + int vertex_b = intersector.getBlueElement(); + + segIterA.resetToVertex(vertex_a); + segIterB.resetToVertex(vertex_b); + Segment segmentA = segIterA.nextSegment(); + Segment segmentB = segIterB.nextSegment(); + + int result = segmentB.intersect(segmentA, null, scalarsB, scalarsA, + tolerance); + + if (result == 2) { + b_boundaries_intersect = true; + } else if (result != 0) { + double scalar_a_0 = scalarsA[0]; + double scalar_b_0 = scalarsB[0]; + + if (scalar_a_0 > 0.0 && scalar_a_0 < 1.0 && scalar_b_0 > 0.0 + && scalar_b_0 < 1.0) { + return false; + } + + b_boundaries_intersect = true; + } + } + + if (!b_boundaries_intersect) { + return false; + } + + Envelope2D env_a = new Envelope2D(), env_b = new Envelope2D(), envInter = new Envelope2D(); + polygon_a.queryEnvelope2D(env_a); + polyline_b.queryEnvelope2D(env_b); + env_a.inflate(1000.0 * tolerance, 1000.0 * tolerance); + env_b.inflate(1000.0 * tolerance, 1000.0 * tolerance); + envInter.setCoords(env_a); + envInter.intersect(env_b); + + Polygon _polygonA; + Polyline _polylineB; + + if (polygon_a.getPointCount() > 10) { + _polygonA = (Polygon) (Clipper.clip(polygon_a, envInter, tolerance, + 0.0)); + if (_polygonA.isEmpty()) { + return false; + } + } else { + _polygonA = polygon_a; + } + + if (polyline_b.getPointCount() > 10) { + _polylineB = (Polyline) Clipper.clip(polyline_b, envInter, + tolerance, 0.0); + if (_polylineB.isEmpty()) { + return false; + } + } else { + _polylineB = polyline_b; + } + + // We just need to determine that interior_interior is false + String scl = "F********"; + boolean bRelation = RelationalOperationsMatrix.polygonRelatePolyline_( + _polygonA, _polylineB, tolerance, scl, progressTracker); + + return bRelation; + } + + private static boolean polygonCrossesPolylineImpl_(Polygon polygon_a, + Polyline polyline_b, double tolerance, + ProgressTracker progressTracker) { + MultiPathImpl polygon_impl_a = (MultiPathImpl) polygon_a._getImpl(); + MultiPathImpl polyline_impl_b = (MultiPathImpl) polyline_b._getImpl(); + + SegmentIteratorImpl segIterA = polygon_impl_a.querySegmentIterator(); + SegmentIteratorImpl segIterB = polyline_impl_b.querySegmentIterator(); + double[] scalarsA = new double[2]; + double[] scalarsB = new double[2]; + + PairwiseIntersectorImpl intersector = new PairwiseIntersectorImpl( + polygon_impl_a, polyline_impl_b, tolerance, false); + + boolean b_boundaries_intersect = false; + + while (intersector.next()) { + int vertex_a = intersector.getRedElement(); + int vertex_b = intersector.getBlueElement(); + + segIterA.resetToVertex(vertex_a); + segIterB.resetToVertex(vertex_b); + Segment segmentA = segIterA.nextSegment(); + Segment segmentB = segIterB.nextSegment(); + + int result = segmentB.intersect(segmentA, null, scalarsB, scalarsA, + tolerance); + + if (result == 2) { + b_boundaries_intersect = true; + } else if (result != 0) { + double scalar_a_0 = scalarsA[0]; + double scalar_b_0 = scalarsB[0]; + + if (scalar_a_0 > 0.0 && scalar_a_0 < 1.0 && scalar_b_0 > 0.0 + && scalar_b_0 < 1.0) { + return true; + } + + b_boundaries_intersect = true; + } + } + + if (!b_boundaries_intersect) { + return false; + } + + Envelope2D env_a = new Envelope2D(), env_b = new Envelope2D(), envAInflated = new Envelope2D(), envBInflated = new Envelope2D(), envInter = new Envelope2D(); + polygon_a.queryEnvelope2D(env_a); + polyline_b.queryEnvelope2D(env_b); + + if (interiorEnvExteriorEnv_(env_b, env_a, tolerance)) { + envAInflated.setCoords(env_a); + envAInflated.inflate(1000.0 * tolerance, 1000.0 * tolerance); + envBInflated.setCoords(env_b); + envBInflated.inflate(1000.0 * tolerance, 1000.0 * tolerance); + envInter.setCoords(envAInflated); + envInter.intersect(envBInflated); + + Polygon _polygonA; + Polyline _polylineB; + + if (polygon_a.getPointCount() > 10) { + _polygonA = (Polygon) (Clipper.clip(polygon_a, envInter, + tolerance, 0.0)); + if (_polygonA.isEmpty()) { + return false; + } + } else { + _polygonA = polygon_a; + } + + if (polyline_b.getPointCount() > 10) { + _polylineB = (Polyline) (Clipper.clip(polyline_b, envInter, + tolerance, 0.0)); + if (_polylineB.isEmpty()) { + return false; + } + } else { + _polylineB = polyline_b; + } + + String scl = "T********"; + boolean bRelation = RelationalOperationsMatrix + .polygonRelatePolyline_(_polygonA, _polylineB, tolerance, + scl, progressTracker); + return bRelation; + } + + String scl = "T*****T**"; + boolean bRelation = RelationalOperationsMatrix.polygonRelatePolyline_( + polygon_a, polyline_b, tolerance, scl, progressTracker); + + return bRelation; + } + + private static boolean polygonContainsPolylineImpl_(Polygon polygon_a, + Polyline polyline_b, double tolerance, + ProgressTracker progress_tracker) { + boolean[] b_result_known = new boolean[1]; + b_result_known[0] = false; + boolean res = polygonContainsMultiPath_(polygon_a, polyline_b, tolerance, b_result_known, progress_tracker); + + if (b_result_known[0]) + return res; + + // We can clip polygon_a to the extent of polyline_b + + Envelope2D envBInflated = new Envelope2D(); + polyline_b.queryEnvelope2D(envBInflated); + envBInflated.inflate(1000.0 * tolerance, 1000.0 * tolerance); + + Polygon _polygonA = null; + + if (polygon_a.getPointCount() > 10) { + _polygonA = (Polygon) Clipper.clip(polygon_a, envBInflated, tolerance, 0.0); + if (_polygonA.isEmpty()) + return false; + } else { + _polygonA = polygon_a; + } + + boolean bContains = RelationalOperationsMatrix.polygonContainsPolyline_(_polygonA, polyline_b, tolerance, progress_tracker); + return bContains; + } + + private static boolean polygonContainsPointImpl_(Polygon polygon_a, + Point2D pt_b, double tolerance, ProgressTracker progressTracker) { + PolygonUtils.PiPResult result = PolygonUtils.isPointInPolygon2D( + polygon_a, pt_b, tolerance); + + if (result == PolygonUtils.PiPResult.PiPInside) + return true; + + return false; + } + + private static boolean polygonTouchesPointImpl_(Polygon polygon_a, + Point2D pt_b, double tolerance, ProgressTracker progressTracker) { + PolygonUtils.PiPResult result = PolygonUtils.isPointInPolygon2D( + polygon_a, pt_b, tolerance); + + if (result == PolygonUtils.PiPResult.PiPBoundary) + return true; + + return false; + } + + static boolean multiPointDisjointPointImpl_(MultiPoint multipoint_a, + Point2D pt_b, double tolerance, ProgressTracker progressTracker) { + Point2D pt_a = new Point2D(); + double tolerance_sq = tolerance * tolerance; + + for (int i = 0; i < multipoint_a.getPointCount(); i++) { + multipoint_a.getXY(i, pt_a); + + if (Point2D.sqrDistance(pt_a, pt_b) <= tolerance_sq) + return false; + } + + return true; + } + + private static final class OverlapEvent { + int m_ivertex_a; + int m_ipath_a; + double m_scalar_a_0; + double m_scalar_a_1; + int m_ivertex_b; + int m_ipath_b; + double m_scalar_b_0; + double m_scalar_b_1; + + static OverlapEvent construct(int ivertex_a, int ipath_a, + double scalar_a_0, double scalar_a_1, int ivertex_b, + int ipath_b, double scalar_b_0, double scalar_b_1) { + OverlapEvent overlapEvent = new OverlapEvent(); + overlapEvent.m_ivertex_a = ivertex_a; + overlapEvent.m_ipath_a = ipath_a; + overlapEvent.m_scalar_a_0 = scalar_a_0; + overlapEvent.m_scalar_a_1 = scalar_a_1; + overlapEvent.m_ivertex_b = ivertex_b; + overlapEvent.m_ipath_b = ipath_b; + overlapEvent.m_scalar_b_0 = scalar_b_0; + overlapEvent.m_scalar_b_1 = scalar_b_1; + return overlapEvent; + } + } + + ArrayList m_overlap_events; + + private RelationalOperations() { + m_overlap_events = new ArrayList(); + } + + private static class OverlapComparer extends + AttributeStreamOfInt32.IntComparator { + OverlapComparer(RelationalOperations rel_ops) { + m_rel_ops = rel_ops; + } + + @Override + public int compare(int o_1, int o_2) { + return m_rel_ops.compareOverlapEvents_(o_1, o_2); + } + + private RelationalOperations m_rel_ops; + } + + int compareOverlapEvents_(int o_1, int o_2) { + OverlapEvent overlapEvent1 = m_overlap_events.get(o_1); + OverlapEvent overlapEvent2 = m_overlap_events.get(o_2); + + if (overlapEvent1.m_ipath_a < overlapEvent2.m_ipath_a) + return -1; + + if (overlapEvent1.m_ipath_a == overlapEvent2.m_ipath_a) { + if (overlapEvent1.m_ivertex_a < overlapEvent2.m_ivertex_a) + return -1; + + if (overlapEvent1.m_ivertex_a == overlapEvent2.m_ivertex_a) { + if (overlapEvent1.m_scalar_a_0 < overlapEvent2.m_scalar_a_0) + return -1; + + if (overlapEvent1.m_scalar_a_0 == overlapEvent2.m_scalar_a_0) { + if (overlapEvent1.m_scalar_a_1 < overlapEvent2.m_scalar_a_1) + return -1; + + if (overlapEvent1.m_scalar_a_1 == overlapEvent2.m_scalar_a_1) { + if (overlapEvent1.m_ivertex_b < overlapEvent2.m_ivertex_b) + return -1; + } + } + } + } + + return 1; + } + + static final class Accelerate_helper { + static boolean accelerate_geometry(Geometry geometry, + SpatialReference sr, + Geometry.GeometryAccelerationDegree accel_degree) { + if (!can_accelerate_geometry(geometry)) + return false; + + double tol = InternalUtils.calculateToleranceFromGeometry(sr, + geometry, false); + boolean bAccelerated = false; + if (GeometryAccelerators.canUseRasterizedGeometry(geometry)) + bAccelerated |= ((MultiVertexGeometryImpl) geometry._getImpl()) + ._buildRasterizedGeometryAccelerator(tol, accel_degree); + + Geometry.Type type = geometry.getType(); + if ((type == Geometry.Type.Polygon || type == Geometry.Type.Polyline) + && GeometryAccelerators.canUseQuadTree(geometry) + && accel_degree != Geometry.GeometryAccelerationDegree.enumMild) + bAccelerated |= ((MultiVertexGeometryImpl) geometry._getImpl()) + ._buildQuadTreeAccelerator(accel_degree); + + if ((type == Geometry.Type.Polygon || type == Geometry.Type.Polyline) + && GeometryAccelerators.canUseQuadTreeForPaths(geometry) + && accel_degree != Geometry.GeometryAccelerationDegree.enumMild) + bAccelerated |= ((MultiPathImpl) geometry._getImpl()) + ._buildQuadTreeForPathsAccelerator(accel_degree); + + return bAccelerated; + } + + static boolean can_accelerate_geometry(Geometry geometry) { + return GeometryAccelerators.canUseRasterizedGeometry(geometry) + || GeometryAccelerators.canUseQuadTree(geometry) || GeometryAccelerators.canUseQuadTreeForPaths(geometry); + } + } } diff --git a/src/main/java/com/esri/core/geometry/RelationalOperationsMatrix.java b/src/main/java/com/esri/core/geometry/RelationalOperationsMatrix.java index 7b01ff75..06a95059 100644 --- a/src/main/java/com/esri/core/geometry/RelationalOperationsMatrix.java +++ b/src/main/java/com/esri/core/geometry/RelationalOperationsMatrix.java @@ -24,2702 +24,2702 @@ package com.esri.core.geometry; class RelationalOperationsMatrix { - private TopoGraph m_topo_graph; - private int[] m_matrix; - private int[] m_max_dim; - private boolean[] m_perform_predicates; - private String m_scl; - private int m_predicates_half_edge; - private int m_predicates_cluster; - private int m_predicate_count; - private int m_cluster_index_a; - private int m_cluster_index_b; - private int m_visited_index; - - private interface MatrixPredicate { - static final int InteriorInterior = 0; - static final int InteriorBoundary = 1; - static final int InteriorExterior = 2; - static final int BoundaryInterior = 3; - static final int BoundaryBoundary = 4; - static final int BoundaryExterior = 5; - static final int ExteriorInterior = 6; - static final int ExteriorBoundary = 7; - static final int ExteriorExterior = 8; - } - - private interface Predicates { - static final int AreaAreaPredicates = 0; - static final int AreaLinePredicates = 1; - static final int LineLinePredicates = 2; - static final int AreaPointPredicates = 3; - static final int LinePointPredicates = 4; - static final int PointPointPredicates = 5; - } - - // Computes the necessary 9 intersection relationships of boundary, - // interior, and exterior of geometry_a vs geometry_b for the given scl - // string. - static boolean relate(Geometry geometry_a, Geometry geometry_b, - SpatialReference sr, String scl, ProgressTracker progress_tracker) { - - if (scl.length() != 9) - throw new GeometryException("relation string length has to be 9 characters"); - - for (int i = 0; i < 9; i++) { - char c = scl.charAt(i); - - if (c != '*' && c != 'T' && c != 'F' && c != '0' && c != '1' && c != '2') - throw new GeometryException("relation string"); - } - - int relation = getPredefinedRelation_(scl, geometry_a.getDimension(), - geometry_b.getDimension()); - - if (relation != RelationalOperations.Relation.unknown) - return RelationalOperations.relate(geometry_a, geometry_b, sr, - relation, progress_tracker); - - Envelope2D env1 = new Envelope2D(); - geometry_a.queryEnvelope2D(env1); - Envelope2D env2 = new Envelope2D(); - geometry_b.queryEnvelope2D(env2); - - Envelope2D envMerged = new Envelope2D(); - envMerged.setCoords(env1); - envMerged.merge(env2); - double tolerance = InternalUtils.calculateToleranceFromGeometry(sr, - envMerged, false); - - Geometry _geometryA = convertGeometry_(geometry_a, tolerance); - Geometry _geometryB = convertGeometry_(geometry_b, tolerance); - - if (_geometryA.isEmpty() || _geometryB.isEmpty()) - return relateEmptyGeometries_(_geometryA, _geometryB, scl); - - int typeA = _geometryA.getType().value(); - int typeB = _geometryB.getType().value(); - - boolean bRelation = false; - - switch (typeA) { - case Geometry.GeometryType.Polygon: - switch (typeB) { - case Geometry.GeometryType.Polygon: - bRelation = polygonRelatePolygon_((Polygon) (_geometryA), - (Polygon) (_geometryB), tolerance, scl, - progress_tracker); - break; - - case Geometry.GeometryType.Polyline: - bRelation = polygonRelatePolyline_((Polygon) (_geometryA), - (Polyline) (_geometryB), tolerance, scl, - progress_tracker); - break; - - case Geometry.GeometryType.Point: - bRelation = polygonRelatePoint_((Polygon) (_geometryA), - (Point) (_geometryB), tolerance, scl, progress_tracker); - break; - - case Geometry.GeometryType.MultiPoint: - bRelation = polygonRelateMultiPoint_((Polygon) (_geometryA), - (MultiPoint) (_geometryB), tolerance, scl, - progress_tracker); - break; - - default: - break; // warning fix - } - break; - - case Geometry.GeometryType.Polyline: - switch (typeB) { - case Geometry.GeometryType.Polygon: - bRelation = polygonRelatePolyline_((Polygon) (_geometryB), - (Polyline) (_geometryA), tolerance, - getTransposeMatrix_(scl), progress_tracker); - break; - - case Geometry.GeometryType.Polyline: - bRelation = polylineRelatePolyline_((Polyline) (_geometryA), - (Polyline) (_geometryB), tolerance, scl, - progress_tracker); - break; - - case Geometry.GeometryType.Point: - bRelation = polylineRelatePoint_((Polyline) (_geometryA), - (Point) (_geometryB), tolerance, scl, progress_tracker); - break; - - case Geometry.GeometryType.MultiPoint: - bRelation = polylineRelateMultiPoint_((Polyline) (_geometryA), - (MultiPoint) (_geometryB), tolerance, scl, - progress_tracker); - break; - - default: - break; // warning fix - } - break; - - case Geometry.GeometryType.Point: - switch (typeB) { - case Geometry.GeometryType.Polygon: - bRelation = polygonRelatePoint_((Polygon) (_geometryB), - (Point) (_geometryA), tolerance, - getTransposeMatrix_(scl), progress_tracker); - break; - - case Geometry.GeometryType.Polyline: - bRelation = polylineRelatePoint_((Polyline) (_geometryB), - (Point) (_geometryA), tolerance, - getTransposeMatrix_(scl), progress_tracker); - break; - - case Geometry.GeometryType.Point: - bRelation = pointRelatePoint_((Point) (_geometryA), - (Point) (_geometryB), tolerance, scl, progress_tracker); - break; - - case Geometry.GeometryType.MultiPoint: - bRelation = multiPointRelatePoint_((MultiPoint) (_geometryB), - (Point) (_geometryA), tolerance, - getTransposeMatrix_(scl), progress_tracker); - break; - - default: - break; // warning fix - } - break; - - case Geometry.GeometryType.MultiPoint: - switch (typeB) { - case Geometry.GeometryType.Polygon: - bRelation = polygonRelateMultiPoint_((Polygon) (_geometryB), - (MultiPoint) (_geometryA), tolerance, - getTransposeMatrix_(scl), progress_tracker); - break; - - case Geometry.GeometryType.Polyline: - bRelation = polylineRelateMultiPoint_((Polyline) (_geometryB), - (MultiPoint) (_geometryA), tolerance, - getTransposeMatrix_(scl), progress_tracker); - break; - - case Geometry.GeometryType.MultiPoint: - bRelation = multiPointRelateMultiPoint_( - (MultiPoint) (_geometryA), (MultiPoint) (_geometryB), - tolerance, scl, progress_tracker); - break; - - case Geometry.GeometryType.Point: - bRelation = multiPointRelatePoint_((MultiPoint) (_geometryA), - (Point) (_geometryB), tolerance, scl, progress_tracker); - break; - - default: - break; // warning fix - } - break; - default: - bRelation = false; - break; - } - - return bRelation; - } - - private RelationalOperationsMatrix() { - m_predicate_count = 0; - m_topo_graph = new TopoGraph(); - m_matrix = new int[9]; - m_max_dim = new int[9]; - m_perform_predicates = new boolean[9]; - m_predicates_half_edge = -1; - m_predicates_cluster = -1; - } - - // Returns true if the relation holds. - static boolean polygonRelatePolygon_(Polygon polygon_a, Polygon polygon_b, - double tolerance, String scl, ProgressTracker progress_tracker) { - RelationalOperationsMatrix relOps = new RelationalOperationsMatrix(); - relOps.resetMatrix_(); - relOps.setPredicates_(scl); - relOps.setAreaAreaPredicates_(); - - Envelope2D env_a = new Envelope2D(), env_b = new Envelope2D(); - polygon_a.queryEnvelope2D(env_a); - polygon_b.queryEnvelope2D(env_b); - - boolean bRelationKnown = false; - boolean b_disjoint = RelationalOperations.envelopeDisjointEnvelope_( - env_a, env_b, tolerance, progress_tracker); - - if (b_disjoint) { - relOps.areaAreaDisjointPredicates_(polygon_a, polygon_b); - bRelationKnown = true; - } - - if (!bRelationKnown) { - // Quick rasterize test to see whether the the geometries are - // disjoint, or if one is contained in the other. - int relation = RelationalOperations - .tryRasterizedContainsOrDisjoint_(polygon_a, polygon_b, - tolerance, false); - - if (relation == RelationalOperations.Relation.disjoint) { - relOps.areaAreaDisjointPredicates_(polygon_a, polygon_b); - bRelationKnown = true; - } else if (relation == RelationalOperations.Relation.contains) { - relOps.areaAreaContainsPredicates_(polygon_b); - bRelationKnown = true; - } else if (relation == RelationalOperations.Relation.within) { - relOps.areaAreaWithinPredicates_(polygon_a); - bRelationKnown = true; - } - } - - if (!bRelationKnown) { - EditShape edit_shape = new EditShape(); - int geom_a = edit_shape.addGeometry(polygon_a); - int geom_b = edit_shape.addGeometry(polygon_b); - relOps.setEditShapeCrackAndCluster_(edit_shape, tolerance, - progress_tracker); - relOps.computeMatrixTopoGraphHalfEdges_(geom_a, geom_b); - relOps.m_topo_graph.removeShape(); - } - - boolean bRelation = relationCompare_(relOps.m_matrix, relOps.m_scl); - return bRelation; - } - - // The relation is based on the simplified-Polygon A containing Polygon B, which may be non-simple. - static boolean polygonContainsPolygon_(Polygon polygon_a, Polygon polygon_b, double tolerance, ProgressTracker progress_tracker) { - assert (!polygon_a.isEmpty()); - assert (!polygon_b.isEmpty()); - - RelationalOperationsMatrix relOps = new RelationalOperationsMatrix(); - relOps.resetMatrix_(); - relOps.setPredicates_("T*****F**"); - relOps.setAreaAreaPredicates_(); - - Envelope2D env_a = new Envelope2D(), env_b = new Envelope2D(); - polygon_a.queryEnvelope2D(env_a); - polygon_b.queryEnvelope2D(env_b); - - boolean bRelationKnown = false; - boolean b_disjoint = RelationalOperations.envelopeDisjointEnvelope_(env_a, env_b, tolerance, progress_tracker); - - if (b_disjoint) { - relOps.areaAreaDisjointPredicates_(polygon_a, polygon_b); - bRelationKnown = true; - } - - if (!bRelationKnown) { - // Quick rasterize test to see whether the the geometries are disjoint, or if one is contained in the other. - int relation = RelationalOperations.tryRasterizedContainsOrDisjoint_(polygon_a, polygon_b, tolerance, false); - - if (relation == RelationalOperations.Relation.disjoint) { - relOps.areaAreaDisjointPredicates_(polygon_a, polygon_b); - bRelationKnown = true; - } else if (relation == RelationalOperations.Relation.contains) { - relOps.areaAreaContainsPredicates_(polygon_b); - bRelationKnown = true; - } else if (relation == RelationalOperations.Relation.within) { - relOps.areaAreaWithinPredicates_(polygon_a); - bRelationKnown = true; - } - } - - if (bRelationKnown) { - boolean bContains = relationCompare_(relOps.m_matrix, relOps.m_scl); - return bContains; - } - - EditShape edit_shape = new EditShape(); - int geom_a = edit_shape.addGeometry(polygon_a); - int geom_b = edit_shape.addGeometry(polygon_b); - - CrackAndCluster.execute(edit_shape, tolerance, progress_tracker, false); - Polyline boundary_b = (Polyline) edit_shape.getGeometry(geom_b).getBoundary(); - edit_shape.filterClosePoints(0, true, true); - Simplificator.execute(edit_shape, geom_a, -1, false, progress_tracker); - - // Make sure Polygon A has exterior - // If the simplified Polygon A does not have interior, then it cannot contain anything. - if (edit_shape.getPointCount(geom_a) == 0) - return false; - - Simplificator.execute(edit_shape, geom_b, -1, false, progress_tracker); - - relOps.setEditShape_(edit_shape, progress_tracker); - - // We see if the simplified Polygon A contains the simplified Polygon B. - - boolean b_empty = edit_shape.getPointCount(geom_b) == 0; - - if (!b_empty) {//geom_b has interior - relOps.computeMatrixTopoGraphHalfEdges_(geom_a, geom_b); - relOps.m_topo_graph.removeShape(); - boolean bContains = relationCompare_(relOps.m_matrix, relOps.m_scl); - - if (!bContains) - return bContains; - } - - Polygon polygon_simple_a = (Polygon) edit_shape.getGeometry(geom_a); - edit_shape = new EditShape(); - geom_a = edit_shape.addGeometry(polygon_simple_a); - geom_b = edit_shape.addGeometry(boundary_b); - relOps.setEditShape_(edit_shape, progress_tracker); - - // Check no interior lines of the boundary intersect the exterior - relOps.m_predicate_count = 0; - relOps.resetMatrix_(); - relOps.setPredicates_(b_empty ? "T*****F**" : "******F**"); - relOps.setAreaLinePredicates_(); - - relOps.computeMatrixTopoGraphHalfEdges_(geom_a, geom_b); - relOps.m_topo_graph.removeShape(); - boolean bContains = relationCompare_(relOps.m_matrix, relOps.m_scl); - return bContains; - } - - // Returns true if the relation holds. - static boolean polygonRelatePolyline_(Polygon polygon_a, - Polyline polyline_b, double tolerance, String scl, - ProgressTracker progress_tracker) { - RelationalOperationsMatrix relOps = new RelationalOperationsMatrix(); - relOps.resetMatrix_(); - relOps.setPredicates_(scl); - relOps.setAreaLinePredicates_(); - - Envelope2D env_a = new Envelope2D(), env_b = new Envelope2D(); - polygon_a.queryEnvelope2D(env_a); - polyline_b.queryEnvelope2D(env_b); - - boolean bRelationKnown = false; - boolean b_disjoint = RelationalOperations.envelopeDisjointEnvelope_( - env_a, env_b, tolerance, progress_tracker); - - if (b_disjoint) { - relOps.areaLineDisjointPredicates_(polygon_a, polyline_b); // passing polyline - // to get boundary - // information - bRelationKnown = true; - } - - if (!bRelationKnown) { - // Quick rasterize test to see whether the the geometries are - // disjoint, or if one is contained in the other. - int relation = RelationalOperations - .tryRasterizedContainsOrDisjoint_(polygon_a, polyline_b, - tolerance, false); - - if (relation == RelationalOperations.Relation.disjoint) { - relOps.areaLineDisjointPredicates_(polygon_a, polyline_b); // passing - // polyline to - // get boundary - // information - bRelationKnown = true; - } else if (relation == RelationalOperations.Relation.contains) { - relOps.areaLineContainsPredicates_(polygon_a, polyline_b); // passing - // polyline to - // get boundary - // information - bRelationKnown = true; - } - } - - if (!bRelationKnown) { - EditShape edit_shape = new EditShape(); - int geom_a = edit_shape.addGeometry(polygon_a); - int geom_b = edit_shape.addGeometry(polyline_b); - relOps.setEditShapeCrackAndCluster_(edit_shape, tolerance, - progress_tracker); - relOps.m_cluster_index_b = relOps.m_topo_graph - .createUserIndexForClusters(); - markClusterEndPoints_(geom_b, relOps.m_topo_graph, - relOps.m_cluster_index_b); - relOps.computeMatrixTopoGraphHalfEdges_(geom_a, geom_b); - relOps.m_topo_graph - .deleteUserIndexForClusters(relOps.m_cluster_index_b); - relOps.m_topo_graph.removeShape(); - } - - boolean bRelation = relationCompare_(relOps.m_matrix, relOps.m_scl); - return bRelation; - } - - static boolean polygonContainsPolyline_(Polygon polygon_a, Polyline polyline_b, double tolerance, ProgressTracker progress_tracker) { - RelationalOperationsMatrix relOps = new RelationalOperationsMatrix(); - relOps.resetMatrix_(); - relOps.setPredicates_("T*****F**"); - relOps.setAreaLinePredicates_(); - - Envelope2D env_a = new Envelope2D(), env_b = new Envelope2D(); - polygon_a.queryEnvelope2D(env_a); - polyline_b.queryEnvelope2D(env_b); - - boolean bRelationKnown = false; - boolean b_disjoint = RelationalOperations.envelopeDisjointEnvelope_(env_a, env_b, tolerance, progress_tracker); - - if (b_disjoint) { - relOps.areaLineDisjointPredicates_(polygon_a, polyline_b); // passing polyline to get boundary information - bRelationKnown = true; - } - - if (!bRelationKnown) { - // Quick rasterize test to see whether the the geometries are disjoint, or if one is contained in the other. - int relation = RelationalOperations.tryRasterizedContainsOrDisjoint_(polygon_a, polyline_b, tolerance, false); - - if (relation == RelationalOperations.Relation.disjoint) { - relOps.areaLineDisjointPredicates_(polygon_a, polyline_b); // passing polyline to get boundary information - bRelationKnown = true; - } else if (relation == RelationalOperations.Relation.contains) { - relOps.areaLineContainsPredicates_(polygon_a, polyline_b); // passing polyline to get boundary information - bRelationKnown = true; - } - } - - if (bRelationKnown) { - boolean bContains = relationCompare_(relOps.m_matrix, relOps.m_scl); - return bContains; - } - - EditShape edit_shape = new EditShape(); - int geom_a = edit_shape.addGeometry(polygon_a); - int geom_b = edit_shape.addGeometry(polyline_b); - relOps.setEditShapeCrackAndCluster_(edit_shape, tolerance, progress_tracker); - - // Make sure Polygon A has exterior - // If the simplified Polygon A does not have interior, then it cannot contain anything. - if (edit_shape.getPointCount(geom_a) == 0) - return false; - - relOps.computeMatrixTopoGraphHalfEdges_(geom_a, geom_b); - relOps.m_topo_graph.removeShape(); - - boolean bContains = relationCompare_(relOps.m_matrix, relOps.m_scl); - return bContains; - } - - // Returns true if the relation holds - static boolean polygonRelateMultiPoint_(Polygon polygon_a, - MultiPoint multipoint_b, double tolerance, String scl, - ProgressTracker progress_tracker) { - RelationalOperationsMatrix relOps = new RelationalOperationsMatrix(); - relOps.resetMatrix_(); - relOps.setPredicates_(scl); - relOps.setAreaPointPredicates_(); - - Envelope2D env_a = new Envelope2D(), env_b = new Envelope2D(); - polygon_a.queryEnvelope2D(env_a); - multipoint_b.queryEnvelope2D(env_b); - - boolean bRelationKnown = false; - boolean b_disjoint = RelationalOperations.envelopeDisjointEnvelope_( - env_a, env_b, tolerance, progress_tracker); - - if (b_disjoint) { - relOps.areaPointDisjointPredicates_(polygon_a); - bRelationKnown = true; - } - - if (!bRelationKnown) { - // Quick rasterize test to see whether the the geometries are - // disjoint, or if one is contained in the other. - int relation = RelationalOperations - .tryRasterizedContainsOrDisjoint_(polygon_a, multipoint_b, - tolerance, false); - - if (relation == RelationalOperations.Relation.disjoint) { - relOps.areaPointDisjointPredicates_(polygon_a); - bRelationKnown = true; - } else if (relation == RelationalOperations.Relation.contains) { - relOps.areaPointContainsPredicates_(polygon_a); - bRelationKnown = true; - } - } - - if (!bRelationKnown) { - EditShape edit_shape = new EditShape(); - int geom_a = edit_shape.addGeometry(polygon_a); - int geom_b = edit_shape.addGeometry(multipoint_b); - relOps.setEditShapeCrackAndCluster_(edit_shape, tolerance, - progress_tracker); - relOps.computeMatrixTopoGraphClusters_(geom_a, geom_b); - relOps.m_topo_graph.removeShape(); - } - - boolean bRelation = relationCompare_(relOps.m_matrix, relOps.m_scl); - return bRelation; - } - - // Returns true if the relation holds. - static boolean polylineRelatePolyline_(Polyline polyline_a, - Polyline polyline_b, double tolerance, String scl, - ProgressTracker progress_tracker) { - RelationalOperationsMatrix relOps = new RelationalOperationsMatrix(); - relOps.resetMatrix_(); - relOps.setPredicates_(scl); - relOps.setLineLinePredicates_(); - - Envelope2D env_a = new Envelope2D(), env_b = new Envelope2D(); - polyline_a.queryEnvelope2D(env_a); - polyline_b.queryEnvelope2D(env_b); - - boolean bRelationKnown = false; - boolean b_disjoint = RelationalOperations.envelopeDisjointEnvelope_( - env_a, env_b, tolerance, progress_tracker); - - if (b_disjoint) { - relOps.lineLineDisjointPredicates_(polyline_a, polyline_b); - bRelationKnown = true; - } - - if (!bRelationKnown) { - // Quick rasterize test to see whether the the geometries are - // disjoint, or if one is contained in the other. - int relation = RelationalOperations - .tryRasterizedContainsOrDisjoint_(polyline_a, polyline_b, - tolerance, false); - - if (relation == RelationalOperations.Relation.disjoint) { - relOps.lineLineDisjointPredicates_(polyline_a, polyline_b); - bRelationKnown = true; - } - } - - if (!bRelationKnown) { - EditShape edit_shape = new EditShape(); - int geom_a = edit_shape.addGeometry(polyline_a); - int geom_b = edit_shape.addGeometry(polyline_b); - relOps.setEditShapeCrackAndCluster_(edit_shape, tolerance, - progress_tracker); - relOps.m_cluster_index_a = relOps.m_topo_graph - .createUserIndexForClusters(); - relOps.m_cluster_index_b = relOps.m_topo_graph - .createUserIndexForClusters(); - markClusterEndPoints_(geom_a, relOps.m_topo_graph, - relOps.m_cluster_index_a); - markClusterEndPoints_(geom_b, relOps.m_topo_graph, - relOps.m_cluster_index_b); - relOps.computeMatrixTopoGraphHalfEdges_(geom_a, geom_b); - relOps.m_topo_graph - .deleteUserIndexForClusters(relOps.m_cluster_index_a); - relOps.m_topo_graph - .deleteUserIndexForClusters(relOps.m_cluster_index_b); - relOps.m_topo_graph.removeShape(); - } - - boolean bRelation = relationCompare_(relOps.m_matrix, relOps.m_scl); - return bRelation; - } - - // Returns true if the relation holds. - static boolean polylineRelateMultiPoint_(Polyline polyline_a, - MultiPoint multipoint_b, double tolerance, String scl, - ProgressTracker progress_tracker) { - RelationalOperationsMatrix relOps = new RelationalOperationsMatrix(); - relOps.resetMatrix_(); - relOps.setPredicates_(scl); - relOps.setLinePointPredicates_(); - - Envelope2D env_a = new Envelope2D(), env_b = new Envelope2D(); - polyline_a.queryEnvelope2D(env_a); - multipoint_b.queryEnvelope2D(env_b); - - boolean bRelationKnown = false; - boolean b_disjoint = RelationalOperations.envelopeDisjointEnvelope_( - env_a, env_b, tolerance, progress_tracker); - - if (b_disjoint) { - relOps.linePointDisjointPredicates_(polyline_a); - bRelationKnown = true; - } - - if (!bRelationKnown) { - // Quick rasterize test to see whether the the geometries are - // disjoint, or if one is contained in the other. - int relation = RelationalOperations - .tryRasterizedContainsOrDisjoint_(polyline_a, multipoint_b, - tolerance, false); - - if (relation == RelationalOperations.Relation.disjoint) { - relOps.linePointDisjointPredicates_(polyline_a); - bRelationKnown = true; - } - } - - if (!bRelationKnown) { - EditShape edit_shape = new EditShape(); - int geom_a = edit_shape.addGeometry(polyline_a); - int geom_b = edit_shape.addGeometry(multipoint_b); - relOps.setEditShapeCrackAndCluster_(edit_shape, tolerance, - progress_tracker); - relOps.m_cluster_index_a = relOps.m_topo_graph - .createUserIndexForClusters(); - markClusterEndPoints_(geom_a, relOps.m_topo_graph, - relOps.m_cluster_index_a); - relOps.computeMatrixTopoGraphClusters_(geom_a, geom_b); - relOps.m_topo_graph - .deleteUserIndexForClusters(relOps.m_cluster_index_a); - relOps.m_topo_graph.removeShape(); - } - - boolean bRelation = relationCompare_(relOps.m_matrix, relOps.m_scl); - return bRelation; - } - - // Returns true if the relation holds. - static boolean multiPointRelateMultiPoint_(MultiPoint multipoint_a, - MultiPoint multipoint_b, double tolerance, String scl, - ProgressTracker progress_tracker) { - RelationalOperationsMatrix relOps = new RelationalOperationsMatrix(); - relOps.resetMatrix_(); - relOps.setPredicates_(scl); - relOps.setPointPointPredicates_(); - - Envelope2D env_a = new Envelope2D(), env_b = new Envelope2D(); - multipoint_a.queryEnvelope2D(env_a); - multipoint_b.queryEnvelope2D(env_b); - - boolean bRelationKnown = false; - boolean b_disjoint = RelationalOperations.envelopeDisjointEnvelope_( - env_a, env_b, tolerance, progress_tracker); - - if (b_disjoint) { - relOps.pointPointDisjointPredicates_(); - bRelationKnown = true; - } - - if (!bRelationKnown) { - EditShape edit_shape = new EditShape(); - int geom_a = edit_shape.addGeometry(multipoint_a); - int geom_b = edit_shape.addGeometry(multipoint_b); - relOps.setEditShapeCrackAndCluster_(edit_shape, tolerance, - progress_tracker); - relOps.computeMatrixTopoGraphClusters_(geom_a, geom_b); - relOps.m_topo_graph.removeShape(); - } - - boolean bRelation = relationCompare_(relOps.m_matrix, relOps.m_scl); - return bRelation; - } - - // Returns true if the relation holds. - static boolean polygonRelatePoint_(Polygon polygon_a, Point point_b, - double tolerance, String scl, ProgressTracker progress_tracker) { - RelationalOperationsMatrix relOps = new RelationalOperationsMatrix(); - relOps.resetMatrix_(); - relOps.setPredicates_(scl); - relOps.setAreaPointPredicates_(); - - Envelope2D env_a = new Envelope2D(); - polygon_a.queryEnvelope2D(env_a); - Point2D pt_b = point_b.getXY(); - - boolean bRelationKnown = false; - boolean b_disjoint = RelationalOperations.pointDisjointEnvelope_(pt_b, env_a, tolerance, progress_tracker); - - if (b_disjoint) { - relOps.areaPointDisjointPredicates_(polygon_a); - bRelationKnown = true; - } - - if (!bRelationKnown) { - PolygonUtils.PiPResult res = PolygonUtils.isPointInPolygon2D(polygon_a, pt_b, tolerance); // uses accelerator - - if (res == PolygonUtils.PiPResult.PiPInside) {// polygon must have area - relOps.m_matrix[MatrixPredicate.InteriorInterior] = 0; - relOps.m_matrix[MatrixPredicate.InteriorExterior] = 2; - relOps.m_matrix[MatrixPredicate.BoundaryInterior] = -1; - relOps.m_matrix[MatrixPredicate.BoundaryExterior] = 1; - relOps.m_matrix[MatrixPredicate.ExteriorInterior] = -1; - } else if (res == PolygonUtils.PiPResult.PiPBoundary) { - relOps.m_matrix[MatrixPredicate.ExteriorInterior] = -1; - - double area = polygon_a.calculateArea2D(); - - if (area != 0) { - relOps.m_matrix[MatrixPredicate.InteriorInterior] = -1; - relOps.m_matrix[MatrixPredicate.BoundaryInterior] = 0; - relOps.m_matrix[MatrixPredicate.InteriorExterior] = 2; - relOps.m_matrix[MatrixPredicate.BoundaryExterior] = 1; - } else { - relOps.m_matrix[MatrixPredicate.InteriorInterior] = 0; - relOps.m_matrix[MatrixPredicate.BoundaryInterior] = -1; - relOps.m_matrix[MatrixPredicate.BoundaryExterior] = -1; - - Envelope2D env = new Envelope2D(); - polygon_a.queryEnvelope2D(env); - relOps.m_matrix[MatrixPredicate.InteriorExterior] = (env.getHeight() == 0.0 && env.getWidth() == 0.0 ? -1 : 1); - } - } else { - relOps.areaPointDisjointPredicates_(polygon_a); - } - } - - boolean bRelation = relationCompare_(relOps.m_matrix, scl); - return bRelation; - } - - // Returns true if the relation holds. - static boolean polylineRelatePoint_(Polyline polyline_a, Point point_b, - double tolerance, String scl, ProgressTracker progress_tracker) { - RelationalOperationsMatrix relOps = new RelationalOperationsMatrix(); - relOps.resetMatrix_(); - relOps.setPredicates_(scl); - relOps.setLinePointPredicates_(); - - Envelope2D env_a = new Envelope2D(); - polyline_a.queryEnvelope2D(env_a); - Point2D pt_b = point_b.getXY(); - - boolean bRelationKnown = false; - boolean b_disjoint = RelationalOperations.pointDisjointEnvelope_(pt_b, env_a, tolerance, progress_tracker); - - if (b_disjoint) { - relOps.linePointDisjointPredicates_(polyline_a); - bRelationKnown = true; - } - - if (!bRelationKnown) { - MultiPoint boundary_a = null; - boolean b_boundary_contains_point_known = false; - boolean b_boundary_contains_point = false; - - if (relOps.m_perform_predicates[MatrixPredicate.InteriorInterior] || relOps.m_perform_predicates[MatrixPredicate.ExteriorInterior]) { - boolean b_intersects = RelationalOperations.linearPathIntersectsPoint_(polyline_a, pt_b, tolerance); - - if (b_intersects) { - if (relOps.m_perform_predicates[MatrixPredicate.InteriorInterior]) { - boundary_a = (MultiPoint) Boundary.calculate(polyline_a, progress_tracker); - b_boundary_contains_point = !RelationalOperations.multiPointDisjointPointImpl_(boundary_a, pt_b, tolerance, progress_tracker); - b_boundary_contains_point_known = true; - - if (b_boundary_contains_point) - relOps.m_matrix[MatrixPredicate.InteriorInterior] = -1; - else - relOps.m_matrix[MatrixPredicate.InteriorInterior] = 0; - } - - relOps.m_matrix[MatrixPredicate.ExteriorInterior] = -1; - } else { - relOps.m_matrix[MatrixPredicate.InteriorInterior] = -1; - relOps.m_matrix[MatrixPredicate.ExteriorInterior] = 0; - } - } - - if (relOps.m_perform_predicates[MatrixPredicate.BoundaryInterior]) { - if (boundary_a != null && boundary_a.isEmpty()) { - relOps.m_matrix[MatrixPredicate.BoundaryInterior] = -1; - } else { - if (!b_boundary_contains_point_known) { - if (boundary_a == null) - boundary_a = (MultiPoint) Boundary.calculate(polyline_a, progress_tracker); - - b_boundary_contains_point = !RelationalOperations.multiPointDisjointPointImpl_(boundary_a, pt_b, tolerance, progress_tracker); - b_boundary_contains_point_known = true; - } - - relOps.m_matrix[MatrixPredicate.BoundaryInterior] = (b_boundary_contains_point ? 0 : -1); - } - } - - if (relOps.m_perform_predicates[MatrixPredicate.BoundaryExterior]) { - if (boundary_a != null && boundary_a.isEmpty()) { - relOps.m_matrix[MatrixPredicate.BoundaryExterior] = -1; - } else { - if (b_boundary_contains_point_known && !b_boundary_contains_point) { - relOps.m_matrix[MatrixPredicate.BoundaryExterior] = 0; - } else { - if (boundary_a == null) - boundary_a = (MultiPoint) Boundary.calculate(polyline_a, progress_tracker); - - boolean b_boundary_equals_point = RelationalOperations.multiPointEqualsPoint_(boundary_a, point_b, tolerance, progress_tracker); - relOps.m_matrix[MatrixPredicate.BoundaryExterior] = (b_boundary_equals_point ? -1 : 0); - } - } - } - - if (relOps.m_perform_predicates[MatrixPredicate.InteriorExterior]) { - boolean b_has_length = polyline_a.calculateLength2D() != 0; - - if (b_has_length) { - relOps.m_matrix[MatrixPredicate.InteriorExterior] = 1; - } else { - // all points are interior - MultiPoint interior_a = new MultiPoint(polyline_a.getDescription()); - interior_a.add(polyline_a, 0, polyline_a.getPointCount()); - boolean b_interior_equals_point = RelationalOperations.multiPointEqualsPoint_(interior_a, point_b, tolerance, progress_tracker); - relOps.m_matrix[MatrixPredicate.InteriorExterior] = (b_interior_equals_point ? -1 : 0); - } - } - } - - boolean bRelation = relationCompare_(relOps.m_matrix, relOps.m_scl); - return bRelation; - } - - // Returns true if the relation holds. - static boolean multiPointRelatePoint_(MultiPoint multipoint_a, - Point point_b, double tolerance, String scl, - ProgressTracker progress_tracker) { - RelationalOperationsMatrix relOps = new RelationalOperationsMatrix(); - relOps.resetMatrix_(); - relOps.setPredicates_(scl); - relOps.setPointPointPredicates_(); - - Envelope2D env_a = new Envelope2D(); - multipoint_a.queryEnvelope2D(env_a); - Point2D pt_b = point_b.getXY(); - - boolean bRelationKnown = false; - boolean b_disjoint = RelationalOperations.pointDisjointEnvelope_(pt_b, - env_a, tolerance, progress_tracker); - - if (b_disjoint) { - relOps.pointPointDisjointPredicates_(); - bRelationKnown = true; - } - - if (!bRelationKnown) { - boolean b_intersects = false; - boolean b_multipoint_contained = true; - double tolerance_sq = tolerance * tolerance; - - for (int i = 0; i < multipoint_a.getPointCount(); i++) { - Point2D pt_a = multipoint_a.getXY(i); - - if (Point2D.sqrDistance(pt_a, pt_b) <= tolerance_sq) - b_intersects = true; - else - b_multipoint_contained = false; - - if (b_intersects && !b_multipoint_contained) - break; - } - - if (b_intersects) { - relOps.m_matrix[MatrixPredicate.InteriorInterior] = 0; - - if (!b_multipoint_contained) - relOps.m_matrix[MatrixPredicate.InteriorExterior] = 0; - else - relOps.m_matrix[MatrixPredicate.InteriorExterior] = -1; - - relOps.m_matrix[MatrixPredicate.ExteriorInterior] = -1; - } else { - relOps.m_matrix[MatrixPredicate.InteriorInterior] = -1; - relOps.m_matrix[MatrixPredicate.InteriorExterior] = 0; - relOps.m_matrix[MatrixPredicate.ExteriorInterior] = 0; - } - } - - boolean bRelation = relationCompare_(relOps.m_matrix, scl); - return bRelation; - } - - // Returns true if the relation holds. - static boolean pointRelatePoint_(Point point_a, Point point_b, - double tolerance, String scl, ProgressTracker progress_tracker) { - Point2D pt_a = point_a.getXY(); - Point2D pt_b = point_b.getXY(); - int[] matrix = new int[9]; - - for (int i = 0; i < 9; i++) - matrix[i] = -1; - - if (Point2D.sqrDistance(pt_a, pt_b) <= tolerance * tolerance) { - matrix[MatrixPredicate.InteriorInterior] = 0; - } else { - matrix[MatrixPredicate.InteriorExterior] = 0; - matrix[MatrixPredicate.ExteriorInterior] = 0; - } - - matrix[MatrixPredicate.ExteriorExterior] = 2; - - boolean bRelation = relationCompare_(matrix, scl); - return bRelation; - } - - // Compares the DE-9I matrix against the scl string. - private static boolean relationCompare_(int[] matrix, String scl) { - for (int i = 0; i < 9; i++) { - switch (scl.charAt(i)) { - case 'T': - assert (matrix[i] != -2); - if (matrix[i] == -1) - return false; - break; - - case 'F': - assert (matrix[i] != -2); - if (matrix[i] != -1) - return false; - break; - - case '0': - assert (matrix[i] != -2); - if (matrix[i] != 0) - return false; - break; - - case '1': - assert (matrix[i] != -2); - if (matrix[i] != 1) - return false; - break; - - case '2': - assert (matrix[i] != -2); - if (matrix[i] != 2) - return false; - break; - - default: - break; - } - } - - return true; - } - - static boolean relateEmptyGeometries_(Geometry geometry_a, Geometry geometry_b, String scl) { - int[] matrix = new int[9]; - - if (geometry_a.isEmpty() && geometry_b.isEmpty()) { - for (int i = 0; i < 9; i++) - matrix[i] = -1; - - return relationCompare_(matrix, scl); - } - - boolean b_transpose = false; - - Geometry g_a; - Geometry g_b; - - if (!geometry_a.isEmpty()) { - g_a = geometry_a; - g_b = geometry_b; - } else { - g_a = geometry_b; - g_b = geometry_a; - b_transpose = true; - } - - matrix[MatrixPredicate.InteriorInterior] = -1; - matrix[MatrixPredicate.InteriorBoundary] = -1; - matrix[MatrixPredicate.BoundaryInterior] = -1; - matrix[MatrixPredicate.BoundaryBoundary] = -1; - matrix[MatrixPredicate.ExteriorInterior] = -1; - matrix[MatrixPredicate.ExteriorBoundary] = -1; - - matrix[MatrixPredicate.ExteriorExterior] = 2; - - int type = g_a.getType().value(); - - if (Geometry.isMultiPath(type)) { - if (type == Geometry.GeometryType.Polygon) { - double area = ((Polygon) g_a).calculateArea2D(); - - if (area != 0) { - matrix[MatrixPredicate.InteriorExterior] = 2; - matrix[MatrixPredicate.BoundaryExterior] = 1; - } else { - matrix[MatrixPredicate.BoundaryExterior] = -1; - - Envelope2D env = new Envelope2D(); - g_a.queryEnvelope2D(env); - matrix[MatrixPredicate.InteriorExterior] = (env.getHeight() == 0.0 && env.getWidth() == 0.0 ? 0 : 1); - } - } else { - boolean b_has_length = ((Polyline) g_a).calculateLength2D() != 0; - matrix[MatrixPredicate.InteriorExterior] = (b_has_length ? 1 : 0); - matrix[MatrixPredicate.BoundaryExterior] = (Boundary.hasNonEmptyBoundary((Polyline) g_a, null) ? 0 : -1); - } - } else { - matrix[MatrixPredicate.InteriorExterior] = 0; - matrix[MatrixPredicate.BoundaryExterior] = -1; - } - - if (b_transpose) - transposeMatrix_(matrix); - - return relationCompare_(matrix, scl); - } - - // Checks whether scl string is a predefined relation. - private static int getPredefinedRelation_(String scl, int dim_a, int dim_b) { - if (equals_(scl)) - return RelationalOperations.Relation.equals; - - if (disjoint_(scl)) - return RelationalOperations.Relation.disjoint; - - if (touches_(scl, dim_a, dim_b)) - return RelationalOperations.Relation.touches; - - if (crosses_(scl, dim_a, dim_b)) - return RelationalOperations.Relation.crosses; - - if (contains_(scl)) - return RelationalOperations.Relation.contains; - - if (overlaps_(scl, dim_a, dim_b)) - return RelationalOperations.Relation.overlaps; - - return RelationalOperations.Relation.unknown; - } - - // Checks whether the scl string is the equals relation. - private static boolean equals_(String scl) { - // Valid for all - if (scl.charAt(0) == 'T' && scl.charAt(1) == '*' - && scl.charAt(2) == 'F' && scl.charAt(3) == '*' - && scl.charAt(4) == '*' && scl.charAt(5) == 'F' - && scl.charAt(6) == 'F' && scl.charAt(7) == 'F' - && scl.charAt(8) == '*') - return true; - - return false; - } - - // Checks whether the scl string is the disjoint relation. - private static boolean disjoint_(String scl) { - if (scl.charAt(0) == 'F' && scl.charAt(1) == 'F' - && scl.charAt(2) == '*' && scl.charAt(3) == 'F' - && scl.charAt(4) == 'F' && scl.charAt(5) == '*' - && scl.charAt(6) == '*' && scl.charAt(7) == '*' - && scl.charAt(8) == '*') - return true; - - return false; - } - - // Checks whether the scl string is the touches relation. - private static boolean touches_(String scl, int dim_a, int dim_b) { - // Points cant touch - if (dim_a == 0 && dim_b == 0) - return false; - - if (!(dim_a == 2 && dim_b == 2)) { - // Valid for area-Line, Line-Line, area-Point, and Line-Point - if (scl.charAt(0) == 'F' && scl.charAt(1) == '*' - && scl.charAt(2) == '*' && scl.charAt(3) == 'T' - && scl.charAt(4) == '*' && scl.charAt(5) == '*' - && scl.charAt(6) == '*' && scl.charAt(7) == '*' - && scl.charAt(8) == '*') - return true; - - if (dim_a == 1 && dim_b == 1) { - // Valid for Line-Line - if (scl.charAt(0) == 'F' && scl.charAt(1) == 'T' - && scl.charAt(2) == '*' && scl.charAt(3) == '*' - && scl.charAt(4) == '*' && scl.charAt(5) == '*' - && scl.charAt(6) == '*' && scl.charAt(7) == '*' - && scl.charAt(8) == '*') - return true; - } - } - - // Valid for area-area, area-Line, Line-Line - - if (dim_b != 0) { - if (scl.charAt(0) == 'F' && scl.charAt(1) == '*' - && scl.charAt(2) == '*' && scl.charAt(3) == '*' - && scl.charAt(4) == 'T' && scl.charAt(5) == '*' - && scl.charAt(6) == '*' && scl.charAt(7) == '*' - && scl.charAt(8) == '*') - return true; - } - - return false; - } - - // Checks whether the scl string is the crosses relation. - private static boolean crosses_(String scl, int dim_a, int dim_b) { - if (dim_a > dim_b) { - // Valid for area-Line, area-Point, Line-Point - if (scl.charAt(0) == 'T' && scl.charAt(1) == '*' - && scl.charAt(2) == '*' && scl.charAt(3) == '*' - && scl.charAt(4) == '*' && scl.charAt(5) == '*' - && scl.charAt(6) == 'T' && scl.charAt(7) == '*' - && scl.charAt(8) == '*') - return true; - - return false; - } - - if (dim_a == 1 && dim_b == 1) { - // Valid for Line-Line - if (scl.charAt(0) == '0' && scl.charAt(1) == '*' - && scl.charAt(2) == '*' && scl.charAt(3) == '*' - && scl.charAt(4) == '*' && scl.charAt(5) == '*' - && scl.charAt(6) == '*' && scl.charAt(7) == '*' - && scl.charAt(8) == '*') - return true; - } - - return false; - } - - // Checks whether the scl string is the contains relation. - private static boolean contains_(String scl) { - // Valid for all - if (scl.charAt(0) == 'T' && scl.charAt(1) == '*' - && scl.charAt(2) == '*' && scl.charAt(3) == '*' - && scl.charAt(4) == '*' && scl.charAt(5) == '*' - && scl.charAt(6) == 'F' && scl.charAt(7) == 'F' - && scl.charAt(8) == '*') - return true; - - return false; - } - - // Checks whether the scl string is the overlaps relation. - private static boolean overlaps_(String scl, int dim_a, int dim_b) { - if (dim_a == dim_b) { - if (dim_a != 1) { - // Valid for area-area, Point-Point - if (scl.charAt(0) == 'T' && scl.charAt(1) == '*' - && scl.charAt(2) == 'T' && scl.charAt(3) == '*' - && scl.charAt(4) == '*' && scl.charAt(5) == '*' - && scl.charAt(6) == 'T' && scl.charAt(7) == '*' - && scl.charAt(8) == '*') - return true; - - return false; - } - - // Valid for Line-Line - if (scl.charAt(0) == '1' && scl.charAt(1) == '*' - && scl.charAt(2) == 'T' && scl.charAt(3) == '*' - && scl.charAt(4) == '*' && scl.charAt(5) == '*' - && scl.charAt(6) == 'T' && scl.charAt(7) == '*' - && scl.charAt(8) == '*') - return true; - } - - return false; - } - - // Marks each cluster of the topoGraph as belonging to an interior vertex of - // the geometry and/or a boundary index of the geometry. - private static void markClusterEndPoints_(int geometry, - TopoGraph topoGraph, int clusterIndex) { - int id = topoGraph.getGeometryID(geometry); - - for (int cluster = topoGraph.getFirstCluster(); cluster != -1; cluster = topoGraph.getNextCluster(cluster)) { - int cluster_parentage = topoGraph.getClusterParentage(cluster); - - if ((cluster_parentage & id) == 0) - continue; - - int first_half_edge = topoGraph.getClusterHalfEdge(cluster); - if (first_half_edge == -1) { - topoGraph.setClusterUserIndex(cluster, clusterIndex, 0); - continue; - } - - int next_half_edge = first_half_edge; - int index = 0; - - do { - int half_edge = next_half_edge; - int half_edge_parentage = topoGraph.getHalfEdgeParentage(half_edge); - - if ((half_edge_parentage & id) != 0) - index++; - - next_half_edge = topoGraph.getHalfEdgeNext(topoGraph.getHalfEdgeTwin(half_edge)); - - } while (next_half_edge != first_half_edge); - - topoGraph.setClusterUserIndex(cluster, clusterIndex, index); - } - - return; - } - - private static String getTransposeMatrix_(String scl) { - String transpose = new String(); - transpose += scl.charAt(0); - transpose += scl.charAt(3); - transpose += scl.charAt(6); - transpose += scl.charAt(1); - transpose += scl.charAt(4); - transpose += scl.charAt(7); - transpose += scl.charAt(2); - transpose += scl.charAt(5); - transpose += scl.charAt(8); - return transpose; - } - - // Allocates the matrix array if need be, and sets all entries to -2. - // -2: Not Computed - // -1: No intersection - // 0: 0-dimension intersection - // 1: 1-dimension intersection - // 2: 2-dimension intersection - private void resetMatrix_() { - for (int i = 0; i < 9; i++) { - m_matrix[i] = -2; - m_max_dim[i] = -2; - } - } - - private static void transposeMatrix_(int[] matrix) { - int temp1 = matrix[1]; - int temp2 = matrix[2]; - int temp5 = matrix[5]; - - matrix[1] = matrix[3]; - matrix[2] = matrix[6]; - matrix[5] = matrix[7]; - - matrix[3] = temp1; - matrix[6] = temp2; - matrix[7] = temp5; - } - - // Sets the relation predicates from the scl string. - private void setPredicates_(String scl) { - m_scl = scl; - - for (int i = 0; i < 9; i++) { - if (m_scl.charAt(i) != '*') { - m_perform_predicates[i] = true; - m_predicate_count++; - } else - m_perform_predicates[i] = false; - } - } - - // Sets the remaining predicates to false - private void setRemainingPredicatesToFalse_() { - for (int i = 0; i < 9; i++) { - if (m_perform_predicates[i] && m_matrix[i] == -2) { - m_matrix[i] = -1; - m_perform_predicates[i] = false; - } - } - } - - // Checks whether the predicate is known. - private boolean isPredicateKnown_(int predicate) { - assert (m_scl.charAt(predicate) != '*'); - - if (m_matrix[predicate] == -2) - return false; - - if (m_matrix[predicate] == -1) { - m_perform_predicates[predicate] = false; - m_predicate_count--; - return true; - } - - if (m_scl.charAt(predicate) != 'T' && m_scl.charAt(predicate) != 'F') { - if (m_matrix[predicate] < m_max_dim[predicate]) { - return false; - } else { - m_perform_predicates[predicate] = false; - m_predicate_count--; - return true; - } - } else { - m_perform_predicates[predicate] = false; - m_predicate_count--; - return true; - } - } - - // Sets the area-area predicates function. - private void setAreaAreaPredicates_() { - m_predicates_half_edge = Predicates.AreaAreaPredicates; - - m_max_dim[MatrixPredicate.InteriorInterior] = 2; - m_max_dim[MatrixPredicate.InteriorBoundary] = 1; - m_max_dim[MatrixPredicate.InteriorExterior] = 2; - m_max_dim[MatrixPredicate.BoundaryInterior] = 1; - m_max_dim[MatrixPredicate.BoundaryBoundary] = 1; - m_max_dim[MatrixPredicate.BoundaryExterior] = 1; - m_max_dim[MatrixPredicate.ExteriorInterior] = 2; - m_max_dim[MatrixPredicate.ExteriorBoundary] = 1; - m_max_dim[MatrixPredicate.ExteriorExterior] = 2; - - // set predicates that are always true/false - if (m_perform_predicates[MatrixPredicate.ExteriorExterior]) { - m_matrix[MatrixPredicate.ExteriorExterior] = 2; // Always true - m_perform_predicates[MatrixPredicate.ExteriorExterior] = false; - m_predicate_count--; - } - } - - // Sets the area-line predicate function. - private void setAreaLinePredicates_() { - m_predicates_half_edge = Predicates.AreaLinePredicates; - m_predicates_cluster = Predicates.AreaPointPredicates; - - m_max_dim[MatrixPredicate.InteriorInterior] = 1; - m_max_dim[MatrixPredicate.InteriorBoundary] = 0; - m_max_dim[MatrixPredicate.InteriorExterior] = 2; - m_max_dim[MatrixPredicate.BoundaryInterior] = 1; - m_max_dim[MatrixPredicate.BoundaryBoundary] = 0; - m_max_dim[MatrixPredicate.BoundaryExterior] = 1; - m_max_dim[MatrixPredicate.ExteriorInterior] = 1; - m_max_dim[MatrixPredicate.ExteriorBoundary] = 0; - m_max_dim[MatrixPredicate.ExteriorExterior] = 2; - - if (m_perform_predicates[MatrixPredicate.ExteriorExterior]) { - m_matrix[MatrixPredicate.ExteriorExterior] = 2; // Always true - m_perform_predicates[MatrixPredicate.ExteriorExterior] = false; - m_predicate_count--; - } - } - - // Sets the line-line predicates function. - private void setLineLinePredicates_() { - m_predicates_half_edge = Predicates.LineLinePredicates; - m_predicates_cluster = Predicates.LinePointPredicates; - - m_max_dim[MatrixPredicate.InteriorInterior] = 1; - m_max_dim[MatrixPredicate.InteriorBoundary] = 0; - m_max_dim[MatrixPredicate.InteriorExterior] = 1; - m_max_dim[MatrixPredicate.BoundaryInterior] = 0; - m_max_dim[MatrixPredicate.BoundaryBoundary] = 0; - m_max_dim[MatrixPredicate.BoundaryExterior] = 0; - m_max_dim[MatrixPredicate.ExteriorInterior] = 1; - m_max_dim[MatrixPredicate.ExteriorBoundary] = 0; - m_max_dim[MatrixPredicate.ExteriorExterior] = 2; - - // set predicates that are always true/false - if (m_perform_predicates[MatrixPredicate.ExteriorExterior]) { - m_matrix[MatrixPredicate.ExteriorExterior] = 2; // Always true - m_perform_predicates[MatrixPredicate.ExteriorExterior] = false; - m_predicate_count--; - } - } - - // Sets the area-point predicate function. - private void setAreaPointPredicates_() { - m_predicates_cluster = Predicates.AreaPointPredicates; - - m_max_dim[MatrixPredicate.InteriorInterior] = 0; - m_max_dim[MatrixPredicate.InteriorBoundary] = -1; - m_max_dim[MatrixPredicate.InteriorExterior] = 2; - m_max_dim[MatrixPredicate.BoundaryInterior] = 0; - m_max_dim[MatrixPredicate.BoundaryBoundary] = -1; - m_max_dim[MatrixPredicate.BoundaryExterior] = 1; - m_max_dim[MatrixPredicate.ExteriorInterior] = 0; - m_max_dim[MatrixPredicate.ExteriorBoundary] = -1; - m_max_dim[MatrixPredicate.ExteriorExterior] = 2; - - // set predicates that are always true/false - if (m_perform_predicates[MatrixPredicate.InteriorBoundary]) { - m_matrix[MatrixPredicate.InteriorBoundary] = -1; // Always false - m_perform_predicates[MatrixPredicate.InteriorBoundary] = false; - m_predicate_count--; - } - - if (m_perform_predicates[MatrixPredicate.BoundaryBoundary]) { - m_matrix[MatrixPredicate.BoundaryBoundary] = -1; // Always false - m_perform_predicates[MatrixPredicate.BoundaryBoundary] = false; - m_predicate_count--; - } - - if (m_perform_predicates[MatrixPredicate.ExteriorBoundary]) { - m_matrix[MatrixPredicate.ExteriorBoundary] = -1; // Always false - m_perform_predicates[MatrixPredicate.ExteriorBoundary] = false; - m_predicate_count--; - } - - if (m_perform_predicates[MatrixPredicate.ExteriorExterior]) { - m_matrix[MatrixPredicate.ExteriorExterior] = 2; // Always true - m_perform_predicates[MatrixPredicate.ExteriorExterior] = false; - m_predicate_count--; - } - } - - // Sets the line-point predicates function. - private void setLinePointPredicates_() { - m_predicates_cluster = Predicates.LinePointPredicates; - - m_max_dim[MatrixPredicate.InteriorInterior] = 0; - m_max_dim[MatrixPredicate.InteriorBoundary] = -1; - m_max_dim[MatrixPredicate.InteriorExterior] = 1; - m_max_dim[MatrixPredicate.BoundaryInterior] = 0; - m_max_dim[MatrixPredicate.BoundaryBoundary] = -1; - m_max_dim[MatrixPredicate.BoundaryExterior] = 0; - m_max_dim[MatrixPredicate.ExteriorInterior] = 0; - m_max_dim[MatrixPredicate.ExteriorBoundary] = -1; - m_max_dim[MatrixPredicate.ExteriorExterior] = 2; - - // set predicates that are always true/false - if (m_perform_predicates[MatrixPredicate.InteriorBoundary]) { - m_matrix[MatrixPredicate.InteriorBoundary] = -1; // Always false - m_perform_predicates[MatrixPredicate.InteriorBoundary] = false; - m_predicate_count--; - } - - if (m_perform_predicates[MatrixPredicate.BoundaryBoundary]) { - m_matrix[MatrixPredicate.BoundaryBoundary] = -1; // Always false - m_perform_predicates[MatrixPredicate.BoundaryBoundary] = false; - m_predicate_count--; - } - - if (m_perform_predicates[MatrixPredicate.ExteriorBoundary]) { - m_matrix[MatrixPredicate.ExteriorBoundary] = -1; // Always false - m_perform_predicates[MatrixPredicate.ExteriorBoundary] = false; - m_predicate_count--; - } - - if (m_perform_predicates[MatrixPredicate.ExteriorExterior]) { - m_matrix[MatrixPredicate.ExteriorExterior] = 2; // Always true - m_perform_predicates[MatrixPredicate.ExteriorExterior] = false; - m_predicate_count--; - } - } - - // Sets the point-point predicates function. - private void setPointPointPredicates_() { - m_predicates_cluster = Predicates.PointPointPredicates; - - m_max_dim[MatrixPredicate.InteriorInterior] = 0; - m_max_dim[MatrixPredicate.InteriorBoundary] = -1; - m_max_dim[MatrixPredicate.InteriorExterior] = 0; - m_max_dim[MatrixPredicate.BoundaryInterior] = -1; - m_max_dim[MatrixPredicate.BoundaryBoundary] = -1; - m_max_dim[MatrixPredicate.BoundaryExterior] = -1; - m_max_dim[MatrixPredicate.ExteriorInterior] = 0; - m_max_dim[MatrixPredicate.ExteriorBoundary] = -1; - m_max_dim[MatrixPredicate.ExteriorExterior] = 2; - - // set predicates that are always true/false - if (m_perform_predicates[MatrixPredicate.InteriorBoundary]) { - m_matrix[MatrixPredicate.InteriorBoundary] = -1; // Always false - m_perform_predicates[MatrixPredicate.InteriorBoundary] = false; - m_predicate_count--; - } - - if (m_perform_predicates[MatrixPredicate.BoundaryInterior]) { - m_matrix[MatrixPredicate.BoundaryInterior] = -1; // Always false - m_perform_predicates[MatrixPredicate.BoundaryInterior] = false; - m_predicate_count--; - } - - if (m_perform_predicates[MatrixPredicate.BoundaryBoundary]) { - m_matrix[MatrixPredicate.BoundaryBoundary] = -1; // Always false - m_perform_predicates[MatrixPredicate.BoundaryBoundary] = false; - m_predicate_count--; - } - - if (m_perform_predicates[MatrixPredicate.BoundaryExterior]) { - m_matrix[MatrixPredicate.BoundaryExterior] = -1; // Always false - m_perform_predicates[MatrixPredicate.BoundaryExterior] = false; - m_predicate_count--; - } - - if (m_perform_predicates[MatrixPredicate.ExteriorBoundary]) { - m_matrix[MatrixPredicate.ExteriorBoundary] = -1; // Always false - m_perform_predicates[MatrixPredicate.ExteriorBoundary] = false; - m_predicate_count--; - } - - if (m_perform_predicates[MatrixPredicate.ExteriorExterior]) { - m_matrix[MatrixPredicate.ExteriorExterior] = 2; // Always true - m_perform_predicates[MatrixPredicate.ExteriorExterior] = false; - m_predicate_count--; - } - } - - // Invokes the 9 relational predicates of area vs area. - private boolean areaAreaPredicates_(int half_edge, int id_a, int id_b) { - boolean bRelationKnown = true; - - if (m_perform_predicates[MatrixPredicate.InteriorInterior]) { - interiorAreaInteriorArea_(half_edge, id_a, id_b); - bRelationKnown &= isPredicateKnown_( - MatrixPredicate.InteriorInterior); - } - - if (m_perform_predicates[MatrixPredicate.InteriorBoundary]) { - interiorAreaBoundaryArea_(half_edge, id_a, - MatrixPredicate.InteriorBoundary); - bRelationKnown &= isPredicateKnown_( - MatrixPredicate.InteriorBoundary); - } - - if (m_perform_predicates[MatrixPredicate.InteriorExterior]) { - interiorAreaExteriorArea_(half_edge, id_a, id_b, - MatrixPredicate.InteriorExterior); - bRelationKnown &= isPredicateKnown_( - MatrixPredicate.InteriorExterior); - } - - if (m_perform_predicates[MatrixPredicate.BoundaryInterior]) { - interiorAreaBoundaryArea_(half_edge, id_b, - MatrixPredicate.BoundaryInterior); - bRelationKnown &= isPredicateKnown_( - MatrixPredicate.BoundaryInterior); - } - - if (m_perform_predicates[MatrixPredicate.BoundaryBoundary]) { - boundaryAreaBoundaryArea_(half_edge, id_a, id_b); - bRelationKnown &= isPredicateKnown_( - MatrixPredicate.BoundaryBoundary); - } - - if (m_perform_predicates[MatrixPredicate.BoundaryExterior]) { - boundaryAreaExteriorArea_(half_edge, id_a, id_b, - MatrixPredicate.BoundaryExterior); - bRelationKnown &= isPredicateKnown_( - MatrixPredicate.BoundaryExterior); - } - - if (m_perform_predicates[MatrixPredicate.ExteriorInterior]) { - interiorAreaExteriorArea_(half_edge, id_b, id_a, - MatrixPredicate.ExteriorInterior); - bRelationKnown &= isPredicateKnown_( - MatrixPredicate.ExteriorInterior); - } - - if (m_perform_predicates[MatrixPredicate.ExteriorBoundary]) { - boundaryAreaExteriorArea_(half_edge, id_b, id_a, - MatrixPredicate.ExteriorBoundary); - bRelationKnown &= isPredicateKnown_( - MatrixPredicate.ExteriorBoundary); - } - - return bRelationKnown; - } - - private void areaAreaDisjointPredicates_(Polygon polygon_a, Polygon polygon_b) { - m_matrix[MatrixPredicate.InteriorInterior] = -1; - m_matrix[MatrixPredicate.InteriorBoundary] = -1; - m_matrix[MatrixPredicate.BoundaryInterior] = -1; - m_matrix[MatrixPredicate.BoundaryBoundary] = -1; - - areaGeomContainsOrDisjointPredicates_(polygon_a, m_perform_predicates[MatrixPredicate.InteriorExterior] ? MatrixPredicate.InteriorExterior : -1, m_scl.charAt(MatrixPredicate.InteriorExterior), m_perform_predicates[MatrixPredicate.BoundaryExterior] ? MatrixPredicate.BoundaryExterior : -1, m_scl.charAt(MatrixPredicate.BoundaryExterior)); - areaGeomContainsOrDisjointPredicates_(polygon_b, m_perform_predicates[MatrixPredicate.ExteriorInterior] ? MatrixPredicate.ExteriorInterior : -1, m_scl.charAt(MatrixPredicate.ExteriorInterior), m_perform_predicates[MatrixPredicate.ExteriorBoundary] ? MatrixPredicate.ExteriorBoundary : -1, m_scl.charAt(MatrixPredicate.ExteriorBoundary)); - } - - private void areaGeomContainsOrDisjointPredicates_(Polygon polygon, int matrix_interior, char c1, int matrix_boundary, char c2) { - if (matrix_interior != -1 || matrix_boundary != -1) { - boolean has_area = ((c1 != 'T' && c1 != 'F' && matrix_interior != -1) || (c2 != 'T' && c2 != 'F' && matrix_boundary != -1) ? polygon.calculateArea2D() != 0 : true); - - if (has_area) { - if (matrix_interior != -1) - m_matrix[matrix_interior] = 2; - if (matrix_boundary != -1) - m_matrix[matrix_boundary] = 1; - } else { - if (matrix_boundary != -1) - m_matrix[matrix_boundary] = -1; - - if (matrix_interior != -1) { - Envelope2D env = new Envelope2D(); - polygon.queryEnvelope2D(env); - m_matrix[matrix_interior] = (env.getHeight() == 0.0 && env.getWidth() == 0.0 ? 0 : 1); - } - } - } - } - - private void areaAreaContainsPredicates_(Polygon polygon_b) { - m_matrix[MatrixPredicate.InteriorExterior] = 2; // im assuming its a proper contains. - m_matrix[MatrixPredicate.BoundaryInterior] = -1; - m_matrix[MatrixPredicate.BoundaryBoundary] = -1; - m_matrix[MatrixPredicate.BoundaryExterior] = 1; - m_matrix[MatrixPredicate.ExteriorInterior] = -1; - m_matrix[MatrixPredicate.ExteriorBoundary] = -1; - - areaGeomContainsOrDisjointPredicates_(polygon_b, m_perform_predicates[MatrixPredicate.InteriorInterior] ? MatrixPredicate.InteriorInterior : -1, m_scl.charAt(MatrixPredicate.InteriorInterior), m_perform_predicates[MatrixPredicate.InteriorBoundary] ? MatrixPredicate.InteriorBoundary : -1, m_scl.charAt(MatrixPredicate.InteriorBoundary)); - - // all other predicates should already be set by set_area_area_predicates - } - - private void areaAreaWithinPredicates_(Polygon polygon_a) { - areaAreaContainsPredicates_(polygon_a); - transposeMatrix_(m_matrix); - } - - private void areaLineDisjointPredicates_(Polygon polygon, Polyline polyline) { - m_matrix[MatrixPredicate.InteriorInterior] = -1; - m_matrix[MatrixPredicate.InteriorBoundary] = -1; - m_matrix[MatrixPredicate.BoundaryInterior] = -1; - m_matrix[MatrixPredicate.BoundaryBoundary] = -1; - - if (m_perform_predicates[MatrixPredicate.ExteriorInterior]) { - char c = m_scl.charAt(MatrixPredicate.ExteriorInterior); - boolean b_has_length = (c != 'T' && c != 'F' ? polyline.calculateLength2D() != 0 : true); - m_matrix[MatrixPredicate.ExteriorInterior] = (b_has_length ? 1 : 0); - } - - if (m_perform_predicates[MatrixPredicate.ExteriorBoundary]) { - boolean has_non_empty_boundary = Boundary.hasNonEmptyBoundary(polyline, null); - m_matrix[MatrixPredicate.ExteriorBoundary] = has_non_empty_boundary ? 0 : -1; - } - - areaGeomContainsOrDisjointPredicates_(polygon, m_perform_predicates[MatrixPredicate.InteriorExterior] ? MatrixPredicate.InteriorExterior : -1, m_scl.charAt(MatrixPredicate.InteriorExterior), m_perform_predicates[MatrixPredicate.BoundaryExterior] ? MatrixPredicate.BoundaryExterior : -1, m_scl.charAt(MatrixPredicate.BoundaryExterior)); - } - - private void areaLineContainsPredicates_(Polygon polygon, Polyline polyline) { - if (m_perform_predicates[MatrixPredicate.InteriorInterior]) { - char c = m_scl.charAt(MatrixPredicate.InteriorInterior); - boolean b_has_length = (c != 'T' && c != 'F' ? polyline.calculateLength2D() != 0 : true); - m_matrix[MatrixPredicate.InteriorInterior] = (b_has_length ? 1 : 0); - } - - if (m_perform_predicates[MatrixPredicate.InteriorBoundary]) { - boolean has_non_empty_boundary = Boundary.hasNonEmptyBoundary(polyline, null); - m_matrix[MatrixPredicate.InteriorBoundary] = has_non_empty_boundary ? 0 : -1; - } - - m_matrix[MatrixPredicate.InteriorExterior] = 2; //assume polygon has area - m_matrix[MatrixPredicate.BoundaryInterior] = -1; - m_matrix[MatrixPredicate.BoundaryBoundary] = -1; - m_matrix[MatrixPredicate.BoundaryExterior] = 1; //assume polygon has area - m_matrix[MatrixPredicate.ExteriorInterior] = -1; - m_matrix[MatrixPredicate.ExteriorBoundary] = -1; - } - - private void areaPointDisjointPredicates_(Polygon polygon) { - m_matrix[MatrixPredicate.InteriorInterior] = -1; - m_matrix[MatrixPredicate.BoundaryInterior] = -1; - m_matrix[MatrixPredicate.ExteriorInterior] = 0; - - areaGeomContainsOrDisjointPredicates_(polygon, m_perform_predicates[MatrixPredicate.InteriorExterior] ? MatrixPredicate.InteriorExterior : -1, m_scl.charAt(MatrixPredicate.InteriorExterior), m_perform_predicates[MatrixPredicate.BoundaryExterior] ? MatrixPredicate.BoundaryExterior : -1, m_scl.charAt(MatrixPredicate.BoundaryExterior)); - } - - private void areaPointContainsPredicates_(Polygon polygon) { - m_matrix[MatrixPredicate.InteriorInterior] = 0; - m_matrix[MatrixPredicate.InteriorExterior] = 2; //assume polygon has area - m_matrix[MatrixPredicate.BoundaryInterior] = -1; - m_matrix[MatrixPredicate.BoundaryExterior] = 1; //assume polygon has area - m_matrix[MatrixPredicate.ExteriorInterior] = -1; - } - - private void lineLineDisjointPredicates_(Polyline polyline_a, - Polyline polyline_b) { - m_matrix[MatrixPredicate.InteriorInterior] = -1; - m_matrix[MatrixPredicate.InteriorBoundary] = -1; - m_matrix[MatrixPredicate.BoundaryInterior] = -1; - m_matrix[MatrixPredicate.BoundaryBoundary] = -1; - - if (m_perform_predicates[MatrixPredicate.InteriorExterior]) { - char c = m_scl.charAt(MatrixPredicate.InteriorExterior); - boolean b_has_length = (c != 'T' && c != 'F' ? polyline_a.calculateLength2D() != 0 : true); - m_matrix[MatrixPredicate.InteriorExterior] = (b_has_length ? 1 : 0); - } - - if (m_perform_predicates[MatrixPredicate.BoundaryExterior]) { - boolean has_non_empty_boundary_a = Boundary.hasNonEmptyBoundary(polyline_a, null); - m_matrix[MatrixPredicate.BoundaryExterior] = has_non_empty_boundary_a ? 0 : -1; - } - - if (m_perform_predicates[MatrixPredicate.ExteriorInterior]) { - char c = m_scl.charAt(MatrixPredicate.ExteriorInterior); - boolean b_has_length = (c != 'T' && c != 'F' ? polyline_b.calculateLength2D() != 0 : true); - m_matrix[MatrixPredicate.ExteriorInterior] = (b_has_length ? 1 : 0); - } - - if (m_perform_predicates[MatrixPredicate.ExteriorBoundary]) { - boolean has_non_empty_boundary_b = Boundary.hasNonEmptyBoundary(polyline_b, null); - m_matrix[MatrixPredicate.ExteriorBoundary] = has_non_empty_boundary_b ? 0 : -1; - } - } - - private void linePointDisjointPredicates_(Polyline polyline) { - m_matrix[MatrixPredicate.InteriorInterior] = -1; - m_matrix[MatrixPredicate.BoundaryInterior] = -1; - - if (m_perform_predicates[MatrixPredicate.InteriorExterior]) { - char c = m_scl.charAt(MatrixPredicate.InteriorExterior); - boolean b_has_length = (c != 'T' && c != 'F' ? polyline.calculateLength2D() != 0 : true); - m_matrix[MatrixPredicate.InteriorExterior] = (b_has_length ? 1 : 0); - } - - if (m_perform_predicates[MatrixPredicate.BoundaryExterior]) { - boolean has_non_empty_boundary = Boundary.hasNonEmptyBoundary(polyline, null); - m_matrix[MatrixPredicate.BoundaryExterior] = (has_non_empty_boundary ? 0 : -1); - } - - m_matrix[MatrixPredicate.ExteriorInterior] = 0; - } - - private void pointPointDisjointPredicates_() { - m_matrix[MatrixPredicate.InteriorInterior] = -1; - m_matrix[MatrixPredicate.InteriorExterior] = 0; - m_matrix[MatrixPredicate.ExteriorInterior] = 0; - } - - // Invokes the 9 relational predicates of area vs Line. - private boolean areaLinePredicates_(int half_edge, int id_a, int id_b) { - boolean bRelationKnown = true; - - if (m_perform_predicates[MatrixPredicate.InteriorInterior]) { - interiorAreaInteriorLine_(half_edge, id_a, id_b); - bRelationKnown &= isPredicateKnown_(MatrixPredicate.InteriorInterior); - } - - if (m_perform_predicates[MatrixPredicate.InteriorBoundary]) { - interiorAreaBoundaryLine_(half_edge, id_a, id_b, m_cluster_index_b); - bRelationKnown &= isPredicateKnown_(MatrixPredicate.InteriorBoundary); - } - - if (m_perform_predicates[MatrixPredicate.InteriorExterior]) { - interiorAreaExteriorLine_(half_edge, id_a, id_b); - bRelationKnown &= isPredicateKnown_(MatrixPredicate.InteriorExterior); - } - - if (m_perform_predicates[MatrixPredicate.BoundaryInterior]) { - boundaryAreaInteriorLine_(half_edge, id_a, id_b, m_cluster_index_b); - bRelationKnown &= isPredicateKnown_(MatrixPredicate.BoundaryInterior); - } - - if (m_perform_predicates[MatrixPredicate.BoundaryBoundary]) { - boundaryAreaBoundaryLine_(half_edge, id_a, id_b, m_cluster_index_b); - bRelationKnown &= isPredicateKnown_(MatrixPredicate.BoundaryBoundary); - } - - if (m_perform_predicates[MatrixPredicate.BoundaryExterior]) { - boundaryAreaExteriorLine_(half_edge, id_a, id_b); - bRelationKnown &= isPredicateKnown_(MatrixPredicate.BoundaryExterior); - } - - if (m_perform_predicates[MatrixPredicate.ExteriorInterior]) { - exteriorAreaInteriorLine_(half_edge, id_a); - bRelationKnown &= isPredicateKnown_(MatrixPredicate.ExteriorInterior); - } - - if (m_perform_predicates[MatrixPredicate.ExteriorBoundary]) { - exteriorAreaBoundaryLine_(half_edge, id_a, id_b, m_cluster_index_b); - bRelationKnown &= isPredicateKnown_(MatrixPredicate.ExteriorBoundary); - } - - return bRelationKnown; - } - - // Invokes the 9 relational predicates of Line vs Line. - private boolean lineLinePredicates_(int half_edge, int id_a, int id_b) { - boolean bRelationKnown = true; - - if (m_perform_predicates[MatrixPredicate.InteriorInterior]) { - interiorLineInteriorLine_(half_edge, id_a, id_b, m_cluster_index_a, m_cluster_index_b); - bRelationKnown &= isPredicateKnown_(MatrixPredicate.InteriorInterior); - } - - if (m_perform_predicates[MatrixPredicate.InteriorBoundary]) { - interiorLineBoundaryLine_(half_edge, id_a, id_b, m_cluster_index_a, m_cluster_index_b, MatrixPredicate.InteriorBoundary); - bRelationKnown &= isPredicateKnown_(MatrixPredicate.InteriorBoundary); - } - - if (m_perform_predicates[MatrixPredicate.InteriorExterior]) { - interiorLineExteriorLine_(half_edge, id_a, id_b, MatrixPredicate.InteriorExterior); - bRelationKnown &= isPredicateKnown_(MatrixPredicate.InteriorExterior); - } - - if (m_perform_predicates[MatrixPredicate.BoundaryInterior]) { - interiorLineBoundaryLine_(half_edge, id_b, id_a, m_cluster_index_b, m_cluster_index_a, MatrixPredicate.BoundaryInterior); - bRelationKnown &= isPredicateKnown_(MatrixPredicate.BoundaryInterior); - } - - if (m_perform_predicates[MatrixPredicate.BoundaryBoundary]) { - boundaryLineBoundaryLine_(half_edge, id_a, id_b, m_cluster_index_a, m_cluster_index_b); - bRelationKnown &= isPredicateKnown_(MatrixPredicate.BoundaryBoundary); - } - - if (m_perform_predicates[MatrixPredicate.BoundaryExterior]) { - boundaryLineExteriorLine_(half_edge, id_a, id_b, m_cluster_index_a, MatrixPredicate.BoundaryExterior); - bRelationKnown &= isPredicateKnown_(MatrixPredicate.BoundaryExterior); - } - - if (m_perform_predicates[MatrixPredicate.ExteriorInterior]) { - interiorLineExteriorLine_(half_edge, id_b, id_a, MatrixPredicate.ExteriorInterior); - bRelationKnown &= isPredicateKnown_(MatrixPredicate.ExteriorInterior); - } - - if (m_perform_predicates[MatrixPredicate.ExteriorBoundary]) { - boundaryLineExteriorLine_(half_edge, id_b, id_a, m_cluster_index_b, MatrixPredicate.ExteriorBoundary); - bRelationKnown &= isPredicateKnown_(MatrixPredicate.ExteriorBoundary); - } - - return bRelationKnown; - } - - // Invokes the 9 relational predicates of area vs Point. - private boolean areaPointPredicates_(int cluster, int id_a, int id_b) { - boolean bRelationKnown = true; - - if (m_perform_predicates[MatrixPredicate.InteriorInterior]) { - interiorAreaInteriorPoint_(cluster, id_a); - bRelationKnown &= isPredicateKnown_(MatrixPredicate.InteriorInterior); - } - - if (m_perform_predicates[MatrixPredicate.InteriorExterior]) { - interiorAreaExteriorPoint_(cluster, id_a); - bRelationKnown &= isPredicateKnown_(MatrixPredicate.InteriorExterior); - } - - if (m_perform_predicates[MatrixPredicate.BoundaryInterior]) { - boundaryAreaInteriorPoint_(cluster, id_a, id_b); - bRelationKnown &= isPredicateKnown_(MatrixPredicate.BoundaryInterior); - } - - if (m_perform_predicates[MatrixPredicate.BoundaryExterior]) { - boundaryAreaExteriorPoint_(cluster, id_a); - bRelationKnown &= isPredicateKnown_(MatrixPredicate.BoundaryExterior); - } - - if (m_perform_predicates[MatrixPredicate.ExteriorInterior]) { - exteriorAreaInteriorPoint_(cluster, id_a); - bRelationKnown &= isPredicateKnown_(MatrixPredicate.ExteriorInterior); - } - - return bRelationKnown; - } - - // Invokes the 9 relational predicates of Line vs Point. - private boolean linePointPredicates_(int cluster, int id_a, int id_b) { - boolean bRelationKnown = true; - - if (m_perform_predicates[MatrixPredicate.InteriorInterior]) { - interiorLineInteriorPoint_(cluster, id_a, id_b, m_cluster_index_a); - bRelationKnown &= isPredicateKnown_(MatrixPredicate.InteriorInterior); - } - - if (m_perform_predicates[MatrixPredicate.InteriorExterior]) { - interiorLineExteriorPoint_(cluster, id_a, id_b, m_cluster_index_a); - bRelationKnown &= isPredicateKnown_(MatrixPredicate.InteriorExterior); - } - - if (m_perform_predicates[MatrixPredicate.BoundaryInterior]) { - boundaryLineInteriorPoint_(cluster, id_a, id_b, m_cluster_index_a); - bRelationKnown &= isPredicateKnown_(MatrixPredicate.BoundaryInterior); - } - - if (m_perform_predicates[MatrixPredicate.BoundaryExterior]) { - boundaryLineExteriorPoint_(cluster, id_a, id_b, m_cluster_index_a); - bRelationKnown &= isPredicateKnown_(MatrixPredicate.BoundaryExterior); - } - - if (m_perform_predicates[MatrixPredicate.ExteriorInterior]) { - exteriorLineInteriorPoint_(cluster, id_a, id_b); - bRelationKnown &= isPredicateKnown_(MatrixPredicate.ExteriorInterior); - } - - return bRelationKnown; - } - - // Invokes the 9 relational predicates of Point vs Point. - private boolean pointPointPredicates_(int cluster, int id_a, int id_b) { - boolean bRelationKnown = true; - - if (m_perform_predicates[MatrixPredicate.InteriorInterior]) { - interiorPointInteriorPoint_(cluster, id_a, id_b); - bRelationKnown &= isPredicateKnown_(MatrixPredicate.InteriorInterior); - } - - if (m_perform_predicates[MatrixPredicate.InteriorExterior]) { - interiorPointExteriorPoint_(cluster, id_a, id_b, MatrixPredicate.InteriorExterior); - bRelationKnown &= isPredicateKnown_(MatrixPredicate.InteriorExterior); - } - - if (m_perform_predicates[MatrixPredicate.ExteriorInterior]) { - interiorPointExteriorPoint_(cluster, id_b, id_a, MatrixPredicate.ExteriorInterior); - bRelationKnown &= isPredicateKnown_(MatrixPredicate.ExteriorInterior); - } - - return bRelationKnown; - } - - // Relational predicate to determine if the interior of area A intersects - // with the interior of area B. - private void interiorAreaInteriorArea_(int half_edge, int id_a, int id_b) { - if (m_matrix[MatrixPredicate.InteriorInterior] == 2) - return; - - int faceParentage = m_topo_graph.getHalfEdgeFaceParentage(half_edge); - - if ((faceParentage & id_a) != 0 && (faceParentage & id_b) != 0) - m_matrix[MatrixPredicate.InteriorInterior] = 2; - } - - // Relational predicate to determine if the interior of area A intersects - // with the boundary of area B. - private void interiorAreaBoundaryArea_(int half_edge, int id_a, - int predicate) { - if (m_matrix[predicate] == 1) - return; - - int faceParentage = m_topo_graph.getHalfEdgeFaceParentage(half_edge); - int twinFaceParentage = m_topo_graph - .getHalfEdgeFaceParentage(m_topo_graph - .getHalfEdgeTwin(half_edge)); - - if ((faceParentage & id_a) != 0 && (twinFaceParentage & id_a) != 0) - m_matrix[predicate] = 1; - } - - // Relational predicate to determine if the interior of area A intersects - // with the exterior of area B. - private void interiorAreaExteriorArea_(int half_edge, int id_a, int id_b, - int predicate) { - if (m_matrix[predicate] == 2) - return; - - int faceParentage = m_topo_graph.getHalfEdgeFaceParentage(half_edge); - - if ((faceParentage & id_a) != 0 && (faceParentage & id_b) == 0) - m_matrix[predicate] = 2; - - } - - // Relational predicate to determine if the boundary of area A intersects - // with the boundary of area B. - private void boundaryAreaBoundaryArea_(int half_edge, int id_a, int id_b) { - if (m_matrix[MatrixPredicate.BoundaryBoundary] == 1) - return; - - int parentage = m_topo_graph.getHalfEdgeParentage(half_edge); - - if ((parentage & id_a) != 0 && (parentage & id_b) != 0) { - m_matrix[MatrixPredicate.BoundaryBoundary] = 1; - return; - } - - if (m_matrix[MatrixPredicate.BoundaryBoundary] != 0) { - if (m_topo_graph.getHalfEdgeUserIndex(m_topo_graph - .getHalfEdgePrev(m_topo_graph.getHalfEdgeTwin(half_edge)), - m_visited_index) != 1) { - int cluster = m_topo_graph.getHalfEdgeTo(half_edge); - int clusterParentage = m_topo_graph - .getClusterParentage(cluster); - - if ((clusterParentage & id_a) != 0 - && (clusterParentage & id_b) != 0) { - m_matrix[MatrixPredicate.BoundaryBoundary] = 0; - } - } - } - } - - // Relational predicate to determine if the boundary of area A intersects - // with the exterior of area B. - private void boundaryAreaExteriorArea_(int half_edge, int id_a, int id_b, - int predicate) { - if (m_matrix[predicate] == 1) - return; - - int faceParentage = m_topo_graph.getHalfEdgeFaceParentage(half_edge); - int twinFaceParentage = m_topo_graph - .getHalfEdgeFaceParentage(m_topo_graph - .getHalfEdgeTwin(half_edge)); - - if ((faceParentage & id_b) == 0 && (twinFaceParentage & id_b) == 0) - m_matrix[predicate] = 1; - } - - // Relational predicate to determine if the interior of area A intersects - // with the interior of Line B. - private void interiorAreaInteriorLine_(int half_edge, int id_a, int id_b) { - if (m_matrix[MatrixPredicate.InteriorInterior] == 1) - return; - - int faceParentage = m_topo_graph.getHalfEdgeFaceParentage(half_edge); - int twinFaceParentage = m_topo_graph - .getHalfEdgeFaceParentage(m_topo_graph - .getHalfEdgeTwin(half_edge)); - - if ((faceParentage & id_a) != 0 && (twinFaceParentage & id_a) != 0) - m_matrix[MatrixPredicate.InteriorInterior] = 1; - } - - // Relational predicate to determine if the interior of area A intersects - // with the boundary of Line B. - private void interiorAreaBoundaryLine_(int half_edge, int id_a, int id_b, - int cluster_index_b) { - if (m_matrix[MatrixPredicate.InteriorBoundary] == 0) - return; - - if (m_topo_graph.getHalfEdgeUserIndex(m_topo_graph - .getHalfEdgePrev(m_topo_graph.getHalfEdgeTwin(half_edge)), - m_visited_index) != 1) { - int cluster = m_topo_graph.getHalfEdgeTo(half_edge); - int clusterParentage = m_topo_graph.getClusterParentage(cluster); - - if ((clusterParentage & id_a) == 0) { - int faceParentage = m_topo_graph - .getHalfEdgeFaceParentage(half_edge); - - if ((faceParentage & id_a) != 0) { - int index = m_topo_graph.getClusterUserIndex(cluster, - cluster_index_b); - - if ((clusterParentage & id_b) != 0 && (index % 2 != 0)) { - assert (index != -1); - m_matrix[MatrixPredicate.InteriorBoundary] = 0; - } - } - } - } - } - - private void interiorAreaExteriorLine_(int half_edge, int id_a, int id_b) { - if (m_matrix[MatrixPredicate.InteriorExterior] == 2) - return; - - int half_edge_parentage = m_topo_graph.getHalfEdgeParentage(half_edge); - - if ((half_edge_parentage & id_a) != 0) {//half edge of polygon - m_matrix[MatrixPredicate.InteriorExterior] = 2; - } - } - - // Relational predicate to determine if the boundary of area A intersects - // with the interior of Line B. - private void boundaryAreaInteriorLine_(int half_edge, int id_a, int id_b, - int cluster_index_b) { - if (m_matrix[MatrixPredicate.BoundaryInterior] == 1) - return; - - int parentage = m_topo_graph.getHalfEdgeParentage(half_edge); - - if ((parentage & id_a) != 0 && (parentage & id_b) != 0) { - m_matrix[MatrixPredicate.BoundaryInterior] = 1; - return; - } - - if (m_matrix[MatrixPredicate.BoundaryInterior] != 0) { - if (m_topo_graph.getHalfEdgeUserIndex(m_topo_graph - .getHalfEdgePrev(m_topo_graph.getHalfEdgeTwin(half_edge)), - m_visited_index) != 1) { - int cluster = m_topo_graph.getHalfEdgeTo(half_edge); - int clusterParentage = m_topo_graph - .getClusterParentage(cluster); - - if ((clusterParentage & id_a) != 0) { - int index = m_topo_graph.getClusterUserIndex(cluster, - cluster_index_b); - - if ((clusterParentage & id_b) != 0 && (index % 2 == 0)) { - assert (index != -1); - m_matrix[MatrixPredicate.BoundaryInterior] = 0; - } - } - } - } - } - - // Relational predicate to determine if the boundary of area A intersects - // with the boundary of Line B. - private void boundaryAreaBoundaryLine_(int half_edge, int id_a, int id_b, - int cluster_index_b) { - if (m_matrix[MatrixPredicate.BoundaryBoundary] == 0) - return; - - if (m_topo_graph.getHalfEdgeUserIndex(m_topo_graph - .getHalfEdgePrev(m_topo_graph.getHalfEdgeTwin(half_edge)), - m_visited_index) != 1) { - int cluster = m_topo_graph.getHalfEdgeTo(half_edge); - int clusterParentage = m_topo_graph.getClusterParentage(cluster); - - if ((clusterParentage & id_a) != 0) { - int index = m_topo_graph.getClusterUserIndex(cluster, - cluster_index_b); - - if ((clusterParentage & id_b) != 0 && (index % 2 != 0)) { - assert (index != -1); - m_matrix[MatrixPredicate.BoundaryBoundary] = 0; - } - } - } - } - - // Relational predicate to determine if the boundary of area A intersects - // with the exterior of Line B. - private void boundaryAreaExteriorLine_(int half_edge, int id_a, int id_b) { - if (m_matrix[MatrixPredicate.BoundaryExterior] == 1) - return; - - int parentage = m_topo_graph.getHalfEdgeParentage(half_edge); - - if ((parentage & id_a) != 0 && (parentage & id_b) == 0) - m_matrix[MatrixPredicate.BoundaryExterior] = 1; - - } - - // Relational predicate to determine if the exterior of area A intersects - // with the interior of Line B. - private void exteriorAreaInteriorLine_(int half_edge, int id_a) { - if (m_matrix[MatrixPredicate.ExteriorInterior] == 1) - return; - - int faceParentage = m_topo_graph.getHalfEdgeFaceParentage(half_edge); - int twinFaceParentage = m_topo_graph - .getHalfEdgeFaceParentage(m_topo_graph - .getHalfEdgeTwin(half_edge)); - - if ((faceParentage & id_a) == 0 && (twinFaceParentage & id_a) == 0) - m_matrix[MatrixPredicate.ExteriorInterior] = 1; - } - - // Relational predicate to determine if the exterior of area A intersects - // with the boundary of Line B. - private void exteriorAreaBoundaryLine_(int half_edge, int id_a, int id_b, - int cluster_index_b) { - if (m_matrix[MatrixPredicate.ExteriorBoundary] == 0) - return; - - if (m_topo_graph.getHalfEdgeUserIndex(m_topo_graph - .getHalfEdgePrev(m_topo_graph.getHalfEdgeTwin(half_edge)), - m_visited_index) != 1) { - int cluster = m_topo_graph.getHalfEdgeTo(half_edge); - int clusterParentage = m_topo_graph.getClusterParentage(cluster); - - if ((clusterParentage & id_a) == 0) { - int faceParentage = m_topo_graph - .getHalfEdgeFaceParentage(half_edge); - - if ((faceParentage & id_a) == 0) { - assert ((m_topo_graph.getHalfEdgeParentage(m_topo_graph - .getHalfEdgeTwin(half_edge)) & id_a) == 0); - - int index = m_topo_graph.getClusterUserIndex(cluster, - cluster_index_b); - - if ((clusterParentage & id_b) != 0 && (index % 2 != 0)) { - assert (index != -1); - m_matrix[MatrixPredicate.ExteriorBoundary] = 0; - } - } - } - } - } - - // Relational predicate to determine if the interior of Line A intersects - // with the interior of Line B. - private void interiorLineInteriorLine_(int half_edge, int id_a, int id_b, - int cluster_index_a, int cluster_index_b) { - if (m_matrix[MatrixPredicate.InteriorInterior] == 1) - return; - - int parentage = m_topo_graph.getHalfEdgeParentage(half_edge); - - if ((parentage & id_a) != 0 && (parentage & id_b) != 0) { - m_matrix[MatrixPredicate.InteriorInterior] = 1; - return; - } - - if (m_matrix[MatrixPredicate.InteriorInterior] != 0) { - if (m_topo_graph.getHalfEdgeUserIndex(m_topo_graph - .getHalfEdgePrev(m_topo_graph.getHalfEdgeTwin(half_edge)), - m_visited_index) != 1) { - int cluster = m_topo_graph.getHalfEdgeTo(half_edge); - int clusterParentage = m_topo_graph - .getClusterParentage(cluster); - - if ((clusterParentage & id_a) != 0 - && (clusterParentage & id_b) != 0) { - int index_a = m_topo_graph.getClusterUserIndex(cluster, - cluster_index_a); - int index_b = m_topo_graph.getClusterUserIndex(cluster, - cluster_index_b); - assert (index_a != -1); - assert (index_b != -1); - - if ((index_a % 2 == 0) && (index_b % 2 == 0)) { - assert ((m_topo_graph.getClusterParentage(cluster) & id_a) != 0 && (m_topo_graph - .getClusterParentage(cluster) & id_b) != 0); - m_matrix[MatrixPredicate.InteriorInterior] = 0; - } - } - } - } - } - - // Relational predicate to determine of the interior of LineA intersects - // with the boundary of Line B. - private void interiorLineBoundaryLine_(int half_edge, int id_a, int id_b, - int cluster_index_a, int cluster_index_b, int predicate) { - if (m_matrix[predicate] == 0) - return; - - if (m_topo_graph.getHalfEdgeUserIndex(m_topo_graph - .getHalfEdgePrev(m_topo_graph.getHalfEdgeTwin(half_edge)), - m_visited_index) != 1) { - int cluster = m_topo_graph.getHalfEdgeTo(half_edge); - int clusterParentage = m_topo_graph.getClusterParentage(cluster); - - if ((clusterParentage & id_a) != 0 - && (clusterParentage & id_b) != 0) { - int index_a = m_topo_graph.getClusterUserIndex(cluster, - cluster_index_a); - int index_b = m_topo_graph.getClusterUserIndex(cluster, - cluster_index_b); - assert (index_a != -1); - assert (index_b != -1); - - if ((index_a % 2 == 0) && (index_b % 2 != 0)) { - assert ((m_topo_graph.getClusterParentage(cluster) & id_a) != 0 && (m_topo_graph - .getClusterParentage(cluster) & id_b) != 0); - m_matrix[predicate] = 0; - } - } - } - } - - // Relational predicate to determine if the interior of Line A intersects - // with the exterior of Line B. - private void interiorLineExteriorLine_(int half_edge, int id_a, int id_b, - int predicate) { - if (m_matrix[predicate] == 1) - return; - - int parentage = m_topo_graph.getHalfEdgeParentage(half_edge); - - if ((parentage & id_a) != 0 && (parentage & id_b) == 0) - m_matrix[predicate] = 1; - } - - // Relational predicate to determine if the boundary of Line A intersects - // with the boundary of Line B. - private void boundaryLineBoundaryLine_(int half_edge, int id_a, int id_b, - int cluster_index_a, int cluster_index_b) { - if (m_matrix[MatrixPredicate.BoundaryBoundary] == 0) - return; - - if (m_topo_graph.getHalfEdgeUserIndex(m_topo_graph - .getHalfEdgePrev(m_topo_graph.getHalfEdgeTwin(half_edge)), - m_visited_index) != 1) { - int cluster = m_topo_graph.getHalfEdgeTo(half_edge); - int clusterParentage = m_topo_graph.getClusterParentage(cluster); - - if ((clusterParentage & id_a) != 0 - && (clusterParentage & id_b) != 0) { - int index_a = m_topo_graph.getClusterUserIndex(cluster, - cluster_index_a); - int index_b = m_topo_graph.getClusterUserIndex(cluster, - cluster_index_b); - assert (index_a != -1); - assert (index_b != -1); - - if ((index_a % 2 != 0) && (index_b % 2 != 0)) { - assert ((m_topo_graph.getClusterParentage(cluster) & id_a) != 0 && (m_topo_graph - .getClusterParentage(cluster) & id_b) != 0); - m_matrix[MatrixPredicate.BoundaryBoundary] = 0; - } - } - } - } - - // Relational predicate to determine if the boundary of Line A intersects - // with the exterior of Line B. - private void boundaryLineExteriorLine_(int half_edge, int id_a, int id_b, - int cluster_index_a, int predicate) { - if (m_matrix[predicate] == 0) - return; - - if (m_topo_graph.getHalfEdgeUserIndex(m_topo_graph - .getHalfEdgePrev(m_topo_graph.getHalfEdgeTwin(half_edge)), - m_visited_index) != 1) { - int cluster = m_topo_graph.getHalfEdgeTo(half_edge); - int clusterParentage = m_topo_graph.getClusterParentage(cluster); - - if ((clusterParentage & id_b) == 0) { - int index = m_topo_graph.getClusterUserIndex(cluster, - cluster_index_a); - assert (index != -1); - - if (index % 2 != 0) { - assert ((m_topo_graph.getClusterParentage(cluster) & id_a) != 0); - m_matrix[predicate] = 0; - } - } - } - } - - // Relational predicate to determine if the interior of area A intersects - // with the interior of Point B. - private void interiorAreaInteriorPoint_(int cluster, int id_a) { - if (m_matrix[MatrixPredicate.InteriorInterior] == 0) - return; - - int clusterParentage = m_topo_graph.getClusterParentage(cluster); - - if ((clusterParentage & id_a) == 0) { - int chain = m_topo_graph.getClusterChain(cluster); - int chainParentage = m_topo_graph.getChainParentage(chain); - - if ((chainParentage & id_a) != 0) { - m_matrix[MatrixPredicate.InteriorInterior] = 0; - } - } - } - - private void interiorAreaExteriorPoint_(int cluster, int id_a) { - if (m_matrix[MatrixPredicate.InteriorExterior] == 2) - return; - - int cluster_parentage = m_topo_graph.getClusterParentage(cluster); - - if ((cluster_parentage & id_a) != 0) { - m_matrix[MatrixPredicate.InteriorExterior] = 2; - } - } - - // Relational predicate to determine if the boundary of area A intersects - // with the interior of Point B. - private void boundaryAreaInteriorPoint_(int cluster, int id_a, int id_b) { - if (m_matrix[MatrixPredicate.BoundaryInterior] == 0) - return; - - int clusterParentage = m_topo_graph.getClusterParentage(cluster); - - if ((clusterParentage & id_a) != 0 && (clusterParentage & id_b) != 0) { - m_matrix[MatrixPredicate.BoundaryInterior] = 0; - } - } - - private void boundaryAreaExteriorPoint_(int cluster, int id_a) { - if (m_matrix[MatrixPredicate.BoundaryExterior] == 1) - return; - - int cluster_parentage = m_topo_graph.getClusterParentage(cluster); - - if ((cluster_parentage & id_a) != 0) { - m_matrix[MatrixPredicate.BoundaryExterior] = 1; - } - } - - // Relational predicate to determine if the exterior of area A intersects - // with the interior of Point B. - private void exteriorAreaInteriorPoint_(int cluster, int id_a) { - if (m_matrix[MatrixPredicate.ExteriorInterior] == 0) - return; - - int clusterParentage = m_topo_graph.getClusterParentage(cluster); - - if ((clusterParentage & id_a) == 0) { - int chain = m_topo_graph.getClusterChain(cluster); - int chainParentage = m_topo_graph.getChainParentage(chain); - - if ((chainParentage & id_a) == 0) { - m_matrix[MatrixPredicate.ExteriorInterior] = 0; - } - } - } - - // Relational predicate to determine if the interior of Line A intersects - // with the interior of Point B. - private void interiorLineInteriorPoint_(int cluster, int id_a, int id_b, - int cluster_index_a) { - if (m_matrix[MatrixPredicate.InteriorInterior] == 0) - return; - - int clusterParentage = m_topo_graph.getClusterParentage(cluster); - - if ((clusterParentage & id_a) != 0 && (clusterParentage & id_b) != 0) { - int index = m_topo_graph.getClusterUserIndex(cluster, - cluster_index_a); - - if (index % 2 == 0) { - m_matrix[MatrixPredicate.InteriorInterior] = 0; - } - } - } - - private void interiorLineExteriorPoint_(int cluster, int id_a, int id_b, int cluster_index_a) { - if (m_matrix[MatrixPredicate.InteriorExterior] == 1) - return; - - int half_edge_a = m_topo_graph.getClusterHalfEdge(cluster); - - if (half_edge_a != -1) { - m_matrix[MatrixPredicate.InteriorExterior] = 1; - return; - } - - if (m_matrix[MatrixPredicate.InteriorExterior] != 0) { - int clusterParentage = m_topo_graph.getClusterParentage(cluster); - - if ((clusterParentage & id_b) == 0) { - assert (m_topo_graph.getClusterUserIndex(cluster, cluster_index_a) % 2 == 0); - m_matrix[MatrixPredicate.InteriorExterior] = 0; - return; - } - } - - return; - } - - // Relational predicate to determine if the boundary of Line A intersects - // with the interior of Point B. - private void boundaryLineInteriorPoint_(int cluster, int id_a, int id_b, - int cluster_index_a) { - if (m_matrix[MatrixPredicate.BoundaryInterior] == 0) - return; - - int clusterParentage = m_topo_graph.getClusterParentage(cluster); - - if ((clusterParentage & id_a) != 0 && (clusterParentage & id_b) != 0) { - int index = m_topo_graph.getClusterUserIndex(cluster, - cluster_index_a); - - if (index % 2 != 0) { - m_matrix[MatrixPredicate.BoundaryInterior] = 0; - } - } - } - - // Relational predicate to determine if the boundary of Line A intersects - // with the exterior of Point B. - private void boundaryLineExteriorPoint_(int cluster, int id_a, int id_b, - int cluster_index_a) { - if (m_matrix[MatrixPredicate.BoundaryExterior] == 0) - return; - - int clusterParentage = m_topo_graph.getClusterParentage(cluster); - - if ((clusterParentage & id_a) != 0 && (clusterParentage & id_b) == 0) { - int index = m_topo_graph.getClusterUserIndex(cluster, - cluster_index_a); - - if (index % 2 != 0) { - m_matrix[MatrixPredicate.BoundaryExterior] = 0; - } - } - } - - // Relational predicate to determine if the exterior of Line A intersects - // with the interior of Point B. - private void exteriorLineInteriorPoint_(int cluster, int id_a, int id_b) { - if (m_matrix[MatrixPredicate.ExteriorInterior] == 0) - return; - - int clusterParentage = m_topo_graph.getClusterParentage(cluster); - - if ((clusterParentage & id_a) == 0 && (clusterParentage & id_b) != 0) { - m_matrix[MatrixPredicate.ExteriorInterior] = 0; - } - } - - // Relational predicate to determine if the interior of Point A intersects - // with the interior of Point B. - private void interiorPointInteriorPoint_(int cluster, int id_a, int id_b) { - if (m_matrix[MatrixPredicate.InteriorInterior] == 0) - return; - - int clusterParentage = m_topo_graph.getClusterParentage(cluster); - - if ((clusterParentage & id_a) != 0 && (clusterParentage & id_b) != 0) { - m_matrix[MatrixPredicate.InteriorInterior] = 0; - } - } - - // Relational predicate to determine if the interior of Point A intersects - // with the exterior of Point B. - private void interiorPointExteriorPoint_(int cluster, int id_a, int id_b, - int predicate) { - if (m_matrix[predicate] == 0) - return; - - int clusterParentage = m_topo_graph.getClusterParentage(cluster); - - if ((clusterParentage & id_a) != 0 && (clusterParentage & id_b) == 0) { - m_matrix[predicate] = 0; - } - } - - // Computes the 9 intersection relationships of boundary, interior, and - // exterior of geometry_a vs geometry_b using the Topo_graph for area/area, - // area/Line, and Line/Line relations - private void computeMatrixTopoGraphHalfEdges_(int geometry_a, int geometry_b) { - boolean bRelationKnown = false; - - int id_a = m_topo_graph.getGeometryID(geometry_a); - int id_b = m_topo_graph.getGeometryID(geometry_b); - - m_visited_index = m_topo_graph.createUserIndexForHalfEdges(); - - for (int cluster = m_topo_graph.getFirstCluster(); cluster != -1; cluster = m_topo_graph - .getNextCluster(cluster)) { - int first_half_edge = m_topo_graph.getClusterHalfEdge(cluster); - if (first_half_edge == -1) { - if (m_predicates_cluster != -1) { - // Treat cluster as an interior point - switch (m_predicates_cluster) { - case Predicates.AreaPointPredicates: - bRelationKnown = areaPointPredicates_(cluster, id_a, id_b); - break; - case Predicates.LinePointPredicates: - bRelationKnown = linePointPredicates_(cluster, id_a, id_b); - break; - default: - throw GeometryException.GeometryInternalError(); - } - } - - continue; - } - - int next_half_edge = first_half_edge; - - do { - int half_edge = next_half_edge; - int visited = m_topo_graph.getHalfEdgeUserIndex(half_edge, - m_visited_index); - - if (visited != 1) { - do { - // Invoke relational predicates - switch (m_predicates_half_edge) { - case Predicates.AreaAreaPredicates: - bRelationKnown = areaAreaPredicates_(half_edge, - id_a, id_b); - break; - case Predicates.AreaLinePredicates: - bRelationKnown = areaLinePredicates_(half_edge, - id_a, id_b); - break; - case Predicates.LineLinePredicates: - bRelationKnown = lineLinePredicates_(half_edge, - id_a, id_b); - break; - default: - throw GeometryException.GeometryInternalError(); - } - - if (bRelationKnown) - break; - - m_topo_graph.setHalfEdgeUserIndex(half_edge, - m_visited_index, 1); - half_edge = m_topo_graph.getHalfEdgeNext(half_edge); - } while (half_edge != next_half_edge && !bRelationKnown); - } - - if (bRelationKnown) - break; - - next_half_edge = m_topo_graph.getHalfEdgeNext(m_topo_graph - .getHalfEdgeTwin(half_edge)); - } while (next_half_edge != first_half_edge); - - if (bRelationKnown) - break; - } - - if (!bRelationKnown) - setRemainingPredicatesToFalse_(); - - m_topo_graph.deleteUserIndexForHalfEdges(m_visited_index); - } - - // Computes the 9 intersection relationships of boundary, interior, and - // exterior of geometry_a vs geometry_b using the Topo_graph for area/Point, - // Line/Point, and Point/Point relations - private void computeMatrixTopoGraphClusters_(int geometry_a, int geometry_b) { - boolean bRelationKnown = false; - - int id_a = m_topo_graph.getGeometryID(geometry_a); - int id_b = m_topo_graph.getGeometryID(geometry_b); - - for (int cluster = m_topo_graph.getFirstCluster(); cluster != -1; cluster = m_topo_graph - .getNextCluster(cluster)) { - // Invoke relational predicates - switch (m_predicates_cluster) { - case Predicates.AreaPointPredicates: - bRelationKnown = areaPointPredicates_(cluster, id_a, id_b); - break; - case Predicates.LinePointPredicates: - bRelationKnown = linePointPredicates_(cluster, id_a, id_b); - break; - case Predicates.PointPointPredicates: - bRelationKnown = pointPointPredicates_(cluster, id_a, id_b); - break; - default: - throw GeometryException.GeometryInternalError(); - } - - if (bRelationKnown) - break; - } - - if (!bRelationKnown) - setRemainingPredicatesToFalse_(); - } - - // Call this method to set the edit shape, if the edit shape has been - // cracked and clustered already. - private void setEditShape_(EditShape shape, ProgressTracker progressTracker) { - m_topo_graph.setEditShape(shape, progressTracker); - } - - private void setEditShapeCrackAndCluster_(EditShape shape, - double tolerance, ProgressTracker progress_tracker) { - editShapeCrackAndCluster_(shape, tolerance, progress_tracker); - setEditShape_(shape, progress_tracker); - } - - private void editShapeCrackAndCluster_(EditShape shape, double tolerance, - ProgressTracker progress_tracker) { - CrackAndCluster.execute(shape, tolerance, progress_tracker, false); //do not filter degenerate segments. - shape.filterClosePoints(0, true, true);//remove degeneracies from polygon geometries. - for (int geometry = shape.getFirstGeometry(); geometry != -1; geometry = shape - .getNextGeometry(geometry)) { - if (shape.getGeometryType(geometry) == Geometry.Type.Polygon - .value()) - Simplificator.execute(shape, geometry, -1, false, progress_tracker); - } - } - - // Upgrades the geometry to a feature geometry. - private static Geometry convertGeometry_(Geometry geometry, double tolerance) { - int gt = geometry.getType().value(); - - if (Geometry.isSegment(gt)) { - Polyline polyline = new Polyline(geometry.getDescription()); - polyline.addSegment((Segment) geometry, true); - return polyline; - } - - if (gt == Geometry.GeometryType.Envelope) { - Envelope envelope = (Envelope) (geometry); - Envelope2D env = new Envelope2D(); - geometry.queryEnvelope2D(env); - - if (env.getHeight() <= tolerance && env.getWidth() <= tolerance) {// treat - // as - // point - Point point = new Point(geometry.getDescription()); - envelope.getCenter(point); - return point; - } - - if (env.getHeight() <= tolerance || env.getWidth() <= tolerance) {// treat - // as - // line - Polyline polyline = new Polyline(geometry.getDescription()); - Point p = new Point(); - envelope.queryCornerByVal(0, p); - polyline.startPath(p); - envelope.queryCornerByVal(2, p); - polyline.lineTo(p); - return polyline; - } - - // treat as polygon - Polygon polygon = new Polygon(geometry.getDescription()); - polygon.addEnvelope(envelope, false); - return polygon; - } - - return geometry; - } + private TopoGraph m_topo_graph; + private int[] m_matrix; + private int[] m_max_dim; + private boolean[] m_perform_predicates; + private String m_scl; + private int m_predicates_half_edge; + private int m_predicates_cluster; + private int m_predicate_count; + private int m_cluster_index_a; + private int m_cluster_index_b; + private int m_visited_index; + + private interface MatrixPredicate { + static final int InteriorInterior = 0; + static final int InteriorBoundary = 1; + static final int InteriorExterior = 2; + static final int BoundaryInterior = 3; + static final int BoundaryBoundary = 4; + static final int BoundaryExterior = 5; + static final int ExteriorInterior = 6; + static final int ExteriorBoundary = 7; + static final int ExteriorExterior = 8; + } + + private interface Predicates { + static final int AreaAreaPredicates = 0; + static final int AreaLinePredicates = 1; + static final int LineLinePredicates = 2; + static final int AreaPointPredicates = 3; + static final int LinePointPredicates = 4; + static final int PointPointPredicates = 5; + } + + // Computes the necessary 9 intersection relationships of boundary, + // interior, and exterior of geometry_a vs geometry_b for the given scl + // string. + static boolean relate(Geometry geometry_a, Geometry geometry_b, + SpatialReference sr, String scl, ProgressTracker progress_tracker) { + + if (scl.length() != 9) + throw new GeometryException("relation string length has to be 9 characters"); + + for (int i = 0; i < 9; i++) { + char c = scl.charAt(i); + + if (c != '*' && c != 'T' && c != 'F' && c != '0' && c != '1' && c != '2') + throw new GeometryException("relation string"); + } + + int relation = getPredefinedRelation_(scl, geometry_a.getDimension(), + geometry_b.getDimension()); + + if (relation != RelationalOperations.Relation.unknown) + return RelationalOperations.relate(geometry_a, geometry_b, sr, + relation, progress_tracker); + + Envelope2D env1 = new Envelope2D(); + geometry_a.queryEnvelope2D(env1); + Envelope2D env2 = new Envelope2D(); + geometry_b.queryEnvelope2D(env2); + + Envelope2D envMerged = new Envelope2D(); + envMerged.setCoords(env1); + envMerged.merge(env2); + double tolerance = InternalUtils.calculateToleranceFromGeometry(sr, + envMerged, false); + + Geometry _geometryA = convertGeometry_(geometry_a, tolerance); + Geometry _geometryB = convertGeometry_(geometry_b, tolerance); + + if (_geometryA.isEmpty() || _geometryB.isEmpty()) + return relateEmptyGeometries_(_geometryA, _geometryB, scl); + + int typeA = _geometryA.getType().value(); + int typeB = _geometryB.getType().value(); + + boolean bRelation = false; + + switch (typeA) { + case Geometry.GeometryType.Polygon: + switch (typeB) { + case Geometry.GeometryType.Polygon: + bRelation = polygonRelatePolygon_((Polygon) (_geometryA), + (Polygon) (_geometryB), tolerance, scl, + progress_tracker); + break; + + case Geometry.GeometryType.Polyline: + bRelation = polygonRelatePolyline_((Polygon) (_geometryA), + (Polyline) (_geometryB), tolerance, scl, + progress_tracker); + break; + + case Geometry.GeometryType.Point: + bRelation = polygonRelatePoint_((Polygon) (_geometryA), + (Point) (_geometryB), tolerance, scl, progress_tracker); + break; + + case Geometry.GeometryType.MultiPoint: + bRelation = polygonRelateMultiPoint_((Polygon) (_geometryA), + (MultiPoint) (_geometryB), tolerance, scl, + progress_tracker); + break; + + default: + break; // warning fix + } + break; + + case Geometry.GeometryType.Polyline: + switch (typeB) { + case Geometry.GeometryType.Polygon: + bRelation = polygonRelatePolyline_((Polygon) (_geometryB), + (Polyline) (_geometryA), tolerance, + getTransposeMatrix_(scl), progress_tracker); + break; + + case Geometry.GeometryType.Polyline: + bRelation = polylineRelatePolyline_((Polyline) (_geometryA), + (Polyline) (_geometryB), tolerance, scl, + progress_tracker); + break; + + case Geometry.GeometryType.Point: + bRelation = polylineRelatePoint_((Polyline) (_geometryA), + (Point) (_geometryB), tolerance, scl, progress_tracker); + break; + + case Geometry.GeometryType.MultiPoint: + bRelation = polylineRelateMultiPoint_((Polyline) (_geometryA), + (MultiPoint) (_geometryB), tolerance, scl, + progress_tracker); + break; + + default: + break; // warning fix + } + break; + + case Geometry.GeometryType.Point: + switch (typeB) { + case Geometry.GeometryType.Polygon: + bRelation = polygonRelatePoint_((Polygon) (_geometryB), + (Point) (_geometryA), tolerance, + getTransposeMatrix_(scl), progress_tracker); + break; + + case Geometry.GeometryType.Polyline: + bRelation = polylineRelatePoint_((Polyline) (_geometryB), + (Point) (_geometryA), tolerance, + getTransposeMatrix_(scl), progress_tracker); + break; + + case Geometry.GeometryType.Point: + bRelation = pointRelatePoint_((Point) (_geometryA), + (Point) (_geometryB), tolerance, scl, progress_tracker); + break; + + case Geometry.GeometryType.MultiPoint: + bRelation = multiPointRelatePoint_((MultiPoint) (_geometryB), + (Point) (_geometryA), tolerance, + getTransposeMatrix_(scl), progress_tracker); + break; + + default: + break; // warning fix + } + break; + + case Geometry.GeometryType.MultiPoint: + switch (typeB) { + case Geometry.GeometryType.Polygon: + bRelation = polygonRelateMultiPoint_((Polygon) (_geometryB), + (MultiPoint) (_geometryA), tolerance, + getTransposeMatrix_(scl), progress_tracker); + break; + + case Geometry.GeometryType.Polyline: + bRelation = polylineRelateMultiPoint_((Polyline) (_geometryB), + (MultiPoint) (_geometryA), tolerance, + getTransposeMatrix_(scl), progress_tracker); + break; + + case Geometry.GeometryType.MultiPoint: + bRelation = multiPointRelateMultiPoint_( + (MultiPoint) (_geometryA), (MultiPoint) (_geometryB), + tolerance, scl, progress_tracker); + break; + + case Geometry.GeometryType.Point: + bRelation = multiPointRelatePoint_((MultiPoint) (_geometryA), + (Point) (_geometryB), tolerance, scl, progress_tracker); + break; + + default: + break; // warning fix + } + break; + default: + bRelation = false; + break; + } + + return bRelation; + } + + private RelationalOperationsMatrix() { + m_predicate_count = 0; + m_topo_graph = new TopoGraph(); + m_matrix = new int[9]; + m_max_dim = new int[9]; + m_perform_predicates = new boolean[9]; + m_predicates_half_edge = -1; + m_predicates_cluster = -1; + } + + // Returns true if the relation holds. + static boolean polygonRelatePolygon_(Polygon polygon_a, Polygon polygon_b, + double tolerance, String scl, ProgressTracker progress_tracker) { + RelationalOperationsMatrix relOps = new RelationalOperationsMatrix(); + relOps.resetMatrix_(); + relOps.setPredicates_(scl); + relOps.setAreaAreaPredicates_(); + + Envelope2D env_a = new Envelope2D(), env_b = new Envelope2D(); + polygon_a.queryEnvelope2D(env_a); + polygon_b.queryEnvelope2D(env_b); + + boolean bRelationKnown = false; + boolean b_disjoint = RelationalOperations.envelopeDisjointEnvelope_( + env_a, env_b, tolerance, progress_tracker); + + if (b_disjoint) { + relOps.areaAreaDisjointPredicates_(polygon_a, polygon_b); + bRelationKnown = true; + } + + if (!bRelationKnown) { + // Quick rasterize test to see whether the the geometries are + // disjoint, or if one is contained in the other. + int relation = RelationalOperations + .tryRasterizedContainsOrDisjoint_(polygon_a, polygon_b, + tolerance, false); + + if (relation == RelationalOperations.Relation.disjoint) { + relOps.areaAreaDisjointPredicates_(polygon_a, polygon_b); + bRelationKnown = true; + } else if (relation == RelationalOperations.Relation.contains) { + relOps.areaAreaContainsPredicates_(polygon_b); + bRelationKnown = true; + } else if (relation == RelationalOperations.Relation.within) { + relOps.areaAreaWithinPredicates_(polygon_a); + bRelationKnown = true; + } + } + + if (!bRelationKnown) { + EditShape edit_shape = new EditShape(); + int geom_a = edit_shape.addGeometry(polygon_a); + int geom_b = edit_shape.addGeometry(polygon_b); + relOps.setEditShapeCrackAndCluster_(edit_shape, tolerance, + progress_tracker); + relOps.computeMatrixTopoGraphHalfEdges_(geom_a, geom_b); + relOps.m_topo_graph.removeShape(); + } + + boolean bRelation = relationCompare_(relOps.m_matrix, relOps.m_scl); + return bRelation; + } + + // The relation is based on the simplified-Polygon A containing Polygon B, which may be non-simple. + static boolean polygonContainsPolygon_(Polygon polygon_a, Polygon polygon_b, double tolerance, ProgressTracker progress_tracker) { + assert (!polygon_a.isEmpty()); + assert (!polygon_b.isEmpty()); + + RelationalOperationsMatrix relOps = new RelationalOperationsMatrix(); + relOps.resetMatrix_(); + relOps.setPredicates_("T*****F**"); + relOps.setAreaAreaPredicates_(); + + Envelope2D env_a = new Envelope2D(), env_b = new Envelope2D(); + polygon_a.queryEnvelope2D(env_a); + polygon_b.queryEnvelope2D(env_b); + + boolean bRelationKnown = false; + boolean b_disjoint = RelationalOperations.envelopeDisjointEnvelope_(env_a, env_b, tolerance, progress_tracker); + + if (b_disjoint) { + relOps.areaAreaDisjointPredicates_(polygon_a, polygon_b); + bRelationKnown = true; + } + + if (!bRelationKnown) { + // Quick rasterize test to see whether the the geometries are disjoint, or if one is contained in the other. + int relation = RelationalOperations.tryRasterizedContainsOrDisjoint_(polygon_a, polygon_b, tolerance, false); + + if (relation == RelationalOperations.Relation.disjoint) { + relOps.areaAreaDisjointPredicates_(polygon_a, polygon_b); + bRelationKnown = true; + } else if (relation == RelationalOperations.Relation.contains) { + relOps.areaAreaContainsPredicates_(polygon_b); + bRelationKnown = true; + } else if (relation == RelationalOperations.Relation.within) { + relOps.areaAreaWithinPredicates_(polygon_a); + bRelationKnown = true; + } + } + + if (bRelationKnown) { + boolean bContains = relationCompare_(relOps.m_matrix, relOps.m_scl); + return bContains; + } + + EditShape edit_shape = new EditShape(); + int geom_a = edit_shape.addGeometry(polygon_a); + int geom_b = edit_shape.addGeometry(polygon_b); + + CrackAndCluster.execute(edit_shape, tolerance, progress_tracker, false); + Polyline boundary_b = (Polyline) edit_shape.getGeometry(geom_b).getBoundary(); + edit_shape.filterClosePoints(0, true, true); + Simplificator.execute(edit_shape, geom_a, -1, false, progress_tracker); + + // Make sure Polygon A has exterior + // If the simplified Polygon A does not have interior, then it cannot contain anything. + if (edit_shape.getPointCount(geom_a) == 0) + return false; + + Simplificator.execute(edit_shape, geom_b, -1, false, progress_tracker); + + relOps.setEditShape_(edit_shape, progress_tracker); + + // We see if the simplified Polygon A contains the simplified Polygon B. + + boolean b_empty = edit_shape.getPointCount(geom_b) == 0; + + if (!b_empty) {//geom_b has interior + relOps.computeMatrixTopoGraphHalfEdges_(geom_a, geom_b); + relOps.m_topo_graph.removeShape(); + boolean bContains = relationCompare_(relOps.m_matrix, relOps.m_scl); + + if (!bContains) + return bContains; + } + + Polygon polygon_simple_a = (Polygon) edit_shape.getGeometry(geom_a); + edit_shape = new EditShape(); + geom_a = edit_shape.addGeometry(polygon_simple_a); + geom_b = edit_shape.addGeometry(boundary_b); + relOps.setEditShape_(edit_shape, progress_tracker); + + // Check no interior lines of the boundary intersect the exterior + relOps.m_predicate_count = 0; + relOps.resetMatrix_(); + relOps.setPredicates_(b_empty ? "T*****F**" : "******F**"); + relOps.setAreaLinePredicates_(); + + relOps.computeMatrixTopoGraphHalfEdges_(geom_a, geom_b); + relOps.m_topo_graph.removeShape(); + boolean bContains = relationCompare_(relOps.m_matrix, relOps.m_scl); + return bContains; + } + + // Returns true if the relation holds. + static boolean polygonRelatePolyline_(Polygon polygon_a, + Polyline polyline_b, double tolerance, String scl, + ProgressTracker progress_tracker) { + RelationalOperationsMatrix relOps = new RelationalOperationsMatrix(); + relOps.resetMatrix_(); + relOps.setPredicates_(scl); + relOps.setAreaLinePredicates_(); + + Envelope2D env_a = new Envelope2D(), env_b = new Envelope2D(); + polygon_a.queryEnvelope2D(env_a); + polyline_b.queryEnvelope2D(env_b); + + boolean bRelationKnown = false; + boolean b_disjoint = RelationalOperations.envelopeDisjointEnvelope_( + env_a, env_b, tolerance, progress_tracker); + + if (b_disjoint) { + relOps.areaLineDisjointPredicates_(polygon_a, polyline_b); // passing polyline + // to get boundary + // information + bRelationKnown = true; + } + + if (!bRelationKnown) { + // Quick rasterize test to see whether the the geometries are + // disjoint, or if one is contained in the other. + int relation = RelationalOperations + .tryRasterizedContainsOrDisjoint_(polygon_a, polyline_b, + tolerance, false); + + if (relation == RelationalOperations.Relation.disjoint) { + relOps.areaLineDisjointPredicates_(polygon_a, polyline_b); // passing + // polyline to + // get boundary + // information + bRelationKnown = true; + } else if (relation == RelationalOperations.Relation.contains) { + relOps.areaLineContainsPredicates_(polygon_a, polyline_b); // passing + // polyline to + // get boundary + // information + bRelationKnown = true; + } + } + + if (!bRelationKnown) { + EditShape edit_shape = new EditShape(); + int geom_a = edit_shape.addGeometry(polygon_a); + int geom_b = edit_shape.addGeometry(polyline_b); + relOps.setEditShapeCrackAndCluster_(edit_shape, tolerance, + progress_tracker); + relOps.m_cluster_index_b = relOps.m_topo_graph + .createUserIndexForClusters(); + markClusterEndPoints_(geom_b, relOps.m_topo_graph, + relOps.m_cluster_index_b); + relOps.computeMatrixTopoGraphHalfEdges_(geom_a, geom_b); + relOps.m_topo_graph + .deleteUserIndexForClusters(relOps.m_cluster_index_b); + relOps.m_topo_graph.removeShape(); + } + + boolean bRelation = relationCompare_(relOps.m_matrix, relOps.m_scl); + return bRelation; + } + + static boolean polygonContainsPolyline_(Polygon polygon_a, Polyline polyline_b, double tolerance, ProgressTracker progress_tracker) { + RelationalOperationsMatrix relOps = new RelationalOperationsMatrix(); + relOps.resetMatrix_(); + relOps.setPredicates_("T*****F**"); + relOps.setAreaLinePredicates_(); + + Envelope2D env_a = new Envelope2D(), env_b = new Envelope2D(); + polygon_a.queryEnvelope2D(env_a); + polyline_b.queryEnvelope2D(env_b); + + boolean bRelationKnown = false; + boolean b_disjoint = RelationalOperations.envelopeDisjointEnvelope_(env_a, env_b, tolerance, progress_tracker); + + if (b_disjoint) { + relOps.areaLineDisjointPredicates_(polygon_a, polyline_b); // passing polyline to get boundary information + bRelationKnown = true; + } + + if (!bRelationKnown) { + // Quick rasterize test to see whether the the geometries are disjoint, or if one is contained in the other. + int relation = RelationalOperations.tryRasterizedContainsOrDisjoint_(polygon_a, polyline_b, tolerance, false); + + if (relation == RelationalOperations.Relation.disjoint) { + relOps.areaLineDisjointPredicates_(polygon_a, polyline_b); // passing polyline to get boundary information + bRelationKnown = true; + } else if (relation == RelationalOperations.Relation.contains) { + relOps.areaLineContainsPredicates_(polygon_a, polyline_b); // passing polyline to get boundary information + bRelationKnown = true; + } + } + + if (bRelationKnown) { + boolean bContains = relationCompare_(relOps.m_matrix, relOps.m_scl); + return bContains; + } + + EditShape edit_shape = new EditShape(); + int geom_a = edit_shape.addGeometry(polygon_a); + int geom_b = edit_shape.addGeometry(polyline_b); + relOps.setEditShapeCrackAndCluster_(edit_shape, tolerance, progress_tracker); + + // Make sure Polygon A has exterior + // If the simplified Polygon A does not have interior, then it cannot contain anything. + if (edit_shape.getPointCount(geom_a) == 0) + return false; + + relOps.computeMatrixTopoGraphHalfEdges_(geom_a, geom_b); + relOps.m_topo_graph.removeShape(); + + boolean bContains = relationCompare_(relOps.m_matrix, relOps.m_scl); + return bContains; + } + + // Returns true if the relation holds + static boolean polygonRelateMultiPoint_(Polygon polygon_a, + MultiPoint multipoint_b, double tolerance, String scl, + ProgressTracker progress_tracker) { + RelationalOperationsMatrix relOps = new RelationalOperationsMatrix(); + relOps.resetMatrix_(); + relOps.setPredicates_(scl); + relOps.setAreaPointPredicates_(); + + Envelope2D env_a = new Envelope2D(), env_b = new Envelope2D(); + polygon_a.queryEnvelope2D(env_a); + multipoint_b.queryEnvelope2D(env_b); + + boolean bRelationKnown = false; + boolean b_disjoint = RelationalOperations.envelopeDisjointEnvelope_( + env_a, env_b, tolerance, progress_tracker); + + if (b_disjoint) { + relOps.areaPointDisjointPredicates_(polygon_a); + bRelationKnown = true; + } + + if (!bRelationKnown) { + // Quick rasterize test to see whether the the geometries are + // disjoint, or if one is contained in the other. + int relation = RelationalOperations + .tryRasterizedContainsOrDisjoint_(polygon_a, multipoint_b, + tolerance, false); + + if (relation == RelationalOperations.Relation.disjoint) { + relOps.areaPointDisjointPredicates_(polygon_a); + bRelationKnown = true; + } else if (relation == RelationalOperations.Relation.contains) { + relOps.areaPointContainsPredicates_(polygon_a); + bRelationKnown = true; + } + } + + if (!bRelationKnown) { + EditShape edit_shape = new EditShape(); + int geom_a = edit_shape.addGeometry(polygon_a); + int geom_b = edit_shape.addGeometry(multipoint_b); + relOps.setEditShapeCrackAndCluster_(edit_shape, tolerance, + progress_tracker); + relOps.computeMatrixTopoGraphClusters_(geom_a, geom_b); + relOps.m_topo_graph.removeShape(); + } + + boolean bRelation = relationCompare_(relOps.m_matrix, relOps.m_scl); + return bRelation; + } + + // Returns true if the relation holds. + static boolean polylineRelatePolyline_(Polyline polyline_a, + Polyline polyline_b, double tolerance, String scl, + ProgressTracker progress_tracker) { + RelationalOperationsMatrix relOps = new RelationalOperationsMatrix(); + relOps.resetMatrix_(); + relOps.setPredicates_(scl); + relOps.setLineLinePredicates_(); + + Envelope2D env_a = new Envelope2D(), env_b = new Envelope2D(); + polyline_a.queryEnvelope2D(env_a); + polyline_b.queryEnvelope2D(env_b); + + boolean bRelationKnown = false; + boolean b_disjoint = RelationalOperations.envelopeDisjointEnvelope_( + env_a, env_b, tolerance, progress_tracker); + + if (b_disjoint) { + relOps.lineLineDisjointPredicates_(polyline_a, polyline_b); + bRelationKnown = true; + } + + if (!bRelationKnown) { + // Quick rasterize test to see whether the the geometries are + // disjoint, or if one is contained in the other. + int relation = RelationalOperations + .tryRasterizedContainsOrDisjoint_(polyline_a, polyline_b, + tolerance, false); + + if (relation == RelationalOperations.Relation.disjoint) { + relOps.lineLineDisjointPredicates_(polyline_a, polyline_b); + bRelationKnown = true; + } + } + + if (!bRelationKnown) { + EditShape edit_shape = new EditShape(); + int geom_a = edit_shape.addGeometry(polyline_a); + int geom_b = edit_shape.addGeometry(polyline_b); + relOps.setEditShapeCrackAndCluster_(edit_shape, tolerance, + progress_tracker); + relOps.m_cluster_index_a = relOps.m_topo_graph + .createUserIndexForClusters(); + relOps.m_cluster_index_b = relOps.m_topo_graph + .createUserIndexForClusters(); + markClusterEndPoints_(geom_a, relOps.m_topo_graph, + relOps.m_cluster_index_a); + markClusterEndPoints_(geom_b, relOps.m_topo_graph, + relOps.m_cluster_index_b); + relOps.computeMatrixTopoGraphHalfEdges_(geom_a, geom_b); + relOps.m_topo_graph + .deleteUserIndexForClusters(relOps.m_cluster_index_a); + relOps.m_topo_graph + .deleteUserIndexForClusters(relOps.m_cluster_index_b); + relOps.m_topo_graph.removeShape(); + } + + boolean bRelation = relationCompare_(relOps.m_matrix, relOps.m_scl); + return bRelation; + } + + // Returns true if the relation holds. + static boolean polylineRelateMultiPoint_(Polyline polyline_a, + MultiPoint multipoint_b, double tolerance, String scl, + ProgressTracker progress_tracker) { + RelationalOperationsMatrix relOps = new RelationalOperationsMatrix(); + relOps.resetMatrix_(); + relOps.setPredicates_(scl); + relOps.setLinePointPredicates_(); + + Envelope2D env_a = new Envelope2D(), env_b = new Envelope2D(); + polyline_a.queryEnvelope2D(env_a); + multipoint_b.queryEnvelope2D(env_b); + + boolean bRelationKnown = false; + boolean b_disjoint = RelationalOperations.envelopeDisjointEnvelope_( + env_a, env_b, tolerance, progress_tracker); + + if (b_disjoint) { + relOps.linePointDisjointPredicates_(polyline_a); + bRelationKnown = true; + } + + if (!bRelationKnown) { + // Quick rasterize test to see whether the the geometries are + // disjoint, or if one is contained in the other. + int relation = RelationalOperations + .tryRasterizedContainsOrDisjoint_(polyline_a, multipoint_b, + tolerance, false); + + if (relation == RelationalOperations.Relation.disjoint) { + relOps.linePointDisjointPredicates_(polyline_a); + bRelationKnown = true; + } + } + + if (!bRelationKnown) { + EditShape edit_shape = new EditShape(); + int geom_a = edit_shape.addGeometry(polyline_a); + int geom_b = edit_shape.addGeometry(multipoint_b); + relOps.setEditShapeCrackAndCluster_(edit_shape, tolerance, + progress_tracker); + relOps.m_cluster_index_a = relOps.m_topo_graph + .createUserIndexForClusters(); + markClusterEndPoints_(geom_a, relOps.m_topo_graph, + relOps.m_cluster_index_a); + relOps.computeMatrixTopoGraphClusters_(geom_a, geom_b); + relOps.m_topo_graph + .deleteUserIndexForClusters(relOps.m_cluster_index_a); + relOps.m_topo_graph.removeShape(); + } + + boolean bRelation = relationCompare_(relOps.m_matrix, relOps.m_scl); + return bRelation; + } + + // Returns true if the relation holds. + static boolean multiPointRelateMultiPoint_(MultiPoint multipoint_a, + MultiPoint multipoint_b, double tolerance, String scl, + ProgressTracker progress_tracker) { + RelationalOperationsMatrix relOps = new RelationalOperationsMatrix(); + relOps.resetMatrix_(); + relOps.setPredicates_(scl); + relOps.setPointPointPredicates_(); + + Envelope2D env_a = new Envelope2D(), env_b = new Envelope2D(); + multipoint_a.queryEnvelope2D(env_a); + multipoint_b.queryEnvelope2D(env_b); + + boolean bRelationKnown = false; + boolean b_disjoint = RelationalOperations.envelopeDisjointEnvelope_( + env_a, env_b, tolerance, progress_tracker); + + if (b_disjoint) { + relOps.pointPointDisjointPredicates_(); + bRelationKnown = true; + } + + if (!bRelationKnown) { + EditShape edit_shape = new EditShape(); + int geom_a = edit_shape.addGeometry(multipoint_a); + int geom_b = edit_shape.addGeometry(multipoint_b); + relOps.setEditShapeCrackAndCluster_(edit_shape, tolerance, + progress_tracker); + relOps.computeMatrixTopoGraphClusters_(geom_a, geom_b); + relOps.m_topo_graph.removeShape(); + } + + boolean bRelation = relationCompare_(relOps.m_matrix, relOps.m_scl); + return bRelation; + } + + // Returns true if the relation holds. + static boolean polygonRelatePoint_(Polygon polygon_a, Point point_b, + double tolerance, String scl, ProgressTracker progress_tracker) { + RelationalOperationsMatrix relOps = new RelationalOperationsMatrix(); + relOps.resetMatrix_(); + relOps.setPredicates_(scl); + relOps.setAreaPointPredicates_(); + + Envelope2D env_a = new Envelope2D(); + polygon_a.queryEnvelope2D(env_a); + Point2D pt_b = point_b.getXY(); + + boolean bRelationKnown = false; + boolean b_disjoint = RelationalOperations.pointDisjointEnvelope_(pt_b, env_a, tolerance, progress_tracker); + + if (b_disjoint) { + relOps.areaPointDisjointPredicates_(polygon_a); + bRelationKnown = true; + } + + if (!bRelationKnown) { + PolygonUtils.PiPResult res = PolygonUtils.isPointInPolygon2D(polygon_a, pt_b, tolerance); // uses accelerator + + if (res == PolygonUtils.PiPResult.PiPInside) {// polygon must have area + relOps.m_matrix[MatrixPredicate.InteriorInterior] = 0; + relOps.m_matrix[MatrixPredicate.InteriorExterior] = 2; + relOps.m_matrix[MatrixPredicate.BoundaryInterior] = -1; + relOps.m_matrix[MatrixPredicate.BoundaryExterior] = 1; + relOps.m_matrix[MatrixPredicate.ExteriorInterior] = -1; + } else if (res == PolygonUtils.PiPResult.PiPBoundary) { + relOps.m_matrix[MatrixPredicate.ExteriorInterior] = -1; + + double area = polygon_a.calculateArea2D(); + + if (area != 0) { + relOps.m_matrix[MatrixPredicate.InteriorInterior] = -1; + relOps.m_matrix[MatrixPredicate.BoundaryInterior] = 0; + relOps.m_matrix[MatrixPredicate.InteriorExterior] = 2; + relOps.m_matrix[MatrixPredicate.BoundaryExterior] = 1; + } else { + relOps.m_matrix[MatrixPredicate.InteriorInterior] = 0; + relOps.m_matrix[MatrixPredicate.BoundaryInterior] = -1; + relOps.m_matrix[MatrixPredicate.BoundaryExterior] = -1; + + Envelope2D env = new Envelope2D(); + polygon_a.queryEnvelope2D(env); + relOps.m_matrix[MatrixPredicate.InteriorExterior] = (env.getHeight() == 0.0 && env.getWidth() == 0.0 ? -1 : 1); + } + } else { + relOps.areaPointDisjointPredicates_(polygon_a); + } + } + + boolean bRelation = relationCompare_(relOps.m_matrix, scl); + return bRelation; + } + + // Returns true if the relation holds. + static boolean polylineRelatePoint_(Polyline polyline_a, Point point_b, + double tolerance, String scl, ProgressTracker progress_tracker) { + RelationalOperationsMatrix relOps = new RelationalOperationsMatrix(); + relOps.resetMatrix_(); + relOps.setPredicates_(scl); + relOps.setLinePointPredicates_(); + + Envelope2D env_a = new Envelope2D(); + polyline_a.queryEnvelope2D(env_a); + Point2D pt_b = point_b.getXY(); + + boolean bRelationKnown = false; + boolean b_disjoint = RelationalOperations.pointDisjointEnvelope_(pt_b, env_a, tolerance, progress_tracker); + + if (b_disjoint) { + relOps.linePointDisjointPredicates_(polyline_a); + bRelationKnown = true; + } + + if (!bRelationKnown) { + MultiPoint boundary_a = null; + boolean b_boundary_contains_point_known = false; + boolean b_boundary_contains_point = false; + + if (relOps.m_perform_predicates[MatrixPredicate.InteriorInterior] || relOps.m_perform_predicates[MatrixPredicate.ExteriorInterior]) { + boolean b_intersects = RelationalOperations.linearPathIntersectsPoint_(polyline_a, pt_b, tolerance); + + if (b_intersects) { + if (relOps.m_perform_predicates[MatrixPredicate.InteriorInterior]) { + boundary_a = (MultiPoint) Boundary.calculate(polyline_a, progress_tracker); + b_boundary_contains_point = !RelationalOperations.multiPointDisjointPointImpl_(boundary_a, pt_b, tolerance, progress_tracker); + b_boundary_contains_point_known = true; + + if (b_boundary_contains_point) + relOps.m_matrix[MatrixPredicate.InteriorInterior] = -1; + else + relOps.m_matrix[MatrixPredicate.InteriorInterior] = 0; + } + + relOps.m_matrix[MatrixPredicate.ExteriorInterior] = -1; + } else { + relOps.m_matrix[MatrixPredicate.InteriorInterior] = -1; + relOps.m_matrix[MatrixPredicate.ExteriorInterior] = 0; + } + } + + if (relOps.m_perform_predicates[MatrixPredicate.BoundaryInterior]) { + if (boundary_a != null && boundary_a.isEmpty()) { + relOps.m_matrix[MatrixPredicate.BoundaryInterior] = -1; + } else { + if (!b_boundary_contains_point_known) { + if (boundary_a == null) + boundary_a = (MultiPoint) Boundary.calculate(polyline_a, progress_tracker); + + b_boundary_contains_point = !RelationalOperations.multiPointDisjointPointImpl_(boundary_a, pt_b, tolerance, progress_tracker); + b_boundary_contains_point_known = true; + } + + relOps.m_matrix[MatrixPredicate.BoundaryInterior] = (b_boundary_contains_point ? 0 : -1); + } + } + + if (relOps.m_perform_predicates[MatrixPredicate.BoundaryExterior]) { + if (boundary_a != null && boundary_a.isEmpty()) { + relOps.m_matrix[MatrixPredicate.BoundaryExterior] = -1; + } else { + if (b_boundary_contains_point_known && !b_boundary_contains_point) { + relOps.m_matrix[MatrixPredicate.BoundaryExterior] = 0; + } else { + if (boundary_a == null) + boundary_a = (MultiPoint) Boundary.calculate(polyline_a, progress_tracker); + + boolean b_boundary_equals_point = RelationalOperations.multiPointEqualsPoint_(boundary_a, point_b, tolerance, progress_tracker); + relOps.m_matrix[MatrixPredicate.BoundaryExterior] = (b_boundary_equals_point ? -1 : 0); + } + } + } + + if (relOps.m_perform_predicates[MatrixPredicate.InteriorExterior]) { + boolean b_has_length = polyline_a.calculateLength2D() != 0; + + if (b_has_length) { + relOps.m_matrix[MatrixPredicate.InteriorExterior] = 1; + } else { + // all points are interior + MultiPoint interior_a = new MultiPoint(polyline_a.getDescription()); + interior_a.add(polyline_a, 0, polyline_a.getPointCount()); + boolean b_interior_equals_point = RelationalOperations.multiPointEqualsPoint_(interior_a, point_b, tolerance, progress_tracker); + relOps.m_matrix[MatrixPredicate.InteriorExterior] = (b_interior_equals_point ? -1 : 0); + } + } + } + + boolean bRelation = relationCompare_(relOps.m_matrix, relOps.m_scl); + return bRelation; + } + + // Returns true if the relation holds. + static boolean multiPointRelatePoint_(MultiPoint multipoint_a, + Point point_b, double tolerance, String scl, + ProgressTracker progress_tracker) { + RelationalOperationsMatrix relOps = new RelationalOperationsMatrix(); + relOps.resetMatrix_(); + relOps.setPredicates_(scl); + relOps.setPointPointPredicates_(); + + Envelope2D env_a = new Envelope2D(); + multipoint_a.queryEnvelope2D(env_a); + Point2D pt_b = point_b.getXY(); + + boolean bRelationKnown = false; + boolean b_disjoint = RelationalOperations.pointDisjointEnvelope_(pt_b, + env_a, tolerance, progress_tracker); + + if (b_disjoint) { + relOps.pointPointDisjointPredicates_(); + bRelationKnown = true; + } + + if (!bRelationKnown) { + boolean b_intersects = false; + boolean b_multipoint_contained = true; + double tolerance_sq = tolerance * tolerance; + + for (int i = 0; i < multipoint_a.getPointCount(); i++) { + Point2D pt_a = multipoint_a.getXY(i); + + if (Point2D.sqrDistance(pt_a, pt_b) <= tolerance_sq) + b_intersects = true; + else + b_multipoint_contained = false; + + if (b_intersects && !b_multipoint_contained) + break; + } + + if (b_intersects) { + relOps.m_matrix[MatrixPredicate.InteriorInterior] = 0; + + if (!b_multipoint_contained) + relOps.m_matrix[MatrixPredicate.InteriorExterior] = 0; + else + relOps.m_matrix[MatrixPredicate.InteriorExterior] = -1; + + relOps.m_matrix[MatrixPredicate.ExteriorInterior] = -1; + } else { + relOps.m_matrix[MatrixPredicate.InteriorInterior] = -1; + relOps.m_matrix[MatrixPredicate.InteriorExterior] = 0; + relOps.m_matrix[MatrixPredicate.ExteriorInterior] = 0; + } + } + + boolean bRelation = relationCompare_(relOps.m_matrix, scl); + return bRelation; + } + + // Returns true if the relation holds. + static boolean pointRelatePoint_(Point point_a, Point point_b, + double tolerance, String scl, ProgressTracker progress_tracker) { + Point2D pt_a = point_a.getXY(); + Point2D pt_b = point_b.getXY(); + int[] matrix = new int[9]; + + for (int i = 0; i < 9; i++) + matrix[i] = -1; + + if (Point2D.sqrDistance(pt_a, pt_b) <= tolerance * tolerance) { + matrix[MatrixPredicate.InteriorInterior] = 0; + } else { + matrix[MatrixPredicate.InteriorExterior] = 0; + matrix[MatrixPredicate.ExteriorInterior] = 0; + } + + matrix[MatrixPredicate.ExteriorExterior] = 2; + + boolean bRelation = relationCompare_(matrix, scl); + return bRelation; + } + + // Compares the DE-9I matrix against the scl string. + private static boolean relationCompare_(int[] matrix, String scl) { + for (int i = 0; i < 9; i++) { + switch (scl.charAt(i)) { + case 'T': + assert (matrix[i] != -2); + if (matrix[i] == -1) + return false; + break; + + case 'F': + assert (matrix[i] != -2); + if (matrix[i] != -1) + return false; + break; + + case '0': + assert (matrix[i] != -2); + if (matrix[i] != 0) + return false; + break; + + case '1': + assert (matrix[i] != -2); + if (matrix[i] != 1) + return false; + break; + + case '2': + assert (matrix[i] != -2); + if (matrix[i] != 2) + return false; + break; + + default: + break; + } + } + + return true; + } + + static boolean relateEmptyGeometries_(Geometry geometry_a, Geometry geometry_b, String scl) { + int[] matrix = new int[9]; + + if (geometry_a.isEmpty() && geometry_b.isEmpty()) { + for (int i = 0; i < 9; i++) + matrix[i] = -1; + + return relationCompare_(matrix, scl); + } + + boolean b_transpose = false; + + Geometry g_a; + Geometry g_b; + + if (!geometry_a.isEmpty()) { + g_a = geometry_a; + g_b = geometry_b; + } else { + g_a = geometry_b; + g_b = geometry_a; + b_transpose = true; + } + + matrix[MatrixPredicate.InteriorInterior] = -1; + matrix[MatrixPredicate.InteriorBoundary] = -1; + matrix[MatrixPredicate.BoundaryInterior] = -1; + matrix[MatrixPredicate.BoundaryBoundary] = -1; + matrix[MatrixPredicate.ExteriorInterior] = -1; + matrix[MatrixPredicate.ExteriorBoundary] = -1; + + matrix[MatrixPredicate.ExteriorExterior] = 2; + + int type = g_a.getType().value(); + + if (Geometry.isMultiPath(type)) { + if (type == Geometry.GeometryType.Polygon) { + double area = ((Polygon) g_a).calculateArea2D(); + + if (area != 0) { + matrix[MatrixPredicate.InteriorExterior] = 2; + matrix[MatrixPredicate.BoundaryExterior] = 1; + } else { + matrix[MatrixPredicate.BoundaryExterior] = -1; + + Envelope2D env = new Envelope2D(); + g_a.queryEnvelope2D(env); + matrix[MatrixPredicate.InteriorExterior] = (env.getHeight() == 0.0 && env.getWidth() == 0.0 ? 0 : 1); + } + } else { + boolean b_has_length = ((Polyline) g_a).calculateLength2D() != 0; + matrix[MatrixPredicate.InteriorExterior] = (b_has_length ? 1 : 0); + matrix[MatrixPredicate.BoundaryExterior] = (Boundary.hasNonEmptyBoundary((Polyline) g_a, null) ? 0 : -1); + } + } else { + matrix[MatrixPredicate.InteriorExterior] = 0; + matrix[MatrixPredicate.BoundaryExterior] = -1; + } + + if (b_transpose) + transposeMatrix_(matrix); + + return relationCompare_(matrix, scl); + } + + // Checks whether scl string is a predefined relation. + private static int getPredefinedRelation_(String scl, int dim_a, int dim_b) { + if (equals_(scl)) + return RelationalOperations.Relation.equals; + + if (disjoint_(scl)) + return RelationalOperations.Relation.disjoint; + + if (touches_(scl, dim_a, dim_b)) + return RelationalOperations.Relation.touches; + + if (crosses_(scl, dim_a, dim_b)) + return RelationalOperations.Relation.crosses; + + if (contains_(scl)) + return RelationalOperations.Relation.contains; + + if (overlaps_(scl, dim_a, dim_b)) + return RelationalOperations.Relation.overlaps; + + return RelationalOperations.Relation.unknown; + } + + // Checks whether the scl string is the equals relation. + private static boolean equals_(String scl) { + // Valid for all + if (scl.charAt(0) == 'T' && scl.charAt(1) == '*' + && scl.charAt(2) == 'F' && scl.charAt(3) == '*' + && scl.charAt(4) == '*' && scl.charAt(5) == 'F' + && scl.charAt(6) == 'F' && scl.charAt(7) == 'F' + && scl.charAt(8) == '*') + return true; + + return false; + } + + // Checks whether the scl string is the disjoint relation. + private static boolean disjoint_(String scl) { + if (scl.charAt(0) == 'F' && scl.charAt(1) == 'F' + && scl.charAt(2) == '*' && scl.charAt(3) == 'F' + && scl.charAt(4) == 'F' && scl.charAt(5) == '*' + && scl.charAt(6) == '*' && scl.charAt(7) == '*' + && scl.charAt(8) == '*') + return true; + + return false; + } + + // Checks whether the scl string is the touches relation. + private static boolean touches_(String scl, int dim_a, int dim_b) { + // Points cant touch + if (dim_a == 0 && dim_b == 0) + return false; + + if (!(dim_a == 2 && dim_b == 2)) { + // Valid for area-Line, Line-Line, area-Point, and Line-Point + if (scl.charAt(0) == 'F' && scl.charAt(1) == '*' + && scl.charAt(2) == '*' && scl.charAt(3) == 'T' + && scl.charAt(4) == '*' && scl.charAt(5) == '*' + && scl.charAt(6) == '*' && scl.charAt(7) == '*' + && scl.charAt(8) == '*') + return true; + + if (dim_a == 1 && dim_b == 1) { + // Valid for Line-Line + if (scl.charAt(0) == 'F' && scl.charAt(1) == 'T' + && scl.charAt(2) == '*' && scl.charAt(3) == '*' + && scl.charAt(4) == '*' && scl.charAt(5) == '*' + && scl.charAt(6) == '*' && scl.charAt(7) == '*' + && scl.charAt(8) == '*') + return true; + } + } + + // Valid for area-area, area-Line, Line-Line + + if (dim_b != 0) { + if (scl.charAt(0) == 'F' && scl.charAt(1) == '*' + && scl.charAt(2) == '*' && scl.charAt(3) == '*' + && scl.charAt(4) == 'T' && scl.charAt(5) == '*' + && scl.charAt(6) == '*' && scl.charAt(7) == '*' + && scl.charAt(8) == '*') + return true; + } + + return false; + } + + // Checks whether the scl string is the crosses relation. + private static boolean crosses_(String scl, int dim_a, int dim_b) { + if (dim_a > dim_b) { + // Valid for area-Line, area-Point, Line-Point + if (scl.charAt(0) == 'T' && scl.charAt(1) == '*' + && scl.charAt(2) == '*' && scl.charAt(3) == '*' + && scl.charAt(4) == '*' && scl.charAt(5) == '*' + && scl.charAt(6) == 'T' && scl.charAt(7) == '*' + && scl.charAt(8) == '*') + return true; + + return false; + } + + if (dim_a == 1 && dim_b == 1) { + // Valid for Line-Line + if (scl.charAt(0) == '0' && scl.charAt(1) == '*' + && scl.charAt(2) == '*' && scl.charAt(3) == '*' + && scl.charAt(4) == '*' && scl.charAt(5) == '*' + && scl.charAt(6) == '*' && scl.charAt(7) == '*' + && scl.charAt(8) == '*') + return true; + } + + return false; + } + + // Checks whether the scl string is the contains relation. + private static boolean contains_(String scl) { + // Valid for all + if (scl.charAt(0) == 'T' && scl.charAt(1) == '*' + && scl.charAt(2) == '*' && scl.charAt(3) == '*' + && scl.charAt(4) == '*' && scl.charAt(5) == '*' + && scl.charAt(6) == 'F' && scl.charAt(7) == 'F' + && scl.charAt(8) == '*') + return true; + + return false; + } + + // Checks whether the scl string is the overlaps relation. + private static boolean overlaps_(String scl, int dim_a, int dim_b) { + if (dim_a == dim_b) { + if (dim_a != 1) { + // Valid for area-area, Point-Point + if (scl.charAt(0) == 'T' && scl.charAt(1) == '*' + && scl.charAt(2) == 'T' && scl.charAt(3) == '*' + && scl.charAt(4) == '*' && scl.charAt(5) == '*' + && scl.charAt(6) == 'T' && scl.charAt(7) == '*' + && scl.charAt(8) == '*') + return true; + + return false; + } + + // Valid for Line-Line + if (scl.charAt(0) == '1' && scl.charAt(1) == '*' + && scl.charAt(2) == 'T' && scl.charAt(3) == '*' + && scl.charAt(4) == '*' && scl.charAt(5) == '*' + && scl.charAt(6) == 'T' && scl.charAt(7) == '*' + && scl.charAt(8) == '*') + return true; + } + + return false; + } + + // Marks each cluster of the topoGraph as belonging to an interior vertex of + // the geometry and/or a boundary index of the geometry. + private static void markClusterEndPoints_(int geometry, + TopoGraph topoGraph, int clusterIndex) { + int id = topoGraph.getGeometryID(geometry); + + for (int cluster = topoGraph.getFirstCluster(); cluster != -1; cluster = topoGraph.getNextCluster(cluster)) { + int cluster_parentage = topoGraph.getClusterParentage(cluster); + + if ((cluster_parentage & id) == 0) + continue; + + int first_half_edge = topoGraph.getClusterHalfEdge(cluster); + if (first_half_edge == -1) { + topoGraph.setClusterUserIndex(cluster, clusterIndex, 0); + continue; + } + + int next_half_edge = first_half_edge; + int index = 0; + + do { + int half_edge = next_half_edge; + int half_edge_parentage = topoGraph.getHalfEdgeParentage(half_edge); + + if ((half_edge_parentage & id) != 0) + index++; + + next_half_edge = topoGraph.getHalfEdgeNext(topoGraph.getHalfEdgeTwin(half_edge)); + + } while (next_half_edge != first_half_edge); + + topoGraph.setClusterUserIndex(cluster, clusterIndex, index); + } + + return; + } + + private static String getTransposeMatrix_(String scl) { + String transpose = new String(); + transpose += scl.charAt(0); + transpose += scl.charAt(3); + transpose += scl.charAt(6); + transpose += scl.charAt(1); + transpose += scl.charAt(4); + transpose += scl.charAt(7); + transpose += scl.charAt(2); + transpose += scl.charAt(5); + transpose += scl.charAt(8); + return transpose; + } + + // Allocates the matrix array if need be, and sets all entries to -2. + // -2: Not Computed + // -1: No intersection + // 0: 0-dimension intersection + // 1: 1-dimension intersection + // 2: 2-dimension intersection + private void resetMatrix_() { + for (int i = 0; i < 9; i++) { + m_matrix[i] = -2; + m_max_dim[i] = -2; + } + } + + private static void transposeMatrix_(int[] matrix) { + int temp1 = matrix[1]; + int temp2 = matrix[2]; + int temp5 = matrix[5]; + + matrix[1] = matrix[3]; + matrix[2] = matrix[6]; + matrix[5] = matrix[7]; + + matrix[3] = temp1; + matrix[6] = temp2; + matrix[7] = temp5; + } + + // Sets the relation predicates from the scl string. + private void setPredicates_(String scl) { + m_scl = scl; + + for (int i = 0; i < 9; i++) { + if (m_scl.charAt(i) != '*') { + m_perform_predicates[i] = true; + m_predicate_count++; + } else + m_perform_predicates[i] = false; + } + } + + // Sets the remaining predicates to false + private void setRemainingPredicatesToFalse_() { + for (int i = 0; i < 9; i++) { + if (m_perform_predicates[i] && m_matrix[i] == -2) { + m_matrix[i] = -1; + m_perform_predicates[i] = false; + } + } + } + + // Checks whether the predicate is known. + private boolean isPredicateKnown_(int predicate) { + assert (m_scl.charAt(predicate) != '*'); + + if (m_matrix[predicate] == -2) + return false; + + if (m_matrix[predicate] == -1) { + m_perform_predicates[predicate] = false; + m_predicate_count--; + return true; + } + + if (m_scl.charAt(predicate) != 'T' && m_scl.charAt(predicate) != 'F') { + if (m_matrix[predicate] < m_max_dim[predicate]) { + return false; + } else { + m_perform_predicates[predicate] = false; + m_predicate_count--; + return true; + } + } else { + m_perform_predicates[predicate] = false; + m_predicate_count--; + return true; + } + } + + // Sets the area-area predicates function. + private void setAreaAreaPredicates_() { + m_predicates_half_edge = Predicates.AreaAreaPredicates; + + m_max_dim[MatrixPredicate.InteriorInterior] = 2; + m_max_dim[MatrixPredicate.InteriorBoundary] = 1; + m_max_dim[MatrixPredicate.InteriorExterior] = 2; + m_max_dim[MatrixPredicate.BoundaryInterior] = 1; + m_max_dim[MatrixPredicate.BoundaryBoundary] = 1; + m_max_dim[MatrixPredicate.BoundaryExterior] = 1; + m_max_dim[MatrixPredicate.ExteriorInterior] = 2; + m_max_dim[MatrixPredicate.ExteriorBoundary] = 1; + m_max_dim[MatrixPredicate.ExteriorExterior] = 2; + + // set predicates that are always true/false + if (m_perform_predicates[MatrixPredicate.ExteriorExterior]) { + m_matrix[MatrixPredicate.ExteriorExterior] = 2; // Always true + m_perform_predicates[MatrixPredicate.ExteriorExterior] = false; + m_predicate_count--; + } + } + + // Sets the area-line predicate function. + private void setAreaLinePredicates_() { + m_predicates_half_edge = Predicates.AreaLinePredicates; + m_predicates_cluster = Predicates.AreaPointPredicates; + + m_max_dim[MatrixPredicate.InteriorInterior] = 1; + m_max_dim[MatrixPredicate.InteriorBoundary] = 0; + m_max_dim[MatrixPredicate.InteriorExterior] = 2; + m_max_dim[MatrixPredicate.BoundaryInterior] = 1; + m_max_dim[MatrixPredicate.BoundaryBoundary] = 0; + m_max_dim[MatrixPredicate.BoundaryExterior] = 1; + m_max_dim[MatrixPredicate.ExteriorInterior] = 1; + m_max_dim[MatrixPredicate.ExteriorBoundary] = 0; + m_max_dim[MatrixPredicate.ExteriorExterior] = 2; + + if (m_perform_predicates[MatrixPredicate.ExteriorExterior]) { + m_matrix[MatrixPredicate.ExteriorExterior] = 2; // Always true + m_perform_predicates[MatrixPredicate.ExteriorExterior] = false; + m_predicate_count--; + } + } + + // Sets the line-line predicates function. + private void setLineLinePredicates_() { + m_predicates_half_edge = Predicates.LineLinePredicates; + m_predicates_cluster = Predicates.LinePointPredicates; + + m_max_dim[MatrixPredicate.InteriorInterior] = 1; + m_max_dim[MatrixPredicate.InteriorBoundary] = 0; + m_max_dim[MatrixPredicate.InteriorExterior] = 1; + m_max_dim[MatrixPredicate.BoundaryInterior] = 0; + m_max_dim[MatrixPredicate.BoundaryBoundary] = 0; + m_max_dim[MatrixPredicate.BoundaryExterior] = 0; + m_max_dim[MatrixPredicate.ExteriorInterior] = 1; + m_max_dim[MatrixPredicate.ExteriorBoundary] = 0; + m_max_dim[MatrixPredicate.ExteriorExterior] = 2; + + // set predicates that are always true/false + if (m_perform_predicates[MatrixPredicate.ExteriorExterior]) { + m_matrix[MatrixPredicate.ExteriorExterior] = 2; // Always true + m_perform_predicates[MatrixPredicate.ExteriorExterior] = false; + m_predicate_count--; + } + } + + // Sets the area-point predicate function. + private void setAreaPointPredicates_() { + m_predicates_cluster = Predicates.AreaPointPredicates; + + m_max_dim[MatrixPredicate.InteriorInterior] = 0; + m_max_dim[MatrixPredicate.InteriorBoundary] = -1; + m_max_dim[MatrixPredicate.InteriorExterior] = 2; + m_max_dim[MatrixPredicate.BoundaryInterior] = 0; + m_max_dim[MatrixPredicate.BoundaryBoundary] = -1; + m_max_dim[MatrixPredicate.BoundaryExterior] = 1; + m_max_dim[MatrixPredicate.ExteriorInterior] = 0; + m_max_dim[MatrixPredicate.ExteriorBoundary] = -1; + m_max_dim[MatrixPredicate.ExteriorExterior] = 2; + + // set predicates that are always true/false + if (m_perform_predicates[MatrixPredicate.InteriorBoundary]) { + m_matrix[MatrixPredicate.InteriorBoundary] = -1; // Always false + m_perform_predicates[MatrixPredicate.InteriorBoundary] = false; + m_predicate_count--; + } + + if (m_perform_predicates[MatrixPredicate.BoundaryBoundary]) { + m_matrix[MatrixPredicate.BoundaryBoundary] = -1; // Always false + m_perform_predicates[MatrixPredicate.BoundaryBoundary] = false; + m_predicate_count--; + } + + if (m_perform_predicates[MatrixPredicate.ExteriorBoundary]) { + m_matrix[MatrixPredicate.ExteriorBoundary] = -1; // Always false + m_perform_predicates[MatrixPredicate.ExteriorBoundary] = false; + m_predicate_count--; + } + + if (m_perform_predicates[MatrixPredicate.ExteriorExterior]) { + m_matrix[MatrixPredicate.ExteriorExterior] = 2; // Always true + m_perform_predicates[MatrixPredicate.ExteriorExterior] = false; + m_predicate_count--; + } + } + + // Sets the line-point predicates function. + private void setLinePointPredicates_() { + m_predicates_cluster = Predicates.LinePointPredicates; + + m_max_dim[MatrixPredicate.InteriorInterior] = 0; + m_max_dim[MatrixPredicate.InteriorBoundary] = -1; + m_max_dim[MatrixPredicate.InteriorExterior] = 1; + m_max_dim[MatrixPredicate.BoundaryInterior] = 0; + m_max_dim[MatrixPredicate.BoundaryBoundary] = -1; + m_max_dim[MatrixPredicate.BoundaryExterior] = 0; + m_max_dim[MatrixPredicate.ExteriorInterior] = 0; + m_max_dim[MatrixPredicate.ExteriorBoundary] = -1; + m_max_dim[MatrixPredicate.ExteriorExterior] = 2; + + // set predicates that are always true/false + if (m_perform_predicates[MatrixPredicate.InteriorBoundary]) { + m_matrix[MatrixPredicate.InteriorBoundary] = -1; // Always false + m_perform_predicates[MatrixPredicate.InteriorBoundary] = false; + m_predicate_count--; + } + + if (m_perform_predicates[MatrixPredicate.BoundaryBoundary]) { + m_matrix[MatrixPredicate.BoundaryBoundary] = -1; // Always false + m_perform_predicates[MatrixPredicate.BoundaryBoundary] = false; + m_predicate_count--; + } + + if (m_perform_predicates[MatrixPredicate.ExteriorBoundary]) { + m_matrix[MatrixPredicate.ExteriorBoundary] = -1; // Always false + m_perform_predicates[MatrixPredicate.ExteriorBoundary] = false; + m_predicate_count--; + } + + if (m_perform_predicates[MatrixPredicate.ExteriorExterior]) { + m_matrix[MatrixPredicate.ExteriorExterior] = 2; // Always true + m_perform_predicates[MatrixPredicate.ExteriorExterior] = false; + m_predicate_count--; + } + } + + // Sets the point-point predicates function. + private void setPointPointPredicates_() { + m_predicates_cluster = Predicates.PointPointPredicates; + + m_max_dim[MatrixPredicate.InteriorInterior] = 0; + m_max_dim[MatrixPredicate.InteriorBoundary] = -1; + m_max_dim[MatrixPredicate.InteriorExterior] = 0; + m_max_dim[MatrixPredicate.BoundaryInterior] = -1; + m_max_dim[MatrixPredicate.BoundaryBoundary] = -1; + m_max_dim[MatrixPredicate.BoundaryExterior] = -1; + m_max_dim[MatrixPredicate.ExteriorInterior] = 0; + m_max_dim[MatrixPredicate.ExteriorBoundary] = -1; + m_max_dim[MatrixPredicate.ExteriorExterior] = 2; + + // set predicates that are always true/false + if (m_perform_predicates[MatrixPredicate.InteriorBoundary]) { + m_matrix[MatrixPredicate.InteriorBoundary] = -1; // Always false + m_perform_predicates[MatrixPredicate.InteriorBoundary] = false; + m_predicate_count--; + } + + if (m_perform_predicates[MatrixPredicate.BoundaryInterior]) { + m_matrix[MatrixPredicate.BoundaryInterior] = -1; // Always false + m_perform_predicates[MatrixPredicate.BoundaryInterior] = false; + m_predicate_count--; + } + + if (m_perform_predicates[MatrixPredicate.BoundaryBoundary]) { + m_matrix[MatrixPredicate.BoundaryBoundary] = -1; // Always false + m_perform_predicates[MatrixPredicate.BoundaryBoundary] = false; + m_predicate_count--; + } + + if (m_perform_predicates[MatrixPredicate.BoundaryExterior]) { + m_matrix[MatrixPredicate.BoundaryExterior] = -1; // Always false + m_perform_predicates[MatrixPredicate.BoundaryExterior] = false; + m_predicate_count--; + } + + if (m_perform_predicates[MatrixPredicate.ExteriorBoundary]) { + m_matrix[MatrixPredicate.ExteriorBoundary] = -1; // Always false + m_perform_predicates[MatrixPredicate.ExteriorBoundary] = false; + m_predicate_count--; + } + + if (m_perform_predicates[MatrixPredicate.ExteriorExterior]) { + m_matrix[MatrixPredicate.ExteriorExterior] = 2; // Always true + m_perform_predicates[MatrixPredicate.ExteriorExterior] = false; + m_predicate_count--; + } + } + + // Invokes the 9 relational predicates of area vs area. + private boolean areaAreaPredicates_(int half_edge, int id_a, int id_b) { + boolean bRelationKnown = true; + + if (m_perform_predicates[MatrixPredicate.InteriorInterior]) { + interiorAreaInteriorArea_(half_edge, id_a, id_b); + bRelationKnown &= isPredicateKnown_( + MatrixPredicate.InteriorInterior); + } + + if (m_perform_predicates[MatrixPredicate.InteriorBoundary]) { + interiorAreaBoundaryArea_(half_edge, id_a, + MatrixPredicate.InteriorBoundary); + bRelationKnown &= isPredicateKnown_( + MatrixPredicate.InteriorBoundary); + } + + if (m_perform_predicates[MatrixPredicate.InteriorExterior]) { + interiorAreaExteriorArea_(half_edge, id_a, id_b, + MatrixPredicate.InteriorExterior); + bRelationKnown &= isPredicateKnown_( + MatrixPredicate.InteriorExterior); + } + + if (m_perform_predicates[MatrixPredicate.BoundaryInterior]) { + interiorAreaBoundaryArea_(half_edge, id_b, + MatrixPredicate.BoundaryInterior); + bRelationKnown &= isPredicateKnown_( + MatrixPredicate.BoundaryInterior); + } + + if (m_perform_predicates[MatrixPredicate.BoundaryBoundary]) { + boundaryAreaBoundaryArea_(half_edge, id_a, id_b); + bRelationKnown &= isPredicateKnown_( + MatrixPredicate.BoundaryBoundary); + } + + if (m_perform_predicates[MatrixPredicate.BoundaryExterior]) { + boundaryAreaExteriorArea_(half_edge, id_a, id_b, + MatrixPredicate.BoundaryExterior); + bRelationKnown &= isPredicateKnown_( + MatrixPredicate.BoundaryExterior); + } + + if (m_perform_predicates[MatrixPredicate.ExteriorInterior]) { + interiorAreaExteriorArea_(half_edge, id_b, id_a, + MatrixPredicate.ExteriorInterior); + bRelationKnown &= isPredicateKnown_( + MatrixPredicate.ExteriorInterior); + } + + if (m_perform_predicates[MatrixPredicate.ExteriorBoundary]) { + boundaryAreaExteriorArea_(half_edge, id_b, id_a, + MatrixPredicate.ExteriorBoundary); + bRelationKnown &= isPredicateKnown_( + MatrixPredicate.ExteriorBoundary); + } + + return bRelationKnown; + } + + private void areaAreaDisjointPredicates_(Polygon polygon_a, Polygon polygon_b) { + m_matrix[MatrixPredicate.InteriorInterior] = -1; + m_matrix[MatrixPredicate.InteriorBoundary] = -1; + m_matrix[MatrixPredicate.BoundaryInterior] = -1; + m_matrix[MatrixPredicate.BoundaryBoundary] = -1; + + areaGeomContainsOrDisjointPredicates_(polygon_a, m_perform_predicates[MatrixPredicate.InteriorExterior] ? MatrixPredicate.InteriorExterior : -1, m_scl.charAt(MatrixPredicate.InteriorExterior), m_perform_predicates[MatrixPredicate.BoundaryExterior] ? MatrixPredicate.BoundaryExterior : -1, m_scl.charAt(MatrixPredicate.BoundaryExterior)); + areaGeomContainsOrDisjointPredicates_(polygon_b, m_perform_predicates[MatrixPredicate.ExteriorInterior] ? MatrixPredicate.ExteriorInterior : -1, m_scl.charAt(MatrixPredicate.ExteriorInterior), m_perform_predicates[MatrixPredicate.ExteriorBoundary] ? MatrixPredicate.ExteriorBoundary : -1, m_scl.charAt(MatrixPredicate.ExteriorBoundary)); + } + + private void areaGeomContainsOrDisjointPredicates_(Polygon polygon, int matrix_interior, char c1, int matrix_boundary, char c2) { + if (matrix_interior != -1 || matrix_boundary != -1) { + boolean has_area = ((c1 != 'T' && c1 != 'F' && matrix_interior != -1) || (c2 != 'T' && c2 != 'F' && matrix_boundary != -1) ? polygon.calculateArea2D() != 0 : true); + + if (has_area) { + if (matrix_interior != -1) + m_matrix[matrix_interior] = 2; + if (matrix_boundary != -1) + m_matrix[matrix_boundary] = 1; + } else { + if (matrix_boundary != -1) + m_matrix[matrix_boundary] = -1; + + if (matrix_interior != -1) { + Envelope2D env = new Envelope2D(); + polygon.queryEnvelope2D(env); + m_matrix[matrix_interior] = (env.getHeight() == 0.0 && env.getWidth() == 0.0 ? 0 : 1); + } + } + } + } + + private void areaAreaContainsPredicates_(Polygon polygon_b) { + m_matrix[MatrixPredicate.InteriorExterior] = 2; // im assuming its a proper contains. + m_matrix[MatrixPredicate.BoundaryInterior] = -1; + m_matrix[MatrixPredicate.BoundaryBoundary] = -1; + m_matrix[MatrixPredicate.BoundaryExterior] = 1; + m_matrix[MatrixPredicate.ExteriorInterior] = -1; + m_matrix[MatrixPredicate.ExteriorBoundary] = -1; + + areaGeomContainsOrDisjointPredicates_(polygon_b, m_perform_predicates[MatrixPredicate.InteriorInterior] ? MatrixPredicate.InteriorInterior : -1, m_scl.charAt(MatrixPredicate.InteriorInterior), m_perform_predicates[MatrixPredicate.InteriorBoundary] ? MatrixPredicate.InteriorBoundary : -1, m_scl.charAt(MatrixPredicate.InteriorBoundary)); + + // all other predicates should already be set by set_area_area_predicates + } + + private void areaAreaWithinPredicates_(Polygon polygon_a) { + areaAreaContainsPredicates_(polygon_a); + transposeMatrix_(m_matrix); + } + + private void areaLineDisjointPredicates_(Polygon polygon, Polyline polyline) { + m_matrix[MatrixPredicate.InteriorInterior] = -1; + m_matrix[MatrixPredicate.InteriorBoundary] = -1; + m_matrix[MatrixPredicate.BoundaryInterior] = -1; + m_matrix[MatrixPredicate.BoundaryBoundary] = -1; + + if (m_perform_predicates[MatrixPredicate.ExteriorInterior]) { + char c = m_scl.charAt(MatrixPredicate.ExteriorInterior); + boolean b_has_length = (c != 'T' && c != 'F' ? polyline.calculateLength2D() != 0 : true); + m_matrix[MatrixPredicate.ExteriorInterior] = (b_has_length ? 1 : 0); + } + + if (m_perform_predicates[MatrixPredicate.ExteriorBoundary]) { + boolean has_non_empty_boundary = Boundary.hasNonEmptyBoundary(polyline, null); + m_matrix[MatrixPredicate.ExteriorBoundary] = has_non_empty_boundary ? 0 : -1; + } + + areaGeomContainsOrDisjointPredicates_(polygon, m_perform_predicates[MatrixPredicate.InteriorExterior] ? MatrixPredicate.InteriorExterior : -1, m_scl.charAt(MatrixPredicate.InteriorExterior), m_perform_predicates[MatrixPredicate.BoundaryExterior] ? MatrixPredicate.BoundaryExterior : -1, m_scl.charAt(MatrixPredicate.BoundaryExterior)); + } + + private void areaLineContainsPredicates_(Polygon polygon, Polyline polyline) { + if (m_perform_predicates[MatrixPredicate.InteriorInterior]) { + char c = m_scl.charAt(MatrixPredicate.InteriorInterior); + boolean b_has_length = (c != 'T' && c != 'F' ? polyline.calculateLength2D() != 0 : true); + m_matrix[MatrixPredicate.InteriorInterior] = (b_has_length ? 1 : 0); + } + + if (m_perform_predicates[MatrixPredicate.InteriorBoundary]) { + boolean has_non_empty_boundary = Boundary.hasNonEmptyBoundary(polyline, null); + m_matrix[MatrixPredicate.InteriorBoundary] = has_non_empty_boundary ? 0 : -1; + } + + m_matrix[MatrixPredicate.InteriorExterior] = 2; //assume polygon has area + m_matrix[MatrixPredicate.BoundaryInterior] = -1; + m_matrix[MatrixPredicate.BoundaryBoundary] = -1; + m_matrix[MatrixPredicate.BoundaryExterior] = 1; //assume polygon has area + m_matrix[MatrixPredicate.ExteriorInterior] = -1; + m_matrix[MatrixPredicate.ExteriorBoundary] = -1; + } + + private void areaPointDisjointPredicates_(Polygon polygon) { + m_matrix[MatrixPredicate.InteriorInterior] = -1; + m_matrix[MatrixPredicate.BoundaryInterior] = -1; + m_matrix[MatrixPredicate.ExteriorInterior] = 0; + + areaGeomContainsOrDisjointPredicates_(polygon, m_perform_predicates[MatrixPredicate.InteriorExterior] ? MatrixPredicate.InteriorExterior : -1, m_scl.charAt(MatrixPredicate.InteriorExterior), m_perform_predicates[MatrixPredicate.BoundaryExterior] ? MatrixPredicate.BoundaryExterior : -1, m_scl.charAt(MatrixPredicate.BoundaryExterior)); + } + + private void areaPointContainsPredicates_(Polygon polygon) { + m_matrix[MatrixPredicate.InteriorInterior] = 0; + m_matrix[MatrixPredicate.InteriorExterior] = 2; //assume polygon has area + m_matrix[MatrixPredicate.BoundaryInterior] = -1; + m_matrix[MatrixPredicate.BoundaryExterior] = 1; //assume polygon has area + m_matrix[MatrixPredicate.ExteriorInterior] = -1; + } + + private void lineLineDisjointPredicates_(Polyline polyline_a, + Polyline polyline_b) { + m_matrix[MatrixPredicate.InteriorInterior] = -1; + m_matrix[MatrixPredicate.InteriorBoundary] = -1; + m_matrix[MatrixPredicate.BoundaryInterior] = -1; + m_matrix[MatrixPredicate.BoundaryBoundary] = -1; + + if (m_perform_predicates[MatrixPredicate.InteriorExterior]) { + char c = m_scl.charAt(MatrixPredicate.InteriorExterior); + boolean b_has_length = (c != 'T' && c != 'F' ? polyline_a.calculateLength2D() != 0 : true); + m_matrix[MatrixPredicate.InteriorExterior] = (b_has_length ? 1 : 0); + } + + if (m_perform_predicates[MatrixPredicate.BoundaryExterior]) { + boolean has_non_empty_boundary_a = Boundary.hasNonEmptyBoundary(polyline_a, null); + m_matrix[MatrixPredicate.BoundaryExterior] = has_non_empty_boundary_a ? 0 : -1; + } + + if (m_perform_predicates[MatrixPredicate.ExteriorInterior]) { + char c = m_scl.charAt(MatrixPredicate.ExteriorInterior); + boolean b_has_length = (c != 'T' && c != 'F' ? polyline_b.calculateLength2D() != 0 : true); + m_matrix[MatrixPredicate.ExteriorInterior] = (b_has_length ? 1 : 0); + } + + if (m_perform_predicates[MatrixPredicate.ExteriorBoundary]) { + boolean has_non_empty_boundary_b = Boundary.hasNonEmptyBoundary(polyline_b, null); + m_matrix[MatrixPredicate.ExteriorBoundary] = has_non_empty_boundary_b ? 0 : -1; + } + } + + private void linePointDisjointPredicates_(Polyline polyline) { + m_matrix[MatrixPredicate.InteriorInterior] = -1; + m_matrix[MatrixPredicate.BoundaryInterior] = -1; + + if (m_perform_predicates[MatrixPredicate.InteriorExterior]) { + char c = m_scl.charAt(MatrixPredicate.InteriorExterior); + boolean b_has_length = (c != 'T' && c != 'F' ? polyline.calculateLength2D() != 0 : true); + m_matrix[MatrixPredicate.InteriorExterior] = (b_has_length ? 1 : 0); + } + + if (m_perform_predicates[MatrixPredicate.BoundaryExterior]) { + boolean has_non_empty_boundary = Boundary.hasNonEmptyBoundary(polyline, null); + m_matrix[MatrixPredicate.BoundaryExterior] = (has_non_empty_boundary ? 0 : -1); + } + + m_matrix[MatrixPredicate.ExteriorInterior] = 0; + } + + private void pointPointDisjointPredicates_() { + m_matrix[MatrixPredicate.InteriorInterior] = -1; + m_matrix[MatrixPredicate.InteriorExterior] = 0; + m_matrix[MatrixPredicate.ExteriorInterior] = 0; + } + + // Invokes the 9 relational predicates of area vs Line. + private boolean areaLinePredicates_(int half_edge, int id_a, int id_b) { + boolean bRelationKnown = true; + + if (m_perform_predicates[MatrixPredicate.InteriorInterior]) { + interiorAreaInteriorLine_(half_edge, id_a, id_b); + bRelationKnown &= isPredicateKnown_(MatrixPredicate.InteriorInterior); + } + + if (m_perform_predicates[MatrixPredicate.InteriorBoundary]) { + interiorAreaBoundaryLine_(half_edge, id_a, id_b, m_cluster_index_b); + bRelationKnown &= isPredicateKnown_(MatrixPredicate.InteriorBoundary); + } + + if (m_perform_predicates[MatrixPredicate.InteriorExterior]) { + interiorAreaExteriorLine_(half_edge, id_a, id_b); + bRelationKnown &= isPredicateKnown_(MatrixPredicate.InteriorExterior); + } + + if (m_perform_predicates[MatrixPredicate.BoundaryInterior]) { + boundaryAreaInteriorLine_(half_edge, id_a, id_b, m_cluster_index_b); + bRelationKnown &= isPredicateKnown_(MatrixPredicate.BoundaryInterior); + } + + if (m_perform_predicates[MatrixPredicate.BoundaryBoundary]) { + boundaryAreaBoundaryLine_(half_edge, id_a, id_b, m_cluster_index_b); + bRelationKnown &= isPredicateKnown_(MatrixPredicate.BoundaryBoundary); + } + + if (m_perform_predicates[MatrixPredicate.BoundaryExterior]) { + boundaryAreaExteriorLine_(half_edge, id_a, id_b); + bRelationKnown &= isPredicateKnown_(MatrixPredicate.BoundaryExterior); + } + + if (m_perform_predicates[MatrixPredicate.ExteriorInterior]) { + exteriorAreaInteriorLine_(half_edge, id_a); + bRelationKnown &= isPredicateKnown_(MatrixPredicate.ExteriorInterior); + } + + if (m_perform_predicates[MatrixPredicate.ExteriorBoundary]) { + exteriorAreaBoundaryLine_(half_edge, id_a, id_b, m_cluster_index_b); + bRelationKnown &= isPredicateKnown_(MatrixPredicate.ExteriorBoundary); + } + + return bRelationKnown; + } + + // Invokes the 9 relational predicates of Line vs Line. + private boolean lineLinePredicates_(int half_edge, int id_a, int id_b) { + boolean bRelationKnown = true; + + if (m_perform_predicates[MatrixPredicate.InteriorInterior]) { + interiorLineInteriorLine_(half_edge, id_a, id_b, m_cluster_index_a, m_cluster_index_b); + bRelationKnown &= isPredicateKnown_(MatrixPredicate.InteriorInterior); + } + + if (m_perform_predicates[MatrixPredicate.InteriorBoundary]) { + interiorLineBoundaryLine_(half_edge, id_a, id_b, m_cluster_index_a, m_cluster_index_b, MatrixPredicate.InteriorBoundary); + bRelationKnown &= isPredicateKnown_(MatrixPredicate.InteriorBoundary); + } + + if (m_perform_predicates[MatrixPredicate.InteriorExterior]) { + interiorLineExteriorLine_(half_edge, id_a, id_b, MatrixPredicate.InteriorExterior); + bRelationKnown &= isPredicateKnown_(MatrixPredicate.InteriorExterior); + } + + if (m_perform_predicates[MatrixPredicate.BoundaryInterior]) { + interiorLineBoundaryLine_(half_edge, id_b, id_a, m_cluster_index_b, m_cluster_index_a, MatrixPredicate.BoundaryInterior); + bRelationKnown &= isPredicateKnown_(MatrixPredicate.BoundaryInterior); + } + + if (m_perform_predicates[MatrixPredicate.BoundaryBoundary]) { + boundaryLineBoundaryLine_(half_edge, id_a, id_b, m_cluster_index_a, m_cluster_index_b); + bRelationKnown &= isPredicateKnown_(MatrixPredicate.BoundaryBoundary); + } + + if (m_perform_predicates[MatrixPredicate.BoundaryExterior]) { + boundaryLineExteriorLine_(half_edge, id_a, id_b, m_cluster_index_a, MatrixPredicate.BoundaryExterior); + bRelationKnown &= isPredicateKnown_(MatrixPredicate.BoundaryExterior); + } + + if (m_perform_predicates[MatrixPredicate.ExteriorInterior]) { + interiorLineExteriorLine_(half_edge, id_b, id_a, MatrixPredicate.ExteriorInterior); + bRelationKnown &= isPredicateKnown_(MatrixPredicate.ExteriorInterior); + } + + if (m_perform_predicates[MatrixPredicate.ExteriorBoundary]) { + boundaryLineExteriorLine_(half_edge, id_b, id_a, m_cluster_index_b, MatrixPredicate.ExteriorBoundary); + bRelationKnown &= isPredicateKnown_(MatrixPredicate.ExteriorBoundary); + } + + return bRelationKnown; + } + + // Invokes the 9 relational predicates of area vs Point. + private boolean areaPointPredicates_(int cluster, int id_a, int id_b) { + boolean bRelationKnown = true; + + if (m_perform_predicates[MatrixPredicate.InteriorInterior]) { + interiorAreaInteriorPoint_(cluster, id_a); + bRelationKnown &= isPredicateKnown_(MatrixPredicate.InteriorInterior); + } + + if (m_perform_predicates[MatrixPredicate.InteriorExterior]) { + interiorAreaExteriorPoint_(cluster, id_a); + bRelationKnown &= isPredicateKnown_(MatrixPredicate.InteriorExterior); + } + + if (m_perform_predicates[MatrixPredicate.BoundaryInterior]) { + boundaryAreaInteriorPoint_(cluster, id_a, id_b); + bRelationKnown &= isPredicateKnown_(MatrixPredicate.BoundaryInterior); + } + + if (m_perform_predicates[MatrixPredicate.BoundaryExterior]) { + boundaryAreaExteriorPoint_(cluster, id_a); + bRelationKnown &= isPredicateKnown_(MatrixPredicate.BoundaryExterior); + } + + if (m_perform_predicates[MatrixPredicate.ExteriorInterior]) { + exteriorAreaInteriorPoint_(cluster, id_a); + bRelationKnown &= isPredicateKnown_(MatrixPredicate.ExteriorInterior); + } + + return bRelationKnown; + } + + // Invokes the 9 relational predicates of Line vs Point. + private boolean linePointPredicates_(int cluster, int id_a, int id_b) { + boolean bRelationKnown = true; + + if (m_perform_predicates[MatrixPredicate.InteriorInterior]) { + interiorLineInteriorPoint_(cluster, id_a, id_b, m_cluster_index_a); + bRelationKnown &= isPredicateKnown_(MatrixPredicate.InteriorInterior); + } + + if (m_perform_predicates[MatrixPredicate.InteriorExterior]) { + interiorLineExteriorPoint_(cluster, id_a, id_b, m_cluster_index_a); + bRelationKnown &= isPredicateKnown_(MatrixPredicate.InteriorExterior); + } + + if (m_perform_predicates[MatrixPredicate.BoundaryInterior]) { + boundaryLineInteriorPoint_(cluster, id_a, id_b, m_cluster_index_a); + bRelationKnown &= isPredicateKnown_(MatrixPredicate.BoundaryInterior); + } + + if (m_perform_predicates[MatrixPredicate.BoundaryExterior]) { + boundaryLineExteriorPoint_(cluster, id_a, id_b, m_cluster_index_a); + bRelationKnown &= isPredicateKnown_(MatrixPredicate.BoundaryExterior); + } + + if (m_perform_predicates[MatrixPredicate.ExteriorInterior]) { + exteriorLineInteriorPoint_(cluster, id_a, id_b); + bRelationKnown &= isPredicateKnown_(MatrixPredicate.ExteriorInterior); + } + + return bRelationKnown; + } + + // Invokes the 9 relational predicates of Point vs Point. + private boolean pointPointPredicates_(int cluster, int id_a, int id_b) { + boolean bRelationKnown = true; + + if (m_perform_predicates[MatrixPredicate.InteriorInterior]) { + interiorPointInteriorPoint_(cluster, id_a, id_b); + bRelationKnown &= isPredicateKnown_(MatrixPredicate.InteriorInterior); + } + + if (m_perform_predicates[MatrixPredicate.InteriorExterior]) { + interiorPointExteriorPoint_(cluster, id_a, id_b, MatrixPredicate.InteriorExterior); + bRelationKnown &= isPredicateKnown_(MatrixPredicate.InteriorExterior); + } + + if (m_perform_predicates[MatrixPredicate.ExteriorInterior]) { + interiorPointExteriorPoint_(cluster, id_b, id_a, MatrixPredicate.ExteriorInterior); + bRelationKnown &= isPredicateKnown_(MatrixPredicate.ExteriorInterior); + } + + return bRelationKnown; + } + + // Relational predicate to determine if the interior of area A intersects + // with the interior of area B. + private void interiorAreaInteriorArea_(int half_edge, int id_a, int id_b) { + if (m_matrix[MatrixPredicate.InteriorInterior] == 2) + return; + + int faceParentage = m_topo_graph.getHalfEdgeFaceParentage(half_edge); + + if ((faceParentage & id_a) != 0 && (faceParentage & id_b) != 0) + m_matrix[MatrixPredicate.InteriorInterior] = 2; + } + + // Relational predicate to determine if the interior of area A intersects + // with the boundary of area B. + private void interiorAreaBoundaryArea_(int half_edge, int id_a, + int predicate) { + if (m_matrix[predicate] == 1) + return; + + int faceParentage = m_topo_graph.getHalfEdgeFaceParentage(half_edge); + int twinFaceParentage = m_topo_graph + .getHalfEdgeFaceParentage(m_topo_graph + .getHalfEdgeTwin(half_edge)); + + if ((faceParentage & id_a) != 0 && (twinFaceParentage & id_a) != 0) + m_matrix[predicate] = 1; + } + + // Relational predicate to determine if the interior of area A intersects + // with the exterior of area B. + private void interiorAreaExteriorArea_(int half_edge, int id_a, int id_b, + int predicate) { + if (m_matrix[predicate] == 2) + return; + + int faceParentage = m_topo_graph.getHalfEdgeFaceParentage(half_edge); + + if ((faceParentage & id_a) != 0 && (faceParentage & id_b) == 0) + m_matrix[predicate] = 2; + + } + + // Relational predicate to determine if the boundary of area A intersects + // with the boundary of area B. + private void boundaryAreaBoundaryArea_(int half_edge, int id_a, int id_b) { + if (m_matrix[MatrixPredicate.BoundaryBoundary] == 1) + return; + + int parentage = m_topo_graph.getHalfEdgeParentage(half_edge); + + if ((parentage & id_a) != 0 && (parentage & id_b) != 0) { + m_matrix[MatrixPredicate.BoundaryBoundary] = 1; + return; + } + + if (m_matrix[MatrixPredicate.BoundaryBoundary] != 0) { + if (m_topo_graph.getHalfEdgeUserIndex(m_topo_graph + .getHalfEdgePrev(m_topo_graph.getHalfEdgeTwin(half_edge)), + m_visited_index) != 1) { + int cluster = m_topo_graph.getHalfEdgeTo(half_edge); + int clusterParentage = m_topo_graph + .getClusterParentage(cluster); + + if ((clusterParentage & id_a) != 0 + && (clusterParentage & id_b) != 0) { + m_matrix[MatrixPredicate.BoundaryBoundary] = 0; + } + } + } + } + + // Relational predicate to determine if the boundary of area A intersects + // with the exterior of area B. + private void boundaryAreaExteriorArea_(int half_edge, int id_a, int id_b, + int predicate) { + if (m_matrix[predicate] == 1) + return; + + int faceParentage = m_topo_graph.getHalfEdgeFaceParentage(half_edge); + int twinFaceParentage = m_topo_graph + .getHalfEdgeFaceParentage(m_topo_graph + .getHalfEdgeTwin(half_edge)); + + if ((faceParentage & id_b) == 0 && (twinFaceParentage & id_b) == 0) + m_matrix[predicate] = 1; + } + + // Relational predicate to determine if the interior of area A intersects + // with the interior of Line B. + private void interiorAreaInteriorLine_(int half_edge, int id_a, int id_b) { + if (m_matrix[MatrixPredicate.InteriorInterior] == 1) + return; + + int faceParentage = m_topo_graph.getHalfEdgeFaceParentage(half_edge); + int twinFaceParentage = m_topo_graph + .getHalfEdgeFaceParentage(m_topo_graph + .getHalfEdgeTwin(half_edge)); + + if ((faceParentage & id_a) != 0 && (twinFaceParentage & id_a) != 0) + m_matrix[MatrixPredicate.InteriorInterior] = 1; + } + + // Relational predicate to determine if the interior of area A intersects + // with the boundary of Line B. + private void interiorAreaBoundaryLine_(int half_edge, int id_a, int id_b, + int cluster_index_b) { + if (m_matrix[MatrixPredicate.InteriorBoundary] == 0) + return; + + if (m_topo_graph.getHalfEdgeUserIndex(m_topo_graph + .getHalfEdgePrev(m_topo_graph.getHalfEdgeTwin(half_edge)), + m_visited_index) != 1) { + int cluster = m_topo_graph.getHalfEdgeTo(half_edge); + int clusterParentage = m_topo_graph.getClusterParentage(cluster); + + if ((clusterParentage & id_a) == 0) { + int faceParentage = m_topo_graph + .getHalfEdgeFaceParentage(half_edge); + + if ((faceParentage & id_a) != 0) { + int index = m_topo_graph.getClusterUserIndex(cluster, + cluster_index_b); + + if ((clusterParentage & id_b) != 0 && (index % 2 != 0)) { + assert (index != -1); + m_matrix[MatrixPredicate.InteriorBoundary] = 0; + } + } + } + } + } + + private void interiorAreaExteriorLine_(int half_edge, int id_a, int id_b) { + if (m_matrix[MatrixPredicate.InteriorExterior] == 2) + return; + + int half_edge_parentage = m_topo_graph.getHalfEdgeParentage(half_edge); + + if ((half_edge_parentage & id_a) != 0) {//half edge of polygon + m_matrix[MatrixPredicate.InteriorExterior] = 2; + } + } + + // Relational predicate to determine if the boundary of area A intersects + // with the interior of Line B. + private void boundaryAreaInteriorLine_(int half_edge, int id_a, int id_b, + int cluster_index_b) { + if (m_matrix[MatrixPredicate.BoundaryInterior] == 1) + return; + + int parentage = m_topo_graph.getHalfEdgeParentage(half_edge); + + if ((parentage & id_a) != 0 && (parentage & id_b) != 0) { + m_matrix[MatrixPredicate.BoundaryInterior] = 1; + return; + } + + if (m_matrix[MatrixPredicate.BoundaryInterior] != 0) { + if (m_topo_graph.getHalfEdgeUserIndex(m_topo_graph + .getHalfEdgePrev(m_topo_graph.getHalfEdgeTwin(half_edge)), + m_visited_index) != 1) { + int cluster = m_topo_graph.getHalfEdgeTo(half_edge); + int clusterParentage = m_topo_graph + .getClusterParentage(cluster); + + if ((clusterParentage & id_a) != 0) { + int index = m_topo_graph.getClusterUserIndex(cluster, + cluster_index_b); + + if ((clusterParentage & id_b) != 0 && (index % 2 == 0)) { + assert (index != -1); + m_matrix[MatrixPredicate.BoundaryInterior] = 0; + } + } + } + } + } + + // Relational predicate to determine if the boundary of area A intersects + // with the boundary of Line B. + private void boundaryAreaBoundaryLine_(int half_edge, int id_a, int id_b, + int cluster_index_b) { + if (m_matrix[MatrixPredicate.BoundaryBoundary] == 0) + return; + + if (m_topo_graph.getHalfEdgeUserIndex(m_topo_graph + .getHalfEdgePrev(m_topo_graph.getHalfEdgeTwin(half_edge)), + m_visited_index) != 1) { + int cluster = m_topo_graph.getHalfEdgeTo(half_edge); + int clusterParentage = m_topo_graph.getClusterParentage(cluster); + + if ((clusterParentage & id_a) != 0) { + int index = m_topo_graph.getClusterUserIndex(cluster, + cluster_index_b); + + if ((clusterParentage & id_b) != 0 && (index % 2 != 0)) { + assert (index != -1); + m_matrix[MatrixPredicate.BoundaryBoundary] = 0; + } + } + } + } + + // Relational predicate to determine if the boundary of area A intersects + // with the exterior of Line B. + private void boundaryAreaExteriorLine_(int half_edge, int id_a, int id_b) { + if (m_matrix[MatrixPredicate.BoundaryExterior] == 1) + return; + + int parentage = m_topo_graph.getHalfEdgeParentage(half_edge); + + if ((parentage & id_a) != 0 && (parentage & id_b) == 0) + m_matrix[MatrixPredicate.BoundaryExterior] = 1; + + } + + // Relational predicate to determine if the exterior of area A intersects + // with the interior of Line B. + private void exteriorAreaInteriorLine_(int half_edge, int id_a) { + if (m_matrix[MatrixPredicate.ExteriorInterior] == 1) + return; + + int faceParentage = m_topo_graph.getHalfEdgeFaceParentage(half_edge); + int twinFaceParentage = m_topo_graph + .getHalfEdgeFaceParentage(m_topo_graph + .getHalfEdgeTwin(half_edge)); + + if ((faceParentage & id_a) == 0 && (twinFaceParentage & id_a) == 0) + m_matrix[MatrixPredicate.ExteriorInterior] = 1; + } + + // Relational predicate to determine if the exterior of area A intersects + // with the boundary of Line B. + private void exteriorAreaBoundaryLine_(int half_edge, int id_a, int id_b, + int cluster_index_b) { + if (m_matrix[MatrixPredicate.ExteriorBoundary] == 0) + return; + + if (m_topo_graph.getHalfEdgeUserIndex(m_topo_graph + .getHalfEdgePrev(m_topo_graph.getHalfEdgeTwin(half_edge)), + m_visited_index) != 1) { + int cluster = m_topo_graph.getHalfEdgeTo(half_edge); + int clusterParentage = m_topo_graph.getClusterParentage(cluster); + + if ((clusterParentage & id_a) == 0) { + int faceParentage = m_topo_graph + .getHalfEdgeFaceParentage(half_edge); + + if ((faceParentage & id_a) == 0) { + assert ((m_topo_graph.getHalfEdgeParentage(m_topo_graph + .getHalfEdgeTwin(half_edge)) & id_a) == 0); + + int index = m_topo_graph.getClusterUserIndex(cluster, + cluster_index_b); + + if ((clusterParentage & id_b) != 0 && (index % 2 != 0)) { + assert (index != -1); + m_matrix[MatrixPredicate.ExteriorBoundary] = 0; + } + } + } + } + } + + // Relational predicate to determine if the interior of Line A intersects + // with the interior of Line B. + private void interiorLineInteriorLine_(int half_edge, int id_a, int id_b, + int cluster_index_a, int cluster_index_b) { + if (m_matrix[MatrixPredicate.InteriorInterior] == 1) + return; + + int parentage = m_topo_graph.getHalfEdgeParentage(half_edge); + + if ((parentage & id_a) != 0 && (parentage & id_b) != 0) { + m_matrix[MatrixPredicate.InteriorInterior] = 1; + return; + } + + if (m_matrix[MatrixPredicate.InteriorInterior] != 0) { + if (m_topo_graph.getHalfEdgeUserIndex(m_topo_graph + .getHalfEdgePrev(m_topo_graph.getHalfEdgeTwin(half_edge)), + m_visited_index) != 1) { + int cluster = m_topo_graph.getHalfEdgeTo(half_edge); + int clusterParentage = m_topo_graph + .getClusterParentage(cluster); + + if ((clusterParentage & id_a) != 0 + && (clusterParentage & id_b) != 0) { + int index_a = m_topo_graph.getClusterUserIndex(cluster, + cluster_index_a); + int index_b = m_topo_graph.getClusterUserIndex(cluster, + cluster_index_b); + assert (index_a != -1); + assert (index_b != -1); + + if ((index_a % 2 == 0) && (index_b % 2 == 0)) { + assert ((m_topo_graph.getClusterParentage(cluster) & id_a) != 0 && (m_topo_graph + .getClusterParentage(cluster) & id_b) != 0); + m_matrix[MatrixPredicate.InteriorInterior] = 0; + } + } + } + } + } + + // Relational predicate to determine of the interior of LineA intersects + // with the boundary of Line B. + private void interiorLineBoundaryLine_(int half_edge, int id_a, int id_b, + int cluster_index_a, int cluster_index_b, int predicate) { + if (m_matrix[predicate] == 0) + return; + + if (m_topo_graph.getHalfEdgeUserIndex(m_topo_graph + .getHalfEdgePrev(m_topo_graph.getHalfEdgeTwin(half_edge)), + m_visited_index) != 1) { + int cluster = m_topo_graph.getHalfEdgeTo(half_edge); + int clusterParentage = m_topo_graph.getClusterParentage(cluster); + + if ((clusterParentage & id_a) != 0 + && (clusterParentage & id_b) != 0) { + int index_a = m_topo_graph.getClusterUserIndex(cluster, + cluster_index_a); + int index_b = m_topo_graph.getClusterUserIndex(cluster, + cluster_index_b); + assert (index_a != -1); + assert (index_b != -1); + + if ((index_a % 2 == 0) && (index_b % 2 != 0)) { + assert ((m_topo_graph.getClusterParentage(cluster) & id_a) != 0 && (m_topo_graph + .getClusterParentage(cluster) & id_b) != 0); + m_matrix[predicate] = 0; + } + } + } + } + + // Relational predicate to determine if the interior of Line A intersects + // with the exterior of Line B. + private void interiorLineExteriorLine_(int half_edge, int id_a, int id_b, + int predicate) { + if (m_matrix[predicate] == 1) + return; + + int parentage = m_topo_graph.getHalfEdgeParentage(half_edge); + + if ((parentage & id_a) != 0 && (parentage & id_b) == 0) + m_matrix[predicate] = 1; + } + + // Relational predicate to determine if the boundary of Line A intersects + // with the boundary of Line B. + private void boundaryLineBoundaryLine_(int half_edge, int id_a, int id_b, + int cluster_index_a, int cluster_index_b) { + if (m_matrix[MatrixPredicate.BoundaryBoundary] == 0) + return; + + if (m_topo_graph.getHalfEdgeUserIndex(m_topo_graph + .getHalfEdgePrev(m_topo_graph.getHalfEdgeTwin(half_edge)), + m_visited_index) != 1) { + int cluster = m_topo_graph.getHalfEdgeTo(half_edge); + int clusterParentage = m_topo_graph.getClusterParentage(cluster); + + if ((clusterParentage & id_a) != 0 + && (clusterParentage & id_b) != 0) { + int index_a = m_topo_graph.getClusterUserIndex(cluster, + cluster_index_a); + int index_b = m_topo_graph.getClusterUserIndex(cluster, + cluster_index_b); + assert (index_a != -1); + assert (index_b != -1); + + if ((index_a % 2 != 0) && (index_b % 2 != 0)) { + assert ((m_topo_graph.getClusterParentage(cluster) & id_a) != 0 && (m_topo_graph + .getClusterParentage(cluster) & id_b) != 0); + m_matrix[MatrixPredicate.BoundaryBoundary] = 0; + } + } + } + } + + // Relational predicate to determine if the boundary of Line A intersects + // with the exterior of Line B. + private void boundaryLineExteriorLine_(int half_edge, int id_a, int id_b, + int cluster_index_a, int predicate) { + if (m_matrix[predicate] == 0) + return; + + if (m_topo_graph.getHalfEdgeUserIndex(m_topo_graph + .getHalfEdgePrev(m_topo_graph.getHalfEdgeTwin(half_edge)), + m_visited_index) != 1) { + int cluster = m_topo_graph.getHalfEdgeTo(half_edge); + int clusterParentage = m_topo_graph.getClusterParentage(cluster); + + if ((clusterParentage & id_b) == 0) { + int index = m_topo_graph.getClusterUserIndex(cluster, + cluster_index_a); + assert (index != -1); + + if (index % 2 != 0) { + assert ((m_topo_graph.getClusterParentage(cluster) & id_a) != 0); + m_matrix[predicate] = 0; + } + } + } + } + + // Relational predicate to determine if the interior of area A intersects + // with the interior of Point B. + private void interiorAreaInteriorPoint_(int cluster, int id_a) { + if (m_matrix[MatrixPredicate.InteriorInterior] == 0) + return; + + int clusterParentage = m_topo_graph.getClusterParentage(cluster); + + if ((clusterParentage & id_a) == 0) { + int chain = m_topo_graph.getClusterChain(cluster); + int chainParentage = m_topo_graph.getChainParentage(chain); + + if ((chainParentage & id_a) != 0) { + m_matrix[MatrixPredicate.InteriorInterior] = 0; + } + } + } + + private void interiorAreaExteriorPoint_(int cluster, int id_a) { + if (m_matrix[MatrixPredicate.InteriorExterior] == 2) + return; + + int cluster_parentage = m_topo_graph.getClusterParentage(cluster); + + if ((cluster_parentage & id_a) != 0) { + m_matrix[MatrixPredicate.InteriorExterior] = 2; + } + } + + // Relational predicate to determine if the boundary of area A intersects + // with the interior of Point B. + private void boundaryAreaInteriorPoint_(int cluster, int id_a, int id_b) { + if (m_matrix[MatrixPredicate.BoundaryInterior] == 0) + return; + + int clusterParentage = m_topo_graph.getClusterParentage(cluster); + + if ((clusterParentage & id_a) != 0 && (clusterParentage & id_b) != 0) { + m_matrix[MatrixPredicate.BoundaryInterior] = 0; + } + } + + private void boundaryAreaExteriorPoint_(int cluster, int id_a) { + if (m_matrix[MatrixPredicate.BoundaryExterior] == 1) + return; + + int cluster_parentage = m_topo_graph.getClusterParentage(cluster); + + if ((cluster_parentage & id_a) != 0) { + m_matrix[MatrixPredicate.BoundaryExterior] = 1; + } + } + + // Relational predicate to determine if the exterior of area A intersects + // with the interior of Point B. + private void exteriorAreaInteriorPoint_(int cluster, int id_a) { + if (m_matrix[MatrixPredicate.ExteriorInterior] == 0) + return; + + int clusterParentage = m_topo_graph.getClusterParentage(cluster); + + if ((clusterParentage & id_a) == 0) { + int chain = m_topo_graph.getClusterChain(cluster); + int chainParentage = m_topo_graph.getChainParentage(chain); + + if ((chainParentage & id_a) == 0) { + m_matrix[MatrixPredicate.ExteriorInterior] = 0; + } + } + } + + // Relational predicate to determine if the interior of Line A intersects + // with the interior of Point B. + private void interiorLineInteriorPoint_(int cluster, int id_a, int id_b, + int cluster_index_a) { + if (m_matrix[MatrixPredicate.InteriorInterior] == 0) + return; + + int clusterParentage = m_topo_graph.getClusterParentage(cluster); + + if ((clusterParentage & id_a) != 0 && (clusterParentage & id_b) != 0) { + int index = m_topo_graph.getClusterUserIndex(cluster, + cluster_index_a); + + if (index % 2 == 0) { + m_matrix[MatrixPredicate.InteriorInterior] = 0; + } + } + } + + private void interiorLineExteriorPoint_(int cluster, int id_a, int id_b, int cluster_index_a) { + if (m_matrix[MatrixPredicate.InteriorExterior] == 1) + return; + + int half_edge_a = m_topo_graph.getClusterHalfEdge(cluster); + + if (half_edge_a != -1) { + m_matrix[MatrixPredicate.InteriorExterior] = 1; + return; + } + + if (m_matrix[MatrixPredicate.InteriorExterior] != 0) { + int clusterParentage = m_topo_graph.getClusterParentage(cluster); + + if ((clusterParentage & id_b) == 0) { + assert (m_topo_graph.getClusterUserIndex(cluster, cluster_index_a) % 2 == 0); + m_matrix[MatrixPredicate.InteriorExterior] = 0; + return; + } + } + + return; + } + + // Relational predicate to determine if the boundary of Line A intersects + // with the interior of Point B. + private void boundaryLineInteriorPoint_(int cluster, int id_a, int id_b, + int cluster_index_a) { + if (m_matrix[MatrixPredicate.BoundaryInterior] == 0) + return; + + int clusterParentage = m_topo_graph.getClusterParentage(cluster); + + if ((clusterParentage & id_a) != 0 && (clusterParentage & id_b) != 0) { + int index = m_topo_graph.getClusterUserIndex(cluster, + cluster_index_a); + + if (index % 2 != 0) { + m_matrix[MatrixPredicate.BoundaryInterior] = 0; + } + } + } + + // Relational predicate to determine if the boundary of Line A intersects + // with the exterior of Point B. + private void boundaryLineExteriorPoint_(int cluster, int id_a, int id_b, + int cluster_index_a) { + if (m_matrix[MatrixPredicate.BoundaryExterior] == 0) + return; + + int clusterParentage = m_topo_graph.getClusterParentage(cluster); + + if ((clusterParentage & id_a) != 0 && (clusterParentage & id_b) == 0) { + int index = m_topo_graph.getClusterUserIndex(cluster, + cluster_index_a); + + if (index % 2 != 0) { + m_matrix[MatrixPredicate.BoundaryExterior] = 0; + } + } + } + + // Relational predicate to determine if the exterior of Line A intersects + // with the interior of Point B. + private void exteriorLineInteriorPoint_(int cluster, int id_a, int id_b) { + if (m_matrix[MatrixPredicate.ExteriorInterior] == 0) + return; + + int clusterParentage = m_topo_graph.getClusterParentage(cluster); + + if ((clusterParentage & id_a) == 0 && (clusterParentage & id_b) != 0) { + m_matrix[MatrixPredicate.ExteriorInterior] = 0; + } + } + + // Relational predicate to determine if the interior of Point A intersects + // with the interior of Point B. + private void interiorPointInteriorPoint_(int cluster, int id_a, int id_b) { + if (m_matrix[MatrixPredicate.InteriorInterior] == 0) + return; + + int clusterParentage = m_topo_graph.getClusterParentage(cluster); + + if ((clusterParentage & id_a) != 0 && (clusterParentage & id_b) != 0) { + m_matrix[MatrixPredicate.InteriorInterior] = 0; + } + } + + // Relational predicate to determine if the interior of Point A intersects + // with the exterior of Point B. + private void interiorPointExteriorPoint_(int cluster, int id_a, int id_b, + int predicate) { + if (m_matrix[predicate] == 0) + return; + + int clusterParentage = m_topo_graph.getClusterParentage(cluster); + + if ((clusterParentage & id_a) != 0 && (clusterParentage & id_b) == 0) { + m_matrix[predicate] = 0; + } + } + + // Computes the 9 intersection relationships of boundary, interior, and + // exterior of geometry_a vs geometry_b using the Topo_graph for area/area, + // area/Line, and Line/Line relations + private void computeMatrixTopoGraphHalfEdges_(int geometry_a, int geometry_b) { + boolean bRelationKnown = false; + + int id_a = m_topo_graph.getGeometryID(geometry_a); + int id_b = m_topo_graph.getGeometryID(geometry_b); + + m_visited_index = m_topo_graph.createUserIndexForHalfEdges(); + + for (int cluster = m_topo_graph.getFirstCluster(); cluster != -1; cluster = m_topo_graph + .getNextCluster(cluster)) { + int first_half_edge = m_topo_graph.getClusterHalfEdge(cluster); + if (first_half_edge == -1) { + if (m_predicates_cluster != -1) { + // Treat cluster as an interior point + switch (m_predicates_cluster) { + case Predicates.AreaPointPredicates: + bRelationKnown = areaPointPredicates_(cluster, id_a, id_b); + break; + case Predicates.LinePointPredicates: + bRelationKnown = linePointPredicates_(cluster, id_a, id_b); + break; + default: + throw GeometryException.GeometryInternalError(); + } + } + + continue; + } + + int next_half_edge = first_half_edge; + + do { + int half_edge = next_half_edge; + int visited = m_topo_graph.getHalfEdgeUserIndex(half_edge, + m_visited_index); + + if (visited != 1) { + do { + // Invoke relational predicates + switch (m_predicates_half_edge) { + case Predicates.AreaAreaPredicates: + bRelationKnown = areaAreaPredicates_(half_edge, + id_a, id_b); + break; + case Predicates.AreaLinePredicates: + bRelationKnown = areaLinePredicates_(half_edge, + id_a, id_b); + break; + case Predicates.LineLinePredicates: + bRelationKnown = lineLinePredicates_(half_edge, + id_a, id_b); + break; + default: + throw GeometryException.GeometryInternalError(); + } + + if (bRelationKnown) + break; + + m_topo_graph.setHalfEdgeUserIndex(half_edge, + m_visited_index, 1); + half_edge = m_topo_graph.getHalfEdgeNext(half_edge); + } while (half_edge != next_half_edge && !bRelationKnown); + } + + if (bRelationKnown) + break; + + next_half_edge = m_topo_graph.getHalfEdgeNext(m_topo_graph + .getHalfEdgeTwin(half_edge)); + } while (next_half_edge != first_half_edge); + + if (bRelationKnown) + break; + } + + if (!bRelationKnown) + setRemainingPredicatesToFalse_(); + + m_topo_graph.deleteUserIndexForHalfEdges(m_visited_index); + } + + // Computes the 9 intersection relationships of boundary, interior, and + // exterior of geometry_a vs geometry_b using the Topo_graph for area/Point, + // Line/Point, and Point/Point relations + private void computeMatrixTopoGraphClusters_(int geometry_a, int geometry_b) { + boolean bRelationKnown = false; + + int id_a = m_topo_graph.getGeometryID(geometry_a); + int id_b = m_topo_graph.getGeometryID(geometry_b); + + for (int cluster = m_topo_graph.getFirstCluster(); cluster != -1; cluster = m_topo_graph + .getNextCluster(cluster)) { + // Invoke relational predicates + switch (m_predicates_cluster) { + case Predicates.AreaPointPredicates: + bRelationKnown = areaPointPredicates_(cluster, id_a, id_b); + break; + case Predicates.LinePointPredicates: + bRelationKnown = linePointPredicates_(cluster, id_a, id_b); + break; + case Predicates.PointPointPredicates: + bRelationKnown = pointPointPredicates_(cluster, id_a, id_b); + break; + default: + throw GeometryException.GeometryInternalError(); + } + + if (bRelationKnown) + break; + } + + if (!bRelationKnown) + setRemainingPredicatesToFalse_(); + } + + // Call this method to set the edit shape, if the edit shape has been + // cracked and clustered already. + private void setEditShape_(EditShape shape, ProgressTracker progressTracker) { + m_topo_graph.setEditShape(shape, progressTracker); + } + + private void setEditShapeCrackAndCluster_(EditShape shape, + double tolerance, ProgressTracker progress_tracker) { + editShapeCrackAndCluster_(shape, tolerance, progress_tracker); + setEditShape_(shape, progress_tracker); + } + + private void editShapeCrackAndCluster_(EditShape shape, double tolerance, + ProgressTracker progress_tracker) { + CrackAndCluster.execute(shape, tolerance, progress_tracker, false); //do not filter degenerate segments. + shape.filterClosePoints(0, true, true);//remove degeneracies from polygon geometries. + for (int geometry = shape.getFirstGeometry(); geometry != -1; geometry = shape + .getNextGeometry(geometry)) { + if (shape.getGeometryType(geometry) == Geometry.Type.Polygon + .value()) + Simplificator.execute(shape, geometry, -1, false, progress_tracker); + } + } + + // Upgrades the geometry to a feature geometry. + private static Geometry convertGeometry_(Geometry geometry, double tolerance) { + int gt = geometry.getType().value(); + + if (Geometry.isSegment(gt)) { + Polyline polyline = new Polyline(geometry.getDescription()); + polyline.addSegment((Segment) geometry, true); + return polyline; + } + + if (gt == Geometry.GeometryType.Envelope) { + Envelope envelope = (Envelope) (geometry); + Envelope2D env = new Envelope2D(); + geometry.queryEnvelope2D(env); + + if (env.getHeight() <= tolerance && env.getWidth() <= tolerance) {// treat + // as + // point + Point point = new Point(geometry.getDescription()); + envelope.getCenter(point); + return point; + } + + if (env.getHeight() <= tolerance || env.getWidth() <= tolerance) {// treat + // as + // line + Polyline polyline = new Polyline(geometry.getDescription()); + Point p = new Point(); + envelope.queryCornerByVal(0, p); + polyline.startPath(p); + envelope.queryCornerByVal(2, p); + polyline.lineTo(p); + return polyline; + } + + // treat as polygon + Polygon polygon = new Polygon(geometry.getDescription()); + polygon.addEnvelope(envelope, false); + return polygon; + } + + return geometry; + } } diff --git a/src/main/java/com/esri/core/geometry/RingOrientationFixer.java b/src/main/java/com/esri/core/geometry/RingOrientationFixer.java index c84703aa..af601b83 100644 --- a/src/main/java/com/esri/core/geometry/RingOrientationFixer.java +++ b/src/main/java/com/esri/core/geometry/RingOrientationFixer.java @@ -24,662 +24,662 @@ package com.esri.core.geometry; class RingOrientationFixer { - EditShape m_shape; - Treap m_AET; - double m_y_scanline; - int m_geometry; - int m_unknown_ring_orientation_count; - IndexMultiDCList m_sorted_vertices; - AttributeStreamOfInt32 m_unknown_nodes; - int m_node_1_user_index; - int m_node_2_user_index; - int m_path_orientation_index; - int m_path_parentage_index; - boolean m_fixSelfTangency; - - static final class Edges { - EditShape m_shape; - AttributeStreamOfInt32 m_end_1_nodes; - AttributeStreamOfInt32 m_end_2_nodes; - AttributeStreamOfInt8 m_directions; - Point2D pt_1 = new Point2D(); - Point2D pt_2 = new Point2D(); - int m_first_free; - - boolean getDirection_(int index) { - return m_shape.getNextVertex(getEnd1(index)) == getEnd2(index); - } - - int getEnd_(int index) { - int v_1 = getEnd1(index); - int v_2 = getEnd2(index); - if (m_shape.getNextVertex(v_1) == v_2) - return v_2; - else - return v_1; - } - - Edges(EditShape shape) { - m_shape = shape; - m_first_free = -1; - } - - Segment getSegment(int index) { - return m_shape.getSegment(getStart(index)); - } - - // True if the start vertex is the lower point of the edge. - boolean isBottomUp(int index) { - int v_1 = getEnd1(index); - int v_2 = getEnd2(index); - if (m_shape.getPrevVertex(v_1) == v_2) { - int temp = v_1; - v_1 = v_2; - v_2 = temp; - } - m_shape.getXY(v_1, pt_1); - m_shape.getXY(v_2, pt_2); - return pt_1.y < pt_2.y; - } - - int getStart(int index) { - int v_1 = getEnd1(index); - int v_2 = getEnd2(index); - return (m_shape.getNextVertex(v_1) == v_2) ? v_1 : v_2; - } - - int getEnd1(int index) { - return m_end_1_nodes.get(index); - } - - int getEnd2(int index) { - return m_end_2_nodes.get(index); - } - - void freeEdge(int edge) { - m_end_1_nodes.set(edge, m_first_free); - m_first_free = edge; - } - - int newEdge(int vertex) { - if (m_first_free != -1) { - int index = m_first_free; - m_first_free = m_end_1_nodes.get(index); - m_end_1_nodes.set(index, vertex); - m_end_2_nodes.set(index, m_shape.getNextVertex(vertex)); - return index; - } else if (m_end_1_nodes == null) { - m_end_1_nodes = new AttributeStreamOfInt32(0); - m_end_2_nodes = new AttributeStreamOfInt32(0); - } - - int index = m_end_1_nodes.size(); - m_end_1_nodes.add(vertex); - m_end_2_nodes.add(m_shape.getNextVertex(vertex)); - return index; - } - - EditShape getShape() { - return m_shape; - } - - int getPath(int index) { - return m_shape.getPathFromVertex(getEnd1(index)); - } - } - - Edges m_edges; - - class RingOrientationTestComparator extends Treap.Comparator { - RingOrientationFixer m_helper; - Line m_line_1; - Line m_line_2; - int m_left_elm; - double m_leftx; - Segment m_seg_1; - - RingOrientationTestComparator(RingOrientationFixer helper) { - m_helper = helper; - m_line_1 = new Line(); - m_line_2 = new Line(); - m_leftx = 0; - m_seg_1 = null; - m_left_elm = -1; - } - - @Override - int compare(Treap treap, int left, int node) { - int right = treap.getElement(node); - RingOrientationFixer.Edges edges = m_helper.m_edges; - double x_1; - if (m_left_elm == left) - x_1 = m_leftx; - else { - m_seg_1 = edges.getSegment(left); - if (m_seg_1 == null) { - EditShape shape = edges.getShape(); - shape.queryLineConnector(edges.getStart(left), m_line_1); - m_seg_1 = m_line_1; - x_1 = m_line_1.intersectionOfYMonotonicWithAxisX( - m_helper.m_y_scanline, 0); - } else - x_1 = m_seg_1.intersectionOfYMonotonicWithAxisX( - m_helper.m_y_scanline, 0); - - m_leftx = x_1; - m_left_elm = left; - } - - Segment seg_2 = edges.getSegment(right); - double x2; - if (seg_2 == null) { - EditShape shape = edges.getShape(); - shape.queryLineConnector(edges.getStart(right), m_line_2); - seg_2 = m_line_2; - x2 = m_line_2.intersectionOfYMonotonicWithAxisX( - m_helper.m_y_scanline, 0); - } else - x2 = seg_2.intersectionOfYMonotonicWithAxisX( - m_helper.m_y_scanline, 0); - - if (x_1 == x2) { - boolean bStartLower1 = edges.isBottomUp(left); - boolean bStartLower2 = edges.isBottomUp(right); - - // apparently these edges originate from same vertex and the - // scanline is on the vertex. move scanline a little. - double y1 = !bStartLower1 ? m_seg_1.getStartY() : m_seg_1 - .getEndY(); - double y2 = !bStartLower2 ? seg_2.getStartY() : seg_2.getEndY(); - double miny = Math.min(y1, y2); - double y = (miny + m_helper.m_y_scanline) * 0.5; - if (y == m_helper.m_y_scanline) { - // assert(0);//ST: not a bug. just curious to see this - // happens. - y = miny; // apparently, one of the segments is almost - // horizontal line. - } - x_1 = m_seg_1.intersectionOfYMonotonicWithAxisX(y, 0); - x2 = seg_2.intersectionOfYMonotonicWithAxisX(y, 0); - assert (x_1 != x2); - } - - return x_1 < x2 ? -1 : (x_1 > x2 ? 1 : 0); - } - - void reset() { - m_left_elm = -1; - } - } - - RingOrientationTestComparator m_sweep_comparator; - - RingOrientationFixer() { - m_AET = new Treap(); - m_AET.disableBalancing(); - m_sweep_comparator = new RingOrientationTestComparator(this); - m_AET.setComparator(m_sweep_comparator); - } - - boolean fixRingOrientation_() { - boolean bFound = false; - - if (m_fixSelfTangency) - bFound = fixRingSelfTangency_(); - - if (m_shape.getPathCount(m_geometry) == 1) { - int path = m_shape.getFirstPath(m_geometry); - double area = m_shape.getRingArea(path); - m_shape.setExterior(path, true); - if (area < 0) { - int first = m_shape.getFirstVertex(path); - m_shape.reverseRingInternal_(first); - m_shape.setLastVertex_(path, m_shape.getPrevVertex(first));// fix - // last - // after - // the - // reverse - return true; - } - - return false; - } - - m_path_orientation_index = m_shape.createPathUserIndex();// used to - // store - // discovered - // orientation - // (3 - - // extrior, - // 2 - - // interior) - m_path_parentage_index = m_shape.createPathUserIndex();// used to - // resolve OGC - // order - for (int path = m_shape.getFirstPath(m_geometry); path != -1; path = m_shape - .getNextPath(path)) { - m_shape.setPathUserIndex(path, m_path_orientation_index, 0); - m_shape.setPathUserIndex(path, m_path_parentage_index, -1); - } - - AttributeStreamOfInt32 bunch = new AttributeStreamOfInt32(0); - m_y_scanline = NumberUtils.TheNaN; - Point2D pt = new Point2D(); - m_unknown_ring_orientation_count = m_shape.getPathCount(m_geometry); - m_node_1_user_index = m_shape.createUserIndex(); - m_node_2_user_index = m_shape.createUserIndex(); - for (int ivertex = m_sorted_vertices.getFirst(m_sorted_vertices - .getFirstList()); ivertex != -1; ivertex = m_sorted_vertices - .getNext(ivertex)) { - int vertex = m_sorted_vertices.getData(ivertex); - m_shape.getXY(vertex, pt); - if (pt.y != m_y_scanline && bunch.size() != 0) { - bFound |= processBunchForRingOrientationTest_(bunch); - m_sweep_comparator.reset(); - bunch.clear(false); - } - - bunch.add(vertex);// all vertices that have same y are added to the - // bunch - m_y_scanline = pt.y; - if (m_unknown_ring_orientation_count == 0) - break; - } - - if (m_unknown_ring_orientation_count > 0) { - bFound |= processBunchForRingOrientationTest_(bunch); - bunch.clear(false); - } - - m_shape.removeUserIndex(m_node_1_user_index); - m_shape.removeUserIndex(m_node_2_user_index); - - // dbg_verify_ring_orientation_();//debug - - for (int path = m_shape.getFirstPath(m_geometry); path != -1; ) { - if (m_shape.getPathUserIndex(path, m_path_orientation_index) == 3) {// exterior - m_shape.setExterior(path, true); - int afterPath = path; - for (int nextHole = m_shape.getPathUserIndex(path, - m_path_parentage_index); nextHole != -1; ) { - int p = m_shape.getPathUserIndex(nextHole, - m_path_parentage_index); - m_shape.movePath(m_geometry, - m_shape.getNextPath(afterPath), nextHole); - afterPath = nextHole; - nextHole = p; - } - path = m_shape.getNextPath(afterPath); - } else { - m_shape.setExterior(path, false); - path = m_shape.getNextPath(path); - } - } - - m_shape.removePathUserIndex(m_path_orientation_index); - m_shape.removePathUserIndex(m_path_parentage_index); - - return bFound; - } - - boolean processBunchForRingOrientationTest_(AttributeStreamOfInt32 bunch) { - return processBunchForRingOrientationTestOddEven_(bunch); - } - - boolean processBunchForRingOrientationTestOddEven_( - AttributeStreamOfInt32 bunch) { - boolean bModified = false; - if (m_edges == null) - m_edges = new Edges(m_shape); - - if (m_unknown_nodes == null) { - m_unknown_nodes = new AttributeStreamOfInt32(0); - m_unknown_nodes.reserve(16); - } else { - m_unknown_nodes.clear(false); - } - - processBunchForRingOrientationRemoveEdges_(bunch); - - // add edges that come into scope - for (int i = 0, n = bunch.size(); i < n; i++) { - int vertex = bunch.get(i); - if (vertex == -1) - continue; - insertEdge_(vertex, -1); - } - - for (int i = 0; i < m_unknown_nodes.size() - && m_unknown_ring_orientation_count > 0; i++) { - int aetNode = m_unknown_nodes.get(i); - int edge = m_AET.getElement(aetNode); - int path = m_edges.getPath(edge); - int orientation = m_shape.getPathUserIndex(path, - m_path_orientation_index); - int prevPath = -1; - if (orientation == 0) { - int node = m_AET.getPrev(aetNode); - int prevNode = aetNode; - boolean odd_even = false; - // find the leftmost edge for which the ring orientation is - // known - while (node != Treap.nullNode()) { - int edge1 = m_AET.getElement(node); - int path1 = m_edges.getPath(edge1); - int orientation1 = m_shape.getPathUserIndex(path1, - m_path_orientation_index); - if (orientation1 != 0) { - prevPath = path1; - break; - } - prevNode = node; - node = m_AET.getPrev(node); - } - if (node == Treap.nullNode()) {// if no edges have ring - // orientation known, then start - // from the left most and it has - // to be exterior ring. - odd_even = true; - node = prevNode; - } else { - int edge1 = m_AET.getElement(node); - odd_even = m_edges.isBottomUp(edge1); - node = m_AET.getNext(node); - odd_even = !odd_even; - } - - do { - int edge1 = m_AET.getElement(node); - int path1 = m_edges.getPath(edge1); - int orientation1 = m_shape.getPathUserIndex(path1, - m_path_orientation_index); - if (orientation1 == 0) { - if (odd_even != m_edges.isBottomUp(edge1)) { - int first = m_shape.getFirstVertex(path1); - m_shape.reverseRingInternal_(first); - m_shape.setLastVertex_(path1, - m_shape.getPrevVertex(first)); - bModified = true; - } - - m_shape.setPathUserIndex(path1, - m_path_orientation_index, odd_even ? 3 : 2); - if (!odd_even) {// link the holes into the linked list - // to mantain the OGC order. - int lastHole = m_shape.getPathUserIndex(prevPath, - m_path_parentage_index); - m_shape.setPathUserIndex(prevPath, - m_path_parentage_index, path1); - m_shape.setPathUserIndex(path1, - m_path_parentage_index, lastHole); - } - - m_unknown_ring_orientation_count--; - if (m_unknown_ring_orientation_count == 0) - return bModified; - } - - prevPath = path1; - prevNode = node; - node = m_AET.getNext(node); - odd_even = !odd_even; - } while (prevNode != aetNode); - } - } - - return bModified; - } - - void processBunchForRingOrientationRemoveEdges_(AttributeStreamOfInt32 bunch) { - // remove all nodes that go out of scope - for (int i = 0, n = bunch.size(); i < n; i++) { - int vertex = bunch.get(i); - int node1 = m_shape.getUserIndex(vertex, m_node_1_user_index); - int node2 = m_shape.getUserIndex(vertex, m_node_2_user_index); - if (node1 != -1) { - int edge = m_AET.getElement(node1); - m_edges.freeEdge(edge); - m_shape.setUserIndex(vertex, m_node_1_user_index, -1); - } - if (node2 != -1) { - int edge = m_AET.getElement(node2); - m_edges.freeEdge(edge); - m_shape.setUserIndex(vertex, m_node_2_user_index, -1); - } - - int reused_node = -1; - if (node1 != -1 && node2 != -1) {// terminating vertex - m_AET.deleteNode(node1, -1); - m_AET.deleteNode(node2, -1); - bunch.set(i, -1); - } else - reused_node = node1 != -1 ? node1 : node2; - - if (reused_node != -1) {// this vertex is a part of vertical chain. - // Sorted order in AET did not change, so - // reuse the AET node. - if (!insertEdge_(vertex, reused_node)) - m_AET.deleteNode(reused_node, -1);// horizontal edge was not - // inserted - bunch.set(i, -1); - } - } - } - - boolean insertEdge_(int vertex, int reused_node) { - Point2D pt_1 = new Point2D(); - Point2D pt_2 = new Point2D(); - m_shape.getXY(vertex, pt_1); - int next = m_shape.getNextVertex(vertex); - m_shape.getXY(next, pt_2); - boolean b_res = false; - if (pt_1.y < pt_2.y) { - b_res = true; - int edge = m_edges.newEdge(vertex); - int aetNode; - if (reused_node == -1) - aetNode = m_AET.addElement(edge, -1); - else { - aetNode = reused_node; - m_AET.setElement(aetNode, edge); - } - int node = m_shape.getUserIndex(next, m_node_1_user_index); - if (node == -1) - m_shape.setUserIndex(next, m_node_1_user_index, aetNode); - else - m_shape.setUserIndex(next, m_node_2_user_index, aetNode); - - int path = m_shape.getPathFromVertex(vertex); - if (m_shape.getPathUserIndex(path, m_path_orientation_index) == 0) { - m_unknown_nodes.add(aetNode); - } - } - - int prev = m_shape.getPrevVertex(vertex); - m_shape.getXY(prev, pt_2); - if (pt_1.y < pt_2.y) { - b_res = true; - int edge = m_edges.newEdge(prev); - int aetNode; - if (reused_node == -1) - aetNode = m_AET.addElement(edge, -1); - else { - aetNode = reused_node; - m_AET.setElement(aetNode, edge); - } - int node = m_shape.getUserIndex(prev, m_node_1_user_index); - if (node == -1) - m_shape.setUserIndex(prev, m_node_1_user_index, aetNode); - else - m_shape.setUserIndex(prev, m_node_2_user_index, aetNode); - - int path = m_shape.getPathFromVertex(vertex); - if (m_shape.getPathUserIndex(path, m_path_orientation_index) == 0) { - m_unknown_nodes.add(aetNode); - } - } - - return b_res; - } - - static boolean execute(EditShape shape, int geometry, - IndexMultiDCList sorted_vertices, boolean fixSelfTangency) { - RingOrientationFixer fixer = new RingOrientationFixer(); - fixer.m_shape = shape; - fixer.m_geometry = geometry; - fixer.m_sorted_vertices = sorted_vertices; - fixer.m_fixSelfTangency = fixSelfTangency; - return fixer.fixRingOrientation_(); - } - - boolean fixRingSelfTangency_() { - AttributeStreamOfInt32 self_tangent_paths = new AttributeStreamOfInt32( - 0); - AttributeStreamOfInt32 self_tangency_clusters = new AttributeStreamOfInt32( - 0); - int tangent_path_first_vertex_index = -1; - int tangent_vertex_cluster_index = -1; - Point2D pt_prev = new Point2D(); - pt_prev.setNaN(); - int prev_vertex = -1; - int old_path = -1; - int current_cluster = -1; - Point2D pt = new Point2D(); - for (int ivertex = m_sorted_vertices.getFirst(m_sorted_vertices - .getFirstList()); ivertex != -1; ivertex = m_sorted_vertices - .getNext(ivertex)) { - int vertex = m_sorted_vertices.getData(ivertex); - m_shape.getXY(vertex, pt); - int path = m_shape.getPathFromVertex(vertex); - if (pt_prev.isEqual(pt) && old_path == path) { - if (tangent_vertex_cluster_index == -1) { - tangent_path_first_vertex_index = m_shape - .createPathUserIndex(); - tangent_vertex_cluster_index = m_shape.createUserIndex(); - } - - if (current_cluster == -1) { - current_cluster = self_tangency_clusters.size(); - m_shape.setUserIndex(prev_vertex, - tangent_vertex_cluster_index, current_cluster); - self_tangency_clusters.add(1); - int p = m_shape.getPathUserIndex(path, - tangent_path_first_vertex_index); - if (p == -1) { - m_shape.setPathUserIndex(path, - tangent_path_first_vertex_index, prev_vertex); - self_tangent_paths.add(path); - } - } - - m_shape.setUserIndex(vertex, tangent_vertex_cluster_index, - current_cluster); - self_tangency_clusters - .setLast(self_tangency_clusters.getLast() + 1); - } else { - current_cluster = -1; - pt_prev.setCoords(pt); - } - - prev_vertex = vertex; - old_path = path; - } - - if (self_tangent_paths.size() == 0) - return false; - - // Now self_tangent_paths contains list of clusters of tangency for each - // path. - // The clusters contains list of clusters and for each cluster it - // contains a list of vertices. - AttributeStreamOfInt32 vertex_stack = new AttributeStreamOfInt32(0); - AttributeStreamOfInt32 cluster_stack = new AttributeStreamOfInt32(0); - - for (int ipath = 0, npath = self_tangent_paths.size(); ipath < npath; ipath++) { - int path = self_tangent_paths.get(ipath); - int first_vertex = m_shape.getPathUserIndex(path, - tangent_path_first_vertex_index); - int cluster = m_shape.getUserIndex(first_vertex, - tangent_vertex_cluster_index); - vertex_stack.clear(false); - cluster_stack.clear(false); - vertex_stack.add(first_vertex); - cluster_stack.add(cluster); - - for (int vertex = m_shape.getNextVertex(first_vertex); vertex != first_vertex; vertex = m_shape - .getNextVertex(vertex)) { - int vertex_to = vertex; - int cluster_to = m_shape.getUserIndex(vertex_to, - tangent_vertex_cluster_index); - if (cluster_to != -1) { - if (cluster_stack.size() == 0) { - cluster_stack.add(cluster_to); - vertex_stack.add(vertex_to); - continue; - } - - if (cluster_stack.getLast() == cluster_to) { - int vertex_from = vertex_stack.getLast(); - - // peel the loop from path - int from_next = m_shape.getNextVertex(vertex_from); - int from_prev = m_shape.getPrevVertex(vertex_from); - int to_next = m_shape.getNextVertex(vertex_to); - int to_prev = m_shape.getPrevVertex(vertex_to); - - m_shape.setNextVertex_(vertex_from, to_next); - m_shape.setPrevVertex_(to_next, vertex_from); - - m_shape.setNextVertex_(vertex_to, from_next); - m_shape.setPrevVertex_(from_next, vertex_to); - - // vertex_from is left in the path we are processing, - // while the vertex_to is in the loop being teared off. - boolean[] first_vertex_correction_requied = new boolean[]{false}; - int new_path = m_shape.insertClosedPath_(m_geometry, - -1, from_next, m_shape.getFirstVertex(path), - first_vertex_correction_requied); - - m_shape.setUserIndex(vertex, - tangent_vertex_cluster_index, -1); - - // Fix the path after peeling if the peeled loop had the - // first path vertex in it - - if (first_vertex_correction_requied[0]) { - m_shape.setFirstVertex_(path, to_next); - } - - int path_size = m_shape.getPathSize(path); - int new_path_size = m_shape.getPathSize(new_path); - path_size -= new_path_size; - assert (path_size >= 3); - m_shape.setPathSize_(path, path_size); - - self_tangency_clusters.set(cluster_to, - self_tangency_clusters.get(cluster_to) - 1); - if (self_tangency_clusters.get(cluster_to) == 1) { - self_tangency_clusters.set(cluster_to, 0); - cluster_stack.removeLast(); - vertex_stack.removeLast(); - } else { - // this cluster has more than two vertices in it. - } - - first_vertex = vertex_from;// reset the counter to - // ensure we find all loops. - vertex = vertex_from; - } else { - vertex_stack.add(vertex); - cluster_stack.add(cluster_to); - } - } - } - } - - m_shape.removePathUserIndex(tangent_path_first_vertex_index); - m_shape.removeUserIndex(tangent_vertex_cluster_index); - return true; - } + EditShape m_shape; + Treap m_AET; + double m_y_scanline; + int m_geometry; + int m_unknown_ring_orientation_count; + IndexMultiDCList m_sorted_vertices; + AttributeStreamOfInt32 m_unknown_nodes; + int m_node_1_user_index; + int m_node_2_user_index; + int m_path_orientation_index; + int m_path_parentage_index; + boolean m_fixSelfTangency; + + static final class Edges { + EditShape m_shape; + AttributeStreamOfInt32 m_end_1_nodes; + AttributeStreamOfInt32 m_end_2_nodes; + AttributeStreamOfInt8 m_directions; + Point2D pt_1 = new Point2D(); + Point2D pt_2 = new Point2D(); + int m_first_free; + + boolean getDirection_(int index) { + return m_shape.getNextVertex(getEnd1(index)) == getEnd2(index); + } + + int getEnd_(int index) { + int v_1 = getEnd1(index); + int v_2 = getEnd2(index); + if (m_shape.getNextVertex(v_1) == v_2) + return v_2; + else + return v_1; + } + + Edges(EditShape shape) { + m_shape = shape; + m_first_free = -1; + } + + Segment getSegment(int index) { + return m_shape.getSegment(getStart(index)); + } + + // True if the start vertex is the lower point of the edge. + boolean isBottomUp(int index) { + int v_1 = getEnd1(index); + int v_2 = getEnd2(index); + if (m_shape.getPrevVertex(v_1) == v_2) { + int temp = v_1; + v_1 = v_2; + v_2 = temp; + } + m_shape.getXY(v_1, pt_1); + m_shape.getXY(v_2, pt_2); + return pt_1.y < pt_2.y; + } + + int getStart(int index) { + int v_1 = getEnd1(index); + int v_2 = getEnd2(index); + return (m_shape.getNextVertex(v_1) == v_2) ? v_1 : v_2; + } + + int getEnd1(int index) { + return m_end_1_nodes.get(index); + } + + int getEnd2(int index) { + return m_end_2_nodes.get(index); + } + + void freeEdge(int edge) { + m_end_1_nodes.set(edge, m_first_free); + m_first_free = edge; + } + + int newEdge(int vertex) { + if (m_first_free != -1) { + int index = m_first_free; + m_first_free = m_end_1_nodes.get(index); + m_end_1_nodes.set(index, vertex); + m_end_2_nodes.set(index, m_shape.getNextVertex(vertex)); + return index; + } else if (m_end_1_nodes == null) { + m_end_1_nodes = new AttributeStreamOfInt32(0); + m_end_2_nodes = new AttributeStreamOfInt32(0); + } + + int index = m_end_1_nodes.size(); + m_end_1_nodes.add(vertex); + m_end_2_nodes.add(m_shape.getNextVertex(vertex)); + return index; + } + + EditShape getShape() { + return m_shape; + } + + int getPath(int index) { + return m_shape.getPathFromVertex(getEnd1(index)); + } + } + + Edges m_edges; + + class RingOrientationTestComparator extends Treap.Comparator { + RingOrientationFixer m_helper; + Line m_line_1; + Line m_line_2; + int m_left_elm; + double m_leftx; + Segment m_seg_1; + + RingOrientationTestComparator(RingOrientationFixer helper) { + m_helper = helper; + m_line_1 = new Line(); + m_line_2 = new Line(); + m_leftx = 0; + m_seg_1 = null; + m_left_elm = -1; + } + + @Override + int compare(Treap treap, int left, int node) { + int right = treap.getElement(node); + RingOrientationFixer.Edges edges = m_helper.m_edges; + double x_1; + if (m_left_elm == left) + x_1 = m_leftx; + else { + m_seg_1 = edges.getSegment(left); + if (m_seg_1 == null) { + EditShape shape = edges.getShape(); + shape.queryLineConnector(edges.getStart(left), m_line_1); + m_seg_1 = m_line_1; + x_1 = m_line_1.intersectionOfYMonotonicWithAxisX( + m_helper.m_y_scanline, 0); + } else + x_1 = m_seg_1.intersectionOfYMonotonicWithAxisX( + m_helper.m_y_scanline, 0); + + m_leftx = x_1; + m_left_elm = left; + } + + Segment seg_2 = edges.getSegment(right); + double x2; + if (seg_2 == null) { + EditShape shape = edges.getShape(); + shape.queryLineConnector(edges.getStart(right), m_line_2); + seg_2 = m_line_2; + x2 = m_line_2.intersectionOfYMonotonicWithAxisX( + m_helper.m_y_scanline, 0); + } else + x2 = seg_2.intersectionOfYMonotonicWithAxisX( + m_helper.m_y_scanline, 0); + + if (x_1 == x2) { + boolean bStartLower1 = edges.isBottomUp(left); + boolean bStartLower2 = edges.isBottomUp(right); + + // apparently these edges originate from same vertex and the + // scanline is on the vertex. move scanline a little. + double y1 = !bStartLower1 ? m_seg_1.getStartY() : m_seg_1 + .getEndY(); + double y2 = !bStartLower2 ? seg_2.getStartY() : seg_2.getEndY(); + double miny = Math.min(y1, y2); + double y = (miny + m_helper.m_y_scanline) * 0.5; + if (y == m_helper.m_y_scanline) { + // assert(0);//ST: not a bug. just curious to see this + // happens. + y = miny; // apparently, one of the segments is almost + // horizontal line. + } + x_1 = m_seg_1.intersectionOfYMonotonicWithAxisX(y, 0); + x2 = seg_2.intersectionOfYMonotonicWithAxisX(y, 0); + assert (x_1 != x2); + } + + return x_1 < x2 ? -1 : (x_1 > x2 ? 1 : 0); + } + + void reset() { + m_left_elm = -1; + } + } + + RingOrientationTestComparator m_sweep_comparator; + + RingOrientationFixer() { + m_AET = new Treap(); + m_AET.disableBalancing(); + m_sweep_comparator = new RingOrientationTestComparator(this); + m_AET.setComparator(m_sweep_comparator); + } + + boolean fixRingOrientation_() { + boolean bFound = false; + + if (m_fixSelfTangency) + bFound = fixRingSelfTangency_(); + + if (m_shape.getPathCount(m_geometry) == 1) { + int path = m_shape.getFirstPath(m_geometry); + double area = m_shape.getRingArea(path); + m_shape.setExterior(path, true); + if (area < 0) { + int first = m_shape.getFirstVertex(path); + m_shape.reverseRingInternal_(first); + m_shape.setLastVertex_(path, m_shape.getPrevVertex(first));// fix + // last + // after + // the + // reverse + return true; + } + + return false; + } + + m_path_orientation_index = m_shape.createPathUserIndex();// used to + // store + // discovered + // orientation + // (3 - + // extrior, + // 2 - + // interior) + m_path_parentage_index = m_shape.createPathUserIndex();// used to + // resolve OGC + // order + for (int path = m_shape.getFirstPath(m_geometry); path != -1; path = m_shape + .getNextPath(path)) { + m_shape.setPathUserIndex(path, m_path_orientation_index, 0); + m_shape.setPathUserIndex(path, m_path_parentage_index, -1); + } + + AttributeStreamOfInt32 bunch = new AttributeStreamOfInt32(0); + m_y_scanline = NumberUtils.TheNaN; + Point2D pt = new Point2D(); + m_unknown_ring_orientation_count = m_shape.getPathCount(m_geometry); + m_node_1_user_index = m_shape.createUserIndex(); + m_node_2_user_index = m_shape.createUserIndex(); + for (int ivertex = m_sorted_vertices.getFirst(m_sorted_vertices + .getFirstList()); ivertex != -1; ivertex = m_sorted_vertices + .getNext(ivertex)) { + int vertex = m_sorted_vertices.getData(ivertex); + m_shape.getXY(vertex, pt); + if (pt.y != m_y_scanline && bunch.size() != 0) { + bFound |= processBunchForRingOrientationTest_(bunch); + m_sweep_comparator.reset(); + bunch.clear(false); + } + + bunch.add(vertex);// all vertices that have same y are added to the + // bunch + m_y_scanline = pt.y; + if (m_unknown_ring_orientation_count == 0) + break; + } + + if (m_unknown_ring_orientation_count > 0) { + bFound |= processBunchForRingOrientationTest_(bunch); + bunch.clear(false); + } + + m_shape.removeUserIndex(m_node_1_user_index); + m_shape.removeUserIndex(m_node_2_user_index); + + // dbg_verify_ring_orientation_();//debug + + for (int path = m_shape.getFirstPath(m_geometry); path != -1; ) { + if (m_shape.getPathUserIndex(path, m_path_orientation_index) == 3) {// exterior + m_shape.setExterior(path, true); + int afterPath = path; + for (int nextHole = m_shape.getPathUserIndex(path, + m_path_parentage_index); nextHole != -1; ) { + int p = m_shape.getPathUserIndex(nextHole, + m_path_parentage_index); + m_shape.movePath(m_geometry, + m_shape.getNextPath(afterPath), nextHole); + afterPath = nextHole; + nextHole = p; + } + path = m_shape.getNextPath(afterPath); + } else { + m_shape.setExterior(path, false); + path = m_shape.getNextPath(path); + } + } + + m_shape.removePathUserIndex(m_path_orientation_index); + m_shape.removePathUserIndex(m_path_parentage_index); + + return bFound; + } + + boolean processBunchForRingOrientationTest_(AttributeStreamOfInt32 bunch) { + return processBunchForRingOrientationTestOddEven_(bunch); + } + + boolean processBunchForRingOrientationTestOddEven_( + AttributeStreamOfInt32 bunch) { + boolean bModified = false; + if (m_edges == null) + m_edges = new Edges(m_shape); + + if (m_unknown_nodes == null) { + m_unknown_nodes = new AttributeStreamOfInt32(0); + m_unknown_nodes.reserve(16); + } else { + m_unknown_nodes.clear(false); + } + + processBunchForRingOrientationRemoveEdges_(bunch); + + // add edges that come into scope + for (int i = 0, n = bunch.size(); i < n; i++) { + int vertex = bunch.get(i); + if (vertex == -1) + continue; + insertEdge_(vertex, -1); + } + + for (int i = 0; i < m_unknown_nodes.size() + && m_unknown_ring_orientation_count > 0; i++) { + int aetNode = m_unknown_nodes.get(i); + int edge = m_AET.getElement(aetNode); + int path = m_edges.getPath(edge); + int orientation = m_shape.getPathUserIndex(path, + m_path_orientation_index); + int prevPath = -1; + if (orientation == 0) { + int node = m_AET.getPrev(aetNode); + int prevNode = aetNode; + boolean odd_even = false; + // find the leftmost edge for which the ring orientation is + // known + while (node != Treap.nullNode()) { + int edge1 = m_AET.getElement(node); + int path1 = m_edges.getPath(edge1); + int orientation1 = m_shape.getPathUserIndex(path1, + m_path_orientation_index); + if (orientation1 != 0) { + prevPath = path1; + break; + } + prevNode = node; + node = m_AET.getPrev(node); + } + if (node == Treap.nullNode()) {// if no edges have ring + // orientation known, then start + // from the left most and it has + // to be exterior ring. + odd_even = true; + node = prevNode; + } else { + int edge1 = m_AET.getElement(node); + odd_even = m_edges.isBottomUp(edge1); + node = m_AET.getNext(node); + odd_even = !odd_even; + } + + do { + int edge1 = m_AET.getElement(node); + int path1 = m_edges.getPath(edge1); + int orientation1 = m_shape.getPathUserIndex(path1, + m_path_orientation_index); + if (orientation1 == 0) { + if (odd_even != m_edges.isBottomUp(edge1)) { + int first = m_shape.getFirstVertex(path1); + m_shape.reverseRingInternal_(first); + m_shape.setLastVertex_(path1, + m_shape.getPrevVertex(first)); + bModified = true; + } + + m_shape.setPathUserIndex(path1, + m_path_orientation_index, odd_even ? 3 : 2); + if (!odd_even) {// link the holes into the linked list + // to mantain the OGC order. + int lastHole = m_shape.getPathUserIndex(prevPath, + m_path_parentage_index); + m_shape.setPathUserIndex(prevPath, + m_path_parentage_index, path1); + m_shape.setPathUserIndex(path1, + m_path_parentage_index, lastHole); + } + + m_unknown_ring_orientation_count--; + if (m_unknown_ring_orientation_count == 0) + return bModified; + } + + prevPath = path1; + prevNode = node; + node = m_AET.getNext(node); + odd_even = !odd_even; + } while (prevNode != aetNode); + } + } + + return bModified; + } + + void processBunchForRingOrientationRemoveEdges_(AttributeStreamOfInt32 bunch) { + // remove all nodes that go out of scope + for (int i = 0, n = bunch.size(); i < n; i++) { + int vertex = bunch.get(i); + int node1 = m_shape.getUserIndex(vertex, m_node_1_user_index); + int node2 = m_shape.getUserIndex(vertex, m_node_2_user_index); + if (node1 != -1) { + int edge = m_AET.getElement(node1); + m_edges.freeEdge(edge); + m_shape.setUserIndex(vertex, m_node_1_user_index, -1); + } + if (node2 != -1) { + int edge = m_AET.getElement(node2); + m_edges.freeEdge(edge); + m_shape.setUserIndex(vertex, m_node_2_user_index, -1); + } + + int reused_node = -1; + if (node1 != -1 && node2 != -1) {// terminating vertex + m_AET.deleteNode(node1, -1); + m_AET.deleteNode(node2, -1); + bunch.set(i, -1); + } else + reused_node = node1 != -1 ? node1 : node2; + + if (reused_node != -1) {// this vertex is a part of vertical chain. + // Sorted order in AET did not change, so + // reuse the AET node. + if (!insertEdge_(vertex, reused_node)) + m_AET.deleteNode(reused_node, -1);// horizontal edge was not + // inserted + bunch.set(i, -1); + } + } + } + + boolean insertEdge_(int vertex, int reused_node) { + Point2D pt_1 = new Point2D(); + Point2D pt_2 = new Point2D(); + m_shape.getXY(vertex, pt_1); + int next = m_shape.getNextVertex(vertex); + m_shape.getXY(next, pt_2); + boolean b_res = false; + if (pt_1.y < pt_2.y) { + b_res = true; + int edge = m_edges.newEdge(vertex); + int aetNode; + if (reused_node == -1) + aetNode = m_AET.addElement(edge, -1); + else { + aetNode = reused_node; + m_AET.setElement(aetNode, edge); + } + int node = m_shape.getUserIndex(next, m_node_1_user_index); + if (node == -1) + m_shape.setUserIndex(next, m_node_1_user_index, aetNode); + else + m_shape.setUserIndex(next, m_node_2_user_index, aetNode); + + int path = m_shape.getPathFromVertex(vertex); + if (m_shape.getPathUserIndex(path, m_path_orientation_index) == 0) { + m_unknown_nodes.add(aetNode); + } + } + + int prev = m_shape.getPrevVertex(vertex); + m_shape.getXY(prev, pt_2); + if (pt_1.y < pt_2.y) { + b_res = true; + int edge = m_edges.newEdge(prev); + int aetNode; + if (reused_node == -1) + aetNode = m_AET.addElement(edge, -1); + else { + aetNode = reused_node; + m_AET.setElement(aetNode, edge); + } + int node = m_shape.getUserIndex(prev, m_node_1_user_index); + if (node == -1) + m_shape.setUserIndex(prev, m_node_1_user_index, aetNode); + else + m_shape.setUserIndex(prev, m_node_2_user_index, aetNode); + + int path = m_shape.getPathFromVertex(vertex); + if (m_shape.getPathUserIndex(path, m_path_orientation_index) == 0) { + m_unknown_nodes.add(aetNode); + } + } + + return b_res; + } + + static boolean execute(EditShape shape, int geometry, + IndexMultiDCList sorted_vertices, boolean fixSelfTangency) { + RingOrientationFixer fixer = new RingOrientationFixer(); + fixer.m_shape = shape; + fixer.m_geometry = geometry; + fixer.m_sorted_vertices = sorted_vertices; + fixer.m_fixSelfTangency = fixSelfTangency; + return fixer.fixRingOrientation_(); + } + + boolean fixRingSelfTangency_() { + AttributeStreamOfInt32 self_tangent_paths = new AttributeStreamOfInt32( + 0); + AttributeStreamOfInt32 self_tangency_clusters = new AttributeStreamOfInt32( + 0); + int tangent_path_first_vertex_index = -1; + int tangent_vertex_cluster_index = -1; + Point2D pt_prev = new Point2D(); + pt_prev.setNaN(); + int prev_vertex = -1; + int old_path = -1; + int current_cluster = -1; + Point2D pt = new Point2D(); + for (int ivertex = m_sorted_vertices.getFirst(m_sorted_vertices + .getFirstList()); ivertex != -1; ivertex = m_sorted_vertices + .getNext(ivertex)) { + int vertex = m_sorted_vertices.getData(ivertex); + m_shape.getXY(vertex, pt); + int path = m_shape.getPathFromVertex(vertex); + if (pt_prev.isEqual(pt) && old_path == path) { + if (tangent_vertex_cluster_index == -1) { + tangent_path_first_vertex_index = m_shape + .createPathUserIndex(); + tangent_vertex_cluster_index = m_shape.createUserIndex(); + } + + if (current_cluster == -1) { + current_cluster = self_tangency_clusters.size(); + m_shape.setUserIndex(prev_vertex, + tangent_vertex_cluster_index, current_cluster); + self_tangency_clusters.add(1); + int p = m_shape.getPathUserIndex(path, + tangent_path_first_vertex_index); + if (p == -1) { + m_shape.setPathUserIndex(path, + tangent_path_first_vertex_index, prev_vertex); + self_tangent_paths.add(path); + } + } + + m_shape.setUserIndex(vertex, tangent_vertex_cluster_index, + current_cluster); + self_tangency_clusters + .setLast(self_tangency_clusters.getLast() + 1); + } else { + current_cluster = -1; + pt_prev.setCoords(pt); + } + + prev_vertex = vertex; + old_path = path; + } + + if (self_tangent_paths.size() == 0) + return false; + + // Now self_tangent_paths contains list of clusters of tangency for each + // path. + // The clusters contains list of clusters and for each cluster it + // contains a list of vertices. + AttributeStreamOfInt32 vertex_stack = new AttributeStreamOfInt32(0); + AttributeStreamOfInt32 cluster_stack = new AttributeStreamOfInt32(0); + + for (int ipath = 0, npath = self_tangent_paths.size(); ipath < npath; ipath++) { + int path = self_tangent_paths.get(ipath); + int first_vertex = m_shape.getPathUserIndex(path, + tangent_path_first_vertex_index); + int cluster = m_shape.getUserIndex(first_vertex, + tangent_vertex_cluster_index); + vertex_stack.clear(false); + cluster_stack.clear(false); + vertex_stack.add(first_vertex); + cluster_stack.add(cluster); + + for (int vertex = m_shape.getNextVertex(first_vertex); vertex != first_vertex; vertex = m_shape + .getNextVertex(vertex)) { + int vertex_to = vertex; + int cluster_to = m_shape.getUserIndex(vertex_to, + tangent_vertex_cluster_index); + if (cluster_to != -1) { + if (cluster_stack.size() == 0) { + cluster_stack.add(cluster_to); + vertex_stack.add(vertex_to); + continue; + } + + if (cluster_stack.getLast() == cluster_to) { + int vertex_from = vertex_stack.getLast(); + + // peel the loop from path + int from_next = m_shape.getNextVertex(vertex_from); + int from_prev = m_shape.getPrevVertex(vertex_from); + int to_next = m_shape.getNextVertex(vertex_to); + int to_prev = m_shape.getPrevVertex(vertex_to); + + m_shape.setNextVertex_(vertex_from, to_next); + m_shape.setPrevVertex_(to_next, vertex_from); + + m_shape.setNextVertex_(vertex_to, from_next); + m_shape.setPrevVertex_(from_next, vertex_to); + + // vertex_from is left in the path we are processing, + // while the vertex_to is in the loop being teared off. + boolean[] first_vertex_correction_requied = new boolean[]{false}; + int new_path = m_shape.insertClosedPath_(m_geometry, + -1, from_next, m_shape.getFirstVertex(path), + first_vertex_correction_requied); + + m_shape.setUserIndex(vertex, + tangent_vertex_cluster_index, -1); + + // Fix the path after peeling if the peeled loop had the + // first path vertex in it + + if (first_vertex_correction_requied[0]) { + m_shape.setFirstVertex_(path, to_next); + } + + int path_size = m_shape.getPathSize(path); + int new_path_size = m_shape.getPathSize(new_path); + path_size -= new_path_size; + assert (path_size >= 3); + m_shape.setPathSize_(path, path_size); + + self_tangency_clusters.set(cluster_to, + self_tangency_clusters.get(cluster_to) - 1); + if (self_tangency_clusters.get(cluster_to) == 1) { + self_tangency_clusters.set(cluster_to, 0); + cluster_stack.removeLast(); + vertex_stack.removeLast(); + } else { + // this cluster has more than two vertices in it. + } + + first_vertex = vertex_from;// reset the counter to + // ensure we find all loops. + vertex = vertex_from; + } else { + vertex_stack.add(vertex); + cluster_stack.add(cluster_to); + } + } + } + } + + m_shape.removePathUserIndex(tangent_path_first_vertex_index); + m_shape.removeUserIndex(tangent_vertex_cluster_index); + return true; + } } diff --git a/src/main/java/com/esri/core/geometry/Segment.java b/src/main/java/com/esri/core/geometry/Segment.java index 9d21eb65..be987933 100644 --- a/src/main/java/com/esri/core/geometry/Segment.java +++ b/src/main/java/com/esri/core/geometry/Segment.java @@ -32,911 +32,911 @@ * A base class for segments. Presently only Line segments are supported. */ public abstract class Segment extends Geometry implements Serializable { - double m_xStart; - - double m_yStart; - - double m_xEnd; - - double m_yEnd; - - double[] m_attributes; - - // Header Definitions - - /** - * Returns XY coordinates of the start point. - */ - public Point2D getStartXY() { - return Point2D.construct(m_xStart, m_yStart); - } - - public void getStartXY(Point2D pt) { - pt.x = m_xStart; - pt.y = m_yStart; - } - - /** - * Sets the XY coordinates of the start point. - */ - public void setStartXY(Point2D pt) { - _setXY(0, pt); - } - - public void setStartXY(double x, double y) { - _setXY(0, Point2D.construct(x, y)); - } - - /** - * Returns XYZ coordinates of the start point. Z if 0 if Z is missing. - */ - public Point3D getStartXYZ() { - return _getXYZ(0); - } - - /** - * Sets the XYZ coordinates of the start point. - */ - public void setStartXYZ(Point3D pt) { - _setXYZ(0, pt); - } - - public void setStartXYZ(double x, double y, double z) { - _setXYZ(0, Point3D.construct(x, y, z)); - } - - /** - * Returns coordinates of the start point in a Point class. - */ - public void queryStart(Point dstPoint) { - _get(0, dstPoint); - } - - /** - * Sets the coordinates of the start point in this segment. - * - * @param srcPoint The new start point of this segment. - */ - public void setStart(Point srcPoint) { - _set(0, srcPoint); - } - - /** - * Returns value of the start vertex attribute's ordinate. Throws if the - * Point is empty. - * - * @param semantics The attribute semantics. - * @param ordinate The attribute's ordinate. For example, the y coordinate of the - * NORMAL has ordinate of 1. - * @return Ordinate value as double. - */ - public double getStartAttributeAsDbl(int semantics, int ordinate) { - return _getAttributeAsDbl(0, semantics, ordinate); - } - - /** - * Returns the value of the start vertex attribute's ordinate. The ordinate - * is always 0 because integer attributes always have one component. - * - * @param semantics The attribute semantics. - * @param ordinate The attribute's ordinate. For example, the y coordinate of the - * NORMAL has ordinate of 1. - * @return Ordinate value truncated to 32 bit integer. - */ - public int getStartAttributeAsInt(int semantics, int ordinate) { - return _getAttributeAsInt(0, semantics, ordinate); - } - - /** - * Sets the value of the start vertex attribute. - * - * @param semantics The attribute semantics. - * @param value is the array to write values to. The attribute type and the - * number of elements must match the persistence type, as well as - * the number of components of the attribute. - */ - public void setStartAttribute(int semantics, int ordinate, double value) { - _setAttribute(0, semantics, ordinate, value); - } - - public void setStartAttribute(int semantics, int ordinate, int value) { - _setAttribute(0, semantics, ordinate, value); - } - - /** - * Returns the X coordinate of starting point. - * - * @return The X coordinate of starting point. - */ - public double getStartX() { - return m_xStart; - } - - /** - * Returns the Y coordinate of starting point. - * - * @return The Y coordinate of starting point. - */ - public double getStartY() { - return m_yStart; - } - - /** - * Returns the X coordinate of ending point. - * - * @return The X coordinate of ending point. - */ - public double getEndX() { - return m_xEnd; - } - - /** - * Returns the Y coordinate of ending point. - * - * @return The Y coordinate of ending point. - */ - public double getEndY() { - return m_yEnd; - } - - /** - * Returns XY coordinates of the end point. - * - * @return The XY coordinates of the end point. - */ - public Point2D getEndXY() { - return Point2D.construct(m_xEnd, m_yEnd); - } - - public void getEndXY(Point2D pt) { - pt.x = m_xEnd; - pt.y = m_yEnd; - } - - /** - * Sets the XY coordinates of the end point. - * - * @param pt The end point of the segment. - */ - public void setEndXY(Point2D pt) { - _setXY(1, pt); - } - - public void setEndXY(double x, double y) { - _setXY(1, Point2D.construct(x, y)); - } - - /** - * Returns XYZ coordinates of the end point. Z if 0 if Z is missing. - * - * @return The XYZ coordinates of the end point. - */ - public Point3D getEndXYZ() { - return _getXYZ(1); - } - - /** - * Sets the XYZ coordinates of the end point. - */ - public void setEndXYZ(Point3D pt) { - _setXYZ(1, pt); - } - - public void setEndXYZ(double x, double y, double z) { - _setXYZ(1, Point3D.construct(x, y, z)); - } - - /** - * Returns coordinates of the end point in this segment. - * - * @param dstPoint The end point of this segment. - */ - public void queryEnd(Point dstPoint) { - _get(1, dstPoint); - } - - /** - * Sets the coordinates of the end point in a Point class. - * - * @param srcPoint The new end point of this segment. - */ - public void setEnd(Point srcPoint) { - _set(1, srcPoint); - } - - /** - * Returns value of the end vertex attribute's ordinate. Throws if the Point - * is empty. - * - * @param semantics The attribute semantics. - * @param ordinate The attribute's ordinate. For example, the y coordinate of the - * NORMAL has ordinate of 1. - * @return Ordinate value as double. - */ - public double getEndAttributeAsDbl(int semantics, int ordinate) { - return _getAttributeAsDbl(1, semantics, ordinate); - } - - /** - * Returns the value of the end vertex attribute's ordinate. The ordinate is - * always 0 because integer attributes always have one component. - * - * @param semantics The attribute semantics. - * @param ordinate The attribute's ordinate. For example, the y coordinate of the - * NORMAL has ordinate of 1. - * @return The ordinate value truncated to 32 bit integer. - */ - public int getEndAttributeAsInt(int semantics, int ordinate) { - return _getAttributeAsInt(1, semantics, ordinate); - } - - /** - * Sets the value of end vertex attribute. - * - * @param semantics The attribute semantics. - * @param ordinate The attribute's ordinate. - * @param value Is the array to write values to. The attribute type and the - * number of elements must match the persistence type, as well as - * the number of components of the attribute. - */ - public void setEndAttribute(int semantics, int ordinate, double value) { - _setAttribute(1, semantics, ordinate, value); - } - - public void setEndAttribute(int semantics, int ordinate, int value) { - _setAttribute(1, semantics, ordinate, value); - } - - @Override - public final int getDimension() { - return 1; - } - - @Override - public final boolean isEmpty() { - return isEmptyImpl(); - } - - @Override - public final void setEmpty() { - - } - - @Override - public double calculateArea2D() { - return 0; - } - - /** - * Calculates intersections of this segment with another segment. - *

- * Note: This is not a topological operation. It needs to be paired with the - * Segment.Overlap call. - * - * @param other The segment to calculate intersection with. - * @param intersectionPoints The intersection points. Can be NULL. - * @param paramThis The value of the parameter in the intersection points for this - * Segment (between 0 and 1). Can be NULL. - * @param paramOther The value of the parameter in the intersection points for the - * other Segment (between 0 and 1). Can be NULL. - * @param tolerance The tolerance value for the intersection calculation. Can be - * 0. - * @return The number of intersection points, 0 when no intersection points - * exist. - */ - int intersect(Segment other, Point2D[] intersectionPoints, - double[] paramThis, double[] paramOther, double tolerance) { - return _intersect(other, intersectionPoints, paramThis, paramOther, - tolerance); - } - - /** - * Returns TRUE if this segment intersects with the other segment with the - * given tolerance. - */ - public boolean isIntersecting(Segment other, double tolerance) { - return _isIntersecting(other, tolerance, false) != 0; - } - - /** - * Returns TRUE if the point and segment intersect (not disjoint) for the - * given tolerance. - */ - public boolean isIntersecting(Point2D pt, double tolerance) { - return _isIntersectingPoint(pt, tolerance, false); - } - - /** - * Non public abstract version of the function. - */ - public boolean isEmptyImpl() { - return false; - } - - // Header Definitions - - // Cpp definitions - - /** - * Creates a segment with start and end points (0,0). - */ - public Segment() { - m_xStart = 0; - m_yStart = 0; - m_xEnd = 0; - m_yEnd = 0; - m_attributes = null; - } - - void _resizeAttributes(int newSize) { - _touch(); - if (m_attributes == null && newSize > 0) { - m_attributes = new double[newSize * 2]; - } else if (m_attributes != null && m_attributes.length < newSize * 2) { - double[] newbuffer = new double[newSize * 2]; - System.arraycopy(m_attributes, 0, newbuffer, 0, m_attributes.length); - m_attributes = newbuffer; - } - } - - static void _attributeCopy(double[] src, int srcStart, double[] dst, - int dstStart, int count) { - if (count > 0) - System.arraycopy(src, srcStart, dst, dstStart, count); - } - - private Point2D _getXY(int endPoint) { - Point2D pt = new Point2D(); - if (endPoint != 0) { - pt.setCoords(m_xEnd, m_yEnd); - } else { - pt.setCoords(m_xStart, m_yStart); - } - return pt; - } - - private void _setXY(int endPoint, Point2D pt) { - if (endPoint != 0) { - m_xEnd = pt.x; - m_yEnd = pt.y; - } else { - m_xStart = pt.x; - m_yStart = pt.y; - } - } - - private Point3D _getXYZ(int endPoint) { - Point3D pt = new Point3D(); - if (endPoint != 0) { - pt.x = m_xEnd; - pt.y = m_yEnd; - } else { - pt.x = m_xStart; - pt.y = m_yStart; - } - - if (m_description.hasZ()) - pt.z = m_attributes[_getEndPointOffset(m_description, endPoint)]; - else - pt.z = VertexDescription.getDefaultValue(Semantics.Z); - - return pt; - } - - private void _setXYZ(int endPoint, Point3D pt) { - _touch(); - boolean bHasZ = hasAttribute(Semantics.Z); - if (!bHasZ && !VertexDescription.isDefaultValue(Semantics.Z, pt.z)) {// add - // Z - // only - // if - // pt.z - // is - // not - // a - // default - // value. - addAttribute(Semantics.Z); - bHasZ = true; - } - - if (endPoint != 0) { - m_xEnd = pt.x; - m_yEnd = pt.y; - } else { - m_xStart = pt.x; - m_yStart = pt.y; - } - - if (bHasZ) - m_attributes[_getEndPointOffset(m_description, endPoint)] = pt.z; - - } - - @Override - protected void _assignVertexDescriptionImpl(VertexDescription newDescription) { - if (m_attributes == null) { - m_description = newDescription; - return; - } - - int[] mapping = VertexDescriptionDesignerImpl.mapAttributes(newDescription, m_description); - - double[] newAttributes = new double[(newDescription.getTotalComponentCount() - 2) * 2]; - - int old_offset0 = _getEndPointOffset(m_description, 0); - int old_offset1 = _getEndPointOffset(m_description, 1); - - int new_offset0 = _getEndPointOffset(newDescription, 0); - int new_offset1 = _getEndPointOffset(newDescription, 1); - - int j = 0; - for (int i = 1, n = newDescription.getAttributeCount(); i < n; i++) { - int semantics = newDescription.getSemantics(i); - int nords = VertexDescription.getComponentCount(semantics); - if (mapping[i] == -1) { - double d = VertexDescription.getDefaultValue(semantics); - for (int ord = 0; ord < nords; ord++) { - newAttributes[new_offset0 + j] = d; - newAttributes[new_offset1 + j] = d; - j++; - } - } else { - int m = mapping[i]; - int offset = m_description._getPointAttributeOffset(m) - 2; - for (int ord = 0; ord < nords; ord++) { - newAttributes[new_offset0 + j] = m_attributes[old_offset0 + offset]; - newAttributes[new_offset1 + j] = m_attributes[old_offset1 + offset]; - j++; - offset++; - } - } - - } - - m_attributes = newAttributes; - m_description = newDescription; - } - - private void _get(int endPoint, Point outPoint) { - if (isEmptyImpl()) - throw new GeometryException("empty geometry");// ._setToDefault(); - - outPoint.assignVertexDescription(m_description); - - if (outPoint.isEmptyImpl()) - outPoint._setToDefault(); - - for (int attributeIndex = 0; attributeIndex < m_description - .getAttributeCount(); attributeIndex++) { - int semantics = m_description._getSemanticsImpl(attributeIndex); - for (int icomp = 0, ncomp = VertexDescription - .getComponentCount(semantics); icomp < ncomp; icomp++) { - double v = _getAttributeAsDbl(endPoint, semantics, icomp); - outPoint.setAttribute(semantics, icomp, v); - } - } - } - - private void _set(int endPoint, Point src) { - _touch(); - Point point = src; - - if (src.isEmptyImpl())// can not assign an empty point - throw new GeometryException("empty_Geometry"); - - VertexDescription vdin = point.getDescription(); - for (int attributeIndex = 0, nattrib = vdin.getAttributeCount(); attributeIndex < nattrib; attributeIndex++) { - int semantics = vdin._getSemanticsImpl(attributeIndex); - int ncomp = VertexDescription.getComponentCount(semantics); - for (int icomp = 0; icomp < ncomp; icomp++) { - double v = point.getAttributeAsDbl(semantics, icomp); - _setAttribute(endPoint, semantics, icomp, v); - } - } - } - - double _getAttributeAsDbl(int endPoint, int semantics, int ordinate) { - if (isEmptyImpl()) - throw new GeometryException( - "This operation was performed on an Empty Geometry."); - - if (semantics == Semantics.POSITION) { - if (endPoint != 0) { - return (ordinate != 0) ? m_yEnd : m_xEnd; - } else { - return (ordinate != 0) ? m_yStart : m_xStart; - } - } - - int ncomps = VertexDescription.getComponentCount(semantics); - if (ordinate >= ncomps) - throw new IndexOutOfBoundsException(); - - int attributeIndex = m_description.getAttributeIndex(semantics); - if (attributeIndex >= 0) { - if (m_attributes != null) - _resizeAttributes(m_description.getTotalComponentCount() - 2); - - return m_attributes[_getEndPointOffset(m_description, endPoint) - + m_description._getPointAttributeOffset(attributeIndex) - - 2 + ordinate]; - } else - return VertexDescription.getDefaultValue(semantics); - } - - private int _getAttributeAsInt(int endPoint, int semantics, int ordinate) { - if (isEmptyImpl()) - throw new GeometryException("Empty_Geometry."); - - return (int) _getAttributeAsDbl(endPoint, semantics, ordinate); - } - - void _setAttribute(int endPoint, int semantics, int ordinate, double value) { - _touch(); - int ncomps = VertexDescription.getComponentCount(semantics); - if (ordinate >= ncomps) - throw new IndexOutOfBoundsException(); - - int attributeIndex = m_description.getAttributeIndex(semantics); - if (attributeIndex < 0) { - addAttribute(semantics); - attributeIndex = m_description.getAttributeIndex(semantics); - } - - if (semantics == Semantics.POSITION) { - if (endPoint != 0) { - if (ordinate != 0) - m_yEnd = value; - else - m_xEnd = value; - } else if (ordinate != 0) - m_yStart = value; - else - m_xStart = value; - return; - } - - if (m_attributes == null) - _resizeAttributes(m_description.getTotalComponentCount() - 2); - - m_attributes[_getEndPointOffset(m_description, endPoint) - + m_description._getPointAttributeOffset(attributeIndex) - 2 - + ordinate] = value; - - } - - void _setAttribute(int endPoint, int semantics, int ordinate, int value) { - _setAttribute(endPoint, semantics, ordinate, (double) value); - } - - @Override - public void copyTo(Geometry dst) { - if (dst.getType() != getType()) - throw new IllegalArgumentException(); - - Segment segDst = (Segment) dst; - segDst.m_description = m_description; - segDst._resizeAttributes(m_description.getTotalComponentCount() - 2); - _attributeCopy(m_attributes, 0, segDst.m_attributes, 0, - (m_description.getTotalComponentCount() - 2) * 2); - segDst.m_xStart = m_xStart; - segDst.m_yStart = m_yStart; - segDst.m_xEnd = m_xEnd; - segDst.m_yEnd = m_yEnd; - dst._touch(); - - _copyToImpl(segDst); - } - - @Override - public Envelope1D queryInterval(int semantics, int ordinate) { - Envelope1D env = new Envelope1D(); - if (isEmptyImpl()) { - env.setEmpty(); - return env; - } - - env.vmin = _getAttributeAsDbl(0, semantics, ordinate); - env.vmax = env.vmin; - env.mergeNE(_getAttributeAsDbl(1, semantics, ordinate)); - return env; - } - - void queryCoord(double t, Point point) { - point.assignVertexDescription(m_description); - point.setXY(getCoord2D(t)); - for (int iattrib = 1, nattrib = m_description.getAttributeCount(); iattrib < nattrib; iattrib++) { - int semantics = m_description._getSemanticsImpl(iattrib); - int ncomp = VertexDescription.getComponentCount(semantics); - for (int iord = 0; iord < ncomp; iord++) { - double value = getAttributeAsDbl(t, semantics, iord); - point.setAttribute(semantics, iord, value); - } - } - } - - boolean _equalsImpl(Segment other) { - if (m_description != other.m_description) - return false; - - if (m_xStart != other.m_xStart || m_xEnd != other.m_xEnd - || m_yStart != other.m_yStart || m_yEnd != other.m_yEnd) - return false; - for (int i = 0; i < (m_description.getTotalComponentCount() - 2) * 2; i++) - if (m_attributes[i] != other.m_attributes[i]) - return false; - - return true; - } - - /** - * Returns true, when this segment is a closed curve (start point is equal - * to end point exactly). - *

- * Note, this will return true for lines, that are degenerate to a point - * too. - */ - boolean isClosed() { - return m_xStart == m_xEnd && m_yStart == m_yEnd; - } - - void reverse() { - _reverseImpl(); - double origxStart = m_xStart; - double origxEnd = m_xEnd; - m_xStart = origxEnd; - m_xEnd = origxStart; - double origyStart = m_yStart; - double origyEnd = m_yEnd; - m_yStart = origyEnd; - m_yEnd = origyStart; - - for (int i = 1, n = m_description.getAttributeCount(); i < n; i++) { - int semantics = m_description.getSemantics(i);// VertexDescription.Semantics - // semantics = - // m_description.getSemantics(i); - for (int iord = 0, nord = VertexDescription - .getComponentCount(semantics); iord < nord; iord++) { - double v1 = _getAttributeAsDbl(0, semantics, iord); - double v2 = _getAttributeAsDbl(1, semantics, iord); - _setAttribute(0, semantics, iord, v2); - _setAttribute(1, semantics, iord, v1); - } - } - } - - int _isIntersecting(Segment other, double tolerance, - boolean bExcludeExactEndpoints) { - int gtThis = getType().value(); - int gtOther = other.getType().value(); - switch (gtThis) { - case Geometry.GeometryType.Line: - if (gtOther == Geometry.GeometryType.Line) - return Line._isIntersectingLineLine((Line) this, (Line) other, - tolerance, bExcludeExactEndpoints); - else - throw GeometryException.GeometryInternalError(); - default: - throw GeometryException.GeometryInternalError(); - } - } - - int _intersect(Segment other, Point2D[] intersectionPoints, - double[] paramThis, double[] paramOther, double tolerance) { - int gtThis = getType().value(); - int gtOther = other.getType().value(); - switch (gtThis) { - case Geometry.GeometryType.Line: - if (gtOther == Geometry.GeometryType.Line) - return Line._intersectLineLine((Line) this, (Line) other, - intersectionPoints, paramThis, paramOther, tolerance); - else - throw GeometryException.GeometryInternalError(); - default: - throw GeometryException.GeometryInternalError(); - } - } - - /** - * A helper function for area calculation. Calculates the Integral(y(t) * - * x'(t) * dt) for t = [0, 1]. The area of a ring is caluclated as a sum of - * the results of CalculateArea2DHelper. - */ - abstract double _calculateArea2DHelper(double xorg, double yorg); - - static int _getEndPointOffset(VertexDescription vd, int endPoint) { - return endPoint * (vd.getTotalComponentCount() - 2); - } - - /** - * Returns the coordinate of the point on this segment for the given - * parameter value. - */ - public Point2D getCoord2D(double t) { - Point2D pt = new Point2D(); - getCoord2D(t, pt); - return pt; - } - - /** - * Returns the coordinate of the point on this segment for the given - * parameter value (segments are parametric curves). - * - * @param t the parameter coordinate along the segment from 0.0 to 1.0. - * Value of 0 returns the start point, 1 returns end point. - * @param dst the coordinate where result will be placed. - */ - public abstract void getCoord2D(double t, Point2D dst); - - /** - * Finds a closest coordinate on this segment. - * - * @param inputPoint The 2D point to find the closest coordinate on this segment. - * @param bExtrapolate TRUE if the segment is extrapolated at the end points along - * the end point tangents. Otherwise the result is limited to - * values between 0 and 1. - * @return The parametric coordinate t on the segment (0 corresponds to the - * start point, 1 corresponds to the end point). Use getCoord2D to - * obtain the 2D coordinate on the segment from t. To find the - * distance, call (inputPoint.sub(seg.getCoord2D(t))).length(); - */ - public abstract double getClosestCoordinate(Point2D inputPoint, - boolean bExtrapolate); - - /** - * Splits this segment into Y monotonic parts and places them into the input - * array. - * - * @param monotonicSegments The in/out array of SegmentBuffer structures that will be - * filled with the monotonic parts. The monotonicSegments array - * must contain at least 3 elements. - * @return The number of monotonic parts if the split had happened. Returns - * 0 if the segment is already monotonic. - */ - abstract int getYMonotonicParts(SegmentBuffer[] monotonicSegments); - - /** - * Calculates intersection points of this segment with an infinite line, - * parallel to one of the axes. - * - * @param bAxisX TRUE if the function works with the line parallel to the axis - * X. - * @param ordinate The ordinate value of the line (x for axis Y, y for axis X). - * @param resultOrdinates The value of ordinate in the intersection points One ordinate - * is equal to the ordinate parameter. This parameter can be - * NULL. - * @param parameters The value of the parameter in the intersection points (between - * 0 and 1). This parameter can be NULL. - * @return The number of intersection points, 0 when no intersection points - * exist, -1 when the segment coincides with the line (infinite - * number of intersection points). - */ - public abstract int intersectionWithAxis2D(boolean bAxisX, double ordinate, - double[] resultOrdinates, double[] parameters); - - void _reverseImpl() { - } - - /** - * Returns True if the segment is degenerate to a point with relation to the - * given tolerance. For Lines this means the line length is not longer than - * the tolerance. For the curves, the distance between the segment endpoints - * should not be longer than the tolerance and the distance from the line, - * connecting the endpoints to the furtherst point on the segment is not - * larger than the tolerance. - */ - abstract boolean isDegenerate(double tolerance); - - // Cpp definitions - - abstract boolean isCurve(); - - abstract Point2D _getTangent(double t); - - abstract boolean _isDegenerate(double tolerance); - - double _calculateSubLength(double t) { - return tToLength(t); - } - - double _calculateSubLength(double t1, double t2) { - return tToLength(t2) - tToLength(t1); - } - - abstract void _copyToImpl(Segment dst); - - /** - * Returns subsegment between parameters t1 and t2. The attributes are - * interpolated along the length of the curve. - */ - public abstract Segment cut(double t1, double t2); - - /** - * Calculates the subsegment between parameters t1 and t2, and stores the - * result in subSegmentBuffer. The attributes are interpolated along the - * length of the curve. - */ - abstract void cut(double t1, double t2, SegmentBuffer subSegmentBuffer); - - /** - * Returns the attribute on the segment for the given parameter value. The - * interpolation of attribute is given by the attribute interpolation type. - */ - public abstract double getAttributeAsDbl(double t, int semantics, - int ordinate); - - abstract boolean _isIntersectingPoint(Point2D pt, double tolerance, - boolean bExcludeExactEndpoints); - - /** - * Calculates intersection point of this segment with an infinite line, - * parallel to axis X. This segment must be to be y-monotonic (or - * horizontal). - * - * @param y The y coordinate of the line. - * @param xParallel For segments, that are horizontal, and have y coordinate, this - * value is returned. - * @return X coordinate of the intersection, or NaN, if no intersection. - */ - abstract double intersectionOfYMonotonicWithAxisX(double y, double xParallel); - - /** - * Converts curves parameter t to the curve length. Can be expensive for curves. - */ - abstract double tToLength(double t); - - abstract double lengthToT(double len); - - public double distance(/* const */Segment otherSegment, - boolean bSegmentsKnownDisjoint) { - // if the segments are not known to be disjoint, and - // the segments are found to touch in any way, then return 0.0 - if (!bSegmentsKnownDisjoint - && _isIntersecting(otherSegment, 0, false) != 0) { - return 0.0; - } - - double minDistance = NumberUtils.doubleMax(); - - Point2D input_point; - double t; - double distance; - - input_point = getStartXY(); - t = otherSegment.getClosestCoordinate(input_point, false); - input_point.sub(otherSegment.getCoord2D(t)); - distance = input_point.length(); - if (distance < minDistance) - minDistance = distance; - - input_point = getEndXY(); - t = otherSegment.getClosestCoordinate(input_point, false); - input_point.sub(otherSegment.getCoord2D(t)); - distance = input_point.length(); - if (distance < minDistance) - minDistance = distance; - - input_point = otherSegment.getStartXY(); - t = getClosestCoordinate(input_point, false); - input_point.sub(getCoord2D(t)); - distance = input_point.length(); - if (distance < minDistance) - minDistance = distance; - - input_point = otherSegment.getEndXY(); - t = getClosestCoordinate(input_point, false); - input_point.sub(getCoord2D(t)); - distance = input_point.length(); - if (distance < minDistance) - minDistance = distance; - - return minDistance; - } - - public Geometry getBoundary() { - return Boundary.calculate(this, null); - } + double m_xStart; + + double m_yStart; + + double m_xEnd; + + double m_yEnd; + + double[] m_attributes; + + // Header Definitions + + /** + * Returns XY coordinates of the start point. + */ + public Point2D getStartXY() { + return Point2D.construct(m_xStart, m_yStart); + } + + public void getStartXY(Point2D pt) { + pt.x = m_xStart; + pt.y = m_yStart; + } + + /** + * Sets the XY coordinates of the start point. + */ + public void setStartXY(Point2D pt) { + _setXY(0, pt); + } + + public void setStartXY(double x, double y) { + _setXY(0, Point2D.construct(x, y)); + } + + /** + * Returns XYZ coordinates of the start point. Z if 0 if Z is missing. + */ + public Point3D getStartXYZ() { + return _getXYZ(0); + } + + /** + * Sets the XYZ coordinates of the start point. + */ + public void setStartXYZ(Point3D pt) { + _setXYZ(0, pt); + } + + public void setStartXYZ(double x, double y, double z) { + _setXYZ(0, Point3D.construct(x, y, z)); + } + + /** + * Returns coordinates of the start point in a Point class. + */ + public void queryStart(Point dstPoint) { + _get(0, dstPoint); + } + + /** + * Sets the coordinates of the start point in this segment. + * + * @param srcPoint The new start point of this segment. + */ + public void setStart(Point srcPoint) { + _set(0, srcPoint); + } + + /** + * Returns value of the start vertex attribute's ordinate. Throws if the + * Point is empty. + * + * @param semantics The attribute semantics. + * @param ordinate The attribute's ordinate. For example, the y coordinate of the + * NORMAL has ordinate of 1. + * @return Ordinate value as double. + */ + public double getStartAttributeAsDbl(int semantics, int ordinate) { + return _getAttributeAsDbl(0, semantics, ordinate); + } + + /** + * Returns the value of the start vertex attribute's ordinate. The ordinate + * is always 0 because integer attributes always have one component. + * + * @param semantics The attribute semantics. + * @param ordinate The attribute's ordinate. For example, the y coordinate of the + * NORMAL has ordinate of 1. + * @return Ordinate value truncated to 32 bit integer. + */ + public int getStartAttributeAsInt(int semantics, int ordinate) { + return _getAttributeAsInt(0, semantics, ordinate); + } + + /** + * Sets the value of the start vertex attribute. + * + * @param semantics The attribute semantics. + * @param value is the array to write values to. The attribute type and the + * number of elements must match the persistence type, as well as + * the number of components of the attribute. + */ + public void setStartAttribute(int semantics, int ordinate, double value) { + _setAttribute(0, semantics, ordinate, value); + } + + public void setStartAttribute(int semantics, int ordinate, int value) { + _setAttribute(0, semantics, ordinate, value); + } + + /** + * Returns the X coordinate of starting point. + * + * @return The X coordinate of starting point. + */ + public double getStartX() { + return m_xStart; + } + + /** + * Returns the Y coordinate of starting point. + * + * @return The Y coordinate of starting point. + */ + public double getStartY() { + return m_yStart; + } + + /** + * Returns the X coordinate of ending point. + * + * @return The X coordinate of ending point. + */ + public double getEndX() { + return m_xEnd; + } + + /** + * Returns the Y coordinate of ending point. + * + * @return The Y coordinate of ending point. + */ + public double getEndY() { + return m_yEnd; + } + + /** + * Returns XY coordinates of the end point. + * + * @return The XY coordinates of the end point. + */ + public Point2D getEndXY() { + return Point2D.construct(m_xEnd, m_yEnd); + } + + public void getEndXY(Point2D pt) { + pt.x = m_xEnd; + pt.y = m_yEnd; + } + + /** + * Sets the XY coordinates of the end point. + * + * @param pt The end point of the segment. + */ + public void setEndXY(Point2D pt) { + _setXY(1, pt); + } + + public void setEndXY(double x, double y) { + _setXY(1, Point2D.construct(x, y)); + } + + /** + * Returns XYZ coordinates of the end point. Z if 0 if Z is missing. + * + * @return The XYZ coordinates of the end point. + */ + public Point3D getEndXYZ() { + return _getXYZ(1); + } + + /** + * Sets the XYZ coordinates of the end point. + */ + public void setEndXYZ(Point3D pt) { + _setXYZ(1, pt); + } + + public void setEndXYZ(double x, double y, double z) { + _setXYZ(1, Point3D.construct(x, y, z)); + } + + /** + * Returns coordinates of the end point in this segment. + * + * @param dstPoint The end point of this segment. + */ + public void queryEnd(Point dstPoint) { + _get(1, dstPoint); + } + + /** + * Sets the coordinates of the end point in a Point class. + * + * @param srcPoint The new end point of this segment. + */ + public void setEnd(Point srcPoint) { + _set(1, srcPoint); + } + + /** + * Returns value of the end vertex attribute's ordinate. Throws if the Point + * is empty. + * + * @param semantics The attribute semantics. + * @param ordinate The attribute's ordinate. For example, the y coordinate of the + * NORMAL has ordinate of 1. + * @return Ordinate value as double. + */ + public double getEndAttributeAsDbl(int semantics, int ordinate) { + return _getAttributeAsDbl(1, semantics, ordinate); + } + + /** + * Returns the value of the end vertex attribute's ordinate. The ordinate is + * always 0 because integer attributes always have one component. + * + * @param semantics The attribute semantics. + * @param ordinate The attribute's ordinate. For example, the y coordinate of the + * NORMAL has ordinate of 1. + * @return The ordinate value truncated to 32 bit integer. + */ + public int getEndAttributeAsInt(int semantics, int ordinate) { + return _getAttributeAsInt(1, semantics, ordinate); + } + + /** + * Sets the value of end vertex attribute. + * + * @param semantics The attribute semantics. + * @param ordinate The attribute's ordinate. + * @param value Is the array to write values to. The attribute type and the + * number of elements must match the persistence type, as well as + * the number of components of the attribute. + */ + public void setEndAttribute(int semantics, int ordinate, double value) { + _setAttribute(1, semantics, ordinate, value); + } + + public void setEndAttribute(int semantics, int ordinate, int value) { + _setAttribute(1, semantics, ordinate, value); + } + + @Override + public final int getDimension() { + return 1; + } + + @Override + public final boolean isEmpty() { + return isEmptyImpl(); + } + + @Override + public final void setEmpty() { + + } + + @Override + public double calculateArea2D() { + return 0; + } + + /** + * Calculates intersections of this segment with another segment. + *

+ * Note: This is not a topological operation. It needs to be paired with the + * Segment.Overlap call. + * + * @param other The segment to calculate intersection with. + * @param intersectionPoints The intersection points. Can be NULL. + * @param paramThis The value of the parameter in the intersection points for this + * Segment (between 0 and 1). Can be NULL. + * @param paramOther The value of the parameter in the intersection points for the + * other Segment (between 0 and 1). Can be NULL. + * @param tolerance The tolerance value for the intersection calculation. Can be + * 0. + * @return The number of intersection points, 0 when no intersection points + * exist. + */ + int intersect(Segment other, Point2D[] intersectionPoints, + double[] paramThis, double[] paramOther, double tolerance) { + return _intersect(other, intersectionPoints, paramThis, paramOther, + tolerance); + } + + /** + * Returns TRUE if this segment intersects with the other segment with the + * given tolerance. + */ + public boolean isIntersecting(Segment other, double tolerance) { + return _isIntersecting(other, tolerance, false) != 0; + } + + /** + * Returns TRUE if the point and segment intersect (not disjoint) for the + * given tolerance. + */ + public boolean isIntersecting(Point2D pt, double tolerance) { + return _isIntersectingPoint(pt, tolerance, false); + } + + /** + * Non public abstract version of the function. + */ + public boolean isEmptyImpl() { + return false; + } + + // Header Definitions + + // Cpp definitions + + /** + * Creates a segment with start and end points (0,0). + */ + public Segment() { + m_xStart = 0; + m_yStart = 0; + m_xEnd = 0; + m_yEnd = 0; + m_attributes = null; + } + + void _resizeAttributes(int newSize) { + _touch(); + if (m_attributes == null && newSize > 0) { + m_attributes = new double[newSize * 2]; + } else if (m_attributes != null && m_attributes.length < newSize * 2) { + double[] newbuffer = new double[newSize * 2]; + System.arraycopy(m_attributes, 0, newbuffer, 0, m_attributes.length); + m_attributes = newbuffer; + } + } + + static void _attributeCopy(double[] src, int srcStart, double[] dst, + int dstStart, int count) { + if (count > 0) + System.arraycopy(src, srcStart, dst, dstStart, count); + } + + private Point2D _getXY(int endPoint) { + Point2D pt = new Point2D(); + if (endPoint != 0) { + pt.setCoords(m_xEnd, m_yEnd); + } else { + pt.setCoords(m_xStart, m_yStart); + } + return pt; + } + + private void _setXY(int endPoint, Point2D pt) { + if (endPoint != 0) { + m_xEnd = pt.x; + m_yEnd = pt.y; + } else { + m_xStart = pt.x; + m_yStart = pt.y; + } + } + + private Point3D _getXYZ(int endPoint) { + Point3D pt = new Point3D(); + if (endPoint != 0) { + pt.x = m_xEnd; + pt.y = m_yEnd; + } else { + pt.x = m_xStart; + pt.y = m_yStart; + } + + if (m_description.hasZ()) + pt.z = m_attributes[_getEndPointOffset(m_description, endPoint)]; + else + pt.z = VertexDescription.getDefaultValue(Semantics.Z); + + return pt; + } + + private void _setXYZ(int endPoint, Point3D pt) { + _touch(); + boolean bHasZ = hasAttribute(Semantics.Z); + if (!bHasZ && !VertexDescription.isDefaultValue(Semantics.Z, pt.z)) {// add + // Z + // only + // if + // pt.z + // is + // not + // a + // default + // value. + addAttribute(Semantics.Z); + bHasZ = true; + } + + if (endPoint != 0) { + m_xEnd = pt.x; + m_yEnd = pt.y; + } else { + m_xStart = pt.x; + m_yStart = pt.y; + } + + if (bHasZ) + m_attributes[_getEndPointOffset(m_description, endPoint)] = pt.z; + + } + + @Override + protected void _assignVertexDescriptionImpl(VertexDescription newDescription) { + if (m_attributes == null) { + m_description = newDescription; + return; + } + + int[] mapping = VertexDescriptionDesignerImpl.mapAttributes(newDescription, m_description); + + double[] newAttributes = new double[(newDescription.getTotalComponentCount() - 2) * 2]; + + int old_offset0 = _getEndPointOffset(m_description, 0); + int old_offset1 = _getEndPointOffset(m_description, 1); + + int new_offset0 = _getEndPointOffset(newDescription, 0); + int new_offset1 = _getEndPointOffset(newDescription, 1); + + int j = 0; + for (int i = 1, n = newDescription.getAttributeCount(); i < n; i++) { + int semantics = newDescription.getSemantics(i); + int nords = VertexDescription.getComponentCount(semantics); + if (mapping[i] == -1) { + double d = VertexDescription.getDefaultValue(semantics); + for (int ord = 0; ord < nords; ord++) { + newAttributes[new_offset0 + j] = d; + newAttributes[new_offset1 + j] = d; + j++; + } + } else { + int m = mapping[i]; + int offset = m_description._getPointAttributeOffset(m) - 2; + for (int ord = 0; ord < nords; ord++) { + newAttributes[new_offset0 + j] = m_attributes[old_offset0 + offset]; + newAttributes[new_offset1 + j] = m_attributes[old_offset1 + offset]; + j++; + offset++; + } + } + + } + + m_attributes = newAttributes; + m_description = newDescription; + } + + private void _get(int endPoint, Point outPoint) { + if (isEmptyImpl()) + throw new GeometryException("empty geometry");// ._setToDefault(); + + outPoint.assignVertexDescription(m_description); + + if (outPoint.isEmptyImpl()) + outPoint._setToDefault(); + + for (int attributeIndex = 0; attributeIndex < m_description + .getAttributeCount(); attributeIndex++) { + int semantics = m_description._getSemanticsImpl(attributeIndex); + for (int icomp = 0, ncomp = VertexDescription + .getComponentCount(semantics); icomp < ncomp; icomp++) { + double v = _getAttributeAsDbl(endPoint, semantics, icomp); + outPoint.setAttribute(semantics, icomp, v); + } + } + } + + private void _set(int endPoint, Point src) { + _touch(); + Point point = src; + + if (src.isEmptyImpl())// can not assign an empty point + throw new GeometryException("empty_Geometry"); + + VertexDescription vdin = point.getDescription(); + for (int attributeIndex = 0, nattrib = vdin.getAttributeCount(); attributeIndex < nattrib; attributeIndex++) { + int semantics = vdin._getSemanticsImpl(attributeIndex); + int ncomp = VertexDescription.getComponentCount(semantics); + for (int icomp = 0; icomp < ncomp; icomp++) { + double v = point.getAttributeAsDbl(semantics, icomp); + _setAttribute(endPoint, semantics, icomp, v); + } + } + } + + double _getAttributeAsDbl(int endPoint, int semantics, int ordinate) { + if (isEmptyImpl()) + throw new GeometryException( + "This operation was performed on an Empty Geometry."); + + if (semantics == Semantics.POSITION) { + if (endPoint != 0) { + return (ordinate != 0) ? m_yEnd : m_xEnd; + } else { + return (ordinate != 0) ? m_yStart : m_xStart; + } + } + + int ncomps = VertexDescription.getComponentCount(semantics); + if (ordinate >= ncomps) + throw new IndexOutOfBoundsException(); + + int attributeIndex = m_description.getAttributeIndex(semantics); + if (attributeIndex >= 0) { + if (m_attributes != null) + _resizeAttributes(m_description.getTotalComponentCount() - 2); + + return m_attributes[_getEndPointOffset(m_description, endPoint) + + m_description._getPointAttributeOffset(attributeIndex) + - 2 + ordinate]; + } else + return VertexDescription.getDefaultValue(semantics); + } + + private int _getAttributeAsInt(int endPoint, int semantics, int ordinate) { + if (isEmptyImpl()) + throw new GeometryException("Empty_Geometry."); + + return (int) _getAttributeAsDbl(endPoint, semantics, ordinate); + } + + void _setAttribute(int endPoint, int semantics, int ordinate, double value) { + _touch(); + int ncomps = VertexDescription.getComponentCount(semantics); + if (ordinate >= ncomps) + throw new IndexOutOfBoundsException(); + + int attributeIndex = m_description.getAttributeIndex(semantics); + if (attributeIndex < 0) { + addAttribute(semantics); + attributeIndex = m_description.getAttributeIndex(semantics); + } + + if (semantics == Semantics.POSITION) { + if (endPoint != 0) { + if (ordinate != 0) + m_yEnd = value; + else + m_xEnd = value; + } else if (ordinate != 0) + m_yStart = value; + else + m_xStart = value; + return; + } + + if (m_attributes == null) + _resizeAttributes(m_description.getTotalComponentCount() - 2); + + m_attributes[_getEndPointOffset(m_description, endPoint) + + m_description._getPointAttributeOffset(attributeIndex) - 2 + + ordinate] = value; + + } + + void _setAttribute(int endPoint, int semantics, int ordinate, int value) { + _setAttribute(endPoint, semantics, ordinate, (double) value); + } + + @Override + public void copyTo(Geometry dst) { + if (dst.getType() != getType()) + throw new IllegalArgumentException(); + + Segment segDst = (Segment) dst; + segDst.m_description = m_description; + segDst._resizeAttributes(m_description.getTotalComponentCount() - 2); + _attributeCopy(m_attributes, 0, segDst.m_attributes, 0, + (m_description.getTotalComponentCount() - 2) * 2); + segDst.m_xStart = m_xStart; + segDst.m_yStart = m_yStart; + segDst.m_xEnd = m_xEnd; + segDst.m_yEnd = m_yEnd; + dst._touch(); + + _copyToImpl(segDst); + } + + @Override + public Envelope1D queryInterval(int semantics, int ordinate) { + Envelope1D env = new Envelope1D(); + if (isEmptyImpl()) { + env.setEmpty(); + return env; + } + + env.vmin = _getAttributeAsDbl(0, semantics, ordinate); + env.vmax = env.vmin; + env.mergeNE(_getAttributeAsDbl(1, semantics, ordinate)); + return env; + } + + void queryCoord(double t, Point point) { + point.assignVertexDescription(m_description); + point.setXY(getCoord2D(t)); + for (int iattrib = 1, nattrib = m_description.getAttributeCount(); iattrib < nattrib; iattrib++) { + int semantics = m_description._getSemanticsImpl(iattrib); + int ncomp = VertexDescription.getComponentCount(semantics); + for (int iord = 0; iord < ncomp; iord++) { + double value = getAttributeAsDbl(t, semantics, iord); + point.setAttribute(semantics, iord, value); + } + } + } + + boolean _equalsImpl(Segment other) { + if (m_description != other.m_description) + return false; + + if (m_xStart != other.m_xStart || m_xEnd != other.m_xEnd + || m_yStart != other.m_yStart || m_yEnd != other.m_yEnd) + return false; + for (int i = 0; i < (m_description.getTotalComponentCount() - 2) * 2; i++) + if (m_attributes[i] != other.m_attributes[i]) + return false; + + return true; + } + + /** + * Returns true, when this segment is a closed curve (start point is equal + * to end point exactly). + *

+ * Note, this will return true for lines, that are degenerate to a point + * too. + */ + boolean isClosed() { + return m_xStart == m_xEnd && m_yStart == m_yEnd; + } + + void reverse() { + _reverseImpl(); + double origxStart = m_xStart; + double origxEnd = m_xEnd; + m_xStart = origxEnd; + m_xEnd = origxStart; + double origyStart = m_yStart; + double origyEnd = m_yEnd; + m_yStart = origyEnd; + m_yEnd = origyStart; + + for (int i = 1, n = m_description.getAttributeCount(); i < n; i++) { + int semantics = m_description.getSemantics(i);// VertexDescription.Semantics + // semantics = + // m_description.getSemantics(i); + for (int iord = 0, nord = VertexDescription + .getComponentCount(semantics); iord < nord; iord++) { + double v1 = _getAttributeAsDbl(0, semantics, iord); + double v2 = _getAttributeAsDbl(1, semantics, iord); + _setAttribute(0, semantics, iord, v2); + _setAttribute(1, semantics, iord, v1); + } + } + } + + int _isIntersecting(Segment other, double tolerance, + boolean bExcludeExactEndpoints) { + int gtThis = getType().value(); + int gtOther = other.getType().value(); + switch (gtThis) { + case Geometry.GeometryType.Line: + if (gtOther == Geometry.GeometryType.Line) + return Line._isIntersectingLineLine((Line) this, (Line) other, + tolerance, bExcludeExactEndpoints); + else + throw GeometryException.GeometryInternalError(); + default: + throw GeometryException.GeometryInternalError(); + } + } + + int _intersect(Segment other, Point2D[] intersectionPoints, + double[] paramThis, double[] paramOther, double tolerance) { + int gtThis = getType().value(); + int gtOther = other.getType().value(); + switch (gtThis) { + case Geometry.GeometryType.Line: + if (gtOther == Geometry.GeometryType.Line) + return Line._intersectLineLine((Line) this, (Line) other, + intersectionPoints, paramThis, paramOther, tolerance); + else + throw GeometryException.GeometryInternalError(); + default: + throw GeometryException.GeometryInternalError(); + } + } + + /** + * A helper function for area calculation. Calculates the Integral(y(t) * + * x'(t) * dt) for t = [0, 1]. The area of a ring is caluclated as a sum of + * the results of CalculateArea2DHelper. + */ + abstract double _calculateArea2DHelper(double xorg, double yorg); + + static int _getEndPointOffset(VertexDescription vd, int endPoint) { + return endPoint * (vd.getTotalComponentCount() - 2); + } + + /** + * Returns the coordinate of the point on this segment for the given + * parameter value. + */ + public Point2D getCoord2D(double t) { + Point2D pt = new Point2D(); + getCoord2D(t, pt); + return pt; + } + + /** + * Returns the coordinate of the point on this segment for the given + * parameter value (segments are parametric curves). + * + * @param t the parameter coordinate along the segment from 0.0 to 1.0. + * Value of 0 returns the start point, 1 returns end point. + * @param dst the coordinate where result will be placed. + */ + public abstract void getCoord2D(double t, Point2D dst); + + /** + * Finds a closest coordinate on this segment. + * + * @param inputPoint The 2D point to find the closest coordinate on this segment. + * @param bExtrapolate TRUE if the segment is extrapolated at the end points along + * the end point tangents. Otherwise the result is limited to + * values between 0 and 1. + * @return The parametric coordinate t on the segment (0 corresponds to the + * start point, 1 corresponds to the end point). Use getCoord2D to + * obtain the 2D coordinate on the segment from t. To find the + * distance, call (inputPoint.sub(seg.getCoord2D(t))).length(); + */ + public abstract double getClosestCoordinate(Point2D inputPoint, + boolean bExtrapolate); + + /** + * Splits this segment into Y monotonic parts and places them into the input + * array. + * + * @param monotonicSegments The in/out array of SegmentBuffer structures that will be + * filled with the monotonic parts. The monotonicSegments array + * must contain at least 3 elements. + * @return The number of monotonic parts if the split had happened. Returns + * 0 if the segment is already monotonic. + */ + abstract int getYMonotonicParts(SegmentBuffer[] monotonicSegments); + + /** + * Calculates intersection points of this segment with an infinite line, + * parallel to one of the axes. + * + * @param bAxisX TRUE if the function works with the line parallel to the axis + * X. + * @param ordinate The ordinate value of the line (x for axis Y, y for axis X). + * @param resultOrdinates The value of ordinate in the intersection points One ordinate + * is equal to the ordinate parameter. This parameter can be + * NULL. + * @param parameters The value of the parameter in the intersection points (between + * 0 and 1). This parameter can be NULL. + * @return The number of intersection points, 0 when no intersection points + * exist, -1 when the segment coincides with the line (infinite + * number of intersection points). + */ + public abstract int intersectionWithAxis2D(boolean bAxisX, double ordinate, + double[] resultOrdinates, double[] parameters); + + void _reverseImpl() { + } + + /** + * Returns True if the segment is degenerate to a point with relation to the + * given tolerance. For Lines this means the line length is not longer than + * the tolerance. For the curves, the distance between the segment endpoints + * should not be longer than the tolerance and the distance from the line, + * connecting the endpoints to the furtherst point on the segment is not + * larger than the tolerance. + */ + abstract boolean isDegenerate(double tolerance); + + // Cpp definitions + + abstract boolean isCurve(); + + abstract Point2D _getTangent(double t); + + abstract boolean _isDegenerate(double tolerance); + + double _calculateSubLength(double t) { + return tToLength(t); + } + + double _calculateSubLength(double t1, double t2) { + return tToLength(t2) - tToLength(t1); + } + + abstract void _copyToImpl(Segment dst); + + /** + * Returns subsegment between parameters t1 and t2. The attributes are + * interpolated along the length of the curve. + */ + public abstract Segment cut(double t1, double t2); + + /** + * Calculates the subsegment between parameters t1 and t2, and stores the + * result in subSegmentBuffer. The attributes are interpolated along the + * length of the curve. + */ + abstract void cut(double t1, double t2, SegmentBuffer subSegmentBuffer); + + /** + * Returns the attribute on the segment for the given parameter value. The + * interpolation of attribute is given by the attribute interpolation type. + */ + public abstract double getAttributeAsDbl(double t, int semantics, + int ordinate); + + abstract boolean _isIntersectingPoint(Point2D pt, double tolerance, + boolean bExcludeExactEndpoints); + + /** + * Calculates intersection point of this segment with an infinite line, + * parallel to axis X. This segment must be to be y-monotonic (or + * horizontal). + * + * @param y The y coordinate of the line. + * @param xParallel For segments, that are horizontal, and have y coordinate, this + * value is returned. + * @return X coordinate of the intersection, or NaN, if no intersection. + */ + abstract double intersectionOfYMonotonicWithAxisX(double y, double xParallel); + + /** + * Converts curves parameter t to the curve length. Can be expensive for curves. + */ + abstract double tToLength(double t); + + abstract double lengthToT(double len); + + public double distance(/* const */Segment otherSegment, + boolean bSegmentsKnownDisjoint) { + // if the segments are not known to be disjoint, and + // the segments are found to touch in any way, then return 0.0 + if (!bSegmentsKnownDisjoint + && _isIntersecting(otherSegment, 0, false) != 0) { + return 0.0; + } + + double minDistance = NumberUtils.doubleMax(); + + Point2D input_point; + double t; + double distance; + + input_point = getStartXY(); + t = otherSegment.getClosestCoordinate(input_point, false); + input_point.sub(otherSegment.getCoord2D(t)); + distance = input_point.length(); + if (distance < minDistance) + minDistance = distance; + + input_point = getEndXY(); + t = otherSegment.getClosestCoordinate(input_point, false); + input_point.sub(otherSegment.getCoord2D(t)); + distance = input_point.length(); + if (distance < minDistance) + minDistance = distance; + + input_point = otherSegment.getStartXY(); + t = getClosestCoordinate(input_point, false); + input_point.sub(getCoord2D(t)); + distance = input_point.length(); + if (distance < minDistance) + minDistance = distance; + + input_point = otherSegment.getEndXY(); + t = getClosestCoordinate(input_point, false); + input_point.sub(getCoord2D(t)); + distance = input_point.length(); + if (distance < minDistance) + minDistance = distance; + + return minDistance; + } + + public Geometry getBoundary() { + return Boundary.calculate(this, null); + } } diff --git a/src/main/java/com/esri/core/geometry/SegmentBuffer.java b/src/main/java/com/esri/core/geometry/SegmentBuffer.java index fbf7a594..b043c3a2 100644 --- a/src/main/java/com/esri/core/geometry/SegmentBuffer.java +++ b/src/main/java/com/esri/core/geometry/SegmentBuffer.java @@ -33,45 +33,45 @@ */ class SegmentBuffer implements Serializable { - private static final long serialVersionUID = 1L; - - Line m_line; - - // PointerOf(Bezier) m_bez; - Segment m_seg; - - public SegmentBuffer() { - m_line = null; - m_seg = null; - } - - public Segment get() { - return m_seg; - } - - public void set(Segment seg) { - m_seg = seg; - if (seg != null) { - if (seg.getType() == Type.Line) { - Line ln = (Line) seg; - m_line = ln; - } - throw GeometryException.GeometryInternalError(); - } - } - - public void create(Geometry.Type type) { - if (type == Geometry.Type.Line) - createLine(); - else - throw new GeometryException("not implemented"); - } - - public void createLine() { - if (null == m_line) { - m_line = new Line(); - - } - m_seg = m_line; - } + private static final long serialVersionUID = 1L; + + Line m_line; + + // PointerOf(Bezier) m_bez; + Segment m_seg; + + public SegmentBuffer() { + m_line = null; + m_seg = null; + } + + public Segment get() { + return m_seg; + } + + public void set(Segment seg) { + m_seg = seg; + if (seg != null) { + if (seg.getType() == Type.Line) { + Line ln = (Line) seg; + m_line = ln; + } + throw GeometryException.GeometryInternalError(); + } + } + + public void create(Geometry.Type type) { + if (type == Geometry.Type.Line) + createLine(); + else + throw new GeometryException("not implemented"); + } + + public void createLine() { + if (null == m_line) { + m_line = new Line(); + + } + m_seg = m_line; + } } diff --git a/src/main/java/com/esri/core/geometry/SegmentFlags.java b/src/main/java/com/esri/core/geometry/SegmentFlags.java index 1722b6c7..d15da3f0 100644 --- a/src/main/java/com/esri/core/geometry/SegmentFlags.java +++ b/src/main/java/com/esri/core/geometry/SegmentFlags.java @@ -25,12 +25,12 @@ package com.esri.core.geometry; interface SegmentFlags { - public static final int enumLineSeg = 1; - public static final int enumBezierSeg = 2; - public static final int enumArcSeg = 4; - public static final int enumNonlinearSegmentMask = 6; - public static final int enumSegmentMask = 7; - public static final int enumDensified = 8; // set for segments that have - // been produced from a - // densified non-linear segment. + public static final int enumLineSeg = 1; + public static final int enumBezierSeg = 2; + public static final int enumArcSeg = 4; + public static final int enumNonlinearSegmentMask = 6; + public static final int enumSegmentMask = 7; + public static final int enumDensified = 8; // set for segments that have + // been produced from a + // densified non-linear segment. } diff --git a/src/main/java/com/esri/core/geometry/SegmentIntersector.java b/src/main/java/com/esri/core/geometry/SegmentIntersector.java index a096b4da..e8748a84 100644 --- a/src/main/java/com/esri/core/geometry/SegmentIntersector.java +++ b/src/main/java/com/esri/core/geometry/SegmentIntersector.java @@ -30,414 +30,414 @@ import java.util.ArrayList; class SegmentIntersector { - private static class IntersectionPart { - public Segment seg; - // Weight controls the snapping. When points of the same rank are - // snapped together, - // The new posistion is calculated as a weighted average. - public double weight_start; - public double weight_end; - // The rank controls the snapping. The point with lower rank will be - // snapped to the point with the higher rank. - public int rank_start; // the rank of the start point - public int rank_end; // the rank of the end point - public int rank_interior; // the rank of the interior point - - IntersectionPart(Segment _seg) { - seg = _seg; - weight_start = 1.0; - weight_end = 1.0; - rank_start = 0; - rank_end = 0; - rank_interior = 0; - } - } - - // typedef std::shared_ptr segment_buffer_sptr; - // typedef std::shared_ptr intersection_part_sptr; - // typedef Dynamic_array intersection_parts; - - private ArrayList m_input_segments; - private ArrayList m_result_segments_1; - private ArrayList m_result_segments_2; - private ArrayList m_recycled_intersection_parts; - private ArrayList m_recycled_segments; - private double[] m_param_1 = new double[15]; - private double[] m_param_2 = new double[15]; - private Point m_point = new Point(); - - private int m_used_recycled_segments; - - private void recycle_(ArrayList parts) { - if (parts == null) - return; - - for (int i = 0, n = (int) parts.size(); i < n; i++) { - recycle_(parts.get(i)); - } - - parts.clear(); - } - - private void recycle_(IntersectionPart part) { - part.seg = null; - m_recycled_intersection_parts.add(part); - } - - private IntersectionPart newIntersectionPart_(Segment _seg) { - if (m_recycled_intersection_parts.isEmpty()) { - IntersectionPart part = new IntersectionPart(_seg); - return part; - } else { - IntersectionPart part = m_recycled_intersection_parts - .get(m_recycled_intersection_parts.size() - 1); - part.seg = _seg; - m_recycled_intersection_parts.remove(m_recycled_intersection_parts - .size() - 1); - return part; - } - } - - private IntersectionPart getResultPart_(int input_segment_index, - int segment_index) { - if (input_segment_index == 0) { - return m_result_segments_1.get(segment_index); - } else { - assert (input_segment_index == 1); - return m_result_segments_2.get(segment_index); - } - } - - private SegmentBuffer newSegmentBuffer_() { - if (m_used_recycled_segments >= m_recycled_segments.size()) { - m_recycled_segments.add(new SegmentBuffer()); - } - - SegmentBuffer p = m_recycled_segments.get(m_used_recycled_segments); - m_used_recycled_segments++; - return p; - } - - private double m_tolerance; - - public SegmentIntersector() { - m_used_recycled_segments = 0; - m_tolerance = 0; - m_input_segments = new ArrayList(); - m_result_segments_1 = new ArrayList(); - m_result_segments_2 = new ArrayList(); - m_recycled_intersection_parts = new ArrayList(); - m_recycled_segments = new ArrayList(); - } - - // Clears the results and input segments - public void clear() { - recycle_(m_input_segments); - recycle_(m_result_segments_1); - recycle_(m_result_segments_2); - m_used_recycled_segments = 0; - } - - // Adds a segment to intersect and returns an index of the segment. - // Two segments has to be pushed for the intersect method to succeed. - public int pushSegment(Segment seg) { - assert (m_input_segments.size() < 2); - m_input_segments.add(newIntersectionPart_(seg)); - // m_param_1.resize(15); - // m_param_2.resize(15); - return (int) m_input_segments.size() - 1; - } - - public void setRankAndWeight(int input_segment_index, double start_weight, - int start_rank, double end_weight, int end_rank, int interior_rank) { - IntersectionPart part = m_input_segments.get(input_segment_index); - part.rank_end = end_rank; - part.weight_start = start_weight; - part.weight_end = end_weight; - part.rank_start = start_rank; - part.rank_end = end_rank; - part.rank_interior = interior_rank; - } - - // Returns the number of segments the input segment has been split to. - public int getResultSegmentCount(int input_segment_index) { - if (input_segment_index == 0) { - return (int) m_result_segments_1.size(); - } else { - assert (input_segment_index == 1); - return (int) m_result_segments_2.size(); - } - } - - // Returns a part of the input segment that is the result of the - // intersection with another segment. - // input_segment_index is the index of the input segment. - // segment_index is between 0 and - // get_result_segment_count(input_segment_index) - 1 - public Segment getResultSegment(int input_segment_index, int segment_index) { - return getResultPart_(input_segment_index, segment_index).seg; - } - - // double get_result_segment_start_point_weight(int input_segment_index, int - // segment_index); - // int get_result_segment_start_point_rank(int input_segment_index, int - // segment_index); - // double get_result_segment_end_point_weight(int input_segment_index, int - // segment_index); - // int get_result_segment_end_point_rank(int input_segment_index, int - // segment_index); - // int get_result_segment_interior_rank(int input_segment_index, int - // segment_index); - public Point getResultPoint() { - return m_point; - } - - // Performs the intersection - public boolean intersect(double tolerance, boolean b_intersecting) { - if (m_input_segments.size() != 2) - throw GeometryException.GeometryInternalError(); - - m_tolerance = tolerance; - double small_tolerance_sqr = MathUtils.sqr(tolerance * 0.01); - boolean bigmove = false; - - IntersectionPart part1 = m_input_segments.get(0); - IntersectionPart part2 = m_input_segments.get(1); - if (b_intersecting - || (part1.seg._isIntersecting(part2.seg, tolerance, true) & 5) != 0) { - if (part1.seg.getType().value() == Geometry.GeometryType.Line) { - Line line_1 = (Line) part1.seg; - if (part2.seg.getType().value() == Geometry.GeometryType.Line) { - Line line_2 = (Line) part2.seg; - int count = Line._intersectLineLine(line_1, line_2, null, - m_param_1, m_param_2, tolerance); - if (count == 0) { - assert (count > 0); - throw GeometryException.GeometryInternalError(); - } - Point2D[] points = new Point2D[9]; - for (int i = 0; i < count; i++) { - // For each point of intersection, we calculate a - // weighted point - // based on the ranks and weights of the endpoints and - // the interior. - double t1 = m_param_1[i]; - double t2 = m_param_2[i]; - int rank1 = part1.rank_interior; - double weight1 = 1.0; - - if (t1 == 0) { - rank1 = part1.rank_start; - weight1 = part1.weight_start; - } else if (t1 == 1.0) { - rank1 = part1.rank_end; - weight1 = part1.weight_end; - } - - int rank2 = part2.rank_interior; - double weight2 = 1.0; - if (t2 == 0) { - rank2 = part2.rank_start; - weight2 = part2.weight_start; - } else if (t2 == 1.0) { - rank2 = part2.rank_end; - weight2 = part2.weight_end; - } - - double ptWeight; - - Point2D pt = new Point2D(); - if (rank1 == rank2) {// for equal ranks use weighted sum - Point2D pt_1 = new Point2D(); - line_1.getCoord2D(t1, pt_1); - Point2D pt_2 = new Point2D(); - line_2.getCoord2D(t2, pt_2); - ptWeight = weight1 + weight2; - double t = weight2 / ptWeight; - MathUtils.lerp(pt_1, pt_2, t, pt); - if (Point2D.sqrDistance(pt, pt_1) - + Point2D.sqrDistance(pt, pt_2) > small_tolerance_sqr) - bigmove = true; - - } else {// for non-equal ranks, the higher rank wins - if (rank1 > rank2) { - line_1.getCoord2D(t1, pt); - ptWeight = weight1; - Point2D pt_2 = new Point2D(); - line_2.getCoord2D(t2, pt_2); - if (Point2D.sqrDistance(pt, pt_2) > small_tolerance_sqr) - bigmove = true; - } else { - line_2.getCoord2D(t2, pt); - ptWeight = weight2; - Point2D pt_1 = new Point2D(); - line_1.getCoord2D(t1, pt_1); - if (Point2D.sqrDistance(pt, pt_1) > small_tolerance_sqr) - bigmove = true; - } - } - points[i] = pt; - } - - // Split the line_1, making sure the endpoints are adusted - // to the weighted - double t0 = 0; - int i0 = -1; - for (int i = 0; i <= count; i++) { - double t = i < count ? m_param_1[i] : 1.0; - if (t != t0) { - SegmentBuffer seg_buffer = newSegmentBuffer_(); - line_1.cut(t0, t, seg_buffer); - if (i0 != -1) - seg_buffer.get().setStartXY(points[i0]); - if (i != count) - seg_buffer.get().setEndXY(points[i]); - - t0 = t; - m_result_segments_1 - .add(newIntersectionPart_(seg_buffer.get())); - } - i0 = i; - } - - int[] indices = new int[9]; - for (int i = 0; i < count; i++) - indices[i] = i; - - if (count > 1) { - if (m_param_2[0] > m_param_2[1]) { - double t = m_param_2[0]; - m_param_2[0] = m_param_2[1]; - m_param_2[1] = t; - int i = indices[0]; - indices[0] = indices[1]; - indices[1] = i; - } - } - - // Split the line_2 - t0 = 0; - i0 = -1; - for (int i = 0; i <= count; i++) { - double t = i < count ? m_param_2[i] : 1.0; - if (t != t0) { - SegmentBuffer seg_buffer = newSegmentBuffer_(); - line_2.cut(t0, t, seg_buffer); - if (i0 != -1) { - int ind = indices[i0]; - seg_buffer.get().setStartXY(points[ind]); - } - if (i != count) { - int ind = indices[i]; - seg_buffer.get().setEndXY(points[ind]); - } - - t0 = t; - m_result_segments_2 - .add(newIntersectionPart_(seg_buffer.get())); - } - i0 = i; - } - - return bigmove; - } - - throw GeometryException.GeometryInternalError(); - } - - throw GeometryException.GeometryInternalError(); - } - - return false; - } - - public void intersect(double tolerance, Point pt_intersector_point, - int point_rank, double point_weight, boolean b_intersecting) { - pt_intersector_point.copyTo(m_point); - if (m_input_segments.size() != 1) - throw GeometryException.GeometryInternalError(); - - m_tolerance = tolerance; - - IntersectionPart part1 = m_input_segments.get(0); - if (b_intersecting - || part1.seg._isIntersectingPoint(pt_intersector_point.getXY(), - tolerance, true)) { - if (part1.seg.getType().value() == Geometry.GeometryType.Line) { - Line line_1 = (Line) (part1.seg); - double t1 = line_1.getClosestCoordinate( - pt_intersector_point.getXY(), false); - m_param_1[0] = t1; - // For each point of intersection, we calculate a weighted point - // based on the ranks and weights of the endpoints and the - // interior. - int rank1 = part1.rank_interior; - double weight1 = 1.0; - - if (t1 == 0) { - rank1 = part1.rank_start; - weight1 = part1.weight_start; - } else if (t1 == 1.0) { - rank1 = part1.rank_end; - weight1 = part1.weight_end; - } - - int rank2 = point_rank; - double weight2 = point_weight; - - double ptWeight; - - Point2D pt = new Point2D(); - if (rank1 == rank2) {// for equal ranks use weighted sum - Point2D pt_1 = new Point2D(); - line_1.getCoord2D(t1, pt_1); - Point2D pt_2 = pt_intersector_point.getXY(); - ptWeight = weight1 + weight2; - double t = weight2 / ptWeight; - MathUtils.lerp(pt_1, pt_2, t, pt); - } else {// for non-equal ranks, the higher rank wins - if (rank1 > rank2) { - pt = new Point2D(); - line_1.getCoord2D(t1, pt); - ptWeight = weight1; - } else { - pt = pt_intersector_point.getXY(); - ptWeight = weight2; - } - } - - // Split the line_1, making sure the endpoints are adusted to - // the weighted - double t0 = 0; - int i0 = -1; - int count = 1; - for (int i = 0; i <= count; i++) { - double t = i < count ? m_param_1[i] : 1.0; - if (t != t0) { - SegmentBuffer seg_buffer = newSegmentBuffer_(); - line_1.cut(t0, t, seg_buffer); - if (i0 != -1) - seg_buffer.get().setStartXY(pt); - if (i != count) - seg_buffer.get().setEndXY(pt); - - t0 = t; - m_result_segments_1.add(newIntersectionPart_(seg_buffer - .get())); - } - i0 = i; - } - - m_point.setXY(pt); - - return; - } - - throw GeometryException.GeometryInternalError(); - } - } - - public double get_tolerance_() { - return m_tolerance; - } + private static class IntersectionPart { + public Segment seg; + // Weight controls the snapping. When points of the same rank are + // snapped together, + // The new posistion is calculated as a weighted average. + public double weight_start; + public double weight_end; + // The rank controls the snapping. The point with lower rank will be + // snapped to the point with the higher rank. + public int rank_start; // the rank of the start point + public int rank_end; // the rank of the end point + public int rank_interior; // the rank of the interior point + + IntersectionPart(Segment _seg) { + seg = _seg; + weight_start = 1.0; + weight_end = 1.0; + rank_start = 0; + rank_end = 0; + rank_interior = 0; + } + } + + // typedef std::shared_ptr segment_buffer_sptr; + // typedef std::shared_ptr intersection_part_sptr; + // typedef Dynamic_array intersection_parts; + + private ArrayList m_input_segments; + private ArrayList m_result_segments_1; + private ArrayList m_result_segments_2; + private ArrayList m_recycled_intersection_parts; + private ArrayList m_recycled_segments; + private double[] m_param_1 = new double[15]; + private double[] m_param_2 = new double[15]; + private Point m_point = new Point(); + + private int m_used_recycled_segments; + + private void recycle_(ArrayList parts) { + if (parts == null) + return; + + for (int i = 0, n = (int) parts.size(); i < n; i++) { + recycle_(parts.get(i)); + } + + parts.clear(); + } + + private void recycle_(IntersectionPart part) { + part.seg = null; + m_recycled_intersection_parts.add(part); + } + + private IntersectionPart newIntersectionPart_(Segment _seg) { + if (m_recycled_intersection_parts.isEmpty()) { + IntersectionPart part = new IntersectionPart(_seg); + return part; + } else { + IntersectionPart part = m_recycled_intersection_parts + .get(m_recycled_intersection_parts.size() - 1); + part.seg = _seg; + m_recycled_intersection_parts.remove(m_recycled_intersection_parts + .size() - 1); + return part; + } + } + + private IntersectionPart getResultPart_(int input_segment_index, + int segment_index) { + if (input_segment_index == 0) { + return m_result_segments_1.get(segment_index); + } else { + assert (input_segment_index == 1); + return m_result_segments_2.get(segment_index); + } + } + + private SegmentBuffer newSegmentBuffer_() { + if (m_used_recycled_segments >= m_recycled_segments.size()) { + m_recycled_segments.add(new SegmentBuffer()); + } + + SegmentBuffer p = m_recycled_segments.get(m_used_recycled_segments); + m_used_recycled_segments++; + return p; + } + + private double m_tolerance; + + public SegmentIntersector() { + m_used_recycled_segments = 0; + m_tolerance = 0; + m_input_segments = new ArrayList(); + m_result_segments_1 = new ArrayList(); + m_result_segments_2 = new ArrayList(); + m_recycled_intersection_parts = new ArrayList(); + m_recycled_segments = new ArrayList(); + } + + // Clears the results and input segments + public void clear() { + recycle_(m_input_segments); + recycle_(m_result_segments_1); + recycle_(m_result_segments_2); + m_used_recycled_segments = 0; + } + + // Adds a segment to intersect and returns an index of the segment. + // Two segments has to be pushed for the intersect method to succeed. + public int pushSegment(Segment seg) { + assert (m_input_segments.size() < 2); + m_input_segments.add(newIntersectionPart_(seg)); + // m_param_1.resize(15); + // m_param_2.resize(15); + return (int) m_input_segments.size() - 1; + } + + public void setRankAndWeight(int input_segment_index, double start_weight, + int start_rank, double end_weight, int end_rank, int interior_rank) { + IntersectionPart part = m_input_segments.get(input_segment_index); + part.rank_end = end_rank; + part.weight_start = start_weight; + part.weight_end = end_weight; + part.rank_start = start_rank; + part.rank_end = end_rank; + part.rank_interior = interior_rank; + } + + // Returns the number of segments the input segment has been split to. + public int getResultSegmentCount(int input_segment_index) { + if (input_segment_index == 0) { + return (int) m_result_segments_1.size(); + } else { + assert (input_segment_index == 1); + return (int) m_result_segments_2.size(); + } + } + + // Returns a part of the input segment that is the result of the + // intersection with another segment. + // input_segment_index is the index of the input segment. + // segment_index is between 0 and + // get_result_segment_count(input_segment_index) - 1 + public Segment getResultSegment(int input_segment_index, int segment_index) { + return getResultPart_(input_segment_index, segment_index).seg; + } + + // double get_result_segment_start_point_weight(int input_segment_index, int + // segment_index); + // int get_result_segment_start_point_rank(int input_segment_index, int + // segment_index); + // double get_result_segment_end_point_weight(int input_segment_index, int + // segment_index); + // int get_result_segment_end_point_rank(int input_segment_index, int + // segment_index); + // int get_result_segment_interior_rank(int input_segment_index, int + // segment_index); + public Point getResultPoint() { + return m_point; + } + + // Performs the intersection + public boolean intersect(double tolerance, boolean b_intersecting) { + if (m_input_segments.size() != 2) + throw GeometryException.GeometryInternalError(); + + m_tolerance = tolerance; + double small_tolerance_sqr = MathUtils.sqr(tolerance * 0.01); + boolean bigmove = false; + + IntersectionPart part1 = m_input_segments.get(0); + IntersectionPart part2 = m_input_segments.get(1); + if (b_intersecting + || (part1.seg._isIntersecting(part2.seg, tolerance, true) & 5) != 0) { + if (part1.seg.getType().value() == Geometry.GeometryType.Line) { + Line line_1 = (Line) part1.seg; + if (part2.seg.getType().value() == Geometry.GeometryType.Line) { + Line line_2 = (Line) part2.seg; + int count = Line._intersectLineLine(line_1, line_2, null, + m_param_1, m_param_2, tolerance); + if (count == 0) { + assert (count > 0); + throw GeometryException.GeometryInternalError(); + } + Point2D[] points = new Point2D[9]; + for (int i = 0; i < count; i++) { + // For each point of intersection, we calculate a + // weighted point + // based on the ranks and weights of the endpoints and + // the interior. + double t1 = m_param_1[i]; + double t2 = m_param_2[i]; + int rank1 = part1.rank_interior; + double weight1 = 1.0; + + if (t1 == 0) { + rank1 = part1.rank_start; + weight1 = part1.weight_start; + } else if (t1 == 1.0) { + rank1 = part1.rank_end; + weight1 = part1.weight_end; + } + + int rank2 = part2.rank_interior; + double weight2 = 1.0; + if (t2 == 0) { + rank2 = part2.rank_start; + weight2 = part2.weight_start; + } else if (t2 == 1.0) { + rank2 = part2.rank_end; + weight2 = part2.weight_end; + } + + double ptWeight; + + Point2D pt = new Point2D(); + if (rank1 == rank2) {// for equal ranks use weighted sum + Point2D pt_1 = new Point2D(); + line_1.getCoord2D(t1, pt_1); + Point2D pt_2 = new Point2D(); + line_2.getCoord2D(t2, pt_2); + ptWeight = weight1 + weight2; + double t = weight2 / ptWeight; + MathUtils.lerp(pt_1, pt_2, t, pt); + if (Point2D.sqrDistance(pt, pt_1) + + Point2D.sqrDistance(pt, pt_2) > small_tolerance_sqr) + bigmove = true; + + } else {// for non-equal ranks, the higher rank wins + if (rank1 > rank2) { + line_1.getCoord2D(t1, pt); + ptWeight = weight1; + Point2D pt_2 = new Point2D(); + line_2.getCoord2D(t2, pt_2); + if (Point2D.sqrDistance(pt, pt_2) > small_tolerance_sqr) + bigmove = true; + } else { + line_2.getCoord2D(t2, pt); + ptWeight = weight2; + Point2D pt_1 = new Point2D(); + line_1.getCoord2D(t1, pt_1); + if (Point2D.sqrDistance(pt, pt_1) > small_tolerance_sqr) + bigmove = true; + } + } + points[i] = pt; + } + + // Split the line_1, making sure the endpoints are adusted + // to the weighted + double t0 = 0; + int i0 = -1; + for (int i = 0; i <= count; i++) { + double t = i < count ? m_param_1[i] : 1.0; + if (t != t0) { + SegmentBuffer seg_buffer = newSegmentBuffer_(); + line_1.cut(t0, t, seg_buffer); + if (i0 != -1) + seg_buffer.get().setStartXY(points[i0]); + if (i != count) + seg_buffer.get().setEndXY(points[i]); + + t0 = t; + m_result_segments_1 + .add(newIntersectionPart_(seg_buffer.get())); + } + i0 = i; + } + + int[] indices = new int[9]; + for (int i = 0; i < count; i++) + indices[i] = i; + + if (count > 1) { + if (m_param_2[0] > m_param_2[1]) { + double t = m_param_2[0]; + m_param_2[0] = m_param_2[1]; + m_param_2[1] = t; + int i = indices[0]; + indices[0] = indices[1]; + indices[1] = i; + } + } + + // Split the line_2 + t0 = 0; + i0 = -1; + for (int i = 0; i <= count; i++) { + double t = i < count ? m_param_2[i] : 1.0; + if (t != t0) { + SegmentBuffer seg_buffer = newSegmentBuffer_(); + line_2.cut(t0, t, seg_buffer); + if (i0 != -1) { + int ind = indices[i0]; + seg_buffer.get().setStartXY(points[ind]); + } + if (i != count) { + int ind = indices[i]; + seg_buffer.get().setEndXY(points[ind]); + } + + t0 = t; + m_result_segments_2 + .add(newIntersectionPart_(seg_buffer.get())); + } + i0 = i; + } + + return bigmove; + } + + throw GeometryException.GeometryInternalError(); + } + + throw GeometryException.GeometryInternalError(); + } + + return false; + } + + public void intersect(double tolerance, Point pt_intersector_point, + int point_rank, double point_weight, boolean b_intersecting) { + pt_intersector_point.copyTo(m_point); + if (m_input_segments.size() != 1) + throw GeometryException.GeometryInternalError(); + + m_tolerance = tolerance; + + IntersectionPart part1 = m_input_segments.get(0); + if (b_intersecting + || part1.seg._isIntersectingPoint(pt_intersector_point.getXY(), + tolerance, true)) { + if (part1.seg.getType().value() == Geometry.GeometryType.Line) { + Line line_1 = (Line) (part1.seg); + double t1 = line_1.getClosestCoordinate( + pt_intersector_point.getXY(), false); + m_param_1[0] = t1; + // For each point of intersection, we calculate a weighted point + // based on the ranks and weights of the endpoints and the + // interior. + int rank1 = part1.rank_interior; + double weight1 = 1.0; + + if (t1 == 0) { + rank1 = part1.rank_start; + weight1 = part1.weight_start; + } else if (t1 == 1.0) { + rank1 = part1.rank_end; + weight1 = part1.weight_end; + } + + int rank2 = point_rank; + double weight2 = point_weight; + + double ptWeight; + + Point2D pt = new Point2D(); + if (rank1 == rank2) {// for equal ranks use weighted sum + Point2D pt_1 = new Point2D(); + line_1.getCoord2D(t1, pt_1); + Point2D pt_2 = pt_intersector_point.getXY(); + ptWeight = weight1 + weight2; + double t = weight2 / ptWeight; + MathUtils.lerp(pt_1, pt_2, t, pt); + } else {// for non-equal ranks, the higher rank wins + if (rank1 > rank2) { + pt = new Point2D(); + line_1.getCoord2D(t1, pt); + ptWeight = weight1; + } else { + pt = pt_intersector_point.getXY(); + ptWeight = weight2; + } + } + + // Split the line_1, making sure the endpoints are adusted to + // the weighted + double t0 = 0; + int i0 = -1; + int count = 1; + for (int i = 0; i <= count; i++) { + double t = i < count ? m_param_1[i] : 1.0; + if (t != t0) { + SegmentBuffer seg_buffer = newSegmentBuffer_(); + line_1.cut(t0, t, seg_buffer); + if (i0 != -1) + seg_buffer.get().setStartXY(pt); + if (i != count) + seg_buffer.get().setEndXY(pt); + + t0 = t; + m_result_segments_1.add(newIntersectionPart_(seg_buffer + .get())); + } + i0 = i; + } + + m_point.setXY(pt); + + return; + } + + throw GeometryException.GeometryInternalError(); + } + } + + public double get_tolerance_() { + return m_tolerance; + } } diff --git a/src/main/java/com/esri/core/geometry/SegmentIterator.java b/src/main/java/com/esri/core/geometry/SegmentIterator.java index 8cb3abcb..b2f7a430 100644 --- a/src/main/java/com/esri/core/geometry/SegmentIterator.java +++ b/src/main/java/com/esri/core/geometry/SegmentIterator.java @@ -38,189 +38,189 @@ * */ public class SegmentIterator { - private SegmentIteratorImpl m_impl; - - SegmentIterator(Object obj) { - m_impl = (SegmentIteratorImpl) obj; - } - - /** - * Moves the iterator to the next path. Returns the TRUE if successful. - * - * @return TRUE if the next path exists. - */ - public boolean nextPath() { - return m_impl.nextPath(); - } - - /** - * Moves the iterator to the previous path. Returns the TRUE if successful. - * - * @return TRUE if the previous path exists. - */ - public boolean previousPath() { - return m_impl.previousPath(); - } - - /** - * Resets the iterator such that a subsequent call to NextPath will set the - * iterator to the first path. - */ - public void resetToFirstPath() { - m_impl.resetToFirstPath(); - } - - /** - * Resets the iterator such that a subsequent call to PreviousPath will set - * the iterator to the last path. - */ - public void resetToLastPath() { - m_impl.resetToLastPath(); - } - - /** - * Resets the iterator such that a subsequent call to NextPath will set the - * iterator to the given path index. A call to PreviousPath will set the - * iterator to the path at pathIndex - 1. - */ - public void resetToPath(int pathIndex) { - m_impl.resetToPath(pathIndex); - } - - /** - * Indicates whether the iterator points to the first segment in the current - * path. - * - * @return TRUE if the iterator points to the first segment in the current - * path. - */ - public boolean isFirstSegmentInPath() { - return m_impl.isFirstSegmentInPath(); - } - - /** - * Indicates whether the iterator points to the last segment in the current - * path. - * - * @return TRUE if the iterator points to the last segment in the current - * path. - */ - public boolean isLastSegmentInPath() { - return m_impl.isLastSegmentInPath(); - } - - /** - * Resets the iterator so that the call to NextSegment will return the first - * segment of the current path. - */ - public void resetToFirstSegment() { - m_impl.resetToFirstSegment(); - } - - /** - * Resets the iterator so that the call to PreviousSegment will return the - * last segment of the current path. - */ - public void resetToLastSegment() { - m_impl.resetToLastSegment(); - } - - /** - * Resets the iterator to a specific vertex. - * The call to next_segment will return the segment that starts at the vertex. - * Call to previous_segment will return the segment that starts at the previous vertex. - * - * @param vertexIndex The vertex index to reset the iterator to. - * @param pathIndex The path index to reset the iterator to. Used as a hint. If the path_index is wrong or -1, then the path_index is automatically calculated. - */ - public void resetToVertex(int vertexIndex, int pathIndex) { - m_impl.resetToVertex(vertexIndex, pathIndex); - } - - /** - * Indicates whether a next segment exists for the path. - * - * @return TRUE is the next segment exists. - */ - public boolean hasNextSegment() { - return m_impl.hasNextSegment(); - } - - /** - * Indicates whether a previous segment exists in the path. - * - * @return TRUE if the previous segment exists. - */ - public boolean hasPreviousSegment() { - return m_impl.hasPreviousSegment(); - } - - /** - * Moves the iterator to the next segment and returns the segment. - *

- * The Segment is returned by value and is owned by the iterator. - */ - public Segment nextSegment() { - return m_impl.nextSegment(); - } - - /** - * Moves the iterator to previous segment and returns the segment. - *

- * The Segment is returned by value and is owned by the iterator. - */ - public Segment previousSegment() { - return m_impl.previousSegment(); - } - - /** - * Returns the index of the current path. - */ - public int getPathIndex() { - return m_impl.getPathIndex(); - } - - /** - * Returns the index of the start point of this segment. - */ - public int getStartPointIndex() { - return m_impl.getStartPointIndex(); - } - - /** - * Returns the index of the end point of the current segment. - */ - public int getEndPointIndex() { - return m_impl.getEndPointIndex(); - } - - /** - * Returns TRUE, if the segment is the closing segment of the closed path - */ - public boolean isClosingSegment() { - return m_impl.isClosingSegment(); - } - - /** - * Switches the iterator to navigation mode. - * - * @param bYesNo If TRUE, the iterator loops over the current path infinitely - * (unless the multipath is empty). - */ - public void setCirculator(boolean bYesNo) { - m_impl.setCirculator(bYesNo); - } - - /** - * Copies this SegmentIterator. - * - * @return SegmentIterator. - */ - public Object copy() { - return new SegmentIterator(m_impl.copy()); - } - - protected Object _getImpl() { - return (SegmentIteratorImpl) m_impl; - } + private SegmentIteratorImpl m_impl; + + SegmentIterator(Object obj) { + m_impl = (SegmentIteratorImpl) obj; + } + + /** + * Moves the iterator to the next path. Returns the TRUE if successful. + * + * @return TRUE if the next path exists. + */ + public boolean nextPath() { + return m_impl.nextPath(); + } + + /** + * Moves the iterator to the previous path. Returns the TRUE if successful. + * + * @return TRUE if the previous path exists. + */ + public boolean previousPath() { + return m_impl.previousPath(); + } + + /** + * Resets the iterator such that a subsequent call to NextPath will set the + * iterator to the first path. + */ + public void resetToFirstPath() { + m_impl.resetToFirstPath(); + } + + /** + * Resets the iterator such that a subsequent call to PreviousPath will set + * the iterator to the last path. + */ + public void resetToLastPath() { + m_impl.resetToLastPath(); + } + + /** + * Resets the iterator such that a subsequent call to NextPath will set the + * iterator to the given path index. A call to PreviousPath will set the + * iterator to the path at pathIndex - 1. + */ + public void resetToPath(int pathIndex) { + m_impl.resetToPath(pathIndex); + } + + /** + * Indicates whether the iterator points to the first segment in the current + * path. + * + * @return TRUE if the iterator points to the first segment in the current + * path. + */ + public boolean isFirstSegmentInPath() { + return m_impl.isFirstSegmentInPath(); + } + + /** + * Indicates whether the iterator points to the last segment in the current + * path. + * + * @return TRUE if the iterator points to the last segment in the current + * path. + */ + public boolean isLastSegmentInPath() { + return m_impl.isLastSegmentInPath(); + } + + /** + * Resets the iterator so that the call to NextSegment will return the first + * segment of the current path. + */ + public void resetToFirstSegment() { + m_impl.resetToFirstSegment(); + } + + /** + * Resets the iterator so that the call to PreviousSegment will return the + * last segment of the current path. + */ + public void resetToLastSegment() { + m_impl.resetToLastSegment(); + } + + /** + * Resets the iterator to a specific vertex. + * The call to next_segment will return the segment that starts at the vertex. + * Call to previous_segment will return the segment that starts at the previous vertex. + * + * @param vertexIndex The vertex index to reset the iterator to. + * @param pathIndex The path index to reset the iterator to. Used as a hint. If the path_index is wrong or -1, then the path_index is automatically calculated. + */ + public void resetToVertex(int vertexIndex, int pathIndex) { + m_impl.resetToVertex(vertexIndex, pathIndex); + } + + /** + * Indicates whether a next segment exists for the path. + * + * @return TRUE is the next segment exists. + */ + public boolean hasNextSegment() { + return m_impl.hasNextSegment(); + } + + /** + * Indicates whether a previous segment exists in the path. + * + * @return TRUE if the previous segment exists. + */ + public boolean hasPreviousSegment() { + return m_impl.hasPreviousSegment(); + } + + /** + * Moves the iterator to the next segment and returns the segment. + *

+ * The Segment is returned by value and is owned by the iterator. + */ + public Segment nextSegment() { + return m_impl.nextSegment(); + } + + /** + * Moves the iterator to previous segment and returns the segment. + *

+ * The Segment is returned by value and is owned by the iterator. + */ + public Segment previousSegment() { + return m_impl.previousSegment(); + } + + /** + * Returns the index of the current path. + */ + public int getPathIndex() { + return m_impl.getPathIndex(); + } + + /** + * Returns the index of the start point of this segment. + */ + public int getStartPointIndex() { + return m_impl.getStartPointIndex(); + } + + /** + * Returns the index of the end point of the current segment. + */ + public int getEndPointIndex() { + return m_impl.getEndPointIndex(); + } + + /** + * Returns TRUE, if the segment is the closing segment of the closed path + */ + public boolean isClosingSegment() { + return m_impl.isClosingSegment(); + } + + /** + * Switches the iterator to navigation mode. + * + * @param bYesNo If TRUE, the iterator loops over the current path infinitely + * (unless the multipath is empty). + */ + public void setCirculator(boolean bYesNo) { + m_impl.setCirculator(bYesNo); + } + + /** + * Copies this SegmentIterator. + * + * @return SegmentIterator. + */ + public Object copy() { + return new SegmentIterator(m_impl.copy()); + } + + protected Object _getImpl() { + return (SegmentIteratorImpl) m_impl; + } } diff --git a/src/main/java/com/esri/core/geometry/SegmentIteratorImpl.java b/src/main/java/com/esri/core/geometry/SegmentIteratorImpl.java index 4b112335..d4580770 100644 --- a/src/main/java/com/esri/core/geometry/SegmentIteratorImpl.java +++ b/src/main/java/com/esri/core/geometry/SegmentIteratorImpl.java @@ -30,446 +30,446 @@ */ final class SegmentIteratorImpl { - protected Line m_line; - - // Bezier m_bezier: - // Arc m_arc; - protected Segment m_currentSegment; - protected Point2D m_dummyPoint; - - protected int m_currentPathIndex; - - protected int m_nextPathIndex; - - protected int m_prevPathIndex; - - protected int m_currentSegmentIndex; - - protected int m_nextSegmentIndex; - - protected int m_prevSegmentIndex; - - protected int m_segmentCount; - - protected int m_pathBegin; - - protected MultiPathImpl m_parent; // parent of the iterator. - - protected boolean m_bCirculator; // If true, the iterator circulates around - // the current Path. - - protected boolean m_bNeedsUpdate; - - public SegmentIteratorImpl(MultiPathImpl parent) { - m_currentSegmentIndex = -1; - m_nextSegmentIndex = 0; - m_nextPathIndex = 0; - m_currentPathIndex = -1; - m_parent = parent; - m_segmentCount = _getSegmentCount(m_nextPathIndex); - m_bCirculator = false; - m_currentSegment = null; - m_pathBegin = -1; - m_dummyPoint = new Point2D(); - } - - public SegmentIteratorImpl(MultiPathImpl parent, int pointIndex) { - if (pointIndex < 0 || pointIndex >= parent.getPointCount()) - throw new IndexOutOfBoundsException(); - - m_currentSegmentIndex = -1; - int path = parent.getPathIndexFromPointIndex(pointIndex); - m_nextSegmentIndex = pointIndex - parent.getPathStart(path); - - m_nextPathIndex = path + 1; - m_currentPathIndex = path; - m_parent = parent; - m_segmentCount = _getSegmentCount(m_currentPathIndex); - m_bCirculator = false; - m_currentSegment = null; - m_pathBegin = m_parent.getPathStart(m_currentPathIndex); - m_dummyPoint = new Point2D(); - } - - public SegmentIteratorImpl(MultiPathImpl parent, int pathIndex, - int segmentIndex) { - if (pathIndex < 0 || pathIndex >= parent.getPathCount() - || segmentIndex < 0) - throw new IndexOutOfBoundsException(); - - int d = parent.isClosedPath(pathIndex) ? 0 : 1; - if (segmentIndex >= parent.getPathSize(pathIndex) - d) - throw new IndexOutOfBoundsException(); - - m_currentSegmentIndex = -1; - m_nextSegmentIndex = segmentIndex; - m_currentPathIndex = pathIndex; - m_nextPathIndex = m_nextSegmentIndex + 1; - m_parent = parent; - m_segmentCount = _getSegmentCount(m_nextPathIndex); - m_bCirculator = false; - m_currentSegment = null; - m_pathBegin = m_parent.getPathStart(m_currentPathIndex); - m_dummyPoint = new Point2D(); - } - - void resetTo(SegmentIteratorImpl src) { - if (m_parent != src.m_parent) - throw new GeometryException("invalid_call"); - - m_currentSegmentIndex = src.m_currentSegmentIndex; - m_nextSegmentIndex = src.m_nextSegmentIndex; - m_currentPathIndex = src.m_currentPathIndex; - m_nextPathIndex = src.m_nextPathIndex; - m_segmentCount = src.m_segmentCount; - m_bCirculator = src.m_bCirculator; - m_pathBegin = src.m_pathBegin; - m_currentSegment = null; - } - - /** - * Moves the iterator to the next curve segment and returns the segment. - *

- * The Segment is returned by value and is owned by the iterator. Note: The - * method can return null if there are no curves in the part. - */ - public Segment nextCurve() { - return null; - // TODO: Fix me. This method is supposed to go only through the curves - // and skip the Line classes!! - // It must be very efficient. - } - - /** - * Moves the iterator to next segment and returns the segment. - *

- * The Segment is returned by value and is owned by the iterator. - */ - public Segment nextSegment() { - if (m_currentSegmentIndex != m_nextSegmentIndex) - _updateSegment(); - - if (m_bCirculator) { - m_nextSegmentIndex = (m_nextSegmentIndex + 1) % m_segmentCount; - } else { - if (m_nextSegmentIndex == m_segmentCount) - throw new IndexOutOfBoundsException(); - - m_nextSegmentIndex++; - } - - return m_currentSegment; - } - - /** - * Moves the iterator to previous segment and returns the segment. - *

- * The Segment is returned by value and is owned by the iterator. - */ - public Segment previousSegment() { - if (m_bCirculator) { - m_nextSegmentIndex = (m_segmentCount + m_nextSegmentIndex - 1) - % m_segmentCount; - } else { - if (m_nextSegmentIndex == 0) - throw new IndexOutOfBoundsException(); - m_nextSegmentIndex--; - } - - if (m_nextSegmentIndex != m_currentSegmentIndex) - _updateSegment(); - - return m_currentSegment; - } - - /** - * Resets the iterator so that the call to NextSegment will return the first - * segment of the current path. - */ - public void resetToFirstSegment() { - m_currentSegmentIndex = -1; - m_nextSegmentIndex = 0; - } - - /** - * Resets the iterator so that the call to PreviousSegment will return the - * last segment of the current path. - */ - public void resetToLastSegment() { - m_nextSegmentIndex = m_segmentCount; - m_currentSegmentIndex = -1; - } - - public void resetToVertex(int vertexIndex) { - resetToVertex(vertexIndex, -1); - } - - public void resetToVertex(int vertexIndex, int _pathIndex) { - if (m_currentPathIndex >= 0 - && m_currentPathIndex < m_parent.getPathCount()) {// check if we - // are in - // the - // current - // path - int start = _getPathBegin(); - if (vertexIndex >= start - && vertexIndex < m_parent.getPathEnd(m_currentPathIndex)) { - m_currentSegmentIndex = -1; - m_nextSegmentIndex = vertexIndex - start; - return; - } - } - - int path_index; - if (_pathIndex >= 0 && _pathIndex < m_parent.getPathCount() - && vertexIndex >= m_parent.getPathStart(_pathIndex) - && vertexIndex < m_parent.getPathEnd(_pathIndex)) { - path_index = _pathIndex; - } else { - path_index = m_parent.getPathIndexFromPointIndex(vertexIndex); - } - - m_nextPathIndex = path_index + 1; - m_currentPathIndex = path_index; - m_currentSegmentIndex = -1; - m_nextSegmentIndex = vertexIndex - m_parent.getPathStart(path_index); - m_segmentCount = _getSegmentCount(path_index); - m_pathBegin = m_parent.getPathStart(m_currentPathIndex); - } - - /** - * Moves the iterator to next path and returns true if successful. - */ - public boolean nextPath() { - // post-increment - m_currentPathIndex = m_nextPathIndex; - if (m_currentPathIndex >= m_parent.getPathCount()) - return false; - - m_currentSegmentIndex = -1; - m_nextSegmentIndex = 0; - m_segmentCount = _getSegmentCount(m_currentPathIndex); - m_pathBegin = m_parent.getPathStart(m_currentPathIndex); - m_nextPathIndex++; - return true; - } - - /** - * Moves the iterator to next path and returns true if successful. - */ - public boolean previousPath() { - // pre-decrement - if (m_nextPathIndex == 0) - return false; - - m_nextPathIndex--; - m_currentSegmentIndex = -1; - m_nextSegmentIndex = 0; - m_segmentCount = _getSegmentCount(m_nextPathIndex); - m_currentPathIndex = m_nextPathIndex; - m_pathBegin = m_parent.getPathStart(m_currentPathIndex); - resetToLastSegment(); - return true; - } - - /** - * Resets the iterator such that the subsequent call to the NextPath will - * set the iterator to the first segment of the first path. - */ - public void resetToFirstPath() { - - m_currentSegmentIndex = -1; - m_nextSegmentIndex = -1; - m_segmentCount = -1; - m_nextPathIndex = 0; - m_currentPathIndex = -1; - m_pathBegin = -1; - } - - /** - * Resets the iterator such that the subsequent call to the PreviousPath - * will set the iterator to the last segment of the last path. - */ - public void resetToLastPath() { - m_nextPathIndex = m_parent.getPathCount(); - m_currentPathIndex = -1; - m_currentSegmentIndex = -1; - m_nextSegmentIndex = -1; - m_segmentCount = -1; - m_pathBegin = -1; - } - - /** - * Resets the iterator such that the subsequent call to the NextPath will - * set the iterator to the first segment of the given path. The call to - * PreviousPath will reset the iterator to the last segment of path - * pathIndex - 1. - */ - public void resetToPath(int pathIndex) { - if (pathIndex < 0) - throw new IndexOutOfBoundsException(); - - m_nextPathIndex = pathIndex; - m_currentPathIndex = -1; - m_currentSegmentIndex = -1; - m_nextSegmentIndex = -1; - m_segmentCount = -1; - m_pathBegin = -1; - } - - public int _getSegmentCount(int pathIndex) { - if (m_parent.isEmptyImpl()) - return 0; - - int d = 1; - if (m_parent.isClosedPath(pathIndex)) - d = 0; - - return m_parent.getPathSize(pathIndex) - d; - } - - /** - * Returns True, if the segment is the closing segment of the closed path - */ - public boolean isClosingSegment() { - return m_currentSegmentIndex == m_segmentCount - 1 - && m_parent.isClosedPath(m_currentPathIndex); - } - - /** - * Switches the iterator navigation mode. - * - * @param bYesNo If True, the iterator loops over the current path infinitely - * (unless the MultiPath is empty). - */ - public void setCirculator(boolean bYesNo) { - m_bCirculator = bYesNo; - } - - /** - * Returns the index of the current path. - */ - public int getPathIndex() { - return m_currentPathIndex; - } - - /** - * Returns the index of the start Point of the current Segment. - */ - public int getStartPointIndex() { - return _getPathBegin() + m_currentSegmentIndex; - } - - public int _getPathBegin() { - return m_parent.getPathStart(m_currentPathIndex); - } - - /** - * Returns the index of the end Point of the current Segment. - */ - public int getEndPointIndex() { - if (isClosingSegment()) { - return m_parent.getPathStart(m_currentPathIndex); - } else { - return getStartPointIndex() + 1; - } - } - - /** - * Returns True if the segment is first one in the current Path. - */ - public boolean isFirstSegmentInPath() { - return m_currentSegmentIndex == 0; - } - - /** - * Returns True if the segment is last one in the current Path. - */ - public boolean isLastSegmentInPath() { - return m_currentSegmentIndex == m_segmentCount - 1; - } - - /** - * Returns True if the call to the NextSegment will succeed. - */ - public boolean hasNextSegment() { - return m_nextSegmentIndex < m_segmentCount; - } - - /** - * Returns True if the call to the NextSegment will succeed. - */ - public boolean hasPreviousSegment() { - return m_nextSegmentIndex > 0; - } - - public SegmentIteratorImpl copy() { - SegmentIteratorImpl clone = new SegmentIteratorImpl(m_parent); - clone.m_currentSegmentIndex = m_currentSegmentIndex; - clone.m_nextSegmentIndex = m_nextSegmentIndex; - clone.m_segmentCount = m_segmentCount; - clone.m_currentPathIndex = m_currentPathIndex; - clone.m_nextPathIndex = m_nextPathIndex; - clone.m_parent = m_parent; - clone.m_bCirculator = m_bCirculator; - return clone; - } - - public void _updateSegment() { - if (m_nextSegmentIndex < 0 || m_nextSegmentIndex >= m_segmentCount) - throw new IndexOutOfBoundsException(); - m_currentSegmentIndex = m_nextSegmentIndex; - - int startVertexIndex = getStartPointIndex(); - m_parent._verifyAllStreams(); - AttributeStreamOfInt8 segFlagStream = m_parent - .getSegmentFlagsStreamRef(); - - int segFlag = SegmentFlags.enumLineSeg; - if (segFlagStream != null) - segFlag = (segFlagStream.read(startVertexIndex) & SegmentFlags.enumSegmentMask); - - VertexDescription vertexDescr = m_parent.getDescription(); - switch (segFlag) { - case SegmentFlags.enumLineSeg: - if (m_line == null) - m_line = new Line(); - m_currentSegment = (Line) m_line; - break; - case SegmentFlags.enumBezierSeg: - throw GeometryException.GeometryInternalError(); - // break; - case SegmentFlags.enumArcSeg: - throw GeometryException.GeometryInternalError(); - // break; - default: - throw GeometryException.GeometryInternalError(); - } - - m_currentSegment.assignVertexDescription(vertexDescr); - - int endVertexIndex = getEndPointIndex(); - m_parent.getXY(startVertexIndex, m_dummyPoint); - m_currentSegment.setStartXY(m_dummyPoint); - m_parent.getXY(endVertexIndex, m_dummyPoint); - m_currentSegment.setEndXY(m_dummyPoint); - - for (int i = 1, nattr = vertexDescr.getAttributeCount(); i < nattr; i++) { - int semantics = vertexDescr.getSemantics(i); - int ncomp = VertexDescription.getComponentCount(semantics); - for (int ord = 0; ord < ncomp; ord++) { - double vs = m_parent.getAttributeAsDbl(semantics, - startVertexIndex, ord); - m_currentSegment.setStartAttribute(semantics, ord, vs); - double ve = m_parent.getAttributeAsDbl(semantics, - endVertexIndex, ord); - m_currentSegment.setEndAttribute(semantics, ord, ve); - } - } - } - - boolean isLastPath() { - return m_currentPathIndex == m_parent.getPathCount() - 1; - } + protected Line m_line; + + // Bezier m_bezier: + // Arc m_arc; + protected Segment m_currentSegment; + protected Point2D m_dummyPoint; + + protected int m_currentPathIndex; + + protected int m_nextPathIndex; + + protected int m_prevPathIndex; + + protected int m_currentSegmentIndex; + + protected int m_nextSegmentIndex; + + protected int m_prevSegmentIndex; + + protected int m_segmentCount; + + protected int m_pathBegin; + + protected MultiPathImpl m_parent; // parent of the iterator. + + protected boolean m_bCirculator; // If true, the iterator circulates around + // the current Path. + + protected boolean m_bNeedsUpdate; + + public SegmentIteratorImpl(MultiPathImpl parent) { + m_currentSegmentIndex = -1; + m_nextSegmentIndex = 0; + m_nextPathIndex = 0; + m_currentPathIndex = -1; + m_parent = parent; + m_segmentCount = _getSegmentCount(m_nextPathIndex); + m_bCirculator = false; + m_currentSegment = null; + m_pathBegin = -1; + m_dummyPoint = new Point2D(); + } + + public SegmentIteratorImpl(MultiPathImpl parent, int pointIndex) { + if (pointIndex < 0 || pointIndex >= parent.getPointCount()) + throw new IndexOutOfBoundsException(); + + m_currentSegmentIndex = -1; + int path = parent.getPathIndexFromPointIndex(pointIndex); + m_nextSegmentIndex = pointIndex - parent.getPathStart(path); + + m_nextPathIndex = path + 1; + m_currentPathIndex = path; + m_parent = parent; + m_segmentCount = _getSegmentCount(m_currentPathIndex); + m_bCirculator = false; + m_currentSegment = null; + m_pathBegin = m_parent.getPathStart(m_currentPathIndex); + m_dummyPoint = new Point2D(); + } + + public SegmentIteratorImpl(MultiPathImpl parent, int pathIndex, + int segmentIndex) { + if (pathIndex < 0 || pathIndex >= parent.getPathCount() + || segmentIndex < 0) + throw new IndexOutOfBoundsException(); + + int d = parent.isClosedPath(pathIndex) ? 0 : 1; + if (segmentIndex >= parent.getPathSize(pathIndex) - d) + throw new IndexOutOfBoundsException(); + + m_currentSegmentIndex = -1; + m_nextSegmentIndex = segmentIndex; + m_currentPathIndex = pathIndex; + m_nextPathIndex = m_nextSegmentIndex + 1; + m_parent = parent; + m_segmentCount = _getSegmentCount(m_nextPathIndex); + m_bCirculator = false; + m_currentSegment = null; + m_pathBegin = m_parent.getPathStart(m_currentPathIndex); + m_dummyPoint = new Point2D(); + } + + void resetTo(SegmentIteratorImpl src) { + if (m_parent != src.m_parent) + throw new GeometryException("invalid_call"); + + m_currentSegmentIndex = src.m_currentSegmentIndex; + m_nextSegmentIndex = src.m_nextSegmentIndex; + m_currentPathIndex = src.m_currentPathIndex; + m_nextPathIndex = src.m_nextPathIndex; + m_segmentCount = src.m_segmentCount; + m_bCirculator = src.m_bCirculator; + m_pathBegin = src.m_pathBegin; + m_currentSegment = null; + } + + /** + * Moves the iterator to the next curve segment and returns the segment. + *

+ * The Segment is returned by value and is owned by the iterator. Note: The + * method can return null if there are no curves in the part. + */ + public Segment nextCurve() { + return null; + // TODO: Fix me. This method is supposed to go only through the curves + // and skip the Line classes!! + // It must be very efficient. + } + + /** + * Moves the iterator to next segment and returns the segment. + *

+ * The Segment is returned by value and is owned by the iterator. + */ + public Segment nextSegment() { + if (m_currentSegmentIndex != m_nextSegmentIndex) + _updateSegment(); + + if (m_bCirculator) { + m_nextSegmentIndex = (m_nextSegmentIndex + 1) % m_segmentCount; + } else { + if (m_nextSegmentIndex == m_segmentCount) + throw new IndexOutOfBoundsException(); + + m_nextSegmentIndex++; + } + + return m_currentSegment; + } + + /** + * Moves the iterator to previous segment and returns the segment. + *

+ * The Segment is returned by value and is owned by the iterator. + */ + public Segment previousSegment() { + if (m_bCirculator) { + m_nextSegmentIndex = (m_segmentCount + m_nextSegmentIndex - 1) + % m_segmentCount; + } else { + if (m_nextSegmentIndex == 0) + throw new IndexOutOfBoundsException(); + m_nextSegmentIndex--; + } + + if (m_nextSegmentIndex != m_currentSegmentIndex) + _updateSegment(); + + return m_currentSegment; + } + + /** + * Resets the iterator so that the call to NextSegment will return the first + * segment of the current path. + */ + public void resetToFirstSegment() { + m_currentSegmentIndex = -1; + m_nextSegmentIndex = 0; + } + + /** + * Resets the iterator so that the call to PreviousSegment will return the + * last segment of the current path. + */ + public void resetToLastSegment() { + m_nextSegmentIndex = m_segmentCount; + m_currentSegmentIndex = -1; + } + + public void resetToVertex(int vertexIndex) { + resetToVertex(vertexIndex, -1); + } + + public void resetToVertex(int vertexIndex, int _pathIndex) { + if (m_currentPathIndex >= 0 + && m_currentPathIndex < m_parent.getPathCount()) {// check if we + // are in + // the + // current + // path + int start = _getPathBegin(); + if (vertexIndex >= start + && vertexIndex < m_parent.getPathEnd(m_currentPathIndex)) { + m_currentSegmentIndex = -1; + m_nextSegmentIndex = vertexIndex - start; + return; + } + } + + int path_index; + if (_pathIndex >= 0 && _pathIndex < m_parent.getPathCount() + && vertexIndex >= m_parent.getPathStart(_pathIndex) + && vertexIndex < m_parent.getPathEnd(_pathIndex)) { + path_index = _pathIndex; + } else { + path_index = m_parent.getPathIndexFromPointIndex(vertexIndex); + } + + m_nextPathIndex = path_index + 1; + m_currentPathIndex = path_index; + m_currentSegmentIndex = -1; + m_nextSegmentIndex = vertexIndex - m_parent.getPathStart(path_index); + m_segmentCount = _getSegmentCount(path_index); + m_pathBegin = m_parent.getPathStart(m_currentPathIndex); + } + + /** + * Moves the iterator to next path and returns true if successful. + */ + public boolean nextPath() { + // post-increment + m_currentPathIndex = m_nextPathIndex; + if (m_currentPathIndex >= m_parent.getPathCount()) + return false; + + m_currentSegmentIndex = -1; + m_nextSegmentIndex = 0; + m_segmentCount = _getSegmentCount(m_currentPathIndex); + m_pathBegin = m_parent.getPathStart(m_currentPathIndex); + m_nextPathIndex++; + return true; + } + + /** + * Moves the iterator to next path and returns true if successful. + */ + public boolean previousPath() { + // pre-decrement + if (m_nextPathIndex == 0) + return false; + + m_nextPathIndex--; + m_currentSegmentIndex = -1; + m_nextSegmentIndex = 0; + m_segmentCount = _getSegmentCount(m_nextPathIndex); + m_currentPathIndex = m_nextPathIndex; + m_pathBegin = m_parent.getPathStart(m_currentPathIndex); + resetToLastSegment(); + return true; + } + + /** + * Resets the iterator such that the subsequent call to the NextPath will + * set the iterator to the first segment of the first path. + */ + public void resetToFirstPath() { + + m_currentSegmentIndex = -1; + m_nextSegmentIndex = -1; + m_segmentCount = -1; + m_nextPathIndex = 0; + m_currentPathIndex = -1; + m_pathBegin = -1; + } + + /** + * Resets the iterator such that the subsequent call to the PreviousPath + * will set the iterator to the last segment of the last path. + */ + public void resetToLastPath() { + m_nextPathIndex = m_parent.getPathCount(); + m_currentPathIndex = -1; + m_currentSegmentIndex = -1; + m_nextSegmentIndex = -1; + m_segmentCount = -1; + m_pathBegin = -1; + } + + /** + * Resets the iterator such that the subsequent call to the NextPath will + * set the iterator to the first segment of the given path. The call to + * PreviousPath will reset the iterator to the last segment of path + * pathIndex - 1. + */ + public void resetToPath(int pathIndex) { + if (pathIndex < 0) + throw new IndexOutOfBoundsException(); + + m_nextPathIndex = pathIndex; + m_currentPathIndex = -1; + m_currentSegmentIndex = -1; + m_nextSegmentIndex = -1; + m_segmentCount = -1; + m_pathBegin = -1; + } + + public int _getSegmentCount(int pathIndex) { + if (m_parent.isEmptyImpl()) + return 0; + + int d = 1; + if (m_parent.isClosedPath(pathIndex)) + d = 0; + + return m_parent.getPathSize(pathIndex) - d; + } + + /** + * Returns True, if the segment is the closing segment of the closed path + */ + public boolean isClosingSegment() { + return m_currentSegmentIndex == m_segmentCount - 1 + && m_parent.isClosedPath(m_currentPathIndex); + } + + /** + * Switches the iterator navigation mode. + * + * @param bYesNo If True, the iterator loops over the current path infinitely + * (unless the MultiPath is empty). + */ + public void setCirculator(boolean bYesNo) { + m_bCirculator = bYesNo; + } + + /** + * Returns the index of the current path. + */ + public int getPathIndex() { + return m_currentPathIndex; + } + + /** + * Returns the index of the start Point of the current Segment. + */ + public int getStartPointIndex() { + return _getPathBegin() + m_currentSegmentIndex; + } + + public int _getPathBegin() { + return m_parent.getPathStart(m_currentPathIndex); + } + + /** + * Returns the index of the end Point of the current Segment. + */ + public int getEndPointIndex() { + if (isClosingSegment()) { + return m_parent.getPathStart(m_currentPathIndex); + } else { + return getStartPointIndex() + 1; + } + } + + /** + * Returns True if the segment is first one in the current Path. + */ + public boolean isFirstSegmentInPath() { + return m_currentSegmentIndex == 0; + } + + /** + * Returns True if the segment is last one in the current Path. + */ + public boolean isLastSegmentInPath() { + return m_currentSegmentIndex == m_segmentCount - 1; + } + + /** + * Returns True if the call to the NextSegment will succeed. + */ + public boolean hasNextSegment() { + return m_nextSegmentIndex < m_segmentCount; + } + + /** + * Returns True if the call to the NextSegment will succeed. + */ + public boolean hasPreviousSegment() { + return m_nextSegmentIndex > 0; + } + + public SegmentIteratorImpl copy() { + SegmentIteratorImpl clone = new SegmentIteratorImpl(m_parent); + clone.m_currentSegmentIndex = m_currentSegmentIndex; + clone.m_nextSegmentIndex = m_nextSegmentIndex; + clone.m_segmentCount = m_segmentCount; + clone.m_currentPathIndex = m_currentPathIndex; + clone.m_nextPathIndex = m_nextPathIndex; + clone.m_parent = m_parent; + clone.m_bCirculator = m_bCirculator; + return clone; + } + + public void _updateSegment() { + if (m_nextSegmentIndex < 0 || m_nextSegmentIndex >= m_segmentCount) + throw new IndexOutOfBoundsException(); + m_currentSegmentIndex = m_nextSegmentIndex; + + int startVertexIndex = getStartPointIndex(); + m_parent._verifyAllStreams(); + AttributeStreamOfInt8 segFlagStream = m_parent + .getSegmentFlagsStreamRef(); + + int segFlag = SegmentFlags.enumLineSeg; + if (segFlagStream != null) + segFlag = (segFlagStream.read(startVertexIndex) & SegmentFlags.enumSegmentMask); + + VertexDescription vertexDescr = m_parent.getDescription(); + switch (segFlag) { + case SegmentFlags.enumLineSeg: + if (m_line == null) + m_line = new Line(); + m_currentSegment = (Line) m_line; + break; + case SegmentFlags.enumBezierSeg: + throw GeometryException.GeometryInternalError(); + // break; + case SegmentFlags.enumArcSeg: + throw GeometryException.GeometryInternalError(); + // break; + default: + throw GeometryException.GeometryInternalError(); + } + + m_currentSegment.assignVertexDescription(vertexDescr); + + int endVertexIndex = getEndPointIndex(); + m_parent.getXY(startVertexIndex, m_dummyPoint); + m_currentSegment.setStartXY(m_dummyPoint); + m_parent.getXY(endVertexIndex, m_dummyPoint); + m_currentSegment.setEndXY(m_dummyPoint); + + for (int i = 1, nattr = vertexDescr.getAttributeCount(); i < nattr; i++) { + int semantics = vertexDescr.getSemantics(i); + int ncomp = VertexDescription.getComponentCount(semantics); + for (int ord = 0; ord < ncomp; ord++) { + double vs = m_parent.getAttributeAsDbl(semantics, + startVertexIndex, ord); + m_currentSegment.setStartAttribute(semantics, ord, vs); + double ve = m_parent.getAttributeAsDbl(semantics, + endVertexIndex, ord); + m_currentSegment.setEndAttribute(semantics, ord, ve); + } + } + } + + boolean isLastPath() { + return m_currentPathIndex == m_parent.getPathCount() - 1; + } } diff --git a/src/main/java/com/esri/core/geometry/ShapeExportFlags.java b/src/main/java/com/esri/core/geometry/ShapeExportFlags.java index 92a0f5fe..cbf69bc4 100644 --- a/src/main/java/com/esri/core/geometry/ShapeExportFlags.java +++ b/src/main/java/com/esri/core/geometry/ShapeExportFlags.java @@ -28,17 +28,17 @@ * Flags used by the OperatorExportToEsriShape. */ public interface ShapeExportFlags { - public static final int ShapeExportDefaults = 0;//! m_ids; - private ArrayDeque m_byteBufferDeque; - private ArrayDeque m_simpleStates; - private ArrayDeque m_featureIDs; - private long m_current_id = -1; - private SimpleStateEnum m_currentSimpleState = SimpleStateEnum.SIMPLE_UNKNOWN; - private String m_currentFeatureID = ""; - - @Deprecated - public SimpleByteBufferCursor(ByteBuffer byteBuffer) { - m_byteBufferDeque = new ArrayDeque<>(); - m_byteBufferDeque.add(byteBuffer); - } - - public SimpleByteBufferCursor(ByteBuffer byteBuffer, long id) { - m_byteBufferDeque = new ArrayDeque<>(1); - m_byteBufferDeque.add(byteBuffer); - m_ids = new ArrayDeque<>(1); - m_ids.push(id); - } - - public SimpleByteBufferCursor(ByteBuffer byteBuffer, long id, SimpleStateEnum simpleState) { - m_byteBufferDeque = new ArrayDeque<>(1); - m_byteBufferDeque.add(byteBuffer); - m_ids = new ArrayDeque<>(1); - m_ids.push(id); - m_simpleStates = new ArrayDeque<>(1); - m_simpleStates.push(simpleState); - } - - public SimpleByteBufferCursor(ByteBuffer byteBuffer, long id, SimpleStateEnum simpleState, String featureID) { - m_byteBufferDeque = new ArrayDeque<>(1); - m_byteBufferDeque.add(byteBuffer); - m_ids = new ArrayDeque<>(1); - m_ids.push(id); - m_simpleStates = new ArrayDeque<>(1); - m_simpleStates.push(simpleState); - m_featureIDs = new ArrayDeque<>(1); - m_featureIDs.push(featureID); - } - - @Deprecated - public SimpleByteBufferCursor(ByteBuffer[] byteBufferArray) { - m_byteBufferDeque = Arrays.stream(byteBufferArray).collect(Collectors.toCollection(ArrayDeque::new)); - } - - @Deprecated - public SimpleByteBufferCursor(List byteBufferArray) { - m_byteBufferDeque = new ArrayDeque<>(byteBufferArray); - } - - public SimpleByteBufferCursor(ArrayDeque byteBufferArrayDeque, ArrayDeque ids) { - m_byteBufferDeque = byteBufferArrayDeque; - m_ids = ids; - } - - public SimpleByteBufferCursor(ArrayDeque arrayDeque, - ArrayDeque ids, - ArrayDeque simpleStates, - ArrayDeque featureIDs) { - if ((arrayDeque.size() & ids.size() & simpleStates.size() & featureIDs.size()) != arrayDeque.size()) { - throw new GeometryException("arrays must be same size"); - } - m_byteBufferDeque = arrayDeque; - m_ids = ids; - m_simpleStates = simpleStates; - m_featureIDs = featureIDs; - } - - @Override - public boolean hasNext() { - return m_byteBufferDeque.size() > 0; - } - - @Override - public long getByteBufferID() { - return m_current_id; - } - - @Override - public SimpleStateEnum getSimpleState() { - return m_currentSimpleState; - } - - @Override - public String getFeatureID() { return m_currentFeatureID; } - - void _incrementInternals() { - if (m_ids != null && !m_ids.isEmpty()) { - m_current_id = m_ids.pop(); - } else { - m_current_id++; - } - - if (m_simpleStates != null && !m_simpleStates.isEmpty()) { - m_currentSimpleState = m_simpleStates.pop(); - } - if (m_featureIDs != null && !m_featureIDs.isEmpty()) { - m_currentFeatureID = m_featureIDs.pop(); - } - } - - @Override - public ByteBuffer next() { - if (hasNext()) { - _incrementInternals(); - return m_byteBufferDeque.pop(); - } - - return null; - } + private ArrayDeque m_ids; + private ArrayDeque m_byteBufferDeque; + private ArrayDeque m_simpleStates; + private ArrayDeque m_featureIDs; + private long m_current_id = -1; + private SimpleStateEnum m_currentSimpleState = SimpleStateEnum.SIMPLE_UNKNOWN; + private String m_currentFeatureID = ""; + + @Deprecated + public SimpleByteBufferCursor(ByteBuffer byteBuffer) { + m_byteBufferDeque = new ArrayDeque<>(); + m_byteBufferDeque.add(byteBuffer); + } + + public SimpleByteBufferCursor(ByteBuffer byteBuffer, long id) { + m_byteBufferDeque = new ArrayDeque<>(1); + m_byteBufferDeque.add(byteBuffer); + m_ids = new ArrayDeque<>(1); + m_ids.push(id); + } + + public SimpleByteBufferCursor(ByteBuffer byteBuffer, long id, SimpleStateEnum simpleState) { + m_byteBufferDeque = new ArrayDeque<>(1); + m_byteBufferDeque.add(byteBuffer); + m_ids = new ArrayDeque<>(1); + m_ids.push(id); + m_simpleStates = new ArrayDeque<>(1); + m_simpleStates.push(simpleState); + } + + public SimpleByteBufferCursor(ByteBuffer byteBuffer, long id, SimpleStateEnum simpleState, String featureID) { + m_byteBufferDeque = new ArrayDeque<>(1); + m_byteBufferDeque.add(byteBuffer); + m_ids = new ArrayDeque<>(1); + m_ids.push(id); + m_simpleStates = new ArrayDeque<>(1); + m_simpleStates.push(simpleState); + m_featureIDs = new ArrayDeque<>(1); + m_featureIDs.push(featureID); + } + + @Deprecated + public SimpleByteBufferCursor(ByteBuffer[] byteBufferArray) { + m_byteBufferDeque = Arrays.stream(byteBufferArray).collect(Collectors.toCollection(ArrayDeque::new)); + } + + @Deprecated + public SimpleByteBufferCursor(List byteBufferArray) { + m_byteBufferDeque = new ArrayDeque<>(byteBufferArray); + } + + public SimpleByteBufferCursor(ArrayDeque byteBufferArrayDeque, ArrayDeque ids) { + m_byteBufferDeque = byteBufferArrayDeque; + m_ids = ids; + } + + public SimpleByteBufferCursor(ArrayDeque arrayDeque, + ArrayDeque ids, + ArrayDeque simpleStates, + ArrayDeque featureIDs) { + if ((arrayDeque.size() & ids.size() & simpleStates.size() & featureIDs.size()) != arrayDeque.size()) { + throw new GeometryException("arrays must be same size"); + } + m_byteBufferDeque = arrayDeque; + m_ids = ids; + m_simpleStates = simpleStates; + m_featureIDs = featureIDs; + } + + @Override + public boolean hasNext() { + return m_byteBufferDeque.size() > 0; + } + + @Override + public long getByteBufferID() { + return m_current_id; + } + + @Override + public SimpleStateEnum getSimpleState() { + return m_currentSimpleState; + } + + @Override + public String getFeatureID() { + return m_currentFeatureID; + } + + void _incrementInternals() { + if (m_ids != null && !m_ids.isEmpty()) { + m_current_id = m_ids.pop(); + } else { + m_current_id++; + } + + if (m_simpleStates != null && !m_simpleStates.isEmpty()) { + m_currentSimpleState = m_simpleStates.pop(); + } + if (m_featureIDs != null && !m_featureIDs.isEmpty()) { + m_currentFeatureID = m_featureIDs.pop(); + } + } + + @Override + public ByteBuffer next() { + if (hasNext()) { + _incrementInternals(); + return m_byteBufferDeque.pop(); + } + + return null; + } } diff --git a/src/main/java/com/esri/core/geometry/SimpleGeometryCursor.java b/src/main/java/com/esri/core/geometry/SimpleGeometryCursor.java index 5a5d7102..33f740cc 100644 --- a/src/main/java/com/esri/core/geometry/SimpleGeometryCursor.java +++ b/src/main/java/com/esri/core/geometry/SimpleGeometryCursor.java @@ -32,70 +32,74 @@ */ public class SimpleGeometryCursor extends GeometryCursor { - private long m_index = -1; - private String m_currentFeatureId = ""; - private SimpleStateEnum m_simpleState = SimpleStateEnum.SIMPLE_UNKNOWN; - private MapGeometryCursor m_mapGeometryCursor = null; - private ArrayDeque m_geometryDeque = null; - - private long m_current_id = -1; - - public SimpleGeometryCursor(Geometry geom) { - m_geometryDeque = new ArrayDeque<>(1); - m_geometryDeque.add(geom); - } - - public SimpleGeometryCursor(Geometry[] geoms) { - m_geometryDeque = Arrays.stream(geoms).collect(Collectors.toCollection(ArrayDeque::new)); - } - - @Deprecated - public SimpleGeometryCursor(List geoms) { - m_geometryDeque = new ArrayDeque<>(geoms); - } - - public SimpleGeometryCursor(ArrayDeque geoms) { - m_geometryDeque = geoms; - } - - public SimpleGeometryCursor(MapGeometryCursor mapGeometryCursor) { - m_mapGeometryCursor = mapGeometryCursor; - } - - @Override - public boolean hasNext() { - return (m_geometryDeque != null && m_geometryDeque.size() > 0) || (m_mapGeometryCursor != null && m_mapGeometryCursor.hasNext()); - } - - @Override - public long getGeometryID() { - return m_current_id; - } - - @Override - public SimpleStateEnum getSimpleState() { return m_simpleState; } - - @Override - public String getFeatureID() { return m_currentFeatureId; } - - @Override - public Geometry next() { - m_index++; - Geometry geometry = null; - if (m_geometryDeque != null && !m_geometryDeque.isEmpty()) { - geometry = m_geometryDeque.pop(); - - // TODO get id off of geometry if exists - m_current_id = m_index; - m_simpleState = geometry.getSimpleState(); - } else if (m_mapGeometryCursor != null && m_mapGeometryCursor.hasNext()) { - geometry = m_mapGeometryCursor.next().m_geometry; - - // TODO get id off of geometry if exists - m_current_id = m_mapGeometryCursor.getGeometryID(); - m_simpleState = m_mapGeometryCursor.getSimpleState(); - } - - return geometry; - } + private long m_index = -1; + private String m_currentFeatureId = ""; + private SimpleStateEnum m_simpleState = SimpleStateEnum.SIMPLE_UNKNOWN; + private MapGeometryCursor m_mapGeometryCursor = null; + private ArrayDeque m_geometryDeque = null; + + private long m_current_id = -1; + + public SimpleGeometryCursor(Geometry geom) { + m_geometryDeque = new ArrayDeque<>(1); + m_geometryDeque.add(geom); + } + + public SimpleGeometryCursor(Geometry[] geoms) { + m_geometryDeque = Arrays.stream(geoms).collect(Collectors.toCollection(ArrayDeque::new)); + } + + @Deprecated + public SimpleGeometryCursor(List geoms) { + m_geometryDeque = new ArrayDeque<>(geoms); + } + + public SimpleGeometryCursor(ArrayDeque geoms) { + m_geometryDeque = geoms; + } + + public SimpleGeometryCursor(MapGeometryCursor mapGeometryCursor) { + m_mapGeometryCursor = mapGeometryCursor; + } + + @Override + public boolean hasNext() { + return (m_geometryDeque != null && m_geometryDeque.size() > 0) || (m_mapGeometryCursor != null && m_mapGeometryCursor.hasNext()); + } + + @Override + public long getGeometryID() { + return m_current_id; + } + + @Override + public SimpleStateEnum getSimpleState() { + return m_simpleState; + } + + @Override + public String getFeatureID() { + return m_currentFeatureId; + } + + @Override + public Geometry next() { + m_index++; + Geometry geometry = null; + if (m_geometryDeque != null && !m_geometryDeque.isEmpty()) { + geometry = m_geometryDeque.pop(); + + // TODO get id off of geometry if exists + m_current_id = m_index; + m_simpleState = geometry.getSimpleState(); + } else if (m_mapGeometryCursor != null && m_mapGeometryCursor.hasNext()) { + geometry = m_mapGeometryCursor.next().m_geometry; + + // TODO get id off of geometry if exists + m_current_id = m_mapGeometryCursor.getGeometryID(); + m_simpleState = m_mapGeometryCursor.getSimpleState(); + } + + return geometry; + } } diff --git a/src/main/java/com/esri/core/geometry/SimpleJsonCursor.java b/src/main/java/com/esri/core/geometry/SimpleJsonCursor.java index 1fb5dd26..1af987a9 100644 --- a/src/main/java/com/esri/core/geometry/SimpleJsonCursor.java +++ b/src/main/java/com/esri/core/geometry/SimpleJsonCursor.java @@ -25,38 +25,38 @@ class SimpleJsonCursor extends JsonCursor { - String m_jsonString; - String[] m_jsonStringArray; - - int m_index; - int m_count; - - public SimpleJsonCursor(String jsonString) { - m_jsonString = jsonString; - m_index = -1; - m_count = 1; - } - - public SimpleJsonCursor(String[] jsonStringArray) { - m_jsonStringArray = jsonStringArray; - m_index = -1; - m_count = jsonStringArray.length; - } - - @Override - public int getID() { - return m_index; - } - - @Override - public String next() { - if (m_index < m_count - 1) { - m_index++; - return m_jsonString != null ? m_jsonString - : m_jsonStringArray[m_index]; - } - - return null; - } + String m_jsonString; + String[] m_jsonStringArray; + + int m_index; + int m_count; + + public SimpleJsonCursor(String jsonString) { + m_jsonString = jsonString; + m_index = -1; + m_count = 1; + } + + public SimpleJsonCursor(String[] jsonStringArray) { + m_jsonStringArray = jsonStringArray; + m_index = -1; + m_count = jsonStringArray.length; + } + + @Override + public int getID() { + return m_index; + } + + @Override + public String next() { + if (m_index < m_count - 1) { + m_index++; + return m_jsonString != null ? m_jsonString + : m_jsonStringArray[m_index]; + } + + return null; + } } diff --git a/src/main/java/com/esri/core/geometry/SimpleJsonReaderCursor.java b/src/main/java/com/esri/core/geometry/SimpleJsonReaderCursor.java index 671947bf..e74aca20 100644 --- a/src/main/java/com/esri/core/geometry/SimpleJsonReaderCursor.java +++ b/src/main/java/com/esri/core/geometry/SimpleJsonReaderCursor.java @@ -28,46 +28,48 @@ import java.util.stream.Collectors; public class SimpleJsonReaderCursor extends JsonReaderCursor { - private ArrayDeque m_jsonDeque; - private int m_index= -1; - private String currentFeatureID = ""; - private SimpleStateEnum simpleState = SimpleStateEnum.SIMPLE_UNKNOWN; - - public SimpleJsonReaderCursor(JsonReader jsonString) { - m_jsonDeque = new ArrayDeque<>(1); - m_jsonDeque.add(jsonString); - } - - public SimpleJsonReaderCursor(JsonReader[] jsonStringArray) { - m_jsonDeque = Arrays.stream(jsonStringArray).collect(Collectors.toCollection(ArrayDeque::new)); - } - - @Override - public int getID() { - return m_index; - } - - @Override - public SimpleStateEnum getSimpleState() { - return simpleState; - } - - @Override - public String getFeatureID() { return currentFeatureID ; } - - @Override - public boolean hasNext() { - return m_jsonDeque.size() > 0; - } - - @Override - public JsonReader next() { - if (!m_jsonDeque.isEmpty()) { - m_index++; - return m_jsonDeque.pop(); - } - - return null; - } + private ArrayDeque m_jsonDeque; + private int m_index = -1; + private String currentFeatureID = ""; + private SimpleStateEnum simpleState = SimpleStateEnum.SIMPLE_UNKNOWN; + + public SimpleJsonReaderCursor(JsonReader jsonString) { + m_jsonDeque = new ArrayDeque<>(1); + m_jsonDeque.add(jsonString); + } + + public SimpleJsonReaderCursor(JsonReader[] jsonStringArray) { + m_jsonDeque = Arrays.stream(jsonStringArray).collect(Collectors.toCollection(ArrayDeque::new)); + } + + @Override + public int getID() { + return m_index; + } + + @Override + public SimpleStateEnum getSimpleState() { + return simpleState; + } + + @Override + public String getFeatureID() { + return currentFeatureID; + } + + @Override + public boolean hasNext() { + return m_jsonDeque.size() > 0; + } + + @Override + public JsonReader next() { + if (!m_jsonDeque.isEmpty()) { + m_index++; + return m_jsonDeque.pop(); + } + + return null; + } } diff --git a/src/main/java/com/esri/core/geometry/SimpleMapGeometryCursor.java b/src/main/java/com/esri/core/geometry/SimpleMapGeometryCursor.java index 5b44cf8b..380c8db4 100644 --- a/src/main/java/com/esri/core/geometry/SimpleMapGeometryCursor.java +++ b/src/main/java/com/esri/core/geometry/SimpleMapGeometryCursor.java @@ -32,48 +32,50 @@ * an array of MapGeometry classes */ class SimpleMapGeometryCursor extends MapGeometryCursor { - private ArrayDeque m_geomDeque; - private long m_index = -1; - private String m_currentFeatureID = ""; - private SimpleStateEnum m_simpleState = SimpleStateEnum.SIMPLE_UNKNOWN; - - - public SimpleMapGeometryCursor(MapGeometry geom) { - m_geomDeque = new ArrayDeque<>(1); - } - - public SimpleMapGeometryCursor(MapGeometry[] geoms) { - m_geomDeque = Arrays.stream(geoms).collect(Collectors.toCollection(ArrayDeque::new)); - } - - @Override - public long getGeometryID() { - return m_index; - } - - @Override - public SimpleStateEnum getSimpleState() { - return m_simpleState; - } - - @Override - public String getFeatureID() { return m_currentFeatureID; } - - - @Override - public boolean hasNext() { - return m_geomDeque.size() > 0; - } - - @Override - public MapGeometry next() { - if (hasNext()) { - m_index++; - MapGeometry mapGeometry = m_geomDeque.pop(); - m_simpleState = mapGeometry.m_geometry.getSimpleState(); - return mapGeometry; - } - - return null; - } + private ArrayDeque m_geomDeque; + private long m_index = -1; + private String m_currentFeatureID = ""; + private SimpleStateEnum m_simpleState = SimpleStateEnum.SIMPLE_UNKNOWN; + + + public SimpleMapGeometryCursor(MapGeometry geom) { + m_geomDeque = new ArrayDeque<>(1); + } + + public SimpleMapGeometryCursor(MapGeometry[] geoms) { + m_geomDeque = Arrays.stream(geoms).collect(Collectors.toCollection(ArrayDeque::new)); + } + + @Override + public long getGeometryID() { + return m_index; + } + + @Override + public SimpleStateEnum getSimpleState() { + return m_simpleState; + } + + @Override + public String getFeatureID() { + return m_currentFeatureID; + } + + + @Override + public boolean hasNext() { + return m_geomDeque.size() > 0; + } + + @Override + public MapGeometry next() { + if (hasNext()) { + m_index++; + MapGeometry mapGeometry = m_geomDeque.pop(); + m_simpleState = mapGeometry.m_geometry.getSimpleState(); + return mapGeometry; + } + + return null; + } } diff --git a/src/main/java/com/esri/core/geometry/SimpleRasterizer.java b/src/main/java/com/esri/core/geometry/SimpleRasterizer.java index 1d5fb614..3dff370f 100644 --- a/src/main/java/com/esri/core/geometry/SimpleRasterizer.java +++ b/src/main/java/com/esri/core/geometry/SimpleRasterizer.java @@ -34,517 +34,517 @@ */ public class SimpleRasterizer { - /** - * Even odd fill rule - */ - public final static int EVEN_ODD = 0; - - /** - * Winding fill rule - */ - public final static int WINDING = 1; - - public static interface ScanCallback { - /** - * Rasterizer calls this method for each scan it produced - * - * @param scans array of scans. Scans are triplets of numbers. The start X coordinate for the scan (inclusive), - * the end X coordinate of the scan (exclusive), the Y coordinate for the scan. - * @param scanCount3 The number of initialized elements in the scans array. The scan count is scanCount3 / 3. - */ - public abstract void drawScan(int[] scans, int scanCount3); - } - - public SimpleRasterizer() { - width_ = -1; - height_ = -1; - } - - /** - * Sets up the rasterizer. - */ - public void setup(int width, int height, ScanCallback callback) { - width_ = width; - height_ = height; - ySortedEdges_ = null; - activeEdgesTable_ = null; - numEdges_ = 0; - callback_ = callback; - if (scanBuffer_ == null) - scanBuffer_ = new int[128 * 3]; - - startAddingEdges(); - } - - public final int getWidth() { - return width_; - } - - public final int getHeight() { - return height_; - } - - /** - * Flushes any cached scans. - */ - public final void flush() { - if (scanPtr_ > 0) { - callback_.drawScan(scanBuffer_, scanPtr_); - scanPtr_ = 0; - } - } - - /** - * Adds edges of a triangle. - */ - public final void addTriangle(double x1, double y1, double x2, double y2, double x3, double y3) { - addEdge(x1, y1, x2, y2); - addEdge(x2, y2, x3, y3); - addEdge(x1, y1, x3, y3); - } - - /** - * Adds edges of the ring to the rasterizer. - * - * @param xy interleaved coordinates x1, y1, x2, y2,... - */ - public final void addRing(double xy[]) { - for (int i = 2; i < xy.length; i += 2) { - addEdge(xy[i - 2], xy[i - 1], xy[i], xy[i + 1]); - } - } - - /** - * Call before starting the edges. - *

- * For example to render two polygons that consist of a single ring: - * startAddingEdges(); - * addRing(...); - * renderEdges(Rasterizer.EVEN_ODD); - * addRing(...); - * renderEdges(Rasterizer.EVEN_ODD); - *

- * For example to render a polygon consisting of three rings: - * startAddingEdges(); - * addRing(...); - * addRing(...); - * addRing(...); - * renderEdges(Rasterizer.EVEN_ODD); - */ - public final void startAddingEdges() { - if (numEdges_ > 0) { - for (int i = 0; i < height_; i++) { - for (Edge e = ySortedEdges_[i]; e != null; ) { - Edge p = e; - e = e.next; - p.next = null; - } - - ySortedEdges_[i] = null; - } - - activeEdgesTable_ = null; - } - - minY_ = height_; - maxY_ = -1; - numEdges_ = 0; - } - - /** - * Renders all edges added so far, and removes them. - * Calls startAddingEdges after it's done. - * - * @param fillMode Fill mode for the polygon fill can be one of two values: EVEN_ODD or WINDING. - *

- * Note, as any other graphics algorithm, the scan line rasterizer doesn't require polygons - * to be topologically simple, or have correct ring orientation. - */ - public final void renderEdges(int fillMode) { - evenOdd_ = fillMode == EVEN_ODD; - for (int line = minY_; line <= maxY_; line++) { - advanceAET_(); - addNewEdgesToAET_(line); - emitScans_(); - } - - startAddingEdges();//reset for new edges - } - - /** - * Add a single edge. - * - * @param x1 - * @param y1 - * @param x2 - * @param y2 - */ - public final void addEdge(double x1, double y1, double x2, double y2) { - if (y1 == y2) - return; - - int dir = 1; - if (y1 > y2) { - double temp; - temp = x1; - x1 = x2; - x2 = temp; - temp = y1; - y1 = y2; - y2 = temp; - dir = -1; - } - - if (y2 < 0 || y1 >= height_) - return; - - if (x1 < 0 && x2 < 0) { - x1 = -1; - x2 = -1; - } else if (x1 >= width_ && x2 >= width_) { - x1 = width_; - x2 = width_; - } - - //clip to extent - double dxdy = (x2 - x1) / (y2 - y1); - - if (y2 > height_) { - y2 = height_; - x2 = dxdy * (y2 - y1) + x1; - } - - if (y1 < 0) { - x1 = dxdy * (0 - y1) + x1; - y1 = 0; - } - - //do not clip x unless it is too small or too big - int bigX = Math.max(width_ + 1, 0x7fffff); - if (x1 < -0x7fffff) { - //from earlier logic, x2 >= -1, therefore dxdy is not 0 - y1 = (0 - x1) / dxdy + y1; - x1 = 0; - } else if (x1 > bigX) { - //from earlier logic, x2 <= width_, therefore dxdy is not 0 - y1 = (width_ - x1) / dxdy + y1; - x1 = width_; - } - - if (x2 < -0x7fffff) { - //from earlier logic, x1 >= -1, therefore dxdy is not 0 - y2 = (0 - x1) / dxdy + y1; - x2 = 0; - } else if (x2 > bigX) { - //from earlier logic, x1 <= width_, therefore dxdy is not 0 - y2 = (width_ - x1) / dxdy + y1; - x2 = width_; - } - - int ystart = (int) y1; - int yend = (int) y2; - if (ystart == yend) - return; - - Edge e = new Edge(); - - e.x = (long) (x1 * 4294967296.0); - e.y = ystart; - e.ymax = yend; - e.dxdy = (long) (dxdy * 4294967296.0); - e.dir = dir; - - if (ySortedEdges_ == null) { - ySortedEdges_ = new Edge[height_]; - } - - e.next = ySortedEdges_[e.y]; - ySortedEdges_[e.y] = e; - - if (e.y < minY_) - minY_ = e.y; - - if (e.ymax > maxY_) - maxY_ = e.ymax; - - numEdges_++; - } - - public final void fillEnvelope(Envelope2D envIn) { - Envelope2D env = new Envelope2D(0, 0, width_, height_); - if (!env.intersect(envIn)) - return; - - int x0 = (int) env.xmin; - int x = (int) env.xmax; - - int xn = NumberUtils.snap(x0, 0, width_); - int xm = NumberUtils.snap(x, 0, width_); - if (x0 < width_ && xn < xm) { - int y0 = (int) env.ymin; - int y1 = (int) env.ymax; - y0 = NumberUtils.snap(y0, 0, height_); - y1 = NumberUtils.snap(y1, 0, height_); - if (y0 < height_) { - for (int y = y0; y < y1; y++) { - scanBuffer_[scanPtr_++] = xn; - scanBuffer_[scanPtr_++] = xm; - scanBuffer_[scanPtr_++] = y; - if (scanPtr_ == scanBuffer_.length) { - callback_.drawScan(scanBuffer_, scanPtr_); - scanPtr_ = 0; - } - } - } - } - } - - final boolean addSegmentStroke(double x1, double y1, double x2, double y2, double half_width, boolean skip_short, double[] helper_xy_10_elm) { - double vec_x = x2 - x1; - double vec_y = y2 - y1; - double len = Math.sqrt(vec_x * vec_x + vec_y * vec_y); - if (skip_short && len < 0.5) - return false; - - boolean bshort = len < 0.00001; - if (bshort) { - len = 0.00001; - vec_x = len; - vec_y = 0.0; - } - - double f = half_width / len; - vec_x *= f; - vec_y *= f; - double vecA_x = -vec_y; - double vecA_y = vec_x; - double vecB_x = vec_y; - double vecB_y = -vec_x; - //extend by half width - x1 -= vec_x; - y1 -= vec_y; - x2 += vec_x; - y2 += vec_y; - //create rotated rectangle - double[] fan = helper_xy_10_elm; - assert (fan.length == 10); - fan[0] = x1 + vecA_x; - fan[1] = y1 + vecA_y;//fan[0].add(pt_start, vecA); - fan[2] = x1 + vecB_x; - fan[3] = y1 + vecB_y;//fan[1].add(pt_start, vecB); - fan[4] = x2 + vecB_x; - fan[5] = y2 + vecB_y;//fan[2].add(pt_end, vecB) - fan[6] = x2 + vecA_x; - fan[7] = y2 + vecA_y;//fan[3].add(pt_end, vecA) - fan[8] = fan[0]; - fan[9] = fan[1]; - addRing(fan); - return true; - } - - public final ScanCallback getScanCallback() { - return callback_; - } - - - //PRIVATE - - private static class Edge { - long x; - long dxdy; - int y; - int ymax; - int dir; - Edge next; - } - - private final void advanceAET_() { - if (activeEdgesTable_ == null) - return; - - boolean needSort = false; - Edge prev = null; - for (Edge e = activeEdgesTable_; e != null; ) { - e.y++; - if (e.y == e.ymax) { - Edge p = e; - e = e.next; - if (prev != null) - prev.next = e; - else - activeEdgesTable_ = e; - - p.next = null; - continue; - } - - e.x += e.dxdy; - if (prev != null && prev.x > e.x) - needSort = true; - - prev = e; - e = e.next; - } - - if (needSort) { - //resort to fix the order - activeEdgesTable_ = sortAET_(activeEdgesTable_); - } - } - - private final void addNewEdgesToAET_(int y) { - if (y >= height_) - return; - - Edge edgesOnLine = ySortedEdges_[y]; - if (edgesOnLine != null) { - ySortedEdges_[y] = null; - edgesOnLine = sortAET_(edgesOnLine);//sort new edges - numEdges_ -= sortedNum_;//set in the sortAET - - // merge the edges with sorted AET - O(n) operation - Edge aet = activeEdgesTable_; - boolean first = true; - Edge newEdge = edgesOnLine; - Edge prev_aet = null; - while (aet != null && newEdge != null) { - if (aet.x > newEdge.x) { - if (first) - activeEdgesTable_ = newEdge; - - Edge p = newEdge.next; - newEdge.next = aet; - if (prev_aet != null) { - prev_aet.next = newEdge; - } - - prev_aet = newEdge; - newEdge = p; - } else { // aet.x <= newEdges.x - Edge p = aet.next; - aet.next = newEdge; - if (prev_aet != null) - prev_aet.next = aet; - - prev_aet = aet; - aet = p; - } - - first = false; - } - - if (activeEdgesTable_ == null) - activeEdgesTable_ = edgesOnLine; - } - } - - private static int snap_(int x, int mi, int ma) { - return x < mi ? mi : x > ma ? ma : x; - } - - private final void emitScans_() { - if (activeEdgesTable_ == null) - return; - - int w = 0; - Edge e0 = activeEdgesTable_; - int x0 = (int) (e0.x >> 32); - for (Edge e = e0.next; e != null; e = e.next) { - if (evenOdd_) - w ^= 1; - else - w += e.dir; - - if (e.x > e0.x) { - int x = (int) (e.x >> 32); - if (w != 0) { - int xx0 = snap_(x0, 0, width_); - int xx = snap_(x, 0, width_); - if (xx > xx0 && xx0 < width_) { - scanBuffer_[scanPtr_++] = xx0; - scanBuffer_[scanPtr_++] = xx; - scanBuffer_[scanPtr_++] = e.y; - if (scanPtr_ == scanBuffer_.length) { - callback_.drawScan(scanBuffer_, scanPtr_); - scanPtr_ = 0; - } - } - } - - e0 = e; - x0 = x; - } - } - } - - static private class EdgeComparator implements Comparator { - @Override - public int compare(Edge o1, Edge o2) { - if (o1 == o2) - return 0; - - return o1.x < o2.x ? -1 : o1.x > o2.x ? 1 : 0; - } - } - - private final static EdgeComparator edgeCompare_ = new EdgeComparator(); - - private final Edge sortAET_(Edge aet) { - int num = 0; - for (Edge e = aet; e != null; e = e.next) - num++; - - sortedNum_ = num; - if (num == 1) - return aet; - - if (sortBuffer_ == null) - sortBuffer_ = new Edge[Math.max(num, 16)]; - - else if (sortBuffer_.length < num) - sortBuffer_ = new Edge[Math.max(num, sortBuffer_.length * 2)]; - - { - int i = 0; - for (Edge e = aet; e != null; e = e.next) - sortBuffer_[i++] = e; - } - - if (num == 2) { - if (sortBuffer_[0].x > sortBuffer_[1].x) { - Edge tmp = sortBuffer_[0]; - sortBuffer_[0] = sortBuffer_[1]; - sortBuffer_[1] = tmp; - } - } else { - Arrays.sort(sortBuffer_, 0, num, edgeCompare_); - } - - aet = sortBuffer_[0]; - sortBuffer_[0] = null; - Edge prev = aet; - for (int i = 1; i < num; i++) { - prev.next = sortBuffer_[i]; - prev = sortBuffer_[i]; - sortBuffer_[i] = null; - } - - prev.next = null; - return aet; - } - - private Edge activeEdgesTable_; - private Edge[] ySortedEdges_; - private Edge[] sortBuffer_; - private int[] scanBuffer_; - int scanPtr_; - private ScanCallback callback_; - private int width_; - private int height_; - private int minY_; - private int maxY_; - private int numEdges_; - private int sortedNum_; - private boolean evenOdd_; + /** + * Even odd fill rule + */ + public final static int EVEN_ODD = 0; + + /** + * Winding fill rule + */ + public final static int WINDING = 1; + + public static interface ScanCallback { + /** + * Rasterizer calls this method for each scan it produced + * + * @param scans array of scans. Scans are triplets of numbers. The start X coordinate for the scan (inclusive), + * the end X coordinate of the scan (exclusive), the Y coordinate for the scan. + * @param scanCount3 The number of initialized elements in the scans array. The scan count is scanCount3 / 3. + */ + public abstract void drawScan(int[] scans, int scanCount3); + } + + public SimpleRasterizer() { + width_ = -1; + height_ = -1; + } + + /** + * Sets up the rasterizer. + */ + public void setup(int width, int height, ScanCallback callback) { + width_ = width; + height_ = height; + ySortedEdges_ = null; + activeEdgesTable_ = null; + numEdges_ = 0; + callback_ = callback; + if (scanBuffer_ == null) + scanBuffer_ = new int[128 * 3]; + + startAddingEdges(); + } + + public final int getWidth() { + return width_; + } + + public final int getHeight() { + return height_; + } + + /** + * Flushes any cached scans. + */ + public final void flush() { + if (scanPtr_ > 0) { + callback_.drawScan(scanBuffer_, scanPtr_); + scanPtr_ = 0; + } + } + + /** + * Adds edges of a triangle. + */ + public final void addTriangle(double x1, double y1, double x2, double y2, double x3, double y3) { + addEdge(x1, y1, x2, y2); + addEdge(x2, y2, x3, y3); + addEdge(x1, y1, x3, y3); + } + + /** + * Adds edges of the ring to the rasterizer. + * + * @param xy interleaved coordinates x1, y1, x2, y2,... + */ + public final void addRing(double xy[]) { + for (int i = 2; i < xy.length; i += 2) { + addEdge(xy[i - 2], xy[i - 1], xy[i], xy[i + 1]); + } + } + + /** + * Call before starting the edges. + *

+ * For example to render two polygons that consist of a single ring: + * startAddingEdges(); + * addRing(...); + * renderEdges(Rasterizer.EVEN_ODD); + * addRing(...); + * renderEdges(Rasterizer.EVEN_ODD); + *

+ * For example to render a polygon consisting of three rings: + * startAddingEdges(); + * addRing(...); + * addRing(...); + * addRing(...); + * renderEdges(Rasterizer.EVEN_ODD); + */ + public final void startAddingEdges() { + if (numEdges_ > 0) { + for (int i = 0; i < height_; i++) { + for (Edge e = ySortedEdges_[i]; e != null; ) { + Edge p = e; + e = e.next; + p.next = null; + } + + ySortedEdges_[i] = null; + } + + activeEdgesTable_ = null; + } + + minY_ = height_; + maxY_ = -1; + numEdges_ = 0; + } + + /** + * Renders all edges added so far, and removes them. + * Calls startAddingEdges after it's done. + * + * @param fillMode Fill mode for the polygon fill can be one of two values: EVEN_ODD or WINDING. + *

+ * Note, as any other graphics algorithm, the scan line rasterizer doesn't require polygons + * to be topologically simple, or have correct ring orientation. + */ + public final void renderEdges(int fillMode) { + evenOdd_ = fillMode == EVEN_ODD; + for (int line = minY_; line <= maxY_; line++) { + advanceAET_(); + addNewEdgesToAET_(line); + emitScans_(); + } + + startAddingEdges();//reset for new edges + } + + /** + * Add a single edge. + * + * @param x1 + * @param y1 + * @param x2 + * @param y2 + */ + public final void addEdge(double x1, double y1, double x2, double y2) { + if (y1 == y2) + return; + + int dir = 1; + if (y1 > y2) { + double temp; + temp = x1; + x1 = x2; + x2 = temp; + temp = y1; + y1 = y2; + y2 = temp; + dir = -1; + } + + if (y2 < 0 || y1 >= height_) + return; + + if (x1 < 0 && x2 < 0) { + x1 = -1; + x2 = -1; + } else if (x1 >= width_ && x2 >= width_) { + x1 = width_; + x2 = width_; + } + + //clip to extent + double dxdy = (x2 - x1) / (y2 - y1); + + if (y2 > height_) { + y2 = height_; + x2 = dxdy * (y2 - y1) + x1; + } + + if (y1 < 0) { + x1 = dxdy * (0 - y1) + x1; + y1 = 0; + } + + //do not clip x unless it is too small or too big + int bigX = Math.max(width_ + 1, 0x7fffff); + if (x1 < -0x7fffff) { + //from earlier logic, x2 >= -1, therefore dxdy is not 0 + y1 = (0 - x1) / dxdy + y1; + x1 = 0; + } else if (x1 > bigX) { + //from earlier logic, x2 <= width_, therefore dxdy is not 0 + y1 = (width_ - x1) / dxdy + y1; + x1 = width_; + } + + if (x2 < -0x7fffff) { + //from earlier logic, x1 >= -1, therefore dxdy is not 0 + y2 = (0 - x1) / dxdy + y1; + x2 = 0; + } else if (x2 > bigX) { + //from earlier logic, x1 <= width_, therefore dxdy is not 0 + y2 = (width_ - x1) / dxdy + y1; + x2 = width_; + } + + int ystart = (int) y1; + int yend = (int) y2; + if (ystart == yend) + return; + + Edge e = new Edge(); + + e.x = (long) (x1 * 4294967296.0); + e.y = ystart; + e.ymax = yend; + e.dxdy = (long) (dxdy * 4294967296.0); + e.dir = dir; + + if (ySortedEdges_ == null) { + ySortedEdges_ = new Edge[height_]; + } + + e.next = ySortedEdges_[e.y]; + ySortedEdges_[e.y] = e; + + if (e.y < minY_) + minY_ = e.y; + + if (e.ymax > maxY_) + maxY_ = e.ymax; + + numEdges_++; + } + + public final void fillEnvelope(Envelope2D envIn) { + Envelope2D env = new Envelope2D(0, 0, width_, height_); + if (!env.intersect(envIn)) + return; + + int x0 = (int) env.xmin; + int x = (int) env.xmax; + + int xn = NumberUtils.snap(x0, 0, width_); + int xm = NumberUtils.snap(x, 0, width_); + if (x0 < width_ && xn < xm) { + int y0 = (int) env.ymin; + int y1 = (int) env.ymax; + y0 = NumberUtils.snap(y0, 0, height_); + y1 = NumberUtils.snap(y1, 0, height_); + if (y0 < height_) { + for (int y = y0; y < y1; y++) { + scanBuffer_[scanPtr_++] = xn; + scanBuffer_[scanPtr_++] = xm; + scanBuffer_[scanPtr_++] = y; + if (scanPtr_ == scanBuffer_.length) { + callback_.drawScan(scanBuffer_, scanPtr_); + scanPtr_ = 0; + } + } + } + } + } + + final boolean addSegmentStroke(double x1, double y1, double x2, double y2, double half_width, boolean skip_short, double[] helper_xy_10_elm) { + double vec_x = x2 - x1; + double vec_y = y2 - y1; + double len = Math.sqrt(vec_x * vec_x + vec_y * vec_y); + if (skip_short && len < 0.5) + return false; + + boolean bshort = len < 0.00001; + if (bshort) { + len = 0.00001; + vec_x = len; + vec_y = 0.0; + } + + double f = half_width / len; + vec_x *= f; + vec_y *= f; + double vecA_x = -vec_y; + double vecA_y = vec_x; + double vecB_x = vec_y; + double vecB_y = -vec_x; + //extend by half width + x1 -= vec_x; + y1 -= vec_y; + x2 += vec_x; + y2 += vec_y; + //create rotated rectangle + double[] fan = helper_xy_10_elm; + assert (fan.length == 10); + fan[0] = x1 + vecA_x; + fan[1] = y1 + vecA_y;//fan[0].add(pt_start, vecA); + fan[2] = x1 + vecB_x; + fan[3] = y1 + vecB_y;//fan[1].add(pt_start, vecB); + fan[4] = x2 + vecB_x; + fan[5] = y2 + vecB_y;//fan[2].add(pt_end, vecB) + fan[6] = x2 + vecA_x; + fan[7] = y2 + vecA_y;//fan[3].add(pt_end, vecA) + fan[8] = fan[0]; + fan[9] = fan[1]; + addRing(fan); + return true; + } + + public final ScanCallback getScanCallback() { + return callback_; + } + + + //PRIVATE + + private static class Edge { + long x; + long dxdy; + int y; + int ymax; + int dir; + Edge next; + } + + private final void advanceAET_() { + if (activeEdgesTable_ == null) + return; + + boolean needSort = false; + Edge prev = null; + for (Edge e = activeEdgesTable_; e != null; ) { + e.y++; + if (e.y == e.ymax) { + Edge p = e; + e = e.next; + if (prev != null) + prev.next = e; + else + activeEdgesTable_ = e; + + p.next = null; + continue; + } + + e.x += e.dxdy; + if (prev != null && prev.x > e.x) + needSort = true; + + prev = e; + e = e.next; + } + + if (needSort) { + //resort to fix the order + activeEdgesTable_ = sortAET_(activeEdgesTable_); + } + } + + private final void addNewEdgesToAET_(int y) { + if (y >= height_) + return; + + Edge edgesOnLine = ySortedEdges_[y]; + if (edgesOnLine != null) { + ySortedEdges_[y] = null; + edgesOnLine = sortAET_(edgesOnLine);//sort new edges + numEdges_ -= sortedNum_;//set in the sortAET + + // merge the edges with sorted AET - O(n) operation + Edge aet = activeEdgesTable_; + boolean first = true; + Edge newEdge = edgesOnLine; + Edge prev_aet = null; + while (aet != null && newEdge != null) { + if (aet.x > newEdge.x) { + if (first) + activeEdgesTable_ = newEdge; + + Edge p = newEdge.next; + newEdge.next = aet; + if (prev_aet != null) { + prev_aet.next = newEdge; + } + + prev_aet = newEdge; + newEdge = p; + } else { // aet.x <= newEdges.x + Edge p = aet.next; + aet.next = newEdge; + if (prev_aet != null) + prev_aet.next = aet; + + prev_aet = aet; + aet = p; + } + + first = false; + } + + if (activeEdgesTable_ == null) + activeEdgesTable_ = edgesOnLine; + } + } + + private static int snap_(int x, int mi, int ma) { + return x < mi ? mi : x > ma ? ma : x; + } + + private final void emitScans_() { + if (activeEdgesTable_ == null) + return; + + int w = 0; + Edge e0 = activeEdgesTable_; + int x0 = (int) (e0.x >> 32); + for (Edge e = e0.next; e != null; e = e.next) { + if (evenOdd_) + w ^= 1; + else + w += e.dir; + + if (e.x > e0.x) { + int x = (int) (e.x >> 32); + if (w != 0) { + int xx0 = snap_(x0, 0, width_); + int xx = snap_(x, 0, width_); + if (xx > xx0 && xx0 < width_) { + scanBuffer_[scanPtr_++] = xx0; + scanBuffer_[scanPtr_++] = xx; + scanBuffer_[scanPtr_++] = e.y; + if (scanPtr_ == scanBuffer_.length) { + callback_.drawScan(scanBuffer_, scanPtr_); + scanPtr_ = 0; + } + } + } + + e0 = e; + x0 = x; + } + } + } + + static private class EdgeComparator implements Comparator { + @Override + public int compare(Edge o1, Edge o2) { + if (o1 == o2) + return 0; + + return o1.x < o2.x ? -1 : o1.x > o2.x ? 1 : 0; + } + } + + private final static EdgeComparator edgeCompare_ = new EdgeComparator(); + + private final Edge sortAET_(Edge aet) { + int num = 0; + for (Edge e = aet; e != null; e = e.next) + num++; + + sortedNum_ = num; + if (num == 1) + return aet; + + if (sortBuffer_ == null) + sortBuffer_ = new Edge[Math.max(num, 16)]; + + else if (sortBuffer_.length < num) + sortBuffer_ = new Edge[Math.max(num, sortBuffer_.length * 2)]; + + { + int i = 0; + for (Edge e = aet; e != null; e = e.next) + sortBuffer_[i++] = e; + } + + if (num == 2) { + if (sortBuffer_[0].x > sortBuffer_[1].x) { + Edge tmp = sortBuffer_[0]; + sortBuffer_[0] = sortBuffer_[1]; + sortBuffer_[1] = tmp; + } + } else { + Arrays.sort(sortBuffer_, 0, num, edgeCompare_); + } + + aet = sortBuffer_[0]; + sortBuffer_[0] = null; + Edge prev = aet; + for (int i = 1; i < num; i++) { + prev.next = sortBuffer_[i]; + prev = sortBuffer_[i]; + sortBuffer_[i] = null; + } + + prev.next = null; + return aet; + } + + private Edge activeEdgesTable_; + private Edge[] ySortedEdges_; + private Edge[] sortBuffer_; + private int[] scanBuffer_; + int scanPtr_; + private ScanCallback callback_; + private int width_; + private int height_; + private int minY_; + private int maxY_; + private int numEdges_; + private int sortedNum_; + private boolean evenOdd_; } diff --git a/src/main/java/com/esri/core/geometry/SimpleStateEnum.java b/src/main/java/com/esri/core/geometry/SimpleStateEnum.java index d0802ebc..87e209d0 100644 --- a/src/main/java/com/esri/core/geometry/SimpleStateEnum.java +++ b/src/main/java/com/esri/core/geometry/SimpleStateEnum.java @@ -1,24 +1,24 @@ package com.esri.core.geometry; public enum SimpleStateEnum { - // on creation, after projection and after generalization a geometry has state simple unknown (not know if simple or not) - SIMPLE_UNKNOWN, - // weak simple (no self intersections, ring orientation is correct, but ring order is not) - WEAK_SIMPLE, - // same as weak simple + OGC ring order. - STRONG_SIMPLE, - // is_simple method has been run on the geometry and it is known to be non-simple, but the reason is unknown - NON_SIMPLE, - // non-simple, because the structure is bad (0 size path, for example). - STRUCTURE_FLAW, - // Non-simple, because there are degenerate segments. - DEGENERATE_SEGMENTS, - // Non-simple, because not clustered properly, that is there are non-coincident vertices closer than tolerance. - CLUSTERING, - // Non-simple, because not cracked properly (intersecting segments, overlaping segments) - CRACKING, - // Non-simple, because there are crossovers (self intersections that are not cracking case). - CROSS_OVER, - // Non-simple, because holes or exteriors have wrong orientation. - RING_ORIENTATION, + // on creation, after projection and after generalization a geometry has state simple unknown (not know if simple or not) + SIMPLE_UNKNOWN, + // weak simple (no self intersections, ring orientation is correct, but ring order is not) + WEAK_SIMPLE, + // same as weak simple + OGC ring order. + STRONG_SIMPLE, + // is_simple method has been run on the geometry and it is known to be non-simple, but the reason is unknown + NON_SIMPLE, + // non-simple, because the structure is bad (0 size path, for example). + STRUCTURE_FLAW, + // Non-simple, because there are degenerate segments. + DEGENERATE_SEGMENTS, + // Non-simple, because not clustered properly, that is there are non-coincident vertices closer than tolerance. + CLUSTERING, + // Non-simple, because not cracked properly (intersecting segments, overlaping segments) + CRACKING, + // Non-simple, because there are crossovers (self intersections that are not cracking case). + CROSS_OVER, + // Non-simple, because holes or exteriors have wrong orientation. + RING_ORIENTATION, } diff --git a/src/main/java/com/esri/core/geometry/SimpleStringCursor.java b/src/main/java/com/esri/core/geometry/SimpleStringCursor.java index 4b9509b6..e3a0b836 100644 --- a/src/main/java/com/esri/core/geometry/SimpleStringCursor.java +++ b/src/main/java/com/esri/core/geometry/SimpleStringCursor.java @@ -7,116 +7,122 @@ import java.util.stream.Collectors; public class SimpleStringCursor extends StringCursor { - private ArrayDeque m_arrayDeque; - private ArrayDeque m_simpleStates; - private ArrayDeque m_ids; - private ArrayDeque m_featureIDs; - private String m_currentFeatureID = ""; - private long m_current_id = -1L; - private SimpleStateEnum m_current_state = SimpleStateEnum.SIMPLE_UNKNOWN; - - @Deprecated - public SimpleStringCursor(String inputString) { - m_arrayDeque = new ArrayDeque<>(1); - m_arrayDeque.push(inputString); - } - - public SimpleStringCursor(String inputString, long id) { - m_arrayDeque = new ArrayDeque<>(1); - m_arrayDeque.push(inputString); - m_ids = new ArrayDeque<>(1); - m_ids.push(id); - } - - public SimpleStringCursor(String inputString, long id, SimpleStateEnum simpleState) { - m_arrayDeque = new ArrayDeque<>(1); - m_arrayDeque.push(inputString); - m_ids = new ArrayDeque<>(1); - m_ids.push(id); - m_simpleStates = new ArrayDeque<>(1); - m_simpleStates.push(simpleState); - } - - public SimpleStringCursor(String inputString, long id, SimpleStateEnum simpleState, String featureID) { - m_arrayDeque = new ArrayDeque<>(1); - m_arrayDeque.push(inputString); - m_ids = new ArrayDeque<>(1); - m_ids.push(id); - m_simpleStates = new ArrayDeque<>(1); - m_simpleStates.push(simpleState); - m_featureIDs = new ArrayDeque<>(1); - m_featureIDs.push(featureID); - } - - @Deprecated - public SimpleStringCursor(String[] inputStringArray) { - m_arrayDeque = Arrays.stream(inputStringArray).collect(Collectors.toCollection(ArrayDeque::new)); - } - - @Deprecated - public SimpleStringCursor(List inputStringArray) { - m_arrayDeque = new ArrayDeque<>(inputStringArray); - } - - public SimpleStringCursor(ArrayDeque arrayDeque, ArrayDeque ids) { - m_ids = ids; - m_arrayDeque = arrayDeque; - } - - public SimpleStringCursor(ArrayDeque arrayDeque, ArrayDeque ids, ArrayDeque simpleStates) { - m_ids = ids; - m_arrayDeque = arrayDeque; - m_simpleStates = simpleStates; - } - - public SimpleStringCursor(ArrayDeque arrayDeque, - ArrayDeque ids, - ArrayDeque simpleStates, - ArrayDeque featureIDs) { - if ((arrayDeque.size() & ids.size() & simpleStates.size() & featureIDs.size()) != arrayDeque.size()) { - throw new GeometryException("arrays must be same size"); - } - m_arrayDeque = arrayDeque; - m_ids = ids; - m_simpleStates = simpleStates; - m_featureIDs = featureIDs; - } - - @Override - public boolean hasNext() { return m_arrayDeque.size() > 0; } - - @Override - public long getID() { - return m_current_id; - } - - @Override - public SimpleStateEnum getSimpleState() { return m_current_state; } - - @Override - public String getFeatureID() { return m_currentFeatureID; } - - void _incrementInternals() { - if (m_ids != null && !m_ids.isEmpty()) { - m_current_id = m_ids.pop(); - } else { - m_current_id++; - } - - if (m_simpleStates != null && !m_simpleStates.isEmpty()) { - m_current_state = m_simpleStates.pop(); - } - if (m_featureIDs != null && !m_featureIDs.isEmpty()) { - m_currentFeatureID = m_featureIDs.pop(); - } - } - - public String next() { - if (hasNext()) { - _incrementInternals(); - return m_arrayDeque.pop(); - } - - return null; - } + private ArrayDeque m_arrayDeque; + private ArrayDeque m_simpleStates; + private ArrayDeque m_ids; + private ArrayDeque m_featureIDs; + private String m_currentFeatureID = ""; + private long m_current_id = -1L; + private SimpleStateEnum m_current_state = SimpleStateEnum.SIMPLE_UNKNOWN; + + @Deprecated + public SimpleStringCursor(String inputString) { + m_arrayDeque = new ArrayDeque<>(1); + m_arrayDeque.push(inputString); + } + + public SimpleStringCursor(String inputString, long id) { + m_arrayDeque = new ArrayDeque<>(1); + m_arrayDeque.push(inputString); + m_ids = new ArrayDeque<>(1); + m_ids.push(id); + } + + public SimpleStringCursor(String inputString, long id, SimpleStateEnum simpleState) { + m_arrayDeque = new ArrayDeque<>(1); + m_arrayDeque.push(inputString); + m_ids = new ArrayDeque<>(1); + m_ids.push(id); + m_simpleStates = new ArrayDeque<>(1); + m_simpleStates.push(simpleState); + } + + public SimpleStringCursor(String inputString, long id, SimpleStateEnum simpleState, String featureID) { + m_arrayDeque = new ArrayDeque<>(1); + m_arrayDeque.push(inputString); + m_ids = new ArrayDeque<>(1); + m_ids.push(id); + m_simpleStates = new ArrayDeque<>(1); + m_simpleStates.push(simpleState); + m_featureIDs = new ArrayDeque<>(1); + m_featureIDs.push(featureID); + } + + @Deprecated + public SimpleStringCursor(String[] inputStringArray) { + m_arrayDeque = Arrays.stream(inputStringArray).collect(Collectors.toCollection(ArrayDeque::new)); + } + + @Deprecated + public SimpleStringCursor(List inputStringArray) { + m_arrayDeque = new ArrayDeque<>(inputStringArray); + } + + public SimpleStringCursor(ArrayDeque arrayDeque, ArrayDeque ids) { + m_ids = ids; + m_arrayDeque = arrayDeque; + } + + public SimpleStringCursor(ArrayDeque arrayDeque, ArrayDeque ids, ArrayDeque simpleStates) { + m_ids = ids; + m_arrayDeque = arrayDeque; + m_simpleStates = simpleStates; + } + + public SimpleStringCursor(ArrayDeque arrayDeque, + ArrayDeque ids, + ArrayDeque simpleStates, + ArrayDeque featureIDs) { + if ((arrayDeque.size() & ids.size() & simpleStates.size() & featureIDs.size()) != arrayDeque.size()) { + throw new GeometryException("arrays must be same size"); + } + m_arrayDeque = arrayDeque; + m_ids = ids; + m_simpleStates = simpleStates; + m_featureIDs = featureIDs; + } + + @Override + public boolean hasNext() { + return m_arrayDeque.size() > 0; + } + + @Override + public long getID() { + return m_current_id; + } + + @Override + public SimpleStateEnum getSimpleState() { + return m_current_state; + } + + @Override + public String getFeatureID() { + return m_currentFeatureID; + } + + void _incrementInternals() { + if (m_ids != null && !m_ids.isEmpty()) { + m_current_id = m_ids.pop(); + } else { + m_current_id++; + } + + if (m_simpleStates != null && !m_simpleStates.isEmpty()) { + m_current_state = m_simpleStates.pop(); + } + if (m_featureIDs != null && !m_featureIDs.isEmpty()) { + m_currentFeatureID = m_featureIDs.pop(); + } + } + + public String next() { + if (hasNext()) { + _incrementInternals(); + return m_arrayDeque.pop(); + } + + return null; + } } diff --git a/src/main/java/com/esri/core/geometry/Simplificator.java b/src/main/java/com/esri/core/geometry/Simplificator.java index 3f9bf314..067402cb 100644 --- a/src/main/java/com/esri/core/geometry/Simplificator.java +++ b/src/main/java/com/esri/core/geometry/Simplificator.java @@ -24,1019 +24,1017 @@ package com.esri.core.geometry; class Simplificator { - private EditShape m_shape; - private int m_geometry; - private IndexMultiDCList m_sortedVertices; - - private AttributeStreamOfInt32 m_bunchEdgeEndPoints; - private AttributeStreamOfInt32 m_bunchEdgeCenterPoints; - private AttributeStreamOfInt32 m_bunchEdgeIndices; - // private AttributeStreamOfInt32 m_orphanVertices; - - private int m_dbgCounter; - private int m_sortedVerticesListIndex; - private int m_userIndexSortedIndexToVertex; - private int m_userIndexSortedAngleIndexToVertex; - private int m_nextVertexToProcess; - private int m_firstCoincidentVertex; - private int m_knownSimpleResult; - private boolean m_bWinding; - private boolean m_fixSelfTangency; - private ProgressTracker m_progressTracker; - - private void _beforeRemoveVertex(int vertex, boolean bChangePathFirst) { - int vertexlistIndex = m_shape.getUserIndex(vertex, - m_userIndexSortedIndexToVertex); - - if (m_nextVertexToProcess == vertexlistIndex) { - m_nextVertexToProcess = m_sortedVertices - .getNext(m_nextVertexToProcess); - } - - if (m_firstCoincidentVertex == vertexlistIndex) - m_firstCoincidentVertex = m_sortedVertices - .getNext(m_firstCoincidentVertex); - - m_sortedVertices.deleteElement(m_sortedVerticesListIndex, - vertexlistIndex); - _removeAngleSortInfo(vertex); - if (bChangePathFirst) { - int path = m_shape.getPathFromVertex(vertex); - if (path != -1) { - int first = m_shape.getFirstVertex(path); - if (first == vertex) { - int next = m_shape.getNextVertex(vertex); - if (next != vertex) { - int p = m_shape.getPathFromVertex(next); - if (p == path) { - m_shape.setFirstVertex_(path, next); - return; - } else { - int prev = m_shape.getPrevVertex(vertex); - if (prev != vertex) { - p = m_shape.getPathFromVertex(prev); - if (p == path) { - m_shape.setFirstVertex_(path, prev); - return; - } - } - } - } - - m_shape.setFirstVertex_(path, -1); - m_shape.setLastVertex_(path, -1); - } - } - } - } - - static class SimplificatorAngleComparer extends - AttributeStreamOfInt32.IntComparator { - Simplificator m_parent; - - public SimplificatorAngleComparer(Simplificator parent) { - m_parent = parent; - } - - @Override - public int compare(int v1, int v2) { - return m_parent._compareAngles(v1, v2); - } - - } - - private boolean _processBunch() { - boolean bModified = false; - int iter = 0; - Point2D ptCenter = new Point2D(); - while (true) { - m_dbgCounter++;// only for debugging - iter++; - // _ASSERT(iter < 10); - if (m_bunchEdgeEndPoints == null) { - m_bunchEdgeEndPoints = new AttributeStreamOfInt32(0); - m_bunchEdgeCenterPoints = new AttributeStreamOfInt32(0); - m_bunchEdgeIndices = new AttributeStreamOfInt32(0); - } else { - m_bunchEdgeEndPoints.clear(false); - m_bunchEdgeCenterPoints.clear(false); - m_bunchEdgeIndices.clear(false); - } - - int currentVertex = m_firstCoincidentVertex; - int index = 0; - boolean bFirst = true; - while (currentVertex != m_nextVertexToProcess) { - int v = m_sortedVertices.getData(currentVertex); - {// debug - Point2D pt = new Point2D(); - m_shape.getXY(v, pt); - double y = pt.x; - } - if (bFirst) { - m_shape.getXY(v, ptCenter); - bFirst = false; - } - int vertP = m_shape.getPrevVertex(v); - int vertN = m_shape.getNextVertex(v); - // _ASSERT(vertP != vertN || m_shape.getPrevVertex(vertN) == v - // && m_shape.getNextVertex(vertP) == v); - - int id = m_shape.getUserIndex(vertP, - m_userIndexSortedAngleIndexToVertex); - if (id != 0xdeadbeef)// avoid adding a point twice - { - // _ASSERT(id == -1); - m_bunchEdgeEndPoints.add(vertP); - m_shape.setUserIndex(vertP, - m_userIndexSortedAngleIndexToVertex, 0xdeadbeef);// mark - // that - // it - // has - // been - // already - // added - m_bunchEdgeCenterPoints.add(v); - m_bunchEdgeIndices.add(index++); - } - - int id2 = m_shape.getUserIndex(vertN, - m_userIndexSortedAngleIndexToVertex); - if (id2 != 0xdeadbeef) // avoid adding a point twice - { - // _ASSERT(id2 == -1); - m_bunchEdgeEndPoints.add(vertN); - m_shape.setUserIndex(vertN, - m_userIndexSortedAngleIndexToVertex, 0xdeadbeef);// mark - // that - // it - // has - // been - // already - // added - m_bunchEdgeCenterPoints.add(v); - m_bunchEdgeIndices.add(index++); - } - - currentVertex = m_sortedVertices.getNext(currentVertex); - } - - if (m_bunchEdgeEndPoints.size() < 2) - break; - - // Sort the bunch edpoints by angle (angle between the axis x and - // the edge, connecting the endpoint with the bunch center) - m_bunchEdgeIndices.Sort(0, m_bunchEdgeIndices.size(), - new SimplificatorAngleComparer(this)); - // SORTDYNAMICARRAYEX(m_bunchEdgeIndices, int, 0, - // m_bunchEdgeIndices.size(), SimplificatorAngleComparer, this); - - for (int i = 0, n = m_bunchEdgeIndices.size(); i < n; i++) { - int indexL = m_bunchEdgeIndices.get(i); - int vertex = m_bunchEdgeEndPoints.get(indexL); - m_shape.setUserIndex(vertex, - m_userIndexSortedAngleIndexToVertex, i);// rember the - // sort by angle - // order - {// debug - Point2D pt = new Point2D(); - m_shape.getXY(vertex, pt); - double y = pt.x; - } - } - - boolean bCrossOverResolved = _processCrossOvers(ptCenter);// see of - // there - // are - // crossing - // over - // edges. - for (int i = 0, n = m_bunchEdgeIndices.size(); i < n; i++) { - int indexL = m_bunchEdgeIndices.get(i); - if (indexL == -1) - continue; - int vertex = m_bunchEdgeEndPoints.get(indexL); - m_shape.setUserIndex(vertex, - m_userIndexSortedAngleIndexToVertex, -1);// remove - // mapping - } - - if (bCrossOverResolved) { - bModified = true; - continue; - } - - break; - } - - return bModified; - } - - private boolean _processCrossOvers(Point2D ptCenter) { - boolean bFound = false; - - // Resolve all overlaps - boolean bContinue = true; - while (bContinue) { - // The nearest pairts in the middle of the list - bContinue = false; - int index1 = 0; - if (m_bunchEdgeIndices.get(index1) == -1) - index1 = _getNextEdgeIndex(index1); - - int index2 = _getNextEdgeIndex(index1); - - for (int i = 0, n = m_bunchEdgeIndices.size(); i < n - && index1 != -1 && index2 != -1 && index1 != index2; i++) { - int edgeindex1 = m_bunchEdgeIndices.get(index1); - int edgeindex2 = m_bunchEdgeIndices.get(index2); - - int vertexB1 = m_bunchEdgeEndPoints.get(edgeindex1); - int vertexB2 = m_bunchEdgeEndPoints.get(edgeindex2); - // _ASSERT(vertexB2 != vertexB1); - - int vertexA1 = m_shape.getNextVertex(vertexB1); - if (!m_shape.isEqualXY(vertexA1, ptCenter)) - vertexA1 = m_shape.getPrevVertex(vertexB1); - int vertexA2 = m_shape.getNextVertex(vertexB2); - if (!m_shape.isEqualXY(vertexA2, ptCenter)) - vertexA2 = m_shape.getPrevVertex(vertexB2); - - // _ASSERT(m_shape.isEqualXY(vertexA1, vertexA2)); - // _ASSERT(m_shape.isEqualXY(vertexA1, ptCenter)); - - boolean bDirection1 = _getDirection(vertexA1, vertexB1); - boolean bDirection2 = _getDirection(vertexA2, vertexB2); - int vertexC1 = bDirection1 ? m_shape.getPrevVertex(vertexA1) - : m_shape.getNextVertex(vertexA1); - int vertexC2 = bDirection2 ? m_shape.getPrevVertex(vertexA2) - : m_shape.getNextVertex(vertexA2); - - boolean bOverlap = false; - if (_removeSpike(vertexA1)) - bOverlap = true; - else if (_removeSpike(vertexA2)) - bOverlap = true; - else if (_removeSpike(vertexB1)) - bOverlap = true; - else if (_removeSpike(vertexB2)) - bOverlap = true; - else if (_removeSpike(vertexC1)) - bOverlap = true; - else if (_removeSpike(vertexC2)) - bOverlap = true; - - if (!bOverlap && m_shape.isEqualXY(vertexB1, vertexB2)) { - bOverlap = true; - _resolveOverlap(bDirection1, bDirection2, vertexA1, - vertexB1, vertexA2, vertexB2); - } - - if (!bOverlap && m_shape.isEqualXY(vertexC1, vertexC2)) { - bOverlap = true; - _resolveOverlap(!bDirection1, !bDirection2, vertexA1, - vertexC1, vertexA2, vertexC2); - } - - if (bOverlap) - bFound = true; - - bContinue |= bOverlap; - - index1 = _getNextEdgeIndex(index1); - index2 = _getNextEdgeIndex(index1); - } - } - - if (!bFound) {// resolve all cross overs - int index1 = 0; - if (m_bunchEdgeIndices.get(index1) == -1) - index1 = _getNextEdgeIndex(index1); - - int index2 = _getNextEdgeIndex(index1); - - for (int i = 0, n = m_bunchEdgeIndices.size(); i < n - && index1 != -1 && index2 != -1 && index1 != index2; i++) { - int edgeindex1 = m_bunchEdgeIndices.get(index1); - int edgeindex2 = m_bunchEdgeIndices.get(index2); - - int vertexB1 = m_bunchEdgeEndPoints.get(edgeindex1); - int vertexB2 = m_bunchEdgeEndPoints.get(edgeindex2); - - int vertexA1 = m_shape.getNextVertex(vertexB1); - if (!m_shape.isEqualXY(vertexA1, ptCenter)) - vertexA1 = m_shape.getPrevVertex(vertexB1); - int vertexA2 = m_shape.getNextVertex(vertexB2); - if (!m_shape.isEqualXY(vertexA2, ptCenter)) - vertexA2 = m_shape.getPrevVertex(vertexB2); - - // _ASSERT(m_shape.isEqualXY(vertexA1, vertexA2)); - // _ASSERT(m_shape.isEqualXY(vertexA1, ptCenter)); - - boolean bDirection1 = _getDirection(vertexA1, vertexB1); - boolean bDirection2 = _getDirection(vertexA2, vertexB2); - int vertexC1 = bDirection1 ? m_shape.getPrevVertex(vertexA1) - : m_shape.getNextVertex(vertexA1); - int vertexC2 = bDirection2 ? m_shape.getPrevVertex(vertexA2) - : m_shape.getNextVertex(vertexA2); - - if (_detectAndResolveCrossOver(bDirection1, bDirection2, - vertexB1, vertexA1, vertexC1, vertexB2, vertexA2, - vertexC2)) { - bFound = true; - } - - index1 = _getNextEdgeIndex(index1); - index2 = _getNextEdgeIndex(index1); - } - } - - return bFound; - } - - static class SimplificatorVertexComparer extends - AttributeStreamOfInt32.IntComparator { - Simplificator m_parent; - - SimplificatorVertexComparer(Simplificator parent) { - m_parent = parent; - } - - @Override - public int compare(int v1, int v2) { - return m_parent._compareVerticesSimple(v1, v2); - } - - } - - private boolean _simplify() { - if (m_shape.getGeometryType(m_geometry) == Polygon.Type.Polygon.value() - && m_shape.getFillRule(m_geometry) == Polygon.FillRule.enumFillRuleWinding) - - { - TopologicalOperations ops = new TopologicalOperations(); - ops.planarSimplifyNoCrackingAndCluster(m_fixSelfTangency, - m_shape, m_geometry, m_progressTracker); - assert (m_shape.getFillRule(m_geometry) == Polygon.FillRule.enumFillRuleOddEven); - } - boolean bChanged = false; - boolean bNeedWindingRepeat = true; - boolean bWinding = false; - - m_userIndexSortedIndexToVertex = -1; - m_userIndexSortedAngleIndexToVertex = -1; - - int pointCount = m_shape.getPointCount(m_geometry); - - // Sort vertices lexicographically - // Firstly copy allvertices to an array. - AttributeStreamOfInt32 verticesSorter = new AttributeStreamOfInt32(0); - verticesSorter.reserve(pointCount); - - for (int path = m_shape.getFirstPath(m_geometry); path != -1; path = m_shape - .getNextPath(path)) { - int vertex = m_shape.getFirstVertex(path); - for (int index = 0, n = m_shape.getPathSize(path); index < n; index++) { - verticesSorter.add(vertex); - vertex = m_shape.getNextVertex(vertex); - } - } - - // Sort - verticesSorter.Sort(0, pointCount, - new SimplificatorVertexComparer(this)); - // SORTDYNAMICARRAYEX(verticesSorter, int, 0, pointCount, - // SimplificatorVertexComparer, this); - - // Copy sorted vertices to the m_sortedVertices list. Make a mapping - // from the edit shape vertices to the sorted vertices. - m_userIndexSortedIndexToVertex = m_shape.createUserIndex();// this index - // is used - // to map - // from edit - // shape - // vertex to - // the - // m_sortedVertices - // list - m_sortedVertices = new IndexMultiDCList(); - m_sortedVerticesListIndex = m_sortedVertices.createList(0); - for (int i = 0; i < pointCount; i++) { - int vertex = verticesSorter.get(i); - {// debug - Point2D pt = new Point2D(); - m_shape.getXY(vertex, pt);// for debugging - double y = pt.x; - } - int vertexlistIndex = m_sortedVertices.addElement( - m_sortedVerticesListIndex, vertex); - m_shape.setUserIndex(vertex, m_userIndexSortedIndexToVertex, - vertexlistIndex);// remember the sorted list element on the - // vertex. - // When we remove a vertex, we also remove associated sorted list - // element. - } - - m_userIndexSortedAngleIndexToVertex = m_shape.createUserIndex();// create - // additional - // list - // to - // store - // angular - // sort - // mapping. - - m_nextVertexToProcess = -1; - - if (_cleanupSpikes())// cleanup any spikes on the polygon. - bChanged = true; - - // External iteration loop for the simplificator. - // ST. I am not sure if it actually needs this loop. TODO: figure this - // out. - while (bNeedWindingRepeat) { - bNeedWindingRepeat = false; - - int max_iter = m_shape.getPointCount(m_geometry) + 10 > 30 ? 1000 - : (m_shape.getPointCount(m_geometry) + 10) - * (m_shape.getPointCount(m_geometry) + 10); - - // Simplify polygon - int iRepeatNum = 0; - boolean bNeedRepeat = false; - - // Internal iteration loop for the simplificator. - // ST. I am not sure if it actually needs this loop. TODO: figure - // this out. - do// while (bNeedRepeat); - { - bNeedRepeat = false; - - boolean bVertexRecheck = false; - m_firstCoincidentVertex = -1; - int coincidentCount = 0; - Point2D ptFirst = new Point2D(); - Point2D pt = new Point2D(); - // Main loop of the simplificator. Go through the vertices and - // for those that have same coordinates, - for (int vlistindex = m_sortedVertices - .getFirst(m_sortedVerticesListIndex); vlistindex != IndexMultiDCList - .nullNode(); ) { - int vertex = m_sortedVertices.getData(vlistindex); - {// debug - // Point2D pt = new Point2D(); - m_shape.getXY(vertex, pt); - double d = pt.x; - } - - if (m_firstCoincidentVertex != -1) { - // Point2D pt = new Point2D(); - m_shape.getXY(vertex, pt); - if (ptFirst.isEqual(pt)) { - coincidentCount++; - } else { - ptFirst.setCoords(pt); - m_nextVertexToProcess = vlistindex;// we remeber the - // next index in - // the member - // variable to - // allow it to - // be updated if - // a vertex is - // removed - // inside of the - // _ProcessBunch. - if (coincidentCount > 0) { - boolean result = _processBunch();// process a - // bunch of - // coinciding - // vertices - if (result) {// something has changed. - // Note that ProcessBunch may - // change m_nextVertexToProcess - // and m_firstCoincidentVertex. - bNeedRepeat = true; - if (m_nextVertexToProcess != IndexMultiDCList - .nullNode()) { - int v = m_sortedVertices - .getData(m_nextVertexToProcess); - m_shape.getXY(v, ptFirst); - } - } - } - - vlistindex = m_nextVertexToProcess; - m_firstCoincidentVertex = vlistindex; - coincidentCount = 0; - } - } else { - m_firstCoincidentVertex = vlistindex; - m_shape.getXY(m_sortedVertices.getData(vlistindex), - ptFirst); - coincidentCount = 0; - } - - if (vlistindex != -1)//vlistindex can be set to -1 after ProcessBunch call above - vlistindex = m_sortedVertices.getNext(vlistindex); - } - - m_nextVertexToProcess = -1; - - if (coincidentCount > 0) { - boolean result = _processBunch(); - if (result) - bNeedRepeat = true; - } - - if (iRepeatNum++ > 10) { - throw GeometryException.GeometryInternalError(); - } - - if (bNeedRepeat) - _fixOrphanVertices();// fix broken structure of the shape - - if (_cleanupSpikes()) - bNeedRepeat = true; - - bNeedWindingRepeat |= bNeedRepeat && bWinding; - - bChanged |= bNeedRepeat; - - } while (bNeedRepeat); - - }// while (bNeedWindingRepeat) - - // Now process rings. Fix ring orientation and determine rings that need - // to be deleted. - - m_shape.removeUserIndex(m_userIndexSortedIndexToVertex); - m_shape.removeUserIndex(m_userIndexSortedAngleIndexToVertex); - - bChanged |= RingOrientationFixer.execute(m_shape, m_geometry, - m_sortedVertices, m_fixSelfTangency); - - return bChanged; - } - - private boolean _getDirection(int vert1, int vert2) { - if (m_shape.getNextVertex(vert2) == vert1) { - // _ASSERT(m_shape.getPrevVertex(vert1) == vert2); - return false; - } else { - // _ASSERT(m_shape.getPrevVertex(vert2) == vert1); - // _ASSERT(m_shape.getNextVertex(vert1) == vert2); - return true; - } - } - - private boolean _detectAndResolveCrossOver(boolean bDirection1, - boolean bDirection2, int vertexB1, int vertexA1, int vertexC1, - int vertexB2, int vertexA2, int vertexC2) { - // _ASSERT(!m_shape.isEqualXY(vertexB1, vertexB2)); - // _ASSERT(!m_shape.isEqualXY(vertexC1, vertexC2)); - - if (vertexA1 == vertexA2) { - _removeAngleSortInfo(vertexB1); - _removeAngleSortInfo(vertexB2); - return false; - } - - // _ASSERT(!m_shape.isEqualXY(vertexB1, vertexC2)); - // _ASSERT(!m_shape.isEqualXY(vertexB1, vertexC1)); - // _ASSERT(!m_shape.isEqualXY(vertexB2, vertexC2)); - // _ASSERT(!m_shape.isEqualXY(vertexB2, vertexC1)); - // _ASSERT(!m_shape.isEqualXY(vertexA1, vertexB1)); - // _ASSERT(!m_shape.isEqualXY(vertexA1, vertexC1)); - // _ASSERT(!m_shape.isEqualXY(vertexA2, vertexB2)); - // _ASSERT(!m_shape.isEqualXY(vertexA2, vertexC2)); - - // _ASSERT(m_shape.isEqualXY(vertexA1, vertexA2)); - - // get indices of the vertices for the angle sort. - int iB1 = m_shape.getUserIndex(vertexB1, - m_userIndexSortedAngleIndexToVertex); - int iC1 = m_shape.getUserIndex(vertexC1, - m_userIndexSortedAngleIndexToVertex); - int iB2 = m_shape.getUserIndex(vertexB2, - m_userIndexSortedAngleIndexToVertex); - int iC2 = m_shape.getUserIndex(vertexC2, - m_userIndexSortedAngleIndexToVertex); - // _ASSERT(iB1 >= 0); - // _ASSERT(iC1 >= 0); - // _ASSERT(iB2 >= 0); - // _ASSERT(iC2 >= 0); - // Sort the indices to restore the angle-sort order - int[] ar = new int[8]; - int[] br = new int[4]; - - ar[0] = 0; - br[0] = iB1; - ar[1] = 0; - br[1] = iC1; - ar[2] = 1; - br[2] = iB2; - ar[3] = 1; - br[3] = iC2; - for (int j = 1; j < 4; j++)// insertion sort - { - int key = br[j]; - int data = ar[j]; - int i = j - 1; - while (i >= 0 && br[i] > key) { - br[i + 1] = br[i]; - ar[i + 1] = ar[i]; - i--; - } - br[i + 1] = key; - ar[i + 1] = data; - } - - int detector = 0; - if (ar[0] != 0) - detector |= 1; - if (ar[1] != 0) - detector |= 2; - if (ar[2] != 0) - detector |= 4; - if (ar[3] != 0) - detector |= 8; - if (detector != 5 && detector != 10)// not an overlap - return false; - - if (bDirection1 == bDirection2) { - if (bDirection1) { - m_shape.setNextVertex_(vertexC2, vertexA1); // B1< >B2 - m_shape.setPrevVertex_(vertexA1, vertexC2); // \ / - m_shape.setNextVertex_(vertexC1, vertexA2); // A1A2 - m_shape.setPrevVertex_(vertexA2, vertexC1); // / \ // - // C2> C1 - } - } else { - if (bDirection1) { - m_shape.setPrevVertex_(vertexA1, vertexB2); // B1< >B2 - m_shape.setPrevVertex_(vertexB2, vertexA1); // \ / - m_shape.setNextVertex_(vertexA2, vertexC1); // A1A2 - m_shape.setPrevVertex_(vertexC1, vertexA2); // / \ // - // C2> >C1 - - } - } - - return true; - } - - private void _resolveOverlap(boolean bDirection1, boolean bDirection2, - int vertexA1, int vertexB1, int vertexA2, int vertexB2) { - if (m_bWinding) { - _resolveOverlapWinding(bDirection1, bDirection2, vertexA1, - vertexB1, vertexA2, vertexB2); - } else { - _resolveOverlapOddEven(bDirection1, bDirection2, vertexA1, - vertexB1, vertexA2, vertexB2); - } - } - - private void _resolveOverlapWinding(boolean bDirection1, - boolean bDirection2, int vertexA1, int vertexB1, int vertexA2, - int vertexB2) { - throw new GeometryException("not implemented."); - } - - private void _resolveOverlapOddEven(boolean bDirection1, - boolean bDirection2, int vertexA1, int vertexB1, int vertexA2, - int vertexB2) { - if (bDirection1 != bDirection2) { - if (bDirection1) { - // _ASSERT(m_shape.getNextVertex(vertexA1) == vertexB1); - // _ASSERT(m_shape.getNextVertex(vertexB2) == vertexA2); - m_shape.setNextVertex_(vertexA1, vertexA2); // B1< B2 - m_shape.setPrevVertex_(vertexA2, vertexA1); // | | - m_shape.setNextVertex_(vertexB2, vertexB1); // | | - m_shape.setPrevVertex_(vertexB1, vertexB2); // A1 >A2 - - _transferVertexData(vertexA2, vertexA1); - _beforeRemoveVertex(vertexA2, true); - m_shape.removeVertexInternal_(vertexA2, true); - _removeAngleSortInfo(vertexA1); - _transferVertexData(vertexB2, vertexB1); - _beforeRemoveVertex(vertexB2, true); - m_shape.removeVertexInternal_(vertexB2, false); - _removeAngleSortInfo(vertexB1); - } else { - m_shape.setNextVertex_(vertexA2, vertexA1); // B1 B2< - m_shape.setPrevVertex_(vertexA1, vertexA2); // | | - m_shape.setNextVertex_(vertexB1, vertexB2); // | | - m_shape.setPrevVertex_(vertexB2, vertexB1); // A1< A2 - - _transferVertexData(vertexA2, vertexA1); - _beforeRemoveVertex(vertexA2, true); - m_shape.removeVertexInternal_(vertexA2, false); - _removeAngleSortInfo(vertexA1); - _transferVertexData(vertexB2, vertexB1); - _beforeRemoveVertex(vertexB2, true); - m_shape.removeVertexInternal_(vertexB2, true); - _removeAngleSortInfo(vertexB1); - } - } else// bDirection1 == bDirection2 - { - if (!bDirection1) { - // _ASSERT(m_shape.getNextVertex(vertexB1) == vertexA1); - // _ASSERT(m_shape.getNextVertex(vertexB2) == vertexA2); - } else { - // _ASSERT(m_shape.getNextVertex(vertexA1) == vertexB1); - // _ASSERT(m_shape.getNextVertex(vertexA2) == vertexB2); - } - - // if (m_shape._RingParentageCheckInternal(vertexA1, vertexA2)) - { - int a1 = bDirection1 ? vertexA1 : vertexB1; - int a2 = bDirection2 ? vertexA2 : vertexB2; - int b1 = bDirection1 ? vertexB1 : vertexA1; - int b2 = bDirection2 ? vertexB2 : vertexA2; - - // m_shape.dbgVerifyIntegrity(a1);//debug - // m_shape.dbgVerifyIntegrity(a2);//debug - - boolean bVisitedA1 = false; - m_shape.setNextVertex_(a1, a2); - m_shape.setNextVertex_(a2, a1); - m_shape.setPrevVertex_(b1, b2); - m_shape.setPrevVertex_(b2, b1); - int v = b2; - while (v != a2) { - int prev = m_shape.getPrevVertex(v); - int next = m_shape.getNextVertex(v); - - m_shape.setPrevVertex_(v, next); - m_shape.setNextVertex_(v, prev); - bVisitedA1 |= v == a1; - v = next; - } - - if (!bVisitedA1) { - // a case of two rings being merged - int prev = m_shape.getPrevVertex(a2); - int next = m_shape.getNextVertex(a2); - m_shape.setPrevVertex_(a2, next); - m_shape.setNextVertex_(a2, prev); - } else { - // merge happend on the same ring. - } - - // m_shape.dbgVerifyIntegrity(b1);//debug - // m_shape.dbgVerifyIntegrity(a1);//debug - - _transferVertexData(a2, a1); - _beforeRemoveVertex(a2, true); - m_shape.removeVertexInternal_(a2, false); - _removeAngleSortInfo(a1); - _transferVertexData(b2, b1); - _beforeRemoveVertex(b2, true); - m_shape.removeVertexInternal_(b2, false); - _removeAngleSortInfo(b1); - - // m_shape.dbgVerifyIntegrity(b1);//debug - // m_shape.dbgVerifyIntegrity(a1);//debug - } - } - } - - private boolean _cleanupSpikes() { - boolean bModified = false; - for (int path = m_shape.getFirstPath(m_geometry); path != -1; ) { - int vertex = m_shape.getFirstVertex(path); - for (int vindex = 0, n = m_shape.getPathSize(path); vindex < n - && n > 1; ) { - int prev = m_shape.getPrevVertex(vertex); - int next = m_shape.getNextVertex(vertex); - if (m_shape.isEqualXY(prev, next)) { - bModified = true; - _beforeRemoveVertex(vertex, false); - m_shape.removeVertex(vertex, true);// not internal, because - // path is valid at this - // point - _beforeRemoveVertex(next, false); - m_shape.removeVertex(next, true); - vertex = prev; - vindex = 0; - n = m_shape.getPathSize(path); - } else { - vertex = next; - vindex++; - } - } - - if (m_shape.getPathSize(path) < 2) { - int vertexL = m_shape.getFirstVertex(path); - for (int vindex = 0, n = m_shape.getPathSize(path); vindex < n; vindex++) { - _beforeRemoveVertex(vertexL, false); - vertexL = m_shape.getNextVertex(vertexL); - } - - path = m_shape.removePath(path); - bModified = true; - } else - path = m_shape.getNextPath(path); - } - - return bModified; - } - - private boolean _removeSpike(int vertexIn) { - // m_shape.dbgVerifyIntegrity(vertex);//debug - int vertex = vertexIn; - - // _ASSERT(m_shape.isEqualXY(m_shape.getNextVertex(vertex), - // m_shape.getPrevVertex(vertex))); - boolean bFound = false; - while (true) { - int next = m_shape.getNextVertex(vertex); - int prev = m_shape.getPrevVertex(vertex); - if (next == vertex) {// last vertex in a ring - _beforeRemoveVertex(vertex, true); - m_shape.removeVertexInternal_(vertex, false); - return true; - } - - if (!m_shape.isEqualXY(next, prev)) - break; - - bFound = true; - _removeAngleSortInfo(prev); - _removeAngleSortInfo(next); - _beforeRemoveVertex(vertex, true); - m_shape.removeVertexInternal_(vertex, false); - // m_shape.dbgVerifyIntegrity(prev);//debug - _transferVertexData(next, prev); - _beforeRemoveVertex(next, true); - m_shape.removeVertexInternal_(next, true); - if (next == prev) - break;// deleted the last vertex - - // m_shape.dbgVerifyIntegrity(prev);//debug - - vertex = prev; - } - return bFound; - } - - private void _fixOrphanVertices() { - int pathCount = 0; - // clean any path info - for (int node = m_sortedVertices.getFirst(m_sortedVertices - .getFirstList()); node != -1; node = m_sortedVertices - .getNext(node)) { - int vertex = m_sortedVertices.getData(node); - m_shape.setPathToVertex_(vertex, -1); - } - int geometrySize = 0; - for (int path = m_shape.getFirstPath(m_geometry); path != -1; ) { - int first = m_shape.getFirstVertex(path); - if (first == -1 || m_shape.getPathFromVertex(first) != -1) { - int p = path; - path = m_shape.getNextPath(path); - m_shape.removePathOnly_(p); - continue; - } - - m_shape.setPathToVertex_(first, path); - int pathSize = 1; - for (int vertex = m_shape.getNextVertex(first); vertex != first; vertex = m_shape - .getNextVertex(vertex)) { - m_shape.setPathToVertex_(vertex, path); - pathSize++; - } - m_shape.setRingAreaValid_(path, false); - m_shape.setPathSize_(path, pathSize); - m_shape.setLastVertex_(path, m_shape.getPrevVertex(first)); - geometrySize += pathSize; - pathCount++; - path = m_shape.getNextPath(path); - } - - // Some vertices do not belong to any path. We have to create new path - // objects for those. - // Produce new paths for the orphan vertices. - for (int node = m_sortedVertices.getFirst(m_sortedVertices - .getFirstList()); node != -1; node = m_sortedVertices - .getNext(node)) { - int vertex = m_sortedVertices.getData(node); - if (m_shape.getPathFromVertex(vertex) != -1) - continue; - - int path = m_shape.insertClosedPath_(m_geometry, -1, vertex, vertex, null); - geometrySize += m_shape.getPathSize(path); - pathCount++; - } - - m_shape.setGeometryPathCount_(m_geometry, pathCount); - m_shape.setGeometryVertexCount_(m_geometry, geometrySize); - int totalPointCount = 0; - for (int geometry = m_shape.getFirstGeometry(); geometry != -1; geometry = m_shape.getNextGeometry(geometry)) { - totalPointCount += m_shape.getPointCount(geometry); - } - - m_shape.setTotalPointCount_(totalPointCount); - } - - private int _getNextEdgeIndex(int indexIn) { - int index = indexIn; - for (int i = 0, n = m_bunchEdgeIndices.size() - 1; i < n; i++) { - index = (index + 1) % m_bunchEdgeIndices.size(); - if (m_bunchEdgeIndices.get(index) != -1) - return index; - } - return -1; - } - - private void _transferVertexData(int vertexFrom, int vertexTo) { - int v1 = m_shape.getUserIndex(vertexTo, m_userIndexSortedIndexToVertex); - int v2 = m_shape.getUserIndex(vertexTo, - m_userIndexSortedAngleIndexToVertex); - m_shape.transferAllDataToTheVertex(vertexFrom, vertexTo); - m_shape.setUserIndex(vertexTo, m_userIndexSortedIndexToVertex, v1); - m_shape.setUserIndex(vertexTo, m_userIndexSortedAngleIndexToVertex, v2); - } - - private void _removeAngleSortInfo(int vertex) { - int angleIndex = m_shape.getUserIndex(vertex, - m_userIndexSortedAngleIndexToVertex); - if (angleIndex != -1) { - m_bunchEdgeIndices.set(angleIndex, -1); - m_shape.setUserIndex(vertex, m_userIndexSortedAngleIndexToVertex, - -1); - } - } - - protected Simplificator() { - m_dbgCounter = 0; - } - - public static boolean execute(EditShape shape, int geometry, - int knownSimpleResult, boolean fixSelfTangency, ProgressTracker progressTracker) { - Simplificator simplificator = new Simplificator(); - simplificator.m_shape = shape; - // simplificator.m_bWinding = bWinding; - simplificator.m_geometry = geometry; - simplificator.m_knownSimpleResult = knownSimpleResult; - simplificator.m_fixSelfTangency = fixSelfTangency; - simplificator.m_progressTracker = progressTracker; - return simplificator._simplify(); - } - - int _compareVerticesSimple(int v1, int v2) { - Point2D pt1 = new Point2D(); - m_shape.getXY(v1, pt1); - Point2D pt2 = new Point2D(); - m_shape.getXY(v2, pt2); - int res = pt1.compare(pt2); - if (res == 0) {// sort equal vertices by the path ID - int i1 = m_shape.getPathFromVertex(v1); - int i2 = m_shape.getPathFromVertex(v2); - res = i1 < i2 ? -1 : (i1 == i2 ? 0 : 1); - } - - return res; - } - - int _compareAngles(int index1, int index2) { - int vert1 = m_bunchEdgeEndPoints.get(index1); - Point2D pt1 = new Point2D(); - m_shape.getXY(vert1, pt1); - Point2D pt2 = new Point2D(); - int vert2 = m_bunchEdgeEndPoints.get(index2); - m_shape.getXY(vert2, pt2); - - if (pt1.isEqual(pt2)) - return 0;// overlap case - - int vert10 = m_bunchEdgeCenterPoints.get(index1); - Point2D pt10 = new Point2D(); - m_shape.getXY(vert10, pt10); - - int vert20 = m_bunchEdgeCenterPoints.get(index2); - Point2D pt20 = new Point2D(); - m_shape.getXY(vert20, pt20); - // _ASSERT(pt10.isEqual(pt20)); - - Point2D v1 = new Point2D(); - v1.sub(pt1, pt10); - Point2D v2 = new Point2D(); - v2.sub(pt2, pt20); - int result = Point2D._compareVectors(v1, v2); - return result; - } + private EditShape m_shape; + private int m_geometry; + private IndexMultiDCList m_sortedVertices; + + private AttributeStreamOfInt32 m_bunchEdgeEndPoints; + private AttributeStreamOfInt32 m_bunchEdgeCenterPoints; + private AttributeStreamOfInt32 m_bunchEdgeIndices; + // private AttributeStreamOfInt32 m_orphanVertices; + + private int m_dbgCounter; + private int m_sortedVerticesListIndex; + private int m_userIndexSortedIndexToVertex; + private int m_userIndexSortedAngleIndexToVertex; + private int m_nextVertexToProcess; + private int m_firstCoincidentVertex; + private int m_knownSimpleResult; + private boolean m_bWinding; + private boolean m_fixSelfTangency; + private ProgressTracker m_progressTracker; + + private void _beforeRemoveVertex(int vertex, boolean bChangePathFirst) { + int vertexlistIndex = m_shape.getUserIndex(vertex, + m_userIndexSortedIndexToVertex); + + if (m_nextVertexToProcess == vertexlistIndex) { + m_nextVertexToProcess = m_sortedVertices + .getNext(m_nextVertexToProcess); + } + + if (m_firstCoincidentVertex == vertexlistIndex) + m_firstCoincidentVertex = m_sortedVertices + .getNext(m_firstCoincidentVertex); + + m_sortedVertices.deleteElement(m_sortedVerticesListIndex, + vertexlistIndex); + _removeAngleSortInfo(vertex); + if (bChangePathFirst) { + int path = m_shape.getPathFromVertex(vertex); + if (path != -1) { + int first = m_shape.getFirstVertex(path); + if (first == vertex) { + int next = m_shape.getNextVertex(vertex); + if (next != vertex) { + int p = m_shape.getPathFromVertex(next); + if (p == path) { + m_shape.setFirstVertex_(path, next); + return; + } else { + int prev = m_shape.getPrevVertex(vertex); + if (prev != vertex) { + p = m_shape.getPathFromVertex(prev); + if (p == path) { + m_shape.setFirstVertex_(path, prev); + return; + } + } + } + } + + m_shape.setFirstVertex_(path, -1); + m_shape.setLastVertex_(path, -1); + } + } + } + } + + static class SimplificatorAngleComparer extends + AttributeStreamOfInt32.IntComparator { + Simplificator m_parent; + + public SimplificatorAngleComparer(Simplificator parent) { + m_parent = parent; + } + + @Override + public int compare(int v1, int v2) { + return m_parent._compareAngles(v1, v2); + } + + } + + private boolean _processBunch() { + boolean bModified = false; + int iter = 0; + Point2D ptCenter = new Point2D(); + while (true) { + m_dbgCounter++;// only for debugging + iter++; + // _ASSERT(iter < 10); + if (m_bunchEdgeEndPoints == null) { + m_bunchEdgeEndPoints = new AttributeStreamOfInt32(0); + m_bunchEdgeCenterPoints = new AttributeStreamOfInt32(0); + m_bunchEdgeIndices = new AttributeStreamOfInt32(0); + } else { + m_bunchEdgeEndPoints.clear(false); + m_bunchEdgeCenterPoints.clear(false); + m_bunchEdgeIndices.clear(false); + } + + int currentVertex = m_firstCoincidentVertex; + int index = 0; + boolean bFirst = true; + while (currentVertex != m_nextVertexToProcess) { + int v = m_sortedVertices.getData(currentVertex); + {// debug + Point2D pt = new Point2D(); + m_shape.getXY(v, pt); + double y = pt.x; + } + if (bFirst) { + m_shape.getXY(v, ptCenter); + bFirst = false; + } + int vertP = m_shape.getPrevVertex(v); + int vertN = m_shape.getNextVertex(v); + // _ASSERT(vertP != vertN || m_shape.getPrevVertex(vertN) == v + // && m_shape.getNextVertex(vertP) == v); + + int id = m_shape.getUserIndex(vertP, + m_userIndexSortedAngleIndexToVertex); + if (id != 0xdeadbeef)// avoid adding a point twice + { + // _ASSERT(id == -1); + m_bunchEdgeEndPoints.add(vertP); + m_shape.setUserIndex(vertP, + m_userIndexSortedAngleIndexToVertex, 0xdeadbeef);// mark + // that + // it + // has + // been + // already + // added + m_bunchEdgeCenterPoints.add(v); + m_bunchEdgeIndices.add(index++); + } + + int id2 = m_shape.getUserIndex(vertN, + m_userIndexSortedAngleIndexToVertex); + if (id2 != 0xdeadbeef) // avoid adding a point twice + { + // _ASSERT(id2 == -1); + m_bunchEdgeEndPoints.add(vertN); + m_shape.setUserIndex(vertN, + m_userIndexSortedAngleIndexToVertex, 0xdeadbeef);// mark + // that + // it + // has + // been + // already + // added + m_bunchEdgeCenterPoints.add(v); + m_bunchEdgeIndices.add(index++); + } + + currentVertex = m_sortedVertices.getNext(currentVertex); + } + + if (m_bunchEdgeEndPoints.size() < 2) + break; + + // Sort the bunch edpoints by angle (angle between the axis x and + // the edge, connecting the endpoint with the bunch center) + m_bunchEdgeIndices.Sort(0, m_bunchEdgeIndices.size(), + new SimplificatorAngleComparer(this)); + // SORTDYNAMICARRAYEX(m_bunchEdgeIndices, int, 0, + // m_bunchEdgeIndices.size(), SimplificatorAngleComparer, this); + + for (int i = 0, n = m_bunchEdgeIndices.size(); i < n; i++) { + int indexL = m_bunchEdgeIndices.get(i); + int vertex = m_bunchEdgeEndPoints.get(indexL); + m_shape.setUserIndex(vertex, + m_userIndexSortedAngleIndexToVertex, i);// rember the + // sort by angle + // order + {// debug + Point2D pt = new Point2D(); + m_shape.getXY(vertex, pt); + double y = pt.x; + } + } + + boolean bCrossOverResolved = _processCrossOvers(ptCenter);// see of + // there + // are + // crossing + // over + // edges. + for (int i = 0, n = m_bunchEdgeIndices.size(); i < n; i++) { + int indexL = m_bunchEdgeIndices.get(i); + if (indexL == -1) + continue; + int vertex = m_bunchEdgeEndPoints.get(indexL); + m_shape.setUserIndex(vertex, + m_userIndexSortedAngleIndexToVertex, -1);// remove + // mapping + } + + if (bCrossOverResolved) { + bModified = true; + continue; + } + + break; + } + + return bModified; + } + + private boolean _processCrossOvers(Point2D ptCenter) { + boolean bFound = false; + + // Resolve all overlaps + boolean bContinue = true; + while (bContinue) { + // The nearest pairts in the middle of the list + bContinue = false; + int index1 = 0; + if (m_bunchEdgeIndices.get(index1) == -1) + index1 = _getNextEdgeIndex(index1); + + int index2 = _getNextEdgeIndex(index1); + + for (int i = 0, n = m_bunchEdgeIndices.size(); i < n + && index1 != -1 && index2 != -1 && index1 != index2; i++) { + int edgeindex1 = m_bunchEdgeIndices.get(index1); + int edgeindex2 = m_bunchEdgeIndices.get(index2); + + int vertexB1 = m_bunchEdgeEndPoints.get(edgeindex1); + int vertexB2 = m_bunchEdgeEndPoints.get(edgeindex2); + // _ASSERT(vertexB2 != vertexB1); + + int vertexA1 = m_shape.getNextVertex(vertexB1); + if (!m_shape.isEqualXY(vertexA1, ptCenter)) + vertexA1 = m_shape.getPrevVertex(vertexB1); + int vertexA2 = m_shape.getNextVertex(vertexB2); + if (!m_shape.isEqualXY(vertexA2, ptCenter)) + vertexA2 = m_shape.getPrevVertex(vertexB2); + + // _ASSERT(m_shape.isEqualXY(vertexA1, vertexA2)); + // _ASSERT(m_shape.isEqualXY(vertexA1, ptCenter)); + + boolean bDirection1 = _getDirection(vertexA1, vertexB1); + boolean bDirection2 = _getDirection(vertexA2, vertexB2); + int vertexC1 = bDirection1 ? m_shape.getPrevVertex(vertexA1) + : m_shape.getNextVertex(vertexA1); + int vertexC2 = bDirection2 ? m_shape.getPrevVertex(vertexA2) + : m_shape.getNextVertex(vertexA2); + + boolean bOverlap = false; + if (_removeSpike(vertexA1)) + bOverlap = true; + else if (_removeSpike(vertexA2)) + bOverlap = true; + else if (_removeSpike(vertexB1)) + bOverlap = true; + else if (_removeSpike(vertexB2)) + bOverlap = true; + else if (_removeSpike(vertexC1)) + bOverlap = true; + else if (_removeSpike(vertexC2)) + bOverlap = true; + + if (!bOverlap && m_shape.isEqualXY(vertexB1, vertexB2)) { + bOverlap = true; + _resolveOverlap(bDirection1, bDirection2, vertexA1, + vertexB1, vertexA2, vertexB2); + } + + if (!bOverlap && m_shape.isEqualXY(vertexC1, vertexC2)) { + bOverlap = true; + _resolveOverlap(!bDirection1, !bDirection2, vertexA1, + vertexC1, vertexA2, vertexC2); + } + + if (bOverlap) + bFound = true; + + bContinue |= bOverlap; + + index1 = _getNextEdgeIndex(index1); + index2 = _getNextEdgeIndex(index1); + } + } + + if (!bFound) {// resolve all cross overs + int index1 = 0; + if (m_bunchEdgeIndices.get(index1) == -1) + index1 = _getNextEdgeIndex(index1); + + int index2 = _getNextEdgeIndex(index1); + + for (int i = 0, n = m_bunchEdgeIndices.size(); i < n + && index1 != -1 && index2 != -1 && index1 != index2; i++) { + int edgeindex1 = m_bunchEdgeIndices.get(index1); + int edgeindex2 = m_bunchEdgeIndices.get(index2); + + int vertexB1 = m_bunchEdgeEndPoints.get(edgeindex1); + int vertexB2 = m_bunchEdgeEndPoints.get(edgeindex2); + + int vertexA1 = m_shape.getNextVertex(vertexB1); + if (!m_shape.isEqualXY(vertexA1, ptCenter)) + vertexA1 = m_shape.getPrevVertex(vertexB1); + int vertexA2 = m_shape.getNextVertex(vertexB2); + if (!m_shape.isEqualXY(vertexA2, ptCenter)) + vertexA2 = m_shape.getPrevVertex(vertexB2); + + // _ASSERT(m_shape.isEqualXY(vertexA1, vertexA2)); + // _ASSERT(m_shape.isEqualXY(vertexA1, ptCenter)); + + boolean bDirection1 = _getDirection(vertexA1, vertexB1); + boolean bDirection2 = _getDirection(vertexA2, vertexB2); + int vertexC1 = bDirection1 ? m_shape.getPrevVertex(vertexA1) + : m_shape.getNextVertex(vertexA1); + int vertexC2 = bDirection2 ? m_shape.getPrevVertex(vertexA2) + : m_shape.getNextVertex(vertexA2); + + if (_detectAndResolveCrossOver(bDirection1, bDirection2, + vertexB1, vertexA1, vertexC1, vertexB2, vertexA2, + vertexC2)) { + bFound = true; + } + + index1 = _getNextEdgeIndex(index1); + index2 = _getNextEdgeIndex(index1); + } + } + + return bFound; + } + + static class SimplificatorVertexComparer extends + AttributeStreamOfInt32.IntComparator { + Simplificator m_parent; + + SimplificatorVertexComparer(Simplificator parent) { + m_parent = parent; + } + + @Override + public int compare(int v1, int v2) { + return m_parent._compareVerticesSimple(v1, v2); + } + + } + + private boolean _simplify() { + if (m_shape.getGeometryType(m_geometry) == Polygon.Type.Polygon.value() + && m_shape.getFillRule(m_geometry) == Polygon.FillRule.enumFillRuleWinding) { + TopologicalOperations ops = new TopologicalOperations(); + ops.planarSimplifyNoCrackingAndCluster(m_fixSelfTangency, + m_shape, m_geometry, m_progressTracker); + assert (m_shape.getFillRule(m_geometry) == Polygon.FillRule.enumFillRuleOddEven); + } + boolean bChanged = false; + boolean bNeedWindingRepeat = true; + boolean bWinding = false; + + m_userIndexSortedIndexToVertex = -1; + m_userIndexSortedAngleIndexToVertex = -1; + + int pointCount = m_shape.getPointCount(m_geometry); + + // Sort vertices lexicographically + // Firstly copy allvertices to an array. + AttributeStreamOfInt32 verticesSorter = new AttributeStreamOfInt32(0); + verticesSorter.reserve(pointCount); + + for (int path = m_shape.getFirstPath(m_geometry); path != -1; path = m_shape + .getNextPath(path)) { + int vertex = m_shape.getFirstVertex(path); + for (int index = 0, n = m_shape.getPathSize(path); index < n; index++) { + verticesSorter.add(vertex); + vertex = m_shape.getNextVertex(vertex); + } + } + + // Sort + verticesSorter.Sort(0, pointCount, + new SimplificatorVertexComparer(this)); + // SORTDYNAMICARRAYEX(verticesSorter, int, 0, pointCount, + // SimplificatorVertexComparer, this); + + // Copy sorted vertices to the m_sortedVertices list. Make a mapping + // from the edit shape vertices to the sorted vertices. + m_userIndexSortedIndexToVertex = m_shape.createUserIndex();// this index + // is used + // to map + // from edit + // shape + // vertex to + // the + // m_sortedVertices + // list + m_sortedVertices = new IndexMultiDCList(); + m_sortedVerticesListIndex = m_sortedVertices.createList(0); + for (int i = 0; i < pointCount; i++) { + int vertex = verticesSorter.get(i); + {// debug + Point2D pt = new Point2D(); + m_shape.getXY(vertex, pt);// for debugging + double y = pt.x; + } + int vertexlistIndex = m_sortedVertices.addElement( + m_sortedVerticesListIndex, vertex); + m_shape.setUserIndex(vertex, m_userIndexSortedIndexToVertex, + vertexlistIndex);// remember the sorted list element on the + // vertex. + // When we remove a vertex, we also remove associated sorted list + // element. + } + + m_userIndexSortedAngleIndexToVertex = m_shape.createUserIndex();// create + // additional + // list + // to + // store + // angular + // sort + // mapping. + + m_nextVertexToProcess = -1; + + if (_cleanupSpikes())// cleanup any spikes on the polygon. + bChanged = true; + + // External iteration loop for the simplificator. + // ST. I am not sure if it actually needs this loop. TODO: figure this + // out. + while (bNeedWindingRepeat) { + bNeedWindingRepeat = false; + + int max_iter = m_shape.getPointCount(m_geometry) + 10 > 30 ? 1000 + : (m_shape.getPointCount(m_geometry) + 10) + * (m_shape.getPointCount(m_geometry) + 10); + + // Simplify polygon + int iRepeatNum = 0; + boolean bNeedRepeat = false; + + // Internal iteration loop for the simplificator. + // ST. I am not sure if it actually needs this loop. TODO: figure + // this out. + do// while (bNeedRepeat); + { + bNeedRepeat = false; + + boolean bVertexRecheck = false; + m_firstCoincidentVertex = -1; + int coincidentCount = 0; + Point2D ptFirst = new Point2D(); + Point2D pt = new Point2D(); + // Main loop of the simplificator. Go through the vertices and + // for those that have same coordinates, + for (int vlistindex = m_sortedVertices + .getFirst(m_sortedVerticesListIndex); vlistindex != IndexMultiDCList + .nullNode(); ) { + int vertex = m_sortedVertices.getData(vlistindex); + {// debug + // Point2D pt = new Point2D(); + m_shape.getXY(vertex, pt); + double d = pt.x; + } + + if (m_firstCoincidentVertex != -1) { + // Point2D pt = new Point2D(); + m_shape.getXY(vertex, pt); + if (ptFirst.isEqual(pt)) { + coincidentCount++; + } else { + ptFirst.setCoords(pt); + m_nextVertexToProcess = vlistindex;// we remeber the + // next index in + // the member + // variable to + // allow it to + // be updated if + // a vertex is + // removed + // inside of the + // _ProcessBunch. + if (coincidentCount > 0) { + boolean result = _processBunch();// process a + // bunch of + // coinciding + // vertices + if (result) {// something has changed. + // Note that ProcessBunch may + // change m_nextVertexToProcess + // and m_firstCoincidentVertex. + bNeedRepeat = true; + if (m_nextVertexToProcess != IndexMultiDCList + .nullNode()) { + int v = m_sortedVertices + .getData(m_nextVertexToProcess); + m_shape.getXY(v, ptFirst); + } + } + } + + vlistindex = m_nextVertexToProcess; + m_firstCoincidentVertex = vlistindex; + coincidentCount = 0; + } + } else { + m_firstCoincidentVertex = vlistindex; + m_shape.getXY(m_sortedVertices.getData(vlistindex), + ptFirst); + coincidentCount = 0; + } + + if (vlistindex != -1)//vlistindex can be set to -1 after ProcessBunch call above + vlistindex = m_sortedVertices.getNext(vlistindex); + } + + m_nextVertexToProcess = -1; + + if (coincidentCount > 0) { + boolean result = _processBunch(); + if (result) + bNeedRepeat = true; + } + + if (iRepeatNum++ > 10) { + throw GeometryException.GeometryInternalError(); + } + + if (bNeedRepeat) + _fixOrphanVertices();// fix broken structure of the shape + + if (_cleanupSpikes()) + bNeedRepeat = true; + + bNeedWindingRepeat |= bNeedRepeat && bWinding; + + bChanged |= bNeedRepeat; + + } while (bNeedRepeat); + + }// while (bNeedWindingRepeat) + + // Now process rings. Fix ring orientation and determine rings that need + // to be deleted. + + m_shape.removeUserIndex(m_userIndexSortedIndexToVertex); + m_shape.removeUserIndex(m_userIndexSortedAngleIndexToVertex); + + bChanged |= RingOrientationFixer.execute(m_shape, m_geometry, + m_sortedVertices, m_fixSelfTangency); + + return bChanged; + } + + private boolean _getDirection(int vert1, int vert2) { + if (m_shape.getNextVertex(vert2) == vert1) { + // _ASSERT(m_shape.getPrevVertex(vert1) == vert2); + return false; + } else { + // _ASSERT(m_shape.getPrevVertex(vert2) == vert1); + // _ASSERT(m_shape.getNextVertex(vert1) == vert2); + return true; + } + } + + private boolean _detectAndResolveCrossOver(boolean bDirection1, + boolean bDirection2, int vertexB1, int vertexA1, int vertexC1, + int vertexB2, int vertexA2, int vertexC2) { + // _ASSERT(!m_shape.isEqualXY(vertexB1, vertexB2)); + // _ASSERT(!m_shape.isEqualXY(vertexC1, vertexC2)); + + if (vertexA1 == vertexA2) { + _removeAngleSortInfo(vertexB1); + _removeAngleSortInfo(vertexB2); + return false; + } + + // _ASSERT(!m_shape.isEqualXY(vertexB1, vertexC2)); + // _ASSERT(!m_shape.isEqualXY(vertexB1, vertexC1)); + // _ASSERT(!m_shape.isEqualXY(vertexB2, vertexC2)); + // _ASSERT(!m_shape.isEqualXY(vertexB2, vertexC1)); + // _ASSERT(!m_shape.isEqualXY(vertexA1, vertexB1)); + // _ASSERT(!m_shape.isEqualXY(vertexA1, vertexC1)); + // _ASSERT(!m_shape.isEqualXY(vertexA2, vertexB2)); + // _ASSERT(!m_shape.isEqualXY(vertexA2, vertexC2)); + + // _ASSERT(m_shape.isEqualXY(vertexA1, vertexA2)); + + // get indices of the vertices for the angle sort. + int iB1 = m_shape.getUserIndex(vertexB1, + m_userIndexSortedAngleIndexToVertex); + int iC1 = m_shape.getUserIndex(vertexC1, + m_userIndexSortedAngleIndexToVertex); + int iB2 = m_shape.getUserIndex(vertexB2, + m_userIndexSortedAngleIndexToVertex); + int iC2 = m_shape.getUserIndex(vertexC2, + m_userIndexSortedAngleIndexToVertex); + // _ASSERT(iB1 >= 0); + // _ASSERT(iC1 >= 0); + // _ASSERT(iB2 >= 0); + // _ASSERT(iC2 >= 0); + // Sort the indices to restore the angle-sort order + int[] ar = new int[8]; + int[] br = new int[4]; + + ar[0] = 0; + br[0] = iB1; + ar[1] = 0; + br[1] = iC1; + ar[2] = 1; + br[2] = iB2; + ar[3] = 1; + br[3] = iC2; + for (int j = 1; j < 4; j++)// insertion sort + { + int key = br[j]; + int data = ar[j]; + int i = j - 1; + while (i >= 0 && br[i] > key) { + br[i + 1] = br[i]; + ar[i + 1] = ar[i]; + i--; + } + br[i + 1] = key; + ar[i + 1] = data; + } + + int detector = 0; + if (ar[0] != 0) + detector |= 1; + if (ar[1] != 0) + detector |= 2; + if (ar[2] != 0) + detector |= 4; + if (ar[3] != 0) + detector |= 8; + if (detector != 5 && detector != 10)// not an overlap + return false; + + if (bDirection1 == bDirection2) { + if (bDirection1) { + m_shape.setNextVertex_(vertexC2, vertexA1); // B1< >B2 + m_shape.setPrevVertex_(vertexA1, vertexC2); // \ / + m_shape.setNextVertex_(vertexC1, vertexA2); // A1A2 + m_shape.setPrevVertex_(vertexA2, vertexC1); // / \ // + // C2> C1 + } + } else { + if (bDirection1) { + m_shape.setPrevVertex_(vertexA1, vertexB2); // B1< >B2 + m_shape.setPrevVertex_(vertexB2, vertexA1); // \ / + m_shape.setNextVertex_(vertexA2, vertexC1); // A1A2 + m_shape.setPrevVertex_(vertexC1, vertexA2); // / \ // + // C2> >C1 + + } + } + + return true; + } + + private void _resolveOverlap(boolean bDirection1, boolean bDirection2, + int vertexA1, int vertexB1, int vertexA2, int vertexB2) { + if (m_bWinding) { + _resolveOverlapWinding(bDirection1, bDirection2, vertexA1, + vertexB1, vertexA2, vertexB2); + } else { + _resolveOverlapOddEven(bDirection1, bDirection2, vertexA1, + vertexB1, vertexA2, vertexB2); + } + } + + private void _resolveOverlapWinding(boolean bDirection1, + boolean bDirection2, int vertexA1, int vertexB1, int vertexA2, + int vertexB2) { + throw new GeometryException("not implemented."); + } + + private void _resolveOverlapOddEven(boolean bDirection1, + boolean bDirection2, int vertexA1, int vertexB1, int vertexA2, + int vertexB2) { + if (bDirection1 != bDirection2) { + if (bDirection1) { + // _ASSERT(m_shape.getNextVertex(vertexA1) == vertexB1); + // _ASSERT(m_shape.getNextVertex(vertexB2) == vertexA2); + m_shape.setNextVertex_(vertexA1, vertexA2); // B1< B2 + m_shape.setPrevVertex_(vertexA2, vertexA1); // | | + m_shape.setNextVertex_(vertexB2, vertexB1); // | | + m_shape.setPrevVertex_(vertexB1, vertexB2); // A1 >A2 + + _transferVertexData(vertexA2, vertexA1); + _beforeRemoveVertex(vertexA2, true); + m_shape.removeVertexInternal_(vertexA2, true); + _removeAngleSortInfo(vertexA1); + _transferVertexData(vertexB2, vertexB1); + _beforeRemoveVertex(vertexB2, true); + m_shape.removeVertexInternal_(vertexB2, false); + _removeAngleSortInfo(vertexB1); + } else { + m_shape.setNextVertex_(vertexA2, vertexA1); // B1 B2< + m_shape.setPrevVertex_(vertexA1, vertexA2); // | | + m_shape.setNextVertex_(vertexB1, vertexB2); // | | + m_shape.setPrevVertex_(vertexB2, vertexB1); // A1< A2 + + _transferVertexData(vertexA2, vertexA1); + _beforeRemoveVertex(vertexA2, true); + m_shape.removeVertexInternal_(vertexA2, false); + _removeAngleSortInfo(vertexA1); + _transferVertexData(vertexB2, vertexB1); + _beforeRemoveVertex(vertexB2, true); + m_shape.removeVertexInternal_(vertexB2, true); + _removeAngleSortInfo(vertexB1); + } + } else// bDirection1 == bDirection2 + { + if (!bDirection1) { + // _ASSERT(m_shape.getNextVertex(vertexB1) == vertexA1); + // _ASSERT(m_shape.getNextVertex(vertexB2) == vertexA2); + } else { + // _ASSERT(m_shape.getNextVertex(vertexA1) == vertexB1); + // _ASSERT(m_shape.getNextVertex(vertexA2) == vertexB2); + } + + // if (m_shape._RingParentageCheckInternal(vertexA1, vertexA2)) + { + int a1 = bDirection1 ? vertexA1 : vertexB1; + int a2 = bDirection2 ? vertexA2 : vertexB2; + int b1 = bDirection1 ? vertexB1 : vertexA1; + int b2 = bDirection2 ? vertexB2 : vertexA2; + + // m_shape.dbgVerifyIntegrity(a1);//debug + // m_shape.dbgVerifyIntegrity(a2);//debug + + boolean bVisitedA1 = false; + m_shape.setNextVertex_(a1, a2); + m_shape.setNextVertex_(a2, a1); + m_shape.setPrevVertex_(b1, b2); + m_shape.setPrevVertex_(b2, b1); + int v = b2; + while (v != a2) { + int prev = m_shape.getPrevVertex(v); + int next = m_shape.getNextVertex(v); + + m_shape.setPrevVertex_(v, next); + m_shape.setNextVertex_(v, prev); + bVisitedA1 |= v == a1; + v = next; + } + + if (!bVisitedA1) { + // a case of two rings being merged + int prev = m_shape.getPrevVertex(a2); + int next = m_shape.getNextVertex(a2); + m_shape.setPrevVertex_(a2, next); + m_shape.setNextVertex_(a2, prev); + } else { + // merge happend on the same ring. + } + + // m_shape.dbgVerifyIntegrity(b1);//debug + // m_shape.dbgVerifyIntegrity(a1);//debug + + _transferVertexData(a2, a1); + _beforeRemoveVertex(a2, true); + m_shape.removeVertexInternal_(a2, false); + _removeAngleSortInfo(a1); + _transferVertexData(b2, b1); + _beforeRemoveVertex(b2, true); + m_shape.removeVertexInternal_(b2, false); + _removeAngleSortInfo(b1); + + // m_shape.dbgVerifyIntegrity(b1);//debug + // m_shape.dbgVerifyIntegrity(a1);//debug + } + } + } + + private boolean _cleanupSpikes() { + boolean bModified = false; + for (int path = m_shape.getFirstPath(m_geometry); path != -1; ) { + int vertex = m_shape.getFirstVertex(path); + for (int vindex = 0, n = m_shape.getPathSize(path); vindex < n + && n > 1; ) { + int prev = m_shape.getPrevVertex(vertex); + int next = m_shape.getNextVertex(vertex); + if (m_shape.isEqualXY(prev, next)) { + bModified = true; + _beforeRemoveVertex(vertex, false); + m_shape.removeVertex(vertex, true);// not internal, because + // path is valid at this + // point + _beforeRemoveVertex(next, false); + m_shape.removeVertex(next, true); + vertex = prev; + vindex = 0; + n = m_shape.getPathSize(path); + } else { + vertex = next; + vindex++; + } + } + + if (m_shape.getPathSize(path) < 2) { + int vertexL = m_shape.getFirstVertex(path); + for (int vindex = 0, n = m_shape.getPathSize(path); vindex < n; vindex++) { + _beforeRemoveVertex(vertexL, false); + vertexL = m_shape.getNextVertex(vertexL); + } + + path = m_shape.removePath(path); + bModified = true; + } else + path = m_shape.getNextPath(path); + } + + return bModified; + } + + private boolean _removeSpike(int vertexIn) { + // m_shape.dbgVerifyIntegrity(vertex);//debug + int vertex = vertexIn; + + // _ASSERT(m_shape.isEqualXY(m_shape.getNextVertex(vertex), + // m_shape.getPrevVertex(vertex))); + boolean bFound = false; + while (true) { + int next = m_shape.getNextVertex(vertex); + int prev = m_shape.getPrevVertex(vertex); + if (next == vertex) {// last vertex in a ring + _beforeRemoveVertex(vertex, true); + m_shape.removeVertexInternal_(vertex, false); + return true; + } + + if (!m_shape.isEqualXY(next, prev)) + break; + + bFound = true; + _removeAngleSortInfo(prev); + _removeAngleSortInfo(next); + _beforeRemoveVertex(vertex, true); + m_shape.removeVertexInternal_(vertex, false); + // m_shape.dbgVerifyIntegrity(prev);//debug + _transferVertexData(next, prev); + _beforeRemoveVertex(next, true); + m_shape.removeVertexInternal_(next, true); + if (next == prev) + break;// deleted the last vertex + + // m_shape.dbgVerifyIntegrity(prev);//debug + + vertex = prev; + } + return bFound; + } + + private void _fixOrphanVertices() { + int pathCount = 0; + // clean any path info + for (int node = m_sortedVertices.getFirst(m_sortedVertices + .getFirstList()); node != -1; node = m_sortedVertices + .getNext(node)) { + int vertex = m_sortedVertices.getData(node); + m_shape.setPathToVertex_(vertex, -1); + } + int geometrySize = 0; + for (int path = m_shape.getFirstPath(m_geometry); path != -1; ) { + int first = m_shape.getFirstVertex(path); + if (first == -1 || m_shape.getPathFromVertex(first) != -1) { + int p = path; + path = m_shape.getNextPath(path); + m_shape.removePathOnly_(p); + continue; + } + + m_shape.setPathToVertex_(first, path); + int pathSize = 1; + for (int vertex = m_shape.getNextVertex(first); vertex != first; vertex = m_shape + .getNextVertex(vertex)) { + m_shape.setPathToVertex_(vertex, path); + pathSize++; + } + m_shape.setRingAreaValid_(path, false); + m_shape.setPathSize_(path, pathSize); + m_shape.setLastVertex_(path, m_shape.getPrevVertex(first)); + geometrySize += pathSize; + pathCount++; + path = m_shape.getNextPath(path); + } + + // Some vertices do not belong to any path. We have to create new path + // objects for those. + // Produce new paths for the orphan vertices. + for (int node = m_sortedVertices.getFirst(m_sortedVertices + .getFirstList()); node != -1; node = m_sortedVertices + .getNext(node)) { + int vertex = m_sortedVertices.getData(node); + if (m_shape.getPathFromVertex(vertex) != -1) + continue; + + int path = m_shape.insertClosedPath_(m_geometry, -1, vertex, vertex, null); + geometrySize += m_shape.getPathSize(path); + pathCount++; + } + + m_shape.setGeometryPathCount_(m_geometry, pathCount); + m_shape.setGeometryVertexCount_(m_geometry, geometrySize); + int totalPointCount = 0; + for (int geometry = m_shape.getFirstGeometry(); geometry != -1; geometry = m_shape.getNextGeometry(geometry)) { + totalPointCount += m_shape.getPointCount(geometry); + } + + m_shape.setTotalPointCount_(totalPointCount); + } + + private int _getNextEdgeIndex(int indexIn) { + int index = indexIn; + for (int i = 0, n = m_bunchEdgeIndices.size() - 1; i < n; i++) { + index = (index + 1) % m_bunchEdgeIndices.size(); + if (m_bunchEdgeIndices.get(index) != -1) + return index; + } + return -1; + } + + private void _transferVertexData(int vertexFrom, int vertexTo) { + int v1 = m_shape.getUserIndex(vertexTo, m_userIndexSortedIndexToVertex); + int v2 = m_shape.getUserIndex(vertexTo, + m_userIndexSortedAngleIndexToVertex); + m_shape.transferAllDataToTheVertex(vertexFrom, vertexTo); + m_shape.setUserIndex(vertexTo, m_userIndexSortedIndexToVertex, v1); + m_shape.setUserIndex(vertexTo, m_userIndexSortedAngleIndexToVertex, v2); + } + + private void _removeAngleSortInfo(int vertex) { + int angleIndex = m_shape.getUserIndex(vertex, + m_userIndexSortedAngleIndexToVertex); + if (angleIndex != -1) { + m_bunchEdgeIndices.set(angleIndex, -1); + m_shape.setUserIndex(vertex, m_userIndexSortedAngleIndexToVertex, + -1); + } + } + + protected Simplificator() { + m_dbgCounter = 0; + } + + public static boolean execute(EditShape shape, int geometry, + int knownSimpleResult, boolean fixSelfTangency, ProgressTracker progressTracker) { + Simplificator simplificator = new Simplificator(); + simplificator.m_shape = shape; + // simplificator.m_bWinding = bWinding; + simplificator.m_geometry = geometry; + simplificator.m_knownSimpleResult = knownSimpleResult; + simplificator.m_fixSelfTangency = fixSelfTangency; + simplificator.m_progressTracker = progressTracker; + return simplificator._simplify(); + } + + int _compareVerticesSimple(int v1, int v2) { + Point2D pt1 = new Point2D(); + m_shape.getXY(v1, pt1); + Point2D pt2 = new Point2D(); + m_shape.getXY(v2, pt2); + int res = pt1.compare(pt2); + if (res == 0) {// sort equal vertices by the path ID + int i1 = m_shape.getPathFromVertex(v1); + int i2 = m_shape.getPathFromVertex(v2); + res = i1 < i2 ? -1 : (i1 == i2 ? 0 : 1); + } + + return res; + } + + int _compareAngles(int index1, int index2) { + int vert1 = m_bunchEdgeEndPoints.get(index1); + Point2D pt1 = new Point2D(); + m_shape.getXY(vert1, pt1); + Point2D pt2 = new Point2D(); + int vert2 = m_bunchEdgeEndPoints.get(index2); + m_shape.getXY(vert2, pt2); + + if (pt1.isEqual(pt2)) + return 0;// overlap case + + int vert10 = m_bunchEdgeCenterPoints.get(index1); + Point2D pt10 = new Point2D(); + m_shape.getXY(vert10, pt10); + + int vert20 = m_bunchEdgeCenterPoints.get(index2); + Point2D pt20 = new Point2D(); + m_shape.getXY(vert20, pt20); + // _ASSERT(pt10.isEqual(pt20)); + + Point2D v1 = new Point2D(); + v1.sub(pt1, pt10); + Point2D v2 = new Point2D(); + v2.sub(pt2, pt20); + int result = Point2D._compareVectors(v1, v2); + return result; + } } diff --git a/src/main/java/com/esri/core/geometry/SizeOf.java b/src/main/java/com/esri/core/geometry/SizeOf.java index 43cc05cb..5b00e508 100644 --- a/src/main/java/com/esri/core/geometry/SizeOf.java +++ b/src/main/java/com/esri/core/geometry/SizeOf.java @@ -72,7 +72,7 @@ public final class SizeOf { public static final int SIZE_OF_OGC_POINT = 24; public static final int SIZE_OF_OGC_POLYGON = 24; - + static final int SIZE_OF_MAPGEOMETRY = 24; static long sizeOfByteArray(int length) { diff --git a/src/main/java/com/esri/core/geometry/SpatialReference.java b/src/main/java/com/esri/core/geometry/SpatialReference.java index 8f251f9b..fb06195e 100644 --- a/src/main/java/com/esri/core/geometry/SpatialReference.java +++ b/src/main/java/com/esri/core/geometry/SpatialReference.java @@ -35,282 +35,283 @@ * This class provide tolerance value for the topological and relational operations. */ public abstract class SpatialReference implements Serializable { - // Note: We use writeReplace with SpatialReferenceSerializer. This field is - // irrelevant. Needs to be removed after final. - private static final long serialVersionUID = 2L; - - enum CoordinateSystemType { - Uknown, Local, GEOGRAPHIC, PROJECTED - } - - /** - * Creates an instance of the spatial reference based on the provided well - * known ID for the horizontal coordinate system. - * - * @param wkid The well-known ID. - * @return SpatialReference The spatial reference. - * @throws IllegalArgumentException if wkid is not supported or does not exist. - */ - public static SpatialReference create(int wkid) { - SpatialReferenceImpl spatRef = SpatialReferenceImpl.createImpl(wkid); - return spatRef; - } - - /** - * From a lat long wgs84 geometry, get the utm zone for the geometries center - * @param geometry - * @return - */ - public static SpatialReference createUTM(Geometry geometry) { - // TODO this will fail for multipart geometries on either side of the dateline - Envelope envelope = new Envelope(); - geometry.queryEnvelope(envelope); - - // if the geometry is outside of the -180 to 180 range shift it back into range - Point point = (Point)OperatorProjectLocal.foldInto360Range(envelope.getCenter(), SpatialReference.create(4326)); - return createUTM(point.getX(), point.getY()); - } - - /** - * choose the UTM zone that contains the longitude and latitude - * @param longitude - * @param latitude - * @return - */ - public static SpatialReference createUTM(double longitude, double latitude) { - // epsg code for N1 32601 - int epsg_code = 32601; - if (latitude < 0) { - // epsg code for S1 32701 - epsg_code += 100; - } - - double diff = longitude + 180.0; - - // TODO ugly - if (diff == 0.0) { - return SpatialReference.create(epsg_code); - } - - // 6 degrees of separation between zones, started with zone one, so subtract 1 - Double interval_count = Math.ceil(diff / 6); - int bump = interval_count.intValue() - 1; - - return SpatialReference.create(epsg_code + bump); - } - - public static SpatialReference createEqualArea(double lon_0, double lat_0) { - // create projection transformation that goes from input to input's equal area azimuthal projection - // +proj=laea +lat_0=52 +lon_0=10 +x_0=4321000 +y_0=3210000 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs - String proj4 = String.format( - "+proj=laea +lat_0=%f +lon_0=%f +x_0=4321000 +y_0=3210000 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs", - lat_0, - lon_0); - return SpatialReference.createFromProj4(proj4); - } - - public static SpatialReference createEqualArea(Geometry geometry, SpatialReference spatialReference) { - Point2D ptCenter = GeoDist.getEnvCenter(geometry, spatialReference); - double longitude = ptCenter.x; - double latitude = ptCenter.y; - - return createEqualArea(longitude, latitude); - } - - /** - * Creates an instance of the spatial reference based on the provided well - * known text representation for the horizontal coordinate system. - * - * @param wktext The well-known text string representation of spatial - * reference. - * @return SpatialReference The spatial reference. - */ - public static SpatialReference create(String wktext) { - return SpatialReferenceImpl.createImpl(wktext); - } - - - - public static SpatialReference createFromProj4(String proj4test) { - return SpatialReferenceImpl.createFromProj4Impl(proj4test); - } - - /** - * @return boolean Is spatial reference local? - */ - boolean isLocal() { - return false; - } - - /** - * Returns spatial reference from the JsonParser. - * - * @param parser The JSON parser. - * @return The spatial reference or null if there is no spatial reference - * information, or the parser does not point to an object start. - * @throws Exception if parsing has failed - */ - public static SpatialReference fromJson(JsonParser parser) throws Exception { - return fromJson(new JsonParserReader(parser)); - } - - public static SpatialReference fromJson(String string) throws Exception { - return fromJson(JsonParserReader.createFromString(string)); - } - - public static SpatialReference fromJson(JsonReader parser) throws Exception { - // Note this class is processed specially: it is expected that the - // iterator points to the first element of the SR object. - boolean bFoundWkid = false; - boolean bFoundLatestWkid = false; - boolean bFoundVcsWkid = false; - boolean bFoundLatestVcsWkid = false; - boolean bFoundWkt = false; - - int wkid = -1; - int latestWkid = -1; - int vcs_wkid = -1; - int latestVcsWkid = -1; - String wkt = null; - while (parser.nextToken() != JsonReader.Token.END_OBJECT) { - String name = parser.currentString(); - parser.nextToken(); - - if (!bFoundWkid && name.equals("wkid")) { - bFoundWkid = true; - - if (parser.currentToken() == JsonReader.Token.VALUE_NUMBER_INT) - wkid = parser.currentIntValue(); - } else if (!bFoundLatestWkid && name.equals("latestWkid")) { - bFoundLatestWkid = true; - - if (parser.currentToken() == JsonReader.Token.VALUE_NUMBER_INT) - latestWkid = parser.currentIntValue(); - } else if (!bFoundWkt && name.equals("wkt")) { - bFoundWkt = true; - - if (parser.currentToken() == JsonReader.Token.VALUE_STRING) - wkt = parser.currentString(); - } else if (!bFoundVcsWkid && name.equals("vcsWkid")) { - bFoundVcsWkid = true; - - if (parser.currentToken() == JsonReader.Token.VALUE_NUMBER_INT) - vcs_wkid = parser.currentIntValue(); - } else if (!bFoundLatestVcsWkid && name.equals("latestVcsWkid")) { - bFoundLatestVcsWkid = true; - - if (parser.currentToken() == JsonReader.Token.VALUE_NUMBER_INT) - latestVcsWkid = parser.currentIntValue(); - } - } - - if (latestVcsWkid <= 0 && vcs_wkid > 0) { - latestVcsWkid = vcs_wkid; - } - - // iter.step_out_after(); do not step out for the spatial reference, - // because this method is used standalone - - SpatialReference spatial_reference = null; - - if (wkt != null && wkt.length() != 0) { - try { - spatial_reference = SpatialReference.create(wkt); - } catch (Exception e) { - } - } - - if (spatial_reference == null && latestWkid > 0) { - try { - spatial_reference = SpatialReference.create(latestWkid); - } catch (Exception e) { - } - } - - if (spatial_reference == null && wkid > 0) { - try { - spatial_reference = SpatialReference.create(wkid); - } catch (Exception e) { - } - } - - return spatial_reference; - } - - /** - * Returns the well known ID for the horizontal coordinate system of the - * spatial reference. - * - * @return wkid The well known ID. - */ - public abstract int getID(); - - public abstract String getText(); - - public abstract String getProj4(); - - - public abstract double getMajorAxis(); - - public abstract double getEccentricitySquared(); - - /** - * Returns the oldest value of the well known ID for the horizontal - * coordinate system of the spatial reference. This ID is used for JSON - * serialization. Not public. - */ - abstract int getOldID(); - - /** - * Returns the latest value of the well known ID for the horizontal - * coordinate system of the spatial reference. This ID is used for JSON - * serialization. Not public. - */ - abstract int getLatestID(); - - /** - * Returns the XY tolerance of the spatial reference. - *

- * The tolerance value defines the precision of topological operations, and - * "thickness" of boundaries of geometries for relational operations. - *

- * When two points have xy coordinates closer than the tolerance value, they - * are considered equal. As well as when a point is within tolerance from - * the line, the point is assumed to be on the line. - *

- * During topological operations the tolerance is increased by a factor of - * about 1.41 and any two points within that distance are snapped - * together. - * - * @return The XY tolerance of the spatial reference. - */ - public double getTolerance() { - return getTolerance(VertexDescription.Semantics.POSITION); - } - - /** - * Get the XY tolerance of the spatial reference - * - * @return The XY tolerance of the spatial reference as double. - */ - abstract double getTolerance(int semantics); - - Object writeReplace() throws ObjectStreamException { - SpatialReferenceSerializer srSerializer = new SpatialReferenceSerializer(); - srSerializer.setSpatialReferenceByValue(this); - return srSerializer; - } - - abstract CoordinateSystemType getCoordinateSystemType(); - - /** - * Returns string representation of the class for debugging purposes. The - * format and content of the returned string is not part of the contract of - * the method and is subject to change in any future release or patch - * without further notice. - */ - public String toString() { - return "[ tol: " + getTolerance() + "; wkid: " + getID() + "; wkt: " - + getText() + "]"; - } + // Note: We use writeReplace with SpatialReferenceSerializer. This field is + // irrelevant. Needs to be removed after final. + private static final long serialVersionUID = 2L; + + enum CoordinateSystemType { + Uknown, Local, GEOGRAPHIC, PROJECTED + } + + /** + * Creates an instance of the spatial reference based on the provided well + * known ID for the horizontal coordinate system. + * + * @param wkid The well-known ID. + * @return SpatialReference The spatial reference. + * @throws IllegalArgumentException if wkid is not supported or does not exist. + */ + public static SpatialReference create(int wkid) { + SpatialReferenceImpl spatRef = SpatialReferenceImpl.createImpl(wkid); + return spatRef; + } + + /** + * From a lat long wgs84 geometry, get the utm zone for the geometries center + * + * @param geometry + * @return + */ + public static SpatialReference createUTM(Geometry geometry) { + // TODO this will fail for multipart geometries on either side of the dateline + Envelope envelope = new Envelope(); + geometry.queryEnvelope(envelope); + + // if the geometry is outside of the -180 to 180 range shift it back into range + Point point = (Point) OperatorProjectLocal.foldInto360Range(envelope.getCenter(), SpatialReference.create(4326)); + return createUTM(point.getX(), point.getY()); + } + + /** + * choose the UTM zone that contains the longitude and latitude + * + * @param longitude + * @param latitude + * @return + */ + public static SpatialReference createUTM(double longitude, double latitude) { + // epsg code for N1 32601 + int epsg_code = 32601; + if (latitude < 0) { + // epsg code for S1 32701 + epsg_code += 100; + } + + double diff = longitude + 180.0; + + // TODO ugly + if (diff == 0.0) { + return SpatialReference.create(epsg_code); + } + + // 6 degrees of separation between zones, started with zone one, so subtract 1 + Double interval_count = Math.ceil(diff / 6); + int bump = interval_count.intValue() - 1; + + return SpatialReference.create(epsg_code + bump); + } + + public static SpatialReference createEqualArea(double lon_0, double lat_0) { + // create projection transformation that goes from input to input's equal area azimuthal projection + // +proj=laea +lat_0=52 +lon_0=10 +x_0=4321000 +y_0=3210000 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs + String proj4 = String.format( + "+proj=laea +lat_0=%f +lon_0=%f +x_0=4321000 +y_0=3210000 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs", + lat_0, + lon_0); + return SpatialReference.createFromProj4(proj4); + } + + public static SpatialReference createEqualArea(Geometry geometry, SpatialReference spatialReference) { + Point2D ptCenter = GeoDist.getEnvCenter(geometry, spatialReference); + double longitude = ptCenter.x; + double latitude = ptCenter.y; + + return createEqualArea(longitude, latitude); + } + + /** + * Creates an instance of the spatial reference based on the provided well + * known text representation for the horizontal coordinate system. + * + * @param wktext The well-known text string representation of spatial + * reference. + * @return SpatialReference The spatial reference. + */ + public static SpatialReference create(String wktext) { + return SpatialReferenceImpl.createImpl(wktext); + } + + + public static SpatialReference createFromProj4(String proj4test) { + return SpatialReferenceImpl.createFromProj4Impl(proj4test); + } + + /** + * @return boolean Is spatial reference local? + */ + boolean isLocal() { + return false; + } + + /** + * Returns spatial reference from the JsonParser. + * + * @param parser The JSON parser. + * @return The spatial reference or null if there is no spatial reference + * information, or the parser does not point to an object start. + * @throws Exception if parsing has failed + */ + public static SpatialReference fromJson(JsonParser parser) throws Exception { + return fromJson(new JsonParserReader(parser)); + } + + public static SpatialReference fromJson(String string) throws Exception { + return fromJson(JsonParserReader.createFromString(string)); + } + + public static SpatialReference fromJson(JsonReader parser) throws Exception { + // Note this class is processed specially: it is expected that the + // iterator points to the first element of the SR object. + boolean bFoundWkid = false; + boolean bFoundLatestWkid = false; + boolean bFoundVcsWkid = false; + boolean bFoundLatestVcsWkid = false; + boolean bFoundWkt = false; + + int wkid = -1; + int latestWkid = -1; + int vcs_wkid = -1; + int latestVcsWkid = -1; + String wkt = null; + while (parser.nextToken() != JsonReader.Token.END_OBJECT) { + String name = parser.currentString(); + parser.nextToken(); + + if (!bFoundWkid && name.equals("wkid")) { + bFoundWkid = true; + + if (parser.currentToken() == JsonReader.Token.VALUE_NUMBER_INT) + wkid = parser.currentIntValue(); + } else if (!bFoundLatestWkid && name.equals("latestWkid")) { + bFoundLatestWkid = true; + + if (parser.currentToken() == JsonReader.Token.VALUE_NUMBER_INT) + latestWkid = parser.currentIntValue(); + } else if (!bFoundWkt && name.equals("wkt")) { + bFoundWkt = true; + + if (parser.currentToken() == JsonReader.Token.VALUE_STRING) + wkt = parser.currentString(); + } else if (!bFoundVcsWkid && name.equals("vcsWkid")) { + bFoundVcsWkid = true; + + if (parser.currentToken() == JsonReader.Token.VALUE_NUMBER_INT) + vcs_wkid = parser.currentIntValue(); + } else if (!bFoundLatestVcsWkid && name.equals("latestVcsWkid")) { + bFoundLatestVcsWkid = true; + + if (parser.currentToken() == JsonReader.Token.VALUE_NUMBER_INT) + latestVcsWkid = parser.currentIntValue(); + } + } + + if (latestVcsWkid <= 0 && vcs_wkid > 0) { + latestVcsWkid = vcs_wkid; + } + + // iter.step_out_after(); do not step out for the spatial reference, + // because this method is used standalone + + SpatialReference spatial_reference = null; + + if (wkt != null && wkt.length() != 0) { + try { + spatial_reference = SpatialReference.create(wkt); + } catch (Exception e) { + } + } + + if (spatial_reference == null && latestWkid > 0) { + try { + spatial_reference = SpatialReference.create(latestWkid); + } catch (Exception e) { + } + } + + if (spatial_reference == null && wkid > 0) { + try { + spatial_reference = SpatialReference.create(wkid); + } catch (Exception e) { + } + } + + return spatial_reference; + } + + /** + * Returns the well known ID for the horizontal coordinate system of the + * spatial reference. + * + * @return wkid The well known ID. + */ + public abstract int getID(); + + public abstract String getText(); + + public abstract String getProj4(); + + + public abstract double getMajorAxis(); + + public abstract double getEccentricitySquared(); + + /** + * Returns the oldest value of the well known ID for the horizontal + * coordinate system of the spatial reference. This ID is used for JSON + * serialization. Not public. + */ + abstract int getOldID(); + + /** + * Returns the latest value of the well known ID for the horizontal + * coordinate system of the spatial reference. This ID is used for JSON + * serialization. Not public. + */ + abstract int getLatestID(); + + /** + * Returns the XY tolerance of the spatial reference. + *

+ * The tolerance value defines the precision of topological operations, and + * "thickness" of boundaries of geometries for relational operations. + *

+ * When two points have xy coordinates closer than the tolerance value, they + * are considered equal. As well as when a point is within tolerance from + * the line, the point is assumed to be on the line. + *

+ * During topological operations the tolerance is increased by a factor of + * about 1.41 and any two points within that distance are snapped + * together. + * + * @return The XY tolerance of the spatial reference. + */ + public double getTolerance() { + return getTolerance(VertexDescription.Semantics.POSITION); + } + + /** + * Get the XY tolerance of the spatial reference + * + * @return The XY tolerance of the spatial reference as double. + */ + abstract double getTolerance(int semantics); + + Object writeReplace() throws ObjectStreamException { + SpatialReferenceSerializer srSerializer = new SpatialReferenceSerializer(); + srSerializer.setSpatialReferenceByValue(this); + return srSerializer; + } + + abstract CoordinateSystemType getCoordinateSystemType(); + + /** + * Returns string representation of the class for debugging purposes. The + * format and content of the returned string is not part of the contract of + * the method and is subject to change in any future release or patch + * without further notice. + */ + public String toString() { + return "[ tol: " + getTolerance() + "; wkid: " + getID() + "; wkt: " + + getText() + "]"; + } } diff --git a/src/main/java/com/esri/core/geometry/SpatialReferenceImpl.java b/src/main/java/com/esri/core/geometry/SpatialReferenceImpl.java index 14c15f96..87149a59 100644 --- a/src/main/java/com/esri/core/geometry/SpatialReferenceImpl.java +++ b/src/main/java/com/esri/core/geometry/SpatialReferenceImpl.java @@ -32,89 +32,89 @@ import org.proj4.PJ; class SpatialReferenceImpl extends SpatialReference { - public final static int c_SULIMIT32 = 2147483645; - public final static long c_SULIMIT64 = 9007199254740990L; - // https://regex101.com/r/F0FAUw/1 - public final static Pattern m_pattern = Pattern.compile("^([\\w\\W]+AUTHORITY[\\s]*\\[[\\s]*\"EPSG\"[\\s]*,[\\s]*[\"]*([\\d]+)[\"]*[\\s]*][\\s]*][\\s\\]]*)$"); - public final static Pattern m_proj4wkid = Pattern.compile("^\\+init=epsg:([\\d]+)"); - - - enum Precision { - Integer32, Integer64, FloatingPoint - } - - - int m_userWkid;// this wkid is provided by user to the create method. - int m_userLatestWkid; - int m_userOldestWkid; - String m_proj4; - String m_userWkt;// a string, the well-known text. - PJ m_pj; - double m_a; - double m_e2; - // public SgCoordRef m_sgCoordRef; - - private final static ReentrantLock m_lock = new ReentrantLock(); - - // TODO If one was going to create member object for locking it would be - // here. - SpatialReferenceImpl() { - m_userWkid = 0; - m_userLatestWkid = -1; - m_userOldestWkid = -1; - m_userWkt = null; - m_proj4 = null; - m_pj = null; - m_a = Double.NEGATIVE_INFINITY; - m_e2 = Double.NEGATIVE_INFINITY; - } - - @Override - public int getID() { - return m_userWkid; - } - - double getFalseX() { - return 0; - } - - double getFalseY() { - return 0; - } - - double getFalseZ() { - return 0; - } - - double getFalseM() { - return 0; - } - - double getGridUnitsXY() { - return 1 / (1.0e-9 * 0.0174532925199433/* getOneDegreeGCSUnit() */); - } - - double getGridUnitsZ() { - return 1 / 0.001; - } - - double getGridUnitsM() { - return 1 / 0.001; - } - - Precision getPrecision() { - return Precision.Integer64; - } - - - PJ getPJ() { - if (m_pj == null) { - m_pj = new PJ(this.getProj4()); - } - return m_pj; - } - - CoordinateSystemType getCoordinateSystemType() { + public final static int c_SULIMIT32 = 2147483645; + public final static long c_SULIMIT64 = 9007199254740990L; + // https://regex101.com/r/F0FAUw/1 + public final static Pattern m_pattern = Pattern.compile("^([\\w\\W]+AUTHORITY[\\s]*\\[[\\s]*\"EPSG\"[\\s]*,[\\s]*[\"]*([\\d]+)[\"]*[\\s]*][\\s]*][\\s\\]]*)$"); + public final static Pattern m_proj4wkid = Pattern.compile("^\\+init=epsg:([\\d]+)"); + + + enum Precision { + Integer32, Integer64, FloatingPoint + } + + + int m_userWkid;// this wkid is provided by user to the create method. + int m_userLatestWkid; + int m_userOldestWkid; + String m_proj4; + String m_userWkt;// a string, the well-known text. + PJ m_pj; + double m_a; + double m_e2; + // public SgCoordRef m_sgCoordRef; + + private final static ReentrantLock m_lock = new ReentrantLock(); + + // TODO If one was going to create member object for locking it would be + // here. + SpatialReferenceImpl() { + m_userWkid = 0; + m_userLatestWkid = -1; + m_userOldestWkid = -1; + m_userWkt = null; + m_proj4 = null; + m_pj = null; + m_a = Double.NEGATIVE_INFINITY; + m_e2 = Double.NEGATIVE_INFINITY; + } + + @Override + public int getID() { + return m_userWkid; + } + + double getFalseX() { + return 0; + } + + double getFalseY() { + return 0; + } + + double getFalseZ() { + return 0; + } + + double getFalseM() { + return 0; + } + + double getGridUnitsXY() { + return 1 / (1.0e-9 * 0.0174532925199433/* getOneDegreeGCSUnit() */); + } + + double getGridUnitsZ() { + return 1 / 0.001; + } + + double getGridUnitsM() { + return 1 / 0.001; + } + + Precision getPrecision() { + return Precision.Integer64; + } + + + PJ getPJ() { + if (m_pj == null) { + m_pj = new PJ(this.getProj4()); + } + return m_pj; + } + + CoordinateSystemType getCoordinateSystemType() { // if (m_userWkid != 0) { // if (Wkid.m_gcsToTol.containsKey(m_userWkid)) { // return CoordinateSystemType.GEOGRAPHIC; @@ -129,279 +129,279 @@ CoordinateSystemType getCoordinateSystemType() { // return CoordinateSystemType.PROJECTED; // } // } - // TODO GEOCENTRIC - if (m_userWkt != null) { - if (m_userWkt.contains("PROJCS")) { - return CoordinateSystemType.PROJECTED; - } else { - return CoordinateSystemType.GEOGRAPHIC; - } - - } - if (getPJ().getType() == PJ.Type.GEOGRAPHIC) { - return CoordinateSystemType.GEOGRAPHIC; - } else if (getPJ().getType() == PJ.Type.PROJECTED) { - return CoordinateSystemType.PROJECTED; - } - - return CoordinateSystemType.Uknown; - } - - @Override - double getTolerance(int semantics) { - double tolerance = 0.001; - if (m_userWkid != 0) { - tolerance = Wkid.find_tolerance_from_wkid(m_userWkid); - } else if (m_userWkt != null) { - tolerance = Wkt.find_tolerance_from_wkt(m_userWkt); - } - return tolerance; - } - - public void queryValidCoordinateRange(Envelope2D env2D) { - double delta = 0; - switch (getPrecision()) { - case Integer32: - delta = c_SULIMIT32 / getGridUnitsXY(); - break; - case Integer64: - delta = c_SULIMIT64 / getGridUnitsXY(); - break; - default: - // TODO - throw GeometryException.GeometryInternalError(); - } - - env2D.setCoords(getFalseX(), getFalseY(), getFalseX() + delta, - getFalseY() + delta); - } - - public boolean requiresReSimplify(SpatialReference dst) { - return dst != this; - } - - @Override - public String getText() { - return m_userWkt; - } - - - @Override - public String getProj4() { - return m_proj4; - } - - /** - * Returns the oldest value of the well known ID for the horizontal - * coordinate system of the spatial reference. This ID is used for JSON - * serialization. Not public. - */ - @Override - int getOldID() { - int ID_ = getID(); - - if (m_userOldestWkid != -1) - return m_userOldestWkid; - - m_userOldestWkid = Wkid.wkid_to_old(ID_); - - if (m_userOldestWkid != -1) - return m_userOldestWkid; - - return ID_; - } - - /** - * Returns the latest value of the well known ID for the horizontal - * coordinate system of the spatial reference. This ID is used for JSON - * serialization. Not public. - */ - @Override - int getLatestID() { - int ID_ = getID(); - - if (m_userLatestWkid != -1) - return m_userLatestWkid; - - m_userLatestWkid = Wkid.wkid_to_new(ID_); - - if (m_userLatestWkid != -1) - return m_userLatestWkid; - - return ID_; - } - - @Override - public double getMajorAxis() { - if (Double.isInfinite(m_a)) { - m_a = this.getPJ().getSemiMajorAxis(); - } - return m_a; - } - - @Override - public double getEccentricitySquared() { - if (Double.isInfinite(m_e2)) { - m_e2 = this.getPJ().getEccentricitySquared(); - } - return m_e2; - } - - public static SpatialReferenceImpl createImpl(int wkid) { - if (wkid <= 0) - throw new IllegalArgumentException("Invalid or unsupported wkid: " - + wkid); - - SpatialReferenceImpl spatRef = new SpatialReferenceImpl(); - spatRef.m_userWkid = wkid; - spatRef.m_proj4 = "+init=epsg:" + Integer.toString(wkid); - - return spatRef; - } - - public static SpatialReferenceImpl createImpl(String wkt) { - if (wkt == null || wkt.length() == 0) - throw new IllegalArgumentException( - "Cannot create SpatialReference from null or empty text."); - - int wkid = __wkid_from_wkt(wkt); - if (wkid == 0) { - SpatialReferenceImpl spatRef = new SpatialReferenceImpl(); - spatRef.m_userWkt = wkt; - return spatRef; - } - - return createImpl(wkid); - } - - private static int __wkid_from_wkt(String wkt) { - Matcher matcher = m_pattern.matcher(wkt); - if (matcher.find()) { - return Integer.parseInt(matcher.group(2)); - } - return 0; - } - - - protected static SpatialReferenceImpl createFromProj4Impl(String proj4Text) { - SpatialReferenceImpl spatRef = new SpatialReferenceImpl(); - spatRef.m_proj4 = proj4Text; - - Matcher matcher = m_proj4wkid.matcher(spatRef.m_proj4); - if (matcher.find()) { - spatRef.m_userWkid = Integer.parseInt(matcher.group(1)); - } - - return spatRef; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) - return true; - if (obj == null) - return false; - if (getClass() != obj.getClass()) - return false; - - SpatialReferenceImpl sr = (SpatialReferenceImpl) obj; - if (m_userWkid != sr.m_userWkid) - return false; - - - if (m_userWkid == 0) { - if (m_userWkt != null && sr.m_userWkt != null && m_userWkt.equals(sr.m_userWkt)) { - return true; - } - - if (m_proj4 != null && sr.m_proj4 != null && m_proj4.equals(sr.m_proj4)) { - return true; - } - - if ((m_proj4 != null || m_userWkt != null) && (sr.m_proj4 != null || sr.m_userWkt != null)) { - return false; - } - } - - return true; - } - - static double geodesicDistanceOnWGS84Impl(Point ptFrom, Point ptTo) { - double a = 6378137.0; // radius of spheroid for WGS_1984 - double e2 = 0.0066943799901413165; // ellipticity for WGS_1984 - double rpu = Math.PI / 180.0; - PeDouble answer = new PeDouble(); - GeoDist.geodesic_distance_ngs(a, e2, ptFrom.getX() * rpu, - ptFrom.getY() * rpu, ptTo.getX() * rpu, ptTo.getY() - * rpu, answer, null, null); - return answer.val; - } - - public String getAuthority() { - int latestWKID = getLatestID(); - if (latestWKID <= 0) - return new String(""); - - return getAuthority_(latestWKID); - } - - private String getAuthority_(int latestWKID) { - String authority; - - if (latestWKID >= 1024 && latestWKID <= 32767) { - - int index = Arrays.binarySearch(m_esri_codes, latestWKID); - - if (index >= 0) - authority = new String("ESRI"); - else - authority = new String("EPSG"); - } else { - authority = new String("ESRI"); - } - - return authority; - } - - private static final int[] m_esri_codes = { - 2181, // ED_1950_Turkey_9 - 2182, // ED_1950_Turkey_10 - 2183, // ED_1950_Turkey_11 - 2184, // ED_1950_Turkey_12 - 2185, // ED_1950_Turkey_13 - 2186, // ED_1950_Turkey_14 - 2187, // ED_1950_Turkey_15 - 4305, // GCS_Voirol_Unifie_1960 - 4812, // GCS_Voirol_Unifie_1960_Paris - 20002, // Pulkovo_1995_GK_Zone_2 - 20003, // Pulkovo_1995_GK_Zone_3 - 20062, // Pulkovo_1995_GK_Zone_2N - 20063, // Pulkovo_1995_GK_Zone_3N - 24721, // La_Canoa_UTM_Zone_21N - 26761, // NAD_1927_StatePlane_Hawaii_1_FIPS_5101 - 26762, // NAD_1927_StatePlane_Hawaii_2_FIPS_5102 - 26763, // NAD_1927_StatePlane_Hawaii_3_FIPS_5103 - 26764, // NAD_1927_StatePlane_Hawaii_4_FIPS_5104 - 26765, // NAD_1927_StatePlane_Hawaii_5_FIPS_5105 - 26788, // NAD_1927_StatePlane_Michigan_North_FIPS_2111 - 26789, // NAD_1927_StatePlane_Michigan_Central_FIPS_2112 - 26790, // NAD_1927_StatePlane_Michigan_South_FIPS_2113 - 30591, // Nord_Algerie - 30592, // Sud_Algerie - 31491, // Germany_Zone_1 - 31492, // Germany_Zone_2 - 31493, // Germany_Zone_3 - 31494, // Germany_Zone_4 - 31495, // Germany_Zone_5 - 32059, // NAD_1927_StatePlane_Puerto_Rico_FIPS_5201 - 32060, // NAD_1927_StatePlane_Virgin_Islands_St_Croix_FIPS_5202 - }; - - @Override - public int hashCode() { - if (m_userWkid != 0) - return NumberUtils.hash(m_userWkid); - - return m_userWkt.hashCode(); - } + // TODO GEOCENTRIC + if (m_userWkt != null) { + if (m_userWkt.contains("PROJCS")) { + return CoordinateSystemType.PROJECTED; + } else { + return CoordinateSystemType.GEOGRAPHIC; + } + + } + if (getPJ().getType() == PJ.Type.GEOGRAPHIC) { + return CoordinateSystemType.GEOGRAPHIC; + } else if (getPJ().getType() == PJ.Type.PROJECTED) { + return CoordinateSystemType.PROJECTED; + } + + return CoordinateSystemType.Uknown; + } + + @Override + double getTolerance(int semantics) { + double tolerance = 0.001; + if (m_userWkid != 0) { + tolerance = Wkid.find_tolerance_from_wkid(m_userWkid); + } else if (m_userWkt != null) { + tolerance = Wkt.find_tolerance_from_wkt(m_userWkt); + } + return tolerance; + } + + public void queryValidCoordinateRange(Envelope2D env2D) { + double delta = 0; + switch (getPrecision()) { + case Integer32: + delta = c_SULIMIT32 / getGridUnitsXY(); + break; + case Integer64: + delta = c_SULIMIT64 / getGridUnitsXY(); + break; + default: + // TODO + throw GeometryException.GeometryInternalError(); + } + + env2D.setCoords(getFalseX(), getFalseY(), getFalseX() + delta, + getFalseY() + delta); + } + + public boolean requiresReSimplify(SpatialReference dst) { + return dst != this; + } + + @Override + public String getText() { + return m_userWkt; + } + + + @Override + public String getProj4() { + return m_proj4; + } + + /** + * Returns the oldest value of the well known ID for the horizontal + * coordinate system of the spatial reference. This ID is used for JSON + * serialization. Not public. + */ + @Override + int getOldID() { + int ID_ = getID(); + + if (m_userOldestWkid != -1) + return m_userOldestWkid; + + m_userOldestWkid = Wkid.wkid_to_old(ID_); + + if (m_userOldestWkid != -1) + return m_userOldestWkid; + + return ID_; + } + + /** + * Returns the latest value of the well known ID for the horizontal + * coordinate system of the spatial reference. This ID is used for JSON + * serialization. Not public. + */ + @Override + int getLatestID() { + int ID_ = getID(); + + if (m_userLatestWkid != -1) + return m_userLatestWkid; + + m_userLatestWkid = Wkid.wkid_to_new(ID_); + + if (m_userLatestWkid != -1) + return m_userLatestWkid; + + return ID_; + } + + @Override + public double getMajorAxis() { + if (Double.isInfinite(m_a)) { + m_a = this.getPJ().getSemiMajorAxis(); + } + return m_a; + } + + @Override + public double getEccentricitySquared() { + if (Double.isInfinite(m_e2)) { + m_e2 = this.getPJ().getEccentricitySquared(); + } + return m_e2; + } + + public static SpatialReferenceImpl createImpl(int wkid) { + if (wkid <= 0) + throw new IllegalArgumentException("Invalid or unsupported wkid: " + + wkid); + + SpatialReferenceImpl spatRef = new SpatialReferenceImpl(); + spatRef.m_userWkid = wkid; + spatRef.m_proj4 = "+init=epsg:" + Integer.toString(wkid); + + return spatRef; + } + + public static SpatialReferenceImpl createImpl(String wkt) { + if (wkt == null || wkt.length() == 0) + throw new IllegalArgumentException( + "Cannot create SpatialReference from null or empty text."); + + int wkid = __wkid_from_wkt(wkt); + if (wkid == 0) { + SpatialReferenceImpl spatRef = new SpatialReferenceImpl(); + spatRef.m_userWkt = wkt; + return spatRef; + } + + return createImpl(wkid); + } + + private static int __wkid_from_wkt(String wkt) { + Matcher matcher = m_pattern.matcher(wkt); + if (matcher.find()) { + return Integer.parseInt(matcher.group(2)); + } + return 0; + } + + + protected static SpatialReferenceImpl createFromProj4Impl(String proj4Text) { + SpatialReferenceImpl spatRef = new SpatialReferenceImpl(); + spatRef.m_proj4 = proj4Text; + + Matcher matcher = m_proj4wkid.matcher(spatRef.m_proj4); + if (matcher.find()) { + spatRef.m_userWkid = Integer.parseInt(matcher.group(1)); + } + + return spatRef; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + + SpatialReferenceImpl sr = (SpatialReferenceImpl) obj; + if (m_userWkid != sr.m_userWkid) + return false; + + + if (m_userWkid == 0) { + if (m_userWkt != null && sr.m_userWkt != null && m_userWkt.equals(sr.m_userWkt)) { + return true; + } + + if (m_proj4 != null && sr.m_proj4 != null && m_proj4.equals(sr.m_proj4)) { + return true; + } + + if ((m_proj4 != null || m_userWkt != null) && (sr.m_proj4 != null || sr.m_userWkt != null)) { + return false; + } + } + + return true; + } + + static double geodesicDistanceOnWGS84Impl(Point ptFrom, Point ptTo) { + double a = 6378137.0; // radius of spheroid for WGS_1984 + double e2 = 0.0066943799901413165; // ellipticity for WGS_1984 + double rpu = Math.PI / 180.0; + PeDouble answer = new PeDouble(); + GeoDist.geodesic_distance_ngs(a, e2, ptFrom.getX() * rpu, + ptFrom.getY() * rpu, ptTo.getX() * rpu, ptTo.getY() + * rpu, answer, null, null); + return answer.val; + } + + public String getAuthority() { + int latestWKID = getLatestID(); + if (latestWKID <= 0) + return new String(""); + + return getAuthority_(latestWKID); + } + + private String getAuthority_(int latestWKID) { + String authority; + + if (latestWKID >= 1024 && latestWKID <= 32767) { + + int index = Arrays.binarySearch(m_esri_codes, latestWKID); + + if (index >= 0) + authority = new String("ESRI"); + else + authority = new String("EPSG"); + } else { + authority = new String("ESRI"); + } + + return authority; + } + + private static final int[] m_esri_codes = { + 2181, // ED_1950_Turkey_9 + 2182, // ED_1950_Turkey_10 + 2183, // ED_1950_Turkey_11 + 2184, // ED_1950_Turkey_12 + 2185, // ED_1950_Turkey_13 + 2186, // ED_1950_Turkey_14 + 2187, // ED_1950_Turkey_15 + 4305, // GCS_Voirol_Unifie_1960 + 4812, // GCS_Voirol_Unifie_1960_Paris + 20002, // Pulkovo_1995_GK_Zone_2 + 20003, // Pulkovo_1995_GK_Zone_3 + 20062, // Pulkovo_1995_GK_Zone_2N + 20063, // Pulkovo_1995_GK_Zone_3N + 24721, // La_Canoa_UTM_Zone_21N + 26761, // NAD_1927_StatePlane_Hawaii_1_FIPS_5101 + 26762, // NAD_1927_StatePlane_Hawaii_2_FIPS_5102 + 26763, // NAD_1927_StatePlane_Hawaii_3_FIPS_5103 + 26764, // NAD_1927_StatePlane_Hawaii_4_FIPS_5104 + 26765, // NAD_1927_StatePlane_Hawaii_5_FIPS_5105 + 26788, // NAD_1927_StatePlane_Michigan_North_FIPS_2111 + 26789, // NAD_1927_StatePlane_Michigan_Central_FIPS_2112 + 26790, // NAD_1927_StatePlane_Michigan_South_FIPS_2113 + 30591, // Nord_Algerie + 30592, // Sud_Algerie + 31491, // Germany_Zone_1 + 31492, // Germany_Zone_2 + 31493, // Germany_Zone_3 + 31494, // Germany_Zone_4 + 31495, // Germany_Zone_5 + 32059, // NAD_1927_StatePlane_Puerto_Rico_FIPS_5201 + 32060, // NAD_1927_StatePlane_Virgin_Islands_St_Croix_FIPS_5202 + }; + + @Override + public int hashCode() { + if (m_userWkid != 0) + return NumberUtils.hash(m_userWkid); + + return m_userWkt.hashCode(); + } } diff --git a/src/main/java/com/esri/core/geometry/SpatialReferenceSerializer.java b/src/main/java/com/esri/core/geometry/SpatialReferenceSerializer.java index 89cad20f..0cf25e6c 100644 --- a/src/main/java/com/esri/core/geometry/SpatialReferenceSerializer.java +++ b/src/main/java/com/esri/core/geometry/SpatialReferenceSerializer.java @@ -31,33 +31,33 @@ import com.esri.core.geometry.SpatialReference; final class SpatialReferenceSerializer implements Serializable { - private static final long serialVersionUID = 10000L; - String wkt = null; - int wkid = 0; - - Object readResolve() throws ObjectStreamException { - SpatialReference sr = null; - try { - if (wkid > 0) - sr = SpatialReference.create(wkid); - else - sr = SpatialReference.create(wkt); - } catch (Exception ex) { - throw new InvalidObjectException( - "Cannot read spatial reference from stream"); - } - return sr; - } - - public void setSpatialReferenceByValue(SpatialReference sr) - throws ObjectStreamException { - try { - if (sr.getID() > 0) - wkid = sr.getID(); - else - wkt = sr.getText(); - } catch (Exception ex) { - throw new InvalidObjectException("Cannot serialize this geometry"); - } - } + private static final long serialVersionUID = 10000L; + String wkt = null; + int wkid = 0; + + Object readResolve() throws ObjectStreamException { + SpatialReference sr = null; + try { + if (wkid > 0) + sr = SpatialReference.create(wkid); + else + sr = SpatialReference.create(wkt); + } catch (Exception ex) { + throw new InvalidObjectException( + "Cannot read spatial reference from stream"); + } + return sr; + } + + public void setSpatialReferenceByValue(SpatialReference sr) + throws ObjectStreamException { + try { + if (sr.getID() > 0) + wkid = sr.getID(); + else + wkt = sr.getText(); + } catch (Exception ex) { + throw new InvalidObjectException("Cannot serialize this geometry"); + } + } } diff --git a/src/main/java/com/esri/core/geometry/StridedIndexTypeCollection.java b/src/main/java/com/esri/core/geometry/StridedIndexTypeCollection.java index 1246b4c1..46ae9573 100644 --- a/src/main/java/com/esri/core/geometry/StridedIndexTypeCollection.java +++ b/src/main/java/com/esri/core/geometry/StridedIndexTypeCollection.java @@ -30,15 +30,15 @@ * time creation and deletion of an element. */ final class StridedIndexTypeCollection { - private int[][] m_buffer = null; - private int m_firstFree = -1; - private int m_last = 0; - private int m_size = 0; - private int m_capacity = 0; - private int m_bufferSize = 0; - private int m_stride; - private int m_realStride; - private int m_blockSize; + private int[][] m_buffer = null; + private int m_firstFree = -1; + private int m_last = 0; + private int m_size = 0; + private int m_capacity = 0; + private int m_bufferSize = 0; + private int m_stride; + private int m_realStride; + private int m_blockSize; /* private final static int m_realBlockSize = 2048;//if you change this, change m_blockSize, m_blockPower, m_blockMask, and st_sizes @@ -47,230 +47,230 @@ final class StridedIndexTypeCollection { private final static int[] st_sizes = {16, 32, 64, 128, 256, 512, 1024, 2048}; */ - private final static int m_realBlockSize = 16384;// if you change this, - // change m_blockSize, - // m_blockPower, - // m_blockMask, and - // st_sizes - private final static int m_blockMask = 0x3FFF; - private final static int m_blockPower = 14; - private final static int[] st_sizes = {16, 32, 64, 128, 256, 512, 1024, - 2048, 4096, 8192, 16384}; - - StridedIndexTypeCollection(int stride) { - m_stride = stride; - m_realStride = stride; - m_blockSize = m_realBlockSize / m_realStride; - } - - private boolean dbgdelete_(int element) { - m_buffer[element >> m_blockPower][(element & m_blockMask) + 1] = -0x7eadbeed; - return true; - } - - void deleteElement(int element) { - assert (dbgdelete_(element)); - int totalStrides = (element >> m_blockPower) * m_blockSize - * m_realStride + (element & m_blockMask); - if (totalStrides < m_last * m_realStride) { - m_buffer[element >> m_blockPower][element & m_blockMask] = m_firstFree; - m_firstFree = element; - } else { - assert (totalStrides == m_last * m_realStride); - m_last--; - } - m_size--; - } - - // Returns the given field of the element. - int getField(int element, int field) { - assert (m_buffer[element >> m_blockPower][(element & m_blockMask) + 1] != -0x7eadbeed); - return m_buffer[element >> m_blockPower][(element & m_blockMask) - + field]; - } - - // Sets the given field of the element. - void setField(int element, int field, int value) { - assert (m_buffer[element >> m_blockPower][(element & m_blockMask) + 1] != -0x7eadbeed); - m_buffer[element >> m_blockPower][(element & m_blockMask) + field] = value; - } - - // Returns the stride size - int getStride() { - return m_stride; - } - - // Creates the new element. This is a constant time operation. - // All fields are initialized to -1. - int newElement() { - int element = m_firstFree; - if (element == -1) { - if (m_last == m_capacity) { - long newcap = m_capacity != 0 ? (((long) m_capacity + 1) * 3 / 2) - : (long) 1; - if (newcap > Integer.MAX_VALUE) - newcap = Integer.MAX_VALUE;// cannot grow past 2gb elements - // presently - - if (newcap == m_capacity) - throw new IndexOutOfBoundsException(); - - grow_(newcap); - } - - element = ((m_last / m_blockSize) << m_blockPower) - + (m_last % m_blockSize) * m_realStride; - m_last++; - } else { - m_firstFree = m_buffer[element >> m_blockPower][element - & m_blockMask]; - } - - m_size++; - int ar[] = m_buffer[element >> m_blockPower]; - int ind = element & m_blockMask; - for (int i = 0; i < m_stride; i++) { - ar[ind + i] = -1; - } - return element; - } - - int elementToIndex(int element) { - return (element >> m_blockPower) * m_blockSize - + (element & m_blockMask) / m_realStride; - } - - // Deletes all elements and frees all the memory if b_free_memory is True. - void deleteAll(boolean b_free_memory) { - m_firstFree = -1; - m_last = 0; - m_size = 0; - if (b_free_memory) { - m_buffer = null; - m_capacity = 0; - } - } - - // Returns the count of existing elements - int size() { - return m_size; - } - - // Sets the capcity of the collection. Only applied if current capacity is - // smaller. - void setCapacity(int capacity) { - if (capacity > m_capacity) - grow_(capacity); - } - - // Returns the capacity of the collection - int capacity() { - return m_capacity; - } - - // Swaps content of two elements (each field of the stride) - void swap(int element1, int element2) { - int ar1[] = m_buffer[element1 >> m_blockPower]; - int ar2[] = m_buffer[element2 >> m_blockPower]; - int ind1 = element1 & m_blockMask; - int ind2 = element2 & m_blockMask; - for (int i = 0; i < m_stride; i++) { - int tmp = ar1[ind1 + i]; - ar1[ind1 + i] = ar2[ind2 + i]; - ar2[ind2 + i] = tmp; - } - } - - // Swaps content of two fields - void swapField(int element1, int element2, int field) { - int ar1[] = m_buffer[element1 >> m_blockPower]; - int ar2[] = m_buffer[element2 >> m_blockPower]; - int ind1 = (element1 & m_blockMask) + field; - int ind2 = (element2 & m_blockMask) + field; - int tmp = ar1[ind1]; - ar1[ind1] = ar2[ind2]; - ar2[ind2] = tmp; - } - - // Returns a value of the index, that never will be returned by new_element - // and is neither -1 nor impossible_index_3. - static int impossibleIndex2() { - return -2; - } - - // Returns a value of the index, that never will be returned by new_element - // and is neither -1 nor impossible_index_2. - static int impossibleIndex3() { - return -3; - } - - static boolean isValidElement(int element) { - return element >= 0; - } - - private void ensureBufferBlocksCapacity(int blocks) { - if (m_buffer.length < blocks) { - int[][] newBuffer = new int[blocks][]; - for (int i = 0; i < m_buffer.length; i++) { - newBuffer[i] = m_buffer[i]; - } - - m_buffer = newBuffer; - } - } - - private void grow_(long newsize) { - if (m_buffer == null) { - m_bufferSize = 0; - m_buffer = new int[8][]; - } - - assert (newsize > m_capacity); - - long nblocks = (newsize + m_blockSize - 1) / m_blockSize; - if (nblocks > Integer.MAX_VALUE) - throw new IndexOutOfBoundsException(); - - ensureBufferBlocksCapacity((int) nblocks); - if (nblocks == 1) { - // When less than one block is needed we allocate smaller arrays - // than m_realBlockSize to avoid initialization cost. - int oldsz = m_capacity > 0 ? m_capacity : 0; - assert (oldsz < newsize); - int i = 0; - int realnewsize = (int) newsize * m_realStride; - while (realnewsize > st_sizes[i]) - // get the size to allocate. Using fixed sizes to reduce - // fragmentation. - i++; - int[] b = new int[st_sizes[i]]; - if (m_bufferSize == 1) { - System.arraycopy(m_buffer[0], 0, b, 0, m_buffer[0].length); - m_buffer[0] = b; - } else { - m_buffer[m_bufferSize] = b; - m_bufferSize++; - } - m_capacity = b.length / m_realStride; - } else { - if (nblocks * m_blockSize > Integer.MAX_VALUE) - throw new IndexOutOfBoundsException(); - - if (m_bufferSize == 1) { - if (m_buffer[0].length < m_realBlockSize) { - // resize the first buffer to ensure it is equal the - // m_realBlockSize. - int[] b = new int[m_realBlockSize]; - System.arraycopy(m_buffer[0], 0, b, 0, m_buffer[0].length); - m_buffer[0] = b; - m_capacity = m_blockSize; - } - } - - while (m_bufferSize < nblocks) { - m_buffer[m_bufferSize++] = new int[m_realBlockSize]; - m_capacity += m_blockSize; - } - } - } + private final static int m_realBlockSize = 16384;// if you change this, + // change m_blockSize, + // m_blockPower, + // m_blockMask, and + // st_sizes + private final static int m_blockMask = 0x3FFF; + private final static int m_blockPower = 14; + private final static int[] st_sizes = {16, 32, 64, 128, 256, 512, 1024, + 2048, 4096, 8192, 16384}; + + StridedIndexTypeCollection(int stride) { + m_stride = stride; + m_realStride = stride; + m_blockSize = m_realBlockSize / m_realStride; + } + + private boolean dbgdelete_(int element) { + m_buffer[element >> m_blockPower][(element & m_blockMask) + 1] = -0x7eadbeed; + return true; + } + + void deleteElement(int element) { + assert (dbgdelete_(element)); + int totalStrides = (element >> m_blockPower) * m_blockSize + * m_realStride + (element & m_blockMask); + if (totalStrides < m_last * m_realStride) { + m_buffer[element >> m_blockPower][element & m_blockMask] = m_firstFree; + m_firstFree = element; + } else { + assert (totalStrides == m_last * m_realStride); + m_last--; + } + m_size--; + } + + // Returns the given field of the element. + int getField(int element, int field) { + assert (m_buffer[element >> m_blockPower][(element & m_blockMask) + 1] != -0x7eadbeed); + return m_buffer[element >> m_blockPower][(element & m_blockMask) + + field]; + } + + // Sets the given field of the element. + void setField(int element, int field, int value) { + assert (m_buffer[element >> m_blockPower][(element & m_blockMask) + 1] != -0x7eadbeed); + m_buffer[element >> m_blockPower][(element & m_blockMask) + field] = value; + } + + // Returns the stride size + int getStride() { + return m_stride; + } + + // Creates the new element. This is a constant time operation. + // All fields are initialized to -1. + int newElement() { + int element = m_firstFree; + if (element == -1) { + if (m_last == m_capacity) { + long newcap = m_capacity != 0 ? (((long) m_capacity + 1) * 3 / 2) + : (long) 1; + if (newcap > Integer.MAX_VALUE) + newcap = Integer.MAX_VALUE;// cannot grow past 2gb elements + // presently + + if (newcap == m_capacity) + throw new IndexOutOfBoundsException(); + + grow_(newcap); + } + + element = ((m_last / m_blockSize) << m_blockPower) + + (m_last % m_blockSize) * m_realStride; + m_last++; + } else { + m_firstFree = m_buffer[element >> m_blockPower][element + & m_blockMask]; + } + + m_size++; + int ar[] = m_buffer[element >> m_blockPower]; + int ind = element & m_blockMask; + for (int i = 0; i < m_stride; i++) { + ar[ind + i] = -1; + } + return element; + } + + int elementToIndex(int element) { + return (element >> m_blockPower) * m_blockSize + + (element & m_blockMask) / m_realStride; + } + + // Deletes all elements and frees all the memory if b_free_memory is True. + void deleteAll(boolean b_free_memory) { + m_firstFree = -1; + m_last = 0; + m_size = 0; + if (b_free_memory) { + m_buffer = null; + m_capacity = 0; + } + } + + // Returns the count of existing elements + int size() { + return m_size; + } + + // Sets the capcity of the collection. Only applied if current capacity is + // smaller. + void setCapacity(int capacity) { + if (capacity > m_capacity) + grow_(capacity); + } + + // Returns the capacity of the collection + int capacity() { + return m_capacity; + } + + // Swaps content of two elements (each field of the stride) + void swap(int element1, int element2) { + int ar1[] = m_buffer[element1 >> m_blockPower]; + int ar2[] = m_buffer[element2 >> m_blockPower]; + int ind1 = element1 & m_blockMask; + int ind2 = element2 & m_blockMask; + for (int i = 0; i < m_stride; i++) { + int tmp = ar1[ind1 + i]; + ar1[ind1 + i] = ar2[ind2 + i]; + ar2[ind2 + i] = tmp; + } + } + + // Swaps content of two fields + void swapField(int element1, int element2, int field) { + int ar1[] = m_buffer[element1 >> m_blockPower]; + int ar2[] = m_buffer[element2 >> m_blockPower]; + int ind1 = (element1 & m_blockMask) + field; + int ind2 = (element2 & m_blockMask) + field; + int tmp = ar1[ind1]; + ar1[ind1] = ar2[ind2]; + ar2[ind2] = tmp; + } + + // Returns a value of the index, that never will be returned by new_element + // and is neither -1 nor impossible_index_3. + static int impossibleIndex2() { + return -2; + } + + // Returns a value of the index, that never will be returned by new_element + // and is neither -1 nor impossible_index_2. + static int impossibleIndex3() { + return -3; + } + + static boolean isValidElement(int element) { + return element >= 0; + } + + private void ensureBufferBlocksCapacity(int blocks) { + if (m_buffer.length < blocks) { + int[][] newBuffer = new int[blocks][]; + for (int i = 0; i < m_buffer.length; i++) { + newBuffer[i] = m_buffer[i]; + } + + m_buffer = newBuffer; + } + } + + private void grow_(long newsize) { + if (m_buffer == null) { + m_bufferSize = 0; + m_buffer = new int[8][]; + } + + assert (newsize > m_capacity); + + long nblocks = (newsize + m_blockSize - 1) / m_blockSize; + if (nblocks > Integer.MAX_VALUE) + throw new IndexOutOfBoundsException(); + + ensureBufferBlocksCapacity((int) nblocks); + if (nblocks == 1) { + // When less than one block is needed we allocate smaller arrays + // than m_realBlockSize to avoid initialization cost. + int oldsz = m_capacity > 0 ? m_capacity : 0; + assert (oldsz < newsize); + int i = 0; + int realnewsize = (int) newsize * m_realStride; + while (realnewsize > st_sizes[i]) + // get the size to allocate. Using fixed sizes to reduce + // fragmentation. + i++; + int[] b = new int[st_sizes[i]]; + if (m_bufferSize == 1) { + System.arraycopy(m_buffer[0], 0, b, 0, m_buffer[0].length); + m_buffer[0] = b; + } else { + m_buffer[m_bufferSize] = b; + m_bufferSize++; + } + m_capacity = b.length / m_realStride; + } else { + if (nblocks * m_blockSize > Integer.MAX_VALUE) + throw new IndexOutOfBoundsException(); + + if (m_bufferSize == 1) { + if (m_buffer[0].length < m_realBlockSize) { + // resize the first buffer to ensure it is equal the + // m_realBlockSize. + int[] b = new int[m_realBlockSize]; + System.arraycopy(m_buffer[0], 0, b, 0, m_buffer[0].length); + m_buffer[0] = b; + m_capacity = m_blockSize; + } + } + + while (m_bufferSize < nblocks) { + m_buffer[m_bufferSize++] = new int[m_realBlockSize]; + m_capacity += m_blockSize; + } + } + } } diff --git a/src/main/java/com/esri/core/geometry/StringCursor.java b/src/main/java/com/esri/core/geometry/StringCursor.java index 35d47156..c1faebe5 100644 --- a/src/main/java/com/esri/core/geometry/StringCursor.java +++ b/src/main/java/com/esri/core/geometry/StringCursor.java @@ -3,13 +3,13 @@ import java.util.Iterator; public abstract class StringCursor implements Iterator { - public abstract String next(); + public abstract String next(); - public abstract long getID(); + public abstract long getID(); - public abstract SimpleStateEnum getSimpleState(); + public abstract SimpleStateEnum getSimpleState(); - public abstract String getFeatureID(); + public abstract String getFeatureID(); - public abstract boolean hasNext(); + public abstract boolean hasNext(); } diff --git a/src/main/java/com/esri/core/geometry/StringUtils.java b/src/main/java/com/esri/core/geometry/StringUtils.java index 3c5d4c3c..9a3cd781 100644 --- a/src/main/java/com/esri/core/geometry/StringUtils.java +++ b/src/main/java/com/esri/core/geometry/StringUtils.java @@ -27,85 +27,85 @@ class StringUtils { - static void appendDouble(double value, int precision, - StringBuilder stringBuilder) { - if (precision < 0) { - precision = 0; - } else if (precision > 17) { - precision = 17; - } - - String format = "%." + precision + "g"; - - String str_dbl = String.format(Locale.US, format, value); - - boolean b_found_dot = false; - boolean b_found_exponent = false; - - for (int i = 0; i < str_dbl.length(); i++) { - char c = str_dbl.charAt(i); - - if (c == '.') { - b_found_dot = true; - } else if (c == 'e' || c == 'E') { - b_found_exponent = true; - break; - } - } - - if (b_found_dot && !b_found_exponent) { - StringBuilder buffer = removeTrailingZeros_(str_dbl); - stringBuilder.append(buffer); - } else { - stringBuilder.append(str_dbl); - } - } - - static void appendDoubleF(double value, int decimals, - StringBuilder stringBuilder) { - if (decimals < 0) { - decimals = 0; - } else if (decimals > 17) { - decimals = 17; - } - - String format = "%." + decimals + "f"; - - String str_dbl = String.format(Locale.US, format, value); - - boolean b_found_dot = false; - - for (int i = 0; i < str_dbl.length(); i++) { - char c = str_dbl.charAt(i); - - if (c == '.') { - b_found_dot = true; - break; - } - } - - if (b_found_dot) { - StringBuilder buffer = removeTrailingZeros_(str_dbl); - stringBuilder.append(buffer); - } else { - stringBuilder.append(str_dbl); - } - } - - static private StringBuilder removeTrailingZeros_(String str_dbl) { - StringBuilder buffer = new StringBuilder(str_dbl); - int non_zero = buffer.length() - 1; - - while (buffer.charAt(non_zero) == '0') { - non_zero--; - } - - buffer.delete(non_zero + 1, buffer.length()); - - if (buffer.charAt(non_zero) == '.') { - buffer.deleteCharAt(non_zero); - } - - return buffer; - } + static void appendDouble(double value, int precision, + StringBuilder stringBuilder) { + if (precision < 0) { + precision = 0; + } else if (precision > 17) { + precision = 17; + } + + String format = "%." + precision + "g"; + + String str_dbl = String.format(Locale.US, format, value); + + boolean b_found_dot = false; + boolean b_found_exponent = false; + + for (int i = 0; i < str_dbl.length(); i++) { + char c = str_dbl.charAt(i); + + if (c == '.') { + b_found_dot = true; + } else if (c == 'e' || c == 'E') { + b_found_exponent = true; + break; + } + } + + if (b_found_dot && !b_found_exponent) { + StringBuilder buffer = removeTrailingZeros_(str_dbl); + stringBuilder.append(buffer); + } else { + stringBuilder.append(str_dbl); + } + } + + static void appendDoubleF(double value, int decimals, + StringBuilder stringBuilder) { + if (decimals < 0) { + decimals = 0; + } else if (decimals > 17) { + decimals = 17; + } + + String format = "%." + decimals + "f"; + + String str_dbl = String.format(Locale.US, format, value); + + boolean b_found_dot = false; + + for (int i = 0; i < str_dbl.length(); i++) { + char c = str_dbl.charAt(i); + + if (c == '.') { + b_found_dot = true; + break; + } + } + + if (b_found_dot) { + StringBuilder buffer = removeTrailingZeros_(str_dbl); + stringBuilder.append(buffer); + } else { + stringBuilder.append(str_dbl); + } + } + + static private StringBuilder removeTrailingZeros_(String str_dbl) { + StringBuilder buffer = new StringBuilder(str_dbl); + int non_zero = buffer.length() - 1; + + while (buffer.charAt(non_zero) == '0') { + non_zero--; + } + + buffer.delete(non_zero + 1, buffer.length()); + + if (buffer.charAt(non_zero) == '.') { + buffer.deleteCharAt(non_zero); + } + + return buffer; + } } diff --git a/src/main/java/com/esri/core/geometry/SweepComparator.java b/src/main/java/com/esri/core/geometry/SweepComparator.java index c7ea2996..2ea54aa9 100644 --- a/src/main/java/com/esri/core/geometry/SweepComparator.java +++ b/src/main/java/com/esri/core/geometry/SweepComparator.java @@ -30,647 +30,647 @@ import java.util.ArrayList; class SweepComparator extends Treap.Comparator { - static final class SimpleEdge { - int m_value; - Line m_line; - Segment m_segment; - Envelope1D m_env; - double m_dxdy; - boolean m_b_horizontal; - boolean m_b_curve; - - SimpleEdge() { - m_value = -1; - m_line = new Line(); - m_dxdy = 55555555; - m_b_horizontal = false; - m_b_curve = false; - - m_env = new Envelope1D(); - m_env.setCoordsNoNaN_(0, 0); - } - } - - private EditShape m_shape; - boolean m_b_intersection_detected; - NonSimpleResult m_non_simple_result; - // Index 1 corresponds to the left segments, index 2 - right, e.g. m_line_1, - // m_line_2 - SimpleEdge m_temp_simple_edge_1; - SimpleEdge m_temp_simple_edge_2; - - int m_prev_1; - int m_prev_2; - int m_vertex_1; - int m_vertex_2; - int m_current_node; - double m_prevx_1; - double m_prevx_2; - double m_prev_y; - double m_prev_x; - double m_sweep_y; - double m_sweep_x; - double m_tolerance; - double m_tolerance_10; - boolean m_b_is_simple; - - ArrayList m_simple_edges_cache; - ArrayList m_simple_edges_recycle; - ArrayList m_simple_edges_buffer; - - // Returns a cached edge for the given value. May return NULL. - SimpleEdge tryGetCachedEdge_(int value) { - SimpleEdge se = m_simple_edges_cache.get((value & NumberUtils.intMax()) % m_simple_edges_cache.size()); - if (se != null) { - if (se.m_value == value) - return se; - else { - // int i = 0; - // cache collision - } - } - return null; - } - - // Removes cached edge from the cache for the given value. - void tryDeleteCachedEdge_(int value) { - int ind = (value & NumberUtils.intMax()) % m_simple_edges_cache.size(); - SimpleEdge se = m_simple_edges_cache.get(ind); - if (se != null && se.m_value == value) {// this value is cached - m_simple_edges_recycle.add(se); - m_simple_edges_cache.set(ind, null); - } else { - // The value has not been cached - } - } - - // Creates a cached edge. May fail and return NULL. - SimpleEdge tryCreateCachedEdge_(int value) { - int ind = (value & NumberUtils.intMax()) % m_simple_edges_cache.size(); - SimpleEdge se = m_simple_edges_cache.get(ind); - if (se == null) { - if (m_simple_edges_recycle.isEmpty()) { - // assert(m_simple_edges_buffer.size() < - // m_simple_edges_buffer.capacity());//should never happen - // assert(m_simple_edges_buffer.size() < - // m_simple_edges_cache.size());//should never happen - m_simple_edges_buffer.add(new SimpleEdge()); - se = m_simple_edges_buffer - .get(m_simple_edges_buffer.size() - 1); - } else { - se = m_simple_edges_recycle - .get(m_simple_edges_recycle.size() - 1); - m_simple_edges_recycle - .remove(m_simple_edges_recycle.size() - 1); - } - - se.m_value = value; - m_simple_edges_cache.set(ind, se); - return se; - } else { - assert (se.m_value != value);// do not call TryCreateCachedEdge - // twice. - } - - return null; - } - - void initSimpleEdge_(SweepComparator.SimpleEdge se, int vertex) { - se.m_segment = m_shape.getSegment(vertex); - se.m_b_curve = se.m_segment != null; - if (!se.m_b_curve) { - m_shape.queryLineConnector(vertex, se.m_line); - se.m_segment = se.m_line; - se.m_env.setCoordsNoNaN_(se.m_line.getStartX(), se.m_line.getEndX()); - se.m_env.vmax += m_tolerance; - se.m_line.orientBottomUp_(); - se.m_b_horizontal = se.m_line.getEndY() == se.m_line.getStartY(); - if (!se.m_b_horizontal) { - se.m_dxdy = (se.m_line.getEndX() - se.m_line.getStartX()) - / (se.m_line.getEndY() - se.m_line.getStartY()); - } - } else { - // se.m_segment = se.m_segment_sptr.get(); - } - } - - // Compares seg_1 and seg_2 x coordinates of intersection with the line - // parallel to axis x, passing through the coordinate y. - // If segments intersect not at the endpoint, the m_b_intersection_detected - // is set. - int compareTwoSegments_(Segment seg_1, Segment seg_2) { - int res = seg_1._isIntersecting(seg_2, m_tolerance, true); - if (res != 0) { - if (res == 2) - return errorCoincident(); - else - return errorCracking(); - } - - Point2D start_1 = seg_1.getStartXY(); - Point2D end1 = seg_1.getEndXY(); - Point2D start2 = seg_2.getStartXY(); - Point2D end2 = seg_2.getEndXY(); - Point2D ptSweep = new Point2D(); - ptSweep.setCoords(m_sweep_x, m_sweep_y); - if (start_1.isEqual(start2) && m_sweep_y == start_1.y) { - assert (start_1.compare(end1) < 0 && start2.compare(end2) < 0); - if (end1.compare(end2) < 0) - ptSweep.setCoords(end1); - else - ptSweep.setCoords(end2); - } else if (start_1.isEqual(end2) && m_sweep_y == start_1.y) { - assert (start_1.compare(end1) < 0 && start2.compare(end2) > 0); - if (end1.compare(start2) < 0) - ptSweep.setCoords(end1); - else - ptSweep.setCoords(start2); - } else if (start2.isEqual(end1) && m_sweep_y == start2.y) { - assert (end1.compare(start_1) < 0 && start2.compare(end2) < 0); - if (start_1.compare(end2) < 0) - ptSweep.setCoords(start_1); - else - ptSweep.setCoords(end2); - } else if (end1.isEqual(end2) && m_sweep_y == end1.y) { - assert (start_1.compare(end1) > 0 && start2.compare(end2) > 0); - if (start_1.compare(start2) < 0) - ptSweep.setCoords(start_1); - else - ptSweep.setCoords(start2); - } - - double xleft = seg_1.intersectionOfYMonotonicWithAxisX(ptSweep.y, - ptSweep.x); - double xright = seg_2.intersectionOfYMonotonicWithAxisX(ptSweep.y, - ptSweep.x); - assert (xleft != xright); - return xleft < xright ? -1 : 1; - } - - int compareNonHorizontal_(SimpleEdge line_1, SimpleEdge line_2) { - if (line_1.m_line.getStartY() == line_2.m_line.getStartY() - && line_1.m_line.getStartX() == line_2.m_line.getStartX()) {// connected - // at - // the - // start - // V - // shape - if (line_1.m_line.getEndY() == line_2.m_line.getEndY() - && line_1.m_line.getEndX() == line_2.m_line.getEndX()) {// connected - // at - // another - // end - // also - if (m_b_is_simple) - return errorCoincident(); - return 0; - } - - return compareNonHorizontalUpperEnd_(line_1, line_2); - } - - if (line_1.m_line.getEndY() == line_2.m_line.getEndY() - && line_1.m_line.getEndX() == line_2.m_line.getEndX()) { - // the case of upside-down V. - return compareNonHorizontalLowerEnd_(line_1, line_2); - } - - int lower = compareNonHorizontalLowerEnd_(line_1, line_2); - int upper = compareNonHorizontalUpperEnd_(line_1, line_2); - if (lower < 0 && upper < 0) - return -1; - if (lower > 0 && upper > 0) - return 1; - - return errorCracking(); - } - - int compareHorizontal1Case1_(Line line_1, Line line_2) { - // line_2 goes up and line_1 is horizontal connected at the start going - // to the right. - if (line_1.getEndX() > line_2.getEndX()) { - // / - // / - // +------------------ - if (line_2.getEndX() > line_2.getStartX() - && line_2.getEndY() - line_2.getStartY() < 2 * m_tolerance - && line_1._isIntersectingPoint(line_2.getEndXY(), - m_tolerance, true)) - return errorCracking(); - } else { - // / - // / - // / - // +-- - assert (line_2.getEndX() - line_2.getStartX() != 0); - // Note: line_2 cannot be vertical here - // Avoid expensive is_intersecting_ by providing a simple estimate. - double dydx = (line_2.getEndY() - line_2.getStartY()) - / (line_2.getEndX() - line_2.getStartX()); - double d = dydx * (line_1.getEndX() - line_1.getStartX()); - if (d < m_tolerance_10 - && line_2._isIntersectingPoint(line_1.getEndXY(), - m_tolerance, true)) - return errorCracking(); - } - - return 1; - } - - int compareHorizontal1Case2_(Line line_1, Line line_2) { - // -----------------+ - // / - // / - // / - // line_2 goes up and below line_1. line_1 is horizontal connected at - // the end to the line_2 end. - if (line_1.getStartX() < line_2.getStartX()) { - if (line_2.getEndX() > line_2.getStartX() - && line_2.getEndY() - line_2.getStartY() < 2 * m_tolerance - && line_1._isIntersectingPoint(line_2.getEndXY(), - m_tolerance, true)) - return errorCracking(); - } else { - // --+ - // / - // / - // / - // Avoid expensive is_intersecting_ by providing a simple estimate. - double dydx = (line_2.getEndY() - line_2.getStartY()) - / (line_2.getEndX() - line_2.getStartX()); - double d = dydx * (line_1.getStartX() - line_1.getEndX()); - if (d < m_tolerance_10 - && line_2._isIntersectingPoint(line_1.getStartXY(), - m_tolerance, true)) - return errorCracking(); - } - - return -1; - } - - int compareHorizontal1Case3_(Line line_1, Line line_2) { - Point2D v0 = new Point2D(); - v0.sub(line_2.getEndXY(), line_2.getStartXY()); - v0.rightPerpendicular(); - v0.normalize(); - Point2D v_1 = new Point2D(); - v_1.sub(line_1.getStartXY(), line_2.getStartXY()); - Point2D v_2 = new Point2D(); - v_2.sub(line_1.getEndXY(), line_2.getStartXY()); - double d_1 = v_1.dotProduct(v0); - double d_2 = v_2.dotProduct(v0); - - double ad1 = Math.abs(d_1); - double ad2 = Math.abs(d_2); - - if (ad1 < ad2) { - if (ad1 < m_tolerance_10 - && line_2._isIntersectingPoint(line_1.getStartXY(), - m_tolerance, true)) - return errorCracking(); - } else { - if (ad2 < m_tolerance_10 - && line_2._isIntersectingPoint(line_1.getEndXY(), - m_tolerance, true)) - return errorCracking(); - } - - if (d_1 < 0 && d_2 < 0) - return -1; - - if (d_1 > 0 && d_2 > 0) - return 1; - - return errorCracking(); - } - - int compareHorizontal1_(Line line_1, Line line_2) { - // Two most important cases of connecting edges - if (line_1.getStartY() == line_2.getStartY() - && line_1.getStartX() == line_2.getStartX()) { - return compareHorizontal1Case1_(line_1, line_2); - } - - if (line_1.getEndY() == line_2.getEndY() - && line_1.getEndX() == line_2.getEndX()) { - return compareHorizontal1Case2_(line_1, line_2); - } - - return compareHorizontal1Case3_(line_1, line_2); - } - - int compareHorizontal2_(Line line_1, Line line_2) { - if (line_1.getEndY() == line_2.getEndY() - && line_1.getEndX() == line_2.getEndX() - && line_1.getStartY() == line_2.getStartY() - && line_1.getStartX() == line_2.getStartX()) {// both lines - // coincide - if (m_b_is_simple) - return errorCoincident(); - return 0; - } else - return errorCracking(); - } - - int compareNonHorizontalLowerEnd_(SimpleEdge line_1, SimpleEdge line_2) { - int sign = 1; - if (line_1.m_line.getStartY() < line_2.m_line.getStartY()) { - sign = -1; - SimpleEdge tmp = line_1; - line_1 = line_2; - line_2 = tmp; - } - - Line l1 = line_1.m_line; - Line l2 = line_2.m_line; - // Now line_1 has Start point higher than line_2 startpoint. - double x_1 = l1.getStartX() - l2.getStartX(); - double x2 = line_2.m_dxdy * (l1.getStartY() - l2.getStartY()); - double tol = m_tolerance_10; - if (x_1 < x2 - tol) - return -sign; - else if (x_1 > x2 + tol) - return sign; - else // Possible problem - { - if (l2._isIntersectingPoint(l1.getStartXY(), m_tolerance, true)) - return errorCracking(); - return x_1 < x2 ? -sign : sign; - } - } - - int compareNonHorizontalUpperEnd_(SimpleEdge line_1, SimpleEdge line_2) { - int sign = 1; - if (line_2.m_line.getEndY() < line_1.m_line.getEndY()) { - sign = -1; - SimpleEdge tmp = line_1; - line_1 = line_2; - line_2 = tmp; - } - - Line l1 = line_1.m_line; - Line l2 = line_2.m_line; - // Now line_1 has End point lower than line_2 endpoint. - double x_1 = l1.getEndX() - l2.getStartX(); - double x2 = line_2.m_dxdy * (l1.getEndY() - l2.getStartY()); - double tol = m_tolerance_10; - if (x_1 < x2 - tol) - return -sign; - else if (x_1 > x2 + tol) - return sign; - else // Possible problem - { - if (l2._isIntersectingPoint(l1.getEndXY(), m_tolerance, true)) - return errorCracking(); - return x_1 < x2 ? -sign : sign; - } - } - - int errorCoincident() {// two segments coincide. - m_b_intersection_detected = true; - assert (m_b_is_simple); - NonSimpleResult.Reason reason = NonSimpleResult.Reason.CrossOver; - m_non_simple_result = new NonSimpleResult(reason, m_vertex_1, - m_vertex_2); - return -1; - } - - int errorCracking() {// cracking error - m_b_intersection_detected = true; - if (m_b_is_simple) {// only report the reason in IsSimple. Do not do - // that for regular cracking. - NonSimpleResult.Reason reason = NonSimpleResult.Reason.Cracking; - m_non_simple_result = new NonSimpleResult(reason, m_vertex_1, - m_vertex_2); - } else {// reset cached data after detected intersection - m_prev_1 = -1; - m_prev_2 = -1; - m_vertex_1 = -1; - m_vertex_2 = -1; - } - return -1; - } - - int compareSegments_(int left, int right, SimpleEdge segLeft, - SimpleEdge segRight) { - if (m_b_intersection_detected) - return -1; - - boolean sameY = m_prev_y == m_sweep_y && m_prev_x == m_sweep_x; - double xleft; - if (sameY && left == m_prev_1) - xleft = m_prevx_1; - else { - xleft = NumberUtils.NaN(); - m_prev_1 = -1; - } - double xright; - if (sameY && right == m_prev_2) - xright = m_prevx_2; - else { - xright = NumberUtils.NaN(); - m_prev_2 = -1; - } - - // Quickly compare x projections. - Envelope1D envLeft = segLeft.m_segment.queryInterval( - VertexDescription.Semantics.POSITION, 0); - Envelope1D envRight = segRight.m_segment.queryInterval( - VertexDescription.Semantics.POSITION, 0); - if (envLeft.vmax < envRight.vmin) - return -1; - if (envRight.vmax < envLeft.vmin) - return 1; - - m_prev_y = m_sweep_y; - m_prev_x = m_sweep_x; - - // Now do intersection with the sweep line (it is a line parallel to the - // axis x.) - if (NumberUtils.isNaN(xleft)) { - m_prev_1 = left; - double x = segLeft.m_segment.intersectionOfYMonotonicWithAxisX( - m_sweep_y, m_sweep_x); - xleft = x; - m_prevx_1 = x; - } - if (NumberUtils.isNaN(xright)) { - m_prev_2 = right; - double x = segRight.m_segment.intersectionOfYMonotonicWithAxisX( - m_sweep_y, m_sweep_x); - xright = x; - m_prevx_2 = x; - } - - if (Math.abs(xleft - xright) <= m_tolerance) { - // special processing as we cannot decide in a simple way. - return compareTwoSegments_(segLeft.m_segment, segRight.m_segment); - } else { - return xleft < xright ? -1 : xleft > xright ? 1 : 0; - } - } - - SweepComparator(EditShape shape, double tol, boolean bIsSimple) { - super(true); - m_shape = shape; - m_sweep_y = NumberUtils.TheNaN; - m_sweep_x = 0; - m_prev_x = 0; - m_prev_y = NumberUtils.TheNaN; - m_tolerance = tol; - m_tolerance_10 = 10 * tol; - m_prevx_2 = NumberUtils.TheNaN; - m_prevx_1 = NumberUtils.TheNaN; - m_b_intersection_detected = false; - m_prev_1 = -1; - m_prev_2 = -1; - m_vertex_1 = -1; - m_vertex_2 = -1; - m_current_node = -1; - m_b_is_simple = bIsSimple; - m_temp_simple_edge_1 = new SimpleEdge(); - m_temp_simple_edge_2 = new SimpleEdge(); - - int s = Math.min(shape.getTotalPointCount() * 3 / 2, - (int) (67 /* SIMPLEDGE_CACHESIZE */)); - int cache_size = Math.min((int) 7, s); - // m_simple_edges_buffer.reserve(cache_size);//must be reserved and - // never grow beyond reserved size - - m_simple_edges_buffer = new ArrayList(); - m_simple_edges_recycle = new ArrayList(); - m_simple_edges_cache = new ArrayList(); - - for (int i = 0; i < cache_size; i++) - m_simple_edges_cache.add(null); - } - - // Makes the comparator to forget about the last detected intersection. - // Need to be called after the intersection has been resolved. - void clearIntersectionDetectedFlag() { - m_b_intersection_detected = false; - } - - // Returns True if there has been intersection detected during compare call. - // Once intersection is detected subsequent calls to compare method do - // nothing until clear_intersection_detected_flag is called. - boolean intersectionDetected() { - return m_b_intersection_detected; - } - - // Returns the node at which the intersection has been detected - int getLastComparedNode() { - return m_current_node; - } - - // When used in IsSimple (see corresponding parameter in ctor), returns the - // reason of non-simplicity - NonSimpleResult getResult() { - return m_non_simple_result; - } - - // Sets new sweep line position. - void setSweepY(double y, double x) { - // _ASSERT(m_sweep_y != y || m_sweep_x != x); - m_sweep_y = y; - m_sweep_x = x; - m_prev_1 = -1; - m_prev_2 = -1; - m_vertex_1 = -1; - m_vertex_2 = -1; - } - - // The compare method. Compares x values of the edge given by its origin - // (elm) and the edge in the sweep structure and checks them for - // intersection at the same time. - @Override - int compare(Treap treap, int left, int node) { - // Compares two segments on a sweep line passing through m_sweep_y, - // m_sweep_x. - if (m_b_intersection_detected) - return -1; - - int right = treap.getElement(node); - m_current_node = node; - return compareSegments(left, left, right, right); - } - - int compareSegments(int leftElm, int left_vertex, int right_elm, - int right_vertex) { - SimpleEdge edgeLeft = tryGetCachedEdge_(leftElm); - if (edgeLeft == null) { - if (m_vertex_1 == left_vertex) - edgeLeft = m_temp_simple_edge_1; - else { - m_vertex_1 = left_vertex; - edgeLeft = tryCreateCachedEdge_(leftElm); - if (edgeLeft == null) { - edgeLeft = m_temp_simple_edge_1; - m_temp_simple_edge_1.m_value = leftElm; - } - initSimpleEdge_(edgeLeft, left_vertex); - } - } else - m_vertex_1 = left_vertex; - - SimpleEdge edgeRight = tryGetCachedEdge_(right_elm); - if (edgeRight == null) { - if (m_vertex_2 == right_vertex) - edgeRight = m_temp_simple_edge_2; - else { - m_vertex_2 = right_vertex; - edgeRight = tryCreateCachedEdge_(right_elm); - if (edgeRight == null) { - edgeRight = m_temp_simple_edge_2; - m_temp_simple_edge_2.m_value = right_elm; - } - initSimpleEdge_(edgeRight, right_vertex); - } - } else - m_vertex_2 = right_vertex; - - if (edgeLeft.m_b_curve || edgeRight.m_b_curve) - return compareSegments_(left_vertex, right_vertex, edgeLeft, - edgeRight); - - // Usually we work with lines, so process them in the fastest way. - // First check - assume segments are far apart. compare x intervals - if (edgeLeft.m_env.vmax < edgeRight.m_env.vmin) - return -1; - if (edgeRight.m_env.vmax < edgeLeft.m_env.vmin) - return 1; - - // compare case by case. - int kind = edgeLeft.m_b_horizontal ? 1 : 0; - kind |= edgeRight.m_b_horizontal ? 2 : 0; - if (kind == 0)// both segments are non-horizontal - return compareNonHorizontal_(edgeLeft, edgeRight); - else if (kind == 1) // line_1 horizontal, line_2 is not - return compareHorizontal1_(edgeLeft.m_line, edgeRight.m_line); - else if (kind == 2) // line_2 horizontal, line_1 is not - return compareHorizontal1_(edgeRight.m_line, edgeLeft.m_line) * -1; - else - // if (kind == 3) //both horizontal - return compareHorizontal2_(edgeLeft.m_line, edgeRight.m_line); - } - - @Override - void onDelete(int elm) { - tryDeleteCachedEdge_(elm); - } - - @Override - void onSet(int oldelm) { - tryDeleteCachedEdge_(oldelm); - } - - @Override - void onEndSearch(int elm) { - tryDeleteCachedEdge_(elm); - } - - @Override - void onAddUniqueElementFailed(int elm) { - tryDeleteCachedEdge_(elm); - } + static final class SimpleEdge { + int m_value; + Line m_line; + Segment m_segment; + Envelope1D m_env; + double m_dxdy; + boolean m_b_horizontal; + boolean m_b_curve; + + SimpleEdge() { + m_value = -1; + m_line = new Line(); + m_dxdy = 55555555; + m_b_horizontal = false; + m_b_curve = false; + + m_env = new Envelope1D(); + m_env.setCoordsNoNaN_(0, 0); + } + } + + private EditShape m_shape; + boolean m_b_intersection_detected; + NonSimpleResult m_non_simple_result; + // Index 1 corresponds to the left segments, index 2 - right, e.g. m_line_1, + // m_line_2 + SimpleEdge m_temp_simple_edge_1; + SimpleEdge m_temp_simple_edge_2; + + int m_prev_1; + int m_prev_2; + int m_vertex_1; + int m_vertex_2; + int m_current_node; + double m_prevx_1; + double m_prevx_2; + double m_prev_y; + double m_prev_x; + double m_sweep_y; + double m_sweep_x; + double m_tolerance; + double m_tolerance_10; + boolean m_b_is_simple; + + ArrayList m_simple_edges_cache; + ArrayList m_simple_edges_recycle; + ArrayList m_simple_edges_buffer; + + // Returns a cached edge for the given value. May return NULL. + SimpleEdge tryGetCachedEdge_(int value) { + SimpleEdge se = m_simple_edges_cache.get((value & NumberUtils.intMax()) % m_simple_edges_cache.size()); + if (se != null) { + if (se.m_value == value) + return se; + else { + // int i = 0; + // cache collision + } + } + return null; + } + + // Removes cached edge from the cache for the given value. + void tryDeleteCachedEdge_(int value) { + int ind = (value & NumberUtils.intMax()) % m_simple_edges_cache.size(); + SimpleEdge se = m_simple_edges_cache.get(ind); + if (se != null && se.m_value == value) {// this value is cached + m_simple_edges_recycle.add(se); + m_simple_edges_cache.set(ind, null); + } else { + // The value has not been cached + } + } + + // Creates a cached edge. May fail and return NULL. + SimpleEdge tryCreateCachedEdge_(int value) { + int ind = (value & NumberUtils.intMax()) % m_simple_edges_cache.size(); + SimpleEdge se = m_simple_edges_cache.get(ind); + if (se == null) { + if (m_simple_edges_recycle.isEmpty()) { + // assert(m_simple_edges_buffer.size() < + // m_simple_edges_buffer.capacity());//should never happen + // assert(m_simple_edges_buffer.size() < + // m_simple_edges_cache.size());//should never happen + m_simple_edges_buffer.add(new SimpleEdge()); + se = m_simple_edges_buffer + .get(m_simple_edges_buffer.size() - 1); + } else { + se = m_simple_edges_recycle + .get(m_simple_edges_recycle.size() - 1); + m_simple_edges_recycle + .remove(m_simple_edges_recycle.size() - 1); + } + + se.m_value = value; + m_simple_edges_cache.set(ind, se); + return se; + } else { + assert (se.m_value != value);// do not call TryCreateCachedEdge + // twice. + } + + return null; + } + + void initSimpleEdge_(SweepComparator.SimpleEdge se, int vertex) { + se.m_segment = m_shape.getSegment(vertex); + se.m_b_curve = se.m_segment != null; + if (!se.m_b_curve) { + m_shape.queryLineConnector(vertex, se.m_line); + se.m_segment = se.m_line; + se.m_env.setCoordsNoNaN_(se.m_line.getStartX(), se.m_line.getEndX()); + se.m_env.vmax += m_tolerance; + se.m_line.orientBottomUp_(); + se.m_b_horizontal = se.m_line.getEndY() == se.m_line.getStartY(); + if (!se.m_b_horizontal) { + se.m_dxdy = (se.m_line.getEndX() - se.m_line.getStartX()) + / (se.m_line.getEndY() - se.m_line.getStartY()); + } + } else { + // se.m_segment = se.m_segment_sptr.get(); + } + } + + // Compares seg_1 and seg_2 x coordinates of intersection with the line + // parallel to axis x, passing through the coordinate y. + // If segments intersect not at the endpoint, the m_b_intersection_detected + // is set. + int compareTwoSegments_(Segment seg_1, Segment seg_2) { + int res = seg_1._isIntersecting(seg_2, m_tolerance, true); + if (res != 0) { + if (res == 2) + return errorCoincident(); + else + return errorCracking(); + } + + Point2D start_1 = seg_1.getStartXY(); + Point2D end1 = seg_1.getEndXY(); + Point2D start2 = seg_2.getStartXY(); + Point2D end2 = seg_2.getEndXY(); + Point2D ptSweep = new Point2D(); + ptSweep.setCoords(m_sweep_x, m_sweep_y); + if (start_1.isEqual(start2) && m_sweep_y == start_1.y) { + assert (start_1.compare(end1) < 0 && start2.compare(end2) < 0); + if (end1.compare(end2) < 0) + ptSweep.setCoords(end1); + else + ptSweep.setCoords(end2); + } else if (start_1.isEqual(end2) && m_sweep_y == start_1.y) { + assert (start_1.compare(end1) < 0 && start2.compare(end2) > 0); + if (end1.compare(start2) < 0) + ptSweep.setCoords(end1); + else + ptSweep.setCoords(start2); + } else if (start2.isEqual(end1) && m_sweep_y == start2.y) { + assert (end1.compare(start_1) < 0 && start2.compare(end2) < 0); + if (start_1.compare(end2) < 0) + ptSweep.setCoords(start_1); + else + ptSweep.setCoords(end2); + } else if (end1.isEqual(end2) && m_sweep_y == end1.y) { + assert (start_1.compare(end1) > 0 && start2.compare(end2) > 0); + if (start_1.compare(start2) < 0) + ptSweep.setCoords(start_1); + else + ptSweep.setCoords(start2); + } + + double xleft = seg_1.intersectionOfYMonotonicWithAxisX(ptSweep.y, + ptSweep.x); + double xright = seg_2.intersectionOfYMonotonicWithAxisX(ptSweep.y, + ptSweep.x); + assert (xleft != xright); + return xleft < xright ? -1 : 1; + } + + int compareNonHorizontal_(SimpleEdge line_1, SimpleEdge line_2) { + if (line_1.m_line.getStartY() == line_2.m_line.getStartY() + && line_1.m_line.getStartX() == line_2.m_line.getStartX()) {// connected + // at + // the + // start + // V + // shape + if (line_1.m_line.getEndY() == line_2.m_line.getEndY() + && line_1.m_line.getEndX() == line_2.m_line.getEndX()) {// connected + // at + // another + // end + // also + if (m_b_is_simple) + return errorCoincident(); + return 0; + } + + return compareNonHorizontalUpperEnd_(line_1, line_2); + } + + if (line_1.m_line.getEndY() == line_2.m_line.getEndY() + && line_1.m_line.getEndX() == line_2.m_line.getEndX()) { + // the case of upside-down V. + return compareNonHorizontalLowerEnd_(line_1, line_2); + } + + int lower = compareNonHorizontalLowerEnd_(line_1, line_2); + int upper = compareNonHorizontalUpperEnd_(line_1, line_2); + if (lower < 0 && upper < 0) + return -1; + if (lower > 0 && upper > 0) + return 1; + + return errorCracking(); + } + + int compareHorizontal1Case1_(Line line_1, Line line_2) { + // line_2 goes up and line_1 is horizontal connected at the start going + // to the right. + if (line_1.getEndX() > line_2.getEndX()) { + // / + // / + // +------------------ + if (line_2.getEndX() > line_2.getStartX() + && line_2.getEndY() - line_2.getStartY() < 2 * m_tolerance + && line_1._isIntersectingPoint(line_2.getEndXY(), + m_tolerance, true)) + return errorCracking(); + } else { + // / + // / + // / + // +-- + assert (line_2.getEndX() - line_2.getStartX() != 0); + // Note: line_2 cannot be vertical here + // Avoid expensive is_intersecting_ by providing a simple estimate. + double dydx = (line_2.getEndY() - line_2.getStartY()) + / (line_2.getEndX() - line_2.getStartX()); + double d = dydx * (line_1.getEndX() - line_1.getStartX()); + if (d < m_tolerance_10 + && line_2._isIntersectingPoint(line_1.getEndXY(), + m_tolerance, true)) + return errorCracking(); + } + + return 1; + } + + int compareHorizontal1Case2_(Line line_1, Line line_2) { + // -----------------+ + // / + // / + // / + // line_2 goes up and below line_1. line_1 is horizontal connected at + // the end to the line_2 end. + if (line_1.getStartX() < line_2.getStartX()) { + if (line_2.getEndX() > line_2.getStartX() + && line_2.getEndY() - line_2.getStartY() < 2 * m_tolerance + && line_1._isIntersectingPoint(line_2.getEndXY(), + m_tolerance, true)) + return errorCracking(); + } else { + // --+ + // / + // / + // / + // Avoid expensive is_intersecting_ by providing a simple estimate. + double dydx = (line_2.getEndY() - line_2.getStartY()) + / (line_2.getEndX() - line_2.getStartX()); + double d = dydx * (line_1.getStartX() - line_1.getEndX()); + if (d < m_tolerance_10 + && line_2._isIntersectingPoint(line_1.getStartXY(), + m_tolerance, true)) + return errorCracking(); + } + + return -1; + } + + int compareHorizontal1Case3_(Line line_1, Line line_2) { + Point2D v0 = new Point2D(); + v0.sub(line_2.getEndXY(), line_2.getStartXY()); + v0.rightPerpendicular(); + v0.normalize(); + Point2D v_1 = new Point2D(); + v_1.sub(line_1.getStartXY(), line_2.getStartXY()); + Point2D v_2 = new Point2D(); + v_2.sub(line_1.getEndXY(), line_2.getStartXY()); + double d_1 = v_1.dotProduct(v0); + double d_2 = v_2.dotProduct(v0); + + double ad1 = Math.abs(d_1); + double ad2 = Math.abs(d_2); + + if (ad1 < ad2) { + if (ad1 < m_tolerance_10 + && line_2._isIntersectingPoint(line_1.getStartXY(), + m_tolerance, true)) + return errorCracking(); + } else { + if (ad2 < m_tolerance_10 + && line_2._isIntersectingPoint(line_1.getEndXY(), + m_tolerance, true)) + return errorCracking(); + } + + if (d_1 < 0 && d_2 < 0) + return -1; + + if (d_1 > 0 && d_2 > 0) + return 1; + + return errorCracking(); + } + + int compareHorizontal1_(Line line_1, Line line_2) { + // Two most important cases of connecting edges + if (line_1.getStartY() == line_2.getStartY() + && line_1.getStartX() == line_2.getStartX()) { + return compareHorizontal1Case1_(line_1, line_2); + } + + if (line_1.getEndY() == line_2.getEndY() + && line_1.getEndX() == line_2.getEndX()) { + return compareHorizontal1Case2_(line_1, line_2); + } + + return compareHorizontal1Case3_(line_1, line_2); + } + + int compareHorizontal2_(Line line_1, Line line_2) { + if (line_1.getEndY() == line_2.getEndY() + && line_1.getEndX() == line_2.getEndX() + && line_1.getStartY() == line_2.getStartY() + && line_1.getStartX() == line_2.getStartX()) {// both lines + // coincide + if (m_b_is_simple) + return errorCoincident(); + return 0; + } else + return errorCracking(); + } + + int compareNonHorizontalLowerEnd_(SimpleEdge line_1, SimpleEdge line_2) { + int sign = 1; + if (line_1.m_line.getStartY() < line_2.m_line.getStartY()) { + sign = -1; + SimpleEdge tmp = line_1; + line_1 = line_2; + line_2 = tmp; + } + + Line l1 = line_1.m_line; + Line l2 = line_2.m_line; + // Now line_1 has Start point higher than line_2 startpoint. + double x_1 = l1.getStartX() - l2.getStartX(); + double x2 = line_2.m_dxdy * (l1.getStartY() - l2.getStartY()); + double tol = m_tolerance_10; + if (x_1 < x2 - tol) + return -sign; + else if (x_1 > x2 + tol) + return sign; + else // Possible problem + { + if (l2._isIntersectingPoint(l1.getStartXY(), m_tolerance, true)) + return errorCracking(); + return x_1 < x2 ? -sign : sign; + } + } + + int compareNonHorizontalUpperEnd_(SimpleEdge line_1, SimpleEdge line_2) { + int sign = 1; + if (line_2.m_line.getEndY() < line_1.m_line.getEndY()) { + sign = -1; + SimpleEdge tmp = line_1; + line_1 = line_2; + line_2 = tmp; + } + + Line l1 = line_1.m_line; + Line l2 = line_2.m_line; + // Now line_1 has End point lower than line_2 endpoint. + double x_1 = l1.getEndX() - l2.getStartX(); + double x2 = line_2.m_dxdy * (l1.getEndY() - l2.getStartY()); + double tol = m_tolerance_10; + if (x_1 < x2 - tol) + return -sign; + else if (x_1 > x2 + tol) + return sign; + else // Possible problem + { + if (l2._isIntersectingPoint(l1.getEndXY(), m_tolerance, true)) + return errorCracking(); + return x_1 < x2 ? -sign : sign; + } + } + + int errorCoincident() {// two segments coincide. + m_b_intersection_detected = true; + assert (m_b_is_simple); + NonSimpleResult.Reason reason = NonSimpleResult.Reason.CrossOver; + m_non_simple_result = new NonSimpleResult(reason, m_vertex_1, + m_vertex_2); + return -1; + } + + int errorCracking() {// cracking error + m_b_intersection_detected = true; + if (m_b_is_simple) {// only report the reason in IsSimple. Do not do + // that for regular cracking. + NonSimpleResult.Reason reason = NonSimpleResult.Reason.Cracking; + m_non_simple_result = new NonSimpleResult(reason, m_vertex_1, + m_vertex_2); + } else {// reset cached data after detected intersection + m_prev_1 = -1; + m_prev_2 = -1; + m_vertex_1 = -1; + m_vertex_2 = -1; + } + return -1; + } + + int compareSegments_(int left, int right, SimpleEdge segLeft, + SimpleEdge segRight) { + if (m_b_intersection_detected) + return -1; + + boolean sameY = m_prev_y == m_sweep_y && m_prev_x == m_sweep_x; + double xleft; + if (sameY && left == m_prev_1) + xleft = m_prevx_1; + else { + xleft = NumberUtils.NaN(); + m_prev_1 = -1; + } + double xright; + if (sameY && right == m_prev_2) + xright = m_prevx_2; + else { + xright = NumberUtils.NaN(); + m_prev_2 = -1; + } + + // Quickly compare x projections. + Envelope1D envLeft = segLeft.m_segment.queryInterval( + VertexDescription.Semantics.POSITION, 0); + Envelope1D envRight = segRight.m_segment.queryInterval( + VertexDescription.Semantics.POSITION, 0); + if (envLeft.vmax < envRight.vmin) + return -1; + if (envRight.vmax < envLeft.vmin) + return 1; + + m_prev_y = m_sweep_y; + m_prev_x = m_sweep_x; + + // Now do intersection with the sweep line (it is a line parallel to the + // axis x.) + if (NumberUtils.isNaN(xleft)) { + m_prev_1 = left; + double x = segLeft.m_segment.intersectionOfYMonotonicWithAxisX( + m_sweep_y, m_sweep_x); + xleft = x; + m_prevx_1 = x; + } + if (NumberUtils.isNaN(xright)) { + m_prev_2 = right; + double x = segRight.m_segment.intersectionOfYMonotonicWithAxisX( + m_sweep_y, m_sweep_x); + xright = x; + m_prevx_2 = x; + } + + if (Math.abs(xleft - xright) <= m_tolerance) { + // special processing as we cannot decide in a simple way. + return compareTwoSegments_(segLeft.m_segment, segRight.m_segment); + } else { + return xleft < xright ? -1 : xleft > xright ? 1 : 0; + } + } + + SweepComparator(EditShape shape, double tol, boolean bIsSimple) { + super(true); + m_shape = shape; + m_sweep_y = NumberUtils.TheNaN; + m_sweep_x = 0; + m_prev_x = 0; + m_prev_y = NumberUtils.TheNaN; + m_tolerance = tol; + m_tolerance_10 = 10 * tol; + m_prevx_2 = NumberUtils.TheNaN; + m_prevx_1 = NumberUtils.TheNaN; + m_b_intersection_detected = false; + m_prev_1 = -1; + m_prev_2 = -1; + m_vertex_1 = -1; + m_vertex_2 = -1; + m_current_node = -1; + m_b_is_simple = bIsSimple; + m_temp_simple_edge_1 = new SimpleEdge(); + m_temp_simple_edge_2 = new SimpleEdge(); + + int s = Math.min(shape.getTotalPointCount() * 3 / 2, + (int) (67 /* SIMPLEDGE_CACHESIZE */)); + int cache_size = Math.min((int) 7, s); + // m_simple_edges_buffer.reserve(cache_size);//must be reserved and + // never grow beyond reserved size + + m_simple_edges_buffer = new ArrayList(); + m_simple_edges_recycle = new ArrayList(); + m_simple_edges_cache = new ArrayList(); + + for (int i = 0; i < cache_size; i++) + m_simple_edges_cache.add(null); + } + + // Makes the comparator to forget about the last detected intersection. + // Need to be called after the intersection has been resolved. + void clearIntersectionDetectedFlag() { + m_b_intersection_detected = false; + } + + // Returns True if there has been intersection detected during compare call. + // Once intersection is detected subsequent calls to compare method do + // nothing until clear_intersection_detected_flag is called. + boolean intersectionDetected() { + return m_b_intersection_detected; + } + + // Returns the node at which the intersection has been detected + int getLastComparedNode() { + return m_current_node; + } + + // When used in IsSimple (see corresponding parameter in ctor), returns the + // reason of non-simplicity + NonSimpleResult getResult() { + return m_non_simple_result; + } + + // Sets new sweep line position. + void setSweepY(double y, double x) { + // _ASSERT(m_sweep_y != y || m_sweep_x != x); + m_sweep_y = y; + m_sweep_x = x; + m_prev_1 = -1; + m_prev_2 = -1; + m_vertex_1 = -1; + m_vertex_2 = -1; + } + + // The compare method. Compares x values of the edge given by its origin + // (elm) and the edge in the sweep structure and checks them for + // intersection at the same time. + @Override + int compare(Treap treap, int left, int node) { + // Compares two segments on a sweep line passing through m_sweep_y, + // m_sweep_x. + if (m_b_intersection_detected) + return -1; + + int right = treap.getElement(node); + m_current_node = node; + return compareSegments(left, left, right, right); + } + + int compareSegments(int leftElm, int left_vertex, int right_elm, + int right_vertex) { + SimpleEdge edgeLeft = tryGetCachedEdge_(leftElm); + if (edgeLeft == null) { + if (m_vertex_1 == left_vertex) + edgeLeft = m_temp_simple_edge_1; + else { + m_vertex_1 = left_vertex; + edgeLeft = tryCreateCachedEdge_(leftElm); + if (edgeLeft == null) { + edgeLeft = m_temp_simple_edge_1; + m_temp_simple_edge_1.m_value = leftElm; + } + initSimpleEdge_(edgeLeft, left_vertex); + } + } else + m_vertex_1 = left_vertex; + + SimpleEdge edgeRight = tryGetCachedEdge_(right_elm); + if (edgeRight == null) { + if (m_vertex_2 == right_vertex) + edgeRight = m_temp_simple_edge_2; + else { + m_vertex_2 = right_vertex; + edgeRight = tryCreateCachedEdge_(right_elm); + if (edgeRight == null) { + edgeRight = m_temp_simple_edge_2; + m_temp_simple_edge_2.m_value = right_elm; + } + initSimpleEdge_(edgeRight, right_vertex); + } + } else + m_vertex_2 = right_vertex; + + if (edgeLeft.m_b_curve || edgeRight.m_b_curve) + return compareSegments_(left_vertex, right_vertex, edgeLeft, + edgeRight); + + // Usually we work with lines, so process them in the fastest way. + // First check - assume segments are far apart. compare x intervals + if (edgeLeft.m_env.vmax < edgeRight.m_env.vmin) + return -1; + if (edgeRight.m_env.vmax < edgeLeft.m_env.vmin) + return 1; + + // compare case by case. + int kind = edgeLeft.m_b_horizontal ? 1 : 0; + kind |= edgeRight.m_b_horizontal ? 2 : 0; + if (kind == 0)// both segments are non-horizontal + return compareNonHorizontal_(edgeLeft, edgeRight); + else if (kind == 1) // line_1 horizontal, line_2 is not + return compareHorizontal1_(edgeLeft.m_line, edgeRight.m_line); + else if (kind == 2) // line_2 horizontal, line_1 is not + return compareHorizontal1_(edgeRight.m_line, edgeLeft.m_line) * -1; + else + // if (kind == 3) //both horizontal + return compareHorizontal2_(edgeLeft.m_line, edgeRight.m_line); + } + + @Override + void onDelete(int elm) { + tryDeleteCachedEdge_(elm); + } + + @Override + void onSet(int oldelm) { + tryDeleteCachedEdge_(oldelm); + } + + @Override + void onEndSearch(int elm) { + tryDeleteCachedEdge_(elm); + } + + @Override + void onAddUniqueElementFailed(int elm) { + tryDeleteCachedEdge_(elm); + } } diff --git a/src/main/java/com/esri/core/geometry/SweepMonkierComparator.java b/src/main/java/com/esri/core/geometry/SweepMonkierComparator.java index eba6bd3a..2edeef22 100644 --- a/src/main/java/com/esri/core/geometry/SweepMonkierComparator.java +++ b/src/main/java/com/esri/core/geometry/SweepMonkierComparator.java @@ -28,107 +28,107 @@ package com.esri.core.geometry; class SweepMonkierComparator extends Treap.MonikerComparator { - protected EditShape m_shape; - protected boolean m_b_intersection_detected; - protected Point2D m_point_of_interest; - protected Line m_line_1; - protected Envelope1D m_env; - protected int m_vertex_1; - protected int m_current_node; - protected double m_min_dist; - protected double m_tolerance; - - SweepMonkierComparator(EditShape shape, double tol) { - m_shape = shape; - m_tolerance = tol; - m_b_intersection_detected = false; - m_vertex_1 = -1; - m_env = new Envelope1D(); - m_point_of_interest = new Point2D(); - m_point_of_interest.setNaN(); - m_line_1 = new Line(); - m_current_node = -1; - m_min_dist = NumberUtils.doubleMax(); - } - - int getCurrentNode() { - return m_current_node; - } - - // Makes the comparator to forget about the last detected intersection. - // Need to be called after the intersection has been resolved. - void clearIntersectionDetectedFlag() { - m_b_intersection_detected = false; - m_min_dist = NumberUtils.doubleMax(); - } - - // Returns True if there has been intersection detected during compare call. - // Once intersection is detected subsequent calls to compare method do - // nothing until clear_intersection_detected_flag is called. - boolean intersectionDetected() { - return m_b_intersection_detected; - } - - void setPoint(Point2D pt) { - m_point_of_interest.setCoords(pt); - } - - // Compares the moniker, contained in the Moniker_comparator with the - // element contained in the given node. - @Override - int compare(Treap treap, int node) { - int vertex = treap.getElement(node); - return compareVertex_(treap, node, vertex); - } - - protected int compareVertex_(Treap treap, int node, int vertex) { - boolean bCurve = m_shape.getSegment(vertex) != null; - if (!bCurve) { - m_shape.queryLineConnector(vertex, m_line_1); - m_env.setCoordsNoNaN_(m_line_1.getStartX(), m_line_1.getEndX()); - } - - if (bCurve) { - throw new GeometryException("not implemented"); - } - - if (m_point_of_interest.x + m_tolerance < m_env.vmin) - return -1; - - if (m_point_of_interest.x - m_tolerance > m_env.vmax) - return 1; - - if (m_line_1.getStartY() == m_line_1.getEndY()) { - m_current_node = node; - m_b_intersection_detected = true; - return 0; - } - - m_line_1.orientBottomUp_(); - Point2D start = m_line_1.getStartXY(); - Point2D vector = new Point2D(); - vector.sub(m_line_1.getEndXY(), start); - vector.rightPerpendicular(); - Point2D v_2 = new Point2D(); - v_2.sub(m_point_of_interest, start); - double dot = vector.dotProduct(v_2); - dot /= vector.length(); - if (dot < -m_tolerance * 10) - return -1; - if (dot > m_tolerance * 10) - return 1; - - if (m_line_1.isIntersecting(m_point_of_interest, m_tolerance)) { - double absDot = Math.abs(dot); - if (absDot < m_min_dist) { - m_current_node = node; - m_min_dist = absDot; - } - m_b_intersection_detected = true; - if (absDot < 0.25 * m_tolerance) - return 0; - } - - return dot < 0 ? -1 : 1; - } + protected EditShape m_shape; + protected boolean m_b_intersection_detected; + protected Point2D m_point_of_interest; + protected Line m_line_1; + protected Envelope1D m_env; + protected int m_vertex_1; + protected int m_current_node; + protected double m_min_dist; + protected double m_tolerance; + + SweepMonkierComparator(EditShape shape, double tol) { + m_shape = shape; + m_tolerance = tol; + m_b_intersection_detected = false; + m_vertex_1 = -1; + m_env = new Envelope1D(); + m_point_of_interest = new Point2D(); + m_point_of_interest.setNaN(); + m_line_1 = new Line(); + m_current_node = -1; + m_min_dist = NumberUtils.doubleMax(); + } + + int getCurrentNode() { + return m_current_node; + } + + // Makes the comparator to forget about the last detected intersection. + // Need to be called after the intersection has been resolved. + void clearIntersectionDetectedFlag() { + m_b_intersection_detected = false; + m_min_dist = NumberUtils.doubleMax(); + } + + // Returns True if there has been intersection detected during compare call. + // Once intersection is detected subsequent calls to compare method do + // nothing until clear_intersection_detected_flag is called. + boolean intersectionDetected() { + return m_b_intersection_detected; + } + + void setPoint(Point2D pt) { + m_point_of_interest.setCoords(pt); + } + + // Compares the moniker, contained in the Moniker_comparator with the + // element contained in the given node. + @Override + int compare(Treap treap, int node) { + int vertex = treap.getElement(node); + return compareVertex_(treap, node, vertex); + } + + protected int compareVertex_(Treap treap, int node, int vertex) { + boolean bCurve = m_shape.getSegment(vertex) != null; + if (!bCurve) { + m_shape.queryLineConnector(vertex, m_line_1); + m_env.setCoordsNoNaN_(m_line_1.getStartX(), m_line_1.getEndX()); + } + + if (bCurve) { + throw new GeometryException("not implemented"); + } + + if (m_point_of_interest.x + m_tolerance < m_env.vmin) + return -1; + + if (m_point_of_interest.x - m_tolerance > m_env.vmax) + return 1; + + if (m_line_1.getStartY() == m_line_1.getEndY()) { + m_current_node = node; + m_b_intersection_detected = true; + return 0; + } + + m_line_1.orientBottomUp_(); + Point2D start = m_line_1.getStartXY(); + Point2D vector = new Point2D(); + vector.sub(m_line_1.getEndXY(), start); + vector.rightPerpendicular(); + Point2D v_2 = new Point2D(); + v_2.sub(m_point_of_interest, start); + double dot = vector.dotProduct(v_2); + dot /= vector.length(); + if (dot < -m_tolerance * 10) + return -1; + if (dot > m_tolerance * 10) + return 1; + + if (m_line_1.isIntersecting(m_point_of_interest, m_tolerance)) { + double absDot = Math.abs(dot); + if (absDot < m_min_dist) { + m_current_node = node; + m_min_dist = absDot; + } + m_b_intersection_detected = true; + if (absDot < 0.25 * m_tolerance) + return 0; + } + + return dot < 0 ? -1 : 1; + } } diff --git a/src/main/java/com/esri/core/geometry/TopoGraph.java b/src/main/java/com/esri/core/geometry/TopoGraph.java index 9816dfd8..b5b2397a 100644 --- a/src/main/java/com/esri/core/geometry/TopoGraph.java +++ b/src/main/java/com/esri/core/geometry/TopoGraph.java @@ -29,2585 +29,2585 @@ final class TopoGraph { - static interface EnumInputMode { - - final static int enumInputModeBuildGraph = 0; - final static int enumInputModeSimplifyAlternate = 4 + 0; - final static int enumInputModeSimplifyWinding = 4 + 1; - final static int enumInputModeIsSimplePolygon = 4 + 3; - } - - EditShape m_shape; - - // cluster data: index, parentage, halfEdge, globalPrev, globalNext - StridedIndexTypeCollection m_clusterData; - StridedIndexTypeCollection m_clusterVertices; - int m_firstCluster; - int m_lastCluster; - // edge data: index, origin, faceParentage, edgeParentage, twin, prev, next - StridedIndexTypeCollection m_halfEdgeData; - // chain data index, half_edge, parentage, parentChain, firstIsland, - // nextInParent, prev, next - StridedIndexTypeCollection m_chainData; - AttributeStreamOfDbl m_chainAreas; - AttributeStreamOfDbl m_chainPerimeters; - - final int c_edgeParentageMask; - final int c_edgeBitMask; - int m_universeChain; - ArrayList m_edgeIndices; - ArrayList m_clusterIndices; - ArrayList m_chainIndices; - - int m_geometryIDIndex; // index of geometryIDs in the m_shape - int m_clusterIndex; // vertex index of cluster handles in the m_shape - int m_halfEdgeIndex; // vertex index of half-edges in the m_shape - int m_tmpHalfEdgeParentageIndex; - int m_tmpHalfEdgeWindingNumberIndex; - int m_tmpHalfEdgeOddEvenNumberIndex = -1; - - int m_universe_geomID = -1; - - boolean m_buildChains = true; - - private boolean m_dirty_check_failed = false; - private double m_check_dirty_planesweep_tolerance = Double.NaN; - - void check_dirty_planesweep(double tolerance) { - m_check_dirty_planesweep_tolerance = tolerance; - } - - boolean dirty_check_failed() { - return m_dirty_check_failed; - } - - NonSimpleResult m_non_simple_result = new NonSimpleResult(); - - int m_pointCount;// point count processed in this Topo_graph. Used to - // reserve data. - - static final class PlaneSweepComparator extends Treap.Comparator { - TopoGraph m_helper; - SegmentBuffer m_buffer_left; - SegmentBuffer m_buffer_right; - Envelope1D interval_left; - Envelope1D interval_right; - double m_y_scanline; - - PlaneSweepComparator(TopoGraph helper) { - m_helper = helper; - m_y_scanline = NumberUtils.TheNaN; - m_buffer_left = new SegmentBuffer(); - m_buffer_right = new SegmentBuffer(); - interval_left = new Envelope1D(); - interval_right = new Envelope1D(); - } - - @Override - int compare(Treap treap, int left, int node) { - int right = treap.getElement(node); - // can be sped up a little, because left or right stay the same - // while an edge is inserted into the tree. - m_helper.querySegmentXY(left, m_buffer_left); - m_helper.querySegmentXY(right, m_buffer_right); - Segment segLeft = m_buffer_left.get(); - Segment segRight = m_buffer_right.get(); - - // Prerequisite: The segments have the start point lexicographically - // above the end point. - assert (segLeft.getStartXY().compare(segLeft.getEndXY()) < 0); - assert (segRight.getStartXY().compare(segRight.getEndXY()) < 0); - - // Simple test for faraway segments - interval_left.setCoords(segLeft.getStartX(), segLeft.getEndX()); - interval_right.setCoords(segRight.getStartX(), segRight.getEndX()); - if (interval_left.vmax < interval_right.vmin) - return -1; - if (interval_left.vmin > interval_right.vmax) - return 1; - - boolean bLeftHorz = segLeft.getStartY() == segLeft.getEndY(); - boolean bRightHorz = segRight.getStartY() == segRight.getEndY(); - if (bLeftHorz || bRightHorz) { - if (bLeftHorz && bRightHorz) { - assert (interval_left.equals(interval_right)); - return 0; - } - - // left segment is horizontal. The right one is not. - // Prerequisite of this algorithm is that this can only happen - // when: - // left - // |right -------------------- end == end - // | | - // | left | - // -------------------- right | - // start == start - // or: - // right segment is horizontal. The left one is not. - // Prerequisite of this algorithm is that his can only happen - // when: - // right - // |left -------------------- end == end - // | | - // | right | - // -------------------- left | - // start == start - - if (segLeft.getStartY() == segRight.getStartY() - && segLeft.getStartX() == segRight.getStartX()) - return bLeftHorz ? 1 : -1; - else if (segLeft.getEndY() == segRight.getEndY() - && segLeft.getEndX() == segRight.getEndX()) - return bLeftHorz ? -1 : 1; - } - - // Now do actual intersections - double xLeft = segLeft.intersectionOfYMonotonicWithAxisX( - m_y_scanline, interval_left.vmin); - double xRight = segRight.intersectionOfYMonotonicWithAxisX( - m_y_scanline, interval_right.vmin); - - if (xLeft == xRight) { - // apparently these edges originate from same vertex and the - // scanline is on the vertex. move scanline a little. - double yLeft = segLeft.getEndY(); - double yRight = segRight.getEndY(); - double miny = Math.min(yLeft, yRight); - double y = (miny + m_y_scanline) * 0.5; - if (y == m_y_scanline) { - // assert(0);//ST: not a bug. just curious to see this - // happens. - y = miny; // apparently, one of the segments is almost - // horizontal line. - } - xLeft = segLeft.intersectionOfYMonotonicWithAxisX(y, - interval_left.vmin); - xRight = segRight.intersectionOfYMonotonicWithAxisX(y, - interval_right.vmin); - } - - return xLeft < xRight ? -1 : (xLeft > xRight ? 1 : 0); - } - - void setY(double y) { - m_y_scanline = y; - } - // void operator=(const Plane_sweep_comparator&); // do not allow - // operator = - } - - ; - - static final class TopoGraphAngleComparer extends IntComparator { - TopoGraph m_parent; - - TopoGraphAngleComparer(TopoGraph parent_) { - m_parent = parent_; - } - - @Override - public int compare(int v1, int v2) { - return m_parent.compareEdgeAngles_(v1, v2); - } - } - - ; - - static final class ClusterSweepMonikerComparator extends - Treap.MonikerComparator { - TopoGraph m_parent; - SegmentBuffer m_segment_buffer; - Point2D m_point; - Envelope1D m_interval; - - ClusterSweepMonikerComparator(TopoGraph parent) { - m_parent = parent; - m_segment_buffer = new SegmentBuffer(); - m_point = new Point2D(); - m_interval = new Envelope1D(); - } - - void setPointXY(Point2D pt) { - m_point.setCoords(pt); - } - - @Override - int compare(Treap treap, int node) { - int half_edge = treap.getElement(node); - - // can be sped up a little, because left or right stay the same - // while an edge is inserted into the tree. - m_parent.querySegmentXY(half_edge, m_segment_buffer); - Segment seg = m_segment_buffer.get(); - - // Simple test for faraway segments - m_interval.setCoords(seg.getStartX(), seg.getEndX()); - if (m_point.x < m_interval.vmin) - return -1; - - if (m_point.x > m_interval.vmax) - return 1; - - // Now do actual intersections - double x = seg.intersectionOfYMonotonicWithAxisX(m_point.y, - m_point.x); - - assert (x != m_point.x); - - return m_point.x < x ? -1 : (m_point.x > x ? 1 : 0); - } - } - - int newCluster_() { - if (m_clusterData == null) - m_clusterData = new StridedIndexTypeCollection(8); - - int cluster = m_clusterData.newElement(); - // m_clusterData->add(-1);//first vertex - m_clusterData.setField(cluster, 1, 0);// parentage - // m_clusterData->add(-1);//first half edge - // m_clusterData->add(-1);//prev cluster - // m_clusterData->add(-1);//next cluster - return cluster; - } - - int newHalfEdgePair_() { - if (m_halfEdgeData == null) - m_halfEdgeData = new StridedIndexTypeCollection(8); - - int halfEdge = m_halfEdgeData.newElement(); - // m_halfEdgeData.add(-1);//origin cluster - m_halfEdgeData.setField(halfEdge, 2, 0);// chain parentage - m_halfEdgeData.setField(halfEdge, 3, 0);// edge parentage - // m_halfEdgeData.add(-1);//twin - // m_halfEdgeData.add(-1);//prev - // m_halfEdgeData.add(-1);//next - int twinHalfEdge = m_halfEdgeData.newElement(); - // m_halfEdgeData.add(-1);//origin cluster - m_halfEdgeData.setField(twinHalfEdge, 2, 0);// chain parentage - m_halfEdgeData.setField(twinHalfEdge, 3, 0);// edge parentage - // m_halfEdgeData.add(-1);//twin - // m_halfEdgeData.add(-1);//prev - // m_halfEdgeData.add(-1);//next - setHalfEdgeTwin_(halfEdge, twinHalfEdge); - setHalfEdgeTwin_(twinHalfEdge, halfEdge); - return halfEdge; - } - - int newChain_() { - if (m_chainData == null) - m_chainData = new StridedIndexTypeCollection(8); - - int chain = m_chainData.newElement(); - // m_chainData->write(chain, + 1, -1);//half_edge - m_chainData.setField(chain, 2, 0);// parentage (geometric) - // m_chainData->write(m_chainReserved + 3, -1);//parent chain - // m_chainData->write(m_chainReserved + 4, -1);//firstIsland - // m_chainData->write(m_chainReserved + 5, -1);//nextInParent - // m_chainData->write(m_chainReserved + 6, -1);//prev - // m_chainData->write(m_chainReserved + 7, -1);//next - // m_chainReserved += 8; - return chain; - } - - int deleteChain_(int chain) { - // Note: this method cannot be after _PlaneSweep - assert (m_universeChain != chain); - int n = getChainNext(chain); - m_chainData.deleteElement(chain); - // Note: no need to update the first chain, because one should never try - // deleting the first (the universe) chain. - return n; - } - - int getClusterIndex_(int cluster) { - return m_clusterData.elementToIndex(cluster); - } - - void setClusterVertexIterator_(int cluster, int verticeList) { - m_clusterData.setField(cluster, 7, verticeList); - } - - void setClusterHalfEdge_(int cluster, int half_edge) { - m_clusterData.setField(cluster, 2, half_edge); - } - - void setClusterParentage_(int cluster, int parentage) { - m_clusterData.setField(cluster, 1, parentage); - } - - void setPrevCluster_(int cluster, int nextCluster) { - m_clusterData.setField(cluster, 3, nextCluster); - } - - void setNextCluster_(int cluster, int nextCluster) { - m_clusterData.setField(cluster, 4, nextCluster); - } - - void setClusterVertexIndex_(int cluster, int index) { - m_clusterData.setField(cluster, 5, index); - } - - int getClusterVertexIndex_(int cluster) { - return m_clusterData.getField(cluster, 5); - } - - void setClusterChain_(int cluster, int chain) { - m_clusterData.setField(cluster, 6, chain); - } - - void addClusterToExteriorChain_(int chain, int cluster) { - assert (getClusterChain(cluster) == -1); - setClusterChain_(cluster, chain); - // There is no link from the chain to the cluster. Only vice versa. - // Consider for change? - } - - int getHalfEdgeIndex_(int he) { - return m_halfEdgeData.elementToIndex(he); - } - - void setHalfEdgeOrigin_(int half_edge, int cluster) { - m_halfEdgeData.setField(half_edge, 1, cluster); - } - - void setHalfEdgeTwin_(int half_edge, int twinHalfEdge) { - m_halfEdgeData.setField(half_edge, 4, twinHalfEdge); - } - - void setHalfEdgePrev_(int half_edge, int prevHalfEdge) { - m_halfEdgeData.setField(half_edge, 5, prevHalfEdge); - } - - void setHalfEdgeNext_(int half_edge, int nextHalfEdge) { - m_halfEdgeData.setField(half_edge, 6, nextHalfEdge); - } - - // void set_half_edge_chain_parentage_(int half_edge, int - // chainParentageMask) { m_halfEdgeData.setField(half_edge + 2, - // chainParentageMask); } - void setHalfEdgeChain_(int half_edge, int chain) { - m_halfEdgeData.setField(half_edge, 2, chain); - } - - void setHalfEdgeParentage_(int half_edge, int parentageMask) { - m_halfEdgeData.setField(half_edge, 3, parentageMask); - } - - int getHalfEdgeParentageMask_(int half_edge) { - return m_halfEdgeData.getField(half_edge, 3); - } - - void setHalfEdgeVertexIterator_(int half_edge, int vertexIterator) { - m_halfEdgeData.setField(half_edge, 7, vertexIterator); - } - - void updateVertexToHalfEdgeConnectionHelper_(int half_edge, boolean bClear) { - int viter = getHalfEdgeVertexIterator(half_edge); - if (viter != -1) { - int he = bClear ? -1 : half_edge; - for (int viter_ = getHalfEdgeVertexIterator(half_edge); viter_ != -1; viter_ = incrementVertexIterator(viter_)) { - int vertex = getVertexFromVertexIterator(viter_); - m_shape.setUserIndex(vertex, m_halfEdgeIndex, he); - } - } - } - - void updateVertexToHalfEdgeConnection_(int half_edge, boolean bClear) { - if (half_edge == -1) - return; - updateVertexToHalfEdgeConnectionHelper_(half_edge, bClear); - updateVertexToHalfEdgeConnectionHelper_(getHalfEdgeTwin(half_edge), - bClear); - } - - int getChainIndex_(int chain) { - return m_chainData.elementToIndex(chain); - } - - void setChainHalfEdge_(int chain, int half_edge) { - m_chainData.setField(chain, 1, half_edge); - } - - void setChainParentage_(int chain, int parentage) { - m_chainData.setField(chain, 2, parentage); - } - - void setChainParent_(int chain, int parentChain) { - assert (m_chainData.getField(chain, 3) != parentChain); - m_chainData.setField(chain, 3, parentChain); - int firstIsland = getChainFirstIsland(parentChain); - setChainNextInParent_(chain, firstIsland); - setChainFirstIsland_(parentChain, chain); - } - - void setChainFirstIsland_(int chain, int islandChain) { - m_chainData.setField(chain, 4, islandChain); - } - - void setChainNextInParent_(int chain, int nextInParent) { - m_chainData.setField(chain, 5, nextInParent); - } - - void setChainPrev_(int chain, int prev) { - m_chainData.setField(chain, 6, prev); - } - - void setChainNext_(int chain, int next) { - m_chainData.setField(chain, 7, next); - } - - void setChainArea_(int chain, double area) { - int chainIndex = getChainIndex_(chain); - m_chainAreas.write(chainIndex, area); - } - - void setChainPerimeter_(int chain, double perimeter) { - int chainIndex = getChainIndex_(chain); - m_chainPerimeters.write(chainIndex, perimeter); - } - - void updateChainAreaAndPerimeter_(int chain) { - double area = 0; - double perimeter = 0; - int firstHalfEdge = getChainHalfEdge(chain); - Point2D origin = new Point2D(), from = new Point2D(), to = new Point2D(); - getHalfEdgeFromXY(firstHalfEdge, origin); - from.setCoords(origin); - int half_edge = firstHalfEdge; - do { - getHalfEdgeToXY(half_edge, to); - perimeter += Point2D.distance(from, to); - int twinChain = getHalfEdgeChain(getHalfEdgeTwin(half_edge)); - if (twinChain != chain)// only count edges are not dangling segments - // of polylines - { - area += ((to.x - origin.x) - (from.x - origin.x)) - * ((to.y - origin.y) + (from.y - origin.y)) * 0.5; - } - - from.setCoords(to); - half_edge = getHalfEdgeNext(half_edge); - } while (half_edge != firstHalfEdge); - - int ind = getChainIndex_(chain); - m_chainAreas.write(ind, area); - m_chainPerimeters.write(ind, perimeter); - } - - int getChainTopMostEdge_(int chain) { - int firstHalfEdge = getChainHalfEdge(chain); - Point2D top = new Point2D(); - getHalfEdgeFromXY(firstHalfEdge, top); - int topEdge = firstHalfEdge; - Point2D v = new Point2D(); - int half_edge = firstHalfEdge; - do { - getHalfEdgeFromXY(half_edge, v); - if (v.compare(top) > 0) { - top.setCoords(v); - topEdge = half_edge; - } - half_edge = getHalfEdgeNext(half_edge); - } while (half_edge != firstHalfEdge); - return topEdge; - } - - void planeSweepParentage_(int inputMode, ProgressTracker progress_tracker) { - PlaneSweepComparator comparator = new PlaneSweepComparator(this); - Treap aet = new Treap(); - aet.setCapacity(m_pointCount / 2); - aet.setComparator(comparator); - - AttributeStreamOfInt32 new_edges = new AttributeStreamOfInt32(0); - int treeNodeIndex = createUserIndexForHalfEdges(); - - ClusterSweepMonikerComparator clusterMoniker = null; - int counter = 0; - // Clusters are sorted by the y, x coordinate in ascending order. - Point2D pt = new Point2D(); - // Each cluster is an event of the sweep-line algorithm. - for (int cluster = getFirstCluster(); cluster != -1; cluster = getNextCluster(cluster)) { - counter++; - if ((counter & 0xFF) == 0) { - if ((progress_tracker != null) - && !(progress_tracker.progress(-1, -1))) - throw new UserCancelException(); - } - - int firstHalfEdge = getClusterHalfEdge(cluster); - if (firstHalfEdge != -1) { - new_edges.resizePreserveCapacity(0); - if (!tryOptimizedInsertion_(aet, treeNodeIndex, new_edges, - cluster, firstHalfEdge))// optimized insertion is for a - // simple chain, in that case we - // simply replace an old edge - // with a new one in AET - O(1) - {// This is more complex than a simple chain of edges - getXY(cluster, pt); - comparator.setY(pt.y); - int clusterHalfEdge = firstHalfEdge; - // Delete all edges that end at the cluster. - do {// edges that end at the cluster have been assigned an - // AET node in the treeNodeIndex. - int attachedTreeNode = getHalfEdgeUserIndex( - clusterHalfEdge, treeNodeIndex); - if (attachedTreeNode != -1) { - assert (attachedTreeNode != StridedIndexTypeCollection - .impossibleIndex2()); - aet.deleteNode(attachedTreeNode, -1); - setHalfEdgeUserIndex(clusterHalfEdge, - treeNodeIndex, - StridedIndexTypeCollection - .impossibleIndex2());// set it to -2 - } - - clusterHalfEdge = getHalfEdgeNext(getHalfEdgeTwin(clusterHalfEdge)); - assert (getHalfEdgeOrigin(clusterHalfEdge) == cluster); - } while (firstHalfEdge != clusterHalfEdge); - - // insert edges that start at the cluster. - // We need to insert only the edges that have the from point - // below the to point. - // This is ensured by the logic of the algorithm. - clusterHalfEdge = firstHalfEdge; - do { - int attachedTreeNode = getHalfEdgeUserIndex( - clusterHalfEdge, treeNodeIndex); - if (attachedTreeNode == -1) { - int newTreeNode = aet.addElement(clusterHalfEdge, - -1); - new_edges.add(newTreeNode); - } - clusterHalfEdge = getHalfEdgeNext(getHalfEdgeTwin(clusterHalfEdge)); - assert (getHalfEdgeOrigin(clusterHalfEdge) == cluster); - } while (firstHalfEdge != clusterHalfEdge); - } - - // Analyze new edges. - // We go in the opposite order, because of the way how the half - // edges are sorted on a cluster. - // We want to go from the left to the right. - for (int i = new_edges.size() - 1; i >= 0; i--) { - int newTreeNode = new_edges.get(i); - int clusterHalfEdge = aet.getElement(newTreeNode); - int twinEdge = getHalfEdgeTwin(clusterHalfEdge); - assert (getHalfEdgeUserIndex(twinEdge, treeNodeIndex) == -1); - setHalfEdgeUserIndex(twinEdge, treeNodeIndex, newTreeNode); - - planeSweepParentagePropagateParentage_(aet, newTreeNode, - inputMode); - } - } else if (getClusterChain(cluster) == -1) { - // get the left half edge of a face. The point belongs to the - // face. - if (clusterMoniker == null) - clusterMoniker = new ClusterSweepMonikerComparator(this); - - getXY(cluster, pt); - clusterMoniker.setPointXY(pt); - int leftNode = aet.searchLowerBound(clusterMoniker, -1); - int chain = m_universeChain; - - if (leftNode != -1) { - int edge = aet.getElement(leftNode); - int leftChain = getHalfEdgeChain(edge); - if (leftChain == getHalfEdgeChain(getHalfEdgeTwin(edge))) { - edge = getLeftSkipPolylines_(aet, leftNode); - } - - if (edge != -1) - chain = getHalfEdgeChain(edge); - } - - addClusterToExteriorChain_(chain, cluster); - } - } - - deleteUserIndexForHalfEdges(treeNodeIndex); - } - - void planeSweepParentagePropagateParentage_(Treap aet, int treeNode, - int inputMode) { - int edge = aet.getElement(treeNode); - int edgeChain = getHalfEdgeChain(edge); - int edgeChainParent = getChainParent(edgeChain); - if (edgeChainParent != -1) - return;// this edge has been processed already. - - // get contributing left edge. - int leftEdge = getLeftSkipPolylines_(aet, treeNode); - - int twinEdge = getHalfEdgeTwin(edge); - int twinHalfEdgeChain = getHalfEdgeChain(twinEdge); - - double chainArea = getChainArea(edgeChain); - double twinChainArea = getChainArea(twinHalfEdgeChain); - - int parentChain = getChainParent(edgeChain); - int twinParentChain = getChainParent(twinHalfEdgeChain); - if (leftEdge == -1 && parentChain == -1) { - // This edge/twin pair does not have a neighbour edge to the left. - // twin parent is not yet been assigned. - if (twinHalfEdgeChain == edgeChain) {// set parentage of a polyline - // edge (any edge for which - // the edge ant its twin - // belong to the same chain) - setChainParent_(twinHalfEdgeChain, getFirstChain()); - twinParentChain = getFirstChain(); - parentChain = twinParentChain; - } else { - // We have two touching chains that do not have parent chain - // set. - // The edge is directed up, the twin edge is directed down. - // There is no edge to the left. THat means there is no other - // than the universe surrounding this edge. - // The edge must belong to a clockwise chain, and the twin edge - // must belong to a ccw chain that encloses this edge. This - // follows from the way how we connect edges around clusters. - assert (twinChainArea < 0 && chainArea > 0); - if (twinParentChain == -1) { - setChainParent_(twinHalfEdgeChain, m_universeChain); - twinParentChain = m_universeChain; - } else { - assert (getFirstChain() == twinParentChain); - } - - setChainParent_(edgeChain, twinHalfEdgeChain); - parentChain = twinHalfEdgeChain; - } - } - - if (leftEdge != -1) { - int leftEdgeChain = getHalfEdgeChain(leftEdge); - // the twin edge has not been processed yet - if (twinParentChain == -1) { - double leftArea = getChainArea(leftEdgeChain); - if (leftArea <= 0) {// if left Edge's chain area is negative, - // then it is a chain that ends at the left - // edge, so we need to get the parent of the - // left chain and it will be the parent of - // this one. - int leftChainParent = getChainParent(leftEdgeChain); - assert (leftChainParent != -1); - - setChainParent_(twinHalfEdgeChain, leftChainParent); - twinParentChain = leftChainParent; - } else // (leftArea > 0) - {// left edge is an edge of positive chain. It surrounds the - // twin chain. - setChainParent_(twinHalfEdgeChain, leftEdgeChain); - twinParentChain = leftEdgeChain; - } - - if (twinHalfEdgeChain == edgeChain) // if this is a polyline - // chain - parentChain = twinParentChain; - } - } - - if (parentChain == -1) { - trySetChainParentFromTwin_(edgeChain, twinHalfEdgeChain); - parentChain = getChainParent(edgeChain); - } - - assert (parentChain != -1); - - if (inputMode == EnumInputMode.enumInputModeBuildGraph) { - propagate_parentage_build_graph_(aet, treeNode, edge, leftEdge, edgeChain, edgeChainParent, twinHalfEdgeChain); - } else if (inputMode == EnumInputMode.enumInputModeSimplifyWinding) { - propagate_parentage_winding_(aet, treeNode, edge, leftEdge, twinEdge, edgeChain, edgeChainParent, twinHalfEdgeChain); - } else if (inputMode == EnumInputMode.enumInputModeSimplifyAlternate) { - propagate_parentage_alternate_(aet, treeNode, edge, leftEdge, twinEdge, edgeChain, edgeChainParent, twinHalfEdgeChain); - } - - } - - void propagate_parentage_build_graph_(Treap aet, int treeNode, int edge, int leftEdge, - int edgeChain, int edgeChainParent, int twinHalfEdgeChain) { - // Now do specific sweep calculations - int chainParentage = getChainParentage(edgeChain); - - if (leftEdge != -1) { - // borrow the parentage from the left edge also - int leftEdgeChain = getHalfEdgeChain(leftEdge); - - // We take parentage from the left edge (that edge has been - // already processed), and move its face parentage accross this - // edge/twin pair. - // While the parentage is moved, accross, any bits of the - // parentage that is present in the twin are removed, because - // the twin is the right edge of the current face. - // The remaining bits are added to the face parentage of this - // edge, indicating that the face this edge borders, belongs to - // all the parents that are still active to the left. - int twinChainParentage = getChainParentage(twinHalfEdgeChain); - int leftChainParentage = getChainParentage(leftEdgeChain); - - int edgeParentage = getHalfEdgeParentage(edge); - int spikeParentage = chainParentage & twinChainParentage - & leftChainParentage; // parentage that needs to stay - leftChainParentage = leftChainParentage - ^ (leftChainParentage & edgeParentage); - leftChainParentage |= spikeParentage; - - if (leftChainParentage != 0) { - // propagate left parentage to the current edge and its - // twin. - setChainParentage_(twinHalfEdgeChain, twinChainParentage - | leftChainParentage); - setChainParentage_(edgeChain, leftChainParentage - | chainParentage); - chainParentage |= leftChainParentage; - } - - // dbg_print_edge_(edge); - } - - for (int rightNode = aet.getNext(treeNode); rightNode != -1; rightNode = aet - .getNext(rightNode)) { - int rightEdge = aet.getElement(rightNode); - int rightTwin = getHalfEdgeTwin(rightEdge); - - int rightTwinChain = getHalfEdgeChain(rightTwin); - int rightTwinChainParentage = getChainParentage(rightTwinChain); - int rightEdgeParentage = getHalfEdgeParentage(rightEdge); - int rightEdgeChain = getHalfEdgeChain(rightEdge); - int rightChainParentage = getChainParentage(rightEdgeChain); - - int spikeParentage = rightTwinChainParentage - & rightChainParentage & chainParentage; // parentage - // that needs to - // stay - chainParentage = chainParentage - ^ (chainParentage & rightEdgeParentage);// only - // parentage - // that is - // abscent in - // the twin is - // propagated to - // the right - chainParentage |= spikeParentage; - - if (chainParentage == 0) - break; - - setChainParentage_(rightTwinChain, rightTwinChainParentage - | chainParentage); - setChainParentage_(rightEdgeChain, rightChainParentage - | chainParentage); - } - } - - void propagate_parentage_winding_(Treap aet, int treeNode, int edge, int leftEdge, int twinEdge, - int edgeChain, int edgeChainParent, int twinHalfEdgeChain) { - - if (edgeChain == twinHalfEdgeChain) - return; - // starting from the left most edge, calculate winding. - int edgeWinding = getHalfEdgeUserIndex(edge, - m_tmpHalfEdgeWindingNumberIndex); - edgeWinding += getHalfEdgeUserIndex(twinEdge, - m_tmpHalfEdgeWindingNumberIndex); - int winding = 0; - AttributeStreamOfInt32 chainStack = new AttributeStreamOfInt32(0); - AttributeStreamOfInt32 windingStack = new AttributeStreamOfInt32(0); - windingStack.add(0); - for (int leftNode = aet.getFirst(-1); leftNode != treeNode; leftNode = aet - .getNext(leftNode)) { - int leftEdge1 = aet.getElement(leftNode); - int leftTwin = getHalfEdgeTwin(leftEdge1); - int l_chain = getHalfEdgeChain(leftEdge1); - int lt_chain = getHalfEdgeChain(leftTwin); - - if (l_chain != lt_chain) { - int leftWinding = getHalfEdgeUserIndex(leftEdge1, - m_tmpHalfEdgeWindingNumberIndex); - leftWinding += getHalfEdgeUserIndex(leftTwin, - m_tmpHalfEdgeWindingNumberIndex); - winding += leftWinding; - - boolean popped = false; - if (chainStack.size() != 0 - && chainStack.getLast() == lt_chain) { - windingStack.removeLast(); - chainStack.removeLast(); - popped = true; - } - - if (getChainParent(lt_chain) == -1) - throw GeometryException.GeometryInternalError(); - - if (!popped || getChainParent(lt_chain) != l_chain) { - windingStack.add(winding); - chainStack.add(l_chain); - } - } - } - - winding += edgeWinding; - - if (chainStack.size() != 0 - && chainStack.getLast() == twinHalfEdgeChain) { - windingStack.removeLast(); - chainStack.removeLast(); - } - - if (winding != 0) { - if (windingStack.getLast() == 0) { - int geometry = m_shape.getFirstGeometry(); - int geometryID = getGeometryID(geometry); - setChainParentage_(edgeChain, geometryID); - } - } else { - if (windingStack.getLast() != 0) { - int geometry = m_shape.getFirstGeometry(); - int geometryID = getGeometryID(geometry); - setChainParentage_(edgeChain, geometryID); - } - } - } - - void propagate_parentage_alternate_(Treap aet, int treeNode, int edge, - int leftEdge, int twinEdge, int edgeChain, int edgeChainParent, - int twinHalfEdgeChain) { - // Now do specific sweep calculations - // This one is done when we are doing a topological operation. - int geometry = m_shape.getFirstGeometry(); - int geometryID = getGeometryID(geometry); - - if (leftEdge == -1) { - // no left edge neighbour means the twin chain is surrounded by the - // universe - assert (getChainParent(twinHalfEdgeChain) == m_universeChain); - assert (getChainParentage(twinHalfEdgeChain) == 0 || getChainParentage(twinHalfEdgeChain) == m_universe_geomID); - assert (getChainParentage(edgeChain) == 0); - setChainParentage_(twinHalfEdgeChain, m_universe_geomID); - int parity = getHalfEdgeUserIndex(edge, - m_tmpHalfEdgeOddEvenNumberIndex); - if ((parity & 1) != 0) - setChainParentage_(edgeChain, geometryID);// set the parenentage - // from the parity - else - setChainParentage_(edgeChain, m_universe_geomID);// this chain - // does not - // belong to - // geometry - } else { - int twin_parentage = getChainParentage(twinHalfEdgeChain); - if (twin_parentage == 0) { - int leftEdgeChain = getHalfEdgeChain(leftEdge); - int left_parentage = getChainParentage(leftEdgeChain); - setChainParentage_(twinHalfEdgeChain, left_parentage); - int parity = getHalfEdgeUserIndex(edge, - m_tmpHalfEdgeOddEvenNumberIndex); - if ((parity & 1) != 0) - setChainParentage_(edgeChain, - (left_parentage == geometryID) ? m_universe_geomID - : geometryID); - else - setChainParentage_(edgeChain, left_parentage); - - } else { - int parity = getHalfEdgeUserIndex(edge, - m_tmpHalfEdgeOddEvenNumberIndex); - if ((parity & 1) != 0) - setChainParentage_(edgeChain, - (twin_parentage == geometryID) ? m_universe_geomID - : geometryID); - else - setChainParentage_(edgeChain, twin_parentage); - } - - } - } - - boolean tryOptimizedInsertion_(Treap aet, int treeNodeIndex, - AttributeStreamOfInt32 new_edges, int cluster, int firstHalfEdge) { - int clusterHalfEdge = firstHalfEdge; - int attachedTreeNode = -1; - int newEdge = -1; - // Delete all edges that end at the cluster. - int count = 0; - do { - if (count == 2) - return false; - int n = getHalfEdgeUserIndex(clusterHalfEdge, treeNodeIndex); - if (n != -1) { - if (attachedTreeNode != -1) - return false;// two edges end at the cluster - attachedTreeNode = n; - } else { - if (newEdge != -1) - return false; // two edges start from the cluster - newEdge = clusterHalfEdge; - } - assert (getHalfEdgeOrigin(clusterHalfEdge) == cluster); - count++; - clusterHalfEdge = getHalfEdgeNext(getHalfEdgeTwin(clusterHalfEdge)); - } while (firstHalfEdge != clusterHalfEdge); - - if (newEdge == -1 || attachedTreeNode == -1) - return false; - - setHalfEdgeUserIndex(aet.getElement(attachedTreeNode), treeNodeIndex, - StridedIndexTypeCollection.impossibleIndex2()); - aet.setElement(attachedTreeNode, newEdge); - new_edges.add(attachedTreeNode); - return true; - } - - boolean trySetChainParentFromTwin_(int chainToSet, int twinChain) { - assert (getChainParent(chainToSet) == -1); - double area = getChainArea(chainToSet); - if (area == 0) - return false; - double twinArea = getChainArea(twinChain); - assert (twinArea != 0); - if (area > 0 && twinArea < 0) { - setChainParent_(chainToSet, twinChain); - return true; - } - if (area < 0 && twinArea > 0) { - setChainParent_(chainToSet, twinChain); - return true; - } else { - int twinParent = getChainParent(twinChain); - if (twinParent != -1) { - setChainParent_(chainToSet, twinParent); - return true; - } - } - - return false; - } - - void createHalfEdges_(int inputMode, AttributeStreamOfInt32 sorted_vertices) { - // After this loop all halfedges will be created. - // This loop also sets the known parentage on the edges. - // The half edges are connected with each other in a random order - m_halfEdgeIndex = m_shape.createUserIndex(); - - for (int i = 0, nvert = sorted_vertices.size(); i < nvert; i++) { - int vertex = sorted_vertices.get(i); - int cluster = m_shape.getUserIndex(vertex, m_clusterIndex); - - int path = m_shape.getPathFromVertex(vertex); - int geometry = m_shape.getGeometryFromPath(path); - int gt = m_shape.getGeometryType(geometry); - if (Geometry.isMultiPath(gt)) { - int next = m_shape.getNextVertex(vertex); - if (next == -1) - continue; - - int clusterTo = m_shape.getUserIndex(next, m_clusterIndex); - assert (clusterTo != -1); - if (cluster == clusterTo) { - if (m_shape.getSegment(vertex) != null) { - assert (m_shape.getSegment(vertex).calculateLength2D() == 0); - } else { - assert (m_shape.getXY(vertex).isEqual(m_shape.getXY(next))); - } - - continue; - } - - int half_edge = newHalfEdgePair_(); - int twinEdge = getHalfEdgeTwin(half_edge); - - // add vertex to the half edge. - int vertIndex = m_clusterVertices.newElement(); - m_clusterVertices.setField(vertIndex, 0, vertex); - m_clusterVertices.setField(vertIndex, 1, -1); - setHalfEdgeVertexIterator_(half_edge, vertIndex); - - setHalfEdgeOrigin_(half_edge, cluster); - int firstHalfEdge = getClusterHalfEdge(cluster); - if (firstHalfEdge == -1) { - setClusterHalfEdge_(cluster, half_edge); - setHalfEdgePrev_(half_edge, twinEdge); - setHalfEdgeNext_(twinEdge, half_edge); - } else { - // It does not matter what order we insert the new edges in. - // We fix the order later. - int firstPrev = getHalfEdgePrev(firstHalfEdge); - assert (getHalfEdgeNext(firstPrev) == firstHalfEdge); - setHalfEdgePrev_(firstHalfEdge, twinEdge); - setHalfEdgeNext_(twinEdge, firstHalfEdge); - assert (getHalfEdgePrev(firstHalfEdge) == twinEdge); - assert (getHalfEdgeNext(twinEdge) == firstHalfEdge); - setHalfEdgeNext_(firstPrev, half_edge); - setHalfEdgePrev_(half_edge, firstPrev); - assert (getHalfEdgePrev(half_edge) == firstPrev); - assert (getHalfEdgeNext(firstPrev) == half_edge); - } - - setHalfEdgeOrigin_(twinEdge, clusterTo); - int firstTo = getClusterHalfEdge(clusterTo); - if (firstTo == -1) { - setClusterHalfEdge_(clusterTo, twinEdge); - setHalfEdgeNext_(half_edge, twinEdge); - setHalfEdgePrev_(twinEdge, half_edge); - } else { - int firstToPrev = getHalfEdgePrev(firstTo); - assert (getHalfEdgeNext(firstToPrev) == firstTo); - setHalfEdgePrev_(firstTo, half_edge); - setHalfEdgeNext_(half_edge, firstTo); - assert (getHalfEdgePrev(firstTo) == half_edge); - assert (getHalfEdgeNext(half_edge) == firstTo); - setHalfEdgeNext_(firstToPrev, twinEdge); - setHalfEdgePrev_(twinEdge, firstToPrev); - assert (getHalfEdgePrev(twinEdge) == firstToPrev); - assert (getHalfEdgeNext(firstToPrev) == twinEdge); - } - - int geometryID = getGeometryID(geometry); - // No chains yet exists, so we use a temporary user index to - // store chain parentage. - // The input polygons has been already simplified so their edges - // directed such that the hole is to the left from the edge - // (each edge is directed from the "from" to "to" point). - if (inputMode == EnumInputMode.enumInputModeBuildGraph) { - setHalfEdgeUserIndex(twinEdge, m_tmpHalfEdgeParentageIndex, - 0); // Hole is always to the left. left side here is - // the twin. - setHalfEdgeUserIndex(half_edge, - m_tmpHalfEdgeParentageIndex, - gt == Geometry.GeometryType.Polygon ? geometryID - : 0); - } else if (inputMode == EnumInputMode.enumInputModeSimplifyWinding) { - Point2D pt_1 = new Point2D(); - m_shape.getXY(vertex, pt_1); - Point2D pt_2 = new Point2D(); - m_shape.getXY(next, pt_2); - int windingNumber = 0; - int windingNumberTwin = 0; - if (pt_1.compare(pt_2) < 0) { - // The edge is directed bottom-up. That means it has the - // winding number of +1. - // The half-edge direction coincides with the edge - // direction. THe twin is directed top-down. - // The half edge will have the winding number of 1 and - // its twin the winding number of 0. - // When crossing the half-edge/twin pair from left to - // right, the winding number is changed by +1 - windingNumber = 1; - } else { - // The edge is directed top-down. That means it has the - // winding number of -1. - // The half-edge direction coincides with the edge - // direction. The twin is directed bottom-up. - // The half edge will have the winding number of 0 and - // its twin the winding number of -1. - // When crossing the half-edge/twin pair from left to - // right, the winding number is changed by -1. - windingNumberTwin = -1; - } - - // When we get a half-edge/twin pair, we can determine the - // winding number of the underlying edge - // by summing up the half-edge and twin's - // winding numbers. - - setHalfEdgeUserIndex(twinEdge, m_tmpHalfEdgeParentageIndex, - 0); - setHalfEdgeUserIndex(half_edge, - m_tmpHalfEdgeParentageIndex, 0); - // We split the winding number between the half edge and its - // twin. - // This allows us to determine which half edge goes in the - // direction of the edge, and also it allows to calculate - // the - // winging number by summing up the winding number of half - // edge and its twin. - setHalfEdgeUserIndex(half_edge, - m_tmpHalfEdgeWindingNumberIndex, windingNumber); - setHalfEdgeUserIndex(twinEdge, - m_tmpHalfEdgeWindingNumberIndex, windingNumberTwin); - } else if (inputMode == EnumInputMode.enumInputModeIsSimplePolygon) { - setHalfEdgeUserIndex(twinEdge, m_tmpHalfEdgeParentageIndex, - m_universe_geomID); - setHalfEdgeUserIndex(half_edge, - m_tmpHalfEdgeParentageIndex, - gt == Geometry.GeometryType.Polygon ? geometryID - : 0); - } else if (inputMode == EnumInputMode.enumInputModeSimplifyAlternate) { - setHalfEdgeUserIndex(twinEdge, m_tmpHalfEdgeParentageIndex, - 0); - setHalfEdgeUserIndex(half_edge, - m_tmpHalfEdgeParentageIndex, 0); - setHalfEdgeUserIndex(half_edge, - m_tmpHalfEdgeOddEvenNumberIndex, 1); - setHalfEdgeUserIndex(twinEdge, - m_tmpHalfEdgeOddEvenNumberIndex, 1); - } - - int edgeBit = gt == Geometry.GeometryType.Polygon ? c_edgeBitMask - : 0; - setHalfEdgeParentage_(half_edge, geometryID | edgeBit); - setHalfEdgeParentage_(twinEdge, geometryID | edgeBit); - } - } - } - - void mergeVertexListsOfEdges_(int eDst, int eSrc) { - assert (getHalfEdgeTo(eDst) == getHalfEdgeTo(eSrc)); - assert (getHalfEdgeOrigin(eDst) == getHalfEdgeOrigin(eSrc)); - - { - int vertFirst2 = getHalfEdgeVertexIterator(eSrc); - if (vertFirst2 != -1) { - int vertFirst1 = getHalfEdgeVertexIterator(eDst); - m_clusterVertices.setField(vertFirst2, 1, vertFirst1); - setHalfEdgeVertexIterator_(eDst, vertFirst2); - setHalfEdgeVertexIterator_(eSrc, -1); - } - } - - int eDstTwin = getHalfEdgeTwin(eDst); - int eSrcTwin = getHalfEdgeTwin(eSrc); - { - int vertFirst2 = getHalfEdgeVertexIterator(eSrcTwin); - if (vertFirst2 != -1) { - int vertFirst1 = getHalfEdgeVertexIterator(eDstTwin); - m_clusterVertices.setField(vertFirst2, 1, vertFirst1); - setHalfEdgeVertexIterator_(eDstTwin, vertFirst2); - setHalfEdgeVertexIterator_(eSrcTwin, -1); - } - } - } - - void sortHalfEdgesByAngle_(int inputMode) { - AttributeStreamOfInt32 angleSorter = new AttributeStreamOfInt32(0); - angleSorter.reserve(10); - TopoGraphAngleComparer tgac = new TopoGraphAngleComparer(this); - // Now go through the clusters, sort edges in each cluster by angle, and - // reconnect the halfedges of sorted edges in the sorted order. - // Also share the parentage information between coinciding edges and - // remove duplicates. - for (int cluster = getFirstCluster(); cluster != -1; cluster = getNextCluster(cluster)) { - angleSorter.clear(false); - int first = getClusterHalfEdge(cluster); - if (first != -1) { - // 1. sort edges originating at the cluster by angle (counter - - // clockwise). - int edge = first; - do { - angleSorter.add(edge);// edges have the cluster in their - // origin and are directed away from - // it. The twin edges are directed - // towards the cluster. - edge = getHalfEdgeNext(getHalfEdgeTwin(edge)); - } while (edge != first); - - if (angleSorter.size() > 1) { - boolean changed_order = true; - if (angleSorter.size() > 2) { - angleSorter.Sort(0, angleSorter.size(), - tgac); // std::sort(angleSorter.get_ptr(), - // angleSorter.get_ptr() - // + - // angleSorter.size(), - // TopoGraphAngleComparer(this)); - angleSorter.add(angleSorter.get(0)); - } else { - //no need to sort most two edge cases. we only need to make sure that edges going up are sorted - if (compareEdgeAnglesForPair_(angleSorter.get(0), - angleSorter.get(1)) > 0) { - int tmp = angleSorter.get(0); - angleSorter.set(0, angleSorter.get(1)); - angleSorter.set(1, tmp); - } else { - changed_order = false; - } - } - // 2. get rid of duplicate edges by merging them (duplicate - // edges appear at this step because we converted all - // segments into the edges, including overlapping). - int e0 = angleSorter.get(0); - int ePrev = e0; - int ePrevTo = getHalfEdgeTo(ePrev); - int ePrevTwin = getHalfEdgeTwin(ePrev); - int prevMerged = -1; - for (int i = 1, n = angleSorter.size(); i < n; i++) { - int e = angleSorter.get(i); - int eTwin = getHalfEdgeTwin(e); - int eTo = getHalfEdgeOrigin(eTwin); - assert (getHalfEdgeOrigin(e) == getHalfEdgeOrigin(ePrev));// e - // origin - // and - // ePrev - // origin - // are - // equal - // by - // definition - // (e - // and - // ePrev - // emanate - // from - // the - // same - // cluster) - if (eTo == ePrevTo && e != ePrev)// e's To cluster and - // ePrev's To - // cluster are - // equal, means the - // edges coincide - // and need to be - // merged. - {// remove duplicate edge. Before removing, propagate - // the parentage to the remaning edge - if (inputMode == EnumInputMode.enumInputModeBuildGraph) { - int newEdgeParentage = getHalfEdgeParentageMask_(ePrev) - | getHalfEdgeParentageMask_(e); - setHalfEdgeParentage_(ePrev, newEdgeParentage); - setHalfEdgeParentage_(ePrevTwin, - newEdgeParentage); - assert (getHalfEdgeParentageMask_(ePrev) == getHalfEdgeParentageMask_(ePrevTwin)); - - setHalfEdgeUserIndex( - ePrev, - m_tmpHalfEdgeParentageIndex, - getHalfEdgeUserIndex(ePrev, - m_tmpHalfEdgeParentageIndex) - | getHalfEdgeUserIndex(e, - m_tmpHalfEdgeParentageIndex)); - setHalfEdgeUserIndex( - ePrevTwin, - m_tmpHalfEdgeParentageIndex, - getHalfEdgeUserIndex(ePrevTwin, - m_tmpHalfEdgeParentageIndex) - | getHalfEdgeUserIndex(eTwin, - m_tmpHalfEdgeParentageIndex)); - } else if (m_tmpHalfEdgeWindingNumberIndex != -1) { - // when doing simplify the - // m_tmpHalfEdgeWindingNumberIndex contains the - // winding number. - // When edges are merged their winding numbers - // are added. - int newHalfEdgeWinding = getHalfEdgeUserIndex( - ePrev, m_tmpHalfEdgeWindingNumberIndex) - + getHalfEdgeUserIndex(e, - m_tmpHalfEdgeWindingNumberIndex); - int newTwinEdgeWinding = getHalfEdgeUserIndex( - ePrevTwin, - m_tmpHalfEdgeWindingNumberIndex) - + getHalfEdgeUserIndex(eTwin, - m_tmpHalfEdgeWindingNumberIndex); - setHalfEdgeUserIndex(ePrev, - m_tmpHalfEdgeWindingNumberIndex, - newHalfEdgeWinding); - setHalfEdgeUserIndex(ePrevTwin, - m_tmpHalfEdgeWindingNumberIndex, - newTwinEdgeWinding); - // The winding number of an edge is a sum of the - // winding numbers of the half edge and its - // twin. - // To determine which half edge direction - // coincides with the edge direction, determine - // which half edge has larger abs value of - // winding number. If half edge and twin winding - // numbers cancel each other, the edge winding - // number is zero, meaning there are - // even number of edges coinciding there and - // half of them has opposite direction to - // another half. - } else if (inputMode == EnumInputMode.enumInputModeIsSimplePolygon) { - m_non_simple_result = new NonSimpleResult(NonSimpleResult.Reason.CrossOver, cluster, -1); - return; - } else if (m_tmpHalfEdgeOddEvenNumberIndex != -1) { - int newHalfEdgeWinding = getHalfEdgeUserIndex( - ePrev, m_tmpHalfEdgeOddEvenNumberIndex) - + getHalfEdgeUserIndex(e, - m_tmpHalfEdgeOddEvenNumberIndex); - int newTwinEdgeWinding = getHalfEdgeUserIndex( - ePrevTwin, - m_tmpHalfEdgeOddEvenNumberIndex) - + getHalfEdgeUserIndex(eTwin, - m_tmpHalfEdgeOddEvenNumberIndex); - setHalfEdgeUserIndex(ePrev, - m_tmpHalfEdgeOddEvenNumberIndex, - newHalfEdgeWinding); - setHalfEdgeUserIndex(ePrevTwin, - m_tmpHalfEdgeOddEvenNumberIndex, - newTwinEdgeWinding); - } - - mergeVertexListsOfEdges_(ePrev, e); - deleteEdgeImpl_(e); - assert (n < 3 || e0 == angleSorter.getLast()); - prevMerged = ePrev; - angleSorter.set(i, -1); - if (e == e0) { - angleSorter.set(0, -1); - e0 = -1; - } - - continue; - } else { - //edges do not coincide - } - - updateVertexToHalfEdgeConnection_(prevMerged, false); - prevMerged = -1; - ePrev = e; - ePrevTo = eTo; - ePrevTwin = eTwin; - } - - - updateVertexToHalfEdgeConnection_(prevMerged, false); - prevMerged = -1; - - if (!changed_order) { - //small optimization to avoid reconnecting if nothing changed - e0 = -1; - for (int i = 0, n = angleSorter.size(); i < n; i++) { - int e = angleSorter.get(i); - if (e == -1) - continue; - e0 = e; - break; - } - - if (first != e0) - setClusterHalfEdge_(cluster, e0); - - continue; //next cluster - } - - - // 3. Reconnect edges in the sorted order. The edges are - // sorted counter clockwise. - // We connect them such that every right turn is made in the - // clockwise order. - // This guarantees that the smallest faces are clockwise. - e0 = -1; - for (int i = 0, n = angleSorter.size(); i < n; i++) { - int e = angleSorter.get(i); - if (e == -1) - continue; - if (e0 == -1) { - e0 = e; - ePrev = e0; - ePrevTo = getHalfEdgeTo(ePrev); - ePrevTwin = getHalfEdgeTwin(ePrev); - continue; - } - - if (e == ePrev) { - // This condition can only happen if all edges in - // the bunch coincide. - assert (i == n - 1); - continue; - } - - int eTwin = getHalfEdgeTwin(e); - int eTo = getHalfEdgeOrigin(eTwin); - assert (getHalfEdgeOrigin(e) == getHalfEdgeOrigin(ePrev)); - assert (eTo != ePrevTo); - setHalfEdgeNext_(ePrevTwin, e); - setHalfEdgePrev_(e, ePrevTwin); - ePrev = e; - ePrevTo = eTo; - ePrevTwin = eTwin; - if (inputMode == EnumInputMode.enumInputModeIsSimplePolygon) { - int par1 = getHalfEdgeUserIndex(e, m_tmpHalfEdgeParentageIndex) | - getHalfEdgeUserIndex(getHalfEdgePrev(e), m_tmpHalfEdgeParentageIndex); - if (par1 == (m_universe_geomID | 1)) { - //violation of face parentage - m_non_simple_result = new NonSimpleResult(NonSimpleResult.Reason.CrossOver, cluster, -1); - return; - } - } - - } - - setClusterHalfEdge_(cluster, e0);// smallest angle goes - // first. - } - } - } - } - - void buildChains_(int inputMode) { - // Creates chains and puts them in the list of chains. - // Does not set the chain parentage - // Does not connect chains - - int firstChain = -1; - int visitedHalfEdgeIndex = createUserIndexForHalfEdges(); - // Visit all the clusters - for (int cluster = getFirstCluster(); cluster != -1; cluster = getNextCluster(cluster)) { - // For each cluster visit all half edges on the cluster - int first = getClusterHalfEdge(cluster); - if (first != -1) { - int edge = first; - do { - if (getHalfEdgeUserIndex(edge, visitedHalfEdgeIndex) != 1)// check - // if - // we - // have - // visited - // this - // halfedge - // already - {// if we have not visited this halfedge yet, then we have - // not created a chain for it yet. - int chain = newChain_();// new chain's parentage is set - // to 0. - setChainHalfEdge_(chain, edge);// Note, the half-edge's - // Origin is the lowest - // point of the chain. - setChainNext_(chain, firstChain);// add the new chain to - // the list of - // chains. - if (firstChain != -1) { - setChainPrev_(firstChain, chain); - } - firstChain = chain; - // go thorough all halfedges until return back to the - // same one. Thus forming a chain. - int parentage = 0; - int e = edge; - do { - // accumulate chain parentage from all the chain - // edges m_tmpHalfEdgeParentageIndex. - parentage |= getHalfEdgeUserIndex(e, - m_tmpHalfEdgeParentageIndex); - assert (getHalfEdgeUserIndex(e, - visitedHalfEdgeIndex) != 1); - setHalfEdgeChain_(e, chain); - setHalfEdgeUserIndex(e, visitedHalfEdgeIndex, 1);// mark - // the - // edge - // visited. - e = getHalfEdgeNext(e); - } while (e != edge); - - assert (inputMode != EnumInputMode.enumInputModeIsSimplePolygon || parentage != (1 | m_universe_geomID)); - - setChainParentage_(chain, parentage); - } - - edge = getHalfEdgeNext(getHalfEdgeTwin(edge));// next - // halfedge - // on the - // cluster - } while (edge != first); - } - } - - // add the Universe chain. We want it to be the one that getFirstChain - // returns. - int chain = newChain_(); - setChainHalfEdge_(chain, -1); - setChainNext_(chain, firstChain); - if (firstChain != -1) - setChainPrev_(firstChain, chain); - - m_universeChain = chain; - - m_chainAreas = new AttributeStreamOfDbl(m_chainData.size(), - NumberUtils.TheNaN); - m_chainPerimeters = new AttributeStreamOfDbl(m_chainData.size(), - NumberUtils.TheNaN); - - setChainArea_(m_universeChain, NumberUtils.positiveInf());// the - // Universe - // is - // infinite - setChainPerimeter_(m_universeChain, NumberUtils.positiveInf());// the - // Universe - // is - // infinite - - deleteUserIndexForHalfEdges(visitedHalfEdgeIndex); - } - - void simplify_(int inputMode) { - if (inputMode == EnumInputMode.enumInputModeSimplifyAlternate) { - simplifyAlternate_(); - } else if (inputMode == EnumInputMode.enumInputModeSimplifyWinding) { - simplifyWinding_(); - } - } - - void simplifyAlternate_() { - //there is nothing to do - } - - void simplifyWinding_() { - //there is nothing to do - } - - private int getFirstUnvisitedHalfEdgeOnCluster_(int cluster, int hintEdge, - int vistiedEdgesIndex) { - // finds first half edge which is unvisited (index is not set to 1. - // when hintEdge != -1, it is used to start going around the edges. - - int edge = hintEdge != -1 ? hintEdge : getClusterHalfEdge(cluster); - if (edge == -1) - return -1; - - int f = edge; - - while (true) { - int v = getHalfEdgeUserIndex(edge, vistiedEdgesIndex); - if (v != 1) { - return edge; - } - - int next = getHalfEdgeNext(getHalfEdgeTwin(edge)); - if (next == f) - return -1; - - edge = next; - } - } - - boolean removeSpikes_() { - boolean removed = false; - int visitedIndex = createUserIndexForHalfEdges(); - for (int cluster = getFirstCluster(); cluster != -1; cluster = getNextCluster(cluster)) { - int nextClusterEdge = -1; //a hint - while (true) { - int firstHalfEdge = getFirstUnvisitedHalfEdgeOnCluster_(cluster, nextClusterEdge, visitedIndex); - if (firstHalfEdge == -1) - break; - - nextClusterEdge = getHalfEdgeNext(getHalfEdgeTwin(firstHalfEdge)); - int faceHalfEdge = firstHalfEdge; - - while (true) { - int faceHalfEdgeNext = getHalfEdgeNext(faceHalfEdge); - int faceHalfEdgePrev = getHalfEdgePrev(faceHalfEdge); - int faceHalfEdgeTwin = getHalfEdgeTwin(faceHalfEdge); - - if (faceHalfEdgePrev == faceHalfEdgeTwin) { - deleteEdgeInternal_(faceHalfEdge); //deletes the edge and its twin - removed = true; - - if (nextClusterEdge == faceHalfEdge || nextClusterEdge == faceHalfEdgeTwin) - nextClusterEdge = -1; //deleted the hint edge - - if (faceHalfEdge == firstHalfEdge || faceHalfEdgePrev == firstHalfEdge) { - firstHalfEdge = faceHalfEdgeNext; - if (faceHalfEdge == firstHalfEdge || faceHalfEdgePrev == firstHalfEdge) { - //deleted all edges in a face - break; - } - - faceHalfEdge = faceHalfEdgeNext; - continue; - } - - } else { - setHalfEdgeUserIndex(faceHalfEdge, visitedIndex, 1); - } - - faceHalfEdge = faceHalfEdgeNext; - if (faceHalfEdge == firstHalfEdge) - break; - } - - } - } - - return removed; - } - - void setEditShapeImpl_(EditShape shape, int inputMode, - AttributeStreamOfInt32 editShapeGeometries, - ProgressTracker progress_tracker, boolean bBuildChains) { - assert (!m_dirty_check_failed); - assert (editShapeGeometries == null || editShapeGeometries.size() > 0); - - removeShape(); - m_buildChains = bBuildChains; - assert (m_shape == null); - m_shape = shape; - m_geometryIDIndex = m_shape.createGeometryUserIndex(); - // sort vertices lexicographically - // Firstly copy all vertices to an array. - AttributeStreamOfInt32 verticesSorter = new AttributeStreamOfInt32(0); - verticesSorter.reserve(editShapeGeometries != null ? m_shape - .getPointCount(editShapeGeometries.get(0)) : m_shape - .getTotalPointCount()); - int path_count = 0; - int geomID = 1; - {// scope - int geometry = editShapeGeometries != null ? editShapeGeometries - .get(0) : m_shape.getFirstGeometry(); - int ind = 1; - while (geometry != -1) { - m_shape.setGeometryUserIndex(geometry, m_geometryIDIndex, - geomID); - geomID = geomID << 1; - for (int path = m_shape.getFirstPath(geometry); path != -1; path = m_shape - .getNextPath(path)) { - int vertex = m_shape.getFirstVertex(path); - for (int index = 0, n = m_shape.getPathSize(path); index < n; index++) { - verticesSorter.add(vertex); - vertex = m_shape.getNextVertex(vertex); - } - } - - if (!Geometry.isPoint(m_shape.getGeometryType(geometry))) - path_count += m_shape.getPathCount(geometry); - - if (editShapeGeometries != null) { - geometry = ind < editShapeGeometries.size() ? editShapeGeometries - .get(ind) : -1; - ind++; - } else - geometry = m_shape.getNextGeometry(geometry); - } - } - - m_universe_geomID = geomID; - - m_pointCount = verticesSorter.size(); - - // sort - m_shape.sortVerticesSimpleByY_(verticesSorter, 0, m_pointCount); - - if (m_clusterVertices == null) { - m_clusterVertices = new StridedIndexTypeCollection(2); - m_clusterData = new StridedIndexTypeCollection(8); - m_halfEdgeData = new StridedIndexTypeCollection(8); - m_chainData = new StridedIndexTypeCollection(8); - } - - m_clusterVertices.setCapacity(m_pointCount); - - ProgressTracker.checkAndThrow(progress_tracker); - - m_clusterData.setCapacity(m_pointCount + 10);// 10 for some self - // intersections - m_halfEdgeData.setCapacity(2 * m_pointCount + 32); - m_chainData.setCapacity(Math.max((int) 32, path_count)); - - // create all clusters - assert (m_clusterIndex == -1);// cleanup was incorrect - m_clusterIndex = m_shape.createUserIndex(); - Point2D ptFirst = new Point2D(); - int ifirst = 0; - Point2D pt = new Point2D(); - ptFirst.setNaN(); - for (int i = 0; i <= m_pointCount; i++) { - if (i < m_pointCount) { - int vertex = verticesSorter.get(i); - m_shape.getXY(vertex, pt); - } else { - pt.setNaN();// makes it to go into the following "if" statement. - } - if (!ptFirst.isEqual(pt)) { - if (ifirst < i) { - int cluster = newCluster_(); - int vertFirst = -1; - int vert = -1; - for (int ind = ifirst; ind < i; ind++) { - vert = verticesSorter.get(ind); - m_shape.setUserIndex(vert, m_clusterIndex, cluster); - - // add vertex to the cluster's vertex list - int vertIndex = m_clusterVertices.newElement(); - m_clusterVertices.setField(vertIndex, 0, vert); - m_clusterVertices.setField(vertIndex, 1, vertFirst); - vertFirst = vertIndex; - - int path = m_shape.getPathFromVertex(vert); - int geometry = m_shape.getGeometryFromPath(path); - int geometryID = getGeometryID(geometry); - setClusterParentage_(cluster, - getClusterParentage(cluster) | geometryID); - } - setClusterVertexIterator_(cluster, vertFirst); - setClusterVertexIndex_(cluster, - m_shape.getVertexIndex(vert)); - - if (m_lastCluster != -1) - setNextCluster_(m_lastCluster, cluster); - - setPrevCluster_(cluster, m_lastCluster); - - m_lastCluster = cluster; - if (m_firstCluster == -1) - m_firstCluster = cluster; - } - ifirst = i; - ptFirst.setCoords(pt); - } - } - - ProgressTracker.checkAndThrow(progress_tracker); - - m_tmpHalfEdgeParentageIndex = createUserIndexForHalfEdges(); - if (inputMode == EnumInputMode.enumInputModeSimplifyWinding) { - m_tmpHalfEdgeWindingNumberIndex = createUserIndexForHalfEdges(); - } - - if (inputMode == EnumInputMode.enumInputModeSimplifyAlternate) { - m_tmpHalfEdgeOddEvenNumberIndex = createUserIndexForHalfEdges(); - } - - createHalfEdges_(inputMode, verticesSorter);// For each geometry produce - // clusters and half edges - - if (m_non_simple_result.m_reason != NonSimpleResult.Reason.NotDetermined) - return; - - sortHalfEdgesByAngle_(inputMode); - if (m_non_simple_result.m_reason != NonSimpleResult.Reason.NotDetermined) - return; - - if (!NumberUtils.isNaN(m_check_dirty_planesweep_tolerance)) { - if (!check_structure_after_dirty_sweep_())// checks the edges. - { - m_dirty_check_failed = true;// set m_dirty_check_failed when an - // issue is found. We'll rerun the - // planesweep using robust crack and - // cluster approach. - return; - } - } - - buildChains_(inputMode); - if (m_non_simple_result.m_reason != NonSimpleResult.Reason.NotDetermined) - return; - - deleteUserIndexForHalfEdges(m_tmpHalfEdgeParentageIndex); - m_tmpHalfEdgeParentageIndex = -1; - - if (m_buildChains) - planeSweepParentage_(inputMode, progress_tracker); - - - simplify_(inputMode); - } - - void deleteEdgeImpl_(int half_edge) { - int halfEdgeNext = getHalfEdgeNext(half_edge); - int halfEdgePrev = getHalfEdgePrev(half_edge); - int halfEdgeTwin = getHalfEdgeTwin(half_edge); - int halfEdgeTwinNext = getHalfEdgeNext(halfEdgeTwin); - int halfEdgeTwinPrev = getHalfEdgePrev(halfEdgeTwin); - - if (halfEdgeNext != halfEdgeTwin) { - setHalfEdgeNext_(halfEdgeTwinPrev, halfEdgeNext); - setHalfEdgePrev_(halfEdgeNext, halfEdgeTwinPrev); - } - - if (halfEdgePrev != halfEdgeTwin) { - setHalfEdgeNext_(halfEdgePrev, halfEdgeTwinNext); - setHalfEdgePrev_(halfEdgeTwinNext, halfEdgePrev); - } - - int cluster_1 = getHalfEdgeOrigin(half_edge); - int clusterFirstEdge1 = getClusterHalfEdge(cluster_1); - if (clusterFirstEdge1 == half_edge) { - if (halfEdgeTwinNext != half_edge) - setClusterHalfEdge_(cluster_1, halfEdgeTwinNext); - else - setClusterHalfEdge_(cluster_1, -1);// cluster has no more edges - } - - int cluster2 = getHalfEdgeOrigin(halfEdgeTwin); - int clusterFirstEdge2 = getClusterHalfEdge(cluster2); - if (clusterFirstEdge2 == halfEdgeTwin) { - if (halfEdgeNext != halfEdgeTwin) - setClusterHalfEdge_(cluster2, halfEdgeNext); - else - setClusterHalfEdge_(cluster2, -1);// cluster has no more edges - } - - m_halfEdgeData.deleteElement(half_edge); - m_halfEdgeData.deleteElement(halfEdgeTwin); - } - - int getLeftSkipPolylines_(Treap aet, int treeNode) { - int leftNode = treeNode; - - for (; ; ) { - leftNode = aet.getPrev(leftNode); - if (leftNode != -1) { - int e = aet.getElement(leftNode); - int leftChain = getHalfEdgeChain(e); - if (leftChain != getHalfEdgeChain(getHalfEdgeTwin(e))) { - return e; - } else { - // the left edge is a piece of polyline - does not - // contribute to the face parentage - } - } else { - return -1; - } - } - } - - TopoGraph() { - c_edgeParentageMask = ((int) -1) - ^ ((int) 1 << (NumberUtils.sizeOf((int) 0) * 8 - 1)); - c_edgeBitMask = (int) 1 << (NumberUtils.sizeOf((int) 0) * 8 - 1); - m_firstCluster = -1; - m_lastCluster = -1; - m_geometryIDIndex = -1; - m_clusterIndex = -1; - m_halfEdgeIndex = -1; - m_universeChain = -1; - m_tmpHalfEdgeParentageIndex = -1; - m_tmpHalfEdgeWindingNumberIndex = -1; - m_pointCount = 0; - } - - EditShape getShape() { - return m_shape; - } - - // Sets an edit shape. The geometry has to be cracked and clustered before - // calling this! - void setEditShape(EditShape shape, ProgressTracker progress_tracker) { - setEditShapeImpl_(shape, EnumInputMode.enumInputModeBuildGraph, null, - progress_tracker, true); - } - - void setEditShape(EditShape shape, ProgressTracker progress_tracker, boolean bBuildChains) { - setEditShapeImpl_(shape, EnumInputMode.enumInputModeBuildGraph, null, - progress_tracker, bBuildChains); - } - - void setAndSimplifyEditShapeAlternate(EditShape shape, int geometry, ProgressTracker progressTracker) { - AttributeStreamOfInt32 geoms = new AttributeStreamOfInt32(0); - geoms.add(geometry); - setEditShapeImpl_(shape, EnumInputMode.enumInputModeSimplifyAlternate, - geoms, progressTracker, shape.getGeometryType(geometry) == Geometry.Type.Polygon.value()); - } - - void setAndSimplifyEditShapeWinding(EditShape shape, int geometry, ProgressTracker progressTracker) { - AttributeStreamOfInt32 geoms = new AttributeStreamOfInt32(0); - geoms.add(geometry); - setEditShapeImpl_(shape, EnumInputMode.enumInputModeSimplifyWinding, - geoms, progressTracker, true); - } - - // Removes shape from the topograph and removes any user index created on - // the edit shape. - void removeShape() { - if (m_shape == null) - return; - - if (m_geometryIDIndex != -1) { - m_shape.removeGeometryUserIndex(m_geometryIDIndex); - m_geometryIDIndex = -1; - } - - if (m_clusterIndex != -1) { - m_shape.removeUserIndex(m_clusterIndex); - m_clusterIndex = -1; - } - - if (m_halfEdgeIndex != -1) { - m_shape.removeUserIndex(m_halfEdgeIndex); - m_halfEdgeIndex = -1; - } - - if (m_tmpHalfEdgeParentageIndex != -1) { - deleteUserIndexForHalfEdges(m_tmpHalfEdgeParentageIndex); - m_tmpHalfEdgeParentageIndex = -1; - } - - if (m_tmpHalfEdgeWindingNumberIndex != -1) { - deleteUserIndexForHalfEdges(m_tmpHalfEdgeWindingNumberIndex); - m_tmpHalfEdgeWindingNumberIndex = -1; - } - - if (m_tmpHalfEdgeOddEvenNumberIndex != -1) { - deleteUserIndexForHalfEdges(m_tmpHalfEdgeOddEvenNumberIndex); - m_tmpHalfEdgeOddEvenNumberIndex = -1; - } - - m_shape = null; - m_clusterData.deleteAll(true); - m_clusterVertices.deleteAll(true); - m_firstCluster = -1; - m_lastCluster = -1; - - if (m_halfEdgeData != null) - m_halfEdgeData.deleteAll(true); - if (m_edgeIndices != null) - m_edgeIndices.clear(); - if (m_clusterIndices != null) - m_clusterIndices.clear(); - if (m_chainIndices != null) - m_chainIndices.clear(); - if (m_chainData != null) - m_chainData.deleteAll(true); - m_universeChain = -1; - m_chainAreas = null; - } - - // Returns a half-edge emanating the cluster. All other half-edges can be - // visited with: - // incident_half_edge = getHalfEdgeTwin(half_edge);//get twin of the - // half_edge, it has the vertex as the end point. - // emanating_half_edge = getHalfEdgeTwin(incident_half_edge); //get next - // emanating half-edge - int getClusterHalfEdge(int cluster) { - return m_clusterData.getField(cluster, 2); - } - - // Returns the coordinates of the cluster - void getXY(int cluster, Point2D pt) { - int vindex = getClusterVertexIndex_(cluster); - m_shape.getXYWithIndex(vindex, pt); - } - - // Returns parentage mask of the cluster - int getClusterParentage(int cluster) { - return m_clusterData.getField(cluster, 1); - } - - // Returns first cluster in the Topo_graph (has lowest y, x coordinates). - int getFirstCluster() { - return m_firstCluster; - } - - // Returns previous cluster in the Topo_graph (in the sorted order of y,x - // coordinates). - int getPrevCluster(int cluster) { - return m_clusterData.getField(cluster, 3); - } - - // Returns next cluster in the Topo_graph (in the sorted order of y,x - // coordinates). - int getNextCluster(int cluster) { - return m_clusterData.getField(cluster, 4); - } - - // Returns an exterior chain of a face this cluster belongs to (belongs only - // to interior). set only for the clusters that are standalone clusters (do - // not have half-edges with them). - int getClusterChain(int cluster) { - return m_clusterData.getField(cluster, 6); - } - - // Returns iterator for cluster vertices - int getClusterVertexIterator(int cluster) { - return m_clusterData.getField(cluster, 7); - } - - // Increments iterator. Returns -1 if no more vertices in the cluster - int incrementVertexIterator(int vertexIterator) { - return m_clusterVertices.getField(vertexIterator, 1); - } - - // Dereference the iterator - int getVertexFromVertexIterator(int vertexIterator) { - return m_clusterVertices.getField(vertexIterator, 0); - } - - // Returns a user index value for the cluster. - int getClusterUserIndex(int cluster, int index) { - int i = getClusterIndex_(cluster); - AttributeStreamOfInt32 stream = m_clusterIndices.get(index); - if (stream.size() <= i) - return -1; - return stream.read(i); - } - - // Sets a user index value for the cluster. - void setClusterUserIndex(int cluster, int index, int value) { - int i = getClusterIndex_(cluster); - AttributeStreamOfInt32 stream = m_clusterIndices.get(index); - if (stream.size() <= i) - stream.resize(m_clusterData.size(), -1); - - stream.write(i, value); - } - - // Creates a new user index for the cluster. The index values are set to -1. - int createUserIndexForClusters() { - if (m_clusterIndices == null) { - m_clusterIndices = new ArrayList(3); - } - - AttributeStreamOfInt32 new_stream = new AttributeStreamOfInt32( - m_clusterData.capacity(), -1); - for (int i = 0, n = m_clusterIndices.size(); i < n; i++) { - if (m_clusterIndices.get(i) == null) { - m_clusterIndices.set(i, new_stream); - return i; - } - } - m_clusterIndices.add(new_stream); - return m_clusterIndices.size() - 1; - } - - // Deletes user index - void deleteUserIndexForClusters(int userIndex) { - assert (m_clusterIndices.get(userIndex) != null); - m_clusterIndices.set(userIndex, null); - } - - // Returns origin of this half edge. To get the other end: - // incident_half_edge = getHalfEdgeTwin(half_edge); - // edge_end_point = getHalfEdgeOrigin(incident_half_edge); - int getHalfEdgeOrigin(int half_edge) { - return m_halfEdgeData.getField(half_edge, 1); - } - - // Returns the to point of the half edge - int getHalfEdgeTo(int half_edge) { - return getHalfEdgeOrigin(getHalfEdgeTwin(half_edge)); - } - - // Twin of this halfedge, it has opposite direction and same endpoints - int getHalfEdgeTwin(int half_edge) { - return m_halfEdgeData.getField(half_edge, 4); - } - - // Returns previous halfedge. It ends, where this halfedge starts. - int getHalfEdgePrev(int half_edge) { - return m_halfEdgeData.getField(half_edge, 5); - } - - // Returns next halfedge. It starts, where this halfedge ends. - int getHalfEdgeNext(int half_edge) { - return m_halfEdgeData.getField(half_edge, 6); - } - - // Returns half edge chain. Chain is on the right from the halfedge - int getHalfEdgeChain(int half_edge) { - return m_halfEdgeData.getField(half_edge, 2); - } - - // Returns half edge chain parentage. The call is implemented as as - // getChainParentage(getHalfEdgeChain()); - int getHalfEdgeFaceParentage(int half_edge) { - return getChainParentage(m_halfEdgeData.getField(half_edge, 2)); - } - - // Returns iterator for cluster vertices - int getHalfEdgeVertexIterator(int half_edge) { - return m_halfEdgeData.getField(half_edge, 7); - } - - // Returns the coordinates of the origin of the half_edge - void getHalfEdgeFromXY(int half_edge, Point2D pt) { - getXY(getHalfEdgeOrigin(half_edge), pt); - } - - // Returns the coordinates of the end of the half_edge - void getHalfEdgeToXY(int half_edge, Point2D pt) { - getXY(getHalfEdgeTo(half_edge), pt); - } - - // Returns parentage mask of this halfedge. Parentage mask of halfedge and - // its twin are the same - int getHalfEdgeParentage(int half_edge) { - return m_halfEdgeData.getField(half_edge, 3) & c_edgeParentageMask; - } - - // Returns a user index value for the half edge - int getHalfEdgeUserIndex(int half_edge, int index) { - int i = getHalfEdgeIndex_(half_edge); - AttributeStreamOfInt32 stream = m_edgeIndices.get(index); - if (stream.size() <= i) - return -1; - - return stream.read(i); - } - - // Sets a user index value for a half edge - void setHalfEdgeUserIndex(int half_edge, int index, int value) { - int i = getHalfEdgeIndex_(half_edge); - AttributeStreamOfInt32 stream = m_edgeIndices.get(index); - if (stream.size() <= i) - stream.resize(m_halfEdgeData.size(), -1); - - stream.write(i, value); - } - - // create a new user index for half edges. The index values are set to -1. - int createUserIndexForHalfEdges() { - if (m_edgeIndices == null) - m_edgeIndices = new ArrayList(3); - - AttributeStreamOfInt32 new_stream = new AttributeStreamOfInt32( - m_halfEdgeData.capacity(), -1); - for (int i = 0, n = m_edgeIndices.size(); i < n; i++) { - if (m_edgeIndices.get(i) == null) { - m_edgeIndices.set(i, new_stream); - return i; - } - } - m_edgeIndices.add(new_stream); - return m_edgeIndices.size() - 1; - } - - // Deletes the given user index for half edges - void deleteUserIndexForHalfEdges(int userIndex) { - assert (m_edgeIndices.get(userIndex) != null); - m_edgeIndices.set(userIndex, null); - } - - // Deletes the half_edge and it's twin. It works presently when removing a - // spike only. - // Returns next valid half-edge, or -1 if no more half edges. - // Use with care. - int deleteEdgeInternal_(int half_edge) { - int chain = getHalfEdgeChain(half_edge); - int halfEdgeTwin = getHalfEdgeTwin(half_edge); - int chainTwin = getHalfEdgeChain(halfEdgeTwin); - // This function only works for spikes. These two asserts check for that - assert (chainTwin == chain); - assert (half_edge == getHalfEdgeNext(halfEdgeTwin) || halfEdgeTwin == getHalfEdgeNext(half_edge)); - - int n = getHalfEdgeNext(half_edge); - if (n == halfEdgeTwin) { - n = getHalfEdgeNext(n); - if (n == half_edge) - n = -1; - } - - if (getChainHalfEdge(chain) == half_edge) { - setChainHalfEdge_(chain, n); - } - - int chainIndex = getChainIndex_(chain); - double v = m_chainAreas.read(chainIndex); - if (!NumberUtils.isNaN(v)) { - setChainArea_(chain, NumberUtils.TheNaN); - setChainPerimeter_(chain, NumberUtils.TheNaN); - } - - updateVertexToHalfEdgeConnection_(half_edge, true); - - deleteEdgeImpl_(half_edge);// does not change chain information - return n; - } - - // Deletes the halfEdges and their twin. The chains are broken after this - // call. - // For every chain the halfedges belong to, it will set the first edge to - // -1. - // However, the halfedge will still reference the chain so one can get the - // parentage information still. - void deleteEdgesBreakFaces_(AttributeStreamOfInt32 edgesToDelete) { - for (int i = 0, n = edgesToDelete.size(); i < n; i++) { - int half_edge = edgesToDelete.get(i); - int chain = getHalfEdgeChain(half_edge); - int halfEdgeTwin = getHalfEdgeTwin(half_edge); - int chainTwin = getHalfEdgeChain(halfEdgeTwin); - setChainHalfEdge_(chain, -1); - setChainHalfEdge_(chainTwin, -1); - updateVertexToHalfEdgeConnection_(half_edge, true); - deleteEdgeImpl_(half_edge); - } - } - - boolean doesHalfEdgeBelongToAPolygonInterior(int half_edge, int polygonId) { - // Half edge belongs to polygon interior if both it and its twin belong - // to boundary of faces that have the polygon parentage (the poygon both - // to the left and to the right of the edge). - int p_1 = getHalfEdgeFaceParentage(half_edge); - int p_2 = getHalfEdgeFaceParentage(getHalfEdgeTwin(half_edge)); - return (p_1 & polygonId) != 0 && (p_2 & polygonId) != 0; - } - - boolean doesHalfEdgeBelongToAPolygonExterior(int half_edge, int polygonId) { - // Half edge belongs to polygon interior if both it and its twin belong - // to boundary of faces that have the polygon parentage (the poygon both - // to the left and to the right of the edge). - int p_1 = getHalfEdgeFaceParentage(half_edge); - int p_2 = getHalfEdgeFaceParentage(getHalfEdgeTwin(half_edge)); - return (p_1 & polygonId) == 0 && (p_2 & polygonId) == 0; - } - - boolean doesHalfEdgeBelongToAPolygonBoundary(int half_edge, int polygonId) { - // Half edge overlaps polygon boundary - int p_1 = getHalfEdgeParentage(half_edge); - return (p_1 & polygonId) != 0; - } - - boolean doesHalfEdgeBelongToAPolylineInterior(int half_edge, int polylineId) { - // Half-edge belongs to a polyline interioir if it has the polyline - // parentage (1D intersection (aka overlap)). - int p_1 = getHalfEdgeParentage(half_edge); - if ((p_1 & polylineId) != 0) { - return true; - } - - return false; - } - - boolean doesHalfEdgeBelongToAPolylineExterior(int half_edge, int polylineId) { - // Half-edge belongs to a polyline Exterioir if it does not have the - // polyline parentage and both its clusters also do not have polyline's - // parentage (to exclude touch at point). - int p_1 = getHalfEdgeParentage(half_edge); - if ((p_1 & polylineId) == 0) { - int c = getHalfEdgeOrigin(half_edge); - int pc = getClusterParentage(c); - if ((pc & polylineId) == 0) { - c = getHalfEdgeTo(half_edge); - pc = getClusterParentage(c); - if ((pc & polylineId) == 0) { - return true; - } - } - } - - return false; - } - - boolean doesClusterBelongToAPolygonInterior(int cluster, int polygonId) { - // cluster belongs to a polygon interior when - // 1) It is a standalone cluster that has face parentage of this polygon - // GetClusterFaceParentage() - // 2) or It is a cluster with half edges attached and - // a) It is not on the polygon boundrary (get_cluster_parentage) - // b) Any half edge associated with it has face parentage of the polygon - // (get_half_edge_face_parentage(getClusterHalfEdge())) - - int chain = getClusterChain(cluster); - if (chain != -1) { - if ((getChainParentage(chain) & polygonId) != 0) { - return true; - } - } else { - int p_1 = getClusterParentage(cluster); - if ((p_1 & polygonId) == 0)// not on the polygon boundary - { - int half_edge = getClusterHalfEdge(cluster); - assert (half_edge != -1); - - int p_2 = getHalfEdgeFaceParentage(half_edge); - if ((p_2 & polygonId) != 0) { - return true; - } - } - } - - return false; - } - - boolean doesClusterBelongToAPolygonExterior(int cluster, int polygonId) { - int p_1 = getClusterParentage(cluster); - if ((p_1 & polygonId) == 0) { - return doesClusterBelongToAPolygonInterior(cluster, polygonId); - } - - return false; - } - - boolean doesClusterBelongToAPolygonBoundary(int cluster, int polygonId) { - int p_1 = getClusterParentage(cluster); - if ((p_1 & polygonId) != 0) { - return true; - } - - return false; - } - - // bool DoesClusterBelongToAPolylineInterioir(int cluster, int polylineId); - // bool does_cluster_belong_to_a_polyline_exterior(int cluster, int - // polylineId); - // bool does_cluster_belong_to_a_polyline_boundary(int cluster, int - // polylineId); - - // Returns the first chain, which is always the Universe chain. - int getFirstChain() { - return m_universeChain; - } - - // Returns the chain half edge. - int getChainHalfEdge(int chain) { - return m_chainData.getField(chain, 1); - } - - // Returns the chain's face parentage. That is the parentage of a face this - // chain borders with. - int getChainParentage(int chain) { - return m_chainData.getField(chain, 2); - } - - // Returns the parent of the chain (the chain, this chain is inside of). - int getChainParent(int chain) { - return m_chainData.getField(chain, 3); - } - - // Returns the first island chain in that chain. Island chains are always - // counterclockwise. - // Each island chain will have its complement chain, which is a chain of a - // twin of any halfedge of that chain. - int getChainFirstIsland(int chain) { - return m_chainData.getField(chain, 4); - } - - // Returns the first island chain in that chain. Island chains are always - // counterclockwise. - int getChainNextInParent(int chain) { - return m_chainData.getField(chain, 5); - } - - // Returns the next chain in arbitrary order. - int getChainNext(int chain) { - return m_chainData.getField(chain, 7); - } - - // Returns the area of the chain. The area does not include any islands. - // +Inf is returned for the universe chain. - double getChainArea(int chain) { - int chainIndex = getChainIndex_(chain); - double v = m_chainAreas.read(chainIndex); - if (NumberUtils.isNaN(v)) { - updateChainAreaAndPerimeter_(chain); - v = m_chainAreas.read(chainIndex); - } - - return v; - } - - // Returns the perimeter of the chain (> 0). +Inf is returned for the - // universe chain. - double getChainPerimeter(int chain) { - int chainIndex = getChainIndex_(chain); - double v = m_chainPerimeters.read(chainIndex); - if (NumberUtils.isNaN(v)) { - updateChainAreaAndPerimeter_(chain); - v = m_chainPerimeters.read(chainIndex); - } - - return v; - } - - // Returns a user index value for the chain. - int getChainUserIndex(int chain, int index) { - int i = getChainIndex_(chain); - AttributeStreamOfInt32 stream = m_chainIndices.get(index); - if (stream.size() <= i) - return -1; - return stream.read(i); - } - - // Sets a user index value for the chain. - void setChainUserIndex(int chain, int index, int value) { - int i = getChainIndex_(chain); - AttributeStreamOfInt32 stream = m_chainIndices.get(index); - if (stream.size() <= i) - stream.resize(m_chainData.size(), -1); - - stream.write(i, value); - } - - // Creates a new user index for the chains. The index values are set to -1. - int createUserIndexForChains() { - if (m_chainIndices == null) { - m_chainIndices = new ArrayList(3); - } - - AttributeStreamOfInt32 new_stream = new AttributeStreamOfInt32( - m_chainData.capacity(), -1); - for (int i = 0, n = m_chainIndices.size(); i < n; i++) { - if (m_chainIndices.get(i) == null) { - m_chainIndices.set(i, new_stream); - return i; - } - } - m_chainIndices.add(new_stream); - return m_chainIndices.size() - 1; - } - - // Deletes user index - void deleteUserIndexForChains(int userIndex) { - assert (m_chainIndices.get(userIndex) != null); - m_chainIndices.set(userIndex, null); - } - - // Returns geometry ID mask from the geometry handle. - // Topo_graph creates a user index for geometries in the shape, which exists - // until the topo graph is destroyed. - int getGeometryID(int geometry) { - return m_shape.getGeometryUserIndex(geometry, m_geometryIDIndex); - } - - // Returns cluster from vertex handle. - // Topo_graph creates a user index for vertices in the shape to hold cluster - // handles. The index exists until the topo graph is destroyed. - int getClusterFromVertex(int vertex) { - return m_shape.getUserIndex(vertex, m_clusterIndex); - } - - int getHalfEdgeFromVertex(int vertex) { - return m_shape.getUserIndex(vertex, m_halfEdgeIndex); - } - - // Finds an edge connecting the two clusters. Returns -1 if not found. - // Could be a slow operation when valency of each cluster is high. - int getHalfEdgeConnector(int clusterFrom, int clusterTo) { - int first_edge = getClusterHalfEdge(clusterFrom); - if (first_edge == -1) - return -1; - int edge = first_edge; - int firstEdgeTo = -1; - int eTo = -1; - // Doing two loops in parallel - one on the half-edges attached to the - // clusterFrom, another - attached to clusterTo. - do { - if (getHalfEdgeTo(edge) == clusterTo) - return edge; - - if (firstEdgeTo == -1) { - firstEdgeTo = getClusterHalfEdge(clusterTo); - if (firstEdgeTo == -1) - return -1; - eTo = firstEdgeTo; - } - - if (getHalfEdgeTo(eTo) == clusterFrom) { - edge = getHalfEdgeTwin(eTo); - assert (getHalfEdgeTo(edge) == clusterTo && getHalfEdgeOrigin(edge) == clusterFrom); - return edge; - } - - edge = getHalfEdgeNext(getHalfEdgeTwin(edge)); - eTo = getHalfEdgeNext(getHalfEdgeTwin(eTo)); - } while (edge != first_edge && eTo != firstEdgeTo); - - return -1; - } - - // Queries segment for the edge (only xy coordinates, no attributes) - void querySegmentXY(int half_edge, SegmentBuffer outBuffer) { - outBuffer.createLine(); - Segment seg = outBuffer.get(); - Point2D pt = new Point2D(); - getHalfEdgeFromXY(half_edge, pt); - seg.setStartXY(pt); - getHalfEdgeToXY(half_edge, pt); - seg.setEndXY(pt); - } - - int compareEdgeAngles_(int edge1, int edge2) { - if (edge1 == edge2) - return 0; - - Point2D pt_1 = new Point2D(); - getHalfEdgeToXY(edge1, pt_1); - - Point2D pt_2 = new Point2D(); - getHalfEdgeToXY(edge2, pt_2); - - if (pt_1.isEqual(pt_2)) - return 0;// overlap case - - Point2D pt10 = new Point2D(); - getHalfEdgeFromXY(edge1, pt10); - - Point2D v_1 = new Point2D(); - v_1.sub(pt_1, pt10); - Point2D v_2 = new Point2D(); - v_2.sub(pt_2, pt10); - int result = Point2D._compareVectors(v_1, v_2); - return result; - } - - int compareEdgeAnglesForPair_(int edge1, int edge2) { - if (edge1 == edge2) - return 0; - - Point2D pt_1 = new Point2D(); - getHalfEdgeToXY(edge1, pt_1); - - Point2D pt_2 = new Point2D(); - getHalfEdgeToXY(edge2, pt_2); - - if (pt_1.isEqual(pt_2)) - return 0;// overlap case - - Point2D pt10 = new Point2D(); - getHalfEdgeFromXY(edge1, pt10); - - Point2D v_1 = new Point2D(); - v_1.sub(pt_1, pt10); - Point2D v_2 = new Point2D(); - v_2.sub(pt_2, pt10); - - if (v_2.y >= 0 && v_1.y > 0) { - int result = Point2D._compareVectors(v_1, v_2); - return result; - } else { - return 0; - } - } - - boolean check_structure_after_dirty_sweep_() { - // for each cluster go through the cluster half edges and check that - // min(edge1_length, edge2_length) * angle_between is less than - // m_check_dirty_planesweep_tolerance. - // Doing this helps us weed out cases missed by the dirty plane sweep. - // We do not need absolute accuracy here. - assert (!m_dirty_check_failed); - assert (!NumberUtils.isNaN(m_check_dirty_planesweep_tolerance)); - double sqr_tol = MathUtils.sqr(m_check_dirty_planesweep_tolerance); - Point2D pt10 = new Point2D(); - Point2D pt_2 = new Point2D(); - Point2D pt_1 = new Point2D(); - Point2D v_1 = new Point2D(); - Point2D v_2 = new Point2D(); - for (int cluster = getFirstCluster(); cluster != -1; cluster = getNextCluster(cluster)) { - int first = getClusterHalfEdge(cluster); - if (first != -1) { - int edge = first; - getHalfEdgeFromXY(edge, pt10); - getHalfEdgeToXY(edge, pt_2); - v_2.sub(pt_2, pt10); - double sqr_len2 = v_2.sqrLength(); - - do { - int prev = edge; - edge = getHalfEdgeNext(getHalfEdgeTwin(edge)); - - if (edge != prev) { - getHalfEdgeToXY(edge, pt_1); - assert (!pt_1.isEqual(pt_2)); - v_1.sub(pt_1, pt10); - double sqr_len1 = v_1.sqrLength(); - - double cross = v_1.crossProduct(v_2); // cross_prod = - // len1 * len2 * - // sinA => sinA - // = cross_prod - // / (len1 * - // len2); - double sqr_sinA = (cross * cross) - / (sqr_len1 * sqr_len2); // sqr_sinA is - // approximately A^2 - // especially for - // smaller angles - double sqr_dist = Math.min(sqr_len1, sqr_len2) - * sqr_sinA; - if (sqr_dist <= sqr_tol) { - // these edges incident on the cluster form a narrow - // wedge and thei require cracking event that was - // missed. - return false; - } - - v_2.setCoords(v_1); - sqr_len2 = sqr_len1; - pt_2.setCoords(pt_1); - } - } while (edge != first); - } - } - - return true; - } + static interface EnumInputMode { + + final static int enumInputModeBuildGraph = 0; + final static int enumInputModeSimplifyAlternate = 4 + 0; + final static int enumInputModeSimplifyWinding = 4 + 1; + final static int enumInputModeIsSimplePolygon = 4 + 3; + } + + EditShape m_shape; + + // cluster data: index, parentage, halfEdge, globalPrev, globalNext + StridedIndexTypeCollection m_clusterData; + StridedIndexTypeCollection m_clusterVertices; + int m_firstCluster; + int m_lastCluster; + // edge data: index, origin, faceParentage, edgeParentage, twin, prev, next + StridedIndexTypeCollection m_halfEdgeData; + // chain data index, half_edge, parentage, parentChain, firstIsland, + // nextInParent, prev, next + StridedIndexTypeCollection m_chainData; + AttributeStreamOfDbl m_chainAreas; + AttributeStreamOfDbl m_chainPerimeters; + + final int c_edgeParentageMask; + final int c_edgeBitMask; + int m_universeChain; + ArrayList m_edgeIndices; + ArrayList m_clusterIndices; + ArrayList m_chainIndices; + + int m_geometryIDIndex; // index of geometryIDs in the m_shape + int m_clusterIndex; // vertex index of cluster handles in the m_shape + int m_halfEdgeIndex; // vertex index of half-edges in the m_shape + int m_tmpHalfEdgeParentageIndex; + int m_tmpHalfEdgeWindingNumberIndex; + int m_tmpHalfEdgeOddEvenNumberIndex = -1; + + int m_universe_geomID = -1; + + boolean m_buildChains = true; + + private boolean m_dirty_check_failed = false; + private double m_check_dirty_planesweep_tolerance = Double.NaN; + + void check_dirty_planesweep(double tolerance) { + m_check_dirty_planesweep_tolerance = tolerance; + } + + boolean dirty_check_failed() { + return m_dirty_check_failed; + } + + NonSimpleResult m_non_simple_result = new NonSimpleResult(); + + int m_pointCount;// point count processed in this Topo_graph. Used to + // reserve data. + + static final class PlaneSweepComparator extends Treap.Comparator { + TopoGraph m_helper; + SegmentBuffer m_buffer_left; + SegmentBuffer m_buffer_right; + Envelope1D interval_left; + Envelope1D interval_right; + double m_y_scanline; + + PlaneSweepComparator(TopoGraph helper) { + m_helper = helper; + m_y_scanline = NumberUtils.TheNaN; + m_buffer_left = new SegmentBuffer(); + m_buffer_right = new SegmentBuffer(); + interval_left = new Envelope1D(); + interval_right = new Envelope1D(); + } + + @Override + int compare(Treap treap, int left, int node) { + int right = treap.getElement(node); + // can be sped up a little, because left or right stay the same + // while an edge is inserted into the tree. + m_helper.querySegmentXY(left, m_buffer_left); + m_helper.querySegmentXY(right, m_buffer_right); + Segment segLeft = m_buffer_left.get(); + Segment segRight = m_buffer_right.get(); + + // Prerequisite: The segments have the start point lexicographically + // above the end point. + assert (segLeft.getStartXY().compare(segLeft.getEndXY()) < 0); + assert (segRight.getStartXY().compare(segRight.getEndXY()) < 0); + + // Simple test for faraway segments + interval_left.setCoords(segLeft.getStartX(), segLeft.getEndX()); + interval_right.setCoords(segRight.getStartX(), segRight.getEndX()); + if (interval_left.vmax < interval_right.vmin) + return -1; + if (interval_left.vmin > interval_right.vmax) + return 1; + + boolean bLeftHorz = segLeft.getStartY() == segLeft.getEndY(); + boolean bRightHorz = segRight.getStartY() == segRight.getEndY(); + if (bLeftHorz || bRightHorz) { + if (bLeftHorz && bRightHorz) { + assert (interval_left.equals(interval_right)); + return 0; + } + + // left segment is horizontal. The right one is not. + // Prerequisite of this algorithm is that this can only happen + // when: + // left + // |right -------------------- end == end + // | | + // | left | + // -------------------- right | + // start == start + // or: + // right segment is horizontal. The left one is not. + // Prerequisite of this algorithm is that his can only happen + // when: + // right + // |left -------------------- end == end + // | | + // | right | + // -------------------- left | + // start == start + + if (segLeft.getStartY() == segRight.getStartY() + && segLeft.getStartX() == segRight.getStartX()) + return bLeftHorz ? 1 : -1; + else if (segLeft.getEndY() == segRight.getEndY() + && segLeft.getEndX() == segRight.getEndX()) + return bLeftHorz ? -1 : 1; + } + + // Now do actual intersections + double xLeft = segLeft.intersectionOfYMonotonicWithAxisX( + m_y_scanline, interval_left.vmin); + double xRight = segRight.intersectionOfYMonotonicWithAxisX( + m_y_scanline, interval_right.vmin); + + if (xLeft == xRight) { + // apparently these edges originate from same vertex and the + // scanline is on the vertex. move scanline a little. + double yLeft = segLeft.getEndY(); + double yRight = segRight.getEndY(); + double miny = Math.min(yLeft, yRight); + double y = (miny + m_y_scanline) * 0.5; + if (y == m_y_scanline) { + // assert(0);//ST: not a bug. just curious to see this + // happens. + y = miny; // apparently, one of the segments is almost + // horizontal line. + } + xLeft = segLeft.intersectionOfYMonotonicWithAxisX(y, + interval_left.vmin); + xRight = segRight.intersectionOfYMonotonicWithAxisX(y, + interval_right.vmin); + } + + return xLeft < xRight ? -1 : (xLeft > xRight ? 1 : 0); + } + + void setY(double y) { + m_y_scanline = y; + } + // void operator=(const Plane_sweep_comparator&); // do not allow + // operator = + } + + ; + + static final class TopoGraphAngleComparer extends IntComparator { + TopoGraph m_parent; + + TopoGraphAngleComparer(TopoGraph parent_) { + m_parent = parent_; + } + + @Override + public int compare(int v1, int v2) { + return m_parent.compareEdgeAngles_(v1, v2); + } + } + + ; + + static final class ClusterSweepMonikerComparator extends + Treap.MonikerComparator { + TopoGraph m_parent; + SegmentBuffer m_segment_buffer; + Point2D m_point; + Envelope1D m_interval; + + ClusterSweepMonikerComparator(TopoGraph parent) { + m_parent = parent; + m_segment_buffer = new SegmentBuffer(); + m_point = new Point2D(); + m_interval = new Envelope1D(); + } + + void setPointXY(Point2D pt) { + m_point.setCoords(pt); + } + + @Override + int compare(Treap treap, int node) { + int half_edge = treap.getElement(node); + + // can be sped up a little, because left or right stay the same + // while an edge is inserted into the tree. + m_parent.querySegmentXY(half_edge, m_segment_buffer); + Segment seg = m_segment_buffer.get(); + + // Simple test for faraway segments + m_interval.setCoords(seg.getStartX(), seg.getEndX()); + if (m_point.x < m_interval.vmin) + return -1; + + if (m_point.x > m_interval.vmax) + return 1; + + // Now do actual intersections + double x = seg.intersectionOfYMonotonicWithAxisX(m_point.y, + m_point.x); + + assert (x != m_point.x); + + return m_point.x < x ? -1 : (m_point.x > x ? 1 : 0); + } + } + + int newCluster_() { + if (m_clusterData == null) + m_clusterData = new StridedIndexTypeCollection(8); + + int cluster = m_clusterData.newElement(); + // m_clusterData->add(-1);//first vertex + m_clusterData.setField(cluster, 1, 0);// parentage + // m_clusterData->add(-1);//first half edge + // m_clusterData->add(-1);//prev cluster + // m_clusterData->add(-1);//next cluster + return cluster; + } + + int newHalfEdgePair_() { + if (m_halfEdgeData == null) + m_halfEdgeData = new StridedIndexTypeCollection(8); + + int halfEdge = m_halfEdgeData.newElement(); + // m_halfEdgeData.add(-1);//origin cluster + m_halfEdgeData.setField(halfEdge, 2, 0);// chain parentage + m_halfEdgeData.setField(halfEdge, 3, 0);// edge parentage + // m_halfEdgeData.add(-1);//twin + // m_halfEdgeData.add(-1);//prev + // m_halfEdgeData.add(-1);//next + int twinHalfEdge = m_halfEdgeData.newElement(); + // m_halfEdgeData.add(-1);//origin cluster + m_halfEdgeData.setField(twinHalfEdge, 2, 0);// chain parentage + m_halfEdgeData.setField(twinHalfEdge, 3, 0);// edge parentage + // m_halfEdgeData.add(-1);//twin + // m_halfEdgeData.add(-1);//prev + // m_halfEdgeData.add(-1);//next + setHalfEdgeTwin_(halfEdge, twinHalfEdge); + setHalfEdgeTwin_(twinHalfEdge, halfEdge); + return halfEdge; + } + + int newChain_() { + if (m_chainData == null) + m_chainData = new StridedIndexTypeCollection(8); + + int chain = m_chainData.newElement(); + // m_chainData->write(chain, + 1, -1);//half_edge + m_chainData.setField(chain, 2, 0);// parentage (geometric) + // m_chainData->write(m_chainReserved + 3, -1);//parent chain + // m_chainData->write(m_chainReserved + 4, -1);//firstIsland + // m_chainData->write(m_chainReserved + 5, -1);//nextInParent + // m_chainData->write(m_chainReserved + 6, -1);//prev + // m_chainData->write(m_chainReserved + 7, -1);//next + // m_chainReserved += 8; + return chain; + } + + int deleteChain_(int chain) { + // Note: this method cannot be after _PlaneSweep + assert (m_universeChain != chain); + int n = getChainNext(chain); + m_chainData.deleteElement(chain); + // Note: no need to update the first chain, because one should never try + // deleting the first (the universe) chain. + return n; + } + + int getClusterIndex_(int cluster) { + return m_clusterData.elementToIndex(cluster); + } + + void setClusterVertexIterator_(int cluster, int verticeList) { + m_clusterData.setField(cluster, 7, verticeList); + } + + void setClusterHalfEdge_(int cluster, int half_edge) { + m_clusterData.setField(cluster, 2, half_edge); + } + + void setClusterParentage_(int cluster, int parentage) { + m_clusterData.setField(cluster, 1, parentage); + } + + void setPrevCluster_(int cluster, int nextCluster) { + m_clusterData.setField(cluster, 3, nextCluster); + } + + void setNextCluster_(int cluster, int nextCluster) { + m_clusterData.setField(cluster, 4, nextCluster); + } + + void setClusterVertexIndex_(int cluster, int index) { + m_clusterData.setField(cluster, 5, index); + } + + int getClusterVertexIndex_(int cluster) { + return m_clusterData.getField(cluster, 5); + } + + void setClusterChain_(int cluster, int chain) { + m_clusterData.setField(cluster, 6, chain); + } + + void addClusterToExteriorChain_(int chain, int cluster) { + assert (getClusterChain(cluster) == -1); + setClusterChain_(cluster, chain); + // There is no link from the chain to the cluster. Only vice versa. + // Consider for change? + } + + int getHalfEdgeIndex_(int he) { + return m_halfEdgeData.elementToIndex(he); + } + + void setHalfEdgeOrigin_(int half_edge, int cluster) { + m_halfEdgeData.setField(half_edge, 1, cluster); + } + + void setHalfEdgeTwin_(int half_edge, int twinHalfEdge) { + m_halfEdgeData.setField(half_edge, 4, twinHalfEdge); + } + + void setHalfEdgePrev_(int half_edge, int prevHalfEdge) { + m_halfEdgeData.setField(half_edge, 5, prevHalfEdge); + } + + void setHalfEdgeNext_(int half_edge, int nextHalfEdge) { + m_halfEdgeData.setField(half_edge, 6, nextHalfEdge); + } + + // void set_half_edge_chain_parentage_(int half_edge, int + // chainParentageMask) { m_halfEdgeData.setField(half_edge + 2, + // chainParentageMask); } + void setHalfEdgeChain_(int half_edge, int chain) { + m_halfEdgeData.setField(half_edge, 2, chain); + } + + void setHalfEdgeParentage_(int half_edge, int parentageMask) { + m_halfEdgeData.setField(half_edge, 3, parentageMask); + } + + int getHalfEdgeParentageMask_(int half_edge) { + return m_halfEdgeData.getField(half_edge, 3); + } + + void setHalfEdgeVertexIterator_(int half_edge, int vertexIterator) { + m_halfEdgeData.setField(half_edge, 7, vertexIterator); + } + + void updateVertexToHalfEdgeConnectionHelper_(int half_edge, boolean bClear) { + int viter = getHalfEdgeVertexIterator(half_edge); + if (viter != -1) { + int he = bClear ? -1 : half_edge; + for (int viter_ = getHalfEdgeVertexIterator(half_edge); viter_ != -1; viter_ = incrementVertexIterator(viter_)) { + int vertex = getVertexFromVertexIterator(viter_); + m_shape.setUserIndex(vertex, m_halfEdgeIndex, he); + } + } + } + + void updateVertexToHalfEdgeConnection_(int half_edge, boolean bClear) { + if (half_edge == -1) + return; + updateVertexToHalfEdgeConnectionHelper_(half_edge, bClear); + updateVertexToHalfEdgeConnectionHelper_(getHalfEdgeTwin(half_edge), + bClear); + } + + int getChainIndex_(int chain) { + return m_chainData.elementToIndex(chain); + } + + void setChainHalfEdge_(int chain, int half_edge) { + m_chainData.setField(chain, 1, half_edge); + } + + void setChainParentage_(int chain, int parentage) { + m_chainData.setField(chain, 2, parentage); + } + + void setChainParent_(int chain, int parentChain) { + assert (m_chainData.getField(chain, 3) != parentChain); + m_chainData.setField(chain, 3, parentChain); + int firstIsland = getChainFirstIsland(parentChain); + setChainNextInParent_(chain, firstIsland); + setChainFirstIsland_(parentChain, chain); + } + + void setChainFirstIsland_(int chain, int islandChain) { + m_chainData.setField(chain, 4, islandChain); + } + + void setChainNextInParent_(int chain, int nextInParent) { + m_chainData.setField(chain, 5, nextInParent); + } + + void setChainPrev_(int chain, int prev) { + m_chainData.setField(chain, 6, prev); + } + + void setChainNext_(int chain, int next) { + m_chainData.setField(chain, 7, next); + } + + void setChainArea_(int chain, double area) { + int chainIndex = getChainIndex_(chain); + m_chainAreas.write(chainIndex, area); + } + + void setChainPerimeter_(int chain, double perimeter) { + int chainIndex = getChainIndex_(chain); + m_chainPerimeters.write(chainIndex, perimeter); + } + + void updateChainAreaAndPerimeter_(int chain) { + double area = 0; + double perimeter = 0; + int firstHalfEdge = getChainHalfEdge(chain); + Point2D origin = new Point2D(), from = new Point2D(), to = new Point2D(); + getHalfEdgeFromXY(firstHalfEdge, origin); + from.setCoords(origin); + int half_edge = firstHalfEdge; + do { + getHalfEdgeToXY(half_edge, to); + perimeter += Point2D.distance(from, to); + int twinChain = getHalfEdgeChain(getHalfEdgeTwin(half_edge)); + if (twinChain != chain)// only count edges are not dangling segments + // of polylines + { + area += ((to.x - origin.x) - (from.x - origin.x)) + * ((to.y - origin.y) + (from.y - origin.y)) * 0.5; + } + + from.setCoords(to); + half_edge = getHalfEdgeNext(half_edge); + } while (half_edge != firstHalfEdge); + + int ind = getChainIndex_(chain); + m_chainAreas.write(ind, area); + m_chainPerimeters.write(ind, perimeter); + } + + int getChainTopMostEdge_(int chain) { + int firstHalfEdge = getChainHalfEdge(chain); + Point2D top = new Point2D(); + getHalfEdgeFromXY(firstHalfEdge, top); + int topEdge = firstHalfEdge; + Point2D v = new Point2D(); + int half_edge = firstHalfEdge; + do { + getHalfEdgeFromXY(half_edge, v); + if (v.compare(top) > 0) { + top.setCoords(v); + topEdge = half_edge; + } + half_edge = getHalfEdgeNext(half_edge); + } while (half_edge != firstHalfEdge); + return topEdge; + } + + void planeSweepParentage_(int inputMode, ProgressTracker progress_tracker) { + PlaneSweepComparator comparator = new PlaneSweepComparator(this); + Treap aet = new Treap(); + aet.setCapacity(m_pointCount / 2); + aet.setComparator(comparator); + + AttributeStreamOfInt32 new_edges = new AttributeStreamOfInt32(0); + int treeNodeIndex = createUserIndexForHalfEdges(); + + ClusterSweepMonikerComparator clusterMoniker = null; + int counter = 0; + // Clusters are sorted by the y, x coordinate in ascending order. + Point2D pt = new Point2D(); + // Each cluster is an event of the sweep-line algorithm. + for (int cluster = getFirstCluster(); cluster != -1; cluster = getNextCluster(cluster)) { + counter++; + if ((counter & 0xFF) == 0) { + if ((progress_tracker != null) + && !(progress_tracker.progress(-1, -1))) + throw new UserCancelException(); + } + + int firstHalfEdge = getClusterHalfEdge(cluster); + if (firstHalfEdge != -1) { + new_edges.resizePreserveCapacity(0); + if (!tryOptimizedInsertion_(aet, treeNodeIndex, new_edges, + cluster, firstHalfEdge))// optimized insertion is for a + // simple chain, in that case we + // simply replace an old edge + // with a new one in AET - O(1) + {// This is more complex than a simple chain of edges + getXY(cluster, pt); + comparator.setY(pt.y); + int clusterHalfEdge = firstHalfEdge; + // Delete all edges that end at the cluster. + do {// edges that end at the cluster have been assigned an + // AET node in the treeNodeIndex. + int attachedTreeNode = getHalfEdgeUserIndex( + clusterHalfEdge, treeNodeIndex); + if (attachedTreeNode != -1) { + assert (attachedTreeNode != StridedIndexTypeCollection + .impossibleIndex2()); + aet.deleteNode(attachedTreeNode, -1); + setHalfEdgeUserIndex(clusterHalfEdge, + treeNodeIndex, + StridedIndexTypeCollection + .impossibleIndex2());// set it to -2 + } + + clusterHalfEdge = getHalfEdgeNext(getHalfEdgeTwin(clusterHalfEdge)); + assert (getHalfEdgeOrigin(clusterHalfEdge) == cluster); + } while (firstHalfEdge != clusterHalfEdge); + + // insert edges that start at the cluster. + // We need to insert only the edges that have the from point + // below the to point. + // This is ensured by the logic of the algorithm. + clusterHalfEdge = firstHalfEdge; + do { + int attachedTreeNode = getHalfEdgeUserIndex( + clusterHalfEdge, treeNodeIndex); + if (attachedTreeNode == -1) { + int newTreeNode = aet.addElement(clusterHalfEdge, + -1); + new_edges.add(newTreeNode); + } + clusterHalfEdge = getHalfEdgeNext(getHalfEdgeTwin(clusterHalfEdge)); + assert (getHalfEdgeOrigin(clusterHalfEdge) == cluster); + } while (firstHalfEdge != clusterHalfEdge); + } + + // Analyze new edges. + // We go in the opposite order, because of the way how the half + // edges are sorted on a cluster. + // We want to go from the left to the right. + for (int i = new_edges.size() - 1; i >= 0; i--) { + int newTreeNode = new_edges.get(i); + int clusterHalfEdge = aet.getElement(newTreeNode); + int twinEdge = getHalfEdgeTwin(clusterHalfEdge); + assert (getHalfEdgeUserIndex(twinEdge, treeNodeIndex) == -1); + setHalfEdgeUserIndex(twinEdge, treeNodeIndex, newTreeNode); + + planeSweepParentagePropagateParentage_(aet, newTreeNode, + inputMode); + } + } else if (getClusterChain(cluster) == -1) { + // get the left half edge of a face. The point belongs to the + // face. + if (clusterMoniker == null) + clusterMoniker = new ClusterSweepMonikerComparator(this); + + getXY(cluster, pt); + clusterMoniker.setPointXY(pt); + int leftNode = aet.searchLowerBound(clusterMoniker, -1); + int chain = m_universeChain; + + if (leftNode != -1) { + int edge = aet.getElement(leftNode); + int leftChain = getHalfEdgeChain(edge); + if (leftChain == getHalfEdgeChain(getHalfEdgeTwin(edge))) { + edge = getLeftSkipPolylines_(aet, leftNode); + } + + if (edge != -1) + chain = getHalfEdgeChain(edge); + } + + addClusterToExteriorChain_(chain, cluster); + } + } + + deleteUserIndexForHalfEdges(treeNodeIndex); + } + + void planeSweepParentagePropagateParentage_(Treap aet, int treeNode, + int inputMode) { + int edge = aet.getElement(treeNode); + int edgeChain = getHalfEdgeChain(edge); + int edgeChainParent = getChainParent(edgeChain); + if (edgeChainParent != -1) + return;// this edge has been processed already. + + // get contributing left edge. + int leftEdge = getLeftSkipPolylines_(aet, treeNode); + + int twinEdge = getHalfEdgeTwin(edge); + int twinHalfEdgeChain = getHalfEdgeChain(twinEdge); + + double chainArea = getChainArea(edgeChain); + double twinChainArea = getChainArea(twinHalfEdgeChain); + + int parentChain = getChainParent(edgeChain); + int twinParentChain = getChainParent(twinHalfEdgeChain); + if (leftEdge == -1 && parentChain == -1) { + // This edge/twin pair does not have a neighbour edge to the left. + // twin parent is not yet been assigned. + if (twinHalfEdgeChain == edgeChain) {// set parentage of a polyline + // edge (any edge for which + // the edge ant its twin + // belong to the same chain) + setChainParent_(twinHalfEdgeChain, getFirstChain()); + twinParentChain = getFirstChain(); + parentChain = twinParentChain; + } else { + // We have two touching chains that do not have parent chain + // set. + // The edge is directed up, the twin edge is directed down. + // There is no edge to the left. THat means there is no other + // than the universe surrounding this edge. + // The edge must belong to a clockwise chain, and the twin edge + // must belong to a ccw chain that encloses this edge. This + // follows from the way how we connect edges around clusters. + assert (twinChainArea < 0 && chainArea > 0); + if (twinParentChain == -1) { + setChainParent_(twinHalfEdgeChain, m_universeChain); + twinParentChain = m_universeChain; + } else { + assert (getFirstChain() == twinParentChain); + } + + setChainParent_(edgeChain, twinHalfEdgeChain); + parentChain = twinHalfEdgeChain; + } + } + + if (leftEdge != -1) { + int leftEdgeChain = getHalfEdgeChain(leftEdge); + // the twin edge has not been processed yet + if (twinParentChain == -1) { + double leftArea = getChainArea(leftEdgeChain); + if (leftArea <= 0) {// if left Edge's chain area is negative, + // then it is a chain that ends at the left + // edge, so we need to get the parent of the + // left chain and it will be the parent of + // this one. + int leftChainParent = getChainParent(leftEdgeChain); + assert (leftChainParent != -1); + + setChainParent_(twinHalfEdgeChain, leftChainParent); + twinParentChain = leftChainParent; + } else // (leftArea > 0) + {// left edge is an edge of positive chain. It surrounds the + // twin chain. + setChainParent_(twinHalfEdgeChain, leftEdgeChain); + twinParentChain = leftEdgeChain; + } + + if (twinHalfEdgeChain == edgeChain) // if this is a polyline + // chain + parentChain = twinParentChain; + } + } + + if (parentChain == -1) { + trySetChainParentFromTwin_(edgeChain, twinHalfEdgeChain); + parentChain = getChainParent(edgeChain); + } + + assert (parentChain != -1); + + if (inputMode == EnumInputMode.enumInputModeBuildGraph) { + propagate_parentage_build_graph_(aet, treeNode, edge, leftEdge, edgeChain, edgeChainParent, twinHalfEdgeChain); + } else if (inputMode == EnumInputMode.enumInputModeSimplifyWinding) { + propagate_parentage_winding_(aet, treeNode, edge, leftEdge, twinEdge, edgeChain, edgeChainParent, twinHalfEdgeChain); + } else if (inputMode == EnumInputMode.enumInputModeSimplifyAlternate) { + propagate_parentage_alternate_(aet, treeNode, edge, leftEdge, twinEdge, edgeChain, edgeChainParent, twinHalfEdgeChain); + } + + } + + void propagate_parentage_build_graph_(Treap aet, int treeNode, int edge, int leftEdge, + int edgeChain, int edgeChainParent, int twinHalfEdgeChain) { + // Now do specific sweep calculations + int chainParentage = getChainParentage(edgeChain); + + if (leftEdge != -1) { + // borrow the parentage from the left edge also + int leftEdgeChain = getHalfEdgeChain(leftEdge); + + // We take parentage from the left edge (that edge has been + // already processed), and move its face parentage accross this + // edge/twin pair. + // While the parentage is moved, accross, any bits of the + // parentage that is present in the twin are removed, because + // the twin is the right edge of the current face. + // The remaining bits are added to the face parentage of this + // edge, indicating that the face this edge borders, belongs to + // all the parents that are still active to the left. + int twinChainParentage = getChainParentage(twinHalfEdgeChain); + int leftChainParentage = getChainParentage(leftEdgeChain); + + int edgeParentage = getHalfEdgeParentage(edge); + int spikeParentage = chainParentage & twinChainParentage + & leftChainParentage; // parentage that needs to stay + leftChainParentage = leftChainParentage + ^ (leftChainParentage & edgeParentage); + leftChainParentage |= spikeParentage; + + if (leftChainParentage != 0) { + // propagate left parentage to the current edge and its + // twin. + setChainParentage_(twinHalfEdgeChain, twinChainParentage + | leftChainParentage); + setChainParentage_(edgeChain, leftChainParentage + | chainParentage); + chainParentage |= leftChainParentage; + } + + // dbg_print_edge_(edge); + } + + for (int rightNode = aet.getNext(treeNode); rightNode != -1; rightNode = aet + .getNext(rightNode)) { + int rightEdge = aet.getElement(rightNode); + int rightTwin = getHalfEdgeTwin(rightEdge); + + int rightTwinChain = getHalfEdgeChain(rightTwin); + int rightTwinChainParentage = getChainParentage(rightTwinChain); + int rightEdgeParentage = getHalfEdgeParentage(rightEdge); + int rightEdgeChain = getHalfEdgeChain(rightEdge); + int rightChainParentage = getChainParentage(rightEdgeChain); + + int spikeParentage = rightTwinChainParentage + & rightChainParentage & chainParentage; // parentage + // that needs to + // stay + chainParentage = chainParentage + ^ (chainParentage & rightEdgeParentage);// only + // parentage + // that is + // abscent in + // the twin is + // propagated to + // the right + chainParentage |= spikeParentage; + + if (chainParentage == 0) + break; + + setChainParentage_(rightTwinChain, rightTwinChainParentage + | chainParentage); + setChainParentage_(rightEdgeChain, rightChainParentage + | chainParentage); + } + } + + void propagate_parentage_winding_(Treap aet, int treeNode, int edge, int leftEdge, int twinEdge, + int edgeChain, int edgeChainParent, int twinHalfEdgeChain) { + + if (edgeChain == twinHalfEdgeChain) + return; + // starting from the left most edge, calculate winding. + int edgeWinding = getHalfEdgeUserIndex(edge, + m_tmpHalfEdgeWindingNumberIndex); + edgeWinding += getHalfEdgeUserIndex(twinEdge, + m_tmpHalfEdgeWindingNumberIndex); + int winding = 0; + AttributeStreamOfInt32 chainStack = new AttributeStreamOfInt32(0); + AttributeStreamOfInt32 windingStack = new AttributeStreamOfInt32(0); + windingStack.add(0); + for (int leftNode = aet.getFirst(-1); leftNode != treeNode; leftNode = aet + .getNext(leftNode)) { + int leftEdge1 = aet.getElement(leftNode); + int leftTwin = getHalfEdgeTwin(leftEdge1); + int l_chain = getHalfEdgeChain(leftEdge1); + int lt_chain = getHalfEdgeChain(leftTwin); + + if (l_chain != lt_chain) { + int leftWinding = getHalfEdgeUserIndex(leftEdge1, + m_tmpHalfEdgeWindingNumberIndex); + leftWinding += getHalfEdgeUserIndex(leftTwin, + m_tmpHalfEdgeWindingNumberIndex); + winding += leftWinding; + + boolean popped = false; + if (chainStack.size() != 0 + && chainStack.getLast() == lt_chain) { + windingStack.removeLast(); + chainStack.removeLast(); + popped = true; + } + + if (getChainParent(lt_chain) == -1) + throw GeometryException.GeometryInternalError(); + + if (!popped || getChainParent(lt_chain) != l_chain) { + windingStack.add(winding); + chainStack.add(l_chain); + } + } + } + + winding += edgeWinding; + + if (chainStack.size() != 0 + && chainStack.getLast() == twinHalfEdgeChain) { + windingStack.removeLast(); + chainStack.removeLast(); + } + + if (winding != 0) { + if (windingStack.getLast() == 0) { + int geometry = m_shape.getFirstGeometry(); + int geometryID = getGeometryID(geometry); + setChainParentage_(edgeChain, geometryID); + } + } else { + if (windingStack.getLast() != 0) { + int geometry = m_shape.getFirstGeometry(); + int geometryID = getGeometryID(geometry); + setChainParentage_(edgeChain, geometryID); + } + } + } + + void propagate_parentage_alternate_(Treap aet, int treeNode, int edge, + int leftEdge, int twinEdge, int edgeChain, int edgeChainParent, + int twinHalfEdgeChain) { + // Now do specific sweep calculations + // This one is done when we are doing a topological operation. + int geometry = m_shape.getFirstGeometry(); + int geometryID = getGeometryID(geometry); + + if (leftEdge == -1) { + // no left edge neighbour means the twin chain is surrounded by the + // universe + assert (getChainParent(twinHalfEdgeChain) == m_universeChain); + assert (getChainParentage(twinHalfEdgeChain) == 0 || getChainParentage(twinHalfEdgeChain) == m_universe_geomID); + assert (getChainParentage(edgeChain) == 0); + setChainParentage_(twinHalfEdgeChain, m_universe_geomID); + int parity = getHalfEdgeUserIndex(edge, + m_tmpHalfEdgeOddEvenNumberIndex); + if ((parity & 1) != 0) + setChainParentage_(edgeChain, geometryID);// set the parenentage + // from the parity + else + setChainParentage_(edgeChain, m_universe_geomID);// this chain + // does not + // belong to + // geometry + } else { + int twin_parentage = getChainParentage(twinHalfEdgeChain); + if (twin_parentage == 0) { + int leftEdgeChain = getHalfEdgeChain(leftEdge); + int left_parentage = getChainParentage(leftEdgeChain); + setChainParentage_(twinHalfEdgeChain, left_parentage); + int parity = getHalfEdgeUserIndex(edge, + m_tmpHalfEdgeOddEvenNumberIndex); + if ((parity & 1) != 0) + setChainParentage_(edgeChain, + (left_parentage == geometryID) ? m_universe_geomID + : geometryID); + else + setChainParentage_(edgeChain, left_parentage); + + } else { + int parity = getHalfEdgeUserIndex(edge, + m_tmpHalfEdgeOddEvenNumberIndex); + if ((parity & 1) != 0) + setChainParentage_(edgeChain, + (twin_parentage == geometryID) ? m_universe_geomID + : geometryID); + else + setChainParentage_(edgeChain, twin_parentage); + } + + } + } + + boolean tryOptimizedInsertion_(Treap aet, int treeNodeIndex, + AttributeStreamOfInt32 new_edges, int cluster, int firstHalfEdge) { + int clusterHalfEdge = firstHalfEdge; + int attachedTreeNode = -1; + int newEdge = -1; + // Delete all edges that end at the cluster. + int count = 0; + do { + if (count == 2) + return false; + int n = getHalfEdgeUserIndex(clusterHalfEdge, treeNodeIndex); + if (n != -1) { + if (attachedTreeNode != -1) + return false;// two edges end at the cluster + attachedTreeNode = n; + } else { + if (newEdge != -1) + return false; // two edges start from the cluster + newEdge = clusterHalfEdge; + } + assert (getHalfEdgeOrigin(clusterHalfEdge) == cluster); + count++; + clusterHalfEdge = getHalfEdgeNext(getHalfEdgeTwin(clusterHalfEdge)); + } while (firstHalfEdge != clusterHalfEdge); + + if (newEdge == -1 || attachedTreeNode == -1) + return false; + + setHalfEdgeUserIndex(aet.getElement(attachedTreeNode), treeNodeIndex, + StridedIndexTypeCollection.impossibleIndex2()); + aet.setElement(attachedTreeNode, newEdge); + new_edges.add(attachedTreeNode); + return true; + } + + boolean trySetChainParentFromTwin_(int chainToSet, int twinChain) { + assert (getChainParent(chainToSet) == -1); + double area = getChainArea(chainToSet); + if (area == 0) + return false; + double twinArea = getChainArea(twinChain); + assert (twinArea != 0); + if (area > 0 && twinArea < 0) { + setChainParent_(chainToSet, twinChain); + return true; + } + if (area < 0 && twinArea > 0) { + setChainParent_(chainToSet, twinChain); + return true; + } else { + int twinParent = getChainParent(twinChain); + if (twinParent != -1) { + setChainParent_(chainToSet, twinParent); + return true; + } + } + + return false; + } + + void createHalfEdges_(int inputMode, AttributeStreamOfInt32 sorted_vertices) { + // After this loop all halfedges will be created. + // This loop also sets the known parentage on the edges. + // The half edges are connected with each other in a random order + m_halfEdgeIndex = m_shape.createUserIndex(); + + for (int i = 0, nvert = sorted_vertices.size(); i < nvert; i++) { + int vertex = sorted_vertices.get(i); + int cluster = m_shape.getUserIndex(vertex, m_clusterIndex); + + int path = m_shape.getPathFromVertex(vertex); + int geometry = m_shape.getGeometryFromPath(path); + int gt = m_shape.getGeometryType(geometry); + if (Geometry.isMultiPath(gt)) { + int next = m_shape.getNextVertex(vertex); + if (next == -1) + continue; + + int clusterTo = m_shape.getUserIndex(next, m_clusterIndex); + assert (clusterTo != -1); + if (cluster == clusterTo) { + if (m_shape.getSegment(vertex) != null) { + assert (m_shape.getSegment(vertex).calculateLength2D() == 0); + } else { + assert (m_shape.getXY(vertex).isEqual(m_shape.getXY(next))); + } + + continue; + } + + int half_edge = newHalfEdgePair_(); + int twinEdge = getHalfEdgeTwin(half_edge); + + // add vertex to the half edge. + int vertIndex = m_clusterVertices.newElement(); + m_clusterVertices.setField(vertIndex, 0, vertex); + m_clusterVertices.setField(vertIndex, 1, -1); + setHalfEdgeVertexIterator_(half_edge, vertIndex); + + setHalfEdgeOrigin_(half_edge, cluster); + int firstHalfEdge = getClusterHalfEdge(cluster); + if (firstHalfEdge == -1) { + setClusterHalfEdge_(cluster, half_edge); + setHalfEdgePrev_(half_edge, twinEdge); + setHalfEdgeNext_(twinEdge, half_edge); + } else { + // It does not matter what order we insert the new edges in. + // We fix the order later. + int firstPrev = getHalfEdgePrev(firstHalfEdge); + assert (getHalfEdgeNext(firstPrev) == firstHalfEdge); + setHalfEdgePrev_(firstHalfEdge, twinEdge); + setHalfEdgeNext_(twinEdge, firstHalfEdge); + assert (getHalfEdgePrev(firstHalfEdge) == twinEdge); + assert (getHalfEdgeNext(twinEdge) == firstHalfEdge); + setHalfEdgeNext_(firstPrev, half_edge); + setHalfEdgePrev_(half_edge, firstPrev); + assert (getHalfEdgePrev(half_edge) == firstPrev); + assert (getHalfEdgeNext(firstPrev) == half_edge); + } + + setHalfEdgeOrigin_(twinEdge, clusterTo); + int firstTo = getClusterHalfEdge(clusterTo); + if (firstTo == -1) { + setClusterHalfEdge_(clusterTo, twinEdge); + setHalfEdgeNext_(half_edge, twinEdge); + setHalfEdgePrev_(twinEdge, half_edge); + } else { + int firstToPrev = getHalfEdgePrev(firstTo); + assert (getHalfEdgeNext(firstToPrev) == firstTo); + setHalfEdgePrev_(firstTo, half_edge); + setHalfEdgeNext_(half_edge, firstTo); + assert (getHalfEdgePrev(firstTo) == half_edge); + assert (getHalfEdgeNext(half_edge) == firstTo); + setHalfEdgeNext_(firstToPrev, twinEdge); + setHalfEdgePrev_(twinEdge, firstToPrev); + assert (getHalfEdgePrev(twinEdge) == firstToPrev); + assert (getHalfEdgeNext(firstToPrev) == twinEdge); + } + + int geometryID = getGeometryID(geometry); + // No chains yet exists, so we use a temporary user index to + // store chain parentage. + // The input polygons has been already simplified so their edges + // directed such that the hole is to the left from the edge + // (each edge is directed from the "from" to "to" point). + if (inputMode == EnumInputMode.enumInputModeBuildGraph) { + setHalfEdgeUserIndex(twinEdge, m_tmpHalfEdgeParentageIndex, + 0); // Hole is always to the left. left side here is + // the twin. + setHalfEdgeUserIndex(half_edge, + m_tmpHalfEdgeParentageIndex, + gt == Geometry.GeometryType.Polygon ? geometryID + : 0); + } else if (inputMode == EnumInputMode.enumInputModeSimplifyWinding) { + Point2D pt_1 = new Point2D(); + m_shape.getXY(vertex, pt_1); + Point2D pt_2 = new Point2D(); + m_shape.getXY(next, pt_2); + int windingNumber = 0; + int windingNumberTwin = 0; + if (pt_1.compare(pt_2) < 0) { + // The edge is directed bottom-up. That means it has the + // winding number of +1. + // The half-edge direction coincides with the edge + // direction. THe twin is directed top-down. + // The half edge will have the winding number of 1 and + // its twin the winding number of 0. + // When crossing the half-edge/twin pair from left to + // right, the winding number is changed by +1 + windingNumber = 1; + } else { + // The edge is directed top-down. That means it has the + // winding number of -1. + // The half-edge direction coincides with the edge + // direction. The twin is directed bottom-up. + // The half edge will have the winding number of 0 and + // its twin the winding number of -1. + // When crossing the half-edge/twin pair from left to + // right, the winding number is changed by -1. + windingNumberTwin = -1; + } + + // When we get a half-edge/twin pair, we can determine the + // winding number of the underlying edge + // by summing up the half-edge and twin's + // winding numbers. + + setHalfEdgeUserIndex(twinEdge, m_tmpHalfEdgeParentageIndex, + 0); + setHalfEdgeUserIndex(half_edge, + m_tmpHalfEdgeParentageIndex, 0); + // We split the winding number between the half edge and its + // twin. + // This allows us to determine which half edge goes in the + // direction of the edge, and also it allows to calculate + // the + // winging number by summing up the winding number of half + // edge and its twin. + setHalfEdgeUserIndex(half_edge, + m_tmpHalfEdgeWindingNumberIndex, windingNumber); + setHalfEdgeUserIndex(twinEdge, + m_tmpHalfEdgeWindingNumberIndex, windingNumberTwin); + } else if (inputMode == EnumInputMode.enumInputModeIsSimplePolygon) { + setHalfEdgeUserIndex(twinEdge, m_tmpHalfEdgeParentageIndex, + m_universe_geomID); + setHalfEdgeUserIndex(half_edge, + m_tmpHalfEdgeParentageIndex, + gt == Geometry.GeometryType.Polygon ? geometryID + : 0); + } else if (inputMode == EnumInputMode.enumInputModeSimplifyAlternate) { + setHalfEdgeUserIndex(twinEdge, m_tmpHalfEdgeParentageIndex, + 0); + setHalfEdgeUserIndex(half_edge, + m_tmpHalfEdgeParentageIndex, 0); + setHalfEdgeUserIndex(half_edge, + m_tmpHalfEdgeOddEvenNumberIndex, 1); + setHalfEdgeUserIndex(twinEdge, + m_tmpHalfEdgeOddEvenNumberIndex, 1); + } + + int edgeBit = gt == Geometry.GeometryType.Polygon ? c_edgeBitMask + : 0; + setHalfEdgeParentage_(half_edge, geometryID | edgeBit); + setHalfEdgeParentage_(twinEdge, geometryID | edgeBit); + } + } + } + + void mergeVertexListsOfEdges_(int eDst, int eSrc) { + assert (getHalfEdgeTo(eDst) == getHalfEdgeTo(eSrc)); + assert (getHalfEdgeOrigin(eDst) == getHalfEdgeOrigin(eSrc)); + + { + int vertFirst2 = getHalfEdgeVertexIterator(eSrc); + if (vertFirst2 != -1) { + int vertFirst1 = getHalfEdgeVertexIterator(eDst); + m_clusterVertices.setField(vertFirst2, 1, vertFirst1); + setHalfEdgeVertexIterator_(eDst, vertFirst2); + setHalfEdgeVertexIterator_(eSrc, -1); + } + } + + int eDstTwin = getHalfEdgeTwin(eDst); + int eSrcTwin = getHalfEdgeTwin(eSrc); + { + int vertFirst2 = getHalfEdgeVertexIterator(eSrcTwin); + if (vertFirst2 != -1) { + int vertFirst1 = getHalfEdgeVertexIterator(eDstTwin); + m_clusterVertices.setField(vertFirst2, 1, vertFirst1); + setHalfEdgeVertexIterator_(eDstTwin, vertFirst2); + setHalfEdgeVertexIterator_(eSrcTwin, -1); + } + } + } + + void sortHalfEdgesByAngle_(int inputMode) { + AttributeStreamOfInt32 angleSorter = new AttributeStreamOfInt32(0); + angleSorter.reserve(10); + TopoGraphAngleComparer tgac = new TopoGraphAngleComparer(this); + // Now go through the clusters, sort edges in each cluster by angle, and + // reconnect the halfedges of sorted edges in the sorted order. + // Also share the parentage information between coinciding edges and + // remove duplicates. + for (int cluster = getFirstCluster(); cluster != -1; cluster = getNextCluster(cluster)) { + angleSorter.clear(false); + int first = getClusterHalfEdge(cluster); + if (first != -1) { + // 1. sort edges originating at the cluster by angle (counter - + // clockwise). + int edge = first; + do { + angleSorter.add(edge);// edges have the cluster in their + // origin and are directed away from + // it. The twin edges are directed + // towards the cluster. + edge = getHalfEdgeNext(getHalfEdgeTwin(edge)); + } while (edge != first); + + if (angleSorter.size() > 1) { + boolean changed_order = true; + if (angleSorter.size() > 2) { + angleSorter.Sort(0, angleSorter.size(), + tgac); // std::sort(angleSorter.get_ptr(), + // angleSorter.get_ptr() + // + + // angleSorter.size(), + // TopoGraphAngleComparer(this)); + angleSorter.add(angleSorter.get(0)); + } else { + //no need to sort most two edge cases. we only need to make sure that edges going up are sorted + if (compareEdgeAnglesForPair_(angleSorter.get(0), + angleSorter.get(1)) > 0) { + int tmp = angleSorter.get(0); + angleSorter.set(0, angleSorter.get(1)); + angleSorter.set(1, tmp); + } else { + changed_order = false; + } + } + // 2. get rid of duplicate edges by merging them (duplicate + // edges appear at this step because we converted all + // segments into the edges, including overlapping). + int e0 = angleSorter.get(0); + int ePrev = e0; + int ePrevTo = getHalfEdgeTo(ePrev); + int ePrevTwin = getHalfEdgeTwin(ePrev); + int prevMerged = -1; + for (int i = 1, n = angleSorter.size(); i < n; i++) { + int e = angleSorter.get(i); + int eTwin = getHalfEdgeTwin(e); + int eTo = getHalfEdgeOrigin(eTwin); + assert (getHalfEdgeOrigin(e) == getHalfEdgeOrigin(ePrev));// e + // origin + // and + // ePrev + // origin + // are + // equal + // by + // definition + // (e + // and + // ePrev + // emanate + // from + // the + // same + // cluster) + if (eTo == ePrevTo && e != ePrev)// e's To cluster and + // ePrev's To + // cluster are + // equal, means the + // edges coincide + // and need to be + // merged. + {// remove duplicate edge. Before removing, propagate + // the parentage to the remaning edge + if (inputMode == EnumInputMode.enumInputModeBuildGraph) { + int newEdgeParentage = getHalfEdgeParentageMask_(ePrev) + | getHalfEdgeParentageMask_(e); + setHalfEdgeParentage_(ePrev, newEdgeParentage); + setHalfEdgeParentage_(ePrevTwin, + newEdgeParentage); + assert (getHalfEdgeParentageMask_(ePrev) == getHalfEdgeParentageMask_(ePrevTwin)); + + setHalfEdgeUserIndex( + ePrev, + m_tmpHalfEdgeParentageIndex, + getHalfEdgeUserIndex(ePrev, + m_tmpHalfEdgeParentageIndex) + | getHalfEdgeUserIndex(e, + m_tmpHalfEdgeParentageIndex)); + setHalfEdgeUserIndex( + ePrevTwin, + m_tmpHalfEdgeParentageIndex, + getHalfEdgeUserIndex(ePrevTwin, + m_tmpHalfEdgeParentageIndex) + | getHalfEdgeUserIndex(eTwin, + m_tmpHalfEdgeParentageIndex)); + } else if (m_tmpHalfEdgeWindingNumberIndex != -1) { + // when doing simplify the + // m_tmpHalfEdgeWindingNumberIndex contains the + // winding number. + // When edges are merged their winding numbers + // are added. + int newHalfEdgeWinding = getHalfEdgeUserIndex( + ePrev, m_tmpHalfEdgeWindingNumberIndex) + + getHalfEdgeUserIndex(e, + m_tmpHalfEdgeWindingNumberIndex); + int newTwinEdgeWinding = getHalfEdgeUserIndex( + ePrevTwin, + m_tmpHalfEdgeWindingNumberIndex) + + getHalfEdgeUserIndex(eTwin, + m_tmpHalfEdgeWindingNumberIndex); + setHalfEdgeUserIndex(ePrev, + m_tmpHalfEdgeWindingNumberIndex, + newHalfEdgeWinding); + setHalfEdgeUserIndex(ePrevTwin, + m_tmpHalfEdgeWindingNumberIndex, + newTwinEdgeWinding); + // The winding number of an edge is a sum of the + // winding numbers of the half edge and its + // twin. + // To determine which half edge direction + // coincides with the edge direction, determine + // which half edge has larger abs value of + // winding number. If half edge and twin winding + // numbers cancel each other, the edge winding + // number is zero, meaning there are + // even number of edges coinciding there and + // half of them has opposite direction to + // another half. + } else if (inputMode == EnumInputMode.enumInputModeIsSimplePolygon) { + m_non_simple_result = new NonSimpleResult(NonSimpleResult.Reason.CrossOver, cluster, -1); + return; + } else if (m_tmpHalfEdgeOddEvenNumberIndex != -1) { + int newHalfEdgeWinding = getHalfEdgeUserIndex( + ePrev, m_tmpHalfEdgeOddEvenNumberIndex) + + getHalfEdgeUserIndex(e, + m_tmpHalfEdgeOddEvenNumberIndex); + int newTwinEdgeWinding = getHalfEdgeUserIndex( + ePrevTwin, + m_tmpHalfEdgeOddEvenNumberIndex) + + getHalfEdgeUserIndex(eTwin, + m_tmpHalfEdgeOddEvenNumberIndex); + setHalfEdgeUserIndex(ePrev, + m_tmpHalfEdgeOddEvenNumberIndex, + newHalfEdgeWinding); + setHalfEdgeUserIndex(ePrevTwin, + m_tmpHalfEdgeOddEvenNumberIndex, + newTwinEdgeWinding); + } + + mergeVertexListsOfEdges_(ePrev, e); + deleteEdgeImpl_(e); + assert (n < 3 || e0 == angleSorter.getLast()); + prevMerged = ePrev; + angleSorter.set(i, -1); + if (e == e0) { + angleSorter.set(0, -1); + e0 = -1; + } + + continue; + } else { + //edges do not coincide + } + + updateVertexToHalfEdgeConnection_(prevMerged, false); + prevMerged = -1; + ePrev = e; + ePrevTo = eTo; + ePrevTwin = eTwin; + } + + + updateVertexToHalfEdgeConnection_(prevMerged, false); + prevMerged = -1; + + if (!changed_order) { + //small optimization to avoid reconnecting if nothing changed + e0 = -1; + for (int i = 0, n = angleSorter.size(); i < n; i++) { + int e = angleSorter.get(i); + if (e == -1) + continue; + e0 = e; + break; + } + + if (first != e0) + setClusterHalfEdge_(cluster, e0); + + continue; //next cluster + } + + + // 3. Reconnect edges in the sorted order. The edges are + // sorted counter clockwise. + // We connect them such that every right turn is made in the + // clockwise order. + // This guarantees that the smallest faces are clockwise. + e0 = -1; + for (int i = 0, n = angleSorter.size(); i < n; i++) { + int e = angleSorter.get(i); + if (e == -1) + continue; + if (e0 == -1) { + e0 = e; + ePrev = e0; + ePrevTo = getHalfEdgeTo(ePrev); + ePrevTwin = getHalfEdgeTwin(ePrev); + continue; + } + + if (e == ePrev) { + // This condition can only happen if all edges in + // the bunch coincide. + assert (i == n - 1); + continue; + } + + int eTwin = getHalfEdgeTwin(e); + int eTo = getHalfEdgeOrigin(eTwin); + assert (getHalfEdgeOrigin(e) == getHalfEdgeOrigin(ePrev)); + assert (eTo != ePrevTo); + setHalfEdgeNext_(ePrevTwin, e); + setHalfEdgePrev_(e, ePrevTwin); + ePrev = e; + ePrevTo = eTo; + ePrevTwin = eTwin; + if (inputMode == EnumInputMode.enumInputModeIsSimplePolygon) { + int par1 = getHalfEdgeUserIndex(e, m_tmpHalfEdgeParentageIndex) | + getHalfEdgeUserIndex(getHalfEdgePrev(e), m_tmpHalfEdgeParentageIndex); + if (par1 == (m_universe_geomID | 1)) { + //violation of face parentage + m_non_simple_result = new NonSimpleResult(NonSimpleResult.Reason.CrossOver, cluster, -1); + return; + } + } + + } + + setClusterHalfEdge_(cluster, e0);// smallest angle goes + // first. + } + } + } + } + + void buildChains_(int inputMode) { + // Creates chains and puts them in the list of chains. + // Does not set the chain parentage + // Does not connect chains + + int firstChain = -1; + int visitedHalfEdgeIndex = createUserIndexForHalfEdges(); + // Visit all the clusters + for (int cluster = getFirstCluster(); cluster != -1; cluster = getNextCluster(cluster)) { + // For each cluster visit all half edges on the cluster + int first = getClusterHalfEdge(cluster); + if (first != -1) { + int edge = first; + do { + if (getHalfEdgeUserIndex(edge, visitedHalfEdgeIndex) != 1)// check + // if + // we + // have + // visited + // this + // halfedge + // already + {// if we have not visited this halfedge yet, then we have + // not created a chain for it yet. + int chain = newChain_();// new chain's parentage is set + // to 0. + setChainHalfEdge_(chain, edge);// Note, the half-edge's + // Origin is the lowest + // point of the chain. + setChainNext_(chain, firstChain);// add the new chain to + // the list of + // chains. + if (firstChain != -1) { + setChainPrev_(firstChain, chain); + } + firstChain = chain; + // go thorough all halfedges until return back to the + // same one. Thus forming a chain. + int parentage = 0; + int e = edge; + do { + // accumulate chain parentage from all the chain + // edges m_tmpHalfEdgeParentageIndex. + parentage |= getHalfEdgeUserIndex(e, + m_tmpHalfEdgeParentageIndex); + assert (getHalfEdgeUserIndex(e, + visitedHalfEdgeIndex) != 1); + setHalfEdgeChain_(e, chain); + setHalfEdgeUserIndex(e, visitedHalfEdgeIndex, 1);// mark + // the + // edge + // visited. + e = getHalfEdgeNext(e); + } while (e != edge); + + assert (inputMode != EnumInputMode.enumInputModeIsSimplePolygon || parentage != (1 | m_universe_geomID)); + + setChainParentage_(chain, parentage); + } + + edge = getHalfEdgeNext(getHalfEdgeTwin(edge));// next + // halfedge + // on the + // cluster + } while (edge != first); + } + } + + // add the Universe chain. We want it to be the one that getFirstChain + // returns. + int chain = newChain_(); + setChainHalfEdge_(chain, -1); + setChainNext_(chain, firstChain); + if (firstChain != -1) + setChainPrev_(firstChain, chain); + + m_universeChain = chain; + + m_chainAreas = new AttributeStreamOfDbl(m_chainData.size(), + NumberUtils.TheNaN); + m_chainPerimeters = new AttributeStreamOfDbl(m_chainData.size(), + NumberUtils.TheNaN); + + setChainArea_(m_universeChain, NumberUtils.positiveInf());// the + // Universe + // is + // infinite + setChainPerimeter_(m_universeChain, NumberUtils.positiveInf());// the + // Universe + // is + // infinite + + deleteUserIndexForHalfEdges(visitedHalfEdgeIndex); + } + + void simplify_(int inputMode) { + if (inputMode == EnumInputMode.enumInputModeSimplifyAlternate) { + simplifyAlternate_(); + } else if (inputMode == EnumInputMode.enumInputModeSimplifyWinding) { + simplifyWinding_(); + } + } + + void simplifyAlternate_() { + //there is nothing to do + } + + void simplifyWinding_() { + //there is nothing to do + } + + private int getFirstUnvisitedHalfEdgeOnCluster_(int cluster, int hintEdge, + int vistiedEdgesIndex) { + // finds first half edge which is unvisited (index is not set to 1. + // when hintEdge != -1, it is used to start going around the edges. + + int edge = hintEdge != -1 ? hintEdge : getClusterHalfEdge(cluster); + if (edge == -1) + return -1; + + int f = edge; + + while (true) { + int v = getHalfEdgeUserIndex(edge, vistiedEdgesIndex); + if (v != 1) { + return edge; + } + + int next = getHalfEdgeNext(getHalfEdgeTwin(edge)); + if (next == f) + return -1; + + edge = next; + } + } + + boolean removeSpikes_() { + boolean removed = false; + int visitedIndex = createUserIndexForHalfEdges(); + for (int cluster = getFirstCluster(); cluster != -1; cluster = getNextCluster(cluster)) { + int nextClusterEdge = -1; //a hint + while (true) { + int firstHalfEdge = getFirstUnvisitedHalfEdgeOnCluster_(cluster, nextClusterEdge, visitedIndex); + if (firstHalfEdge == -1) + break; + + nextClusterEdge = getHalfEdgeNext(getHalfEdgeTwin(firstHalfEdge)); + int faceHalfEdge = firstHalfEdge; + + while (true) { + int faceHalfEdgeNext = getHalfEdgeNext(faceHalfEdge); + int faceHalfEdgePrev = getHalfEdgePrev(faceHalfEdge); + int faceHalfEdgeTwin = getHalfEdgeTwin(faceHalfEdge); + + if (faceHalfEdgePrev == faceHalfEdgeTwin) { + deleteEdgeInternal_(faceHalfEdge); //deletes the edge and its twin + removed = true; + + if (nextClusterEdge == faceHalfEdge || nextClusterEdge == faceHalfEdgeTwin) + nextClusterEdge = -1; //deleted the hint edge + + if (faceHalfEdge == firstHalfEdge || faceHalfEdgePrev == firstHalfEdge) { + firstHalfEdge = faceHalfEdgeNext; + if (faceHalfEdge == firstHalfEdge || faceHalfEdgePrev == firstHalfEdge) { + //deleted all edges in a face + break; + } + + faceHalfEdge = faceHalfEdgeNext; + continue; + } + + } else { + setHalfEdgeUserIndex(faceHalfEdge, visitedIndex, 1); + } + + faceHalfEdge = faceHalfEdgeNext; + if (faceHalfEdge == firstHalfEdge) + break; + } + + } + } + + return removed; + } + + void setEditShapeImpl_(EditShape shape, int inputMode, + AttributeStreamOfInt32 editShapeGeometries, + ProgressTracker progress_tracker, boolean bBuildChains) { + assert (!m_dirty_check_failed); + assert (editShapeGeometries == null || editShapeGeometries.size() > 0); + + removeShape(); + m_buildChains = bBuildChains; + assert (m_shape == null); + m_shape = shape; + m_geometryIDIndex = m_shape.createGeometryUserIndex(); + // sort vertices lexicographically + // Firstly copy all vertices to an array. + AttributeStreamOfInt32 verticesSorter = new AttributeStreamOfInt32(0); + verticesSorter.reserve(editShapeGeometries != null ? m_shape + .getPointCount(editShapeGeometries.get(0)) : m_shape + .getTotalPointCount()); + int path_count = 0; + int geomID = 1; + {// scope + int geometry = editShapeGeometries != null ? editShapeGeometries + .get(0) : m_shape.getFirstGeometry(); + int ind = 1; + while (geometry != -1) { + m_shape.setGeometryUserIndex(geometry, m_geometryIDIndex, + geomID); + geomID = geomID << 1; + for (int path = m_shape.getFirstPath(geometry); path != -1; path = m_shape + .getNextPath(path)) { + int vertex = m_shape.getFirstVertex(path); + for (int index = 0, n = m_shape.getPathSize(path); index < n; index++) { + verticesSorter.add(vertex); + vertex = m_shape.getNextVertex(vertex); + } + } + + if (!Geometry.isPoint(m_shape.getGeometryType(geometry))) + path_count += m_shape.getPathCount(geometry); + + if (editShapeGeometries != null) { + geometry = ind < editShapeGeometries.size() ? editShapeGeometries + .get(ind) : -1; + ind++; + } else + geometry = m_shape.getNextGeometry(geometry); + } + } + + m_universe_geomID = geomID; + + m_pointCount = verticesSorter.size(); + + // sort + m_shape.sortVerticesSimpleByY_(verticesSorter, 0, m_pointCount); + + if (m_clusterVertices == null) { + m_clusterVertices = new StridedIndexTypeCollection(2); + m_clusterData = new StridedIndexTypeCollection(8); + m_halfEdgeData = new StridedIndexTypeCollection(8); + m_chainData = new StridedIndexTypeCollection(8); + } + + m_clusterVertices.setCapacity(m_pointCount); + + ProgressTracker.checkAndThrow(progress_tracker); + + m_clusterData.setCapacity(m_pointCount + 10);// 10 for some self + // intersections + m_halfEdgeData.setCapacity(2 * m_pointCount + 32); + m_chainData.setCapacity(Math.max((int) 32, path_count)); + + // create all clusters + assert (m_clusterIndex == -1);// cleanup was incorrect + m_clusterIndex = m_shape.createUserIndex(); + Point2D ptFirst = new Point2D(); + int ifirst = 0; + Point2D pt = new Point2D(); + ptFirst.setNaN(); + for (int i = 0; i <= m_pointCount; i++) { + if (i < m_pointCount) { + int vertex = verticesSorter.get(i); + m_shape.getXY(vertex, pt); + } else { + pt.setNaN();// makes it to go into the following "if" statement. + } + if (!ptFirst.isEqual(pt)) { + if (ifirst < i) { + int cluster = newCluster_(); + int vertFirst = -1; + int vert = -1; + for (int ind = ifirst; ind < i; ind++) { + vert = verticesSorter.get(ind); + m_shape.setUserIndex(vert, m_clusterIndex, cluster); + + // add vertex to the cluster's vertex list + int vertIndex = m_clusterVertices.newElement(); + m_clusterVertices.setField(vertIndex, 0, vert); + m_clusterVertices.setField(vertIndex, 1, vertFirst); + vertFirst = vertIndex; + + int path = m_shape.getPathFromVertex(vert); + int geometry = m_shape.getGeometryFromPath(path); + int geometryID = getGeometryID(geometry); + setClusterParentage_(cluster, + getClusterParentage(cluster) | geometryID); + } + setClusterVertexIterator_(cluster, vertFirst); + setClusterVertexIndex_(cluster, + m_shape.getVertexIndex(vert)); + + if (m_lastCluster != -1) + setNextCluster_(m_lastCluster, cluster); + + setPrevCluster_(cluster, m_lastCluster); + + m_lastCluster = cluster; + if (m_firstCluster == -1) + m_firstCluster = cluster; + } + ifirst = i; + ptFirst.setCoords(pt); + } + } + + ProgressTracker.checkAndThrow(progress_tracker); + + m_tmpHalfEdgeParentageIndex = createUserIndexForHalfEdges(); + if (inputMode == EnumInputMode.enumInputModeSimplifyWinding) { + m_tmpHalfEdgeWindingNumberIndex = createUserIndexForHalfEdges(); + } + + if (inputMode == EnumInputMode.enumInputModeSimplifyAlternate) { + m_tmpHalfEdgeOddEvenNumberIndex = createUserIndexForHalfEdges(); + } + + createHalfEdges_(inputMode, verticesSorter);// For each geometry produce + // clusters and half edges + + if (m_non_simple_result.m_reason != NonSimpleResult.Reason.NotDetermined) + return; + + sortHalfEdgesByAngle_(inputMode); + if (m_non_simple_result.m_reason != NonSimpleResult.Reason.NotDetermined) + return; + + if (!NumberUtils.isNaN(m_check_dirty_planesweep_tolerance)) { + if (!check_structure_after_dirty_sweep_())// checks the edges. + { + m_dirty_check_failed = true;// set m_dirty_check_failed when an + // issue is found. We'll rerun the + // planesweep using robust crack and + // cluster approach. + return; + } + } + + buildChains_(inputMode); + if (m_non_simple_result.m_reason != NonSimpleResult.Reason.NotDetermined) + return; + + deleteUserIndexForHalfEdges(m_tmpHalfEdgeParentageIndex); + m_tmpHalfEdgeParentageIndex = -1; + + if (m_buildChains) + planeSweepParentage_(inputMode, progress_tracker); + + + simplify_(inputMode); + } + + void deleteEdgeImpl_(int half_edge) { + int halfEdgeNext = getHalfEdgeNext(half_edge); + int halfEdgePrev = getHalfEdgePrev(half_edge); + int halfEdgeTwin = getHalfEdgeTwin(half_edge); + int halfEdgeTwinNext = getHalfEdgeNext(halfEdgeTwin); + int halfEdgeTwinPrev = getHalfEdgePrev(halfEdgeTwin); + + if (halfEdgeNext != halfEdgeTwin) { + setHalfEdgeNext_(halfEdgeTwinPrev, halfEdgeNext); + setHalfEdgePrev_(halfEdgeNext, halfEdgeTwinPrev); + } + + if (halfEdgePrev != halfEdgeTwin) { + setHalfEdgeNext_(halfEdgePrev, halfEdgeTwinNext); + setHalfEdgePrev_(halfEdgeTwinNext, halfEdgePrev); + } + + int cluster_1 = getHalfEdgeOrigin(half_edge); + int clusterFirstEdge1 = getClusterHalfEdge(cluster_1); + if (clusterFirstEdge1 == half_edge) { + if (halfEdgeTwinNext != half_edge) + setClusterHalfEdge_(cluster_1, halfEdgeTwinNext); + else + setClusterHalfEdge_(cluster_1, -1);// cluster has no more edges + } + + int cluster2 = getHalfEdgeOrigin(halfEdgeTwin); + int clusterFirstEdge2 = getClusterHalfEdge(cluster2); + if (clusterFirstEdge2 == halfEdgeTwin) { + if (halfEdgeNext != halfEdgeTwin) + setClusterHalfEdge_(cluster2, halfEdgeNext); + else + setClusterHalfEdge_(cluster2, -1);// cluster has no more edges + } + + m_halfEdgeData.deleteElement(half_edge); + m_halfEdgeData.deleteElement(halfEdgeTwin); + } + + int getLeftSkipPolylines_(Treap aet, int treeNode) { + int leftNode = treeNode; + + for (; ; ) { + leftNode = aet.getPrev(leftNode); + if (leftNode != -1) { + int e = aet.getElement(leftNode); + int leftChain = getHalfEdgeChain(e); + if (leftChain != getHalfEdgeChain(getHalfEdgeTwin(e))) { + return e; + } else { + // the left edge is a piece of polyline - does not + // contribute to the face parentage + } + } else { + return -1; + } + } + } + + TopoGraph() { + c_edgeParentageMask = ((int) -1) + ^ ((int) 1 << (NumberUtils.sizeOf((int) 0) * 8 - 1)); + c_edgeBitMask = (int) 1 << (NumberUtils.sizeOf((int) 0) * 8 - 1); + m_firstCluster = -1; + m_lastCluster = -1; + m_geometryIDIndex = -1; + m_clusterIndex = -1; + m_halfEdgeIndex = -1; + m_universeChain = -1; + m_tmpHalfEdgeParentageIndex = -1; + m_tmpHalfEdgeWindingNumberIndex = -1; + m_pointCount = 0; + } + + EditShape getShape() { + return m_shape; + } + + // Sets an edit shape. The geometry has to be cracked and clustered before + // calling this! + void setEditShape(EditShape shape, ProgressTracker progress_tracker) { + setEditShapeImpl_(shape, EnumInputMode.enumInputModeBuildGraph, null, + progress_tracker, true); + } + + void setEditShape(EditShape shape, ProgressTracker progress_tracker, boolean bBuildChains) { + setEditShapeImpl_(shape, EnumInputMode.enumInputModeBuildGraph, null, + progress_tracker, bBuildChains); + } + + void setAndSimplifyEditShapeAlternate(EditShape shape, int geometry, ProgressTracker progressTracker) { + AttributeStreamOfInt32 geoms = new AttributeStreamOfInt32(0); + geoms.add(geometry); + setEditShapeImpl_(shape, EnumInputMode.enumInputModeSimplifyAlternate, + geoms, progressTracker, shape.getGeometryType(geometry) == Geometry.Type.Polygon.value()); + } + + void setAndSimplifyEditShapeWinding(EditShape shape, int geometry, ProgressTracker progressTracker) { + AttributeStreamOfInt32 geoms = new AttributeStreamOfInt32(0); + geoms.add(geometry); + setEditShapeImpl_(shape, EnumInputMode.enumInputModeSimplifyWinding, + geoms, progressTracker, true); + } + + // Removes shape from the topograph and removes any user index created on + // the edit shape. + void removeShape() { + if (m_shape == null) + return; + + if (m_geometryIDIndex != -1) { + m_shape.removeGeometryUserIndex(m_geometryIDIndex); + m_geometryIDIndex = -1; + } + + if (m_clusterIndex != -1) { + m_shape.removeUserIndex(m_clusterIndex); + m_clusterIndex = -1; + } + + if (m_halfEdgeIndex != -1) { + m_shape.removeUserIndex(m_halfEdgeIndex); + m_halfEdgeIndex = -1; + } + + if (m_tmpHalfEdgeParentageIndex != -1) { + deleteUserIndexForHalfEdges(m_tmpHalfEdgeParentageIndex); + m_tmpHalfEdgeParentageIndex = -1; + } + + if (m_tmpHalfEdgeWindingNumberIndex != -1) { + deleteUserIndexForHalfEdges(m_tmpHalfEdgeWindingNumberIndex); + m_tmpHalfEdgeWindingNumberIndex = -1; + } + + if (m_tmpHalfEdgeOddEvenNumberIndex != -1) { + deleteUserIndexForHalfEdges(m_tmpHalfEdgeOddEvenNumberIndex); + m_tmpHalfEdgeOddEvenNumberIndex = -1; + } + + m_shape = null; + m_clusterData.deleteAll(true); + m_clusterVertices.deleteAll(true); + m_firstCluster = -1; + m_lastCluster = -1; + + if (m_halfEdgeData != null) + m_halfEdgeData.deleteAll(true); + if (m_edgeIndices != null) + m_edgeIndices.clear(); + if (m_clusterIndices != null) + m_clusterIndices.clear(); + if (m_chainIndices != null) + m_chainIndices.clear(); + if (m_chainData != null) + m_chainData.deleteAll(true); + m_universeChain = -1; + m_chainAreas = null; + } + + // Returns a half-edge emanating the cluster. All other half-edges can be + // visited with: + // incident_half_edge = getHalfEdgeTwin(half_edge);//get twin of the + // half_edge, it has the vertex as the end point. + // emanating_half_edge = getHalfEdgeTwin(incident_half_edge); //get next + // emanating half-edge + int getClusterHalfEdge(int cluster) { + return m_clusterData.getField(cluster, 2); + } + + // Returns the coordinates of the cluster + void getXY(int cluster, Point2D pt) { + int vindex = getClusterVertexIndex_(cluster); + m_shape.getXYWithIndex(vindex, pt); + } + + // Returns parentage mask of the cluster + int getClusterParentage(int cluster) { + return m_clusterData.getField(cluster, 1); + } + + // Returns first cluster in the Topo_graph (has lowest y, x coordinates). + int getFirstCluster() { + return m_firstCluster; + } + + // Returns previous cluster in the Topo_graph (in the sorted order of y,x + // coordinates). + int getPrevCluster(int cluster) { + return m_clusterData.getField(cluster, 3); + } + + // Returns next cluster in the Topo_graph (in the sorted order of y,x + // coordinates). + int getNextCluster(int cluster) { + return m_clusterData.getField(cluster, 4); + } + + // Returns an exterior chain of a face this cluster belongs to (belongs only + // to interior). set only for the clusters that are standalone clusters (do + // not have half-edges with them). + int getClusterChain(int cluster) { + return m_clusterData.getField(cluster, 6); + } + + // Returns iterator for cluster vertices + int getClusterVertexIterator(int cluster) { + return m_clusterData.getField(cluster, 7); + } + + // Increments iterator. Returns -1 if no more vertices in the cluster + int incrementVertexIterator(int vertexIterator) { + return m_clusterVertices.getField(vertexIterator, 1); + } + + // Dereference the iterator + int getVertexFromVertexIterator(int vertexIterator) { + return m_clusterVertices.getField(vertexIterator, 0); + } + + // Returns a user index value for the cluster. + int getClusterUserIndex(int cluster, int index) { + int i = getClusterIndex_(cluster); + AttributeStreamOfInt32 stream = m_clusterIndices.get(index); + if (stream.size() <= i) + return -1; + return stream.read(i); + } + + // Sets a user index value for the cluster. + void setClusterUserIndex(int cluster, int index, int value) { + int i = getClusterIndex_(cluster); + AttributeStreamOfInt32 stream = m_clusterIndices.get(index); + if (stream.size() <= i) + stream.resize(m_clusterData.size(), -1); + + stream.write(i, value); + } + + // Creates a new user index for the cluster. The index values are set to -1. + int createUserIndexForClusters() { + if (m_clusterIndices == null) { + m_clusterIndices = new ArrayList(3); + } + + AttributeStreamOfInt32 new_stream = new AttributeStreamOfInt32( + m_clusterData.capacity(), -1); + for (int i = 0, n = m_clusterIndices.size(); i < n; i++) { + if (m_clusterIndices.get(i) == null) { + m_clusterIndices.set(i, new_stream); + return i; + } + } + m_clusterIndices.add(new_stream); + return m_clusterIndices.size() - 1; + } + + // Deletes user index + void deleteUserIndexForClusters(int userIndex) { + assert (m_clusterIndices.get(userIndex) != null); + m_clusterIndices.set(userIndex, null); + } + + // Returns origin of this half edge. To get the other end: + // incident_half_edge = getHalfEdgeTwin(half_edge); + // edge_end_point = getHalfEdgeOrigin(incident_half_edge); + int getHalfEdgeOrigin(int half_edge) { + return m_halfEdgeData.getField(half_edge, 1); + } + + // Returns the to point of the half edge + int getHalfEdgeTo(int half_edge) { + return getHalfEdgeOrigin(getHalfEdgeTwin(half_edge)); + } + + // Twin of this halfedge, it has opposite direction and same endpoints + int getHalfEdgeTwin(int half_edge) { + return m_halfEdgeData.getField(half_edge, 4); + } + + // Returns previous halfedge. It ends, where this halfedge starts. + int getHalfEdgePrev(int half_edge) { + return m_halfEdgeData.getField(half_edge, 5); + } + + // Returns next halfedge. It starts, where this halfedge ends. + int getHalfEdgeNext(int half_edge) { + return m_halfEdgeData.getField(half_edge, 6); + } + + // Returns half edge chain. Chain is on the right from the halfedge + int getHalfEdgeChain(int half_edge) { + return m_halfEdgeData.getField(half_edge, 2); + } + + // Returns half edge chain parentage. The call is implemented as as + // getChainParentage(getHalfEdgeChain()); + int getHalfEdgeFaceParentage(int half_edge) { + return getChainParentage(m_halfEdgeData.getField(half_edge, 2)); + } + + // Returns iterator for cluster vertices + int getHalfEdgeVertexIterator(int half_edge) { + return m_halfEdgeData.getField(half_edge, 7); + } + + // Returns the coordinates of the origin of the half_edge + void getHalfEdgeFromXY(int half_edge, Point2D pt) { + getXY(getHalfEdgeOrigin(half_edge), pt); + } + + // Returns the coordinates of the end of the half_edge + void getHalfEdgeToXY(int half_edge, Point2D pt) { + getXY(getHalfEdgeTo(half_edge), pt); + } + + // Returns parentage mask of this halfedge. Parentage mask of halfedge and + // its twin are the same + int getHalfEdgeParentage(int half_edge) { + return m_halfEdgeData.getField(half_edge, 3) & c_edgeParentageMask; + } + + // Returns a user index value for the half edge + int getHalfEdgeUserIndex(int half_edge, int index) { + int i = getHalfEdgeIndex_(half_edge); + AttributeStreamOfInt32 stream = m_edgeIndices.get(index); + if (stream.size() <= i) + return -1; + + return stream.read(i); + } + + // Sets a user index value for a half edge + void setHalfEdgeUserIndex(int half_edge, int index, int value) { + int i = getHalfEdgeIndex_(half_edge); + AttributeStreamOfInt32 stream = m_edgeIndices.get(index); + if (stream.size() <= i) + stream.resize(m_halfEdgeData.size(), -1); + + stream.write(i, value); + } + + // create a new user index for half edges. The index values are set to -1. + int createUserIndexForHalfEdges() { + if (m_edgeIndices == null) + m_edgeIndices = new ArrayList(3); + + AttributeStreamOfInt32 new_stream = new AttributeStreamOfInt32( + m_halfEdgeData.capacity(), -1); + for (int i = 0, n = m_edgeIndices.size(); i < n; i++) { + if (m_edgeIndices.get(i) == null) { + m_edgeIndices.set(i, new_stream); + return i; + } + } + m_edgeIndices.add(new_stream); + return m_edgeIndices.size() - 1; + } + + // Deletes the given user index for half edges + void deleteUserIndexForHalfEdges(int userIndex) { + assert (m_edgeIndices.get(userIndex) != null); + m_edgeIndices.set(userIndex, null); + } + + // Deletes the half_edge and it's twin. It works presently when removing a + // spike only. + // Returns next valid half-edge, or -1 if no more half edges. + // Use with care. + int deleteEdgeInternal_(int half_edge) { + int chain = getHalfEdgeChain(half_edge); + int halfEdgeTwin = getHalfEdgeTwin(half_edge); + int chainTwin = getHalfEdgeChain(halfEdgeTwin); + // This function only works for spikes. These two asserts check for that + assert (chainTwin == chain); + assert (half_edge == getHalfEdgeNext(halfEdgeTwin) || halfEdgeTwin == getHalfEdgeNext(half_edge)); + + int n = getHalfEdgeNext(half_edge); + if (n == halfEdgeTwin) { + n = getHalfEdgeNext(n); + if (n == half_edge) + n = -1; + } + + if (getChainHalfEdge(chain) == half_edge) { + setChainHalfEdge_(chain, n); + } + + int chainIndex = getChainIndex_(chain); + double v = m_chainAreas.read(chainIndex); + if (!NumberUtils.isNaN(v)) { + setChainArea_(chain, NumberUtils.TheNaN); + setChainPerimeter_(chain, NumberUtils.TheNaN); + } + + updateVertexToHalfEdgeConnection_(half_edge, true); + + deleteEdgeImpl_(half_edge);// does not change chain information + return n; + } + + // Deletes the halfEdges and their twin. The chains are broken after this + // call. + // For every chain the halfedges belong to, it will set the first edge to + // -1. + // However, the halfedge will still reference the chain so one can get the + // parentage information still. + void deleteEdgesBreakFaces_(AttributeStreamOfInt32 edgesToDelete) { + for (int i = 0, n = edgesToDelete.size(); i < n; i++) { + int half_edge = edgesToDelete.get(i); + int chain = getHalfEdgeChain(half_edge); + int halfEdgeTwin = getHalfEdgeTwin(half_edge); + int chainTwin = getHalfEdgeChain(halfEdgeTwin); + setChainHalfEdge_(chain, -1); + setChainHalfEdge_(chainTwin, -1); + updateVertexToHalfEdgeConnection_(half_edge, true); + deleteEdgeImpl_(half_edge); + } + } + + boolean doesHalfEdgeBelongToAPolygonInterior(int half_edge, int polygonId) { + // Half edge belongs to polygon interior if both it and its twin belong + // to boundary of faces that have the polygon parentage (the poygon both + // to the left and to the right of the edge). + int p_1 = getHalfEdgeFaceParentage(half_edge); + int p_2 = getHalfEdgeFaceParentage(getHalfEdgeTwin(half_edge)); + return (p_1 & polygonId) != 0 && (p_2 & polygonId) != 0; + } + + boolean doesHalfEdgeBelongToAPolygonExterior(int half_edge, int polygonId) { + // Half edge belongs to polygon interior if both it and its twin belong + // to boundary of faces that have the polygon parentage (the poygon both + // to the left and to the right of the edge). + int p_1 = getHalfEdgeFaceParentage(half_edge); + int p_2 = getHalfEdgeFaceParentage(getHalfEdgeTwin(half_edge)); + return (p_1 & polygonId) == 0 && (p_2 & polygonId) == 0; + } + + boolean doesHalfEdgeBelongToAPolygonBoundary(int half_edge, int polygonId) { + // Half edge overlaps polygon boundary + int p_1 = getHalfEdgeParentage(half_edge); + return (p_1 & polygonId) != 0; + } + + boolean doesHalfEdgeBelongToAPolylineInterior(int half_edge, int polylineId) { + // Half-edge belongs to a polyline interioir if it has the polyline + // parentage (1D intersection (aka overlap)). + int p_1 = getHalfEdgeParentage(half_edge); + if ((p_1 & polylineId) != 0) { + return true; + } + + return false; + } + + boolean doesHalfEdgeBelongToAPolylineExterior(int half_edge, int polylineId) { + // Half-edge belongs to a polyline Exterioir if it does not have the + // polyline parentage and both its clusters also do not have polyline's + // parentage (to exclude touch at point). + int p_1 = getHalfEdgeParentage(half_edge); + if ((p_1 & polylineId) == 0) { + int c = getHalfEdgeOrigin(half_edge); + int pc = getClusterParentage(c); + if ((pc & polylineId) == 0) { + c = getHalfEdgeTo(half_edge); + pc = getClusterParentage(c); + if ((pc & polylineId) == 0) { + return true; + } + } + } + + return false; + } + + boolean doesClusterBelongToAPolygonInterior(int cluster, int polygonId) { + // cluster belongs to a polygon interior when + // 1) It is a standalone cluster that has face parentage of this polygon + // GetClusterFaceParentage() + // 2) or It is a cluster with half edges attached and + // a) It is not on the polygon boundrary (get_cluster_parentage) + // b) Any half edge associated with it has face parentage of the polygon + // (get_half_edge_face_parentage(getClusterHalfEdge())) + + int chain = getClusterChain(cluster); + if (chain != -1) { + if ((getChainParentage(chain) & polygonId) != 0) { + return true; + } + } else { + int p_1 = getClusterParentage(cluster); + if ((p_1 & polygonId) == 0)// not on the polygon boundary + { + int half_edge = getClusterHalfEdge(cluster); + assert (half_edge != -1); + + int p_2 = getHalfEdgeFaceParentage(half_edge); + if ((p_2 & polygonId) != 0) { + return true; + } + } + } + + return false; + } + + boolean doesClusterBelongToAPolygonExterior(int cluster, int polygonId) { + int p_1 = getClusterParentage(cluster); + if ((p_1 & polygonId) == 0) { + return doesClusterBelongToAPolygonInterior(cluster, polygonId); + } + + return false; + } + + boolean doesClusterBelongToAPolygonBoundary(int cluster, int polygonId) { + int p_1 = getClusterParentage(cluster); + if ((p_1 & polygonId) != 0) { + return true; + } + + return false; + } + + // bool DoesClusterBelongToAPolylineInterioir(int cluster, int polylineId); + // bool does_cluster_belong_to_a_polyline_exterior(int cluster, int + // polylineId); + // bool does_cluster_belong_to_a_polyline_boundary(int cluster, int + // polylineId); + + // Returns the first chain, which is always the Universe chain. + int getFirstChain() { + return m_universeChain; + } + + // Returns the chain half edge. + int getChainHalfEdge(int chain) { + return m_chainData.getField(chain, 1); + } + + // Returns the chain's face parentage. That is the parentage of a face this + // chain borders with. + int getChainParentage(int chain) { + return m_chainData.getField(chain, 2); + } + + // Returns the parent of the chain (the chain, this chain is inside of). + int getChainParent(int chain) { + return m_chainData.getField(chain, 3); + } + + // Returns the first island chain in that chain. Island chains are always + // counterclockwise. + // Each island chain will have its complement chain, which is a chain of a + // twin of any halfedge of that chain. + int getChainFirstIsland(int chain) { + return m_chainData.getField(chain, 4); + } + + // Returns the first island chain in that chain. Island chains are always + // counterclockwise. + int getChainNextInParent(int chain) { + return m_chainData.getField(chain, 5); + } + + // Returns the next chain in arbitrary order. + int getChainNext(int chain) { + return m_chainData.getField(chain, 7); + } + + // Returns the area of the chain. The area does not include any islands. + // +Inf is returned for the universe chain. + double getChainArea(int chain) { + int chainIndex = getChainIndex_(chain); + double v = m_chainAreas.read(chainIndex); + if (NumberUtils.isNaN(v)) { + updateChainAreaAndPerimeter_(chain); + v = m_chainAreas.read(chainIndex); + } + + return v; + } + + // Returns the perimeter of the chain (> 0). +Inf is returned for the + // universe chain. + double getChainPerimeter(int chain) { + int chainIndex = getChainIndex_(chain); + double v = m_chainPerimeters.read(chainIndex); + if (NumberUtils.isNaN(v)) { + updateChainAreaAndPerimeter_(chain); + v = m_chainPerimeters.read(chainIndex); + } + + return v; + } + + // Returns a user index value for the chain. + int getChainUserIndex(int chain, int index) { + int i = getChainIndex_(chain); + AttributeStreamOfInt32 stream = m_chainIndices.get(index); + if (stream.size() <= i) + return -1; + return stream.read(i); + } + + // Sets a user index value for the chain. + void setChainUserIndex(int chain, int index, int value) { + int i = getChainIndex_(chain); + AttributeStreamOfInt32 stream = m_chainIndices.get(index); + if (stream.size() <= i) + stream.resize(m_chainData.size(), -1); + + stream.write(i, value); + } + + // Creates a new user index for the chains. The index values are set to -1. + int createUserIndexForChains() { + if (m_chainIndices == null) { + m_chainIndices = new ArrayList(3); + } + + AttributeStreamOfInt32 new_stream = new AttributeStreamOfInt32( + m_chainData.capacity(), -1); + for (int i = 0, n = m_chainIndices.size(); i < n; i++) { + if (m_chainIndices.get(i) == null) { + m_chainIndices.set(i, new_stream); + return i; + } + } + m_chainIndices.add(new_stream); + return m_chainIndices.size() - 1; + } + + // Deletes user index + void deleteUserIndexForChains(int userIndex) { + assert (m_chainIndices.get(userIndex) != null); + m_chainIndices.set(userIndex, null); + } + + // Returns geometry ID mask from the geometry handle. + // Topo_graph creates a user index for geometries in the shape, which exists + // until the topo graph is destroyed. + int getGeometryID(int geometry) { + return m_shape.getGeometryUserIndex(geometry, m_geometryIDIndex); + } + + // Returns cluster from vertex handle. + // Topo_graph creates a user index for vertices in the shape to hold cluster + // handles. The index exists until the topo graph is destroyed. + int getClusterFromVertex(int vertex) { + return m_shape.getUserIndex(vertex, m_clusterIndex); + } + + int getHalfEdgeFromVertex(int vertex) { + return m_shape.getUserIndex(vertex, m_halfEdgeIndex); + } + + // Finds an edge connecting the two clusters. Returns -1 if not found. + // Could be a slow operation when valency of each cluster is high. + int getHalfEdgeConnector(int clusterFrom, int clusterTo) { + int first_edge = getClusterHalfEdge(clusterFrom); + if (first_edge == -1) + return -1; + int edge = first_edge; + int firstEdgeTo = -1; + int eTo = -1; + // Doing two loops in parallel - one on the half-edges attached to the + // clusterFrom, another - attached to clusterTo. + do { + if (getHalfEdgeTo(edge) == clusterTo) + return edge; + + if (firstEdgeTo == -1) { + firstEdgeTo = getClusterHalfEdge(clusterTo); + if (firstEdgeTo == -1) + return -1; + eTo = firstEdgeTo; + } + + if (getHalfEdgeTo(eTo) == clusterFrom) { + edge = getHalfEdgeTwin(eTo); + assert (getHalfEdgeTo(edge) == clusterTo && getHalfEdgeOrigin(edge) == clusterFrom); + return edge; + } + + edge = getHalfEdgeNext(getHalfEdgeTwin(edge)); + eTo = getHalfEdgeNext(getHalfEdgeTwin(eTo)); + } while (edge != first_edge && eTo != firstEdgeTo); + + return -1; + } + + // Queries segment for the edge (only xy coordinates, no attributes) + void querySegmentXY(int half_edge, SegmentBuffer outBuffer) { + outBuffer.createLine(); + Segment seg = outBuffer.get(); + Point2D pt = new Point2D(); + getHalfEdgeFromXY(half_edge, pt); + seg.setStartXY(pt); + getHalfEdgeToXY(half_edge, pt); + seg.setEndXY(pt); + } + + int compareEdgeAngles_(int edge1, int edge2) { + if (edge1 == edge2) + return 0; + + Point2D pt_1 = new Point2D(); + getHalfEdgeToXY(edge1, pt_1); + + Point2D pt_2 = new Point2D(); + getHalfEdgeToXY(edge2, pt_2); + + if (pt_1.isEqual(pt_2)) + return 0;// overlap case + + Point2D pt10 = new Point2D(); + getHalfEdgeFromXY(edge1, pt10); + + Point2D v_1 = new Point2D(); + v_1.sub(pt_1, pt10); + Point2D v_2 = new Point2D(); + v_2.sub(pt_2, pt10); + int result = Point2D._compareVectors(v_1, v_2); + return result; + } + + int compareEdgeAnglesForPair_(int edge1, int edge2) { + if (edge1 == edge2) + return 0; + + Point2D pt_1 = new Point2D(); + getHalfEdgeToXY(edge1, pt_1); + + Point2D pt_2 = new Point2D(); + getHalfEdgeToXY(edge2, pt_2); + + if (pt_1.isEqual(pt_2)) + return 0;// overlap case + + Point2D pt10 = new Point2D(); + getHalfEdgeFromXY(edge1, pt10); + + Point2D v_1 = new Point2D(); + v_1.sub(pt_1, pt10); + Point2D v_2 = new Point2D(); + v_2.sub(pt_2, pt10); + + if (v_2.y >= 0 && v_1.y > 0) { + int result = Point2D._compareVectors(v_1, v_2); + return result; + } else { + return 0; + } + } + + boolean check_structure_after_dirty_sweep_() { + // for each cluster go through the cluster half edges and check that + // min(edge1_length, edge2_length) * angle_between is less than + // m_check_dirty_planesweep_tolerance. + // Doing this helps us weed out cases missed by the dirty plane sweep. + // We do not need absolute accuracy here. + assert (!m_dirty_check_failed); + assert (!NumberUtils.isNaN(m_check_dirty_planesweep_tolerance)); + double sqr_tol = MathUtils.sqr(m_check_dirty_planesweep_tolerance); + Point2D pt10 = new Point2D(); + Point2D pt_2 = new Point2D(); + Point2D pt_1 = new Point2D(); + Point2D v_1 = new Point2D(); + Point2D v_2 = new Point2D(); + for (int cluster = getFirstCluster(); cluster != -1; cluster = getNextCluster(cluster)) { + int first = getClusterHalfEdge(cluster); + if (first != -1) { + int edge = first; + getHalfEdgeFromXY(edge, pt10); + getHalfEdgeToXY(edge, pt_2); + v_2.sub(pt_2, pt10); + double sqr_len2 = v_2.sqrLength(); + + do { + int prev = edge; + edge = getHalfEdgeNext(getHalfEdgeTwin(edge)); + + if (edge != prev) { + getHalfEdgeToXY(edge, pt_1); + assert (!pt_1.isEqual(pt_2)); + v_1.sub(pt_1, pt10); + double sqr_len1 = v_1.sqrLength(); + + double cross = v_1.crossProduct(v_2); // cross_prod = + // len1 * len2 * + // sinA => sinA + // = cross_prod + // / (len1 * + // len2); + double sqr_sinA = (cross * cross) + / (sqr_len1 * sqr_len2); // sqr_sinA is + // approximately A^2 + // especially for + // smaller angles + double sqr_dist = Math.min(sqr_len1, sqr_len2) + * sqr_sinA; + if (sqr_dist <= sqr_tol) { + // these edges incident on the cluster form a narrow + // wedge and thei require cracking event that was + // missed. + return false; + } + + v_2.setCoords(v_1); + sqr_len2 = sqr_len1; + pt_2.setCoords(pt_1); + } + } while (edge != first); + } + } + + return true; + } } diff --git a/src/main/java/com/esri/core/geometry/TopologicalOperations.java b/src/main/java/com/esri/core/geometry/TopologicalOperations.java index 01e9e97f..b5b800c6 100644 --- a/src/main/java/com/esri/core/geometry/TopologicalOperations.java +++ b/src/main/java/com/esri/core/geometry/TopologicalOperations.java @@ -30,2126 +30,2126 @@ import java.util.ArrayList; final class TopologicalOperations { - TopoGraph m_topo_graph = null; - Point2D m_dummy_pt_1 = new Point2D(); - Point2D m_dummy_pt_2 = new Point2D(); - int m_from_edge_for_polylines; - boolean m_mask_lookup[] = null; - boolean m_bOGCOutput = false; - - boolean isGoodParentage(int parentage) { - return parentage < m_mask_lookup.length ? m_mask_lookup[parentage] - : false; - } - - void cut(int sideIndex, int cuttee, int cutter, - AttributeStreamOfInt32 cutHandles) { - int gtCuttee = m_topo_graph.getShape().getGeometryType(cuttee); - int gtCutter = m_topo_graph.getShape().getGeometryType(cutter); - int dimCuttee = Geometry.getDimensionFromType(gtCuttee); - int dimCutter = Geometry.getDimensionFromType(gtCutter); - - if (dimCuttee == 2 && dimCutter == 1) { - cutPolygonPolyline_(sideIndex, cuttee, cutter, cutHandles); - return; - } - - throw GeometryException.GeometryInternalError(); - } - - static final class CompareCuts extends IntComparator { - private EditShape m_editShape; - - public CompareCuts(EditShape editShape) { - m_editShape = editShape; - } - - @Override - public int compare(int c1, int c2) { - int path1 = m_editShape.getFirstPath(c1); - double area1 = m_editShape.getRingArea(path1); - int path2 = m_editShape.getFirstPath(c2); - double area2 = m_editShape.getRingArea(path2); - if (area1 < area2) - return -1; - if (area1 == area2) - return 0; - return 1; - } - } - - public TopologicalOperations() { - m_from_edge_for_polylines = -1; - } - - void setEditShape(EditShape shape, ProgressTracker progressTracker) { - if (m_topo_graph == null) - m_topo_graph = new TopoGraph(); - m_topo_graph.setEditShape(shape, progressTracker); - } - - void setEditShapeCrackAndCluster(EditShape shape, double tolerance, - ProgressTracker progressTracker) { - CrackAndCluster.execute(shape, tolerance, progressTracker, true); - for (int geometry = shape.getFirstGeometry(); geometry != -1; geometry = shape - .getNextGeometry(geometry)) { - if (shape.getGeometryType(geometry) == Geometry.Type.Polygon - .value()) - Simplificator.execute(shape, geometry, -1, m_bOGCOutput, progressTracker); - } - - setEditShape(shape, progressTracker); - } - - private void collectPolygonPathsPreservingFrom_(int geometryFrom, - int newGeometry, int visitedEdges, int visitedClusters, - int geometry_dominant) { - // This function tries to create polygon paths using the paths that were - // in the input shape. - // This way we preserve original shape as much as possible. - EditShape shape = m_topo_graph.getShape(); - if (shape.getGeometryType(geometryFrom) != Geometry.Type.Polygon - .value()) - return; - - for (int path = shape.getFirstPath(geometryFrom); path != -1; path = shape - .getNextPath(path)) { - int first_vertex = shape.getFirstVertex(path); - int firstCluster = m_topo_graph.getClusterFromVertex(first_vertex); - assert (firstCluster != -1); - int secondVertex = shape.getNextVertex(first_vertex); - int secondCluster = m_topo_graph.getClusterFromVertex(secondVertex); - assert (secondCluster != -1); - - int firstHalfEdge = m_topo_graph - .getHalfEdgeFromVertex(first_vertex); - - if (firstHalfEdge == -1) - continue;// Usually there will be a half-edge that starts at - // first_vertex and goes to secondVertex, but it - // could happen that this half edge has been - // removed. - - assert (m_topo_graph.getHalfEdgeTo(firstHalfEdge) == secondCluster && m_topo_graph - .getHalfEdgeOrigin(firstHalfEdge) == firstCluster); - - int visited = m_topo_graph.getHalfEdgeUserIndex(firstHalfEdge, - visitedEdges); - if (visited == 1 || visited == 2) - continue; - - int parentage = m_topo_graph - .getHalfEdgeFaceParentage(firstHalfEdge); - if (!isGoodParentage(parentage)) { - m_topo_graph.setHalfEdgeUserIndex(firstHalfEdge, visitedEdges, - 2); - continue; - } - - m_topo_graph.setHalfEdgeUserIndex(firstHalfEdge, visitedEdges, 1); - - int newPath = shape.insertPath(newGeometry, -1);// add new path at - // the end - int half_edge = firstHalfEdge; - int vertex = first_vertex; - int cluster = m_topo_graph.getClusterFromVertex(vertex); - int dir = 1; - //Walk the chain of half edges, preferably selecting vertices that belong to the - //polygon path we have started from. - do { - int vertex_dominant = getVertexByID_(vertex, geometry_dominant); - shape.addVertex(newPath, vertex_dominant); - if (visitedClusters != -1) - m_topo_graph.setClusterUserIndex(cluster, visitedClusters, - 1); - - m_topo_graph.setHalfEdgeUserIndex(half_edge, visitedEdges, 1); - half_edge = m_topo_graph.getHalfEdgeNext(half_edge); - int v; - int cv; - do {// move in a loop through coincident vertices (probably - // vertical segments). - v = dir == 1 ? shape.getNextVertex(vertex) : shape - .getPrevVertex(vertex);// if we came to the polyline - // tail, the next may return - // -1. - cv = v != -1 ? m_topo_graph.getClusterFromVertex(v) : -1; - } while (cv == cluster); - - int originCluster = m_topo_graph.getHalfEdgeOrigin(half_edge); - if (originCluster != cv) { - // try going opposite way - do {// move in a loop through coincident vertices (probably - // vertical segments). - v = dir == 1 ? shape.getPrevVertex(vertex) : shape - .getNextVertex(vertex);// if we came to the - // polyline tail, the - // next may return -1. - cv = v != -1 ? m_topo_graph.getClusterFromVertex(v) - : -1; - } while (cv == cluster); - - if (originCluster != cv) {// pick any vertex. - cv = originCluster; - int iterator = m_topo_graph - .getClusterVertexIterator(cv); - v = m_topo_graph.getVertexFromVertexIterator(iterator); - } else { - dir = -dir;// remember direction we were going for - // performance - } - } - cluster = cv; - vertex = v; - } while (half_edge != firstHalfEdge); - - shape.setClosedPath(newPath, true); - } - } - - // processes Topo_graph and removes edges that border faces with good - // parentage - // If bAllowBrokenFaces is True the function will break face structure for - // dissolved faces. Only face parentage will be uasable. - void dissolveCommonEdges_() { - int visitedEdges = m_topo_graph.createUserIndexForHalfEdges(); - AttributeStreamOfInt32 edgesToDelete = new AttributeStreamOfInt32(0); - // Now extract paths that - for (int cluster = m_topo_graph.getFirstCluster(); cluster != -1; cluster = m_topo_graph - .getNextCluster(cluster)) { - int firstHalfEdge = m_topo_graph.getClusterHalfEdge(cluster); - int half_edge = firstHalfEdge; - if (firstHalfEdge == -1) - continue; - - do { - int visited = m_topo_graph.getHalfEdgeUserIndex(half_edge, - visitedEdges); - if (visited != 1) { - int halfEdgeTwin = m_topo_graph.getHalfEdgeTwin(half_edge); - m_topo_graph.setHalfEdgeUserIndex(halfEdgeTwin, - visitedEdges, 1); - m_topo_graph.setHalfEdgeUserIndex(half_edge, visitedEdges, - 1); - int parentage = m_topo_graph - .getHalfEdgeFaceParentage(half_edge); - if (isGoodParentage(parentage)) { - int twinParentage = m_topo_graph - .getHalfEdgeFaceParentage(halfEdgeTwin); - if (isGoodParentage(twinParentage)) { - // This half_edge pair is a border between two faces - // that share the parentage or it is a dangling edge - edgesToDelete.add(half_edge);// remember for - // subsequent delete - } - } - } - - half_edge = m_topo_graph.getHalfEdgeNext(m_topo_graph - .getHalfEdgeTwin(half_edge)); - } while (half_edge != firstHalfEdge); - } - - m_topo_graph.deleteUserIndexForHalfEdges(visitedEdges); - m_topo_graph.deleteEdgesBreakFaces_(edgesToDelete); - } - - int getVertexByID_(int vertex, int geometry_id) { - if (geometry_id == -1) - return vertex; - - return getVertexByIDImpl_(vertex, geometry_id); - } - - int getVertexByIDImpl_(int vertex, int geometry_id) { - EditShape shape = m_topo_graph.getShape(); - int v; - int geometry; - int vertex_iterator = m_topo_graph - .getClusterVertexIterator(m_topo_graph - .getClusterFromVertex(vertex)); - - do { - v = m_topo_graph.getVertexFromVertexIterator(vertex_iterator); - geometry = shape.getGeometryFromPath(shape.getPathFromVertex(v)); - - if (geometry == geometry_id) - return v; - - vertex_iterator = m_topo_graph - .incrementVertexIterator(vertex_iterator); - } while (vertex_iterator != -1); - - return vertex; - } - - private int topoOperationPolygonPolygon_(int geometry_a, int geometry_b, - int geometry_dominant) { - dissolveCommonEdges_();// faces are partially broken after this call. - // See help to this call. - - EditShape shape = m_topo_graph.getShape(); - int newGeometry = shape.createGeometry(Geometry.Type.Polygon); - int visitedEdges = m_topo_graph.createUserIndexForHalfEdges(); - - topoOperationPolygonPolygonHelper_(geometry_a, geometry_b, newGeometry, - geometry_dominant, visitedEdges, -1); - - m_topo_graph.deleteUserIndexForHalfEdges(visitedEdges); - Simplificator.execute(shape, newGeometry, - MultiVertexGeometryImpl.GeometryXSimple.Weak, m_bOGCOutput, null); - return newGeometry; - } - - private void topoOperationPolygonPolygonHelper_(int geometry_a, - int geometry_b, int newGeometryPolygon, int geometry_dominant, - int visitedEdges, int visitedClusters) { - collectPolygonPathsPreservingFrom_(geometry_a, newGeometryPolygon, - visitedEdges, visitedClusters, geometry_dominant); - if (geometry_b != -1) - collectPolygonPathsPreservingFrom_(geometry_b, newGeometryPolygon, - visitedEdges, visitedClusters, geometry_dominant); - - EditShape shape = m_topo_graph.getShape(); - // Now extract polygon paths that has not been extracted on the previous - // step. - for (int cluster = m_topo_graph.getFirstCluster(); cluster != -1; cluster = m_topo_graph - .getNextCluster(cluster)) { - int firstHalfEdge = m_topo_graph.getClusterHalfEdge(cluster); - if (firstHalfEdge == -1) - continue; - - int half_edge = firstHalfEdge; - do { - int visited = m_topo_graph.getHalfEdgeUserIndex(half_edge, - visitedEdges); - if (visited != 1 && visited != 2) { - int parentage = m_topo_graph - .getHalfEdgeFaceParentage(half_edge); - if (isGoodParentage(parentage)) {// Extract face. - int newPath = shape.insertPath(newGeometryPolygon, -1);// add - // new - // path - // at - // the - // end - int faceHalfEdge = half_edge; - do { - int viter = m_topo_graph - .getHalfEdgeVertexIterator(faceHalfEdge); - int v; - if (viter != -1) { - v = m_topo_graph - .getVertexFromVertexIterator(viter); - } else { - int viter1 = m_topo_graph - .getHalfEdgeVertexIterator(m_topo_graph - .getHalfEdgeTwin(faceHalfEdge)); - assert (viter1 != -1); - v = m_topo_graph - .getVertexFromVertexIterator(viter1); - v = m_topo_graph.getShape().getNextVertex(v); - } - - assert (v != -1); - int vertex_dominant = getVertexByID_(v, - geometry_dominant); - shape.addVertex(newPath, vertex_dominant); - assert (isGoodParentage(m_topo_graph - .getHalfEdgeFaceParentage(faceHalfEdge))); - m_topo_graph.setHalfEdgeUserIndex(faceHalfEdge, - visitedEdges, 1);// - - if (visitedClusters != -1) { - int c = m_topo_graph - .getClusterFromVertex(vertex_dominant); - m_topo_graph.setClusterUserIndex(c, - visitedClusters, 1); - } - - faceHalfEdge = m_topo_graph - .getHalfEdgeNext(faceHalfEdge); - } while (faceHalfEdge != half_edge); - - shape.setClosedPath(newPath, true); - } else { - // cannot extract a face - m_topo_graph.setHalfEdgeUserIndex(half_edge, - visitedEdges, 2); - } - - } - - half_edge = m_topo_graph.getHalfEdgeNext(m_topo_graph - .getHalfEdgeTwin(half_edge)); - } while (half_edge != firstHalfEdge); - } - } - - int[] topoOperationPolygonPolygonEx_(int geometry_a, int geometry_b, - int geometry_dominant) { - EditShape shape = m_topo_graph.getShape(); - int newGeometryPolygon = shape.createGeometry(Geometry.Type.Polygon); - int newGeometryPolyline = shape.createGeometry(Geometry.Type.Polyline); - int newGeometryMultipoint = shape - .createGeometry(Geometry.Type.MultiPoint); - - dissolveCommonEdges_();// faces are partially broken after this call. - // See help to this call. - - int multipointPath = -1; - int visitedEdges = m_topo_graph.createUserIndexForHalfEdges(); - int visitedClusters = m_topo_graph.createUserIndexForClusters(); - - topoOperationPolygonPolygonHelper_(geometry_a, geometry_b, - newGeometryPolygon, geometry_dominant, visitedEdges, - visitedClusters); - - for (int cluster = m_topo_graph.getFirstCluster(); cluster != -1; cluster = m_topo_graph - .getNextCluster(cluster)) { - int firstHalfEdge = m_topo_graph.getClusterHalfEdge(cluster); - if (firstHalfEdge == -1) - continue; - - int half_edge = firstHalfEdge; - do { - int visited1 = m_topo_graph.getHalfEdgeUserIndex(half_edge, - visitedEdges); - int visited2 = m_topo_graph.getHalfEdgeUserIndex( - m_topo_graph.getHalfEdgeTwin(half_edge), visitedEdges); - int visited = visited1 | visited2; - if (visited == 2) { - int parentage = m_topo_graph - .getHalfEdgeParentage(half_edge); - if (isGoodParentage(parentage)) {// Extract face. - int newPath = shape.insertPath(newGeometryPolyline, -1);// add - // new - // path - // at - // the - // end - int polyHalfEdge = half_edge; - int vert = selectVertex_(cluster, shape); - assert (vert != -1); - int vertex_dominant = getVertexByID_(vert, - geometry_dominant); - shape.addVertex(newPath, vertex_dominant); - m_topo_graph.setClusterUserIndex(cluster, - visitedClusters, 1); - - do { - int clusterTo = m_topo_graph - .getHalfEdgeTo(polyHalfEdge); - int vert1 = selectVertex_(clusterTo, shape); - assert (vert1 != -1); - int vertex_dominant1 = getVertexByID_(vert1, - geometry_dominant); - shape.addVertex(newPath, vertex_dominant1); - m_topo_graph.setHalfEdgeUserIndex(polyHalfEdge, - visitedEdges, 1);// - m_topo_graph.setHalfEdgeUserIndex( - m_topo_graph.getHalfEdgeTwin(polyHalfEdge), - visitedEdges, 1);// - m_topo_graph.setClusterUserIndex(clusterTo, - visitedClusters, 1); - - polyHalfEdge = m_topo_graph - .getHalfEdgeNext(polyHalfEdge); - visited1 = m_topo_graph.getHalfEdgeUserIndex( - polyHalfEdge, visitedEdges); - visited2 = m_topo_graph.getHalfEdgeUserIndex( - m_topo_graph.getHalfEdgeTwin(polyHalfEdge), - visitedEdges); - visited = visited1 | visited2; - if (visited != 2) - break; - - parentage = m_topo_graph - .getHalfEdgeParentage(polyHalfEdge); - if (!isGoodParentage(parentage)) { - m_topo_graph.setHalfEdgeUserIndex(polyHalfEdge, - visitedEdges, 1); - m_topo_graph.setHalfEdgeUserIndex(m_topo_graph - .getHalfEdgeTwin(polyHalfEdge), - visitedEdges, 1); - break; - } - - } while (polyHalfEdge != half_edge); - - } else { - m_topo_graph.setHalfEdgeUserIndex(half_edge, - visitedEdges, 1); - m_topo_graph.setHalfEdgeUserIndex( - m_topo_graph.getHalfEdgeTwin(half_edge), - visitedEdges, 1); - } - } - - half_edge = m_topo_graph.getHalfEdgeNext(m_topo_graph - .getHalfEdgeTwin(half_edge)); - } while (half_edge != firstHalfEdge); - } - - for (int cluster = m_topo_graph.getFirstCluster(); cluster != -1; cluster = m_topo_graph - .getNextCluster(cluster)) { - int visited = m_topo_graph.getClusterUserIndex(cluster, - visitedClusters); - if (visited == 1) - continue; - - int parentage = m_topo_graph.getClusterParentage(cluster); - if (isGoodParentage(parentage)) { - if (multipointPath == -1) - multipointPath = shape - .insertPath(newGeometryMultipoint, -1); - int viter = m_topo_graph.getClusterVertexIterator(cluster); - int v; - if (viter != -1) { - v = m_topo_graph.getVertexFromVertexIterator(viter); - int vertex_dominant = getVertexByID_(v, geometry_dominant); - shape.addVertex(multipointPath, vertex_dominant); - } - } - } - - m_topo_graph.deleteUserIndexForClusters(visitedClusters); - m_topo_graph.deleteUserIndexForHalfEdges(visitedEdges); - Simplificator.execute(shape, newGeometryPolygon, - MultiVertexGeometryImpl.GeometryXSimple.Weak, m_bOGCOutput, null); - int[] result = new int[3];// always returns size 3 result. - - result[0] = newGeometryMultipoint; - result[1] = newGeometryPolyline; - result[2] = newGeometryPolygon; - return result; - } - - int selectVertex_(int cluster, EditShape shape) { - int vert = -1; - for (int iterator = m_topo_graph.getClusterVertexIterator(cluster); iterator != -1; iterator = m_topo_graph - .incrementVertexIterator(iterator)) { - int vertex = m_topo_graph.getVertexFromVertexIterator(iterator); - if (vert == -1) - vert = vertex; - int geometry = shape.getGeometryFromPath(shape - .getPathFromVertex(vertex)); - int geomID = m_topo_graph.getGeometryID(geometry); - if (isGoodParentage(geomID)) { - vert = vertex; - break; - } - } - - return vert; - } - - private double prevailingDirection_(EditShape shape, int half_edge) { - int cluster = m_topo_graph.getHalfEdgeOrigin(half_edge); - int clusterTo = m_topo_graph.getHalfEdgeTo(half_edge); - int signTotal = 0; - int signCorrect = 0; - for (int iterator = m_topo_graph.getClusterVertexIterator(cluster); iterator != -1; iterator = m_topo_graph - .incrementVertexIterator(iterator)) { - int vertex = m_topo_graph.getVertexFromVertexIterator(iterator); - int path = shape.getPathFromVertex(vertex); - int geometry = shape.getGeometryFromPath(path); - int geomID = m_topo_graph.getGeometryID(geometry); - int nextVert = shape.getNextVertex(vertex); - int prevVert = shape.getPrevVertex(vertex); - - int firstVert = shape.getFirstVertex(path); - if (firstVert == vertex) {// remember the first half edge of the - // path. We use it to produce correct - // startpath for closed polyline loops - m_from_edge_for_polylines = half_edge; - } - - if (nextVert != -1 - && m_topo_graph.getClusterFromVertex(nextVert) == clusterTo) { - signTotal++; - if (isGoodParentage(geomID)) { - if (firstVert == nextVert) {// remember the first vertex of - // the path. We use it to - // produce correct startpath for - // closed polyline loops - m_from_edge_for_polylines = m_topo_graph - .getHalfEdgeNext(half_edge); - } - - // update the sign - signCorrect++; - } - } else if (prevVert != -1 - && m_topo_graph.getClusterFromVertex(prevVert) == clusterTo) { - signTotal--; - if (isGoodParentage(geomID)) { - if (firstVert == prevVert) {// remember the first vertex of - // the path. We use it to - // produce correct startpath for - // closed polyline loops - m_from_edge_for_polylines = m_topo_graph - .getHalfEdgeNext(half_edge); - } - - // update the sign - signCorrect--; - } - } - } - - m_topo_graph.getXY(cluster, m_dummy_pt_1); - m_topo_graph.getXY(clusterTo, m_dummy_pt_2); - double len = Point2D.distance(m_dummy_pt_1, m_dummy_pt_2); - return (signCorrect != 0 ? signCorrect : signTotal) * len; - } - - int getCombinedHalfEdgeParentage_(int e) { - return m_topo_graph.getHalfEdgeParentage(e) - | m_topo_graph.getHalfEdgeFaceParentage(e) - | m_topo_graph.getHalfEdgeFaceParentage(m_topo_graph - .getHalfEdgeTwin(e)); - } - - int tryMoveThroughCrossroadBackwards_(int half_edge) { - int e = m_topo_graph.getHalfEdgeTwin(m_topo_graph - .getHalfEdgePrev(half_edge)); - int goodEdge = -1; - while (e != half_edge) { - int parentage = getCombinedHalfEdgeParentage_(e); - if (isGoodParentage(parentage)) { - if (goodEdge != -1) - return -1; - - goodEdge = e; - } - - e = m_topo_graph.getHalfEdgeTwin(m_topo_graph.getHalfEdgePrev(e)); - } - - return goodEdge != -1 ? m_topo_graph.getHalfEdgeTwin(goodEdge) : -1; - } - - int tryMoveThroughCrossroadForward_(int half_edge) { - int e = m_topo_graph.getHalfEdgeTwin(m_topo_graph - .getHalfEdgeNext(half_edge)); - int goodEdge = -1; - while (e != half_edge) { - int parentage = getCombinedHalfEdgeParentage_(e); - if (isGoodParentage(parentage)) { - if (goodEdge != -1) - return -1;// more than one way to move through the - // intersection - goodEdge = e; - } - - e = m_topo_graph.getHalfEdgeTwin(m_topo_graph.getHalfEdgeNext(e)); - } - - return goodEdge != -1 ? m_topo_graph.getHalfEdgeTwin(goodEdge) : -1; - } - - private void restorePolylineParts_(int first_edge, int newGeometry, - int visitedEdges, int visitedClusters, int geometry_dominant) { - assert (isGoodParentage(getCombinedHalfEdgeParentage_(first_edge))); - EditShape shape = m_topo_graph.getShape(); - int half_edge = first_edge; - int halfEdgeTwin = m_topo_graph.getHalfEdgeTwin(half_edge); - m_topo_graph.setHalfEdgeUserIndex(half_edge, visitedEdges, 1); - m_topo_graph.setHalfEdgeUserIndex(halfEdgeTwin, visitedEdges, 1); - double prevailingLength = prevailingDirection_(shape, half_edge);// prevailing - // direction - // is - // used - // to - // figure - // out - // the - // polyline - // direction. - // Prevailing length is the sum of the length of vectors that constitute - // the polyline. - // Vector length is positive, if the halfedge direction coincides with - // the direction of the original geometry - // and negative otherwise. - - m_from_edge_for_polylines = -1; - int fromEdge = half_edge; - int toEdge = -1; - boolean b_found_impassable_crossroad = false; - int edgeCount = 1; - while (true) { - int halfEdgePrev = m_topo_graph.getHalfEdgePrev(half_edge); - if (halfEdgePrev == halfEdgeTwin) - break;// the end of a polyline - - int halfEdgeTwinNext = m_topo_graph.getHalfEdgeNext(halfEdgeTwin); - if (m_topo_graph.getHalfEdgeTwin(halfEdgePrev) != halfEdgeTwinNext) { - // Crossroads is here. We can move through the crossroad only if - // there is only a single way to pass through. - //When doing planar_simplify we'll never go through the crossroad. - half_edge = tryMoveThroughCrossroadBackwards_(half_edge); - if (half_edge == -1) - break; - else { - b_found_impassable_crossroad = true; - halfEdgeTwin = m_topo_graph.getHalfEdgeTwin(half_edge); - } - } else { - half_edge = halfEdgePrev; - halfEdgeTwin = halfEdgeTwinNext; - } - - if (half_edge == first_edge) {// we are in a loop. No need to search - // for the toEdge. Just remember the - // toEdge and skip the next while - // loop. - toEdge = first_edge; - break; - } - int parentage = getCombinedHalfEdgeParentage_(half_edge); - if (!isGoodParentage(parentage)) - break; - - m_topo_graph.setHalfEdgeUserIndex(half_edge, visitedEdges, 1); - m_topo_graph.setHalfEdgeUserIndex(halfEdgeTwin, visitedEdges, 1); - fromEdge = half_edge; - prevailingLength += prevailingDirection_(shape, half_edge); - edgeCount++; - } - - if (toEdge == -1) { - half_edge = first_edge; - halfEdgeTwin = m_topo_graph.getHalfEdgeTwin(half_edge); - toEdge = half_edge; - while (true) { - int halfEdgeNext = m_topo_graph.getHalfEdgeNext(half_edge); - if (halfEdgeNext == halfEdgeTwin) - break; - - int halfEdgeTwinPrev = m_topo_graph - .getHalfEdgePrev(halfEdgeTwin); - if (m_topo_graph.getHalfEdgeTwin(halfEdgeNext) != halfEdgeTwinPrev) { - // Crossroads is here. We can move through the crossroad - // only if there is only a single way to pass through. - half_edge = tryMoveThroughCrossroadForward_(half_edge); - if (half_edge == -1) { - b_found_impassable_crossroad = true; - break; - } else - halfEdgeTwin = m_topo_graph.getHalfEdgeTwin(half_edge); - } else { - half_edge = halfEdgeNext; - halfEdgeTwin = halfEdgeTwinPrev; - } - - int parentage = getCombinedHalfEdgeParentage_(half_edge); - if (!isGoodParentage(parentage)) - break; - - m_topo_graph.setHalfEdgeUserIndex(half_edge, visitedEdges, 1); - m_topo_graph - .setHalfEdgeUserIndex(halfEdgeTwin, visitedEdges, 1); - toEdge = half_edge; - prevailingLength += prevailingDirection_(shape, half_edge); - edgeCount++; - } - } else { - // toEdge has been found in the first while loop. This happens when - // we go around a face. - // Closed loops need special processing as we do not know where the - // polyline started or ended. - - if (m_from_edge_for_polylines != -1) { - fromEdge = m_from_edge_for_polylines; - toEdge = m_topo_graph - .getHalfEdgePrev(m_from_edge_for_polylines);// try - // simply - // getting - // prev - int fromEdgeTwin = m_topo_graph.getHalfEdgeTwin(fromEdge); - int fromEdgeTwinNext = m_topo_graph - .getHalfEdgeNext(fromEdgeTwin); - if (m_topo_graph.getHalfEdgeTwin(toEdge) != fromEdgeTwinNext) { - // Crossroads is here. Pass through the crossroad. - toEdge = tryMoveThroughCrossroadBackwards_(fromEdge); - if (toEdge == -1) - throw GeometryException.GeometryInternalError();// what? - } - - assert (isGoodParentage(getCombinedHalfEdgeParentage_(m_from_edge_for_polylines))); - assert (isGoodParentage(getCombinedHalfEdgeParentage_(toEdge))); - } - } - - boolean dir = prevailingLength >= 0; - if (!dir) { - int e = toEdge; - toEdge = fromEdge; - fromEdge = e; - toEdge = m_topo_graph.getHalfEdgeTwin(toEdge);// switch to twin so - // that we can use - // next instead of - // Prev - assert (isGoodParentage(getCombinedHalfEdgeParentage_(toEdge))); - fromEdge = m_topo_graph.getHalfEdgeTwin(fromEdge); - assert (isGoodParentage(getCombinedHalfEdgeParentage_(fromEdge))); - } - - int newPath = shape.insertPath(newGeometry, -1);// add new path at the - // end - half_edge = fromEdge; - int cluster = m_topo_graph.getHalfEdgeOrigin(fromEdge); - int clusterLast = m_topo_graph.getHalfEdgeTo(toEdge); - boolean b_closed = clusterLast == cluster; - // The linestrings can touch at boundary points only, while closed path - // has no boundary, therefore no other path can touch it. - // Therefore, if a closed path touches another path, we need to split - // the closed path in two to make the result OGC simple. - boolean b_closed_linestring_touches_other_linestring = b_closed - && b_found_impassable_crossroad; - - int vert = selectVertex_(cluster, shape); - assert (vert != -1); - int vertex_dominant = getVertexByID_(vert, geometry_dominant); - shape.addVertex(newPath, vertex_dominant); - - if (visitedClusters != -1) { - m_topo_graph.setClusterUserIndex(cluster, visitedClusters, 1); - } - - int counter = 0; - int splitAt = b_closed_linestring_touches_other_linestring ? (edgeCount + 1) / 2 : -1; - while (true) { - int clusterTo = m_topo_graph.getHalfEdgeTo(half_edge); - int vert_1 = selectVertex_(clusterTo, shape); - vertex_dominant = getVertexByID_(vert_1, geometry_dominant); - shape.addVertex(newPath, vertex_dominant); - counter++; - if (visitedClusters != -1) { - m_topo_graph.setClusterUserIndex(clusterTo, visitedClusters, 1); - } - - if (b_closed_linestring_touches_other_linestring - && counter == splitAt) { - newPath = shape.insertPath(newGeometry, -1);// add new path at - // the end - shape.addVertex(newPath, vertex_dominant); - } - - assert (isGoodParentage(getCombinedHalfEdgeParentage_(half_edge))); - if (half_edge == toEdge) - break; - - int halfEdgeNext = m_topo_graph.getHalfEdgeNext(half_edge); - if (m_topo_graph.getHalfEdgePrev(m_topo_graph - .getHalfEdgeTwin(half_edge)) != m_topo_graph - .getHalfEdgeTwin(halfEdgeNext)) {// crossroads. - half_edge = tryMoveThroughCrossroadForward_(half_edge); - if (half_edge == -1) - throw GeometryException.GeometryInternalError();// a bug. This - // shoulf - // never - // happen - } else - half_edge = halfEdgeNext; - } - } - - private int topoOperationPolylinePolylineOrPolygon_(int geometry_dominant) { - EditShape shape = m_topo_graph.getShape(); - int newGeometry = shape.createGeometry(Geometry.Type.Polyline); - int visitedEdges = m_topo_graph.createUserIndexForHalfEdges(); - - for (int cluster = m_topo_graph.getFirstCluster(); cluster != -1; cluster = m_topo_graph - .getNextCluster(cluster)) { - int firstClusterHalfEdge = m_topo_graph.getClusterHalfEdge(cluster); - int clusterHalfEdge = firstClusterHalfEdge; - do { - int visited = m_topo_graph.getHalfEdgeUserIndex( - clusterHalfEdge, visitedEdges); - if (visited != 1) { - int parentage = getCombinedHalfEdgeParentage_(clusterHalfEdge); - if (isGoodParentage(parentage)) { - restorePolylineParts_(clusterHalfEdge, newGeometry, - visitedEdges, -1, geometry_dominant); - } else { - // - } - } - - clusterHalfEdge = m_topo_graph.getHalfEdgeNext(m_topo_graph - .getHalfEdgeTwin(clusterHalfEdge)); - } while (clusterHalfEdge != firstClusterHalfEdge); - } - - m_topo_graph.deleteUserIndexForHalfEdges(visitedEdges); - return newGeometry; - } - - int[] topoOperationPolylinePolylineOrPolygonEx_(int geometry_dominant) { - EditShape shape = m_topo_graph.getShape(); - int newPolyline = shape.createGeometry(Geometry.Type.Polyline); - int newMultipoint = shape.createGeometry(Geometry.Type.MultiPoint); - int visitedEdges = m_topo_graph.createUserIndexForHalfEdges(); - int visitedClusters = m_topo_graph.createUserIndexForClusters(); - int multipointPath = -1; - for (int cluster = m_topo_graph.getFirstCluster(); cluster != -1; cluster = m_topo_graph - .getNextCluster(cluster)) { - int firstClusterHalfEdge = m_topo_graph.getClusterHalfEdge(cluster); - int clusterHalfEdge = firstClusterHalfEdge; - do { - int visited = m_topo_graph.getHalfEdgeUserIndex( - clusterHalfEdge, visitedEdges); - if (visited != 1) { - int parentage = getCombinedHalfEdgeParentage_(clusterHalfEdge); - if (isGoodParentage(parentage)) { - restorePolylineParts_(clusterHalfEdge, newPolyline, - visitedEdges, visitedClusters, - geometry_dominant); - } else { - // - } - } - - clusterHalfEdge = m_topo_graph.getHalfEdgeNext(m_topo_graph - .getHalfEdgeTwin(clusterHalfEdge)); - } while (clusterHalfEdge != firstClusterHalfEdge); - } - - for (int cluster = m_topo_graph.getFirstCluster(); cluster != -1; cluster = m_topo_graph - .getNextCluster(cluster)) { - int visited = m_topo_graph.getClusterUserIndex(cluster, - visitedClusters); - if (visited != 1) { - int parentage = m_topo_graph.getClusterParentage(cluster); - if (isGoodParentage(parentage)) { - if (multipointPath == -1) - multipointPath = shape.insertPath(newMultipoint, -1); - - int viter = m_topo_graph.getClusterVertexIterator(cluster); - int v; - if (viter != -1) { - v = m_topo_graph.getVertexFromVertexIterator(viter); - int vertex_dominant = getVertexByID_(v, - geometry_dominant); - shape.addVertex(multipointPath, vertex_dominant); - } - } else { - // - } - } - } - - m_topo_graph.deleteUserIndexForHalfEdges(visitedEdges); - m_topo_graph.deleteUserIndexForClusters(visitedClusters); - int[] result = new int[2]; - result[0] = newMultipoint; - result[1] = newPolyline; - return result; - } - - private int topoOperationMultiPoint_() { - EditShape shape = m_topo_graph.getShape(); - int newGeometry = shape.createGeometry(Geometry.Type.MultiPoint); - int newPath = shape.insertPath(newGeometry, -1);// add new path at the - // end - - // Now extract paths that - for (int cluster = m_topo_graph.getFirstCluster(); cluster != -1; cluster = m_topo_graph - .getNextCluster(cluster)) { - int parentage = m_topo_graph.getClusterParentage(cluster); - if (isGoodParentage(parentage)) { - int vert = -1; - for (int iterator = m_topo_graph - .getClusterVertexIterator(cluster); iterator != -1; iterator = m_topo_graph - .incrementVertexIterator(iterator)) { - int vertex = m_topo_graph - .getVertexFromVertexIterator(iterator); - if (vert == -1) - vert = vertex; - int geometry = shape.getGeometryFromPath(shape - .getPathFromVertex(vertex)); - int geomID = m_topo_graph.getGeometryID(geometry); - if (isGoodParentage(geomID)) { - vert = vertex; - break; - } - } - assert (vert != -1); - shape.addVertex(newPath, vert); - } - } - - return newGeometry; - } - - void initMaskLookupArray_(int len) { - m_mask_lookup = new boolean[len]; - for (int i = 0; i < len; i++) { - m_mask_lookup[i] = false; - } - } - - static MultiPoint processMultiPointIntersectOrDiff_(MultiPoint multi_point, - Geometry intersector, double tolerance, boolean bClipIn) { - MultiPoint multi_point_out = ((MultiPoint) multi_point.createInstance()); - Point2D[] input_points = new Point2D[1000]; - PolygonUtils.PiPResult[] test_results = new PolygonUtils.PiPResult[1000]; - int npoints = multi_point.getPointCount(); - boolean bFirstOut = true; - boolean bArea = (intersector.getDimension() == 2); - if (intersector.getDimension() != 1 && intersector.getDimension() != 2) - throw GeometryException.GeometryInternalError(); - - for (int ipoints = 0; ipoints < npoints; ) { - int num = multi_point.queryCoordinates(input_points, 1000, ipoints, - -1) - ipoints; - - if (bArea) - PolygonUtils.testPointsInArea2D(intersector, input_points, - (int) num, tolerance, test_results); - else - PolygonUtils.testPointsOnLine2D(intersector, input_points, - (int) num, tolerance, test_results); - int i0 = 0; - for (int i = 0; i < num; i++) { - boolean bTest = test_results[i] == PolygonUtils.PiPResult.PiPOutside; - if (!bClipIn) - bTest = !bTest; - - if (bTest) { - if (bFirstOut) { - bFirstOut = false; - multi_point_out.add(multi_point, 0, ipoints); - } - - if (i0 != i) - multi_point_out.add(multi_point, ipoints + i0, ipoints - + i); - - i0 = i + 1; - } - } - - if (!bFirstOut && i0 != num) - multi_point_out.add(multi_point, ipoints + i0, ipoints + num); - - ipoints += num; - } - - if (bFirstOut) - return multi_point; - - return multi_point_out; - } - - static MultiPoint intersection(MultiPoint multi_point, Geometry multi_path, - double tolerance) { - return processMultiPointIntersectOrDiff_(multi_point, multi_path, - tolerance, true); - } - - static MultiPoint difference(MultiPoint multi_point, Geometry multi_path, - double tolerance) { - return processMultiPointIntersectOrDiff_(multi_point, multi_path, - tolerance, false); - } - - static Point processPointIntersectOrDiff_(Point point, - Geometry intersector, double tolerance, boolean bClipIn) { - if (point.isEmpty()) - return ((Point) point.createInstance()); - if (intersector.isEmpty()) { - return bClipIn ? ((Point) point.createInstance()) : null; - } - - Point2D[] input_points = new Point2D[1]; - PolygonUtils.PiPResult[] test_results = new PolygonUtils.PiPResult[1]; - boolean bArea = intersector.getDimension() == 2; - if (intersector.getDimension() != 1 && intersector.getDimension() != 2) - throw GeometryException.GeometryInternalError(); - input_points[0] = point.getXY(); - if (bArea) - PolygonUtils.testPointsInArea2D(intersector, input_points, 1, - tolerance, test_results); - else - PolygonUtils.testPointsOnLine2D(intersector, input_points, 1, - tolerance, test_results); - - boolean bTest = test_results[0] == PolygonUtils.PiPResult.PiPOutside; - if (!bClipIn) - bTest = !bTest; - - if (!bTest) - return point; - else - return ((Point) point.createInstance()); - } - - static Point intersection(Point point, Geometry geom, double tolerance) { - return processPointIntersectOrDiff_(point, geom, tolerance, true); - } - - static Point difference(Point point, Geometry geom, double tolerance) { - return processPointIntersectOrDiff_(point, geom, tolerance, false); - } - - static Point intersection(Point point, Point point2, double tolerance) { - if (point.isEmpty() || point2.isEmpty()) - return (Point) point.createInstance(); - - if (CrackAndCluster.non_empty_points_need_to_cluster(tolerance, point, - point2)) { - return CrackAndCluster.cluster_non_empty_points(point, point2, 1, - 1, 1, 1); - } - - return (Point) point.createInstance(); - } - - static Point difference(Point point, Point point2, double tolerance) { - if (point.isEmpty()) - return (Point) point.createInstance(); - if (point2.isEmpty()) - return point; - - if (CrackAndCluster.non_empty_points_need_to_cluster(tolerance, point, - point2)) { - return (Point) point.createInstance(); - } - - return point; - } - - MultiVertexGeometry planarSimplifyImpl_(MultiVertexGeometry input_geom, - double tolerance, boolean b_use_winding_rule_for_polygons, - boolean dirty_result, ProgressTracker progress_tracker) { - if (input_geom.isEmpty()) - return input_geom; - - EditShape shape = new EditShape(); - int geom = shape.addGeometry(input_geom); - return planarSimplify(shape, geom, tolerance, - b_use_winding_rule_for_polygons, dirty_result, progress_tracker); - } - - MultiVertexGeometry planarSimplify(EditShape shape, int geom, - double tolerance, boolean b_use_winding_rule_for_polygons, - boolean dirty_result, ProgressTracker progress_tracker) { - // This method will produce a polygon from a polyline when - // b_use_winding_rule_for_polygons is true. This is used by buffer. - m_topo_graph = new TopoGraph(); - try { - if (dirty_result - && shape.getGeometryType(geom) != Geometry.Type.MultiPoint - .value()) { - PlaneSweepCrackerHelper plane_sweeper = new PlaneSweepCrackerHelper(); - plane_sweeper.sweepVertical(shape, tolerance); - if (plane_sweeper.hadCompications())// shame. The one pass - // planesweep had some - // complications. Need to do - // full crack and cluster. - { - CrackAndCluster.execute(shape, tolerance, progress_tracker, true); - dirty_result = false; - } else { - m_topo_graph.check_dirty_planesweep(tolerance); - } - } else { - CrackAndCluster.execute(shape, tolerance, progress_tracker, true); - dirty_result = false; - } - - if (!b_use_winding_rule_for_polygons - || shape.getGeometryType(geom) == Geometry.Type.MultiPoint - .value()) - m_topo_graph.setAndSimplifyEditShapeAlternate(shape, geom, progress_tracker); - else - m_topo_graph.setAndSimplifyEditShapeWinding(shape, geom, progress_tracker); - - if (m_topo_graph.dirty_check_failed()) { - // we ran the sweep_vertical() before and it produced some - // issues that where detected by topo graph only. - assert (dirty_result); - m_topo_graph.removeShape(); - m_topo_graph = null; - // that's at most two level recursion - return planarSimplify(shape, geom, tolerance, - b_use_winding_rule_for_polygons, false, - progress_tracker); - } else { - //can proceed - } - - m_topo_graph.check_dirty_planesweep(NumberUtils.TheNaN); - - int ID_a = m_topo_graph.getGeometryID(geom); - initMaskLookupArray_((ID_a) + 1); - m_mask_lookup[ID_a] = true; // Works only when there is a single - // geometry in the edit shape. - // To make it work when many geometries are present, this need to be - // modified. - - if (shape.getGeometryType(geom) == Geometry.Type.Polygon.value() - || (b_use_winding_rule_for_polygons && shape - .getGeometryType(geom) != Geometry.Type.MultiPoint - .value())) { - // geom can be a polygon or a polyline. - // It can be a polyline only when the winding rule is true. - shape.setFillRule(geom, Polygon.FillRule.enumFillRuleOddEven); - int resGeom = topoOperationPolygonPolygon_(geom, -1, -1); - - Polygon polygon = (Polygon) shape.getGeometry(resGeom); - polygon.setFillRule(Polygon.FillRule.enumFillRuleOddEven);//standardize the fill rule. - if (!dirty_result) { - ((MultiVertexGeometryImpl) polygon._getImpl()).setIsSimple( - GeometryXSimple.Strong, tolerance, false); - ((MultiPathImpl) polygon._getImpl())._updateOGCFlags(); - } else - ((MultiVertexGeometryImpl) polygon._getImpl()).setIsSimple( - GeometryXSimple.Weak, 0.0, false);// dirty result means - // simple but with 0 - // tolerance. - - return polygon; - } else if (shape.getGeometryType(geom) == Geometry.Type.Polyline - .value()) { - int resGeom = topoOperationPolylinePolylineOrPolygon_(-1); - - Polyline polyline = (Polyline) shape.getGeometry(resGeom); - if (!dirty_result) - ((MultiVertexGeometryImpl) polyline._getImpl()).setIsSimple( - GeometryXSimple.Strong, tolerance, false); - - return polyline; - } else if (shape.getGeometryType(geom) == Geometry.Type.MultiPoint - .value()) { - int resGeom = topoOperationMultiPoint_(); - - MultiPoint mp = (MultiPoint) shape.getGeometry(resGeom); - if (!dirty_result) - ((MultiVertexGeometryImpl) mp._getImpl()).setIsSimple( - GeometryXSimple.Strong, tolerance, false); - - return mp; - } else { - throw GeometryException.GeometryInternalError(); - } - } finally { - m_topo_graph.removeShape(); - } - } - - // static - static MultiVertexGeometry planarSimplify(MultiVertexGeometry input_geom, - double tolerance, boolean use_winding_rule_for_polygons, - boolean dirty_result, ProgressTracker progress_tracker) { - TopologicalOperations topoOps = new TopologicalOperations(); - return topoOps.planarSimplifyImpl_(input_geom, tolerance, - use_winding_rule_for_polygons, dirty_result, progress_tracker); - } - - boolean planarSimplifyNoCrackingAndCluster(boolean OGCoutput, EditShape shape, int geom, ProgressTracker progress_tracker) { - m_bOGCOutput = OGCoutput; - m_topo_graph = new TopoGraph(); - int rule = shape.getFillRule(geom); - int gt = shape.getGeometryType(geom); - if (rule != Polygon.FillRule.enumFillRuleWinding || gt == GeometryType.MultiPoint) - m_topo_graph.setAndSimplifyEditShapeAlternate(shape, geom, progress_tracker); - else - m_topo_graph.setAndSimplifyEditShapeWinding(shape, geom, progress_tracker); - - if (m_topo_graph.dirty_check_failed()) - return false; - - m_topo_graph.check_dirty_planesweep(NumberUtils.TheNaN); - - int ID_a = m_topo_graph.getGeometryID(geom); - initMaskLookupArray_((ID_a) + 1); - m_mask_lookup[ID_a] = true; //Works only when there is a single geometry in the edit shape. - //To make it work when many geometries are present, this need to be modified. - - if (shape.getGeometryType(geom) == GeometryType.Polygon || (rule == Polygon.FillRule.enumFillRuleWinding && shape.getGeometryType(geom) != GeometryType.MultiPoint)) { - //geom can be a polygon or a polyline. - //It can be a polyline only when the winding rule is true. - shape.setFillRule(geom, Polygon.FillRule.enumFillRuleOddEven); - int resGeom = topoOperationPolygonPolygon_(geom, -1, -1); - shape.swapGeometry(resGeom, geom); - shape.removeGeometry(resGeom); - } else if (shape.getGeometryType(geom) == GeometryType.Polyline) { - int resGeom = topoOperationPolylinePolylineOrPolygon_(-1); - shape.swapGeometry(resGeom, geom); - shape.removeGeometry(resGeom); - } else if (shape.getGeometryType(geom) == GeometryType.MultiPoint) { - int resGeom = topoOperationMultiPoint_(); - shape.swapGeometry(resGeom, geom); - shape.removeGeometry(resGeom); - } else { - throw new GeometryException("internal error"); - } - - return true; - } - - - static MultiVertexGeometry simplifyOGC(MultiVertexGeometry input_geom, double tolerance, boolean dirty_result, ProgressTracker progress_tracker) { - TopologicalOperations topoOps = new TopologicalOperations(); - topoOps.m_bOGCOutput = true; - return topoOps.planarSimplifyImpl_(input_geom, tolerance, false, dirty_result, progress_tracker); - } - - public int difference(int geometry_a, int geometry_b) { - int gtA = m_topo_graph.getShape().getGeometryType(geometry_a); - int gtB = m_topo_graph.getShape().getGeometryType(geometry_b); - int dim_a = Geometry.getDimensionFromType(gtA); - int dim_b = Geometry.getDimensionFromType(gtB); - if (dim_a > dim_b) { - return geometry_a; - } - - int ID_a = m_topo_graph.getGeometryID(geometry_a); - int ID_b = m_topo_graph.getGeometryID(geometry_b); - initMaskLookupArray_((ID_a | ID_b) + 1); - m_mask_lookup[m_topo_graph.getGeometryID(geometry_a)] = true; - - if (dim_a == 2 && dim_b == 2) - return topoOperationPolygonPolygon_(geometry_a, geometry_b, -1); - if (dim_a == 1 && dim_b == 2) - return topoOperationPolylinePolylineOrPolygon_(-1); - if (dim_a == 1 && dim_b == 1) - return topoOperationPolylinePolylineOrPolygon_(-1); - if (dim_a == 0) - return topoOperationMultiPoint_(); - - throw GeometryException.GeometryInternalError(); - } - - int dissolve(int geometry_a, int geometry_b) { - int gtA = m_topo_graph.getShape().getGeometryType(geometry_a); - int gtB = m_topo_graph.getShape().getGeometryType(geometry_b); - int dim_a = Geometry.getDimensionFromType(gtA); - int dim_b = Geometry.getDimensionFromType(gtB); - if (dim_a > dim_b) { - return geometry_a; - } - - if (dim_a < dim_b) { - return geometry_b; - } - - int ID_a = m_topo_graph.getGeometryID(geometry_a); - int ID_b = m_topo_graph.getGeometryID(geometry_b); - initMaskLookupArray_(((ID_a | ID_b) + 1)); - - m_mask_lookup[m_topo_graph.getGeometryID(geometry_a)] = true; - m_mask_lookup[m_topo_graph.getGeometryID(geometry_b)] = true; - m_mask_lookup[m_topo_graph.getGeometryID(geometry_a) - | m_topo_graph.getGeometryID(geometry_b)] = true; - - if (dim_a == 2 && dim_b == 2) - return topoOperationPolygonPolygon_(geometry_a, geometry_b, -1); - if (dim_a == 1 && dim_b == 1) - return topoOperationPolylinePolylineOrPolygon_(-1); - if (dim_a == 0 && dim_b == 0) - return topoOperationMultiPoint_(); - - throw GeometryException.GeometryInternalError(); - } - - public int intersection(int geometry_a, int geometry_b) { - int gtA = m_topo_graph.getShape().getGeometryType(geometry_a); - int gtB = m_topo_graph.getShape().getGeometryType(geometry_b); - int dim_a = Geometry.getDimensionFromType(gtA); - int dim_b = Geometry.getDimensionFromType(gtB); - - int ID_a = m_topo_graph.getGeometryID(geometry_a); - int ID_b = m_topo_graph.getGeometryID(geometry_b); - initMaskLookupArray_(((ID_a | ID_b) + 1)); - - m_mask_lookup[m_topo_graph.getGeometryID(geometry_a) - | m_topo_graph.getGeometryID(geometry_b)] = true; - - int geometry_dominant = -1; - boolean b_vertex_dominance = (m_topo_graph.getShape() - .getVertexDescription().getAttributeCount() > 1); - if (b_vertex_dominance) - geometry_dominant = geometry_a; - - if (dim_a == 2 && dim_b == 2)// intersect two polygons - return topoOperationPolygonPolygon_(geometry_a, geometry_b, - geometry_dominant); - if ((dim_a == 1 && dim_b > 0) || (dim_b == 1 && dim_a > 0))// intersect - // polyline - // with - // polyline - // or - // polygon - return topoOperationPolylinePolylineOrPolygon_(geometry_dominant); - if (dim_a == 0 || dim_b == 0)// intersect a multipoint with something - // else - return topoOperationMultiPoint_(); - - throw GeometryException.GeometryInternalError(); - } - - int[] intersectionEx(int geometry_a, int geometry_b) { - int gtA = m_topo_graph.getShape().getGeometryType(geometry_a); - int gtB = m_topo_graph.getShape().getGeometryType(geometry_b); - int dim_a = Geometry.getDimensionFromType(gtA); - int dim_b = Geometry.getDimensionFromType(gtB); - - int ID_a = m_topo_graph.getGeometryID(geometry_a); - int ID_b = m_topo_graph.getGeometryID(geometry_b); - initMaskLookupArray_(((ID_a | ID_b) + 1)); - - m_mask_lookup[m_topo_graph.getGeometryID(geometry_a) - | m_topo_graph.getGeometryID(geometry_b)] = true; - - int geometry_dominant = -1; - boolean b_vertex_dominance = (m_topo_graph.getShape() - .getVertexDescription().getAttributeCount() > 1); - if (b_vertex_dominance) - geometry_dominant = geometry_a; - - if (dim_a == 2 && dim_b == 2)// intersect two polygons - return topoOperationPolygonPolygonEx_(geometry_a, geometry_b, - geometry_dominant); - if ((dim_a == 1 && dim_b > 0) || (dim_b == 1 && dim_a > 0))// intersect - // polyline - // with - // polyline - // or - // polygon - return topoOperationPolylinePolylineOrPolygonEx_(geometry_dominant); - if (dim_a == 0 || dim_b == 0)// intersect a multipoint with something - // else - { - int[] res = new int[1]; - res[0] = topoOperationMultiPoint_(); - return res; - } - - throw GeometryException.GeometryInternalError(); - } - - public int symmetricDifference(int geometry_a, int geometry_b) { - int gtA = m_topo_graph.getShape().getGeometryType(geometry_a); - int gtB = m_topo_graph.getShape().getGeometryType(geometry_b); - int dim_a = Geometry.getDimensionFromType(gtA); - int dim_b = Geometry.getDimensionFromType(gtB); - - int ID_a = m_topo_graph.getGeometryID(geometry_a); - int ID_b = m_topo_graph.getGeometryID(geometry_b); - initMaskLookupArray_((ID_a | ID_b) + 1); - - m_mask_lookup[m_topo_graph.getGeometryID(geometry_a)] = true; - m_mask_lookup[m_topo_graph.getGeometryID(geometry_b)] = true; - - if (dim_a == 2 && dim_b == 2) - return topoOperationPolygonPolygon_(geometry_a, geometry_b, -1); - if (dim_a == 1 && dim_b == 1) - return topoOperationPolylinePolylineOrPolygon_(-1); - if (dim_a == 0 && dim_b == 0) - return topoOperationMultiPoint_(); - - throw GeometryException.GeometryInternalError(); - } - - int extractShape(int geometry_in) { - int gtA = m_topo_graph.getShape().getGeometryType(geometry_in); - int dim_a = Geometry.getDimensionFromType(gtA); - - int ID_a = m_topo_graph.getGeometryID(geometry_in); - initMaskLookupArray_((ID_a) + 1); - m_mask_lookup[m_topo_graph.getGeometryID(geometry_in)] = true; // Works - // only - // when - // there - // is a - // single - // geometry - // in - // the - // edit - // shape. - // To make it work when many geometries are present, this need to be - // modified. - - if (dim_a == 2) - return topoOperationPolygonPolygon_(geometry_in, -1, -1); - if (dim_a == 1) - return topoOperationPolylinePolylineOrPolygon_(-1); - if (dim_a == 0) - return topoOperationMultiPoint_(); - - throw GeometryException.GeometryInternalError(); - } - - static Geometry normalizeInputGeometry_(Geometry geom) { - Geometry.Type gt = geom.getType(); - if (gt == Geometry.Type.Envelope) { - Polygon poly = new Polygon(geom.getDescription()); - if (!geom.isEmpty()) - poly.addEnvelope((Envelope) geom, false); - return poly; - } - if (gt == Geometry.Type.Point) { - MultiPoint poly = new MultiPoint(geom.getDescription()); - if (!geom.isEmpty()) - poly.add((Point) geom); - return poly; - } - if (gt == Geometry.Type.Line) { - Polyline poly = new Polyline(geom.getDescription()); - if (!geom.isEmpty()) - poly.addSegment((Segment) geom, true); - return poly; - } - - return geom; - } - - static Geometry normalizeResult_(Geometry geomRes, Geometry geom_a, - Geometry dummy, char op) { - // assert(strchr("-&^|",op) != NULL); - Geometry.Type gtRes = geomRes.getType(); - if (gtRes == Geometry.Type.Envelope) { - Polygon poly = new Polygon(geomRes.getDescription()); - if (!geomRes.isEmpty()) - poly.addEnvelope((Envelope) geomRes, false); - return poly; - } - - if (gtRes == Geometry.Type.Point && (op == '|' || op == '^')) { - MultiPoint poly = new MultiPoint(geomRes.getDescription()); - if (!geomRes.isEmpty()) - poly.add((Point) geomRes); - return poly; - } - - if (gtRes == Geometry.Type.Line) { - Polyline poly = new Polyline(geomRes.getDescription()); - if (!geomRes.isEmpty()) - poly.addSegment((Segment) geomRes, true); - return poly; - } - - if (gtRes == Geometry.Type.Point && op == '-') { - if (geom_a.getType() == Geometry.Type.Point) { - Point pt = new Point(geomRes.getDescription()); - if (!geomRes.isEmpty()) { - assert (((MultiPoint) geomRes).getPointCount() == 1); - ((MultiPoint) geomRes).getPointByVal(0, pt); - } - return pt; - } - } - - if (gtRes == Geometry.Type.MultiPoint && op == '&') { - if (geom_a.getType() == Geometry.Type.Point) { - Point pt = new Point(geomRes.getDescription()); - if (!geomRes.isEmpty()) { - assert (((MultiPoint) geomRes).getPointCount() == 1); - ((MultiPoint) geomRes).getPointByVal(0, pt); - } - return pt; - } - } - - return geomRes; - } - - // static - public static Geometry difference(Geometry geometry_a, Geometry geometry_b, - SpatialReference sr, ProgressTracker progress_tracker) { - if (geometry_a.isEmpty() || geometry_b.isEmpty() - || geometry_a.getDimension() > geometry_b.getDimension()) - return normalizeResult_(normalizeInputGeometry_(geometry_a), - geometry_a, geometry_b, '-'); - - Envelope2D env2D_1 = new Envelope2D(); - geometry_a.queryEnvelope2D(env2D_1); - Envelope2D env2D_2 = new Envelope2D(); - geometry_b.queryEnvelope2D(env2D_2); - - if (!env2D_1.isIntersecting(env2D_2)) { - return normalizeResult_(normalizeInputGeometry_(geometry_a), - geometry_a, geometry_b, '-'); - } - - Envelope2D envMerged = new Envelope2D(); - envMerged.setCoords(env2D_1); - envMerged.merge(env2D_2); - double tolerance = InternalUtils.calculateToleranceFromGeometry(sr, - envMerged, true);// conservative to have same effect as simplify - - TopologicalOperations topoOps = new TopologicalOperations(); - EditShape edit_shape = new EditShape(); - int geom_a = edit_shape - .addGeometry(normalizeInputGeometry_(geometry_a)); - int geom_b = edit_shape - .addGeometry(normalizeInputGeometry_(geometry_b)); - topoOps.setEditShapeCrackAndCluster(edit_shape, tolerance, - progress_tracker); - int result = topoOps.difference(geom_a, geom_b); - Geometry resGeom = edit_shape.getGeometry(result); - - Geometry res_geom = normalizeResult_(resGeom, geometry_a, geometry_b, - '-'); - - if (Geometry.isMultiPath(res_geom.getType().value())) { - ((MultiVertexGeometryImpl) res_geom._getImpl()).setIsSimple( - GeometryXSimple.Strong, tolerance, false); - if (res_geom.getType() == Geometry.Type.Polygon) - ((MultiPathImpl) res_geom._getImpl())._updateOGCFlags(); - } - - return res_geom; - } - - public static Geometry dissolve(Geometry geometry_a, Geometry geometry_b, - SpatialReference sr, ProgressTracker progress_tracker) { - if (geometry_a.getDimension() > geometry_b.getDimension()) - return normalizeResult_(normalizeInputGeometry_(geometry_a), - geometry_a, geometry_b, '|'); - - if (geometry_a.getDimension() < geometry_b.getDimension()) - return normalizeResult_(normalizeInputGeometry_(geometry_b), - geometry_a, geometry_b, '|'); - - if (geometry_a.isEmpty()) - return normalizeResult_(normalizeInputGeometry_(geometry_b), - geometry_a, geometry_b, '|'); - - if (geometry_b.isEmpty()) - return normalizeResult_(normalizeInputGeometry_(geometry_a), - geometry_a, geometry_b, '|'); - - Envelope2D env2D_1 = new Envelope2D(); - geometry_a.queryEnvelope2D(env2D_1); - Envelope2D env2D_2 = new Envelope2D(); - geometry_b.queryEnvelope2D(env2D_2); - - Envelope2D envMerged = new Envelope2D(); - envMerged.setCoords(env2D_1); - envMerged.merge(env2D_2); - double tolerance = InternalUtils.calculateToleranceFromGeometry(sr, - envMerged, true);// conservative to have same effect as simplify - - if (!env2D_1.isIntersecting(env2D_2.getInflated(tolerance, tolerance))) { - // TODO: add optimization here to merge two geometries if the - // envelopes do not overlap. - Geometry geom1 = normalizeInputGeometry_(geometry_a); - assert (Geometry.isMultiVertex(geom1.getType().value())); - Geometry geom2 = normalizeInputGeometry_(geometry_b); - assert (Geometry.isMultiVertex(geom2.getType().value())); - assert (geom1.getType() == geom2.getType()); - switch (geom1.getType().value()) { - case Geometry.GeometryType.MultiPoint: { - Geometry res = Geometry._clone(geom1); - ((MultiPoint) res).add((MultiPoint) geom2, 0, -1); - return res; - } - // break; - case Geometry.GeometryType.Polyline: { - Geometry res = Geometry._clone(geom1); - ((Polyline) res).add((MultiPath) geom2, false); - return res; - } - // break; - case Geometry.GeometryType.Polygon: { - Geometry res = Geometry._clone(geom1); - ((Polygon) res).add((MultiPath) geom2, false); - return res; - } - // break; - default: - throw GeometryException.GeometryInternalError(); - } - } - - TopologicalOperations topoOps = new TopologicalOperations(); - EditShape edit_shape = new EditShape(); - int geom_a = edit_shape - .addGeometry(normalizeInputGeometry_(geometry_a)); - int geom_b = edit_shape - .addGeometry(normalizeInputGeometry_(geometry_b)); - topoOps.setEditShapeCrackAndCluster(edit_shape, tolerance, - progress_tracker); - int result = topoOps.dissolve(geom_a, geom_b); - - Geometry res_geom = normalizeResult_(edit_shape.getGeometry(result), - geometry_a, geometry_b, '|'); - - if (Geometry.isMultiPath(res_geom.getType().value())) { - ((MultiVertexGeometryImpl) res_geom._getImpl()).setIsSimple( - GeometryXSimple.Strong, tolerance, false); - if (res_geom.getType() == Geometry.Type.Polygon) - ((MultiPathImpl) res_geom._getImpl())._updateOGCFlags(); - } - - return res_geom; - } - - static Geometry dissolveDirty(ArrayList geometries, - SpatialReference sr, ProgressTracker progress_tracker) { - if (geometries.size() < 2) - throw new IllegalArgumentException( - "not enough geometries to dissolve"); - - int dim = 0; - for (int i = 0, n = geometries.size(); i < n; i++) { - dim = Math.max(geometries.get(i).getDimension(), dim); - } - - Envelope2D envMerged = new Envelope2D(); - envMerged.setEmpty(); - - EditShape shape = new EditShape(); - int geom = -1; - int count = 0; - int any_index = -1; - for (int i = 0, n = geometries.size(); i < n; i++) { - if (geometries.get(i).getDimension() == dim) { - if (!geometries.get(i).isEmpty()) { - any_index = i; - if (geom == -1) - geom = shape - .addGeometry(normalizeInputGeometry_(geometries - .get(i))); - else - shape.appendGeometry(geom, - normalizeInputGeometry_(geometries.get(i))); - - Envelope2D env = new Envelope2D(); - geometries.get(i).queryLooseEnvelope2D(env); - envMerged.merge(env); - count++; - } else if (any_index == -1) - any_index = i; - } - } - - if (count < 2) { - return normalizeInputGeometry_(geometries.get(any_index)); - } - - boolean winding = dim == 2; - - SpatialReference psr = dim == 0 ? sr : null;// if points, then use - // correct tolerance. - double tolerance = InternalUtils.calculateToleranceFromGeometry(psr, - envMerged, true); - TopologicalOperations topoOps = new TopologicalOperations(); - return topoOps.planarSimplify(shape, geom, tolerance, winding, true, - progress_tracker); - } - - // static - public static Geometry intersection(Geometry geometry_a, - Geometry geometry_b, SpatialReference sr, - ProgressTracker progress_tracker) { - - Envelope2D env2D_1 = new Envelope2D(); - geometry_a.queryEnvelope2D(env2D_1); - Envelope2D env2D_2 = new Envelope2D(); - geometry_b.queryEnvelope2D(env2D_2); - - Envelope2D envMerged = new Envelope2D(); - envMerged.setCoords(env2D_1); - envMerged.merge(env2D_2); - double tolerance = InternalUtils.calculateToleranceFromGeometry(sr, - envMerged, true);// conservative to have same effect as simplify - - Envelope2D e = new Envelope2D(); - e.setCoords(env2D_2); - double tol_cluster = InternalUtils - .adjust_tolerance_for_TE_clustering(tolerance); - e.inflate(tol_cluster, tol_cluster); - - if (!env2D_1.isIntersecting(e))// also includes the empty geometry - // cases - { - if (geometry_a.getDimension() <= geometry_b.getDimension()) - return normalizeResult_( - normalizeInputGeometry_(geometry_a.createInstance()), - geometry_a, geometry_b, '&'); - - if (geometry_a.getDimension() > geometry_b.getDimension()) - return normalizeResult_( - normalizeInputGeometry_(geometry_b.createInstance()), - geometry_a, geometry_b, '&'); - } - - TopologicalOperations topoOps = new TopologicalOperations(); - EditShape edit_shape = new EditShape(); - int geom_a = edit_shape - .addGeometry(normalizeInputGeometry_(geometry_a)); - int geom_b = edit_shape - .addGeometry(normalizeInputGeometry_(geometry_b)); - - topoOps.setEditShapeCrackAndCluster(edit_shape, tolerance, - progress_tracker); - int result = topoOps.intersection(geom_a, geom_b); - Geometry res_geom = normalizeResult_(edit_shape.getGeometry(result), - geometry_a, geometry_b, '&'); - - if (Geometry.isMultiPath(res_geom.getType().value())) { - ((MultiVertexGeometryImpl) res_geom._getImpl()).setIsSimple( - GeometryXSimple.Strong, tolerance, false); - if (res_geom.getType() == Geometry.Type.Polygon) - ((MultiPathImpl) res_geom._getImpl())._updateOGCFlags(); - } - - return res_geom; - } - - static Geometry[] intersectionEx(Geometry geometry_a, Geometry geometry_b, - SpatialReference sr, ProgressTracker progress_tracker) { - Geometry[] res_vec = new Geometry[3]; - - Envelope2D env2D_1 = new Envelope2D(); - geometry_a.queryEnvelope2D(env2D_1); - Envelope2D env2D_2 = new Envelope2D(); - geometry_b.queryEnvelope2D(env2D_2); - - Envelope2D envMerged = new Envelope2D(); - envMerged.setCoords(env2D_1); - envMerged.merge(env2D_2); - double tolerance = InternalUtils.calculateToleranceFromGeometry(sr, - envMerged, true);// conservative to have same effect as simplify - - Envelope2D e = new Envelope2D(); - e.setCoords(env2D_2); - double tol_cluster = InternalUtils - .adjust_tolerance_for_TE_clustering(tolerance); - e.inflate(tol_cluster, tol_cluster); - - if (!env2D_1.isIntersecting(e))// also includes the empty geometry - // cases - { - if (geometry_a.getDimension() <= geometry_b.getDimension()) { - Geometry geom = normalizeResult_( - normalizeInputGeometry_(geometry_a.createInstance()), - geometry_a, geometry_b, '&'); - res_vec[geom.getDimension()] = geom; - return res_vec; - } - - if (geometry_a.getDimension() > geometry_b.getDimension()) { - Geometry geom = normalizeResult_( - normalizeInputGeometry_(geometry_b.createInstance()), - geometry_a, geometry_b, '&'); - res_vec[geom.getDimension()] = geom; - return res_vec; - } - - } - - TopologicalOperations topoOps = new TopologicalOperations(); - EditShape edit_shape = new EditShape(); - int geom_a = edit_shape - .addGeometry(normalizeInputGeometry_(geometry_a)); - int geom_b = edit_shape - .addGeometry(normalizeInputGeometry_(geometry_b)); - - topoOps.setEditShapeCrackAndCluster(edit_shape, tolerance, - progress_tracker); - int[] result_geom_handles = topoOps.intersectionEx(geom_a, geom_b); - for (int i = 0; i < result_geom_handles.length; i++) { - Geometry res_geom = normalizeResult_( - edit_shape.getGeometry(result_geom_handles[i]), geometry_a, - geometry_b, '&'); - - if (Geometry.isMultiPath(res_geom.getType().value())) { - ((MultiVertexGeometryImpl) res_geom._getImpl()).setIsSimple( - MultiVertexGeometryImpl.GeometryXSimple.Strong, - tolerance, false); - if (res_geom.getType().value() == Geometry.GeometryType.Polygon) - ((MultiPathImpl) res_geom._getImpl())._updateOGCFlags(); - } - - res_vec[res_geom.getDimension()] = res_geom; - } - - return res_vec; - } - - // static - public static Geometry symmetricDifference(Geometry geometry_a, - Geometry geometry_b, SpatialReference sr, - ProgressTracker progress_tracker) { - if (geometry_a.getDimension() > geometry_b.getDimension()) - return normalizeResult_(normalizeInputGeometry_(geometry_a), - geometry_a, geometry_b, '^'); - - if (geometry_a.getDimension() < geometry_b.getDimension()) - return normalizeResult_(normalizeInputGeometry_(geometry_b), - geometry_a, geometry_b, '^'); - - if (geometry_a.isEmpty()) - return normalizeResult_(normalizeInputGeometry_(geometry_b), - geometry_a, geometry_b, '^'); - - if (geometry_b.isEmpty()) - return normalizeResult_(normalizeInputGeometry_(geometry_a), - geometry_a, geometry_b, '^'); - - Envelope2D env2D_1 = new Envelope2D(); - geometry_a.queryEnvelope2D(env2D_1); - Envelope2D env2D_2 = new Envelope2D(); - geometry_b.queryEnvelope2D(env2D_2); - // TODO: add optimization here to merge two geometries if the envelopes - // do not overlap. - - Envelope2D envMerged = new Envelope2D(); - envMerged.setCoords(env2D_1); - envMerged.merge(env2D_2); - double tolerance = InternalUtils.calculateToleranceFromGeometry(sr, - envMerged, true);// conservative to have same effect as simplify - - TopologicalOperations topoOps = new TopologicalOperations(); - EditShape edit_shape = new EditShape(); - int geom_a = edit_shape - .addGeometry(normalizeInputGeometry_(geometry_a)); - int geom_b = edit_shape - .addGeometry(normalizeInputGeometry_(geometry_b)); - topoOps.setEditShapeCrackAndCluster(edit_shape, tolerance, - progress_tracker); - int result = topoOps.symmetricDifference(geom_a, geom_b); - Geometry res_geom = normalizeResult_(edit_shape.getGeometry(result), - geometry_a, geometry_b, '^'); - - if (Geometry.isMultiPath(res_geom.getType().value())) { - ((MultiVertexGeometryImpl) res_geom._getImpl()).setIsSimple( - GeometryXSimple.Strong, tolerance, false); - if (res_geom.getType() == Geometry.Type.Polygon) - ((MultiPathImpl) res_geom._getImpl())._updateOGCFlags(); - } - - return res_geom; - } - - static Geometry _denormalizeGeometry(Geometry geom, Geometry geomA, - Geometry geomB) { - Geometry.Type gtA = geomA.getType(); - Geometry.Type gtB = geomB.getType(); - Geometry.Type gt = geom.getType(); - if (gt == Geometry.Type.MultiPoint) { - if (gtA == Geometry.Type.Point || gtB == Geometry.Type.Point) { - MultiPoint mp = (MultiPoint) geom; - if (mp.getPointCount() <= 1) { - Point pt = new Point(geom.getDescription()); - if (!mp.isEmpty()) - mp.getPointByVal(0, pt); - return (Geometry) pt; - } - } - } - return geom; - } - - private void flushVertices_(int geometry, AttributeStreamOfInt32 vertices) { - EditShape shape = m_topo_graph.getShape(); - int path = shape.insertPath(geometry, -1); - int size = vertices.size(); - // _ASSERT(size != 0); - for (int i = 0; i < size; i++) { - int vertex = vertices.get(i); - shape.addVertex(path, vertex); - } - shape.setClosedPath(path, true);// need to close polygon rings - } - - private void setHalfEdgeOrientations_(int orientationIndex, int cutter) { - EditShape shape = m_topo_graph.getShape(); - - for (int igeometry = shape.getFirstGeometry(); igeometry != -1; igeometry = shape - .getNextGeometry(igeometry)) { - if (igeometry != cutter) - continue; - - for (int ipath = shape.getFirstPath(igeometry); ipath != -1; ipath = shape - .getNextPath(ipath)) { - int ivertex = shape.getFirstVertex(ipath); - if (ivertex == -1) - continue; - - int ivertexNext = shape.getNextVertex(ivertex); - assert (ivertexNext != -1); - - while (ivertexNext != -1) { - int clusterFrom = m_topo_graph - .getClusterFromVertex(ivertex); - int clusterTo = m_topo_graph - .getClusterFromVertex(ivertexNext); - int half_edge = m_topo_graph.getHalfEdgeConnector( - clusterFrom, clusterTo); - - if (half_edge != -1) { - int halfEdgeTwin = m_topo_graph - .getHalfEdgeTwin(half_edge); - m_topo_graph.setHalfEdgeUserIndex(half_edge, - orientationIndex, 1); - m_topo_graph.setHalfEdgeUserIndex(halfEdgeTwin, - orientationIndex, 2); - } - - ivertex = ivertexNext; - ivertexNext = shape.getNextVertex(ivertex); - } - } - } - } - - private void processPolygonCuts_(int orientationIndex, int sideIndex, - int cuttee, int cutter) { - int idCuttee = m_topo_graph.getGeometryID(cuttee); - int idCutter = m_topo_graph.getGeometryID(cutter); - AttributeStreamOfInt32 vertices = new AttributeStreamOfInt32(0); - vertices.reserve(256); - EditShape shape = m_topo_graph.getShape(); - - int visitedIndex = m_topo_graph.createUserIndexForHalfEdges(); - for (int cluster = m_topo_graph.getFirstCluster(); cluster != -1; cluster = m_topo_graph - .getNextCluster(cluster)) { - int firstHalfEdge = m_topo_graph.getClusterHalfEdge(cluster); - - if (firstHalfEdge == -1) - continue; - - int half_edge = firstHalfEdge; - - do { - int visited = m_topo_graph.getHalfEdgeUserIndex(half_edge, - visitedIndex); - if (visited != 1) { - int faceHalfEdge = half_edge; - int toHalfEdge = half_edge; - boolean bFoundCutter = false; - int side = 0; - do { - m_topo_graph.setHalfEdgeUserIndex(faceHalfEdge, - visitedIndex, 1); - if (!bFoundCutter) { - int edgeParentage = m_topo_graph - .getHalfEdgeParentage(faceHalfEdge); - if ((edgeParentage & idCutter) != 0) { - int faceParentage = m_topo_graph - .getHalfEdgeFaceParentage(faceHalfEdge); - if ((faceParentage & idCuttee) != 0) { - toHalfEdge = faceHalfEdge;// reset the loop - bFoundCutter = true; - } - } - } - - if (bFoundCutter) { - int clusterOrigin = m_topo_graph - .getHalfEdgeOrigin(faceHalfEdge); - int iterator = m_topo_graph - .getClusterVertexIterator(clusterOrigin); - assert (iterator != -1); - int vertex = m_topo_graph - .getVertexFromVertexIterator(iterator); - vertices.add(vertex); - - // get side - if (orientationIndex != -1) { - int edgeParentage = m_topo_graph - .getHalfEdgeParentage(faceHalfEdge); - if ((edgeParentage & idCutter) != 0) { - int orientation = m_topo_graph - .getHalfEdgeUserIndex(faceHalfEdge, - orientationIndex); - assert (orientation == 1 || orientation == 2); - side |= orientation; - } - } - } - - int next = m_topo_graph.getHalfEdgeNext(faceHalfEdge); - faceHalfEdge = next; - } while (faceHalfEdge != toHalfEdge); - - if (bFoundCutter - && m_topo_graph.getChainArea(m_topo_graph - .getHalfEdgeChain(toHalfEdge)) > 0.0) {// if - // we - // found - // a - // cutter - // face - // and - // its - // area - // is - // positive, - // then - // add - // the - // cutter - // face - // as - // new - // polygon. - int geometry = shape - .createGeometry(Geometry.Type.Polygon); - flushVertices_(geometry, vertices);// adds the cutter - // face vertices to - // the new polygon - - if (sideIndex != -1) - shape.setGeometryUserIndex(geometry, sideIndex, - side); // what is that? - } - - vertices.clear(false); - } - half_edge = m_topo_graph.getHalfEdgeNext(m_topo_graph - .getHalfEdgeTwin(half_edge)); - } while (half_edge != firstHalfEdge); - } - - m_topo_graph.deleteUserIndexForHalfEdges(visitedIndex); - } - - private void cutPolygonPolyline_(int sideIndex, int cuttee, int cutter, - AttributeStreamOfInt32 cutHandles) { - m_topo_graph.removeSpikes_(); - - int orientationIndex = -1; - if (sideIndex != -1) { - orientationIndex = m_topo_graph.createUserIndexForHalfEdges(); - setHalfEdgeOrientations_(orientationIndex, cutter); - } - - processPolygonCuts_(orientationIndex, sideIndex, cuttee, cutter); - - EditShape shape = m_topo_graph.getShape(); - - int cutCount = 0; - for (int geometry_handle = shape.getFirstGeometry(); geometry_handle != -1; geometry_handle = shape - .getNextGeometry(geometry_handle)) { - if (geometry_handle != cuttee && geometry_handle != cutter) { - cutHandles.add(geometry_handle); - cutCount++; - } - } - - // sort - CompareCuts compareCuts = new CompareCuts(shape); - cutHandles.Sort(0, cutCount, compareCuts); - } - - //call this if EditShape instance has to survive the TopologicalOperations life. - void removeShape() { - if (m_topo_graph != null) { - m_topo_graph.removeShape(); - m_topo_graph = null; - } - - } + TopoGraph m_topo_graph = null; + Point2D m_dummy_pt_1 = new Point2D(); + Point2D m_dummy_pt_2 = new Point2D(); + int m_from_edge_for_polylines; + boolean m_mask_lookup[] = null; + boolean m_bOGCOutput = false; + + boolean isGoodParentage(int parentage) { + return parentage < m_mask_lookup.length ? m_mask_lookup[parentage] + : false; + } + + void cut(int sideIndex, int cuttee, int cutter, + AttributeStreamOfInt32 cutHandles) { + int gtCuttee = m_topo_graph.getShape().getGeometryType(cuttee); + int gtCutter = m_topo_graph.getShape().getGeometryType(cutter); + int dimCuttee = Geometry.getDimensionFromType(gtCuttee); + int dimCutter = Geometry.getDimensionFromType(gtCutter); + + if (dimCuttee == 2 && dimCutter == 1) { + cutPolygonPolyline_(sideIndex, cuttee, cutter, cutHandles); + return; + } + + throw GeometryException.GeometryInternalError(); + } + + static final class CompareCuts extends IntComparator { + private EditShape m_editShape; + + public CompareCuts(EditShape editShape) { + m_editShape = editShape; + } + + @Override + public int compare(int c1, int c2) { + int path1 = m_editShape.getFirstPath(c1); + double area1 = m_editShape.getRingArea(path1); + int path2 = m_editShape.getFirstPath(c2); + double area2 = m_editShape.getRingArea(path2); + if (area1 < area2) + return -1; + if (area1 == area2) + return 0; + return 1; + } + } + + public TopologicalOperations() { + m_from_edge_for_polylines = -1; + } + + void setEditShape(EditShape shape, ProgressTracker progressTracker) { + if (m_topo_graph == null) + m_topo_graph = new TopoGraph(); + m_topo_graph.setEditShape(shape, progressTracker); + } + + void setEditShapeCrackAndCluster(EditShape shape, double tolerance, + ProgressTracker progressTracker) { + CrackAndCluster.execute(shape, tolerance, progressTracker, true); + for (int geometry = shape.getFirstGeometry(); geometry != -1; geometry = shape + .getNextGeometry(geometry)) { + if (shape.getGeometryType(geometry) == Geometry.Type.Polygon + .value()) + Simplificator.execute(shape, geometry, -1, m_bOGCOutput, progressTracker); + } + + setEditShape(shape, progressTracker); + } + + private void collectPolygonPathsPreservingFrom_(int geometryFrom, + int newGeometry, int visitedEdges, int visitedClusters, + int geometry_dominant) { + // This function tries to create polygon paths using the paths that were + // in the input shape. + // This way we preserve original shape as much as possible. + EditShape shape = m_topo_graph.getShape(); + if (shape.getGeometryType(geometryFrom) != Geometry.Type.Polygon + .value()) + return; + + for (int path = shape.getFirstPath(geometryFrom); path != -1; path = shape + .getNextPath(path)) { + int first_vertex = shape.getFirstVertex(path); + int firstCluster = m_topo_graph.getClusterFromVertex(first_vertex); + assert (firstCluster != -1); + int secondVertex = shape.getNextVertex(first_vertex); + int secondCluster = m_topo_graph.getClusterFromVertex(secondVertex); + assert (secondCluster != -1); + + int firstHalfEdge = m_topo_graph + .getHalfEdgeFromVertex(first_vertex); + + if (firstHalfEdge == -1) + continue;// Usually there will be a half-edge that starts at + // first_vertex and goes to secondVertex, but it + // could happen that this half edge has been + // removed. + + assert (m_topo_graph.getHalfEdgeTo(firstHalfEdge) == secondCluster && m_topo_graph + .getHalfEdgeOrigin(firstHalfEdge) == firstCluster); + + int visited = m_topo_graph.getHalfEdgeUserIndex(firstHalfEdge, + visitedEdges); + if (visited == 1 || visited == 2) + continue; + + int parentage = m_topo_graph + .getHalfEdgeFaceParentage(firstHalfEdge); + if (!isGoodParentage(parentage)) { + m_topo_graph.setHalfEdgeUserIndex(firstHalfEdge, visitedEdges, + 2); + continue; + } + + m_topo_graph.setHalfEdgeUserIndex(firstHalfEdge, visitedEdges, 1); + + int newPath = shape.insertPath(newGeometry, -1);// add new path at + // the end + int half_edge = firstHalfEdge; + int vertex = first_vertex; + int cluster = m_topo_graph.getClusterFromVertex(vertex); + int dir = 1; + //Walk the chain of half edges, preferably selecting vertices that belong to the + //polygon path we have started from. + do { + int vertex_dominant = getVertexByID_(vertex, geometry_dominant); + shape.addVertex(newPath, vertex_dominant); + if (visitedClusters != -1) + m_topo_graph.setClusterUserIndex(cluster, visitedClusters, + 1); + + m_topo_graph.setHalfEdgeUserIndex(half_edge, visitedEdges, 1); + half_edge = m_topo_graph.getHalfEdgeNext(half_edge); + int v; + int cv; + do {// move in a loop through coincident vertices (probably + // vertical segments). + v = dir == 1 ? shape.getNextVertex(vertex) : shape + .getPrevVertex(vertex);// if we came to the polyline + // tail, the next may return + // -1. + cv = v != -1 ? m_topo_graph.getClusterFromVertex(v) : -1; + } while (cv == cluster); + + int originCluster = m_topo_graph.getHalfEdgeOrigin(half_edge); + if (originCluster != cv) { + // try going opposite way + do {// move in a loop through coincident vertices (probably + // vertical segments). + v = dir == 1 ? shape.getPrevVertex(vertex) : shape + .getNextVertex(vertex);// if we came to the + // polyline tail, the + // next may return -1. + cv = v != -1 ? m_topo_graph.getClusterFromVertex(v) + : -1; + } while (cv == cluster); + + if (originCluster != cv) {// pick any vertex. + cv = originCluster; + int iterator = m_topo_graph + .getClusterVertexIterator(cv); + v = m_topo_graph.getVertexFromVertexIterator(iterator); + } else { + dir = -dir;// remember direction we were going for + // performance + } + } + cluster = cv; + vertex = v; + } while (half_edge != firstHalfEdge); + + shape.setClosedPath(newPath, true); + } + } + + // processes Topo_graph and removes edges that border faces with good + // parentage + // If bAllowBrokenFaces is True the function will break face structure for + // dissolved faces. Only face parentage will be uasable. + void dissolveCommonEdges_() { + int visitedEdges = m_topo_graph.createUserIndexForHalfEdges(); + AttributeStreamOfInt32 edgesToDelete = new AttributeStreamOfInt32(0); + // Now extract paths that + for (int cluster = m_topo_graph.getFirstCluster(); cluster != -1; cluster = m_topo_graph + .getNextCluster(cluster)) { + int firstHalfEdge = m_topo_graph.getClusterHalfEdge(cluster); + int half_edge = firstHalfEdge; + if (firstHalfEdge == -1) + continue; + + do { + int visited = m_topo_graph.getHalfEdgeUserIndex(half_edge, + visitedEdges); + if (visited != 1) { + int halfEdgeTwin = m_topo_graph.getHalfEdgeTwin(half_edge); + m_topo_graph.setHalfEdgeUserIndex(halfEdgeTwin, + visitedEdges, 1); + m_topo_graph.setHalfEdgeUserIndex(half_edge, visitedEdges, + 1); + int parentage = m_topo_graph + .getHalfEdgeFaceParentage(half_edge); + if (isGoodParentage(parentage)) { + int twinParentage = m_topo_graph + .getHalfEdgeFaceParentage(halfEdgeTwin); + if (isGoodParentage(twinParentage)) { + // This half_edge pair is a border between two faces + // that share the parentage or it is a dangling edge + edgesToDelete.add(half_edge);// remember for + // subsequent delete + } + } + } + + half_edge = m_topo_graph.getHalfEdgeNext(m_topo_graph + .getHalfEdgeTwin(half_edge)); + } while (half_edge != firstHalfEdge); + } + + m_topo_graph.deleteUserIndexForHalfEdges(visitedEdges); + m_topo_graph.deleteEdgesBreakFaces_(edgesToDelete); + } + + int getVertexByID_(int vertex, int geometry_id) { + if (geometry_id == -1) + return vertex; + + return getVertexByIDImpl_(vertex, geometry_id); + } + + int getVertexByIDImpl_(int vertex, int geometry_id) { + EditShape shape = m_topo_graph.getShape(); + int v; + int geometry; + int vertex_iterator = m_topo_graph + .getClusterVertexIterator(m_topo_graph + .getClusterFromVertex(vertex)); + + do { + v = m_topo_graph.getVertexFromVertexIterator(vertex_iterator); + geometry = shape.getGeometryFromPath(shape.getPathFromVertex(v)); + + if (geometry == geometry_id) + return v; + + vertex_iterator = m_topo_graph + .incrementVertexIterator(vertex_iterator); + } while (vertex_iterator != -1); + + return vertex; + } + + private int topoOperationPolygonPolygon_(int geometry_a, int geometry_b, + int geometry_dominant) { + dissolveCommonEdges_();// faces are partially broken after this call. + // See help to this call. + + EditShape shape = m_topo_graph.getShape(); + int newGeometry = shape.createGeometry(Geometry.Type.Polygon); + int visitedEdges = m_topo_graph.createUserIndexForHalfEdges(); + + topoOperationPolygonPolygonHelper_(geometry_a, geometry_b, newGeometry, + geometry_dominant, visitedEdges, -1); + + m_topo_graph.deleteUserIndexForHalfEdges(visitedEdges); + Simplificator.execute(shape, newGeometry, + MultiVertexGeometryImpl.GeometryXSimple.Weak, m_bOGCOutput, null); + return newGeometry; + } + + private void topoOperationPolygonPolygonHelper_(int geometry_a, + int geometry_b, int newGeometryPolygon, int geometry_dominant, + int visitedEdges, int visitedClusters) { + collectPolygonPathsPreservingFrom_(geometry_a, newGeometryPolygon, + visitedEdges, visitedClusters, geometry_dominant); + if (geometry_b != -1) + collectPolygonPathsPreservingFrom_(geometry_b, newGeometryPolygon, + visitedEdges, visitedClusters, geometry_dominant); + + EditShape shape = m_topo_graph.getShape(); + // Now extract polygon paths that has not been extracted on the previous + // step. + for (int cluster = m_topo_graph.getFirstCluster(); cluster != -1; cluster = m_topo_graph + .getNextCluster(cluster)) { + int firstHalfEdge = m_topo_graph.getClusterHalfEdge(cluster); + if (firstHalfEdge == -1) + continue; + + int half_edge = firstHalfEdge; + do { + int visited = m_topo_graph.getHalfEdgeUserIndex(half_edge, + visitedEdges); + if (visited != 1 && visited != 2) { + int parentage = m_topo_graph + .getHalfEdgeFaceParentage(half_edge); + if (isGoodParentage(parentage)) {// Extract face. + int newPath = shape.insertPath(newGeometryPolygon, -1);// add + // new + // path + // at + // the + // end + int faceHalfEdge = half_edge; + do { + int viter = m_topo_graph + .getHalfEdgeVertexIterator(faceHalfEdge); + int v; + if (viter != -1) { + v = m_topo_graph + .getVertexFromVertexIterator(viter); + } else { + int viter1 = m_topo_graph + .getHalfEdgeVertexIterator(m_topo_graph + .getHalfEdgeTwin(faceHalfEdge)); + assert (viter1 != -1); + v = m_topo_graph + .getVertexFromVertexIterator(viter1); + v = m_topo_graph.getShape().getNextVertex(v); + } + + assert (v != -1); + int vertex_dominant = getVertexByID_(v, + geometry_dominant); + shape.addVertex(newPath, vertex_dominant); + assert (isGoodParentage(m_topo_graph + .getHalfEdgeFaceParentage(faceHalfEdge))); + m_topo_graph.setHalfEdgeUserIndex(faceHalfEdge, + visitedEdges, 1);// + + if (visitedClusters != -1) { + int c = m_topo_graph + .getClusterFromVertex(vertex_dominant); + m_topo_graph.setClusterUserIndex(c, + visitedClusters, 1); + } + + faceHalfEdge = m_topo_graph + .getHalfEdgeNext(faceHalfEdge); + } while (faceHalfEdge != half_edge); + + shape.setClosedPath(newPath, true); + } else { + // cannot extract a face + m_topo_graph.setHalfEdgeUserIndex(half_edge, + visitedEdges, 2); + } + + } + + half_edge = m_topo_graph.getHalfEdgeNext(m_topo_graph + .getHalfEdgeTwin(half_edge)); + } while (half_edge != firstHalfEdge); + } + } + + int[] topoOperationPolygonPolygonEx_(int geometry_a, int geometry_b, + int geometry_dominant) { + EditShape shape = m_topo_graph.getShape(); + int newGeometryPolygon = shape.createGeometry(Geometry.Type.Polygon); + int newGeometryPolyline = shape.createGeometry(Geometry.Type.Polyline); + int newGeometryMultipoint = shape + .createGeometry(Geometry.Type.MultiPoint); + + dissolveCommonEdges_();// faces are partially broken after this call. + // See help to this call. + + int multipointPath = -1; + int visitedEdges = m_topo_graph.createUserIndexForHalfEdges(); + int visitedClusters = m_topo_graph.createUserIndexForClusters(); + + topoOperationPolygonPolygonHelper_(geometry_a, geometry_b, + newGeometryPolygon, geometry_dominant, visitedEdges, + visitedClusters); + + for (int cluster = m_topo_graph.getFirstCluster(); cluster != -1; cluster = m_topo_graph + .getNextCluster(cluster)) { + int firstHalfEdge = m_topo_graph.getClusterHalfEdge(cluster); + if (firstHalfEdge == -1) + continue; + + int half_edge = firstHalfEdge; + do { + int visited1 = m_topo_graph.getHalfEdgeUserIndex(half_edge, + visitedEdges); + int visited2 = m_topo_graph.getHalfEdgeUserIndex( + m_topo_graph.getHalfEdgeTwin(half_edge), visitedEdges); + int visited = visited1 | visited2; + if (visited == 2) { + int parentage = m_topo_graph + .getHalfEdgeParentage(half_edge); + if (isGoodParentage(parentage)) {// Extract face. + int newPath = shape.insertPath(newGeometryPolyline, -1);// add + // new + // path + // at + // the + // end + int polyHalfEdge = half_edge; + int vert = selectVertex_(cluster, shape); + assert (vert != -1); + int vertex_dominant = getVertexByID_(vert, + geometry_dominant); + shape.addVertex(newPath, vertex_dominant); + m_topo_graph.setClusterUserIndex(cluster, + visitedClusters, 1); + + do { + int clusterTo = m_topo_graph + .getHalfEdgeTo(polyHalfEdge); + int vert1 = selectVertex_(clusterTo, shape); + assert (vert1 != -1); + int vertex_dominant1 = getVertexByID_(vert1, + geometry_dominant); + shape.addVertex(newPath, vertex_dominant1); + m_topo_graph.setHalfEdgeUserIndex(polyHalfEdge, + visitedEdges, 1);// + m_topo_graph.setHalfEdgeUserIndex( + m_topo_graph.getHalfEdgeTwin(polyHalfEdge), + visitedEdges, 1);// + m_topo_graph.setClusterUserIndex(clusterTo, + visitedClusters, 1); + + polyHalfEdge = m_topo_graph + .getHalfEdgeNext(polyHalfEdge); + visited1 = m_topo_graph.getHalfEdgeUserIndex( + polyHalfEdge, visitedEdges); + visited2 = m_topo_graph.getHalfEdgeUserIndex( + m_topo_graph.getHalfEdgeTwin(polyHalfEdge), + visitedEdges); + visited = visited1 | visited2; + if (visited != 2) + break; + + parentage = m_topo_graph + .getHalfEdgeParentage(polyHalfEdge); + if (!isGoodParentage(parentage)) { + m_topo_graph.setHalfEdgeUserIndex(polyHalfEdge, + visitedEdges, 1); + m_topo_graph.setHalfEdgeUserIndex(m_topo_graph + .getHalfEdgeTwin(polyHalfEdge), + visitedEdges, 1); + break; + } + + } while (polyHalfEdge != half_edge); + + } else { + m_topo_graph.setHalfEdgeUserIndex(half_edge, + visitedEdges, 1); + m_topo_graph.setHalfEdgeUserIndex( + m_topo_graph.getHalfEdgeTwin(half_edge), + visitedEdges, 1); + } + } + + half_edge = m_topo_graph.getHalfEdgeNext(m_topo_graph + .getHalfEdgeTwin(half_edge)); + } while (half_edge != firstHalfEdge); + } + + for (int cluster = m_topo_graph.getFirstCluster(); cluster != -1; cluster = m_topo_graph + .getNextCluster(cluster)) { + int visited = m_topo_graph.getClusterUserIndex(cluster, + visitedClusters); + if (visited == 1) + continue; + + int parentage = m_topo_graph.getClusterParentage(cluster); + if (isGoodParentage(parentage)) { + if (multipointPath == -1) + multipointPath = shape + .insertPath(newGeometryMultipoint, -1); + int viter = m_topo_graph.getClusterVertexIterator(cluster); + int v; + if (viter != -1) { + v = m_topo_graph.getVertexFromVertexIterator(viter); + int vertex_dominant = getVertexByID_(v, geometry_dominant); + shape.addVertex(multipointPath, vertex_dominant); + } + } + } + + m_topo_graph.deleteUserIndexForClusters(visitedClusters); + m_topo_graph.deleteUserIndexForHalfEdges(visitedEdges); + Simplificator.execute(shape, newGeometryPolygon, + MultiVertexGeometryImpl.GeometryXSimple.Weak, m_bOGCOutput, null); + int[] result = new int[3];// always returns size 3 result. + + result[0] = newGeometryMultipoint; + result[1] = newGeometryPolyline; + result[2] = newGeometryPolygon; + return result; + } + + int selectVertex_(int cluster, EditShape shape) { + int vert = -1; + for (int iterator = m_topo_graph.getClusterVertexIterator(cluster); iterator != -1; iterator = m_topo_graph + .incrementVertexIterator(iterator)) { + int vertex = m_topo_graph.getVertexFromVertexIterator(iterator); + if (vert == -1) + vert = vertex; + int geometry = shape.getGeometryFromPath(shape + .getPathFromVertex(vertex)); + int geomID = m_topo_graph.getGeometryID(geometry); + if (isGoodParentage(geomID)) { + vert = vertex; + break; + } + } + + return vert; + } + + private double prevailingDirection_(EditShape shape, int half_edge) { + int cluster = m_topo_graph.getHalfEdgeOrigin(half_edge); + int clusterTo = m_topo_graph.getHalfEdgeTo(half_edge); + int signTotal = 0; + int signCorrect = 0; + for (int iterator = m_topo_graph.getClusterVertexIterator(cluster); iterator != -1; iterator = m_topo_graph + .incrementVertexIterator(iterator)) { + int vertex = m_topo_graph.getVertexFromVertexIterator(iterator); + int path = shape.getPathFromVertex(vertex); + int geometry = shape.getGeometryFromPath(path); + int geomID = m_topo_graph.getGeometryID(geometry); + int nextVert = shape.getNextVertex(vertex); + int prevVert = shape.getPrevVertex(vertex); + + int firstVert = shape.getFirstVertex(path); + if (firstVert == vertex) {// remember the first half edge of the + // path. We use it to produce correct + // startpath for closed polyline loops + m_from_edge_for_polylines = half_edge; + } + + if (nextVert != -1 + && m_topo_graph.getClusterFromVertex(nextVert) == clusterTo) { + signTotal++; + if (isGoodParentage(geomID)) { + if (firstVert == nextVert) {// remember the first vertex of + // the path. We use it to + // produce correct startpath for + // closed polyline loops + m_from_edge_for_polylines = m_topo_graph + .getHalfEdgeNext(half_edge); + } + + // update the sign + signCorrect++; + } + } else if (prevVert != -1 + && m_topo_graph.getClusterFromVertex(prevVert) == clusterTo) { + signTotal--; + if (isGoodParentage(geomID)) { + if (firstVert == prevVert) {// remember the first vertex of + // the path. We use it to + // produce correct startpath for + // closed polyline loops + m_from_edge_for_polylines = m_topo_graph + .getHalfEdgeNext(half_edge); + } + + // update the sign + signCorrect--; + } + } + } + + m_topo_graph.getXY(cluster, m_dummy_pt_1); + m_topo_graph.getXY(clusterTo, m_dummy_pt_2); + double len = Point2D.distance(m_dummy_pt_1, m_dummy_pt_2); + return (signCorrect != 0 ? signCorrect : signTotal) * len; + } + + int getCombinedHalfEdgeParentage_(int e) { + return m_topo_graph.getHalfEdgeParentage(e) + | m_topo_graph.getHalfEdgeFaceParentage(e) + | m_topo_graph.getHalfEdgeFaceParentage(m_topo_graph + .getHalfEdgeTwin(e)); + } + + int tryMoveThroughCrossroadBackwards_(int half_edge) { + int e = m_topo_graph.getHalfEdgeTwin(m_topo_graph + .getHalfEdgePrev(half_edge)); + int goodEdge = -1; + while (e != half_edge) { + int parentage = getCombinedHalfEdgeParentage_(e); + if (isGoodParentage(parentage)) { + if (goodEdge != -1) + return -1; + + goodEdge = e; + } + + e = m_topo_graph.getHalfEdgeTwin(m_topo_graph.getHalfEdgePrev(e)); + } + + return goodEdge != -1 ? m_topo_graph.getHalfEdgeTwin(goodEdge) : -1; + } + + int tryMoveThroughCrossroadForward_(int half_edge) { + int e = m_topo_graph.getHalfEdgeTwin(m_topo_graph + .getHalfEdgeNext(half_edge)); + int goodEdge = -1; + while (e != half_edge) { + int parentage = getCombinedHalfEdgeParentage_(e); + if (isGoodParentage(parentage)) { + if (goodEdge != -1) + return -1;// more than one way to move through the + // intersection + goodEdge = e; + } + + e = m_topo_graph.getHalfEdgeTwin(m_topo_graph.getHalfEdgeNext(e)); + } + + return goodEdge != -1 ? m_topo_graph.getHalfEdgeTwin(goodEdge) : -1; + } + + private void restorePolylineParts_(int first_edge, int newGeometry, + int visitedEdges, int visitedClusters, int geometry_dominant) { + assert (isGoodParentage(getCombinedHalfEdgeParentage_(first_edge))); + EditShape shape = m_topo_graph.getShape(); + int half_edge = first_edge; + int halfEdgeTwin = m_topo_graph.getHalfEdgeTwin(half_edge); + m_topo_graph.setHalfEdgeUserIndex(half_edge, visitedEdges, 1); + m_topo_graph.setHalfEdgeUserIndex(halfEdgeTwin, visitedEdges, 1); + double prevailingLength = prevailingDirection_(shape, half_edge);// prevailing + // direction + // is + // used + // to + // figure + // out + // the + // polyline + // direction. + // Prevailing length is the sum of the length of vectors that constitute + // the polyline. + // Vector length is positive, if the halfedge direction coincides with + // the direction of the original geometry + // and negative otherwise. + + m_from_edge_for_polylines = -1; + int fromEdge = half_edge; + int toEdge = -1; + boolean b_found_impassable_crossroad = false; + int edgeCount = 1; + while (true) { + int halfEdgePrev = m_topo_graph.getHalfEdgePrev(half_edge); + if (halfEdgePrev == halfEdgeTwin) + break;// the end of a polyline + + int halfEdgeTwinNext = m_topo_graph.getHalfEdgeNext(halfEdgeTwin); + if (m_topo_graph.getHalfEdgeTwin(halfEdgePrev) != halfEdgeTwinNext) { + // Crossroads is here. We can move through the crossroad only if + // there is only a single way to pass through. + //When doing planar_simplify we'll never go through the crossroad. + half_edge = tryMoveThroughCrossroadBackwards_(half_edge); + if (half_edge == -1) + break; + else { + b_found_impassable_crossroad = true; + halfEdgeTwin = m_topo_graph.getHalfEdgeTwin(half_edge); + } + } else { + half_edge = halfEdgePrev; + halfEdgeTwin = halfEdgeTwinNext; + } + + if (half_edge == first_edge) {// we are in a loop. No need to search + // for the toEdge. Just remember the + // toEdge and skip the next while + // loop. + toEdge = first_edge; + break; + } + int parentage = getCombinedHalfEdgeParentage_(half_edge); + if (!isGoodParentage(parentage)) + break; + + m_topo_graph.setHalfEdgeUserIndex(half_edge, visitedEdges, 1); + m_topo_graph.setHalfEdgeUserIndex(halfEdgeTwin, visitedEdges, 1); + fromEdge = half_edge; + prevailingLength += prevailingDirection_(shape, half_edge); + edgeCount++; + } + + if (toEdge == -1) { + half_edge = first_edge; + halfEdgeTwin = m_topo_graph.getHalfEdgeTwin(half_edge); + toEdge = half_edge; + while (true) { + int halfEdgeNext = m_topo_graph.getHalfEdgeNext(half_edge); + if (halfEdgeNext == halfEdgeTwin) + break; + + int halfEdgeTwinPrev = m_topo_graph + .getHalfEdgePrev(halfEdgeTwin); + if (m_topo_graph.getHalfEdgeTwin(halfEdgeNext) != halfEdgeTwinPrev) { + // Crossroads is here. We can move through the crossroad + // only if there is only a single way to pass through. + half_edge = tryMoveThroughCrossroadForward_(half_edge); + if (half_edge == -1) { + b_found_impassable_crossroad = true; + break; + } else + halfEdgeTwin = m_topo_graph.getHalfEdgeTwin(half_edge); + } else { + half_edge = halfEdgeNext; + halfEdgeTwin = halfEdgeTwinPrev; + } + + int parentage = getCombinedHalfEdgeParentage_(half_edge); + if (!isGoodParentage(parentage)) + break; + + m_topo_graph.setHalfEdgeUserIndex(half_edge, visitedEdges, 1); + m_topo_graph + .setHalfEdgeUserIndex(halfEdgeTwin, visitedEdges, 1); + toEdge = half_edge; + prevailingLength += prevailingDirection_(shape, half_edge); + edgeCount++; + } + } else { + // toEdge has been found in the first while loop. This happens when + // we go around a face. + // Closed loops need special processing as we do not know where the + // polyline started or ended. + + if (m_from_edge_for_polylines != -1) { + fromEdge = m_from_edge_for_polylines; + toEdge = m_topo_graph + .getHalfEdgePrev(m_from_edge_for_polylines);// try + // simply + // getting + // prev + int fromEdgeTwin = m_topo_graph.getHalfEdgeTwin(fromEdge); + int fromEdgeTwinNext = m_topo_graph + .getHalfEdgeNext(fromEdgeTwin); + if (m_topo_graph.getHalfEdgeTwin(toEdge) != fromEdgeTwinNext) { + // Crossroads is here. Pass through the crossroad. + toEdge = tryMoveThroughCrossroadBackwards_(fromEdge); + if (toEdge == -1) + throw GeometryException.GeometryInternalError();// what? + } + + assert (isGoodParentage(getCombinedHalfEdgeParentage_(m_from_edge_for_polylines))); + assert (isGoodParentage(getCombinedHalfEdgeParentage_(toEdge))); + } + } + + boolean dir = prevailingLength >= 0; + if (!dir) { + int e = toEdge; + toEdge = fromEdge; + fromEdge = e; + toEdge = m_topo_graph.getHalfEdgeTwin(toEdge);// switch to twin so + // that we can use + // next instead of + // Prev + assert (isGoodParentage(getCombinedHalfEdgeParentage_(toEdge))); + fromEdge = m_topo_graph.getHalfEdgeTwin(fromEdge); + assert (isGoodParentage(getCombinedHalfEdgeParentage_(fromEdge))); + } + + int newPath = shape.insertPath(newGeometry, -1);// add new path at the + // end + half_edge = fromEdge; + int cluster = m_topo_graph.getHalfEdgeOrigin(fromEdge); + int clusterLast = m_topo_graph.getHalfEdgeTo(toEdge); + boolean b_closed = clusterLast == cluster; + // The linestrings can touch at boundary points only, while closed path + // has no boundary, therefore no other path can touch it. + // Therefore, if a closed path touches another path, we need to split + // the closed path in two to make the result OGC simple. + boolean b_closed_linestring_touches_other_linestring = b_closed + && b_found_impassable_crossroad; + + int vert = selectVertex_(cluster, shape); + assert (vert != -1); + int vertex_dominant = getVertexByID_(vert, geometry_dominant); + shape.addVertex(newPath, vertex_dominant); + + if (visitedClusters != -1) { + m_topo_graph.setClusterUserIndex(cluster, visitedClusters, 1); + } + + int counter = 0; + int splitAt = b_closed_linestring_touches_other_linestring ? (edgeCount + 1) / 2 : -1; + while (true) { + int clusterTo = m_topo_graph.getHalfEdgeTo(half_edge); + int vert_1 = selectVertex_(clusterTo, shape); + vertex_dominant = getVertexByID_(vert_1, geometry_dominant); + shape.addVertex(newPath, vertex_dominant); + counter++; + if (visitedClusters != -1) { + m_topo_graph.setClusterUserIndex(clusterTo, visitedClusters, 1); + } + + if (b_closed_linestring_touches_other_linestring + && counter == splitAt) { + newPath = shape.insertPath(newGeometry, -1);// add new path at + // the end + shape.addVertex(newPath, vertex_dominant); + } + + assert (isGoodParentage(getCombinedHalfEdgeParentage_(half_edge))); + if (half_edge == toEdge) + break; + + int halfEdgeNext = m_topo_graph.getHalfEdgeNext(half_edge); + if (m_topo_graph.getHalfEdgePrev(m_topo_graph + .getHalfEdgeTwin(half_edge)) != m_topo_graph + .getHalfEdgeTwin(halfEdgeNext)) {// crossroads. + half_edge = tryMoveThroughCrossroadForward_(half_edge); + if (half_edge == -1) + throw GeometryException.GeometryInternalError();// a bug. This + // shoulf + // never + // happen + } else + half_edge = halfEdgeNext; + } + } + + private int topoOperationPolylinePolylineOrPolygon_(int geometry_dominant) { + EditShape shape = m_topo_graph.getShape(); + int newGeometry = shape.createGeometry(Geometry.Type.Polyline); + int visitedEdges = m_topo_graph.createUserIndexForHalfEdges(); + + for (int cluster = m_topo_graph.getFirstCluster(); cluster != -1; cluster = m_topo_graph + .getNextCluster(cluster)) { + int firstClusterHalfEdge = m_topo_graph.getClusterHalfEdge(cluster); + int clusterHalfEdge = firstClusterHalfEdge; + do { + int visited = m_topo_graph.getHalfEdgeUserIndex( + clusterHalfEdge, visitedEdges); + if (visited != 1) { + int parentage = getCombinedHalfEdgeParentage_(clusterHalfEdge); + if (isGoodParentage(parentage)) { + restorePolylineParts_(clusterHalfEdge, newGeometry, + visitedEdges, -1, geometry_dominant); + } else { + // + } + } + + clusterHalfEdge = m_topo_graph.getHalfEdgeNext(m_topo_graph + .getHalfEdgeTwin(clusterHalfEdge)); + } while (clusterHalfEdge != firstClusterHalfEdge); + } + + m_topo_graph.deleteUserIndexForHalfEdges(visitedEdges); + return newGeometry; + } + + int[] topoOperationPolylinePolylineOrPolygonEx_(int geometry_dominant) { + EditShape shape = m_topo_graph.getShape(); + int newPolyline = shape.createGeometry(Geometry.Type.Polyline); + int newMultipoint = shape.createGeometry(Geometry.Type.MultiPoint); + int visitedEdges = m_topo_graph.createUserIndexForHalfEdges(); + int visitedClusters = m_topo_graph.createUserIndexForClusters(); + int multipointPath = -1; + for (int cluster = m_topo_graph.getFirstCluster(); cluster != -1; cluster = m_topo_graph + .getNextCluster(cluster)) { + int firstClusterHalfEdge = m_topo_graph.getClusterHalfEdge(cluster); + int clusterHalfEdge = firstClusterHalfEdge; + do { + int visited = m_topo_graph.getHalfEdgeUserIndex( + clusterHalfEdge, visitedEdges); + if (visited != 1) { + int parentage = getCombinedHalfEdgeParentage_(clusterHalfEdge); + if (isGoodParentage(parentage)) { + restorePolylineParts_(clusterHalfEdge, newPolyline, + visitedEdges, visitedClusters, + geometry_dominant); + } else { + // + } + } + + clusterHalfEdge = m_topo_graph.getHalfEdgeNext(m_topo_graph + .getHalfEdgeTwin(clusterHalfEdge)); + } while (clusterHalfEdge != firstClusterHalfEdge); + } + + for (int cluster = m_topo_graph.getFirstCluster(); cluster != -1; cluster = m_topo_graph + .getNextCluster(cluster)) { + int visited = m_topo_graph.getClusterUserIndex(cluster, + visitedClusters); + if (visited != 1) { + int parentage = m_topo_graph.getClusterParentage(cluster); + if (isGoodParentage(parentage)) { + if (multipointPath == -1) + multipointPath = shape.insertPath(newMultipoint, -1); + + int viter = m_topo_graph.getClusterVertexIterator(cluster); + int v; + if (viter != -1) { + v = m_topo_graph.getVertexFromVertexIterator(viter); + int vertex_dominant = getVertexByID_(v, + geometry_dominant); + shape.addVertex(multipointPath, vertex_dominant); + } + } else { + // + } + } + } + + m_topo_graph.deleteUserIndexForHalfEdges(visitedEdges); + m_topo_graph.deleteUserIndexForClusters(visitedClusters); + int[] result = new int[2]; + result[0] = newMultipoint; + result[1] = newPolyline; + return result; + } + + private int topoOperationMultiPoint_() { + EditShape shape = m_topo_graph.getShape(); + int newGeometry = shape.createGeometry(Geometry.Type.MultiPoint); + int newPath = shape.insertPath(newGeometry, -1);// add new path at the + // end + + // Now extract paths that + for (int cluster = m_topo_graph.getFirstCluster(); cluster != -1; cluster = m_topo_graph + .getNextCluster(cluster)) { + int parentage = m_topo_graph.getClusterParentage(cluster); + if (isGoodParentage(parentage)) { + int vert = -1; + for (int iterator = m_topo_graph + .getClusterVertexIterator(cluster); iterator != -1; iterator = m_topo_graph + .incrementVertexIterator(iterator)) { + int vertex = m_topo_graph + .getVertexFromVertexIterator(iterator); + if (vert == -1) + vert = vertex; + int geometry = shape.getGeometryFromPath(shape + .getPathFromVertex(vertex)); + int geomID = m_topo_graph.getGeometryID(geometry); + if (isGoodParentage(geomID)) { + vert = vertex; + break; + } + } + assert (vert != -1); + shape.addVertex(newPath, vert); + } + } + + return newGeometry; + } + + void initMaskLookupArray_(int len) { + m_mask_lookup = new boolean[len]; + for (int i = 0; i < len; i++) { + m_mask_lookup[i] = false; + } + } + + static MultiPoint processMultiPointIntersectOrDiff_(MultiPoint multi_point, + Geometry intersector, double tolerance, boolean bClipIn) { + MultiPoint multi_point_out = ((MultiPoint) multi_point.createInstance()); + Point2D[] input_points = new Point2D[1000]; + PolygonUtils.PiPResult[] test_results = new PolygonUtils.PiPResult[1000]; + int npoints = multi_point.getPointCount(); + boolean bFirstOut = true; + boolean bArea = (intersector.getDimension() == 2); + if (intersector.getDimension() != 1 && intersector.getDimension() != 2) + throw GeometryException.GeometryInternalError(); + + for (int ipoints = 0; ipoints < npoints; ) { + int num = multi_point.queryCoordinates(input_points, 1000, ipoints, + -1) - ipoints; + + if (bArea) + PolygonUtils.testPointsInArea2D(intersector, input_points, + (int) num, tolerance, test_results); + else + PolygonUtils.testPointsOnLine2D(intersector, input_points, + (int) num, tolerance, test_results); + int i0 = 0; + for (int i = 0; i < num; i++) { + boolean bTest = test_results[i] == PolygonUtils.PiPResult.PiPOutside; + if (!bClipIn) + bTest = !bTest; + + if (bTest) { + if (bFirstOut) { + bFirstOut = false; + multi_point_out.add(multi_point, 0, ipoints); + } + + if (i0 != i) + multi_point_out.add(multi_point, ipoints + i0, ipoints + + i); + + i0 = i + 1; + } + } + + if (!bFirstOut && i0 != num) + multi_point_out.add(multi_point, ipoints + i0, ipoints + num); + + ipoints += num; + } + + if (bFirstOut) + return multi_point; + + return multi_point_out; + } + + static MultiPoint intersection(MultiPoint multi_point, Geometry multi_path, + double tolerance) { + return processMultiPointIntersectOrDiff_(multi_point, multi_path, + tolerance, true); + } + + static MultiPoint difference(MultiPoint multi_point, Geometry multi_path, + double tolerance) { + return processMultiPointIntersectOrDiff_(multi_point, multi_path, + tolerance, false); + } + + static Point processPointIntersectOrDiff_(Point point, + Geometry intersector, double tolerance, boolean bClipIn) { + if (point.isEmpty()) + return ((Point) point.createInstance()); + if (intersector.isEmpty()) { + return bClipIn ? ((Point) point.createInstance()) : null; + } + + Point2D[] input_points = new Point2D[1]; + PolygonUtils.PiPResult[] test_results = new PolygonUtils.PiPResult[1]; + boolean bArea = intersector.getDimension() == 2; + if (intersector.getDimension() != 1 && intersector.getDimension() != 2) + throw GeometryException.GeometryInternalError(); + input_points[0] = point.getXY(); + if (bArea) + PolygonUtils.testPointsInArea2D(intersector, input_points, 1, + tolerance, test_results); + else + PolygonUtils.testPointsOnLine2D(intersector, input_points, 1, + tolerance, test_results); + + boolean bTest = test_results[0] == PolygonUtils.PiPResult.PiPOutside; + if (!bClipIn) + bTest = !bTest; + + if (!bTest) + return point; + else + return ((Point) point.createInstance()); + } + + static Point intersection(Point point, Geometry geom, double tolerance) { + return processPointIntersectOrDiff_(point, geom, tolerance, true); + } + + static Point difference(Point point, Geometry geom, double tolerance) { + return processPointIntersectOrDiff_(point, geom, tolerance, false); + } + + static Point intersection(Point point, Point point2, double tolerance) { + if (point.isEmpty() || point2.isEmpty()) + return (Point) point.createInstance(); + + if (CrackAndCluster.non_empty_points_need_to_cluster(tolerance, point, + point2)) { + return CrackAndCluster.cluster_non_empty_points(point, point2, 1, + 1, 1, 1); + } + + return (Point) point.createInstance(); + } + + static Point difference(Point point, Point point2, double tolerance) { + if (point.isEmpty()) + return (Point) point.createInstance(); + if (point2.isEmpty()) + return point; + + if (CrackAndCluster.non_empty_points_need_to_cluster(tolerance, point, + point2)) { + return (Point) point.createInstance(); + } + + return point; + } + + MultiVertexGeometry planarSimplifyImpl_(MultiVertexGeometry input_geom, + double tolerance, boolean b_use_winding_rule_for_polygons, + boolean dirty_result, ProgressTracker progress_tracker) { + if (input_geom.isEmpty()) + return input_geom; + + EditShape shape = new EditShape(); + int geom = shape.addGeometry(input_geom); + return planarSimplify(shape, geom, tolerance, + b_use_winding_rule_for_polygons, dirty_result, progress_tracker); + } + + MultiVertexGeometry planarSimplify(EditShape shape, int geom, + double tolerance, boolean b_use_winding_rule_for_polygons, + boolean dirty_result, ProgressTracker progress_tracker) { + // This method will produce a polygon from a polyline when + // b_use_winding_rule_for_polygons is true. This is used by buffer. + m_topo_graph = new TopoGraph(); + try { + if (dirty_result + && shape.getGeometryType(geom) != Geometry.Type.MultiPoint + .value()) { + PlaneSweepCrackerHelper plane_sweeper = new PlaneSweepCrackerHelper(); + plane_sweeper.sweepVertical(shape, tolerance); + if (plane_sweeper.hadCompications())// shame. The one pass + // planesweep had some + // complications. Need to do + // full crack and cluster. + { + CrackAndCluster.execute(shape, tolerance, progress_tracker, true); + dirty_result = false; + } else { + m_topo_graph.check_dirty_planesweep(tolerance); + } + } else { + CrackAndCluster.execute(shape, tolerance, progress_tracker, true); + dirty_result = false; + } + + if (!b_use_winding_rule_for_polygons + || shape.getGeometryType(geom) == Geometry.Type.MultiPoint + .value()) + m_topo_graph.setAndSimplifyEditShapeAlternate(shape, geom, progress_tracker); + else + m_topo_graph.setAndSimplifyEditShapeWinding(shape, geom, progress_tracker); + + if (m_topo_graph.dirty_check_failed()) { + // we ran the sweep_vertical() before and it produced some + // issues that where detected by topo graph only. + assert (dirty_result); + m_topo_graph.removeShape(); + m_topo_graph = null; + // that's at most two level recursion + return planarSimplify(shape, geom, tolerance, + b_use_winding_rule_for_polygons, false, + progress_tracker); + } else { + //can proceed + } + + m_topo_graph.check_dirty_planesweep(NumberUtils.TheNaN); + + int ID_a = m_topo_graph.getGeometryID(geom); + initMaskLookupArray_((ID_a) + 1); + m_mask_lookup[ID_a] = true; // Works only when there is a single + // geometry in the edit shape. + // To make it work when many geometries are present, this need to be + // modified. + + if (shape.getGeometryType(geom) == Geometry.Type.Polygon.value() + || (b_use_winding_rule_for_polygons && shape + .getGeometryType(geom) != Geometry.Type.MultiPoint + .value())) { + // geom can be a polygon or a polyline. + // It can be a polyline only when the winding rule is true. + shape.setFillRule(geom, Polygon.FillRule.enumFillRuleOddEven); + int resGeom = topoOperationPolygonPolygon_(geom, -1, -1); + + Polygon polygon = (Polygon) shape.getGeometry(resGeom); + polygon.setFillRule(Polygon.FillRule.enumFillRuleOddEven);//standardize the fill rule. + if (!dirty_result) { + ((MultiVertexGeometryImpl) polygon._getImpl()).setIsSimple( + GeometryXSimple.Strong, tolerance, false); + ((MultiPathImpl) polygon._getImpl())._updateOGCFlags(); + } else + ((MultiVertexGeometryImpl) polygon._getImpl()).setIsSimple( + GeometryXSimple.Weak, 0.0, false);// dirty result means + // simple but with 0 + // tolerance. + + return polygon; + } else if (shape.getGeometryType(geom) == Geometry.Type.Polyline + .value()) { + int resGeom = topoOperationPolylinePolylineOrPolygon_(-1); + + Polyline polyline = (Polyline) shape.getGeometry(resGeom); + if (!dirty_result) + ((MultiVertexGeometryImpl) polyline._getImpl()).setIsSimple( + GeometryXSimple.Strong, tolerance, false); + + return polyline; + } else if (shape.getGeometryType(geom) == Geometry.Type.MultiPoint + .value()) { + int resGeom = topoOperationMultiPoint_(); + + MultiPoint mp = (MultiPoint) shape.getGeometry(resGeom); + if (!dirty_result) + ((MultiVertexGeometryImpl) mp._getImpl()).setIsSimple( + GeometryXSimple.Strong, tolerance, false); + + return mp; + } else { + throw GeometryException.GeometryInternalError(); + } + } finally { + m_topo_graph.removeShape(); + } + } + + // static + static MultiVertexGeometry planarSimplify(MultiVertexGeometry input_geom, + double tolerance, boolean use_winding_rule_for_polygons, + boolean dirty_result, ProgressTracker progress_tracker) { + TopologicalOperations topoOps = new TopologicalOperations(); + return topoOps.planarSimplifyImpl_(input_geom, tolerance, + use_winding_rule_for_polygons, dirty_result, progress_tracker); + } + + boolean planarSimplifyNoCrackingAndCluster(boolean OGCoutput, EditShape shape, int geom, ProgressTracker progress_tracker) { + m_bOGCOutput = OGCoutput; + m_topo_graph = new TopoGraph(); + int rule = shape.getFillRule(geom); + int gt = shape.getGeometryType(geom); + if (rule != Polygon.FillRule.enumFillRuleWinding || gt == GeometryType.MultiPoint) + m_topo_graph.setAndSimplifyEditShapeAlternate(shape, geom, progress_tracker); + else + m_topo_graph.setAndSimplifyEditShapeWinding(shape, geom, progress_tracker); + + if (m_topo_graph.dirty_check_failed()) + return false; + + m_topo_graph.check_dirty_planesweep(NumberUtils.TheNaN); + + int ID_a = m_topo_graph.getGeometryID(geom); + initMaskLookupArray_((ID_a) + 1); + m_mask_lookup[ID_a] = true; //Works only when there is a single geometry in the edit shape. + //To make it work when many geometries are present, this need to be modified. + + if (shape.getGeometryType(geom) == GeometryType.Polygon || (rule == Polygon.FillRule.enumFillRuleWinding && shape.getGeometryType(geom) != GeometryType.MultiPoint)) { + //geom can be a polygon or a polyline. + //It can be a polyline only when the winding rule is true. + shape.setFillRule(geom, Polygon.FillRule.enumFillRuleOddEven); + int resGeom = topoOperationPolygonPolygon_(geom, -1, -1); + shape.swapGeometry(resGeom, geom); + shape.removeGeometry(resGeom); + } else if (shape.getGeometryType(geom) == GeometryType.Polyline) { + int resGeom = topoOperationPolylinePolylineOrPolygon_(-1); + shape.swapGeometry(resGeom, geom); + shape.removeGeometry(resGeom); + } else if (shape.getGeometryType(geom) == GeometryType.MultiPoint) { + int resGeom = topoOperationMultiPoint_(); + shape.swapGeometry(resGeom, geom); + shape.removeGeometry(resGeom); + } else { + throw new GeometryException("internal error"); + } + + return true; + } + + + static MultiVertexGeometry simplifyOGC(MultiVertexGeometry input_geom, double tolerance, boolean dirty_result, ProgressTracker progress_tracker) { + TopologicalOperations topoOps = new TopologicalOperations(); + topoOps.m_bOGCOutput = true; + return topoOps.planarSimplifyImpl_(input_geom, tolerance, false, dirty_result, progress_tracker); + } + + public int difference(int geometry_a, int geometry_b) { + int gtA = m_topo_graph.getShape().getGeometryType(geometry_a); + int gtB = m_topo_graph.getShape().getGeometryType(geometry_b); + int dim_a = Geometry.getDimensionFromType(gtA); + int dim_b = Geometry.getDimensionFromType(gtB); + if (dim_a > dim_b) { + return geometry_a; + } + + int ID_a = m_topo_graph.getGeometryID(geometry_a); + int ID_b = m_topo_graph.getGeometryID(geometry_b); + initMaskLookupArray_((ID_a | ID_b) + 1); + m_mask_lookup[m_topo_graph.getGeometryID(geometry_a)] = true; + + if (dim_a == 2 && dim_b == 2) + return topoOperationPolygonPolygon_(geometry_a, geometry_b, -1); + if (dim_a == 1 && dim_b == 2) + return topoOperationPolylinePolylineOrPolygon_(-1); + if (dim_a == 1 && dim_b == 1) + return topoOperationPolylinePolylineOrPolygon_(-1); + if (dim_a == 0) + return topoOperationMultiPoint_(); + + throw GeometryException.GeometryInternalError(); + } + + int dissolve(int geometry_a, int geometry_b) { + int gtA = m_topo_graph.getShape().getGeometryType(geometry_a); + int gtB = m_topo_graph.getShape().getGeometryType(geometry_b); + int dim_a = Geometry.getDimensionFromType(gtA); + int dim_b = Geometry.getDimensionFromType(gtB); + if (dim_a > dim_b) { + return geometry_a; + } + + if (dim_a < dim_b) { + return geometry_b; + } + + int ID_a = m_topo_graph.getGeometryID(geometry_a); + int ID_b = m_topo_graph.getGeometryID(geometry_b); + initMaskLookupArray_(((ID_a | ID_b) + 1)); + + m_mask_lookup[m_topo_graph.getGeometryID(geometry_a)] = true; + m_mask_lookup[m_topo_graph.getGeometryID(geometry_b)] = true; + m_mask_lookup[m_topo_graph.getGeometryID(geometry_a) + | m_topo_graph.getGeometryID(geometry_b)] = true; + + if (dim_a == 2 && dim_b == 2) + return topoOperationPolygonPolygon_(geometry_a, geometry_b, -1); + if (dim_a == 1 && dim_b == 1) + return topoOperationPolylinePolylineOrPolygon_(-1); + if (dim_a == 0 && dim_b == 0) + return topoOperationMultiPoint_(); + + throw GeometryException.GeometryInternalError(); + } + + public int intersection(int geometry_a, int geometry_b) { + int gtA = m_topo_graph.getShape().getGeometryType(geometry_a); + int gtB = m_topo_graph.getShape().getGeometryType(geometry_b); + int dim_a = Geometry.getDimensionFromType(gtA); + int dim_b = Geometry.getDimensionFromType(gtB); + + int ID_a = m_topo_graph.getGeometryID(geometry_a); + int ID_b = m_topo_graph.getGeometryID(geometry_b); + initMaskLookupArray_(((ID_a | ID_b) + 1)); + + m_mask_lookup[m_topo_graph.getGeometryID(geometry_a) + | m_topo_graph.getGeometryID(geometry_b)] = true; + + int geometry_dominant = -1; + boolean b_vertex_dominance = (m_topo_graph.getShape() + .getVertexDescription().getAttributeCount() > 1); + if (b_vertex_dominance) + geometry_dominant = geometry_a; + + if (dim_a == 2 && dim_b == 2)// intersect two polygons + return topoOperationPolygonPolygon_(geometry_a, geometry_b, + geometry_dominant); + if ((dim_a == 1 && dim_b > 0) || (dim_b == 1 && dim_a > 0))// intersect + // polyline + // with + // polyline + // or + // polygon + return topoOperationPolylinePolylineOrPolygon_(geometry_dominant); + if (dim_a == 0 || dim_b == 0)// intersect a multipoint with something + // else + return topoOperationMultiPoint_(); + + throw GeometryException.GeometryInternalError(); + } + + int[] intersectionEx(int geometry_a, int geometry_b) { + int gtA = m_topo_graph.getShape().getGeometryType(geometry_a); + int gtB = m_topo_graph.getShape().getGeometryType(geometry_b); + int dim_a = Geometry.getDimensionFromType(gtA); + int dim_b = Geometry.getDimensionFromType(gtB); + + int ID_a = m_topo_graph.getGeometryID(geometry_a); + int ID_b = m_topo_graph.getGeometryID(geometry_b); + initMaskLookupArray_(((ID_a | ID_b) + 1)); + + m_mask_lookup[m_topo_graph.getGeometryID(geometry_a) + | m_topo_graph.getGeometryID(geometry_b)] = true; + + int geometry_dominant = -1; + boolean b_vertex_dominance = (m_topo_graph.getShape() + .getVertexDescription().getAttributeCount() > 1); + if (b_vertex_dominance) + geometry_dominant = geometry_a; + + if (dim_a == 2 && dim_b == 2)// intersect two polygons + return topoOperationPolygonPolygonEx_(geometry_a, geometry_b, + geometry_dominant); + if ((dim_a == 1 && dim_b > 0) || (dim_b == 1 && dim_a > 0))// intersect + // polyline + // with + // polyline + // or + // polygon + return topoOperationPolylinePolylineOrPolygonEx_(geometry_dominant); + if (dim_a == 0 || dim_b == 0)// intersect a multipoint with something + // else + { + int[] res = new int[1]; + res[0] = topoOperationMultiPoint_(); + return res; + } + + throw GeometryException.GeometryInternalError(); + } + + public int symmetricDifference(int geometry_a, int geometry_b) { + int gtA = m_topo_graph.getShape().getGeometryType(geometry_a); + int gtB = m_topo_graph.getShape().getGeometryType(geometry_b); + int dim_a = Geometry.getDimensionFromType(gtA); + int dim_b = Geometry.getDimensionFromType(gtB); + + int ID_a = m_topo_graph.getGeometryID(geometry_a); + int ID_b = m_topo_graph.getGeometryID(geometry_b); + initMaskLookupArray_((ID_a | ID_b) + 1); + + m_mask_lookup[m_topo_graph.getGeometryID(geometry_a)] = true; + m_mask_lookup[m_topo_graph.getGeometryID(geometry_b)] = true; + + if (dim_a == 2 && dim_b == 2) + return topoOperationPolygonPolygon_(geometry_a, geometry_b, -1); + if (dim_a == 1 && dim_b == 1) + return topoOperationPolylinePolylineOrPolygon_(-1); + if (dim_a == 0 && dim_b == 0) + return topoOperationMultiPoint_(); + + throw GeometryException.GeometryInternalError(); + } + + int extractShape(int geometry_in) { + int gtA = m_topo_graph.getShape().getGeometryType(geometry_in); + int dim_a = Geometry.getDimensionFromType(gtA); + + int ID_a = m_topo_graph.getGeometryID(geometry_in); + initMaskLookupArray_((ID_a) + 1); + m_mask_lookup[m_topo_graph.getGeometryID(geometry_in)] = true; // Works + // only + // when + // there + // is a + // single + // geometry + // in + // the + // edit + // shape. + // To make it work when many geometries are present, this need to be + // modified. + + if (dim_a == 2) + return topoOperationPolygonPolygon_(geometry_in, -1, -1); + if (dim_a == 1) + return topoOperationPolylinePolylineOrPolygon_(-1); + if (dim_a == 0) + return topoOperationMultiPoint_(); + + throw GeometryException.GeometryInternalError(); + } + + static Geometry normalizeInputGeometry_(Geometry geom) { + Geometry.Type gt = geom.getType(); + if (gt == Geometry.Type.Envelope) { + Polygon poly = new Polygon(geom.getDescription()); + if (!geom.isEmpty()) + poly.addEnvelope((Envelope) geom, false); + return poly; + } + if (gt == Geometry.Type.Point) { + MultiPoint poly = new MultiPoint(geom.getDescription()); + if (!geom.isEmpty()) + poly.add((Point) geom); + return poly; + } + if (gt == Geometry.Type.Line) { + Polyline poly = new Polyline(geom.getDescription()); + if (!geom.isEmpty()) + poly.addSegment((Segment) geom, true); + return poly; + } + + return geom; + } + + static Geometry normalizeResult_(Geometry geomRes, Geometry geom_a, + Geometry dummy, char op) { + // assert(strchr("-&^|",op) != NULL); + Geometry.Type gtRes = geomRes.getType(); + if (gtRes == Geometry.Type.Envelope) { + Polygon poly = new Polygon(geomRes.getDescription()); + if (!geomRes.isEmpty()) + poly.addEnvelope((Envelope) geomRes, false); + return poly; + } + + if (gtRes == Geometry.Type.Point && (op == '|' || op == '^')) { + MultiPoint poly = new MultiPoint(geomRes.getDescription()); + if (!geomRes.isEmpty()) + poly.add((Point) geomRes); + return poly; + } + + if (gtRes == Geometry.Type.Line) { + Polyline poly = new Polyline(geomRes.getDescription()); + if (!geomRes.isEmpty()) + poly.addSegment((Segment) geomRes, true); + return poly; + } + + if (gtRes == Geometry.Type.Point && op == '-') { + if (geom_a.getType() == Geometry.Type.Point) { + Point pt = new Point(geomRes.getDescription()); + if (!geomRes.isEmpty()) { + assert (((MultiPoint) geomRes).getPointCount() == 1); + ((MultiPoint) geomRes).getPointByVal(0, pt); + } + return pt; + } + } + + if (gtRes == Geometry.Type.MultiPoint && op == '&') { + if (geom_a.getType() == Geometry.Type.Point) { + Point pt = new Point(geomRes.getDescription()); + if (!geomRes.isEmpty()) { + assert (((MultiPoint) geomRes).getPointCount() == 1); + ((MultiPoint) geomRes).getPointByVal(0, pt); + } + return pt; + } + } + + return geomRes; + } + + // static + public static Geometry difference(Geometry geometry_a, Geometry geometry_b, + SpatialReference sr, ProgressTracker progress_tracker) { + if (geometry_a.isEmpty() || geometry_b.isEmpty() + || geometry_a.getDimension() > geometry_b.getDimension()) + return normalizeResult_(normalizeInputGeometry_(geometry_a), + geometry_a, geometry_b, '-'); + + Envelope2D env2D_1 = new Envelope2D(); + geometry_a.queryEnvelope2D(env2D_1); + Envelope2D env2D_2 = new Envelope2D(); + geometry_b.queryEnvelope2D(env2D_2); + + if (!env2D_1.isIntersecting(env2D_2)) { + return normalizeResult_(normalizeInputGeometry_(geometry_a), + geometry_a, geometry_b, '-'); + } + + Envelope2D envMerged = new Envelope2D(); + envMerged.setCoords(env2D_1); + envMerged.merge(env2D_2); + double tolerance = InternalUtils.calculateToleranceFromGeometry(sr, + envMerged, true);// conservative to have same effect as simplify + + TopologicalOperations topoOps = new TopologicalOperations(); + EditShape edit_shape = new EditShape(); + int geom_a = edit_shape + .addGeometry(normalizeInputGeometry_(geometry_a)); + int geom_b = edit_shape + .addGeometry(normalizeInputGeometry_(geometry_b)); + topoOps.setEditShapeCrackAndCluster(edit_shape, tolerance, + progress_tracker); + int result = topoOps.difference(geom_a, geom_b); + Geometry resGeom = edit_shape.getGeometry(result); + + Geometry res_geom = normalizeResult_(resGeom, geometry_a, geometry_b, + '-'); + + if (Geometry.isMultiPath(res_geom.getType().value())) { + ((MultiVertexGeometryImpl) res_geom._getImpl()).setIsSimple( + GeometryXSimple.Strong, tolerance, false); + if (res_geom.getType() == Geometry.Type.Polygon) + ((MultiPathImpl) res_geom._getImpl())._updateOGCFlags(); + } + + return res_geom; + } + + public static Geometry dissolve(Geometry geometry_a, Geometry geometry_b, + SpatialReference sr, ProgressTracker progress_tracker) { + if (geometry_a.getDimension() > geometry_b.getDimension()) + return normalizeResult_(normalizeInputGeometry_(geometry_a), + geometry_a, geometry_b, '|'); + + if (geometry_a.getDimension() < geometry_b.getDimension()) + return normalizeResult_(normalizeInputGeometry_(geometry_b), + geometry_a, geometry_b, '|'); + + if (geometry_a.isEmpty()) + return normalizeResult_(normalizeInputGeometry_(geometry_b), + geometry_a, geometry_b, '|'); + + if (geometry_b.isEmpty()) + return normalizeResult_(normalizeInputGeometry_(geometry_a), + geometry_a, geometry_b, '|'); + + Envelope2D env2D_1 = new Envelope2D(); + geometry_a.queryEnvelope2D(env2D_1); + Envelope2D env2D_2 = new Envelope2D(); + geometry_b.queryEnvelope2D(env2D_2); + + Envelope2D envMerged = new Envelope2D(); + envMerged.setCoords(env2D_1); + envMerged.merge(env2D_2); + double tolerance = InternalUtils.calculateToleranceFromGeometry(sr, + envMerged, true);// conservative to have same effect as simplify + + if (!env2D_1.isIntersecting(env2D_2.getInflated(tolerance, tolerance))) { + // TODO: add optimization here to merge two geometries if the + // envelopes do not overlap. + Geometry geom1 = normalizeInputGeometry_(geometry_a); + assert (Geometry.isMultiVertex(geom1.getType().value())); + Geometry geom2 = normalizeInputGeometry_(geometry_b); + assert (Geometry.isMultiVertex(geom2.getType().value())); + assert (geom1.getType() == geom2.getType()); + switch (geom1.getType().value()) { + case Geometry.GeometryType.MultiPoint: { + Geometry res = Geometry._clone(geom1); + ((MultiPoint) res).add((MultiPoint) geom2, 0, -1); + return res; + } + // break; + case Geometry.GeometryType.Polyline: { + Geometry res = Geometry._clone(geom1); + ((Polyline) res).add((MultiPath) geom2, false); + return res; + } + // break; + case Geometry.GeometryType.Polygon: { + Geometry res = Geometry._clone(geom1); + ((Polygon) res).add((MultiPath) geom2, false); + return res; + } + // break; + default: + throw GeometryException.GeometryInternalError(); + } + } + + TopologicalOperations topoOps = new TopologicalOperations(); + EditShape edit_shape = new EditShape(); + int geom_a = edit_shape + .addGeometry(normalizeInputGeometry_(geometry_a)); + int geom_b = edit_shape + .addGeometry(normalizeInputGeometry_(geometry_b)); + topoOps.setEditShapeCrackAndCluster(edit_shape, tolerance, + progress_tracker); + int result = topoOps.dissolve(geom_a, geom_b); + + Geometry res_geom = normalizeResult_(edit_shape.getGeometry(result), + geometry_a, geometry_b, '|'); + + if (Geometry.isMultiPath(res_geom.getType().value())) { + ((MultiVertexGeometryImpl) res_geom._getImpl()).setIsSimple( + GeometryXSimple.Strong, tolerance, false); + if (res_geom.getType() == Geometry.Type.Polygon) + ((MultiPathImpl) res_geom._getImpl())._updateOGCFlags(); + } + + return res_geom; + } + + static Geometry dissolveDirty(ArrayList geometries, + SpatialReference sr, ProgressTracker progress_tracker) { + if (geometries.size() < 2) + throw new IllegalArgumentException( + "not enough geometries to dissolve"); + + int dim = 0; + for (int i = 0, n = geometries.size(); i < n; i++) { + dim = Math.max(geometries.get(i).getDimension(), dim); + } + + Envelope2D envMerged = new Envelope2D(); + envMerged.setEmpty(); + + EditShape shape = new EditShape(); + int geom = -1; + int count = 0; + int any_index = -1; + for (int i = 0, n = geometries.size(); i < n; i++) { + if (geometries.get(i).getDimension() == dim) { + if (!geometries.get(i).isEmpty()) { + any_index = i; + if (geom == -1) + geom = shape + .addGeometry(normalizeInputGeometry_(geometries + .get(i))); + else + shape.appendGeometry(geom, + normalizeInputGeometry_(geometries.get(i))); + + Envelope2D env = new Envelope2D(); + geometries.get(i).queryLooseEnvelope2D(env); + envMerged.merge(env); + count++; + } else if (any_index == -1) + any_index = i; + } + } + + if (count < 2) { + return normalizeInputGeometry_(geometries.get(any_index)); + } + + boolean winding = dim == 2; + + SpatialReference psr = dim == 0 ? sr : null;// if points, then use + // correct tolerance. + double tolerance = InternalUtils.calculateToleranceFromGeometry(psr, + envMerged, true); + TopologicalOperations topoOps = new TopologicalOperations(); + return topoOps.planarSimplify(shape, geom, tolerance, winding, true, + progress_tracker); + } + + // static + public static Geometry intersection(Geometry geometry_a, + Geometry geometry_b, SpatialReference sr, + ProgressTracker progress_tracker) { + + Envelope2D env2D_1 = new Envelope2D(); + geometry_a.queryEnvelope2D(env2D_1); + Envelope2D env2D_2 = new Envelope2D(); + geometry_b.queryEnvelope2D(env2D_2); + + Envelope2D envMerged = new Envelope2D(); + envMerged.setCoords(env2D_1); + envMerged.merge(env2D_2); + double tolerance = InternalUtils.calculateToleranceFromGeometry(sr, + envMerged, true);// conservative to have same effect as simplify + + Envelope2D e = new Envelope2D(); + e.setCoords(env2D_2); + double tol_cluster = InternalUtils + .adjust_tolerance_for_TE_clustering(tolerance); + e.inflate(tol_cluster, tol_cluster); + + if (!env2D_1.isIntersecting(e))// also includes the empty geometry + // cases + { + if (geometry_a.getDimension() <= geometry_b.getDimension()) + return normalizeResult_( + normalizeInputGeometry_(geometry_a.createInstance()), + geometry_a, geometry_b, '&'); + + if (geometry_a.getDimension() > geometry_b.getDimension()) + return normalizeResult_( + normalizeInputGeometry_(geometry_b.createInstance()), + geometry_a, geometry_b, '&'); + } + + TopologicalOperations topoOps = new TopologicalOperations(); + EditShape edit_shape = new EditShape(); + int geom_a = edit_shape + .addGeometry(normalizeInputGeometry_(geometry_a)); + int geom_b = edit_shape + .addGeometry(normalizeInputGeometry_(geometry_b)); + + topoOps.setEditShapeCrackAndCluster(edit_shape, tolerance, + progress_tracker); + int result = topoOps.intersection(geom_a, geom_b); + Geometry res_geom = normalizeResult_(edit_shape.getGeometry(result), + geometry_a, geometry_b, '&'); + + if (Geometry.isMultiPath(res_geom.getType().value())) { + ((MultiVertexGeometryImpl) res_geom._getImpl()).setIsSimple( + GeometryXSimple.Strong, tolerance, false); + if (res_geom.getType() == Geometry.Type.Polygon) + ((MultiPathImpl) res_geom._getImpl())._updateOGCFlags(); + } + + return res_geom; + } + + static Geometry[] intersectionEx(Geometry geometry_a, Geometry geometry_b, + SpatialReference sr, ProgressTracker progress_tracker) { + Geometry[] res_vec = new Geometry[3]; + + Envelope2D env2D_1 = new Envelope2D(); + geometry_a.queryEnvelope2D(env2D_1); + Envelope2D env2D_2 = new Envelope2D(); + geometry_b.queryEnvelope2D(env2D_2); + + Envelope2D envMerged = new Envelope2D(); + envMerged.setCoords(env2D_1); + envMerged.merge(env2D_2); + double tolerance = InternalUtils.calculateToleranceFromGeometry(sr, + envMerged, true);// conservative to have same effect as simplify + + Envelope2D e = new Envelope2D(); + e.setCoords(env2D_2); + double tol_cluster = InternalUtils + .adjust_tolerance_for_TE_clustering(tolerance); + e.inflate(tol_cluster, tol_cluster); + + if (!env2D_1.isIntersecting(e))// also includes the empty geometry + // cases + { + if (geometry_a.getDimension() <= geometry_b.getDimension()) { + Geometry geom = normalizeResult_( + normalizeInputGeometry_(geometry_a.createInstance()), + geometry_a, geometry_b, '&'); + res_vec[geom.getDimension()] = geom; + return res_vec; + } + + if (geometry_a.getDimension() > geometry_b.getDimension()) { + Geometry geom = normalizeResult_( + normalizeInputGeometry_(geometry_b.createInstance()), + geometry_a, geometry_b, '&'); + res_vec[geom.getDimension()] = geom; + return res_vec; + } + + } + + TopologicalOperations topoOps = new TopologicalOperations(); + EditShape edit_shape = new EditShape(); + int geom_a = edit_shape + .addGeometry(normalizeInputGeometry_(geometry_a)); + int geom_b = edit_shape + .addGeometry(normalizeInputGeometry_(geometry_b)); + + topoOps.setEditShapeCrackAndCluster(edit_shape, tolerance, + progress_tracker); + int[] result_geom_handles = topoOps.intersectionEx(geom_a, geom_b); + for (int i = 0; i < result_geom_handles.length; i++) { + Geometry res_geom = normalizeResult_( + edit_shape.getGeometry(result_geom_handles[i]), geometry_a, + geometry_b, '&'); + + if (Geometry.isMultiPath(res_geom.getType().value())) { + ((MultiVertexGeometryImpl) res_geom._getImpl()).setIsSimple( + MultiVertexGeometryImpl.GeometryXSimple.Strong, + tolerance, false); + if (res_geom.getType().value() == Geometry.GeometryType.Polygon) + ((MultiPathImpl) res_geom._getImpl())._updateOGCFlags(); + } + + res_vec[res_geom.getDimension()] = res_geom; + } + + return res_vec; + } + + // static + public static Geometry symmetricDifference(Geometry geometry_a, + Geometry geometry_b, SpatialReference sr, + ProgressTracker progress_tracker) { + if (geometry_a.getDimension() > geometry_b.getDimension()) + return normalizeResult_(normalizeInputGeometry_(geometry_a), + geometry_a, geometry_b, '^'); + + if (geometry_a.getDimension() < geometry_b.getDimension()) + return normalizeResult_(normalizeInputGeometry_(geometry_b), + geometry_a, geometry_b, '^'); + + if (geometry_a.isEmpty()) + return normalizeResult_(normalizeInputGeometry_(geometry_b), + geometry_a, geometry_b, '^'); + + if (geometry_b.isEmpty()) + return normalizeResult_(normalizeInputGeometry_(geometry_a), + geometry_a, geometry_b, '^'); + + Envelope2D env2D_1 = new Envelope2D(); + geometry_a.queryEnvelope2D(env2D_1); + Envelope2D env2D_2 = new Envelope2D(); + geometry_b.queryEnvelope2D(env2D_2); + // TODO: add optimization here to merge two geometries if the envelopes + // do not overlap. + + Envelope2D envMerged = new Envelope2D(); + envMerged.setCoords(env2D_1); + envMerged.merge(env2D_2); + double tolerance = InternalUtils.calculateToleranceFromGeometry(sr, + envMerged, true);// conservative to have same effect as simplify + + TopologicalOperations topoOps = new TopologicalOperations(); + EditShape edit_shape = new EditShape(); + int geom_a = edit_shape + .addGeometry(normalizeInputGeometry_(geometry_a)); + int geom_b = edit_shape + .addGeometry(normalizeInputGeometry_(geometry_b)); + topoOps.setEditShapeCrackAndCluster(edit_shape, tolerance, + progress_tracker); + int result = topoOps.symmetricDifference(geom_a, geom_b); + Geometry res_geom = normalizeResult_(edit_shape.getGeometry(result), + geometry_a, geometry_b, '^'); + + if (Geometry.isMultiPath(res_geom.getType().value())) { + ((MultiVertexGeometryImpl) res_geom._getImpl()).setIsSimple( + GeometryXSimple.Strong, tolerance, false); + if (res_geom.getType() == Geometry.Type.Polygon) + ((MultiPathImpl) res_geom._getImpl())._updateOGCFlags(); + } + + return res_geom; + } + + static Geometry _denormalizeGeometry(Geometry geom, Geometry geomA, + Geometry geomB) { + Geometry.Type gtA = geomA.getType(); + Geometry.Type gtB = geomB.getType(); + Geometry.Type gt = geom.getType(); + if (gt == Geometry.Type.MultiPoint) { + if (gtA == Geometry.Type.Point || gtB == Geometry.Type.Point) { + MultiPoint mp = (MultiPoint) geom; + if (mp.getPointCount() <= 1) { + Point pt = new Point(geom.getDescription()); + if (!mp.isEmpty()) + mp.getPointByVal(0, pt); + return (Geometry) pt; + } + } + } + return geom; + } + + private void flushVertices_(int geometry, AttributeStreamOfInt32 vertices) { + EditShape shape = m_topo_graph.getShape(); + int path = shape.insertPath(geometry, -1); + int size = vertices.size(); + // _ASSERT(size != 0); + for (int i = 0; i < size; i++) { + int vertex = vertices.get(i); + shape.addVertex(path, vertex); + } + shape.setClosedPath(path, true);// need to close polygon rings + } + + private void setHalfEdgeOrientations_(int orientationIndex, int cutter) { + EditShape shape = m_topo_graph.getShape(); + + for (int igeometry = shape.getFirstGeometry(); igeometry != -1; igeometry = shape + .getNextGeometry(igeometry)) { + if (igeometry != cutter) + continue; + + for (int ipath = shape.getFirstPath(igeometry); ipath != -1; ipath = shape + .getNextPath(ipath)) { + int ivertex = shape.getFirstVertex(ipath); + if (ivertex == -1) + continue; + + int ivertexNext = shape.getNextVertex(ivertex); + assert (ivertexNext != -1); + + while (ivertexNext != -1) { + int clusterFrom = m_topo_graph + .getClusterFromVertex(ivertex); + int clusterTo = m_topo_graph + .getClusterFromVertex(ivertexNext); + int half_edge = m_topo_graph.getHalfEdgeConnector( + clusterFrom, clusterTo); + + if (half_edge != -1) { + int halfEdgeTwin = m_topo_graph + .getHalfEdgeTwin(half_edge); + m_topo_graph.setHalfEdgeUserIndex(half_edge, + orientationIndex, 1); + m_topo_graph.setHalfEdgeUserIndex(halfEdgeTwin, + orientationIndex, 2); + } + + ivertex = ivertexNext; + ivertexNext = shape.getNextVertex(ivertex); + } + } + } + } + + private void processPolygonCuts_(int orientationIndex, int sideIndex, + int cuttee, int cutter) { + int idCuttee = m_topo_graph.getGeometryID(cuttee); + int idCutter = m_topo_graph.getGeometryID(cutter); + AttributeStreamOfInt32 vertices = new AttributeStreamOfInt32(0); + vertices.reserve(256); + EditShape shape = m_topo_graph.getShape(); + + int visitedIndex = m_topo_graph.createUserIndexForHalfEdges(); + for (int cluster = m_topo_graph.getFirstCluster(); cluster != -1; cluster = m_topo_graph + .getNextCluster(cluster)) { + int firstHalfEdge = m_topo_graph.getClusterHalfEdge(cluster); + + if (firstHalfEdge == -1) + continue; + + int half_edge = firstHalfEdge; + + do { + int visited = m_topo_graph.getHalfEdgeUserIndex(half_edge, + visitedIndex); + if (visited != 1) { + int faceHalfEdge = half_edge; + int toHalfEdge = half_edge; + boolean bFoundCutter = false; + int side = 0; + do { + m_topo_graph.setHalfEdgeUserIndex(faceHalfEdge, + visitedIndex, 1); + if (!bFoundCutter) { + int edgeParentage = m_topo_graph + .getHalfEdgeParentage(faceHalfEdge); + if ((edgeParentage & idCutter) != 0) { + int faceParentage = m_topo_graph + .getHalfEdgeFaceParentage(faceHalfEdge); + if ((faceParentage & idCuttee) != 0) { + toHalfEdge = faceHalfEdge;// reset the loop + bFoundCutter = true; + } + } + } + + if (bFoundCutter) { + int clusterOrigin = m_topo_graph + .getHalfEdgeOrigin(faceHalfEdge); + int iterator = m_topo_graph + .getClusterVertexIterator(clusterOrigin); + assert (iterator != -1); + int vertex = m_topo_graph + .getVertexFromVertexIterator(iterator); + vertices.add(vertex); + + // get side + if (orientationIndex != -1) { + int edgeParentage = m_topo_graph + .getHalfEdgeParentage(faceHalfEdge); + if ((edgeParentage & idCutter) != 0) { + int orientation = m_topo_graph + .getHalfEdgeUserIndex(faceHalfEdge, + orientationIndex); + assert (orientation == 1 || orientation == 2); + side |= orientation; + } + } + } + + int next = m_topo_graph.getHalfEdgeNext(faceHalfEdge); + faceHalfEdge = next; + } while (faceHalfEdge != toHalfEdge); + + if (bFoundCutter + && m_topo_graph.getChainArea(m_topo_graph + .getHalfEdgeChain(toHalfEdge)) > 0.0) {// if + // we + // found + // a + // cutter + // face + // and + // its + // area + // is + // positive, + // then + // add + // the + // cutter + // face + // as + // new + // polygon. + int geometry = shape + .createGeometry(Geometry.Type.Polygon); + flushVertices_(geometry, vertices);// adds the cutter + // face vertices to + // the new polygon + + if (sideIndex != -1) + shape.setGeometryUserIndex(geometry, sideIndex, + side); // what is that? + } + + vertices.clear(false); + } + half_edge = m_topo_graph.getHalfEdgeNext(m_topo_graph + .getHalfEdgeTwin(half_edge)); + } while (half_edge != firstHalfEdge); + } + + m_topo_graph.deleteUserIndexForHalfEdges(visitedIndex); + } + + private void cutPolygonPolyline_(int sideIndex, int cuttee, int cutter, + AttributeStreamOfInt32 cutHandles) { + m_topo_graph.removeSpikes_(); + + int orientationIndex = -1; + if (sideIndex != -1) { + orientationIndex = m_topo_graph.createUserIndexForHalfEdges(); + setHalfEdgeOrientations_(orientationIndex, cutter); + } + + processPolygonCuts_(orientationIndex, sideIndex, cuttee, cutter); + + EditShape shape = m_topo_graph.getShape(); + + int cutCount = 0; + for (int geometry_handle = shape.getFirstGeometry(); geometry_handle != -1; geometry_handle = shape + .getNextGeometry(geometry_handle)) { + if (geometry_handle != cuttee && geometry_handle != cutter) { + cutHandles.add(geometry_handle); + cutCount++; + } + } + + // sort + CompareCuts compareCuts = new CompareCuts(shape); + cutHandles.Sort(0, cutCount, compareCuts); + } + + //call this if EditShape instance has to survive the TopologicalOperations life. + void removeShape() { + if (m_topo_graph != null) { + m_topo_graph.removeShape(); + m_topo_graph = null; + } + + } } diff --git a/src/main/java/com/esri/core/geometry/Transformation2D.java b/src/main/java/com/esri/core/geometry/Transformation2D.java index e8c92ed7..0ce1e57c 100644 --- a/src/main/java/com/esri/core/geometry/Transformation2D.java +++ b/src/main/java/com/esri/core/geometry/Transformation2D.java @@ -47,819 +47,819 @@ */ public final class Transformation2D { - /** - * Matrix coefficient XX of the transformation. - */ - public double xx; - /** - * Matrix coefficient XY of the transformation. - */ - public double xy; - /** - * X translation component of the transformation. - */ - public double xd; - /** - * Matrix coefficient YX of the transformation. - */ - - public double yx; - /** - * Matrix coefficient YY of the transformation. - */ - public double yy; - /** - * Y translation component of the transformation. - */ - - public double yd; - - /** - * Creates a 2D affine transformation with identity transformation. - */ - public Transformation2D() { - setIdentity(); - } - - /** - * Creates a 2D affine transformation with a specified scale. - * - * @param scale The scale to use for the transformation. - */ - public Transformation2D(double scale) { - setScale(scale); - } - - /** - * Initializes a zero transformation. Transforms any coordinate to (0, 0). - */ - public void setZero() { - xx = 0; - yy = 0; - xy = 0; - yx = 0; - xd = 0; - yd = 0; - } - - void transform(Point2D psrc, Point2D pdst) { - double x = xx * psrc.x + xy * psrc.y + xd; - double y = yx * psrc.x + yy * psrc.y + yd; - pdst.x = x; - pdst.y = y; - } - - /** - * Returns True when all members of this transformation are equal to the - * corresponding members of the other. - */ - - @Override - public boolean equals(Object other) { - if (this == other) - return true; - if (!(other instanceof Transformation2D)) - return false; - Transformation2D that = (Transformation2D) other; - - return (xx == that.xx && xy == that.xy && xd == that.xd - && yx == that.yx && yy == that.yy && yd == that.yd); - } - - /** - * Returns the hash code for the 2D transformation. - */ - - @Override - public int hashCode() { - int hash = NumberUtils.hash(xx); - hash = NumberUtils.hash(hash, xy); - hash = NumberUtils.hash(hash, xd); - hash = NumberUtils.hash(hash, yx); - hash = NumberUtils.hash(hash, yy); - hash = NumberUtils.hash(hash, yd); - return hash; - } - - void transform(Point2D[] points, int start, int count) { - int n = Math.min(points.length, start + count); - for (int i = count; i < n; i++) { - transform(points[i], points[i]); - } - } - - /** - * Transforms an array of points. - * - * @param pointsIn The points to be transformed. - * @param count The number of points to transform. - * @param pointsOut The transformed points are returned using this array. It - * should have the same or greater size as the input array. - */ - public void transform(Point[] pointsIn, int count, Point[] pointsOut) { - Point2D res = new Point2D(); - for (int i = 0; i < count; i++) { - Point2D p = pointsIn[i].getXY(); - res.x = xx * p.x + xy * p.y + xd; - res.y = yx * p.x + yy * p.y + yd; - pointsOut[i] = new Point(res.x, res.y); - } - } - - /** - * Transforms an array of points stored in an array of doubles as - * interleaved XY coordinates. - * - * @param pointsXYInterleaved The array of points with interleaved X, Y values to be - * transformed. - * @param start The start point index to transform from (the actual element - * index is 2 * start). - * @param count The number of points to transform (the actual element count is - * 2 * count). - */ - public void transform(double[] pointsXYInterleaved, int start, int count) { - int n = Math.min(pointsXYInterleaved.length, (start + count) * 2) / 2; - for (int i = count; i < n; i++) { - double px = pointsXYInterleaved[2 * i]; - double py = pointsXYInterleaved[2 * i + 1]; - pointsXYInterleaved[2 * i] = xx * px + xy * py + xd; - pointsXYInterleaved[2 * i + 1] = yx * px + yy * py + yd; - } - } - - /** - * Multiplies this matrix on the right with the "right" matrix. Stores the - * result into this matrix and returns a reference to it.
- * Equivalent to this *= right. - * - * @param right The matrix to be multiplied with. - */ - public void multiply(Transformation2D right) { - multiply(this, right, this); - } - - /** - * Multiplies this matrix on the left with the "left" matrix. Stores the - * result into this matrix and returns a reference to it.
- * Equivalent to this = left * this. - * - * @param left The matrix to be multiplied with. - */ - public void mulLeft(Transformation2D left) { - multiply(left, this, this); - } - - /** - * Performs multiplication of matrices a and b and places the result into - * this matrix. The a, b, and result could point to same objects.
- * Equivalent to result = a * b. - * - * @param a The 2D transformation to be multiplied. - * @param b The 2D transformation to be multiplied. - * @param result The 2D transformation created by multiplication of matrices. - */ - public static void multiply(Transformation2D a, Transformation2D b, - Transformation2D result) { - double xx, xy, xd, yx, yy, yd; - - xx = a.xx * b.xx + a.yx * b.xy; - xy = a.xy * b.xx + a.yy * b.xy; - xd = a.xd * b.xx + a.yd * b.xy + b.xd; - yx = a.xx * b.yx + a.yx * b.yy; - yy = a.xy * b.yx + a.yy * b.yy; - yd = a.xd * b.yx + a.yd * b.yy + b.yd; - - result.xx = xx; - result.xy = xy; - result.xd = xd; - result.yx = yx; - result.yy = yy; - result.yd = yd; - } - - /** - * Returns a copy of the Transformation2D object. - * - * @return A copy of this object. - */ - public Transformation2D copy() { - Transformation2D result = new Transformation2D(); - result.xx = xx; - result.xy = xy; - result.xd = xd; - result.yx = yx; - result.yy = yy; - result.yd = yd; - return result; - } - - /** - * Writes the matrix coefficients in the order XX, XY, XD, YX, YY, YD into - * the given array. - * - * @param coefs The array into which the coefficients are returned. Should be - * of size 6 elements. - */ - public void getCoefficients(double[] coefs) { - if (coefs.length < 6) - throw new GeometryException( - "Buffer is too small. coefs needs 6 members"); - - coefs[0] = xx; - coefs[1] = xy; - coefs[2] = xd; - coefs[3] = yx; - coefs[4] = yy; - coefs[5] = yd; - } - - /** - * Transforms envelope - * - * @param env The envelope that is to be transformed - */ - void transform(Envelope2D env) { - - if (env.isEmpty()) - return; - - Point2D[] buf = new Point2D[4]; - env.queryCorners(buf); - transform(buf, buf); - env.setFromPoints(buf, 4); - } - - void transform(Point2D[] pointsIn, Point2D[] pointsOut) { - for (int i = 0; i < pointsIn.length; i++) { - Point2D res = new Point2D(); - Point2D p = pointsIn[i]; - res.x = xx * p.x + xy * p.y + xd; - res.y = yx * p.x + yy * p.y + yd; - pointsOut[i] = res; - } - } - - /** - * Initialize transformation from two rectangles. - */ - void initializeFromRect(Envelope2D src, Envelope2D dest) { - if (src.isEmpty() || dest.isEmpty() || 0 == src.getWidth() - || 0 == src.getHeight()) - setZero(); - else { - xy = yx = 0; - xx = dest.getWidth() / src.getWidth(); - yy = dest.getHeight() / src.getHeight(); - xd = dest.xmin - src.xmin * xx; - yd = dest.ymin - src.ymin * yy; - } - } - - /** - * Initializes an orhtonormal transformation from the Src and Dest - * rectangles. - *

- * The result transformation proportionally fits the Src into the Dest. The - * center of the Src will be in the center of the Dest. - */ - void initializeFromRectIsotropic(Envelope2D src, Envelope2D dest) { - - if (src.isEmpty() || dest.isEmpty() || 0 == src.getWidth() - || 0 == src.getHeight()) - setZero(); - else { - yx = 0; - xy = 0; - xx = dest.getWidth() / src.getWidth(); - yy = dest.getHeight() / src.getHeight(); - if (xx > yy) - xx = yy; - else - yy = xx; - - Point2D destCenter = dest.getCenter(); - Point2D srcCenter = src.getCenter(); - xd = destCenter.x - srcCenter.x * xx; - yd = destCenter.y - srcCenter.y * yy; - } - } - - /** - * Initializes transformation from Position, Tangent vector and offset - * value. Tangent vector must have unity length - */ - void initializeFromCurveParameters(Point2D Position, Point2D Tangent, - double Offset) { - // TODO - } - - /** - * Transforms size. - *

- * Creates an AABB with width of SizeSrc.x and height of SizeSrc.y. - * Transforms that AABB and gets a quadrangle in new coordinate system. The - * result x contains the length of the quadrangle edge, which were parallel - * to X in the original system, and y contains the length of the edge, that - * were parallel to the Y axis in the original system. - */ - Point2D transformSize(Point2D SizeSrc) { - Point2D pt = new Point2D(); - pt.x = Math.sqrt(xx * xx + yx * yx) * SizeSrc.x; - pt.y = Math.sqrt(xy * xy + yy * yy) * SizeSrc.y; - return pt; - } - - /** - * Transforms a tolerance value. - * - * @param tolerance The tolerance value. - */ - public double transform(double tolerance) { - // the function should be implemented as follows: find encompassing - // circle for the transformed circle of radius = Tolerance. - - // this is approximation. - Point2D pt1 = new Point2D(); - Point2D pt2 = new Point2D(); - /* + /** + * Matrix coefficient XX of the transformation. + */ + public double xx; + /** + * Matrix coefficient XY of the transformation. + */ + public double xy; + /** + * X translation component of the transformation. + */ + public double xd; + /** + * Matrix coefficient YX of the transformation. + */ + + public double yx; + /** + * Matrix coefficient YY of the transformation. + */ + public double yy; + /** + * Y translation component of the transformation. + */ + + public double yd; + + /** + * Creates a 2D affine transformation with identity transformation. + */ + public Transformation2D() { + setIdentity(); + } + + /** + * Creates a 2D affine transformation with a specified scale. + * + * @param scale The scale to use for the transformation. + */ + public Transformation2D(double scale) { + setScale(scale); + } + + /** + * Initializes a zero transformation. Transforms any coordinate to (0, 0). + */ + public void setZero() { + xx = 0; + yy = 0; + xy = 0; + yx = 0; + xd = 0; + yd = 0; + } + + void transform(Point2D psrc, Point2D pdst) { + double x = xx * psrc.x + xy * psrc.y + xd; + double y = yx * psrc.x + yy * psrc.y + yd; + pdst.x = x; + pdst.y = y; + } + + /** + * Returns True when all members of this transformation are equal to the + * corresponding members of the other. + */ + + @Override + public boolean equals(Object other) { + if (this == other) + return true; + if (!(other instanceof Transformation2D)) + return false; + Transformation2D that = (Transformation2D) other; + + return (xx == that.xx && xy == that.xy && xd == that.xd + && yx == that.yx && yy == that.yy && yd == that.yd); + } + + /** + * Returns the hash code for the 2D transformation. + */ + + @Override + public int hashCode() { + int hash = NumberUtils.hash(xx); + hash = NumberUtils.hash(hash, xy); + hash = NumberUtils.hash(hash, xd); + hash = NumberUtils.hash(hash, yx); + hash = NumberUtils.hash(hash, yy); + hash = NumberUtils.hash(hash, yd); + return hash; + } + + void transform(Point2D[] points, int start, int count) { + int n = Math.min(points.length, start + count); + for (int i = count; i < n; i++) { + transform(points[i], points[i]); + } + } + + /** + * Transforms an array of points. + * + * @param pointsIn The points to be transformed. + * @param count The number of points to transform. + * @param pointsOut The transformed points are returned using this array. It + * should have the same or greater size as the input array. + */ + public void transform(Point[] pointsIn, int count, Point[] pointsOut) { + Point2D res = new Point2D(); + for (int i = 0; i < count; i++) { + Point2D p = pointsIn[i].getXY(); + res.x = xx * p.x + xy * p.y + xd; + res.y = yx * p.x + yy * p.y + yd; + pointsOut[i] = new Point(res.x, res.y); + } + } + + /** + * Transforms an array of points stored in an array of doubles as + * interleaved XY coordinates. + * + * @param pointsXYInterleaved The array of points with interleaved X, Y values to be + * transformed. + * @param start The start point index to transform from (the actual element + * index is 2 * start). + * @param count The number of points to transform (the actual element count is + * 2 * count). + */ + public void transform(double[] pointsXYInterleaved, int start, int count) { + int n = Math.min(pointsXYInterleaved.length, (start + count) * 2) / 2; + for (int i = count; i < n; i++) { + double px = pointsXYInterleaved[2 * i]; + double py = pointsXYInterleaved[2 * i + 1]; + pointsXYInterleaved[2 * i] = xx * px + xy * py + xd; + pointsXYInterleaved[2 * i + 1] = yx * px + yy * py + yd; + } + } + + /** + * Multiplies this matrix on the right with the "right" matrix. Stores the + * result into this matrix and returns a reference to it.
+ * Equivalent to this *= right. + * + * @param right The matrix to be multiplied with. + */ + public void multiply(Transformation2D right) { + multiply(this, right, this); + } + + /** + * Multiplies this matrix on the left with the "left" matrix. Stores the + * result into this matrix and returns a reference to it.
+ * Equivalent to this = left * this. + * + * @param left The matrix to be multiplied with. + */ + public void mulLeft(Transformation2D left) { + multiply(left, this, this); + } + + /** + * Performs multiplication of matrices a and b and places the result into + * this matrix. The a, b, and result could point to same objects.
+ * Equivalent to result = a * b. + * + * @param a The 2D transformation to be multiplied. + * @param b The 2D transformation to be multiplied. + * @param result The 2D transformation created by multiplication of matrices. + */ + public static void multiply(Transformation2D a, Transformation2D b, + Transformation2D result) { + double xx, xy, xd, yx, yy, yd; + + xx = a.xx * b.xx + a.yx * b.xy; + xy = a.xy * b.xx + a.yy * b.xy; + xd = a.xd * b.xx + a.yd * b.xy + b.xd; + yx = a.xx * b.yx + a.yx * b.yy; + yy = a.xy * b.yx + a.yy * b.yy; + yd = a.xd * b.yx + a.yd * b.yy + b.yd; + + result.xx = xx; + result.xy = xy; + result.xd = xd; + result.yx = yx; + result.yy = yy; + result.yd = yd; + } + + /** + * Returns a copy of the Transformation2D object. + * + * @return A copy of this object. + */ + public Transformation2D copy() { + Transformation2D result = new Transformation2D(); + result.xx = xx; + result.xy = xy; + result.xd = xd; + result.yx = yx; + result.yy = yy; + result.yd = yd; + return result; + } + + /** + * Writes the matrix coefficients in the order XX, XY, XD, YX, YY, YD into + * the given array. + * + * @param coefs The array into which the coefficients are returned. Should be + * of size 6 elements. + */ + public void getCoefficients(double[] coefs) { + if (coefs.length < 6) + throw new GeometryException( + "Buffer is too small. coefs needs 6 members"); + + coefs[0] = xx; + coefs[1] = xy; + coefs[2] = xd; + coefs[3] = yx; + coefs[4] = yy; + coefs[5] = yd; + } + + /** + * Transforms envelope + * + * @param env The envelope that is to be transformed + */ + void transform(Envelope2D env) { + + if (env.isEmpty()) + return; + + Point2D[] buf = new Point2D[4]; + env.queryCorners(buf); + transform(buf, buf); + env.setFromPoints(buf, 4); + } + + void transform(Point2D[] pointsIn, Point2D[] pointsOut) { + for (int i = 0; i < pointsIn.length; i++) { + Point2D res = new Point2D(); + Point2D p = pointsIn[i]; + res.x = xx * p.x + xy * p.y + xd; + res.y = yx * p.x + yy * p.y + yd; + pointsOut[i] = res; + } + } + + /** + * Initialize transformation from two rectangles. + */ + void initializeFromRect(Envelope2D src, Envelope2D dest) { + if (src.isEmpty() || dest.isEmpty() || 0 == src.getWidth() + || 0 == src.getHeight()) + setZero(); + else { + xy = yx = 0; + xx = dest.getWidth() / src.getWidth(); + yy = dest.getHeight() / src.getHeight(); + xd = dest.xmin - src.xmin * xx; + yd = dest.ymin - src.ymin * yy; + } + } + + /** + * Initializes an orhtonormal transformation from the Src and Dest + * rectangles. + *

+ * The result transformation proportionally fits the Src into the Dest. The + * center of the Src will be in the center of the Dest. + */ + void initializeFromRectIsotropic(Envelope2D src, Envelope2D dest) { + + if (src.isEmpty() || dest.isEmpty() || 0 == src.getWidth() + || 0 == src.getHeight()) + setZero(); + else { + yx = 0; + xy = 0; + xx = dest.getWidth() / src.getWidth(); + yy = dest.getHeight() / src.getHeight(); + if (xx > yy) + xx = yy; + else + yy = xx; + + Point2D destCenter = dest.getCenter(); + Point2D srcCenter = src.getCenter(); + xd = destCenter.x - srcCenter.x * xx; + yd = destCenter.y - srcCenter.y * yy; + } + } + + /** + * Initializes transformation from Position, Tangent vector and offset + * value. Tangent vector must have unity length + */ + void initializeFromCurveParameters(Point2D Position, Point2D Tangent, + double Offset) { + // TODO + } + + /** + * Transforms size. + *

+ * Creates an AABB with width of SizeSrc.x and height of SizeSrc.y. + * Transforms that AABB and gets a quadrangle in new coordinate system. The + * result x contains the length of the quadrangle edge, which were parallel + * to X in the original system, and y contains the length of the edge, that + * were parallel to the Y axis in the original system. + */ + Point2D transformSize(Point2D SizeSrc) { + Point2D pt = new Point2D(); + pt.x = Math.sqrt(xx * xx + yx * yx) * SizeSrc.x; + pt.y = Math.sqrt(xy * xy + yy * yy) * SizeSrc.y; + return pt; + } + + /** + * Transforms a tolerance value. + * + * @param tolerance The tolerance value. + */ + public double transform(double tolerance) { + // the function should be implemented as follows: find encompassing + // circle for the transformed circle of radius = Tolerance. + + // this is approximation. + Point2D pt1 = new Point2D(); + Point2D pt2 = new Point2D(); + /* * pt[0].Set(0, 0); pt[1].Set(1, 0); pt[2].Set(0, 1); Transform(pt); * pt[1] -= pt[0]; pt[2] -= pt[0]; */ - pt1.setCoords(xx, yx); - pt2.setCoords(xy, yy); - pt1.sub(pt1); - double d1 = pt1.sqrLength() * 0.5; - pt1.setCoords(xx, yx); - pt2.setCoords(xy, yy); - pt1.add(pt2); - double d2 = pt1.sqrLength() * 0.5; - return tolerance * ((d1 > d2) ? Math.sqrt(d1) : Math.sqrt(d2)); - } - - // Performs linear part of the transformation only. Same as if xd, yd would - // be zeroed. - void transformWithoutShift(Point2D[] pointsIn, int from, int count, - Point2D[] pointsOut) { - for (int i = from, n = from + count; i < n; i++) { - Point2D p = pointsIn[i]; - double new_x = xx * p.x + xy * p.y; - double new_y = yx * p.x + yy * p.y; - pointsOut[i].setCoords(new_x, new_y); - } - } - - Point2D transformWithoutShift(Point2D srcPoint) { - double new_x = xx * srcPoint.x + xy * srcPoint.y; - double new_y = yx * srcPoint.x + yy * srcPoint.y; - return Point2D.construct(new_x, new_y); - } - - /** - * Sets this matrix to be the identity matrix. - */ - public void setIdentity() { - xx = 1.0; - xy = 0; - xd = 0; - yx = 0; - yy = 1.0; - yd = 0; - } - - /** - * Returns TRUE if this matrix is the identity matrix. - */ - public boolean isIdentity() { - return xx == 1.0 && yy == 1.0 - && (0 == xy && 0 == xd && 0 == yx && 0 == yd); - } - - /** - * Returns TRUE if this matrix is an identity matrix within the given - * tolerance. - * - * @param tol The tolerance value. - */ - public boolean isIdentity(double tol) { - Point2D pt = Point2D.construct(0., 1.); - transform(pt, pt); - pt.sub(Point2D.construct(0., 1.)); - if (pt.sqrLength() > tol * tol) - return false; - - pt.setCoords(0, 0); - transform(pt, pt); - if (pt.sqrLength() > tol * tol) - return false; - - pt.setCoords(1.0, 0.0); - transform(pt, pt); - pt.sub(Point2D.construct(1.0, 0.0)); - return pt.sqrLength() <= tol * tol; - } - - /** - * Returns TRUE for reflective transformations. It inverts the sign of - * vector cross product. - */ - public boolean isReflective() { - return xx * yy - yx * xy < 0; - } - - /** - * Returns TRUE if this transformation is a uniform transformation. - *

- * The uniform transformation is a transformation, which transforms a square - * to a square. - */ - public boolean isUniform(double eps) { - double v1 = xx * xx + yx * yx; - double v2 = xy * xy + yy * yy; - double e = (v1 + v2) * eps; - return Math.abs(v1 - v2) <= e && Math.abs(xx * xy + yx * yy) <= e; - } - - /** - * Returns TRUE if this transformation is a shift transformation. The shift - * transformation performs shift only. - */ - public boolean isShift() { - return xx == 1.0 && yy == 1.0 && 0 == xy && 0 == yx; - } - - /** - * Returns TRUE if this transformation is a shift transformation within the - * given tolerance. - * - * @param tol The tolerance value. - */ - public boolean isShift(double tol) { - Point2D pt = transformWithoutShift(Point2D.construct(0.0, 1.0)); - pt.y -= 1.0; - if (pt.sqrLength() > tol * tol) - return false; - - pt = transformWithoutShift(Point2D.construct(1.0, 0.0)); - pt.x -= 1.0; - return pt.sqrLength() <= tol * tol; - } - - /** - * Returns TRUE if this is an orthonormal transformation with the given - * tolerance. The orthonormal: Rotation or rotoinversion and shift - * (preserves lengths of vectors and angles between vectors). - * - * @param tol The tolerance value. - */ - public boolean isOrthonormal(double tol) { - Transformation2D r = new Transformation2D(); - r.xx = xx * xx + xy * xy; - r.xy = xx * yx + xy * yy; - r.yx = yx * xx + yy * xy; - r.yy = yx * yx + yy * yy; - r.xd = 0; - r.yd = 0; - - return r.isIdentity(tol); - } - - /** - * Returns TRUE if this matrix is degenerated (does not have an inverse) - * within the given tolerance. - * - * @param tol The tolerance value. - */ - public boolean isDegenerate(double tol) { - return Math.abs(xx * yy - yx * xy) <= 2 * tol - * (Math.abs(xx * yy) + Math.abs(yx * xy)); - } - - /** - * Returns TRUE, if this transformation does not have rotation and shear - * within the given tolerance. - * - * @param tol The tolerance value. - */ - public boolean isScaleAndShift(double tol) { - return xy * xy + yx * yx < (xx * xx + yy * yy) * tol; - } - - /** - * Set this transformation to be a shift. - * - * @param x The X coordinate to shift to. - * @param y The Y coordinate to shift to. - */ - public void setShift(double x, double y) { - xx = 1; - xy = 0; - xd = x; - yx = 0; - yy = 1; - yd = y; - } - - /** - * Set this transformation to be a scale. - * - * @param x The X coordinate to scale to. - * @param y The Y coordinate to scale to. - */ - public void setScale(double x, double y) { - xx = x; - xy = 0; - xd = 0; - yx = 0; - yy = y; - yd = 0; - } - - /** - * Set transformation to be a uniform scale. - * - * @param _scale The scale of the transformation. - */ - public void setScale(double _scale) { - setScale(_scale, _scale); - } - - /** - * Sets the transformation to be a flip around the X axis. Flips the X - * coordinates so that the x0 becomes x1 and vice verse. - * - * @param x0 The X coordinate to flip. - * @param x1 The X coordinate to flip to. - */ - public void setFlipX(double x0, double x1) { - xx = -1; - xy = 0; - xd = x0 + x1; - yx = 0; - yy = 1; - yd = 0; - } - - /** - * Sets the transformation to be a flip around the Y axis. Flips the Y - * coordinates so that the y0 becomes y1 and vice verse. - * - * @param y0 The Y coordinate to flip. - * @param y1 The Y coordinate to flip to. - */ - public void setFlipY(double y0, double y1) { - xx = 1; - xy = 0; - xd = 0; - yx = 0; - yy = -1; - yd = y0 + y1; - } - - /** - * Set transformation to a shear. - * - * @param proportionX The proportion of shearing in x direction. - * @param proportionY The proportion of shearing in y direction. - */ - public void setShear(double proportionX, double proportionY) { - xx = 1; - xy = proportionX; - xd = 0; - yx = proportionY; - yy = 1; - yd = 0; - } - - /** - * Sets this transformation to be a rotation around point (0, 0). - *

- * When the axis Y is directed up and X is directed to the right, the - * positive angle corresponds to the anti-clockwise rotation. When the axis - * Y is directed down and X is directed to the right, the positive angle - * corresponds to the clockwise rotation. - * - * @param angle_in_Radians The rotation angle in radian. - */ - public void setRotate(double angle_in_Radians) { - setRotate(Math.cos(angle_in_Radians), Math.sin(angle_in_Radians)); - } - - /** - * Produces a transformation that swaps x and y coordinate values. xx = 0.0; - * xy = 1.0; xd = 0; yx = 1.0; yy = 0.0; yd = 0; - */ - Transformation2D setSwapCoordinates() { - xx = 0.0; - xy = 1.0; - xd = 0; - yx = 1.0; - yy = 0.0; - yd = 0; - return this; - } - - /** - * Sets this transformation to be a rotation around point rotationCenter. - *

- * When the axis Y is directed up and X is directed to the right, the - * positive angle corresponds to the anti-clockwise rotation. When the axis - * Y is directed down and X is directed to the right, the positive angle - * corresponds to the clockwise rotation. - * - * @param angle_in_Radians The rotation angle in radian. - * @param rotationCenter The center point of the rotation. - */ - void setRotate(double angle_in_Radians, Point2D rotationCenter) { - setRotate(Math.cos(angle_in_Radians), Math.sin(angle_in_Radians), - rotationCenter); - } - - /** - * Sets rotation for this transformation. - *

- * When the axis Y is directed up and X is directed to the right, the - * positive angle corresponds to the anti-clockwise rotation. When the axis - * Y is directed down and X is directed to the right, the positive angle - * corresponds to the clockwise rotation. - * - * @param cosA The rotation angle. - * @param sinA The rotation angle. - */ - - public void setRotate(double cosA, double sinA) { - xx = cosA; - xy = -sinA; - xd = 0; - yx = sinA; - yy = cosA; - yd = 0; - } - - /** - * Sets this transformation to be a rotation around point rotationCenter. - *

- * When the axis Y is directed up and X is directed to the right, the - * positive angle corresponds to the anti-clockwise rotation. When the axis - * Y is directed down and X is directed to the right, the positive angle - * corresponds to the clockwise rotation. - * - * @param cosA The cos of the rotation angle. - * @param sinA The sin of the rotation angle. - * @param rotationCenter The center point of the rotation. - */ - void setRotate(double cosA, double sinA, Point2D rotationCenter) { - setShift(-rotationCenter.x, -rotationCenter.y); - Transformation2D temp = new Transformation2D(); - temp.setRotate(cosA, sinA); - multiply(temp); - shift(rotationCenter.x, rotationCenter.y); - } - - /** - * Shifts the transformation. - * - * @param x The shift factor in X direction. - * @param y The shift factor in Y direction. - */ - public void shift(double x, double y) { - xd += x; - yd += y; - } - - /** - * Scales the transformation. - * - * @param x The scale factor in X direction. - * @param y The scale factor in Y direction. - */ - public void scale(double x, double y) { - xx *= x; - xy *= x; - xd *= x; - yx *= y; - yy *= y; - yd *= y; - } - - /** - * Flips the transformation around the X axis. - * - * @param x0 The X coordinate to flip. - * @param x1 The X coordinate to flip to. - */ - public void flipX(double x0, double x1) { - xx = -xx; - xy = -xy; - xd = x0 + x1 - xd; - } - - /** - * Flips the transformation around the Y axis. - * - * @param y0 The Y coordinate to flip. - * @param y1 The Y coordinate to flip to. - */ - public void flipY(double y0, double y1) { - yx = -yx; - yy = -yy; - yd = y0 + y1 - yd; - } - - /** - * Shears the transformation. - * - * @param proportionX The proportion of shearing in x direction. - * @param proportionY The proportion of shearing in y direction. - */ - public void shear(double proportionX, double proportionY) { - Transformation2D temp = new Transformation2D(); - temp.setShear(proportionX, proportionY); - multiply(temp); - } - - /** - * Rotates the transformation. - * - * @param angle_in_Radians The rotation angle in radian. - */ - public void rotate(double angle_in_Radians) { - Transformation2D temp = new Transformation2D(); - temp.setRotate(angle_in_Radians); - multiply(temp); - } - - /** - * Rotates the transformation. - * - * @param cos The cos angle of the rotation. - * @param sin The sin angle of the rotation. - */ - public void rotate(double cos, double sin) { - Transformation2D temp = new Transformation2D(); - temp.setRotate(cos, sin); - multiply(temp); - } - - /** - * Rotates the transformation aroung a center point. - * - * @param cos The cos angle of the rotation. - * @param sin sin angle of the rotation. - * @param rotationCenter The center point of the rotation. - */ - public void rotate(double cos, double sin, Point2D rotationCenter) { - Transformation2D temp = new Transformation2D(); - temp.setRotate(cos, sin, rotationCenter); - multiply(temp); - } - - /** - * Produces inverse matrix for this matrix and puts result into the inverse - * parameter. - * - * @param inverse The result inverse matrix. - */ - public void inverse(Transformation2D inverse) { - double det = xx * yy - xy * yx; - - if (det == 0) { - inverse.setZero(); - return; - } - - det = 1 / det; - - inverse.xd = (xy * yd - xd * yy) * det; - inverse.yd = (xd * yx - xx * yd) * det; - inverse.xx = yy * det; - inverse.xy = -xy * det; - inverse.yx = -yx * det; - inverse.yy = xx * det; - } - - /** - * Inverses the matrix. - */ - public void inverse() { - inverse(this); - } - - /** - * Extracts scaling part of the transformation. this == scale * - * rotateNshearNshift. - * - * @param scale The destination matrix where the scale part is copied. - * @param rotateNshearNshift The destination matrix where the part excluding rotation is - * copied. - */ - public void extractScaleTransform(Transformation2D scale, - Transformation2D rotateNshearNshift) { - - scale.setScale(Math.sqrt(xx * xx + xy * xy), - Math.sqrt(yx * yx + yy * yy)); - rotateNshearNshift.setScale(1.0 / scale.xx, 1.0 / scale.yy); - rotateNshearNshift.multiply(this); - } + pt1.setCoords(xx, yx); + pt2.setCoords(xy, yy); + pt1.sub(pt1); + double d1 = pt1.sqrLength() * 0.5; + pt1.setCoords(xx, yx); + pt2.setCoords(xy, yy); + pt1.add(pt2); + double d2 = pt1.sqrLength() * 0.5; + return tolerance * ((d1 > d2) ? Math.sqrt(d1) : Math.sqrt(d2)); + } + + // Performs linear part of the transformation only. Same as if xd, yd would + // be zeroed. + void transformWithoutShift(Point2D[] pointsIn, int from, int count, + Point2D[] pointsOut) { + for (int i = from, n = from + count; i < n; i++) { + Point2D p = pointsIn[i]; + double new_x = xx * p.x + xy * p.y; + double new_y = yx * p.x + yy * p.y; + pointsOut[i].setCoords(new_x, new_y); + } + } + + Point2D transformWithoutShift(Point2D srcPoint) { + double new_x = xx * srcPoint.x + xy * srcPoint.y; + double new_y = yx * srcPoint.x + yy * srcPoint.y; + return Point2D.construct(new_x, new_y); + } + + /** + * Sets this matrix to be the identity matrix. + */ + public void setIdentity() { + xx = 1.0; + xy = 0; + xd = 0; + yx = 0; + yy = 1.0; + yd = 0; + } + + /** + * Returns TRUE if this matrix is the identity matrix. + */ + public boolean isIdentity() { + return xx == 1.0 && yy == 1.0 + && (0 == xy && 0 == xd && 0 == yx && 0 == yd); + } + + /** + * Returns TRUE if this matrix is an identity matrix within the given + * tolerance. + * + * @param tol The tolerance value. + */ + public boolean isIdentity(double tol) { + Point2D pt = Point2D.construct(0., 1.); + transform(pt, pt); + pt.sub(Point2D.construct(0., 1.)); + if (pt.sqrLength() > tol * tol) + return false; + + pt.setCoords(0, 0); + transform(pt, pt); + if (pt.sqrLength() > tol * tol) + return false; + + pt.setCoords(1.0, 0.0); + transform(pt, pt); + pt.sub(Point2D.construct(1.0, 0.0)); + return pt.sqrLength() <= tol * tol; + } + + /** + * Returns TRUE for reflective transformations. It inverts the sign of + * vector cross product. + */ + public boolean isReflective() { + return xx * yy - yx * xy < 0; + } + + /** + * Returns TRUE if this transformation is a uniform transformation. + *

+ * The uniform transformation is a transformation, which transforms a square + * to a square. + */ + public boolean isUniform(double eps) { + double v1 = xx * xx + yx * yx; + double v2 = xy * xy + yy * yy; + double e = (v1 + v2) * eps; + return Math.abs(v1 - v2) <= e && Math.abs(xx * xy + yx * yy) <= e; + } + + /** + * Returns TRUE if this transformation is a shift transformation. The shift + * transformation performs shift only. + */ + public boolean isShift() { + return xx == 1.0 && yy == 1.0 && 0 == xy && 0 == yx; + } + + /** + * Returns TRUE if this transformation is a shift transformation within the + * given tolerance. + * + * @param tol The tolerance value. + */ + public boolean isShift(double tol) { + Point2D pt = transformWithoutShift(Point2D.construct(0.0, 1.0)); + pt.y -= 1.0; + if (pt.sqrLength() > tol * tol) + return false; + + pt = transformWithoutShift(Point2D.construct(1.0, 0.0)); + pt.x -= 1.0; + return pt.sqrLength() <= tol * tol; + } + + /** + * Returns TRUE if this is an orthonormal transformation with the given + * tolerance. The orthonormal: Rotation or rotoinversion and shift + * (preserves lengths of vectors and angles between vectors). + * + * @param tol The tolerance value. + */ + public boolean isOrthonormal(double tol) { + Transformation2D r = new Transformation2D(); + r.xx = xx * xx + xy * xy; + r.xy = xx * yx + xy * yy; + r.yx = yx * xx + yy * xy; + r.yy = yx * yx + yy * yy; + r.xd = 0; + r.yd = 0; + + return r.isIdentity(tol); + } + + /** + * Returns TRUE if this matrix is degenerated (does not have an inverse) + * within the given tolerance. + * + * @param tol The tolerance value. + */ + public boolean isDegenerate(double tol) { + return Math.abs(xx * yy - yx * xy) <= 2 * tol + * (Math.abs(xx * yy) + Math.abs(yx * xy)); + } + + /** + * Returns TRUE, if this transformation does not have rotation and shear + * within the given tolerance. + * + * @param tol The tolerance value. + */ + public boolean isScaleAndShift(double tol) { + return xy * xy + yx * yx < (xx * xx + yy * yy) * tol; + } + + /** + * Set this transformation to be a shift. + * + * @param x The X coordinate to shift to. + * @param y The Y coordinate to shift to. + */ + public void setShift(double x, double y) { + xx = 1; + xy = 0; + xd = x; + yx = 0; + yy = 1; + yd = y; + } + + /** + * Set this transformation to be a scale. + * + * @param x The X coordinate to scale to. + * @param y The Y coordinate to scale to. + */ + public void setScale(double x, double y) { + xx = x; + xy = 0; + xd = 0; + yx = 0; + yy = y; + yd = 0; + } + + /** + * Set transformation to be a uniform scale. + * + * @param _scale The scale of the transformation. + */ + public void setScale(double _scale) { + setScale(_scale, _scale); + } + + /** + * Sets the transformation to be a flip around the X axis. Flips the X + * coordinates so that the x0 becomes x1 and vice verse. + * + * @param x0 The X coordinate to flip. + * @param x1 The X coordinate to flip to. + */ + public void setFlipX(double x0, double x1) { + xx = -1; + xy = 0; + xd = x0 + x1; + yx = 0; + yy = 1; + yd = 0; + } + + /** + * Sets the transformation to be a flip around the Y axis. Flips the Y + * coordinates so that the y0 becomes y1 and vice verse. + * + * @param y0 The Y coordinate to flip. + * @param y1 The Y coordinate to flip to. + */ + public void setFlipY(double y0, double y1) { + xx = 1; + xy = 0; + xd = 0; + yx = 0; + yy = -1; + yd = y0 + y1; + } + + /** + * Set transformation to a shear. + * + * @param proportionX The proportion of shearing in x direction. + * @param proportionY The proportion of shearing in y direction. + */ + public void setShear(double proportionX, double proportionY) { + xx = 1; + xy = proportionX; + xd = 0; + yx = proportionY; + yy = 1; + yd = 0; + } + + /** + * Sets this transformation to be a rotation around point (0, 0). + *

+ * When the axis Y is directed up and X is directed to the right, the + * positive angle corresponds to the anti-clockwise rotation. When the axis + * Y is directed down and X is directed to the right, the positive angle + * corresponds to the clockwise rotation. + * + * @param angle_in_Radians The rotation angle in radian. + */ + public void setRotate(double angle_in_Radians) { + setRotate(Math.cos(angle_in_Radians), Math.sin(angle_in_Radians)); + } + + /** + * Produces a transformation that swaps x and y coordinate values. xx = 0.0; + * xy = 1.0; xd = 0; yx = 1.0; yy = 0.0; yd = 0; + */ + Transformation2D setSwapCoordinates() { + xx = 0.0; + xy = 1.0; + xd = 0; + yx = 1.0; + yy = 0.0; + yd = 0; + return this; + } + + /** + * Sets this transformation to be a rotation around point rotationCenter. + *

+ * When the axis Y is directed up and X is directed to the right, the + * positive angle corresponds to the anti-clockwise rotation. When the axis + * Y is directed down and X is directed to the right, the positive angle + * corresponds to the clockwise rotation. + * + * @param angle_in_Radians The rotation angle in radian. + * @param rotationCenter The center point of the rotation. + */ + void setRotate(double angle_in_Radians, Point2D rotationCenter) { + setRotate(Math.cos(angle_in_Radians), Math.sin(angle_in_Radians), + rotationCenter); + } + + /** + * Sets rotation for this transformation. + *

+ * When the axis Y is directed up and X is directed to the right, the + * positive angle corresponds to the anti-clockwise rotation. When the axis + * Y is directed down and X is directed to the right, the positive angle + * corresponds to the clockwise rotation. + * + * @param cosA The rotation angle. + * @param sinA The rotation angle. + */ + + public void setRotate(double cosA, double sinA) { + xx = cosA; + xy = -sinA; + xd = 0; + yx = sinA; + yy = cosA; + yd = 0; + } + + /** + * Sets this transformation to be a rotation around point rotationCenter. + *

+ * When the axis Y is directed up and X is directed to the right, the + * positive angle corresponds to the anti-clockwise rotation. When the axis + * Y is directed down and X is directed to the right, the positive angle + * corresponds to the clockwise rotation. + * + * @param cosA The cos of the rotation angle. + * @param sinA The sin of the rotation angle. + * @param rotationCenter The center point of the rotation. + */ + void setRotate(double cosA, double sinA, Point2D rotationCenter) { + setShift(-rotationCenter.x, -rotationCenter.y); + Transformation2D temp = new Transformation2D(); + temp.setRotate(cosA, sinA); + multiply(temp); + shift(rotationCenter.x, rotationCenter.y); + } + + /** + * Shifts the transformation. + * + * @param x The shift factor in X direction. + * @param y The shift factor in Y direction. + */ + public void shift(double x, double y) { + xd += x; + yd += y; + } + + /** + * Scales the transformation. + * + * @param x The scale factor in X direction. + * @param y The scale factor in Y direction. + */ + public void scale(double x, double y) { + xx *= x; + xy *= x; + xd *= x; + yx *= y; + yy *= y; + yd *= y; + } + + /** + * Flips the transformation around the X axis. + * + * @param x0 The X coordinate to flip. + * @param x1 The X coordinate to flip to. + */ + public void flipX(double x0, double x1) { + xx = -xx; + xy = -xy; + xd = x0 + x1 - xd; + } + + /** + * Flips the transformation around the Y axis. + * + * @param y0 The Y coordinate to flip. + * @param y1 The Y coordinate to flip to. + */ + public void flipY(double y0, double y1) { + yx = -yx; + yy = -yy; + yd = y0 + y1 - yd; + } + + /** + * Shears the transformation. + * + * @param proportionX The proportion of shearing in x direction. + * @param proportionY The proportion of shearing in y direction. + */ + public void shear(double proportionX, double proportionY) { + Transformation2D temp = new Transformation2D(); + temp.setShear(proportionX, proportionY); + multiply(temp); + } + + /** + * Rotates the transformation. + * + * @param angle_in_Radians The rotation angle in radian. + */ + public void rotate(double angle_in_Radians) { + Transformation2D temp = new Transformation2D(); + temp.setRotate(angle_in_Radians); + multiply(temp); + } + + /** + * Rotates the transformation. + * + * @param cos The cos angle of the rotation. + * @param sin The sin angle of the rotation. + */ + public void rotate(double cos, double sin) { + Transformation2D temp = new Transformation2D(); + temp.setRotate(cos, sin); + multiply(temp); + } + + /** + * Rotates the transformation aroung a center point. + * + * @param cos The cos angle of the rotation. + * @param sin sin angle of the rotation. + * @param rotationCenter The center point of the rotation. + */ + public void rotate(double cos, double sin, Point2D rotationCenter) { + Transformation2D temp = new Transformation2D(); + temp.setRotate(cos, sin, rotationCenter); + multiply(temp); + } + + /** + * Produces inverse matrix for this matrix and puts result into the inverse + * parameter. + * + * @param inverse The result inverse matrix. + */ + public void inverse(Transformation2D inverse) { + double det = xx * yy - xy * yx; + + if (det == 0) { + inverse.setZero(); + return; + } + + det = 1 / det; + + inverse.xd = (xy * yd - xd * yy) * det; + inverse.yd = (xd * yx - xx * yd) * det; + inverse.xx = yy * det; + inverse.xy = -xy * det; + inverse.yx = -yx * det; + inverse.yy = xx * det; + } + + /** + * Inverses the matrix. + */ + public void inverse() { + inverse(this); + } + + /** + * Extracts scaling part of the transformation. this == scale * + * rotateNshearNshift. + * + * @param scale The destination matrix where the scale part is copied. + * @param rotateNshearNshift The destination matrix where the part excluding rotation is + * copied. + */ + public void extractScaleTransform(Transformation2D scale, + Transformation2D rotateNshearNshift) { + + scale.setScale(Math.sqrt(xx * xx + xy * xy), + Math.sqrt(yx * yx + yy * yy)); + rotateNshearNshift.setScale(1.0 / scale.xx, 1.0 / scale.yy); + rotateNshearNshift.multiply(this); + } } diff --git a/src/main/java/com/esri/core/geometry/Transformation3D.java b/src/main/java/com/esri/core/geometry/Transformation3D.java index 3890c370..1ac7465e 100644 --- a/src/main/java/com/esri/core/geometry/Transformation3D.java +++ b/src/main/java/com/esri/core/geometry/Transformation3D.java @@ -35,228 +35,228 @@ */ final class Transformation3D { - public double xx, yx, zx, xd, xy, yy, zy, yd, xz, yz, zz, zd; - - public Transformation3D() { - - } - - /** - * Sets all elements to 0, thus producing and invalid transformation. - */ - public void setZero() { - xx = 0.0; - yx = 0.0; - zx = 0.0; - xy = 0.0; - yy = 0.0; - zy = 0.0; - xz = 0.0; - yz = 0.0; - zz = 0.0; - xd = 0.0; - yd = 0.0; - zd = 0.0; - } - - public void setScale(double scaleX, double scaleY, double scaleZ) { - xx = scaleX; - yx = 0.0; - zx = 0.0; - xy = 0.0; - yy = scaleY; - zy = 0.0; - xz = 0.0; - yz = 0.0; - zz = scaleZ; - xd = 0.0; - yd = 0.0; - zd = 0.0; - } - - public void setTranslate(double deltax, double deltay, double deltaz) { - xx = 1.0; - yx = 0.0; - zx = 0.0; - xy = 0.0; - yy = 1.0; - zy = 0.0; - xz = 0.0; - yz = 0.0; - zz = 1.0; - xd = deltax; - yd = deltay; - zd = deltaz; - } - - public void translate(double deltax, double deltay, double deltaz) { - xd += deltax; - yd += deltay; - zd += deltaz; - } - - /** - * Transforms an envelope. The result is the bounding box of the transformed - * envelope. - */ - public Envelope3D transform(Envelope3D env) { - - if (env.isEmpty()) - return env; - - Point3D[] buf = new Point3D[8]; - env.queryCorners(buf); - - transform(buf, 8, buf); - env.setFromPoints(buf); - return env; - } - - void transform(Point3D[] pointsIn, int count, Point3D[] pointsOut) { - for (int i = 0; i < count; i++) { - Point3D res = new Point3D(); - Point3D src = pointsIn[i]; - res.x = xx * src.x + xy * src.y + xz * src.z + xd; - res.y = yx * src.x + yy * src.y + yz * src.z + yd; - res.z = zx * src.x + zy * src.y + zz * src.z + zd; - pointsOut[i] = res; - } - } - - public Point3D transform(Point3D src) { - Point3D res = new Point3D(); - res.x = xx * src.x + xy * src.y + xz * src.z + xd; - res.y = yx * src.x + yy * src.y + yz * src.z + yd; - res.z = zx * src.x + zy * src.y + zz * src.z + zd; - return res; - } - - public void transform(Point3D[] points, int start, int count) { - int n = Math.min(points.length, start + count); - for (int i = start; i < n; i++) { - Point3D res = new Point3D(); - Point3D src = points[i]; - res.x = xx * src.x + xy * src.y + xz * src.z + xd; - res.y = yx * src.x + yy * src.y + yz * src.z + yd; - res.z = zx * src.x + zy * src.y + zz * src.z + zd; - points[i] = res; - } - } - - public void mul(Transformation3D right) { - multiply(this, right, this); - } - - public void mulLeft(Transformation3D left) { - multiply(left, this, this); - } - - /** - * Performs multiplication of matrices a and b and places result into - * result. The a, b, and result could point to same objects.
- * Equivalent to result = a * b. - */ - // static - public static void multiply(Transformation3D a, Transformation3D b, - Transformation3D result) { - double xx, yx, zx; - double xy, yy, zy; - double xz, yz, zz; - double xd, yd, zd; - - xx = a.xx * b.xx + a.yx * b.xy + a.zx * b.xz; - yx = a.xx * b.yx + a.yx * b.yy + a.zx * b.yz; - zx = a.xx * b.zx + a.yx * b.zy + a.zx * b.zz; - xy = a.xy * b.xx + a.yy * b.xy + a.zy * b.xz; - yy = a.xy * b.yx + a.yy * b.yy + a.zy * b.yz; - zy = a.xy * b.zx + a.yy * b.zy + a.zy * b.zz; - xz = a.xz * b.xx + a.yz * b.xy + a.zz * b.xz; - yz = a.xz * b.yx + a.yz * b.yy + a.zz * b.yz; - zz = a.xz * b.zx + a.yz * b.zy + a.zz * b.zz; - xd = a.xd * b.xx + a.yd * b.xy + a.zd * b.xz + b.xd; - yd = a.xd * b.yx + a.yd * b.yy + a.zd * b.yz + b.yd; - zd = a.xd * b.zx + a.yd * b.zy + a.zd * b.zz + b.zd; - - result.xx = xx; - result.yx = yx; - result.zx = zx; - result.xy = xy; - result.yy = yy; - result.zy = zy; - result.xz = xz; - result.yz = yz; - result.zz = zz; - result.xd = xd; - result.yd = yd; - result.zd = zd; - } - - /** - * Calculates the Inverse transformation. - * - * @param src The input transformation. - * @param result The inverse of the input transformation. Throws the - * GeometryException("math singularity") exception if the Inverse - * can not be calculated. - */ - public static void inverse(Transformation3D src, Transformation3D result) { - double det = src.xx * (src.yy * src.zz - src.zy * src.yz) - src.yx - * (src.xy * src.zz - src.zy * src.xz) + src.zx - * (src.xy * src.yz - src.yy * src.xz); - if (det != 0) { - double xx, yx, zx; - double xy, yy, zy; - double xz, yz, zz; - double xd, yd, zd; - - double det_1 = 1.0 / det; - xx = (src.yy * src.zz - src.zy * src.yz) * det_1; - xy = -(src.xy * src.zz - src.zy * src.xz) * det_1; - xz = (src.xy * src.yz - src.yy * src.xz) * det_1; - - yx = -(src.yx * src.zz - src.yz * src.zx) * det_1; - yy = (src.xx * src.zz - src.zx * src.xz) * det_1; - yz = -(src.xx * src.yz - src.yx * src.xz) * det_1; - - zx = (src.yx * src.zy - src.zx * src.yy) * det_1; - zy = -(src.xx * src.zy - src.zx * src.xy) * det_1; - zz = (src.xx * src.yy - src.yx * src.xy) * det_1; - - xd = -(src.xd * xx + src.yd * xy + src.zd * xz); - yd = -(src.xd * yx + src.yd * yy + src.zd * yz); - zd = -(src.xd * zx + src.yd * zy + src.zd * zz); - - result.xx = xx; - result.yx = yx; - result.zx = zx; - result.xy = xy; - result.yy = yy; - result.zy = zy; - result.xz = xz; - result.yz = yz; - result.zz = zz; - result.xd = xd; - result.yd = yd; - result.zd = zd; - } else { - throw new GeometryException("math singularity"); - } - } - - public Transformation3D copy() { - Transformation3D result = new Transformation3D(); - result.xx = xx; - result.yx = yx; - result.zx = zx; - result.xy = xy; - result.yy = yy; - result.zy = zy; - result.xz = xz; - result.yz = yz; - result.zz = zz; - result.xd = xd; - result.yd = yd; - result.zd = zd; - return result; - } + public double xx, yx, zx, xd, xy, yy, zy, yd, xz, yz, zz, zd; + + public Transformation3D() { + + } + + /** + * Sets all elements to 0, thus producing and invalid transformation. + */ + public void setZero() { + xx = 0.0; + yx = 0.0; + zx = 0.0; + xy = 0.0; + yy = 0.0; + zy = 0.0; + xz = 0.0; + yz = 0.0; + zz = 0.0; + xd = 0.0; + yd = 0.0; + zd = 0.0; + } + + public void setScale(double scaleX, double scaleY, double scaleZ) { + xx = scaleX; + yx = 0.0; + zx = 0.0; + xy = 0.0; + yy = scaleY; + zy = 0.0; + xz = 0.0; + yz = 0.0; + zz = scaleZ; + xd = 0.0; + yd = 0.0; + zd = 0.0; + } + + public void setTranslate(double deltax, double deltay, double deltaz) { + xx = 1.0; + yx = 0.0; + zx = 0.0; + xy = 0.0; + yy = 1.0; + zy = 0.0; + xz = 0.0; + yz = 0.0; + zz = 1.0; + xd = deltax; + yd = deltay; + zd = deltaz; + } + + public void translate(double deltax, double deltay, double deltaz) { + xd += deltax; + yd += deltay; + zd += deltaz; + } + + /** + * Transforms an envelope. The result is the bounding box of the transformed + * envelope. + */ + public Envelope3D transform(Envelope3D env) { + + if (env.isEmpty()) + return env; + + Point3D[] buf = new Point3D[8]; + env.queryCorners(buf); + + transform(buf, 8, buf); + env.setFromPoints(buf); + return env; + } + + void transform(Point3D[] pointsIn, int count, Point3D[] pointsOut) { + for (int i = 0; i < count; i++) { + Point3D res = new Point3D(); + Point3D src = pointsIn[i]; + res.x = xx * src.x + xy * src.y + xz * src.z + xd; + res.y = yx * src.x + yy * src.y + yz * src.z + yd; + res.z = zx * src.x + zy * src.y + zz * src.z + zd; + pointsOut[i] = res; + } + } + + public Point3D transform(Point3D src) { + Point3D res = new Point3D(); + res.x = xx * src.x + xy * src.y + xz * src.z + xd; + res.y = yx * src.x + yy * src.y + yz * src.z + yd; + res.z = zx * src.x + zy * src.y + zz * src.z + zd; + return res; + } + + public void transform(Point3D[] points, int start, int count) { + int n = Math.min(points.length, start + count); + for (int i = start; i < n; i++) { + Point3D res = new Point3D(); + Point3D src = points[i]; + res.x = xx * src.x + xy * src.y + xz * src.z + xd; + res.y = yx * src.x + yy * src.y + yz * src.z + yd; + res.z = zx * src.x + zy * src.y + zz * src.z + zd; + points[i] = res; + } + } + + public void mul(Transformation3D right) { + multiply(this, right, this); + } + + public void mulLeft(Transformation3D left) { + multiply(left, this, this); + } + + /** + * Performs multiplication of matrices a and b and places result into + * result. The a, b, and result could point to same objects.
+ * Equivalent to result = a * b. + */ + // static + public static void multiply(Transformation3D a, Transformation3D b, + Transformation3D result) { + double xx, yx, zx; + double xy, yy, zy; + double xz, yz, zz; + double xd, yd, zd; + + xx = a.xx * b.xx + a.yx * b.xy + a.zx * b.xz; + yx = a.xx * b.yx + a.yx * b.yy + a.zx * b.yz; + zx = a.xx * b.zx + a.yx * b.zy + a.zx * b.zz; + xy = a.xy * b.xx + a.yy * b.xy + a.zy * b.xz; + yy = a.xy * b.yx + a.yy * b.yy + a.zy * b.yz; + zy = a.xy * b.zx + a.yy * b.zy + a.zy * b.zz; + xz = a.xz * b.xx + a.yz * b.xy + a.zz * b.xz; + yz = a.xz * b.yx + a.yz * b.yy + a.zz * b.yz; + zz = a.xz * b.zx + a.yz * b.zy + a.zz * b.zz; + xd = a.xd * b.xx + a.yd * b.xy + a.zd * b.xz + b.xd; + yd = a.xd * b.yx + a.yd * b.yy + a.zd * b.yz + b.yd; + zd = a.xd * b.zx + a.yd * b.zy + a.zd * b.zz + b.zd; + + result.xx = xx; + result.yx = yx; + result.zx = zx; + result.xy = xy; + result.yy = yy; + result.zy = zy; + result.xz = xz; + result.yz = yz; + result.zz = zz; + result.xd = xd; + result.yd = yd; + result.zd = zd; + } + + /** + * Calculates the Inverse transformation. + * + * @param src The input transformation. + * @param result The inverse of the input transformation. Throws the + * GeometryException("math singularity") exception if the Inverse + * can not be calculated. + */ + public static void inverse(Transformation3D src, Transformation3D result) { + double det = src.xx * (src.yy * src.zz - src.zy * src.yz) - src.yx + * (src.xy * src.zz - src.zy * src.xz) + src.zx + * (src.xy * src.yz - src.yy * src.xz); + if (det != 0) { + double xx, yx, zx; + double xy, yy, zy; + double xz, yz, zz; + double xd, yd, zd; + + double det_1 = 1.0 / det; + xx = (src.yy * src.zz - src.zy * src.yz) * det_1; + xy = -(src.xy * src.zz - src.zy * src.xz) * det_1; + xz = (src.xy * src.yz - src.yy * src.xz) * det_1; + + yx = -(src.yx * src.zz - src.yz * src.zx) * det_1; + yy = (src.xx * src.zz - src.zx * src.xz) * det_1; + yz = -(src.xx * src.yz - src.yx * src.xz) * det_1; + + zx = (src.yx * src.zy - src.zx * src.yy) * det_1; + zy = -(src.xx * src.zy - src.zx * src.xy) * det_1; + zz = (src.xx * src.yy - src.yx * src.xy) * det_1; + + xd = -(src.xd * xx + src.yd * xy + src.zd * xz); + yd = -(src.xd * yx + src.yd * yy + src.zd * yz); + zd = -(src.xd * zx + src.yd * zy + src.zd * zz); + + result.xx = xx; + result.yx = yx; + result.zx = zx; + result.xy = xy; + result.yy = yy; + result.zy = zy; + result.xz = xz; + result.yz = yz; + result.zz = zz; + result.xd = xd; + result.yd = yd; + result.zd = zd; + } else { + throw new GeometryException("math singularity"); + } + } + + public Transformation3D copy() { + Transformation3D result = new Transformation3D(); + result.xx = xx; + result.yx = yx; + result.zx = zx; + result.xy = xy; + result.yy = yy; + result.zy = zy; + result.xz = xz; + result.yz = yz; + result.zz = zz; + result.xd = xd; + result.yd = yd; + result.zd = zd; + return result; + } } diff --git a/src/main/java/com/esri/core/geometry/Treap.java b/src/main/java/com/esri/core/geometry/Treap.java index 3a815530..e67eaf6d 100644 --- a/src/main/java/com/esri/core/geometry/Treap.java +++ b/src/main/java/com/esri/core/geometry/Treap.java @@ -26,945 +26,945 @@ package com.esri.core.geometry; final class Treap { - static abstract class Comparator { - Comparator() { - m_b_notify_on_actions = false; - } - - Comparator(boolean bNotifyOnActions) { - m_b_notify_on_actions = bNotifyOnActions; - } - - // Compares the element elm to the element contained in the given node - abstract int compare(Treap treap, int elm, int node); - - // These virtual methods are called only when Comparator(true) ctro has - // been used. - void onDelete(int elm) { - } - - void onSet(int elm) { - } - - void onEndSearch(int elm) { - } - - void onAddUniqueElementFailed(int elm) { - } - - private boolean m_b_notify_on_actions; - - // void operator=(const Comparator&); // do not allow operator = - void onDeleteImpl_(Treap treap, int node) { - if (m_b_notify_on_actions) - onDelete(treap.getElement(node)); - } - - void onSetImpl_(Treap treap, int node) { - if (m_b_notify_on_actions) - onSet(treap.getElement(node)); - } - - void onAddUniqueElementFailedImpl_(int elm) { - if (m_b_notify_on_actions) - onAddUniqueElementFailed(elm); - } - - void onEndSearchImpl_(int elm) { - if (m_b_notify_on_actions) - onEndSearch(elm); - } - } - - ; - - static abstract class MonikerComparator { - // Compares the moniker, contained in the MonikerComparator with the - // element contained in the given node. - abstract int compare(Treap treap, int node); - } - - ; - - public Treap() { - m_random = 124234251; - m_b_balancing = true; - m_touchFlag = 0; - m_defaultTreap = nullNode(); - m_treapData = new StridedIndexTypeCollection(7); - m_comparator = null; - } - - // Sets the comparator - public void setComparator(Comparator comparator) { - m_comparator = comparator; - } - - // Returns the comparator - public Comparator getComparator() { - return m_comparator; - } - - // Stops auto-balancing - public void disableBalancing() { - m_b_balancing = false; - } - - // Reserves memory for nodes givne number of nodes - public void setCapacity(int capacity) { - m_treapData.setCapacity(capacity); - } - - // Create a new treap and returns the treap handle. - public int createTreap(int treap_data) { - int treap = m_treapData.newElement(); - setSize_(0, treap); - setTreapData_(treap_data, treap); - return treap; - } - - // Deletes the treap at the given treap handle. - public void deleteTreap(int treap) { - m_treapData.deleteElement(treap); - } - - // Adds new element to the treap. Allows duplicates to be added. - public int addElement(int element, int treap) { - int treap_; - if (treap == -1) { - if (m_defaultTreap == nullNode()) - m_defaultTreap = createTreap(-1); - treap_ = m_defaultTreap; - } else { - treap_ = treap; - } - - return addElement_(element, 0, treap_); - } - - // Adds new element to the treap if it is not equal to other elements. - // If the return value is -1, then get_duplicate_element reutrns the node of - // the already existing element equal to element. - public int addUniqueElement(int element, int treap) { - int treap_; - if (treap == -1) { - if (m_defaultTreap == nullNode()) - m_defaultTreap = createTreap(-1); - treap_ = m_defaultTreap; - } else { - treap_ = treap; - } - - return addElement_(element, 1, treap_); - } - - // Adds a new element to the treap that is known to be bigger or equal of - // all elements already in the treap. - // Use this method when adding elements from a sorted list for maximum - // performance (it does not call the treap comparator). - public int addBiggestElement(int element, int treap) { - int treap_; - if (treap == -1) { - if (m_defaultTreap == nullNode()) - m_defaultTreap = createTreap(-1); - treap_ = m_defaultTreap; - } else { - treap_ = treap; - } - - if (getRoot_(treap_) == nullNode()) { - int newNode = newNode_(element); - setRoot_(newNode, treap_); - addToList_(-1, newNode, treap_); - return newNode; - } - - int cur = getLast_(treap_); - int newNode = newNode_(element); - setRight_(cur, newNode); - setParent_(newNode, cur); - assert (m_b_balancing);// don't use this method for unbalanced tree, or - // the performance will be bad. - bubbleUp_(newNode); - if (getParent(newNode) == nullNode()) - setRoot_(newNode, treap_); - - addToList_(-1, newNode, treap_); - return newNode; - } - - // template void build_from_sorted(const Iterator& begin, - // const Iterator& end); - // Adds new element to the treap at the known position, thus avoiding a call - // to the comparator. - // If bCallCompare is True, the comparator will be called at most twice, - // once to compare with prevElement and once to compare with nextElement. - // When bUnique is true, if the return value is -1, then - // get_duplicate_element reutrns the node of the already existing element. - public int addElementAtPosition(int prevNode, int nextNode, int element, - boolean bUnique, boolean bCallCompare, int treap) { - int treap_ = treap; - if (treap_ == -1) { - if (m_defaultTreap == nullNode()) - m_defaultTreap = createTreap(-1); - treap_ = m_defaultTreap; - } - - // dbg_check_(m_root); - if (getRoot_(treap_) == nullNode()) { - assert (nextNode == nullNode() && prevNode == nullNode()); - int root = newNode_(element); - setRoot_(root, treap_); - addToList_(-1, root, treap_); - return root; - } - - int cmpNext; - int cmpPrev; - if (bCallCompare) { - cmpNext = nextNode != nullNode() ? m_comparator.compare(this, - element, nextNode) : -1; - assert (cmpNext <= 0); - cmpPrev = prevNode != nullNode() ? m_comparator.compare(this, - element, prevNode) : 1; - // cmpPrev can be negative in plane sweep when intersection is - // detected. - } else { - cmpNext = -1; - cmpPrev = 1; - } - - if (bUnique && (cmpNext == 0 || cmpPrev == 0)) { - m_comparator.onAddUniqueElementFailedImpl_(element); - int cur = cmpNext == 0 ? nextNode : prevNode; - setDuplicateElement_(cur, treap_); - return -1;// return negative value. - } - - int cur; - int cmp; - boolean bNext; - if (nextNode != nullNode() && prevNode != nullNode()) { - // randomize the the cost to insert a node. - bNext = m_random > NumberUtils.nextRand(m_random) >> 1; - } else - bNext = nextNode != nullNode(); - - if (bNext) { - cmp = cmpNext; - cur = nextNode; - } else { - cmp = cmpPrev; - cur = prevNode; - } - - int newNode = -1; - int before = -1; - boolean b_first = true; - for (; ; ) { - if (cmp < 0) { - int left = getLeft(cur); - if (left != nullNode()) - cur = left; - else { - before = cur; - newNode = newNode_(element); - setLeft_(cur, newNode); - setParent_(newNode, cur); - break; - } - } else { - int right = getRight(cur); - if (right != nullNode()) - cur = right; - else { - before = getNext(cur); - newNode = newNode_(element); - setRight_(cur, newNode); - setParent_(newNode, cur); - break; - } - } - - if (b_first) { - cmp *= -1; - b_first = false; - } - } - - bubbleUp_(newNode); - if (getParent(newNode) == nullNode()) - setRoot_(newNode, treap_); - - addToList_(before, newNode, treap_); - // dbg_check_(m_root); - return newNode; - } - - // Get duplicate element - public int getDuplicateElement(int treap) { - if (treap == -1) - return getDuplicateElement_(m_defaultTreap); - - return getDuplicateElement_(treap); - } - - // Removes a node from the treap. Throws if doesn't exist. - public void deleteNode(int treap_node_index, int treap) { - touch_(); - // assert(isValidNode(treap_node_index)); - if (m_comparator != null) - m_comparator.onDeleteImpl_(this, treap_node_index); - - int treap_; - if (treap == -1) - treap_ = m_defaultTreap; - else - treap_ = treap; - - if (!m_b_balancing) { - unbalancedDelete_(treap_node_index, treap_); - } else - deleteNode_(treap_node_index, treap_); - } - - // Finds an element in the treap and returns its node or -1. - public int search(int data, int treap) { - int cur = getRoot(treap); - while (cur != nullNode()) { - int res = m_comparator.compare(this, data, cur); - if (res == 0) - return cur; - else if (res < 0) - cur = getLeft(cur); - else - cur = getRight(cur); - } - - m_comparator.onEndSearchImpl_(data); - return nullNode(); - } - - // Find a first node in the treap which is less or equal the moniker. - // Returns closest smaller (Comparator::compare returns -1) or any equal. - public int searchLowerBound(MonikerComparator moniker, int treap) { - int cur = getRoot(treap); - int bound = -1; - while (cur != nullNode()) { - int res = moniker.compare(this, cur); - if (res == 0) - return cur; - else if (res < 0) - cur = getLeft(cur); - else { - bound = cur; - cur = getRight(cur); - } - } - - return bound; - } - - // Find a first node in the treap which is greater or equal the moniker. - // Returns closest greater (Comparator::compare returns 1) or any equal. - public int searchUpperBound(MonikerComparator moniker, int treap) { - int cur = getRoot(treap); - int bound = -1; - while (cur != nullNode()) { - int res = moniker.compare(this, cur); - if (res == 0) - return cur; - else if (res < 0) { - bound = cur; - cur = getLeft(cur); - } else { - cur = getRight(cur); - } - } - - return bound; - } - - // Returns treap node data (element) from the given node index. - public int getElement(int treap_node_index) { - return m_treapData.getField(treap_node_index, 3);// no error checking - // here - } - - // Returns treap node for the left node for the given treap node index - public int getLeft(int treap_node_index) { - return m_treapData.getField(treap_node_index, 0);// no error checking - // here - } - - // Returns treap index for the right node for the given treap node index - public int getRight(int treap_node_index) { - return m_treapData.getField(treap_node_index, 1);// no error checking - // here - } - - // Returns treap index for the parent node for the given treap node index - public int getParent(int treap_node_index) { - return m_treapData.getField(treap_node_index, 2);// no error checking - // here - } - - // Returns next treap index. Allows to navigate Treap in the sorted order - public int getNext(int treap_node_index) { - return m_treapData.getField(treap_node_index, 6); - } - - // Returns prev treap index. Allows to navigate Treap in the sorted order - // backwards - public int getPrev(int treap_node_index) { - return m_treapData.getField(treap_node_index, 5); - } - - // Returns the first element in the treap (least one). Used together with - // get_next to write a loop - public int getFirst(int treap) { - if (treap == -1) - return getFirst_(m_defaultTreap); - - return getFirst_(treap); - } - - // Returns the last element in the treap (greatest one). Used together with - // get_prev to write a loop - public int getLast(int treap) { - if (treap == -1) - return getLast_(m_defaultTreap); - - return getLast_(treap); - } - - // Gets the treap data associated with the treap. - public int getTreapData(int treap) { - if (treap == -1) - return getTreapData_(m_defaultTreap); - - return getTreapData_(treap); - } - - // Change the element value. Note: do not call this method if setting the - // element will change the sorted order. - public void setElement(int treap_node_index, int newElement) { - if (m_comparator != null) - m_comparator.onSetImpl_(this, treap_node_index); - setElement_(treap_node_index, newElement); - } - - // Returns the root of the treap. - public int getRoot(int treap) { - if (treap == -1) - return getRoot_(m_defaultTreap); - - return getRoot_(treap); - } - - // Check if the node is Null (does not exist). - public static int nullNode() { - return -1; - } - - // Clears all nodes - public void clear() { - m_treapData.deleteAll(false); - m_defaultTreap = nullNode(); - } - - // Total number of nodes - public int size(int treap) { - if (treap == -1) - return getSize_(m_defaultTreap); - - return getSize_(treap); - } - - // Returns the maximum depth of this Treap at given moment - public int getMaxDepth(int treap) { - return getMaxDepthHelper_(getRoot(treap)); - } - - public int getStateFlag() { - m_touchFlag &= 0x7FFFFFFF; - return m_touchFlag; - } - - private int m_defaultTreap; - private int m_random; - private Treap.Comparator m_comparator;// comparator used to arrange the - // nodes - private StridedIndexTypeCollection m_treapData; // m_left (0), m_right (1), - // m_parent (2), m_element - // (3), m_priority (4), - // m_prev (5), m_next (6) - // (optional: m_root (0), - // m_first (1), m_last (2), - // m_duplicate_element (3), - // m_treap_size (4), - // m_treapData (5)) - private int m_touchFlag; - private boolean m_b_balancing; - - private void touch_() { - if (m_touchFlag >= 0) { - m_touchFlag += 0x80000001; - } - } - - private int getPriority_(int treap_node_index) { - return m_treapData.getField(treap_node_index, 4);// no error checking - // here - } - - private void bubbleDown_(int treap_node_index) { - int left = getLeft(treap_node_index); - int right = getRight(treap_node_index); - int priority = getPriority_(treap_node_index); - while (left != nullNode() || right != nullNode()) { - int lcprior = left != nullNode() ? getPriority_(left) : NumberUtils - .intMax(); - int rcprior = right != nullNode() ? getPriority_(right) - : NumberUtils.intMax(); - int minprior = Math.min(lcprior, rcprior); - - if (priority <= minprior) - return; - - if (lcprior <= rcprior) - rotateRight_(left); - else - rotateLeft_(treap_node_index); - - left = getLeft(treap_node_index); - right = getRight(treap_node_index); - } - } - - private void bubbleUp_(int node) { - if (!m_b_balancing) - return; - int priority = getPriority_(node); - int parent = getParent(node); - while (parent != nullNode() && getPriority_(parent) > priority) { - if (getLeft(parent) == node) - rotateRight_(node); - else - rotateLeft_(parent); - - parent = getParent(node); - } - } - - private void rotateLeft_(int treap_node_index) { - int px = treap_node_index; - int py = getRight(treap_node_index); - int ptemp; - setParent_(py, getParent(px)); - setParent_(px, py); - - ptemp = getLeft(py); - setRight_(px, ptemp); - - if (ptemp != nullNode()) - setParent_(ptemp, px); - - setLeft_(py, px); - - ptemp = getParent(py); - if (ptemp != nullNode()) { - if (getLeft(ptemp) == px) - setLeft_(ptemp, py); - else { - assert (getRight(ptemp) == px); - setRight_(ptemp, py); - } - } - } - - private void rotateRight_(int treap_node_index) { - int py = getParent(treap_node_index); - int px = treap_node_index; - int ptemp; - - setParent_(px, getParent(py)); - setParent_(py, px); - - ptemp = getRight(px); - setLeft_(py, ptemp); - - if (ptemp != nullNode()) - setParent_(ptemp, py); - - setRight_(px, py); - - ptemp = getParent(px); - if (ptemp != nullNode()) { - if (getLeft(ptemp) == py) - setLeft_(ptemp, px); - else { - assert (getRight(ptemp) == py); - setRight_(ptemp, px); - } - } - } - - private void setParent_(int treap_node_index, int new_parent) { - m_treapData.setField(treap_node_index, 2, new_parent); // no error - // checking here - } - - private void setLeft_(int treap_node_index, int new_left) { - m_treapData.setField(treap_node_index, 0, new_left); // no error - // checking here - } - - private void setRight_(int treap_node_index, int new_right) { - m_treapData.setField(treap_node_index, 1, new_right); // no error - // checking here - } - - private void setPriority_(int treap_node_index, int new_priority) { - m_treapData.setField(treap_node_index, 4, new_priority); // no error - // checking - // here - } - - private void setPrev_(int treap_node_index, int prev) { - assert (prev != treap_node_index); - m_treapData.setField(treap_node_index, 5, prev); // no error checking - // here - } - - private void setNext_(int treap_node_index, int next) { - assert (next != treap_node_index); - m_treapData.setField(treap_node_index, 6, next); // no error checking - // here - } - - private void setRoot_(int root, int treap) { - m_treapData.setField(treap, 0, root); - } - - private void setFirst_(int first, int treap) { - m_treapData.setField(treap, 1, first); - } - - private void setLast_(int last, int treap) { - m_treapData.setField(treap, 2, last); - } - - private void setDuplicateElement_(int duplicate_element, int treap) { - m_treapData.setField(treap, 3, duplicate_element); - } - - private void setSize_(int size, int treap) { - m_treapData.setField(treap, 4, size); - } - - private void setTreapData_(int treap_data, int treap) { - m_treapData.setField(treap, 5, treap_data); - } - - private int getRoot_(int treap) { - if (treap == -1) - return nullNode(); - - return m_treapData.getField(treap, 0); - } - - private int getFirst_(int treap) { - if (treap == -1) - return nullNode(); - - return m_treapData.getField(treap, 1); - } - - private int getLast_(int treap) { - if (treap == -1) - return nullNode(); - - return m_treapData.getField(treap, 2); - } - - private int getDuplicateElement_(int treap) { - if (treap == -1) - return nullNode(); - - return m_treapData.getField(treap, 3); - } - - private int getSize_(int treap) { - if (treap == -1) - return 0; - - return m_treapData.getField(treap, 4); - } - - private int getTreapData_(int treap) { - return m_treapData.getField(treap, 5); - } - - private int newNode_(int element) { - touch_(); - int newNode = m_treapData.newElement(); - setPriority_(newNode, generatePriority_()); - setElement_(newNode, element); - return newNode; - } - - private void freeNode_(int treap_node_index, int treap) { - if (treap_node_index == nullNode()) - return; - - m_treapData.deleteElement(treap_node_index); - } - - private int generatePriority_() { - m_random = NumberUtils.nextRand(m_random); - return m_random & (NumberUtils.intMax() >> 1); - } - - private int getMaxDepthHelper_(int node) { - if (node == nullNode()) - return 0; - - return 1 + Math.max(getMaxDepthHelper_(getLeft(node)), - getMaxDepthHelper_(getRight(node))); - } - - private int addElement_(int element, int kind, int treap) { - // dbg_check_(m_root); - if (getRoot_(treap) == nullNode()) { - int newNode = newNode_(element); - setRoot_(newNode, treap); - addToList_(-1, newNode, treap); - return newNode; - } - - int cur = getRoot_(treap); - int newNode = -1; - int before = -1; - - for (; ; ) { - int cmp = kind == -1 ? 1 : m_comparator.compare(this, element, cur); - if (cmp < 0) { - int left = getLeft(cur); - if (left != nullNode()) - cur = left; - else { - before = cur; - newNode = newNode_(element); - setLeft_(cur, newNode); - setParent_(newNode, cur); - break; - } - } else { - if (kind == 1 && cmp == 0) { - m_comparator.onAddUniqueElementFailedImpl_(element); - setDuplicateElement_(cur, treap); - return -1;// return negative value. - } - - int right = getRight(cur); - if (right != nullNode()) - cur = right; - else { - before = getNext(cur); - newNode = newNode_(element); - setRight_(cur, newNode); - setParent_(newNode, cur); - break; - } - } - } - - bubbleUp_(newNode); - if (getParent(newNode) == nullNode()) - setRoot_(newNode, treap); - - addToList_(before, newNode, treap); - // dbg_check_(m_root); - return newNode; - } - - private void addToList_(int before, int node, int treap) { - assert (before != node); - int prev; - if (before != -1) { - prev = getPrev(before); - setPrev_(before, node); - } else - prev = getLast_(treap); - - setPrev_(node, prev); - if (prev != -1) - setNext_(prev, node); - setNext_(node, before); - - if (before == getFirst_(treap)) { - setFirst_(node, treap); - } - if (before == -1) { - setLast_(node, treap); - } - - setSize_(getSize_(treap) + 1, treap); - } - - private void removeFromList_(int node, int treap) { - int prev = getPrev(node); - int next = getNext(node); - if (prev != -1) - setNext_(prev, next); - else - setFirst_(next, treap); - - if (next != -1) - setPrev_(next, prev); - else - setLast_(prev, treap); - - setSize_(getSize_(treap) - 1, treap); - } - - private void unbalancedDelete_(int treap_node_index, int treap) { - assert (!m_b_balancing); - // dbg_check_(m_root); - removeFromList_(treap_node_index, treap); - int left = getLeft(treap_node_index); - int right = getRight(treap_node_index); - int parent = getParent(treap_node_index); - int x = treap_node_index; - if (left != -1 && right != -1) { - m_random = NumberUtils.nextRand(m_random); - int R; - if (m_random > (NumberUtils.intMax() >> 1)) - R = getNext(treap_node_index); - else - R = getPrev(treap_node_index); - - assert (R != -1);// cannot be NULL becaus the node has left and - // right - - boolean bFixMe = getParent(R) == treap_node_index; - - // swap left, right, and parent - m_treapData.swapField(treap_node_index, R, 0); - m_treapData.swapField(treap_node_index, R, 1); - m_treapData.swapField(treap_node_index, R, 2); - - if (parent != -1) { - // Connect ex-parent of int to R. - if (getLeft(parent) == treap_node_index) { - setLeft_(parent, R); - } else { - assert (getRight(parent) == treap_node_index); - setRight_(parent, R); - } - } else {// int was the root. Make R the Root. - setRoot_(R, treap); - } - - if (bFixMe) {// R was a child of int - if (left == R) { - setLeft_(R, treap_node_index); - setParent_(right, R); - } else if (right == R) { - setRight_(R, treap_node_index); - setParent_(left, R); - } - - setParent_(treap_node_index, R); - parent = R; - } else { - setParent_(left, R); - setParent_(right, R); - parent = getParent(treap_node_index); - x = R; - } - - assert (parent != -1); - left = getLeft(treap_node_index); - right = getRight(treap_node_index); - if (left != -1) - setParent_(left, treap_node_index); - if (right != -1) - setParent_(right, treap_node_index); - - assert (left == -1 || right == -1); - } - - // At most one child is not NULL. - int child = left != -1 ? left : right; - - if (parent == -1) { - setRoot_(child, treap); - } else { - if (getLeft(parent) == x) { - setLeft_(parent, child); - } else { - assert (getRight(parent) == x); - setRight_(parent, child); - } - } - - if (child != -1) - setParent_(child, parent); - - freeNode_(treap_node_index, treap); - // dbg_check_(m_root); - } - - private void deleteNode_(int treap_node_index, int treap) { - assert (m_b_balancing); - setPriority_(treap_node_index, NumberUtils.intMax()); // set the node - // priority high - int prl = nullNode(); - int prr = nullNode(); - int root = getRoot_(treap); - boolean isroot = (root == treap_node_index); - - if (isroot) { - // remember children of the root node, if the root node is to be - // deleted - prl = getLeft(root); - prr = getRight(root); - - if (prl == nullNode() && prr == nullNode()) { - removeFromList_(root, treap); - freeNode_(root, treap); - setRoot_(nullNode(), treap); - return; - } - } - - bubbleDown_(treap_node_index); // let the node to slide to the leaves of - // tree - - int p = getParent(treap_node_index); - - if (p != nullNode()) { - if (getLeft(p) == treap_node_index) - setLeft_(p, nullNode()); - else - setRight_(p, nullNode()); - } - - removeFromList_(treap_node_index, treap); - freeNode_(treap_node_index, treap); - - if (isroot) // if the root node is deleted, assign new root - setRoot_((prl == nullNode() || getParent(prl) != nullNode()) ? prr - : prl, treap); - - assert (getParent(getRoot(treap)) == nullNode()); - } - - private void setElement_(int treap_node_index, int newElement) { - touch_(); - m_treapData.setField(treap_node_index, 3, newElement);// no error - // checking here - } + static abstract class Comparator { + Comparator() { + m_b_notify_on_actions = false; + } + + Comparator(boolean bNotifyOnActions) { + m_b_notify_on_actions = bNotifyOnActions; + } + + // Compares the element elm to the element contained in the given node + abstract int compare(Treap treap, int elm, int node); + + // These virtual methods are called only when Comparator(true) ctro has + // been used. + void onDelete(int elm) { + } + + void onSet(int elm) { + } + + void onEndSearch(int elm) { + } + + void onAddUniqueElementFailed(int elm) { + } + + private boolean m_b_notify_on_actions; + + // void operator=(const Comparator&); // do not allow operator = + void onDeleteImpl_(Treap treap, int node) { + if (m_b_notify_on_actions) + onDelete(treap.getElement(node)); + } + + void onSetImpl_(Treap treap, int node) { + if (m_b_notify_on_actions) + onSet(treap.getElement(node)); + } + + void onAddUniqueElementFailedImpl_(int elm) { + if (m_b_notify_on_actions) + onAddUniqueElementFailed(elm); + } + + void onEndSearchImpl_(int elm) { + if (m_b_notify_on_actions) + onEndSearch(elm); + } + } + + ; + + static abstract class MonikerComparator { + // Compares the moniker, contained in the MonikerComparator with the + // element contained in the given node. + abstract int compare(Treap treap, int node); + } + + ; + + public Treap() { + m_random = 124234251; + m_b_balancing = true; + m_touchFlag = 0; + m_defaultTreap = nullNode(); + m_treapData = new StridedIndexTypeCollection(7); + m_comparator = null; + } + + // Sets the comparator + public void setComparator(Comparator comparator) { + m_comparator = comparator; + } + + // Returns the comparator + public Comparator getComparator() { + return m_comparator; + } + + // Stops auto-balancing + public void disableBalancing() { + m_b_balancing = false; + } + + // Reserves memory for nodes givne number of nodes + public void setCapacity(int capacity) { + m_treapData.setCapacity(capacity); + } + + // Create a new treap and returns the treap handle. + public int createTreap(int treap_data) { + int treap = m_treapData.newElement(); + setSize_(0, treap); + setTreapData_(treap_data, treap); + return treap; + } + + // Deletes the treap at the given treap handle. + public void deleteTreap(int treap) { + m_treapData.deleteElement(treap); + } + + // Adds new element to the treap. Allows duplicates to be added. + public int addElement(int element, int treap) { + int treap_; + if (treap == -1) { + if (m_defaultTreap == nullNode()) + m_defaultTreap = createTreap(-1); + treap_ = m_defaultTreap; + } else { + treap_ = treap; + } + + return addElement_(element, 0, treap_); + } + + // Adds new element to the treap if it is not equal to other elements. + // If the return value is -1, then get_duplicate_element reutrns the node of + // the already existing element equal to element. + public int addUniqueElement(int element, int treap) { + int treap_; + if (treap == -1) { + if (m_defaultTreap == nullNode()) + m_defaultTreap = createTreap(-1); + treap_ = m_defaultTreap; + } else { + treap_ = treap; + } + + return addElement_(element, 1, treap_); + } + + // Adds a new element to the treap that is known to be bigger or equal of + // all elements already in the treap. + // Use this method when adding elements from a sorted list for maximum + // performance (it does not call the treap comparator). + public int addBiggestElement(int element, int treap) { + int treap_; + if (treap == -1) { + if (m_defaultTreap == nullNode()) + m_defaultTreap = createTreap(-1); + treap_ = m_defaultTreap; + } else { + treap_ = treap; + } + + if (getRoot_(treap_) == nullNode()) { + int newNode = newNode_(element); + setRoot_(newNode, treap_); + addToList_(-1, newNode, treap_); + return newNode; + } + + int cur = getLast_(treap_); + int newNode = newNode_(element); + setRight_(cur, newNode); + setParent_(newNode, cur); + assert (m_b_balancing);// don't use this method for unbalanced tree, or + // the performance will be bad. + bubbleUp_(newNode); + if (getParent(newNode) == nullNode()) + setRoot_(newNode, treap_); + + addToList_(-1, newNode, treap_); + return newNode; + } + + // template void build_from_sorted(const Iterator& begin, + // const Iterator& end); + // Adds new element to the treap at the known position, thus avoiding a call + // to the comparator. + // If bCallCompare is True, the comparator will be called at most twice, + // once to compare with prevElement and once to compare with nextElement. + // When bUnique is true, if the return value is -1, then + // get_duplicate_element reutrns the node of the already existing element. + public int addElementAtPosition(int prevNode, int nextNode, int element, + boolean bUnique, boolean bCallCompare, int treap) { + int treap_ = treap; + if (treap_ == -1) { + if (m_defaultTreap == nullNode()) + m_defaultTreap = createTreap(-1); + treap_ = m_defaultTreap; + } + + // dbg_check_(m_root); + if (getRoot_(treap_) == nullNode()) { + assert (nextNode == nullNode() && prevNode == nullNode()); + int root = newNode_(element); + setRoot_(root, treap_); + addToList_(-1, root, treap_); + return root; + } + + int cmpNext; + int cmpPrev; + if (bCallCompare) { + cmpNext = nextNode != nullNode() ? m_comparator.compare(this, + element, nextNode) : -1; + assert (cmpNext <= 0); + cmpPrev = prevNode != nullNode() ? m_comparator.compare(this, + element, prevNode) : 1; + // cmpPrev can be negative in plane sweep when intersection is + // detected. + } else { + cmpNext = -1; + cmpPrev = 1; + } + + if (bUnique && (cmpNext == 0 || cmpPrev == 0)) { + m_comparator.onAddUniqueElementFailedImpl_(element); + int cur = cmpNext == 0 ? nextNode : prevNode; + setDuplicateElement_(cur, treap_); + return -1;// return negative value. + } + + int cur; + int cmp; + boolean bNext; + if (nextNode != nullNode() && prevNode != nullNode()) { + // randomize the the cost to insert a node. + bNext = m_random > NumberUtils.nextRand(m_random) >> 1; + } else + bNext = nextNode != nullNode(); + + if (bNext) { + cmp = cmpNext; + cur = nextNode; + } else { + cmp = cmpPrev; + cur = prevNode; + } + + int newNode = -1; + int before = -1; + boolean b_first = true; + for (; ; ) { + if (cmp < 0) { + int left = getLeft(cur); + if (left != nullNode()) + cur = left; + else { + before = cur; + newNode = newNode_(element); + setLeft_(cur, newNode); + setParent_(newNode, cur); + break; + } + } else { + int right = getRight(cur); + if (right != nullNode()) + cur = right; + else { + before = getNext(cur); + newNode = newNode_(element); + setRight_(cur, newNode); + setParent_(newNode, cur); + break; + } + } + + if (b_first) { + cmp *= -1; + b_first = false; + } + } + + bubbleUp_(newNode); + if (getParent(newNode) == nullNode()) + setRoot_(newNode, treap_); + + addToList_(before, newNode, treap_); + // dbg_check_(m_root); + return newNode; + } + + // Get duplicate element + public int getDuplicateElement(int treap) { + if (treap == -1) + return getDuplicateElement_(m_defaultTreap); + + return getDuplicateElement_(treap); + } + + // Removes a node from the treap. Throws if doesn't exist. + public void deleteNode(int treap_node_index, int treap) { + touch_(); + // assert(isValidNode(treap_node_index)); + if (m_comparator != null) + m_comparator.onDeleteImpl_(this, treap_node_index); + + int treap_; + if (treap == -1) + treap_ = m_defaultTreap; + else + treap_ = treap; + + if (!m_b_balancing) { + unbalancedDelete_(treap_node_index, treap_); + } else + deleteNode_(treap_node_index, treap_); + } + + // Finds an element in the treap and returns its node or -1. + public int search(int data, int treap) { + int cur = getRoot(treap); + while (cur != nullNode()) { + int res = m_comparator.compare(this, data, cur); + if (res == 0) + return cur; + else if (res < 0) + cur = getLeft(cur); + else + cur = getRight(cur); + } + + m_comparator.onEndSearchImpl_(data); + return nullNode(); + } + + // Find a first node in the treap which is less or equal the moniker. + // Returns closest smaller (Comparator::compare returns -1) or any equal. + public int searchLowerBound(MonikerComparator moniker, int treap) { + int cur = getRoot(treap); + int bound = -1; + while (cur != nullNode()) { + int res = moniker.compare(this, cur); + if (res == 0) + return cur; + else if (res < 0) + cur = getLeft(cur); + else { + bound = cur; + cur = getRight(cur); + } + } + + return bound; + } + + // Find a first node in the treap which is greater or equal the moniker. + // Returns closest greater (Comparator::compare returns 1) or any equal. + public int searchUpperBound(MonikerComparator moniker, int treap) { + int cur = getRoot(treap); + int bound = -1; + while (cur != nullNode()) { + int res = moniker.compare(this, cur); + if (res == 0) + return cur; + else if (res < 0) { + bound = cur; + cur = getLeft(cur); + } else { + cur = getRight(cur); + } + } + + return bound; + } + + // Returns treap node data (element) from the given node index. + public int getElement(int treap_node_index) { + return m_treapData.getField(treap_node_index, 3);// no error checking + // here + } + + // Returns treap node for the left node for the given treap node index + public int getLeft(int treap_node_index) { + return m_treapData.getField(treap_node_index, 0);// no error checking + // here + } + + // Returns treap index for the right node for the given treap node index + public int getRight(int treap_node_index) { + return m_treapData.getField(treap_node_index, 1);// no error checking + // here + } + + // Returns treap index for the parent node for the given treap node index + public int getParent(int treap_node_index) { + return m_treapData.getField(treap_node_index, 2);// no error checking + // here + } + + // Returns next treap index. Allows to navigate Treap in the sorted order + public int getNext(int treap_node_index) { + return m_treapData.getField(treap_node_index, 6); + } + + // Returns prev treap index. Allows to navigate Treap in the sorted order + // backwards + public int getPrev(int treap_node_index) { + return m_treapData.getField(treap_node_index, 5); + } + + // Returns the first element in the treap (least one). Used together with + // get_next to write a loop + public int getFirst(int treap) { + if (treap == -1) + return getFirst_(m_defaultTreap); + + return getFirst_(treap); + } + + // Returns the last element in the treap (greatest one). Used together with + // get_prev to write a loop + public int getLast(int treap) { + if (treap == -1) + return getLast_(m_defaultTreap); + + return getLast_(treap); + } + + // Gets the treap data associated with the treap. + public int getTreapData(int treap) { + if (treap == -1) + return getTreapData_(m_defaultTreap); + + return getTreapData_(treap); + } + + // Change the element value. Note: do not call this method if setting the + // element will change the sorted order. + public void setElement(int treap_node_index, int newElement) { + if (m_comparator != null) + m_comparator.onSetImpl_(this, treap_node_index); + setElement_(treap_node_index, newElement); + } + + // Returns the root of the treap. + public int getRoot(int treap) { + if (treap == -1) + return getRoot_(m_defaultTreap); + + return getRoot_(treap); + } + + // Check if the node is Null (does not exist). + public static int nullNode() { + return -1; + } + + // Clears all nodes + public void clear() { + m_treapData.deleteAll(false); + m_defaultTreap = nullNode(); + } + + // Total number of nodes + public int size(int treap) { + if (treap == -1) + return getSize_(m_defaultTreap); + + return getSize_(treap); + } + + // Returns the maximum depth of this Treap at given moment + public int getMaxDepth(int treap) { + return getMaxDepthHelper_(getRoot(treap)); + } + + public int getStateFlag() { + m_touchFlag &= 0x7FFFFFFF; + return m_touchFlag; + } + + private int m_defaultTreap; + private int m_random; + private Treap.Comparator m_comparator;// comparator used to arrange the + // nodes + private StridedIndexTypeCollection m_treapData; // m_left (0), m_right (1), + // m_parent (2), m_element + // (3), m_priority (4), + // m_prev (5), m_next (6) + // (optional: m_root (0), + // m_first (1), m_last (2), + // m_duplicate_element (3), + // m_treap_size (4), + // m_treapData (5)) + private int m_touchFlag; + private boolean m_b_balancing; + + private void touch_() { + if (m_touchFlag >= 0) { + m_touchFlag += 0x80000001; + } + } + + private int getPriority_(int treap_node_index) { + return m_treapData.getField(treap_node_index, 4);// no error checking + // here + } + + private void bubbleDown_(int treap_node_index) { + int left = getLeft(treap_node_index); + int right = getRight(treap_node_index); + int priority = getPriority_(treap_node_index); + while (left != nullNode() || right != nullNode()) { + int lcprior = left != nullNode() ? getPriority_(left) : NumberUtils + .intMax(); + int rcprior = right != nullNode() ? getPriority_(right) + : NumberUtils.intMax(); + int minprior = Math.min(lcprior, rcprior); + + if (priority <= minprior) + return; + + if (lcprior <= rcprior) + rotateRight_(left); + else + rotateLeft_(treap_node_index); + + left = getLeft(treap_node_index); + right = getRight(treap_node_index); + } + } + + private void bubbleUp_(int node) { + if (!m_b_balancing) + return; + int priority = getPriority_(node); + int parent = getParent(node); + while (parent != nullNode() && getPriority_(parent) > priority) { + if (getLeft(parent) == node) + rotateRight_(node); + else + rotateLeft_(parent); + + parent = getParent(node); + } + } + + private void rotateLeft_(int treap_node_index) { + int px = treap_node_index; + int py = getRight(treap_node_index); + int ptemp; + setParent_(py, getParent(px)); + setParent_(px, py); + + ptemp = getLeft(py); + setRight_(px, ptemp); + + if (ptemp != nullNode()) + setParent_(ptemp, px); + + setLeft_(py, px); + + ptemp = getParent(py); + if (ptemp != nullNode()) { + if (getLeft(ptemp) == px) + setLeft_(ptemp, py); + else { + assert (getRight(ptemp) == px); + setRight_(ptemp, py); + } + } + } + + private void rotateRight_(int treap_node_index) { + int py = getParent(treap_node_index); + int px = treap_node_index; + int ptemp; + + setParent_(px, getParent(py)); + setParent_(py, px); + + ptemp = getRight(px); + setLeft_(py, ptemp); + + if (ptemp != nullNode()) + setParent_(ptemp, py); + + setRight_(px, py); + + ptemp = getParent(px); + if (ptemp != nullNode()) { + if (getLeft(ptemp) == py) + setLeft_(ptemp, px); + else { + assert (getRight(ptemp) == py); + setRight_(ptemp, px); + } + } + } + + private void setParent_(int treap_node_index, int new_parent) { + m_treapData.setField(treap_node_index, 2, new_parent); // no error + // checking here + } + + private void setLeft_(int treap_node_index, int new_left) { + m_treapData.setField(treap_node_index, 0, new_left); // no error + // checking here + } + + private void setRight_(int treap_node_index, int new_right) { + m_treapData.setField(treap_node_index, 1, new_right); // no error + // checking here + } + + private void setPriority_(int treap_node_index, int new_priority) { + m_treapData.setField(treap_node_index, 4, new_priority); // no error + // checking + // here + } + + private void setPrev_(int treap_node_index, int prev) { + assert (prev != treap_node_index); + m_treapData.setField(treap_node_index, 5, prev); // no error checking + // here + } + + private void setNext_(int treap_node_index, int next) { + assert (next != treap_node_index); + m_treapData.setField(treap_node_index, 6, next); // no error checking + // here + } + + private void setRoot_(int root, int treap) { + m_treapData.setField(treap, 0, root); + } + + private void setFirst_(int first, int treap) { + m_treapData.setField(treap, 1, first); + } + + private void setLast_(int last, int treap) { + m_treapData.setField(treap, 2, last); + } + + private void setDuplicateElement_(int duplicate_element, int treap) { + m_treapData.setField(treap, 3, duplicate_element); + } + + private void setSize_(int size, int treap) { + m_treapData.setField(treap, 4, size); + } + + private void setTreapData_(int treap_data, int treap) { + m_treapData.setField(treap, 5, treap_data); + } + + private int getRoot_(int treap) { + if (treap == -1) + return nullNode(); + + return m_treapData.getField(treap, 0); + } + + private int getFirst_(int treap) { + if (treap == -1) + return nullNode(); + + return m_treapData.getField(treap, 1); + } + + private int getLast_(int treap) { + if (treap == -1) + return nullNode(); + + return m_treapData.getField(treap, 2); + } + + private int getDuplicateElement_(int treap) { + if (treap == -1) + return nullNode(); + + return m_treapData.getField(treap, 3); + } + + private int getSize_(int treap) { + if (treap == -1) + return 0; + + return m_treapData.getField(treap, 4); + } + + private int getTreapData_(int treap) { + return m_treapData.getField(treap, 5); + } + + private int newNode_(int element) { + touch_(); + int newNode = m_treapData.newElement(); + setPriority_(newNode, generatePriority_()); + setElement_(newNode, element); + return newNode; + } + + private void freeNode_(int treap_node_index, int treap) { + if (treap_node_index == nullNode()) + return; + + m_treapData.deleteElement(treap_node_index); + } + + private int generatePriority_() { + m_random = NumberUtils.nextRand(m_random); + return m_random & (NumberUtils.intMax() >> 1); + } + + private int getMaxDepthHelper_(int node) { + if (node == nullNode()) + return 0; + + return 1 + Math.max(getMaxDepthHelper_(getLeft(node)), + getMaxDepthHelper_(getRight(node))); + } + + private int addElement_(int element, int kind, int treap) { + // dbg_check_(m_root); + if (getRoot_(treap) == nullNode()) { + int newNode = newNode_(element); + setRoot_(newNode, treap); + addToList_(-1, newNode, treap); + return newNode; + } + + int cur = getRoot_(treap); + int newNode = -1; + int before = -1; + + for (; ; ) { + int cmp = kind == -1 ? 1 : m_comparator.compare(this, element, cur); + if (cmp < 0) { + int left = getLeft(cur); + if (left != nullNode()) + cur = left; + else { + before = cur; + newNode = newNode_(element); + setLeft_(cur, newNode); + setParent_(newNode, cur); + break; + } + } else { + if (kind == 1 && cmp == 0) { + m_comparator.onAddUniqueElementFailedImpl_(element); + setDuplicateElement_(cur, treap); + return -1;// return negative value. + } + + int right = getRight(cur); + if (right != nullNode()) + cur = right; + else { + before = getNext(cur); + newNode = newNode_(element); + setRight_(cur, newNode); + setParent_(newNode, cur); + break; + } + } + } + + bubbleUp_(newNode); + if (getParent(newNode) == nullNode()) + setRoot_(newNode, treap); + + addToList_(before, newNode, treap); + // dbg_check_(m_root); + return newNode; + } + + private void addToList_(int before, int node, int treap) { + assert (before != node); + int prev; + if (before != -1) { + prev = getPrev(before); + setPrev_(before, node); + } else + prev = getLast_(treap); + + setPrev_(node, prev); + if (prev != -1) + setNext_(prev, node); + setNext_(node, before); + + if (before == getFirst_(treap)) { + setFirst_(node, treap); + } + if (before == -1) { + setLast_(node, treap); + } + + setSize_(getSize_(treap) + 1, treap); + } + + private void removeFromList_(int node, int treap) { + int prev = getPrev(node); + int next = getNext(node); + if (prev != -1) + setNext_(prev, next); + else + setFirst_(next, treap); + + if (next != -1) + setPrev_(next, prev); + else + setLast_(prev, treap); + + setSize_(getSize_(treap) - 1, treap); + } + + private void unbalancedDelete_(int treap_node_index, int treap) { + assert (!m_b_balancing); + // dbg_check_(m_root); + removeFromList_(treap_node_index, treap); + int left = getLeft(treap_node_index); + int right = getRight(treap_node_index); + int parent = getParent(treap_node_index); + int x = treap_node_index; + if (left != -1 && right != -1) { + m_random = NumberUtils.nextRand(m_random); + int R; + if (m_random > (NumberUtils.intMax() >> 1)) + R = getNext(treap_node_index); + else + R = getPrev(treap_node_index); + + assert (R != -1);// cannot be NULL becaus the node has left and + // right + + boolean bFixMe = getParent(R) == treap_node_index; + + // swap left, right, and parent + m_treapData.swapField(treap_node_index, R, 0); + m_treapData.swapField(treap_node_index, R, 1); + m_treapData.swapField(treap_node_index, R, 2); + + if (parent != -1) { + // Connect ex-parent of int to R. + if (getLeft(parent) == treap_node_index) { + setLeft_(parent, R); + } else { + assert (getRight(parent) == treap_node_index); + setRight_(parent, R); + } + } else {// int was the root. Make R the Root. + setRoot_(R, treap); + } + + if (bFixMe) {// R was a child of int + if (left == R) { + setLeft_(R, treap_node_index); + setParent_(right, R); + } else if (right == R) { + setRight_(R, treap_node_index); + setParent_(left, R); + } + + setParent_(treap_node_index, R); + parent = R; + } else { + setParent_(left, R); + setParent_(right, R); + parent = getParent(treap_node_index); + x = R; + } + + assert (parent != -1); + left = getLeft(treap_node_index); + right = getRight(treap_node_index); + if (left != -1) + setParent_(left, treap_node_index); + if (right != -1) + setParent_(right, treap_node_index); + + assert (left == -1 || right == -1); + } + + // At most one child is not NULL. + int child = left != -1 ? left : right; + + if (parent == -1) { + setRoot_(child, treap); + } else { + if (getLeft(parent) == x) { + setLeft_(parent, child); + } else { + assert (getRight(parent) == x); + setRight_(parent, child); + } + } + + if (child != -1) + setParent_(child, parent); + + freeNode_(treap_node_index, treap); + // dbg_check_(m_root); + } + + private void deleteNode_(int treap_node_index, int treap) { + assert (m_b_balancing); + setPriority_(treap_node_index, NumberUtils.intMax()); // set the node + // priority high + int prl = nullNode(); + int prr = nullNode(); + int root = getRoot_(treap); + boolean isroot = (root == treap_node_index); + + if (isroot) { + // remember children of the root node, if the root node is to be + // deleted + prl = getLeft(root); + prr = getRight(root); + + if (prl == nullNode() && prr == nullNode()) { + removeFromList_(root, treap); + freeNode_(root, treap); + setRoot_(nullNode(), treap); + return; + } + } + + bubbleDown_(treap_node_index); // let the node to slide to the leaves of + // tree + + int p = getParent(treap_node_index); + + if (p != nullNode()) { + if (getLeft(p) == treap_node_index) + setLeft_(p, nullNode()); + else + setRight_(p, nullNode()); + } + + removeFromList_(treap_node_index, treap); + freeNode_(treap_node_index, treap); + + if (isroot) // if the root node is deleted, assign new root + setRoot_((prl == nullNode() || getParent(prl) != nullNode()) ? prr + : prl, treap); + + assert (getParent(getRoot(treap)) == nullNode()); + } + + private void setElement_(int treap_node_index, int newElement) { + touch_(); + m_treapData.setField(treap_node_index, 3, newElement);// no error + // checking here + } } diff --git a/src/main/java/com/esri/core/geometry/UserCancelException.java b/src/main/java/com/esri/core/geometry/UserCancelException.java index 2d495a0b..d72479a1 100644 --- a/src/main/java/com/esri/core/geometry/UserCancelException.java +++ b/src/main/java/com/esri/core/geometry/UserCancelException.java @@ -24,9 +24,9 @@ package com.esri.core.geometry; class UserCancelException extends GeometryException { - private static final long serialVersionUID = 1L; + private static final long serialVersionUID = 1L; - public UserCancelException() { - super("user cancel"); - } + public UserCancelException() { + super("user cancel"); + } } diff --git a/src/main/java/com/esri/core/geometry/VertexDescription.java b/src/main/java/com/esri/core/geometry/VertexDescription.java index 032e27d5..5b4efd40 100644 --- a/src/main/java/com/esri/core/geometry/VertexDescription.java +++ b/src/main/java/com/esri/core/geometry/VertexDescription.java @@ -43,331 +43,331 @@ * database table, and the VertexDescription defines the fields of the table. */ public final class VertexDescription { - /** - * Describes the attribute and, in case of predefined attributes, provides a - * hint of the attribute use. - */ - public interface Semantics { - static final int POSITION = 0; // xy coordinates of a point (2D - // vector of double, linear - // interpolation) - - static final int Z = 1; // z coordinates of a point (double, - // linear interpolation) - - static final int M = 2; // m attribute (double, linear - // interpolation) - - static final int ID = 3; // id (int, no interpolation) - - static final int NORMAL = 4; // xyz coordinates of normal vector - // (float, angular interpolation) - - static final int TEXTURE1D = 5; // u coordinates of texture - // (float, linear interpolation) - - static final int TEXTURE2D = 6; // uv coordinates of texture - // (float, linear interpolation) - - static final int TEXTURE3D = 7; // uvw coordinates of texture - // (float, linear interpolation) - - static final int ID2 = 8; // two component ID - - static final int MAXSEMANTICS = 8; // the max semantics value - } - - /** - * Specifies how the attribute is interpolated along the segments. are - * represented as int64 - */ - interface Interpolation { - public static final int NONE = 0; - - public static final int LINEAR = 1; - - public static final int ANGULAR = 2; - } - - /** - * Specifies the type of the attribute. - */ - interface Persistence { - public static final int enumFloat = 0; - - public static final int enumDouble = 1; - - public static final int enumInt32 = 2; - - public static final int enumInt64 = 3; - - public static final int enumInt8 = 4; // 8 bit integer. Can be signed or - // unsigned depending on - // platform. - - public static final int enumInt16 = 5; - } - - ; - - /** - * Returns the attribute count of this description. The value is always - * greater or equal to 1. The first attribute is always a POSITION. - */ - public final int getAttributeCount() { - return m_attributeCount; - } - - /** - * Returns the semantics of the given attribute. - * - * @param attributeIndex The index of the attribute in the description. Max value is - * getAttributeCount() - 1. - */ - public final int getSemantics(int attributeIndex) { - return m_indexToSemantics[attributeIndex]; - } - - /** - * Returns the index the given attribute in the vertex description. - * - * @param semantics - * @return Returns the attribute index or -1 of the attribute does not exist - */ - public final int getAttributeIndex(int semantics) { - return m_semanticsToIndexMap[semantics]; - } - - /** - * Returns the interpolation type for the attribute. - * - * @param semantics The semantics of the attribute. - */ - static int getInterpolation(int semantics) { - return _interpolation[semantics]; - } - - /** - * Returns the persistence type for the attribute. - * - * @param semantics The semantics of the attribute. - */ - static int getPersistence(int semantics) { - return _persistence[semantics]; - } - - /** - * Returns the size of the persistence type in bytes. - * - * @param persistence The persistence type to query. - */ - static int getPersistenceSize(int persistence) { - return _persistencesize[persistence]; - } - - /** - * Returns the size of the semantics in bytes. - */ - static int getPersistenceSizeSemantics(int semantics) { - return getPersistenceSize(getPersistence(semantics)) - * getComponentCount(semantics); - } - - /** - * Returns the number of the components of the given semantics. For example, - * it returns 2 for the POSITION. - * - * @param semantics The semantics of the attribute. - */ - public static int getComponentCount(int semantics) { - return _components[semantics]; - } - - /** - * Returns True if the attribute with the given name and given set exists. - * - * @param semantics The semantics of the attribute. - */ - public boolean hasAttribute(int semantics) { - return (m_semanticsBitArray & (1 << semantics)) != 0; - } - - /** - * Returns True if this vertex description includes all attributes from the - * src. - * - * @param src The Vertex_description to compare with. - * @return The function returns false, only when this description does not - * have some of the attribute that src has. - */ - public final boolean hasAttributesFrom(VertexDescription src) { - return (m_semanticsBitArray & src.m_semanticsBitArray) == src.m_semanticsBitArray; - } - - /** - * Returns True, if the vertex has Z attribute. - */ - public final boolean hasZ() { - return hasAttribute(Semantics.Z); - } - - /** - * Returns True, if the vertex has M attribute. - */ - public final boolean hasM() { - return hasAttribute(Semantics.M); - } - - /** - * Returns True, if the vertex has ID attribute. - */ - public final boolean hasID() { - return hasAttribute(Semantics.ID); - } - - /** - * Returns default value for each ordinate of the vertex attribute with - * given semantics. - */ - public static double getDefaultValue(int semantics) { - return _defaultValues[semantics]; - } - - int getPointAttributeOffset_(int attributeIndex) { - return m_pointAttributeOffsets[attributeIndex]; - } - - /** - * Returns the total component count. - */ - public int getTotalComponentCount() { - return m_totalComponentCount; - } - - /** - * Checks if the given value is the default one. The simple equality test - * with GetDefaultValue does not work due to the use of NaNs as default - * value for some parameters. - */ - public static boolean isDefaultValue(int semantics, double v) { - return NumberUtils.doubleToInt64Bits(_defaultValues[semantics]) == NumberUtils - .doubleToInt64Bits(v); - } - - static boolean isIntegerPersistence(int persistence) { - return persistence >= Persistence.enumInt32; - } - - static boolean isIntegerSemantics(int semantics) { - return isIntegerPersistence(getPersistence(semantics)); - } - - @Override - public boolean equals(Object _other) { - return (Object) this == _other; - } - - /** - * Returns a packed array of double representation of all ordinates of - * attributes of a point, i.e.: X, Y, Z, ID, TEXTURE2D.u, TEXTURE2D.v - */ - double[] _getDefaultPointAttributes() { - return m_defaultPointAttributes; - } - - double _getDefaultPointAttributeValue(int attributeIndex, int ordinate) { - return m_defaultPointAttributes[_getPointAttributeOffset(attributeIndex) - + ordinate]; - } - - /** - * Returns an offset to the first ordinate of the given attribute. This - * method is used for the cases when one wants to have a packed array of - * ordinates of all attributes, i.e.: X, Y, Z, ID, TEXTURE2D.u, TEXTURE2D.v - */ - int _getPointAttributeOffset(int attributeIndex) { - return m_pointAttributeOffsets[attributeIndex]; - } - - int _getPointAttributeOffsetFromSemantics(int semantics) { - return m_pointAttributeOffsets[getAttributeIndex(semantics)]; - } - - @Override - public int hashCode() { - return m_hash; - } - - int _getSemanticsImpl(int attributeIndex) { - return m_indexToSemantics[attributeIndex]; - } - - VertexDescription(int bitMask) { - m_semanticsBitArray = bitMask; - m_attributeCount = 0; - m_totalComponentCount = 0; - m_semanticsToIndexMap = new int[Semantics.MAXSEMANTICS + 1]; - Arrays.fill(m_semanticsToIndexMap, -1); - for (int i = 0, flag = 1, n = Semantics.MAXSEMANTICS + 1; i < n; i++) { - if ((bitMask & flag) != 0) { - m_semanticsToIndexMap[i] = m_attributeCount; - m_attributeCount++; - int comps = getComponentCount(i); - m_totalComponentCount += comps; - } - - flag <<= 1; - } - - m_indexToSemantics = new int[m_attributeCount]; - for (int i = 0, n = Semantics.MAXSEMANTICS + 1; i < n; i++) { - int attrib = m_semanticsToIndexMap[i]; - if (attrib >= 0) - m_indexToSemantics[attrib] = i; - } - - m_defaultPointAttributes = new double[m_totalComponentCount]; - m_pointAttributeOffsets = new int[m_attributeCount]; - int offset = 0; - for (int i = 0, n = m_attributeCount; i < n; i++) { - int semantics = getSemantics(i); - int comps = getComponentCount(semantics); - double v = getDefaultValue(semantics); - m_pointAttributeOffsets[i] = offset; - for (int icomp = 0; icomp < comps; icomp++) { - m_defaultPointAttributes[offset] = v; - offset++; - } - } - - m_hash = NumberUtils.hash(m_semanticsBitArray); - } - - private int m_attributeCount; - int m_semanticsBitArray; //the main component - private int m_totalComponentCount; - private int m_hash; - - private int[] m_semanticsToIndexMap; - private int[] m_indexToSemantics; - private int[] m_pointAttributeOffsets; - private double[] m_defaultPointAttributes; - - static final double[] _defaultValues = {0, 0, NumberUtils.NaN(), 0, 0, 0, - 0, 0, 0}; - - static final int[] _interpolation = {Interpolation.LINEAR, - Interpolation.LINEAR, Interpolation.LINEAR, Interpolation.NONE, - Interpolation.ANGULAR, Interpolation.LINEAR, Interpolation.LINEAR, - Interpolation.LINEAR, Interpolation.NONE,}; - - static final int[] _persistence = {Persistence.enumDouble, - Persistence.enumDouble, Persistence.enumDouble, - Persistence.enumInt32, Persistence.enumFloat, - Persistence.enumFloat, Persistence.enumFloat, - Persistence.enumFloat, Persistence.enumInt32,}; - - static final int[] _persistencesize = {4, 8, 4, 8, 1, 2}; - - static final int[] _components = {2, 1, 1, 1, 3, 1, 2, 3, 2,}; + /** + * Describes the attribute and, in case of predefined attributes, provides a + * hint of the attribute use. + */ + public interface Semantics { + static final int POSITION = 0; // xy coordinates of a point (2D + // vector of double, linear + // interpolation) + + static final int Z = 1; // z coordinates of a point (double, + // linear interpolation) + + static final int M = 2; // m attribute (double, linear + // interpolation) + + static final int ID = 3; // id (int, no interpolation) + + static final int NORMAL = 4; // xyz coordinates of normal vector + // (float, angular interpolation) + + static final int TEXTURE1D = 5; // u coordinates of texture + // (float, linear interpolation) + + static final int TEXTURE2D = 6; // uv coordinates of texture + // (float, linear interpolation) + + static final int TEXTURE3D = 7; // uvw coordinates of texture + // (float, linear interpolation) + + static final int ID2 = 8; // two component ID + + static final int MAXSEMANTICS = 8; // the max semantics value + } + + /** + * Specifies how the attribute is interpolated along the segments. are + * represented as int64 + */ + interface Interpolation { + public static final int NONE = 0; + + public static final int LINEAR = 1; + + public static final int ANGULAR = 2; + } + + /** + * Specifies the type of the attribute. + */ + interface Persistence { + public static final int enumFloat = 0; + + public static final int enumDouble = 1; + + public static final int enumInt32 = 2; + + public static final int enumInt64 = 3; + + public static final int enumInt8 = 4; // 8 bit integer. Can be signed or + // unsigned depending on + // platform. + + public static final int enumInt16 = 5; + } + + ; + + /** + * Returns the attribute count of this description. The value is always + * greater or equal to 1. The first attribute is always a POSITION. + */ + public final int getAttributeCount() { + return m_attributeCount; + } + + /** + * Returns the semantics of the given attribute. + * + * @param attributeIndex The index of the attribute in the description. Max value is + * getAttributeCount() - 1. + */ + public final int getSemantics(int attributeIndex) { + return m_indexToSemantics[attributeIndex]; + } + + /** + * Returns the index the given attribute in the vertex description. + * + * @param semantics + * @return Returns the attribute index or -1 of the attribute does not exist + */ + public final int getAttributeIndex(int semantics) { + return m_semanticsToIndexMap[semantics]; + } + + /** + * Returns the interpolation type for the attribute. + * + * @param semantics The semantics of the attribute. + */ + static int getInterpolation(int semantics) { + return _interpolation[semantics]; + } + + /** + * Returns the persistence type for the attribute. + * + * @param semantics The semantics of the attribute. + */ + static int getPersistence(int semantics) { + return _persistence[semantics]; + } + + /** + * Returns the size of the persistence type in bytes. + * + * @param persistence The persistence type to query. + */ + static int getPersistenceSize(int persistence) { + return _persistencesize[persistence]; + } + + /** + * Returns the size of the semantics in bytes. + */ + static int getPersistenceSizeSemantics(int semantics) { + return getPersistenceSize(getPersistence(semantics)) + * getComponentCount(semantics); + } + + /** + * Returns the number of the components of the given semantics. For example, + * it returns 2 for the POSITION. + * + * @param semantics The semantics of the attribute. + */ + public static int getComponentCount(int semantics) { + return _components[semantics]; + } + + /** + * Returns True if the attribute with the given name and given set exists. + * + * @param semantics The semantics of the attribute. + */ + public boolean hasAttribute(int semantics) { + return (m_semanticsBitArray & (1 << semantics)) != 0; + } + + /** + * Returns True if this vertex description includes all attributes from the + * src. + * + * @param src The Vertex_description to compare with. + * @return The function returns false, only when this description does not + * have some of the attribute that src has. + */ + public final boolean hasAttributesFrom(VertexDescription src) { + return (m_semanticsBitArray & src.m_semanticsBitArray) == src.m_semanticsBitArray; + } + + /** + * Returns True, if the vertex has Z attribute. + */ + public final boolean hasZ() { + return hasAttribute(Semantics.Z); + } + + /** + * Returns True, if the vertex has M attribute. + */ + public final boolean hasM() { + return hasAttribute(Semantics.M); + } + + /** + * Returns True, if the vertex has ID attribute. + */ + public final boolean hasID() { + return hasAttribute(Semantics.ID); + } + + /** + * Returns default value for each ordinate of the vertex attribute with + * given semantics. + */ + public static double getDefaultValue(int semantics) { + return _defaultValues[semantics]; + } + + int getPointAttributeOffset_(int attributeIndex) { + return m_pointAttributeOffsets[attributeIndex]; + } + + /** + * Returns the total component count. + */ + public int getTotalComponentCount() { + return m_totalComponentCount; + } + + /** + * Checks if the given value is the default one. The simple equality test + * with GetDefaultValue does not work due to the use of NaNs as default + * value for some parameters. + */ + public static boolean isDefaultValue(int semantics, double v) { + return NumberUtils.doubleToInt64Bits(_defaultValues[semantics]) == NumberUtils + .doubleToInt64Bits(v); + } + + static boolean isIntegerPersistence(int persistence) { + return persistence >= Persistence.enumInt32; + } + + static boolean isIntegerSemantics(int semantics) { + return isIntegerPersistence(getPersistence(semantics)); + } + + @Override + public boolean equals(Object _other) { + return (Object) this == _other; + } + + /** + * Returns a packed array of double representation of all ordinates of + * attributes of a point, i.e.: X, Y, Z, ID, TEXTURE2D.u, TEXTURE2D.v + */ + double[] _getDefaultPointAttributes() { + return m_defaultPointAttributes; + } + + double _getDefaultPointAttributeValue(int attributeIndex, int ordinate) { + return m_defaultPointAttributes[_getPointAttributeOffset(attributeIndex) + + ordinate]; + } + + /** + * Returns an offset to the first ordinate of the given attribute. This + * method is used for the cases when one wants to have a packed array of + * ordinates of all attributes, i.e.: X, Y, Z, ID, TEXTURE2D.u, TEXTURE2D.v + */ + int _getPointAttributeOffset(int attributeIndex) { + return m_pointAttributeOffsets[attributeIndex]; + } + + int _getPointAttributeOffsetFromSemantics(int semantics) { + return m_pointAttributeOffsets[getAttributeIndex(semantics)]; + } + + @Override + public int hashCode() { + return m_hash; + } + + int _getSemanticsImpl(int attributeIndex) { + return m_indexToSemantics[attributeIndex]; + } + + VertexDescription(int bitMask) { + m_semanticsBitArray = bitMask; + m_attributeCount = 0; + m_totalComponentCount = 0; + m_semanticsToIndexMap = new int[Semantics.MAXSEMANTICS + 1]; + Arrays.fill(m_semanticsToIndexMap, -1); + for (int i = 0, flag = 1, n = Semantics.MAXSEMANTICS + 1; i < n; i++) { + if ((bitMask & flag) != 0) { + m_semanticsToIndexMap[i] = m_attributeCount; + m_attributeCount++; + int comps = getComponentCount(i); + m_totalComponentCount += comps; + } + + flag <<= 1; + } + + m_indexToSemantics = new int[m_attributeCount]; + for (int i = 0, n = Semantics.MAXSEMANTICS + 1; i < n; i++) { + int attrib = m_semanticsToIndexMap[i]; + if (attrib >= 0) + m_indexToSemantics[attrib] = i; + } + + m_defaultPointAttributes = new double[m_totalComponentCount]; + m_pointAttributeOffsets = new int[m_attributeCount]; + int offset = 0; + for (int i = 0, n = m_attributeCount; i < n; i++) { + int semantics = getSemantics(i); + int comps = getComponentCount(semantics); + double v = getDefaultValue(semantics); + m_pointAttributeOffsets[i] = offset; + for (int icomp = 0; icomp < comps; icomp++) { + m_defaultPointAttributes[offset] = v; + offset++; + } + } + + m_hash = NumberUtils.hash(m_semanticsBitArray); + } + + private int m_attributeCount; + int m_semanticsBitArray; //the main component + private int m_totalComponentCount; + private int m_hash; + + private int[] m_semanticsToIndexMap; + private int[] m_indexToSemantics; + private int[] m_pointAttributeOffsets; + private double[] m_defaultPointAttributes; + + static final double[] _defaultValues = {0, 0, NumberUtils.NaN(), 0, 0, 0, + 0, 0, 0}; + + static final int[] _interpolation = {Interpolation.LINEAR, + Interpolation.LINEAR, Interpolation.LINEAR, Interpolation.NONE, + Interpolation.ANGULAR, Interpolation.LINEAR, Interpolation.LINEAR, + Interpolation.LINEAR, Interpolation.NONE,}; + + static final int[] _persistence = {Persistence.enumDouble, + Persistence.enumDouble, Persistence.enumDouble, + Persistence.enumInt32, Persistence.enumFloat, + Persistence.enumFloat, Persistence.enumFloat, + Persistence.enumFloat, Persistence.enumInt32,}; + + static final int[] _persistencesize = {4, 8, 4, 8, 1, 2}; + + static final int[] _components = {2, 1, 1, 1, 3, 1, 2, 3, 2,}; } diff --git a/src/main/java/com/esri/core/geometry/VertexDescriptionDesignerImpl.java b/src/main/java/com/esri/core/geometry/VertexDescriptionDesignerImpl.java index 2334ec50..c6d69b15 100644 --- a/src/main/java/com/esri/core/geometry/VertexDescriptionDesignerImpl.java +++ b/src/main/java/com/esri/core/geometry/VertexDescriptionDesignerImpl.java @@ -33,58 +33,58 @@ * instance. */ final class VertexDescriptionDesignerImpl { - static VertexDescription getVertexDescription(int descriptionBitMask) { - return VertexDescriptionHash.getInstance() - .FindOrAdd(descriptionBitMask); - } - - static VertexDescription getMergedVertexDescription( - VertexDescription descr1, VertexDescription descr2) { - int mask = descr1.m_semanticsBitArray | descr2.m_semanticsBitArray; - if ((mask & descr1.m_semanticsBitArray) == mask) { - return descr1; - } else if ((mask & descr2.m_semanticsBitArray) == mask) { - return descr2; - } - - return getVertexDescription(mask); - } - - static VertexDescription getMergedVertexDescription( - VertexDescription descr, int semantics) { - int mask = descr.m_semanticsBitArray | (1 << semantics); - if ((mask & descr.m_semanticsBitArray) == mask) { - return descr; - } - - return getVertexDescription(mask); - } - - static VertexDescription removeSemanticsFromVertexDescription( - VertexDescription descr, int semanticsToRemove) { - int mask = (descr.m_semanticsBitArray | (1 << (int) semanticsToRemove)) - - (1 << (int) semanticsToRemove); - if (mask == descr.m_semanticsBitArray) { - return descr; - } - - return getVertexDescription(mask); - } - - static VertexDescription getDefaultDescriptor2D() { - return VertexDescriptionHash.getInstance().getVD2D(); - } - - static VertexDescription getDefaultDescriptor3D() { - return VertexDescriptionHash.getInstance().getVD3D(); - } - - static int[] mapAttributes(VertexDescription src, VertexDescription dest) { - int[] srcToDst = new int[src.getAttributeCount()]; - Arrays.fill(srcToDst, -1); - for (int i = 0, nsrc = src.getAttributeCount(); i < nsrc; i++) { - srcToDst[i] = dest.getAttributeIndex(src.getSemantics(i)); - } - return srcToDst; - } + static VertexDescription getVertexDescription(int descriptionBitMask) { + return VertexDescriptionHash.getInstance() + .FindOrAdd(descriptionBitMask); + } + + static VertexDescription getMergedVertexDescription( + VertexDescription descr1, VertexDescription descr2) { + int mask = descr1.m_semanticsBitArray | descr2.m_semanticsBitArray; + if ((mask & descr1.m_semanticsBitArray) == mask) { + return descr1; + } else if ((mask & descr2.m_semanticsBitArray) == mask) { + return descr2; + } + + return getVertexDescription(mask); + } + + static VertexDescription getMergedVertexDescription( + VertexDescription descr, int semantics) { + int mask = descr.m_semanticsBitArray | (1 << semantics); + if ((mask & descr.m_semanticsBitArray) == mask) { + return descr; + } + + return getVertexDescription(mask); + } + + static VertexDescription removeSemanticsFromVertexDescription( + VertexDescription descr, int semanticsToRemove) { + int mask = (descr.m_semanticsBitArray | (1 << (int) semanticsToRemove)) + - (1 << (int) semanticsToRemove); + if (mask == descr.m_semanticsBitArray) { + return descr; + } + + return getVertexDescription(mask); + } + + static VertexDescription getDefaultDescriptor2D() { + return VertexDescriptionHash.getInstance().getVD2D(); + } + + static VertexDescription getDefaultDescriptor3D() { + return VertexDescriptionHash.getInstance().getVD3D(); + } + + static int[] mapAttributes(VertexDescription src, VertexDescription dest) { + int[] srcToDst = new int[src.getAttributeCount()]; + Arrays.fill(srcToDst, -1); + for (int i = 0, nsrc = src.getAttributeCount(); i < nsrc; i++) { + srcToDst[i] = dest.getAttributeIndex(src.getSemantics(i)); + } + return srcToDst; + } } diff --git a/src/main/java/com/esri/core/geometry/VertexDescriptionHash.java b/src/main/java/com/esri/core/geometry/VertexDescriptionHash.java index 9009e9b7..8e12dfec 100644 --- a/src/main/java/com/esri/core/geometry/VertexDescriptionHash.java +++ b/src/main/java/com/esri/core/geometry/VertexDescriptionHash.java @@ -37,45 +37,45 @@ * VertexDescription instances to prevent duplicates. */ final class VertexDescriptionHash { - HashMap m_map = new HashMap(); - - private static VertexDescription m_vd2D = new VertexDescription(1); - private static VertexDescription m_vd3D = new VertexDescription(3); - - private static final VertexDescriptionHash INSTANCE = new VertexDescriptionHash(); - - private VertexDescriptionHash() { - m_map.put(1, m_vd2D); - m_map.put(3, m_vd3D); - } - - public static VertexDescriptionHash getInstance() { - return INSTANCE; - } - - public final VertexDescription getVD2D() { - return m_vd2D; - } - - public final VertexDescription getVD3D() { - return m_vd3D; - } - - public final VertexDescription FindOrAdd(int bitSet) { - if (bitSet == 1) - return m_vd2D; - if (bitSet == 3) - return m_vd3D; - - synchronized (this) { - VertexDescription vd = m_map.get(bitSet); - if (vd == null) { - vd = new VertexDescription(bitSet); - m_map.put(bitSet, vd); - } - - return vd; - } - } + HashMap m_map = new HashMap(); + + private static VertexDescription m_vd2D = new VertexDescription(1); + private static VertexDescription m_vd3D = new VertexDescription(3); + + private static final VertexDescriptionHash INSTANCE = new VertexDescriptionHash(); + + private VertexDescriptionHash() { + m_map.put(1, m_vd2D); + m_map.put(3, m_vd3D); + } + + public static VertexDescriptionHash getInstance() { + return INSTANCE; + } + + public final VertexDescription getVD2D() { + return m_vd2D; + } + + public final VertexDescription getVD3D() { + return m_vd3D; + } + + public final VertexDescription FindOrAdd(int bitSet) { + if (bitSet == 1) + return m_vd2D; + if (bitSet == 3) + return m_vd3D; + + synchronized (this) { + VertexDescription vd = m_map.get(bitSet); + if (vd == null) { + vd = new VertexDescription(bitSet); + m_map.put(bitSet, vd); + } + + return vd; + } + } } diff --git a/src/main/java/com/esri/core/geometry/WkbByteOrder.java b/src/main/java/com/esri/core/geometry/WkbByteOrder.java index cba07658..c973d82e 100644 --- a/src/main/java/com/esri/core/geometry/WkbByteOrder.java +++ b/src/main/java/com/esri/core/geometry/WkbByteOrder.java @@ -25,6 +25,6 @@ package com.esri.core.geometry; interface WkbByteOrder { - public static final int wkbXDR = 0; // Big Endian - public static final int wkbNDR = 1; // Little Endian + public static final int wkbXDR = 0; // Big Endian + public static final int wkbNDR = 1; // Little Endian } diff --git a/src/main/java/com/esri/core/geometry/WkbExportFlags.java b/src/main/java/com/esri/core/geometry/WkbExportFlags.java index 88b11b8a..61b25286 100644 --- a/src/main/java/com/esri/core/geometry/WkbExportFlags.java +++ b/src/main/java/com/esri/core/geometry/WkbExportFlags.java @@ -28,14 +28,14 @@ * Flags used by the OperatorExpotToWkb. */ public interface WkbExportFlags { - public static final int wkbExportDefaults = 0;//! readTolerances(String resourceName) { - try { - ArrayList tolerances = new ArrayList(); - InputStream input = Wkid.class.getResourceAsStream(resourceName); - BufferedReader reader = new BufferedReader(new InputStreamReader( - input)); - while (true) { - String s = reader.readLine(); - if (s == null) - break; - int sep = s.indexOf('\t', 0); - String id_s = s.substring(0, sep); - int tol_index = Integer.parseInt(id_s); - if (tol_index != tolerances.size()) - throw new IllegalArgumentException("Wkid.readTolerances"); - String tol_val = s.substring(sep + 1, s.length()); - tolerances.add(Double.parseDouble(tol_val)); - } - - return tolerances; - } catch (IOException ex) { - - } - return null; - } - - static HashMap readToleranceMap(String resourceName) { - try { - HashMap hashMap = new HashMap( - 600); - InputStream input = Wkid.class.getResourceAsStream(resourceName); - BufferedReader reader = new BufferedReader(new InputStreamReader( - input)); - while (true) { - String s = reader.readLine(); - if (s == null) - break; - int sep = s.indexOf('\t', 0); - String id_s = s.substring(0, sep); - int wkid = Integer.parseInt(id_s); - String id_t = s.substring(sep + 1, s.length()); - int tol = Integer.parseInt(id_t); - hashMap.put(wkid, tol); - } - return hashMap; - } catch (IOException ex) { - } - return null; - - } - - static ArrayList m_gcs_tolerances = readTolerances("gcs_tolerances.txt"); - static ArrayList m_pcs_tolerances = readTolerances("pcs_tolerances.txt"); - static HashMap m_gcsToTol = readToleranceMap("gcs_id_to_tolerance.txt"); - static HashMap m_pcsToTol = readToleranceMap("pcs_id_to_tolerance.txt"); - static HashMap m_wkid_to_new; - static HashMap m_wkid_to_old; - - static { - try { - m_wkid_to_new = new HashMap(100); - m_wkid_to_old = new HashMap(100); - { - InputStream input = Wkid.class.getResourceAsStream("new_to_old_wkid.txt"); - BufferedReader reader = new BufferedReader( - new InputStreamReader(input)); - while (true) { - String s = reader.readLine(); - if (s == null) - break; - s = s.trim(); - if (s.length() == 0) - continue; - int sep = s.indexOf('\t', 0); - String id_s = s.substring(0, sep); - int wkid_new = Integer.parseInt(id_s); - String id_t = s.substring(sep + 1, s.length()); - int wkid_old = Integer.parseInt(id_t); - m_wkid_to_new.put(wkid_old, wkid_new); - m_wkid_to_old.put(wkid_new, wkid_old); - } - } - { - InputStream input = Wkid.class - .getResourceAsStream("intermediate_to_old_wkid.txt"); - BufferedReader reader = new BufferedReader( - new InputStreamReader(input)); - while (true) { - String s = reader.readLine(); - if (s == null) - break; - s = s.trim(); - if (s.length() == 0) - continue; - int sep = s.indexOf('\t', 0); - String id_s = s.substring(0, sep); - int wkid = Integer.parseInt(id_s); - String id_t = s.substring(sep + 1, s.length()); - int wkid_old = Integer.parseInt(id_t); - m_wkid_to_old.put(wkid, wkid_old); - m_wkid_to_new.put(wkid, m_wkid_to_new.get(wkid_old)); - } - } - - } catch (IOException ex) { - - } - } - - public static double find_tolerance_from_wkid(int wkid) { - double tol = find_tolerance_from_wkid_helper(wkid); - if (tol == 1e38) { - int old = wkid_to_old(wkid); - if (old != wkid) - tol = find_tolerance_from_wkid_helper(old); - if (tol == 1e38) - return 1e-10; - } - - return tol; - } - - private static double find_tolerance_from_wkid_helper(int wkid) { - if (m_gcsToTol.containsKey(wkid)) { - return m_gcs_tolerances.get(m_gcsToTol.get(wkid)); - } - - if (m_pcsToTol.containsKey(wkid)) { - return m_pcs_tolerances.get(m_pcsToTol.get(wkid)); - } - - return 1e38; - } - - public static int wkid_to_new(int wkid) { - if (m_wkid_to_new.containsKey(wkid)) { - return m_wkid_to_new.get(wkid); - } - return wkid; - } - - public static int wkid_to_old(int wkid) { - if (m_wkid_to_old.containsKey(wkid)) { - return m_wkid_to_old.get(wkid); - } - return wkid; - } + static ArrayList readTolerances(String resourceName) { + try { + ArrayList tolerances = new ArrayList(); + InputStream input = Wkid.class.getResourceAsStream(resourceName); + BufferedReader reader = new BufferedReader(new InputStreamReader( + input)); + while (true) { + String s = reader.readLine(); + if (s == null) + break; + int sep = s.indexOf('\t', 0); + String id_s = s.substring(0, sep); + int tol_index = Integer.parseInt(id_s); + if (tol_index != tolerances.size()) + throw new IllegalArgumentException("Wkid.readTolerances"); + String tol_val = s.substring(sep + 1, s.length()); + tolerances.add(Double.parseDouble(tol_val)); + } + + return tolerances; + } catch (IOException ex) { + + } + return null; + } + + static HashMap readToleranceMap(String resourceName) { + try { + HashMap hashMap = new HashMap( + 600); + InputStream input = Wkid.class.getResourceAsStream(resourceName); + BufferedReader reader = new BufferedReader(new InputStreamReader( + input)); + while (true) { + String s = reader.readLine(); + if (s == null) + break; + int sep = s.indexOf('\t', 0); + String id_s = s.substring(0, sep); + int wkid = Integer.parseInt(id_s); + String id_t = s.substring(sep + 1, s.length()); + int tol = Integer.parseInt(id_t); + hashMap.put(wkid, tol); + } + return hashMap; + } catch (IOException ex) { + } + return null; + + } + + static ArrayList m_gcs_tolerances = readTolerances("gcs_tolerances.txt"); + static ArrayList m_pcs_tolerances = readTolerances("pcs_tolerances.txt"); + static HashMap m_gcsToTol = readToleranceMap("gcs_id_to_tolerance.txt"); + static HashMap m_pcsToTol = readToleranceMap("pcs_id_to_tolerance.txt"); + static HashMap m_wkid_to_new; + static HashMap m_wkid_to_old; + + static { + try { + m_wkid_to_new = new HashMap(100); + m_wkid_to_old = new HashMap(100); + { + InputStream input = Wkid.class.getResourceAsStream("new_to_old_wkid.txt"); + BufferedReader reader = new BufferedReader( + new InputStreamReader(input)); + while (true) { + String s = reader.readLine(); + if (s == null) + break; + s = s.trim(); + if (s.length() == 0) + continue; + int sep = s.indexOf('\t', 0); + String id_s = s.substring(0, sep); + int wkid_new = Integer.parseInt(id_s); + String id_t = s.substring(sep + 1, s.length()); + int wkid_old = Integer.parseInt(id_t); + m_wkid_to_new.put(wkid_old, wkid_new); + m_wkid_to_old.put(wkid_new, wkid_old); + } + } + { + InputStream input = Wkid.class + .getResourceAsStream("intermediate_to_old_wkid.txt"); + BufferedReader reader = new BufferedReader( + new InputStreamReader(input)); + while (true) { + String s = reader.readLine(); + if (s == null) + break; + s = s.trim(); + if (s.length() == 0) + continue; + int sep = s.indexOf('\t', 0); + String id_s = s.substring(0, sep); + int wkid = Integer.parseInt(id_s); + String id_t = s.substring(sep + 1, s.length()); + int wkid_old = Integer.parseInt(id_t); + m_wkid_to_old.put(wkid, wkid_old); + m_wkid_to_new.put(wkid, m_wkid_to_new.get(wkid_old)); + } + } + + } catch (IOException ex) { + + } + } + + public static double find_tolerance_from_wkid(int wkid) { + double tol = find_tolerance_from_wkid_helper(wkid); + if (tol == 1e38) { + int old = wkid_to_old(wkid); + if (old != wkid) + tol = find_tolerance_from_wkid_helper(old); + if (tol == 1e38) + return 1e-10; + } + + return tol; + } + + private static double find_tolerance_from_wkid_helper(int wkid) { + if (m_gcsToTol.containsKey(wkid)) { + return m_gcs_tolerances.get(m_gcsToTol.get(wkid)); + } + + if (m_pcsToTol.containsKey(wkid)) { + return m_pcs_tolerances.get(m_pcsToTol.get(wkid)); + } + + return 1e38; + } + + public static int wkid_to_new(int wkid) { + if (m_wkid_to_new.containsKey(wkid)) { + return m_wkid_to_new.get(wkid); + } + return wkid; + } + + public static int wkid_to_old(int wkid) { + if (m_wkid_to_old.containsKey(wkid)) { + return m_wkid_to_old.get(wkid); + } + return wkid; + } } diff --git a/src/main/java/com/esri/core/geometry/Wkt.java b/src/main/java/com/esri/core/geometry/Wkt.java index 933bafc2..675baf72 100644 --- a/src/main/java/com/esri/core/geometry/Wkt.java +++ b/src/main/java/com/esri/core/geometry/Wkt.java @@ -25,85 +25,85 @@ package com.esri.core.geometry; final class Wkt { - public static double find_tolerance_from_wkt(String buffer) { - double tolerance = -1.0; + public static double find_tolerance_from_wkt(String buffer) { + double tolerance = -1.0; - if (buffer != null && buffer.length() > 0) { - int n1, n2; + if (buffer != null && buffer.length() > 0) { + int n1, n2; - n1 = buffer.indexOf("PROJCS"); - if (n1 >= 0) { - double factor = 0.0; + n1 = buffer.indexOf("PROJCS"); + if (n1 >= 0) { + double factor = 0.0; - n1 = buffer.lastIndexOf("UNIT"); - if (n1 >= 0) { - n1 = buffer.indexOf(',', n1 + 4); - if (n1 > 0) { - n1++; - n2 = buffer.indexOf(']', n1 + 1); - if (n2 > 0) { - try { - factor = Double.parseDouble(buffer.substring( - n1, n2)); - } catch (NumberFormatException e) { - factor = 0.0; - } - } - } - } + n1 = buffer.lastIndexOf("UNIT"); + if (n1 >= 0) { + n1 = buffer.indexOf(',', n1 + 4); + if (n1 > 0) { + n1++; + n2 = buffer.indexOf(']', n1 + 1); + if (n2 > 0) { + try { + factor = Double.parseDouble(buffer.substring( + n1, n2)); + } catch (NumberFormatException e) { + factor = 0.0; + } + } + } + } - if (factor > 0.0) - tolerance = (0.001 / factor); - } else { - n1 = buffer.indexOf("GEOGCS"); - if (n1 >= 0) { - double axis = 0.0; - double factor = 0.0; + if (factor > 0.0) + tolerance = (0.001 / factor); + } else { + n1 = buffer.indexOf("GEOGCS"); + if (n1 >= 0) { + double axis = 0.0; + double factor = 0.0; - n1 = buffer.indexOf("SPHEROID", n1 + 6); - if (n1 > 0) { - n1 = buffer.indexOf(',', n1 + 8); - if (n1 > 0) { - n1++; - n2 = buffer.indexOf(',', n1 + 1); - if (n2 > 0) { - try { - axis = Double.parseDouble(buffer.substring( - n1, n2)); - } catch (NumberFormatException e) { - axis = 0.0; - } - } + n1 = buffer.indexOf("SPHEROID", n1 + 6); + if (n1 > 0) { + n1 = buffer.indexOf(',', n1 + 8); + if (n1 > 0) { + n1++; + n2 = buffer.indexOf(',', n1 + 1); + if (n2 > 0) { + try { + axis = Double.parseDouble(buffer.substring( + n1, n2)); + } catch (NumberFormatException e) { + axis = 0.0; + } + } - if (axis > 0.0) { - n1 = buffer.indexOf("UNIT", n2 + 1); - if (n1 >= 0) { - n1 = buffer.indexOf(',', n1 + 4); - if (n1 > 0) { - n1++; - n2 = buffer.indexOf(',', n1 + 1); - if (n2 > 0) { - try { - factor = Double - .parseDouble(buffer - .substring(n1, - n2)); - } catch (NumberFormatException e) { - factor = 0.0; - } - } - } - } - } - } - } + if (axis > 0.0) { + n1 = buffer.indexOf("UNIT", n2 + 1); + if (n1 >= 0) { + n1 = buffer.indexOf(',', n1 + 4); + if (n1 > 0) { + n1++; + n2 = buffer.indexOf(',', n1 + 1); + if (n2 > 0) { + try { + factor = Double + .parseDouble(buffer + .substring(n1, + n2)); + } catch (NumberFormatException e) { + factor = 0.0; + } + } + } + } + } + } + } - if (axis > 0.0 && factor > 0.0) - tolerance = (0.001 / (axis * factor)); - } - } - } + if (axis > 0.0 && factor > 0.0) + tolerance = (0.001 / (axis * factor)); + } + } + } - return tolerance; - } + return tolerance; + } } diff --git a/src/main/java/com/esri/core/geometry/WktExportFlags.java b/src/main/java/com/esri/core/geometry/WktExportFlags.java index cea6bbdb..66fe7cad 100644 --- a/src/main/java/com/esri/core/geometry/WktExportFlags.java +++ b/src/main/java/com/esri/core/geometry/WktExportFlags.java @@ -27,21 +27,21 @@ * Flags used by the OperatorExportToWkt */ public interface WktExportFlags { - public static final int wktExportDefaults = 0; - public static final int wktExportPoint = 1; - public static final int wktExportMultiPoint = 2; - public static final int wktExportLineString = 4; - public static final int wktExportMultiLineString = 8; - public static final int wktExportPolygon = 16; - public static final int wktExportMultiPolygon = 32; - public static final int wktExportStripZs = 64; - public static final int wktExportStripMs = 128; - public static final int wktExportFailIfNotSimple = 4096; - public static final int wktExportPrecision16 = 0x2000; - public static final int wktExportPrecision15 = 0x4000; - public static final int wktExportPrecision14 = 0x6000; - public static final int wktExportPrecision13 = 0x8000; - public static final int wktExportPrecision12 = 0xa000; - public static final int wktExportPrecision11 = 0xc000; - public static final int wktExportPrecision10 = 0xe000; + public static final int wktExportDefaults = 0; + public static final int wktExportPoint = 1; + public static final int wktExportMultiPoint = 2; + public static final int wktExportLineString = 4; + public static final int wktExportMultiLineString = 8; + public static final int wktExportPolygon = 16; + public static final int wktExportMultiPolygon = 32; + public static final int wktExportStripZs = 64; + public static final int wktExportStripMs = 128; + public static final int wktExportFailIfNotSimple = 4096; + public static final int wktExportPrecision16 = 0x2000; + public static final int wktExportPrecision15 = 0x4000; + public static final int wktExportPrecision14 = 0x6000; + public static final int wktExportPrecision13 = 0x8000; + public static final int wktExportPrecision12 = 0xa000; + public static final int wktExportPrecision11 = 0xc000; + public static final int wktExportPrecision10 = 0xe000; } diff --git a/src/main/java/com/esri/core/geometry/WktImportFlags.java b/src/main/java/com/esri/core/geometry/WktImportFlags.java index 4e197239..16c4429d 100644 --- a/src/main/java/com/esri/core/geometry/WktImportFlags.java +++ b/src/main/java/com/esri/core/geometry/WktImportFlags.java @@ -27,6 +27,6 @@ * Flags used by the OperatorImportFromWkt. */ public interface WktImportFlags { - public static final int wktImportDefaults = 0;//! 200 ? m_wkt_string - // .substring(0, 200) + "..." : m_wkt_string); - //throw new IllegalArgumentException( - // "Could not parse Well-Known Text: " + snippet); - throw new IllegalArgumentException( - "Could not parse Well-Known Text around position: " + m_end_token); - } - - m_function_stack.add(State.attributes); - } - - private void attributes_() { - skipWhiteSpace_(); - m_start_token = m_end_token; - m_function_stack.removeLast(); - - // Z and M is not allowed to have a space between them - boolean b_has_zs = false, b_has_ms = false; - - if (m_wkt_string.charAt(m_end_token) == 'z' - || m_wkt_string.charAt(m_end_token) == 'Z') { - b_has_zs = true; - - if (++m_end_token >= m_wkt_string.length()) - throw new IllegalArgumentException(); - } - - if (m_wkt_string.charAt(m_end_token) == 'm' - || m_wkt_string.charAt(m_end_token) == 'M') { - b_has_ms = true; - - if (++m_end_token >= m_wkt_string.length()) - throw new IllegalArgumentException(); - } - - if (m_b_check_consistent_attributes) { - if (b_has_zs != m_b_has_zs || b_has_ms != m_b_has_ms) - throw new IllegalArgumentException(); - } - - m_b_has_zs = b_has_zs; - m_b_has_ms = b_has_ms; - - if (m_b_has_zs || m_b_has_ms) { - if (m_b_has_zs && !m_b_has_ms) - m_current_token_type = WktToken.attribute_z; - else if (m_b_has_ms && !m_b_has_zs) - m_current_token_type = WktToken.attribute_m; - else - m_current_token_type = WktToken.attribute_zm; - } else { - nextToken(); - } - } - - private void geometryCollectionStart_() { - skipWhiteSpace_(); - m_start_token = m_end_token; - m_b_check_consistent_attributes = true; - - if (empty_()) { - m_function_stack.removeLast(); - } else if (leftParen_()) { - m_function_stack.removeLast(); - m_function_stack.add(State.geometryCollectionEnd); - m_function_stack.add(State.geometry); - } else { - throw new IllegalArgumentException(); - } - } - - private void geometryCollectionEnd_() { - skipWhiteSpace_(); - m_start_token = m_end_token; - - if (comma_()) { - m_function_stack.add(State.geometry); - geometry_(); - } else if (rightParen_()) { - m_function_stack.removeLast(); - } else { - throw new IllegalArgumentException(); - } - } - - private void multiPolygonStart_() { - skipWhiteSpace_(); - m_start_token = m_end_token; - - if (empty_()) { - m_function_stack.removeLast(); - } else if (leftParen_()) { - m_function_stack.removeLast(); - m_function_stack.add(State.multiPolygonEnd); - m_function_stack.add(State.polygonStart); - } else { - throw new IllegalArgumentException(); - } - } - - private void multiPolygonEnd_() { - skipWhiteSpace_(); - m_start_token = m_end_token; - - if (comma_()) { - m_function_stack.add(State.polygonStart); - polygonStart_(); - } else if (rightParen_()) { - m_function_stack.removeLast(); - } else { - throw new IllegalArgumentException(); - } - } - - private void multiLineStringStart_() { - skipWhiteSpace_(); - m_start_token = m_end_token; - - if (empty_()) { - m_function_stack.removeLast(); - } else if (leftParen_()) { - m_function_stack.removeLast(); - m_function_stack.add(State.multiLineStringEnd); - m_function_stack.add(State.lineStringStart); - } else { - throw new IllegalArgumentException(); - } - } - - private void multiLineStringEnd_() { - skipWhiteSpace_(); - m_start_token = m_end_token; - - if (comma_()) { - m_function_stack.add(State.lineStringStart); - lineStringStart_(); - } else if (rightParen_()) { - m_function_stack.removeLast(); - } else { - throw new IllegalArgumentException(); - } - } - - private void multiPointStart_() { - skipWhiteSpace_(); - m_start_token = m_end_token; - - if (empty_()) { - m_function_stack.removeLast(); - } else if (leftParen_()) { - m_function_stack.removeLast(); - m_function_stack.add(State.multiPointEnd); - m_function_stack.add(State.pointStartAlt); - } else { - throw new IllegalArgumentException(); - } - } - - private void multiPointEnd_() { - skipWhiteSpace_(); - m_start_token = m_end_token; - - if (comma_()) { - m_function_stack.add(State.pointStart); - pointStart_(); - } else if (rightParen_()) { - m_function_stack.removeLast(); - } else { - throw new IllegalArgumentException(); - } - } - - private void polygonStart_() { - skipWhiteSpace_(); - m_start_token = m_end_token; - - if (empty_()) { - m_function_stack.removeLast(); - } else if (leftParen_()) { - m_function_stack.removeLast(); - m_function_stack.add(State.polygonEnd); - m_function_stack.add(State.lineStringStart); - } else { - throw new IllegalArgumentException(); - } - } - - private void polygonEnd_() { - skipWhiteSpace_(); - m_start_token = m_end_token; - - if (comma_()) { - m_function_stack.add(State.lineStringStart); - lineStringStart_(); - } else if (rightParen_()) { - m_function_stack.removeLast(); - } else { - throw new IllegalArgumentException(); - } - } - - private void lineStringStart_() { - skipWhiteSpace_(); - m_start_token = m_end_token; - - if (empty_()) { - m_function_stack.removeLast(); - } else if (leftParen_()) { - m_function_stack.removeLast(); - m_function_stack.add(State.lineStringEnd); - m_function_stack.add(State.xLiteral); - } else { - throw new IllegalArgumentException(); - } - } - - private void lineStringEnd_() { - skipWhiteSpace_(); - m_start_token = m_end_token; - - if (comma_()) { - m_function_stack.add(State.xLiteral); - xLiteral_(); - } else if (rightParen_()) { - m_function_stack.removeLast(); - } else { - throw new IllegalArgumentException(); - } - } - - private void pointStart_() { - skipWhiteSpace_(); - m_start_token = m_end_token; - - if (empty_()) { - m_function_stack.removeLast(); - } else if (leftParen_()) { - m_function_stack.removeLast(); - m_function_stack.add(State.pointEnd); - m_function_stack.add(State.xLiteral); - } else { - throw new IllegalArgumentException(); - } - } - - private void pointStartAlt_() { - skipWhiteSpace_(); - m_start_token = m_end_token; - - if (empty_()) {// ogc standard - m_function_stack.removeLast(); - } else if (leftParen_()) {// ogc standard - m_function_stack.removeLast(); - m_function_stack.add(State.pointEnd); - m_function_stack.add(State.xLiteral); - } else {// not ogc standard. treat as linestring - m_function_stack.removeLast(); - m_function_stack.removeLast(); - m_function_stack.add(State.lineStringEnd); - m_function_stack.add(State.xLiteral); - nextToken(); - } - } - - private void pointEnd_() { - skipWhiteSpace_(); - m_start_token = m_end_token; - - if (rightParen_()) { - m_function_stack.removeLast(); - } else { - throw new IllegalArgumentException(); - } - } - - private void xLiteral_() { - signedNumericLiteral_(); - m_current_token_type = WktToken.x_literal; - m_function_stack.removeLast(); - m_function_stack.add(State.yLiteral); - } - - private void yLiteral_() { - signedNumericLiteral_(); - m_current_token_type = WktToken.y_literal; - m_function_stack.removeLast(); - - if (m_b_has_zs) - m_function_stack.add(State.zLiteral); - else if (m_b_has_ms) - m_function_stack.add(State.mLiteral); - } - - private void zLiteral_() { - signedNumericLiteral_(); - m_current_token_type = WktToken.z_literal; - m_function_stack.removeLast(); - - if (m_b_has_ms) - m_function_stack.add(State.mLiteral); - } - - private void mLiteral_() { - signedNumericLiteral_(); - m_current_token_type = WktToken.m_literal; - m_function_stack.removeLast(); - } - - private boolean nan_() { - if (m_start_token + 3 <= m_wkt_string.length() - && m_wkt_string.regionMatches(true, m_start_token, "nan", 0, 3)) { - m_end_token += 3; - m_b_nan = true; - return true; - } - - m_b_nan = false; - return false; - } - - private void sign_() { - // Optional - or + sign - if (m_wkt_string.charAt(m_end_token) == '-' - || m_wkt_string.charAt(m_end_token) == '+') { - if (++m_end_token >= m_wkt_string.length()) - throw new IllegalArgumentException(); - } - } - - private void signedNumericLiteral_() { - skipWhiteSpace_(); - m_start_token = m_end_token; - - if (nan_()) - return; - - sign_(); // Optional - unsignedNumericLiteral_(); - } - - private void unsignedNumericLiteral_() { - exactNumericLiteral_(); - exp_(); // Optional - } - - private void exactNumericLiteral_() { - if (Character.isDigit(m_wkt_string.charAt(m_end_token))) { - digits_(); - - // Optional - if (m_wkt_string.charAt(m_end_token) == '.') { - if (++m_end_token >= m_wkt_string.length()) - throw new IllegalArgumentException(); - - // Optional - if (Character.isDigit(m_wkt_string.charAt(m_end_token))) - digits_(); - } - } else if (m_wkt_string.charAt(m_end_token) == '.') { - if (++m_end_token >= m_wkt_string.length()) - throw new IllegalArgumentException(); - - if (!Character.isDigit(m_wkt_string.charAt(m_end_token))) - throw new IllegalArgumentException(); - - digits_(); - } else { - throw new IllegalArgumentException(); - } - } - - private void digits_() { - do { - if (++m_end_token >= m_wkt_string.length()) - throw new IllegalArgumentException(); - - } while (Character.isDigit(m_wkt_string.charAt(m_end_token))); - } - - private void exp_() { - // This is an optional state - if (m_wkt_string.charAt(m_end_token) == 'e' - || m_wkt_string.charAt(m_end_token) == 'E') { - if (++m_end_token >= m_wkt_string.length()) - throw new IllegalArgumentException(); - - sign_(); // optional - - if (!Character.isDigit(m_wkt_string.charAt(m_end_token))) - throw new IllegalArgumentException(); - - digits_(); - } - } - - private void skipWhiteSpace_() { - if (m_end_token >= m_wkt_string.length()) - throw new IllegalArgumentException(); - - while (Character.isWhitespace(m_wkt_string.charAt(m_end_token))) { - if (++m_end_token >= m_wkt_string.length()) - throw new IllegalArgumentException(); - } - } - - private boolean empty_() { - if (m_start_token + 5 <= m_wkt_string.length() - && m_wkt_string.regionMatches(true, m_start_token, "empty", 0, 5)) { - m_end_token += 5; - m_current_token_type = WktToken.empty; - return true; - } - - return false; - } - - private boolean comma_() { - if (m_wkt_string.charAt(m_end_token) == ',') { - m_end_token++; - return true; - } - - return false; - } - - private boolean leftParen_() { - if (m_wkt_string.charAt(m_end_token) == '(') { - m_end_token++; - m_current_token_type = WktToken.left_paren; - return true; - } - - return false; - } - - private boolean rightParen_() { - if (m_wkt_string.charAt(m_end_token) == ')') { - m_end_token++; - m_current_token_type = WktToken.right_paren; - return true; - } - - return false; - } + interface WktToken { + static final int not_available = 0; + static final int empty = 50; + static final int left_paren = 51; + static final int right_paren = 52; + static final int x_literal = 0x80000000; + static final int y_literal = 0x40000000; + static final int z_literal = 0x20000000; + static final int m_literal = 0x10000000; + static final int point = 1; + static final int linestring = 2; + static final int polygon = 3; + static final int multipoint = 4; + static final int multilinestring = 5; + static final int multipolygon = 6; + static final int geometrycollection = 7; + static final int attribute_z = 1000; + static final int attribute_m = 2000; + static final int attribute_zm = 3000; + } + + WktParser() { + } + + WktParser(String string) { + resetParser(string); + } + + void resetParser(String string) { + if (m_function_stack == null) + m_function_stack = new AttributeStreamOfInt32(0); + + reset_(); + m_wkt_string = string; + } + + int nextToken() { + switch (m_function_stack.getLast()) { + case State.xLiteral: + xLiteral_(); + break; + case State.yLiteral: + yLiteral_(); + break; + case State.zLiteral: + zLiteral_(); + break; + case State.mLiteral: + mLiteral_(); + break; + case State.pointStart: + pointStart_(); + break; + case State.pointStartAlt: + pointStartAlt_(); + break; + case State.pointEnd: + pointEnd_(); + break; + case State.lineStringStart: + lineStringStart_(); + break; + case State.lineStringEnd: + lineStringEnd_(); + break; + case State.multiPointStart: + multiPointStart_(); + break; + case State.multiPointEnd: + multiPointEnd_(); + break; + case State.polygonStart: + polygonStart_(); + break; + case State.polygonEnd: + polygonEnd_(); + break; + case State.multiLineStringStart: + multiLineStringStart_(); + break; + case State.multiLineStringEnd: + multiLineStringEnd_(); + break; + case State.multiPolygonStart: + multiPolygonStart_(); + break; + case State.multiPolygonEnd: + multiPolygonEnd_(); + break; + case State.geometryCollectionStart: + geometryCollectionStart_(); + break; + case State.geometryCollectionEnd: + geometryCollectionEnd_(); + break; + case State.accept: + accept_(); + break; + case State.geometry: + geometry_(); + break; + case State.attributes: + attributes_(); + break; + } + + return m_current_token_type; + } + + double currentNumericLiteral() { + if (((int) m_current_token_type & (int) Number.signed_numeric_literal) == 0) + throw new GeometryException("runtime error"); + + if (m_b_nan) + return NumberUtils.TheNaN; + + double value = Double.parseDouble(m_wkt_string.substring(m_start_token, + m_end_token)); + return value; + } + + int currentToken() { + return m_current_token_type; + } + + boolean hasZs() { + return m_b_has_zs; + } + + boolean hasMs() { + return m_b_has_ms; + } + + private String m_wkt_string; + private int m_start_token; + private int m_end_token; + private int m_current_token_type; + + private boolean m_b_has_zs; + private boolean m_b_has_ms; + private boolean m_b_check_consistent_attributes; + private boolean m_b_nan; + + private AttributeStreamOfInt32 m_function_stack; + + private interface State { + static final int xLiteral = 0; + static final int yLiteral = 1; + static final int zLiteral = 2; + static final int mLiteral = 3; + static final int pointStart = 4; + static final int pointStartAlt = 5; + static final int pointEnd = 6; + static final int lineStringStart = 7; + static final int lineStringEnd = 8; + static final int multiPointStart = 9; + static final int multiPointEnd = 10; + static final int polygonStart = 11; + static final int polygonEnd = 12; + static final int multiLineStringStart = 13; + static final int multiLineStringEnd = 14; + static final int multiPolygonStart = 15; + static final int multiPolygonEnd = 16; + static final int geometryCollectionStart = 17; + static final int geometryCollectionEnd = 18; + static final int accept = 19; + static final int geometry = 20; + static final int attributes = 21; + } + + private interface Number { + static final int signed_numeric_literal = WktToken.x_literal + | WktToken.y_literal | WktToken.z_literal | WktToken.m_literal; + } + + private void reset_() { + m_function_stack.add(State.accept); + m_function_stack.add(State.geometry); + m_start_token = -1; + m_end_token = 0; + m_current_token_type = WktToken.not_available; + m_b_has_zs = false; + m_b_has_ms = false; + m_b_check_consistent_attributes = false; + m_b_nan = false; + } + + private void accept_() { + m_start_token = m_end_token; + m_current_token_type = WktToken.not_available; + } + + private void geometry_() { + skipWhiteSpace_(); + m_start_token = m_end_token; + m_function_stack.removeLast(); + + if (m_start_token + 5 <= m_wkt_string.length() + && m_wkt_string.regionMatches(true, m_start_token, "point", 0, + 5)) { + m_end_token = m_start_token + 5; + m_current_token_type = WktToken.point; + m_function_stack.add(State.pointStart); + } else if (m_start_token + 10 <= m_wkt_string.length() + && m_wkt_string.regionMatches(true, m_start_token, + "linestring", 0, 10)) { + m_end_token = m_start_token + 10; + m_current_token_type = WktToken.linestring; + m_function_stack.add(State.lineStringStart); + } else if (m_start_token + 10 <= m_wkt_string.length() + && m_wkt_string.regionMatches(true, m_start_token, + "multipoint", 0, 10)) { + m_end_token = m_start_token + 10; + m_current_token_type = WktToken.multipoint; + m_function_stack.add(State.multiPointStart); + } else if (m_start_token + 7 <= m_wkt_string.length() + && m_wkt_string.regionMatches(true, m_start_token, "polygon", + 0, 7)) { + m_end_token = m_start_token + 7; + m_current_token_type = WktToken.polygon; + m_function_stack.add(State.polygonStart); + } else if (m_start_token + 15 <= m_wkt_string.length() + && m_wkt_string.regionMatches(true, m_start_token, + "multilinestring", 0, 15)) { + m_end_token = m_start_token + 15; + m_current_token_type = WktToken.multilinestring; + m_function_stack.add(State.multiLineStringStart); + } else if (m_start_token + 12 <= m_wkt_string.length() + && m_wkt_string.regionMatches(true, m_start_token, + "multipolygon", 0, 12)) { + m_end_token = m_start_token + 12; + m_current_token_type = WktToken.multipolygon; + m_function_stack.add(State.multiPolygonStart); + } else if (m_start_token + 18 <= m_wkt_string.length() + && m_wkt_string.regionMatches(true, m_start_token, + "geometrycollection", 0, 18)) { + m_end_token = m_start_token + 18; + m_current_token_type = WktToken.geometrycollection; + m_function_stack.add(State.geometryCollectionStart); + } else { + //String snippet = (m_wkt_string.length() > 200 ? m_wkt_string + // .substring(0, 200) + "..." : m_wkt_string); + //throw new IllegalArgumentException( + // "Could not parse Well-Known Text: " + snippet); + throw new IllegalArgumentException( + "Could not parse Well-Known Text around position: " + m_end_token); + } + + m_function_stack.add(State.attributes); + } + + private void attributes_() { + skipWhiteSpace_(); + m_start_token = m_end_token; + m_function_stack.removeLast(); + + // Z and M is not allowed to have a space between them + boolean b_has_zs = false, b_has_ms = false; + + if (m_wkt_string.charAt(m_end_token) == 'z' + || m_wkt_string.charAt(m_end_token) == 'Z') { + b_has_zs = true; + + if (++m_end_token >= m_wkt_string.length()) + throw new IllegalArgumentException(); + } + + if (m_wkt_string.charAt(m_end_token) == 'm' + || m_wkt_string.charAt(m_end_token) == 'M') { + b_has_ms = true; + + if (++m_end_token >= m_wkt_string.length()) + throw new IllegalArgumentException(); + } + + if (m_b_check_consistent_attributes) { + if (b_has_zs != m_b_has_zs || b_has_ms != m_b_has_ms) + throw new IllegalArgumentException(); + } + + m_b_has_zs = b_has_zs; + m_b_has_ms = b_has_ms; + + if (m_b_has_zs || m_b_has_ms) { + if (m_b_has_zs && !m_b_has_ms) + m_current_token_type = WktToken.attribute_z; + else if (m_b_has_ms && !m_b_has_zs) + m_current_token_type = WktToken.attribute_m; + else + m_current_token_type = WktToken.attribute_zm; + } else { + nextToken(); + } + } + + private void geometryCollectionStart_() { + skipWhiteSpace_(); + m_start_token = m_end_token; + m_b_check_consistent_attributes = true; + + if (empty_()) { + m_function_stack.removeLast(); + } else if (leftParen_()) { + m_function_stack.removeLast(); + m_function_stack.add(State.geometryCollectionEnd); + m_function_stack.add(State.geometry); + } else { + throw new IllegalArgumentException(); + } + } + + private void geometryCollectionEnd_() { + skipWhiteSpace_(); + m_start_token = m_end_token; + + if (comma_()) { + m_function_stack.add(State.geometry); + geometry_(); + } else if (rightParen_()) { + m_function_stack.removeLast(); + } else { + throw new IllegalArgumentException(); + } + } + + private void multiPolygonStart_() { + skipWhiteSpace_(); + m_start_token = m_end_token; + + if (empty_()) { + m_function_stack.removeLast(); + } else if (leftParen_()) { + m_function_stack.removeLast(); + m_function_stack.add(State.multiPolygonEnd); + m_function_stack.add(State.polygonStart); + } else { + throw new IllegalArgumentException(); + } + } + + private void multiPolygonEnd_() { + skipWhiteSpace_(); + m_start_token = m_end_token; + + if (comma_()) { + m_function_stack.add(State.polygonStart); + polygonStart_(); + } else if (rightParen_()) { + m_function_stack.removeLast(); + } else { + throw new IllegalArgumentException(); + } + } + + private void multiLineStringStart_() { + skipWhiteSpace_(); + m_start_token = m_end_token; + + if (empty_()) { + m_function_stack.removeLast(); + } else if (leftParen_()) { + m_function_stack.removeLast(); + m_function_stack.add(State.multiLineStringEnd); + m_function_stack.add(State.lineStringStart); + } else { + throw new IllegalArgumentException(); + } + } + + private void multiLineStringEnd_() { + skipWhiteSpace_(); + m_start_token = m_end_token; + + if (comma_()) { + m_function_stack.add(State.lineStringStart); + lineStringStart_(); + } else if (rightParen_()) { + m_function_stack.removeLast(); + } else { + throw new IllegalArgumentException(); + } + } + + private void multiPointStart_() { + skipWhiteSpace_(); + m_start_token = m_end_token; + + if (empty_()) { + m_function_stack.removeLast(); + } else if (leftParen_()) { + m_function_stack.removeLast(); + m_function_stack.add(State.multiPointEnd); + m_function_stack.add(State.pointStartAlt); + } else { + throw new IllegalArgumentException(); + } + } + + private void multiPointEnd_() { + skipWhiteSpace_(); + m_start_token = m_end_token; + + if (comma_()) { + m_function_stack.add(State.pointStart); + pointStart_(); + } else if (rightParen_()) { + m_function_stack.removeLast(); + } else { + throw new IllegalArgumentException(); + } + } + + private void polygonStart_() { + skipWhiteSpace_(); + m_start_token = m_end_token; + + if (empty_()) { + m_function_stack.removeLast(); + } else if (leftParen_()) { + m_function_stack.removeLast(); + m_function_stack.add(State.polygonEnd); + m_function_stack.add(State.lineStringStart); + } else { + throw new IllegalArgumentException(); + } + } + + private void polygonEnd_() { + skipWhiteSpace_(); + m_start_token = m_end_token; + + if (comma_()) { + m_function_stack.add(State.lineStringStart); + lineStringStart_(); + } else if (rightParen_()) { + m_function_stack.removeLast(); + } else { + throw new IllegalArgumentException(); + } + } + + private void lineStringStart_() { + skipWhiteSpace_(); + m_start_token = m_end_token; + + if (empty_()) { + m_function_stack.removeLast(); + } else if (leftParen_()) { + m_function_stack.removeLast(); + m_function_stack.add(State.lineStringEnd); + m_function_stack.add(State.xLiteral); + } else { + throw new IllegalArgumentException(); + } + } + + private void lineStringEnd_() { + skipWhiteSpace_(); + m_start_token = m_end_token; + + if (comma_()) { + m_function_stack.add(State.xLiteral); + xLiteral_(); + } else if (rightParen_()) { + m_function_stack.removeLast(); + } else { + throw new IllegalArgumentException(); + } + } + + private void pointStart_() { + skipWhiteSpace_(); + m_start_token = m_end_token; + + if (empty_()) { + m_function_stack.removeLast(); + } else if (leftParen_()) { + m_function_stack.removeLast(); + m_function_stack.add(State.pointEnd); + m_function_stack.add(State.xLiteral); + } else { + throw new IllegalArgumentException(); + } + } + + private void pointStartAlt_() { + skipWhiteSpace_(); + m_start_token = m_end_token; + + if (empty_()) {// ogc standard + m_function_stack.removeLast(); + } else if (leftParen_()) {// ogc standard + m_function_stack.removeLast(); + m_function_stack.add(State.pointEnd); + m_function_stack.add(State.xLiteral); + } else {// not ogc standard. treat as linestring + m_function_stack.removeLast(); + m_function_stack.removeLast(); + m_function_stack.add(State.lineStringEnd); + m_function_stack.add(State.xLiteral); + nextToken(); + } + } + + private void pointEnd_() { + skipWhiteSpace_(); + m_start_token = m_end_token; + + if (rightParen_()) { + m_function_stack.removeLast(); + } else { + throw new IllegalArgumentException(); + } + } + + private void xLiteral_() { + signedNumericLiteral_(); + m_current_token_type = WktToken.x_literal; + m_function_stack.removeLast(); + m_function_stack.add(State.yLiteral); + } + + private void yLiteral_() { + signedNumericLiteral_(); + m_current_token_type = WktToken.y_literal; + m_function_stack.removeLast(); + + if (m_b_has_zs) + m_function_stack.add(State.zLiteral); + else if (m_b_has_ms) + m_function_stack.add(State.mLiteral); + } + + private void zLiteral_() { + signedNumericLiteral_(); + m_current_token_type = WktToken.z_literal; + m_function_stack.removeLast(); + + if (m_b_has_ms) + m_function_stack.add(State.mLiteral); + } + + private void mLiteral_() { + signedNumericLiteral_(); + m_current_token_type = WktToken.m_literal; + m_function_stack.removeLast(); + } + + private boolean nan_() { + if (m_start_token + 3 <= m_wkt_string.length() + && m_wkt_string.regionMatches(true, m_start_token, "nan", 0, 3)) { + m_end_token += 3; + m_b_nan = true; + return true; + } + + m_b_nan = false; + return false; + } + + private void sign_() { + // Optional - or + sign + if (m_wkt_string.charAt(m_end_token) == '-' + || m_wkt_string.charAt(m_end_token) == '+') { + if (++m_end_token >= m_wkt_string.length()) + throw new IllegalArgumentException(); + } + } + + private void signedNumericLiteral_() { + skipWhiteSpace_(); + m_start_token = m_end_token; + + if (nan_()) + return; + + sign_(); // Optional + unsignedNumericLiteral_(); + } + + private void unsignedNumericLiteral_() { + exactNumericLiteral_(); + exp_(); // Optional + } + + private void exactNumericLiteral_() { + if (Character.isDigit(m_wkt_string.charAt(m_end_token))) { + digits_(); + + // Optional + if (m_wkt_string.charAt(m_end_token) == '.') { + if (++m_end_token >= m_wkt_string.length()) + throw new IllegalArgumentException(); + + // Optional + if (Character.isDigit(m_wkt_string.charAt(m_end_token))) + digits_(); + } + } else if (m_wkt_string.charAt(m_end_token) == '.') { + if (++m_end_token >= m_wkt_string.length()) + throw new IllegalArgumentException(); + + if (!Character.isDigit(m_wkt_string.charAt(m_end_token))) + throw new IllegalArgumentException(); + + digits_(); + } else { + throw new IllegalArgumentException(); + } + } + + private void digits_() { + do { + if (++m_end_token >= m_wkt_string.length()) + throw new IllegalArgumentException(); + + } while (Character.isDigit(m_wkt_string.charAt(m_end_token))); + } + + private void exp_() { + // This is an optional state + if (m_wkt_string.charAt(m_end_token) == 'e' + || m_wkt_string.charAt(m_end_token) == 'E') { + if (++m_end_token >= m_wkt_string.length()) + throw new IllegalArgumentException(); + + sign_(); // optional + + if (!Character.isDigit(m_wkt_string.charAt(m_end_token))) + throw new IllegalArgumentException(); + + digits_(); + } + } + + private void skipWhiteSpace_() { + if (m_end_token >= m_wkt_string.length()) + throw new IllegalArgumentException(); + + while (Character.isWhitespace(m_wkt_string.charAt(m_end_token))) { + if (++m_end_token >= m_wkt_string.length()) + throw new IllegalArgumentException(); + } + } + + private boolean empty_() { + if (m_start_token + 5 <= m_wkt_string.length() + && m_wkt_string.regionMatches(true, m_start_token, "empty", 0, 5)) { + m_end_token += 5; + m_current_token_type = WktToken.empty; + return true; + } + + return false; + } + + private boolean comma_() { + if (m_wkt_string.charAt(m_end_token) == ',') { + m_end_token++; + return true; + } + + return false; + } + + private boolean leftParen_() { + if (m_wkt_string.charAt(m_end_token) == '(') { + m_end_token++; + m_current_token_type = WktToken.left_paren; + return true; + } + + return false; + } + + private boolean rightParen_() { + if (m_wkt_string.charAt(m_end_token) == ')') { + m_end_token++; + m_current_token_type = WktToken.right_paren; + return true; + } + + return false; + } } diff --git a/src/main/java/com/esri/core/geometry/ogc/OGCConcreteGeometryCollection.java b/src/main/java/com/esri/core/geometry/ogc/OGCConcreteGeometryCollection.java index 4947470f..284b035c 100644 --- a/src/main/java/com/esri/core/geometry/ogc/OGCConcreteGeometryCollection.java +++ b/src/main/java/com/esri/core/geometry/ogc/OGCConcreteGeometryCollection.java @@ -41,75 +41,74 @@ import static com.esri.core.geometry.SizeOf.SIZE_OF_OGC_CONCRETE_GEOMETRY_COLLECTION; public class OGCConcreteGeometryCollection extends OGCGeometryCollection { - public OGCConcreteGeometryCollection(List geoms, - SpatialReference sr) { - geometries = geoms; - esriSR = sr; - } - - public OGCConcreteGeometryCollection(OGCGeometry geom, SpatialReference sr) { - geometries = new ArrayList(1); - geometries.add(geom); - esriSR = sr; - } - - @Override - public int dimension() { - int maxD = 0; - for (int i = 0, n = numGeometries(); i < n; i++) - maxD = Math.max(geometryN(i).dimension(), maxD); - - return maxD; - } - - @Override - public int coordinateDimension() { - return isEmpty() ? 2 : geometryN(0).coordinateDimension(); - } - - @Override - public boolean is3D() { - return !isEmpty() && geometries.get(0).is3D(); - } - - @Override - public boolean isMeasured() { - return !isEmpty() && geometries.get(0).isMeasured(); - } - - @Override - public OGCGeometry envelope() { - GeometryCursor gc = getEsriGeometryCursor(); - Envelope env = new Envelope(); - for (Geometry g = gc.next(); g != null; g = gc.next()) { - Envelope e = new Envelope(); - g.queryEnvelope(e); - env.merge(e); - } - - Polygon polygon = new Polygon(); - polygon.addEnvelope(env, false); - return new OGCPolygon(polygon, esriSR); - } - - @Override - public int numGeometries() { - return geometries.size(); - } - - @Override - public OGCGeometry geometryN(int n) { - return geometries.get(n); - } - - @Override - public String geometryType() { - return "GeometryCollection"; - } - - @Override - public long estimateMemorySize() - { + public OGCConcreteGeometryCollection(List geoms, + SpatialReference sr) { + geometries = geoms; + esriSR = sr; + } + + public OGCConcreteGeometryCollection(OGCGeometry geom, SpatialReference sr) { + geometries = new ArrayList(1); + geometries.add(geom); + esriSR = sr; + } + + @Override + public int dimension() { + int maxD = 0; + for (int i = 0, n = numGeometries(); i < n; i++) + maxD = Math.max(geometryN(i).dimension(), maxD); + + return maxD; + } + + @Override + public int coordinateDimension() { + return isEmpty() ? 2 : geometryN(0).coordinateDimension(); + } + + @Override + public boolean is3D() { + return !isEmpty() && geometries.get(0).is3D(); + } + + @Override + public boolean isMeasured() { + return !isEmpty() && geometries.get(0).isMeasured(); + } + + @Override + public OGCGeometry envelope() { + GeometryCursor gc = getEsriGeometryCursor(); + Envelope env = new Envelope(); + for (Geometry g = gc.next(); g != null; g = gc.next()) { + Envelope e = new Envelope(); + g.queryEnvelope(e); + env.merge(e); + } + + Polygon polygon = new Polygon(); + polygon.addEnvelope(env, false); + return new OGCPolygon(polygon, esriSR); + } + + @Override + public int numGeometries() { + return geometries.size(); + } + + @Override + public OGCGeometry geometryN(int n) { + return geometries.get(n); + } + + @Override + public String geometryType() { + return "GeometryCollection"; + } + + @Override + public long estimateMemorySize() { long size = SIZE_OF_OGC_CONCRETE_GEOMETRY_COLLECTION; if (geometries != null) { for (OGCGeometry geometry : geometries) { @@ -131,313 +130,315 @@ public String asText() { if (is3D() || isMeasured()) sb.append(' '); - int n = numGeometries(); + int n = numGeometries(); + + if (n == 0) { + sb.append("EMPTY"); + return sb.toString(); + } + + sb.append('('); + for (int i = 0; i < n; i++) { + if (i > 0) + sb.append(", "); + + sb.append(geometryN(i).asText()); + } + sb.append(')'); + + return sb.toString(); + } + + @Override + public ByteBuffer asBinary() { + + ArrayList buffers = new ArrayList(0); + + int size = 9; + int n = numGeometries(); + for (int i = 0; i < n; i++) { + ByteBuffer buffer = geometryN(i).asBinary(); + buffers.add(buffer); + size += buffer.capacity(); + } + + ByteBuffer wkbBuffer = ByteBuffer.allocate(size).order( + ByteOrder.nativeOrder()); + + byte byteOrder = (byte) (wkbBuffer.order() == ByteOrder.LITTLE_ENDIAN ? 1 + : 0); + int wkbType = 7; + + if (is3D()) + wkbType += 1000; + if (isMeasured()) + wkbType += 2000; + + wkbBuffer.put(0, byteOrder); + wkbBuffer.putInt(1, wkbType); + wkbBuffer.putInt(5, n); + + int offset = 9; + for (int i = 0; i < n; i++) { + byte[] arr = buffers.get(i).array(); + System.arraycopy(arr, 0, wkbBuffer.array(), offset, arr.length); + offset += arr.length; + } + + return wkbBuffer; + } + + @Override + public String asGeoJson() { + return asGeoJsonImpl(GeoJsonExportFlags.geoJsonExportDefaults); + } + + @Override + String asGeoJsonImpl(int export_flags) { + StringBuilder sb = new StringBuilder(); + + sb.append("{\"type\":\"GeometryCollection\",\"geometries\":"); + + sb.append("["); + for (int i = 0, n = numGeometries(); i < n; i++) { + if (i > 0) + sb.append(","); + + if (geometryN(i) != null) + sb.append(geometryN(i).asGeoJsonImpl(GeoJsonExportFlags.geoJsonExportSkipCRS)); + } + + sb.append("],\"crs\":"); + + if (esriSR != null) { + String crs_value = OperatorExportToGeoJson.local().exportSpatialReference(0, esriSR); + sb.append(crs_value); + } else { + sb.append("\"null\""); + } + + sb.append("}"); + + return sb.toString(); + } + + @Override + public boolean isEmpty() { + return numGeometries() == 0; + } + + @Override + public double MinZ() { + double z = Double.NaN; + for (int i = 0, n = numGeometries(); i < n; i++) + z = i == 0 ? geometryN(i).MinZ() : Math.min(geometryN(i).MinZ(), z); + return z; + } + + @Override + public double MaxZ() { + double z = Double.NaN; + for (int i = 0, n = numGeometries(); i < n; i++) + z = i == 0 ? geometryN(i).MaxZ() : Math.min(geometryN(i).MaxZ(), z); + return z; + } + + @Override + public double MinMeasure() { + double z = Double.NaN; + for (int i = 0, n = numGeometries(); i < n; i++) + z = i == 0 ? geometryN(i).MinMeasure() : Math.min(geometryN(i) + .MinMeasure(), z); + return z; + } + + @Override + public double MaxMeasure() { + double z = Double.NaN; + for (int i = 0, n = numGeometries(); i < n; i++) + z = i == 0 ? geometryN(i).MaxMeasure() : Math.min(geometryN(i) + .MaxMeasure(), z); + return z; + } + + @Override + public boolean isSimple() { + for (int i = 0, n = numGeometries(); i < n; i++) + if (!geometryN(i).isSimple()) + return false; + return true; + } + + /** + * makeSimpleRelaxed is not supported for the GeometryCollection instance. + */ + @Override + public OGCGeometry makeSimple() { + throw new UnsupportedOperationException(); + } + + @Override + public boolean isSimpleRelaxed() { + for (int i = 0, n = numGeometries(); i < n; i++) + if (!geometryN(i).isSimpleRelaxed()) + return false; + return true; + } + + /** + * makeSimpleRelaxed is not supported for the GeometryCollection instance. + */ + @Override + public OGCGeometry makeSimpleRelaxed(boolean forceProcessing) { + throw new UnsupportedOperationException(); + } + + @Override + public OGCGeometry boundary() { + throw new UnsupportedOperationException(); + } + + @Override + public OGCGeometry locateAlong(double mValue) { + // TODO Auto-generated method stub + throw new UnsupportedOperationException(); + } + + @Override + public OGCGeometry locateBetween(double mStart, double mEnd) { + // TODO Auto-generated method stub + throw new UnsupportedOperationException(); + } - if (n == 0) { - sb.append("EMPTY"); - return sb.toString(); - } - - sb.append('('); - for (int i = 0; i < n; i++) { - if (i > 0) - sb.append(", "); - - sb.append(geometryN(i).asText()); - } - sb.append(')'); - - return sb.toString(); - } - - @Override - public ByteBuffer asBinary() { - - ArrayList buffers = new ArrayList(0); - - int size = 9; - int n = numGeometries(); - for (int i = 0; i < n; i++) { - ByteBuffer buffer = geometryN(i).asBinary(); - buffers.add(buffer); - size += buffer.capacity(); - } - - ByteBuffer wkbBuffer = ByteBuffer.allocate(size).order( - ByteOrder.nativeOrder()); - - byte byteOrder = (byte) (wkbBuffer.order() == ByteOrder.LITTLE_ENDIAN ? 1 - : 0); - int wkbType = 7; - - if (is3D()) - wkbType += 1000; - if (isMeasured()) - wkbType += 2000; - - wkbBuffer.put(0, byteOrder); - wkbBuffer.putInt(1, wkbType); - wkbBuffer.putInt(5, n); - - int offset = 9; - for (int i = 0; i < n; i++) { - byte[] arr = buffers.get(i).array(); - System.arraycopy(arr, 0, wkbBuffer.array(), offset, arr.length); - offset += arr.length; - } - - return wkbBuffer; - } - - @Override - public String asGeoJson() { - return asGeoJsonImpl(GeoJsonExportFlags.geoJsonExportDefaults); - } - - @Override - String asGeoJsonImpl(int export_flags) { - StringBuilder sb = new StringBuilder(); - - sb.append("{\"type\":\"GeometryCollection\",\"geometries\":"); - - sb.append("["); - for (int i = 0, n = numGeometries(); i < n; i++) { - if (i > 0) - sb.append(","); - - if (geometryN(i) != null) - sb.append(geometryN(i).asGeoJsonImpl(GeoJsonExportFlags.geoJsonExportSkipCRS)); - } - - sb.append("],\"crs\":"); - - if (esriSR != null) { - String crs_value = OperatorExportToGeoJson.local().exportSpatialReference(0, esriSR); - sb.append(crs_value); - } else { - sb.append("\"null\""); - } - - sb.append("}"); - - return sb.toString(); - } - - @Override - public boolean isEmpty() { - return numGeometries() == 0; - } - - @Override - public double MinZ() { - double z = Double.NaN; - for (int i = 0, n = numGeometries(); i < n; i++) - z = i == 0 ? geometryN(i).MinZ() : Math.min(geometryN(i).MinZ(), z); - return z; - } - - @Override - public double MaxZ() { - double z = Double.NaN; - for (int i = 0, n = numGeometries(); i < n; i++) - z = i == 0 ? geometryN(i).MaxZ() : Math.min(geometryN(i).MaxZ(), z); - return z; - } - - @Override - public double MinMeasure() { - double z = Double.NaN; - for (int i = 0, n = numGeometries(); i < n; i++) - z = i == 0 ? geometryN(i).MinMeasure() : Math.min(geometryN(i) - .MinMeasure(), z); - return z; - } - - @Override - public double MaxMeasure() { - double z = Double.NaN; - for (int i = 0, n = numGeometries(); i < n; i++) - z = i == 0 ? geometryN(i).MaxMeasure() : Math.min(geometryN(i) - .MaxMeasure(), z); - return z; - } - - @Override - public boolean isSimple() { - for (int i = 0, n = numGeometries(); i < n; i++) - if (!geometryN(i).isSimple()) - return false; - return true; - } - - /** - * makeSimpleRelaxed is not supported for the GeometryCollection instance. - */ - @Override - public OGCGeometry makeSimple() { - throw new UnsupportedOperationException(); - } - - @Override - public boolean isSimpleRelaxed() { - for (int i = 0, n = numGeometries(); i < n; i++) - if (!geometryN(i).isSimpleRelaxed()) - return false; - return true; - } - - /** - * makeSimpleRelaxed is not supported for the GeometryCollection instance. - */ - @Override - public OGCGeometry makeSimpleRelaxed(boolean forceProcessing) { - throw new UnsupportedOperationException(); - } - - @Override - public OGCGeometry boundary() { - throw new UnsupportedOperationException(); - } - - @Override - public OGCGeometry locateAlong(double mValue) { - // TODO Auto-generated method stub - throw new UnsupportedOperationException(); - } - - @Override - public OGCGeometry locateBetween(double mStart, double mEnd) { - // TODO Auto-generated method stub - throw new UnsupportedOperationException(); - } - - @Override - public Geometry getEsriGeometry() { - return null; - } - - @Override - public GeometryCursor getEsriGeometryCursor() { - return new GeometryCursorOGC(geometries); - } - - @Override - protected boolean isConcreteGeometryCollection() { - return true; - } - - static class GeometryCursorOGC extends GeometryCursor { - private int m_index; - private int m_ind; - private List m_geoms; - GeometryCursor m_curs; - - GeometryCursorOGC(List geoms) { - m_geoms = geoms; - m_index = -1; - m_curs = null; - m_ind = 0; - } - - @Override - public boolean hasNext() { return m_curs != null && m_curs.hasNext(); } - - @Override - public Geometry next() { - while (true) { - if (m_curs != null) { - Geometry g = m_curs.next(); - if (g != null) { - m_index++; - return g; - } - m_curs = null; - } - if (m_ind >= m_geoms.size()) - return null; - - int i = m_ind; - m_ind++; - if (m_geoms.get(i) == null) - continue;// filter out nulls - if (!m_geoms.get(i).isConcreteGeometryCollection()) { - m_index++; - return m_geoms.get(i).getEsriGeometry(); - } else { - OGCConcreteGeometryCollection gc = (OGCConcreteGeometryCollection) m_geoms - .get(i); - m_curs = new GeometryCursorOGC(gc.geometries); - return next(); - } - } - } - - @Override - public long getGeometryID() { - return m_index; - } - - } - - List geometries; - - @Override - public void setSpatialReference(SpatialReference esriSR_) { - esriSR = esriSR_; - for (int i = 0, n = geometries.size(); i < n; i++) { - if (geometries.get(i) != null) - geometries.get(i).setSpatialReference(esriSR_); - } - } - - @Override - public OGCGeometry convertToMulti() { - return this; - } - - @Override - public String asJson() { - throw new UnsupportedOperationException(); - } - - @Override - public boolean equals(Object other) { - if (other == null) - return false; - - if (other == this) - return true; - - if (other.getClass() != getClass()) - return false; - - OGCConcreteGeometryCollection another = (OGCConcreteGeometryCollection) other; - if (geometries != null) { - if (!geometries.equals(another.geometries)) - return false; - } else if (another.geometries != null) - return false; - - if (esriSR == another.esriSR) { - return true; - } - - if (esriSR != null && another.esriSR != null) { - return esriSR.equals(another.esriSR); - } - - return false; - } - - @Override - public int hashCode() { - int hash = 1; - if (geometries != null) - hash = geometries.hashCode(); - - if (esriSR != null) - hash = NumberUtils.hashCombine(hash, esriSR.hashCode()); - - return hash; - } + @Override + public Geometry getEsriGeometry() { + return null; + } + + @Override + public GeometryCursor getEsriGeometryCursor() { + return new GeometryCursorOGC(geometries); + } + + @Override + protected boolean isConcreteGeometryCollection() { + return true; + } + + static class GeometryCursorOGC extends GeometryCursor { + private int m_index; + private int m_ind; + private List m_geoms; + GeometryCursor m_curs; + + GeometryCursorOGC(List geoms) { + m_geoms = geoms; + m_index = -1; + m_curs = null; + m_ind = 0; + } + + @Override + public boolean hasNext() { + return m_curs != null && m_curs.hasNext(); + } + + @Override + public Geometry next() { + while (true) { + if (m_curs != null) { + Geometry g = m_curs.next(); + if (g != null) { + m_index++; + return g; + } + m_curs = null; + } + if (m_ind >= m_geoms.size()) + return null; + + int i = m_ind; + m_ind++; + if (m_geoms.get(i) == null) + continue;// filter out nulls + if (!m_geoms.get(i).isConcreteGeometryCollection()) { + m_index++; + return m_geoms.get(i).getEsriGeometry(); + } else { + OGCConcreteGeometryCollection gc = (OGCConcreteGeometryCollection) m_geoms + .get(i); + m_curs = new GeometryCursorOGC(gc.geometries); + return next(); + } + } + } + + @Override + public long getGeometryID() { + return m_index; + } + + } + + List geometries; + + @Override + public void setSpatialReference(SpatialReference esriSR_) { + esriSR = esriSR_; + for (int i = 0, n = geometries.size(); i < n; i++) { + if (geometries.get(i) != null) + geometries.get(i).setSpatialReference(esriSR_); + } + } + + @Override + public OGCGeometry convertToMulti() { + return this; + } + + @Override + public String asJson() { + throw new UnsupportedOperationException(); + } + + @Override + public boolean equals(Object other) { + if (other == null) + return false; + + if (other == this) + return true; + + if (other.getClass() != getClass()) + return false; + + OGCConcreteGeometryCollection another = (OGCConcreteGeometryCollection) other; + if (geometries != null) { + if (!geometries.equals(another.geometries)) + return false; + } else if (another.geometries != null) + return false; + + if (esriSR == another.esriSR) { + return true; + } + + if (esriSR != null && another.esriSR != null) { + return esriSR.equals(another.esriSR); + } + + return false; + } + + @Override + public int hashCode() { + int hash = 1; + if (geometries != null) + hash = geometries.hashCode(); + + if (esriSR != null) + hash = NumberUtils.hashCombine(hash, esriSR.hashCode()); + + return hash; + } } diff --git a/src/main/java/com/esri/core/geometry/ogc/OGCCurve.java b/src/main/java/com/esri/core/geometry/ogc/OGCCurve.java index 9f421294..0755dc2e 100644 --- a/src/main/java/com/esri/core/geometry/ogc/OGCCurve.java +++ b/src/main/java/com/esri/core/geometry/ogc/OGCCurve.java @@ -27,24 +27,24 @@ import com.esri.core.geometry.MultiPoint; public abstract class OGCCurve extends OGCGeometry { - public abstract double length(); + public abstract double length(); - public abstract OGCPoint startPoint(); + public abstract OGCPoint startPoint(); - public abstract OGCPoint endPoint(); + public abstract OGCPoint endPoint(); - public abstract boolean isClosed(); + public abstract boolean isClosed(); - public boolean isRing() { - return isSimple() && isClosed(); - } + public boolean isRing() { + return isSimple() && isClosed(); + } - @Override - public OGCGeometry boundary() { - if (isClosed()) - return new OGCMultiPoint(new MultiPoint(getEsriGeometry() - .getDescription()), esriSR);// return empty multipoint; - else - return new OGCMultiPoint(startPoint(), endPoint()); - } + @Override + public OGCGeometry boundary() { + if (isClosed()) + return new OGCMultiPoint(new MultiPoint(getEsriGeometry() + .getDescription()), esriSR);// return empty multipoint; + else + return new OGCMultiPoint(startPoint(), endPoint()); + } } diff --git a/src/main/java/com/esri/core/geometry/ogc/OGCGeometry.java b/src/main/java/com/esri/core/geometry/ogc/OGCGeometry.java index d6ae9d23..56dae766 100644 --- a/src/main/java/com/esri/core/geometry/ogc/OGCGeometry.java +++ b/src/main/java/com/esri/core/geometry/ogc/OGCGeometry.java @@ -66,23 +66,23 @@ * OGC Simple Feature Access specification v.1.2.1 */ public abstract class OGCGeometry { - public int dimension() { - return getEsriGeometry().getDimension(); - } + public int dimension() { + return getEsriGeometry().getDimension(); + } - public int coordinateDimension() { - int d = 2; - if (getEsriGeometry().getDescription().hasAttribute( - VertexDescription.Semantics.M)) - d++; - if (getEsriGeometry().getDescription().hasAttribute( - VertexDescription.Semantics.Z)) - d++; + public int coordinateDimension() { + int d = 2; + if (getEsriGeometry().getDescription().hasAttribute( + VertexDescription.Semantics.M)) + d++; + if (getEsriGeometry().getDescription().hasAttribute( + VertexDescription.Semantics.Z)) + d++; - return d; - } + return d; + } - abstract public String geometryType(); + abstract public String geometryType(); /** * Returns an estimate of this object size in bytes. @@ -99,667 +99,667 @@ public int SRID() { if (esriSR == null) return 0; - return esriSR.getID(); - } - - public OGCGeometry envelope() { - com.esri.core.geometry.Envelope env = new com.esri.core.geometry.Envelope(); - getEsriGeometry().queryEnvelope(env); - com.esri.core.geometry.Polygon polygon = new com.esri.core.geometry.Polygon(); - polygon.addEnvelope(env, false); - return new OGCPolygon(polygon, esriSR); - } - - public String asText() { - return GeometryEngine.geometryToWkt(getEsriGeometry(), 0); - } - - public ByteBuffer asBinary() { - OperatorExportToWkb op = (OperatorExportToWkb) OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.ExportToWkb); - return op.execute(0, getEsriGeometry(), null); - } - - public String asGeoJson() { - OperatorExportToGeoJson op = (OperatorExportToGeoJson) OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.ExportToGeoJson); - return op.execute(esriSR, getEsriGeometry()); - } - - String asGeoJsonImpl(int export_flags) { - OperatorExportToGeoJson op = (OperatorExportToGeoJson) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.ExportToGeoJson); - return op.execute(export_flags, esriSR, getEsriGeometry()); - } - - /** - * @return Convert to REST JSON. - */ - public String asJson() { - return GeometryEngine.geometryToJson(esriSR, getEsriGeometry()); - } - - - public boolean isEmpty() { - return getEsriGeometry().isEmpty(); - } - - public double MinZ() { - Envelope1D e = getEsriGeometry().queryInterval( - VertexDescription.Semantics.Z, 0); - return e.vmin; - } - - public double MaxZ() { - Envelope1D e = getEsriGeometry().queryInterval( - VertexDescription.Semantics.Z, 0); - return e.vmax; - } - - public double MinMeasure() { - Envelope1D e = getEsriGeometry().queryInterval( - VertexDescription.Semantics.M, 0); - return e.vmin; - } - - public double MaxMeasure() { - Envelope1D e = getEsriGeometry().queryInterval( - VertexDescription.Semantics.M, 0); - return e.vmax; - } - - /** - * Returns true if this geometric object has no anomalous geometric points, - * such as self intersection or self tangency. See the - * "Simple feature access - Part 1" document (OGC 06-103r4) for meaning of - * "simple" for each geometry type. - *

- * The method has O(n log n) complexity when the input geometry is simple. - * For non-simple geometries, it terminates immediately when the first issue - * is encountered. - * - * @return True if geometry is simple and false otherwise. - *

- * Note: If isSimple is true, then isSimpleRelaxed is true too. - */ - public boolean isSimple() { - return OperatorSimplifyOGC.local().isSimpleOGC(getEsriGeometry(), - esriSR, true, null, null); - } - - /** - * Extension method - checks if geometry is simple for Geodatabase. - * - * @return Returns true if geometry is simple, false otherwise. - *

- * Note: If isSimpleRelaxed is true, then isSimple is either true or false. Geodatabase has more relaxed requirements for simple geometries than OGC. - */ - public boolean isSimpleRelaxed() { - OperatorSimplify op = (OperatorSimplify) OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.Simplify); - return op.isSimpleAsFeature(getEsriGeometry(), esriSR, true, null, null); - } - - @Deprecated - /** - * Use makeSimpleRelaxed instead. - */ - public OGCGeometry MakeSimpleRelaxed(boolean forceProcessing) { - return makeSimpleRelaxed(forceProcessing); - } - - /** - * Makes a simple geometry for Geodatabase. - * - * @return Returns simplified geometry. - *

- * Note: isSimpleRelaxed should return true after this operation. - */ - public OGCGeometry makeSimpleRelaxed(boolean forceProcessing) { - OperatorSimplify op = (OperatorSimplify) OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.Simplify); - return OGCGeometry.createFromEsriGeometry( - op.execute(getEsriGeometry(), esriSR, forceProcessing, null), - esriSR); - } - - /** - * Resolves topological issues in this geometry and makes it Simple according to OGC specification. - * - * @return Returns simplified geometry. - *

- * Note: isSimple and isSimpleRelaxed should return true after this operation. - */ - public OGCGeometry makeSimple() { - return simplifyBunch_(getEsriGeometryCursor()); - } - - public boolean is3D() { - return getEsriGeometry().getDescription().hasAttribute( - VertexDescription.Semantics.Z); - } - - public boolean isMeasured() { - return getEsriGeometry().getDescription().hasAttribute( - VertexDescription.Semantics.M); - } - - abstract public OGCGeometry boundary(); - - /** - * OGC equals. Performs topological comparison with tolerance. - * This is different from equals(Object), that uses exact comparison. - */ - public boolean Equals(OGCGeometry another) { - if (this == another) - return true; - - if (another == null) - return false; - - com.esri.core.geometry.Geometry geom1 = getEsriGeometry(); - com.esri.core.geometry.Geometry geom2 = another.getEsriGeometry(); - return com.esri.core.geometry.GeometryEngine.equals(geom1, geom2, - getEsriSpatialReference()); - } - - @Deprecated - public boolean equals(OGCGeometry another) { - return Equals(another); - } - - public boolean disjoint(OGCGeometry another) { - com.esri.core.geometry.Geometry geom1 = getEsriGeometry(); - com.esri.core.geometry.Geometry geom2 = another.getEsriGeometry(); - return com.esri.core.geometry.GeometryEngine.disjoint(geom1, geom2, - getEsriSpatialReference()); - } - - public boolean intersects(OGCGeometry another) { - return !disjoint(another); - } - - public boolean touches(OGCGeometry another) { - com.esri.core.geometry.Geometry geom1 = getEsriGeometry(); - com.esri.core.geometry.Geometry geom2 = another.getEsriGeometry(); - return com.esri.core.geometry.GeometryEngine.touches(geom1, geom2, - getEsriSpatialReference()); - } - - public boolean crosses(OGCGeometry another) { - com.esri.core.geometry.Geometry geom1 = getEsriGeometry(); - com.esri.core.geometry.Geometry geom2 = another.getEsriGeometry(); - return com.esri.core.geometry.GeometryEngine.crosses(geom1, geom2, - getEsriSpatialReference()); - } - - public boolean within(OGCGeometry another) { - com.esri.core.geometry.Geometry geom1 = getEsriGeometry(); - com.esri.core.geometry.Geometry geom2 = another.getEsriGeometry(); - return com.esri.core.geometry.GeometryEngine.within(geom1, geom2, - getEsriSpatialReference()); - } - - public boolean contains(OGCGeometry another) { - com.esri.core.geometry.Geometry geom1 = getEsriGeometry(); - com.esri.core.geometry.Geometry geom2 = another.getEsriGeometry(); - return com.esri.core.geometry.GeometryEngine.contains(geom1, geom2, - getEsriSpatialReference()); - } - - public boolean overlaps(OGCGeometry another) { - com.esri.core.geometry.Geometry geom1 = getEsriGeometry(); - com.esri.core.geometry.Geometry geom2 = another.getEsriGeometry(); - return com.esri.core.geometry.GeometryEngine.overlaps(geom1, geom2, - getEsriSpatialReference()); - } - - public boolean relate(OGCGeometry another, String matrix) { - com.esri.core.geometry.Geometry geom1 = getEsriGeometry(); - com.esri.core.geometry.Geometry geom2 = another.getEsriGeometry(); - return com.esri.core.geometry.GeometryEngine.relate(geom1, geom2, - getEsriSpatialReference(), matrix); - } - - abstract public OGCGeometry locateAlong(double mValue); - - abstract public OGCGeometry locateBetween(double mStart, double mEnd); - - // analysis - public double distance(OGCGeometry another) { - com.esri.core.geometry.Geometry geom1 = getEsriGeometry(); - com.esri.core.geometry.Geometry geom2 = another.getEsriGeometry(); - return com.esri.core.geometry.GeometryEngine.distance(geom1, geom2, - getEsriSpatialReference()); - } - - // This method firstly groups geometries by dimension (points, lines, - // areas), - // then simplifies each group such that each group is reduced to a single - // geometry. - // As a result there are at most three geometries, each geometry is Simple. - // Afterwards - // it produces a single OGCGeometry. - private OGCGeometry simplifyBunch_(GeometryCursor gc) { - // Combines geometries into multipoint, polyline, and polygon types, - // simplifying them and unioning them, - // then produces OGCGeometry from the result. - // Can produce OGCConcreteGoemetryCollection - MultiPoint dstMultiPoint = null; - ArrayDeque dstPolylines = new ArrayDeque<>(); - ArrayDeque dstPolygons = new ArrayDeque<>(); - for (com.esri.core.geometry.Geometry g = gc.next(); g != null; g = gc - .next()) { - switch (g.getType()) { - case Point: - if (dstMultiPoint == null) - dstMultiPoint = new MultiPoint(); - dstMultiPoint.add((Point) g); - break; - case MultiPoint: - if (dstMultiPoint == null) - dstMultiPoint = new MultiPoint(); - dstMultiPoint.add((MultiPoint) g, 0, -1); - break; - case Polyline: - dstPolylines.add((Polyline) g.copy()); - break; - case Polygon: - dstPolygons.add((Polygon) g.copy()); - break; - default: - throw new UnsupportedOperationException(); - } - } - - ArrayDeque result = new ArrayDeque<>(3); - if (dstMultiPoint != null) { - Geometry resMP = OperatorSimplifyOGC.local().execute(dstMultiPoint, - esriSR, true, null); - result.add(resMP); - } - - if (dstPolylines.size() > 0) { - if (dstPolylines.size() == 1) { - Geometry resMP = OperatorSimplifyOGC.local().execute(dstPolylines.pop(), esriSR, true, null); - result.add(resMP); - } else { - GeometryCursor res = OperatorUnion.local().execute(new SimpleGeometryCursor(dstPolylines), esriSR, null); - Geometry resPolyline = res.next(); - Geometry resMP = OperatorSimplifyOGC.local().execute( - resPolyline, esriSR, true, null); - result.add(resMP); - } - } - - if (dstPolygons.size() > 0) { - if (dstPolygons.size() == 1) { - Geometry resMP = OperatorSimplifyOGC.local().execute(dstPolygons.pop(), esriSR, true, null); - result.add(resMP); - } else { - GeometryCursor res = OperatorUnion.local().execute(new SimpleGeometryCursor(dstPolygons), esriSR, null); - Geometry resPolygon = res.next(); - Geometry resMP = OperatorSimplifyOGC.local().execute( - resPolygon, esriSR, true, null); - result.add(resMP); - } - } - - return OGCGeometry.createFromEsriCursor(new SimpleGeometryCursor(result), esriSR); - } - - public OGCGeometry buffer(double distance) { - OperatorBuffer op = (OperatorBuffer) OperatorFactoryLocal.getInstance() - .getOperator(Operator.Type.Buffer); - if (distance == 0) {// when distance is 0, return self (maybe we should - // create a copy instead). - return this; - } - - double d[] = {distance}; - com.esri.core.geometry.GeometryCursor cursor = op.execute( - getEsriGeometryCursor(), getEsriSpatialReference(), d, true, - null); - return OGCGeometry.createFromEsriGeometry(cursor.next(), esriSR); - } - - public OGCGeometry convexHull() { - com.esri.core.geometry.OperatorConvexHull op = (OperatorConvexHull) OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.ConvexHull); - com.esri.core.geometry.GeometryCursor cursor = op.execute( - getEsriGeometryCursor(), true, null); - return OGCGeometry.createFromEsriCursor(cursor, esriSR); - } - - public OGCGeometry intersection(OGCGeometry another) { - com.esri.core.geometry.OperatorIntersection op = (OperatorIntersection) OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.Intersection); - com.esri.core.geometry.GeometryCursor cursor = op.execute( - getEsriGeometryCursor(), another.getEsriGeometryCursor(), - getEsriSpatialReference(), null, 7); - return OGCGeometry.createFromEsriCursor(cursor, esriSR, true); - } - - public OGCGeometry union(OGCGeometry another) { - OperatorUnion op = (OperatorUnion) OperatorFactoryLocal.getInstance() - .getOperator(Operator.Type.Union); - GeometryCursorAppend ap = new GeometryCursorAppend( - getEsriGeometryCursor(), another.getEsriGeometryCursor()); - com.esri.core.geometry.GeometryCursor cursor = op.execute(ap, - getEsriSpatialReference(), null); - return OGCGeometry.createFromEsriCursor(cursor, esriSR); - } - - public OGCGeometry difference(OGCGeometry another) { - com.esri.core.geometry.Geometry geom1 = getEsriGeometry(); - com.esri.core.geometry.Geometry geom2 = another.getEsriGeometry(); - return createFromEsriGeometry( - com.esri.core.geometry.GeometryEngine.difference(geom1, geom2, - getEsriSpatialReference()), esriSR); - } - - public OGCGeometry symDifference(OGCGeometry another) { - com.esri.core.geometry.Geometry geom1 = getEsriGeometry(); - com.esri.core.geometry.Geometry geom2 = another.getEsriGeometry(); - return createFromEsriGeometry( - com.esri.core.geometry.GeometryEngine.symmetricDifference( - geom1, geom2, getEsriSpatialReference()), esriSR); - } - - public abstract com.esri.core.geometry.Geometry getEsriGeometry(); - - public GeometryCursor getEsriGeometryCursor() { - return new SimpleGeometryCursor(getEsriGeometry()); - } - - public com.esri.core.geometry.SpatialReference getEsriSpatialReference() { - return esriSR; - } - - /** - * Create an OGCGeometry instance from the GeometryCursor. - * - * @param gc - * @param sr - * @return Geometry instance created from the geometry cursor. - */ - public static OGCGeometry createFromEsriCursor(GeometryCursor gc, - SpatialReference sr) { - return createFromEsriCursor(gc, sr, false); - } - - public static OGCGeometry createFromEsriCursor(GeometryCursor gc, - SpatialReference sr, boolean skipEmpty) { - ArrayList geoms = new ArrayList(10); - Geometry emptyGeom = null; - for (Geometry g = gc.next(); g != null; g = gc.next()) { - emptyGeom = g; - if (!skipEmpty || !g.isEmpty()) - geoms.add(createFromEsriGeometry(g, sr)); - } - - if (geoms.size() == 1) { - return geoms.get(0); - } else if (geoms.size() == 0) - return createFromEsriGeometry(emptyGeom, sr); - else - return new OGCConcreteGeometryCollection(geoms, sr); - } - - public static OGCGeometry fromText(String text) { - OperatorImportFromWkt op = (OperatorImportFromWkt) OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.ImportFromWkt); - OGCStructure ogcStructure = op.executeOGC(0, text, null); - return OGCGeometry.createFromOGCStructure(ogcStructure, - SpatialReference.create(4326)); - } - - public static OGCGeometry fromBinary(ByteBuffer binary) { - OperatorImportFromWkb op = (OperatorImportFromWkb) OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.ImportFromWkb); - OGCStructure ogcStructure = op.executeOGC(0, binary, null); - return OGCGeometry.createFromOGCStructure(ogcStructure, - SpatialReference.create(4326)); - } - - public static OGCGeometry fromEsriShape(ByteBuffer buffer) { - OperatorImportFromESRIShape op = (OperatorImportFromESRIShape) OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.ImportFromESRIShape); - Geometry g = op.execute(0, Geometry.Type.Unknown, buffer); - return OGCGeometry.createFromEsriGeometry(g, - SpatialReference.create(4326)); - } - - public static OGCGeometry fromJson(String string) { - MapGeometry mapGeom = GeometryEngine.jsonToGeometry(JsonParserReader.createFromString(string)); - return OGCGeometry.createFromEsriGeometry(mapGeom.getGeometry(), - mapGeom.getSpatialReference()); - } - - public static OGCGeometry fromGeoJson(String string) { - OperatorImportFromGeoJson op = (OperatorImportFromGeoJson) OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.ImportFromGeoJson); - MapOGCStructure mapOGCStructure = op.executeOGC(0, string, null); - return OGCGeometry.createFromOGCStructure( - mapOGCStructure.m_ogcStructure, - mapOGCStructure.m_spatialReference); - } - - public static OGCGeometry createFromEsriGeometry(Geometry geom, - SpatialReference sr) { - return createFromEsriGeometry(geom, sr, false); - } - - public static OGCGeometry createFromEsriGeometry(Geometry geom, - SpatialReference sr, boolean multiType) { - if (geom == null) - return null; - Geometry.Type t = geom.getType(); - if (t == Geometry.Type.Polygon) { - if (!multiType && ((Polygon) geom).getExteriorRingCount() == 1) - return new OGCPolygon((Polygon) geom, sr); - else - return new OGCMultiPolygon((Polygon) geom, sr); - } - if (t == Geometry.Type.Polyline) { - if (!multiType && ((Polyline) geom).getPathCount() == 1) - return new OGCLineString((Polyline) geom, 0, sr); - else - return new OGCMultiLineString((Polyline) geom, sr); - } - if (t == Geometry.Type.MultiPoint) { - if (!multiType && ((MultiPoint) geom).getPointCount() <= 1) { - if (geom.isEmpty()) - return new OGCPoint(new Point(), sr); - else - return new OGCPoint(((MultiPoint) geom).getPoint(0), sr); - } else - return new OGCMultiPoint((MultiPoint) geom, sr); - } - if (t == Geometry.Type.Point) { - if (!multiType) { - return new OGCPoint((Point) geom, sr); - } else { - return new OGCMultiPoint((Point) geom, sr); - } - } - if (t == Geometry.Type.Envelope) { - Polygon p = new Polygon(); - p.addEnvelope((Envelope) geom, false); - return createFromEsriGeometry(p, sr, multiType); - } - - throw new UnsupportedOperationException(); - } - - public static OGCGeometry createFromOGCStructure(OGCStructure ogcStructure, - SpatialReference sr) { - ArrayList collectionStack = new ArrayList( - 0); - ArrayList structureStack = new ArrayList(0); - ArrayList indices = new ArrayList(0); - - OGCGeometry[] geometries = new OGCGeometry[1]; - OGCConcreteGeometryCollection root = new OGCConcreteGeometryCollection( - Arrays.asList(geometries), sr); - - structureStack.add(ogcStructure); - collectionStack.add(root); - indices.add(0); - - while (!structureStack.isEmpty()) { - OGCStructure lastStructure = structureStack.get(structureStack - .size() - 1); - if (indices.get(indices.size() - 1) == lastStructure.m_structures - .size()) { - structureStack.remove(structureStack.size() - 1); - collectionStack.remove(collectionStack.size() - 1); - indices.remove(indices.size() - 1); - continue; - } - - OGCConcreteGeometryCollection lastCollection = collectionStack - .get(collectionStack.size() - 1); - OGCGeometry g; - int i = indices.get(indices.size() - 1); - - int type = lastStructure.m_structures.get(i).m_type; - - switch (type) { - case 1: - g = new OGCPoint( - (Point) lastStructure.m_structures.get(i).m_geometry, - sr); - lastCollection.geometries.set(i, g); - indices.set(indices.size() - 1, i + 1); - break; - case 2: - g = new OGCLineString( - (Polyline) lastStructure.m_structures.get(i).m_geometry, - 0, sr); - lastCollection.geometries.set(i, g); - indices.set(indices.size() - 1, i + 1); - break; - case 3: - g = new OGCPolygon( - (Polygon) lastStructure.m_structures.get(i).m_geometry, - 0, sr); - lastCollection.geometries.set(i, g); - indices.set(indices.size() - 1, i + 1); - break; - case 4: - g = new OGCMultiPoint( - (MultiPoint) lastStructure.m_structures.get(i).m_geometry, - sr); - lastCollection.geometries.set(i, g); - indices.set(indices.size() - 1, i + 1); - break; - case 5: - g = new OGCMultiLineString( - (Polyline) lastStructure.m_structures.get(i).m_geometry, - sr); - lastCollection.geometries.set(i, g); - indices.set(indices.size() - 1, i + 1); - break; - case 6: - g = new OGCMultiPolygon( - (Polygon) lastStructure.m_structures.get(i).m_geometry, - sr); - lastCollection.geometries.set(i, g); - indices.set(indices.size() - 1, i + 1); - break; - case 7: - geometries = new OGCGeometry[lastStructure.m_structures.get(i).m_structures - .size()]; - g = new OGCConcreteGeometryCollection( - Arrays.asList(geometries), sr); - lastCollection.geometries.set(i, g); - indices.set(indices.size() - 1, i + 1); - structureStack.add(lastStructure.m_structures.get(i)); - collectionStack.add((OGCConcreteGeometryCollection) g); - indices.add(0); - break; - default: - throw new UnsupportedOperationException(); - } - } - - return root.geometries.get(0); - } - - protected boolean isConcreteGeometryCollection() { - return false; - } - - /** - * SpatialReference of the Geometry. - */ - public com.esri.core.geometry.SpatialReference esriSR; - - public void setSpatialReference(SpatialReference esriSR_) { - esriSR = esriSR_; - } - - /** - * Converts this Geometry to the OGCMulti* if it is not OGCMulti* or - * OGCGeometryCollection already. - * - * @return OGCMulti* or OGCGeometryCollection instance. - */ - public abstract OGCGeometry convertToMulti(); - - @Override - public String toString() { - String snippet = asText(); - if (snippet.length() > 200) { - snippet = snippet.substring(0, 197) + "..."; - } - return String - .format("%s: %s", this.getClass().getSimpleName(), snippet); - } - - @Override - public boolean equals(Object other) { - if (other == null) - return false; - - if (other == this) - return true; - - if (other.getClass() != getClass()) - return false; - - OGCGeometry another = (OGCGeometry) other; - com.esri.core.geometry.Geometry geom1 = getEsriGeometry(); - com.esri.core.geometry.Geometry geom2 = another.getEsriGeometry(); - - if (geom1 == null) { - if (geom2 != null) - return false; - } else if (!geom1.equals(geom2)) { - return false; - } - - if (esriSR == another.esriSR) { - return true; - } - - if (esriSR != null && another.esriSR != null) { - return esriSR.equals(another.esriSR); - } - - return false; - } - - @Override - public int hashCode() { - int hash = 1; - com.esri.core.geometry.Geometry geom1 = getEsriGeometry(); - if (geom1 != null) - hash = geom1.hashCode(); - - if (esriSR != null) - hash = NumberUtils.hashCombine(hash, esriSR.hashCode()); - - return hash; - } + return esriSR.getID(); + } + + public OGCGeometry envelope() { + com.esri.core.geometry.Envelope env = new com.esri.core.geometry.Envelope(); + getEsriGeometry().queryEnvelope(env); + com.esri.core.geometry.Polygon polygon = new com.esri.core.geometry.Polygon(); + polygon.addEnvelope(env, false); + return new OGCPolygon(polygon, esriSR); + } + + public String asText() { + return GeometryEngine.geometryToWkt(getEsriGeometry(), 0); + } + + public ByteBuffer asBinary() { + OperatorExportToWkb op = (OperatorExportToWkb) OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.ExportToWkb); + return op.execute(0, getEsriGeometry(), null); + } + + public String asGeoJson() { + OperatorExportToGeoJson op = (OperatorExportToGeoJson) OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.ExportToGeoJson); + return op.execute(esriSR, getEsriGeometry()); + } + + String asGeoJsonImpl(int export_flags) { + OperatorExportToGeoJson op = (OperatorExportToGeoJson) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.ExportToGeoJson); + return op.execute(export_flags, esriSR, getEsriGeometry()); + } + + /** + * @return Convert to REST JSON. + */ + public String asJson() { + return GeometryEngine.geometryToJson(esriSR, getEsriGeometry()); + } + + + public boolean isEmpty() { + return getEsriGeometry().isEmpty(); + } + + public double MinZ() { + Envelope1D e = getEsriGeometry().queryInterval( + VertexDescription.Semantics.Z, 0); + return e.vmin; + } + + public double MaxZ() { + Envelope1D e = getEsriGeometry().queryInterval( + VertexDescription.Semantics.Z, 0); + return e.vmax; + } + + public double MinMeasure() { + Envelope1D e = getEsriGeometry().queryInterval( + VertexDescription.Semantics.M, 0); + return e.vmin; + } + + public double MaxMeasure() { + Envelope1D e = getEsriGeometry().queryInterval( + VertexDescription.Semantics.M, 0); + return e.vmax; + } + + /** + * Returns true if this geometric object has no anomalous geometric points, + * such as self intersection or self tangency. See the + * "Simple feature access - Part 1" document (OGC 06-103r4) for meaning of + * "simple" for each geometry type. + *

+ * The method has O(n log n) complexity when the input geometry is simple. + * For non-simple geometries, it terminates immediately when the first issue + * is encountered. + * + * @return True if geometry is simple and false otherwise. + *

+ * Note: If isSimple is true, then isSimpleRelaxed is true too. + */ + public boolean isSimple() { + return OperatorSimplifyOGC.local().isSimpleOGC(getEsriGeometry(), + esriSR, true, null, null); + } + + /** + * Extension method - checks if geometry is simple for Geodatabase. + * + * @return Returns true if geometry is simple, false otherwise. + *

+ * Note: If isSimpleRelaxed is true, then isSimple is either true or false. Geodatabase has more relaxed requirements for simple geometries than OGC. + */ + public boolean isSimpleRelaxed() { + OperatorSimplify op = (OperatorSimplify) OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Simplify); + return op.isSimpleAsFeature(getEsriGeometry(), esriSR, true, null, null); + } + + @Deprecated + /** + * Use makeSimpleRelaxed instead. + */ + public OGCGeometry MakeSimpleRelaxed(boolean forceProcessing) { + return makeSimpleRelaxed(forceProcessing); + } + + /** + * Makes a simple geometry for Geodatabase. + * + * @return Returns simplified geometry. + *

+ * Note: isSimpleRelaxed should return true after this operation. + */ + public OGCGeometry makeSimpleRelaxed(boolean forceProcessing) { + OperatorSimplify op = (OperatorSimplify) OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Simplify); + return OGCGeometry.createFromEsriGeometry( + op.execute(getEsriGeometry(), esriSR, forceProcessing, null), + esriSR); + } + + /** + * Resolves topological issues in this geometry and makes it Simple according to OGC specification. + * + * @return Returns simplified geometry. + *

+ * Note: isSimple and isSimpleRelaxed should return true after this operation. + */ + public OGCGeometry makeSimple() { + return simplifyBunch_(getEsriGeometryCursor()); + } + + public boolean is3D() { + return getEsriGeometry().getDescription().hasAttribute( + VertexDescription.Semantics.Z); + } + + public boolean isMeasured() { + return getEsriGeometry().getDescription().hasAttribute( + VertexDescription.Semantics.M); + } + + abstract public OGCGeometry boundary(); + + /** + * OGC equals. Performs topological comparison with tolerance. + * This is different from equals(Object), that uses exact comparison. + */ + public boolean Equals(OGCGeometry another) { + if (this == another) + return true; + + if (another == null) + return false; + + com.esri.core.geometry.Geometry geom1 = getEsriGeometry(); + com.esri.core.geometry.Geometry geom2 = another.getEsriGeometry(); + return com.esri.core.geometry.GeometryEngine.equals(geom1, geom2, + getEsriSpatialReference()); + } + + @Deprecated + public boolean equals(OGCGeometry another) { + return Equals(another); + } + + public boolean disjoint(OGCGeometry another) { + com.esri.core.geometry.Geometry geom1 = getEsriGeometry(); + com.esri.core.geometry.Geometry geom2 = another.getEsriGeometry(); + return com.esri.core.geometry.GeometryEngine.disjoint(geom1, geom2, + getEsriSpatialReference()); + } + + public boolean intersects(OGCGeometry another) { + return !disjoint(another); + } + + public boolean touches(OGCGeometry another) { + com.esri.core.geometry.Geometry geom1 = getEsriGeometry(); + com.esri.core.geometry.Geometry geom2 = another.getEsriGeometry(); + return com.esri.core.geometry.GeometryEngine.touches(geom1, geom2, + getEsriSpatialReference()); + } + + public boolean crosses(OGCGeometry another) { + com.esri.core.geometry.Geometry geom1 = getEsriGeometry(); + com.esri.core.geometry.Geometry geom2 = another.getEsriGeometry(); + return com.esri.core.geometry.GeometryEngine.crosses(geom1, geom2, + getEsriSpatialReference()); + } + + public boolean within(OGCGeometry another) { + com.esri.core.geometry.Geometry geom1 = getEsriGeometry(); + com.esri.core.geometry.Geometry geom2 = another.getEsriGeometry(); + return com.esri.core.geometry.GeometryEngine.within(geom1, geom2, + getEsriSpatialReference()); + } + + public boolean contains(OGCGeometry another) { + com.esri.core.geometry.Geometry geom1 = getEsriGeometry(); + com.esri.core.geometry.Geometry geom2 = another.getEsriGeometry(); + return com.esri.core.geometry.GeometryEngine.contains(geom1, geom2, + getEsriSpatialReference()); + } + + public boolean overlaps(OGCGeometry another) { + com.esri.core.geometry.Geometry geom1 = getEsriGeometry(); + com.esri.core.geometry.Geometry geom2 = another.getEsriGeometry(); + return com.esri.core.geometry.GeometryEngine.overlaps(geom1, geom2, + getEsriSpatialReference()); + } + + public boolean relate(OGCGeometry another, String matrix) { + com.esri.core.geometry.Geometry geom1 = getEsriGeometry(); + com.esri.core.geometry.Geometry geom2 = another.getEsriGeometry(); + return com.esri.core.geometry.GeometryEngine.relate(geom1, geom2, + getEsriSpatialReference(), matrix); + } + + abstract public OGCGeometry locateAlong(double mValue); + + abstract public OGCGeometry locateBetween(double mStart, double mEnd); + + // analysis + public double distance(OGCGeometry another) { + com.esri.core.geometry.Geometry geom1 = getEsriGeometry(); + com.esri.core.geometry.Geometry geom2 = another.getEsriGeometry(); + return com.esri.core.geometry.GeometryEngine.distance(geom1, geom2, + getEsriSpatialReference()); + } + + // This method firstly groups geometries by dimension (points, lines, + // areas), + // then simplifies each group such that each group is reduced to a single + // geometry. + // As a result there are at most three geometries, each geometry is Simple. + // Afterwards + // it produces a single OGCGeometry. + private OGCGeometry simplifyBunch_(GeometryCursor gc) { + // Combines geometries into multipoint, polyline, and polygon types, + // simplifying them and unioning them, + // then produces OGCGeometry from the result. + // Can produce OGCConcreteGoemetryCollection + MultiPoint dstMultiPoint = null; + ArrayDeque dstPolylines = new ArrayDeque<>(); + ArrayDeque dstPolygons = new ArrayDeque<>(); + for (com.esri.core.geometry.Geometry g = gc.next(); g != null; g = gc + .next()) { + switch (g.getType()) { + case Point: + if (dstMultiPoint == null) + dstMultiPoint = new MultiPoint(); + dstMultiPoint.add((Point) g); + break; + case MultiPoint: + if (dstMultiPoint == null) + dstMultiPoint = new MultiPoint(); + dstMultiPoint.add((MultiPoint) g, 0, -1); + break; + case Polyline: + dstPolylines.add((Polyline) g.copy()); + break; + case Polygon: + dstPolygons.add((Polygon) g.copy()); + break; + default: + throw new UnsupportedOperationException(); + } + } + + ArrayDeque result = new ArrayDeque<>(3); + if (dstMultiPoint != null) { + Geometry resMP = OperatorSimplifyOGC.local().execute(dstMultiPoint, + esriSR, true, null); + result.add(resMP); + } + + if (dstPolylines.size() > 0) { + if (dstPolylines.size() == 1) { + Geometry resMP = OperatorSimplifyOGC.local().execute(dstPolylines.pop(), esriSR, true, null); + result.add(resMP); + } else { + GeometryCursor res = OperatorUnion.local().execute(new SimpleGeometryCursor(dstPolylines), esriSR, null); + Geometry resPolyline = res.next(); + Geometry resMP = OperatorSimplifyOGC.local().execute( + resPolyline, esriSR, true, null); + result.add(resMP); + } + } + + if (dstPolygons.size() > 0) { + if (dstPolygons.size() == 1) { + Geometry resMP = OperatorSimplifyOGC.local().execute(dstPolygons.pop(), esriSR, true, null); + result.add(resMP); + } else { + GeometryCursor res = OperatorUnion.local().execute(new SimpleGeometryCursor(dstPolygons), esriSR, null); + Geometry resPolygon = res.next(); + Geometry resMP = OperatorSimplifyOGC.local().execute( + resPolygon, esriSR, true, null); + result.add(resMP); + } + } + + return OGCGeometry.createFromEsriCursor(new SimpleGeometryCursor(result), esriSR); + } + + public OGCGeometry buffer(double distance) { + OperatorBuffer op = (OperatorBuffer) OperatorFactoryLocal.getInstance() + .getOperator(Operator.Type.Buffer); + if (distance == 0) {// when distance is 0, return self (maybe we should + // create a copy instead). + return this; + } + + double d[] = {distance}; + com.esri.core.geometry.GeometryCursor cursor = op.execute( + getEsriGeometryCursor(), getEsriSpatialReference(), d, true, + null); + return OGCGeometry.createFromEsriGeometry(cursor.next(), esriSR); + } + + public OGCGeometry convexHull() { + com.esri.core.geometry.OperatorConvexHull op = (OperatorConvexHull) OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.ConvexHull); + com.esri.core.geometry.GeometryCursor cursor = op.execute( + getEsriGeometryCursor(), true, null); + return OGCGeometry.createFromEsriCursor(cursor, esriSR); + } + + public OGCGeometry intersection(OGCGeometry another) { + com.esri.core.geometry.OperatorIntersection op = (OperatorIntersection) OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Intersection); + com.esri.core.geometry.GeometryCursor cursor = op.execute( + getEsriGeometryCursor(), another.getEsriGeometryCursor(), + getEsriSpatialReference(), null, 7); + return OGCGeometry.createFromEsriCursor(cursor, esriSR, true); + } + + public OGCGeometry union(OGCGeometry another) { + OperatorUnion op = (OperatorUnion) OperatorFactoryLocal.getInstance() + .getOperator(Operator.Type.Union); + GeometryCursorAppend ap = new GeometryCursorAppend( + getEsriGeometryCursor(), another.getEsriGeometryCursor()); + com.esri.core.geometry.GeometryCursor cursor = op.execute(ap, + getEsriSpatialReference(), null); + return OGCGeometry.createFromEsriCursor(cursor, esriSR); + } + + public OGCGeometry difference(OGCGeometry another) { + com.esri.core.geometry.Geometry geom1 = getEsriGeometry(); + com.esri.core.geometry.Geometry geom2 = another.getEsriGeometry(); + return createFromEsriGeometry( + com.esri.core.geometry.GeometryEngine.difference(geom1, geom2, + getEsriSpatialReference()), esriSR); + } + + public OGCGeometry symDifference(OGCGeometry another) { + com.esri.core.geometry.Geometry geom1 = getEsriGeometry(); + com.esri.core.geometry.Geometry geom2 = another.getEsriGeometry(); + return createFromEsriGeometry( + com.esri.core.geometry.GeometryEngine.symmetricDifference( + geom1, geom2, getEsriSpatialReference()), esriSR); + } + + public abstract com.esri.core.geometry.Geometry getEsriGeometry(); + + public GeometryCursor getEsriGeometryCursor() { + return new SimpleGeometryCursor(getEsriGeometry()); + } + + public com.esri.core.geometry.SpatialReference getEsriSpatialReference() { + return esriSR; + } + + /** + * Create an OGCGeometry instance from the GeometryCursor. + * + * @param gc + * @param sr + * @return Geometry instance created from the geometry cursor. + */ + public static OGCGeometry createFromEsriCursor(GeometryCursor gc, + SpatialReference sr) { + return createFromEsriCursor(gc, sr, false); + } + + public static OGCGeometry createFromEsriCursor(GeometryCursor gc, + SpatialReference sr, boolean skipEmpty) { + ArrayList geoms = new ArrayList(10); + Geometry emptyGeom = null; + for (Geometry g = gc.next(); g != null; g = gc.next()) { + emptyGeom = g; + if (!skipEmpty || !g.isEmpty()) + geoms.add(createFromEsriGeometry(g, sr)); + } + + if (geoms.size() == 1) { + return geoms.get(0); + } else if (geoms.size() == 0) + return createFromEsriGeometry(emptyGeom, sr); + else + return new OGCConcreteGeometryCollection(geoms, sr); + } + + public static OGCGeometry fromText(String text) { + OperatorImportFromWkt op = (OperatorImportFromWkt) OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.ImportFromWkt); + OGCStructure ogcStructure = op.executeOGC(0, text, null); + return OGCGeometry.createFromOGCStructure(ogcStructure, + SpatialReference.create(4326)); + } + + public static OGCGeometry fromBinary(ByteBuffer binary) { + OperatorImportFromWkb op = (OperatorImportFromWkb) OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.ImportFromWkb); + OGCStructure ogcStructure = op.executeOGC(0, binary, null); + return OGCGeometry.createFromOGCStructure(ogcStructure, + SpatialReference.create(4326)); + } + + public static OGCGeometry fromEsriShape(ByteBuffer buffer) { + OperatorImportFromESRIShape op = (OperatorImportFromESRIShape) OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.ImportFromESRIShape); + Geometry g = op.execute(0, Geometry.Type.Unknown, buffer); + return OGCGeometry.createFromEsriGeometry(g, + SpatialReference.create(4326)); + } + + public static OGCGeometry fromJson(String string) { + MapGeometry mapGeom = GeometryEngine.jsonToGeometry(JsonParserReader.createFromString(string)); + return OGCGeometry.createFromEsriGeometry(mapGeom.getGeometry(), + mapGeom.getSpatialReference()); + } + + public static OGCGeometry fromGeoJson(String string) { + OperatorImportFromGeoJson op = (OperatorImportFromGeoJson) OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.ImportFromGeoJson); + MapOGCStructure mapOGCStructure = op.executeOGC(0, string, null); + return OGCGeometry.createFromOGCStructure( + mapOGCStructure.m_ogcStructure, + mapOGCStructure.m_spatialReference); + } + + public static OGCGeometry createFromEsriGeometry(Geometry geom, + SpatialReference sr) { + return createFromEsriGeometry(geom, sr, false); + } + + public static OGCGeometry createFromEsriGeometry(Geometry geom, + SpatialReference sr, boolean multiType) { + if (geom == null) + return null; + Geometry.Type t = geom.getType(); + if (t == Geometry.Type.Polygon) { + if (!multiType && ((Polygon) geom).getExteriorRingCount() == 1) + return new OGCPolygon((Polygon) geom, sr); + else + return new OGCMultiPolygon((Polygon) geom, sr); + } + if (t == Geometry.Type.Polyline) { + if (!multiType && ((Polyline) geom).getPathCount() == 1) + return new OGCLineString((Polyline) geom, 0, sr); + else + return new OGCMultiLineString((Polyline) geom, sr); + } + if (t == Geometry.Type.MultiPoint) { + if (!multiType && ((MultiPoint) geom).getPointCount() <= 1) { + if (geom.isEmpty()) + return new OGCPoint(new Point(), sr); + else + return new OGCPoint(((MultiPoint) geom).getPoint(0), sr); + } else + return new OGCMultiPoint((MultiPoint) geom, sr); + } + if (t == Geometry.Type.Point) { + if (!multiType) { + return new OGCPoint((Point) geom, sr); + } else { + return new OGCMultiPoint((Point) geom, sr); + } + } + if (t == Geometry.Type.Envelope) { + Polygon p = new Polygon(); + p.addEnvelope((Envelope) geom, false); + return createFromEsriGeometry(p, sr, multiType); + } + + throw new UnsupportedOperationException(); + } + + public static OGCGeometry createFromOGCStructure(OGCStructure ogcStructure, + SpatialReference sr) { + ArrayList collectionStack = new ArrayList( + 0); + ArrayList structureStack = new ArrayList(0); + ArrayList indices = new ArrayList(0); + + OGCGeometry[] geometries = new OGCGeometry[1]; + OGCConcreteGeometryCollection root = new OGCConcreteGeometryCollection( + Arrays.asList(geometries), sr); + + structureStack.add(ogcStructure); + collectionStack.add(root); + indices.add(0); + + while (!structureStack.isEmpty()) { + OGCStructure lastStructure = structureStack.get(structureStack + .size() - 1); + if (indices.get(indices.size() - 1) == lastStructure.m_structures + .size()) { + structureStack.remove(structureStack.size() - 1); + collectionStack.remove(collectionStack.size() - 1); + indices.remove(indices.size() - 1); + continue; + } + + OGCConcreteGeometryCollection lastCollection = collectionStack + .get(collectionStack.size() - 1); + OGCGeometry g; + int i = indices.get(indices.size() - 1); + + int type = lastStructure.m_structures.get(i).m_type; + + switch (type) { + case 1: + g = new OGCPoint( + (Point) lastStructure.m_structures.get(i).m_geometry, + sr); + lastCollection.geometries.set(i, g); + indices.set(indices.size() - 1, i + 1); + break; + case 2: + g = new OGCLineString( + (Polyline) lastStructure.m_structures.get(i).m_geometry, + 0, sr); + lastCollection.geometries.set(i, g); + indices.set(indices.size() - 1, i + 1); + break; + case 3: + g = new OGCPolygon( + (Polygon) lastStructure.m_structures.get(i).m_geometry, + 0, sr); + lastCollection.geometries.set(i, g); + indices.set(indices.size() - 1, i + 1); + break; + case 4: + g = new OGCMultiPoint( + (MultiPoint) lastStructure.m_structures.get(i).m_geometry, + sr); + lastCollection.geometries.set(i, g); + indices.set(indices.size() - 1, i + 1); + break; + case 5: + g = new OGCMultiLineString( + (Polyline) lastStructure.m_structures.get(i).m_geometry, + sr); + lastCollection.geometries.set(i, g); + indices.set(indices.size() - 1, i + 1); + break; + case 6: + g = new OGCMultiPolygon( + (Polygon) lastStructure.m_structures.get(i).m_geometry, + sr); + lastCollection.geometries.set(i, g); + indices.set(indices.size() - 1, i + 1); + break; + case 7: + geometries = new OGCGeometry[lastStructure.m_structures.get(i).m_structures + .size()]; + g = new OGCConcreteGeometryCollection( + Arrays.asList(geometries), sr); + lastCollection.geometries.set(i, g); + indices.set(indices.size() - 1, i + 1); + structureStack.add(lastStructure.m_structures.get(i)); + collectionStack.add((OGCConcreteGeometryCollection) g); + indices.add(0); + break; + default: + throw new UnsupportedOperationException(); + } + } + + return root.geometries.get(0); + } + + protected boolean isConcreteGeometryCollection() { + return false; + } + + /** + * SpatialReference of the Geometry. + */ + public com.esri.core.geometry.SpatialReference esriSR; + + public void setSpatialReference(SpatialReference esriSR_) { + esriSR = esriSR_; + } + + /** + * Converts this Geometry to the OGCMulti* if it is not OGCMulti* or + * OGCGeometryCollection already. + * + * @return OGCMulti* or OGCGeometryCollection instance. + */ + public abstract OGCGeometry convertToMulti(); + + @Override + public String toString() { + String snippet = asText(); + if (snippet.length() > 200) { + snippet = snippet.substring(0, 197) + "..."; + } + return String + .format("%s: %s", this.getClass().getSimpleName(), snippet); + } + + @Override + public boolean equals(Object other) { + if (other == null) + return false; + + if (other == this) + return true; + + if (other.getClass() != getClass()) + return false; + + OGCGeometry another = (OGCGeometry) other; + com.esri.core.geometry.Geometry geom1 = getEsriGeometry(); + com.esri.core.geometry.Geometry geom2 = another.getEsriGeometry(); + + if (geom1 == null) { + if (geom2 != null) + return false; + } else if (!geom1.equals(geom2)) { + return false; + } + + if (esriSR == another.esriSR) { + return true; + } + + if (esriSR != null && another.esriSR != null) { + return esriSR.equals(another.esriSR); + } + + return false; + } + + @Override + public int hashCode() { + int hash = 1; + com.esri.core.geometry.Geometry geom1 = getEsriGeometry(); + if (geom1 != null) + hash = geom1.hashCode(); + + if (esriSR != null) + hash = NumberUtils.hashCombine(hash, esriSR.hashCode()); + + return hash; + } } diff --git a/src/main/java/com/esri/core/geometry/ogc/OGCGeometryCollection.java b/src/main/java/com/esri/core/geometry/ogc/OGCGeometryCollection.java index 840d8728..5d5cddf2 100644 --- a/src/main/java/com/esri/core/geometry/ogc/OGCGeometryCollection.java +++ b/src/main/java/com/esri/core/geometry/ogc/OGCGeometryCollection.java @@ -25,15 +25,15 @@ package com.esri.core.geometry.ogc; public abstract class OGCGeometryCollection extends OGCGeometry { - /** - * Returns the number of geometries in this GeometryCollection. - */ - public abstract int numGeometries(); - - /** - * Returns the Nth geometry in this GeometryCollection. - * - * @param n The 0 based index of the geometry. - */ - public abstract OGCGeometry geometryN(int n); + /** + * Returns the number of geometries in this GeometryCollection. + */ + public abstract int numGeometries(); + + /** + * Returns the Nth geometry in this GeometryCollection. + * + * @param n The 0 based index of the geometry. + */ + public abstract OGCGeometry geometryN(int n); } diff --git a/src/main/java/com/esri/core/geometry/ogc/OGCLineString.java b/src/main/java/com/esri/core/geometry/ogc/OGCLineString.java index 2d892e62..815ddc97 100644 --- a/src/main/java/com/esri/core/geometry/ogc/OGCLineString.java +++ b/src/main/java/com/esri/core/geometry/ogc/OGCLineString.java @@ -51,79 +51,78 @@ public int numPoints() { return multiPath.getPointCount() + d; } - @Override - public String asText() { - return GeometryEngine.geometryToWkt(getEsriGeometry(), - WktExportFlags.wktExportLineString); - } - - @Override - public ByteBuffer asBinary() { - OperatorExportToWkb op = (OperatorExportToWkb) OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.ExportToWkb); - return op.execute(WkbExportFlags.wkbExportLineString, - getEsriGeometry(), null); - } - - /** - * Returns the specified Point N in this LineString. - * - * @param n The 0 based index of the Point. - */ - public OGCPoint pointN(int n) { - int nn; - if (multiPath.isClosedPath(0) && n == multiPath.getPathSize(0)) { - nn = multiPath.getPathStart(0); - } else - nn = n + multiPath.getPathStart(0); - - return (OGCPoint) OGCGeometry.createFromEsriGeometry( - multiPath.getPoint(nn), esriSR); - } - - @Override - public boolean isClosed() { - return multiPath.isClosedPathInXYPlane(0); - } - - public OGCLineString(MultiPath mp, int pathIndex, SpatialReference sr) { - multiPath = new Polyline(); - if (!mp.isEmpty()) - multiPath.addPath(mp, pathIndex, true); - esriSR = sr; - } - - public OGCLineString(MultiPath mp, int pathIndex, SpatialReference sr, - boolean reversed) { - multiPath = new Polyline(); - if (!mp.isEmpty()) - multiPath.addPath(mp, pathIndex, !reversed); - esriSR = sr; - } - - @Override - public double length() { - return multiPath.calculateLength2D(); - } - - @Override - public OGCPoint startPoint() { - return pointN(0); - } - - @Override - public OGCPoint endPoint() { - return pointN(numPoints() - 1); - } - - @Override - public String geometryType() { - return "LineString"; - } + @Override + public String asText() { + return GeometryEngine.geometryToWkt(getEsriGeometry(), + WktExportFlags.wktExportLineString); + } + + @Override + public ByteBuffer asBinary() { + OperatorExportToWkb op = (OperatorExportToWkb) OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.ExportToWkb); + return op.execute(WkbExportFlags.wkbExportLineString, + getEsriGeometry(), null); + } + + /** + * Returns the specified Point N in this LineString. + * + * @param n The 0 based index of the Point. + */ + public OGCPoint pointN(int n) { + int nn; + if (multiPath.isClosedPath(0) && n == multiPath.getPathSize(0)) { + nn = multiPath.getPathStart(0); + } else + nn = n + multiPath.getPathStart(0); + + return (OGCPoint) OGCGeometry.createFromEsriGeometry( + multiPath.getPoint(nn), esriSR); + } @Override - public long estimateMemorySize() - { + public boolean isClosed() { + return multiPath.isClosedPathInXYPlane(0); + } + + public OGCLineString(MultiPath mp, int pathIndex, SpatialReference sr) { + multiPath = new Polyline(); + if (!mp.isEmpty()) + multiPath.addPath(mp, pathIndex, true); + esriSR = sr; + } + + public OGCLineString(MultiPath mp, int pathIndex, SpatialReference sr, + boolean reversed) { + multiPath = new Polyline(); + if (!mp.isEmpty()) + multiPath.addPath(mp, pathIndex, !reversed); + esriSR = sr; + } + + @Override + public double length() { + return multiPath.calculateLength2D(); + } + + @Override + public OGCPoint startPoint() { + return pointN(0); + } + + @Override + public OGCPoint endPoint() { + return pointN(numPoints() - 1); + } + + @Override + public String geometryType() { + return "LineString"; + } + + @Override + public long estimateMemorySize() { return SIZE_OF_OGC_LINE_STRING + (multiPath != null ? multiPath.estimateMemorySize() : 0); } @@ -132,20 +131,20 @@ public OGCGeometry locateAlong(double mValue) { throw new UnsupportedOperationException(); } - @Override - public OGCGeometry locateBetween(double mStart, double mEnd) { - throw new UnsupportedOperationException(); - } + @Override + public OGCGeometry locateBetween(double mStart, double mEnd) { + throw new UnsupportedOperationException(); + } - @Override - public Geometry getEsriGeometry() { - return multiPath; - } + @Override + public Geometry getEsriGeometry() { + return multiPath; + } - @Override - public OGCGeometry convertToMulti() { - return new OGCMultiLineString((Polyline) multiPath, esriSR); - } + @Override + public OGCGeometry convertToMulti() { + return new OGCMultiLineString((Polyline) multiPath, esriSR); + } - MultiPath multiPath; + MultiPath multiPath; } diff --git a/src/main/java/com/esri/core/geometry/ogc/OGCLinearRing.java b/src/main/java/com/esri/core/geometry/ogc/OGCLinearRing.java index e2ceefeb..fe7d1215 100644 --- a/src/main/java/com/esri/core/geometry/ogc/OGCLinearRing.java +++ b/src/main/java/com/esri/core/geometry/ogc/OGCLinearRing.java @@ -29,36 +29,36 @@ public class OGCLinearRing extends OGCLineString { - public OGCLinearRing(MultiPath mp, int pathIndex, SpatialReference sr, - boolean reversed) { - super(mp, pathIndex, sr, reversed); - if (!mp.isClosedPath(0)) - throw new IllegalArgumentException("LinearRing path must be closed"); - } + public OGCLinearRing(MultiPath mp, int pathIndex, SpatialReference sr, + boolean reversed) { + super(mp, pathIndex, sr, reversed); + if (!mp.isClosedPath(0)) + throw new IllegalArgumentException("LinearRing path must be closed"); + } - public int numPoints() { - if (multiPath.isEmpty()) - return 0; - return multiPath.getPointCount() + 1; - } + public int numPoints() { + if (multiPath.isEmpty()) + return 0; + return multiPath.getPointCount() + 1; + } - public boolean isClosed() { - return true; - } + public boolean isClosed() { + return true; + } - public boolean isRing() { - return true; - } + public boolean isRing() { + return true; + } - @Override - public OGCPoint pointN(int n) { - int nn; - if (n == multiPath.getPathSize(0)) { - nn = multiPath.getPathStart(0); - } else - nn = multiPath.getPathStart(0) + n; + @Override + public OGCPoint pointN(int n) { + int nn; + if (n == multiPath.getPathSize(0)) { + nn = multiPath.getPathStart(0); + } else + nn = multiPath.getPathStart(0) + n; - return (OGCPoint) OGCGeometry.createFromEsriGeometry( - multiPath.getPoint(nn), esriSR); - } + return (OGCPoint) OGCGeometry.createFromEsriGeometry( + multiPath.getPoint(nn), esriSR); + } } diff --git a/src/main/java/com/esri/core/geometry/ogc/OGCMultiCurve.java b/src/main/java/com/esri/core/geometry/ogc/OGCMultiCurve.java index d4ed738d..9aae3bee 100644 --- a/src/main/java/com/esri/core/geometry/ogc/OGCMultiCurve.java +++ b/src/main/java/com/esri/core/geometry/ogc/OGCMultiCurve.java @@ -27,22 +27,22 @@ import com.esri.core.geometry.MultiPath; public abstract class OGCMultiCurve extends OGCGeometryCollection { - public int numGeometries() { - MultiPath mp = (MultiPath) getEsriGeometry(); - return mp.getPathCount(); - } - - public boolean isClosed() { - MultiPath mp = (MultiPath) getEsriGeometry(); - for (int i = 0, n = mp.getPathCount(); i < n; i++) { - if (!mp.isClosedPathInXYPlane(i)) - return false; - } - - return true; - } - - public double length() { - return getEsriGeometry().calculateLength2D(); - } + public int numGeometries() { + MultiPath mp = (MultiPath) getEsriGeometry(); + return mp.getPathCount(); + } + + public boolean isClosed() { + MultiPath mp = (MultiPath) getEsriGeometry(); + for (int i = 0, n = mp.getPathCount(); i < n; i++) { + if (!mp.isClosedPathInXYPlane(i)) + return false; + } + + return true; + } + + public double length() { + return getEsriGeometry().calculateLength2D(); + } } diff --git a/src/main/java/com/esri/core/geometry/ogc/OGCMultiLineString.java b/src/main/java/com/esri/core/geometry/ogc/OGCMultiLineString.java index da102f76..c9520713 100644 --- a/src/main/java/com/esri/core/geometry/ogc/OGCMultiLineString.java +++ b/src/main/java/com/esri/core/geometry/ogc/OGCMultiLineString.java @@ -43,46 +43,45 @@ public class OGCMultiLineString extends OGCMultiCurve { - public OGCMultiLineString(Polyline poly, SpatialReference sr) { - polyline = poly; - esriSR = sr; - } - - @Override - public String asText() { - return GeometryEngine.geometryToWkt(getEsriGeometry(), - WktExportFlags.wktExportMultiLineString); - } - - @Override - public String asGeoJson() { - OperatorExportToGeoJson op = (OperatorExportToGeoJson) OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.ExportToGeoJson); - return op.execute(GeoJsonExportFlags.geoJsonExportPreferMultiGeometry, null, getEsriGeometry()); - } - - @Override - public ByteBuffer asBinary() { - OperatorExportToWkb op = (OperatorExportToWkb) OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.ExportToWkb); - return op.execute(WkbExportFlags.wkbExportMultiLineString, - getEsriGeometry(), null); - } - - @Override - public OGCGeometry geometryN(int n) { - OGCLineString ls = new OGCLineString(polyline, n, esriSR); - return ls; - } - - @Override - public String geometryType() { - return "MultiLineString"; - } + public OGCMultiLineString(Polyline poly, SpatialReference sr) { + polyline = poly; + esriSR = sr; + } + + @Override + public String asText() { + return GeometryEngine.geometryToWkt(getEsriGeometry(), + WktExportFlags.wktExportMultiLineString); + } + + @Override + public String asGeoJson() { + OperatorExportToGeoJson op = (OperatorExportToGeoJson) OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.ExportToGeoJson); + return op.execute(GeoJsonExportFlags.geoJsonExportPreferMultiGeometry, null, getEsriGeometry()); + } + + @Override + public ByteBuffer asBinary() { + OperatorExportToWkb op = (OperatorExportToWkb) OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.ExportToWkb); + return op.execute(WkbExportFlags.wkbExportMultiLineString, + getEsriGeometry(), null); + } + + @Override + public OGCGeometry geometryN(int n) { + OGCLineString ls = new OGCLineString(polyline, n, esriSR); + return ls; + } + + @Override + public String geometryType() { + return "MultiLineString"; + } @Override - public long estimateMemorySize() - { + public long estimateMemorySize() { return SIZE_OF_OGC_MULTI_LINE_STRING + (polyline != null ? polyline.estimateMemorySize() : 0); } @@ -94,27 +93,27 @@ public OGCGeometry boundary() { return OGCGeometry.createFromEsriGeometry(g, esriSR, true); } - @Override - public OGCGeometry locateAlong(double mValue) { - // TODO Auto-generated method stub - throw new UnsupportedOperationException(); - } - - @Override - public OGCGeometry locateBetween(double mStart, double mEnd) { - // TODO Auto-generated method stub - throw new UnsupportedOperationException(); - } - - @Override - public Geometry getEsriGeometry() { - return polyline; - } - - @Override - public OGCGeometry convertToMulti() { - return this; - } - - Polyline polyline; + @Override + public OGCGeometry locateAlong(double mValue) { + // TODO Auto-generated method stub + throw new UnsupportedOperationException(); + } + + @Override + public OGCGeometry locateBetween(double mStart, double mEnd) { + // TODO Auto-generated method stub + throw new UnsupportedOperationException(); + } + + @Override + public Geometry getEsriGeometry() { + return polyline; + } + + @Override + public OGCGeometry convertToMulti() { + return this; + } + + Polyline polyline; } diff --git a/src/main/java/com/esri/core/geometry/ogc/OGCMultiPoint.java b/src/main/java/com/esri/core/geometry/ogc/OGCMultiPoint.java index 38c1fac6..77ced741 100644 --- a/src/main/java/com/esri/core/geometry/ogc/OGCMultiPoint.java +++ b/src/main/java/com/esri/core/geometry/ogc/OGCMultiPoint.java @@ -40,95 +40,92 @@ import static com.esri.core.geometry.SizeOf.SIZE_OF_OGC_MULTI_POINT; public class OGCMultiPoint extends OGCGeometryCollection { - public int numGeometries() { - return multiPoint.getPointCount(); - } - - @Override - public String asText() { - return GeometryEngine.geometryToWkt(getEsriGeometry(), - WktExportFlags.wktExportMultiPoint); - } - - @Override - public ByteBuffer asBinary() { - OperatorExportToWkb op = (OperatorExportToWkb) OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.ExportToWkb); - return op.execute(WkbExportFlags.wkbExportMultiPoint, - getEsriGeometry(), null); - } - - public OGCGeometry geometryN(int n) { - return OGCGeometry.createFromEsriGeometry(multiPoint.getPoint(n), - esriSR); - } - - @Override - public String geometryType() { - return "MultiPoint"; - } + public int numGeometries() { + return multiPoint.getPointCount(); + } + + @Override + public String asText() { + return GeometryEngine.geometryToWkt(getEsriGeometry(), + WktExportFlags.wktExportMultiPoint); + } + + @Override + public ByteBuffer asBinary() { + OperatorExportToWkb op = (OperatorExportToWkb) OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.ExportToWkb); + return op.execute(WkbExportFlags.wkbExportMultiPoint, + getEsriGeometry(), null); + } + + public OGCGeometry geometryN(int n) { + return OGCGeometry.createFromEsriGeometry(multiPoint.getPoint(n), + esriSR); + } @Override - public long estimateMemorySize() - { + public String geometryType() { + return "MultiPoint"; + } + + @Override + public long estimateMemorySize() { return SIZE_OF_OGC_MULTI_POINT + (multiPoint != null ? multiPoint.estimateMemorySize() : 0); } /** - * - * @param mp - * MultiPoint instance will be referenced by this OGC class + * @param mp MultiPoint instance will be referenced by this OGC class */ public OGCMultiPoint(MultiPoint mp, SpatialReference sr) { multiPoint = mp; esriSR = sr; } - public OGCMultiPoint(Point startPoint, SpatialReference sr) { - multiPoint = new MultiPoint(); - multiPoint.add((Point) startPoint); - esriSR = sr; - } - - public OGCMultiPoint(OGCPoint startPoint, OGCPoint endPoint) { - multiPoint = new MultiPoint(); - multiPoint.add((Point) startPoint.getEsriGeometry()); - multiPoint.add((Point) endPoint.getEsriGeometry()); - esriSR = startPoint.esriSR; - } - - public OGCMultiPoint(SpatialReference sr) { - esriSR = sr; - multiPoint = new MultiPoint(); - } - - @Override - public OGCGeometry boundary() { - return new OGCMultiPoint((MultiPoint) multiPoint.createInstance(), - esriSR);// return empty multipoint - } - - @Override - public OGCGeometry locateAlong(double mValue) { - // TODO Auto-generated method stub - throw new UnsupportedOperationException(); - } - - @Override - public OGCGeometry locateBetween(double mStart, double mEnd) { - // TODO Auto-generated method stub - throw new UnsupportedOperationException(); - } - - @Override - public Geometry getEsriGeometry() { - return multiPoint; - } - - @Override - public OGCGeometry convertToMulti() { - return this; - } - - private com.esri.core.geometry.MultiPoint multiPoint; + public OGCMultiPoint(Point startPoint, SpatialReference sr) { + multiPoint = new MultiPoint(); + multiPoint.add((Point) startPoint); + esriSR = sr; + } + + public OGCMultiPoint(OGCPoint startPoint, OGCPoint endPoint) { + multiPoint = new MultiPoint(); + multiPoint.add((Point) startPoint.getEsriGeometry()); + multiPoint.add((Point) endPoint.getEsriGeometry()); + esriSR = startPoint.esriSR; + } + + public OGCMultiPoint(SpatialReference sr) { + esriSR = sr; + multiPoint = new MultiPoint(); + } + + @Override + public OGCGeometry boundary() { + return new OGCMultiPoint((MultiPoint) multiPoint.createInstance(), + esriSR);// return empty multipoint + } + + @Override + public OGCGeometry locateAlong(double mValue) { + // TODO Auto-generated method stub + throw new UnsupportedOperationException(); + } + + @Override + public OGCGeometry locateBetween(double mStart, double mEnd) { + // TODO Auto-generated method stub + throw new UnsupportedOperationException(); + } + + @Override + public Geometry getEsriGeometry() { + return multiPoint; + } + + @Override + public OGCGeometry convertToMulti() { + return this; + } + + private com.esri.core.geometry.MultiPoint multiPoint; } diff --git a/src/main/java/com/esri/core/geometry/ogc/OGCMultiPolygon.java b/src/main/java/com/esri/core/geometry/ogc/OGCMultiPolygon.java index f9d8ec04..43c6726f 100644 --- a/src/main/java/com/esri/core/geometry/ogc/OGCMultiPolygon.java +++ b/src/main/java/com/esri/core/geometry/ogc/OGCMultiPolygon.java @@ -43,60 +43,59 @@ public class OGCMultiPolygon extends OGCMultiSurface { - public OGCMultiPolygon(Polygon src, SpatialReference sr) { - polygon = src; - esriSR = sr; - } - - @Override - public String asText() { - return GeometryEngine.geometryToWkt(getEsriGeometry(), - WktExportFlags.wktExportMultiPolygon); - } - - @Override - public ByteBuffer asBinary() { - OperatorExportToWkb op = (OperatorExportToWkb) OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.ExportToWkb); - return op.execute(WkbExportFlags.wkbExportMultiPolygon, - getEsriGeometry(), null); - } - - @Override - public String asGeoJson() { - OperatorExportToGeoJson op = (OperatorExportToGeoJson) OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.ExportToGeoJson); - return op.execute(GeoJsonExportFlags.geoJsonExportPreferMultiGeometry, null, getEsriGeometry()); - } - - @Override - public int numGeometries() { - return polygon.getExteriorRingCount(); - } - - @Override - public OGCGeometry geometryN(int n) { - int exterior = 0; - for (int i = 0; i < polygon.getPathCount(); i++) { - if (polygon.isExteriorRing(i)) - exterior++; - - if (exterior == n + 1) { - return new OGCPolygon(polygon, i, esriSR); - } - } - - throw new IllegalArgumentException("geometryN: n out of range"); - } - - @Override - public String geometryType() { - return "MultiPolygon"; - } + public OGCMultiPolygon(Polygon src, SpatialReference sr) { + polygon = src; + esriSR = sr; + } + + @Override + public String asText() { + return GeometryEngine.geometryToWkt(getEsriGeometry(), + WktExportFlags.wktExportMultiPolygon); + } + + @Override + public ByteBuffer asBinary() { + OperatorExportToWkb op = (OperatorExportToWkb) OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.ExportToWkb); + return op.execute(WkbExportFlags.wkbExportMultiPolygon, + getEsriGeometry(), null); + } + + @Override + public String asGeoJson() { + OperatorExportToGeoJson op = (OperatorExportToGeoJson) OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.ExportToGeoJson); + return op.execute(GeoJsonExportFlags.geoJsonExportPreferMultiGeometry, null, getEsriGeometry()); + } + + @Override + public int numGeometries() { + return polygon.getExteriorRingCount(); + } + + @Override + public OGCGeometry geometryN(int n) { + int exterior = 0; + for (int i = 0; i < polygon.getPathCount(); i++) { + if (polygon.isExteriorRing(i)) + exterior++; + + if (exterior == n + 1) { + return new OGCPolygon(polygon, i, esriSR); + } + } + + throw new IllegalArgumentException("geometryN: n out of range"); + } @Override - public long estimateMemorySize() - { + public String geometryType() { + return "MultiPolygon"; + } + + @Override + public long estimateMemorySize() { return SIZE_OF_OGC_MULTI_POLYGON + (polygon != null ? polygon.estimateMemorySize() : 0); } @@ -108,27 +107,27 @@ public OGCGeometry boundary() { esriSR, true); } - @Override - public OGCGeometry locateAlong(double mValue) { - // TODO Auto-generated method stub - throw new UnsupportedOperationException(); - } - - @Override - public OGCGeometry locateBetween(double mStart, double mEnd) { - // TODO Auto-generated method stub - throw new UnsupportedOperationException(); - } - - @Override - public Geometry getEsriGeometry() { - return polygon; - } - - @Override - public OGCGeometry convertToMulti() { - return this; - } - - Polygon polygon; + @Override + public OGCGeometry locateAlong(double mValue) { + // TODO Auto-generated method stub + throw new UnsupportedOperationException(); + } + + @Override + public OGCGeometry locateBetween(double mStart, double mEnd) { + // TODO Auto-generated method stub + throw new UnsupportedOperationException(); + } + + @Override + public Geometry getEsriGeometry() { + return polygon; + } + + @Override + public OGCGeometry convertToMulti() { + return this; + } + + Polygon polygon; } diff --git a/src/main/java/com/esri/core/geometry/ogc/OGCMultiSurface.java b/src/main/java/com/esri/core/geometry/ogc/OGCMultiSurface.java index 243e06f9..1c98be92 100644 --- a/src/main/java/com/esri/core/geometry/ogc/OGCMultiSurface.java +++ b/src/main/java/com/esri/core/geometry/ogc/OGCMultiSurface.java @@ -25,17 +25,17 @@ package com.esri.core.geometry.ogc; public abstract class OGCMultiSurface extends OGCGeometryCollection { - public double area() { - return getEsriGeometry().calculateArea2D(); - } - - public OGCPoint centroid() { - // TODO - throw new UnsupportedOperationException(); - } - - public OGCPoint pointOnSurface() { - // TODO - throw new UnsupportedOperationException(); - } + public double area() { + return getEsriGeometry().calculateArea2D(); + } + + public OGCPoint centroid() { + // TODO + throw new UnsupportedOperationException(); + } + + public OGCPoint pointOnSurface() { + // TODO + throw new UnsupportedOperationException(); + } } diff --git a/src/main/java/com/esri/core/geometry/ogc/OGCPoint.java b/src/main/java/com/esri/core/geometry/ogc/OGCPoint.java index 24e65ef1..f27c1f5a 100644 --- a/src/main/java/com/esri/core/geometry/ogc/OGCPoint.java +++ b/src/main/java/com/esri/core/geometry/ogc/OGCPoint.java @@ -39,49 +39,48 @@ import static com.esri.core.geometry.SizeOf.SIZE_OF_OGC_POINT; public final class OGCPoint extends OGCGeometry { - public OGCPoint(Point pt, SpatialReference sr) { - point = pt; - esriSR = sr; - } - - @Override - public String asText() { - return GeometryEngine.geometryToWkt(getEsriGeometry(), - WktExportFlags.wktExportPoint); - } - - @Override - public ByteBuffer asBinary() { - OperatorExportToWkb op = (OperatorExportToWkb) OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.ExportToWkb); - return op.execute(WkbExportFlags.wkbExportPoint, getEsriGeometry(), - null); - } - - public double X() { - return point.getX(); - } - - public double Y() { - return point.getY(); - } - - public double Z() { - return point.getZ(); - } - - public double M() { - return point.getM(); - } - - @Override - public String geometryType() { - return "Point"; - } + public OGCPoint(Point pt, SpatialReference sr) { + point = pt; + esriSR = sr; + } + + @Override + public String asText() { + return GeometryEngine.geometryToWkt(getEsriGeometry(), + WktExportFlags.wktExportPoint); + } + + @Override + public ByteBuffer asBinary() { + OperatorExportToWkb op = (OperatorExportToWkb) OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.ExportToWkb); + return op.execute(WkbExportFlags.wkbExportPoint, getEsriGeometry(), + null); + } + + public double X() { + return point.getX(); + } + + public double Y() { + return point.getY(); + } + + public double Z() { + return point.getZ(); + } + + public double M() { + return point.getM(); + } @Override - public long estimateMemorySize() - { + public String geometryType() { + return "Point"; + } + + @Override + public long estimateMemorySize() { return SIZE_OF_OGC_POINT + (point != null ? point.estimateMemorySize() : 0); } @@ -91,28 +90,28 @@ public OGCGeometry boundary() { .getDescription()), esriSR);// return empty point } - @Override - public OGCGeometry locateAlong(double mValue) { - // TODO Auto-generated method stub - throw new UnsupportedOperationException(); - } - - @Override - public OGCGeometry locateBetween(double mStart, double mEnd) { - // TODO Auto-generated method stub - throw new UnsupportedOperationException(); - } - - @Override - public com.esri.core.geometry.Geometry getEsriGeometry() { - return point; - } - - @Override - public OGCGeometry convertToMulti() { - return new OGCMultiPoint(point, esriSR); - } - - com.esri.core.geometry.Point point; + @Override + public OGCGeometry locateAlong(double mValue) { + // TODO Auto-generated method stub + throw new UnsupportedOperationException(); + } + + @Override + public OGCGeometry locateBetween(double mStart, double mEnd) { + // TODO Auto-generated method stub + throw new UnsupportedOperationException(); + } + + @Override + public com.esri.core.geometry.Geometry getEsriGeometry() { + return point; + } + + @Override + public OGCGeometry convertToMulti() { + return new OGCMultiPoint(point, esriSR); + } + + com.esri.core.geometry.Point point; } diff --git a/src/main/java/com/esri/core/geometry/ogc/OGCPolygon.java b/src/main/java/com/esri/core/geometry/ogc/OGCPolygon.java index 9deeb69e..1a04c6d3 100644 --- a/src/main/java/com/esri/core/geometry/ogc/OGCPolygon.java +++ b/src/main/java/com/esri/core/geometry/ogc/OGCPolygon.java @@ -40,83 +40,82 @@ import static com.esri.core.geometry.SizeOf.SIZE_OF_OGC_POLYGON; public class OGCPolygon extends OGCSurface { - public OGCPolygon(Polygon src, int exteriorRing, SpatialReference sr) { - polygon = new Polygon(); - for (int i = exteriorRing, n = src.getPathCount(); i < n; i++) { - if (i > exteriorRing && src.isExteriorRing(i)) - break; - polygon.addPath(src, i, true); - } - esriSR = sr; - } - - public OGCPolygon(Polygon geom, SpatialReference sr) { - polygon = geom; - if (geom.getExteriorRingCount() > 1) - throw new IllegalArgumentException( - "Polygon has to have one exterior ring. Simplify geom with OperatorSimplify."); - esriSR = sr; - } - - @Override - public String asText() { - return GeometryEngine.geometryToWkt(getEsriGeometry(), - WktExportFlags.wktExportPolygon); - } - - @Override - public ByteBuffer asBinary() { - OperatorExportToWkb op = (OperatorExportToWkb) OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.ExportToWkb); - return op.execute(WkbExportFlags.wkbExportPolygon, getEsriGeometry(), - null); - } - - /** - * Returns the exterior ring of this Polygon. - * - * @return OGCLinearRing instance. - */ - public OGCLineString exteriorRing() { - if (polygon.isEmpty()) - return new OGCLinearRing((Polygon) polygon.createInstance(), 0, - esriSR, true); - return new OGCLinearRing(polygon, 0, esriSR, true); - } - - /** - * Returns the number of interior rings in this Polygon. - */ - public int numInteriorRing() { - return polygon.getPathCount() - 1; - } - - /** - * Returns the Nth interior ring for this Polygon as a LineString. - * - * @param n The 0 based index of the interior ring. - * @return OGCLinearRing instance. - */ - public OGCLineString interiorRingN(int n) { - return new OGCLinearRing(polygon, n + 1, esriSR, true); - } - - @Override - public OGCMultiCurve boundary() { - Polyline polyline = new Polyline(); - polyline.add(polygon, true); // adds reversed path - return (OGCMultiCurve) OGCGeometry.createFromEsriGeometry(polyline, - esriSR, true); - } - - @Override - public String geometryType() { - return "Polygon"; - } + public OGCPolygon(Polygon src, int exteriorRing, SpatialReference sr) { + polygon = new Polygon(); + for (int i = exteriorRing, n = src.getPathCount(); i < n; i++) { + if (i > exteriorRing && src.isExteriorRing(i)) + break; + polygon.addPath(src, i, true); + } + esriSR = sr; + } + + public OGCPolygon(Polygon geom, SpatialReference sr) { + polygon = geom; + if (geom.getExteriorRingCount() > 1) + throw new IllegalArgumentException( + "Polygon has to have one exterior ring. Simplify geom with OperatorSimplify."); + esriSR = sr; + } + + @Override + public String asText() { + return GeometryEngine.geometryToWkt(getEsriGeometry(), + WktExportFlags.wktExportPolygon); + } + + @Override + public ByteBuffer asBinary() { + OperatorExportToWkb op = (OperatorExportToWkb) OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.ExportToWkb); + return op.execute(WkbExportFlags.wkbExportPolygon, getEsriGeometry(), + null); + } + + /** + * Returns the exterior ring of this Polygon. + * + * @return OGCLinearRing instance. + */ + public OGCLineString exteriorRing() { + if (polygon.isEmpty()) + return new OGCLinearRing((Polygon) polygon.createInstance(), 0, + esriSR, true); + return new OGCLinearRing(polygon, 0, esriSR, true); + } + + /** + * Returns the number of interior rings in this Polygon. + */ + public int numInteriorRing() { + return polygon.getPathCount() - 1; + } + + /** + * Returns the Nth interior ring for this Polygon as a LineString. + * + * @param n The 0 based index of the interior ring. + * @return OGCLinearRing instance. + */ + public OGCLineString interiorRingN(int n) { + return new OGCLinearRing(polygon, n + 1, esriSR, true); + } @Override - public long estimateMemorySize() - { + public OGCMultiCurve boundary() { + Polyline polyline = new Polyline(); + polyline.add(polygon, true); // adds reversed path + return (OGCMultiCurve) OGCGeometry.createFromEsriGeometry(polyline, + esriSR, true); + } + + @Override + public String geometryType() { + return "Polygon"; + } + + @Override + public long estimateMemorySize() { return SIZE_OF_OGC_POLYGON + (polygon != null ? polygon.estimateMemorySize() : 0); } @@ -126,21 +125,21 @@ public OGCGeometry locateAlong(double mValue) { throw new UnsupportedOperationException(); } - @Override - public OGCGeometry locateBetween(double mStart, double mEnd) { - // TODO Auto-generated method stub - throw new UnsupportedOperationException(); - } + @Override + public OGCGeometry locateBetween(double mStart, double mEnd) { + // TODO Auto-generated method stub + throw new UnsupportedOperationException(); + } - @Override - public Geometry getEsriGeometry() { - return polygon; - } + @Override + public Geometry getEsriGeometry() { + return polygon; + } - @Override - public OGCGeometry convertToMulti() { - return new OGCMultiPolygon(polygon, esriSR); - } + @Override + public OGCGeometry convertToMulti() { + return new OGCMultiPolygon(polygon, esriSR); + } - Polygon polygon; + Polygon polygon; } diff --git a/src/main/java/com/esri/core/geometry/ogc/OGCSurface.java b/src/main/java/com/esri/core/geometry/ogc/OGCSurface.java index 5e255cac..43d192e6 100644 --- a/src/main/java/com/esri/core/geometry/ogc/OGCSurface.java +++ b/src/main/java/com/esri/core/geometry/ogc/OGCSurface.java @@ -25,20 +25,20 @@ package com.esri.core.geometry.ogc; public abstract class OGCSurface extends OGCGeometry { - public double area() { - return getEsriGeometry().calculateArea2D(); - } + public double area() { + return getEsriGeometry().calculateArea2D(); + } - public OGCPoint centroid() { - // TODO: implement me; - throw new UnsupportedOperationException(); - } + public OGCPoint centroid() { + // TODO: implement me; + throw new UnsupportedOperationException(); + } - public OGCPoint pointOnSurface() { - // TODO: support this (need to port OperatorLabelPoint) - throw new UnsupportedOperationException(); - } + public OGCPoint pointOnSurface() { + // TODO: support this (need to port OperatorLabelPoint) + throw new UnsupportedOperationException(); + } - public abstract OGCMultiCurve boundary(); + public abstract OGCMultiCurve boundary(); } diff --git a/src/test/java/com/esri/core/geometry/GeometryUtils.java b/src/test/java/com/esri/core/geometry/GeometryUtils.java index 1197eba1..2734db7c 100644 --- a/src/test/java/com/esri/core/geometry/GeometryUtils.java +++ b/src/test/java/com/esri/core/geometry/GeometryUtils.java @@ -29,97 +29,97 @@ import java.util.Scanner; public class GeometryUtils { - public static String getGeometryType(Geometry geomIn) { - // there are five types: esriGeometryPoint - // esriGeometryMultipoint - // esriGeometryPolyline - // esriGeometryPolygon - // esriGeometryEnvelope - if (geomIn instanceof Point) - return "esriGeometryPoint"; - if (geomIn instanceof MultiPoint) - return "esriGeometryMultipoint"; - if (geomIn instanceof Polyline) - return "esriGeometryPolyline"; - if (geomIn instanceof Polygon) - return "esriGeometryPolygon"; - if (geomIn instanceof Envelope) - return "esriGeometryEnvelope"; - else - return null; - } - - static Geometry getGeometryFromJSon(String jsonStr) { - try { - Geometry geom = GeometryEngine.jsonToGeometry(jsonStr).getGeometry(); - return geom; - } catch (Exception ex) { - return null; - } - } - - public enum SpatialRelationType { - esriGeometryRelationCross, esriGeometryRelationDisjoint, esriGeometryRelationIn, esriGeometryRelationInteriorIntersection, esriGeometryRelationIntersection, esriGeometryRelationLineCoincidence, esriGeometryRelationLineTouch, esriGeometryRelationOverlap, esriGeometryRelationPointTouch, esriGeometryRelationTouch, esriGeometryRelationWithin, esriGeometryRelationRelation - } - - static String getJSonStringFromGeometry(Geometry geomIn, SpatialReference sr) { - String jsonStr4Geom = GeometryEngine.geometryToJson(sr, geomIn); - String jsonStrNew = "{\"geometryType\":\"" + getGeometryType(geomIn) - + "\",\"geometries\":[" + jsonStr4Geom + "]}"; - return jsonStrNew; - } - - public static Geometry loadFromTextFileDbg(String textFileName) - throws FileNotFoundException { - String fullPath = textFileName; - // string fullCSVPathName = System.IO.Path.Combine( directoryPath , - // CsvFileName); - File fileInfo = new File(fullPath); - - Scanner scanner = new Scanner(fileInfo); - - Geometry geom = null; - - // grab first line - String line = scanner.nextLine(); - String geomTypeString = line.substring(1); - if (geomTypeString.equalsIgnoreCase("polygon")) - geom = new Polygon(); - else if (geomTypeString.equalsIgnoreCase("polyline")) - geom = new Polyline(); - else if (geomTypeString.equalsIgnoreCase("multipoint")) - geom = new MultiPoint(); - else if (geomTypeString.equalsIgnoreCase("point")) - geom = new Point(); - - while (line.startsWith("*")) - if (scanner.hasNextLine()) - line = scanner.nextLine(); - - int j = 0; - Geometry.Type geomType = geom.getType(); - while (scanner.hasNextLine()) { - String[] parsedLine = line.split("\\s+"); - double xVal = Double.parseDouble(parsedLine[0]); - double yVal = Double.parseDouble(parsedLine[1]); - if (j == 0 - && (geomType == Geometry.Type.Polygon || geomType == Geometry.Type.Polyline)) - ((MultiPath) geom).startPath(xVal, yVal); - else { - if (geomType == Geometry.Type.Polygon - || geomType == Geometry.Type.Polyline) - ((MultiPath) geom).lineTo(xVal, yVal); - else if (geomType == Geometry.Type.MultiPoint) - ((MultiPoint) geom).add(xVal, yVal); - // else if(geomType == Geometry.Type.Point) - // Point geom = null;//new Point(xVal, yVal); - } - j++; - line = scanner.nextLine(); - } - - scanner.close(); - - return geom; - } + public static String getGeometryType(Geometry geomIn) { + // there are five types: esriGeometryPoint + // esriGeometryMultipoint + // esriGeometryPolyline + // esriGeometryPolygon + // esriGeometryEnvelope + if (geomIn instanceof Point) + return "esriGeometryPoint"; + if (geomIn instanceof MultiPoint) + return "esriGeometryMultipoint"; + if (geomIn instanceof Polyline) + return "esriGeometryPolyline"; + if (geomIn instanceof Polygon) + return "esriGeometryPolygon"; + if (geomIn instanceof Envelope) + return "esriGeometryEnvelope"; + else + return null; + } + + static Geometry getGeometryFromJSon(String jsonStr) { + try { + Geometry geom = GeometryEngine.jsonToGeometry(jsonStr).getGeometry(); + return geom; + } catch (Exception ex) { + return null; + } + } + + public enum SpatialRelationType { + esriGeometryRelationCross, esriGeometryRelationDisjoint, esriGeometryRelationIn, esriGeometryRelationInteriorIntersection, esriGeometryRelationIntersection, esriGeometryRelationLineCoincidence, esriGeometryRelationLineTouch, esriGeometryRelationOverlap, esriGeometryRelationPointTouch, esriGeometryRelationTouch, esriGeometryRelationWithin, esriGeometryRelationRelation + } + + static String getJSonStringFromGeometry(Geometry geomIn, SpatialReference sr) { + String jsonStr4Geom = GeometryEngine.geometryToJson(sr, geomIn); + String jsonStrNew = "{\"geometryType\":\"" + getGeometryType(geomIn) + + "\",\"geometries\":[" + jsonStr4Geom + "]}"; + return jsonStrNew; + } + + public static Geometry loadFromTextFileDbg(String textFileName) + throws FileNotFoundException { + String fullPath = textFileName; + // string fullCSVPathName = System.IO.Path.Combine( directoryPath , + // CsvFileName); + File fileInfo = new File(fullPath); + + Scanner scanner = new Scanner(fileInfo); + + Geometry geom = null; + + // grab first line + String line = scanner.nextLine(); + String geomTypeString = line.substring(1); + if (geomTypeString.equalsIgnoreCase("polygon")) + geom = new Polygon(); + else if (geomTypeString.equalsIgnoreCase("polyline")) + geom = new Polyline(); + else if (geomTypeString.equalsIgnoreCase("multipoint")) + geom = new MultiPoint(); + else if (geomTypeString.equalsIgnoreCase("point")) + geom = new Point(); + + while (line.startsWith("*")) + if (scanner.hasNextLine()) + line = scanner.nextLine(); + + int j = 0; + Geometry.Type geomType = geom.getType(); + while (scanner.hasNextLine()) { + String[] parsedLine = line.split("\\s+"); + double xVal = Double.parseDouble(parsedLine[0]); + double yVal = Double.parseDouble(parsedLine[1]); + if (j == 0 + && (geomType == Geometry.Type.Polygon || geomType == Geometry.Type.Polyline)) + ((MultiPath) geom).startPath(xVal, yVal); + else { + if (geomType == Geometry.Type.Polygon + || geomType == Geometry.Type.Polyline) + ((MultiPath) geom).lineTo(xVal, yVal); + else if (geomType == Geometry.Type.MultiPoint) + ((MultiPoint) geom).add(xVal, yVal); + // else if(geomType == Geometry.Type.Point) + // Point geom = null;//new Point(xVal, yVal); + } + j++; + line = scanner.nextLine(); + } + + scanner.close(); + + return geom; + } } diff --git a/src/test/java/com/esri/core/geometry/RandomCoordinateGenerator.java b/src/test/java/com/esri/core/geometry/RandomCoordinateGenerator.java index 449f0f8b..dcaa0444 100644 --- a/src/test/java/com/esri/core/geometry/RandomCoordinateGenerator.java +++ b/src/test/java/com/esri/core/geometry/RandomCoordinateGenerator.java @@ -29,384 +29,384 @@ public class RandomCoordinateGenerator { - // final Point openString[] = { new Point(1220, 1320), - // new Point(1220, 2320), new Point(3000, 2320), - // new Point(3520, 1720), new Point(3000, 1320), }; - // - // final Point3D openString3[] = { new Point3D(1220, 1320, 11), - // new Point3D(1220, 2320, 2), new Point3D(3000, 2320, 3), - // new Point3D(3520, 1720, 4), new Point3D(3000, 1320, 5), }; + // final Point openString[] = { new Point(1220, 1320), + // new Point(1220, 2320), new Point(3000, 2320), + // new Point(3520, 1720), new Point(3000, 1320), }; + // + // final Point3D openString3[] = { new Point3D(1220, 1320, 11), + // new Point3D(1220, 2320, 2), new Point3D(3000, 2320, 3), + // new Point3D(3520, 1720, 4), new Point3D(3000, 1320, 5), }; - Vector points; - Envelope env; - int maxcount; - double tolerance; - double maxlen; - Random random = new Random(1973); + Vector points; + Envelope env; + int maxcount; + double tolerance; + double maxlen; + Random random = new Random(1973); - Point _GenerateNewPoint() { - if (points.size() == maxcount) - return _RandomizeExisting(); - Point pt; - double f = random.nextDouble() - 0.5; - if (points.size() == 0) - pt = env.getCenter(); - else - pt = points.lastElement(); - // pt.x = pt.x + env.Width() * f; - pt.setX(pt.getX() + maxlen * f); - f = 1.0 * random.nextDouble() - 0.5; - pt.setY(pt.getY() + env.getHeight() * f); - pt.setY(pt.getY() + maxlen * f); - pt = _snapClip(pt, env); - points.add(pt); - return pt; - } + Point _GenerateNewPoint() { + if (points.size() == maxcount) + return _RandomizeExisting(); + Point pt; + double f = random.nextDouble() - 0.5; + if (points.size() == 0) + pt = env.getCenter(); + else + pt = points.lastElement(); + // pt.x = pt.x + env.Width() * f; + pt.setX(pt.getX() + maxlen * f); + f = 1.0 * random.nextDouble() - 0.5; + pt.setY(pt.getY() + env.getHeight() * f); + pt.setY(pt.getY() + maxlen * f); + pt = _snapClip(pt, env); + points.add(pt); + return pt; + } - Point _RandomizeExisting() { - if (points.size() == 0) { - return _GenerateNewPoint(); - } + Point _RandomizeExisting() { + if (points.size() == 0) { + return _GenerateNewPoint(); + } - double f = random.nextDouble(); - int num = (int) (f * points.size()); - Point pt = points.get(num); + double f = random.nextDouble(); + int num = (int) (f * points.size()); + Point pt = points.get(num); - f = random.nextDouble(); + f = random.nextDouble(); - // if (f > 0.9) - if (f > 2) { - f = random.nextDouble(); - pt.setX(pt.getX() + (1 - 2 * f) * 2 * tolerance); - f = random.nextDouble(); - pt.setY(pt.getY() + (1 - 2 * f) * 2 * tolerance); - pt = _snapClip(pt, env); - } - return pt; - } + // if (f > 0.9) + if (f > 2) { + f = random.nextDouble(); + pt.setX(pt.getX() + (1 - 2 * f) * 2 * tolerance); + f = random.nextDouble(); + pt.setY(pt.getY() + (1 - 2 * f) * 2 * tolerance); + pt = _snapClip(pt, env); + } + return pt; + } - public RandomCoordinateGenerator(int count, Envelope e, double tol) { - env = e; - maxlen = (env.getWidth() + env.getHeight()) / 2 * 0.1; - points = new Vector(); - points.ensureCapacity(count); - tolerance = tol; - maxcount = count; - } + public RandomCoordinateGenerator(int count, Envelope e, double tol) { + env = e; + maxlen = (env.getWidth() + env.getHeight()) / 2 * 0.1; + points = new Vector(); + points.ensureCapacity(count); + tolerance = tol; + maxcount = count; + } - Point GetRandomCoord() { - double f = random.nextDouble(); - Point pt; - if (f > 0.9) - pt = _RandomizeExisting(); - else - pt = _GenerateNewPoint(); + Point GetRandomCoord() { + double f = random.nextDouble(); + Point pt; + if (f > 0.9) + pt = _RandomizeExisting(); + else + pt = _GenerateNewPoint(); - return pt; - } + return pt; + } - Point _snapClip(Point pt, Envelope env) { - double x = pt.getX(); - if (x < env.getXMin()) - x = env.getXMin(); - if (x > env.getXMax()) - x = env.getXMax(); - double y = pt.getY(); - if (y < env.getYMin()) - y = env.getYMin(); - if (y > env.getYMax()) - y = env.getYMax(); - return new Point(x, y); - } + Point _snapClip(Point pt, Envelope env) { + double x = pt.getX(); + if (x < env.getXMin()) + x = env.getXMin(); + if (x > env.getXMax()) + x = env.getXMax(); + double y = pt.getY(); + if (y < env.getYMin()) + y = env.getYMin(); + if (y > env.getYMax()) + y = env.getYMax(); + return new Point(x, y); + } - // void CompareGeometryContent(MultiVertexGeometry geom, Point buf[], int - // sz) { - // Assert.assertTrue(!geom.isEmpty()); - // Assert.assertTrue(geom.getPointCount() == sz); - // // Go through the geometry points - // for (int i = 0; i < geom.getPointCount(); i++) { - // Point point = new Point(); // not a right pattern the point has to - // // be created outside of the loop. - // geom.getPointByVal(i, point); - // Assert.assertTrue(point.getX() == buf[i].getX()); - // Assert.assertTrue(point.getY() == buf[i].getY()); - // Assert.assertTrue(point.getX() == buf[i].getX()); - // Assert.assertTrue(point.getY() == buf[i].getY()); - // } - // if (geom.getType() == Geometry.Type.Polygon - // || geom.getType() == Geometry.Type.Polyline) { - // CompareGeometryContent((MultiPath) geom, buf, sz); - // } - // } - // - // void CompareGeometryContent(MultiPath geom, Point buf[], int sz) { - // // Go through the geometry parts - // int j = 0; - // for (int ipart = 0; ipart < geom.getPathCount(); ipart++) { - // int start = geom.getPathStart(ipart); - // for (int i = 0; i < geom.getPathSize(ipart); i++, j++) { - // Point point = geom.getPoint(start + i); - // Assert.assertTrue(point.getX() == buf[j].getX()); - // Assert.assertTrue(point.getY() == buf[j].getY()); - // - // } - // } - // } + // void CompareGeometryContent(MultiVertexGeometry geom, Point buf[], int + // sz) { + // Assert.assertTrue(!geom.isEmpty()); + // Assert.assertTrue(geom.getPointCount() == sz); + // // Go through the geometry points + // for (int i = 0; i < geom.getPointCount(); i++) { + // Point point = new Point(); // not a right pattern the point has to + // // be created outside of the loop. + // geom.getPointByVal(i, point); + // Assert.assertTrue(point.getX() == buf[i].getX()); + // Assert.assertTrue(point.getY() == buf[i].getY()); + // Assert.assertTrue(point.getX() == buf[i].getX()); + // Assert.assertTrue(point.getY() == buf[i].getY()); + // } + // if (geom.getType() == Geometry.Type.Polygon + // || geom.getType() == Geometry.Type.Polyline) { + // CompareGeometryContent((MultiPath) geom, buf, sz); + // } + // } + // + // void CompareGeometryContent(MultiPath geom, Point buf[], int sz) { + // // Go through the geometry parts + // int j = 0; + // for (int ipart = 0; ipart < geom.getPathCount(); ipart++) { + // int start = geom.getPathStart(ipart); + // for (int i = 0; i < geom.getPathSize(ipart); i++, j++) { + // Point point = geom.getPoint(start + i); + // Assert.assertTrue(point.getX() == buf[j].getX()); + // Assert.assertTrue(point.getY() == buf[j].getY()); + // + // } + // } + // } - // void CompareGeometryContent(MultiVertexGeometry geom, Point3D buf[], int - // sz) { - // Assert.assertTrue(!geom.isEmpty()); - // Assert.assertTrue(geom.getPointCount() == sz); - // // Go through the geometry points - // for (int i = 0; i < geom.getPointCount(); i++) { - // Point point = new Point(); // not a right pattern the point has to - // // be created outside of the loop. - // geom.getPointByVal(i, point); - // Assert.assertTrue(point.getX() == buf[i].x); - // Assert.assertTrue(point.getY() == buf[i].y); - // Assert.assertTrue(point.getZ() == buf[i].z); - // Point3D pt = point.getXYZ(); - // Assert.assertTrue(pt.x == buf[i].x); - // Assert.assertTrue(pt.y == buf[i].y); - // Assert.assertTrue(pt.z == buf[i].z); - // } - // - // { - // MultiVertexGeometryImpl mpGeom = (MultiVertexGeometryImpl) geom - // ._getImpl(); - // AttributeStreamOfDbl streamPos = (AttributeStreamOfDbl) mpGeom - // .getAttributeStreamRef(VertexDescription.Semantics.POSITION); - // AttributeStreamOfDbl streamZ = (AttributeStreamOfDbl) mpGeom - // .getAttributeStreamRef(VertexDescription.Semantics.Z); - // for (int i = 0; i < geom.getPointCount(); i++) { - // double x = streamPos.read(2 * i); - // double y = streamPos.read(2 * i + 1); - // double z = streamZ.read(i); - // - // Assert.assertTrue(x == buf[i].x); - // Assert.assertTrue(y == buf[i].y); - // Assert.assertTrue(z == buf[i].z); - // } - // } - // - // if (geom.getType() == Geometry.Type.Polygon - // || geom.getType() == Geometry.Type.Polyline) { - // CompareGeometryContent((MultiPath) geom, buf, sz); - // } - // } + // void CompareGeometryContent(MultiVertexGeometry geom, Point3D buf[], int + // sz) { + // Assert.assertTrue(!geom.isEmpty()); + // Assert.assertTrue(geom.getPointCount() == sz); + // // Go through the geometry points + // for (int i = 0; i < geom.getPointCount(); i++) { + // Point point = new Point(); // not a right pattern the point has to + // // be created outside of the loop. + // geom.getPointByVal(i, point); + // Assert.assertTrue(point.getX() == buf[i].x); + // Assert.assertTrue(point.getY() == buf[i].y); + // Assert.assertTrue(point.getZ() == buf[i].z); + // Point3D pt = point.getXYZ(); + // Assert.assertTrue(pt.x == buf[i].x); + // Assert.assertTrue(pt.y == buf[i].y); + // Assert.assertTrue(pt.z == buf[i].z); + // } + // + // { + // MultiVertexGeometryImpl mpGeom = (MultiVertexGeometryImpl) geom + // ._getImpl(); + // AttributeStreamOfDbl streamPos = (AttributeStreamOfDbl) mpGeom + // .getAttributeStreamRef(VertexDescription.Semantics.POSITION); + // AttributeStreamOfDbl streamZ = (AttributeStreamOfDbl) mpGeom + // .getAttributeStreamRef(VertexDescription.Semantics.Z); + // for (int i = 0; i < geom.getPointCount(); i++) { + // double x = streamPos.read(2 * i); + // double y = streamPos.read(2 * i + 1); + // double z = streamZ.read(i); + // + // Assert.assertTrue(x == buf[i].x); + // Assert.assertTrue(y == buf[i].y); + // Assert.assertTrue(z == buf[i].z); + // } + // } + // + // if (geom.getType() == Geometry.Type.Polygon + // || geom.getType() == Geometry.Type.Polyline) { + // CompareGeometryContent((MultiPath) geom, buf, sz); + // } + // } - // void CompareGeometryContent(MultiPath geom, Point3D buf[], int sz) { - // Assert.assertTrue(!geom.isEmpty()); - // Assert.assertTrue(geom.getPointCount() == sz); - // - // // Go through the geometry parts - // int j = 0; - // for (int ipart = 0; ipart < geom.getPathCount(); ipart++) { - // int start = geom.getPathStart(ipart); - // for (int i = 0; i < geom.getPathSize(ipart); i++, j++) { - // double x = geom.getAttributeAsDbl( - // VertexDescription.Semantics.POSITION, i + start, 0); - // double y = geom.getAttributeAsDbl( - // VertexDescription.Semantics.POSITION, i + start, 1); - // double z = geom.getAttributeAsDbl( - // VertexDescription.Semantics.Z, i + start, 0); - // Assert.assertTrue(x == buf[j].x); - // Assert.assertTrue(y == buf[j].y); - // Assert.assertTrue(z == buf[j].z); - // - // Point point = new Point(); // not a right pattern the point has - // // to be created outside of the - // // loop. - // geom.getPointByVal(start + i, point); - // Assert.assertTrue(point.getX() == buf[j].x); - // Assert.assertTrue(point.getY() == buf[j].y); - // Assert.assertTrue(point.getZ() == buf[j].z); - // Point3D pt = point.getXYZ(); - // Assert.assertTrue(pt.x == buf[j].x); - // Assert.assertTrue(pt.y == buf[j].y); - // Assert.assertTrue(pt.z == buf[j].z); - // } - // } - // } + // void CompareGeometryContent(MultiPath geom, Point3D buf[], int sz) { + // Assert.assertTrue(!geom.isEmpty()); + // Assert.assertTrue(geom.getPointCount() == sz); + // + // // Go through the geometry parts + // int j = 0; + // for (int ipart = 0; ipart < geom.getPathCount(); ipart++) { + // int start = geom.getPathStart(ipart); + // for (int i = 0; i < geom.getPathSize(ipart); i++, j++) { + // double x = geom.getAttributeAsDbl( + // VertexDescription.Semantics.POSITION, i + start, 0); + // double y = geom.getAttributeAsDbl( + // VertexDescription.Semantics.POSITION, i + start, 1); + // double z = geom.getAttributeAsDbl( + // VertexDescription.Semantics.Z, i + start, 0); + // Assert.assertTrue(x == buf[j].x); + // Assert.assertTrue(y == buf[j].y); + // Assert.assertTrue(z == buf[j].z); + // + // Point point = new Point(); // not a right pattern the point has + // // to be created outside of the + // // loop. + // geom.getPointByVal(start + i, point); + // Assert.assertTrue(point.getX() == buf[j].x); + // Assert.assertTrue(point.getY() == buf[j].y); + // Assert.assertTrue(point.getZ() == buf[j].z); + // Point3D pt = point.getXYZ(); + // Assert.assertTrue(pt.x == buf[j].x); + // Assert.assertTrue(pt.y == buf[j].y); + // Assert.assertTrue(pt.z == buf[j].z); + // } + // } + // } - // void CompareGeometryContent(MultiVertexGeometry geom1, - // MultiVertexGeometry geom2) { - // // Geometry types - // Assert.assertTrue(geom1.getType() == geom2.getType()); - // - // // Envelopes - // Envelope env1 = new Envelope(); - // geom1.queryEnvelope(env1); - // - // Envelope env2 = new Envelope(); - // geom2.queryEnvelope(env2); - // - // Assert.assertTrue(env1.getXMin() == env2.getXMin() - // && env1.getXMax() == env2.getXMax() - // && env1.getYMin() == env2.getYMin() - // && env1.getYMax() == env2.getYMax()); - // - // int type = geom1.getType(); - // if (type == Geometry.Type.Polyline || type == Geometry.Type.Polygon) { - // // Part Count - // int partCount1 = ((MultiPath) geom1).getPathCount(); - // int partCount2 = ((MultiPath) geom2).getPathCount(); - // Assert.assertTrue(partCount1 == partCount2); - // - // // Part indices - // for (int i = 0; i < partCount1; i++) { - // int start1 = ((MultiPath) geom1).getPathStart(i); - // int start2 = ((MultiPath) geom2).getPathStart(i); - // Assert.assertTrue(start1 == start2); - // int end1 = ((MultiPath) geom1).getPathEnd(i); - // int end2 = ((MultiPath) geom2).getPathEnd(i); - // Assert.assertTrue(end1 == end2); - // } - // } - // - // // Point count - // int pointCount1 = geom1.getPointCount(); - // int pointCount2 = geom2.getPointCount(); - // Assert.assertTrue(pointCount1 == pointCount2); - // - // if (type == Geometry.Type.MultiPoint || type == Geometry.Type.Polyline - // || type == Geometry.Type.Polygon) { - // MultiVertexGeometryImpl mpGeom1 = (MultiVertexGeometryImpl) geom1 - // ._getImpl(); - // MultiVertexGeometryImpl mpGeom2 = (MultiVertexGeometryImpl) geom2 - // ._getImpl(); - // // POSITION - // AttributeStreamBase positionStream1 = mpGeom1 - // .getAttributeStreamRef(VertexDescription.Semantics.POSITION); - // AttributeStreamOfDbl position1 = (AttributeStreamOfDbl) positionStream1; - // - // AttributeStreamBase positionStream2 = mpGeom2 - // .getAttributeStreamRef(VertexDescription.Semantics.POSITION); - // AttributeStreamOfDbl position2 = (AttributeStreamOfDbl) positionStream2; - // - // for (int i = 0; i < pointCount1; i++) { - // double x1 = position1.read(2 * i); - // double x2 = position2.read(2 * i); - // Assert.assertTrue(x1 == x2); - // - // double y1 = position1.read(2 * i + 1); - // double y2 = position2.read(2 * i + 1); - // Assert.assertTrue(y1 == y2); - // } - // - // // Zs - // boolean bHasZs1 = mpGeom1 - // .hasAttribute(VertexDescription.Semantics.Z); - // boolean bHasZs2 = mpGeom2 - // .hasAttribute(VertexDescription.Semantics.Z); - // Assert.assertTrue(bHasZs1 == bHasZs2); - // - // if (bHasZs1) { - // AttributeStreamBase zStream1 = mpGeom1 - // .getAttributeStreamRef(VertexDescription.Semantics.Z); - // AttributeStreamOfDbl zs1 = (AttributeStreamOfDbl) zStream1; - // - // AttributeStreamBase zStream2 = mpGeom2 - // .getAttributeStreamRef(VertexDescription.Semantics.Z); - // AttributeStreamOfDbl zs2 = (AttributeStreamOfDbl) zStream2; - // - // for (int i = 0; i < pointCount1; i++) { - // double z1 = zs1.read(i); - // double z2 = zs2.read(i); - // Assert.assertTrue(z1 == z2); - // } - // } - // - // // Ms - // boolean bHasMs1 = mpGeom1 - // .hasAttribute(VertexDescription.Semantics.M); - // boolean bHasMs2 = mpGeom2 - // .hasAttribute(VertexDescription.Semantics.M); - // Assert.assertTrue(bHasMs1 == bHasMs2); - // - // if (bHasMs1) { - // AttributeStreamBase mStream1 = mpGeom1 - // .getAttributeStreamRef(VertexDescription.Semantics.M); - // AttributeStreamOfDbl ms1 = (AttributeStreamOfDbl) mStream1; - // - // AttributeStreamBase mStream2 = mpGeom2 - // .getAttributeStreamRef(VertexDescription.Semantics.M); - // AttributeStreamOfDbl ms2 = (AttributeStreamOfDbl) mStream2; - // - // for (int i = 0; i < pointCount1; i++) { - // double m1 = ms1.read(i); - // double m2 = ms2.read(i); - // Assert.assertTrue(m1 == m2); - // } - // } - // - // // IDs - // boolean bHasIDs1 = mpGeom1 - // .hasAttribute(VertexDescription.Semantics.ID); - // boolean bHasIDs2 = mpGeom2 - // .hasAttribute(VertexDescription.Semantics.ID); - // Assert.assertTrue(bHasIDs1 == bHasIDs2); - // - // if (bHasIDs1) { - // AttributeStreamBase idStream1 = mpGeom1 - // .getAttributeStreamRef(VertexDescription.Semantics.ID); - // AttributeStreamOfInt32 ids1 = (AttributeStreamOfInt32) idStream1; - // - // AttributeStreamBase idStream2 = mpGeom2 - // .getAttributeStreamRef(VertexDescription.Semantics.ID); - // AttributeStreamOfInt32 ids2 = (AttributeStreamOfInt32) idStream2; - // - // for (int i = 0; i < pointCount1; i++) { - // int id1 = ids1.read(i); - // int id2 = ids2.read(i); - // Assert.assertTrue(id1 == id2); - // } - // } - // } - // } - // - // void SimpleTest(Geometry point) { - // Assert.assertTrue(point != null); - // point.addAttribute(VertexDescription.Semantics.Z); - // Assert.assertTrue(point - // .hasAttribute(VertexDescription.Semantics.POSITION)); - // Assert.assertTrue(point.hasAttribute(VertexDescription.Semantics.Z)); - // point.addAttribute(VertexDescription.Semantics.Z);// duplicate call - // Assert.assertTrue(point.getDescription().getAttributeCount() == 2); - // Assert - // .assertTrue(point.getDescription().getSemantics(1) == - // VertexDescription.Semantics.Z); - // point.dropAttribute(VertexDescription.Semantics.Z); - // Assert.assertTrue(!point.hasAttribute(VertexDescription.Semantics.Z)); - // point.dropAttribute(VertexDescription.Semantics.Z);// duplicate call - // Assert.assertTrue(!point.hasAttribute(VertexDescription.Semantics.Z)); - // Assert.assertTrue(point.getDescription().getAttributeCount() == 1); - // Assert - // .assertTrue(point.getDescription().getSemantics(0) == - // VertexDescription.Semantics.POSITION); - // - // point.addAttribute(VertexDescription.Semantics.M); - // Assert.assertTrue(point - // .hasAttribute(VertexDescription.Semantics.POSITION)); - // Assert.assertTrue(!point.hasAttribute(VertexDescription.Semantics.Z)); - // Assert.assertTrue(point.hasAttribute(VertexDescription.Semantics.M)); - // point.dropAttribute(VertexDescription.Semantics.M); - // Assert.assertTrue(!point.hasAttribute(VertexDescription.Semantics.M)); - // - // point.addAttribute(VertexDescription.Semantics.ID); - // Assert.assertTrue(point - // .hasAttribute(VertexDescription.Semantics.POSITION)); - // Assert.assertTrue(!point.hasAttribute(VertexDescription.Semantics.Z)); - // Assert.assertTrue(!point.hasAttribute(VertexDescription.Semantics.M)); - // point.dropAttribute(VertexDescription.Semantics.ID); - // Assert.assertTrue(!point.hasAttribute(VertexDescription.Semantics.ID)); - // - // // TEST_ASSERT(point->IsEmpty()); - // // TEST_ASSERT(point->GetPointCount() == 0); - // // TEST_ASSERT(point->GetPartCount() == 0); - // - // point = null; - // Assert.assertTrue(point == null); - // } + // void CompareGeometryContent(MultiVertexGeometry geom1, + // MultiVertexGeometry geom2) { + // // Geometry types + // Assert.assertTrue(geom1.getType() == geom2.getType()); + // + // // Envelopes + // Envelope env1 = new Envelope(); + // geom1.queryEnvelope(env1); + // + // Envelope env2 = new Envelope(); + // geom2.queryEnvelope(env2); + // + // Assert.assertTrue(env1.getXMin() == env2.getXMin() + // && env1.getXMax() == env2.getXMax() + // && env1.getYMin() == env2.getYMin() + // && env1.getYMax() == env2.getYMax()); + // + // int type = geom1.getType(); + // if (type == Geometry.Type.Polyline || type == Geometry.Type.Polygon) { + // // Part Count + // int partCount1 = ((MultiPath) geom1).getPathCount(); + // int partCount2 = ((MultiPath) geom2).getPathCount(); + // Assert.assertTrue(partCount1 == partCount2); + // + // // Part indices + // for (int i = 0; i < partCount1; i++) { + // int start1 = ((MultiPath) geom1).getPathStart(i); + // int start2 = ((MultiPath) geom2).getPathStart(i); + // Assert.assertTrue(start1 == start2); + // int end1 = ((MultiPath) geom1).getPathEnd(i); + // int end2 = ((MultiPath) geom2).getPathEnd(i); + // Assert.assertTrue(end1 == end2); + // } + // } + // + // // Point count + // int pointCount1 = geom1.getPointCount(); + // int pointCount2 = geom2.getPointCount(); + // Assert.assertTrue(pointCount1 == pointCount2); + // + // if (type == Geometry.Type.MultiPoint || type == Geometry.Type.Polyline + // || type == Geometry.Type.Polygon) { + // MultiVertexGeometryImpl mpGeom1 = (MultiVertexGeometryImpl) geom1 + // ._getImpl(); + // MultiVertexGeometryImpl mpGeom2 = (MultiVertexGeometryImpl) geom2 + // ._getImpl(); + // // POSITION + // AttributeStreamBase positionStream1 = mpGeom1 + // .getAttributeStreamRef(VertexDescription.Semantics.POSITION); + // AttributeStreamOfDbl position1 = (AttributeStreamOfDbl) positionStream1; + // + // AttributeStreamBase positionStream2 = mpGeom2 + // .getAttributeStreamRef(VertexDescription.Semantics.POSITION); + // AttributeStreamOfDbl position2 = (AttributeStreamOfDbl) positionStream2; + // + // for (int i = 0; i < pointCount1; i++) { + // double x1 = position1.read(2 * i); + // double x2 = position2.read(2 * i); + // Assert.assertTrue(x1 == x2); + // + // double y1 = position1.read(2 * i + 1); + // double y2 = position2.read(2 * i + 1); + // Assert.assertTrue(y1 == y2); + // } + // + // // Zs + // boolean bHasZs1 = mpGeom1 + // .hasAttribute(VertexDescription.Semantics.Z); + // boolean bHasZs2 = mpGeom2 + // .hasAttribute(VertexDescription.Semantics.Z); + // Assert.assertTrue(bHasZs1 == bHasZs2); + // + // if (bHasZs1) { + // AttributeStreamBase zStream1 = mpGeom1 + // .getAttributeStreamRef(VertexDescription.Semantics.Z); + // AttributeStreamOfDbl zs1 = (AttributeStreamOfDbl) zStream1; + // + // AttributeStreamBase zStream2 = mpGeom2 + // .getAttributeStreamRef(VertexDescription.Semantics.Z); + // AttributeStreamOfDbl zs2 = (AttributeStreamOfDbl) zStream2; + // + // for (int i = 0; i < pointCount1; i++) { + // double z1 = zs1.read(i); + // double z2 = zs2.read(i); + // Assert.assertTrue(z1 == z2); + // } + // } + // + // // Ms + // boolean bHasMs1 = mpGeom1 + // .hasAttribute(VertexDescription.Semantics.M); + // boolean bHasMs2 = mpGeom2 + // .hasAttribute(VertexDescription.Semantics.M); + // Assert.assertTrue(bHasMs1 == bHasMs2); + // + // if (bHasMs1) { + // AttributeStreamBase mStream1 = mpGeom1 + // .getAttributeStreamRef(VertexDescription.Semantics.M); + // AttributeStreamOfDbl ms1 = (AttributeStreamOfDbl) mStream1; + // + // AttributeStreamBase mStream2 = mpGeom2 + // .getAttributeStreamRef(VertexDescription.Semantics.M); + // AttributeStreamOfDbl ms2 = (AttributeStreamOfDbl) mStream2; + // + // for (int i = 0; i < pointCount1; i++) { + // double m1 = ms1.read(i); + // double m2 = ms2.read(i); + // Assert.assertTrue(m1 == m2); + // } + // } + // + // // IDs + // boolean bHasIDs1 = mpGeom1 + // .hasAttribute(VertexDescription.Semantics.ID); + // boolean bHasIDs2 = mpGeom2 + // .hasAttribute(VertexDescription.Semantics.ID); + // Assert.assertTrue(bHasIDs1 == bHasIDs2); + // + // if (bHasIDs1) { + // AttributeStreamBase idStream1 = mpGeom1 + // .getAttributeStreamRef(VertexDescription.Semantics.ID); + // AttributeStreamOfInt32 ids1 = (AttributeStreamOfInt32) idStream1; + // + // AttributeStreamBase idStream2 = mpGeom2 + // .getAttributeStreamRef(VertexDescription.Semantics.ID); + // AttributeStreamOfInt32 ids2 = (AttributeStreamOfInt32) idStream2; + // + // for (int i = 0; i < pointCount1; i++) { + // int id1 = ids1.read(i); + // int id2 = ids2.read(i); + // Assert.assertTrue(id1 == id2); + // } + // } + // } + // } + // + // void SimpleTest(Geometry point) { + // Assert.assertTrue(point != null); + // point.addAttribute(VertexDescription.Semantics.Z); + // Assert.assertTrue(point + // .hasAttribute(VertexDescription.Semantics.POSITION)); + // Assert.assertTrue(point.hasAttribute(VertexDescription.Semantics.Z)); + // point.addAttribute(VertexDescription.Semantics.Z);// duplicate call + // Assert.assertTrue(point.getDescription().getAttributeCount() == 2); + // Assert + // .assertTrue(point.getDescription().getSemantics(1) == + // VertexDescription.Semantics.Z); + // point.dropAttribute(VertexDescription.Semantics.Z); + // Assert.assertTrue(!point.hasAttribute(VertexDescription.Semantics.Z)); + // point.dropAttribute(VertexDescription.Semantics.Z);// duplicate call + // Assert.assertTrue(!point.hasAttribute(VertexDescription.Semantics.Z)); + // Assert.assertTrue(point.getDescription().getAttributeCount() == 1); + // Assert + // .assertTrue(point.getDescription().getSemantics(0) == + // VertexDescription.Semantics.POSITION); + // + // point.addAttribute(VertexDescription.Semantics.M); + // Assert.assertTrue(point + // .hasAttribute(VertexDescription.Semantics.POSITION)); + // Assert.assertTrue(!point.hasAttribute(VertexDescription.Semantics.Z)); + // Assert.assertTrue(point.hasAttribute(VertexDescription.Semantics.M)); + // point.dropAttribute(VertexDescription.Semantics.M); + // Assert.assertTrue(!point.hasAttribute(VertexDescription.Semantics.M)); + // + // point.addAttribute(VertexDescription.Semantics.ID); + // Assert.assertTrue(point + // .hasAttribute(VertexDescription.Semantics.POSITION)); + // Assert.assertTrue(!point.hasAttribute(VertexDescription.Semantics.Z)); + // Assert.assertTrue(!point.hasAttribute(VertexDescription.Semantics.M)); + // point.dropAttribute(VertexDescription.Semantics.ID); + // Assert.assertTrue(!point.hasAttribute(VertexDescription.Semantics.ID)); + // + // // TEST_ASSERT(point->IsEmpty()); + // // TEST_ASSERT(point->GetPointCount() == 0); + // // TEST_ASSERT(point->GetPartCount() == 0); + // + // point = null; + // Assert.assertTrue(point == null); + // } } diff --git a/src/test/java/com/esri/core/geometry/TestAttributes.java b/src/test/java/com/esri/core/geometry/TestAttributes.java index f16ddb1a..7fc21555 100644 --- a/src/test/java/com/esri/core/geometry/TestAttributes.java +++ b/src/test/java/com/esri/core/geometry/TestAttributes.java @@ -29,319 +29,319 @@ public class TestAttributes extends TestCase { - @Test - public void testPoint() { - Point pt = new Point(); - pt.setXY(100, 200); - assertFalse(pt.hasAttribute(VertexDescription.Semantics.M)); - pt.addAttribute(VertexDescription.Semantics.M); - assertTrue(pt.hasAttribute(VertexDescription.Semantics.M)); - assertTrue(Double.isNaN(pt.getM())); - pt.setAttribute(VertexDescription.Semantics.M, 0, 13); - assertTrue(pt.getM() == 13); - pt.addAttribute(VertexDescription.Semantics.Z); - assertTrue(pt.getZ() == 0); - assertTrue(pt.getM() == 13); - pt.setAttribute(VertexDescription.Semantics.Z, 0, 11); - assertTrue(pt.getZ() == 11); - assertTrue(pt.getM() == 13); - pt.addAttribute(VertexDescription.Semantics.ID); - assertTrue(pt.getID() == 0); - assertTrue(pt.getZ() == 11); - assertTrue(pt.getM() == 13); - pt.setAttribute(VertexDescription.Semantics.ID, 0, 1); - assertTrue(pt.getID() == 1); - assertTrue(pt.getZ() == 11); - assertTrue(pt.getM() == 13); - pt.dropAttribute(VertexDescription.Semantics.M); - assertTrue(pt.getID() == 1); - assertTrue(pt.getZ() == 11); - assertFalse(pt.hasAttribute(VertexDescription.Semantics.M)); - - Point pt1 = new Point(); - assertFalse(pt1.hasAttribute(VertexDescription.Semantics.M)); - assertFalse(pt1.hasAttribute(VertexDescription.Semantics.Z)); - assertFalse(pt1.hasAttribute(VertexDescription.Semantics.ID)); - pt1.mergeVertexDescription(pt.getDescription()); - assertFalse(pt1.hasAttribute(VertexDescription.Semantics.M)); - assertTrue(pt1.hasAttribute(VertexDescription.Semantics.Z)); - assertTrue(pt1.hasAttribute(VertexDescription.Semantics.ID)); - } - - @Test - public void testEnvelope() { - Envelope env = new Envelope(); - env.setCoords(100, 200, 250, 300); - assertFalse(env.hasAttribute(VertexDescription.Semantics.M)); - env.addAttribute(VertexDescription.Semantics.M); - assertTrue(env.hasAttribute(VertexDescription.Semantics.M)); - assertTrue(env.queryInterval(VertexDescription.Semantics.M, 0).isEmpty()); - env.setInterval(VertexDescription.Semantics.M, 0, 1, 2); - assertTrue(env.queryInterval(VertexDescription.Semantics.M, 0).vmin == 1); - assertTrue(env.queryInterval(VertexDescription.Semantics.M, 0).vmax == 2); - - assertFalse(env.hasAttribute(VertexDescription.Semantics.Z)); - env.addAttribute(VertexDescription.Semantics.Z); - assertTrue(env.hasAttribute(VertexDescription.Semantics.Z)); - assertTrue(env.queryInterval(VertexDescription.Semantics.Z, 0).vmin == 0); - assertTrue(env.queryInterval(VertexDescription.Semantics.Z, 0).vmax == 0); - env.setInterval(VertexDescription.Semantics.Z, 0, 3, 4); - assertTrue(env.queryInterval(VertexDescription.Semantics.Z, 0).vmin == 3); - assertTrue(env.queryInterval(VertexDescription.Semantics.Z, 0).vmax == 4); - - - assertFalse(env.hasAttribute(VertexDescription.Semantics.ID)); - env.addAttribute(VertexDescription.Semantics.ID); - assertTrue(env.hasAttribute(VertexDescription.Semantics.ID)); - assertTrue(env.queryInterval(VertexDescription.Semantics.ID, 0).vmin == 0); - assertTrue(env.queryInterval(VertexDescription.Semantics.ID, 0).vmax == 0); - env.setInterval(VertexDescription.Semantics.ID, 0, 5, 6); - assertTrue(env.queryInterval(VertexDescription.Semantics.ID, 0).vmin == 5); - assertTrue(env.queryInterval(VertexDescription.Semantics.ID, 0).vmax == 6); - assertTrue(env.queryInterval(VertexDescription.Semantics.Z, 0).vmin == 3); - assertTrue(env.queryInterval(VertexDescription.Semantics.Z, 0).vmax == 4); - assertTrue(env.queryInterval(VertexDescription.Semantics.M, 0).vmin == 1); - assertTrue(env.queryInterval(VertexDescription.Semantics.M, 0).vmax == 2); - - env.dropAttribute(VertexDescription.Semantics.M); - assertFalse(env.hasAttribute(VertexDescription.Semantics.M)); - assertTrue(env.queryInterval(VertexDescription.Semantics.ID, 0).vmin == 5); - assertTrue(env.queryInterval(VertexDescription.Semantics.ID, 0).vmax == 6); - assertTrue(env.queryInterval(VertexDescription.Semantics.Z, 0).vmin == 3); - assertTrue(env.queryInterval(VertexDescription.Semantics.Z, 0).vmax == 4); - - Envelope env1 = new Envelope(); - env.copyTo(env1); - assertFalse(env1.hasAttribute(VertexDescription.Semantics.M)); - assertTrue(env1.queryInterval(VertexDescription.Semantics.ID, 0).vmin == 5); - assertTrue(env1.queryInterval(VertexDescription.Semantics.ID, 0).vmax == 6); - assertTrue(env1.queryInterval(VertexDescription.Semantics.Z, 0).vmin == 3); - assertTrue(env1.queryInterval(VertexDescription.Semantics.Z, 0).vmax == 4); - } - - @Test - public void testLine() { - Line env = new Line(); - env.setStartXY(100, 200); - env.setEndXY(250, 300); - assertFalse(env.hasAttribute(VertexDescription.Semantics.M)); - env.addAttribute(VertexDescription.Semantics.M); - assertTrue(env.hasAttribute(VertexDescription.Semantics.M)); - env.setStartAttribute(VertexDescription.Semantics.M, 0, 1); - env.setEndAttribute(VertexDescription.Semantics.M, 0, 2); - assertTrue(env.getStartAttributeAsDbl(VertexDescription.Semantics.M, 0) == 1); - assertTrue(env.getEndAttributeAsDbl(VertexDescription.Semantics.M, 0) == 2); - - assertFalse(env.hasAttribute(VertexDescription.Semantics.Z)); - env.addAttribute(VertexDescription.Semantics.Z); - assertTrue(env.hasAttribute(VertexDescription.Semantics.Z)); - env.setStartAttribute(VertexDescription.Semantics.Z, 0, 3); - env.setEndAttribute(VertexDescription.Semantics.Z, 0, 4); - assertTrue(env.getStartAttributeAsDbl(VertexDescription.Semantics.Z, 0) == 3); - assertTrue(env.getEndAttributeAsDbl(VertexDescription.Semantics.Z, 0) == 4); - - - assertFalse(env.hasAttribute(VertexDescription.Semantics.ID)); - env.addAttribute(VertexDescription.Semantics.ID); - assertTrue(env.hasAttribute(VertexDescription.Semantics.ID)); - env.setStartAttribute(VertexDescription.Semantics.ID, 0, 5); - env.setEndAttribute(VertexDescription.Semantics.ID, 0, 6); - assertTrue(env.getStartAttributeAsDbl(VertexDescription.Semantics.ID, 0) == 5); - assertTrue(env.getEndAttributeAsDbl(VertexDescription.Semantics.ID, 0) == 6); - - assertTrue(env.getStartAttributeAsDbl(VertexDescription.Semantics.M, 0) == 1); - assertTrue(env.getEndAttributeAsDbl(VertexDescription.Semantics.M, 0) == 2); - assertTrue(env.getStartAttributeAsDbl(VertexDescription.Semantics.Z, 0) == 3); - assertTrue(env.getEndAttributeAsDbl(VertexDescription.Semantics.Z, 0) == 4); - - env.dropAttribute(VertexDescription.Semantics.M); - assertFalse(env.hasAttribute(VertexDescription.Semantics.M)); - assertTrue(env.getStartAttributeAsDbl(VertexDescription.Semantics.ID, 0) == 5); - assertTrue(env.getEndAttributeAsDbl(VertexDescription.Semantics.ID, 0) == 6); - assertTrue(env.getStartAttributeAsDbl(VertexDescription.Semantics.Z, 0) == 3); - assertTrue(env.getEndAttributeAsDbl(VertexDescription.Semantics.Z, 0) == 4); - - Line env1 = new Line(); - env.copyTo(env1); - assertFalse(env1.hasAttribute(VertexDescription.Semantics.M)); - assertTrue(env.getStartAttributeAsDbl(VertexDescription.Semantics.ID, 0) == 5); - assertTrue(env.getEndAttributeAsDbl(VertexDescription.Semantics.ID, 0) == 6); - assertTrue(env.getStartAttributeAsDbl(VertexDescription.Semantics.Z, 0) == 3); - assertTrue(env.getEndAttributeAsDbl(VertexDescription.Semantics.Z, 0) == 4); - } - - @Test - public void testMultiPoint() { - MultiPoint mp = new MultiPoint(); - mp.add(new Point(100, 200)); - mp.add(new Point(101, 201)); - mp.add(new Point(102, 202)); - assertFalse(mp.hasAttribute(VertexDescription.Semantics.M)); - mp.addAttribute(VertexDescription.Semantics.M); - assertTrue(mp.hasAttribute(VertexDescription.Semantics.M)); - assertTrue(Double.isNaN(mp.getAttributeAsDbl(VertexDescription.Semantics.M, 0, 0))); - assertTrue(Double.isNaN(mp.getAttributeAsDbl(VertexDescription.Semantics.M, 1, 0))); - assertTrue(Double.isNaN(mp.getAttributeAsDbl(VertexDescription.Semantics.M, 2, 0))); - mp.setAttribute(VertexDescription.Semantics.M, 0, 0, 1); - mp.setAttribute(VertexDescription.Semantics.M, 1, 0, 2); - mp.setAttribute(VertexDescription.Semantics.M, 2, 0, 3); - assertTrue(mp.getAttributeAsDbl(VertexDescription.Semantics.M, 0, 0) == 1); - assertTrue(mp.getAttributeAsDbl(VertexDescription.Semantics.M, 1, 0) == 2); - assertTrue(mp.getAttributeAsDbl(VertexDescription.Semantics.M, 2, 0) == 3); - - assertFalse(mp.hasAttribute(VertexDescription.Semantics.Z)); - mp.addAttribute(VertexDescription.Semantics.Z); - assertTrue(mp.hasAttribute(VertexDescription.Semantics.Z)); - assertTrue(mp.getAttributeAsDbl(VertexDescription.Semantics.Z, 0, 0) == 0); - assertTrue(mp.getAttributeAsDbl(VertexDescription.Semantics.Z, 1, 0) == 0); - assertTrue(mp.getAttributeAsDbl(VertexDescription.Semantics.Z, 2, 0) == 0); - mp.setAttribute(VertexDescription.Semantics.Z, 0, 0, 11); - mp.setAttribute(VertexDescription.Semantics.Z, 1, 0, 21); - mp.setAttribute(VertexDescription.Semantics.Z, 2, 0, 31); - assertTrue(mp.getAttributeAsDbl(VertexDescription.Semantics.M, 0, 0) == 1); - assertTrue(mp.getAttributeAsDbl(VertexDescription.Semantics.M, 1, 0) == 2); - assertTrue(mp.getAttributeAsDbl(VertexDescription.Semantics.M, 2, 0) == 3); - assertTrue(mp.getAttributeAsDbl(VertexDescription.Semantics.Z, 0, 0) == 11); - assertTrue(mp.getAttributeAsDbl(VertexDescription.Semantics.Z, 1, 0) == 21); - assertTrue(mp.getAttributeAsDbl(VertexDescription.Semantics.Z, 2, 0) == 31); - - assertFalse(mp.hasAttribute(VertexDescription.Semantics.ID)); - mp.addAttribute(VertexDescription.Semantics.ID); - assertTrue(mp.hasAttribute(VertexDescription.Semantics.ID)); - assertTrue(mp.getAttributeAsDbl(VertexDescription.Semantics.ID, 0, 0) == 0); - assertTrue(mp.getAttributeAsDbl(VertexDescription.Semantics.ID, 1, 0) == 0); - assertTrue(mp.getAttributeAsDbl(VertexDescription.Semantics.ID, 2, 0) == 0); - mp.setAttribute(VertexDescription.Semantics.ID, 0, 0, -11); - mp.setAttribute(VertexDescription.Semantics.ID, 1, 0, -21); - mp.setAttribute(VertexDescription.Semantics.ID, 2, 0, -31); - assertTrue(mp.getAttributeAsDbl(VertexDescription.Semantics.M, 0, 0) == 1); - assertTrue(mp.getAttributeAsDbl(VertexDescription.Semantics.M, 1, 0) == 2); - assertTrue(mp.getAttributeAsDbl(VertexDescription.Semantics.M, 2, 0) == 3); - assertTrue(mp.getAttributeAsDbl(VertexDescription.Semantics.Z, 0, 0) == 11); - assertTrue(mp.getAttributeAsDbl(VertexDescription.Semantics.Z, 1, 0) == 21); - assertTrue(mp.getAttributeAsDbl(VertexDescription.Semantics.Z, 2, 0) == 31); - assertTrue(mp.getAttributeAsDbl(VertexDescription.Semantics.ID, 0, 0) == -11); - assertTrue(mp.getAttributeAsDbl(VertexDescription.Semantics.ID, 1, 0) == -21); - assertTrue(mp.getAttributeAsDbl(VertexDescription.Semantics.ID, 2, 0) == -31); - - mp.dropAttribute(VertexDescription.Semantics.M); - assertFalse(mp.hasAttribute(VertexDescription.Semantics.M)); - assertTrue(mp.getAttributeAsDbl(VertexDescription.Semantics.Z, 0, 0) == 11); - assertTrue(mp.getAttributeAsDbl(VertexDescription.Semantics.Z, 1, 0) == 21); - assertTrue(mp.getAttributeAsDbl(VertexDescription.Semantics.Z, 2, 0) == 31); - assertTrue(mp.getAttributeAsDbl(VertexDescription.Semantics.ID, 0, 0) == -11); - assertTrue(mp.getAttributeAsDbl(VertexDescription.Semantics.ID, 1, 0) == -21); - assertTrue(mp.getAttributeAsDbl(VertexDescription.Semantics.ID, 2, 0) == -31); - - MultiPoint mp1 = new MultiPoint(); - mp.copyTo(mp1); - assertFalse(mp1.hasAttribute(VertexDescription.Semantics.M)); - assertTrue(mp1.getAttributeAsDbl(VertexDescription.Semantics.Z, 0, 0) == 11); - assertTrue(mp1.getAttributeAsDbl(VertexDescription.Semantics.Z, 1, 0) == 21); - assertTrue(mp1.getAttributeAsDbl(VertexDescription.Semantics.Z, 2, 0) == 31); - assertTrue(mp1.getAttributeAsDbl(VertexDescription.Semantics.ID, 0, 0) == -11); - assertTrue(mp1.getAttributeAsDbl(VertexDescription.Semantics.ID, 1, 0) == -21); - assertTrue(mp1.getAttributeAsDbl(VertexDescription.Semantics.ID, 2, 0) == -31); - - mp1.dropAllAttributes(); - mp1.mergeVertexDescription(mp.getDescription()); - assertTrue(mp1.getAttributeAsDbl(VertexDescription.Semantics.Z, 0, 0) == 0); - assertTrue(mp1.getAttributeAsDbl(VertexDescription.Semantics.Z, 1, 0) == 0); - assertTrue(mp1.getAttributeAsDbl(VertexDescription.Semantics.Z, 2, 0) == 0); - assertTrue(Double.isNaN(mp1.getAttributeAsDbl(VertexDescription.Semantics.M, 0, 0))); - assertTrue(Double.isNaN(mp1.getAttributeAsDbl(VertexDescription.Semantics.M, 1, 0))); - assertTrue(Double.isNaN(mp1.getAttributeAsDbl(VertexDescription.Semantics.M, 2, 0))); - assertTrue(mp1.getAttributeAsDbl(VertexDescription.Semantics.ID, 0, 0) == 0); - assertTrue(mp1.getAttributeAsDbl(VertexDescription.Semantics.ID, 1, 0) == 0); - assertTrue(mp1.getAttributeAsDbl(VertexDescription.Semantics.ID, 2, 0) == 0); - - } - - @Test - public void testPolygon() { - Polygon mp = new Polygon(); - mp.startPath(new Point(100, 200)); - mp.lineTo(new Point(101, 201)); - mp.lineTo(new Point(102, 202)); - assertFalse(mp.hasAttribute(VertexDescription.Semantics.M)); - mp.addAttribute(VertexDescription.Semantics.M); - assertTrue(mp.hasAttribute(VertexDescription.Semantics.M)); - assertTrue(Double.isNaN(mp.getAttributeAsDbl(VertexDescription.Semantics.M, 0, 0))); - assertTrue(Double.isNaN(mp.getAttributeAsDbl(VertexDescription.Semantics.M, 1, 0))); - assertTrue(Double.isNaN(mp.getAttributeAsDbl(VertexDescription.Semantics.M, 2, 0))); - mp.setAttribute(VertexDescription.Semantics.M, 0, 0, 1); - mp.setAttribute(VertexDescription.Semantics.M, 1, 0, 2); - mp.setAttribute(VertexDescription.Semantics.M, 2, 0, 3); - assertTrue(mp.getAttributeAsDbl(VertexDescription.Semantics.M, 0, 0) == 1); - assertTrue(mp.getAttributeAsDbl(VertexDescription.Semantics.M, 1, 0) == 2); - assertTrue(mp.getAttributeAsDbl(VertexDescription.Semantics.M, 2, 0) == 3); - - assertFalse(mp.hasAttribute(VertexDescription.Semantics.Z)); - mp.addAttribute(VertexDescription.Semantics.Z); - assertTrue(mp.hasAttribute(VertexDescription.Semantics.Z)); - assertTrue(mp.getAttributeAsDbl(VertexDescription.Semantics.Z, 0, 0) == 0); - assertTrue(mp.getAttributeAsDbl(VertexDescription.Semantics.Z, 1, 0) == 0); - assertTrue(mp.getAttributeAsDbl(VertexDescription.Semantics.Z, 2, 0) == 0); - mp.setAttribute(VertexDescription.Semantics.Z, 0, 0, 11); - mp.setAttribute(VertexDescription.Semantics.Z, 1, 0, 21); - mp.setAttribute(VertexDescription.Semantics.Z, 2, 0, 31); - assertTrue(mp.getAttributeAsDbl(VertexDescription.Semantics.M, 0, 0) == 1); - assertTrue(mp.getAttributeAsDbl(VertexDescription.Semantics.M, 1, 0) == 2); - assertTrue(mp.getAttributeAsDbl(VertexDescription.Semantics.M, 2, 0) == 3); - assertTrue(mp.getAttributeAsDbl(VertexDescription.Semantics.Z, 0, 0) == 11); - assertTrue(mp.getAttributeAsDbl(VertexDescription.Semantics.Z, 1, 0) == 21); - assertTrue(mp.getAttributeAsDbl(VertexDescription.Semantics.Z, 2, 0) == 31); - - assertFalse(mp.hasAttribute(VertexDescription.Semantics.ID)); - mp.addAttribute(VertexDescription.Semantics.ID); - assertTrue(mp.hasAttribute(VertexDescription.Semantics.ID)); - assertTrue(mp.getAttributeAsDbl(VertexDescription.Semantics.ID, 0, 0) == 0); - assertTrue(mp.getAttributeAsDbl(VertexDescription.Semantics.ID, 1, 0) == 0); - assertTrue(mp.getAttributeAsDbl(VertexDescription.Semantics.ID, 2, 0) == 0); - mp.setAttribute(VertexDescription.Semantics.ID, 0, 0, -11); - mp.setAttribute(VertexDescription.Semantics.ID, 1, 0, -21); - mp.setAttribute(VertexDescription.Semantics.ID, 2, 0, -31); - assertTrue(mp.getAttributeAsDbl(VertexDescription.Semantics.M, 0, 0) == 1); - assertTrue(mp.getAttributeAsDbl(VertexDescription.Semantics.M, 1, 0) == 2); - assertTrue(mp.getAttributeAsDbl(VertexDescription.Semantics.M, 2, 0) == 3); - assertTrue(mp.getAttributeAsDbl(VertexDescription.Semantics.Z, 0, 0) == 11); - assertTrue(mp.getAttributeAsDbl(VertexDescription.Semantics.Z, 1, 0) == 21); - assertTrue(mp.getAttributeAsDbl(VertexDescription.Semantics.Z, 2, 0) == 31); - assertTrue(mp.getAttributeAsDbl(VertexDescription.Semantics.ID, 0, 0) == -11); - assertTrue(mp.getAttributeAsDbl(VertexDescription.Semantics.ID, 1, 0) == -21); - assertTrue(mp.getAttributeAsDbl(VertexDescription.Semantics.ID, 2, 0) == -31); - - mp.dropAttribute(VertexDescription.Semantics.M); - assertFalse(mp.hasAttribute(VertexDescription.Semantics.M)); - assertTrue(mp.getAttributeAsDbl(VertexDescription.Semantics.Z, 0, 0) == 11); - assertTrue(mp.getAttributeAsDbl(VertexDescription.Semantics.Z, 1, 0) == 21); - assertTrue(mp.getAttributeAsDbl(VertexDescription.Semantics.Z, 2, 0) == 31); - assertTrue(mp.getAttributeAsDbl(VertexDescription.Semantics.ID, 0, 0) == -11); - assertTrue(mp.getAttributeAsDbl(VertexDescription.Semantics.ID, 1, 0) == -21); - assertTrue(mp.getAttributeAsDbl(VertexDescription.Semantics.ID, 2, 0) == -31); - - Polygon mp1 = new Polygon(); - mp.copyTo(mp1); - assertFalse(mp1.hasAttribute(VertexDescription.Semantics.M)); - assertTrue(mp1.getAttributeAsDbl(VertexDescription.Semantics.Z, 0, 0) == 11); - assertTrue(mp1.getAttributeAsDbl(VertexDescription.Semantics.Z, 1, 0) == 21); - assertTrue(mp1.getAttributeAsDbl(VertexDescription.Semantics.Z, 2, 0) == 31); - assertTrue(mp1.getAttributeAsDbl(VertexDescription.Semantics.ID, 0, 0) == -11); - assertTrue(mp1.getAttributeAsDbl(VertexDescription.Semantics.ID, 1, 0) == -21); - assertTrue(mp1.getAttributeAsDbl(VertexDescription.Semantics.ID, 2, 0) == -31); - - mp1.dropAllAttributes(); - mp1.mergeVertexDescription(mp.getDescription()); - assertTrue(mp1.getAttributeAsDbl(VertexDescription.Semantics.Z, 0, 0) == 0); - assertTrue(mp1.getAttributeAsDbl(VertexDescription.Semantics.Z, 1, 0) == 0); - assertTrue(mp1.getAttributeAsDbl(VertexDescription.Semantics.Z, 2, 0) == 0); - assertTrue(Double.isNaN(mp1.getAttributeAsDbl(VertexDescription.Semantics.M, 0, 0))); - assertTrue(Double.isNaN(mp1.getAttributeAsDbl(VertexDescription.Semantics.M, 1, 0))); - assertTrue(Double.isNaN(mp1.getAttributeAsDbl(VertexDescription.Semantics.M, 2, 0))); - assertTrue(mp1.getAttributeAsDbl(VertexDescription.Semantics.ID, 0, 0) == 0); - assertTrue(mp1.getAttributeAsDbl(VertexDescription.Semantics.ID, 1, 0) == 0); - assertTrue(mp1.getAttributeAsDbl(VertexDescription.Semantics.ID, 2, 0) == 0); - - } + @Test + public void testPoint() { + Point pt = new Point(); + pt.setXY(100, 200); + assertFalse(pt.hasAttribute(VertexDescription.Semantics.M)); + pt.addAttribute(VertexDescription.Semantics.M); + assertTrue(pt.hasAttribute(VertexDescription.Semantics.M)); + assertTrue(Double.isNaN(pt.getM())); + pt.setAttribute(VertexDescription.Semantics.M, 0, 13); + assertTrue(pt.getM() == 13); + pt.addAttribute(VertexDescription.Semantics.Z); + assertTrue(pt.getZ() == 0); + assertTrue(pt.getM() == 13); + pt.setAttribute(VertexDescription.Semantics.Z, 0, 11); + assertTrue(pt.getZ() == 11); + assertTrue(pt.getM() == 13); + pt.addAttribute(VertexDescription.Semantics.ID); + assertTrue(pt.getID() == 0); + assertTrue(pt.getZ() == 11); + assertTrue(pt.getM() == 13); + pt.setAttribute(VertexDescription.Semantics.ID, 0, 1); + assertTrue(pt.getID() == 1); + assertTrue(pt.getZ() == 11); + assertTrue(pt.getM() == 13); + pt.dropAttribute(VertexDescription.Semantics.M); + assertTrue(pt.getID() == 1); + assertTrue(pt.getZ() == 11); + assertFalse(pt.hasAttribute(VertexDescription.Semantics.M)); + + Point pt1 = new Point(); + assertFalse(pt1.hasAttribute(VertexDescription.Semantics.M)); + assertFalse(pt1.hasAttribute(VertexDescription.Semantics.Z)); + assertFalse(pt1.hasAttribute(VertexDescription.Semantics.ID)); + pt1.mergeVertexDescription(pt.getDescription()); + assertFalse(pt1.hasAttribute(VertexDescription.Semantics.M)); + assertTrue(pt1.hasAttribute(VertexDescription.Semantics.Z)); + assertTrue(pt1.hasAttribute(VertexDescription.Semantics.ID)); + } + + @Test + public void testEnvelope() { + Envelope env = new Envelope(); + env.setCoords(100, 200, 250, 300); + assertFalse(env.hasAttribute(VertexDescription.Semantics.M)); + env.addAttribute(VertexDescription.Semantics.M); + assertTrue(env.hasAttribute(VertexDescription.Semantics.M)); + assertTrue(env.queryInterval(VertexDescription.Semantics.M, 0).isEmpty()); + env.setInterval(VertexDescription.Semantics.M, 0, 1, 2); + assertTrue(env.queryInterval(VertexDescription.Semantics.M, 0).vmin == 1); + assertTrue(env.queryInterval(VertexDescription.Semantics.M, 0).vmax == 2); + + assertFalse(env.hasAttribute(VertexDescription.Semantics.Z)); + env.addAttribute(VertexDescription.Semantics.Z); + assertTrue(env.hasAttribute(VertexDescription.Semantics.Z)); + assertTrue(env.queryInterval(VertexDescription.Semantics.Z, 0).vmin == 0); + assertTrue(env.queryInterval(VertexDescription.Semantics.Z, 0).vmax == 0); + env.setInterval(VertexDescription.Semantics.Z, 0, 3, 4); + assertTrue(env.queryInterval(VertexDescription.Semantics.Z, 0).vmin == 3); + assertTrue(env.queryInterval(VertexDescription.Semantics.Z, 0).vmax == 4); + + + assertFalse(env.hasAttribute(VertexDescription.Semantics.ID)); + env.addAttribute(VertexDescription.Semantics.ID); + assertTrue(env.hasAttribute(VertexDescription.Semantics.ID)); + assertTrue(env.queryInterval(VertexDescription.Semantics.ID, 0).vmin == 0); + assertTrue(env.queryInterval(VertexDescription.Semantics.ID, 0).vmax == 0); + env.setInterval(VertexDescription.Semantics.ID, 0, 5, 6); + assertTrue(env.queryInterval(VertexDescription.Semantics.ID, 0).vmin == 5); + assertTrue(env.queryInterval(VertexDescription.Semantics.ID, 0).vmax == 6); + assertTrue(env.queryInterval(VertexDescription.Semantics.Z, 0).vmin == 3); + assertTrue(env.queryInterval(VertexDescription.Semantics.Z, 0).vmax == 4); + assertTrue(env.queryInterval(VertexDescription.Semantics.M, 0).vmin == 1); + assertTrue(env.queryInterval(VertexDescription.Semantics.M, 0).vmax == 2); + + env.dropAttribute(VertexDescription.Semantics.M); + assertFalse(env.hasAttribute(VertexDescription.Semantics.M)); + assertTrue(env.queryInterval(VertexDescription.Semantics.ID, 0).vmin == 5); + assertTrue(env.queryInterval(VertexDescription.Semantics.ID, 0).vmax == 6); + assertTrue(env.queryInterval(VertexDescription.Semantics.Z, 0).vmin == 3); + assertTrue(env.queryInterval(VertexDescription.Semantics.Z, 0).vmax == 4); + + Envelope env1 = new Envelope(); + env.copyTo(env1); + assertFalse(env1.hasAttribute(VertexDescription.Semantics.M)); + assertTrue(env1.queryInterval(VertexDescription.Semantics.ID, 0).vmin == 5); + assertTrue(env1.queryInterval(VertexDescription.Semantics.ID, 0).vmax == 6); + assertTrue(env1.queryInterval(VertexDescription.Semantics.Z, 0).vmin == 3); + assertTrue(env1.queryInterval(VertexDescription.Semantics.Z, 0).vmax == 4); + } + + @Test + public void testLine() { + Line env = new Line(); + env.setStartXY(100, 200); + env.setEndXY(250, 300); + assertFalse(env.hasAttribute(VertexDescription.Semantics.M)); + env.addAttribute(VertexDescription.Semantics.M); + assertTrue(env.hasAttribute(VertexDescription.Semantics.M)); + env.setStartAttribute(VertexDescription.Semantics.M, 0, 1); + env.setEndAttribute(VertexDescription.Semantics.M, 0, 2); + assertTrue(env.getStartAttributeAsDbl(VertexDescription.Semantics.M, 0) == 1); + assertTrue(env.getEndAttributeAsDbl(VertexDescription.Semantics.M, 0) == 2); + + assertFalse(env.hasAttribute(VertexDescription.Semantics.Z)); + env.addAttribute(VertexDescription.Semantics.Z); + assertTrue(env.hasAttribute(VertexDescription.Semantics.Z)); + env.setStartAttribute(VertexDescription.Semantics.Z, 0, 3); + env.setEndAttribute(VertexDescription.Semantics.Z, 0, 4); + assertTrue(env.getStartAttributeAsDbl(VertexDescription.Semantics.Z, 0) == 3); + assertTrue(env.getEndAttributeAsDbl(VertexDescription.Semantics.Z, 0) == 4); + + + assertFalse(env.hasAttribute(VertexDescription.Semantics.ID)); + env.addAttribute(VertexDescription.Semantics.ID); + assertTrue(env.hasAttribute(VertexDescription.Semantics.ID)); + env.setStartAttribute(VertexDescription.Semantics.ID, 0, 5); + env.setEndAttribute(VertexDescription.Semantics.ID, 0, 6); + assertTrue(env.getStartAttributeAsDbl(VertexDescription.Semantics.ID, 0) == 5); + assertTrue(env.getEndAttributeAsDbl(VertexDescription.Semantics.ID, 0) == 6); + + assertTrue(env.getStartAttributeAsDbl(VertexDescription.Semantics.M, 0) == 1); + assertTrue(env.getEndAttributeAsDbl(VertexDescription.Semantics.M, 0) == 2); + assertTrue(env.getStartAttributeAsDbl(VertexDescription.Semantics.Z, 0) == 3); + assertTrue(env.getEndAttributeAsDbl(VertexDescription.Semantics.Z, 0) == 4); + + env.dropAttribute(VertexDescription.Semantics.M); + assertFalse(env.hasAttribute(VertexDescription.Semantics.M)); + assertTrue(env.getStartAttributeAsDbl(VertexDescription.Semantics.ID, 0) == 5); + assertTrue(env.getEndAttributeAsDbl(VertexDescription.Semantics.ID, 0) == 6); + assertTrue(env.getStartAttributeAsDbl(VertexDescription.Semantics.Z, 0) == 3); + assertTrue(env.getEndAttributeAsDbl(VertexDescription.Semantics.Z, 0) == 4); + + Line env1 = new Line(); + env.copyTo(env1); + assertFalse(env1.hasAttribute(VertexDescription.Semantics.M)); + assertTrue(env.getStartAttributeAsDbl(VertexDescription.Semantics.ID, 0) == 5); + assertTrue(env.getEndAttributeAsDbl(VertexDescription.Semantics.ID, 0) == 6); + assertTrue(env.getStartAttributeAsDbl(VertexDescription.Semantics.Z, 0) == 3); + assertTrue(env.getEndAttributeAsDbl(VertexDescription.Semantics.Z, 0) == 4); + } + + @Test + public void testMultiPoint() { + MultiPoint mp = new MultiPoint(); + mp.add(new Point(100, 200)); + mp.add(new Point(101, 201)); + mp.add(new Point(102, 202)); + assertFalse(mp.hasAttribute(VertexDescription.Semantics.M)); + mp.addAttribute(VertexDescription.Semantics.M); + assertTrue(mp.hasAttribute(VertexDescription.Semantics.M)); + assertTrue(Double.isNaN(mp.getAttributeAsDbl(VertexDescription.Semantics.M, 0, 0))); + assertTrue(Double.isNaN(mp.getAttributeAsDbl(VertexDescription.Semantics.M, 1, 0))); + assertTrue(Double.isNaN(mp.getAttributeAsDbl(VertexDescription.Semantics.M, 2, 0))); + mp.setAttribute(VertexDescription.Semantics.M, 0, 0, 1); + mp.setAttribute(VertexDescription.Semantics.M, 1, 0, 2); + mp.setAttribute(VertexDescription.Semantics.M, 2, 0, 3); + assertTrue(mp.getAttributeAsDbl(VertexDescription.Semantics.M, 0, 0) == 1); + assertTrue(mp.getAttributeAsDbl(VertexDescription.Semantics.M, 1, 0) == 2); + assertTrue(mp.getAttributeAsDbl(VertexDescription.Semantics.M, 2, 0) == 3); + + assertFalse(mp.hasAttribute(VertexDescription.Semantics.Z)); + mp.addAttribute(VertexDescription.Semantics.Z); + assertTrue(mp.hasAttribute(VertexDescription.Semantics.Z)); + assertTrue(mp.getAttributeAsDbl(VertexDescription.Semantics.Z, 0, 0) == 0); + assertTrue(mp.getAttributeAsDbl(VertexDescription.Semantics.Z, 1, 0) == 0); + assertTrue(mp.getAttributeAsDbl(VertexDescription.Semantics.Z, 2, 0) == 0); + mp.setAttribute(VertexDescription.Semantics.Z, 0, 0, 11); + mp.setAttribute(VertexDescription.Semantics.Z, 1, 0, 21); + mp.setAttribute(VertexDescription.Semantics.Z, 2, 0, 31); + assertTrue(mp.getAttributeAsDbl(VertexDescription.Semantics.M, 0, 0) == 1); + assertTrue(mp.getAttributeAsDbl(VertexDescription.Semantics.M, 1, 0) == 2); + assertTrue(mp.getAttributeAsDbl(VertexDescription.Semantics.M, 2, 0) == 3); + assertTrue(mp.getAttributeAsDbl(VertexDescription.Semantics.Z, 0, 0) == 11); + assertTrue(mp.getAttributeAsDbl(VertexDescription.Semantics.Z, 1, 0) == 21); + assertTrue(mp.getAttributeAsDbl(VertexDescription.Semantics.Z, 2, 0) == 31); + + assertFalse(mp.hasAttribute(VertexDescription.Semantics.ID)); + mp.addAttribute(VertexDescription.Semantics.ID); + assertTrue(mp.hasAttribute(VertexDescription.Semantics.ID)); + assertTrue(mp.getAttributeAsDbl(VertexDescription.Semantics.ID, 0, 0) == 0); + assertTrue(mp.getAttributeAsDbl(VertexDescription.Semantics.ID, 1, 0) == 0); + assertTrue(mp.getAttributeAsDbl(VertexDescription.Semantics.ID, 2, 0) == 0); + mp.setAttribute(VertexDescription.Semantics.ID, 0, 0, -11); + mp.setAttribute(VertexDescription.Semantics.ID, 1, 0, -21); + mp.setAttribute(VertexDescription.Semantics.ID, 2, 0, -31); + assertTrue(mp.getAttributeAsDbl(VertexDescription.Semantics.M, 0, 0) == 1); + assertTrue(mp.getAttributeAsDbl(VertexDescription.Semantics.M, 1, 0) == 2); + assertTrue(mp.getAttributeAsDbl(VertexDescription.Semantics.M, 2, 0) == 3); + assertTrue(mp.getAttributeAsDbl(VertexDescription.Semantics.Z, 0, 0) == 11); + assertTrue(mp.getAttributeAsDbl(VertexDescription.Semantics.Z, 1, 0) == 21); + assertTrue(mp.getAttributeAsDbl(VertexDescription.Semantics.Z, 2, 0) == 31); + assertTrue(mp.getAttributeAsDbl(VertexDescription.Semantics.ID, 0, 0) == -11); + assertTrue(mp.getAttributeAsDbl(VertexDescription.Semantics.ID, 1, 0) == -21); + assertTrue(mp.getAttributeAsDbl(VertexDescription.Semantics.ID, 2, 0) == -31); + + mp.dropAttribute(VertexDescription.Semantics.M); + assertFalse(mp.hasAttribute(VertexDescription.Semantics.M)); + assertTrue(mp.getAttributeAsDbl(VertexDescription.Semantics.Z, 0, 0) == 11); + assertTrue(mp.getAttributeAsDbl(VertexDescription.Semantics.Z, 1, 0) == 21); + assertTrue(mp.getAttributeAsDbl(VertexDescription.Semantics.Z, 2, 0) == 31); + assertTrue(mp.getAttributeAsDbl(VertexDescription.Semantics.ID, 0, 0) == -11); + assertTrue(mp.getAttributeAsDbl(VertexDescription.Semantics.ID, 1, 0) == -21); + assertTrue(mp.getAttributeAsDbl(VertexDescription.Semantics.ID, 2, 0) == -31); + + MultiPoint mp1 = new MultiPoint(); + mp.copyTo(mp1); + assertFalse(mp1.hasAttribute(VertexDescription.Semantics.M)); + assertTrue(mp1.getAttributeAsDbl(VertexDescription.Semantics.Z, 0, 0) == 11); + assertTrue(mp1.getAttributeAsDbl(VertexDescription.Semantics.Z, 1, 0) == 21); + assertTrue(mp1.getAttributeAsDbl(VertexDescription.Semantics.Z, 2, 0) == 31); + assertTrue(mp1.getAttributeAsDbl(VertexDescription.Semantics.ID, 0, 0) == -11); + assertTrue(mp1.getAttributeAsDbl(VertexDescription.Semantics.ID, 1, 0) == -21); + assertTrue(mp1.getAttributeAsDbl(VertexDescription.Semantics.ID, 2, 0) == -31); + + mp1.dropAllAttributes(); + mp1.mergeVertexDescription(mp.getDescription()); + assertTrue(mp1.getAttributeAsDbl(VertexDescription.Semantics.Z, 0, 0) == 0); + assertTrue(mp1.getAttributeAsDbl(VertexDescription.Semantics.Z, 1, 0) == 0); + assertTrue(mp1.getAttributeAsDbl(VertexDescription.Semantics.Z, 2, 0) == 0); + assertTrue(Double.isNaN(mp1.getAttributeAsDbl(VertexDescription.Semantics.M, 0, 0))); + assertTrue(Double.isNaN(mp1.getAttributeAsDbl(VertexDescription.Semantics.M, 1, 0))); + assertTrue(Double.isNaN(mp1.getAttributeAsDbl(VertexDescription.Semantics.M, 2, 0))); + assertTrue(mp1.getAttributeAsDbl(VertexDescription.Semantics.ID, 0, 0) == 0); + assertTrue(mp1.getAttributeAsDbl(VertexDescription.Semantics.ID, 1, 0) == 0); + assertTrue(mp1.getAttributeAsDbl(VertexDescription.Semantics.ID, 2, 0) == 0); + + } + + @Test + public void testPolygon() { + Polygon mp = new Polygon(); + mp.startPath(new Point(100, 200)); + mp.lineTo(new Point(101, 201)); + mp.lineTo(new Point(102, 202)); + assertFalse(mp.hasAttribute(VertexDescription.Semantics.M)); + mp.addAttribute(VertexDescription.Semantics.M); + assertTrue(mp.hasAttribute(VertexDescription.Semantics.M)); + assertTrue(Double.isNaN(mp.getAttributeAsDbl(VertexDescription.Semantics.M, 0, 0))); + assertTrue(Double.isNaN(mp.getAttributeAsDbl(VertexDescription.Semantics.M, 1, 0))); + assertTrue(Double.isNaN(mp.getAttributeAsDbl(VertexDescription.Semantics.M, 2, 0))); + mp.setAttribute(VertexDescription.Semantics.M, 0, 0, 1); + mp.setAttribute(VertexDescription.Semantics.M, 1, 0, 2); + mp.setAttribute(VertexDescription.Semantics.M, 2, 0, 3); + assertTrue(mp.getAttributeAsDbl(VertexDescription.Semantics.M, 0, 0) == 1); + assertTrue(mp.getAttributeAsDbl(VertexDescription.Semantics.M, 1, 0) == 2); + assertTrue(mp.getAttributeAsDbl(VertexDescription.Semantics.M, 2, 0) == 3); + + assertFalse(mp.hasAttribute(VertexDescription.Semantics.Z)); + mp.addAttribute(VertexDescription.Semantics.Z); + assertTrue(mp.hasAttribute(VertexDescription.Semantics.Z)); + assertTrue(mp.getAttributeAsDbl(VertexDescription.Semantics.Z, 0, 0) == 0); + assertTrue(mp.getAttributeAsDbl(VertexDescription.Semantics.Z, 1, 0) == 0); + assertTrue(mp.getAttributeAsDbl(VertexDescription.Semantics.Z, 2, 0) == 0); + mp.setAttribute(VertexDescription.Semantics.Z, 0, 0, 11); + mp.setAttribute(VertexDescription.Semantics.Z, 1, 0, 21); + mp.setAttribute(VertexDescription.Semantics.Z, 2, 0, 31); + assertTrue(mp.getAttributeAsDbl(VertexDescription.Semantics.M, 0, 0) == 1); + assertTrue(mp.getAttributeAsDbl(VertexDescription.Semantics.M, 1, 0) == 2); + assertTrue(mp.getAttributeAsDbl(VertexDescription.Semantics.M, 2, 0) == 3); + assertTrue(mp.getAttributeAsDbl(VertexDescription.Semantics.Z, 0, 0) == 11); + assertTrue(mp.getAttributeAsDbl(VertexDescription.Semantics.Z, 1, 0) == 21); + assertTrue(mp.getAttributeAsDbl(VertexDescription.Semantics.Z, 2, 0) == 31); + + assertFalse(mp.hasAttribute(VertexDescription.Semantics.ID)); + mp.addAttribute(VertexDescription.Semantics.ID); + assertTrue(mp.hasAttribute(VertexDescription.Semantics.ID)); + assertTrue(mp.getAttributeAsDbl(VertexDescription.Semantics.ID, 0, 0) == 0); + assertTrue(mp.getAttributeAsDbl(VertexDescription.Semantics.ID, 1, 0) == 0); + assertTrue(mp.getAttributeAsDbl(VertexDescription.Semantics.ID, 2, 0) == 0); + mp.setAttribute(VertexDescription.Semantics.ID, 0, 0, -11); + mp.setAttribute(VertexDescription.Semantics.ID, 1, 0, -21); + mp.setAttribute(VertexDescription.Semantics.ID, 2, 0, -31); + assertTrue(mp.getAttributeAsDbl(VertexDescription.Semantics.M, 0, 0) == 1); + assertTrue(mp.getAttributeAsDbl(VertexDescription.Semantics.M, 1, 0) == 2); + assertTrue(mp.getAttributeAsDbl(VertexDescription.Semantics.M, 2, 0) == 3); + assertTrue(mp.getAttributeAsDbl(VertexDescription.Semantics.Z, 0, 0) == 11); + assertTrue(mp.getAttributeAsDbl(VertexDescription.Semantics.Z, 1, 0) == 21); + assertTrue(mp.getAttributeAsDbl(VertexDescription.Semantics.Z, 2, 0) == 31); + assertTrue(mp.getAttributeAsDbl(VertexDescription.Semantics.ID, 0, 0) == -11); + assertTrue(mp.getAttributeAsDbl(VertexDescription.Semantics.ID, 1, 0) == -21); + assertTrue(mp.getAttributeAsDbl(VertexDescription.Semantics.ID, 2, 0) == -31); + + mp.dropAttribute(VertexDescription.Semantics.M); + assertFalse(mp.hasAttribute(VertexDescription.Semantics.M)); + assertTrue(mp.getAttributeAsDbl(VertexDescription.Semantics.Z, 0, 0) == 11); + assertTrue(mp.getAttributeAsDbl(VertexDescription.Semantics.Z, 1, 0) == 21); + assertTrue(mp.getAttributeAsDbl(VertexDescription.Semantics.Z, 2, 0) == 31); + assertTrue(mp.getAttributeAsDbl(VertexDescription.Semantics.ID, 0, 0) == -11); + assertTrue(mp.getAttributeAsDbl(VertexDescription.Semantics.ID, 1, 0) == -21); + assertTrue(mp.getAttributeAsDbl(VertexDescription.Semantics.ID, 2, 0) == -31); + + Polygon mp1 = new Polygon(); + mp.copyTo(mp1); + assertFalse(mp1.hasAttribute(VertexDescription.Semantics.M)); + assertTrue(mp1.getAttributeAsDbl(VertexDescription.Semantics.Z, 0, 0) == 11); + assertTrue(mp1.getAttributeAsDbl(VertexDescription.Semantics.Z, 1, 0) == 21); + assertTrue(mp1.getAttributeAsDbl(VertexDescription.Semantics.Z, 2, 0) == 31); + assertTrue(mp1.getAttributeAsDbl(VertexDescription.Semantics.ID, 0, 0) == -11); + assertTrue(mp1.getAttributeAsDbl(VertexDescription.Semantics.ID, 1, 0) == -21); + assertTrue(mp1.getAttributeAsDbl(VertexDescription.Semantics.ID, 2, 0) == -31); + + mp1.dropAllAttributes(); + mp1.mergeVertexDescription(mp.getDescription()); + assertTrue(mp1.getAttributeAsDbl(VertexDescription.Semantics.Z, 0, 0) == 0); + assertTrue(mp1.getAttributeAsDbl(VertexDescription.Semantics.Z, 1, 0) == 0); + assertTrue(mp1.getAttributeAsDbl(VertexDescription.Semantics.Z, 2, 0) == 0); + assertTrue(Double.isNaN(mp1.getAttributeAsDbl(VertexDescription.Semantics.M, 0, 0))); + assertTrue(Double.isNaN(mp1.getAttributeAsDbl(VertexDescription.Semantics.M, 1, 0))); + assertTrue(Double.isNaN(mp1.getAttributeAsDbl(VertexDescription.Semantics.M, 2, 0))); + assertTrue(mp1.getAttributeAsDbl(VertexDescription.Semantics.ID, 0, 0) == 0); + assertTrue(mp1.getAttributeAsDbl(VertexDescription.Semantics.ID, 1, 0) == 0); + assertTrue(mp1.getAttributeAsDbl(VertexDescription.Semantics.ID, 2, 0) == 0); + + } } diff --git a/src/test/java/com/esri/core/geometry/TestBuffer.java b/src/test/java/com/esri/core/geometry/TestBuffer.java index 5008834a..aa6d7900 100755 --- a/src/test/java/com/esri/core/geometry/TestBuffer.java +++ b/src/test/java/com/esri/core/geometry/TestBuffer.java @@ -28,372 +28,372 @@ import org.junit.Test; public class TestBuffer extends TestCase { - @Override - protected void setUp() throws Exception { - super.setUp(); - } - - @Override - protected void tearDown() throws Exception { - super.tearDown(); - } - - @Test - public void testBufferPolytonWKT() { - String wkt = "MULTIPOLYGON (((-98.42049 46.08456, -98.42052 46.08682, -98.40509 46.08681, -98.40511 46.08456, -98.42049 46.08456)))"; - SpatialReference sr = SpatialReference.create(4326); - com.esri.core.geometry.Geometry geom = com.esri.core.geometry.OperatorImportFromWkt.local().execute(0, com.esri.core.geometry.Geometry.Type.Unknown, wkt, null); - com.esri.core.geometry.Geometry buffered = com.esri.core.geometry.OperatorBuffer.local().execute(geom, sr, 2.0, null); - String exportedGeom = com.esri.core.geometry.OperatorExportToWkt.local().execute(0, buffered, null); - int position = exportedGeom.indexOf('('); - assertEquals("MULTIPOLYGON", exportedGeom.substring(0, position - 1)); - } - - @Test - public void testBufferPoint() { - SpatialReference sr = SpatialReference.create(4326); - Point inputGeom = new Point(12, 120); - OperatorBuffer buffer = (OperatorBuffer) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.Buffer); - OperatorSimplify simplify = (OperatorSimplify) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.Simplify); - Geometry result = buffer.execute(inputGeom, sr, 40.0, null); - assertTrue(result.getType().value() == Geometry.GeometryType.Polygon); - Polygon poly = (Polygon) result; - int pathCount = poly.getPathCount(); - assertTrue(pathCount == 1); - int pointCount = poly.getPointCount(); - assertTrue(Math.abs(pointCount - 100.0) < 10); - Envelope2D env2D = new Envelope2D(); - result.queryEnvelope2D(env2D); - assertTrue(Math.abs(env2D.getWidth() - 80) < 0.01 - && Math.abs(env2D.getHeight() - 80) < 0.01); - assertTrue(Math.abs(env2D.getCenterX() - 12) < 0.001 - && Math.abs(env2D.getCenterY() - 120) < 0.001); - NonSimpleResult nsr = new NonSimpleResult(); - boolean is_simple = simplify.isSimpleAsFeature(result, sr, true, nsr, - null); - assertTrue(is_simple); - - { - result = buffer.execute(inputGeom, sr, 0, null); - assertTrue(result.getType().value() == Geometry.GeometryType.Polygon); - assertTrue(result.isEmpty()); - } - - { - result = buffer.execute(inputGeom, sr, -1, null); - assertTrue(result.getType().value() == Geometry.GeometryType.Polygon); - assertTrue(result.isEmpty()); - } - } - - @Test - public void testBufferEnvelope() { - SpatialReference sr = SpatialReference.create(4326); - Envelope inputGeom = new Envelope(1, 0, 200, 400); - OperatorBuffer buffer = (OperatorBuffer) OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.Buffer); - OperatorSimplify simplify = (OperatorSimplify) OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.Simplify); - Geometry result = buffer.execute(inputGeom, sr, 40.0, null); - assertTrue(result.getType().value() == Geometry.GeometryType.Polygon); - Polygon poly = (Polygon) (result); - Envelope2D env2D = new Envelope2D(); - result.queryEnvelope2D(env2D); - assertTrue(Math.abs(env2D.getWidth() - (80 + 199)) < 0.001 - && Math.abs(env2D.getHeight() - (80 + 400)) < 0.001); - assertTrue(Math.abs(env2D.getCenterX() - 201.0 / 2) < 0.001 - && Math.abs(env2D.getCenterY() - 400 / 2.0) < 0.001); - int pathCount = poly.getPathCount(); - assertTrue(pathCount == 1); - int pointCount = poly.getPointCount(); - assertTrue(Math.abs(pointCount - 104.0) < 10); - NonSimpleResult nsr = new NonSimpleResult(); - assertTrue(simplify.isSimpleAsFeature(result, sr, true, nsr, null)); - - { - result = buffer.execute(inputGeom, sr, -200.0, null); - assertTrue(result.getType().value() == Geometry.GeometryType.Polygon); - assertTrue(result.isEmpty()); - } - - { - result = buffer.execute(inputGeom, sr, -200.0, null); - assertTrue(result.getType().value() == Geometry.GeometryType.Polygon); - assertTrue(result.isEmpty()); - } - - { - result = buffer.execute(inputGeom, sr, -199 / 2.0, null); - assertTrue(result.getType().value() == Geometry.GeometryType.Polygon); - assertTrue(result.isEmpty()); - } - - { - result = buffer.execute(inputGeom, sr, -50.0, null); - poly = (Polygon) (result); - result.queryEnvelope2D(env2D); - assertTrue(Math.abs(env2D.getWidth() - (199 - 100)) < 0.001 - && Math.abs(env2D.getHeight() - (400 - 100)) < 0.001); - assertTrue(Math.abs(env2D.getCenterX() - 201.0 / 2) < 0.001 - && Math.abs(env2D.getCenterY() - 400 / 2.0) < 0.001); - pathCount = poly.getPathCount(); - assertTrue(pathCount == 1); - pointCount = poly.getPointCount(); - assertTrue(Math.abs(pointCount - 4.0) < 10); - assertTrue(simplify.isSimpleAsFeature(result, sr, null)); - } - } - - @Test - public void testBufferMultiPoint() { - SpatialReference sr = SpatialReference.create(4326); - OperatorBuffer buffer = (OperatorBuffer) OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.Buffer); - OperatorSimplify simplify = (OperatorSimplify) OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.Simplify); - MultiPoint inputGeom = new MultiPoint(); - inputGeom.add(12, 120); - inputGeom.add(20, 120); - Geometry result = buffer.execute(inputGeom, sr, 40.0, null); - assertTrue(result.getType().value() == Geometry.GeometryType.Polygon); - Polygon poly = (Polygon) (result); - Envelope2D env2D = new Envelope2D(); - result.queryEnvelope2D(env2D); - assertTrue(Math.abs(env2D.getWidth() - 80 - 8) < 0.001 - && Math.abs(env2D.getHeight() - 80) < 0.001); - assertTrue(Math.abs(env2D.getCenterX() - 16) < 0.001 - && Math.abs(env2D.getCenterY() - 120) < 0.001); - int pathCount = poly.getPathCount(); - assertTrue(pathCount == 1); - int pointCount = poly.getPointCount(); - assertTrue(Math.abs(pointCount - 108.0) < 10); - assertTrue(simplify.isSimpleAsFeature(result, sr, null)); - - { - result = buffer.execute(inputGeom, sr, 0, null); - assertTrue(result.getType().value() == Geometry.GeometryType.Polygon); - assertTrue(result.isEmpty()); - } - - { - result = buffer.execute(inputGeom, sr, -1, null); - assertTrue(result.getType().value() == Geometry.GeometryType.Polygon); - assertTrue(result.isEmpty()); - } - } - - @Test - public void testBufferLine() { - SpatialReference sr = SpatialReference.create(4326); - Line inputGeom = new Line(12, 120, 20, 120); - OperatorBuffer buffer = (OperatorBuffer) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.Buffer); - OperatorSimplify simplify = (OperatorSimplify) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.Simplify); - Geometry result = buffer.execute(inputGeom, sr, 40.0, null); - assertTrue(result.getType().value() == Geometry.GeometryType.Polygon); - Polygon poly = (Polygon) (result); - Envelope2D env2D = new Envelope2D(); - result.queryEnvelope2D(env2D); - assertTrue(Math.abs(env2D.getWidth() - 80 - 8) < 0.001 - && Math.abs(env2D.getHeight() - 80) < 0.001); - assertTrue(Math.abs(env2D.getCenterX() - 16) < 0.001 - && Math.abs(env2D.getCenterY() - 120) < 0.001); - int pathCount = poly.getPathCount(); - assertTrue(pathCount == 1); - int pointCount = poly.getPointCount(); - assertTrue(Math.abs(pointCount - 100.0) < 10); - assertTrue(simplify.isSimpleAsFeature(result, sr, null)); - - { - result = buffer.execute(inputGeom, sr, 0, null); - assertTrue(result.getType().value() == Geometry.GeometryType.Polygon); - assertTrue(result.isEmpty()); - } - - { - result = buffer.execute(inputGeom, sr, -1, null); - assertTrue(result.getType().value() == Geometry.GeometryType.Polygon); - assertTrue(result.isEmpty()); - } - } - - @Test - public void testBufferPolyline() { - SpatialReference sr = SpatialReference.create(4326); - Polyline inputGeom = new Polyline(); - OperatorBuffer buffer = (OperatorBuffer) OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.Buffer); - OperatorSimplify simplify = (OperatorSimplify) OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.Simplify); - inputGeom.startPath(0, 0); - inputGeom.lineTo(50, 50); - inputGeom.lineTo(50, 0); - inputGeom.lineTo(0, 50); - - { - Geometry result = buffer.execute(inputGeom, sr, 0, null); - assertTrue(result.getType().value() == Geometry.GeometryType.Polygon); - assertTrue(result.isEmpty()); - } - - { - Geometry result = buffer.execute(inputGeom, sr, -1, null); - assertTrue(result.getType().value() == Geometry.GeometryType.Polygon); - assertTrue(result.isEmpty()); - } - - { - Geometry result = buffer.execute(inputGeom, sr, 40.0, null); - assertTrue(result.getType().value() == Geometry.GeometryType.Polygon); - Polygon poly = (Polygon) (result); - Envelope2D env2D = new Envelope2D(); - result.queryEnvelope2D(env2D); - assertTrue(Math.abs(env2D.getWidth() - 80 - 50) < 0.1 - && Math.abs(env2D.getHeight() - 80 - 50) < 0.1); - assertTrue(Math.abs(env2D.getCenterX() - 25) < 0.1 - && Math.abs(env2D.getCenterY() - 25) < 0.1); - int pathCount = poly.getPathCount(); - assertTrue(pathCount == 1); - int pointCount = poly.getPointCount(); - assertTrue(Math.abs(pointCount - 171.0) < 10); - assertTrue(simplify.isSimpleAsFeature(result, sr, null)); - } - - { - Geometry result = buffer.execute(inputGeom, sr, 4.0, null); - assertTrue(result.getType().value() == Geometry.GeometryType.Polygon); - Polygon poly = (Polygon) (result); - Envelope2D env2D = new Envelope2D(); - result.queryEnvelope2D(env2D); - assertTrue(Math.abs(env2D.getWidth() - 8 - 50) < 0.1 - && Math.abs(env2D.getHeight() - 8 - 50) < 0.1); - assertTrue(Math.abs(env2D.getCenterX() - 25) < 0.1 - && Math.abs(env2D.getCenterY() - 25) < 0.1); - int pathCount = poly.getPathCount(); - assertTrue(pathCount == 2); - int pointCount = poly.getPointCount(); - assertTrue(Math.abs(pointCount - 186.0) < 10); - assertTrue(simplify.isSimpleAsFeature(result, sr, null)); - } - - { - inputGeom = new Polyline(); - inputGeom.startPath(0, 0); - inputGeom.lineTo(50, 50); - inputGeom.startPath(50, 0); - inputGeom.lineTo(0, 50); - - Geometry result = buffer.execute(inputGeom, sr, 4.0, null); - assertTrue(result.getType().value() == Geometry.GeometryType.Polygon); - Polygon poly = (Polygon) (result); - Envelope2D env2D = new Envelope2D(); - result.queryEnvelope2D(env2D); - assertTrue(Math.abs(env2D.getWidth() - 8 - 50) < 0.1 - && Math.abs(env2D.getHeight() - 8 - 50) < 0.1); - assertTrue(Math.abs(env2D.getCenterX() - 25) < 0.1 - && Math.abs(env2D.getCenterY() - 25) < 0.1); - int pathCount = poly.getPathCount(); - assertTrue(pathCount == 1); - int pointCount = poly.getPointCount(); - assertTrue(Math.abs(pointCount - 208.0) < 10); - assertTrue(simplify.isSimpleAsFeature(result, sr, null)); - } - - { - inputGeom = new Polyline(); - inputGeom.startPath(1.762614, 0.607368); - inputGeom.lineTo(1.762414, 0.606655); - inputGeom.lineTo(1.763006, 0.607034); - inputGeom.lineTo(1.762548, 0.607135); - - Geometry result = buffer.execute(inputGeom, sr, 0.005, null); - assertTrue(simplify.isSimpleAsFeature(result, sr, null)); - } - } - - @Test - public void testBufferPolygon() { - SpatialReference sr = SpatialReference.create(4326); - Polygon inputGeom = new Polygon(); - OperatorBuffer buffer = (OperatorBuffer) OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.Buffer); - OperatorSimplify simplify = (OperatorSimplify) OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.Simplify); - inputGeom.startPath(0, 0); - inputGeom.lineTo(50, 50); - inputGeom.lineTo(50, 0); - - { - Geometry result = buffer.execute(inputGeom, sr, 0, null); - assertTrue(result.getType().value() == Geometry.GeometryType.Polygon); - assertTrue(result == inputGeom); - } - - { - Geometry result = buffer.execute(inputGeom, sr, 10, null); - assertTrue(result.getType().value() == Geometry.GeometryType.Polygon); - Polygon poly = (Polygon) (result); - Envelope2D env2D = new Envelope2D(); - result.queryEnvelope2D(env2D); - assertTrue(Math.abs(env2D.getWidth() - 20 - 50) < 0.1 - && Math.abs(env2D.getHeight() - 20 - 50) < 0.1); - assertTrue(Math.abs(env2D.getCenterX() - 25) < 0.1 - && Math.abs(env2D.getCenterY() - 25) < 0.1); - int pathCount = poly.getPathCount(); - assertTrue(pathCount == 1); - int pointCount = poly.getPointCount(); - assertTrue(Math.abs(pointCount - 104.0) < 10); - assertTrue(simplify.isSimpleAsFeature(result, sr, null)); - } - - { - sr = SpatialReference.create(4326); - inputGeom = new Polygon(); - inputGeom.startPath(0, 0); - inputGeom.lineTo(50, 50); - inputGeom.lineTo(50, 0); - - Geometry result = buffer.execute(inputGeom, sr, -10, null); - assertTrue(result.getType().value() == Geometry.GeometryType.Polygon); - Polygon poly = (Polygon) (result); - Envelope2D env2D = new Envelope2D(); - result.queryEnvelope2D(env2D); - assertTrue(Math.abs(env2D.getWidth() - 15.85) < 0.1 - && Math.abs(env2D.getHeight() - 15.85) < 0.1); - assertTrue(Math.abs(env2D.getCenterX() - 32.07) < 0.1 - && Math.abs(env2D.getCenterY() - 17.93) < 0.1); - int pathCount = poly.getPathCount(); - assertTrue(pathCount == 1); - int pointCount = poly.getPointCount(); - assertTrue(pointCount == 3); - assertTrue(simplify.isSimpleAsFeature(result, sr, null)); - } - - { - sr = SpatialReference.create(4326); - inputGeom = new Polygon(); - inputGeom.startPath(0, 0); - inputGeom.lineTo(0, 50); - inputGeom.lineTo(50, 50); - inputGeom.lineTo(50, 0); - inputGeom.startPath(10, 10); - inputGeom.lineTo(40, 10); - inputGeom.lineTo(40, 40); - inputGeom.lineTo(10, 40); - - Geometry result = buffer.execute(inputGeom, sr, -2, null); - assertTrue(result.getType().value() == Geometry.GeometryType.Polygon); - Polygon poly = (Polygon) (result); - Envelope2D env2D = new Envelope2D(); - result.queryEnvelope2D(env2D); - assertTrue(Math.abs(env2D.getWidth() + 4 - 50) < 0.1 - && Math.abs(env2D.getHeight() + 4 - 50) < 0.1); - assertTrue(Math.abs(env2D.getCenterX() - 25) < 0.1 - && Math.abs(env2D.getCenterY() - 25) < 0.1); - int pathCount = poly.getPathCount(); - assertTrue(pathCount == 2); - int pointCount = poly.getPointCount(); - assertTrue(Math.abs(pointCount - 108) < 10); - assertTrue(simplify.isSimpleAsFeature(result, sr, null)); - } - } + @Override + protected void setUp() throws Exception { + super.setUp(); + } + + @Override + protected void tearDown() throws Exception { + super.tearDown(); + } + + @Test + public void testBufferPolytonWKT() { + String wkt = "MULTIPOLYGON (((-98.42049 46.08456, -98.42052 46.08682, -98.40509 46.08681, -98.40511 46.08456, -98.42049 46.08456)))"; + SpatialReference sr = SpatialReference.create(4326); + com.esri.core.geometry.Geometry geom = com.esri.core.geometry.OperatorImportFromWkt.local().execute(0, com.esri.core.geometry.Geometry.Type.Unknown, wkt, null); + com.esri.core.geometry.Geometry buffered = com.esri.core.geometry.OperatorBuffer.local().execute(geom, sr, 2.0, null); + String exportedGeom = com.esri.core.geometry.OperatorExportToWkt.local().execute(0, buffered, null); + int position = exportedGeom.indexOf('('); + assertEquals("MULTIPOLYGON", exportedGeom.substring(0, position - 1)); + } + + @Test + public void testBufferPoint() { + SpatialReference sr = SpatialReference.create(4326); + Point inputGeom = new Point(12, 120); + OperatorBuffer buffer = (OperatorBuffer) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.Buffer); + OperatorSimplify simplify = (OperatorSimplify) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.Simplify); + Geometry result = buffer.execute(inputGeom, sr, 40.0, null); + assertTrue(result.getType().value() == Geometry.GeometryType.Polygon); + Polygon poly = (Polygon) result; + int pathCount = poly.getPathCount(); + assertTrue(pathCount == 1); + int pointCount = poly.getPointCount(); + assertTrue(Math.abs(pointCount - 100.0) < 10); + Envelope2D env2D = new Envelope2D(); + result.queryEnvelope2D(env2D); + assertTrue(Math.abs(env2D.getWidth() - 80) < 0.01 + && Math.abs(env2D.getHeight() - 80) < 0.01); + assertTrue(Math.abs(env2D.getCenterX() - 12) < 0.001 + && Math.abs(env2D.getCenterY() - 120) < 0.001); + NonSimpleResult nsr = new NonSimpleResult(); + boolean is_simple = simplify.isSimpleAsFeature(result, sr, true, nsr, + null); + assertTrue(is_simple); + + { + result = buffer.execute(inputGeom, sr, 0, null); + assertTrue(result.getType().value() == Geometry.GeometryType.Polygon); + assertTrue(result.isEmpty()); + } + + { + result = buffer.execute(inputGeom, sr, -1, null); + assertTrue(result.getType().value() == Geometry.GeometryType.Polygon); + assertTrue(result.isEmpty()); + } + } + + @Test + public void testBufferEnvelope() { + SpatialReference sr = SpatialReference.create(4326); + Envelope inputGeom = new Envelope(1, 0, 200, 400); + OperatorBuffer buffer = (OperatorBuffer) OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Buffer); + OperatorSimplify simplify = (OperatorSimplify) OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Simplify); + Geometry result = buffer.execute(inputGeom, sr, 40.0, null); + assertTrue(result.getType().value() == Geometry.GeometryType.Polygon); + Polygon poly = (Polygon) (result); + Envelope2D env2D = new Envelope2D(); + result.queryEnvelope2D(env2D); + assertTrue(Math.abs(env2D.getWidth() - (80 + 199)) < 0.001 + && Math.abs(env2D.getHeight() - (80 + 400)) < 0.001); + assertTrue(Math.abs(env2D.getCenterX() - 201.0 / 2) < 0.001 + && Math.abs(env2D.getCenterY() - 400 / 2.0) < 0.001); + int pathCount = poly.getPathCount(); + assertTrue(pathCount == 1); + int pointCount = poly.getPointCount(); + assertTrue(Math.abs(pointCount - 104.0) < 10); + NonSimpleResult nsr = new NonSimpleResult(); + assertTrue(simplify.isSimpleAsFeature(result, sr, true, nsr, null)); + + { + result = buffer.execute(inputGeom, sr, -200.0, null); + assertTrue(result.getType().value() == Geometry.GeometryType.Polygon); + assertTrue(result.isEmpty()); + } + + { + result = buffer.execute(inputGeom, sr, -200.0, null); + assertTrue(result.getType().value() == Geometry.GeometryType.Polygon); + assertTrue(result.isEmpty()); + } + + { + result = buffer.execute(inputGeom, sr, -199 / 2.0, null); + assertTrue(result.getType().value() == Geometry.GeometryType.Polygon); + assertTrue(result.isEmpty()); + } + + { + result = buffer.execute(inputGeom, sr, -50.0, null); + poly = (Polygon) (result); + result.queryEnvelope2D(env2D); + assertTrue(Math.abs(env2D.getWidth() - (199 - 100)) < 0.001 + && Math.abs(env2D.getHeight() - (400 - 100)) < 0.001); + assertTrue(Math.abs(env2D.getCenterX() - 201.0 / 2) < 0.001 + && Math.abs(env2D.getCenterY() - 400 / 2.0) < 0.001); + pathCount = poly.getPathCount(); + assertTrue(pathCount == 1); + pointCount = poly.getPointCount(); + assertTrue(Math.abs(pointCount - 4.0) < 10); + assertTrue(simplify.isSimpleAsFeature(result, sr, null)); + } + } + + @Test + public void testBufferMultiPoint() { + SpatialReference sr = SpatialReference.create(4326); + OperatorBuffer buffer = (OperatorBuffer) OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Buffer); + OperatorSimplify simplify = (OperatorSimplify) OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Simplify); + MultiPoint inputGeom = new MultiPoint(); + inputGeom.add(12, 120); + inputGeom.add(20, 120); + Geometry result = buffer.execute(inputGeom, sr, 40.0, null); + assertTrue(result.getType().value() == Geometry.GeometryType.Polygon); + Polygon poly = (Polygon) (result); + Envelope2D env2D = new Envelope2D(); + result.queryEnvelope2D(env2D); + assertTrue(Math.abs(env2D.getWidth() - 80 - 8) < 0.001 + && Math.abs(env2D.getHeight() - 80) < 0.001); + assertTrue(Math.abs(env2D.getCenterX() - 16) < 0.001 + && Math.abs(env2D.getCenterY() - 120) < 0.001); + int pathCount = poly.getPathCount(); + assertTrue(pathCount == 1); + int pointCount = poly.getPointCount(); + assertTrue(Math.abs(pointCount - 108.0) < 10); + assertTrue(simplify.isSimpleAsFeature(result, sr, null)); + + { + result = buffer.execute(inputGeom, sr, 0, null); + assertTrue(result.getType().value() == Geometry.GeometryType.Polygon); + assertTrue(result.isEmpty()); + } + + { + result = buffer.execute(inputGeom, sr, -1, null); + assertTrue(result.getType().value() == Geometry.GeometryType.Polygon); + assertTrue(result.isEmpty()); + } + } + + @Test + public void testBufferLine() { + SpatialReference sr = SpatialReference.create(4326); + Line inputGeom = new Line(12, 120, 20, 120); + OperatorBuffer buffer = (OperatorBuffer) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.Buffer); + OperatorSimplify simplify = (OperatorSimplify) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.Simplify); + Geometry result = buffer.execute(inputGeom, sr, 40.0, null); + assertTrue(result.getType().value() == Geometry.GeometryType.Polygon); + Polygon poly = (Polygon) (result); + Envelope2D env2D = new Envelope2D(); + result.queryEnvelope2D(env2D); + assertTrue(Math.abs(env2D.getWidth() - 80 - 8) < 0.001 + && Math.abs(env2D.getHeight() - 80) < 0.001); + assertTrue(Math.abs(env2D.getCenterX() - 16) < 0.001 + && Math.abs(env2D.getCenterY() - 120) < 0.001); + int pathCount = poly.getPathCount(); + assertTrue(pathCount == 1); + int pointCount = poly.getPointCount(); + assertTrue(Math.abs(pointCount - 100.0) < 10); + assertTrue(simplify.isSimpleAsFeature(result, sr, null)); + + { + result = buffer.execute(inputGeom, sr, 0, null); + assertTrue(result.getType().value() == Geometry.GeometryType.Polygon); + assertTrue(result.isEmpty()); + } + + { + result = buffer.execute(inputGeom, sr, -1, null); + assertTrue(result.getType().value() == Geometry.GeometryType.Polygon); + assertTrue(result.isEmpty()); + } + } + + @Test + public void testBufferPolyline() { + SpatialReference sr = SpatialReference.create(4326); + Polyline inputGeom = new Polyline(); + OperatorBuffer buffer = (OperatorBuffer) OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Buffer); + OperatorSimplify simplify = (OperatorSimplify) OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Simplify); + inputGeom.startPath(0, 0); + inputGeom.lineTo(50, 50); + inputGeom.lineTo(50, 0); + inputGeom.lineTo(0, 50); + + { + Geometry result = buffer.execute(inputGeom, sr, 0, null); + assertTrue(result.getType().value() == Geometry.GeometryType.Polygon); + assertTrue(result.isEmpty()); + } + + { + Geometry result = buffer.execute(inputGeom, sr, -1, null); + assertTrue(result.getType().value() == Geometry.GeometryType.Polygon); + assertTrue(result.isEmpty()); + } + + { + Geometry result = buffer.execute(inputGeom, sr, 40.0, null); + assertTrue(result.getType().value() == Geometry.GeometryType.Polygon); + Polygon poly = (Polygon) (result); + Envelope2D env2D = new Envelope2D(); + result.queryEnvelope2D(env2D); + assertTrue(Math.abs(env2D.getWidth() - 80 - 50) < 0.1 + && Math.abs(env2D.getHeight() - 80 - 50) < 0.1); + assertTrue(Math.abs(env2D.getCenterX() - 25) < 0.1 + && Math.abs(env2D.getCenterY() - 25) < 0.1); + int pathCount = poly.getPathCount(); + assertTrue(pathCount == 1); + int pointCount = poly.getPointCount(); + assertTrue(Math.abs(pointCount - 171.0) < 10); + assertTrue(simplify.isSimpleAsFeature(result, sr, null)); + } + + { + Geometry result = buffer.execute(inputGeom, sr, 4.0, null); + assertTrue(result.getType().value() == Geometry.GeometryType.Polygon); + Polygon poly = (Polygon) (result); + Envelope2D env2D = new Envelope2D(); + result.queryEnvelope2D(env2D); + assertTrue(Math.abs(env2D.getWidth() - 8 - 50) < 0.1 + && Math.abs(env2D.getHeight() - 8 - 50) < 0.1); + assertTrue(Math.abs(env2D.getCenterX() - 25) < 0.1 + && Math.abs(env2D.getCenterY() - 25) < 0.1); + int pathCount = poly.getPathCount(); + assertTrue(pathCount == 2); + int pointCount = poly.getPointCount(); + assertTrue(Math.abs(pointCount - 186.0) < 10); + assertTrue(simplify.isSimpleAsFeature(result, sr, null)); + } + + { + inputGeom = new Polyline(); + inputGeom.startPath(0, 0); + inputGeom.lineTo(50, 50); + inputGeom.startPath(50, 0); + inputGeom.lineTo(0, 50); + + Geometry result = buffer.execute(inputGeom, sr, 4.0, null); + assertTrue(result.getType().value() == Geometry.GeometryType.Polygon); + Polygon poly = (Polygon) (result); + Envelope2D env2D = new Envelope2D(); + result.queryEnvelope2D(env2D); + assertTrue(Math.abs(env2D.getWidth() - 8 - 50) < 0.1 + && Math.abs(env2D.getHeight() - 8 - 50) < 0.1); + assertTrue(Math.abs(env2D.getCenterX() - 25) < 0.1 + && Math.abs(env2D.getCenterY() - 25) < 0.1); + int pathCount = poly.getPathCount(); + assertTrue(pathCount == 1); + int pointCount = poly.getPointCount(); + assertTrue(Math.abs(pointCount - 208.0) < 10); + assertTrue(simplify.isSimpleAsFeature(result, sr, null)); + } + + { + inputGeom = new Polyline(); + inputGeom.startPath(1.762614, 0.607368); + inputGeom.lineTo(1.762414, 0.606655); + inputGeom.lineTo(1.763006, 0.607034); + inputGeom.lineTo(1.762548, 0.607135); + + Geometry result = buffer.execute(inputGeom, sr, 0.005, null); + assertTrue(simplify.isSimpleAsFeature(result, sr, null)); + } + } + + @Test + public void testBufferPolygon() { + SpatialReference sr = SpatialReference.create(4326); + Polygon inputGeom = new Polygon(); + OperatorBuffer buffer = (OperatorBuffer) OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Buffer); + OperatorSimplify simplify = (OperatorSimplify) OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Simplify); + inputGeom.startPath(0, 0); + inputGeom.lineTo(50, 50); + inputGeom.lineTo(50, 0); + + { + Geometry result = buffer.execute(inputGeom, sr, 0, null); + assertTrue(result.getType().value() == Geometry.GeometryType.Polygon); + assertTrue(result == inputGeom); + } + + { + Geometry result = buffer.execute(inputGeom, sr, 10, null); + assertTrue(result.getType().value() == Geometry.GeometryType.Polygon); + Polygon poly = (Polygon) (result); + Envelope2D env2D = new Envelope2D(); + result.queryEnvelope2D(env2D); + assertTrue(Math.abs(env2D.getWidth() - 20 - 50) < 0.1 + && Math.abs(env2D.getHeight() - 20 - 50) < 0.1); + assertTrue(Math.abs(env2D.getCenterX() - 25) < 0.1 + && Math.abs(env2D.getCenterY() - 25) < 0.1); + int pathCount = poly.getPathCount(); + assertTrue(pathCount == 1); + int pointCount = poly.getPointCount(); + assertTrue(Math.abs(pointCount - 104.0) < 10); + assertTrue(simplify.isSimpleAsFeature(result, sr, null)); + } + + { + sr = SpatialReference.create(4326); + inputGeom = new Polygon(); + inputGeom.startPath(0, 0); + inputGeom.lineTo(50, 50); + inputGeom.lineTo(50, 0); + + Geometry result = buffer.execute(inputGeom, sr, -10, null); + assertTrue(result.getType().value() == Geometry.GeometryType.Polygon); + Polygon poly = (Polygon) (result); + Envelope2D env2D = new Envelope2D(); + result.queryEnvelope2D(env2D); + assertTrue(Math.abs(env2D.getWidth() - 15.85) < 0.1 + && Math.abs(env2D.getHeight() - 15.85) < 0.1); + assertTrue(Math.abs(env2D.getCenterX() - 32.07) < 0.1 + && Math.abs(env2D.getCenterY() - 17.93) < 0.1); + int pathCount = poly.getPathCount(); + assertTrue(pathCount == 1); + int pointCount = poly.getPointCount(); + assertTrue(pointCount == 3); + assertTrue(simplify.isSimpleAsFeature(result, sr, null)); + } + + { + sr = SpatialReference.create(4326); + inputGeom = new Polygon(); + inputGeom.startPath(0, 0); + inputGeom.lineTo(0, 50); + inputGeom.lineTo(50, 50); + inputGeom.lineTo(50, 0); + inputGeom.startPath(10, 10); + inputGeom.lineTo(40, 10); + inputGeom.lineTo(40, 40); + inputGeom.lineTo(10, 40); + + Geometry result = buffer.execute(inputGeom, sr, -2, null); + assertTrue(result.getType().value() == Geometry.GeometryType.Polygon); + Polygon poly = (Polygon) (result); + Envelope2D env2D = new Envelope2D(); + result.queryEnvelope2D(env2D); + assertTrue(Math.abs(env2D.getWidth() + 4 - 50) < 0.1 + && Math.abs(env2D.getHeight() + 4 - 50) < 0.1); + assertTrue(Math.abs(env2D.getCenterX() - 25) < 0.1 + && Math.abs(env2D.getCenterY() - 25) < 0.1); + int pathCount = poly.getPathCount(); + assertTrue(pathCount == 2); + int pointCount = poly.getPointCount(); + assertTrue(Math.abs(pointCount - 108) < 10); + assertTrue(simplify.isSimpleAsFeature(result, sr, null)); + } + } } diff --git a/src/test/java/com/esri/core/geometry/TestCircles.java b/src/test/java/com/esri/core/geometry/TestCircles.java index 3e2a68eb..4bff60d2 100644 --- a/src/test/java/com/esri/core/geometry/TestCircles.java +++ b/src/test/java/com/esri/core/geometry/TestCircles.java @@ -7,162 +7,162 @@ import java.util.HashMap; public class TestCircles extends TestCase { - @Test - public void testSquare() { - Polygon polygon = new Polygon(); - polygon.startPath(-2, -2); - polygon.lineTo(-2, 2); - polygon.lineTo(2, 2); - polygon.lineTo(2, -2); - polygon.closeAllPaths(); - OperatorEnclosingCircleCursor operatorEnclosingCircleCursor = new OperatorEnclosingCircleCursor(new SimpleGeometryCursor(polygon), null, null); - Geometry geometry = operatorEnclosingCircleCursor.next(); - double radius = Math.sqrt(2*2 + 2*2); - double area = Math.PI * radius * radius; - assertEquals(geometry.calculateArea2D(), area, 1e-1); - } - - @Test - public void testBuffered() { - Point center = new Point(0,0); - Geometry buffered = GeometryEngine.buffer(center, SpatialReference.create(4326), 20); - OperatorEnclosingCircleCursor operatorEnclosingCircleCursor = new OperatorEnclosingCircleCursor(new SimpleGeometryCursor(buffered), null, null); - Geometry geometry = operatorEnclosingCircleCursor.next(); - assertEquals(geometry.calculateArea2D(), buffered.calculateArea2D(), 1e-10); - assertTrue(GeometryEngine.equals(geometry, buffered, null)); - } - - @Test - public void testGeodesicBuffer() { - Point center = new Point(0,0); - Geometry buffered = OperatorGeodesicBuffer.local().execute(center, - SpatialReference.create(4326), - GeodeticCurveType.Geodesic, - 4000, - 20, - false, - null); - - OperatorEnclosingCircleCursor operatorEnclosingCircleCursor = new OperatorEnclosingCircleCursor( - new SimpleGeometryCursor(buffered), - SpatialReference.create(4326), - null); - - Geometry geometry = operatorEnclosingCircleCursor.next(); - // This tests fails without multiVertexGeometry._setDirtyFlag(DirtyFlags.dirtyAll, true); in project - assertEquals(geometry.calculateArea2D(), buffered.calculateArea2D(), 1e-4); - - Geometry bufferedContainer = OperatorGeodesicBuffer.local().execute(center, - SpatialReference.create(4326), - GeodeticCurveType.Geodesic, - 4050, - 20, - false, - null); - assertTrue(GeometryEngine.contains(bufferedContainer, geometry, SpatialReference.create(4326))); - } - - @Test - public void testBufferedClipped() { - Point center = new Point(0,0); - Geometry buffered = GeometryEngine.buffer(center, SpatialReference.create(4326), 20); - Geometry clipped = GeometryEngine.clip(buffered, new Envelope(0, -20, 40, 40), null); - OperatorEnclosingCircleCursor operatorEnclosingCircleCursor = new OperatorEnclosingCircleCursor(new SimpleGeometryCursor(clipped), null, null); - Geometry geometry = operatorEnclosingCircleCursor.next(); - assertEquals(geometry.calculateArea2D(), buffered.calculateArea2D(), 1e-10); - assertTrue(GeometryEngine.equals(geometry, buffered, null)); - } - - @Test - public void testBufferedClippedUnionedSmall() { - Point center = new Point(0,0); - Geometry buffered = GeometryEngine.buffer(center, SpatialReference.create(4326), 20); - Geometry clipped = GeometryEngine.clip(buffered, new Envelope(0, -20, 40, 40), null); - Geometry bufferedSmall = GeometryEngine.buffer(center, SpatialReference.create(4326), 10); - Geometry[] two = new Geometry[] {bufferedSmall, clipped}; - Geometry unionedGeom = GeometryEngine.union(two, null); - OperatorEnclosingCircleCursor operatorEnclosingCircleCursor = new OperatorEnclosingCircleCursor(new SimpleGeometryCursor(unionedGeom), null, null); - Geometry geometry = operatorEnclosingCircleCursor.next(); - assertEquals(geometry.calculateArea2D(), buffered.calculateArea2D(), 1e-10); - assertTrue(GeometryEngine.equals(geometry, buffered, null)); - } - - - @Test - public void testRandomPoints() { - int count = 400; - Envelope e = new Envelope(0,0,40, 40); - RandomCoordinateGenerator randomCoordinateGenerator = new RandomCoordinateGenerator(count, e, SpatialReference.create(4326).getTolerance()); - MultiPoint multiPoint = new MultiPoint(); - for (int i = 0; i < count; i++) { - multiPoint.add(randomCoordinateGenerator._GenerateNewPoint()); - } - - int run_count = 10; - ArrayDeque geometries = new ArrayDeque<>(); - for (int i = 0; i < run_count; i++) { - OperatorEnclosingCircleCursor operatorEnclosingCircleCursor = new OperatorEnclosingCircleCursor(new SimpleGeometryCursor(multiPoint), SpatialReference.create(4326), null); - Geometry geometry = operatorEnclosingCircleCursor.next(); - geometries.add(geometry); - } + @Test + public void testSquare() { + Polygon polygon = new Polygon(); + polygon.startPath(-2, -2); + polygon.lineTo(-2, 2); + polygon.lineTo(2, 2); + polygon.lineTo(2, -2); + polygon.closeAllPaths(); + OperatorEnclosingCircleCursor operatorEnclosingCircleCursor = new OperatorEnclosingCircleCursor(new SimpleGeometryCursor(polygon), null, null); + Geometry geometry = operatorEnclosingCircleCursor.next(); + double radius = Math.sqrt(2 * 2 + 2 * 2); + double area = Math.PI * radius * radius; + assertEquals(geometry.calculateArea2D(), area, 1e-1); + } + + @Test + public void testBuffered() { + Point center = new Point(0, 0); + Geometry buffered = GeometryEngine.buffer(center, SpatialReference.create(4326), 20); + OperatorEnclosingCircleCursor operatorEnclosingCircleCursor = new OperatorEnclosingCircleCursor(new SimpleGeometryCursor(buffered), null, null); + Geometry geometry = operatorEnclosingCircleCursor.next(); + assertEquals(geometry.calculateArea2D(), buffered.calculateArea2D(), 1e-10); + assertTrue(GeometryEngine.equals(geometry, buffered, null)); + } + + @Test + public void testGeodesicBuffer() { + Point center = new Point(0, 0); + Geometry buffered = OperatorGeodesicBuffer.local().execute(center, + SpatialReference.create(4326), + GeodeticCurveType.Geodesic, + 4000, + 20, + false, + null); + + OperatorEnclosingCircleCursor operatorEnclosingCircleCursor = new OperatorEnclosingCircleCursor( + new SimpleGeometryCursor(buffered), + SpatialReference.create(4326), + null); + + Geometry geometry = operatorEnclosingCircleCursor.next(); + // This tests fails without multiVertexGeometry._setDirtyFlag(DirtyFlags.dirtyAll, true); in project + assertEquals(geometry.calculateArea2D(), buffered.calculateArea2D(), 1e-4); + + Geometry bufferedContainer = OperatorGeodesicBuffer.local().execute(center, + SpatialReference.create(4326), + GeodeticCurveType.Geodesic, + 4050, + 20, + false, + null); + assertTrue(GeometryEngine.contains(bufferedContainer, geometry, SpatialReference.create(4326))); + } + + @Test + public void testBufferedClipped() { + Point center = new Point(0, 0); + Geometry buffered = GeometryEngine.buffer(center, SpatialReference.create(4326), 20); + Geometry clipped = GeometryEngine.clip(buffered, new Envelope(0, -20, 40, 40), null); + OperatorEnclosingCircleCursor operatorEnclosingCircleCursor = new OperatorEnclosingCircleCursor(new SimpleGeometryCursor(clipped), null, null); + Geometry geometry = operatorEnclosingCircleCursor.next(); + assertEquals(geometry.calculateArea2D(), buffered.calculateArea2D(), 1e-10); + assertTrue(GeometryEngine.equals(geometry, buffered, null)); + } + + @Test + public void testBufferedClippedUnionedSmall() { + Point center = new Point(0, 0); + Geometry buffered = GeometryEngine.buffer(center, SpatialReference.create(4326), 20); + Geometry clipped = GeometryEngine.clip(buffered, new Envelope(0, -20, 40, 40), null); + Geometry bufferedSmall = GeometryEngine.buffer(center, SpatialReference.create(4326), 10); + Geometry[] two = new Geometry[]{bufferedSmall, clipped}; + Geometry unionedGeom = GeometryEngine.union(two, null); + OperatorEnclosingCircleCursor operatorEnclosingCircleCursor = new OperatorEnclosingCircleCursor(new SimpleGeometryCursor(unionedGeom), null, null); + Geometry geometry = operatorEnclosingCircleCursor.next(); + assertEquals(geometry.calculateArea2D(), buffered.calculateArea2D(), 1e-10); + assertTrue(GeometryEngine.equals(geometry, buffered, null)); + } + + + @Test + public void testRandomPoints() { + int count = 400; + Envelope e = new Envelope(0, 0, 40, 40); + RandomCoordinateGenerator randomCoordinateGenerator = new RandomCoordinateGenerator(count, e, SpatialReference.create(4326).getTolerance()); + MultiPoint multiPoint = new MultiPoint(); + for (int i = 0; i < count; i++) { + multiPoint.add(randomCoordinateGenerator._GenerateNewPoint()); + } + + int run_count = 10; + ArrayDeque geometries = new ArrayDeque<>(); + for (int i = 0; i < run_count; i++) { + OperatorEnclosingCircleCursor operatorEnclosingCircleCursor = new OperatorEnclosingCircleCursor(new SimpleGeometryCursor(multiPoint), SpatialReference.create(4326), null); + Geometry geometry = operatorEnclosingCircleCursor.next(); + geometries.add(geometry); + } // http://opensourceconnections.com/blog/2014/04/11/indexing-polygons-in-lucene-with-accuracy/ // http://my-spatial4j-project.blogspot.be/2014/01/minimum-bounding-circle-algorithm-jts.html - Geometry firstGeometry = geometries.peekFirst(); - OperatorEquals operatorEquals = (OperatorEquals) (OperatorFactoryLocal.getInstance().getOperator(Operator.Type.Equals)); - HashMap results = operatorEquals.execute(firstGeometry, new SimpleGeometryCursor(geometries), SpatialReference.create(4326), null); - for (Long key : results.keySet()) { - assertTrue(results.get(key)); - } - } - - @Test - public void testReproject() { - String wktGeom = "MULTIPOLYGON (((-70.64802717477117 43.128706076602874, -70.6479465414909 43.128714649179535, -70.64788357509747 43.1287487128581, -70.64783854531692 43.12880812176545, -70.64781164513992 43.12889262152996, -70.64780298999258 43.12900185036985, -70.64781261723796 43.12913534064154, -70.6478404860113 43.12929252084091, -70.64788647738838 43.129472718047886, -70.64795039488814 43.129675160806016, -70.6480319653057 43.129898982422475, -70.64813083987421 43.13014322467642, -70.64824659574889 43.13040684191825, -70.64837873780841 43.13068870554328, -70.64852670076544 43.1309876088204, -70.64868985157774 43.131302272055365, -70.64886749214907 43.13163134806583, -70.64905886230987 43.13197342794668, -70.64926314306291 43.132327047097704, -70.64947946008277 43.13269069149233, -70.64970688745207 43.13306280415637, -70.64994445161977 43.13344179183187, -70.65019113556465 43.133826031796644, -70.65044588314564 43.134213878809994, -70.6507076036211 43.13460367215594, -70.65097517631722 43.134993742752336, -70.6512474554263 43.135382420297674, -70.65152327491325 43.135768040422036, -70.65180145351054 43.13614895181459, -70.65208079977953 43.13652352329523, -70.652360117216 43.13689015080003, -70.65263820937935 43.1372472642522, -70.65291388502193 43.137593334287445, -70.65318596319706 43.13792687880496, -70.65345327832425 43.138246469317686, -70.65371468518873 43.138550737072336, -70.65396906385456 43.138838378914386, -70.65421532446969 43.139108162872276, -70.65445241194281 43.139358933437414, -70.65467931047083 43.13958961651619, -70.6548950478984 43.13979922403416, -70.65509869988989 43.1399868581709, -70.65528939389672 43.14015171520967, -70.65546631290157 43.14029308898237, -70.65562869892466 43.140410373897815, -70.65577585627611 43.140503067538305, -70.65590715454054 43.14057077281427, -70.65602203128121 43.14061319966741, -70.65611999445184 43.14063016631512, -70.65620062450573 43.14062160003068, -70.65626357619321 43.14058753745568, -70.65630858004002 43.14052812444448, -70.65633544349907 43.14044361543887, -70.65634405177256 43.1403343723787, -70.6563343682994 43.14020086315083, -70.6563064349065 43.14004365958389, -70.65626037162373 43.13986343499769, -70.65619637616243 43.13966096131707, -70.65611472306145 43.13943710576406, -70.65601576250236 43.139192827140086, -70.65589991880132 43.13892917171714, -70.65576768858229 43.13864726875311, -70.65561963864108 43.13834832565221, -70.65545640350835 43.13803362279019, -70.65527868272287 43.13770450802718, -70.65508723782693 43.13736239093172, -70.65488288909637 43.137008736740526, -70.65466651201972 43.13664506007974, -70.65443903354196 43.136272918475676, -70.6542014280886 43.13589390568142, -70.65395471338735 43.13550964484906, -70.65369994610545 43.13512178157618, -70.65343821732164 43.13473197685713, -70.65317064785184 43.13434189996841, -70.65289838344866 43.1339532213195, -70.6526225898955 43.133567605299014, -70.65234444801612 43.13318670314755, -70.652065148621 43.13281214588721, -70.65178588741227 43.132445537338526, -70.6515078598685 43.13208844725394, -70.65123225613159 43.13174240459792, -70.65096025591802 43.13140889100291, -70.65069302347425 43.131089334426754, -70.65043170260032 43.13078510304208, -70.65017741176095 43.130497499381256, -70.64993123930526 43.130227754762316, -70.64969423881631 43.12997702402096, -70.64946742460877 43.12974638056925, -70.64925176739531 43.12953681180378, -70.64904819013898 43.12934921488169, -70.64885756410969 43.12918439288311, -70.64868070516164 43.12904305137656, -70.64851837024698 43.128925795401116, -70.64837125418116 43.12883312687935, -70.64823998667316 43.12876544247107, -70.64812512963337 43.128723031877364, -70.64802717477117 43.128706076602874)))"; - Geometry geometry = OperatorImportFromWkt.local().execute(0, Geometry.Type.Unknown, wktGeom, null); - ProjectionTransformation projectionTransformation = ProjectionTransformation.getEqualArea(geometry, SpatialReference.create(4326)); - Geometry projected = OperatorProject.local().execute(geometry, projectionTransformation, null); - Envelope envelope = new Envelope(); - projected.queryEnvelope(envelope); - Point2D centerXY = envelope.getCenter2D(); - - double minRadius = Double.MAX_VALUE; - double maxRadius = Double.MIN_VALUE; - for (Point2D point2D : ((MultiVertexGeometry)geometry).getCoordinates2D()) { - double radius = Point2D.distance(point2D, centerXY); - if (radius > maxRadius) { - maxRadius = radius; - } - if (radius < minRadius) { - minRadius = radius; - } - } - assertEquals(minRadius, maxRadius, 0.01); - } - - @Test - public void testVsGeodesicBuffer() { - // POINT (-70.651886936570745 43.134525834585826) - Point point = new Point(-70.651886936570745, 43.1345088834585826); - SpatialReference spatialReferenceWGS84 = SpatialReference.create(4326); - Geometry geodesicBuffered = OperatorGeodesicBuffer.local().execute( - point, - spatialReferenceWGS84, - GeodeticCurveType.Geodesic, - 148.7, - 0.07, - false, - null); - - String wktInput = "POLYGON ((-70.651656936570745 43.135157834585826,-70.651570654984525 43.135150076925918,-70.651511247908587 43.135155437576621,-70.651490101488349 43.135169249238288,-70.651490181628759 43.135200763774868,-70.651510336532638 43.135249995303397,-70.651530872098633 43.135299226354952,-70.651527230781454 43.13533529757845,-70.651492643684321 43.135349305799508,-70.651420172717224 43.135354852495823,-70.651358095085072 43.135346745253884,-70.651247805462717 43.1353033127629,-70.651151361236941 43.135246176463966,-70.651124154255413 43.135206047556842,-70.651131534466685 43.135151913051203,-70.651155914561755 43.135115542493892,-70.651228730259774 43.135051460048707,-70.651305024993519 43.134973818456416,-70.651360674948108 43.134914489191551,-70.651385078643216 43.134864606853156,-70.651402827815218 43.134810322639233,-70.651410310326284 43.134688652284062,-70.651418383361829 43.134517448375689,-70.651411370788409 43.134413996600074,-70.651390876709428 43.134337752182525,-70.651346755889904 43.134225832686319,-70.651289063395154 43.134109606122337,-70.651241242017051 43.133988734078358,-70.651230854601764 43.133916846697829,-70.651221013347353 43.133822439140715,-70.651200519724128 43.133746194674302,-70.651218268699594 43.133691907971006,-70.651249886578313 43.133610413350418,-70.651273539332081 43.133547040518167,-70.651322422674269 43.133478800504768,-70.651363984222755 43.133424172537978,-70.651409243106613 43.133378494590104,-70.651437060507462 43.133355583224628,-70.651492145648632 43.133332275678633,-70.651599176252716 43.133326227462319,-70.651675204034788 43.133338636120527,-70.65179240423565 43.133381970944697,-70.651905761872953 43.133425353603045,-70.652022336036893 43.133459688797871,-70.652087607730962 43.133472252516562,-70.652149929372143 43.133489361765591,-70.652232540703992 43.133546699550045,-70.652400720753761 43.133656829257461,-70.652544825855031 43.133771806958798,-70.652698657471404 43.133891149422396,-70.652757214424 43.133939825915917,-70.652829643131227 43.133961291723317,-70.652898471779253 43.133991812912122,-70.652963804959569 43.134049399817712,-70.652994471176711 43.134089475795044,-70.653000937222828 43.134129903976664,-70.653000836372897 43.134197439788288,-70.652972837499135 43.134256375321229,-70.652903321638362 43.134342923487154,-70.652853489821567 43.134447199036558,-70.652811504521395 43.134528843881576,-70.652776716073816 43.134592380423527,-70.652745241323998 43.134664872485907,-70.652703717209832 43.134692485155824,-70.65263790191085 43.134702441934749,-70.652537796343609 43.13469488220742,-70.652481986521252 43.134691185475688,-70.652416288981371 43.134705643385693,-70.652347112331768 43.134733655368471,-70.652274294763117 43.134797738553125,-70.652239263183958 43.134852272522785,-70.652214826278225 43.134929165745028,-70.652217533236211 43.135001163954207,-70.652248296977746 43.135059250554882,-70.652327397729081 43.135157158057844,-70.652414500765317 43.135209927851037,-70.6524582645621 43.135222802033766,-70.652470512866358 43.135249637832572,-70.652646824360005 43.135404671730349,-70.652680946443695 43.135444700365824,-70.652691113545444 43.135494075980539,-70.652680299690047 43.135534756334458,-70.652642334694633 43.135580326883741,-70.6525831461027 43.135608197219,-70.65250042683256 43.135618395612582,-70.652403778584315 43.1356107883392,-70.652328034290349 43.135580366756479,-70.652207940292428 43.135501055652128,-70.652084166050003 43.135399292820672,-70.651994153636025 43.135324055019495,-70.651897729053019 43.13525341019011,-70.651815354685922 43.135205077192047,-70.651746646220062 43.135179051560065,-70.651656936570745 43.135157834585826))"; - Geometry input = OperatorImportFromWkt.local().execute(0, Geometry.Type.Unknown, wktInput, null); - Geometry encircled = OperatorEnclosingCircle.local().execute(input, spatialReferenceWGS84, null); + Geometry firstGeometry = geometries.peekFirst(); + OperatorEquals operatorEquals = (OperatorEquals) (OperatorFactoryLocal.getInstance().getOperator(Operator.Type.Equals)); + HashMap results = operatorEquals.execute(firstGeometry, new SimpleGeometryCursor(geometries), SpatialReference.create(4326), null); + for (Long key : results.keySet()) { + assertTrue(results.get(key)); + } + } + + @Test + public void testReproject() { + String wktGeom = "MULTIPOLYGON (((-70.64802717477117 43.128706076602874, -70.6479465414909 43.128714649179535, -70.64788357509747 43.1287487128581, -70.64783854531692 43.12880812176545, -70.64781164513992 43.12889262152996, -70.64780298999258 43.12900185036985, -70.64781261723796 43.12913534064154, -70.6478404860113 43.12929252084091, -70.64788647738838 43.129472718047886, -70.64795039488814 43.129675160806016, -70.6480319653057 43.129898982422475, -70.64813083987421 43.13014322467642, -70.64824659574889 43.13040684191825, -70.64837873780841 43.13068870554328, -70.64852670076544 43.1309876088204, -70.64868985157774 43.131302272055365, -70.64886749214907 43.13163134806583, -70.64905886230987 43.13197342794668, -70.64926314306291 43.132327047097704, -70.64947946008277 43.13269069149233, -70.64970688745207 43.13306280415637, -70.64994445161977 43.13344179183187, -70.65019113556465 43.133826031796644, -70.65044588314564 43.134213878809994, -70.6507076036211 43.13460367215594, -70.65097517631722 43.134993742752336, -70.6512474554263 43.135382420297674, -70.65152327491325 43.135768040422036, -70.65180145351054 43.13614895181459, -70.65208079977953 43.13652352329523, -70.652360117216 43.13689015080003, -70.65263820937935 43.1372472642522, -70.65291388502193 43.137593334287445, -70.65318596319706 43.13792687880496, -70.65345327832425 43.138246469317686, -70.65371468518873 43.138550737072336, -70.65396906385456 43.138838378914386, -70.65421532446969 43.139108162872276, -70.65445241194281 43.139358933437414, -70.65467931047083 43.13958961651619, -70.6548950478984 43.13979922403416, -70.65509869988989 43.1399868581709, -70.65528939389672 43.14015171520967, -70.65546631290157 43.14029308898237, -70.65562869892466 43.140410373897815, -70.65577585627611 43.140503067538305, -70.65590715454054 43.14057077281427, -70.65602203128121 43.14061319966741, -70.65611999445184 43.14063016631512, -70.65620062450573 43.14062160003068, -70.65626357619321 43.14058753745568, -70.65630858004002 43.14052812444448, -70.65633544349907 43.14044361543887, -70.65634405177256 43.1403343723787, -70.6563343682994 43.14020086315083, -70.6563064349065 43.14004365958389, -70.65626037162373 43.13986343499769, -70.65619637616243 43.13966096131707, -70.65611472306145 43.13943710576406, -70.65601576250236 43.139192827140086, -70.65589991880132 43.13892917171714, -70.65576768858229 43.13864726875311, -70.65561963864108 43.13834832565221, -70.65545640350835 43.13803362279019, -70.65527868272287 43.13770450802718, -70.65508723782693 43.13736239093172, -70.65488288909637 43.137008736740526, -70.65466651201972 43.13664506007974, -70.65443903354196 43.136272918475676, -70.6542014280886 43.13589390568142, -70.65395471338735 43.13550964484906, -70.65369994610545 43.13512178157618, -70.65343821732164 43.13473197685713, -70.65317064785184 43.13434189996841, -70.65289838344866 43.1339532213195, -70.6526225898955 43.133567605299014, -70.65234444801612 43.13318670314755, -70.652065148621 43.13281214588721, -70.65178588741227 43.132445537338526, -70.6515078598685 43.13208844725394, -70.65123225613159 43.13174240459792, -70.65096025591802 43.13140889100291, -70.65069302347425 43.131089334426754, -70.65043170260032 43.13078510304208, -70.65017741176095 43.130497499381256, -70.64993123930526 43.130227754762316, -70.64969423881631 43.12997702402096, -70.64946742460877 43.12974638056925, -70.64925176739531 43.12953681180378, -70.64904819013898 43.12934921488169, -70.64885756410969 43.12918439288311, -70.64868070516164 43.12904305137656, -70.64851837024698 43.128925795401116, -70.64837125418116 43.12883312687935, -70.64823998667316 43.12876544247107, -70.64812512963337 43.128723031877364, -70.64802717477117 43.128706076602874)))"; + Geometry geometry = OperatorImportFromWkt.local().execute(0, Geometry.Type.Unknown, wktGeom, null); + ProjectionTransformation projectionTransformation = ProjectionTransformation.getEqualArea(geometry, SpatialReference.create(4326)); + Geometry projected = OperatorProject.local().execute(geometry, projectionTransformation, null); + Envelope envelope = new Envelope(); + projected.queryEnvelope(envelope); + Point2D centerXY = envelope.getCenter2D(); + + double minRadius = Double.MAX_VALUE; + double maxRadius = Double.MIN_VALUE; + for (Point2D point2D : ((MultiVertexGeometry) geometry).getCoordinates2D()) { + double radius = Point2D.distance(point2D, centerXY); + if (radius > maxRadius) { + maxRadius = radius; + } + if (radius < minRadius) { + minRadius = radius; + } + } + assertEquals(minRadius, maxRadius, 0.01); + } + + @Test + public void testVsGeodesicBuffer() { + // POINT (-70.651886936570745 43.134525834585826) + Point point = new Point(-70.651886936570745, 43.1345088834585826); + SpatialReference spatialReferenceWGS84 = SpatialReference.create(4326); + Geometry geodesicBuffered = OperatorGeodesicBuffer.local().execute( + point, + spatialReferenceWGS84, + GeodeticCurveType.Geodesic, + 148.7, + 0.07, + false, + null); + + String wktInput = "POLYGON ((-70.651656936570745 43.135157834585826,-70.651570654984525 43.135150076925918,-70.651511247908587 43.135155437576621,-70.651490101488349 43.135169249238288,-70.651490181628759 43.135200763774868,-70.651510336532638 43.135249995303397,-70.651530872098633 43.135299226354952,-70.651527230781454 43.13533529757845,-70.651492643684321 43.135349305799508,-70.651420172717224 43.135354852495823,-70.651358095085072 43.135346745253884,-70.651247805462717 43.1353033127629,-70.651151361236941 43.135246176463966,-70.651124154255413 43.135206047556842,-70.651131534466685 43.135151913051203,-70.651155914561755 43.135115542493892,-70.651228730259774 43.135051460048707,-70.651305024993519 43.134973818456416,-70.651360674948108 43.134914489191551,-70.651385078643216 43.134864606853156,-70.651402827815218 43.134810322639233,-70.651410310326284 43.134688652284062,-70.651418383361829 43.134517448375689,-70.651411370788409 43.134413996600074,-70.651390876709428 43.134337752182525,-70.651346755889904 43.134225832686319,-70.651289063395154 43.134109606122337,-70.651241242017051 43.133988734078358,-70.651230854601764 43.133916846697829,-70.651221013347353 43.133822439140715,-70.651200519724128 43.133746194674302,-70.651218268699594 43.133691907971006,-70.651249886578313 43.133610413350418,-70.651273539332081 43.133547040518167,-70.651322422674269 43.133478800504768,-70.651363984222755 43.133424172537978,-70.651409243106613 43.133378494590104,-70.651437060507462 43.133355583224628,-70.651492145648632 43.133332275678633,-70.651599176252716 43.133326227462319,-70.651675204034788 43.133338636120527,-70.65179240423565 43.133381970944697,-70.651905761872953 43.133425353603045,-70.652022336036893 43.133459688797871,-70.652087607730962 43.133472252516562,-70.652149929372143 43.133489361765591,-70.652232540703992 43.133546699550045,-70.652400720753761 43.133656829257461,-70.652544825855031 43.133771806958798,-70.652698657471404 43.133891149422396,-70.652757214424 43.133939825915917,-70.652829643131227 43.133961291723317,-70.652898471779253 43.133991812912122,-70.652963804959569 43.134049399817712,-70.652994471176711 43.134089475795044,-70.653000937222828 43.134129903976664,-70.653000836372897 43.134197439788288,-70.652972837499135 43.134256375321229,-70.652903321638362 43.134342923487154,-70.652853489821567 43.134447199036558,-70.652811504521395 43.134528843881576,-70.652776716073816 43.134592380423527,-70.652745241323998 43.134664872485907,-70.652703717209832 43.134692485155824,-70.65263790191085 43.134702441934749,-70.652537796343609 43.13469488220742,-70.652481986521252 43.134691185475688,-70.652416288981371 43.134705643385693,-70.652347112331768 43.134733655368471,-70.652274294763117 43.134797738553125,-70.652239263183958 43.134852272522785,-70.652214826278225 43.134929165745028,-70.652217533236211 43.135001163954207,-70.652248296977746 43.135059250554882,-70.652327397729081 43.135157158057844,-70.652414500765317 43.135209927851037,-70.6524582645621 43.135222802033766,-70.652470512866358 43.135249637832572,-70.652646824360005 43.135404671730349,-70.652680946443695 43.135444700365824,-70.652691113545444 43.135494075980539,-70.652680299690047 43.135534756334458,-70.652642334694633 43.135580326883741,-70.6525831461027 43.135608197219,-70.65250042683256 43.135618395612582,-70.652403778584315 43.1356107883392,-70.652328034290349 43.135580366756479,-70.652207940292428 43.135501055652128,-70.652084166050003 43.135399292820672,-70.651994153636025 43.135324055019495,-70.651897729053019 43.13525341019011,-70.651815354685922 43.135205077192047,-70.651746646220062 43.135179051560065,-70.651656936570745 43.135157834585826))"; + Geometry input = OperatorImportFromWkt.local().execute(0, Geometry.Type.Unknown, wktInput, null); + Geometry encircled = OperatorEnclosingCircle.local().execute(input, spatialReferenceWGS84, null); // assertEquals(encircled.calculateArea2D(), geodesicBuffered.calculateArea2D(), 1e-8); // assertEquals(0.0, OperatorDifference.local().execute(encircled, geodesicBuffered, spatialReferenceWGS84, null).calculateArea2D(), 1e-8); - assertTrue(GeometryEngine.contains(geodesicBuffered, encircled, spatialReferenceWGS84)); - } + assertTrue(GeometryEngine.contains(geodesicBuffered, encircled, spatialReferenceWGS84)); + } // @Test // public void testCircleVertexContain() { diff --git a/src/test/java/com/esri/core/geometry/TestClip.java b/src/test/java/com/esri/core/geometry/TestClip.java index 544a3ce4..3dcca075 100644 --- a/src/test/java/com/esri/core/geometry/TestClip.java +++ b/src/test/java/com/esri/core/geometry/TestClip.java @@ -28,390 +28,390 @@ import org.junit.Test; public class TestClip extends TestCase { - @Override - protected void setUp() throws Exception { - super.setUp(); - } - - @Override - protected void tearDown() throws Exception { - super.tearDown(); - } - - @Test - public static void testClipGeometries() { - // RandomTest(); - OperatorFactoryLocal engine = OperatorFactoryLocal.getInstance(); - OperatorClip clipOp = (OperatorClip) engine - .getOperator(Operator.Type.Clip); - - Polygon polygon = makePolygon(); - SimpleGeometryCursor polygonCurs = new SimpleGeometryCursor(polygon); - - Polyline polyline = makePolyline(); - SimpleGeometryCursor polylineCurs = new SimpleGeometryCursor(polyline); - - MultiPoint multipoint = makeMultiPoint(); - SimpleGeometryCursor multipointCurs = new SimpleGeometryCursor( - multipoint); - - Point point = makePoint(); - SimpleGeometryCursor pointCurs = new SimpleGeometryCursor(point); - - SpatialReference spatialRef = SpatialReference.create(3857); - - Envelope2D envelope = new Envelope2D(); - envelope.xmin = 0; - envelope.xmax = 20; - envelope.ymin = 5; - envelope.ymax = 15; - - // Cursor implementation - GeometryCursor clipPolygonCurs = clipOp.execute(polygonCurs, envelope, - spatialRef, null); - Polygon clippedPolygon = (Polygon) clipPolygonCurs.next(); - double area = clippedPolygon.calculateArea2D(); - assertTrue(Math.abs(area - 25) < 0.00001); - - // Single Geometry implementation - clippedPolygon = (Polygon) clipOp.execute(polygon, envelope, - spatialRef, null); - area = clippedPolygon.calculateArea2D(); - assertTrue(Math.abs(area - 25) < 0.00001); - - // Cursor implementation - GeometryCursor clipPolylineCurs = clipOp.execute(polylineCurs, - envelope, spatialRef, null); - Polyline clippedPolyline = (Polyline) clipPolylineCurs.next(); - double length = clippedPolyline.calculateLength2D(); - assertTrue(Math.abs(length - 10 * Math.sqrt(2.0)) < 1e-10); - - // Single Geometry implementation - clippedPolyline = (Polyline) clipOp.execute(polyline, envelope, - spatialRef, null); - length = clippedPolyline.calculateLength2D(); - assertTrue(Math.abs(length - 10 * Math.sqrt(2.0)) < 1e-10); - - // Cursor implementation - GeometryCursor clipMulti_pointCurs = clipOp.execute(multipointCurs, - envelope, spatialRef, null); - MultiPoint clipped_multi_point = (MultiPoint) clipMulti_pointCurs - .next(); - int pointCount = clipped_multi_point.getPointCount(); - assertTrue(pointCount == 2); - - // Cursor implementation - GeometryCursor clipPointCurs = clipOp.execute(pointCurs, envelope, - spatialRef, null); - Point clippedPoint = (Point) clipPointCurs.next(); - assertTrue(clippedPoint != null); - - // RandomTest(); - - Polyline _poly = new Polyline(); - _poly.startPath(2, 2); - _poly.lineTo(0, 0); - - Envelope2D _env = new Envelope2D(); - _env.setCoords(2, 1, 5, 3); - - Polyline _clippedPolyline = (Polyline) clipOp.execute(_poly, _env, - spatialRef, null); - assertTrue(_clippedPolyline.isEmpty()); - - { - Polygon poly = new Polygon(); - poly.addEnvelope(new Envelope2D(0, 0, 100, 100), false); - poly.addEnvelope(new Envelope2D(5, 5, 95, 95), true); - - Polygon clippedPoly = (Polygon) clipOp.execute(poly, - new Envelope2D(-10, -10, 110, 50), spatialRef, null); - assertTrue(clippedPoly.getPathCount() == 1); - assertTrue(clippedPoly.getPointCount() == 8); - } - } - - static Polygon makePolygon() { - Polygon poly = new Polygon(); - poly.startPath(0, 0); - poly.lineTo(10, 10); - poly.lineTo(20, 0); - - return poly; - } - - static Polyline makePolyline() { - Polyline poly = new Polyline(); - poly.startPath(0, 0); - poly.lineTo(10, 10); - poly.lineTo(20, 0); - - return poly; - } - - @Test - public static void testGetXCorrectCR185697() { - OperatorFactoryLocal engine = OperatorFactoryLocal.getInstance(); - OperatorClip clipOp = (OperatorClip) engine - .getOperator(Operator.Type.Clip); - - Polyline polylineCR = makePolylineCR(); - @SuppressWarnings("unused") - SimpleGeometryCursor polylineCursCR = new SimpleGeometryCursor( - polylineCR); - - SpatialReference gcsWGS84 = SpatialReference.create(4326); - - Envelope2D envelopeCR = new Envelope2D(); - envelopeCR.xmin = -180; - envelopeCR.xmax = 180; - envelopeCR.ymin = -90; - envelopeCR.ymax = 90; - // CR - Polyline clippedPolylineCR = (Polyline) clipOp.execute(polylineCR, - envelopeCR, gcsWGS84, null); - Point pointResult = new Point(); - clippedPolylineCR.getPointByVal(0, pointResult); - assertTrue(pointResult.getX() == -180); - clippedPolylineCR.getPointByVal(1, pointResult); - assertTrue(pointResult.getX() == -90); - clippedPolylineCR.getPointByVal(2, pointResult); - assertTrue(pointResult.getX() == 0); - clippedPolylineCR.getPointByVal(3, pointResult); - assertTrue(pointResult.getX() == 100); - clippedPolylineCR.getPointByVal(4, pointResult); - assertTrue(pointResult.getX() == 170); - clippedPolylineCR.getPointByVal(5, pointResult); - assertTrue(pointResult.getX() == 180); - } - - @Test - public static void testArcObjectsFailureCR196492() { - OperatorFactoryLocal engine = OperatorFactoryLocal.getInstance(); - OperatorClip clipOp = (OperatorClip) engine - .getOperator(Operator.Type.Clip); - - Polygon polygon = new Polygon(); - polygon.addEnvelope(new Envelope2D(0, 0, 600, 600), false); - polygon.startPath(30, 300); - polygon.lineTo(20, 310); - polygon.lineTo(10, 300); - - SpatialReference gcsWGS84 = SpatialReference.create(4326); - - Envelope2D envelopeCR = new Envelope2D(10, 10, 500, 500); - - Polygon clippedPolygon = (Polygon) clipOp.execute(polygon, envelopeCR, - gcsWGS84, null); - assertTrue(clippedPolygon.getPointCount() == 7); - // ((MultiPathImpl::SPtr)clippedPolygon._GetImpl()).SaveToTextFileDbg("c:\\temp\\test_ArcObjects_failure_CR196492.txt"); - } - - static Polyline makePolylineCR() { - Polyline polyline = new Polyline(); - - polyline.startPath(-200, -90); - polyline.lineTo(-180, -85); - polyline.lineTo(-90, -70); - polyline.lineTo(0, 0); - polyline.lineTo(100, 25); - polyline.lineTo(170, 45); - polyline.lineTo(225, 65); - - return polyline; - } - - static MultiPoint makeMultiPoint() { - MultiPoint mpoint = new MultiPoint(); - - Point2D pt1 = new Point2D(); - pt1.x = 10; - pt1.y = 10; - - Point2D pt2 = new Point2D(); - pt2.x = 15; - pt2.y = 10; - - Point2D pt3 = new Point2D(); - pt3.x = 10; - pt3.y = 20; - - mpoint.add(pt1.x, pt1.y); - mpoint.add(pt2.x, pt2.y); - mpoint.add(pt3.x, pt3.y); - - return mpoint; - } - - static Point makePoint() { - Point point = new Point(); - - Point2D pt = new Point2D(); - pt.setCoords(10, 20); - point.setXY(pt); - - return point; - } - - @Test - public static void testClipOfCoinciding() { - OperatorFactoryLocal engine = OperatorFactoryLocal.getInstance(); - OperatorClip clipOp = (OperatorClip) engine - .getOperator(Operator.Type.Clip); - - Polygon polygon = new Polygon(); - Envelope2D envelopeCR = new Envelope2D(); - envelopeCR.xmin = -180; - envelopeCR.xmax = 180; - envelopeCR.ymin = -90; - envelopeCR.ymax = 90; - - polygon.addEnvelope(envelopeCR, false); - - SpatialReference gcsWGS84 = SpatialReference.create(4326); - // CR - Polygon clippedPolygon = (Polygon) clipOp.execute(polygon, envelopeCR, - gcsWGS84, null); - assertTrue(clippedPolygon.getPathCount() == 1); - assertTrue(clippedPolygon.getPointCount() == 4); - - OperatorDensifyByLength densifyOp = (OperatorDensifyByLength) engine - .getOperator(Operator.Type.DensifyByLength); - polygon.setEmpty(); - polygon.addEnvelope(envelopeCR, false); - polygon = (Polygon) densifyOp.execute(polygon, 1, null); - - int pc = polygon.getPointCount(); - int pathc = polygon.getPathCount(); - assertTrue(pc == 1080); - assertTrue(pathc == 1); - clippedPolygon = (Polygon) clipOp.execute(polygon, envelopeCR, - gcsWGS84, null); - int _pathc = clippedPolygon.getPathCount(); - int _pc = clippedPolygon.getPointCount(); - assertTrue(_pathc == 1); - assertTrue(_pc == pc); - } - - @Test - public static void testClipAttributes() { - OperatorFactoryLocal engine = OperatorFactoryLocal.getInstance(); - OperatorClip clipOp = (OperatorClip) engine - .getOperator(Operator.Type.Clip); - { - Polygon polygon = new Polygon(); - polygon.addAttribute(VertexDescription.Semantics.M); - - polygon.startPath(0, 0); - polygon.lineTo(30, 30); - polygon.lineTo(60, 0); - - polygon.setAttribute(VertexDescription.Semantics.M, 0, 0, 0); - polygon.setAttribute(VertexDescription.Semantics.M, 1, 0, 60); - polygon.setAttribute(VertexDescription.Semantics.M, 2, 0, 120); - - Envelope2D clipper = new Envelope2D(); - clipper.setCoords(10, 0, 50, 20); - Polygon clippedPolygon = (Polygon) clipOp.execute(polygon, clipper, - SpatialReference.create(4326), null); - - assertTrue(clippedPolygon.getAttributeAsDbl( - VertexDescription.Semantics.M, 0, 0) == 100); - assertTrue(clippedPolygon.getAttributeAsDbl( - VertexDescription.Semantics.M, 1, 0) == 19.999999999999996); // 20.0 - assertTrue(clippedPolygon.getAttributeAsDbl( - VertexDescription.Semantics.M, 2, 0) == 20); - assertTrue(clippedPolygon.getAttributeAsDbl( - VertexDescription.Semantics.M, 3, 0) == 40); - assertTrue(clippedPolygon.getAttributeAsDbl( - VertexDescription.Semantics.M, 4, 0) == 80); - assertTrue(clippedPolygon.getAttributeAsDbl( - VertexDescription.Semantics.M, 5, 0) == 100); - } - - { - Polygon polygon = new Polygon(); - polygon.addAttribute(VertexDescription.Semantics.M); - - polygon.startPath(0, 0); - polygon.lineTo(0, 40); - polygon.lineTo(20, 40); - polygon.lineTo(20, 0); - - polygon.setAttribute(VertexDescription.Semantics.M, 0, 0, 0); - polygon.setAttribute(VertexDescription.Semantics.M, 1, 0, 60); - polygon.setAttribute(VertexDescription.Semantics.M, 2, 0, 120); - polygon.setAttribute(VertexDescription.Semantics.M, 3, 0, 180); - - Envelope2D clipper = new Envelope2D(); - clipper.setCoords(0, 10, 20, 20); - Polygon clippedPolygon = (Polygon) clipOp.execute(polygon, clipper, - SpatialReference.create(4326), null); - - assertTrue(clippedPolygon.getAttributeAsDbl( - VertexDescription.Semantics.M, 0, 0) == 15); - assertTrue(clippedPolygon.getAttributeAsDbl( - VertexDescription.Semantics.M, 1, 0) == 30); - assertTrue(clippedPolygon.getAttributeAsDbl( - VertexDescription.Semantics.M, 2, 0) == 150); - assertTrue(clippedPolygon.getAttributeAsDbl( - VertexDescription.Semantics.M, 3, 0) == 165); - } - } - - @Test - public static void testClipIssue258243() { - Polygon poly1 = new Polygon(); - poly1.startPath(21.476191371901479, 41.267022001907215); - poly1.lineTo(59.669186665158051, 36.62700518555863); - poly1.lineTo(20.498578117352313, 30.363180148246094); - poly1.lineTo(18.342565836615044, 46.303295352085627); - poly1.lineTo(17.869569458621626, 23.886816966894159); - poly1.lineTo(19.835465558090434, 20); - poly1.lineTo(18.83911285048551, 43.515995498114791); - poly1.lineTo(20.864485260298004, 20.235921201027757); - poly1.lineTo(18.976127544787012, 20); - poly1.lineTo(34.290201277718218, 61.801369014954794); - poly1.lineTo(20.734727419368866, 20); - poly1.lineTo(18.545865698148113, 20); - poly1.lineTo(19.730260558565515, 20); - poly1.lineTo(19.924806216827005, 23.780315893949187); - poly1.lineTo(21.675168105421452, 36.699924873001258); - poly1.lineTo(22.500527828912158, 43.703424859922983); - poly1.lineTo(42.009527116514818, 36.995486982256089); - poly1.lineTo(24.469729873835782, 58.365871758247039); - poly1.lineTo(24.573736036545878, 36.268390409195824); - poly1.lineTo(22.726502169802746, 20); - poly1.lineTo(23.925834885228145, 20); - poly1.lineTo(25.495346880936729, 20); - poly1.lineTo(23.320941499288317, 20); - poly1.lineTo(24.05655665646276, 28.659578774758632); - poly1.lineTo(23.205940789341135, 38.491506888710504); - poly1.lineTo(21.472847203385509, 53.057228182018044); - poly1.lineTo(25.04257681654104, 20); - poly1.lineTo(25.880572351149542, 25.16102863979474); - poly1.lineTo(26.756283333879658, 20); - poly1.lineTo(21.476191371901479, 41.267022001907215); - Envelope2D env = new Envelope2D(); - env.setCoords(24.269517325186033, 19.999998900000001, - 57.305574253225409, 61.801370114954793); - - try { - Geometry output_geom = OperatorClip.local().execute(poly1, env, - SpatialReference.create(4326), null); - Envelope envPoly = new Envelope(); - poly1.queryEnvelope(envPoly); - Envelope e = new Envelope(env); - e.intersect(envPoly); - Envelope clippedEnv = new Envelope(); - output_geom.queryEnvelope(clippedEnv); - assertTrue(Math.abs(clippedEnv.getXMin() - e.getXMin()) < 1e-10 && - Math.abs(clippedEnv.getYMin() - e.getYMin()) < 1e-10 && - Math.abs(clippedEnv.getXMax() - e.getXMax()) < 1e-10 && - Math.abs(clippedEnv.getYMax() - e.getYMax()) < 1e-10); - } catch (Exception e) { - assertTrue(false); - } - - } + @Override + protected void setUp() throws Exception { + super.setUp(); + } + + @Override + protected void tearDown() throws Exception { + super.tearDown(); + } + + @Test + public static void testClipGeometries() { + // RandomTest(); + OperatorFactoryLocal engine = OperatorFactoryLocal.getInstance(); + OperatorClip clipOp = (OperatorClip) engine + .getOperator(Operator.Type.Clip); + + Polygon polygon = makePolygon(); + SimpleGeometryCursor polygonCurs = new SimpleGeometryCursor(polygon); + + Polyline polyline = makePolyline(); + SimpleGeometryCursor polylineCurs = new SimpleGeometryCursor(polyline); + + MultiPoint multipoint = makeMultiPoint(); + SimpleGeometryCursor multipointCurs = new SimpleGeometryCursor( + multipoint); + + Point point = makePoint(); + SimpleGeometryCursor pointCurs = new SimpleGeometryCursor(point); + + SpatialReference spatialRef = SpatialReference.create(3857); + + Envelope2D envelope = new Envelope2D(); + envelope.xmin = 0; + envelope.xmax = 20; + envelope.ymin = 5; + envelope.ymax = 15; + + // Cursor implementation + GeometryCursor clipPolygonCurs = clipOp.execute(polygonCurs, envelope, + spatialRef, null); + Polygon clippedPolygon = (Polygon) clipPolygonCurs.next(); + double area = clippedPolygon.calculateArea2D(); + assertTrue(Math.abs(area - 25) < 0.00001); + + // Single Geometry implementation + clippedPolygon = (Polygon) clipOp.execute(polygon, envelope, + spatialRef, null); + area = clippedPolygon.calculateArea2D(); + assertTrue(Math.abs(area - 25) < 0.00001); + + // Cursor implementation + GeometryCursor clipPolylineCurs = clipOp.execute(polylineCurs, + envelope, spatialRef, null); + Polyline clippedPolyline = (Polyline) clipPolylineCurs.next(); + double length = clippedPolyline.calculateLength2D(); + assertTrue(Math.abs(length - 10 * Math.sqrt(2.0)) < 1e-10); + + // Single Geometry implementation + clippedPolyline = (Polyline) clipOp.execute(polyline, envelope, + spatialRef, null); + length = clippedPolyline.calculateLength2D(); + assertTrue(Math.abs(length - 10 * Math.sqrt(2.0)) < 1e-10); + + // Cursor implementation + GeometryCursor clipMulti_pointCurs = clipOp.execute(multipointCurs, + envelope, spatialRef, null); + MultiPoint clipped_multi_point = (MultiPoint) clipMulti_pointCurs + .next(); + int pointCount = clipped_multi_point.getPointCount(); + assertTrue(pointCount == 2); + + // Cursor implementation + GeometryCursor clipPointCurs = clipOp.execute(pointCurs, envelope, + spatialRef, null); + Point clippedPoint = (Point) clipPointCurs.next(); + assertTrue(clippedPoint != null); + + // RandomTest(); + + Polyline _poly = new Polyline(); + _poly.startPath(2, 2); + _poly.lineTo(0, 0); + + Envelope2D _env = new Envelope2D(); + _env.setCoords(2, 1, 5, 3); + + Polyline _clippedPolyline = (Polyline) clipOp.execute(_poly, _env, + spatialRef, null); + assertTrue(_clippedPolyline.isEmpty()); + + { + Polygon poly = new Polygon(); + poly.addEnvelope(new Envelope2D(0, 0, 100, 100), false); + poly.addEnvelope(new Envelope2D(5, 5, 95, 95), true); + + Polygon clippedPoly = (Polygon) clipOp.execute(poly, + new Envelope2D(-10, -10, 110, 50), spatialRef, null); + assertTrue(clippedPoly.getPathCount() == 1); + assertTrue(clippedPoly.getPointCount() == 8); + } + } + + static Polygon makePolygon() { + Polygon poly = new Polygon(); + poly.startPath(0, 0); + poly.lineTo(10, 10); + poly.lineTo(20, 0); + + return poly; + } + + static Polyline makePolyline() { + Polyline poly = new Polyline(); + poly.startPath(0, 0); + poly.lineTo(10, 10); + poly.lineTo(20, 0); + + return poly; + } + + @Test + public static void testGetXCorrectCR185697() { + OperatorFactoryLocal engine = OperatorFactoryLocal.getInstance(); + OperatorClip clipOp = (OperatorClip) engine + .getOperator(Operator.Type.Clip); + + Polyline polylineCR = makePolylineCR(); + @SuppressWarnings("unused") + SimpleGeometryCursor polylineCursCR = new SimpleGeometryCursor( + polylineCR); + + SpatialReference gcsWGS84 = SpatialReference.create(4326); + + Envelope2D envelopeCR = new Envelope2D(); + envelopeCR.xmin = -180; + envelopeCR.xmax = 180; + envelopeCR.ymin = -90; + envelopeCR.ymax = 90; + // CR + Polyline clippedPolylineCR = (Polyline) clipOp.execute(polylineCR, + envelopeCR, gcsWGS84, null); + Point pointResult = new Point(); + clippedPolylineCR.getPointByVal(0, pointResult); + assertTrue(pointResult.getX() == -180); + clippedPolylineCR.getPointByVal(1, pointResult); + assertTrue(pointResult.getX() == -90); + clippedPolylineCR.getPointByVal(2, pointResult); + assertTrue(pointResult.getX() == 0); + clippedPolylineCR.getPointByVal(3, pointResult); + assertTrue(pointResult.getX() == 100); + clippedPolylineCR.getPointByVal(4, pointResult); + assertTrue(pointResult.getX() == 170); + clippedPolylineCR.getPointByVal(5, pointResult); + assertTrue(pointResult.getX() == 180); + } + + @Test + public static void testArcObjectsFailureCR196492() { + OperatorFactoryLocal engine = OperatorFactoryLocal.getInstance(); + OperatorClip clipOp = (OperatorClip) engine + .getOperator(Operator.Type.Clip); + + Polygon polygon = new Polygon(); + polygon.addEnvelope(new Envelope2D(0, 0, 600, 600), false); + polygon.startPath(30, 300); + polygon.lineTo(20, 310); + polygon.lineTo(10, 300); + + SpatialReference gcsWGS84 = SpatialReference.create(4326); + + Envelope2D envelopeCR = new Envelope2D(10, 10, 500, 500); + + Polygon clippedPolygon = (Polygon) clipOp.execute(polygon, envelopeCR, + gcsWGS84, null); + assertTrue(clippedPolygon.getPointCount() == 7); + // ((MultiPathImpl::SPtr)clippedPolygon._GetImpl()).SaveToTextFileDbg("c:\\temp\\test_ArcObjects_failure_CR196492.txt"); + } + + static Polyline makePolylineCR() { + Polyline polyline = new Polyline(); + + polyline.startPath(-200, -90); + polyline.lineTo(-180, -85); + polyline.lineTo(-90, -70); + polyline.lineTo(0, 0); + polyline.lineTo(100, 25); + polyline.lineTo(170, 45); + polyline.lineTo(225, 65); + + return polyline; + } + + static MultiPoint makeMultiPoint() { + MultiPoint mpoint = new MultiPoint(); + + Point2D pt1 = new Point2D(); + pt1.x = 10; + pt1.y = 10; + + Point2D pt2 = new Point2D(); + pt2.x = 15; + pt2.y = 10; + + Point2D pt3 = new Point2D(); + pt3.x = 10; + pt3.y = 20; + + mpoint.add(pt1.x, pt1.y); + mpoint.add(pt2.x, pt2.y); + mpoint.add(pt3.x, pt3.y); + + return mpoint; + } + + static Point makePoint() { + Point point = new Point(); + + Point2D pt = new Point2D(); + pt.setCoords(10, 20); + point.setXY(pt); + + return point; + } + + @Test + public static void testClipOfCoinciding() { + OperatorFactoryLocal engine = OperatorFactoryLocal.getInstance(); + OperatorClip clipOp = (OperatorClip) engine + .getOperator(Operator.Type.Clip); + + Polygon polygon = new Polygon(); + Envelope2D envelopeCR = new Envelope2D(); + envelopeCR.xmin = -180; + envelopeCR.xmax = 180; + envelopeCR.ymin = -90; + envelopeCR.ymax = 90; + + polygon.addEnvelope(envelopeCR, false); + + SpatialReference gcsWGS84 = SpatialReference.create(4326); + // CR + Polygon clippedPolygon = (Polygon) clipOp.execute(polygon, envelopeCR, + gcsWGS84, null); + assertTrue(clippedPolygon.getPathCount() == 1); + assertTrue(clippedPolygon.getPointCount() == 4); + + OperatorDensifyByLength densifyOp = (OperatorDensifyByLength) engine + .getOperator(Operator.Type.DensifyByLength); + polygon.setEmpty(); + polygon.addEnvelope(envelopeCR, false); + polygon = (Polygon) densifyOp.execute(polygon, 1, null); + + int pc = polygon.getPointCount(); + int pathc = polygon.getPathCount(); + assertTrue(pc == 1080); + assertTrue(pathc == 1); + clippedPolygon = (Polygon) clipOp.execute(polygon, envelopeCR, + gcsWGS84, null); + int _pathc = clippedPolygon.getPathCount(); + int _pc = clippedPolygon.getPointCount(); + assertTrue(_pathc == 1); + assertTrue(_pc == pc); + } + + @Test + public static void testClipAttributes() { + OperatorFactoryLocal engine = OperatorFactoryLocal.getInstance(); + OperatorClip clipOp = (OperatorClip) engine + .getOperator(Operator.Type.Clip); + { + Polygon polygon = new Polygon(); + polygon.addAttribute(VertexDescription.Semantics.M); + + polygon.startPath(0, 0); + polygon.lineTo(30, 30); + polygon.lineTo(60, 0); + + polygon.setAttribute(VertexDescription.Semantics.M, 0, 0, 0); + polygon.setAttribute(VertexDescription.Semantics.M, 1, 0, 60); + polygon.setAttribute(VertexDescription.Semantics.M, 2, 0, 120); + + Envelope2D clipper = new Envelope2D(); + clipper.setCoords(10, 0, 50, 20); + Polygon clippedPolygon = (Polygon) clipOp.execute(polygon, clipper, + SpatialReference.create(4326), null); + + assertTrue(clippedPolygon.getAttributeAsDbl( + VertexDescription.Semantics.M, 0, 0) == 100); + assertTrue(clippedPolygon.getAttributeAsDbl( + VertexDescription.Semantics.M, 1, 0) == 19.999999999999996); // 20.0 + assertTrue(clippedPolygon.getAttributeAsDbl( + VertexDescription.Semantics.M, 2, 0) == 20); + assertTrue(clippedPolygon.getAttributeAsDbl( + VertexDescription.Semantics.M, 3, 0) == 40); + assertTrue(clippedPolygon.getAttributeAsDbl( + VertexDescription.Semantics.M, 4, 0) == 80); + assertTrue(clippedPolygon.getAttributeAsDbl( + VertexDescription.Semantics.M, 5, 0) == 100); + } + + { + Polygon polygon = new Polygon(); + polygon.addAttribute(VertexDescription.Semantics.M); + + polygon.startPath(0, 0); + polygon.lineTo(0, 40); + polygon.lineTo(20, 40); + polygon.lineTo(20, 0); + + polygon.setAttribute(VertexDescription.Semantics.M, 0, 0, 0); + polygon.setAttribute(VertexDescription.Semantics.M, 1, 0, 60); + polygon.setAttribute(VertexDescription.Semantics.M, 2, 0, 120); + polygon.setAttribute(VertexDescription.Semantics.M, 3, 0, 180); + + Envelope2D clipper = new Envelope2D(); + clipper.setCoords(0, 10, 20, 20); + Polygon clippedPolygon = (Polygon) clipOp.execute(polygon, clipper, + SpatialReference.create(4326), null); + + assertTrue(clippedPolygon.getAttributeAsDbl( + VertexDescription.Semantics.M, 0, 0) == 15); + assertTrue(clippedPolygon.getAttributeAsDbl( + VertexDescription.Semantics.M, 1, 0) == 30); + assertTrue(clippedPolygon.getAttributeAsDbl( + VertexDescription.Semantics.M, 2, 0) == 150); + assertTrue(clippedPolygon.getAttributeAsDbl( + VertexDescription.Semantics.M, 3, 0) == 165); + } + } + + @Test + public static void testClipIssue258243() { + Polygon poly1 = new Polygon(); + poly1.startPath(21.476191371901479, 41.267022001907215); + poly1.lineTo(59.669186665158051, 36.62700518555863); + poly1.lineTo(20.498578117352313, 30.363180148246094); + poly1.lineTo(18.342565836615044, 46.303295352085627); + poly1.lineTo(17.869569458621626, 23.886816966894159); + poly1.lineTo(19.835465558090434, 20); + poly1.lineTo(18.83911285048551, 43.515995498114791); + poly1.lineTo(20.864485260298004, 20.235921201027757); + poly1.lineTo(18.976127544787012, 20); + poly1.lineTo(34.290201277718218, 61.801369014954794); + poly1.lineTo(20.734727419368866, 20); + poly1.lineTo(18.545865698148113, 20); + poly1.lineTo(19.730260558565515, 20); + poly1.lineTo(19.924806216827005, 23.780315893949187); + poly1.lineTo(21.675168105421452, 36.699924873001258); + poly1.lineTo(22.500527828912158, 43.703424859922983); + poly1.lineTo(42.009527116514818, 36.995486982256089); + poly1.lineTo(24.469729873835782, 58.365871758247039); + poly1.lineTo(24.573736036545878, 36.268390409195824); + poly1.lineTo(22.726502169802746, 20); + poly1.lineTo(23.925834885228145, 20); + poly1.lineTo(25.495346880936729, 20); + poly1.lineTo(23.320941499288317, 20); + poly1.lineTo(24.05655665646276, 28.659578774758632); + poly1.lineTo(23.205940789341135, 38.491506888710504); + poly1.lineTo(21.472847203385509, 53.057228182018044); + poly1.lineTo(25.04257681654104, 20); + poly1.lineTo(25.880572351149542, 25.16102863979474); + poly1.lineTo(26.756283333879658, 20); + poly1.lineTo(21.476191371901479, 41.267022001907215); + Envelope2D env = new Envelope2D(); + env.setCoords(24.269517325186033, 19.999998900000001, + 57.305574253225409, 61.801370114954793); + + try { + Geometry output_geom = OperatorClip.local().execute(poly1, env, + SpatialReference.create(4326), null); + Envelope envPoly = new Envelope(); + poly1.queryEnvelope(envPoly); + Envelope e = new Envelope(env); + e.intersect(envPoly); + Envelope clippedEnv = new Envelope(); + output_geom.queryEnvelope(clippedEnv); + assertTrue(Math.abs(clippedEnv.getXMin() - e.getXMin()) < 1e-10 && + Math.abs(clippedEnv.getYMin() - e.getYMin()) < 1e-10 && + Math.abs(clippedEnv.getXMax() - e.getXMax()) < 1e-10 && + Math.abs(clippedEnv.getYMax() - e.getYMax()) < 1e-10); + } catch (Exception e) { + assertTrue(false); + } + + } } diff --git a/src/test/java/com/esri/core/geometry/TestCommonMethods.java b/src/test/java/com/esri/core/geometry/TestCommonMethods.java index 59e40789..39ca5b92 100644 --- a/src/test/java/com/esri/core/geometry/TestCommonMethods.java +++ b/src/test/java/com/esri/core/geometry/TestCommonMethods.java @@ -30,237 +30,237 @@ import java.io.*; public class TestCommonMethods extends TestCase { - public static boolean compareDouble(double a, double b, double tol) { - double diff = Math.abs(a - b); - return diff <= tol * Math.abs(a) + tol; - } - - public static Point[] pointsFromMultiPath(MultiPath geom) { - int numberOfPoints = geom.getPointCount(); - Point[] points = new Point[numberOfPoints]; - for (int i = 0; i < geom.getPointCount(); i++) { - points[i] = geom.getPoint(i); - } - return points; - } - - @Test - public static void compareGeometryContent(MultiVertexGeometry geom1, - MultiVertexGeometry geom2) { - // Geometry types - assertTrue(geom1.getType().value() == geom2.getType().value()); - - // Envelopes - Envelope2D env1 = new Envelope2D(); - geom1.queryEnvelope2D(env1); - - Envelope2D env2 = new Envelope2D(); - geom2.queryEnvelope2D(env2); - - assertTrue(env1.xmin == env2.xmin && env1.xmax == env2.xmax - && env1.ymin == env2.ymin && env1.ymax == env2.ymax); - - int type = geom1.getType().value(); - if (type == Geometry.GeometryType.Polyline - || type == Geometry.GeometryType.Polygon) { - // Part Count - int partCount1 = ((MultiPath) geom1).getPathCount(); - int partCount2 = ((MultiPath) geom2).getPathCount(); - assertTrue(partCount1 == partCount2); - - // Part indices - for (int i = 0; i < partCount1; i++) { - int start1 = ((MultiPath) geom1).getPathStart(i); - int start2 = ((MultiPath) geom2).getPathStart(i); - assertTrue(start1 == start2); - int end1 = ((MultiPath) geom1).getPathEnd(i); - int end2 = ((MultiPath) geom2).getPathEnd(i); - assertTrue(end1 == end2); - } - } - - // Point count - int pointCount1 = geom1.getPointCount(); - int pointCount2 = geom2.getPointCount(); - assertTrue(pointCount1 == pointCount2); - - if (type == Geometry.GeometryType.MultiPoint - || type == Geometry.GeometryType.Polyline - || type == Geometry.GeometryType.Polygon) { - // POSITION - AttributeStreamBase positionStream1 = ((MultiVertexGeometryImpl) geom1 - ._getImpl()) - .getAttributeStreamRef(VertexDescription.Semantics.POSITION); - AttributeStreamOfDbl position1 = (AttributeStreamOfDbl) (positionStream1); - - AttributeStreamBase positionStream2 = ((MultiVertexGeometryImpl) geom2 - ._getImpl()) - .getAttributeStreamRef(VertexDescription.Semantics.POSITION); - AttributeStreamOfDbl position2 = (AttributeStreamOfDbl) (positionStream2); - - for (int i = 0; i < pointCount1; i++) { - double x1 = position1.read(2 * i); - double x2 = position2.read(2 * i); - assertTrue(x1 == x2); - - double y1 = position1.read(2 * i + 1); - double y2 = position2.read(2 * i + 1); - assertTrue(y1 == y2); - } - - // Zs - boolean bHasZs1 = geom1.hasAttribute(VertexDescription.Semantics.Z); - boolean bHasZs2 = geom2.hasAttribute(VertexDescription.Semantics.Z); - assertTrue(bHasZs1 == bHasZs2); - - if (bHasZs1) { - AttributeStreamBase zStream1 = ((MultiVertexGeometryImpl) geom1 - ._getImpl()) - .getAttributeStreamRef(VertexDescription.Semantics.Z); - AttributeStreamOfDbl zs1 = (AttributeStreamOfDbl) (zStream1); - - AttributeStreamBase zStream2 = ((MultiVertexGeometryImpl) geom2 - ._getImpl()) - .getAttributeStreamRef(VertexDescription.Semantics.Z); - AttributeStreamOfDbl zs2 = (AttributeStreamOfDbl) (zStream2); - - for (int i = 0; i < pointCount1; i++) { - double z1 = zs1.read(i); - double z2 = zs2.read(i); - assertTrue(z1 == z2); - } - } - - // Ms - boolean bHasMs1 = geom1.hasAttribute(VertexDescription.Semantics.M); - boolean bHasMs2 = geom2.hasAttribute(VertexDescription.Semantics.M); - assertTrue(bHasMs1 == bHasMs2); - - if (bHasMs1) { - AttributeStreamBase mStream1 = ((MultiVertexGeometryImpl) geom1 - ._getImpl()) - .getAttributeStreamRef(VertexDescription.Semantics.M); - AttributeStreamOfDbl ms1 = (AttributeStreamOfDbl) (mStream1); - - AttributeStreamBase mStream2 = ((MultiVertexGeometryImpl) geom2 - ._getImpl()) - .getAttributeStreamRef(VertexDescription.Semantics.M); - AttributeStreamOfDbl ms2 = (AttributeStreamOfDbl) (mStream2); - - for (int i = 0; i < pointCount1; i++) { - double m1 = ms1.read(i); - double m2 = ms2.read(i); - assertTrue(m1 == m2); - } - } - - // IDs - boolean bHasIDs1 = geom1 - .hasAttribute(VertexDescription.Semantics.ID); - boolean bHasIDs2 = geom2 - .hasAttribute(VertexDescription.Semantics.ID); - assertTrue(bHasIDs1 == bHasIDs2); - - if (bHasIDs1) { - AttributeStreamBase idStream1 = ((MultiVertexGeometryImpl) geom1 - ._getImpl()) - .getAttributeStreamRef(VertexDescription.Semantics.ID); - AttributeStreamOfInt32 ids1 = (AttributeStreamOfInt32) (idStream1); - - AttributeStreamBase idStream2 = ((MultiVertexGeometryImpl) geom2 - ._getImpl()) - .getAttributeStreamRef(VertexDescription.Semantics.ID); - AttributeStreamOfInt32 ids2 = (AttributeStreamOfInt32) (idStream2); - - for (int i = 0; i < pointCount1; i++) { - int id1 = ids1.read(i); - int id2 = ids2.read(i); - assertTrue(id1 == id2); - } - } - } - } - - @Test - public static void compareGeometryContent(MultiPoint geom1, MultiPoint geom2) { - // Geometry types - assertTrue(geom1.getType().value() == geom2.getType().value()); - - // Envelopes - Envelope env1 = new Envelope(); - geom1.queryEnvelope(env1); - - Envelope env2 = new Envelope(); - geom2.queryEnvelope(env2); - - assertTrue(env1.getXMin() == env2.getXMin() - && env1.getXMax() == env2.getXMax() - && env1.getYMin() == env2.getYMin() - && env1.getYMax() == env2.getYMax()); - - // Point count - int pointCount1 = geom1.getPointCount(); - int pointCount2 = geom2.getPointCount(); - assertTrue(pointCount1 == pointCount2); - - Point point1; - Point point2; - - for (int i = 0; i < pointCount1; i++) { - point1 = geom1.getPoint(i); - point2 = geom2.getPoint(i); - - double x1 = point1.getX(); - double x2 = point2.getX(); - assertTrue(x1 == x2); - - double y1 = point1.getY(); - double y2 = point2.getY(); - assertTrue(y1 == y2); - } - } - - @Test - public static void testNothing() { - - } - - public static boolean writeObjectToFile(String fileName, Object obj) { - try { - File f = new File(fileName); - f.setWritable(true); - - FileOutputStream fout = new FileOutputStream(f); - ObjectOutputStream oo = new ObjectOutputStream(fout); - oo.writeObject(obj); - fout.close(); - return true; - } catch (Exception ex) { - return false; - } - } - - public static Object readObjectFromFile(String fileName) { - try { - File f = new File(fileName); - f.setReadable(true); - - FileInputStream streamIn = new FileInputStream(f); - ObjectInputStream ii = new ObjectInputStream(streamIn); - Object obj = ii.readObject(); - streamIn.close(); - return obj; - } catch (Exception ex) { - return null; - } - } - - public static MapGeometry fromJson(String jsonString) { - try { - return OperatorImportFromJson.local().execute(Geometry.Type.Unknown, jsonString); - } catch (Exception ex) { - } - - return null; - } + public static boolean compareDouble(double a, double b, double tol) { + double diff = Math.abs(a - b); + return diff <= tol * Math.abs(a) + tol; + } + + public static Point[] pointsFromMultiPath(MultiPath geom) { + int numberOfPoints = geom.getPointCount(); + Point[] points = new Point[numberOfPoints]; + for (int i = 0; i < geom.getPointCount(); i++) { + points[i] = geom.getPoint(i); + } + return points; + } + + @Test + public static void compareGeometryContent(MultiVertexGeometry geom1, + MultiVertexGeometry geom2) { + // Geometry types + assertTrue(geom1.getType().value() == geom2.getType().value()); + + // Envelopes + Envelope2D env1 = new Envelope2D(); + geom1.queryEnvelope2D(env1); + + Envelope2D env2 = new Envelope2D(); + geom2.queryEnvelope2D(env2); + + assertTrue(env1.xmin == env2.xmin && env1.xmax == env2.xmax + && env1.ymin == env2.ymin && env1.ymax == env2.ymax); + + int type = geom1.getType().value(); + if (type == Geometry.GeometryType.Polyline + || type == Geometry.GeometryType.Polygon) { + // Part Count + int partCount1 = ((MultiPath) geom1).getPathCount(); + int partCount2 = ((MultiPath) geom2).getPathCount(); + assertTrue(partCount1 == partCount2); + + // Part indices + for (int i = 0; i < partCount1; i++) { + int start1 = ((MultiPath) geom1).getPathStart(i); + int start2 = ((MultiPath) geom2).getPathStart(i); + assertTrue(start1 == start2); + int end1 = ((MultiPath) geom1).getPathEnd(i); + int end2 = ((MultiPath) geom2).getPathEnd(i); + assertTrue(end1 == end2); + } + } + + // Point count + int pointCount1 = geom1.getPointCount(); + int pointCount2 = geom2.getPointCount(); + assertTrue(pointCount1 == pointCount2); + + if (type == Geometry.GeometryType.MultiPoint + || type == Geometry.GeometryType.Polyline + || type == Geometry.GeometryType.Polygon) { + // POSITION + AttributeStreamBase positionStream1 = ((MultiVertexGeometryImpl) geom1 + ._getImpl()) + .getAttributeStreamRef(VertexDescription.Semantics.POSITION); + AttributeStreamOfDbl position1 = (AttributeStreamOfDbl) (positionStream1); + + AttributeStreamBase positionStream2 = ((MultiVertexGeometryImpl) geom2 + ._getImpl()) + .getAttributeStreamRef(VertexDescription.Semantics.POSITION); + AttributeStreamOfDbl position2 = (AttributeStreamOfDbl) (positionStream2); + + for (int i = 0; i < pointCount1; i++) { + double x1 = position1.read(2 * i); + double x2 = position2.read(2 * i); + assertTrue(x1 == x2); + + double y1 = position1.read(2 * i + 1); + double y2 = position2.read(2 * i + 1); + assertTrue(y1 == y2); + } + + // Zs + boolean bHasZs1 = geom1.hasAttribute(VertexDescription.Semantics.Z); + boolean bHasZs2 = geom2.hasAttribute(VertexDescription.Semantics.Z); + assertTrue(bHasZs1 == bHasZs2); + + if (bHasZs1) { + AttributeStreamBase zStream1 = ((MultiVertexGeometryImpl) geom1 + ._getImpl()) + .getAttributeStreamRef(VertexDescription.Semantics.Z); + AttributeStreamOfDbl zs1 = (AttributeStreamOfDbl) (zStream1); + + AttributeStreamBase zStream2 = ((MultiVertexGeometryImpl) geom2 + ._getImpl()) + .getAttributeStreamRef(VertexDescription.Semantics.Z); + AttributeStreamOfDbl zs2 = (AttributeStreamOfDbl) (zStream2); + + for (int i = 0; i < pointCount1; i++) { + double z1 = zs1.read(i); + double z2 = zs2.read(i); + assertTrue(z1 == z2); + } + } + + // Ms + boolean bHasMs1 = geom1.hasAttribute(VertexDescription.Semantics.M); + boolean bHasMs2 = geom2.hasAttribute(VertexDescription.Semantics.M); + assertTrue(bHasMs1 == bHasMs2); + + if (bHasMs1) { + AttributeStreamBase mStream1 = ((MultiVertexGeometryImpl) geom1 + ._getImpl()) + .getAttributeStreamRef(VertexDescription.Semantics.M); + AttributeStreamOfDbl ms1 = (AttributeStreamOfDbl) (mStream1); + + AttributeStreamBase mStream2 = ((MultiVertexGeometryImpl) geom2 + ._getImpl()) + .getAttributeStreamRef(VertexDescription.Semantics.M); + AttributeStreamOfDbl ms2 = (AttributeStreamOfDbl) (mStream2); + + for (int i = 0; i < pointCount1; i++) { + double m1 = ms1.read(i); + double m2 = ms2.read(i); + assertTrue(m1 == m2); + } + } + + // IDs + boolean bHasIDs1 = geom1 + .hasAttribute(VertexDescription.Semantics.ID); + boolean bHasIDs2 = geom2 + .hasAttribute(VertexDescription.Semantics.ID); + assertTrue(bHasIDs1 == bHasIDs2); + + if (bHasIDs1) { + AttributeStreamBase idStream1 = ((MultiVertexGeometryImpl) geom1 + ._getImpl()) + .getAttributeStreamRef(VertexDescription.Semantics.ID); + AttributeStreamOfInt32 ids1 = (AttributeStreamOfInt32) (idStream1); + + AttributeStreamBase idStream2 = ((MultiVertexGeometryImpl) geom2 + ._getImpl()) + .getAttributeStreamRef(VertexDescription.Semantics.ID); + AttributeStreamOfInt32 ids2 = (AttributeStreamOfInt32) (idStream2); + + for (int i = 0; i < pointCount1; i++) { + int id1 = ids1.read(i); + int id2 = ids2.read(i); + assertTrue(id1 == id2); + } + } + } + } + + @Test + public static void compareGeometryContent(MultiPoint geom1, MultiPoint geom2) { + // Geometry types + assertTrue(geom1.getType().value() == geom2.getType().value()); + + // Envelopes + Envelope env1 = new Envelope(); + geom1.queryEnvelope(env1); + + Envelope env2 = new Envelope(); + geom2.queryEnvelope(env2); + + assertTrue(env1.getXMin() == env2.getXMin() + && env1.getXMax() == env2.getXMax() + && env1.getYMin() == env2.getYMin() + && env1.getYMax() == env2.getYMax()); + + // Point count + int pointCount1 = geom1.getPointCount(); + int pointCount2 = geom2.getPointCount(); + assertTrue(pointCount1 == pointCount2); + + Point point1; + Point point2; + + for (int i = 0; i < pointCount1; i++) { + point1 = geom1.getPoint(i); + point2 = geom2.getPoint(i); + + double x1 = point1.getX(); + double x2 = point2.getX(); + assertTrue(x1 == x2); + + double y1 = point1.getY(); + double y2 = point2.getY(); + assertTrue(y1 == y2); + } + } + + @Test + public static void testNothing() { + + } + + public static boolean writeObjectToFile(String fileName, Object obj) { + try { + File f = new File(fileName); + f.setWritable(true); + + FileOutputStream fout = new FileOutputStream(f); + ObjectOutputStream oo = new ObjectOutputStream(fout); + oo.writeObject(obj); + fout.close(); + return true; + } catch (Exception ex) { + return false; + } + } + + public static Object readObjectFromFile(String fileName) { + try { + File f = new File(fileName); + f.setReadable(true); + + FileInputStream streamIn = new FileInputStream(f); + ObjectInputStream ii = new ObjectInputStream(streamIn); + Object obj = ii.readObject(); + streamIn.close(); + return obj; + } catch (Exception ex) { + return null; + } + } + + public static MapGeometry fromJson(String jsonString) { + try { + return OperatorImportFromJson.local().execute(Geometry.Type.Unknown, jsonString); + } catch (Exception ex) { + } + + return null; + } } diff --git a/src/test/java/com/esri/core/geometry/TestContains.java b/src/test/java/com/esri/core/geometry/TestContains.java index 3cfa443f..1b96b598 100644 --- a/src/test/java/com/esri/core/geometry/TestContains.java +++ b/src/test/java/com/esri/core/geometry/TestContains.java @@ -33,24 +33,24 @@ import java.io.IOException; public class TestContains extends TestCase { - @Override - protected void setUp() throws Exception { - super.setUp(); - } - - @Override - protected void tearDown() throws Exception { - super.tearDown(); - } - - @Test - public static void testContainsFailureCR186456() throws JsonParseException, IOException { - String str = "{\"rings\":[[[406944.399999999,287461.450000001],[406947.750000011,287462.299999997],[406946.44999999,287467.450000001],[406943.050000005,287466.550000005],[406927.799999992,287456.849999994],[406926.949999996,287456.599999995],[406924.800000005,287455.999999998],[406924.300000007,287455.849999999],[406924.200000008,287456.099999997],[406923.450000011,287458.449999987],[406922.999999987,287459.800000008],[406922.29999999,287462.099999998],[406921.949999991,287463.449999992],[406921.449999993,287465.050000011],[406920.749999996,287466.700000004],[406919.800000001,287468.599999996],[406919.050000004,287469.99999999],[406917.800000009,287471.800000008],[406916.04999999,287473.550000001],[406915.449999993,287473.999999999],[406913.700000001,287475.449999993],[406913.300000002,287475.899999991],[406912.050000008,287477.250000011],[406913.450000002,287478.150000007],[406915.199999994,287478.650000005],[406915.999999991,287478.800000005],[406918.300000007,287479.200000003],[406920.649999997,287479.450000002],[406923.100000013,287479.550000001],[406925.750000001,287479.450000002],[406928.39999999,287479.150000003],[406929.80000001,287478.950000004],[406932.449999998,287478.350000006],[406935.099999987,287477.60000001],[406938.699999998,287476.349999989],[406939.649999994,287473.949999999],[406939.799999993,287473.949999999],[406941.249999987,287473.75],[406942.700000007,287473.250000002],[406943.100000005,287473.100000003],[406943.950000001,287472.750000004],[406944.799999998,287472.300000006],[406944.999999997,287472.200000007],[406946.099999992,287471.200000011],[406946.299999991,287470.950000012],[406948.00000001,287468.599999996],[406948.10000001,287468.399999997],[406950.100000001,287465.050000011],[406951.949999993,287461.450000001],[406952.049999993,287461.300000001],[406952.69999999,287459.900000007],[406953.249999987,287458.549999987],[406953.349999987,287458.299999988],[406953.650000012,287457.299999992],[406953.900000011,287456.349999996],[406954.00000001,287455.300000001],[406954.00000001,287454.750000003],[406953.850000011,287453.750000008],[406953.550000012,287452.900000011],[406953.299999987,287452.299999988],[406954.500000008,287450.299999996],[406954.00000001,287449.000000002],[406953.399999987,287447.950000006],[406953.199999988,287447.550000008],[406952.69999999,287446.850000011],[406952.149999992,287446.099999988],[406951.499999995,287445.499999991],[406951.149999996,287445.249999992],[406950.449999999,287444.849999994],[406949.600000003,287444.599999995],[406949.350000004,287444.549999995],[406948.250000009,287444.499999995],[406947.149999987,287444.699999994],[406946.849999989,287444.749999994],[406945.899999993,287444.949999993],[406944.999999997,287445.349999991],[406944.499999999,287445.64999999],[406943.650000003,287446.349999987],[406942.900000006,287447.10000001],[406942.500000008,287447.800000007],[406942.00000001,287448.700000003],[406941.600000011,287449.599999999],[406941.350000013,287450.849999994],[406941.350000013,287451.84999999],[406941.450000012,287452.850000012],[406941.750000011,287453.850000007],[406941.800000011,287454.000000007],[406942.150000009,287454.850000003],[406942.650000007,287455.6],[406943.150000005,287456.299999997],[406944.499999999,287457.299999992],[406944.899999997,287457.599999991],[406945.299999995,287457.949999989],[406944.399999999,287461.450000001],[406941.750000011,287461.999999998],[406944.399999999,287461.450000001]],[[406944.399999999,287461.450000001],[406947.750000011,287462.299999997],[406946.44999999,287467.450000001],[406943.050000005,287466.550000005],[406927.799999992,287456.849999994],[406944.399999999,287461.450000001]]]}"; - JsonFactory jsonFactory = new JsonFactory(); - JsonParser jsonParser = jsonFactory.createParser(str); - MapGeometry mg = GeometryEngine.jsonToGeometry(jsonParser); - boolean res = GeometryEngine.contains(mg.getGeometry(), - mg.getGeometry(), null); - assertTrue(res); - } + @Override + protected void setUp() throws Exception { + super.setUp(); + } + + @Override + protected void tearDown() throws Exception { + super.tearDown(); + } + + @Test + public static void testContainsFailureCR186456() throws JsonParseException, IOException { + String str = "{\"rings\":[[[406944.399999999,287461.450000001],[406947.750000011,287462.299999997],[406946.44999999,287467.450000001],[406943.050000005,287466.550000005],[406927.799999992,287456.849999994],[406926.949999996,287456.599999995],[406924.800000005,287455.999999998],[406924.300000007,287455.849999999],[406924.200000008,287456.099999997],[406923.450000011,287458.449999987],[406922.999999987,287459.800000008],[406922.29999999,287462.099999998],[406921.949999991,287463.449999992],[406921.449999993,287465.050000011],[406920.749999996,287466.700000004],[406919.800000001,287468.599999996],[406919.050000004,287469.99999999],[406917.800000009,287471.800000008],[406916.04999999,287473.550000001],[406915.449999993,287473.999999999],[406913.700000001,287475.449999993],[406913.300000002,287475.899999991],[406912.050000008,287477.250000011],[406913.450000002,287478.150000007],[406915.199999994,287478.650000005],[406915.999999991,287478.800000005],[406918.300000007,287479.200000003],[406920.649999997,287479.450000002],[406923.100000013,287479.550000001],[406925.750000001,287479.450000002],[406928.39999999,287479.150000003],[406929.80000001,287478.950000004],[406932.449999998,287478.350000006],[406935.099999987,287477.60000001],[406938.699999998,287476.349999989],[406939.649999994,287473.949999999],[406939.799999993,287473.949999999],[406941.249999987,287473.75],[406942.700000007,287473.250000002],[406943.100000005,287473.100000003],[406943.950000001,287472.750000004],[406944.799999998,287472.300000006],[406944.999999997,287472.200000007],[406946.099999992,287471.200000011],[406946.299999991,287470.950000012],[406948.00000001,287468.599999996],[406948.10000001,287468.399999997],[406950.100000001,287465.050000011],[406951.949999993,287461.450000001],[406952.049999993,287461.300000001],[406952.69999999,287459.900000007],[406953.249999987,287458.549999987],[406953.349999987,287458.299999988],[406953.650000012,287457.299999992],[406953.900000011,287456.349999996],[406954.00000001,287455.300000001],[406954.00000001,287454.750000003],[406953.850000011,287453.750000008],[406953.550000012,287452.900000011],[406953.299999987,287452.299999988],[406954.500000008,287450.299999996],[406954.00000001,287449.000000002],[406953.399999987,287447.950000006],[406953.199999988,287447.550000008],[406952.69999999,287446.850000011],[406952.149999992,287446.099999988],[406951.499999995,287445.499999991],[406951.149999996,287445.249999992],[406950.449999999,287444.849999994],[406949.600000003,287444.599999995],[406949.350000004,287444.549999995],[406948.250000009,287444.499999995],[406947.149999987,287444.699999994],[406946.849999989,287444.749999994],[406945.899999993,287444.949999993],[406944.999999997,287445.349999991],[406944.499999999,287445.64999999],[406943.650000003,287446.349999987],[406942.900000006,287447.10000001],[406942.500000008,287447.800000007],[406942.00000001,287448.700000003],[406941.600000011,287449.599999999],[406941.350000013,287450.849999994],[406941.350000013,287451.84999999],[406941.450000012,287452.850000012],[406941.750000011,287453.850000007],[406941.800000011,287454.000000007],[406942.150000009,287454.850000003],[406942.650000007,287455.6],[406943.150000005,287456.299999997],[406944.499999999,287457.299999992],[406944.899999997,287457.599999991],[406945.299999995,287457.949999989],[406944.399999999,287461.450000001],[406941.750000011,287461.999999998],[406944.399999999,287461.450000001]],[[406944.399999999,287461.450000001],[406947.750000011,287462.299999997],[406946.44999999,287467.450000001],[406943.050000005,287466.550000005],[406927.799999992,287456.849999994],[406944.399999999,287461.450000001]]]}"; + JsonFactory jsonFactory = new JsonFactory(); + JsonParser jsonParser = jsonFactory.createParser(str); + MapGeometry mg = GeometryEngine.jsonToGeometry(jsonParser); + boolean res = GeometryEngine.contains(mg.getGeometry(), + mg.getGeometry(), null); + assertTrue(res); + } } diff --git a/src/test/java/com/esri/core/geometry/TestConvexHull.java b/src/test/java/com/esri/core/geometry/TestConvexHull.java index 16166fda..b2e2d59e 100644 --- a/src/test/java/com/esri/core/geometry/TestConvexHull.java +++ b/src/test/java/com/esri/core/geometry/TestConvexHull.java @@ -28,969 +28,969 @@ import org.junit.Test; public class TestConvexHull extends TestCase { - @Override - protected void setUp() throws Exception { - super.setUp(); - } - - @Override - protected void tearDown() throws Exception { - super.tearDown(); - } - - @Test - public static void testFewPoints() { - { - Polygon polygon = new Polygon(); - polygon.addPath((Point2D[]) null, 0, true); - polygon.insertPoint(0, -1, Point2D.construct(5, 5)); - - Point convex_hull = (Point) OperatorConvexHull.local().execute(polygon, null); - assertTrue(convex_hull.getXY().equals(Point2D.construct(5, 5))); - } - - { - Point2D[] pts = new Point2D[3]; - - pts[0] = Point2D.construct(0, 0); - pts[1] = Point2D.construct(0, 0); - pts[2] = Point2D.construct(0, 0); - - int[] out_pts = new int[3]; - int res = ConvexHull.construct(pts, 3, out_pts); - assertTrue(res == 1); - assertTrue(out_pts[0] == 0); - } - - { - Point2D[] pts = new Point2D[1]; - pts[0] = Point2D.construct(0, 0); - - int[] out_pts = new int[1]; - int res = ConvexHull.construct(pts, 1, out_pts); - assertTrue(res == 1); - assertTrue(out_pts[0] == 0); - } - } - - @Test - public static void testDegenerate() { - OperatorConvexHull bounding = (OperatorConvexHull) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.ConvexHull); - OperatorDensifyByLength densify = (OperatorDensifyByLength) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.DensifyByLength); - - { - Polygon polygon = new Polygon(); - polygon.startPath(0, 0); - polygon.lineTo(1, 0); - polygon.lineTo(0, 0); - polygon.lineTo(2, 0); - polygon.lineTo(1, 0); - polygon.lineTo(3, 0); - - polygon.startPath(0, 0); - polygon.lineTo(1, 0); - polygon.lineTo(0, 0); - polygon.lineTo(2, 0); - polygon.lineTo(1, 0); - polygon.lineTo(3, 0); - - Polygon densified = (Polygon) (densify.execute(polygon, .5, null)); - Polyline convex_hull = (Polyline) (bounding.execute(densified, null)); - assertTrue(bounding.isConvex(convex_hull, null)); - assertTrue(convex_hull.calculateArea2D() == 0.0); - - Point2D p1 = convex_hull.getXY(0); - Point2D p2 = convex_hull.getXY(1); - assertTrue(p1.x == 0.0 && p1.y == 0.0); - assertTrue(p2.x == 3.0 && p2.y == 0.0); - } - - { - Polygon polygon = new Polygon(); - polygon.startPath(0, 0); - polygon.lineTo(0, 0); - polygon.lineTo(0, 0); - polygon.lineTo(NumberUtils.doubleEps(), 0); - polygon.lineTo(0, NumberUtils.doubleEps()); - polygon.lineTo(10, 0); - polygon.lineTo(10, 0); - polygon.lineTo(10, 5); - polygon.lineTo(10, 10); - polygon.lineTo(5, 10); - polygon.lineTo(10, 0); - polygon.lineTo(10, 10); - polygon.lineTo(5, 10); - polygon.lineTo(0, 10); - polygon.lineTo(0, 0); - - polygon.startPath(0, 0); - polygon.lineTo(0, 0); - polygon.lineTo(0, 0); - polygon.lineTo(10, 0); - polygon.lineTo(10, 0); - polygon.lineTo(10, 5); - polygon.lineTo(10, 10); - polygon.lineTo(5, 10); - polygon.lineTo(10, 0); - polygon.lineTo(10, 10); - polygon.lineTo(5, 10); - polygon.lineTo(0, 10); - polygon.lineTo(0, 0); - - polygon.startPath(0, 0); - polygon.lineTo(0, 0); - polygon.lineTo(0, 0); - polygon.lineTo(10, 0); - polygon.lineTo(5, 0); - polygon.lineTo(10, 0); - polygon.lineTo(10, 5); - polygon.lineTo(10, 0); - polygon.lineTo(10, 10); - polygon.lineTo(5, 10); - polygon.lineTo(10, 10); - polygon.lineTo(5, 10); - polygon.lineTo(0, 10); - polygon.lineTo(5, 10); - polygon.lineTo(0, 0); - - Polygon densified = (Polygon) (densify.execute(polygon, 1, null)); - Polygon convex_hull = (Polygon) (bounding.execute(densified, null)); - - double area = convex_hull.calculateArea2D(); - assertTrue(bounding.isConvex(convex_hull, null)); - assertTrue(area == 100.0); - - Point2D p1 = convex_hull.getXY(0); - Point2D p2 = convex_hull.getXY(1); - Point2D p3 = convex_hull.getXY(2); - Point2D p4 = convex_hull.getXY(3); - assertTrue(p1.x == 0.0 && p1.y == 0.0); - assertTrue(p2.x == 0.0 && p2.y == 10.0); - assertTrue(p3.x == 10.0 && p3.y == 10.0); - assertTrue(p4.x == 10.0 && p4.y == 0.0); - } - - { - Polygon polygon = new Polygon(); - polygon.startPath(0, 0); - polygon.lineTo(0, 10); - polygon.lineTo(5, 10); - polygon.lineTo(5, 5); - polygon.lineTo(5, 8); - polygon.lineTo(10, 10); - polygon.lineTo(10, 0); - - Polygon densified = (Polygon) (densify.execute(polygon, 1, null)); - Polygon convex_hull = (Polygon) (bounding.execute(densified, null)); - - double area = convex_hull.calculateArea2D(); - assertTrue(bounding.isConvex(convex_hull, null)); - assertTrue(area == 100.0); - - Point2D p1 = convex_hull.getXY(0); - Point2D p2 = convex_hull.getXY(1); - Point2D p3 = convex_hull.getXY(2); - Point2D p4 = convex_hull.getXY(3); - assertTrue(p1.x == 0.0 && p1.y == 0.0); - assertTrue(p2.x == 0.0 && p2.y == 10.0); - assertTrue(p3.x == 10.0 && p3.y == 10.0); - assertTrue(p4.x == 10.0 && p4.y == 0.0); - } - - { - Polygon polygon = new Polygon(); - polygon.startPath(0, 0); - polygon.lineTo(0, 0); - polygon.lineTo(0, 0); - polygon.lineTo(10, 0); - polygon.lineTo(5, 0); - polygon.lineTo(10, 0); - polygon.lineTo(10, 5); - polygon.lineTo(10, 0); - polygon.lineTo(10, 10); - polygon.lineTo(5, 10); - polygon.lineTo(10, 10); - polygon.lineTo(5, 10); - polygon.lineTo(0, 10); - polygon.lineTo(5, 10); - polygon.lineTo(0, 0); - Polygon densified = (Polygon) (densify.execute(polygon, 1, null)); - Polygon convex_hull = (Polygon) (bounding.execute(densified, null)); - double area = convex_hull.calculateArea2D(); - assertTrue(bounding.isConvex(convex_hull, null)); - assertTrue(area == 100.0); - - Point2D p1 = convex_hull.getXY(0); - Point2D p2 = convex_hull.getXY(1); - Point2D p3 = convex_hull.getXY(2); - Point2D p4 = convex_hull.getXY(3); - assertTrue(p1.x == 0.0 && p1.y == 0.0); - assertTrue(p2.x == 0.0 && p2.y == 10.0); - assertTrue(p3.x == 10.0 && p3.y == 10.0); - assertTrue(p4.x == 10.0 && p4.y == 0.0); - } - - { - Polygon polygon = new Polygon(); - polygon.startPath(0, 0); - polygon.lineTo(0, 0); - polygon.lineTo(0, 10); - polygon.lineTo(0, 0); - polygon.lineTo(10, 10); - polygon.lineTo(0, 0); - polygon.lineTo(10, 0); - polygon.lineTo(0, 0); - - polygon.startPath(0, 10); - polygon.lineTo(0, 10); - polygon.lineTo(10, 10); - polygon.lineTo(0, 10); - polygon.lineTo(10, 0); - polygon.lineTo(0, 10); - polygon.lineTo(0, 0); - polygon.lineTo(0, 10); - - polygon.startPath(10, 10); - polygon.lineTo(10, 10); - polygon.lineTo(10, 0); - polygon.lineTo(10, 10); - polygon.lineTo(0, 0); - polygon.lineTo(10, 10); - polygon.lineTo(0, 10); - polygon.lineTo(10, 10); - - polygon.startPath(10, 0); - polygon.lineTo(10, 0); - polygon.lineTo(0, 0); - polygon.lineTo(10, 0); - polygon.lineTo(0, 10); - polygon.lineTo(10, 0); - polygon.lineTo(10, 10); - polygon.lineTo(10, 0); - - Polygon densified = (Polygon) (densify.execute(polygon, 1, null)); - Polygon convex_hull = (Polygon) (bounding.execute(densified, null)); - assertTrue(bounding.isConvex(convex_hull, null)); - - Point2D p1 = convex_hull.getXY(0); - Point2D p2 = convex_hull.getXY(1); - Point2D p3 = convex_hull.getXY(2); - Point2D p4 = convex_hull.getXY(3); - assertTrue(p1.x == 0.0 && p1.y == 0.0); - assertTrue(p2.x == 0.0 && p2.y == 10.0); - assertTrue(p3.x == 10.0 && p3.y == 10.0); - assertTrue(p4.x == 10.0 && p4.y == 0.0); - } - - { - MultiPoint mpoint = new MultiPoint(); - mpoint.add(4, 4); - mpoint.add(4, 4); - mpoint.add(4, 4); - mpoint.add(4, 4); - - Point convex_hull = (Point) bounding.execute(mpoint, null); - assertTrue(convex_hull.calculateArea2D() == 0.0); - assertTrue(bounding.isConvex(convex_hull, null)); - assertTrue(convex_hull.getXY().equals(Point2D.construct(4, 4))); - } - - { - MultiPoint mpoint = new MultiPoint(); - mpoint.add(4, 4); - - Point convex_hull = (Point) bounding.execute(mpoint, null); - assertTrue(bounding.isConvex(convex_hull, null)); - assertTrue(convex_hull.getXY().equals(Point2D.construct(4, 4))); - } - - { - MultiPoint mpoint = new MultiPoint(); - mpoint.add(4, 4); - mpoint.add(4, 5); - - Polyline convex_hull = (Polyline) bounding.execute(mpoint, null); - assertTrue(convex_hull.getPointCount() == 2); - assertTrue(bounding.isConvex(convex_hull, null)); - assertTrue(convex_hull.calculateLength2D() == 1.0); - } - - { - Line line = new Line(); - line.setStartXY(0, 0); - line.setEndXY(0, 1); - - Polyline convex_hull = (Polyline) bounding.execute(line, null); - assertTrue(convex_hull.getPointCount() == 2); - assertTrue(bounding.isConvex(convex_hull, null)); - assertTrue(convex_hull.calculateLength2D() == 1.0); - } - - { - Polyline polyline = new Polyline(); - polyline.startPath(0, 0); - polyline.lineTo(0, 1); - - Polyline convex_hull = (Polyline) bounding.execute(polyline, null); - assertTrue(convex_hull.getPointCount() == 2); - assertTrue(bounding.isConvex(convex_hull, null)); - assertTrue(polyline == convex_hull); - assertTrue(convex_hull.calculateLength2D() == 1.0); - } - - { - Envelope env = new Envelope(0, 0, 10, 10); - assertTrue(OperatorConvexHull.local().isConvex(env, null)); - - Polygon convex_hull = (Polygon) bounding.execute(env, null); - assertTrue(bounding.isConvex(convex_hull, null)); - assertTrue(convex_hull.getPointCount() == 4); - assertTrue(convex_hull.getXY(0).equals(Point2D.construct(0, 0))); - assertTrue(convex_hull.getXY(1).equals(Point2D.construct(0, 10))); - assertTrue(convex_hull.getXY(2).equals(Point2D.construct(10, 10))); - assertTrue(convex_hull.getXY(3).equals(Point2D.construct(10, 0))); - } - - { - Envelope env = new Envelope(0, 0, 0, 10); - assertTrue(!OperatorConvexHull.local().isConvex(env, null)); - - Polyline convex_hull = (Polyline) bounding.execute(env, null); - assertTrue(bounding.isConvex(convex_hull, null)); - assertTrue(convex_hull.getPointCount() == 2); - assertTrue(convex_hull.getXY(0).equals(Point2D.construct(0, 0))); - assertTrue(convex_hull.getXY(1).equals(Point2D.construct(0, 10))); - } - - { - Envelope env = new Envelope(0, 0, 0, 10); - assertTrue(!OperatorConvexHull.local().isConvex(env, null)); - - Polyline convex_hull = (Polyline) bounding.execute(env, null); - assertTrue(bounding.isConvex(convex_hull, null)); - assertTrue(convex_hull.getPointCount() == 2); - assertTrue(convex_hull.getXY(0).equals(Point2D.construct(0, 0))); - assertTrue(convex_hull.getXY(1).equals(Point2D.construct(0, 10))); - } - - { - Envelope env = new Envelope(5, 5, 5, 5); - assertTrue(!OperatorConvexHull.local().isConvex(env, null)); - - Point convex_hull = (Point) bounding.execute(env, null); - assertTrue(bounding.isConvex(convex_hull, null)); - assertTrue(convex_hull.getXY().equals(Point2D.construct(5, 5))); - } - } - - @Test - public static void testSegment() { - { - Line line = new Line(); - line.setStartXY(5, 5); - line.setEndXY(5, 5); - - assertTrue(!OperatorConvexHull.local().isConvex(line, null)); - Point point = (Point) OperatorConvexHull.local().execute(line, null); - assertTrue(point.getXY().equals(Point2D.construct(5, 5))); - } - - { - Line line = new Line(); - line.setStartXY(5, 5); - line.setEndXY(5, 6); - - assertTrue(OperatorConvexHull.local().isConvex(line, null)); - Polyline polyline = (Polyline) OperatorConvexHull.local().execute(line, null); - assertTrue(polyline.getPointCount() == 2); - assertTrue(polyline.getXY(0).equals(Point2D.construct(5, 5))); - assertTrue(polyline.getXY(1).equals(Point2D.construct(5, 6))); - } - } - - - @Test - public static void testSquare() { - OperatorConvexHull bounding = (OperatorConvexHull) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.ConvexHull); - OperatorDensifyByLength densify = (OperatorDensifyByLength) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.DensifyByLength); - OperatorDifference difference = (OperatorDifference) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.Difference); - OperatorContains contains = (OperatorContains) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.Contains); - - Polygon square = new Polygon(); - square.startPath(0, 0); - square.lineTo(2, 3); - square.lineTo(1, 4); - square.lineTo(0, 5); - square.lineTo(0, 7); - square.lineTo(2, 7); - square.lineTo(0, 10); - square.lineTo(4, 7); - square.lineTo(6, 7); - square.lineTo(7, 10); - square.lineTo(8, 10); - square.lineTo(10, 10); - square.lineTo(8, 7); - square.lineTo(10, 5); - square.lineTo(8, 3); - square.lineTo(10, 1); - square.lineTo(10, 0); - square.lineTo(5, 5); - square.lineTo(8, 0); - square.lineTo(4, 3); - square.lineTo(5, 0); - square.lineTo(3, 1); - square.lineTo(3, 0); - square.lineTo(2, 1); - - Polygon densified = (Polygon) (densify.execute(square, 1.0, null)); - - densified.addAttribute(VertexDescription.Semantics.ID); - for (int i = 0; i < densified.getPointCount(); i++) - densified.setAttribute(VertexDescription.Semantics.ID, i, 0, i); - - Polygon convex_hull = (Polygon) (bounding.execute(densified, null)); - Polygon differenced = (Polygon) (difference.execute(densified, convex_hull, SpatialReference.create(4326), null)); - - assertTrue(differenced.isEmpty()); - assertTrue(bounding.isConvex(convex_hull, null)); - } - - @Test - public static void testPolygons() { - OperatorConvexHull bounding = (OperatorConvexHull) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.ConvexHull); - OperatorDensifyByLength densify = (OperatorDensifyByLength) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.DensifyByLength); - OperatorDifference difference = (OperatorDifference) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.Difference); - - { - Polygon shape = (Polygon) (TestCommonMethods.fromJson("{\"rings\":[[[1.3734426370715553,-90],[-24.377532184629967,-63.428606327053856],[-25.684686546621553,90],[-24.260574484321914,80.526315789473699],[-25.414389575040037,90],[-23.851448513708718,90],[-23.100135788742072,87.435887853000679],[5.6085096351011448,-48.713222410606306],[1.3734426370715553,-90]]]}").getGeometry()); - Polygon convex_hull = (Polygon) (bounding.execute(shape, null)); - Polygon differenced = (Polygon) (difference.execute(shape, convex_hull, SpatialReference.create(4326), null)); - assertTrue(differenced.isEmpty()); - assertTrue(bounding.isConvex(convex_hull, null)); - } - - { - Polygon polygon = (Polygon) (TestCommonMethods.fromJson("{\"rings\":[[[-179.64843749999693,-43.3535476539993],[-179.99999999999696,-43.430006211999284],[-179.99999999999696,39.329644416999436],[-179.64843749999693,38.98862638799943],[-89.99999999999838,29.008084980999506],[-112.8515624999981,-16.20113770599962],[-115.66406249999804,-18.882554574999688],[-124.80468749999788,-23.7925511709996],[-138.86718749999767,-30.6635901109995],[-157.49999999999736,-38.468358112999354],[-162.42187499999724,-39.56498442199932],[-179.64843749999693,-43.3535476539993]],[[179.99999999999696,-43.430006211999284],[179.64843749999693,-43.50646476999926],[162.0703124999973,-42.36267115399919],[160.3124999999973,-42.24790485699929],[143.78906249999756,-41.1680427339993],[138.16406249999767,-39.64744846799925],[98.43749999999845,-28.523889212999524],[78.39843749999878,-5.1644422999998705],[75.9374999999988,19.738611663999766],[88.2421874999986,33.51651305599954],[108.63281249999815,44.160795160999356],[138.16406249999767,51.02062617799914],[140.9765624999976,51.68129673399923],[160.3124999999973,52.8064856429991],[162.0703124999973,52.908902047999206],[163.12499999999727,52.97036560499911],[165.93749999999716,52.97036560499911],[179.99999999999696,39.329644416999436],[179.99999999999696,-43.430006211999284]]]}").getGeometry()); - Polygon densified = (Polygon) (densify.execute(polygon, 10.0, null)); - Polygon convex_hull = (Polygon) (bounding.execute(densified, null)); - Polygon differenced = (Polygon) (difference.execute(densified, convex_hull, SpatialReference.create(4326), null)); - assertTrue(differenced.isEmpty()); - assertTrue(bounding.isConvex(convex_hull, null)); - } - - { - Polygon polygon = new Polygon(); - polygon.startPath(1, 0); - polygon.lineTo(-1, 0); - polygon.lineTo(0, -1); - Polygon convex_hull = (Polygon) (bounding.execute(polygon, null)); - Polygon differenced = (Polygon) (difference.execute(polygon, convex_hull, SpatialReference.create(4326), null)); - assertTrue(differenced.isEmpty()); - assertTrue(bounding.isConvex(convex_hull, null)); - } - - { - Polygon polygon = (Polygon) (TestCommonMethods.fromJson("{\"rings\":[[[-38.554566833914528,21.902000764339238],[-30.516168471666138,90],[-38.554566833914528,21.902000764339238]],[[-43.013227444613932,28.423410187206883],[-43.632473335895916,90],[-42.342268693420237,62.208637129146894],[-37.218731802058755,63.685357222187029],[-32.522681335230686,47.080307818055296],[-40.537308829621097,-21.881392019745604],[-47.59510451722663,18.812521648505964],[-53.25344489340366,30.362745244224911],[-46.629060462410138,90],[-50.069277433245119,18.254168921734287],[-42.171214434397982,72.623347387008081],[-43.000844452530551,90],[-46.162281544954659,90],[-39.462049205071331,90],[-47.434856316742902,38.662565208814371],[-52.13115779642537,-19.952586632199857],[-56.025328966335081,90],[-60.056846215416158,-44.023645282268355],[-60.12338894192289,50.374596189881942],[-35.787508034048379,-7.8839007676038513],[-60.880218074135605,-46.447995750907815],[-67.782542852117956,-85.106300958016107],[-65.053131764313761,-0.96651520578494665],[-72.375821140304154,90],[-78.561502106749245,90],[-83.809168672565946,33.234498214085811],[-60.880218054506344,-46.447995733653201],[-75.637095425108981,59.886574792622838],[-71.364085965028096,31.976373491332097],[-67.89968380886117,90],[-67.544349171474749,8.8435794458927504],[-70.780047377934707,80.683454463576624],[-64.996733940204948,34.349882797035313],[-56.631753638680905,39.815838152456926],[-60.392350183516896,52.75446132093407],[-58.51633728692137,90],[-64.646972065627097,41.444197803942579],[-73.355591244695518,-0.15370205145035776],[-43.013227444613932,28.423410187206883]],[[-69.646471076946,-85.716191379686904],[-62.854465128320491,-45.739046580967972],[-71.377481570643141,-90],[-66.613495837251435,-90],[-66.9765142407159,-90],[-66.870099169607329,-90],[-67.23180828626819,-61.248439074609649],[-58.889775875438851,-90],[-53.391995883729322,-69.476385967096491],[-69.646471076946,-85.716191379686904]]]}").getGeometry()); - Polygon densified = (Polygon) (densify.execute(polygon, 10.0, null)); - Polygon convex_hull = (Polygon) (bounding.execute(densified, null)); - Polygon differenced = (Polygon) (difference.execute(densified, convex_hull, SpatialReference.create(4326), null)); - assertTrue(differenced.isEmpty()); - // assertTrue(bounding.isConvex(*convex_hull, null)); - } - - { - Polygon polygon = (Polygon) (TestCommonMethods.fromJson("{\"rings\":[[[-38.554566833914528,21.902000764339238],[-30.516168471666138,90],[-38.554566833914528,21.902000764339238]],[[-43.013227444613932,28.423410187206883],[-43.632473335895916,90],[-42.342268693420237,62.208637129146894],[-37.218731802058755,63.685357222187029],[-32.522681335230686,47.080307818055296],[-40.537308829621097,-21.881392019745604],[-47.59510451722663,18.812521648505964],[-53.25344489340366,30.362745244224911],[-46.629060462410138,90],[-50.069277433245119,18.254168921734287],[-42.171214434397982,72.623347387008081],[-43.000844452530551,90],[-46.162281544954659,90],[-39.462049205071331,90],[-47.434856316742902,38.662565208814371],[-52.13115779642537,-19.952586632199857],[-56.025328966335081,90],[-60.056846215416158,-44.023645282268355],[-60.12338894192289,50.374596189881942],[-35.787508034048379,-7.8839007676038513],[-60.880218074135605,-46.447995750907815],[-67.782542852117956,-85.106300958016107],[-65.053131764313761,-0.96651520578494665],[-72.375821140304154,90],[-78.561502106749245,90],[-83.809168672565946,33.234498214085811],[-60.880218054506344,-46.447995733653201],[-75.637095425108981,59.886574792622838],[-71.364085965028096,31.976373491332097],[-67.89968380886117,90],[-67.544349171474749,8.8435794458927504],[-70.780047377934707,80.683454463576624],[-64.996733940204948,34.349882797035313],[-56.631753638680905,39.815838152456926],[-60.392350183516896,52.75446132093407],[-58.51633728692137,90],[-64.646972065627097,41.444197803942579],[-73.355591244695518,-0.15370205145035776],[-43.013227444613932,28.423410187206883]],[[-69.646471076946,-85.716191379686904],[-62.854465128320491,-45.739046580967972],[-71.377481570643141,-90],[-66.613495837251435,-90],[-66.9765142407159,-90],[-66.870099169607329,-90],[-67.23180828626819,-61.248439074609649],[-58.889775875438851,-90],[-53.391995883729322,-69.476385967096491],[-69.646471076946,-85.716191379686904]]]}").getGeometry()); - Polygon convex_hull = (Polygon) (bounding.execute(polygon, null)); - Polygon differenced = (Polygon) (difference.execute(polygon, convex_hull, SpatialReference.create(4326), null)); - assertTrue(differenced.isEmpty()); - assertTrue(bounding.isConvex(convex_hull, null)); - } - - { - Polygon polygon = (Polygon) (TestCommonMethods.fromJson("{\"rings\":[[[-36.269498017702901,-26.37490682626369],[-49.146436641060951,-49.881862499696126],[-37.560006446488146,-45.592052597656789],[-39.13770692863632,-69.085816352131204],[-65.415587331361877,-90],[-51.462290812033373,-16.760787566546721],[-28.454456182408332,90],[-36.269498017702901,-26.37490682626369]],[[-40.542178258552283,-90],[-39.13770692863632,-69.085816352131204],[-16.295804332590937,-50.906277575066262],[-40.542178258552283,-90]],[[-16.295804332590937,-50.906277575066262],[-5.6790432913971927,-33.788307256548933],[14.686101893282586,-26.248228042967728],[-16.295804332590937,-50.906277575066262]],[[-37.560006446488146,-45.592052597656789],[-36.269498017702901,-26.37490682626369],[27.479825940672225,90],[71.095881152477034,90],[-5.6790432913971927,-33.788307256548933],[-37.560006446488146,-45.592052597656789]]]}").getGeometry()); - Polygon convex_hull = (Polygon) (bounding.execute(polygon, null)); - Polygon differenced = (Polygon) (difference.execute(polygon, convex_hull, SpatialReference.create(4326), null)); - assertTrue(differenced.isEmpty()); - assertTrue(bounding.isConvex(convex_hull, null)); - } - - { - Polygon polygon = (Polygon) (TestCommonMethods.fromJson("{\"rings\":[[[-77.020281185856106,-80.085419699581706],[-77.328568930885723,-83.404479889897416],[-80.495259564600545,-90],[-77.020281185856106,-80.085419699581706]],[[-77.941187535385211,-90],[-77.328568930885723,-83.404479889897416],[-39.252034383972621,-4.0994329574862469],[-39.29471328421063,-6.5269494453154593],[-77.941187535385211,-90]],[[-77.020281185856106,-80.085419699581706],[-62.688864277996522,74.208210509833052],[-38.108861278327581,80.371071656873013],[-37.597643844595929,90],[-38.663943358642484,29.350366647752089],[-77.020281185856106,-80.085419699581706]],[[-40.265125886194951,-61.722668598742551],[-39.29471328421063,-6.5269494453154593],[-15.554402498931253,44.750073899273843],[-8.4447006412989474,13.127318978368956],[-5.310206313296316,-4.5170390491918795],[-40.265125886194951,-61.722668598742551]],[[-39.252034383972621,-4.0994329574862469],[-38.663943358642484,29.350366647752089],[-22.476078360563164,75.536520897660651],[-15.632105532320049,45.095683888365997],[-39.252034383972621,-4.0994329574862469]],[[-15.554402498931253,44.750073899273843],[-15.632105532320049,45.095683888365997],[-8.9755856576261941,58.959750756602595],[-15.554402498931253,44.750073899273843]]]}").getGeometry()); - Polygon convex_hull = (Polygon) (bounding.execute(polygon, null)); - Polygon differenced = (Polygon) (difference.execute(polygon, convex_hull, SpatialReference.create(4326), null)); - assertTrue(differenced.isEmpty()); - assertTrue(bounding.isConvex(convex_hull, null)); - } - - { - Polygon polygon = (Polygon) (TestCommonMethods.fromJson("{\"rings\":[[[-68.840007952128175,37.060080998089632],[-68.145986924561413,31.114096694815196],[-69.187773850176768,30.693518246958952],[-68.840007952128175,37.060080998089632]],[[-75.780513355389928,-90],[-69.21567111077384,30.182802098042274],[-50.875629803516389,37.146119571446704],[-75.780513355389928,-90]],[[4.2911006174797457,-1.144569312564311],[-66.484019915251849,80.191238371060038],[-65.948228008382316,90],[4.2911006174797457,-1.144569312564311]],[[-90,22.291441435181515],[-69.187773850176768,30.693518246958952],[-69.21567111077384,30.182802098042274],[-90,22.291441435181515]],[[-68.840007952128175,37.060080998089632],[-75.019206401201359,90],[-66.484019915251849,80.191238371060038],[-68.840007952128175,37.060080998089632]]]}").getGeometry()); - Polygon convex_hull = (Polygon) (bounding.execute(polygon, null)); - Polygon differenced = (Polygon) (difference.execute(polygon, convex_hull, SpatialReference.create(4326), null)); - assertTrue(differenced.isEmpty()); - assertTrue(bounding.isConvex(convex_hull, null)); - } - - { - Polygon polygon = (Polygon) (TestCommonMethods.fromJson("{\"rings\":[[[27.570109889215438,22.850616190228489],[75.703105729477357,-90],[2.1548000876241362,-15.817792950796967],[27.570109889215438,22.850616190228489]],[[-0.069915984436478951,-90],[-46.602410662754053,-89.999999998014729],[-14.977190481820156,-41.883452819243004],[-0.069915984436478951,-90]],[[-14.977190481820156,-41.883452819243004],[-34.509989609682322,21.163004866431177],[2.1548000876241362,-15.817792950796967],[-14.977190481820156,-41.883452819243004]]]}").getGeometry()); - Polygon convex_hull = (Polygon) (bounding.execute(polygon, null)); - Polygon differenced = (Polygon) (difference.execute(polygon, convex_hull, SpatialReference.create(4326), null)); - assertTrue(differenced.isEmpty()); - assertTrue(bounding.isConvex(convex_hull, null)); - } - - { - Polygon polygon = (Polygon) (TestCommonMethods.fromJson("{\"rings\":[[[28.865673900286581,33.379551302126075],[39.505669183485338,-34.957993133630559],[7.152466542048213,-90],[28.865673900286581,33.379551302126075]],[[-64.597291313620858,2.4515644574812248],[20.050002923927103,90],[24.375150856531356,62.220853377417541],[-64.597291313620858,2.4515644574812248]],[[28.865673900286581,33.379551302126075],[24.375150856531356,62.220853377417541],[35.223952527956932,69.508785974507163],[28.865673900286581,33.379551302126075]]]}").getGeometry()); - Polygon convex_hull = (Polygon) (bounding.execute(polygon, null)); - Polygon differenced = (Polygon) (difference.execute(polygon, convex_hull, SpatialReference.create(4326), null)); - assertTrue(differenced.isEmpty()); - assertTrue(bounding.isConvex(convex_hull, null)); - } - - { - Polygon polygon = (Polygon) (TestCommonMethods.fromJson("{\"rings\":[[[-66.582505384413167,-51.305212522944061],[-60.169897093348865,-90],[-90,-90],[-66.582505384413167,-51.305212522944061]],[[20.858462934004656,-90],[-35.056287147954386,0.78833269359179781],[18.933251883215579,90],[20.858462934004656,-90]],[[-66.582505384413167,-51.305212522944061],[-90,90],[-35.056287147954386,0.78833269359179781],[-66.582505384413167,-51.305212522944061]]]}").getGeometry()); - Polygon convex_hull = (Polygon) (bounding.execute(polygon, null)); - Polygon differenced = (Polygon) (difference.execute(polygon, convex_hull, SpatialReference.create(4326), null)); - assertTrue(differenced.isEmpty()); - assertTrue(bounding.isConvex(convex_hull, null)); - } - - { - Polygon polygon = (Polygon) (TestCommonMethods.fromJson("{\"rings\":[[[36.692916710279974,-90],[-31.182443792600079,6.434474852744998],[-90,90],[52.245260790065387,57.329280208760991],[36.692916710279974,-90]]]}").getGeometry()); - Polygon convex_hull = (Polygon) (bounding.execute(polygon, null)); - Polygon differenced = (Polygon) (difference.execute(polygon, convex_hull, SpatialReference.create(4326), null)); - assertTrue(differenced.isEmpty()); - assertTrue(bounding.isConvex(convex_hull, null)); - } - - { - Polygon polygon = (Polygon) (TestCommonMethods.fromJson("{\"rings\":[[[-17.959089916602533,-4.3577640799248218],[-29.181784251472308,-90],[-65.493717350127127,15.053615507086979],[-17.959089916602533,-4.3577640799248218]],[[-21.884657435973146,-34.517617672142393],[-17.94005076020704,-4.3655389655558539],[9.3768748358343359,-15.520758655380195],[-21.884657435973146,-34.517617672142393]],[[-17.94005076020704,-4.3655389655558539],[-17.959089916602533,-4.3577640799248218],[-5.8963967801936494,87.694641571893939],[-17.94005076020704,-4.3655389655558539]]]}").getGeometry()); - Polygon convex_hull = (Polygon) (bounding.execute(polygon, null)); - Polygon differenced = (Polygon) (difference.execute(polygon, convex_hull, SpatialReference.create(4326), null)); - assertTrue(differenced.isEmpty()); - assertTrue(bounding.isConvex(convex_hull, null)); - } - - { - Polygon polygon = (Polygon) (TestCommonMethods.fromJson("{\"rings\":[[[17.198360589495572,-77.168667157542373],[-24.835678541343281,-83.717338556412017],[-30.259244993378722,61.914816728303791],[17.198360589495572,-77.168667157542373]],[[-8.3544985146710644,-90],[17.979891823366039,-79.459092168186686],[21.576625471325329,-90],[-8.3544985146710644,-90]],[[17.979891823366039,-79.459092168186686],[17.198360589495572,-77.168667157542373],[27.846596597209441,-75.509730732825361],[17.979891823366039,-79.459092168186686]]]}").getGeometry()); - Polygon convex_hull = (Polygon) (bounding.execute(polygon, null)); - Polygon differenced = (Polygon) (difference.execute(polygon, convex_hull, SpatialReference.create(4326), null)); - assertTrue(differenced.isEmpty()); - assertTrue(bounding.isConvex(convex_hull, null)); - } - - { - Polygon polygon = (Polygon) (TestCommonMethods.fromJson("{\"rings\":[[[-1.2588613620456419,13.607321860624268],[61.845346679259052,48.415944386573557],[15.226225965240992,-5.3702891526017318],[0.92681706095183469,1.6819284384951441],[3.8469417404317623,-14.250715301799051],[7.2615297628459139,-14.559458820527061],[4.4896578086498238,-17.757471781424698],[14.589138845678622,-72.861774161244625],[-10.508572009494033,-35.06149380752737],[-58.12642296329372,-90],[-15.260062192400673,90],[-1.2588613620456419,13.607321860624268]],[[0.92681706095183469,1.6819284384951441],[-1.2588613620456419,13.607321860624268],[-11.641308877525201,7.8803076458946304],[0.92681706095183469,1.6819284384951441]],[[-10.508572009494033,-35.06149380752737],[4.4896578086498238,-17.757471781424698],[3.8469417404317623,-14.250715301799051],[-26.125369947914503,-11.54064986657559],[-10.508572009494033,-35.06149380752737]],[[39.829571435268129,-17.504227477249202],[7.2615297628459139,-14.559458820527061],[15.226225965240992,-5.3702891526017318],[39.829571435268129,-17.504227477249202]]]}").getGeometry()); - Polygon convex_hull = (Polygon) (bounding.execute(polygon, null)); - Polygon differenced = (Polygon) (difference.execute(polygon, convex_hull, SpatialReference.create(4326), null)); - assertTrue(differenced.isEmpty()); - assertTrue(bounding.isConvex(convex_hull, null)); - } - - { - Polygon polygon = (Polygon) (TestCommonMethods.fromJson("{\"rings\":[[[-19.681975166855118,-34.10344217707847],[-90,89.999999998418275],[53.036316534501381,90],[-19.681975166855118,-34.10344217707847]],[[-52.434509065706855,-90],[-29.2339442498794,-50.405148598356135],[-2.8515119199232331,-90],[-52.434509065706855,-90]],[[18.310881874573923,-90],[-25.473718245381271,-43.987822508814972],[-19.681975166855118,-34.10344217707847],[-15.406194071963924,-41.649717163101563],[18.310881874573923,-90]],[[-29.2339442498794,-50.405148598356135],[-52.27954259799813,-15.81822990020261],[-25.473718245381271,-43.987822508814972],[-29.2339442498794,-50.405148598356135]]]}").getGeometry()); - Polygon convex_hull = (Polygon) (bounding.execute(polygon, null)); - Polygon differenced = (Polygon) (difference.execute(polygon, convex_hull, SpatialReference.create(4326), null)); - assertTrue(differenced.isEmpty()); - assertTrue(bounding.isConvex(convex_hull, null)); - } - - { - Polygon polygon = (Polygon) (TestCommonMethods.fromJson("{\"rings\":[[[49.939516827702498,-90],[-20.470128740962011,-68.102019032647391],[-20.124197553433845,-67.213968219799824],[34.438329237618149,-61.893901496061034],[49.939516827702498,-90]],[[-82.380918375714089,-73.284249936115529],[-4.7432060543229699,9.1484031048644194],[-11.790524932251525,21.926303986370414],[-3.4862200343039369,10.483021157965428],[19.753975453441285,35.158541777575607],[5.5896897290794696,-1.2030408273476854],[73.839023528563189,-58.052174675157325],[34.438329237618149,-61.893901496061034],[3.6757233436274213,-6.1164440290327313],[-20.124197553433845,-67.213968219799824],[-82.380918375714089,-73.284249936115529]],[[5.5896897290794696,-1.2030408273476854],[4.0842948437219349,0.050896618883412792],[-3.4862200343039369,10.483021157965428],[-4.7432060543229699,9.1484031048644194],[3.6757233436274213,-6.1164440290327313],[5.5896897290794696,-1.2030408273476854]]]}").getGeometry()); - Polygon convex_hull = (Polygon) (bounding.execute(polygon, null)); - Polygon differenced = (Polygon) (difference.execute(polygon, convex_hull, SpatialReference.create(4326), null)); - assertTrue(differenced.isEmpty()); - assertTrue(bounding.isConvex(convex_hull, null)); - } - - { - Polygon polygon = (Polygon) (TestCommonMethods.fromJson("{\"rings\":[[[4.7618727814345867,-14.245890151885444],[-7.1675180359486266,-90],[-83.232840716292529,40.620187389409224],[-29.219286930421923,6.9418934044755334],[-29.378277853968513,6.9629531745072839],[-28.933835455648254,6.7639099538036529],[4.7618727814345867,-14.245890151885444]],[[51.056303527367277,-43.111190419066219],[4.7618727814345867,-14.245890151885444],[5.632592229367642,-8.716640778187827],[-28.933835455648254,6.7639099538036529],[-29.219286930421923,6.9418934044755334],[2.700964609629902,2.7137705544807242],[12.385960896403816,0.48342578457646468],[51.056303527367277,-43.111190419066219]]]}").getGeometry()); - Polygon convex_hull = (Polygon) (bounding.execute(polygon, null)); - Polygon differenced = (Polygon) (difference.execute(polygon, convex_hull, SpatialReference.create(4326), null)); - assertTrue(differenced.isEmpty()); - assertTrue(bounding.isConvex(convex_hull, null)); - } - - { - Polygon polygon = (Polygon) (TestCommonMethods.fromJson("{\"rings\":[[[-21.426619830983732,-89.379667629404466],[-72.784461583687971,-88.999754827814016],[-81.94289434769162,25.456737039611831],[-38.382426191605546,-57.204127144336077],[-41.663734179022256,-78.439084394036513],[-29.749353943865881,-73.586348060869426],[-21.426619830983732,-89.379667629404466]],[[-21.09971823441461,-90],[-21.426619830983732,-89.379667629404466],[-21.080965849893449,-89.382224558742934],[-21.09971823441461,-90]],[[62.431917153693021,-90],[-21.080965849893449,-89.382224558742934],[-20.486971473666468,-69.813772479288062],[19.166418765782844,-53.662915804391695],[63.671046682728601,-90],[62.431917153693021,-90]],[[-29.749353943865881,-73.586348060869426],[-38.382426191605546,-57.204127144336077],[-31.449272112025476,-12.336278393150847],[-41.028899505665962,-4.5147159296945967],[-30.750049689226596,-7.8112663207986941],[-15.63587330244308,90],[-18.721998818789388,-11.66880646480822],[60.158611185675326,-36.966763960486929],[19.166418765782844,-53.662915804391695],[-19.049573405176112,-22.46036923493498],[-20.486971473666468,-69.813772479288062],[-29.749353943865881,-73.586348060869426]],[[-19.049573405176112,-22.46036923493498],[-18.721998818789388,-11.66880646480822],[-30.750049689226596,-7.8112663207986941],[-31.449272112025476,-12.336278393150847],[-19.049573405176112,-22.46036923493498]]]}").getGeometry()); - Polygon convex_hull = (Polygon) (bounding.execute(polygon, null)); - Polygon differenced = (Polygon) (difference.execute(polygon, convex_hull, SpatialReference.create(4326), null)); - assertTrue(differenced.isEmpty()); - assertTrue(bounding.isConvex(convex_hull, null)); - } - - { - Polygon polygon = (Polygon) (TestCommonMethods.fromJson("{\"rings\":[[[-17.906614911617162,-53.670186894017093],[-72.687715727164573,-90],[-77.889582483879749,90],[-47.149885004784061,16.372797801863811],[-58.874489264131405,8.3403055152440846],[-44.017112148517498,8.8692333739436133],[-43.760297522359615,8.2541153357643502],[-48.398890069305921,4.7201397602360009],[-38.665987052649818,-3.9476907252248874],[-17.906614911617162,-53.670186894017093]],[[-2.7387498969355368,-90],[-17.906614911617162,-53.670186894017093],[-6.8038688963847829,-46.30705103709559],[-2.7387498969355368,-90]],[[-6.8038688963847829,-46.30705103709559],[-8.2224486207861638,-31.0597897622158],[2.1962303277340673,-40.338351652092697],[-6.8038688963847829,-46.30705103709559]],[[-8.2224486207861638,-31.0597897622158],[-38.665987052649818,-3.9476907252248874],[-43.760297522359615,8.2541153357643502],[-42.90074612601282,8.9089763975751382],[-44.017112148517498,8.8692333739436133],[-47.149885004784061,16.372797801863811],[45.190674429223662,79.635046572817728],[40.490070954305672,72.441418146356597],[63.53694979672099,90],[75.056911135062407,13.108310545642606],[-0.027204347469059975,10.435289586728711],[-10.580480746811602,-5.715051428780245],[-8.2224486207861638,-31.0597897622158]],[[-42.90074612601282,8.9089763975751382],[-0.027204347469059975,10.435289586728711],[40.490070954305672,72.441418146356597],[-42.90074612601282,8.9089763975751382]]]}").getGeometry()); - Polygon convex_hull = (Polygon) (bounding.execute(polygon, null)); - Polygon differenced = (Polygon) (difference.execute(polygon, convex_hull, SpatialReference.create(4326), null)); - assertTrue(differenced.isEmpty()); - assertTrue(bounding.isConvex(convex_hull, null)); - } - } - - @Test - public static void testPolylines() { - OperatorConvexHull bounding = (OperatorConvexHull) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.ConvexHull); - OperatorDensifyByLength densify = (OperatorDensifyByLength) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.DensifyByLength); - OperatorDifference difference = (OperatorDifference) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.Difference); - OperatorContains contains = (OperatorContains) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.Contains); - - { - Polyline poly = new Polyline(); - poly.startPath(0, 0); - poly.lineTo(0, 10); - poly.lineTo(0, 20); - poly.lineTo(0, 40); - poly.lineTo(0, 500); - - Polyline densified = (Polyline) (densify.execute(poly, 10.0, null)); - Polyline convex_hull = (Polyline) (bounding.execute(densified, null)); - Polyline differenced = (Polyline) (difference.execute(densified, convex_hull, SpatialReference.create(4326), null)); - assertTrue(differenced.isEmpty()); - assertTrue(bounding.isConvex(convex_hull, null)); - } - - { - Polyline polyline = new Polyline(); - polyline.startPath(-200, -90); - polyline.lineTo(-180, -85); - polyline.lineTo(-90, -70); - polyline.lineTo(0, 0); - polyline.lineTo(100, 25); - polyline.lineTo(170, 45); - polyline.lineTo(225, 65); - - Polyline densified = (Polyline) (densify.execute(polyline, 10.0, null)); - Polygon convex_hull = (Polygon) (bounding.execute(densified, null)); - boolean bcontains = contains.execute(convex_hull, densified, SpatialReference.create(4326), null); - - assertTrue(bcontains); - assertTrue(bounding.isConvex(convex_hull, null)); - } - } - - @Test - public static void testNonSimpleShape() { - OperatorConvexHull bounding = (OperatorConvexHull) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.ConvexHull); - OperatorDensifyByLength densify = (OperatorDensifyByLength) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.DensifyByLength); - OperatorDifference difference = (OperatorDifference) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.Difference); - OperatorContains contains = (OperatorContains) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.Contains); - - { - Polygon shape = (Polygon) (TestCommonMethods.fromJson("{\"rings\":[[[6.7260599916745036,-90],[15.304037095218971,-18.924146439950675],[6.3163297788539232,-90],[5.2105387036445823,-59.980719950158637],[5.1217504663506981,-60.571174400735508],[8.2945138368731044,-27.967042187979146],[15.76606600357545,28.594953216378414],[8.4365340991447919,66.880924521951329],[10.115022726199774,65.247385313781265],[12.721206966604395,-23.793016208456883],[12.051875868947576,-11.368909618476637],[11.867118776841318,44.968896646914239],[7.5326099218274614,35.095776924526533],[8.86765609460479,90],[3.2036592678446922,55.507964789691712],[0.23585282258761486,-42.620591380394039],[-1.2660432762142744,90],[5.5580612840503001,-9.4879902323389196],[12.258387597532487,-35.945231749575591],[-48.746716054894101,90],[7.2294405148356846,-15.719232058488402],[13.798313011339591,-10.467172541381753],[7.4430022048746718,6.3951685161785656],[6.4876332898327815,31.10016146737189],[9.3645424359058911,47.123308099298804],[13.398605254542668,-6.4398318586014325],[-90,90],[13.360786277212718,82.971274676174545],[7.9405631778693566,90],[10.512482079680538,90],[16.994982794293946,19.60673041736408],[16.723893839323615,22.728853852102926],[23.178783416627525,90],[6.7260599916745036,-90]],[[26.768777234301993,90],[20.949797955126346,90],[11.967758262201434,-0.45048849056049711],[17.535751576687339,52.767528591651441],[26.768777234301993,90]],[[18.677765775891793,12.559680067559942],[19.060218406331451,90],[17.123595624401705,90],[-2.3805299720687887,-90],[-11.882782057881979,-90],[21.640575461689693,90],[11.368255808198477,85.501555553904794],[17.390084032215348,90],[23.999392897519989,78.255909006554603],[-6.8860811786563101,69.49189433189926],[29.232578855788898,90],[25.951412073846683,90],[-5.5572284181160772,-16.763772082849457],[18.677765775891793,12.559680067559942]]]}").getGeometry()); - Polygon densified = (Polygon) (densify.execute(shape, 10.0, null)); - Polygon convex_hull = (Polygon) (bounding.execute(densified, null)); - Polygon differenced = (Polygon) (difference.execute(densified, convex_hull, SpatialReference.create(4326), null)); - assertTrue(differenced.isEmpty()); - assertTrue(bounding.isConvex(convex_hull, null)); - } - - { - Polygon shape = (Polygon) (TestCommonMethods.fromJson("{\"rings\":[[[-13.630596027421603,3.6796011190640709],[-10.617275202716886,-28.133054337834409],[-81.617315194491695,90],[-4.0763362539824293,-90],[2.8213537979804073,-90],[-5.1515857979973365,-11.605767592612787],[43.477754021411123,35.507543731267589],[-45.818261267516704,-90],[-4.8545715514870018,-64.204371906322223],[-1.744951154293144,-30.257848194381509],[-7.8524748309267149,15.513561279453089],[-10.657563385538953,-81.785061432086309],[-6.3487369893289411,-31.849779201388415],[-14.768278524737962,-12.004393281111058],[-27.001635582579841,90],[-14.967554248940855,-78.970629918591811],[-12.999635147475825,-38.584472796107939],[-13.630596027421603,3.6796011190640709]],[[-16.338143621861352,-37.415690513288375],[-21.553879270366266,-90],[-18.649338100909404,-90],[-24.880584966233631,1.3133858590648728],[-16.483464632078249,-53.979692212288882],[-24.836979215403964,-68.69859399640147],[-29.708282990385214,-90],[-27.469962102507036,-1.6392995673644872],[-20.405051753708271,61.943199597870034],[-18.242567838912599,24.405109362934219],[-66.334547696572528,-52.678390155566603],[-13.471083255903507,-33.782708412943229],[-7.092757068096085,33.673785662500464],[-2.7427100969018205,74.386868339212668],[-8.2174861339989675,90],[-15.699459164009667,90],[-9.5910045204059156,90],[-8.4504603287557369,90],[-1.5498862802092637,2.5144190340747681],[-6.5326327868410639,-17.428029961128306],[-10.947786354404593,31.516236387466538],[-7.4777936485986354,12.486727826508769],[-13.89052186883092,12.397126427870356],[-10.530672679779606,-55.463541447339118],[-8.7161833631330374,-90],[-4.7231067612639519,-90],[-3.9692500849117041,-32.204677519048822],[3.740804266163555,32.88191805391007],[6.2021313886056246,76.617541950091564],[6.1183997672398194,90],[0.59730820015390673,90],[7.3242950674530753,18.030401540676614],[1.8252371571535342,90],[-16.338143621861352,-37.415690513288375]]]}").getGeometry()); - Polygon densified = (Polygon) (densify.execute(shape, 10.0, null)); - Polygon convex_hull = (Polygon) (bounding.execute(densified, null)); - Polygon differenced = (Polygon) (difference.execute(densified, convex_hull, SpatialReference.create(4326), null)); - assertTrue(differenced.isEmpty()); - assertTrue(bounding.isConvex(convex_hull, null)); - } - - { - Polygon shape = (Polygon) (TestCommonMethods.fromJson("{\"rings\":[[[-11.752662474672046,-90],[-76.236530072126513,7.3247326417920817],[18.933251883215579,90],[51.538924439116798,90],[52.253017336758049,80.352482145105284],[41.767201918260639,51.890297432229289],[21.697252770910882,-1.3185641048567049],[45.112193442818935,60.758441021743636],[48.457184967377231,69.626584611257954],[49.531808284502759,70.202152706968036],[52.394797054144334,71.533541126234581],[ 52.9671102343993,70.704964290210626],[58.527850348069251,16.670036266565845],[62.310807912773328,-34.249918700039238],[62.775020703241523,-43.541598916699364],[64.631871865114277,-80.708319783339874],[65.096084655582459,-90],[-11.752662474672046,-90]]]}").getGeometry()); - Polygon convex_hull = (Polygon) (bounding.execute(shape, null)); - Polygon differenced = (Polygon) (difference.execute(shape, convex_hull, SpatialReference.create(4326), null)); - assertTrue(differenced.isEmpty()); - assertTrue(bounding.isConvex(convex_hull, null)); - } - - { - Polygon polygon = new Polygon(); - polygon.startPath(0, 0); - polygon.lineTo(0, 10); - polygon.lineTo(4, 10); - polygon.lineTo(9, 1); - polygon.lineTo(1, 1); - polygon.lineTo(5, 10); - polygon.lineTo(10, 10); - polygon.lineTo(10, 0); - - Polygon densified = (Polygon) (densify.execute(polygon, 1, null)); - Polygon convex_hull = (Polygon) (bounding.execute(densified, null)); - - double area = convex_hull.calculateArea2D(); - assertTrue(bounding.isConvex(convex_hull, null)); - assertTrue(area == 100.0); - - Point2D p1 = convex_hull.getXY(0); - Point2D p2 = convex_hull.getXY(1); - Point2D p3 = convex_hull.getXY(2); - Point2D p4 = convex_hull.getXY(3); - assertTrue(p1.x == 0.0 && p1.y == 0.0); - assertTrue(p2.x == 0.0 && p2.y == 10.0); - assertTrue(p3.x == 10.0 && p3.y == 10.0); - assertTrue(p4.x == 10.0 && p4.y == 0.0); - } - - { - Polygon polygon = new Polygon(); - polygon.startPath(0, 0); - polygon.lineTo(0, 10); - polygon.lineTo(8, 10); - polygon.lineTo(10, 8); - polygon.lineTo(10, 0); - polygon.lineTo(0, 0); - polygon.lineTo(0, 10); - polygon.lineTo(10, 10); - polygon.lineTo(10, 0); - - // Polygon densified = (Polygon)(densify.execute(polygon, 1, null)); - Polygon convex_hull = (Polygon) (bounding.execute(polygon, null)); - - double area = convex_hull.calculateArea2D(); - assertTrue(bounding.isConvex(convex_hull, null)); - assertTrue(area == 100.0); - - Point2D p1 = convex_hull.getXY(0); - Point2D p2 = convex_hull.getXY(1); - Point2D p3 = convex_hull.getXY(2); - Point2D p4 = convex_hull.getXY(3); - assertTrue(p1.x == 0.0 && p1.y == 0.0); - assertTrue(p2.x == 0.0 && p2.y == 10.0); - assertTrue(p3.x == 10.0 && p3.y == 10.0); - assertTrue(p4.x == 10.0 && p4.y == 0.0); - } - } - - @Test - public static void testStar() { - OperatorConvexHull bounding = (OperatorConvexHull) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.ConvexHull); - OperatorDensifyByLength densify = (OperatorDensifyByLength) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.DensifyByLength); - - Polygon star = new Polygon(); - star.startPath(0, 0); - star.lineTo(0, 0); - star.lineTo(5, 10); - star.lineTo(5, 10); - star.lineTo(10, 0); - star.lineTo(10, 0); - star.startPath(0, 5); - star.lineTo(0, 5); - star.lineTo(10, 5); - star.lineTo(10, 5); - star.lineTo(5, -5); - star.lineTo(5, -5); - - star.reversePath(1); - - Polygon densified = (Polygon) (densify.execute(star, 1.0, null)); - Polygon convex_hull = (Polygon) (bounding.execute(densified, null)); - - assertTrue(bounding.isConvex(convex_hull, null)); - - Point2D p1 = convex_hull.getXY(0); - Point2D p2 = convex_hull.getXY(1); - Point2D p3 = convex_hull.getXY(2); - Point2D p4 = convex_hull.getXY(3); - Point2D p5 = convex_hull.getXY(4); - Point2D p6 = convex_hull.getXY(5); - assertTrue(p1.x == 0.0 && p1.y == 0.0); - assertTrue(p2.x == 0.0 && p2.y == 5.0); - assertTrue(p3.x == 5.0 && p3.y == 10.0); - assertTrue(p4.x == 10.0 && p4.y == 5.0); - assertTrue(p5.x == 10.0 && p5.y == 0.0); - assertTrue(p6.x == 5.0 && p6.y == -5.0); - } - - @Test - public static void testPointsArray() { - Point2D[] points = new Point2D[6]; - int[] convex_hull = new int[6]; - - for (int i = 0; i < 6; i++) - points[i] = new Point2D(); - - points[0].x = 0; - points[0].y = 0; - points[1].x = 5; - points[1].y = 10; - points[2].x = 10; - points[2].y = 0; - points[3].x = 0; - points[3].y = 5; - points[4].x = 10; - points[4].y = 5; - points[5].x = 5; - points[5].y = -5; - - ConvexHull.construct(points, 6, convex_hull); - assertTrue(convex_hull[0] == 0); - assertTrue(convex_hull[1] == 3); - assertTrue(convex_hull[2] == 1); - assertTrue(convex_hull[3] == 4); - assertTrue(convex_hull[4] == 2); - assertTrue(convex_hull[5] == 5); - } - - @Test - public static void testMergeCursor() { - OperatorConvexHull bounding = (OperatorConvexHull) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.ConvexHull); - - Polygon geom1 = new Polygon(); - Polygon geom2 = new Polygon(); - Point geom3 = new Point(); - Line geom4 = new Line(); - Envelope geom5 = new Envelope(); - MultiPoint geom6 = new MultiPoint(); - - // polygon - geom1.startPath(0, 0); - geom1.lineTo(0, 0); - geom1.lineTo(5, 11); - geom1.lineTo(5, 11); - geom1.lineTo(10, 0); - geom1.lineTo(10, 0); - - // polygon - geom2.startPath(0, 5); - geom2.lineTo(0, 5); - geom2.lineTo(10, 5); - geom2.lineTo(10, 5); - geom2.lineTo(5, -5); - geom2.lineTo(5, -5); - - // point - geom3.setXY(15, 1.25); - - // segment - geom4.setEndXY(-5, 1.25); - geom4.setStartXY(0, 0); - - // envelope - geom5.setCoords(0, 0, 5, 10); - - // multi_point - geom6.add(10, 5); - geom6.add(10, 10); - - // create cursor - Geometry[] geoms = new Geometry[6]; - geoms[0] = geom1; - geoms[1] = geom2; - geoms[2] = geom3; - geoms[3] = geom4; - geoms[4] = geom5; - geoms[5] = geom6; - GeometryCursor cursor = new SimpleGeometryCursor(geoms); - - // create convex hull from the cursor with b_merge set to true - GeometryCursor convex_hull_curs = bounding.execute(cursor, true, null); - Polygon convex_hull = (Polygon) (convex_hull_curs.next()); - assertTrue(convex_hull_curs.next() == null); - - assertTrue(bounding.isConvex(convex_hull, null)); - - Point2D p1 = convex_hull.getXY(0); - Point2D p2 = convex_hull.getXY(1); - Point2D p3 = convex_hull.getXY(2); - Point2D p4 = convex_hull.getXY(3); - Point2D p5 = convex_hull.getXY(4); - Point2D p6 = convex_hull.getXY(5); - assertTrue(p1.x == 5.0 && p1.y == 11.0); - assertTrue(p2.x == 10.0 && p2.y == 10); - assertTrue(p3.x == 15.0 && p3.y == 1.25); - assertTrue(p4.x == 5.0 && p4.y == -5.0); - assertTrue(p5.x == -5.0 && p5.y == 1.25); - assertTrue(p6.x == 0.0 && p6.y == 10.0); - - // Test GeometryEngine - Geometry[] merged_hull = GeometryEngine.convexHull(geoms, true); - convex_hull = (Polygon) merged_hull[0]; - p1 = convex_hull.getXY(0); - p2 = convex_hull.getXY(1); - p3 = convex_hull.getXY(2); - p4 = convex_hull.getXY(3); - p5 = convex_hull.getXY(4); - p6 = convex_hull.getXY(5); - assertTrue(p1.x == 5.0 && p1.y == 11.0); - assertTrue(p2.x == 10.0 && p2.y == 10); - assertTrue(p3.x == 15.0 && p3.y == 1.25); - assertTrue(p4.x == 5.0 && p4.y == -5.0); - assertTrue(p5.x == -5.0 && p5.y == 1.25); - assertTrue(p6.x == 0.0 && p6.y == 10.0); - - } - - @Test - public void testHullTickTock() { - Polygon geom1 = new Polygon(); - Polygon geom2 = new Polygon(); - Point geom3 = new Point(); - Line geom4 = new Line(); - Envelope geom5 = new Envelope(); - MultiPoint geom6 = new MultiPoint(); - - // polygon - geom1.startPath(0, 0); - geom1.lineTo(0, 0); - geom1.lineTo(5, 11); - geom1.lineTo(5, 11); - geom1.lineTo(10, 0); - geom1.lineTo(10, 0); - - // polygon - geom2.startPath(0, 5); - geom2.lineTo(0, 5); - geom2.lineTo(10, 5); - geom2.lineTo(10, 5); - geom2.lineTo(5, -5); - geom2.lineTo(5, -5); - - // point - geom3.setXY(15, 1.25); - - // segment - geom4.setEndXY(-5, 1.25); - geom4.setStartXY(0, 0); - - // envelope - geom5.setCoords(0, 0, 5, 10); - - // multi_point - geom6.add(10, 5); - geom6.add(10, 10); - - // Create - ListeningGeometryCursor gc = new ListeningGeometryCursor(); - GeometryCursor ticktock = OperatorConvexHull.local().execute(gc, true, null); - - // Use tick-tock to push a geometry and do a piece of work. - gc.tick(geom1); - ticktock.tock(); - gc.tick(geom2); - ticktock.tock(); - gc.tick(geom3);// skiped one tock just for testing. - ticktock.tock(); - gc.tick(geom4); - ticktock.tock(); - gc.tick(geom5); - ticktock.tock(); - gc.tick(geom6); - ticktock.tock(); - // Get the result - Geometry result = ticktock.next(); - Polygon convex_hull = (Polygon) result; - assertTrue(OperatorConvexHull.local().isConvex(convex_hull, null)); - - Point2D p1 = convex_hull.getXY(0); - Point2D p2 = convex_hull.getXY(1); - Point2D p3 = convex_hull.getXY(2); - Point2D p4 = convex_hull.getXY(3); - Point2D p5 = convex_hull.getXY(4); - Point2D p6 = convex_hull.getXY(5); - assertTrue(p1.x == 5.0 && p1.y == 11.0); - assertTrue(p2.x == 10.0 && p2.y == 10); - assertTrue(p3.x == 15.0 && p3.y == 1.25); - assertTrue(p4.x == 5.0 && p4.y == -5.0); - assertTrue(p5.x == -5.0 && p5.y == 1.25); - assertTrue(p6.x == 0.0 && p6.y == 10.0); - } + @Override + protected void setUp() throws Exception { + super.setUp(); + } + + @Override + protected void tearDown() throws Exception { + super.tearDown(); + } + + @Test + public static void testFewPoints() { + { + Polygon polygon = new Polygon(); + polygon.addPath((Point2D[]) null, 0, true); + polygon.insertPoint(0, -1, Point2D.construct(5, 5)); + + Point convex_hull = (Point) OperatorConvexHull.local().execute(polygon, null); + assertTrue(convex_hull.getXY().equals(Point2D.construct(5, 5))); + } + + { + Point2D[] pts = new Point2D[3]; + + pts[0] = Point2D.construct(0, 0); + pts[1] = Point2D.construct(0, 0); + pts[2] = Point2D.construct(0, 0); + + int[] out_pts = new int[3]; + int res = ConvexHull.construct(pts, 3, out_pts); + assertTrue(res == 1); + assertTrue(out_pts[0] == 0); + } + + { + Point2D[] pts = new Point2D[1]; + pts[0] = Point2D.construct(0, 0); + + int[] out_pts = new int[1]; + int res = ConvexHull.construct(pts, 1, out_pts); + assertTrue(res == 1); + assertTrue(out_pts[0] == 0); + } + } + + @Test + public static void testDegenerate() { + OperatorConvexHull bounding = (OperatorConvexHull) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.ConvexHull); + OperatorDensifyByLength densify = (OperatorDensifyByLength) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.DensifyByLength); + + { + Polygon polygon = new Polygon(); + polygon.startPath(0, 0); + polygon.lineTo(1, 0); + polygon.lineTo(0, 0); + polygon.lineTo(2, 0); + polygon.lineTo(1, 0); + polygon.lineTo(3, 0); + + polygon.startPath(0, 0); + polygon.lineTo(1, 0); + polygon.lineTo(0, 0); + polygon.lineTo(2, 0); + polygon.lineTo(1, 0); + polygon.lineTo(3, 0); + + Polygon densified = (Polygon) (densify.execute(polygon, .5, null)); + Polyline convex_hull = (Polyline) (bounding.execute(densified, null)); + assertTrue(bounding.isConvex(convex_hull, null)); + assertTrue(convex_hull.calculateArea2D() == 0.0); + + Point2D p1 = convex_hull.getXY(0); + Point2D p2 = convex_hull.getXY(1); + assertTrue(p1.x == 0.0 && p1.y == 0.0); + assertTrue(p2.x == 3.0 && p2.y == 0.0); + } + + { + Polygon polygon = new Polygon(); + polygon.startPath(0, 0); + polygon.lineTo(0, 0); + polygon.lineTo(0, 0); + polygon.lineTo(NumberUtils.doubleEps(), 0); + polygon.lineTo(0, NumberUtils.doubleEps()); + polygon.lineTo(10, 0); + polygon.lineTo(10, 0); + polygon.lineTo(10, 5); + polygon.lineTo(10, 10); + polygon.lineTo(5, 10); + polygon.lineTo(10, 0); + polygon.lineTo(10, 10); + polygon.lineTo(5, 10); + polygon.lineTo(0, 10); + polygon.lineTo(0, 0); + + polygon.startPath(0, 0); + polygon.lineTo(0, 0); + polygon.lineTo(0, 0); + polygon.lineTo(10, 0); + polygon.lineTo(10, 0); + polygon.lineTo(10, 5); + polygon.lineTo(10, 10); + polygon.lineTo(5, 10); + polygon.lineTo(10, 0); + polygon.lineTo(10, 10); + polygon.lineTo(5, 10); + polygon.lineTo(0, 10); + polygon.lineTo(0, 0); + + polygon.startPath(0, 0); + polygon.lineTo(0, 0); + polygon.lineTo(0, 0); + polygon.lineTo(10, 0); + polygon.lineTo(5, 0); + polygon.lineTo(10, 0); + polygon.lineTo(10, 5); + polygon.lineTo(10, 0); + polygon.lineTo(10, 10); + polygon.lineTo(5, 10); + polygon.lineTo(10, 10); + polygon.lineTo(5, 10); + polygon.lineTo(0, 10); + polygon.lineTo(5, 10); + polygon.lineTo(0, 0); + + Polygon densified = (Polygon) (densify.execute(polygon, 1, null)); + Polygon convex_hull = (Polygon) (bounding.execute(densified, null)); + + double area = convex_hull.calculateArea2D(); + assertTrue(bounding.isConvex(convex_hull, null)); + assertTrue(area == 100.0); + + Point2D p1 = convex_hull.getXY(0); + Point2D p2 = convex_hull.getXY(1); + Point2D p3 = convex_hull.getXY(2); + Point2D p4 = convex_hull.getXY(3); + assertTrue(p1.x == 0.0 && p1.y == 0.0); + assertTrue(p2.x == 0.0 && p2.y == 10.0); + assertTrue(p3.x == 10.0 && p3.y == 10.0); + assertTrue(p4.x == 10.0 && p4.y == 0.0); + } + + { + Polygon polygon = new Polygon(); + polygon.startPath(0, 0); + polygon.lineTo(0, 10); + polygon.lineTo(5, 10); + polygon.lineTo(5, 5); + polygon.lineTo(5, 8); + polygon.lineTo(10, 10); + polygon.lineTo(10, 0); + + Polygon densified = (Polygon) (densify.execute(polygon, 1, null)); + Polygon convex_hull = (Polygon) (bounding.execute(densified, null)); + + double area = convex_hull.calculateArea2D(); + assertTrue(bounding.isConvex(convex_hull, null)); + assertTrue(area == 100.0); + + Point2D p1 = convex_hull.getXY(0); + Point2D p2 = convex_hull.getXY(1); + Point2D p3 = convex_hull.getXY(2); + Point2D p4 = convex_hull.getXY(3); + assertTrue(p1.x == 0.0 && p1.y == 0.0); + assertTrue(p2.x == 0.0 && p2.y == 10.0); + assertTrue(p3.x == 10.0 && p3.y == 10.0); + assertTrue(p4.x == 10.0 && p4.y == 0.0); + } + + { + Polygon polygon = new Polygon(); + polygon.startPath(0, 0); + polygon.lineTo(0, 0); + polygon.lineTo(0, 0); + polygon.lineTo(10, 0); + polygon.lineTo(5, 0); + polygon.lineTo(10, 0); + polygon.lineTo(10, 5); + polygon.lineTo(10, 0); + polygon.lineTo(10, 10); + polygon.lineTo(5, 10); + polygon.lineTo(10, 10); + polygon.lineTo(5, 10); + polygon.lineTo(0, 10); + polygon.lineTo(5, 10); + polygon.lineTo(0, 0); + Polygon densified = (Polygon) (densify.execute(polygon, 1, null)); + Polygon convex_hull = (Polygon) (bounding.execute(densified, null)); + double area = convex_hull.calculateArea2D(); + assertTrue(bounding.isConvex(convex_hull, null)); + assertTrue(area == 100.0); + + Point2D p1 = convex_hull.getXY(0); + Point2D p2 = convex_hull.getXY(1); + Point2D p3 = convex_hull.getXY(2); + Point2D p4 = convex_hull.getXY(3); + assertTrue(p1.x == 0.0 && p1.y == 0.0); + assertTrue(p2.x == 0.0 && p2.y == 10.0); + assertTrue(p3.x == 10.0 && p3.y == 10.0); + assertTrue(p4.x == 10.0 && p4.y == 0.0); + } + + { + Polygon polygon = new Polygon(); + polygon.startPath(0, 0); + polygon.lineTo(0, 0); + polygon.lineTo(0, 10); + polygon.lineTo(0, 0); + polygon.lineTo(10, 10); + polygon.lineTo(0, 0); + polygon.lineTo(10, 0); + polygon.lineTo(0, 0); + + polygon.startPath(0, 10); + polygon.lineTo(0, 10); + polygon.lineTo(10, 10); + polygon.lineTo(0, 10); + polygon.lineTo(10, 0); + polygon.lineTo(0, 10); + polygon.lineTo(0, 0); + polygon.lineTo(0, 10); + + polygon.startPath(10, 10); + polygon.lineTo(10, 10); + polygon.lineTo(10, 0); + polygon.lineTo(10, 10); + polygon.lineTo(0, 0); + polygon.lineTo(10, 10); + polygon.lineTo(0, 10); + polygon.lineTo(10, 10); + + polygon.startPath(10, 0); + polygon.lineTo(10, 0); + polygon.lineTo(0, 0); + polygon.lineTo(10, 0); + polygon.lineTo(0, 10); + polygon.lineTo(10, 0); + polygon.lineTo(10, 10); + polygon.lineTo(10, 0); + + Polygon densified = (Polygon) (densify.execute(polygon, 1, null)); + Polygon convex_hull = (Polygon) (bounding.execute(densified, null)); + assertTrue(bounding.isConvex(convex_hull, null)); + + Point2D p1 = convex_hull.getXY(0); + Point2D p2 = convex_hull.getXY(1); + Point2D p3 = convex_hull.getXY(2); + Point2D p4 = convex_hull.getXY(3); + assertTrue(p1.x == 0.0 && p1.y == 0.0); + assertTrue(p2.x == 0.0 && p2.y == 10.0); + assertTrue(p3.x == 10.0 && p3.y == 10.0); + assertTrue(p4.x == 10.0 && p4.y == 0.0); + } + + { + MultiPoint mpoint = new MultiPoint(); + mpoint.add(4, 4); + mpoint.add(4, 4); + mpoint.add(4, 4); + mpoint.add(4, 4); + + Point convex_hull = (Point) bounding.execute(mpoint, null); + assertTrue(convex_hull.calculateArea2D() == 0.0); + assertTrue(bounding.isConvex(convex_hull, null)); + assertTrue(convex_hull.getXY().equals(Point2D.construct(4, 4))); + } + + { + MultiPoint mpoint = new MultiPoint(); + mpoint.add(4, 4); + + Point convex_hull = (Point) bounding.execute(mpoint, null); + assertTrue(bounding.isConvex(convex_hull, null)); + assertTrue(convex_hull.getXY().equals(Point2D.construct(4, 4))); + } + + { + MultiPoint mpoint = new MultiPoint(); + mpoint.add(4, 4); + mpoint.add(4, 5); + + Polyline convex_hull = (Polyline) bounding.execute(mpoint, null); + assertTrue(convex_hull.getPointCount() == 2); + assertTrue(bounding.isConvex(convex_hull, null)); + assertTrue(convex_hull.calculateLength2D() == 1.0); + } + + { + Line line = new Line(); + line.setStartXY(0, 0); + line.setEndXY(0, 1); + + Polyline convex_hull = (Polyline) bounding.execute(line, null); + assertTrue(convex_hull.getPointCount() == 2); + assertTrue(bounding.isConvex(convex_hull, null)); + assertTrue(convex_hull.calculateLength2D() == 1.0); + } + + { + Polyline polyline = new Polyline(); + polyline.startPath(0, 0); + polyline.lineTo(0, 1); + + Polyline convex_hull = (Polyline) bounding.execute(polyline, null); + assertTrue(convex_hull.getPointCount() == 2); + assertTrue(bounding.isConvex(convex_hull, null)); + assertTrue(polyline == convex_hull); + assertTrue(convex_hull.calculateLength2D() == 1.0); + } + + { + Envelope env = new Envelope(0, 0, 10, 10); + assertTrue(OperatorConvexHull.local().isConvex(env, null)); + + Polygon convex_hull = (Polygon) bounding.execute(env, null); + assertTrue(bounding.isConvex(convex_hull, null)); + assertTrue(convex_hull.getPointCount() == 4); + assertTrue(convex_hull.getXY(0).equals(Point2D.construct(0, 0))); + assertTrue(convex_hull.getXY(1).equals(Point2D.construct(0, 10))); + assertTrue(convex_hull.getXY(2).equals(Point2D.construct(10, 10))); + assertTrue(convex_hull.getXY(3).equals(Point2D.construct(10, 0))); + } + + { + Envelope env = new Envelope(0, 0, 0, 10); + assertTrue(!OperatorConvexHull.local().isConvex(env, null)); + + Polyline convex_hull = (Polyline) bounding.execute(env, null); + assertTrue(bounding.isConvex(convex_hull, null)); + assertTrue(convex_hull.getPointCount() == 2); + assertTrue(convex_hull.getXY(0).equals(Point2D.construct(0, 0))); + assertTrue(convex_hull.getXY(1).equals(Point2D.construct(0, 10))); + } + + { + Envelope env = new Envelope(0, 0, 0, 10); + assertTrue(!OperatorConvexHull.local().isConvex(env, null)); + + Polyline convex_hull = (Polyline) bounding.execute(env, null); + assertTrue(bounding.isConvex(convex_hull, null)); + assertTrue(convex_hull.getPointCount() == 2); + assertTrue(convex_hull.getXY(0).equals(Point2D.construct(0, 0))); + assertTrue(convex_hull.getXY(1).equals(Point2D.construct(0, 10))); + } + + { + Envelope env = new Envelope(5, 5, 5, 5); + assertTrue(!OperatorConvexHull.local().isConvex(env, null)); + + Point convex_hull = (Point) bounding.execute(env, null); + assertTrue(bounding.isConvex(convex_hull, null)); + assertTrue(convex_hull.getXY().equals(Point2D.construct(5, 5))); + } + } + + @Test + public static void testSegment() { + { + Line line = new Line(); + line.setStartXY(5, 5); + line.setEndXY(5, 5); + + assertTrue(!OperatorConvexHull.local().isConvex(line, null)); + Point point = (Point) OperatorConvexHull.local().execute(line, null); + assertTrue(point.getXY().equals(Point2D.construct(5, 5))); + } + + { + Line line = new Line(); + line.setStartXY(5, 5); + line.setEndXY(5, 6); + + assertTrue(OperatorConvexHull.local().isConvex(line, null)); + Polyline polyline = (Polyline) OperatorConvexHull.local().execute(line, null); + assertTrue(polyline.getPointCount() == 2); + assertTrue(polyline.getXY(0).equals(Point2D.construct(5, 5))); + assertTrue(polyline.getXY(1).equals(Point2D.construct(5, 6))); + } + } + + + @Test + public static void testSquare() { + OperatorConvexHull bounding = (OperatorConvexHull) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.ConvexHull); + OperatorDensifyByLength densify = (OperatorDensifyByLength) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.DensifyByLength); + OperatorDifference difference = (OperatorDifference) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.Difference); + OperatorContains contains = (OperatorContains) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.Contains); + + Polygon square = new Polygon(); + square.startPath(0, 0); + square.lineTo(2, 3); + square.lineTo(1, 4); + square.lineTo(0, 5); + square.lineTo(0, 7); + square.lineTo(2, 7); + square.lineTo(0, 10); + square.lineTo(4, 7); + square.lineTo(6, 7); + square.lineTo(7, 10); + square.lineTo(8, 10); + square.lineTo(10, 10); + square.lineTo(8, 7); + square.lineTo(10, 5); + square.lineTo(8, 3); + square.lineTo(10, 1); + square.lineTo(10, 0); + square.lineTo(5, 5); + square.lineTo(8, 0); + square.lineTo(4, 3); + square.lineTo(5, 0); + square.lineTo(3, 1); + square.lineTo(3, 0); + square.lineTo(2, 1); + + Polygon densified = (Polygon) (densify.execute(square, 1.0, null)); + + densified.addAttribute(VertexDescription.Semantics.ID); + for (int i = 0; i < densified.getPointCount(); i++) + densified.setAttribute(VertexDescription.Semantics.ID, i, 0, i); + + Polygon convex_hull = (Polygon) (bounding.execute(densified, null)); + Polygon differenced = (Polygon) (difference.execute(densified, convex_hull, SpatialReference.create(4326), null)); + + assertTrue(differenced.isEmpty()); + assertTrue(bounding.isConvex(convex_hull, null)); + } + + @Test + public static void testPolygons() { + OperatorConvexHull bounding = (OperatorConvexHull) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.ConvexHull); + OperatorDensifyByLength densify = (OperatorDensifyByLength) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.DensifyByLength); + OperatorDifference difference = (OperatorDifference) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.Difference); + + { + Polygon shape = (Polygon) (TestCommonMethods.fromJson("{\"rings\":[[[1.3734426370715553,-90],[-24.377532184629967,-63.428606327053856],[-25.684686546621553,90],[-24.260574484321914,80.526315789473699],[-25.414389575040037,90],[-23.851448513708718,90],[-23.100135788742072,87.435887853000679],[5.6085096351011448,-48.713222410606306],[1.3734426370715553,-90]]]}").getGeometry()); + Polygon convex_hull = (Polygon) (bounding.execute(shape, null)); + Polygon differenced = (Polygon) (difference.execute(shape, convex_hull, SpatialReference.create(4326), null)); + assertTrue(differenced.isEmpty()); + assertTrue(bounding.isConvex(convex_hull, null)); + } + + { + Polygon polygon = (Polygon) (TestCommonMethods.fromJson("{\"rings\":[[[-179.64843749999693,-43.3535476539993],[-179.99999999999696,-43.430006211999284],[-179.99999999999696,39.329644416999436],[-179.64843749999693,38.98862638799943],[-89.99999999999838,29.008084980999506],[-112.8515624999981,-16.20113770599962],[-115.66406249999804,-18.882554574999688],[-124.80468749999788,-23.7925511709996],[-138.86718749999767,-30.6635901109995],[-157.49999999999736,-38.468358112999354],[-162.42187499999724,-39.56498442199932],[-179.64843749999693,-43.3535476539993]],[[179.99999999999696,-43.430006211999284],[179.64843749999693,-43.50646476999926],[162.0703124999973,-42.36267115399919],[160.3124999999973,-42.24790485699929],[143.78906249999756,-41.1680427339993],[138.16406249999767,-39.64744846799925],[98.43749999999845,-28.523889212999524],[78.39843749999878,-5.1644422999998705],[75.9374999999988,19.738611663999766],[88.2421874999986,33.51651305599954],[108.63281249999815,44.160795160999356],[138.16406249999767,51.02062617799914],[140.9765624999976,51.68129673399923],[160.3124999999973,52.8064856429991],[162.0703124999973,52.908902047999206],[163.12499999999727,52.97036560499911],[165.93749999999716,52.97036560499911],[179.99999999999696,39.329644416999436],[179.99999999999696,-43.430006211999284]]]}").getGeometry()); + Polygon densified = (Polygon) (densify.execute(polygon, 10.0, null)); + Polygon convex_hull = (Polygon) (bounding.execute(densified, null)); + Polygon differenced = (Polygon) (difference.execute(densified, convex_hull, SpatialReference.create(4326), null)); + assertTrue(differenced.isEmpty()); + assertTrue(bounding.isConvex(convex_hull, null)); + } + + { + Polygon polygon = new Polygon(); + polygon.startPath(1, 0); + polygon.lineTo(-1, 0); + polygon.lineTo(0, -1); + Polygon convex_hull = (Polygon) (bounding.execute(polygon, null)); + Polygon differenced = (Polygon) (difference.execute(polygon, convex_hull, SpatialReference.create(4326), null)); + assertTrue(differenced.isEmpty()); + assertTrue(bounding.isConvex(convex_hull, null)); + } + + { + Polygon polygon = (Polygon) (TestCommonMethods.fromJson("{\"rings\":[[[-38.554566833914528,21.902000764339238],[-30.516168471666138,90],[-38.554566833914528,21.902000764339238]],[[-43.013227444613932,28.423410187206883],[-43.632473335895916,90],[-42.342268693420237,62.208637129146894],[-37.218731802058755,63.685357222187029],[-32.522681335230686,47.080307818055296],[-40.537308829621097,-21.881392019745604],[-47.59510451722663,18.812521648505964],[-53.25344489340366,30.362745244224911],[-46.629060462410138,90],[-50.069277433245119,18.254168921734287],[-42.171214434397982,72.623347387008081],[-43.000844452530551,90],[-46.162281544954659,90],[-39.462049205071331,90],[-47.434856316742902,38.662565208814371],[-52.13115779642537,-19.952586632199857],[-56.025328966335081,90],[-60.056846215416158,-44.023645282268355],[-60.12338894192289,50.374596189881942],[-35.787508034048379,-7.8839007676038513],[-60.880218074135605,-46.447995750907815],[-67.782542852117956,-85.106300958016107],[-65.053131764313761,-0.96651520578494665],[-72.375821140304154,90],[-78.561502106749245,90],[-83.809168672565946,33.234498214085811],[-60.880218054506344,-46.447995733653201],[-75.637095425108981,59.886574792622838],[-71.364085965028096,31.976373491332097],[-67.89968380886117,90],[-67.544349171474749,8.8435794458927504],[-70.780047377934707,80.683454463576624],[-64.996733940204948,34.349882797035313],[-56.631753638680905,39.815838152456926],[-60.392350183516896,52.75446132093407],[-58.51633728692137,90],[-64.646972065627097,41.444197803942579],[-73.355591244695518,-0.15370205145035776],[-43.013227444613932,28.423410187206883]],[[-69.646471076946,-85.716191379686904],[-62.854465128320491,-45.739046580967972],[-71.377481570643141,-90],[-66.613495837251435,-90],[-66.9765142407159,-90],[-66.870099169607329,-90],[-67.23180828626819,-61.248439074609649],[-58.889775875438851,-90],[-53.391995883729322,-69.476385967096491],[-69.646471076946,-85.716191379686904]]]}").getGeometry()); + Polygon densified = (Polygon) (densify.execute(polygon, 10.0, null)); + Polygon convex_hull = (Polygon) (bounding.execute(densified, null)); + Polygon differenced = (Polygon) (difference.execute(densified, convex_hull, SpatialReference.create(4326), null)); + assertTrue(differenced.isEmpty()); + // assertTrue(bounding.isConvex(*convex_hull, null)); + } + + { + Polygon polygon = (Polygon) (TestCommonMethods.fromJson("{\"rings\":[[[-38.554566833914528,21.902000764339238],[-30.516168471666138,90],[-38.554566833914528,21.902000764339238]],[[-43.013227444613932,28.423410187206883],[-43.632473335895916,90],[-42.342268693420237,62.208637129146894],[-37.218731802058755,63.685357222187029],[-32.522681335230686,47.080307818055296],[-40.537308829621097,-21.881392019745604],[-47.59510451722663,18.812521648505964],[-53.25344489340366,30.362745244224911],[-46.629060462410138,90],[-50.069277433245119,18.254168921734287],[-42.171214434397982,72.623347387008081],[-43.000844452530551,90],[-46.162281544954659,90],[-39.462049205071331,90],[-47.434856316742902,38.662565208814371],[-52.13115779642537,-19.952586632199857],[-56.025328966335081,90],[-60.056846215416158,-44.023645282268355],[-60.12338894192289,50.374596189881942],[-35.787508034048379,-7.8839007676038513],[-60.880218074135605,-46.447995750907815],[-67.782542852117956,-85.106300958016107],[-65.053131764313761,-0.96651520578494665],[-72.375821140304154,90],[-78.561502106749245,90],[-83.809168672565946,33.234498214085811],[-60.880218054506344,-46.447995733653201],[-75.637095425108981,59.886574792622838],[-71.364085965028096,31.976373491332097],[-67.89968380886117,90],[-67.544349171474749,8.8435794458927504],[-70.780047377934707,80.683454463576624],[-64.996733940204948,34.349882797035313],[-56.631753638680905,39.815838152456926],[-60.392350183516896,52.75446132093407],[-58.51633728692137,90],[-64.646972065627097,41.444197803942579],[-73.355591244695518,-0.15370205145035776],[-43.013227444613932,28.423410187206883]],[[-69.646471076946,-85.716191379686904],[-62.854465128320491,-45.739046580967972],[-71.377481570643141,-90],[-66.613495837251435,-90],[-66.9765142407159,-90],[-66.870099169607329,-90],[-67.23180828626819,-61.248439074609649],[-58.889775875438851,-90],[-53.391995883729322,-69.476385967096491],[-69.646471076946,-85.716191379686904]]]}").getGeometry()); + Polygon convex_hull = (Polygon) (bounding.execute(polygon, null)); + Polygon differenced = (Polygon) (difference.execute(polygon, convex_hull, SpatialReference.create(4326), null)); + assertTrue(differenced.isEmpty()); + assertTrue(bounding.isConvex(convex_hull, null)); + } + + { + Polygon polygon = (Polygon) (TestCommonMethods.fromJson("{\"rings\":[[[-36.269498017702901,-26.37490682626369],[-49.146436641060951,-49.881862499696126],[-37.560006446488146,-45.592052597656789],[-39.13770692863632,-69.085816352131204],[-65.415587331361877,-90],[-51.462290812033373,-16.760787566546721],[-28.454456182408332,90],[-36.269498017702901,-26.37490682626369]],[[-40.542178258552283,-90],[-39.13770692863632,-69.085816352131204],[-16.295804332590937,-50.906277575066262],[-40.542178258552283,-90]],[[-16.295804332590937,-50.906277575066262],[-5.6790432913971927,-33.788307256548933],[14.686101893282586,-26.248228042967728],[-16.295804332590937,-50.906277575066262]],[[-37.560006446488146,-45.592052597656789],[-36.269498017702901,-26.37490682626369],[27.479825940672225,90],[71.095881152477034,90],[-5.6790432913971927,-33.788307256548933],[-37.560006446488146,-45.592052597656789]]]}").getGeometry()); + Polygon convex_hull = (Polygon) (bounding.execute(polygon, null)); + Polygon differenced = (Polygon) (difference.execute(polygon, convex_hull, SpatialReference.create(4326), null)); + assertTrue(differenced.isEmpty()); + assertTrue(bounding.isConvex(convex_hull, null)); + } + + { + Polygon polygon = (Polygon) (TestCommonMethods.fromJson("{\"rings\":[[[-77.020281185856106,-80.085419699581706],[-77.328568930885723,-83.404479889897416],[-80.495259564600545,-90],[-77.020281185856106,-80.085419699581706]],[[-77.941187535385211,-90],[-77.328568930885723,-83.404479889897416],[-39.252034383972621,-4.0994329574862469],[-39.29471328421063,-6.5269494453154593],[-77.941187535385211,-90]],[[-77.020281185856106,-80.085419699581706],[-62.688864277996522,74.208210509833052],[-38.108861278327581,80.371071656873013],[-37.597643844595929,90],[-38.663943358642484,29.350366647752089],[-77.020281185856106,-80.085419699581706]],[[-40.265125886194951,-61.722668598742551],[-39.29471328421063,-6.5269494453154593],[-15.554402498931253,44.750073899273843],[-8.4447006412989474,13.127318978368956],[-5.310206313296316,-4.5170390491918795],[-40.265125886194951,-61.722668598742551]],[[-39.252034383972621,-4.0994329574862469],[-38.663943358642484,29.350366647752089],[-22.476078360563164,75.536520897660651],[-15.632105532320049,45.095683888365997],[-39.252034383972621,-4.0994329574862469]],[[-15.554402498931253,44.750073899273843],[-15.632105532320049,45.095683888365997],[-8.9755856576261941,58.959750756602595],[-15.554402498931253,44.750073899273843]]]}").getGeometry()); + Polygon convex_hull = (Polygon) (bounding.execute(polygon, null)); + Polygon differenced = (Polygon) (difference.execute(polygon, convex_hull, SpatialReference.create(4326), null)); + assertTrue(differenced.isEmpty()); + assertTrue(bounding.isConvex(convex_hull, null)); + } + + { + Polygon polygon = (Polygon) (TestCommonMethods.fromJson("{\"rings\":[[[-68.840007952128175,37.060080998089632],[-68.145986924561413,31.114096694815196],[-69.187773850176768,30.693518246958952],[-68.840007952128175,37.060080998089632]],[[-75.780513355389928,-90],[-69.21567111077384,30.182802098042274],[-50.875629803516389,37.146119571446704],[-75.780513355389928,-90]],[[4.2911006174797457,-1.144569312564311],[-66.484019915251849,80.191238371060038],[-65.948228008382316,90],[4.2911006174797457,-1.144569312564311]],[[-90,22.291441435181515],[-69.187773850176768,30.693518246958952],[-69.21567111077384,30.182802098042274],[-90,22.291441435181515]],[[-68.840007952128175,37.060080998089632],[-75.019206401201359,90],[-66.484019915251849,80.191238371060038],[-68.840007952128175,37.060080998089632]]]}").getGeometry()); + Polygon convex_hull = (Polygon) (bounding.execute(polygon, null)); + Polygon differenced = (Polygon) (difference.execute(polygon, convex_hull, SpatialReference.create(4326), null)); + assertTrue(differenced.isEmpty()); + assertTrue(bounding.isConvex(convex_hull, null)); + } + + { + Polygon polygon = (Polygon) (TestCommonMethods.fromJson("{\"rings\":[[[27.570109889215438,22.850616190228489],[75.703105729477357,-90],[2.1548000876241362,-15.817792950796967],[27.570109889215438,22.850616190228489]],[[-0.069915984436478951,-90],[-46.602410662754053,-89.999999998014729],[-14.977190481820156,-41.883452819243004],[-0.069915984436478951,-90]],[[-14.977190481820156,-41.883452819243004],[-34.509989609682322,21.163004866431177],[2.1548000876241362,-15.817792950796967],[-14.977190481820156,-41.883452819243004]]]}").getGeometry()); + Polygon convex_hull = (Polygon) (bounding.execute(polygon, null)); + Polygon differenced = (Polygon) (difference.execute(polygon, convex_hull, SpatialReference.create(4326), null)); + assertTrue(differenced.isEmpty()); + assertTrue(bounding.isConvex(convex_hull, null)); + } + + { + Polygon polygon = (Polygon) (TestCommonMethods.fromJson("{\"rings\":[[[28.865673900286581,33.379551302126075],[39.505669183485338,-34.957993133630559],[7.152466542048213,-90],[28.865673900286581,33.379551302126075]],[[-64.597291313620858,2.4515644574812248],[20.050002923927103,90],[24.375150856531356,62.220853377417541],[-64.597291313620858,2.4515644574812248]],[[28.865673900286581,33.379551302126075],[24.375150856531356,62.220853377417541],[35.223952527956932,69.508785974507163],[28.865673900286581,33.379551302126075]]]}").getGeometry()); + Polygon convex_hull = (Polygon) (bounding.execute(polygon, null)); + Polygon differenced = (Polygon) (difference.execute(polygon, convex_hull, SpatialReference.create(4326), null)); + assertTrue(differenced.isEmpty()); + assertTrue(bounding.isConvex(convex_hull, null)); + } + + { + Polygon polygon = (Polygon) (TestCommonMethods.fromJson("{\"rings\":[[[-66.582505384413167,-51.305212522944061],[-60.169897093348865,-90],[-90,-90],[-66.582505384413167,-51.305212522944061]],[[20.858462934004656,-90],[-35.056287147954386,0.78833269359179781],[18.933251883215579,90],[20.858462934004656,-90]],[[-66.582505384413167,-51.305212522944061],[-90,90],[-35.056287147954386,0.78833269359179781],[-66.582505384413167,-51.305212522944061]]]}").getGeometry()); + Polygon convex_hull = (Polygon) (bounding.execute(polygon, null)); + Polygon differenced = (Polygon) (difference.execute(polygon, convex_hull, SpatialReference.create(4326), null)); + assertTrue(differenced.isEmpty()); + assertTrue(bounding.isConvex(convex_hull, null)); + } + + { + Polygon polygon = (Polygon) (TestCommonMethods.fromJson("{\"rings\":[[[36.692916710279974,-90],[-31.182443792600079,6.434474852744998],[-90,90],[52.245260790065387,57.329280208760991],[36.692916710279974,-90]]]}").getGeometry()); + Polygon convex_hull = (Polygon) (bounding.execute(polygon, null)); + Polygon differenced = (Polygon) (difference.execute(polygon, convex_hull, SpatialReference.create(4326), null)); + assertTrue(differenced.isEmpty()); + assertTrue(bounding.isConvex(convex_hull, null)); + } + + { + Polygon polygon = (Polygon) (TestCommonMethods.fromJson("{\"rings\":[[[-17.959089916602533,-4.3577640799248218],[-29.181784251472308,-90],[-65.493717350127127,15.053615507086979],[-17.959089916602533,-4.3577640799248218]],[[-21.884657435973146,-34.517617672142393],[-17.94005076020704,-4.3655389655558539],[9.3768748358343359,-15.520758655380195],[-21.884657435973146,-34.517617672142393]],[[-17.94005076020704,-4.3655389655558539],[-17.959089916602533,-4.3577640799248218],[-5.8963967801936494,87.694641571893939],[-17.94005076020704,-4.3655389655558539]]]}").getGeometry()); + Polygon convex_hull = (Polygon) (bounding.execute(polygon, null)); + Polygon differenced = (Polygon) (difference.execute(polygon, convex_hull, SpatialReference.create(4326), null)); + assertTrue(differenced.isEmpty()); + assertTrue(bounding.isConvex(convex_hull, null)); + } + + { + Polygon polygon = (Polygon) (TestCommonMethods.fromJson("{\"rings\":[[[17.198360589495572,-77.168667157542373],[-24.835678541343281,-83.717338556412017],[-30.259244993378722,61.914816728303791],[17.198360589495572,-77.168667157542373]],[[-8.3544985146710644,-90],[17.979891823366039,-79.459092168186686],[21.576625471325329,-90],[-8.3544985146710644,-90]],[[17.979891823366039,-79.459092168186686],[17.198360589495572,-77.168667157542373],[27.846596597209441,-75.509730732825361],[17.979891823366039,-79.459092168186686]]]}").getGeometry()); + Polygon convex_hull = (Polygon) (bounding.execute(polygon, null)); + Polygon differenced = (Polygon) (difference.execute(polygon, convex_hull, SpatialReference.create(4326), null)); + assertTrue(differenced.isEmpty()); + assertTrue(bounding.isConvex(convex_hull, null)); + } + + { + Polygon polygon = (Polygon) (TestCommonMethods.fromJson("{\"rings\":[[[-1.2588613620456419,13.607321860624268],[61.845346679259052,48.415944386573557],[15.226225965240992,-5.3702891526017318],[0.92681706095183469,1.6819284384951441],[3.8469417404317623,-14.250715301799051],[7.2615297628459139,-14.559458820527061],[4.4896578086498238,-17.757471781424698],[14.589138845678622,-72.861774161244625],[-10.508572009494033,-35.06149380752737],[-58.12642296329372,-90],[-15.260062192400673,90],[-1.2588613620456419,13.607321860624268]],[[0.92681706095183469,1.6819284384951441],[-1.2588613620456419,13.607321860624268],[-11.641308877525201,7.8803076458946304],[0.92681706095183469,1.6819284384951441]],[[-10.508572009494033,-35.06149380752737],[4.4896578086498238,-17.757471781424698],[3.8469417404317623,-14.250715301799051],[-26.125369947914503,-11.54064986657559],[-10.508572009494033,-35.06149380752737]],[[39.829571435268129,-17.504227477249202],[7.2615297628459139,-14.559458820527061],[15.226225965240992,-5.3702891526017318],[39.829571435268129,-17.504227477249202]]]}").getGeometry()); + Polygon convex_hull = (Polygon) (bounding.execute(polygon, null)); + Polygon differenced = (Polygon) (difference.execute(polygon, convex_hull, SpatialReference.create(4326), null)); + assertTrue(differenced.isEmpty()); + assertTrue(bounding.isConvex(convex_hull, null)); + } + + { + Polygon polygon = (Polygon) (TestCommonMethods.fromJson("{\"rings\":[[[-19.681975166855118,-34.10344217707847],[-90,89.999999998418275],[53.036316534501381,90],[-19.681975166855118,-34.10344217707847]],[[-52.434509065706855,-90],[-29.2339442498794,-50.405148598356135],[-2.8515119199232331,-90],[-52.434509065706855,-90]],[[18.310881874573923,-90],[-25.473718245381271,-43.987822508814972],[-19.681975166855118,-34.10344217707847],[-15.406194071963924,-41.649717163101563],[18.310881874573923,-90]],[[-29.2339442498794,-50.405148598356135],[-52.27954259799813,-15.81822990020261],[-25.473718245381271,-43.987822508814972],[-29.2339442498794,-50.405148598356135]]]}").getGeometry()); + Polygon convex_hull = (Polygon) (bounding.execute(polygon, null)); + Polygon differenced = (Polygon) (difference.execute(polygon, convex_hull, SpatialReference.create(4326), null)); + assertTrue(differenced.isEmpty()); + assertTrue(bounding.isConvex(convex_hull, null)); + } + + { + Polygon polygon = (Polygon) (TestCommonMethods.fromJson("{\"rings\":[[[49.939516827702498,-90],[-20.470128740962011,-68.102019032647391],[-20.124197553433845,-67.213968219799824],[34.438329237618149,-61.893901496061034],[49.939516827702498,-90]],[[-82.380918375714089,-73.284249936115529],[-4.7432060543229699,9.1484031048644194],[-11.790524932251525,21.926303986370414],[-3.4862200343039369,10.483021157965428],[19.753975453441285,35.158541777575607],[5.5896897290794696,-1.2030408273476854],[73.839023528563189,-58.052174675157325],[34.438329237618149,-61.893901496061034],[3.6757233436274213,-6.1164440290327313],[-20.124197553433845,-67.213968219799824],[-82.380918375714089,-73.284249936115529]],[[5.5896897290794696,-1.2030408273476854],[4.0842948437219349,0.050896618883412792],[-3.4862200343039369,10.483021157965428],[-4.7432060543229699,9.1484031048644194],[3.6757233436274213,-6.1164440290327313],[5.5896897290794696,-1.2030408273476854]]]}").getGeometry()); + Polygon convex_hull = (Polygon) (bounding.execute(polygon, null)); + Polygon differenced = (Polygon) (difference.execute(polygon, convex_hull, SpatialReference.create(4326), null)); + assertTrue(differenced.isEmpty()); + assertTrue(bounding.isConvex(convex_hull, null)); + } + + { + Polygon polygon = (Polygon) (TestCommonMethods.fromJson("{\"rings\":[[[4.7618727814345867,-14.245890151885444],[-7.1675180359486266,-90],[-83.232840716292529,40.620187389409224],[-29.219286930421923,6.9418934044755334],[-29.378277853968513,6.9629531745072839],[-28.933835455648254,6.7639099538036529],[4.7618727814345867,-14.245890151885444]],[[51.056303527367277,-43.111190419066219],[4.7618727814345867,-14.245890151885444],[5.632592229367642,-8.716640778187827],[-28.933835455648254,6.7639099538036529],[-29.219286930421923,6.9418934044755334],[2.700964609629902,2.7137705544807242],[12.385960896403816,0.48342578457646468],[51.056303527367277,-43.111190419066219]]]}").getGeometry()); + Polygon convex_hull = (Polygon) (bounding.execute(polygon, null)); + Polygon differenced = (Polygon) (difference.execute(polygon, convex_hull, SpatialReference.create(4326), null)); + assertTrue(differenced.isEmpty()); + assertTrue(bounding.isConvex(convex_hull, null)); + } + + { + Polygon polygon = (Polygon) (TestCommonMethods.fromJson("{\"rings\":[[[-21.426619830983732,-89.379667629404466],[-72.784461583687971,-88.999754827814016],[-81.94289434769162,25.456737039611831],[-38.382426191605546,-57.204127144336077],[-41.663734179022256,-78.439084394036513],[-29.749353943865881,-73.586348060869426],[-21.426619830983732,-89.379667629404466]],[[-21.09971823441461,-90],[-21.426619830983732,-89.379667629404466],[-21.080965849893449,-89.382224558742934],[-21.09971823441461,-90]],[[62.431917153693021,-90],[-21.080965849893449,-89.382224558742934],[-20.486971473666468,-69.813772479288062],[19.166418765782844,-53.662915804391695],[63.671046682728601,-90],[62.431917153693021,-90]],[[-29.749353943865881,-73.586348060869426],[-38.382426191605546,-57.204127144336077],[-31.449272112025476,-12.336278393150847],[-41.028899505665962,-4.5147159296945967],[-30.750049689226596,-7.8112663207986941],[-15.63587330244308,90],[-18.721998818789388,-11.66880646480822],[60.158611185675326,-36.966763960486929],[19.166418765782844,-53.662915804391695],[-19.049573405176112,-22.46036923493498],[-20.486971473666468,-69.813772479288062],[-29.749353943865881,-73.586348060869426]],[[-19.049573405176112,-22.46036923493498],[-18.721998818789388,-11.66880646480822],[-30.750049689226596,-7.8112663207986941],[-31.449272112025476,-12.336278393150847],[-19.049573405176112,-22.46036923493498]]]}").getGeometry()); + Polygon convex_hull = (Polygon) (bounding.execute(polygon, null)); + Polygon differenced = (Polygon) (difference.execute(polygon, convex_hull, SpatialReference.create(4326), null)); + assertTrue(differenced.isEmpty()); + assertTrue(bounding.isConvex(convex_hull, null)); + } + + { + Polygon polygon = (Polygon) (TestCommonMethods.fromJson("{\"rings\":[[[-17.906614911617162,-53.670186894017093],[-72.687715727164573,-90],[-77.889582483879749,90],[-47.149885004784061,16.372797801863811],[-58.874489264131405,8.3403055152440846],[-44.017112148517498,8.8692333739436133],[-43.760297522359615,8.2541153357643502],[-48.398890069305921,4.7201397602360009],[-38.665987052649818,-3.9476907252248874],[-17.906614911617162,-53.670186894017093]],[[-2.7387498969355368,-90],[-17.906614911617162,-53.670186894017093],[-6.8038688963847829,-46.30705103709559],[-2.7387498969355368,-90]],[[-6.8038688963847829,-46.30705103709559],[-8.2224486207861638,-31.0597897622158],[2.1962303277340673,-40.338351652092697],[-6.8038688963847829,-46.30705103709559]],[[-8.2224486207861638,-31.0597897622158],[-38.665987052649818,-3.9476907252248874],[-43.760297522359615,8.2541153357643502],[-42.90074612601282,8.9089763975751382],[-44.017112148517498,8.8692333739436133],[-47.149885004784061,16.372797801863811],[45.190674429223662,79.635046572817728],[40.490070954305672,72.441418146356597],[63.53694979672099,90],[75.056911135062407,13.108310545642606],[-0.027204347469059975,10.435289586728711],[-10.580480746811602,-5.715051428780245],[-8.2224486207861638,-31.0597897622158]],[[-42.90074612601282,8.9089763975751382],[-0.027204347469059975,10.435289586728711],[40.490070954305672,72.441418146356597],[-42.90074612601282,8.9089763975751382]]]}").getGeometry()); + Polygon convex_hull = (Polygon) (bounding.execute(polygon, null)); + Polygon differenced = (Polygon) (difference.execute(polygon, convex_hull, SpatialReference.create(4326), null)); + assertTrue(differenced.isEmpty()); + assertTrue(bounding.isConvex(convex_hull, null)); + } + } + + @Test + public static void testPolylines() { + OperatorConvexHull bounding = (OperatorConvexHull) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.ConvexHull); + OperatorDensifyByLength densify = (OperatorDensifyByLength) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.DensifyByLength); + OperatorDifference difference = (OperatorDifference) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.Difference); + OperatorContains contains = (OperatorContains) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.Contains); + + { + Polyline poly = new Polyline(); + poly.startPath(0, 0); + poly.lineTo(0, 10); + poly.lineTo(0, 20); + poly.lineTo(0, 40); + poly.lineTo(0, 500); + + Polyline densified = (Polyline) (densify.execute(poly, 10.0, null)); + Polyline convex_hull = (Polyline) (bounding.execute(densified, null)); + Polyline differenced = (Polyline) (difference.execute(densified, convex_hull, SpatialReference.create(4326), null)); + assertTrue(differenced.isEmpty()); + assertTrue(bounding.isConvex(convex_hull, null)); + } + + { + Polyline polyline = new Polyline(); + polyline.startPath(-200, -90); + polyline.lineTo(-180, -85); + polyline.lineTo(-90, -70); + polyline.lineTo(0, 0); + polyline.lineTo(100, 25); + polyline.lineTo(170, 45); + polyline.lineTo(225, 65); + + Polyline densified = (Polyline) (densify.execute(polyline, 10.0, null)); + Polygon convex_hull = (Polygon) (bounding.execute(densified, null)); + boolean bcontains = contains.execute(convex_hull, densified, SpatialReference.create(4326), null); + + assertTrue(bcontains); + assertTrue(bounding.isConvex(convex_hull, null)); + } + } + + @Test + public static void testNonSimpleShape() { + OperatorConvexHull bounding = (OperatorConvexHull) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.ConvexHull); + OperatorDensifyByLength densify = (OperatorDensifyByLength) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.DensifyByLength); + OperatorDifference difference = (OperatorDifference) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.Difference); + OperatorContains contains = (OperatorContains) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.Contains); + + { + Polygon shape = (Polygon) (TestCommonMethods.fromJson("{\"rings\":[[[6.7260599916745036,-90],[15.304037095218971,-18.924146439950675],[6.3163297788539232,-90],[5.2105387036445823,-59.980719950158637],[5.1217504663506981,-60.571174400735508],[8.2945138368731044,-27.967042187979146],[15.76606600357545,28.594953216378414],[8.4365340991447919,66.880924521951329],[10.115022726199774,65.247385313781265],[12.721206966604395,-23.793016208456883],[12.051875868947576,-11.368909618476637],[11.867118776841318,44.968896646914239],[7.5326099218274614,35.095776924526533],[8.86765609460479,90],[3.2036592678446922,55.507964789691712],[0.23585282258761486,-42.620591380394039],[-1.2660432762142744,90],[5.5580612840503001,-9.4879902323389196],[12.258387597532487,-35.945231749575591],[-48.746716054894101,90],[7.2294405148356846,-15.719232058488402],[13.798313011339591,-10.467172541381753],[7.4430022048746718,6.3951685161785656],[6.4876332898327815,31.10016146737189],[9.3645424359058911,47.123308099298804],[13.398605254542668,-6.4398318586014325],[-90,90],[13.360786277212718,82.971274676174545],[7.9405631778693566,90],[10.512482079680538,90],[16.994982794293946,19.60673041736408],[16.723893839323615,22.728853852102926],[23.178783416627525,90],[6.7260599916745036,-90]],[[26.768777234301993,90],[20.949797955126346,90],[11.967758262201434,-0.45048849056049711],[17.535751576687339,52.767528591651441],[26.768777234301993,90]],[[18.677765775891793,12.559680067559942],[19.060218406331451,90],[17.123595624401705,90],[-2.3805299720687887,-90],[-11.882782057881979,-90],[21.640575461689693,90],[11.368255808198477,85.501555553904794],[17.390084032215348,90],[23.999392897519989,78.255909006554603],[-6.8860811786563101,69.49189433189926],[29.232578855788898,90],[25.951412073846683,90],[-5.5572284181160772,-16.763772082849457],[18.677765775891793,12.559680067559942]]]}").getGeometry()); + Polygon densified = (Polygon) (densify.execute(shape, 10.0, null)); + Polygon convex_hull = (Polygon) (bounding.execute(densified, null)); + Polygon differenced = (Polygon) (difference.execute(densified, convex_hull, SpatialReference.create(4326), null)); + assertTrue(differenced.isEmpty()); + assertTrue(bounding.isConvex(convex_hull, null)); + } + + { + Polygon shape = (Polygon) (TestCommonMethods.fromJson("{\"rings\":[[[-13.630596027421603,3.6796011190640709],[-10.617275202716886,-28.133054337834409],[-81.617315194491695,90],[-4.0763362539824293,-90],[2.8213537979804073,-90],[-5.1515857979973365,-11.605767592612787],[43.477754021411123,35.507543731267589],[-45.818261267516704,-90],[-4.8545715514870018,-64.204371906322223],[-1.744951154293144,-30.257848194381509],[-7.8524748309267149,15.513561279453089],[-10.657563385538953,-81.785061432086309],[-6.3487369893289411,-31.849779201388415],[-14.768278524737962,-12.004393281111058],[-27.001635582579841,90],[-14.967554248940855,-78.970629918591811],[-12.999635147475825,-38.584472796107939],[-13.630596027421603,3.6796011190640709]],[[-16.338143621861352,-37.415690513288375],[-21.553879270366266,-90],[-18.649338100909404,-90],[-24.880584966233631,1.3133858590648728],[-16.483464632078249,-53.979692212288882],[-24.836979215403964,-68.69859399640147],[-29.708282990385214,-90],[-27.469962102507036,-1.6392995673644872],[-20.405051753708271,61.943199597870034],[-18.242567838912599,24.405109362934219],[-66.334547696572528,-52.678390155566603],[-13.471083255903507,-33.782708412943229],[-7.092757068096085,33.673785662500464],[-2.7427100969018205,74.386868339212668],[-8.2174861339989675,90],[-15.699459164009667,90],[-9.5910045204059156,90],[-8.4504603287557369,90],[-1.5498862802092637,2.5144190340747681],[-6.5326327868410639,-17.428029961128306],[-10.947786354404593,31.516236387466538],[-7.4777936485986354,12.486727826508769],[-13.89052186883092,12.397126427870356],[-10.530672679779606,-55.463541447339118],[-8.7161833631330374,-90],[-4.7231067612639519,-90],[-3.9692500849117041,-32.204677519048822],[3.740804266163555,32.88191805391007],[6.2021313886056246,76.617541950091564],[6.1183997672398194,90],[0.59730820015390673,90],[7.3242950674530753,18.030401540676614],[1.8252371571535342,90],[-16.338143621861352,-37.415690513288375]]]}").getGeometry()); + Polygon densified = (Polygon) (densify.execute(shape, 10.0, null)); + Polygon convex_hull = (Polygon) (bounding.execute(densified, null)); + Polygon differenced = (Polygon) (difference.execute(densified, convex_hull, SpatialReference.create(4326), null)); + assertTrue(differenced.isEmpty()); + assertTrue(bounding.isConvex(convex_hull, null)); + } + + { + Polygon shape = (Polygon) (TestCommonMethods.fromJson("{\"rings\":[[[-11.752662474672046,-90],[-76.236530072126513,7.3247326417920817],[18.933251883215579,90],[51.538924439116798,90],[52.253017336758049,80.352482145105284],[41.767201918260639,51.890297432229289],[21.697252770910882,-1.3185641048567049],[45.112193442818935,60.758441021743636],[48.457184967377231,69.626584611257954],[49.531808284502759,70.202152706968036],[52.394797054144334,71.533541126234581],[ 52.9671102343993,70.704964290210626],[58.527850348069251,16.670036266565845],[62.310807912773328,-34.249918700039238],[62.775020703241523,-43.541598916699364],[64.631871865114277,-80.708319783339874],[65.096084655582459,-90],[-11.752662474672046,-90]]]}").getGeometry()); + Polygon convex_hull = (Polygon) (bounding.execute(shape, null)); + Polygon differenced = (Polygon) (difference.execute(shape, convex_hull, SpatialReference.create(4326), null)); + assertTrue(differenced.isEmpty()); + assertTrue(bounding.isConvex(convex_hull, null)); + } + + { + Polygon polygon = new Polygon(); + polygon.startPath(0, 0); + polygon.lineTo(0, 10); + polygon.lineTo(4, 10); + polygon.lineTo(9, 1); + polygon.lineTo(1, 1); + polygon.lineTo(5, 10); + polygon.lineTo(10, 10); + polygon.lineTo(10, 0); + + Polygon densified = (Polygon) (densify.execute(polygon, 1, null)); + Polygon convex_hull = (Polygon) (bounding.execute(densified, null)); + + double area = convex_hull.calculateArea2D(); + assertTrue(bounding.isConvex(convex_hull, null)); + assertTrue(area == 100.0); + + Point2D p1 = convex_hull.getXY(0); + Point2D p2 = convex_hull.getXY(1); + Point2D p3 = convex_hull.getXY(2); + Point2D p4 = convex_hull.getXY(3); + assertTrue(p1.x == 0.0 && p1.y == 0.0); + assertTrue(p2.x == 0.0 && p2.y == 10.0); + assertTrue(p3.x == 10.0 && p3.y == 10.0); + assertTrue(p4.x == 10.0 && p4.y == 0.0); + } + + { + Polygon polygon = new Polygon(); + polygon.startPath(0, 0); + polygon.lineTo(0, 10); + polygon.lineTo(8, 10); + polygon.lineTo(10, 8); + polygon.lineTo(10, 0); + polygon.lineTo(0, 0); + polygon.lineTo(0, 10); + polygon.lineTo(10, 10); + polygon.lineTo(10, 0); + + // Polygon densified = (Polygon)(densify.execute(polygon, 1, null)); + Polygon convex_hull = (Polygon) (bounding.execute(polygon, null)); + + double area = convex_hull.calculateArea2D(); + assertTrue(bounding.isConvex(convex_hull, null)); + assertTrue(area == 100.0); + + Point2D p1 = convex_hull.getXY(0); + Point2D p2 = convex_hull.getXY(1); + Point2D p3 = convex_hull.getXY(2); + Point2D p4 = convex_hull.getXY(3); + assertTrue(p1.x == 0.0 && p1.y == 0.0); + assertTrue(p2.x == 0.0 && p2.y == 10.0); + assertTrue(p3.x == 10.0 && p3.y == 10.0); + assertTrue(p4.x == 10.0 && p4.y == 0.0); + } + } + + @Test + public static void testStar() { + OperatorConvexHull bounding = (OperatorConvexHull) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.ConvexHull); + OperatorDensifyByLength densify = (OperatorDensifyByLength) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.DensifyByLength); + + Polygon star = new Polygon(); + star.startPath(0, 0); + star.lineTo(0, 0); + star.lineTo(5, 10); + star.lineTo(5, 10); + star.lineTo(10, 0); + star.lineTo(10, 0); + star.startPath(0, 5); + star.lineTo(0, 5); + star.lineTo(10, 5); + star.lineTo(10, 5); + star.lineTo(5, -5); + star.lineTo(5, -5); + + star.reversePath(1); + + Polygon densified = (Polygon) (densify.execute(star, 1.0, null)); + Polygon convex_hull = (Polygon) (bounding.execute(densified, null)); + + assertTrue(bounding.isConvex(convex_hull, null)); + + Point2D p1 = convex_hull.getXY(0); + Point2D p2 = convex_hull.getXY(1); + Point2D p3 = convex_hull.getXY(2); + Point2D p4 = convex_hull.getXY(3); + Point2D p5 = convex_hull.getXY(4); + Point2D p6 = convex_hull.getXY(5); + assertTrue(p1.x == 0.0 && p1.y == 0.0); + assertTrue(p2.x == 0.0 && p2.y == 5.0); + assertTrue(p3.x == 5.0 && p3.y == 10.0); + assertTrue(p4.x == 10.0 && p4.y == 5.0); + assertTrue(p5.x == 10.0 && p5.y == 0.0); + assertTrue(p6.x == 5.0 && p6.y == -5.0); + } + + @Test + public static void testPointsArray() { + Point2D[] points = new Point2D[6]; + int[] convex_hull = new int[6]; + + for (int i = 0; i < 6; i++) + points[i] = new Point2D(); + + points[0].x = 0; + points[0].y = 0; + points[1].x = 5; + points[1].y = 10; + points[2].x = 10; + points[2].y = 0; + points[3].x = 0; + points[3].y = 5; + points[4].x = 10; + points[4].y = 5; + points[5].x = 5; + points[5].y = -5; + + ConvexHull.construct(points, 6, convex_hull); + assertTrue(convex_hull[0] == 0); + assertTrue(convex_hull[1] == 3); + assertTrue(convex_hull[2] == 1); + assertTrue(convex_hull[3] == 4); + assertTrue(convex_hull[4] == 2); + assertTrue(convex_hull[5] == 5); + } + + @Test + public static void testMergeCursor() { + OperatorConvexHull bounding = (OperatorConvexHull) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.ConvexHull); + + Polygon geom1 = new Polygon(); + Polygon geom2 = new Polygon(); + Point geom3 = new Point(); + Line geom4 = new Line(); + Envelope geom5 = new Envelope(); + MultiPoint geom6 = new MultiPoint(); + + // polygon + geom1.startPath(0, 0); + geom1.lineTo(0, 0); + geom1.lineTo(5, 11); + geom1.lineTo(5, 11); + geom1.lineTo(10, 0); + geom1.lineTo(10, 0); + + // polygon + geom2.startPath(0, 5); + geom2.lineTo(0, 5); + geom2.lineTo(10, 5); + geom2.lineTo(10, 5); + geom2.lineTo(5, -5); + geom2.lineTo(5, -5); + + // point + geom3.setXY(15, 1.25); + + // segment + geom4.setEndXY(-5, 1.25); + geom4.setStartXY(0, 0); + + // envelope + geom5.setCoords(0, 0, 5, 10); + + // multi_point + geom6.add(10, 5); + geom6.add(10, 10); + + // create cursor + Geometry[] geoms = new Geometry[6]; + geoms[0] = geom1; + geoms[1] = geom2; + geoms[2] = geom3; + geoms[3] = geom4; + geoms[4] = geom5; + geoms[5] = geom6; + GeometryCursor cursor = new SimpleGeometryCursor(geoms); + + // create convex hull from the cursor with b_merge set to true + GeometryCursor convex_hull_curs = bounding.execute(cursor, true, null); + Polygon convex_hull = (Polygon) (convex_hull_curs.next()); + assertTrue(convex_hull_curs.next() == null); + + assertTrue(bounding.isConvex(convex_hull, null)); + + Point2D p1 = convex_hull.getXY(0); + Point2D p2 = convex_hull.getXY(1); + Point2D p3 = convex_hull.getXY(2); + Point2D p4 = convex_hull.getXY(3); + Point2D p5 = convex_hull.getXY(4); + Point2D p6 = convex_hull.getXY(5); + assertTrue(p1.x == 5.0 && p1.y == 11.0); + assertTrue(p2.x == 10.0 && p2.y == 10); + assertTrue(p3.x == 15.0 && p3.y == 1.25); + assertTrue(p4.x == 5.0 && p4.y == -5.0); + assertTrue(p5.x == -5.0 && p5.y == 1.25); + assertTrue(p6.x == 0.0 && p6.y == 10.0); + + // Test GeometryEngine + Geometry[] merged_hull = GeometryEngine.convexHull(geoms, true); + convex_hull = (Polygon) merged_hull[0]; + p1 = convex_hull.getXY(0); + p2 = convex_hull.getXY(1); + p3 = convex_hull.getXY(2); + p4 = convex_hull.getXY(3); + p5 = convex_hull.getXY(4); + p6 = convex_hull.getXY(5); + assertTrue(p1.x == 5.0 && p1.y == 11.0); + assertTrue(p2.x == 10.0 && p2.y == 10); + assertTrue(p3.x == 15.0 && p3.y == 1.25); + assertTrue(p4.x == 5.0 && p4.y == -5.0); + assertTrue(p5.x == -5.0 && p5.y == 1.25); + assertTrue(p6.x == 0.0 && p6.y == 10.0); + + } + + @Test + public void testHullTickTock() { + Polygon geom1 = new Polygon(); + Polygon geom2 = new Polygon(); + Point geom3 = new Point(); + Line geom4 = new Line(); + Envelope geom5 = new Envelope(); + MultiPoint geom6 = new MultiPoint(); + + // polygon + geom1.startPath(0, 0); + geom1.lineTo(0, 0); + geom1.lineTo(5, 11); + geom1.lineTo(5, 11); + geom1.lineTo(10, 0); + geom1.lineTo(10, 0); + + // polygon + geom2.startPath(0, 5); + geom2.lineTo(0, 5); + geom2.lineTo(10, 5); + geom2.lineTo(10, 5); + geom2.lineTo(5, -5); + geom2.lineTo(5, -5); + + // point + geom3.setXY(15, 1.25); + + // segment + geom4.setEndXY(-5, 1.25); + geom4.setStartXY(0, 0); + + // envelope + geom5.setCoords(0, 0, 5, 10); + + // multi_point + geom6.add(10, 5); + geom6.add(10, 10); + + // Create + ListeningGeometryCursor gc = new ListeningGeometryCursor(); + GeometryCursor ticktock = OperatorConvexHull.local().execute(gc, true, null); + + // Use tick-tock to push a geometry and do a piece of work. + gc.tick(geom1); + ticktock.tock(); + gc.tick(geom2); + ticktock.tock(); + gc.tick(geom3);// skiped one tock just for testing. + ticktock.tock(); + gc.tick(geom4); + ticktock.tock(); + gc.tick(geom5); + ticktock.tock(); + gc.tick(geom6); + ticktock.tock(); + // Get the result + Geometry result = ticktock.next(); + Polygon convex_hull = (Polygon) result; + assertTrue(OperatorConvexHull.local().isConvex(convex_hull, null)); + + Point2D p1 = convex_hull.getXY(0); + Point2D p2 = convex_hull.getXY(1); + Point2D p3 = convex_hull.getXY(2); + Point2D p4 = convex_hull.getXY(3); + Point2D p5 = convex_hull.getXY(4); + Point2D p6 = convex_hull.getXY(5); + assertTrue(p1.x == 5.0 && p1.y == 11.0); + assertTrue(p2.x == 10.0 && p2.y == 10); + assertTrue(p3.x == 15.0 && p3.y == 1.25); + assertTrue(p4.x == 5.0 && p4.y == -5.0); + assertTrue(p5.x == -5.0 && p5.y == 1.25); + assertTrue(p6.x == 0.0 && p6.y == 10.0); + } } diff --git a/src/test/java/com/esri/core/geometry/TestCut.java b/src/test/java/com/esri/core/geometry/TestCut.java index e6c1fbb3..799fd36d 100644 --- a/src/test/java/com/esri/core/geometry/TestCut.java +++ b/src/test/java/com/esri/core/geometry/TestCut.java @@ -30,555 +30,555 @@ import java.util.ArrayDeque; public class TestCut extends TestCase { - @Override - protected void setUp() throws Exception { - super.setUp(); - } - - @Override - protected void tearDown() throws Exception { - super.tearDown(); - } - - @Test - public static void testCut4326() { - SpatialReference sr = SpatialReference.create(4326); - testConsiderTouch1(sr); - testConsiderTouch2(sr); - testPolygon5(sr); - testPolygon7(sr); - testPolygon8(sr); - testPolygon9(sr); - testEngine(sr); - - } - - public static void testConsiderTouch1(SpatialReference spatialReference) { - OperatorFactoryLocal engine = OperatorFactoryLocal.getInstance(); - OperatorCut opCut = (OperatorCut) engine.getOperator(Operator.Type.Cut); - - Polyline polyline1 = makePolyline1(); - Polyline polyline11 = makePolyline1(); - Polyline cutter1 = makePolylineCutter1(); - - ArrayDeque polylines = new ArrayDeque<>(); - polylines.push(polyline1); - polylines.push(polyline11); - - SimpleGeometryCursor simpleGeometryCursor = new SimpleGeometryCursor(polylines); - GeometryCursor cursor = opCut.execute(true, simpleGeometryCursor, cutter1, - spatialReference, null); - Polyline cut; - int pathCount; - int segmentCount; - double length; - - cut = (Polyline) cursor.next(); - pathCount = cut.getPathCount(); - segmentCount = cut.getSegmentCount(); - length = cut.calculateLength2D(); - assertTrue(pathCount == 4); - assertTrue(segmentCount == 4); - assertTrue(length == 6); - - cut = (Polyline) cursor.next(); - pathCount = cut.getPathCount(); - segmentCount = cut.getSegmentCount(); - length = cut.calculateLength2D(); - assertTrue(pathCount == 6); - assertTrue(segmentCount == 8); - assertTrue(length == 12); - - cut = (Polyline) cursor.next(); - pathCount = cut.getPathCount(); - segmentCount = cut.getSegmentCount(); - length = cut.calculateLength2D(); - assertTrue(pathCount == 1); - assertTrue(segmentCount == 1); - assertTrue(length == 1); - - cut = (Polyline) cursor.next(); - pathCount = cut.getPathCount(); - segmentCount = cut.getSegmentCount(); - length = cut.calculateLength2D(); - assertTrue(pathCount == 1); - assertTrue(segmentCount == 1); - assertTrue(length == 1); - - cut = (Polyline) cursor.next(); - pathCount = cut.getPathCount(); - segmentCount = cut.getSegmentCount(); - length = cut.calculateLength2D(); - assertTrue(pathCount == 4); - assertTrue(segmentCount == 4); - assertTrue(length == 6); - - cut = (Polyline) cursor.next(); - pathCount = cut.getPathCount(); - segmentCount = cut.getSegmentCount(); - length = cut.calculateLength2D(); - assertTrue(pathCount == 6); - assertTrue(segmentCount == 8); - assertTrue(length == 12); - - cut = (Polyline) cursor.next(); - pathCount = cut.getPathCount(); - segmentCount = cut.getSegmentCount(); - length = cut.calculateLength2D(); - assertTrue(pathCount == 1); - assertTrue(segmentCount == 1); - assertTrue(length == 1); - - cut = (Polyline) cursor.next(); - pathCount = cut.getPathCount(); - segmentCount = cut.getSegmentCount(); - length = cut.calculateLength2D(); - assertTrue(pathCount == 1); - assertTrue(segmentCount == 1); - assertTrue(length == 1); - - cut = (Polyline) cursor.next(); - assertTrue(cut == null); - } - - public static void testConsiderTouch2(SpatialReference spatialReference) { - OperatorFactoryLocal engine = OperatorFactoryLocal.getInstance(); - OperatorCut opCut = (OperatorCut) engine.getOperator(Operator.Type.Cut); - - Polyline polyline2 = makePolyline2(); - Polyline cutter2 = makePolylineCutter2(); - - GeometryCursor cursor = opCut.execute(true, polyline2, cutter2, - spatialReference, null); - Polyline cut; - int pathCount; - int segmentCount; - double length; - - cut = (Polyline) cursor.next(); - pathCount = cut.getPathCount(); - segmentCount = cut.getSegmentCount(); - length = cut.calculateLength2D(); - assertTrue(pathCount == 4); - assertTrue(segmentCount == 4); - assertTrue(Math.abs(length - 5.74264068) <= 0.001); - - cut = (Polyline) cursor.next(); - pathCount = cut.getPathCount(); - segmentCount = cut.getSegmentCount(); - length = cut.calculateLength2D(); - assertTrue(pathCount == 6); - assertTrue(segmentCount == 8); - assertTrue(length == 6.75); - - cut = (Polyline) cursor.next(); - pathCount = cut.getPathCount(); - segmentCount = cut.getSegmentCount(); - length = cut.calculateLength2D(); - assertTrue(pathCount == 1); - assertTrue(segmentCount == 1); - assertTrue(Math.abs(length - 0.5) <= 0.001); - - cut = (Polyline) cursor.next(); - pathCount = cut.getPathCount(); - segmentCount = cut.getSegmentCount(); - length = cut.calculateLength2D(); - assertTrue(pathCount == 1); - assertTrue(segmentCount == 1); - assertTrue(Math.abs(length - 0.25) <= 0.001); - - cut = (Polyline) cursor.next(); - pathCount = cut.getPathCount(); - segmentCount = cut.getSegmentCount(); - length = cut.calculateLength2D(); - assertTrue(pathCount == 1); - assertTrue(segmentCount == 1); - assertTrue(Math.abs(length - 1) <= 0.001); - - cut = (Polyline) cursor.next(); - pathCount = cut.getPathCount(); - segmentCount = cut.getSegmentCount(); - length = cut.calculateLength2D(); - assertTrue(pathCount == 1); - assertTrue(segmentCount == 1); - assertTrue(Math.abs(length - 1.41421356) <= 0.001); - - cut = (Polyline) cursor.next(); - assertTrue(cut == null); - } - - public static void testPolygon5(SpatialReference spatialReference) { - OperatorFactoryLocal engine = OperatorFactoryLocal.getInstance(); - OperatorCut opCut = (OperatorCut) engine.getOperator(Operator.Type.Cut); - - Polygon polygon5 = makePolygon5(); - Polyline cutter5 = makePolygonCutter5(); - - GeometryCursor cursor = opCut.execute(true, polygon5, cutter5, - spatialReference, null); - Polygon cut; - int pathCount; - int pointCount; - double area; - - cut = (Polygon) cursor.next(); - pathCount = cut.getPathCount(); - pointCount = cut.getPointCount(); - area = cut.calculateArea2D(); - assertTrue(pathCount == 4); - assertTrue(pointCount == 12); - assertTrue(area == 450); - - cut = (Polygon) cursor.next(); - pathCount = cut.getPathCount(); - pointCount = cut.getPointCount(); - area = cut.calculateArea2D(); - assertTrue(pathCount == 1); - assertTrue(pointCount == 4); - assertTrue(area == 450); - - cut = (Polygon) cursor.next(); - assertTrue(cut == null); - } - - public static void testPolygon7(SpatialReference spatialReference) { - OperatorFactoryLocal engine = OperatorFactoryLocal.getInstance(); - OperatorCut opCut = (OperatorCut) engine.getOperator(Operator.Type.Cut); - - Polygon cut; - int path_count; - int point_count; - double area; - - Polygon polygon7 = makePolygon7(); - Polyline cutter7 = makePolygonCutter7(); - GeometryCursor cursor = opCut.execute(false, polygon7, cutter7, - spatialReference, null); - - cut = (Polygon) cursor.next(); - path_count = cut.getPathCount(); - point_count = cut.getPointCount(); - area = cut.calculateArea2D(); - assertTrue(path_count == 1); - assertTrue(point_count == 4); - assertTrue(area == 100); - - cut = (Polygon) cursor.next(); - assertTrue(cut.isEmpty()); - - cut = (Polygon) cursor.next(); - path_count = cut.getPathCount(); - point_count = cut.getPointCount(); - area = cut.calculateArea2D(); - assertTrue(path_count == 2); - assertTrue(point_count == 8); - assertTrue(area == 800); - - cut = (Polygon) cursor.next(); - assertTrue(cut == null); - } - - public static void testPolygon8(SpatialReference spatialReference) { - OperatorFactoryLocal engine = OperatorFactoryLocal.getInstance(); - OperatorCut opCut = (OperatorCut) engine.getOperator(Operator.Type.Cut); - - Polygon polygon8 = makePolygon8(); - Polyline cutter8 = makePolygonCutter8(); - - GeometryCursor cursor = opCut.execute(true, polygon8, cutter8, - spatialReference, null); - Polygon cut; - int pathCount; - int pointCount; - double area; - - cut = (Polygon) cursor.next(); - assertTrue(cut.isEmpty()); - - cut = (Polygon) cursor.next(); - pathCount = cut.getPathCount(); - pointCount = cut.getPointCount(); - area = cut.calculateArea2D(); - assertTrue(pathCount == 1); - assertTrue(pointCount == 4); - assertTrue(area == 100); - - cut = (Polygon) cursor.next(); - pathCount = cut.getPathCount(); - pointCount = cut.getPointCount(); - area = cut.calculateArea2D(); - assertTrue(pathCount == 2); - assertTrue(pointCount == 8); - assertTrue(area == 800); - - cut = (Polygon) cursor.next(); - assertTrue(cut == null); - } - - public static void testPolygon9(SpatialReference spatialReference) { - OperatorFactoryLocal engine = OperatorFactoryLocal.getInstance(); - OperatorCut opCut = (OperatorCut) engine.getOperator(Operator.Type.Cut); - - Polygon cut; - int path_count; - int point_count; - double area; - - Polygon polygon9 = makePolygon9(); - Polyline cutter9 = makePolygonCutter9(); - GeometryCursor cursor = opCut.execute(false, polygon9, cutter9, - spatialReference, null); - - cut = (Polygon) cursor.next(); - path_count = cut.getPathCount(); - point_count = cut.getPointCount(); - area = cut.calculateArea2D(); - assertTrue(path_count == 3); - assertTrue(point_count == 12); - assertTrue(area == 150); - - cut = (Polygon) cursor.next(); - path_count = cut.getPathCount(); - point_count = cut.getPointCount(); - area = cut.calculateArea2D(); - assertTrue(path_count == 3); - assertTrue(point_count == 12); - assertTrue(area == 150); - - cut = (Polygon) cursor.next(); - assertTrue(cut == null); - } - - public static void testEngine(SpatialReference spatialReference) { - Polygon polygon8 = makePolygon8(); - Polyline cutter8 = makePolygonCutter8(); - - Geometry[] cuts = GeometryEngine.cut(polygon8, cutter8, - spatialReference); - Polygon cut; - int pathCount; - int pointCount; - double area; - - cut = (Polygon) cuts[0]; - pathCount = cut.getPathCount(); - pointCount = cut.getPointCount(); - area = cut.calculateArea2D(); - assertTrue(pathCount == 1); - assertTrue(pointCount == 4); - assertTrue(area == 100); - - cut = (Polygon) cuts[1]; - pathCount = cut.getPathCount(); - pointCount = cut.getPointCount(); - area = cut.calculateArea2D(); - assertTrue(pathCount == 2); - assertTrue(pointCount == 8); - assertTrue(area == 800); - } - - public static Polyline makePolyline1() { - Polyline poly = new Polyline(); - - poly.startPath(0, 0); - poly.lineTo(2, 0); - poly.lineTo(4, 0); - poly.lineTo(6, 0); - poly.lineTo(8, 0); - poly.lineTo(10, 0); - poly.lineTo(12, 0); - poly.lineTo(14, 0); - poly.lineTo(16, 0); - poly.lineTo(18, 0); - poly.lineTo(20, 0); - - return poly; - } - - public static Polyline makePolylineCutter1() { - Polyline poly = new Polyline(); - - poly.startPath(1, 0); - poly.lineTo(4, 0); - - poly.startPath(6, -1); - poly.lineTo(6, 1); - - poly.startPath(6, 0); - poly.lineTo(8, 0); - - poly.startPath(9, -1); - poly.lineTo(9, 1); - - poly.startPath(10, 0); - poly.lineTo(12, 0); - - poly.startPath(12, 1); - poly.lineTo(12, -1); - - poly.startPath(12, 0); - poly.lineTo(15, 0); - - poly.startPath(15, 1); - poly.lineTo(15, -1); - - poly.startPath(16, 0); - poly.lineTo(16, -1); - poly.lineTo(17, -1); - poly.lineTo(17, 1); - poly.lineTo(17, 0); - poly.lineTo(18, 0); - - poly.startPath(18, 0); - poly.lineTo(18, -1); - - return poly; - } - - public static Polyline makePolyline2() { - Polyline poly = new Polyline(); - - poly.startPath(-2, 0); - poly.lineTo(-1, 0); - poly.lineTo(0, 0); - poly.lineTo(2, 0); - poly.lineTo(4, 2); - poly.lineTo(8, 2); - poly.lineTo(10, 4); - poly.lineTo(12, 4); - - return poly; - } - - public static Polyline makePolylineCutter2() { - Polyline poly = new Polyline(); - - poly.startPath(-1.5, 0); - poly.lineTo(-.75, 0); - - poly.startPath(-.5, 0); - poly.lineTo(1, 0); - poly.lineTo(1, 2); - poly.lineTo(3, -2); - poly.lineTo(4, 2); - poly.lineTo(5, -2); - poly.lineTo(5, 4); - poly.lineTo(8, 2); - poly.lineTo(6, 0); - poly.lineTo(6, 3); - - poly.startPath(9, 5); - poly.lineTo(9, 2); - poly.lineTo(10, 2); - poly.lineTo(10, 5); - poly.lineTo(10.5, 5); - poly.lineTo(10.5, 3); - - poly.startPath(11, 4); - poly.lineTo(11, 5); + @Override + protected void setUp() throws Exception { + super.setUp(); + } + + @Override + protected void tearDown() throws Exception { + super.tearDown(); + } + + @Test + public static void testCut4326() { + SpatialReference sr = SpatialReference.create(4326); + testConsiderTouch1(sr); + testConsiderTouch2(sr); + testPolygon5(sr); + testPolygon7(sr); + testPolygon8(sr); + testPolygon9(sr); + testEngine(sr); + + } + + public static void testConsiderTouch1(SpatialReference spatialReference) { + OperatorFactoryLocal engine = OperatorFactoryLocal.getInstance(); + OperatorCut opCut = (OperatorCut) engine.getOperator(Operator.Type.Cut); + + Polyline polyline1 = makePolyline1(); + Polyline polyline11 = makePolyline1(); + Polyline cutter1 = makePolylineCutter1(); + + ArrayDeque polylines = new ArrayDeque<>(); + polylines.push(polyline1); + polylines.push(polyline11); + + SimpleGeometryCursor simpleGeometryCursor = new SimpleGeometryCursor(polylines); + GeometryCursor cursor = opCut.execute(true, simpleGeometryCursor, cutter1, + spatialReference, null); + Polyline cut; + int pathCount; + int segmentCount; + double length; + + cut = (Polyline) cursor.next(); + pathCount = cut.getPathCount(); + segmentCount = cut.getSegmentCount(); + length = cut.calculateLength2D(); + assertTrue(pathCount == 4); + assertTrue(segmentCount == 4); + assertTrue(length == 6); + + cut = (Polyline) cursor.next(); + pathCount = cut.getPathCount(); + segmentCount = cut.getSegmentCount(); + length = cut.calculateLength2D(); + assertTrue(pathCount == 6); + assertTrue(segmentCount == 8); + assertTrue(length == 12); + + cut = (Polyline) cursor.next(); + pathCount = cut.getPathCount(); + segmentCount = cut.getSegmentCount(); + length = cut.calculateLength2D(); + assertTrue(pathCount == 1); + assertTrue(segmentCount == 1); + assertTrue(length == 1); + + cut = (Polyline) cursor.next(); + pathCount = cut.getPathCount(); + segmentCount = cut.getSegmentCount(); + length = cut.calculateLength2D(); + assertTrue(pathCount == 1); + assertTrue(segmentCount == 1); + assertTrue(length == 1); + + cut = (Polyline) cursor.next(); + pathCount = cut.getPathCount(); + segmentCount = cut.getSegmentCount(); + length = cut.calculateLength2D(); + assertTrue(pathCount == 4); + assertTrue(segmentCount == 4); + assertTrue(length == 6); + + cut = (Polyline) cursor.next(); + pathCount = cut.getPathCount(); + segmentCount = cut.getSegmentCount(); + length = cut.calculateLength2D(); + assertTrue(pathCount == 6); + assertTrue(segmentCount == 8); + assertTrue(length == 12); + + cut = (Polyline) cursor.next(); + pathCount = cut.getPathCount(); + segmentCount = cut.getSegmentCount(); + length = cut.calculateLength2D(); + assertTrue(pathCount == 1); + assertTrue(segmentCount == 1); + assertTrue(length == 1); + + cut = (Polyline) cursor.next(); + pathCount = cut.getPathCount(); + segmentCount = cut.getSegmentCount(); + length = cut.calculateLength2D(); + assertTrue(pathCount == 1); + assertTrue(segmentCount == 1); + assertTrue(length == 1); + + cut = (Polyline) cursor.next(); + assertTrue(cut == null); + } + + public static void testConsiderTouch2(SpatialReference spatialReference) { + OperatorFactoryLocal engine = OperatorFactoryLocal.getInstance(); + OperatorCut opCut = (OperatorCut) engine.getOperator(Operator.Type.Cut); + + Polyline polyline2 = makePolyline2(); + Polyline cutter2 = makePolylineCutter2(); + + GeometryCursor cursor = opCut.execute(true, polyline2, cutter2, + spatialReference, null); + Polyline cut; + int pathCount; + int segmentCount; + double length; + + cut = (Polyline) cursor.next(); + pathCount = cut.getPathCount(); + segmentCount = cut.getSegmentCount(); + length = cut.calculateLength2D(); + assertTrue(pathCount == 4); + assertTrue(segmentCount == 4); + assertTrue(Math.abs(length - 5.74264068) <= 0.001); + + cut = (Polyline) cursor.next(); + pathCount = cut.getPathCount(); + segmentCount = cut.getSegmentCount(); + length = cut.calculateLength2D(); + assertTrue(pathCount == 6); + assertTrue(segmentCount == 8); + assertTrue(length == 6.75); + + cut = (Polyline) cursor.next(); + pathCount = cut.getPathCount(); + segmentCount = cut.getSegmentCount(); + length = cut.calculateLength2D(); + assertTrue(pathCount == 1); + assertTrue(segmentCount == 1); + assertTrue(Math.abs(length - 0.5) <= 0.001); + + cut = (Polyline) cursor.next(); + pathCount = cut.getPathCount(); + segmentCount = cut.getSegmentCount(); + length = cut.calculateLength2D(); + assertTrue(pathCount == 1); + assertTrue(segmentCount == 1); + assertTrue(Math.abs(length - 0.25) <= 0.001); + + cut = (Polyline) cursor.next(); + pathCount = cut.getPathCount(); + segmentCount = cut.getSegmentCount(); + length = cut.calculateLength2D(); + assertTrue(pathCount == 1); + assertTrue(segmentCount == 1); + assertTrue(Math.abs(length - 1) <= 0.001); + + cut = (Polyline) cursor.next(); + pathCount = cut.getPathCount(); + segmentCount = cut.getSegmentCount(); + length = cut.calculateLength2D(); + assertTrue(pathCount == 1); + assertTrue(segmentCount == 1); + assertTrue(Math.abs(length - 1.41421356) <= 0.001); + + cut = (Polyline) cursor.next(); + assertTrue(cut == null); + } + + public static void testPolygon5(SpatialReference spatialReference) { + OperatorFactoryLocal engine = OperatorFactoryLocal.getInstance(); + OperatorCut opCut = (OperatorCut) engine.getOperator(Operator.Type.Cut); + + Polygon polygon5 = makePolygon5(); + Polyline cutter5 = makePolygonCutter5(); + + GeometryCursor cursor = opCut.execute(true, polygon5, cutter5, + spatialReference, null); + Polygon cut; + int pathCount; + int pointCount; + double area; + + cut = (Polygon) cursor.next(); + pathCount = cut.getPathCount(); + pointCount = cut.getPointCount(); + area = cut.calculateArea2D(); + assertTrue(pathCount == 4); + assertTrue(pointCount == 12); + assertTrue(area == 450); + + cut = (Polygon) cursor.next(); + pathCount = cut.getPathCount(); + pointCount = cut.getPointCount(); + area = cut.calculateArea2D(); + assertTrue(pathCount == 1); + assertTrue(pointCount == 4); + assertTrue(area == 450); + + cut = (Polygon) cursor.next(); + assertTrue(cut == null); + } + + public static void testPolygon7(SpatialReference spatialReference) { + OperatorFactoryLocal engine = OperatorFactoryLocal.getInstance(); + OperatorCut opCut = (OperatorCut) engine.getOperator(Operator.Type.Cut); + + Polygon cut; + int path_count; + int point_count; + double area; + + Polygon polygon7 = makePolygon7(); + Polyline cutter7 = makePolygonCutter7(); + GeometryCursor cursor = opCut.execute(false, polygon7, cutter7, + spatialReference, null); + + cut = (Polygon) cursor.next(); + path_count = cut.getPathCount(); + point_count = cut.getPointCount(); + area = cut.calculateArea2D(); + assertTrue(path_count == 1); + assertTrue(point_count == 4); + assertTrue(area == 100); + + cut = (Polygon) cursor.next(); + assertTrue(cut.isEmpty()); + + cut = (Polygon) cursor.next(); + path_count = cut.getPathCount(); + point_count = cut.getPointCount(); + area = cut.calculateArea2D(); + assertTrue(path_count == 2); + assertTrue(point_count == 8); + assertTrue(area == 800); + + cut = (Polygon) cursor.next(); + assertTrue(cut == null); + } + + public static void testPolygon8(SpatialReference spatialReference) { + OperatorFactoryLocal engine = OperatorFactoryLocal.getInstance(); + OperatorCut opCut = (OperatorCut) engine.getOperator(Operator.Type.Cut); + + Polygon polygon8 = makePolygon8(); + Polyline cutter8 = makePolygonCutter8(); + + GeometryCursor cursor = opCut.execute(true, polygon8, cutter8, + spatialReference, null); + Polygon cut; + int pathCount; + int pointCount; + double area; + + cut = (Polygon) cursor.next(); + assertTrue(cut.isEmpty()); + + cut = (Polygon) cursor.next(); + pathCount = cut.getPathCount(); + pointCount = cut.getPointCount(); + area = cut.calculateArea2D(); + assertTrue(pathCount == 1); + assertTrue(pointCount == 4); + assertTrue(area == 100); + + cut = (Polygon) cursor.next(); + pathCount = cut.getPathCount(); + pointCount = cut.getPointCount(); + area = cut.calculateArea2D(); + assertTrue(pathCount == 2); + assertTrue(pointCount == 8); + assertTrue(area == 800); + + cut = (Polygon) cursor.next(); + assertTrue(cut == null); + } + + public static void testPolygon9(SpatialReference spatialReference) { + OperatorFactoryLocal engine = OperatorFactoryLocal.getInstance(); + OperatorCut opCut = (OperatorCut) engine.getOperator(Operator.Type.Cut); + + Polygon cut; + int path_count; + int point_count; + double area; + + Polygon polygon9 = makePolygon9(); + Polyline cutter9 = makePolygonCutter9(); + GeometryCursor cursor = opCut.execute(false, polygon9, cutter9, + spatialReference, null); + + cut = (Polygon) cursor.next(); + path_count = cut.getPathCount(); + point_count = cut.getPointCount(); + area = cut.calculateArea2D(); + assertTrue(path_count == 3); + assertTrue(point_count == 12); + assertTrue(area == 150); + + cut = (Polygon) cursor.next(); + path_count = cut.getPathCount(); + point_count = cut.getPointCount(); + area = cut.calculateArea2D(); + assertTrue(path_count == 3); + assertTrue(point_count == 12); + assertTrue(area == 150); + + cut = (Polygon) cursor.next(); + assertTrue(cut == null); + } + + public static void testEngine(SpatialReference spatialReference) { + Polygon polygon8 = makePolygon8(); + Polyline cutter8 = makePolygonCutter8(); + + Geometry[] cuts = GeometryEngine.cut(polygon8, cutter8, + spatialReference); + Polygon cut; + int pathCount; + int pointCount; + double area; + + cut = (Polygon) cuts[0]; + pathCount = cut.getPathCount(); + pointCount = cut.getPointCount(); + area = cut.calculateArea2D(); + assertTrue(pathCount == 1); + assertTrue(pointCount == 4); + assertTrue(area == 100); + + cut = (Polygon) cuts[1]; + pathCount = cut.getPathCount(); + pointCount = cut.getPointCount(); + area = cut.calculateArea2D(); + assertTrue(pathCount == 2); + assertTrue(pointCount == 8); + assertTrue(area == 800); + } + + public static Polyline makePolyline1() { + Polyline poly = new Polyline(); + + poly.startPath(0, 0); + poly.lineTo(2, 0); + poly.lineTo(4, 0); + poly.lineTo(6, 0); + poly.lineTo(8, 0); + poly.lineTo(10, 0); + poly.lineTo(12, 0); + poly.lineTo(14, 0); + poly.lineTo(16, 0); + poly.lineTo(18, 0); + poly.lineTo(20, 0); + + return poly; + } + + public static Polyline makePolylineCutter1() { + Polyline poly = new Polyline(); + + poly.startPath(1, 0); + poly.lineTo(4, 0); + + poly.startPath(6, -1); + poly.lineTo(6, 1); + + poly.startPath(6, 0); + poly.lineTo(8, 0); + + poly.startPath(9, -1); + poly.lineTo(9, 1); + + poly.startPath(10, 0); + poly.lineTo(12, 0); + + poly.startPath(12, 1); + poly.lineTo(12, -1); + + poly.startPath(12, 0); + poly.lineTo(15, 0); + + poly.startPath(15, 1); + poly.lineTo(15, -1); + + poly.startPath(16, 0); + poly.lineTo(16, -1); + poly.lineTo(17, -1); + poly.lineTo(17, 1); + poly.lineTo(17, 0); + poly.lineTo(18, 0); + + poly.startPath(18, 0); + poly.lineTo(18, -1); + + return poly; + } + + public static Polyline makePolyline2() { + Polyline poly = new Polyline(); + + poly.startPath(-2, 0); + poly.lineTo(-1, 0); + poly.lineTo(0, 0); + poly.lineTo(2, 0); + poly.lineTo(4, 2); + poly.lineTo(8, 2); + poly.lineTo(10, 4); + poly.lineTo(12, 4); + + return poly; + } + + public static Polyline makePolylineCutter2() { + Polyline poly = new Polyline(); + + poly.startPath(-1.5, 0); + poly.lineTo(-.75, 0); + + poly.startPath(-.5, 0); + poly.lineTo(1, 0); + poly.lineTo(1, 2); + poly.lineTo(3, -2); + poly.lineTo(4, 2); + poly.lineTo(5, -2); + poly.lineTo(5, 4); + poly.lineTo(8, 2); + poly.lineTo(6, 0); + poly.lineTo(6, 3); + + poly.startPath(9, 5); + poly.lineTo(9, 2); + poly.lineTo(10, 2); + poly.lineTo(10, 5); + poly.lineTo(10.5, 5); + poly.lineTo(10.5, 3); + + poly.startPath(11, 4); + poly.lineTo(11, 5); - poly.startPath(12, 5); - poly.lineTo(12, 4); + poly.startPath(12, 5); + poly.lineTo(12, 4); - return poly; - } + return poly; + } - public static Polygon makePolygon5() { - Polygon poly = new Polygon(); + public static Polygon makePolygon5() { + Polygon poly = new Polygon(); - poly.startPath(0, 0); - poly.lineTo(0, 30); - poly.lineTo(30, 30); - poly.lineTo(30, 0); + poly.startPath(0, 0); + poly.lineTo(0, 30); + poly.lineTo(30, 30); + poly.lineTo(30, 0); - return poly; - } + return poly; + } - public static Polyline makePolygonCutter5() { - Polyline poly = new Polyline(); + public static Polyline makePolygonCutter5() { + Polyline poly = new Polyline(); - poly.startPath(15, 0); - poly.lineTo(0, 15); - poly.lineTo(15, 30); - poly.lineTo(30, 15); - poly.lineTo(15, 0); + poly.startPath(15, 0); + poly.lineTo(0, 15); + poly.lineTo(15, 30); + poly.lineTo(30, 15); + poly.lineTo(15, 0); - return poly; - } + return poly; + } - public static Polygon makePolygon7() { - Polygon poly = new Polygon(); + public static Polygon makePolygon7() { + Polygon poly = new Polygon(); - poly.startPath(0, 0); - poly.lineTo(0, 30); - poly.lineTo(30, 30); - poly.lineTo(30, 0); - - return poly; - } - - public static Polyline makePolygonCutter7() { - Polyline poly = new Polyline(); - - poly.startPath(10, 10); - poly.lineTo(20, 10); - poly.lineTo(20, 20); - poly.lineTo(10, 20); - poly.lineTo(10, 10); - - return poly; - } + poly.startPath(0, 0); + poly.lineTo(0, 30); + poly.lineTo(30, 30); + poly.lineTo(30, 0); + + return poly; + } + + public static Polyline makePolygonCutter7() { + Polyline poly = new Polyline(); + + poly.startPath(10, 10); + poly.lineTo(20, 10); + poly.lineTo(20, 20); + poly.lineTo(10, 20); + poly.lineTo(10, 10); + + return poly; + } - public static Polygon makePolygon8() { - Polygon poly = new Polygon(); + public static Polygon makePolygon8() { + Polygon poly = new Polygon(); - poly.startPath(0, 0); - poly.lineTo(0, 30); - poly.lineTo(30, 30); - poly.lineTo(30, 0); - - return poly; - } - - public static Polyline makePolygonCutter8() { - Polyline poly = new Polyline(); + poly.startPath(0, 0); + poly.lineTo(0, 30); + poly.lineTo(30, 30); + poly.lineTo(30, 0); + + return poly; + } + + public static Polyline makePolygonCutter8() { + Polyline poly = new Polyline(); - poly.startPath(10, 10); - poly.lineTo(10, 20); - poly.lineTo(20, 20); - poly.lineTo(20, 10); - poly.lineTo(10, 10); + poly.startPath(10, 10); + poly.lineTo(10, 20); + poly.lineTo(20, 20); + poly.lineTo(20, 10); + poly.lineTo(10, 10); - return poly; - } + return poly; + } - public static Polygon makePolygon9() { - Polygon poly = new Polygon(); + public static Polygon makePolygon9() { + Polygon poly = new Polygon(); - poly.startPath(0, 0); - poly.lineTo(0, 10); - poly.lineTo(10, 10); - poly.lineTo(10, 0); + poly.startPath(0, 0); + poly.lineTo(0, 10); + poly.lineTo(10, 10); + poly.lineTo(10, 0); - poly.startPath(0, 20); - poly.lineTo(0, 30); - poly.lineTo(10, 30); - poly.lineTo(10, 20); + poly.startPath(0, 20); + poly.lineTo(0, 30); + poly.lineTo(10, 30); + poly.lineTo(10, 20); - poly.startPath(0, 40); - poly.lineTo(0, 50); - poly.lineTo(10, 50); - poly.lineTo(10, 40); + poly.startPath(0, 40); + poly.lineTo(0, 50); + poly.lineTo(10, 50); + poly.lineTo(10, 40); - return poly; - } + return poly; + } - public static Polyline makePolygonCutter9() { - Polyline poly = new Polyline(); + public static Polyline makePolygonCutter9() { + Polyline poly = new Polyline(); - poly.startPath(5, -1); - poly.lineTo(5, 51); + poly.startPath(5, -1); + poly.lineTo(5, 51); - return poly; - } + return poly; + } } diff --git a/src/test/java/com/esri/core/geometry/TestDifference.java b/src/test/java/com/esri/core/geometry/TestDifference.java index dd0e9225..2e4fea12 100644 --- a/src/test/java/com/esri/core/geometry/TestDifference.java +++ b/src/test/java/com/esri/core/geometry/TestDifference.java @@ -28,647 +28,647 @@ import org.junit.Test; public class TestDifference extends TestCase { - @Override - protected void setUp() throws Exception { - super.setUp(); - } - - @Override - protected void tearDown() throws Exception { - super.tearDown(); - } - - @Test - public static void testDifferenceAndSymmetricDifference() { - OperatorFactoryLocal engine = OperatorFactoryLocal.getInstance(); - OperatorDifference differenceOp = (OperatorDifference) engine - .getOperator(Operator.Type.Difference); - - SpatialReference spatialRef = SpatialReference.create(102113); - Polygon polygon1 = makePolygon1(); - Polygon polygon2 = makePolygon2(); - Polyline polyline1 = makePolyline1(); - MultiPoint multipoint1 = makeMultiPoint1(); - MultiPoint multipoint2 = makeMultiPoint2(); - MultiPoint multipoint3 = makeMultiPoint3(); - Point point1 = makePoint1(); - Point point2 = makePoint2(); - Envelope envelope1 = makeEnvelope1(); - Envelope envelope2 = makeEnvelope2(); - Envelope envelope3 = makeEnvelope3(); - - Polygon outputPolygon = (Polygon) differenceOp.execute(polygon1, - polygon2, spatialRef, null); - double area = outputPolygon.calculateArea2D(); - assertTrue(Math.abs(area - 75) <= 0.001); - - { - Point point_1 = new Point(-130, 10); - Point point_2 = new Point(-130, 10); - Geometry baseGeom = new Point(point_1.getX(), point_1.getY()); - Geometry comparisonGeom = new Point(point_2.getX(), point2.getY()); - SpatialReference sr = SpatialReference.create(4326); - @SuppressWarnings("unused") - Geometry geom = differenceOp.execute(baseGeom, comparisonGeom, sr, - null); - } - - OperatorSymmetricDifference symDifferenceOp = (OperatorSymmetricDifference) engine - .getOperator(Operator.Type.SymmetricDifference); - outputPolygon = (Polygon) symDifferenceOp.execute(polygon1, polygon2, - spatialRef, null); - - area = outputPolygon.calculateArea2D(); - assertTrue(Math.abs(area - 150) <= 0.001); - - Polyline outputPolyline = (Polyline) differenceOp.execute(polyline1, - polygon1, spatialRef, null); - double length = outputPolyline.calculateLength2D(); - assertTrue(Math.abs(length * length - 50) < 0.001); - - MultiPoint outputMultiPoint = (MultiPoint) differenceOp.execute( - multipoint1, polygon1, spatialRef, null); - int pointCount = outputMultiPoint.getPointCount(); - assertTrue(pointCount == 1); - - outputMultiPoint = (MultiPoint) (symDifferenceOp.execute(multipoint1, - point1, spatialRef, null)); - pointCount = outputMultiPoint.getPointCount(); - assertTrue(pointCount == 2); - - outputMultiPoint = (MultiPoint) (symDifferenceOp.execute(multipoint1, - point2, spatialRef, null)); - pointCount = outputMultiPoint.getPointCount(); - assertTrue(pointCount == 4); - - outputMultiPoint = (MultiPoint) (differenceOp.execute(multipoint1, - point1, spatialRef, null)); - pointCount = outputMultiPoint.getPointCount(); - assertTrue(pointCount == 2); - - outputMultiPoint = (MultiPoint) (differenceOp.execute(multipoint1, - point2, spatialRef, null)); - pointCount = outputMultiPoint.getPointCount(); - assertTrue(pointCount == 3); - - outputPolygon = (Polygon) (differenceOp.execute(polygon1, envelope1, - spatialRef, null)); - area = outputPolygon.calculateArea2D(); - assertTrue(Math.abs(area - 75) <= 0.001); - - outputPolygon = (Polygon) (differenceOp.execute(polygon2, envelope2, - spatialRef, null)); - area = outputPolygon.calculateArea2D(); - assertTrue(Math.abs(area - 75) <= 0.001); - - outputPolyline = (Polyline) (differenceOp.execute(polyline1, envelope2, - spatialRef, null)); - length = outputPolyline.calculateLength2D(); - assertTrue(Math.abs(length * length - 50) <= 0.001); - - outputMultiPoint = (MultiPoint) (differenceOp.execute(multipoint1, - envelope2, spatialRef, null)); - pointCount = outputMultiPoint.getPointCount(); - assertTrue(pointCount == 1); - - outputMultiPoint = (MultiPoint) (differenceOp.execute(multipoint2, - envelope2, spatialRef, null)); - pointCount = outputMultiPoint.getPointCount(); - assertTrue(pointCount == 6); - - outputMultiPoint = (MultiPoint) (differenceOp.execute(multipoint3, - envelope2, spatialRef, null)); - pointCount = outputMultiPoint.getPointCount(); - assertTrue(pointCount == 0); - - Point outputPoint = (Point) (differenceOp.execute(point1, envelope2, - spatialRef, null)); - assertTrue(!outputPoint.isEmpty()); - - outputPoint = (Point) (differenceOp.execute(point2, envelope2, - spatialRef, null)); - assertTrue(outputPoint.isEmpty()); - - outputPolygon = (Polygon) (differenceOp.execute(envelope3, envelope2, - spatialRef, null)); - assertTrue(outputPolygon != null && outputPolygon.isEmpty()); - - outputPolygon = (Polygon) (symDifferenceOp.execute(envelope3, - envelope3, spatialRef, null)); - assertTrue(outputPolygon != null && outputPolygon.isEmpty()); - - outputPoint = (Point) (differenceOp.execute(point1, polygon1, - spatialRef, null)); - assertTrue(outputPoint != null); - } - - @Test - public static void testPointTypes() { - OperatorFactoryLocal engine = OperatorFactoryLocal.getInstance(); - OperatorDifference difference = (OperatorDifference) engine - .getOperator(Operator.Type.Difference); - OperatorSymmetricDifference sym_difference = (OperatorSymmetricDifference) engine - .getOperator(Operator.Type.SymmetricDifference); - - {// point/point - Point point_1 = new Point(); - Point point_2 = new Point(); - point_1.setXY(0, 0); - point_2.setXY(0.0000000009, 0.0000000009); - Point differenced = (Point) (difference.execute(point_1, point_2, - SpatialReference.create(4326), null)); - assertTrue(differenced.isEmpty()); - - MultiPoint sym_differenced = (MultiPoint) (sym_difference.execute( - point_1, point_2, SpatialReference.create(4326), null)); - assertTrue(sym_differenced.isEmpty()); - } - - {// point/point - Point point_1 = new Point(); - Point point_2 = new Point(); - point_1.setXY(0, 0); - point_2.setXY(0.000000009, 0.0); - Point differenced = (Point) (difference.execute(point_1, point_2, - SpatialReference.create(4326), null)); - assertTrue(differenced.isEmpty()); - - MultiPoint sym_differenced = (MultiPoint) (sym_difference.execute( - point_1, point_2, SpatialReference.create(4326), null)); - assertTrue(sym_differenced.isEmpty()); - } - - {// point/point - Point point_1 = new Point(); - Point point_2 = new Point(); - point_1.setXY(0, 0); - point_2.setXY(0.00000002, 0.00000002); - Point differenced_1 = (Point) (difference.execute(point_1, point_2, - SpatialReference.create(4326), null)); - assertTrue(!differenced_1.isEmpty()); - - Point differenced_2 = (Point) (difference.execute(point_2, point_1, - SpatialReference.create(4326), null)); - assertTrue(!differenced_2.isEmpty()); - - MultiPoint sym_differenced = (MultiPoint) (sym_difference.execute( - point_1, point_2, SpatialReference.create(4326), null)); - assertTrue(!sym_differenced.isEmpty()); - assertTrue(sym_differenced.getXY(0).x == 0 - && sym_differenced.getXY(0).y == 0); - assertTrue(sym_differenced.getXY(1).x == 0.00000002 - && sym_differenced.getXY(1).y == 0.00000002); - } - - {// multi_point/point - MultiPoint multi_point_1 = new MultiPoint(); - Point point_2 = new Point(); - multi_point_1.add(0, 0); - multi_point_1.add(1, 1); - point_2.setXY(0.0000000009, 0.0000000009); - MultiPoint differenced_1 = (MultiPoint) (difference - .execute(multi_point_1, point_2, - SpatialReference.create(4326), null)); - assertTrue(!differenced_1.isEmpty()); - assertTrue(differenced_1.getPointCount() == 1); - assertTrue(differenced_1.getXY(0).x == 1 - && differenced_1.getXY(0).y == 1); - - Point differenced_2 = (Point) (difference.execute(point_2, - multi_point_1, SpatialReference.create(4326), null)); - assertTrue(differenced_2.isEmpty()); - } - - {// multi_point/point - MultiPoint multi_point_1 = new MultiPoint(); - Point point_2 = new Point(); - multi_point_1.add(0, 0); - multi_point_1.add(1, 1); - point_2.setXY(0.000000009, 0.0); - MultiPoint differenced_1 = (MultiPoint) (difference - .execute(multi_point_1, point_2, - SpatialReference.create(4326), null)); - assertTrue(!differenced_1.isEmpty()); - assertTrue(differenced_1.getXY(0).x == 1.0 - && differenced_1.getXY(0).y == 1.0); - - Point differenced_2 = (Point) (difference.execute(point_2, - multi_point_1, SpatialReference.create(4326), null)); - assertTrue(differenced_2.isEmpty()); - - MultiPoint sym_differenced = (MultiPoint) (sym_difference - .execute(multi_point_1, point_2, - SpatialReference.create(4326), null)); - assertTrue(!sym_differenced.isEmpty()); - assertTrue(sym_differenced.getPointCount() == 1); - assertTrue(sym_differenced.getXY(0).x == 1 - && sym_differenced.getXY(0).y == 1); - } - - {// multi_point/point - MultiPoint multi_point_1 = new MultiPoint(); - Point point_2 = new Point(); - multi_point_1.add(0, 0); - multi_point_1.add(0, 0); - point_2.setXY(0.000000009, 0.0); - MultiPoint differenced_1 = (MultiPoint) (difference - .execute(multi_point_1, point_2, - SpatialReference.create(4326), null)); - assertTrue(differenced_1.isEmpty()); - - MultiPoint sym_differenced = (MultiPoint) (sym_difference - .execute(multi_point_1, point_2, - SpatialReference.create(4326), null)); - assertTrue(sym_differenced.isEmpty()); - } - - {// multi_point/polygon - MultiPoint multi_point_1 = new MultiPoint(); - Polygon polygon_2 = new Polygon(); - multi_point_1.add(0, 0); - multi_point_1.add(0, 0); - multi_point_1.add(2, 2); - - polygon_2.startPath(-1, -1); - polygon_2.lineTo(-1, 1); - polygon_2.lineTo(1, 1); - polygon_2.lineTo(1, -1); - MultiPoint differenced_1 = (MultiPoint) (difference.execute( - multi_point_1, polygon_2, SpatialReference.create(4326), - null)); - assertTrue(!differenced_1.isEmpty()); - assertTrue(differenced_1.getPointCount() == 1); - assertTrue(differenced_1.getXY(0).x == 2 - && differenced_1.getXY(0).y == 2); - } - - {// multi_point/polygon - MultiPoint multi_point_1 = new MultiPoint(); - Polygon polygon_2 = new Polygon(); - multi_point_1.add(0, 0); - multi_point_1.add(0, 0); - multi_point_1.add(1, 1); - - polygon_2.startPath(-1, -1); - polygon_2.lineTo(-1, 1); - polygon_2.lineTo(1, 1); - polygon_2.lineTo(1, -1); - MultiPoint differenced_1 = (MultiPoint) (difference.execute( - multi_point_1, polygon_2, SpatialReference.create(4326), - null)); - assertTrue(differenced_1.isEmpty()); - } - - {// multi_point/envelope - MultiPoint multi_point_1 = new MultiPoint(); - Envelope envelope_2 = new Envelope(); - multi_point_1.add(-2, 0); - multi_point_1.add(0, 2); - multi_point_1.add(2, 0); - multi_point_1.add(0, -2); - - envelope_2.setCoords(-1, -1, 1, 1); - MultiPoint differenced_1 = (MultiPoint) (difference.execute( - multi_point_1, envelope_2, SpatialReference.create(4326), - null)); - assertTrue(!differenced_1.isEmpty() - && differenced_1 == multi_point_1); - } - - {// multi_point/polygon - MultiPoint multi_point_1 = new MultiPoint(); - Polygon polygon_2 = new Polygon(); - multi_point_1.add(2, 2); - multi_point_1.add(2, 2); - multi_point_1.add(-2, -2); - - polygon_2.startPath(-1, -1); - polygon_2.lineTo(-1, 1); - polygon_2.lineTo(1, 1); - polygon_2.lineTo(1, -1); - MultiPoint differenced_1 = (MultiPoint) (difference.execute( - multi_point_1, polygon_2, SpatialReference.create(4326), - null)); - assertTrue(!differenced_1.isEmpty() - && differenced_1 == multi_point_1); - } - - {// point/polygon - Point point_1 = new Point(); - Polygon polygon_2 = new Polygon(); - point_1.setXY(0, 0); - - polygon_2.startPath(-1, -1); - polygon_2.lineTo(-1, 1); - polygon_2.lineTo(1, 1); - polygon_2.lineTo(1, -1); - Point differenced_1 = (Point) (difference.execute(point_1, - polygon_2, SpatialReference.create(4326), null)); - assertTrue(differenced_1.isEmpty()); - - polygon_2.setEmpty(); - polygon_2.startPath(1, 1); - polygon_2.lineTo(1, 2); - polygon_2.lineTo(2, 2); - polygon_2.lineTo(2, 1); - differenced_1 = (Point) (difference.execute(point_1, polygon_2, - SpatialReference.create(4326), null)); - assertTrue(!differenced_1.isEmpty()); - assertTrue(differenced_1 == point_1); - } - - {// point/polygon - Point point_1 = new Point(); - Polygon polygon_2 = new Polygon(); - point_1.setXY(0, 0); - - polygon_2.startPath(1, 0); - polygon_2.lineTo(0, 1); - polygon_2.lineTo(1, 1); - Point differenced_1 = (Point) (difference.execute(point_1, - polygon_2, SpatialReference.create(4326), null)); - assertTrue(!differenced_1.isEmpty()); - assertTrue(differenced_1 == point_1); - - point_1.setEmpty(); - point_1.setXY(0.5, 0.5); - - polygon_2.setEmpty(); - polygon_2.startPath(1, 0); - polygon_2.lineTo(0, 1); - polygon_2.lineTo(1, 1); - differenced_1 = (Point) (difference.execute(point_1, polygon_2, - SpatialReference.create(4326), null)); - assertTrue(differenced_1.isEmpty()); - } - - {// point/envelope - Point point_1 = new Point(); - Envelope envelope_2 = new Envelope(); - point_1.setXY(0, 0); - - envelope_2.setCoords(-1, -1, 1, 1); - Point differenced_1 = (Point) (difference.execute(point_1, - envelope_2, SpatialReference.create(4326), null)); - assertTrue(differenced_1.isEmpty()); - - envelope_2.setEmpty(); - envelope_2.setCoords(1, 1, 2, 2); - differenced_1 = (Point) (difference.execute(point_1, envelope_2, - SpatialReference.create(4326), null)); - assertTrue(!differenced_1.isEmpty()); - assertTrue(differenced_1 == point_1); - } - - {// point/polyline - Point point_1 = new Point(); - Polyline polyline_2 = new Polyline(); - point_1.setXY(0, 0); - - polyline_2.startPath(-1, 0); - polyline_2.lineTo(1, 0); - Point differenced_1 = (Point) (difference.execute(point_1, - polyline_2, SpatialReference.create(4326), null)); - assertTrue(differenced_1.isEmpty()); - - polyline_2.setEmpty(); - polyline_2.startPath(1, 0); - polyline_2.lineTo(2, 0); - differenced_1 = (Point) (difference.execute(point_1, polyline_2, - SpatialReference.create(4326), null)); - assertTrue(!differenced_1.isEmpty()); - assertTrue(differenced_1 == point_1); - - polyline_2.setEmpty(); - polyline_2.startPath(-1, -1); - polyline_2.lineTo(-1, 1); - polyline_2.lineTo(1, 1); - polyline_2.lineTo(1, -1); - differenced_1 = (Point) (difference.execute(point_1, polyline_2, - SpatialReference.create(4326), null)); - assertTrue(!differenced_1.isEmpty()); - assertTrue(differenced_1 == point_1); - } - } - - @Test - public static void testDifferenceOnPolyline() { - // # * * # - // # * @ - // # @ * - // # * - // - // /////////////////////////////// - // - // The polyline drawn in *s represents basePl - // The polyline drawn in #s represents compPl - // The @ represents their intersection points, so that - // the difference polyline will be basePl with two new vertices @ added. - - Polyline basePl = new Polyline(); - basePl.startPath(new Point(-117, 20)); - basePl.lineTo(new Point(-130, 10)); - basePl.lineTo(new Point(-120, 50)); - - Polyline compPl = new Polyline(); - compPl.startPath(new Point(-116, 20)); - compPl.lineTo(new Point(-131, 10)); - compPl.lineTo(new Point(-121, 50)); - - Geometry diffGeom = GeometryEngine.difference(basePl, compPl, - SpatialReference.create(4326)); - assertTrue(diffGeom instanceof Polyline); - Polyline diffPolyline = (Polyline) diffGeom; - int pointCountDiffPolyline = diffPolyline.getPointCount(); - - // first line in comp_pl is 3y = 2x + 292 - assertEquals(3 * 20, 2 * (-116) + 292); - assertEquals(3 * 10, 2 * (-131) + 292); - - // new points should also lie on this line - assertTrue(3.0 * diffPolyline.getCoordinates2D()[1].y - 2.0 - * diffPolyline.getCoordinates2D()[1].x - 292.0 == 0.0); - assertTrue(3.0 * diffPolyline.getCoordinates2D()[3].y - 2.0 - * diffPolyline.getCoordinates2D()[3].x - 292.0 == 0.0); - - for (int i = 0; i < 3; i++) { - assertTrue(basePl.getCoordinates2D()[i].x == diffPolyline - .getCoordinates2D()[2 * i].x); - assertTrue(basePl.getCoordinates2D()[i].y == diffPolyline - .getCoordinates2D()[2 * i].y); - } - - assertEquals(5, pointCountDiffPolyline); - } - - public static Polygon makePolygon1() { - Polygon poly = new Polygon(); - poly.startPath(0, 0); - poly.lineTo(0, 10); - poly.lineTo(10, 10); - poly.lineTo(10, 0); - - return poly; - } - - public static Polygon makePolygon2() { - Polygon poly = new Polygon(); - - poly.startPath(5, 5); - poly.lineTo(5, 15); - poly.lineTo(15, 15); - poly.lineTo(15, 5); - - return poly; - } - - public static Polyline makePolyline1() { - Polyline poly = new Polyline(); - poly.startPath(0, 0); - poly.lineTo(15, 15); - - return poly; - } - - public static MultiPoint makeMultiPoint1() { - MultiPoint mpoint = new MultiPoint(); - Point2D pt1 = new Point2D(); - pt1.x = 1.0; - pt1.y = 1.0; - - Point2D pt2 = new Point2D(); - pt2.x = 5.0; - pt2.y = 5.0; - - Point2D pt3 = new Point2D(); - pt3.x = 15.0; - pt3.y = 15.0; - - mpoint.add(pt1.x, pt1.y); - mpoint.add(pt2.x, pt2.y); - mpoint.add(pt3.x, pt3.y); - - return mpoint; - } - - public static MultiPoint makeMultiPoint2() { - MultiPoint mpoint = new MultiPoint(); - Point2D pt1 = new Point2D(); - pt1.x = 1.0; - pt1.y = 1.0; - - Point2D pt2 = new Point2D(); - pt2.x = 1.0; - pt2.y = 1.0; - - Point2D pt3 = new Point2D(); - pt3.x = 15.0; - pt3.y = 15.0; - - Point2D pt4 = new Point2D(); - pt4.x = 15.0; - pt4.y = 15.0; - - Point2D pt5 = new Point2D(); - pt5.x = 1.0; - pt5.y = 1.0; - - Point2D pt6 = new Point2D(); - pt6.x = 1.0; - pt6.y = 1.0; - - Point2D pt7 = new Point2D(); - pt7.x = 15.0; - pt7.y = 15.0; - - Point2D pt8 = new Point2D(); - pt8.x = 15.0; - pt8.y = 15.0; - - Point2D pt9 = new Point2D(); - pt9.x = 15.0; - pt9.y = 15.0; - - Point2D pt10 = new Point2D(); - pt10.x = 1.0; - pt10.y = 1.0; - - Point2D pt11 = new Point2D(); - pt11.x = 15.0; - pt11.y = 15.0; - - mpoint.add(pt1.x, pt1.y); - mpoint.add(pt2.x, pt2.y); - mpoint.add(pt3.x, pt3.y); - mpoint.add(pt4.x, pt4.y); - mpoint.add(pt5.x, pt5.y); - mpoint.add(pt6.x, pt6.y); - mpoint.add(pt7.x, pt7.y); - mpoint.add(pt8.x, pt8.y); - mpoint.add(pt9.x, pt9.y); - mpoint.add(pt10.x, pt10.y); - mpoint.add(pt11.x, pt11.y); - - return mpoint; - } - - public static MultiPoint makeMultiPoint3() { - MultiPoint mpoint = new MultiPoint(); - Point2D pt1 = new Point2D(); - pt1.x = 1.0; - pt1.y = 1.0; - - Point2D pt2 = new Point2D(); - pt2.x = 5.0; - pt2.y = 5.0; - - mpoint.add(pt1.x, pt1.y); - mpoint.add(pt2.x, pt2.y); - - return mpoint; - } - - public static Point makePoint1() { - Point point = new Point(); - - Point2D pt = new Point2D(); - pt.setCoords(15, 15); - point.setXY(pt); - - return point; - } - - public static Point makePoint2() { - Point point = new Point(); - - Point2D pt = new Point2D(); - pt.setCoords(7, 7); - point.setXY(pt); - - return point; - } - - public static Envelope makeEnvelope1() { - Envelope2D env = new Envelope2D(); - env.setCoords(5, 5, 15, 15); - Envelope envelope = new Envelope(env); - - return envelope; - } - - public static Envelope makeEnvelope2() { - Envelope2D env = new Envelope2D(); - env.setCoords(0, 0, 10, 10); - Envelope envelope = new Envelope(env); + @Override + protected void setUp() throws Exception { + super.setUp(); + } + + @Override + protected void tearDown() throws Exception { + super.tearDown(); + } + + @Test + public static void testDifferenceAndSymmetricDifference() { + OperatorFactoryLocal engine = OperatorFactoryLocal.getInstance(); + OperatorDifference differenceOp = (OperatorDifference) engine + .getOperator(Operator.Type.Difference); + + SpatialReference spatialRef = SpatialReference.create(102113); + Polygon polygon1 = makePolygon1(); + Polygon polygon2 = makePolygon2(); + Polyline polyline1 = makePolyline1(); + MultiPoint multipoint1 = makeMultiPoint1(); + MultiPoint multipoint2 = makeMultiPoint2(); + MultiPoint multipoint3 = makeMultiPoint3(); + Point point1 = makePoint1(); + Point point2 = makePoint2(); + Envelope envelope1 = makeEnvelope1(); + Envelope envelope2 = makeEnvelope2(); + Envelope envelope3 = makeEnvelope3(); + + Polygon outputPolygon = (Polygon) differenceOp.execute(polygon1, + polygon2, spatialRef, null); + double area = outputPolygon.calculateArea2D(); + assertTrue(Math.abs(area - 75) <= 0.001); + + { + Point point_1 = new Point(-130, 10); + Point point_2 = new Point(-130, 10); + Geometry baseGeom = new Point(point_1.getX(), point_1.getY()); + Geometry comparisonGeom = new Point(point_2.getX(), point2.getY()); + SpatialReference sr = SpatialReference.create(4326); + @SuppressWarnings("unused") + Geometry geom = differenceOp.execute(baseGeom, comparisonGeom, sr, + null); + } + + OperatorSymmetricDifference symDifferenceOp = (OperatorSymmetricDifference) engine + .getOperator(Operator.Type.SymmetricDifference); + outputPolygon = (Polygon) symDifferenceOp.execute(polygon1, polygon2, + spatialRef, null); + + area = outputPolygon.calculateArea2D(); + assertTrue(Math.abs(area - 150) <= 0.001); + + Polyline outputPolyline = (Polyline) differenceOp.execute(polyline1, + polygon1, spatialRef, null); + double length = outputPolyline.calculateLength2D(); + assertTrue(Math.abs(length * length - 50) < 0.001); + + MultiPoint outputMultiPoint = (MultiPoint) differenceOp.execute( + multipoint1, polygon1, spatialRef, null); + int pointCount = outputMultiPoint.getPointCount(); + assertTrue(pointCount == 1); + + outputMultiPoint = (MultiPoint) (symDifferenceOp.execute(multipoint1, + point1, spatialRef, null)); + pointCount = outputMultiPoint.getPointCount(); + assertTrue(pointCount == 2); + + outputMultiPoint = (MultiPoint) (symDifferenceOp.execute(multipoint1, + point2, spatialRef, null)); + pointCount = outputMultiPoint.getPointCount(); + assertTrue(pointCount == 4); + + outputMultiPoint = (MultiPoint) (differenceOp.execute(multipoint1, + point1, spatialRef, null)); + pointCount = outputMultiPoint.getPointCount(); + assertTrue(pointCount == 2); + + outputMultiPoint = (MultiPoint) (differenceOp.execute(multipoint1, + point2, spatialRef, null)); + pointCount = outputMultiPoint.getPointCount(); + assertTrue(pointCount == 3); + + outputPolygon = (Polygon) (differenceOp.execute(polygon1, envelope1, + spatialRef, null)); + area = outputPolygon.calculateArea2D(); + assertTrue(Math.abs(area - 75) <= 0.001); + + outputPolygon = (Polygon) (differenceOp.execute(polygon2, envelope2, + spatialRef, null)); + area = outputPolygon.calculateArea2D(); + assertTrue(Math.abs(area - 75) <= 0.001); + + outputPolyline = (Polyline) (differenceOp.execute(polyline1, envelope2, + spatialRef, null)); + length = outputPolyline.calculateLength2D(); + assertTrue(Math.abs(length * length - 50) <= 0.001); + + outputMultiPoint = (MultiPoint) (differenceOp.execute(multipoint1, + envelope2, spatialRef, null)); + pointCount = outputMultiPoint.getPointCount(); + assertTrue(pointCount == 1); + + outputMultiPoint = (MultiPoint) (differenceOp.execute(multipoint2, + envelope2, spatialRef, null)); + pointCount = outputMultiPoint.getPointCount(); + assertTrue(pointCount == 6); + + outputMultiPoint = (MultiPoint) (differenceOp.execute(multipoint3, + envelope2, spatialRef, null)); + pointCount = outputMultiPoint.getPointCount(); + assertTrue(pointCount == 0); + + Point outputPoint = (Point) (differenceOp.execute(point1, envelope2, + spatialRef, null)); + assertTrue(!outputPoint.isEmpty()); + + outputPoint = (Point) (differenceOp.execute(point2, envelope2, + spatialRef, null)); + assertTrue(outputPoint.isEmpty()); + + outputPolygon = (Polygon) (differenceOp.execute(envelope3, envelope2, + spatialRef, null)); + assertTrue(outputPolygon != null && outputPolygon.isEmpty()); + + outputPolygon = (Polygon) (symDifferenceOp.execute(envelope3, + envelope3, spatialRef, null)); + assertTrue(outputPolygon != null && outputPolygon.isEmpty()); + + outputPoint = (Point) (differenceOp.execute(point1, polygon1, + spatialRef, null)); + assertTrue(outputPoint != null); + } + + @Test + public static void testPointTypes() { + OperatorFactoryLocal engine = OperatorFactoryLocal.getInstance(); + OperatorDifference difference = (OperatorDifference) engine + .getOperator(Operator.Type.Difference); + OperatorSymmetricDifference sym_difference = (OperatorSymmetricDifference) engine + .getOperator(Operator.Type.SymmetricDifference); + + {// point/point + Point point_1 = new Point(); + Point point_2 = new Point(); + point_1.setXY(0, 0); + point_2.setXY(0.0000000009, 0.0000000009); + Point differenced = (Point) (difference.execute(point_1, point_2, + SpatialReference.create(4326), null)); + assertTrue(differenced.isEmpty()); + + MultiPoint sym_differenced = (MultiPoint) (sym_difference.execute( + point_1, point_2, SpatialReference.create(4326), null)); + assertTrue(sym_differenced.isEmpty()); + } + + {// point/point + Point point_1 = new Point(); + Point point_2 = new Point(); + point_1.setXY(0, 0); + point_2.setXY(0.000000009, 0.0); + Point differenced = (Point) (difference.execute(point_1, point_2, + SpatialReference.create(4326), null)); + assertTrue(differenced.isEmpty()); + + MultiPoint sym_differenced = (MultiPoint) (sym_difference.execute( + point_1, point_2, SpatialReference.create(4326), null)); + assertTrue(sym_differenced.isEmpty()); + } + + {// point/point + Point point_1 = new Point(); + Point point_2 = new Point(); + point_1.setXY(0, 0); + point_2.setXY(0.00000002, 0.00000002); + Point differenced_1 = (Point) (difference.execute(point_1, point_2, + SpatialReference.create(4326), null)); + assertTrue(!differenced_1.isEmpty()); + + Point differenced_2 = (Point) (difference.execute(point_2, point_1, + SpatialReference.create(4326), null)); + assertTrue(!differenced_2.isEmpty()); + + MultiPoint sym_differenced = (MultiPoint) (sym_difference.execute( + point_1, point_2, SpatialReference.create(4326), null)); + assertTrue(!sym_differenced.isEmpty()); + assertTrue(sym_differenced.getXY(0).x == 0 + && sym_differenced.getXY(0).y == 0); + assertTrue(sym_differenced.getXY(1).x == 0.00000002 + && sym_differenced.getXY(1).y == 0.00000002); + } + + {// multi_point/point + MultiPoint multi_point_1 = new MultiPoint(); + Point point_2 = new Point(); + multi_point_1.add(0, 0); + multi_point_1.add(1, 1); + point_2.setXY(0.0000000009, 0.0000000009); + MultiPoint differenced_1 = (MultiPoint) (difference + .execute(multi_point_1, point_2, + SpatialReference.create(4326), null)); + assertTrue(!differenced_1.isEmpty()); + assertTrue(differenced_1.getPointCount() == 1); + assertTrue(differenced_1.getXY(0).x == 1 + && differenced_1.getXY(0).y == 1); + + Point differenced_2 = (Point) (difference.execute(point_2, + multi_point_1, SpatialReference.create(4326), null)); + assertTrue(differenced_2.isEmpty()); + } + + {// multi_point/point + MultiPoint multi_point_1 = new MultiPoint(); + Point point_2 = new Point(); + multi_point_1.add(0, 0); + multi_point_1.add(1, 1); + point_2.setXY(0.000000009, 0.0); + MultiPoint differenced_1 = (MultiPoint) (difference + .execute(multi_point_1, point_2, + SpatialReference.create(4326), null)); + assertTrue(!differenced_1.isEmpty()); + assertTrue(differenced_1.getXY(0).x == 1.0 + && differenced_1.getXY(0).y == 1.0); + + Point differenced_2 = (Point) (difference.execute(point_2, + multi_point_1, SpatialReference.create(4326), null)); + assertTrue(differenced_2.isEmpty()); + + MultiPoint sym_differenced = (MultiPoint) (sym_difference + .execute(multi_point_1, point_2, + SpatialReference.create(4326), null)); + assertTrue(!sym_differenced.isEmpty()); + assertTrue(sym_differenced.getPointCount() == 1); + assertTrue(sym_differenced.getXY(0).x == 1 + && sym_differenced.getXY(0).y == 1); + } + + {// multi_point/point + MultiPoint multi_point_1 = new MultiPoint(); + Point point_2 = new Point(); + multi_point_1.add(0, 0); + multi_point_1.add(0, 0); + point_2.setXY(0.000000009, 0.0); + MultiPoint differenced_1 = (MultiPoint) (difference + .execute(multi_point_1, point_2, + SpatialReference.create(4326), null)); + assertTrue(differenced_1.isEmpty()); + + MultiPoint sym_differenced = (MultiPoint) (sym_difference + .execute(multi_point_1, point_2, + SpatialReference.create(4326), null)); + assertTrue(sym_differenced.isEmpty()); + } + + {// multi_point/polygon + MultiPoint multi_point_1 = new MultiPoint(); + Polygon polygon_2 = new Polygon(); + multi_point_1.add(0, 0); + multi_point_1.add(0, 0); + multi_point_1.add(2, 2); + + polygon_2.startPath(-1, -1); + polygon_2.lineTo(-1, 1); + polygon_2.lineTo(1, 1); + polygon_2.lineTo(1, -1); + MultiPoint differenced_1 = (MultiPoint) (difference.execute( + multi_point_1, polygon_2, SpatialReference.create(4326), + null)); + assertTrue(!differenced_1.isEmpty()); + assertTrue(differenced_1.getPointCount() == 1); + assertTrue(differenced_1.getXY(0).x == 2 + && differenced_1.getXY(0).y == 2); + } + + {// multi_point/polygon + MultiPoint multi_point_1 = new MultiPoint(); + Polygon polygon_2 = new Polygon(); + multi_point_1.add(0, 0); + multi_point_1.add(0, 0); + multi_point_1.add(1, 1); + + polygon_2.startPath(-1, -1); + polygon_2.lineTo(-1, 1); + polygon_2.lineTo(1, 1); + polygon_2.lineTo(1, -1); + MultiPoint differenced_1 = (MultiPoint) (difference.execute( + multi_point_1, polygon_2, SpatialReference.create(4326), + null)); + assertTrue(differenced_1.isEmpty()); + } + + {// multi_point/envelope + MultiPoint multi_point_1 = new MultiPoint(); + Envelope envelope_2 = new Envelope(); + multi_point_1.add(-2, 0); + multi_point_1.add(0, 2); + multi_point_1.add(2, 0); + multi_point_1.add(0, -2); + + envelope_2.setCoords(-1, -1, 1, 1); + MultiPoint differenced_1 = (MultiPoint) (difference.execute( + multi_point_1, envelope_2, SpatialReference.create(4326), + null)); + assertTrue(!differenced_1.isEmpty() + && differenced_1 == multi_point_1); + } + + {// multi_point/polygon + MultiPoint multi_point_1 = new MultiPoint(); + Polygon polygon_2 = new Polygon(); + multi_point_1.add(2, 2); + multi_point_1.add(2, 2); + multi_point_1.add(-2, -2); + + polygon_2.startPath(-1, -1); + polygon_2.lineTo(-1, 1); + polygon_2.lineTo(1, 1); + polygon_2.lineTo(1, -1); + MultiPoint differenced_1 = (MultiPoint) (difference.execute( + multi_point_1, polygon_2, SpatialReference.create(4326), + null)); + assertTrue(!differenced_1.isEmpty() + && differenced_1 == multi_point_1); + } + + {// point/polygon + Point point_1 = new Point(); + Polygon polygon_2 = new Polygon(); + point_1.setXY(0, 0); + + polygon_2.startPath(-1, -1); + polygon_2.lineTo(-1, 1); + polygon_2.lineTo(1, 1); + polygon_2.lineTo(1, -1); + Point differenced_1 = (Point) (difference.execute(point_1, + polygon_2, SpatialReference.create(4326), null)); + assertTrue(differenced_1.isEmpty()); + + polygon_2.setEmpty(); + polygon_2.startPath(1, 1); + polygon_2.lineTo(1, 2); + polygon_2.lineTo(2, 2); + polygon_2.lineTo(2, 1); + differenced_1 = (Point) (difference.execute(point_1, polygon_2, + SpatialReference.create(4326), null)); + assertTrue(!differenced_1.isEmpty()); + assertTrue(differenced_1 == point_1); + } + + {// point/polygon + Point point_1 = new Point(); + Polygon polygon_2 = new Polygon(); + point_1.setXY(0, 0); + + polygon_2.startPath(1, 0); + polygon_2.lineTo(0, 1); + polygon_2.lineTo(1, 1); + Point differenced_1 = (Point) (difference.execute(point_1, + polygon_2, SpatialReference.create(4326), null)); + assertTrue(!differenced_1.isEmpty()); + assertTrue(differenced_1 == point_1); + + point_1.setEmpty(); + point_1.setXY(0.5, 0.5); + + polygon_2.setEmpty(); + polygon_2.startPath(1, 0); + polygon_2.lineTo(0, 1); + polygon_2.lineTo(1, 1); + differenced_1 = (Point) (difference.execute(point_1, polygon_2, + SpatialReference.create(4326), null)); + assertTrue(differenced_1.isEmpty()); + } + + {// point/envelope + Point point_1 = new Point(); + Envelope envelope_2 = new Envelope(); + point_1.setXY(0, 0); + + envelope_2.setCoords(-1, -1, 1, 1); + Point differenced_1 = (Point) (difference.execute(point_1, + envelope_2, SpatialReference.create(4326), null)); + assertTrue(differenced_1.isEmpty()); + + envelope_2.setEmpty(); + envelope_2.setCoords(1, 1, 2, 2); + differenced_1 = (Point) (difference.execute(point_1, envelope_2, + SpatialReference.create(4326), null)); + assertTrue(!differenced_1.isEmpty()); + assertTrue(differenced_1 == point_1); + } + + {// point/polyline + Point point_1 = new Point(); + Polyline polyline_2 = new Polyline(); + point_1.setXY(0, 0); + + polyline_2.startPath(-1, 0); + polyline_2.lineTo(1, 0); + Point differenced_1 = (Point) (difference.execute(point_1, + polyline_2, SpatialReference.create(4326), null)); + assertTrue(differenced_1.isEmpty()); + + polyline_2.setEmpty(); + polyline_2.startPath(1, 0); + polyline_2.lineTo(2, 0); + differenced_1 = (Point) (difference.execute(point_1, polyline_2, + SpatialReference.create(4326), null)); + assertTrue(!differenced_1.isEmpty()); + assertTrue(differenced_1 == point_1); + + polyline_2.setEmpty(); + polyline_2.startPath(-1, -1); + polyline_2.lineTo(-1, 1); + polyline_2.lineTo(1, 1); + polyline_2.lineTo(1, -1); + differenced_1 = (Point) (difference.execute(point_1, polyline_2, + SpatialReference.create(4326), null)); + assertTrue(!differenced_1.isEmpty()); + assertTrue(differenced_1 == point_1); + } + } + + @Test + public static void testDifferenceOnPolyline() { + // # * * # + // # * @ + // # @ * + // # * + // + // /////////////////////////////// + // + // The polyline drawn in *s represents basePl + // The polyline drawn in #s represents compPl + // The @ represents their intersection points, so that + // the difference polyline will be basePl with two new vertices @ added. + + Polyline basePl = new Polyline(); + basePl.startPath(new Point(-117, 20)); + basePl.lineTo(new Point(-130, 10)); + basePl.lineTo(new Point(-120, 50)); + + Polyline compPl = new Polyline(); + compPl.startPath(new Point(-116, 20)); + compPl.lineTo(new Point(-131, 10)); + compPl.lineTo(new Point(-121, 50)); + + Geometry diffGeom = GeometryEngine.difference(basePl, compPl, + SpatialReference.create(4326)); + assertTrue(diffGeom instanceof Polyline); + Polyline diffPolyline = (Polyline) diffGeom; + int pointCountDiffPolyline = diffPolyline.getPointCount(); + + // first line in comp_pl is 3y = 2x + 292 + assertEquals(3 * 20, 2 * (-116) + 292); + assertEquals(3 * 10, 2 * (-131) + 292); + + // new points should also lie on this line + assertTrue(3.0 * diffPolyline.getCoordinates2D()[1].y - 2.0 + * diffPolyline.getCoordinates2D()[1].x - 292.0 == 0.0); + assertTrue(3.0 * diffPolyline.getCoordinates2D()[3].y - 2.0 + * diffPolyline.getCoordinates2D()[3].x - 292.0 == 0.0); + + for (int i = 0; i < 3; i++) { + assertTrue(basePl.getCoordinates2D()[i].x == diffPolyline + .getCoordinates2D()[2 * i].x); + assertTrue(basePl.getCoordinates2D()[i].y == diffPolyline + .getCoordinates2D()[2 * i].y); + } + + assertEquals(5, pointCountDiffPolyline); + } + + public static Polygon makePolygon1() { + Polygon poly = new Polygon(); + poly.startPath(0, 0); + poly.lineTo(0, 10); + poly.lineTo(10, 10); + poly.lineTo(10, 0); + + return poly; + } + + public static Polygon makePolygon2() { + Polygon poly = new Polygon(); + + poly.startPath(5, 5); + poly.lineTo(5, 15); + poly.lineTo(15, 15); + poly.lineTo(15, 5); + + return poly; + } + + public static Polyline makePolyline1() { + Polyline poly = new Polyline(); + poly.startPath(0, 0); + poly.lineTo(15, 15); + + return poly; + } + + public static MultiPoint makeMultiPoint1() { + MultiPoint mpoint = new MultiPoint(); + Point2D pt1 = new Point2D(); + pt1.x = 1.0; + pt1.y = 1.0; + + Point2D pt2 = new Point2D(); + pt2.x = 5.0; + pt2.y = 5.0; + + Point2D pt3 = new Point2D(); + pt3.x = 15.0; + pt3.y = 15.0; + + mpoint.add(pt1.x, pt1.y); + mpoint.add(pt2.x, pt2.y); + mpoint.add(pt3.x, pt3.y); + + return mpoint; + } + + public static MultiPoint makeMultiPoint2() { + MultiPoint mpoint = new MultiPoint(); + Point2D pt1 = new Point2D(); + pt1.x = 1.0; + pt1.y = 1.0; + + Point2D pt2 = new Point2D(); + pt2.x = 1.0; + pt2.y = 1.0; + + Point2D pt3 = new Point2D(); + pt3.x = 15.0; + pt3.y = 15.0; + + Point2D pt4 = new Point2D(); + pt4.x = 15.0; + pt4.y = 15.0; + + Point2D pt5 = new Point2D(); + pt5.x = 1.0; + pt5.y = 1.0; + + Point2D pt6 = new Point2D(); + pt6.x = 1.0; + pt6.y = 1.0; + + Point2D pt7 = new Point2D(); + pt7.x = 15.0; + pt7.y = 15.0; + + Point2D pt8 = new Point2D(); + pt8.x = 15.0; + pt8.y = 15.0; + + Point2D pt9 = new Point2D(); + pt9.x = 15.0; + pt9.y = 15.0; + + Point2D pt10 = new Point2D(); + pt10.x = 1.0; + pt10.y = 1.0; + + Point2D pt11 = new Point2D(); + pt11.x = 15.0; + pt11.y = 15.0; + + mpoint.add(pt1.x, pt1.y); + mpoint.add(pt2.x, pt2.y); + mpoint.add(pt3.x, pt3.y); + mpoint.add(pt4.x, pt4.y); + mpoint.add(pt5.x, pt5.y); + mpoint.add(pt6.x, pt6.y); + mpoint.add(pt7.x, pt7.y); + mpoint.add(pt8.x, pt8.y); + mpoint.add(pt9.x, pt9.y); + mpoint.add(pt10.x, pt10.y); + mpoint.add(pt11.x, pt11.y); + + return mpoint; + } + + public static MultiPoint makeMultiPoint3() { + MultiPoint mpoint = new MultiPoint(); + Point2D pt1 = new Point2D(); + pt1.x = 1.0; + pt1.y = 1.0; + + Point2D pt2 = new Point2D(); + pt2.x = 5.0; + pt2.y = 5.0; + + mpoint.add(pt1.x, pt1.y); + mpoint.add(pt2.x, pt2.y); + + return mpoint; + } + + public static Point makePoint1() { + Point point = new Point(); + + Point2D pt = new Point2D(); + pt.setCoords(15, 15); + point.setXY(pt); + + return point; + } + + public static Point makePoint2() { + Point point = new Point(); + + Point2D pt = new Point2D(); + pt.setCoords(7, 7); + point.setXY(pt); + + return point; + } + + public static Envelope makeEnvelope1() { + Envelope2D env = new Envelope2D(); + env.setCoords(5, 5, 15, 15); + Envelope envelope = new Envelope(env); + + return envelope; + } + + public static Envelope makeEnvelope2() { + Envelope2D env = new Envelope2D(); + env.setCoords(0, 0, 10, 10); + Envelope envelope = new Envelope(env); - return envelope; - } - - public static Envelope makeEnvelope3() { - Envelope2D env = new Envelope2D(); - env.setCoords(5, 5, 6, 6); - Envelope envelope = new Envelope(env); + return envelope; + } + + public static Envelope makeEnvelope3() { + Envelope2D env = new Envelope2D(); + env.setCoords(5, 5, 6, 6); + Envelope envelope = new Envelope(env); - return envelope; - } + return envelope; + } } diff --git a/src/test/java/com/esri/core/geometry/TestDistance.java b/src/test/java/com/esri/core/geometry/TestDistance.java index 9d47f276..06312db8 100644 --- a/src/test/java/com/esri/core/geometry/TestDistance.java +++ b/src/test/java/com/esri/core/geometry/TestDistance.java @@ -28,155 +28,155 @@ import org.junit.Test; public class TestDistance extends TestCase { - @Override - protected void setUp() throws Exception { - super.setUp(); - } - - @Override - protected void tearDown() throws Exception { - super.tearDown(); - } - - @Test - public static void testDistanceBetweenVariousGeometries() { - Polygon polygon = makePolygon(); - Polyline polyline = makePolyline(); - MultiPoint multipoint = makeMultiPoint(); - Point point = makePoint(); - // SpatialReference spatialRef = - // SpatialReference.create(3857);//PCS_WGS_1984_WEB_MERCATOR_AUXSPHERE - - double distance; - - distance = GeometryEngine.distance(polygon, polyline, null); - assertTrue(Math.abs(distance - 5.0) < 0.00001); - - distance = GeometryEngine.distance(polygon, multipoint, null); - assertTrue(Math.abs(distance - 5.0) < 0.00001); - - distance = GeometryEngine.distance(polygon, point, null); - assertTrue(Math.abs(distance - 5.0) < 0.00001); - } - - @Test - public static void testDistanceBetweenTriangles() { - double distance; - - Polygon poly = new Polygon(); - Polygon poly2 = new Polygon(); - - poly.startPath(0.0, 0.0); - poly.lineTo(1.0, 2.0); - poly.lineTo(0.0, 2.0); - - double xSeparation = 0.1; - double ySeparation = 0.1; - - poly2.startPath(xSeparation + 1.0, 2.0 - ySeparation); - poly2.lineTo(xSeparation + 2.0, 2.0 - ySeparation); - poly2.lineTo(xSeparation + 2.0, 4.0 - ySeparation); - - distance = GeometryEngine.distance(poly, poly2, null); - - assertTrue(0.0 < distance && distance < xSeparation + ySeparation); - } - - @Test - public static void testDistanceBetweenPointAndEnvelope() { - Envelope env = new Envelope(23, 23, 23, 23); - Point pt = new Point(30, 30); - double dist = GeometryEngine.distance(env, pt, null); // expect just under 10. - assertTrue(Math.abs(dist - 9.8994949) < 0.0001); - } - - @Test - public static void testDistanceBetweenHugeGeometries() { - /* const */ - int N = 1000; // Should be even + @Override + protected void setUp() throws Exception { + super.setUp(); + } + + @Override + protected void tearDown() throws Exception { + super.tearDown(); + } + + @Test + public static void testDistanceBetweenVariousGeometries() { + Polygon polygon = makePolygon(); + Polyline polyline = makePolyline(); + MultiPoint multipoint = makeMultiPoint(); + Point point = makePoint(); + // SpatialReference spatialRef = + // SpatialReference.create(3857);//PCS_WGS_1984_WEB_MERCATOR_AUXSPHERE + + double distance; + + distance = GeometryEngine.distance(polygon, polyline, null); + assertTrue(Math.abs(distance - 5.0) < 0.00001); + + distance = GeometryEngine.distance(polygon, multipoint, null); + assertTrue(Math.abs(distance - 5.0) < 0.00001); + + distance = GeometryEngine.distance(polygon, point, null); + assertTrue(Math.abs(distance - 5.0) < 0.00001); + } + + @Test + public static void testDistanceBetweenTriangles() { + double distance; + + Polygon poly = new Polygon(); + Polygon poly2 = new Polygon(); + + poly.startPath(0.0, 0.0); + poly.lineTo(1.0, 2.0); + poly.lineTo(0.0, 2.0); + + double xSeparation = 0.1; + double ySeparation = 0.1; + + poly2.startPath(xSeparation + 1.0, 2.0 - ySeparation); + poly2.lineTo(xSeparation + 2.0, 2.0 - ySeparation); + poly2.lineTo(xSeparation + 2.0, 4.0 - ySeparation); + + distance = GeometryEngine.distance(poly, poly2, null); + + assertTrue(0.0 < distance && distance < xSeparation + ySeparation); + } + + @Test + public static void testDistanceBetweenPointAndEnvelope() { + Envelope env = new Envelope(23, 23, 23, 23); + Point pt = new Point(30, 30); + double dist = GeometryEngine.distance(env, pt, null); // expect just under 10. + assertTrue(Math.abs(dist - 9.8994949) < 0.0001); + } + + @Test + public static void testDistanceBetweenHugeGeometries() { /* const */ - double theoreticalDistance = 0.77; - - Polygon poly = new Polygon(); - Polygon poly2 = new Polygon(); - - double theta = 0.0; - double thetaPlusPi = Math.PI; - double dTheta = 2.0 * Math.PI / N; - double distance; - - poly.startPath(Math.cos(theta), Math.sin(theta)); - // Add something so that poly2's bounding box is in poly's. Deleting - // this should not affect answer. - poly.lineTo(1.0, 1.5 + theoreticalDistance); - poly.lineTo(3.5 + theoreticalDistance, 1.5 + theoreticalDistance); - poly.lineTo(3.5 + theoreticalDistance, 2.0 + theoreticalDistance); - poly.lineTo(0.95, 2.0 + theoreticalDistance); - // /////////////////////////////////////////////////////////// - poly2.startPath(2.0 + theoreticalDistance + Math.cos(thetaPlusPi), - Math.sin(thetaPlusPi)); - for (double i = 1; i < N; i++) { - theta += dTheta; - thetaPlusPi += dTheta; - poly.lineTo(Math.cos(theta), Math.sin(theta)); - poly2.lineTo(2.0 + theoreticalDistance + Math.cos(thetaPlusPi), - Math.sin(thetaPlusPi)); - } - - distance = GeometryEngine.distance(poly, poly2, null); - - assertTrue(Math.abs(distance - theoreticalDistance) < 1.0e-10); - } - - private static Polygon makePolygon() { - Polygon poly = new Polygon(); - - poly.startPath(0, 0); - poly.lineTo(0, 10); - poly.lineTo(10, 10); - poly.lineTo(10, 0); - - poly.startPath(3, 3); - poly.lineTo(7, 3); - poly.lineTo(7, 7); - poly.lineTo(3, 7); - - return poly; - } - - private static Polyline makePolyline() { - Polyline poly = new Polyline(); - poly.startPath(0, 15); - poly.lineTo(15, 15); - return poly; - } - - private static MultiPoint makeMultiPoint() { - MultiPoint mpoint = new MultiPoint(); - mpoint.add(0, 30); - mpoint.add(15, 15); - mpoint.add(0, 15); - return mpoint; - } - - private static Point makePoint() { - Point point = new Point(); - Point2D pt = new Point2D(); - pt.setCoords(0, 15); - point.setXY(pt); - return point; - } - - @Test - public static void testDistanceWithNullSpatialReference() { - // There was a bug that distance op did not work with null Spatial - // Reference. - String str1 = "{\"paths\":[[[-117.138791850991,34.017492675023],[-117.138762336971,34.0174925550462]]]}"; - String str2 = "{\"paths\":[[[-117.138867827972,34.0174854109623],[-117.138850197027,34.0174929160126],[-117.138791850991,34.017492675023]]]}"; - MapGeometry geom1 = GeometryEngine.jsonToGeometry(JsonParserReader.createFromString(str1)); - MapGeometry geom2 = GeometryEngine.jsonToGeometry(JsonParserReader.createFromString(str2)); - double distance = GeometryEngine.distance(geom1.getGeometry(), - geom2.getGeometry(), null); - assertTrue(distance == 0); - } + int N = 1000; // Should be even + /* const */ + double theoreticalDistance = 0.77; + + Polygon poly = new Polygon(); + Polygon poly2 = new Polygon(); + + double theta = 0.0; + double thetaPlusPi = Math.PI; + double dTheta = 2.0 * Math.PI / N; + double distance; + + poly.startPath(Math.cos(theta), Math.sin(theta)); + // Add something so that poly2's bounding box is in poly's. Deleting + // this should not affect answer. + poly.lineTo(1.0, 1.5 + theoreticalDistance); + poly.lineTo(3.5 + theoreticalDistance, 1.5 + theoreticalDistance); + poly.lineTo(3.5 + theoreticalDistance, 2.0 + theoreticalDistance); + poly.lineTo(0.95, 2.0 + theoreticalDistance); + // /////////////////////////////////////////////////////////// + poly2.startPath(2.0 + theoreticalDistance + Math.cos(thetaPlusPi), + Math.sin(thetaPlusPi)); + for (double i = 1; i < N; i++) { + theta += dTheta; + thetaPlusPi += dTheta; + poly.lineTo(Math.cos(theta), Math.sin(theta)); + poly2.lineTo(2.0 + theoreticalDistance + Math.cos(thetaPlusPi), + Math.sin(thetaPlusPi)); + } + + distance = GeometryEngine.distance(poly, poly2, null); + + assertTrue(Math.abs(distance - theoreticalDistance) < 1.0e-10); + } + + private static Polygon makePolygon() { + Polygon poly = new Polygon(); + + poly.startPath(0, 0); + poly.lineTo(0, 10); + poly.lineTo(10, 10); + poly.lineTo(10, 0); + + poly.startPath(3, 3); + poly.lineTo(7, 3); + poly.lineTo(7, 7); + poly.lineTo(3, 7); + + return poly; + } + + private static Polyline makePolyline() { + Polyline poly = new Polyline(); + poly.startPath(0, 15); + poly.lineTo(15, 15); + return poly; + } + + private static MultiPoint makeMultiPoint() { + MultiPoint mpoint = new MultiPoint(); + mpoint.add(0, 30); + mpoint.add(15, 15); + mpoint.add(0, 15); + return mpoint; + } + + private static Point makePoint() { + Point point = new Point(); + Point2D pt = new Point2D(); + pt.setCoords(0, 15); + point.setXY(pt); + return point; + } + + @Test + public static void testDistanceWithNullSpatialReference() { + // There was a bug that distance op did not work with null Spatial + // Reference. + String str1 = "{\"paths\":[[[-117.138791850991,34.017492675023],[-117.138762336971,34.0174925550462]]]}"; + String str2 = "{\"paths\":[[[-117.138867827972,34.0174854109623],[-117.138850197027,34.0174929160126],[-117.138791850991,34.017492675023]]]}"; + MapGeometry geom1 = GeometryEngine.jsonToGeometry(JsonParserReader.createFromString(str1)); + MapGeometry geom2 = GeometryEngine.jsonToGeometry(JsonParserReader.createFromString(str2)); + double distance = GeometryEngine.distance(geom1.getGeometry(), + geom2.getGeometry(), null); + assertTrue(distance == 0); + } } diff --git a/src/test/java/com/esri/core/geometry/TestEditShape.java b/src/test/java/com/esri/core/geometry/TestEditShape.java index a95e1ed1..95b5ea30 100644 --- a/src/test/java/com/esri/core/geometry/TestEditShape.java +++ b/src/test/java/com/esri/core/geometry/TestEditShape.java @@ -28,392 +28,392 @@ import org.junit.Test; public class TestEditShape extends TestCase { - @Override - protected void setUp() throws Exception { - super.setUp(); - } - - @Override - protected void tearDown() throws Exception { - super.tearDown(); - } - - @Test - public static void testEditShape() { - { - // Single part polygon - Polygon poly = new Polygon(); - poly.startPath(10, 10); - poly.lineTo(10, 12); - poly.lineTo(14, 15); - poly.lineTo(10, 11); - EditShape editShape = new EditShape(); - int geom = editShape.addGeometry(poly); - Polygon poly2 = (Polygon) editShape.getGeometry(geom); - assertTrue(poly.equals(poly2)); - } - - { - // Two part poly - Polygon poly = new Polygon(); - poly.startPath(10, 10); - poly.lineTo(10, 12); - poly.lineTo(14, 15); - poly.lineTo(10, 11); - - poly.startPath(100, 10); - poly.lineTo(100, 12); - poly.lineTo(14, 150); - poly.lineTo(10, 101); - poly.lineTo(100, 11); - - EditShape editShape = new EditShape(); - int geom = editShape.addGeometry(poly); - Polygon poly2 = (Polygon) editShape.getGeometry(geom); - assertTrue(poly.equals(poly2)); - } - - { - // Single part polyline - Polyline poly = new Polyline(); - poly.startPath(10, 10); - poly.lineTo(10, 12); - poly.lineTo(14, 15); - poly.lineTo(10, 11); - EditShape editShape = new EditShape(); - int geom = editShape.addGeometry(poly); - Polyline poly2 = (Polyline) editShape.getGeometry(geom); - assertTrue(poly.equals(poly2)); - } - - { - // Two part poly - Polyline poly = new Polyline(); - poly.startPath(10, 10); - poly.lineTo(10, 12); - poly.lineTo(14, 15); - poly.lineTo(10, 11); - - poly.startPath(100, 10); - poly.lineTo(100, 12); - poly.lineTo(14, 150); - poly.lineTo(10, 101); - poly.lineTo(100, 11); - - EditShape editShape = new EditShape(); - int geom = editShape.addGeometry(poly); - Polyline poly2 = (Polyline) editShape.getGeometry(geom); - assertTrue(poly.equals(poly2)); - } - - { - // Five part poly. Close one of parts to test if it works. - Polyline poly = new Polyline(); - poly.startPath(10, 10); - poly.lineTo(10, 12); - poly.lineTo(14, 15); - poly.lineTo(10, 11); - - poly.startPath(100, 10); - poly.lineTo(100, 12); - poly.lineTo(14, 150); - poly.lineTo(10, 101); - poly.lineTo(100, 11); - - poly.startPath(1100, 101); - poly.lineTo(1300, 132); - poly.lineTo(144, 150); - poly.lineTo(106, 1051); - poly.lineTo(1600, 161); - - poly.startPath(100, 190); - poly.lineTo(1800, 192); - poly.lineTo(184, 8150); - poly.lineTo(1080, 181); - - poly.startPath(1030, 10); - poly.lineTo(1300, 132); - poly.lineTo(314, 3150); - poly.lineTo(310, 1301); - poly.lineTo(3100, 311); - - EditShape editShape = new EditShape(); - int geom = editShape.addGeometry(poly); - editShape.setClosedPath( - editShape.getNextPath(editShape.getFirstPath(geom)), true); - ((MultiPathImpl) poly._getImpl()).closePathWithLine(1); - Polyline poly2 = (Polyline) editShape.getGeometry(geom); - assertTrue(poly.equals(poly2)); - } - - { - // Test erase - Polyline poly = new Polyline(); - poly.startPath(10, 10); - poly.lineTo(10, 12); - poly.lineTo(314, 3150); - poly.lineTo(310, 1301); - poly.lineTo(3100, 311); - - EditShape editShape = new EditShape(); - int geom = editShape.addGeometry(poly); - int vertex = editShape.getFirstVertex(editShape.getFirstPath(geom)); - vertex = editShape.removeVertex(vertex, true); - vertex = editShape.getNextVertex(vertex); - editShape.removeVertex(vertex, true); - Polyline poly2 = (Polyline) editShape.getGeometry(geom); - - poly.setEmpty(); - poly.startPath(10, 12); - poly.lineTo(310, 1301); - poly.lineTo(3100, 311); - - assertTrue(poly.equals(poly2)); - } - - { - // Test erase - Polygon poly = new Polygon(); - poly.startPath(10, 10); - poly.lineTo(10, 12); - poly.lineTo(314, 3150); - poly.lineTo(310, 1301); - poly.lineTo(3100, 311); - - EditShape editShape = new EditShape(); - int geom = editShape.addGeometry(poly); - int vertex = editShape.getFirstVertex(editShape.getFirstPath(geom)); - vertex = editShape.removeVertex(vertex, true); - vertex = editShape.getNextVertex(vertex); - editShape.removeVertex(vertex, true); - Polygon poly2 = (Polygon) editShape.getGeometry(geom); - - poly.setEmpty(); - poly.startPath(10, 12); - poly.lineTo(310, 1301); - poly.lineTo(3100, 311); - - assertTrue(poly.equals(poly2)); - } - - { - // Test Filter Close Points - Polygon poly = new Polygon(); - poly.startPath(10, 10); - poly.lineTo(10, 10.001); - poly.lineTo(10.001, 10); - - EditShape editShape = new EditShape(); - int geom = editShape.addGeometry(poly); - editShape.filterClosePoints(0.002, true, false); - Polygon poly2 = (Polygon) editShape.getGeometry(geom); - assertTrue(poly2.isEmpty()); - } - - { - // Test Filter Close Points - Polygon poly = new Polygon(); - poly.startPath(10, 10); - poly.lineTo(10, 10.0025); - poly.lineTo(11.0, 10); - - EditShape editShape = new EditShape(); - int geom = editShape.addGeometry(poly); - editShape.filterClosePoints(0.002, true, false); - Polygon poly2 = (Polygon) editShape.getGeometry(geom); - assertTrue(!poly2.isEmpty()); - } - - { - // Test Filter Close Points - Polygon poly = new Polygon(); - poly.startPath(10, 10); - poly.lineTo(10, 10.001); - poly.lineTo(11.0, 10); - - EditShape editShape = new EditShape(); - int geom = editShape.addGeometry(poly); - editShape.filterClosePoints(0.002, true, false); - Polygon poly2 = (Polygon) editShape.getGeometry(geom); - assertTrue(poly2.isEmpty()); - } - - { - // Test attribute splitting 1 - Polyline polyline = new Polyline(); - polyline.startPath(0, 0); - polyline.lineTo(1, 1); - polyline.lineTo(2, 2); - polyline.lineTo(3, 3); - polyline.lineTo(4, 4); - - polyline.startPath(5, 5); - polyline.lineTo(6, 6); - polyline.lineTo(7, 7); - polyline.lineTo(8, 8); - polyline.lineTo(9, 9); - - polyline.addAttribute(VertexDescription.Semantics.Z); - polyline.setAttribute(VertexDescription.Semantics.Z, 0, 0, 4); - polyline.setAttribute(VertexDescription.Semantics.Z, 1, 0, 8); - polyline.setAttribute(VertexDescription.Semantics.Z, 2, 0, 12); - polyline.setAttribute(VertexDescription.Semantics.Z, 3, 0, 16); - polyline.setAttribute(VertexDescription.Semantics.Z, 4, 0, 20); - - polyline.setAttribute(VertexDescription.Semantics.Z, 5, 0, 22); - polyline.setAttribute(VertexDescription.Semantics.Z, 6, 0, 26); - polyline.setAttribute(VertexDescription.Semantics.Z, 7, 0, 30); - polyline.setAttribute(VertexDescription.Semantics.Z, 8, 0, 34); - polyline.setAttribute(VertexDescription.Semantics.Z, 9, 0, 38); - - EditShape shape = new EditShape(); - int geometry = shape.addGeometry(polyline); - - AttributeStreamOfInt32 vertex_handles = new AttributeStreamOfInt32( - 0); - - for (int path = shape.getFirstPath(geometry); path != -1; path = shape - .getNextPath(path)) { - for (int vertex = shape.getFirstVertex(path); vertex != -1; vertex = shape - .getNextVertex(vertex)) { - if (vertex != shape.getLastVertex(path)) - vertex_handles.add(vertex); - } - } - - double[] t = new double[1]; - for (int i = 0; i < vertex_handles.size(); i++) { - int vertex = vertex_handles.read(i); - t[0] = 0.5; - shape.splitSegment(vertex, t, 1); - } - - Polyline chopped_polyline = (Polyline) shape.getGeometry(geometry); - assertTrue(chopped_polyline.getPointCount() == 18); - - double att_ = 4; - for (int i = 0; i < 18; i++) { - double att = chopped_polyline.getAttributeAsDbl( - VertexDescription.Semantics.Z, i, 0); - assertTrue(att == att_); - att_ += 2; - } - - } - - { // Test attribute splitting 2 - Polyline line1 = new Polyline(), line2 = new Polyline(); - line1.addAttribute(VertexDescription.Semantics.M); - line2.addAttribute(VertexDescription.Semantics.M); - line1.startPath(0, 0); - line1.lineTo(10, 10); - line2.startPath(10, 0); - line2.lineTo(0, 10); - line1.setAttribute(VertexDescription.Semantics.M, 0, 0, 7); - line1.setAttribute(VertexDescription.Semantics.M, 1, 0, 17); - line2.setAttribute(VertexDescription.Semantics.M, 0, 0, 5); - line2.setAttribute(VertexDescription.Semantics.M, 1, 0, 15); - - EditShape shape = new EditShape(); - int g1 = shape.addGeometry(line1); - int g2 = shape.addGeometry(line2); - CrackAndCluster.execute(shape, 0.001, null, true); - - Polyline chopped_line1 = (Polyline) shape.getGeometry(g1); - Polyline chopped_line2 = (Polyline) shape.getGeometry(g2); - - double att1 = chopped_line1.getAttributeAsDbl( - VertexDescription.Semantics.M, 1, 0); - double att2 = chopped_line2.getAttributeAsDbl( - VertexDescription.Semantics.M, 1, 0); - assertTrue(att1 == 12); - assertTrue(att2 == 10); - } - - { // Test attribute splitting 3 - Polygon polygon = new Polygon(); - polygon.addAttribute(VertexDescription.Semantics.M); - polygon.startPath(0, 0); - polygon.lineTo(0, 10); - polygon.lineTo(10, 10); - polygon.lineTo(10, 0); - - polygon.setAttribute(VertexDescription.Semantics.M, 0, 0, 7); - polygon.setAttribute(VertexDescription.Semantics.M, 1, 0, 17); - polygon.setAttribute(VertexDescription.Semantics.M, 2, 0, 23); - polygon.setAttribute(VertexDescription.Semantics.M, 3, 0, 43); - - EditShape shape = new EditShape(); - int geometry = shape.addGeometry(polygon); - - AttributeStreamOfInt32 vertex_handles = new AttributeStreamOfInt32( - 0); - - int start_v = shape.getFirstVertex(shape.getFirstPath(geometry)); - int v = start_v; - - do { - vertex_handles.add(v); - v = shape.getNextVertex(v); - } while (v != start_v); - - double[] t = new double[1]; - for (int i = 0; i < vertex_handles.size(); i++) { - int v1 = vertex_handles.read(i); - t[0] = 0.5; - shape.splitSegment(v1, t, 1); - } - - Polygon cut_polygon = (Polygon) shape.getGeometry(geometry); - assertTrue(cut_polygon.getPointCount() == 8); - - @SuppressWarnings("unused") - Point2D pt0 = cut_polygon.getXY(0); - double a0 = cut_polygon.getAttributeAsDbl( - VertexDescription.Semantics.M, 0, 0); - assertTrue(a0 == 25); - - @SuppressWarnings("unused") - Point2D pt1 = cut_polygon.getXY(1); - double a1 = cut_polygon.getAttributeAsDbl( - VertexDescription.Semantics.M, 1, 0); - assertTrue(a1 == 7); - - @SuppressWarnings("unused") - Point2D pt2 = cut_polygon.getXY(2); - double a2 = cut_polygon.getAttributeAsDbl( - VertexDescription.Semantics.M, 2, 0); - assertTrue(a2 == 12); - - @SuppressWarnings("unused") - Point2D pt3 = cut_polygon.getXY(3); - double a3 = cut_polygon.getAttributeAsDbl( - VertexDescription.Semantics.M, 3, 0); - assertTrue(a3 == 17); - - @SuppressWarnings("unused") - Point2D pt4 = cut_polygon.getXY(4); - double a4 = cut_polygon.getAttributeAsDbl( - VertexDescription.Semantics.M, 4, 0); - assertTrue(a4 == 20); - - @SuppressWarnings("unused") - Point2D pt5 = cut_polygon.getXY(5); - double a5 = cut_polygon.getAttributeAsDbl( - VertexDescription.Semantics.M, 5, 0); - assertTrue(a5 == 23); - - @SuppressWarnings("unused") - Point2D pt6 = cut_polygon.getXY(6); - double a6 = cut_polygon.getAttributeAsDbl( - VertexDescription.Semantics.M, 6, 0); - assertTrue(a6 == 33); - - @SuppressWarnings("unused") - Point2D pt7 = cut_polygon.getXY(7); - double a7 = cut_polygon.getAttributeAsDbl( - VertexDescription.Semantics.M, 7, 0); - assertTrue(a7 == 43); - } - } + @Override + protected void setUp() throws Exception { + super.setUp(); + } + + @Override + protected void tearDown() throws Exception { + super.tearDown(); + } + + @Test + public static void testEditShape() { + { + // Single part polygon + Polygon poly = new Polygon(); + poly.startPath(10, 10); + poly.lineTo(10, 12); + poly.lineTo(14, 15); + poly.lineTo(10, 11); + EditShape editShape = new EditShape(); + int geom = editShape.addGeometry(poly); + Polygon poly2 = (Polygon) editShape.getGeometry(geom); + assertTrue(poly.equals(poly2)); + } + + { + // Two part poly + Polygon poly = new Polygon(); + poly.startPath(10, 10); + poly.lineTo(10, 12); + poly.lineTo(14, 15); + poly.lineTo(10, 11); + + poly.startPath(100, 10); + poly.lineTo(100, 12); + poly.lineTo(14, 150); + poly.lineTo(10, 101); + poly.lineTo(100, 11); + + EditShape editShape = new EditShape(); + int geom = editShape.addGeometry(poly); + Polygon poly2 = (Polygon) editShape.getGeometry(geom); + assertTrue(poly.equals(poly2)); + } + + { + // Single part polyline + Polyline poly = new Polyline(); + poly.startPath(10, 10); + poly.lineTo(10, 12); + poly.lineTo(14, 15); + poly.lineTo(10, 11); + EditShape editShape = new EditShape(); + int geom = editShape.addGeometry(poly); + Polyline poly2 = (Polyline) editShape.getGeometry(geom); + assertTrue(poly.equals(poly2)); + } + + { + // Two part poly + Polyline poly = new Polyline(); + poly.startPath(10, 10); + poly.lineTo(10, 12); + poly.lineTo(14, 15); + poly.lineTo(10, 11); + + poly.startPath(100, 10); + poly.lineTo(100, 12); + poly.lineTo(14, 150); + poly.lineTo(10, 101); + poly.lineTo(100, 11); + + EditShape editShape = new EditShape(); + int geom = editShape.addGeometry(poly); + Polyline poly2 = (Polyline) editShape.getGeometry(geom); + assertTrue(poly.equals(poly2)); + } + + { + // Five part poly. Close one of parts to test if it works. + Polyline poly = new Polyline(); + poly.startPath(10, 10); + poly.lineTo(10, 12); + poly.lineTo(14, 15); + poly.lineTo(10, 11); + + poly.startPath(100, 10); + poly.lineTo(100, 12); + poly.lineTo(14, 150); + poly.lineTo(10, 101); + poly.lineTo(100, 11); + + poly.startPath(1100, 101); + poly.lineTo(1300, 132); + poly.lineTo(144, 150); + poly.lineTo(106, 1051); + poly.lineTo(1600, 161); + + poly.startPath(100, 190); + poly.lineTo(1800, 192); + poly.lineTo(184, 8150); + poly.lineTo(1080, 181); + + poly.startPath(1030, 10); + poly.lineTo(1300, 132); + poly.lineTo(314, 3150); + poly.lineTo(310, 1301); + poly.lineTo(3100, 311); + + EditShape editShape = new EditShape(); + int geom = editShape.addGeometry(poly); + editShape.setClosedPath( + editShape.getNextPath(editShape.getFirstPath(geom)), true); + ((MultiPathImpl) poly._getImpl()).closePathWithLine(1); + Polyline poly2 = (Polyline) editShape.getGeometry(geom); + assertTrue(poly.equals(poly2)); + } + + { + // Test erase + Polyline poly = new Polyline(); + poly.startPath(10, 10); + poly.lineTo(10, 12); + poly.lineTo(314, 3150); + poly.lineTo(310, 1301); + poly.lineTo(3100, 311); + + EditShape editShape = new EditShape(); + int geom = editShape.addGeometry(poly); + int vertex = editShape.getFirstVertex(editShape.getFirstPath(geom)); + vertex = editShape.removeVertex(vertex, true); + vertex = editShape.getNextVertex(vertex); + editShape.removeVertex(vertex, true); + Polyline poly2 = (Polyline) editShape.getGeometry(geom); + + poly.setEmpty(); + poly.startPath(10, 12); + poly.lineTo(310, 1301); + poly.lineTo(3100, 311); + + assertTrue(poly.equals(poly2)); + } + + { + // Test erase + Polygon poly = new Polygon(); + poly.startPath(10, 10); + poly.lineTo(10, 12); + poly.lineTo(314, 3150); + poly.lineTo(310, 1301); + poly.lineTo(3100, 311); + + EditShape editShape = new EditShape(); + int geom = editShape.addGeometry(poly); + int vertex = editShape.getFirstVertex(editShape.getFirstPath(geom)); + vertex = editShape.removeVertex(vertex, true); + vertex = editShape.getNextVertex(vertex); + editShape.removeVertex(vertex, true); + Polygon poly2 = (Polygon) editShape.getGeometry(geom); + + poly.setEmpty(); + poly.startPath(10, 12); + poly.lineTo(310, 1301); + poly.lineTo(3100, 311); + + assertTrue(poly.equals(poly2)); + } + + { + // Test Filter Close Points + Polygon poly = new Polygon(); + poly.startPath(10, 10); + poly.lineTo(10, 10.001); + poly.lineTo(10.001, 10); + + EditShape editShape = new EditShape(); + int geom = editShape.addGeometry(poly); + editShape.filterClosePoints(0.002, true, false); + Polygon poly2 = (Polygon) editShape.getGeometry(geom); + assertTrue(poly2.isEmpty()); + } + + { + // Test Filter Close Points + Polygon poly = new Polygon(); + poly.startPath(10, 10); + poly.lineTo(10, 10.0025); + poly.lineTo(11.0, 10); + + EditShape editShape = new EditShape(); + int geom = editShape.addGeometry(poly); + editShape.filterClosePoints(0.002, true, false); + Polygon poly2 = (Polygon) editShape.getGeometry(geom); + assertTrue(!poly2.isEmpty()); + } + + { + // Test Filter Close Points + Polygon poly = new Polygon(); + poly.startPath(10, 10); + poly.lineTo(10, 10.001); + poly.lineTo(11.0, 10); + + EditShape editShape = new EditShape(); + int geom = editShape.addGeometry(poly); + editShape.filterClosePoints(0.002, true, false); + Polygon poly2 = (Polygon) editShape.getGeometry(geom); + assertTrue(poly2.isEmpty()); + } + + { + // Test attribute splitting 1 + Polyline polyline = new Polyline(); + polyline.startPath(0, 0); + polyline.lineTo(1, 1); + polyline.lineTo(2, 2); + polyline.lineTo(3, 3); + polyline.lineTo(4, 4); + + polyline.startPath(5, 5); + polyline.lineTo(6, 6); + polyline.lineTo(7, 7); + polyline.lineTo(8, 8); + polyline.lineTo(9, 9); + + polyline.addAttribute(VertexDescription.Semantics.Z); + polyline.setAttribute(VertexDescription.Semantics.Z, 0, 0, 4); + polyline.setAttribute(VertexDescription.Semantics.Z, 1, 0, 8); + polyline.setAttribute(VertexDescription.Semantics.Z, 2, 0, 12); + polyline.setAttribute(VertexDescription.Semantics.Z, 3, 0, 16); + polyline.setAttribute(VertexDescription.Semantics.Z, 4, 0, 20); + + polyline.setAttribute(VertexDescription.Semantics.Z, 5, 0, 22); + polyline.setAttribute(VertexDescription.Semantics.Z, 6, 0, 26); + polyline.setAttribute(VertexDescription.Semantics.Z, 7, 0, 30); + polyline.setAttribute(VertexDescription.Semantics.Z, 8, 0, 34); + polyline.setAttribute(VertexDescription.Semantics.Z, 9, 0, 38); + + EditShape shape = new EditShape(); + int geometry = shape.addGeometry(polyline); + + AttributeStreamOfInt32 vertex_handles = new AttributeStreamOfInt32( + 0); + + for (int path = shape.getFirstPath(geometry); path != -1; path = shape + .getNextPath(path)) { + for (int vertex = shape.getFirstVertex(path); vertex != -1; vertex = shape + .getNextVertex(vertex)) { + if (vertex != shape.getLastVertex(path)) + vertex_handles.add(vertex); + } + } + + double[] t = new double[1]; + for (int i = 0; i < vertex_handles.size(); i++) { + int vertex = vertex_handles.read(i); + t[0] = 0.5; + shape.splitSegment(vertex, t, 1); + } + + Polyline chopped_polyline = (Polyline) shape.getGeometry(geometry); + assertTrue(chopped_polyline.getPointCount() == 18); + + double att_ = 4; + for (int i = 0; i < 18; i++) { + double att = chopped_polyline.getAttributeAsDbl( + VertexDescription.Semantics.Z, i, 0); + assertTrue(att == att_); + att_ += 2; + } + + } + + { // Test attribute splitting 2 + Polyline line1 = new Polyline(), line2 = new Polyline(); + line1.addAttribute(VertexDescription.Semantics.M); + line2.addAttribute(VertexDescription.Semantics.M); + line1.startPath(0, 0); + line1.lineTo(10, 10); + line2.startPath(10, 0); + line2.lineTo(0, 10); + line1.setAttribute(VertexDescription.Semantics.M, 0, 0, 7); + line1.setAttribute(VertexDescription.Semantics.M, 1, 0, 17); + line2.setAttribute(VertexDescription.Semantics.M, 0, 0, 5); + line2.setAttribute(VertexDescription.Semantics.M, 1, 0, 15); + + EditShape shape = new EditShape(); + int g1 = shape.addGeometry(line1); + int g2 = shape.addGeometry(line2); + CrackAndCluster.execute(shape, 0.001, null, true); + + Polyline chopped_line1 = (Polyline) shape.getGeometry(g1); + Polyline chopped_line2 = (Polyline) shape.getGeometry(g2); + + double att1 = chopped_line1.getAttributeAsDbl( + VertexDescription.Semantics.M, 1, 0); + double att2 = chopped_line2.getAttributeAsDbl( + VertexDescription.Semantics.M, 1, 0); + assertTrue(att1 == 12); + assertTrue(att2 == 10); + } + + { // Test attribute splitting 3 + Polygon polygon = new Polygon(); + polygon.addAttribute(VertexDescription.Semantics.M); + polygon.startPath(0, 0); + polygon.lineTo(0, 10); + polygon.lineTo(10, 10); + polygon.lineTo(10, 0); + + polygon.setAttribute(VertexDescription.Semantics.M, 0, 0, 7); + polygon.setAttribute(VertexDescription.Semantics.M, 1, 0, 17); + polygon.setAttribute(VertexDescription.Semantics.M, 2, 0, 23); + polygon.setAttribute(VertexDescription.Semantics.M, 3, 0, 43); + + EditShape shape = new EditShape(); + int geometry = shape.addGeometry(polygon); + + AttributeStreamOfInt32 vertex_handles = new AttributeStreamOfInt32( + 0); + + int start_v = shape.getFirstVertex(shape.getFirstPath(geometry)); + int v = start_v; + + do { + vertex_handles.add(v); + v = shape.getNextVertex(v); + } while (v != start_v); + + double[] t = new double[1]; + for (int i = 0; i < vertex_handles.size(); i++) { + int v1 = vertex_handles.read(i); + t[0] = 0.5; + shape.splitSegment(v1, t, 1); + } + + Polygon cut_polygon = (Polygon) shape.getGeometry(geometry); + assertTrue(cut_polygon.getPointCount() == 8); + + @SuppressWarnings("unused") + Point2D pt0 = cut_polygon.getXY(0); + double a0 = cut_polygon.getAttributeAsDbl( + VertexDescription.Semantics.M, 0, 0); + assertTrue(a0 == 25); + + @SuppressWarnings("unused") + Point2D pt1 = cut_polygon.getXY(1); + double a1 = cut_polygon.getAttributeAsDbl( + VertexDescription.Semantics.M, 1, 0); + assertTrue(a1 == 7); + + @SuppressWarnings("unused") + Point2D pt2 = cut_polygon.getXY(2); + double a2 = cut_polygon.getAttributeAsDbl( + VertexDescription.Semantics.M, 2, 0); + assertTrue(a2 == 12); + + @SuppressWarnings("unused") + Point2D pt3 = cut_polygon.getXY(3); + double a3 = cut_polygon.getAttributeAsDbl( + VertexDescription.Semantics.M, 3, 0); + assertTrue(a3 == 17); + + @SuppressWarnings("unused") + Point2D pt4 = cut_polygon.getXY(4); + double a4 = cut_polygon.getAttributeAsDbl( + VertexDescription.Semantics.M, 4, 0); + assertTrue(a4 == 20); + + @SuppressWarnings("unused") + Point2D pt5 = cut_polygon.getXY(5); + double a5 = cut_polygon.getAttributeAsDbl( + VertexDescription.Semantics.M, 5, 0); + assertTrue(a5 == 23); + + @SuppressWarnings("unused") + Point2D pt6 = cut_polygon.getXY(6); + double a6 = cut_polygon.getAttributeAsDbl( + VertexDescription.Semantics.M, 6, 0); + assertTrue(a6 == 33); + + @SuppressWarnings("unused") + Point2D pt7 = cut_polygon.getXY(7); + double a7 = cut_polygon.getAttributeAsDbl( + VertexDescription.Semantics.M, 7, 0); + assertTrue(a7 == 43); + } + } } diff --git a/src/test/java/com/esri/core/geometry/TestEnvelope2DIntersector.java b/src/test/java/com/esri/core/geometry/TestEnvelope2DIntersector.java index 32c07b43..6416b430 100644 --- a/src/test/java/com/esri/core/geometry/TestEnvelope2DIntersector.java +++ b/src/test/java/com/esri/core/geometry/TestEnvelope2DIntersector.java @@ -31,336 +31,336 @@ import java.util.Random; public class TestEnvelope2DIntersector extends TestCase { - @Override - protected void setUp() throws Exception { - super.setUp(); - } - - @Override - protected void tearDown() throws Exception { - super.tearDown(); - } - - @Test - public static void testEnvelope2Dintersector() { - ArrayList envelopes = new ArrayList(0); - - Envelope2D env0 = new Envelope2D(2, 3, 4, 4); - Envelope2D env1 = new Envelope2D(5, 13, 9, 15); - Envelope2D env2 = new Envelope2D(6, 9, 11, 12); - Envelope2D env3 = new Envelope2D(8, 10, 9, 17); - Envelope2D env4 = new Envelope2D(11.001, 12, 14, 14); - Envelope2D env5 = new Envelope2D(1, 3, 3, 4); - Envelope2D env6 = new Envelope2D(0, 2, 5, 10); - Envelope2D env7 = new Envelope2D(4, 7, 5, 10); - Envelope2D env8 = new Envelope2D(3, 15, 15, 15); - Envelope2D env9 = new Envelope2D(0, 9, 14, 9); - Envelope2D env10 = new Envelope2D(0, 8.999, 14, 8.999); - - envelopes.add(env0); - envelopes.add(env1); - envelopes.add(env2); - envelopes.add(env3); - envelopes.add(env4); - envelopes.add(env5); - envelopes.add(env6); - envelopes.add(env7); - envelopes.add(env8); - envelopes.add(env9); - envelopes.add(env10); - - Envelope2DIntersectorImpl intersector = new Envelope2DIntersectorImpl(); - intersector.setTolerance(0.001); - - intersector.startConstruction(); - for (int i = 0; i < envelopes.size(); i++) - intersector.addEnvelope(i, envelopes.get(i)); - intersector.endConstruction(); - - int count = 0; - while (intersector.next()) { - int env_a = intersector.getHandleA(); - int env_b = intersector.getHandleB(); - count++; - Envelope2D env = new Envelope2D(); - env.setCoords(envelopes.get(env_a)); - env.inflate(0.001, 0.001); - assertTrue(env.isIntersecting(envelopes.get(env_b))); - } - - assert (count == 16); - - Envelope2DIntersectorImpl intersector2 = new Envelope2DIntersectorImpl(); - intersector2.setTolerance(0.0); - intersector2.startConstruction(); - for (int i = 0; i < envelopes.size(); i++) - intersector2.addEnvelope(i, envelopes.get(i)); - intersector2.endConstruction(); - - count = 0; - while (intersector2.next()) { - int env_a = intersector2.getHandleA(); - int env_b = intersector2.getHandleB(); - count++; - Envelope2D env = new Envelope2D(); - env.setCoords(envelopes.get(env_a)); - assertTrue(env.isIntersecting(envelopes.get(env_b))); - } - - assert (count == 13); - - env0 = new Envelope2D(0, 0, 0, 10); - env1 = new Envelope2D(0, 10, 10, 10); - env2 = new Envelope2D(10, 0, 10, 10); - env3 = new Envelope2D(0, 0, 10, 0); - envelopes.clear(); - - envelopes.add(env0); - envelopes.add(env1); - envelopes.add(env2); - envelopes.add(env3); - - Envelope2DIntersectorImpl intersector3 = new Envelope2DIntersectorImpl(); - intersector3.setTolerance(0.001); - - intersector3.startConstruction(); - for (int i = 0; i < envelopes.size(); i++) - intersector3.addEnvelope(i, envelopes.get(i)); - intersector3.endConstruction(); - ; - count = 0; - while (intersector3.next()) { - int env_a = intersector3.getHandleA(); - int env_b = intersector3.getHandleB(); - count++; - Envelope2D env = new Envelope2D(); - env.setCoords(envelopes.get(env_a)); - assertTrue(env.isIntersecting(envelopes.get(env_b))); - } - - assertTrue(count == 4); - - env0 = new Envelope2D(0, 0, 0, 10); - envelopes.clear(); - - envelopes.add(env0); - envelopes.add(env0); - envelopes.add(env0); - envelopes.add(env0); - - Envelope2DIntersectorImpl intersector4 = new Envelope2DIntersectorImpl(); - intersector4.setTolerance(0.001); - - intersector4.startConstruction(); - for (int i = 0; i < envelopes.size(); i++) - intersector4.addEnvelope(i, envelopes.get(i)); - intersector4.endConstruction(); - - count = 0; - while (intersector4.next()) { - int env_a = intersector4.getHandleA(); - int env_b = intersector4.getHandleB(); - count++; - Envelope2D env = new Envelope2D(); - env.setCoords(envelopes.get(env_a)); - assertTrue(env.isIntersecting(envelopes.get(env_b))); - } - - assert (count == 6); - - env0 = new Envelope2D(0, 10, 10, 10); - envelopes.clear(); - - envelopes.add(env0); - envelopes.add(env0); - envelopes.add(env0); - envelopes.add(env0); - - Envelope2DIntersectorImpl intersector5 = new Envelope2DIntersectorImpl(); - intersector5.setTolerance(0.001); - - intersector5.startConstruction(); - for (int i = 0; i < envelopes.size(); i++) - intersector5.addEnvelope(i, envelopes.get(i)); - intersector5.endConstruction(); - - count = 0; - while (intersector5.next()) { - int env_a = intersector5.getHandleA(); - int env_b = intersector5.getHandleB(); - count++; - Envelope2D env = new Envelope2D(); - env.setCoords(envelopes.get(env_a)); - assertTrue(env.isIntersecting(envelopes.get(env_b))); - } - - assertTrue(count == 6); - } - - @Test - public static void testRandom() { - int passcount = 10; - int figureSize = 100; - int figureSize2 = 100; - Envelope extent1 = new Envelope(), extent2 = new Envelope(), extent3 = new Envelope(), extent4 = new Envelope(); - extent1.setCoords(-10000, 5000, 10000, 25000);// red - extent2.setCoords(-10000, 2000, 10000, 8000);// blue - extent3.setCoords(-10000, -8000, 10000, -2000);// blue - extent4.setCoords(-10000, -25000, 10000, -5000);// red - - RandomCoordinateGenerator generator1 = new RandomCoordinateGenerator( - Math.max(figureSize, 10000), extent1, 0.001); - RandomCoordinateGenerator generator2 = new RandomCoordinateGenerator( - Math.max(figureSize, 10000), extent2, 0.001); - RandomCoordinateGenerator generator3 = new RandomCoordinateGenerator( - Math.max(figureSize, 10000), extent3, 0.001); - RandomCoordinateGenerator generator4 = new RandomCoordinateGenerator( - Math.max(figureSize, 10000), extent4, 0.001); - - Random random = new Random(1982); - int rand_max = 511; - - int qCount = 0; - int eCount = 0; - @SuppressWarnings("unused") - int bCount = 0; - for (int c = 0; c < passcount; c++) { - Polygon polyRed = new Polygon(); - Polygon polyBlue = new Polygon(); - - int r = figureSize; - if (r < 3) - continue; - - Point pt; - for (int j = 0; j < r; j++) { - int rand = random.nextInt(rand_max); - boolean bRandomNew = (r > 10) - && ((1.0 * rand) / rand_max > 0.95); - pt = generator1.GetRandomCoord(); - if (j == 0 || bRandomNew) - polyRed.startPath(pt); - else - polyRed.lineTo(pt); - } - - for (int j = 0; j < r; j++) { - int rand = random.nextInt(rand_max); - boolean bRandomNew = (r > 10) - && ((1.0 * rand) / rand_max > 0.95); - pt = generator4.GetRandomCoord(); - if (j == 0 || bRandomNew) - polyRed.startPath(pt); - else - polyRed.lineTo(pt); - } - - r = figureSize2; - if (r < 3) - continue; - - for (int j = 0; j < r; j++) { - int rand = random.nextInt(rand_max); - boolean bRandomNew = (r > 10) - && ((1.0 * rand) / rand_max > 0.95); - pt = generator2.GetRandomCoord(); - if (j == 0 || bRandomNew) - polyBlue.startPath(pt); - else - polyBlue.lineTo(pt); - } - - for (int j = 0; j < r; j++) { - int rand = random.nextInt(rand_max); - boolean bRandomNew = (r > 10) - && ((1.0 * rand) / rand_max > 0.95); - pt = generator3.GetRandomCoord(); - if (j == 0 || bRandomNew) - polyBlue.startPath(pt); - else - polyBlue.lineTo(pt); - } - - Envelope2D env = new Envelope2D(); - - // Quad_tree - QuadTree quadTree = buildQuadTree(polyBlue); - QuadTree.QuadTreeIterator iterator = quadTree.getIterator(); - - SegmentIteratorImpl _segIterRed = ((MultiPathImpl) polyRed - ._getImpl()).querySegmentIterator(); - - while (_segIterRed.nextPath()) { - while (_segIterRed.hasNextSegment()) { - Segment segmentRed = _segIterRed.nextSegment(); - segmentRed.queryEnvelope2D(env); - iterator.resetIterator(env, 0.001); - while (iterator.next() != -1) - qCount++; - } - } - - // Envelope_2D_intersector - - ArrayList envelopes_red = new ArrayList(); - ArrayList envelopes_blue = new ArrayList(); - - SegmentIterator segIterRed = polyRed.querySegmentIterator(); - while (segIterRed.nextPath()) { - while (segIterRed.hasNextSegment()) { - Segment segment = segIterRed.nextSegment(); - env = new Envelope2D(); - segment.queryEnvelope2D(env); - envelopes_red.add(env); - } - } - - SegmentIterator segIterBlue = polyBlue.querySegmentIterator(); - while (segIterBlue.nextPath()) { - while (segIterBlue.hasNextSegment()) { - Segment segment = segIterBlue.nextSegment(); - env = new Envelope2D(); - segment.queryEnvelope2D(env); - envelopes_blue.add(env); - } - } - - Envelope2DIntersectorImpl intersector = new Envelope2DIntersectorImpl(); - intersector.setTolerance(0.001); - - intersector.startRedConstruction(); - for (int i = 0; i < envelopes_red.size(); i++) - intersector.addRedEnvelope(i, envelopes_red.get(i)); - intersector.endRedConstruction(); - - intersector.startBlueConstruction(); - for (int i = 0; i < envelopes_blue.size(); i++) - intersector.addBlueEnvelope(i, envelopes_blue.get(i)); - intersector.endBlueConstruction(); - - while (intersector.next()) - eCount++; - - assertTrue(qCount == eCount); - } - } - - public static QuadTree buildQuadTree(MultiPath multipath) { - Envelope2D extent = new Envelope2D(); - multipath.queryEnvelope2D(extent); - QuadTree quadTree = new QuadTree(extent, 8); - int hint_index = -1; - SegmentIterator seg_iter = multipath.querySegmentIterator(); - while (seg_iter.nextPath()) { - while (seg_iter.hasNextSegment()) { - Segment segment = seg_iter.nextSegment(); - int index = seg_iter.getStartPointIndex(); - Envelope2D boundingbox = new Envelope2D(); - segment.queryEnvelope2D(boundingbox); - hint_index = quadTree.insert(index, boundingbox, hint_index); - } - } - - return quadTree; - } + @Override + protected void setUp() throws Exception { + super.setUp(); + } + + @Override + protected void tearDown() throws Exception { + super.tearDown(); + } + + @Test + public static void testEnvelope2Dintersector() { + ArrayList envelopes = new ArrayList(0); + + Envelope2D env0 = new Envelope2D(2, 3, 4, 4); + Envelope2D env1 = new Envelope2D(5, 13, 9, 15); + Envelope2D env2 = new Envelope2D(6, 9, 11, 12); + Envelope2D env3 = new Envelope2D(8, 10, 9, 17); + Envelope2D env4 = new Envelope2D(11.001, 12, 14, 14); + Envelope2D env5 = new Envelope2D(1, 3, 3, 4); + Envelope2D env6 = new Envelope2D(0, 2, 5, 10); + Envelope2D env7 = new Envelope2D(4, 7, 5, 10); + Envelope2D env8 = new Envelope2D(3, 15, 15, 15); + Envelope2D env9 = new Envelope2D(0, 9, 14, 9); + Envelope2D env10 = new Envelope2D(0, 8.999, 14, 8.999); + + envelopes.add(env0); + envelopes.add(env1); + envelopes.add(env2); + envelopes.add(env3); + envelopes.add(env4); + envelopes.add(env5); + envelopes.add(env6); + envelopes.add(env7); + envelopes.add(env8); + envelopes.add(env9); + envelopes.add(env10); + + Envelope2DIntersectorImpl intersector = new Envelope2DIntersectorImpl(); + intersector.setTolerance(0.001); + + intersector.startConstruction(); + for (int i = 0; i < envelopes.size(); i++) + intersector.addEnvelope(i, envelopes.get(i)); + intersector.endConstruction(); + + int count = 0; + while (intersector.next()) { + int env_a = intersector.getHandleA(); + int env_b = intersector.getHandleB(); + count++; + Envelope2D env = new Envelope2D(); + env.setCoords(envelopes.get(env_a)); + env.inflate(0.001, 0.001); + assertTrue(env.isIntersecting(envelopes.get(env_b))); + } + + assert (count == 16); + + Envelope2DIntersectorImpl intersector2 = new Envelope2DIntersectorImpl(); + intersector2.setTolerance(0.0); + intersector2.startConstruction(); + for (int i = 0; i < envelopes.size(); i++) + intersector2.addEnvelope(i, envelopes.get(i)); + intersector2.endConstruction(); + + count = 0; + while (intersector2.next()) { + int env_a = intersector2.getHandleA(); + int env_b = intersector2.getHandleB(); + count++; + Envelope2D env = new Envelope2D(); + env.setCoords(envelopes.get(env_a)); + assertTrue(env.isIntersecting(envelopes.get(env_b))); + } + + assert (count == 13); + + env0 = new Envelope2D(0, 0, 0, 10); + env1 = new Envelope2D(0, 10, 10, 10); + env2 = new Envelope2D(10, 0, 10, 10); + env3 = new Envelope2D(0, 0, 10, 0); + envelopes.clear(); + + envelopes.add(env0); + envelopes.add(env1); + envelopes.add(env2); + envelopes.add(env3); + + Envelope2DIntersectorImpl intersector3 = new Envelope2DIntersectorImpl(); + intersector3.setTolerance(0.001); + + intersector3.startConstruction(); + for (int i = 0; i < envelopes.size(); i++) + intersector3.addEnvelope(i, envelopes.get(i)); + intersector3.endConstruction(); + ; + count = 0; + while (intersector3.next()) { + int env_a = intersector3.getHandleA(); + int env_b = intersector3.getHandleB(); + count++; + Envelope2D env = new Envelope2D(); + env.setCoords(envelopes.get(env_a)); + assertTrue(env.isIntersecting(envelopes.get(env_b))); + } + + assertTrue(count == 4); + + env0 = new Envelope2D(0, 0, 0, 10); + envelopes.clear(); + + envelopes.add(env0); + envelopes.add(env0); + envelopes.add(env0); + envelopes.add(env0); + + Envelope2DIntersectorImpl intersector4 = new Envelope2DIntersectorImpl(); + intersector4.setTolerance(0.001); + + intersector4.startConstruction(); + for (int i = 0; i < envelopes.size(); i++) + intersector4.addEnvelope(i, envelopes.get(i)); + intersector4.endConstruction(); + + count = 0; + while (intersector4.next()) { + int env_a = intersector4.getHandleA(); + int env_b = intersector4.getHandleB(); + count++; + Envelope2D env = new Envelope2D(); + env.setCoords(envelopes.get(env_a)); + assertTrue(env.isIntersecting(envelopes.get(env_b))); + } + + assert (count == 6); + + env0 = new Envelope2D(0, 10, 10, 10); + envelopes.clear(); + + envelopes.add(env0); + envelopes.add(env0); + envelopes.add(env0); + envelopes.add(env0); + + Envelope2DIntersectorImpl intersector5 = new Envelope2DIntersectorImpl(); + intersector5.setTolerance(0.001); + + intersector5.startConstruction(); + for (int i = 0; i < envelopes.size(); i++) + intersector5.addEnvelope(i, envelopes.get(i)); + intersector5.endConstruction(); + + count = 0; + while (intersector5.next()) { + int env_a = intersector5.getHandleA(); + int env_b = intersector5.getHandleB(); + count++; + Envelope2D env = new Envelope2D(); + env.setCoords(envelopes.get(env_a)); + assertTrue(env.isIntersecting(envelopes.get(env_b))); + } + + assertTrue(count == 6); + } + + @Test + public static void testRandom() { + int passcount = 10; + int figureSize = 100; + int figureSize2 = 100; + Envelope extent1 = new Envelope(), extent2 = new Envelope(), extent3 = new Envelope(), extent4 = new Envelope(); + extent1.setCoords(-10000, 5000, 10000, 25000);// red + extent2.setCoords(-10000, 2000, 10000, 8000);// blue + extent3.setCoords(-10000, -8000, 10000, -2000);// blue + extent4.setCoords(-10000, -25000, 10000, -5000);// red + + RandomCoordinateGenerator generator1 = new RandomCoordinateGenerator( + Math.max(figureSize, 10000), extent1, 0.001); + RandomCoordinateGenerator generator2 = new RandomCoordinateGenerator( + Math.max(figureSize, 10000), extent2, 0.001); + RandomCoordinateGenerator generator3 = new RandomCoordinateGenerator( + Math.max(figureSize, 10000), extent3, 0.001); + RandomCoordinateGenerator generator4 = new RandomCoordinateGenerator( + Math.max(figureSize, 10000), extent4, 0.001); + + Random random = new Random(1982); + int rand_max = 511; + + int qCount = 0; + int eCount = 0; + @SuppressWarnings("unused") + int bCount = 0; + for (int c = 0; c < passcount; c++) { + Polygon polyRed = new Polygon(); + Polygon polyBlue = new Polygon(); + + int r = figureSize; + if (r < 3) + continue; + + Point pt; + for (int j = 0; j < r; j++) { + int rand = random.nextInt(rand_max); + boolean bRandomNew = (r > 10) + && ((1.0 * rand) / rand_max > 0.95); + pt = generator1.GetRandomCoord(); + if (j == 0 || bRandomNew) + polyRed.startPath(pt); + else + polyRed.lineTo(pt); + } + + for (int j = 0; j < r; j++) { + int rand = random.nextInt(rand_max); + boolean bRandomNew = (r > 10) + && ((1.0 * rand) / rand_max > 0.95); + pt = generator4.GetRandomCoord(); + if (j == 0 || bRandomNew) + polyRed.startPath(pt); + else + polyRed.lineTo(pt); + } + + r = figureSize2; + if (r < 3) + continue; + + for (int j = 0; j < r; j++) { + int rand = random.nextInt(rand_max); + boolean bRandomNew = (r > 10) + && ((1.0 * rand) / rand_max > 0.95); + pt = generator2.GetRandomCoord(); + if (j == 0 || bRandomNew) + polyBlue.startPath(pt); + else + polyBlue.lineTo(pt); + } + + for (int j = 0; j < r; j++) { + int rand = random.nextInt(rand_max); + boolean bRandomNew = (r > 10) + && ((1.0 * rand) / rand_max > 0.95); + pt = generator3.GetRandomCoord(); + if (j == 0 || bRandomNew) + polyBlue.startPath(pt); + else + polyBlue.lineTo(pt); + } + + Envelope2D env = new Envelope2D(); + + // Quad_tree + QuadTree quadTree = buildQuadTree(polyBlue); + QuadTree.QuadTreeIterator iterator = quadTree.getIterator(); + + SegmentIteratorImpl _segIterRed = ((MultiPathImpl) polyRed + ._getImpl()).querySegmentIterator(); + + while (_segIterRed.nextPath()) { + while (_segIterRed.hasNextSegment()) { + Segment segmentRed = _segIterRed.nextSegment(); + segmentRed.queryEnvelope2D(env); + iterator.resetIterator(env, 0.001); + while (iterator.next() != -1) + qCount++; + } + } + + // Envelope_2D_intersector + + ArrayList envelopes_red = new ArrayList(); + ArrayList envelopes_blue = new ArrayList(); + + SegmentIterator segIterRed = polyRed.querySegmentIterator(); + while (segIterRed.nextPath()) { + while (segIterRed.hasNextSegment()) { + Segment segment = segIterRed.nextSegment(); + env = new Envelope2D(); + segment.queryEnvelope2D(env); + envelopes_red.add(env); + } + } + + SegmentIterator segIterBlue = polyBlue.querySegmentIterator(); + while (segIterBlue.nextPath()) { + while (segIterBlue.hasNextSegment()) { + Segment segment = segIterBlue.nextSegment(); + env = new Envelope2D(); + segment.queryEnvelope2D(env); + envelopes_blue.add(env); + } + } + + Envelope2DIntersectorImpl intersector = new Envelope2DIntersectorImpl(); + intersector.setTolerance(0.001); + + intersector.startRedConstruction(); + for (int i = 0; i < envelopes_red.size(); i++) + intersector.addRedEnvelope(i, envelopes_red.get(i)); + intersector.endRedConstruction(); + + intersector.startBlueConstruction(); + for (int i = 0; i < envelopes_blue.size(); i++) + intersector.addBlueEnvelope(i, envelopes_blue.get(i)); + intersector.endBlueConstruction(); + + while (intersector.next()) + eCount++; + + assertTrue(qCount == eCount); + } + } + + public static QuadTree buildQuadTree(MultiPath multipath) { + Envelope2D extent = new Envelope2D(); + multipath.queryEnvelope2D(extent); + QuadTree quadTree = new QuadTree(extent, 8); + int hint_index = -1; + SegmentIterator seg_iter = multipath.querySegmentIterator(); + while (seg_iter.nextPath()) { + while (seg_iter.hasNextSegment()) { + Segment segment = seg_iter.nextSegment(); + int index = seg_iter.getStartPointIndex(); + Envelope2D boundingbox = new Envelope2D(); + segment.queryEnvelope2D(boundingbox); + hint_index = quadTree.insert(index, boundingbox, hint_index); + } + } + + return quadTree; + } } diff --git a/src/test/java/com/esri/core/geometry/TestEquals.java b/src/test/java/com/esri/core/geometry/TestEquals.java index 3dda17be..90ed71a0 100644 --- a/src/test/java/com/esri/core/geometry/TestEquals.java +++ b/src/test/java/com/esri/core/geometry/TestEquals.java @@ -28,165 +28,165 @@ import org.junit.Test; public class TestEquals extends TestCase { - @Override - protected void setUp() throws Exception { - super.setUp(); - } - - @Override - protected void tearDown() throws Exception { - super.tearDown(); - } - - @Test - public void testEqualsOnEnvelopes() { - SpatialReference sr = SpatialReference.create(4326); - - Point p = new Point(-130, 10); - Envelope env = new Envelope(p, 12, 12); - Envelope env2 = new Envelope(-136, 4, -124, 16); - - boolean isEqual; - try { - isEqual = GeometryEngine.equals(env, env2, sr); - - } catch (IllegalArgumentException ex) { - isEqual = false; - } - assertTrue(isEqual); - } - - @Test - public void testEqualsOnPoints() { - SpatialReference sr = SpatialReference.create(4326); - - Point p1 = new Point(-116, 40); - @SuppressWarnings("unused") - Point p2 = new Point(-120, 39); - @SuppressWarnings("unused") - Point p3 = new Point(-121, 10); - @SuppressWarnings("unused") - Point p4 = new Point(-130, 12); - @SuppressWarnings("unused") - Point p5 = new Point(-108, 25); - - Point p12 = new Point(-116, 40); - @SuppressWarnings("unused") - Point p22 = new Point(-120, 39); - @SuppressWarnings("unused") - Point p32 = new Point(-121, 10); - @SuppressWarnings("unused") - Point p42 = new Point(-130, 12); - @SuppressWarnings("unused") - Point p52 = new Point(-108, 25); - - boolean isEqual1 = false; - boolean isEqual2 = false; - boolean isEqual3 = false; - boolean isEqual4 = false; - boolean isEqual5 = false; - - try { - isEqual1 = GeometryEngine.equals(p1, p12, sr); - isEqual2 = GeometryEngine.equals(p1, p12, sr); - isEqual3 = GeometryEngine.equals(p1, p12, sr); - isEqual4 = GeometryEngine.equals(p1, p12, sr); - isEqual5 = GeometryEngine.equals(p1, p12, sr); - } catch (IllegalArgumentException ex) { - - } - - assertTrue(isEqual1 && isEqual2 && isEqual3 && isEqual4 && isEqual5); - } - - @Test - public void testEqualsOnPolygons() { - SpatialReference sr = SpatialReference.create(4326); - - Polygon baseMp = new Polygon(); - Polygon compMp = new Polygon(); - - baseMp.startPath(-116, 40); - baseMp.lineTo(-120, 39); - baseMp.lineTo(-121, 10); - baseMp.lineTo(-130, 12); - baseMp.lineTo(-108, 25); - - compMp.startPath(-116, 40); - compMp.lineTo(-120, 39); - compMp.lineTo(-121, 10); - compMp.lineTo(-130, 12); - compMp.lineTo(-108, 25); - - boolean isEqual; - - try { - isEqual = GeometryEngine.equals(baseMp, compMp, sr); - - } catch (IllegalArgumentException ex) { - isEqual = false; - } - - assertTrue(isEqual); - } - - @Test - public void testEqualsOnPolylines() { - SpatialReference sr = SpatialReference.create(4326); - - Polyline baseMp = new Polyline(); - Polyline compMp = new Polyline(); - - baseMp.startPath(-116, 40); - baseMp.lineTo(-120, 39); - baseMp.lineTo(-121, 10); - baseMp.lineTo(-130, 12); - baseMp.lineTo(-108, 25); - - compMp.startPath(-116, 40); - compMp.lineTo(-120, 39); - compMp.lineTo(-121, 10); - compMp.lineTo(-130, 12); - compMp.lineTo(-108, 25); - - boolean isEqual; - - try { - isEqual = GeometryEngine.equals(baseMp, compMp, sr); - } catch (IllegalArgumentException ex) { - isEqual = false; - } - - assertTrue(isEqual); - } - - @Test - public void testEqualsOnMultiPoints() { - SpatialReference sr = SpatialReference.create(4326); - - MultiPoint baseMp = new MultiPoint(); - MultiPoint compMp = new MultiPoint(); - - baseMp.add(new Point(-116, 40)); - baseMp.add(new Point(-120, 39)); - baseMp.add(new Point(-121, 10)); - baseMp.add(new Point(-130, 12)); - baseMp.add(new Point(-108, 25)); - - compMp.add(new Point(-116, 40)); - compMp.add(new Point(-120, 39)); - compMp.add(new Point(-121, 10)); - compMp.add(new Point(-130, 12)); - compMp.add(new Point(-108, 25)); - - boolean isEqual; - - try { - isEqual = GeometryEngine.equals(baseMp, compMp, sr); - } catch (IllegalArgumentException ex) { - isEqual = false; - } - - assertTrue(isEqual); - } + @Override + protected void setUp() throws Exception { + super.setUp(); + } + + @Override + protected void tearDown() throws Exception { + super.tearDown(); + } + + @Test + public void testEqualsOnEnvelopes() { + SpatialReference sr = SpatialReference.create(4326); + + Point p = new Point(-130, 10); + Envelope env = new Envelope(p, 12, 12); + Envelope env2 = new Envelope(-136, 4, -124, 16); + + boolean isEqual; + try { + isEqual = GeometryEngine.equals(env, env2, sr); + + } catch (IllegalArgumentException ex) { + isEqual = false; + } + assertTrue(isEqual); + } + + @Test + public void testEqualsOnPoints() { + SpatialReference sr = SpatialReference.create(4326); + + Point p1 = new Point(-116, 40); + @SuppressWarnings("unused") + Point p2 = new Point(-120, 39); + @SuppressWarnings("unused") + Point p3 = new Point(-121, 10); + @SuppressWarnings("unused") + Point p4 = new Point(-130, 12); + @SuppressWarnings("unused") + Point p5 = new Point(-108, 25); + + Point p12 = new Point(-116, 40); + @SuppressWarnings("unused") + Point p22 = new Point(-120, 39); + @SuppressWarnings("unused") + Point p32 = new Point(-121, 10); + @SuppressWarnings("unused") + Point p42 = new Point(-130, 12); + @SuppressWarnings("unused") + Point p52 = new Point(-108, 25); + + boolean isEqual1 = false; + boolean isEqual2 = false; + boolean isEqual3 = false; + boolean isEqual4 = false; + boolean isEqual5 = false; + + try { + isEqual1 = GeometryEngine.equals(p1, p12, sr); + isEqual2 = GeometryEngine.equals(p1, p12, sr); + isEqual3 = GeometryEngine.equals(p1, p12, sr); + isEqual4 = GeometryEngine.equals(p1, p12, sr); + isEqual5 = GeometryEngine.equals(p1, p12, sr); + } catch (IllegalArgumentException ex) { + + } + + assertTrue(isEqual1 && isEqual2 && isEqual3 && isEqual4 && isEqual5); + } + + @Test + public void testEqualsOnPolygons() { + SpatialReference sr = SpatialReference.create(4326); + + Polygon baseMp = new Polygon(); + Polygon compMp = new Polygon(); + + baseMp.startPath(-116, 40); + baseMp.lineTo(-120, 39); + baseMp.lineTo(-121, 10); + baseMp.lineTo(-130, 12); + baseMp.lineTo(-108, 25); + + compMp.startPath(-116, 40); + compMp.lineTo(-120, 39); + compMp.lineTo(-121, 10); + compMp.lineTo(-130, 12); + compMp.lineTo(-108, 25); + + boolean isEqual; + + try { + isEqual = GeometryEngine.equals(baseMp, compMp, sr); + + } catch (IllegalArgumentException ex) { + isEqual = false; + } + + assertTrue(isEqual); + } + + @Test + public void testEqualsOnPolylines() { + SpatialReference sr = SpatialReference.create(4326); + + Polyline baseMp = new Polyline(); + Polyline compMp = new Polyline(); + + baseMp.startPath(-116, 40); + baseMp.lineTo(-120, 39); + baseMp.lineTo(-121, 10); + baseMp.lineTo(-130, 12); + baseMp.lineTo(-108, 25); + + compMp.startPath(-116, 40); + compMp.lineTo(-120, 39); + compMp.lineTo(-121, 10); + compMp.lineTo(-130, 12); + compMp.lineTo(-108, 25); + + boolean isEqual; + + try { + isEqual = GeometryEngine.equals(baseMp, compMp, sr); + } catch (IllegalArgumentException ex) { + isEqual = false; + } + + assertTrue(isEqual); + } + + @Test + public void testEqualsOnMultiPoints() { + SpatialReference sr = SpatialReference.create(4326); + + MultiPoint baseMp = new MultiPoint(); + MultiPoint compMp = new MultiPoint(); + + baseMp.add(new Point(-116, 40)); + baseMp.add(new Point(-120, 39)); + baseMp.add(new Point(-121, 10)); + baseMp.add(new Point(-130, 12)); + baseMp.add(new Point(-108, 25)); + + compMp.add(new Point(-116, 40)); + compMp.add(new Point(-120, 39)); + compMp.add(new Point(-121, 10)); + compMp.add(new Point(-130, 12)); + compMp.add(new Point(-108, 25)); + + boolean isEqual; + + try { + isEqual = GeometryEngine.equals(baseMp, compMp, sr); + } catch (IllegalArgumentException ex) { + isEqual = false; + } + + assertTrue(isEqual); + } } diff --git a/src/test/java/com/esri/core/geometry/TestFailed.java b/src/test/java/com/esri/core/geometry/TestFailed.java index f8c8875c..05bede19 100644 --- a/src/test/java/com/esri/core/geometry/TestFailed.java +++ b/src/test/java/com/esri/core/geometry/TestFailed.java @@ -28,61 +28,61 @@ import org.junit.Test; public class TestFailed extends TestCase { - @Override - protected void setUp() throws Exception { - super.setUp(); - } + @Override + protected void setUp() throws Exception { + super.setUp(); + } - @Override - protected void tearDown() throws Exception { - super.tearDown(); - } + @Override + protected void tearDown() throws Exception { + super.tearDown(); + } - @Test - public void testCenterXY() { - Envelope env = new Envelope(-130, 30, -70, 50); - assertEquals(-100, env.getCenterX(), 0); - assertEquals(40, env.getCenterY(), 0); - } + @Test + public void testCenterXY() { + Envelope env = new Envelope(-130, 30, -70, 50); + assertEquals(-100, env.getCenterX(), 0); + assertEquals(40, env.getCenterY(), 0); + } - @Test - public void testGeometryOperationSupport() { - Geometry baseGeom = new Point(-130, 10); - Geometry comparisonGeom = new Point(-130, 10); - SpatialReference sr = SpatialReference.create(4326); + @Test + public void testGeometryOperationSupport() { + Geometry baseGeom = new Point(-130, 10); + Geometry comparisonGeom = new Point(-130, 10); + SpatialReference sr = SpatialReference.create(4326); - @SuppressWarnings("unused") - Geometry diffGeom = null; - int noException = 1; // no exception - try { - diffGeom = GeometryEngine.difference(baseGeom, comparisonGeom, sr); + @SuppressWarnings("unused") + Geometry diffGeom = null; + int noException = 1; // no exception + try { + diffGeom = GeometryEngine.difference(baseGeom, comparisonGeom, sr); - } catch (IllegalArgumentException ex) { - noException = 0; - } catch (GeometryException ex) { - noException = 0; - } - assertEquals(noException, 1); - } + } catch (IllegalArgumentException ex) { + noException = 0; + } catch (GeometryException ex) { + noException = 0; + } + assertEquals(noException, 1); + } - @Test - public void TestIntersection() { - OperatorIntersects op = (OperatorIntersects) OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.Intersects); - Polygon polygon = new Polygon(); - // outer ring1 - polygon.startPath(0, 0); - polygon.lineTo(10, 10); - polygon.lineTo(20, 0); + @Test + public void TestIntersection() { + OperatorIntersects op = (OperatorIntersects) OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Intersects); + Polygon polygon = new Polygon(); + // outer ring1 + polygon.startPath(0, 0); + polygon.lineTo(10, 10); + polygon.lineTo(20, 0); - Point point1 = new Point(15, 10); - Point point2 = new Point(2, 10); - Point point3 = new Point(5, 5); - boolean res = op.execute(polygon, point1, null, null); - assertTrue(!res); - res = op.execute(polygon, point2, null, null); - assertTrue(!res); - res = op.execute(polygon, point3, null, null); - assertTrue(res); - } + Point point1 = new Point(15, 10); + Point point2 = new Point(2, 10); + Point point3 = new Point(5, 5); + boolean res = op.execute(polygon, point1, null, null); + assertTrue(!res); + res = op.execute(polygon, point2, null, null); + assertTrue(!res); + res = op.execute(polygon, point3, null, null); + assertTrue(res); + } } diff --git a/src/test/java/com/esri/core/geometry/TestGeneralize.java b/src/test/java/com/esri/core/geometry/TestGeneralize.java index f8e99d0d..259c2a6c 100644 --- a/src/test/java/com/esri/core/geometry/TestGeneralize.java +++ b/src/test/java/com/esri/core/geometry/TestGeneralize.java @@ -28,222 +28,222 @@ import org.junit.Test; public class TestGeneralize extends TestCase { - @Override - protected void setUp() throws Exception { - super.setUp(); - } - - @Override - protected void tearDown() throws Exception { - super.tearDown(); - } - - @Test - public void test1() { - OperatorFactoryLocal engine = OperatorFactoryLocal.getInstance(); - OperatorGeneralize op = (OperatorGeneralize) engine.getOperator(Operator.Type.Generalize); - - Polygon poly = new Polygon(); - poly.startPath(0, 0); - poly.lineTo(1, 1); - poly.lineTo(2, 0); - poly.lineTo(3, 2); - poly.lineTo(4, 1); - poly.lineTo(5, 0); - poly.lineTo(5, 10); - poly.lineTo(0, 10); - Geometry geom = op.execute(poly, 2, true, null); - Polygon p = (Polygon) geom; - Point2D[] points = p.getCoordinates2D(); - assertTrue(points.length == 4); - assertTrue(points[0].x == 0 && points[0].y == 0); - assertTrue(points[1].x == 5 && points[1].y == 0); - assertTrue(points[2].x == 5 && points[2].y == 10); - assertTrue(points[3].x == 0 && points[3].y == 10); - - Geometry geom1 = op.execute(geom, 5, false, null); - p = (Polygon) geom1; - points = p.getCoordinates2D(); - assertTrue(points.length == 3); - assertTrue(points[0].x == 0 && points[0].y == 0); - assertTrue(points[1].x == 5 && points[1].y == 10); - assertTrue(points[2].x == 5 && points[2].y == 10); - - geom1 = op.execute(geom, 5, true, null); - p = (Polygon) geom1; - points = p.getCoordinates2D(); - assertTrue(points.length == 0); - } - - @Test - public void test2() { - OperatorFactoryLocal engine = OperatorFactoryLocal.getInstance(); - OperatorGeneralize op = (OperatorGeneralize) engine.getOperator(Operator.Type.Generalize); - - Polyline polyline = new Polyline(); - polyline.startPath(0, 0); - polyline.lineTo(1, 1); - polyline.lineTo(2, 0); - polyline.lineTo(3, 2); - polyline.lineTo(4, 1); - polyline.lineTo(5, 0); - polyline.lineTo(5, 10); - polyline.lineTo(0, 10); - Geometry geom = op.execute(polyline, 2, true, null); - Polyline p = (Polyline) geom; - Point2D[] points = p.getCoordinates2D(); - assertTrue(points.length == 4); - assertTrue(points[0].x == 0 && points[0].y == 0); - assertTrue(points[1].x == 5 && points[1].y == 0); - assertTrue(points[2].x == 5 && points[2].y == 10); - assertTrue(points[3].x == 0 && points[3].y == 10); - - Geometry geom1 = op.execute(geom, 5, false, null); - p = (Polyline) geom1; - points = p.getCoordinates2D(); - assertTrue(points.length == 2); - assertTrue(points[0].x == 0 && points[0].y == 0); - assertTrue(points[1].x == 0 && points[1].y == 10); - - geom1 = op.execute(geom, 5, true, null); - p = (Polyline) geom1; - points = p.getCoordinates2D(); - assertTrue(points.length == 2); - assertTrue(points[0].x == 0 && points[0].y == 0); - assertTrue(points[1].x == 0 && points[1].y == 10); - } - - @Test - public void testLargeDeviation() { - { - Polygon input_polygon = new Polygon(); - input_polygon - .addEnvelope(Envelope2D.construct(0, 0, 20, 10), false); - Geometry densified_geom = OperatorDensifyByLength.local().execute( - input_polygon, 1, null); - - Geometry geom = OperatorGeneralize.local().execute( - densified_geom, - 1, - true, - null); - - int pc = ((MultiPath) geom).getPointCount(); - assertTrue(pc == 4); - - Geometry large_dev1 = OperatorGeneralize.local().execute( - densified_geom, - 40, - true, - null); - - int pc1 = ((MultiPath) large_dev1).getPointCount(); - assertTrue(pc1 == 0); - - Geometry large_dev2 = OperatorGeneralize.local().execute( - densified_geom, 40, false, null); - int pc2 = ((MultiPath) large_dev2).getPointCount(); - assertTrue(pc2 == 3); - } - } - - @Test - public void test1Area() { - OperatorFactoryLocal engine = OperatorFactoryLocal.getInstance(); - OperatorGeneralizeByArea op = (OperatorGeneralizeByArea) engine.getOperator(Operator.Type.GeneralizeByArea); - - assertNotNull(op); - - Polygon poly = new Polygon(); - poly.startPath(0, 0); - poly.lineTo(1, 1); - poly.lineTo(2, 0); - poly.lineTo(3, 2); - poly.lineTo(4, 1); - poly.lineTo(5, 0); - poly.lineTo(5, 10); - poly.lineTo(0, 10); - poly = (Polygon) GeometryEngine.simplify(poly, SpatialReference.create(4326)); - String words2 = GeometryEngine.geometryToWkt(poly, 0); - Geometry geom = op.execute(poly, 50.0, true, GeneralizeType.Neither, SpatialReference.create(4326), null); - String words = GeometryEngine.geometryToWkt(geom, 0); - assertNotNull(geom); - Polygon p = (Polygon) geom; - Point2D[] points = p.getCoordinates2D(); - assertTrue(points.length == 4); - assertTrue(points[0].x == 0 && points[0].y == 0); - assertTrue(points[1].x == 0 && points[1].y == 10); - assertTrue(points[2].x == 5 && points[2].y == 10); - assertTrue(points[3].x == 5 && points[3].y == 0); - - OperatorContains operatorContains = (OperatorContains) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.Contains); - - Geometry geomContainer = op.execute(poly, 35, true, GeneralizeType.ResultContainsOriginal, SpatialReference.create(4326),null); - words = GeometryEngine.geometryToWkt(geomContainer, 0); - assertTrue(operatorContains.execute(geomContainer, poly, null, null)); - - Geometry geomContained = op.execute(poly, 30, true, GeneralizeType.ResultWithinOriginal, SpatialReference.create(4326),null); - assertTrue(operatorContains.execute(poly, geomContained, null, null)); - } - - @Test - public void testTreapTriangleManagement() { - Geometry geom = GeometryEngine.geometryFromWkt("POLYGON ((-70.651656936570745 43.135157834585826,-70.651570654984525 43.135150076925918,-70.651511247908587 43.135155437576621,-70.651490101488349 43.135169249238288,-70.651490181628759 43.135200763774868,-70.651510336532638 43.135249995303397,-70.651530872098633 43.135299226354952,-70.651527230781454 43.13533529757845,-70.651492643684321 43.135349305799508,-70.651420172717224 43.135354852495823,-70.651358095085072 43.135346745253884,-70.651247805462717 43.1353033127629,-70.651151361236941 43.135246176463966,-70.651124154255413 43.135206047556842,-70.651131534466685 43.135151913051203,-70.651155914561755 43.135115542493892,-70.651228730259774 43.135051460048707,-70.651305024993519 43.134973818456416,-70.651360674948108 43.134914489191551,-70.651385078643216 43.134864606853156,-70.651402827815218 43.134810322639233,-70.651410310326284 43.134688652284062,-70.651418383361829 43.134517448375689,-70.651411370788409 43.134413996600074,-70.651390876709428 43.134337752182525,-70.651346755889904 43.134225832686319,-70.651289063395154 43.134109606122337,-70.651241242017051 43.133988734078358,-70.651230854601764 43.133916846697829,-70.651221013347353 43.133822439140715,-70.651200519724128 43.133746194674302,-70.651218268699594 43.133691907971006,-70.651249886578313 43.133610413350418,-70.651273539332081 43.133547040518167,-70.651322422674269 43.133478800504768,-70.651363984222755 43.133424172537978,-70.651409243106613 43.133378494590104,-70.651437060507462 43.133355583224628,-70.651492145648632 43.133332275678633,-70.651599176252716 43.133326227462319,-70.651675204034788 43.133338636120527,-70.65179240423565 43.133381970944697,-70.651905761872953 43.133425353603045,-70.652022336036893 43.133459688797871,-70.652087607730962 43.133472252516562,-70.652149929372143 43.133489361765591,-70.652232540703992 43.133546699550045,-70.652400720753761 43.133656829257461,-70.652544825855031 43.133771806958798,-70.652698657471404 43.133891149422396,-70.652757214424 43.133939825915917,-70.652829643131227 43.133961291723317,-70.652898471779253 43.133991812912122,-70.652963804959569 43.134049399817712,-70.652994471176711 43.134089475795044,-70.653000937222828 43.134129903976664,-70.653000836372897 43.134197439788288,-70.652972837499135 43.134256375321229,-70.652903321638362 43.134342923487154,-70.652853489821567 43.134447199036558,-70.652811504521395 43.134528843881576,-70.652776716073816 43.134592380423527,-70.652745241323998 43.134664872485907,-70.652703717209832 43.134692485155824,-70.65263790191085 43.134702441934749,-70.652537796343609 43.13469488220742,-70.652481986521252 43.134691185475688,-70.652416288981371 43.134705643385693,-70.652347112331768 43.134733655368471,-70.652274294763117 43.134797738553125,-70.652239263183958 43.134852272522785,-70.652214826278225 43.134929165745028,-70.652217533236211 43.135001163954207,-70.652248296977746 43.135059250554882,-70.652327397729081 43.135157158057844,-70.652414500765317 43.135209927851037,-70.6524582645621 43.135222802033766,-70.652470512866358 43.135249637832572,-70.652646824360005 43.135404671730349,-70.652680946443695 43.135444700365824,-70.652691113545444 43.135494075980539,-70.652680299690047 43.135534756334458,-70.652642334694633 43.135580326883741,-70.6525831461027 43.135608197219,-70.65250042683256 43.135618395612582,-70.652403778584315 43.1356107883392,-70.652328034290349 43.135580366756479,-70.652207940292428 43.135501055652128,-70.652084166050003 43.135399292820672,-70.651994153636025 43.135324055019495,-70.651897729053019 43.13525341019011,-70.651815354685922 43.135205077192047,-70.651746646220062 43.135179051560065,-70.651656936570745 43.135157834585826))", 0, Geometry.Type.Unknown); - geom = GeometryEngine.simplify(geom, SpatialReference.create(4326)); - - OperatorFactoryLocal engine = OperatorFactoryLocal.getInstance(); - OperatorGeneralizeByArea op = (OperatorGeneralizeByArea) engine.getOperator(Operator.Type.GeneralizeByArea); - OperatorContains operatorContains = (OperatorContains) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.Contains); - - Geometry geomContainer = op.execute(geom, 15, true, GeneralizeType.ResultContainsOriginal, SpatialReference.create(4326),null); - String words = GeometryEngine.geometryToWkt(geomContainer, 0); - assertTrue(operatorContains.execute(geomContainer, geom, null, null)); - } - - @Test - public void testCompleteRemoval() { - - OperatorFactoryLocal engine = OperatorFactoryLocal.getInstance(); - OperatorGeneralizeByArea op = (OperatorGeneralizeByArea) engine.getOperator(Operator.Type.GeneralizeByArea); - - assertNotNull(op); - - Polygon poly = new Polygon(); - poly.startPath(0, 0); - poly.lineTo(1, 1); - poly.lineTo(2, 0); - poly.lineTo(3, 2); - poly.lineTo(4, 1); - poly.lineTo(5, 0); - poly.lineTo(5, 10); - poly.lineTo(0, 10); - Geometry geom = op.execute(poly, 100.0, true, GeneralizeType.Neither, SpatialReference.create(4326),null); - assertTrue(geom.isEmpty()); - } - - - @Test - public void testNonSimple() { - String wktInput = "MULTIPOLYGON (((5 -2, 5.1308062584602858 -1.9957178464772056, 5.2610523844401031 -1.9828897227476183, 5.3901806440322559 -1.9615705608064609, 5.5176380902050415 -1.9318516525781355, 5.6428789306063223 -1.8938602589902089, 5.7427813527082074 -1.8569533817705199, 30.742781352708207 8.1430466182294818, 30.765366864730179 8.1522409349774279, 30.884577380438003 8.2062545169346244, 31 8.2679491924311233, 31.111140466039203 8.3370607753949102, 31.217522858017439 8.4132933194175301, 31.318691630200139 8.4963203850420452, 31.414213562373096 8.585786437626906, 31.503679614957953 8.6813083697998632, 31.58670668058247 8.7824771419825591, 31.66293922460509 8.8888595339607956, 31.732050807568879 9, 31.788854381999833 9.1055728090000834, 36.788854381999833 19.105572809000083, 36.793745483065379 19.115422619561997, 36.847759065022572 19.234633135269821, 36.893860258990209 19.357121069393678, 36.931851652578139 19.48236190979496, 36.961570560806464 19.609819355967744, 36.982889722747622 19.738947615559898, 36.995717846477206 19.869193741539714, 37 20, 36.995717846477206 20.130806258460286, 36.982889722747622 20.261052384440102, 36.961570560806457 20.390180644032256, 36.931851652578132 20.51763809020504, 36.893860258990209 20.642878930606322, 36.847759065022572 20.765366864730179, 36.793745483065379 20.884577380438003, 36.788854381999833 20.894427190999917, 31.788854381999833 30.894427190999917, 31.732050807568875 31, 31.66293922460509 31.111140466039203, 31.58670668058247 31.217522858017439, 31.503679614957953 31.318691630200135, 31.414213562373096 31.414213562373092, 31.318691630200139 31.503679614957953, 31.217522858017443 31.58670668058247, 31.111140466039203 31.66293922460509, 31 31.732050807568875, 30.884577380438003 31.793745483065376, 30.765366864730179 31.847759065022572, 30.642878930606322 31.893860258990209, 30.568176659382747 31.917596225416773, 3.5681766593827478 39.917596225416773, 3.5176380902050415 39.931851652578132, 3.3901806440322564 39.961570560806464, 3.2610523844401031 39.982889722747622, 3.1308062584602863 39.995717846477206, 3 40, 2.8691937415397142 39.995717846477206, 2.7389476155598973 39.982889722747615, 2.6098193559677441 39.961570560806464, 2.482361909794959 39.931851652578132, 2.3571210693936773 39.893860258990209, 2.2346331352698212 39.847759065022572, 2.1154226195619983 39.793745483065379, 2.0000000000000009 39.732050807568875, 1.8888595339607963 39.66293922460509, 1.7824771419825594 39.586706680582466, 1.681308369799863 39.503679614957953, 1.5857864376269055 39.414213562373092, 1.4963203850420457 39.318691630200135, 1.4132933194175301 39.217522858017446, 1.33706077539491 39.111140466039203, 1.267949192431123 39, 1.2062545169346237 38.884577380438003, 1.1522409349774267 38.765366864730183, 1.1061397410097888 38.642878930606322, 1.0681483474218634 38.51763809020504, 1.0384294391935391 38.390180644032256, 1.0171102772523792 38.261052384440106, 1.004282153522793 38.130806258460282, 1 38, 1.0042821535227944 37.869193741539718, 1.0171102772523806 37.738947615559894, 1.0384294391935405 37.609819355967744, 1.0681483474218647 37.48236190979496, 1.1061397410097897 37.357121069393678, 1.1522409349774274 37.234633135269817, 1.2062545169346244 37.115422619561997, 1.2679491924311235 37, 1.3370607753949102 36.888859533960797, 1.4132933194175301 36.782477141982561, 1.4963203850420457 36.681308369799865, 1.5857864376269053 36.585786437626908, 1.6813083697998625 36.496320385042047, 1.7824771419825589 36.413293319417534, 1.8888595339607956 36.33706077539491, 2 36.267949192431118, 2.1154226195619974 36.206254516934621, 2.2346331352698208 36.152240934977428, 2.3571210693936768 36.106139741009784, 2.4318233406172522 36.082403774583227, 28.599409577746219 28.329044889507976, 32.763932022500207 20, 28.551206229908889 11.574548414817356, 6.3845784837230717 2.7078973163430327, 6.3268887828044376 2.9386561200175683, 19.26118525018893 13.447771999767468, 19.318691630200139 13.496320385042045, 19.414213562373096 13.585786437626906, 19.503679614957953 13.681308369799863, 19.58670668058247 13.782477141982559, 19.66293922460509 13.888859533960796, 19.732050807568879 14, 19.793745483065376 14.115422619561997, 19.847759065022572 14.234633135269821, 19.893860258990212 14.357121069393678, 19.931851652578136 14.482361909794959, 19.961570560806461 14.609819355967744, 19.982889722747622 14.738947615559898, 19.995717846477206 14.869193741539714, 20 15, 19.995717846477206 15.130806258460286, 19.982889722747618 15.261052384440102, 19.961570560806461 15.390180644032256, 19.931851652578136 15.517638090205041, 19.893860258990209 15.642878930606322, 19.847759065022572 15.765366864730179, 19.793745483065376 15.884577380438001, 19.732050807568875 16, 19.66293922460509 16.111140466039203, 19.58670668058247 16.217522858017439, 19.503679614957953 16.318691630200135, 19.414213562373096 16.414213562373092, 19.318691630200139 16.503679614957953, 19.217522858017443 16.58670668058247, 19.111140466039203 16.66293922460509, 19 16.732050807568875, 18.884577380438003 16.793745483065376, 18.765366864730179 16.847759065022572, 18.642878930606322 16.893860258990209, 18.51763809020504 16.931851652578139, 18.390180644032256 16.961570560806461, 18.261052384440102 16.982889722747622, 18.130806258460286 16.995717846477206, 18 17, 17.869193741539714 16.995717846477206, 17.738947615559898 16.982889722747622, 17.609819355967744 16.961570560806457, 17.48236190979496 16.931851652578136, 17.357121069393678 16.893860258990209, 17.234633135269821 16.847759065022572, 17.115422619561997 16.793745483065376, 17 16.732050807568875, 16.888859533960797 16.66293922460509, 16.782477141982561 16.58670668058247, 16.73881474981107 16.552228000232532, 5.2559522566699819 7.2224022245553954, 1.9402850002906638 20.485071250072664, 1.9318516525781353 20.51763809020504, 1.8938602589902103 20.642878930606322, 1.8477590650225726 20.765366864730179, 1.7937454830653756 20.884577380438003, 1.7320508075688765 21, 1.6629392246050898 21.111140466039203, 1.5867066805824699 21.217522858017439, 1.5036796149579543 21.318691630200139, 1.4142135623730947 21.414213562373096, 1.3186916302001375 21.503679614957953, 1.2175228580174411 21.58670668058247, 1.1111404660392044 21.66293922460509, 0.99999999999999989 21.732050807568879, 0.88457738043800249 21.793745483065376, 0.76536686473017945 21.847759065022572, 0.64287893060632306 21.893860258990212, 0.51763809020504148 21.931851652578136, 0.3901806440322565 21.961570560806461, 0.26105238444010315 21.982889722747622, 0.13080625846028612 21.995717846477206, 0 22, -0.13080625846028585 21.995717846477206, -0.26105238444010281 21.982889722747618, -0.39018064403225611 21.961570560806461, -0.51763809020504103 21.931851652578136, -0.64287893060632262 21.893860258990209, -0.7653668647301789 21.847759065022572, -0.88457738043800183 21.793745483065376, -0.99999999999999922 21.732050807568875, -1.1111404660392037 21.66293922460509, -1.2175228580174406 21.58670668058247, -1.318691630200137 21.503679614957953, -1.4142135623730945 21.414213562373096, -1.5036796149579543 21.318691630200139, -1.5867066805824699 21.217522858017443, -1.66293922460509 21.111140466039203, -1.732050807568877 21, -1.7937454830653763 20.884577380438003, -1.8477590650225733 20.765366864730179, -1.8938602589902112 20.642878930606322, -1.9318516525781366 20.51763809020504, -1.9615705608064609 20.390180644032256, -1.9828897227476208 20.261052384440102, -1.995717846477207 20.130806258460286, -2 20, -1.9957178464772056 19.869193741539714, -1.9828897227476194 19.738947615559898, -1.9615705608064595 19.609819355967744, -1.9402850002906638 19.514928749927336, 1.4516369470040571 5.947240960748454, 1.3901806440322564 5.9615705608064609, 1.2610523844401031 5.982889722747621, 1.130806258460286 5.9957178464772074, 1 6, 0.86919374153971418 5.9957178464772056, 0.73894761555989719 5.9828897227476192, 0.60981935596774384 5.9615705608064591, 0.48236190979495897 5.9318516525781355, 0.35712106939367738 5.8938602589902107, 0.2346331352698211 5.8477590650225721, 0.11542261956199817 5.7937454830653756, 7.7715611723760958E-16 5.7320508075688767, -0.11114046603920369 5.6629392246050898, -0.21752285801744065 5.5867066805824699, -0.31869163020013702 5.5036796149579548, -0.41421356237309448 5.4142135623730949, -0.50367961495795432 5.3186916302001377, -0.58670668058246989 5.2175228580174409, -0.66293922460509003 5.1111404660392044, -0.73205080756887697 5, -0.79374548306537629 4.8845773804380022, -0.84775906502257325 4.7653668647301792, -0.89386025899021115 4.6428789306063232, -0.93185165257813662 4.5176380902050415, -0.96157056080646086 4.3901806440322568, -0.98288972274762076 4.2610523844401031, -0.99571784647720696 4.1308062584602858, -1 4, -0.99571784647720563 3.8691937415397142, -0.98288972274761943 3.7389476155598973, -0.96157056080645953 3.6098193559677441, -0.93185165257813529 3.482361909794959, -0.89386025899021027 3.3571210693936773, -0.84775906502257259 3.2346331352698212, -0.79374548306537562 3.1154226195619983, -0.78885438199983171 3.1055728090000843, 0.21114561800016829 1.1055728090000843, 0.26794919243112347 1.0000000000000009, 0.33706077539491019 0.88885953396079653, 0.41329331941753011 0.78247714198255913, 0.49632038504204568 0.6813083697998632, 0.5857864376269053 0.58578643762690552, 0.68130836979986253 0.49632038504204568, 0.78247714198255891 0.41329331941753011, 0.88885953396079564 0.33706077539491019, 1 0.26794919243112325, 1.1154226195619974 0.20625451693462349, 1.2346331352698205 0.15224093497742697, 1.3571210693936768 0.10613974100978885, 1.4823619097949585 0.068148347421863598, 1.6098193559677436 0.038429439193539139, 1.7389476155598969 0.017110277252379014, 1.8691937415397137 0.0042821535227930418, 2 0, 2.1308062584602858 0.0042821535227943741, 2.2610523844401027 0.01711027725238079, 2.3901806440322559 0.038429439193540471, 2.517638090205041 0.068148347421864486, 2.6428789306063227 0.10613974100978973, 2.7653668647301788 0.15224093497742741, 2.8845773804380017 0.20625451693462438, 2.8866117144161265 0.20734189110017509, 3.0597149997093362 -0.48507125007266438, 3.0681483474218645 -0.5176380902050397, 3.1061397410097897 -0.64287893060632229, 3.1522409349774274 -0.76536686473017923, 3.2062545169346244 -0.88457738043800305, 3.2679491924311233 -1, 3.3370607753949102 -1.1111404660392026, 3.4132933194175301 -1.2175228580174391, 3.4963203850420457 -1.3186916302001386, 3.5857864376269051 -1.4142135623730958, 3.6813083697998623 -1.503679614957953, 3.7824771419825591 -1.5867066805824699, 3.8888595339607956 -1.6629392246050898, 4 -1.7320508075688785, 4.1154226195619978 -1.7937454830653756, 4.2346331352698208 -1.8477590650225721, 4.3571210693936768 -1.8938602589902125, 4.4823619097949585 -1.9318516525781355, 4.6098193559677432 -1.9615705608064609, 4.7389476155598969 -1.9828897227476219, 4.8691937415397142 -1.9957178464772056, 5 -2)))"; - double percentReduce = 60; - boolean removedDegenerates = true; - GeneralizeType generalizeType = GeneralizeType.ResultContainsOriginal; - Geometry geometry = ((OperatorGeneralizeByArea)OperatorFactoryLocal - .getInstance() - .getOperator(Operator.Type.GeneralizeByArea)) - .execute( - GeometryEngine.geometryFromWkt( - wktInput, - 0, - Geometry.Type.Unknown), - percentReduce, - removedDegenerates, - generalizeType, - SpatialReference.create(4326), - null); - - assertTrue(GeometryEngine.isSimple(geometry, SpatialReference.create(4326))); - // TODO fix this sample + @Override + protected void setUp() throws Exception { + super.setUp(); + } + + @Override + protected void tearDown() throws Exception { + super.tearDown(); + } + + @Test + public void test1() { + OperatorFactoryLocal engine = OperatorFactoryLocal.getInstance(); + OperatorGeneralize op = (OperatorGeneralize) engine.getOperator(Operator.Type.Generalize); + + Polygon poly = new Polygon(); + poly.startPath(0, 0); + poly.lineTo(1, 1); + poly.lineTo(2, 0); + poly.lineTo(3, 2); + poly.lineTo(4, 1); + poly.lineTo(5, 0); + poly.lineTo(5, 10); + poly.lineTo(0, 10); + Geometry geom = op.execute(poly, 2, true, null); + Polygon p = (Polygon) geom; + Point2D[] points = p.getCoordinates2D(); + assertTrue(points.length == 4); + assertTrue(points[0].x == 0 && points[0].y == 0); + assertTrue(points[1].x == 5 && points[1].y == 0); + assertTrue(points[2].x == 5 && points[2].y == 10); + assertTrue(points[3].x == 0 && points[3].y == 10); + + Geometry geom1 = op.execute(geom, 5, false, null); + p = (Polygon) geom1; + points = p.getCoordinates2D(); + assertTrue(points.length == 3); + assertTrue(points[0].x == 0 && points[0].y == 0); + assertTrue(points[1].x == 5 && points[1].y == 10); + assertTrue(points[2].x == 5 && points[2].y == 10); + + geom1 = op.execute(geom, 5, true, null); + p = (Polygon) geom1; + points = p.getCoordinates2D(); + assertTrue(points.length == 0); + } + + @Test + public void test2() { + OperatorFactoryLocal engine = OperatorFactoryLocal.getInstance(); + OperatorGeneralize op = (OperatorGeneralize) engine.getOperator(Operator.Type.Generalize); + + Polyline polyline = new Polyline(); + polyline.startPath(0, 0); + polyline.lineTo(1, 1); + polyline.lineTo(2, 0); + polyline.lineTo(3, 2); + polyline.lineTo(4, 1); + polyline.lineTo(5, 0); + polyline.lineTo(5, 10); + polyline.lineTo(0, 10); + Geometry geom = op.execute(polyline, 2, true, null); + Polyline p = (Polyline) geom; + Point2D[] points = p.getCoordinates2D(); + assertTrue(points.length == 4); + assertTrue(points[0].x == 0 && points[0].y == 0); + assertTrue(points[1].x == 5 && points[1].y == 0); + assertTrue(points[2].x == 5 && points[2].y == 10); + assertTrue(points[3].x == 0 && points[3].y == 10); + + Geometry geom1 = op.execute(geom, 5, false, null); + p = (Polyline) geom1; + points = p.getCoordinates2D(); + assertTrue(points.length == 2); + assertTrue(points[0].x == 0 && points[0].y == 0); + assertTrue(points[1].x == 0 && points[1].y == 10); + + geom1 = op.execute(geom, 5, true, null); + p = (Polyline) geom1; + points = p.getCoordinates2D(); + assertTrue(points.length == 2); + assertTrue(points[0].x == 0 && points[0].y == 0); + assertTrue(points[1].x == 0 && points[1].y == 10); + } + + @Test + public void testLargeDeviation() { + { + Polygon input_polygon = new Polygon(); + input_polygon + .addEnvelope(Envelope2D.construct(0, 0, 20, 10), false); + Geometry densified_geom = OperatorDensifyByLength.local().execute( + input_polygon, 1, null); + + Geometry geom = OperatorGeneralize.local().execute( + densified_geom, + 1, + true, + null); + + int pc = ((MultiPath) geom).getPointCount(); + assertTrue(pc == 4); + + Geometry large_dev1 = OperatorGeneralize.local().execute( + densified_geom, + 40, + true, + null); + + int pc1 = ((MultiPath) large_dev1).getPointCount(); + assertTrue(pc1 == 0); + + Geometry large_dev2 = OperatorGeneralize.local().execute( + densified_geom, 40, false, null); + int pc2 = ((MultiPath) large_dev2).getPointCount(); + assertTrue(pc2 == 3); + } + } + + @Test + public void test1Area() { + OperatorFactoryLocal engine = OperatorFactoryLocal.getInstance(); + OperatorGeneralizeByArea op = (OperatorGeneralizeByArea) engine.getOperator(Operator.Type.GeneralizeByArea); + + assertNotNull(op); + + Polygon poly = new Polygon(); + poly.startPath(0, 0); + poly.lineTo(1, 1); + poly.lineTo(2, 0); + poly.lineTo(3, 2); + poly.lineTo(4, 1); + poly.lineTo(5, 0); + poly.lineTo(5, 10); + poly.lineTo(0, 10); + poly = (Polygon) GeometryEngine.simplify(poly, SpatialReference.create(4326)); + String words2 = GeometryEngine.geometryToWkt(poly, 0); + Geometry geom = op.execute(poly, 50.0, true, GeneralizeType.Neither, SpatialReference.create(4326), null); + String words = GeometryEngine.geometryToWkt(geom, 0); + assertNotNull(geom); + Polygon p = (Polygon) geom; + Point2D[] points = p.getCoordinates2D(); + assertTrue(points.length == 4); + assertTrue(points[0].x == 0 && points[0].y == 0); + assertTrue(points[1].x == 0 && points[1].y == 10); + assertTrue(points[2].x == 5 && points[2].y == 10); + assertTrue(points[3].x == 5 && points[3].y == 0); + + OperatorContains operatorContains = (OperatorContains) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.Contains); + + Geometry geomContainer = op.execute(poly, 35, true, GeneralizeType.ResultContainsOriginal, SpatialReference.create(4326), null); + words = GeometryEngine.geometryToWkt(geomContainer, 0); + assertTrue(operatorContains.execute(geomContainer, poly, null, null)); + + Geometry geomContained = op.execute(poly, 30, true, GeneralizeType.ResultWithinOriginal, SpatialReference.create(4326), null); + assertTrue(operatorContains.execute(poly, geomContained, null, null)); + } + + @Test + public void testTreapTriangleManagement() { + Geometry geom = GeometryEngine.geometryFromWkt("POLYGON ((-70.651656936570745 43.135157834585826,-70.651570654984525 43.135150076925918,-70.651511247908587 43.135155437576621,-70.651490101488349 43.135169249238288,-70.651490181628759 43.135200763774868,-70.651510336532638 43.135249995303397,-70.651530872098633 43.135299226354952,-70.651527230781454 43.13533529757845,-70.651492643684321 43.135349305799508,-70.651420172717224 43.135354852495823,-70.651358095085072 43.135346745253884,-70.651247805462717 43.1353033127629,-70.651151361236941 43.135246176463966,-70.651124154255413 43.135206047556842,-70.651131534466685 43.135151913051203,-70.651155914561755 43.135115542493892,-70.651228730259774 43.135051460048707,-70.651305024993519 43.134973818456416,-70.651360674948108 43.134914489191551,-70.651385078643216 43.134864606853156,-70.651402827815218 43.134810322639233,-70.651410310326284 43.134688652284062,-70.651418383361829 43.134517448375689,-70.651411370788409 43.134413996600074,-70.651390876709428 43.134337752182525,-70.651346755889904 43.134225832686319,-70.651289063395154 43.134109606122337,-70.651241242017051 43.133988734078358,-70.651230854601764 43.133916846697829,-70.651221013347353 43.133822439140715,-70.651200519724128 43.133746194674302,-70.651218268699594 43.133691907971006,-70.651249886578313 43.133610413350418,-70.651273539332081 43.133547040518167,-70.651322422674269 43.133478800504768,-70.651363984222755 43.133424172537978,-70.651409243106613 43.133378494590104,-70.651437060507462 43.133355583224628,-70.651492145648632 43.133332275678633,-70.651599176252716 43.133326227462319,-70.651675204034788 43.133338636120527,-70.65179240423565 43.133381970944697,-70.651905761872953 43.133425353603045,-70.652022336036893 43.133459688797871,-70.652087607730962 43.133472252516562,-70.652149929372143 43.133489361765591,-70.652232540703992 43.133546699550045,-70.652400720753761 43.133656829257461,-70.652544825855031 43.133771806958798,-70.652698657471404 43.133891149422396,-70.652757214424 43.133939825915917,-70.652829643131227 43.133961291723317,-70.652898471779253 43.133991812912122,-70.652963804959569 43.134049399817712,-70.652994471176711 43.134089475795044,-70.653000937222828 43.134129903976664,-70.653000836372897 43.134197439788288,-70.652972837499135 43.134256375321229,-70.652903321638362 43.134342923487154,-70.652853489821567 43.134447199036558,-70.652811504521395 43.134528843881576,-70.652776716073816 43.134592380423527,-70.652745241323998 43.134664872485907,-70.652703717209832 43.134692485155824,-70.65263790191085 43.134702441934749,-70.652537796343609 43.13469488220742,-70.652481986521252 43.134691185475688,-70.652416288981371 43.134705643385693,-70.652347112331768 43.134733655368471,-70.652274294763117 43.134797738553125,-70.652239263183958 43.134852272522785,-70.652214826278225 43.134929165745028,-70.652217533236211 43.135001163954207,-70.652248296977746 43.135059250554882,-70.652327397729081 43.135157158057844,-70.652414500765317 43.135209927851037,-70.6524582645621 43.135222802033766,-70.652470512866358 43.135249637832572,-70.652646824360005 43.135404671730349,-70.652680946443695 43.135444700365824,-70.652691113545444 43.135494075980539,-70.652680299690047 43.135534756334458,-70.652642334694633 43.135580326883741,-70.6525831461027 43.135608197219,-70.65250042683256 43.135618395612582,-70.652403778584315 43.1356107883392,-70.652328034290349 43.135580366756479,-70.652207940292428 43.135501055652128,-70.652084166050003 43.135399292820672,-70.651994153636025 43.135324055019495,-70.651897729053019 43.13525341019011,-70.651815354685922 43.135205077192047,-70.651746646220062 43.135179051560065,-70.651656936570745 43.135157834585826))", 0, Geometry.Type.Unknown); + geom = GeometryEngine.simplify(geom, SpatialReference.create(4326)); + + OperatorFactoryLocal engine = OperatorFactoryLocal.getInstance(); + OperatorGeneralizeByArea op = (OperatorGeneralizeByArea) engine.getOperator(Operator.Type.GeneralizeByArea); + OperatorContains operatorContains = (OperatorContains) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.Contains); + + Geometry geomContainer = op.execute(geom, 15, true, GeneralizeType.ResultContainsOriginal, SpatialReference.create(4326), null); + String words = GeometryEngine.geometryToWkt(geomContainer, 0); + assertTrue(operatorContains.execute(geomContainer, geom, null, null)); + } + + @Test + public void testCompleteRemoval() { + + OperatorFactoryLocal engine = OperatorFactoryLocal.getInstance(); + OperatorGeneralizeByArea op = (OperatorGeneralizeByArea) engine.getOperator(Operator.Type.GeneralizeByArea); + + assertNotNull(op); + + Polygon poly = new Polygon(); + poly.startPath(0, 0); + poly.lineTo(1, 1); + poly.lineTo(2, 0); + poly.lineTo(3, 2); + poly.lineTo(4, 1); + poly.lineTo(5, 0); + poly.lineTo(5, 10); + poly.lineTo(0, 10); + Geometry geom = op.execute(poly, 100.0, true, GeneralizeType.Neither, SpatialReference.create(4326), null); + assertTrue(geom.isEmpty()); + } + + + @Test + public void testNonSimple() { + String wktInput = "MULTIPOLYGON (((5 -2, 5.1308062584602858 -1.9957178464772056, 5.2610523844401031 -1.9828897227476183, 5.3901806440322559 -1.9615705608064609, 5.5176380902050415 -1.9318516525781355, 5.6428789306063223 -1.8938602589902089, 5.7427813527082074 -1.8569533817705199, 30.742781352708207 8.1430466182294818, 30.765366864730179 8.1522409349774279, 30.884577380438003 8.2062545169346244, 31 8.2679491924311233, 31.111140466039203 8.3370607753949102, 31.217522858017439 8.4132933194175301, 31.318691630200139 8.4963203850420452, 31.414213562373096 8.585786437626906, 31.503679614957953 8.6813083697998632, 31.58670668058247 8.7824771419825591, 31.66293922460509 8.8888595339607956, 31.732050807568879 9, 31.788854381999833 9.1055728090000834, 36.788854381999833 19.105572809000083, 36.793745483065379 19.115422619561997, 36.847759065022572 19.234633135269821, 36.893860258990209 19.357121069393678, 36.931851652578139 19.48236190979496, 36.961570560806464 19.609819355967744, 36.982889722747622 19.738947615559898, 36.995717846477206 19.869193741539714, 37 20, 36.995717846477206 20.130806258460286, 36.982889722747622 20.261052384440102, 36.961570560806457 20.390180644032256, 36.931851652578132 20.51763809020504, 36.893860258990209 20.642878930606322, 36.847759065022572 20.765366864730179, 36.793745483065379 20.884577380438003, 36.788854381999833 20.894427190999917, 31.788854381999833 30.894427190999917, 31.732050807568875 31, 31.66293922460509 31.111140466039203, 31.58670668058247 31.217522858017439, 31.503679614957953 31.318691630200135, 31.414213562373096 31.414213562373092, 31.318691630200139 31.503679614957953, 31.217522858017443 31.58670668058247, 31.111140466039203 31.66293922460509, 31 31.732050807568875, 30.884577380438003 31.793745483065376, 30.765366864730179 31.847759065022572, 30.642878930606322 31.893860258990209, 30.568176659382747 31.917596225416773, 3.5681766593827478 39.917596225416773, 3.5176380902050415 39.931851652578132, 3.3901806440322564 39.961570560806464, 3.2610523844401031 39.982889722747622, 3.1308062584602863 39.995717846477206, 3 40, 2.8691937415397142 39.995717846477206, 2.7389476155598973 39.982889722747615, 2.6098193559677441 39.961570560806464, 2.482361909794959 39.931851652578132, 2.3571210693936773 39.893860258990209, 2.2346331352698212 39.847759065022572, 2.1154226195619983 39.793745483065379, 2.0000000000000009 39.732050807568875, 1.8888595339607963 39.66293922460509, 1.7824771419825594 39.586706680582466, 1.681308369799863 39.503679614957953, 1.5857864376269055 39.414213562373092, 1.4963203850420457 39.318691630200135, 1.4132933194175301 39.217522858017446, 1.33706077539491 39.111140466039203, 1.267949192431123 39, 1.2062545169346237 38.884577380438003, 1.1522409349774267 38.765366864730183, 1.1061397410097888 38.642878930606322, 1.0681483474218634 38.51763809020504, 1.0384294391935391 38.390180644032256, 1.0171102772523792 38.261052384440106, 1.004282153522793 38.130806258460282, 1 38, 1.0042821535227944 37.869193741539718, 1.0171102772523806 37.738947615559894, 1.0384294391935405 37.609819355967744, 1.0681483474218647 37.48236190979496, 1.1061397410097897 37.357121069393678, 1.1522409349774274 37.234633135269817, 1.2062545169346244 37.115422619561997, 1.2679491924311235 37, 1.3370607753949102 36.888859533960797, 1.4132933194175301 36.782477141982561, 1.4963203850420457 36.681308369799865, 1.5857864376269053 36.585786437626908, 1.6813083697998625 36.496320385042047, 1.7824771419825589 36.413293319417534, 1.8888595339607956 36.33706077539491, 2 36.267949192431118, 2.1154226195619974 36.206254516934621, 2.2346331352698208 36.152240934977428, 2.3571210693936768 36.106139741009784, 2.4318233406172522 36.082403774583227, 28.599409577746219 28.329044889507976, 32.763932022500207 20, 28.551206229908889 11.574548414817356, 6.3845784837230717 2.7078973163430327, 6.3268887828044376 2.9386561200175683, 19.26118525018893 13.447771999767468, 19.318691630200139 13.496320385042045, 19.414213562373096 13.585786437626906, 19.503679614957953 13.681308369799863, 19.58670668058247 13.782477141982559, 19.66293922460509 13.888859533960796, 19.732050807568879 14, 19.793745483065376 14.115422619561997, 19.847759065022572 14.234633135269821, 19.893860258990212 14.357121069393678, 19.931851652578136 14.482361909794959, 19.961570560806461 14.609819355967744, 19.982889722747622 14.738947615559898, 19.995717846477206 14.869193741539714, 20 15, 19.995717846477206 15.130806258460286, 19.982889722747618 15.261052384440102, 19.961570560806461 15.390180644032256, 19.931851652578136 15.517638090205041, 19.893860258990209 15.642878930606322, 19.847759065022572 15.765366864730179, 19.793745483065376 15.884577380438001, 19.732050807568875 16, 19.66293922460509 16.111140466039203, 19.58670668058247 16.217522858017439, 19.503679614957953 16.318691630200135, 19.414213562373096 16.414213562373092, 19.318691630200139 16.503679614957953, 19.217522858017443 16.58670668058247, 19.111140466039203 16.66293922460509, 19 16.732050807568875, 18.884577380438003 16.793745483065376, 18.765366864730179 16.847759065022572, 18.642878930606322 16.893860258990209, 18.51763809020504 16.931851652578139, 18.390180644032256 16.961570560806461, 18.261052384440102 16.982889722747622, 18.130806258460286 16.995717846477206, 18 17, 17.869193741539714 16.995717846477206, 17.738947615559898 16.982889722747622, 17.609819355967744 16.961570560806457, 17.48236190979496 16.931851652578136, 17.357121069393678 16.893860258990209, 17.234633135269821 16.847759065022572, 17.115422619561997 16.793745483065376, 17 16.732050807568875, 16.888859533960797 16.66293922460509, 16.782477141982561 16.58670668058247, 16.73881474981107 16.552228000232532, 5.2559522566699819 7.2224022245553954, 1.9402850002906638 20.485071250072664, 1.9318516525781353 20.51763809020504, 1.8938602589902103 20.642878930606322, 1.8477590650225726 20.765366864730179, 1.7937454830653756 20.884577380438003, 1.7320508075688765 21, 1.6629392246050898 21.111140466039203, 1.5867066805824699 21.217522858017439, 1.5036796149579543 21.318691630200139, 1.4142135623730947 21.414213562373096, 1.3186916302001375 21.503679614957953, 1.2175228580174411 21.58670668058247, 1.1111404660392044 21.66293922460509, 0.99999999999999989 21.732050807568879, 0.88457738043800249 21.793745483065376, 0.76536686473017945 21.847759065022572, 0.64287893060632306 21.893860258990212, 0.51763809020504148 21.931851652578136, 0.3901806440322565 21.961570560806461, 0.26105238444010315 21.982889722747622, 0.13080625846028612 21.995717846477206, 0 22, -0.13080625846028585 21.995717846477206, -0.26105238444010281 21.982889722747618, -0.39018064403225611 21.961570560806461, -0.51763809020504103 21.931851652578136, -0.64287893060632262 21.893860258990209, -0.7653668647301789 21.847759065022572, -0.88457738043800183 21.793745483065376, -0.99999999999999922 21.732050807568875, -1.1111404660392037 21.66293922460509, -1.2175228580174406 21.58670668058247, -1.318691630200137 21.503679614957953, -1.4142135623730945 21.414213562373096, -1.5036796149579543 21.318691630200139, -1.5867066805824699 21.217522858017443, -1.66293922460509 21.111140466039203, -1.732050807568877 21, -1.7937454830653763 20.884577380438003, -1.8477590650225733 20.765366864730179, -1.8938602589902112 20.642878930606322, -1.9318516525781366 20.51763809020504, -1.9615705608064609 20.390180644032256, -1.9828897227476208 20.261052384440102, -1.995717846477207 20.130806258460286, -2 20, -1.9957178464772056 19.869193741539714, -1.9828897227476194 19.738947615559898, -1.9615705608064595 19.609819355967744, -1.9402850002906638 19.514928749927336, 1.4516369470040571 5.947240960748454, 1.3901806440322564 5.9615705608064609, 1.2610523844401031 5.982889722747621, 1.130806258460286 5.9957178464772074, 1 6, 0.86919374153971418 5.9957178464772056, 0.73894761555989719 5.9828897227476192, 0.60981935596774384 5.9615705608064591, 0.48236190979495897 5.9318516525781355, 0.35712106939367738 5.8938602589902107, 0.2346331352698211 5.8477590650225721, 0.11542261956199817 5.7937454830653756, 7.7715611723760958E-16 5.7320508075688767, -0.11114046603920369 5.6629392246050898, -0.21752285801744065 5.5867066805824699, -0.31869163020013702 5.5036796149579548, -0.41421356237309448 5.4142135623730949, -0.50367961495795432 5.3186916302001377, -0.58670668058246989 5.2175228580174409, -0.66293922460509003 5.1111404660392044, -0.73205080756887697 5, -0.79374548306537629 4.8845773804380022, -0.84775906502257325 4.7653668647301792, -0.89386025899021115 4.6428789306063232, -0.93185165257813662 4.5176380902050415, -0.96157056080646086 4.3901806440322568, -0.98288972274762076 4.2610523844401031, -0.99571784647720696 4.1308062584602858, -1 4, -0.99571784647720563 3.8691937415397142, -0.98288972274761943 3.7389476155598973, -0.96157056080645953 3.6098193559677441, -0.93185165257813529 3.482361909794959, -0.89386025899021027 3.3571210693936773, -0.84775906502257259 3.2346331352698212, -0.79374548306537562 3.1154226195619983, -0.78885438199983171 3.1055728090000843, 0.21114561800016829 1.1055728090000843, 0.26794919243112347 1.0000000000000009, 0.33706077539491019 0.88885953396079653, 0.41329331941753011 0.78247714198255913, 0.49632038504204568 0.6813083697998632, 0.5857864376269053 0.58578643762690552, 0.68130836979986253 0.49632038504204568, 0.78247714198255891 0.41329331941753011, 0.88885953396079564 0.33706077539491019, 1 0.26794919243112325, 1.1154226195619974 0.20625451693462349, 1.2346331352698205 0.15224093497742697, 1.3571210693936768 0.10613974100978885, 1.4823619097949585 0.068148347421863598, 1.6098193559677436 0.038429439193539139, 1.7389476155598969 0.017110277252379014, 1.8691937415397137 0.0042821535227930418, 2 0, 2.1308062584602858 0.0042821535227943741, 2.2610523844401027 0.01711027725238079, 2.3901806440322559 0.038429439193540471, 2.517638090205041 0.068148347421864486, 2.6428789306063227 0.10613974100978973, 2.7653668647301788 0.15224093497742741, 2.8845773804380017 0.20625451693462438, 2.8866117144161265 0.20734189110017509, 3.0597149997093362 -0.48507125007266438, 3.0681483474218645 -0.5176380902050397, 3.1061397410097897 -0.64287893060632229, 3.1522409349774274 -0.76536686473017923, 3.2062545169346244 -0.88457738043800305, 3.2679491924311233 -1, 3.3370607753949102 -1.1111404660392026, 3.4132933194175301 -1.2175228580174391, 3.4963203850420457 -1.3186916302001386, 3.5857864376269051 -1.4142135623730958, 3.6813083697998623 -1.503679614957953, 3.7824771419825591 -1.5867066805824699, 3.8888595339607956 -1.6629392246050898, 4 -1.7320508075688785, 4.1154226195619978 -1.7937454830653756, 4.2346331352698208 -1.8477590650225721, 4.3571210693936768 -1.8938602589902125, 4.4823619097949585 -1.9318516525781355, 4.6098193559677432 -1.9615705608064609, 4.7389476155598969 -1.9828897227476219, 4.8691937415397142 -1.9957178464772056, 5 -2)))"; + double percentReduce = 60; + boolean removedDegenerates = true; + GeneralizeType generalizeType = GeneralizeType.ResultContainsOriginal; + Geometry geometry = ((OperatorGeneralizeByArea) OperatorFactoryLocal + .getInstance() + .getOperator(Operator.Type.GeneralizeByArea)) + .execute( + GeometryEngine.geometryFromWkt( + wktInput, + 0, + Geometry.Type.Unknown), + percentReduce, + removedDegenerates, + generalizeType, + SpatialReference.create(4326), + null); + + assertTrue(GeometryEngine.isSimple(geometry, SpatialReference.create(4326))); + // TODO fix this sample // assertTrue(GeometryEngine.contains( // geometry, // GeometryEngine.geometryFromWkt( @@ -251,22 +251,22 @@ public void testNonSimple() { // 0, // Geometry.Type.Unknown), // SpatialReference.create(4326))); - } - - @Test - public void testCorruptedTreapSearch() { - String wktInput = "MULTIPOLYGON (((5 2, 5.1308062584602858 2.0042821535227944, 5.2610523844401031 2.0171102772523808, 5.3901806440322559 2.0384294391935405, 5.5176380902050415 2.0681483474218645, 5.6428789306063223 2.1061397410097897, 5.7653668647301792 2.1522409349774274, 5.8845773804380022 2.2062545169346244, 5.9999999999999991 2.2679491924311233, 6.1111404660392035 2.3370607753949102, 6.2175228580174409 2.4132933194175301, 6.3186916302001368 2.4963203850420457, 6.414213562373094 2.5857864376269051, 6.5036796149579548 2.6813083697998623, 6.5867066805824699 2.7824771419825591, 6.6629392246050898 2.8888595339607956, 6.7320508075688767 3, 6.7937454830653765 3.1154226195619974, 6.847759065022573 3.2346331352698208, 6.8938602589902107 3.3571210693936768, 6.9318516525781364 3.4823619097949585, 6.9615705608064609 3.6098193559677436, 6.982889722747621 3.7389476155598969, 6.9957178464772074 3.8691937415397137, 7 4, 6.9992288413054364 4.0555341344807063, 6.6947736951561883 15.015919395853626, 11.905851560026523 31.393592685446105, 11.931851652578136 31.48236190979496, 11.961570560806461 31.609819355967744, 11.982889722747622 31.738947615559898, 11.995717846477207 31.869193741539714, 12 32, 11.995717846477206 32.130806258460282, 11.982889722747618 32.261052384440106, 11.961570560806459 32.390180644032256, 11.931851652578136 32.51763809020504, 11.893860258990211 32.642878930606322, 11.847759065022572 32.765366864730183, 11.793745483065376 32.884577380438003, 11.732050807568877 33, 11.66293922460509 33.111140466039203, 11.6 33.200000000000003, 5.5999999999999996 41.200000000000003, 5.5867066805824699 41.217522858017439, 5.5036796149579548 41.318691630200135, 5.4142135623730949 41.414213562373092, 5.3186916302001377 41.503679614957953, 5.2175228580174409 41.586706680582466, 5.1111404660392044 41.66293922460509, 5 41.732050807568875, 4.8845773804380022 41.793745483065379, 4.7653668647301792 41.847759065022572, 4.6428789306063232 41.893860258990209, 4.5176380902050415 41.931851652578139, 4.3901806440322568 41.961570560806464, 4.2610523844401031 41.982889722747622, 4.1308062584602858 41.995717846477206, 4 42, 3.8691937415397142 41.995717846477206, 3.7389476155598973 41.982889722747622, 3.6098193559677441 41.961570560806457, 3.482361909794959 41.931851652578132, 3.3571210693936773 41.893860258990209, 3.2346331352698212 41.847759065022572, 3.1154226195619983 41.793745483065379, 3.0000000000000009 41.732050807568875, 2.8888595339607965 41.66293922460509, 2.7824771419825591 41.586706680582466, 2.6813083697998632 41.503679614957953, 2.5857864376269055 41.414213562373092, 2.4963203850420457 41.318691630200135, 2.4132933194175301 41.217522858017439, 2.3370607753949102 41.111140466039203, 2.2679491924311233 41, 2.2062545169346235 40.884577380438003, 2.152240934977427 40.765366864730183, 2.1061397410097888 40.642878930606322, 2.0681483474218636 40.51763809020504, 2.0384294391935391 40.390180644032256, 2.017110277252379 40.261052384440106, 2.004282153522793 40.130806258460289, 2 40, 2.0007711586945636 39.944465865519291, 2.6774890785664391 15.582620750131777, 1.094148439973476 10.606407314553895, 1.0681483474218636 10.517638090205041, 1.0384294391935391 10.390180644032256, 1.017110277252379 10.261052384440102, 1.004282153522793 10.130806258460286, 1 10, 1.0042821535227944 9.8691937415397142, 1.0171102772523808 9.7389476155598977, 1.0384294391935405 9.6098193559677441, 1.0681483474218645 9.4823619097949585, 1.1061397410097897 9.3571210693936777, 1.1522409349774274 9.2346331352698208, 1.2062545169346244 9.115422619561997, 1.2679491924311233 9, 1.3370607753949102 8.8888595339607974, 1.4132933194175301 8.7824771419825591, 1.4963203850420457 8.6813083697998632, 1.5857864376269051 8.585786437626906, 1.6813083697998623 8.4963203850420452, 1.7824771419825591 8.4132933194175301, 1.8888595339607956 8.3370607753949102, 2 8.2679491924311233, 2.1154226195619974 8.2062545169346244, 2.2346331352698208 8.1522409349774279, 2.3571210693936768 8.1061397410097893, 2.4823619097949585 8.0681483474218645, 2.6098193559677436 8.0384294391935391, 2.7389476155598969 8.0171102772523781, 2.8691937415397137 8.0042821535227926, 2.888015599689953 8.0036659896852811, 3.0007711586945636 3.9444658655192932, 3.0042821535227944 3.8691937415397142, 3.0171102772523808 3.7389476155598973, 3.0384294391935405 3.6098193559677441, 3.0681483474218645 3.482361909794959, 3.1061397410097897 3.3571210693936773, 3.1522409349774274 3.2346331352698212, 3.2062545169346244 3.1154226195619983, 3.2679491924311233 3.0000000000000009, 3.3370607753949102 2.8888595339607965, 3.4132933194175301 2.7824771419825591, 3.4963203850420457 2.6813083697998632, 3.5857864376269051 2.5857864376269055, 3.6813083697998623 2.4963203850420457, 3.7824771419825591 2.4132933194175301, 3.8888595339607956 2.3370607753949102, 4 2.2679491924311233, 4.1154226195619978 2.2062545169346235, 4.2346331352698208 2.152240934977427, 4.3571210693936768 2.1061397410097888, 4.4823619097949585 2.0681483474218636, 4.6098193559677432 2.0384294391935391, 4.7389476155598969 2.017110277252379, 4.8691937415397142 2.004282153522793, 5 2), (6.3577402241893219 27.149124350660838, 6.1738780506195123 33.768162599173976, 7.7816940790703999 31.624407894572798, 6.3577402241893219 27.149124350660838)))\n" + - "MULTIPOLYGON (((5 -2, 5.1308062584602858 -1.9957178464772056, 5.2610523844401031 -1.9828897227476183, 5.3901806440322559 -1.9615705608064609, 5.5176380902050415 -1.9318516525781355, 5.6428789306063223 -1.8938602589902089, 5.7427813527082074 -1.8569533817705199, 30.742781352708207 8.1430466182294818, 30.765366864730179 8.1522409349774279, 30.884577380438003 8.2062545169346244, 31 8.2679491924311233, 31.111140466039203 8.3370607753949102, 31.217522858017439 8.4132933194175301, 31.318691630200139 8.4963203850420452, 31.414213562373096 8.585786437626906, 31.503679614957953 8.6813083697998632, 31.58670668058247 8.7824771419825591, 31.66293922460509 8.8888595339607956, 31.732050807568879 9, 31.788854381999833 9.1055728090000834, 36.788854381999833 19.105572809000083, 36.793745483065379 19.115422619561997, 36.847759065022572 19.234633135269821, 36.893860258990209 19.357121069393678, 36.931851652578139 19.48236190979496, 36.961570560806464 19.609819355967744, 36.982889722747622 19.738947615559898, 36.995717846477206 19.869193741539714, 37 20, 36.995717846477206 20.130806258460286, 36.982889722747622 20.261052384440102, 36.961570560806457 20.390180644032256, 36.931851652578132 20.51763809020504, 36.893860258990209 20.642878930606322, 36.847759065022572 20.765366864730179, 36.793745483065379 20.884577380438003, 36.788854381999833 20.894427190999917, 31.788854381999833 30.894427190999917, 31.732050807568875 31, 31.66293922460509 31.111140466039203, 31.58670668058247 31.217522858017439, 31.503679614957953 31.318691630200135, 31.414213562373096 31.414213562373092, 31.318691630200139 31.503679614957953, 31.217522858017443 31.58670668058247, 31.111140466039203 31.66293922460509, 31 31.732050807568875, 30.884577380438003 31.793745483065376, 30.765366864730179 31.847759065022572, 30.642878930606322 31.893860258990209, 30.568176659382747 31.917596225416773, 3.5681766593827478 39.917596225416773, 3.5176380902050415 39.931851652578132, 3.3901806440322564 39.961570560806464, 3.2610523844401031 39.982889722747622, 3.1308062584602863 39.995717846477206, 3 40, 2.8691937415397142 39.995717846477206, 2.7389476155598973 39.982889722747615, 2.6098193559677441 39.961570560806464, 2.482361909794959 39.931851652578132, 2.3571210693936773 39.893860258990209, 2.2346331352698212 39.847759065022572, 2.1154226195619983 39.793745483065379, 2.0000000000000009 39.732050807568875, 1.8888595339607963 39.66293922460509, 1.7824771419825594 39.586706680582466, 1.681308369799863 39.503679614957953, 1.5857864376269055 39.414213562373092, 1.4963203850420457 39.318691630200135, 1.4132933194175301 39.217522858017446, 1.33706077539491 39.111140466039203, 1.267949192431123 39, 1.2062545169346237 38.884577380438003, 1.1522409349774267 38.765366864730183, 1.1061397410097888 38.642878930606322, 1.0681483474218634 38.51763809020504, 1.0384294391935391 38.390180644032256, 1.0171102772523792 38.261052384440106, 1.004282153522793 38.130806258460282, 1 38, 1.0042821535227944 37.869193741539718, 1.0171102772523806 37.738947615559894, 1.0384294391935405 37.609819355967744, 1.0681483474218647 37.48236190979496, 1.1061397410097897 37.357121069393678, 1.1522409349774274 37.234633135269817, 1.2062545169346244 37.115422619561997, 1.2679491924311235 37, 1.3370607753949102 36.888859533960797, 1.4132933194175301 36.782477141982561, 1.4963203850420457 36.681308369799865, 1.5857864376269053 36.585786437626908, 1.6813083697998625 36.496320385042047, 1.7824771419825589 36.413293319417534, 1.8888595339607956 36.33706077539491, 2 36.267949192431118, 2.1154226195619974 36.206254516934621, 2.2346331352698208 36.152240934977428, 2.3571210693936768 36.106139741009784, 2.4318233406172522 36.082403774583227, 28.599409577746219 28.329044889507976, 32.763932022500207 20, 28.551206229908889 11.574548414817356, 6.3845784837230717 2.7078973163430327, 6.3268887828044376 2.9386561200175683, 19.26118525018893 13.447771999767468, 19.318691630200139 13.496320385042045, 19.414213562373096 13.585786437626906, 19.503679614957953 13.681308369799863, 19.58670668058247 13.782477141982559, 19.66293922460509 13.888859533960796, 19.732050807568879 14, 19.793745483065376 14.115422619561997, 19.847759065022572 14.234633135269821, 19.893860258990212 14.357121069393678, 19.931851652578136 14.482361909794959, 19.961570560806461 14.609819355967744, 19.982889722747622 14.738947615559898, 19.995717846477206 14.869193741539714, 20 15, 19.995717846477206 15.130806258460286, 19.982889722747618 15.261052384440102, 19.961570560806461 15.390180644032256, 19.931851652578136 15.517638090205041, 19.893860258990209 15.642878930606322, 19.847759065022572 15.765366864730179, 19.793745483065376 15.884577380438001, 19.732050807568875 16, 19.66293922460509 16.111140466039203, 19.58670668058247 16.217522858017439, 19.503679614957953 16.318691630200135, 19.414213562373096 16.414213562373092, 19.318691630200139 16.503679614957953, 19.217522858017443 16.58670668058247, 19.111140466039203 16.66293922460509, 19 16.732050807568875, 18.884577380438003 16.793745483065376, 18.765366864730179 16.847759065022572, 18.642878930606322 16.893860258990209, 18.51763809020504 16.931851652578139, 18.390180644032256 16.961570560806461, 18.261052384440102 16.982889722747622, 18.130806258460286 16.995717846477206, 18 17, 17.869193741539714 16.995717846477206, 17.738947615559898 16.982889722747622, 17.609819355967744 16.961570560806457, 17.48236190979496 16.931851652578136, 17.357121069393678 16.893860258990209, 17.234633135269821 16.847759065022572, 17.115422619561997 16.793745483065376, 17 16.732050807568875, 16.888859533960797 16.66293922460509, 16.782477141982561 16.58670668058247, 16.73881474981107 16.552228000232532, 5.2559522566699819 7.2224022245553954, 1.9402850002906638 20.485071250072664, 1.9318516525781353 20.51763809020504, 1.8938602589902103 20.642878930606322, 1.8477590650225726 20.765366864730179, 1.7937454830653756 20.884577380438003, 1.7320508075688765 21, 1.6629392246050898 21.111140466039203, 1.5867066805824699 21.217522858017439, 1.5036796149579543 21.318691630200139, 1.4142135623730947 21.414213562373096, 1.3186916302001375 21.503679614957953, 1.2175228580174411 21.58670668058247, 1.1111404660392044 21.66293922460509, 0.99999999999999989 21.732050807568879, 0.88457738043800249 21.793745483065376, 0.76536686473017945 21.847759065022572, 0.64287893060632306 21.893860258990212, 0.51763809020504148 21.931851652578136, 0.3901806440322565 21.961570560806461, 0.26105238444010315 21.982889722747622, 0.13080625846028612 21.995717846477206, 0 22, -0.13080625846028585 21.995717846477206, -0.26105238444010281 21.982889722747618, -0.39018064403225611 21.961570560806461, -0.51763809020504103 21.931851652578136, -0.64287893060632262 21.893860258990209, -0.7653668647301789 21.847759065022572, -0.88457738043800183 21.793745483065376, -0.99999999999999922 21.732050807568875, -1.1111404660392037 21.66293922460509, -1.2175228580174406 21.58670668058247, -1.318691630200137 21.503679614957953, -1.4142135623730945 21.414213562373096, -1.5036796149579543 21.318691630200139, -1.5867066805824699 21.217522858017443, -1.66293922460509 21.111140466039203, -1.732050807568877 21, -1.7937454830653763 20.884577380438003, -1.8477590650225733 20.765366864730179, -1.8938602589902112 20.642878930606322, -1.9318516525781366 20.51763809020504, -1.9615705608064609 20.390180644032256, -1.9828897227476208 20.261052384440102, -1.995717846477207 20.130806258460286, -2 20, -1.9957178464772056 19.869193741539714, -1.9828897227476194 19.738947615559898, -1.9615705608064595 19.609819355967744, -1.9402850002906638 19.514928749927336, 1.4516369470040571 5.947240960748454, 1.3901806440322564 5.9615705608064609, 1.2610523844401031 5.982889722747621, 1.130806258460286 5.9957178464772074, 1 6, 0.86919374153971418 5.9957178464772056, 0.73894761555989719 5.9828897227476192, 0.60981935596774384 5.9615705608064591, 0.48236190979495897 5.9318516525781355, 0.35712106939367738 5.8938602589902107, 0.2346331352698211 5.8477590650225721, 0.11542261956199817 5.7937454830653756, 7.7715611723760958E-16 5.7320508075688767, -0.11114046603920369 5.6629392246050898, -0.21752285801744065 5.5867066805824699, -0.31869163020013702 5.5036796149579548, -0.41421356237309448 5.4142135623730949, -0.50367961495795432 5.3186916302001377, -0.58670668058246989 5.2175228580174409, -0.66293922460509003 5.1111404660392044, -0.73205080756887697 5, -0.79374548306537629 4.8845773804380022, -0.84775906502257325 4.7653668647301792, -0.89386025899021115 4.6428789306063232, -0.93185165257813662 4.5176380902050415, -0.96157056080646086 4.3901806440322568, -0.98288972274762076 4.2610523844401031, -0.99571784647720696 4.1308062584602858, -1 4, -0.99571784647720563 3.8691937415397142, -0.98288972274761943 3.7389476155598973, -0.96157056080645953 3.6098193559677441, -0.93185165257813529 3.482361909794959, -0.89386025899021027 3.3571210693936773, -0.84775906502257259 3.2346331352698212, -0.79374548306537562 3.1154226195619983, -0.78885438199983171 3.1055728090000843, 0.21114561800016829 1.1055728090000843, 0.26794919243112347 1.0000000000000009, 0.33706077539491019 0.88885953396079653, 0.41329331941753011 0.78247714198255913, 0.49632038504204568 0.6813083697998632, 0.5857864376269053 0.58578643762690552, 0.68130836979986253 0.49632038504204568, 0.78247714198255891 0.41329331941753011, 0.88885953396079564 0.33706077539491019, 1 0.26794919243112325, 1.1154226195619974 0.20625451693462349, 1.2346331352698205 0.15224093497742697, 1.3571210693936768 0.10613974100978885, 1.4823619097949585 0.068148347421863598, 1.6098193559677436 0.038429439193539139, 1.7389476155598969 0.017110277252379014, 1.8691937415397137 0.0042821535227930418, 2 0, 2.1308062584602858 0.0042821535227943741, 2.2610523844401027 0.01711027725238079, 2.3901806440322559 0.038429439193540471, 2.517638090205041 0.068148347421864486, 2.6428789306063227 0.10613974100978973, 2.7653668647301788 0.15224093497742741, 2.8845773804380017 0.20625451693462438, 2.8866117144161265 0.20734189110017509, 3.0597149997093362 -0.48507125007266438, 3.0681483474218645 -0.5176380902050397, 3.1061397410097897 -0.64287893060632229, 3.1522409349774274 -0.76536686473017923, 3.2062545169346244 -0.88457738043800305, 3.2679491924311233 -1, 3.3370607753949102 -1.1111404660392026, 3.4132933194175301 -1.2175228580174391, 3.4963203850420457 -1.3186916302001386, 3.5857864376269051 -1.4142135623730958, 3.6813083697998623 -1.503679614957953, 3.7824771419825591 -1.5867066805824699, 3.8888595339607956 -1.6629392246050898, 4 -1.7320508075688785, 4.1154226195619978 -1.7937454830653756, 4.2346331352698208 -1.8477590650225721, 4.3571210693936768 -1.8938602589902125, 4.4823619097949585 -1.9318516525781355, 4.6098193559677432 -1.9615705608064609, 4.7389476155598969 -1.9828897227476219, 4.8691937415397142 -1.9957178464772056, 5 -2)))"; - Geometry poly = GeometryEngine.geometryFromWkt(wktInput, 0, Geometry.Type.Unknown); - OperatorFactoryLocal engine = OperatorFactoryLocal.getInstance(); - OperatorGeneralizeByArea op = (OperatorGeneralizeByArea) engine.getOperator(Operator.Type.GeneralizeByArea); - Geometry geom = op.execute(poly, 5, true, GeneralizeType.Neither, SpatialReference.create(4326),null); - int res = ((MultiVertexGeometry)geom).getPointCount(); - int original = ((MultiVertexGeometry)poly).getPointCount(); - double ratio = (double)res/original; - assertTrue(res/(double)original < 0.95); - Geometry geom2 = op.execute(poly, true, res, GeneralizeType.Neither, SpatialReference.create(4326),null); - assertTrue(((MultiVertexGeometry)geom2).getPointCount() <= res); + } + + @Test + public void testCorruptedTreapSearch() { + String wktInput = "MULTIPOLYGON (((5 2, 5.1308062584602858 2.0042821535227944, 5.2610523844401031 2.0171102772523808, 5.3901806440322559 2.0384294391935405, 5.5176380902050415 2.0681483474218645, 5.6428789306063223 2.1061397410097897, 5.7653668647301792 2.1522409349774274, 5.8845773804380022 2.2062545169346244, 5.9999999999999991 2.2679491924311233, 6.1111404660392035 2.3370607753949102, 6.2175228580174409 2.4132933194175301, 6.3186916302001368 2.4963203850420457, 6.414213562373094 2.5857864376269051, 6.5036796149579548 2.6813083697998623, 6.5867066805824699 2.7824771419825591, 6.6629392246050898 2.8888595339607956, 6.7320508075688767 3, 6.7937454830653765 3.1154226195619974, 6.847759065022573 3.2346331352698208, 6.8938602589902107 3.3571210693936768, 6.9318516525781364 3.4823619097949585, 6.9615705608064609 3.6098193559677436, 6.982889722747621 3.7389476155598969, 6.9957178464772074 3.8691937415397137, 7 4, 6.9992288413054364 4.0555341344807063, 6.6947736951561883 15.015919395853626, 11.905851560026523 31.393592685446105, 11.931851652578136 31.48236190979496, 11.961570560806461 31.609819355967744, 11.982889722747622 31.738947615559898, 11.995717846477207 31.869193741539714, 12 32, 11.995717846477206 32.130806258460282, 11.982889722747618 32.261052384440106, 11.961570560806459 32.390180644032256, 11.931851652578136 32.51763809020504, 11.893860258990211 32.642878930606322, 11.847759065022572 32.765366864730183, 11.793745483065376 32.884577380438003, 11.732050807568877 33, 11.66293922460509 33.111140466039203, 11.6 33.200000000000003, 5.5999999999999996 41.200000000000003, 5.5867066805824699 41.217522858017439, 5.5036796149579548 41.318691630200135, 5.4142135623730949 41.414213562373092, 5.3186916302001377 41.503679614957953, 5.2175228580174409 41.586706680582466, 5.1111404660392044 41.66293922460509, 5 41.732050807568875, 4.8845773804380022 41.793745483065379, 4.7653668647301792 41.847759065022572, 4.6428789306063232 41.893860258990209, 4.5176380902050415 41.931851652578139, 4.3901806440322568 41.961570560806464, 4.2610523844401031 41.982889722747622, 4.1308062584602858 41.995717846477206, 4 42, 3.8691937415397142 41.995717846477206, 3.7389476155598973 41.982889722747622, 3.6098193559677441 41.961570560806457, 3.482361909794959 41.931851652578132, 3.3571210693936773 41.893860258990209, 3.2346331352698212 41.847759065022572, 3.1154226195619983 41.793745483065379, 3.0000000000000009 41.732050807568875, 2.8888595339607965 41.66293922460509, 2.7824771419825591 41.586706680582466, 2.6813083697998632 41.503679614957953, 2.5857864376269055 41.414213562373092, 2.4963203850420457 41.318691630200135, 2.4132933194175301 41.217522858017439, 2.3370607753949102 41.111140466039203, 2.2679491924311233 41, 2.2062545169346235 40.884577380438003, 2.152240934977427 40.765366864730183, 2.1061397410097888 40.642878930606322, 2.0681483474218636 40.51763809020504, 2.0384294391935391 40.390180644032256, 2.017110277252379 40.261052384440106, 2.004282153522793 40.130806258460289, 2 40, 2.0007711586945636 39.944465865519291, 2.6774890785664391 15.582620750131777, 1.094148439973476 10.606407314553895, 1.0681483474218636 10.517638090205041, 1.0384294391935391 10.390180644032256, 1.017110277252379 10.261052384440102, 1.004282153522793 10.130806258460286, 1 10, 1.0042821535227944 9.8691937415397142, 1.0171102772523808 9.7389476155598977, 1.0384294391935405 9.6098193559677441, 1.0681483474218645 9.4823619097949585, 1.1061397410097897 9.3571210693936777, 1.1522409349774274 9.2346331352698208, 1.2062545169346244 9.115422619561997, 1.2679491924311233 9, 1.3370607753949102 8.8888595339607974, 1.4132933194175301 8.7824771419825591, 1.4963203850420457 8.6813083697998632, 1.5857864376269051 8.585786437626906, 1.6813083697998623 8.4963203850420452, 1.7824771419825591 8.4132933194175301, 1.8888595339607956 8.3370607753949102, 2 8.2679491924311233, 2.1154226195619974 8.2062545169346244, 2.2346331352698208 8.1522409349774279, 2.3571210693936768 8.1061397410097893, 2.4823619097949585 8.0681483474218645, 2.6098193559677436 8.0384294391935391, 2.7389476155598969 8.0171102772523781, 2.8691937415397137 8.0042821535227926, 2.888015599689953 8.0036659896852811, 3.0007711586945636 3.9444658655192932, 3.0042821535227944 3.8691937415397142, 3.0171102772523808 3.7389476155598973, 3.0384294391935405 3.6098193559677441, 3.0681483474218645 3.482361909794959, 3.1061397410097897 3.3571210693936773, 3.1522409349774274 3.2346331352698212, 3.2062545169346244 3.1154226195619983, 3.2679491924311233 3.0000000000000009, 3.3370607753949102 2.8888595339607965, 3.4132933194175301 2.7824771419825591, 3.4963203850420457 2.6813083697998632, 3.5857864376269051 2.5857864376269055, 3.6813083697998623 2.4963203850420457, 3.7824771419825591 2.4132933194175301, 3.8888595339607956 2.3370607753949102, 4 2.2679491924311233, 4.1154226195619978 2.2062545169346235, 4.2346331352698208 2.152240934977427, 4.3571210693936768 2.1061397410097888, 4.4823619097949585 2.0681483474218636, 4.6098193559677432 2.0384294391935391, 4.7389476155598969 2.017110277252379, 4.8691937415397142 2.004282153522793, 5 2), (6.3577402241893219 27.149124350660838, 6.1738780506195123 33.768162599173976, 7.7816940790703999 31.624407894572798, 6.3577402241893219 27.149124350660838)))\n" + + "MULTIPOLYGON (((5 -2, 5.1308062584602858 -1.9957178464772056, 5.2610523844401031 -1.9828897227476183, 5.3901806440322559 -1.9615705608064609, 5.5176380902050415 -1.9318516525781355, 5.6428789306063223 -1.8938602589902089, 5.7427813527082074 -1.8569533817705199, 30.742781352708207 8.1430466182294818, 30.765366864730179 8.1522409349774279, 30.884577380438003 8.2062545169346244, 31 8.2679491924311233, 31.111140466039203 8.3370607753949102, 31.217522858017439 8.4132933194175301, 31.318691630200139 8.4963203850420452, 31.414213562373096 8.585786437626906, 31.503679614957953 8.6813083697998632, 31.58670668058247 8.7824771419825591, 31.66293922460509 8.8888595339607956, 31.732050807568879 9, 31.788854381999833 9.1055728090000834, 36.788854381999833 19.105572809000083, 36.793745483065379 19.115422619561997, 36.847759065022572 19.234633135269821, 36.893860258990209 19.357121069393678, 36.931851652578139 19.48236190979496, 36.961570560806464 19.609819355967744, 36.982889722747622 19.738947615559898, 36.995717846477206 19.869193741539714, 37 20, 36.995717846477206 20.130806258460286, 36.982889722747622 20.261052384440102, 36.961570560806457 20.390180644032256, 36.931851652578132 20.51763809020504, 36.893860258990209 20.642878930606322, 36.847759065022572 20.765366864730179, 36.793745483065379 20.884577380438003, 36.788854381999833 20.894427190999917, 31.788854381999833 30.894427190999917, 31.732050807568875 31, 31.66293922460509 31.111140466039203, 31.58670668058247 31.217522858017439, 31.503679614957953 31.318691630200135, 31.414213562373096 31.414213562373092, 31.318691630200139 31.503679614957953, 31.217522858017443 31.58670668058247, 31.111140466039203 31.66293922460509, 31 31.732050807568875, 30.884577380438003 31.793745483065376, 30.765366864730179 31.847759065022572, 30.642878930606322 31.893860258990209, 30.568176659382747 31.917596225416773, 3.5681766593827478 39.917596225416773, 3.5176380902050415 39.931851652578132, 3.3901806440322564 39.961570560806464, 3.2610523844401031 39.982889722747622, 3.1308062584602863 39.995717846477206, 3 40, 2.8691937415397142 39.995717846477206, 2.7389476155598973 39.982889722747615, 2.6098193559677441 39.961570560806464, 2.482361909794959 39.931851652578132, 2.3571210693936773 39.893860258990209, 2.2346331352698212 39.847759065022572, 2.1154226195619983 39.793745483065379, 2.0000000000000009 39.732050807568875, 1.8888595339607963 39.66293922460509, 1.7824771419825594 39.586706680582466, 1.681308369799863 39.503679614957953, 1.5857864376269055 39.414213562373092, 1.4963203850420457 39.318691630200135, 1.4132933194175301 39.217522858017446, 1.33706077539491 39.111140466039203, 1.267949192431123 39, 1.2062545169346237 38.884577380438003, 1.1522409349774267 38.765366864730183, 1.1061397410097888 38.642878930606322, 1.0681483474218634 38.51763809020504, 1.0384294391935391 38.390180644032256, 1.0171102772523792 38.261052384440106, 1.004282153522793 38.130806258460282, 1 38, 1.0042821535227944 37.869193741539718, 1.0171102772523806 37.738947615559894, 1.0384294391935405 37.609819355967744, 1.0681483474218647 37.48236190979496, 1.1061397410097897 37.357121069393678, 1.1522409349774274 37.234633135269817, 1.2062545169346244 37.115422619561997, 1.2679491924311235 37, 1.3370607753949102 36.888859533960797, 1.4132933194175301 36.782477141982561, 1.4963203850420457 36.681308369799865, 1.5857864376269053 36.585786437626908, 1.6813083697998625 36.496320385042047, 1.7824771419825589 36.413293319417534, 1.8888595339607956 36.33706077539491, 2 36.267949192431118, 2.1154226195619974 36.206254516934621, 2.2346331352698208 36.152240934977428, 2.3571210693936768 36.106139741009784, 2.4318233406172522 36.082403774583227, 28.599409577746219 28.329044889507976, 32.763932022500207 20, 28.551206229908889 11.574548414817356, 6.3845784837230717 2.7078973163430327, 6.3268887828044376 2.9386561200175683, 19.26118525018893 13.447771999767468, 19.318691630200139 13.496320385042045, 19.414213562373096 13.585786437626906, 19.503679614957953 13.681308369799863, 19.58670668058247 13.782477141982559, 19.66293922460509 13.888859533960796, 19.732050807568879 14, 19.793745483065376 14.115422619561997, 19.847759065022572 14.234633135269821, 19.893860258990212 14.357121069393678, 19.931851652578136 14.482361909794959, 19.961570560806461 14.609819355967744, 19.982889722747622 14.738947615559898, 19.995717846477206 14.869193741539714, 20 15, 19.995717846477206 15.130806258460286, 19.982889722747618 15.261052384440102, 19.961570560806461 15.390180644032256, 19.931851652578136 15.517638090205041, 19.893860258990209 15.642878930606322, 19.847759065022572 15.765366864730179, 19.793745483065376 15.884577380438001, 19.732050807568875 16, 19.66293922460509 16.111140466039203, 19.58670668058247 16.217522858017439, 19.503679614957953 16.318691630200135, 19.414213562373096 16.414213562373092, 19.318691630200139 16.503679614957953, 19.217522858017443 16.58670668058247, 19.111140466039203 16.66293922460509, 19 16.732050807568875, 18.884577380438003 16.793745483065376, 18.765366864730179 16.847759065022572, 18.642878930606322 16.893860258990209, 18.51763809020504 16.931851652578139, 18.390180644032256 16.961570560806461, 18.261052384440102 16.982889722747622, 18.130806258460286 16.995717846477206, 18 17, 17.869193741539714 16.995717846477206, 17.738947615559898 16.982889722747622, 17.609819355967744 16.961570560806457, 17.48236190979496 16.931851652578136, 17.357121069393678 16.893860258990209, 17.234633135269821 16.847759065022572, 17.115422619561997 16.793745483065376, 17 16.732050807568875, 16.888859533960797 16.66293922460509, 16.782477141982561 16.58670668058247, 16.73881474981107 16.552228000232532, 5.2559522566699819 7.2224022245553954, 1.9402850002906638 20.485071250072664, 1.9318516525781353 20.51763809020504, 1.8938602589902103 20.642878930606322, 1.8477590650225726 20.765366864730179, 1.7937454830653756 20.884577380438003, 1.7320508075688765 21, 1.6629392246050898 21.111140466039203, 1.5867066805824699 21.217522858017439, 1.5036796149579543 21.318691630200139, 1.4142135623730947 21.414213562373096, 1.3186916302001375 21.503679614957953, 1.2175228580174411 21.58670668058247, 1.1111404660392044 21.66293922460509, 0.99999999999999989 21.732050807568879, 0.88457738043800249 21.793745483065376, 0.76536686473017945 21.847759065022572, 0.64287893060632306 21.893860258990212, 0.51763809020504148 21.931851652578136, 0.3901806440322565 21.961570560806461, 0.26105238444010315 21.982889722747622, 0.13080625846028612 21.995717846477206, 0 22, -0.13080625846028585 21.995717846477206, -0.26105238444010281 21.982889722747618, -0.39018064403225611 21.961570560806461, -0.51763809020504103 21.931851652578136, -0.64287893060632262 21.893860258990209, -0.7653668647301789 21.847759065022572, -0.88457738043800183 21.793745483065376, -0.99999999999999922 21.732050807568875, -1.1111404660392037 21.66293922460509, -1.2175228580174406 21.58670668058247, -1.318691630200137 21.503679614957953, -1.4142135623730945 21.414213562373096, -1.5036796149579543 21.318691630200139, -1.5867066805824699 21.217522858017443, -1.66293922460509 21.111140466039203, -1.732050807568877 21, -1.7937454830653763 20.884577380438003, -1.8477590650225733 20.765366864730179, -1.8938602589902112 20.642878930606322, -1.9318516525781366 20.51763809020504, -1.9615705608064609 20.390180644032256, -1.9828897227476208 20.261052384440102, -1.995717846477207 20.130806258460286, -2 20, -1.9957178464772056 19.869193741539714, -1.9828897227476194 19.738947615559898, -1.9615705608064595 19.609819355967744, -1.9402850002906638 19.514928749927336, 1.4516369470040571 5.947240960748454, 1.3901806440322564 5.9615705608064609, 1.2610523844401031 5.982889722747621, 1.130806258460286 5.9957178464772074, 1 6, 0.86919374153971418 5.9957178464772056, 0.73894761555989719 5.9828897227476192, 0.60981935596774384 5.9615705608064591, 0.48236190979495897 5.9318516525781355, 0.35712106939367738 5.8938602589902107, 0.2346331352698211 5.8477590650225721, 0.11542261956199817 5.7937454830653756, 7.7715611723760958E-16 5.7320508075688767, -0.11114046603920369 5.6629392246050898, -0.21752285801744065 5.5867066805824699, -0.31869163020013702 5.5036796149579548, -0.41421356237309448 5.4142135623730949, -0.50367961495795432 5.3186916302001377, -0.58670668058246989 5.2175228580174409, -0.66293922460509003 5.1111404660392044, -0.73205080756887697 5, -0.79374548306537629 4.8845773804380022, -0.84775906502257325 4.7653668647301792, -0.89386025899021115 4.6428789306063232, -0.93185165257813662 4.5176380902050415, -0.96157056080646086 4.3901806440322568, -0.98288972274762076 4.2610523844401031, -0.99571784647720696 4.1308062584602858, -1 4, -0.99571784647720563 3.8691937415397142, -0.98288972274761943 3.7389476155598973, -0.96157056080645953 3.6098193559677441, -0.93185165257813529 3.482361909794959, -0.89386025899021027 3.3571210693936773, -0.84775906502257259 3.2346331352698212, -0.79374548306537562 3.1154226195619983, -0.78885438199983171 3.1055728090000843, 0.21114561800016829 1.1055728090000843, 0.26794919243112347 1.0000000000000009, 0.33706077539491019 0.88885953396079653, 0.41329331941753011 0.78247714198255913, 0.49632038504204568 0.6813083697998632, 0.5857864376269053 0.58578643762690552, 0.68130836979986253 0.49632038504204568, 0.78247714198255891 0.41329331941753011, 0.88885953396079564 0.33706077539491019, 1 0.26794919243112325, 1.1154226195619974 0.20625451693462349, 1.2346331352698205 0.15224093497742697, 1.3571210693936768 0.10613974100978885, 1.4823619097949585 0.068148347421863598, 1.6098193559677436 0.038429439193539139, 1.7389476155598969 0.017110277252379014, 1.8691937415397137 0.0042821535227930418, 2 0, 2.1308062584602858 0.0042821535227943741, 2.2610523844401027 0.01711027725238079, 2.3901806440322559 0.038429439193540471, 2.517638090205041 0.068148347421864486, 2.6428789306063227 0.10613974100978973, 2.7653668647301788 0.15224093497742741, 2.8845773804380017 0.20625451693462438, 2.8866117144161265 0.20734189110017509, 3.0597149997093362 -0.48507125007266438, 3.0681483474218645 -0.5176380902050397, 3.1061397410097897 -0.64287893060632229, 3.1522409349774274 -0.76536686473017923, 3.2062545169346244 -0.88457738043800305, 3.2679491924311233 -1, 3.3370607753949102 -1.1111404660392026, 3.4132933194175301 -1.2175228580174391, 3.4963203850420457 -1.3186916302001386, 3.5857864376269051 -1.4142135623730958, 3.6813083697998623 -1.503679614957953, 3.7824771419825591 -1.5867066805824699, 3.8888595339607956 -1.6629392246050898, 4 -1.7320508075688785, 4.1154226195619978 -1.7937454830653756, 4.2346331352698208 -1.8477590650225721, 4.3571210693936768 -1.8938602589902125, 4.4823619097949585 -1.9318516525781355, 4.6098193559677432 -1.9615705608064609, 4.7389476155598969 -1.9828897227476219, 4.8691937415397142 -1.9957178464772056, 5 -2)))"; + Geometry poly = GeometryEngine.geometryFromWkt(wktInput, 0, Geometry.Type.Unknown); + OperatorFactoryLocal engine = OperatorFactoryLocal.getInstance(); + OperatorGeneralizeByArea op = (OperatorGeneralizeByArea) engine.getOperator(Operator.Type.GeneralizeByArea); + Geometry geom = op.execute(poly, 5, true, GeneralizeType.Neither, SpatialReference.create(4326), null); + int res = ((MultiVertexGeometry) geom).getPointCount(); + int original = ((MultiVertexGeometry) poly).getPointCount(); + double ratio = (double) res / original; + assertTrue(res / (double) original < 0.95); + Geometry geom2 = op.execute(poly, true, res, GeneralizeType.Neither, SpatialReference.create(4326), null); + assertTrue(((MultiVertexGeometry) geom2).getPointCount() <= res); // String geojson = "{ \"type\": \"Feature\", \"properties\": { \"COUNTY\": \"Penobscot\", \"CNTYCODE\": \"19\", \"LAND\": \"n\", \"ISLAND\": \"n\", \"TAG\": \"n\", \"Shape_area\": 6530336.8256299999, \"Shape_len\": 56904.047782399997 }, \"geometry\": { \"type\": \"Polygon\", \"coordinates\": [ [ [ -68.695451692814189, 44.822195593602991 ], [ -68.695816087325994, 44.821903991195327 ], [ -68.696174426639388, 44.821711397443501 ], [ -68.696558567568886, 44.821572884584675 ], [ -68.696949609800058, 44.821474899471852 ], [ -68.697256901116091, 44.8213676875367 ], [ -68.697320799660304, 44.821318345186519 ], [ -68.697615556845776, 44.821188592823191 ], [ -68.698378545976411, 44.820938550102532 ], [ -68.698435156506562, 44.820920695108391 ], [ -68.69883192031557, 44.820710189947867 ], [ -68.699062657760294, 44.820652282960367 ], [ -68.699370360284775, 44.820617084374163 ], [ -68.701141054792856, 44.820558717280676 ], [ -68.701487605435759, 44.82057762811877 ], [ -68.701911437797563, 44.820632748628718 ], [ -68.70283548793708, 44.820648659500918 ], [ -68.703681639952507, 44.820515825631873 ], [ -68.704181264140928, 44.820359582185233 ], [ -68.704489092967719, 44.820297362137836 ], [ -68.704989474238957, 44.820303155036257 ], [ -68.705297799299089, 44.820375971553318 ], [ -68.705567458318939, 44.820358662585058 ], [ -68.70591330097642, 44.820283033953537 ], [ -68.706259025405714, 44.820153393000268 ], [ -68.707028672758838, 44.820060845749843 ], [ -68.707374910353323, 44.819985216211904 ], [ -68.707592100462804, 44.819868742036917 ], [ -68.707694462929339, 44.819792486857864 ], [ -68.707770875016166, 44.819689157189472 ], [ -68.707789007679523, 44.819464146139595 ], [ -68.707730751490473, 44.819337968108044 ], [ -68.707659880441298, 44.819283773228037 ], [ -68.707576200438197, 44.819261052747741 ], [ -68.707108550110888, 44.81927335711427 ], [ -68.707697862979572, 44.818806752534506 ], [ -68.70819004704336, 44.818866526433681 ], [ -68.708376591172751, 44.818948021908511 ], [ -68.708563304168294, 44.81907453000612 ], [ -68.708559320346566, 44.819551637731571 ], [ -68.708617601218435, 44.819673316158109 ], [ -68.709048345319403, 44.8197689360547 ], [ -68.709261373718149, 44.820008038747282 ], [ -68.709377173200522, 44.820089354962377 ], [ -68.709608623084335, 44.820202467396875 ], [ -68.709833368515561, 44.820234548383922 ], [ -68.710231130402889, 44.820208550305409 ], [ -68.710391390599668, 44.820172947827515 ], [ -68.710596166348765, 44.820087946208503 ], [ -68.710750055965008, 44.820061330585148 ], [ -68.710942502233308, 44.82007081882567 ], [ -68.712135677071814, 44.820015319829345 ], [ -68.712443387278597, 44.819975584283789 ], [ -68.712686784165612, 44.819913182395688 ], [ -68.713392560919672, 44.819865441533217 ], [ -68.716009635307771, 44.819772953313979 ], [ -68.716792482087286, 44.819725388080997 ], [ -68.717401129352595, 44.819573858831298 ], [ -68.71832380500507, 44.819382590539817 ], [ -68.719099748128428, 44.819289977453316 ], [ -68.719901181063932, 44.819156913377398 ], [ -68.720676944833869, 44.819100298125058 ], [ -68.721548389776331, 44.818872869458616 ], [ -68.721971165718969, 44.818738866992419 ], [ -68.722945234603259, 44.818538685443407 ], [ -68.72342783755235, 44.818373312940288 ], [ -68.724468363700822, 44.81782669204572 ], [ -68.725485044780186, 44.817221491552786 ], [ -68.726452730121551, 44.817025763931447 ], [ -68.726606732, 44.816972121695052 ], [ -68.726823714448699, 44.816806098626316 ], [ -68.727393063882147, 44.816510385999273 ], [ -68.727744531541006, 44.816236657577228 ], [ -68.728108807421563, 44.81601247031989 ], [ -68.728882984707582, 44.815609210628736 ], [ -68.730205742580338, 44.814694115634502 ], [ -68.731142364712213, 44.814111181554438 ], [ -68.732194707686304, 44.81354201139667 ], [ -68.732322590398056, 44.813492799414071 ], [ -68.73285922495063, 44.813079954581475 ], [ -68.734847494451969, 44.811882789630445 ], [ -68.735519196861276, 44.811569269299596 ], [ -68.736075846860587, 44.811264482694284 ], [ -68.736542748338991, 44.811054008218079 ], [ -68.737285361231272, 44.81078565106511 ], [ -68.738194206280426, 44.810373634066408 ], [ -68.738577747792448, 44.81015396161547 ], [ -68.739011942544209, 44.809830873214104 ], [ -68.73916496247854, 44.809637674764268 ], [ -68.739278067404385, 44.809268841467606 ], [ -68.739289709728382, 44.809052816213381 ], [ -68.739379110604645, 44.808944993089099 ], [ -68.739801306658848, 44.808738907447292 ], [ -68.739928746369046, 44.808608661744117 ], [ -68.740159550798651, 44.808604685341628 ], [ -68.740275082530914, 44.808573440839666 ], [ -68.740415719594239, 44.808497243215427 ], [ -68.740941485561748, 44.80845792622285 ], [ -68.740980130502123, 44.808476018407177 ], [ -68.741188152281438, 44.80897161333133 ], [ -68.741252758676168, 44.809016769790588 ], [ -68.741361708869633, 44.809044022116524 ], [ -68.741483822462598, 44.80904429859779 ], [ -68.74171365109278, 44.808905284132514 ], [ -68.742406737337902, 44.808920354782721 ], [ -68.742560307914303, 44.808866687471998 ], [ -68.742739175008836, 44.808723054290901 ], [ -68.74285433058246, 44.808687304572757 ], [ -68.744393775156766, 44.808641248574901 ], [ -68.744701934997593, 44.808659942387102 ], [ -68.745393760471003, 44.80851294880722 ], [ -68.745604783762118, 44.808423396405175 ], [ -68.746890030152656, 44.807674565996791 ], [ -68.747784232064419, 44.807059893372035 ], [ -68.748115961513975, 44.806745547369985 ], [ -68.748292659098013, 44.806277818981066 ], [ -68.748586111067979, 44.805949884424457 ], [ -68.748713008452199, 44.805756615538854 ], [ -68.748762966462394, 44.805536171721116 ], [ -68.749338094387127, 44.805132334560987 ], [ -68.749479853677684, 44.805065129206461 ], [ -68.749630403318932, 44.804975436641236 ], [ -68.74980313149544, 44.804782266975039 ], [ -68.750227707815029, 44.804558138285152 ], [ -68.750652420232754, 44.804392526210989 ], [ -68.751539169978301, 44.804110890388202 ], [ -68.752079552768663, 44.803878005653502 ], [ -68.752324621496953, 44.803675985622874 ], [ -68.752576316023536, 44.803401963737414 ], [ -68.752917756208078, 44.803209154691054 ], [ -68.753741243831598, 44.802837342083969 ], [ -68.754178847472929, 44.802608727462719 ], [ -68.755246495802211, 44.80214740397647 ], [ -68.75621141192336, 44.801681351966444 ], [ -68.756379046675562, 44.801564681346385 ], [ -68.75649457459167, 44.801528918622907 ], [ -68.757041406400404, 44.801255513179669 ], [ -68.75731807201862, 44.801143574835528 ], [ -68.758540273874985, 44.80056101862634 ], [ -68.759833086856261, 44.80000110411347 ], [ -68.761351548576656, 44.799234595471795 ], [ -68.762399850969942, 44.798741657051664 ], [ -68.762528590996538, 44.798660905543066 ], [ -68.76311593193843, 44.798419064966623 ], [ -68.764290928989183, 44.797854351064849 ], [ -68.765056928664066, 44.797396819296736 ], [ -68.765359260254357, 44.797280409912119 ], [ -68.765744398973183, 44.797204683019253 ], [ -68.765853702644392, 44.797141889927616 ], [ -68.765989228060278, 44.797043143839083 ], [ -68.766196183736781, 44.79676899993494 ], [ -68.766595968516938, 44.796391721736214 ], [ -68.766931270087539, 44.796131341497535 ], [ -68.767092762035375, 44.79596063129938 ], [ -68.767356401061235, 44.795835134142663 ], [ -68.767407976443195, 44.795781225479288 ], [ -68.76835318064613, 44.794365289549297 ], [ -68.768573712505116, 44.793951636411805 ], [ -68.769058260418049, 44.793412482442264 ], [ -68.76919335878452, 44.793318230884395 ], [ -68.769308808570628, 44.793295957794278 ], [ -68.76953992717155, 44.793296424408801 ], [ -68.769604074352173, 44.793260547187714 ], [ -68.769643115624532, 44.793179604079413 ], [ -68.769605239337722, 44.79306700093634 ], [ -68.769413805420456, 44.792922577945546 ], [ -68.76940782742426, 44.792837044746591 ], [ -68.769562890416026, 44.792589795367036 ], [ -68.769756273185465, 44.792441646971689 ], [ -68.769878888626124, 44.7923068609933 ], [ -68.769956858171582, 44.792171984959602 ], [ -68.770262293423301, 44.791862022606956 ], [ -68.770276886312146, 44.791668503311932 ], [ -68.770526109892913, 44.79138543276629 ], [ -68.770739470414171, 44.791079783315702 ], [ -68.771004883596888, 44.790697720220322 ], [ -68.771014027482607, 44.790283635542679 ], [ -68.771150097202366, 44.790040847588322 ], [ -68.771196291810568, 44.789847391433852 ], [ -68.771191239872593, 44.789626824453514 ], [ -68.771090828991603, 44.789244027558006 ], [ -68.771098701882494, 44.7890504945995 ], [ -68.771144273277073, 44.788915552738281 ], [ -68.771234896308329, 44.78877619933079 ], [ -68.771518169421824, 44.78856971482768 ], [ -68.77159532693554, 44.788538361751385 ], [ -68.77171121437479, 44.78850258427093 ], [ -68.771942028882165, 44.788476037269348 ], [ -68.772288408005508, 44.788494733020507 ], [ -68.772378332010533, 44.788431897456157 ], [ -68.772391578804118, 44.788377910073592 ], [ -68.772327760780144, 44.788332770127056 ], [ -68.771822295691948, 44.788183225330179 ], [ -68.771726119812556, 44.788129019349938 ], [ -68.771701064263524, 44.788070453703497 ], [ -68.771895643813892, 44.787715254867287 ], [ -68.772166177882255, 44.787531247137345 ], [ -68.772527879215332, 44.787068352872602 ], [ -68.772800635192027, 44.786519759289412 ], [ -68.772905311210849, 44.786218388531772 ], [ -68.772907724515406, 44.785804289992655 ], [ -68.77278089085523, 44.785610488866176 ], [ -68.772525281400803, 44.785416431171129 ], [ -68.772333653091337, 44.785326026307239 ], [ -68.772135082617311, 44.785294122861785 ], [ -68.772039284844894, 44.78524441965115 ], [ -68.772081361511141, 44.78469086264171 ], [ -68.772191773002305, 44.784439020950785 ], [ -68.772314057949743, 44.784380749206775 ], [ -68.772737335188012, 44.784327578460491 ], [ -68.772904815783903, 44.7842288875257 ], [ -68.77299528256475, 44.784125541511813 ], [ -68.773105565594264, 44.78390520358392 ], [ -68.773176588818572, 44.783824325179275 ], [ -68.773524412407554, 44.783568451654375 ], [ -68.773679116333085, 44.783402215061926 ], [ -68.773891903578175, 44.783231593994579 ], [ -68.774233890651587, 44.782849674443177 ], [ -68.774324996768641, 44.78268331121533 ], [ -68.774583316607917, 44.782382247238466 ], [ -68.774828285748882, 44.782162173338413 ], [ -68.775228105320053, 44.781735354463095 ], [ -68.775447251533606, 44.781551237617272 ], [ -68.775868718530432, 44.780836385125433 ], [ -68.776042953339157, 44.780620671503648 ], [ -68.776231174258058, 44.780260950562095 ], [ -68.776405922404592, 44.780013729911275 ], [ -68.777425927468428, 44.778845426855298 ], [ -68.777677066903919, 44.778656868709596 ], [ -68.778076030902696, 44.778441589156301 ], [ -68.778577135026822, 44.778267017313013 ], [ -68.779276940137763, 44.778083826402529 ], [ -68.779553572030622, 44.777940323684398 ], [ -68.780139375146547, 44.777509344353462 ], [ -68.780868818538025, 44.776691536858401 ], [ -68.781010948003328, 44.776498260311399 ], [ -68.781352729415701, 44.776147826074464 ], [ -68.781559435632545, 44.775900661047963 ], [ -68.781636566207993, 44.775765772534605 ], [ -68.781894622447126, 44.775622231079431 ], [ -68.782048293716926, 44.775613520875787 ], [ -68.782317545871479, 44.775641042606495 ], [ -68.782478189103387, 44.775564828358974 ], [ -68.782600654699593, 44.775452529435526 ], [ -68.78310278458811, 44.775097898251445 ], [ -68.784041816359917, 44.774613552553895 ], [ -68.784376287524552, 44.77441613601593 ], [ -68.784646853631486, 44.774196089069271 ], [ -68.784995648255915, 44.773755636317958 ], [ -68.785073557522978, 44.773620746898153 ], [ -68.785292878751847, 44.773373593721793 ], [ -68.785629120078838, 44.772906109661022 ], [ -68.785668228566422, 44.772798155199482 ], [ -68.785592127255271, 44.772658476989569 ], [ -68.785592327104851, 44.772604463431101 ], [ -68.785777644692217, 44.772366251526016 ], [ -68.785851453775592, 44.772163839567746 ], [ -68.785768355406873, 44.772100666203279 ], [ -68.785429575577126, 44.771973997684732 ], [ -68.785334142003123, 44.771829783681163 ], [ -68.785341473455944, 44.771663253845652 ], [ -68.785406926454442, 44.771478831091862 ], [ -68.785459811421063, 44.771168349632276 ], [ -68.785434819423855, 44.771091784805002 ], [ -68.785351789730825, 44.77101060660501 ], [ -68.785250916723996, 44.770735847941545 ], [ -68.785310182710745, 44.770515404276829 ], [ -68.785311494572341, 44.770267843410664 ], [ -68.785096318028863, 44.769826325977917 ], [ -68.784803545143774, 44.769438675883706 ], [ -68.784714527045139, 44.769375493078655 ], [ -68.784669524743251, 44.769370906446809 ], [ -68.784470834648246, 44.769384034518389 ], [ -68.78420130177291, 44.76943753950647 ], [ -68.784008264186326, 44.769522696071554 ], [ -68.783694089009984, 44.769580617214714 ], [ -68.783070626604243, 44.769867506633268 ], [ -68.782800597749329, 44.769948015603781 ], [ -68.78264697185837, 44.769947723333424 ], [ -68.782340318809389, 44.769789599225568 ], [ -68.782109891596903, 44.769735145875238 ], [ -68.782020552481555, 44.76965395570739 ], [ -68.782020670997156, 44.769622445906052 ], [ -68.782329413534242, 44.769433987151317 ], [ -68.782677143951545, 44.76916908133898 ], [ -68.782870161924663, 44.769088426262144 ], [ -68.783088655857583, 44.768953810300239 ], [ -68.783512222182253, 44.768896098227884 ], [ -68.783896880060539, 44.768892324937006 ], [ -68.784589406005708, 44.768826116407126 ], [ -68.784782390276106, 44.768754461613682 ], [ -68.785000865353041, 44.768624341230989 ], [ -68.78519341681563, 44.768561685998975 ], [ -68.785296753995596, 44.768489861684486 ], [ -68.785541690218764, 44.768251762054298 ], [ -68.785709868064387, 44.767945998534941 ], [ -68.785736214318959, 44.767869530046532 ], [ -68.785714471154591, 44.767234826848487 ], [ -68.785755081576923, 44.766825300110298 ], [ -68.785865690797962, 44.766600448006521 ], [ -68.785988080959882, 44.766501653023312 ], [ -68.785994450004765, 44.766488164598549 ], [ -68.78601394837284, 44.766447687501191 ], [ -68.786310208734392, 44.766209682289976 ], [ -68.786683532705723, 44.76595381308983 ], [ -68.786741485453618, 44.765872901578966 ], [ -68.787043992843081, 44.765652910771593 ], [ -68.787373526185192, 44.765491482758996 ], [ -68.787636098766797, 44.765284918885285 ], [ -68.78782871866386, 44.765199755348142 ], [ -68.788150546741875, 44.764984296759529 ], [ -68.788549377660019, 44.764768979850153 ], [ -68.788934548746496, 44.76461665389634 ], [ -68.789165998837333, 44.764495550508443 ], [ -68.789578047801001, 44.764226242886195 ], [ -68.789944442257621, 44.76402436418455 ], [ -68.790440190545254, 44.763647179305771 ], [ -68.790902955034028, 44.763436473216785 ], [ -68.790979906295107, 44.763450116955511 ], [ -68.791183996118704, 44.76357652300991 ], [ -68.791452533843938, 44.763680539931251 ], [ -68.791721839222916, 44.763681031378603 ], [ -68.792562104483238, 44.763579034429227 ], [ -68.793275917937265, 44.763170723018241 ], [ -68.793506473957919, 44.763184642989323 ], [ -68.793814940286381, 44.76305466829114 ], [ -68.793956685527107, 44.762946896054288 ], [ -68.794072148804759, 44.762902093872832 ], [ -68.794572353236305, 44.762817470609775 ], [ -68.794701529717074, 44.762691672670101 ], [ -68.794663512813727, 44.762610582026241 ], [ -68.794471182829113, 44.762619239661383 ], [ -68.794349621667408, 44.762601016156793 ], [ -68.794118260940593, 44.762704125575887 ], [ -68.794002483359108, 44.762726423276774 ], [ -68.79385526284679, 44.762708150450358 ], [ -68.79390059677911, 44.762618211399527 ], [ -68.794119598828289, 44.762438559637651 ], [ -68.794434913389793, 44.762268084441047 ], [ -68.794710993262086, 44.762138046492609 ], [ -68.795064349741864, 44.76203965914754 ], [ -68.7952057308094, 44.761922881092943 ], [ -68.795488596574572, 44.761770346948495 ], [ -68.795719843637599, 44.761698742093969 ], [ -68.796258622435687, 44.761645693000666 ], [ -68.796566581438384, 44.761542713183211 ], [ -68.796759412699828, 44.761502547858363 ], [ -68.797336179343631, 44.761427052287225 ], [ -68.797811208691854, 44.761310865086898 ], [ -68.79814495421958, 44.76117191980493 ], [ -68.798414345497292, 44.761145387345685 ], [ -68.799360911865662, 44.760548402916378 ], [ -68.799617180855279, 44.760431821147115 ], [ -68.800246592436537, 44.760198865374868 ], [ -68.800401188211723, 44.760023590621124 ], [ -68.800768126199941, 44.759758662850373 ], [ -68.800877274876569, 44.759709340948426 ], [ -68.800992552899757, 44.759714041168571 ], [ -68.80120348813486, 44.759804431958621 ], [ -68.801460585740003, 44.75967434710396 ], [ -68.801743547241756, 44.759485790710741 ], [ -68.801885944102992, 44.759296987349536 ], [ -68.802272006567179, 44.75897807633347 ], [ -68.80266564742729, 44.758519638865074 ], [ -68.803084230627647, 44.758155768264771 ], [ -68.80342470412505, 44.758003313674507 ], [ -68.804194879401265, 44.757815586202938 ], [ -68.804548648734837, 44.757582133698442 ], [ -68.804780826893264, 44.757343970511108 ], [ -68.804960853251316, 44.75723175064612 ], [ -68.805076202350875, 44.757213942883766 ], [ -68.805293772194744, 44.757326841465698 ], [ -68.805562563048056, 44.757354307693056 ], [ -68.805755054931097, 44.757291617566459 ], [ -68.805908948238255, 44.757318887501718 ], [ -68.806178528770531, 44.757229321859654 ], [ -68.806255426783181, 44.757256458122548 ], [ -68.806331178203592, 44.757391623058766 ], [ -68.806369798141887, 44.757414192525808 ], [ -68.8064468202152, 44.757405321994305 ], [ -68.806549701505531, 44.757337979335439 ], [ -68.806672423590101, 44.757122130821102 ], [ -68.806782136958716, 44.757018790126281 ], [ -68.807051801098638, 44.75690221677317 ], [ -68.807142104391929, 44.756816842607641 ], [ -68.807373407075289, 44.75671821091067 ], [ -68.807502054348006, 44.75661940325157 ], [ -68.807848780585701, 44.75648045388499 ], [ -68.808087002091739, 44.756318809663526 ], [ -68.808401654355606, 44.756206807988619 ], [ -68.808652446374822, 44.756063193386673 ], [ -68.808960045872851, 44.756054704733806 ], [ -68.80923601569333, 44.755938136750906 ], [ -68.809274898779066, 44.755879685796785 ], [ -68.809268850764056, 44.755798655811283 ], [ -68.809340557228253, 44.7556052256623 ], [ -68.809456533074552, 44.755515398232312 ], [ -68.809533581743025, 44.75549751954474 ], [ -68.810071718902236, 44.755498415742906 ], [ -68.810264114913323, 44.755462726140244 ], [ -68.810533408317383, 44.755454172113957 ], [ -68.810571854526643, 44.755409222885284 ], [ -68.810546670768289, 44.755382175318204 ], [ -68.810322610327518, 44.755323287513349 ], [ -68.81013757596466, 44.755160937756699 ], [ -68.810080577238011, 44.755084324892891 ], [ -68.810106431855303, 44.755025851980591 ], [ -68.810421014346346, 44.754931849301087 ], [ -68.810691541071961, 44.754666731838874 ], [ -68.810884618676084, 44.754541018805909 ], [ -68.811076352390984, 44.754586346635811 ], [ -68.811460957745311, 44.754568977507525 ], [ -68.811467845914279, 44.754514977283314 ], [ -68.811136643462973, 44.754379393534357 ], [ -68.811078065598863, 44.754302776035644 ], [ -68.811413084639341, 44.753992750485338 ], [ -68.811593438767929, 44.75377249241383 ], [ -68.811824287084306, 44.753808879906828 ], [ -68.81197786696552, 44.753809132852403 ], [ -68.81220914944889, 44.75371048876476 ], [ -68.812171099296066, 44.753633908080275 ], [ -68.811857549310361, 44.753534364842139 ], [ -68.811800568034371, 44.753453251039495 ], [ -68.811891296985777, 44.753232844750222 ], [ -68.811872624675729, 44.753147292266561 ], [ -68.811899119471633, 44.753012299351546 ], [ -68.811996165067612, 44.752791903365285 ], [ -68.812164363504536, 44.752544613505961 ], [ -68.812204164261757, 44.75232412567113 ], [ -68.812255837535332, 44.752216182372628 ], [ -68.812449191208287, 44.752000443544198 ], [ -68.812834601986424, 44.751852538837198 ], [ -68.81300918073957, 44.751582754142532 ], [ -68.81318295723068, 44.751439000840321 ], [ -68.813235210961196, 44.751272544599409 ], [ -68.813538471808641, 44.750885940049244 ], [ -68.813776929072503, 44.750638764929661 ], [ -68.814098572584356, 44.750427734168696 ], [ -68.814493642638681, 44.75009078877234 ], [ -68.814533968932153, 44.750072849550847 ], [ -68.814560139463936, 44.750036882614744 ], [ -68.814663392873143, 44.749969533163053 ], [ -68.814773386808668, 44.749893193446468 ], [ -68.814818433917466, 44.749879761076379 ], [ -68.814857210230826, 44.749852818123784 ], [ -68.81489595425569, 44.749834876208666 ], [ -68.814934278432105, 44.749825937213885 ], [ -68.814973018891862, 44.74980799526675 ], [ -68.81501171246758, 44.749808057947561 ], [ -68.815050456414866, 44.749790115979764 ], [ -68.815088780546034, 44.749781174422246 ], [ -68.815127524451597, 44.749763232428137 ], [ -68.81515953116056, 44.749754283131111 ], [ -68.815236993715445, 44.749727400143961 ], [ -68.815282029765385, 44.749718471890482 ], [ -68.815320325142594, 44.749718533822687 ], [ -68.815359097599938, 44.749691588181918 ], [ -68.815397841362241, 44.749673646095587 ], [ -68.815436559970905, 44.749664707569551 ], [ -68.81547530369933, 44.74964676294605 ], [ -68.815513627662639, 44.749637823756224 ], [ -68.815539259613928, 44.749646866261664 ], [ -68.815565400696954, 44.749619902659148 ], [ -68.815591118519407, 44.749601939470558 ], [ -68.815629862150189, 44.749583997305024 ], [ -68.81566818604837, 44.749575058063108 ], [ -68.815707356378638, 44.749548115456356 ], [ -68.815745691028283, 44.749534671887794 ], [ -68.815810550405317, 44.749498769583887 ], [ -68.815926375444491, 44.749449441465714 ], [ -68.816036381012623, 44.749368598737128 ], [ -68.816081427414701, 44.749355168377228 ], [ -68.816107177031967, 44.749328201508106 ], [ -68.816145920320508, 44.749310259166087 ], [ -68.816210384593958, 44.749274353486854 ], [ -68.816249550946083, 44.749247410686294 ], [ -68.816281571458703, 44.749233959288794 ], [ -68.816352786742598, 44.749184558966114 ], [ -68.816391533390586, 44.749166614034941 ], [ -68.816429796376184, 44.749175681668724 ], [ -68.816442939601913, 44.749139693355396 ], [ -68.816481711151056, 44.749112747329249 ], [ -68.816507855078356, 44.749085783513038 ], [ -68.816546569601087, 44.749076844603188 ], [ -68.816572286879023, 44.74905888119104 ], [ -68.816578646901021, 44.749045385982988 ], [ -68.816753533874532, 44.748919636197307 ], [ -68.816977810085774, 44.748780457365989 ], [ -68.817871923954471, 44.748169728708909 ], [ -68.817981399117343, 44.748129394495869 ], [ -68.819118677868175, 44.74738850841338 ], [ -68.81931299844598, 44.747231275296443 ], [ -68.819596944516107, 44.747069681643779 ], [ -68.820600610661103, 44.746756181179748 ], [ -68.820980842951059, 44.746603741410304 ], [ -68.821360519127069, 44.746500806852715 ], [ -68.821411979647792, 44.746455874259482 ], [ -68.821502013540552, 44.746447013903001 ], [ -68.821528150408852, 44.74642004891377 ], [ -68.822107527128807, 44.746204896097659 ], [ -68.822636062298287, 44.745962655550038 ], [ -68.822674746278807, 44.74596271561964 ], [ -68.823913000987631, 44.745343469555706 ], [ -68.824455107174913, 44.745047229790259 ], [ -68.824779449224906, 44.744701136133102 ], [ -68.825145060820077, 44.744152558364988 ], [ -68.825250330848689, 44.743932155830194 ], [ -68.825347774336592, 44.743819777171197 ], [ -68.825639512068122, 44.743550154402548 ], [ -68.82571698853684, 44.743514263185517 ], [ -68.826001967737042, 44.743258132088243 ], [ -68.826092498510377, 44.743213256878036 ], [ -68.826092987541358, 44.743181749958786 ], [ -68.826274736171015, 44.742992978069964 ], [ -68.826585357960383, 44.742741383798169 ], [ -68.827103903445632, 44.742256044512274 ], [ -68.827792028265009, 44.741554899230536 ], [ -68.828000465874297, 44.741280645385359 ], [ -68.828064940576041, 44.741235729351779 ], [ -68.828430960316567, 44.7406646322722 ], [ -68.828717376257302, 44.740313970878702 ], [ -68.828809535576255, 44.740116059345254 ], [ -68.82881057703618, 44.740030538981124 ], [ -68.828856684710985, 44.739922579594392 ], [ -68.828954588641025, 44.739783190089696 ], [ -68.82902114321972, 44.739567232756301 ], [ -68.829009555336853, 44.739481693474197 ], [ -68.829049896124886, 44.739319711113907 ], [ -68.829042592012641, 44.738851579162706 ], [ -68.829011651380185, 44.738770510204212 ], [ -68.828983396478918, 44.73844638486203 ], [ -68.828933037038013, 44.738392297692926 ], [ -68.82887076833903, 44.73822565980808 ], [ -68.82871935254623, 44.738040883468251 ], [ -68.828491051386735, 44.737828985609433 ], [ -68.827855235578454, 44.737413922145521 ], [ -68.827830636590292, 44.737323861282306 ], [ -68.827882488310493, 44.737274424473483 ], [ -68.827998188786637, 44.737256596536071 ], [ -68.828397230644569, 44.737122162390939 ], [ -68.828397705055409, 44.73709515471235 ], [ -68.828656919430031, 44.736861482048852 ], [ -68.828939181523381, 44.736308264480869 ], [ -68.829081150873833, 44.736213948906361 ], [ -68.829177493359865, 44.736200590105739 ], [ -68.829306807281213, 44.73611526146567 ], [ -68.829821654428969, 44.735922479237459 ], [ -68.830041522467653, 44.735778768555541 ], [ -68.830171465837125, 44.735612419693254 ], [ -68.830303881295663, 44.735275027848502 ], [ -68.830259850829663, 44.734676305893345 ], [ -68.830186480356275, 44.734392624905261 ], [ -68.829985341957737, 44.734072741681857 ], [ -68.829928218893954, 44.734036647097625 ], [ -68.829569115154101, 44.733482468299613 ], [ -68.829531913909165, 44.733383385293763 ], [ -68.829462296402056, 44.733302261154122 ], [ -68.829475397948642, 44.733275272307367 ], [ -68.829317773713001, 44.733058982121293 ], [ -68.828906619461691, 44.732626249904555 ], [ -68.828800394486848, 44.732378530755966 ], [ -68.828800868664587, 44.732351523052863 ], [ -68.828890879554251, 44.732342656814211 ], [ -68.829005608478639, 44.732383337501261 ], [ -68.829246003969317, 44.732635763980362 ], [ -68.829308370202071, 44.732766389916996 ], [ -68.82943036356447, 44.732753069228963 ], [ -68.829516219301652, 44.732546143806033 ], [ -68.829490592708922, 44.732537104418519 ], [ -68.829492567473608, 44.732402072831889 ], [ -68.829442878074715, 44.732253456125427 ], [ -68.829426125258408, 44.732046379981803 ], [ -68.829447596220263, 44.73185736066857 ], [ -68.829423448021132, 44.731749298470184 ], [ -68.82945276169859, 44.731443261076713 ], [ -68.829432340874419, 44.731006616646113 ], [ -68.82945893742162, 44.730952642034332 ], [ -68.829433811865556, 44.730907593825883 ], [ -68.829435962199966, 44.730714046309252 ], [ -68.829476809151288, 44.730511552925805 ], [ -68.829451604407467, 44.730493510511664 ], [ -68.82945165348211, 44.729939866764241 ], [ -68.829492340423286, 44.729255748343704 ], [ -68.829562540999063, 44.728733716931735 ], [ -68.829629501415539, 44.728508758292868 ], [ -68.829623792229512, 44.728436730637284 ], [ -68.829669345814409, 44.728378282406844 ], [ -68.829644691427688, 44.728306226494041 ], [ -68.82968396329872, 44.728238767748948 ], [ -68.829727390920212, 44.727829226758928 ], [ -68.829819592842227, 44.727608804916379 ], [ -68.829821158974539, 44.7274782744229 ], [ -68.82990113775513, 44.727253332461949 ], [ -68.829875579406334, 44.727221786640698 ], [ -68.829916389797575, 44.727032798396209 ], [ -68.829995456481413, 44.726848368908968 ], [ -68.830063425981905, 44.726546890455154 ], [ -68.830104797490321, 44.72629938926665 ], [ -68.830109467475623, 44.725916793752461 ], [ -68.830097986058647, 44.72579524494784 ], [ -68.829924540759578, 44.725326862148158 ], [ -68.829705169793812, 44.724912427475232 ], [ -68.829536620124074, 44.724525074156929 ], [ -68.829537594069521, 44.724462060075552 ], [ -68.829374835695731, 44.724254763114665 ], [ -68.829386742188802, 44.724232274276915 ], [ -68.829192550933783, 44.723844882096294 ], [ -68.829194038884168, 44.723741357352353 ], [ -68.829155840102587, 44.723714294272902 ], [ -68.829188884070703, 44.723615316043968 ], [ -68.829157447344272, 44.723570258277555 ], [ -68.829192979958805, 44.723295737587819 ], [ -68.829245321827614, 44.723210293679266 ], [ -68.829233799487199, 44.723102250153232 ], [ -68.829272630626406, 44.723048293802513 ], [ -68.829267343315706, 44.722967263034278 ], [ -68.829341152598801, 44.72269280199454 ], [ -68.829365727795363, 44.722251722279978 ], [ -68.829273135886254, 44.721936501074545 ], [ -68.829179398515109, 44.721742809877199 ], [ -68.828870460232409, 44.721332741169498 ], [ -68.828776804833907, 44.721112041276847 ], [ -68.828798761088322, 44.720891517173435 ], [ -68.828851608166616, 44.72076556271859 ], [ -68.828881361546067, 44.719910382574234 ], [ -68.828915378323558, 44.719748390228617 ], [ -68.828967292935658, 44.719671946904974 ], [ -68.82904477519746, 44.719622550305594 ], [ -68.829225343370226, 44.719537298217716 ], [ -68.829379730880788, 44.719501519342074 ], [ -68.829476151309606, 44.719452153250259 ], [ -68.829546795727538, 44.71944775436566 ], [ -68.829688656742974, 44.719375946748812 ], [ -68.829804350893895, 44.719349113252754 ], [ -68.830023258311016, 44.719241410381784 ], [ -68.830164513146144, 44.719241620525032 ], [ -68.830409543423031, 44.719106949789094 ], [ -68.830681779999651, 44.718841783325878 ], [ -68.830766615338916, 44.718706874206383 ], [ -68.830825143178046, 44.718661947623303 ], [ -68.830858207623209, 44.718553967701041 ], [ -68.830929924486668, 44.718450549336076 ], [ -68.830981691144245, 44.718423617478031 ], [ -68.830995278248068, 44.718365121303421 ], [ -68.831072822526068, 44.71829321675969 ], [ -68.831145037828549, 44.71815378433601 ], [ -68.831216231947948, 44.718095378310458 ], [ -68.831191045130765, 44.718072834435411 ], [ -68.831204142408623, 44.718045847822459 ], [ -68.831295298857896, 44.717906443290524 ], [ -68.831347074408853, 44.717875011955904 ], [ -68.831341315317033, 44.717820988996301 ], [ -68.831490997357022, 44.717636662435147 ], [ -68.831478447960819, 44.717609635434464 ], [ -68.831523991145161, 44.717551188848162 ], [ -68.831627695464434, 44.717438811033098 ], [ -68.831802020947336, 44.717326539660291 ], [ -68.831866335702301, 44.71732663436056 ], [ -68.831879436033105, 44.717299642649515 ], [ -68.832027589734622, 44.71723234570657 ], [ -68.832213965004271, 44.717183107038942 ], [ -68.832362629382899, 44.717075298935931 ], [ -68.832820189397793, 44.716846406284048 ], [ -68.833556578930853, 44.716383860815327 ], [ -68.833827857875391, 44.716168197966915 ], [ -68.835549895834205, 44.714703303497821 ], [ -68.83562834575477, 44.714586383791527 ], [ -68.835738334118005, 44.714478515574598 ], [ -68.835770696324317, 44.714474060280494 ], [ -68.836977660178562, 44.713188447838974 ], [ -68.837212666436002, 44.712815185887379 ], [ -68.837335318638054, 44.712549789960185 ], [ -68.837746388780104, 44.712136265987411 ], [ -68.837864241952218, 44.711893369316655 ], [ -68.837817114646299, 44.711533205647136 ], [ -68.837742774893911, 44.71131254266497 ], [ -68.837756362560555, 44.711249543775935 ], [ -68.837705557684615, 44.711217963750478 ], [ -68.837661598821384, 44.711136880752562 ], [ -68.837606498865213, 44.710947752855375 ], [ -68.837653594721516, 44.710754268403797 ], [ -68.83782113423689, 44.710659980394482 ], [ -68.838199584702309, 44.710620005656708 ], [ -68.838315723511855, 44.710566155608987 ], [ -68.838470722804786, 44.710440343555121 ], [ -68.838575805688109, 44.710246940704465 ], [ -68.838564309556077, 44.71012538988429 ], [ -68.838475868143405, 44.710008234612374 ], [ -68.837993622433544, 44.70963845351573 ], [ -68.83791241375549, 44.70947629720046 ], [ -68.837944877008823, 44.709435831739818 ], [ -68.837952267563139, 44.709332314994434 ], [ -68.838102867516611, 44.709084962577315 ], [ -68.838199761417997, 44.708999577497138 ], [ -68.838335310840293, 44.708914247057798 ], [ -68.838676567902183, 44.708770690830427 ], [ -68.838857657375044, 44.708622405863984 ], [ -68.838942924445462, 44.70846048502009 ], [ -68.838996717502681, 44.708267009374119 ], [ -68.838985727507065, 44.708104950240113 ], [ -68.838885766961113, 44.707875248064859 ], [ -68.838671555590025, 44.707591369909515 ], [ -68.838126509124621, 44.707113473887148 ], [ -68.83806323627806, 44.707027861768239 ], [ -68.838013962018806, 44.707014286368832 ], [ -68.837340693329821, 44.706405668792115 ], [ -68.837264289253284, 44.706360549068286 ], [ -68.837150697412767, 44.706211846473785 ], [ -68.837086893790897, 44.706175745768221 ], [ -68.837017773911995, 44.706063118687439 ], [ -68.836988404927027, 44.705847018500585 ], [ -68.837165278639802, 44.70551418222189 ], [ -68.8372237720843, 44.705473751537212 ], [ -68.837184550328644, 44.705253140514593 ], [ -68.837076358500013, 44.705149458917333 ], [ -68.836597193358529, 44.704824686898448 ], [ -68.835540335855413, 44.704287528460739 ], [ -68.835437808326546, 44.704273877757366 ], [ -68.835240879931561, 44.704165567177029 ], [ -68.835219131420899, 44.704183538150986 ], [ -68.835055593270837, 44.7041247856518 ], [ -68.834902323722858, 44.704061548430019 ], [ -68.834832656945125, 44.704007433113681 ], [ -68.83475576123486, 44.703998318195445 ], [ -68.834220201070124, 44.703812994099962 ], [ -68.834169443169714, 44.703767906998472 ], [ -68.834143777127537, 44.703776873415372 ], [ -68.834022423602448, 44.703727184373221 ], [ -68.833517832917693, 44.703613918921796 ], [ -68.833210065141728, 44.703640478423324 ], [ -68.832631782331134, 44.703774667653548 ], [ -68.832249157481954, 44.703634569229592 ], [ -68.832032948400169, 44.703512719253787 ], [ -68.83200728214976, 44.703521682679849 ], [ -68.831663368285362, 44.703363634357707 ], [ -68.830987189272491, 44.703115069448778 ], [ -68.830872989647531, 44.703047382775971 ], [ -68.830502538460067, 44.70292980321927 ], [ -68.830316614968794, 44.702974538046092 ], [ -68.83015501668234, 44.703064322165851 ], [ -68.829627939742679, 44.703761222447639 ], [ -68.829367811142419, 44.704089423466165 ], [ -68.829089888437309, 44.70429606255162 ], [ -68.82896113227882, 44.704349884381898 ], [ -68.828819883624035, 44.704358673935388 ], [ -68.828743449060937, 44.704327051467686 ], [ -68.828661809402263, 44.704187391972077 ], [ -68.828734583195754, 44.703989447646023 ], [ -68.828786311413495, 44.703971522853855 ], [ -68.828761158191611, 44.703939974751094 ], [ -68.828946519440748, 44.70341811400364 ], [ -68.829338984025767, 44.702757025485994 ], [ -68.829355711458547, 44.702432962882952 ], [ -68.829332681468813, 44.70221687257267 ], [ -68.829346736499687, 44.702131371080277 ], [ -68.829424761109749, 44.702023458430936 ], [ -68.829656573220348, 44.701933780074313 ], [ -68.830080005006408, 44.701871392546948 ], [ -68.830131261183368, 44.701880472484405 ], [ -68.830156962961397, 44.701858003995454 ], [ -68.830388260713647, 44.701808834969256 ], [ -68.831157640927003, 44.701742454845906 ], [ -68.831228355199642, 44.701706552137942 ], [ -68.831614009089805, 44.701612595193957 ], [ -68.831929567348411, 44.701478024739743 ], [ -68.832059428275784, 44.701311672602799 ], [ -68.832171378314783, 44.701064270818662 ], [ -68.832203788840019, 44.701041811724075 ], [ -68.83219124618752, 44.701014787243757 ], [ -68.832295938534727, 44.700821389277273 ], [ -68.8323764078648, 44.700546932475241 ], [ -68.832432445172316, 44.70012840327675 ], [ -68.832471583778172, 44.70009695279191 ], [ -68.832452794127036, 44.700047412451219 ], [ -68.83251141860616, 44.699961975912508 ], [ -68.832491342310988, 44.699948440929283 ], [ -68.832513462877515, 44.6997999350341 ], [ -68.832701396569121, 44.699053011871314 ], [ -68.832703423787649, 44.698895472785914 ], [ -68.83276939681565, 44.698724521804785 ], [ -68.83275737823466, 44.698652487200995 ], [ -68.832798164818172, 44.698458995094718 ], [ -68.832904858307657, 44.698117058520829 ], [ -68.832943626922997, 44.6980766036259 ], [ -68.832971792493581, 44.697883095486965 ], [ -68.833010551487007, 44.69784714240167 ], [ -68.83299215636886, 44.697797600196232 ], [ -68.833056058398569, 44.69765815641501 ], [ -68.83309889568433, 44.697437661096195 ], [ -68.833251685932069, 44.696974259151517 ], [ -68.833290967549132, 44.696893295772405 ], [ -68.83334273969372, 44.696857361562486 ], [ -68.833337436059779, 44.696780834945621 ], [ -68.833408653359328, 44.696704417460118 ], [ -68.83350750678872, 44.696479501796432 ], [ -68.833565577126649, 44.696448078513356 ], [ -68.833572424801801, 44.696398573172281 ], [ -68.833670702416541, 44.696236677386075 ], [ -68.833728823960868, 44.696187244184806 ], [ -68.83376857589279, 44.696079275239576 ], [ -68.834151119452713, 44.695679227032841 ], [ -68.834611711827748, 44.695292788585363 ], [ -68.834773450521965, 44.695004944446005 ], [ -68.834883913664868, 44.694577491171025 ], [ -68.834886960524045, 44.6943389301249 ], [ -68.834442325071066, 44.693280504549278 ], [ -68.834375742625681, 44.692978826622529 ], [ -68.834201939359033, 44.69279852547006 ], [ -68.833921779612012, 44.692708093927315 ], [ -68.833270461600222, 44.692630626014626 ], [ -68.832621770008856, 44.69246313123255 ], [ -68.832061072373804, 44.692282259139517 ], [ -68.831288985646353, 44.692101074727489 ], [ -68.830903103677556, 44.6920239825929 ], [ -68.830551429295753, 44.691982949711829 ], [ -68.828481327604109, 44.691565749390804 ], [ -68.828059995426074, 44.691475091349936 ], [ -68.827254122063906, 44.691257818961056 ], [ -68.826182456390683, 44.691035632306281 ], [ -68.823973887592544, 44.690393079343721 ], [ -68.822572464667417, 44.689940788137669 ], [ -68.821941144956909, 44.689786765163653 ], [ -68.821308766910505, 44.68972276157637 ], [ -68.820727632597709, 44.689793870483136 ], [ -68.820443421671129, 44.689878946686363 ], [ -68.819946688401288, 44.69020225045292 ], [ -68.819786424888264, 44.690377544880114 ], [ -68.819678102551563, 44.690588930906458 ], [ -68.819583198641809, 44.691178438474907 ], [ -68.81959167209537, 44.692132709103063 ], [ -68.819644755605509, 44.692438873715879 ], [ -68.819780973286072, 44.692898212384478 ], [ -68.820115748205964, 44.693429883345395 ], [ -68.820103764925719, 44.693605411570353 ], [ -68.820065314928442, 44.693668366875499 ], [ -68.819998563391437, 44.693699769627507 ], [ -68.819957220782257, 44.693677200225885 ], [ -68.819856177456643, 44.693573515886342 ], [ -68.819806427048235, 44.693465405521657 ], [ -68.819711727065979, 44.693352727355325 ], [ -68.819705977644077, 44.693298701081261 ], [ -68.819433430913918, 44.693050703808964 ], [ -68.819250327782612, 44.692829858578442 ], [ -68.818887663918758, 44.692140596050976 ], [ -68.818813895598353, 44.691892912299942 ], [ -68.818803453796832, 44.691699346191591 ], [ -68.818950867707471, 44.691096417018599 ], [ -68.818953072076496, 44.690893869747129 ], [ -68.818944564082926, 44.690839839038418 ], [ -68.818821642145522, 44.690799132443047 ], [ -68.818784782309478, 44.690731556201854 ], [ -68.818793118028864, 44.690461495862557 ], [ -68.818716298944167, 44.690434367851616 ], [ -68.818524207958347, 44.690443063981114 ], [ -68.818305497028874, 44.69051473586287 ], [ -68.818208636530443, 44.690591103198479 ], [ -68.818149521821454, 44.690703537855683 ], [ -68.818026068654902, 44.690833877399328 ], [ -68.817929217841098, 44.690905742655531 ], [ -68.817691152525569, 44.690986383694799 ], [ -68.817312717042782, 44.691057798592176 ], [ -68.817037000280422, 44.691070860275069 ], [ -68.816696859281834, 44.691128831422887 ], [ -68.816575405050258, 44.691124134573045 ], [ -68.816549319264325, 44.691142097577732 ], [ -68.816402187620838, 44.691150862345083 ], [ -68.816268775941481, 44.691060625796808 ], [ -68.81616371085461, 44.69073636846651 ], [ -68.81617833677393, 44.690601356482908 ], [ -68.816140620506488, 44.690556284765954 ], [ -68.816118220087645, 44.690281673275294 ], [ -68.816030884724753, 44.690087980519998 ], [ -68.815791767453959, 44.689754505582506 ], [ -68.815792304329477, 44.689709495436205 ], [ -68.815715910403085, 44.689673362380397 ], [ -68.815405697045406, 44.689375781686209 ], [ -68.814986114081378, 44.68911853468493 ], [ -68.814794611106251, 44.6890687089141 ], [ -68.814532385031313, 44.689059282178242 ], [ -68.814295951071387, 44.689000381397747 ], [ -68.81409917146442, 44.688874029407856 ], [ -68.813706322928752, 44.6885222947869 ], [ -68.813797793347149, 44.688405410776603 ], [ -68.813785901341305, 44.688301863774925 ], [ -68.813747678822196, 44.688292800247503 ], [ -68.81368489983717, 44.688193669580109 ], [ -68.813731019483882, 44.688076714135192 ], [ -68.814008789289204, 44.687910623268259 ], [ -68.814060084714555, 44.687901703080449 ], [ -68.814124987839662, 44.687829789077014 ], [ -68.814254201535888, 44.687748978445335 ], [ -68.814448930607071, 44.687528734160679 ], [ -68.814514868936186, 44.687380302609895 ], [ -68.814444803832885, 44.687339677111254 ], [ -68.814284214390256, 44.687366424715975 ], [ -68.814207738152177, 44.687357296619261 ], [ -68.814144475310769, 44.687285174028702 ], [ -68.814132438962261, 44.687226640350772 ], [ -68.814171184557125, 44.687195192915766 ], [ -68.814258586852759, 44.686871249124856 ], [ -68.814233535214314, 44.686812691774627 ], [ -68.814125784021343, 44.686717995043921 ], [ -68.814094859452439, 44.686645920021931 ], [ -68.814208471651398, 44.686263504798511 ], [ -68.814196012496168, 44.686213969121866 ], [ -68.814145737236558, 44.686150871360624 ], [ -68.813871531829818, 44.68606940393439 ], [ -68.813712564501813, 44.685961115239074 ], [ -68.813625240087418, 44.685767420515667 ], [ -68.813614805693689, 44.685578353084459 ], [ -68.813628969859877, 44.685465844819234 ], [ -68.813674447972417, 44.685425407324281 ], [ -68.813656174247825, 44.685344356622203 ], [ -68.813800594708141, 44.685070019000655 ], [ -68.813917344058439, 44.68493967297001 ], [ -68.814009447406249, 44.68474627070939 ], [ -68.814036639774471, 44.684629284245247 ], [ -68.81401107992555, 44.684606735842301 ], [ -68.814000637433878, 44.684417668394566 ], [ -68.81396721783284, 44.68426457336372 ], [ -68.814538748797048, 44.684098956708908 ], [ -68.815793432051422, 44.683754394335573 ], [ -68.815861151241165, 44.684285647574541 ], [ -68.815991758912759, 44.684758485437683 ], [ -68.816245188077957, 44.685299038616805 ], [ -68.816536985352116, 44.685803642983807 ], [ -68.816853652557825, 44.686177752671973 ], [ -68.817085974038648, 44.686412186410479 ], [ -68.81760049872247, 44.686822620532695 ], [ -68.818313474461092, 44.687206359553102 ], [ -68.81868250932645, 44.687359989058471 ], [ -68.819249902963875, 44.687513928956633 ], [ -68.819929129108132, 44.687596022755692 ], [ -68.821056148778823, 44.687615799057312 ], [ -68.821822613758343, 44.687671012132455 ], [ -68.822415526231566, 44.687739453394265 ], [ -68.822959014092561, 44.687848326654645 ], [ -68.825623538791064, 44.688563616242504 ], [ -68.827411734831074, 44.689047960703519 ], [ -68.831223528226218, 44.690061929657659 ], [ -68.83287621236758, 44.69052348384961 ], [ -68.834886450375862, 44.691048547476079 ], [ -68.835612923435662, 44.691319668979119 ], [ -68.83594490510545, 44.691491191649462 ], [ -68.836499238022569, 44.691834077186201 ], [ -68.837024749951439, 44.692329961787891 ], [ -68.83741401626672, 44.692893167790352 ], [ -68.837619495184128, 44.693316573937778 ], [ -68.837738817832545, 44.693663335880466 ], [ -68.837834086338404, 44.694010063525305 ], [ -68.837843867577348, 44.694320659939464 ], [ -68.837811838601695, 44.694914773379992 ], [ -68.837720859762669, 44.695292746841183 ], [ -68.837442265903235, 44.695764977100055 ], [ -68.836543009134488, 44.697024033137872 ], [ -68.836241571360233, 44.697617757445421 ], [ -68.836253670163302, 44.69766278574297 ], [ -68.836207436766401, 44.697973304283934 ], [ -68.836218079933218, 44.698535967663894 ], [ -68.836301004784374, 44.699062727369174 ], [ -68.836299466845887, 44.699188759307845 ], [ -68.836330930490675, 44.699220314820209 ], [ -68.836366994311078, 44.699440924355848 ], [ -68.836545071953935, 44.700075846828923 ], [ -68.837004240726756, 44.70132783794584 ], [ -68.837042441101588, 44.701350399174032 ], [ -68.837205774918374, 44.701620702649755 ], [ -68.837256587267973, 44.701647783626811 ], [ -68.837337669797236, 44.701850452034357 ], [ -68.837528604867728, 44.702125298688166 ], [ -68.837875928145849, 44.702053772783948 ], [ -68.839752499027838, 44.701718830577107 ], [ -68.839766650861392, 44.701736855302237 ], [ -68.839726943416409, 44.701831325777277 ], [ -68.839777703956358, 44.701876407917183 ], [ -68.839889760323032, 44.702151137426398 ], [ -68.84016001974652, 44.702561126091872 ], [ -68.840234829672653, 44.702754782453795 ], [ -68.840312890165492, 44.703200508906214 ], [ -68.840294246879438, 44.703668609301332 ], [ -68.840354957583273, 44.703970272494182 ], [ -68.840760172048007, 44.704366945156302 ], [ -68.840878082022996, 44.704668690283036 ], [ -68.840960220011809, 44.704781333184265 ], [ -68.84115035587611, 44.704925636673615 ], [ -68.841276995920779, 44.705065349773768 ], [ -68.841338328794748, 44.705285992629051 ], [ -68.8413304890136, 44.705412015743171 ], [ -68.841367741798862, 44.705493088096176 ], [ -68.841501237895599, 44.705578798395827 ], [ -68.841808451490849, 44.705610732206537 ], [ -68.841910474800272, 44.705664885534986 ], [ -68.841960735829133, 44.705750480049176 ], [ -68.842011550487967, 44.705777558894432 ], [ -68.84226141871963, 44.706300037557284 ], [ -68.842285522764698, 44.706430609181297 ], [ -68.842336240429532, 44.706493694942957 ], [ -68.842459931250048, 44.706849460864376 ], [ -68.842760542735746, 44.707430529738019 ], [ -68.842822344093008, 44.707624166329232 ], [ -68.842845417702534, 44.707844755616108 ], [ -68.84285142768961, 44.708393910531093 ], [ -68.842798618773301, 44.708519871791751 ], [ -68.842824175948422, 44.708551414822985 ], [ -68.842816460754548, 44.708632424764197 ], [ -68.842704000720431, 44.70893385049277 ], [ -68.842716549967079, 44.708960873790261 ], [ -68.842363891755824, 44.709532040799481 ], [ -68.84223208190231, 44.709837938634209 ], [ -68.842154070530754, 44.70994585739308 ], [ -68.842139540190715, 44.710067371884797 ], [ -68.842303922145547, 44.71026115040916 ], [ -68.842341570203246, 44.71034222543215 ], [ -68.842408140398206, 44.710670903786138 ], [ -68.842483053662036, 44.710833048188604 ], [ -68.842666362745277, 44.711031354105891 ], [ -68.842653268854193, 44.711058344582277 ], [ -68.842742309673, 44.711103478064736 ], [ -68.842945418354205, 44.711274802271681 ], [ -68.843059067884994, 44.71140999604917 ], [ -68.843108868246361, 44.711522592830384 ], [ -68.843057483390695, 44.711558531906796 ], [ -68.842939331289884, 44.71177442753951 ], [ -68.84289910948911, 44.711913909081204 ], [ -68.84294574900656, 44.71231908079443 ], [ -68.842852662082606, 44.712602525767558 ], [ -68.842412370131669, 44.713038534439157 ], [ -68.842205193310846, 44.713200291883609 ], [ -68.842049222314643, 44.713393627606301 ], [ -68.841891699493445, 44.713721995943274 ], [ -68.841676173708123, 44.71405028616158 ], [ -68.841624391352042, 44.714086224026111 ], [ -68.841617523866105, 44.714144728317684 ], [ -68.841539086592732, 44.714261652165021 ], [ -68.841190425885245, 44.714508731140697 ], [ -68.841112480170423, 44.71458964571373 ], [ -68.840929377501453, 44.714890973588936 ], [ -68.840630595626607, 44.715219140754336 ], [ -68.840501279603359, 44.71532698918918 ], [ -68.840424265524618, 44.715353887592485 ], [ -68.840152682424474, 44.715547059055808 ], [ -68.839514135366201, 44.715892754552549 ], [ -68.839404179730991, 44.715991627779196 ], [ -68.839181229696848, 44.716427929145063 ], [ -68.839077076071106, 44.71656731918474 ], [ -68.838824308244497, 44.716801022923327 ], [ -68.838553204051181, 44.716958181567179 ], [ -68.838251068562244, 44.717065782989394 ], [ -68.837838607508814, 44.717258749284035 ], [ -68.837735044839306, 44.71732611963877 ], [ -68.837514682479608, 44.71753736234286 ], [ -68.837292261582121, 44.717919646200656 ], [ -68.837277639815838, 44.718068165744107 ], [ -68.837170925967314, 44.718414604496182 ], [ -68.837039475510167, 44.718715997050623 ], [ -68.836948427101049, 44.718823895992486 ], [ -68.836959943290069, 44.718936440644498 ], [ -68.836843316153548, 44.719017297013551 ], [ -68.836843214964048, 44.719053303987415 ], [ -68.836608781580182, 44.719354548845224 ], [ -68.836128789271541, 44.719839988573646 ], [ -68.835753444623066, 44.720154532341333 ], [ -68.835546672776303, 44.72029827327512 ], [ -68.835507996641013, 44.720302719407421 ], [ -68.835352486490265, 44.72045553495537 ], [ -68.835042089661272, 44.720684647747973 ], [ -68.834827483692365, 44.720945406051925 ], [ -68.834775091550014, 44.721053359118415 ], [ -68.834792921748772, 44.721165915589829 ], [ -68.834912898672016, 44.721296622051085 ], [ -68.835097717723755, 44.721382411263384 ], [ -68.835353727846439, 44.721409789089392 ], [ -68.835391873435924, 44.721454854837134 ], [ -68.835443220766379, 44.721436924025561 ], [ -68.83551969423614, 44.721464042652634 ], [ -68.835608630709842, 44.721549692907132 ], [ -68.835620237565308, 44.721630729987389 ], [ -68.835374635538926, 44.721828429094842 ], [ -68.83534805794099, 44.721877903369354 ], [ -68.835290408604862, 44.721891323219168 ], [ -68.835167449578748, 44.721976668020879 ], [ -68.835133958399936, 44.722098151485966 ], [ -68.835151229010151, 44.72226922331518 ], [ -68.835122437599878, 44.722543753130566 ], [ -68.835068563595058, 44.722755231151488 ], [ -68.834937169134307, 44.723029612678289 ], [ -68.834833404206236, 44.723164497419191 ], [ -68.834819819831097, 44.723222994005134 ], [ -68.834781096215892, 44.723240942793545 ], [ -68.834806248239587, 44.723276988779354 ], [ -68.834792154084028, 44.723375993542462 ], [ -68.834918818926127, 44.72351571327674 ], [ -68.834995291363867, 44.723542832243652 ], [ -68.83525734955937, 44.723529707585158 ], [ -68.835365404125938, 44.723552367517811 ], [ -68.835422062030318, 44.723610965406365 ], [ -68.835286383052733, 44.723867336320168 ], [ -68.835130446250105, 44.724029154509211 ], [ -68.835064496609462, 44.724186603156241 ], [ -68.834856634538838, 44.724429363784978 ], [ -68.834737800514404, 44.724726269786558 ], [ -68.834488663966965, 44.72518953438329 ], [ -68.834435311545079, 44.725355997010467 ], [ -68.834407621587445, 44.725518000014588 ], [ -68.834418130465338, 44.7257070643304 ], [ -68.83451606401313, 44.72609880989215 ], [ -68.834463778627722, 44.726166251462992 ], [ -68.834310362568047, 44.726688165428335 ], [ -68.834273818911655, 44.727048208202035 ], [ -68.834283230247337, 44.727345299617546 ], [ -68.834332491366837, 44.727507411666849 ], [ -68.834534146846678, 44.727782277640614 ], [ -68.834558220159934, 44.72792184898497 ], [ -68.834475660651734, 44.728367344668534 ], [ -68.834472978717443, 44.728614903432245 ], [ -68.834540066420459, 44.728889574227338 ], [ -68.83466580334516, 44.729083307111587 ], [ -68.834888851810192, 44.729196160311268 ], [ -68.835048614399369, 44.729218897775162 ], [ -68.835414128235001, 44.729192416675936 ], [ -68.835478519951053, 44.729170002847376 ], [ -68.835594141730596, 44.729174668659688 ], [ -68.83569577707577, 44.729237835426396 ], [ -68.835719846732644, 44.729377406485057 ], [ -68.835692145670265, 44.729543908982492 ], [ -68.835612682633311, 44.729732841068369 ], [ -68.83542990144764, 44.730034159785127 ], [ -68.835390112870428, 44.730146632944383 ], [ -68.835325210607451, 44.73020955485341 ], [ -68.835308517288354, 44.730529114977223 ], [ -68.835344125211691, 44.730776731387081 ], [ -68.835297081873335, 44.730943205859916 ], [ -68.835019109606833, 44.731131853547694 ], [ -68.834823734330939, 44.731415145740151 ], [ -68.834655709820822, 44.73151842692566 ], [ -68.834519575738341, 44.731648767512901 ], [ -68.834431150260841, 44.732071750289251 ], [ -68.83431382878571, 44.732247126055896 ], [ -68.83400395229674, 44.732408719037082 ], [ -68.833965119269934, 44.732462674394171 ], [ -68.833957860211797, 44.732516678156784 ], [ -68.833995595257306, 44.732570747327571 ], [ -68.834065702831168, 44.73262036170393 ], [ -68.834180942678444, 44.732620529079583 ], [ -68.834566839374517, 44.732513060082979 ], [ -68.834643327013069, 44.732540179265094 ], [ -68.834693052113636, 44.732679787601711 ], [ -68.834679478167089, 44.732733782267672 ], [ -68.834523425930868, 44.732927106928912 ], [ -68.834173211534704, 44.733255184135047 ], [ -68.833525959060765, 44.733803386697502 ], [ -68.833389322704278, 44.733969729666008 ], [ -68.833375743717383, 44.734023724157375 ], [ -68.8334192288618, 44.734136318013405 ], [ -68.833495679360482, 44.734176938338827 ], [ -68.833726570309977, 44.734177274723059 ], [ -68.833764796834757, 44.734195337647542 ], [ -68.833827149135942, 44.734334962188591 ], [ -68.833798813653445, 44.734582485858688 ], [ -68.833580562701911, 44.735131312102389 ], [ -68.833584664350184, 44.735351874546531 ], [ -68.833659662026236, 44.735487018268387 ], [ -68.833695339773286, 44.735707629190898 ], [ -68.833746099373869, 44.735761717378949 ], [ -68.833822578019252, 44.735793336389605 ], [ -68.834168743764053, 44.735784835915752 ], [ -68.834476100265846, 44.73582129131136 ], [ -68.834929619389172, 44.735952482853698 ], [ -68.835543469934223, 44.73605239336402 ], [ -68.836278694867758, 44.736219994367154 ], [ -68.836624341550319, 44.736256498862794 ], [ -68.836688206770759, 44.736283593578946 ], [ -68.836700301216723, 44.736333125845796 ], [ -68.836648495864026, 44.736369061279902 ], [ -68.836372940714028, 44.736391173397777 ], [ -68.836340524717045, 44.736409131709976 ], [ -68.835494308747897, 44.736407915948703 ], [ -68.835275945591988, 44.736443610727058 ], [ -68.835083717513953, 44.736447835128452 ], [ -68.834852276284593, 44.736501517481095 ], [ -68.834543253931372, 44.736631600317331 ], [ -68.834362152283177, 44.736748367481731 ], [ -68.83415521482263, 44.736928114751073 ], [ -68.833998758512053, 44.737121437965762 ], [ -68.833830166748115, 44.737278733789722 ], [ -68.833394438106723, 44.737822740968262 ], [ -68.833295994898037, 44.73801614780416 ], [ -68.833189272025692, 44.738344576853081 ], [ -68.833187137375575, 44.738538124204254 ], [ -68.83322432227078, 44.738646207027074 ], [ -68.833210182788235, 44.738758714197679 ], [ -68.832949994730981, 44.739059912883711 ], [ -68.832458267412477, 44.739441789138766 ], [ -68.832455107181758, 44.739716360072208 ], [ -68.832430691136636, 44.739833353863617 ], [ -68.832372039096796, 44.739914287905783 ], [ -68.831959030967056, 44.740098230264209 ], [ -68.831878614921294, 44.740197136771975 ], [ -68.831871248574942, 44.740287149665349 ], [ -68.831908422607881, 44.740399734681809 ], [ -68.832199166680638, 44.740728746970753 ], [ -68.83221068492584, 44.740841291671416 ], [ -68.832144769721864, 44.740976231657996 ], [ -68.831936822038756, 44.74122349296362 ], [ -68.831898115124517, 44.741232434603589 ], [ -68.83191061410885, 44.741277463605407 ], [ -68.831819530170179, 44.741380858674091 ], [ -68.831326225564553, 44.741888760772973 ], [ -68.831279083052863, 44.742082238975748 ], [ -68.831270230706721, 44.742275778750169 ], [ -68.831221183845258, 44.742446752570643 ], [ -68.831104831837536, 44.742685141486888 ], [ -68.830591498157133, 44.7432875361757 ], [ -68.830526628549194, 44.743332453033418 ], [ -68.83009715326186, 44.743871954518511 ], [ -68.829965216962719, 44.74417333685679 ], [ -68.829937949232459, 44.74431733413504 ], [ -68.829967870972041, 44.744479418784444 ], [ -68.830024372320224, 44.744596534915203 ], [ -68.830157424715935, 44.74472276097314 ], [ -68.830289494483679, 44.744916510065657 ], [ -68.830269928378385, 44.745128035966353 ], [ -68.830149726622423, 44.745330411123717 ], [ -68.83012869662366, 44.745501426046559 ], [ -68.830289977224027, 44.745965281970719 ], [ -68.83047808062841, 44.746303149371464 ], [ -68.830617928388776, 44.746402384126633 ], [ -68.83074615856998, 44.746425078260792 ], [ -68.831195629081719, 44.746344723464517 ], [ -68.831350135601312, 44.746290937543662 ], [ -68.831465406113537, 44.746291107705161 ], [ -68.832596723372589, 44.746585344007443 ], [ -68.832673202640578, 44.746621470560058 ], [ -68.833071940425569, 44.747022654834346 ], [ -68.833285182615256, 44.747410066655917 ], [ -68.833322363918342, 44.747522651082107 ], [ -68.833342334330609, 44.74799080053166 ], [ -68.833316574489402, 44.748026772384442 ], [ -68.8333407624947, 44.748125832403282 ], [ -68.833287320784422, 44.748314805244036 ], [ -68.833318259517256, 44.748400372203164 ], [ -68.833423775719595, 44.748499550864636 ], [ -68.833124875337688, 44.748656655197095 ], [ -68.832962876000821, 44.748705933240757 ], [ -68.832540918920515, 44.748822342233858 ], [ -68.832523665994401, 44.748781808197251 ], [ -68.83223557299786, 44.748340270954643 ], [ -68.832217726541359, 44.74823221640559 ], [ -68.832160575139511, 44.748200624793846 ], [ -68.832009726106321, 44.747943837590718 ], [ -68.832037026006901, 44.747786337041752 ], [ -68.832297326676283, 44.74746263453072 ], [ -68.832415604403138, 44.74724225215094 ], [ -68.832397848989814, 44.747102690100455 ], [ -68.832265767208597, 44.746908946013207 ], [ -68.83214479795582, 44.746832247568626 ], [ -68.831991327648225, 44.746800514260144 ], [ -68.831792774065207, 44.746795720189319 ], [ -68.831091946574574, 44.746974733109553 ], [ -68.830905540192845, 44.747001463103963 ], [ -68.830790745179314, 44.746974287248051 ], [ -68.830764638624032, 44.746992253291857 ], [ -68.83064936672254, 44.746992082420974 ], [ -68.830098897858221, 44.746914744126478 ], [ -68.82995797024239, 44.746914534388068 ], [ -68.829721271776691, 44.746860167546096 ], [ -68.829562499711798, 44.746751902352813 ], [ -68.829461917243208, 44.746589712170987 ], [ -68.829251325859488, 44.745981738561767 ], [ -68.82926490798566, 44.745927747189882 ], [ -68.829226744087805, 44.745882677050943 ], [ -68.829190052944014, 44.745743086152693 ], [ -68.829088946043953, 44.745625905400217 ], [ -68.829000353982508, 44.745549252055689 ], [ -68.828847425962493, 44.745468002945309 ], [ -68.828808715733473, 44.745476946037236 ], [ -68.828642656899518, 44.745431686430614 ], [ -68.828449980899848, 44.745444900096807 ], [ -68.828135084397616, 44.745538952124022 ], [ -68.827651172366103, 44.745785787298367 ], [ -68.827262584987196, 44.746100281734712 ], [ -68.82650993463686, 44.746841831504504 ], [ -68.826249540612636, 44.747188024741014 ], [ -68.826117097643674, 44.747516410028545 ], [ -68.826102925143516, 44.747633417901838 ], [ -68.825930128382538, 44.74812378148917 ], [ -68.82587768149132, 44.748236229171226 ], [ -68.825760423040521, 44.748371087006774 ], [ -68.825618934907467, 44.748424885223763 ], [ -68.825542511816451, 44.7483707543444 ], [ -68.825504217307724, 44.74837069584121 ], [ -68.82546648347487, 44.74831662401666 ], [ -68.82547432314874, 44.748204105856608 ], [ -68.825417297402481, 44.748131999809289 ], [ -68.825405780914778, 44.748023953868341 ], [ -68.825342887054589, 44.747933836598307 ], [ -68.825215661930542, 44.747839114026938 ], [ -68.825018088227282, 44.747771296912362 ], [ -68.824729989832704, 44.74774384680061 ], [ -68.824124580249801, 44.747949971669421 ], [ -68.824034939021772, 44.747958834695979 ], [ -68.823970062643198, 44.748003745271156 ], [ -68.823229402816281, 44.748286175026969 ], [ -68.822874262330814, 44.748492677830285 ], [ -68.822667278370147, 44.748663402733577 ], [ -68.82180349501634, 44.749053657772635 ], [ -68.821649477664479, 44.749071422076653 ], [ -68.821623900386228, 44.749044376307509 ], [ -68.821559498630165, 44.749062280422216 ], [ -68.821316412686144, 44.749030392728351 ], [ -68.821123622397252, 44.749075101478162 ], [ -68.820968648820596, 44.749146877524126 ], [ -68.820645378916694, 44.749384930950271 ], [ -68.820129428535097, 44.749631683766445 ], [ -68.820013659054425, 44.749663009005047 ], [ -68.819783234169876, 44.749622136900342 ], [ -68.819667918645891, 44.749635457787228 ], [ -68.819267246444909, 44.749877886855124 ], [ -68.81903885674582, 44.750071075276104 ], [ -68.819019453679317, 44.75008904924659 ], [ -68.818352512418841, 44.750515599274706 ], [ -68.817747667911718, 44.751027766247546 ], [ -68.816961512637519, 44.751818716319193 ], [ -68.816555394987958, 44.752277183566839 ], [ -68.815889938364634, 44.753216854329906 ], [ -68.815837447458946, 44.753459831972947 ], [ -68.815584964703149, 44.753896036766619 ], [ -68.815396715705944, 44.754368354218684 ], [ -68.815332187917093, 44.754422264006088 ], [ -68.815178520313111, 44.754449018716748 ], [ -68.815101316878753, 44.754516415779705 ], [ -68.815215936456354, 44.754602122995372 ], [ -68.815241298054005, 44.754696686827963 ], [ -68.815173469073144, 44.754795604190726 ], [ -68.814906550467612, 44.754925704112402 ], [ -68.814809972347717, 44.755002068155086 ], [ -68.814809799837676, 44.755056081976939 ], [ -68.81489284808552, 44.755137236507871 ], [ -68.814911626066703, 44.755191278531242 ], [ -68.814878632457507, 44.755384777460812 ], [ -68.81478760306662, 44.755578179760306 ], [ -68.81462687409433, 44.755712952973354 ], [ -68.814170268839206, 44.756018290794827 ], [ -68.814008954848035, 44.756211578068374 ], [ -68.813911617591003, 44.756400467566898 ], [ -68.813571007326786, 44.756606964817927 ], [ -68.813500334472721, 44.756606849431421 ], [ -68.813185762354095, 44.756696358780601 ], [ -68.812928718714133, 44.756821970897789 ], [ -68.812587942437176, 44.757077977210585 ], [ -68.81241971803361, 44.757329766744014 ], [ -68.812291135833263, 44.757410575344544 ], [ -68.811442697434316, 44.757877297201709 ], [ -68.810433826295338, 44.758379755916145 ], [ -68.809491268302196, 44.758882318820412 ], [ -68.80928333122155, 44.75895848981316 ], [ -68.809129826632784, 44.758931227592939 ], [ -68.809014121624656, 44.758935535960475 ], [ -68.808552150654691, 44.759056293498602 ], [ -68.808256740260632, 44.759195333614137 ], [ -68.80797957597899, 44.759428926363476 ], [ -68.807895919796223, 44.759532311997724 ], [ -68.806778475046571, 44.759895022424708 ], [ -68.805474297387136, 44.760477956969851 ], [ -68.805050170373846, 44.760729299337747 ], [ -68.804773012631642, 44.760953883167225 ], [ -68.804386404119597, 44.761317814548704 ], [ -68.804308935897495, 44.761457217479737 ], [ -68.804282324809307, 44.761619213997683 ], [ -68.80424343283498, 44.761677660666756 ], [ -68.803934802735668, 44.761866181628136 ], [ -68.803805514519539, 44.762032500992412 ], [ -68.803715635084359, 44.762104365267142 ], [ -68.803484228377059, 44.762225498268307 ], [ -68.803137533739118, 44.762341930333477 ], [ -68.802925092944406, 44.762458595601025 ], [ -68.802429401687448, 44.76284483844848 ], [ -68.801717250100268, 44.763019146699342 ], [ -68.801569196716116, 44.763126917607259 ], [ -68.801401520983788, 44.763315679001423 ], [ -68.801176281025747, 44.763477324145548 ], [ -68.801060477213213, 44.763508632545069 ], [ -68.800875102633157, 44.763445291873531 ], [ -68.800836341651433, 44.763463228959218 ], [ -68.800765289196391, 44.763571133071146 ], [ -68.80084745580173, 44.763791831743134 ], [ -68.800821023071265, 44.763899813682052 ], [ -68.800678897759056, 44.764120123583993 ], [ -68.800573922853573, 44.764561050998736 ], [ -68.800470100267276, 44.764781427492267 ], [ -68.800500162676755, 44.765110063336607 ], [ -68.800485979493018, 44.765330593861918 ], [ -68.800554951722987, 44.765713314137898 ], [ -68.800528796387866, 44.765740274203026 ], [ -68.800375045479143, 44.765780516688899 ], [ -68.80033009122134, 44.765762433437047 ], [ -68.800146492954724, 44.765411022550616 ], [ -68.799929856925729, 44.765136071434114 ], [ -68.799827799595022, 44.765072877517916 ], [ -68.799712495468043, 44.765072675364451 ], [ -68.799629065338493, 44.765104036562803 ], [ -68.79921707227588, 44.765368881222983 ], [ -68.798991646523078, 44.765580038901376 ], [ -68.798733419095228, 44.765912669194051 ], [ -68.798681313640301, 44.766020605351464 ], [ -68.798533187881915, 44.766146376803945 ], [ -68.798436346834677, 44.766285741456258 ], [ -68.798198452178084, 44.766447360818226 ], [ -68.797967189193272, 44.766518970603066 ], [ -68.797812788494994, 44.766631227139456 ], [ -68.797631080959974, 44.766986496775814 ], [ -68.797636329725279, 44.767180053056769 ], [ -68.797571472451949, 44.767319476128719 ], [ -68.797378190468891, 44.767481172818492 ], [ -68.797287702261613, 44.767611546659879 ], [ -68.797159028911736, 44.767705840781794 ], [ -68.796857417097584, 44.767790828714318 ], [ -68.796760667497281, 44.767903183823151 ], [ -68.796631613690323, 44.767992977387124 ], [ -68.796400705727706, 44.768073585686523 ], [ -68.796265963678039, 44.768095851850688 ], [ -68.79597792077621, 44.768027820802672 ], [ -68.795914103158637, 44.767982693875801 ], [ -68.795894959911521, 44.767924146405335 ], [ -68.795978933460745, 44.767852277809304 ], [ -68.796325485587502, 44.767789881315835 ], [ -68.796415347864439, 44.767727023973634 ], [ -68.796699249318806, 44.767398946527464 ], [ -68.796892513625636, 44.767241750235378 ], [ -68.797459960639685, 44.766684615953203 ], [ -68.797595687457189, 44.766491307068954 ], [ -68.798078158188616, 44.766177078227464 ], [ -68.798309564145285, 44.766064962362755 ], [ -68.798412705094435, 44.765930108216118 ], [ -68.798465108324677, 44.765736651167494 ], [ -68.798805763501917, 44.765548204049558 ], [ -68.798903043983998, 44.765395339477429 ], [ -68.799199223517633, 44.765161797415793 ], [ -68.799282953629827, 44.765044915511027 ], [ -68.7995203880415, 44.76501382249841 ], [ -68.799982953307691, 44.764740064229066 ], [ -68.799959026586734, 44.764465452986435 ], [ -68.800036888490339, 44.764330555564932 ], [ -68.80003718351297, 44.764245034521466 ], [ -68.799974165434861, 44.764082882123539 ], [ -68.800014020629604, 44.76386239405948 ], [ -68.800262668896579, 44.763669279588456 ], [ -68.800464033083685, 44.763561603538761 ], [ -68.800554402909825, 44.763462736942891 ], [ -68.800619155761765, 44.763350322677091 ], [ -68.800619937518363, 44.763237794200158 ], [ -68.800549926583628, 44.763156652175056 ], [ -68.800402795872827, 44.763111382190239 ], [ -68.80016006981441, 44.762962421038822 ], [ -68.800044738834771, 44.762971220256127 ], [ -68.799350728241407, 44.763262577145774 ], [ -68.799081455876347, 44.763253102664187 ], [ -68.798965948906286, 44.763311417730023 ], [ -68.798579808074166, 44.763639318268901 ], [ -68.798579526550299, 44.763720337545529 ], [ -68.798739079918136, 44.763828646865711 ], [ -68.798783399574802, 44.763914246525509 ], [ -68.798705441529989, 44.764076151195837 ], [ -68.798589256319772, 44.764215481932069 ], [ -68.798473860008897, 44.764242286627791 ], [ -68.798161031556248, 44.764039181009181 ], [ -68.798084093487546, 44.764021040338029 ], [ -68.797975075502976, 44.764029848624659 ], [ -68.797132586603084, 44.764541487077373 ], [ -68.797125683851192, 44.764595488826238 ], [ -68.797176923693996, 44.764622588096351 ], [ -68.797369326403, 44.764595921366826 ], [ -68.797433235821387, 44.764614039409736 ], [ -68.797407379855443, 44.764668007556409 ], [ -68.795713002133667, 44.765097098866185 ], [ -68.794018025198156, 44.765575678641348 ], [ -68.793671423293773, 44.765656072734373 ], [ -68.793556130560248, 44.765651362591534 ], [ -68.793324977398839, 44.765691455645758 ], [ -68.792246040100608, 44.76604959136565 ], [ -68.791410567030397, 44.766453169690756 ], [ -68.791255930419538, 44.766628432000118 ], [ -68.791114284692156, 44.766704693463709 ], [ -68.790767733506271, 44.76676707415767 ], [ -68.790356262052114, 44.766982373024618 ], [ -68.790118345566398, 44.767143980516195 ], [ -68.789982699426119, 44.767305773109577 ], [ -68.789853998580242, 44.767404560748133 ], [ -68.789120448438069, 44.767898335529331 ], [ -68.788991117121938, 44.768060136072577 ], [ -68.788848348636492, 44.768334443875347 ], [ -68.788461558084677, 44.768707321607806 ], [ -68.788402815733349, 44.76889626042766 ], [ -68.788354373799777, 44.76950832895087 ], [ -68.78827608307931, 44.769751245411932 ], [ -68.788235278029248, 44.770111260309264 ], [ -68.788246318352051, 44.770439861285688 ], [ -68.788167417247365, 44.770741294753797 ], [ -68.78816641372093, 44.770907836535351 ], [ -68.788254858794701, 44.771128555626795 ], [ -68.78824676274121, 44.771398610349628 ], [ -68.788289813395693, 44.77172277131536 ], [ -68.788260948803753, 44.772271858269697 ], [ -68.788194964587092, 44.77260482059674 ], [ -68.788025206353439, 44.773126637801724 ], [ -68.788124237330067, 44.773693964027075 ], [ -68.787979142499012, 44.77438236784694 ], [ -68.78792749945579, 44.774463294092214 ], [ -68.787882430551605, 44.774476715756201 ], [ -68.787805382985596, 44.774485573692772 ], [ -68.787536537774642, 44.774453566375172 ], [ -68.787305444497719, 44.77446663908038 ], [ -68.787202560019438, 44.774520461341652 ], [ -68.787163655137377, 44.774574402760422 ], [ -68.787118202112424, 44.774691346938027 ], [ -68.786723320354824, 44.775316270704401 ], [ -68.786611815623246, 44.775779678640745 ], [ -68.786642551282029, 44.776013796309449 ], [ -68.786762480088697, 44.776266082651738 ], [ -68.786704334645364, 44.776396508288428 ], [ -68.786556126709826, 44.77653126488282 ], [ -68.786221126566403, 44.776764698515038 ], [ -68.786079209700645, 44.776903968043023 ], [ -68.785448414906782, 44.777339395160659 ], [ -68.785095393697816, 44.777424252782765 ], [ -68.784864662600981, 44.77744182300836 ], [ -68.784243402638481, 44.777328121465708 ], [ -68.784128040693233, 44.777336907038475 ], [ -68.78406338760449, 44.777408803331902 ], [ -68.783997897045936, 44.77760222862203 ], [ -68.783830199053682, 44.777768452258236 ], [ -68.78352126902837, 44.777997425477409 ], [ -68.78347603791984, 44.77805135361529 ], [ -68.783482055590056, 44.778132387102097 ], [ -68.783365960524904, 44.778231191137962 ], [ -68.782477616172542, 44.778873167362299 ], [ -68.782421858666439, 44.778891063264759 ], [ -68.782387461944381, 44.778899998786414 ], [ -68.782374223138632, 44.778953987433887 ], [ -68.781936502851593, 44.779290738571667 ], [ -68.781743513855389, 44.779353384846132 ], [ -68.781589832619517, 44.779362094614548 ], [ -68.78072663171146, 44.780031110848299 ], [ -68.780378946469497, 44.780264500332088 ], [ -68.780095694320934, 44.780484512505261 ], [ -68.779798478511225, 44.780844029701711 ], [ -68.779792263928755, 44.780817012046747 ], [ -68.779618090316944, 44.781019226153198 ], [ -68.779617575628421, 44.781050732613679 ], [ -68.779431122505287, 44.78115840037227 ], [ -68.779258322576538, 44.781104052657518 ], [ -68.779104739318299, 44.781085751086408 ], [ -68.778924039333333, 44.781238439431831 ], [ -68.778916911433413, 44.781346453336312 ], [ -68.779102044464793, 44.78148184494011 ], [ -68.779076181012471, 44.781531309490397 ], [ -68.778896166447424, 44.781607478859982 ], [ -68.778730146599443, 44.781535138860065 ], [ -68.778653019463292, 44.781561997583587 ], [ -68.778105549805204, 44.78197503885233 ], [ -68.777660343036374, 44.782397280161952 ], [ -68.776988914708951, 44.783197172519557 ], [ -68.776651642021108, 44.783795164831979 ], [ -68.776287397953851, 44.784622661366129 ], [ -68.775948621469269, 44.785504217884125 ], [ -68.775767118742579, 44.785859451951261 ], [ -68.775521250641532, 44.786205555656217 ], [ -68.775501581237236, 44.78628653895494 ], [ -68.775358822376575, 44.786529319097717 ], [ -68.774909007255545, 44.787716731924476 ], [ -68.774863781542066, 44.787766154792678 ], [ -68.774765804751596, 44.788072039741813 ], [ -68.774764657189408, 44.788265583739786 ], [ -68.774777195297943, 44.788292616614306 ], [ -68.774943673345149, 44.788351460359905 ], [ -68.775045726873557, 44.788419178024235 ], [ -68.775249188356142, 44.788721154927146 ], [ -68.775261394949212, 44.78883370326318 ], [ -68.775168716150375, 44.78930164025401 ], [ -68.775057738520047, 44.789598496225594 ], [ -68.774972175741937, 44.789962921247742 ], [ -68.7747115633221, 44.790642077529839 ], [ -68.774516354383763, 44.791060297046165 ], [ -68.774224967763431, 44.791523336311009 ], [ -68.774126984691591, 44.791829220497561 ], [ -68.773997245189392, 44.792072024449809 ], [ -68.773931626924536, 44.792283447797317 ], [ -68.773841543915609, 44.792386795164482 ], [ -68.773815899633291, 44.792377740832841 ], [ -68.773751290773191, 44.792431626603339 ], [ -68.773609203385192, 44.792597888039552 ], [ -68.773293345136707, 44.792849320589205 ], [ -68.773035984057003, 44.792992848574428 ], [ -68.772346968397812, 44.793495602912436 ], [ -68.772140248380126, 44.793720249141742 ], [ -68.771843895196298, 44.793931208131511 ], [ -68.771572992757342, 44.794200735263324 ], [ -68.771502181885197, 44.794223099958593 ], [ -68.771348601896648, 44.794195784461088 ], [ -68.771277833550329, 44.794209148069704 ], [ -68.77109055040313, 44.79441132243754 ], [ -68.770988152746739, 44.794429121726338 ], [ -68.770413503062727, 44.795076130135541 ], [ -68.770387715337435, 44.795103083896826 ], [ -68.77037455083007, 44.795134567315074 ], [ -68.770297187439454, 44.79521543106415 ], [ -68.770284044252918, 44.795242410232575 ], [ -68.770226164020215, 44.795291808255186 ], [ -68.770206698447296, 44.79531877469406 ], [ -68.770180892649577, 44.795350230141935 ], [ -68.770109847511662, 44.795431108985895 ], [ -68.770097099115134, 44.795458088926473 ], [ -68.770058164182572, 44.795512024258286 ], [ -68.770019375727628, 44.795529950727399 ], [ -68.769961566521332, 44.795561341673825 ], [ -68.769903685704236, 44.795610737018293 ], [ -68.769896843188434, 44.7956422331157 ], [ -68.769851589104263, 44.795696153132553 ], [ -68.769838840518702, 44.795723135553409 ], [ -68.769799905166678, 44.795777070793804 ], [ -68.769774009454267, 44.795831032307497 ], [ -68.769780206570829, 44.795862552171499 ], [ -68.769805780190282, 44.795889609321534 ], [ -68.76981201311402, 44.795912128225666 ], [ -68.769830851541471, 44.795943673564928 ], [ -68.769843762512096, 44.795975206955639 ], [ -68.769830636826114, 44.795997686843982 ], [ -68.769811170820631, 44.796024655721958 ], [ -68.769804723145299, 44.796056150096604 ], [ -68.769772235238989, 44.796078590949584 ], [ -68.769753167646115, 44.79610555810968 ], [ -68.769726962792689, 44.796137012646099 ], [ -68.769714210525009, 44.796163995043514 ], [ -68.76968840408162, 44.796195450374135 ], [ -68.76967526038743, 44.796222429466823 ], [ -68.769643167269436, 44.796244871078599 ], [ -68.769623683191455, 44.796276339146338 ], [ -68.769597930379931, 44.796294291764582 ], [ -68.769572141720886, 44.796321247844759 ], [ -68.76954004851811, 44.796343686916266 ], [ -68.769520564350685, 44.796375154965659 ], [ -68.769481771614565, 44.796393083751546 ], [ -68.769456000792232, 44.796415535560314 ], [ -68.769430194096145, 44.796446990830653 ], [ -68.769391383364024, 44.796469418808485 ], [ -68.769378634392467, 44.796496401173499 ], [ -68.769307715794611, 44.796545769879316 ], [ -68.769249797561542, 44.796604165837131 ], [ -68.769185215612794, 44.796649048009712 ], [ -68.769159426613328, 44.796676001483561 ], [ -68.76912106792723, 44.796684927483781 ], [ -68.769075955909727, 44.79670284084488 ], [ -68.76904388025018, 44.796720780551908 ], [ -68.769017678258493, 44.796752234927958 ], [ -68.76897928005971, 44.796770161829166 ], [ -68.768966135903881, 44.796797140836567 ], [ -68.768942375290678, 44.796810598076512 ], [ -68.768914593459243, 44.796842049232126 ], [ -68.768882409874209, 44.796886994271205 ], [ -68.76886915430552, 44.79694098114814 ], [ -68.768875368995936, 44.79696800434261 ], [ -68.768875135431202, 44.797026514319981 ], [ -68.768868313682859, 44.797053508620458 ], [ -68.768868098061247, 44.797107521885316 ], [ -68.768874309262557, 44.797134540050493 ], [ -68.768886846522307, 44.797161573527866 ], [ -68.768890688108087, 44.797188586896603 ], [ -68.768911464911696, 44.797328163701749 ], [ -68.768949360557102, 44.797436267771488 ], [ -68.76894914501824, 44.797490278522297 ], [ -68.768961678863079, 44.797517311982851 ], [ -68.768967875713969, 44.797548831881066 ], [ -68.768987127102903, 44.797575878927496 ], [ -68.768986516646862, 44.797629891388972 ], [ -68.768999449016917, 44.797656923140103 ], [ -68.76900566385369, 44.797683943813624 ], [ -68.769024520373065, 44.797710987543738 ], [ -68.769024304892241, 44.797765000802947 ], [ -68.769022563508557, 44.797805508179692 ], [ -68.76898563662823, 44.797850443647043 ], [ -68.768985474987176, 44.797890954218069 ], [ -68.769094237907936, 44.797963192427794 ], [ -68.769175685554629, 44.798048875613489 ], [ -68.769211280723951, 44.798138972880139 ], [ -68.769237447875383, 44.798314569621951 ], [ -68.769281739525169, 44.798503705762982 ], [ -68.769326171406519, 44.798656833039729 ], [ -68.769298640353639, 44.798724293908968 ], [ -68.769198442716075, 44.798782606915083 ], [ -68.769116078520071, 44.798827450582692 ], [ -68.76910875049208, 44.798881449452246 ], [ -68.769137308759781, 44.798953525430157 ], [ -68.769192838649346, 44.799097674155291 ], [ -68.769819767640939, 44.800719342139466 ], [ -68.770070039301999, 44.80137701126835 ], [ -68.770111203199491, 44.8014581133706 ], [ -68.770267953239298, 44.801787012504697 ], [ -68.770315982122639, 44.801832119232337 ], [ -68.770385275118883, 44.801895273245414 ], [ -68.770593781854899, 44.802026225891041 ], [ -68.771019391335628, 44.802315150326216 ], [ -68.771273109486515, 44.802504707776215 ], [ -68.771359505504279, 44.802540889873342 ], [ -68.771372902628457, 44.802549917713101 ], [ -68.771500009573458, 44.802581679429707 ], [ -68.771662023461062, 44.802582003466902 ], [ -68.771770954714057, 44.802613728540756 ], [ -68.771861279695344, 44.802654419874223 ], [ -68.771897923644858, 44.80268150114798 ], [ -68.771933756423763, 44.802713080044256 ], [ -68.77196994876131, 44.802753660666269 ], [ -68.772106201156731, 44.802771937182854 ], [ -68.772208884223517, 44.802785647331476 ], [ -68.772230122628088, 44.802911719022234 ], [ -68.772242078280115, 44.803087286584926 ], [ -68.771288229436266, 44.802878329317771 ], [ -68.771057140420439, 44.802760837987151 ], [ -68.770079281805167, 44.802034189764228 ], [ -68.76995745309145, 44.801867406723368 ], [ -68.769000257498575, 44.799533893489787 ], [ -68.768823119932037, 44.799168944609292 ], [ -68.768757674110617, 44.799033779164894 ], [ -68.7687153434279, 44.798948172409524 ], [ -68.768657853849632, 44.798898541543579 ], [ -68.768526463537583, 44.798754238824365 ], [ -68.768517951095717, 44.798709211421723 ], [ -68.768499094490053, 44.798682167612554 ], [ -68.768492879784574, 44.798655146916445 ], [ -68.768467305563561, 44.798628089484303 ], [ -68.768390887986484, 44.79856941656994 ], [ -68.768377955688706, 44.79854238726513 ], [ -68.768339703713337, 44.798524305117262 ], [ -68.768314147646578, 44.798492743407955 ], [ -68.768288950510836, 44.798470188468443 ], [ -68.768250336177516, 44.798443102018489 ], [ -68.768194434327256, 44.798393476647455 ], [ -68.768168856834777, 44.798366419140613 ], [ -68.767727545773823, 44.797964922797505 ], [ -68.767394180845585, 44.797937236211958 ], [ -68.767201584846958, 44.797981856451962 ], [ -68.767085994641079, 44.79803563195555 ], [ -68.766248483942562, 44.79859206230951 ], [ -68.765515041945548, 44.799009160445678 ], [ -68.764368596206822, 44.799740486024405 ], [ -68.763953638602359, 44.8000502058702 ], [ -68.763228614943614, 44.800525824559784 ], [ -68.762617271403073, 44.800835132192056 ], [ -68.761575496855826, 44.801269570626516 ], [ -68.760777378103882, 44.801736017498278 ], [ -68.760430212266257, 44.80188832972501 ], [ -68.758887395562965, 44.802411711664043 ], [ -68.757730069170591, 44.802872878964486 ], [ -68.756667844498708, 44.803455764719743 ], [ -68.756158862458989, 44.803837276361648 ], [ -68.755780081286318, 44.804079527103397 ], [ -68.755630785446996, 44.804155725181261 ], [ -68.755591796684243, 44.804218658910514 ], [ -68.755476221288959, 44.804263421618735 ], [ -68.755154144801267, 44.804456279793996 ], [ -68.754188243643512, 44.805048352315531 ], [ -68.753814520248341, 44.805304112660778 ], [ -68.753080431938088, 44.805739136099184 ], [ -68.752096003081562, 44.806218621741891 ], [ -68.751272050538347, 44.806684945695594 ], [ -68.750469250966219, 44.807191820716923 ], [ -68.749852402834165, 44.807645083893668 ], [ -68.749479895890076, 44.807977349981059 ], [ -68.749457686732953, 44.807995305793696 ], [ -68.749214998760323, 44.808188321224733 ], [ -68.749112953874999, 44.808300626048712 ], [ -68.74907458089983, 44.808309542755033 ], [ -68.747836365653541, 44.809333071206531 ], [ -68.747785428777945, 44.809413977848507 ], [ -68.74773401562409, 44.809422867814455 ], [ -68.747095860694202, 44.809948086962436 ], [ -68.746533174831058, 44.810293424767622 ], [ -68.745336355261941, 44.810781383697922 ], [ -68.744190106825002, 44.811089398106546 ], [ -68.743882365721788, 44.811151722955479 ], [ -68.743010320110557, 44.811203775036518 ], [ -68.742945984972451, 44.811185628238015 ], [ -68.742920038647085, 44.811154062548944 ], [ -68.742787959992242, 44.810906201090496 ], [ -68.742444531209983, 44.810995452236732 ], [ -68.742176430849923, 44.811206399817308 ], [ -68.741984210105159, 44.811241974755013 ], [ -68.741907341138671, 44.811286811115046 ], [ -68.741435651454736, 44.811776365491383 ], [ -68.741410391492352, 44.811857329895801 ], [ -68.741371938042732, 44.811884248346111 ], [ -68.740571565337319, 44.812166001079632 ], [ -68.739605369610615, 44.812586909633936 ], [ -68.738889049186639, 44.812981368498768 ], [ -68.738690449470297, 44.813201475235715 ], [ -68.73855796805023, 44.813394714926012 ], [ -68.738136860318477, 44.813789849462445 ], [ -68.737767117891138, 44.814185097861404 ], [ -68.737409555955509, 44.814508357507215 ], [ -68.737064274809867, 44.814737119980521 ], [ -68.736386416467866, 44.815095642522891 ], [ -68.736091934043458, 44.815189489223194 ], [ -68.734002917560744, 44.815670762018222 ], [ -68.733695765622301, 44.815769069753934 ], [ -68.732696235865532, 44.816023299538877 ], [ -68.732119884185607, 44.816215494413811 ], [ -68.731192662437095, 44.816726442913762 ], [ -68.730707362773529, 44.817139403217361 ], [ -68.730548134467654, 44.817382087185671 ], [ -68.730369773276621, 44.817575213999788 ], [ -68.72964152280575, 44.818127127606381 ], [ -68.729193425850198, 44.81835562269449 ], [ -68.728617290155327, 44.818579310080928 ], [ -68.728194287122264, 44.818681829370227 ], [ -68.726939925317708, 44.81923247230192 ], [ -68.725902862907191, 44.819630588691979 ], [ -68.725518409590123, 44.819769199554514 ], [ -68.725281416129334, 44.819818141452259 ], [ -68.72462827953693, 44.820019117161692 ], [ -68.723898182669117, 44.820278418327604 ], [ -68.723712375795245, 44.820367991438253 ], [ -68.72348201246281, 44.820434950128892 ], [ -68.722366874947667, 44.8207023120668 ], [ -68.720334853666742, 44.821021432493708 ], [ -68.719610627300952, 44.821204203378066 ], [ -68.718958178599593, 44.821337632089701 ], [ -68.71893127924686, 44.821342065130928 ], [ -68.717431968806906, 44.821756970372952 ], [ -68.715847032500847, 44.821735033429746 ], [ -68.715391903426976, 44.82176990910105 ], [ -68.714545667137045, 44.821920833981743 ], [ -68.713846130315105, 44.821901078874482 ], [ -68.713499739695067, 44.821927218419027 ], [ -68.713307440081977, 44.821967244061945 ], [ -68.712878273023989, 44.822105700059829 ], [ -68.71266080176953, 44.822199677298528 ], [ -68.712506859052681, 44.822235298851595 ], [ -68.712352684763331, 44.822239412389322 ], [ -68.711967542703078, 44.822188927467849 ], [ -68.711614236033299, 44.822174533125647 ], [ -68.711029311224635, 44.822078532931847 ], [ -68.710424050684864, 44.822018484644417 ], [ -68.709399829300352, 44.821952868617103 ], [ -68.708808507360089, 44.821793823803432 ], [ -68.708487710868383, 44.821761495675894 ], [ -68.708294741556571, 44.821697988499935 ], [ -68.707896987792381, 44.821719478097904 ], [ -68.707396958623349, 44.821799217023148 ], [ -68.707333162726698, 44.821830560782189 ], [ -68.706935852353368, 44.82191956437056 ], [ -68.706580982279675, 44.821981670145512 ], [ -68.706061346791003, 44.822029845619106 ], [ -68.705211934438069, 44.822023154519435 ], [ -68.705042129031881, 44.821991208864766 ], [ -68.704723217219623, 44.821976882029915 ], [ -68.703545289554327, 44.821978329678359 ], [ -68.702802442776289, 44.822079924155659 ], [ -68.701889463008442, 44.822059540167125 ], [ -68.701020172555914, 44.821998755049208 ], [ -68.700481022895715, 44.821997342330654 ], [ -68.700327143265426, 44.822019442383642 ], [ -68.699801597166598, 44.822139591813581 ], [ -68.699417067548424, 44.822201597358585 ], [ -68.699186273933293, 44.822268505886186 ], [ -68.69902002464643, 44.822389597525628 ], [ -68.698764046133689, 44.822510452279921 ], [ -68.698342092551243, 44.82277490406058 ], [ -68.698214504640234, 44.822909599364429 ], [ -68.698138278439359, 44.823048932039342 ], [ -68.697732110659956, 44.823997591079404 ], [ -68.6977100011201, 44.824672696798231 ], [ -68.696123278357163, 44.824600969714965 ], [ -68.694359126627134, 44.824519744896591 ], [ -68.694372198638902, 44.824438758465995 ], [ -68.69449946028422, 44.824218548475557 ], [ -68.694453308573841, 44.823975363263443 ], [ -68.694471328030374, 44.823781863922889 ], [ -68.69473074163686, 44.823012869114393 ], [ -68.694889425037132, 44.822680211449196 ], [ -68.695068066101427, 44.822460132988944 ], [ -68.695451692814189, 44.822195593602991 ] ], [ [ -68.768674936508816, 44.798479971962571 ], [ -68.768706133452909, 44.79848453691617 ], [ -68.768746848623479, 44.79848011759816 ], [ -68.768775349323178, 44.798466672510592 ], [ -68.768806690061055, 44.798435228602386 ], [ -68.768819439729484, 44.798408246311837 ], [ -68.768819547562941, 44.798381240941104 ], [ -68.768816535293894, 44.798345228228222 ], [ -68.768804497082826, 44.798291187658819 ], [ -68.768788875471571, 44.798246145880931 ], [ -68.768764611976039, 44.798187581297753 ], [ -68.76872429258502, 44.798092975067973 ], [ -68.768699598304963, 44.798043413134444 ], [ -68.768670308616635, 44.797957832758549 ], [ -68.768664093897556, 44.797930814579054 ], [ -68.768626090543663, 44.797849715797511 ], [ -68.768600480640046, 44.797831659346073 ], [ -68.768562229063548, 44.797813577271306 ], [ -68.76852395598651, 44.797799996908992 ], [ -68.768485219516592, 44.79780442015926 ], [ -68.768446820454542, 44.797822346876359 ], [ -68.768433672283678, 44.797849325809331 ], [ -68.768407885914371, 44.797876281623843 ], [ -68.768420437595367, 44.797898813409297 ], [ -68.768427047140648, 44.797925832402804 ], [ -68.76844588546335, 44.797957377959143 ], [ -68.768445777447155, 44.7979843858419 ], [ -68.768458332702494, 44.798006915119281 ], [ -68.768499437961978, 44.798101523037353 ], [ -68.768533129375186, 44.798173609581156 ], [ -68.768551935379605, 44.798214156079517 ], [ -68.768551396483787, 44.798250164114272 ], [ -68.768557503218659, 44.798304190179799 ], [ -68.768572747693696, 44.798344734477709 ], [ -68.768613210933637, 44.798403329411819 ], [ -68.768637941271407, 44.798443887897193 ], [ -68.768656402907723, 44.798470933395251 ], [ -68.768674936508816, 44.798479971962571 ] ], [ [ -68.833682325085277, 44.736805895583146 ], [ -68.833644093338975, 44.736787835142458 ], [ -68.833592757673301, 44.736796763989538 ], [ -68.833541001639674, 44.736814693324618 ], [ -68.833508979067545, 44.736832648894392 ], [ -68.833476536098644, 44.736859609987434 ], [ -68.833437806479452, 44.736877558268304 ], [ -68.833399047468049, 44.736904507616742 ], [ -68.833360275483969, 44.736935961260947 ], [ -68.833321545777892, 44.73695390950202 ], [ -68.833282799597015, 44.736976357012217 ], [ -68.833192197054203, 44.737052745565109 ], [ -68.833153463695666, 44.737070693743483 ], [ -68.833121046324621, 44.737088651137682 ], [ -68.833089001092844, 44.737115610189811 ], [ -68.833056570723656, 44.737138069363574 ], [ -68.833036742943165, 44.737169548076047 ], [ -68.833036665222266, 44.737196556353538 ], [ -68.833061873988711, 44.737214597961234 ], [ -68.833100516837931, 44.737228157379299 ], [ -68.833151852960583, 44.73721922875486 ], [ -68.833203253761965, 44.737187796138457 ], [ -68.833241987183442, 44.737169847943676 ], [ -68.833268114410401, 44.737142880209326 ], [ -68.833313147341542, 44.737129443003766 ], [ -68.833358678212761, 44.737079994481341 ], [ -68.833416828084452, 44.737035068674949 ], [ -68.833448850804132, 44.737017110621089 ], [ -68.833474977873294, 44.736990140327713 ], [ -68.833513707549074, 44.736972194545437 ], [ -68.833539414207095, 44.736954227258579 ], [ -68.833623231704237, 44.73690483442644 ], [ -68.833681762820049, 44.736864410856398 ], [ -68.833694866744437, 44.736837419035133 ], [ -68.833682325085277, 44.736805895583146 ] ], [ [ -68.740186150626997, 44.808753283859232 ], [ -68.740096483544065, 44.808744076314731 ], [ -68.7400580517685, 44.808766495137334 ], [ -68.740038982831621, 44.8087889580232 ], [ -68.740026213949179, 44.808815931985777 ], [ -68.740019752135694, 44.808847427086071 ], [ -68.740020025830447, 44.808874435758746 ], [ -68.740032943620164, 44.808901470703056 ], [ -68.740046219525851, 44.808937509980915 ], [ -68.740084824069925, 44.80896460589468 ], [ -68.740123472605703, 44.808982698375459 ], [ -68.7401875306211, 44.808973840602135 ], [ -68.740257743135572, 44.808915487473321 ], [ -68.740276304793937, 44.808830006289099 ], [ -68.740250739317048, 44.808802942606846 ], [ -68.740224839466336, 44.808762372877801 ], [ -68.740186150626997, 44.808753283859232 ] ] ] } }"; // // try { @@ -275,16 +275,16 @@ public void testCorruptedTreapSearch() { // } catch (Exception e) { // // } - } - - @Test - public void testCircle() { - String wkt = "MULTIPOLYGON (((1 1.003617493421433, 1 1.003617493421433, 0.9997649519467132 1.003609747854938, 0.999530910495817 1.003586544337746, 0.9992988779321902 1.003547982273469, 0.9990698479248393 1.003494226857979, 0.9988448012666893 1.003425508366702, 0.9986247016712343 1.003342121161815, 0.9984104916443466 1.003244422424226, 0.9982030884490086 1.003132830616434, 0.9980033801801327 1.00300782368356, 0.9978122219659593 1.002869937000829, 0.9976304323118294 1.0027197610768, 0.9974587896014011 1.002557939022455, 0.9972980287696582 1.002385163797099, 0.9971488381613458 1.002202175242747, 0.9970118565877482 1.002009756919397, 0.9968876705940221 1.001808732754287, 0.9967768119485581 1.001599963518935, 0.9966797553651014 1.00138434314843, 0.9965969164675547 1.001162794918189, 0.9965286500065195 1.000936267494034, 0.996475248335696 1.000705730872183, 0.9964369401552167 1.000472172226337, 0.9964138895278762 1.000236591679669, 0.9964061951730092 0.9999999980200158, 0.9964138900413748 0.9997634043769587, 0.9964369411734271 0.9995278238797968, 0.9964752498411954 0.9992942653155188, 0.996528651973547 0.9990637288059012, 0.9965969188624519 0.9988372015227254, 0.9966797581468895 0.9986156534597946, 0.9967768150696379 0.9984000332800704, 0.9968876740009895 0.998191264255702, 0.9970118602223078 0.9979902403181697, 0.9971488419613082 0.9977978222350962, 0.9972980326700053 0.9976148339296087, 0.9974587935353963 0.997442058957447, 0.9976304362121615 0.9972802371562934, 0.9978122257658933 0.9971300614811209, 0.9980033838146521 0.996992175038654, 0.9982030918559268 0.9968671683333408, 0.9984104947653715 0.996755576736514, 0.9986247044529655 0.9966578781896839, 0.9988448036615318 0.9965744911520876, 0.9990698498918178 0.9965057728017697, 0.9992988794376493 0.9964520174984963, 0.9995309115139988 0.996413455515775, 0.999764952460197 0.9963902520480808, 1 0.9963825064981803, 1.000235047539803 0.9963902520480808, 1.000469088486001 0.9964134555157748, 1.000701120562351 0.9964520174984965, 1.000930150108182 0.9965057728017697, 1.001155196338468 0.9965744911520876, 1.001375295547035 0.9966578781896839, 1.001589505234629 0.996755576736514, 1.001796908144073 0.9968671683333408, 1.001996616185348 0.996992175038654, 1.002187774234107 0.9971300614811212, 1.002369563787838 0.9972802371562934, 1.002541206464604 0.997442058957447, 1.002701967329995 0.9976148339296087, 1.002851158038692 0.9977978222350962, 1.002988139777692 0.99799024031817, 1.00311232599901 0.998191264255702, 1.003223184930362 0.9984000332800704, 1.00332024185311 0.9986156534597949, 1.003403081137548 0.9988372015227254, 1.003471348026453 0.9990637288059014, 1.003524750158804 0.9992942653155188, 1.003563058826573 0.9995278238797968, 1.003586109958625 0.9997634043769587, 1.003593804826991 0.9999999980200158, 1.003586110472124 1.00023659167967, 1.003563059844783 1.000472172226337, 1.003524751664304 1.000705730872183, 1.003471349993481 1.000936267494034, 1.003403083532445 1.001162794918189, 1.003320244634899 1.00138434314843, 1.003223188051442 1.001599963518935, 1.003112329405978 1.001808732754287, 1.002988143412252 1.002009756919397, 1.002851161838654 1.002202175242747, 1.002701971230342 1.002385163797099, 1.002541210398599 1.002557939022455, 1.002369567688171 1.0027197610768, 1.002187778034041 1.002869937000829, 1.001996619819867 1.00300782368356, 1.001796911550991 1.003132830616434, 1.001589508355653 1.003244422424226, 1.001375298328766 1.003342121161815, 1.001155198733311 1.003425508366702, 1.000930152075161 1.003494226857979, 1.00070112206781 1.003547982273469, 1.000469089504183 1.003586544337747, 1.000235048053287 1.003609747854938, 1 1.003617493421433)))"; - Geometry poly = GeometryEngine.geometryFromWkt(wkt, 0, Geometry.Type.Unknown); - OperatorFactoryLocal engine = OperatorFactoryLocal.getInstance(); - OperatorGeneralizeByArea op = (OperatorGeneralizeByArea) engine.getOperator(Operator.Type.GeneralizeByArea); - Geometry geom = op.execute(poly, 95, true, GeneralizeType.Neither, SpatialReference.create(4326),null); - int res = ((MultiVertexGeometry)geom).getPointCount(); - int original = ((MultiVertexGeometry)poly).getPointCount(); - } + } + + @Test + public void testCircle() { + String wkt = "MULTIPOLYGON (((1 1.003617493421433, 1 1.003617493421433, 0.9997649519467132 1.003609747854938, 0.999530910495817 1.003586544337746, 0.9992988779321902 1.003547982273469, 0.9990698479248393 1.003494226857979, 0.9988448012666893 1.003425508366702, 0.9986247016712343 1.003342121161815, 0.9984104916443466 1.003244422424226, 0.9982030884490086 1.003132830616434, 0.9980033801801327 1.00300782368356, 0.9978122219659593 1.002869937000829, 0.9976304323118294 1.0027197610768, 0.9974587896014011 1.002557939022455, 0.9972980287696582 1.002385163797099, 0.9971488381613458 1.002202175242747, 0.9970118565877482 1.002009756919397, 0.9968876705940221 1.001808732754287, 0.9967768119485581 1.001599963518935, 0.9966797553651014 1.00138434314843, 0.9965969164675547 1.001162794918189, 0.9965286500065195 1.000936267494034, 0.996475248335696 1.000705730872183, 0.9964369401552167 1.000472172226337, 0.9964138895278762 1.000236591679669, 0.9964061951730092 0.9999999980200158, 0.9964138900413748 0.9997634043769587, 0.9964369411734271 0.9995278238797968, 0.9964752498411954 0.9992942653155188, 0.996528651973547 0.9990637288059012, 0.9965969188624519 0.9988372015227254, 0.9966797581468895 0.9986156534597946, 0.9967768150696379 0.9984000332800704, 0.9968876740009895 0.998191264255702, 0.9970118602223078 0.9979902403181697, 0.9971488419613082 0.9977978222350962, 0.9972980326700053 0.9976148339296087, 0.9974587935353963 0.997442058957447, 0.9976304362121615 0.9972802371562934, 0.9978122257658933 0.9971300614811209, 0.9980033838146521 0.996992175038654, 0.9982030918559268 0.9968671683333408, 0.9984104947653715 0.996755576736514, 0.9986247044529655 0.9966578781896839, 0.9988448036615318 0.9965744911520876, 0.9990698498918178 0.9965057728017697, 0.9992988794376493 0.9964520174984963, 0.9995309115139988 0.996413455515775, 0.999764952460197 0.9963902520480808, 1 0.9963825064981803, 1.000235047539803 0.9963902520480808, 1.000469088486001 0.9964134555157748, 1.000701120562351 0.9964520174984965, 1.000930150108182 0.9965057728017697, 1.001155196338468 0.9965744911520876, 1.001375295547035 0.9966578781896839, 1.001589505234629 0.996755576736514, 1.001796908144073 0.9968671683333408, 1.001996616185348 0.996992175038654, 1.002187774234107 0.9971300614811212, 1.002369563787838 0.9972802371562934, 1.002541206464604 0.997442058957447, 1.002701967329995 0.9976148339296087, 1.002851158038692 0.9977978222350962, 1.002988139777692 0.99799024031817, 1.00311232599901 0.998191264255702, 1.003223184930362 0.9984000332800704, 1.00332024185311 0.9986156534597949, 1.003403081137548 0.9988372015227254, 1.003471348026453 0.9990637288059014, 1.003524750158804 0.9992942653155188, 1.003563058826573 0.9995278238797968, 1.003586109958625 0.9997634043769587, 1.003593804826991 0.9999999980200158, 1.003586110472124 1.00023659167967, 1.003563059844783 1.000472172226337, 1.003524751664304 1.000705730872183, 1.003471349993481 1.000936267494034, 1.003403083532445 1.001162794918189, 1.003320244634899 1.00138434314843, 1.003223188051442 1.001599963518935, 1.003112329405978 1.001808732754287, 1.002988143412252 1.002009756919397, 1.002851161838654 1.002202175242747, 1.002701971230342 1.002385163797099, 1.002541210398599 1.002557939022455, 1.002369567688171 1.0027197610768, 1.002187778034041 1.002869937000829, 1.001996619819867 1.00300782368356, 1.001796911550991 1.003132830616434, 1.001589508355653 1.003244422424226, 1.001375298328766 1.003342121161815, 1.001155198733311 1.003425508366702, 1.000930152075161 1.003494226857979, 1.00070112206781 1.003547982273469, 1.000469089504183 1.003586544337747, 1.000235048053287 1.003609747854938, 1 1.003617493421433)))"; + Geometry poly = GeometryEngine.geometryFromWkt(wkt, 0, Geometry.Type.Unknown); + OperatorFactoryLocal engine = OperatorFactoryLocal.getInstance(); + OperatorGeneralizeByArea op = (OperatorGeneralizeByArea) engine.getOperator(Operator.Type.GeneralizeByArea); + Geometry geom = op.execute(poly, 95, true, GeneralizeType.Neither, SpatialReference.create(4326), null); + int res = ((MultiVertexGeometry) geom).getPointCount(); + int original = ((MultiVertexGeometry) poly).getPointCount(); + } } diff --git a/src/test/java/com/esri/core/geometry/TestGeodetic.java b/src/test/java/com/esri/core/geometry/TestGeodetic.java index c4b69078..f00b48ee 100644 --- a/src/test/java/com/esri/core/geometry/TestGeodetic.java +++ b/src/test/java/com/esri/core/geometry/TestGeodetic.java @@ -4,468 +4,468 @@ import org.junit.Test; public class TestGeodetic extends TestCase { - @Override - protected void setUp() throws Exception { - super.setUp(); - } - - @Override - protected void tearDown() throws Exception { - super.tearDown(); - } - - @Test - public void testCoastalPolyline() throws Exception { - SpatialReference sr = SpatialReference.create(4326); - String geoJSON = "{ \"type\": \"LineString\", \"coordinates\": [ [ -90.418136, 46.566094 ], [ -90.348407, 46.600635 ], [ -90.327626, 46.607744 ], [ -90.306609, 46.602741 ], [ -90.265294, 46.618516 ], [ -90.237609, 46.624485 ], [ -90.164026, 46.645515 ], [ -90.100695, 46.655132 ], [ -90.045420, 46.668272 ], [ -90.028392, 46.674390 ], [ -89.996034, 46.693225 ], [ -89.985817, 46.703190 ], [ -89.973803, 46.710322 ], [ -89.957101, 46.716929 ], [ -89.918466, 46.740324 ], [ -89.892355, 46.763088 ], [ -89.848652, 46.795711 ], [ -89.831956, 46.804053 ], [ -89.790663, 46.818469 ], [ -89.720277, 46.830413 ], [ -89.673375, 46.833229 ], [ -89.660625, 46.831056 ], [ -89.642255, 46.825340 ], [ -89.634938, 46.819488 ], [ -89.619329, 46.818890 ], [ -89.569808, 46.831859 ], [ -89.535683, 46.835878 ], [ -89.513938, 46.841835 ], [ -89.499080, 46.841621 ], [ -89.491252, 46.838448 ], [ -89.471540, 46.837359 ], [ -89.437047, 46.839512 ], [ -89.415154, 46.843983 ], [ -89.372032, 46.857386 ], [ -89.249143, 46.903326 ], [ -89.227914, 46.912954 ], [ -89.201511, 46.931149 ], [ -89.168244, 46.965536 ], [ -89.142595, 46.984859 ], [ -89.128698, 46.992599 ], [ -89.118339, 46.994220 ], [ -89.113158, 46.989356 ], [ -89.106277, 46.986480 ], [ -89.086742, 46.985298 ], [ -89.063103, 46.988522 ], [ -89.039490, 46.999419 ], [ -89.028930, 47.001140 ], [ -89.022994, 46.995120 ], [ -88.998417, 46.995314 ], [ -88.987197, 46.997239 ], [ -88.972802, 47.002096 ], [ -88.959409, 47.008496 ], [ -88.944045, 47.020129 ], [ -88.924492, 47.042156 ], [ -88.914189, 47.059246 ], [ -88.903706, 47.086161 ], [ -88.889140, 47.100575 ], [ -88.855372, 47.114263 ], [ -88.848176, 47.115065 ], [ -88.814834, 47.141399 ], [ -88.789813, 47.150925 ], [ -88.778022, 47.150465 ], [ -88.764351, 47.155762 ], [ -88.729688, 47.185834 ], [ -88.699660, 47.204831 ], [ -88.672395, 47.219137 ], [ -88.656359, 47.225624 ], [ -88.640323, 47.226784 ], [ -88.623579, 47.232352 ], [ -88.609830, 47.238894 ], [ -88.584912, 47.242361 ], [ -88.573997, 47.245989 ], [ -88.500780, 47.293503 ], [ -88.477733, 47.313460 ], [ -88.470484, 47.327653 ], [ -88.459262, 47.339903 ], [ -88.418673, 47.371188 ], [ -88.389459, 47.384431 ], [ -88.324083, 47.403542 ], [ -88.303447, 47.412204 ], [ -88.285195, 47.422392 ], [ -88.239161, 47.429969 ], [ -88.227446, 47.435093 ], [ -88.218424, 47.441585 ], [ -88.216977, 47.445493 ], [ -88.217822, 47.448738 ], [ -88.181820, 47.457657 ], [ -88.150571, 47.460093 ], [ -88.139651, 47.462693 ], [ -88.085252, 47.468961 ], [ -88.076388, 47.467752 ], [ -88.049326, 47.469785 ], [ -88.048226, 47.470008 ], [ -88.048077, 47.474973 ], [ -88.040291, 47.475999 ], [ -87.978934, 47.479420 ], [ -87.929269, 47.478737 ], [ -87.902416, 47.477045 ], [ -87.898036, 47.474872 ], [ -87.816958, 47.471998 ], [ -87.801184, 47.473301 ], [ -87.756739, 47.460717 ], [ -87.730804, 47.449112 ], [ -87.715942, 47.439816 ], [ -87.710471, 47.406200 ], [ -87.712421, 47.401400 ], [ -87.721274, 47.401032 ], [ -87.742417, 47.405823 ], [ -87.751380, 47.405066 ], [ -87.759057, 47.403013 ], [ -87.765019, 47.398652 ], [ -87.800294, 47.392148 ], [ -87.815371, 47.384790 ], [ -87.827115, 47.386160 ], [ -87.834822, 47.390478 ], [ -87.848252, 47.394864 ], [ -87.856700, 47.395387 ], [ -87.882245, 47.395588 ], [ -87.941613, 47.390073 ], [ -87.957058, 47.387260 ], [ -87.965063, 47.374430 ], [ -87.965598, 47.368645 ], [ -87.962567, 47.362543 ], [ -87.954796, 47.356809 ], [ -87.947397, 47.355461 ], [ -87.938787, 47.346777 ], [ -87.938250, 47.342299 ], [ -87.943360, 47.335899 ], [ -87.946352, 47.334254 ], [ -87.958386, 47.334435 ], [ -87.968604, 47.332582 ], [ -87.989133, 47.322633 ], [ -88.016478, 47.306275 ], [ -88.054849, 47.298240 ], [ -88.060090, 47.295796 ], [ -88.071476, 47.286768 ], [ -88.096851, 47.261351 ], [ -88.108833, 47.259131 ], [ -88.117456, 47.255174 ], [ -88.131943, 47.239554 ], [ -88.163059, 47.216278 ], [ -88.194218, 47.209242 ], [ -88.204849, 47.210498 ], [ -88.212361, 47.209423 ], [ -88.228987, 47.199042 ], [ -88.236892, 47.189236 ], [ -88.242006, 47.174767 ], [ -88.242660, 47.158426 ], [ -88.239470, 47.151137 ], [ -88.236721, 47.149287 ], [ -88.231797, 47.149609 ], [ -88.232164, 47.145975 ], [ -88.239895, 47.139436 ], [ -88.247628, 47.135981 ], [ -88.249571, 47.136231 ], [ -88.250785, 47.140209 ], [ -88.255303, 47.143640 ], [ -88.262972, 47.145174 ], [ -88.272017, 47.143511 ], [ -88.281701, 47.138212 ], [ -88.289040, 47.129689 ], [ -88.289543, 47.126604 ], [ -88.287870, 47.125374 ], [ -88.287173, 47.120420 ], [ -88.288347, 47.114547 ], [ -88.297625, 47.098505 ], [ -88.340052, 47.080494 ], [ -88.346709, 47.079372 ], [ -88.349952, 47.076377 ], [ -88.353191, 47.069063 ], [ -88.353952, 47.058047 ], [ -88.359054, 47.039739 ], [ -88.367624, 47.019213 ], [ -88.373966, 47.012262 ], [ -88.385606, 47.004522 ], [ -88.404498, 46.983353 ], [ -88.411145, 46.977984 ], [ -88.443901, 46.972251 ], [ -88.448570, 46.946769 ], [ -88.455404, 46.923321 ], [ -88.475859, 46.886042 ], [ -88.477935, 46.850560 ], [ -88.483748, 46.831727 ], [ -88.482579, 46.826197 ], [ -88.473342, 46.806226 ], [ -88.462349, 46.786711 ], [ -88.438427, 46.786714 ], [ -88.433835, 46.793502 ], [ -88.415225, 46.811715 ], [ -88.381410, 46.838466 ], [ -88.382204, 46.844477 ], [ -88.382052, 46.845437 ], [ -88.390135, 46.851595 ], [ -88.404008, 46.848331 ], [ -88.389727, 46.867100 ], [ -88.372591, 46.872812 ], [ -88.375855, 46.863428 ], [ -88.369848, 46.857568 ], [ -88.368767, 46.857313 ], [ -88.360868, 46.856202 ], [ -88.351940, 46.857028 ], [ -88.310290, 46.889748 ], [ -88.281244, 46.906632 ], [ -88.261593, 46.915516 ], [ -88.244437, 46.929612 ], [ -88.167227, 46.958855 ], [ -88.155374, 46.965069 ], [ -88.143688, 46.966665 ], [ -88.132876, 46.962204 ], [ -88.150114, 46.943630 ], [ -88.187522, 46.918999 ], [ -88.175197, 46.904580 ], [ -88.161913, 46.904941 ], [ -88.126927, 46.909840 ], [ -88.101315, 46.917207 ], [ -88.081870, 46.920458 ], [ -88.065192, 46.918563 ], [ -88.032408, 46.908890 ], [ -88.004298, 46.906982 ], [ -87.986113, 46.905957 ], [ -87.956000, 46.909051 ], [ -87.900339, 46.909686 ], [ -87.874538, 46.892578 ], [ -87.846195, 46.883905 ], [ -87.841228, 46.884363 ], [ -87.827162, 46.889713 ], [ -87.816794, 46.891154 ], [ -87.813226, 46.888023 ], [ -87.793194, 46.880822 ], [ -87.782461, 46.879859 ], [ -87.776930, 46.876726 ], [ -87.776313, 46.872591 ], [ -87.778752, 46.870422 ], [ -87.776804, 46.866823 ], [ -87.765989, 46.861316 ], [ -87.755868, 46.860453 ], [ -87.746646, 46.865427 ], [ -87.741014, 46.865247 ], [ -87.734870, 46.850120 ], [ -87.736732, 46.847216 ], [ -87.734325, 46.836955 ], [ -87.731522, 46.831196 ], [ -87.727358, 46.827656 ], [ -87.713737, 46.825534 ], [ -87.694590, 46.827182 ], [ -87.685698, 46.832530 ], [ -87.687930, 46.839159 ], [ -87.687164, 46.841742 ], [ -87.680668, 46.842496 ], [ -87.674541, 46.836964 ], [ -87.673177, 46.827593 ], [ -87.674345, 46.824050 ], [ -87.672015, 46.820415 ], [ -87.662261, 46.815157 ], [ -87.651510, 46.812411 ], [ -87.641887, 46.813733 ], [ -87.633300, 46.812107 ], [ -87.628081, 46.805157 ], [ -87.607988, 46.788408 ], [ -87.595307, 46.782950 ], [ -87.590767, 46.753009 ], [ -87.582745, 46.730527 ], [ -87.573203, 46.720471 ], [ -87.523308, 46.688488 ], [ -87.524444, 46.677586 ], [ -87.503025, 46.647497 ], [ -87.492860, 46.642561 ], [ -87.467965, 46.635623 ], [ -87.466537, 46.631555 ], [ -87.467563, 46.626228 ], [ -87.464108, 46.614811 ], [ -87.451368, 46.605923 ], [ -87.442612, 46.602776 ], [ -87.411167, 46.601669 ], [ -87.403275, 46.595215 ], [ -87.383961, 46.593070 ], [ -87.381649, 46.580059 ], [ -87.392974, 46.572523 ], [ -87.392828, 46.570852 ], [ -87.382206, 46.553681 ], [ -87.375613, 46.547140 ], [ -87.390300, 46.542577 ], [ -87.393985, 46.533183 ], [ -87.389290, 46.524472 ], [ -87.381349, 46.517292 ], [ -87.366767, 46.507303 ], [ -87.351071, 46.500749 ], [ -87.310755, 46.492017 ], [ -87.258732, 46.488255 ], [ -87.202404, 46.490827 ], [ -87.175065, 46.497548 ], [ -87.127440, 46.494014 ], [ -87.107559, 46.496124 ], [ -87.098760, 46.503609 ], [ -87.077279, 46.515339 ], [ -87.046022, 46.519956 ], [ -87.029892, 46.525599 ], [ -87.017136, 46.533550 ], [ -87.008724, 46.532723 ], [ -86.976958, 46.526581 ], [ -86.964534, 46.516549 ], [ -86.962842, 46.509646 ], [ -86.946980, 46.484567 ], [ -86.946218, 46.479059 ], [ -86.949526, 46.476315 ], [ -86.947077, 46.472064 ], [ -86.927725, 46.464566 ], [ -86.903742, 46.466138 ], [ -86.889094, 46.458499 ], [ -86.883976, 46.450976 ], [ -86.883919, 46.441514 ], [ -86.875151, 46.437280 ], [ -86.850111, 46.434114 ], [ -86.837448, 46.434186 ], [ -86.816026, 46.437892 ], [ -86.810967, 46.449663 ], [ -86.808817, 46.460611 ], [ -86.803557, 46.466669 ], [ -86.787905, 46.477729 ], [ -86.768516, 46.479072 ], [ -86.750157, 46.479109 ], [ -86.735929, 46.475231 ], [ -86.731096, 46.471760 ], [ -86.730829, 46.468057 ], [ -86.710573, 46.444908 ], [ -86.703230, 46.439378 ], [ -86.698139, 46.438624 ], [ -86.686412, 46.454965 ], [ -86.688816, 46.463152 ], [ -86.686468, 46.471655 ], [ -86.683819, 46.498079 ], [ -86.696001, 46.503160 ], [ -86.701929, 46.511571 ], [ -86.709325, 46.543914 ], [ -86.695645, 46.555026 ], [ -86.678182, 46.561039 ], [ -86.675764, 46.557061 ], [ -86.670927, 46.556489 ], [ -86.656479, 46.558453 ], [ -86.652865, 46.560555 ], [ -86.627380, 46.533710 ], [ -86.629086, 46.518144 ], [ -86.632109, 46.508865 ], [ -86.634530, 46.504523 ], [ -86.641088, 46.500438 ], [ -86.645528, 46.492039 ], [ -86.646393, 46.485776 ], [ -86.636671, 46.478298 ], [ -86.627441, 46.477540 ], [ -86.620603, 46.483873 ], [ -86.618061, 46.489452 ], [ -86.612173, 46.493295 ], [ -86.609393, 46.492976 ], [ -86.606932, 46.478531 ], [ -86.609039, 46.470239 ], [ -86.586168, 46.463324 ], [ -86.557731, 46.487434 ], [ -86.524959, 46.505381 ], [ -86.495054, 46.524874 ], [ -86.484003, 46.535965 ], [ -86.481956, 46.542709 ], [ -86.469306, 46.551422 ], [ -86.459930, 46.551928 ], [ -86.444390, 46.548137 ], [ -86.437167, 46.548960 ], [ -86.390409, 46.563194 ], [ -86.349890, 46.578035 ], [ -86.188024, 46.654008 ], [ -86.161681, 46.669475 ], [ -86.138295, 46.672935 ], [ -86.119862, 46.657256 ], [ -86.112126, 46.655044 ], [ -86.099843, 46.654615 ], [ -86.074219, 46.657799 ], [ -86.036969, 46.667627 ], [ -85.995044, 46.673676 ], [ -85.953670, 46.676869 ], [ -85.924047, 46.684733 ], [ -85.877908, 46.690914 ], [ -85.841057, 46.688896 ], [ -85.794923, 46.681083 ], [ -85.750606, 46.677368 ], [ -85.714415, 46.677156 ], [ -85.668753, 46.680404 ], [ -85.624573, 46.678862 ], [ -85.587345, 46.674627 ], [ -85.542517, 46.674263 ], [ -85.509510, 46.675786 ], [ -85.482096, 46.680432 ], [ -85.369805, 46.713754 ], [ -85.289846, 46.744644 ], [ -85.256860, 46.753380 ], [ -85.173042, 46.763634 ], [ -85.063556, 46.757856 ], [ -85.036286, 46.760435 ], [ -85.009240, 46.769224 ], [ -84.989497, 46.772403 ], [ -84.964652, 46.772845 ], [ -84.954009, 46.771362 ], [ -84.951580, 46.769488 ], [ -84.987539, 46.745483 ], [ -85.007616, 46.728339 ], [ -85.020159, 46.712463 ], [ -85.027513, 46.697451 ], [ -85.030078, 46.684769 ], [ -85.028291, 46.675125 ], [ -85.035504, 46.625021 ], [ -85.037056, 46.600995 ], [ -85.035476, 46.581547 ], [ -85.031507, 46.568703 ], [ -85.029594, 46.554419 ], [ -85.027374, 46.553756 ], [ -85.025491, 46.546397 ], [ -85.027083, 46.543038 ], [ -85.045534, 46.537694 ], [ -85.052954, 46.532827 ], [ -85.056133, 46.526520 ], [ -85.054943, 46.514750 ], [ -85.049847, 46.503963 ], [ -85.033766, 46.487670 ], [ -85.025598, 46.483028 ], [ -85.015211, 46.479712 ], [ -84.969464, 46.476290 ], [ -84.955307, 46.480269 ], [ -84.947269, 46.487399 ], [ -84.937145, 46.489252 ], [ -84.934432, 46.480315 ], [ -84.921931, 46.469962 ], [ -84.915184, 46.467515 ], [ -84.893423, 46.465406 ], [ -84.875070, 46.466781 ], [ -84.861448, 46.469930 ], [ -84.849767, 46.460245 ], [ -84.843907, 46.448661 ], [ -84.829491, 46.444071 ], [ -84.800101, 46.446219 ], [ -84.769151, 46.453523 ], [ -84.723338, 46.468266 ], [ -84.689672, 46.483923 ], [ -84.678423, 46.487694 ], [ -84.653880, 46.482250 ], [ -84.631020, 46.484868 ], [ -84.616489, 46.471870 ], [ -84.607945, 46.456747 ], [ -84.584167, 46.439410 ], [ -84.573522, 46.427895 ], [ -84.551496, 46.418522 ], [ -84.503719, 46.439190 ], [ -84.493401, 46.440313 ], [ -84.479513, 46.432573 ], [ -84.471848, 46.434289 ], [ -84.462597, 46.440940 ], [ -84.455527, 46.453897 ], [ -84.455256, 46.462785 ], [ -84.463322, 46.467435 ] ] }"; - OperatorImportFromGeoJson importerGeoJson = (OperatorImportFromGeoJson) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.ImportFromGeoJson); - Geometry geom = importerGeoJson.execute(0, Geometry.Type.Unknown, geoJSON, null).getGeometry(); - - - double distance = 9000; - OperatorGeodesicBuffer opBuf = (OperatorGeodesicBuffer) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.GeodesicBuffer); - Polygon poly = (Polygon) opBuf.execute(geom, sr, GeodeticCurveType.Geodesic, distance, 100.0, false, null); - - String words = GeometryEngine.geometryToWkt(poly, 0); - assertTrue(poly.getType() == Geometry.Type.Polygon); - } - - @Test - public void testTriangleLength() { - Point pt_0 = new Point(10, 10); - Point pt_1 = new Point(20, 20); - Point pt_2 = new Point(20, 10); - double length = 0.0; - length += GeometryEngine.geodesicDistanceOnWGS84(pt_0, pt_1); - length += GeometryEngine.geodesicDistanceOnWGS84(pt_1, pt_2); - length += GeometryEngine.geodesicDistanceOnWGS84(pt_2, pt_0); - assertTrue(Math.abs(length - 3744719.4094597572) < 1e-13 * 3744719.4094597572); - } - - - @Test - public void testGeodesicForward() { - double latitude = 0; - double longitude = 0; - double distance = 1000; - double azimuth = 0; - double a = 6378137.0; // radius of spheroid for WGS_1984 - double e2 = 0.0066943799901413165; // ellipticity for WGS_1984 - double rpu = Math.PI / 180.0; - PeDouble lam2 = new PeDouble(); - PeDouble phi2 = new PeDouble(); - GeoDist.geodesic_forward(a, e2, longitude, latitude, distance, azimuth, lam2, phi2); - assertEquals(longitude, lam2.val / rpu, 0.00001); - } - - @Test - public void testRotationInvariance() { - Point pt_0 = new Point(10, 40); - Point pt_1 = new Point(20, 60); - Point pt_2 = new Point(20, 40); - double length = 0.0; - length += GeometryEngine.geodesicDistanceOnWGS84(pt_0, pt_1); - length += GeometryEngine.geodesicDistanceOnWGS84(pt_1, pt_2); - length += GeometryEngine.geodesicDistanceOnWGS84(pt_2, pt_0); - assertTrue(Math.abs(length - 5409156.3896271614) < 1e-13 * 5409156.3896271614); - - for (int i = -540; i < 540; i += 5) { - pt_0.setXY(i + 10, 40); - pt_1.setXY(i + 20, 60); - pt_2.setXY(i + 20, 40); - length = 0.0; - length += GeometryEngine.geodesicDistanceOnWGS84(pt_0, pt_1); - length += GeometryEngine.geodesicDistanceOnWGS84(pt_1, pt_2); - length += GeometryEngine.geodesicDistanceOnWGS84(pt_2, pt_0); - assertTrue(Math.abs(length - 5409156.3896271614) < 1e-13 * 5409156.3896271614); - } - } - - @Test - public void testDistanceFailure() { - { - Point p1 = new Point(-60.668485, -31.996013333333334); - Point p2 = new Point(119.13731666666666, 32.251583333333336); - double d = GeometryEngine.geodesicDistanceOnWGS84(p1, p2); - assertTrue(Math.abs(d - 19973410.50579736) < 1e-13 * 19973410.50579736); - } - - { - Point p1 = new Point(121.27343833333333, 27.467438333333334); - Point p2 = new Point(-58.55804833333333, -27.035613333333334); - double d = GeometryEngine.geodesicDistanceOnWGS84(p1, p2); - assertTrue(Math.abs(d - 19954707.428360686) < 1e-13 * 19954707.428360686); - } - - { - Point p1 = new Point(-53.329865, -36.08110166666667); - Point p2 = new Point(126.52895166666667, 35.97385); - double d = GeometryEngine.geodesicDistanceOnWGS84(p1, p2); - assertTrue(Math.abs(d - 19990586.700431127) < 1e-13 * 19990586.700431127); - } - - { - Point p1 = new Point(-4.7181166667, 36.1160166667); - Point p2 = new Point(175.248925, -35.7606716667); - double d = GeometryEngine.geodesicDistanceOnWGS84(p1, p2); - assertTrue(Math.abs(d - 19964450.206594173) < 1e-12 * 19964450.206594173); - } - } - - @Test - public void testDensifyPolyline() { - SpatialReference sr = SpatialReference.create(4326); - { - Polyline polyline = new Polyline(); - polyline.startPath(0, 0); - polyline.lineTo(4, 4); - polyline.lineTo(4, 8); - polyline.lineTo(8, 20); - - OperatorGeodeticDensifyByLength op = (OperatorGeodeticDensifyByLength) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.GeodeticDensifyByLength); - Polyline polylineDense = (Polyline) op.execute(polyline, sr, 5000, GeodeticCurveType.Geodesic, null); - assertEquals(polyline.calculateLength2D(), polylineDense.calculateLength2D(), .001); - assertEquals(polyline.getPoint(polyline.getPointCount() - 1).getX(), polylineDense.getPoint(polylineDense.getPointCount() - 1).getX()); - assertEquals(polyline.getPoint(polyline.getPointCount() - 1).getY(), polylineDense.getPoint(polylineDense.getPointCount() - 1).getY()); - assertEquals(polyline.getPoint(0).getX(), polylineDense.getPoint(0).getX()); - assertEquals(polyline.getPoint(0).getY(), polylineDense.getPoint(0).getY()); - - polyline.startPath(-2, -2); - polyline.lineTo(-4, -4); - polyline.lineTo(-8, -8); - polylineDense = (Polyline) op.execute(polyline, sr, 5000, GeodeticCurveType.Geodesic, null); - assertEquals(polyline.calculateLength2D(), polylineDense.calculateLength2D(), .001); - assertEquals(polyline.calculatePathLength2D(0), polylineDense.calculatePathLength2D(0), .001); - assertEquals(polyline.calculatePathLength2D(1), polylineDense.calculatePathLength2D(1), .001); - } - - { - Polyline polyline = new Polyline(); - polyline.startPath(0, 0); - polyline.lineTo(0, 1); - OperatorGeodeticDensifyByLength op = (OperatorGeodeticDensifyByLength) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.GeodeticDensifyByLength); - Polyline polylineDense = (Polyline) op.execute(polyline, sr, 100000, GeodeticCurveType.Geodesic, null); - assertEquals(polyline.getPoint(polyline.getPointCount() - 1).getX(), polylineDense.getPoint(polylineDense.getPointCount() - 1).getX()); - assertEquals(polyline.getPoint(polyline.getPointCount() - 1).getY(), polylineDense.getPoint(polylineDense.getPointCount() - 1).getY()); - assertEquals(polyline.getPoint(0).getX(), polylineDense.getPoint(0).getX()); - assertEquals(polyline.getPoint(0).getY(), polylineDense.getPoint(0).getY()); - assertEquals(3, polylineDense.getPointCount()); - - assertEquals( - GeometryEngine.geodesicDistanceOnWGS84( - polyline.getPoint(0), - polylineDense.getPoint(1)) + - GeometryEngine.geodesicDistanceOnWGS84( - polylineDense.getPoint(1), - polyline.getPoint(1)), - GeometryEngine.geodesicDistanceOnWGS84( - polyline.getPoint(0), - polyline.getPoint(1)) - ); - } - - { - Polyline polyline = new Polyline(); - polyline.startPath(0, 0); - polyline.lineTo(1, 0); - polyline.lineTo(2, 0); - OperatorGeodeticDensifyByLength op = (OperatorGeodeticDensifyByLength) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.GeodeticDensifyByLength); - Polyline polylineDense = (Polyline) op.execute(polyline, sr, 100000, GeodeticCurveType.Geodesic, null); - assertEquals(polyline.getPoint(polyline.getPointCount() - 1).getX(), polylineDense.getPoint(polylineDense.getPointCount() - 1).getX()); - assertEquals(polyline.getPoint(polyline.getPointCount() - 1).getY(), polylineDense.getPoint(polylineDense.getPointCount() - 1).getY()); - assertEquals(polyline.getPoint(0).getX(), polylineDense.getPoint(0).getX()); - assertEquals(polyline.getPoint(0).getY(), polylineDense.getPoint(0).getY()); - assertEquals(5, polylineDense.getPointCount()); - - assertEquals(polyline.getPoint(1).getX(), polylineDense.getPoint(2).getX()); - assertEquals(polyline.getPoint(1).getY(), polylineDense.getPoint(2).getY()); - assertEquals( - GeometryEngine.geodesicDistanceOnWGS84( - polyline.getPoint(0), - polylineDense.getPoint(1)) + - GeometryEngine.geodesicDistanceOnWGS84( - polylineDense.getPoint(1), - polyline.getPoint(1)), - GeometryEngine.geodesicDistanceOnWGS84( - polyline.getPoint(0), - polyline.getPoint(1)), - .0000001 - ); - } - - { - Polyline polyline = new Polyline(); - polyline.startPath(0, 0); - polyline.lineTo(4, 4); - polyline.lineTo(4, 8); - polyline.lineTo(8, 20); - double max_distance = 55000 / 3.5; - OperatorGeodeticDensifyByLength op = (OperatorGeodeticDensifyByLength) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.GeodeticDensifyByLength); - Polyline polylineDense = (Polyline) op.execute(polyline, sr, max_distance, GeodeticCurveType.Geodesic, null); - String words = GeometryEngine.geometryToWkt(polylineDense, 0); - assertEquals( - GeometryEngine.geodesicDistanceOnWGS84( - polyline.getPoint(0), - polyline.getPoint(polyline.getPointCount() - 1)), - GeometryEngine.geodesicDistanceOnWGS84( - polylineDense.getPoint(0), - polylineDense.getPoint(polylineDense.getPointCount() - 1)), - .0000001 - ); - } - } - - @Test - public void testDensifyPolygon() { - { - Polygon polygon = new Polygon(); - polygon.startPath(0, 0); - polygon.lineTo(0, 4); - polygon.lineTo(4, 4); - polygon.lineTo(4, 0); - polygon.closeAllPaths(); - SpatialReference sr = SpatialReference.create(4326); - OperatorGeodeticDensifyByLength op = (OperatorGeodeticDensifyByLength) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.GeodeticDensifyByLength); - Polygon polygonDense = (Polygon) op.execute(polygon, sr, 5000, GeodeticCurveType.Geodesic, null); - assertEquals(polygon.calculateLength2D(), polygonDense.calculateLength2D(), .005); - assertEquals(polygon.calculateArea2D(), polygonDense.calculateArea2D(), 0.007); - - polygon.startPath(-2, -2); - polygon.lineTo(-4, -4); - polygon.lineTo(-8, -8); - polygon.closeAllPaths(); - polygonDense = (Polygon) op.execute(polygon, sr, 5000, GeodeticCurveType.Geodesic, null); - assertEquals(polygon.calculateLength2D(), polygonDense.calculateLength2D(), .004); - assertEquals(polygon.calculateArea2D(), polygonDense.calculateArea2D(), 0.1); - } - } - - @Test - public void testInflateEnv2D() { - Envelope2D envOrig = new Envelope2D(0, -4, 4, 8); - Envelope2D env2D = new Envelope2D(0, -4, 4, 8); - - double a = 6378137.0; // radius of spheroid for WGS_1984 - double e2 = 0.0066943799901413165; // ellipticity for WGS_1984 - - GeoDist.inflateEnv2D(a, e2, env2D, 1000, 2000); - assertTrue(env2D.xmin < envOrig.xmin); - assertTrue(env2D.ymax > envOrig.ymax); - assertTrue(env2D.xmax > envOrig.xmax); - assertTrue(env2D.ymin < envOrig.ymin); - } - - @Test - public void testDeflateEnv2D() { - Envelope2D envOrig = new Envelope2D(0, -4, 4, 8); - Envelope2D env2D = new Envelope2D(0, -4, 4, 8); - - double a = 6378137.0; // radius of spheroid for WGS_1984 - double e2 = 0.0066943799901413165; // ellipticity for WGS_1984 - - GeoDist.inflateEnv2D(a, e2, env2D, -100, 2000); - assertTrue(env2D.xmin > envOrig.xmin); - assertTrue(env2D.ymax > envOrig.ymax); - assertTrue(env2D.xmax < envOrig.xmax); - assertTrue(env2D.ymin < envOrig.ymin); - } - - @Test - public void testVicenty() { - // test data from - // http://geographiclib.sourceforge.net/cgi-bin/GeodSolve - - double a = 6378137.0; // radius of spheroid for WGS_1984 - double e2 = 0.0066943799901413165; // ellipticity for WGS_1984 - double rpu = Math.PI / 180.0; - double dpu = 180.0 / Math.PI; - double distance = 2000.0; - { - Point p1 = new Point(0.0, 0.0); - PeDouble lam = new PeDouble(); - PeDouble phi = new PeDouble(); - GeoDist.geodesic_forward(a, e2, p1.getX() * rpu, p1.getY() * rpu, distance, 0.0 * rpu, lam, phi); - assertEquals(0.0, lam.val * dpu, 0.000001); - assertEquals(0.01808739, phi.val * dpu, 0.000001); - - PeDouble actualDistance = new PeDouble(); - GeoDist.geodesic_distance_ngs(a, e2, p1.getX() * rpu, p1.getY() * rpu, lam.val, phi.val, actualDistance, null, null); - assertEquals(actualDistance.val, distance, .02); - - } - { - Point p1 = new Point(45.0, 45.0); - PeDouble lam = new PeDouble(); - PeDouble phi = new PeDouble(); - GeoDist.geodesic_forward(a, e2, p1.getX() * rpu, p1.getY() * rpu, distance, 20.0 * rpu, lam, phi); - - assertEquals(45.01691097, phi.val * dpu, 0.000001); - assertEquals(45.00867811, lam.val * dpu, 0.000001); - } - { - Point p1 = new Point(60.0, 45.0); - PeDouble lam = new PeDouble(); - PeDouble phi = new PeDouble(); - GeoDist.geodesic_forward(a, e2, p1.getX() * rpu, p1.getY() * rpu, distance, 20.0 * rpu, lam, phi); - - //45.01691097 - assertEquals(45.01691097, phi.val * dpu, 0.000001); - assertEquals(60.00867811, lam.val * dpu, 0.000001); - } - { - Point p1 = new Point(-65.0, -45.0); - PeDouble lam = new PeDouble(); - PeDouble phi = new PeDouble(); - GeoDist.geodesic_forward(a, e2, p1.getX() * rpu, p1.getY() * rpu, distance, -20.0 * rpu, lam, phi); - - //-44.98308832 -65.00867301 - assertEquals(-44.98308832, phi.val * dpu, 0.000001); - assertEquals(-65.00867301, lam.val * dpu, 0.000001); - } - { - Point p1 = new Point(-165.0, -45.0); - PeDouble lam = new PeDouble(); - PeDouble phi = new PeDouble(); - GeoDist.geodesic_forward(a, e2, p1.getX() * rpu, p1.getY() * rpu, distance, 220.0 * rpu, lam, phi); - - //-45.01378505 -165.01630863 - assertEquals(-45.01378505, phi.val * dpu, 0.000001); - assertEquals(-165.01630863, lam.val * dpu, 0.000001); - } - } - - @Test - public void testGeodeticBufferPoint() { - { - SpatialReference sr = SpatialReference.create(4326); - Point p1 = new Point(0.0, 0.0); - OperatorGeodesicBuffer opBuf = (OperatorGeodesicBuffer) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.GeodesicBuffer); - double distance = 1000; - Polygon poly = (Polygon) opBuf.execute(p1, sr, GeodeticCurveType.Geodesic, distance, 0.1, false, null); - //String words = GeometryEngine.geometryToWkt(poly, 0); - assertNotNull(poly); - assertTrue(poly.getType() == Geometry.Type.Polygon); - double area = poly.calculateArea2D(); - assertEquals(2.550450219554701E-4, area, 0.0000000001); - assertEquals(97, poly.getPointCount()); - - assertTrue(OperatorContains.local().execute(poly, p1, sr, null)); - } - } - - @Test - public void testGeodeticBufferMultiPoint() { - { - SpatialReference sr = SpatialReference.create(4326); - MultiPoint mp = new MultiPoint(); - mp.add(0.0, 0.0); - mp.add(20.0, 0.0); - OperatorGeodesicBuffer opBuf = (OperatorGeodesicBuffer) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.GeodesicBuffer); - double distance = 100000; - Polygon poly = (Polygon) opBuf.execute(mp, sr, GeodeticCurveType.Geodesic, distance, 150, false, null); - String words = GeometryEngine.geometryToWkt(poly, 0); - assertNotNull(poly); - assertTrue(poly.getType() == Geometry.Type.Polygon); - assertEquals(2, poly.getPathCount()); - double area = poly.calculateArea2D(); - assertEquals(5.095268886272399, area, 0.0000000001); - assertEquals(120, poly.getPointCount()); - - assertTrue(OperatorContains.local().execute(poly, mp, sr, null)); - } - } - - @Test - public void testGeodeticBufferPolyline() { - { - Polyline polyline = new Polyline(); - polyline.startPath(0, 0); - polyline.lineTo(4, 4); - polyline.lineTo(4, 8); - polyline.lineTo(8, 20); - SpatialReference sr = SpatialReference.create(4326); - - OperatorBuffer opBufNorm = (OperatorBuffer) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.Buffer); - Polygon polyNorm = (Polygon) opBufNorm.execute(polyline, sr, .7, null); - - - String words = GeometryEngine.geometryToWkt(polyline, 0); - double distance = 55000; - OperatorGeodesicBuffer opBuf = (OperatorGeodesicBuffer) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.GeodesicBuffer); - Polygon poly = (Polygon) opBuf.execute(polyline, sr, GeodeticCurveType.Geodesic, distance, 150.0, false, null); - - words = GeometryEngine.geometryToWkt(poly, 0); - assertNotNull(poly); - assertTrue(poly.getType() == Geometry.Type.Polygon); - double area = poly.calculateArea2D(); - assertEquals(23.296270856192834, area, 0.00000000001); - } - } - - @Test - public void testBufferGeodeticPolyline2() { - SpatialReference sr = SpatialReference.create(4326); - Polyline inputGeom = new Polyline(); - OperatorGeodesicBuffer buffer = (OperatorGeodesicBuffer) OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.GeodesicBuffer); - OperatorSimplify simplify = (OperatorSimplify) OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.Simplify); - inputGeom.startPath(0, 0); - inputGeom.lineTo(50, 50); - inputGeom.lineTo(50, 0); - inputGeom.lineTo(0, 50); - - { - Geometry result = buffer.execute(inputGeom, sr, 0, 0, 0, false, null); - assertTrue(result.getType().value() == Geometry.GeometryType.Polygon); - assertTrue(result.isEmpty()); - } - - { - Geometry result = buffer.execute(inputGeom, sr, 0, -1, 0, false, null); - assertTrue(result.getType().value() == Geometry.GeometryType.Polygon); - assertTrue(result.isEmpty()); - } - - { - String words = GeometryEngine.geometryToWkt(inputGeom, 0); - Geometry result = buffer.execute(inputGeom, sr, 0, 40.0, 50, false, null); - assertTrue(result.getType().value() == Geometry.GeometryType.Polygon); - Polygon poly = (Polygon) (result); - - words = GeometryEngine.geometryToWkt(poly, 0); + @Override + protected void setUp() throws Exception { + super.setUp(); + } + + @Override + protected void tearDown() throws Exception { + super.tearDown(); + } + + @Test + public void testCoastalPolyline() throws Exception { + SpatialReference sr = SpatialReference.create(4326); + String geoJSON = "{ \"type\": \"LineString\", \"coordinates\": [ [ -90.418136, 46.566094 ], [ -90.348407, 46.600635 ], [ -90.327626, 46.607744 ], [ -90.306609, 46.602741 ], [ -90.265294, 46.618516 ], [ -90.237609, 46.624485 ], [ -90.164026, 46.645515 ], [ -90.100695, 46.655132 ], [ -90.045420, 46.668272 ], [ -90.028392, 46.674390 ], [ -89.996034, 46.693225 ], [ -89.985817, 46.703190 ], [ -89.973803, 46.710322 ], [ -89.957101, 46.716929 ], [ -89.918466, 46.740324 ], [ -89.892355, 46.763088 ], [ -89.848652, 46.795711 ], [ -89.831956, 46.804053 ], [ -89.790663, 46.818469 ], [ -89.720277, 46.830413 ], [ -89.673375, 46.833229 ], [ -89.660625, 46.831056 ], [ -89.642255, 46.825340 ], [ -89.634938, 46.819488 ], [ -89.619329, 46.818890 ], [ -89.569808, 46.831859 ], [ -89.535683, 46.835878 ], [ -89.513938, 46.841835 ], [ -89.499080, 46.841621 ], [ -89.491252, 46.838448 ], [ -89.471540, 46.837359 ], [ -89.437047, 46.839512 ], [ -89.415154, 46.843983 ], [ -89.372032, 46.857386 ], [ -89.249143, 46.903326 ], [ -89.227914, 46.912954 ], [ -89.201511, 46.931149 ], [ -89.168244, 46.965536 ], [ -89.142595, 46.984859 ], [ -89.128698, 46.992599 ], [ -89.118339, 46.994220 ], [ -89.113158, 46.989356 ], [ -89.106277, 46.986480 ], [ -89.086742, 46.985298 ], [ -89.063103, 46.988522 ], [ -89.039490, 46.999419 ], [ -89.028930, 47.001140 ], [ -89.022994, 46.995120 ], [ -88.998417, 46.995314 ], [ -88.987197, 46.997239 ], [ -88.972802, 47.002096 ], [ -88.959409, 47.008496 ], [ -88.944045, 47.020129 ], [ -88.924492, 47.042156 ], [ -88.914189, 47.059246 ], [ -88.903706, 47.086161 ], [ -88.889140, 47.100575 ], [ -88.855372, 47.114263 ], [ -88.848176, 47.115065 ], [ -88.814834, 47.141399 ], [ -88.789813, 47.150925 ], [ -88.778022, 47.150465 ], [ -88.764351, 47.155762 ], [ -88.729688, 47.185834 ], [ -88.699660, 47.204831 ], [ -88.672395, 47.219137 ], [ -88.656359, 47.225624 ], [ -88.640323, 47.226784 ], [ -88.623579, 47.232352 ], [ -88.609830, 47.238894 ], [ -88.584912, 47.242361 ], [ -88.573997, 47.245989 ], [ -88.500780, 47.293503 ], [ -88.477733, 47.313460 ], [ -88.470484, 47.327653 ], [ -88.459262, 47.339903 ], [ -88.418673, 47.371188 ], [ -88.389459, 47.384431 ], [ -88.324083, 47.403542 ], [ -88.303447, 47.412204 ], [ -88.285195, 47.422392 ], [ -88.239161, 47.429969 ], [ -88.227446, 47.435093 ], [ -88.218424, 47.441585 ], [ -88.216977, 47.445493 ], [ -88.217822, 47.448738 ], [ -88.181820, 47.457657 ], [ -88.150571, 47.460093 ], [ -88.139651, 47.462693 ], [ -88.085252, 47.468961 ], [ -88.076388, 47.467752 ], [ -88.049326, 47.469785 ], [ -88.048226, 47.470008 ], [ -88.048077, 47.474973 ], [ -88.040291, 47.475999 ], [ -87.978934, 47.479420 ], [ -87.929269, 47.478737 ], [ -87.902416, 47.477045 ], [ -87.898036, 47.474872 ], [ -87.816958, 47.471998 ], [ -87.801184, 47.473301 ], [ -87.756739, 47.460717 ], [ -87.730804, 47.449112 ], [ -87.715942, 47.439816 ], [ -87.710471, 47.406200 ], [ -87.712421, 47.401400 ], [ -87.721274, 47.401032 ], [ -87.742417, 47.405823 ], [ -87.751380, 47.405066 ], [ -87.759057, 47.403013 ], [ -87.765019, 47.398652 ], [ -87.800294, 47.392148 ], [ -87.815371, 47.384790 ], [ -87.827115, 47.386160 ], [ -87.834822, 47.390478 ], [ -87.848252, 47.394864 ], [ -87.856700, 47.395387 ], [ -87.882245, 47.395588 ], [ -87.941613, 47.390073 ], [ -87.957058, 47.387260 ], [ -87.965063, 47.374430 ], [ -87.965598, 47.368645 ], [ -87.962567, 47.362543 ], [ -87.954796, 47.356809 ], [ -87.947397, 47.355461 ], [ -87.938787, 47.346777 ], [ -87.938250, 47.342299 ], [ -87.943360, 47.335899 ], [ -87.946352, 47.334254 ], [ -87.958386, 47.334435 ], [ -87.968604, 47.332582 ], [ -87.989133, 47.322633 ], [ -88.016478, 47.306275 ], [ -88.054849, 47.298240 ], [ -88.060090, 47.295796 ], [ -88.071476, 47.286768 ], [ -88.096851, 47.261351 ], [ -88.108833, 47.259131 ], [ -88.117456, 47.255174 ], [ -88.131943, 47.239554 ], [ -88.163059, 47.216278 ], [ -88.194218, 47.209242 ], [ -88.204849, 47.210498 ], [ -88.212361, 47.209423 ], [ -88.228987, 47.199042 ], [ -88.236892, 47.189236 ], [ -88.242006, 47.174767 ], [ -88.242660, 47.158426 ], [ -88.239470, 47.151137 ], [ -88.236721, 47.149287 ], [ -88.231797, 47.149609 ], [ -88.232164, 47.145975 ], [ -88.239895, 47.139436 ], [ -88.247628, 47.135981 ], [ -88.249571, 47.136231 ], [ -88.250785, 47.140209 ], [ -88.255303, 47.143640 ], [ -88.262972, 47.145174 ], [ -88.272017, 47.143511 ], [ -88.281701, 47.138212 ], [ -88.289040, 47.129689 ], [ -88.289543, 47.126604 ], [ -88.287870, 47.125374 ], [ -88.287173, 47.120420 ], [ -88.288347, 47.114547 ], [ -88.297625, 47.098505 ], [ -88.340052, 47.080494 ], [ -88.346709, 47.079372 ], [ -88.349952, 47.076377 ], [ -88.353191, 47.069063 ], [ -88.353952, 47.058047 ], [ -88.359054, 47.039739 ], [ -88.367624, 47.019213 ], [ -88.373966, 47.012262 ], [ -88.385606, 47.004522 ], [ -88.404498, 46.983353 ], [ -88.411145, 46.977984 ], [ -88.443901, 46.972251 ], [ -88.448570, 46.946769 ], [ -88.455404, 46.923321 ], [ -88.475859, 46.886042 ], [ -88.477935, 46.850560 ], [ -88.483748, 46.831727 ], [ -88.482579, 46.826197 ], [ -88.473342, 46.806226 ], [ -88.462349, 46.786711 ], [ -88.438427, 46.786714 ], [ -88.433835, 46.793502 ], [ -88.415225, 46.811715 ], [ -88.381410, 46.838466 ], [ -88.382204, 46.844477 ], [ -88.382052, 46.845437 ], [ -88.390135, 46.851595 ], [ -88.404008, 46.848331 ], [ -88.389727, 46.867100 ], [ -88.372591, 46.872812 ], [ -88.375855, 46.863428 ], [ -88.369848, 46.857568 ], [ -88.368767, 46.857313 ], [ -88.360868, 46.856202 ], [ -88.351940, 46.857028 ], [ -88.310290, 46.889748 ], [ -88.281244, 46.906632 ], [ -88.261593, 46.915516 ], [ -88.244437, 46.929612 ], [ -88.167227, 46.958855 ], [ -88.155374, 46.965069 ], [ -88.143688, 46.966665 ], [ -88.132876, 46.962204 ], [ -88.150114, 46.943630 ], [ -88.187522, 46.918999 ], [ -88.175197, 46.904580 ], [ -88.161913, 46.904941 ], [ -88.126927, 46.909840 ], [ -88.101315, 46.917207 ], [ -88.081870, 46.920458 ], [ -88.065192, 46.918563 ], [ -88.032408, 46.908890 ], [ -88.004298, 46.906982 ], [ -87.986113, 46.905957 ], [ -87.956000, 46.909051 ], [ -87.900339, 46.909686 ], [ -87.874538, 46.892578 ], [ -87.846195, 46.883905 ], [ -87.841228, 46.884363 ], [ -87.827162, 46.889713 ], [ -87.816794, 46.891154 ], [ -87.813226, 46.888023 ], [ -87.793194, 46.880822 ], [ -87.782461, 46.879859 ], [ -87.776930, 46.876726 ], [ -87.776313, 46.872591 ], [ -87.778752, 46.870422 ], [ -87.776804, 46.866823 ], [ -87.765989, 46.861316 ], [ -87.755868, 46.860453 ], [ -87.746646, 46.865427 ], [ -87.741014, 46.865247 ], [ -87.734870, 46.850120 ], [ -87.736732, 46.847216 ], [ -87.734325, 46.836955 ], [ -87.731522, 46.831196 ], [ -87.727358, 46.827656 ], [ -87.713737, 46.825534 ], [ -87.694590, 46.827182 ], [ -87.685698, 46.832530 ], [ -87.687930, 46.839159 ], [ -87.687164, 46.841742 ], [ -87.680668, 46.842496 ], [ -87.674541, 46.836964 ], [ -87.673177, 46.827593 ], [ -87.674345, 46.824050 ], [ -87.672015, 46.820415 ], [ -87.662261, 46.815157 ], [ -87.651510, 46.812411 ], [ -87.641887, 46.813733 ], [ -87.633300, 46.812107 ], [ -87.628081, 46.805157 ], [ -87.607988, 46.788408 ], [ -87.595307, 46.782950 ], [ -87.590767, 46.753009 ], [ -87.582745, 46.730527 ], [ -87.573203, 46.720471 ], [ -87.523308, 46.688488 ], [ -87.524444, 46.677586 ], [ -87.503025, 46.647497 ], [ -87.492860, 46.642561 ], [ -87.467965, 46.635623 ], [ -87.466537, 46.631555 ], [ -87.467563, 46.626228 ], [ -87.464108, 46.614811 ], [ -87.451368, 46.605923 ], [ -87.442612, 46.602776 ], [ -87.411167, 46.601669 ], [ -87.403275, 46.595215 ], [ -87.383961, 46.593070 ], [ -87.381649, 46.580059 ], [ -87.392974, 46.572523 ], [ -87.392828, 46.570852 ], [ -87.382206, 46.553681 ], [ -87.375613, 46.547140 ], [ -87.390300, 46.542577 ], [ -87.393985, 46.533183 ], [ -87.389290, 46.524472 ], [ -87.381349, 46.517292 ], [ -87.366767, 46.507303 ], [ -87.351071, 46.500749 ], [ -87.310755, 46.492017 ], [ -87.258732, 46.488255 ], [ -87.202404, 46.490827 ], [ -87.175065, 46.497548 ], [ -87.127440, 46.494014 ], [ -87.107559, 46.496124 ], [ -87.098760, 46.503609 ], [ -87.077279, 46.515339 ], [ -87.046022, 46.519956 ], [ -87.029892, 46.525599 ], [ -87.017136, 46.533550 ], [ -87.008724, 46.532723 ], [ -86.976958, 46.526581 ], [ -86.964534, 46.516549 ], [ -86.962842, 46.509646 ], [ -86.946980, 46.484567 ], [ -86.946218, 46.479059 ], [ -86.949526, 46.476315 ], [ -86.947077, 46.472064 ], [ -86.927725, 46.464566 ], [ -86.903742, 46.466138 ], [ -86.889094, 46.458499 ], [ -86.883976, 46.450976 ], [ -86.883919, 46.441514 ], [ -86.875151, 46.437280 ], [ -86.850111, 46.434114 ], [ -86.837448, 46.434186 ], [ -86.816026, 46.437892 ], [ -86.810967, 46.449663 ], [ -86.808817, 46.460611 ], [ -86.803557, 46.466669 ], [ -86.787905, 46.477729 ], [ -86.768516, 46.479072 ], [ -86.750157, 46.479109 ], [ -86.735929, 46.475231 ], [ -86.731096, 46.471760 ], [ -86.730829, 46.468057 ], [ -86.710573, 46.444908 ], [ -86.703230, 46.439378 ], [ -86.698139, 46.438624 ], [ -86.686412, 46.454965 ], [ -86.688816, 46.463152 ], [ -86.686468, 46.471655 ], [ -86.683819, 46.498079 ], [ -86.696001, 46.503160 ], [ -86.701929, 46.511571 ], [ -86.709325, 46.543914 ], [ -86.695645, 46.555026 ], [ -86.678182, 46.561039 ], [ -86.675764, 46.557061 ], [ -86.670927, 46.556489 ], [ -86.656479, 46.558453 ], [ -86.652865, 46.560555 ], [ -86.627380, 46.533710 ], [ -86.629086, 46.518144 ], [ -86.632109, 46.508865 ], [ -86.634530, 46.504523 ], [ -86.641088, 46.500438 ], [ -86.645528, 46.492039 ], [ -86.646393, 46.485776 ], [ -86.636671, 46.478298 ], [ -86.627441, 46.477540 ], [ -86.620603, 46.483873 ], [ -86.618061, 46.489452 ], [ -86.612173, 46.493295 ], [ -86.609393, 46.492976 ], [ -86.606932, 46.478531 ], [ -86.609039, 46.470239 ], [ -86.586168, 46.463324 ], [ -86.557731, 46.487434 ], [ -86.524959, 46.505381 ], [ -86.495054, 46.524874 ], [ -86.484003, 46.535965 ], [ -86.481956, 46.542709 ], [ -86.469306, 46.551422 ], [ -86.459930, 46.551928 ], [ -86.444390, 46.548137 ], [ -86.437167, 46.548960 ], [ -86.390409, 46.563194 ], [ -86.349890, 46.578035 ], [ -86.188024, 46.654008 ], [ -86.161681, 46.669475 ], [ -86.138295, 46.672935 ], [ -86.119862, 46.657256 ], [ -86.112126, 46.655044 ], [ -86.099843, 46.654615 ], [ -86.074219, 46.657799 ], [ -86.036969, 46.667627 ], [ -85.995044, 46.673676 ], [ -85.953670, 46.676869 ], [ -85.924047, 46.684733 ], [ -85.877908, 46.690914 ], [ -85.841057, 46.688896 ], [ -85.794923, 46.681083 ], [ -85.750606, 46.677368 ], [ -85.714415, 46.677156 ], [ -85.668753, 46.680404 ], [ -85.624573, 46.678862 ], [ -85.587345, 46.674627 ], [ -85.542517, 46.674263 ], [ -85.509510, 46.675786 ], [ -85.482096, 46.680432 ], [ -85.369805, 46.713754 ], [ -85.289846, 46.744644 ], [ -85.256860, 46.753380 ], [ -85.173042, 46.763634 ], [ -85.063556, 46.757856 ], [ -85.036286, 46.760435 ], [ -85.009240, 46.769224 ], [ -84.989497, 46.772403 ], [ -84.964652, 46.772845 ], [ -84.954009, 46.771362 ], [ -84.951580, 46.769488 ], [ -84.987539, 46.745483 ], [ -85.007616, 46.728339 ], [ -85.020159, 46.712463 ], [ -85.027513, 46.697451 ], [ -85.030078, 46.684769 ], [ -85.028291, 46.675125 ], [ -85.035504, 46.625021 ], [ -85.037056, 46.600995 ], [ -85.035476, 46.581547 ], [ -85.031507, 46.568703 ], [ -85.029594, 46.554419 ], [ -85.027374, 46.553756 ], [ -85.025491, 46.546397 ], [ -85.027083, 46.543038 ], [ -85.045534, 46.537694 ], [ -85.052954, 46.532827 ], [ -85.056133, 46.526520 ], [ -85.054943, 46.514750 ], [ -85.049847, 46.503963 ], [ -85.033766, 46.487670 ], [ -85.025598, 46.483028 ], [ -85.015211, 46.479712 ], [ -84.969464, 46.476290 ], [ -84.955307, 46.480269 ], [ -84.947269, 46.487399 ], [ -84.937145, 46.489252 ], [ -84.934432, 46.480315 ], [ -84.921931, 46.469962 ], [ -84.915184, 46.467515 ], [ -84.893423, 46.465406 ], [ -84.875070, 46.466781 ], [ -84.861448, 46.469930 ], [ -84.849767, 46.460245 ], [ -84.843907, 46.448661 ], [ -84.829491, 46.444071 ], [ -84.800101, 46.446219 ], [ -84.769151, 46.453523 ], [ -84.723338, 46.468266 ], [ -84.689672, 46.483923 ], [ -84.678423, 46.487694 ], [ -84.653880, 46.482250 ], [ -84.631020, 46.484868 ], [ -84.616489, 46.471870 ], [ -84.607945, 46.456747 ], [ -84.584167, 46.439410 ], [ -84.573522, 46.427895 ], [ -84.551496, 46.418522 ], [ -84.503719, 46.439190 ], [ -84.493401, 46.440313 ], [ -84.479513, 46.432573 ], [ -84.471848, 46.434289 ], [ -84.462597, 46.440940 ], [ -84.455527, 46.453897 ], [ -84.455256, 46.462785 ], [ -84.463322, 46.467435 ] ] }"; + OperatorImportFromGeoJson importerGeoJson = (OperatorImportFromGeoJson) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.ImportFromGeoJson); + Geometry geom = importerGeoJson.execute(0, Geometry.Type.Unknown, geoJSON, null).getGeometry(); + + + double distance = 9000; + OperatorGeodesicBuffer opBuf = (OperatorGeodesicBuffer) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.GeodesicBuffer); + Polygon poly = (Polygon) opBuf.execute(geom, sr, GeodeticCurveType.Geodesic, distance, 100.0, false, null); + + String words = GeometryEngine.geometryToWkt(poly, 0); + assertTrue(poly.getType() == Geometry.Type.Polygon); + } + + @Test + public void testTriangleLength() { + Point pt_0 = new Point(10, 10); + Point pt_1 = new Point(20, 20); + Point pt_2 = new Point(20, 10); + double length = 0.0; + length += GeometryEngine.geodesicDistanceOnWGS84(pt_0, pt_1); + length += GeometryEngine.geodesicDistanceOnWGS84(pt_1, pt_2); + length += GeometryEngine.geodesicDistanceOnWGS84(pt_2, pt_0); + assertTrue(Math.abs(length - 3744719.4094597572) < 1e-13 * 3744719.4094597572); + } + + + @Test + public void testGeodesicForward() { + double latitude = 0; + double longitude = 0; + double distance = 1000; + double azimuth = 0; + double a = 6378137.0; // radius of spheroid for WGS_1984 + double e2 = 0.0066943799901413165; // ellipticity for WGS_1984 + double rpu = Math.PI / 180.0; + PeDouble lam2 = new PeDouble(); + PeDouble phi2 = new PeDouble(); + GeoDist.geodesic_forward(a, e2, longitude, latitude, distance, azimuth, lam2, phi2); + assertEquals(longitude, lam2.val / rpu, 0.00001); + } + + @Test + public void testRotationInvariance() { + Point pt_0 = new Point(10, 40); + Point pt_1 = new Point(20, 60); + Point pt_2 = new Point(20, 40); + double length = 0.0; + length += GeometryEngine.geodesicDistanceOnWGS84(pt_0, pt_1); + length += GeometryEngine.geodesicDistanceOnWGS84(pt_1, pt_2); + length += GeometryEngine.geodesicDistanceOnWGS84(pt_2, pt_0); + assertTrue(Math.abs(length - 5409156.3896271614) < 1e-13 * 5409156.3896271614); + + for (int i = -540; i < 540; i += 5) { + pt_0.setXY(i + 10, 40); + pt_1.setXY(i + 20, 60); + pt_2.setXY(i + 20, 40); + length = 0.0; + length += GeometryEngine.geodesicDistanceOnWGS84(pt_0, pt_1); + length += GeometryEngine.geodesicDistanceOnWGS84(pt_1, pt_2); + length += GeometryEngine.geodesicDistanceOnWGS84(pt_2, pt_0); + assertTrue(Math.abs(length - 5409156.3896271614) < 1e-13 * 5409156.3896271614); + } + } + + @Test + public void testDistanceFailure() { + { + Point p1 = new Point(-60.668485, -31.996013333333334); + Point p2 = new Point(119.13731666666666, 32.251583333333336); + double d = GeometryEngine.geodesicDistanceOnWGS84(p1, p2); + assertTrue(Math.abs(d - 19973410.50579736) < 1e-13 * 19973410.50579736); + } + + { + Point p1 = new Point(121.27343833333333, 27.467438333333334); + Point p2 = new Point(-58.55804833333333, -27.035613333333334); + double d = GeometryEngine.geodesicDistanceOnWGS84(p1, p2); + assertTrue(Math.abs(d - 19954707.428360686) < 1e-13 * 19954707.428360686); + } + + { + Point p1 = new Point(-53.329865, -36.08110166666667); + Point p2 = new Point(126.52895166666667, 35.97385); + double d = GeometryEngine.geodesicDistanceOnWGS84(p1, p2); + assertTrue(Math.abs(d - 19990586.700431127) < 1e-13 * 19990586.700431127); + } + + { + Point p1 = new Point(-4.7181166667, 36.1160166667); + Point p2 = new Point(175.248925, -35.7606716667); + double d = GeometryEngine.geodesicDistanceOnWGS84(p1, p2); + assertTrue(Math.abs(d - 19964450.206594173) < 1e-12 * 19964450.206594173); + } + } + + @Test + public void testDensifyPolyline() { + SpatialReference sr = SpatialReference.create(4326); + { + Polyline polyline = new Polyline(); + polyline.startPath(0, 0); + polyline.lineTo(4, 4); + polyline.lineTo(4, 8); + polyline.lineTo(8, 20); + + OperatorGeodeticDensifyByLength op = (OperatorGeodeticDensifyByLength) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.GeodeticDensifyByLength); + Polyline polylineDense = (Polyline) op.execute(polyline, sr, 5000, GeodeticCurveType.Geodesic, null); + assertEquals(polyline.calculateLength2D(), polylineDense.calculateLength2D(), .001); + assertEquals(polyline.getPoint(polyline.getPointCount() - 1).getX(), polylineDense.getPoint(polylineDense.getPointCount() - 1).getX()); + assertEquals(polyline.getPoint(polyline.getPointCount() - 1).getY(), polylineDense.getPoint(polylineDense.getPointCount() - 1).getY()); + assertEquals(polyline.getPoint(0).getX(), polylineDense.getPoint(0).getX()); + assertEquals(polyline.getPoint(0).getY(), polylineDense.getPoint(0).getY()); + + polyline.startPath(-2, -2); + polyline.lineTo(-4, -4); + polyline.lineTo(-8, -8); + polylineDense = (Polyline) op.execute(polyline, sr, 5000, GeodeticCurveType.Geodesic, null); + assertEquals(polyline.calculateLength2D(), polylineDense.calculateLength2D(), .001); + assertEquals(polyline.calculatePathLength2D(0), polylineDense.calculatePathLength2D(0), .001); + assertEquals(polyline.calculatePathLength2D(1), polylineDense.calculatePathLength2D(1), .001); + } + + { + Polyline polyline = new Polyline(); + polyline.startPath(0, 0); + polyline.lineTo(0, 1); + OperatorGeodeticDensifyByLength op = (OperatorGeodeticDensifyByLength) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.GeodeticDensifyByLength); + Polyline polylineDense = (Polyline) op.execute(polyline, sr, 100000, GeodeticCurveType.Geodesic, null); + assertEquals(polyline.getPoint(polyline.getPointCount() - 1).getX(), polylineDense.getPoint(polylineDense.getPointCount() - 1).getX()); + assertEquals(polyline.getPoint(polyline.getPointCount() - 1).getY(), polylineDense.getPoint(polylineDense.getPointCount() - 1).getY()); + assertEquals(polyline.getPoint(0).getX(), polylineDense.getPoint(0).getX()); + assertEquals(polyline.getPoint(0).getY(), polylineDense.getPoint(0).getY()); + assertEquals(3, polylineDense.getPointCount()); + + assertEquals( + GeometryEngine.geodesicDistanceOnWGS84( + polyline.getPoint(0), + polylineDense.getPoint(1)) + + GeometryEngine.geodesicDistanceOnWGS84( + polylineDense.getPoint(1), + polyline.getPoint(1)), + GeometryEngine.geodesicDistanceOnWGS84( + polyline.getPoint(0), + polyline.getPoint(1)) + ); + } + + { + Polyline polyline = new Polyline(); + polyline.startPath(0, 0); + polyline.lineTo(1, 0); + polyline.lineTo(2, 0); + OperatorGeodeticDensifyByLength op = (OperatorGeodeticDensifyByLength) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.GeodeticDensifyByLength); + Polyline polylineDense = (Polyline) op.execute(polyline, sr, 100000, GeodeticCurveType.Geodesic, null); + assertEquals(polyline.getPoint(polyline.getPointCount() - 1).getX(), polylineDense.getPoint(polylineDense.getPointCount() - 1).getX()); + assertEquals(polyline.getPoint(polyline.getPointCount() - 1).getY(), polylineDense.getPoint(polylineDense.getPointCount() - 1).getY()); + assertEquals(polyline.getPoint(0).getX(), polylineDense.getPoint(0).getX()); + assertEquals(polyline.getPoint(0).getY(), polylineDense.getPoint(0).getY()); + assertEquals(5, polylineDense.getPointCount()); + + assertEquals(polyline.getPoint(1).getX(), polylineDense.getPoint(2).getX()); + assertEquals(polyline.getPoint(1).getY(), polylineDense.getPoint(2).getY()); + assertEquals( + GeometryEngine.geodesicDistanceOnWGS84( + polyline.getPoint(0), + polylineDense.getPoint(1)) + + GeometryEngine.geodesicDistanceOnWGS84( + polylineDense.getPoint(1), + polyline.getPoint(1)), + GeometryEngine.geodesicDistanceOnWGS84( + polyline.getPoint(0), + polyline.getPoint(1)), + .0000001 + ); + } + + { + Polyline polyline = new Polyline(); + polyline.startPath(0, 0); + polyline.lineTo(4, 4); + polyline.lineTo(4, 8); + polyline.lineTo(8, 20); + double max_distance = 55000 / 3.5; + OperatorGeodeticDensifyByLength op = (OperatorGeodeticDensifyByLength) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.GeodeticDensifyByLength); + Polyline polylineDense = (Polyline) op.execute(polyline, sr, max_distance, GeodeticCurveType.Geodesic, null); + String words = GeometryEngine.geometryToWkt(polylineDense, 0); + assertEquals( + GeometryEngine.geodesicDistanceOnWGS84( + polyline.getPoint(0), + polyline.getPoint(polyline.getPointCount() - 1)), + GeometryEngine.geodesicDistanceOnWGS84( + polylineDense.getPoint(0), + polylineDense.getPoint(polylineDense.getPointCount() - 1)), + .0000001 + ); + } + } + + @Test + public void testDensifyPolygon() { + { + Polygon polygon = new Polygon(); + polygon.startPath(0, 0); + polygon.lineTo(0, 4); + polygon.lineTo(4, 4); + polygon.lineTo(4, 0); + polygon.closeAllPaths(); + SpatialReference sr = SpatialReference.create(4326); + OperatorGeodeticDensifyByLength op = (OperatorGeodeticDensifyByLength) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.GeodeticDensifyByLength); + Polygon polygonDense = (Polygon) op.execute(polygon, sr, 5000, GeodeticCurveType.Geodesic, null); + assertEquals(polygon.calculateLength2D(), polygonDense.calculateLength2D(), .005); + assertEquals(polygon.calculateArea2D(), polygonDense.calculateArea2D(), 0.007); + + polygon.startPath(-2, -2); + polygon.lineTo(-4, -4); + polygon.lineTo(-8, -8); + polygon.closeAllPaths(); + polygonDense = (Polygon) op.execute(polygon, sr, 5000, GeodeticCurveType.Geodesic, null); + assertEquals(polygon.calculateLength2D(), polygonDense.calculateLength2D(), .004); + assertEquals(polygon.calculateArea2D(), polygonDense.calculateArea2D(), 0.1); + } + } + + @Test + public void testInflateEnv2D() { + Envelope2D envOrig = new Envelope2D(0, -4, 4, 8); + Envelope2D env2D = new Envelope2D(0, -4, 4, 8); + + double a = 6378137.0; // radius of spheroid for WGS_1984 + double e2 = 0.0066943799901413165; // ellipticity for WGS_1984 + + GeoDist.inflateEnv2D(a, e2, env2D, 1000, 2000); + assertTrue(env2D.xmin < envOrig.xmin); + assertTrue(env2D.ymax > envOrig.ymax); + assertTrue(env2D.xmax > envOrig.xmax); + assertTrue(env2D.ymin < envOrig.ymin); + } + + @Test + public void testDeflateEnv2D() { + Envelope2D envOrig = new Envelope2D(0, -4, 4, 8); + Envelope2D env2D = new Envelope2D(0, -4, 4, 8); + + double a = 6378137.0; // radius of spheroid for WGS_1984 + double e2 = 0.0066943799901413165; // ellipticity for WGS_1984 + + GeoDist.inflateEnv2D(a, e2, env2D, -100, 2000); + assertTrue(env2D.xmin > envOrig.xmin); + assertTrue(env2D.ymax > envOrig.ymax); + assertTrue(env2D.xmax < envOrig.xmax); + assertTrue(env2D.ymin < envOrig.ymin); + } + + @Test + public void testVicenty() { + // test data from + // http://geographiclib.sourceforge.net/cgi-bin/GeodSolve + + double a = 6378137.0; // radius of spheroid for WGS_1984 + double e2 = 0.0066943799901413165; // ellipticity for WGS_1984 + double rpu = Math.PI / 180.0; + double dpu = 180.0 / Math.PI; + double distance = 2000.0; + { + Point p1 = new Point(0.0, 0.0); + PeDouble lam = new PeDouble(); + PeDouble phi = new PeDouble(); + GeoDist.geodesic_forward(a, e2, p1.getX() * rpu, p1.getY() * rpu, distance, 0.0 * rpu, lam, phi); + assertEquals(0.0, lam.val * dpu, 0.000001); + assertEquals(0.01808739, phi.val * dpu, 0.000001); + + PeDouble actualDistance = new PeDouble(); + GeoDist.geodesic_distance_ngs(a, e2, p1.getX() * rpu, p1.getY() * rpu, lam.val, phi.val, actualDistance, null, null); + assertEquals(actualDistance.val, distance, .02); + + } + { + Point p1 = new Point(45.0, 45.0); + PeDouble lam = new PeDouble(); + PeDouble phi = new PeDouble(); + GeoDist.geodesic_forward(a, e2, p1.getX() * rpu, p1.getY() * rpu, distance, 20.0 * rpu, lam, phi); + + assertEquals(45.01691097, phi.val * dpu, 0.000001); + assertEquals(45.00867811, lam.val * dpu, 0.000001); + } + { + Point p1 = new Point(60.0, 45.0); + PeDouble lam = new PeDouble(); + PeDouble phi = new PeDouble(); + GeoDist.geodesic_forward(a, e2, p1.getX() * rpu, p1.getY() * rpu, distance, 20.0 * rpu, lam, phi); + + //45.01691097 + assertEquals(45.01691097, phi.val * dpu, 0.000001); + assertEquals(60.00867811, lam.val * dpu, 0.000001); + } + { + Point p1 = new Point(-65.0, -45.0); + PeDouble lam = new PeDouble(); + PeDouble phi = new PeDouble(); + GeoDist.geodesic_forward(a, e2, p1.getX() * rpu, p1.getY() * rpu, distance, -20.0 * rpu, lam, phi); + + //-44.98308832 -65.00867301 + assertEquals(-44.98308832, phi.val * dpu, 0.000001); + assertEquals(-65.00867301, lam.val * dpu, 0.000001); + } + { + Point p1 = new Point(-165.0, -45.0); + PeDouble lam = new PeDouble(); + PeDouble phi = new PeDouble(); + GeoDist.geodesic_forward(a, e2, p1.getX() * rpu, p1.getY() * rpu, distance, 220.0 * rpu, lam, phi); + + //-45.01378505 -165.01630863 + assertEquals(-45.01378505, phi.val * dpu, 0.000001); + assertEquals(-165.01630863, lam.val * dpu, 0.000001); + } + } + + @Test + public void testGeodeticBufferPoint() { + { + SpatialReference sr = SpatialReference.create(4326); + Point p1 = new Point(0.0, 0.0); + OperatorGeodesicBuffer opBuf = (OperatorGeodesicBuffer) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.GeodesicBuffer); + double distance = 1000; + Polygon poly = (Polygon) opBuf.execute(p1, sr, GeodeticCurveType.Geodesic, distance, 0.1, false, null); + //String words = GeometryEngine.geometryToWkt(poly, 0); + assertNotNull(poly); + assertTrue(poly.getType() == Geometry.Type.Polygon); + double area = poly.calculateArea2D(); + assertEquals(2.550450219554701E-4, area, 0.0000000001); + assertEquals(97, poly.getPointCount()); + + assertTrue(OperatorContains.local().execute(poly, p1, sr, null)); + } + } + + @Test + public void testGeodeticBufferMultiPoint() { + { + SpatialReference sr = SpatialReference.create(4326); + MultiPoint mp = new MultiPoint(); + mp.add(0.0, 0.0); + mp.add(20.0, 0.0); + OperatorGeodesicBuffer opBuf = (OperatorGeodesicBuffer) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.GeodesicBuffer); + double distance = 100000; + Polygon poly = (Polygon) opBuf.execute(mp, sr, GeodeticCurveType.Geodesic, distance, 150, false, null); + String words = GeometryEngine.geometryToWkt(poly, 0); + assertNotNull(poly); + assertTrue(poly.getType() == Geometry.Type.Polygon); + assertEquals(2, poly.getPathCount()); + double area = poly.calculateArea2D(); + assertEquals(5.095268886272399, area, 0.0000000001); + assertEquals(120, poly.getPointCount()); + + assertTrue(OperatorContains.local().execute(poly, mp, sr, null)); + } + } + + @Test + public void testGeodeticBufferPolyline() { + { + Polyline polyline = new Polyline(); + polyline.startPath(0, 0); + polyline.lineTo(4, 4); + polyline.lineTo(4, 8); + polyline.lineTo(8, 20); + SpatialReference sr = SpatialReference.create(4326); + + OperatorBuffer opBufNorm = (OperatorBuffer) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.Buffer); + Polygon polyNorm = (Polygon) opBufNorm.execute(polyline, sr, .7, null); + + + String words = GeometryEngine.geometryToWkt(polyline, 0); + double distance = 55000; + OperatorGeodesicBuffer opBuf = (OperatorGeodesicBuffer) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.GeodesicBuffer); + Polygon poly = (Polygon) opBuf.execute(polyline, sr, GeodeticCurveType.Geodesic, distance, 150.0, false, null); + + words = GeometryEngine.geometryToWkt(poly, 0); + assertNotNull(poly); + assertTrue(poly.getType() == Geometry.Type.Polygon); + double area = poly.calculateArea2D(); + assertEquals(23.296270856192834, area, 0.00000000001); + } + } + + @Test + public void testBufferGeodeticPolyline2() { + SpatialReference sr = SpatialReference.create(4326); + Polyline inputGeom = new Polyline(); + OperatorGeodesicBuffer buffer = (OperatorGeodesicBuffer) OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.GeodesicBuffer); + OperatorSimplify simplify = (OperatorSimplify) OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Simplify); + inputGeom.startPath(0, 0); + inputGeom.lineTo(50, 50); + inputGeom.lineTo(50, 0); + inputGeom.lineTo(0, 50); + + { + Geometry result = buffer.execute(inputGeom, sr, 0, 0, 0, false, null); + assertTrue(result.getType().value() == Geometry.GeometryType.Polygon); + assertTrue(result.isEmpty()); + } + + { + Geometry result = buffer.execute(inputGeom, sr, 0, -1, 0, false, null); + assertTrue(result.getType().value() == Geometry.GeometryType.Polygon); + assertTrue(result.isEmpty()); + } + + { + String words = GeometryEngine.geometryToWkt(inputGeom, 0); + Geometry result = buffer.execute(inputGeom, sr, 0, 40.0, 50, false, null); + assertTrue(result.getType().value() == Geometry.GeometryType.Polygon); + Polygon poly = (Polygon) (result); + + words = GeometryEngine.geometryToWkt(poly, 0); // Envelope2D env2D = new Envelope2D(); // result.queryEnvelope2D(env2D); // assertTrue(Math.abs(env2D.getWidth() - 80 - 50) < 0.1 // && Math.abs(env2D.getHeight() - 80 - 50) < 0.1); // assertTrue(Math.abs(env2D.getCenterX() - 25) < 0.1 // && Math.abs(env2D.getCenterY() - 25) < 0.1); - int pathCount = poly.getPathCount(); - // should have a hole in it - assertEquals(pathCount, 2); + int pathCount = poly.getPathCount(); + // should have a hole in it + assertEquals(pathCount, 2); - assertTrue(simplify.isSimpleAsFeature(result, sr, null)); - } + assertTrue(simplify.isSimpleAsFeature(result, sr, null)); + } - { - String words = GeometryEngine.geometryToWkt(inputGeom, 0); - Geometry result = buffer.execute(inputGeom, sr, 0, 3000000.0, 50, false, null); - assertTrue(result.getType().value() == Geometry.GeometryType.Polygon); - Polygon poly = (Polygon) (result); + { + String words = GeometryEngine.geometryToWkt(inputGeom, 0); + Geometry result = buffer.execute(inputGeom, sr, 0, 3000000.0, 50, false, null); + assertTrue(result.getType().value() == Geometry.GeometryType.Polygon); + Polygon poly = (Polygon) (result); - words = GeometryEngine.geometryToWkt(poly, 0); + words = GeometryEngine.geometryToWkt(poly, 0); // Envelope2D env2D = new Envelope2D(); // result.queryEnvelope2D(env2D); // assertTrue(Math.abs(env2D.getWidth() - 80 - 50) < 0.1 // && Math.abs(env2D.getHeight() - 80 - 50) < 0.1); // assertTrue(Math.abs(env2D.getCenterX() - 25) < 0.1 // && Math.abs(env2D.getCenterY() - 25) < 0.1); - int pathCount = poly.getPathCount(); - // should have a hole in it - assertEquals(pathCount, 1); + int pathCount = poly.getPathCount(); + // should have a hole in it + assertEquals(pathCount, 1); - assertTrue(simplify.isSimpleAsFeature(result, sr, null)); - } + assertTrue(simplify.isSimpleAsFeature(result, sr, null)); + } // // { // Geometry result = buffer.execute(inputGeom, sr, 4.0, null); @@ -506,169 +506,169 @@ public void testBufferGeodeticPolyline2() { // assertTrue(Math.abs(pointCount - 208.0) < 10); // assertTrue(simplify.isSimpleAsFeature(result, sr, null)); // } - } - - @Test - public void testGeodeticBufferSegment() { - { - Polyline polyline = new Polyline(); - polyline.startPath(0, 0); - polyline.lineTo(4, 4); - SegmentIterator segmentIterator = polyline.querySegmentIterator(); - segmentIterator.nextPath(); - Segment segment = segmentIterator.nextSegment(); - SpatialReference sr = SpatialReference.create(4326); - - - double distance = 55000; - OperatorGeodesicBuffer opBuf = (OperatorGeodesicBuffer) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.GeodesicBuffer); - Polygon poly = (Polygon) opBuf.execute(segment, sr, GeodeticCurveType.Geodesic, distance, 300.0, false, null); - - String words = GeometryEngine.geometryToWkt(poly, 0); - assertNotNull(poly); - assertTrue(poly.getType() == Geometry.Type.Polygon); - double area = poly.calculateArea2D(); - assertEquals(6.379702184244028, area, .0001); - } - } - - @Test - public void testBufferArcs() { - Polyline polyline = new Polyline(); - polyline.startPath(5, 25); - polyline.lineTo(10, 32); - SpatialReference sr = SpatialReference.create(4326); - OperatorGeodesicBuffer op = (OperatorGeodesicBuffer) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.GeodesicBuffer); - Polygon poly = (Polygon) op.execute(polyline, sr, 0, 3000, 500, false, null); - String words = GeometryEngine.geometryToWkt(poly, 0); - assertEquals(13, poly.getPointCount()); - } - - @Test - public void testPolygonBoundaryBug() { - SpatialReference sr = SpatialReference.create(4326); - OperatorImportFromWkt opWKT = (OperatorImportFromWkt) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.ImportFromWkt); - OperatorGeodesicBuffer opBuf = (OperatorGeodesicBuffer) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.GeodesicBuffer); - double distance = 59000; - { - String wkt = "MULTILINESTRING ((5 0,3 38,3.9 37.7,4 40,30 10,5 0))"; - Geometry geom = opWKT.execute(0, Geometry.Type.Polyline, wkt, null); - Polygon poly = (Polygon) opBuf.execute(geom, sr, GeodeticCurveType.Geodesic, distance, 2000, false, null); - String words = GeometryEngine.geometryToWkt(poly, 0); - Envelope2D env2D = new Envelope2D(); - poly.queryEnvelope2D(env2D); - assertTrue(env2D.xmin < 3); - assertTrue(env2D.ymin < 0); - assertTrue(env2D.xmax > 30); - assertTrue(env2D.ymax > 40); - assertEquals(Geometry.Type.Polygon, poly.getType()); - assertEquals(24, poly.getPointCount()); - } - { - String wkt = "MULTILINESTRING ((15 5, 5 10, 10 20, 10 30, 16.666666666666664 33.333333333333329, 10 40, 20 40, 28.333333333333339 40, 20 45, 40 40, 45 40, 42 36, 45 30, 39.827586206896555 33.103448275862071, 36 28, 35.277777777777779 25.833333333333332, 45 20, 36.25 11.25, 40 10, 33.75 8.7500000000000018, 30 5, 23.333333333333336 6.6666666666666661, 15 5))"; - Geometry geom = opWKT.execute(0, Geometry.Type.Polyline, wkt, null); - Polygon poly = (Polygon) opBuf.execute(geom, sr, GeodeticCurveType.Geodesic, distance, 2000, false, null); - Envelope2D env2D = new Envelope2D(); - poly.queryEnvelope2D(env2D); - assertTrue(env2D.xmin < 5); - assertTrue(env2D.ymin < 5); - assertTrue(env2D.xmax > 45); - assertTrue(env2D.ymax > 45); - distance = 200000; - poly = (Polygon) opBuf.execute(geom, sr, GeodeticCurveType.Geodesic, distance, 2000, false, null); - env2D = new Envelope2D(); - poly.queryEnvelope2D(env2D); - assertTrue(env2D.xmin < 5); - assertTrue(env2D.ymin < 5); - assertTrue(env2D.xmax > 45); - assertTrue(env2D.ymax > 45); - distance = 20045; - poly = (Polygon) opBuf.execute(geom, sr, GeodeticCurveType.Geodesic, distance, 200, false, null); - env2D = new Envelope2D(); - poly.queryEnvelope2D(env2D); - assertTrue(env2D.xmin < 5); - assertTrue(env2D.ymin < 5); - assertTrue(env2D.xmax > 45); - assertTrue(env2D.ymax > 45); - } - } - - @Test - public void testImperfectArcEndings() throws Exception { - SpatialReference sr = SpatialReference.create(4326); - String wkt = "MULTILINESTRING ((79.599689290259803 80.056892196564064, 79.679967837449936 80.045572571657544, 79.760065736245338 80.034233827076164, 79.839983299288846 80.022876027925264, 79.919720840553722 80.011499239123168, 80 80))"; - Polyline polyline = new Polyline(); - polyline.startPath(79, 80); - polyline.lineTo(82, 82); - OperatorImportFromWkt opWKT = (OperatorImportFromWkt) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.ImportFromWkt); - Geometry geom = opWKT.execute(0, Geometry.Type.Polyline, wkt, null); - OperatorGeodesicBuffer opBuf = (OperatorGeodesicBuffer) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.GeodesicBuffer); - double distance = 300; - Polygon poly = (Polygon) opBuf.execute(polyline, sr, GeodeticCurveType.Geodesic, distance, 5, false, null); - String words = GeometryEngine.geometryToWkt(poly, 0); - assertEquals(Geometry.Type.Polygon, poly.getType()); - assertEquals(21, poly.getPointCount()); - } - - @Test - public void testPolygon() { - SpatialReference sr = SpatialReference.create(4326); - OperatorImportFromWkt opWKT = (OperatorImportFromWkt) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.ImportFromWkt); - OperatorGeodesicBuffer opBuf = (OperatorGeodesicBuffer) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.GeodesicBuffer); - String wkt = "MULTIPOLYGON (((15 5, 23.333333333333336 6.6666666666666661, 30 5, 33.75 8.7500000000000018, 40 10, 36.25 11.25, 45 20, 35.277777777777779 25.833333333333332, 36 28, 39.827586206896555 33.103448275862071, 45 30, 42 36, 45 40, 40 40, 20 45, 28.333333333333339 40, 20 40, 10 40, 16.666666666666664 33.333333333333329, 10 30, 10 20, 5 10, 15 5)))"; - Geometry geom = opWKT.execute(0, Geometry.Type.Polygon, wkt, null); - Envelope2D env2DOrig = new Envelope2D(); - geom.queryEnvelope2D(env2DOrig); - double distance = 300; - Polygon poly = (Polygon) opBuf.execute(geom, sr, GeodeticCurveType.Geodesic, distance, 5, false, null); - Envelope2D env2DPositiveBuff = new Envelope2D(); - poly.queryEnvelope2D(env2DPositiveBuff); - assertTrue(env2DPositiveBuff.xmax > env2DOrig.xmax); - assertTrue(env2DPositiveBuff.ymax > env2DOrig.ymax); - assertTrue(env2DPositiveBuff.xmin < env2DOrig.xmin); - assertTrue(env2DPositiveBuff.ymin < env2DOrig.ymin); - - distance = -300; - poly = (Polygon) opBuf.execute(geom, sr, GeodeticCurveType.Geodesic, distance, 5, false, null); - Envelope2D env2DNegativeBuff = new Envelope2D(); - poly.queryEnvelope2D(env2DNegativeBuff); - assertTrue(env2DNegativeBuff.xmax < env2DOrig.xmax); - assertTrue(env2DNegativeBuff.ymax < env2DOrig.ymax); - assertTrue(env2DNegativeBuff.xmin > env2DOrig.xmin); - assertTrue(env2DNegativeBuff.ymin > env2DOrig.ymin); - } - - @Test - public void testEnvelop() { - SpatialReference sr = SpatialReference.create(4326); - OperatorImportFromWkt opWKT = (OperatorImportFromWkt) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.ImportFromWkt); - OperatorGeodesicBuffer opBuf = (OperatorGeodesicBuffer) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.GeodesicBuffer); - String wkt = "MULTIPOLYGON (((15 5, 23.333333333333336 6.6666666666666661, 30 5, 33.75 8.7500000000000018, 40 10, 36.25 11.25, 45 20, 35.277777777777779 25.833333333333332, 36 28, 39.827586206896555 33.103448275862071, 45 30, 42 36, 45 40, 40 40, 20 45, 28.333333333333339 40, 20 40, 10 40, 16.666666666666664 33.333333333333329, 10 30, 10 20, 5 10, 15 5)))"; - Geometry geom = opWKT.execute(0, Geometry.Type.Polygon, wkt, null); - Envelope envOrig = new Envelope(); - geom.queryEnvelope(envOrig); - double distance = 300; - Polygon poly = (Polygon) opBuf.execute(envOrig, sr, GeodeticCurveType.Geodesic, distance, 5, false, null); - OperatorContains opContains = (OperatorContains) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.Contains); - assertTrue(opContains.execute(poly, geom, sr, null)); - String words = GeometryEngine.geometryToWkt(poly, 0); - assertTrue(opContains.execute(poly, envOrig, sr, null)); - } - - - @Test - public void testDegeneratePolyline() { - String wkt = "LINESTRING (0 0, 0 80)"; - SpatialReference sr = SpatialReference.create(4326); - OperatorImportFromWkt opWKT = (OperatorImportFromWkt) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.ImportFromWkt); - OperatorGeodesicBuffer opBuf = (OperatorGeodesicBuffer) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.GeodesicBuffer); - Geometry geom = opWKT.execute(0, Geometry.Type.Unknown, wkt, null); - double distance = 30000; - Polygon poly = (Polygon) opBuf.execute(geom, sr, GeodeticCurveType.Geodesic, distance, 500, false, null); - OperatorContains opContains = (OperatorContains) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.Contains); - String words = GeometryEngine.geometryToWkt(poly, 0); - assertTrue(opContains.execute(poly, geom, sr, null)); - } + } + + @Test + public void testGeodeticBufferSegment() { + { + Polyline polyline = new Polyline(); + polyline.startPath(0, 0); + polyline.lineTo(4, 4); + SegmentIterator segmentIterator = polyline.querySegmentIterator(); + segmentIterator.nextPath(); + Segment segment = segmentIterator.nextSegment(); + SpatialReference sr = SpatialReference.create(4326); + + + double distance = 55000; + OperatorGeodesicBuffer opBuf = (OperatorGeodesicBuffer) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.GeodesicBuffer); + Polygon poly = (Polygon) opBuf.execute(segment, sr, GeodeticCurveType.Geodesic, distance, 300.0, false, null); + + String words = GeometryEngine.geometryToWkt(poly, 0); + assertNotNull(poly); + assertTrue(poly.getType() == Geometry.Type.Polygon); + double area = poly.calculateArea2D(); + assertEquals(6.379702184244028, area, .0001); + } + } + + @Test + public void testBufferArcs() { + Polyline polyline = new Polyline(); + polyline.startPath(5, 25); + polyline.lineTo(10, 32); + SpatialReference sr = SpatialReference.create(4326); + OperatorGeodesicBuffer op = (OperatorGeodesicBuffer) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.GeodesicBuffer); + Polygon poly = (Polygon) op.execute(polyline, sr, 0, 3000, 500, false, null); + String words = GeometryEngine.geometryToWkt(poly, 0); + assertEquals(13, poly.getPointCount()); + } + + @Test + public void testPolygonBoundaryBug() { + SpatialReference sr = SpatialReference.create(4326); + OperatorImportFromWkt opWKT = (OperatorImportFromWkt) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.ImportFromWkt); + OperatorGeodesicBuffer opBuf = (OperatorGeodesicBuffer) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.GeodesicBuffer); + double distance = 59000; + { + String wkt = "MULTILINESTRING ((5 0,3 38,3.9 37.7,4 40,30 10,5 0))"; + Geometry geom = opWKT.execute(0, Geometry.Type.Polyline, wkt, null); + Polygon poly = (Polygon) opBuf.execute(geom, sr, GeodeticCurveType.Geodesic, distance, 2000, false, null); + String words = GeometryEngine.geometryToWkt(poly, 0); + Envelope2D env2D = new Envelope2D(); + poly.queryEnvelope2D(env2D); + assertTrue(env2D.xmin < 3); + assertTrue(env2D.ymin < 0); + assertTrue(env2D.xmax > 30); + assertTrue(env2D.ymax > 40); + assertEquals(Geometry.Type.Polygon, poly.getType()); + assertEquals(24, poly.getPointCount()); + } + { + String wkt = "MULTILINESTRING ((15 5, 5 10, 10 20, 10 30, 16.666666666666664 33.333333333333329, 10 40, 20 40, 28.333333333333339 40, 20 45, 40 40, 45 40, 42 36, 45 30, 39.827586206896555 33.103448275862071, 36 28, 35.277777777777779 25.833333333333332, 45 20, 36.25 11.25, 40 10, 33.75 8.7500000000000018, 30 5, 23.333333333333336 6.6666666666666661, 15 5))"; + Geometry geom = opWKT.execute(0, Geometry.Type.Polyline, wkt, null); + Polygon poly = (Polygon) opBuf.execute(geom, sr, GeodeticCurveType.Geodesic, distance, 2000, false, null); + Envelope2D env2D = new Envelope2D(); + poly.queryEnvelope2D(env2D); + assertTrue(env2D.xmin < 5); + assertTrue(env2D.ymin < 5); + assertTrue(env2D.xmax > 45); + assertTrue(env2D.ymax > 45); + distance = 200000; + poly = (Polygon) opBuf.execute(geom, sr, GeodeticCurveType.Geodesic, distance, 2000, false, null); + env2D = new Envelope2D(); + poly.queryEnvelope2D(env2D); + assertTrue(env2D.xmin < 5); + assertTrue(env2D.ymin < 5); + assertTrue(env2D.xmax > 45); + assertTrue(env2D.ymax > 45); + distance = 20045; + poly = (Polygon) opBuf.execute(geom, sr, GeodeticCurveType.Geodesic, distance, 200, false, null); + env2D = new Envelope2D(); + poly.queryEnvelope2D(env2D); + assertTrue(env2D.xmin < 5); + assertTrue(env2D.ymin < 5); + assertTrue(env2D.xmax > 45); + assertTrue(env2D.ymax > 45); + } + } + + @Test + public void testImperfectArcEndings() throws Exception { + SpatialReference sr = SpatialReference.create(4326); + String wkt = "MULTILINESTRING ((79.599689290259803 80.056892196564064, 79.679967837449936 80.045572571657544, 79.760065736245338 80.034233827076164, 79.839983299288846 80.022876027925264, 79.919720840553722 80.011499239123168, 80 80))"; + Polyline polyline = new Polyline(); + polyline.startPath(79, 80); + polyline.lineTo(82, 82); + OperatorImportFromWkt opWKT = (OperatorImportFromWkt) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.ImportFromWkt); + Geometry geom = opWKT.execute(0, Geometry.Type.Polyline, wkt, null); + OperatorGeodesicBuffer opBuf = (OperatorGeodesicBuffer) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.GeodesicBuffer); + double distance = 300; + Polygon poly = (Polygon) opBuf.execute(polyline, sr, GeodeticCurveType.Geodesic, distance, 5, false, null); + String words = GeometryEngine.geometryToWkt(poly, 0); + assertEquals(Geometry.Type.Polygon, poly.getType()); + assertEquals(21, poly.getPointCount()); + } + + @Test + public void testPolygon() { + SpatialReference sr = SpatialReference.create(4326); + OperatorImportFromWkt opWKT = (OperatorImportFromWkt) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.ImportFromWkt); + OperatorGeodesicBuffer opBuf = (OperatorGeodesicBuffer) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.GeodesicBuffer); + String wkt = "MULTIPOLYGON (((15 5, 23.333333333333336 6.6666666666666661, 30 5, 33.75 8.7500000000000018, 40 10, 36.25 11.25, 45 20, 35.277777777777779 25.833333333333332, 36 28, 39.827586206896555 33.103448275862071, 45 30, 42 36, 45 40, 40 40, 20 45, 28.333333333333339 40, 20 40, 10 40, 16.666666666666664 33.333333333333329, 10 30, 10 20, 5 10, 15 5)))"; + Geometry geom = opWKT.execute(0, Geometry.Type.Polygon, wkt, null); + Envelope2D env2DOrig = new Envelope2D(); + geom.queryEnvelope2D(env2DOrig); + double distance = 300; + Polygon poly = (Polygon) opBuf.execute(geom, sr, GeodeticCurveType.Geodesic, distance, 5, false, null); + Envelope2D env2DPositiveBuff = new Envelope2D(); + poly.queryEnvelope2D(env2DPositiveBuff); + assertTrue(env2DPositiveBuff.xmax > env2DOrig.xmax); + assertTrue(env2DPositiveBuff.ymax > env2DOrig.ymax); + assertTrue(env2DPositiveBuff.xmin < env2DOrig.xmin); + assertTrue(env2DPositiveBuff.ymin < env2DOrig.ymin); + + distance = -300; + poly = (Polygon) opBuf.execute(geom, sr, GeodeticCurveType.Geodesic, distance, 5, false, null); + Envelope2D env2DNegativeBuff = new Envelope2D(); + poly.queryEnvelope2D(env2DNegativeBuff); + assertTrue(env2DNegativeBuff.xmax < env2DOrig.xmax); + assertTrue(env2DNegativeBuff.ymax < env2DOrig.ymax); + assertTrue(env2DNegativeBuff.xmin > env2DOrig.xmin); + assertTrue(env2DNegativeBuff.ymin > env2DOrig.ymin); + } + + @Test + public void testEnvelop() { + SpatialReference sr = SpatialReference.create(4326); + OperatorImportFromWkt opWKT = (OperatorImportFromWkt) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.ImportFromWkt); + OperatorGeodesicBuffer opBuf = (OperatorGeodesicBuffer) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.GeodesicBuffer); + String wkt = "MULTIPOLYGON (((15 5, 23.333333333333336 6.6666666666666661, 30 5, 33.75 8.7500000000000018, 40 10, 36.25 11.25, 45 20, 35.277777777777779 25.833333333333332, 36 28, 39.827586206896555 33.103448275862071, 45 30, 42 36, 45 40, 40 40, 20 45, 28.333333333333339 40, 20 40, 10 40, 16.666666666666664 33.333333333333329, 10 30, 10 20, 5 10, 15 5)))"; + Geometry geom = opWKT.execute(0, Geometry.Type.Polygon, wkt, null); + Envelope envOrig = new Envelope(); + geom.queryEnvelope(envOrig); + double distance = 300; + Polygon poly = (Polygon) opBuf.execute(envOrig, sr, GeodeticCurveType.Geodesic, distance, 5, false, null); + OperatorContains opContains = (OperatorContains) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.Contains); + assertTrue(opContains.execute(poly, geom, sr, null)); + String words = GeometryEngine.geometryToWkt(poly, 0); + assertTrue(opContains.execute(poly, envOrig, sr, null)); + } + + + @Test + public void testDegeneratePolyline() { + String wkt = "LINESTRING (0 0, 0 80)"; + SpatialReference sr = SpatialReference.create(4326); + OperatorImportFromWkt opWKT = (OperatorImportFromWkt) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.ImportFromWkt); + OperatorGeodesicBuffer opBuf = (OperatorGeodesicBuffer) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.GeodesicBuffer); + Geometry geom = opWKT.execute(0, Geometry.Type.Unknown, wkt, null); + double distance = 30000; + Polygon poly = (Polygon) opBuf.execute(geom, sr, GeodeticCurveType.Geodesic, distance, 500, false, null); + OperatorContains opContains = (OperatorContains) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.Contains); + String words = GeometryEngine.geometryToWkt(poly, 0); + assertTrue(opContains.execute(poly, geom, sr, null)); + } // @Test @@ -714,51 +714,51 @@ public void testDegeneratePolyline() { // */ // } - @Test - public void testEnvelopeMidpoint() { - Envelope2D envelope2D = new Envelope2D(45, -10, 55, 10); - Point2D centerPoint = new Point2D(); - double a = 6378137.0; // radius of spheroid for WGS_1984 - double e2 = 0.0066943799901413165; // ellipticity for WGS_1984 - GeoDist.getEnvCenter(a, e2, envelope2D, centerPoint); - assertEquals(50, centerPoint.x, 1e-12); - assertEquals(0, centerPoint.y, 1e-4); - } - - @Test - public void testEnvelopeMidpointDateline() { - Envelope2D envelope2D = new Envelope2D(175, -10, -175, 10); - Point2D centerPoint = new Point2D(); - double a = 6378137.0; // radius of spheroid for WGS_1984 - double e2 = 0.0066943799901413165; // ellipticity for WGS_1984 - GeoDist.getEnvCenter(a, e2, envelope2D, centerPoint); - assertEquals(180, Math.abs(centerPoint.x), 1e-12); - assertEquals(0, centerPoint.y, 1e-4); - } - - @Test - public void testDifferentDatums() { - SpatialReference spatialReferenceWgs = SpatialReference.create(4326); - SpatialReference spatialReferenceNad = SpatialReference.create(4269); - - Polyline polyline = new Polyline(); - polyline.startPath(-172.54, 23.81); - polyline.lineTo(-47.74, 86.46); - - Geometry geometryW = OperatorGeodeticDensifyByLength.local().execute(polyline, spatialReferenceWgs, 5000, GeodeticCurveType.Geodesic, null); - Geometry geometryW2 = OperatorGeodeticDensifyByLength.local().execute(polyline, spatialReferenceWgs, 5000, GeodeticCurveType.Geodesic, null); - Geometry geometryN = OperatorGeodeticDensifyByLength.local().execute(polyline, spatialReferenceNad, 5000, GeodeticCurveType.Geodesic, null); - assertFalse(geometryN.equals(geometryW)); - assertTrue(geometryW.equals(geometryW2)); - - Geometry geometryWBuff = OperatorGeodesicBuffer.local().execute(polyline, spatialReferenceWgs, GeodeticCurveType.Geodesic, 200, 20, false, null); - Geometry geometryNBuff = OperatorGeodesicBuffer.local().execute(polyline, spatialReferenceNad, GeodeticCurveType.Geodesic, 200, 20, false, null); - assertFalse(geometryNBuff.equals(geometryWBuff)); - - } - - @Test - public void testProjectedGeodetic() { + @Test + public void testEnvelopeMidpoint() { + Envelope2D envelope2D = new Envelope2D(45, -10, 55, 10); + Point2D centerPoint = new Point2D(); + double a = 6378137.0; // radius of spheroid for WGS_1984 + double e2 = 0.0066943799901413165; // ellipticity for WGS_1984 + GeoDist.getEnvCenter(a, e2, envelope2D, centerPoint); + assertEquals(50, centerPoint.x, 1e-12); + assertEquals(0, centerPoint.y, 1e-4); + } + + @Test + public void testEnvelopeMidpointDateline() { + Envelope2D envelope2D = new Envelope2D(175, -10, -175, 10); + Point2D centerPoint = new Point2D(); + double a = 6378137.0; // radius of spheroid for WGS_1984 + double e2 = 0.0066943799901413165; // ellipticity for WGS_1984 + GeoDist.getEnvCenter(a, e2, envelope2D, centerPoint); + assertEquals(180, Math.abs(centerPoint.x), 1e-12); + assertEquals(0, centerPoint.y, 1e-4); + } + + @Test + public void testDifferentDatums() { + SpatialReference spatialReferenceWgs = SpatialReference.create(4326); + SpatialReference spatialReferenceNad = SpatialReference.create(4269); + + Polyline polyline = new Polyline(); + polyline.startPath(-172.54, 23.81); + polyline.lineTo(-47.74, 86.46); + + Geometry geometryW = OperatorGeodeticDensifyByLength.local().execute(polyline, spatialReferenceWgs, 5000, GeodeticCurveType.Geodesic, null); + Geometry geometryW2 = OperatorGeodeticDensifyByLength.local().execute(polyline, spatialReferenceWgs, 5000, GeodeticCurveType.Geodesic, null); + Geometry geometryN = OperatorGeodeticDensifyByLength.local().execute(polyline, spatialReferenceNad, 5000, GeodeticCurveType.Geodesic, null); + assertFalse(geometryN.equals(geometryW)); + assertTrue(geometryW.equals(geometryW2)); + + Geometry geometryWBuff = OperatorGeodesicBuffer.local().execute(polyline, spatialReferenceWgs, GeodeticCurveType.Geodesic, 200, 20, false, null); + Geometry geometryNBuff = OperatorGeodesicBuffer.local().execute(polyline, spatialReferenceNad, GeodeticCurveType.Geodesic, 200, 20, false, null); + assertFalse(geometryNBuff.equals(geometryWBuff)); + + } + + @Test + public void testProjectedGeodetic() { /* // POINT (4322181.519435114 3212199.338618969) proj4: "+proj=laea +lat_0=31.593750 +lon_0=-94.718750 +x_0=4321000 +y_0=3210000 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs" SpatialReferenceData spatialReferenceData = SpatialReferenceData.newBuilder().setProj4("+proj=laea +lat_0=31.593750 +lon_0=-94.718750 +x_0=4321000 +y_0=3210000 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs").build(); @@ -784,17 +784,17 @@ public void testProjectedGeodetic() { GeometryResponse geometryResponse1 = stub.geometryOperationUnary(geometryRequest1); assertTrue(geometryResponse1.getSpatialRelationship()); */ - Geometry point = GeometryEngine.geometryFromWkt("POINT (4322181.519435114 3212199.338618969)", 0, Geometry.Type.Unknown); - SpatialReference spatialReference = SpatialReference.createFromProj4("+proj=laea +lat_0=31.593750 +lon_0=-94.718750 +x_0=4321000 +y_0=3210000 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs"); - OperatorGeodesicBuffer operatorGeodesicBuffer = (OperatorGeodesicBuffer)OperatorFactoryLocal.getInstance().getOperator(Operator.Type.GeodesicBuffer); - Geometry buffered = operatorGeodesicBuffer.execute(point, spatialReference, GeodeticCurveType.Geodesic, 400, Double.NaN, false, null); + Geometry point = GeometryEngine.geometryFromWkt("POINT (4322181.519435114 3212199.338618969)", 0, Geometry.Type.Unknown); + SpatialReference spatialReference = SpatialReference.createFromProj4("+proj=laea +lat_0=31.593750 +lon_0=-94.718750 +x_0=4321000 +y_0=3210000 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs"); + OperatorGeodesicBuffer operatorGeodesicBuffer = (OperatorGeodesicBuffer) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.GeodesicBuffer); + Geometry buffered = operatorGeodesicBuffer.execute(point, spatialReference, GeodeticCurveType.Geodesic, 400, Double.NaN, false, null); - assertFalse(GeometryEngine.disjoint(buffered, point, spatialReference)); - } + assertFalse(GeometryEngine.disjoint(buffered, point, spatialReference)); + } - public void testLengthAccurateCR191313() { - /* + public void testLengthAccurateCR191313() { + /* * // random_test(); OperatorFactoryLocal engine = * OperatorFactoryLocal.getInstance(); //TODO: Make this: * OperatorShapePreservingLength geoLengthOp = @@ -811,139 +811,139 @@ public void testLengthAccurateCR191313() { * geoLengthOp.execute(polyline, spatialRef, null); * assertTrue(Math.abs(length - 2738362.3249366437) < 2e-9 * length); */ - } - - public void testGeodeticLength() { - Polyline polyline = new Polyline(); - polyline.startPath(0,0); - polyline.lineTo(1, 0); - double length = OperatorGeodeticLength.local().execute(polyline, SpatialReference.create(4326), GeodeticCurveType.Geodesic, null); - assertEquals(111319.4907932264, length); - - polyline = new Polyline(); - polyline.startPath(0,0); - polyline.lineTo(-1, 0); - length = OperatorGeodeticLength.local().execute(polyline, SpatialReference.create(4326), GeodeticCurveType.Geodesic, null); - assertEquals(111319.4907932264, length); - - polyline = new Polyline(); - polyline.startPath(179,0); - polyline.lineTo(-180, 0); - length = OperatorGeodeticLength.local().execute(polyline, SpatialReference.create(4326), GeodeticCurveType.Geodesic, null); - assertEquals(111319.4907932264, length, 14); - - polyline = new Polyline(); - polyline.startPath(-179,0); - polyline.lineTo(-180, 0); - length = OperatorGeodeticLength.local().execute(polyline, SpatialReference.create(4326), GeodeticCurveType.Geodesic, null); - assertEquals(111319.4907932264, length, 14); - - polyline = new Polyline(); - polyline.startPath(179,0); - polyline.lineTo(180, 0); - length = OperatorGeodeticLength.local().execute(polyline, SpatialReference.create(4326), GeodeticCurveType.Geodesic, null); - assertEquals(111319.4907932264, length, 14); - - polyline = new Polyline(); - polyline.startPath(180,0); - polyline.lineTo(179, 0); - length = OperatorGeodeticLength.local().execute(polyline, SpatialReference.create(4326), GeodeticCurveType.Geodesic, null); - assertEquals(111319.4907932264, length, 14); - - polyline = new Polyline(); - polyline.startPath(180,0); - polyline.lineTo(177, 0); - length = OperatorGeodeticLength.local().execute(polyline, SpatialReference.create(4326), GeodeticCurveType.Geodesic, null); - assertEquals(3 * 111319.4907932264, length, 14); - - polyline = new Polyline(); - polyline.startPath(0,90); - polyline.lineTo(0, 0); - length = OperatorGeodeticLength.local().execute(polyline, SpatialReference.create(4326), GeodeticCurveType.Geodesic, null); - assertEquals(10001.96572931 * 1000, length, 14); - - polyline = new Polyline(); - polyline.startPath(1,0); - polyline.lineTo(1, 90); - length = OperatorGeodeticLength.local().execute(polyline, SpatialReference.create(4326), GeodeticCurveType.Geodesic, null); - assertEquals(10001.96572931 * 1000, length, 14); - - polyline = new Polyline(); - polyline.startPath(1,90); - polyline.lineTo(1, 0); - polyline.lineTo(0, 0); - polyline.lineTo(0,90); - length = OperatorGeodeticLength.local().execute(polyline, SpatialReference.create(4326), GeodeticCurveType.Geodesic, null); - assertEquals(10001.96572931 * 1000 * 2 + 111319.4907932264, length, 14); - - Polygon polygon = new Polygon(); - polygon.startPath(1,90); - polygon.lineTo(1, 0); - polygon.lineTo(0, 0); - polygon.lineTo(0,90); - length = OperatorGeodeticLength.local().execute(polygon, SpatialReference.create(4326), GeodeticCurveType.Geodesic, null); - assertEquals(10001.96572931 * 1000 * 2 + 111319.4907932264, length, 14); - - - polygon = new Polygon(); - polygon.startPath(10,90); - polygon.lineTo(10, 0); - polygon.lineTo(0, 0); - polygon.lineTo(0,90); - polygon.closeAllPaths(); - length = OperatorGeodeticLength.local().execute(polygon, SpatialReference.create(4326), GeodeticCurveType.Geodesic, null); - assertEquals(2.11171263665557E7, length, 14); - - polygon = new Polygon(); - polygon.startPath(10,90); - polygon.lineTo(10, 0); - polygon.lineTo(0, 0); - polygon.lineTo(0,90); - polygon.startPath(8, 3); - polygon.lineTo(2, 3); - polygon.lineTo(5, 80); - length = OperatorGeodeticLength.local().execute(polygon, SpatialReference.create(4326), GeodeticCurveType.Geodesic, null); - assertEquals(3.889408543061711E7, length, 14); - - } - - public void testInverse() { - SpatialReference sr = SpatialReference.create(4326); - Point point1 = new Point(0,0); - Point point2 = new Point(-1, 0); - InverseResult inverseResult = OperatorGeodeticInverse.local().execute(point1, point2, sr, sr, GeodeticCurveType.Geodesic, null); - - assertEquals(111319.4907932264, inverseResult.getDistance_m()); - assertEquals(-Math.PI / 2, inverseResult.getAz12_rad()); - assertEquals(Math.PI / 2, inverseResult.getAz21_rad()); - - point1 = new Point(179,0); - point2 = new Point(-180, 0); - inverseResult = OperatorGeodeticInverse.local().execute(point1, point2, sr, sr, GeodeticCurveType.Geodesic, null); - assertEquals(111319.4907932264, inverseResult.getDistance_m(), 14); - assertEquals(Math.PI / 2, inverseResult.getAz12_rad()); - assertEquals(-Math.PI / 2, inverseResult.getAz21_rad()); - - point2 = new Point(179,0); - point1 = new Point(-180, 0); - inverseResult = OperatorGeodeticInverse.local().execute(point1, point2, sr, sr, GeodeticCurveType.Geodesic, null); - assertEquals(111319.4907932264, inverseResult.getDistance_m(), 14); - assertEquals(-Math.PI / 2, inverseResult.getAz12_rad()); - assertEquals(Math.PI / 2, inverseResult.getAz21_rad()); - - - point1 = new Point(0,90); - point2 = new Point(0, 0); - inverseResult = OperatorGeodeticInverse.local().execute(point1, point2, sr, sr, GeodeticCurveType.Geodesic, null); - assertEquals(10001.96572931 * 1000, inverseResult.getDistance_m(), 14); - assertEquals(Math.PI, inverseResult.getAz12_rad()); - assertEquals(0, inverseResult.getAz21_rad(), 14); - - point1 = new Point(1,0); - point2 = new Point(1, 90); - inverseResult = OperatorGeodeticInverse.local().execute(point1, point2, sr, sr, GeodeticCurveType.Geodesic, null); - assertEquals(10001.96572931 * 1000, inverseResult.getDistance_m(), 14); - assertEquals(0, inverseResult.getAz12_rad(), 14); - assertEquals(Math.PI, inverseResult.getAz21_rad(), 14); - } + } + + public void testGeodeticLength() { + Polyline polyline = new Polyline(); + polyline.startPath(0, 0); + polyline.lineTo(1, 0); + double length = OperatorGeodeticLength.local().execute(polyline, SpatialReference.create(4326), GeodeticCurveType.Geodesic, null); + assertEquals(111319.4907932264, length); + + polyline = new Polyline(); + polyline.startPath(0, 0); + polyline.lineTo(-1, 0); + length = OperatorGeodeticLength.local().execute(polyline, SpatialReference.create(4326), GeodeticCurveType.Geodesic, null); + assertEquals(111319.4907932264, length); + + polyline = new Polyline(); + polyline.startPath(179, 0); + polyline.lineTo(-180, 0); + length = OperatorGeodeticLength.local().execute(polyline, SpatialReference.create(4326), GeodeticCurveType.Geodesic, null); + assertEquals(111319.4907932264, length, 14); + + polyline = new Polyline(); + polyline.startPath(-179, 0); + polyline.lineTo(-180, 0); + length = OperatorGeodeticLength.local().execute(polyline, SpatialReference.create(4326), GeodeticCurveType.Geodesic, null); + assertEquals(111319.4907932264, length, 14); + + polyline = new Polyline(); + polyline.startPath(179, 0); + polyline.lineTo(180, 0); + length = OperatorGeodeticLength.local().execute(polyline, SpatialReference.create(4326), GeodeticCurveType.Geodesic, null); + assertEquals(111319.4907932264, length, 14); + + polyline = new Polyline(); + polyline.startPath(180, 0); + polyline.lineTo(179, 0); + length = OperatorGeodeticLength.local().execute(polyline, SpatialReference.create(4326), GeodeticCurveType.Geodesic, null); + assertEquals(111319.4907932264, length, 14); + + polyline = new Polyline(); + polyline.startPath(180, 0); + polyline.lineTo(177, 0); + length = OperatorGeodeticLength.local().execute(polyline, SpatialReference.create(4326), GeodeticCurveType.Geodesic, null); + assertEquals(3 * 111319.4907932264, length, 14); + + polyline = new Polyline(); + polyline.startPath(0, 90); + polyline.lineTo(0, 0); + length = OperatorGeodeticLength.local().execute(polyline, SpatialReference.create(4326), GeodeticCurveType.Geodesic, null); + assertEquals(10001.96572931 * 1000, length, 14); + + polyline = new Polyline(); + polyline.startPath(1, 0); + polyline.lineTo(1, 90); + length = OperatorGeodeticLength.local().execute(polyline, SpatialReference.create(4326), GeodeticCurveType.Geodesic, null); + assertEquals(10001.96572931 * 1000, length, 14); + + polyline = new Polyline(); + polyline.startPath(1, 90); + polyline.lineTo(1, 0); + polyline.lineTo(0, 0); + polyline.lineTo(0, 90); + length = OperatorGeodeticLength.local().execute(polyline, SpatialReference.create(4326), GeodeticCurveType.Geodesic, null); + assertEquals(10001.96572931 * 1000 * 2 + 111319.4907932264, length, 14); + + Polygon polygon = new Polygon(); + polygon.startPath(1, 90); + polygon.lineTo(1, 0); + polygon.lineTo(0, 0); + polygon.lineTo(0, 90); + length = OperatorGeodeticLength.local().execute(polygon, SpatialReference.create(4326), GeodeticCurveType.Geodesic, null); + assertEquals(10001.96572931 * 1000 * 2 + 111319.4907932264, length, 14); + + + polygon = new Polygon(); + polygon.startPath(10, 90); + polygon.lineTo(10, 0); + polygon.lineTo(0, 0); + polygon.lineTo(0, 90); + polygon.closeAllPaths(); + length = OperatorGeodeticLength.local().execute(polygon, SpatialReference.create(4326), GeodeticCurveType.Geodesic, null); + assertEquals(2.11171263665557E7, length, 14); + + polygon = new Polygon(); + polygon.startPath(10, 90); + polygon.lineTo(10, 0); + polygon.lineTo(0, 0); + polygon.lineTo(0, 90); + polygon.startPath(8, 3); + polygon.lineTo(2, 3); + polygon.lineTo(5, 80); + length = OperatorGeodeticLength.local().execute(polygon, SpatialReference.create(4326), GeodeticCurveType.Geodesic, null); + assertEquals(3.889408543061711E7, length, 14); + + } + + public void testInverse() { + SpatialReference sr = SpatialReference.create(4326); + Point point1 = new Point(0, 0); + Point point2 = new Point(-1, 0); + InverseResult inverseResult = OperatorGeodeticInverse.local().execute(point1, point2, sr, sr, GeodeticCurveType.Geodesic, null); + + assertEquals(111319.4907932264, inverseResult.getDistance_m()); + assertEquals(-Math.PI / 2, inverseResult.getAz12_rad()); + assertEquals(Math.PI / 2, inverseResult.getAz21_rad()); + + point1 = new Point(179, 0); + point2 = new Point(-180, 0); + inverseResult = OperatorGeodeticInverse.local().execute(point1, point2, sr, sr, GeodeticCurveType.Geodesic, null); + assertEquals(111319.4907932264, inverseResult.getDistance_m(), 14); + assertEquals(Math.PI / 2, inverseResult.getAz12_rad()); + assertEquals(-Math.PI / 2, inverseResult.getAz21_rad()); + + point2 = new Point(179, 0); + point1 = new Point(-180, 0); + inverseResult = OperatorGeodeticInverse.local().execute(point1, point2, sr, sr, GeodeticCurveType.Geodesic, null); + assertEquals(111319.4907932264, inverseResult.getDistance_m(), 14); + assertEquals(-Math.PI / 2, inverseResult.getAz12_rad()); + assertEquals(Math.PI / 2, inverseResult.getAz21_rad()); + + + point1 = new Point(0, 90); + point2 = new Point(0, 0); + inverseResult = OperatorGeodeticInverse.local().execute(point1, point2, sr, sr, GeodeticCurveType.Geodesic, null); + assertEquals(10001.96572931 * 1000, inverseResult.getDistance_m(), 14); + assertEquals(Math.PI, inverseResult.getAz12_rad()); + assertEquals(0, inverseResult.getAz21_rad(), 14); + + point1 = new Point(1, 0); + point2 = new Point(1, 90); + inverseResult = OperatorGeodeticInverse.local().execute(point1, point2, sr, sr, GeodeticCurveType.Geodesic, null); + assertEquals(10001.96572931 * 1000, inverseResult.getDistance_m(), 14); + assertEquals(0, inverseResult.getAz12_rad(), 14); + assertEquals(Math.PI, inverseResult.getAz21_rad(), 14); + } } diff --git a/src/test/java/com/esri/core/geometry/TestGeomToGeoJson.java b/src/test/java/com/esri/core/geometry/TestGeomToGeoJson.java index 2700ff1a..1202e973 100644 --- a/src/test/java/com/esri/core/geometry/TestGeomToGeoJson.java +++ b/src/test/java/com/esri/core/geometry/TestGeomToGeoJson.java @@ -33,456 +33,456 @@ import java.util.List; public class TestGeomToGeoJson extends TestCase { - OperatorFactoryLocal factory = OperatorFactoryLocal.getInstance(); - - @Override - protected void setUp() throws Exception { - super.setUp(); - } - - @Override - protected void tearDown() throws Exception { - super.tearDown(); - } - - @Test - public void testPoint() { - Point p = new Point(10.0, 20.0); - OperatorExportToGeoJson exporter = (OperatorExportToGeoJson) factory.getOperator(Operator.Type.ExportToGeoJson); - String result = exporter.execute(p); - assertEquals("{\"type\":\"Point\",\"coordinates\":[10,20]}", result); - } - - @Test - public void testEmptyPoint() { - Point p = new Point(); - OperatorExportToGeoJson exporter = (OperatorExportToGeoJson) factory.getOperator(Operator.Type.ExportToGeoJson); - String result = exporter.execute(p); - assertEquals("{\"type\":\"Point\",\"coordinates\":[]}", result); - } - - @Test - public void testPointGeometryEngine() { - Point p = new Point(10.0, 20.0); - String result = GeometryEngine.geometryToGeoJson(p); - assertEquals("{\"type\":\"Point\",\"coordinates\":[10,20]}", result); - } - - @Test - public void testOGCPoint() { - Point p = new Point(10.0, 20.0); - OGCGeometry ogcPoint = new OGCPoint(p, null); - String result = ogcPoint.asGeoJson(); - assertEquals("{\"type\":\"Point\",\"coordinates\":[10,20],\"crs\":null}", result); - } - - @Test - public void testMultiPoint() { - MultiPoint mp = new MultiPoint(); - mp.add(10.0, 20.0); - mp.add(20.0, 30.0); - OperatorExportToGeoJson exporter = (OperatorExportToGeoJson) factory.getOperator(Operator.Type.ExportToGeoJson); - String result = exporter.execute(mp); - assertEquals("{\"type\":\"MultiPoint\",\"coordinates\":[[10,20],[20,30]]}", result); - } - - @Test - public void testEmptyMultiPoint() { - MultiPoint mp = new MultiPoint(); - OperatorExportToGeoJson exporter = (OperatorExportToGeoJson) factory.getOperator(Operator.Type.ExportToGeoJson); - String result = exporter.execute(mp); - assertEquals("{\"type\":\"MultiPoint\",\"coordinates\":[]}", result); - } - - @Test - public void testMultiPointGeometryEngine() { - MultiPoint mp = new MultiPoint(); - mp.add(10.0, 20.0); - mp.add(20.0, 30.0); - String result = GeometryEngine.geometryToGeoJson(mp); - assertEquals("{\"type\":\"MultiPoint\",\"coordinates\":[[10,20],[20,30]]}", result); - } - - @Test - public void testOGCMultiPoint() { - MultiPoint mp = new MultiPoint(); - mp.add(10.0, 20.0); - mp.add(20.0, 30.0); - OGCMultiPoint ogcMultiPoint = new OGCMultiPoint(mp, null); - String result = ogcMultiPoint.asGeoJson(); - assertEquals("{\"type\":\"MultiPoint\",\"coordinates\":[[10,20],[20,30]],\"crs\":null}", result); - } - - @Test - public void testPolyline() { - Polyline p = new Polyline(); - p.startPath(100.0, 0.0); - p.lineTo(101.0, 0.0); - p.lineTo(101.0, 1.0); - p.lineTo(100.0, 1.0); - OperatorExportToGeoJson exporter = (OperatorExportToGeoJson) factory.getOperator(Operator.Type.ExportToGeoJson); - String result = exporter.execute(p); - assertEquals("{\"type\":\"LineString\",\"coordinates\":[[100,0],[101,0],[101,1],[100,1]]}", result); - } - - @Test - public void testEmptyPolyline() { - Polyline p = new Polyline(); - OperatorExportToGeoJson exporter = (OperatorExportToGeoJson) factory.getOperator(Operator.Type.ExportToGeoJson); - String result = exporter.execute(p); - assertEquals("{\"type\":\"LineString\",\"coordinates\":[]}", result); - } - - @Test - public void testPolylineGeometryEngine() { - Polyline p = new Polyline(); - p.startPath(100.0, 0.0); - p.lineTo(101.0, 0.0); - p.lineTo(101.0, 1.0); - p.lineTo(100.0, 1.0); - String result = GeometryEngine.geometryToGeoJson(p); - assertEquals("{\"type\":\"LineString\",\"coordinates\":[[100,0],[101,0],[101,1],[100,1]]}", result); - } - - @Test - public void testOGCLineString() { - Polyline p = new Polyline(); - p.startPath(100.0, 0.0); - p.lineTo(101.0, 0.0); - p.lineTo(101.0, 1.0); - p.lineTo(100.0, 1.0); - OGCLineString ogcLineString = new OGCLineString(p, 0, null); - String result = ogcLineString.asGeoJson(); - assertEquals("{\"type\":\"LineString\",\"coordinates\":[[100,0],[101,0],[101,1],[100,1]],\"crs\":null}", result); - } - - @Test - public void testPolygon() { - Polygon p = new Polygon(); - p.startPath(100.0, 0.0); - p.lineTo(101.0, 0.0); - p.lineTo(101.0, 1.0); - p.lineTo(100.0, 1.0); - p.closePathWithLine(); - OperatorExportToGeoJson exporter = (OperatorExportToGeoJson) factory.getOperator(Operator.Type.ExportToGeoJson); - String result = exporter.execute(p); - assertEquals("{\"type\":\"Polygon\",\"coordinates\":[[[100,0],[100,1],[101,1],[101,0],[100,0]]]}", result); - } - - @Test - public void testPolygonWithHole() { - Polygon p = new Polygon(); - - //exterior ring - has to be clockwise for Esri - p.startPath(100.0, 0.0); - p.lineTo(100.0, 1.0); - p.lineTo(101.0, 1.0); - p.lineTo(101.0, 0.0); - p.closePathWithLine(); - - //hole - counterclockwise for Esri - p.startPath(100.2, 0.2); - p.lineTo(100.8, 0.2); - p.lineTo(100.8, 0.8); - p.lineTo(100.2, 0.8); - p.closePathWithLine(); - - OperatorExportToGeoJson exporter = (OperatorExportToGeoJson) factory.getOperator(Operator.Type.ExportToGeoJson); - String result = exporter.execute(p); - assertEquals("{\"type\":\"Polygon\",\"coordinates\":[[[100,0],[101,0],[101,1],[100,1],[100,0]],[[100.2,0.2],[100.2,0.8],[100.8,0.8],[100.8,0.2],[100.2,0.2]]]}", result); - } - - @Test - public void testPolygonWithHoleReversed() { - Polygon p = new Polygon(); - - //exterior ring - has to be clockwise for Esri - p.startPath(100.0, 0.0); - p.lineTo(100.0, 1.0); - p.lineTo(101.0, 1.0); - p.lineTo(101.0, 0.0); - p.closePathWithLine(); - - //hole - counterclockwise for Esri - p.startPath(100.2, 0.2); - p.lineTo(100.8, 0.2); - p.lineTo(100.8, 0.8); - p.lineTo(100.2, 0.8); - p.closePathWithLine(); - - p.reverseAllPaths();//make it reversed. Exterior ring - ccw, hole - cw. - - OperatorExportToGeoJson exporter = (OperatorExportToGeoJson) factory.getOperator(Operator.Type.ExportToGeoJson); - String result = exporter.execute(p); - assertEquals("{\"type\":\"Polygon\",\"coordinates\":[[[100,0],[100,1],[101,1],[101,0],[100,0]],[[100.2,0.2],[100.8,0.2],[100.8,0.8],[100.2,0.8],[100.2,0.2]]]}", result); - // TODO make a pull request on this bug - // OperatorImportFromGeoJson importer = (OperatorImportFromGeoJson) factory.getOperator(Operator.Type.ImportFromGeoJson); - // MapGeometry resultGeom = importer.execute(GeoJsonImportFlags.geoJsonImportSkipCRS, Geometry.Type.Unknown, result, null); - // assertTrue(resultGeom.m_geometry.equals(p)); - } - - @Test - public void testMultiPolygon() throws IOException { - JsonFactory jsonFactory = new JsonFactory(); - - //String geoJsonPolygon = "{\"type\":\"MultiPolygon\",\"coordinates\":[[[[-100,-100],[-100,100],[100,100],[100,-100],[-100,-100]],[[-90,-90],[90,90],[-90,90],[90,-90],[-90,-90]]],[[[-10.0,-10.0],[-10.0,10.0],[10.0,10.0],[10.0,-10.0],[-10.0,-10.0]]]]}"; - String esriJsonPolygon = "{\"rings\": [[[-100, -100], [-100, 100], [100, 100], [100, -100], [-100, -100]], [[-90, -90], [90, 90], [-90, 90], [90, -90], [-90, -90]], [[-10, -10], [-10, 10], [10, 10], [10, -10], [-10, -10]]]}"; - - JsonParser parser = jsonFactory.createParser(esriJsonPolygon); - MapGeometry parsedPoly = GeometryEngine.jsonToGeometry(parser); - //MapGeometry parsedPoly = GeometryEngine.geometryFromGeoJson(jsonPolygon, 0, Geometry.Type.Polygon); - - Polygon poly = (Polygon) parsedPoly.getGeometry(); - - OperatorExportToGeoJson exporter = (OperatorExportToGeoJson) factory.getOperator(Operator.Type.ExportToGeoJson); - //String result = exporter.execute(parsedPoly.getGeometry()); - String result = exporter.execute(poly); - assertEquals("{\"type\":\"MultiPolygon\",\"coordinates\":[[[[-100,-100],[100,-100],[100,100],[-100,100],[-100,-100]],[[-90,-90],[90,-90],[-90,90],[90,90],[-90,-90]]],[[[-10,-10],[10,-10],[10,10],[-10,10],[-10,-10]]]]}", result); - } - - @Test - public void testMultiPolygonCursor() throws IOException { - Polygon p = new Polygon(); - - //exterior ring - has to be clockwise for Esri - p.startPath(100.0, 0.0); - p.lineTo(100.0, 1.0); - p.lineTo(101.0, 1.0); - p.lineTo(101.0, 0.0); - p.closePathWithLine(); - - //hole - counterclockwise for Esri - p.startPath(100.2, 0.2); - p.lineTo(100.8, 0.2); - p.lineTo(100.8, 0.8); - p.lineTo(100.2, 0.8); - p.closePathWithLine(); - - SimpleGeometryCursor simpleGeometryCursor = new SimpleGeometryCursor(p); - OperatorExportToGeoJsonCursor exportToGeoJsonCursor = new OperatorExportToGeoJsonCursor(GeoJsonExportFlags.geoJsonExportSkipCRS, null, simpleGeometryCursor); - String result = exportToGeoJsonCursor.next(); - assertEquals("{\"type\":\"Polygon\",\"coordinates\":[[[100,0],[101,0],[101,1],[100,1],[100,0]],[[100.2,0.2],[100.2,0.8],[100.8,0.8],[100.8,0.2],[100.2,0.2]]]}", result); - - SimpleStringCursor simpleStringCursor = new SimpleStringCursor(result); - - OperatorImportFromGeoJsonCursor importFromGeoJsonCursor = new OperatorImportFromGeoJsonCursor(GeoJsonImportFlags.geoJsonImportSkipCRS, simpleStringCursor, null); - MapGeometry mapGeometry = importFromGeoJsonCursor.next(); - - assertTrue(p.equals(mapGeometry.m_geometry)); - } - - - @Test - public void testEmptyPolygon() { - Polygon p = new Polygon(); - OperatorExportToGeoJson exporter = (OperatorExportToGeoJson) factory.getOperator(Operator.Type.ExportToGeoJson); - String result = exporter.execute(p); - assertEquals("{\"type\":\"Polygon\",\"coordinates\":[]}", result); - - MapGeometry imported = OperatorImportFromGeoJson.local().execute(0, Geometry.Type.Unknown, result, null); - assertTrue(imported.getGeometry().isEmpty()); - assertTrue(imported.getGeometry().getType() == Geometry.Type.Polygon); - } - - @Test - public void testPolygonGeometryEngine() { - Polygon p = new Polygon(); - p.startPath(100.0, 0.0); - p.lineTo(101.0, 0.0); - p.lineTo(101.0, 1.0); - p.lineTo(100.0, 1.0); - p.closePathWithLine(); - String result = GeometryEngine.geometryToGeoJson(p); - assertEquals("{\"type\":\"Polygon\",\"coordinates\":[[[100,0],[100,1],[101,1],[101,0],[100,0]]]}", result); - } - - @Test - public void testOGCPolygon() { - Polygon p = new Polygon(); - p.startPath(100.0, 0.0); - p.lineTo(101.0, 0.0); - p.lineTo(101.0, 1.0); - p.lineTo(100.0, 1.0); - p.closePathWithLine(); - OGCPolygon ogcPolygon = new OGCPolygon(p, null); - String result = ogcPolygon.asGeoJson(); - assertEquals("{\"type\":\"Polygon\",\"coordinates\":[[[100,0],[100,1],[101,1],[101,0],[100,0]]],\"crs\":null}", result); - } - - @Test - public void testPolygonWithHoleGeometryEngine() { - Polygon p = new Polygon(); - - p.startPath(100.0, 0.0);//clockwise exterior - p.lineTo(100.0, 1.0); - p.lineTo(101.0, 1.0); - p.lineTo(101.0, 0.0); - p.closePathWithLine(); - - p.startPath(100.2, 0.2);//counterclockwise hole - p.lineTo(100.8, 0.2); - p.lineTo(100.8, 0.8); - p.lineTo(100.2, 0.8); - p.closePathWithLine(); - - String result = GeometryEngine.geometryToGeoJson(p); - assertEquals("{\"type\":\"Polygon\",\"coordinates\":[[[100,0],[101,0],[101,1],[100,1],[100,0]],[[100.2,0.2],[100.2,0.8],[100.8,0.8],[100.8,0.2],[100.2,0.2]]]}", result); - } - - @Test - public void testPolylineWithTwoPaths() { - Polyline p = new Polyline(); - - p.startPath(100.0, 0.0); - p.lineTo(100.0, 1.0); - - p.startPath(100.2, 0.2); - p.lineTo(100.8, 0.2); - - String result = GeometryEngine.geometryToGeoJson(p); - assertEquals("{\"type\":\"MultiLineString\",\"coordinates\":[[[100,0],[100,1]],[[100.2,0.2],[100.8,0.2]]]}", result); - } - - @Test - public void testOGCPolygonWithHole() { - Polygon p = new Polygon(); - - p.startPath(100.0, 0.0); - p.lineTo(100.0, 1.0); - p.lineTo(101.0, 1.0); - p.lineTo(101.0, 0.0); - p.closePathWithLine(); - - p.startPath(100.2, 0.2); - p.lineTo(100.8, 0.2); - p.lineTo(100.8, 0.8); - p.lineTo(100.2, 0.8); - p.closePathWithLine(); - - OGCPolygon ogcPolygon = new OGCPolygon(p, null); - String result = ogcPolygon.asGeoJson(); - assertEquals("{\"type\":\"Polygon\",\"coordinates\":[[[100,0],[101,0],[101,1],[100,1],[100,0]],[[100.2,0.2],[100.2,0.8],[100.8,0.8],[100.8,0.2],[100.2,0.2]]],\"crs\":null}", result); - } - - @Test - public void testGeometryCollection() { - SpatialReference sr = SpatialReference.create(4326); - - StringBuilder geometrySb = new StringBuilder(); - geometrySb - .append("{\"type\" : \"GeometryCollection\", \"geometries\" : ["); - - OGCPoint point = new OGCPoint(new Point(1.0, 1.0), sr); - assertEquals("{\"x\":1,\"y\":1,\"spatialReference\":{\"wkid\":4326}}", - point.asJson()); - assertEquals( - "{\"type\":\"Point\",\"coordinates\":[1,1],\"crs\":{\"type\":\"name\",\"properties\":{\"name\":\"EPSG:4326\"}}}", - point.asGeoJson()); - geometrySb.append(point.asGeoJson()).append(", "); - - OGCLineString line = new OGCLineString(new Polyline( - new Point(1.0, 1.0), new Point(2.0, 2.0)), 0, sr); - assertEquals( - "{\"paths\":[[[1,1],[2,2]]],\"spatialReference\":{\"wkid\":4326}}", - line.asJson()); - assertEquals( - "{\"type\":\"LineString\",\"coordinates\":[[1,1],[2,2]],\"crs\":{\"type\":\"name\",\"properties\":{\"name\":\"EPSG:4326\"}}}", - line.asGeoJson()); - geometrySb.append(line.asGeoJson()).append(", "); - - Polygon p = new Polygon(); - p.startPath(1.0, 1.0); - p.lineTo(2.0, 2.0); - p.lineTo(3.0, 1.0); - p.lineTo(2.0, 0.0); - - OGCPolygon polygon = new OGCPolygon(p, sr); - assertEquals( - "{\"rings\":[[[1,1],[2,2],[3,1],[2,0],[1,1]]],\"spatialReference\":{\"wkid\":4326}}", - polygon.asJson()); - assertEquals( - "{\"type\":\"Polygon\",\"coordinates\":[[[1,1],[2,0],[3,1],[2,2],[1,1]]],\"crs\":{\"type\":\"name\",\"properties\":{\"name\":\"EPSG:4326\"}}}", - polygon.asGeoJson()); - geometrySb.append(polygon.asGeoJson()).append("]}"); - - List geoms = new ArrayList(3); - geoms.add(point); - geoms.add(line); - geoms.add(polygon); - OGCConcreteGeometryCollection collection = new OGCConcreteGeometryCollection( - geoms, sr); - String s2 = collection.asGeoJson(); - - assertEquals("{\"type\":\"GeometryCollection\",\"geometries\":[{\"type\":\"Point\",\"coordinates\":[1,1]},{\"type\":\"LineString\",\"coordinates\":[[1,1],[2,2]]},{\"type\":\"Polygon\",\"coordinates\":[[[1,1],[2,0],[3,1],[2,2],[1,1]]]}],\"crs\":{\"type\":\"name\",\"properties\":{\"name\":\"EPSG:4326\"}}}", collection.asGeoJson()); - } - - @Test - public void testEmptyGeometryCollection() { - SpatialReference sr = SpatialReference.create(4326); - OGCConcreteGeometryCollection collection = new OGCConcreteGeometryCollection( - new ArrayList(), sr); - String s2 = collection.asGeoJson(); - assertEquals( - "{\"type\":\"GeometryCollection\",\"geometries\":[],\"crs\":{\"type\":\"name\",\"properties\":{\"name\":\"EPSG:4326\"}}}", - collection.asGeoJson()); - } - - //Envelope is exported as a polygon (we don't support bbox, as it is not a GeoJson geometry, but simply a field)! - @Test - public void testEnvelope() { - Envelope e = new Envelope(); - e.setCoords(-180.0, -90.0, 180.0, 90.0); - String result = OperatorExportToGeoJson.local().execute(e); - assertEquals("{\"type\":\"Polygon\",\"coordinates\":[[[-180,-90],[180,-90],[180,90],[-180,90],[-180,-90]]]}", result); - } - - @Test - public void testEmptyEnvelope() { - Envelope e = new Envelope(); - String result = OperatorExportToGeoJson.local().execute(e); - assertEquals("{\"type\":\"Polygon\",\"coordinates\":[]}", result); - } - - @Test - public void testEnvelopeGeometryEngine() { - Envelope e = new Envelope(); - e.setCoords(-180.0, -90.0, 180.0, 90.0); - String result = GeometryEngine.geometryToGeoJson(e); - assertEquals("{\"type\":\"Polygon\",\"coordinates\":[[[-180,-90],[180,-90],[180,90],[-180,90],[-180,-90]]]}", result); - } - - @Test - public void testOldCRS() { - String inputStr = "{\"type\":\"Polygon\",\"coordinates\":[[[-180,-90],[180,-90],[180,90],[-180,90],[-180,-90]]], \"crs\":\"EPSG:4267\"}"; - MapGeometry mg = OperatorImportFromGeoJson.local().execute(GeoJsonImportFlags.geoJsonImportDefaults, Geometry.Type.Unknown, inputStr, null); - String result = GeometryEngine.geometryToGeoJson(mg.getSpatialReference(), mg.getGeometry()); - assertEquals("{\"type\":\"Polygon\",\"coordinates\":[[[-180,-90],[180,-90],[180,90],[-180,90],[-180,-90]]],\"crs\":{\"type\":\"name\",\"properties\":{\"name\":\"EPSG:4267\"}}}", result); - } - - // bbox is not supported anymore. - // @Test - // public void testEnvelope() { - // Envelope e = new Envelope(); - // e.setCoords(-180.0, -90.0, 180.0, 90.0); - // OperatorExportToGeoJson exporter = (OperatorExportToGeoJson) factory.getOperator(Operator.Type.ExportToGeoJson); - // String result = exporter.execute(e); - // assertEquals("{\"bbox\":[-180.0,-90.0,180.0,90.0]}", result); - // } - // - // @Test - // public void testEmptyEnvelope() { - // Envelope e = new Envelope(); - // OperatorExportToGeoJson exporter = (OperatorExportToGeoJson) factory.getOperator(Operator.Type.ExportToGeoJson); - // String result = exporter.execute(e); - // assertEquals("{\"bbox\":null}", result); - // } - // - // @Test - // public void testEnvelopeGeometryEngine() { - // Envelope e = new Envelope(); - // e.setCoords(-180.0, -90.0, 180.0, 90.0); - // String result = GeometryEngine.geometryToGeoJson(e); - // assertEquals("{\"bbox\":[-180.0,-90.0,180.0,90.0]}", result); - // } + OperatorFactoryLocal factory = OperatorFactoryLocal.getInstance(); + + @Override + protected void setUp() throws Exception { + super.setUp(); + } + + @Override + protected void tearDown() throws Exception { + super.tearDown(); + } + + @Test + public void testPoint() { + Point p = new Point(10.0, 20.0); + OperatorExportToGeoJson exporter = (OperatorExportToGeoJson) factory.getOperator(Operator.Type.ExportToGeoJson); + String result = exporter.execute(p); + assertEquals("{\"type\":\"Point\",\"coordinates\":[10,20]}", result); + } + + @Test + public void testEmptyPoint() { + Point p = new Point(); + OperatorExportToGeoJson exporter = (OperatorExportToGeoJson) factory.getOperator(Operator.Type.ExportToGeoJson); + String result = exporter.execute(p); + assertEquals("{\"type\":\"Point\",\"coordinates\":[]}", result); + } + + @Test + public void testPointGeometryEngine() { + Point p = new Point(10.0, 20.0); + String result = GeometryEngine.geometryToGeoJson(p); + assertEquals("{\"type\":\"Point\",\"coordinates\":[10,20]}", result); + } + + @Test + public void testOGCPoint() { + Point p = new Point(10.0, 20.0); + OGCGeometry ogcPoint = new OGCPoint(p, null); + String result = ogcPoint.asGeoJson(); + assertEquals("{\"type\":\"Point\",\"coordinates\":[10,20],\"crs\":null}", result); + } + + @Test + public void testMultiPoint() { + MultiPoint mp = new MultiPoint(); + mp.add(10.0, 20.0); + mp.add(20.0, 30.0); + OperatorExportToGeoJson exporter = (OperatorExportToGeoJson) factory.getOperator(Operator.Type.ExportToGeoJson); + String result = exporter.execute(mp); + assertEquals("{\"type\":\"MultiPoint\",\"coordinates\":[[10,20],[20,30]]}", result); + } + + @Test + public void testEmptyMultiPoint() { + MultiPoint mp = new MultiPoint(); + OperatorExportToGeoJson exporter = (OperatorExportToGeoJson) factory.getOperator(Operator.Type.ExportToGeoJson); + String result = exporter.execute(mp); + assertEquals("{\"type\":\"MultiPoint\",\"coordinates\":[]}", result); + } + + @Test + public void testMultiPointGeometryEngine() { + MultiPoint mp = new MultiPoint(); + mp.add(10.0, 20.0); + mp.add(20.0, 30.0); + String result = GeometryEngine.geometryToGeoJson(mp); + assertEquals("{\"type\":\"MultiPoint\",\"coordinates\":[[10,20],[20,30]]}", result); + } + + @Test + public void testOGCMultiPoint() { + MultiPoint mp = new MultiPoint(); + mp.add(10.0, 20.0); + mp.add(20.0, 30.0); + OGCMultiPoint ogcMultiPoint = new OGCMultiPoint(mp, null); + String result = ogcMultiPoint.asGeoJson(); + assertEquals("{\"type\":\"MultiPoint\",\"coordinates\":[[10,20],[20,30]],\"crs\":null}", result); + } + + @Test + public void testPolyline() { + Polyline p = new Polyline(); + p.startPath(100.0, 0.0); + p.lineTo(101.0, 0.0); + p.lineTo(101.0, 1.0); + p.lineTo(100.0, 1.0); + OperatorExportToGeoJson exporter = (OperatorExportToGeoJson) factory.getOperator(Operator.Type.ExportToGeoJson); + String result = exporter.execute(p); + assertEquals("{\"type\":\"LineString\",\"coordinates\":[[100,0],[101,0],[101,1],[100,1]]}", result); + } + + @Test + public void testEmptyPolyline() { + Polyline p = new Polyline(); + OperatorExportToGeoJson exporter = (OperatorExportToGeoJson) factory.getOperator(Operator.Type.ExportToGeoJson); + String result = exporter.execute(p); + assertEquals("{\"type\":\"LineString\",\"coordinates\":[]}", result); + } + + @Test + public void testPolylineGeometryEngine() { + Polyline p = new Polyline(); + p.startPath(100.0, 0.0); + p.lineTo(101.0, 0.0); + p.lineTo(101.0, 1.0); + p.lineTo(100.0, 1.0); + String result = GeometryEngine.geometryToGeoJson(p); + assertEquals("{\"type\":\"LineString\",\"coordinates\":[[100,0],[101,0],[101,1],[100,1]]}", result); + } + + @Test + public void testOGCLineString() { + Polyline p = new Polyline(); + p.startPath(100.0, 0.0); + p.lineTo(101.0, 0.0); + p.lineTo(101.0, 1.0); + p.lineTo(100.0, 1.0); + OGCLineString ogcLineString = new OGCLineString(p, 0, null); + String result = ogcLineString.asGeoJson(); + assertEquals("{\"type\":\"LineString\",\"coordinates\":[[100,0],[101,0],[101,1],[100,1]],\"crs\":null}", result); + } + + @Test + public void testPolygon() { + Polygon p = new Polygon(); + p.startPath(100.0, 0.0); + p.lineTo(101.0, 0.0); + p.lineTo(101.0, 1.0); + p.lineTo(100.0, 1.0); + p.closePathWithLine(); + OperatorExportToGeoJson exporter = (OperatorExportToGeoJson) factory.getOperator(Operator.Type.ExportToGeoJson); + String result = exporter.execute(p); + assertEquals("{\"type\":\"Polygon\",\"coordinates\":[[[100,0],[100,1],[101,1],[101,0],[100,0]]]}", result); + } + + @Test + public void testPolygonWithHole() { + Polygon p = new Polygon(); + + //exterior ring - has to be clockwise for Esri + p.startPath(100.0, 0.0); + p.lineTo(100.0, 1.0); + p.lineTo(101.0, 1.0); + p.lineTo(101.0, 0.0); + p.closePathWithLine(); + + //hole - counterclockwise for Esri + p.startPath(100.2, 0.2); + p.lineTo(100.8, 0.2); + p.lineTo(100.8, 0.8); + p.lineTo(100.2, 0.8); + p.closePathWithLine(); + + OperatorExportToGeoJson exporter = (OperatorExportToGeoJson) factory.getOperator(Operator.Type.ExportToGeoJson); + String result = exporter.execute(p); + assertEquals("{\"type\":\"Polygon\",\"coordinates\":[[[100,0],[101,0],[101,1],[100,1],[100,0]],[[100.2,0.2],[100.2,0.8],[100.8,0.8],[100.8,0.2],[100.2,0.2]]]}", result); + } + + @Test + public void testPolygonWithHoleReversed() { + Polygon p = new Polygon(); + + //exterior ring - has to be clockwise for Esri + p.startPath(100.0, 0.0); + p.lineTo(100.0, 1.0); + p.lineTo(101.0, 1.0); + p.lineTo(101.0, 0.0); + p.closePathWithLine(); + + //hole - counterclockwise for Esri + p.startPath(100.2, 0.2); + p.lineTo(100.8, 0.2); + p.lineTo(100.8, 0.8); + p.lineTo(100.2, 0.8); + p.closePathWithLine(); + + p.reverseAllPaths();//make it reversed. Exterior ring - ccw, hole - cw. + + OperatorExportToGeoJson exporter = (OperatorExportToGeoJson) factory.getOperator(Operator.Type.ExportToGeoJson); + String result = exporter.execute(p); + assertEquals("{\"type\":\"Polygon\",\"coordinates\":[[[100,0],[100,1],[101,1],[101,0],[100,0]],[[100.2,0.2],[100.8,0.2],[100.8,0.8],[100.2,0.8],[100.2,0.2]]]}", result); + // TODO make a pull request on this bug + // OperatorImportFromGeoJson importer = (OperatorImportFromGeoJson) factory.getOperator(Operator.Type.ImportFromGeoJson); + // MapGeometry resultGeom = importer.execute(GeoJsonImportFlags.geoJsonImportSkipCRS, Geometry.Type.Unknown, result, null); + // assertTrue(resultGeom.m_geometry.equals(p)); + } + + @Test + public void testMultiPolygon() throws IOException { + JsonFactory jsonFactory = new JsonFactory(); + + //String geoJsonPolygon = "{\"type\":\"MultiPolygon\",\"coordinates\":[[[[-100,-100],[-100,100],[100,100],[100,-100],[-100,-100]],[[-90,-90],[90,90],[-90,90],[90,-90],[-90,-90]]],[[[-10.0,-10.0],[-10.0,10.0],[10.0,10.0],[10.0,-10.0],[-10.0,-10.0]]]]}"; + String esriJsonPolygon = "{\"rings\": [[[-100, -100], [-100, 100], [100, 100], [100, -100], [-100, -100]], [[-90, -90], [90, 90], [-90, 90], [90, -90], [-90, -90]], [[-10, -10], [-10, 10], [10, 10], [10, -10], [-10, -10]]]}"; + + JsonParser parser = jsonFactory.createParser(esriJsonPolygon); + MapGeometry parsedPoly = GeometryEngine.jsonToGeometry(parser); + //MapGeometry parsedPoly = GeometryEngine.geometryFromGeoJson(jsonPolygon, 0, Geometry.Type.Polygon); + + Polygon poly = (Polygon) parsedPoly.getGeometry(); + + OperatorExportToGeoJson exporter = (OperatorExportToGeoJson) factory.getOperator(Operator.Type.ExportToGeoJson); + //String result = exporter.execute(parsedPoly.getGeometry()); + String result = exporter.execute(poly); + assertEquals("{\"type\":\"MultiPolygon\",\"coordinates\":[[[[-100,-100],[100,-100],[100,100],[-100,100],[-100,-100]],[[-90,-90],[90,-90],[-90,90],[90,90],[-90,-90]]],[[[-10,-10],[10,-10],[10,10],[-10,10],[-10,-10]]]]}", result); + } + + @Test + public void testMultiPolygonCursor() throws IOException { + Polygon p = new Polygon(); + + //exterior ring - has to be clockwise for Esri + p.startPath(100.0, 0.0); + p.lineTo(100.0, 1.0); + p.lineTo(101.0, 1.0); + p.lineTo(101.0, 0.0); + p.closePathWithLine(); + + //hole - counterclockwise for Esri + p.startPath(100.2, 0.2); + p.lineTo(100.8, 0.2); + p.lineTo(100.8, 0.8); + p.lineTo(100.2, 0.8); + p.closePathWithLine(); + + SimpleGeometryCursor simpleGeometryCursor = new SimpleGeometryCursor(p); + OperatorExportToGeoJsonCursor exportToGeoJsonCursor = new OperatorExportToGeoJsonCursor(GeoJsonExportFlags.geoJsonExportSkipCRS, null, simpleGeometryCursor); + String result = exportToGeoJsonCursor.next(); + assertEquals("{\"type\":\"Polygon\",\"coordinates\":[[[100,0],[101,0],[101,1],[100,1],[100,0]],[[100.2,0.2],[100.2,0.8],[100.8,0.8],[100.8,0.2],[100.2,0.2]]]}", result); + + SimpleStringCursor simpleStringCursor = new SimpleStringCursor(result); + + OperatorImportFromGeoJsonCursor importFromGeoJsonCursor = new OperatorImportFromGeoJsonCursor(GeoJsonImportFlags.geoJsonImportSkipCRS, simpleStringCursor, null); + MapGeometry mapGeometry = importFromGeoJsonCursor.next(); + + assertTrue(p.equals(mapGeometry.m_geometry)); + } + + + @Test + public void testEmptyPolygon() { + Polygon p = new Polygon(); + OperatorExportToGeoJson exporter = (OperatorExportToGeoJson) factory.getOperator(Operator.Type.ExportToGeoJson); + String result = exporter.execute(p); + assertEquals("{\"type\":\"Polygon\",\"coordinates\":[]}", result); + + MapGeometry imported = OperatorImportFromGeoJson.local().execute(0, Geometry.Type.Unknown, result, null); + assertTrue(imported.getGeometry().isEmpty()); + assertTrue(imported.getGeometry().getType() == Geometry.Type.Polygon); + } + + @Test + public void testPolygonGeometryEngine() { + Polygon p = new Polygon(); + p.startPath(100.0, 0.0); + p.lineTo(101.0, 0.0); + p.lineTo(101.0, 1.0); + p.lineTo(100.0, 1.0); + p.closePathWithLine(); + String result = GeometryEngine.geometryToGeoJson(p); + assertEquals("{\"type\":\"Polygon\",\"coordinates\":[[[100,0],[100,1],[101,1],[101,0],[100,0]]]}", result); + } + + @Test + public void testOGCPolygon() { + Polygon p = new Polygon(); + p.startPath(100.0, 0.0); + p.lineTo(101.0, 0.0); + p.lineTo(101.0, 1.0); + p.lineTo(100.0, 1.0); + p.closePathWithLine(); + OGCPolygon ogcPolygon = new OGCPolygon(p, null); + String result = ogcPolygon.asGeoJson(); + assertEquals("{\"type\":\"Polygon\",\"coordinates\":[[[100,0],[100,1],[101,1],[101,0],[100,0]]],\"crs\":null}", result); + } + + @Test + public void testPolygonWithHoleGeometryEngine() { + Polygon p = new Polygon(); + + p.startPath(100.0, 0.0);//clockwise exterior + p.lineTo(100.0, 1.0); + p.lineTo(101.0, 1.0); + p.lineTo(101.0, 0.0); + p.closePathWithLine(); + + p.startPath(100.2, 0.2);//counterclockwise hole + p.lineTo(100.8, 0.2); + p.lineTo(100.8, 0.8); + p.lineTo(100.2, 0.8); + p.closePathWithLine(); + + String result = GeometryEngine.geometryToGeoJson(p); + assertEquals("{\"type\":\"Polygon\",\"coordinates\":[[[100,0],[101,0],[101,1],[100,1],[100,0]],[[100.2,0.2],[100.2,0.8],[100.8,0.8],[100.8,0.2],[100.2,0.2]]]}", result); + } + + @Test + public void testPolylineWithTwoPaths() { + Polyline p = new Polyline(); + + p.startPath(100.0, 0.0); + p.lineTo(100.0, 1.0); + + p.startPath(100.2, 0.2); + p.lineTo(100.8, 0.2); + + String result = GeometryEngine.geometryToGeoJson(p); + assertEquals("{\"type\":\"MultiLineString\",\"coordinates\":[[[100,0],[100,1]],[[100.2,0.2],[100.8,0.2]]]}", result); + } + + @Test + public void testOGCPolygonWithHole() { + Polygon p = new Polygon(); + + p.startPath(100.0, 0.0); + p.lineTo(100.0, 1.0); + p.lineTo(101.0, 1.0); + p.lineTo(101.0, 0.0); + p.closePathWithLine(); + + p.startPath(100.2, 0.2); + p.lineTo(100.8, 0.2); + p.lineTo(100.8, 0.8); + p.lineTo(100.2, 0.8); + p.closePathWithLine(); + + OGCPolygon ogcPolygon = new OGCPolygon(p, null); + String result = ogcPolygon.asGeoJson(); + assertEquals("{\"type\":\"Polygon\",\"coordinates\":[[[100,0],[101,0],[101,1],[100,1],[100,0]],[[100.2,0.2],[100.2,0.8],[100.8,0.8],[100.8,0.2],[100.2,0.2]]],\"crs\":null}", result); + } + + @Test + public void testGeometryCollection() { + SpatialReference sr = SpatialReference.create(4326); + + StringBuilder geometrySb = new StringBuilder(); + geometrySb + .append("{\"type\" : \"GeometryCollection\", \"geometries\" : ["); + + OGCPoint point = new OGCPoint(new Point(1.0, 1.0), sr); + assertEquals("{\"x\":1,\"y\":1,\"spatialReference\":{\"wkid\":4326}}", + point.asJson()); + assertEquals( + "{\"type\":\"Point\",\"coordinates\":[1,1],\"crs\":{\"type\":\"name\",\"properties\":{\"name\":\"EPSG:4326\"}}}", + point.asGeoJson()); + geometrySb.append(point.asGeoJson()).append(", "); + + OGCLineString line = new OGCLineString(new Polyline( + new Point(1.0, 1.0), new Point(2.0, 2.0)), 0, sr); + assertEquals( + "{\"paths\":[[[1,1],[2,2]]],\"spatialReference\":{\"wkid\":4326}}", + line.asJson()); + assertEquals( + "{\"type\":\"LineString\",\"coordinates\":[[1,1],[2,2]],\"crs\":{\"type\":\"name\",\"properties\":{\"name\":\"EPSG:4326\"}}}", + line.asGeoJson()); + geometrySb.append(line.asGeoJson()).append(", "); + + Polygon p = new Polygon(); + p.startPath(1.0, 1.0); + p.lineTo(2.0, 2.0); + p.lineTo(3.0, 1.0); + p.lineTo(2.0, 0.0); + + OGCPolygon polygon = new OGCPolygon(p, sr); + assertEquals( + "{\"rings\":[[[1,1],[2,2],[3,1],[2,0],[1,1]]],\"spatialReference\":{\"wkid\":4326}}", + polygon.asJson()); + assertEquals( + "{\"type\":\"Polygon\",\"coordinates\":[[[1,1],[2,0],[3,1],[2,2],[1,1]]],\"crs\":{\"type\":\"name\",\"properties\":{\"name\":\"EPSG:4326\"}}}", + polygon.asGeoJson()); + geometrySb.append(polygon.asGeoJson()).append("]}"); + + List geoms = new ArrayList(3); + geoms.add(point); + geoms.add(line); + geoms.add(polygon); + OGCConcreteGeometryCollection collection = new OGCConcreteGeometryCollection( + geoms, sr); + String s2 = collection.asGeoJson(); + + assertEquals("{\"type\":\"GeometryCollection\",\"geometries\":[{\"type\":\"Point\",\"coordinates\":[1,1]},{\"type\":\"LineString\",\"coordinates\":[[1,1],[2,2]]},{\"type\":\"Polygon\",\"coordinates\":[[[1,1],[2,0],[3,1],[2,2],[1,1]]]}],\"crs\":{\"type\":\"name\",\"properties\":{\"name\":\"EPSG:4326\"}}}", collection.asGeoJson()); + } + + @Test + public void testEmptyGeometryCollection() { + SpatialReference sr = SpatialReference.create(4326); + OGCConcreteGeometryCollection collection = new OGCConcreteGeometryCollection( + new ArrayList(), sr); + String s2 = collection.asGeoJson(); + assertEquals( + "{\"type\":\"GeometryCollection\",\"geometries\":[],\"crs\":{\"type\":\"name\",\"properties\":{\"name\":\"EPSG:4326\"}}}", + collection.asGeoJson()); + } + + //Envelope is exported as a polygon (we don't support bbox, as it is not a GeoJson geometry, but simply a field)! + @Test + public void testEnvelope() { + Envelope e = new Envelope(); + e.setCoords(-180.0, -90.0, 180.0, 90.0); + String result = OperatorExportToGeoJson.local().execute(e); + assertEquals("{\"type\":\"Polygon\",\"coordinates\":[[[-180,-90],[180,-90],[180,90],[-180,90],[-180,-90]]]}", result); + } + + @Test + public void testEmptyEnvelope() { + Envelope e = new Envelope(); + String result = OperatorExportToGeoJson.local().execute(e); + assertEquals("{\"type\":\"Polygon\",\"coordinates\":[]}", result); + } + + @Test + public void testEnvelopeGeometryEngine() { + Envelope e = new Envelope(); + e.setCoords(-180.0, -90.0, 180.0, 90.0); + String result = GeometryEngine.geometryToGeoJson(e); + assertEquals("{\"type\":\"Polygon\",\"coordinates\":[[[-180,-90],[180,-90],[180,90],[-180,90],[-180,-90]]]}", result); + } + + @Test + public void testOldCRS() { + String inputStr = "{\"type\":\"Polygon\",\"coordinates\":[[[-180,-90],[180,-90],[180,90],[-180,90],[-180,-90]]], \"crs\":\"EPSG:4267\"}"; + MapGeometry mg = OperatorImportFromGeoJson.local().execute(GeoJsonImportFlags.geoJsonImportDefaults, Geometry.Type.Unknown, inputStr, null); + String result = GeometryEngine.geometryToGeoJson(mg.getSpatialReference(), mg.getGeometry()); + assertEquals("{\"type\":\"Polygon\",\"coordinates\":[[[-180,-90],[180,-90],[180,90],[-180,90],[-180,-90]]],\"crs\":{\"type\":\"name\",\"properties\":{\"name\":\"EPSG:4267\"}}}", result); + } + + // bbox is not supported anymore. + // @Test + // public void testEnvelope() { + // Envelope e = new Envelope(); + // e.setCoords(-180.0, -90.0, 180.0, 90.0); + // OperatorExportToGeoJson exporter = (OperatorExportToGeoJson) factory.getOperator(Operator.Type.ExportToGeoJson); + // String result = exporter.execute(e); + // assertEquals("{\"bbox\":[-180.0,-90.0,180.0,90.0]}", result); + // } + // + // @Test + // public void testEmptyEnvelope() { + // Envelope e = new Envelope(); + // OperatorExportToGeoJson exporter = (OperatorExportToGeoJson) factory.getOperator(Operator.Type.ExportToGeoJson); + // String result = exporter.execute(e); + // assertEquals("{\"bbox\":null}", result); + // } + // + // @Test + // public void testEnvelopeGeometryEngine() { + // Envelope e = new Envelope(); + // e.setCoords(-180.0, -90.0, 180.0, 90.0); + // String result = GeometryEngine.geometryToGeoJson(e); + // assertEquals("{\"bbox\":[-180.0,-90.0,180.0,90.0]}", result); + // } } diff --git a/src/test/java/com/esri/core/geometry/TestGeomToJSonExportSRFromWkiOrWkt_CR181369.java b/src/test/java/com/esri/core/geometry/TestGeomToJSonExportSRFromWkiOrWkt_CR181369.java index 3055d990..672f033a 100644 --- a/src/test/java/com/esri/core/geometry/TestGeomToJSonExportSRFromWkiOrWkt_CR181369.java +++ b/src/test/java/com/esri/core/geometry/TestGeomToJSonExportSRFromWkiOrWkt_CR181369.java @@ -33,557 +33,557 @@ import java.io.IOException; public class TestGeomToJSonExportSRFromWkiOrWkt_CR181369 extends TestCase { - @Override - protected void setUp() throws Exception { - super.setUp(); - } - - @Override - protected void tearDown() throws Exception { - super.tearDown(); - } - - JsonFactory factory = new JsonFactory(); - SpatialReference spatialReferenceWebMerc1 = SpatialReference.create(102100); - SpatialReference spatialReferenceWebMerc2 = SpatialReference - .create(spatialReferenceWebMerc1.getLatestID()); - SpatialReference spatialReferenceWGS84 = SpatialReference.create(4326); - - @Test - public void testLocalExport() - throws JsonParseException, IOException { - String s = OperatorExportToJson.local().execute(null, new Point(1000000.2, 2000000.3)); - //assertTrue(s.contains(".")); - //assertFalse(s.contains(",")); - Polyline line = new Polyline(); - line.startPath(1.1, 2.2); - line.lineTo(2.3, 4.5); - String s1 = OperatorExportToJson.local().execute(null, line); - assertTrue(s.contains(".")); - } - - boolean testPoint() throws JsonParseException, IOException { - boolean bAnswer = true; - Point point1 = new Point(10.0, 20.0); - Point pointEmpty = new Point(); - { - JsonParser pointWebMerc1Parser = factory - .createParser(GeometryEngine.geometryToJson( - spatialReferenceWebMerc1, point1)); - MapGeometry pointWebMerc1MP = GeometryEngine - .jsonToGeometry(pointWebMerc1Parser); - assertTrue(point1.getX() == ((Point) pointWebMerc1MP.getGeometry()) - .getX()); - assertTrue(point1.getY() == ((Point) pointWebMerc1MP.getGeometry()) - .getY()); - assertTrue(spatialReferenceWebMerc1.getID() == pointWebMerc1MP - .getSpatialReference().getID() - || pointWebMerc1MP.getSpatialReference().getID() == 3857); - - if (!checkResultSpatialRef(pointWebMerc1MP, 102100, 3857)) { - bAnswer = false; - } - - pointWebMerc1Parser = factory.createParser(GeometryEngine - .geometryToJson(null, point1)); - pointWebMerc1MP = GeometryEngine - .jsonToGeometry(pointWebMerc1Parser); - assertTrue(null == pointWebMerc1MP.getSpatialReference()); - - if (pointWebMerc1MP.getSpatialReference() != null) { - if (!checkResultSpatialRef(pointWebMerc1MP, 102100, 3857)) { - bAnswer = false; - } - } - - String pointEmptyString = GeometryEngine.geometryToJson( - spatialReferenceWebMerc1, pointEmpty); - pointWebMerc1Parser = factory.createParser(pointEmptyString); - } - - JsonParser pointWebMerc2Parser = factory - .createParser(GeometryEngine.geometryToJson( - spatialReferenceWebMerc2, point1)); - MapGeometry pointWebMerc2MP = GeometryEngine - .jsonToGeometry(pointWebMerc2Parser); - assertTrue(point1.getX() == ((Point) pointWebMerc2MP.getGeometry()) - .getX()); - assertTrue(point1.getY() == ((Point) pointWebMerc2MP.getGeometry()) - .getY()); - assertTrue(spatialReferenceWebMerc2.getLatestID() == pointWebMerc2MP - .getSpatialReference().getLatestID()); - if (!checkResultSpatialRef(pointWebMerc2MP, - spatialReferenceWebMerc2.getLatestID(), 0)) { - bAnswer = false; - } - - { - JsonParser pointWgs84Parser = factory - .createParser(GeometryEngine.geometryToJson( - spatialReferenceWGS84, point1)); - MapGeometry pointWgs84MP = GeometryEngine - .jsonToGeometry(pointWgs84Parser); - assertTrue(point1.getX() == ((Point) pointWgs84MP.getGeometry()) - .getX()); - assertTrue(point1.getY() == ((Point) pointWgs84MP.getGeometry()) - .getY()); - assertTrue(spatialReferenceWGS84.getID() == pointWgs84MP - .getSpatialReference().getID()); - if (!checkResultSpatialRef(pointWgs84MP, 4326, 0)) { - bAnswer = false; - } - } - - { - Point p = new Point(); - String s = GeometryEngine.geometryToJson(spatialReferenceWebMerc1, - p); - assertTrue(s - .equals("{\"x\":null,\"y\":null,\"spatialReference\":{\"wkid\":102100,\"latestWkid\":3857}}")); - - p.addAttribute(VertexDescription.Semantics.Z); - p.addAttribute(VertexDescription.Semantics.M); - s = GeometryEngine.geometryToJson(spatialReferenceWebMerc1, p); - assertTrue(s - .equals("{\"x\":null,\"y\":null,\"z\":null,\"m\":null,\"spatialReference\":{\"wkid\":102100,\"latestWkid\":3857}}")); - - } - - { - Point p = new Point(10.0, 20.0, 30.0); - p.addAttribute(VertexDescription.Semantics.M); - String s = GeometryEngine.geometryToJson(spatialReferenceWebMerc1, - p); - assertTrue(s - .equals("{\"x\":10,\"y\":20,\"z\":30,\"m\":null,\"spatialReference\":{\"wkid\":102100,\"latestWkid\":3857}}")); - } - - {// import - String s = "{\"x\":0.0,\"y\":1.0,\"z\":5.0,\"m\":11.0,\"spatialReference\":{\"wkid\":102100,\"latestWkid\":3857}}"; - JsonParser parser = factory.createParser(s); - MapGeometry map_pt = GeometryEngine.jsonToGeometry(parser); - Point pt = (Point) map_pt.getGeometry(); - assertTrue(pt.getX() == 0.0); - assertTrue(pt.getY() == 1.0); - assertTrue(pt.getZ() == 5.0); - assertTrue(pt.getM() == 11.0); - } - - { - String s = "{\"x\" : 5.0, \"y\" : null, \"spatialReference\" : {\"wkid\" : 4326}} "; - JsonParser parser = factory.createParser(s); - MapGeometry map_pt = GeometryEngine.jsonToGeometry(parser); - Point pt = (Point) map_pt.getGeometry(); - assertTrue(pt.isEmpty()); - SpatialReference spatial_reference = map_pt.getSpatialReference(); - assertTrue(spatial_reference.getID() == 4326); - } - - return bAnswer; - } - - boolean testMultiPoint() throws JsonParseException, IOException { - boolean bAnswer = true; - - MultiPoint multiPoint1 = new MultiPoint(); - multiPoint1.add(-97.06138, 32.837); - multiPoint1.add(-97.06133, 32.836); - multiPoint1.add(-97.06124, 32.834); - multiPoint1.add(-97.06127, 32.832); - - { - String s = GeometryEngine.geometryToJson(spatialReferenceWGS84, - multiPoint1); - JsonParser mPointWgs84Parser = factory.createParser(s); - MapGeometry mPointWgs84MP = GeometryEngine - .jsonToGeometry(mPointWgs84Parser); - assertTrue(multiPoint1.getPointCount() == ((MultiPoint) mPointWgs84MP - .getGeometry()).getPointCount()); - assertTrue(multiPoint1.getPoint(0).getX() == ((MultiPoint) mPointWgs84MP - .getGeometry()).getPoint(0).getX()); - assertTrue(multiPoint1.getPoint(0).getY() == ((MultiPoint) mPointWgs84MP - .getGeometry()).getPoint(0).getY()); - int lastIndex = multiPoint1.getPointCount() - 1; - assertTrue(multiPoint1.getPoint(lastIndex).getX() == ((MultiPoint) mPointWgs84MP - .getGeometry()).getPoint(lastIndex).getX()); - assertTrue(multiPoint1.getPoint(lastIndex).getY() == ((MultiPoint) mPointWgs84MP - .getGeometry()).getPoint(lastIndex).getY()); - - assertTrue(spatialReferenceWGS84.getID() == mPointWgs84MP - .getSpatialReference().getID()); - if (!checkResultSpatialRef(mPointWgs84MP, 4326, 0)) { - bAnswer = false; - } - - } - - { - MultiPoint p = new MultiPoint(); - p.addAttribute(VertexDescription.Semantics.Z); - p.addAttribute(VertexDescription.Semantics.M); - String s = GeometryEngine.geometryToJson(spatialReferenceWebMerc1, - p); - assertTrue(s - .equals("{\"hasZ\":true,\"hasM\":true,\"points\":[],\"spatialReference\":{\"wkid\":102100,\"latestWkid\":3857}}")); - - p.add(10.0, 20.0, 30.0); - p.add(20.0, 40.0, 60.0); - s = GeometryEngine.geometryToJson(spatialReferenceWebMerc1, p); - assertTrue(s - .equals("{\"hasZ\":true,\"hasM\":true,\"points\":[[10,20,30,null],[20,40,60,null]],\"spatialReference\":{\"wkid\":102100,\"latestWkid\":3857}}")); - } - { - String points = "{\"hasM\" : false, \"hasZ\" : true, \"uncle remus\" : null, \"points\" : [ [0,0,1], [0.0,10.0,1], [10.0,10.0,1], [10.0,0.0,1, 6666] ],\"spatialReference\" : {\"wkid\" : 4326}}"; - MapGeometry mp = GeometryEngine.jsonToGeometry(factory - .createParser(points)); - MultiPoint multipoint = (MultiPoint) mp.getGeometry(); - assertTrue(multipoint.getPointCount() == 4); - Point2D point2d; - point2d = multipoint.getXY(0); - assertTrue(point2d.x == 0.0 && point2d.y == 0.0); - point2d = multipoint.getXY(1); - assertTrue(point2d.x == 0.0 && point2d.y == 10.0); - point2d = multipoint.getXY(2); - assertTrue(point2d.x == 10.0 && point2d.y == 10.0); - point2d = multipoint.getXY(3); - assertTrue(point2d.x == 10.0 && point2d.y == 0.0); - assertTrue(multipoint.hasAttribute(VertexDescription.Semantics.Z)); - assertTrue(!multipoint.hasAttribute(VertexDescription.Semantics.M)); - double z = multipoint.getAttributeAsDbl( - VertexDescription.Semantics.Z, 0, 0); - assertTrue(z == 1); - SpatialReference spatial_reference = mp.getSpatialReference(); - assertTrue(spatial_reference.getID() == 4326); - } - - return bAnswer; - } - - boolean testPolyline() throws JsonParseException, IOException { - boolean bAnswer = true; - - Polyline polyline = new Polyline(); - polyline.startPath(-97.06138, 32.837); - polyline.lineTo(-97.06133, 32.836); - polyline.lineTo(-97.06124, 32.834); - polyline.lineTo(-97.06127, 32.832); - - polyline.startPath(-97.06326, 32.759); - polyline.lineTo(-97.06298, 32.755); - - { - JsonParser polylinePathsWgs84Parser = factory - .createParser(GeometryEngine.geometryToJson( - spatialReferenceWGS84, polyline)); - MapGeometry mPolylineWGS84MP = GeometryEngine - .jsonToGeometry(polylinePathsWgs84Parser); - - assertTrue(polyline.getPointCount() == ((Polyline) mPolylineWGS84MP - .getGeometry()).getPointCount()); - assertTrue(polyline.getPoint(0).getX() == ((Polyline) mPolylineWGS84MP - .getGeometry()).getPoint(0).getX()); - assertTrue(polyline.getPoint(0).getY() == ((Polyline) mPolylineWGS84MP - .getGeometry()).getPoint(0).getY()); - - assertTrue(polyline.getPathCount() == ((Polyline) mPolylineWGS84MP - .getGeometry()).getPathCount()); - assertTrue(polyline.getSegmentCount() == ((Polyline) mPolylineWGS84MP - .getGeometry()).getSegmentCount()); - assertTrue(polyline.getSegmentCount(0) == ((Polyline) mPolylineWGS84MP - .getGeometry()).getSegmentCount(0)); - assertTrue(polyline.getSegmentCount(1) == ((Polyline) mPolylineWGS84MP - .getGeometry()).getSegmentCount(1)); - - int lastIndex = polyline.getPointCount() - 1; - assertTrue(polyline.getPoint(lastIndex).getX() == ((Polyline) mPolylineWGS84MP - .getGeometry()).getPoint(lastIndex).getX()); - assertTrue(polyline.getPoint(lastIndex).getY() == ((Polyline) mPolylineWGS84MP - .getGeometry()).getPoint(lastIndex).getY()); - - assertTrue(spatialReferenceWGS84.getID() == mPolylineWGS84MP - .getSpatialReference().getID()); - - if (!checkResultSpatialRef(mPolylineWGS84MP, 4326, 0)) { - bAnswer = false; - } - } - - { - Polyline p = new Polyline(); - p.addAttribute(VertexDescription.Semantics.Z); - p.addAttribute(VertexDescription.Semantics.M); - String s = GeometryEngine.geometryToJson(spatialReferenceWebMerc1, - p); - assertTrue(s - .equals("{\"hasZ\":true,\"hasM\":true,\"paths\":[],\"spatialReference\":{\"wkid\":102100,\"latestWkid\":3857}}")); - - p.startPath(0, 0); - p.lineTo(0, 1); - p.startPath(2, 2); - p.lineTo(3, 3); - - p.setAttribute(VertexDescription.Semantics.Z, 0, 0, 3); - p.setAttribute(VertexDescription.Semantics.M, 1, 0, 5); - s = GeometryEngine.geometryToJson(spatialReferenceWebMerc1, p); - assertTrue(s - .equals("{\"hasZ\":true,\"hasM\":true,\"paths\":[[[0,0,3,null],[0,1,0,5]],[[2,2,0,null],[3,3,0,null]]],\"spatialReference\":{\"wkid\":102100,\"latestWkid\":3857}}")); - } - - { - String paths = "{\"hasZ\" : true, \"paths\" : [ [ [0.0, 0.0,3], [0, 10.0,3], [10.0, 10.0,3, 6666], [10.0, 0.0,3, 6666] ], [ [1.0, 1,3], [1.0, 9.0,3], [9.0, 9.0,3], [1.0, 9.0,3] ] ], \"spatialReference\" : {\"wkid\" : 4326}, \"hasM\" : false}"; - MapGeometry mapGeometry = GeometryEngine.jsonToGeometry(factory - .createParser(paths)); - Polyline p = (Polyline) mapGeometry.getGeometry(); - assertTrue(p.getPathCount() == 2); - @SuppressWarnings("unused") - int count = p.getPathCount(); - assertTrue(p.getPointCount() == 8); - assertTrue(p.hasAttribute(VertexDescription.Semantics.Z)); - assertTrue(!p.hasAttribute(VertexDescription.Semantics.M)); - double z = p.getAttributeAsDbl(VertexDescription.Semantics.Z, 0, 0); - assertTrue(z == 3); - double length = p.calculateLength2D(); - assertTrue(Math.abs(length - 54.0) <= 0.001); - SpatialReference spatial_reference = mapGeometry - .getSpatialReference(); - assertTrue(spatial_reference.getID() == 4326); - } - - return bAnswer; - } - - boolean testPolygon() throws JsonParseException, IOException { - boolean bAnswer = true; - - Polygon polygon = new Polygon(); - polygon.startPath(-97.06138, 32.837); - polygon.lineTo(-97.06133, 32.836); - polygon.lineTo(-97.06124, 32.834); - polygon.lineTo(-97.06127, 32.832); - - polygon.startPath(-97.06326, 32.759); - polygon.lineTo(-97.06298, 32.755); - - { - JsonParser polygonPathsWgs84Parser = factory - .createParser(GeometryEngine.geometryToJson( - spatialReferenceWGS84, polygon)); - MapGeometry mPolygonWGS84MP = GeometryEngine - .jsonToGeometry(polygonPathsWgs84Parser); - - assertTrue(polygon.getPointCount() + 1 == ((Polygon) mPolygonWGS84MP - .getGeometry()).getPointCount()); - assertTrue(polygon.getPoint(0).getX() == ((Polygon) mPolygonWGS84MP - .getGeometry()).getPoint(0).getX()); - assertTrue(polygon.getPoint(0).getY() == ((Polygon) mPolygonWGS84MP - .getGeometry()).getPoint(0).getY()); - - assertTrue(polygon.getPathCount() == ((Polygon) mPolygonWGS84MP - .getGeometry()).getPathCount()); - assertTrue(polygon.getSegmentCount() + 1 == ((Polygon) mPolygonWGS84MP - .getGeometry()).getSegmentCount()); - assertTrue(polygon.getSegmentCount(0) == ((Polygon) mPolygonWGS84MP - .getGeometry()).getSegmentCount(0)); - assertTrue(polygon.getSegmentCount(1) + 1 == ((Polygon) mPolygonWGS84MP - .getGeometry()).getSegmentCount(1)); - - int lastIndex = polygon.getPointCount() - 1; - assertTrue(polygon.getPoint(lastIndex).getX() == ((Polygon) mPolygonWGS84MP - .getGeometry()).getPoint(lastIndex).getX()); - assertTrue(polygon.getPoint(lastIndex).getY() == ((Polygon) mPolygonWGS84MP - .getGeometry()).getPoint(lastIndex).getY()); - - assertTrue(spatialReferenceWGS84.getID() == mPolygonWGS84MP - .getSpatialReference().getID()); - - if (!checkResultSpatialRef(mPolygonWGS84MP, 4326, 0)) { - bAnswer = false; - } - } - - { - Polygon p = new Polygon(); - p.addAttribute(VertexDescription.Semantics.Z); - p.addAttribute(VertexDescription.Semantics.M); - String s = GeometryEngine.geometryToJson(spatialReferenceWebMerc1, - p); - assertTrue(s - .equals("{\"hasZ\":true,\"hasM\":true,\"rings\":[],\"spatialReference\":{\"wkid\":102100,\"latestWkid\":3857}}")); - - p.startPath(0, 0); - p.lineTo(0, 1); - p.lineTo(4, 4); - p.startPath(2, 2); - p.lineTo(3, 3); - p.lineTo(7, 8); - - p.setAttribute(VertexDescription.Semantics.Z, 0, 0, 3); - p.setAttribute(VertexDescription.Semantics.M, 1, 0, 7); - p.setAttribute(VertexDescription.Semantics.M, 2, 0, 5); - p.setAttribute(VertexDescription.Semantics.M, 5, 0, 5); - s = GeometryEngine.geometryToJson(spatialReferenceWebMerc1, p); - assertTrue(s - .equals("{\"hasZ\":true,\"hasM\":true,\"rings\":[[[0,0,3,null],[0,1,0,7],[4,4,0,5],[0,0,3,null]],[[2,2,0,null],[3,3,0,null],[7,8,0,5],[2,2,0,null]]],\"spatialReference\":{\"wkid\":102100,\"latestWkid\":3857}}")); - } - - { - // Test Import Polygon from Polygon - String rings = "{\"hasZ\": true, \"rings\" : [ [ [0,0, 5], [0.0, 10.0, 5], [10.0,10.0, 5, 66666], [10.0,0.0, 5] ], [ [12, 12] ], [ [13 , 17], [13 , 17] ], [ [1.0, 1.0, 5, 66666], [9.0,1.0, 5], [9.0,9.0, 5], [1.0,9.0, 5], [1.0, 1.0, 5] ] ] }"; - MapGeometry mapGeometry = GeometryEngine.jsonToGeometry(factory - .createParser(rings)); - Polygon p = (Polygon) mapGeometry.getGeometry(); - @SuppressWarnings("unused") - double area = p.calculateArea2D(); - @SuppressWarnings("unused") - double length = p.calculateLength2D(); - assertTrue(p.getPathCount() == 4); - int count = p.getPointCount(); - assertTrue(count == 15); - assertTrue(p.hasAttribute(VertexDescription.Semantics.Z)); - assertTrue(!p.hasAttribute(VertexDescription.Semantics.M)); - } - - return bAnswer; - } - - boolean testEnvelope() throws JsonParseException, IOException { - boolean bAnswer = true; - - Envelope envelope = new Envelope(); - envelope.setCoords(-109.55, 25.76, -86.39, 49.94); - - { - JsonParser envelopeWGS84Parser = factory - .createParser(GeometryEngine.geometryToJson( - spatialReferenceWGS84, envelope)); - MapGeometry envelopeWGS84MP = GeometryEngine - .jsonToGeometry(envelopeWGS84Parser); - assertTrue(envelope.isEmpty() == envelopeWGS84MP.getGeometry() - .isEmpty()); - assertTrue(envelope.getXMax() == ((Envelope) envelopeWGS84MP - .getGeometry()).getXMax()); - assertTrue(envelope.getYMax() == ((Envelope) envelopeWGS84MP - .getGeometry()).getYMax()); - assertTrue(envelope.getXMin() == ((Envelope) envelopeWGS84MP - .getGeometry()).getXMin()); - assertTrue(envelope.getYMin() == ((Envelope) envelopeWGS84MP - .getGeometry()).getYMin()); - assertTrue(spatialReferenceWGS84.getID() == envelopeWGS84MP - .getSpatialReference().getID()); - if (!checkResultSpatialRef(envelopeWGS84MP, 4326, 0)) { - bAnswer = false; - } - } - - {// export - Envelope e = new Envelope(); - e.addAttribute(VertexDescription.Semantics.Z); - e.addAttribute(VertexDescription.Semantics.M); - String s = GeometryEngine.geometryToJson(spatialReferenceWebMerc1, - e); - assertTrue(s - .equals("{\"xmin\":null,\"ymin\":null,\"xmax\":null,\"ymax\":null,\"zmin\":null,\"zmax\":null,\"mmin\":null,\"mmax\":null,\"spatialReference\":{\"wkid\":102100,\"latestWkid\":3857}}")); - - e.setCoords(0, 1, 2, 3); - - Envelope1D z = new Envelope1D(); - Envelope1D m = new Envelope1D(); - z.setCoords(5, 7); - m.setCoords(11, 13); - - e.setInterval(VertexDescription.Semantics.Z, 0, z); - e.setInterval(VertexDescription.Semantics.M, 0, m); - s = GeometryEngine.geometryToJson(spatialReferenceWebMerc1, e); - assertTrue(s - .equals("{\"xmin\":0,\"ymin\":1,\"xmax\":2,\"ymax\":3,\"zmin\":5,\"zmax\":7,\"mmin\":11,\"mmax\":13,\"spatialReference\":{\"wkid\":102100,\"latestWkid\":3857}}")); - } - - {// import - String s = "{\"xmin\":0.0,\"ymin\":1.0,\"xmax\":2.0,\"ymax\":3.0,\"zmin\":5.0,\"zmax\":7.0,\"mmin\":11.0,\"mmax\":13.0,\"spatialReference\":{\"wkid\":102100,\"latestWkid\":3857}}"; - JsonParser parser = factory.createParser(s); - MapGeometry map_env = GeometryEngine.jsonToGeometry(parser); - Envelope env = (Envelope) map_env.getGeometry(); - Envelope1D z = env.queryInterval(VertexDescription.Semantics.Z, 0); - Envelope1D m = env.queryInterval(VertexDescription.Semantics.M, 0); - assertTrue(z.vmin == 5.0); - assertTrue(z.vmax == 7.0); - assertTrue(m.vmin == 11.0); - assertTrue(m.vmax == 13.0); - } - - { - String s = "{ \"zmin\" : 33, \"xmin\" : -109.55, \"zmax\" : 53, \"ymin\" : 25.76, \"xmax\" : -86.39, \"ymax\" : 49.94, \"mmax\" : 13}"; - JsonParser parser = factory.createParser(s); - MapGeometry map_env = GeometryEngine.jsonToGeometry(parser); - Envelope env = (Envelope) map_env.getGeometry(); - Envelope2D e = new Envelope2D(); - env.queryEnvelope2D(e); - assertTrue(e.xmin == -109.55 && e.ymin == 25.76 && e.xmax == -86.39 - && e.ymax == 49.94); - - Envelope1D e1D; - assertTrue(env.hasAttribute(VertexDescription.Semantics.Z)); - e1D = env.queryInterval(VertexDescription.Semantics.Z, 0); - assertTrue(e1D.vmin == 33 && e1D.vmax == 53); - - assertTrue(!env.hasAttribute(VertexDescription.Semantics.M)); - } - - return bAnswer; - } - - boolean testCR181369() throws JsonParseException, IOException { - // CR181369 - boolean bAnswer = true; - - String jsonStringPointAndWKT = "{\"x\":10.0,\"y\":20.0,\"spatialReference\":{\"wkt\" : \"PROJCS[\\\"NAD83_UTM_zone_15N\\\",GEOGCS[\\\"GCS_North_American_1983\\\",DATUM[\\\"D_North_American_1983\\\",SPHEROID[\\\"GRS_1980\\\",6378137.0,298.257222101]],PRIMEM[\\\"Greenwich\\\",0.0],UNIT[\\\"Degree\\\",0.0174532925199433]],PROJECTION[\\\"Transverse_Mercator\\\"],PARAMETER[\\\"false_easting\\\",500000.0],PARAMETER[\\\"false_northing\\\",0.0],PARAMETER[\\\"central_meridian\\\",-93.0],PARAMETER[\\\"scale_factor\\\",0.9996],PARAMETER[\\\"latitude_of_origin\\\",0.0],UNIT[\\\"Meter\\\",1.0]]\"} }"; - JsonParser jsonParserPointAndWKT = factory - .createParser(jsonStringPointAndWKT); - MapGeometry mapGeom2 = GeometryEngine - .jsonToGeometry(jsonParserPointAndWKT); - String jsonStringPointAndWKT2 = GeometryEngine.geometryToJson( - mapGeom2.getSpatialReference(), mapGeom2.getGeometry()); - JsonParser jsonParserPointAndWKT2 = factory - .createParser(jsonStringPointAndWKT2); - MapGeometry mapGeom3 = GeometryEngine - .jsonToGeometry(jsonParserPointAndWKT2); - assertTrue(((Point) mapGeom2.getGeometry()).getX() == ((Point) mapGeom3 - .getGeometry()).getX()); - assertTrue(((Point) mapGeom2.getGeometry()).getY() == ((Point) mapGeom3 - .getGeometry()).getY()); - - String s1 = mapGeom2.getSpatialReference().getText(); - String s2 = mapGeom3.getSpatialReference().getText(); - assertTrue(s1.equals(s2)); - - int id2 = mapGeom2.getSpatialReference().getID(); - int id3 = mapGeom3.getSpatialReference().getID(); - assertTrue(id2 == id3); - if (!checkResultSpatialRef(mapGeom3, mapGeom2.getSpatialReference() - .getID(), 0)) { - bAnswer = false; - } - return bAnswer; - } - - boolean checkResultSpatialRef(MapGeometry mapGeometry, int expectWki1, - int expectWki2) { - SpatialReference sr = mapGeometry.getSpatialReference(); - String Wkt = sr.getText(); - int wki1 = sr.getLatestID(); - if (!(wki1 == expectWki1 || wki1 == expectWki2)) - return false; - if (!(Wkt != null && Wkt.length() > 0)) - return false; - SpatialReference sr2 = SpatialReference.create(Wkt); - int wki2 = sr2.getID(); - if (expectWki2 > 0) { - if (!(wki2 == expectWki1 || wki2 == expectWki2)) - return false; - } else { - if (!(wki2 == expectWki1)) - return false; - } - return true; - } + @Override + protected void setUp() throws Exception { + super.setUp(); + } + + @Override + protected void tearDown() throws Exception { + super.tearDown(); + } + + JsonFactory factory = new JsonFactory(); + SpatialReference spatialReferenceWebMerc1 = SpatialReference.create(102100); + SpatialReference spatialReferenceWebMerc2 = SpatialReference + .create(spatialReferenceWebMerc1.getLatestID()); + SpatialReference spatialReferenceWGS84 = SpatialReference.create(4326); + + @Test + public void testLocalExport() + throws JsonParseException, IOException { + String s = OperatorExportToJson.local().execute(null, new Point(1000000.2, 2000000.3)); + //assertTrue(s.contains(".")); + //assertFalse(s.contains(",")); + Polyline line = new Polyline(); + line.startPath(1.1, 2.2); + line.lineTo(2.3, 4.5); + String s1 = OperatorExportToJson.local().execute(null, line); + assertTrue(s.contains(".")); + } + + boolean testPoint() throws JsonParseException, IOException { + boolean bAnswer = true; + Point point1 = new Point(10.0, 20.0); + Point pointEmpty = new Point(); + { + JsonParser pointWebMerc1Parser = factory + .createParser(GeometryEngine.geometryToJson( + spatialReferenceWebMerc1, point1)); + MapGeometry pointWebMerc1MP = GeometryEngine + .jsonToGeometry(pointWebMerc1Parser); + assertTrue(point1.getX() == ((Point) pointWebMerc1MP.getGeometry()) + .getX()); + assertTrue(point1.getY() == ((Point) pointWebMerc1MP.getGeometry()) + .getY()); + assertTrue(spatialReferenceWebMerc1.getID() == pointWebMerc1MP + .getSpatialReference().getID() + || pointWebMerc1MP.getSpatialReference().getID() == 3857); + + if (!checkResultSpatialRef(pointWebMerc1MP, 102100, 3857)) { + bAnswer = false; + } + + pointWebMerc1Parser = factory.createParser(GeometryEngine + .geometryToJson(null, point1)); + pointWebMerc1MP = GeometryEngine + .jsonToGeometry(pointWebMerc1Parser); + assertTrue(null == pointWebMerc1MP.getSpatialReference()); + + if (pointWebMerc1MP.getSpatialReference() != null) { + if (!checkResultSpatialRef(pointWebMerc1MP, 102100, 3857)) { + bAnswer = false; + } + } + + String pointEmptyString = GeometryEngine.geometryToJson( + spatialReferenceWebMerc1, pointEmpty); + pointWebMerc1Parser = factory.createParser(pointEmptyString); + } + + JsonParser pointWebMerc2Parser = factory + .createParser(GeometryEngine.geometryToJson( + spatialReferenceWebMerc2, point1)); + MapGeometry pointWebMerc2MP = GeometryEngine + .jsonToGeometry(pointWebMerc2Parser); + assertTrue(point1.getX() == ((Point) pointWebMerc2MP.getGeometry()) + .getX()); + assertTrue(point1.getY() == ((Point) pointWebMerc2MP.getGeometry()) + .getY()); + assertTrue(spatialReferenceWebMerc2.getLatestID() == pointWebMerc2MP + .getSpatialReference().getLatestID()); + if (!checkResultSpatialRef(pointWebMerc2MP, + spatialReferenceWebMerc2.getLatestID(), 0)) { + bAnswer = false; + } + + { + JsonParser pointWgs84Parser = factory + .createParser(GeometryEngine.geometryToJson( + spatialReferenceWGS84, point1)); + MapGeometry pointWgs84MP = GeometryEngine + .jsonToGeometry(pointWgs84Parser); + assertTrue(point1.getX() == ((Point) pointWgs84MP.getGeometry()) + .getX()); + assertTrue(point1.getY() == ((Point) pointWgs84MP.getGeometry()) + .getY()); + assertTrue(spatialReferenceWGS84.getID() == pointWgs84MP + .getSpatialReference().getID()); + if (!checkResultSpatialRef(pointWgs84MP, 4326, 0)) { + bAnswer = false; + } + } + + { + Point p = new Point(); + String s = GeometryEngine.geometryToJson(spatialReferenceWebMerc1, + p); + assertTrue(s + .equals("{\"x\":null,\"y\":null,\"spatialReference\":{\"wkid\":102100,\"latestWkid\":3857}}")); + + p.addAttribute(VertexDescription.Semantics.Z); + p.addAttribute(VertexDescription.Semantics.M); + s = GeometryEngine.geometryToJson(spatialReferenceWebMerc1, p); + assertTrue(s + .equals("{\"x\":null,\"y\":null,\"z\":null,\"m\":null,\"spatialReference\":{\"wkid\":102100,\"latestWkid\":3857}}")); + + } + + { + Point p = new Point(10.0, 20.0, 30.0); + p.addAttribute(VertexDescription.Semantics.M); + String s = GeometryEngine.geometryToJson(spatialReferenceWebMerc1, + p); + assertTrue(s + .equals("{\"x\":10,\"y\":20,\"z\":30,\"m\":null,\"spatialReference\":{\"wkid\":102100,\"latestWkid\":3857}}")); + } + + {// import + String s = "{\"x\":0.0,\"y\":1.0,\"z\":5.0,\"m\":11.0,\"spatialReference\":{\"wkid\":102100,\"latestWkid\":3857}}"; + JsonParser parser = factory.createParser(s); + MapGeometry map_pt = GeometryEngine.jsonToGeometry(parser); + Point pt = (Point) map_pt.getGeometry(); + assertTrue(pt.getX() == 0.0); + assertTrue(pt.getY() == 1.0); + assertTrue(pt.getZ() == 5.0); + assertTrue(pt.getM() == 11.0); + } + + { + String s = "{\"x\" : 5.0, \"y\" : null, \"spatialReference\" : {\"wkid\" : 4326}} "; + JsonParser parser = factory.createParser(s); + MapGeometry map_pt = GeometryEngine.jsonToGeometry(parser); + Point pt = (Point) map_pt.getGeometry(); + assertTrue(pt.isEmpty()); + SpatialReference spatial_reference = map_pt.getSpatialReference(); + assertTrue(spatial_reference.getID() == 4326); + } + + return bAnswer; + } + + boolean testMultiPoint() throws JsonParseException, IOException { + boolean bAnswer = true; + + MultiPoint multiPoint1 = new MultiPoint(); + multiPoint1.add(-97.06138, 32.837); + multiPoint1.add(-97.06133, 32.836); + multiPoint1.add(-97.06124, 32.834); + multiPoint1.add(-97.06127, 32.832); + + { + String s = GeometryEngine.geometryToJson(spatialReferenceWGS84, + multiPoint1); + JsonParser mPointWgs84Parser = factory.createParser(s); + MapGeometry mPointWgs84MP = GeometryEngine + .jsonToGeometry(mPointWgs84Parser); + assertTrue(multiPoint1.getPointCount() == ((MultiPoint) mPointWgs84MP + .getGeometry()).getPointCount()); + assertTrue(multiPoint1.getPoint(0).getX() == ((MultiPoint) mPointWgs84MP + .getGeometry()).getPoint(0).getX()); + assertTrue(multiPoint1.getPoint(0).getY() == ((MultiPoint) mPointWgs84MP + .getGeometry()).getPoint(0).getY()); + int lastIndex = multiPoint1.getPointCount() - 1; + assertTrue(multiPoint1.getPoint(lastIndex).getX() == ((MultiPoint) mPointWgs84MP + .getGeometry()).getPoint(lastIndex).getX()); + assertTrue(multiPoint1.getPoint(lastIndex).getY() == ((MultiPoint) mPointWgs84MP + .getGeometry()).getPoint(lastIndex).getY()); + + assertTrue(spatialReferenceWGS84.getID() == mPointWgs84MP + .getSpatialReference().getID()); + if (!checkResultSpatialRef(mPointWgs84MP, 4326, 0)) { + bAnswer = false; + } + + } + + { + MultiPoint p = new MultiPoint(); + p.addAttribute(VertexDescription.Semantics.Z); + p.addAttribute(VertexDescription.Semantics.M); + String s = GeometryEngine.geometryToJson(spatialReferenceWebMerc1, + p); + assertTrue(s + .equals("{\"hasZ\":true,\"hasM\":true,\"points\":[],\"spatialReference\":{\"wkid\":102100,\"latestWkid\":3857}}")); + + p.add(10.0, 20.0, 30.0); + p.add(20.0, 40.0, 60.0); + s = GeometryEngine.geometryToJson(spatialReferenceWebMerc1, p); + assertTrue(s + .equals("{\"hasZ\":true,\"hasM\":true,\"points\":[[10,20,30,null],[20,40,60,null]],\"spatialReference\":{\"wkid\":102100,\"latestWkid\":3857}}")); + } + { + String points = "{\"hasM\" : false, \"hasZ\" : true, \"uncle remus\" : null, \"points\" : [ [0,0,1], [0.0,10.0,1], [10.0,10.0,1], [10.0,0.0,1, 6666] ],\"spatialReference\" : {\"wkid\" : 4326}}"; + MapGeometry mp = GeometryEngine.jsonToGeometry(factory + .createParser(points)); + MultiPoint multipoint = (MultiPoint) mp.getGeometry(); + assertTrue(multipoint.getPointCount() == 4); + Point2D point2d; + point2d = multipoint.getXY(0); + assertTrue(point2d.x == 0.0 && point2d.y == 0.0); + point2d = multipoint.getXY(1); + assertTrue(point2d.x == 0.0 && point2d.y == 10.0); + point2d = multipoint.getXY(2); + assertTrue(point2d.x == 10.0 && point2d.y == 10.0); + point2d = multipoint.getXY(3); + assertTrue(point2d.x == 10.0 && point2d.y == 0.0); + assertTrue(multipoint.hasAttribute(VertexDescription.Semantics.Z)); + assertTrue(!multipoint.hasAttribute(VertexDescription.Semantics.M)); + double z = multipoint.getAttributeAsDbl( + VertexDescription.Semantics.Z, 0, 0); + assertTrue(z == 1); + SpatialReference spatial_reference = mp.getSpatialReference(); + assertTrue(spatial_reference.getID() == 4326); + } + + return bAnswer; + } + + boolean testPolyline() throws JsonParseException, IOException { + boolean bAnswer = true; + + Polyline polyline = new Polyline(); + polyline.startPath(-97.06138, 32.837); + polyline.lineTo(-97.06133, 32.836); + polyline.lineTo(-97.06124, 32.834); + polyline.lineTo(-97.06127, 32.832); + + polyline.startPath(-97.06326, 32.759); + polyline.lineTo(-97.06298, 32.755); + + { + JsonParser polylinePathsWgs84Parser = factory + .createParser(GeometryEngine.geometryToJson( + spatialReferenceWGS84, polyline)); + MapGeometry mPolylineWGS84MP = GeometryEngine + .jsonToGeometry(polylinePathsWgs84Parser); + + assertTrue(polyline.getPointCount() == ((Polyline) mPolylineWGS84MP + .getGeometry()).getPointCount()); + assertTrue(polyline.getPoint(0).getX() == ((Polyline) mPolylineWGS84MP + .getGeometry()).getPoint(0).getX()); + assertTrue(polyline.getPoint(0).getY() == ((Polyline) mPolylineWGS84MP + .getGeometry()).getPoint(0).getY()); + + assertTrue(polyline.getPathCount() == ((Polyline) mPolylineWGS84MP + .getGeometry()).getPathCount()); + assertTrue(polyline.getSegmentCount() == ((Polyline) mPolylineWGS84MP + .getGeometry()).getSegmentCount()); + assertTrue(polyline.getSegmentCount(0) == ((Polyline) mPolylineWGS84MP + .getGeometry()).getSegmentCount(0)); + assertTrue(polyline.getSegmentCount(1) == ((Polyline) mPolylineWGS84MP + .getGeometry()).getSegmentCount(1)); + + int lastIndex = polyline.getPointCount() - 1; + assertTrue(polyline.getPoint(lastIndex).getX() == ((Polyline) mPolylineWGS84MP + .getGeometry()).getPoint(lastIndex).getX()); + assertTrue(polyline.getPoint(lastIndex).getY() == ((Polyline) mPolylineWGS84MP + .getGeometry()).getPoint(lastIndex).getY()); + + assertTrue(spatialReferenceWGS84.getID() == mPolylineWGS84MP + .getSpatialReference().getID()); + + if (!checkResultSpatialRef(mPolylineWGS84MP, 4326, 0)) { + bAnswer = false; + } + } + + { + Polyline p = new Polyline(); + p.addAttribute(VertexDescription.Semantics.Z); + p.addAttribute(VertexDescription.Semantics.M); + String s = GeometryEngine.geometryToJson(spatialReferenceWebMerc1, + p); + assertTrue(s + .equals("{\"hasZ\":true,\"hasM\":true,\"paths\":[],\"spatialReference\":{\"wkid\":102100,\"latestWkid\":3857}}")); + + p.startPath(0, 0); + p.lineTo(0, 1); + p.startPath(2, 2); + p.lineTo(3, 3); + + p.setAttribute(VertexDescription.Semantics.Z, 0, 0, 3); + p.setAttribute(VertexDescription.Semantics.M, 1, 0, 5); + s = GeometryEngine.geometryToJson(spatialReferenceWebMerc1, p); + assertTrue(s + .equals("{\"hasZ\":true,\"hasM\":true,\"paths\":[[[0,0,3,null],[0,1,0,5]],[[2,2,0,null],[3,3,0,null]]],\"spatialReference\":{\"wkid\":102100,\"latestWkid\":3857}}")); + } + + { + String paths = "{\"hasZ\" : true, \"paths\" : [ [ [0.0, 0.0,3], [0, 10.0,3], [10.0, 10.0,3, 6666], [10.0, 0.0,3, 6666] ], [ [1.0, 1,3], [1.0, 9.0,3], [9.0, 9.0,3], [1.0, 9.0,3] ] ], \"spatialReference\" : {\"wkid\" : 4326}, \"hasM\" : false}"; + MapGeometry mapGeometry = GeometryEngine.jsonToGeometry(factory + .createParser(paths)); + Polyline p = (Polyline) mapGeometry.getGeometry(); + assertTrue(p.getPathCount() == 2); + @SuppressWarnings("unused") + int count = p.getPathCount(); + assertTrue(p.getPointCount() == 8); + assertTrue(p.hasAttribute(VertexDescription.Semantics.Z)); + assertTrue(!p.hasAttribute(VertexDescription.Semantics.M)); + double z = p.getAttributeAsDbl(VertexDescription.Semantics.Z, 0, 0); + assertTrue(z == 3); + double length = p.calculateLength2D(); + assertTrue(Math.abs(length - 54.0) <= 0.001); + SpatialReference spatial_reference = mapGeometry + .getSpatialReference(); + assertTrue(spatial_reference.getID() == 4326); + } + + return bAnswer; + } + + boolean testPolygon() throws JsonParseException, IOException { + boolean bAnswer = true; + + Polygon polygon = new Polygon(); + polygon.startPath(-97.06138, 32.837); + polygon.lineTo(-97.06133, 32.836); + polygon.lineTo(-97.06124, 32.834); + polygon.lineTo(-97.06127, 32.832); + + polygon.startPath(-97.06326, 32.759); + polygon.lineTo(-97.06298, 32.755); + + { + JsonParser polygonPathsWgs84Parser = factory + .createParser(GeometryEngine.geometryToJson( + spatialReferenceWGS84, polygon)); + MapGeometry mPolygonWGS84MP = GeometryEngine + .jsonToGeometry(polygonPathsWgs84Parser); + + assertTrue(polygon.getPointCount() + 1 == ((Polygon) mPolygonWGS84MP + .getGeometry()).getPointCount()); + assertTrue(polygon.getPoint(0).getX() == ((Polygon) mPolygonWGS84MP + .getGeometry()).getPoint(0).getX()); + assertTrue(polygon.getPoint(0).getY() == ((Polygon) mPolygonWGS84MP + .getGeometry()).getPoint(0).getY()); + + assertTrue(polygon.getPathCount() == ((Polygon) mPolygonWGS84MP + .getGeometry()).getPathCount()); + assertTrue(polygon.getSegmentCount() + 1 == ((Polygon) mPolygonWGS84MP + .getGeometry()).getSegmentCount()); + assertTrue(polygon.getSegmentCount(0) == ((Polygon) mPolygonWGS84MP + .getGeometry()).getSegmentCount(0)); + assertTrue(polygon.getSegmentCount(1) + 1 == ((Polygon) mPolygonWGS84MP + .getGeometry()).getSegmentCount(1)); + + int lastIndex = polygon.getPointCount() - 1; + assertTrue(polygon.getPoint(lastIndex).getX() == ((Polygon) mPolygonWGS84MP + .getGeometry()).getPoint(lastIndex).getX()); + assertTrue(polygon.getPoint(lastIndex).getY() == ((Polygon) mPolygonWGS84MP + .getGeometry()).getPoint(lastIndex).getY()); + + assertTrue(spatialReferenceWGS84.getID() == mPolygonWGS84MP + .getSpatialReference().getID()); + + if (!checkResultSpatialRef(mPolygonWGS84MP, 4326, 0)) { + bAnswer = false; + } + } + + { + Polygon p = new Polygon(); + p.addAttribute(VertexDescription.Semantics.Z); + p.addAttribute(VertexDescription.Semantics.M); + String s = GeometryEngine.geometryToJson(spatialReferenceWebMerc1, + p); + assertTrue(s + .equals("{\"hasZ\":true,\"hasM\":true,\"rings\":[],\"spatialReference\":{\"wkid\":102100,\"latestWkid\":3857}}")); + + p.startPath(0, 0); + p.lineTo(0, 1); + p.lineTo(4, 4); + p.startPath(2, 2); + p.lineTo(3, 3); + p.lineTo(7, 8); + + p.setAttribute(VertexDescription.Semantics.Z, 0, 0, 3); + p.setAttribute(VertexDescription.Semantics.M, 1, 0, 7); + p.setAttribute(VertexDescription.Semantics.M, 2, 0, 5); + p.setAttribute(VertexDescription.Semantics.M, 5, 0, 5); + s = GeometryEngine.geometryToJson(spatialReferenceWebMerc1, p); + assertTrue(s + .equals("{\"hasZ\":true,\"hasM\":true,\"rings\":[[[0,0,3,null],[0,1,0,7],[4,4,0,5],[0,0,3,null]],[[2,2,0,null],[3,3,0,null],[7,8,0,5],[2,2,0,null]]],\"spatialReference\":{\"wkid\":102100,\"latestWkid\":3857}}")); + } + + { + // Test Import Polygon from Polygon + String rings = "{\"hasZ\": true, \"rings\" : [ [ [0,0, 5], [0.0, 10.0, 5], [10.0,10.0, 5, 66666], [10.0,0.0, 5] ], [ [12, 12] ], [ [13 , 17], [13 , 17] ], [ [1.0, 1.0, 5, 66666], [9.0,1.0, 5], [9.0,9.0, 5], [1.0,9.0, 5], [1.0, 1.0, 5] ] ] }"; + MapGeometry mapGeometry = GeometryEngine.jsonToGeometry(factory + .createParser(rings)); + Polygon p = (Polygon) mapGeometry.getGeometry(); + @SuppressWarnings("unused") + double area = p.calculateArea2D(); + @SuppressWarnings("unused") + double length = p.calculateLength2D(); + assertTrue(p.getPathCount() == 4); + int count = p.getPointCount(); + assertTrue(count == 15); + assertTrue(p.hasAttribute(VertexDescription.Semantics.Z)); + assertTrue(!p.hasAttribute(VertexDescription.Semantics.M)); + } + + return bAnswer; + } + + boolean testEnvelope() throws JsonParseException, IOException { + boolean bAnswer = true; + + Envelope envelope = new Envelope(); + envelope.setCoords(-109.55, 25.76, -86.39, 49.94); + + { + JsonParser envelopeWGS84Parser = factory + .createParser(GeometryEngine.geometryToJson( + spatialReferenceWGS84, envelope)); + MapGeometry envelopeWGS84MP = GeometryEngine + .jsonToGeometry(envelopeWGS84Parser); + assertTrue(envelope.isEmpty() == envelopeWGS84MP.getGeometry() + .isEmpty()); + assertTrue(envelope.getXMax() == ((Envelope) envelopeWGS84MP + .getGeometry()).getXMax()); + assertTrue(envelope.getYMax() == ((Envelope) envelopeWGS84MP + .getGeometry()).getYMax()); + assertTrue(envelope.getXMin() == ((Envelope) envelopeWGS84MP + .getGeometry()).getXMin()); + assertTrue(envelope.getYMin() == ((Envelope) envelopeWGS84MP + .getGeometry()).getYMin()); + assertTrue(spatialReferenceWGS84.getID() == envelopeWGS84MP + .getSpatialReference().getID()); + if (!checkResultSpatialRef(envelopeWGS84MP, 4326, 0)) { + bAnswer = false; + } + } + + {// export + Envelope e = new Envelope(); + e.addAttribute(VertexDescription.Semantics.Z); + e.addAttribute(VertexDescription.Semantics.M); + String s = GeometryEngine.geometryToJson(spatialReferenceWebMerc1, + e); + assertTrue(s + .equals("{\"xmin\":null,\"ymin\":null,\"xmax\":null,\"ymax\":null,\"zmin\":null,\"zmax\":null,\"mmin\":null,\"mmax\":null,\"spatialReference\":{\"wkid\":102100,\"latestWkid\":3857}}")); + + e.setCoords(0, 1, 2, 3); + + Envelope1D z = new Envelope1D(); + Envelope1D m = new Envelope1D(); + z.setCoords(5, 7); + m.setCoords(11, 13); + + e.setInterval(VertexDescription.Semantics.Z, 0, z); + e.setInterval(VertexDescription.Semantics.M, 0, m); + s = GeometryEngine.geometryToJson(spatialReferenceWebMerc1, e); + assertTrue(s + .equals("{\"xmin\":0,\"ymin\":1,\"xmax\":2,\"ymax\":3,\"zmin\":5,\"zmax\":7,\"mmin\":11,\"mmax\":13,\"spatialReference\":{\"wkid\":102100,\"latestWkid\":3857}}")); + } + + {// import + String s = "{\"xmin\":0.0,\"ymin\":1.0,\"xmax\":2.0,\"ymax\":3.0,\"zmin\":5.0,\"zmax\":7.0,\"mmin\":11.0,\"mmax\":13.0,\"spatialReference\":{\"wkid\":102100,\"latestWkid\":3857}}"; + JsonParser parser = factory.createParser(s); + MapGeometry map_env = GeometryEngine.jsonToGeometry(parser); + Envelope env = (Envelope) map_env.getGeometry(); + Envelope1D z = env.queryInterval(VertexDescription.Semantics.Z, 0); + Envelope1D m = env.queryInterval(VertexDescription.Semantics.M, 0); + assertTrue(z.vmin == 5.0); + assertTrue(z.vmax == 7.0); + assertTrue(m.vmin == 11.0); + assertTrue(m.vmax == 13.0); + } + + { + String s = "{ \"zmin\" : 33, \"xmin\" : -109.55, \"zmax\" : 53, \"ymin\" : 25.76, \"xmax\" : -86.39, \"ymax\" : 49.94, \"mmax\" : 13}"; + JsonParser parser = factory.createParser(s); + MapGeometry map_env = GeometryEngine.jsonToGeometry(parser); + Envelope env = (Envelope) map_env.getGeometry(); + Envelope2D e = new Envelope2D(); + env.queryEnvelope2D(e); + assertTrue(e.xmin == -109.55 && e.ymin == 25.76 && e.xmax == -86.39 + && e.ymax == 49.94); + + Envelope1D e1D; + assertTrue(env.hasAttribute(VertexDescription.Semantics.Z)); + e1D = env.queryInterval(VertexDescription.Semantics.Z, 0); + assertTrue(e1D.vmin == 33 && e1D.vmax == 53); + + assertTrue(!env.hasAttribute(VertexDescription.Semantics.M)); + } + + return bAnswer; + } + + boolean testCR181369() throws JsonParseException, IOException { + // CR181369 + boolean bAnswer = true; + + String jsonStringPointAndWKT = "{\"x\":10.0,\"y\":20.0,\"spatialReference\":{\"wkt\" : \"PROJCS[\\\"NAD83_UTM_zone_15N\\\",GEOGCS[\\\"GCS_North_American_1983\\\",DATUM[\\\"D_North_American_1983\\\",SPHEROID[\\\"GRS_1980\\\",6378137.0,298.257222101]],PRIMEM[\\\"Greenwich\\\",0.0],UNIT[\\\"Degree\\\",0.0174532925199433]],PROJECTION[\\\"Transverse_Mercator\\\"],PARAMETER[\\\"false_easting\\\",500000.0],PARAMETER[\\\"false_northing\\\",0.0],PARAMETER[\\\"central_meridian\\\",-93.0],PARAMETER[\\\"scale_factor\\\",0.9996],PARAMETER[\\\"latitude_of_origin\\\",0.0],UNIT[\\\"Meter\\\",1.0]]\"} }"; + JsonParser jsonParserPointAndWKT = factory + .createParser(jsonStringPointAndWKT); + MapGeometry mapGeom2 = GeometryEngine + .jsonToGeometry(jsonParserPointAndWKT); + String jsonStringPointAndWKT2 = GeometryEngine.geometryToJson( + mapGeom2.getSpatialReference(), mapGeom2.getGeometry()); + JsonParser jsonParserPointAndWKT2 = factory + .createParser(jsonStringPointAndWKT2); + MapGeometry mapGeom3 = GeometryEngine + .jsonToGeometry(jsonParserPointAndWKT2); + assertTrue(((Point) mapGeom2.getGeometry()).getX() == ((Point) mapGeom3 + .getGeometry()).getX()); + assertTrue(((Point) mapGeom2.getGeometry()).getY() == ((Point) mapGeom3 + .getGeometry()).getY()); + + String s1 = mapGeom2.getSpatialReference().getText(); + String s2 = mapGeom3.getSpatialReference().getText(); + assertTrue(s1.equals(s2)); + + int id2 = mapGeom2.getSpatialReference().getID(); + int id3 = mapGeom3.getSpatialReference().getID(); + assertTrue(id2 == id3); + if (!checkResultSpatialRef(mapGeom3, mapGeom2.getSpatialReference() + .getID(), 0)) { + bAnswer = false; + } + return bAnswer; + } + + boolean checkResultSpatialRef(MapGeometry mapGeometry, int expectWki1, + int expectWki2) { + SpatialReference sr = mapGeometry.getSpatialReference(); + String Wkt = sr.getText(); + int wki1 = sr.getLatestID(); + if (!(wki1 == expectWki1 || wki1 == expectWki2)) + return false; + if (!(Wkt != null && Wkt.length() > 0)) + return false; + SpatialReference sr2 = SpatialReference.create(Wkt); + int wki2 = sr2.getID(); + if (expectWki2 > 0) { + if (!(wki2 == expectWki1 || wki2 == expectWki2)) + return false; + } else { + if (!(wki2 == expectWki1)) + return false; + } + return true; + } } diff --git a/src/test/java/com/esri/core/geometry/TestImportExport.java b/src/test/java/com/esri/core/geometry/TestImportExport.java index 4b249cc1..5f94539e 100644 --- a/src/test/java/com/esri/core/geometry/TestImportExport.java +++ b/src/test/java/com/esri/core/geometry/TestImportExport.java @@ -36,18 +36,18 @@ public class TestImportExport extends TestCase { - @Override - protected void setUp() throws Exception { - super.setUp(); - } - - @Override - protected void tearDown() throws Exception { - super.tearDown(); - } - - @Test - public static void testImportExportShapePolygon() { + @Override + protected void setUp() throws Exception { + super.setUp(); + } + + @Override + protected void tearDown() throws Exception { + super.tearDown(); + } + + @Test + public static void testImportExportShapePolygon() { // { // String s = "MULTIPOLYGON (((-1.4337158203098852 53.42590083930004, -1.4346462383651897 53.42590083930004, -1.4349713164114632 53.42426406667512, -1.4344808816770183 53.42391134176576, -1.4337158203098852 53.424339319373516, -1.4337158203098852 53.42590083930004, -1.4282226562499147 53.42590083930004, -1.4282226562499147 53.42262754610009, -1.423659941537096 53.42262754610009, -1.4227294921872726 53.42418897437618, -1.4199829101572732 53.42265258737483, -1.4172363281222147 53.42418897437334, -1.4144897460898278 53.42265258737625, -1.4144897460898278 53.42099079900008, -1.4117431640598568 53.42099079712516, -1.4117431640598568 53.41849780932388, -1.4112778948070286 53.41771711805022, -1.4114404909237805 53.41689867267529, -1.411277890108579 53.416080187950215, -1.4117431640598568 53.4152995338453, -1.4117431657531654 53.40953184824072, -1.41723632610001 53.40953184402311, -1.4172363281199125 53.406257299700044, -1.4227294921899158 53.406257299700044, -1.4227294921899158 53.40789459668797, -1.4254760767598498 53.40789460061099, -1.4262193642339867 53.40914148401417, -1.4273828468095076 53.409531853100034, -1.4337158203098852 53.409531790075235, -1.4337158203098852 53.41280609140024, -1.4392089843723568 53.41280609140024, -1.439208984371362 53.41608014067522, -1.441160015802268 53.41935368587538, -1.4427511170075604 53.41935368587538, -1.4447021484373863 53.42099064750012, -1.4501953124999432 53.42099064750012, -1.4501953124999432 53.43214683850347, -1.4513643355446106 53.434108816701794, -1.4502702625278232 53.43636597733034, -1.4494587195580948 53.437354845300334, -1.4431075935937656 53.437354845300334, -1.4372459179209045 53.43244635455021, -1.433996276212838 53.42917388040006, -1.4337158203098852 53.42917388040006, -1.4337158203098852 53.42590083930004)))"; // Geometry g = OperatorImportFromWkt.local().execute(0, Geometry.Type.Unknown, s, null); @@ -57,2139 +57,2139 @@ public static void testImportExportShapePolygon() { // OperatorFactoryLocal.saveToWKTFileDbg("c:/temp/simplifiedeeee", simple, null); // int i = 0; // } - OperatorExportToESRIShape exporterShape = (OperatorExportToESRIShape) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.ExportToESRIShape); - OperatorImportFromESRIShape importerShape = (OperatorImportFromESRIShape) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.ImportFromESRIShape); - - Polygon polygon = makePolygon(); - - byte[] esriShape = GeometryEngine.geometryToEsriShape(polygon); - Geometry imported = GeometryEngine.geometryFromEsriShape(esriShape, Geometry.Type.Unknown); - TestCommonMethods.compareGeometryContent((MultiPath) imported, polygon); - - // Test Import Polygon from Polygon - ByteBuffer polygonShapeBuffer = exporterShape.execute(0, polygon); - Geometry polygonShapeGeometry = importerShape.execute(0, Geometry.Type.Polygon, polygonShapeBuffer); - - TestCommonMethods.compareGeometryContent((MultiPath) polygonShapeGeometry, polygon); - - // Test Import Envelope from Polygon - Geometry envelopeShapeGeometry = importerShape.execute(0, Geometry.Type.Envelope, polygonShapeBuffer); - Envelope envelope = (Envelope) envelopeShapeGeometry; - - @SuppressWarnings("unused") Envelope env = new Envelope(), otherenv = new Envelope(); - polygon.queryEnvelope(otherenv); - assertTrue(envelope.getXMin() == otherenv.getXMin()); - assertTrue(envelope.getXMax() == otherenv.getXMax()); - assertTrue(envelope.getYMin() == otherenv.getYMin()); - assertTrue(envelope.getYMax() == otherenv.getYMax()); - - Envelope1D interval, otherinterval; - interval = envelope.queryInterval(VertexDescription.Semantics.Z, 0); - otherinterval = polygon.queryInterval(VertexDescription.Semantics.Z, 0); - assertTrue(interval.vmin == otherinterval.vmin); - assertTrue(interval.vmax == otherinterval.vmax); - } - - @Test - public static void testImportExportShapePolyline() { - OperatorExportToESRIShape exporterShape = (OperatorExportToESRIShape) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.ExportToESRIShape); - OperatorImportFromESRIShape importerShape = (OperatorImportFromESRIShape) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.ImportFromESRIShape); - - Polyline polyline = makePolyline(); - - // Test Import Polyline from Polyline - ByteBuffer polylineShapeBuffer = exporterShape.execute(0, polyline); - Geometry polylineShapeGeometry = importerShape.execute(0, Geometry.Type.Polyline, polylineShapeBuffer); - - // TODO test this - TestCommonMethods.compareGeometryContent((MultiPath) polylineShapeGeometry, polyline); - - // Test Import Envelope from Polyline; - Geometry envelopeShapeGeometry = importerShape.execute(0, Geometry.Type.Envelope, polylineShapeBuffer); - Envelope envelope = (Envelope) envelopeShapeGeometry; - - Envelope env = new Envelope(), otherenv = new Envelope(); - envelope.queryEnvelope(env); - polyline.queryEnvelope(otherenv); - assertTrue(env.getXMin() == otherenv.getXMin()); - assertTrue(env.getXMax() == otherenv.getXMax()); - assertTrue(env.getYMin() == otherenv.getYMin()); - assertTrue(env.getYMax() == otherenv.getYMax()); - - Envelope1D interval, otherinterval; - interval = envelope.queryInterval(VertexDescription.Semantics.Z, 0); - otherinterval = polyline.queryInterval(VertexDescription.Semantics.Z, 0); - assertTrue(interval.vmin == otherinterval.vmin); - assertTrue(interval.vmax == otherinterval.vmax); - } - - @Test - public static void testImportExportShapeMultiPoint() { - OperatorExportToESRIShape exporterShape = (OperatorExportToESRIShape) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.ExportToESRIShape); - OperatorImportFromESRIShape importerShape = (OperatorImportFromESRIShape) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.ImportFromESRIShape); - - MultiPoint multipoint = makeMultiPoint(); - - // Test Import MultiPoint from MultiPoint - ByteBuffer multipointShapeBuffer = exporterShape.execute(0, multipoint); - MultiPoint multipointShapeGeometry = (MultiPoint) importerShape.execute(0, Geometry.Type.MultiPoint, multipointShapeBuffer); - - TestCommonMethods.compareGeometryContent((MultiPoint) multipointShapeGeometry, multipoint); - - // Test Import Envelope from MultiPoint - Geometry envelopeShapeGeometry = importerShape.execute(0, Geometry.Type.Envelope, multipointShapeBuffer); - Envelope envelope = (Envelope) envelopeShapeGeometry; - - Envelope env = new Envelope(), otherenv = new Envelope(); - envelope.queryEnvelope(env); - multipoint.queryEnvelope(otherenv); - assertTrue(env.getXMin() == otherenv.getXMin()); - assertTrue(env.getXMax() == otherenv.getXMax()); - assertTrue(env.getYMin() == otherenv.getYMin()); - assertTrue(env.getYMax() == otherenv.getYMax()); - - Envelope1D interval, otherinterval; - interval = envelope.queryInterval(VertexDescription.Semantics.Z, 0); - otherinterval = multipoint.queryInterval(VertexDescription.Semantics.Z, 0); - assertTrue(interval.vmin == otherinterval.vmin); - assertTrue(interval.vmax == otherinterval.vmax); - - interval = envelope.queryInterval(VertexDescription.Semantics.ID, 0); - otherinterval = multipoint.queryInterval(VertexDescription.Semantics.ID, 0); - assertTrue(interval.vmin == otherinterval.vmin); - assertTrue(interval.vmax == otherinterval.vmax); - } - - @Test - public static void testImportExportShapePoint() { - OperatorExportToESRIShape exporterShape = (OperatorExportToESRIShape) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.ExportToESRIShape); - OperatorImportFromESRIShape importerShape = (OperatorImportFromESRIShape) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.ImportFromESRIShape); - - // Point - Point point = makePoint(); - - // Test Import Point from Point - ByteBuffer pointShapeBuffer = exporterShape.execute(0, point); - Point pointShapeGeometry = (Point) importerShape.execute(0, Geometry.Type.Point, pointShapeBuffer); - - double x1 = point.getX(); - double x2 = pointShapeGeometry.getX(); - assertTrue(x1 == x2); - - double y1 = point.getY(); - double y2 = pointShapeGeometry.getY(); - assertTrue(y1 == y2); - - double z1 = point.getZ(); - double z2 = pointShapeGeometry.getZ(); - assertTrue(z1 == z2); - - double m1 = point.getM(); - double m2 = pointShapeGeometry.getM(); - assertTrue(m1 == m2); - - int id1 = point.getID(); - int id2 = pointShapeGeometry.getID(); - assertTrue(id1 == id2); - - // Test Import Multipoint from Point - MultiPoint multipointShapeGeometry = (MultiPoint) importerShape.execute(0, Geometry.Type.MultiPoint, pointShapeBuffer); - Point point2d = multipointShapeGeometry.getPoint(0); - assertTrue(x1 == point2d.getX() && y1 == point2d.getY()); - - int pointCount = multipointShapeGeometry.getPointCount(); - assertTrue(pointCount == 1); - - z2 = multipointShapeGeometry.getAttributeAsDbl(VertexDescription.Semantics.Z, 0, 0); - assertTrue(z1 == z2); - - m2 = multipointShapeGeometry.getAttributeAsDbl(VertexDescription.Semantics.M, 0, 0); - assertTrue(m1 == m2); - - id2 = multipointShapeGeometry.getAttributeAsInt(VertexDescription.Semantics.ID, 0, 0); - assertTrue(id1 == id2); - - // Test Import Envelope from Point - Geometry envelopeShapeGeometry = importerShape.execute(0, Geometry.Type.Envelope, pointShapeBuffer); - Envelope envelope = (Envelope) envelopeShapeGeometry; - - Envelope env = new Envelope(), otherenv = new Envelope(); - envelope.queryEnvelope(env); - point.queryEnvelope(otherenv); - assertTrue(env.getXMin() == otherenv.getXMin()); - assertTrue(env.getXMax() == otherenv.getXMax()); - assertTrue(env.getYMin() == otherenv.getYMin()); - assertTrue(env.getYMax() == otherenv.getYMax()); - - Envelope1D interval, otherinterval; - interval = envelope.queryInterval(VertexDescription.Semantics.Z, 0); - otherinterval = point.queryInterval(VertexDescription.Semantics.Z, 0); - assertTrue(interval.vmin == otherinterval.vmin); - assertTrue(interval.vmax == otherinterval.vmax); - - interval = envelope.queryInterval(VertexDescription.Semantics.ID, 0); - otherinterval = point.queryInterval(VertexDescription.Semantics.ID, 0); - assertTrue(interval.vmin == otherinterval.vmin); - assertTrue(interval.vmax == otherinterval.vmax); - } - - @Test - public static void testImportExportShapeEnvelope() { - OperatorExportToESRIShape exporterShape = (OperatorExportToESRIShape) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.ExportToESRIShape); - OperatorImportFromESRIShape importerShape = (OperatorImportFromESRIShape) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.ImportFromESRIShape); - - // Test Export Envelope to Polygon - Envelope envelope = makeEnvelope(); - - ByteBuffer polygonShapeBuffer = exporterShape.execute(0, envelope); - Polygon polygon = (Polygon) importerShape.execute(0, Geometry.Type.Polygon, polygonShapeBuffer); - int pointCount = polygon.getPointCount(); - assertTrue(pointCount == 4); - - Envelope env = new Envelope(); - - envelope.queryEnvelope(env); - // interval = envelope.queryInterval(VertexDescription.Semantics.Z, 0); - Point point3d; - point3d = polygon.getPoint(0); - assertTrue(point3d.getX() == env.getXMin() && point3d.getY() == env.getYMin());// && point3d.z == - // interval.vmin); - point3d = polygon.getPoint(1); - assertTrue(point3d.getX() == env.getXMin() && point3d.getY() == env.getYMax());// && point3d.z == - // interval.vmax); - point3d = polygon.getPoint(2); - assertTrue(point3d.getX() == env.getXMax() && point3d.getY() == env.getYMax());// && point3d.z == - // interval.vmin); - point3d = polygon.getPoint(3); - assertTrue(point3d.getX() == env.getXMax() && point3d.getY() == env.getYMin());// && point3d.z == - // interval.vmax); - - Envelope1D interval; - interval = envelope.queryInterval(VertexDescription.Semantics.M, 0); - double m = polygon.getAttributeAsDbl(VertexDescription.Semantics.M, 0, 0); - assertTrue(m == interval.vmin); - m = polygon.getAttributeAsDbl(VertexDescription.Semantics.M, 1, 0); - assertTrue(m == interval.vmax); - m = polygon.getAttributeAsDbl(VertexDescription.Semantics.M, 2, 0); - assertTrue(m == interval.vmin); - m = polygon.getAttributeAsDbl(VertexDescription.Semantics.M, 3, 0); - assertTrue(m == interval.vmax); - - interval = envelope.queryInterval(VertexDescription.Semantics.ID, 0); - double id = polygon.getAttributeAsDbl(VertexDescription.Semantics.ID, 0, 0); - assertTrue(id == interval.vmin); - id = polygon.getAttributeAsDbl(VertexDescription.Semantics.ID, 1, 0); - assertTrue(id == interval.vmax); - id = polygon.getAttributeAsDbl(VertexDescription.Semantics.ID, 2, 0); - assertTrue(id == interval.vmin); - id = polygon.getAttributeAsDbl(VertexDescription.Semantics.ID, 3, 0); - assertTrue(id == interval.vmax); - } - - @Test - public static void testImportExportWkbGeometryCollection() { - OperatorImportFromWkb importerWKB = (OperatorImportFromWkb) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.ImportFromWkb); - - int offset = 0; - ByteBuffer wkbBuffer = ByteBuffer.allocate(600).order(ByteOrder.nativeOrder()); - wkbBuffer.put(offset, (byte) WkbByteOrder.wkbNDR); - offset += 1; // byte order - wkbBuffer.putInt(offset, WkbGeometryType.wkbGeometryCollection); - offset += 4; // type - wkbBuffer.putInt(offset, 3); // 3 geometries - offset += 4; - wkbBuffer.put(offset, (byte) WkbByteOrder.wkbNDR); - offset += 1; - wkbBuffer.putInt(offset, WkbGeometryType.wkbPoint); - offset += 4; - wkbBuffer.putDouble(offset, 0); - offset += 8; - wkbBuffer.putDouble(offset, 0); - offset += 8; - wkbBuffer.put(offset, (byte) WkbByteOrder.wkbNDR); - offset += 1; // byte order - wkbBuffer.putInt(offset, WkbGeometryType.wkbGeometryCollection); - offset += 4; // type - wkbBuffer.putInt(offset, 7); // 7 empty geometries - offset += 4; - wkbBuffer.put(offset, (byte) WkbByteOrder.wkbNDR); - offset += 1; - wkbBuffer.putInt(offset, WkbGeometryType.wkbLineString); - offset += 4; - wkbBuffer.putInt(offset, 0); // 0 points, for empty linestring - offset += 4; - wkbBuffer.put(offset, (byte) WkbByteOrder.wkbNDR); - offset += 1; - wkbBuffer.putInt(offset, WkbGeometryType.wkbPolygon); - offset += 4; - wkbBuffer.putInt(offset, 0); // 0 points, for empty polygon - offset += 4; - wkbBuffer.put(offset, (byte) WkbByteOrder.wkbNDR); - offset += 1; - wkbBuffer.putInt(offset, WkbGeometryType.wkbMultiPolygon); - offset += 4; - wkbBuffer.putInt(offset, 0); // 0 points, for empty multipolygon - offset += 4; - wkbBuffer.put(offset, (byte) WkbByteOrder.wkbNDR); - offset += 1; - wkbBuffer.putInt(offset, WkbGeometryType.wkbMultiLineString); - offset += 4; - wkbBuffer.putInt(offset, 0); // 0 points, for empty multilinestring - offset += 4; - wkbBuffer.put(offset, (byte) WkbByteOrder.wkbNDR); - offset += 1; - wkbBuffer.putInt(offset, WkbGeometryType.wkbGeometryCollection); - offset += 4; - wkbBuffer.putInt(offset, 0); // 0 geometries, for empty - // geometrycollection - offset += 4; - wkbBuffer.put(offset, (byte) WkbByteOrder.wkbNDR); - offset += 1; - wkbBuffer.putInt(offset, WkbGeometryType.wkbMultiPoint); - offset += 4; - wkbBuffer.putInt(offset, 0); // 0 points, for empty multipoint - offset += 4; - wkbBuffer.put(offset, (byte) WkbByteOrder.wkbNDR); - offset += 1; - wkbBuffer.putInt(offset, WkbGeometryType.wkbPoint); - offset += 4; - wkbBuffer.putDouble(offset, 66); - offset += 8; - wkbBuffer.putDouble(offset, 88); - offset += 8; - wkbBuffer.put(offset, (byte) WkbByteOrder.wkbNDR); - offset += 1; - wkbBuffer.putInt(offset, WkbGeometryType.wkbPoint); - offset += 4; - wkbBuffer.putDouble(offset, 13); - offset += 8; - wkbBuffer.putDouble(offset, 17); - offset += 8; - - // "GeometryCollection( Point (0 0), GeometryCollection( LineString empty, Polygon empty, MultiPolygon empty, MultiLineString empty, MultiPoint empty ), Point (13 17) )"; - OGCStructure structure = importerWKB.executeOGC(0, wkbBuffer, null).m_structures.get(0); - - assertTrue(structure.m_type == 7); - assertTrue(structure.m_structures.get(0).m_type == 1); - assertTrue(structure.m_structures.get(1).m_type == 7); - assertTrue(structure.m_structures.get(2).m_type == 1); - - assertTrue(structure.m_structures.get(1).m_structures.get(0).m_type == 2); - assertTrue(structure.m_structures.get(1).m_structures.get(1).m_type == 3); - assertTrue(structure.m_structures.get(1).m_structures.get(2).m_type == 6); - assertTrue(structure.m_structures.get(1).m_structures.get(3).m_type == 5); - assertTrue(structure.m_structures.get(1).m_structures.get(4).m_type == 7); - assertTrue(structure.m_structures.get(1).m_structures.get(5).m_type == 4); - assertTrue(structure.m_structures.get(1).m_structures.get(6).m_type == 1); - - Point p = (Point) structure.m_structures.get(1).m_structures.get(6).m_geometry; - assertTrue(p.getX() == 66); - assertTrue(p.getY() == 88); - - p = (Point) structure.m_structures.get(2).m_geometry; - assertTrue(p.getX() == 13); - assertTrue(p.getY() == 17); - } - - @Test - public static void testImportExportWKBPolygon() { - OperatorExportToWkb exporterWKB = (OperatorExportToWkb) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.ExportToWkb); - OperatorExportToWkt exporterWKT = (OperatorExportToWkt) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.ExportToWkt); - OperatorImportFromWkb importerWKB = (OperatorImportFromWkb) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.ImportFromWkb); - - // Test Import Polygon with bad rings - int offset = 0; - ByteBuffer wkbBuffer = ByteBuffer.allocate(500).order(ByteOrder.nativeOrder()); - wkbBuffer.put(offset, (byte) WkbByteOrder.wkbNDR); - offset += 1; // byte order - wkbBuffer.putInt(offset, WkbGeometryType.wkbPolygon); - offset += 4; // type - wkbBuffer.putInt(offset, 8); - offset += 4; // num rings - wkbBuffer.putInt(offset, 4); - offset += 4; // num points - wkbBuffer.putDouble(offset, 0.0); - offset += 8; // x - wkbBuffer.putDouble(offset, 0.0); - offset += 8; // y - wkbBuffer.putDouble(offset, 0.0); - offset += 8; // x - wkbBuffer.putDouble(offset, 10.0); - offset += 8; // y - wkbBuffer.putDouble(offset, 10.0); - offset += 8; // x - wkbBuffer.putDouble(offset, 10.0); - offset += 8; // y - wkbBuffer.putDouble(offset, 0.0); - offset += 8; // x - wkbBuffer.putDouble(offset, 0.0); - offset += 8; // y - wkbBuffer.putInt(offset, 1); - offset += 4; // num points - wkbBuffer.putDouble(offset, 36.0); - offset += 8; // x - wkbBuffer.putDouble(offset, 17.0); - offset += 8; // y - wkbBuffer.putInt(offset, 2); - offset += 4; // num points - wkbBuffer.putDouble(offset, 19.0); - offset += 8; // x - wkbBuffer.putDouble(offset, 19.0); - offset += 8; // y - wkbBuffer.putDouble(offset, -19.0); - offset += 8; // x - wkbBuffer.putDouble(offset, -19.0); - offset += 8; // y - wkbBuffer.putInt(offset, 4); - offset += 4; // num points - wkbBuffer.putDouble(offset, 23.0); - offset += 8; // x - wkbBuffer.putDouble(offset, 88); - offset += 8; // y - wkbBuffer.putDouble(offset, 13.0); - offset += 8; // x - wkbBuffer.putDouble(offset, 43.0); - offset += 8; // y - wkbBuffer.putDouble(offset, 59.0); - offset += 8; // x - wkbBuffer.putDouble(offset, 79.0); - offset += 8; // y - wkbBuffer.putDouble(offset, 83.0); - offset += 8; // x - wkbBuffer.putDouble(offset, 87.0); - offset += 8; // y - wkbBuffer.putInt(offset, 3); - offset += 4; // num points - wkbBuffer.putDouble(offset, 23.0); - offset += 8; // x - wkbBuffer.putDouble(offset, 88); - offset += 8; // y - wkbBuffer.putDouble(offset, 88); - offset += 8; // x - wkbBuffer.putDouble(offset, 43.0); - offset += 8; // y - wkbBuffer.putDouble(offset, 67.0); - offset += 8; // x - wkbBuffer.putDouble(offset, 79.0); - offset += 8; // y - wkbBuffer.putInt(offset, 0); - offset += 4; // num points - wkbBuffer.putInt(offset, 3); - offset += 4; // num points - wkbBuffer.putDouble(offset, 23.0); - offset += 8; // x - wkbBuffer.putDouble(offset, 88); - offset += 8; // y - wkbBuffer.putDouble(offset, 88); - offset += 8; // x - wkbBuffer.putDouble(offset, 43.0); - offset += 8; // y - wkbBuffer.putDouble(offset, 67.0); - offset += 8; // x - wkbBuffer.putDouble(offset, 88); - offset += 8; // y - wkbBuffer.putInt(offset, 2); - offset += 4; // num points - wkbBuffer.putDouble(offset, 23.0); - offset += 8; // x - wkbBuffer.putDouble(offset, 67.0); - offset += 8; // y - wkbBuffer.putDouble(offset, 43.0); - offset += 8; // x - wkbBuffer.putDouble(offset, 67.0); - offset += 8; // y - - Geometry p = importerWKB.execute(0, Geometry.Type.Polygon, wkbBuffer, null); - int pc = ((Polygon) p).getPathCount(); - String wktString = exporterWKT.execute(0, p, null); - assertTrue(wktString.equals("MULTIPOLYGON (((0 0, 10 10, 0 10, 0 0), (36 17, 36 17, 36 17), (19 19, -19 -19, 19 19), (23 88, 83 87, 59 79, 13 43, 23 88), (23 88, 67 79, 88 43, 23 88), (23 88, 67 88, 88 43, 23 88), (23 67, 43 67, 23 67)))")); - - wktString = exporterWKT.execute(WktExportFlags.wktExportPolygon, p, null); - assertTrue(wktString.equals("POLYGON ((0 0, 10 10, 0 10, 0 0), (36 17, 36 17, 36 17), (19 19, -19 -19, 19 19), (23 88, 83 87, 59 79, 13 43, 23 88), (23 88, 67 79, 88 43, 23 88), (23 88, 67 88, 88 43, 23 88), (23 67, 43 67, 23 67))")); - - Polygon polygon = makePolygon(); - - // Test Import Polygon from Polygon8 - ByteBuffer polygonWKBBuffer = exporterWKB.execute(0, polygon, null); - int wkbType = polygonWKBBuffer.getInt(1); - assertTrue(wkbType == WkbGeometryType.wkbMultiPolygonZM); - Geometry polygonWKBGeometry = importerWKB.execute(0, Geometry.Type.Polygon, polygonWKBBuffer, null); - TestCommonMethods.compareGeometryContent((MultiVertexGeometry) polygonWKBGeometry, polygon); - - // Test WKB_export_multi_polygon on nonempty single part polygon - Polygon polygon2 = makePolygon2(); - assertTrue(polygon2.getPathCount() == 1); - polygonWKBBuffer = exporterWKB.execute(WkbExportFlags.wkbExportMultiPolygon, polygon2, null); - polygonWKBGeometry = importerWKB.execute(0, Geometry.Type.Polygon, polygonWKBBuffer, null); - TestCommonMethods.compareGeometryContent((MultiVertexGeometry) polygonWKBGeometry, polygon2); - wkbType = polygonWKBBuffer.getInt(1); - assertTrue(wkbType == WkbGeometryType.wkbMultiPolygonZM); - - // Test WKB_export_polygon on nonempty single part polygon - assertTrue(polygon2.getPathCount() == 1); - polygonWKBBuffer = exporterWKB.execute(WkbExportFlags.wkbExportPolygon, polygon2, null); - polygonWKBGeometry = importerWKB.execute(0, Geometry.Type.Polygon, polygonWKBBuffer, null); - TestCommonMethods.compareGeometryContent((MultiVertexGeometry) polygonWKBGeometry, polygon2); - wkbType = polygonWKBBuffer.getInt(1); - assertTrue(wkbType == WkbGeometryType.wkbPolygonZM); - - // Test WKB_export_polygon on empty polygon - Polygon polygon3 = new Polygon(); - polygonWKBBuffer = exporterWKB.execute(WkbExportFlags.wkbExportPolygon, polygon3, null); - polygonWKBGeometry = importerWKB.execute(0, Geometry.Type.Polygon, polygonWKBBuffer, null); - assertTrue(polygonWKBGeometry.isEmpty() == true); - wkbType = polygonWKBBuffer.getInt(1); - assertTrue(wkbType == WkbGeometryType.wkbPolygon); - - // Test WKB_export_defaults on empty polygon - polygonWKBBuffer = exporterWKB.execute(0, polygon3, null); - polygonWKBGeometry = importerWKB.execute(0, Geometry.Type.Polygon, polygonWKBBuffer, null); - assertTrue(polygonWKBGeometry.isEmpty() == true); - wkbType = polygonWKBBuffer.getInt(1); - assertTrue(wkbType == WkbGeometryType.wkbMultiPolygon); - } - - @Test - public static void testImportExportWKBPolyline() { - OperatorExportToWkb exporterWKB = (OperatorExportToWkb) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.ExportToWkb); - OperatorExportToWkt exporterWKT = (OperatorExportToWkt) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.ExportToWkt); - OperatorImportFromWkb importerWKB = (OperatorImportFromWkb) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.ImportFromWkb); - - // Test Import Polyline with bad paths (i.e. paths with one point or - // zero points) - int offset = 0; - ByteBuffer wkbBuffer = ByteBuffer.allocate(500).order(ByteOrder.nativeOrder()); - wkbBuffer.put(offset, (byte) WkbByteOrder.wkbNDR); - offset += 1; // byte order - wkbBuffer.putInt(offset, WkbGeometryType.wkbMultiLineString); - offset += 4; // type - wkbBuffer.putInt(offset, 4); - offset += 4; // num paths - wkbBuffer.put(offset, (byte) WkbByteOrder.wkbNDR); - offset += 1; // byte order - wkbBuffer.putInt(offset, WkbGeometryType.wkbLineString); - offset += 4; // type - wkbBuffer.putInt(offset, 1); - offset += 4; // num points - wkbBuffer.putDouble(offset, 36.0); - offset += 8; // x - wkbBuffer.putDouble(offset, 17.0); - offset += 8; // y - wkbBuffer.put(offset, (byte) WkbByteOrder.wkbNDR); - offset += 1; // byte order - wkbBuffer.putInt(offset, WkbGeometryType.wkbLineString); - offset += 4; // type - wkbBuffer.putInt(offset, 0); - offset += 4; // num points - wkbBuffer.put(offset, (byte) WkbByteOrder.wkbNDR); - offset += 1; // byte order - wkbBuffer.putInt(offset, WkbGeometryType.wkbLineString); - offset += 4; // type - wkbBuffer.putInt(offset, 1); - offset += 4; // num points - wkbBuffer.putDouble(offset, 19.0); - offset += 8; // x - wkbBuffer.putDouble(offset, 19.0); - offset += 8; // y - wkbBuffer.put(offset, (byte) WkbByteOrder.wkbNDR); - offset += 1; // byte order - wkbBuffer.putInt(offset, WkbGeometryType.wkbLineString); - offset += 4; // type - wkbBuffer.putInt(offset, 3); - offset += 4; // num points - wkbBuffer.putDouble(offset, 88); - offset += 8; // x - wkbBuffer.putDouble(offset, 29.0); - offset += 8; // y - wkbBuffer.putDouble(offset, 13.0); - offset += 8; // x - wkbBuffer.putDouble(offset, 43.0); - offset += 8; // y - wkbBuffer.putDouble(offset, 59.0); - offset += 8; // x - wkbBuffer.putDouble(offset, 88); - offset += 8; // y - - Polyline p = (Polyline) (importerWKB.execute(0, Geometry.Type.Polyline, wkbBuffer, null)); - int pc = p.getPointCount(); - int pac = p.getPathCount(); - assertTrue(p.getPointCount() == 7); - assertTrue(p.getPathCount() == 3); - - String wktString = exporterWKT.execute(0, p, null); - assertTrue(wktString.equals("MULTILINESTRING ((36 17, 36 17), (19 19, 19 19), (88 29, 13 43, 59 88))")); - - Polyline polyline = makePolyline(); - polyline.dropAttribute(VertexDescription.Semantics.ID); - - // Test Import Polyline from Polyline - ByteBuffer polylineWKBBuffer = exporterWKB.execute(0, polyline, null); - int wkbType = polylineWKBBuffer.getInt(1); - assertTrue(wkbType == WkbGeometryType.wkbMultiLineStringZM); - Geometry polylineWKBGeometry = importerWKB.execute(0, Geometry.Type.Polyline, polylineWKBBuffer, null); - TestCommonMethods.compareGeometryContent((MultiVertexGeometry) polylineWKBGeometry, polyline); - - // Test wkbExportMultiPolyline on nonempty single part polyline - Polyline polyline2 = makePolyline2(); - assertTrue(polyline2.getPathCount() == 1); - polylineWKBBuffer = exporterWKB.execute(WkbExportFlags.wkbExportMultiLineString, polyline2, null); - polylineWKBGeometry = importerWKB.execute(0, Geometry.Type.Polyline, polylineWKBBuffer, null); - TestCommonMethods.compareGeometryContent((MultiVertexGeometry) polylineWKBGeometry, polyline2); - wkbType = polylineWKBBuffer.getInt(1); - assertTrue(wkbType == WkbGeometryType.wkbMultiLineStringZM); - - // Test wkbExportPolyline on nonempty single part polyline - assertTrue(polyline2.getPathCount() == 1); - polylineWKBBuffer = exporterWKB.execute(WkbExportFlags.wkbExportLineString, polyline2, null); - polylineWKBGeometry = importerWKB.execute(0, Geometry.Type.Polyline, polylineWKBBuffer, null); - TestCommonMethods.compareGeometryContent((MultiVertexGeometry) polylineWKBGeometry, polyline2); - wkbType = polylineWKBBuffer.getInt(1); - assertTrue(wkbType == WkbGeometryType.wkbLineStringZM); - - // Test wkbExportPolyline on empty polyline - Polyline polyline3 = new Polyline(); - polylineWKBBuffer = exporterWKB.execute(WkbExportFlags.wkbExportLineString, polyline3, null); - polylineWKBGeometry = importerWKB.execute(0, Geometry.Type.Polyline, polylineWKBBuffer, null); - assertTrue(polylineWKBGeometry.isEmpty() == true); - wkbType = polylineWKBBuffer.getInt(1); - assertTrue(wkbType == WkbGeometryType.wkbLineString); - - // Test WKB_export_defaults on empty polyline - polylineWKBBuffer = exporterWKB.execute(0, polyline3, null); - polylineWKBGeometry = importerWKB.execute(0, Geometry.Type.Polyline, polylineWKBBuffer, null); - assertTrue(polylineWKBGeometry.isEmpty() == true); - wkbType = polylineWKBBuffer.getInt(1); - assertTrue(wkbType == WkbGeometryType.wkbMultiLineString); - } - - @Test - public static void testImportExportWKBMultiPoint() { - OperatorExportToWkb exporterWKB = (OperatorExportToWkb) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.ExportToWkb); - OperatorImportFromWkb importerWKB = (OperatorImportFromWkb) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.ImportFromWkb); - - MultiPoint multipoint = makeMultiPoint(); - multipoint.dropAttribute(VertexDescription.Semantics.ID); - - // Test Import Multi_point from Multi_point - ByteBuffer multipointWKBBuffer = exporterWKB.execute(0, multipoint, null); - int wkbType = multipointWKBBuffer.getInt(1); - assertTrue(wkbType == WkbGeometryType.wkbMultiPointZ); - MultiPoint multipointWKBGeometry = (MultiPoint) (importerWKB.execute(0, Geometry.Type.MultiPoint, multipointWKBBuffer, null)); - TestCommonMethods.compareGeometryContent((MultiVertexGeometry) multipointWKBGeometry, multipoint); - - // Test WKB_export_point on nonempty single point Multi_point - MultiPoint multipoint2 = makeMultiPoint2(); - assertTrue(multipoint2.getPointCount() == 1); - ByteBuffer pointWKBBuffer = exporterWKB.execute(WkbExportFlags.wkbExportPoint, multipoint2, null); - Point pointWKBGeometry = (Point) (importerWKB.execute(0, Geometry.Type.Point, pointWKBBuffer, null)); - Point3D point3d, mpoint3d; - point3d = pointWKBGeometry.getXYZ(); - mpoint3d = multipoint2.getXYZ(0); - assertTrue(point3d.x == mpoint3d.x && point3d.y == mpoint3d.y && point3d.z == mpoint3d.z); - wkbType = pointWKBBuffer.getInt(1); - assertTrue(wkbType == WkbGeometryType.wkbPointZ); - - // Test WKB_export_point on empty Multi_point - MultiPoint multipoint3 = new MultiPoint(); - pointWKBBuffer = exporterWKB.execute(WkbExportFlags.wkbExportPoint, multipoint3, null); - pointWKBGeometry = (Point) (importerWKB.execute(0, Geometry.Type.Point, pointWKBBuffer, null)); - assertTrue(pointWKBGeometry.isEmpty() == true); - wkbType = pointWKBBuffer.getInt(1); - assertTrue(wkbType == WkbGeometryType.wkbPoint); - - // Test WKB_export_defaults on empty Multi_point - multipointWKBBuffer = exporterWKB.execute(0, multipoint3, null); - multipointWKBGeometry = (MultiPoint) (importerWKB.execute(0, Geometry.Type.MultiPoint, multipointWKBBuffer, null)); - assertTrue(multipointWKBGeometry.isEmpty() == true); - wkbType = multipointWKBBuffer.getInt(1); - assertTrue(wkbType == WkbGeometryType.wkbMultiPoint); - } - - @Test - public static void testImportExportWKBPoint() { - OperatorExportToWkb exporterWKB = (OperatorExportToWkb) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.ExportToWkb); - OperatorImportFromWkb importerWKB = (OperatorImportFromWkb) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.ImportFromWkb); - - // Point - Point point = makePoint(); - - // Test Import Point from Point - ByteBuffer pointWKBBuffer = exporterWKB.execute(0, point, null); - int wkbType = pointWKBBuffer.getInt(1); - assertTrue(wkbType == WkbGeometryType.wkbPointZM); - Point pointWKBGeometry = (Point) (importerWKB.execute(0, Geometry.Type.Point, pointWKBBuffer, null)); - - double x_1 = point.getX(); - double x2 = pointWKBGeometry.getX(); - assertTrue(x_1 == x2); - - double y1 = point.getY(); - double y2 = pointWKBGeometry.getY(); - assertTrue(y1 == y2); - - double z_1 = point.getZ(); - double z_2 = pointWKBGeometry.getZ(); - assertTrue(z_1 == z_2); - - double m1 = point.getM(); - double m2 = pointWKBGeometry.getM(); - assertTrue(m1 == m2); - - // Test WKB_export_defaults on empty point - Point point2 = new Point(); - pointWKBBuffer = exporterWKB.execute(0, point2, null); - pointWKBGeometry = (Point) (importerWKB.execute(0, Geometry.Type.Point, pointWKBBuffer, null)); - assertTrue(pointWKBGeometry.isEmpty() == true); - wkbType = pointWKBBuffer.getInt(1); - assertTrue(wkbType == WkbGeometryType.wkbPoint); - - // Test WKB_export_point on empty point - pointWKBBuffer = exporterWKB.execute(WkbExportFlags.wkbExportPoint, point2, null); - pointWKBGeometry = (Point) (importerWKB.execute(0, Geometry.Type.Point, pointWKBBuffer, null)); - assertTrue(pointWKBGeometry.isEmpty() == true); - wkbType = pointWKBBuffer.getInt(1); - assertTrue(wkbType == WkbGeometryType.wkbPoint); - - // Test WKB_export_multi_point on empty point - MultiPoint multipoint = new MultiPoint(); - ByteBuffer multipointWKBBuffer = exporterWKB.execute(WkbExportFlags.wkbExportMultiPoint, multipoint, null); - MultiPoint multipointWKBGeometry = (MultiPoint) (importerWKB.execute(0, Geometry.Type.MultiPoint, multipointWKBBuffer, null)); - assertTrue(multipointWKBGeometry.isEmpty() == true); - wkbType = multipointWKBBuffer.getInt(1); - assertTrue(wkbType == WkbGeometryType.wkbMultiPoint); - - // Test WKB_export_point on nonempty single point Multi_point - MultiPoint multipoint2 = makeMultiPoint2(); - assertTrue(multipoint2.getPointCount() == 1); - pointWKBBuffer = exporterWKB.execute(WkbExportFlags.wkbExportPoint, multipoint2, null); - pointWKBGeometry = (Point) (importerWKB.execute(0, Geometry.Type.Point, pointWKBBuffer, null)); - Point3D point3d, mpoint3d; - point3d = pointWKBGeometry.getXYZ(); - mpoint3d = multipoint2.getXYZ(0); - assertTrue(point3d.x == mpoint3d.x && point3d.y == mpoint3d.y && point3d.z == mpoint3d.z); - wkbType = pointWKBBuffer.getInt(1); - assertTrue(wkbType == WkbGeometryType.wkbPointZ); - } - - @Test - public static void testImportExportWKBEnvelope() { - OperatorExportToWkb exporterWKB = (OperatorExportToWkb) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.ExportToWkb); - OperatorImportFromWkb importerWKB = (OperatorImportFromWkb) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.ImportFromWkb); - - // Test Export Envelope to Polygon (WKB_export_defaults) - Envelope envelope = makeEnvelope(); - envelope.dropAttribute(VertexDescription.Semantics.ID); - - ByteBuffer polygonWKBBuffer = exporterWKB.execute(0, envelope, null); - int wkbType = polygonWKBBuffer.getInt(1); - assertTrue(wkbType == WkbGeometryType.wkbPolygonZM); - Polygon polygon = (Polygon) (importerWKB.execute(0, Geometry.Type.Polygon, polygonWKBBuffer, null)); - int point_count = polygon.getPointCount(); - assertTrue(point_count == 4); - - Envelope2D env = new Envelope2D(); - Envelope1D interval; - - envelope.queryEnvelope2D(env); - interval = envelope.queryInterval(VertexDescription.Semantics.Z, 0); - Point3D point3d; - point3d = polygon.getXYZ(0); - assertTrue(point3d.x == env.xmin && point3d.y == env.ymin && point3d.z == interval.vmin); - point3d = polygon.getXYZ(1); - assertTrue(point3d.x == env.xmin && point3d.y == env.ymax && point3d.z == interval.vmax); - point3d = polygon.getXYZ(2); - assertTrue(point3d.x == env.xmax && point3d.y == env.ymax && point3d.z == interval.vmin); - point3d = polygon.getXYZ(3); - assertTrue(point3d.x == env.xmax && point3d.y == env.ymin && point3d.z == interval.vmax); - - interval = envelope.queryInterval(VertexDescription.Semantics.M, 0); - double m = polygon.getAttributeAsDbl(VertexDescription.Semantics.M, 0, 0); - assertTrue(m == interval.vmin); - m = polygon.getAttributeAsDbl(VertexDescription.Semantics.M, 1, 0); - assertTrue(m == interval.vmax); - m = polygon.getAttributeAsDbl(VertexDescription.Semantics.M, 2, 0); - assertTrue(m == interval.vmin); - m = polygon.getAttributeAsDbl(VertexDescription.Semantics.M, 3, 0); - assertTrue(m == interval.vmax); - - // Test WKB_export_multi_polygon on nonempty Envelope - polygonWKBBuffer = exporterWKB.execute(WkbExportFlags.wkbExportMultiPolygon, envelope, null); - wkbType = polygonWKBBuffer.getInt(1); - assertTrue(wkbType == WkbGeometryType.wkbMultiPolygonZM); - polygon = (Polygon) (importerWKB.execute(0, Geometry.Type.Polygon, polygonWKBBuffer, null)); - point_count = polygon.getPointCount(); - assertTrue(point_count == 4); - - envelope.queryEnvelope2D(env); - interval = envelope.queryInterval(VertexDescription.Semantics.Z, 0); - point3d = polygon.getXYZ(0); - assertTrue(point3d.x == env.xmin && point3d.y == env.ymin && point3d.z == interval.vmin); - point3d = polygon.getXYZ(1); - assertTrue(point3d.x == env.xmin && point3d.y == env.ymax && point3d.z == interval.vmax); - point3d = polygon.getXYZ(2); - assertTrue(point3d.x == env.xmax && point3d.y == env.ymax && point3d.z == interval.vmin); - point3d = polygon.getXYZ(3); - assertTrue(point3d.x == env.xmax && point3d.y == env.ymin && point3d.z == interval.vmax); - - interval = envelope.queryInterval(VertexDescription.Semantics.M, 0); - m = polygon.getAttributeAsDbl(VertexDescription.Semantics.M, 0, 0); - assertTrue(m == interval.vmin); - m = polygon.getAttributeAsDbl(VertexDescription.Semantics.M, 1, 0); - assertTrue(m == interval.vmax); - m = polygon.getAttributeAsDbl(VertexDescription.Semantics.M, 2, 0); - assertTrue(m == interval.vmin); - m = polygon.getAttributeAsDbl(VertexDescription.Semantics.M, 3, 0); - assertTrue(m == interval.vmax); - - // Test WKB_export_defaults on empty Envelope - Envelope envelope2 = new Envelope(); - polygonWKBBuffer = exporterWKB.execute(0, envelope2, null); - wkbType = polygonWKBBuffer.getInt(1); - assertTrue(wkbType == WkbGeometryType.wkbPolygon); - polygon = (Polygon) (importerWKB.execute(0, Geometry.Type.Polygon, polygonWKBBuffer, null)); - assertTrue(polygon.isEmpty()); - - // Test WKB_export_polygon on empty Envelope - polygonWKBBuffer = exporterWKB.execute(WkbExportFlags.wkbExportPolygon, envelope2, null); - wkbType = polygonWKBBuffer.getInt(1); - assertTrue(wkbType == WkbGeometryType.wkbPolygon); - polygon = (Polygon) (importerWKB.execute(0, Geometry.Type.Polygon, polygonWKBBuffer, null)); - assertTrue(polygon.isEmpty()); - } - - @Test - public static void testImportExportWktGeometryCollection() { - OperatorImportFromWkt importerWKT = (OperatorImportFromWkt) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.ImportFromWkt); - OperatorExportToWkt exporterWKT = (OperatorExportToWkt) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.ExportToWkt); - - String wktString; - Envelope2D envelope = new Envelope2D(); - WktParser wktParser = new WktParser(); - - wktString = "GeometryCollection( Point (0 0), GeometryCollection( Point (0 0) , Point (1 1) , Point (2 2), LineString empty ), Point (1 1), Point (2 2) )"; - OGCStructure structure = importerWKT.executeOGC(0, wktString, null).m_structures.get(0); - - assertTrue(structure.m_type == 7); - assertTrue(structure.m_structures.get(0).m_type == 1); - assertTrue(structure.m_structures.get(0).m_type == 1); - assertTrue(structure.m_structures.get(1).m_type == 7); - assertTrue(structure.m_structures.get(2).m_type == 1); - assertTrue(structure.m_structures.get(3).m_type == 1); - - assertTrue(structure.m_structures.get(1).m_structures.get(0).m_type == 1); - assertTrue(structure.m_structures.get(1).m_structures.get(1).m_type == 1); - assertTrue(structure.m_structures.get(1).m_structures.get(2).m_type == 1); - assertTrue(structure.m_structures.get(1).m_structures.get(3).m_type == 2); - } - - @Test - public static void testImportExportWktMultiPolygon() { - OperatorImportFromWkt importerWKT = (OperatorImportFromWkt) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.ImportFromWkt); - OperatorExportToWkt exporterWKT = (OperatorExportToWkt) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.ExportToWkt); - - Polygon polygon; - String wktString; - Envelope2D envelope = new Envelope2D(); - WktParser wktParser = new WktParser(); - - // Test Import from MultiPolygon - wktString = "Multipolygon M empty"; - polygon = (Polygon) importerWKT.execute(0, Geometry.Type.Polygon, wktString, null); - assertTrue(polygon != null); - assertTrue(polygon.isEmpty()); - assertTrue(polygon.hasAttribute(VertexDescription.Semantics.M)); - - polygon = (Polygon) GeometryEngine.geometryFromWkt(wktString, 0, Geometry.Type.Unknown); - assertTrue(polygon != null); - assertTrue(polygon.isEmpty()); - assertTrue(polygon.hasAttribute(VertexDescription.Semantics.M)); - - wktString = exporterWKT.execute(0, polygon, null); - assertTrue(wktString.equals("MULTIPOLYGON M EMPTY")); - - wktString = GeometryEngine.geometryToWkt(polygon, 0); - assertTrue(wktString.equals("MULTIPOLYGON M EMPTY")); - - wktString = "Multipolygon Z (empty, (empty, (10 10 5, 20 10 5, 20 20 5, 10 20 5, 10 10 5), (12 12 3), empty, (10 10 1, 12 12 1)), empty, ((90 90 88, 60 90 7, 60 60 7), empty, (70 70 7, 80 80 7, 70 80 7, 70 70 7)), empty)"; - polygon = (Polygon) (importerWKT.execute(0, Geometry.Type.Polygon, wktString, null)); - assertTrue(polygon != null); - polygon.queryEnvelope2D(envelope); - assertTrue(envelope.xmin == 10 && envelope.xmax == 90 && envelope.ymin == 10 && envelope.ymax == 90); - assertTrue(polygon.getPointCount() == 14); - assertTrue(polygon.getPathCount() == 5); - // assertTrue(polygon.calculate_area_2D() > 0.0); - assertTrue(polygon.hasAttribute(VertexDescription.Semantics.Z)); - - double z = polygon.getAttributeAsDbl(VertexDescription.Semantics.Z, 0, 0); - assertTrue(z == 5); - - // Test Export to WKT MultiPolygon - wktString = exporterWKT.execute(0, polygon, null); - assertTrue(wktString.equals("MULTIPOLYGON Z (((10 10 5, 20 10 5, 20 20 5, 10 20 5, 10 10 5), (12 12 3, 12 12 3, 12 12 3), (10 10 1, 12 12 1, 10 10 1)), ((90 90 88, 60 90 7, 60 60 7, 90 90 88), (70 70 7, 70 80 7, 80 80 7, 70 70 7)))")); - wktParser.resetParser(wktString); - while (wktParser.nextToken() != WktParser.WktToken.not_available) { - } - - // Test import Polygon - wktString = "POLYGON z (EMPTY, EMPTY, (10 10 5, 10 20 5, 20 20 5, 20 10 5), (12 12 3), EMPTY, (10 10 1, 12 12 1), EMPTY, (60 60 7, 60 90 7, 90 90 7, 60 60 7), EMPTY, (70 70 7, 70 80 7, 80 80 7), EMPTY)"; - polygon = (Polygon) (importerWKT.execute(0, Geometry.Type.Polygon, wktString, null)); - assertTrue(polygon != null); - assertTrue(polygon.getPointCount() == 14); - assertTrue(polygon.getPathCount() == 5); - assertTrue(polygon.hasAttribute(VertexDescription.Semantics.Z)); - - // Test Export to WKT Polygon - wktString = exporterWKT.execute(WktExportFlags.wktExportPolygon, polygon, null); - assertTrue(wktString.equals("POLYGON Z ((10 10 5, 20 10 5, 20 20 5, 10 20 5, 10 10 5), (12 12 3, 12 12 3, 12 12 3), (10 10 1, 12 12 1, 10 10 1), (60 60 7, 60 90 7, 90 90 7, 60 60 7), (70 70 7, 70 80 7, 80 80 7, 70 70 7))")); - wktParser.resetParser(wktString); - while (wktParser.nextToken() != WktParser.WktToken.not_available) { - } - - Envelope env = new Envelope(); - env.addAttribute(VertexDescription.Semantics.Z); - polygon.queryEnvelope(env); - - wktString = exporterWKT.execute(0, env, null); - assertTrue(wktString.equals("POLYGON Z ((10 10 1, 90 10 7, 90 90 1, 10 90 7, 10 10 1))")); - wktParser.resetParser(wktString); - while (wktParser.nextToken() != WktParser.WktToken.not_available) { - } - - wktString = exporterWKT.execute(WktExportFlags.wktExportMultiPolygon, env, null); - assertTrue(wktString.equals("MULTIPOLYGON Z (((10 10 1, 90 10 7, 90 90 1, 10 90 7, 10 10 1)))")); - wktParser.resetParser(wktString); - while (wktParser.nextToken() != WktParser.WktToken.not_available) { - } - - env.setEmpty(); - - wktString = exporterWKT.execute(0, env, null); - assertTrue(wktString.equals("POLYGON Z EMPTY")); - wktParser.resetParser(wktString); - while (wktParser.nextToken() != WktParser.WktToken.not_available) { - } - - wktString = exporterWKT.execute(WktExportFlags.wktExportMultiPolygon, env, null); - assertTrue(wktString.equals("MULTIPOLYGON Z EMPTY")); - wktParser.resetParser(wktString); - while (wktParser.nextToken() != WktParser.WktToken.not_available) { - } - - wktString = "MULTIPOLYGON (((5 10, 8 10, 10 10, 10 0, 0 0, 0 10, 2 10, 5 10)))"; // ring - // is - // oriented - // clockwise - polygon = (Polygon) (importerWKT.execute(0, Geometry.Type.Polygon, wktString, null)); - assertTrue(polygon != null); - assertTrue(polygon.calculateArea2D() > 0); - - wktString = "MULTIPOLYGON Z (((90 10 7, 10 10 1, 10 90 7, 90 90 1, 90 10 7)))"; // ring - // is - // oriented - // clockwise - polygon = (Polygon) (importerWKT.execute(0, Geometry.Type.Polygon, wktString, null)); - assertTrue(polygon != null); - assertTrue(polygon.getPointCount() == 4); - assertTrue(polygon.getPathCount() == 1); - assertTrue(polygon.hasAttribute(VertexDescription.Semantics.Z)); - assertTrue(polygon.calculateArea2D() > 0); - - wktString = exporterWKT.execute(WktExportFlags.wktExportMultiPolygon, polygon, null); - assertTrue(wktString.equals("MULTIPOLYGON Z (((90 10 7, 90 90 1, 10 90 7, 10 10 1, 90 10 7)))")); - } - - @Test - public static void testImportExportWktPolygon() { - OperatorImportFromWkt importerWKT = (OperatorImportFromWkt) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.ImportFromWkt); - // OperatorExportToWkt exporterWKT = - // (OperatorExportToWkt)OperatorFactoryLocal.getInstance().getOperator(Operator.Type.ExportToWkt); - - Polygon polygon; - String wktString; - Envelope2D envelope = new Envelope2D(); - - // Test Import from Polygon - wktString = "Polygon ZM empty"; - polygon = (Polygon) (importerWKT.execute(0, Geometry.Type.Unknown, wktString, null)); - assertTrue(polygon != null); - assertTrue(polygon.isEmpty()); - assertTrue(polygon.hasAttribute(VertexDescription.Semantics.Z)); - assertTrue(polygon.hasAttribute(VertexDescription.Semantics.M)); - - wktString = "Polygon z (empty, (10 10 5, 20 10 5, 20 20 5, 10 20 5, 10 10 5), (12 12 3), empty, (10 10 1, 12 12 1))"; - polygon = (Polygon) (importerWKT.execute(0, Geometry.Type.Unknown, wktString, null)); - assertTrue(polygon != null); - polygon.queryEnvelope2D(envelope); - assertTrue(envelope.xmin == 10 && envelope.xmax == 20 && envelope.ymin == 10 && envelope.ymax == 20); - assertTrue(polygon.getPointCount() == 8); - assertTrue(polygon.getPathCount() == 3); - assertTrue(polygon.hasAttribute(VertexDescription.Semantics.Z)); - - wktString = "polygon ((35 10, 10 20, 15 40, 45 45, 35 10), (20 30, 35 35, 30 20, 20 30))"; - Polygon polygon2 = (Polygon) (importerWKT.execute(0, Geometry.Type.Unknown, wktString, null)); - assertTrue(polygon2 != null); - - // wktString = exporterWKT.execute(0, *polygon2, null); - } - - @Test - public static void testImportExportWktLineString() { - OperatorImportFromWkt importerWKT = (OperatorImportFromWkt) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.ImportFromWkt); - // OperatorExportToWkt exporterWKT = - // (OperatorExportToWkt)OperatorFactoryLocal.getInstance().getOperator(Operator.Type.ExportToWkt); - - Polyline polyline; - String wktString; - Envelope2D envelope = new Envelope2D(); - - // Test Import from LineString - wktString = "LineString ZM empty"; - polyline = (Polyline) (importerWKT.execute(0, Geometry.Type.Unknown, wktString, null)); - assertTrue(polyline != null); - assertTrue(polyline.isEmpty()); - assertTrue(polyline.hasAttribute(VertexDescription.Semantics.Z)); - assertTrue(polyline.hasAttribute(VertexDescription.Semantics.M)); - - wktString = "LineString m (10 10 5, 10 20 5, 20 20 5, 20 10 5)"; - polyline = (Polyline) (importerWKT.execute(0, Geometry.Type.Unknown, wktString, null)); - assertTrue(polyline != null); - polyline.queryEnvelope2D(envelope); - assertTrue(envelope.xmin == 10 && envelope.xmax == 20 && envelope.ymin == 10 && envelope.ymax == 20); - assertTrue(polyline.getPointCount() == 4); - assertTrue(polyline.getPathCount() == 1); - assertTrue(polyline.hasAttribute(VertexDescription.Semantics.M)); - } - - @Test - public static void testImportExportWktMultiLineString() { - OperatorImportFromWkt importerWKT = (OperatorImportFromWkt) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.ImportFromWkt); - OperatorExportToWkt exporterWKT = (OperatorExportToWkt) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.ExportToWkt); - - Polyline polyline; - String wktString; - Envelope2D envelope = new Envelope2D(); - WktParser wktParser = new WktParser(); - - // Test Import from MultiLineString - wktString = "MultiLineStringZMempty"; - polyline = (Polyline) (importerWKT.execute(0, Geometry.Type.Unknown, wktString, null)); - assertTrue(polyline != null); - assertTrue(polyline.isEmpty()); - assertTrue(polyline.hasAttribute(VertexDescription.Semantics.Z)); - assertTrue(polyline.hasAttribute(VertexDescription.Semantics.M)); - - wktString = "MultiLineStringm(empty, empty, (10 10 5, 10 20 5, 20 88 5, 20 10 5), (12 88 3), empty, (10 10 1, 12 12 1), empty, (88 60 7, 60 90 7, 90 90 7), empty, (70 70 7, 70 80 7, 80 80 7), empty)"; - polyline = (Polyline) (importerWKT.execute(0, Geometry.Type.Unknown, wktString, null)); - assertTrue(polyline != null); - polyline.queryEnvelope2D(envelope); - assertTrue(envelope.xmin == 10 && envelope.xmax == 90 && envelope.ymin == 10 && envelope.ymax == 90); - assertTrue(polyline.getPointCount() == 14); - assertTrue(polyline.getPathCount() == 5); - assertTrue(polyline.hasAttribute(VertexDescription.Semantics.M)); - - wktString = exporterWKT.execute(0, polyline, null); - assertTrue(wktString.equals("MULTILINESTRING M ((10 10 5, 10 20 5, 20 88 5, 20 10 5), (12 88 3, 12 88 3), (10 10 1, 12 12 1), (88 60 7, 60 90 7, 90 90 7), (70 70 7, 70 80 7, 80 80 7))")); - wktParser.resetParser(wktString); - while (wktParser.nextToken() != WktParser.WktToken.not_available) { - } - - // Test Import LineString - wktString = "Linestring Z(10 10 5, 10 20 5, 20 20 5, 20 10 5)"; - polyline = (Polyline) (importerWKT.execute(0, Geometry.Type.Unknown, wktString, null)); - assertTrue(polyline.getPointCount() == 4); - wktString = exporterWKT.execute(WktExportFlags.wktExportLineString, polyline, null); - assertTrue(wktString.equals("LINESTRING Z (10 10 5, 10 20 5, 20 20 5, 20 10 5)")); - wktParser.resetParser(wktString); - while (wktParser.nextToken() != WktParser.WktToken.not_available) { - } - - wktString = exporterWKT.execute(0, polyline, null); - assertTrue(wktString.equals("MULTILINESTRING Z ((10 10 5, 10 20 5, 20 20 5, 20 10 5))")); - wktParser.resetParser(wktString); - while (wktParser.nextToken() != WktParser.WktToken.not_available) { - } - } - - @Test - public static void testImportExportWktMultiPoint() { - OperatorImportFromWkt importerWKT = (OperatorImportFromWkt) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.ImportFromWkt); - OperatorExportToWkt exporterWKT = (OperatorExportToWkt) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.ExportToWkt); - - MultiPoint multipoint; - String wktString; - Envelope2D envelope = new Envelope2D(); - WktParser wktParser = new WktParser(); - - // Test Import from Multi_point - wktString = " MultiPoint ZM empty"; - multipoint = (MultiPoint) (importerWKT.execute(0, Geometry.Type.Unknown, wktString, null)); - assertTrue(multipoint != null); - assertTrue(multipoint.isEmpty()); - assertTrue(multipoint.hasAttribute(VertexDescription.Semantics.Z)); - assertTrue(multipoint.hasAttribute(VertexDescription.Semantics.M)); - - wktString = exporterWKT.execute(0, multipoint, null); - assertTrue(wktString.equals("MULTIPOINT ZM EMPTY")); - wktParser.resetParser(wktString); - while (wktParser.nextToken() != WktParser.WktToken.not_available) { - } - - wktString = exporterWKT.execute(WktExportFlags.wktExportPoint, multipoint, null); - assertTrue(wktString.equals("POINT ZM EMPTY")); - wktParser.resetParser(wktString); - while (wktParser.nextToken() != WktParser.WktToken.not_available) { - } - - multipoint = new MultiPoint(); - multipoint.add(118.15114354234563, 33.82234433423462345); - multipoint.add(88, 88); - - wktString = exporterWKT.execute(WktExportFlags.wktExportPrecision10, multipoint, null); - assertTrue(wktString.equals("MULTIPOINT ((118.1511435 33.82234433), (88 88))")); - wktParser.resetParser(wktString); - while (wktParser.nextToken() != WktParser.WktToken.not_available) { - } - - multipoint = new MultiPoint(); - multipoint.add(88, 2); - multipoint.add(88, 88); - - wktString = exporterWKT.execute(0, multipoint, null); - assertTrue(wktString.equals("MULTIPOINT ((88 2), (88 88))")); - wktParser.resetParser(wktString); - while (wktParser.nextToken() != WktParser.WktToken.not_available) { - } - - wktString = "Multipoint zm (empty, empty, (10 88 88 33), (10 20 5 33), (20 20 5 33), (20 10 5 33), (12 12 3 33), empty, (10 10 1 33), (12 12 1 33), empty, (60 60 7 33), (60 90.1 7 33), (90 90 7 33), empty, (70 70 7 33), (70 80 7 33), (80 80 7 33), empty)"; - multipoint = (MultiPoint) (importerWKT.execute(0, Geometry.Type.Unknown, wktString, null)); - assertTrue(multipoint != null); - multipoint.queryEnvelope2D(envelope); - // assertTrue(envelope.xmin == 10 && envelope.xmax == 90 && - // envelope.ymin == 10 && Math.abs(envelope.ymax - 90.1) <= 0.001); - assertTrue(multipoint.getPointCount() == 13); - assertTrue(multipoint.hasAttribute(VertexDescription.Semantics.Z)); - assertTrue(multipoint.hasAttribute(VertexDescription.Semantics.M)); - - wktString = "Multipoint zm (10 88 88 33, 10 20 5 33, 20 20 5 33, 20 10 5 33, 12 12 3 33, 10 10 1 33, 12 12 1 33, 60 60 7 33, 60 90.1 7 33, 90 90 7 33, 70 70 7 33, 70 80 7 33, 80 80 7 33)"; - multipoint = (MultiPoint) (importerWKT.execute(0, Geometry.Type.Unknown, wktString, null)); - assertTrue(multipoint != null); - // assertTrue(envelope.xmin == 10 && envelope.xmax == 90 && - // envelope.ymin == 10 && ::fabs(envelope.ymax - 90.1) <= 0.001); - assertTrue(multipoint.getPointCount() == 13); - assertTrue(multipoint.hasAttribute(VertexDescription.Semantics.Z)); - assertTrue(multipoint.hasAttribute(VertexDescription.Semantics.M)); - - wktString = exporterWKT.execute(WktExportFlags.wktExportPrecision15, multipoint, null); - assertTrue(wktString.equals("MULTIPOINT ZM ((10 88 88 33), (10 20 5 33), (20 20 5 33), (20 10 5 33), (12 12 3 33), (10 10 1 33), (12 12 1 33), (60 60 7 33), (60 90.1 7 33), (90 90 7 33), (70 70 7 33), (70 80 7 33), (80 80 7 33))")); - wktParser.resetParser(wktString); - while (wktParser.nextToken() != WktParser.WktToken.not_available) { - } - - wktString = "Multipoint zm (empty, empty, (10 10 5 33))"; - multipoint = (MultiPoint) (importerWKT.execute(0, Geometry.Type.Unknown, wktString, null)); - - wktString = exporterWKT.execute(WktExportFlags.wktExportPoint, multipoint, null); - assertTrue(wktString.equals("POINT ZM (10 10 5 33)")); - wktParser.resetParser(wktString); - while (wktParser.nextToken() != WktParser.WktToken.not_available) { - } - } - - @Test - public static void testImportExportWktPoint() { - OperatorImportFromWkt importerWKT = (OperatorImportFromWkt) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.ImportFromWkt); - OperatorExportToWkt exporterWKT = (OperatorExportToWkt) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.ExportToWkt); - - Point point; - String wktString; - WktParser wktParser = new WktParser(); - - // Test Import from Point - wktString = "Point ZM empty"; - point = (Point) (importerWKT.execute(0, Geometry.Type.Unknown, wktString, null)); - assertTrue(point != null); - assertTrue(point.isEmpty()); - assertTrue(point.hasAttribute(VertexDescription.Semantics.Z)); - assertTrue(point.hasAttribute(VertexDescription.Semantics.M)); - - wktString = exporterWKT.execute(0, point, null); - assertTrue(wktString.equals("POINT ZM EMPTY")); - wktParser.resetParser(wktString); - while (wktParser.nextToken() != WktParser.WktToken.not_available) { - } - - wktString = exporterWKT.execute(WktExportFlags.wktExportMultiPoint, point, null); - assertTrue(wktString.equals("MULTIPOINT ZM EMPTY")); - wktParser.resetParser(wktString); - while (wktParser.nextToken() != WktParser.WktToken.not_available) { - } - - wktString = "Point zm (30.1 10.6 5.1 33.1)"; - point = (Point) (importerWKT.execute(0, Geometry.Type.Unknown, wktString, null)); - assertTrue(point != null); - assertTrue(point.hasAttribute(VertexDescription.Semantics.Z)); - assertTrue(point.hasAttribute(VertexDescription.Semantics.M)); - double x = point.getX(); - double y = point.getY(); - double z = point.getZ(); - double m = point.getM(); - - assertTrue(x == 30.1); - assertTrue(y == 10.6); - assertTrue(z == 5.1); - assertTrue(m == 33.1); - - wktString = exporterWKT.execute(WktExportFlags.wktExportPrecision15, point, null); - assertTrue(wktString.equals("POINT ZM (30.1 10.6 5.1 33.1)")); - wktParser.resetParser(wktString); - while (wktParser.nextToken() != WktParser.WktToken.not_available) { - } - - wktString = exporterWKT.execute(WktExportFlags.wktExportMultiPoint | WktExportFlags.wktExportPrecision15, point, null); - assertTrue(wktString.equals("MULTIPOINT ZM ((30.1 10.6 5.1 33.1))")); - wktParser.resetParser(wktString); - while (wktParser.nextToken() != WktParser.WktToken.not_available) { - } - } - - @Deprecated - @Test - public static void testImportGeoJsonGeometryCollection() { - OperatorImportFromGeoJson importer = (OperatorImportFromGeoJson) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.ImportFromGeoJson); - - String geoJsonString; - Envelope2D envelope = new Envelope2D(); - WktParser wktParser = new WktParser(); - - geoJsonString = "{\"type\" : \"GeometryCollection\", \"geometries\" : [{\"type\" : \"Point\", \"coordinates\": [0,0]}, {\"type\" : \"GeometryCollection\" , \"geometries\" : [ {\"type\" : \"Point\", \"coordinates\" : [0, 0]} , {\"type\" : \"Point\", \"coordinates\" : [1, 1]} ,{ \"type\" : \"Point\", \"coordinates\" : [2, 2]}, {\"type\" : \"LineString\", \"coordinates\" : []}]} , {\"type\" : \"Point\", \"coordinates\" : [1, 1]}, {\"type\" : \"Point\" , \"coordinates\" : [2, 2]} ] }"; - OGCStructure structure = importer.executeOGC(0, geoJsonString, null).m_ogcStructure.m_structures.get(0); - - assertTrue(structure.m_type == 7); - assertTrue(structure.m_structures.get(0).m_type == 1); - assertTrue(structure.m_structures.get(0).m_type == 1); - assertTrue(structure.m_structures.get(1).m_type == 7); - assertTrue(structure.m_structures.get(2).m_type == 1); - assertTrue(structure.m_structures.get(3).m_type == 1); - - assertTrue(structure.m_structures.get(1).m_structures.get(0).m_type == 1); - assertTrue(structure.m_structures.get(1).m_structures.get(1).m_type == 1); - assertTrue(structure.m_structures.get(1).m_structures.get(2).m_type == 1); - assertTrue(structure.m_structures.get(1).m_structures.get(3).m_type == 2); - } - - @Test - public static void testImportGeoJsonMultiPolygon() throws Exception { - OperatorImportFromGeoJson importerGeoJson = (OperatorImportFromGeoJson) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.ImportFromGeoJson); - OperatorExportToGeoJson exporterGeoJson = (OperatorExportToGeoJson) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.ExportToGeoJson); - - MapGeometry map_geometry; - Polygon polygon; - SpatialReference spatial_reference; - String geoJsonString; - Envelope2D envelope = new Envelope2D(); - - // Test Import from MultiPolygon - geoJsonString = "{\"type\": \"MultiPolygon\", \"coordinates\": []}"; - polygon = (Polygon) (importerGeoJson.execute(0, Geometry.Type.Polygon, geoJsonString, null).getGeometry()); - assertTrue(polygon != null); - assertTrue(polygon.isEmpty()); - assertTrue(!polygon.hasAttribute(VertexDescription.Semantics.M)); - - geoJsonString = "{\"coordinates\" : [], \"type\": \"MultiPolygon\", \"crs\": {\"type\": \"name\", \"some\": \"stuff\", \"properties\": {\"some\" : \"stuff\", \"name\": \"urn:ogc:def:crs:OGC:1.3:CRS84\"}}}"; - map_geometry = importerGeoJson.execute(0, Geometry.Type.Polygon, geoJsonString, null); - polygon = (Polygon) map_geometry.getGeometry(); - spatial_reference = map_geometry.getSpatialReference(); - assertTrue(polygon != null); - assertTrue(polygon.isEmpty()); - assertTrue(spatial_reference.getLatestID() == 4326); - - geoJsonString = "{\"coordinates\" : null, \"crs\": null, \"type\": \"MultiPolygon\"}"; - map_geometry = importerGeoJson.execute(0, Geometry.Type.Polygon, geoJsonString, null); - polygon = (Polygon) map_geometry.getGeometry(); - spatial_reference = map_geometry.getSpatialReference(); - assertTrue(polygon != null); - assertTrue(polygon.isEmpty()); - assertTrue(spatial_reference == null); - - geoJsonString = "{\"type\": \"MultiPolygon\", \"coordinates\" : [[], [], [[[]]]], \"crsURN\": \"urn:ogc:def:crs:OGC:1.3:CRS27\"}"; - map_geometry = importerGeoJson.execute(0, Geometry.Type.Polygon, geoJsonString, null); - polygon = (Polygon) map_geometry.getGeometry(); - spatial_reference = map_geometry.getSpatialReference(); - assertTrue(polygon != null); - assertTrue(polygon.isEmpty()); - assertTrue(spatial_reference != null); - assertTrue(spatial_reference.getLatestID() == 4267); - - geoJsonString = "{\"coordinates\" : [[], [[], [[10, 10, 5], [20, 10, 5], [20, 20, 5], [10, 20, 5], [10, 10, 5]], [[12, 12, 3]], [], [[10, 10, 1], [12, 12, 1]]], [], [[[90, 90, 88], [60, 90, 7], [60, 60, 7]], [], [[70, 70, 7], [80, 80, 7], [70, 80, 7], [70, 70, 7]]], []], \"crs\": {\"type\": \"link\", \"properties\": {\"href\": \"http://spatialreference.org/ref/sr-org/6928/ogcwkt/\"}}, \"type\": \"MultiPolygon\"}"; - map_geometry = importerGeoJson.execute(0, Geometry.Type.Unknown, geoJsonString, null); - polygon = (Polygon) map_geometry.getGeometry(); - spatial_reference = map_geometry.getSpatialReference(); - assertTrue(polygon != null); - polygon.queryEnvelope2D(envelope); - assertTrue(envelope.xmin == 10 && envelope.xmax == 90 && envelope.ymin == 10 && envelope.ymax == 90); - assertTrue(polygon.getPointCount() == 14); - assertTrue(polygon.getPathCount() == 5); - assertTrue(spatial_reference.getLatestID() == 3857); - - map_geometry = importerGeoJson.execute(0, Geometry.Type.Unknown, geoJsonString, null); - polygon = (Polygon) map_geometry.getGeometry(); - spatial_reference = map_geometry.getSpatialReference(); - assertTrue(polygon != null); - polygon.queryEnvelope2D(envelope); - assertTrue(envelope.xmin == 10 && envelope.xmax == 90 && envelope.ymin == 10 && envelope.ymax == 90); - assertTrue(polygon.getPointCount() == 14); - assertTrue(polygon.getPathCount() == 5); - assertTrue(spatial_reference.getLatestID() == 3857); - - // Test Export to GeoJSON MultiPolygon - geoJsonString = exporterGeoJson.execute(GeoJsonExportFlags.geoJsonExportSkipCRS, spatial_reference, polygon); - assertTrue(geoJsonString.equals("{\"type\":\"MultiPolygon\",\"coordinates\":[[[[10,10,5],[20,10,5],[20,20,5],[10,20,5],[10,10,5]],[[12,12,3],[12,12,3],[12,12,3]],[[10,10,1],[12,12,1],[10,10,1]]],[[[90,90,88],[60,90,7],[60,60,7],[90,90,88]],[[70,70,7],[70,80,7],[80,80,7],[70,70,7]]]]}")); - - geoJsonString = exporterGeoJson.execute(0, spatial_reference, polygon); - assertTrue(geoJsonString.equals("{\"type\":\"MultiPolygon\",\"coordinates\":[[[[10,10,5],[20,10,5],[20,20,5],[10,20,5],[10,10,5]],[[12,12,3],[12,12,3],[12,12,3]],[[10,10,1],[12,12,1],[10,10,1]]],[[[90,90,88],[60,90,7],[60,60,7],[90,90,88]],[[70,70,7],[70,80,7],[80,80,7],[70,70,7]]]],\"crs\":{\"type\":\"name\",\"properties\":{\"name\":\"EPSG:3857\"}}}")); - - geoJsonString = "{\"type\": \"MultiPolygon\", \"coordinates\": [[[[90, 10, 7], [10, 10, 1], [10, 90, 7], [90, 90, 1], [90, 10, 7]]]] }"; // ring - // i // clockwise - polygon = (Polygon) (importerGeoJson.execute(0, Geometry.Type.Polygon, geoJsonString, null).getGeometry()); - assertTrue(polygon != null); - assertTrue(polygon.getPointCount() == 4); - assertTrue(polygon.getPathCount() == 1); - assertTrue(polygon.hasAttribute(VertexDescription.Semantics.Z)); - assertTrue(polygon.calculateArea2D() > 0); - - // Test import Polygon - geoJsonString = "{\"type\": \"Polygon\", \"coordinates\": [[], [], [[10, 10, 5], [10, 20, 5], [20, 20, 5], [20, 10, 5]], [[12, 12, 3]], [], [[10, 10, 1], [12, 12, 1]], [], [[60, 60, 7], [60, 90, 7], [90, 90, 7], [60, 60, 7]], [], [[70, 70, 7], [70, 80, 7], [80, 80, 7]], []] }"; - map_geometry = importerGeoJson.execute(0, Geometry.Type.Polygon, geoJsonString, null); - polygon = (Polygon) map_geometry.getGeometry(); - spatial_reference = map_geometry.getSpatialReference(); - assertTrue(polygon != null); - assertTrue(polygon.getPointCount() == 14); - assertTrue(polygon.getPathCount() == 5); - assertTrue(spatial_reference.getLatestID() == 4326); - - geoJsonString = exporterGeoJson.execute(0, spatial_reference, polygon); - assertTrue(geoJsonString.equals("{\"type\":\"Polygon\",\"coordinates\":[[[10,10,5],[20,10,5],[20,20,5],[10,20,5],[10,10,5]],[[12,12,3],[12,12,3],[12,12,3]],[[10,10,1],[12,12,1],[10,10,1]],[[60,60,7],[60,90,7],[90,90,7],[60,60,7]],[[70,70,7],[70,80,7],[80,80,7],[70,70,7]]],\"crs\":{\"type\":\"name\",\"properties\":{\"name\":\"EPSG:4326\"}}}")); - - Envelope env = new Envelope(); - env.addAttribute(VertexDescription.Semantics.Z); - polygon.queryEnvelope(env); - - geoJsonString = "{\"coordinates\" : [], \"type\": \"MultiPolygon\", \"crs\":{\"esriwkt\":\"PROJCS[\\\"Gnomonic\\\",GEOGCS[\\\"GCS_WGS_1984\\\",DATUM[\\\"D_WGS_1984\\\",SPHEROID[\\\"WGS_1984\\\",6378137.0,298.257223563]],PRIMEM[\\\"Greenwich\\\",0.0],UNIT[\\\"Degree\\\",0.0174532925199433]],PROJECTION[\\\"Gnomonic\\\"],PARAMETER[\\\"Longitude_Of_Center\\\",0.0],PARAMETER[\\\"Latitude_Of_Center\\\",-45.0],UNIT[\\\"Meter\\\",1.0]]\"}}"; - map_geometry = importerGeoJson.execute(0, Geometry.Type.Polygon, geoJsonString, null); - polygon = (Polygon) map_geometry.getGeometry(); - spatial_reference = map_geometry.getSpatialReference(); - String wkt = spatial_reference.getText(); - assertTrue(wkt.equals( - "PROJCS[\"Gnomonic\",GEOGCS[\"GCS_WGS_1984\",DATUM[\"D_WGS_1984\",SPHEROID[\"WGS_1984\",6378137.0,298.257223563]],PRIMEM[\"Greenwich\",0.0],UNIT[\"Degree\",0.0174532925199433]],PROJECTION[\"Gnomonic\"],PARAMETER[\"Longitude_Of_Center\",0.0],PARAMETER[\"Latitude_Of_Center\",-45.0],UNIT[\"Meter\",1.0]]")); - - geoJsonString = "{\"coordinates\" : [], \"type\": \"MultiPolygon\", \"crs\":{\"type\":\"name\",\"properties\":{\"name\":\"PROJCS[\\\"Gnomonic\\\",GEOGCS[\\\"GCS_WGS_1984\\\",DATUM[\\\"D_WGS_1984\\\",SPHEROID[\\\"WGS_1984\\\",6378137.0,298.257223563]],PRIMEM[\\\"Greenwich\\\",0.0],UNIT[\\\"Degree\\\",0.0174532925199433]],PROJECTION[\\\"Gnomonic\\\"],PARAMETER[\\\"Longitude_Of_Center\\\",0.0],PARAMETER[\\\"Latitude_Of_Center\\\",-45.0],UNIT[\\\"Meter\\\",1.0]]\"}}}"; - map_geometry = importerGeoJson.execute(0, Geometry.Type.Polygon, geoJsonString, null); - polygon = (Polygon) map_geometry.getGeometry(); - spatial_reference = map_geometry.getSpatialReference(); - wkt = spatial_reference.getText(); - assertTrue(wkt.equals( - "PROJCS[\"Gnomonic\",GEOGCS[\"GCS_WGS_1984\",DATUM[\"D_WGS_1984\",SPHEROID[\"WGS_1984\",6378137.0,298.257223563]],PRIMEM[\"Greenwich\",0.0],UNIT[\"Degree\",0.0174532925199433]],PROJECTION[\"Gnomonic\"],PARAMETER[\"Longitude_Of_Center\",0.0],PARAMETER[\"Latitude_Of_Center\",-45.0],UNIT[\"Meter\",1.0]]")); - assertTrue(polygon != null); - assertTrue(polygon.isEmpty()); - - // AGOL exports wkt like this... - geoJsonString = "{\"coordinates\" : [], \"type\": \"MultiPolygon\", \"crs\":{\"type\":\"name\",\"properties\":{\"name\":\"ESRI:PROJCS[\\\"Gnomonic\\\",GEOGCS[\\\"GCS_WGS_1984\\\",DATUM[\\\"D_WGS_1984\\\",SPHEROID[\\\"WGS_1984\\\",6378137.0,298.257223563]],PRIMEM[\\\"Greenwich\\\",0.0],UNIT[\\\"Degree\\\",0.0174532925199433]],PROJECTION[\\\"Gnomonic\\\"],PARAMETER[\\\"Longitude_Of_Center\\\",0.0],PARAMETER[\\\"Latitude_Of_Center\\\",-45.0],UNIT[\\\"Meter\\\",1.0]]\"}}}"; - map_geometry = importerGeoJson.execute(0, Geometry.Type.Polygon, geoJsonString, null); - polygon = (Polygon) map_geometry.getGeometry(); - spatial_reference = map_geometry.getSpatialReference(); - wkt = spatial_reference.getText(); - assertTrue(wkt.equals( - "PROJCS[\"Gnomonic\",GEOGCS[\"GCS_WGS_1984\",DATUM[\"D_WGS_1984\",SPHEROID[\"WGS_1984\",6378137.0,298.257223563]],PRIMEM[\"Greenwich\",0.0],UNIT[\"Degree\",0.0174532925199433]],PROJECTION[\"Gnomonic\"],PARAMETER[\"Longitude_Of_Center\",0.0],PARAMETER[\"Latitude_Of_Center\",-45.0],UNIT[\"Meter\",1.0]]")); - assertTrue(polygon != null); - assertTrue(polygon.isEmpty()); - - boolean exceptionThrownNoWKT = false; - - try { - geoJsonString = exporterGeoJson.execute(GeoJsonExportFlags.geoJsonExportPreferMultiGeometry, - spatial_reference, polygon); - } catch (Exception e) { - exceptionThrownNoWKT = true; - } - - assertTrue(exceptionThrownNoWKT); - } - - @Test - public static void testImportGeoJsonMultiLineString() throws Exception { - OperatorImportFromGeoJson importerGeoJson = (OperatorImportFromGeoJson) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.ImportFromGeoJson); - OperatorExportToGeoJson exporterGeoJson = (OperatorExportToGeoJson) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.ExportToGeoJson); - MapGeometry map_geometry; - Polyline polyline; - SpatialReference spatial_reference; - String geoJsonString; - Envelope2D envelope = new Envelope2D(); - - // Test Import from MultiLineString - geoJsonString = "{\"type\":\"MultiLineString\",\"coordinates\":[], \"crs\" : {\"type\" : \"URL\", \"properties\" : {\"url\" : \"http://www.opengis.net/def/crs/EPSG/0/3857\"}}}"; - map_geometry = importerGeoJson.execute(0, Geometry.Type.Unknown, geoJsonString, null); - polyline = (Polyline) map_geometry.getGeometry(); - spatial_reference = map_geometry.getSpatialReference(); - assertTrue(polyline != null); - assertTrue(spatial_reference != null); - assertTrue(polyline.isEmpty()); - assertTrue(spatial_reference.getLatestID() == 3857); - - geoJsonString = "{\"crs\" : {\"type\" : \"link\", \"properties\" : {\"href\" : \"www.spatialreference.org/ref/epsg/4309/\"}}, \"type\":\"MultiLineString\",\"coordinates\":[[], [], [[10, 10, 5], [10, 20, 5], [20, 88, 5], [20, 10, 5]], [[12, 88, 3]], [], [[10, 10, 1], [12, 12, 1]], [], [[88, 60, 7], [60, 90, 7], [90, 90, 7]], [], [[70, 70, 7], [70, 80, 7], [80, 80, 7]], []]}"; - map_geometry = importerGeoJson.execute(0, Geometry.Type.Unknown, geoJsonString, null); - polyline = (Polyline) map_geometry.getGeometry(); - spatial_reference = map_geometry.getSpatialReference(); - assertTrue(polyline != null); - polyline.queryEnvelope2D(envelope); - assertTrue(envelope.xmin == 10 && envelope.xmax == 90 && envelope.ymin == 10 && envelope.ymax == 90); - assertTrue(polyline.getPointCount() == 14); - assertTrue(polyline.getPathCount() == 5); - assertTrue(polyline.hasAttribute(VertexDescription.Semantics.Z)); - assertTrue(spatial_reference.getLatestID() == 4309); - - geoJsonString = exporterGeoJson.execute(0, spatial_reference, polyline); - assertTrue(geoJsonString.equals("{\"type\":\"MultiLineString\",\"coordinates\":[[[10,10,5],[10,20,5],[20,88,5],[20,10,5]],[[12,88,3],[12,88,3]],[[10,10,1],[12,12,1]],[[88,60,7],[60,90,7],[90,90,7]],[[70,70,7],[70,80,7],[80,80,7]]],\"crs\":{\"type\":\"name\",\"properties\":{\"name\":\"EPSG:4309\"}}}")); - - // Test Import LineString - geoJsonString = "{\"type\": \"LineString\", \"coordinates\": [[10, 10, 5], [10, 20, 5], [20, 20, 5], [20, 10, 5]]}"; - polyline = (Polyline) (importerGeoJson.execute(0, Geometry.Type.Unknown, geoJsonString, null).getGeometry()); - assertTrue(polyline.getPointCount() == 4); - assertTrue(polyline.hasAttribute(VertexDescription.Semantics.Z)); - - geoJsonString = "{\"type\": \"LineString\", \"coordinates\": [[10, 10, 5], [10, 20, 5, 3], [20, 20, 5], [20, 10, 5]]}"; - polyline = (Polyline) (importerGeoJson.execute(0, Geometry.Type.Unknown, geoJsonString, null).getGeometry()); - assertTrue(polyline.getPointCount() == 4); - assertTrue(polyline.hasAttribute(VertexDescription.Semantics.Z)); - assertTrue(polyline.hasAttribute(VertexDescription.Semantics.M)); - - geoJsonString = "{\"type\":\"LineString\",\"coordinates\": [[10, 10, 5], [10, 20, 5], [20, 20, 5], [], [20, 10, 5]],\"crs\" : {\"type\" : \"link\", \"properties\" : {\"href\" : \"www.opengis.net/def/crs/EPSG/0/3857\"}}}"; - map_geometry = importerGeoJson.execute(0, Geometry.Type.Unknown, geoJsonString, null); - polyline = (Polyline) (map_geometry.getGeometry()); - spatial_reference = map_geometry.getSpatialReference(); - assertTrue(polyline.getPointCount() == 4); - assertTrue(spatial_reference.getLatestID() == 3857); - geoJsonString = exporterGeoJson.execute(0, spatial_reference, polyline); - assertTrue(geoJsonString.equals("{\"type\":\"LineString\",\"coordinates\":[[10,10,5],[10,20,5],[20,20,5],[20,10,5]],\"crs\":{\"type\":\"name\",\"properties\":{\"name\":\"EPSG:3857\"}}}")); - - geoJsonString = exporterGeoJson.execute(0, null, polyline); - assertTrue(geoJsonString.equals("{\"type\":\"LineString\",\"coordinates\":[[10,10,5],[10,20,5],[20,20,5],[20,10,5]],\"crs\":null}")); - } - - @Test - public static void testImportGeoJsonMultiPoint() throws Exception { - OperatorImportFromGeoJson importerGeoJson = (OperatorImportFromGeoJson) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.ImportFromGeoJson); - OperatorExportToGeoJson exporterGeoJson = (OperatorExportToGeoJson) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.ExportToGeoJson); - MapGeometry map_geometry; - MultiPoint multipoint; - SpatialReference spatial_reference; - String geoJsonString; - Envelope2D envelope = new Envelope2D(); - - // Test Import from Multi_point - - geoJsonString = "{\"type\":\"MultiPoint\",\"coordinates\":[]}"; - map_geometry = importerGeoJson.execute(0, Geometry.Type.Unknown, geoJsonString, null); - multipoint = (MultiPoint) map_geometry.getGeometry(); - spatial_reference = map_geometry.getSpatialReference(); - assertTrue(multipoint != null); - assertTrue(multipoint.isEmpty()); - assertTrue(spatial_reference.getLatestID() == 4326); - - geoJsonString = exporterGeoJson.execute(0, null, multipoint); - assertTrue(geoJsonString.equals("{\"type\":\"MultiPoint\",\"coordinates\":[],\"crs\":null}")); - - multipoint = new MultiPoint(); - multipoint.add(118.15, 2); - multipoint.add(88, 88); - - geoJsonString = exporterGeoJson.execute(GeoJsonExportFlags.geoJsonExportPrecision16, SpatialReference.create(4269), multipoint); - assertTrue(geoJsonString.equals("{\"type\":\"MultiPoint\",\"coordinates\":[[118.15,2],[88,88]],\"crs\":{\"type\":\"name\",\"properties\":{\"name\":\"EPSG:4269\"}}}")); - - multipoint.setEmpty(); - multipoint.add(88, 2); - multipoint.add(88, 88); - - geoJsonString = exporterGeoJson.execute(0, SpatialReference.create(102100), multipoint); - assertTrue(geoJsonString.equals("{\"type\":\"MultiPoint\",\"coordinates\":[[88,2],[88,88]],\"crs\":{\"type\":\"name\",\"properties\":{\"name\":\"EPSG:3857\"}}}")); - - geoJsonString = "{\"type\":\"MultiPoint\",\"coordinates\":[[], [], [10, 88, 88, 33], [10, 20, 5, 33], [20, 20, 5, 33], [20, 10, 5, 33], [12, 12, 3, 33], [], [10, 10, 1, 33], [12, 12, 1, 33], [], [60, 60, 7, 33], [60, 90.1, 7, 33], [90, 90, 7, 33], [], [70, 70, 7, 33], [70, 80, 7, 33], [80, 80, 7, 33], []],\"crs\":{\"type\":\"OGC\",\"properties\":{\"urn\":\"urn:ogc:def:crs:OGC:1.3:CRS83\"}}}"; - map_geometry = importerGeoJson.execute(0, Geometry.Type.Unknown, geoJsonString, null); - multipoint = (MultiPoint) map_geometry.getGeometry(); - spatial_reference = map_geometry.getSpatialReference(); - assertTrue(multipoint != null); - assertTrue(multipoint.getPointCount() == 13); - assertTrue(multipoint.hasAttribute(VertexDescription.Semantics.Z)); - assertTrue(multipoint.hasAttribute(VertexDescription.Semantics.M)); - assertTrue(spatial_reference.getLatestID() == 4269); - - geoJsonString = "{\"type\":\"MultiPoint\",\"coordinates\": [[10, 88, 88, 33], [10, 20, 5, 33], [20, 20, 5, 33], [], [20, 10, 5, 33], [12, 12, 3, 33], [], [10, 10, 1, 33], [12, 12, 1, 33], [60, 60, 7, 33], [60, 90.1, 7, 33], [90, 90, 7, 33], [70, 70, 7, 33], [70, 80, 7, 33], [80, 80, 7, 33]]}"; - multipoint = (MultiPoint) importerGeoJson.execute(0, Geometry.Type.Unknown, geoJsonString, null).getGeometry(); - assertTrue(multipoint != null); - assertTrue(multipoint.getPointCount() == 13); - assertTrue(multipoint.hasAttribute(VertexDescription.Semantics.Z)); - assertTrue(multipoint.hasAttribute(VertexDescription.Semantics.M)); - - geoJsonString = exporterGeoJson.execute(GeoJsonExportFlags.geoJsonExportPrecision15, null, multipoint); - assertTrue(geoJsonString.equals("{\"type\":\"MultiPoint\",\"coordinates\":[[10,88,88,33],[10,20,5,33],[20,20,5,33],[20,10,5,33],[12,12,3,33],[10,10,1,33],[12,12,1,33],[60,60,7,33],[60,90.1,7,33],[90,90,7,33],[70,70,7,33],[70,80,7,33],[80,80,7,33]],\"crs\":null}")); - - geoJsonString = "{\"type\":\"MultiPoint\",\"coordinates\":[[], [], [10, 10, 5, 33]]}"; - multipoint = (MultiPoint) importerGeoJson.execute(0, Geometry.Type.Unknown, geoJsonString, null).getGeometry(); - - geoJsonString = exporterGeoJson.execute(0, null, multipoint); - assertTrue(geoJsonString.equals("{\"type\":\"MultiPoint\",\"coordinates\":[[10,10,5,33]],\"crs\":null}")); - } - - @Test - public static void testImportGeoJsonPolygon() throws Exception { - OperatorImportFromGeoJson importerGeoJson = (OperatorImportFromGeoJson) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.ImportFromGeoJson); - - Polygon polygon; - String geoJsonString; - Envelope2D envelope = new Envelope2D(); - - // Test Import from Polygon - geoJsonString = "{\"type\": \"Polygon\", \"coordinates\": []}"; - polygon = (Polygon) (importerGeoJson.execute(0, Geometry.Type.Unknown, geoJsonString, null).getGeometry()); - assertTrue(polygon != null); - assertTrue(polygon.isEmpty()); - assertTrue(!polygon.hasAttribute(VertexDescription.Semantics.Z)); - assertTrue(!polygon.hasAttribute(VertexDescription.Semantics.M)); - - geoJsonString = "{\"type\": \"Polygon\", \"coordinates\": [[], [[10, 10, 5], [20, 10, 5], [20, 20, 5], [10, 20, 5], [10, 10, 5]], [[12, 12, 3]], [], [[10, 10, 1], [12, 12, 1]]]}"; - polygon = (Polygon) (importerGeoJson.execute(0, Geometry.Type.Unknown, geoJsonString, null).getGeometry()); - assertTrue(polygon != null); - polygon.queryEnvelope2D(envelope); - assertTrue(envelope.xmin == 10 && envelope.xmax == 20 && envelope.ymin == 10 && envelope.ymax == 20); - assertTrue(polygon.getPointCount() == 8); - assertTrue(polygon.getPathCount() == 3); - assertTrue(polygon.hasAttribute(VertexDescription.Semantics.Z)); - - geoJsonString = "{\"type\": \"Polygon\", \"coordinates\": [[[35, 10], [10, 20], [15, 40], [45, 45], [35, 10]], [[20, 30], [35, 35], [30, 20], [20, 30]]]}"; - Polygon polygon2 = (Polygon) (importerGeoJson.execute(0, Geometry.Type.Unknown, geoJsonString, null).getGeometry()); - assertTrue(polygon2 != null); - } - - @Test - public static void testImportGeoJsonLineString() throws Exception { - OperatorImportFromGeoJson importerGeoJson = (OperatorImportFromGeoJson) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.ImportFromGeoJson); - - Polyline polyline; - String geoJsonString; - Envelope2D envelope = new Envelope2D(); - - // Test Import from LineString - geoJsonString = "{\"type\": \"LineString\", \"coordinates\": []}"; - polyline = (Polyline) (importerGeoJson.execute(0, Geometry.Type.Unknown, geoJsonString, null).getGeometry()); - assertTrue(polyline != null); - assertTrue(polyline.isEmpty()); - assertTrue(!polyline.hasAttribute(VertexDescription.Semantics.Z)); - assertTrue(!polyline.hasAttribute(VertexDescription.Semantics.M)); - - geoJsonString = "{\"type\": \"LineString\", \"coordinates\": [[10, 10, 5], [10, 20, 5], [20, 20, 5], [20, 10, 5]]}"; - polyline = (Polyline) (importerGeoJson.execute(0, Geometry.Type.Unknown, geoJsonString, null).getGeometry()); - assertTrue(polyline != null); - polyline.queryEnvelope2D(envelope); - assertTrue(envelope.xmin == 10 && envelope.xmax == 20 && envelope.ymin == 10 && envelope.ymax == 20); - assertTrue(polyline.getPointCount() == 4); - assertTrue(polyline.getPathCount() == 1); - assertTrue(!polyline.hasAttribute(VertexDescription.Semantics.M)); - } - - @Test - public static void testImportGeoJsonPoint() throws Exception { - OperatorImportFromGeoJson importerGeoJson = (OperatorImportFromGeoJson) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.ImportFromGeoJson); - OperatorExportToGeoJson exporterGeoJson = (OperatorExportToGeoJson) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.ExportToGeoJson); - MapGeometry map_geometry; - SpatialReference spatial_reference; - Point point; - String geoJsonString; - - // Test Import from Point - geoJsonString = "{\"type\":\"Point\",\"coordinates\":[],\"crs\":{\"type\":\"name\",\"properties\":{\"name\":\"EPSG:3857\"}}}"; - map_geometry = importerGeoJson.execute(0, Geometry.Type.Unknown, geoJsonString, null); - point = (Point) map_geometry.getGeometry(); - spatial_reference = map_geometry.getSpatialReference(); - assertTrue(spatial_reference.getLatestID() == 3857); - - assertTrue(point != null); - assertTrue(point.isEmpty()); - - geoJsonString = exporterGeoJson.execute(0, null, point); - assertTrue(geoJsonString.equals("{\"type\":\"Point\",\"coordinates\":[],\"crs\":null}")); - - geoJsonString = exporterGeoJson.execute(GeoJsonExportFlags.geoJsonExportPreferMultiGeometry, null, point); - assertTrue(geoJsonString.equals("{\"type\":\"MultiPoint\",\"coordinates\":[],\"crs\":null}")); - - geoJsonString = "{\"type\":\"Point\",\"coordinates\":[30.1,10.6,5.1,33.1],\"crs\":{\"type\":\"name\",\"properties\":{\"name\":\"urn:ogc:def:crs:ESRI::54051\"}}}"; - map_geometry = importerGeoJson.execute(0, Geometry.Type.Unknown, geoJsonString, null); - point = (Point) map_geometry.getGeometry(); - spatial_reference = map_geometry.getSpatialReference(); - assertTrue(point != null); - assertTrue(point.hasAttribute(VertexDescription.Semantics.Z)); - assertTrue(point.hasAttribute(VertexDescription.Semantics.M)); - assertTrue(spatial_reference.getLatestID() == 54051); - double x = point.getX(); - double y = point.getY(); - double z = point.getZ(); - double m = point.getM(); - - assertTrue(x == 30.1); - assertTrue(y == 10.6); - assertTrue(z == 5.1); - assertTrue(m == 33.1); - - geoJsonString = exporterGeoJson.execute(GeoJsonExportFlags.geoJsonExportPrecision15, spatial_reference, point); - assertTrue(geoJsonString.equals("{\"type\":\"Point\",\"coordinates\":[30.1,10.6,5.1,33.1],\"crs\":{\"type\":\"name\",\"properties\":{\"name\":\"ESRI:54051\"}}}")); - - geoJsonString = exporterGeoJson.execute(GeoJsonExportFlags.geoJsonExportPrecision15, SpatialReference.create(4287), point); - assertTrue(geoJsonString.equals("{\"type\":\"Point\",\"coordinates\":[30.1,10.6,5.1,33.1],\"crs\":{\"type\":\"name\",\"properties\":{\"name\":\"EPSG:4287\"}}}")); - - geoJsonString = exporterGeoJson.execute(GeoJsonExportFlags.geoJsonExportPreferMultiGeometry | GeoJsonExportFlags.geoJsonExportPrecision15, null, point); - assertTrue(geoJsonString.equals("{\"type\":\"MultiPoint\",\"coordinates\":[[30.1,10.6,5.1,33.1]],\"crs\":null}")); - } - - @Test - public static void testImportExportGeoJsonMalformed() { - OperatorImportFromGeoJson importerGeoJson = (OperatorImportFromGeoJson) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.ImportFromGeoJson); - OperatorExportToGeoJson exporterGeoJson = (OperatorExportToGeoJson) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.ExportToGeoJson); - - String geoJsonString; - - try { - geoJsonString = "{\"type\":\"MultiPolygon\",\"coordinates\":[[[2,2,2],[3,3,3],[4,4,4],[2,2,2]]]}"; - importerGeoJson.execute(0, Geometry.Type.Unknown, geoJsonString, null); - assertTrue(false); - } catch (Exception e) { - } - - try { - geoJsonString = "{\"type\":\"Polygon\",\"coordinates\":[[2,2,2],[3,3,3],[4,4,4],[2,2,2]]}"; - importerGeoJson.execute(0, Geometry.Type.Unknown, geoJsonString, null); - assertTrue(false); - } catch (Exception e) { - } - - try { - geoJsonString = "{\"type\":\"Polygon\",\"coordinates\":[[[2,2,2],[3,3,3],[4,4,4],[2,2,2]],2,4]}"; - importerGeoJson.execute(0, Geometry.Type.Unknown, geoJsonString, null); - assertTrue(false); - } catch (Exception e) { - } - - try { - geoJsonString = "{\"type\":\"MultiPoint\",\"coordinates\":[[[2,2,2],[3,3,3],[4,4,4],[2,2,2]]]}"; - importerGeoJson.execute(0, Geometry.Type.Unknown, geoJsonString, null); - assertTrue(false); - } catch (Exception e) { - } - - try { - geoJsonString = "{\"type\":\"LineString\",\"coordinates\":[[[2,2,2],[3,3,3],[4,4,4],[2,2,2]]]}"; - importerGeoJson.execute(0, Geometry.Type.Unknown, geoJsonString, null); - assertTrue(false); - } catch (Exception e) { - } - - try { - geoJsonString = "{\"type\":\"MultiPoint\",\"coordinates\":[[2,2,2],[3,3,3],[4,4,4],[2,2,2],[[]]]}"; - importerGeoJson.execute(0, Geometry.Type.Unknown, geoJsonString, null); - assertTrue(false); - } catch (Exception e) { - } - - try { - geoJsonString = "{\"type\":\"MultiPolygon\",\"coordinates\":[[[[2,2,2],[3,3,3],[4,4,4],[2,2,2],[[]]]]]}"; - importerGeoJson.execute(0, Geometry.Type.Unknown, geoJsonString, null); - assertTrue(false); - } catch (Exception e) { - } - - try { - geoJsonString = "{\"type\":\"MultiPolygon\",\"coordinates\":[[[[2,2,2],[3,3,3],[4,4,4],[2,2,2]],[1,1,1],[2,2,2],[3,3,3],[1,1,1]]]}"; - importerGeoJson.execute(0, Geometry.Type.Unknown, geoJsonString, null); - assertTrue(false); - } catch (Exception e) { - } - - try { - geoJsonString = "{\"type\":\"Polygon\",\"coordinates\":[[[2,2,2],[3,3,3],[4,4,4],[2,2,2]],[1,1,1],[2,2,2],[3,3,3],[1,1,1]]}"; - importerGeoJson.execute(0, Geometry.Type.Unknown, geoJsonString, null); - assertTrue(false); - } catch (Exception e) { - } - - try { - geoJsonString = "{\"type\":\"MultiPolygon\",\"coordinates\":[[[[[]]]]}"; - importerGeoJson.execute(0, Geometry.Type.Unknown, geoJsonString, null); - assertTrue(false); - } catch (Exception e) { - } - - try { - geoJsonString = "{\"type\":\"MultiPolygon\",\"coordinates\":[[[[{}]]]}"; - importerGeoJson.execute(0, Geometry.Type.Unknown, geoJsonString, null); - assertTrue(false); - } catch (Exception e) { - } - - try { - geoJsonString = "{\"type\":\"Point\",\"coordinates\":[30.1,10.6,[],33.1],\"crs\":\"EPSG:3857\"}"; - importerGeoJson.execute(0, Geometry.Type.Unknown, geoJsonString, null); - assertTrue(false); - } catch (Exception e) { - } - } - - @Test - public static void testImportGeoJsonSpatialReference() throws Exception { - OperatorImportFromGeoJson importerGeoJson = (OperatorImportFromGeoJson) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.ImportFromGeoJson); - - String geoJsonString4326; - String geoJsonString3857; - - // Test Import from Point - geoJsonString4326 = "{\"type\": \"Point\", \"coordinates\": [3.0, 5.0], \"crs\": \"EPSG:4326\"}"; - geoJsonString3857 = "{\"type\": \"Point\", \"coordinates\": [3.0, 5.0], \"crs\": \"EPSG:3857\"}"; - - MapGeometry mapGeometry4326 = importerGeoJson.execute(0, Geometry.Type.Unknown, geoJsonString4326, null); - MapGeometry mapGeometry3857 = importerGeoJson.execute(0, Geometry.Type.Unknown, geoJsonString3857, null); - - assertTrue(mapGeometry4326.equals(mapGeometry3857) == false); - assertTrue(mapGeometry4326.getGeometry().equals(mapGeometry3857.getGeometry())); - } - - @Test - public static void testImportExportESRICursors() { - Polygon poly1 = new Polygon(); - Envelope2D env1 = new Envelope2D(); - env1.setCoords(855277, 3892059, 855277 + 100, 3892059 + 100); - poly1.addEnvelope(env1, false); - - Polygon poly2 = new Polygon(); - Envelope2D env2 = new Envelope2D(); - env2.setCoords(855277, 3892059, 855277 + 300, 3892059 + 200); - poly2.addEnvelope(env2, false); - List list2 = new ArrayList<>(); - list2.add(poly1); - list2.add(poly2); - SimpleGeometryCursor simpleGeometryCursor2 = new SimpleGeometryCursor(new ArrayDeque(list2)); - - OperatorExportToESRIShapeCursor operatorExportToESRIShapeCursor = new OperatorExportToESRIShapeCursor(0, simpleGeometryCursor2); - OperatorImportFromESRIShapeCursor operatorImportFromESRIShapeCursor = new OperatorImportFromESRIShapeCursor(0, 0, operatorExportToESRIShapeCursor); - - SpatialReference inputSR = SpatialReference.create(3857); - OperatorFactoryLocal projEnv = OperatorFactoryLocal.getInstance(); - OperatorCrosses operatorCrosses = (OperatorCrosses) (projEnv.getOperator(Operator.Type.Crosses)); - HashMap relate_map = operatorCrosses.execute(poly1, - operatorImportFromESRIShapeCursor, inputSR, null); - - assertNotNull(relate_map); - - assertTrue(!relate_map.get(0L)); - assertTrue(!relate_map.get(1L)); - } - - @Test - public static void testImportExportWKBCursors() { - Polygon poly1 = new Polygon(); - Envelope2D env1 = new Envelope2D(); - env1.setCoords(855277, 3892059, 855277 + 100, 3892059 + 100); - poly1.addEnvelope(env1, false); - - Polygon poly2 = new Polygon(); - Envelope2D env2 = new Envelope2D(); - env2.setCoords(855277, 3892059, 855277 + 300, 3892059 + 200); - poly2.addEnvelope(env2, false); - List list2 = new ArrayList<>(); - list2.add(poly1); - list2.add(poly2); - SimpleGeometryCursor simpleGeometryCursor2 = new SimpleGeometryCursor(new ArrayDeque(list2)); - - OperatorExportToWkbCursor operatorExportToWkbCursor = new OperatorExportToWkbCursor(0, simpleGeometryCursor2); - OperatorImportFromWkbCursor operatorImportFromWkbCursor = new OperatorImportFromWkbCursor(0, operatorExportToWkbCursor); - - SpatialReference inputSR = SpatialReference.create(3857); - OperatorFactoryLocal projEnv = OperatorFactoryLocal.getInstance(); - OperatorCrosses operatorCrosses = (OperatorCrosses) (projEnv.getOperator(Operator.Type.Crosses)); - HashMap relate_map = operatorCrosses.execute(poly1, - operatorImportFromWkbCursor, inputSR, null); - - assertNotNull(relate_map); - assertTrue(!relate_map.get(0L)); - assertTrue(!relate_map.get(1L)); - - simpleGeometryCursor2 = new SimpleGeometryCursor(new ArrayDeque(list2)); - operatorExportToWkbCursor = new OperatorExportToWkbCursor(0, simpleGeometryCursor2); - operatorImportFromWkbCursor = new OperatorImportFromWkbCursor(0, operatorExportToWkbCursor); - double[] distances = {400, 400}; - - OperatorBufferCursor operatorBufferCursor = new OperatorBufferCursor(operatorImportFromWkbCursor, null, distances, NumberUtils.NaN(), 96, false, null); - relate_map = ((OperatorWithin) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.Within)).execute(poly2, operatorBufferCursor, inputSR, null); - assertNotNull(relate_map); - assertTrue(relate_map.get(0L)); - assertTrue(relate_map.get(1L)); - } - - static double randomWithRange(double min, double max) { - double range = Math.abs(max - min); - return (Math.random() * range) + (min <= max ? min : max); - } - - @Test - public static void testImportExportWKTCursors() { - Polygon poly1 = new Polygon(); - Envelope2D env1 = new Envelope2D(); - env1.setCoords(855277, 3892059, 855277 + 100, 3892059 + 100); - poly1.addEnvelope(env1, false); - - Polygon poly2 = new Polygon(); - Envelope2D env2 = new Envelope2D(); - env2.setCoords(855277, 3892059, 855277 + 300, 3892059 + 200); - poly2.addEnvelope(env2, false); - List list2 = new ArrayList<>(); - list2.add(poly1); - list2.add(poly2); - - int size = 1000; - String[] points = new String[size]; - List pointList = new ArrayList<>(size); - ArrayDeque pointArrayDeque = new ArrayDeque<>(size); - ArrayDeque ids = new ArrayDeque<>(size); - for (int i = 0; i < size; i++) { - pointList.add(new Point(randomWithRange(-20, 20), randomWithRange(-20, 20))); - ids.push((long) i + size); - points[i] = (String.format("Point(%f %f)", pointList.get(i).getX(), pointList.get(i).getY())); - pointArrayDeque.push(points[i]); - } - - SimpleStringCursor simpleStringCursor = new SimpleStringCursor(points); - OperatorImportFromWktCursor operatorImportFromWktCursor = new OperatorImportFromWktCursor(0, simpleStringCursor); - int index = 0; - while (operatorImportFromWktCursor.hasNext()) { - Point point_orig = pointList.get(index); - - Point geom = (Point) operatorImportFromWktCursor.next(); - long geometryID = operatorImportFromWktCursor.getGeometryID(); - assertEquals(geometryID, index); - index++; - assertEquals(point_orig.getX(), geom.getX(), 0.000001); - assertEquals(point_orig.getY(), geom.getY(), 0.000001); - } - - - SimpleGeometryCursor simpleGeometryCursor2 = new SimpleGeometryCursor(new ArrayDeque(list2)); - OperatorExportToWktCursor operatorExportToWktCursor = new OperatorExportToWktCursor(0, simpleGeometryCursor2, null); - operatorImportFromWktCursor = new OperatorImportFromWktCursor(0, operatorExportToWktCursor); - - SpatialReference inputSR = SpatialReference.create(3857); - OperatorFactoryLocal projEnv = OperatorFactoryLocal.getInstance(); - OperatorCrosses operatorCrosses = (OperatorCrosses) (projEnv.getOperator(Operator.Type.Crosses)); - HashMap relate_map = operatorCrosses.execute(poly1, - operatorImportFromWktCursor, inputSR, null); - - assertNotNull(relate_map); - assertTrue(!relate_map.get(0L)); - assertTrue(!relate_map.get(1L)); - - simpleGeometryCursor2 = new SimpleGeometryCursor(new ArrayDeque(list2)); - operatorExportToWktCursor = new OperatorExportToWktCursor(0, simpleGeometryCursor2, null); - operatorImportFromWktCursor = new OperatorImportFromWktCursor(0, operatorExportToWktCursor); - double[] distances = {400, 400}; - OperatorBufferCursor operatorBufferCursor = new OperatorBufferCursor(operatorImportFromWktCursor, null, distances, NumberUtils.NaN(), 96, false, null); - relate_map = ((OperatorWithin) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.Within)).execute(poly2, operatorBufferCursor, inputSR, null); - assertNotNull(relate_map); - assertTrue(relate_map.get(0L)); - assertTrue(relate_map.get(1L)); - - - } - - @Test - public void testWKTID() { - Polygon poly1 = new Polygon(); - Envelope2D env1 = new Envelope2D(); - env1.setCoords(855277, 3892059, 855277 + 100, 3892059 + 100); - poly1.addEnvelope(env1, false); - - Polygon poly2 = new Polygon(); - Envelope2D env2 = new Envelope2D(); - env2.setCoords(855277, 3892059, 855277 + 300, 3892059 + 200); - poly2.addEnvelope(env2, false); - List list2 = new ArrayList<>(); - list2.add(poly1); - list2.add(poly2); - - int size = 1000; - String[] points = new String[size]; - List pointList = new ArrayList<>(size); - List stringList = new ArrayList<>(size); - ArrayDeque pointArrayDeque = new ArrayDeque<>(size); - ArrayDeque ids = new ArrayDeque<>(size); - for (int i = 0; i < size; i++) { - pointList.add(new Point(randomWithRange(-20, 20), randomWithRange(-20, 20))); - ids.addLast((long) i + size); - points[i] = (String.format("Point(%f %f)", pointList.get(i).getX(), pointList.get(i).getY())); - pointArrayDeque.addLast(points[i]); - stringList.add(points[i]); - } - - SimpleStringCursor simpleStringCursor1 = new SimpleStringCursor(points); - SimpleStringCursor simpleStringCursor2 = new SimpleStringCursor(pointArrayDeque.clone(), ids.clone()); - SimpleStringCursor simpleStringCursor3 = new SimpleStringCursor(stringList); - - OperatorImportFromWktCursor operatorImportFromWktCursor1 = new OperatorImportFromWktCursor(0, simpleStringCursor1); - OperatorImportFromWktCursor operatorImportFromWktCursor2 = new OperatorImportFromWktCursor(0, simpleStringCursor2); - OperatorImportFromWktCursor operatorImportFromWktCursor3 = new OperatorImportFromWktCursor(0, simpleStringCursor3); - - while (operatorImportFromWktCursor1.hasNext()) { - Geometry geometry1 = operatorImportFromWktCursor1.next(); - Geometry geometry2 = operatorImportFromWktCursor2.next(); - Geometry geometry3 = operatorImportFromWktCursor3.next(); - assertTrue(geometry1.equals(geometry3)); - assertTrue(geometry1.equals(geometry2)); - } - - SimpleStringCursor simpleStringCursor = new SimpleStringCursor(pointArrayDeque.clone(), ids.clone()); - OperatorImportFromWktCursor operatorImportFromWktCursor = new OperatorImportFromWktCursor(0, simpleStringCursor); - int index = 0; - while (operatorImportFromWktCursor.hasNext()) { - Point point_orig = pointList.get(index); - - Point geom = (Point) operatorImportFromWktCursor.next(); - long geometryID = operatorImportFromWktCursor.getGeometryID(); - assertEquals(geometryID, index + size); - index++; - assertEquals(point_orig.getX(), geom.getX(), 0.000001); - assertEquals(point_orig.getY(), geom.getY(), 0.000001); - } - assertTrue(index > 0); - - simpleStringCursor = new SimpleStringCursor(pointArrayDeque.clone(), ids.clone()); - operatorImportFromWktCursor = new OperatorImportFromWktCursor(0, simpleStringCursor); - OperatorProjectCursor operatorProjectCursor = new OperatorProjectCursor(operatorImportFromWktCursor, new ProjectionTransformation(SpatialReference.create(3857), SpatialReference.create(4326)), null); - double[]stuff = new double[] {3000}; - OperatorGeodesicBufferCursor operatorGeodesicBufferCursor = new OperatorGeodesicBufferCursor(operatorProjectCursor, SpatialReference.create(4326), stuff, 10, false, false, null); - index = 0; - while (operatorGeodesicBufferCursor.hasNext()) { - Geometry polygon = operatorGeodesicBufferCursor.next(); - long geometryID = operatorGeodesicBufferCursor.getGeometryID(); - assertEquals(geometryID, index + size); - index ++; - } - assertTrue(index > 0); - - - } - - @Test - public void testMultiPointOrdering() { - MultiPoint multiPoint = new MultiPoint(); - for (double longitude = -180; longitude < 180; longitude+=10.0) { - for (double latitude = -80; latitude < 80; latitude+=10.0) { - multiPoint.add(longitude, latitude); - } - } - String wktGeom = OperatorExportToWkt.local().execute(0, multiPoint, null); - Geometry roundTripGeom = OperatorImportFromWkt.local().execute(0, Geometry.Type.Unknown, wktGeom, null); - assertTrue(roundTripGeom.equals(multiPoint)); - } - - - public static Polygon makePolygon() { - Polygon poly = new Polygon(); - poly.startPath(0, 0); - poly.lineTo(0, 10); - poly.lineTo(10, 10); - poly.lineTo(10, 0); - - poly.startPath(3, 3); - poly.lineTo(7, 3); - poly.lineTo(7, 7); - poly.lineTo(3, 7); - - poly.startPath(15, 0); - poly.lineTo(15, 15); - poly.lineTo(30, 15); - poly.lineTo(30, 0); - - poly.setAttribute(VertexDescription.Semantics.Z, 0, 0, 2); - poly.setAttribute(VertexDescription.Semantics.Z, 1, 0, 3); - poly.setAttribute(VertexDescription.Semantics.Z, 2, 0, 5); - poly.setAttribute(VertexDescription.Semantics.Z, 3, 0, 7); - poly.setAttribute(VertexDescription.Semantics.Z, 4, 0, 11); - poly.setAttribute(VertexDescription.Semantics.Z, 5, 0, 13); - poly.setAttribute(VertexDescription.Semantics.Z, 6, 0, 17); - poly.setAttribute(VertexDescription.Semantics.Z, 7, 0, 19); - poly.setAttribute(VertexDescription.Semantics.Z, 8, 0, 23); - poly.setAttribute(VertexDescription.Semantics.Z, 9, 0, 29); - poly.setAttribute(VertexDescription.Semantics.Z, 10, 0, 31); - poly.setAttribute(VertexDescription.Semantics.Z, 11, 0, 37); - - poly.setAttribute(VertexDescription.Semantics.M, 0, 0, 2); - poly.setAttribute(VertexDescription.Semantics.M, 1, 0, 4); - poly.setAttribute(VertexDescription.Semantics.M, 2, 0, 8); - poly.setAttribute(VertexDescription.Semantics.M, 3, 0, 16); - poly.setAttribute(VertexDescription.Semantics.M, 4, 0, 32); - poly.setAttribute(VertexDescription.Semantics.M, 5, 0, 64); - poly.setAttribute(VertexDescription.Semantics.M, 6, 0, 128); - poly.setAttribute(VertexDescription.Semantics.M, 7, 0, 256); - poly.setAttribute(VertexDescription.Semantics.M, 8, 0, 512); - poly.setAttribute(VertexDescription.Semantics.M, 9, 0, 1024); - poly.setAttribute(VertexDescription.Semantics.M, 10, 0, 2048); - poly.setAttribute(VertexDescription.Semantics.M, 11, 0, 4096); - - return poly; - } - - public static Polygon makePolygon2() { - Polygon poly = new Polygon(); - poly.startPath(0, 0); - poly.lineTo(0, 10); - poly.lineTo(10, 10); - poly.lineTo(10, 0); - - poly.setAttribute(VertexDescription.Semantics.Z, 0, 0, 2); - poly.setAttribute(VertexDescription.Semantics.Z, 1, 0, 3); - poly.setAttribute(VertexDescription.Semantics.Z, 2, 0, 5); - poly.setAttribute(VertexDescription.Semantics.Z, 3, 0, 7); - - poly.setAttribute(VertexDescription.Semantics.M, 0, 0, 2); - poly.setAttribute(VertexDescription.Semantics.M, 1, 0, 4); - poly.setAttribute(VertexDescription.Semantics.M, 2, 0, 8); - poly.setAttribute(VertexDescription.Semantics.M, 3, 0, 16); - - return poly; - } - - public static Polyline makePolyline() { - Polyline poly = new Polyline(); - poly.startPath(10, 1); - poly.lineTo(15, 20); - poly.lineTo(30, 14); - poly.lineTo(60, 144); - - poly.startPath(20, 13); - poly.lineTo(150, 120); - poly.lineTo(300, 414); - poly.lineTo(610, 14); - - poly.setAttribute(VertexDescription.Semantics.Z, 0, 0, 2); - poly.setAttribute(VertexDescription.Semantics.Z, 1, 0, 3); - poly.setAttribute(VertexDescription.Semantics.Z, 2, 0, 5); - poly.setAttribute(VertexDescription.Semantics.Z, 3, 0, 7); - poly.setAttribute(VertexDescription.Semantics.Z, 4, 0, 11); - poly.setAttribute(VertexDescription.Semantics.Z, 5, 0, 13); - poly.setAttribute(VertexDescription.Semantics.Z, 6, 0, 17); - poly.setAttribute(VertexDescription.Semantics.Z, 7, 0, 19); - - poly.setAttribute(VertexDescription.Semantics.M, 0, 0, 2); - poly.setAttribute(VertexDescription.Semantics.M, 1, 0, 4); - poly.setAttribute(VertexDescription.Semantics.M, 2, 0, 8); - poly.setAttribute(VertexDescription.Semantics.M, 3, 0, 16); - poly.setAttribute(VertexDescription.Semantics.M, 4, 0, 32); - poly.setAttribute(VertexDescription.Semantics.M, 5, 0, 64); - poly.setAttribute(VertexDescription.Semantics.M, 6, 0, 128); - poly.setAttribute(VertexDescription.Semantics.M, 7, 0, 256); - - poly.setAttribute(VertexDescription.Semantics.ID, 0, 0, 1); - poly.setAttribute(VertexDescription.Semantics.ID, 1, 0, 2); - poly.setAttribute(VertexDescription.Semantics.ID, 2, 0, 3); - poly.setAttribute(VertexDescription.Semantics.ID, 3, 0, 5); - poly.setAttribute(VertexDescription.Semantics.ID, 4, 0, 8); - poly.setAttribute(VertexDescription.Semantics.ID, 5, 0, 13); - poly.setAttribute(VertexDescription.Semantics.ID, 6, 0, 21); - poly.setAttribute(VertexDescription.Semantics.ID, 7, 0, 34); - - return poly; - } - - public static Polyline makePolyline2() { - Polyline poly = new Polyline(); - poly.startPath(10, 1); - poly.lineTo(15, 20); - poly.lineTo(30, 14); - poly.lineTo(60, 144); - - poly.setAttribute(VertexDescription.Semantics.Z, 0, 0, 2); - poly.setAttribute(VertexDescription.Semantics.Z, 1, 0, 3); - poly.setAttribute(VertexDescription.Semantics.Z, 2, 0, 5); - poly.setAttribute(VertexDescription.Semantics.Z, 3, 0, 7); - - poly.setAttribute(VertexDescription.Semantics.M, 0, 0, 2); - poly.setAttribute(VertexDescription.Semantics.M, 1, 0, 4); - poly.setAttribute(VertexDescription.Semantics.M, 2, 0, 8); - poly.setAttribute(VertexDescription.Semantics.M, 3, 0, 16); - - return poly; - } - - public static Point makePoint() { - Point point = new Point(); - point.setXY(11, 13); - - point.setZ(32); - point.setM(243); - point.setID(1024); - - return point; - } - - public static MultiPoint makeMultiPoint() { - MultiPoint mpoint = new MultiPoint(); - Point pt1 = new Point(); - pt1.setXY(0, 0); - pt1.setZ(-1); - - Point pt2 = new Point(); - pt2.setXY(0, 0); - pt2.setZ(1); - - Point pt3 = new Point(); - pt3.setXY(0, 1); - pt3.setZ(1); - - mpoint.add(pt1); - mpoint.add(pt2); - mpoint.add(pt3); - - mpoint.setAttribute(VertexDescription.Semantics.ID, 0, 0, 7); - mpoint.setAttribute(VertexDescription.Semantics.ID, 1, 0, 11); - mpoint.setAttribute(VertexDescription.Semantics.ID, 2, 0, 13); - - return mpoint; - } - - public static MultiPoint makeMultiPoint2() { - MultiPoint mpoint = new MultiPoint(); - Point pt1 = new Point(); - pt1.setX(0.0); - pt1.setY(0.0); - pt1.setZ(-1.0); - - mpoint.add(pt1); - - return mpoint; - } - - public static Envelope makeEnvelope() { - Envelope envelope; - - Envelope env = new Envelope(0.0, 0.0, 5.0, 5.0); - envelope = env; - - Envelope1D interval = new Envelope1D(); - interval.vmin = -3.0; - interval.vmax = -7.0; - envelope.setInterval(VertexDescription.Semantics.Z, 0, interval); - - interval.vmin = 16.0; - interval.vmax = 32.0; - envelope.setInterval(VertexDescription.Semantics.M, 0, interval); - - interval.vmin = 5.0; - interval.vmax = 11.0; - envelope.setInterval(VertexDescription.Semantics.ID, 0, interval); - - return envelope; - } + OperatorExportToESRIShape exporterShape = (OperatorExportToESRIShape) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.ExportToESRIShape); + OperatorImportFromESRIShape importerShape = (OperatorImportFromESRIShape) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.ImportFromESRIShape); + + Polygon polygon = makePolygon(); + + byte[] esriShape = GeometryEngine.geometryToEsriShape(polygon); + Geometry imported = GeometryEngine.geometryFromEsriShape(esriShape, Geometry.Type.Unknown); + TestCommonMethods.compareGeometryContent((MultiPath) imported, polygon); + + // Test Import Polygon from Polygon + ByteBuffer polygonShapeBuffer = exporterShape.execute(0, polygon); + Geometry polygonShapeGeometry = importerShape.execute(0, Geometry.Type.Polygon, polygonShapeBuffer); + + TestCommonMethods.compareGeometryContent((MultiPath) polygonShapeGeometry, polygon); + + // Test Import Envelope from Polygon + Geometry envelopeShapeGeometry = importerShape.execute(0, Geometry.Type.Envelope, polygonShapeBuffer); + Envelope envelope = (Envelope) envelopeShapeGeometry; + + @SuppressWarnings("unused") Envelope env = new Envelope(), otherenv = new Envelope(); + polygon.queryEnvelope(otherenv); + assertTrue(envelope.getXMin() == otherenv.getXMin()); + assertTrue(envelope.getXMax() == otherenv.getXMax()); + assertTrue(envelope.getYMin() == otherenv.getYMin()); + assertTrue(envelope.getYMax() == otherenv.getYMax()); + + Envelope1D interval, otherinterval; + interval = envelope.queryInterval(VertexDescription.Semantics.Z, 0); + otherinterval = polygon.queryInterval(VertexDescription.Semantics.Z, 0); + assertTrue(interval.vmin == otherinterval.vmin); + assertTrue(interval.vmax == otherinterval.vmax); + } + + @Test + public static void testImportExportShapePolyline() { + OperatorExportToESRIShape exporterShape = (OperatorExportToESRIShape) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.ExportToESRIShape); + OperatorImportFromESRIShape importerShape = (OperatorImportFromESRIShape) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.ImportFromESRIShape); + + Polyline polyline = makePolyline(); + + // Test Import Polyline from Polyline + ByteBuffer polylineShapeBuffer = exporterShape.execute(0, polyline); + Geometry polylineShapeGeometry = importerShape.execute(0, Geometry.Type.Polyline, polylineShapeBuffer); + + // TODO test this + TestCommonMethods.compareGeometryContent((MultiPath) polylineShapeGeometry, polyline); + + // Test Import Envelope from Polyline; + Geometry envelopeShapeGeometry = importerShape.execute(0, Geometry.Type.Envelope, polylineShapeBuffer); + Envelope envelope = (Envelope) envelopeShapeGeometry; + + Envelope env = new Envelope(), otherenv = new Envelope(); + envelope.queryEnvelope(env); + polyline.queryEnvelope(otherenv); + assertTrue(env.getXMin() == otherenv.getXMin()); + assertTrue(env.getXMax() == otherenv.getXMax()); + assertTrue(env.getYMin() == otherenv.getYMin()); + assertTrue(env.getYMax() == otherenv.getYMax()); + + Envelope1D interval, otherinterval; + interval = envelope.queryInterval(VertexDescription.Semantics.Z, 0); + otherinterval = polyline.queryInterval(VertexDescription.Semantics.Z, 0); + assertTrue(interval.vmin == otherinterval.vmin); + assertTrue(interval.vmax == otherinterval.vmax); + } + + @Test + public static void testImportExportShapeMultiPoint() { + OperatorExportToESRIShape exporterShape = (OperatorExportToESRIShape) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.ExportToESRIShape); + OperatorImportFromESRIShape importerShape = (OperatorImportFromESRIShape) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.ImportFromESRIShape); + + MultiPoint multipoint = makeMultiPoint(); + + // Test Import MultiPoint from MultiPoint + ByteBuffer multipointShapeBuffer = exporterShape.execute(0, multipoint); + MultiPoint multipointShapeGeometry = (MultiPoint) importerShape.execute(0, Geometry.Type.MultiPoint, multipointShapeBuffer); + + TestCommonMethods.compareGeometryContent((MultiPoint) multipointShapeGeometry, multipoint); + + // Test Import Envelope from MultiPoint + Geometry envelopeShapeGeometry = importerShape.execute(0, Geometry.Type.Envelope, multipointShapeBuffer); + Envelope envelope = (Envelope) envelopeShapeGeometry; + + Envelope env = new Envelope(), otherenv = new Envelope(); + envelope.queryEnvelope(env); + multipoint.queryEnvelope(otherenv); + assertTrue(env.getXMin() == otherenv.getXMin()); + assertTrue(env.getXMax() == otherenv.getXMax()); + assertTrue(env.getYMin() == otherenv.getYMin()); + assertTrue(env.getYMax() == otherenv.getYMax()); + + Envelope1D interval, otherinterval; + interval = envelope.queryInterval(VertexDescription.Semantics.Z, 0); + otherinterval = multipoint.queryInterval(VertexDescription.Semantics.Z, 0); + assertTrue(interval.vmin == otherinterval.vmin); + assertTrue(interval.vmax == otherinterval.vmax); + + interval = envelope.queryInterval(VertexDescription.Semantics.ID, 0); + otherinterval = multipoint.queryInterval(VertexDescription.Semantics.ID, 0); + assertTrue(interval.vmin == otherinterval.vmin); + assertTrue(interval.vmax == otherinterval.vmax); + } + + @Test + public static void testImportExportShapePoint() { + OperatorExportToESRIShape exporterShape = (OperatorExportToESRIShape) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.ExportToESRIShape); + OperatorImportFromESRIShape importerShape = (OperatorImportFromESRIShape) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.ImportFromESRIShape); + + // Point + Point point = makePoint(); + + // Test Import Point from Point + ByteBuffer pointShapeBuffer = exporterShape.execute(0, point); + Point pointShapeGeometry = (Point) importerShape.execute(0, Geometry.Type.Point, pointShapeBuffer); + + double x1 = point.getX(); + double x2 = pointShapeGeometry.getX(); + assertTrue(x1 == x2); + + double y1 = point.getY(); + double y2 = pointShapeGeometry.getY(); + assertTrue(y1 == y2); + + double z1 = point.getZ(); + double z2 = pointShapeGeometry.getZ(); + assertTrue(z1 == z2); + + double m1 = point.getM(); + double m2 = pointShapeGeometry.getM(); + assertTrue(m1 == m2); + + int id1 = point.getID(); + int id2 = pointShapeGeometry.getID(); + assertTrue(id1 == id2); + + // Test Import Multipoint from Point + MultiPoint multipointShapeGeometry = (MultiPoint) importerShape.execute(0, Geometry.Type.MultiPoint, pointShapeBuffer); + Point point2d = multipointShapeGeometry.getPoint(0); + assertTrue(x1 == point2d.getX() && y1 == point2d.getY()); + + int pointCount = multipointShapeGeometry.getPointCount(); + assertTrue(pointCount == 1); + + z2 = multipointShapeGeometry.getAttributeAsDbl(VertexDescription.Semantics.Z, 0, 0); + assertTrue(z1 == z2); + + m2 = multipointShapeGeometry.getAttributeAsDbl(VertexDescription.Semantics.M, 0, 0); + assertTrue(m1 == m2); + + id2 = multipointShapeGeometry.getAttributeAsInt(VertexDescription.Semantics.ID, 0, 0); + assertTrue(id1 == id2); + + // Test Import Envelope from Point + Geometry envelopeShapeGeometry = importerShape.execute(0, Geometry.Type.Envelope, pointShapeBuffer); + Envelope envelope = (Envelope) envelopeShapeGeometry; + + Envelope env = new Envelope(), otherenv = new Envelope(); + envelope.queryEnvelope(env); + point.queryEnvelope(otherenv); + assertTrue(env.getXMin() == otherenv.getXMin()); + assertTrue(env.getXMax() == otherenv.getXMax()); + assertTrue(env.getYMin() == otherenv.getYMin()); + assertTrue(env.getYMax() == otherenv.getYMax()); + + Envelope1D interval, otherinterval; + interval = envelope.queryInterval(VertexDescription.Semantics.Z, 0); + otherinterval = point.queryInterval(VertexDescription.Semantics.Z, 0); + assertTrue(interval.vmin == otherinterval.vmin); + assertTrue(interval.vmax == otherinterval.vmax); + + interval = envelope.queryInterval(VertexDescription.Semantics.ID, 0); + otherinterval = point.queryInterval(VertexDescription.Semantics.ID, 0); + assertTrue(interval.vmin == otherinterval.vmin); + assertTrue(interval.vmax == otherinterval.vmax); + } + + @Test + public static void testImportExportShapeEnvelope() { + OperatorExportToESRIShape exporterShape = (OperatorExportToESRIShape) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.ExportToESRIShape); + OperatorImportFromESRIShape importerShape = (OperatorImportFromESRIShape) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.ImportFromESRIShape); + + // Test Export Envelope to Polygon + Envelope envelope = makeEnvelope(); + + ByteBuffer polygonShapeBuffer = exporterShape.execute(0, envelope); + Polygon polygon = (Polygon) importerShape.execute(0, Geometry.Type.Polygon, polygonShapeBuffer); + int pointCount = polygon.getPointCount(); + assertTrue(pointCount == 4); + + Envelope env = new Envelope(); + + envelope.queryEnvelope(env); + // interval = envelope.queryInterval(VertexDescription.Semantics.Z, 0); + Point point3d; + point3d = polygon.getPoint(0); + assertTrue(point3d.getX() == env.getXMin() && point3d.getY() == env.getYMin());// && point3d.z == + // interval.vmin); + point3d = polygon.getPoint(1); + assertTrue(point3d.getX() == env.getXMin() && point3d.getY() == env.getYMax());// && point3d.z == + // interval.vmax); + point3d = polygon.getPoint(2); + assertTrue(point3d.getX() == env.getXMax() && point3d.getY() == env.getYMax());// && point3d.z == + // interval.vmin); + point3d = polygon.getPoint(3); + assertTrue(point3d.getX() == env.getXMax() && point3d.getY() == env.getYMin());// && point3d.z == + // interval.vmax); + + Envelope1D interval; + interval = envelope.queryInterval(VertexDescription.Semantics.M, 0); + double m = polygon.getAttributeAsDbl(VertexDescription.Semantics.M, 0, 0); + assertTrue(m == interval.vmin); + m = polygon.getAttributeAsDbl(VertexDescription.Semantics.M, 1, 0); + assertTrue(m == interval.vmax); + m = polygon.getAttributeAsDbl(VertexDescription.Semantics.M, 2, 0); + assertTrue(m == interval.vmin); + m = polygon.getAttributeAsDbl(VertexDescription.Semantics.M, 3, 0); + assertTrue(m == interval.vmax); + + interval = envelope.queryInterval(VertexDescription.Semantics.ID, 0); + double id = polygon.getAttributeAsDbl(VertexDescription.Semantics.ID, 0, 0); + assertTrue(id == interval.vmin); + id = polygon.getAttributeAsDbl(VertexDescription.Semantics.ID, 1, 0); + assertTrue(id == interval.vmax); + id = polygon.getAttributeAsDbl(VertexDescription.Semantics.ID, 2, 0); + assertTrue(id == interval.vmin); + id = polygon.getAttributeAsDbl(VertexDescription.Semantics.ID, 3, 0); + assertTrue(id == interval.vmax); + } + + @Test + public static void testImportExportWkbGeometryCollection() { + OperatorImportFromWkb importerWKB = (OperatorImportFromWkb) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.ImportFromWkb); + + int offset = 0; + ByteBuffer wkbBuffer = ByteBuffer.allocate(600).order(ByteOrder.nativeOrder()); + wkbBuffer.put(offset, (byte) WkbByteOrder.wkbNDR); + offset += 1; // byte order + wkbBuffer.putInt(offset, WkbGeometryType.wkbGeometryCollection); + offset += 4; // type + wkbBuffer.putInt(offset, 3); // 3 geometries + offset += 4; + wkbBuffer.put(offset, (byte) WkbByteOrder.wkbNDR); + offset += 1; + wkbBuffer.putInt(offset, WkbGeometryType.wkbPoint); + offset += 4; + wkbBuffer.putDouble(offset, 0); + offset += 8; + wkbBuffer.putDouble(offset, 0); + offset += 8; + wkbBuffer.put(offset, (byte) WkbByteOrder.wkbNDR); + offset += 1; // byte order + wkbBuffer.putInt(offset, WkbGeometryType.wkbGeometryCollection); + offset += 4; // type + wkbBuffer.putInt(offset, 7); // 7 empty geometries + offset += 4; + wkbBuffer.put(offset, (byte) WkbByteOrder.wkbNDR); + offset += 1; + wkbBuffer.putInt(offset, WkbGeometryType.wkbLineString); + offset += 4; + wkbBuffer.putInt(offset, 0); // 0 points, for empty linestring + offset += 4; + wkbBuffer.put(offset, (byte) WkbByteOrder.wkbNDR); + offset += 1; + wkbBuffer.putInt(offset, WkbGeometryType.wkbPolygon); + offset += 4; + wkbBuffer.putInt(offset, 0); // 0 points, for empty polygon + offset += 4; + wkbBuffer.put(offset, (byte) WkbByteOrder.wkbNDR); + offset += 1; + wkbBuffer.putInt(offset, WkbGeometryType.wkbMultiPolygon); + offset += 4; + wkbBuffer.putInt(offset, 0); // 0 points, for empty multipolygon + offset += 4; + wkbBuffer.put(offset, (byte) WkbByteOrder.wkbNDR); + offset += 1; + wkbBuffer.putInt(offset, WkbGeometryType.wkbMultiLineString); + offset += 4; + wkbBuffer.putInt(offset, 0); // 0 points, for empty multilinestring + offset += 4; + wkbBuffer.put(offset, (byte) WkbByteOrder.wkbNDR); + offset += 1; + wkbBuffer.putInt(offset, WkbGeometryType.wkbGeometryCollection); + offset += 4; + wkbBuffer.putInt(offset, 0); // 0 geometries, for empty + // geometrycollection + offset += 4; + wkbBuffer.put(offset, (byte) WkbByteOrder.wkbNDR); + offset += 1; + wkbBuffer.putInt(offset, WkbGeometryType.wkbMultiPoint); + offset += 4; + wkbBuffer.putInt(offset, 0); // 0 points, for empty multipoint + offset += 4; + wkbBuffer.put(offset, (byte) WkbByteOrder.wkbNDR); + offset += 1; + wkbBuffer.putInt(offset, WkbGeometryType.wkbPoint); + offset += 4; + wkbBuffer.putDouble(offset, 66); + offset += 8; + wkbBuffer.putDouble(offset, 88); + offset += 8; + wkbBuffer.put(offset, (byte) WkbByteOrder.wkbNDR); + offset += 1; + wkbBuffer.putInt(offset, WkbGeometryType.wkbPoint); + offset += 4; + wkbBuffer.putDouble(offset, 13); + offset += 8; + wkbBuffer.putDouble(offset, 17); + offset += 8; + + // "GeometryCollection( Point (0 0), GeometryCollection( LineString empty, Polygon empty, MultiPolygon empty, MultiLineString empty, MultiPoint empty ), Point (13 17) )"; + OGCStructure structure = importerWKB.executeOGC(0, wkbBuffer, null).m_structures.get(0); + + assertTrue(structure.m_type == 7); + assertTrue(structure.m_structures.get(0).m_type == 1); + assertTrue(structure.m_structures.get(1).m_type == 7); + assertTrue(structure.m_structures.get(2).m_type == 1); + + assertTrue(structure.m_structures.get(1).m_structures.get(0).m_type == 2); + assertTrue(structure.m_structures.get(1).m_structures.get(1).m_type == 3); + assertTrue(structure.m_structures.get(1).m_structures.get(2).m_type == 6); + assertTrue(structure.m_structures.get(1).m_structures.get(3).m_type == 5); + assertTrue(structure.m_structures.get(1).m_structures.get(4).m_type == 7); + assertTrue(structure.m_structures.get(1).m_structures.get(5).m_type == 4); + assertTrue(structure.m_structures.get(1).m_structures.get(6).m_type == 1); + + Point p = (Point) structure.m_structures.get(1).m_structures.get(6).m_geometry; + assertTrue(p.getX() == 66); + assertTrue(p.getY() == 88); + + p = (Point) structure.m_structures.get(2).m_geometry; + assertTrue(p.getX() == 13); + assertTrue(p.getY() == 17); + } + + @Test + public static void testImportExportWKBPolygon() { + OperatorExportToWkb exporterWKB = (OperatorExportToWkb) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.ExportToWkb); + OperatorExportToWkt exporterWKT = (OperatorExportToWkt) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.ExportToWkt); + OperatorImportFromWkb importerWKB = (OperatorImportFromWkb) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.ImportFromWkb); + + // Test Import Polygon with bad rings + int offset = 0; + ByteBuffer wkbBuffer = ByteBuffer.allocate(500).order(ByteOrder.nativeOrder()); + wkbBuffer.put(offset, (byte) WkbByteOrder.wkbNDR); + offset += 1; // byte order + wkbBuffer.putInt(offset, WkbGeometryType.wkbPolygon); + offset += 4; // type + wkbBuffer.putInt(offset, 8); + offset += 4; // num rings + wkbBuffer.putInt(offset, 4); + offset += 4; // num points + wkbBuffer.putDouble(offset, 0.0); + offset += 8; // x + wkbBuffer.putDouble(offset, 0.0); + offset += 8; // y + wkbBuffer.putDouble(offset, 0.0); + offset += 8; // x + wkbBuffer.putDouble(offset, 10.0); + offset += 8; // y + wkbBuffer.putDouble(offset, 10.0); + offset += 8; // x + wkbBuffer.putDouble(offset, 10.0); + offset += 8; // y + wkbBuffer.putDouble(offset, 0.0); + offset += 8; // x + wkbBuffer.putDouble(offset, 0.0); + offset += 8; // y + wkbBuffer.putInt(offset, 1); + offset += 4; // num points + wkbBuffer.putDouble(offset, 36.0); + offset += 8; // x + wkbBuffer.putDouble(offset, 17.0); + offset += 8; // y + wkbBuffer.putInt(offset, 2); + offset += 4; // num points + wkbBuffer.putDouble(offset, 19.0); + offset += 8; // x + wkbBuffer.putDouble(offset, 19.0); + offset += 8; // y + wkbBuffer.putDouble(offset, -19.0); + offset += 8; // x + wkbBuffer.putDouble(offset, -19.0); + offset += 8; // y + wkbBuffer.putInt(offset, 4); + offset += 4; // num points + wkbBuffer.putDouble(offset, 23.0); + offset += 8; // x + wkbBuffer.putDouble(offset, 88); + offset += 8; // y + wkbBuffer.putDouble(offset, 13.0); + offset += 8; // x + wkbBuffer.putDouble(offset, 43.0); + offset += 8; // y + wkbBuffer.putDouble(offset, 59.0); + offset += 8; // x + wkbBuffer.putDouble(offset, 79.0); + offset += 8; // y + wkbBuffer.putDouble(offset, 83.0); + offset += 8; // x + wkbBuffer.putDouble(offset, 87.0); + offset += 8; // y + wkbBuffer.putInt(offset, 3); + offset += 4; // num points + wkbBuffer.putDouble(offset, 23.0); + offset += 8; // x + wkbBuffer.putDouble(offset, 88); + offset += 8; // y + wkbBuffer.putDouble(offset, 88); + offset += 8; // x + wkbBuffer.putDouble(offset, 43.0); + offset += 8; // y + wkbBuffer.putDouble(offset, 67.0); + offset += 8; // x + wkbBuffer.putDouble(offset, 79.0); + offset += 8; // y + wkbBuffer.putInt(offset, 0); + offset += 4; // num points + wkbBuffer.putInt(offset, 3); + offset += 4; // num points + wkbBuffer.putDouble(offset, 23.0); + offset += 8; // x + wkbBuffer.putDouble(offset, 88); + offset += 8; // y + wkbBuffer.putDouble(offset, 88); + offset += 8; // x + wkbBuffer.putDouble(offset, 43.0); + offset += 8; // y + wkbBuffer.putDouble(offset, 67.0); + offset += 8; // x + wkbBuffer.putDouble(offset, 88); + offset += 8; // y + wkbBuffer.putInt(offset, 2); + offset += 4; // num points + wkbBuffer.putDouble(offset, 23.0); + offset += 8; // x + wkbBuffer.putDouble(offset, 67.0); + offset += 8; // y + wkbBuffer.putDouble(offset, 43.0); + offset += 8; // x + wkbBuffer.putDouble(offset, 67.0); + offset += 8; // y + + Geometry p = importerWKB.execute(0, Geometry.Type.Polygon, wkbBuffer, null); + int pc = ((Polygon) p).getPathCount(); + String wktString = exporterWKT.execute(0, p, null); + assertTrue(wktString.equals("MULTIPOLYGON (((0 0, 10 10, 0 10, 0 0), (36 17, 36 17, 36 17), (19 19, -19 -19, 19 19), (23 88, 83 87, 59 79, 13 43, 23 88), (23 88, 67 79, 88 43, 23 88), (23 88, 67 88, 88 43, 23 88), (23 67, 43 67, 23 67)))")); + + wktString = exporterWKT.execute(WktExportFlags.wktExportPolygon, p, null); + assertTrue(wktString.equals("POLYGON ((0 0, 10 10, 0 10, 0 0), (36 17, 36 17, 36 17), (19 19, -19 -19, 19 19), (23 88, 83 87, 59 79, 13 43, 23 88), (23 88, 67 79, 88 43, 23 88), (23 88, 67 88, 88 43, 23 88), (23 67, 43 67, 23 67))")); + + Polygon polygon = makePolygon(); + + // Test Import Polygon from Polygon8 + ByteBuffer polygonWKBBuffer = exporterWKB.execute(0, polygon, null); + int wkbType = polygonWKBBuffer.getInt(1); + assertTrue(wkbType == WkbGeometryType.wkbMultiPolygonZM); + Geometry polygonWKBGeometry = importerWKB.execute(0, Geometry.Type.Polygon, polygonWKBBuffer, null); + TestCommonMethods.compareGeometryContent((MultiVertexGeometry) polygonWKBGeometry, polygon); + + // Test WKB_export_multi_polygon on nonempty single part polygon + Polygon polygon2 = makePolygon2(); + assertTrue(polygon2.getPathCount() == 1); + polygonWKBBuffer = exporterWKB.execute(WkbExportFlags.wkbExportMultiPolygon, polygon2, null); + polygonWKBGeometry = importerWKB.execute(0, Geometry.Type.Polygon, polygonWKBBuffer, null); + TestCommonMethods.compareGeometryContent((MultiVertexGeometry) polygonWKBGeometry, polygon2); + wkbType = polygonWKBBuffer.getInt(1); + assertTrue(wkbType == WkbGeometryType.wkbMultiPolygonZM); + + // Test WKB_export_polygon on nonempty single part polygon + assertTrue(polygon2.getPathCount() == 1); + polygonWKBBuffer = exporterWKB.execute(WkbExportFlags.wkbExportPolygon, polygon2, null); + polygonWKBGeometry = importerWKB.execute(0, Geometry.Type.Polygon, polygonWKBBuffer, null); + TestCommonMethods.compareGeometryContent((MultiVertexGeometry) polygonWKBGeometry, polygon2); + wkbType = polygonWKBBuffer.getInt(1); + assertTrue(wkbType == WkbGeometryType.wkbPolygonZM); + + // Test WKB_export_polygon on empty polygon + Polygon polygon3 = new Polygon(); + polygonWKBBuffer = exporterWKB.execute(WkbExportFlags.wkbExportPolygon, polygon3, null); + polygonWKBGeometry = importerWKB.execute(0, Geometry.Type.Polygon, polygonWKBBuffer, null); + assertTrue(polygonWKBGeometry.isEmpty() == true); + wkbType = polygonWKBBuffer.getInt(1); + assertTrue(wkbType == WkbGeometryType.wkbPolygon); + + // Test WKB_export_defaults on empty polygon + polygonWKBBuffer = exporterWKB.execute(0, polygon3, null); + polygonWKBGeometry = importerWKB.execute(0, Geometry.Type.Polygon, polygonWKBBuffer, null); + assertTrue(polygonWKBGeometry.isEmpty() == true); + wkbType = polygonWKBBuffer.getInt(1); + assertTrue(wkbType == WkbGeometryType.wkbMultiPolygon); + } + + @Test + public static void testImportExportWKBPolyline() { + OperatorExportToWkb exporterWKB = (OperatorExportToWkb) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.ExportToWkb); + OperatorExportToWkt exporterWKT = (OperatorExportToWkt) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.ExportToWkt); + OperatorImportFromWkb importerWKB = (OperatorImportFromWkb) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.ImportFromWkb); + + // Test Import Polyline with bad paths (i.e. paths with one point or + // zero points) + int offset = 0; + ByteBuffer wkbBuffer = ByteBuffer.allocate(500).order(ByteOrder.nativeOrder()); + wkbBuffer.put(offset, (byte) WkbByteOrder.wkbNDR); + offset += 1; // byte order + wkbBuffer.putInt(offset, WkbGeometryType.wkbMultiLineString); + offset += 4; // type + wkbBuffer.putInt(offset, 4); + offset += 4; // num paths + wkbBuffer.put(offset, (byte) WkbByteOrder.wkbNDR); + offset += 1; // byte order + wkbBuffer.putInt(offset, WkbGeometryType.wkbLineString); + offset += 4; // type + wkbBuffer.putInt(offset, 1); + offset += 4; // num points + wkbBuffer.putDouble(offset, 36.0); + offset += 8; // x + wkbBuffer.putDouble(offset, 17.0); + offset += 8; // y + wkbBuffer.put(offset, (byte) WkbByteOrder.wkbNDR); + offset += 1; // byte order + wkbBuffer.putInt(offset, WkbGeometryType.wkbLineString); + offset += 4; // type + wkbBuffer.putInt(offset, 0); + offset += 4; // num points + wkbBuffer.put(offset, (byte) WkbByteOrder.wkbNDR); + offset += 1; // byte order + wkbBuffer.putInt(offset, WkbGeometryType.wkbLineString); + offset += 4; // type + wkbBuffer.putInt(offset, 1); + offset += 4; // num points + wkbBuffer.putDouble(offset, 19.0); + offset += 8; // x + wkbBuffer.putDouble(offset, 19.0); + offset += 8; // y + wkbBuffer.put(offset, (byte) WkbByteOrder.wkbNDR); + offset += 1; // byte order + wkbBuffer.putInt(offset, WkbGeometryType.wkbLineString); + offset += 4; // type + wkbBuffer.putInt(offset, 3); + offset += 4; // num points + wkbBuffer.putDouble(offset, 88); + offset += 8; // x + wkbBuffer.putDouble(offset, 29.0); + offset += 8; // y + wkbBuffer.putDouble(offset, 13.0); + offset += 8; // x + wkbBuffer.putDouble(offset, 43.0); + offset += 8; // y + wkbBuffer.putDouble(offset, 59.0); + offset += 8; // x + wkbBuffer.putDouble(offset, 88); + offset += 8; // y + + Polyline p = (Polyline) (importerWKB.execute(0, Geometry.Type.Polyline, wkbBuffer, null)); + int pc = p.getPointCount(); + int pac = p.getPathCount(); + assertTrue(p.getPointCount() == 7); + assertTrue(p.getPathCount() == 3); + + String wktString = exporterWKT.execute(0, p, null); + assertTrue(wktString.equals("MULTILINESTRING ((36 17, 36 17), (19 19, 19 19), (88 29, 13 43, 59 88))")); + + Polyline polyline = makePolyline(); + polyline.dropAttribute(VertexDescription.Semantics.ID); + + // Test Import Polyline from Polyline + ByteBuffer polylineWKBBuffer = exporterWKB.execute(0, polyline, null); + int wkbType = polylineWKBBuffer.getInt(1); + assertTrue(wkbType == WkbGeometryType.wkbMultiLineStringZM); + Geometry polylineWKBGeometry = importerWKB.execute(0, Geometry.Type.Polyline, polylineWKBBuffer, null); + TestCommonMethods.compareGeometryContent((MultiVertexGeometry) polylineWKBGeometry, polyline); + + // Test wkbExportMultiPolyline on nonempty single part polyline + Polyline polyline2 = makePolyline2(); + assertTrue(polyline2.getPathCount() == 1); + polylineWKBBuffer = exporterWKB.execute(WkbExportFlags.wkbExportMultiLineString, polyline2, null); + polylineWKBGeometry = importerWKB.execute(0, Geometry.Type.Polyline, polylineWKBBuffer, null); + TestCommonMethods.compareGeometryContent((MultiVertexGeometry) polylineWKBGeometry, polyline2); + wkbType = polylineWKBBuffer.getInt(1); + assertTrue(wkbType == WkbGeometryType.wkbMultiLineStringZM); + + // Test wkbExportPolyline on nonempty single part polyline + assertTrue(polyline2.getPathCount() == 1); + polylineWKBBuffer = exporterWKB.execute(WkbExportFlags.wkbExportLineString, polyline2, null); + polylineWKBGeometry = importerWKB.execute(0, Geometry.Type.Polyline, polylineWKBBuffer, null); + TestCommonMethods.compareGeometryContent((MultiVertexGeometry) polylineWKBGeometry, polyline2); + wkbType = polylineWKBBuffer.getInt(1); + assertTrue(wkbType == WkbGeometryType.wkbLineStringZM); + + // Test wkbExportPolyline on empty polyline + Polyline polyline3 = new Polyline(); + polylineWKBBuffer = exporterWKB.execute(WkbExportFlags.wkbExportLineString, polyline3, null); + polylineWKBGeometry = importerWKB.execute(0, Geometry.Type.Polyline, polylineWKBBuffer, null); + assertTrue(polylineWKBGeometry.isEmpty() == true); + wkbType = polylineWKBBuffer.getInt(1); + assertTrue(wkbType == WkbGeometryType.wkbLineString); + + // Test WKB_export_defaults on empty polyline + polylineWKBBuffer = exporterWKB.execute(0, polyline3, null); + polylineWKBGeometry = importerWKB.execute(0, Geometry.Type.Polyline, polylineWKBBuffer, null); + assertTrue(polylineWKBGeometry.isEmpty() == true); + wkbType = polylineWKBBuffer.getInt(1); + assertTrue(wkbType == WkbGeometryType.wkbMultiLineString); + } + + @Test + public static void testImportExportWKBMultiPoint() { + OperatorExportToWkb exporterWKB = (OperatorExportToWkb) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.ExportToWkb); + OperatorImportFromWkb importerWKB = (OperatorImportFromWkb) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.ImportFromWkb); + + MultiPoint multipoint = makeMultiPoint(); + multipoint.dropAttribute(VertexDescription.Semantics.ID); + + // Test Import Multi_point from Multi_point + ByteBuffer multipointWKBBuffer = exporterWKB.execute(0, multipoint, null); + int wkbType = multipointWKBBuffer.getInt(1); + assertTrue(wkbType == WkbGeometryType.wkbMultiPointZ); + MultiPoint multipointWKBGeometry = (MultiPoint) (importerWKB.execute(0, Geometry.Type.MultiPoint, multipointWKBBuffer, null)); + TestCommonMethods.compareGeometryContent((MultiVertexGeometry) multipointWKBGeometry, multipoint); + + // Test WKB_export_point on nonempty single point Multi_point + MultiPoint multipoint2 = makeMultiPoint2(); + assertTrue(multipoint2.getPointCount() == 1); + ByteBuffer pointWKBBuffer = exporterWKB.execute(WkbExportFlags.wkbExportPoint, multipoint2, null); + Point pointWKBGeometry = (Point) (importerWKB.execute(0, Geometry.Type.Point, pointWKBBuffer, null)); + Point3D point3d, mpoint3d; + point3d = pointWKBGeometry.getXYZ(); + mpoint3d = multipoint2.getXYZ(0); + assertTrue(point3d.x == mpoint3d.x && point3d.y == mpoint3d.y && point3d.z == mpoint3d.z); + wkbType = pointWKBBuffer.getInt(1); + assertTrue(wkbType == WkbGeometryType.wkbPointZ); + + // Test WKB_export_point on empty Multi_point + MultiPoint multipoint3 = new MultiPoint(); + pointWKBBuffer = exporterWKB.execute(WkbExportFlags.wkbExportPoint, multipoint3, null); + pointWKBGeometry = (Point) (importerWKB.execute(0, Geometry.Type.Point, pointWKBBuffer, null)); + assertTrue(pointWKBGeometry.isEmpty() == true); + wkbType = pointWKBBuffer.getInt(1); + assertTrue(wkbType == WkbGeometryType.wkbPoint); + + // Test WKB_export_defaults on empty Multi_point + multipointWKBBuffer = exporterWKB.execute(0, multipoint3, null); + multipointWKBGeometry = (MultiPoint) (importerWKB.execute(0, Geometry.Type.MultiPoint, multipointWKBBuffer, null)); + assertTrue(multipointWKBGeometry.isEmpty() == true); + wkbType = multipointWKBBuffer.getInt(1); + assertTrue(wkbType == WkbGeometryType.wkbMultiPoint); + } + + @Test + public static void testImportExportWKBPoint() { + OperatorExportToWkb exporterWKB = (OperatorExportToWkb) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.ExportToWkb); + OperatorImportFromWkb importerWKB = (OperatorImportFromWkb) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.ImportFromWkb); + + // Point + Point point = makePoint(); + + // Test Import Point from Point + ByteBuffer pointWKBBuffer = exporterWKB.execute(0, point, null); + int wkbType = pointWKBBuffer.getInt(1); + assertTrue(wkbType == WkbGeometryType.wkbPointZM); + Point pointWKBGeometry = (Point) (importerWKB.execute(0, Geometry.Type.Point, pointWKBBuffer, null)); + + double x_1 = point.getX(); + double x2 = pointWKBGeometry.getX(); + assertTrue(x_1 == x2); + + double y1 = point.getY(); + double y2 = pointWKBGeometry.getY(); + assertTrue(y1 == y2); + + double z_1 = point.getZ(); + double z_2 = pointWKBGeometry.getZ(); + assertTrue(z_1 == z_2); + + double m1 = point.getM(); + double m2 = pointWKBGeometry.getM(); + assertTrue(m1 == m2); + + // Test WKB_export_defaults on empty point + Point point2 = new Point(); + pointWKBBuffer = exporterWKB.execute(0, point2, null); + pointWKBGeometry = (Point) (importerWKB.execute(0, Geometry.Type.Point, pointWKBBuffer, null)); + assertTrue(pointWKBGeometry.isEmpty() == true); + wkbType = pointWKBBuffer.getInt(1); + assertTrue(wkbType == WkbGeometryType.wkbPoint); + + // Test WKB_export_point on empty point + pointWKBBuffer = exporterWKB.execute(WkbExportFlags.wkbExportPoint, point2, null); + pointWKBGeometry = (Point) (importerWKB.execute(0, Geometry.Type.Point, pointWKBBuffer, null)); + assertTrue(pointWKBGeometry.isEmpty() == true); + wkbType = pointWKBBuffer.getInt(1); + assertTrue(wkbType == WkbGeometryType.wkbPoint); + + // Test WKB_export_multi_point on empty point + MultiPoint multipoint = new MultiPoint(); + ByteBuffer multipointWKBBuffer = exporterWKB.execute(WkbExportFlags.wkbExportMultiPoint, multipoint, null); + MultiPoint multipointWKBGeometry = (MultiPoint) (importerWKB.execute(0, Geometry.Type.MultiPoint, multipointWKBBuffer, null)); + assertTrue(multipointWKBGeometry.isEmpty() == true); + wkbType = multipointWKBBuffer.getInt(1); + assertTrue(wkbType == WkbGeometryType.wkbMultiPoint); + + // Test WKB_export_point on nonempty single point Multi_point + MultiPoint multipoint2 = makeMultiPoint2(); + assertTrue(multipoint2.getPointCount() == 1); + pointWKBBuffer = exporterWKB.execute(WkbExportFlags.wkbExportPoint, multipoint2, null); + pointWKBGeometry = (Point) (importerWKB.execute(0, Geometry.Type.Point, pointWKBBuffer, null)); + Point3D point3d, mpoint3d; + point3d = pointWKBGeometry.getXYZ(); + mpoint3d = multipoint2.getXYZ(0); + assertTrue(point3d.x == mpoint3d.x && point3d.y == mpoint3d.y && point3d.z == mpoint3d.z); + wkbType = pointWKBBuffer.getInt(1); + assertTrue(wkbType == WkbGeometryType.wkbPointZ); + } + + @Test + public static void testImportExportWKBEnvelope() { + OperatorExportToWkb exporterWKB = (OperatorExportToWkb) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.ExportToWkb); + OperatorImportFromWkb importerWKB = (OperatorImportFromWkb) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.ImportFromWkb); + + // Test Export Envelope to Polygon (WKB_export_defaults) + Envelope envelope = makeEnvelope(); + envelope.dropAttribute(VertexDescription.Semantics.ID); + + ByteBuffer polygonWKBBuffer = exporterWKB.execute(0, envelope, null); + int wkbType = polygonWKBBuffer.getInt(1); + assertTrue(wkbType == WkbGeometryType.wkbPolygonZM); + Polygon polygon = (Polygon) (importerWKB.execute(0, Geometry.Type.Polygon, polygonWKBBuffer, null)); + int point_count = polygon.getPointCount(); + assertTrue(point_count == 4); + + Envelope2D env = new Envelope2D(); + Envelope1D interval; + + envelope.queryEnvelope2D(env); + interval = envelope.queryInterval(VertexDescription.Semantics.Z, 0); + Point3D point3d; + point3d = polygon.getXYZ(0); + assertTrue(point3d.x == env.xmin && point3d.y == env.ymin && point3d.z == interval.vmin); + point3d = polygon.getXYZ(1); + assertTrue(point3d.x == env.xmin && point3d.y == env.ymax && point3d.z == interval.vmax); + point3d = polygon.getXYZ(2); + assertTrue(point3d.x == env.xmax && point3d.y == env.ymax && point3d.z == interval.vmin); + point3d = polygon.getXYZ(3); + assertTrue(point3d.x == env.xmax && point3d.y == env.ymin && point3d.z == interval.vmax); + + interval = envelope.queryInterval(VertexDescription.Semantics.M, 0); + double m = polygon.getAttributeAsDbl(VertexDescription.Semantics.M, 0, 0); + assertTrue(m == interval.vmin); + m = polygon.getAttributeAsDbl(VertexDescription.Semantics.M, 1, 0); + assertTrue(m == interval.vmax); + m = polygon.getAttributeAsDbl(VertexDescription.Semantics.M, 2, 0); + assertTrue(m == interval.vmin); + m = polygon.getAttributeAsDbl(VertexDescription.Semantics.M, 3, 0); + assertTrue(m == interval.vmax); + + // Test WKB_export_multi_polygon on nonempty Envelope + polygonWKBBuffer = exporterWKB.execute(WkbExportFlags.wkbExportMultiPolygon, envelope, null); + wkbType = polygonWKBBuffer.getInt(1); + assertTrue(wkbType == WkbGeometryType.wkbMultiPolygonZM); + polygon = (Polygon) (importerWKB.execute(0, Geometry.Type.Polygon, polygonWKBBuffer, null)); + point_count = polygon.getPointCount(); + assertTrue(point_count == 4); + + envelope.queryEnvelope2D(env); + interval = envelope.queryInterval(VertexDescription.Semantics.Z, 0); + point3d = polygon.getXYZ(0); + assertTrue(point3d.x == env.xmin && point3d.y == env.ymin && point3d.z == interval.vmin); + point3d = polygon.getXYZ(1); + assertTrue(point3d.x == env.xmin && point3d.y == env.ymax && point3d.z == interval.vmax); + point3d = polygon.getXYZ(2); + assertTrue(point3d.x == env.xmax && point3d.y == env.ymax && point3d.z == interval.vmin); + point3d = polygon.getXYZ(3); + assertTrue(point3d.x == env.xmax && point3d.y == env.ymin && point3d.z == interval.vmax); + + interval = envelope.queryInterval(VertexDescription.Semantics.M, 0); + m = polygon.getAttributeAsDbl(VertexDescription.Semantics.M, 0, 0); + assertTrue(m == interval.vmin); + m = polygon.getAttributeAsDbl(VertexDescription.Semantics.M, 1, 0); + assertTrue(m == interval.vmax); + m = polygon.getAttributeAsDbl(VertexDescription.Semantics.M, 2, 0); + assertTrue(m == interval.vmin); + m = polygon.getAttributeAsDbl(VertexDescription.Semantics.M, 3, 0); + assertTrue(m == interval.vmax); + + // Test WKB_export_defaults on empty Envelope + Envelope envelope2 = new Envelope(); + polygonWKBBuffer = exporterWKB.execute(0, envelope2, null); + wkbType = polygonWKBBuffer.getInt(1); + assertTrue(wkbType == WkbGeometryType.wkbPolygon); + polygon = (Polygon) (importerWKB.execute(0, Geometry.Type.Polygon, polygonWKBBuffer, null)); + assertTrue(polygon.isEmpty()); + + // Test WKB_export_polygon on empty Envelope + polygonWKBBuffer = exporterWKB.execute(WkbExportFlags.wkbExportPolygon, envelope2, null); + wkbType = polygonWKBBuffer.getInt(1); + assertTrue(wkbType == WkbGeometryType.wkbPolygon); + polygon = (Polygon) (importerWKB.execute(0, Geometry.Type.Polygon, polygonWKBBuffer, null)); + assertTrue(polygon.isEmpty()); + } + + @Test + public static void testImportExportWktGeometryCollection() { + OperatorImportFromWkt importerWKT = (OperatorImportFromWkt) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.ImportFromWkt); + OperatorExportToWkt exporterWKT = (OperatorExportToWkt) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.ExportToWkt); + + String wktString; + Envelope2D envelope = new Envelope2D(); + WktParser wktParser = new WktParser(); + + wktString = "GeometryCollection( Point (0 0), GeometryCollection( Point (0 0) , Point (1 1) , Point (2 2), LineString empty ), Point (1 1), Point (2 2) )"; + OGCStructure structure = importerWKT.executeOGC(0, wktString, null).m_structures.get(0); + + assertTrue(structure.m_type == 7); + assertTrue(structure.m_structures.get(0).m_type == 1); + assertTrue(structure.m_structures.get(0).m_type == 1); + assertTrue(structure.m_structures.get(1).m_type == 7); + assertTrue(structure.m_structures.get(2).m_type == 1); + assertTrue(structure.m_structures.get(3).m_type == 1); + + assertTrue(structure.m_structures.get(1).m_structures.get(0).m_type == 1); + assertTrue(structure.m_structures.get(1).m_structures.get(1).m_type == 1); + assertTrue(structure.m_structures.get(1).m_structures.get(2).m_type == 1); + assertTrue(structure.m_structures.get(1).m_structures.get(3).m_type == 2); + } + + @Test + public static void testImportExportWktMultiPolygon() { + OperatorImportFromWkt importerWKT = (OperatorImportFromWkt) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.ImportFromWkt); + OperatorExportToWkt exporterWKT = (OperatorExportToWkt) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.ExportToWkt); + + Polygon polygon; + String wktString; + Envelope2D envelope = new Envelope2D(); + WktParser wktParser = new WktParser(); + + // Test Import from MultiPolygon + wktString = "Multipolygon M empty"; + polygon = (Polygon) importerWKT.execute(0, Geometry.Type.Polygon, wktString, null); + assertTrue(polygon != null); + assertTrue(polygon.isEmpty()); + assertTrue(polygon.hasAttribute(VertexDescription.Semantics.M)); + + polygon = (Polygon) GeometryEngine.geometryFromWkt(wktString, 0, Geometry.Type.Unknown); + assertTrue(polygon != null); + assertTrue(polygon.isEmpty()); + assertTrue(polygon.hasAttribute(VertexDescription.Semantics.M)); + + wktString = exporterWKT.execute(0, polygon, null); + assertTrue(wktString.equals("MULTIPOLYGON M EMPTY")); + + wktString = GeometryEngine.geometryToWkt(polygon, 0); + assertTrue(wktString.equals("MULTIPOLYGON M EMPTY")); + + wktString = "Multipolygon Z (empty, (empty, (10 10 5, 20 10 5, 20 20 5, 10 20 5, 10 10 5), (12 12 3), empty, (10 10 1, 12 12 1)), empty, ((90 90 88, 60 90 7, 60 60 7), empty, (70 70 7, 80 80 7, 70 80 7, 70 70 7)), empty)"; + polygon = (Polygon) (importerWKT.execute(0, Geometry.Type.Polygon, wktString, null)); + assertTrue(polygon != null); + polygon.queryEnvelope2D(envelope); + assertTrue(envelope.xmin == 10 && envelope.xmax == 90 && envelope.ymin == 10 && envelope.ymax == 90); + assertTrue(polygon.getPointCount() == 14); + assertTrue(polygon.getPathCount() == 5); + // assertTrue(polygon.calculate_area_2D() > 0.0); + assertTrue(polygon.hasAttribute(VertexDescription.Semantics.Z)); + + double z = polygon.getAttributeAsDbl(VertexDescription.Semantics.Z, 0, 0); + assertTrue(z == 5); + + // Test Export to WKT MultiPolygon + wktString = exporterWKT.execute(0, polygon, null); + assertTrue(wktString.equals("MULTIPOLYGON Z (((10 10 5, 20 10 5, 20 20 5, 10 20 5, 10 10 5), (12 12 3, 12 12 3, 12 12 3), (10 10 1, 12 12 1, 10 10 1)), ((90 90 88, 60 90 7, 60 60 7, 90 90 88), (70 70 7, 70 80 7, 80 80 7, 70 70 7)))")); + wktParser.resetParser(wktString); + while (wktParser.nextToken() != WktParser.WktToken.not_available) { + } + + // Test import Polygon + wktString = "POLYGON z (EMPTY, EMPTY, (10 10 5, 10 20 5, 20 20 5, 20 10 5), (12 12 3), EMPTY, (10 10 1, 12 12 1), EMPTY, (60 60 7, 60 90 7, 90 90 7, 60 60 7), EMPTY, (70 70 7, 70 80 7, 80 80 7), EMPTY)"; + polygon = (Polygon) (importerWKT.execute(0, Geometry.Type.Polygon, wktString, null)); + assertTrue(polygon != null); + assertTrue(polygon.getPointCount() == 14); + assertTrue(polygon.getPathCount() == 5); + assertTrue(polygon.hasAttribute(VertexDescription.Semantics.Z)); + + // Test Export to WKT Polygon + wktString = exporterWKT.execute(WktExportFlags.wktExportPolygon, polygon, null); + assertTrue(wktString.equals("POLYGON Z ((10 10 5, 20 10 5, 20 20 5, 10 20 5, 10 10 5), (12 12 3, 12 12 3, 12 12 3), (10 10 1, 12 12 1, 10 10 1), (60 60 7, 60 90 7, 90 90 7, 60 60 7), (70 70 7, 70 80 7, 80 80 7, 70 70 7))")); + wktParser.resetParser(wktString); + while (wktParser.nextToken() != WktParser.WktToken.not_available) { + } + + Envelope env = new Envelope(); + env.addAttribute(VertexDescription.Semantics.Z); + polygon.queryEnvelope(env); + + wktString = exporterWKT.execute(0, env, null); + assertTrue(wktString.equals("POLYGON Z ((10 10 1, 90 10 7, 90 90 1, 10 90 7, 10 10 1))")); + wktParser.resetParser(wktString); + while (wktParser.nextToken() != WktParser.WktToken.not_available) { + } + + wktString = exporterWKT.execute(WktExportFlags.wktExportMultiPolygon, env, null); + assertTrue(wktString.equals("MULTIPOLYGON Z (((10 10 1, 90 10 7, 90 90 1, 10 90 7, 10 10 1)))")); + wktParser.resetParser(wktString); + while (wktParser.nextToken() != WktParser.WktToken.not_available) { + } + + env.setEmpty(); + + wktString = exporterWKT.execute(0, env, null); + assertTrue(wktString.equals("POLYGON Z EMPTY")); + wktParser.resetParser(wktString); + while (wktParser.nextToken() != WktParser.WktToken.not_available) { + } + + wktString = exporterWKT.execute(WktExportFlags.wktExportMultiPolygon, env, null); + assertTrue(wktString.equals("MULTIPOLYGON Z EMPTY")); + wktParser.resetParser(wktString); + while (wktParser.nextToken() != WktParser.WktToken.not_available) { + } + + wktString = "MULTIPOLYGON (((5 10, 8 10, 10 10, 10 0, 0 0, 0 10, 2 10, 5 10)))"; // ring + // is + // oriented + // clockwise + polygon = (Polygon) (importerWKT.execute(0, Geometry.Type.Polygon, wktString, null)); + assertTrue(polygon != null); + assertTrue(polygon.calculateArea2D() > 0); + + wktString = "MULTIPOLYGON Z (((90 10 7, 10 10 1, 10 90 7, 90 90 1, 90 10 7)))"; // ring + // is + // oriented + // clockwise + polygon = (Polygon) (importerWKT.execute(0, Geometry.Type.Polygon, wktString, null)); + assertTrue(polygon != null); + assertTrue(polygon.getPointCount() == 4); + assertTrue(polygon.getPathCount() == 1); + assertTrue(polygon.hasAttribute(VertexDescription.Semantics.Z)); + assertTrue(polygon.calculateArea2D() > 0); + + wktString = exporterWKT.execute(WktExportFlags.wktExportMultiPolygon, polygon, null); + assertTrue(wktString.equals("MULTIPOLYGON Z (((90 10 7, 90 90 1, 10 90 7, 10 10 1, 90 10 7)))")); + } + + @Test + public static void testImportExportWktPolygon() { + OperatorImportFromWkt importerWKT = (OperatorImportFromWkt) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.ImportFromWkt); + // OperatorExportToWkt exporterWKT = + // (OperatorExportToWkt)OperatorFactoryLocal.getInstance().getOperator(Operator.Type.ExportToWkt); + + Polygon polygon; + String wktString; + Envelope2D envelope = new Envelope2D(); + + // Test Import from Polygon + wktString = "Polygon ZM empty"; + polygon = (Polygon) (importerWKT.execute(0, Geometry.Type.Unknown, wktString, null)); + assertTrue(polygon != null); + assertTrue(polygon.isEmpty()); + assertTrue(polygon.hasAttribute(VertexDescription.Semantics.Z)); + assertTrue(polygon.hasAttribute(VertexDescription.Semantics.M)); + + wktString = "Polygon z (empty, (10 10 5, 20 10 5, 20 20 5, 10 20 5, 10 10 5), (12 12 3), empty, (10 10 1, 12 12 1))"; + polygon = (Polygon) (importerWKT.execute(0, Geometry.Type.Unknown, wktString, null)); + assertTrue(polygon != null); + polygon.queryEnvelope2D(envelope); + assertTrue(envelope.xmin == 10 && envelope.xmax == 20 && envelope.ymin == 10 && envelope.ymax == 20); + assertTrue(polygon.getPointCount() == 8); + assertTrue(polygon.getPathCount() == 3); + assertTrue(polygon.hasAttribute(VertexDescription.Semantics.Z)); + + wktString = "polygon ((35 10, 10 20, 15 40, 45 45, 35 10), (20 30, 35 35, 30 20, 20 30))"; + Polygon polygon2 = (Polygon) (importerWKT.execute(0, Geometry.Type.Unknown, wktString, null)); + assertTrue(polygon2 != null); + + // wktString = exporterWKT.execute(0, *polygon2, null); + } + + @Test + public static void testImportExportWktLineString() { + OperatorImportFromWkt importerWKT = (OperatorImportFromWkt) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.ImportFromWkt); + // OperatorExportToWkt exporterWKT = + // (OperatorExportToWkt)OperatorFactoryLocal.getInstance().getOperator(Operator.Type.ExportToWkt); + + Polyline polyline; + String wktString; + Envelope2D envelope = new Envelope2D(); + + // Test Import from LineString + wktString = "LineString ZM empty"; + polyline = (Polyline) (importerWKT.execute(0, Geometry.Type.Unknown, wktString, null)); + assertTrue(polyline != null); + assertTrue(polyline.isEmpty()); + assertTrue(polyline.hasAttribute(VertexDescription.Semantics.Z)); + assertTrue(polyline.hasAttribute(VertexDescription.Semantics.M)); + + wktString = "LineString m (10 10 5, 10 20 5, 20 20 5, 20 10 5)"; + polyline = (Polyline) (importerWKT.execute(0, Geometry.Type.Unknown, wktString, null)); + assertTrue(polyline != null); + polyline.queryEnvelope2D(envelope); + assertTrue(envelope.xmin == 10 && envelope.xmax == 20 && envelope.ymin == 10 && envelope.ymax == 20); + assertTrue(polyline.getPointCount() == 4); + assertTrue(polyline.getPathCount() == 1); + assertTrue(polyline.hasAttribute(VertexDescription.Semantics.M)); + } + + @Test + public static void testImportExportWktMultiLineString() { + OperatorImportFromWkt importerWKT = (OperatorImportFromWkt) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.ImportFromWkt); + OperatorExportToWkt exporterWKT = (OperatorExportToWkt) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.ExportToWkt); + + Polyline polyline; + String wktString; + Envelope2D envelope = new Envelope2D(); + WktParser wktParser = new WktParser(); + + // Test Import from MultiLineString + wktString = "MultiLineStringZMempty"; + polyline = (Polyline) (importerWKT.execute(0, Geometry.Type.Unknown, wktString, null)); + assertTrue(polyline != null); + assertTrue(polyline.isEmpty()); + assertTrue(polyline.hasAttribute(VertexDescription.Semantics.Z)); + assertTrue(polyline.hasAttribute(VertexDescription.Semantics.M)); + + wktString = "MultiLineStringm(empty, empty, (10 10 5, 10 20 5, 20 88 5, 20 10 5), (12 88 3), empty, (10 10 1, 12 12 1), empty, (88 60 7, 60 90 7, 90 90 7), empty, (70 70 7, 70 80 7, 80 80 7), empty)"; + polyline = (Polyline) (importerWKT.execute(0, Geometry.Type.Unknown, wktString, null)); + assertTrue(polyline != null); + polyline.queryEnvelope2D(envelope); + assertTrue(envelope.xmin == 10 && envelope.xmax == 90 && envelope.ymin == 10 && envelope.ymax == 90); + assertTrue(polyline.getPointCount() == 14); + assertTrue(polyline.getPathCount() == 5); + assertTrue(polyline.hasAttribute(VertexDescription.Semantics.M)); + + wktString = exporterWKT.execute(0, polyline, null); + assertTrue(wktString.equals("MULTILINESTRING M ((10 10 5, 10 20 5, 20 88 5, 20 10 5), (12 88 3, 12 88 3), (10 10 1, 12 12 1), (88 60 7, 60 90 7, 90 90 7), (70 70 7, 70 80 7, 80 80 7))")); + wktParser.resetParser(wktString); + while (wktParser.nextToken() != WktParser.WktToken.not_available) { + } + + // Test Import LineString + wktString = "Linestring Z(10 10 5, 10 20 5, 20 20 5, 20 10 5)"; + polyline = (Polyline) (importerWKT.execute(0, Geometry.Type.Unknown, wktString, null)); + assertTrue(polyline.getPointCount() == 4); + wktString = exporterWKT.execute(WktExportFlags.wktExportLineString, polyline, null); + assertTrue(wktString.equals("LINESTRING Z (10 10 5, 10 20 5, 20 20 5, 20 10 5)")); + wktParser.resetParser(wktString); + while (wktParser.nextToken() != WktParser.WktToken.not_available) { + } + + wktString = exporterWKT.execute(0, polyline, null); + assertTrue(wktString.equals("MULTILINESTRING Z ((10 10 5, 10 20 5, 20 20 5, 20 10 5))")); + wktParser.resetParser(wktString); + while (wktParser.nextToken() != WktParser.WktToken.not_available) { + } + } + + @Test + public static void testImportExportWktMultiPoint() { + OperatorImportFromWkt importerWKT = (OperatorImportFromWkt) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.ImportFromWkt); + OperatorExportToWkt exporterWKT = (OperatorExportToWkt) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.ExportToWkt); + + MultiPoint multipoint; + String wktString; + Envelope2D envelope = new Envelope2D(); + WktParser wktParser = new WktParser(); + + // Test Import from Multi_point + wktString = " MultiPoint ZM empty"; + multipoint = (MultiPoint) (importerWKT.execute(0, Geometry.Type.Unknown, wktString, null)); + assertTrue(multipoint != null); + assertTrue(multipoint.isEmpty()); + assertTrue(multipoint.hasAttribute(VertexDescription.Semantics.Z)); + assertTrue(multipoint.hasAttribute(VertexDescription.Semantics.M)); + + wktString = exporterWKT.execute(0, multipoint, null); + assertTrue(wktString.equals("MULTIPOINT ZM EMPTY")); + wktParser.resetParser(wktString); + while (wktParser.nextToken() != WktParser.WktToken.not_available) { + } + + wktString = exporterWKT.execute(WktExportFlags.wktExportPoint, multipoint, null); + assertTrue(wktString.equals("POINT ZM EMPTY")); + wktParser.resetParser(wktString); + while (wktParser.nextToken() != WktParser.WktToken.not_available) { + } + + multipoint = new MultiPoint(); + multipoint.add(118.15114354234563, 33.82234433423462345); + multipoint.add(88, 88); + + wktString = exporterWKT.execute(WktExportFlags.wktExportPrecision10, multipoint, null); + assertTrue(wktString.equals("MULTIPOINT ((118.1511435 33.82234433), (88 88))")); + wktParser.resetParser(wktString); + while (wktParser.nextToken() != WktParser.WktToken.not_available) { + } + + multipoint = new MultiPoint(); + multipoint.add(88, 2); + multipoint.add(88, 88); + + wktString = exporterWKT.execute(0, multipoint, null); + assertTrue(wktString.equals("MULTIPOINT ((88 2), (88 88))")); + wktParser.resetParser(wktString); + while (wktParser.nextToken() != WktParser.WktToken.not_available) { + } + + wktString = "Multipoint zm (empty, empty, (10 88 88 33), (10 20 5 33), (20 20 5 33), (20 10 5 33), (12 12 3 33), empty, (10 10 1 33), (12 12 1 33), empty, (60 60 7 33), (60 90.1 7 33), (90 90 7 33), empty, (70 70 7 33), (70 80 7 33), (80 80 7 33), empty)"; + multipoint = (MultiPoint) (importerWKT.execute(0, Geometry.Type.Unknown, wktString, null)); + assertTrue(multipoint != null); + multipoint.queryEnvelope2D(envelope); + // assertTrue(envelope.xmin == 10 && envelope.xmax == 90 && + // envelope.ymin == 10 && Math.abs(envelope.ymax - 90.1) <= 0.001); + assertTrue(multipoint.getPointCount() == 13); + assertTrue(multipoint.hasAttribute(VertexDescription.Semantics.Z)); + assertTrue(multipoint.hasAttribute(VertexDescription.Semantics.M)); + + wktString = "Multipoint zm (10 88 88 33, 10 20 5 33, 20 20 5 33, 20 10 5 33, 12 12 3 33, 10 10 1 33, 12 12 1 33, 60 60 7 33, 60 90.1 7 33, 90 90 7 33, 70 70 7 33, 70 80 7 33, 80 80 7 33)"; + multipoint = (MultiPoint) (importerWKT.execute(0, Geometry.Type.Unknown, wktString, null)); + assertTrue(multipoint != null); + // assertTrue(envelope.xmin == 10 && envelope.xmax == 90 && + // envelope.ymin == 10 && ::fabs(envelope.ymax - 90.1) <= 0.001); + assertTrue(multipoint.getPointCount() == 13); + assertTrue(multipoint.hasAttribute(VertexDescription.Semantics.Z)); + assertTrue(multipoint.hasAttribute(VertexDescription.Semantics.M)); + + wktString = exporterWKT.execute(WktExportFlags.wktExportPrecision15, multipoint, null); + assertTrue(wktString.equals("MULTIPOINT ZM ((10 88 88 33), (10 20 5 33), (20 20 5 33), (20 10 5 33), (12 12 3 33), (10 10 1 33), (12 12 1 33), (60 60 7 33), (60 90.1 7 33), (90 90 7 33), (70 70 7 33), (70 80 7 33), (80 80 7 33))")); + wktParser.resetParser(wktString); + while (wktParser.nextToken() != WktParser.WktToken.not_available) { + } + + wktString = "Multipoint zm (empty, empty, (10 10 5 33))"; + multipoint = (MultiPoint) (importerWKT.execute(0, Geometry.Type.Unknown, wktString, null)); + + wktString = exporterWKT.execute(WktExportFlags.wktExportPoint, multipoint, null); + assertTrue(wktString.equals("POINT ZM (10 10 5 33)")); + wktParser.resetParser(wktString); + while (wktParser.nextToken() != WktParser.WktToken.not_available) { + } + } + + @Test + public static void testImportExportWktPoint() { + OperatorImportFromWkt importerWKT = (OperatorImportFromWkt) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.ImportFromWkt); + OperatorExportToWkt exporterWKT = (OperatorExportToWkt) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.ExportToWkt); + + Point point; + String wktString; + WktParser wktParser = new WktParser(); + + // Test Import from Point + wktString = "Point ZM empty"; + point = (Point) (importerWKT.execute(0, Geometry.Type.Unknown, wktString, null)); + assertTrue(point != null); + assertTrue(point.isEmpty()); + assertTrue(point.hasAttribute(VertexDescription.Semantics.Z)); + assertTrue(point.hasAttribute(VertexDescription.Semantics.M)); + + wktString = exporterWKT.execute(0, point, null); + assertTrue(wktString.equals("POINT ZM EMPTY")); + wktParser.resetParser(wktString); + while (wktParser.nextToken() != WktParser.WktToken.not_available) { + } + + wktString = exporterWKT.execute(WktExportFlags.wktExportMultiPoint, point, null); + assertTrue(wktString.equals("MULTIPOINT ZM EMPTY")); + wktParser.resetParser(wktString); + while (wktParser.nextToken() != WktParser.WktToken.not_available) { + } + + wktString = "Point zm (30.1 10.6 5.1 33.1)"; + point = (Point) (importerWKT.execute(0, Geometry.Type.Unknown, wktString, null)); + assertTrue(point != null); + assertTrue(point.hasAttribute(VertexDescription.Semantics.Z)); + assertTrue(point.hasAttribute(VertexDescription.Semantics.M)); + double x = point.getX(); + double y = point.getY(); + double z = point.getZ(); + double m = point.getM(); + + assertTrue(x == 30.1); + assertTrue(y == 10.6); + assertTrue(z == 5.1); + assertTrue(m == 33.1); + + wktString = exporterWKT.execute(WktExportFlags.wktExportPrecision15, point, null); + assertTrue(wktString.equals("POINT ZM (30.1 10.6 5.1 33.1)")); + wktParser.resetParser(wktString); + while (wktParser.nextToken() != WktParser.WktToken.not_available) { + } + + wktString = exporterWKT.execute(WktExportFlags.wktExportMultiPoint | WktExportFlags.wktExportPrecision15, point, null); + assertTrue(wktString.equals("MULTIPOINT ZM ((30.1 10.6 5.1 33.1))")); + wktParser.resetParser(wktString); + while (wktParser.nextToken() != WktParser.WktToken.not_available) { + } + } + + @Deprecated + @Test + public static void testImportGeoJsonGeometryCollection() { + OperatorImportFromGeoJson importer = (OperatorImportFromGeoJson) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.ImportFromGeoJson); + + String geoJsonString; + Envelope2D envelope = new Envelope2D(); + WktParser wktParser = new WktParser(); + + geoJsonString = "{\"type\" : \"GeometryCollection\", \"geometries\" : [{\"type\" : \"Point\", \"coordinates\": [0,0]}, {\"type\" : \"GeometryCollection\" , \"geometries\" : [ {\"type\" : \"Point\", \"coordinates\" : [0, 0]} , {\"type\" : \"Point\", \"coordinates\" : [1, 1]} ,{ \"type\" : \"Point\", \"coordinates\" : [2, 2]}, {\"type\" : \"LineString\", \"coordinates\" : []}]} , {\"type\" : \"Point\", \"coordinates\" : [1, 1]}, {\"type\" : \"Point\" , \"coordinates\" : [2, 2]} ] }"; + OGCStructure structure = importer.executeOGC(0, geoJsonString, null).m_ogcStructure.m_structures.get(0); + + assertTrue(structure.m_type == 7); + assertTrue(structure.m_structures.get(0).m_type == 1); + assertTrue(structure.m_structures.get(0).m_type == 1); + assertTrue(structure.m_structures.get(1).m_type == 7); + assertTrue(structure.m_structures.get(2).m_type == 1); + assertTrue(structure.m_structures.get(3).m_type == 1); + + assertTrue(structure.m_structures.get(1).m_structures.get(0).m_type == 1); + assertTrue(structure.m_structures.get(1).m_structures.get(1).m_type == 1); + assertTrue(structure.m_structures.get(1).m_structures.get(2).m_type == 1); + assertTrue(structure.m_structures.get(1).m_structures.get(3).m_type == 2); + } + + @Test + public static void testImportGeoJsonMultiPolygon() throws Exception { + OperatorImportFromGeoJson importerGeoJson = (OperatorImportFromGeoJson) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.ImportFromGeoJson); + OperatorExportToGeoJson exporterGeoJson = (OperatorExportToGeoJson) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.ExportToGeoJson); + + MapGeometry map_geometry; + Polygon polygon; + SpatialReference spatial_reference; + String geoJsonString; + Envelope2D envelope = new Envelope2D(); + + // Test Import from MultiPolygon + geoJsonString = "{\"type\": \"MultiPolygon\", \"coordinates\": []}"; + polygon = (Polygon) (importerGeoJson.execute(0, Geometry.Type.Polygon, geoJsonString, null).getGeometry()); + assertTrue(polygon != null); + assertTrue(polygon.isEmpty()); + assertTrue(!polygon.hasAttribute(VertexDescription.Semantics.M)); + + geoJsonString = "{\"coordinates\" : [], \"type\": \"MultiPolygon\", \"crs\": {\"type\": \"name\", \"some\": \"stuff\", \"properties\": {\"some\" : \"stuff\", \"name\": \"urn:ogc:def:crs:OGC:1.3:CRS84\"}}}"; + map_geometry = importerGeoJson.execute(0, Geometry.Type.Polygon, geoJsonString, null); + polygon = (Polygon) map_geometry.getGeometry(); + spatial_reference = map_geometry.getSpatialReference(); + assertTrue(polygon != null); + assertTrue(polygon.isEmpty()); + assertTrue(spatial_reference.getLatestID() == 4326); + + geoJsonString = "{\"coordinates\" : null, \"crs\": null, \"type\": \"MultiPolygon\"}"; + map_geometry = importerGeoJson.execute(0, Geometry.Type.Polygon, geoJsonString, null); + polygon = (Polygon) map_geometry.getGeometry(); + spatial_reference = map_geometry.getSpatialReference(); + assertTrue(polygon != null); + assertTrue(polygon.isEmpty()); + assertTrue(spatial_reference == null); + + geoJsonString = "{\"type\": \"MultiPolygon\", \"coordinates\" : [[], [], [[[]]]], \"crsURN\": \"urn:ogc:def:crs:OGC:1.3:CRS27\"}"; + map_geometry = importerGeoJson.execute(0, Geometry.Type.Polygon, geoJsonString, null); + polygon = (Polygon) map_geometry.getGeometry(); + spatial_reference = map_geometry.getSpatialReference(); + assertTrue(polygon != null); + assertTrue(polygon.isEmpty()); + assertTrue(spatial_reference != null); + assertTrue(spatial_reference.getLatestID() == 4267); + + geoJsonString = "{\"coordinates\" : [[], [[], [[10, 10, 5], [20, 10, 5], [20, 20, 5], [10, 20, 5], [10, 10, 5]], [[12, 12, 3]], [], [[10, 10, 1], [12, 12, 1]]], [], [[[90, 90, 88], [60, 90, 7], [60, 60, 7]], [], [[70, 70, 7], [80, 80, 7], [70, 80, 7], [70, 70, 7]]], []], \"crs\": {\"type\": \"link\", \"properties\": {\"href\": \"http://spatialreference.org/ref/sr-org/6928/ogcwkt/\"}}, \"type\": \"MultiPolygon\"}"; + map_geometry = importerGeoJson.execute(0, Geometry.Type.Unknown, geoJsonString, null); + polygon = (Polygon) map_geometry.getGeometry(); + spatial_reference = map_geometry.getSpatialReference(); + assertTrue(polygon != null); + polygon.queryEnvelope2D(envelope); + assertTrue(envelope.xmin == 10 && envelope.xmax == 90 && envelope.ymin == 10 && envelope.ymax == 90); + assertTrue(polygon.getPointCount() == 14); + assertTrue(polygon.getPathCount() == 5); + assertTrue(spatial_reference.getLatestID() == 3857); + + map_geometry = importerGeoJson.execute(0, Geometry.Type.Unknown, geoJsonString, null); + polygon = (Polygon) map_geometry.getGeometry(); + spatial_reference = map_geometry.getSpatialReference(); + assertTrue(polygon != null); + polygon.queryEnvelope2D(envelope); + assertTrue(envelope.xmin == 10 && envelope.xmax == 90 && envelope.ymin == 10 && envelope.ymax == 90); + assertTrue(polygon.getPointCount() == 14); + assertTrue(polygon.getPathCount() == 5); + assertTrue(spatial_reference.getLatestID() == 3857); + + // Test Export to GeoJSON MultiPolygon + geoJsonString = exporterGeoJson.execute(GeoJsonExportFlags.geoJsonExportSkipCRS, spatial_reference, polygon); + assertTrue(geoJsonString.equals("{\"type\":\"MultiPolygon\",\"coordinates\":[[[[10,10,5],[20,10,5],[20,20,5],[10,20,5],[10,10,5]],[[12,12,3],[12,12,3],[12,12,3]],[[10,10,1],[12,12,1],[10,10,1]]],[[[90,90,88],[60,90,7],[60,60,7],[90,90,88]],[[70,70,7],[70,80,7],[80,80,7],[70,70,7]]]]}")); + + geoJsonString = exporterGeoJson.execute(0, spatial_reference, polygon); + assertTrue(geoJsonString.equals("{\"type\":\"MultiPolygon\",\"coordinates\":[[[[10,10,5],[20,10,5],[20,20,5],[10,20,5],[10,10,5]],[[12,12,3],[12,12,3],[12,12,3]],[[10,10,1],[12,12,1],[10,10,1]]],[[[90,90,88],[60,90,7],[60,60,7],[90,90,88]],[[70,70,7],[70,80,7],[80,80,7],[70,70,7]]]],\"crs\":{\"type\":\"name\",\"properties\":{\"name\":\"EPSG:3857\"}}}")); + + geoJsonString = "{\"type\": \"MultiPolygon\", \"coordinates\": [[[[90, 10, 7], [10, 10, 1], [10, 90, 7], [90, 90, 1], [90, 10, 7]]]] }"; // ring + // i // clockwise + polygon = (Polygon) (importerGeoJson.execute(0, Geometry.Type.Polygon, geoJsonString, null).getGeometry()); + assertTrue(polygon != null); + assertTrue(polygon.getPointCount() == 4); + assertTrue(polygon.getPathCount() == 1); + assertTrue(polygon.hasAttribute(VertexDescription.Semantics.Z)); + assertTrue(polygon.calculateArea2D() > 0); + + // Test import Polygon + geoJsonString = "{\"type\": \"Polygon\", \"coordinates\": [[], [], [[10, 10, 5], [10, 20, 5], [20, 20, 5], [20, 10, 5]], [[12, 12, 3]], [], [[10, 10, 1], [12, 12, 1]], [], [[60, 60, 7], [60, 90, 7], [90, 90, 7], [60, 60, 7]], [], [[70, 70, 7], [70, 80, 7], [80, 80, 7]], []] }"; + map_geometry = importerGeoJson.execute(0, Geometry.Type.Polygon, geoJsonString, null); + polygon = (Polygon) map_geometry.getGeometry(); + spatial_reference = map_geometry.getSpatialReference(); + assertTrue(polygon != null); + assertTrue(polygon.getPointCount() == 14); + assertTrue(polygon.getPathCount() == 5); + assertTrue(spatial_reference.getLatestID() == 4326); + + geoJsonString = exporterGeoJson.execute(0, spatial_reference, polygon); + assertTrue(geoJsonString.equals("{\"type\":\"Polygon\",\"coordinates\":[[[10,10,5],[20,10,5],[20,20,5],[10,20,5],[10,10,5]],[[12,12,3],[12,12,3],[12,12,3]],[[10,10,1],[12,12,1],[10,10,1]],[[60,60,7],[60,90,7],[90,90,7],[60,60,7]],[[70,70,7],[70,80,7],[80,80,7],[70,70,7]]],\"crs\":{\"type\":\"name\",\"properties\":{\"name\":\"EPSG:4326\"}}}")); + + Envelope env = new Envelope(); + env.addAttribute(VertexDescription.Semantics.Z); + polygon.queryEnvelope(env); + + geoJsonString = "{\"coordinates\" : [], \"type\": \"MultiPolygon\", \"crs\":{\"esriwkt\":\"PROJCS[\\\"Gnomonic\\\",GEOGCS[\\\"GCS_WGS_1984\\\",DATUM[\\\"D_WGS_1984\\\",SPHEROID[\\\"WGS_1984\\\",6378137.0,298.257223563]],PRIMEM[\\\"Greenwich\\\",0.0],UNIT[\\\"Degree\\\",0.0174532925199433]],PROJECTION[\\\"Gnomonic\\\"],PARAMETER[\\\"Longitude_Of_Center\\\",0.0],PARAMETER[\\\"Latitude_Of_Center\\\",-45.0],UNIT[\\\"Meter\\\",1.0]]\"}}"; + map_geometry = importerGeoJson.execute(0, Geometry.Type.Polygon, geoJsonString, null); + polygon = (Polygon) map_geometry.getGeometry(); + spatial_reference = map_geometry.getSpatialReference(); + String wkt = spatial_reference.getText(); + assertTrue(wkt.equals( + "PROJCS[\"Gnomonic\",GEOGCS[\"GCS_WGS_1984\",DATUM[\"D_WGS_1984\",SPHEROID[\"WGS_1984\",6378137.0,298.257223563]],PRIMEM[\"Greenwich\",0.0],UNIT[\"Degree\",0.0174532925199433]],PROJECTION[\"Gnomonic\"],PARAMETER[\"Longitude_Of_Center\",0.0],PARAMETER[\"Latitude_Of_Center\",-45.0],UNIT[\"Meter\",1.0]]")); + + geoJsonString = "{\"coordinates\" : [], \"type\": \"MultiPolygon\", \"crs\":{\"type\":\"name\",\"properties\":{\"name\":\"PROJCS[\\\"Gnomonic\\\",GEOGCS[\\\"GCS_WGS_1984\\\",DATUM[\\\"D_WGS_1984\\\",SPHEROID[\\\"WGS_1984\\\",6378137.0,298.257223563]],PRIMEM[\\\"Greenwich\\\",0.0],UNIT[\\\"Degree\\\",0.0174532925199433]],PROJECTION[\\\"Gnomonic\\\"],PARAMETER[\\\"Longitude_Of_Center\\\",0.0],PARAMETER[\\\"Latitude_Of_Center\\\",-45.0],UNIT[\\\"Meter\\\",1.0]]\"}}}"; + map_geometry = importerGeoJson.execute(0, Geometry.Type.Polygon, geoJsonString, null); + polygon = (Polygon) map_geometry.getGeometry(); + spatial_reference = map_geometry.getSpatialReference(); + wkt = spatial_reference.getText(); + assertTrue(wkt.equals( + "PROJCS[\"Gnomonic\",GEOGCS[\"GCS_WGS_1984\",DATUM[\"D_WGS_1984\",SPHEROID[\"WGS_1984\",6378137.0,298.257223563]],PRIMEM[\"Greenwich\",0.0],UNIT[\"Degree\",0.0174532925199433]],PROJECTION[\"Gnomonic\"],PARAMETER[\"Longitude_Of_Center\",0.0],PARAMETER[\"Latitude_Of_Center\",-45.0],UNIT[\"Meter\",1.0]]")); + assertTrue(polygon != null); + assertTrue(polygon.isEmpty()); + + // AGOL exports wkt like this... + geoJsonString = "{\"coordinates\" : [], \"type\": \"MultiPolygon\", \"crs\":{\"type\":\"name\",\"properties\":{\"name\":\"ESRI:PROJCS[\\\"Gnomonic\\\",GEOGCS[\\\"GCS_WGS_1984\\\",DATUM[\\\"D_WGS_1984\\\",SPHEROID[\\\"WGS_1984\\\",6378137.0,298.257223563]],PRIMEM[\\\"Greenwich\\\",0.0],UNIT[\\\"Degree\\\",0.0174532925199433]],PROJECTION[\\\"Gnomonic\\\"],PARAMETER[\\\"Longitude_Of_Center\\\",0.0],PARAMETER[\\\"Latitude_Of_Center\\\",-45.0],UNIT[\\\"Meter\\\",1.0]]\"}}}"; + map_geometry = importerGeoJson.execute(0, Geometry.Type.Polygon, geoJsonString, null); + polygon = (Polygon) map_geometry.getGeometry(); + spatial_reference = map_geometry.getSpatialReference(); + wkt = spatial_reference.getText(); + assertTrue(wkt.equals( + "PROJCS[\"Gnomonic\",GEOGCS[\"GCS_WGS_1984\",DATUM[\"D_WGS_1984\",SPHEROID[\"WGS_1984\",6378137.0,298.257223563]],PRIMEM[\"Greenwich\",0.0],UNIT[\"Degree\",0.0174532925199433]],PROJECTION[\"Gnomonic\"],PARAMETER[\"Longitude_Of_Center\",0.0],PARAMETER[\"Latitude_Of_Center\",-45.0],UNIT[\"Meter\",1.0]]")); + assertTrue(polygon != null); + assertTrue(polygon.isEmpty()); + + boolean exceptionThrownNoWKT = false; + + try { + geoJsonString = exporterGeoJson.execute(GeoJsonExportFlags.geoJsonExportPreferMultiGeometry, + spatial_reference, polygon); + } catch (Exception e) { + exceptionThrownNoWKT = true; + } + + assertTrue(exceptionThrownNoWKT); + } + + @Test + public static void testImportGeoJsonMultiLineString() throws Exception { + OperatorImportFromGeoJson importerGeoJson = (OperatorImportFromGeoJson) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.ImportFromGeoJson); + OperatorExportToGeoJson exporterGeoJson = (OperatorExportToGeoJson) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.ExportToGeoJson); + MapGeometry map_geometry; + Polyline polyline; + SpatialReference spatial_reference; + String geoJsonString; + Envelope2D envelope = new Envelope2D(); + + // Test Import from MultiLineString + geoJsonString = "{\"type\":\"MultiLineString\",\"coordinates\":[], \"crs\" : {\"type\" : \"URL\", \"properties\" : {\"url\" : \"http://www.opengis.net/def/crs/EPSG/0/3857\"}}}"; + map_geometry = importerGeoJson.execute(0, Geometry.Type.Unknown, geoJsonString, null); + polyline = (Polyline) map_geometry.getGeometry(); + spatial_reference = map_geometry.getSpatialReference(); + assertTrue(polyline != null); + assertTrue(spatial_reference != null); + assertTrue(polyline.isEmpty()); + assertTrue(spatial_reference.getLatestID() == 3857); + + geoJsonString = "{\"crs\" : {\"type\" : \"link\", \"properties\" : {\"href\" : \"www.spatialreference.org/ref/epsg/4309/\"}}, \"type\":\"MultiLineString\",\"coordinates\":[[], [], [[10, 10, 5], [10, 20, 5], [20, 88, 5], [20, 10, 5]], [[12, 88, 3]], [], [[10, 10, 1], [12, 12, 1]], [], [[88, 60, 7], [60, 90, 7], [90, 90, 7]], [], [[70, 70, 7], [70, 80, 7], [80, 80, 7]], []]}"; + map_geometry = importerGeoJson.execute(0, Geometry.Type.Unknown, geoJsonString, null); + polyline = (Polyline) map_geometry.getGeometry(); + spatial_reference = map_geometry.getSpatialReference(); + assertTrue(polyline != null); + polyline.queryEnvelope2D(envelope); + assertTrue(envelope.xmin == 10 && envelope.xmax == 90 && envelope.ymin == 10 && envelope.ymax == 90); + assertTrue(polyline.getPointCount() == 14); + assertTrue(polyline.getPathCount() == 5); + assertTrue(polyline.hasAttribute(VertexDescription.Semantics.Z)); + assertTrue(spatial_reference.getLatestID() == 4309); + + geoJsonString = exporterGeoJson.execute(0, spatial_reference, polyline); + assertTrue(geoJsonString.equals("{\"type\":\"MultiLineString\",\"coordinates\":[[[10,10,5],[10,20,5],[20,88,5],[20,10,5]],[[12,88,3],[12,88,3]],[[10,10,1],[12,12,1]],[[88,60,7],[60,90,7],[90,90,7]],[[70,70,7],[70,80,7],[80,80,7]]],\"crs\":{\"type\":\"name\",\"properties\":{\"name\":\"EPSG:4309\"}}}")); + + // Test Import LineString + geoJsonString = "{\"type\": \"LineString\", \"coordinates\": [[10, 10, 5], [10, 20, 5], [20, 20, 5], [20, 10, 5]]}"; + polyline = (Polyline) (importerGeoJson.execute(0, Geometry.Type.Unknown, geoJsonString, null).getGeometry()); + assertTrue(polyline.getPointCount() == 4); + assertTrue(polyline.hasAttribute(VertexDescription.Semantics.Z)); + + geoJsonString = "{\"type\": \"LineString\", \"coordinates\": [[10, 10, 5], [10, 20, 5, 3], [20, 20, 5], [20, 10, 5]]}"; + polyline = (Polyline) (importerGeoJson.execute(0, Geometry.Type.Unknown, geoJsonString, null).getGeometry()); + assertTrue(polyline.getPointCount() == 4); + assertTrue(polyline.hasAttribute(VertexDescription.Semantics.Z)); + assertTrue(polyline.hasAttribute(VertexDescription.Semantics.M)); + + geoJsonString = "{\"type\":\"LineString\",\"coordinates\": [[10, 10, 5], [10, 20, 5], [20, 20, 5], [], [20, 10, 5]],\"crs\" : {\"type\" : \"link\", \"properties\" : {\"href\" : \"www.opengis.net/def/crs/EPSG/0/3857\"}}}"; + map_geometry = importerGeoJson.execute(0, Geometry.Type.Unknown, geoJsonString, null); + polyline = (Polyline) (map_geometry.getGeometry()); + spatial_reference = map_geometry.getSpatialReference(); + assertTrue(polyline.getPointCount() == 4); + assertTrue(spatial_reference.getLatestID() == 3857); + geoJsonString = exporterGeoJson.execute(0, spatial_reference, polyline); + assertTrue(geoJsonString.equals("{\"type\":\"LineString\",\"coordinates\":[[10,10,5],[10,20,5],[20,20,5],[20,10,5]],\"crs\":{\"type\":\"name\",\"properties\":{\"name\":\"EPSG:3857\"}}}")); + + geoJsonString = exporterGeoJson.execute(0, null, polyline); + assertTrue(geoJsonString.equals("{\"type\":\"LineString\",\"coordinates\":[[10,10,5],[10,20,5],[20,20,5],[20,10,5]],\"crs\":null}")); + } + + @Test + public static void testImportGeoJsonMultiPoint() throws Exception { + OperatorImportFromGeoJson importerGeoJson = (OperatorImportFromGeoJson) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.ImportFromGeoJson); + OperatorExportToGeoJson exporterGeoJson = (OperatorExportToGeoJson) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.ExportToGeoJson); + MapGeometry map_geometry; + MultiPoint multipoint; + SpatialReference spatial_reference; + String geoJsonString; + Envelope2D envelope = new Envelope2D(); + + // Test Import from Multi_point + + geoJsonString = "{\"type\":\"MultiPoint\",\"coordinates\":[]}"; + map_geometry = importerGeoJson.execute(0, Geometry.Type.Unknown, geoJsonString, null); + multipoint = (MultiPoint) map_geometry.getGeometry(); + spatial_reference = map_geometry.getSpatialReference(); + assertTrue(multipoint != null); + assertTrue(multipoint.isEmpty()); + assertTrue(spatial_reference.getLatestID() == 4326); + + geoJsonString = exporterGeoJson.execute(0, null, multipoint); + assertTrue(geoJsonString.equals("{\"type\":\"MultiPoint\",\"coordinates\":[],\"crs\":null}")); + + multipoint = new MultiPoint(); + multipoint.add(118.15, 2); + multipoint.add(88, 88); + + geoJsonString = exporterGeoJson.execute(GeoJsonExportFlags.geoJsonExportPrecision16, SpatialReference.create(4269), multipoint); + assertTrue(geoJsonString.equals("{\"type\":\"MultiPoint\",\"coordinates\":[[118.15,2],[88,88]],\"crs\":{\"type\":\"name\",\"properties\":{\"name\":\"EPSG:4269\"}}}")); + + multipoint.setEmpty(); + multipoint.add(88, 2); + multipoint.add(88, 88); + + geoJsonString = exporterGeoJson.execute(0, SpatialReference.create(102100), multipoint); + assertTrue(geoJsonString.equals("{\"type\":\"MultiPoint\",\"coordinates\":[[88,2],[88,88]],\"crs\":{\"type\":\"name\",\"properties\":{\"name\":\"EPSG:3857\"}}}")); + + geoJsonString = "{\"type\":\"MultiPoint\",\"coordinates\":[[], [], [10, 88, 88, 33], [10, 20, 5, 33], [20, 20, 5, 33], [20, 10, 5, 33], [12, 12, 3, 33], [], [10, 10, 1, 33], [12, 12, 1, 33], [], [60, 60, 7, 33], [60, 90.1, 7, 33], [90, 90, 7, 33], [], [70, 70, 7, 33], [70, 80, 7, 33], [80, 80, 7, 33], []],\"crs\":{\"type\":\"OGC\",\"properties\":{\"urn\":\"urn:ogc:def:crs:OGC:1.3:CRS83\"}}}"; + map_geometry = importerGeoJson.execute(0, Geometry.Type.Unknown, geoJsonString, null); + multipoint = (MultiPoint) map_geometry.getGeometry(); + spatial_reference = map_geometry.getSpatialReference(); + assertTrue(multipoint != null); + assertTrue(multipoint.getPointCount() == 13); + assertTrue(multipoint.hasAttribute(VertexDescription.Semantics.Z)); + assertTrue(multipoint.hasAttribute(VertexDescription.Semantics.M)); + assertTrue(spatial_reference.getLatestID() == 4269); + + geoJsonString = "{\"type\":\"MultiPoint\",\"coordinates\": [[10, 88, 88, 33], [10, 20, 5, 33], [20, 20, 5, 33], [], [20, 10, 5, 33], [12, 12, 3, 33], [], [10, 10, 1, 33], [12, 12, 1, 33], [60, 60, 7, 33], [60, 90.1, 7, 33], [90, 90, 7, 33], [70, 70, 7, 33], [70, 80, 7, 33], [80, 80, 7, 33]]}"; + multipoint = (MultiPoint) importerGeoJson.execute(0, Geometry.Type.Unknown, geoJsonString, null).getGeometry(); + assertTrue(multipoint != null); + assertTrue(multipoint.getPointCount() == 13); + assertTrue(multipoint.hasAttribute(VertexDescription.Semantics.Z)); + assertTrue(multipoint.hasAttribute(VertexDescription.Semantics.M)); + + geoJsonString = exporterGeoJson.execute(GeoJsonExportFlags.geoJsonExportPrecision15, null, multipoint); + assertTrue(geoJsonString.equals("{\"type\":\"MultiPoint\",\"coordinates\":[[10,88,88,33],[10,20,5,33],[20,20,5,33],[20,10,5,33],[12,12,3,33],[10,10,1,33],[12,12,1,33],[60,60,7,33],[60,90.1,7,33],[90,90,7,33],[70,70,7,33],[70,80,7,33],[80,80,7,33]],\"crs\":null}")); + + geoJsonString = "{\"type\":\"MultiPoint\",\"coordinates\":[[], [], [10, 10, 5, 33]]}"; + multipoint = (MultiPoint) importerGeoJson.execute(0, Geometry.Type.Unknown, geoJsonString, null).getGeometry(); + + geoJsonString = exporterGeoJson.execute(0, null, multipoint); + assertTrue(geoJsonString.equals("{\"type\":\"MultiPoint\",\"coordinates\":[[10,10,5,33]],\"crs\":null}")); + } + + @Test + public static void testImportGeoJsonPolygon() throws Exception { + OperatorImportFromGeoJson importerGeoJson = (OperatorImportFromGeoJson) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.ImportFromGeoJson); + + Polygon polygon; + String geoJsonString; + Envelope2D envelope = new Envelope2D(); + + // Test Import from Polygon + geoJsonString = "{\"type\": \"Polygon\", \"coordinates\": []}"; + polygon = (Polygon) (importerGeoJson.execute(0, Geometry.Type.Unknown, geoJsonString, null).getGeometry()); + assertTrue(polygon != null); + assertTrue(polygon.isEmpty()); + assertTrue(!polygon.hasAttribute(VertexDescription.Semantics.Z)); + assertTrue(!polygon.hasAttribute(VertexDescription.Semantics.M)); + + geoJsonString = "{\"type\": \"Polygon\", \"coordinates\": [[], [[10, 10, 5], [20, 10, 5], [20, 20, 5], [10, 20, 5], [10, 10, 5]], [[12, 12, 3]], [], [[10, 10, 1], [12, 12, 1]]]}"; + polygon = (Polygon) (importerGeoJson.execute(0, Geometry.Type.Unknown, geoJsonString, null).getGeometry()); + assertTrue(polygon != null); + polygon.queryEnvelope2D(envelope); + assertTrue(envelope.xmin == 10 && envelope.xmax == 20 && envelope.ymin == 10 && envelope.ymax == 20); + assertTrue(polygon.getPointCount() == 8); + assertTrue(polygon.getPathCount() == 3); + assertTrue(polygon.hasAttribute(VertexDescription.Semantics.Z)); + + geoJsonString = "{\"type\": \"Polygon\", \"coordinates\": [[[35, 10], [10, 20], [15, 40], [45, 45], [35, 10]], [[20, 30], [35, 35], [30, 20], [20, 30]]]}"; + Polygon polygon2 = (Polygon) (importerGeoJson.execute(0, Geometry.Type.Unknown, geoJsonString, null).getGeometry()); + assertTrue(polygon2 != null); + } + + @Test + public static void testImportGeoJsonLineString() throws Exception { + OperatorImportFromGeoJson importerGeoJson = (OperatorImportFromGeoJson) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.ImportFromGeoJson); + + Polyline polyline; + String geoJsonString; + Envelope2D envelope = new Envelope2D(); + + // Test Import from LineString + geoJsonString = "{\"type\": \"LineString\", \"coordinates\": []}"; + polyline = (Polyline) (importerGeoJson.execute(0, Geometry.Type.Unknown, geoJsonString, null).getGeometry()); + assertTrue(polyline != null); + assertTrue(polyline.isEmpty()); + assertTrue(!polyline.hasAttribute(VertexDescription.Semantics.Z)); + assertTrue(!polyline.hasAttribute(VertexDescription.Semantics.M)); + + geoJsonString = "{\"type\": \"LineString\", \"coordinates\": [[10, 10, 5], [10, 20, 5], [20, 20, 5], [20, 10, 5]]}"; + polyline = (Polyline) (importerGeoJson.execute(0, Geometry.Type.Unknown, geoJsonString, null).getGeometry()); + assertTrue(polyline != null); + polyline.queryEnvelope2D(envelope); + assertTrue(envelope.xmin == 10 && envelope.xmax == 20 && envelope.ymin == 10 && envelope.ymax == 20); + assertTrue(polyline.getPointCount() == 4); + assertTrue(polyline.getPathCount() == 1); + assertTrue(!polyline.hasAttribute(VertexDescription.Semantics.M)); + } + + @Test + public static void testImportGeoJsonPoint() throws Exception { + OperatorImportFromGeoJson importerGeoJson = (OperatorImportFromGeoJson) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.ImportFromGeoJson); + OperatorExportToGeoJson exporterGeoJson = (OperatorExportToGeoJson) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.ExportToGeoJson); + MapGeometry map_geometry; + SpatialReference spatial_reference; + Point point; + String geoJsonString; + + // Test Import from Point + geoJsonString = "{\"type\":\"Point\",\"coordinates\":[],\"crs\":{\"type\":\"name\",\"properties\":{\"name\":\"EPSG:3857\"}}}"; + map_geometry = importerGeoJson.execute(0, Geometry.Type.Unknown, geoJsonString, null); + point = (Point) map_geometry.getGeometry(); + spatial_reference = map_geometry.getSpatialReference(); + assertTrue(spatial_reference.getLatestID() == 3857); + + assertTrue(point != null); + assertTrue(point.isEmpty()); + + geoJsonString = exporterGeoJson.execute(0, null, point); + assertTrue(geoJsonString.equals("{\"type\":\"Point\",\"coordinates\":[],\"crs\":null}")); + + geoJsonString = exporterGeoJson.execute(GeoJsonExportFlags.geoJsonExportPreferMultiGeometry, null, point); + assertTrue(geoJsonString.equals("{\"type\":\"MultiPoint\",\"coordinates\":[],\"crs\":null}")); + + geoJsonString = "{\"type\":\"Point\",\"coordinates\":[30.1,10.6,5.1,33.1],\"crs\":{\"type\":\"name\",\"properties\":{\"name\":\"urn:ogc:def:crs:ESRI::54051\"}}}"; + map_geometry = importerGeoJson.execute(0, Geometry.Type.Unknown, geoJsonString, null); + point = (Point) map_geometry.getGeometry(); + spatial_reference = map_geometry.getSpatialReference(); + assertTrue(point != null); + assertTrue(point.hasAttribute(VertexDescription.Semantics.Z)); + assertTrue(point.hasAttribute(VertexDescription.Semantics.M)); + assertTrue(spatial_reference.getLatestID() == 54051); + double x = point.getX(); + double y = point.getY(); + double z = point.getZ(); + double m = point.getM(); + + assertTrue(x == 30.1); + assertTrue(y == 10.6); + assertTrue(z == 5.1); + assertTrue(m == 33.1); + + geoJsonString = exporterGeoJson.execute(GeoJsonExportFlags.geoJsonExportPrecision15, spatial_reference, point); + assertTrue(geoJsonString.equals("{\"type\":\"Point\",\"coordinates\":[30.1,10.6,5.1,33.1],\"crs\":{\"type\":\"name\",\"properties\":{\"name\":\"ESRI:54051\"}}}")); + + geoJsonString = exporterGeoJson.execute(GeoJsonExportFlags.geoJsonExportPrecision15, SpatialReference.create(4287), point); + assertTrue(geoJsonString.equals("{\"type\":\"Point\",\"coordinates\":[30.1,10.6,5.1,33.1],\"crs\":{\"type\":\"name\",\"properties\":{\"name\":\"EPSG:4287\"}}}")); + + geoJsonString = exporterGeoJson.execute(GeoJsonExportFlags.geoJsonExportPreferMultiGeometry | GeoJsonExportFlags.geoJsonExportPrecision15, null, point); + assertTrue(geoJsonString.equals("{\"type\":\"MultiPoint\",\"coordinates\":[[30.1,10.6,5.1,33.1]],\"crs\":null}")); + } + + @Test + public static void testImportExportGeoJsonMalformed() { + OperatorImportFromGeoJson importerGeoJson = (OperatorImportFromGeoJson) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.ImportFromGeoJson); + OperatorExportToGeoJson exporterGeoJson = (OperatorExportToGeoJson) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.ExportToGeoJson); + + String geoJsonString; + + try { + geoJsonString = "{\"type\":\"MultiPolygon\",\"coordinates\":[[[2,2,2],[3,3,3],[4,4,4],[2,2,2]]]}"; + importerGeoJson.execute(0, Geometry.Type.Unknown, geoJsonString, null); + assertTrue(false); + } catch (Exception e) { + } + + try { + geoJsonString = "{\"type\":\"Polygon\",\"coordinates\":[[2,2,2],[3,3,3],[4,4,4],[2,2,2]]}"; + importerGeoJson.execute(0, Geometry.Type.Unknown, geoJsonString, null); + assertTrue(false); + } catch (Exception e) { + } + + try { + geoJsonString = "{\"type\":\"Polygon\",\"coordinates\":[[[2,2,2],[3,3,3],[4,4,4],[2,2,2]],2,4]}"; + importerGeoJson.execute(0, Geometry.Type.Unknown, geoJsonString, null); + assertTrue(false); + } catch (Exception e) { + } + + try { + geoJsonString = "{\"type\":\"MultiPoint\",\"coordinates\":[[[2,2,2],[3,3,3],[4,4,4],[2,2,2]]]}"; + importerGeoJson.execute(0, Geometry.Type.Unknown, geoJsonString, null); + assertTrue(false); + } catch (Exception e) { + } + + try { + geoJsonString = "{\"type\":\"LineString\",\"coordinates\":[[[2,2,2],[3,3,3],[4,4,4],[2,2,2]]]}"; + importerGeoJson.execute(0, Geometry.Type.Unknown, geoJsonString, null); + assertTrue(false); + } catch (Exception e) { + } + + try { + geoJsonString = "{\"type\":\"MultiPoint\",\"coordinates\":[[2,2,2],[3,3,3],[4,4,4],[2,2,2],[[]]]}"; + importerGeoJson.execute(0, Geometry.Type.Unknown, geoJsonString, null); + assertTrue(false); + } catch (Exception e) { + } + + try { + geoJsonString = "{\"type\":\"MultiPolygon\",\"coordinates\":[[[[2,2,2],[3,3,3],[4,4,4],[2,2,2],[[]]]]]}"; + importerGeoJson.execute(0, Geometry.Type.Unknown, geoJsonString, null); + assertTrue(false); + } catch (Exception e) { + } + + try { + geoJsonString = "{\"type\":\"MultiPolygon\",\"coordinates\":[[[[2,2,2],[3,3,3],[4,4,4],[2,2,2]],[1,1,1],[2,2,2],[3,3,3],[1,1,1]]]}"; + importerGeoJson.execute(0, Geometry.Type.Unknown, geoJsonString, null); + assertTrue(false); + } catch (Exception e) { + } + + try { + geoJsonString = "{\"type\":\"Polygon\",\"coordinates\":[[[2,2,2],[3,3,3],[4,4,4],[2,2,2]],[1,1,1],[2,2,2],[3,3,3],[1,1,1]]}"; + importerGeoJson.execute(0, Geometry.Type.Unknown, geoJsonString, null); + assertTrue(false); + } catch (Exception e) { + } + + try { + geoJsonString = "{\"type\":\"MultiPolygon\",\"coordinates\":[[[[[]]]]}"; + importerGeoJson.execute(0, Geometry.Type.Unknown, geoJsonString, null); + assertTrue(false); + } catch (Exception e) { + } + + try { + geoJsonString = "{\"type\":\"MultiPolygon\",\"coordinates\":[[[[{}]]]}"; + importerGeoJson.execute(0, Geometry.Type.Unknown, geoJsonString, null); + assertTrue(false); + } catch (Exception e) { + } + + try { + geoJsonString = "{\"type\":\"Point\",\"coordinates\":[30.1,10.6,[],33.1],\"crs\":\"EPSG:3857\"}"; + importerGeoJson.execute(0, Geometry.Type.Unknown, geoJsonString, null); + assertTrue(false); + } catch (Exception e) { + } + } + + @Test + public static void testImportGeoJsonSpatialReference() throws Exception { + OperatorImportFromGeoJson importerGeoJson = (OperatorImportFromGeoJson) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.ImportFromGeoJson); + + String geoJsonString4326; + String geoJsonString3857; + + // Test Import from Point + geoJsonString4326 = "{\"type\": \"Point\", \"coordinates\": [3.0, 5.0], \"crs\": \"EPSG:4326\"}"; + geoJsonString3857 = "{\"type\": \"Point\", \"coordinates\": [3.0, 5.0], \"crs\": \"EPSG:3857\"}"; + + MapGeometry mapGeometry4326 = importerGeoJson.execute(0, Geometry.Type.Unknown, geoJsonString4326, null); + MapGeometry mapGeometry3857 = importerGeoJson.execute(0, Geometry.Type.Unknown, geoJsonString3857, null); + + assertTrue(mapGeometry4326.equals(mapGeometry3857) == false); + assertTrue(mapGeometry4326.getGeometry().equals(mapGeometry3857.getGeometry())); + } + + @Test + public static void testImportExportESRICursors() { + Polygon poly1 = new Polygon(); + Envelope2D env1 = new Envelope2D(); + env1.setCoords(855277, 3892059, 855277 + 100, 3892059 + 100); + poly1.addEnvelope(env1, false); + + Polygon poly2 = new Polygon(); + Envelope2D env2 = new Envelope2D(); + env2.setCoords(855277, 3892059, 855277 + 300, 3892059 + 200); + poly2.addEnvelope(env2, false); + List list2 = new ArrayList<>(); + list2.add(poly1); + list2.add(poly2); + SimpleGeometryCursor simpleGeometryCursor2 = new SimpleGeometryCursor(new ArrayDeque(list2)); + + OperatorExportToESRIShapeCursor operatorExportToESRIShapeCursor = new OperatorExportToESRIShapeCursor(0, simpleGeometryCursor2); + OperatorImportFromESRIShapeCursor operatorImportFromESRIShapeCursor = new OperatorImportFromESRIShapeCursor(0, 0, operatorExportToESRIShapeCursor); + + SpatialReference inputSR = SpatialReference.create(3857); + OperatorFactoryLocal projEnv = OperatorFactoryLocal.getInstance(); + OperatorCrosses operatorCrosses = (OperatorCrosses) (projEnv.getOperator(Operator.Type.Crosses)); + HashMap relate_map = operatorCrosses.execute(poly1, + operatorImportFromESRIShapeCursor, inputSR, null); + + assertNotNull(relate_map); + + assertTrue(!relate_map.get(0L)); + assertTrue(!relate_map.get(1L)); + } + + @Test + public static void testImportExportWKBCursors() { + Polygon poly1 = new Polygon(); + Envelope2D env1 = new Envelope2D(); + env1.setCoords(855277, 3892059, 855277 + 100, 3892059 + 100); + poly1.addEnvelope(env1, false); + + Polygon poly2 = new Polygon(); + Envelope2D env2 = new Envelope2D(); + env2.setCoords(855277, 3892059, 855277 + 300, 3892059 + 200); + poly2.addEnvelope(env2, false); + List list2 = new ArrayList<>(); + list2.add(poly1); + list2.add(poly2); + SimpleGeometryCursor simpleGeometryCursor2 = new SimpleGeometryCursor(new ArrayDeque(list2)); + + OperatorExportToWkbCursor operatorExportToWkbCursor = new OperatorExportToWkbCursor(0, simpleGeometryCursor2); + OperatorImportFromWkbCursor operatorImportFromWkbCursor = new OperatorImportFromWkbCursor(0, operatorExportToWkbCursor); + + SpatialReference inputSR = SpatialReference.create(3857); + OperatorFactoryLocal projEnv = OperatorFactoryLocal.getInstance(); + OperatorCrosses operatorCrosses = (OperatorCrosses) (projEnv.getOperator(Operator.Type.Crosses)); + HashMap relate_map = operatorCrosses.execute(poly1, + operatorImportFromWkbCursor, inputSR, null); + + assertNotNull(relate_map); + assertTrue(!relate_map.get(0L)); + assertTrue(!relate_map.get(1L)); + + simpleGeometryCursor2 = new SimpleGeometryCursor(new ArrayDeque(list2)); + operatorExportToWkbCursor = new OperatorExportToWkbCursor(0, simpleGeometryCursor2); + operatorImportFromWkbCursor = new OperatorImportFromWkbCursor(0, operatorExportToWkbCursor); + double[] distances = {400, 400}; + + OperatorBufferCursor operatorBufferCursor = new OperatorBufferCursor(operatorImportFromWkbCursor, null, distances, NumberUtils.NaN(), 96, false, null); + relate_map = ((OperatorWithin) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.Within)).execute(poly2, operatorBufferCursor, inputSR, null); + assertNotNull(relate_map); + assertTrue(relate_map.get(0L)); + assertTrue(relate_map.get(1L)); + } + + static double randomWithRange(double min, double max) { + double range = Math.abs(max - min); + return (Math.random() * range) + (min <= max ? min : max); + } + + @Test + public static void testImportExportWKTCursors() { + Polygon poly1 = new Polygon(); + Envelope2D env1 = new Envelope2D(); + env1.setCoords(855277, 3892059, 855277 + 100, 3892059 + 100); + poly1.addEnvelope(env1, false); + + Polygon poly2 = new Polygon(); + Envelope2D env2 = new Envelope2D(); + env2.setCoords(855277, 3892059, 855277 + 300, 3892059 + 200); + poly2.addEnvelope(env2, false); + List list2 = new ArrayList<>(); + list2.add(poly1); + list2.add(poly2); + + int size = 1000; + String[] points = new String[size]; + List pointList = new ArrayList<>(size); + ArrayDeque pointArrayDeque = new ArrayDeque<>(size); + ArrayDeque ids = new ArrayDeque<>(size); + for (int i = 0; i < size; i++) { + pointList.add(new Point(randomWithRange(-20, 20), randomWithRange(-20, 20))); + ids.push((long) i + size); + points[i] = (String.format("Point(%f %f)", pointList.get(i).getX(), pointList.get(i).getY())); + pointArrayDeque.push(points[i]); + } + + SimpleStringCursor simpleStringCursor = new SimpleStringCursor(points); + OperatorImportFromWktCursor operatorImportFromWktCursor = new OperatorImportFromWktCursor(0, simpleStringCursor); + int index = 0; + while (operatorImportFromWktCursor.hasNext()) { + Point point_orig = pointList.get(index); + + Point geom = (Point) operatorImportFromWktCursor.next(); + long geometryID = operatorImportFromWktCursor.getGeometryID(); + assertEquals(geometryID, index); + index++; + assertEquals(point_orig.getX(), geom.getX(), 0.000001); + assertEquals(point_orig.getY(), geom.getY(), 0.000001); + } + + + SimpleGeometryCursor simpleGeometryCursor2 = new SimpleGeometryCursor(new ArrayDeque(list2)); + OperatorExportToWktCursor operatorExportToWktCursor = new OperatorExportToWktCursor(0, simpleGeometryCursor2, null); + operatorImportFromWktCursor = new OperatorImportFromWktCursor(0, operatorExportToWktCursor); + + SpatialReference inputSR = SpatialReference.create(3857); + OperatorFactoryLocal projEnv = OperatorFactoryLocal.getInstance(); + OperatorCrosses operatorCrosses = (OperatorCrosses) (projEnv.getOperator(Operator.Type.Crosses)); + HashMap relate_map = operatorCrosses.execute(poly1, + operatorImportFromWktCursor, inputSR, null); + + assertNotNull(relate_map); + assertTrue(!relate_map.get(0L)); + assertTrue(!relate_map.get(1L)); + + simpleGeometryCursor2 = new SimpleGeometryCursor(new ArrayDeque(list2)); + operatorExportToWktCursor = new OperatorExportToWktCursor(0, simpleGeometryCursor2, null); + operatorImportFromWktCursor = new OperatorImportFromWktCursor(0, operatorExportToWktCursor); + double[] distances = {400, 400}; + OperatorBufferCursor operatorBufferCursor = new OperatorBufferCursor(operatorImportFromWktCursor, null, distances, NumberUtils.NaN(), 96, false, null); + relate_map = ((OperatorWithin) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.Within)).execute(poly2, operatorBufferCursor, inputSR, null); + assertNotNull(relate_map); + assertTrue(relate_map.get(0L)); + assertTrue(relate_map.get(1L)); + + + } + + @Test + public void testWKTID() { + Polygon poly1 = new Polygon(); + Envelope2D env1 = new Envelope2D(); + env1.setCoords(855277, 3892059, 855277 + 100, 3892059 + 100); + poly1.addEnvelope(env1, false); + + Polygon poly2 = new Polygon(); + Envelope2D env2 = new Envelope2D(); + env2.setCoords(855277, 3892059, 855277 + 300, 3892059 + 200); + poly2.addEnvelope(env2, false); + List list2 = new ArrayList<>(); + list2.add(poly1); + list2.add(poly2); + + int size = 1000; + String[] points = new String[size]; + List pointList = new ArrayList<>(size); + List stringList = new ArrayList<>(size); + ArrayDeque pointArrayDeque = new ArrayDeque<>(size); + ArrayDeque ids = new ArrayDeque<>(size); + for (int i = 0; i < size; i++) { + pointList.add(new Point(randomWithRange(-20, 20), randomWithRange(-20, 20))); + ids.addLast((long) i + size); + points[i] = (String.format("Point(%f %f)", pointList.get(i).getX(), pointList.get(i).getY())); + pointArrayDeque.addLast(points[i]); + stringList.add(points[i]); + } + + SimpleStringCursor simpleStringCursor1 = new SimpleStringCursor(points); + SimpleStringCursor simpleStringCursor2 = new SimpleStringCursor(pointArrayDeque.clone(), ids.clone()); + SimpleStringCursor simpleStringCursor3 = new SimpleStringCursor(stringList); + + OperatorImportFromWktCursor operatorImportFromWktCursor1 = new OperatorImportFromWktCursor(0, simpleStringCursor1); + OperatorImportFromWktCursor operatorImportFromWktCursor2 = new OperatorImportFromWktCursor(0, simpleStringCursor2); + OperatorImportFromWktCursor operatorImportFromWktCursor3 = new OperatorImportFromWktCursor(0, simpleStringCursor3); + + while (operatorImportFromWktCursor1.hasNext()) { + Geometry geometry1 = operatorImportFromWktCursor1.next(); + Geometry geometry2 = operatorImportFromWktCursor2.next(); + Geometry geometry3 = operatorImportFromWktCursor3.next(); + assertTrue(geometry1.equals(geometry3)); + assertTrue(geometry1.equals(geometry2)); + } + + SimpleStringCursor simpleStringCursor = new SimpleStringCursor(pointArrayDeque.clone(), ids.clone()); + OperatorImportFromWktCursor operatorImportFromWktCursor = new OperatorImportFromWktCursor(0, simpleStringCursor); + int index = 0; + while (operatorImportFromWktCursor.hasNext()) { + Point point_orig = pointList.get(index); + + Point geom = (Point) operatorImportFromWktCursor.next(); + long geometryID = operatorImportFromWktCursor.getGeometryID(); + assertEquals(geometryID, index + size); + index++; + assertEquals(point_orig.getX(), geom.getX(), 0.000001); + assertEquals(point_orig.getY(), geom.getY(), 0.000001); + } + assertTrue(index > 0); + + simpleStringCursor = new SimpleStringCursor(pointArrayDeque.clone(), ids.clone()); + operatorImportFromWktCursor = new OperatorImportFromWktCursor(0, simpleStringCursor); + OperatorProjectCursor operatorProjectCursor = new OperatorProjectCursor(operatorImportFromWktCursor, new ProjectionTransformation(SpatialReference.create(3857), SpatialReference.create(4326)), null); + double[] stuff = new double[]{3000}; + OperatorGeodesicBufferCursor operatorGeodesicBufferCursor = new OperatorGeodesicBufferCursor(operatorProjectCursor, SpatialReference.create(4326), stuff, 10, false, false, null); + index = 0; + while (operatorGeodesicBufferCursor.hasNext()) { + Geometry polygon = operatorGeodesicBufferCursor.next(); + long geometryID = operatorGeodesicBufferCursor.getGeometryID(); + assertEquals(geometryID, index + size); + index++; + } + assertTrue(index > 0); + + + } + + @Test + public void testMultiPointOrdering() { + MultiPoint multiPoint = new MultiPoint(); + for (double longitude = -180; longitude < 180; longitude += 10.0) { + for (double latitude = -80; latitude < 80; latitude += 10.0) { + multiPoint.add(longitude, latitude); + } + } + String wktGeom = OperatorExportToWkt.local().execute(0, multiPoint, null); + Geometry roundTripGeom = OperatorImportFromWkt.local().execute(0, Geometry.Type.Unknown, wktGeom, null); + assertTrue(roundTripGeom.equals(multiPoint)); + } + + + public static Polygon makePolygon() { + Polygon poly = new Polygon(); + poly.startPath(0, 0); + poly.lineTo(0, 10); + poly.lineTo(10, 10); + poly.lineTo(10, 0); + + poly.startPath(3, 3); + poly.lineTo(7, 3); + poly.lineTo(7, 7); + poly.lineTo(3, 7); + + poly.startPath(15, 0); + poly.lineTo(15, 15); + poly.lineTo(30, 15); + poly.lineTo(30, 0); + + poly.setAttribute(VertexDescription.Semantics.Z, 0, 0, 2); + poly.setAttribute(VertexDescription.Semantics.Z, 1, 0, 3); + poly.setAttribute(VertexDescription.Semantics.Z, 2, 0, 5); + poly.setAttribute(VertexDescription.Semantics.Z, 3, 0, 7); + poly.setAttribute(VertexDescription.Semantics.Z, 4, 0, 11); + poly.setAttribute(VertexDescription.Semantics.Z, 5, 0, 13); + poly.setAttribute(VertexDescription.Semantics.Z, 6, 0, 17); + poly.setAttribute(VertexDescription.Semantics.Z, 7, 0, 19); + poly.setAttribute(VertexDescription.Semantics.Z, 8, 0, 23); + poly.setAttribute(VertexDescription.Semantics.Z, 9, 0, 29); + poly.setAttribute(VertexDescription.Semantics.Z, 10, 0, 31); + poly.setAttribute(VertexDescription.Semantics.Z, 11, 0, 37); + + poly.setAttribute(VertexDescription.Semantics.M, 0, 0, 2); + poly.setAttribute(VertexDescription.Semantics.M, 1, 0, 4); + poly.setAttribute(VertexDescription.Semantics.M, 2, 0, 8); + poly.setAttribute(VertexDescription.Semantics.M, 3, 0, 16); + poly.setAttribute(VertexDescription.Semantics.M, 4, 0, 32); + poly.setAttribute(VertexDescription.Semantics.M, 5, 0, 64); + poly.setAttribute(VertexDescription.Semantics.M, 6, 0, 128); + poly.setAttribute(VertexDescription.Semantics.M, 7, 0, 256); + poly.setAttribute(VertexDescription.Semantics.M, 8, 0, 512); + poly.setAttribute(VertexDescription.Semantics.M, 9, 0, 1024); + poly.setAttribute(VertexDescription.Semantics.M, 10, 0, 2048); + poly.setAttribute(VertexDescription.Semantics.M, 11, 0, 4096); + + return poly; + } + + public static Polygon makePolygon2() { + Polygon poly = new Polygon(); + poly.startPath(0, 0); + poly.lineTo(0, 10); + poly.lineTo(10, 10); + poly.lineTo(10, 0); + + poly.setAttribute(VertexDescription.Semantics.Z, 0, 0, 2); + poly.setAttribute(VertexDescription.Semantics.Z, 1, 0, 3); + poly.setAttribute(VertexDescription.Semantics.Z, 2, 0, 5); + poly.setAttribute(VertexDescription.Semantics.Z, 3, 0, 7); + + poly.setAttribute(VertexDescription.Semantics.M, 0, 0, 2); + poly.setAttribute(VertexDescription.Semantics.M, 1, 0, 4); + poly.setAttribute(VertexDescription.Semantics.M, 2, 0, 8); + poly.setAttribute(VertexDescription.Semantics.M, 3, 0, 16); + + return poly; + } + + public static Polyline makePolyline() { + Polyline poly = new Polyline(); + poly.startPath(10, 1); + poly.lineTo(15, 20); + poly.lineTo(30, 14); + poly.lineTo(60, 144); + + poly.startPath(20, 13); + poly.lineTo(150, 120); + poly.lineTo(300, 414); + poly.lineTo(610, 14); + + poly.setAttribute(VertexDescription.Semantics.Z, 0, 0, 2); + poly.setAttribute(VertexDescription.Semantics.Z, 1, 0, 3); + poly.setAttribute(VertexDescription.Semantics.Z, 2, 0, 5); + poly.setAttribute(VertexDescription.Semantics.Z, 3, 0, 7); + poly.setAttribute(VertexDescription.Semantics.Z, 4, 0, 11); + poly.setAttribute(VertexDescription.Semantics.Z, 5, 0, 13); + poly.setAttribute(VertexDescription.Semantics.Z, 6, 0, 17); + poly.setAttribute(VertexDescription.Semantics.Z, 7, 0, 19); + + poly.setAttribute(VertexDescription.Semantics.M, 0, 0, 2); + poly.setAttribute(VertexDescription.Semantics.M, 1, 0, 4); + poly.setAttribute(VertexDescription.Semantics.M, 2, 0, 8); + poly.setAttribute(VertexDescription.Semantics.M, 3, 0, 16); + poly.setAttribute(VertexDescription.Semantics.M, 4, 0, 32); + poly.setAttribute(VertexDescription.Semantics.M, 5, 0, 64); + poly.setAttribute(VertexDescription.Semantics.M, 6, 0, 128); + poly.setAttribute(VertexDescription.Semantics.M, 7, 0, 256); + + poly.setAttribute(VertexDescription.Semantics.ID, 0, 0, 1); + poly.setAttribute(VertexDescription.Semantics.ID, 1, 0, 2); + poly.setAttribute(VertexDescription.Semantics.ID, 2, 0, 3); + poly.setAttribute(VertexDescription.Semantics.ID, 3, 0, 5); + poly.setAttribute(VertexDescription.Semantics.ID, 4, 0, 8); + poly.setAttribute(VertexDescription.Semantics.ID, 5, 0, 13); + poly.setAttribute(VertexDescription.Semantics.ID, 6, 0, 21); + poly.setAttribute(VertexDescription.Semantics.ID, 7, 0, 34); + + return poly; + } + + public static Polyline makePolyline2() { + Polyline poly = new Polyline(); + poly.startPath(10, 1); + poly.lineTo(15, 20); + poly.lineTo(30, 14); + poly.lineTo(60, 144); + + poly.setAttribute(VertexDescription.Semantics.Z, 0, 0, 2); + poly.setAttribute(VertexDescription.Semantics.Z, 1, 0, 3); + poly.setAttribute(VertexDescription.Semantics.Z, 2, 0, 5); + poly.setAttribute(VertexDescription.Semantics.Z, 3, 0, 7); + + poly.setAttribute(VertexDescription.Semantics.M, 0, 0, 2); + poly.setAttribute(VertexDescription.Semantics.M, 1, 0, 4); + poly.setAttribute(VertexDescription.Semantics.M, 2, 0, 8); + poly.setAttribute(VertexDescription.Semantics.M, 3, 0, 16); + + return poly; + } + + public static Point makePoint() { + Point point = new Point(); + point.setXY(11, 13); + + point.setZ(32); + point.setM(243); + point.setID(1024); + + return point; + } + + public static MultiPoint makeMultiPoint() { + MultiPoint mpoint = new MultiPoint(); + Point pt1 = new Point(); + pt1.setXY(0, 0); + pt1.setZ(-1); + + Point pt2 = new Point(); + pt2.setXY(0, 0); + pt2.setZ(1); + + Point pt3 = new Point(); + pt3.setXY(0, 1); + pt3.setZ(1); + + mpoint.add(pt1); + mpoint.add(pt2); + mpoint.add(pt3); + + mpoint.setAttribute(VertexDescription.Semantics.ID, 0, 0, 7); + mpoint.setAttribute(VertexDescription.Semantics.ID, 1, 0, 11); + mpoint.setAttribute(VertexDescription.Semantics.ID, 2, 0, 13); + + return mpoint; + } + + public static MultiPoint makeMultiPoint2() { + MultiPoint mpoint = new MultiPoint(); + Point pt1 = new Point(); + pt1.setX(0.0); + pt1.setY(0.0); + pt1.setZ(-1.0); + + mpoint.add(pt1); + + return mpoint; + } + + public static Envelope makeEnvelope() { + Envelope envelope; + + Envelope env = new Envelope(0.0, 0.0, 5.0, 5.0); + envelope = env; + + Envelope1D interval = new Envelope1D(); + interval.vmin = -3.0; + interval.vmax = -7.0; + envelope.setInterval(VertexDescription.Semantics.Z, 0, interval); + + interval.vmin = 16.0; + interval.vmax = 32.0; + envelope.setInterval(VertexDescription.Semantics.M, 0, interval); + + interval.vmin = 5.0; + interval.vmax = 11.0; + envelope.setInterval(VertexDescription.Semantics.ID, 0, interval); + + return envelope; + } } diff --git a/src/test/java/com/esri/core/geometry/TestInterpolateAttributes.java b/src/test/java/com/esri/core/geometry/TestInterpolateAttributes.java index 0584267e..f20f3063 100644 --- a/src/test/java/com/esri/core/geometry/TestInterpolateAttributes.java +++ b/src/test/java/com/esri/core/geometry/TestInterpolateAttributes.java @@ -28,194 +28,194 @@ import org.junit.Test; public class TestInterpolateAttributes extends TestCase { - @Override - protected void setUp() throws Exception { - super.setUp(); - } - - @Override - protected void tearDown() throws Exception { - super.tearDown(); - } - - @Test - public static void test1() { - Polyline poly = new Polyline(); - - poly.startPath(0, 0); - poly.lineTo(0, 1.0 / 3.0); - poly.lineTo(0, 2.0 / 3.0); - poly.lineTo(0, 4.0 / 3.0); - poly.lineTo(0, Math.sqrt(6.0)); - poly.lineTo(0, Math.sqrt(7.0)); - - poly.setAttribute(VertexDescription.Semantics.M, 0, 0, 3); - poly.setAttribute(VertexDescription.Semantics.M, 1, 0, 5); - poly.setAttribute(VertexDescription.Semantics.M, 2, 0, 7); - poly.setAttribute(VertexDescription.Semantics.M, 5, 0, 11); - - poly.interpolateAttributes(VertexDescription.Semantics.M, 0, 1, 0, 1); - assertTrue(poly.getAttributeAsDbl(VertexDescription.Semantics.M, 0, 0) == 3); - assertTrue(poly.getAttributeAsDbl(VertexDescription.Semantics.M, 1, 0) == 5); - assertTrue(poly.getAttributeAsDbl(VertexDescription.Semantics.M, 2, 0) == 7); - assertTrue(NumberUtils.isNaN(poly.getAttributeAsDbl( - VertexDescription.Semantics.M, 3, 0))); - assertTrue(NumberUtils.isNaN(poly.getAttributeAsDbl( - VertexDescription.Semantics.M, 4, 0))); - assertTrue(poly.getAttributeAsDbl(VertexDescription.Semantics.M, 5, 0) == 11); - - poly.interpolateAttributes(VertexDescription.Semantics.M, 0, 1, 0, 2); - assertTrue(poly.getAttributeAsDbl(VertexDescription.Semantics.M, 0, 0) == 3); - assertTrue(poly.getAttributeAsDbl(VertexDescription.Semantics.M, 1, 0) == 5); - assertTrue(poly.getAttributeAsDbl(VertexDescription.Semantics.M, 2, 0) == 7); - assertTrue(NumberUtils.isNaN(poly.getAttributeAsDbl( - VertexDescription.Semantics.M, 3, 0))); - assertTrue(NumberUtils.isNaN(poly.getAttributeAsDbl( - VertexDescription.Semantics.M, 4, 0))); - assertTrue(poly.getAttributeAsDbl(VertexDescription.Semantics.M, 5, 0) == 11); - - poly.interpolateAttributes(VertexDescription.Semantics.M, 0, 2, 0, 5); - assertTrue(poly.getAttributeAsDbl(VertexDescription.Semantics.M, 0, 0) == 3); - assertTrue(poly.getAttributeAsDbl(VertexDescription.Semantics.M, 1, 0) == 5); - assertTrue(poly.getAttributeAsDbl(VertexDescription.Semantics.M, 2, 0) == 7); - double a3 = poly.getAttributeAsDbl(VertexDescription.Semantics.M, 3, 0); - assertTrue(a3 > 7 && a3 < 11); - double a4 = poly.getAttributeAsDbl(VertexDescription.Semantics.M, 4, 0); - assertTrue(a4 > a3 && a4 < 11); - assertTrue(poly.getAttributeAsDbl(VertexDescription.Semantics.M, 5, 0) == 11); - - poly.startPath(0, Math.sqrt(8.0)); - poly.lineTo(0, Math.sqrt(10.0)); - poly.lineTo(0, Math.sqrt(11.0)); - } - - @Test - public static void test2() { - Polyline poly = new Polyline(); - - poly.startPath(0, 0); - poly.lineTo(0, 1.0 / 3.0); - - poly.startPath(0, Math.sqrt(8.0)); - poly.lineTo(0, Math.sqrt(10.0)); - - poly.setAttribute(VertexDescription.Semantics.M, 1, 0, Math.sqrt(3.0)); - - poly.interpolateAttributes(VertexDescription.Semantics.M, 0, 1, 1, 0); - assertTrue(poly.getAttributeAsDbl(VertexDescription.Semantics.M, 1, 0) == Math - .sqrt(3.0)); - assertTrue(NumberUtils.isNaN(poly.getAttributeAsDbl( - VertexDescription.Semantics.M, 2, 0))); - - poly.setAttribute(VertexDescription.Semantics.M, 3, 0, Math.sqrt(5.0)); - poly.interpolateAttributes(VertexDescription.Semantics.M, 0, 1, 1, 1); - assertTrue(poly.getAttributeAsDbl(VertexDescription.Semantics.M, 1, 0) == Math - .sqrt(3.0)); - double a2 = poly.getAttributeAsDbl(VertexDescription.Semantics.M, 2, 0); - assertTrue(a2 == Math.sqrt(3.0)); - assertTrue(poly.getAttributeAsDbl(VertexDescription.Semantics.M, 3, 0) == Math - .sqrt(5.0)); - } - - @Test - public static void test3() { - Polyline poly = new Polyline(); - - poly.startPath(0, Math.sqrt(0.0)); - poly.lineTo(0, Math.sqrt(5.0)); - - poly.startPath(0, Math.sqrt(8.0)); - poly.lineTo(0, Math.sqrt(10.0)); - - poly.setAttribute(VertexDescription.Semantics.M, 0, 0, Math.sqrt(3.0)); - poly.setAttribute(VertexDescription.Semantics.M, 2, 0, Math.sqrt(5.0)); - - poly.interpolateAttributes(VertexDescription.Semantics.M, 0, 0, 1, 0); - assertTrue(poly.getAttributeAsDbl(VertexDescription.Semantics.M, 0, 0) == Math - .sqrt(3.0)); - assertTrue(poly.getAttributeAsDbl(VertexDescription.Semantics.M, 1, 0) == Math - .sqrt(5.0)); - assertTrue(poly.getAttributeAsDbl(VertexDescription.Semantics.M, 2, 0) == Math - .sqrt(5.0)); - } - - @Test - public static void test4() { - Polyline poly = new Polyline(); - - poly.startPath(0, Math.sqrt(0.0)); - poly.lineTo(0, Math.sqrt(1.0)); - - poly.startPath(0, Math.sqrt(1.0)); - poly.lineTo(0, Math.sqrt(2.0)); - - poly.startPath(0, Math.sqrt(2.0)); - poly.lineTo(0, Math.sqrt(3.0)); - - poly.startPath(0, Math.sqrt(3.0)); - poly.lineTo(0, Math.sqrt(4.0)); - - poly.startPath(0, Math.sqrt(4.0)); - poly.lineTo(0, Math.sqrt(5.0)); - - poly.setAttribute(VertexDescription.Semantics.M, 1, 0, Math.sqrt(1.0)); - poly.setAttribute(VertexDescription.Semantics.M, 8, 0, Math.sqrt(4.0)); - - poly.interpolateAttributes(VertexDescription.Semantics.M, 0, 1, 4, 0); - - assertTrue(NumberUtils.isNaN(poly.getAttributeAsDbl( - VertexDescription.Semantics.M, 0, 0))); - assertTrue(poly.getAttributeAsDbl(VertexDescription.Semantics.M, 1, 0) == Math - .sqrt(1.0)); - assertTrue(poly.getAttributeAsDbl(VertexDescription.Semantics.M, 2, 0) == Math - .sqrt(1.0)); - assertTrue(poly.getAttributeAsDbl(VertexDescription.Semantics.M, 3, 0) == Math - .sqrt(2.0)); - assertTrue(poly.getAttributeAsDbl(VertexDescription.Semantics.M, 4, 0) == Math - .sqrt(2.0)); - assertTrue(poly.getAttributeAsDbl(VertexDescription.Semantics.M, 5, 0) == Math - .sqrt(3.0)); - assertTrue(poly.getAttributeAsDbl(VertexDescription.Semantics.M, 6, 0) == Math - .sqrt(3.0)); - assertTrue(poly.getAttributeAsDbl(VertexDescription.Semantics.M, 7, 0) == Math - .sqrt(4.0)); - assertTrue(poly.getAttributeAsDbl(VertexDescription.Semantics.M, 8, 0) == Math - .sqrt(4.0)); - assertTrue(NumberUtils.isNaN(poly.getAttributeAsDbl( - VertexDescription.Semantics.M, 9, 0))); - } - - @Test - public static void test5() { - Polygon poly = new Polygon(); - - poly.startPath(0, 0); - poly.lineTo(0, 1); - poly.lineTo(1, 1); - poly.lineTo(1, 0); - - poly.startPath(2, 0); - poly.lineTo(2, 1); - poly.lineTo(3, 1); - poly.lineTo(3, 0); - - poly.setAttribute(VertexDescription.Semantics.M, 1, 0, 1); - poly.setAttribute(VertexDescription.Semantics.M, 3, 0, 3); - - poly.setAttribute(VertexDescription.Semantics.M, 6, 0, 1); - poly.setAttribute(VertexDescription.Semantics.M, 5, 0, 4); - - poly.interpolateAttributes(VertexDescription.Semantics.M, 0, 3, 1); - poly.interpolateAttributes(VertexDescription.Semantics.M, 0, 1, 3); - poly.interpolateAttributes(VertexDescription.Semantics.M, 1, 2, 1); - - assertTrue(poly.getAttributeAsDbl(VertexDescription.Semantics.M, 0, 0) == 2); - assertTrue(poly.getAttributeAsDbl(VertexDescription.Semantics.M, 1, 0) == 1); - assertTrue(poly.getAttributeAsDbl(VertexDescription.Semantics.M, 2, 0) == 2); - assertTrue(poly.getAttributeAsDbl(VertexDescription.Semantics.M, 3, 0) == 3); - assertTrue(poly.getAttributeAsDbl(VertexDescription.Semantics.M, 4, 0) == 3); - assertTrue(poly.getAttributeAsDbl(VertexDescription.Semantics.M, 5, 0) == 4); - assertTrue(poly.getAttributeAsDbl(VertexDescription.Semantics.M, 6, 0) == 1); - assertTrue(poly.getAttributeAsDbl(VertexDescription.Semantics.M, 7, 0) == 2); - } + @Override + protected void setUp() throws Exception { + super.setUp(); + } + + @Override + protected void tearDown() throws Exception { + super.tearDown(); + } + + @Test + public static void test1() { + Polyline poly = new Polyline(); + + poly.startPath(0, 0); + poly.lineTo(0, 1.0 / 3.0); + poly.lineTo(0, 2.0 / 3.0); + poly.lineTo(0, 4.0 / 3.0); + poly.lineTo(0, Math.sqrt(6.0)); + poly.lineTo(0, Math.sqrt(7.0)); + + poly.setAttribute(VertexDescription.Semantics.M, 0, 0, 3); + poly.setAttribute(VertexDescription.Semantics.M, 1, 0, 5); + poly.setAttribute(VertexDescription.Semantics.M, 2, 0, 7); + poly.setAttribute(VertexDescription.Semantics.M, 5, 0, 11); + + poly.interpolateAttributes(VertexDescription.Semantics.M, 0, 1, 0, 1); + assertTrue(poly.getAttributeAsDbl(VertexDescription.Semantics.M, 0, 0) == 3); + assertTrue(poly.getAttributeAsDbl(VertexDescription.Semantics.M, 1, 0) == 5); + assertTrue(poly.getAttributeAsDbl(VertexDescription.Semantics.M, 2, 0) == 7); + assertTrue(NumberUtils.isNaN(poly.getAttributeAsDbl( + VertexDescription.Semantics.M, 3, 0))); + assertTrue(NumberUtils.isNaN(poly.getAttributeAsDbl( + VertexDescription.Semantics.M, 4, 0))); + assertTrue(poly.getAttributeAsDbl(VertexDescription.Semantics.M, 5, 0) == 11); + + poly.interpolateAttributes(VertexDescription.Semantics.M, 0, 1, 0, 2); + assertTrue(poly.getAttributeAsDbl(VertexDescription.Semantics.M, 0, 0) == 3); + assertTrue(poly.getAttributeAsDbl(VertexDescription.Semantics.M, 1, 0) == 5); + assertTrue(poly.getAttributeAsDbl(VertexDescription.Semantics.M, 2, 0) == 7); + assertTrue(NumberUtils.isNaN(poly.getAttributeAsDbl( + VertexDescription.Semantics.M, 3, 0))); + assertTrue(NumberUtils.isNaN(poly.getAttributeAsDbl( + VertexDescription.Semantics.M, 4, 0))); + assertTrue(poly.getAttributeAsDbl(VertexDescription.Semantics.M, 5, 0) == 11); + + poly.interpolateAttributes(VertexDescription.Semantics.M, 0, 2, 0, 5); + assertTrue(poly.getAttributeAsDbl(VertexDescription.Semantics.M, 0, 0) == 3); + assertTrue(poly.getAttributeAsDbl(VertexDescription.Semantics.M, 1, 0) == 5); + assertTrue(poly.getAttributeAsDbl(VertexDescription.Semantics.M, 2, 0) == 7); + double a3 = poly.getAttributeAsDbl(VertexDescription.Semantics.M, 3, 0); + assertTrue(a3 > 7 && a3 < 11); + double a4 = poly.getAttributeAsDbl(VertexDescription.Semantics.M, 4, 0); + assertTrue(a4 > a3 && a4 < 11); + assertTrue(poly.getAttributeAsDbl(VertexDescription.Semantics.M, 5, 0) == 11); + + poly.startPath(0, Math.sqrt(8.0)); + poly.lineTo(0, Math.sqrt(10.0)); + poly.lineTo(0, Math.sqrt(11.0)); + } + + @Test + public static void test2() { + Polyline poly = new Polyline(); + + poly.startPath(0, 0); + poly.lineTo(0, 1.0 / 3.0); + + poly.startPath(0, Math.sqrt(8.0)); + poly.lineTo(0, Math.sqrt(10.0)); + + poly.setAttribute(VertexDescription.Semantics.M, 1, 0, Math.sqrt(3.0)); + + poly.interpolateAttributes(VertexDescription.Semantics.M, 0, 1, 1, 0); + assertTrue(poly.getAttributeAsDbl(VertexDescription.Semantics.M, 1, 0) == Math + .sqrt(3.0)); + assertTrue(NumberUtils.isNaN(poly.getAttributeAsDbl( + VertexDescription.Semantics.M, 2, 0))); + + poly.setAttribute(VertexDescription.Semantics.M, 3, 0, Math.sqrt(5.0)); + poly.interpolateAttributes(VertexDescription.Semantics.M, 0, 1, 1, 1); + assertTrue(poly.getAttributeAsDbl(VertexDescription.Semantics.M, 1, 0) == Math + .sqrt(3.0)); + double a2 = poly.getAttributeAsDbl(VertexDescription.Semantics.M, 2, 0); + assertTrue(a2 == Math.sqrt(3.0)); + assertTrue(poly.getAttributeAsDbl(VertexDescription.Semantics.M, 3, 0) == Math + .sqrt(5.0)); + } + + @Test + public static void test3() { + Polyline poly = new Polyline(); + + poly.startPath(0, Math.sqrt(0.0)); + poly.lineTo(0, Math.sqrt(5.0)); + + poly.startPath(0, Math.sqrt(8.0)); + poly.lineTo(0, Math.sqrt(10.0)); + + poly.setAttribute(VertexDescription.Semantics.M, 0, 0, Math.sqrt(3.0)); + poly.setAttribute(VertexDescription.Semantics.M, 2, 0, Math.sqrt(5.0)); + + poly.interpolateAttributes(VertexDescription.Semantics.M, 0, 0, 1, 0); + assertTrue(poly.getAttributeAsDbl(VertexDescription.Semantics.M, 0, 0) == Math + .sqrt(3.0)); + assertTrue(poly.getAttributeAsDbl(VertexDescription.Semantics.M, 1, 0) == Math + .sqrt(5.0)); + assertTrue(poly.getAttributeAsDbl(VertexDescription.Semantics.M, 2, 0) == Math + .sqrt(5.0)); + } + + @Test + public static void test4() { + Polyline poly = new Polyline(); + + poly.startPath(0, Math.sqrt(0.0)); + poly.lineTo(0, Math.sqrt(1.0)); + + poly.startPath(0, Math.sqrt(1.0)); + poly.lineTo(0, Math.sqrt(2.0)); + + poly.startPath(0, Math.sqrt(2.0)); + poly.lineTo(0, Math.sqrt(3.0)); + + poly.startPath(0, Math.sqrt(3.0)); + poly.lineTo(0, Math.sqrt(4.0)); + + poly.startPath(0, Math.sqrt(4.0)); + poly.lineTo(0, Math.sqrt(5.0)); + + poly.setAttribute(VertexDescription.Semantics.M, 1, 0, Math.sqrt(1.0)); + poly.setAttribute(VertexDescription.Semantics.M, 8, 0, Math.sqrt(4.0)); + + poly.interpolateAttributes(VertexDescription.Semantics.M, 0, 1, 4, 0); + + assertTrue(NumberUtils.isNaN(poly.getAttributeAsDbl( + VertexDescription.Semantics.M, 0, 0))); + assertTrue(poly.getAttributeAsDbl(VertexDescription.Semantics.M, 1, 0) == Math + .sqrt(1.0)); + assertTrue(poly.getAttributeAsDbl(VertexDescription.Semantics.M, 2, 0) == Math + .sqrt(1.0)); + assertTrue(poly.getAttributeAsDbl(VertexDescription.Semantics.M, 3, 0) == Math + .sqrt(2.0)); + assertTrue(poly.getAttributeAsDbl(VertexDescription.Semantics.M, 4, 0) == Math + .sqrt(2.0)); + assertTrue(poly.getAttributeAsDbl(VertexDescription.Semantics.M, 5, 0) == Math + .sqrt(3.0)); + assertTrue(poly.getAttributeAsDbl(VertexDescription.Semantics.M, 6, 0) == Math + .sqrt(3.0)); + assertTrue(poly.getAttributeAsDbl(VertexDescription.Semantics.M, 7, 0) == Math + .sqrt(4.0)); + assertTrue(poly.getAttributeAsDbl(VertexDescription.Semantics.M, 8, 0) == Math + .sqrt(4.0)); + assertTrue(NumberUtils.isNaN(poly.getAttributeAsDbl( + VertexDescription.Semantics.M, 9, 0))); + } + + @Test + public static void test5() { + Polygon poly = new Polygon(); + + poly.startPath(0, 0); + poly.lineTo(0, 1); + poly.lineTo(1, 1); + poly.lineTo(1, 0); + + poly.startPath(2, 0); + poly.lineTo(2, 1); + poly.lineTo(3, 1); + poly.lineTo(3, 0); + + poly.setAttribute(VertexDescription.Semantics.M, 1, 0, 1); + poly.setAttribute(VertexDescription.Semantics.M, 3, 0, 3); + + poly.setAttribute(VertexDescription.Semantics.M, 6, 0, 1); + poly.setAttribute(VertexDescription.Semantics.M, 5, 0, 4); + + poly.interpolateAttributes(VertexDescription.Semantics.M, 0, 3, 1); + poly.interpolateAttributes(VertexDescription.Semantics.M, 0, 1, 3); + poly.interpolateAttributes(VertexDescription.Semantics.M, 1, 2, 1); + + assertTrue(poly.getAttributeAsDbl(VertexDescription.Semantics.M, 0, 0) == 2); + assertTrue(poly.getAttributeAsDbl(VertexDescription.Semantics.M, 1, 0) == 1); + assertTrue(poly.getAttributeAsDbl(VertexDescription.Semantics.M, 2, 0) == 2); + assertTrue(poly.getAttributeAsDbl(VertexDescription.Semantics.M, 3, 0) == 3); + assertTrue(poly.getAttributeAsDbl(VertexDescription.Semantics.M, 4, 0) == 3); + assertTrue(poly.getAttributeAsDbl(VertexDescription.Semantics.M, 5, 0) == 4); + assertTrue(poly.getAttributeAsDbl(VertexDescription.Semantics.M, 6, 0) == 1); + assertTrue(poly.getAttributeAsDbl(VertexDescription.Semantics.M, 7, 0) == 2); + } } diff --git a/src/test/java/com/esri/core/geometry/TestIntersect2.java b/src/test/java/com/esri/core/geometry/TestIntersect2.java index f4e0478f..a214b910 100644 --- a/src/test/java/com/esri/core/geometry/TestIntersect2.java +++ b/src/test/java/com/esri/core/geometry/TestIntersect2.java @@ -29,376 +29,376 @@ import org.junit.Test; public class TestIntersect2 extends TestCase { - @Override - protected void setUp() throws Exception { - super.setUp(); - } - - @Override - protected void tearDown() throws Exception { - super.tearDown(); - } - - @Test - /** - * Intersect - * throw InvalidShapeException when applying between polyline and polygon - * - * */ - public void testIntersectBetweenPolylineAndPolygon() { - Polyline basePl = new Polyline(); - basePl.startPath(new Point(-117, 20)); - basePl.lineTo(new Point(-117, 10)); - basePl.lineTo(new Point(-130, 10)); - basePl.lineTo(new Point(-130, 20)); - basePl.lineTo(new Point(-117, 20)); - - Polygon compPl = new Polygon(); - compPl.startPath(-116, 20); - compPl.lineTo(-131, 10); - compPl.lineTo(-121, 50); - - Geometry intersectGeom = null; - - @SuppressWarnings("unused") - int noException = 1; // no exception - try { - intersectGeom = GeometryEngine.intersect(basePl, compPl, - SpatialReference.create(4326)); - - } catch (Exception ex) { - noException = 0; - } - assertNotNull(intersectGeom); - - // Geometry[] geometries = new Geometry[1]; - // geometries[0] = basePl; - // BorgGeometryUtils.getIntersectFromRestWS(geometries, compPl, 4326); - } - - @Test - public void testIntersectBetweenPolylines() { - Polyline basePl = new Polyline(); - basePl.startPath(new Point(-117, 20)); - basePl.lineTo(new Point(-130, 10)); - basePl.lineTo(new Point(-120, 50)); - - Polyline compPl = new Polyline(); - compPl.startPath(new Point(-116, 20)); - compPl.lineTo(new Point(-131, 10)); - compPl.lineTo(new Point(-121, 50)); - - int noException = 1; // no exception - try { - @SuppressWarnings("unused") - Geometry intersectGeom = GeometryEngine.intersect(basePl, compPl, - SpatialReference.create(4326)); - - } catch (Exception ex) { - noException = 0; - } - assertEquals(noException, 1); - } - - @Test - public void testPointAndPolyline1() { - Point basePl = new Point(-116, 20); - - Polyline compPl = new Polyline(); - compPl.startPath(new Point(-116, 20)); - compPl.lineTo(new Point(-131, 10)); - compPl.lineTo(new Point(-121, 50)); - - int noException = 1; // no exception - Geometry intersectGeom = null; - try { - intersectGeom = GeometryEngine.intersect(basePl, compPl, - SpatialReference.create(4326)); - } catch (Exception ex) { - noException = 0; - } - assertEquals(noException, 1); - assertNotNull(intersectGeom); - assertTrue(intersectGeom.getType() == Type.Point); - - Point ip = (Point) intersectGeom; - assertEquals(ip.getX(), -116, 0.1E7); - assertEquals(ip.getY(), 20, 0.1E7); - } - - @Test - public void testPointAndPolyline2() { - Point basePl = new Point(-115, 20); - Polyline compPl = new Polyline(); - compPl.startPath(new Point(-116, 20)); - compPl.lineTo(new Point(-131, 10)); - compPl.lineTo(new Point(-121, 50)); - - int noException = 1; // no exception - Geometry intersectGeom = null; - try { - intersectGeom = GeometryEngine.intersect(basePl, compPl, - SpatialReference.create(4326)); - } catch (Exception ex) { - noException = 0; - } - assertEquals(noException, 1); - assertTrue(intersectGeom.isEmpty()); - } - - @Test - public void testPointAndPolygon1() { - Point basePl = new Point(-116, 20); - Polygon compPl = new Polygon(); - compPl.startPath(new Point(-116, 20)); - compPl.lineTo(new Point(-131, 10)); - compPl.lineTo(new Point(-121, 50)); - - int noException = 1; // no exception - Geometry intersectGeom = null; - try { - intersectGeom = GeometryEngine.intersect(basePl, compPl, - SpatialReference.create(4326)); - - } catch (Exception ex) { - noException = 0; - } - assertEquals(noException, 1); - assertNotNull(intersectGeom); - assertTrue(intersectGeom.getType() == Type.Point); - - Point ip = (Point) intersectGeom; - assertEquals(ip.getX(), -116, 0.1E7); - assertEquals(ip.getY(), 20, 0.1E7); - - try { - MultiPoint mp = new MultiPoint(); - mp.add(basePl); - intersectGeom = GeometryEngine.intersect(mp, compPl, SpatialReference.create(4326)); - } catch (Exception ex) { - noException = 0; - } - assertEquals(noException, 1); - assertNotNull(intersectGeom); - assertTrue(intersectGeom.getType() == Type.MultiPoint); - MultiPoint mpOut = (MultiPoint) intersectGeom; - assertTrue(mpOut.getPointCount() == 1); - assertEquals(mpOut.getPoint(0).getX(), -116, 0.1E7); - assertEquals(mpOut.getPoint(0).getY(), 20, 0.1E7); - } - - @Test - public void testPointAndPolygon2() { - Point basePl = new Point(-115, 20); - Polygon compPl = new Polygon(); - compPl.startPath(new Point(-116, 20)); - compPl.lineTo(new Point(-131, 10)); - compPl.lineTo(new Point(-121, 50)); - - int noException = 1; // no exception - Geometry intersectGeom = null; - try { - intersectGeom = GeometryEngine.intersect(basePl, compPl, - SpatialReference.create(4326)); - - } catch (Exception ex) { - noException = 0; - } - assertEquals(noException, 1); - assertTrue(intersectGeom.isEmpty()); - } - - @Test - public void testPointAndPolygon3() { - Point basePl = new Point(-121, 20); - Polygon compPl = new Polygon(); - compPl.startPath(new Point(-116, 20)); - compPl.lineTo(new Point(-131, 10)); - compPl.lineTo(new Point(-121, 50)); - - int noException = 1; // no exception - Geometry intersectGeom = null; - try { - intersectGeom = GeometryEngine.intersect(basePl, compPl, - SpatialReference.create(4326)); - - } catch (Exception ex) { - noException = 0; - } - assertEquals(noException, 1); - assertNotNull(intersectGeom); - assertTrue(intersectGeom.getType() == Type.Point); - - Point ip = (Point) intersectGeom; - assertEquals(ip.getX(), -121, 0.1E7); - assertEquals(ip.getY(), 20, 0.1E7); - } - - @Test - public void testPointAndPoint1() { - Point basePl = new Point(-116, 20); - Point compPl = new Point(-116, 20); - - int noException = 1; // no exception - Geometry intersectGeom = null; - try { - intersectGeom = GeometryEngine.intersect(basePl, compPl, - SpatialReference.create(4326)); - } catch (Exception ex) { - noException = 0; - } - assertEquals(noException, 1); - assertNotNull(intersectGeom); - assertTrue(intersectGeom.getType() == Type.Point); - - Point ip = (Point) intersectGeom; - assertEquals(ip.getX(), -116, 0.1E7); - assertEquals(ip.getY(), 20, 0.1E7); - } - - @Test - public void testPointAndPoint2() { - Point basePl = new Point(-115, 20); - Point compPl = new Point(-116, 20); - - int noException = 1; // no exception - Geometry intersectGeom = null; - try { - intersectGeom = GeometryEngine.intersect(basePl, compPl, - SpatialReference.create(4326)); - } catch (Exception ex) { - noException = 0; - } - assertEquals(noException, 1); - assertTrue(intersectGeom.isEmpty()); - } - - @Test - public void testPointAndMultiPoint1() { - Point basePl = new Point(-116, 20); - MultiPoint compPl = new MultiPoint(); - compPl.add(new Point(-116, 20)); - compPl.add(new Point(-118, 21)); - - int noException = 1; // no exception - Geometry intersectGeom = null; - try { - intersectGeom = GeometryEngine.intersect(basePl, compPl, - SpatialReference.create(4326)); - } catch (Exception ex) { - noException = 0; - } - assertEquals(noException, 1); - assertNotNull(intersectGeom); - assertTrue(intersectGeom.getType() == Type.Point); - - Point ip = (Point) intersectGeom; - assertEquals(ip.getX(), -116, 0.1E7); - assertEquals(ip.getY(), 20, 0.1E7); - } - - @Test - public void testPointAndMultiPoint2() { - Point basePl = new Point(-115, 20); - - MultiPoint compPl = new MultiPoint(); - compPl.add(new Point(-116, 20)); - compPl.add(new Point(-117, 21)); - compPl.add(new Point(-118, 20)); - compPl.add(new Point(-119, 21)); - - int noException = 1; // no exception - Geometry intersectGeom = null; - - try { - intersectGeom = GeometryEngine.intersect(basePl, compPl, - SpatialReference.create(4326)); - } catch (Exception ex) { - noException = 0; - } - assertEquals(noException, 1); - assertTrue(intersectGeom.isEmpty()); - } - - @Test - public void testMultiPointAndMultiPoint1() { - MultiPoint basePl = new MultiPoint(); - basePl.add(new Point(-116, 20)); - basePl.add(new Point(-117, 20)); - - MultiPoint compPl = new MultiPoint(); - compPl.add(new Point(-116, 20)); - compPl.add(new Point(-118, 21)); - - int noException = 1; // no exception - Geometry intersectGeom = null; - try { - intersectGeom = GeometryEngine.intersect(basePl, compPl, - SpatialReference.create(4326)); - } catch (Exception ex) { - noException = 0; - } - assertEquals(noException, 1); - assertNotNull(intersectGeom); - assertTrue(intersectGeom.getType() == Type.MultiPoint); - - MultiPoint imp = (MultiPoint) intersectGeom; - assertEquals(imp.getCoordinates2D().length, 1); - assertEquals(imp.getCoordinates2D()[0].x, -116, 0.0); - assertEquals(imp.getCoordinates2D()[0].y, 20, 0.0); - } - - @Test - public void testMultiPointAndMultiPoint2() { - MultiPoint basePl = new MultiPoint(); - basePl.add(new Point(-116, 20)); - basePl.add(new Point(-118, 21)); - - MultiPoint compPl = new MultiPoint(); - compPl.add(new Point(-116, 20)); - compPl.add(new Point(-118, 21)); - - int noException = 1; // no exception - Geometry intersectGeom = null; - try { - intersectGeom = GeometryEngine.intersect(basePl, compPl, - SpatialReference.create(4326)); - } catch (Exception ex) { - noException = 0; - } - - assertEquals(noException, 1); - assertNotNull(intersectGeom); - assertTrue(intersectGeom.getType() == Type.MultiPoint); - - MultiPoint ip = (MultiPoint) intersectGeom; - assertEquals(ip.getPoint(0).getX(), -116, 0.1E7); - assertEquals(ip.getPoint(0).getY(), 20, 0.1E7); - assertEquals(ip.getPoint(0).getX(), -118, 0.1E7); - assertEquals(ip.getPoint(0).getY(), 21, 0.1E7); - } - - @Test - public void testMultiPointAndMultiPoint3() { - MultiPoint basePl = new MultiPoint(); - basePl.add(new Point(-116, 21)); - basePl.add(new Point(-117, 20)); - - MultiPoint compPl = new MultiPoint(); - compPl.add(new Point(-116, 20)); - compPl.add(new Point(-117, 21)); - compPl.add(new Point(-118, 20)); - compPl.add(new Point(-119, 21)); - - int noException = 1; // no exception - Geometry intersectGeom = null; - try { - intersectGeom = GeometryEngine.intersect(basePl, compPl, - SpatialReference.create(4326)); - } catch (Exception ex) { - noException = 0; - } - assertEquals(noException, 1); - assertTrue(intersectGeom.isEmpty()); - } + @Override + protected void setUp() throws Exception { + super.setUp(); + } + + @Override + protected void tearDown() throws Exception { + super.tearDown(); + } + + @Test + /** + * Intersect + * throw InvalidShapeException when applying between polyline and polygon + * + * */ + public void testIntersectBetweenPolylineAndPolygon() { + Polyline basePl = new Polyline(); + basePl.startPath(new Point(-117, 20)); + basePl.lineTo(new Point(-117, 10)); + basePl.lineTo(new Point(-130, 10)); + basePl.lineTo(new Point(-130, 20)); + basePl.lineTo(new Point(-117, 20)); + + Polygon compPl = new Polygon(); + compPl.startPath(-116, 20); + compPl.lineTo(-131, 10); + compPl.lineTo(-121, 50); + + Geometry intersectGeom = null; + + @SuppressWarnings("unused") + int noException = 1; // no exception + try { + intersectGeom = GeometryEngine.intersect(basePl, compPl, + SpatialReference.create(4326)); + + } catch (Exception ex) { + noException = 0; + } + assertNotNull(intersectGeom); + + // Geometry[] geometries = new Geometry[1]; + // geometries[0] = basePl; + // BorgGeometryUtils.getIntersectFromRestWS(geometries, compPl, 4326); + } + + @Test + public void testIntersectBetweenPolylines() { + Polyline basePl = new Polyline(); + basePl.startPath(new Point(-117, 20)); + basePl.lineTo(new Point(-130, 10)); + basePl.lineTo(new Point(-120, 50)); + + Polyline compPl = new Polyline(); + compPl.startPath(new Point(-116, 20)); + compPl.lineTo(new Point(-131, 10)); + compPl.lineTo(new Point(-121, 50)); + + int noException = 1; // no exception + try { + @SuppressWarnings("unused") + Geometry intersectGeom = GeometryEngine.intersect(basePl, compPl, + SpatialReference.create(4326)); + + } catch (Exception ex) { + noException = 0; + } + assertEquals(noException, 1); + } + + @Test + public void testPointAndPolyline1() { + Point basePl = new Point(-116, 20); + + Polyline compPl = new Polyline(); + compPl.startPath(new Point(-116, 20)); + compPl.lineTo(new Point(-131, 10)); + compPl.lineTo(new Point(-121, 50)); + + int noException = 1; // no exception + Geometry intersectGeom = null; + try { + intersectGeom = GeometryEngine.intersect(basePl, compPl, + SpatialReference.create(4326)); + } catch (Exception ex) { + noException = 0; + } + assertEquals(noException, 1); + assertNotNull(intersectGeom); + assertTrue(intersectGeom.getType() == Type.Point); + + Point ip = (Point) intersectGeom; + assertEquals(ip.getX(), -116, 0.1E7); + assertEquals(ip.getY(), 20, 0.1E7); + } + + @Test + public void testPointAndPolyline2() { + Point basePl = new Point(-115, 20); + Polyline compPl = new Polyline(); + compPl.startPath(new Point(-116, 20)); + compPl.lineTo(new Point(-131, 10)); + compPl.lineTo(new Point(-121, 50)); + + int noException = 1; // no exception + Geometry intersectGeom = null; + try { + intersectGeom = GeometryEngine.intersect(basePl, compPl, + SpatialReference.create(4326)); + } catch (Exception ex) { + noException = 0; + } + assertEquals(noException, 1); + assertTrue(intersectGeom.isEmpty()); + } + + @Test + public void testPointAndPolygon1() { + Point basePl = new Point(-116, 20); + Polygon compPl = new Polygon(); + compPl.startPath(new Point(-116, 20)); + compPl.lineTo(new Point(-131, 10)); + compPl.lineTo(new Point(-121, 50)); + + int noException = 1; // no exception + Geometry intersectGeom = null; + try { + intersectGeom = GeometryEngine.intersect(basePl, compPl, + SpatialReference.create(4326)); + + } catch (Exception ex) { + noException = 0; + } + assertEquals(noException, 1); + assertNotNull(intersectGeom); + assertTrue(intersectGeom.getType() == Type.Point); + + Point ip = (Point) intersectGeom; + assertEquals(ip.getX(), -116, 0.1E7); + assertEquals(ip.getY(), 20, 0.1E7); + + try { + MultiPoint mp = new MultiPoint(); + mp.add(basePl); + intersectGeom = GeometryEngine.intersect(mp, compPl, SpatialReference.create(4326)); + } catch (Exception ex) { + noException = 0; + } + assertEquals(noException, 1); + assertNotNull(intersectGeom); + assertTrue(intersectGeom.getType() == Type.MultiPoint); + MultiPoint mpOut = (MultiPoint) intersectGeom; + assertTrue(mpOut.getPointCount() == 1); + assertEquals(mpOut.getPoint(0).getX(), -116, 0.1E7); + assertEquals(mpOut.getPoint(0).getY(), 20, 0.1E7); + } + + @Test + public void testPointAndPolygon2() { + Point basePl = new Point(-115, 20); + Polygon compPl = new Polygon(); + compPl.startPath(new Point(-116, 20)); + compPl.lineTo(new Point(-131, 10)); + compPl.lineTo(new Point(-121, 50)); + + int noException = 1; // no exception + Geometry intersectGeom = null; + try { + intersectGeom = GeometryEngine.intersect(basePl, compPl, + SpatialReference.create(4326)); + + } catch (Exception ex) { + noException = 0; + } + assertEquals(noException, 1); + assertTrue(intersectGeom.isEmpty()); + } + + @Test + public void testPointAndPolygon3() { + Point basePl = new Point(-121, 20); + Polygon compPl = new Polygon(); + compPl.startPath(new Point(-116, 20)); + compPl.lineTo(new Point(-131, 10)); + compPl.lineTo(new Point(-121, 50)); + + int noException = 1; // no exception + Geometry intersectGeom = null; + try { + intersectGeom = GeometryEngine.intersect(basePl, compPl, + SpatialReference.create(4326)); + + } catch (Exception ex) { + noException = 0; + } + assertEquals(noException, 1); + assertNotNull(intersectGeom); + assertTrue(intersectGeom.getType() == Type.Point); + + Point ip = (Point) intersectGeom; + assertEquals(ip.getX(), -121, 0.1E7); + assertEquals(ip.getY(), 20, 0.1E7); + } + + @Test + public void testPointAndPoint1() { + Point basePl = new Point(-116, 20); + Point compPl = new Point(-116, 20); + + int noException = 1; // no exception + Geometry intersectGeom = null; + try { + intersectGeom = GeometryEngine.intersect(basePl, compPl, + SpatialReference.create(4326)); + } catch (Exception ex) { + noException = 0; + } + assertEquals(noException, 1); + assertNotNull(intersectGeom); + assertTrue(intersectGeom.getType() == Type.Point); + + Point ip = (Point) intersectGeom; + assertEquals(ip.getX(), -116, 0.1E7); + assertEquals(ip.getY(), 20, 0.1E7); + } + + @Test + public void testPointAndPoint2() { + Point basePl = new Point(-115, 20); + Point compPl = new Point(-116, 20); + + int noException = 1; // no exception + Geometry intersectGeom = null; + try { + intersectGeom = GeometryEngine.intersect(basePl, compPl, + SpatialReference.create(4326)); + } catch (Exception ex) { + noException = 0; + } + assertEquals(noException, 1); + assertTrue(intersectGeom.isEmpty()); + } + + @Test + public void testPointAndMultiPoint1() { + Point basePl = new Point(-116, 20); + MultiPoint compPl = new MultiPoint(); + compPl.add(new Point(-116, 20)); + compPl.add(new Point(-118, 21)); + + int noException = 1; // no exception + Geometry intersectGeom = null; + try { + intersectGeom = GeometryEngine.intersect(basePl, compPl, + SpatialReference.create(4326)); + } catch (Exception ex) { + noException = 0; + } + assertEquals(noException, 1); + assertNotNull(intersectGeom); + assertTrue(intersectGeom.getType() == Type.Point); + + Point ip = (Point) intersectGeom; + assertEquals(ip.getX(), -116, 0.1E7); + assertEquals(ip.getY(), 20, 0.1E7); + } + + @Test + public void testPointAndMultiPoint2() { + Point basePl = new Point(-115, 20); + + MultiPoint compPl = new MultiPoint(); + compPl.add(new Point(-116, 20)); + compPl.add(new Point(-117, 21)); + compPl.add(new Point(-118, 20)); + compPl.add(new Point(-119, 21)); + + int noException = 1; // no exception + Geometry intersectGeom = null; + + try { + intersectGeom = GeometryEngine.intersect(basePl, compPl, + SpatialReference.create(4326)); + } catch (Exception ex) { + noException = 0; + } + assertEquals(noException, 1); + assertTrue(intersectGeom.isEmpty()); + } + + @Test + public void testMultiPointAndMultiPoint1() { + MultiPoint basePl = new MultiPoint(); + basePl.add(new Point(-116, 20)); + basePl.add(new Point(-117, 20)); + + MultiPoint compPl = new MultiPoint(); + compPl.add(new Point(-116, 20)); + compPl.add(new Point(-118, 21)); + + int noException = 1; // no exception + Geometry intersectGeom = null; + try { + intersectGeom = GeometryEngine.intersect(basePl, compPl, + SpatialReference.create(4326)); + } catch (Exception ex) { + noException = 0; + } + assertEquals(noException, 1); + assertNotNull(intersectGeom); + assertTrue(intersectGeom.getType() == Type.MultiPoint); + + MultiPoint imp = (MultiPoint) intersectGeom; + assertEquals(imp.getCoordinates2D().length, 1); + assertEquals(imp.getCoordinates2D()[0].x, -116, 0.0); + assertEquals(imp.getCoordinates2D()[0].y, 20, 0.0); + } + + @Test + public void testMultiPointAndMultiPoint2() { + MultiPoint basePl = new MultiPoint(); + basePl.add(new Point(-116, 20)); + basePl.add(new Point(-118, 21)); + + MultiPoint compPl = new MultiPoint(); + compPl.add(new Point(-116, 20)); + compPl.add(new Point(-118, 21)); + + int noException = 1; // no exception + Geometry intersectGeom = null; + try { + intersectGeom = GeometryEngine.intersect(basePl, compPl, + SpatialReference.create(4326)); + } catch (Exception ex) { + noException = 0; + } + + assertEquals(noException, 1); + assertNotNull(intersectGeom); + assertTrue(intersectGeom.getType() == Type.MultiPoint); + + MultiPoint ip = (MultiPoint) intersectGeom; + assertEquals(ip.getPoint(0).getX(), -116, 0.1E7); + assertEquals(ip.getPoint(0).getY(), 20, 0.1E7); + assertEquals(ip.getPoint(0).getX(), -118, 0.1E7); + assertEquals(ip.getPoint(0).getY(), 21, 0.1E7); + } + + @Test + public void testMultiPointAndMultiPoint3() { + MultiPoint basePl = new MultiPoint(); + basePl.add(new Point(-116, 21)); + basePl.add(new Point(-117, 20)); + + MultiPoint compPl = new MultiPoint(); + compPl.add(new Point(-116, 20)); + compPl.add(new Point(-117, 21)); + compPl.add(new Point(-118, 20)); + compPl.add(new Point(-119, 21)); + + int noException = 1; // no exception + Geometry intersectGeom = null; + try { + intersectGeom = GeometryEngine.intersect(basePl, compPl, + SpatialReference.create(4326)); + } catch (Exception ex) { + noException = 0; + } + assertEquals(noException, 1); + assertTrue(intersectGeom.isEmpty()); + } } diff --git a/src/test/java/com/esri/core/geometry/TestIntersection.java b/src/test/java/com/esri/core/geometry/TestIntersection.java index 3d79354e..b9547383 100644 --- a/src/test/java/com/esri/core/geometry/TestIntersection.java +++ b/src/test/java/com/esri/core/geometry/TestIntersection.java @@ -30,1042 +30,1037 @@ //import java.util.Random; public class TestIntersection extends TestCase { - static OperatorFactoryLocal projEnv = OperatorFactoryLocal.getInstance(); - static int codeIn = 26910;// NAD_1983_UTM_Zone_10N : GEOGRAPHIC 6269 - static int codeOut = 32610;// WGS_1984_UTM_Zone_10N; : GEOGRAPHIC 4326 - static SpatialReference inputSR; - static SpatialReference outputSR; - - @Override - protected void setUp() throws Exception { - super.setUp(); - projEnv = OperatorFactoryLocal.getInstance(); - inputSR = SpatialReference.create(codeIn); - outputSR = SpatialReference.create(codeOut); - } - - @Override - protected void tearDown() throws Exception { - super.tearDown(); - } - - @Test - public void testIntersection1() { - // OperatorFactoryLocal projEnv = OperatorFactoryLocal.getInstance(); - // int codeIn = 26910;//NAD_1983_UTM_Zone_10N : GEOGRAPHIC 6269 - // int codeOut = 32610;//WGS_1984_UTM_Zone_10N; : GEOGRAPHIC 4326 - // int codeIn = SpatialReference::PCS_WGS_1984_UTM_10N; - // int codeOut = SpatialReference::PCS_WORLD_MOLLWEIDE; - // int codeOut = 102100; - inputSR = SpatialReference.create(codeIn); - assertTrue(inputSR.getID() == codeIn); - outputSR = SpatialReference.create(codeOut); - assertTrue(outputSR.getID() == codeOut); - - OperatorIntersection operatorIntersection = (OperatorIntersection) projEnv - .getOperator(Operator.Type.Intersection); - - Polygon poly1 = new Polygon(); - Envelope env1 = new Envelope(855277, 3892059, 855277 + 100, - 3892059 + 100); - // Envelope env1 = new Envelope(-1000000, -1000000, 1000000, 1000000); - // env1.SetCoords(-8552770, -3892059, 855277 + 100, 3892059 + 100); - poly1.addEnvelope(env1, false); - - Polygon poly2 = new Polygon(); - Envelope env2 = new Envelope(855277 + 1, 3892059 + 1, 855277 + 30, - 3892059 + 20); - poly2.addEnvelope(env2, false); - - GeometryCursor cursor1 = new SimpleGeometryCursor(poly1); - GeometryCursor cursor2 = new SimpleGeometryCursor(poly2); - - GeometryCursor outputGeoms = operatorIntersection.execute(cursor1, - cursor2, inputSR, null); - Geometry geomr = outputGeoms.next(); - assertNotNull(geomr); - assertTrue(geomr.getType() == Geometry.Type.Polygon); - Polygon geom = (Polygon) geomr; - assertTrue(geom.getPointCount() == 4); - Point[] points = TestCommonMethods.pointsFromMultiPath(geom);// SPtrOfArrayOf(Point2D) - // pts = - // geom.get.getCoordinates2D(); - assertTrue(Math.abs(points[0].getX() - 855278.000000000) < 1e-7); - assertTrue(Math.abs(points[0].getY() - 3892060.0000000000) < 1e-7); - assertTrue(Math.abs(points[2].getX() - 855307.00000000093) < 1e-7); - assertTrue(Math.abs(points[2].getY() - 3892079.0000000000) < 1e-7); - - geomr = operatorIntersection.execute(poly1, poly2, inputSR, null); - assertNotNull(geomr); - assertTrue(geomr.getType() == Geometry.Type.Polygon); - Polygon outputGeom = (Polygon) geomr; - - assertTrue(outputGeom.getPointCount() == 4); - points = TestCommonMethods.pointsFromMultiPath(outputGeom); - assertTrue(Math.abs(points[0].getX() - 855278.000000000) < 1e-7); - assertTrue(Math.abs(points[0].getY() - 3892060.0000000000) < 1e-7); - assertTrue(Math.abs(points[2].getX() - 855307.00000000093) < 1e-7); - assertTrue(Math.abs(points[2].getY() - 3892079.0000000000) < 1e-7); - } - - @Test - public void testSelfIntersecting() {// Test that we do not fail if there is - // self-intersection - // OperatorFactoryLocal projEnv = - // OperatorFactoryLocal.getInstance(); - OperatorIntersection operatorIntersection = (OperatorIntersection) projEnv - .getOperator(Operator.Type.Intersection); - SpatialReference sr = SpatialReference.create(4326); - Polygon poly1 = new Polygon(); - Envelope2D env1 = new Envelope2D(); - env1.setCoords(0, 0, 20, 30); - poly1.addEnvelope(env1, false); - Polygon poly2 = new Polygon(); - poly2.startPath(0, 0); - poly2.lineTo(10, 10); - poly2.lineTo(0, 10); - poly2.lineTo(10, 0); - @SuppressWarnings("unused") - Polygon res = (Polygon) (operatorIntersection.execute(poly1, poly2, sr, - null)); - // Operator_equals equals = - // (Operator_equals)projEnv.get_operator(Operator::equals); - // assertTrue(equals.execute(res, poly2, sr, NULL) == true); - } - - @Test - public void testMultipoint() { - Polygon poly1 = new Polygon(); - Envelope env1 = new Envelope(855277, 3892059, 855277 + 100, - 3892059 + 100); - - poly1.addEnvelope(env1, false); - MultiPoint multiPoint = new MultiPoint(); - multiPoint.add(855277 + 10, 3892059 + 10); - multiPoint.add(855277, 3892059); - multiPoint.add(855277 + 100, 3892059 + 100); - multiPoint.add(855277 + 100, 3892059 + 101); - multiPoint.add(855277 + 101, 3892059 + 100); - multiPoint.add(855277 + 101, 3892059 + 101); - OperatorIntersection operatorIntersection = (OperatorIntersection) projEnv - .getOperator(Operator.Type.Intersection); - - MultiPoint mpResult = (MultiPoint) operatorIntersection.execute(poly1, - multiPoint, inputSR, null); - assertTrue(mpResult.getPointCount() == 3); - assertTrue(mpResult.getPoint(0).getX() == 855277 + 10 - && mpResult.getPoint(0).getY() == 3892059 + 10); - assertTrue(mpResult.getPoint(1).getX() == 855277 - && mpResult.getPoint(1).getY() == 3892059); - assertTrue(mpResult.getPoint(2).getX() == 855277 + 100 - && mpResult.getPoint(2).getY() == 3892059 + 100); - - // Test intersection of Polygon with Envelope (calls Clip) - Polygon poly = new Polygon(); - poly.startPath(0, 0); - poly.lineTo(10, 10); - poly.lineTo(20, 0); - - env1.setXMin(0); - env1.setXMax(20); - env1.setYMin(5); - env1.setYMax(15); - - Envelope envelope1 = env1; - - Polygon clippedPoly = (Polygon) operatorIntersection.execute(poly, - envelope1, inputSR, null); - double area = clippedPoly.calculateArea2D(); - assertTrue(Math.abs(area - 25) < 0.00001); - - // Geometry res = GeometryEngine.difference(poly, envelope1, inputSR); - Envelope env2 = new Envelope(855277 + 1, 3892059 + 1, 855277 + 30, - 3892059 + 20); - env2.setXMin(5); - env2.setXMax(10); - env2.setYMin(0); - env2.setYMax(20); - - Envelope envelope2 = env2; - - Envelope clippedEnvelope = (Envelope) operatorIntersection.execute( - envelope1, envelope2, inputSR, null); - area = clippedEnvelope.calculateArea2D(); - assertTrue(Math.abs(area - 50) < 0.00001); - } - - @Test - public void testDifferenceOnPolyline() { - Polyline basePl = new Polyline(); - basePl.startPath(-117, 20); - basePl.lineTo(-130, 10); - basePl.lineTo(-120, 50); - - Polyline compPl = new Polyline(); - compPl.startPath(-116, 20); - compPl.lineTo(-131, 10); - compPl.lineTo(-121, 50); - - // OperatorFactoryLocal projEnv = OperatorFactoryLocal.getInstance(); - OperatorDifference op = (OperatorDifference) projEnv - .getOperator(Operator.Type.Difference); - Polyline diffGeom = (Polyline) (op.execute(basePl, compPl, - SpatialReference.create(4326), null)); - int pc = diffGeom.getPointCount(); - assertTrue(pc == 5); - } - - @Test - public void testDifferenceOnPolyline2() { - Polyline basePl = new Polyline(); - basePl.startPath(0, 0); - basePl.lineTo(10, 10); - basePl.lineTo(20, 20); - basePl.lineTo(10, 0); - basePl.lineTo(20, 10); - - Polyline compPl = new Polyline(); - compPl.startPath(5, 0); - compPl.lineTo(5, 10); - compPl.lineTo(0, 10); - compPl.lineTo(7.5, 2.5); - - // Operator_factory_local::SaveJSONToTextFileDbg("c:/temp/basePl.txt", - // *basePl, null); - // Operator_factory_local::SaveJSONToTextFileDbg("c:/temp/compPl.txt", - // *compPl, null); - // OperatorFactoryLocal projEnv = OperatorFactoryLocal.getInstance(); - OperatorDifference op = (OperatorDifference) projEnv - .getOperator(Operator.Type.Difference); - Polyline diffGeom = (Polyline) (op.execute(basePl, compPl, - SpatialReference.create(4326), null)); - // Operator_factory_local::SaveJSONToTextFileDbg("c:/temp/diffGeom.txt", - // *diffGeom, null); - int pathc = diffGeom.getPathCount(); - assertTrue(pathc == 1); - int pc = diffGeom.getPointCount(); - assertTrue(pc == 6); - - Polyline resPl = new Polyline(); - resPl.startPath(0, 0); - resPl.lineTo(5, 5); - resPl.lineTo(10, 10); - resPl.lineTo(20, 20); - resPl.lineTo(10, 0); - resPl.lineTo(20, 10); - // Operator_factory_local::SaveJSONToTextFileDbg("c:/temp/resPl.txt", - // *resPl, null); - assertTrue(resPl.equals(diffGeom)); - } - - @Test - public void testDifferencePointPolyline() { - Polyline basePl = new Polyline(); - basePl.startPath(0, 0); - basePl.lineTo(10, 10); - basePl.lineTo(20, 20); - basePl.lineTo(10, 0); - basePl.lineTo(20, 10); - - Point compPl = new Point(5, 5); - - // OperatorFactoryLocal projEnv = OperatorFactoryLocal.getInstance(); - OperatorDifference op = (OperatorDifference) projEnv - .getOperator(Operator.Type.Difference); - Polyline diffGeom = (Polyline) (op.execute(basePl, compPl, - SpatialReference.create(4326), null)); - int pathc = diffGeom.getPathCount(); - assertTrue(pathc == 1); - int pc = diffGeom.getPointCount(); - assertTrue(pc == 5); - - Polyline resPl = new Polyline(); - resPl.startPath(0, 0); - resPl.lineTo(10, 10); - resPl.lineTo(20, 20); - resPl.lineTo(10, 0); - resPl.lineTo(20, 10); - assertTrue(resPl.equals(diffGeom));// no change happens to the original - // polyline - } - - @Test - public void testIntersectionPolylinePolygon() { - { - Polygon polygon = new Polygon(); - polygon.startPath(0, 0); - polygon.lineTo(0, 10); - polygon.lineTo(20, 10); - polygon.lineTo(20, 0); - polygon.addAttribute(VertexDescription.Semantics.Z); - polygon.setAttribute(VertexDescription.Semantics.Z, 0, 0, 3); - polygon.setAttribute(VertexDescription.Semantics.Z, 3, 0, 3); - polygon.interpolateAttributes(0, 0, 3); - Polyline polyline = new Polyline(); - polyline.startPath(0, 10); - polyline.lineTo(5, 5); - polyline.lineTo(6, 4); - polyline.lineTo(7, -1); - polyline.addAttribute(VertexDescription.Semantics.Z); - polyline.setAttribute(VertexDescription.Semantics.Z, 0, 0, 5); - polyline.setAttribute(VertexDescription.Semantics.Z, 3, 0, 5); - polyline.interpolateAttributes(0, 0, 0, 3); - - // OperatorFactoryLocal projEnv = - // OperatorFactoryLocal.getInstance(); - OperatorIntersection operatorIntersection = (OperatorIntersection) projEnv - .getOperator(Operator.Type.Intersection); - Geometry geom = operatorIntersection.execute(polyline, polygon, - null, null); - assertTrue(!geom.isEmpty()); - Polyline poly = (Polyline) (geom); - for (int i = 0; i < poly.getPointCount(); i++) - assertTrue(poly.getAttributeAsDbl( - VertexDescription.Semantics.Z, i, 0) == 5); - - // std::shared_ptr jsonExport = - // (Operator_export_to_JSON>)Operator_factory_local::get_instance().get_operator(Operator::Operator_type::export_to_JSON); - // std::string str = jsonExport.execute(0, geom, null, null); - // OutputDebugStringA(str.c_str()); - // OutputDebugString(L"\n"); - // assertTrue(str=="{\"paths\":[[[0,10],[5,5],[6,4],[6.7999999999999998,4.4408922169635528e-016]]]}"); - } - - { - Polygon polygon = new Polygon(); - polygon.startPath(0, 0); - polygon.lineTo(0, 10); - polygon.lineTo(20, 10); - polygon.lineTo(20, 0); - polygon.addAttribute(VertexDescription.Semantics.Z); - polygon.setAttribute(VertexDescription.Semantics.Z, 0, 0, 3); - polygon.setAttribute(VertexDescription.Semantics.Z, 3, 0, 3); - polygon.interpolateAttributes(0, 0, 3); - Polyline polyline = new Polyline(); - polyline.startPath(0, 10); - polyline.lineTo(20, 0); - polyline.lineTo(5, 5); - polyline.addAttribute(VertexDescription.Semantics.Z); - polyline.setAttribute(VertexDescription.Semantics.Z, 0, 0, 5); - polyline.setAttribute(VertexDescription.Semantics.Z, 1, 0, 5); - polyline.setAttribute(VertexDescription.Semantics.Z, 2, 0, 5); - - // OperatorFactoryLocal projEnv = - // OperatorFactoryLocal.getInstance(); - OperatorIntersection operatorIntersection = (OperatorIntersection) projEnv - .getOperator(Operator.Type.Intersection); - Geometry geom = operatorIntersection.execute(polyline, polygon, - null, null); - assertTrue(!geom.isEmpty()); - Polyline poly = (Polyline) (geom); - for (int i = 0; i < poly.getPointCount(); i++) - assertTrue(poly.getAttributeAsDbl( - VertexDescription.Semantics.Z, i, 0) == 5); - - // Operator_export_to_JSON> jsonExport = - // (Operator_export_to_JSON>)Operator_factory_local::get_instance().get_operator(Operator::Operator_type::export_to_JSON); - // std::string str = jsonExport.execute(0, geom, null, null); - // OutputDebugStringA(str.c_str()); - // OutputDebugString(L"\n"); - // assertTrue(str=="{\"paths\":[[[0,10],[20,0],[5,5]]]}"); - } - - { - Polygon polygon = new Polygon(); - polygon.startPath(0, 0); - polygon.lineTo(0, 10); - polygon.lineTo(20, 10); - polygon.lineTo(20, 0); - polygon.addAttribute(VertexDescription.Semantics.Z); - polygon.setAttribute(VertexDescription.Semantics.Z, 0, 0, 3); - polygon.setAttribute(VertexDescription.Semantics.Z, 3, 0, 3); - polygon.interpolateAttributes(0, 0, 3); - Polyline polyline = new Polyline(); - polyline.startPath(0, 0); - polyline.lineTo(0, 10); - polyline.lineTo(20, 10); - polyline.addAttribute(VertexDescription.Semantics.Z); - polyline.setAttribute(VertexDescription.Semantics.Z, 0, 0, 5); - polyline.setAttribute(VertexDescription.Semantics.Z, 1, 0, 5); - polyline.setAttribute(VertexDescription.Semantics.Z, 2, 0, 5); - - // OperatorFactoryLocal projEnv = - // OperatorFactoryLocal.getInstance(); - OperatorIntersection operatorIntersection = (OperatorIntersection) projEnv - .getOperator(Operator.Type.Intersection); - Geometry geom = operatorIntersection.execute(polyline, polygon, - null, null); - assertTrue(!geom.isEmpty()); - Polyline poly = (Polyline) (geom); - for (int i = 0; i < poly.getPointCount(); i++) - assertTrue(poly.getAttributeAsDbl( - VertexDescription.Semantics.Z, i, 0) == 5); - - // Operator_export_to_JSON> jsonExport = - // (Operator_export_to_JSON>)Operator_factory_local::get_instance().get_operator(Operator::Operator_type::export_to_JSON); - // std::string str = jsonExport.execute(0, geom, null, null); - // OutputDebugStringA(str.c_str()); - // OutputDebugString(L"\n"); - // assertTrue(str=="{\"paths\":[[[0,0],[0,10],[20,10]]]}"); - } - - { - Polygon polygon = new Polygon(); - polygon.startPath(0, 0); - polygon.lineTo(0, 10); - polygon.lineTo(20, 10); - polygon.lineTo(20, 0); - Polyline polyline = new Polyline(); - polyline.startPath(3, -1); - polyline.lineTo(17, 1); - polyline.lineTo(10, 8); - polyline.lineTo(-1, 5); - polyline.addAttribute(VertexDescription.Semantics.Z); - polyline.setAttribute(VertexDescription.Semantics.Z, 0, 0, 5); - polyline.setAttribute(VertexDescription.Semantics.Z, 1, 0, 5); - polyline.setAttribute(VertexDescription.Semantics.Z, 2, 0, 5); - polyline.setAttribute(VertexDescription.Semantics.Z, 3, 0, 5); - - // OperatorFactoryLocal projEnv = - // OperatorFactoryLocal.getInstance(); - OperatorIntersection operatorIntersection = (OperatorIntersection) projEnv - .getOperator(Operator.Type.Intersection); - Geometry geom = operatorIntersection.execute(polyline, polygon, - null, null); - assertTrue(!geom.isEmpty()); - Polyline poly = (Polyline) geom; - for (int i = 0; i < poly.getPointCount(); i++) - assertTrue(poly.getAttributeAsDbl( - VertexDescription.Semantics.Z, i, 0) == 5); - - // Operator_export_to_JSON> jsonExport = - // (Operator_export_to_JSON>)Operator_factory_local::get_instance().get_operator(Operator::Operator_type::export_to_JSON); - // std::string str = jsonExport.execute(0, geom, null, null); - // OutputDebugStringA(str.c_str()); - // OutputDebugString(L"\n"); - // assertTrue(str=="{\"paths\":[[[10,0],[17,1],[10,8],[4.7377092701401439e-024,5.2727272727272734]]]}"); - } - - { - Polygon polygon = new Polygon(); - polygon.startPath(0, 0); - polygon.lineTo(0, 10); - polygon.lineTo(20, 10); - polygon.lineTo(20, 0); - Polyline polyline = new Polyline(); - polyline.startPath(0, 15); - polyline.lineTo(3, -1); - polyline.lineTo(17, 1); - polyline.lineTo(10, 8); - polyline.lineTo(-1, 5); - polyline.addAttribute(VertexDescription.Semantics.Z); - polyline.setAttribute(VertexDescription.Semantics.Z, 0, 0, 5); - polyline.setAttribute(VertexDescription.Semantics.Z, 4, 0, 5); - polyline.interpolateAttributes(0, 0, 0, 4); - OperatorIntersection operatorIntersection = (OperatorIntersection) projEnv - .getOperator(Operator.Type.Intersection); - Geometry geom = operatorIntersection.execute(polyline, polygon, - null, null); - assertTrue(!geom.isEmpty()); - Polyline poly = (Polyline) geom; - for (int i = 0; i < poly.getPointCount(); i++) - assertTrue(poly.getAttributeAsDbl( - VertexDescription.Semantics.Z, i, 0) == 5); - - // Operator_export_to_JSON> jsonExport = - // (Operator_export_to_JSON>)Operator_factory_local::get_instance().get_operator(Operator::Operator_type::export_to_JSON); - // std::string str = jsonExport.execute(0, geom, null, null); - // OutputDebugStringA(str.c_str()); - // OutputDebugString(L"\n"); - // assertTrue(str=="{\"paths\":[[[0.9375,10],[2.8125,9.476226333847234e-024]],[[10,0],[17,1],[10,8],[4.7377092701401439e-024,5.2727272727272734]]]}"); - } - - { - Polygon polygon = new Polygon(); - polygon.startPath(0, 0); - polygon.lineTo(0, 10); - polygon.lineTo(20, 10); - polygon.lineTo(20, 0); - Polyline polyline = new Polyline(); - polyline.startPath(5, 5); - polyline.lineTo(1, 1); - polyline.lineTo(-1, 1); - polyline.lineTo(-1, 10); - polyline.lineTo(0, 10); - polyline.lineTo(6, 6); - polyline.addAttribute(VertexDescription.Semantics.Z); - polyline.setAttribute(VertexDescription.Semantics.Z, 0, 0, 5); - polyline.setAttribute(VertexDescription.Semantics.Z, 5, 0, 5); - polyline.interpolateAttributes(0, 0, 0, 5); - - // OperatorFactoryLocal projEnv = - // OperatorFactoryLocal.getInstance(); - OperatorIntersection operatorIntersection = (OperatorIntersection) projEnv - .getOperator(Operator.Type.Intersection); - Geometry geom = operatorIntersection.execute(polyline, polygon, - null, null); - assertTrue(!geom.isEmpty()); - Polyline poly = (Polyline) geom; - for (int i = 0; i < poly.getPointCount(); i++) - assertTrue(poly.getAttributeAsDbl( - VertexDescription.Semantics.Z, i, 0) == 5); - - // Operator_export_to_JSON> jsonExport = - // (Operator_export_to_JSON>)Operator_factory_local::get_instance().get_operator(Operator::Operator_type::export_to_JSON); - // std::string str = jsonExport.execute(0, geom, null, null); - // OutputDebugStringA(str.c_str()); - // OutputDebugString(L"\n"); - // assertTrue(str=="{\"paths\":[[[5,5],[1,1],[4.738113166923617e-023,1]],[[0,10],[6,6]]]}"); - } - - { - Polygon polygon = new Polygon(); - polygon.startPath(0, 0); - polygon.lineTo(0, 10); - polygon.lineTo(20, 10); - polygon.lineTo(20, 0); - Polyline polyline = new Polyline(); - polyline.startPath(0, 15); - polyline.lineTo(3, -1); - polyline.lineTo(17, 1); - polyline.lineTo(10, 8); - polyline.lineTo(-1, 5); - polyline.startPath(19, 15); - polyline.lineTo(29, 9); - polyline.startPath(19, 15); - polyline.lineTo(29, 9); - polyline.startPath(5, 5); - polyline.lineTo(1, 1); - polyline.lineTo(-1, 1); - polyline.lineTo(-1, 10); - polyline.lineTo(0, 10); - polyline.lineTo(6, 6); - polyline.addAttribute(VertexDescription.Semantics.Z); - polyline.setAttribute(VertexDescription.Semantics.Z, 0, 0, 5); - polyline.setAttribute(VertexDescription.Semantics.Z, 14, 0, 5); - polyline.interpolateAttributes(0, 0, 3, 5); - - // OperatorFactoryLocal projEnv = - // OperatorFactoryLocal.getInstance(); - OperatorIntersection operatorIntersection = (OperatorIntersection) projEnv - .getOperator(Operator.Type.Intersection); - Geometry geom = operatorIntersection.execute(polyline, polygon, - null, null); - assertTrue(!geom.isEmpty()); - Polyline poly = (Polyline) geom; - for (int i = 0; i < poly.getPointCount(); i++) - assertTrue(poly.getAttributeAsDbl( - VertexDescription.Semantics.Z, i, 0) == 5); - - // Operator_export_to_JSON> jsonExport = - // (Operator_export_to_JSON>)Operator_factory_local::get_instance().get_operator(Operator::Operator_type::export_to_JSON); - // std::string str = jsonExport.execute(0, geom, null, null); - // OutputDebugStringA(str.c_str()); - // OutputDebugString(L"\n"); - // assertTrue(str=="{\"paths\":[[[0.9375,10],[2.8125,9.476226333847234e-024]],[[10,0],[17,1],[10,8],[4.7377092701401439e-024,5.2727272727272734]],[[5,5],[1,1],[4.738113166923617e-023,1]],[[0,10],[6,6]]]}"); - } - } - - @Test - public void testMultiPointPolyline() { - Polyline polyline = new Polyline(); - polyline.startPath(0, 0); - polyline.lineTo(0, 10); - polyline.lineTo(20, 10); - polyline.lineTo(20, 0); - MultiPoint mp = new MultiPoint(); - mp.add(0, 10, 7); - mp.add(0, 5, 7); - mp.add(1, 5, 7); - // OperatorFactoryLocal projEnv = OperatorFactoryLocal.getInstance(); - OperatorIntersection operatorIntersection = (OperatorIntersection) projEnv - .getOperator(Operator.Type.Intersection); - OperatorDifference operatorDifference = (OperatorDifference) projEnv - .getOperator(Operator.Type.Difference); - - {// intersect - Geometry geom = operatorIntersection.execute(polyline, mp, null, - null); - MultiPoint res = (MultiPoint) geom; - assertTrue(res.getPointCount() == 2); - Point2D pt_1 = res.getXY(0); - Point2D pt_2 = res.getXY(1); - assertTrue(Point2D.distance(pt_1, new Point2D(0, 10)) < 1e-10 - && Point2D.distance(pt_2, new Point2D(0, 5)) < 1e-10 - || Point2D.distance(pt_2, new Point2D(0, 10)) < 1e-10 - && Point2D.distance(pt_1, new Point2D(0, 5)) < 1e-10); - - assertTrue(res.getAttributeAsDbl(VertexDescription.Semantics.Z, 0, - 0) == 7); - assertTrue(res.getAttributeAsDbl(VertexDescription.Semantics.Z, 1, - 0) == 7); - } - - {// difference - Geometry geom = operatorDifference - .execute(polyline, mp, null, null); - // assertTrue(geom.getGeometryType() == - // Geometry.GeometryType.Polyline); - Polyline res = (Polyline) geom; - assertTrue(res.getPointCount() == 4); - } - {// difference - Geometry geom = operatorDifference - .execute(mp, polyline, null, null); - // assertTrue(geom.getType() == Geometry.GeometryType.MultiPoint); - MultiPoint res = (MultiPoint) geom; - assertTrue(res.getPointCount() == 1); - Point2D pt_1 = res.getXY(0); - assertTrue(Point2D.distance(pt_1, new Point2D(1, 5)) < 1e-10); - } - {// difference (subtract empty) - Geometry geom = operatorDifference.execute(mp, new Polyline(), - null, null); - // assertTrue(geom.getGeometryType() == - // Geometry.GeometryType.MultiPoint); - MultiPoint res = (MultiPoint) geom; - assertTrue(res.getPointCount() == 3); - Point2D pt_1 = res.getXY(0); - assertTrue(Point2D.distance(pt_1, new Point2D(0, 10)) < 1e-10); - } - - } - - @Test - public void testPointPolyline() { - Polyline polyline = new Polyline(); - polyline.startPath(0, 0); - polyline.lineTo(0, 10); - polyline.lineTo(20, 10); - polyline.lineTo(20, 0); - Point p_1 = new Point(0, 5, 7); - Point p_2 = new Point(0, 10, 7); - Point p3 = new Point(1, 5, 7); - // OperatorFactoryLocal projEnv = OperatorFactoryLocal.getInstance(); - OperatorIntersection operatorIntersection = (OperatorIntersection) projEnv - .getOperator(Operator.Type.Intersection); - OperatorDifference operatorDiff = (OperatorDifference) projEnv - .getOperator(Operator.Type.Difference); - OperatorUnion operatorUnion = (OperatorUnion) projEnv - .getOperator(Operator.Type.Union); - OperatorSymmetricDifference operatorSymDiff = (OperatorSymmetricDifference) projEnv - .getOperator(Operator.Type.SymmetricDifference); - - {// intersect case1 - Geometry geom = operatorIntersection.execute(polyline, p_1, null, - null); - // assertTrue(geom.getType() == Geometry::enum_point); - Point res = (Point) geom; - Point2D pt_1 = res.getXY(); - assertTrue(Point2D.distance(pt_1, new Point2D(0, 5)) < 1e-10); - assertTrue(res.getAttributeAsDbl(VertexDescription.Semantics.Z, 0) == 7); - } - {// intersect case2 - Geometry geom = operatorIntersection.execute(polyline, p_2, null, - null); - // assertTrue(geom.getType() == Geometry::enum_point); - Point res = (Point) geom; - Point2D pt_1 = res.getXY(); - assertTrue(Point2D.distance(pt_1, new Point2D(0, 10)) < 1e-10); - assertTrue(res.getAttributeAsDbl(VertexDescription.Semantics.Z, 0) == 7); - } - {// intersect case3 - Geometry geom = operatorIntersection.execute(polyline, p3, null, - null); - // assertTrue(geom.getType() == Geometry::enum_point); - assertTrue(geom.isEmpty()); - assertTrue(geom.hasAttribute(VertexDescription.Semantics.Z)); - } - - {// difference case1 - Geometry geom = operatorDiff.execute(polyline, p_1, null, null); - // assertTrue(geom.getType() == Geometry.GeometryType.Polyline); - Polyline res = (Polyline) geom; - assertTrue(res.getPointCount() == 4); - } - {// difference case2 - Geometry geom = operatorDiff.execute(p_1, polyline, null, null); - // assertTrue(geom.getType() == Geometry::enum_point); - Point res = (Point) geom; - assertTrue(res.isEmpty()); - } - {// difference case3 - Geometry geom = operatorDiff.execute(p_2, polyline, null, null); - // assertTrue(geom.getType() == Geometry::enum_point); - Point res = (Point) geom; - assertTrue(res.isEmpty()); - } - {// difference case4 - Geometry geom = operatorDiff.execute(p3, polyline, null, null); - // assertTrue(geom.getType() == Geometry::enum_point); - Point res = (Point) geom; - Point2D pt_1 = res.getXY(); - assertTrue(Point2D.distance(pt_1, new Point2D(1, 5)) < 1e-10); - } - - {// union case1 - Geometry geom = operatorUnion.execute(p_1, polyline, null, null); - // assertTrue(geom.getType() == Geometry.GeometryType.Polyline); - Polyline res = (Polyline) geom; - assertTrue(!res.isEmpty()); - } - {// union case2 - Geometry geom = operatorUnion.execute(polyline, p_1, null, null); - // assertTrue(geom.getType() == Geometry.GeometryType.Polyline); - Polyline res = (Polyline) geom; - assertTrue(!res.isEmpty()); - } - - {// symmetric difference case1 - Geometry geom = operatorSymDiff.execute(polyline, p_1, null, null); - assertTrue(geom.getType().value() == Geometry.GeometryType.Polyline); - Polyline res = (Polyline) (geom); - assertTrue(!res.isEmpty()); - } - {// symmetric difference case2 - Geometry geom = operatorSymDiff.execute(p_1, polyline, null, null); - assertTrue(geom.getType().value() == Geometry.GeometryType.Polyline); - Polyline res = (Polyline) (geom); - assertTrue(!res.isEmpty()); - } - } - - @Test - public void testPolylinePolylineIntersectionExtended() { - {// crossing intersection - Polyline basePl = new Polyline(); - basePl.startPath(0, 10); - basePl.lineTo(100, 10); - - Polyline compPl = new Polyline(); - compPl.startPath(50, 0); - compPl.lineTo(50, 100); - - OperatorIntersection op = (OperatorIntersection) projEnv - .getOperator(Operator.Type.Intersection); - GeometryCursor result_cursor = op.execute(new SimpleGeometryCursor( - basePl), new SimpleGeometryCursor(compPl), SpatialReference - .create(4326), null, 3); - - // dimension is 3, means it has to return a point and a polyline - Geometry geom1 = result_cursor.next(); - assertTrue(geom1 != null); - assertTrue(geom1.getDimension() == 0); - assertTrue(geom1.getType().value() == Geometry.GeometryType.MultiPoint); - assertTrue(((MultiPoint) geom1).getPointCount() == 1); - - Geometry geom2 = result_cursor.next(); - assertTrue(geom2 != null); - assertTrue(geom2.getDimension() == 1); - assertTrue(geom2.getType().value() == Geometry.GeometryType.Polyline); - assertTrue(((Polyline) geom2).getPointCount() == 0); - - Geometry geom3 = result_cursor.next(); - assertTrue(geom3 == null); - } - - {// crossing + overlapping intersection - Polyline basePl = new Polyline(); - basePl.startPath(0, 10); - basePl.lineTo(100, 10); - - Polyline compPl = new Polyline(); - compPl.startPath(50, 0); - compPl.lineTo(50, 100); - compPl.lineTo(70, 10); - compPl.lineTo(100, 10); - - OperatorIntersection op = (OperatorIntersection) projEnv - .getOperator(Operator.Type.Intersection); - GeometryCursor result_cursor = op.execute(new SimpleGeometryCursor( - basePl), new SimpleGeometryCursor(compPl), SpatialReference - .create(4326), null, 3); - - // dimension is 3, means it has to return a point and a polyline - Geometry geom1 = result_cursor.next(); - assertTrue(geom1 != null); - assertTrue(geom1.getDimension() == 0); - assertTrue(geom1.getType().value() == Geometry.GeometryType.MultiPoint); - assertTrue(((MultiPoint) geom1).getPointCount() == 1); - - Geometry geom2 = result_cursor.next(); - assertTrue(geom2 != null); - assertTrue(geom2.getDimension() == 1); - assertTrue(geom2.getType().value() == Geometry.GeometryType.Polyline); - assertTrue(((Polyline) geom2).getPathCount() == 1); - assertTrue(((Polyline) geom2).getPointCount() == 2); - - Geometry geom3 = result_cursor.next(); - assertTrue(geom3 == null); - } - } - - @Test - public void testPolygonPolygonIntersectionExtended() { - {// crossing intersection - Polygon basePl = new Polygon(); - basePl.startPath(0, 0); - basePl.lineTo(100, 0); - basePl.lineTo(100, 100); - basePl.lineTo(0, 100); - - Polygon compPl = new Polygon(); - compPl.startPath(100, 100); - compPl.lineTo(200, 100); - compPl.lineTo(200, 200); - compPl.lineTo(100, 200); - - OperatorIntersection op = (OperatorIntersection) projEnv - .getOperator(Operator.Type.Intersection); - GeometryCursor result_cursor = op.execute(new SimpleGeometryCursor( - basePl), new SimpleGeometryCursor(compPl), SpatialReference - .create(4326), null, 7); - - Geometry geom1 = result_cursor.next(); - assertTrue(geom1 != null); - assertTrue(geom1.getDimension() == 0); - assertTrue(geom1.getType().value() == Geometry.GeometryType.MultiPoint); - assertTrue(((MultiPoint) geom1).getPointCount() == 1); - - Geometry geom2 = result_cursor.next(); - assertTrue(geom2 != null); - assertTrue(geom2.getDimension() == 1); - assertTrue(geom2.getType().value() == Geometry.GeometryType.Polyline); - assertTrue(((Polyline) geom2).getPointCount() == 0); - - Geometry geom3 = result_cursor.next(); - assertTrue(geom3 != null); - assertTrue(geom3.getDimension() == 2); - assertTrue(geom3.getType().value() == Geometry.GeometryType.Polygon); - assertTrue(((Polygon) geom3).getPointCount() == 0); - - - - - - - - } - - {// crossing + overlapping intersection - Polygon basePl = new Polygon(); - basePl.startPath(0, 0); - basePl.lineTo(100, 0); - basePl.lineTo(100, 100); - basePl.lineTo(0, 100); - - Polygon compPl = new Polygon(); - compPl.startPath(100, 100); - compPl.lineTo(200, 100); - compPl.lineTo(200, 200); - compPl.lineTo(100, 200); - - compPl.startPath(100, 20); - compPl.lineTo(200, 20); - compPl.lineTo(200, 40); - compPl.lineTo(100, 40); - - compPl.startPath(-10, -10); - compPl.lineTo(-10, 10); - compPl.lineTo(10, 10); - compPl.lineTo(10, -10); - - OperatorIntersection op = (OperatorIntersection) projEnv - .getOperator(Operator.Type.Intersection); - GeometryCursor result_cursor = op.execute(new SimpleGeometryCursor( - basePl), new SimpleGeometryCursor(compPl), SpatialReference - .create(4326), null, 7); - - // dimension is 3, means it has to return a point and a polyline - - Geometry geom1 = result_cursor.next(); - assertTrue(geom1 != null); - assertEquals(geom1.getDimension(), 0); - assertTrue(geom1.getType().value() == Geometry.GeometryType.MultiPoint); - assertTrue(((MultiPoint) geom1).getPointCount() == 1); - - Geometry geom2 = result_cursor.next(); - assertTrue(geom2 != null); - assertTrue(geom2.getDimension() == 1); - assertTrue(geom2.getType().value() == Geometry.GeometryType.Polyline); - assertTrue(((Polyline) geom2).getPathCount() == 1); - assertTrue(((Polyline) geom2).getPointCount() == 2); - - Geometry geom3 = result_cursor.next(); - assertTrue(geom3 != null); - assertTrue(geom3.getDimension() == 2); - assertTrue(geom3.getType().value() == Geometry.GeometryType.Polygon); - assertTrue(((Polygon) geom3).getPathCount() == 1); - assertTrue(((Polygon) geom3).getPointCount() == 4); - - Geometry geom4 = result_cursor.next(); - assertTrue(geom4 == null); - } - } - - @Test - public void testFromProjection() { - MultiPoint multiPointInitial = new MultiPoint(); - multiPointInitial.add(-20037508.342789244, 3360107.7777777780); - multiPointInitial.add(-18924313.434856508, 3360107.7777777780); - multiPointInitial.add(-18924313.434856508, -3360107.7777777780); - multiPointInitial.add(-20037508.342789244, -3360107.7777777780); - Geometry geom1 = ((MultiPoint) multiPointInitial); - - SpatialReference sr = SpatialReference.create(102100); - - Envelope2D env = new Envelope2D(); - env.setCoords(/* xmin */-20037508.342788246, /* ymin */ - -30240971.958386172, /* xmax */20037508.342788246, /* ymax */ - 30240971.958386205); - // /*xmin*/ -20037508.342788246 - // /*ymin*/ -30240971.958386172 - // /*xmax*/ 20037508.342788246 - // /*ymax*/ 30240971.958386205 - - Polygon poly = new Polygon(); - poly.startPath(env.xmin, env.ymin); - poly.lineTo(env.xmin, env.ymax); - poly.lineTo(env.xmax, env.ymax); - poly.lineTo(env.xmax, env.ymin); - - Geometry geom2 = new Envelope(env); - // Geometry geom2 = poly; - - OperatorIntersection operatorIntersection = (OperatorIntersection) OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.Intersection); - - MultiPoint multiPointOut = (MultiPoint) (operatorIntersection.execute( - geom1, geom2, sr, null)); - - assertTrue(multiPointOut.getCoordinates2D().length == 2); - assertTrue(multiPointOut.getCoordinates2D()[0].x == -18924313.434856508); - assertTrue(multiPointOut.getCoordinates2D()[0].y == 3360107.7777777780); - assertTrue(multiPointOut.getCoordinates2D()[1].x == -18924313.434856508); - assertTrue(multiPointOut.getCoordinates2D()[1].y == -3360107.7777777780); - } - - @Test - public void testIssue258128() { - Polygon poly1 = new Polygon(); - poly1.startPath(0, 0); - poly1.lineTo(0, 10); - poly1.lineTo(10, 10); - poly1.lineTo(10, 0); - - Polygon poly2 = new Polygon(); - poly2.startPath(10.5, 4); - poly2.lineTo(10.5, 8); - poly2.lineTo(14, 10); - - try { - GeometryCursor result_cursor = OperatorIntersection.local().execute(new SimpleGeometryCursor( - poly1), new SimpleGeometryCursor(poly2), SpatialReference - .create(4326), null, 1); - while (result_cursor.next() != null) { - - } - } catch (Exception e) { - assertTrue(false); - } - } - - @Test - public void testUnionTickTock() { - Polygon poly1 = new Polygon(); - poly1.startPath(0, 0); - poly1.lineTo(0, 10); - poly1.lineTo(10, 10); - poly1.lineTo(10, 0); - - Polygon poly2 = new Polygon(); - poly2.startPath(10.5, 4); - poly2.lineTo(10.5, 8); - poly2.lineTo(14, 10); - - Transformation2D trans = new Transformation2D(); - - Polygon poly3 = (Polygon) poly1.copy(); - trans.setShift(2, 3); - poly3.applyTransformation(trans); - - Polygon poly4 = (Polygon) poly1.copy(); - trans.setShift(-2, -3); - poly4.applyTransformation(trans); - - // Create - ListeningGeometryCursor gc = new ListeningGeometryCursor(); - GeometryCursor ticktock = OperatorUnion.local().execute(gc, null, null); - - // Use tick-tock to push a geometry and do a piece of work. - gc.tick(poly1); - ticktock.tock(); - gc.tick(poly2); - gc.tick(poly3);// skiped one tock just for testing. - ticktock.tock(); - gc.tick(poly4); - ticktock.tock(); - // Get the result - Geometry result = ticktock.next(); - - // Use ListeningGeometryCursor to put all geometries in. - ListeningGeometryCursor gc2 = new ListeningGeometryCursor(); - gc2.tick(poly1); - gc2.tick(poly2); - gc2.tick(poly3); - gc2.tick(poly4); - - GeometryCursor res = OperatorUnion.local().execute(gc2, null, null); - // Calling next will process all geometries at once. - Geometry result2 = res.next(); - assertTrue(result.equals(result2)); - } - - @Test - public void testIntersectionIssueLinePoly1() { - String wkt1 = new String("polygon((0 0, 10 0, 10 10, 0 10, 0 0))"); - String wkt2 = new String("linestring(9 5, 10 5, 9 4, 8 3)"); - Geometry g1 = OperatorImportFromWkt.local().execute(0, Geometry.Type.Unknown, wkt1, null); - Geometry g2 = OperatorImportFromWkt.local().execute(0, Geometry.Type.Unknown, wkt2, null); - Geometry res = OperatorIntersection.local().execute(g1, g2, null, null); - assertTrue(((Polyline) res).getPathCount() == 1); - assertTrue(((Polyline) res).getPointCount() == 4); - } - - @Test - public void testSharedEdgeIntersection_13() { - String s1 = "{\"rings\":[[[0.099604024000029767,0.2107958250000479],[0.14626826900007472,0.2107958250000479],[0.14626826900007472,0.18285316400005058],[0.099604024000029767,0.18285316400005058],[0.099604024000029767,0.2107958250000479]]]}"; - String s2 = "{\"paths\":[[[0.095692051000071388,0.15910190100004229],[0.10324853600002371,0.18285316400004228],[0.12359292700006108,0.18285316400004228],[0.12782611200003657,0.1705583920000322],[0.13537063000007138,0.18285316400004228]]]}"; - - Polygon polygon = (Polygon) TestCommonMethods.fromJson(s1).getGeometry(); - Polyline polyline = (Polyline) TestCommonMethods.fromJson(s2).getGeometry(); - SpatialReference sr = SpatialReference.create(4326); - - Geometry g = OperatorIntersection.local().execute(polygon, polyline, sr, null); - assertTrue(!g.isEmpty()); - } - - @Test - public void testIntersectionIssue2() { - String s1 = "{\"rings\":[[[-97.174860352323378,48.717174479818425],[-97.020624513410553,58.210155436624177],[-94.087641114245969,58.210155436624177],[-94.087641114245969,48.639781902013226],[-97.174860352323378,48.717174479818425]]]}"; - String s2 = "{\"rings\":[[[-94.08764111399995,52.68342763000004],[-94.08764111399995,56.835188018000053],[-90.285921520999977,62.345706350000057],[-94.08764111399995,52.68342763000004]]]}"; - - Polygon polygon1 = (Polygon) TestCommonMethods.fromJson(s1).getGeometry(); - Polygon polygon2 = (Polygon) TestCommonMethods.fromJson(s2).getGeometry(); - SpatialReference sr = SpatialReference.create(4326); - - GeometryCursor res = OperatorIntersection.local().execute(new SimpleGeometryCursor(polygon1), new SimpleGeometryCursor(polygon2), sr, null, 2); - Geometry g = res.next(); - assertTrue(g != null); - assertTrue(!g.isEmpty()); - Geometry g2 = res.next(); - assertTrue(g2 == null); - - String ss = "{\"paths\":[[[-94.08764111412296,52.68342763000004],[-94.08764111410767,56.83518801800005]]]}"; - Polyline polyline = (Polyline) TestCommonMethods.fromJson(ss).getGeometry(); - boolean eq = OperatorEquals.local().execute(g, polyline, sr, null); - assertTrue(eq); - } + static OperatorFactoryLocal projEnv = OperatorFactoryLocal.getInstance(); + static int codeIn = 26910;// NAD_1983_UTM_Zone_10N : GEOGRAPHIC 6269 + static int codeOut = 32610;// WGS_1984_UTM_Zone_10N; : GEOGRAPHIC 4326 + static SpatialReference inputSR; + static SpatialReference outputSR; + + @Override + protected void setUp() throws Exception { + super.setUp(); + projEnv = OperatorFactoryLocal.getInstance(); + inputSR = SpatialReference.create(codeIn); + outputSR = SpatialReference.create(codeOut); + } + + @Override + protected void tearDown() throws Exception { + super.tearDown(); + } + + @Test + public void testIntersection1() { + // OperatorFactoryLocal projEnv = OperatorFactoryLocal.getInstance(); + // int codeIn = 26910;//NAD_1983_UTM_Zone_10N : GEOGRAPHIC 6269 + // int codeOut = 32610;//WGS_1984_UTM_Zone_10N; : GEOGRAPHIC 4326 + // int codeIn = SpatialReference::PCS_WGS_1984_UTM_10N; + // int codeOut = SpatialReference::PCS_WORLD_MOLLWEIDE; + // int codeOut = 102100; + inputSR = SpatialReference.create(codeIn); + assertTrue(inputSR.getID() == codeIn); + outputSR = SpatialReference.create(codeOut); + assertTrue(outputSR.getID() == codeOut); + + OperatorIntersection operatorIntersection = (OperatorIntersection) projEnv + .getOperator(Operator.Type.Intersection); + + Polygon poly1 = new Polygon(); + Envelope env1 = new Envelope(855277, 3892059, 855277 + 100, + 3892059 + 100); + // Envelope env1 = new Envelope(-1000000, -1000000, 1000000, 1000000); + // env1.SetCoords(-8552770, -3892059, 855277 + 100, 3892059 + 100); + poly1.addEnvelope(env1, false); + + Polygon poly2 = new Polygon(); + Envelope env2 = new Envelope(855277 + 1, 3892059 + 1, 855277 + 30, + 3892059 + 20); + poly2.addEnvelope(env2, false); + + GeometryCursor cursor1 = new SimpleGeometryCursor(poly1); + GeometryCursor cursor2 = new SimpleGeometryCursor(poly2); + + GeometryCursor outputGeoms = operatorIntersection.execute(cursor1, + cursor2, inputSR, null); + Geometry geomr = outputGeoms.next(); + assertNotNull(geomr); + assertTrue(geomr.getType() == Geometry.Type.Polygon); + Polygon geom = (Polygon) geomr; + assertTrue(geom.getPointCount() == 4); + Point[] points = TestCommonMethods.pointsFromMultiPath(geom);// SPtrOfArrayOf(Point2D) + // pts = + // geom.get.getCoordinates2D(); + assertTrue(Math.abs(points[0].getX() - 855278.000000000) < 1e-7); + assertTrue(Math.abs(points[0].getY() - 3892060.0000000000) < 1e-7); + assertTrue(Math.abs(points[2].getX() - 855307.00000000093) < 1e-7); + assertTrue(Math.abs(points[2].getY() - 3892079.0000000000) < 1e-7); + + geomr = operatorIntersection.execute(poly1, poly2, inputSR, null); + assertNotNull(geomr); + assertTrue(geomr.getType() == Geometry.Type.Polygon); + Polygon outputGeom = (Polygon) geomr; + + assertTrue(outputGeom.getPointCount() == 4); + points = TestCommonMethods.pointsFromMultiPath(outputGeom); + assertTrue(Math.abs(points[0].getX() - 855278.000000000) < 1e-7); + assertTrue(Math.abs(points[0].getY() - 3892060.0000000000) < 1e-7); + assertTrue(Math.abs(points[2].getX() - 855307.00000000093) < 1e-7); + assertTrue(Math.abs(points[2].getY() - 3892079.0000000000) < 1e-7); + } + + @Test + public void testSelfIntersecting() {// Test that we do not fail if there is + // self-intersection + // OperatorFactoryLocal projEnv = + // OperatorFactoryLocal.getInstance(); + OperatorIntersection operatorIntersection = (OperatorIntersection) projEnv + .getOperator(Operator.Type.Intersection); + SpatialReference sr = SpatialReference.create(4326); + Polygon poly1 = new Polygon(); + Envelope2D env1 = new Envelope2D(); + env1.setCoords(0, 0, 20, 30); + poly1.addEnvelope(env1, false); + Polygon poly2 = new Polygon(); + poly2.startPath(0, 0); + poly2.lineTo(10, 10); + poly2.lineTo(0, 10); + poly2.lineTo(10, 0); + @SuppressWarnings("unused") + Polygon res = (Polygon) (operatorIntersection.execute(poly1, poly2, sr, + null)); + // Operator_equals equals = + // (Operator_equals)projEnv.get_operator(Operator::equals); + // assertTrue(equals.execute(res, poly2, sr, NULL) == true); + } + + @Test + public void testMultipoint() { + Polygon poly1 = new Polygon(); + Envelope env1 = new Envelope(855277, 3892059, 855277 + 100, + 3892059 + 100); + + poly1.addEnvelope(env1, false); + MultiPoint multiPoint = new MultiPoint(); + multiPoint.add(855277 + 10, 3892059 + 10); + multiPoint.add(855277, 3892059); + multiPoint.add(855277 + 100, 3892059 + 100); + multiPoint.add(855277 + 100, 3892059 + 101); + multiPoint.add(855277 + 101, 3892059 + 100); + multiPoint.add(855277 + 101, 3892059 + 101); + OperatorIntersection operatorIntersection = (OperatorIntersection) projEnv + .getOperator(Operator.Type.Intersection); + + MultiPoint mpResult = (MultiPoint) operatorIntersection.execute(poly1, + multiPoint, inputSR, null); + assertTrue(mpResult.getPointCount() == 3); + assertTrue(mpResult.getPoint(0).getX() == 855277 + 10 + && mpResult.getPoint(0).getY() == 3892059 + 10); + assertTrue(mpResult.getPoint(1).getX() == 855277 + && mpResult.getPoint(1).getY() == 3892059); + assertTrue(mpResult.getPoint(2).getX() == 855277 + 100 + && mpResult.getPoint(2).getY() == 3892059 + 100); + + // Test intersection of Polygon with Envelope (calls Clip) + Polygon poly = new Polygon(); + poly.startPath(0, 0); + poly.lineTo(10, 10); + poly.lineTo(20, 0); + + env1.setXMin(0); + env1.setXMax(20); + env1.setYMin(5); + env1.setYMax(15); + + Envelope envelope1 = env1; + + Polygon clippedPoly = (Polygon) operatorIntersection.execute(poly, + envelope1, inputSR, null); + double area = clippedPoly.calculateArea2D(); + assertTrue(Math.abs(area - 25) < 0.00001); + + // Geometry res = GeometryEngine.difference(poly, envelope1, inputSR); + Envelope env2 = new Envelope(855277 + 1, 3892059 + 1, 855277 + 30, + 3892059 + 20); + env2.setXMin(5); + env2.setXMax(10); + env2.setYMin(0); + env2.setYMax(20); + + Envelope envelope2 = env2; + + Envelope clippedEnvelope = (Envelope) operatorIntersection.execute( + envelope1, envelope2, inputSR, null); + area = clippedEnvelope.calculateArea2D(); + assertTrue(Math.abs(area - 50) < 0.00001); + } + + @Test + public void testDifferenceOnPolyline() { + Polyline basePl = new Polyline(); + basePl.startPath(-117, 20); + basePl.lineTo(-130, 10); + basePl.lineTo(-120, 50); + + Polyline compPl = new Polyline(); + compPl.startPath(-116, 20); + compPl.lineTo(-131, 10); + compPl.lineTo(-121, 50); + + // OperatorFactoryLocal projEnv = OperatorFactoryLocal.getInstance(); + OperatorDifference op = (OperatorDifference) projEnv + .getOperator(Operator.Type.Difference); + Polyline diffGeom = (Polyline) (op.execute(basePl, compPl, + SpatialReference.create(4326), null)); + int pc = diffGeom.getPointCount(); + assertTrue(pc == 5); + } + + @Test + public void testDifferenceOnPolyline2() { + Polyline basePl = new Polyline(); + basePl.startPath(0, 0); + basePl.lineTo(10, 10); + basePl.lineTo(20, 20); + basePl.lineTo(10, 0); + basePl.lineTo(20, 10); + + Polyline compPl = new Polyline(); + compPl.startPath(5, 0); + compPl.lineTo(5, 10); + compPl.lineTo(0, 10); + compPl.lineTo(7.5, 2.5); + + // Operator_factory_local::SaveJSONToTextFileDbg("c:/temp/basePl.txt", + // *basePl, null); + // Operator_factory_local::SaveJSONToTextFileDbg("c:/temp/compPl.txt", + // *compPl, null); + // OperatorFactoryLocal projEnv = OperatorFactoryLocal.getInstance(); + OperatorDifference op = (OperatorDifference) projEnv + .getOperator(Operator.Type.Difference); + Polyline diffGeom = (Polyline) (op.execute(basePl, compPl, + SpatialReference.create(4326), null)); + // Operator_factory_local::SaveJSONToTextFileDbg("c:/temp/diffGeom.txt", + // *diffGeom, null); + int pathc = diffGeom.getPathCount(); + assertTrue(pathc == 1); + int pc = diffGeom.getPointCount(); + assertTrue(pc == 6); + + Polyline resPl = new Polyline(); + resPl.startPath(0, 0); + resPl.lineTo(5, 5); + resPl.lineTo(10, 10); + resPl.lineTo(20, 20); + resPl.lineTo(10, 0); + resPl.lineTo(20, 10); + // Operator_factory_local::SaveJSONToTextFileDbg("c:/temp/resPl.txt", + // *resPl, null); + assertTrue(resPl.equals(diffGeom)); + } + + @Test + public void testDifferencePointPolyline() { + Polyline basePl = new Polyline(); + basePl.startPath(0, 0); + basePl.lineTo(10, 10); + basePl.lineTo(20, 20); + basePl.lineTo(10, 0); + basePl.lineTo(20, 10); + + Point compPl = new Point(5, 5); + + // OperatorFactoryLocal projEnv = OperatorFactoryLocal.getInstance(); + OperatorDifference op = (OperatorDifference) projEnv + .getOperator(Operator.Type.Difference); + Polyline diffGeom = (Polyline) (op.execute(basePl, compPl, + SpatialReference.create(4326), null)); + int pathc = diffGeom.getPathCount(); + assertTrue(pathc == 1); + int pc = diffGeom.getPointCount(); + assertTrue(pc == 5); + + Polyline resPl = new Polyline(); + resPl.startPath(0, 0); + resPl.lineTo(10, 10); + resPl.lineTo(20, 20); + resPl.lineTo(10, 0); + resPl.lineTo(20, 10); + assertTrue(resPl.equals(diffGeom));// no change happens to the original + // polyline + } + + @Test + public void testIntersectionPolylinePolygon() { + { + Polygon polygon = new Polygon(); + polygon.startPath(0, 0); + polygon.lineTo(0, 10); + polygon.lineTo(20, 10); + polygon.lineTo(20, 0); + polygon.addAttribute(VertexDescription.Semantics.Z); + polygon.setAttribute(VertexDescription.Semantics.Z, 0, 0, 3); + polygon.setAttribute(VertexDescription.Semantics.Z, 3, 0, 3); + polygon.interpolateAttributes(0, 0, 3); + Polyline polyline = new Polyline(); + polyline.startPath(0, 10); + polyline.lineTo(5, 5); + polyline.lineTo(6, 4); + polyline.lineTo(7, -1); + polyline.addAttribute(VertexDescription.Semantics.Z); + polyline.setAttribute(VertexDescription.Semantics.Z, 0, 0, 5); + polyline.setAttribute(VertexDescription.Semantics.Z, 3, 0, 5); + polyline.interpolateAttributes(0, 0, 0, 3); + + // OperatorFactoryLocal projEnv = + // OperatorFactoryLocal.getInstance(); + OperatorIntersection operatorIntersection = (OperatorIntersection) projEnv + .getOperator(Operator.Type.Intersection); + Geometry geom = operatorIntersection.execute(polyline, polygon, + null, null); + assertTrue(!geom.isEmpty()); + Polyline poly = (Polyline) (geom); + for (int i = 0; i < poly.getPointCount(); i++) + assertTrue(poly.getAttributeAsDbl( + VertexDescription.Semantics.Z, i, 0) == 5); + + // std::shared_ptr jsonExport = + // (Operator_export_to_JSON>)Operator_factory_local::get_instance().get_operator(Operator::Operator_type::export_to_JSON); + // std::string str = jsonExport.execute(0, geom, null, null); + // OutputDebugStringA(str.c_str()); + // OutputDebugString(L"\n"); + // assertTrue(str=="{\"paths\":[[[0,10],[5,5],[6,4],[6.7999999999999998,4.4408922169635528e-016]]]}"); + } + + { + Polygon polygon = new Polygon(); + polygon.startPath(0, 0); + polygon.lineTo(0, 10); + polygon.lineTo(20, 10); + polygon.lineTo(20, 0); + polygon.addAttribute(VertexDescription.Semantics.Z); + polygon.setAttribute(VertexDescription.Semantics.Z, 0, 0, 3); + polygon.setAttribute(VertexDescription.Semantics.Z, 3, 0, 3); + polygon.interpolateAttributes(0, 0, 3); + Polyline polyline = new Polyline(); + polyline.startPath(0, 10); + polyline.lineTo(20, 0); + polyline.lineTo(5, 5); + polyline.addAttribute(VertexDescription.Semantics.Z); + polyline.setAttribute(VertexDescription.Semantics.Z, 0, 0, 5); + polyline.setAttribute(VertexDescription.Semantics.Z, 1, 0, 5); + polyline.setAttribute(VertexDescription.Semantics.Z, 2, 0, 5); + + // OperatorFactoryLocal projEnv = + // OperatorFactoryLocal.getInstance(); + OperatorIntersection operatorIntersection = (OperatorIntersection) projEnv + .getOperator(Operator.Type.Intersection); + Geometry geom = operatorIntersection.execute(polyline, polygon, + null, null); + assertTrue(!geom.isEmpty()); + Polyline poly = (Polyline) (geom); + for (int i = 0; i < poly.getPointCount(); i++) + assertTrue(poly.getAttributeAsDbl( + VertexDescription.Semantics.Z, i, 0) == 5); + + // Operator_export_to_JSON> jsonExport = + // (Operator_export_to_JSON>)Operator_factory_local::get_instance().get_operator(Operator::Operator_type::export_to_JSON); + // std::string str = jsonExport.execute(0, geom, null, null); + // OutputDebugStringA(str.c_str()); + // OutputDebugString(L"\n"); + // assertTrue(str=="{\"paths\":[[[0,10],[20,0],[5,5]]]}"); + } + + { + Polygon polygon = new Polygon(); + polygon.startPath(0, 0); + polygon.lineTo(0, 10); + polygon.lineTo(20, 10); + polygon.lineTo(20, 0); + polygon.addAttribute(VertexDescription.Semantics.Z); + polygon.setAttribute(VertexDescription.Semantics.Z, 0, 0, 3); + polygon.setAttribute(VertexDescription.Semantics.Z, 3, 0, 3); + polygon.interpolateAttributes(0, 0, 3); + Polyline polyline = new Polyline(); + polyline.startPath(0, 0); + polyline.lineTo(0, 10); + polyline.lineTo(20, 10); + polyline.addAttribute(VertexDescription.Semantics.Z); + polyline.setAttribute(VertexDescription.Semantics.Z, 0, 0, 5); + polyline.setAttribute(VertexDescription.Semantics.Z, 1, 0, 5); + polyline.setAttribute(VertexDescription.Semantics.Z, 2, 0, 5); + + // OperatorFactoryLocal projEnv = + // OperatorFactoryLocal.getInstance(); + OperatorIntersection operatorIntersection = (OperatorIntersection) projEnv + .getOperator(Operator.Type.Intersection); + Geometry geom = operatorIntersection.execute(polyline, polygon, + null, null); + assertTrue(!geom.isEmpty()); + Polyline poly = (Polyline) (geom); + for (int i = 0; i < poly.getPointCount(); i++) + assertTrue(poly.getAttributeAsDbl( + VertexDescription.Semantics.Z, i, 0) == 5); + + // Operator_export_to_JSON> jsonExport = + // (Operator_export_to_JSON>)Operator_factory_local::get_instance().get_operator(Operator::Operator_type::export_to_JSON); + // std::string str = jsonExport.execute(0, geom, null, null); + // OutputDebugStringA(str.c_str()); + // OutputDebugString(L"\n"); + // assertTrue(str=="{\"paths\":[[[0,0],[0,10],[20,10]]]}"); + } + + { + Polygon polygon = new Polygon(); + polygon.startPath(0, 0); + polygon.lineTo(0, 10); + polygon.lineTo(20, 10); + polygon.lineTo(20, 0); + Polyline polyline = new Polyline(); + polyline.startPath(3, -1); + polyline.lineTo(17, 1); + polyline.lineTo(10, 8); + polyline.lineTo(-1, 5); + polyline.addAttribute(VertexDescription.Semantics.Z); + polyline.setAttribute(VertexDescription.Semantics.Z, 0, 0, 5); + polyline.setAttribute(VertexDescription.Semantics.Z, 1, 0, 5); + polyline.setAttribute(VertexDescription.Semantics.Z, 2, 0, 5); + polyline.setAttribute(VertexDescription.Semantics.Z, 3, 0, 5); + + // OperatorFactoryLocal projEnv = + // OperatorFactoryLocal.getInstance(); + OperatorIntersection operatorIntersection = (OperatorIntersection) projEnv + .getOperator(Operator.Type.Intersection); + Geometry geom = operatorIntersection.execute(polyline, polygon, + null, null); + assertTrue(!geom.isEmpty()); + Polyline poly = (Polyline) geom; + for (int i = 0; i < poly.getPointCount(); i++) + assertTrue(poly.getAttributeAsDbl( + VertexDescription.Semantics.Z, i, 0) == 5); + + // Operator_export_to_JSON> jsonExport = + // (Operator_export_to_JSON>)Operator_factory_local::get_instance().get_operator(Operator::Operator_type::export_to_JSON); + // std::string str = jsonExport.execute(0, geom, null, null); + // OutputDebugStringA(str.c_str()); + // OutputDebugString(L"\n"); + // assertTrue(str=="{\"paths\":[[[10,0],[17,1],[10,8],[4.7377092701401439e-024,5.2727272727272734]]]}"); + } + + { + Polygon polygon = new Polygon(); + polygon.startPath(0, 0); + polygon.lineTo(0, 10); + polygon.lineTo(20, 10); + polygon.lineTo(20, 0); + Polyline polyline = new Polyline(); + polyline.startPath(0, 15); + polyline.lineTo(3, -1); + polyline.lineTo(17, 1); + polyline.lineTo(10, 8); + polyline.lineTo(-1, 5); + polyline.addAttribute(VertexDescription.Semantics.Z); + polyline.setAttribute(VertexDescription.Semantics.Z, 0, 0, 5); + polyline.setAttribute(VertexDescription.Semantics.Z, 4, 0, 5); + polyline.interpolateAttributes(0, 0, 0, 4); + OperatorIntersection operatorIntersection = (OperatorIntersection) projEnv + .getOperator(Operator.Type.Intersection); + Geometry geom = operatorIntersection.execute(polyline, polygon, + null, null); + assertTrue(!geom.isEmpty()); + Polyline poly = (Polyline) geom; + for (int i = 0; i < poly.getPointCount(); i++) + assertTrue(poly.getAttributeAsDbl( + VertexDescription.Semantics.Z, i, 0) == 5); + + // Operator_export_to_JSON> jsonExport = + // (Operator_export_to_JSON>)Operator_factory_local::get_instance().get_operator(Operator::Operator_type::export_to_JSON); + // std::string str = jsonExport.execute(0, geom, null, null); + // OutputDebugStringA(str.c_str()); + // OutputDebugString(L"\n"); + // assertTrue(str=="{\"paths\":[[[0.9375,10],[2.8125,9.476226333847234e-024]],[[10,0],[17,1],[10,8],[4.7377092701401439e-024,5.2727272727272734]]]}"); + } + + { + Polygon polygon = new Polygon(); + polygon.startPath(0, 0); + polygon.lineTo(0, 10); + polygon.lineTo(20, 10); + polygon.lineTo(20, 0); + Polyline polyline = new Polyline(); + polyline.startPath(5, 5); + polyline.lineTo(1, 1); + polyline.lineTo(-1, 1); + polyline.lineTo(-1, 10); + polyline.lineTo(0, 10); + polyline.lineTo(6, 6); + polyline.addAttribute(VertexDescription.Semantics.Z); + polyline.setAttribute(VertexDescription.Semantics.Z, 0, 0, 5); + polyline.setAttribute(VertexDescription.Semantics.Z, 5, 0, 5); + polyline.interpolateAttributes(0, 0, 0, 5); + + // OperatorFactoryLocal projEnv = + // OperatorFactoryLocal.getInstance(); + OperatorIntersection operatorIntersection = (OperatorIntersection) projEnv + .getOperator(Operator.Type.Intersection); + Geometry geom = operatorIntersection.execute(polyline, polygon, + null, null); + assertTrue(!geom.isEmpty()); + Polyline poly = (Polyline) geom; + for (int i = 0; i < poly.getPointCount(); i++) + assertTrue(poly.getAttributeAsDbl( + VertexDescription.Semantics.Z, i, 0) == 5); + + // Operator_export_to_JSON> jsonExport = + // (Operator_export_to_JSON>)Operator_factory_local::get_instance().get_operator(Operator::Operator_type::export_to_JSON); + // std::string str = jsonExport.execute(0, geom, null, null); + // OutputDebugStringA(str.c_str()); + // OutputDebugString(L"\n"); + // assertTrue(str=="{\"paths\":[[[5,5],[1,1],[4.738113166923617e-023,1]],[[0,10],[6,6]]]}"); + } + + { + Polygon polygon = new Polygon(); + polygon.startPath(0, 0); + polygon.lineTo(0, 10); + polygon.lineTo(20, 10); + polygon.lineTo(20, 0); + Polyline polyline = new Polyline(); + polyline.startPath(0, 15); + polyline.lineTo(3, -1); + polyline.lineTo(17, 1); + polyline.lineTo(10, 8); + polyline.lineTo(-1, 5); + polyline.startPath(19, 15); + polyline.lineTo(29, 9); + polyline.startPath(19, 15); + polyline.lineTo(29, 9); + polyline.startPath(5, 5); + polyline.lineTo(1, 1); + polyline.lineTo(-1, 1); + polyline.lineTo(-1, 10); + polyline.lineTo(0, 10); + polyline.lineTo(6, 6); + polyline.addAttribute(VertexDescription.Semantics.Z); + polyline.setAttribute(VertexDescription.Semantics.Z, 0, 0, 5); + polyline.setAttribute(VertexDescription.Semantics.Z, 14, 0, 5); + polyline.interpolateAttributes(0, 0, 3, 5); + + // OperatorFactoryLocal projEnv = + // OperatorFactoryLocal.getInstance(); + OperatorIntersection operatorIntersection = (OperatorIntersection) projEnv + .getOperator(Operator.Type.Intersection); + Geometry geom = operatorIntersection.execute(polyline, polygon, + null, null); + assertTrue(!geom.isEmpty()); + Polyline poly = (Polyline) geom; + for (int i = 0; i < poly.getPointCount(); i++) + assertTrue(poly.getAttributeAsDbl( + VertexDescription.Semantics.Z, i, 0) == 5); + + // Operator_export_to_JSON> jsonExport = + // (Operator_export_to_JSON>)Operator_factory_local::get_instance().get_operator(Operator::Operator_type::export_to_JSON); + // std::string str = jsonExport.execute(0, geom, null, null); + // OutputDebugStringA(str.c_str()); + // OutputDebugString(L"\n"); + // assertTrue(str=="{\"paths\":[[[0.9375,10],[2.8125,9.476226333847234e-024]],[[10,0],[17,1],[10,8],[4.7377092701401439e-024,5.2727272727272734]],[[5,5],[1,1],[4.738113166923617e-023,1]],[[0,10],[6,6]]]}"); + } + } + + @Test + public void testMultiPointPolyline() { + Polyline polyline = new Polyline(); + polyline.startPath(0, 0); + polyline.lineTo(0, 10); + polyline.lineTo(20, 10); + polyline.lineTo(20, 0); + MultiPoint mp = new MultiPoint(); + mp.add(0, 10, 7); + mp.add(0, 5, 7); + mp.add(1, 5, 7); + // OperatorFactoryLocal projEnv = OperatorFactoryLocal.getInstance(); + OperatorIntersection operatorIntersection = (OperatorIntersection) projEnv + .getOperator(Operator.Type.Intersection); + OperatorDifference operatorDifference = (OperatorDifference) projEnv + .getOperator(Operator.Type.Difference); + + {// intersect + Geometry geom = operatorIntersection.execute(polyline, mp, null, + null); + MultiPoint res = (MultiPoint) geom; + assertTrue(res.getPointCount() == 2); + Point2D pt_1 = res.getXY(0); + Point2D pt_2 = res.getXY(1); + assertTrue(Point2D.distance(pt_1, new Point2D(0, 10)) < 1e-10 + && Point2D.distance(pt_2, new Point2D(0, 5)) < 1e-10 + || Point2D.distance(pt_2, new Point2D(0, 10)) < 1e-10 + && Point2D.distance(pt_1, new Point2D(0, 5)) < 1e-10); + + assertTrue(res.getAttributeAsDbl(VertexDescription.Semantics.Z, 0, + 0) == 7); + assertTrue(res.getAttributeAsDbl(VertexDescription.Semantics.Z, 1, + 0) == 7); + } + + {// difference + Geometry geom = operatorDifference + .execute(polyline, mp, null, null); + // assertTrue(geom.getGeometryType() == + // Geometry.GeometryType.Polyline); + Polyline res = (Polyline) geom; + assertTrue(res.getPointCount() == 4); + } + {// difference + Geometry geom = operatorDifference + .execute(mp, polyline, null, null); + // assertTrue(geom.getType() == Geometry.GeometryType.MultiPoint); + MultiPoint res = (MultiPoint) geom; + assertTrue(res.getPointCount() == 1); + Point2D pt_1 = res.getXY(0); + assertTrue(Point2D.distance(pt_1, new Point2D(1, 5)) < 1e-10); + } + {// difference (subtract empty) + Geometry geom = operatorDifference.execute(mp, new Polyline(), + null, null); + // assertTrue(geom.getGeometryType() == + // Geometry.GeometryType.MultiPoint); + MultiPoint res = (MultiPoint) geom; + assertTrue(res.getPointCount() == 3); + Point2D pt_1 = res.getXY(0); + assertTrue(Point2D.distance(pt_1, new Point2D(0, 10)) < 1e-10); + } + + } + + @Test + public void testPointPolyline() { + Polyline polyline = new Polyline(); + polyline.startPath(0, 0); + polyline.lineTo(0, 10); + polyline.lineTo(20, 10); + polyline.lineTo(20, 0); + Point p_1 = new Point(0, 5, 7); + Point p_2 = new Point(0, 10, 7); + Point p3 = new Point(1, 5, 7); + // OperatorFactoryLocal projEnv = OperatorFactoryLocal.getInstance(); + OperatorIntersection operatorIntersection = (OperatorIntersection) projEnv + .getOperator(Operator.Type.Intersection); + OperatorDifference operatorDiff = (OperatorDifference) projEnv + .getOperator(Operator.Type.Difference); + OperatorUnion operatorUnion = (OperatorUnion) projEnv + .getOperator(Operator.Type.Union); + OperatorSymmetricDifference operatorSymDiff = (OperatorSymmetricDifference) projEnv + .getOperator(Operator.Type.SymmetricDifference); + + {// intersect case1 + Geometry geom = operatorIntersection.execute(polyline, p_1, null, + null); + // assertTrue(geom.getType() == Geometry::enum_point); + Point res = (Point) geom; + Point2D pt_1 = res.getXY(); + assertTrue(Point2D.distance(pt_1, new Point2D(0, 5)) < 1e-10); + assertTrue(res.getAttributeAsDbl(VertexDescription.Semantics.Z, 0) == 7); + } + {// intersect case2 + Geometry geom = operatorIntersection.execute(polyline, p_2, null, + null); + // assertTrue(geom.getType() == Geometry::enum_point); + Point res = (Point) geom; + Point2D pt_1 = res.getXY(); + assertTrue(Point2D.distance(pt_1, new Point2D(0, 10)) < 1e-10); + assertTrue(res.getAttributeAsDbl(VertexDescription.Semantics.Z, 0) == 7); + } + {// intersect case3 + Geometry geom = operatorIntersection.execute(polyline, p3, null, + null); + // assertTrue(geom.getType() == Geometry::enum_point); + assertTrue(geom.isEmpty()); + assertTrue(geom.hasAttribute(VertexDescription.Semantics.Z)); + } + + {// difference case1 + Geometry geom = operatorDiff.execute(polyline, p_1, null, null); + // assertTrue(geom.getType() == Geometry.GeometryType.Polyline); + Polyline res = (Polyline) geom; + assertTrue(res.getPointCount() == 4); + } + {// difference case2 + Geometry geom = operatorDiff.execute(p_1, polyline, null, null); + // assertTrue(geom.getType() == Geometry::enum_point); + Point res = (Point) geom; + assertTrue(res.isEmpty()); + } + {// difference case3 + Geometry geom = operatorDiff.execute(p_2, polyline, null, null); + // assertTrue(geom.getType() == Geometry::enum_point); + Point res = (Point) geom; + assertTrue(res.isEmpty()); + } + {// difference case4 + Geometry geom = operatorDiff.execute(p3, polyline, null, null); + // assertTrue(geom.getType() == Geometry::enum_point); + Point res = (Point) geom; + Point2D pt_1 = res.getXY(); + assertTrue(Point2D.distance(pt_1, new Point2D(1, 5)) < 1e-10); + } + + {// union case1 + Geometry geom = operatorUnion.execute(p_1, polyline, null, null); + // assertTrue(geom.getType() == Geometry.GeometryType.Polyline); + Polyline res = (Polyline) geom; + assertTrue(!res.isEmpty()); + } + {// union case2 + Geometry geom = operatorUnion.execute(polyline, p_1, null, null); + // assertTrue(geom.getType() == Geometry.GeometryType.Polyline); + Polyline res = (Polyline) geom; + assertTrue(!res.isEmpty()); + } + + {// symmetric difference case1 + Geometry geom = operatorSymDiff.execute(polyline, p_1, null, null); + assertTrue(geom.getType().value() == Geometry.GeometryType.Polyline); + Polyline res = (Polyline) (geom); + assertTrue(!res.isEmpty()); + } + {// symmetric difference case2 + Geometry geom = operatorSymDiff.execute(p_1, polyline, null, null); + assertTrue(geom.getType().value() == Geometry.GeometryType.Polyline); + Polyline res = (Polyline) (geom); + assertTrue(!res.isEmpty()); + } + } + + @Test + public void testPolylinePolylineIntersectionExtended() { + {// crossing intersection + Polyline basePl = new Polyline(); + basePl.startPath(0, 10); + basePl.lineTo(100, 10); + + Polyline compPl = new Polyline(); + compPl.startPath(50, 0); + compPl.lineTo(50, 100); + + OperatorIntersection op = (OperatorIntersection) projEnv + .getOperator(Operator.Type.Intersection); + GeometryCursor result_cursor = op.execute(new SimpleGeometryCursor( + basePl), new SimpleGeometryCursor(compPl), SpatialReference + .create(4326), null, 3); + + // dimension is 3, means it has to return a point and a polyline + Geometry geom1 = result_cursor.next(); + assertTrue(geom1 != null); + assertTrue(geom1.getDimension() == 0); + assertTrue(geom1.getType().value() == Geometry.GeometryType.MultiPoint); + assertTrue(((MultiPoint) geom1).getPointCount() == 1); + + Geometry geom2 = result_cursor.next(); + assertTrue(geom2 != null); + assertTrue(geom2.getDimension() == 1); + assertTrue(geom2.getType().value() == Geometry.GeometryType.Polyline); + assertTrue(((Polyline) geom2).getPointCount() == 0); + + Geometry geom3 = result_cursor.next(); + assertTrue(geom3 == null); + } + + {// crossing + overlapping intersection + Polyline basePl = new Polyline(); + basePl.startPath(0, 10); + basePl.lineTo(100, 10); + + Polyline compPl = new Polyline(); + compPl.startPath(50, 0); + compPl.lineTo(50, 100); + compPl.lineTo(70, 10); + compPl.lineTo(100, 10); + + OperatorIntersection op = (OperatorIntersection) projEnv + .getOperator(Operator.Type.Intersection); + GeometryCursor result_cursor = op.execute(new SimpleGeometryCursor( + basePl), new SimpleGeometryCursor(compPl), SpatialReference + .create(4326), null, 3); + + // dimension is 3, means it has to return a point and a polyline + Geometry geom1 = result_cursor.next(); + assertTrue(geom1 != null); + assertTrue(geom1.getDimension() == 0); + assertTrue(geom1.getType().value() == Geometry.GeometryType.MultiPoint); + assertTrue(((MultiPoint) geom1).getPointCount() == 1); + + Geometry geom2 = result_cursor.next(); + assertTrue(geom2 != null); + assertTrue(geom2.getDimension() == 1); + assertTrue(geom2.getType().value() == Geometry.GeometryType.Polyline); + assertTrue(((Polyline) geom2).getPathCount() == 1); + assertTrue(((Polyline) geom2).getPointCount() == 2); + + Geometry geom3 = result_cursor.next(); + assertTrue(geom3 == null); + } + } + + @Test + public void testPolygonPolygonIntersectionExtended() { + {// crossing intersection + Polygon basePl = new Polygon(); + basePl.startPath(0, 0); + basePl.lineTo(100, 0); + basePl.lineTo(100, 100); + basePl.lineTo(0, 100); + + Polygon compPl = new Polygon(); + compPl.startPath(100, 100); + compPl.lineTo(200, 100); + compPl.lineTo(200, 200); + compPl.lineTo(100, 200); + + OperatorIntersection op = (OperatorIntersection) projEnv + .getOperator(Operator.Type.Intersection); + GeometryCursor result_cursor = op.execute(new SimpleGeometryCursor( + basePl), new SimpleGeometryCursor(compPl), SpatialReference + .create(4326), null, 7); + + Geometry geom1 = result_cursor.next(); + assertTrue(geom1 != null); + assertTrue(geom1.getDimension() == 0); + assertTrue(geom1.getType().value() == Geometry.GeometryType.MultiPoint); + assertTrue(((MultiPoint) geom1).getPointCount() == 1); + + Geometry geom2 = result_cursor.next(); + assertTrue(geom2 != null); + assertTrue(geom2.getDimension() == 1); + assertTrue(geom2.getType().value() == Geometry.GeometryType.Polyline); + assertTrue(((Polyline) geom2).getPointCount() == 0); + + Geometry geom3 = result_cursor.next(); + assertTrue(geom3 != null); + assertTrue(geom3.getDimension() == 2); + assertTrue(geom3.getType().value() == Geometry.GeometryType.Polygon); + assertTrue(((Polygon) geom3).getPointCount() == 0); + + + } + + {// crossing + overlapping intersection + Polygon basePl = new Polygon(); + basePl.startPath(0, 0); + basePl.lineTo(100, 0); + basePl.lineTo(100, 100); + basePl.lineTo(0, 100); + + Polygon compPl = new Polygon(); + compPl.startPath(100, 100); + compPl.lineTo(200, 100); + compPl.lineTo(200, 200); + compPl.lineTo(100, 200); + + compPl.startPath(100, 20); + compPl.lineTo(200, 20); + compPl.lineTo(200, 40); + compPl.lineTo(100, 40); + + compPl.startPath(-10, -10); + compPl.lineTo(-10, 10); + compPl.lineTo(10, 10); + compPl.lineTo(10, -10); + + OperatorIntersection op = (OperatorIntersection) projEnv + .getOperator(Operator.Type.Intersection); + GeometryCursor result_cursor = op.execute(new SimpleGeometryCursor( + basePl), new SimpleGeometryCursor(compPl), SpatialReference + .create(4326), null, 7); + + // dimension is 3, means it has to return a point and a polyline + + Geometry geom1 = result_cursor.next(); + assertTrue(geom1 != null); + assertEquals(geom1.getDimension(), 0); + assertTrue(geom1.getType().value() == Geometry.GeometryType.MultiPoint); + assertTrue(((MultiPoint) geom1).getPointCount() == 1); + + Geometry geom2 = result_cursor.next(); + assertTrue(geom2 != null); + assertTrue(geom2.getDimension() == 1); + assertTrue(geom2.getType().value() == Geometry.GeometryType.Polyline); + assertTrue(((Polyline) geom2).getPathCount() == 1); + assertTrue(((Polyline) geom2).getPointCount() == 2); + + Geometry geom3 = result_cursor.next(); + assertTrue(geom3 != null); + assertTrue(geom3.getDimension() == 2); + assertTrue(geom3.getType().value() == Geometry.GeometryType.Polygon); + assertTrue(((Polygon) geom3).getPathCount() == 1); + assertTrue(((Polygon) geom3).getPointCount() == 4); + + Geometry geom4 = result_cursor.next(); + assertTrue(geom4 == null); + } + } + + @Test + public void testFromProjection() { + MultiPoint multiPointInitial = new MultiPoint(); + multiPointInitial.add(-20037508.342789244, 3360107.7777777780); + multiPointInitial.add(-18924313.434856508, 3360107.7777777780); + multiPointInitial.add(-18924313.434856508, -3360107.7777777780); + multiPointInitial.add(-20037508.342789244, -3360107.7777777780); + Geometry geom1 = ((MultiPoint) multiPointInitial); + + SpatialReference sr = SpatialReference.create(102100); + + Envelope2D env = new Envelope2D(); + env.setCoords(/* xmin */-20037508.342788246, /* ymin */ + -30240971.958386172, /* xmax */20037508.342788246, /* ymax */ + 30240971.958386205); + // /*xmin*/ -20037508.342788246 + // /*ymin*/ -30240971.958386172 + // /*xmax*/ 20037508.342788246 + // /*ymax*/ 30240971.958386205 + + Polygon poly = new Polygon(); + poly.startPath(env.xmin, env.ymin); + poly.lineTo(env.xmin, env.ymax); + poly.lineTo(env.xmax, env.ymax); + poly.lineTo(env.xmax, env.ymin); + + Geometry geom2 = new Envelope(env); + // Geometry geom2 = poly; + + OperatorIntersection operatorIntersection = (OperatorIntersection) OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Intersection); + + MultiPoint multiPointOut = (MultiPoint) (operatorIntersection.execute( + geom1, geom2, sr, null)); + + assertTrue(multiPointOut.getCoordinates2D().length == 2); + assertTrue(multiPointOut.getCoordinates2D()[0].x == -18924313.434856508); + assertTrue(multiPointOut.getCoordinates2D()[0].y == 3360107.7777777780); + assertTrue(multiPointOut.getCoordinates2D()[1].x == -18924313.434856508); + assertTrue(multiPointOut.getCoordinates2D()[1].y == -3360107.7777777780); + } + + @Test + public void testIssue258128() { + Polygon poly1 = new Polygon(); + poly1.startPath(0, 0); + poly1.lineTo(0, 10); + poly1.lineTo(10, 10); + poly1.lineTo(10, 0); + + Polygon poly2 = new Polygon(); + poly2.startPath(10.5, 4); + poly2.lineTo(10.5, 8); + poly2.lineTo(14, 10); + + try { + GeometryCursor result_cursor = OperatorIntersection.local().execute(new SimpleGeometryCursor( + poly1), new SimpleGeometryCursor(poly2), SpatialReference + .create(4326), null, 1); + while (result_cursor.next() != null) { + + } + } catch (Exception e) { + assertTrue(false); + } + } + + @Test + public void testUnionTickTock() { + Polygon poly1 = new Polygon(); + poly1.startPath(0, 0); + poly1.lineTo(0, 10); + poly1.lineTo(10, 10); + poly1.lineTo(10, 0); + + Polygon poly2 = new Polygon(); + poly2.startPath(10.5, 4); + poly2.lineTo(10.5, 8); + poly2.lineTo(14, 10); + + Transformation2D trans = new Transformation2D(); + + Polygon poly3 = (Polygon) poly1.copy(); + trans.setShift(2, 3); + poly3.applyTransformation(trans); + + Polygon poly4 = (Polygon) poly1.copy(); + trans.setShift(-2, -3); + poly4.applyTransformation(trans); + + // Create + ListeningGeometryCursor gc = new ListeningGeometryCursor(); + GeometryCursor ticktock = OperatorUnion.local().execute(gc, null, null); + + // Use tick-tock to push a geometry and do a piece of work. + gc.tick(poly1); + ticktock.tock(); + gc.tick(poly2); + gc.tick(poly3);// skiped one tock just for testing. + ticktock.tock(); + gc.tick(poly4); + ticktock.tock(); + // Get the result + Geometry result = ticktock.next(); + + // Use ListeningGeometryCursor to put all geometries in. + ListeningGeometryCursor gc2 = new ListeningGeometryCursor(); + gc2.tick(poly1); + gc2.tick(poly2); + gc2.tick(poly3); + gc2.tick(poly4); + + GeometryCursor res = OperatorUnion.local().execute(gc2, null, null); + // Calling next will process all geometries at once. + Geometry result2 = res.next(); + assertTrue(result.equals(result2)); + } + + @Test + public void testIntersectionIssueLinePoly1() { + String wkt1 = new String("polygon((0 0, 10 0, 10 10, 0 10, 0 0))"); + String wkt2 = new String("linestring(9 5, 10 5, 9 4, 8 3)"); + Geometry g1 = OperatorImportFromWkt.local().execute(0, Geometry.Type.Unknown, wkt1, null); + Geometry g2 = OperatorImportFromWkt.local().execute(0, Geometry.Type.Unknown, wkt2, null); + Geometry res = OperatorIntersection.local().execute(g1, g2, null, null); + assertTrue(((Polyline) res).getPathCount() == 1); + assertTrue(((Polyline) res).getPointCount() == 4); + } + + @Test + public void testSharedEdgeIntersection_13() { + String s1 = "{\"rings\":[[[0.099604024000029767,0.2107958250000479],[0.14626826900007472,0.2107958250000479],[0.14626826900007472,0.18285316400005058],[0.099604024000029767,0.18285316400005058],[0.099604024000029767,0.2107958250000479]]]}"; + String s2 = "{\"paths\":[[[0.095692051000071388,0.15910190100004229],[0.10324853600002371,0.18285316400004228],[0.12359292700006108,0.18285316400004228],[0.12782611200003657,0.1705583920000322],[0.13537063000007138,0.18285316400004228]]]}"; + + Polygon polygon = (Polygon) TestCommonMethods.fromJson(s1).getGeometry(); + Polyline polyline = (Polyline) TestCommonMethods.fromJson(s2).getGeometry(); + SpatialReference sr = SpatialReference.create(4326); + + Geometry g = OperatorIntersection.local().execute(polygon, polyline, sr, null); + assertTrue(!g.isEmpty()); + } + + @Test + public void testIntersectionIssue2() { + String s1 = "{\"rings\":[[[-97.174860352323378,48.717174479818425],[-97.020624513410553,58.210155436624177],[-94.087641114245969,58.210155436624177],[-94.087641114245969,48.639781902013226],[-97.174860352323378,48.717174479818425]]]}"; + String s2 = "{\"rings\":[[[-94.08764111399995,52.68342763000004],[-94.08764111399995,56.835188018000053],[-90.285921520999977,62.345706350000057],[-94.08764111399995,52.68342763000004]]]}"; + + Polygon polygon1 = (Polygon) TestCommonMethods.fromJson(s1).getGeometry(); + Polygon polygon2 = (Polygon) TestCommonMethods.fromJson(s2).getGeometry(); + SpatialReference sr = SpatialReference.create(4326); + + GeometryCursor res = OperatorIntersection.local().execute(new SimpleGeometryCursor(polygon1), new SimpleGeometryCursor(polygon2), sr, null, 2); + Geometry g = res.next(); + assertTrue(g != null); + assertTrue(!g.isEmpty()); + Geometry g2 = res.next(); + assertTrue(g2 == null); + + String ss = "{\"paths\":[[[-94.08764111412296,52.68342763000004],[-94.08764111410767,56.83518801800005]]]}"; + Polyline polyline = (Polyline) TestCommonMethods.fromJson(ss).getGeometry(); + boolean eq = OperatorEquals.local().execute(g, polyline, sr, null); + assertTrue(eq); + } /* Point2D uniqueIntersectionPointOfNonDisjointGeometries(Geometry g1, Geometry g2, SpatialReference sr) { diff --git a/src/test/java/com/esri/core/geometry/TestIntervalTree.java b/src/test/java/com/esri/core/geometry/TestIntervalTree.java index ba82e1cc..43c1e300 100644 --- a/src/test/java/com/esri/core/geometry/TestIntervalTree.java +++ b/src/test/java/com/esri/core/geometry/TestIntervalTree.java @@ -31,317 +31,317 @@ import java.util.Random; public class TestIntervalTree extends TestCase { - @Override - protected void setUp() throws Exception { - super.setUp(); - } - - @Override - protected void tearDown() throws Exception { - super.tearDown(); - } - - static void construct(IntervalTreeImpl interval_tree, - ArrayList intervals) { - interval_tree.startConstruction(); - for (int i = 0; i < intervals.size(); i++) - interval_tree.addInterval(intervals.get(i)); - interval_tree.endConstruction(); - } - - @Test - public static void testIntervalTree() { - ArrayList intervals = new ArrayList(0); - - Envelope1D env0 = new Envelope1D(2, 3); - Envelope1D env1 = new Envelope1D(5, 13); - Envelope1D env2 = new Envelope1D(6, 9); - Envelope1D env3 = new Envelope1D(8, 10); - Envelope1D env4 = new Envelope1D(11, 12); - Envelope1D env5 = new Envelope1D(1, 3); - Envelope1D env6 = new Envelope1D(0, 2); - Envelope1D env7 = new Envelope1D(4, 7); - Envelope1D env8; - - intervals.add(env0); - intervals.add(env1); - intervals.add(env2); - intervals.add(env3); - intervals.add(env4); - intervals.add(env5); - intervals.add(env6); - intervals.add(env7); - - int counter; - IntervalTreeImpl intervalTree = new IntervalTreeImpl(false); - construct(intervalTree, intervals); - IntervalTreeImpl.IntervalTreeIteratorImpl iterator = intervalTree - .getIterator(new Envelope1D(-1, 14), 0.0); - - counter = 0; - while (iterator.next() != -1) - counter++; - assertTrue(counter == 8); - - iterator.resetIterator(new Envelope1D(2.5, 10.5), 0.0); - counter = 0; - while (iterator.next() != -1) - counter++; - assertTrue(counter == 6); - - iterator.resetIterator(5.0, 0.0); - counter = 0; - while (iterator.next() != -1) - counter++; - assertTrue(counter == 2); - - iterator.resetIterator(7, 0.0); - counter = 0; - while (iterator.next() != -1) - counter++; - assertTrue(counter == 3); - - iterator.resetIterator(new Envelope1D(2.0, 10.5), 0.0); - counter = 0; - while (iterator.next() != -1) - counter++; - assertTrue(counter == 7); - - iterator.resetIterator(new Envelope1D(2.5, 11), 0.0); - counter = 0; - while (iterator.next() != -1) - counter++; - assertTrue(counter == 7); - - iterator.resetIterator(new Envelope1D(2.1, 2.5), 0.0); - counter = 0; - while (iterator.next() != -1) - counter++; - assertTrue(counter == 2); - - iterator.resetIterator(new Envelope1D(2.1, 5), 0.0); - counter = 0; - while (iterator.next() != -1) - counter++; - assertTrue(counter == 4); - - iterator.resetIterator(new Envelope1D(2.0, 5), 0.0); - counter = 0; - while (iterator.next() != -1) - counter++; - assertTrue(counter == 5); - - iterator.resetIterator(new Envelope1D(5.0, 11), 0.0); - counter = 0; - while (iterator.next() != -1) - counter++; - assertTrue(counter == 5); - - iterator.resetIterator(new Envelope1D(8, 10.5), 0.0); - counter = 0; - while (iterator.next() != -1) - counter++; - assertTrue(counter == 3); - - iterator.resetIterator(new Envelope1D(10, 11), 0.0); - counter = 0; - while (iterator.next() != -1) - counter++; - assertTrue(counter == 3); - - iterator.resetIterator(new Envelope1D(10, 10.9), 0.0); - counter = 0; - while (iterator.next() != -1) - counter++; - assertTrue(counter == 2); - - iterator.resetIterator(new Envelope1D(11.5, 12), 0.0); - counter = 0; - while (iterator.next() != -1) - counter++; - assertTrue(counter == 2); - - env0 = new Envelope1D(0, 4); - env1 = new Envelope1D(6, 7); - env2 = new Envelope1D(9, 10); - env3 = new Envelope1D(9, 11); - env4 = new Envelope1D(7, 12); - env5 = new Envelope1D(13, 15); - env6 = new Envelope1D(1, 6); - env7 = new Envelope1D(3, 3); - env8 = new Envelope1D(8, 8); - - intervals.clear(); - intervals.add(env0); - intervals.add(env1); - intervals.add(env2); - intervals.add(env3); - intervals.add(env4); - intervals.add(env5); - intervals.add(env6); - intervals.add(env7); - intervals.add(env8); - - IntervalTreeImpl intervalTree2 = new IntervalTreeImpl(true); - construct(intervalTree2, intervals); - - intervalTree2.insert(0); - intervalTree2.insert(1); - intervalTree2.insert(2); - intervalTree2.insert(3); - intervalTree2.insert(4); - intervalTree2.insert(5); - intervalTree2.insert(6); - intervalTree2.insert(7); - intervalTree2.insert(8); - - iterator = intervalTree2.getIterator(new Envelope1D(8, 8), 0.0); - - counter = 0; - while (iterator.next() != -1) - counter++; - assertTrue(counter == 2); - - iterator.resetIterator(new Envelope1D(3, 7), 0.0); - counter = 0; - while (iterator.next() != -1) - counter++; - assertTrue(counter == 5); - - iterator.resetIterator(new Envelope1D(1, 3), 0.0); - counter = 0; - while (iterator.next() != -1) - counter++; - assertTrue(counter == 3); - - iterator.resetIterator(new Envelope1D(6, 9), 0.0); - counter = 0; - while (iterator.next() != -1) - counter++; - assertTrue(counter == 6); - - iterator.resetIterator(new Envelope1D(10, 14), 0.0); - counter = 0; - while (iterator.next() != -1) - counter++; - assertTrue(counter == 4); - - env0 = new Envelope1D(11, 14); - env1 = new Envelope1D(21, 36); - env2 = new Envelope1D(15, 19); - env3 = new Envelope1D(3, 8); - env4 = new Envelope1D(34, 38); - env5 = new Envelope1D(23, 27); - env6 = new Envelope1D(6, 36); - - intervals.clear(); - intervals.add(env0); - intervals.add(env1); - intervals.add(env2); - intervals.add(env3); - intervals.add(env4); - intervals.add(env5); - intervals.add(env6); - - IntervalTreeImpl intervalTree3 = new IntervalTreeImpl(false); - construct(intervalTree3, intervals); - iterator = intervalTree3.getIterator(new Envelope1D(50, 50), 0.0); - assert (iterator.next() == -1); - } - - @Test - public static void testIntervalTreeRandomConstruction() { - @SuppressWarnings("unused") - int pointcount = 0; - int passcount = 1000; - int figureSize = 50; - Envelope env = new Envelope(); - env.setCoords(-10000, -10000, 10000, 10000); - RandomCoordinateGenerator generator = new RandomCoordinateGenerator( - Math.max(figureSize, 10000), env, 0.001); - Random random = new Random(2013); - int rand_max = 98765; - ArrayList intervals = new ArrayList(); - AttributeStreamOfInt8 intervalsFound = new AttributeStreamOfInt8(0); - - for (int i = 0; i < passcount; i++) { - int r = figureSize; - if (r < 3) - continue; - Polygon poly = new Polygon(); - Point pt; - for (int j = 0; j < r; j++) { - int rand = random.nextInt(rand_max); - boolean bRandomNew = (r > 10) - && ((1.0 * rand) / rand_max > 0.95); - pt = generator.GetRandomCoord(); - if (j == 0 || bRandomNew) - poly.startPath(pt); - else - poly.lineTo(pt); - } - - { - intervals.clear(); - SegmentIterator seg_iter = poly.querySegmentIterator(); - Envelope1D interval; - - Envelope1D range = poly.queryInterval( - VertexDescription.Semantics.POSITION, 0); - range.vmin -= 0.01; - range.vmax += 0.01; - - while (seg_iter.nextPath()) { - while (seg_iter.hasNextSegment()) { - Segment segment = seg_iter.nextSegment(); - interval = segment.queryInterval( - VertexDescription.Semantics.POSITION, 0); - intervals.add(interval); - } - } - - intervalsFound.resize(intervals.size(), 0); - - // Just test construction for assertions - IntervalTreeImpl intervalTree = new IntervalTreeImpl(true); - construct(intervalTree, intervals); - - for (int j = 0; j < intervals.size(); j++) - intervalTree.insert(j); - - IntervalTreeImpl.IntervalTreeIteratorImpl iterator = intervalTree - .getIterator(range, 0.0); - - int count = 0; - int handle; - while ((handle = iterator.next()) != -1) { - count++; - intervalsFound.write(handle, (byte) 1); - } - - assertTrue(count == intervals.size()); - - for (int j = 0; j < intervalsFound.size(); j++) { - interval = intervals.get(j); - assertTrue(intervalsFound.read(j) == 1); - } - - for (int j = 0; j < intervals.size() >> 1; j++) - intervalTree.remove(j); - - iterator.resetIterator(range, 0.0); - - count = 0; - while ((handle = iterator.next()) != -1) { - count++; - intervalsFound.write(handle, (byte) 1); - } - - assertTrue(count == intervals.size() - (intervals.size() >> 1)); - - for (int j = (intervals.size() >> 1); j < intervals.size(); j++) - intervalTree.remove(j); - } - } - } + @Override + protected void setUp() throws Exception { + super.setUp(); + } + + @Override + protected void tearDown() throws Exception { + super.tearDown(); + } + + static void construct(IntervalTreeImpl interval_tree, + ArrayList intervals) { + interval_tree.startConstruction(); + for (int i = 0; i < intervals.size(); i++) + interval_tree.addInterval(intervals.get(i)); + interval_tree.endConstruction(); + } + + @Test + public static void testIntervalTree() { + ArrayList intervals = new ArrayList(0); + + Envelope1D env0 = new Envelope1D(2, 3); + Envelope1D env1 = new Envelope1D(5, 13); + Envelope1D env2 = new Envelope1D(6, 9); + Envelope1D env3 = new Envelope1D(8, 10); + Envelope1D env4 = new Envelope1D(11, 12); + Envelope1D env5 = new Envelope1D(1, 3); + Envelope1D env6 = new Envelope1D(0, 2); + Envelope1D env7 = new Envelope1D(4, 7); + Envelope1D env8; + + intervals.add(env0); + intervals.add(env1); + intervals.add(env2); + intervals.add(env3); + intervals.add(env4); + intervals.add(env5); + intervals.add(env6); + intervals.add(env7); + + int counter; + IntervalTreeImpl intervalTree = new IntervalTreeImpl(false); + construct(intervalTree, intervals); + IntervalTreeImpl.IntervalTreeIteratorImpl iterator = intervalTree + .getIterator(new Envelope1D(-1, 14), 0.0); + + counter = 0; + while (iterator.next() != -1) + counter++; + assertTrue(counter == 8); + + iterator.resetIterator(new Envelope1D(2.5, 10.5), 0.0); + counter = 0; + while (iterator.next() != -1) + counter++; + assertTrue(counter == 6); + + iterator.resetIterator(5.0, 0.0); + counter = 0; + while (iterator.next() != -1) + counter++; + assertTrue(counter == 2); + + iterator.resetIterator(7, 0.0); + counter = 0; + while (iterator.next() != -1) + counter++; + assertTrue(counter == 3); + + iterator.resetIterator(new Envelope1D(2.0, 10.5), 0.0); + counter = 0; + while (iterator.next() != -1) + counter++; + assertTrue(counter == 7); + + iterator.resetIterator(new Envelope1D(2.5, 11), 0.0); + counter = 0; + while (iterator.next() != -1) + counter++; + assertTrue(counter == 7); + + iterator.resetIterator(new Envelope1D(2.1, 2.5), 0.0); + counter = 0; + while (iterator.next() != -1) + counter++; + assertTrue(counter == 2); + + iterator.resetIterator(new Envelope1D(2.1, 5), 0.0); + counter = 0; + while (iterator.next() != -1) + counter++; + assertTrue(counter == 4); + + iterator.resetIterator(new Envelope1D(2.0, 5), 0.0); + counter = 0; + while (iterator.next() != -1) + counter++; + assertTrue(counter == 5); + + iterator.resetIterator(new Envelope1D(5.0, 11), 0.0); + counter = 0; + while (iterator.next() != -1) + counter++; + assertTrue(counter == 5); + + iterator.resetIterator(new Envelope1D(8, 10.5), 0.0); + counter = 0; + while (iterator.next() != -1) + counter++; + assertTrue(counter == 3); + + iterator.resetIterator(new Envelope1D(10, 11), 0.0); + counter = 0; + while (iterator.next() != -1) + counter++; + assertTrue(counter == 3); + + iterator.resetIterator(new Envelope1D(10, 10.9), 0.0); + counter = 0; + while (iterator.next() != -1) + counter++; + assertTrue(counter == 2); + + iterator.resetIterator(new Envelope1D(11.5, 12), 0.0); + counter = 0; + while (iterator.next() != -1) + counter++; + assertTrue(counter == 2); + + env0 = new Envelope1D(0, 4); + env1 = new Envelope1D(6, 7); + env2 = new Envelope1D(9, 10); + env3 = new Envelope1D(9, 11); + env4 = new Envelope1D(7, 12); + env5 = new Envelope1D(13, 15); + env6 = new Envelope1D(1, 6); + env7 = new Envelope1D(3, 3); + env8 = new Envelope1D(8, 8); + + intervals.clear(); + intervals.add(env0); + intervals.add(env1); + intervals.add(env2); + intervals.add(env3); + intervals.add(env4); + intervals.add(env5); + intervals.add(env6); + intervals.add(env7); + intervals.add(env8); + + IntervalTreeImpl intervalTree2 = new IntervalTreeImpl(true); + construct(intervalTree2, intervals); + + intervalTree2.insert(0); + intervalTree2.insert(1); + intervalTree2.insert(2); + intervalTree2.insert(3); + intervalTree2.insert(4); + intervalTree2.insert(5); + intervalTree2.insert(6); + intervalTree2.insert(7); + intervalTree2.insert(8); + + iterator = intervalTree2.getIterator(new Envelope1D(8, 8), 0.0); + + counter = 0; + while (iterator.next() != -1) + counter++; + assertTrue(counter == 2); + + iterator.resetIterator(new Envelope1D(3, 7), 0.0); + counter = 0; + while (iterator.next() != -1) + counter++; + assertTrue(counter == 5); + + iterator.resetIterator(new Envelope1D(1, 3), 0.0); + counter = 0; + while (iterator.next() != -1) + counter++; + assertTrue(counter == 3); + + iterator.resetIterator(new Envelope1D(6, 9), 0.0); + counter = 0; + while (iterator.next() != -1) + counter++; + assertTrue(counter == 6); + + iterator.resetIterator(new Envelope1D(10, 14), 0.0); + counter = 0; + while (iterator.next() != -1) + counter++; + assertTrue(counter == 4); + + env0 = new Envelope1D(11, 14); + env1 = new Envelope1D(21, 36); + env2 = new Envelope1D(15, 19); + env3 = new Envelope1D(3, 8); + env4 = new Envelope1D(34, 38); + env5 = new Envelope1D(23, 27); + env6 = new Envelope1D(6, 36); + + intervals.clear(); + intervals.add(env0); + intervals.add(env1); + intervals.add(env2); + intervals.add(env3); + intervals.add(env4); + intervals.add(env5); + intervals.add(env6); + + IntervalTreeImpl intervalTree3 = new IntervalTreeImpl(false); + construct(intervalTree3, intervals); + iterator = intervalTree3.getIterator(new Envelope1D(50, 50), 0.0); + assert (iterator.next() == -1); + } + + @Test + public static void testIntervalTreeRandomConstruction() { + @SuppressWarnings("unused") + int pointcount = 0; + int passcount = 1000; + int figureSize = 50; + Envelope env = new Envelope(); + env.setCoords(-10000, -10000, 10000, 10000); + RandomCoordinateGenerator generator = new RandomCoordinateGenerator( + Math.max(figureSize, 10000), env, 0.001); + Random random = new Random(2013); + int rand_max = 98765; + ArrayList intervals = new ArrayList(); + AttributeStreamOfInt8 intervalsFound = new AttributeStreamOfInt8(0); + + for (int i = 0; i < passcount; i++) { + int r = figureSize; + if (r < 3) + continue; + Polygon poly = new Polygon(); + Point pt; + for (int j = 0; j < r; j++) { + int rand = random.nextInt(rand_max); + boolean bRandomNew = (r > 10) + && ((1.0 * rand) / rand_max > 0.95); + pt = generator.GetRandomCoord(); + if (j == 0 || bRandomNew) + poly.startPath(pt); + else + poly.lineTo(pt); + } + + { + intervals.clear(); + SegmentIterator seg_iter = poly.querySegmentIterator(); + Envelope1D interval; + + Envelope1D range = poly.queryInterval( + VertexDescription.Semantics.POSITION, 0); + range.vmin -= 0.01; + range.vmax += 0.01; + + while (seg_iter.nextPath()) { + while (seg_iter.hasNextSegment()) { + Segment segment = seg_iter.nextSegment(); + interval = segment.queryInterval( + VertexDescription.Semantics.POSITION, 0); + intervals.add(interval); + } + } + + intervalsFound.resize(intervals.size(), 0); + + // Just test construction for assertions + IntervalTreeImpl intervalTree = new IntervalTreeImpl(true); + construct(intervalTree, intervals); + + for (int j = 0; j < intervals.size(); j++) + intervalTree.insert(j); + + IntervalTreeImpl.IntervalTreeIteratorImpl iterator = intervalTree + .getIterator(range, 0.0); + + int count = 0; + int handle; + while ((handle = iterator.next()) != -1) { + count++; + intervalsFound.write(handle, (byte) 1); + } + + assertTrue(count == intervals.size()); + + for (int j = 0; j < intervalsFound.size(); j++) { + interval = intervals.get(j); + assertTrue(intervalsFound.read(j) == 1); + } + + for (int j = 0; j < intervals.size() >> 1; j++) + intervalTree.remove(j); + + iterator.resetIterator(range, 0.0); + + count = 0; + while ((handle = iterator.next()) != -1) { + count++; + intervalsFound.write(handle, (byte) 1); + } + + assertTrue(count == intervals.size() - (intervals.size() >> 1)); + + for (int j = (intervals.size() >> 1); j < intervals.size(); j++) + intervalTree.remove(j); + } + } + } } diff --git a/src/test/java/com/esri/core/geometry/TestJSonGeometry.java b/src/test/java/com/esri/core/geometry/TestJSonGeometry.java index 6959309e..786fe093 100644 --- a/src/test/java/com/esri/core/geometry/TestJSonGeometry.java +++ b/src/test/java/com/esri/core/geometry/TestJSonGeometry.java @@ -31,41 +31,41 @@ import java.util.Map; public class TestJSonGeometry extends TestCase { - @Override - protected void setUp() throws Exception { - super.setUp(); - } + @Override + protected void setUp() throws Exception { + super.setUp(); + } - @Override - protected void tearDown() throws Exception { - super.tearDown(); - } + @Override + protected void tearDown() throws Exception { + super.tearDown(); + } - @Test - public void testGetSpatialReferenceFor4326() { - String completeStr = "GEOGCS[\"GCS_Sphere\",DATUM[\"D_Sphere\"," - + "SPHEROID[\"Sphere\",6371000.0,0.0]],PRIMEM[\"Greenwich\",0.0]," - + "UNIT[\"Degree\",0.0174532925199433]]"; + @Test + public void testGetSpatialReferenceFor4326() { + String completeStr = "GEOGCS[\"GCS_Sphere\",DATUM[\"D_Sphere\"," + + "SPHEROID[\"Sphere\",6371000.0,0.0]],PRIMEM[\"Greenwich\",0.0]," + + "UNIT[\"Degree\",0.0174532925199433]]"; - // 4326 GCS_WGS_1984 - SpatialReference sr = SpatialReference.create(completeStr); - assertNotNull(sr); - } + // 4326 GCS_WGS_1984 + SpatialReference sr = SpatialReference.create(completeStr); + assertNotNull(sr); + } } final class HashMapClassForTesting { - static Map SR_WKI_WKTs = new HashMap() { - /** - * added to get rid of warning - */ - private static final long serialVersionUID = 8630934425353750539L; + static Map SR_WKI_WKTs = new HashMap() { + /** + * added to get rid of warning + */ + private static final long serialVersionUID = 8630934425353750539L; - { - put(4035, - "GEOGCS[\"GCS_Sphere\",DATUM[\"D_Sphere\"," - + "SPHEROID[\"Sphere\",6371000.0,0.0]],PRIMEM[\"Greenwich\",0.0]," - + "UNIT[\"Degree\",0.0174532925199433]]"); - } - }; + { + put(4035, + "GEOGCS[\"GCS_Sphere\",DATUM[\"D_Sphere\"," + + "SPHEROID[\"Sphere\",6371000.0,0.0]],PRIMEM[\"Greenwich\",0.0]," + + "UNIT[\"Degree\",0.0174532925199433]]"); + } + }; } diff --git a/src/test/java/com/esri/core/geometry/TestJSonToGeomFromWkiOrWkt_CR177613.java b/src/test/java/com/esri/core/geometry/TestJSonToGeomFromWkiOrWkt_CR177613.java index 234b3dad..2fb7c2af 100644 --- a/src/test/java/com/esri/core/geometry/TestJSonToGeomFromWkiOrWkt_CR177613.java +++ b/src/test/java/com/esri/core/geometry/TestJSonToGeomFromWkiOrWkt_CR177613.java @@ -34,115 +34,115 @@ import java.io.IOException; public class TestJSonToGeomFromWkiOrWkt_CR177613 extends TestCase { - JsonFactory factory = new JsonFactory(); - - @Override - protected void setUp() throws Exception { - super.setUp(); - } - - @Override - protected void tearDown() throws Exception { - super.tearDown(); - } - - @Test - public void testPolygonWithEmptyWKT_NoWKI() throws JsonParseException, - IOException { - String jsonStringPg = "{ \"rings\" :[ [ [-97.06138,32.837], [-97.06133,32.836], " - + "[-97.06124,32.834], [-97.06127,32.832], [-97.06138,32.837] ], " - + "[ [-97.06326,32.759], [-97.06298,32.755], [-97.06153,32.749], [-97.06326,32.759] ]], " - + "\"spatialReference\" : {\"wkt\" : \"\"}}"; - JsonParser jsonParserPg = factory.createParser(jsonStringPg); - jsonParserPg.nextToken(); - - MapGeometry mapGeom = GeometryEngine.jsonToGeometry(jsonParserPg); - Utils.showProjectedGeometryInfo(mapGeom); - SpatialReference sr = mapGeom.getSpatialReference(); - assertTrue(sr == null); - } - - @Test - public void testOnlyWKI() throws JsonParseException, IOException { - String jsonStringSR = "{\"wkid\" : 4326}"; - JsonParser jsonParserSR = factory.createParser(jsonStringSR); - jsonParserSR.nextToken(); - - MapGeometry mapGeom = GeometryEngine.jsonToGeometry(jsonParserSR); - Utils.showProjectedGeometryInfo(mapGeom); - SpatialReference sr = mapGeom.getSpatialReference(); - assertTrue(sr == null); - } - - @Test - public void testMP2onCR175871() throws Exception { - Polygon pg = new Polygon(); - pg.startPath(-50, 10); - pg.lineTo(-50, 12); - pg.lineTo(-45, 12); - pg.lineTo(-45, 10); - - Polygon pg1 = new Polygon(); - pg1.startPath(-45, 10); - pg1.lineTo(-40, 10); - pg1.lineTo(-40, 8); - pg.add(pg1, false); - - try { - String jSonStr = GeometryEngine.geometryToJson(4326, pg); - JsonFactory jf = new JsonFactory(); - - JsonParser jp = jf.createParser(jSonStr); - jp.nextToken(); - MapGeometry mg = GeometryEngine.jsonToGeometry(jp); - Geometry gm = mg.getGeometry(); - assertEquals(Geometry.Type.Polygon, gm.getType()); - - Polygon pgNew = (Polygon) gm; - - assertEquals(pgNew.getPathCount(), pg.getPathCount()); - assertEquals(pgNew.getPointCount(), pg.getPointCount()); - assertEquals(pgNew.getSegmentCount(), pg.getSegmentCount()); - - assertEquals(pgNew.getPoint(0).getX(), pg.getPoint(0).getX(), - 0.000000001); - assertEquals(pgNew.getPoint(1).getX(), pg.getPoint(1).getX(), - 0.000000001); - assertEquals(pgNew.getPoint(2).getX(), pg.getPoint(2).getX(), - 0.000000001); - assertEquals(pgNew.getPoint(3).getX(), pg.getPoint(3).getX(), - 0.000000001); - - assertEquals(pgNew.getPoint(0).getY(), pg.getPoint(0).getY(), - 0.000000001); - assertEquals(pgNew.getPoint(1).getY(), pg.getPoint(1).getY(), - 0.000000001); - assertEquals(pgNew.getPoint(2).getY(), pg.getPoint(2).getY(), - 0.000000001); - assertEquals(pgNew.getPoint(3).getY(), pg.getPoint(3).getY(), - 0.000000001); - } catch (Exception ex) { - String err = ex.getMessage(); - System.out.print(err); - throw ex; - } - } - - public static int fromJsonToWkid(JsonParser parser) - throws JsonParseException, IOException { - int wkid = 0; - if (parser.getCurrentToken() != JsonToken.START_OBJECT) { - return 0; - } - - while (parser.nextToken() != JsonToken.END_OBJECT) { - String fieldName = parser.getCurrentName(); - - if ("wkid".equals(fieldName)) { - parser.nextToken(); - wkid = parser.getIntValue(); - } - } - return wkid; - } + JsonFactory factory = new JsonFactory(); + + @Override + protected void setUp() throws Exception { + super.setUp(); + } + + @Override + protected void tearDown() throws Exception { + super.tearDown(); + } + + @Test + public void testPolygonWithEmptyWKT_NoWKI() throws JsonParseException, + IOException { + String jsonStringPg = "{ \"rings\" :[ [ [-97.06138,32.837], [-97.06133,32.836], " + + "[-97.06124,32.834], [-97.06127,32.832], [-97.06138,32.837] ], " + + "[ [-97.06326,32.759], [-97.06298,32.755], [-97.06153,32.749], [-97.06326,32.759] ]], " + + "\"spatialReference\" : {\"wkt\" : \"\"}}"; + JsonParser jsonParserPg = factory.createParser(jsonStringPg); + jsonParserPg.nextToken(); + + MapGeometry mapGeom = GeometryEngine.jsonToGeometry(jsonParserPg); + Utils.showProjectedGeometryInfo(mapGeom); + SpatialReference sr = mapGeom.getSpatialReference(); + assertTrue(sr == null); + } + + @Test + public void testOnlyWKI() throws JsonParseException, IOException { + String jsonStringSR = "{\"wkid\" : 4326}"; + JsonParser jsonParserSR = factory.createParser(jsonStringSR); + jsonParserSR.nextToken(); + + MapGeometry mapGeom = GeometryEngine.jsonToGeometry(jsonParserSR); + Utils.showProjectedGeometryInfo(mapGeom); + SpatialReference sr = mapGeom.getSpatialReference(); + assertTrue(sr == null); + } + + @Test + public void testMP2onCR175871() throws Exception { + Polygon pg = new Polygon(); + pg.startPath(-50, 10); + pg.lineTo(-50, 12); + pg.lineTo(-45, 12); + pg.lineTo(-45, 10); + + Polygon pg1 = new Polygon(); + pg1.startPath(-45, 10); + pg1.lineTo(-40, 10); + pg1.lineTo(-40, 8); + pg.add(pg1, false); + + try { + String jSonStr = GeometryEngine.geometryToJson(4326, pg); + JsonFactory jf = new JsonFactory(); + + JsonParser jp = jf.createParser(jSonStr); + jp.nextToken(); + MapGeometry mg = GeometryEngine.jsonToGeometry(jp); + Geometry gm = mg.getGeometry(); + assertEquals(Geometry.Type.Polygon, gm.getType()); + + Polygon pgNew = (Polygon) gm; + + assertEquals(pgNew.getPathCount(), pg.getPathCount()); + assertEquals(pgNew.getPointCount(), pg.getPointCount()); + assertEquals(pgNew.getSegmentCount(), pg.getSegmentCount()); + + assertEquals(pgNew.getPoint(0).getX(), pg.getPoint(0).getX(), + 0.000000001); + assertEquals(pgNew.getPoint(1).getX(), pg.getPoint(1).getX(), + 0.000000001); + assertEquals(pgNew.getPoint(2).getX(), pg.getPoint(2).getX(), + 0.000000001); + assertEquals(pgNew.getPoint(3).getX(), pg.getPoint(3).getX(), + 0.000000001); + + assertEquals(pgNew.getPoint(0).getY(), pg.getPoint(0).getY(), + 0.000000001); + assertEquals(pgNew.getPoint(1).getY(), pg.getPoint(1).getY(), + 0.000000001); + assertEquals(pgNew.getPoint(2).getY(), pg.getPoint(2).getY(), + 0.000000001); + assertEquals(pgNew.getPoint(3).getY(), pg.getPoint(3).getY(), + 0.000000001); + } catch (Exception ex) { + String err = ex.getMessage(); + System.out.print(err); + throw ex; + } + } + + public static int fromJsonToWkid(JsonParser parser) + throws JsonParseException, IOException { + int wkid = 0; + if (parser.getCurrentToken() != JsonToken.START_OBJECT) { + return 0; + } + + while (parser.nextToken() != JsonToken.END_OBJECT) { + String fieldName = parser.getCurrentName(); + + if ("wkid".equals(fieldName)) { + parser.nextToken(); + wkid = parser.getIntValue(); + } + } + return wkid; + } } diff --git a/src/test/java/com/esri/core/geometry/TestJsonParser.java b/src/test/java/com/esri/core/geometry/TestJsonParser.java index 856f2045..3ba521bb 100644 --- a/src/test/java/com/esri/core/geometry/TestJsonParser.java +++ b/src/test/java/com/esri/core/geometry/TestJsonParser.java @@ -36,538 +36,538 @@ public class TestJsonParser extends TestCase { - JsonFactory factory = new JsonFactory(); - SpatialReference spatialReferenceWebMerc1 = SpatialReference.create(102100); - SpatialReference spatialReferenceWebMerc2 = SpatialReference.create(spatialReferenceWebMerc1.getLatestID()); - SpatialReference spatialReferenceWGS84 = SpatialReference.create(4326); - - @Override - protected void setUp() throws Exception { - super.setUp(); - } - - @Override - protected void tearDown() throws Exception { - super.tearDown(); - } - - @Test - public void test3DPoint() throws JsonParseException, IOException { - String jsonString3DPt = "{\"x\" : -118.15, \"y\" : 33.80, \"z\" : 10.0, \"spatialReference\" : {\"wkid\" : 4326}}"; - - JsonParser jsonParser3DPt = factory.createParser(jsonString3DPt); - MapGeometry point3DMP = GeometryEngine.jsonToGeometry(jsonParser3DPt); - assertTrue(-118.15 == ((Point) point3DMP.getGeometry()).getX()); - assertTrue(33.80 == ((Point) point3DMP.getGeometry()).getY()); - assertTrue(spatialReferenceWGS84.getID() == point3DMP.getSpatialReference().getID()); - } - - @Test - public void test3DPoint1() throws JsonParseException, IOException { - Point point1 = new Point(10.0, 20.0); - Point pointEmpty = new Point(); - { - JsonParser pointWebMerc1Parser = factory - .createParser(GeometryEngine.geometryToJson(spatialReferenceWebMerc1, point1)); - MapGeometry pointWebMerc1MP = GeometryEngine.jsonToGeometry(pointWebMerc1Parser); - assertTrue(point1.getX() == ((Point) pointWebMerc1MP.getGeometry()).getX()); - assertTrue(point1.getY() == ((Point) pointWebMerc1MP.getGeometry()).getY()); - int srIdOri = spatialReferenceWebMerc1.getID(); - int srIdAfter = pointWebMerc1MP.getSpatialReference().getID(); - assertTrue(srIdOri == srIdAfter || srIdAfter == 3857); - - pointWebMerc1Parser = factory.createParser(GeometryEngine.geometryToJson(null, point1)); - pointWebMerc1MP = GeometryEngine.jsonToGeometry(pointWebMerc1Parser); - assertTrue(null == pointWebMerc1MP.getSpatialReference()); - - String pointEmptyString = GeometryEngine.geometryToJson(spatialReferenceWebMerc1, pointEmpty); - pointWebMerc1Parser = factory.createParser(pointEmptyString); - - pointWebMerc1MP = GeometryEngine.jsonToGeometry(pointWebMerc1Parser); - assertTrue(pointWebMerc1MP.getGeometry().isEmpty()); - int srIdOri2 = spatialReferenceWebMerc1.getID(); - int srIdAfter2 = pointWebMerc1MP.getSpatialReference().getID(); - assertTrue(srIdOri2 == srIdAfter2 || srIdAfter2 == 3857); - } - } - - @Test - public void test3DPoint2() throws JsonParseException, IOException { - { - Point point1 = new Point(10.0, 20.0); - JsonParser pointWebMerc2Parser = factory - .createParser(GeometryEngine.geometryToJson(spatialReferenceWebMerc2, point1)); - MapGeometry pointWebMerc2MP = GeometryEngine.jsonToGeometry(pointWebMerc2Parser); - assertTrue(point1.getX() == ((Point) pointWebMerc2MP.getGeometry()).getX()); - assertTrue(point1.getY() == ((Point) pointWebMerc2MP.getGeometry()).getY()); - assertTrue(spatialReferenceWebMerc2.getLatestID() == pointWebMerc2MP.getSpatialReference().getLatestID()); - } - } - - @Test - public void test3DPoint3() throws JsonParseException, IOException { - { - Point point1 = new Point(10.0, 20.0); - JsonParser pointWgs84Parser = factory - .createParser(GeometryEngine.geometryToJson(spatialReferenceWGS84, point1)); - MapGeometry pointWgs84MP = GeometryEngine.jsonToGeometry(pointWgs84Parser); - assertTrue(point1.getX() == ((Point) pointWgs84MP.getGeometry()).getX()); - assertTrue(point1.getY() == ((Point) pointWgs84MP.getGeometry()).getY()); - assertTrue(spatialReferenceWGS84.getID() == pointWgs84MP.getSpatialReference().getID()); - } - } - - @Test - public void testMultiPoint() throws JsonParseException, IOException { - MultiPoint multiPoint1 = new MultiPoint(); - multiPoint1.add(-97.06138, 32.837); - multiPoint1.add(-97.06133, 32.836); - multiPoint1.add(-97.06124, 32.834); - multiPoint1.add(-97.06127, 32.832); - - { - JsonParser mPointWgs84Parser = factory - .createParser(GeometryEngine.geometryToJson(spatialReferenceWGS84, multiPoint1)); - MapGeometry mPointWgs84MP = GeometryEngine.jsonToGeometry(mPointWgs84Parser); - assertTrue(multiPoint1.getPointCount() == ((MultiPoint) mPointWgs84MP.getGeometry()).getPointCount()); - assertTrue(multiPoint1.getPoint(0).getX() == ((MultiPoint) mPointWgs84MP.getGeometry()).getPoint(0).getX()); - assertTrue(multiPoint1.getPoint(0).getY() == ((MultiPoint) mPointWgs84MP.getGeometry()).getPoint(0).getY()); - int lastIndex = multiPoint1.getPointCount() - 1; - assertTrue(multiPoint1.getPoint(lastIndex).getX() == ((MultiPoint) mPointWgs84MP.getGeometry()) - .getPoint(lastIndex).getX()); - assertTrue(multiPoint1.getPoint(lastIndex).getY() == ((MultiPoint) mPointWgs84MP.getGeometry()) - .getPoint(lastIndex).getY()); - - assertTrue(spatialReferenceWGS84.getID() == mPointWgs84MP.getSpatialReference().getID()); - - MultiPoint mPointEmpty = new MultiPoint(); - String mPointEmptyString = GeometryEngine.geometryToJson(spatialReferenceWGS84, mPointEmpty); - mPointWgs84Parser = factory.createParser(mPointEmptyString); - - mPointWgs84MP = GeometryEngine.jsonToGeometry(mPointWgs84Parser); - assertTrue(mPointWgs84MP.getGeometry().isEmpty()); - assertTrue(spatialReferenceWGS84.getID() == mPointWgs84MP.getSpatialReference().getID()); - - } - } - - @Test - public void testPolyline() throws JsonParseException, IOException { - Polyline polyline = new Polyline(); - polyline.startPath(-97.06138, 32.837); - polyline.lineTo(-97.06133, 32.836); - polyline.lineTo(-97.06124, 32.834); - polyline.lineTo(-97.06127, 32.832); - - polyline.startPath(-97.06326, 32.759); - polyline.lineTo(-97.06298, 32.755); - - { - JsonParser polylinePathsWgs84Parser = factory - .createParser(GeometryEngine.geometryToJson(spatialReferenceWGS84, polyline)); - MapGeometry mPolylineWGS84MP = GeometryEngine.jsonToGeometry(polylinePathsWgs84Parser); - - assertTrue(polyline.getPointCount() == ((Polyline) mPolylineWGS84MP.getGeometry()).getPointCount()); - assertTrue(polyline.getPoint(0).getX() == ((Polyline) mPolylineWGS84MP.getGeometry()).getPoint(0).getX()); - assertTrue(polyline.getPoint(0).getY() == ((Polyline) mPolylineWGS84MP.getGeometry()).getPoint(0).getY()); - - assertTrue(polyline.getPathCount() == ((Polyline) mPolylineWGS84MP.getGeometry()).getPathCount()); - assertTrue(polyline.getSegmentCount() == ((Polyline) mPolylineWGS84MP.getGeometry()).getSegmentCount()); - assertTrue(polyline.getSegmentCount(0) == ((Polyline) mPolylineWGS84MP.getGeometry()).getSegmentCount(0)); - assertTrue(polyline.getSegmentCount(1) == ((Polyline) mPolylineWGS84MP.getGeometry()).getSegmentCount(1)); - - int lastIndex = polyline.getPointCount() - 1; - assertTrue(polyline.getPoint(lastIndex).getX() == ((Polyline) mPolylineWGS84MP.getGeometry()) - .getPoint(lastIndex).getX()); - assertTrue(polyline.getPoint(lastIndex).getY() == ((Polyline) mPolylineWGS84MP.getGeometry()) - .getPoint(lastIndex).getY()); - - assertTrue(spatialReferenceWGS84.getID() == mPolylineWGS84MP.getSpatialReference().getID()); - - Polyline emptyPolyline = new Polyline(); - String emptyString = GeometryEngine.geometryToJson(spatialReferenceWGS84, emptyPolyline); - mPolylineWGS84MP = GeometryEngine.jsonToGeometry(factory.createParser(emptyString)); - assertTrue(mPolylineWGS84MP.getGeometry().isEmpty()); - assertTrue(spatialReferenceWGS84.getID() == mPolylineWGS84MP.getSpatialReference().getID()); - } - } - - @Test - public void testPolygon() throws JsonParseException, IOException { - Polygon polygon = new Polygon(); - polygon.startPath(-97.06138, 32.837); - polygon.lineTo(-97.06133, 32.836); - polygon.lineTo(-97.06124, 32.834); - polygon.lineTo(-97.06127, 32.832); - - polygon.startPath(-97.06326, 32.759); - polygon.lineTo(-97.06298, 32.755); - - { - JsonParser polygonPathsWgs84Parser = factory - .createParser(GeometryEngine.geometryToJson(spatialReferenceWGS84, polygon)); - MapGeometry mPolygonWGS84MP = GeometryEngine.jsonToGeometry(polygonPathsWgs84Parser); - - assertTrue(polygon.getPointCount() + 1 == ((Polygon) mPolygonWGS84MP.getGeometry()).getPointCount()); - assertTrue(polygon.getPoint(0).getX() == ((Polygon) mPolygonWGS84MP.getGeometry()).getPoint(0).getX()); - assertTrue(polygon.getPoint(0).getY() == ((Polygon) mPolygonWGS84MP.getGeometry()).getPoint(0).getY()); - - assertTrue(polygon.getPathCount() == ((Polygon) mPolygonWGS84MP.getGeometry()).getPathCount()); - assertTrue(polygon.getSegmentCount() + 1 == ((Polygon) mPolygonWGS84MP.getGeometry()).getSegmentCount()); - assertTrue(polygon.getSegmentCount(0) == ((Polygon) mPolygonWGS84MP.getGeometry()).getSegmentCount(0)); - assertTrue(polygon.getSegmentCount(1) + 1 == ((Polygon) mPolygonWGS84MP.getGeometry()).getSegmentCount(1)); - - int lastIndex = polygon.getPointCount() - 1; - assertTrue(polygon.getPoint(lastIndex).getX() == ((Polygon) mPolygonWGS84MP.getGeometry()) - .getPoint(lastIndex).getX()); - assertTrue(polygon.getPoint(lastIndex).getY() == ((Polygon) mPolygonWGS84MP.getGeometry()) - .getPoint(lastIndex).getY()); - - assertTrue(spatialReferenceWGS84.getID() == mPolygonWGS84MP.getSpatialReference().getID()); - - Polygon emptyPolygon = new Polygon(); - String emptyPolygonString = GeometryEngine.geometryToJson(spatialReferenceWGS84, emptyPolygon); - polygonPathsWgs84Parser = factory.createParser(emptyPolygonString); - mPolygonWGS84MP = GeometryEngine.jsonToGeometry(polygonPathsWgs84Parser); - - assertTrue(mPolygonWGS84MP.getGeometry().isEmpty()); - assertTrue(spatialReferenceWGS84.getID() == mPolygonWGS84MP.getSpatialReference().getID()); - } - } - - @Test - public void testEnvelope() throws JsonParseException, IOException { - Envelope envelope = new Envelope(); - envelope.setCoords(-109.55, 25.76, -86.39, 49.94); - - { - JsonParser envelopeWGS84Parser = factory - .createParser(GeometryEngine.geometryToJson(spatialReferenceWGS84, envelope)); - MapGeometry envelopeWGS84MP = GeometryEngine.jsonToGeometry(envelopeWGS84Parser); - assertTrue(envelope.isEmpty() == envelopeWGS84MP.getGeometry().isEmpty()); - assertTrue(envelope.getXMax() == ((Envelope) envelopeWGS84MP.getGeometry()).getXMax()); - assertTrue(envelope.getYMax() == ((Envelope) envelopeWGS84MP.getGeometry()).getYMax()); - assertTrue(envelope.getXMin() == ((Envelope) envelopeWGS84MP.getGeometry()).getXMin()); - assertTrue(envelope.getYMin() == ((Envelope) envelopeWGS84MP.getGeometry()).getYMin()); - assertTrue(spatialReferenceWGS84.getID() == envelopeWGS84MP.getSpatialReference().getID()); - - Envelope emptyEnvelope = new Envelope(); - String emptyEnvString = GeometryEngine.geometryToJson(spatialReferenceWGS84, emptyEnvelope); - envelopeWGS84Parser = factory.createParser(emptyEnvString); - envelopeWGS84MP = GeometryEngine.jsonToGeometry(envelopeWGS84Parser); - - assertTrue(envelopeWGS84MP.getGeometry().isEmpty()); - assertTrue(spatialReferenceWGS84.getID() == envelopeWGS84MP.getSpatialReference().getID()); - } - } - - @Test - public void testCR181369() throws JsonParseException, IOException { - // CR181369 - { - String jsonStringPointAndWKT = "{\"x\":10.0,\"y\":20.0,\"spatialReference\":{\"wkt\" : \"PROJCS[\\\"NAD83_UTM_zone_15N\\\",GEOGCS[\\\"GCS_North_American_1983\\\",DATUM[\\\"D_North_American_1983\\\",SPHEROID[\\\"GRS_1980\\\",6378137.0,298.257222101]],PRIMEM[\\\"Greenwich\\\",0.0],UNIT[\\\"Degree\\\",0.0174532925199433]],PROJECTION[\\\"Transverse_Mercator\\\"],PARAMETER[\\\"false_easting\\\",500000.0],PARAMETER[\\\"false_northing\\\",0.0],PARAMETER[\\\"central_meridian\\\",-93.0],PARAMETER[\\\"scale_factor\\\",0.9996],PARAMETER[\\\"latitude_of_origin\\\",0.0],UNIT[\\\"Meter\\\",1.0]]\"} }"; - JsonParser jsonParserPointAndWKT = factory.createParser(jsonStringPointAndWKT); - MapGeometry mapGeom2 = GeometryEngine.jsonToGeometry(jsonParserPointAndWKT); - String jsonStringPointAndWKT2 = GeometryEngine.geometryToJson(mapGeom2.getSpatialReference(), - mapGeom2.getGeometry()); - JsonParser jsonParserPointAndWKT2 = factory.createParser(jsonStringPointAndWKT2); - MapGeometry mapGeom3 = GeometryEngine.jsonToGeometry(jsonParserPointAndWKT2); - assertTrue(((Point) mapGeom2.getGeometry()).getX() == ((Point) mapGeom3.getGeometry()).getX()); - assertTrue(((Point) mapGeom2.getGeometry()).getY() == ((Point) mapGeom3.getGeometry()).getY()); - assertTrue(mapGeom2.getSpatialReference().getText().equals(mapGeom3.getSpatialReference().getText())); - assertTrue(mapGeom2.getSpatialReference().getID() == mapGeom3.getSpatialReference().getID()); - } - } - - @Test - public void testSpatialRef() throws JsonParseException, IOException { - // String jsonStringPt = - // "{\"x\":-20037508.342787,\"y\":20037508.342787},\"spatialReference\":{\"wkid\":102100}}"; - String jsonStringPt = "{\"x\":10.0,\"y\":20.0,\"spatialReference\":{\"wkid\": 102100}}";// 102100 - @SuppressWarnings("unused") - String jsonStringPt2 = "{\"x\":10.0,\"y\":20.0,\"spatialReference\":{\"wkid\":4326}}"; - String jsonStringMpt = "{ \"points\" : [ [-97.06138,32.837], [-97.06133,32.836], [-97.06124,32.834], [-97.06127,32.832] ], \"spatialReference\" : {\"wkid\" : 4326}}";// 4326 - String jsonStringMpt3D = "{\"hasZs\" : true,\"points\" : [ [-97.06138,32.837,35.0], [-97.06133,32.836,35.1], [-97.06124,32.834,35.2], [-97.06127,32.832,35.3] ],\"spatialReference\" : {\"wkid\" : 4326}}"; - String jsonStringPl = "{\"paths\" : [ [ [-97.06138,32.837], [-97.06133,32.836], [-97.06124,32.834], [-97.06127,32.832] ], [ [-97.06326,32.759], [-97.06298,32.755] ]],\"spatialReference\" : {\"wkid\" : 4326}}"; - String jsonStringPl3D = "{\"hasMs\" : true,\"paths\" : [[ [-97.06138,32.837,5], [-97.06133,32.836,6], [-97.06124,32.834,7], [-97.06127,32.832,8] ],[ [-97.06326,32.759], [-97.06298,32.755] ]],\"spatialReference\" : {\"wkid\" : 4326}}"; - String jsonStringPg = "{ \"rings\" :[ [ [-97.06138,32.837], [-97.06133,32.836], [-97.06124,32.834], [-97.06127,32.832], [-97.06138,32.837] ], [ [-97.06326,32.759], [-97.06298,32.755], [-97.06153,32.749], [-97.06326,32.759] ]], \"spatialReference\" : {\"wkt\" : \"\"}}"; - String jsonStringPg3D = "{\"hasZs\" : true,\"hasMs\" : true,\"rings\" : [ [ [-97.06138, 32.837, 35.1, 4], [-97.06133, 32.836, 35.2, 4.1], [-97.06124, 32.834, 35.3, 4.2], [-97.06127, 32.832, 35.2, 44.3], [-97.06138, 32.837, 35.1, 4] ],[ [-97.06326, 32.759, 35.4], [-97.06298, 32.755, 35.5], [-97.06153, 32.749, 35.6], [-97.06326, 32.759, 35.4] ]],\"spatialReference\" : {\"wkid\" : 4326}}"; - String jsonStringPg2 = "{ \"spatialReference\" : {\"wkid\" : 4326}, \"rings\" : [[[-118.35,32.81],[-118.42,32.806],[-118.511,32.892],[-118.35,32.81]]]}"; - String jsonStringPg3 = "{ \"spatialReference\": {\"layerName\":\"GAS_POINTS\",\"name\":null,\"sdesrid\":102100,\"wkid\":102100,\"wkt\":null}}"; - String jsonString2SpatialReferences = "{ \"spatialReference\": {\"layerName\":\"GAS_POINTS\",\"name\":null,\"sdesrid\":102100,\"wkid\":102100,\"wkt\":\"GEOGCS[\\\"GCS_WGS_1984\\\",DATUM[\\\"D_WGS_1984\\\",SPHEROID[\\\"WGS_1984\\\",6378137,298.257223563]],PRIMEM[\\\"Greenwich\\\",0],UNIT[\\\"Degree\\\",0.017453292519943295]]\"}}"; - String jsonString2SpatialReferences2 = "{ \"spatialReference\": {\"layerName\":\"GAS_POINTS\",\"name\":null,\"sdesrid\":10,\"wkid\":10,\"wkt\":\"GEOGCS[\\\"GCS_WGS_1984\\\",DATUM[\\\"D_WGS_1984\\\",SPHEROID[\\\"WGS_1984\\\",6378137,298.257223563]],PRIMEM[\\\"Greenwich\\\",0],UNIT[\\\"Degree\\\",0.017453292519943295]]\"}}"; - String jsonStringSR = "{\"wkid\" : 4326}"; - String jsonStringEnv = "{\"xmin\" : -109.55, \"ymin\" : 25.76, \"xmax\" : -86.39, \"ymax\" : 49.94,\"spatialReference\" : {\"wkid\" : 4326}}"; - String jsonStringHongKon = "{\"xmin\" : -122.55, \"ymin\" : 37.65, \"xmax\" : -122.28, \"ymax\" : 37.84,\"spatialReference\" : {\"wkid\" : 4326}}"; - @SuppressWarnings("unused") - String jsonStringWKT = " {\"wkt\" : \"GEOGCS[\\\"GCS_WGS_1984\\\",DATUM[\\\"D_WGS_1984\\\",SPHEROID[\\\"WGS_1984\\\",6378137,298.257223563]],PRIMEM[\\\"Greenwich\\\",0],UNIT[\\\"Degree\\\",0.017453292519943295]]\"}"; - String jsonStringInvalidWKID = "{\"x\":10.0,\"y\":20.0},\"spatialReference\":{\"wkid\":35253523}}"; - String jsonStringOregon = "{\"xmin\":7531831.219849482,\"ymin\":585702.9799639136,\"xmax\":7750143.589982405,\"ymax\":733289.6299999952,\"spatialReference\":{\"wkid\":102726}}"; - - JsonParser jsonParserPt = factory.createParser(jsonStringPt); - JsonParser jsonParserMpt = factory.createParser(jsonStringMpt); - JsonParser jsonParserMpt3D = factory.createParser(jsonStringMpt3D); - JsonParser jsonParserPl = factory.createParser(jsonStringPl); - JsonParser jsonParserPl3D = factory.createParser(jsonStringPl3D); - JsonParser jsonParserPg = factory.createParser(jsonStringPg); - JsonParser jsonParserPg3D = factory.createParser(jsonStringPg3D); - JsonParser jsonParserPg2 = factory.createParser(jsonStringPg2); - @SuppressWarnings("unused") - JsonParser jsonParserSR = factory.createParser(jsonStringSR); - JsonParser jsonParserEnv = factory.createParser(jsonStringEnv); - JsonParser jsonParserPg3 = factory.createParser(jsonStringPg3); - @SuppressWarnings("unused") - JsonParser jsonParserCrazy1 = factory.createParser(jsonString2SpatialReferences); - @SuppressWarnings("unused") - JsonParser jsonParserCrazy2 = factory.createParser(jsonString2SpatialReferences2); - JsonParser jsonParserInvalidWKID = factory.createParser(jsonStringInvalidWKID); - @SuppressWarnings("unused") - JsonParser jsonParseHongKon = factory.createParser(jsonStringHongKon); - JsonParser jsonParseOregon = factory.createParser(jsonStringOregon); - - MapGeometry mapGeom = GeometryEngine.jsonToGeometry(jsonParserPt); - // showProjectedGeometryInfo(mapGeom); - Assert.assertTrue(mapGeom.getSpatialReference().getID() == 102100); - - MapGeometry mapGeomOregon = GeometryEngine.jsonToGeometry(jsonParseOregon); - Assert.assertTrue(mapGeomOregon.getSpatialReference().getID() == 102726); - - mapGeom = GeometryEngine.jsonToGeometry(jsonParserMpt); - Assert.assertTrue(mapGeom.getSpatialReference().getID() == 4326); - - mapGeom = GeometryEngine.jsonToGeometry(jsonParserMpt3D); - Assert.assertTrue(mapGeom.getSpatialReference().getID() == 4326); - { - Assert.assertTrue(((MultiPoint) mapGeom.getGeometry()).getPoint(0).getX() == -97.06138); - Assert.assertTrue(((MultiPoint) mapGeom.getGeometry()).getPoint(0).getY() == 32.837); - Assert.assertTrue(((MultiPoint) mapGeom.getGeometry()).getPoint(3).getX() == -97.06127); - Assert.assertTrue(((MultiPoint) mapGeom.getGeometry()).getPoint(3).getY() == 32.832); - } - // showProjectedGeometryInfo(mapGeom); - - mapGeom = GeometryEngine.jsonToGeometry(jsonParserPl); - Assert.assertTrue(mapGeom.getSpatialReference().getID() == 4326); - // showProjectedGeometryInfo(mapGeom); - - mapGeom = GeometryEngine.jsonToGeometry(jsonParserPl3D); - { - // [[ [-97.06138,32.837,5], [-97.06133,32.836,6], - // [-97.06124,32.834,7], [-97.06127,32.832,8] ], - // [ [-97.06326,32.759], [-97.06298,32.755] ]]"; - Assert.assertTrue(((Polyline) mapGeom.getGeometry()).getPoint(0).getX() == -97.06138); - Assert.assertTrue(((Polyline) mapGeom.getGeometry()).getPoint(0).getY() == 32.837); - int lastIndex = ((Polyline) mapGeom.getGeometry()).getPointCount() - 1; - Assert.assertTrue(((Polyline) mapGeom.getGeometry()).getPoint(lastIndex).getX() == -97.06298);// -97.06153, - // 32.749 - Assert.assertTrue(((Polyline) mapGeom.getGeometry()).getPoint(lastIndex).getY() == 32.755); - int lastIndexFirstLine = ((Polyline) mapGeom.getGeometry()).getPathEnd(0) - 1; - Assert.assertTrue(((Polyline) mapGeom.getGeometry()).getPoint(lastIndexFirstLine).getX() == -97.06127);// -97.06153, - // 32.749 - Assert.assertTrue(((Polyline) mapGeom.getGeometry()).getPoint(lastIndexFirstLine).getY() == 32.832); - } - - mapGeom = GeometryEngine.jsonToGeometry(jsonParserPg); - Assert.assertTrue(mapGeom.getSpatialReference() == null); - - mapGeom = GeometryEngine.jsonToGeometry(jsonParserPg3D); - { - Assert.assertTrue(((Polygon) mapGeom.getGeometry()).getPoint(0).getX() == -97.06138); - Assert.assertTrue(((Polygon) mapGeom.getGeometry()).getPoint(0).getY() == 32.837); - int lastIndex = ((Polygon) mapGeom.getGeometry()).getPointCount() - 1; - Assert.assertTrue(((Polygon) mapGeom.getGeometry()).getPoint(lastIndex).getX() == -97.06153);// -97.06153, - // 32.749 - Assert.assertTrue(((Polygon) mapGeom.getGeometry()).getPoint(lastIndex).getY() == 32.749); - } - - mapGeom = GeometryEngine.jsonToGeometry(jsonParserPg2); - Assert.assertTrue(mapGeom.getSpatialReference().getID() == 4326); - // showProjectedGeometryInfo(mapGeom); - - mapGeom = GeometryEngine.jsonToGeometry(jsonParserPg3); - Assert.assertTrue(mapGeom.getSpatialReference().getID() == 102100); - // showProjectedGeometryInfo(mapGeom); - - // mapGeom = GeometryEngine.jsonToGeometry(jsonParserCrazy1); - // Assert.assertTrue(mapGeom.getSpatialReference().getText().equals("")); - // showProjectedGeometryInfo(mapGeom); - - mapGeom = GeometryEngine.jsonToGeometry(jsonParserEnv); - Assert.assertTrue(mapGeom.getSpatialReference().getID() == 4326); - // showProjectedGeometryInfo(mapGeom); - - try { - GeometryEngine.jsonToGeometry(jsonParserInvalidWKID); - } catch (Exception ex) { - Assert.assertTrue("Should not throw for invalid wkid", false); - } - } - - @Test - public void testMP2onCR175871() throws Exception { - Polygon pg = new Polygon(); - pg.startPath(-50, 10); - pg.lineTo(-50, 12); - pg.lineTo(-45, 12); - pg.lineTo(-45, 10); - - Polygon pg1 = new Polygon(); - pg1.startPath(-45, 10); - pg1.lineTo(-40, 10); - pg1.lineTo(-40, 8); - pg.add(pg1, false); - - SpatialReference spatialReference = SpatialReference.create(4326); - - try { - String jSonStr = GeometryEngine.geometryToJson(spatialReference, pg); - JsonFactory jf = new JsonFactory(); - - JsonParser jp = jf.createParser(jSonStr); - jp.nextToken(); - MapGeometry mg = GeometryEngine.jsonToGeometry(jp); - Geometry gm = mg.getGeometry(); - Assert.assertEquals(Geometry.Type.Polygon, gm.getType()); - Assert.assertTrue(mg.getSpatialReference().getID() == 4326); - - Polygon pgNew = (Polygon) gm; - - Assert.assertEquals(pgNew.getPathCount(), pg.getPathCount()); - Assert.assertEquals(pgNew.getPointCount(), pg.getPointCount()); - Assert.assertEquals(pgNew.getSegmentCount(), pg.getSegmentCount()); - - Assert.assertEquals(pgNew.getPoint(0).getX(), pg.getPoint(0).getX(), 0.000000001); - Assert.assertEquals(pgNew.getPoint(1).getX(), pg.getPoint(1).getX(), 0.000000001); - Assert.assertEquals(pgNew.getPoint(2).getX(), pg.getPoint(2).getX(), 0.000000001); - Assert.assertEquals(pgNew.getPoint(3).getX(), pg.getPoint(3).getX(), 0.000000001); - - Assert.assertEquals(pgNew.getPoint(0).getY(), pg.getPoint(0).getY(), 0.000000001); - Assert.assertEquals(pgNew.getPoint(1).getY(), pg.getPoint(1).getY(), 0.000000001); - Assert.assertEquals(pgNew.getPoint(2).getY(), pg.getPoint(2).getY(), 0.000000001); - Assert.assertEquals(pgNew.getPoint(3).getY(), pg.getPoint(3).getY(), 0.000000001); - } catch (Exception ex) { - String err = ex.getMessage(); - System.out.print(err); - throw ex; - } - } - - @Test - public static int fromJsonToWkid(JsonParser parser) throws JsonParseException, IOException { - int wkid = 0; - if (parser.getCurrentToken() != JsonToken.START_OBJECT) { - return 0; - } - - while (parser.nextToken() != JsonToken.END_OBJECT) { - String fieldName = parser.getCurrentName(); - - if ("wkid".equals(fieldName)) { - parser.nextToken(); - wkid = parser.getIntValue(); - } - } - return wkid; - } - - @SuppressWarnings("unused") - private static void showProjectedGeometryInfo(MapGeometry mapGeom) { - System.out.println("\n"); - MapGeometry geom = mapGeom; - // while ((geom = geomCursor.next()) != null) { - - if (geom.getGeometry() instanceof Point) { - Point pnt = (Point) geom.getGeometry(); - System.out.println("Point(" + pnt.getX() + " , " + pnt.getY() + ")"); - if (geom.getSpatialReference() == null) { - System.out.println("No spatial reference"); - } else { - System.out.println("wkid: " + geom.getSpatialReference().getID()); - } - - } else if (geom.getGeometry() instanceof MultiPoint) { - MultiPoint mp = (MultiPoint) geom.getGeometry(); - System.out.println("Multipoint has " + mp.getPointCount() + " points."); - - System.out.println("wkid: " + geom.getSpatialReference().getID()); - - } else if (geom.getGeometry() instanceof Polygon) { - Polygon mp = (Polygon) geom.getGeometry(); - System.out.println("Polygon has " + mp.getPointCount() + " points and " + mp.getPathCount() + " parts."); - if (mp.getPathCount() > 1) { - System.out.println("Part start of 2nd segment : " + mp.getPathStart(1)); - System.out.println("Part end of 2nd segment : " + mp.getPathEnd(1)); - System.out.println("Part size of 2nd segment : " + mp.getPathSize(1)); - - int start = mp.getPathStart(1); - int end = mp.getPathEnd(1); - for (int i = start; i < end; i++) { - Point pp = mp.getPoint(i); - System.out.println("Point(" + i + ") = (" + pp.getX() + ", " + pp.getY() + ")"); - } - } - System.out.println("wkid: " + geom.getSpatialReference().getID()); - - } else if (geom.getGeometry() instanceof Polyline) { - Polyline mp = (Polyline) geom.getGeometry(); - System.out.println("Polyline has " + mp.getPointCount() + " points and " + mp.getPathCount() + " parts."); - System.out.println("Part start of 2nd segment : " + mp.getPathStart(1)); - System.out.println("Part end of 2nd segment : " + mp.getPathEnd(1)); - System.out.println("Part size of 2nd segment : " + mp.getPathSize(1)); - int start = mp.getPathStart(1); - int end = mp.getPathEnd(1); - for (int i = start; i < end; i++) { - Point pp = mp.getPoint(i); - System.out.println("Point(" + i + ") = (" + pp.getX() + ", " + pp.getY() + ")"); - } - - System.out.println("wkid: " + geom.getSpatialReference().getID()); - } - } - - @Test - public void testGeometryToJSON() { - Polygon geom = new Polygon(); - geom.startPath(new Point(-113, 34)); - geom.lineTo(new Point(-105, 34)); - geom.lineTo(new Point(-108, 40)); - - String outputPolygon1 = GeometryEngine.geometryToJson(-1, geom);// Test - // WKID - // == -1 - // System.out.println("Geom JSON STRING is" + outputPolygon1); - String correctPolygon1 = "{\"rings\":[[[-113,34],[-105,34],[-108,40],[-113,34]]]}"; - - assertEquals(correctPolygon1, outputPolygon1); - - String outputPolygon2 = GeometryEngine.geometryToJson(4326, geom); - // System.out.println("Geom JSON STRING is" + outputPolygon2); - - String correctPolygon2 = "{\"rings\":[[[-113,34],[-105,34],[-108,40],[-113,34]]],\"spatialReference\":{\"wkid\":4326}}"; - assertEquals(correctPolygon2, outputPolygon2); - } - - @Test - public void testGeometryToJSONOldID() throws Exception {// CR - Polygon geom = new Polygon(); - geom.startPath(new Point(-113, 34)); - geom.lineTo(new Point(-105, 34)); - geom.lineTo(new Point(-108, 40)); - String outputPolygon = GeometryEngine.geometryToJson(SpatialReference.create(3857), geom);// Test - // WKID - // == - // -1 - String correctPolygon = "{\"rings\":[[[-113,34],[-105,34],[-108,40],[-113,34]]],\"spatialReference\":{\"wkid\":102100,\"latestWkid\":3857}}"; - assertTrue(outputPolygon.equals(correctPolygon)); - JsonFactory jf = new JsonFactory(); - JsonParser jp = jf.createParser(outputPolygon); - jp.nextToken(); - MapGeometry mg = GeometryEngine.jsonToGeometry(jp); - @SuppressWarnings("unused") - int srId = mg.getSpatialReference().getID(); - @SuppressWarnings("unused") - int srOldId = mg.getSpatialReference().getOldID(); - Assert.assertTrue(mg.getSpatialReference().getID() == 3857); - Assert.assertTrue(mg.getSpatialReference().getLatestID() == 3857); - Assert.assertTrue(mg.getSpatialReference().getOldID() == 102100); - } + JsonFactory factory = new JsonFactory(); + SpatialReference spatialReferenceWebMerc1 = SpatialReference.create(102100); + SpatialReference spatialReferenceWebMerc2 = SpatialReference.create(spatialReferenceWebMerc1.getLatestID()); + SpatialReference spatialReferenceWGS84 = SpatialReference.create(4326); + + @Override + protected void setUp() throws Exception { + super.setUp(); + } + + @Override + protected void tearDown() throws Exception { + super.tearDown(); + } + + @Test + public void test3DPoint() throws JsonParseException, IOException { + String jsonString3DPt = "{\"x\" : -118.15, \"y\" : 33.80, \"z\" : 10.0, \"spatialReference\" : {\"wkid\" : 4326}}"; + + JsonParser jsonParser3DPt = factory.createParser(jsonString3DPt); + MapGeometry point3DMP = GeometryEngine.jsonToGeometry(jsonParser3DPt); + assertTrue(-118.15 == ((Point) point3DMP.getGeometry()).getX()); + assertTrue(33.80 == ((Point) point3DMP.getGeometry()).getY()); + assertTrue(spatialReferenceWGS84.getID() == point3DMP.getSpatialReference().getID()); + } + + @Test + public void test3DPoint1() throws JsonParseException, IOException { + Point point1 = new Point(10.0, 20.0); + Point pointEmpty = new Point(); + { + JsonParser pointWebMerc1Parser = factory + .createParser(GeometryEngine.geometryToJson(spatialReferenceWebMerc1, point1)); + MapGeometry pointWebMerc1MP = GeometryEngine.jsonToGeometry(pointWebMerc1Parser); + assertTrue(point1.getX() == ((Point) pointWebMerc1MP.getGeometry()).getX()); + assertTrue(point1.getY() == ((Point) pointWebMerc1MP.getGeometry()).getY()); + int srIdOri = spatialReferenceWebMerc1.getID(); + int srIdAfter = pointWebMerc1MP.getSpatialReference().getID(); + assertTrue(srIdOri == srIdAfter || srIdAfter == 3857); + + pointWebMerc1Parser = factory.createParser(GeometryEngine.geometryToJson(null, point1)); + pointWebMerc1MP = GeometryEngine.jsonToGeometry(pointWebMerc1Parser); + assertTrue(null == pointWebMerc1MP.getSpatialReference()); + + String pointEmptyString = GeometryEngine.geometryToJson(spatialReferenceWebMerc1, pointEmpty); + pointWebMerc1Parser = factory.createParser(pointEmptyString); + + pointWebMerc1MP = GeometryEngine.jsonToGeometry(pointWebMerc1Parser); + assertTrue(pointWebMerc1MP.getGeometry().isEmpty()); + int srIdOri2 = spatialReferenceWebMerc1.getID(); + int srIdAfter2 = pointWebMerc1MP.getSpatialReference().getID(); + assertTrue(srIdOri2 == srIdAfter2 || srIdAfter2 == 3857); + } + } + + @Test + public void test3DPoint2() throws JsonParseException, IOException { + { + Point point1 = new Point(10.0, 20.0); + JsonParser pointWebMerc2Parser = factory + .createParser(GeometryEngine.geometryToJson(spatialReferenceWebMerc2, point1)); + MapGeometry pointWebMerc2MP = GeometryEngine.jsonToGeometry(pointWebMerc2Parser); + assertTrue(point1.getX() == ((Point) pointWebMerc2MP.getGeometry()).getX()); + assertTrue(point1.getY() == ((Point) pointWebMerc2MP.getGeometry()).getY()); + assertTrue(spatialReferenceWebMerc2.getLatestID() == pointWebMerc2MP.getSpatialReference().getLatestID()); + } + } + + @Test + public void test3DPoint3() throws JsonParseException, IOException { + { + Point point1 = new Point(10.0, 20.0); + JsonParser pointWgs84Parser = factory + .createParser(GeometryEngine.geometryToJson(spatialReferenceWGS84, point1)); + MapGeometry pointWgs84MP = GeometryEngine.jsonToGeometry(pointWgs84Parser); + assertTrue(point1.getX() == ((Point) pointWgs84MP.getGeometry()).getX()); + assertTrue(point1.getY() == ((Point) pointWgs84MP.getGeometry()).getY()); + assertTrue(spatialReferenceWGS84.getID() == pointWgs84MP.getSpatialReference().getID()); + } + } + + @Test + public void testMultiPoint() throws JsonParseException, IOException { + MultiPoint multiPoint1 = new MultiPoint(); + multiPoint1.add(-97.06138, 32.837); + multiPoint1.add(-97.06133, 32.836); + multiPoint1.add(-97.06124, 32.834); + multiPoint1.add(-97.06127, 32.832); + + { + JsonParser mPointWgs84Parser = factory + .createParser(GeometryEngine.geometryToJson(spatialReferenceWGS84, multiPoint1)); + MapGeometry mPointWgs84MP = GeometryEngine.jsonToGeometry(mPointWgs84Parser); + assertTrue(multiPoint1.getPointCount() == ((MultiPoint) mPointWgs84MP.getGeometry()).getPointCount()); + assertTrue(multiPoint1.getPoint(0).getX() == ((MultiPoint) mPointWgs84MP.getGeometry()).getPoint(0).getX()); + assertTrue(multiPoint1.getPoint(0).getY() == ((MultiPoint) mPointWgs84MP.getGeometry()).getPoint(0).getY()); + int lastIndex = multiPoint1.getPointCount() - 1; + assertTrue(multiPoint1.getPoint(lastIndex).getX() == ((MultiPoint) mPointWgs84MP.getGeometry()) + .getPoint(lastIndex).getX()); + assertTrue(multiPoint1.getPoint(lastIndex).getY() == ((MultiPoint) mPointWgs84MP.getGeometry()) + .getPoint(lastIndex).getY()); + + assertTrue(spatialReferenceWGS84.getID() == mPointWgs84MP.getSpatialReference().getID()); + + MultiPoint mPointEmpty = new MultiPoint(); + String mPointEmptyString = GeometryEngine.geometryToJson(spatialReferenceWGS84, mPointEmpty); + mPointWgs84Parser = factory.createParser(mPointEmptyString); + + mPointWgs84MP = GeometryEngine.jsonToGeometry(mPointWgs84Parser); + assertTrue(mPointWgs84MP.getGeometry().isEmpty()); + assertTrue(spatialReferenceWGS84.getID() == mPointWgs84MP.getSpatialReference().getID()); + + } + } + + @Test + public void testPolyline() throws JsonParseException, IOException { + Polyline polyline = new Polyline(); + polyline.startPath(-97.06138, 32.837); + polyline.lineTo(-97.06133, 32.836); + polyline.lineTo(-97.06124, 32.834); + polyline.lineTo(-97.06127, 32.832); + + polyline.startPath(-97.06326, 32.759); + polyline.lineTo(-97.06298, 32.755); + + { + JsonParser polylinePathsWgs84Parser = factory + .createParser(GeometryEngine.geometryToJson(spatialReferenceWGS84, polyline)); + MapGeometry mPolylineWGS84MP = GeometryEngine.jsonToGeometry(polylinePathsWgs84Parser); + + assertTrue(polyline.getPointCount() == ((Polyline) mPolylineWGS84MP.getGeometry()).getPointCount()); + assertTrue(polyline.getPoint(0).getX() == ((Polyline) mPolylineWGS84MP.getGeometry()).getPoint(0).getX()); + assertTrue(polyline.getPoint(0).getY() == ((Polyline) mPolylineWGS84MP.getGeometry()).getPoint(0).getY()); + + assertTrue(polyline.getPathCount() == ((Polyline) mPolylineWGS84MP.getGeometry()).getPathCount()); + assertTrue(polyline.getSegmentCount() == ((Polyline) mPolylineWGS84MP.getGeometry()).getSegmentCount()); + assertTrue(polyline.getSegmentCount(0) == ((Polyline) mPolylineWGS84MP.getGeometry()).getSegmentCount(0)); + assertTrue(polyline.getSegmentCount(1) == ((Polyline) mPolylineWGS84MP.getGeometry()).getSegmentCount(1)); + + int lastIndex = polyline.getPointCount() - 1; + assertTrue(polyline.getPoint(lastIndex).getX() == ((Polyline) mPolylineWGS84MP.getGeometry()) + .getPoint(lastIndex).getX()); + assertTrue(polyline.getPoint(lastIndex).getY() == ((Polyline) mPolylineWGS84MP.getGeometry()) + .getPoint(lastIndex).getY()); + + assertTrue(spatialReferenceWGS84.getID() == mPolylineWGS84MP.getSpatialReference().getID()); + + Polyline emptyPolyline = new Polyline(); + String emptyString = GeometryEngine.geometryToJson(spatialReferenceWGS84, emptyPolyline); + mPolylineWGS84MP = GeometryEngine.jsonToGeometry(factory.createParser(emptyString)); + assertTrue(mPolylineWGS84MP.getGeometry().isEmpty()); + assertTrue(spatialReferenceWGS84.getID() == mPolylineWGS84MP.getSpatialReference().getID()); + } + } + + @Test + public void testPolygon() throws JsonParseException, IOException { + Polygon polygon = new Polygon(); + polygon.startPath(-97.06138, 32.837); + polygon.lineTo(-97.06133, 32.836); + polygon.lineTo(-97.06124, 32.834); + polygon.lineTo(-97.06127, 32.832); + + polygon.startPath(-97.06326, 32.759); + polygon.lineTo(-97.06298, 32.755); + + { + JsonParser polygonPathsWgs84Parser = factory + .createParser(GeometryEngine.geometryToJson(spatialReferenceWGS84, polygon)); + MapGeometry mPolygonWGS84MP = GeometryEngine.jsonToGeometry(polygonPathsWgs84Parser); + + assertTrue(polygon.getPointCount() + 1 == ((Polygon) mPolygonWGS84MP.getGeometry()).getPointCount()); + assertTrue(polygon.getPoint(0).getX() == ((Polygon) mPolygonWGS84MP.getGeometry()).getPoint(0).getX()); + assertTrue(polygon.getPoint(0).getY() == ((Polygon) mPolygonWGS84MP.getGeometry()).getPoint(0).getY()); + + assertTrue(polygon.getPathCount() == ((Polygon) mPolygonWGS84MP.getGeometry()).getPathCount()); + assertTrue(polygon.getSegmentCount() + 1 == ((Polygon) mPolygonWGS84MP.getGeometry()).getSegmentCount()); + assertTrue(polygon.getSegmentCount(0) == ((Polygon) mPolygonWGS84MP.getGeometry()).getSegmentCount(0)); + assertTrue(polygon.getSegmentCount(1) + 1 == ((Polygon) mPolygonWGS84MP.getGeometry()).getSegmentCount(1)); + + int lastIndex = polygon.getPointCount() - 1; + assertTrue(polygon.getPoint(lastIndex).getX() == ((Polygon) mPolygonWGS84MP.getGeometry()) + .getPoint(lastIndex).getX()); + assertTrue(polygon.getPoint(lastIndex).getY() == ((Polygon) mPolygonWGS84MP.getGeometry()) + .getPoint(lastIndex).getY()); + + assertTrue(spatialReferenceWGS84.getID() == mPolygonWGS84MP.getSpatialReference().getID()); + + Polygon emptyPolygon = new Polygon(); + String emptyPolygonString = GeometryEngine.geometryToJson(spatialReferenceWGS84, emptyPolygon); + polygonPathsWgs84Parser = factory.createParser(emptyPolygonString); + mPolygonWGS84MP = GeometryEngine.jsonToGeometry(polygonPathsWgs84Parser); + + assertTrue(mPolygonWGS84MP.getGeometry().isEmpty()); + assertTrue(spatialReferenceWGS84.getID() == mPolygonWGS84MP.getSpatialReference().getID()); + } + } + + @Test + public void testEnvelope() throws JsonParseException, IOException { + Envelope envelope = new Envelope(); + envelope.setCoords(-109.55, 25.76, -86.39, 49.94); + + { + JsonParser envelopeWGS84Parser = factory + .createParser(GeometryEngine.geometryToJson(spatialReferenceWGS84, envelope)); + MapGeometry envelopeWGS84MP = GeometryEngine.jsonToGeometry(envelopeWGS84Parser); + assertTrue(envelope.isEmpty() == envelopeWGS84MP.getGeometry().isEmpty()); + assertTrue(envelope.getXMax() == ((Envelope) envelopeWGS84MP.getGeometry()).getXMax()); + assertTrue(envelope.getYMax() == ((Envelope) envelopeWGS84MP.getGeometry()).getYMax()); + assertTrue(envelope.getXMin() == ((Envelope) envelopeWGS84MP.getGeometry()).getXMin()); + assertTrue(envelope.getYMin() == ((Envelope) envelopeWGS84MP.getGeometry()).getYMin()); + assertTrue(spatialReferenceWGS84.getID() == envelopeWGS84MP.getSpatialReference().getID()); + + Envelope emptyEnvelope = new Envelope(); + String emptyEnvString = GeometryEngine.geometryToJson(spatialReferenceWGS84, emptyEnvelope); + envelopeWGS84Parser = factory.createParser(emptyEnvString); + envelopeWGS84MP = GeometryEngine.jsonToGeometry(envelopeWGS84Parser); + + assertTrue(envelopeWGS84MP.getGeometry().isEmpty()); + assertTrue(spatialReferenceWGS84.getID() == envelopeWGS84MP.getSpatialReference().getID()); + } + } + + @Test + public void testCR181369() throws JsonParseException, IOException { + // CR181369 + { + String jsonStringPointAndWKT = "{\"x\":10.0,\"y\":20.0,\"spatialReference\":{\"wkt\" : \"PROJCS[\\\"NAD83_UTM_zone_15N\\\",GEOGCS[\\\"GCS_North_American_1983\\\",DATUM[\\\"D_North_American_1983\\\",SPHEROID[\\\"GRS_1980\\\",6378137.0,298.257222101]],PRIMEM[\\\"Greenwich\\\",0.0],UNIT[\\\"Degree\\\",0.0174532925199433]],PROJECTION[\\\"Transverse_Mercator\\\"],PARAMETER[\\\"false_easting\\\",500000.0],PARAMETER[\\\"false_northing\\\",0.0],PARAMETER[\\\"central_meridian\\\",-93.0],PARAMETER[\\\"scale_factor\\\",0.9996],PARAMETER[\\\"latitude_of_origin\\\",0.0],UNIT[\\\"Meter\\\",1.0]]\"} }"; + JsonParser jsonParserPointAndWKT = factory.createParser(jsonStringPointAndWKT); + MapGeometry mapGeom2 = GeometryEngine.jsonToGeometry(jsonParserPointAndWKT); + String jsonStringPointAndWKT2 = GeometryEngine.geometryToJson(mapGeom2.getSpatialReference(), + mapGeom2.getGeometry()); + JsonParser jsonParserPointAndWKT2 = factory.createParser(jsonStringPointAndWKT2); + MapGeometry mapGeom3 = GeometryEngine.jsonToGeometry(jsonParserPointAndWKT2); + assertTrue(((Point) mapGeom2.getGeometry()).getX() == ((Point) mapGeom3.getGeometry()).getX()); + assertTrue(((Point) mapGeom2.getGeometry()).getY() == ((Point) mapGeom3.getGeometry()).getY()); + assertTrue(mapGeom2.getSpatialReference().getText().equals(mapGeom3.getSpatialReference().getText())); + assertTrue(mapGeom2.getSpatialReference().getID() == mapGeom3.getSpatialReference().getID()); + } + } + + @Test + public void testSpatialRef() throws JsonParseException, IOException { + // String jsonStringPt = + // "{\"x\":-20037508.342787,\"y\":20037508.342787},\"spatialReference\":{\"wkid\":102100}}"; + String jsonStringPt = "{\"x\":10.0,\"y\":20.0,\"spatialReference\":{\"wkid\": 102100}}";// 102100 + @SuppressWarnings("unused") + String jsonStringPt2 = "{\"x\":10.0,\"y\":20.0,\"spatialReference\":{\"wkid\":4326}}"; + String jsonStringMpt = "{ \"points\" : [ [-97.06138,32.837], [-97.06133,32.836], [-97.06124,32.834], [-97.06127,32.832] ], \"spatialReference\" : {\"wkid\" : 4326}}";// 4326 + String jsonStringMpt3D = "{\"hasZs\" : true,\"points\" : [ [-97.06138,32.837,35.0], [-97.06133,32.836,35.1], [-97.06124,32.834,35.2], [-97.06127,32.832,35.3] ],\"spatialReference\" : {\"wkid\" : 4326}}"; + String jsonStringPl = "{\"paths\" : [ [ [-97.06138,32.837], [-97.06133,32.836], [-97.06124,32.834], [-97.06127,32.832] ], [ [-97.06326,32.759], [-97.06298,32.755] ]],\"spatialReference\" : {\"wkid\" : 4326}}"; + String jsonStringPl3D = "{\"hasMs\" : true,\"paths\" : [[ [-97.06138,32.837,5], [-97.06133,32.836,6], [-97.06124,32.834,7], [-97.06127,32.832,8] ],[ [-97.06326,32.759], [-97.06298,32.755] ]],\"spatialReference\" : {\"wkid\" : 4326}}"; + String jsonStringPg = "{ \"rings\" :[ [ [-97.06138,32.837], [-97.06133,32.836], [-97.06124,32.834], [-97.06127,32.832], [-97.06138,32.837] ], [ [-97.06326,32.759], [-97.06298,32.755], [-97.06153,32.749], [-97.06326,32.759] ]], \"spatialReference\" : {\"wkt\" : \"\"}}"; + String jsonStringPg3D = "{\"hasZs\" : true,\"hasMs\" : true,\"rings\" : [ [ [-97.06138, 32.837, 35.1, 4], [-97.06133, 32.836, 35.2, 4.1], [-97.06124, 32.834, 35.3, 4.2], [-97.06127, 32.832, 35.2, 44.3], [-97.06138, 32.837, 35.1, 4] ],[ [-97.06326, 32.759, 35.4], [-97.06298, 32.755, 35.5], [-97.06153, 32.749, 35.6], [-97.06326, 32.759, 35.4] ]],\"spatialReference\" : {\"wkid\" : 4326}}"; + String jsonStringPg2 = "{ \"spatialReference\" : {\"wkid\" : 4326}, \"rings\" : [[[-118.35,32.81],[-118.42,32.806],[-118.511,32.892],[-118.35,32.81]]]}"; + String jsonStringPg3 = "{ \"spatialReference\": {\"layerName\":\"GAS_POINTS\",\"name\":null,\"sdesrid\":102100,\"wkid\":102100,\"wkt\":null}}"; + String jsonString2SpatialReferences = "{ \"spatialReference\": {\"layerName\":\"GAS_POINTS\",\"name\":null,\"sdesrid\":102100,\"wkid\":102100,\"wkt\":\"GEOGCS[\\\"GCS_WGS_1984\\\",DATUM[\\\"D_WGS_1984\\\",SPHEROID[\\\"WGS_1984\\\",6378137,298.257223563]],PRIMEM[\\\"Greenwich\\\",0],UNIT[\\\"Degree\\\",0.017453292519943295]]\"}}"; + String jsonString2SpatialReferences2 = "{ \"spatialReference\": {\"layerName\":\"GAS_POINTS\",\"name\":null,\"sdesrid\":10,\"wkid\":10,\"wkt\":\"GEOGCS[\\\"GCS_WGS_1984\\\",DATUM[\\\"D_WGS_1984\\\",SPHEROID[\\\"WGS_1984\\\",6378137,298.257223563]],PRIMEM[\\\"Greenwich\\\",0],UNIT[\\\"Degree\\\",0.017453292519943295]]\"}}"; + String jsonStringSR = "{\"wkid\" : 4326}"; + String jsonStringEnv = "{\"xmin\" : -109.55, \"ymin\" : 25.76, \"xmax\" : -86.39, \"ymax\" : 49.94,\"spatialReference\" : {\"wkid\" : 4326}}"; + String jsonStringHongKon = "{\"xmin\" : -122.55, \"ymin\" : 37.65, \"xmax\" : -122.28, \"ymax\" : 37.84,\"spatialReference\" : {\"wkid\" : 4326}}"; + @SuppressWarnings("unused") + String jsonStringWKT = " {\"wkt\" : \"GEOGCS[\\\"GCS_WGS_1984\\\",DATUM[\\\"D_WGS_1984\\\",SPHEROID[\\\"WGS_1984\\\",6378137,298.257223563]],PRIMEM[\\\"Greenwich\\\",0],UNIT[\\\"Degree\\\",0.017453292519943295]]\"}"; + String jsonStringInvalidWKID = "{\"x\":10.0,\"y\":20.0},\"spatialReference\":{\"wkid\":35253523}}"; + String jsonStringOregon = "{\"xmin\":7531831.219849482,\"ymin\":585702.9799639136,\"xmax\":7750143.589982405,\"ymax\":733289.6299999952,\"spatialReference\":{\"wkid\":102726}}"; + + JsonParser jsonParserPt = factory.createParser(jsonStringPt); + JsonParser jsonParserMpt = factory.createParser(jsonStringMpt); + JsonParser jsonParserMpt3D = factory.createParser(jsonStringMpt3D); + JsonParser jsonParserPl = factory.createParser(jsonStringPl); + JsonParser jsonParserPl3D = factory.createParser(jsonStringPl3D); + JsonParser jsonParserPg = factory.createParser(jsonStringPg); + JsonParser jsonParserPg3D = factory.createParser(jsonStringPg3D); + JsonParser jsonParserPg2 = factory.createParser(jsonStringPg2); + @SuppressWarnings("unused") + JsonParser jsonParserSR = factory.createParser(jsonStringSR); + JsonParser jsonParserEnv = factory.createParser(jsonStringEnv); + JsonParser jsonParserPg3 = factory.createParser(jsonStringPg3); + @SuppressWarnings("unused") + JsonParser jsonParserCrazy1 = factory.createParser(jsonString2SpatialReferences); + @SuppressWarnings("unused") + JsonParser jsonParserCrazy2 = factory.createParser(jsonString2SpatialReferences2); + JsonParser jsonParserInvalidWKID = factory.createParser(jsonStringInvalidWKID); + @SuppressWarnings("unused") + JsonParser jsonParseHongKon = factory.createParser(jsonStringHongKon); + JsonParser jsonParseOregon = factory.createParser(jsonStringOregon); + + MapGeometry mapGeom = GeometryEngine.jsonToGeometry(jsonParserPt); + // showProjectedGeometryInfo(mapGeom); + Assert.assertTrue(mapGeom.getSpatialReference().getID() == 102100); + + MapGeometry mapGeomOregon = GeometryEngine.jsonToGeometry(jsonParseOregon); + Assert.assertTrue(mapGeomOregon.getSpatialReference().getID() == 102726); + + mapGeom = GeometryEngine.jsonToGeometry(jsonParserMpt); + Assert.assertTrue(mapGeom.getSpatialReference().getID() == 4326); + + mapGeom = GeometryEngine.jsonToGeometry(jsonParserMpt3D); + Assert.assertTrue(mapGeom.getSpatialReference().getID() == 4326); + { + Assert.assertTrue(((MultiPoint) mapGeom.getGeometry()).getPoint(0).getX() == -97.06138); + Assert.assertTrue(((MultiPoint) mapGeom.getGeometry()).getPoint(0).getY() == 32.837); + Assert.assertTrue(((MultiPoint) mapGeom.getGeometry()).getPoint(3).getX() == -97.06127); + Assert.assertTrue(((MultiPoint) mapGeom.getGeometry()).getPoint(3).getY() == 32.832); + } + // showProjectedGeometryInfo(mapGeom); + + mapGeom = GeometryEngine.jsonToGeometry(jsonParserPl); + Assert.assertTrue(mapGeom.getSpatialReference().getID() == 4326); + // showProjectedGeometryInfo(mapGeom); + + mapGeom = GeometryEngine.jsonToGeometry(jsonParserPl3D); + { + // [[ [-97.06138,32.837,5], [-97.06133,32.836,6], + // [-97.06124,32.834,7], [-97.06127,32.832,8] ], + // [ [-97.06326,32.759], [-97.06298,32.755] ]]"; + Assert.assertTrue(((Polyline) mapGeom.getGeometry()).getPoint(0).getX() == -97.06138); + Assert.assertTrue(((Polyline) mapGeom.getGeometry()).getPoint(0).getY() == 32.837); + int lastIndex = ((Polyline) mapGeom.getGeometry()).getPointCount() - 1; + Assert.assertTrue(((Polyline) mapGeom.getGeometry()).getPoint(lastIndex).getX() == -97.06298);// -97.06153, + // 32.749 + Assert.assertTrue(((Polyline) mapGeom.getGeometry()).getPoint(lastIndex).getY() == 32.755); + int lastIndexFirstLine = ((Polyline) mapGeom.getGeometry()).getPathEnd(0) - 1; + Assert.assertTrue(((Polyline) mapGeom.getGeometry()).getPoint(lastIndexFirstLine).getX() == -97.06127);// -97.06153, + // 32.749 + Assert.assertTrue(((Polyline) mapGeom.getGeometry()).getPoint(lastIndexFirstLine).getY() == 32.832); + } + + mapGeom = GeometryEngine.jsonToGeometry(jsonParserPg); + Assert.assertTrue(mapGeom.getSpatialReference() == null); + + mapGeom = GeometryEngine.jsonToGeometry(jsonParserPg3D); + { + Assert.assertTrue(((Polygon) mapGeom.getGeometry()).getPoint(0).getX() == -97.06138); + Assert.assertTrue(((Polygon) mapGeom.getGeometry()).getPoint(0).getY() == 32.837); + int lastIndex = ((Polygon) mapGeom.getGeometry()).getPointCount() - 1; + Assert.assertTrue(((Polygon) mapGeom.getGeometry()).getPoint(lastIndex).getX() == -97.06153);// -97.06153, + // 32.749 + Assert.assertTrue(((Polygon) mapGeom.getGeometry()).getPoint(lastIndex).getY() == 32.749); + } + + mapGeom = GeometryEngine.jsonToGeometry(jsonParserPg2); + Assert.assertTrue(mapGeom.getSpatialReference().getID() == 4326); + // showProjectedGeometryInfo(mapGeom); + + mapGeom = GeometryEngine.jsonToGeometry(jsonParserPg3); + Assert.assertTrue(mapGeom.getSpatialReference().getID() == 102100); + // showProjectedGeometryInfo(mapGeom); + + // mapGeom = GeometryEngine.jsonToGeometry(jsonParserCrazy1); + // Assert.assertTrue(mapGeom.getSpatialReference().getText().equals("")); + // showProjectedGeometryInfo(mapGeom); + + mapGeom = GeometryEngine.jsonToGeometry(jsonParserEnv); + Assert.assertTrue(mapGeom.getSpatialReference().getID() == 4326); + // showProjectedGeometryInfo(mapGeom); + + try { + GeometryEngine.jsonToGeometry(jsonParserInvalidWKID); + } catch (Exception ex) { + Assert.assertTrue("Should not throw for invalid wkid", false); + } + } + + @Test + public void testMP2onCR175871() throws Exception { + Polygon pg = new Polygon(); + pg.startPath(-50, 10); + pg.lineTo(-50, 12); + pg.lineTo(-45, 12); + pg.lineTo(-45, 10); + + Polygon pg1 = new Polygon(); + pg1.startPath(-45, 10); + pg1.lineTo(-40, 10); + pg1.lineTo(-40, 8); + pg.add(pg1, false); + + SpatialReference spatialReference = SpatialReference.create(4326); + + try { + String jSonStr = GeometryEngine.geometryToJson(spatialReference, pg); + JsonFactory jf = new JsonFactory(); + + JsonParser jp = jf.createParser(jSonStr); + jp.nextToken(); + MapGeometry mg = GeometryEngine.jsonToGeometry(jp); + Geometry gm = mg.getGeometry(); + Assert.assertEquals(Geometry.Type.Polygon, gm.getType()); + Assert.assertTrue(mg.getSpatialReference().getID() == 4326); + + Polygon pgNew = (Polygon) gm; + + Assert.assertEquals(pgNew.getPathCount(), pg.getPathCount()); + Assert.assertEquals(pgNew.getPointCount(), pg.getPointCount()); + Assert.assertEquals(pgNew.getSegmentCount(), pg.getSegmentCount()); + + Assert.assertEquals(pgNew.getPoint(0).getX(), pg.getPoint(0).getX(), 0.000000001); + Assert.assertEquals(pgNew.getPoint(1).getX(), pg.getPoint(1).getX(), 0.000000001); + Assert.assertEquals(pgNew.getPoint(2).getX(), pg.getPoint(2).getX(), 0.000000001); + Assert.assertEquals(pgNew.getPoint(3).getX(), pg.getPoint(3).getX(), 0.000000001); + + Assert.assertEquals(pgNew.getPoint(0).getY(), pg.getPoint(0).getY(), 0.000000001); + Assert.assertEquals(pgNew.getPoint(1).getY(), pg.getPoint(1).getY(), 0.000000001); + Assert.assertEquals(pgNew.getPoint(2).getY(), pg.getPoint(2).getY(), 0.000000001); + Assert.assertEquals(pgNew.getPoint(3).getY(), pg.getPoint(3).getY(), 0.000000001); + } catch (Exception ex) { + String err = ex.getMessage(); + System.out.print(err); + throw ex; + } + } + + @Test + public static int fromJsonToWkid(JsonParser parser) throws JsonParseException, IOException { + int wkid = 0; + if (parser.getCurrentToken() != JsonToken.START_OBJECT) { + return 0; + } + + while (parser.nextToken() != JsonToken.END_OBJECT) { + String fieldName = parser.getCurrentName(); + + if ("wkid".equals(fieldName)) { + parser.nextToken(); + wkid = parser.getIntValue(); + } + } + return wkid; + } + + @SuppressWarnings("unused") + private static void showProjectedGeometryInfo(MapGeometry mapGeom) { + System.out.println("\n"); + MapGeometry geom = mapGeom; + // while ((geom = geomCursor.next()) != null) { + + if (geom.getGeometry() instanceof Point) { + Point pnt = (Point) geom.getGeometry(); + System.out.println("Point(" + pnt.getX() + " , " + pnt.getY() + ")"); + if (geom.getSpatialReference() == null) { + System.out.println("No spatial reference"); + } else { + System.out.println("wkid: " + geom.getSpatialReference().getID()); + } + + } else if (geom.getGeometry() instanceof MultiPoint) { + MultiPoint mp = (MultiPoint) geom.getGeometry(); + System.out.println("Multipoint has " + mp.getPointCount() + " points."); + + System.out.println("wkid: " + geom.getSpatialReference().getID()); + + } else if (geom.getGeometry() instanceof Polygon) { + Polygon mp = (Polygon) geom.getGeometry(); + System.out.println("Polygon has " + mp.getPointCount() + " points and " + mp.getPathCount() + " parts."); + if (mp.getPathCount() > 1) { + System.out.println("Part start of 2nd segment : " + mp.getPathStart(1)); + System.out.println("Part end of 2nd segment : " + mp.getPathEnd(1)); + System.out.println("Part size of 2nd segment : " + mp.getPathSize(1)); + + int start = mp.getPathStart(1); + int end = mp.getPathEnd(1); + for (int i = start; i < end; i++) { + Point pp = mp.getPoint(i); + System.out.println("Point(" + i + ") = (" + pp.getX() + ", " + pp.getY() + ")"); + } + } + System.out.println("wkid: " + geom.getSpatialReference().getID()); + + } else if (geom.getGeometry() instanceof Polyline) { + Polyline mp = (Polyline) geom.getGeometry(); + System.out.println("Polyline has " + mp.getPointCount() + " points and " + mp.getPathCount() + " parts."); + System.out.println("Part start of 2nd segment : " + mp.getPathStart(1)); + System.out.println("Part end of 2nd segment : " + mp.getPathEnd(1)); + System.out.println("Part size of 2nd segment : " + mp.getPathSize(1)); + int start = mp.getPathStart(1); + int end = mp.getPathEnd(1); + for (int i = start; i < end; i++) { + Point pp = mp.getPoint(i); + System.out.println("Point(" + i + ") = (" + pp.getX() + ", " + pp.getY() + ")"); + } + + System.out.println("wkid: " + geom.getSpatialReference().getID()); + } + } + + @Test + public void testGeometryToJSON() { + Polygon geom = new Polygon(); + geom.startPath(new Point(-113, 34)); + geom.lineTo(new Point(-105, 34)); + geom.lineTo(new Point(-108, 40)); + + String outputPolygon1 = GeometryEngine.geometryToJson(-1, geom);// Test + // WKID + // == -1 + // System.out.println("Geom JSON STRING is" + outputPolygon1); + String correctPolygon1 = "{\"rings\":[[[-113,34],[-105,34],[-108,40],[-113,34]]]}"; + + assertEquals(correctPolygon1, outputPolygon1); + + String outputPolygon2 = GeometryEngine.geometryToJson(4326, geom); + // System.out.println("Geom JSON STRING is" + outputPolygon2); + + String correctPolygon2 = "{\"rings\":[[[-113,34],[-105,34],[-108,40],[-113,34]]],\"spatialReference\":{\"wkid\":4326}}"; + assertEquals(correctPolygon2, outputPolygon2); + } + + @Test + public void testGeometryToJSONOldID() throws Exception {// CR + Polygon geom = new Polygon(); + geom.startPath(new Point(-113, 34)); + geom.lineTo(new Point(-105, 34)); + geom.lineTo(new Point(-108, 40)); + String outputPolygon = GeometryEngine.geometryToJson(SpatialReference.create(3857), geom);// Test + // WKID + // == + // -1 + String correctPolygon = "{\"rings\":[[[-113,34],[-105,34],[-108,40],[-113,34]]],\"spatialReference\":{\"wkid\":102100,\"latestWkid\":3857}}"; + assertTrue(outputPolygon.equals(correctPolygon)); + JsonFactory jf = new JsonFactory(); + JsonParser jp = jf.createParser(outputPolygon); + jp.nextToken(); + MapGeometry mg = GeometryEngine.jsonToGeometry(jp); + @SuppressWarnings("unused") + int srId = mg.getSpatialReference().getID(); + @SuppressWarnings("unused") + int srOldId = mg.getSpatialReference().getOldID(); + Assert.assertTrue(mg.getSpatialReference().getID() == 3857); + Assert.assertTrue(mg.getSpatialReference().getLatestID() == 3857); + Assert.assertTrue(mg.getSpatialReference().getOldID() == 102100); + } } diff --git a/src/test/java/com/esri/core/geometry/TestMathUtils.java b/src/test/java/com/esri/core/geometry/TestMathUtils.java index e7e5721a..c6d39735 100644 --- a/src/test/java/com/esri/core/geometry/TestMathUtils.java +++ b/src/test/java/com/esri/core/geometry/TestMathUtils.java @@ -28,38 +28,38 @@ import org.junit.Test; public class TestMathUtils extends TestCase { - @Override - protected void setUp() throws Exception { - super.setUp(); - } + @Override + protected void setUp() throws Exception { + super.setUp(); + } - @Override - protected void tearDown() throws Exception { - super.tearDown(); - } + @Override + protected void tearDown() throws Exception { + super.tearDown(); + } - @Test - public static void testKahanSummation() { - double s = 0.0; - for (int i = 0; i < 10000; i++) { - if (i == 0) { - s += 1e6; - } else - s += 1e-7; - } + @Test + public static void testKahanSummation() { + double s = 0.0; + for (int i = 0; i < 10000; i++) { + if (i == 0) { + s += 1e6; + } else + s += 1e-7; + } - double trueAnswer = 1e6 + 9999 * 1e-7; - assertTrue(Math.abs(s - trueAnswer) > 1e-9); // precision loss - MathUtils.KahanSummator sum = new MathUtils.KahanSummator(0); - for (int i = 0; i < 10000; i++) { - if (i == 0) { - sum.add(1e6); - } else - sum.add(1e-7); - } - double kahanResult = sum.getResult(); - // 1000000.0009999000 //C++ - // 1000000.0009999 //Java - assertTrue(kahanResult == trueAnswer); // nice answer! - } + double trueAnswer = 1e6 + 9999 * 1e-7; + assertTrue(Math.abs(s - trueAnswer) > 1e-9); // precision loss + MathUtils.KahanSummator sum = new MathUtils.KahanSummator(0); + for (int i = 0; i < 10000; i++) { + if (i == 0) { + sum.add(1e6); + } else + sum.add(1e-7); + } + double kahanResult = sum.getResult(); + // 1000000.0009999000 //C++ + // 1000000.0009999 //Java + assertTrue(kahanResult == trueAnswer); // nice answer! + } } diff --git a/src/test/java/com/esri/core/geometry/TestMultiPoint.java b/src/test/java/com/esri/core/geometry/TestMultiPoint.java index 9263357a..765ac7c5 100644 --- a/src/test/java/com/esri/core/geometry/TestMultiPoint.java +++ b/src/test/java/com/esri/core/geometry/TestMultiPoint.java @@ -28,295 +28,295 @@ import org.junit.Test; public class TestMultiPoint extends TestCase { - @Override - protected void setUp() throws Exception { - super.setUp(); - } - - @Override - protected void tearDown() throws Exception { - super.tearDown(); - } - - static void simpleTest(Geometry point) { - assertTrue(point != null); - // point->AddAttribute(VertexDescription::Semantics::Z); - // assertTrue(point->HasAttribute(VertexDescription::Semantics::POSITION)); - // assertTrue(point.->HasAttribute(VertexDescription::Semantics::Z)); - // point->AddAttribute(VertexDescription::Semantics::Z);//duplicate call - // assertTrue(point->GetDescription()->GetAttributeCount() == 2); - // assertTrue(point->GetDescription()->GetSemantics(1) == - // VertexDescription::Semantics::Z); - // point->DropAttribute(VertexDescription::Semantics::Z); - // assertFalse(point->HasAttribute(VertexDescription::Semantics::Z)); - // point->DropAttribute(VertexDescription::Semantics::Z);//duplicate - // call - // assertFalse(point->HasAttribute(VertexDescription::Semantics::Z)); - // assertTrue(point->GetDescription()->GetAttributeCount() == 1); - // assertTrue(point->GetDescription()->GetSemantics(0) == - // VertexDescription::Semantics::POSITION); - - // point->AddAttribute(VertexDescription::Semantics::M); - // assertTrue(point->HasAttribute(VertexDescription::Semantics::POSITION)); - // assertFalse(point->HasAttribute(VertexDescription::Semantics::Z)); - // assertTrue(point->HasAttribute(VertexDescription::Semantics::M)); - // point->DropAttribute(VertexDescription::Semantics::M); - // assertFalse(point->HasAttribute(VertexDescription::Semantics::M)); - // - // point->AddAttribute(VertexDescription::Semantics::ID); - // assertTrue(point->HasAttribute(VertexDescription::Semantics::POSITION)); - // assertFalse(point->HasAttribute(VertexDescription::Semantics::Z)); - // assertFalse(point->HasAttribute(VertexDescription::Semantics::M)); - // point->DropAttribute(VertexDescription::Semantics::ID); - // assertFalse(point->HasAttribute(VertexDescription::Semantics::ID)); - // / - // assertTrue(point->IsEmpty()); - // assertTrue(point->GetPointCount() == 0); - // assertTrue(point->GetPartCount() == 0); - - point = null; - assertFalse(point != null); - } - - @Test - public static void testCreation() { - {// simple create - MultiPoint mpoint = new MultiPoint(); - assertTrue(mpoint.getClass() == MultiPoint.class); - // assertFalse(mpoint.getClass() == Polyline.class); - - assertTrue(mpoint != null); - assertTrue(mpoint.getType() == Geometry.Type.MultiPoint); - assertTrue(mpoint.isEmpty()); - assertTrue(mpoint.getPointCount() == 0); - mpoint = null; - assertFalse(mpoint != null); - } - {// play with default attributes - MultiPoint mpoint = new MultiPoint(); - simpleTest(mpoint); - } - - {// simple create 2D - MultiPoint mpoint = new MultiPoint(); - assertTrue(mpoint != null); - - - MultiPoint mpoint1 = new MultiPoint(); - assertTrue(mpoint1 != null); - - mpoint.setEmpty(); - Point pt = new Point(0, 0); - mpoint.add(pt); - Point pt3 = mpoint.getPoint(0); - assertTrue(pt3.getX() == 0 && pt3.getY() == 0/** && pt3.getZ() == 0 */ - ); - // assertFalse(mpoint->HasAttribute(VertexDescription::Semantics::Z)); - // pt3.setZ(115.0); - mpoint.setPoint(0, pt3); - pt3 = mpoint.getPoint(0); - assertTrue(pt3.getX() == 0 && pt3.getY() == 0/* && pt3.getZ() == 115 */); - // assertTrue(mpoint->HasAttribute(VertexDescription::Semantics::Z)); - // CompareGeometryContent(mpoint, &pt, 1); - } - - {// move 3d - MultiPoint mpoint = new MultiPoint(); - assertTrue(mpoint != null); - Point pt = new Point(0, 0); - mpoint.add(pt); - Point pt3 = mpoint.getPoint(0); - assertTrue(pt3.getX() == 0 && pt3.getY() == 0/* && pt3.getZ() == 0 */); - } - - { // test QueryInterval - MultiPoint mpoint = new MultiPoint(); - - Point pt1 = new Point(0.0, 0.0); - // pt1.setZ(-1.0); - - Point pt2 = new Point(0.0, 0.0); - // pt2.setZ(1.0); - - mpoint.add(pt1); - mpoint.add(pt2); - - // Envelope1D e = - // mpoint->QueryInterval(enum_value2(VertexDescription, Semantics, - // Z), 0); - Envelope e = new Envelope(); - mpoint.queryEnvelope(e); - // assertTrue(e.get == -1.0 && e.vmax == 1.0); - } - - { - @SuppressWarnings("unused") - MultiPoint geom = new MultiPoint(); - // int sz = sizeof(openString) / sizeof(openString[0]); - // for (int i = 0; i < sz; i++) - // geom.add(openString[i]); - // CompareGeometryContent(geom, openString, sz); - } - - { - @SuppressWarnings("unused") - MultiPoint geom = new MultiPoint(); - // int sz = sizeof(openString) / sizeof(openString[0]); - // Point point = GCNEW Point; - // for (int i = 0; i < sz; i++) - // { - // point.setXY(openString[i]); - // geom.add(point); - // } - // CompareGeometryContent(geom, openString, sz); - } - - // Test AddPoints - { - @SuppressWarnings("unused") - MultiPoint geom = new MultiPoint(); - // int sz = sizeof(openString) / sizeof(openString[0]); - // geom.addPoints(openString, sz, 0, -1); - // CompareGeometryContent((MultiVertexGeometry)geom, openString, - // sz); - } - - // Test InsertPoint(Point2D) - { - MultiPoint mpoint = new MultiPoint(); - Point pt0 = new Point(0.0, 0.0); - // pt0.setZ(-1.0); - // pt0.setID(7); - - Point pt1 = new Point(0.0, 0.0); - // pt1.setZ(1.0); - // pt1.setID(11); - - Point pt2 = new Point(0.0, 1.0); - // pt2.setZ(1.0); - // pt2.setID(13); - - mpoint.add(pt0); - mpoint.add(pt1); - mpoint.add(pt2); - - Point pt3 = new Point(-11.0, -13.0); - - mpoint.add(pt3); - mpoint.insertPoint(1, pt3); - assertTrue(mpoint.getPointCount() == 5); - - Point pt; - pt = mpoint.getPoint(0); - assertTrue(pt.getX() == pt0.getX() && pt.getY() == pt0.getY()/* - * && - * pt. - * getZ - * () == - * pt0 - * .getZ - * () - */); - - pt = mpoint.getPoint(1); - assertTrue(pt.getX() == pt3.getX() && pt.getY() == pt3.getY()); - - pt = mpoint.getPoint(2); - assertTrue(pt.getX() == pt1.getX() && pt.getY() == pt1.getY()/* - * && - * pt. - * getZ - * () == - * pt1 - * .getZ - * () - */); - - pt = mpoint.getPoint(3); - assertTrue(pt.getX() == pt2.getX() && pt.getY() == pt2.getY()/* - * && - * pt. - * getZ - * () == - * pt2 - * .getZ - * () - */); - - Point point = new Point(); - point.setXY(17.0, 19.0); - // point.setID(12); - // point.setM(5); - - mpoint.insertPoint(2, point); - mpoint.add(point); - - assertTrue(mpoint.getPointCount() == 7); - - // double m; - // int id; - // pt = mpoint.getXYZ(2); - // assertTrue(pt.x == 17.0 && pt.y == 19.0 && pt.z == defaultZ); - // m = mpoint.getAttributeAsDbl(enum_value2(VertexDescription, - // Semantics, M), 2, 0); - // assertTrue(m == 5); - // id = mpoint.getAttributeAsInt(enum_value2(VertexDescription, - // Semantics, ID), 2, 0); - // assertTrue(id == 23); - // - // pt = mpoint.getXYZ(3); - // assertTrue(pt.x == pt1.x && pt.y == pt1.y && pt.z == pt1.z); - // m = mpoint.getAttributeAsDbl(enum_value2(VertexDescription, - // Semantics, M), 3, 0); - // assertTrue(NumberUtils::IsNaN(m)); - // id = mpoint.getAttributeAsInt(enum_value2(VertexDescription, - // Semantics, ID), 3, 0); - // assertTrue(id == 11); - } - - MultiPoint mpoint = new MultiPoint(); - Point pt0 = new Point(0.0, 0.0, -1.0); - - Point pt1 = new Point(0.0, 0.0, 1.0); - - Point pt2 = new Point(0.0, 1.0, 1.0); - - mpoint.add(pt0); - mpoint.add(pt1); - mpoint.add(pt2); - - mpoint.removePoint(1); - - Point pt; - pt = mpoint.getPoint(0); - assertTrue(pt.getX() == pt0.getX() && pt.getY() == pt0.getY()); - pt = mpoint.getPoint(1); - assertTrue(pt.getX() == pt2.getX() && pt.getY() == pt2.getY()); - - assertTrue(mpoint.getPointCount() == 2); - } - - @Test - public static void testCopy() { - MultiPoint mpoint = new MultiPoint(); - Point pt0 = new Point(0.0, 0.0, -1.0); - Point pt1 = new Point(0.0, 0.0, 1.0); - Point pt2 = new Point(0.0, 1.0, 1.0); - - mpoint.add(pt0); - mpoint.add(pt1); - mpoint.add(pt2); - mpoint.removePoint(1); - - MultiPoint mpCopy = (MultiPoint) mpoint.copy(); - assertTrue(mpCopy.equals(mpoint)); - - Point pt; - pt = mpCopy.getPoint(0); - assertTrue(pt.getX() == pt0.getX() && pt.getY() == pt0.getY()); - pt = mpCopy.getPoint(1); - assertTrue(pt.getX() == pt2.getX() && pt.getY() == pt2.getY()); - - assertTrue(mpCopy.getPointCount() == 2); - } + @Override + protected void setUp() throws Exception { + super.setUp(); + } + + @Override + protected void tearDown() throws Exception { + super.tearDown(); + } + + static void simpleTest(Geometry point) { + assertTrue(point != null); + // point->AddAttribute(VertexDescription::Semantics::Z); + // assertTrue(point->HasAttribute(VertexDescription::Semantics::POSITION)); + // assertTrue(point.->HasAttribute(VertexDescription::Semantics::Z)); + // point->AddAttribute(VertexDescription::Semantics::Z);//duplicate call + // assertTrue(point->GetDescription()->GetAttributeCount() == 2); + // assertTrue(point->GetDescription()->GetSemantics(1) == + // VertexDescription::Semantics::Z); + // point->DropAttribute(VertexDescription::Semantics::Z); + // assertFalse(point->HasAttribute(VertexDescription::Semantics::Z)); + // point->DropAttribute(VertexDescription::Semantics::Z);//duplicate + // call + // assertFalse(point->HasAttribute(VertexDescription::Semantics::Z)); + // assertTrue(point->GetDescription()->GetAttributeCount() == 1); + // assertTrue(point->GetDescription()->GetSemantics(0) == + // VertexDescription::Semantics::POSITION); + + // point->AddAttribute(VertexDescription::Semantics::M); + // assertTrue(point->HasAttribute(VertexDescription::Semantics::POSITION)); + // assertFalse(point->HasAttribute(VertexDescription::Semantics::Z)); + // assertTrue(point->HasAttribute(VertexDescription::Semantics::M)); + // point->DropAttribute(VertexDescription::Semantics::M); + // assertFalse(point->HasAttribute(VertexDescription::Semantics::M)); + // + // point->AddAttribute(VertexDescription::Semantics::ID); + // assertTrue(point->HasAttribute(VertexDescription::Semantics::POSITION)); + // assertFalse(point->HasAttribute(VertexDescription::Semantics::Z)); + // assertFalse(point->HasAttribute(VertexDescription::Semantics::M)); + // point->DropAttribute(VertexDescription::Semantics::ID); + // assertFalse(point->HasAttribute(VertexDescription::Semantics::ID)); + // / + // assertTrue(point->IsEmpty()); + // assertTrue(point->GetPointCount() == 0); + // assertTrue(point->GetPartCount() == 0); + + point = null; + assertFalse(point != null); + } + + @Test + public static void testCreation() { + {// simple create + MultiPoint mpoint = new MultiPoint(); + assertTrue(mpoint.getClass() == MultiPoint.class); + // assertFalse(mpoint.getClass() == Polyline.class); + + assertTrue(mpoint != null); + assertTrue(mpoint.getType() == Geometry.Type.MultiPoint); + assertTrue(mpoint.isEmpty()); + assertTrue(mpoint.getPointCount() == 0); + mpoint = null; + assertFalse(mpoint != null); + } + {// play with default attributes + MultiPoint mpoint = new MultiPoint(); + simpleTest(mpoint); + } + + {// simple create 2D + MultiPoint mpoint = new MultiPoint(); + assertTrue(mpoint != null); + + + MultiPoint mpoint1 = new MultiPoint(); + assertTrue(mpoint1 != null); + + mpoint.setEmpty(); + Point pt = new Point(0, 0); + mpoint.add(pt); + Point pt3 = mpoint.getPoint(0); + assertTrue(pt3.getX() == 0 && pt3.getY() == 0/** && pt3.getZ() == 0 */ + ); + // assertFalse(mpoint->HasAttribute(VertexDescription::Semantics::Z)); + // pt3.setZ(115.0); + mpoint.setPoint(0, pt3); + pt3 = mpoint.getPoint(0); + assertTrue(pt3.getX() == 0 && pt3.getY() == 0/* && pt3.getZ() == 115 */); + // assertTrue(mpoint->HasAttribute(VertexDescription::Semantics::Z)); + // CompareGeometryContent(mpoint, &pt, 1); + } + + {// move 3d + MultiPoint mpoint = new MultiPoint(); + assertTrue(mpoint != null); + Point pt = new Point(0, 0); + mpoint.add(pt); + Point pt3 = mpoint.getPoint(0); + assertTrue(pt3.getX() == 0 && pt3.getY() == 0/* && pt3.getZ() == 0 */); + } + + { // test QueryInterval + MultiPoint mpoint = new MultiPoint(); + + Point pt1 = new Point(0.0, 0.0); + // pt1.setZ(-1.0); + + Point pt2 = new Point(0.0, 0.0); + // pt2.setZ(1.0); + + mpoint.add(pt1); + mpoint.add(pt2); + + // Envelope1D e = + // mpoint->QueryInterval(enum_value2(VertexDescription, Semantics, + // Z), 0); + Envelope e = new Envelope(); + mpoint.queryEnvelope(e); + // assertTrue(e.get == -1.0 && e.vmax == 1.0); + } + + { + @SuppressWarnings("unused") + MultiPoint geom = new MultiPoint(); + // int sz = sizeof(openString) / sizeof(openString[0]); + // for (int i = 0; i < sz; i++) + // geom.add(openString[i]); + // CompareGeometryContent(geom, openString, sz); + } + + { + @SuppressWarnings("unused") + MultiPoint geom = new MultiPoint(); + // int sz = sizeof(openString) / sizeof(openString[0]); + // Point point = GCNEW Point; + // for (int i = 0; i < sz; i++) + // { + // point.setXY(openString[i]); + // geom.add(point); + // } + // CompareGeometryContent(geom, openString, sz); + } + + // Test AddPoints + { + @SuppressWarnings("unused") + MultiPoint geom = new MultiPoint(); + // int sz = sizeof(openString) / sizeof(openString[0]); + // geom.addPoints(openString, sz, 0, -1); + // CompareGeometryContent((MultiVertexGeometry)geom, openString, + // sz); + } + + // Test InsertPoint(Point2D) + { + MultiPoint mpoint = new MultiPoint(); + Point pt0 = new Point(0.0, 0.0); + // pt0.setZ(-1.0); + // pt0.setID(7); + + Point pt1 = new Point(0.0, 0.0); + // pt1.setZ(1.0); + // pt1.setID(11); + + Point pt2 = new Point(0.0, 1.0); + // pt2.setZ(1.0); + // pt2.setID(13); + + mpoint.add(pt0); + mpoint.add(pt1); + mpoint.add(pt2); + + Point pt3 = new Point(-11.0, -13.0); + + mpoint.add(pt3); + mpoint.insertPoint(1, pt3); + assertTrue(mpoint.getPointCount() == 5); + + Point pt; + pt = mpoint.getPoint(0); + assertTrue(pt.getX() == pt0.getX() && pt.getY() == pt0.getY()/* + * && + * pt. + * getZ + * () == + * pt0 + * .getZ + * () + */); + + pt = mpoint.getPoint(1); + assertTrue(pt.getX() == pt3.getX() && pt.getY() == pt3.getY()); + + pt = mpoint.getPoint(2); + assertTrue(pt.getX() == pt1.getX() && pt.getY() == pt1.getY()/* + * && + * pt. + * getZ + * () == + * pt1 + * .getZ + * () + */); + + pt = mpoint.getPoint(3); + assertTrue(pt.getX() == pt2.getX() && pt.getY() == pt2.getY()/* + * && + * pt. + * getZ + * () == + * pt2 + * .getZ + * () + */); + + Point point = new Point(); + point.setXY(17.0, 19.0); + // point.setID(12); + // point.setM(5); + + mpoint.insertPoint(2, point); + mpoint.add(point); + + assertTrue(mpoint.getPointCount() == 7); + + // double m; + // int id; + // pt = mpoint.getXYZ(2); + // assertTrue(pt.x == 17.0 && pt.y == 19.0 && pt.z == defaultZ); + // m = mpoint.getAttributeAsDbl(enum_value2(VertexDescription, + // Semantics, M), 2, 0); + // assertTrue(m == 5); + // id = mpoint.getAttributeAsInt(enum_value2(VertexDescription, + // Semantics, ID), 2, 0); + // assertTrue(id == 23); + // + // pt = mpoint.getXYZ(3); + // assertTrue(pt.x == pt1.x && pt.y == pt1.y && pt.z == pt1.z); + // m = mpoint.getAttributeAsDbl(enum_value2(VertexDescription, + // Semantics, M), 3, 0); + // assertTrue(NumberUtils::IsNaN(m)); + // id = mpoint.getAttributeAsInt(enum_value2(VertexDescription, + // Semantics, ID), 3, 0); + // assertTrue(id == 11); + } + + MultiPoint mpoint = new MultiPoint(); + Point pt0 = new Point(0.0, 0.0, -1.0); + + Point pt1 = new Point(0.0, 0.0, 1.0); + + Point pt2 = new Point(0.0, 1.0, 1.0); + + mpoint.add(pt0); + mpoint.add(pt1); + mpoint.add(pt2); + + mpoint.removePoint(1); + + Point pt; + pt = mpoint.getPoint(0); + assertTrue(pt.getX() == pt0.getX() && pt.getY() == pt0.getY()); + pt = mpoint.getPoint(1); + assertTrue(pt.getX() == pt2.getX() && pt.getY() == pt2.getY()); + + assertTrue(mpoint.getPointCount() == 2); + } + + @Test + public static void testCopy() { + MultiPoint mpoint = new MultiPoint(); + Point pt0 = new Point(0.0, 0.0, -1.0); + Point pt1 = new Point(0.0, 0.0, 1.0); + Point pt2 = new Point(0.0, 1.0, 1.0); + + mpoint.add(pt0); + mpoint.add(pt1); + mpoint.add(pt2); + mpoint.removePoint(1); + + MultiPoint mpCopy = (MultiPoint) mpoint.copy(); + assertTrue(mpCopy.equals(mpoint)); + + Point pt; + pt = mpCopy.getPoint(0); + assertTrue(pt.getX() == pt0.getX() && pt.getY() == pt0.getY()); + pt = mpCopy.getPoint(1); + assertTrue(pt.getX() == pt2.getX() && pt.getY() == pt2.getY()); + + assertTrue(mpCopy.getPointCount() == 2); + } } diff --git a/src/test/java/com/esri/core/geometry/TestOGC.java b/src/test/java/com/esri/core/geometry/TestOGC.java index fa0f61ef..68a93f40 100644 --- a/src/test/java/com/esri/core/geometry/TestOGC.java +++ b/src/test/java/com/esri/core/geometry/TestOGC.java @@ -31,886 +31,886 @@ import java.nio.ByteBuffer; public class TestOGC extends TestCase { - @Override - protected void setUp() throws Exception { - super.setUp(); - } - - @Override - protected void tearDown() throws Exception { - super.tearDown(); - } - - @Test - public void testPoint() { - OGCGeometry g = OGCGeometry.fromText("POINT(1 2)"); - assertTrue(g.geometryType().equals("Point")); - OGCPoint p = (OGCPoint) g; - assertTrue(p.X() == 1); - assertTrue(p.Y() == 2); - assertTrue(g.equals(OGCGeometry.fromText("POINT(1 2)"))); - assertTrue(!g.equals(OGCGeometry.fromText("POINT(1 3)"))); - assertTrue(g.equals((Object) OGCGeometry.fromText("POINT(1 2)"))); - assertTrue(!g.equals((Object) OGCGeometry.fromText("POINT(1 3)"))); - OGCGeometry buf = g.buffer(10); - assertTrue(buf.geometryType().equals("Polygon")); - OGCPolygon poly = (OGCPolygon) buf.envelope(); - double a = poly.area(); - assertTrue(Math.abs(a - 400) < 1e-1); - } - - @Test - public void testPolygon() throws Exception { - OGCGeometry g = OGCGeometry - .fromText("POLYGON((-10 -10, 10 -10, 10 10, -10 10, -10 -10), (-5 -5, -5 5, 5 5, 5 -5, -5 -5))"); - assertTrue(g.geometryType().equals("Polygon")); - OGCPolygon p = (OGCPolygon) g; - assertTrue(p.numInteriorRing() == 1); - OGCLineString ls = p.exteriorRing(); - // assertTrue(ls.pointN(1).equals(OGCGeometry.fromText("POINT(10 -10)"))); - boolean b = ls - .Equals(OGCGeometry - .fromText("LINESTRING(-10 -10, 10 -10, 10 10, -10 10, -10 -10)")); - assertTrue(b); - OGCLineString lsi = p.interiorRingN(0); - b = lsi.Equals(OGCGeometry - .fromText("LINESTRING(-5 -5, -5 5, 5 5, 5 -5, -5 -5)")); - assertTrue(b); - b = lsi.equals((Object) OGCGeometry - .fromText("LINESTRING(-5 -5, -5 5, 5 5, 5 -5, -5 -5)")); - assertTrue(!lsi.Equals(ls)); - OGCMultiCurve boundary = p.boundary(); - String s = boundary.asText(); - assertTrue(s.equals("MULTILINESTRING ((-10 -10, 10 -10, 10 10, -10 10, -10 -10), (-5 -5, -5 5, 5 5, 5 -5, -5 -5))")); - - { - OGCGeometry g2 = OGCGeometry.fromGeoJson( - "{\"type\": \"Polygon\", \"coordinates\": [[[1.00000001,1.00000001], [4.00000001,1.00000001], [4.00000001,4.00000001], [1.00000001,4.00000001]]]}"); - OGCGeometry - .fromGeoJson( - "{\"type\": \"LineString\", \"coordinates\": [[1.00000001,1.00000001], [7.00000001,8.00000001]]}") - .intersects(g2); - OGCGeometry - .fromGeoJson( - "{\"type\": \"LineString\", \"coordinates\": [[2.449,4.865], [7.00000001,8.00000001]]}") - .intersects(g2); - - OGCGeometry g3 = OGCGeometry.fromGeoJson( - "{\"type\": \"Polygon\", \"coordinates\": [[[1.00000001,1.00000001], [4.00000001,1.00000001], [4.00000001,4.00000001], [1.00000001,4.00000001]]]}"); - boolean bb = g2.equals((Object) g3); - assertTrue(bb); - } - } - - @Test - public void testGeometryCollection() throws Exception { - OGCGeometry g = OGCGeometry - .fromText("GEOMETRYCOLLECTION(POLYGON EMPTY, POINT(1 1), LINESTRING EMPTY, MULTIPOLYGON EMPTY, MULTILINESTRING EMPTY)"); - assertTrue(g.geometryType().equals("GeometryCollection")); - OGCConcreteGeometryCollection gc = (OGCConcreteGeometryCollection) g; - assertTrue(gc.numGeometries() == 5); - assertTrue(gc.geometryN(0).geometryType().equals("Polygon")); - assertTrue(gc.geometryN(1).geometryType().equals("Point")); - assertTrue(gc.geometryN(2).geometryType().equals("LineString")); - assertTrue(gc.geometryN(3).geometryType().equals("MultiPolygon")); - assertTrue(gc.geometryN(4).geometryType().equals("MultiLineString")); - - g = OGCGeometry - .fromText("GEOMETRYCOLLECTION(POLYGON EMPTY, POINT(1 1), GEOMETRYCOLLECTION EMPTY, LINESTRING EMPTY, GEOMETRYCOLLECTION(POLYGON EMPTY, POINT(1 1), LINESTRING EMPTY, MULTIPOLYGON EMPTY, MULTILINESTRING EMPTY, MULTIPOINT EMPTY), MULTIPOLYGON EMPTY, MULTILINESTRING EMPTY)"); - assertTrue(g.geometryType().equals("GeometryCollection")); - gc = (OGCConcreteGeometryCollection) g; - assertTrue(gc.numGeometries() == 7); - assertTrue(gc.geometryN(0).geometryType().equals("Polygon")); - assertTrue(gc.geometryN(1).geometryType().equals("Point")); - assertTrue(gc.geometryN(2).geometryType().equals("GeometryCollection")); - assertTrue(gc.geometryN(3).geometryType().equals("LineString")); - assertTrue(gc.geometryN(4).geometryType().equals("GeometryCollection")); - assertTrue(gc.geometryN(5).geometryType().equals("MultiPolygon")); - assertTrue(gc.geometryN(6).geometryType().equals("MultiLineString")); - - OGCConcreteGeometryCollection gc2 = (OGCConcreteGeometryCollection) gc - .geometryN(4); - assertTrue(gc2.numGeometries() == 6); - assertTrue(gc2.geometryN(0).geometryType().equals("Polygon")); - assertTrue(gc2.geometryN(1).geometryType().equals("Point")); - assertTrue(gc2.geometryN(2).geometryType().equals("LineString")); - assertTrue(gc2.geometryN(3).geometryType().equals("MultiPolygon")); - assertTrue(gc2.geometryN(4).geometryType().equals("MultiLineString")); - assertTrue(gc2.geometryN(5).geometryType().equals("MultiPoint")); - - ByteBuffer wkbBuffer = g.asBinary(); - g = OGCGeometry.fromBinary(wkbBuffer); - - assertTrue(g.geometryType().equals("GeometryCollection")); - gc = (OGCConcreteGeometryCollection) g; - assertTrue(gc.numGeometries() == 7); - assertTrue(gc.geometryN(0).geometryType().equals("Polygon")); - assertTrue(gc.geometryN(1).geometryType().equals("Point")); - assertTrue(gc.geometryN(2).geometryType().equals("GeometryCollection")); - assertTrue(gc.geometryN(3).geometryType().equals("LineString")); - assertTrue(gc.geometryN(4).geometryType().equals("GeometryCollection")); - assertTrue(gc.geometryN(5).geometryType().equals("MultiPolygon")); - assertTrue(gc.geometryN(6).geometryType().equals("MultiLineString")); - - gc2 = (OGCConcreteGeometryCollection) gc.geometryN(4); - assertTrue(gc2.numGeometries() == 6); - assertTrue(gc2.geometryN(0).geometryType().equals("Polygon")); - assertTrue(gc2.geometryN(1).geometryType().equals("Point")); - assertTrue(gc2.geometryN(2).geometryType().equals("LineString")); - assertTrue(gc2.geometryN(3).geometryType().equals("MultiPolygon")); - assertTrue(gc2.geometryN(4).geometryType().equals("MultiLineString")); - assertTrue(gc2.geometryN(5).geometryType().equals("MultiPoint")); - - String wktString = g.asText(); - assertTrue(wktString - .equals("GEOMETRYCOLLECTION (POLYGON EMPTY, POINT (1 1), GEOMETRYCOLLECTION EMPTY, LINESTRING EMPTY, GEOMETRYCOLLECTION (POLYGON EMPTY, POINT (1 1), LINESTRING EMPTY, MULTIPOLYGON EMPTY, MULTILINESTRING EMPTY, MULTIPOINT EMPTY), MULTIPOLYGON EMPTY, MULTILINESTRING EMPTY)")); - - g = OGCGeometry - .fromGeoJson("{\"type\" : \"GeometryCollection\", \"geometries\" : [{\"type\" : \"Polygon\", \"coordinates\" : []}, {\"type\" : \"Point\", \"coordinates\" : [1, 1]}, {\"type\" : \"GeometryCollection\", \"geometries\" : []}, {\"type\" : \"LineString\", \"coordinates\" : []}, {\"type\" : \"GeometryCollection\", \"geometries\" : [{\"type\": \"Polygon\", \"coordinates\" : []}, {\"type\" : \"Point\", \"coordinates\" : [1,1]}, {\"type\" : \"LineString\", \"coordinates\" : []}, {\"type\" : \"MultiPolygon\", \"coordinates\" : []}, {\"type\" : \"MultiLineString\", \"coordinates\" : []}, {\"type\" : \"MultiPoint\", \"coordinates\" : []}]}, {\"type\" : \"MultiPolygon\", \"coordinates\" : []}, {\"type\" : \"MultiLineString\", \"coordinates\" : []} ] }"); - - wktString = g.asText(); - assertTrue(wktString - .equals("GEOMETRYCOLLECTION (POLYGON EMPTY, POINT (1 1), GEOMETRYCOLLECTION EMPTY, LINESTRING EMPTY, GEOMETRYCOLLECTION (POLYGON EMPTY, POINT (1 1), LINESTRING EMPTY, MULTIPOLYGON EMPTY, MULTILINESTRING EMPTY, MULTIPOINT EMPTY), MULTIPOLYGON EMPTY, MULTILINESTRING EMPTY)")); - - assertTrue(g.equals((Object) OGCGeometry.fromText(wktString))); - - assertTrue(g.hashCode() == OGCGeometry.fromText(wktString).hashCode()); - - } - - @Test - public void testFirstPointOfPolygon() { - OGCGeometry g = OGCGeometry - .fromText("POLYGON((-10 -10, 10 -10, 10 10, -10 10, -10 -10), (-5 -5, -5 5, 5 5, 5 -5, -5 -5))"); - assertTrue(g.geometryType().equals("Polygon")); - OGCPolygon p = (OGCPolygon) g; - assertTrue(p.numInteriorRing() == 1); - OGCLineString ls = p.exteriorRing(); - OGCPoint p1 = ls.pointN(1); - assertTrue(ls.pointN(1).equals(OGCGeometry.fromText("POINT(10 -10)"))); - OGCPoint p2 = ls.pointN(3); - assertTrue(ls.pointN(3).equals(OGCGeometry.fromText("POINT(-10 10)"))); - OGCPoint p0 = ls.pointN(0); - assertTrue(ls.pointN(0).equals(OGCGeometry.fromText("POINT(-10 -10)"))); - String ms = g.convertToMulti().asText(); - assertTrue(ms.equals("MULTIPOLYGON (((-10 -10, 10 -10, 10 10, -10 10, -10 -10), (-5 -5, -5 5, 5 5, 5 -5, -5 -5)))")); - - } - - @Test - public void testFirstPointOfLineString() { - OGCGeometry g = OGCGeometry - .fromText("LINESTRING(-10 -10, 10 -10, 10 10, -10 10, -10 -10)"); - assertTrue(g.geometryType().equals("LineString")); - OGCLineString p = (OGCLineString) g; - assertTrue(p.numPoints() == 5); - assertTrue(p.isClosed()); - assertTrue(p.pointN(1).equals(OGCGeometry.fromText("POINT(10 -10)"))); - String ms = g.convertToMulti().asText(); - assertTrue(ms.equals("MULTILINESTRING ((-10 -10, 10 -10, 10 10, -10 10, -10 -10))")); - } - - @Test - public void testPointInPolygon() { - OGCGeometry g = OGCGeometry - .fromText("POLYGON((-10 -10, 10 -10, 10 10, -10 10, -10 -10), (-5 -5, -5 5, 5 5, 5 -5, -5 -5))"); - assertTrue(g.geometryType().equals("Polygon")); - assertTrue(!g.contains(OGCGeometry.fromText("POINT(0 0)"))); - assertTrue(g.contains(OGCGeometry.fromText("POINT(9 9)"))); - assertTrue(!g.contains(OGCGeometry.fromText("POINT(-20 1)"))); - assertTrue(g.disjoint(OGCGeometry.fromText("POINT(0 0)"))); - assertTrue(!g.disjoint(OGCGeometry.fromText("POINT(9 9)"))); - assertTrue(g.disjoint(OGCGeometry.fromText("POINT(-20 1)"))); - } - - @Test - public void testMultiPolygon() { - { - OGCGeometry g = OGCGeometry - .fromText("MULTIPOLYGON(((-10 -10, 10 -10, 10 10, -10 10, -10 -10), (-5 -5, -5 5, 5 5, 5 -5, -5 -5)))"); - assertTrue(g.geometryType().equals("MultiPolygon")); // the type is - // reduced - assertTrue(!g.contains(OGCGeometry.fromText("POINT(0 0)"))); - assertTrue(g.contains(OGCGeometry.fromText("POINT(9 9)"))); - assertTrue(!g.contains(OGCGeometry.fromText("POINT(-20 1)"))); - assertTrue(g.disjoint(OGCGeometry.fromText("POINT(0 0)"))); - assertTrue(!g.disjoint(OGCGeometry.fromText("POINT(9 9)"))); - assertTrue(g.disjoint(OGCGeometry.fromText("POINT(-20 1)"))); - assertTrue(g.convertToMulti() == g); - } - - { - OGCGeometry g = OGCGeometry - .fromText("MULTIPOLYGON(((-10 -10, 10 -10, 10 10, -10 10, -10 -10), (-5 -5, -5 5, 5 5, 5 -5, -5 -5)), ((90 90, 110 90, 110 110, 90 110, 90 90), (95 95, 95 105, 105 105, 105 95, 95 95)))"); - assertTrue(g.geometryType().equals("MultiPolygon")); // the type is - - OGCMultiPolygon mp = (OGCMultiPolygon) g; - assertTrue(mp.numGeometries() == 2); - OGCGeometry p1 = mp.geometryN(0); - assertTrue(p1.geometryType().equals("Polygon")); // the type is - assertTrue(p1.contains(OGCGeometry.fromText("POINT(9 9)"))); - assertTrue(!p1.contains(OGCGeometry.fromText("POINT(109 109)"))); - OGCGeometry p2 = mp.geometryN(1); - assertTrue(p2.geometryType().equals("Polygon")); // the type is - assertTrue(!p2.contains(OGCGeometry.fromText("POINT(9 9)"))); - assertTrue(p2.contains(OGCGeometry.fromText("POINT(109 109)"))); - } - } - - @Test - public void testMultiPolygonUnion() { - OGCGeometry g = OGCGeometry - .fromText("POLYGON((-10 -10, 10 -10, 10 10, -10 10, -10 -10), (-5 -5, -5 5, 5 5, 5 -5, -5 -5))"); - OGCGeometry g2 = OGCGeometry - .fromText("POLYGON((90 90, 110 90, 110 110, 90 110, 90 90))"); - OGCGeometry u = g.union(g2); - assertTrue(u.geometryType().equals("MultiPolygon")); - assertTrue(!u.contains(OGCGeometry.fromText("POINT(0 0)"))); - assertTrue(u.contains(OGCGeometry.fromText("POINT(9 9)"))); - assertTrue(!u.contains(OGCGeometry.fromText("POINT(-20 1)"))); - assertTrue(u.disjoint(OGCGeometry.fromText("POINT(0 0)"))); - assertTrue(!u.disjoint(OGCGeometry.fromText("POINT(9 9)"))); - assertTrue(u.disjoint(OGCGeometry.fromText("POINT(-20 1)"))); - assertTrue(u.contains(OGCGeometry.fromText("POINT(100 100)"))); - } - - @Test - public void testIntersection() { - OGCGeometry g = OGCGeometry.fromText("LINESTRING(0 0, 10 10)"); - OGCGeometry g2 = OGCGeometry.fromText("LINESTRING(10 0, 0 10)"); - OGCGeometry u = g.intersection(g2); - assertTrue(u.dimension() == 0); - String s = u.asText(); - assertTrue(u.equals(OGCGeometry.fromText("POINT(5 5)"))); - } - - @Test - public void testPointSymDif() { - OGCGeometry g1 = OGCGeometry.fromText("POINT(1 2)"); - OGCGeometry g2 = OGCGeometry.fromText("POINT(3 4)"); - OGCGeometry gg = g1.symDifference(g2); - assertTrue(gg.equals(OGCGeometry.fromText("MULTIPOINT(1 2, 3 4)"))); - - OGCGeometry g3 = OGCGeometry.fromText("POINT(1 2)"); - OGCGeometry gg1 = g1.symDifference(g3); - assertTrue(gg1 == null || gg1.isEmpty()); - - } - - @Test - public void testNullSr() { - String wkt = "point (0 0)"; - OGCGeometry g = OGCGeometry.fromText(wkt); - g.setSpatialReference(null); - assertTrue(g.SRID() < 1); - } - - @Test - public void testIsectPoint() { - String wkt = "point (0 0)"; - String wk2 = "point (0 0)"; - OGCGeometry g0 = OGCGeometry.fromText(wkt); - OGCGeometry g1 = OGCGeometry.fromText(wk2); - g0.setSpatialReference(null); - g1.setSpatialReference(null); - try { - OGCGeometry rslt = g0.intersection(g1); // ArrayIndexOutOfBoundsException - assertTrue(rslt != null); - } catch (Exception e) { - assertTrue(false); - } - } - - @Test - public void testIsectDisjoint() { - String wk3 = "linestring (0 0, 1 1)"; - String wk4 = "linestring (2 2, 4 4)"; - OGCGeometry g0 = OGCGeometry.fromText(wk3); - OGCGeometry g1 = OGCGeometry.fromText(wk4); - g0.setSpatialReference(null); - g1.setSpatialReference(null); - try { - OGCGeometry rslt = g0.intersection(g1); // null - assertTrue(rslt != null); - } catch (Exception e) { - assertTrue(false); - } - } - - @Test - public void test_polygon_is_simple_for_OGC() { - try { - { - String s = "{\"rings\":[[[0, 0], [0, 10], [10, 10], [10, 0], [0, 0]]]}"; - OGCGeometry g = OGCGeometry.fromJson(s); - boolean res = g.isSimple(); - assertTrue(res); - assertTrue(g.isSimpleRelaxed()); - } - - {// exterior ring is self-tangent - String s = "{\"rings\":[[[0, 0], [0, 10], [5, 5], [10, 10], [10, 0], [5, 5], [0, 0]]]}"; - OGCGeometry g = OGCGeometry.fromJson(s); - boolean res = g.isSimple(); - assertTrue(!res); - assertTrue(g.isSimpleRelaxed()); - } - - {// ring orientation (hole is cw) - String s = "{\"rings\":[[[-100, -100], [-100, 100], [100, 100], [100, -100], [-100, -100]], [[0, 0], [5, 5], [10, 0], [0, 0]]]}"; - OGCGeometry g = OGCGeometry.fromJson(s); - boolean res = g.isSimple(); - assertTrue(!res); - assertTrue(!g.isSimpleRelaxed()); - } - { - String s = "{\"rings\":[[[-100, -100], [-100, 100], [100, 100], [100, -100], [-100, -100]], [[0, 0], [10, 0], [5, 5], [0, 0]]]}"; - OGCGeometry g = OGCGeometry.fromJson(s); - boolean res = g.isSimple(); - assertTrue(res); - assertTrue(g.isSimpleRelaxed()); - } - - {// ring order - String s = "{\"rings\":[[[0, 0], [10, 0], [5, 5], [0, 0]], [[-100, -100], [-100, 100], [100, 100], [100, -100], [-100, -100]]]}"; - OGCGeometry g = OGCGeometry.fromJson(s); - boolean res = g.isSimple(); - assertTrue(!res); - assertTrue(g.isSimpleRelaxed()); - } - - { - // hole is self tangent - String s = "{\"rings\":[[[-100, -100], [-100, 100], [100, 100], [100, -100], [-100, -100]], [[0, 0], [5, 5], [10, 0], [10, 10], [5, 5], [0, 10], [0, 0]]]}"; - OGCGeometry g = OGCGeometry.fromJson(s); - boolean res = g.isSimple(); - assertTrue(!res); - assertTrue(g.isSimpleRelaxed()); - } - { - // two holes touch - String s = "{\"rings\":[[[-100, -100], [-100, 100], [100, 100], [100, -100], [-100, -100]], [[0, 0], [10, 0], [5, 5], [0, 0]], [[10, 10], [0, 10], [5, 5], [10, 10]]]}"; - OGCGeometry g = OGCGeometry.fromJson(s); - boolean res = g.isSimple(); - assertTrue(res); - assertTrue(g.isSimpleRelaxed()); - } - { - // two holes touch, bad orientation - String s = "{\"rings\":[[[-100, -100], [-100, 100], [100, 100], [100, -100], [-100, -100]], [[0, 0], [5, 5], [10, 0], [0, 0]], [[10, 10], [0, 10], [5, 5], [10, 10]]]}"; - OGCGeometry g = OGCGeometry.fromJson(s); - boolean res = g.isSimple(); - assertTrue(!res); - assertTrue(!g.isSimpleRelaxed()); - - } - - { - // hole touches exterior in two spots - String s = "{\"rings\":[[[-100, -100], [-100, 100], [0, 100], [100, 100], [100, -100], [0, -100], [-100, -100]], [[0, -100], [10, 0], [0, 100], [-10, 0], [0, -100]]]}"; - OGCGeometry g = OGCGeometry.fromJson(s); - boolean res = g.isSimple(); - assertTrue(!res); - assertTrue(g.isSimpleRelaxed()); - } - - { - // hole touches exterior in one spot - String s = "{\"rings\":[[[-100, -100], [-100, 100], [0, 100], [100, 100], [100, -100], [0, -100], [-100, -100]], [[0, -100], [10, 0], [0, 90], [-10, 0], [0, -100]]]}"; - OGCGeometry g = OGCGeometry.fromJson(s); - boolean res = g.isSimple(); - assertTrue(res); - assertTrue(g.isSimpleRelaxed()); - - } - - { - // exterior has inversion (planar simple) - String s = "{\"rings\":[[[-100, -100], [-100, 100], [0, 100], [100, 100], [100, -100], [0, -100], [10, 0], [0, 90], [-10, 0], [0, -100], [-100, -100]]]}"; - OGCGeometry g = OGCGeometry.fromJson(s); - boolean res = g.isSimple(); - assertTrue(!res); - assertTrue(g.isSimpleRelaxed()); - } - - { - // two holes touch in one spot, and they also touch exterior in - // two spots, producing disconnected interior - String s = "{\"rings\":[[[-100, -100], [-100, 100], [0, 100], [100, 100], [100, -100], [0, -100], [-100, -100]], [[0, -100], [10, -50], [0, 0], [-10, -50], [0, -100]], [[0, 0], [10, 50], [0, 100], [-10, 50], [0, 0]]]}"; - OGCGeometry g = OGCGeometry.fromJson(s); - boolean res = g.isSimple(); - assertTrue(!res); - assertTrue(g.isSimpleRelaxed()); - } - } catch (Exception ex) { - assertTrue(false); - } - } - - @Test - public void test_polygon_simplify_for_OGC() { - try { - { - //degenerate - String s = "{\"rings\":[[[0, 0], [0, 10], [10, 10], [10, 0], [20, 0], [10, 0], [0, 0]]]}"; - OGCGeometry g = OGCGeometry.fromJson(s); - boolean res = g.isSimple(); - assertTrue(!res); - Geometry resg = OperatorSimplifyOGC.local().execute(g.getEsriGeometry(), null, true, null); - OGCGeometry og = OGCGeometry.createFromEsriGeometry(resg, null); - String res_str = og.asText(); - assertTrue(og.isSimple()); - } - { - String s = "{\"rings\":[[[0, 0], [0, 10], [10, 10], [10, 0], [0, 0]]]}"; - OGCGeometry g = OGCGeometry.fromJson(s); - boolean res = g.isSimple(); - assertTrue(res); - assertTrue(g.isSimpleRelaxed()); - Geometry resg = OperatorSimplifyOGC.local().execute(g.getEsriGeometry(), null, true, null); - OGCGeometry og = OGCGeometry.createFromEsriGeometry(resg, null); - String res_str = og.asText(); - assertTrue(og.geometryType().equals("Polygon")); - assertTrue(((OGCPolygon) og).numInteriorRing() == 0); - assertTrue(og.isSimple()); - } - - {// exterior ring is self-tangent - String s = "{\"rings\":[[[0, 0], [0, 10], [5, 5], [10, 10], [10, 0], [5, 5], [0, 0]]]}"; - OGCGeometry g = OGCGeometry.fromJson(s); - boolean res = g.isSimple(); - assertTrue(!res); - assertTrue(g.isSimpleRelaxed()); - Geometry resg = OperatorSimplifyOGC.local().execute(g.getEsriGeometry(), null, true, null); - OGCGeometry og = OGCGeometry.createFromEsriGeometry(resg, null); - res = og.isSimple(); - assertTrue(res); - assertTrue(og.geometryType().equals("MultiPolygon")); - assertTrue(((OGCGeometryCollection) og).numGeometries() == 2); - } - - {// ring orientation (hole is cw) - String s = "{\"rings\":[[[-100, -100], [-100, 100], [100, 100], [100, -100], [-100, -100]], [[0, 0], [5, 5], [10, 0], [0, 0]]]}"; - OGCGeometry g = OGCGeometry.fromJson(s); - boolean res = g.isSimple(); - assertTrue(!res); - assertTrue(!g.isSimpleRelaxed()); - Geometry resg = OperatorSimplifyOGC.local().execute(g.getEsriGeometry(), null, true, null); - OGCGeometry og = OGCGeometry.createFromEsriGeometry(resg, null); - res = og.isSimple(); - assertTrue(res); - assertTrue(og.geometryType().equals("Polygon")); - assertTrue(((OGCPolygon) og).numInteriorRing() == 1); - } - - {// ring order - String s = "{\"rings\":[[[0, 0], [10, 0], [5, 5], [0, 0]], [[-100, -100], [-100, 100], [100, 100], [100, -100], [-100, -100]]]}"; - OGCGeometry g = OGCGeometry.fromJson(s); - boolean res = g.isSimple(); - assertTrue(!res); - assertTrue(g.isSimpleRelaxed()); - Geometry resg = OperatorSimplifyOGC.local().execute(g.getEsriGeometry(), null, true, null); - OGCGeometry og = OGCGeometry.createFromEsriGeometry(resg, null); - res = og.isSimple(); - assertTrue(res); - assertTrue(og.geometryType().equals("Polygon")); - } - - { - // hole is self tangent - String s = "{\"rings\":[[[-100, -100], [-100, 100], [100, 100], [100, -100], [-100, -100]], [[0, 0], [5, 5], [10, 0], [10, 10], [5, 5], [0, 10], [0, 0]]]}"; - OGCGeometry g = OGCGeometry.fromJson(s); - boolean res = g.isSimple(); - assertTrue(!res); - assertTrue(g.isSimpleRelaxed()); - Geometry resg = OperatorSimplifyOGC.local().execute(g.getEsriGeometry(), null, true, null); - OGCGeometry og = OGCGeometry.createFromEsriGeometry(resg, null); - String res_str = og.asText(); - res = og.isSimple(); - assertTrue(res); - assertTrue(og.geometryType().equals("Polygon")); - assertTrue(((OGCPolygon) og).numInteriorRing() == 2); - } - { - // two holes touch - String s = "{\"rings\":[[[-100, -100], [-100, 100], [100, 100], [100, -100], [-100, -100]], [[0, 0], [10, 0], [5, 5], [0, 0]], [[10, 10], [0, 10], [5, 5], [10, 10]]]}"; - OGCGeometry g = OGCGeometry.fromJson(s); - boolean res = g.isSimple(); - assertTrue(res); - assertTrue(g.isSimpleRelaxed()); - Geometry resg = OperatorSimplifyOGC.local().execute(g.getEsriGeometry(), null, true, null); - OGCGeometry og = OGCGeometry.createFromEsriGeometry(resg, null); - assertTrue(og.geometryType().equals("Polygon")); - assertTrue(((OGCPolygon) og).numInteriorRing() == 2); - } - { - // two holes touch, bad orientation - String s = "{\"rings\":[[[-100, -100], [-100, 100], [100, 100], [100, -100], [-100, -100]], [[0, 0], [5, 5], [10, 0], [0, 0]], [[10, 10], [0, 10], [5, 5], [10, 10]]]}"; - OGCGeometry g = OGCGeometry.fromJson(s); - boolean res = g.isSimple(); - assertTrue(!res); - assertTrue(!g.isSimpleRelaxed()); - Geometry resg = OperatorSimplifyOGC.local().execute(g.getEsriGeometry(), null, true, null); - OGCGeometry og = OGCGeometry.createFromEsriGeometry(resg, null); - assertTrue(og.geometryType().equals("Polygon")); - assertTrue(((OGCPolygon) og).numInteriorRing() == 2); - } - - { - // hole touches exterior in two spots - //OperatorSimplifyOGC produces a multipolygon with two polygons without holes. - String s = "{\"rings\":[[[-100, -100], [-100, 100], [0, 100], [100, 100], [100, -100], [0, -100], [-100, -100]], [[0, -100], [10, 0], [0, 100], [-10, 0], [0, -100]]]}"; - OGCGeometry g = OGCGeometry.fromJson(s); - boolean res = g.isSimple(); - assertTrue(!res); - assertTrue(g.isSimpleRelaxed()); - Geometry resg = OperatorSimplifyOGC.local().execute(g.getEsriGeometry(), null, true, null); - OGCGeometry og = OGCGeometry.createFromEsriGeometry(resg, null); - assertTrue(og.geometryType().equals("MultiPolygon")); - assertTrue(((OGCMultiPolygon) og).numGeometries() == 2); - assertTrue(((OGCPolygon) ((OGCMultiPolygon) og).geometryN(0)).numInteriorRing() == 0); - assertTrue(((OGCPolygon) ((OGCMultiPolygon) og).geometryN(1)).numInteriorRing() == 0); - } - - { - // hole touches exterior in one spot - //OperatorSimplifyOGC produces a polygons with a hole. - String s = "{\"rings\":[[[-100, -100], [-100, 100], [0, 100], [100, 100], [100, -100], [0, -100], [-100, -100]], [[0, -100], [10, 0], [0, 90], [-10, 0], [0, -100]]]}"; - OGCGeometry g = OGCGeometry.fromJson(s); - boolean res = g.isSimple(); - assertTrue(res); - assertTrue(g.isSimpleRelaxed()); - Geometry resg = OperatorSimplifyOGC.local().execute(g.getEsriGeometry(), null, true, null); - OGCGeometry og = OGCGeometry.createFromEsriGeometry(resg, null); - assertTrue(og.geometryType().equals("Polygon")); - assertTrue(((OGCPolygon) og).numInteriorRing() == 1); - } - - { - // exterior has inversion (non simple for OGC) - //OperatorSimplifyOGC produces a polygons with a hole. - String s = "{\"rings\":[[[-100, -100], [-100, 100], [0, 100], [100, 100], [100, -100], [0, -100], [10, 0], [0, 90], [-10, 0], [0, -100], [-100, -100]]]}"; - OGCGeometry g = OGCGeometry.fromJson(s); - boolean res = g.isSimple(); - assertTrue(!res); - assertTrue(g.isSimpleRelaxed()); - Geometry resg = OperatorSimplifyOGC.local().execute(g.getEsriGeometry(), null, true, null); - OGCGeometry og = OGCGeometry.createFromEsriGeometry(resg, null); - assertTrue(og.geometryType().equals("Polygon")); - assertTrue(((OGCPolygon) og).numInteriorRing() == 1); - } - - { - // two holes touch in one spot, and they also touch exterior in - // two spots, producing disconnected interior - //OperatorSimplifyOGC produces two polygons with no holes. - String s = "{\"rings\":[[[-100, -100], [-100, 100], [0, 100], [100, 100], [100, -100], [0, -100], [-100, -100]], [[0, -100], [10, -50], [0, 0], [-10, -50], [0, -100]], [[0, 0], [10, 50], [0, 100], [-10, 50], [0, 0]]]}"; - OGCGeometry g = OGCGeometry.fromJson(s); - boolean res = g.isSimple(); - assertTrue(!res); - assertTrue(g.isSimpleRelaxed()); - Geometry resg = OperatorSimplifyOGC.local().execute(g.getEsriGeometry(), null, true, null); - OGCGeometry og = OGCGeometry.createFromEsriGeometry(resg, null); - assertTrue(og.geometryType().equals("MultiPolygon")); - assertTrue(((OGCMultiPolygon) og).numGeometries() == 2); - assertTrue(((OGCPolygon) ((OGCMultiPolygon) og).geometryN(0)).numInteriorRing() == 0); - assertTrue(((OGCPolygon) ((OGCMultiPolygon) og).geometryN(1)).numInteriorRing() == 0); - } - - - { - OGCGeometry g = OGCGeometry.fromJson("{\"rings\":[[[-3,4],[6,4],[6,-3],[-3,-3],[-3,4]],[[0,2],[2,2],[0,0],[4,0],[4,2],[2,0],[2,2],[4,2],[3,3],[2,2],[1,3],[0,2]]], \"spatialReference\":{\"wkid\":4326}}"); - assertTrue(g.geometryType().equals("Polygon")); - boolean res = g.isSimple(); - assertTrue(!res); - assertTrue(!g.isSimpleRelaxed()); - OGCGeometry simpleG = g.makeSimple(); - assertTrue(simpleG.geometryType().equals("MultiPolygon")); - assertTrue(simpleG.isSimple()); - OGCMultiPolygon mp = (OGCMultiPolygon) simpleG; - assertTrue(mp.numGeometries() == 2); - OGCPolygon g1 = (OGCPolygon) mp.geometryN(0); - OGCPolygon g2 = (OGCPolygon) mp.geometryN(1); - assertTrue((g1.numInteriorRing() == 0 && g1.numInteriorRing() == 2) || - (g1.numInteriorRing() == 2 && g2.numInteriorRing() == 0)); - - OGCGeometry oldOutput = OGCGeometry.fromJson("{\"rings\":[[[-3,-3],[-3,4],[6,4],[6,-3],[-3,-3]],[[0,0],[2,0],[4,0],[4,2],[3,3],[2,2],[1,3],[0,2],[2,2],[0,0]],[[2,0],[2,2],[4,2],[2,0]]],\"spatialReference\":{\"wkid\":4326}}"); - assertTrue(oldOutput.isSimpleRelaxed()); - assertFalse(oldOutput.isSimple()); - } - } catch (Exception ex) { - assertTrue(false); - } - } - - @Test - public void test_polyline_is_simple_for_OGC() { - try { - { - String s = "{\"paths\":[[[0, 10], [8, 5], [5, 2], [6, 0]]]}"; - OGCGeometry g = OGCGeometry.fromJson(s); - boolean res = g.isSimple(); - assertTrue(res); - assertTrue(g.isSimpleRelaxed()); - } - { - String s = "{\"paths\":[[[0, 10], [6, 0], [7, 5], [0, 3]]]}";// self - // intersection - OGCGeometry g = OGCGeometry.fromJson(s); - boolean res = g.isSimple(); - assertTrue(!res); - assertTrue(g.isSimpleRelaxed()); - } - - { - String s = "{\"paths\":[[[0, 10], [6, 0], [0, 3], [0, 10]]]}"; // closed - OGCGeometry g = OGCGeometry.fromJson(s); - boolean res = g.isSimple(); - assertTrue(res); - assertTrue(g.isSimpleRelaxed()); - } - - { - String s = "{\"paths\":[[[0, 10], [5, 5], [6, 0], [0, 3], [5, 5], [0, 9], [0, 10]]]}"; // closed - // with - // self - // tangent - OGCGeometry g = OGCGeometry.fromJson(s); - boolean res = g.isSimple(); - assertTrue(!res); - assertTrue(g.isSimpleRelaxed()); - } - - { - String s = "{\"paths\":[[[0, 10], [5, 2]], [[5, 2], [6, 0]]]}";// two - // paths - // connected - // at - // a - // point - OGCGeometry g = OGCGeometry.fromJson(s); - boolean res = g.isSimple(); - assertTrue(res); - assertTrue(g.isSimpleRelaxed()); - } - - { - String s = "{\"paths\":[[[0, 0], [3, 3], [5, 0], [0, 0]], [[0, 10], [3, 3], [10, 10], [0, 10]]]}";// two - // closed - // rings - // touch - // at - // one - // point - OGCGeometry g = OGCGeometry.fromJson(s); - boolean res = g.isSimple(); - assertTrue(!res); - assertTrue(g.isSimpleRelaxed()); - } - - { - String s = "{\"paths\":[[[3, 3], [0, 0], [5, 0], [3, 3]], [[3, 3], [0, 10], [10, 10], [3, 3]]]}"; - // two closed rings touch at one point. The touch happens at the - // endpoints of the paths. - OGCGeometry g = OGCGeometry.fromJson(s); - boolean res = g.isSimple(); - assertTrue(!res); - assertTrue(g.isSimpleRelaxed()); - } - - { - String s = "{\"paths\":[[[0, 0], [10, 10]], [[0, 10], [10, 0]]]}";// two - // lines - // intersect - OGCGeometry g = OGCGeometry.fromJson(s); - boolean res = g.isSimple(); - assertTrue(!res); - assertTrue(g.isSimpleRelaxed()); - } - - { - String s = "{\"paths\":[[[0, 0], [5, 5], [0, 10]], [[10, 10], [5, 5], [10, 0]]]}";// two - // paths - // share - // mid - // point. - OGCGeometry g = OGCGeometry.fromJson(s); - boolean res = g.isSimple(); - assertTrue(!res); - assertTrue(g.isSimpleRelaxed()); - } - } catch (Exception ex) { - assertTrue(false); - } - - } - - @Test - public void test_multipoint_is_simple_for_OGC() { - try { - - SpatialReference sr = SpatialReference.create(4326); - { - String s = "{\"points\":[[0, 0], [5, 5], [0, 10]]}"; - OGCGeometry g = OGCGeometry.fromJson(s); - boolean res = g.isSimple(); - assertTrue(res); - assertTrue(g.isSimpleRelaxed()); - } - { - String s = "{\"points\":[[0, 0], [5, 5], [0, 0], [0, 10]]}"; - OGCGeometry g = OGCGeometry.fromJson(s); - boolean res = g.isSimple(); - assertTrue(!res); - assertTrue(!g.isSimpleRelaxed()); - } - { - String s = "{\"points\":[[0, 0], [5, 5], [1e-10, -1e-10], [0, 10]]}"; - OGCGeometry g = OGCGeometry.fromJson(s); - g.setSpatialReference(sr); - boolean res = g.isSimple(); - assertTrue(!res); - assertTrue(g.isSimpleRelaxed()); - } - } catch (Exception ex) { - assertTrue(false); - } - - } - - @Test - public void testGeometryCollectionBuffer() { - OGCGeometry g = OGCGeometry - .fromText("GEOMETRYCOLLECTION(POINT(1 1), POINT(1 1), POINT(1 2), LINESTRING (0 0, 1 1, 1 0, 0 1), MULTIPOLYGON EMPTY, MULTILINESTRING EMPTY)"); - OGCGeometry simpleG = g.buffer(0); - String t = simpleG.geometryType(); - String rt = simpleG.asText(); - assertTrue(simpleG.geometryType().equals("GeometryCollection")); - } - - @Test - public void testIsectTria1() { - String wkt = "polygon((1 0, 3 0, 1 2, 1 0))"; - String wk2 = "polygon((0 1, 2 1, 0 3, 0 1))"; - OGCGeometry g0 = OGCGeometry.fromText(wkt); - OGCGeometry g1 = OGCGeometry.fromText(wk2); - g0.setSpatialReference(SpatialReference.create(4326)); - g1.setSpatialReference(SpatialReference.create(4326)); - OGCGeometry rslt = g0.intersection(g1); - assertTrue(rslt != null); - assertTrue(rslt.geometryType().equals("Polygon")); - assertTrue(rslt.esriSR.getID() == 4326); - String s = rslt.asText(); - } - - @Test - public void testIsectTriaJson1() throws Exception { - String json1 = "{\"rings\":[[[1, 0], [3, 0], [1, 2], [1, 0]]], \"spatialReference\":{\"wkid\":4326}}"; - String json2 = "{\"rings\":[[[0, 1], [2, 1], [0, 3], [0, 1]]], \"spatialReference\":{\"wkid\":4326}}"; - OGCGeometry g0 = OGCGeometry.fromJson(json1); - OGCGeometry g1 = OGCGeometry.fromJson(json2); - OGCGeometry rslt = g0.intersection(g1); - assertTrue(rslt != null); - assertTrue(rslt.geometryType().equals("Polygon")); - assertTrue(rslt.esriSR.getID() == 4326); - String s = GeometryEngine.geometryToJson(rslt.getEsriSpatialReference().getID(), rslt.getEsriGeometry()); - } - - @Test - public void testIsectTria2() { - String wkt = "polygon((1 0, 3 0, 1 2, 1 0))"; - String wk2 = "polygon((0 3, 2 1, 3 1, 0 3))"; - OGCGeometry g0 = OGCGeometry.fromText(wkt); - OGCGeometry g1 = OGCGeometry.fromText(wk2); - g0.setSpatialReference(null); - g1.setSpatialReference(null); - OGCGeometry rslt = g0.intersection(g1); - assertTrue(rslt != null); - assertTrue(rslt.dimension() == 1); - assertTrue(rslt.geometryType().equals("LineString")); - String s = rslt.asText(); - } - - @Test - public void testIsectTria3() { - String wkt = "polygon((1 0, 3 0, 1 2, 1 0))"; - String wk2 = "polygon((2 2, 2 1, 3 1, 2 2))"; - OGCGeometry g0 = OGCGeometry.fromText(wkt); - OGCGeometry g1 = OGCGeometry.fromText(wk2); - g0.setSpatialReference(SpatialReference.create(4326)); - g1.setSpatialReference(SpatialReference.create(4326)); - OGCGeometry rslt = g0.intersection(g1); - assertTrue(rslt != null); - assertTrue(rslt.dimension() == 0); - assertTrue(rslt.geometryType().equals("Point")); - assertTrue(rslt.esriSR.getID() == 4326); - String s = rslt.asText(); - } - - @Test - public void testMultiPointSinglePoint() { - String wkt = "multipoint((1 0))"; - OGCGeometry g0 = OGCGeometry.fromText(wkt); - assertTrue(g0.dimension() == 0); - String gt = g0.geometryType(); - assertTrue(gt.equals("MultiPoint")); - OGCMultiPoint mp = (OGCMultiPoint) g0; - assertTrue(mp.numGeometries() == 1); - OGCGeometry p = mp.geometryN(0); - String s = p.asText(); - assertTrue(s.equals("POINT (1 0)")); - - String ms = p.convertToMulti().asText(); - assertTrue(ms.equals("MULTIPOINT ((1 0))")); - - } - - @Test - public void testWktMultiPolygon() { - String restJson = "{\"rings\": [[[-100, -100], [-100, 100], [100, 100], [100, -100], [-100, -100]], [[-90, -90], [90, 90], [-90, 90], [90, -90], [-90, -90]], [[-10, -10], [-10, 10], [10, 10], [10, -10], [-10, -10]]]}"; - MapGeometry g = null; - g = OperatorImportFromJson.local().execute(Geometry.Type.Unknown, restJson); - String wkt = OperatorExportToWkt.local().execute(0, g.getGeometry(), null); - assertTrue(wkt.equals("MULTIPOLYGON (((-100 -100, 100 -100, 100 100, -100 100, -100 -100), (-90 -90, 90 -90, -90 90, 90 90, -90 -90)), ((-10 -10, 10 -10, 10 10, -10 10, -10 -10)))")); - } - - @Test - public void testMultiPolygonArea() { - //MultiPolygon Area #36 - String wkt = "MULTIPOLYGON (((1001200 2432900, 1001420 2432691, 1001250 2432388, 1001498 2432325, 1001100 2432100, 1001500 2431900, 1002044 2431764, 1002059 2432120, 1002182 2432003, 1002400 2432300, 1002650 2432150, 1002610 2432323, 1002772 2432434, 1002410 2432821, 1002700 2433000, 1001824 2432866, 1001600 2433150, 1001200 2432900)), ((1000393 2433983, 1000914 2434018, 1000933 2433817, 1000568 2433834, 1000580 2433584, 1000700 2433750, 1000800 2433650, 1000700 2433450, 1000600 2433550, 1000200 2433350, 1000100 2433900, 1000393 2433983)), ((1001200 2432900, 1000878 2432891, 1000900 2433300, 1001659 2433509, 1001600 2433150, 1001200 2432900)), ((1002450 2431650, 1002300 2431650, 1002300 2431900, 1002500 2432100, 1002600 2431800, 1002450 2431800, 1002450 2431650)), ((999750 2433550, 999850 2433600, 999900 2433350, 999780 2433433, 999750 2433550)), ((1002950 2432050, 1003005 2431932, 1002850 2432250, 1002928 2432210, 1002950 2432050)), ((1002600 2431750, 1002642 2431882, 1002750 2431900, 1002750 2431750, 1002600 2431750)), ((1002950 2431750, 1003050 2431650, 1002968 2431609, 1002950 2431750)))"; - { - OGCGeometry ogcg = OGCGeometry.fromText(wkt); - assertTrue(ogcg.geometryType().equals("MultiPolygon")); - OGCMultiPolygon mp = (OGCMultiPolygon) ogcg; - double a = mp.area(); - assertTrue(Math.abs(mp.area() - 2037634.5) < a * 1e-14); - } - - { - OGCGeometry ogcg = OGCGeometry.fromText(wkt); - assertTrue(ogcg.geometryType().equals("MultiPolygon")); - Geometry g = ogcg.getEsriGeometry(); - double a = g.calculateArea2D(); - assertTrue(Math.abs(a - 2037634.5) < a * 1e-14); - } - } - - @Test - public void testPolylineSimplifyIssueGithub52() throws Exception { - String json = "{\"paths\":[[[2,0],[4,3],[5,1],[3.25,1.875],[1,3]]],\"spatialReference\":{\"wkid\":4326}}"; - { - OGCGeometry g = OGCGeometry.fromJson(json); - assertTrue(g.geometryType().equals("LineString")); - OGCGeometry simpleG = g.makeSimple();//make ogc simple - assertTrue(simpleG.geometryType().equals("MultiLineString")); - assertTrue(simpleG.isSimpleRelaxed());//geodatabase simple - assertTrue(simpleG.isSimple());//ogc simple - OGCMultiLineString mls = (OGCMultiLineString) simpleG; - assertTrue(mls.numGeometries() == 4); - OGCGeometry baseGeom = OGCGeometry.fromJson("{\"paths\":[[[2,0],[3.25,1.875]],[[3.25,1.875],[4,3],[5,1]],[[5,1],[3.25,1.875]],[[3.25,1.875],[1,3]]],\"spatialReference\":{\"wkid\":4326}}"); - assertTrue(simpleG.equals(baseGeom)); - - } - } + @Override + protected void setUp() throws Exception { + super.setUp(); + } + + @Override + protected void tearDown() throws Exception { + super.tearDown(); + } + + @Test + public void testPoint() { + OGCGeometry g = OGCGeometry.fromText("POINT(1 2)"); + assertTrue(g.geometryType().equals("Point")); + OGCPoint p = (OGCPoint) g; + assertTrue(p.X() == 1); + assertTrue(p.Y() == 2); + assertTrue(g.equals(OGCGeometry.fromText("POINT(1 2)"))); + assertTrue(!g.equals(OGCGeometry.fromText("POINT(1 3)"))); + assertTrue(g.equals((Object) OGCGeometry.fromText("POINT(1 2)"))); + assertTrue(!g.equals((Object) OGCGeometry.fromText("POINT(1 3)"))); + OGCGeometry buf = g.buffer(10); + assertTrue(buf.geometryType().equals("Polygon")); + OGCPolygon poly = (OGCPolygon) buf.envelope(); + double a = poly.area(); + assertTrue(Math.abs(a - 400) < 1e-1); + } + + @Test + public void testPolygon() throws Exception { + OGCGeometry g = OGCGeometry + .fromText("POLYGON((-10 -10, 10 -10, 10 10, -10 10, -10 -10), (-5 -5, -5 5, 5 5, 5 -5, -5 -5))"); + assertTrue(g.geometryType().equals("Polygon")); + OGCPolygon p = (OGCPolygon) g; + assertTrue(p.numInteriorRing() == 1); + OGCLineString ls = p.exteriorRing(); + // assertTrue(ls.pointN(1).equals(OGCGeometry.fromText("POINT(10 -10)"))); + boolean b = ls + .Equals(OGCGeometry + .fromText("LINESTRING(-10 -10, 10 -10, 10 10, -10 10, -10 -10)")); + assertTrue(b); + OGCLineString lsi = p.interiorRingN(0); + b = lsi.Equals(OGCGeometry + .fromText("LINESTRING(-5 -5, -5 5, 5 5, 5 -5, -5 -5)")); + assertTrue(b); + b = lsi.equals((Object) OGCGeometry + .fromText("LINESTRING(-5 -5, -5 5, 5 5, 5 -5, -5 -5)")); + assertTrue(!lsi.Equals(ls)); + OGCMultiCurve boundary = p.boundary(); + String s = boundary.asText(); + assertTrue(s.equals("MULTILINESTRING ((-10 -10, 10 -10, 10 10, -10 10, -10 -10), (-5 -5, -5 5, 5 5, 5 -5, -5 -5))")); + + { + OGCGeometry g2 = OGCGeometry.fromGeoJson( + "{\"type\": \"Polygon\", \"coordinates\": [[[1.00000001,1.00000001], [4.00000001,1.00000001], [4.00000001,4.00000001], [1.00000001,4.00000001]]]}"); + OGCGeometry + .fromGeoJson( + "{\"type\": \"LineString\", \"coordinates\": [[1.00000001,1.00000001], [7.00000001,8.00000001]]}") + .intersects(g2); + OGCGeometry + .fromGeoJson( + "{\"type\": \"LineString\", \"coordinates\": [[2.449,4.865], [7.00000001,8.00000001]]}") + .intersects(g2); + + OGCGeometry g3 = OGCGeometry.fromGeoJson( + "{\"type\": \"Polygon\", \"coordinates\": [[[1.00000001,1.00000001], [4.00000001,1.00000001], [4.00000001,4.00000001], [1.00000001,4.00000001]]]}"); + boolean bb = g2.equals((Object) g3); + assertTrue(bb); + } + } + + @Test + public void testGeometryCollection() throws Exception { + OGCGeometry g = OGCGeometry + .fromText("GEOMETRYCOLLECTION(POLYGON EMPTY, POINT(1 1), LINESTRING EMPTY, MULTIPOLYGON EMPTY, MULTILINESTRING EMPTY)"); + assertTrue(g.geometryType().equals("GeometryCollection")); + OGCConcreteGeometryCollection gc = (OGCConcreteGeometryCollection) g; + assertTrue(gc.numGeometries() == 5); + assertTrue(gc.geometryN(0).geometryType().equals("Polygon")); + assertTrue(gc.geometryN(1).geometryType().equals("Point")); + assertTrue(gc.geometryN(2).geometryType().equals("LineString")); + assertTrue(gc.geometryN(3).geometryType().equals("MultiPolygon")); + assertTrue(gc.geometryN(4).geometryType().equals("MultiLineString")); + + g = OGCGeometry + .fromText("GEOMETRYCOLLECTION(POLYGON EMPTY, POINT(1 1), GEOMETRYCOLLECTION EMPTY, LINESTRING EMPTY, GEOMETRYCOLLECTION(POLYGON EMPTY, POINT(1 1), LINESTRING EMPTY, MULTIPOLYGON EMPTY, MULTILINESTRING EMPTY, MULTIPOINT EMPTY), MULTIPOLYGON EMPTY, MULTILINESTRING EMPTY)"); + assertTrue(g.geometryType().equals("GeometryCollection")); + gc = (OGCConcreteGeometryCollection) g; + assertTrue(gc.numGeometries() == 7); + assertTrue(gc.geometryN(0).geometryType().equals("Polygon")); + assertTrue(gc.geometryN(1).geometryType().equals("Point")); + assertTrue(gc.geometryN(2).geometryType().equals("GeometryCollection")); + assertTrue(gc.geometryN(3).geometryType().equals("LineString")); + assertTrue(gc.geometryN(4).geometryType().equals("GeometryCollection")); + assertTrue(gc.geometryN(5).geometryType().equals("MultiPolygon")); + assertTrue(gc.geometryN(6).geometryType().equals("MultiLineString")); + + OGCConcreteGeometryCollection gc2 = (OGCConcreteGeometryCollection) gc + .geometryN(4); + assertTrue(gc2.numGeometries() == 6); + assertTrue(gc2.geometryN(0).geometryType().equals("Polygon")); + assertTrue(gc2.geometryN(1).geometryType().equals("Point")); + assertTrue(gc2.geometryN(2).geometryType().equals("LineString")); + assertTrue(gc2.geometryN(3).geometryType().equals("MultiPolygon")); + assertTrue(gc2.geometryN(4).geometryType().equals("MultiLineString")); + assertTrue(gc2.geometryN(5).geometryType().equals("MultiPoint")); + + ByteBuffer wkbBuffer = g.asBinary(); + g = OGCGeometry.fromBinary(wkbBuffer); + + assertTrue(g.geometryType().equals("GeometryCollection")); + gc = (OGCConcreteGeometryCollection) g; + assertTrue(gc.numGeometries() == 7); + assertTrue(gc.geometryN(0).geometryType().equals("Polygon")); + assertTrue(gc.geometryN(1).geometryType().equals("Point")); + assertTrue(gc.geometryN(2).geometryType().equals("GeometryCollection")); + assertTrue(gc.geometryN(3).geometryType().equals("LineString")); + assertTrue(gc.geometryN(4).geometryType().equals("GeometryCollection")); + assertTrue(gc.geometryN(5).geometryType().equals("MultiPolygon")); + assertTrue(gc.geometryN(6).geometryType().equals("MultiLineString")); + + gc2 = (OGCConcreteGeometryCollection) gc.geometryN(4); + assertTrue(gc2.numGeometries() == 6); + assertTrue(gc2.geometryN(0).geometryType().equals("Polygon")); + assertTrue(gc2.geometryN(1).geometryType().equals("Point")); + assertTrue(gc2.geometryN(2).geometryType().equals("LineString")); + assertTrue(gc2.geometryN(3).geometryType().equals("MultiPolygon")); + assertTrue(gc2.geometryN(4).geometryType().equals("MultiLineString")); + assertTrue(gc2.geometryN(5).geometryType().equals("MultiPoint")); + + String wktString = g.asText(); + assertTrue(wktString + .equals("GEOMETRYCOLLECTION (POLYGON EMPTY, POINT (1 1), GEOMETRYCOLLECTION EMPTY, LINESTRING EMPTY, GEOMETRYCOLLECTION (POLYGON EMPTY, POINT (1 1), LINESTRING EMPTY, MULTIPOLYGON EMPTY, MULTILINESTRING EMPTY, MULTIPOINT EMPTY), MULTIPOLYGON EMPTY, MULTILINESTRING EMPTY)")); + + g = OGCGeometry + .fromGeoJson("{\"type\" : \"GeometryCollection\", \"geometries\" : [{\"type\" : \"Polygon\", \"coordinates\" : []}, {\"type\" : \"Point\", \"coordinates\" : [1, 1]}, {\"type\" : \"GeometryCollection\", \"geometries\" : []}, {\"type\" : \"LineString\", \"coordinates\" : []}, {\"type\" : \"GeometryCollection\", \"geometries\" : [{\"type\": \"Polygon\", \"coordinates\" : []}, {\"type\" : \"Point\", \"coordinates\" : [1,1]}, {\"type\" : \"LineString\", \"coordinates\" : []}, {\"type\" : \"MultiPolygon\", \"coordinates\" : []}, {\"type\" : \"MultiLineString\", \"coordinates\" : []}, {\"type\" : \"MultiPoint\", \"coordinates\" : []}]}, {\"type\" : \"MultiPolygon\", \"coordinates\" : []}, {\"type\" : \"MultiLineString\", \"coordinates\" : []} ] }"); + + wktString = g.asText(); + assertTrue(wktString + .equals("GEOMETRYCOLLECTION (POLYGON EMPTY, POINT (1 1), GEOMETRYCOLLECTION EMPTY, LINESTRING EMPTY, GEOMETRYCOLLECTION (POLYGON EMPTY, POINT (1 1), LINESTRING EMPTY, MULTIPOLYGON EMPTY, MULTILINESTRING EMPTY, MULTIPOINT EMPTY), MULTIPOLYGON EMPTY, MULTILINESTRING EMPTY)")); + + assertTrue(g.equals((Object) OGCGeometry.fromText(wktString))); + + assertTrue(g.hashCode() == OGCGeometry.fromText(wktString).hashCode()); + + } + + @Test + public void testFirstPointOfPolygon() { + OGCGeometry g = OGCGeometry + .fromText("POLYGON((-10 -10, 10 -10, 10 10, -10 10, -10 -10), (-5 -5, -5 5, 5 5, 5 -5, -5 -5))"); + assertTrue(g.geometryType().equals("Polygon")); + OGCPolygon p = (OGCPolygon) g; + assertTrue(p.numInteriorRing() == 1); + OGCLineString ls = p.exteriorRing(); + OGCPoint p1 = ls.pointN(1); + assertTrue(ls.pointN(1).equals(OGCGeometry.fromText("POINT(10 -10)"))); + OGCPoint p2 = ls.pointN(3); + assertTrue(ls.pointN(3).equals(OGCGeometry.fromText("POINT(-10 10)"))); + OGCPoint p0 = ls.pointN(0); + assertTrue(ls.pointN(0).equals(OGCGeometry.fromText("POINT(-10 -10)"))); + String ms = g.convertToMulti().asText(); + assertTrue(ms.equals("MULTIPOLYGON (((-10 -10, 10 -10, 10 10, -10 10, -10 -10), (-5 -5, -5 5, 5 5, 5 -5, -5 -5)))")); + + } + + @Test + public void testFirstPointOfLineString() { + OGCGeometry g = OGCGeometry + .fromText("LINESTRING(-10 -10, 10 -10, 10 10, -10 10, -10 -10)"); + assertTrue(g.geometryType().equals("LineString")); + OGCLineString p = (OGCLineString) g; + assertTrue(p.numPoints() == 5); + assertTrue(p.isClosed()); + assertTrue(p.pointN(1).equals(OGCGeometry.fromText("POINT(10 -10)"))); + String ms = g.convertToMulti().asText(); + assertTrue(ms.equals("MULTILINESTRING ((-10 -10, 10 -10, 10 10, -10 10, -10 -10))")); + } + + @Test + public void testPointInPolygon() { + OGCGeometry g = OGCGeometry + .fromText("POLYGON((-10 -10, 10 -10, 10 10, -10 10, -10 -10), (-5 -5, -5 5, 5 5, 5 -5, -5 -5))"); + assertTrue(g.geometryType().equals("Polygon")); + assertTrue(!g.contains(OGCGeometry.fromText("POINT(0 0)"))); + assertTrue(g.contains(OGCGeometry.fromText("POINT(9 9)"))); + assertTrue(!g.contains(OGCGeometry.fromText("POINT(-20 1)"))); + assertTrue(g.disjoint(OGCGeometry.fromText("POINT(0 0)"))); + assertTrue(!g.disjoint(OGCGeometry.fromText("POINT(9 9)"))); + assertTrue(g.disjoint(OGCGeometry.fromText("POINT(-20 1)"))); + } + + @Test + public void testMultiPolygon() { + { + OGCGeometry g = OGCGeometry + .fromText("MULTIPOLYGON(((-10 -10, 10 -10, 10 10, -10 10, -10 -10), (-5 -5, -5 5, 5 5, 5 -5, -5 -5)))"); + assertTrue(g.geometryType().equals("MultiPolygon")); // the type is + // reduced + assertTrue(!g.contains(OGCGeometry.fromText("POINT(0 0)"))); + assertTrue(g.contains(OGCGeometry.fromText("POINT(9 9)"))); + assertTrue(!g.contains(OGCGeometry.fromText("POINT(-20 1)"))); + assertTrue(g.disjoint(OGCGeometry.fromText("POINT(0 0)"))); + assertTrue(!g.disjoint(OGCGeometry.fromText("POINT(9 9)"))); + assertTrue(g.disjoint(OGCGeometry.fromText("POINT(-20 1)"))); + assertTrue(g.convertToMulti() == g); + } + + { + OGCGeometry g = OGCGeometry + .fromText("MULTIPOLYGON(((-10 -10, 10 -10, 10 10, -10 10, -10 -10), (-5 -5, -5 5, 5 5, 5 -5, -5 -5)), ((90 90, 110 90, 110 110, 90 110, 90 90), (95 95, 95 105, 105 105, 105 95, 95 95)))"); + assertTrue(g.geometryType().equals("MultiPolygon")); // the type is + + OGCMultiPolygon mp = (OGCMultiPolygon) g; + assertTrue(mp.numGeometries() == 2); + OGCGeometry p1 = mp.geometryN(0); + assertTrue(p1.geometryType().equals("Polygon")); // the type is + assertTrue(p1.contains(OGCGeometry.fromText("POINT(9 9)"))); + assertTrue(!p1.contains(OGCGeometry.fromText("POINT(109 109)"))); + OGCGeometry p2 = mp.geometryN(1); + assertTrue(p2.geometryType().equals("Polygon")); // the type is + assertTrue(!p2.contains(OGCGeometry.fromText("POINT(9 9)"))); + assertTrue(p2.contains(OGCGeometry.fromText("POINT(109 109)"))); + } + } + + @Test + public void testMultiPolygonUnion() { + OGCGeometry g = OGCGeometry + .fromText("POLYGON((-10 -10, 10 -10, 10 10, -10 10, -10 -10), (-5 -5, -5 5, 5 5, 5 -5, -5 -5))"); + OGCGeometry g2 = OGCGeometry + .fromText("POLYGON((90 90, 110 90, 110 110, 90 110, 90 90))"); + OGCGeometry u = g.union(g2); + assertTrue(u.geometryType().equals("MultiPolygon")); + assertTrue(!u.contains(OGCGeometry.fromText("POINT(0 0)"))); + assertTrue(u.contains(OGCGeometry.fromText("POINT(9 9)"))); + assertTrue(!u.contains(OGCGeometry.fromText("POINT(-20 1)"))); + assertTrue(u.disjoint(OGCGeometry.fromText("POINT(0 0)"))); + assertTrue(!u.disjoint(OGCGeometry.fromText("POINT(9 9)"))); + assertTrue(u.disjoint(OGCGeometry.fromText("POINT(-20 1)"))); + assertTrue(u.contains(OGCGeometry.fromText("POINT(100 100)"))); + } + + @Test + public void testIntersection() { + OGCGeometry g = OGCGeometry.fromText("LINESTRING(0 0, 10 10)"); + OGCGeometry g2 = OGCGeometry.fromText("LINESTRING(10 0, 0 10)"); + OGCGeometry u = g.intersection(g2); + assertTrue(u.dimension() == 0); + String s = u.asText(); + assertTrue(u.equals(OGCGeometry.fromText("POINT(5 5)"))); + } + + @Test + public void testPointSymDif() { + OGCGeometry g1 = OGCGeometry.fromText("POINT(1 2)"); + OGCGeometry g2 = OGCGeometry.fromText("POINT(3 4)"); + OGCGeometry gg = g1.symDifference(g2); + assertTrue(gg.equals(OGCGeometry.fromText("MULTIPOINT(1 2, 3 4)"))); + + OGCGeometry g3 = OGCGeometry.fromText("POINT(1 2)"); + OGCGeometry gg1 = g1.symDifference(g3); + assertTrue(gg1 == null || gg1.isEmpty()); + + } + + @Test + public void testNullSr() { + String wkt = "point (0 0)"; + OGCGeometry g = OGCGeometry.fromText(wkt); + g.setSpatialReference(null); + assertTrue(g.SRID() < 1); + } + + @Test + public void testIsectPoint() { + String wkt = "point (0 0)"; + String wk2 = "point (0 0)"; + OGCGeometry g0 = OGCGeometry.fromText(wkt); + OGCGeometry g1 = OGCGeometry.fromText(wk2); + g0.setSpatialReference(null); + g1.setSpatialReference(null); + try { + OGCGeometry rslt = g0.intersection(g1); // ArrayIndexOutOfBoundsException + assertTrue(rslt != null); + } catch (Exception e) { + assertTrue(false); + } + } + + @Test + public void testIsectDisjoint() { + String wk3 = "linestring (0 0, 1 1)"; + String wk4 = "linestring (2 2, 4 4)"; + OGCGeometry g0 = OGCGeometry.fromText(wk3); + OGCGeometry g1 = OGCGeometry.fromText(wk4); + g0.setSpatialReference(null); + g1.setSpatialReference(null); + try { + OGCGeometry rslt = g0.intersection(g1); // null + assertTrue(rslt != null); + } catch (Exception e) { + assertTrue(false); + } + } + + @Test + public void test_polygon_is_simple_for_OGC() { + try { + { + String s = "{\"rings\":[[[0, 0], [0, 10], [10, 10], [10, 0], [0, 0]]]}"; + OGCGeometry g = OGCGeometry.fromJson(s); + boolean res = g.isSimple(); + assertTrue(res); + assertTrue(g.isSimpleRelaxed()); + } + + {// exterior ring is self-tangent + String s = "{\"rings\":[[[0, 0], [0, 10], [5, 5], [10, 10], [10, 0], [5, 5], [0, 0]]]}"; + OGCGeometry g = OGCGeometry.fromJson(s); + boolean res = g.isSimple(); + assertTrue(!res); + assertTrue(g.isSimpleRelaxed()); + } + + {// ring orientation (hole is cw) + String s = "{\"rings\":[[[-100, -100], [-100, 100], [100, 100], [100, -100], [-100, -100]], [[0, 0], [5, 5], [10, 0], [0, 0]]]}"; + OGCGeometry g = OGCGeometry.fromJson(s); + boolean res = g.isSimple(); + assertTrue(!res); + assertTrue(!g.isSimpleRelaxed()); + } + { + String s = "{\"rings\":[[[-100, -100], [-100, 100], [100, 100], [100, -100], [-100, -100]], [[0, 0], [10, 0], [5, 5], [0, 0]]]}"; + OGCGeometry g = OGCGeometry.fromJson(s); + boolean res = g.isSimple(); + assertTrue(res); + assertTrue(g.isSimpleRelaxed()); + } + + {// ring order + String s = "{\"rings\":[[[0, 0], [10, 0], [5, 5], [0, 0]], [[-100, -100], [-100, 100], [100, 100], [100, -100], [-100, -100]]]}"; + OGCGeometry g = OGCGeometry.fromJson(s); + boolean res = g.isSimple(); + assertTrue(!res); + assertTrue(g.isSimpleRelaxed()); + } + + { + // hole is self tangent + String s = "{\"rings\":[[[-100, -100], [-100, 100], [100, 100], [100, -100], [-100, -100]], [[0, 0], [5, 5], [10, 0], [10, 10], [5, 5], [0, 10], [0, 0]]]}"; + OGCGeometry g = OGCGeometry.fromJson(s); + boolean res = g.isSimple(); + assertTrue(!res); + assertTrue(g.isSimpleRelaxed()); + } + { + // two holes touch + String s = "{\"rings\":[[[-100, -100], [-100, 100], [100, 100], [100, -100], [-100, -100]], [[0, 0], [10, 0], [5, 5], [0, 0]], [[10, 10], [0, 10], [5, 5], [10, 10]]]}"; + OGCGeometry g = OGCGeometry.fromJson(s); + boolean res = g.isSimple(); + assertTrue(res); + assertTrue(g.isSimpleRelaxed()); + } + { + // two holes touch, bad orientation + String s = "{\"rings\":[[[-100, -100], [-100, 100], [100, 100], [100, -100], [-100, -100]], [[0, 0], [5, 5], [10, 0], [0, 0]], [[10, 10], [0, 10], [5, 5], [10, 10]]]}"; + OGCGeometry g = OGCGeometry.fromJson(s); + boolean res = g.isSimple(); + assertTrue(!res); + assertTrue(!g.isSimpleRelaxed()); + + } + + { + // hole touches exterior in two spots + String s = "{\"rings\":[[[-100, -100], [-100, 100], [0, 100], [100, 100], [100, -100], [0, -100], [-100, -100]], [[0, -100], [10, 0], [0, 100], [-10, 0], [0, -100]]]}"; + OGCGeometry g = OGCGeometry.fromJson(s); + boolean res = g.isSimple(); + assertTrue(!res); + assertTrue(g.isSimpleRelaxed()); + } + + { + // hole touches exterior in one spot + String s = "{\"rings\":[[[-100, -100], [-100, 100], [0, 100], [100, 100], [100, -100], [0, -100], [-100, -100]], [[0, -100], [10, 0], [0, 90], [-10, 0], [0, -100]]]}"; + OGCGeometry g = OGCGeometry.fromJson(s); + boolean res = g.isSimple(); + assertTrue(res); + assertTrue(g.isSimpleRelaxed()); + + } + + { + // exterior has inversion (planar simple) + String s = "{\"rings\":[[[-100, -100], [-100, 100], [0, 100], [100, 100], [100, -100], [0, -100], [10, 0], [0, 90], [-10, 0], [0, -100], [-100, -100]]]}"; + OGCGeometry g = OGCGeometry.fromJson(s); + boolean res = g.isSimple(); + assertTrue(!res); + assertTrue(g.isSimpleRelaxed()); + } + + { + // two holes touch in one spot, and they also touch exterior in + // two spots, producing disconnected interior + String s = "{\"rings\":[[[-100, -100], [-100, 100], [0, 100], [100, 100], [100, -100], [0, -100], [-100, -100]], [[0, -100], [10, -50], [0, 0], [-10, -50], [0, -100]], [[0, 0], [10, 50], [0, 100], [-10, 50], [0, 0]]]}"; + OGCGeometry g = OGCGeometry.fromJson(s); + boolean res = g.isSimple(); + assertTrue(!res); + assertTrue(g.isSimpleRelaxed()); + } + } catch (Exception ex) { + assertTrue(false); + } + } + + @Test + public void test_polygon_simplify_for_OGC() { + try { + { + //degenerate + String s = "{\"rings\":[[[0, 0], [0, 10], [10, 10], [10, 0], [20, 0], [10, 0], [0, 0]]]}"; + OGCGeometry g = OGCGeometry.fromJson(s); + boolean res = g.isSimple(); + assertTrue(!res); + Geometry resg = OperatorSimplifyOGC.local().execute(g.getEsriGeometry(), null, true, null); + OGCGeometry og = OGCGeometry.createFromEsriGeometry(resg, null); + String res_str = og.asText(); + assertTrue(og.isSimple()); + } + { + String s = "{\"rings\":[[[0, 0], [0, 10], [10, 10], [10, 0], [0, 0]]]}"; + OGCGeometry g = OGCGeometry.fromJson(s); + boolean res = g.isSimple(); + assertTrue(res); + assertTrue(g.isSimpleRelaxed()); + Geometry resg = OperatorSimplifyOGC.local().execute(g.getEsriGeometry(), null, true, null); + OGCGeometry og = OGCGeometry.createFromEsriGeometry(resg, null); + String res_str = og.asText(); + assertTrue(og.geometryType().equals("Polygon")); + assertTrue(((OGCPolygon) og).numInteriorRing() == 0); + assertTrue(og.isSimple()); + } + + {// exterior ring is self-tangent + String s = "{\"rings\":[[[0, 0], [0, 10], [5, 5], [10, 10], [10, 0], [5, 5], [0, 0]]]}"; + OGCGeometry g = OGCGeometry.fromJson(s); + boolean res = g.isSimple(); + assertTrue(!res); + assertTrue(g.isSimpleRelaxed()); + Geometry resg = OperatorSimplifyOGC.local().execute(g.getEsriGeometry(), null, true, null); + OGCGeometry og = OGCGeometry.createFromEsriGeometry(resg, null); + res = og.isSimple(); + assertTrue(res); + assertTrue(og.geometryType().equals("MultiPolygon")); + assertTrue(((OGCGeometryCollection) og).numGeometries() == 2); + } + + {// ring orientation (hole is cw) + String s = "{\"rings\":[[[-100, -100], [-100, 100], [100, 100], [100, -100], [-100, -100]], [[0, 0], [5, 5], [10, 0], [0, 0]]]}"; + OGCGeometry g = OGCGeometry.fromJson(s); + boolean res = g.isSimple(); + assertTrue(!res); + assertTrue(!g.isSimpleRelaxed()); + Geometry resg = OperatorSimplifyOGC.local().execute(g.getEsriGeometry(), null, true, null); + OGCGeometry og = OGCGeometry.createFromEsriGeometry(resg, null); + res = og.isSimple(); + assertTrue(res); + assertTrue(og.geometryType().equals("Polygon")); + assertTrue(((OGCPolygon) og).numInteriorRing() == 1); + } + + {// ring order + String s = "{\"rings\":[[[0, 0], [10, 0], [5, 5], [0, 0]], [[-100, -100], [-100, 100], [100, 100], [100, -100], [-100, -100]]]}"; + OGCGeometry g = OGCGeometry.fromJson(s); + boolean res = g.isSimple(); + assertTrue(!res); + assertTrue(g.isSimpleRelaxed()); + Geometry resg = OperatorSimplifyOGC.local().execute(g.getEsriGeometry(), null, true, null); + OGCGeometry og = OGCGeometry.createFromEsriGeometry(resg, null); + res = og.isSimple(); + assertTrue(res); + assertTrue(og.geometryType().equals("Polygon")); + } + + { + // hole is self tangent + String s = "{\"rings\":[[[-100, -100], [-100, 100], [100, 100], [100, -100], [-100, -100]], [[0, 0], [5, 5], [10, 0], [10, 10], [5, 5], [0, 10], [0, 0]]]}"; + OGCGeometry g = OGCGeometry.fromJson(s); + boolean res = g.isSimple(); + assertTrue(!res); + assertTrue(g.isSimpleRelaxed()); + Geometry resg = OperatorSimplifyOGC.local().execute(g.getEsriGeometry(), null, true, null); + OGCGeometry og = OGCGeometry.createFromEsriGeometry(resg, null); + String res_str = og.asText(); + res = og.isSimple(); + assertTrue(res); + assertTrue(og.geometryType().equals("Polygon")); + assertTrue(((OGCPolygon) og).numInteriorRing() == 2); + } + { + // two holes touch + String s = "{\"rings\":[[[-100, -100], [-100, 100], [100, 100], [100, -100], [-100, -100]], [[0, 0], [10, 0], [5, 5], [0, 0]], [[10, 10], [0, 10], [5, 5], [10, 10]]]}"; + OGCGeometry g = OGCGeometry.fromJson(s); + boolean res = g.isSimple(); + assertTrue(res); + assertTrue(g.isSimpleRelaxed()); + Geometry resg = OperatorSimplifyOGC.local().execute(g.getEsriGeometry(), null, true, null); + OGCGeometry og = OGCGeometry.createFromEsriGeometry(resg, null); + assertTrue(og.geometryType().equals("Polygon")); + assertTrue(((OGCPolygon) og).numInteriorRing() == 2); + } + { + // two holes touch, bad orientation + String s = "{\"rings\":[[[-100, -100], [-100, 100], [100, 100], [100, -100], [-100, -100]], [[0, 0], [5, 5], [10, 0], [0, 0]], [[10, 10], [0, 10], [5, 5], [10, 10]]]}"; + OGCGeometry g = OGCGeometry.fromJson(s); + boolean res = g.isSimple(); + assertTrue(!res); + assertTrue(!g.isSimpleRelaxed()); + Geometry resg = OperatorSimplifyOGC.local().execute(g.getEsriGeometry(), null, true, null); + OGCGeometry og = OGCGeometry.createFromEsriGeometry(resg, null); + assertTrue(og.geometryType().equals("Polygon")); + assertTrue(((OGCPolygon) og).numInteriorRing() == 2); + } + + { + // hole touches exterior in two spots + //OperatorSimplifyOGC produces a multipolygon with two polygons without holes. + String s = "{\"rings\":[[[-100, -100], [-100, 100], [0, 100], [100, 100], [100, -100], [0, -100], [-100, -100]], [[0, -100], [10, 0], [0, 100], [-10, 0], [0, -100]]]}"; + OGCGeometry g = OGCGeometry.fromJson(s); + boolean res = g.isSimple(); + assertTrue(!res); + assertTrue(g.isSimpleRelaxed()); + Geometry resg = OperatorSimplifyOGC.local().execute(g.getEsriGeometry(), null, true, null); + OGCGeometry og = OGCGeometry.createFromEsriGeometry(resg, null); + assertTrue(og.geometryType().equals("MultiPolygon")); + assertTrue(((OGCMultiPolygon) og).numGeometries() == 2); + assertTrue(((OGCPolygon) ((OGCMultiPolygon) og).geometryN(0)).numInteriorRing() == 0); + assertTrue(((OGCPolygon) ((OGCMultiPolygon) og).geometryN(1)).numInteriorRing() == 0); + } + + { + // hole touches exterior in one spot + //OperatorSimplifyOGC produces a polygons with a hole. + String s = "{\"rings\":[[[-100, -100], [-100, 100], [0, 100], [100, 100], [100, -100], [0, -100], [-100, -100]], [[0, -100], [10, 0], [0, 90], [-10, 0], [0, -100]]]}"; + OGCGeometry g = OGCGeometry.fromJson(s); + boolean res = g.isSimple(); + assertTrue(res); + assertTrue(g.isSimpleRelaxed()); + Geometry resg = OperatorSimplifyOGC.local().execute(g.getEsriGeometry(), null, true, null); + OGCGeometry og = OGCGeometry.createFromEsriGeometry(resg, null); + assertTrue(og.geometryType().equals("Polygon")); + assertTrue(((OGCPolygon) og).numInteriorRing() == 1); + } + + { + // exterior has inversion (non simple for OGC) + //OperatorSimplifyOGC produces a polygons with a hole. + String s = "{\"rings\":[[[-100, -100], [-100, 100], [0, 100], [100, 100], [100, -100], [0, -100], [10, 0], [0, 90], [-10, 0], [0, -100], [-100, -100]]]}"; + OGCGeometry g = OGCGeometry.fromJson(s); + boolean res = g.isSimple(); + assertTrue(!res); + assertTrue(g.isSimpleRelaxed()); + Geometry resg = OperatorSimplifyOGC.local().execute(g.getEsriGeometry(), null, true, null); + OGCGeometry og = OGCGeometry.createFromEsriGeometry(resg, null); + assertTrue(og.geometryType().equals("Polygon")); + assertTrue(((OGCPolygon) og).numInteriorRing() == 1); + } + + { + // two holes touch in one spot, and they also touch exterior in + // two spots, producing disconnected interior + //OperatorSimplifyOGC produces two polygons with no holes. + String s = "{\"rings\":[[[-100, -100], [-100, 100], [0, 100], [100, 100], [100, -100], [0, -100], [-100, -100]], [[0, -100], [10, -50], [0, 0], [-10, -50], [0, -100]], [[0, 0], [10, 50], [0, 100], [-10, 50], [0, 0]]]}"; + OGCGeometry g = OGCGeometry.fromJson(s); + boolean res = g.isSimple(); + assertTrue(!res); + assertTrue(g.isSimpleRelaxed()); + Geometry resg = OperatorSimplifyOGC.local().execute(g.getEsriGeometry(), null, true, null); + OGCGeometry og = OGCGeometry.createFromEsriGeometry(resg, null); + assertTrue(og.geometryType().equals("MultiPolygon")); + assertTrue(((OGCMultiPolygon) og).numGeometries() == 2); + assertTrue(((OGCPolygon) ((OGCMultiPolygon) og).geometryN(0)).numInteriorRing() == 0); + assertTrue(((OGCPolygon) ((OGCMultiPolygon) og).geometryN(1)).numInteriorRing() == 0); + } + + + { + OGCGeometry g = OGCGeometry.fromJson("{\"rings\":[[[-3,4],[6,4],[6,-3],[-3,-3],[-3,4]],[[0,2],[2,2],[0,0],[4,0],[4,2],[2,0],[2,2],[4,2],[3,3],[2,2],[1,3],[0,2]]], \"spatialReference\":{\"wkid\":4326}}"); + assertTrue(g.geometryType().equals("Polygon")); + boolean res = g.isSimple(); + assertTrue(!res); + assertTrue(!g.isSimpleRelaxed()); + OGCGeometry simpleG = g.makeSimple(); + assertTrue(simpleG.geometryType().equals("MultiPolygon")); + assertTrue(simpleG.isSimple()); + OGCMultiPolygon mp = (OGCMultiPolygon) simpleG; + assertTrue(mp.numGeometries() == 2); + OGCPolygon g1 = (OGCPolygon) mp.geometryN(0); + OGCPolygon g2 = (OGCPolygon) mp.geometryN(1); + assertTrue((g1.numInteriorRing() == 0 && g1.numInteriorRing() == 2) || + (g1.numInteriorRing() == 2 && g2.numInteriorRing() == 0)); + + OGCGeometry oldOutput = OGCGeometry.fromJson("{\"rings\":[[[-3,-3],[-3,4],[6,4],[6,-3],[-3,-3]],[[0,0],[2,0],[4,0],[4,2],[3,3],[2,2],[1,3],[0,2],[2,2],[0,0]],[[2,0],[2,2],[4,2],[2,0]]],\"spatialReference\":{\"wkid\":4326}}"); + assertTrue(oldOutput.isSimpleRelaxed()); + assertFalse(oldOutput.isSimple()); + } + } catch (Exception ex) { + assertTrue(false); + } + } + + @Test + public void test_polyline_is_simple_for_OGC() { + try { + { + String s = "{\"paths\":[[[0, 10], [8, 5], [5, 2], [6, 0]]]}"; + OGCGeometry g = OGCGeometry.fromJson(s); + boolean res = g.isSimple(); + assertTrue(res); + assertTrue(g.isSimpleRelaxed()); + } + { + String s = "{\"paths\":[[[0, 10], [6, 0], [7, 5], [0, 3]]]}";// self + // intersection + OGCGeometry g = OGCGeometry.fromJson(s); + boolean res = g.isSimple(); + assertTrue(!res); + assertTrue(g.isSimpleRelaxed()); + } + + { + String s = "{\"paths\":[[[0, 10], [6, 0], [0, 3], [0, 10]]]}"; // closed + OGCGeometry g = OGCGeometry.fromJson(s); + boolean res = g.isSimple(); + assertTrue(res); + assertTrue(g.isSimpleRelaxed()); + } + + { + String s = "{\"paths\":[[[0, 10], [5, 5], [6, 0], [0, 3], [5, 5], [0, 9], [0, 10]]]}"; // closed + // with + // self + // tangent + OGCGeometry g = OGCGeometry.fromJson(s); + boolean res = g.isSimple(); + assertTrue(!res); + assertTrue(g.isSimpleRelaxed()); + } + + { + String s = "{\"paths\":[[[0, 10], [5, 2]], [[5, 2], [6, 0]]]}";// two + // paths + // connected + // at + // a + // point + OGCGeometry g = OGCGeometry.fromJson(s); + boolean res = g.isSimple(); + assertTrue(res); + assertTrue(g.isSimpleRelaxed()); + } + + { + String s = "{\"paths\":[[[0, 0], [3, 3], [5, 0], [0, 0]], [[0, 10], [3, 3], [10, 10], [0, 10]]]}";// two + // closed + // rings + // touch + // at + // one + // point + OGCGeometry g = OGCGeometry.fromJson(s); + boolean res = g.isSimple(); + assertTrue(!res); + assertTrue(g.isSimpleRelaxed()); + } + + { + String s = "{\"paths\":[[[3, 3], [0, 0], [5, 0], [3, 3]], [[3, 3], [0, 10], [10, 10], [3, 3]]]}"; + // two closed rings touch at one point. The touch happens at the + // endpoints of the paths. + OGCGeometry g = OGCGeometry.fromJson(s); + boolean res = g.isSimple(); + assertTrue(!res); + assertTrue(g.isSimpleRelaxed()); + } + + { + String s = "{\"paths\":[[[0, 0], [10, 10]], [[0, 10], [10, 0]]]}";// two + // lines + // intersect + OGCGeometry g = OGCGeometry.fromJson(s); + boolean res = g.isSimple(); + assertTrue(!res); + assertTrue(g.isSimpleRelaxed()); + } + + { + String s = "{\"paths\":[[[0, 0], [5, 5], [0, 10]], [[10, 10], [5, 5], [10, 0]]]}";// two + // paths + // share + // mid + // point. + OGCGeometry g = OGCGeometry.fromJson(s); + boolean res = g.isSimple(); + assertTrue(!res); + assertTrue(g.isSimpleRelaxed()); + } + } catch (Exception ex) { + assertTrue(false); + } + + } + + @Test + public void test_multipoint_is_simple_for_OGC() { + try { + + SpatialReference sr = SpatialReference.create(4326); + { + String s = "{\"points\":[[0, 0], [5, 5], [0, 10]]}"; + OGCGeometry g = OGCGeometry.fromJson(s); + boolean res = g.isSimple(); + assertTrue(res); + assertTrue(g.isSimpleRelaxed()); + } + { + String s = "{\"points\":[[0, 0], [5, 5], [0, 0], [0, 10]]}"; + OGCGeometry g = OGCGeometry.fromJson(s); + boolean res = g.isSimple(); + assertTrue(!res); + assertTrue(!g.isSimpleRelaxed()); + } + { + String s = "{\"points\":[[0, 0], [5, 5], [1e-10, -1e-10], [0, 10]]}"; + OGCGeometry g = OGCGeometry.fromJson(s); + g.setSpatialReference(sr); + boolean res = g.isSimple(); + assertTrue(!res); + assertTrue(g.isSimpleRelaxed()); + } + } catch (Exception ex) { + assertTrue(false); + } + + } + + @Test + public void testGeometryCollectionBuffer() { + OGCGeometry g = OGCGeometry + .fromText("GEOMETRYCOLLECTION(POINT(1 1), POINT(1 1), POINT(1 2), LINESTRING (0 0, 1 1, 1 0, 0 1), MULTIPOLYGON EMPTY, MULTILINESTRING EMPTY)"); + OGCGeometry simpleG = g.buffer(0); + String t = simpleG.geometryType(); + String rt = simpleG.asText(); + assertTrue(simpleG.geometryType().equals("GeometryCollection")); + } + + @Test + public void testIsectTria1() { + String wkt = "polygon((1 0, 3 0, 1 2, 1 0))"; + String wk2 = "polygon((0 1, 2 1, 0 3, 0 1))"; + OGCGeometry g0 = OGCGeometry.fromText(wkt); + OGCGeometry g1 = OGCGeometry.fromText(wk2); + g0.setSpatialReference(SpatialReference.create(4326)); + g1.setSpatialReference(SpatialReference.create(4326)); + OGCGeometry rslt = g0.intersection(g1); + assertTrue(rslt != null); + assertTrue(rslt.geometryType().equals("Polygon")); + assertTrue(rslt.esriSR.getID() == 4326); + String s = rslt.asText(); + } + + @Test + public void testIsectTriaJson1() throws Exception { + String json1 = "{\"rings\":[[[1, 0], [3, 0], [1, 2], [1, 0]]], \"spatialReference\":{\"wkid\":4326}}"; + String json2 = "{\"rings\":[[[0, 1], [2, 1], [0, 3], [0, 1]]], \"spatialReference\":{\"wkid\":4326}}"; + OGCGeometry g0 = OGCGeometry.fromJson(json1); + OGCGeometry g1 = OGCGeometry.fromJson(json2); + OGCGeometry rslt = g0.intersection(g1); + assertTrue(rslt != null); + assertTrue(rslt.geometryType().equals("Polygon")); + assertTrue(rslt.esriSR.getID() == 4326); + String s = GeometryEngine.geometryToJson(rslt.getEsriSpatialReference().getID(), rslt.getEsriGeometry()); + } + + @Test + public void testIsectTria2() { + String wkt = "polygon((1 0, 3 0, 1 2, 1 0))"; + String wk2 = "polygon((0 3, 2 1, 3 1, 0 3))"; + OGCGeometry g0 = OGCGeometry.fromText(wkt); + OGCGeometry g1 = OGCGeometry.fromText(wk2); + g0.setSpatialReference(null); + g1.setSpatialReference(null); + OGCGeometry rslt = g0.intersection(g1); + assertTrue(rslt != null); + assertTrue(rslt.dimension() == 1); + assertTrue(rslt.geometryType().equals("LineString")); + String s = rslt.asText(); + } + + @Test + public void testIsectTria3() { + String wkt = "polygon((1 0, 3 0, 1 2, 1 0))"; + String wk2 = "polygon((2 2, 2 1, 3 1, 2 2))"; + OGCGeometry g0 = OGCGeometry.fromText(wkt); + OGCGeometry g1 = OGCGeometry.fromText(wk2); + g0.setSpatialReference(SpatialReference.create(4326)); + g1.setSpatialReference(SpatialReference.create(4326)); + OGCGeometry rslt = g0.intersection(g1); + assertTrue(rslt != null); + assertTrue(rslt.dimension() == 0); + assertTrue(rslt.geometryType().equals("Point")); + assertTrue(rslt.esriSR.getID() == 4326); + String s = rslt.asText(); + } + + @Test + public void testMultiPointSinglePoint() { + String wkt = "multipoint((1 0))"; + OGCGeometry g0 = OGCGeometry.fromText(wkt); + assertTrue(g0.dimension() == 0); + String gt = g0.geometryType(); + assertTrue(gt.equals("MultiPoint")); + OGCMultiPoint mp = (OGCMultiPoint) g0; + assertTrue(mp.numGeometries() == 1); + OGCGeometry p = mp.geometryN(0); + String s = p.asText(); + assertTrue(s.equals("POINT (1 0)")); + + String ms = p.convertToMulti().asText(); + assertTrue(ms.equals("MULTIPOINT ((1 0))")); + + } + + @Test + public void testWktMultiPolygon() { + String restJson = "{\"rings\": [[[-100, -100], [-100, 100], [100, 100], [100, -100], [-100, -100]], [[-90, -90], [90, 90], [-90, 90], [90, -90], [-90, -90]], [[-10, -10], [-10, 10], [10, 10], [10, -10], [-10, -10]]]}"; + MapGeometry g = null; + g = OperatorImportFromJson.local().execute(Geometry.Type.Unknown, restJson); + String wkt = OperatorExportToWkt.local().execute(0, g.getGeometry(), null); + assertTrue(wkt.equals("MULTIPOLYGON (((-100 -100, 100 -100, 100 100, -100 100, -100 -100), (-90 -90, 90 -90, -90 90, 90 90, -90 -90)), ((-10 -10, 10 -10, 10 10, -10 10, -10 -10)))")); + } + + @Test + public void testMultiPolygonArea() { + //MultiPolygon Area #36 + String wkt = "MULTIPOLYGON (((1001200 2432900, 1001420 2432691, 1001250 2432388, 1001498 2432325, 1001100 2432100, 1001500 2431900, 1002044 2431764, 1002059 2432120, 1002182 2432003, 1002400 2432300, 1002650 2432150, 1002610 2432323, 1002772 2432434, 1002410 2432821, 1002700 2433000, 1001824 2432866, 1001600 2433150, 1001200 2432900)), ((1000393 2433983, 1000914 2434018, 1000933 2433817, 1000568 2433834, 1000580 2433584, 1000700 2433750, 1000800 2433650, 1000700 2433450, 1000600 2433550, 1000200 2433350, 1000100 2433900, 1000393 2433983)), ((1001200 2432900, 1000878 2432891, 1000900 2433300, 1001659 2433509, 1001600 2433150, 1001200 2432900)), ((1002450 2431650, 1002300 2431650, 1002300 2431900, 1002500 2432100, 1002600 2431800, 1002450 2431800, 1002450 2431650)), ((999750 2433550, 999850 2433600, 999900 2433350, 999780 2433433, 999750 2433550)), ((1002950 2432050, 1003005 2431932, 1002850 2432250, 1002928 2432210, 1002950 2432050)), ((1002600 2431750, 1002642 2431882, 1002750 2431900, 1002750 2431750, 1002600 2431750)), ((1002950 2431750, 1003050 2431650, 1002968 2431609, 1002950 2431750)))"; + { + OGCGeometry ogcg = OGCGeometry.fromText(wkt); + assertTrue(ogcg.geometryType().equals("MultiPolygon")); + OGCMultiPolygon mp = (OGCMultiPolygon) ogcg; + double a = mp.area(); + assertTrue(Math.abs(mp.area() - 2037634.5) < a * 1e-14); + } + + { + OGCGeometry ogcg = OGCGeometry.fromText(wkt); + assertTrue(ogcg.geometryType().equals("MultiPolygon")); + Geometry g = ogcg.getEsriGeometry(); + double a = g.calculateArea2D(); + assertTrue(Math.abs(a - 2037634.5) < a * 1e-14); + } + } + + @Test + public void testPolylineSimplifyIssueGithub52() throws Exception { + String json = "{\"paths\":[[[2,0],[4,3],[5,1],[3.25,1.875],[1,3]]],\"spatialReference\":{\"wkid\":4326}}"; + { + OGCGeometry g = OGCGeometry.fromJson(json); + assertTrue(g.geometryType().equals("LineString")); + OGCGeometry simpleG = g.makeSimple();//make ogc simple + assertTrue(simpleG.geometryType().equals("MultiLineString")); + assertTrue(simpleG.isSimpleRelaxed());//geodatabase simple + assertTrue(simpleG.isSimple());//ogc simple + OGCMultiLineString mls = (OGCMultiLineString) simpleG; + assertTrue(mls.numGeometries() == 4); + OGCGeometry baseGeom = OGCGeometry.fromJson("{\"paths\":[[[2,0],[3.25,1.875]],[[3.25,1.875],[4,3],[5,1]],[[5,1],[3.25,1.875]],[[3.25,1.875],[1,3]]],\"spatialReference\":{\"wkid\":4326}}"); + assertTrue(simpleG.equals(baseGeom)); + + } + } } diff --git a/src/test/java/com/esri/core/geometry/TestOffset.java b/src/test/java/com/esri/core/geometry/TestOffset.java index 51fc59be..385e3504 100644 --- a/src/test/java/com/esri/core/geometry/TestOffset.java +++ b/src/test/java/com/esri/core/geometry/TestOffset.java @@ -29,148 +29,148 @@ import org.junit.Test; public class TestOffset extends TestCase { - @Override - protected void setUp() throws Exception { - super.setUp(); - } - - @Override - protected void tearDown() throws Exception { - super.tearDown(); - } - - @Test - public void testOffsetPoint() { - try { - Point point = new Point(); - point.setXY(0, 0); - - OperatorOffset offset = (OperatorOffset) OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.Offset); - - Geometry outputGeom = offset.execute(point, null, 2, - JoinType.Round, 2, 0, null); - - assertNull(outputGeom); - } catch (Exception ex) { - } - - try { - MultiPoint mp = new MultiPoint(); - mp.add(0, 0); - mp.add(10, 10); - - OperatorOffset offset = (OperatorOffset) OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.Offset); - - Geometry outputGeom = offset.execute(mp, null, 2, JoinType.Round, - 2, 0, null); - - assertNull(outputGeom); - } catch (Exception ex) { - } - } - - @Test - public void testOffsetPolyline() { - for (long i = -5; i <= 5; i++) { - try { - OffsetPolyline_(i, JoinType.Round); - } catch (Exception ex) { - fail("OffsetPolyline(Round) failure"); - } - - try { - OffsetPolyline_(i, JoinType.Miter); - } catch (Exception ex) { - fail("OffsetPolyline(Miter) failure"); - } - - try { - OffsetPolyline_(i, JoinType.Bevel); - } catch (Exception ex) { - fail("OffsetPolyline(Bevel) failure"); - } - - try { - OffsetPolyline_(i, JoinType.Square); - } catch (Exception ex) { - fail("OffsetPolyline(Square) failure"); - } - } - } - - public void OffsetPolyline_(double distance, JoinType joins) { - Polyline polyline = new Polyline(); - polyline.startPath(0, 0); - polyline.lineTo(6, 0); - polyline.lineTo(6, 1); - polyline.lineTo(4, 1); - polyline.lineTo(4, 2); - polyline.lineTo(10, 2); - - OperatorOffset offset = (OperatorOffset) OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.Offset); - - Geometry outputGeom = offset.execute(polyline, null, distance, joins, - 2, 0, null); - - assertNotNull(outputGeom); - } - - @Test - public void testOffsetPolygon() { - for (long i = -5; i <= 5; i++) { - try { - OffsetPolygon_(i, JoinType.Round); - } catch (Exception ex) { - fail("OffsetPolyline(Round) failure"); - } - - try { - OffsetPolygon_(i, JoinType.Miter); - } catch (Exception ex) { - fail("OffsetPolyline(Miter) failure"); - } - - try { - OffsetPolygon_(i, JoinType.Bevel); - } catch (Exception ex) { - fail("OffsetPolyline(Bevel) failure"); - } - - try { - OffsetPolygon_(i, JoinType.Square); - } catch (Exception ex) { - fail("OffsetPolyline(Square) failure"); - } - } - } - - public void OffsetPolygon_(double distance, JoinType joins) { - Polygon polygon = new Polygon(); - polygon.startPath(0, 0); - polygon.lineTo(0, 16); - polygon.lineTo(16, 16); - polygon.lineTo(16, 11); - polygon.lineTo(10, 10); - polygon.lineTo(10, 12); - polygon.lineTo(3, 12); - polygon.lineTo(3, 4); - polygon.lineTo(10, 4); - polygon.lineTo(10, 6); - polygon.lineTo(16, 5); - polygon.lineTo(16, 0); - - OperatorOffset offset = (OperatorOffset) OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.Offset); - - Geometry outputGeom = offset.execute(polygon, null, distance, joins, 2, - 0, null); - - assertNotNull(outputGeom); - if (distance > 2) { - assertTrue(outputGeom.isEmpty()); - } - } + @Override + protected void setUp() throws Exception { + super.setUp(); + } + + @Override + protected void tearDown() throws Exception { + super.tearDown(); + } + + @Test + public void testOffsetPoint() { + try { + Point point = new Point(); + point.setXY(0, 0); + + OperatorOffset offset = (OperatorOffset) OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Offset); + + Geometry outputGeom = offset.execute(point, null, 2, + JoinType.Round, 2, 0, null); + + assertNull(outputGeom); + } catch (Exception ex) { + } + + try { + MultiPoint mp = new MultiPoint(); + mp.add(0, 0); + mp.add(10, 10); + + OperatorOffset offset = (OperatorOffset) OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Offset); + + Geometry outputGeom = offset.execute(mp, null, 2, JoinType.Round, + 2, 0, null); + + assertNull(outputGeom); + } catch (Exception ex) { + } + } + + @Test + public void testOffsetPolyline() { + for (long i = -5; i <= 5; i++) { + try { + OffsetPolyline_(i, JoinType.Round); + } catch (Exception ex) { + fail("OffsetPolyline(Round) failure"); + } + + try { + OffsetPolyline_(i, JoinType.Miter); + } catch (Exception ex) { + fail("OffsetPolyline(Miter) failure"); + } + + try { + OffsetPolyline_(i, JoinType.Bevel); + } catch (Exception ex) { + fail("OffsetPolyline(Bevel) failure"); + } + + try { + OffsetPolyline_(i, JoinType.Square); + } catch (Exception ex) { + fail("OffsetPolyline(Square) failure"); + } + } + } + + public void OffsetPolyline_(double distance, JoinType joins) { + Polyline polyline = new Polyline(); + polyline.startPath(0, 0); + polyline.lineTo(6, 0); + polyline.lineTo(6, 1); + polyline.lineTo(4, 1); + polyline.lineTo(4, 2); + polyline.lineTo(10, 2); + + OperatorOffset offset = (OperatorOffset) OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Offset); + + Geometry outputGeom = offset.execute(polyline, null, distance, joins, + 2, 0, null); + + assertNotNull(outputGeom); + } + + @Test + public void testOffsetPolygon() { + for (long i = -5; i <= 5; i++) { + try { + OffsetPolygon_(i, JoinType.Round); + } catch (Exception ex) { + fail("OffsetPolyline(Round) failure"); + } + + try { + OffsetPolygon_(i, JoinType.Miter); + } catch (Exception ex) { + fail("OffsetPolyline(Miter) failure"); + } + + try { + OffsetPolygon_(i, JoinType.Bevel); + } catch (Exception ex) { + fail("OffsetPolyline(Bevel) failure"); + } + + try { + OffsetPolygon_(i, JoinType.Square); + } catch (Exception ex) { + fail("OffsetPolyline(Square) failure"); + } + } + } + + public void OffsetPolygon_(double distance, JoinType joins) { + Polygon polygon = new Polygon(); + polygon.startPath(0, 0); + polygon.lineTo(0, 16); + polygon.lineTo(16, 16); + polygon.lineTo(16, 11); + polygon.lineTo(10, 10); + polygon.lineTo(10, 12); + polygon.lineTo(3, 12); + polygon.lineTo(3, 4); + polygon.lineTo(10, 4); + polygon.lineTo(10, 6); + polygon.lineTo(16, 5); + polygon.lineTo(16, 0); + + OperatorOffset offset = (OperatorOffset) OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Offset); + + Geometry outputGeom = offset.execute(polygon, null, distance, joins, 2, + 0, null); + + assertNotNull(outputGeom); + if (distance > 2) { + assertTrue(outputGeom.isEmpty()); + } + } } diff --git a/src/test/java/com/esri/core/geometry/TestPoint.java b/src/test/java/com/esri/core/geometry/TestPoint.java index 2b191d72..7bfad411 100644 --- a/src/test/java/com/esri/core/geometry/TestPoint.java +++ b/src/test/java/com/esri/core/geometry/TestPoint.java @@ -30,214 +30,214 @@ import java.util.Random; public class TestPoint extends TestCase { - @Override - protected void setUp() throws Exception { - super.setUp(); - } - - @Override - protected void tearDown() throws Exception { - super.tearDown(); - } - - @Test - public void testPt() { - Point pt = new Point(); - assertTrue(pt.isEmpty()); - pt.setXY(10, 2); - assertFalse(pt.isEmpty()); - - pt.toString(); - } - - @Test - public void testEnvelope2000() { - Point points[] = new Point[2000]; - Random random = new Random(69); - for (int i = 0; i < 2000; i++) { - points[i] = new Point(); - points[i].setX(random.nextDouble() * 100); - points[i].setY(random.nextDouble() * 100); - } - for (int iter = 0; iter < 2; iter++) { - final long startTime = System.nanoTime(); - Envelope geomExtent = new Envelope(); - Envelope fullExtent = new Envelope(); - for (int i = 0; i < 2000; i++) { - points[i].queryEnvelope(geomExtent); - fullExtent.merge(geomExtent); - } - long endTime = System.nanoTime(); - } - } - - @Test - public void testBasic() { - assertTrue(Geometry.getDimensionFromType(Geometry.Type.Polygon.value()) == 2); - assertTrue(Geometry - .getDimensionFromType(Geometry.Type.Polyline.value()) == 1); - assertTrue(Geometry - .getDimensionFromType(Geometry.Type.Envelope.value()) == 2); - assertTrue(Geometry.getDimensionFromType(Geometry.Type.Line.value()) == 1); - assertTrue(Geometry.getDimensionFromType(Geometry.Type.Point.value()) == 0); - assertTrue(Geometry.getDimensionFromType(Geometry.Type.MultiPoint - .value()) == 0); - - assertTrue(Geometry.isLinear(Geometry.Type.Polygon.value())); - assertTrue(Geometry.isLinear(Geometry.Type.Polyline.value())); - assertTrue(Geometry.isLinear(Geometry.Type.Envelope.value())); - assertTrue(Geometry.isLinear(Geometry.Type.Line.value())); - assertTrue(!Geometry.isLinear(Geometry.Type.Point.value())); - assertTrue(!Geometry.isLinear(Geometry.Type.MultiPoint.value())); - - assertTrue(Geometry.isArea(Geometry.Type.Polygon.value())); - assertTrue(!Geometry.isArea(Geometry.Type.Polyline.value())); - assertTrue(Geometry.isArea(Geometry.Type.Envelope.value())); - assertTrue(!Geometry.isArea(Geometry.Type.Line.value())); - assertTrue(!Geometry.isArea(Geometry.Type.Point.value())); - assertTrue(!Geometry.isArea(Geometry.Type.MultiPoint.value())); - - assertTrue(!Geometry.isPoint(Geometry.Type.Polygon.value())); - assertTrue(!Geometry.isPoint(Geometry.Type.Polyline.value())); - assertTrue(!Geometry.isPoint(Geometry.Type.Envelope.value())); - assertTrue(!Geometry.isPoint(Geometry.Type.Line.value())); - assertTrue(Geometry.isPoint(Geometry.Type.Point.value())); - assertTrue(Geometry.isPoint(Geometry.Type.MultiPoint.value())); - - assertTrue(Geometry.isMultiVertex(Geometry.Type.Polygon.value())); - assertTrue(Geometry.isMultiVertex(Geometry.Type.Polyline.value())); - assertTrue(!Geometry.isMultiVertex(Geometry.Type.Envelope.value())); - assertTrue(!Geometry.isMultiVertex(Geometry.Type.Line.value())); - assertTrue(!Geometry.isMultiVertex(Geometry.Type.Point.value())); - assertTrue(Geometry.isMultiVertex(Geometry.Type.MultiPoint.value())); - - assertTrue(Geometry.isMultiPath(Geometry.Type.Polygon.value())); - assertTrue(Geometry.isMultiPath(Geometry.Type.Polyline.value())); - assertTrue(!Geometry.isMultiPath(Geometry.Type.Envelope.value())); - assertTrue(!Geometry.isMultiPath(Geometry.Type.Line.value())); - assertTrue(!Geometry.isMultiPath(Geometry.Type.Point.value())); - assertTrue(!Geometry.isMultiPath(Geometry.Type.MultiPoint.value())); - - assertTrue(!Geometry.isSegment(Geometry.Type.Polygon.value())); - assertTrue(!Geometry.isSegment(Geometry.Type.Polyline.value())); - assertTrue(!Geometry.isSegment(Geometry.Type.Envelope.value())); - assertTrue(Geometry.isSegment(Geometry.Type.Line.value())); - assertTrue(!Geometry.isSegment(Geometry.Type.Point.value())); - assertTrue(!Geometry.isSegment(Geometry.Type.MultiPoint.value())); - } - - @Test - public void testCopy() { - Point pt = new Point(); - Point copyPt = (Point) pt.copy(); - assertTrue(copyPt.equals(pt)); - - pt.setXY(11, 13); - copyPt = (Point) pt.copy(); - assertTrue(copyPt.equals(pt)); - assertTrue(copyPt.getXY().isEqual(new Point2D(11, 13))); - - assertTrue(copyPt.getXY().equals((Object) new Point2D(11, 13))); - } - - @Test - public void testEnvelope2D_corners() { - Envelope2D env = new Envelope2D(0, 1, 2, 3); - assertFalse(env.equals(null)); - assertTrue(env.equals((Object) new Envelope2D(0, 1, 2, 3))); - - Point2D pt2D = env.getLowerLeft(); - assertTrue(pt2D.equals(Point2D.construct(0, 1))); - pt2D = env.getUpperLeft(); - assertTrue(pt2D.equals(Point2D.construct(0, 3))); - pt2D = env.getUpperRight(); - assertTrue(pt2D.equals(Point2D.construct(2, 3))); - pt2D = env.getLowerRight(); - assertTrue(pt2D.equals(Point2D.construct(2, 1))); - - { - Point2D[] corners = new Point2D[4]; - env.queryCorners(corners); - assertTrue(corners[0].equals(Point2D.construct(0, 1))); - assertTrue(corners[1].equals(Point2D.construct(0, 3))); - assertTrue(corners[2].equals(Point2D.construct(2, 3))); - assertTrue(corners[3].equals(Point2D.construct(2, 1))); - - env.queryCorners(corners); - assertTrue(corners[0].equals(env.queryCorner(0))); - assertTrue(corners[1].equals(env.queryCorner(1))); - assertTrue(corners[2].equals(env.queryCorner(2))); - assertTrue(corners[3].equals(env.queryCorner(3))); - } - - { - Point2D[] corners = new Point2D[4]; - env.queryCornersReversed(corners); - assertTrue(corners[0].equals(Point2D.construct(0, 1))); - assertTrue(corners[1].equals(Point2D.construct(2, 1))); - assertTrue(corners[2].equals(Point2D.construct(2, 3))); - assertTrue(corners[3].equals(Point2D.construct(0, 3))); - - env.queryCornersReversed(corners); - assertTrue(corners[0].equals(env.queryCorner(0))); - assertTrue(corners[1].equals(env.queryCorner(3))); - assertTrue(corners[2].equals(env.queryCorner(2))); - assertTrue(corners[3].equals(env.queryCorner(1))); - } - - assertTrue(env.getCenter().equals(Point2D.construct(1, 2))); - - assertFalse(env.containsExclusive(env.getUpperLeft())); - assertTrue(env.contains(env.getUpperLeft())); - assertTrue(env.containsExclusive(env.getCenter())); - } - - @Test - public void testReplaceNaNs() { - Envelope env = new Envelope(); - Point pt = new Point(); - pt.setXY(1, 2); - pt.setZ(Double.NaN); - pt.queryEnvelope(env); - pt.replaceNaNs(VertexDescription.Semantics.Z, 5); - assertTrue(pt.equals(new Point(1, 2, 5))); - - assertTrue(env.hasZ()); - assertTrue(env.queryInterval(VertexDescription.Semantics.Z, 0).isEmpty()); - env.replaceNaNs(VertexDescription.Semantics.Z, 5); - assertTrue(env.queryInterval(VertexDescription.Semantics.Z, 0).equals(new Envelope1D(5, 5))); - } - - @Test - public void testTriangleArea() { - Point2D pt = new Point2D(0, 0); - Point2D pt2 = new Point2D(2, 2); - Point2D pt1 = new Point2D(0, 2); - double area = pt.calculateTriangleArea2D(pt1, pt2); - assertEquals(2.0, area); - double area2 = pt.calculateTriangleArea2D(pt2, pt1); - assertEquals(area, area2); - } - - @Test - public void testCircleCenter() { - Point2D pt = new Point2D(-2, -2); - Point2D pt2 = new Point2D(2, 2); - Point2D pt1 = new Point2D(-2, 2); - Point2D center = Point2D.calculateCircleCenterFromThreePoints(pt, pt2, pt1); - assertEquals(center.x, 0.0); - assertEquals(center.y, 0.0); - - center = Point2D.calculateCircleCenterFromThreePoints(pt1, pt, pt2); - assertEquals(center.x, 0.0); - assertEquals(center.y, 0.0); - - center = Point2D.calculateCircleCenterFromThreePoints(pt2, pt, pt1); - assertEquals(center.x, 0.0); - assertEquals(center.y, 0.0); - - center = Point2D.calculateCircleCenterFromThreePoints(pt1, pt2, pt); - assertEquals(center.x, 0.0); - assertEquals(center.y, 0.0); - } + @Override + protected void setUp() throws Exception { + super.setUp(); + } + + @Override + protected void tearDown() throws Exception { + super.tearDown(); + } + + @Test + public void testPt() { + Point pt = new Point(); + assertTrue(pt.isEmpty()); + pt.setXY(10, 2); + assertFalse(pt.isEmpty()); + + pt.toString(); + } + + @Test + public void testEnvelope2000() { + Point points[] = new Point[2000]; + Random random = new Random(69); + for (int i = 0; i < 2000; i++) { + points[i] = new Point(); + points[i].setX(random.nextDouble() * 100); + points[i].setY(random.nextDouble() * 100); + } + for (int iter = 0; iter < 2; iter++) { + final long startTime = System.nanoTime(); + Envelope geomExtent = new Envelope(); + Envelope fullExtent = new Envelope(); + for (int i = 0; i < 2000; i++) { + points[i].queryEnvelope(geomExtent); + fullExtent.merge(geomExtent); + } + long endTime = System.nanoTime(); + } + } + + @Test + public void testBasic() { + assertTrue(Geometry.getDimensionFromType(Geometry.Type.Polygon.value()) == 2); + assertTrue(Geometry + .getDimensionFromType(Geometry.Type.Polyline.value()) == 1); + assertTrue(Geometry + .getDimensionFromType(Geometry.Type.Envelope.value()) == 2); + assertTrue(Geometry.getDimensionFromType(Geometry.Type.Line.value()) == 1); + assertTrue(Geometry.getDimensionFromType(Geometry.Type.Point.value()) == 0); + assertTrue(Geometry.getDimensionFromType(Geometry.Type.MultiPoint + .value()) == 0); + + assertTrue(Geometry.isLinear(Geometry.Type.Polygon.value())); + assertTrue(Geometry.isLinear(Geometry.Type.Polyline.value())); + assertTrue(Geometry.isLinear(Geometry.Type.Envelope.value())); + assertTrue(Geometry.isLinear(Geometry.Type.Line.value())); + assertTrue(!Geometry.isLinear(Geometry.Type.Point.value())); + assertTrue(!Geometry.isLinear(Geometry.Type.MultiPoint.value())); + + assertTrue(Geometry.isArea(Geometry.Type.Polygon.value())); + assertTrue(!Geometry.isArea(Geometry.Type.Polyline.value())); + assertTrue(Geometry.isArea(Geometry.Type.Envelope.value())); + assertTrue(!Geometry.isArea(Geometry.Type.Line.value())); + assertTrue(!Geometry.isArea(Geometry.Type.Point.value())); + assertTrue(!Geometry.isArea(Geometry.Type.MultiPoint.value())); + + assertTrue(!Geometry.isPoint(Geometry.Type.Polygon.value())); + assertTrue(!Geometry.isPoint(Geometry.Type.Polyline.value())); + assertTrue(!Geometry.isPoint(Geometry.Type.Envelope.value())); + assertTrue(!Geometry.isPoint(Geometry.Type.Line.value())); + assertTrue(Geometry.isPoint(Geometry.Type.Point.value())); + assertTrue(Geometry.isPoint(Geometry.Type.MultiPoint.value())); + + assertTrue(Geometry.isMultiVertex(Geometry.Type.Polygon.value())); + assertTrue(Geometry.isMultiVertex(Geometry.Type.Polyline.value())); + assertTrue(!Geometry.isMultiVertex(Geometry.Type.Envelope.value())); + assertTrue(!Geometry.isMultiVertex(Geometry.Type.Line.value())); + assertTrue(!Geometry.isMultiVertex(Geometry.Type.Point.value())); + assertTrue(Geometry.isMultiVertex(Geometry.Type.MultiPoint.value())); + + assertTrue(Geometry.isMultiPath(Geometry.Type.Polygon.value())); + assertTrue(Geometry.isMultiPath(Geometry.Type.Polyline.value())); + assertTrue(!Geometry.isMultiPath(Geometry.Type.Envelope.value())); + assertTrue(!Geometry.isMultiPath(Geometry.Type.Line.value())); + assertTrue(!Geometry.isMultiPath(Geometry.Type.Point.value())); + assertTrue(!Geometry.isMultiPath(Geometry.Type.MultiPoint.value())); + + assertTrue(!Geometry.isSegment(Geometry.Type.Polygon.value())); + assertTrue(!Geometry.isSegment(Geometry.Type.Polyline.value())); + assertTrue(!Geometry.isSegment(Geometry.Type.Envelope.value())); + assertTrue(Geometry.isSegment(Geometry.Type.Line.value())); + assertTrue(!Geometry.isSegment(Geometry.Type.Point.value())); + assertTrue(!Geometry.isSegment(Geometry.Type.MultiPoint.value())); + } + + @Test + public void testCopy() { + Point pt = new Point(); + Point copyPt = (Point) pt.copy(); + assertTrue(copyPt.equals(pt)); + + pt.setXY(11, 13); + copyPt = (Point) pt.copy(); + assertTrue(copyPt.equals(pt)); + assertTrue(copyPt.getXY().isEqual(new Point2D(11, 13))); + + assertTrue(copyPt.getXY().equals((Object) new Point2D(11, 13))); + } + + @Test + public void testEnvelope2D_corners() { + Envelope2D env = new Envelope2D(0, 1, 2, 3); + assertFalse(env.equals(null)); + assertTrue(env.equals((Object) new Envelope2D(0, 1, 2, 3))); + + Point2D pt2D = env.getLowerLeft(); + assertTrue(pt2D.equals(Point2D.construct(0, 1))); + pt2D = env.getUpperLeft(); + assertTrue(pt2D.equals(Point2D.construct(0, 3))); + pt2D = env.getUpperRight(); + assertTrue(pt2D.equals(Point2D.construct(2, 3))); + pt2D = env.getLowerRight(); + assertTrue(pt2D.equals(Point2D.construct(2, 1))); + + { + Point2D[] corners = new Point2D[4]; + env.queryCorners(corners); + assertTrue(corners[0].equals(Point2D.construct(0, 1))); + assertTrue(corners[1].equals(Point2D.construct(0, 3))); + assertTrue(corners[2].equals(Point2D.construct(2, 3))); + assertTrue(corners[3].equals(Point2D.construct(2, 1))); + + env.queryCorners(corners); + assertTrue(corners[0].equals(env.queryCorner(0))); + assertTrue(corners[1].equals(env.queryCorner(1))); + assertTrue(corners[2].equals(env.queryCorner(2))); + assertTrue(corners[3].equals(env.queryCorner(3))); + } + + { + Point2D[] corners = new Point2D[4]; + env.queryCornersReversed(corners); + assertTrue(corners[0].equals(Point2D.construct(0, 1))); + assertTrue(corners[1].equals(Point2D.construct(2, 1))); + assertTrue(corners[2].equals(Point2D.construct(2, 3))); + assertTrue(corners[3].equals(Point2D.construct(0, 3))); + + env.queryCornersReversed(corners); + assertTrue(corners[0].equals(env.queryCorner(0))); + assertTrue(corners[1].equals(env.queryCorner(3))); + assertTrue(corners[2].equals(env.queryCorner(2))); + assertTrue(corners[3].equals(env.queryCorner(1))); + } + + assertTrue(env.getCenter().equals(Point2D.construct(1, 2))); + + assertFalse(env.containsExclusive(env.getUpperLeft())); + assertTrue(env.contains(env.getUpperLeft())); + assertTrue(env.containsExclusive(env.getCenter())); + } + + @Test + public void testReplaceNaNs() { + Envelope env = new Envelope(); + Point pt = new Point(); + pt.setXY(1, 2); + pt.setZ(Double.NaN); + pt.queryEnvelope(env); + pt.replaceNaNs(VertexDescription.Semantics.Z, 5); + assertTrue(pt.equals(new Point(1, 2, 5))); + + assertTrue(env.hasZ()); + assertTrue(env.queryInterval(VertexDescription.Semantics.Z, 0).isEmpty()); + env.replaceNaNs(VertexDescription.Semantics.Z, 5); + assertTrue(env.queryInterval(VertexDescription.Semantics.Z, 0).equals(new Envelope1D(5, 5))); + } + + @Test + public void testTriangleArea() { + Point2D pt = new Point2D(0, 0); + Point2D pt2 = new Point2D(2, 2); + Point2D pt1 = new Point2D(0, 2); + double area = pt.calculateTriangleArea2D(pt1, pt2); + assertEquals(2.0, area); + double area2 = pt.calculateTriangleArea2D(pt2, pt1); + assertEquals(area, area2); + } + + @Test + public void testCircleCenter() { + Point2D pt = new Point2D(-2, -2); + Point2D pt2 = new Point2D(2, 2); + Point2D pt1 = new Point2D(-2, 2); + Point2D center = Point2D.calculateCircleCenterFromThreePoints(pt, pt2, pt1); + assertEquals(center.x, 0.0); + assertEquals(center.y, 0.0); + + center = Point2D.calculateCircleCenterFromThreePoints(pt1, pt, pt2); + assertEquals(center.x, 0.0); + assertEquals(center.y, 0.0); + + center = Point2D.calculateCircleCenterFromThreePoints(pt2, pt, pt1); + assertEquals(center.x, 0.0); + assertEquals(center.y, 0.0); + + center = Point2D.calculateCircleCenterFromThreePoints(pt1, pt2, pt); + assertEquals(center.x, 0.0); + assertEquals(center.y, 0.0); + } } diff --git a/src/test/java/com/esri/core/geometry/TestPolygon.java b/src/test/java/com/esri/core/geometry/TestPolygon.java index e5633a13..4256e79e 100644 --- a/src/test/java/com/esri/core/geometry/TestPolygon.java +++ b/src/test/java/com/esri/core/geometry/TestPolygon.java @@ -28,1301 +28,1301 @@ import org.junit.Test; public class TestPolygon extends TestCase { - @Override - protected void setUp() throws Exception { - super.setUp(); - } - - @Override - protected void tearDown() throws Exception { - super.tearDown(); - } - - @Test - public void testCreation() { - // simple create - - Polygon poly = new Polygon(); - @SuppressWarnings("unused") - int number = poly.getStateFlag(); - - assertTrue(poly != null); - // assertTrue(poly.getClass() == Polygon.class); - // assertFalse(poly.getClass() == Envelope.class); - - assertTrue(poly.getType() == Geometry.Type.Polygon); - assertTrue(poly.isEmpty()); - assertTrue(poly.getPointCount() == 0); - assertTrue(poly.getPathCount() == 0); - number = poly.getStateFlag(); - poly = null; - assertFalse(poly != null); - - // play with default attributes - @SuppressWarnings("unused") - Polygon poly2 = new Polygon(); - // SimpleTest(poly2); - - // creation1(); - // creation2(); - // addpath(); - // addpath2(); - // removepath(); - // reversepath(); - // reverseallpaths(); - // openallpaths(); - // openpath(); - // insertpath(); - // insertpoints(); - // insertpoint(); - // removepoint(); - // insertpointsfromaray(); - // createWithStreams(); - // testBug1(); - } - - @Test - public void testCreation1() { - // Simple area and length calcul test - Polygon poly = new Polygon(); - @SuppressWarnings("unused") - int number = poly.getStateFlag(); - Envelope env = new Envelope(1000, 2000, 1010, 2010); - env.toString(); - poly.addEnvelope(env, false); - poly.toString(); - number = poly.getStateFlag(); - assertTrue(Math.abs(poly.calculateArea2D() - 100) < 1e-12); - assertTrue(Math.abs(poly.calculateLength2D() - 40) < 1e-12); - poly.setEmpty(); - number = poly.getStateFlag(); - poly.addEnvelope(env, true); - number = poly.getStateFlag(); - assertTrue(Math.abs(poly.calculateArea2D() + 100) < 1e-12); - number = poly.getStateFlag(); - } - - @Test - public void testCreation2() { - Polygon poly = new Polygon(); - int state1 = poly.getStateFlag(); - poly.startPath(10, 1); - poly.lineTo(15, 20); - poly.lineTo(30, 14); - poly.lineTo(60, 144); - poly.closePathWithLine(); - int state2 = poly.getStateFlag(); - assertTrue(state2 == state1 + 1); - - // MultiPathImpl::Pointer mpImpl = - // (MultiPathImpl::Pointer)poly->_GetImpl(); - // - // assertTrue(mpImpl.getPointCount() == 4); - // assertTrue(mpImpl.getPathCount() == 1); - // AttributeStreamBase xy = - // mpImpl.getAttributeStreamRef(enum_value2(VertexDescription, - // Semantics, POSITION)); - // double x, y; - // x = xy.readAsDbl(2 * 2); - // y = xy.readAsDbl(2 * 2 + 1); - // assertTrue(x == 30); assertTrue(y == 14); - // - // AttributeStreamOfIndexType parts = mpImpl.getPathStreamRef(); - // assertTrue(parts.size() == 2); - // assertTrue(parts.read(0) == 0); - // assertTrue(parts.read(1) == 4); - // assertTrue(mpImpl.isClosedPath(0)); - // assertTrue(mpImpl.getSegmentFlagsStreamRef() == NULLPTR); - // assertTrue(mpImpl.getSegmentIndexStreamRef() == NULLPTR); - // assertTrue(mpImpl.getSegmentDataStreamRef() == NULLPTR); - - poly.startPath(20, 13); - poly.lineTo(150, 120); - poly.lineTo(300, 414); - poly.lineTo(610, 14); - poly.lineTo(6210, 140); - poly.closePathWithLine(); - - // assertTrue(mpImpl.getPointCount() == 9); - // assertTrue(mpImpl.getPathCount() == 2); - // assertTrue(mpImpl.isClosedPath(1)); - // xy = mpImpl.getAttributeStreamRef(enum_value2(VertexDescription, - // Semantics, POSITION)); - // x = xy.readAsDbl(2 * 3); - // y = xy.readAsDbl(2 * 3 + 1); - // assertTrue(x == 60); assertTrue(y == 144); - // - // x = xy.readAsDbl(2 * 6); - // y = xy.readAsDbl(2 * 6 + 1); - // assertTrue(x == 300); assertTrue(y == 414); - - // parts = mpImpl.getPathStreamRef(); - // assertTrue(parts.size() == 3); - // assertTrue(parts.read(0) == 0); - // assertTrue(parts.read(1) == 4); - // assertTrue(parts.read(2) == 9); - // assertTrue(mpImpl.getSegmentIndexStreamRef() == NULLPTR); - // assertTrue(mpImpl.getSegmentFlagsStreamRef() == NULLPTR); - // assertTrue(mpImpl.getSegmentDataStreamRef() == NULLPTR); - - poly.startPath(200, 1333); - poly.lineTo(1150, 1120); - poly.lineTo(300, 4114); - poly.lineTo(6110, 114); - poly.lineTo(61210, 1140); - - assertTrue(poly.isClosedPath(2) == true); - poly.closeAllPaths(); - assertTrue(poly.isClosedPath(2) == true); - - { - Polygon poly2 = new Polygon(); - poly2.startPath(10, 10); - poly2.lineTo(100, 10); - poly2.lineTo(100, 100); - poly2.lineTo(10, 100); - } - - { - Polygon poly3 = new Polygon(); - // create a star (non-simple) - poly3.startPath(1, 0); - poly3.lineTo(5, 10); - poly3.lineTo(9, 0); - poly3.lineTo(0, 6); - poly3.lineTo(10, 6); - } - } - - @Test - public void testCreateWithStreams() { - // Polygon poly = new Polygon(); - // poly.addAttribute((int)Semantics.M); - // try - // { - // OutputDebugString(L"Test an assert\n"); - // GeometryException::m_assertOnException = false; - // ((MultiPathImpl::Pointer)poly->_GetImpl()).getPathStreamRef(); - // } - // catch(GeometryException except) - // { - // assertTrue(except->empty_geometry); - // GeometryException::m_assertOnException = true; - // } - // try - // { - // OutputDebugString(L"Test an assert\n"); - // GeometryException::m_assertOnException = false; - // ((MultiPathImpl::Pointer)poly->_GetImpl()).getAttributeStreamRef(enum_value2(VertexDescription, - // Semantics, POSITION)); - // } - // catch(GeometryException except) - // { - // assertTrue(except->empty_geometry); - // GeometryException::m_assertOnException = true; - // } - // - // MultiPathImpl::Pointer mpImpl = - // (MultiPathImpl::Pointer)poly->_GetImpl(); - // - // AttributeStreamOfIndexType parts = - // (AttributeStreamOfIndexType)AttributeStreamBase::CreateIndexStream(3); - // mpImpl.setPathStreamRef(parts); - // - // parts.write(0, 0); //first element is always 0 - // parts.write(1, 4); //second element is the index of the first vertex - // of the second part - // parts.write(2, 8); //the third element is the total point count. - // - // AttributeStreamOfInt8 flags = - // (AttributeStreamOfInt8)AttributeStreamBase::CreateByteStream(3); - // mpImpl.setPathFlagsStreamRef(flags); - // flags.write(0, enum_value1(PathFlags, enumClosed)); - // flags.write(1, enum_value1(PathFlags, enumClosed)); - // flags.write(2, 0); - // - // AttributeStreamOfDbl xy = - // (AttributeStreamOfDbl)AttributeStreamBase::CreateDoubleStream(16); - // //16 doubles means 8 points - // mpImpl.setAttributeStreamRef(enum_value2(VertexDescription, - // Semantics, POSITION), xy); - // - // Envelope2D env; - // env.SetCoords(-1000, -2000, 1000, 2000); - // Point2D buf[4]; - // env.QueryCorners(buf); - // xy.writeRange(0, 8, (double*)buf, 0, true); - // - // env.SetCoords(-100, -200, 100, 200); - // env.QueryCornersReversed(buf); //make a hole by quering reversed - // order - // xy.writeRange(8, 8, (double*)buf, 0, true); - // - // mpImpl.notifyModified(MultiVertexGeometryImpl::DirtyAll); //notify - // the path that the vertices had changed. - // - // assertTrue(poly.getPointCount() == 8); - // assertTrue(poly.getPathCount() == 2); - // assertTrue(poly.getPathSize(1) == 4); - // assertTrue(poly.isClosedPath(0)); - // assertTrue(poly.isClosedPath(1)); - } - - @Test - public void testCloneStuff() { - Polygon poly = new Polygon(); - poly.startPath(10, 1); - poly.lineTo(15, 20); - poly.lineTo(30, 14); - poly.lineTo(60, 144); - - poly.startPath(10, 1); - poly.lineTo(15, 20); - poly.lineTo(300, 14); - poly.lineTo(60, 144); - - poly.startPath(10, 1); - poly.lineTo(125, 20); - poly.lineTo(30, 14); - poly.lineTo(600, 144); - poly.closePathWithLine(); - - Polygon clone = (Polygon) poly.copy(); - assertTrue(clone.getPathCount() == 3); - assertTrue(clone.getPathStart(2) == 8); - assertTrue(clone.isClosedPath(0)); - assertTrue(clone.isClosedPath(1)); - assertTrue(clone.isClosedPath(2)); - assertTrue(clone.getXY(5).isEqual(new Point2D(15, 20))); - } - - @Test - public void testCloneStuffEnvelope() { - Envelope env = new Envelope(11, 12, 15, 24); - Envelope eCopy = (Envelope) env.copy(); - assertTrue(eCopy.equals(env)); - assertTrue(eCopy.getXMin() == 11); - assertTrue(eCopy.getYMin() == 12); - assertTrue(eCopy.getXMax() == 15); - assertTrue(eCopy.getYMax() == 24); - } - - @Test - public void testCloneStuffPolyline() { - Polyline poly = new Polyline(); - poly.startPath(10, 1); - poly.lineTo(15, 20); - poly.lineTo(30, 14); - poly.lineTo(60, 144); - - poly.startPath(10, 1); - poly.lineTo(15, 20); - poly.lineTo(300, 14); - poly.lineTo(60, 144); - - poly.startPath(10, 1); - poly.lineTo(125, 20); - poly.lineTo(30, 14); - poly.lineTo(600, 144); - poly.closePathWithLine(); - - Polyline clone = (Polyline) poly.copy(); - assertTrue(clone.getPathCount() == 3); - assertTrue(clone.getPathStart(2) == 8); - assertTrue(!clone.isClosedPath(0)); - assertTrue(!clone.isClosedPath(1)); - assertTrue(clone.isClosedPath(2)); - assertTrue(clone.getXY(5).isEqual(new Point2D(15, 20))); - } - - @Test - public void testAddpath() { - Polygon poly = new Polygon(); - poly.startPath(10, 1); - poly.lineTo(15, 20); - poly.lineTo(30, 14); - poly.lineTo(60, 144); - - poly.startPath(10, 1); - poly.lineTo(15, 20); - poly.lineTo(300, 14); - poly.lineTo(60, 144); - - poly.startPath(10, 1); - poly.lineTo(125, 20); - poly.lineTo(30, 14); - poly.lineTo(600, 144); - - Polygon poly1 = new Polygon(); - poly1.addPath(poly, 2, true); - poly1.addPath(poly, 0, true); - - assertTrue(poly1.getPathCount() == 2); - assertTrue(poly1.getPathStart(1) == 4); - assertTrue(poly1.isClosedPath(0)); - assertTrue(poly1.isClosedPath(1)); - Point ptOut = poly1.getPoint(6); - assertTrue(ptOut.getX() == 30 && ptOut.getY() == 14); - } - - @Test - public void testAddpath2() { - Polygon polygon = new Polygon(); - polygon.startPath(-179, 34); - polygon.lineTo(-154, 34); - polygon.lineTo(-179, 36); - polygon.lineTo(-180, 90); - polygon.lineTo(180, 90); - polygon.lineTo(180, 36); - polygon.lineTo(70, 46); - polygon.lineTo(-76, 80); - polygon.lineTo(12, 38); - polygon.lineTo(-69, 51); - polygon.lineTo(-95, 29); - polygon.lineTo(-105, 7); - polygon.lineTo(-112, -27); - polygon.lineTo(-149, -11); - polygon.lineTo(-149, -11); - polygon.lineTo(-166, -4); - polygon.lineTo(-179, 5); - - Polyline polyline = new Polyline(); - polyline.startPath(180, 5); - polyline.lineTo(140, 34); - polyline.lineTo(180, 34); - - polygon.addPath(polyline, 0, true); - - Point startpoint = polygon.getPoint(17); - assertTrue(startpoint.getX() == 180 && startpoint.getY() == 5); - } - - @Test - public void testRemovepath() { - Polygon poly = new Polygon(); - poly.startPath(10, 1); - poly.lineTo(15, 20); - poly.lineTo(30, 14); - poly.lineTo(60, 144); - - poly.startPath(10, 1); - poly.lineTo(15, 20); - poly.lineTo(300, 14); - poly.lineTo(60, 144); - - poly.startPath(10, 1); - poly.lineTo(125, 20); - poly.lineTo(30, 14); - poly.lineTo(600, 144); - - // poly->SetAttribute(enum_value2(VertexDescription, Semantics, Z), 0, - // 0, 2); - // poly->SetAttribute(enum_value2(VertexDescription, Semantics, Z), 1, - // 0, 3); - // poly->SetAttribute(enum_value2(VertexDescription, Semantics, Z), 2, - // 0, 5); - // poly->SetAttribute(enum_value2(VertexDescription, Semantics, Z), 3, - // 0, 7); - // poly->SetAttribute(enum_value2(VertexDescription, Semantics, Z), 4, - // 0, 11); - // poly->SetAttribute(enum_value2(VertexDescription, Semantics, Z), 5, - // 0, 13); - // poly->SetAttribute(enum_value2(VertexDescription, Semantics, Z), 6, - // 0, 17); - // poly->SetAttribute(enum_value2(VertexDescription, Semantics, Z), 7, - // 0, 19); - // poly->SetAttribute(enum_value2(VertexDescription, Semantics, Z), 8, - // 0, 23); - // poly->SetAttribute(enum_value2(VertexDescription, Semantics, Z), 9, - // 0, 29); - // poly->SetAttribute(enum_value2(VertexDescription, Semantics, Z), 10, - // 0, 31); - // poly->SetAttribute(enum_value2(VertexDescription, Semantics, Z), 11, - // 0, 37); - - poly.removePath(1); - - assertTrue(poly.getPathCount() == 2); - assertTrue(poly.getPathStart(1) == 4); - assertTrue(poly.isClosedPath(0)); - assertTrue(poly.isClosedPath(1)); - Point ptOut = poly.getPoint(4); - assertTrue(ptOut.getX() == 10 && ptOut.getY() == 1); - poly.removePath(0); - poly.removePath(0); - assertTrue(poly.getPathCount() == 0); - - Polygon poly2 = new Polygon(); - poly2.startPath(0, 0); - poly2.lineTo(0, 10); - poly2.lineTo(10, 10); - poly2.startPath(1, 1); - poly2.lineTo(2, 2); - poly2.removePath(0); - // poly2->StartPath(0, 0); - poly2.lineTo(0, 10); - poly2.lineTo(10, 10); - - // Polygon polygon2 = new Polygon(); - // polygon2.addPath(poly, -1, true); - // polygon2.addPath(poly, -1, true); - // polygon2.addPath(poly, -1, true); - // assertTrue(polygon2.getPathCount() == 3); - // polygon2.removePath(0); - // polygon2.removePath(0); - // polygon2.removePath(0); - // assertTrue(polygon2.getPathCount() == 0); - // polygon2.addPath(poly, -1, true); - - // Point point1 = new Point(); - // Point point2 = new Point(); - // point1.setX(0); - // point1.setY(0); - // point2.setX(0); - // point2.setY(0); - // polygon2.addPath(poly2, 0, true); - // polygon2.removePath(0); - // polygon2.insertPoint(0, 0, point1); - // polygon2.insertPoint(0, 0, point2); - // assertTrue(polygon2.getPathCount() == 1); - // assertTrue(polygon2.getPointCount() == 2); - - Polygon polygon3 = new Polygon(); - polygon3.startPath(0, 0); - polygon3.lineTo(0, 10); - polygon3.lineTo(10, 10); - double area = polygon3.calculateArea2D(); - polygon3.removePath(0); - - polygon3.startPath(0, 0); - polygon3.lineTo(0, 10); - polygon3.lineTo(10, 10); - area = polygon3.calculateArea2D(); - assertTrue(area > 0.0); - } - - @Test - public void testReversepath() { - Polygon poly = new Polygon(); - poly.startPath(10, 1); - poly.lineTo(15, 20); - poly.lineTo(30, 14); - poly.lineTo(60, 144); - - poly.startPath(10, 1); - poly.lineTo(15, 20); - poly.lineTo(300, 14); - poly.lineTo(60, 144); - - poly.startPath(10, 1); - poly.lineTo(125, 20); - poly.lineTo(30, 14); - poly.lineTo(600, 144); - - // poly.setAttribute(enum_value2(VertexDescription, Semantics, Z), 0, 0, - // 2); - // poly.setAttribute(enum_value2(VertexDescription, Semantics, Z), 1, 0, - // 3); - // poly.setAttribute(enum_value2(VertexDescription, Semantics, Z), 2, 0, - // 5); - // poly.setAttribute(enum_value2(VertexDescription, Semantics, Z), 3, 0, - // 7); - // poly.setAttribute(enum_value2(VertexDescription, Semantics, Z), 4, 0, - // 11); - // poly.setAttribute(enum_value2(VertexDescription, Semantics, Z), 5, 0, - // 13); - // poly.setAttribute(enum_value2(VertexDescription, Semantics, Z), 6, 0, - // 17); - // poly.setAttribute(enum_value2(VertexDescription, Semantics, Z), 7, 0, - // 19); - // poly.setAttribute(enum_value2(VertexDescription, Semantics, Z), 8, 0, - // 23); - // poly.setAttribute(enum_value2(VertexDescription, Semantics, Z), 9, 0, - // 29); - // poly.setAttribute(enum_value2(VertexDescription, Semantics, Z), 10, - // 0, 31); - // poly.setAttribute(enum_value2(VertexDescription, Semantics, Z), 11, - // 0, 37); - - poly.reversePath(1); - - assertTrue(poly.getPathCount() == 3); - assertTrue(poly.getPathStart(1) == 4); - assertTrue(poly.isClosedPath(0)); - assertTrue(poly.isClosedPath(1)); - Point ptOut = poly.getPoint(4); - assertTrue(ptOut.getX() == 10 && ptOut.getY() == 1); - } - - @Test - public void testReverseAllPaths() { - Polygon poly = new Polygon(); - poly.startPath(10, 1); - poly.lineTo(15, 20); - poly.lineTo(30, 14); - poly.lineTo(60, 144); - - poly.startPath(10, 1); - poly.lineTo(15, 20); - poly.lineTo(300, 14); - poly.lineTo(60, 144); - - poly.startPath(10, 1); - poly.lineTo(125, 20); - poly.lineTo(30, 14); - poly.lineTo(600, 144); - - // poly.setAttribute(enum_value2(VertexDescription, Semantics, Z), 0, 0, - // 2); - // poly.setAttribute(enum_value2(VertexDescription, Semantics, Z), 1, 0, - // 3); - // poly.setAttribute(enum_value2(VertexDescription, Semantics, Z), 2, 0, - // 5); - // poly.setAttribute(enum_value2(VertexDescription, Semantics, Z), 3, 0, - // 7); - // - // poly.setAttribute(enum_value2(VertexDescription, Semantics, Z), 4, 0, - // 11); - // poly.setAttribute(enum_value2(VertexDescription, Semantics, Z), 5, 0, - // 13); - // poly.setAttribute(enum_value2(VertexDescription, Semantics, Z), 6, 0, - // 17); - // poly.setAttribute(enum_value2(VertexDescription, Semantics, Z), 7, 0, - // 19); - // - // poly.setAttribute(enum_value2(VertexDescription, Semantics, Z), 8, 0, - // 23); - // poly.setAttribute(enum_value2(VertexDescription, Semantics, Z), 9, 0, - // 29); - // poly.setAttribute(enum_value2(VertexDescription, Semantics, Z), 10, - // 0, 31); - // poly.setAttribute(enum_value2(VertexDescription, Semantics, Z), 11, - // 0, 37); - - double area = poly.calculateArea2D(); - poly.reverseAllPaths(); - double areaReversed = poly.calculateArea2D(); - assertTrue(Math.abs(area + areaReversed) <= 0.001); - } - - @Test - public void testOpenAllPaths() { - Polyline poly = new Polyline(); - poly.startPath(10, 1); - poly.lineTo(15, 20); - poly.lineTo(30, 14); - poly.lineTo(60, 144); - poly.closePathWithLine(); - - poly.startPath(10, 1); - poly.lineTo(15, 20); - poly.lineTo(300, 14); - poly.lineTo(60, 144); - poly.closePathWithLine(); - - poly.startPath(10, 1); - poly.lineTo(125, 20); - poly.lineTo(30, 14); - poly.lineTo(600, 144); - poly.closePathWithLine(); - - // poly.setAttribute(enum_value2(VertexDescription, Semantics, Z), 0, 0, - // 2); - // poly.setAttribute(enum_value2(VertexDescription, Semantics, Z), 1, 0, - // 3); - // poly.setAttribute(enum_value2(VertexDescription, Semantics, Z), 2, 0, - // 5); - // poly.setAttribute(enum_value2(VertexDescription, Semantics, Z), 3, 0, - // 7); - // poly.setAttribute(enum_value2(VertexDescription, Semantics, Z), 4, 0, - // 11); - // poly.setAttribute(enum_value2(VertexDescription, Semantics, Z), 5, 0, - // 13); - // poly.setAttribute(enum_value2(VertexDescription, Semantics, Z), 6, 0, - // 17); - // poly.setAttribute(enum_value2(VertexDescription, Semantics, Z), 7, 0, - // 19); - // poly.setAttribute(enum_value2(VertexDescription, Semantics, Z), 8, 0, - // 23); - // poly.setAttribute(enum_value2(VertexDescription, Semantics, Z), 9, 0, - // 29); - // poly.setAttribute(enum_value2(VertexDescription, Semantics, Z), 10, - // 0, 31); - // poly.setAttribute(enum_value2(VertexDescription, Semantics, Z), 11, - // 0, 37); - - // MultiPathImpl::Pointer mpImpl = - // (MultiPathImpl::Pointer)poly->_GetImpl(); - // poly.openAllPathsAndDuplicateStartVertex(); - - // assertTrue(poly.getPathCount() == 3); - // assertTrue(poly.getPathStart(0) == 0); - // assertTrue(poly.getPathStart(1) == 5); - // assertTrue(poly.getPathStart(2) == 10); - // assertTrue(poly.getPointCount() == 15); - // Point ptstart = poly.getPoint(0); - // Point ptend = poly.getPoint(4); - // assertTrue(ptstart.getX() == ptend.getX() && ptstart.getY() == - // ptend.getY()); - // ptstart = poly.getPoint(5); - // ptend = poly.getPoint(9); - // assertTrue(ptstart.getX() == ptend.getX() && ptstart.getY() == - // ptend.getY()); - // ptstart = poly.getPoint(10); - // ptend = poly.getPoint(14); - // assertTrue(ptstart.getX() == ptend.getX() && ptstart.getY() == - // ptend.getY()); - } - - @Test - public void testOpenPath() { - Polyline poly = new Polyline(); - poly.startPath(10, 1); - poly.lineTo(15, 20); - poly.lineTo(30, 14); - poly.lineTo(60, 144); - poly.closePathWithLine(); - - poly.startPath(10, 1); - poly.lineTo(15, 20); - poly.lineTo(300, 14); - poly.lineTo(60, 144); - poly.closePathWithLine(); - - poly.startPath(10, 1); - poly.lineTo(125, 20); - poly.lineTo(30, 14); - poly.lineTo(600, 144); - poly.closePathWithLine(); - - // poly.setAttribute(enum_value2(VertexDescription, Semantics, Z), 0, 0, - // 2); - // poly.setAttribute(enum_value2(VertexDescription, Semantics, Z), 1, 0, - // 3); - // poly.setAttribute(enum_value2(VertexDescription, Semantics, Z), 2, 0, - // 5); - // poly.setAttribute(enum_value2(VertexDescription, Semantics, Z), 3, 0, - // 7); - // poly.setAttribute(enum_value2(VertexDescription, Semantics, Z), 4, 0, - // 11); - // poly.setAttribute(enum_value2(VertexDescription, Semantics, Z), 5, 0, - // 13); - // poly.setAttribute(enum_value2(VertexDescription, Semantics, Z), 6, 0, - // 17); - // poly.setAttribute(enum_value2(VertexDescription, Semantics, Z), 7, 0, - // 19); - // poly.setAttribute(enum_value2(VertexDescription, Semantics, Z), 8, 0, - // 23); - // poly.setAttribute(enum_value2(VertexDescription, Semantics, Z), 9, 0, - // 29); - // poly.setAttribute(enum_value2(VertexDescription, Semantics, Z), 10, - // 0, 31); - // poly.setAttribute(enum_value2(VertexDescription, Semantics, Z), 11, - // 0, 37); - // - // MultiPathImpl::Pointer mpImpl = - // (MultiPathImpl::Pointer)poly->_GetImpl(); - // poly.openPathAndDuplicateStartVertex(1); - - // assertTrue(poly.getPathCount() == 3); - // assertTrue(poly.getPathStart(0) == 0); - // assertTrue(poly.getPathStart(1) == 4); - // assertTrue(poly.getPathStart(2) == 9); - // assertTrue(poly.getPointCount() == 13); - // Point ptstart = poly.getPoint(4); - // Point ptend = poly.getPoint(8); - // assertTrue(ptstart.getX() == ptend.getX() && ptstart.getY() == - // ptend.getY()); - // ptstart = poly.getPoint(9); - // assertTrue(ptstart.getX() == 10 && ptstart.getY() == 1); - } - - @Test - public void testInsertPath() { - Polygon poly = new Polygon(); - poly.startPath(10, 1); - poly.lineTo(15, 20); - poly.lineTo(30, 14); - poly.lineTo(60, 144); - - poly.startPath(12, 2); - poly.lineTo(16, 21); - poly.lineTo(301, 15); - poly.lineTo(61, 145); - - poly.startPath(13, 3); - poly.lineTo(126, 22); - poly.lineTo(31, 16); - poly.lineTo(601, 146); - - // poly.setAttribute(enum_value2(VertexDescription, Semantics, Z), 0, 0, - // 2); - // poly.setAttribute(enum_value2(VertexDescription, Semantics, Z), 1, 0, - // 3); - // poly.setAttribute(enum_value2(VertexDescription, Semantics, Z), 2, 0, - // 5); - // poly.setAttribute(enum_value2(VertexDescription, Semantics, Z), 3, 0, - // 7); - // poly.setAttribute(enum_value2(VertexDescription, Semantics, Z), 4, 0, - // 11); - // poly.setAttribute(enum_value2(VertexDescription, Semantics, Z), 5, 0, - // 13); - // poly.setAttribute(enum_value2(VertexDescription, Semantics, Z), 6, 0, - // 17); - // poly.setAttribute(enum_value2(VertexDescription, Semantics, Z), 7, 0, - // 19); - // poly.setAttribute(enum_value2(VertexDescription, Semantics, Z), 8, 0, - // 23); - // poly.setAttribute(enum_value2(VertexDescription, Semantics, Z), 9, 0, - // 29); - // poly.setAttribute(enum_value2(VertexDescription, Semantics, Z), 10, - // 0, 31); - // poly.setAttribute(enum_value2(VertexDescription, Semantics, Z), 11, - // 0, 37); - - Polygon poly2 = new Polygon(); - poly2.startPath(12, 2); - poly2.lineTo(16, 21); - poly2.lineTo(301, 15); - poly2.lineTo(61, 145); - - poly.insertPath(0, poly2, 0, false); - - assertTrue(poly.getPathCount() == 4); - assertTrue(poly.getPathStart(0) == 0); - assertTrue(poly.getPathStart(1) == 4); - assertTrue(poly.getPathStart(2) == 8); - assertTrue(poly.getPathStart(3) == 12); - assertTrue(poly.getPointCount() == 16); - - Point2D pt0 = poly.getXY(0); - assertTrue(pt0.x == 12 && pt0.y == 2); - Point2D pt1 = poly.getXY(1); - assertTrue(pt1.x == 61 && pt1.y == 145); - Point2D pt2 = poly.getXY(2); - assertTrue(pt2.x == 301 && pt2.y == 15); - Point2D pt3 = poly.getXY(3); - assertTrue(pt3.x == 16 && pt3.y == 21); - - Point pt2d = new Point(-27, -27); - - poly.insertPoint(1, 0, pt2d); - assertTrue(poly.getPathCount() == 4); - assertTrue(poly.getPathStart(0) == 0); - assertTrue(poly.getPathStart(1) == 4); - assertTrue(poly.getPathStart(2) == 9); - assertTrue(poly.getPathStart(3) == 13); - assertTrue(poly.getPointCount() == 17); - } - - @Test - public void testInsertPoints() { - {// forward insertion - Polygon poly = new Polygon(); - poly.startPath(10, 1); - poly.lineTo(15, 20); - poly.lineTo(30, 14); - poly.lineTo(60, 144); - - poly.startPath(10, 1); - poly.lineTo(15, 20); - poly.lineTo(300, 14); - poly.lineTo(314, 217); - poly.lineTo(60, 144); - - poly.startPath(10, 1); - poly.lineTo(125, 20); - poly.lineTo(30, 14); - poly.lineTo(600, 144); - - Polygon poly1 = new Polygon(); - poly1.startPath(1, 17); - poly1.lineTo(1, 207); - poly1.lineTo(3, 147); - poly1.lineTo(6, 1447); - - poly1.startPath(1000, 17); - poly1.lineTo(1250, 207); - poly1.lineTo(300, 147); - poly1.lineTo(6000, 1447); - - poly1.insertPoints(1, 2, poly, 1, 1, 3, true);// forward - - assertTrue(poly1.getPathCount() == 2); - assertTrue(poly1.getPathStart(1) == 4); - assertTrue(poly1.isClosedPath(0)); - assertTrue(poly1.isClosedPath(1)); - assertTrue(poly1.getPointCount() == 11); - assertTrue(poly1.getPathSize(1) == 7); - // Point2D ptOut; - // ptOut = poly1.getXY(5); - // assertTrue(ptOut.x == 1250 && ptOut.y == 207); - // ptOut = poly1.getXY(6); - // assertTrue(ptOut.x == 15 && ptOut.y == 20); - // ptOut = poly1.getXY(7); - // assertTrue(ptOut.x == 300 && ptOut.y == 14); - // ptOut = poly1.getXY(8); - // assertTrue(ptOut.x == 314 && ptOut.y == 217); - // ptOut = poly1.getXY(9); - // assertTrue(ptOut.x == 300 && ptOut.y == 147); - // ptOut = poly1.getXY(10); - // assertTrue(ptOut.x == 6000 && ptOut.y == 1447); - } - - {// reverse insertion - Polygon poly = new Polygon(); - poly.startPath(10, 1); - poly.lineTo(15, 20); - poly.lineTo(30, 14); - poly.lineTo(60, 144); - - poly.startPath(10, 1); - poly.lineTo(15, 20); - poly.lineTo(300, 14); - poly.lineTo(314, 217); - poly.lineTo(60, 144); - - poly.startPath(10, 1); - poly.lineTo(125, 20); - poly.lineTo(30, 14); - poly.lineTo(600, 144); - - Polygon poly1 = new Polygon(); - poly1.startPath(1, 17); - poly1.lineTo(1, 207); - poly1.lineTo(3, 147); - poly1.lineTo(6, 1447); - - poly1.startPath(1000, 17); - poly1.lineTo(1250, 207); - poly1.lineTo(300, 147); - poly1.lineTo(6000, 1447); - - poly1.insertPoints(1, 2, poly, 1, 1, 3, false);// reverse - - assertTrue(poly1.getPathCount() == 2); - assertTrue(poly1.getPathStart(1) == 4); - assertTrue(poly1.isClosedPath(0)); - assertTrue(poly1.isClosedPath(1)); - assertTrue(poly1.getPointCount() == 11); - assertTrue(poly1.getPathSize(1) == 7); - // Point2D ptOut; - // ptOut = poly1.getXY(5); - // assertTrue(ptOut.x == 1250 && ptOut.y == 207); - // ptOut = poly1.getXY(6); - // assertTrue(ptOut.x == 314 && ptOut.y == 217); - // ptOut = poly1.getXY(7); - // assertTrue(ptOut.x == 300 && ptOut.y == 14); - // ptOut = poly1.getXY(8); - // assertTrue(ptOut.x == 15 && ptOut.y == 20); - // ptOut = poly1.getXY(9); - // assertTrue(ptOut.x == 300 && ptOut.y == 147); - // ptOut = poly1.getXY(10); - // assertTrue(ptOut.x == 6000 && ptOut.y == 1447); - } - } - - @Test - public void testInsertPoint() { - Polygon poly = new Polygon(); - poly.startPath(10, 1); - poly.lineTo(15, 20); - poly.lineTo(30, 14); - poly.lineTo(60, 144); - - poly.startPath(10, 1); - poly.lineTo(15, 20); - poly.lineTo(300, 14); - poly.lineTo(314, 217); - poly.lineTo(60, 144); - - poly.startPath(10, 1); - poly.lineTo(125, 20); - poly.lineTo(30, 14); - poly.lineTo(600, 144); - - Point pt = new Point(-33, -34); - poly.insertPoint(1, 1, pt); - - pt = poly.getPoint(4); - assertTrue(pt.getX() == 10 && pt.getY() == 1); - pt = poly.getPoint(5); - assertTrue(pt.getX() == -33 && pt.getY() == -34); - pt = poly.getPoint(6); - assertTrue(pt.getX() == 15 && pt.getY() == 20); - - assertTrue(poly.getPointCount() == 14); - assertTrue(poly.getPathSize(1) == 6); - assertTrue(poly.getPathSize(2) == 4); - assertTrue(poly.getPathCount() == 3); - } - - @Test - public void testRemovePoint() { - Polygon poly = new Polygon(); - poly.startPath(10, 1); - poly.lineTo(15, 20); - poly.lineTo(30, 14); - poly.lineTo(60, 144); - - poly.startPath(10, 1); - poly.lineTo(15, 20); - poly.lineTo(300, 14); - poly.lineTo(314, 217); - poly.lineTo(60, 144); - - poly.startPath(10, 1); - poly.lineTo(125, 20); - poly.lineTo(30, 14); - poly.lineTo(600, 144); - - poly.removePoint(1, 1); - - Point pt; - - pt = poly.getPoint(4); - assertTrue(pt.getX() == 10 && pt.getY() == 1); - pt = poly.getPoint(5); - assertTrue(pt.getX() == 300 && pt.getY() == 14); - - assertTrue(poly.getPointCount() == 12); - assertTrue(poly.getPathSize(0) == 4); - assertTrue(poly.getPathSize(1) == 4); - assertTrue(poly.getPathSize(2) == 4); - } - - @Test - public static void testPolygonAreaAndLength() { - Polygon poly; + @Override + protected void setUp() throws Exception { + super.setUp(); + } + + @Override + protected void tearDown() throws Exception { + super.tearDown(); + } + + @Test + public void testCreation() { + // simple create + + Polygon poly = new Polygon(); + @SuppressWarnings("unused") + int number = poly.getStateFlag(); + + assertTrue(poly != null); + // assertTrue(poly.getClass() == Polygon.class); + // assertFalse(poly.getClass() == Envelope.class); + + assertTrue(poly.getType() == Geometry.Type.Polygon); + assertTrue(poly.isEmpty()); + assertTrue(poly.getPointCount() == 0); + assertTrue(poly.getPathCount() == 0); + number = poly.getStateFlag(); + poly = null; + assertFalse(poly != null); + + // play with default attributes + @SuppressWarnings("unused") + Polygon poly2 = new Polygon(); + // SimpleTest(poly2); + + // creation1(); + // creation2(); + // addpath(); + // addpath2(); + // removepath(); + // reversepath(); + // reverseallpaths(); + // openallpaths(); + // openpath(); + // insertpath(); + // insertpoints(); + // insertpoint(); + // removepoint(); + // insertpointsfromaray(); + // createWithStreams(); + // testBug1(); + } + + @Test + public void testCreation1() { + // Simple area and length calcul test + Polygon poly = new Polygon(); + @SuppressWarnings("unused") + int number = poly.getStateFlag(); + Envelope env = new Envelope(1000, 2000, 1010, 2010); + env.toString(); + poly.addEnvelope(env, false); + poly.toString(); + number = poly.getStateFlag(); + assertTrue(Math.abs(poly.calculateArea2D() - 100) < 1e-12); + assertTrue(Math.abs(poly.calculateLength2D() - 40) < 1e-12); + poly.setEmpty(); + number = poly.getStateFlag(); + poly.addEnvelope(env, true); + number = poly.getStateFlag(); + assertTrue(Math.abs(poly.calculateArea2D() + 100) < 1e-12); + number = poly.getStateFlag(); + } + + @Test + public void testCreation2() { + Polygon poly = new Polygon(); + int state1 = poly.getStateFlag(); + poly.startPath(10, 1); + poly.lineTo(15, 20); + poly.lineTo(30, 14); + poly.lineTo(60, 144); + poly.closePathWithLine(); + int state2 = poly.getStateFlag(); + assertTrue(state2 == state1 + 1); + + // MultiPathImpl::Pointer mpImpl = + // (MultiPathImpl::Pointer)poly->_GetImpl(); + // + // assertTrue(mpImpl.getPointCount() == 4); + // assertTrue(mpImpl.getPathCount() == 1); + // AttributeStreamBase xy = + // mpImpl.getAttributeStreamRef(enum_value2(VertexDescription, + // Semantics, POSITION)); + // double x, y; + // x = xy.readAsDbl(2 * 2); + // y = xy.readAsDbl(2 * 2 + 1); + // assertTrue(x == 30); assertTrue(y == 14); + // + // AttributeStreamOfIndexType parts = mpImpl.getPathStreamRef(); + // assertTrue(parts.size() == 2); + // assertTrue(parts.read(0) == 0); + // assertTrue(parts.read(1) == 4); + // assertTrue(mpImpl.isClosedPath(0)); + // assertTrue(mpImpl.getSegmentFlagsStreamRef() == NULLPTR); + // assertTrue(mpImpl.getSegmentIndexStreamRef() == NULLPTR); + // assertTrue(mpImpl.getSegmentDataStreamRef() == NULLPTR); + + poly.startPath(20, 13); + poly.lineTo(150, 120); + poly.lineTo(300, 414); + poly.lineTo(610, 14); + poly.lineTo(6210, 140); + poly.closePathWithLine(); + + // assertTrue(mpImpl.getPointCount() == 9); + // assertTrue(mpImpl.getPathCount() == 2); + // assertTrue(mpImpl.isClosedPath(1)); + // xy = mpImpl.getAttributeStreamRef(enum_value2(VertexDescription, + // Semantics, POSITION)); + // x = xy.readAsDbl(2 * 3); + // y = xy.readAsDbl(2 * 3 + 1); + // assertTrue(x == 60); assertTrue(y == 144); + // + // x = xy.readAsDbl(2 * 6); + // y = xy.readAsDbl(2 * 6 + 1); + // assertTrue(x == 300); assertTrue(y == 414); + + // parts = mpImpl.getPathStreamRef(); + // assertTrue(parts.size() == 3); + // assertTrue(parts.read(0) == 0); + // assertTrue(parts.read(1) == 4); + // assertTrue(parts.read(2) == 9); + // assertTrue(mpImpl.getSegmentIndexStreamRef() == NULLPTR); + // assertTrue(mpImpl.getSegmentFlagsStreamRef() == NULLPTR); + // assertTrue(mpImpl.getSegmentDataStreamRef() == NULLPTR); + + poly.startPath(200, 1333); + poly.lineTo(1150, 1120); + poly.lineTo(300, 4114); + poly.lineTo(6110, 114); + poly.lineTo(61210, 1140); + + assertTrue(poly.isClosedPath(2) == true); + poly.closeAllPaths(); + assertTrue(poly.isClosedPath(2) == true); + + { + Polygon poly2 = new Polygon(); + poly2.startPath(10, 10); + poly2.lineTo(100, 10); + poly2.lineTo(100, 100); + poly2.lineTo(10, 100); + } + + { + Polygon poly3 = new Polygon(); + // create a star (non-simple) + poly3.startPath(1, 0); + poly3.lineTo(5, 10); + poly3.lineTo(9, 0); + poly3.lineTo(0, 6); + poly3.lineTo(10, 6); + } + } + + @Test + public void testCreateWithStreams() { + // Polygon poly = new Polygon(); + // poly.addAttribute((int)Semantics.M); + // try + // { + // OutputDebugString(L"Test an assert\n"); + // GeometryException::m_assertOnException = false; + // ((MultiPathImpl::Pointer)poly->_GetImpl()).getPathStreamRef(); + // } + // catch(GeometryException except) + // { + // assertTrue(except->empty_geometry); + // GeometryException::m_assertOnException = true; + // } + // try + // { + // OutputDebugString(L"Test an assert\n"); + // GeometryException::m_assertOnException = false; + // ((MultiPathImpl::Pointer)poly->_GetImpl()).getAttributeStreamRef(enum_value2(VertexDescription, + // Semantics, POSITION)); + // } + // catch(GeometryException except) + // { + // assertTrue(except->empty_geometry); + // GeometryException::m_assertOnException = true; + // } + // + // MultiPathImpl::Pointer mpImpl = + // (MultiPathImpl::Pointer)poly->_GetImpl(); + // + // AttributeStreamOfIndexType parts = + // (AttributeStreamOfIndexType)AttributeStreamBase::CreateIndexStream(3); + // mpImpl.setPathStreamRef(parts); + // + // parts.write(0, 0); //first element is always 0 + // parts.write(1, 4); //second element is the index of the first vertex + // of the second part + // parts.write(2, 8); //the third element is the total point count. + // + // AttributeStreamOfInt8 flags = + // (AttributeStreamOfInt8)AttributeStreamBase::CreateByteStream(3); + // mpImpl.setPathFlagsStreamRef(flags); + // flags.write(0, enum_value1(PathFlags, enumClosed)); + // flags.write(1, enum_value1(PathFlags, enumClosed)); + // flags.write(2, 0); + // + // AttributeStreamOfDbl xy = + // (AttributeStreamOfDbl)AttributeStreamBase::CreateDoubleStream(16); + // //16 doubles means 8 points + // mpImpl.setAttributeStreamRef(enum_value2(VertexDescription, + // Semantics, POSITION), xy); + // + // Envelope2D env; + // env.SetCoords(-1000, -2000, 1000, 2000); + // Point2D buf[4]; + // env.QueryCorners(buf); + // xy.writeRange(0, 8, (double*)buf, 0, true); + // + // env.SetCoords(-100, -200, 100, 200); + // env.QueryCornersReversed(buf); //make a hole by quering reversed + // order + // xy.writeRange(8, 8, (double*)buf, 0, true); + // + // mpImpl.notifyModified(MultiVertexGeometryImpl::DirtyAll); //notify + // the path that the vertices had changed. + // + // assertTrue(poly.getPointCount() == 8); + // assertTrue(poly.getPathCount() == 2); + // assertTrue(poly.getPathSize(1) == 4); + // assertTrue(poly.isClosedPath(0)); + // assertTrue(poly.isClosedPath(1)); + } + + @Test + public void testCloneStuff() { + Polygon poly = new Polygon(); + poly.startPath(10, 1); + poly.lineTo(15, 20); + poly.lineTo(30, 14); + poly.lineTo(60, 144); + + poly.startPath(10, 1); + poly.lineTo(15, 20); + poly.lineTo(300, 14); + poly.lineTo(60, 144); + + poly.startPath(10, 1); + poly.lineTo(125, 20); + poly.lineTo(30, 14); + poly.lineTo(600, 144); + poly.closePathWithLine(); + + Polygon clone = (Polygon) poly.copy(); + assertTrue(clone.getPathCount() == 3); + assertTrue(clone.getPathStart(2) == 8); + assertTrue(clone.isClosedPath(0)); + assertTrue(clone.isClosedPath(1)); + assertTrue(clone.isClosedPath(2)); + assertTrue(clone.getXY(5).isEqual(new Point2D(15, 20))); + } + + @Test + public void testCloneStuffEnvelope() { + Envelope env = new Envelope(11, 12, 15, 24); + Envelope eCopy = (Envelope) env.copy(); + assertTrue(eCopy.equals(env)); + assertTrue(eCopy.getXMin() == 11); + assertTrue(eCopy.getYMin() == 12); + assertTrue(eCopy.getXMax() == 15); + assertTrue(eCopy.getYMax() == 24); + } + + @Test + public void testCloneStuffPolyline() { + Polyline poly = new Polyline(); + poly.startPath(10, 1); + poly.lineTo(15, 20); + poly.lineTo(30, 14); + poly.lineTo(60, 144); + + poly.startPath(10, 1); + poly.lineTo(15, 20); + poly.lineTo(300, 14); + poly.lineTo(60, 144); + + poly.startPath(10, 1); + poly.lineTo(125, 20); + poly.lineTo(30, 14); + poly.lineTo(600, 144); + poly.closePathWithLine(); + + Polyline clone = (Polyline) poly.copy(); + assertTrue(clone.getPathCount() == 3); + assertTrue(clone.getPathStart(2) == 8); + assertTrue(!clone.isClosedPath(0)); + assertTrue(!clone.isClosedPath(1)); + assertTrue(clone.isClosedPath(2)); + assertTrue(clone.getXY(5).isEqual(new Point2D(15, 20))); + } + + @Test + public void testAddpath() { + Polygon poly = new Polygon(); + poly.startPath(10, 1); + poly.lineTo(15, 20); + poly.lineTo(30, 14); + poly.lineTo(60, 144); + + poly.startPath(10, 1); + poly.lineTo(15, 20); + poly.lineTo(300, 14); + poly.lineTo(60, 144); + + poly.startPath(10, 1); + poly.lineTo(125, 20); + poly.lineTo(30, 14); + poly.lineTo(600, 144); + + Polygon poly1 = new Polygon(); + poly1.addPath(poly, 2, true); + poly1.addPath(poly, 0, true); + + assertTrue(poly1.getPathCount() == 2); + assertTrue(poly1.getPathStart(1) == 4); + assertTrue(poly1.isClosedPath(0)); + assertTrue(poly1.isClosedPath(1)); + Point ptOut = poly1.getPoint(6); + assertTrue(ptOut.getX() == 30 && ptOut.getY() == 14); + } + + @Test + public void testAddpath2() { + Polygon polygon = new Polygon(); + polygon.startPath(-179, 34); + polygon.lineTo(-154, 34); + polygon.lineTo(-179, 36); + polygon.lineTo(-180, 90); + polygon.lineTo(180, 90); + polygon.lineTo(180, 36); + polygon.lineTo(70, 46); + polygon.lineTo(-76, 80); + polygon.lineTo(12, 38); + polygon.lineTo(-69, 51); + polygon.lineTo(-95, 29); + polygon.lineTo(-105, 7); + polygon.lineTo(-112, -27); + polygon.lineTo(-149, -11); + polygon.lineTo(-149, -11); + polygon.lineTo(-166, -4); + polygon.lineTo(-179, 5); + + Polyline polyline = new Polyline(); + polyline.startPath(180, 5); + polyline.lineTo(140, 34); + polyline.lineTo(180, 34); + + polygon.addPath(polyline, 0, true); + + Point startpoint = polygon.getPoint(17); + assertTrue(startpoint.getX() == 180 && startpoint.getY() == 5); + } + + @Test + public void testRemovepath() { + Polygon poly = new Polygon(); + poly.startPath(10, 1); + poly.lineTo(15, 20); + poly.lineTo(30, 14); + poly.lineTo(60, 144); + + poly.startPath(10, 1); + poly.lineTo(15, 20); + poly.lineTo(300, 14); + poly.lineTo(60, 144); + + poly.startPath(10, 1); + poly.lineTo(125, 20); + poly.lineTo(30, 14); + poly.lineTo(600, 144); + + // poly->SetAttribute(enum_value2(VertexDescription, Semantics, Z), 0, + // 0, 2); + // poly->SetAttribute(enum_value2(VertexDescription, Semantics, Z), 1, + // 0, 3); + // poly->SetAttribute(enum_value2(VertexDescription, Semantics, Z), 2, + // 0, 5); + // poly->SetAttribute(enum_value2(VertexDescription, Semantics, Z), 3, + // 0, 7); + // poly->SetAttribute(enum_value2(VertexDescription, Semantics, Z), 4, + // 0, 11); + // poly->SetAttribute(enum_value2(VertexDescription, Semantics, Z), 5, + // 0, 13); + // poly->SetAttribute(enum_value2(VertexDescription, Semantics, Z), 6, + // 0, 17); + // poly->SetAttribute(enum_value2(VertexDescription, Semantics, Z), 7, + // 0, 19); + // poly->SetAttribute(enum_value2(VertexDescription, Semantics, Z), 8, + // 0, 23); + // poly->SetAttribute(enum_value2(VertexDescription, Semantics, Z), 9, + // 0, 29); + // poly->SetAttribute(enum_value2(VertexDescription, Semantics, Z), 10, + // 0, 31); + // poly->SetAttribute(enum_value2(VertexDescription, Semantics, Z), 11, + // 0, 37); + + poly.removePath(1); + + assertTrue(poly.getPathCount() == 2); + assertTrue(poly.getPathStart(1) == 4); + assertTrue(poly.isClosedPath(0)); + assertTrue(poly.isClosedPath(1)); + Point ptOut = poly.getPoint(4); + assertTrue(ptOut.getX() == 10 && ptOut.getY() == 1); + poly.removePath(0); + poly.removePath(0); + assertTrue(poly.getPathCount() == 0); + + Polygon poly2 = new Polygon(); + poly2.startPath(0, 0); + poly2.lineTo(0, 10); + poly2.lineTo(10, 10); + poly2.startPath(1, 1); + poly2.lineTo(2, 2); + poly2.removePath(0); + // poly2->StartPath(0, 0); + poly2.lineTo(0, 10); + poly2.lineTo(10, 10); + + // Polygon polygon2 = new Polygon(); + // polygon2.addPath(poly, -1, true); + // polygon2.addPath(poly, -1, true); + // polygon2.addPath(poly, -1, true); + // assertTrue(polygon2.getPathCount() == 3); + // polygon2.removePath(0); + // polygon2.removePath(0); + // polygon2.removePath(0); + // assertTrue(polygon2.getPathCount() == 0); + // polygon2.addPath(poly, -1, true); + + // Point point1 = new Point(); + // Point point2 = new Point(); + // point1.setX(0); + // point1.setY(0); + // point2.setX(0); + // point2.setY(0); + // polygon2.addPath(poly2, 0, true); + // polygon2.removePath(0); + // polygon2.insertPoint(0, 0, point1); + // polygon2.insertPoint(0, 0, point2); + // assertTrue(polygon2.getPathCount() == 1); + // assertTrue(polygon2.getPointCount() == 2); + + Polygon polygon3 = new Polygon(); + polygon3.startPath(0, 0); + polygon3.lineTo(0, 10); + polygon3.lineTo(10, 10); + double area = polygon3.calculateArea2D(); + polygon3.removePath(0); + + polygon3.startPath(0, 0); + polygon3.lineTo(0, 10); + polygon3.lineTo(10, 10); + area = polygon3.calculateArea2D(); + assertTrue(area > 0.0); + } + + @Test + public void testReversepath() { + Polygon poly = new Polygon(); + poly.startPath(10, 1); + poly.lineTo(15, 20); + poly.lineTo(30, 14); + poly.lineTo(60, 144); + + poly.startPath(10, 1); + poly.lineTo(15, 20); + poly.lineTo(300, 14); + poly.lineTo(60, 144); + + poly.startPath(10, 1); + poly.lineTo(125, 20); + poly.lineTo(30, 14); + poly.lineTo(600, 144); + + // poly.setAttribute(enum_value2(VertexDescription, Semantics, Z), 0, 0, + // 2); + // poly.setAttribute(enum_value2(VertexDescription, Semantics, Z), 1, 0, + // 3); + // poly.setAttribute(enum_value2(VertexDescription, Semantics, Z), 2, 0, + // 5); + // poly.setAttribute(enum_value2(VertexDescription, Semantics, Z), 3, 0, + // 7); + // poly.setAttribute(enum_value2(VertexDescription, Semantics, Z), 4, 0, + // 11); + // poly.setAttribute(enum_value2(VertexDescription, Semantics, Z), 5, 0, + // 13); + // poly.setAttribute(enum_value2(VertexDescription, Semantics, Z), 6, 0, + // 17); + // poly.setAttribute(enum_value2(VertexDescription, Semantics, Z), 7, 0, + // 19); + // poly.setAttribute(enum_value2(VertexDescription, Semantics, Z), 8, 0, + // 23); + // poly.setAttribute(enum_value2(VertexDescription, Semantics, Z), 9, 0, + // 29); + // poly.setAttribute(enum_value2(VertexDescription, Semantics, Z), 10, + // 0, 31); + // poly.setAttribute(enum_value2(VertexDescription, Semantics, Z), 11, + // 0, 37); + + poly.reversePath(1); + + assertTrue(poly.getPathCount() == 3); + assertTrue(poly.getPathStart(1) == 4); + assertTrue(poly.isClosedPath(0)); + assertTrue(poly.isClosedPath(1)); + Point ptOut = poly.getPoint(4); + assertTrue(ptOut.getX() == 10 && ptOut.getY() == 1); + } + + @Test + public void testReverseAllPaths() { + Polygon poly = new Polygon(); + poly.startPath(10, 1); + poly.lineTo(15, 20); + poly.lineTo(30, 14); + poly.lineTo(60, 144); + + poly.startPath(10, 1); + poly.lineTo(15, 20); + poly.lineTo(300, 14); + poly.lineTo(60, 144); + + poly.startPath(10, 1); + poly.lineTo(125, 20); + poly.lineTo(30, 14); + poly.lineTo(600, 144); + + // poly.setAttribute(enum_value2(VertexDescription, Semantics, Z), 0, 0, + // 2); + // poly.setAttribute(enum_value2(VertexDescription, Semantics, Z), 1, 0, + // 3); + // poly.setAttribute(enum_value2(VertexDescription, Semantics, Z), 2, 0, + // 5); + // poly.setAttribute(enum_value2(VertexDescription, Semantics, Z), 3, 0, + // 7); + // + // poly.setAttribute(enum_value2(VertexDescription, Semantics, Z), 4, 0, + // 11); + // poly.setAttribute(enum_value2(VertexDescription, Semantics, Z), 5, 0, + // 13); + // poly.setAttribute(enum_value2(VertexDescription, Semantics, Z), 6, 0, + // 17); + // poly.setAttribute(enum_value2(VertexDescription, Semantics, Z), 7, 0, + // 19); + // + // poly.setAttribute(enum_value2(VertexDescription, Semantics, Z), 8, 0, + // 23); + // poly.setAttribute(enum_value2(VertexDescription, Semantics, Z), 9, 0, + // 29); + // poly.setAttribute(enum_value2(VertexDescription, Semantics, Z), 10, + // 0, 31); + // poly.setAttribute(enum_value2(VertexDescription, Semantics, Z), 11, + // 0, 37); + + double area = poly.calculateArea2D(); + poly.reverseAllPaths(); + double areaReversed = poly.calculateArea2D(); + assertTrue(Math.abs(area + areaReversed) <= 0.001); + } + + @Test + public void testOpenAllPaths() { + Polyline poly = new Polyline(); + poly.startPath(10, 1); + poly.lineTo(15, 20); + poly.lineTo(30, 14); + poly.lineTo(60, 144); + poly.closePathWithLine(); + + poly.startPath(10, 1); + poly.lineTo(15, 20); + poly.lineTo(300, 14); + poly.lineTo(60, 144); + poly.closePathWithLine(); + + poly.startPath(10, 1); + poly.lineTo(125, 20); + poly.lineTo(30, 14); + poly.lineTo(600, 144); + poly.closePathWithLine(); + + // poly.setAttribute(enum_value2(VertexDescription, Semantics, Z), 0, 0, + // 2); + // poly.setAttribute(enum_value2(VertexDescription, Semantics, Z), 1, 0, + // 3); + // poly.setAttribute(enum_value2(VertexDescription, Semantics, Z), 2, 0, + // 5); + // poly.setAttribute(enum_value2(VertexDescription, Semantics, Z), 3, 0, + // 7); + // poly.setAttribute(enum_value2(VertexDescription, Semantics, Z), 4, 0, + // 11); + // poly.setAttribute(enum_value2(VertexDescription, Semantics, Z), 5, 0, + // 13); + // poly.setAttribute(enum_value2(VertexDescription, Semantics, Z), 6, 0, + // 17); + // poly.setAttribute(enum_value2(VertexDescription, Semantics, Z), 7, 0, + // 19); + // poly.setAttribute(enum_value2(VertexDescription, Semantics, Z), 8, 0, + // 23); + // poly.setAttribute(enum_value2(VertexDescription, Semantics, Z), 9, 0, + // 29); + // poly.setAttribute(enum_value2(VertexDescription, Semantics, Z), 10, + // 0, 31); + // poly.setAttribute(enum_value2(VertexDescription, Semantics, Z), 11, + // 0, 37); + + // MultiPathImpl::Pointer mpImpl = + // (MultiPathImpl::Pointer)poly->_GetImpl(); + // poly.openAllPathsAndDuplicateStartVertex(); + + // assertTrue(poly.getPathCount() == 3); + // assertTrue(poly.getPathStart(0) == 0); + // assertTrue(poly.getPathStart(1) == 5); + // assertTrue(poly.getPathStart(2) == 10); + // assertTrue(poly.getPointCount() == 15); + // Point ptstart = poly.getPoint(0); + // Point ptend = poly.getPoint(4); + // assertTrue(ptstart.getX() == ptend.getX() && ptstart.getY() == + // ptend.getY()); + // ptstart = poly.getPoint(5); + // ptend = poly.getPoint(9); + // assertTrue(ptstart.getX() == ptend.getX() && ptstart.getY() == + // ptend.getY()); + // ptstart = poly.getPoint(10); + // ptend = poly.getPoint(14); + // assertTrue(ptstart.getX() == ptend.getX() && ptstart.getY() == + // ptend.getY()); + } + + @Test + public void testOpenPath() { + Polyline poly = new Polyline(); + poly.startPath(10, 1); + poly.lineTo(15, 20); + poly.lineTo(30, 14); + poly.lineTo(60, 144); + poly.closePathWithLine(); + + poly.startPath(10, 1); + poly.lineTo(15, 20); + poly.lineTo(300, 14); + poly.lineTo(60, 144); + poly.closePathWithLine(); + + poly.startPath(10, 1); + poly.lineTo(125, 20); + poly.lineTo(30, 14); + poly.lineTo(600, 144); + poly.closePathWithLine(); + + // poly.setAttribute(enum_value2(VertexDescription, Semantics, Z), 0, 0, + // 2); + // poly.setAttribute(enum_value2(VertexDescription, Semantics, Z), 1, 0, + // 3); + // poly.setAttribute(enum_value2(VertexDescription, Semantics, Z), 2, 0, + // 5); + // poly.setAttribute(enum_value2(VertexDescription, Semantics, Z), 3, 0, + // 7); + // poly.setAttribute(enum_value2(VertexDescription, Semantics, Z), 4, 0, + // 11); + // poly.setAttribute(enum_value2(VertexDescription, Semantics, Z), 5, 0, + // 13); + // poly.setAttribute(enum_value2(VertexDescription, Semantics, Z), 6, 0, + // 17); + // poly.setAttribute(enum_value2(VertexDescription, Semantics, Z), 7, 0, + // 19); + // poly.setAttribute(enum_value2(VertexDescription, Semantics, Z), 8, 0, + // 23); + // poly.setAttribute(enum_value2(VertexDescription, Semantics, Z), 9, 0, + // 29); + // poly.setAttribute(enum_value2(VertexDescription, Semantics, Z), 10, + // 0, 31); + // poly.setAttribute(enum_value2(VertexDescription, Semantics, Z), 11, + // 0, 37); + // + // MultiPathImpl::Pointer mpImpl = + // (MultiPathImpl::Pointer)poly->_GetImpl(); + // poly.openPathAndDuplicateStartVertex(1); + + // assertTrue(poly.getPathCount() == 3); + // assertTrue(poly.getPathStart(0) == 0); + // assertTrue(poly.getPathStart(1) == 4); + // assertTrue(poly.getPathStart(2) == 9); + // assertTrue(poly.getPointCount() == 13); + // Point ptstart = poly.getPoint(4); + // Point ptend = poly.getPoint(8); + // assertTrue(ptstart.getX() == ptend.getX() && ptstart.getY() == + // ptend.getY()); + // ptstart = poly.getPoint(9); + // assertTrue(ptstart.getX() == 10 && ptstart.getY() == 1); + } + + @Test + public void testInsertPath() { + Polygon poly = new Polygon(); + poly.startPath(10, 1); + poly.lineTo(15, 20); + poly.lineTo(30, 14); + poly.lineTo(60, 144); + + poly.startPath(12, 2); + poly.lineTo(16, 21); + poly.lineTo(301, 15); + poly.lineTo(61, 145); + + poly.startPath(13, 3); + poly.lineTo(126, 22); + poly.lineTo(31, 16); + poly.lineTo(601, 146); + + // poly.setAttribute(enum_value2(VertexDescription, Semantics, Z), 0, 0, + // 2); + // poly.setAttribute(enum_value2(VertexDescription, Semantics, Z), 1, 0, + // 3); + // poly.setAttribute(enum_value2(VertexDescription, Semantics, Z), 2, 0, + // 5); + // poly.setAttribute(enum_value2(VertexDescription, Semantics, Z), 3, 0, + // 7); + // poly.setAttribute(enum_value2(VertexDescription, Semantics, Z), 4, 0, + // 11); + // poly.setAttribute(enum_value2(VertexDescription, Semantics, Z), 5, 0, + // 13); + // poly.setAttribute(enum_value2(VertexDescription, Semantics, Z), 6, 0, + // 17); + // poly.setAttribute(enum_value2(VertexDescription, Semantics, Z), 7, 0, + // 19); + // poly.setAttribute(enum_value2(VertexDescription, Semantics, Z), 8, 0, + // 23); + // poly.setAttribute(enum_value2(VertexDescription, Semantics, Z), 9, 0, + // 29); + // poly.setAttribute(enum_value2(VertexDescription, Semantics, Z), 10, + // 0, 31); + // poly.setAttribute(enum_value2(VertexDescription, Semantics, Z), 11, + // 0, 37); + + Polygon poly2 = new Polygon(); + poly2.startPath(12, 2); + poly2.lineTo(16, 21); + poly2.lineTo(301, 15); + poly2.lineTo(61, 145); + + poly.insertPath(0, poly2, 0, false); + + assertTrue(poly.getPathCount() == 4); + assertTrue(poly.getPathStart(0) == 0); + assertTrue(poly.getPathStart(1) == 4); + assertTrue(poly.getPathStart(2) == 8); + assertTrue(poly.getPathStart(3) == 12); + assertTrue(poly.getPointCount() == 16); + + Point2D pt0 = poly.getXY(0); + assertTrue(pt0.x == 12 && pt0.y == 2); + Point2D pt1 = poly.getXY(1); + assertTrue(pt1.x == 61 && pt1.y == 145); + Point2D pt2 = poly.getXY(2); + assertTrue(pt2.x == 301 && pt2.y == 15); + Point2D pt3 = poly.getXY(3); + assertTrue(pt3.x == 16 && pt3.y == 21); + + Point pt2d = new Point(-27, -27); + + poly.insertPoint(1, 0, pt2d); + assertTrue(poly.getPathCount() == 4); + assertTrue(poly.getPathStart(0) == 0); + assertTrue(poly.getPathStart(1) == 4); + assertTrue(poly.getPathStart(2) == 9); + assertTrue(poly.getPathStart(3) == 13); + assertTrue(poly.getPointCount() == 17); + } + + @Test + public void testInsertPoints() { + {// forward insertion + Polygon poly = new Polygon(); + poly.startPath(10, 1); + poly.lineTo(15, 20); + poly.lineTo(30, 14); + poly.lineTo(60, 144); + + poly.startPath(10, 1); + poly.lineTo(15, 20); + poly.lineTo(300, 14); + poly.lineTo(314, 217); + poly.lineTo(60, 144); + + poly.startPath(10, 1); + poly.lineTo(125, 20); + poly.lineTo(30, 14); + poly.lineTo(600, 144); + + Polygon poly1 = new Polygon(); + poly1.startPath(1, 17); + poly1.lineTo(1, 207); + poly1.lineTo(3, 147); + poly1.lineTo(6, 1447); + + poly1.startPath(1000, 17); + poly1.lineTo(1250, 207); + poly1.lineTo(300, 147); + poly1.lineTo(6000, 1447); + + poly1.insertPoints(1, 2, poly, 1, 1, 3, true);// forward + + assertTrue(poly1.getPathCount() == 2); + assertTrue(poly1.getPathStart(1) == 4); + assertTrue(poly1.isClosedPath(0)); + assertTrue(poly1.isClosedPath(1)); + assertTrue(poly1.getPointCount() == 11); + assertTrue(poly1.getPathSize(1) == 7); + // Point2D ptOut; + // ptOut = poly1.getXY(5); + // assertTrue(ptOut.x == 1250 && ptOut.y == 207); + // ptOut = poly1.getXY(6); + // assertTrue(ptOut.x == 15 && ptOut.y == 20); + // ptOut = poly1.getXY(7); + // assertTrue(ptOut.x == 300 && ptOut.y == 14); + // ptOut = poly1.getXY(8); + // assertTrue(ptOut.x == 314 && ptOut.y == 217); + // ptOut = poly1.getXY(9); + // assertTrue(ptOut.x == 300 && ptOut.y == 147); + // ptOut = poly1.getXY(10); + // assertTrue(ptOut.x == 6000 && ptOut.y == 1447); + } + + {// reverse insertion + Polygon poly = new Polygon(); + poly.startPath(10, 1); + poly.lineTo(15, 20); + poly.lineTo(30, 14); + poly.lineTo(60, 144); + + poly.startPath(10, 1); + poly.lineTo(15, 20); + poly.lineTo(300, 14); + poly.lineTo(314, 217); + poly.lineTo(60, 144); + + poly.startPath(10, 1); + poly.lineTo(125, 20); + poly.lineTo(30, 14); + poly.lineTo(600, 144); + + Polygon poly1 = new Polygon(); + poly1.startPath(1, 17); + poly1.lineTo(1, 207); + poly1.lineTo(3, 147); + poly1.lineTo(6, 1447); + + poly1.startPath(1000, 17); + poly1.lineTo(1250, 207); + poly1.lineTo(300, 147); + poly1.lineTo(6000, 1447); + + poly1.insertPoints(1, 2, poly, 1, 1, 3, false);// reverse + + assertTrue(poly1.getPathCount() == 2); + assertTrue(poly1.getPathStart(1) == 4); + assertTrue(poly1.isClosedPath(0)); + assertTrue(poly1.isClosedPath(1)); + assertTrue(poly1.getPointCount() == 11); + assertTrue(poly1.getPathSize(1) == 7); + // Point2D ptOut; + // ptOut = poly1.getXY(5); + // assertTrue(ptOut.x == 1250 && ptOut.y == 207); + // ptOut = poly1.getXY(6); + // assertTrue(ptOut.x == 314 && ptOut.y == 217); + // ptOut = poly1.getXY(7); + // assertTrue(ptOut.x == 300 && ptOut.y == 14); + // ptOut = poly1.getXY(8); + // assertTrue(ptOut.x == 15 && ptOut.y == 20); + // ptOut = poly1.getXY(9); + // assertTrue(ptOut.x == 300 && ptOut.y == 147); + // ptOut = poly1.getXY(10); + // assertTrue(ptOut.x == 6000 && ptOut.y == 1447); + } + } + + @Test + public void testInsertPoint() { + Polygon poly = new Polygon(); + poly.startPath(10, 1); + poly.lineTo(15, 20); + poly.lineTo(30, 14); + poly.lineTo(60, 144); + + poly.startPath(10, 1); + poly.lineTo(15, 20); + poly.lineTo(300, 14); + poly.lineTo(314, 217); + poly.lineTo(60, 144); + + poly.startPath(10, 1); + poly.lineTo(125, 20); + poly.lineTo(30, 14); + poly.lineTo(600, 144); + + Point pt = new Point(-33, -34); + poly.insertPoint(1, 1, pt); + + pt = poly.getPoint(4); + assertTrue(pt.getX() == 10 && pt.getY() == 1); + pt = poly.getPoint(5); + assertTrue(pt.getX() == -33 && pt.getY() == -34); + pt = poly.getPoint(6); + assertTrue(pt.getX() == 15 && pt.getY() == 20); + + assertTrue(poly.getPointCount() == 14); + assertTrue(poly.getPathSize(1) == 6); + assertTrue(poly.getPathSize(2) == 4); + assertTrue(poly.getPathCount() == 3); + } + + @Test + public void testRemovePoint() { + Polygon poly = new Polygon(); + poly.startPath(10, 1); + poly.lineTo(15, 20); + poly.lineTo(30, 14); + poly.lineTo(60, 144); + + poly.startPath(10, 1); + poly.lineTo(15, 20); + poly.lineTo(300, 14); + poly.lineTo(314, 217); + poly.lineTo(60, 144); + + poly.startPath(10, 1); + poly.lineTo(125, 20); + poly.lineTo(30, 14); + poly.lineTo(600, 144); + + poly.removePoint(1, 1); + + Point pt; + + pt = poly.getPoint(4); + assertTrue(pt.getX() == 10 && pt.getY() == 1); + pt = poly.getPoint(5); + assertTrue(pt.getX() == 300 && pt.getY() == 14); + + assertTrue(poly.getPointCount() == 12); + assertTrue(poly.getPathSize(0) == 4); + assertTrue(poly.getPathSize(1) == 4); + assertTrue(poly.getPathSize(2) == 4); + } + + @Test + public static void testPolygonAreaAndLength() { + Polygon poly; /* const */ - double r = 1.0; - /* const */ - double epsilon = 1.0e-14; + double r = 1.0; /* const */ - int nMax = 40; - - // If r == 1.0 and nMax == 40 and epsilon == 1.0e-14, it will pass. - // But if r == 1.0 and nMax == 40 and epsilon == 1.0e-15, it will fail. - - for (int n = 3; n < nMax; n++) { - // regular polygon with n vertices and length from center to vertex - // = r - poly = new Polygon(); - double theta = 0.0; - poly.startPath(r, 0.0); - for (int k = 1; k <= n; k++) { - theta -= 2 * Math.PI / n; - poly.lineTo(r * Math.cos(theta), r * Math.sin(theta)); - } - double sinPiOverN = Math.sin(Math.PI / n); - double sinTwoPiOverN = Math.sin(2.0 * Math.PI / n); - double analyticalLength = 2.0 * n * r * sinPiOverN; - double analyticalArea = 0.5 * n * r * r * sinTwoPiOverN; - double calculatedLength = poly.calculateLength2D(); - double calculatedArea = poly.calculateArea2D(); - assertTrue(Math.abs(analyticalLength - calculatedLength) < epsilon); - assertTrue(Math.abs(analyticalArea - calculatedArea) < epsilon); - } - } - - @Test - public void testInsertPointsFromArray() { - {// Test forward insertion of an array of Point2D - // ArrayOf(Point2D) arr = new ArrayOf(Point2D)(5); - // arr[0].SetCoords(10, 1); - // arr[1].SetCoords(15, 20); - // arr[2].SetCoords(300, 14); - // arr[3].SetCoords(314, 217); - // arr[4].SetCoords(60, 144); - - Polygon poly1 = new Polygon(); - poly1.startPath(1, 17); - poly1.lineTo(1, 207); - poly1.lineTo(3, 147); - poly1.lineTo(6, 1447); - - poly1.startPath(1000, 17); - poly1.lineTo(1250, 207); - poly1.lineTo(300, 147); - poly1.lineTo(6000, 1447); - - assertTrue(poly1.getPathCount() == 2); - assertTrue(poly1.getPathStart(1) == 4); - assertTrue(poly1.isClosedPath(0)); - assertTrue(poly1.isClosedPath(1)); - } - - {// Test reversed insertion of an array of Point2D - } - } - - @Test - public void testCR177477() { - Polygon pg = new Polygon(); - pg.startPath(-130, 40); - pg.lineTo(-70, 40); - pg.lineTo(-70, 10); - pg.lineTo(-130, 10); - - Polygon pg2 = new Polygon(); - pg2.startPath(-60, 40); - pg2.lineTo(-50, 40); - pg2.lineTo(-50, 10); - pg2.lineTo(-60, 10); - - pg.add(pg2, false); - } - - @Test - public void testCR177477getPathEnd() { - Polygon pg = new Polygon(); - pg.startPath(-130, 40); - pg.lineTo(-70, 40); - pg.lineTo(-70, 10); - pg.lineTo(-130, 10); - - pg.startPath(-60, 40); - pg.lineTo(-50, 40); - pg.lineTo(-50, 10); - pg.lineTo(-60, 10); - - pg.startPath(-40, 40); - pg.lineTo(-30, 40); - pg.lineTo(-30, 10); - pg.lineTo(-40, 10); - - int pathCount = pg.getPathCount(); - assertTrue(pathCount == 3); - - // int startIndex = pg.getPathStart(pathCount - 1); - - // int endIndex = pg.getPathEnd(pathCount - 1); - - Line line = new Line(); - line.toString(); - - line.setStart(new Point(0, 0)); - line.setEnd(new Point(1, 0)); - - line.toString(); - - double geoLength = GeometryEngine.geodesicDistanceOnWGS84(new Point(0, - 0), new Point(1, 0)); - assertTrue(Math.abs(geoLength - 111319) < 1); - } - - @Test - public void testBug1() { - Polygon pg = new Polygon(); - pg.startPath(-130, 40); - for (int i = 0; i < 1000; i++) - pg.lineTo(-70, 40); - for (int i = 0; i < 999; i++) - pg.removePoint(0, pg.getPointCount() - 1); - - pg.lineTo(-70, 40); - } - - @Test - public void testGeometryCopy() { - boolean noException = true; - - Polyline polyline = new Polyline(); - - Point p1 = new Point(-85.59285621496956, 38.26004727491098); - Point p2 = new Point(-85.56417866635002, 38.28084064314639); - Point p3 = new Point(-85.56845156650877, 38.24659881865461); - Point p4 = new Point(-85.55341069949853, 38.26671513050464); - - polyline.startPath(p1); - try { - polyline.lineTo(p2); - polyline.copy(); - polyline.lineTo(p3); - polyline.copy(); - polyline.lineTo(p4); // exception thrown here!!! - - } catch (Exception e) { - e.printStackTrace(); - noException = false; - } - - assertTrue(noException); - }// end of method - - @Test - public void testBoundary() { - Geometry g = OperatorImportFromWkt - .local() - .execute( - 0, - Geometry.Type.Unknown, - "POLYGON((-10 -10, 10 -10, 10 10, -10 10, -10 -10), (-5 -5, -5 5, 5 5, 5 -5, -5 -5))", - null); - - Geometry boundary = OperatorBoundary.local().execute(g, null); - Polyline polyline = (Polyline) boundary; - polyline.reverseAllPaths(); - String s = OperatorExportToWkt.local().execute(0, boundary, null); - assertTrue(s - .equals("MULTILINESTRING ((-10 -10, 10 -10, 10 10, -10 10, -10 -10), (-5 -5, -5 5, 5 5, 5 -5, -5 -5))")); - } - - @Test - public void testReplaceNaNs() { - { - MultiPoint mp = new MultiPoint(); - Point pt = new Point(); - pt.setXY(1, 2); - pt.setZ(Double.NaN); - mp.add(pt); - pt = new Point(); - pt.setXY(11, 12); - pt.setZ(3); - mp.add(pt); - - mp.replaceNaNs(VertexDescription.Semantics.Z, 5); - assertTrue(mp.getPoint(0).equals(new Point(1, 2, 5))); - assertTrue(mp.getPoint(1).equals(new Point(11, 12, 3))); - } - - { - Polygon mp = new Polygon(); - Point pt = new Point(); - pt.setXY(1, 2); - pt.setZ(Double.NaN); - mp.startPath(pt); - pt = new Point(); - pt.setXY(11, 12); - pt.setZ(3); - mp.lineTo(pt); - - mp.replaceNaNs(VertexDescription.Semantics.Z, 5); - assertTrue(mp.getPoint(0).equals(new Point(1, 2, 5))); - assertTrue(mp.getPoint(1).equals(new Point(11, 12, 3))); - } - - { - Polygon mp = new Polygon(); - Point pt1 = new Point(); - pt1.setXY(1, 2); - pt1.setM(Double.NaN); - mp.startPath(pt1); - Point pt2 = new Point(); - pt2.setXY(11, 12); - pt2.setM(3); - mp.lineTo(pt2); - - mp.replaceNaNs(VertexDescription.Semantics.M, 5); - Point p = new Point(1, 2); - p.setM(5); - boolean b = mp.getPoint(0).equals(p); - assertTrue(b); - p = new Point(11, 12); - p.setM(3); - b = mp.getPoint(1).equals(p); - assertTrue(b); - } - - } - - @Test - public void testPolygon2PolygonFails() { - OperatorFactoryLocal factory = OperatorFactoryLocal.getInstance(); - OperatorExportToGeoJson exporter = (OperatorExportToGeoJson) factory - .getOperator(Operator.Type.ExportToGeoJson); - String result = exporter.execute(birmingham()); - - OperatorImportFromGeoJson importer = (OperatorImportFromGeoJson) factory - .getOperator(Operator.Type.ImportFromGeoJson); - MapGeometry mapGeometry = importer.execute( - GeoJsonImportFlags.geoJsonImportDefaults, - Geometry.Type.Polygon, result, null); - Polygon polygon = (Polygon) mapGeometry.getGeometry(); - assertEquals(birmingham(), polygon); - } - - @Test - public void testPolygon2PolygonFails2() { - String birminghamGeojson = GeometryEngine - .geometryToGeoJson(birmingham()); - MapGeometry returnedGeometry = GeometryEngine.geoJsonToGeometry( - birminghamGeojson, GeoJsonImportFlags.geoJsonImportDefaults, - Geometry.Type.Polygon); - Polygon polygon = (Polygon) returnedGeometry.getGeometry(); - assertEquals(polygon, birmingham()); - } - - @Test - public void testPolygon2PolygonWorks() { - String birminghamGeojson = GeometryEngine - .geometryToGeoJson(birmingham()); - MapGeometry returnedGeometry = GeometryEngine.geoJsonToGeometry( - birminghamGeojson, GeoJsonImportFlags.geoJsonImportDefaults, - Geometry.Type.Polygon); - Polygon polygon = (Polygon) returnedGeometry.getGeometry(); - assertEquals(polygon.toString(), birmingham().toString()); - } - - @Test - public void testPolygon2Polygon2Works() { - String birminghamJson = GeometryEngine.geometryToJson(4326, - birmingham()); - MapGeometry returnedGeometry = GeometryEngine - .jsonToGeometry(birminghamJson); - Polygon polygon = (Polygon) returnedGeometry.getGeometry(); - assertEquals(polygon, birmingham()); - String s = polygon.toString(); - } - - @Test - public void testSegmentIteratorCrash() { - Polygon poly = new Polygon(); - - // clockwise => outer ring - poly.startPath(0, 0); - poly.lineTo(-0.5, 0.5); - poly.lineTo(0.5, 1); - poly.lineTo(1, 0.5); - poly.lineTo(0.5, 0); - - // hole - poly.startPath(0.5, 0.2); - poly.lineTo(0.6, 0.5); - poly.lineTo(0.2, 0.9); - poly.lineTo(-0.2, 0.5); - poly.lineTo(0.1, 0.2); - poly.lineTo(0.2, 0.3); - - // island - poly.startPath(0.1, 0.7); - poly.lineTo(0.3, 0.7); - poly.lineTo(0.3, 0.4); - poly.lineTo(0.1, 0.4); - - assertEquals(poly.getSegmentCount(), 15); - assertEquals(poly.getPathCount(), 3); - SegmentIterator segmentIterator = poly.querySegmentIterator(); - int paths = 0; - int segments = 0; - while (segmentIterator.nextPath()) { - paths++; - Segment segment; - while (segmentIterator.hasNextSegment()) { - segment = segmentIterator.nextSegment(); - segments++; - } - } - assertEquals(paths, 3); - assertEquals(segments, 15); - } - - private static Polygon birmingham() { - Polygon poly = new Polygon(); - poly.addEnvelope(new Envelope(-1.954245, 52.513531, -1.837357, - 52.450123), false); - poly.addEnvelope(new Envelope(0, 0, 1, 1), false); - return poly; - } + double epsilon = 1.0e-14; + /* const */ + int nMax = 40; + + // If r == 1.0 and nMax == 40 and epsilon == 1.0e-14, it will pass. + // But if r == 1.0 and nMax == 40 and epsilon == 1.0e-15, it will fail. + + for (int n = 3; n < nMax; n++) { + // regular polygon with n vertices and length from center to vertex + // = r + poly = new Polygon(); + double theta = 0.0; + poly.startPath(r, 0.0); + for (int k = 1; k <= n; k++) { + theta -= 2 * Math.PI / n; + poly.lineTo(r * Math.cos(theta), r * Math.sin(theta)); + } + double sinPiOverN = Math.sin(Math.PI / n); + double sinTwoPiOverN = Math.sin(2.0 * Math.PI / n); + double analyticalLength = 2.0 * n * r * sinPiOverN; + double analyticalArea = 0.5 * n * r * r * sinTwoPiOverN; + double calculatedLength = poly.calculateLength2D(); + double calculatedArea = poly.calculateArea2D(); + assertTrue(Math.abs(analyticalLength - calculatedLength) < epsilon); + assertTrue(Math.abs(analyticalArea - calculatedArea) < epsilon); + } + } + + @Test + public void testInsertPointsFromArray() { + {// Test forward insertion of an array of Point2D + // ArrayOf(Point2D) arr = new ArrayOf(Point2D)(5); + // arr[0].SetCoords(10, 1); + // arr[1].SetCoords(15, 20); + // arr[2].SetCoords(300, 14); + // arr[3].SetCoords(314, 217); + // arr[4].SetCoords(60, 144); + + Polygon poly1 = new Polygon(); + poly1.startPath(1, 17); + poly1.lineTo(1, 207); + poly1.lineTo(3, 147); + poly1.lineTo(6, 1447); + + poly1.startPath(1000, 17); + poly1.lineTo(1250, 207); + poly1.lineTo(300, 147); + poly1.lineTo(6000, 1447); + + assertTrue(poly1.getPathCount() == 2); + assertTrue(poly1.getPathStart(1) == 4); + assertTrue(poly1.isClosedPath(0)); + assertTrue(poly1.isClosedPath(1)); + } + + {// Test reversed insertion of an array of Point2D + } + } + + @Test + public void testCR177477() { + Polygon pg = new Polygon(); + pg.startPath(-130, 40); + pg.lineTo(-70, 40); + pg.lineTo(-70, 10); + pg.lineTo(-130, 10); + + Polygon pg2 = new Polygon(); + pg2.startPath(-60, 40); + pg2.lineTo(-50, 40); + pg2.lineTo(-50, 10); + pg2.lineTo(-60, 10); + + pg.add(pg2, false); + } + + @Test + public void testCR177477getPathEnd() { + Polygon pg = new Polygon(); + pg.startPath(-130, 40); + pg.lineTo(-70, 40); + pg.lineTo(-70, 10); + pg.lineTo(-130, 10); + + pg.startPath(-60, 40); + pg.lineTo(-50, 40); + pg.lineTo(-50, 10); + pg.lineTo(-60, 10); + + pg.startPath(-40, 40); + pg.lineTo(-30, 40); + pg.lineTo(-30, 10); + pg.lineTo(-40, 10); + + int pathCount = pg.getPathCount(); + assertTrue(pathCount == 3); + + // int startIndex = pg.getPathStart(pathCount - 1); + + // int endIndex = pg.getPathEnd(pathCount - 1); + + Line line = new Line(); + line.toString(); + + line.setStart(new Point(0, 0)); + line.setEnd(new Point(1, 0)); + + line.toString(); + + double geoLength = GeometryEngine.geodesicDistanceOnWGS84(new Point(0, + 0), new Point(1, 0)); + assertTrue(Math.abs(geoLength - 111319) < 1); + } + + @Test + public void testBug1() { + Polygon pg = new Polygon(); + pg.startPath(-130, 40); + for (int i = 0; i < 1000; i++) + pg.lineTo(-70, 40); + for (int i = 0; i < 999; i++) + pg.removePoint(0, pg.getPointCount() - 1); + + pg.lineTo(-70, 40); + } + + @Test + public void testGeometryCopy() { + boolean noException = true; + + Polyline polyline = new Polyline(); + + Point p1 = new Point(-85.59285621496956, 38.26004727491098); + Point p2 = new Point(-85.56417866635002, 38.28084064314639); + Point p3 = new Point(-85.56845156650877, 38.24659881865461); + Point p4 = new Point(-85.55341069949853, 38.26671513050464); + + polyline.startPath(p1); + try { + polyline.lineTo(p2); + polyline.copy(); + polyline.lineTo(p3); + polyline.copy(); + polyline.lineTo(p4); // exception thrown here!!! + + } catch (Exception e) { + e.printStackTrace(); + noException = false; + } + + assertTrue(noException); + }// end of method + + @Test + public void testBoundary() { + Geometry g = OperatorImportFromWkt + .local() + .execute( + 0, + Geometry.Type.Unknown, + "POLYGON((-10 -10, 10 -10, 10 10, -10 10, -10 -10), (-5 -5, -5 5, 5 5, 5 -5, -5 -5))", + null); + + Geometry boundary = OperatorBoundary.local().execute(g, null); + Polyline polyline = (Polyline) boundary; + polyline.reverseAllPaths(); + String s = OperatorExportToWkt.local().execute(0, boundary, null); + assertTrue(s + .equals("MULTILINESTRING ((-10 -10, 10 -10, 10 10, -10 10, -10 -10), (-5 -5, -5 5, 5 5, 5 -5, -5 -5))")); + } + + @Test + public void testReplaceNaNs() { + { + MultiPoint mp = new MultiPoint(); + Point pt = new Point(); + pt.setXY(1, 2); + pt.setZ(Double.NaN); + mp.add(pt); + pt = new Point(); + pt.setXY(11, 12); + pt.setZ(3); + mp.add(pt); + + mp.replaceNaNs(VertexDescription.Semantics.Z, 5); + assertTrue(mp.getPoint(0).equals(new Point(1, 2, 5))); + assertTrue(mp.getPoint(1).equals(new Point(11, 12, 3))); + } + + { + Polygon mp = new Polygon(); + Point pt = new Point(); + pt.setXY(1, 2); + pt.setZ(Double.NaN); + mp.startPath(pt); + pt = new Point(); + pt.setXY(11, 12); + pt.setZ(3); + mp.lineTo(pt); + + mp.replaceNaNs(VertexDescription.Semantics.Z, 5); + assertTrue(mp.getPoint(0).equals(new Point(1, 2, 5))); + assertTrue(mp.getPoint(1).equals(new Point(11, 12, 3))); + } + + { + Polygon mp = new Polygon(); + Point pt1 = new Point(); + pt1.setXY(1, 2); + pt1.setM(Double.NaN); + mp.startPath(pt1); + Point pt2 = new Point(); + pt2.setXY(11, 12); + pt2.setM(3); + mp.lineTo(pt2); + + mp.replaceNaNs(VertexDescription.Semantics.M, 5); + Point p = new Point(1, 2); + p.setM(5); + boolean b = mp.getPoint(0).equals(p); + assertTrue(b); + p = new Point(11, 12); + p.setM(3); + b = mp.getPoint(1).equals(p); + assertTrue(b); + } + + } + + @Test + public void testPolygon2PolygonFails() { + OperatorFactoryLocal factory = OperatorFactoryLocal.getInstance(); + OperatorExportToGeoJson exporter = (OperatorExportToGeoJson) factory + .getOperator(Operator.Type.ExportToGeoJson); + String result = exporter.execute(birmingham()); + + OperatorImportFromGeoJson importer = (OperatorImportFromGeoJson) factory + .getOperator(Operator.Type.ImportFromGeoJson); + MapGeometry mapGeometry = importer.execute( + GeoJsonImportFlags.geoJsonImportDefaults, + Geometry.Type.Polygon, result, null); + Polygon polygon = (Polygon) mapGeometry.getGeometry(); + assertEquals(birmingham(), polygon); + } + + @Test + public void testPolygon2PolygonFails2() { + String birminghamGeojson = GeometryEngine + .geometryToGeoJson(birmingham()); + MapGeometry returnedGeometry = GeometryEngine.geoJsonToGeometry( + birminghamGeojson, GeoJsonImportFlags.geoJsonImportDefaults, + Geometry.Type.Polygon); + Polygon polygon = (Polygon) returnedGeometry.getGeometry(); + assertEquals(polygon, birmingham()); + } + + @Test + public void testPolygon2PolygonWorks() { + String birminghamGeojson = GeometryEngine + .geometryToGeoJson(birmingham()); + MapGeometry returnedGeometry = GeometryEngine.geoJsonToGeometry( + birminghamGeojson, GeoJsonImportFlags.geoJsonImportDefaults, + Geometry.Type.Polygon); + Polygon polygon = (Polygon) returnedGeometry.getGeometry(); + assertEquals(polygon.toString(), birmingham().toString()); + } + + @Test + public void testPolygon2Polygon2Works() { + String birminghamJson = GeometryEngine.geometryToJson(4326, + birmingham()); + MapGeometry returnedGeometry = GeometryEngine + .jsonToGeometry(birminghamJson); + Polygon polygon = (Polygon) returnedGeometry.getGeometry(); + assertEquals(polygon, birmingham()); + String s = polygon.toString(); + } + + @Test + public void testSegmentIteratorCrash() { + Polygon poly = new Polygon(); + + // clockwise => outer ring + poly.startPath(0, 0); + poly.lineTo(-0.5, 0.5); + poly.lineTo(0.5, 1); + poly.lineTo(1, 0.5); + poly.lineTo(0.5, 0); + + // hole + poly.startPath(0.5, 0.2); + poly.lineTo(0.6, 0.5); + poly.lineTo(0.2, 0.9); + poly.lineTo(-0.2, 0.5); + poly.lineTo(0.1, 0.2); + poly.lineTo(0.2, 0.3); + + // island + poly.startPath(0.1, 0.7); + poly.lineTo(0.3, 0.7); + poly.lineTo(0.3, 0.4); + poly.lineTo(0.1, 0.4); + + assertEquals(poly.getSegmentCount(), 15); + assertEquals(poly.getPathCount(), 3); + SegmentIterator segmentIterator = poly.querySegmentIterator(); + int paths = 0; + int segments = 0; + while (segmentIterator.nextPath()) { + paths++; + Segment segment; + while (segmentIterator.hasNextSegment()) { + segment = segmentIterator.nextSegment(); + segments++; + } + } + assertEquals(paths, 3); + assertEquals(segments, 15); + } + + private static Polygon birmingham() { + Polygon poly = new Polygon(); + poly.addEnvelope(new Envelope(-1.954245, 52.513531, -1.837357, + 52.450123), false); + poly.addEnvelope(new Envelope(0, 0, 1, 1), false); + return poly; + } } diff --git a/src/test/java/com/esri/core/geometry/TestPolygonUtils.java b/src/test/java/com/esri/core/geometry/TestPolygonUtils.java index 6f1f7e1d..d0fc72bc 100644 --- a/src/test/java/com/esri/core/geometry/TestPolygonUtils.java +++ b/src/test/java/com/esri/core/geometry/TestPolygonUtils.java @@ -28,129 +28,129 @@ import org.junit.Test; public class TestPolygonUtils extends TestCase { - @Override - protected void setUp() throws Exception { - super.setUp(); - } - - @Override - protected void tearDown() throws Exception { - super.tearDown(); - } - - @Test - public static void testPointInAnyOuterRing() { - Polygon polygon = new Polygon(); - // outer ring1 - polygon.startPath(-200, -100); - polygon.lineTo(200, -100); - polygon.lineTo(200, 100); - polygon.lineTo(-190, 100); - polygon.lineTo(-190, 90); - polygon.lineTo(-200, 90); - - // hole - polygon.startPath(-100, 50); - polygon.lineTo(100, 50); - polygon.lineTo(100, -40); - polygon.lineTo(90, -40); - polygon.lineTo(90, -50); - polygon.lineTo(-100, -50); - - // island - polygon.startPath(-10, -10); - polygon.lineTo(10, -10); - polygon.lineTo(10, 10); - polygon.lineTo(-10, 10); - - // outer ring2 - polygon.startPath(300, 300); - polygon.lineTo(310, 300); - polygon.lineTo(310, 310); - polygon.lineTo(300, 310); - - polygon.reverseAllPaths(); - - Point2D testPointIn1 = new Point2D(1, 2); // inside the island - Point2D testPointIn2 = new Point2D(190, 90); // inside, betwen outer - // ring1 and the hole - Point2D testPointIn3 = new Point2D(305, 305); // inside the outer ring2 - Point2D testPointOut1 = new Point2D(300, 2); // outside any - Point2D testPointOut2 = new Point2D(-195, 95); // outside any (in the - // concave area of outer - // ring 2) - Point2D testPointOut3 = new Point2D(99, 49); // outside (in the hole) - - PolygonUtils.PiPResult res; - // is_point_in_polygon_2D - res = PolygonUtils.isPointInPolygon2D(polygon, testPointIn1, 0); - assertTrue(res == PolygonUtils.PiPResult.PiPInside); - res = PolygonUtils.isPointInPolygon2D(polygon, testPointIn2, 0); - assertTrue(res == PolygonUtils.PiPResult.PiPInside); - res = PolygonUtils.isPointInPolygon2D(polygon, testPointIn3, 0); - assertTrue(res == PolygonUtils.PiPResult.PiPInside); - - res = PolygonUtils.isPointInPolygon2D(polygon, testPointOut1, 0); - assertTrue(res == PolygonUtils.PiPResult.PiPOutside); - res = PolygonUtils.isPointInPolygon2D(polygon, testPointOut2, 0); - assertTrue(res == PolygonUtils.PiPResult.PiPOutside); - res = PolygonUtils.isPointInPolygon2D(polygon, testPointOut3, 0); - assertTrue(res == PolygonUtils.PiPResult.PiPOutside); - - // Ispoint_in_any_outer_ring - res = PolygonUtils.isPointInAnyOuterRing(polygon, testPointIn1, 0); - assertTrue(res == PolygonUtils.PiPResult.PiPInside); - res = PolygonUtils.isPointInAnyOuterRing(polygon, testPointIn2, 0); - assertTrue(res == PolygonUtils.PiPResult.PiPInside); - res = PolygonUtils.isPointInAnyOuterRing(polygon, testPointIn3, 0); - assertTrue(res == PolygonUtils.PiPResult.PiPInside); - - res = PolygonUtils.isPointInAnyOuterRing(polygon, testPointOut1, 0); - assertTrue(res == PolygonUtils.PiPResult.PiPOutside); - res = PolygonUtils.isPointInAnyOuterRing(polygon, testPointOut2, 0); - assertTrue(res == PolygonUtils.PiPResult.PiPOutside); - res = PolygonUtils.isPointInAnyOuterRing(polygon, testPointOut3, 0); - assertTrue(res == PolygonUtils.PiPResult.PiPInside);// inside of outer - // ring - } - - @Test - public static void testPointInPolygonBugCR181840() { - PolygonUtils.PiPResult res; - {// pointInPolygonBugCR181840 - point in polygon bug - Polygon polygon = new Polygon(); - // outer ring1 - polygon.startPath(0, 0); - polygon.lineTo(10, 10); - polygon.lineTo(20, 0); - - res = PolygonUtils.isPointInPolygon2D(polygon, - Point2D.construct(15, 10), 0); - assertTrue(res == PolygonUtils.PiPResult.PiPOutside); - res = PolygonUtils.isPointInPolygon2D(polygon, - Point2D.construct(2, 10), 0); - assertTrue(res == PolygonUtils.PiPResult.PiPOutside); - res = PolygonUtils.isPointInPolygon2D(polygon, - Point2D.construct(5, 5), 0); - assertTrue(res == PolygonUtils.PiPResult.PiPInside); - } - - {// CR181840 - point in polygon bug - Polygon polygon = new Polygon(); - // outer ring1 - polygon.startPath(10, 10); - polygon.lineTo(20, 0); - polygon.lineTo(0, 0); - - res = PolygonUtils.isPointInPolygon2D(polygon, - Point2D.construct(15, 10), 0); - assertTrue(res == PolygonUtils.PiPResult.PiPOutside); - res = PolygonUtils.isPointInPolygon2D(polygon, - Point2D.construct(2, 10), 0); - assertTrue(res == PolygonUtils.PiPResult.PiPOutside); - res = PolygonUtils.isPointInPolygon2D(polygon, - Point2D.construct(5, 5), 0); - assertTrue(res == PolygonUtils.PiPResult.PiPInside); - } - } + @Override + protected void setUp() throws Exception { + super.setUp(); + } + + @Override + protected void tearDown() throws Exception { + super.tearDown(); + } + + @Test + public static void testPointInAnyOuterRing() { + Polygon polygon = new Polygon(); + // outer ring1 + polygon.startPath(-200, -100); + polygon.lineTo(200, -100); + polygon.lineTo(200, 100); + polygon.lineTo(-190, 100); + polygon.lineTo(-190, 90); + polygon.lineTo(-200, 90); + + // hole + polygon.startPath(-100, 50); + polygon.lineTo(100, 50); + polygon.lineTo(100, -40); + polygon.lineTo(90, -40); + polygon.lineTo(90, -50); + polygon.lineTo(-100, -50); + + // island + polygon.startPath(-10, -10); + polygon.lineTo(10, -10); + polygon.lineTo(10, 10); + polygon.lineTo(-10, 10); + + // outer ring2 + polygon.startPath(300, 300); + polygon.lineTo(310, 300); + polygon.lineTo(310, 310); + polygon.lineTo(300, 310); + + polygon.reverseAllPaths(); + + Point2D testPointIn1 = new Point2D(1, 2); // inside the island + Point2D testPointIn2 = new Point2D(190, 90); // inside, betwen outer + // ring1 and the hole + Point2D testPointIn3 = new Point2D(305, 305); // inside the outer ring2 + Point2D testPointOut1 = new Point2D(300, 2); // outside any + Point2D testPointOut2 = new Point2D(-195, 95); // outside any (in the + // concave area of outer + // ring 2) + Point2D testPointOut3 = new Point2D(99, 49); // outside (in the hole) + + PolygonUtils.PiPResult res; + // is_point_in_polygon_2D + res = PolygonUtils.isPointInPolygon2D(polygon, testPointIn1, 0); + assertTrue(res == PolygonUtils.PiPResult.PiPInside); + res = PolygonUtils.isPointInPolygon2D(polygon, testPointIn2, 0); + assertTrue(res == PolygonUtils.PiPResult.PiPInside); + res = PolygonUtils.isPointInPolygon2D(polygon, testPointIn3, 0); + assertTrue(res == PolygonUtils.PiPResult.PiPInside); + + res = PolygonUtils.isPointInPolygon2D(polygon, testPointOut1, 0); + assertTrue(res == PolygonUtils.PiPResult.PiPOutside); + res = PolygonUtils.isPointInPolygon2D(polygon, testPointOut2, 0); + assertTrue(res == PolygonUtils.PiPResult.PiPOutside); + res = PolygonUtils.isPointInPolygon2D(polygon, testPointOut3, 0); + assertTrue(res == PolygonUtils.PiPResult.PiPOutside); + + // Ispoint_in_any_outer_ring + res = PolygonUtils.isPointInAnyOuterRing(polygon, testPointIn1, 0); + assertTrue(res == PolygonUtils.PiPResult.PiPInside); + res = PolygonUtils.isPointInAnyOuterRing(polygon, testPointIn2, 0); + assertTrue(res == PolygonUtils.PiPResult.PiPInside); + res = PolygonUtils.isPointInAnyOuterRing(polygon, testPointIn3, 0); + assertTrue(res == PolygonUtils.PiPResult.PiPInside); + + res = PolygonUtils.isPointInAnyOuterRing(polygon, testPointOut1, 0); + assertTrue(res == PolygonUtils.PiPResult.PiPOutside); + res = PolygonUtils.isPointInAnyOuterRing(polygon, testPointOut2, 0); + assertTrue(res == PolygonUtils.PiPResult.PiPOutside); + res = PolygonUtils.isPointInAnyOuterRing(polygon, testPointOut3, 0); + assertTrue(res == PolygonUtils.PiPResult.PiPInside);// inside of outer + // ring + } + + @Test + public static void testPointInPolygonBugCR181840() { + PolygonUtils.PiPResult res; + {// pointInPolygonBugCR181840 - point in polygon bug + Polygon polygon = new Polygon(); + // outer ring1 + polygon.startPath(0, 0); + polygon.lineTo(10, 10); + polygon.lineTo(20, 0); + + res = PolygonUtils.isPointInPolygon2D(polygon, + Point2D.construct(15, 10), 0); + assertTrue(res == PolygonUtils.PiPResult.PiPOutside); + res = PolygonUtils.isPointInPolygon2D(polygon, + Point2D.construct(2, 10), 0); + assertTrue(res == PolygonUtils.PiPResult.PiPOutside); + res = PolygonUtils.isPointInPolygon2D(polygon, + Point2D.construct(5, 5), 0); + assertTrue(res == PolygonUtils.PiPResult.PiPInside); + } + + {// CR181840 - point in polygon bug + Polygon polygon = new Polygon(); + // outer ring1 + polygon.startPath(10, 10); + polygon.lineTo(20, 0); + polygon.lineTo(0, 0); + + res = PolygonUtils.isPointInPolygon2D(polygon, + Point2D.construct(15, 10), 0); + assertTrue(res == PolygonUtils.PiPResult.PiPOutside); + res = PolygonUtils.isPointInPolygon2D(polygon, + Point2D.construct(2, 10), 0); + assertTrue(res == PolygonUtils.PiPResult.PiPOutside); + res = PolygonUtils.isPointInPolygon2D(polygon, + Point2D.construct(5, 5), 0); + assertTrue(res == PolygonUtils.PiPResult.PiPInside); + } + } } diff --git a/src/test/java/com/esri/core/geometry/TestProjection.java b/src/test/java/com/esri/core/geometry/TestProjection.java index 13d4cc54..80387a8d 100644 --- a/src/test/java/com/esri/core/geometry/TestProjection.java +++ b/src/test/java/com/esri/core/geometry/TestProjection.java @@ -18,467 +18,467 @@ */ public class TestProjection extends TestCase { - static { - System.loadLibrary("proj"); - } - - SpatialReference spatialReferenceWGS = SpatialReference.create(4326); - SpatialReference spatialReferenceMerc = SpatialReference.create(3857); - ProjectionTransformation projectionTransformationToMerc = new ProjectionTransformation(spatialReferenceWGS, spatialReferenceMerc); - ProjectionTransformation projectionTransformationToWGS = new ProjectionTransformation(spatialReferenceMerc, spatialReferenceWGS); - - @Before - public void setUp() { - - } - - @Test - public void testProj4() throws Exception { - PJ sourcePJ = new PJ("+init=epsg:32632"); // (x,y) axis order - PJ targetPJ = new PJ("+proj=latlong +datum=WGS84"); // (λ,φ) axis order - PJ sourcePJ_utm = new PJ("+proj=utm +zone=32 +datum=WGS84 +units=m +no_defs "); - PJ targetPJ_4326 = new PJ("+init=epsg:4326"); - - double[] coordinates_32632 = { - 500000, 0, // First coordinate - 400000, 100000, // Second coordinate - 600000, -100000 // Third coordinate - }; - - double[] coordinates_WGS84 = Arrays.copyOf(coordinates_32632, coordinates_32632.length); - double[] coordinates_4326 = Arrays.copyOf(coordinates_32632, coordinates_32632.length); - - sourcePJ.transform(targetPJ, 2, coordinates_WGS84, 0, 3); - sourcePJ.transform(targetPJ_4326, 2, coordinates_4326, 0, 3); - - for (int i = 0; i < coordinates_WGS84.length; i++) { - if (i == 1) - continue; - - assertTrue((Math.abs(coordinates_WGS84[i] - coordinates_32632[i])) > 1); - assertTrue((Math.abs(coordinates_4326[i] - coordinates_32632[i])) > 1); - assertEquals(coordinates_4326[i], coordinates_WGS84[i]); - } - } - - @Test - public void testProj4Strings() { - SpatialReference spatialReference = SpatialReference.create(4326); - assertEquals("+init=epsg:4326", spatialReference.getProj4()); - - spatialReference = SpatialReference.create(32632); - assertEquals("+init=epsg:32632", spatialReference.getProj4()); - } - - @Test - public void testProjectionCursor_1() { - ProjectionTransformation projectionTransformation = new ProjectionTransformation(SpatialReference.create(32632), SpatialReference.create(4326)); - Point point = new Point(500000, 0); - Point pointOut = (Point) OperatorProject.local().execute(point, projectionTransformation, null); - assertNotNull(pointOut); - assertTrue(Math.abs(point.getX() - pointOut.getY()) > 1); - projectionTransformation = new ProjectionTransformation(SpatialReference.create(4326), SpatialReference.create(32632)); - Point originalPoint = (Point) OperatorProject.local().execute(pointOut, projectionTransformation, null); - assertEquals(originalPoint.getX(), point.getX()); - assertEquals(originalPoint.getY(), point.getY()); - } - - @Test - public void testProjectMultiPoint() { - ProjectionTransformation projectionTransformation = new ProjectionTransformation(SpatialReference.create(32632), SpatialReference.create(4326)); - MultiPoint multiPoint = new MultiPoint(); - multiPoint.add(500000, 0); - multiPoint.add(400000, 100000); - multiPoint.add(600000, -100000); - MultiPoint multiPointOut = (MultiPoint) OperatorProject.local().execute(multiPoint, projectionTransformation, null); - assertNotNull(multiPointOut); - assertFalse(multiPointOut.equals(multiPoint)); - assertEquals(multiPoint.getPointCount(), multiPointOut.getPointCount()); - projectionTransformation = new ProjectionTransformation(SpatialReference.create(4326), SpatialReference.create(32632)); - MultiPoint originalMultiPoint = (MultiPoint) OperatorProject.local().execute(multiPointOut, projectionTransformation, null); - - for (int i = 0; i < multiPoint.getPointCount(); i++) { - assertEquals(multiPoint.getPoint(i).getX(), originalMultiPoint.getPoint(i).getX(), 1e-9); - assertEquals(multiPoint.getPoint(i).getY(), originalMultiPoint.getPoint(i).getY(), 1e-9); - } - } - - @Test - public void testProjectPolyline() { - ProjectionTransformation projectionTransformation = new ProjectionTransformation(SpatialReference.create(32632), SpatialReference.create(4326)); - Polyline polyline = new Polyline(); - polyline.startPath(500000, 0); - polyline.lineTo(400000, 100000); - polyline.lineTo(600000, -100000); - Polyline polylineOut = (Polyline) OperatorProject.local().execute(polyline, projectionTransformation, null); - assertNotNull(polylineOut); - assertFalse(polylineOut.equals(polyline)); - - MultiPathImpl polyline_impl = (MultiPathImpl) polylineOut._getImpl(); - int point_count = polyline_impl.getPointCount(); - int path_count = polyline_impl.getPathCount(); - assertEquals(point_count, 3); - assertEquals(path_count, 1); - - assertEquals(polyline.getPointCount(), polylineOut.getPointCount()); - projectionTransformation = new ProjectionTransformation(SpatialReference.create(4326), SpatialReference.create(32632)); - Polyline originalPolyline = (Polyline) OperatorProject.local().execute(polylineOut, projectionTransformation, null); - for (int i = 0; i < polyline.getPointCount(); i++) { - assertEquals(polyline.getPoint(i).getX(), originalPolyline.getPoint(i).getX(), 1e-9); - assertEquals(polyline.getPoint(i).getY(), originalPolyline.getPoint(i).getY(), 1e-9); - } - } - - @Test - public void testProjectPolygon() { - ProjectionTransformation projectionTransformation = new ProjectionTransformation(SpatialReference.create(32632), SpatialReference.create(4326)); - Polygon polygon = new Polygon(); - polygon.startPath(500000, 0); - polygon.lineTo(400000, 100000); - polygon.lineTo(600000, -100000); - polygon.closeAllPaths(); - Polygon polygonOut = (Polygon) OperatorProject.local().execute(polygon, projectionTransformation, null); - assertNotNull(polygonOut); - assertFalse(polygonOut.equals(polygon)); - assertEquals(polygon.getPointCount(), polygonOut.getPointCount()); - projectionTransformation = new ProjectionTransformation(SpatialReference.create(4326), SpatialReference.create(32632)); - Polygon originalPolygon = (Polygon) OperatorProject.local().execute(polygonOut, projectionTransformation, null); - - for (int i = 0; i < polygon.getPointCount(); i++) { - assertEquals(polygon.getPoint(i).getX(), originalPolygon.getPoint(i).getX(), 1e-9); - assertEquals(polygon.getPoint(i).getY(), originalPolygon.getPoint(i).getY(), 1e-9); - } - } - - @Test - public void testProjectEnvelope() { - Envelope2D envelope2D = new Envelope2D(-10000, -10000, 10000, 10000); - String proj4 = String.format( - "+proj=laea +lat_0=%f +lon_0=%f +x_0=0.0 +y_0=0.0 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs", - 0f, 0f); - - SpatialReference spatialReference = SpatialReference.createFromProj4(proj4); - SpatialReference spatialReferenceWgs84 = SpatialReference.create(4326); - ProjectionTransformation projectionTransformation = new ProjectionTransformation(spatialReference, spatialReferenceWgs84); - Polygon polygon = (Polygon) OperatorProject.local().execute(new Envelope(envelope2D), projectionTransformation, null); - assertNotNull(polygon); - Point2D point2D = new Point2D(); - double a = 6378137.0; // radius of spheroid for WGS_1984 - double e2 = 0.0066943799901413165; // ellipticity for WGS_1984 - Envelope2D gcsEnvelope = new Envelope2D(); - polygon.queryEnvelope2D(gcsEnvelope); - GeoDist.getEnvCenter(a, e2, gcsEnvelope, point2D); - assertEquals(point2D.x, 0, 1e-14); - assertEquals(point2D.y, 0, 1e-6); - - // TODO - } - - @Test - public void testEPSGCodes() { - String wktGeom = "MULTIPOLYGON (((6311583.246999994 1871386.1630000025, 6311570 1871325, 6311749.093999997 1871285.9699999988, 6311768.118000001 1871345.9619999975, 6311583.246999994 1871386.1630000025)))"; - SpatialReference spatialReference = SpatialReference.create(2230); - SpatialReference spatialReferenceWgs84 = SpatialReference.create(4326); - ProjectionTransformation projectionTransformation = new ProjectionTransformation(spatialReference, spatialReferenceWgs84); - SimpleStringCursor simpleStringCursor = new SimpleStringCursor(wktGeom); - OperatorImportFromWktCursor wktCursor = new OperatorImportFromWktCursor(0, simpleStringCursor); - OperatorProjectCursor projectCursor = new OperatorProjectCursor(wktCursor, projectionTransformation, null); - Geometry geometry = projectCursor.next(); - - assertNotNull(geometry); - } - - @Test - public void testFoldInto360() { - String wktGeom = "POLYGON((120 48.2246726495652,140 48.2246726495652,140 25.799891182088334,120 25.799891182088334,120 48.2246726495652))"; - SimpleStringCursor result = new SimpleStringCursor(wktGeom); - - OperatorImportFromWktCursor wktCursor = new OperatorImportFromWktCursor(0, result); - Geometry expectedGeometry = wktCursor.next(); - - String wktGeom360 = "POLYGON((480 48.2246726495652,500 48.2246726495652,500 25.799891182088334,480 25.799891182088334,480 48.2246726495652))"; - SimpleStringCursor test = new SimpleStringCursor(wktGeom360); - wktCursor = new OperatorImportFromWktCursor(0, test); - OperatorProjectCursor projectCursor = new OperatorProjectCursor(wktCursor, this.projectionTransformationToMerc, null); - OperatorProjectCursor reProjectCursor = new OperatorProjectCursor(projectCursor, this.projectionTransformationToWGS, null); - - Polygon actualGeometry = (Polygon) reProjectCursor.next(); - - assertTrue(GeometryEngine.equals(actualGeometry, expectedGeometry, spatialReferenceWGS)); - } - - - @Test - public void testWrapTriangle() { - String wktGeom = "POLYGON((167 30, 201 49, 199 18, 167 30))"; - SimpleStringCursor simpleStringCursor = new SimpleStringCursor(wktGeom); - OperatorImportFromWktCursor wktCursor = new OperatorImportFromWktCursor(0, simpleStringCursor); - OperatorProjectCursor projectCursor = new OperatorProjectCursor(wktCursor, this.projectionTransformationToMerc, null); - OperatorProjectCursor reProjectCursor = new OperatorProjectCursor(projectCursor, this.projectionTransformationToWGS, null); - - Polygon result = (Polygon) reProjectCursor.next(); - NonSimpleResult nonSimpleResult = new NonSimpleResult(); - OperatorSimplify simplify = (OperatorSimplify) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.Simplify); - boolean isSimple = simplify.isSimpleAsFeature(result, spatialReferenceWGS, true, nonSimpleResult, null); - - simpleStringCursor = new SimpleStringCursor(wktGeom); - wktCursor = new OperatorImportFromWktCursor(0, simpleStringCursor); - Polygon expected = (Polygon) wktCursor.next(); - assertTrue(GeometryEngine.isSimple(expected, spatialReferenceWGS)); - - assertEquals(expected.calculateArea2D(), result.calculateArea2D(), 1e-10); - } - - @Test - public void testWrapTriangleOtherSide() { - String wktGeom = "POLYGON((-193 -30, -160 -29, -158 -40, -193 -30))"; - SimpleStringCursor simpleStringCursor = new SimpleStringCursor(wktGeom); - OperatorImportFromWktCursor wktCursor = new OperatorImportFromWktCursor(0, simpleStringCursor); - OperatorProjectCursor projectCursor = new OperatorProjectCursor(wktCursor, this.projectionTransformationToMerc, null); - OperatorProjectCursor reProjectCursor = new OperatorProjectCursor(projectCursor, this.projectionTransformationToWGS, null); - - Polygon result = (Polygon) reProjectCursor.next(); - NonSimpleResult nonSimpleResult = new NonSimpleResult(); - OperatorSimplify simplify = (OperatorSimplify) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.Simplify); - boolean isSimple = simplify.isSimpleAsFeature(result, spatialReferenceWGS, true, nonSimpleResult, null); - - simpleStringCursor = new SimpleStringCursor(wktGeom); - wktCursor = new OperatorImportFromWktCursor(0, simpleStringCursor); - Polygon expected = (Polygon) wktCursor.next(); - assertTrue(GeometryEngine.isSimple(expected, spatialReferenceWGS)); - - assertEquals(expected.calculateArea2D(), result.calculateArea2D(), .00000000001); - } - - @Test - public void testWrap() { - String wktGeom = "POLYGON((167.87109375 30.751277776257812," + - "201.43359375 49.38237278700955," + - "232.49609375 -5.266007882805485," + - "116.19500625 -17.308687886770024," + - "199.54296875 18.979025953255267," + - "126.03515625 12.897489183755892," + - "167.87109375 30.751277776257812))"; - SimpleStringCursor simpleStringCursor = new SimpleStringCursor(wktGeom); - OperatorImportFromWktCursor wktCursor = new OperatorImportFromWktCursor(0, simpleStringCursor); - OperatorProjectCursor projectCursor = new OperatorProjectCursor(wktCursor, this.projectionTransformationToMerc, null); - OperatorProjectCursor reProjectCursor = new OperatorProjectCursor(projectCursor, this.projectionTransformationToWGS, null); - - Polygon result = (Polygon) reProjectCursor.next(); - NonSimpleResult nonSimpleResult = new NonSimpleResult(); - OperatorSimplify simplify = (OperatorSimplify) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.Simplify); - boolean isSimple = simplify.isSimpleAsFeature(result, spatialReferenceWGS, true, nonSimpleResult, null); - - simpleStringCursor = new SimpleStringCursor(wktGeom); - wktCursor = new OperatorImportFromWktCursor(0, simpleStringCursor); - Polygon expected = (Polygon) wktCursor.next(); - assertTrue(GeometryEngine.isSimple(expected, spatialReferenceWGS)); - - assertEquals(expected.calculateArea2D(), result.calculateArea2D(), 1e-10); - } - - @Test - public void testAlbers() { - String wktGeom = "MULTIPOLYGON (((40 40, 20 45, 45 30, 40 40)), ((20 35, 45 20, 30 5, 10 10, 10 30, 20 35), (30 20, 20 25, 20 15, 30 20)))"; - - SpatialReference sr = SpatialReference.create(2163); - ProjectionTransformation projectionTransformation = new ProjectionTransformation(spatialReferenceWGS, sr); - - SimpleStringCursor simpleStringCursor = new SimpleStringCursor(wktGeom); - OperatorImportFromWktCursor wktCursor = new OperatorImportFromWktCursor(0, simpleStringCursor); - OperatorProjectCursor projectCursor = new OperatorProjectCursor(wktCursor, projectionTransformation, null); - OperatorProjectCursor reProjectCursor = new OperatorProjectCursor(projectCursor, projectionTransformation.getReverse(), null); - - Polygon result = (Polygon) reProjectCursor.next(); - NonSimpleResult nonSimpleResult = new NonSimpleResult(); - OperatorSimplify simplify = (OperatorSimplify) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.Simplify); - boolean isSimple = simplify.isSimpleAsFeature(result, spatialReferenceWGS, true, nonSimpleResult, null); - assertTrue(isSimple); - - simpleStringCursor = new SimpleStringCursor(wktGeom, 99); - wktCursor = new OperatorImportFromWktCursor(0, simpleStringCursor); - Polygon expected = (Polygon) wktCursor.next(); - assertTrue(GeometryEngine.isSimple(expected, spatialReferenceWGS)); - assertEquals(wktCursor.getGeometryID(), 99); - assertEquals(expected.calculateArea2D(), result.calculateArea2D(), 1e-10); - } - - @Test - public void testProjectionTransformation() { - int count = 400; - Envelope e = new Envelope(0,0,40, 40); - RandomCoordinateGenerator randomCoordinateGenerator = new RandomCoordinateGenerator(count, e, SpatialReference.create(4326).getTolerance()); - MultiPoint multiPoint = new MultiPoint(); - for (int i = 0; i < count; i++) { - multiPoint.add(randomCoordinateGenerator._GenerateNewPoint()); - } - - ProjectionTransformation projectionTransformation = ProjectionTransformation.getEqualArea(multiPoint, spatialReferenceWGS); - Geometry projected = OperatorProject.local().execute(multiPoint, projectionTransformation, null); - Geometry reprojected = OperatorProject.local().execute(projected, projectionTransformation.getReverse(), null); - - assertTrue(OperatorEquals.local().execute(reprojected, multiPoint, SpatialReference.create(104919), null)); - - Geometry reProjectedConvexhull = OperatorProject.local().execute(OperatorConvexHull.local().execute(projected, null), projectionTransformation.getReverse(), null); - Geometry convexHull = OperatorConvexHull.local().execute(multiPoint, null); - - assertEquals(convexHull.calculateArea2D(), reProjectedConvexhull.calculateArea2D(), 1); - } - - @Test - public void testGeometryEnvelope() { - MultiPoint multiPoint = new MultiPoint(); - multiPoint.add(0,0); - multiPoint.add(0,20); - multiPoint.add(40,40); - - ProjectionTransformation projectionTransformation = ProjectionTransformation.getEqualArea(multiPoint, spatialReferenceWGS); - Geometry projected = OperatorProject.local().execute(multiPoint, projectionTransformation, null); - - Envelope2D envelope2D = new Envelope2D(); - projected.queryEnvelope2D(envelope2D); - - assertTrue(envelope2D.xmax != 40); - - } - - @Test - public void testDateline() { - String wktGeom = "POLYGON((-185 0, -185 10, -170 10, -170 0),(-182 3, -172 3, -172 7, -182 7))"; - Geometry original = OperatorImportFromWkt.local().execute( - 0, - Geometry.Type.Unknown, - wktGeom, - null); - Geometry projected = OperatorProject.local().execute( - original, - projectionTransformationToMerc, null); - - assertNotNull(projected); - - Geometry reProjected = OperatorProject.local().execute(projected, projectionTransformationToWGS, null); - assertNotNull(reProjected); - - NonSimpleResult nonSimpleResult = new NonSimpleResult(); - assertTrue(OperatorSimplify.local().isSimpleAsFeature(reProjected, spatialReferenceWGS, true, nonSimpleResult, null)); - - assertEquals(original.calculateArea2D(), reProjected.calculateArea2D(), 0.00001); - } - - @Test - public void testWrapNotWGS84() { - String wktGeom = "POLYGON((-185 0, -185 10, -170 10, -170 0),(-182 3, -172 3, -172 7, -182 7))"; - Geometry original = OperatorImportFromWkt.local().execute(0,Geometry.Type.Unknown, wktGeom,null); - ProjectionTransformation projectionTransformation = new ProjectionTransformation(SpatialReference.create(4269), spatialReferenceMerc); - Geometry projected = OperatorProject.local().execute( - original, - projectionTransformation, null); - - assertNotNull(projected); - - Geometry reProjected = OperatorProject.local().execute(projected, projectionTransformation.getReverse(), null); - assertNotNull(reProjected); - - NonSimpleResult nonSimpleResult = new NonSimpleResult(); - assertTrue(OperatorSimplify.local().isSimpleAsFeature(reProjected, SpatialReference.create(4269), true, nonSimpleResult, null)); - - assertEquals(original.calculateArea2D(), reProjected.calculateArea2D(), 0.00001); - } - - @Test - public void testReProjectMultiPoint() { - MultiPoint multiPoint = new MultiPoint(); - for (double longitude = -180; longitude < 180; longitude+=10.0) { - for (double latitude = -80; latitude < 80; latitude+=10.0) { - multiPoint.add(longitude, latitude); - } - } - - ProjectionTransformation projectionTransformation = new ProjectionTransformation(SpatialReference.create(4326), SpatialReference.create(3857)); - Geometry projected = OperatorProject.local().execute(multiPoint, projectionTransformation, null); - Geometry reprojected = OperatorProject.local().execute(projected, projectionTransformation.getReverse(), null); - - assertTrue(OperatorEquals.local().execute(multiPoint, reprojected, SpatialReference.create(4326), null)); - } - - @Test - public void testExampleLAEA_bug() { - double distance = 800; - SpatialReference spatialReferenceLAEAeurope = SpatialReference.create(3035); - ProjectionTransformation projectionTransformation = new ProjectionTransformation(spatialReferenceWGS, spatialReferenceLAEAeurope); - Point europeCenter = new Point(10, 52); - Point projectedCenter = (Point)OperatorProject.local().execute(europeCenter, projectionTransformation, null); - Point projected200mN = new Point(projectedCenter.getX(), projectedCenter.getY() + distance); - Point projected200mE = new Point(projectedCenter.getX() - distance, projectedCenter.getY()); - Point reprojected200mN = (Point)OperatorProject.local().execute(projected200mN, projectionTransformation.getReverse(), null); - Point reprojected200mE = (Point)OperatorProject.local().execute(projected200mE, projectionTransformation.getReverse(), null); - double distanceEast = SpatialReferenceImpl.geodesicDistanceOnWGS84Impl(europeCenter, reprojected200mE); - double distanceNorth = SpatialReferenceImpl.geodesicDistanceOnWGS84Impl(europeCenter, reprojected200mN); - assertEquals(distance, distanceNorth, 1e-3); - assertEquals(distance, distanceEast, 1e-6); - - projectionTransformation = null; - ProjectionTransformation projectionTransformationEA = ProjectionTransformation.getEqualArea(europeCenter, spatialReferenceWGS); - projectedCenter = (Point)OperatorProject.local().execute(europeCenter, projectionTransformationEA, null); - projected200mN = new Point(projectedCenter.getX(), projectedCenter.getY() + distance); - projected200mE = new Point(projectedCenter.getX() - distance, projectedCenter.getY()); - reprojected200mN = (Point)OperatorProject.local().execute(projected200mN, projectionTransformationEA.getReverse(), null); - reprojected200mE = (Point)OperatorProject.local().execute(projected200mE, projectionTransformationEA.getReverse(), null); - distanceEast = SpatialReferenceImpl.geodesicDistanceOnWGS84Impl(europeCenter, reprojected200mE); - distanceNorth = SpatialReferenceImpl.geodesicDistanceOnWGS84Impl(europeCenter, reprojected200mN); - assertEquals(distance, distanceNorth, 1e-3); - assertEquals(distance, distanceEast, 1e-6); - - } - - @Test - public void testEqualAreaProjection() { - // POINT (-70.651886936570745 43.134525834585826) - Point point = new Point(-70.651886936570745, 43.1345088834585826); - SpatialReference utmZone = SpatialReference.createUTM(point); - SpatialReference spatialReferenceWGS84 = SpatialReference.create(4326); - ProjectionTransformation projectionTransformationUTM = new ProjectionTransformation(spatialReferenceWGS84, utmZone); - - ProjectionTransformation projectionTransformationEA = ProjectionTransformation.getEqualArea(point, spatialReferenceWGS84); - - Geometry geodesicBuffered = OperatorGeodesicBuffer.local().execute( - point, - spatialReferenceWGS84, - GeodeticCurveType.Geodesic, - 135.7, - 0.07, - false, - null); - - Geometry utmPoint = GeometryEngine.project(point, spatialReferenceWGS84, utmZone); - Geometry utmPoint2 = OperatorProject.local().execute(point, projectionTransformationUTM, null); - assertTrue(utmPoint.equals(utmPoint2)); - - Geometry utmReprojected = GeometryEngine.project(utmPoint, utmZone, spatialReferenceWGS84); - Geometry utmReprojected2 = OperatorProject.local().execute(utmPoint2, projectionTransformationUTM.getReverse(), null); - assertTrue(utmReprojected.equals(utmReprojected2)); - - Geometry utmBuffer = OperatorBuffer.local().execute(utmPoint, utmZone, 135.6, null); - Geometry reProjectedUTMBuffer = OperatorProject.local().execute(utmBuffer, projectionTransformationUTM.getReverse(), null); - double differenceVal = GeometryEngine.difference(reProjectedUTMBuffer, geodesicBuffered, spatialReferenceWGS84).calculateArea2D(); - assertEquals(differenceVal, 0.0, 1e-14); - - assertTrue(GeometryEngine.contains(geodesicBuffered, reProjectedUTMBuffer, spatialReferenceWGS84)); - - Geometry aziEAPoint = OperatorProject.local().execute(point, projectionTransformationEA, null); - Geometry aziEABuffere = OperatorBuffer.local().execute(aziEAPoint, null, 135.6, null); - assertEquals(aziEABuffere.calculateArea2D(), utmBuffer.calculateArea2D(), 1e-5); - Geometry reProjectedAZIBuffer = OperatorProject.local().execute(aziEABuffere, projectionTransformationEA.getReverse(), null); - - Geometry projectedGeodesicAZI = OperatorProject.local().execute(geodesicBuffered, projectionTransformationEA, null); - Geometry reprojectedGeodesicAZI = OperatorProject.local().execute(projectedGeodesicAZI, projectionTransformationEA.getReverse(), null); - differenceVal = GeometryEngine.difference(reProjectedAZIBuffer, reprojectedGeodesicAZI, spatialReferenceWGS84).calculateArea2D(); - assertEquals(differenceVal, 0.0, 1e-14); - - differenceVal = GeometryEngine.difference(reProjectedAZIBuffer, geodesicBuffered, spatialReferenceWGS84).calculateArea2D(); - assertEquals(differenceVal, 0.0, 1e-14); - - assertTrue(GeometryEngine.contains(geodesicBuffered, reProjectedAZIBuffer, spatialReferenceWGS84)); + static { + System.loadLibrary("proj"); + } + + SpatialReference spatialReferenceWGS = SpatialReference.create(4326); + SpatialReference spatialReferenceMerc = SpatialReference.create(3857); + ProjectionTransformation projectionTransformationToMerc = new ProjectionTransformation(spatialReferenceWGS, spatialReferenceMerc); + ProjectionTransformation projectionTransformationToWGS = new ProjectionTransformation(spatialReferenceMerc, spatialReferenceWGS); + + @Before + public void setUp() { + + } + + @Test + public void testProj4() throws Exception { + PJ sourcePJ = new PJ("+init=epsg:32632"); // (x,y) axis order + PJ targetPJ = new PJ("+proj=latlong +datum=WGS84"); // (λ,φ) axis order + PJ sourcePJ_utm = new PJ("+proj=utm +zone=32 +datum=WGS84 +units=m +no_defs "); + PJ targetPJ_4326 = new PJ("+init=epsg:4326"); + + double[] coordinates_32632 = { + 500000, 0, // First coordinate + 400000, 100000, // Second coordinate + 600000, -100000 // Third coordinate + }; + + double[] coordinates_WGS84 = Arrays.copyOf(coordinates_32632, coordinates_32632.length); + double[] coordinates_4326 = Arrays.copyOf(coordinates_32632, coordinates_32632.length); + + sourcePJ.transform(targetPJ, 2, coordinates_WGS84, 0, 3); + sourcePJ.transform(targetPJ_4326, 2, coordinates_4326, 0, 3); + + for (int i = 0; i < coordinates_WGS84.length; i++) { + if (i == 1) + continue; + + assertTrue((Math.abs(coordinates_WGS84[i] - coordinates_32632[i])) > 1); + assertTrue((Math.abs(coordinates_4326[i] - coordinates_32632[i])) > 1); + assertEquals(coordinates_4326[i], coordinates_WGS84[i]); + } + } + + @Test + public void testProj4Strings() { + SpatialReference spatialReference = SpatialReference.create(4326); + assertEquals("+init=epsg:4326", spatialReference.getProj4()); + + spatialReference = SpatialReference.create(32632); + assertEquals("+init=epsg:32632", spatialReference.getProj4()); + } + + @Test + public void testProjectionCursor_1() { + ProjectionTransformation projectionTransformation = new ProjectionTransformation(SpatialReference.create(32632), SpatialReference.create(4326)); + Point point = new Point(500000, 0); + Point pointOut = (Point) OperatorProject.local().execute(point, projectionTransformation, null); + assertNotNull(pointOut); + assertTrue(Math.abs(point.getX() - pointOut.getY()) > 1); + projectionTransformation = new ProjectionTransformation(SpatialReference.create(4326), SpatialReference.create(32632)); + Point originalPoint = (Point) OperatorProject.local().execute(pointOut, projectionTransformation, null); + assertEquals(originalPoint.getX(), point.getX()); + assertEquals(originalPoint.getY(), point.getY()); + } + + @Test + public void testProjectMultiPoint() { + ProjectionTransformation projectionTransformation = new ProjectionTransformation(SpatialReference.create(32632), SpatialReference.create(4326)); + MultiPoint multiPoint = new MultiPoint(); + multiPoint.add(500000, 0); + multiPoint.add(400000, 100000); + multiPoint.add(600000, -100000); + MultiPoint multiPointOut = (MultiPoint) OperatorProject.local().execute(multiPoint, projectionTransformation, null); + assertNotNull(multiPointOut); + assertFalse(multiPointOut.equals(multiPoint)); + assertEquals(multiPoint.getPointCount(), multiPointOut.getPointCount()); + projectionTransformation = new ProjectionTransformation(SpatialReference.create(4326), SpatialReference.create(32632)); + MultiPoint originalMultiPoint = (MultiPoint) OperatorProject.local().execute(multiPointOut, projectionTransformation, null); + + for (int i = 0; i < multiPoint.getPointCount(); i++) { + assertEquals(multiPoint.getPoint(i).getX(), originalMultiPoint.getPoint(i).getX(), 1e-9); + assertEquals(multiPoint.getPoint(i).getY(), originalMultiPoint.getPoint(i).getY(), 1e-9); + } + } + + @Test + public void testProjectPolyline() { + ProjectionTransformation projectionTransformation = new ProjectionTransformation(SpatialReference.create(32632), SpatialReference.create(4326)); + Polyline polyline = new Polyline(); + polyline.startPath(500000, 0); + polyline.lineTo(400000, 100000); + polyline.lineTo(600000, -100000); + Polyline polylineOut = (Polyline) OperatorProject.local().execute(polyline, projectionTransformation, null); + assertNotNull(polylineOut); + assertFalse(polylineOut.equals(polyline)); + + MultiPathImpl polyline_impl = (MultiPathImpl) polylineOut._getImpl(); + int point_count = polyline_impl.getPointCount(); + int path_count = polyline_impl.getPathCount(); + assertEquals(point_count, 3); + assertEquals(path_count, 1); + + assertEquals(polyline.getPointCount(), polylineOut.getPointCount()); + projectionTransformation = new ProjectionTransformation(SpatialReference.create(4326), SpatialReference.create(32632)); + Polyline originalPolyline = (Polyline) OperatorProject.local().execute(polylineOut, projectionTransformation, null); + for (int i = 0; i < polyline.getPointCount(); i++) { + assertEquals(polyline.getPoint(i).getX(), originalPolyline.getPoint(i).getX(), 1e-9); + assertEquals(polyline.getPoint(i).getY(), originalPolyline.getPoint(i).getY(), 1e-9); + } + } + + @Test + public void testProjectPolygon() { + ProjectionTransformation projectionTransformation = new ProjectionTransformation(SpatialReference.create(32632), SpatialReference.create(4326)); + Polygon polygon = new Polygon(); + polygon.startPath(500000, 0); + polygon.lineTo(400000, 100000); + polygon.lineTo(600000, -100000); + polygon.closeAllPaths(); + Polygon polygonOut = (Polygon) OperatorProject.local().execute(polygon, projectionTransformation, null); + assertNotNull(polygonOut); + assertFalse(polygonOut.equals(polygon)); + assertEquals(polygon.getPointCount(), polygonOut.getPointCount()); + projectionTransformation = new ProjectionTransformation(SpatialReference.create(4326), SpatialReference.create(32632)); + Polygon originalPolygon = (Polygon) OperatorProject.local().execute(polygonOut, projectionTransformation, null); + + for (int i = 0; i < polygon.getPointCount(); i++) { + assertEquals(polygon.getPoint(i).getX(), originalPolygon.getPoint(i).getX(), 1e-9); + assertEquals(polygon.getPoint(i).getY(), originalPolygon.getPoint(i).getY(), 1e-9); + } + } + + @Test + public void testProjectEnvelope() { + Envelope2D envelope2D = new Envelope2D(-10000, -10000, 10000, 10000); + String proj4 = String.format( + "+proj=laea +lat_0=%f +lon_0=%f +x_0=0.0 +y_0=0.0 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs", + 0f, 0f); + + SpatialReference spatialReference = SpatialReference.createFromProj4(proj4); + SpatialReference spatialReferenceWgs84 = SpatialReference.create(4326); + ProjectionTransformation projectionTransformation = new ProjectionTransformation(spatialReference, spatialReferenceWgs84); + Polygon polygon = (Polygon) OperatorProject.local().execute(new Envelope(envelope2D), projectionTransformation, null); + assertNotNull(polygon); + Point2D point2D = new Point2D(); + double a = 6378137.0; // radius of spheroid for WGS_1984 + double e2 = 0.0066943799901413165; // ellipticity for WGS_1984 + Envelope2D gcsEnvelope = new Envelope2D(); + polygon.queryEnvelope2D(gcsEnvelope); + GeoDist.getEnvCenter(a, e2, gcsEnvelope, point2D); + assertEquals(point2D.x, 0, 1e-14); + assertEquals(point2D.y, 0, 1e-6); + + // TODO + } + + @Test + public void testEPSGCodes() { + String wktGeom = "MULTIPOLYGON (((6311583.246999994 1871386.1630000025, 6311570 1871325, 6311749.093999997 1871285.9699999988, 6311768.118000001 1871345.9619999975, 6311583.246999994 1871386.1630000025)))"; + SpatialReference spatialReference = SpatialReference.create(2230); + SpatialReference spatialReferenceWgs84 = SpatialReference.create(4326); + ProjectionTransformation projectionTransformation = new ProjectionTransformation(spatialReference, spatialReferenceWgs84); + SimpleStringCursor simpleStringCursor = new SimpleStringCursor(wktGeom); + OperatorImportFromWktCursor wktCursor = new OperatorImportFromWktCursor(0, simpleStringCursor); + OperatorProjectCursor projectCursor = new OperatorProjectCursor(wktCursor, projectionTransformation, null); + Geometry geometry = projectCursor.next(); + + assertNotNull(geometry); + } + + @Test + public void testFoldInto360() { + String wktGeom = "POLYGON((120 48.2246726495652,140 48.2246726495652,140 25.799891182088334,120 25.799891182088334,120 48.2246726495652))"; + SimpleStringCursor result = new SimpleStringCursor(wktGeom); + + OperatorImportFromWktCursor wktCursor = new OperatorImportFromWktCursor(0, result); + Geometry expectedGeometry = wktCursor.next(); + + String wktGeom360 = "POLYGON((480 48.2246726495652,500 48.2246726495652,500 25.799891182088334,480 25.799891182088334,480 48.2246726495652))"; + SimpleStringCursor test = new SimpleStringCursor(wktGeom360); + wktCursor = new OperatorImportFromWktCursor(0, test); + OperatorProjectCursor projectCursor = new OperatorProjectCursor(wktCursor, this.projectionTransformationToMerc, null); + OperatorProjectCursor reProjectCursor = new OperatorProjectCursor(projectCursor, this.projectionTransformationToWGS, null); + + Polygon actualGeometry = (Polygon) reProjectCursor.next(); + + assertTrue(GeometryEngine.equals(actualGeometry, expectedGeometry, spatialReferenceWGS)); + } + + + @Test + public void testWrapTriangle() { + String wktGeom = "POLYGON((167 30, 201 49, 199 18, 167 30))"; + SimpleStringCursor simpleStringCursor = new SimpleStringCursor(wktGeom); + OperatorImportFromWktCursor wktCursor = new OperatorImportFromWktCursor(0, simpleStringCursor); + OperatorProjectCursor projectCursor = new OperatorProjectCursor(wktCursor, this.projectionTransformationToMerc, null); + OperatorProjectCursor reProjectCursor = new OperatorProjectCursor(projectCursor, this.projectionTransformationToWGS, null); + + Polygon result = (Polygon) reProjectCursor.next(); + NonSimpleResult nonSimpleResult = new NonSimpleResult(); + OperatorSimplify simplify = (OperatorSimplify) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.Simplify); + boolean isSimple = simplify.isSimpleAsFeature(result, spatialReferenceWGS, true, nonSimpleResult, null); + + simpleStringCursor = new SimpleStringCursor(wktGeom); + wktCursor = new OperatorImportFromWktCursor(0, simpleStringCursor); + Polygon expected = (Polygon) wktCursor.next(); + assertTrue(GeometryEngine.isSimple(expected, spatialReferenceWGS)); + + assertEquals(expected.calculateArea2D(), result.calculateArea2D(), 1e-10); + } + + @Test + public void testWrapTriangleOtherSide() { + String wktGeom = "POLYGON((-193 -30, -160 -29, -158 -40, -193 -30))"; + SimpleStringCursor simpleStringCursor = new SimpleStringCursor(wktGeom); + OperatorImportFromWktCursor wktCursor = new OperatorImportFromWktCursor(0, simpleStringCursor); + OperatorProjectCursor projectCursor = new OperatorProjectCursor(wktCursor, this.projectionTransformationToMerc, null); + OperatorProjectCursor reProjectCursor = new OperatorProjectCursor(projectCursor, this.projectionTransformationToWGS, null); + + Polygon result = (Polygon) reProjectCursor.next(); + NonSimpleResult nonSimpleResult = new NonSimpleResult(); + OperatorSimplify simplify = (OperatorSimplify) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.Simplify); + boolean isSimple = simplify.isSimpleAsFeature(result, spatialReferenceWGS, true, nonSimpleResult, null); + + simpleStringCursor = new SimpleStringCursor(wktGeom); + wktCursor = new OperatorImportFromWktCursor(0, simpleStringCursor); + Polygon expected = (Polygon) wktCursor.next(); + assertTrue(GeometryEngine.isSimple(expected, spatialReferenceWGS)); + + assertEquals(expected.calculateArea2D(), result.calculateArea2D(), .00000000001); + } + + @Test + public void testWrap() { + String wktGeom = "POLYGON((167.87109375 30.751277776257812," + + "201.43359375 49.38237278700955," + + "232.49609375 -5.266007882805485," + + "116.19500625 -17.308687886770024," + + "199.54296875 18.979025953255267," + + "126.03515625 12.897489183755892," + + "167.87109375 30.751277776257812))"; + SimpleStringCursor simpleStringCursor = new SimpleStringCursor(wktGeom); + OperatorImportFromWktCursor wktCursor = new OperatorImportFromWktCursor(0, simpleStringCursor); + OperatorProjectCursor projectCursor = new OperatorProjectCursor(wktCursor, this.projectionTransformationToMerc, null); + OperatorProjectCursor reProjectCursor = new OperatorProjectCursor(projectCursor, this.projectionTransformationToWGS, null); + + Polygon result = (Polygon) reProjectCursor.next(); + NonSimpleResult nonSimpleResult = new NonSimpleResult(); + OperatorSimplify simplify = (OperatorSimplify) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.Simplify); + boolean isSimple = simplify.isSimpleAsFeature(result, spatialReferenceWGS, true, nonSimpleResult, null); + + simpleStringCursor = new SimpleStringCursor(wktGeom); + wktCursor = new OperatorImportFromWktCursor(0, simpleStringCursor); + Polygon expected = (Polygon) wktCursor.next(); + assertTrue(GeometryEngine.isSimple(expected, spatialReferenceWGS)); + + assertEquals(expected.calculateArea2D(), result.calculateArea2D(), 1e-10); + } + + @Test + public void testAlbers() { + String wktGeom = "MULTIPOLYGON (((40 40, 20 45, 45 30, 40 40)), ((20 35, 45 20, 30 5, 10 10, 10 30, 20 35), (30 20, 20 25, 20 15, 30 20)))"; + + SpatialReference sr = SpatialReference.create(2163); + ProjectionTransformation projectionTransformation = new ProjectionTransformation(spatialReferenceWGS, sr); + + SimpleStringCursor simpleStringCursor = new SimpleStringCursor(wktGeom); + OperatorImportFromWktCursor wktCursor = new OperatorImportFromWktCursor(0, simpleStringCursor); + OperatorProjectCursor projectCursor = new OperatorProjectCursor(wktCursor, projectionTransformation, null); + OperatorProjectCursor reProjectCursor = new OperatorProjectCursor(projectCursor, projectionTransformation.getReverse(), null); + + Polygon result = (Polygon) reProjectCursor.next(); + NonSimpleResult nonSimpleResult = new NonSimpleResult(); + OperatorSimplify simplify = (OperatorSimplify) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.Simplify); + boolean isSimple = simplify.isSimpleAsFeature(result, spatialReferenceWGS, true, nonSimpleResult, null); + assertTrue(isSimple); + + simpleStringCursor = new SimpleStringCursor(wktGeom, 99); + wktCursor = new OperatorImportFromWktCursor(0, simpleStringCursor); + Polygon expected = (Polygon) wktCursor.next(); + assertTrue(GeometryEngine.isSimple(expected, spatialReferenceWGS)); + assertEquals(wktCursor.getGeometryID(), 99); + assertEquals(expected.calculateArea2D(), result.calculateArea2D(), 1e-10); + } + + @Test + public void testProjectionTransformation() { + int count = 400; + Envelope e = new Envelope(0, 0, 40, 40); + RandomCoordinateGenerator randomCoordinateGenerator = new RandomCoordinateGenerator(count, e, SpatialReference.create(4326).getTolerance()); + MultiPoint multiPoint = new MultiPoint(); + for (int i = 0; i < count; i++) { + multiPoint.add(randomCoordinateGenerator._GenerateNewPoint()); + } + + ProjectionTransformation projectionTransformation = ProjectionTransformation.getEqualArea(multiPoint, spatialReferenceWGS); + Geometry projected = OperatorProject.local().execute(multiPoint, projectionTransformation, null); + Geometry reprojected = OperatorProject.local().execute(projected, projectionTransformation.getReverse(), null); + + assertTrue(OperatorEquals.local().execute(reprojected, multiPoint, SpatialReference.create(104919), null)); + + Geometry reProjectedConvexhull = OperatorProject.local().execute(OperatorConvexHull.local().execute(projected, null), projectionTransformation.getReverse(), null); + Geometry convexHull = OperatorConvexHull.local().execute(multiPoint, null); + + assertEquals(convexHull.calculateArea2D(), reProjectedConvexhull.calculateArea2D(), 1); + } + + @Test + public void testGeometryEnvelope() { + MultiPoint multiPoint = new MultiPoint(); + multiPoint.add(0, 0); + multiPoint.add(0, 20); + multiPoint.add(40, 40); + + ProjectionTransformation projectionTransformation = ProjectionTransformation.getEqualArea(multiPoint, spatialReferenceWGS); + Geometry projected = OperatorProject.local().execute(multiPoint, projectionTransformation, null); + + Envelope2D envelope2D = new Envelope2D(); + projected.queryEnvelope2D(envelope2D); + + assertTrue(envelope2D.xmax != 40); + + } + + @Test + public void testDateline() { + String wktGeom = "POLYGON((-185 0, -185 10, -170 10, -170 0),(-182 3, -172 3, -172 7, -182 7))"; + Geometry original = OperatorImportFromWkt.local().execute( + 0, + Geometry.Type.Unknown, + wktGeom, + null); + Geometry projected = OperatorProject.local().execute( + original, + projectionTransformationToMerc, null); + + assertNotNull(projected); + + Geometry reProjected = OperatorProject.local().execute(projected, projectionTransformationToWGS, null); + assertNotNull(reProjected); + + NonSimpleResult nonSimpleResult = new NonSimpleResult(); + assertTrue(OperatorSimplify.local().isSimpleAsFeature(reProjected, spatialReferenceWGS, true, nonSimpleResult, null)); + + assertEquals(original.calculateArea2D(), reProjected.calculateArea2D(), 0.00001); + } + + @Test + public void testWrapNotWGS84() { + String wktGeom = "POLYGON((-185 0, -185 10, -170 10, -170 0),(-182 3, -172 3, -172 7, -182 7))"; + Geometry original = OperatorImportFromWkt.local().execute(0, Geometry.Type.Unknown, wktGeom, null); + ProjectionTransformation projectionTransformation = new ProjectionTransformation(SpatialReference.create(4269), spatialReferenceMerc); + Geometry projected = OperatorProject.local().execute( + original, + projectionTransformation, null); + + assertNotNull(projected); + + Geometry reProjected = OperatorProject.local().execute(projected, projectionTransformation.getReverse(), null); + assertNotNull(reProjected); + + NonSimpleResult nonSimpleResult = new NonSimpleResult(); + assertTrue(OperatorSimplify.local().isSimpleAsFeature(reProjected, SpatialReference.create(4269), true, nonSimpleResult, null)); + + assertEquals(original.calculateArea2D(), reProjected.calculateArea2D(), 0.00001); + } + + @Test + public void testReProjectMultiPoint() { + MultiPoint multiPoint = new MultiPoint(); + for (double longitude = -180; longitude < 180; longitude += 10.0) { + for (double latitude = -80; latitude < 80; latitude += 10.0) { + multiPoint.add(longitude, latitude); + } + } + + ProjectionTransformation projectionTransformation = new ProjectionTransformation(SpatialReference.create(4326), SpatialReference.create(3857)); + Geometry projected = OperatorProject.local().execute(multiPoint, projectionTransformation, null); + Geometry reprojected = OperatorProject.local().execute(projected, projectionTransformation.getReverse(), null); + + assertTrue(OperatorEquals.local().execute(multiPoint, reprojected, SpatialReference.create(4326), null)); + } + + @Test + public void testExampleLAEA_bug() { + double distance = 800; + SpatialReference spatialReferenceLAEAeurope = SpatialReference.create(3035); + ProjectionTransformation projectionTransformation = new ProjectionTransformation(spatialReferenceWGS, spatialReferenceLAEAeurope); + Point europeCenter = new Point(10, 52); + Point projectedCenter = (Point) OperatorProject.local().execute(europeCenter, projectionTransformation, null); + Point projected200mN = new Point(projectedCenter.getX(), projectedCenter.getY() + distance); + Point projected200mE = new Point(projectedCenter.getX() - distance, projectedCenter.getY()); + Point reprojected200mN = (Point) OperatorProject.local().execute(projected200mN, projectionTransformation.getReverse(), null); + Point reprojected200mE = (Point) OperatorProject.local().execute(projected200mE, projectionTransformation.getReverse(), null); + double distanceEast = SpatialReferenceImpl.geodesicDistanceOnWGS84Impl(europeCenter, reprojected200mE); + double distanceNorth = SpatialReferenceImpl.geodesicDistanceOnWGS84Impl(europeCenter, reprojected200mN); + assertEquals(distance, distanceNorth, 1e-3); + assertEquals(distance, distanceEast, 1e-6); + + projectionTransformation = null; + ProjectionTransformation projectionTransformationEA = ProjectionTransformation.getEqualArea(europeCenter, spatialReferenceWGS); + projectedCenter = (Point) OperatorProject.local().execute(europeCenter, projectionTransformationEA, null); + projected200mN = new Point(projectedCenter.getX(), projectedCenter.getY() + distance); + projected200mE = new Point(projectedCenter.getX() - distance, projectedCenter.getY()); + reprojected200mN = (Point) OperatorProject.local().execute(projected200mN, projectionTransformationEA.getReverse(), null); + reprojected200mE = (Point) OperatorProject.local().execute(projected200mE, projectionTransformationEA.getReverse(), null); + distanceEast = SpatialReferenceImpl.geodesicDistanceOnWGS84Impl(europeCenter, reprojected200mE); + distanceNorth = SpatialReferenceImpl.geodesicDistanceOnWGS84Impl(europeCenter, reprojected200mN); + assertEquals(distance, distanceNorth, 1e-3); + assertEquals(distance, distanceEast, 1e-6); + + } + + @Test + public void testEqualAreaProjection() { + // POINT (-70.651886936570745 43.134525834585826) + Point point = new Point(-70.651886936570745, 43.1345088834585826); + SpatialReference utmZone = SpatialReference.createUTM(point); + SpatialReference spatialReferenceWGS84 = SpatialReference.create(4326); + ProjectionTransformation projectionTransformationUTM = new ProjectionTransformation(spatialReferenceWGS84, utmZone); + + ProjectionTransformation projectionTransformationEA = ProjectionTransformation.getEqualArea(point, spatialReferenceWGS84); + + Geometry geodesicBuffered = OperatorGeodesicBuffer.local().execute( + point, + spatialReferenceWGS84, + GeodeticCurveType.Geodesic, + 135.7, + 0.07, + false, + null); + + Geometry utmPoint = GeometryEngine.project(point, spatialReferenceWGS84, utmZone); + Geometry utmPoint2 = OperatorProject.local().execute(point, projectionTransformationUTM, null); + assertTrue(utmPoint.equals(utmPoint2)); + + Geometry utmReprojected = GeometryEngine.project(utmPoint, utmZone, spatialReferenceWGS84); + Geometry utmReprojected2 = OperatorProject.local().execute(utmPoint2, projectionTransformationUTM.getReverse(), null); + assertTrue(utmReprojected.equals(utmReprojected2)); + + Geometry utmBuffer = OperatorBuffer.local().execute(utmPoint, utmZone, 135.6, null); + Geometry reProjectedUTMBuffer = OperatorProject.local().execute(utmBuffer, projectionTransformationUTM.getReverse(), null); + double differenceVal = GeometryEngine.difference(reProjectedUTMBuffer, geodesicBuffered, spatialReferenceWGS84).calculateArea2D(); + assertEquals(differenceVal, 0.0, 1e-14); + + assertTrue(GeometryEngine.contains(geodesicBuffered, reProjectedUTMBuffer, spatialReferenceWGS84)); + + Geometry aziEAPoint = OperatorProject.local().execute(point, projectionTransformationEA, null); + Geometry aziEABuffere = OperatorBuffer.local().execute(aziEAPoint, null, 135.6, null); + assertEquals(aziEABuffere.calculateArea2D(), utmBuffer.calculateArea2D(), 1e-5); + Geometry reProjectedAZIBuffer = OperatorProject.local().execute(aziEABuffere, projectionTransformationEA.getReverse(), null); + + Geometry projectedGeodesicAZI = OperatorProject.local().execute(geodesicBuffered, projectionTransformationEA, null); + Geometry reprojectedGeodesicAZI = OperatorProject.local().execute(projectedGeodesicAZI, projectionTransformationEA.getReverse(), null); + differenceVal = GeometryEngine.difference(reProjectedAZIBuffer, reprojectedGeodesicAZI, spatialReferenceWGS84).calculateArea2D(); + assertEquals(differenceVal, 0.0, 1e-14); + + differenceVal = GeometryEngine.difference(reProjectedAZIBuffer, geodesicBuffered, spatialReferenceWGS84).calculateArea2D(); + assertEquals(differenceVal, 0.0, 1e-14); + + assertTrue(GeometryEngine.contains(geodesicBuffered, reProjectedAZIBuffer, spatialReferenceWGS84)); // String wktInput = "POLYGON ((-70.651656936570745 43.135157834585826,-70.651570654984525 43.135150076925918,-70.651511247908587 43.135155437576621,-70.651490101488349 43.135169249238288,-70.651490181628759 43.135200763774868,-70.651510336532638 43.135249995303397,-70.651530872098633 43.135299226354952,-70.651527230781454 43.13533529757845,-70.651492643684321 43.135349305799508,-70.651420172717224 43.135354852495823,-70.651358095085072 43.135346745253884,-70.651247805462717 43.1353033127629,-70.651151361236941 43.135246176463966,-70.651124154255413 43.135206047556842,-70.651131534466685 43.135151913051203,-70.651155914561755 43.135115542493892,-70.651228730259774 43.135051460048707,-70.651305024993519 43.134973818456416,-70.651360674948108 43.134914489191551,-70.651385078643216 43.134864606853156,-70.651402827815218 43.134810322639233,-70.651410310326284 43.134688652284062,-70.651418383361829 43.134517448375689,-70.651411370788409 43.134413996600074,-70.651390876709428 43.134337752182525,-70.651346755889904 43.134225832686319,-70.651289063395154 43.134109606122337,-70.651241242017051 43.133988734078358,-70.651230854601764 43.133916846697829,-70.651221013347353 43.133822439140715,-70.651200519724128 43.133746194674302,-70.651218268699594 43.133691907971006,-70.651249886578313 43.133610413350418,-70.651273539332081 43.133547040518167,-70.651322422674269 43.133478800504768,-70.651363984222755 43.133424172537978,-70.651409243106613 43.133378494590104,-70.651437060507462 43.133355583224628,-70.651492145648632 43.133332275678633,-70.651599176252716 43.133326227462319,-70.651675204034788 43.133338636120527,-70.65179240423565 43.133381970944697,-70.651905761872953 43.133425353603045,-70.652022336036893 43.133459688797871,-70.652087607730962 43.133472252516562,-70.652149929372143 43.133489361765591,-70.652232540703992 43.133546699550045,-70.652400720753761 43.133656829257461,-70.652544825855031 43.133771806958798,-70.652698657471404 43.133891149422396,-70.652757214424 43.133939825915917,-70.652829643131227 43.133961291723317,-70.652898471779253 43.133991812912122,-70.652963804959569 43.134049399817712,-70.652994471176711 43.134089475795044,-70.653000937222828 43.134129903976664,-70.653000836372897 43.134197439788288,-70.652972837499135 43.134256375321229,-70.652903321638362 43.134342923487154,-70.652853489821567 43.134447199036558,-70.652811504521395 43.134528843881576,-70.652776716073816 43.134592380423527,-70.652745241323998 43.134664872485907,-70.652703717209832 43.134692485155824,-70.65263790191085 43.134702441934749,-70.652537796343609 43.13469488220742,-70.652481986521252 43.134691185475688,-70.652416288981371 43.134705643385693,-70.652347112331768 43.134733655368471,-70.652274294763117 43.134797738553125,-70.652239263183958 43.134852272522785,-70.652214826278225 43.134929165745028,-70.652217533236211 43.135001163954207,-70.652248296977746 43.135059250554882,-70.652327397729081 43.135157158057844,-70.652414500765317 43.135209927851037,-70.6524582645621 43.135222802033766,-70.652470512866358 43.135249637832572,-70.652646824360005 43.135404671730349,-70.652680946443695 43.135444700365824,-70.652691113545444 43.135494075980539,-70.652680299690047 43.135534756334458,-70.652642334694633 43.135580326883741,-70.6525831461027 43.135608197219,-70.65250042683256 43.135618395612582,-70.652403778584315 43.1356107883392,-70.652328034290349 43.135580366756479,-70.652207940292428 43.135501055652128,-70.652084166050003 43.135399292820672,-70.651994153636025 43.135324055019495,-70.651897729053019 43.13525341019011,-70.651815354685922 43.135205077192047,-70.651746646220062 43.135179051560065,-70.651656936570745 43.135157834585826))"; // Geometry input = OperatorImportFromWkt.local().execute(0, Geometry.Type.Unknown, wktInput, null); @@ -487,85 +487,85 @@ public void testEqualAreaProjection() { // assertEquals(encircled.calculateArea2D(), buffered.calculateArea2D(), 1e-8); // assertEquals(0.0, OperatorDifference.local().execute(encircled, buffered, spatialReferenceWGS84, null).calculateArea2D(), 1e-8); // assertTrue(GeometryEngine.contains(buffered, encircled, spatialReferenceWGS84)); - } - - @Test - public void testETRS() { - Point point = new Point(-180, -90); - SimpleGeometryCursor simpleGeometryCursor = new SimpleGeometryCursor(point); - - ProjectionTransformation projectionTransformation = new ProjectionTransformation(SpatialReference.create(4326), SpatialReference.create(3035)); - OperatorProjectCursor projectCursor = new OperatorProjectCursor(simpleGeometryCursor, projectionTransformation, null); - OperatorProjectCursor reProjectCursor = new OperatorProjectCursor(projectCursor, projectionTransformation.getReverse(), null); - - while (reProjectCursor.hasNext()) { - Geometry geometry = reProjectCursor.next(); - assertTrue(geometry.isEmpty()); - } - } - - @Test - public void testProjectionTrans() { - Point point = new Point(-180, -90); - SimpleGeometryCursor simpleGeometryCursor = new SimpleGeometryCursor(point); - - ProjectionTransformation projectionTransformation = new ProjectionTransformation(null, SpatialReference.create(3035)); - OperatorProjectCursor projectCursor = new OperatorProjectCursor(simpleGeometryCursor, projectionTransformation, null); - OperatorProjectCursor reProjectCursor = new OperatorProjectCursor(projectCursor, projectionTransformation.getReverse(), null); - - - while (reProjectCursor.hasNext()) { - try { - Geometry geometry = reProjectCursor.next(); - assertTrue(geometry.isEmpty()); - } catch (GeometryException exception) { - assertEquals("From and To Spatial references required to Project Geometry", exception.getMessage()); - } - } - } - - @Test - public void testIdNumber() { - assertEquals(0, Project.ordinal()); - assertEquals(1, ExportToJson.ordinal()); - } - - @Test - public void testAziEA() { - MultiPoint multiPoint = new MultiPoint(); - multiPoint.add(0,0); - multiPoint.add(1,1); - multiPoint.add(-1,-1); - SpatialReference utmZone = SpatialReference.createUTM(multiPoint); - SpatialReference spatialReferenceWGS84 = SpatialReference.create(4326); - ProjectionTransformation projectionTransformationUTM = new ProjectionTransformation(spatialReferenceWGS84, utmZone); - Geometry utmPoint = GeometryEngine.project(multiPoint, spatialReferenceWGS84, utmZone); - Geometry utmPoint2 = OperatorProject.local().execute(multiPoint, projectionTransformationUTM, null); - ProjectionTransformation projectionTransformationAziUTM = ProjectionTransformation.getEqualArea(utmPoint, utmZone); - ProjectionTransformation projectionTransformationAziWGS84 = ProjectionTransformation.getEqualArea(multiPoint, spatialReferenceWGS84); - Geometry aziPoint1 = OperatorProject.local().execute(utmPoint, projectionTransformationAziUTM, null); - Geometry aziPoint2 = OperatorProject.local().execute(multiPoint, projectionTransformationAziWGS84, null); - assertEquals(((MultiPoint)aziPoint1).getPoint(0).getX(), ((MultiPoint)aziPoint2).getPoint(0).getX()); - Geometry utmPoint1 = OperatorProject.local().execute(aziPoint1, projectionTransformationAziUTM.getReverse(), null); - Geometry wgs84Point1 = OperatorProject.local().execute(aziPoint2, projectionTransformationAziWGS84.getReverse(), null); - Geometry wgs84Point2 = OperatorProject.local().execute(utmPoint1, projectionTransformationUTM.getReverse(), null); - assertEquals(((MultiPoint)wgs84Point1).getPoint(2).getY(), ((MultiPoint)wgs84Point2).getPoint(2).getY(), 0.0000000000001); - } - - @Test - public void testAziRoundTrip() { - String wkt= "POLYGON ((39.99430071558862 19.99640653733888, 39.99430071558862 20.00359325081125, 40.00569928441138 20.00359325081125, 40.00569928441138 19.99640653733888, 39.99430071558862 19.99640653733888))"; - Geometry geometry = GeometryEngine.geometryFromWkt(wkt, 0, Geometry.Type.Unknown); - SpatialReference spatialReference4326 = SpatialReference.create(4326); - SpatialReference spatialReferenceAzi = SpatialReference.createEqualArea(40, 20); - - // TODO this loses data - Geometry roundTrip = GeometryEngine.project(GeometryEngine.project(geometry, spatialReference4326, spatialReferenceAzi), spatialReferenceAzi, spatialReference4326); - // TODO lost data if it were assertTrue(GeometryEngine.equals(geometry, roundTrip, spatialReference4326)); it would faile - - Geometry buffered = GeometryEngine.buffer(geometry, spatialReference4326, spatialReference4326.getTolerance() * 3); - assertTrue(GeometryEngine.contains(buffered, roundTrip, spatialReference4326)); - Geometry difference = GeometryEngine.difference(geometry, roundTrip, spatialReference4326); - assertEquals(difference.calculateArea2D(), 0.0); - } + } + + @Test + public void testETRS() { + Point point = new Point(-180, -90); + SimpleGeometryCursor simpleGeometryCursor = new SimpleGeometryCursor(point); + + ProjectionTransformation projectionTransformation = new ProjectionTransformation(SpatialReference.create(4326), SpatialReference.create(3035)); + OperatorProjectCursor projectCursor = new OperatorProjectCursor(simpleGeometryCursor, projectionTransformation, null); + OperatorProjectCursor reProjectCursor = new OperatorProjectCursor(projectCursor, projectionTransformation.getReverse(), null); + + while (reProjectCursor.hasNext()) { + Geometry geometry = reProjectCursor.next(); + assertTrue(geometry.isEmpty()); + } + } + + @Test + public void testProjectionTrans() { + Point point = new Point(-180, -90); + SimpleGeometryCursor simpleGeometryCursor = new SimpleGeometryCursor(point); + + ProjectionTransformation projectionTransformation = new ProjectionTransformation(null, SpatialReference.create(3035)); + OperatorProjectCursor projectCursor = new OperatorProjectCursor(simpleGeometryCursor, projectionTransformation, null); + OperatorProjectCursor reProjectCursor = new OperatorProjectCursor(projectCursor, projectionTransformation.getReverse(), null); + + + while (reProjectCursor.hasNext()) { + try { + Geometry geometry = reProjectCursor.next(); + assertTrue(geometry.isEmpty()); + } catch (GeometryException exception) { + assertEquals("From and To Spatial references required to Project Geometry", exception.getMessage()); + } + } + } + + @Test + public void testIdNumber() { + assertEquals(0, Project.ordinal()); + assertEquals(1, ExportToJson.ordinal()); + } + + @Test + public void testAziEA() { + MultiPoint multiPoint = new MultiPoint(); + multiPoint.add(0, 0); + multiPoint.add(1, 1); + multiPoint.add(-1, -1); + SpatialReference utmZone = SpatialReference.createUTM(multiPoint); + SpatialReference spatialReferenceWGS84 = SpatialReference.create(4326); + ProjectionTransformation projectionTransformationUTM = new ProjectionTransformation(spatialReferenceWGS84, utmZone); + Geometry utmPoint = GeometryEngine.project(multiPoint, spatialReferenceWGS84, utmZone); + Geometry utmPoint2 = OperatorProject.local().execute(multiPoint, projectionTransformationUTM, null); + ProjectionTransformation projectionTransformationAziUTM = ProjectionTransformation.getEqualArea(utmPoint, utmZone); + ProjectionTransformation projectionTransformationAziWGS84 = ProjectionTransformation.getEqualArea(multiPoint, spatialReferenceWGS84); + Geometry aziPoint1 = OperatorProject.local().execute(utmPoint, projectionTransformationAziUTM, null); + Geometry aziPoint2 = OperatorProject.local().execute(multiPoint, projectionTransformationAziWGS84, null); + assertEquals(((MultiPoint) aziPoint1).getPoint(0).getX(), ((MultiPoint) aziPoint2).getPoint(0).getX()); + Geometry utmPoint1 = OperatorProject.local().execute(aziPoint1, projectionTransformationAziUTM.getReverse(), null); + Geometry wgs84Point1 = OperatorProject.local().execute(aziPoint2, projectionTransformationAziWGS84.getReverse(), null); + Geometry wgs84Point2 = OperatorProject.local().execute(utmPoint1, projectionTransformationUTM.getReverse(), null); + assertEquals(((MultiPoint) wgs84Point1).getPoint(2).getY(), ((MultiPoint) wgs84Point2).getPoint(2).getY(), 0.0000000000001); + } + + @Test + public void testAziRoundTrip() { + String wkt = "POLYGON ((39.99430071558862 19.99640653733888, 39.99430071558862 20.00359325081125, 40.00569928441138 20.00359325081125, 40.00569928441138 19.99640653733888, 39.99430071558862 19.99640653733888))"; + Geometry geometry = GeometryEngine.geometryFromWkt(wkt, 0, Geometry.Type.Unknown); + SpatialReference spatialReference4326 = SpatialReference.create(4326); + SpatialReference spatialReferenceAzi = SpatialReference.createEqualArea(40, 20); + + // TODO this loses data + Geometry roundTrip = GeometryEngine.project(GeometryEngine.project(geometry, spatialReference4326, spatialReferenceAzi), spatialReferenceAzi, spatialReference4326); + // TODO lost data if it were assertTrue(GeometryEngine.equals(geometry, roundTrip, spatialReference4326)); it would faile + + Geometry buffered = GeometryEngine.buffer(geometry, spatialReference4326, spatialReference4326.getTolerance() * 3); + assertTrue(GeometryEngine.contains(buffered, roundTrip, spatialReference4326)); + Geometry difference = GeometryEngine.difference(geometry, roundTrip, spatialReference4326); + assertEquals(difference.calculateArea2D(), 0.0); + } } \ No newline at end of file diff --git a/src/test/java/com/esri/core/geometry/TestProjectionGigsData.java b/src/test/java/com/esri/core/geometry/TestProjectionGigsData.java index c7a6ec3f..3f31ea00 100644 --- a/src/test/java/com/esri/core/geometry/TestProjectionGigsData.java +++ b/src/test/java/com/esri/core/geometry/TestProjectionGigsData.java @@ -29,155 +29,155 @@ */ @RunWith(Parameterized.class) public class TestProjectionGigsData extends TestCase { - static { - System.loadLibrary("proj"); - } - - private Path path; - private String testName; - private String testData; - private String description; - private double[] tolConversions_i0 = new double[3]; - private double[] tolConversions_i1 = new double[3]; - private double[] tolRoundTrips_i0 = new double[3]; - private double[] tolRoundTrips_i1 = new double[3]; - private int roundTripTimes; - ProjectionTransformation leftToRightTransformation; - ProjectionTransformation rightToLeftTransformation; - private Polyline leftPolyline = new Polyline(); - private Polyline rightPolyline = new Polyline(); - private Polygon leftPolygon = new Polygon(); - private Polygon rightPolygon = new Polygon(); - private MultiPoint leftMultiPoint = new MultiPoint(); - private MultiPoint rightMultiPoint = new MultiPoint(); - - - public TestProjectionGigsData(Path path, String testName) throws java.io.IOException, org.json.JSONException { - this.path = path; - this.testName = testName; - // http://www.adam-bien.com/roller/abien/entry/java_8_reading_a_file - String content = new String(Files.readAllBytes(path), Charset.defaultCharset()); - JSONObject obj = new JSONObject(content); - this.description = obj.getString("description"); - JSONArray projectionsItems = obj.getJSONArray("projections"); - String leftProjection = projectionsItems.getString(0); - String rightProjection = projectionsItems.getString(1); - SpatialReference leftSR = SpatialReference.createFromProj4(leftProjection); - SpatialReference rightSR = SpatialReference.createFromProj4(rightProjection); - this.leftToRightTransformation = new ProjectionTransformation(leftSR, rightSR); - this.rightToLeftTransformation = new ProjectionTransformation(rightSR, leftSR); - - JSONArray coordinatePairs = obj.getJSONArray("coordinates"); - for (int i = 0; i < coordinatePairs.length(); i++) { - JSONArray coordinatePair = coordinatePairs.getJSONArray(i); - JSONArray pt1Array = coordinatePair.getJSONArray(0); - JSONArray pt2Array = coordinatePair.getJSONArray(1); - - Point pt1 = new Point(pt1Array.getDouble(0), pt1Array.getDouble(1)); - Point pt2 = new Point(pt2Array.getDouble(0), pt2Array.getDouble(1)); - if (coordinatePair.getJSONArray(0).length() == 3) { - // if point3d - pt1.setZ(pt1Array.getDouble(2)); - pt2.setZ(pt2Array.getDouble(2)); - } - - if (i == 0) { - leftPolyline.startPath(pt1); - leftPolygon.startPath(pt1); - - rightPolyline.startPath(pt2); - rightPolygon.startPath(pt2); - } else { - leftPolyline.lineTo(pt1); - leftPolygon.lineTo(pt1); - - rightPolyline.lineTo(pt2); - rightPolygon.lineTo(pt2); - } - leftMultiPoint.add(pt1); - rightMultiPoint.add(pt2); - } - - leftPolygon.closeAllPaths(); - rightPolygon.closeAllPaths(); - - /* - * "tests": [ - * {"tolerances": [2.7777777777777776e-07, 0.03], "type": "conversion"}, - * {"times": 1000, "tolerances": [5.555555555555556e-08, 0.006], "type": "roundtrip"}] - * */ - JSONArray testsItems = obj.getJSONArray("tests"); - JSONObject testObj1 = testsItems.getJSONObject(0); - JSONObject testObj2 = testsItems.getJSONObject(1); - - double tolObj1Index1 = testObj1.getJSONArray("tolerances").getDouble(0); - double tolObj2Index1 = testObj2.getJSONArray("tolerances").getDouble(0); - double[] tolObj1Index2 = new double[3]; - double[] tolObj2Index2 = new double[3]; - - if (testObj1.getJSONArray("tolerances").optJSONArray(1) != null) { - for (int i = 0; i < 3; i++) { - tolObj1Index2[i] = testObj1.getJSONArray("tolerances").getJSONArray(1).getDouble(i); - tolObj2Index2[i] = testObj2.getJSONArray("tolerances").getJSONArray(1).getDouble(i); - } - } else { - Arrays.fill(tolObj1Index2, testObj1.getJSONArray("tolerances").getDouble(1)); - Arrays.fill(tolObj2Index2, testObj2.getJSONArray("tolerances").getDouble(1)); - } - - if (testObj1.getString("type").equals("conversion")) { - Arrays.fill(this.tolConversions_i0, tolObj1Index1); - this.tolConversions_i1 = tolObj1Index2; - - Arrays.fill(this.tolRoundTrips_i0, tolObj2Index1); - this.tolRoundTrips_i1 = tolObj2Index2; - this.roundTripTimes = testObj2.getInt("times"); - } else { - Arrays.fill(this.tolConversions_i0, tolObj2Index1); - this.tolConversions_i1 = tolObj2Index2; - - Arrays.fill(this.tolRoundTrips_i0, tolObj1Index1); - this.tolRoundTrips_i1 = tolObj1Index2; - this.roundTripTimes = testObj1.getInt("times"); - } + static { + System.loadLibrary("proj"); + } + + private Path path; + private String testName; + private String testData; + private String description; + private double[] tolConversions_i0 = new double[3]; + private double[] tolConversions_i1 = new double[3]; + private double[] tolRoundTrips_i0 = new double[3]; + private double[] tolRoundTrips_i1 = new double[3]; + private int roundTripTimes; + ProjectionTransformation leftToRightTransformation; + ProjectionTransformation rightToLeftTransformation; + private Polyline leftPolyline = new Polyline(); + private Polyline rightPolyline = new Polyline(); + private Polygon leftPolygon = new Polygon(); + private Polygon rightPolygon = new Polygon(); + private MultiPoint leftMultiPoint = new MultiPoint(); + private MultiPoint rightMultiPoint = new MultiPoint(); + + + public TestProjectionGigsData(Path path, String testName) throws java.io.IOException, org.json.JSONException { + this.path = path; + this.testName = testName; + // http://www.adam-bien.com/roller/abien/entry/java_8_reading_a_file + String content = new String(Files.readAllBytes(path), Charset.defaultCharset()); + JSONObject obj = new JSONObject(content); + this.description = obj.getString("description"); + JSONArray projectionsItems = obj.getJSONArray("projections"); + String leftProjection = projectionsItems.getString(0); + String rightProjection = projectionsItems.getString(1); + SpatialReference leftSR = SpatialReference.createFromProj4(leftProjection); + SpatialReference rightSR = SpatialReference.createFromProj4(rightProjection); + this.leftToRightTransformation = new ProjectionTransformation(leftSR, rightSR); + this.rightToLeftTransformation = new ProjectionTransformation(rightSR, leftSR); + + JSONArray coordinatePairs = obj.getJSONArray("coordinates"); + for (int i = 0; i < coordinatePairs.length(); i++) { + JSONArray coordinatePair = coordinatePairs.getJSONArray(i); + JSONArray pt1Array = coordinatePair.getJSONArray(0); + JSONArray pt2Array = coordinatePair.getJSONArray(1); + + Point pt1 = new Point(pt1Array.getDouble(0), pt1Array.getDouble(1)); + Point pt2 = new Point(pt2Array.getDouble(0), pt2Array.getDouble(1)); + if (coordinatePair.getJSONArray(0).length() == 3) { + // if point3d + pt1.setZ(pt1Array.getDouble(2)); + pt2.setZ(pt2Array.getDouble(2)); + } + + if (i == 0) { + leftPolyline.startPath(pt1); + leftPolygon.startPath(pt1); + + rightPolyline.startPath(pt2); + rightPolygon.startPath(pt2); + } else { + leftPolyline.lineTo(pt1); + leftPolygon.lineTo(pt1); + + rightPolyline.lineTo(pt2); + rightPolygon.lineTo(pt2); + } + leftMultiPoint.add(pt1); + rightMultiPoint.add(pt2); + } + + leftPolygon.closeAllPaths(); + rightPolygon.closeAllPaths(); + + /* + * "tests": [ + * {"tolerances": [2.7777777777777776e-07, 0.03], "type": "conversion"}, + * {"times": 1000, "tolerances": [5.555555555555556e-08, 0.006], "type": "roundtrip"}] + * */ + JSONArray testsItems = obj.getJSONArray("tests"); + JSONObject testObj1 = testsItems.getJSONObject(0); + JSONObject testObj2 = testsItems.getJSONObject(1); + + double tolObj1Index1 = testObj1.getJSONArray("tolerances").getDouble(0); + double tolObj2Index1 = testObj2.getJSONArray("tolerances").getDouble(0); + double[] tolObj1Index2 = new double[3]; + double[] tolObj2Index2 = new double[3]; + + if (testObj1.getJSONArray("tolerances").optJSONArray(1) != null) { + for (int i = 0; i < 3; i++) { + tolObj1Index2[i] = testObj1.getJSONArray("tolerances").getJSONArray(1).getDouble(i); + tolObj2Index2[i] = testObj2.getJSONArray("tolerances").getJSONArray(1).getDouble(i); + } + } else { + Arrays.fill(tolObj1Index2, testObj1.getJSONArray("tolerances").getDouble(1)); + Arrays.fill(tolObj2Index2, testObj2.getJSONArray("tolerances").getDouble(1)); + } + + if (testObj1.getString("type").equals("conversion")) { + Arrays.fill(this.tolConversions_i0, tolObj1Index1); + this.tolConversions_i1 = tolObj1Index2; + + Arrays.fill(this.tolRoundTrips_i0, tolObj2Index1); + this.tolRoundTrips_i1 = tolObj2Index2; + this.roundTripTimes = testObj2.getInt("times"); + } else { + Arrays.fill(this.tolConversions_i0, tolObj2Index1); + this.tolConversions_i1 = tolObj2Index2; + + Arrays.fill(this.tolRoundTrips_i0, tolObj1Index1); + this.tolRoundTrips_i1 = tolObj1Index2; + this.roundTripTimes = testObj1.getInt("times"); + } // leftPolyline = (Polyline)leftPolygon.getBoundary(); // rightPolyline = (Polyline)rightPolyline.getBoundary(); - } + } - @Test - public void testConversionPoints() throws Exception { - assertTrue(this.description, true); + @Test + public void testConversionPoints() throws Exception { + assertTrue(this.description, true); - Point[] leftExpected = new Point[leftMultiPoint.getPointCount()]; - leftMultiPoint.queryCoordinates(leftExpected); + Point[] leftExpected = new Point[leftMultiPoint.getPointCount()]; + leftMultiPoint.queryCoordinates(leftExpected); - Point[] rightExpected = new Point[rightMultiPoint.getPointCount()]; - rightMultiPoint.queryCoordinates(rightExpected); + Point[] rightExpected = new Point[rightMultiPoint.getPointCount()]; + rightMultiPoint.queryCoordinates(rightExpected); - Point[] rightActual = IntStream.range(0, rightMultiPoint.getPointCount()) - .mapToObj(i -> new Point()) - .toArray(Point[]::new); + Point[] rightActual = IntStream.range(0, rightMultiPoint.getPointCount()) + .mapToObj(i -> new Point()) + .toArray(Point[]::new); - Point[] leftActual = IntStream.range(0, leftMultiPoint.getPointCount()) - .mapToObj(i -> new Point()) - .toArray(Point[]::new); + Point[] leftActual = IntStream.range(0, leftMultiPoint.getPointCount()) + .mapToObj(i -> new Point()) + .toArray(Point[]::new); - OperatorProject.local().transform(this.leftToRightTransformation, leftExpected, leftExpected.length, rightActual); - // test_right = self.transform(self.proj_left, self.proj_right, self.coords_left) - StringBuilder errorMessages = new StringBuilder(); - errorMessages.append("\n").append(this.testName); - errorMessages.append("\n").append(this.description); - int nonMatches = listCountMatches(rightActual, rightExpected, this.tolConversions_i1, errorMessages); - assertEquals(errorMessages.toString(), 0, nonMatches); + OperatorProject.local().transform(this.leftToRightTransformation, leftExpected, leftExpected.length, rightActual); + // test_right = self.transform(self.proj_left, self.proj_right, self.coords_left) + StringBuilder errorMessages = new StringBuilder(); + errorMessages.append("\n").append(this.testName); + errorMessages.append("\n").append(this.description); + int nonMatches = listCountMatches(rightActual, rightExpected, this.tolConversions_i1, errorMessages); + assertEquals(errorMessages.toString(), 0, nonMatches); - // results1 = list_count_matches(test_right, self.coords_right, tolerances[1]) + // results1 = list_count_matches(test_right, self.coords_right, tolerances[1]) - OperatorProject.local().transform(this.rightToLeftTransformation, rightExpected, rightExpected.length, leftActual); - // test_left = self.transform(self.proj_right, self.proj_left, self.coords_right) - nonMatches += listCountMatches(leftActual, leftExpected, this.tolConversions_i0, errorMessages); - // results2 = list_count_matches(test_left, self.coords_left, tolerances[0]) - assertEquals(errorMessages.toString(), 0, nonMatches); + OperatorProject.local().transform(this.rightToLeftTransformation, rightExpected, rightExpected.length, leftActual); + // test_left = self.transform(self.proj_right, self.proj_left, self.coords_right) + nonMatches += listCountMatches(leftActual, leftExpected, this.tolConversions_i0, errorMessages); + // results2 = list_count_matches(test_left, self.coords_left, tolerances[0]) + assertEquals(errorMessages.toString(), 0, nonMatches); // tolerances = kwargs.get('tolerances', [0.0000000000001, 0.0000000000001]) @@ -189,91 +189,91 @@ public void testConversionPoints() throws Exception { // results2 = list_count_matches(test_left, self.coords_left, tolerances[0]) // // return (results1[0] + results2[0], results1[1] + results2[1]) - } - - /** - * counts coordinates in lists that match and don't match. - * assumes that lists are the same length (they should be) - *

- * returns tuple (matches, non_matches) - * """ - * matches, non_matches = 0, 0 - * iter_ex_coords = iter(ex_coords) - * for c in coords: - * ex_coord = next(iter_ex_coords) - * if match_func(c, ex_coord, tolerance): - * matches = matches + 1 - * else: - * non_matches = non_matches + 1 - *

- * return (matches, non_matches) - */ - public static int listCountMatches(Point[] actualPoints, Point[] expectedPoints, double[] tolerances, StringBuilder errorMessages) { - // matches, non_matches = 0, 0 - int nonMatches = 0; - for (int i = 0; i < actualPoints.length; i++) { - Point actualPoint = actualPoints[i]; - Point expectedPoint = expectedPoints[i]; - String message = matchCheck(actualPoint, expectedPoint, tolerances); - if (message != null) { - nonMatches += 1; - errorMessages.append("\nError at index: ").append(i).append("\n"); - errorMessages.append(message); - } - } - - return nonMatches; - } - - /** - * Check if coordinate matches expected coordinate within a given tolerance. - * float coordinate elements will be checked based on this value - * list/tuple coordinate elements will be checked based on the - * corresponding values - * - * @param pt - * @param expectedPoint - * @param tolerance error rate - * @return string - */ - public static String matchCheck(Point pt, Point expectedPoint, double[] tolerance) { - double[] coord_diff = new double[]{Math.abs(pt.getX() - expectedPoint.getX()), Math.abs(pt.getY() - expectedPoint.getY())}; - if (pt.hasZ()) - coord_diff = new double[]{Math.abs(pt.getX() - expectedPoint.getX()), Math.abs(pt.getY() - expectedPoint.getY()), Math.abs(pt.getZ() - expectedPoint.getZ())}; - - boolean matching = true; - StringBuilder stringBuilder = new StringBuilder(); - for (int i = 0; i < coord_diff.length; i++) { - if (coord_diff[i] > tolerance[i]) { - matching = false; - - stringBuilder.append("Non-match at "); - if (i == 0) - stringBuilder.append(" x position\n"); - else if (i == 1) - stringBuilder.append(" y position\n"); - else - stringBuilder.append(" z position\n"); - stringBuilder.append("Actual coordinate:\n"); - stringBuilder.append(pt.toString()); - stringBuilder.append("\nExpected coordinate:\n"); - stringBuilder.append(expectedPoint.toString()); - stringBuilder.append("\nActual difference:\n"); - stringBuilder.append("x: ").append(coord_diff[0]).append(" y:").append(coord_diff[1]); - if (pt.hasZ()) - stringBuilder.append(" z:").append(coord_diff[2]); - stringBuilder.append("\nFor tolerances:\n"); - stringBuilder.append("x:").append(tolerance[0]).append(" y:").append(tolerance[1]); - if (pt.hasZ()) - stringBuilder.append(" z:").append(tolerance[2]); - stringBuilder.append("\n\n"); - } - } - if (!matching) - return stringBuilder.toString(); - - return null; - } + } + + /** + * counts coordinates in lists that match and don't match. + * assumes that lists are the same length (they should be) + *

+ * returns tuple (matches, non_matches) + * """ + * matches, non_matches = 0, 0 + * iter_ex_coords = iter(ex_coords) + * for c in coords: + * ex_coord = next(iter_ex_coords) + * if match_func(c, ex_coord, tolerance): + * matches = matches + 1 + * else: + * non_matches = non_matches + 1 + *

+ * return (matches, non_matches) + */ + public static int listCountMatches(Point[] actualPoints, Point[] expectedPoints, double[] tolerances, StringBuilder errorMessages) { + // matches, non_matches = 0, 0 + int nonMatches = 0; + for (int i = 0; i < actualPoints.length; i++) { + Point actualPoint = actualPoints[i]; + Point expectedPoint = expectedPoints[i]; + String message = matchCheck(actualPoint, expectedPoint, tolerances); + if (message != null) { + nonMatches += 1; + errorMessages.append("\nError at index: ").append(i).append("\n"); + errorMessages.append(message); + } + } + + return nonMatches; + } + + /** + * Check if coordinate matches expected coordinate within a given tolerance. + * float coordinate elements will be checked based on this value + * list/tuple coordinate elements will be checked based on the + * corresponding values + * + * @param pt + * @param expectedPoint + * @param tolerance error rate + * @return string + */ + public static String matchCheck(Point pt, Point expectedPoint, double[] tolerance) { + double[] coord_diff = new double[]{Math.abs(pt.getX() - expectedPoint.getX()), Math.abs(pt.getY() - expectedPoint.getY())}; + if (pt.hasZ()) + coord_diff = new double[]{Math.abs(pt.getX() - expectedPoint.getX()), Math.abs(pt.getY() - expectedPoint.getY()), Math.abs(pt.getZ() - expectedPoint.getZ())}; + + boolean matching = true; + StringBuilder stringBuilder = new StringBuilder(); + for (int i = 0; i < coord_diff.length; i++) { + if (coord_diff[i] > tolerance[i]) { + matching = false; + + stringBuilder.append("Non-match at "); + if (i == 0) + stringBuilder.append(" x position\n"); + else if (i == 1) + stringBuilder.append(" y position\n"); + else + stringBuilder.append(" z position\n"); + stringBuilder.append("Actual coordinate:\n"); + stringBuilder.append(pt.toString()); + stringBuilder.append("\nExpected coordinate:\n"); + stringBuilder.append(expectedPoint.toString()); + stringBuilder.append("\nActual difference:\n"); + stringBuilder.append("x: ").append(coord_diff[0]).append(" y:").append(coord_diff[1]); + if (pt.hasZ()) + stringBuilder.append(" z:").append(coord_diff[2]); + stringBuilder.append("\nFor tolerances:\n"); + stringBuilder.append("x:").append(tolerance[0]).append(" y:").append(tolerance[1]); + if (pt.hasZ()) + stringBuilder.append(" z:").append(tolerance[2]); + stringBuilder.append("\n\n"); + } + } + if (!matching) + return stringBuilder.toString(); + + return null; + } // if len(exc) == 3: // # coordinate triples @@ -297,61 +297,61 @@ else if (i == 1) // ''.format(c1=cor, c2=exc, res=coord_diff, tol=tolerance)) // - @Test - public void testRoundtripPoints() throws Exception { - assertTrue(this.description, true); - } - - - @Test - public void testConversionMultiPoints() throws Exception { - assertTrue(this.description, true); - } - - @Test - public void testRoundtripMultiPoints() throws Exception { - assertTrue(this.description, true); - } - - @Test - public void testConversionPolyline() throws Exception { - assertTrue(this.description, true); - } - - @Test - public void testRoundtripPolyline() throws Exception { - assertTrue(this.description, true); - } - - @Test - public void testConversionPolygon() throws Exception { - assertTrue(this.description, true); - } - - @Test - public void testRoundtripPolygon() throws Exception { - assertTrue(this.description, true); - } - - @Parameterized.Parameters(name = "{1}") - public static Collection data() throws java.io.IOException, java.net.URISyntaxException { - // load the files as you want - URL urls = TestProjectionGigsData.class.getResource("gigs"); - Path gigsDir = Paths.get(urls.toURI()); - - // https://stackoverflow.com/a/36815191/445372 - Stream paths = Files.walk(gigsDir, 1, FileVisitOption.FOLLOW_LINKS); - - Collection data = paths - // https://stackoverflow.com/a/20533064/445372 - .filter(p -> p.toString().toLowerCase().endsWith(".json")) - // https://www.mkyong.com/java8/java-8-filter-a-map-examples/ - .map(p -> new Object[]{p, p.getFileName().toString().split(".json")[0].replace('.', '_')}) - // https://www.javabrahman.com/java-8/java-8-how-to-use-collectors-tocollection-collector-with-examples/ - .collect(Collectors.toCollection(ArrayList::new)); - - return data; - } + @Test + public void testRoundtripPoints() throws Exception { + assertTrue(this.description, true); + } + + + @Test + public void testConversionMultiPoints() throws Exception { + assertTrue(this.description, true); + } + + @Test + public void testRoundtripMultiPoints() throws Exception { + assertTrue(this.description, true); + } + + @Test + public void testConversionPolyline() throws Exception { + assertTrue(this.description, true); + } + + @Test + public void testRoundtripPolyline() throws Exception { + assertTrue(this.description, true); + } + + @Test + public void testConversionPolygon() throws Exception { + assertTrue(this.description, true); + } + + @Test + public void testRoundtripPolygon() throws Exception { + assertTrue(this.description, true); + } + + @Parameterized.Parameters(name = "{1}") + public static Collection data() throws java.io.IOException, java.net.URISyntaxException { + // load the files as you want + URL urls = TestProjectionGigsData.class.getResource("gigs"); + Path gigsDir = Paths.get(urls.toURI()); + + // https://stackoverflow.com/a/36815191/445372 + Stream paths = Files.walk(gigsDir, 1, FileVisitOption.FOLLOW_LINKS); + + Collection data = paths + // https://stackoverflow.com/a/20533064/445372 + .filter(p -> p.toString().toLowerCase().endsWith(".json")) + // https://www.mkyong.com/java8/java-8-filter-a-map-examples/ + .map(p -> new Object[]{p, p.getFileName().toString().split(".json")[0].replace('.', '_')}) + // https://www.javabrahman.com/java-8/java-8-how-to-use-collectors-tocollection-collector-with-examples/ + .collect(Collectors.toCollection(ArrayList::new)); + + return data; + } } diff --git a/src/test/java/com/esri/core/geometry/TestProximity2D.java b/src/test/java/com/esri/core/geometry/TestProximity2D.java index 2ee1c681..9f6cd184 100644 --- a/src/test/java/com/esri/core/geometry/TestProximity2D.java +++ b/src/test/java/com/esri/core/geometry/TestProximity2D.java @@ -28,259 +28,259 @@ import org.junit.Test; public class TestProximity2D extends TestCase { - @Override - protected void setUp() throws Exception { - super.setUp(); - } - - @Override - protected void tearDown() throws Exception { - super.tearDown(); - } - - @Test - public void testProximity_2D_1() { - OperatorFactoryLocal engine = OperatorFactoryLocal.getInstance(); - - @SuppressWarnings("unused") - OperatorProximity2D proximityOp = (OperatorProximity2D) engine - .getOperator(Operator.Type.Proximity2D); - - Point inputPoint = new Point(3, 2); - - Point point0 = new Point(2.75, 2); - // Point point1 = new Point(3, 2.5); - // Point point2 = new Point(3.75, 2); - // Point point3 = new Point(2.25, 2.5); - // Point point4 = new Point(4, 2.25); - - // GetNearestVertices for Polygon (Native and DotNet) - Polygon polygon = MakePolygon(); - - Proximity2DResult[] resultArray = GeometryEngine.getNearestVertices( - polygon, inputPoint, 2.0, 8); - assertTrue(resultArray.length == 8); - - double lastdistance; - double distance; - - Proximity2DResult result0 = resultArray[0]; - lastdistance = result0.getDistance(); - assertTrue(lastdistance <= 2.0); - - Proximity2DResult result1 = resultArray[1]; - distance = result1.getDistance(); - assertTrue(distance <= 2.0 && distance >= lastdistance); - lastdistance = distance; - - Proximity2DResult result2 = resultArray[2]; - distance = result2.getDistance(); - assertTrue(distance <= 2.0 && distance >= lastdistance); - lastdistance = distance; - - Proximity2DResult result3 = resultArray[3]; - distance = result3.getDistance(); - assertTrue(distance <= 2.0 && distance >= lastdistance); - lastdistance = distance; - - Proximity2DResult result4 = resultArray[4]; - distance = result4.getDistance(); - assertTrue(distance <= 2.0 && distance >= lastdistance); - lastdistance = distance; - - Proximity2DResult result5 = resultArray[5]; - distance = result5.getDistance(); - assertTrue(distance <= 2.0 && distance >= lastdistance); - lastdistance = distance; - - Proximity2DResult result6 = resultArray[6]; - distance = result6.getDistance(); - assertTrue(distance <= 2.0 && distance >= lastdistance); - lastdistance = distance; - - Proximity2DResult result7 = resultArray[7]; - distance = result7.getDistance(); - assertTrue(distance <= 2.0 && distance >= lastdistance); - // lastdistance = distance; - - // Point[] coordinates = polygon.get.getCoordinates2D(); - // int pointCount = polygon.getPointCount(); - // - // int hits = 0; - // for (int i = 0; i < pointCount; i++) - // { - // Point ipoint = coordinates[i]; - // distance = Point::Distance(ipoint, inputPoint); - // - // if (distance < lastdistance) - // hits++; - // } - - // assertTrue(hits < 8); - - // GetNearestVertices for Point - Point point = MakePoint(); - resultArray = GeometryEngine.getNearestVertices(point, inputPoint, 1.0, - 1); - assertTrue(resultArray.length == 1); - result0 = resultArray[0]; - Point resultPoint0 = result0.getCoordinate(); - assertTrue(resultPoint0.getX() == point.getX() - && resultPoint0.getY() == point.getY()); - - // GetNearestVertex for Polygon - result0 = GeometryEngine.getNearestVertex(polygon, inputPoint); - resultPoint0 = result0.getCoordinate(); - assertTrue(resultPoint0.getX() == point0.getX() - && resultPoint0.getY() == point0.getY()); - - // GetNearestVertex for Point - result0 = GeometryEngine.getNearestVertex(point, inputPoint); - resultPoint0 = result0.getCoordinate(); - assertTrue(resultPoint0.getX() == point.getX() - && resultPoint0.getY() == point.getY()); - - // GetNearestCoordinate for Polygon - Polygon polygon2 = MakePolygon2(); - result0 = GeometryEngine.getNearestCoordinate(polygon2, inputPoint, - true); - resultPoint0 = result0.getCoordinate(); - assertTrue(resultPoint0.getX() == inputPoint.getX() - && resultPoint0.getY() == inputPoint.getY()); - - // GetNearestCoordinate for Polyline - Polyline polyline = MakePolyline(); - result0 = GeometryEngine.getNearestCoordinate(polyline, inputPoint, - true); - resultPoint0 = result0.getCoordinate(); - assertTrue(resultPoint0.getX() == 0.0 && resultPoint0.getY() == 2.0); - - Polygon pp = new Polygon(); - pp.startPath(0, 0); - pp.lineTo(0, 10); - pp.lineTo(10, 10); - pp.lineTo(10, 0); - - inputPoint.setXY(15, -5); - - result0 = proximityOp.getNearestCoordinate(pp, inputPoint, true, true); - boolean is_right = result0.isRightSide(); - assertTrue(!is_right); - } - - Polygon MakePolygon() { - Polygon poly = new Polygon(); - poly.startPath(3, -2); - poly.lineTo(2, -1); - poly.lineTo(3, 0); - poly.lineTo(4, 0); - - poly.startPath(1.75, 1); - poly.lineTo(0.75, 2); - poly.lineTo(1.75, 3); - poly.lineTo(2.25, 2.5); - poly.lineTo(2.75, 2); - - poly.startPath(3, 2.5); - poly.lineTo(2.5, 3); - poly.lineTo(2, 3.5); - poly.lineTo(3, 4.5); - poly.lineTo(4, 3.5); - - poly.startPath(4.75, 1); - poly.lineTo(3.75, 2); - poly.lineTo(4, 2.25); - poly.lineTo(4.75, 3); - poly.lineTo(5.75, 2); - - return poly; - } - - Polygon MakePolygon2() { - Polygon poly = new Polygon(); - poly.startPath(0, 0); - poly.lineTo(0, 10); - poly.lineTo(10, 10); - poly.lineTo(10, 0); - - return poly; - } - - Polyline MakePolyline() { - Polyline poly = new Polyline(); - poly.startPath(0, 0); - poly.lineTo(0, 10); - poly.lineTo(10, 10); - poly.lineTo(10, 0); - - return poly; - } - - Point MakePoint() { - Point point = new Point(3, 2.5); - - return point; - } - - @Test - public void testProximity2D_2() { - Point point1 = new Point(3, 2); - Point point2 = new Point(2, 4); - Envelope envelope = new Envelope(); - envelope.setCoords(4, 3, 7, 6); - Polygon polygonToTest = new Polygon(); - polygonToTest.addEnvelope(envelope, false); - Proximity2DResult prxResult1 = GeometryEngine.getNearestVertex( - envelope, point1); - Proximity2DResult prxResult2 = GeometryEngine.getNearestVertex( - polygonToTest, point1); - Proximity2DResult prxResult3 = GeometryEngine.getNearestCoordinate( - envelope, point2, false); - Proximity2DResult prxResult4 = GeometryEngine.getNearestCoordinate( - polygonToTest, point2, false); - - Point result1 = prxResult1.getCoordinate(); - Point result2 = prxResult2.getCoordinate(); - assertTrue(result1.getX() == result2.getX()); - Point result3 = prxResult3.getCoordinate(); - Point result4 = prxResult4.getCoordinate(); - assertTrue(result3.getX() == result4.getX()); - } - - @Test - public static void testProximity2D_3() { - OperatorFactoryLocal factory = OperatorFactoryLocal.getInstance(); - OperatorProximity2D proximity = (OperatorProximity2D) factory - .getOperator(com.esri.core.geometry.Operator.Type.Proximity2D); - - Polygon polygon = new Polygon(); - polygon.startPath(new Point(-120, 22)); - polygon.lineTo(new Point(-120, 10)); - polygon.lineTo(new Point(-110, 10)); - polygon.lineTo(new Point(-110, 22)); - - Point point = new Point(); - point.setXY(-110, 20); - Proximity2DResult result = proximity.getNearestCoordinate(polygon, - point, false); - Point point2 = new Point(); - point2.setXY(-120, 12); - @SuppressWarnings("unused") - Proximity2DResult[] results = proximity.getNearestVertices(polygon, - point2, 10, 12); - } - - @Test - public static void testCR254240() { - OperatorProximity2D proximityOp = OperatorProximity2D.local(); - - Point inputPoint = new Point(-12, 12); - Polyline line = new Polyline(); - line.startPath(-10, 0); - line.lineTo(0, 0); - - Proximity2DResult result = proximityOp.getNearestCoordinate(line, - inputPoint, false, true); - assertTrue(result.isRightSide() == false); - } + @Override + protected void setUp() throws Exception { + super.setUp(); + } + + @Override + protected void tearDown() throws Exception { + super.tearDown(); + } + + @Test + public void testProximity_2D_1() { + OperatorFactoryLocal engine = OperatorFactoryLocal.getInstance(); + + @SuppressWarnings("unused") + OperatorProximity2D proximityOp = (OperatorProximity2D) engine + .getOperator(Operator.Type.Proximity2D); + + Point inputPoint = new Point(3, 2); + + Point point0 = new Point(2.75, 2); + // Point point1 = new Point(3, 2.5); + // Point point2 = new Point(3.75, 2); + // Point point3 = new Point(2.25, 2.5); + // Point point4 = new Point(4, 2.25); + + // GetNearestVertices for Polygon (Native and DotNet) + Polygon polygon = MakePolygon(); + + Proximity2DResult[] resultArray = GeometryEngine.getNearestVertices( + polygon, inputPoint, 2.0, 8); + assertTrue(resultArray.length == 8); + + double lastdistance; + double distance; + + Proximity2DResult result0 = resultArray[0]; + lastdistance = result0.getDistance(); + assertTrue(lastdistance <= 2.0); + + Proximity2DResult result1 = resultArray[1]; + distance = result1.getDistance(); + assertTrue(distance <= 2.0 && distance >= lastdistance); + lastdistance = distance; + + Proximity2DResult result2 = resultArray[2]; + distance = result2.getDistance(); + assertTrue(distance <= 2.0 && distance >= lastdistance); + lastdistance = distance; + + Proximity2DResult result3 = resultArray[3]; + distance = result3.getDistance(); + assertTrue(distance <= 2.0 && distance >= lastdistance); + lastdistance = distance; + + Proximity2DResult result4 = resultArray[4]; + distance = result4.getDistance(); + assertTrue(distance <= 2.0 && distance >= lastdistance); + lastdistance = distance; + + Proximity2DResult result5 = resultArray[5]; + distance = result5.getDistance(); + assertTrue(distance <= 2.0 && distance >= lastdistance); + lastdistance = distance; + + Proximity2DResult result6 = resultArray[6]; + distance = result6.getDistance(); + assertTrue(distance <= 2.0 && distance >= lastdistance); + lastdistance = distance; + + Proximity2DResult result7 = resultArray[7]; + distance = result7.getDistance(); + assertTrue(distance <= 2.0 && distance >= lastdistance); + // lastdistance = distance; + + // Point[] coordinates = polygon.get.getCoordinates2D(); + // int pointCount = polygon.getPointCount(); + // + // int hits = 0; + // for (int i = 0; i < pointCount; i++) + // { + // Point ipoint = coordinates[i]; + // distance = Point::Distance(ipoint, inputPoint); + // + // if (distance < lastdistance) + // hits++; + // } + + // assertTrue(hits < 8); + + // GetNearestVertices for Point + Point point = MakePoint(); + resultArray = GeometryEngine.getNearestVertices(point, inputPoint, 1.0, + 1); + assertTrue(resultArray.length == 1); + result0 = resultArray[0]; + Point resultPoint0 = result0.getCoordinate(); + assertTrue(resultPoint0.getX() == point.getX() + && resultPoint0.getY() == point.getY()); + + // GetNearestVertex for Polygon + result0 = GeometryEngine.getNearestVertex(polygon, inputPoint); + resultPoint0 = result0.getCoordinate(); + assertTrue(resultPoint0.getX() == point0.getX() + && resultPoint0.getY() == point0.getY()); + + // GetNearestVertex for Point + result0 = GeometryEngine.getNearestVertex(point, inputPoint); + resultPoint0 = result0.getCoordinate(); + assertTrue(resultPoint0.getX() == point.getX() + && resultPoint0.getY() == point.getY()); + + // GetNearestCoordinate for Polygon + Polygon polygon2 = MakePolygon2(); + result0 = GeometryEngine.getNearestCoordinate(polygon2, inputPoint, + true); + resultPoint0 = result0.getCoordinate(); + assertTrue(resultPoint0.getX() == inputPoint.getX() + && resultPoint0.getY() == inputPoint.getY()); + + // GetNearestCoordinate for Polyline + Polyline polyline = MakePolyline(); + result0 = GeometryEngine.getNearestCoordinate(polyline, inputPoint, + true); + resultPoint0 = result0.getCoordinate(); + assertTrue(resultPoint0.getX() == 0.0 && resultPoint0.getY() == 2.0); + + Polygon pp = new Polygon(); + pp.startPath(0, 0); + pp.lineTo(0, 10); + pp.lineTo(10, 10); + pp.lineTo(10, 0); + + inputPoint.setXY(15, -5); + + result0 = proximityOp.getNearestCoordinate(pp, inputPoint, true, true); + boolean is_right = result0.isRightSide(); + assertTrue(!is_right); + } + + Polygon MakePolygon() { + Polygon poly = new Polygon(); + poly.startPath(3, -2); + poly.lineTo(2, -1); + poly.lineTo(3, 0); + poly.lineTo(4, 0); + + poly.startPath(1.75, 1); + poly.lineTo(0.75, 2); + poly.lineTo(1.75, 3); + poly.lineTo(2.25, 2.5); + poly.lineTo(2.75, 2); + + poly.startPath(3, 2.5); + poly.lineTo(2.5, 3); + poly.lineTo(2, 3.5); + poly.lineTo(3, 4.5); + poly.lineTo(4, 3.5); + + poly.startPath(4.75, 1); + poly.lineTo(3.75, 2); + poly.lineTo(4, 2.25); + poly.lineTo(4.75, 3); + poly.lineTo(5.75, 2); + + return poly; + } + + Polygon MakePolygon2() { + Polygon poly = new Polygon(); + poly.startPath(0, 0); + poly.lineTo(0, 10); + poly.lineTo(10, 10); + poly.lineTo(10, 0); + + return poly; + } + + Polyline MakePolyline() { + Polyline poly = new Polyline(); + poly.startPath(0, 0); + poly.lineTo(0, 10); + poly.lineTo(10, 10); + poly.lineTo(10, 0); + + return poly; + } + + Point MakePoint() { + Point point = new Point(3, 2.5); + + return point; + } + + @Test + public void testProximity2D_2() { + Point point1 = new Point(3, 2); + Point point2 = new Point(2, 4); + Envelope envelope = new Envelope(); + envelope.setCoords(4, 3, 7, 6); + Polygon polygonToTest = new Polygon(); + polygonToTest.addEnvelope(envelope, false); + Proximity2DResult prxResult1 = GeometryEngine.getNearestVertex( + envelope, point1); + Proximity2DResult prxResult2 = GeometryEngine.getNearestVertex( + polygonToTest, point1); + Proximity2DResult prxResult3 = GeometryEngine.getNearestCoordinate( + envelope, point2, false); + Proximity2DResult prxResult4 = GeometryEngine.getNearestCoordinate( + polygonToTest, point2, false); + + Point result1 = prxResult1.getCoordinate(); + Point result2 = prxResult2.getCoordinate(); + assertTrue(result1.getX() == result2.getX()); + Point result3 = prxResult3.getCoordinate(); + Point result4 = prxResult4.getCoordinate(); + assertTrue(result3.getX() == result4.getX()); + } + + @Test + public static void testProximity2D_3() { + OperatorFactoryLocal factory = OperatorFactoryLocal.getInstance(); + OperatorProximity2D proximity = (OperatorProximity2D) factory + .getOperator(com.esri.core.geometry.Operator.Type.Proximity2D); + + Polygon polygon = new Polygon(); + polygon.startPath(new Point(-120, 22)); + polygon.lineTo(new Point(-120, 10)); + polygon.lineTo(new Point(-110, 10)); + polygon.lineTo(new Point(-110, 22)); + + Point point = new Point(); + point.setXY(-110, 20); + Proximity2DResult result = proximity.getNearestCoordinate(polygon, + point, false); + Point point2 = new Point(); + point2.setXY(-120, 12); + @SuppressWarnings("unused") + Proximity2DResult[] results = proximity.getNearestVertices(polygon, + point2, 10, 12); + } + + @Test + public static void testCR254240() { + OperatorProximity2D proximityOp = OperatorProximity2D.local(); + + Point inputPoint = new Point(-12, 12); + Polyline line = new Polyline(); + line.startPath(-10, 0); + line.lineTo(0, 0); + + Proximity2DResult result = proximityOp.getNearestCoordinate(line, + inputPoint, false, true); + assertTrue(result.isRightSide() == false); + } } diff --git a/src/test/java/com/esri/core/geometry/TestQuadTree.java b/src/test/java/com/esri/core/geometry/TestQuadTree.java index bbe2a01a..7374c3a9 100644 --- a/src/test/java/com/esri/core/geometry/TestQuadTree.java +++ b/src/test/java/com/esri/core/geometry/TestQuadTree.java @@ -9,428 +9,428 @@ import java.util.Random; public class TestQuadTree extends TestCase { - @Override - protected void setUp() throws Exception { - super.setUp(); - } + @Override + protected void setUp() throws Exception { + super.setUp(); + } - @Override - protected void tearDown() throws Exception { - super.tearDown(); - } - - @Test - public static void test1() { - - { - QuadTree quad_tree = new QuadTree(Envelope2D.construct(-10, -10, 10, 10), 8); - - QuadTree.QuadTreeIterator qt = quad_tree.getIterator(true); - assertTrue(qt.next() == -1); - - qt.resetIterator(Envelope2D.construct(0, 0, 0, 0), 0); - - assertTrue(quad_tree.getIntersectionCount(Envelope2D.construct(0, 0, 0, 0), 0, 10) == 0); - assertTrue(quad_tree.getElementCount() == 0); - } - - Polyline polyline; - polyline = makePolyline(); - - MultiPathImpl polylineImpl = (MultiPathImpl) polyline._getImpl(); - QuadTree quadtree = buildQuadTree_(polylineImpl, false); - - Line queryline = new Line(34, 9, 66, 46); - QuadTree.QuadTreeIterator qtIter = quadtree.getIterator(); - assertTrue(qtIter.next() == -1); - - qtIter.resetIterator(queryline, 0.0); - - int element_handle = qtIter.next(); - while (element_handle > 0) { - int index = quadtree.getElement(element_handle); - assertTrue(index == 6 || index == 8 || index == 14); - element_handle = qtIter.next(); - } - - Envelope2D envelope = new Envelope2D(34, 9, 66, 46); - Polygon queryPolygon = new Polygon(); - queryPolygon.addEnvelope(envelope, true); - - qtIter.resetIterator(queryline, 0.0); - - element_handle = qtIter.next(); - while (element_handle > 0) { - int index = quadtree.getElement(element_handle); - assertTrue(index == 6 || index == 8 || index == 14); - element_handle = qtIter.next(); - } - } - - @Test - public static void testQuadTreeWithDuplicates() { - int pass_count = 10; - int figure_size = 400; - int figure_size2 = 100; - Envelope extent1 = new Envelope(); - extent1.setCoords(-100000, -100000, 100000, 100000); - - RandomCoordinateGenerator generator1 = new RandomCoordinateGenerator(Math.max(figure_size, 10000), extent1, 0.001); - Random random = new Random(2013); - int rand_max = 32; - - Polygon poly_red = new Polygon(); - Polygon poly_blue = new Polygon(); - - int r = figure_size; - - for (int c = 0; c < pass_count; c++) { - Point pt; - for (int j = 0; j < r; j++) { - int rand = random.nextInt(rand_max); - boolean b_random_new = r > 10 && ((1.0 * rand) / rand_max > 0.95); - pt = generator1.GetRandomCoord(); - if (j == 0 || b_random_new) - poly_blue.startPath(pt); - else - poly_blue.lineTo(pt); - } - - Envelope2D env = new Envelope2D(); - - QuadTree quad_tree_blue = buildQuadTree_((MultiPathImpl) poly_blue._getImpl(), false); - QuadTree quad_tree_blue_duplicates = buildQuadTree_((MultiPathImpl) poly_blue._getImpl(), true); - - Envelope2D e1 = quad_tree_blue.getDataExtent(); - Envelope2D e2 = quad_tree_blue_duplicates.getDataExtent(); - assertTrue(e1.equals(e2)); - assertTrue(quad_tree_blue.getElementCount() == poly_blue.getSegmentCount()); - - SegmentIterator seg_iter_blue = poly_blue.querySegmentIterator(); - - poly_red.setEmpty(); - - r = figure_size2; - if (r < 3) - continue; - - for (int j = 0; j < r; j++) { - int rand = random.nextInt(rand_max); - boolean b_random_new = r > 10 && ((1.0 * rand) / rand_max > 0.95); - pt = generator1.GetRandomCoord(); - if (j == 0 || b_random_new) - poly_red.startPath(pt); - else - poly_red.lineTo(pt); - } - - QuadTree.QuadTreeIterator iterator = quad_tree_blue.getIterator(); - SegmentIteratorImpl seg_iter_red = ((MultiPathImpl) poly_red._getImpl()).querySegmentIterator(); - - HashMap map1 = new HashMap(0); - - int count = 0; - int intersections_per_query = 0; - while (seg_iter_red.nextPath()) { - while (seg_iter_red.hasNextSegment()) { - Segment segment_red = seg_iter_red.nextSegment(); - segment_red.queryEnvelope2D(env); - - iterator.resetIterator(env, 0.0); - - int count_upper = 0; - int element_handle; - while ((element_handle = iterator.next()) != -1) { - count_upper++; - int index = quad_tree_blue.getElement(element_handle); - Boolean iter = (Boolean) map1.get(index); - if (iter == null) { - count++; - map1.put(index, true); - } - - intersections_per_query++; - } - - int intersection_count = quad_tree_blue.getIntersectionCount(env, 0.0, -1); - assertTrue(intersection_count == count_upper); - } - } - - seg_iter_red.resetToFirstPath(); - - HashMap map2 = new HashMap(0); - QuadTree.QuadTreeIterator iterator_duplicates = quad_tree_blue_duplicates.getIterator(); - - int count_duplicates = 0; - int intersections_per_query_duplicates = 0; - while (seg_iter_red.nextPath()) { - while (seg_iter_red.hasNextSegment()) { - Segment segment_red = seg_iter_red.nextSegment(); - segment_red.queryEnvelope2D(env); - - iterator_duplicates.resetIterator(env, 0.0); - - int count_lower = 0; - HashMap map_per_query = new HashMap(0); - - int count_upper = 0; - int element_handle; - while ((element_handle = iterator_duplicates.next()) != -1) { - count_upper++; - int index = quad_tree_blue_duplicates.getElement(element_handle); - Boolean iter = (Boolean) map2.get(index); - if (iter == null) { - count_duplicates++; - map2.put(index, true); - } - - - Boolean iter_lower = (Boolean) map_per_query.get(index); - if (iter_lower == null) { - count_lower++; - intersections_per_query_duplicates++; - map_per_query.put(index, true); - } - - int q = quad_tree_blue_duplicates.getQuad(element_handle); - assertTrue(quad_tree_blue_duplicates.getSubTreeElementCount(q) >= quad_tree_blue_duplicates.getContainedSubTreeElementCount(q)); - } - - int intersection_count = quad_tree_blue_duplicates.getIntersectionCount(env, 0.0, -1); - boolean b_has_data = quad_tree_blue_duplicates.hasData(env, 0.0); - assertTrue(b_has_data || intersection_count == 0); - assertTrue(count_lower <= intersection_count && intersection_count <= count_upper); - assertTrue(count_upper <= 4 * count_lower); - } - } - - assertTrue(count == count_duplicates); - assertTrue(intersections_per_query == intersections_per_query_duplicates); - } - } - - @Test - public static void testSortedIterator() { - int pass_count = 10; - int figure_size = 400; - int figure_size2 = 100; - Envelope extent1 = new Envelope(); - extent1.setCoords(-100000, -100000, 100000, 100000); - - RandomCoordinateGenerator generator1 = new RandomCoordinateGenerator(Math.max(figure_size, 10000), extent1, 0.001); - - Random random = new Random(2013); - int rand_max = 32; - - Polygon poly_red = new Polygon(); - Polygon poly_blue = new Polygon(); - - int r = figure_size; - - for (int c = 0; c < pass_count; c++) { - Point pt; - for (int j = 0; j < r; j++) { - int rand = random.nextInt(rand_max); - boolean b_random_new = r > 10 && ((1.0 * rand) / rand_max > 0.95); - pt = generator1.GetRandomCoord(); - if (j == 0 || b_random_new) - poly_blue.startPath(pt); - else - poly_blue.lineTo(pt); - } - - Envelope2D env = new Envelope2D(); - - QuadTree quad_tree_blue = buildQuadTree_((MultiPathImpl) poly_blue._getImpl(), false); - - Envelope2D e1 = quad_tree_blue.getDataExtent(); - assertTrue(quad_tree_blue.getElementCount() == poly_blue.getSegmentCount()); - - SegmentIterator seg_iter_blue = poly_blue.querySegmentIterator(); - - poly_red.setEmpty(); - - r = figure_size2; - if (r < 3) - continue; - - for (int j = 0; j < r; j++) { - int rand = random.nextInt(rand_max); - boolean b_random_new = r > 10 && ((1.0 * rand) / rand_max > 0.95); - pt = generator1.GetRandomCoord(); - if (j == 0 || b_random_new) - poly_red.startPath(pt); - else - poly_red.lineTo(pt); - } - - QuadTree.QuadTreeIterator iterator = quad_tree_blue.getIterator(); - SegmentIteratorImpl seg_iter_red = ((MultiPathImpl) poly_red._getImpl()).querySegmentIterator(); - - HashMap map1 = new HashMap(0); - - int count = 0; - int intersections_per_query = 0; - while (seg_iter_red.nextPath()) { - while (seg_iter_red.hasNextSegment()) { - Segment segment_red = seg_iter_red.nextSegment(); - segment_red.queryEnvelope2D(env); - - iterator.resetIterator(env, 0.0); - - int count_upper = 0; - int element_handle; - while ((element_handle = iterator.next()) != -1) { - count_upper++; - int index = quad_tree_blue.getElement(element_handle); - Boolean iter = (Boolean) map1.get(index); - if (iter == null) { - count++; - map1.put(index, true); - } - - intersections_per_query++; - } - - int intersection_count = quad_tree_blue.getIntersectionCount(env, 0.0, -1); - assertTrue(intersection_count == count_upper); - } - } - - seg_iter_red.resetToFirstPath(); - - HashMap map2 = new HashMap(0); - QuadTree.QuadTreeIterator sorted_iterator = quad_tree_blue.getIterator(true); - - int count_sorted = 0; - int intersections_per_query_sorted = 0; - while (seg_iter_red.nextPath()) { - while (seg_iter_red.hasNextSegment()) { - Segment segment_red = seg_iter_red.nextSegment(); - segment_red.queryEnvelope2D(env); - - sorted_iterator.resetIterator(env, 0.0); - - int count_upper_sorted = 0; - int element_handle; - int last_index = -1; - while ((element_handle = sorted_iterator.next()) != -1) { - count_upper_sorted++; - int index = quad_tree_blue.getElement(element_handle); - assertTrue(last_index < index); // ensure the element handles are returned in sorted order - last_index = index; - Boolean iter = (Boolean) map2.get(index); - if (iter == null) { - count_sorted++; - map2.put(index, true); - } - - intersections_per_query_sorted++; - } - - int intersection_count = quad_tree_blue.getIntersectionCount(env, 0.0, -1); - assertTrue(intersection_count == count_upper_sorted); - } - } - - assertTrue(count == count_sorted); - assertTrue(intersections_per_query == intersections_per_query_sorted); - } - } - - @Test - public static void test_perf_quad_tree() { - Envelope extent1 = new Envelope(); - extent1.setCoords(-1000, -1000, 1000, 1000); - - RandomCoordinateGenerator generator1 = new RandomCoordinateGenerator(1000, extent1, 0.001); - //HiResTimer timer; - for (int N = 16; N <= 1024/**1024*/; N *= 2) { - //timer.StartMeasurement(); - - Envelope2D extent = new Envelope2D(); - extent.setCoords(-1000, -1000, 1000, 1000); - HashMap data = new HashMap(0); - QuadTree qt = new QuadTree(extent, 10); - for (int i = 0; i < N; i++) { - Envelope2D env = new Envelope2D(); - Point2D center = generator1.GetRandomCoord().getXY(); - double w = 10; - env.setCoords(center, w, w); - env.intersect(extent); - if (env.isEmpty()) - continue; - - int h = qt.insert(i, env); - data.put(h, env); - } - - int ecount = 0; - AttributeStreamOfInt32 handles = new AttributeStreamOfInt32(0); - QuadTree.QuadTreeIterator iter = qt.getIterator(); - - Iterator> pairs = data.entrySet().iterator(); - while (pairs.hasNext()) { - Map.Entry entry = pairs.next(); - iter.resetIterator((Envelope2D) entry.getValue(), 0.001); - boolean remove_self = false; - for (int h = iter.next(); h != -1; h = iter.next()) { - if (h != entry.getKey()) - handles.add(h); - else { - remove_self = true; - } - - ecount++; - } - - for (int i = 0; i < handles.size(); i++) { - qt.removeElement(handles.get(i));//remove elements that were selected. - } - - if (remove_self) - qt.removeElement(entry.getKey()); - handles.resize(0); - } - - //printf("%d %0.3f (%I64d, %f, mem %I64d)\n", N, timer.GetMilliseconds(), ecount, ecount / double(N * N), memsize); - } - } - - @Test - public static void test2() { - MultiPoint multipoint = new MultiPoint(); - - for (int i = 0; i < 100; i++) { - for (int j = 0; j < 100; j++) { - multipoint.add(i, j); - } - } - - Envelope2D extent = new Envelope2D(); - multipoint.queryEnvelope2D(extent); - - MultiPointImpl multipointImpl = (MultiPointImpl) multipoint._getImpl(); - QuadTree quadtree = buildQuadTree_(multipointImpl); - - QuadTree.QuadTreeIterator qtIter = quadtree.getIterator(); - assertTrue(qtIter.next() == -1); - - int count = 0; - qtIter.resetIterator(extent, 0.0); - - while (qtIter.next() != -1) { - count++; - } - - assertTrue(count == 10000); - } - - static double randomWithRange(double min, double max) { - double range = Math.abs(max - min); - return (Math.random() * range) + (min <= max ? min : max); - } + @Override + protected void tearDown() throws Exception { + super.tearDown(); + } + + @Test + public static void test1() { + + { + QuadTree quad_tree = new QuadTree(Envelope2D.construct(-10, -10, 10, 10), 8); + + QuadTree.QuadTreeIterator qt = quad_tree.getIterator(true); + assertTrue(qt.next() == -1); + + qt.resetIterator(Envelope2D.construct(0, 0, 0, 0), 0); + + assertTrue(quad_tree.getIntersectionCount(Envelope2D.construct(0, 0, 0, 0), 0, 10) == 0); + assertTrue(quad_tree.getElementCount() == 0); + } + + Polyline polyline; + polyline = makePolyline(); + + MultiPathImpl polylineImpl = (MultiPathImpl) polyline._getImpl(); + QuadTree quadtree = buildQuadTree_(polylineImpl, false); + + Line queryline = new Line(34, 9, 66, 46); + QuadTree.QuadTreeIterator qtIter = quadtree.getIterator(); + assertTrue(qtIter.next() == -1); + + qtIter.resetIterator(queryline, 0.0); + + int element_handle = qtIter.next(); + while (element_handle > 0) { + int index = quadtree.getElement(element_handle); + assertTrue(index == 6 || index == 8 || index == 14); + element_handle = qtIter.next(); + } + + Envelope2D envelope = new Envelope2D(34, 9, 66, 46); + Polygon queryPolygon = new Polygon(); + queryPolygon.addEnvelope(envelope, true); + + qtIter.resetIterator(queryline, 0.0); + + element_handle = qtIter.next(); + while (element_handle > 0) { + int index = quadtree.getElement(element_handle); + assertTrue(index == 6 || index == 8 || index == 14); + element_handle = qtIter.next(); + } + } + + @Test + public static void testQuadTreeWithDuplicates() { + int pass_count = 10; + int figure_size = 400; + int figure_size2 = 100; + Envelope extent1 = new Envelope(); + extent1.setCoords(-100000, -100000, 100000, 100000); + + RandomCoordinateGenerator generator1 = new RandomCoordinateGenerator(Math.max(figure_size, 10000), extent1, 0.001); + Random random = new Random(2013); + int rand_max = 32; + + Polygon poly_red = new Polygon(); + Polygon poly_blue = new Polygon(); + + int r = figure_size; + + for (int c = 0; c < pass_count; c++) { + Point pt; + for (int j = 0; j < r; j++) { + int rand = random.nextInt(rand_max); + boolean b_random_new = r > 10 && ((1.0 * rand) / rand_max > 0.95); + pt = generator1.GetRandomCoord(); + if (j == 0 || b_random_new) + poly_blue.startPath(pt); + else + poly_blue.lineTo(pt); + } + + Envelope2D env = new Envelope2D(); + + QuadTree quad_tree_blue = buildQuadTree_((MultiPathImpl) poly_blue._getImpl(), false); + QuadTree quad_tree_blue_duplicates = buildQuadTree_((MultiPathImpl) poly_blue._getImpl(), true); + + Envelope2D e1 = quad_tree_blue.getDataExtent(); + Envelope2D e2 = quad_tree_blue_duplicates.getDataExtent(); + assertTrue(e1.equals(e2)); + assertTrue(quad_tree_blue.getElementCount() == poly_blue.getSegmentCount()); + + SegmentIterator seg_iter_blue = poly_blue.querySegmentIterator(); + + poly_red.setEmpty(); + + r = figure_size2; + if (r < 3) + continue; + + for (int j = 0; j < r; j++) { + int rand = random.nextInt(rand_max); + boolean b_random_new = r > 10 && ((1.0 * rand) / rand_max > 0.95); + pt = generator1.GetRandomCoord(); + if (j == 0 || b_random_new) + poly_red.startPath(pt); + else + poly_red.lineTo(pt); + } + + QuadTree.QuadTreeIterator iterator = quad_tree_blue.getIterator(); + SegmentIteratorImpl seg_iter_red = ((MultiPathImpl) poly_red._getImpl()).querySegmentIterator(); + + HashMap map1 = new HashMap(0); + + int count = 0; + int intersections_per_query = 0; + while (seg_iter_red.nextPath()) { + while (seg_iter_red.hasNextSegment()) { + Segment segment_red = seg_iter_red.nextSegment(); + segment_red.queryEnvelope2D(env); + + iterator.resetIterator(env, 0.0); + + int count_upper = 0; + int element_handle; + while ((element_handle = iterator.next()) != -1) { + count_upper++; + int index = quad_tree_blue.getElement(element_handle); + Boolean iter = (Boolean) map1.get(index); + if (iter == null) { + count++; + map1.put(index, true); + } + + intersections_per_query++; + } + + int intersection_count = quad_tree_blue.getIntersectionCount(env, 0.0, -1); + assertTrue(intersection_count == count_upper); + } + } + + seg_iter_red.resetToFirstPath(); + + HashMap map2 = new HashMap(0); + QuadTree.QuadTreeIterator iterator_duplicates = quad_tree_blue_duplicates.getIterator(); + + int count_duplicates = 0; + int intersections_per_query_duplicates = 0; + while (seg_iter_red.nextPath()) { + while (seg_iter_red.hasNextSegment()) { + Segment segment_red = seg_iter_red.nextSegment(); + segment_red.queryEnvelope2D(env); + + iterator_duplicates.resetIterator(env, 0.0); + + int count_lower = 0; + HashMap map_per_query = new HashMap(0); + + int count_upper = 0; + int element_handle; + while ((element_handle = iterator_duplicates.next()) != -1) { + count_upper++; + int index = quad_tree_blue_duplicates.getElement(element_handle); + Boolean iter = (Boolean) map2.get(index); + if (iter == null) { + count_duplicates++; + map2.put(index, true); + } + + + Boolean iter_lower = (Boolean) map_per_query.get(index); + if (iter_lower == null) { + count_lower++; + intersections_per_query_duplicates++; + map_per_query.put(index, true); + } + + int q = quad_tree_blue_duplicates.getQuad(element_handle); + assertTrue(quad_tree_blue_duplicates.getSubTreeElementCount(q) >= quad_tree_blue_duplicates.getContainedSubTreeElementCount(q)); + } + + int intersection_count = quad_tree_blue_duplicates.getIntersectionCount(env, 0.0, -1); + boolean b_has_data = quad_tree_blue_duplicates.hasData(env, 0.0); + assertTrue(b_has_data || intersection_count == 0); + assertTrue(count_lower <= intersection_count && intersection_count <= count_upper); + assertTrue(count_upper <= 4 * count_lower); + } + } + + assertTrue(count == count_duplicates); + assertTrue(intersections_per_query == intersections_per_query_duplicates); + } + } + + @Test + public static void testSortedIterator() { + int pass_count = 10; + int figure_size = 400; + int figure_size2 = 100; + Envelope extent1 = new Envelope(); + extent1.setCoords(-100000, -100000, 100000, 100000); + + RandomCoordinateGenerator generator1 = new RandomCoordinateGenerator(Math.max(figure_size, 10000), extent1, 0.001); + + Random random = new Random(2013); + int rand_max = 32; + + Polygon poly_red = new Polygon(); + Polygon poly_blue = new Polygon(); + + int r = figure_size; + + for (int c = 0; c < pass_count; c++) { + Point pt; + for (int j = 0; j < r; j++) { + int rand = random.nextInt(rand_max); + boolean b_random_new = r > 10 && ((1.0 * rand) / rand_max > 0.95); + pt = generator1.GetRandomCoord(); + if (j == 0 || b_random_new) + poly_blue.startPath(pt); + else + poly_blue.lineTo(pt); + } + + Envelope2D env = new Envelope2D(); + + QuadTree quad_tree_blue = buildQuadTree_((MultiPathImpl) poly_blue._getImpl(), false); + + Envelope2D e1 = quad_tree_blue.getDataExtent(); + assertTrue(quad_tree_blue.getElementCount() == poly_blue.getSegmentCount()); + + SegmentIterator seg_iter_blue = poly_blue.querySegmentIterator(); + + poly_red.setEmpty(); + + r = figure_size2; + if (r < 3) + continue; + + for (int j = 0; j < r; j++) { + int rand = random.nextInt(rand_max); + boolean b_random_new = r > 10 && ((1.0 * rand) / rand_max > 0.95); + pt = generator1.GetRandomCoord(); + if (j == 0 || b_random_new) + poly_red.startPath(pt); + else + poly_red.lineTo(pt); + } + + QuadTree.QuadTreeIterator iterator = quad_tree_blue.getIterator(); + SegmentIteratorImpl seg_iter_red = ((MultiPathImpl) poly_red._getImpl()).querySegmentIterator(); + + HashMap map1 = new HashMap(0); + + int count = 0; + int intersections_per_query = 0; + while (seg_iter_red.nextPath()) { + while (seg_iter_red.hasNextSegment()) { + Segment segment_red = seg_iter_red.nextSegment(); + segment_red.queryEnvelope2D(env); + + iterator.resetIterator(env, 0.0); + + int count_upper = 0; + int element_handle; + while ((element_handle = iterator.next()) != -1) { + count_upper++; + int index = quad_tree_blue.getElement(element_handle); + Boolean iter = (Boolean) map1.get(index); + if (iter == null) { + count++; + map1.put(index, true); + } + + intersections_per_query++; + } + + int intersection_count = quad_tree_blue.getIntersectionCount(env, 0.0, -1); + assertTrue(intersection_count == count_upper); + } + } + + seg_iter_red.resetToFirstPath(); + + HashMap map2 = new HashMap(0); + QuadTree.QuadTreeIterator sorted_iterator = quad_tree_blue.getIterator(true); + + int count_sorted = 0; + int intersections_per_query_sorted = 0; + while (seg_iter_red.nextPath()) { + while (seg_iter_red.hasNextSegment()) { + Segment segment_red = seg_iter_red.nextSegment(); + segment_red.queryEnvelope2D(env); + + sorted_iterator.resetIterator(env, 0.0); + + int count_upper_sorted = 0; + int element_handle; + int last_index = -1; + while ((element_handle = sorted_iterator.next()) != -1) { + count_upper_sorted++; + int index = quad_tree_blue.getElement(element_handle); + assertTrue(last_index < index); // ensure the element handles are returned in sorted order + last_index = index; + Boolean iter = (Boolean) map2.get(index); + if (iter == null) { + count_sorted++; + map2.put(index, true); + } + + intersections_per_query_sorted++; + } + + int intersection_count = quad_tree_blue.getIntersectionCount(env, 0.0, -1); + assertTrue(intersection_count == count_upper_sorted); + } + } + + assertTrue(count == count_sorted); + assertTrue(intersections_per_query == intersections_per_query_sorted); + } + } + + @Test + public static void test_perf_quad_tree() { + Envelope extent1 = new Envelope(); + extent1.setCoords(-1000, -1000, 1000, 1000); + + RandomCoordinateGenerator generator1 = new RandomCoordinateGenerator(1000, extent1, 0.001); + //HiResTimer timer; + for (int N = 16; N <= 1024/**1024*/; N *= 2) { + //timer.StartMeasurement(); + + Envelope2D extent = new Envelope2D(); + extent.setCoords(-1000, -1000, 1000, 1000); + HashMap data = new HashMap(0); + QuadTree qt = new QuadTree(extent, 10); + for (int i = 0; i < N; i++) { + Envelope2D env = new Envelope2D(); + Point2D center = generator1.GetRandomCoord().getXY(); + double w = 10; + env.setCoords(center, w, w); + env.intersect(extent); + if (env.isEmpty()) + continue; + + int h = qt.insert(i, env); + data.put(h, env); + } + + int ecount = 0; + AttributeStreamOfInt32 handles = new AttributeStreamOfInt32(0); + QuadTree.QuadTreeIterator iter = qt.getIterator(); + + Iterator> pairs = data.entrySet().iterator(); + while (pairs.hasNext()) { + Map.Entry entry = pairs.next(); + iter.resetIterator((Envelope2D) entry.getValue(), 0.001); + boolean remove_self = false; + for (int h = iter.next(); h != -1; h = iter.next()) { + if (h != entry.getKey()) + handles.add(h); + else { + remove_self = true; + } + + ecount++; + } + + for (int i = 0; i < handles.size(); i++) { + qt.removeElement(handles.get(i));//remove elements that were selected. + } + + if (remove_self) + qt.removeElement(entry.getKey()); + handles.resize(0); + } + + //printf("%d %0.3f (%I64d, %f, mem %I64d)\n", N, timer.GetMilliseconds(), ecount, ecount / double(N * N), memsize); + } + } + + @Test + public static void test2() { + MultiPoint multipoint = new MultiPoint(); + + for (int i = 0; i < 100; i++) { + for (int j = 0; j < 100; j++) { + multipoint.add(i, j); + } + } + + Envelope2D extent = new Envelope2D(); + multipoint.queryEnvelope2D(extent); + + MultiPointImpl multipointImpl = (MultiPointImpl) multipoint._getImpl(); + QuadTree quadtree = buildQuadTree_(multipointImpl); + + QuadTree.QuadTreeIterator qtIter = quadtree.getIterator(); + assertTrue(qtIter.next() == -1); + + int count = 0; + qtIter.resetIterator(extent, 0.0); + + while (qtIter.next() != -1) { + count++; + } + + assertTrue(count == 10000); + } + + static double randomWithRange(double min, double max) { + double range = Math.abs(max - min); + return (Math.random() * range) + (min <= max ? min : max); + } // @Test // public static void test_quadTree() { @@ -468,76 +468,76 @@ static double randomWithRange(double min, double max) { // } - public static Polyline makePolyline() { - Polyline poly = new Polyline(); - - // 0 - poly.startPath(0, 40); - poly.lineTo(30, 0); - - // 1 - poly.startPath(20, 70); - poly.lineTo(45, 100); - - // 2 - poly.startPath(50, 100); - poly.lineTo(50, 60); - - // 3 - poly.startPath(35, 25); - poly.lineTo(65, 45); - - // 4 - poly.startPath(60, 10); - poly.lineTo(65, 35); - - // 5 - poly.startPath(60, 60); - poly.lineTo(100, 60); - - // 6 - poly.startPath(80, 10); - poly.lineTo(80, 99); - - // 7 - poly.startPath(60, 60); - poly.lineTo(65, 35); - - return poly; - } - - static QuadTree buildQuadTree_(MultiPathImpl multipathImpl, boolean bStoreDuplicates) { - Envelope2D extent = new Envelope2D(); - multipathImpl.queryEnvelope2D(extent); - QuadTree quadTree = new QuadTree(extent, 8, bStoreDuplicates); - int hint_index = -1; - Envelope2D boundingbox = new Envelope2D(); - SegmentIteratorImpl seg_iter = multipathImpl.querySegmentIterator(); - while (seg_iter.nextPath()) { - while (seg_iter.hasNextSegment()) { - Segment segment = seg_iter.nextSegment(); - int index = seg_iter.getStartPointIndex(); - segment.queryEnvelope2D(boundingbox); - hint_index = quadTree.insert(index, boundingbox, hint_index); - } - } - - return quadTree; - } - - static QuadTree buildQuadTree_(MultiPointImpl multipointImpl) { - Envelope2D extent = new Envelope2D(); - multipointImpl.queryEnvelope2D(extent); - QuadTree quadTree = new QuadTree(extent, 8); - Envelope2D boundingbox = new Envelope2D(); - Point2D pt; - - for (int i = 0; i < multipointImpl.getPointCount(); i++) { - pt = multipointImpl.getXY(i); - boundingbox.setCoords(pt.x, pt.y, pt.x, pt.y); - quadTree.insert(i, boundingbox, -1); - } - - return quadTree; - } + public static Polyline makePolyline() { + Polyline poly = new Polyline(); + + // 0 + poly.startPath(0, 40); + poly.lineTo(30, 0); + + // 1 + poly.startPath(20, 70); + poly.lineTo(45, 100); + + // 2 + poly.startPath(50, 100); + poly.lineTo(50, 60); + + // 3 + poly.startPath(35, 25); + poly.lineTo(65, 45); + + // 4 + poly.startPath(60, 10); + poly.lineTo(65, 35); + + // 5 + poly.startPath(60, 60); + poly.lineTo(100, 60); + + // 6 + poly.startPath(80, 10); + poly.lineTo(80, 99); + + // 7 + poly.startPath(60, 60); + poly.lineTo(65, 35); + + return poly; + } + + static QuadTree buildQuadTree_(MultiPathImpl multipathImpl, boolean bStoreDuplicates) { + Envelope2D extent = new Envelope2D(); + multipathImpl.queryEnvelope2D(extent); + QuadTree quadTree = new QuadTree(extent, 8, bStoreDuplicates); + int hint_index = -1; + Envelope2D boundingbox = new Envelope2D(); + SegmentIteratorImpl seg_iter = multipathImpl.querySegmentIterator(); + while (seg_iter.nextPath()) { + while (seg_iter.hasNextSegment()) { + Segment segment = seg_iter.nextSegment(); + int index = seg_iter.getStartPointIndex(); + segment.queryEnvelope2D(boundingbox); + hint_index = quadTree.insert(index, boundingbox, hint_index); + } + } + + return quadTree; + } + + static QuadTree buildQuadTree_(MultiPointImpl multipointImpl) { + Envelope2D extent = new Envelope2D(); + multipointImpl.queryEnvelope2D(extent); + QuadTree quadTree = new QuadTree(extent, 8); + Envelope2D boundingbox = new Envelope2D(); + Point2D pt; + + for (int i = 0; i < multipointImpl.getPointCount(); i++) { + pt = multipointImpl.getXY(i); + boundingbox.setCoords(pt.x, pt.y, pt.x, pt.y); + quadTree.insert(i, boundingbox, -1); + } + + return quadTree; + } } diff --git a/src/test/java/com/esri/core/geometry/TestRandomPoints.java b/src/test/java/com/esri/core/geometry/TestRandomPoints.java index ed2d346a..81b92429 100644 --- a/src/test/java/com/esri/core/geometry/TestRandomPoints.java +++ b/src/test/java/com/esri/core/geometry/TestRandomPoints.java @@ -7,89 +7,89 @@ * Created by davidraleigh on 5/10/17. */ public class TestRandomPoints extends TestCase { - @Override - protected void setUp() throws Exception { - super.setUp(); - } + @Override + protected void setUp() throws Exception { + super.setUp(); + } - @Override - protected void tearDown() throws Exception { - super.tearDown(); - } + @Override + protected void tearDown() throws Exception { + super.tearDown(); + } - @Test - public void testPointCreate() { - OperatorRandomPoints operatorRandomPoints = OperatorRandomPoints.local(); - Polygon poly = new Polygon(); - poly.startPath(0, 0); - poly.lineTo(0, 10); - poly.lineTo(10, 10); - poly.closePathWithLine(); - SpatialReference sr = SpatialReference.create(4326); - MultiPoint geometry = (MultiPoint) operatorRandomPoints.execute(poly, 1.3, 1977, sr, null); - assertNotNull(geometry); - assertEquals(788593, geometry.getPointCount()); - assertNotNull(geometry.getXY(0)); - assertNotNull(geometry.getXY(geometry.getPointCount() - 1)); - Polygon bufferedpoly = (Polygon) OperatorBuffer.local().execute(poly, sr, sr.getTolerance() * 2, null); - boolean t = OperatorContains.local().execute(bufferedpoly, geometry, sr, null); - assertTrue(t); - } + @Test + public void testPointCreate() { + OperatorRandomPoints operatorRandomPoints = OperatorRandomPoints.local(); + Polygon poly = new Polygon(); + poly.startPath(0, 0); + poly.lineTo(0, 10); + poly.lineTo(10, 10); + poly.closePathWithLine(); + SpatialReference sr = SpatialReference.create(4326); + MultiPoint geometry = (MultiPoint) operatorRandomPoints.execute(poly, 1.3, 1977, sr, null); + assertNotNull(geometry); + assertEquals(788593, geometry.getPointCount()); + assertNotNull(geometry.getXY(0)); + assertNotNull(geometry.getXY(geometry.getPointCount() - 1)); + Polygon bufferedpoly = (Polygon) OperatorBuffer.local().execute(poly, sr, sr.getTolerance() * 2, null); + boolean t = OperatorContains.local().execute(bufferedpoly, geometry, sr, null); + assertTrue(t); + } - @Test - public void testPolygonWithHole() { - String wktpolygon = "POLYGON((0 0, 0 10, 10 10, 10 0),(3 3, 7 3, 7 7, 3 7))"; - Geometry geometry = GeometryEngine.geometryFromWkt(wktpolygon, 0, Geometry.Type.Unknown); - MultiPoint multiPoint = (MultiPoint)OperatorRandomPoints.local().execute(geometry, .0013, 1977, SpatialReference.create(4326), null); + @Test + public void testPolygonWithHole() { + String wktpolygon = "POLYGON((0 0, 0 10, 10 10, 10 0),(3 3, 7 3, 7 7, 3 7))"; + Geometry geometry = GeometryEngine.geometryFromWkt(wktpolygon, 0, Geometry.Type.Unknown); + MultiPoint multiPoint = (MultiPoint) OperatorRandomPoints.local().execute(geometry, .0013, 1977, SpatialReference.create(4326), null); - String wktPolygonNoRing = "POLYGON((0 0, 0 10, 10 10, 10 0))"; - Geometry geometryNoRing = GeometryEngine.geometryFromWkt(wktPolygonNoRing, 0, Geometry.Type.Unknown); - MultiPoint multiPointNoRing = (MultiPoint)OperatorRandomPoints.local().execute(geometryNoRing, 0.0013, 1977, SpatialReference.create(4326), null); + String wktPolygonNoRing = "POLYGON((0 0, 0 10, 10 10, 10 0))"; + Geometry geometryNoRing = GeometryEngine.geometryFromWkt(wktPolygonNoRing, 0, Geometry.Type.Unknown); + MultiPoint multiPointNoRing = (MultiPoint) OperatorRandomPoints.local().execute(geometryNoRing, 0.0013, 1977, SpatialReference.create(4326), null); - Geometry intersector = OperatorGeodeticDensifyByLength.local().execute(geometry, SpatialReference.create(4326), 1232535.5660433513, GeodeticCurveType.Geodesic, null); - Geometry geom = GeometryEngine.intersect(intersector, multiPointNoRing, SpatialReference.create(4326)); + Geometry intersector = OperatorGeodeticDensifyByLength.local().execute(geometry, SpatialReference.create(4326), 1232535.5660433513, GeodeticCurveType.Geodesic, null); + Geometry geom = GeometryEngine.intersect(intersector, multiPointNoRing, SpatialReference.create(4326)); - assertEquals(multiPoint.getPointCount(), ((MultiPoint)geom).getPointCount()); - } + assertEquals(multiPoint.getPointCount(), ((MultiPoint) geom).getPointCount()); + } - @Test - public void testMultiPartPolygonCreate() { - String wktpolygon2 = "MULTIPOLYGON (((0 0, 0 10, 10 10, 10 0)), ((20 0, 20 10, 30 10, 30 0)))"; - Geometry geometry2 = GeometryEngine.geometryFromWkt(wktpolygon2, 0, Geometry.Type.Unknown); - MultiPoint multiPoint2 = (MultiPoint)OperatorRandomPoints.local().execute(geometry2, 1.3, 1977, SpatialReference.create(4326), null); + @Test + public void testMultiPartPolygonCreate() { + String wktpolygon2 = "MULTIPOLYGON (((0 0, 0 10, 10 10, 10 0)), ((20 0, 20 10, 30 10, 30 0)))"; + Geometry geometry2 = GeometryEngine.geometryFromWkt(wktpolygon2, 0, Geometry.Type.Unknown); + MultiPoint multiPoint2 = (MultiPoint) OperatorRandomPoints.local().execute(geometry2, 1.3, 1977, SpatialReference.create(4326), null); - String wktPolygon = "POLYGON((0 0, 0 10, 10 10, 10 0))"; - Geometry geometry = GeometryEngine.geometryFromWkt(wktPolygon, 0, Geometry.Type.Unknown); - MultiPoint multiPoint = (MultiPoint)OperatorRandomPoints.local().execute(geometry, 1.3, 1977, SpatialReference.create(4326), null); + String wktPolygon = "POLYGON((0 0, 0 10, 10 10, 10 0))"; + Geometry geometry = GeometryEngine.geometryFromWkt(wktPolygon, 0, Geometry.Type.Unknown); + MultiPoint multiPoint = (MultiPoint) OperatorRandomPoints.local().execute(geometry, 1.3, 1977, SpatialReference.create(4326), null); - assertTrue(multiPoint.getPointCount() * 2 > 3179429); - assertTrue(multiPoint2.getPointCount() * 2 > 3179429); - } + assertTrue(multiPoint.getPointCount() * 2 > 3179429); + assertTrue(multiPoint2.getPointCount() * 2 > 3179429); + } - @Test - public void testExcpetion() { - String wkt = "Polygon((0 0, 0 10, 10 10,10 0))"; - Geometry geometry = GeometryEngine.geometryFromWkt(wkt, 0, Geometry.Type.Unknown); - try { - // TODO exception java.lang.OutOfMemoryError: Java heap space for pointsPerSquareKm == 300 - MultiPoint multiPoint = (MultiPoint)OperatorRandomPoints.local().execute(geometry, 1000, 1977, SpatialReference.create(4326), null); - fail("Expected an GeometryException to be thrown"); - } catch (GeometryException geometryException) { - assertEquals(geometryException.getMessage(), "Random Point count outside of available"); - } - } + @Test + public void testExcpetion() { + String wkt = "Polygon((0 0, 0 10, 10 10,10 0))"; + Geometry geometry = GeometryEngine.geometryFromWkt(wkt, 0, Geometry.Type.Unknown); + try { + // TODO exception java.lang.OutOfMemoryError: Java heap space for pointsPerSquareKm == 300 + MultiPoint multiPoint = (MultiPoint) OperatorRandomPoints.local().execute(geometry, 1000, 1977, SpatialReference.create(4326), null); + fail("Expected an GeometryException to be thrown"); + } catch (GeometryException geometryException) { + assertEquals(geometryException.getMessage(), "Random Point count outside of available"); + } + } - @Test - public void testSpecificArea() { - double pointsPerKmSquare = 1; - Double areaKm = 16207.53; - int lowEstimate = areaKm.intValue() - 20; - String wkt = "POLYGON ((46.030485054706105 26.017389342815264, 45.997526070331105 25.016021311217134, 47.469694039081105 24.996108304947285, 47.447721382831105 26.007515943484098, 46.030485054706105 26.017389342815264))"; - Geometry geometry = GeometryEngine.geometryFromWkt(wkt, 0, Geometry.Type.Unknown); + @Test + public void testSpecificArea() { + double pointsPerKmSquare = 1; + Double areaKm = 16207.53; + int lowEstimate = areaKm.intValue() - 20; + String wkt = "POLYGON ((46.030485054706105 26.017389342815264, 45.997526070331105 25.016021311217134, 47.469694039081105 24.996108304947285, 47.447721382831105 26.007515943484098, 46.030485054706105 26.017389342815264))"; + Geometry geometry = GeometryEngine.geometryFromWkt(wkt, 0, Geometry.Type.Unknown); - MultiPoint multiPoint = (MultiPoint)OperatorRandomPoints.local().execute(geometry, pointsPerKmSquare, 1977, SpatialReference.create(4326), null); + MultiPoint multiPoint = (MultiPoint) OperatorRandomPoints.local().execute(geometry, pointsPerKmSquare, 1977, SpatialReference.create(4326), null); - assertTrue(multiPoint.getPointCount() > lowEstimate); - assertTrue(multiPoint.getPointCount() < lowEstimate + 20); - } + assertTrue(multiPoint.getPointCount() > lowEstimate); + assertTrue(multiPoint.getPointCount() < lowEstimate + 20); + } } diff --git a/src/test/java/com/esri/core/geometry/TestRasterizedGeometry2D.java b/src/test/java/com/esri/core/geometry/TestRasterizedGeometry2D.java index b0b806ee..2e035024 100644 --- a/src/test/java/com/esri/core/geometry/TestRasterizedGeometry2D.java +++ b/src/test/java/com/esri/core/geometry/TestRasterizedGeometry2D.java @@ -29,130 +29,130 @@ import org.junit.Test; public class TestRasterizedGeometry2D extends TestCase { - boolean rgHelper(RasterizedGeometry2D rg, MultiPath mp) { - SegmentIterator iter = mp.querySegmentIterator(); - while (iter.nextPath()) { - while (iter.hasNextSegment()) { - Segment seg = iter.nextSegment(); - int count = 20; - - for (int i = 0; i < count; i++) { - double t = (1.0 * i / count); - Point2D pt = seg.getCoord2D(t); - RasterizedGeometry2D.HitType hit = rg.queryPointInGeometry( - pt.x, pt.y); - if (hit != RasterizedGeometry2D.HitType.Border) - return false; - } - } - } - - if (mp.getType() != Geometry.Type.Polygon) - return true; - - Polygon poly = (Polygon) mp; - Envelope2D env = new Envelope2D(); - poly.queryEnvelope2D(env); - int count = 100; - for (int iy = 0; iy < count; iy++) { - double ty = 1.0 * iy / count; - double y = env.ymin * (1.0 - ty) + ty * env.ymax; - for (int ix = 0; ix < count; ix++) { - double tx = 1.0 * ix / count; - double x = env.xmin * (1.0 - tx) + tx * env.xmax; - - RasterizedGeometry2D.HitType hit = rg - .queryPointInGeometry(x, y); - PolygonUtils.PiPResult res = PolygonUtils.isPointInPolygon2D( - poly, new Point2D(x, y), 0); - if (res == PolygonUtils.PiPResult.PiPInside) { - boolean bgood = (hit == RasterizedGeometry2D.HitType.Border || hit == RasterizedGeometry2D.HitType.Inside); - if (!bgood) - return false; - } else if (res == PolygonUtils.PiPResult.PiPOutside) { - boolean bgood = (hit == RasterizedGeometry2D.HitType.Border || hit == RasterizedGeometry2D.HitType.Outside); - if (!bgood) - return false; - } else { - boolean bgood = (hit == RasterizedGeometry2D.HitType.Border); - if (!bgood) - return false; - } - } - } - - return true; - } - - @Test - public void test() { - { - Polygon poly = new Polygon(); - poly.startPath(10, 10); - poly.lineTo(100, 10); - poly.lineTo(100, 100); - poly.lineTo(10, 100); - - // create using move semantics. Usually we do not use this - // approach. - RasterizedGeometry2D rg = RasterizedGeometry2D - .create(poly, 0, 1024); - //rg.dbgSaveToBitmap("c:/temp/_dbg.bmp"); - RasterizedGeometry2D.HitType res; - res = rg.queryPointInGeometry(7, 10); - assertTrue(res == RasterizedGeometry2D.HitType.Outside); - res = rg.queryPointInGeometry(10, 10); - assertTrue(res == RasterizedGeometry2D.HitType.Border); - res = rg.queryPointInGeometry(50, 50); - assertTrue(res == RasterizedGeometry2D.HitType.Inside); - - assertTrue(rgHelper(rg, poly)); - } - - { - Polygon poly = new Polygon(); - // create a star (non-simple) - poly.startPath(1, 0); - poly.lineTo(5, 10); - poly.lineTo(9, 0); - poly.lineTo(0, 6); - poly.lineTo(10, 6); - - RasterizedGeometry2D rg = RasterizedGeometry2D - .create(poly, 0, 1024); - //rg.dbgSaveToBitmap("c:/temp/_dbg.bmp"); - RasterizedGeometry2D.HitType res; - res = rg.queryPointInGeometry(5, 5.5); - assertTrue(res == RasterizedGeometry2D.HitType.Outside); - res = rg.queryPointInGeometry(5, 8); - assertTrue(res == RasterizedGeometry2D.HitType.Inside); - res = rg.queryPointInGeometry(1.63, 0.77); - assertTrue(res == RasterizedGeometry2D.HitType.Inside); - res = rg.queryPointInGeometry(1, 3); - assertTrue(res == RasterizedGeometry2D.HitType.Outside); - res = rg.queryPointInGeometry(1.6, 0.1); - assertTrue(res == RasterizedGeometry2D.HitType.Outside); - assertTrue(rgHelper(rg, poly)); - } - - { - Polygon poly = new Polygon(); - // create a star (non-simple) - poly.startPath(1, 0); - poly.lineTo(5, 10); - poly.lineTo(9, 0); - poly.lineTo(0, 6); - poly.lineTo(10, 6); - - SpatialReference sr = SpatialReference.create(4326); - poly = (Polygon) OperatorSimplify.local().execute(poly, sr, true, null); - OperatorContains.local().accelerateGeometry(poly, sr, GeometryAccelerationDegree.enumMedium); - assertFalse(OperatorContains.local().execute(poly, new Point(5, 5.5), sr, null)); - assertTrue(OperatorContains.local().execute(poly, new Point(5, 8), sr, null)); - assertTrue(OperatorContains.local().execute(poly, new Point(1.63, 0.77), sr, null)); - assertFalse(OperatorContains.local().execute(poly, new Point(1, 3), sr, null)); - assertFalse(OperatorContains.local().execute(poly, new Point(1.6, 0.1), sr, null)); - } + boolean rgHelper(RasterizedGeometry2D rg, MultiPath mp) { + SegmentIterator iter = mp.querySegmentIterator(); + while (iter.nextPath()) { + while (iter.hasNextSegment()) { + Segment seg = iter.nextSegment(); + int count = 20; + + for (int i = 0; i < count; i++) { + double t = (1.0 * i / count); + Point2D pt = seg.getCoord2D(t); + RasterizedGeometry2D.HitType hit = rg.queryPointInGeometry( + pt.x, pt.y); + if (hit != RasterizedGeometry2D.HitType.Border) + return false; + } + } + } + + if (mp.getType() != Geometry.Type.Polygon) + return true; + + Polygon poly = (Polygon) mp; + Envelope2D env = new Envelope2D(); + poly.queryEnvelope2D(env); + int count = 100; + for (int iy = 0; iy < count; iy++) { + double ty = 1.0 * iy / count; + double y = env.ymin * (1.0 - ty) + ty * env.ymax; + for (int ix = 0; ix < count; ix++) { + double tx = 1.0 * ix / count; + double x = env.xmin * (1.0 - tx) + tx * env.xmax; + + RasterizedGeometry2D.HitType hit = rg + .queryPointInGeometry(x, y); + PolygonUtils.PiPResult res = PolygonUtils.isPointInPolygon2D( + poly, new Point2D(x, y), 0); + if (res == PolygonUtils.PiPResult.PiPInside) { + boolean bgood = (hit == RasterizedGeometry2D.HitType.Border || hit == RasterizedGeometry2D.HitType.Inside); + if (!bgood) + return false; + } else if (res == PolygonUtils.PiPResult.PiPOutside) { + boolean bgood = (hit == RasterizedGeometry2D.HitType.Border || hit == RasterizedGeometry2D.HitType.Outside); + if (!bgood) + return false; + } else { + boolean bgood = (hit == RasterizedGeometry2D.HitType.Border); + if (!bgood) + return false; + } + } + } + + return true; + } + + @Test + public void test() { + { + Polygon poly = new Polygon(); + poly.startPath(10, 10); + poly.lineTo(100, 10); + poly.lineTo(100, 100); + poly.lineTo(10, 100); + + // create using move semantics. Usually we do not use this + // approach. + RasterizedGeometry2D rg = RasterizedGeometry2D + .create(poly, 0, 1024); + //rg.dbgSaveToBitmap("c:/temp/_dbg.bmp"); + RasterizedGeometry2D.HitType res; + res = rg.queryPointInGeometry(7, 10); + assertTrue(res == RasterizedGeometry2D.HitType.Outside); + res = rg.queryPointInGeometry(10, 10); + assertTrue(res == RasterizedGeometry2D.HitType.Border); + res = rg.queryPointInGeometry(50, 50); + assertTrue(res == RasterizedGeometry2D.HitType.Inside); + + assertTrue(rgHelper(rg, poly)); + } + + { + Polygon poly = new Polygon(); + // create a star (non-simple) + poly.startPath(1, 0); + poly.lineTo(5, 10); + poly.lineTo(9, 0); + poly.lineTo(0, 6); + poly.lineTo(10, 6); + + RasterizedGeometry2D rg = RasterizedGeometry2D + .create(poly, 0, 1024); + //rg.dbgSaveToBitmap("c:/temp/_dbg.bmp"); + RasterizedGeometry2D.HitType res; + res = rg.queryPointInGeometry(5, 5.5); + assertTrue(res == RasterizedGeometry2D.HitType.Outside); + res = rg.queryPointInGeometry(5, 8); + assertTrue(res == RasterizedGeometry2D.HitType.Inside); + res = rg.queryPointInGeometry(1.63, 0.77); + assertTrue(res == RasterizedGeometry2D.HitType.Inside); + res = rg.queryPointInGeometry(1, 3); + assertTrue(res == RasterizedGeometry2D.HitType.Outside); + res = rg.queryPointInGeometry(1.6, 0.1); + assertTrue(res == RasterizedGeometry2D.HitType.Outside); + assertTrue(rgHelper(rg, poly)); + } + + { + Polygon poly = new Polygon(); + // create a star (non-simple) + poly.startPath(1, 0); + poly.lineTo(5, 10); + poly.lineTo(9, 0); + poly.lineTo(0, 6); + poly.lineTo(10, 6); + + SpatialReference sr = SpatialReference.create(4326); + poly = (Polygon) OperatorSimplify.local().execute(poly, sr, true, null); + OperatorContains.local().accelerateGeometry(poly, sr, GeometryAccelerationDegree.enumMedium); + assertFalse(OperatorContains.local().execute(poly, new Point(5, 5.5), sr, null)); + assertTrue(OperatorContains.local().execute(poly, new Point(5, 8), sr, null)); + assertTrue(OperatorContains.local().execute(poly, new Point(1.63, 0.77), sr, null)); + assertFalse(OperatorContains.local().execute(poly, new Point(1, 3), sr, null)); + assertFalse(OperatorContains.local().execute(poly, new Point(1.6, 0.1), sr, null)); + } /* { @@ -171,5 +171,5 @@ public void test() { rg.dbgSaveToBitmap("c:/temp/_dbg.bmp"); for (;;){} }*/ - } + } } diff --git a/src/test/java/com/esri/core/geometry/TestRelation.java b/src/test/java/com/esri/core/geometry/TestRelation.java index 50b8c0b9..fc912f22 100644 --- a/src/test/java/com/esri/core/geometry/TestRelation.java +++ b/src/test/java/com/esri/core/geometry/TestRelation.java @@ -11,71 +11,71 @@ public class TestRelation extends TestCase { - @Override - protected void setUp() throws Exception { - super.setUp(); - } - - @Override - protected void tearDown() throws Exception { - super.tearDown(); - } - - @Test - public void testCreation() { - { - OperatorFactoryLocal projEnv = OperatorFactoryLocal.getInstance(); - SpatialReference inputSR = SpatialReference.create(3857); - - Polygon poly1 = new Polygon(); - Envelope2D env1 = new Envelope2D(); - env1.setCoords(855277, 3892059, 855277 + 100, 3892059 + 100); - poly1.addEnvelope(env1, false); - - Polygon poly2 = new Polygon(); - Envelope2D env2 = new Envelope2D(); - env2.setCoords(855277, 3892059, 855277 + 300, 3892059 + 200); - poly2.addEnvelope(env2, false); - - Polygon poly3 = new Polygon(); - Envelope2D env3 = new Envelope2D(); - env3.setCoords(855277 + 100, 3892059 + 100, 855277 - 300, 3892059 - 200); - poly3.addEnvelope(env3, false); - - - List list2 = new ArrayList<>(); - list2.add(poly1); - list2.add(poly2); - - List list3 = new ArrayList<>(list2); - list3.add(poly3); - - - { - OperatorEquals operatorEquals = (OperatorEquals) (projEnv - .getOperator(Operator.Type.Equals)); - boolean result = operatorEquals.execute(poly1, poly2, inputSR, - null); - assertTrue(!result); - Polygon poly11 = new Polygon(); - poly1.copyTo(poly11); - result = operatorEquals.execute(poly1, poly11, inputSR, null); - assertTrue(result); - } - { - OperatorCrosses operatorCrosses = (OperatorCrosses) (projEnv - .getOperator(Operator.Type.Crosses)); - boolean result = operatorCrosses.execute(poly1, poly2, inputSR, - null); - assertTrue(!result); - - SimpleGeometryCursor simpleGeometryCursor1 = new SimpleGeometryCursor(poly1); - SimpleGeometryCursor simpleGeometryCursor2 = new SimpleGeometryCursor(new ArrayDeque(list2)); - HashMap relate_map = - operatorCrosses.execute(poly1, simpleGeometryCursor2, inputSR, null); - assertNotNull(relate_map); - assertTrue(!relate_map.get(0L)); - assertTrue(!relate_map.get(1L)); + @Override + protected void setUp() throws Exception { + super.setUp(); + } + + @Override + protected void tearDown() throws Exception { + super.tearDown(); + } + + @Test + public void testCreation() { + { + OperatorFactoryLocal projEnv = OperatorFactoryLocal.getInstance(); + SpatialReference inputSR = SpatialReference.create(3857); + + Polygon poly1 = new Polygon(); + Envelope2D env1 = new Envelope2D(); + env1.setCoords(855277, 3892059, 855277 + 100, 3892059 + 100); + poly1.addEnvelope(env1, false); + + Polygon poly2 = new Polygon(); + Envelope2D env2 = new Envelope2D(); + env2.setCoords(855277, 3892059, 855277 + 300, 3892059 + 200); + poly2.addEnvelope(env2, false); + + Polygon poly3 = new Polygon(); + Envelope2D env3 = new Envelope2D(); + env3.setCoords(855277 + 100, 3892059 + 100, 855277 - 300, 3892059 - 200); + poly3.addEnvelope(env3, false); + + + List list2 = new ArrayList<>(); + list2.add(poly1); + list2.add(poly2); + + List list3 = new ArrayList<>(list2); + list3.add(poly3); + + + { + OperatorEquals operatorEquals = (OperatorEquals) (projEnv + .getOperator(Operator.Type.Equals)); + boolean result = operatorEquals.execute(poly1, poly2, inputSR, + null); + assertTrue(!result); + Polygon poly11 = new Polygon(); + poly1.copyTo(poly11); + result = operatorEquals.execute(poly1, poly11, inputSR, null); + assertTrue(result); + } + { + OperatorCrosses operatorCrosses = (OperatorCrosses) (projEnv + .getOperator(Operator.Type.Crosses)); + boolean result = operatorCrosses.execute(poly1, poly2, inputSR, + null); + assertTrue(!result); + + SimpleGeometryCursor simpleGeometryCursor1 = new SimpleGeometryCursor(poly1); + SimpleGeometryCursor simpleGeometryCursor2 = new SimpleGeometryCursor(new ArrayDeque(list2)); + HashMap relate_map = + operatorCrosses.execute(poly1, simpleGeometryCursor2, inputSR, null); + assertNotNull(relate_map); + assertTrue(!relate_map.get(0L)); + assertTrue(!relate_map.get(1L)); /*simpleGeometryCursor2 = new SimpleGeometryCursor(list2); SimpleGeometryCursor simpleGeometryCursor3 = new SimpleGeometryCursor(list3); @@ -87,5454 +87,5454 @@ public void testCreation() { assertTrue(!relate_map.get(1).get(1)); assertTrue(!relate_map.get(2).get(0)); assertTrue(!relate_map.get(2).get(1));*/ - } - { - OperatorWithin operatorWithin = (OperatorWithin) (projEnv - .getOperator(Operator.Type.Within)); - boolean result = operatorWithin.execute(poly1, poly2, inputSR, - null); - assertTrue(result); - } - - { - OperatorDisjoint operatorDisjoint = (OperatorDisjoint) (projEnv - .getOperator(Operator.Type.Disjoint)); - OperatorIntersects operatorIntersects = (OperatorIntersects) (projEnv - .getOperator(Operator.Type.Intersects)); - boolean result = operatorDisjoint.execute(poly1, poly2, - inputSR, null); - assertTrue(!result); - { - result = operatorIntersects.execute(poly1, poly2, inputSR, - null); - assertTrue(result); - } - } - - { - OperatorDisjoint operatorDisjoint = (OperatorDisjoint) (projEnv - .getOperator(Operator.Type.Disjoint)); - OperatorIntersects operatorIntersects = (OperatorIntersects) (projEnv - .getOperator(Operator.Type.Intersects)); - Envelope2D env2D = new Envelope2D(); - poly2.queryEnvelope2D(env2D); - Envelope envelope = new Envelope(env2D); - boolean result = operatorDisjoint.execute(envelope, poly2, - inputSR, null); - assertTrue(!result); - { - result = operatorIntersects.execute(envelope, poly2, - inputSR, null); - assertTrue(result); - } - } - - { - OperatorDisjoint operatorDisjoint = (OperatorDisjoint) (projEnv - .getOperator(Operator.Type.Disjoint)); - OperatorIntersects operatorIntersects = (OperatorIntersects) (projEnv - .getOperator(Operator.Type.Intersects)); - Polygon poly = new Polygon(); - - Envelope2D env2D = new Envelope2D(); - env2D.setCoords(855277, 3892059, 855277 + 100, 3892059 + 100); - poly.addEnvelope(env2D, false); - env2D.setCoords(855277 + 10, 3892059 + 10, 855277 + 90, - 3892059 + 90); - poly.addEnvelope(env2D, true); - - env2D.setCoords(855277 + 20, 3892059 + 20, 855277 + 200, - 3892059 + 80); - Envelope envelope = new Envelope(env2D); - boolean result = operatorDisjoint.execute(envelope, poly, - inputSR, null); - assertTrue(!result); - { - result = operatorIntersects.execute(envelope, poly, - inputSR, null); - assertTrue(result); - } - } - - { - OperatorTouches operatorTouches = (OperatorTouches) (projEnv - .getOperator(Operator.Type.Touches)); - boolean result = operatorTouches.execute(poly1, poly2, inputSR, - null); - assertTrue(!result); - } - - } - } - - @Test - public void testOperatorDisjoint() { - { - OperatorFactoryLocal projEnv = OperatorFactoryLocal.getInstance(); - SpatialReference inputSR = SpatialReference.create(3857); - - Polygon poly1 = new Polygon(); - Envelope2D env1 = new Envelope2D(); - env1.setCoords(855277, 3892059, 855277 + 100, 3892059 + 100); - poly1.addEnvelope(env1, false); - - Polygon poly2 = new Polygon(); - Envelope2D env2 = new Envelope2D(); - env2.setCoords(855277, 3892059, 855277 + 300, 3892059 + 200); - poly2.addEnvelope(env2, false); - - Polygon poly3 = new Polygon(); - Envelope2D env3 = new Envelope2D(); - env3.setCoords(855277 + 100, 3892059 + 100, 855277 + 100 + 100, - 3892059 + 100 + 100); - poly3.addEnvelope(env3, false); - - Polygon poly4 = new Polygon(); - Envelope2D env4 = new Envelope2D(); - env4.setCoords(855277 + 200, 3892059 + 200, 855277 + 200 + 100, - 3892059 + 200 + 100); - poly4.addEnvelope(env4, false); - - Point point1 = new Point(855277, 3892059); - Point point2 = new Point(855277 + 2, 3892059 + 3); - Point point3 = new Point(855277 - 2, 3892059 - 3); - - { - OperatorDisjoint operatorDisjoint = (OperatorDisjoint) (projEnv - .getOperator(Operator.Type.Disjoint)); - boolean result = operatorDisjoint.execute(poly1, poly2, - inputSR, null); - assertTrue(!result); - result = operatorDisjoint.execute(poly1, poly3, inputSR, null); - assertTrue(!result); - result = operatorDisjoint.execute(poly1, poly4, inputSR, null); - assertTrue(result); - - result = operatorDisjoint.execute(poly1, point1, inputSR, null); - assertTrue(!result); - result = operatorDisjoint.execute(point1, poly1, inputSR, null); - assertTrue(!result); - result = operatorDisjoint.execute(poly1, point2, inputSR, null); - assertTrue(!result); - result = operatorDisjoint.execute(point2, poly1, inputSR, null); - assertTrue(!result); - result = operatorDisjoint.execute(poly1, point3, inputSR, null); - assertTrue(result); - result = operatorDisjoint.execute(point3, poly1, inputSR, null); - assertTrue(result); - } - } - } - - @Test - public void testTouchPointLineCR183227() {// Tests CR 183227 - OperatorTouches operatorTouches = (OperatorTouches) (OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.Touches)); - - Geometry baseGeom = new Point(-130, 10); - Polyline pl = new Polyline(); - // pl.startPath(std::make_shared(-130, 10)); - pl.startPath(-130, 10); - pl.lineTo(-131, 15); - pl.lineTo(-140, 20); - - SpatialReference sr = SpatialReference.create(4326); - - boolean isTouched; - boolean isTouched2; - isTouched = operatorTouches.execute(baseGeom, pl, sr, null); - isTouched2 = operatorTouches.execute(pl, baseGeom, sr, null); - assertTrue(isTouched && isTouched2); - - { - baseGeom = new Point(-131, 15); - isTouched = operatorTouches.execute(baseGeom, pl, sr, null); - isTouched2 = operatorTouches.execute(pl, baseGeom, sr, null); - assertTrue(!isTouched && !isTouched2); - } - } - - @Test - public void testTouchPointLineClosed() {// Tests CR 183227 - OperatorTouches operatorTouches = (OperatorTouches) (OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.Touches)); - - Geometry baseGeom = new Point(-130, 10); - Polyline pl = new Polyline(); - pl.startPath(-130, 10); - pl.lineTo(-131, 15); - pl.lineTo(-140, 20); - pl.lineTo(-130, 10); - - SpatialReference sr = SpatialReference.create(4326); - - boolean isTouched; - boolean isTouched2; - isTouched = operatorTouches.execute(baseGeom, pl, sr, null); - isTouched2 = operatorTouches.execute(pl, baseGeom, sr, null); - assertTrue(!isTouched && !isTouched2);// this may change in future - - { - baseGeom = new Point(-131, 15); - isTouched = operatorTouches.execute(baseGeom, pl, sr, null); - isTouched2 = operatorTouches.execute(pl, baseGeom, sr, null); - assertTrue(!isTouched && !isTouched2); - } - } - - @Test - public void testTouchPolygonPolygon() { - OperatorTouches operatorTouches = (OperatorTouches) (OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.Touches)); - - Polygon pg = new Polygon(); - pg.startPath(-130, 10); - pg.lineTo(-131, 15); - pg.lineTo(-140, 20); - - Polygon pg2 = new Polygon(); - pg2.startPath(-130, 10); - pg2.lineTo(-131, 15); - pg2.lineTo(-120, 20); - SpatialReference sr = SpatialReference.create(4326); - - boolean isTouched; - boolean isTouched2; - isTouched = operatorTouches.execute(pg, pg2, sr, null); - isTouched2 = operatorTouches.execute(pg2, pg, sr, null); - assertTrue(isTouched && isTouched2); - } - - @Test - public void testContainsFailureCR186456() { - { - OperatorContains op = (OperatorContains) (OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.Contains)); - String str = "{\"rings\":[[[406944.399999999,287461.450000001],[406947.750000011,287462.299999997],[406946.44999999,287467.450000001],[406943.050000005,287466.550000005],[406927.799999992,287456.849999994],[406926.949999996,287456.599999995],[406924.800000005,287455.999999998],[406924.300000007,287455.849999999],[406924.200000008,287456.099999997],[406923.450000011,287458.449999987],[406922.999999987,287459.800000008],[406922.29999999,287462.099999998],[406921.949999991,287463.449999992],[406921.449999993,287465.050000011],[406920.749999996,287466.700000004],[406919.800000001,287468.599999996],[406919.050000004,287469.99999999],[406917.800000009,287471.800000008],[406916.04999999,287473.550000001],[406915.449999993,287473.999999999],[406913.700000001,287475.449999993],[406913.300000002,287475.899999991],[406912.050000008,287477.250000011],[406913.450000002,287478.150000007],[406915.199999994,287478.650000005],[406915.999999991,287478.800000005],[406918.300000007,287479.200000003],[406920.649999997,287479.450000002],[406923.100000013,287479.550000001],[406925.750000001,287479.450000002],[406928.39999999,287479.150000003],[406929.80000001,287478.950000004],[406932.449999998,287478.350000006],[406935.099999987,287477.60000001],[406938.699999998,287476.349999989],[406939.649999994,287473.949999999],[406939.799999993,287473.949999999],[406941.249999987,287473.75],[406942.700000007,287473.250000002],[406943.100000005,287473.100000003],[406943.950000001,287472.750000004],[406944.799999998,287472.300000006],[406944.999999997,287472.200000007],[406946.099999992,287471.200000011],[406946.299999991,287470.950000012],[406948.00000001,287468.599999996],[406948.10000001,287468.399999997],[406950.100000001,287465.050000011],[406951.949999993,287461.450000001],[406952.049999993,287461.300000001],[406952.69999999,287459.900000007],[406953.249999987,287458.549999987],[406953.349999987,287458.299999988],[406953.650000012,287457.299999992],[406953.900000011,287456.349999996],[406954.00000001,287455.300000001],[406954.00000001,287454.750000003],[406953.850000011,287453.750000008],[406953.550000012,287452.900000011],[406953.299999987,287452.299999988],[406954.500000008,287450.299999996],[406954.00000001,287449.000000002],[406953.399999987,287447.950000006],[406953.199999988,287447.550000008],[406952.69999999,287446.850000011],[406952.149999992,287446.099999988],[406951.499999995,287445.499999991],[406951.149999996,287445.249999992],[406950.449999999,287444.849999994],[406949.600000003,287444.599999995],[406949.350000004,287444.549999995],[406948.250000009,287444.499999995],[406947.149999987,287444.699999994],[406946.849999989,287444.749999994],[406945.899999993,287444.949999993],[406944.999999997,287445.349999991],[406944.499999999,287445.64999999],[406943.650000003,287446.349999987],[406942.900000006,287447.10000001],[406942.500000008,287447.800000007],[406942.00000001,287448.700000003],[406941.600000011,287449.599999999],[406941.350000013,287450.849999994],[406941.350000013,287451.84999999],[406941.450000012,287452.850000012],[406941.750000011,287453.850000007],[406941.800000011,287454.000000007],[406942.150000009,287454.850000003],[406942.650000007,287455.6],[406943.150000005,287456.299999997],[406944.499999999,287457.299999992],[406944.899999997,287457.599999991],[406945.299999995,287457.949999989],[406944.399999999,287461.450000001],[406941.750000011,287461.999999998],[406944.399999999,287461.450000001]],[[406944.399999999,287461.450000001],[406947.750000011,287462.299999997],[406946.44999999,287467.450000001],[406943.050000005,287466.550000005],[406927.799999992,287456.849999994],[406944.399999999,287461.450000001]]]}"; - MapGeometry mg = TestCommonMethods.fromJson(str); - boolean res = op.execute((mg.getGeometry()), (mg.getGeometry()), - null, null); - assertTrue(res); - } - } - - @Test - public void testWithin() { - { - OperatorWithin op = (OperatorWithin) (OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.Within)); - String str1 = "{\"rings\":[[[0,0],[0,200],[200,200],[200,0],[0,0],[0,0],[0,0]]]}"; - MapGeometry mg1 = TestCommonMethods.fromJson(str1); - String str2 = "{\"x\":100,\"y\":100}"; - MapGeometry mg2 = TestCommonMethods.fromJson(str2); - - boolean res = op.execute((mg2.getGeometry()), (mg1.getGeometry()), - null, null); - assertTrue(res); - res = op.execute((mg1.getGeometry()), (mg2.getGeometry()), null, - null); - assertTrue(!res); - - } - - {// polygon - OperatorWithin op = (OperatorWithin) (OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.Within)); - String str1 = "{\"rings\":[[[0,0],[0,200],[200,200],[200,0],[100,0]]]}"; - MapGeometry mg1 = TestCommonMethods.fromJson(str1); - String str2 = "{\"rings\":[[[10,10],[10,100],[100,100],[100,10]]]}"; - MapGeometry mg2 = TestCommonMethods.fromJson(str2); - - boolean res = op.execute((mg2.getGeometry()), (mg1.getGeometry()), - null, null); - assertTrue(res); - res = op.execute((mg1.getGeometry()), (mg2.getGeometry()), null, - null); - assertTrue(!res); - } - - {// Multi_point - OperatorWithin op = (OperatorWithin) (OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.Within)); - String str1 = "{\"points\":[[0,0],[0,200],[200,200]]}"; - MapGeometry mg1 = TestCommonMethods.fromJson(str1); - String str2 = "{\"points\":[[0,0],[0,200]]}"; - MapGeometry mg2 = TestCommonMethods.fromJson(str2); - boolean res = op.execute((mg2.getGeometry()), (mg1.getGeometry()), - null, null); - assertTrue(res); - res = op.execute((mg1.getGeometry()), (mg2.getGeometry()), null, - null); - assertTrue(!res); - } - - {// Multi_point - OperatorWithin op = (OperatorWithin) (OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.Within)); - String str1 = "{\"points\":[[0,0],[0,200]]}"; - MapGeometry mg1 = TestCommonMethods.fromJson(str1); - String str2 = "{\"points\":[[0,0],[0,200]]}"; - MapGeometry mg2 = TestCommonMethods.fromJson(str2); - boolean res = op.execute((mg2.getGeometry()), (mg1.getGeometry()), - null, null); - assertTrue(res); - res = op.execute((mg1.getGeometry()), (mg2.getGeometry()), null, - null); - assertTrue(res); - } - - {// Multi_point - OperatorWithin op = (OperatorWithin) (OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.Within)); - String str1 = "{\"points\":[[0,0],[0,200],[200,200]]}"; - MapGeometry mg1 = TestCommonMethods.fromJson(str1); - String str2 = "{\"points\":[[0,0],[0,200], [1, 1]]}"; - MapGeometry mg2 = TestCommonMethods.fromJson(str2); - boolean res = op.execute((mg2.getGeometry()), (mg1.getGeometry()), - null, null); - assertTrue(!res); - res = op.execute((mg1.getGeometry()), (mg2.getGeometry()), null, - null); - assertTrue(!res); - } - } - - @Test - public void testContains() { - { - OperatorContains op = (OperatorContains) (OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.Contains)); - String str1 = "{\"rings\":[[[0,0],[0,200],[200,200],[200,0],[0,0],[0,0],[0,0]]]}"; - MapGeometry mg1 = TestCommonMethods.fromJson(str1); - String str2 = "{\"x\":100,\"y\":100}"; - MapGeometry mg2 = TestCommonMethods.fromJson(str2); - - boolean res = op.execute((mg2.getGeometry()), (mg1.getGeometry()), - null, null); - assertTrue(!res); - res = op.execute((mg1.getGeometry()), (mg2.getGeometry()), null, - null); - assertTrue(res); - } - - {// polygon - OperatorContains op = (OperatorContains) (OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.Contains)); - String str1 = "{\"rings\":[[[0,0],[0,200],[200,200],[200,0],[0,0]]]}"; - MapGeometry mg1 = TestCommonMethods.fromJson(str1); - String str2 = "{\"rings\":[[[10,10],[10,100],[100,100],[10,10]]]}"; - MapGeometry mg2 = TestCommonMethods.fromJson(str2); - - boolean res = op.execute((mg2.getGeometry()), (mg1.getGeometry()), - null, null); - assertTrue(!res); - res = op.execute((mg1.getGeometry()), (mg2.getGeometry()), null, - null); - assertTrue(res); - } - - {// Multi_point - OperatorContains op = (OperatorContains) (OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.Contains)); - String str1 = "{\"points\":[[0,0],[0,200],[200,200]]}"; - MapGeometry mg1 = TestCommonMethods.fromJson(str1); - String str2 = "{\"points\":[[0,0],[0,200]]}"; - MapGeometry mg2 = TestCommonMethods.fromJson(str2); - boolean res = op.execute((mg2.getGeometry()), (mg1.getGeometry()), - null, null); - assertTrue(!res); - res = op.execute((mg1.getGeometry()), (mg2.getGeometry()), null, - null); - assertTrue(res); - } - - {// Multi_point - OperatorContains op = (OperatorContains) (OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.Contains)); - String str1 = "{\"points\":[[0,0],[0,200]]}"; - MapGeometry mg1 = TestCommonMethods.fromJson(str1); - String str2 = "{\"points\":[[0,0],[0,200]]}"; - MapGeometry mg2 = TestCommonMethods.fromJson(str2); - boolean res = op.execute((mg2.getGeometry()), (mg1.getGeometry()), - null, null); - assertTrue(res); - res = op.execute((mg1.getGeometry()), (mg2.getGeometry()), null, - null); - assertTrue(res); - } - - {// Multi_point - OperatorContains op = (OperatorContains) (OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.Contains)); - String str1 = "{\"points\":[[0,0],[0,200],[200,200]]}"; - MapGeometry mg1 = TestCommonMethods.fromJson(str1); - String str2 = "{\"points\":[[0,0],[0,200], [1, 1]]}"; - MapGeometry mg2 = TestCommonMethods.fromJson(str2); - boolean res = op.execute((mg2.getGeometry()), (mg1.getGeometry()), - null, null); - assertTrue(!res); - res = op.execute((mg1.getGeometry()), (mg2.getGeometry()), null, - null); - assertTrue(!res); - } - } - - @Test - public void testOverlaps() { - {// empty polygon - OperatorOverlaps op = (OperatorOverlaps) (OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.Overlaps)); - Polygon poly1 = new Polygon(); - Polygon poly2 = new Polygon(); - - boolean res = op.execute(poly1, poly2, null, null); - assertTrue(!res); - res = op.execute(poly1, poly2, null, null); - assertTrue(!res); - } - {// polygon - OperatorOverlaps op = (OperatorOverlaps) (OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.Overlaps)); - String str1 = "{\"rings\":[[[0,0],[0,200],[200,200],[200,0],[0,0],[0,0],[0,0]]]}"; - MapGeometry mg1 = TestCommonMethods.fromJson(str1); - String str2 = "{\"x\":100,\"y\":100}"; - MapGeometry mg2 = TestCommonMethods.fromJson(str2); - - boolean res = op.execute((mg2.getGeometry()), (mg1.getGeometry()), - null, null); - assertTrue(!res); - res = op.execute((mg1.getGeometry()), (mg2.getGeometry()), null, - null); - assertTrue(!res); - } - {// polygon - OperatorOverlaps op = (OperatorOverlaps) (OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.Overlaps)); - String str1 = "{\"rings\":[[[0,0],[0,200],[200,200],[200,0],[0,0]]]}"; - MapGeometry mg1 = TestCommonMethods.fromJson(str1); - MapGeometry mg2 = TestCommonMethods.fromJson(str1); - Transformation2D trans = new Transformation2D(); - trans.setShift(300, 0); - mg2.getGeometry().applyTransformation(trans); - boolean res = op.execute((mg2.getGeometry()), (mg1.getGeometry()), - null, null); - assertTrue(!res); - res = op.execute((mg1.getGeometry()), (mg2.getGeometry()), null, - null); - assertTrue(!res); - } - {// polygon - OperatorOverlaps op = (OperatorOverlaps) (OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.Overlaps)); - String str1 = "{\"rings\":[[[0,0],[0,200],[200,200],[200,0],[0,0]]]}"; - MapGeometry mg1 = TestCommonMethods.fromJson(str1); - MapGeometry mg2 = TestCommonMethods.fromJson(str1); - Transformation2D trans = new Transformation2D(); - trans.setShift(30, 0); - mg2.getGeometry().applyTransformation(trans); - boolean res = op.execute((mg2.getGeometry()), (mg1.getGeometry()), - null, null); - assertTrue(res); - res = op.execute((mg1.getGeometry()), (mg2.getGeometry()), null, - null); - assertTrue(res); - } - {// polygon - OperatorOverlaps op = (OperatorOverlaps) (OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.Overlaps)); - String str1 = "{\"rings\":[[[0,0],[0,200],[200,200],[200,0],[0,0]]]}"; - MapGeometry mg1 = TestCommonMethods.fromJson(str1); - MapGeometry mg2 = TestCommonMethods.fromJson(str1); - Transformation2D trans = new Transformation2D(); - trans.setShift(0, 0); - mg2.getGeometry().applyTransformation(trans); - boolean res = op.execute((mg2.getGeometry()), (mg1.getGeometry()), - null, null); - assertTrue(!res); - res = op.execute((mg1.getGeometry()), (mg2.getGeometry()), null, - null); - assertTrue(!res); - } - - {// polyline - OperatorOverlaps op = (OperatorOverlaps) (OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.Overlaps)); - String str1 = "{\"paths\":[[[0,0],[100,0],[200,0]]]}"; - MapGeometry mg1 = TestCommonMethods.fromJson(str1); - MapGeometry mg2 = TestCommonMethods.fromJson(str1); - Transformation2D trans = new Transformation2D(); - trans.setShift(0, 0); - mg2.getGeometry().applyTransformation(trans); - boolean res = op.execute((mg2.getGeometry()), (mg1.getGeometry()), - null, null); - assertTrue(!res); - res = op.execute((mg1.getGeometry()), (mg2.getGeometry()), null, - null); - assertTrue(!res); - } - - {// polyline - OperatorOverlaps op = (OperatorOverlaps) (OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.Overlaps)); - String str1 = "{\"paths\":[[[0,0],[100,0],[200,0]]]}"; - MapGeometry mg1 = TestCommonMethods.fromJson(str1); - MapGeometry mg2 = TestCommonMethods.fromJson(str1); - Transformation2D trans = new Transformation2D(); - trans.setShift(10, 0); - mg2.getGeometry().applyTransformation(trans); - boolean res = op.execute((mg2.getGeometry()), (mg1.getGeometry()), - null, null); - assertTrue(res); - res = op.execute((mg1.getGeometry()), (mg2.getGeometry()), null, - null); - assertTrue(res); - } - - {// polyline - OperatorOverlaps op = (OperatorOverlaps) (OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.Overlaps)); - String str1 = "{\"paths\":[[[0,0],[100,0],[200,0]]]}"; - MapGeometry mg1 = TestCommonMethods.fromJson(str1); - MapGeometry mg2 = TestCommonMethods.fromJson(str1); - Transformation2D trans = new Transformation2D(); - trans.setShift(200, 0); - mg2.getGeometry().applyTransformation(trans); - boolean res = op.execute((mg2.getGeometry()), (mg1.getGeometry()), - null, null); - assertTrue(!res); - res = op.execute((mg1.getGeometry()), (mg2.getGeometry()), null, - null); - assertTrue(!res); - } - - {// Multi_point - OperatorOverlaps op = (OperatorOverlaps) (OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.Overlaps)); - String str1 = "{\"points\":[[0,0],[0,200],[200,200],[200,0]]}"; - MapGeometry mg1 = TestCommonMethods.fromJson(str1); - MapGeometry mg2 = TestCommonMethods.fromJson(str1); - Transformation2D trans = new Transformation2D(); - trans.setShift(0, 0); - mg2.getGeometry().applyTransformation(trans); - boolean res = op.execute((mg2.getGeometry()), (mg1.getGeometry()), - null, null); - assertTrue(!res); - res = op.execute((mg1.getGeometry()), (mg2.getGeometry()), null, - null); - assertTrue(!res); - } - {// Multi_point - OperatorOverlaps op = (OperatorOverlaps) (OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.Overlaps)); - String str1 = "{\"points\":[[0,0],[0,200],[200,200]]}"; - MapGeometry mg1 = TestCommonMethods.fromJson(str1); - String str2 = "{\"points\":[[0,0],[0,200]]}"; - MapGeometry mg2 = TestCommonMethods.fromJson(str2); - boolean res = op.execute((mg2.getGeometry()), (mg1.getGeometry()), - null, null); - assertTrue(!res); - res = op.execute((mg1.getGeometry()), (mg2.getGeometry()), null, - null); - assertTrue(!res); - } - {// Multi_point - OperatorOverlaps op = (OperatorOverlaps) (OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.Overlaps)); - String str1 = "{\"points\":[[0,0],[0,200],[200,200]]}"; - MapGeometry mg1 = TestCommonMethods.fromJson(str1); - String str2 = "{\"points\":[[0,0],[0,200], [0,2]]}"; - MapGeometry mg2 = TestCommonMethods.fromJson(str2); - boolean res = op.execute((mg2.getGeometry()), (mg1.getGeometry()), - null, null); - assertTrue(res); - res = op.execute((mg1.getGeometry()), (mg2.getGeometry()), null, - null); - assertTrue(res); - } - } - - @Test - public void testPolygonPolygonEquals() { - OperatorEquals equals = (OperatorEquals) (OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.Equals)); - SpatialReference sr = SpatialReference.create(102100); - @SuppressWarnings("unused") - double tolerance = sr - .getTolerance(VertexDescription.Semantics.POSITION); - - // Polygon1 and Polygon2 are topologically equal, but have differing - // number of vertices - String str1 = "{\"rings\":[[[0,0],[0,5],[0,7],[0,10],[0,10],[10,10],[10,0],[0,0]],[[1,1],[9,1],[9,9],[1,9],[1,1],[1,1]]]}"; - String str2 = "{\"rings\":[[[0,10],[10,10],[10,0],[0,0],[0,10]],[[9,1],[9,6],[9,9],[1,9],[1,1],[1,1],[9,1]]]}"; - - Polygon polygon1 = (Polygon) TestCommonMethods.fromJson(str1) - .getGeometry(); - Polygon polygon2 = (Polygon) TestCommonMethods.fromJson(str2) - .getGeometry(); - // wiggleGeometry(polygon1, tolerance, 1982); - // wiggleGeometry(polygon2, tolerance, 511); - - equals.accelerateGeometry(polygon1, sr, - Geometry.GeometryAccelerationDegree.enumHot); - equals.accelerateGeometry(polygon2, sr, - Geometry.GeometryAccelerationDegree.enumHot); - - boolean res = equals.execute(polygon1, polygon2, sr, null); - assertTrue(res); - equals.execute(polygon2, polygon1, sr, null); - assertTrue(res); - - // The outer rings of Polygon1 and Polygon2 are equal, but Polygon1 has - // a hole. - str1 = "{\"rings\":[[[0,0],[0,5],[0,10],[10,10],[10,0],[0,0]],[[1,1],[9,1],[9,9],[1,9],[1,1],[1,1]]]}"; - str2 = "{\"rings\":[[[0,10],[10,10],[5,10],[10,10],[10,0],[0,0],[0,10]]]}"; - polygon1 = (Polygon) TestCommonMethods.fromJson(str1).getGeometry(); - polygon2 = (Polygon) TestCommonMethods.fromJson(str2).getGeometry(); - - res = equals.execute(polygon1, polygon2, sr, null); - assertTrue(!res); - res = equals.execute(polygon2, polygon1, sr, null); - assertTrue(!res); - - // The rings are equal but rotated - str1 = "{\"rings\":[[[0,0],[0,10],[10,10],[10,0],[0,0]]]}"; - str2 = "{\"rings\":[[[0,10],[10,10],[10,0],[0,0],[0,10]]]}"; - - polygon1 = (Polygon) TestCommonMethods.fromJson(str1).getGeometry(); - polygon2 = (Polygon) TestCommonMethods.fromJson(str2).getGeometry(); - - res = equals.execute(polygon1, polygon2, sr, null); - assertTrue(res); - res = equals.execute(polygon2, polygon1, sr, null); - assertTrue(res); - - // The rings are equal but opposite orientation - str1 = "{\"rings\":[[[0,0],[0,10],[10,10],[10,0],[0,0]]]}"; - str2 = "{\"rings\":[[[0,0],[10,0],[10,10],[0,10],[0,0]]]}"; - - polygon1 = (Polygon) TestCommonMethods.fromJson(str1).getGeometry(); - polygon2 = (Polygon) TestCommonMethods.fromJson(str2).getGeometry(); - - res = equals.execute(polygon1, polygon2, sr, null); - assertTrue(!res); - res = equals.execute(polygon2, polygon1, sr, null); - assertTrue(!res); - - // The rings are equal but first polygon has two rings stacked - str1 = "{\"rings\":[[[0,0],[0,10],[10,10],[10,0],[0,0]],[[0,10],[10,10],[10,0],[0,0],[0,10]]]}"; - str2 = "{\"rings\":[[[0,10],[10,10],[10,0],[0,0],[0,10]]]}"; - polygon1 = (Polygon) TestCommonMethods.fromJson(str1).getGeometry(); - polygon2 = (Polygon) TestCommonMethods.fromJson(str2).getGeometry(); - - res = equals.execute(polygon1, polygon2, sr, null); - assertTrue(!res); - res = equals.execute(polygon2, polygon1, sr, null); - assertTrue(!res); - } - - @Test - public void testMultiPointMultiPointEquals() { - OperatorEquals equals = (OperatorEquals) OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.Equals); - SpatialReference sr = SpatialReference.create(102100); - @SuppressWarnings("unused") - double tolerance = sr - .getTolerance(VertexDescription.Semantics.POSITION); - - MultiPoint multipoint1 = new MultiPoint(); - MultiPoint multipoint2 = new MultiPoint(); - - multipoint1.add(0, 0); - multipoint1.add(1, 1); - multipoint1.add(2, 2); - multipoint1.add(3, 3); - multipoint1.add(4, 4); - multipoint1.add(1, 1); - multipoint1.add(0, 0); - - multipoint2.add(4, 4); - multipoint2.add(3, 3); - multipoint2.add(2, 2); - multipoint2.add(1, 1); - multipoint2.add(0, 0); - multipoint2.add(2, 2); - - wiggleGeometry(multipoint1, 0.001, 123); - wiggleGeometry(multipoint2, 0.001, 5937); - boolean res = equals.execute(multipoint1, multipoint2, sr, null); - assertTrue(res); - res = equals.execute(multipoint2, multipoint1, sr, null); - assertTrue(res); - - multipoint1.add(1, 2); - res = equals.execute(multipoint1, multipoint2, sr, null); - assertTrue(!res); - res = equals.execute(multipoint2, multipoint1, sr, null); - assertTrue(!res); - } - - @Test - public void testMultiPointPointEquals() { - OperatorEquals equals = (OperatorEquals) (OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.Equals)); - OperatorWithin within = (OperatorWithin) (OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.Within)); - SpatialReference sr = SpatialReference.create(102100); - @SuppressWarnings("unused") - double tolerance = sr - .getTolerance(VertexDescription.Semantics.POSITION); - - MultiPoint multipoint1 = new MultiPoint(); - Point point2 = new Point(); - - multipoint1.add(2, 2); - multipoint1.add(2, 2); - - point2.setXY(2, 2); - - wiggleGeometry(multipoint1, 0.001, 123); - boolean res = equals.execute(multipoint1, point2, sr, null); - assertTrue(res); - res = equals.execute(point2, multipoint1, sr, null); - assertTrue(res); - - res = within.execute(multipoint1, point2, sr, null); - assertTrue(res); - res = within.execute(point2, multipoint1, sr, null); - assertTrue(res); - - multipoint1.add(4, 4); - res = equals.execute(multipoint1, point2, sr, null); - assertTrue(!res); - res = equals.execute(point2, multipoint1, sr, null); - assertTrue(!res); - - res = within.execute(multipoint1, point2, sr, null); - assertTrue(!res); - res = within.execute(point2, multipoint1, sr, null); - assertTrue(res); - } - - @Test - public void testPointPointEquals() { - OperatorEquals equals = (OperatorEquals) (OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.Equals)); - OperatorWithin within = (OperatorWithin) (OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.Within)); - OperatorContains contains = (OperatorContains) (OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.Contains)); - OperatorDisjoint disjoint = (OperatorDisjoint) (OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.Disjoint)); - SpatialReference sr = SpatialReference.create(102100); - @SuppressWarnings("unused") - double tolerance = sr - .getTolerance(VertexDescription.Semantics.POSITION); - - Point point1 = new Point(); - Point point2 = new Point(); - - point1.setXY(2, 2); - point2.setXY(2, 2); - - boolean res = equals.execute(point1, point2, sr, null); - assertTrue(res); - res = equals.execute(point2, point1, sr, null); - assertTrue(res); - - res = within.execute(point1, point2, sr, null); - assertTrue(res); - res = within.execute(point2, point1, sr, null); - assertTrue(res); - - res = contains.execute(point1, point2, sr, null); - assertTrue(res); - res = contains.execute(point2, point1, sr, null); - assertTrue(res); - - res = disjoint.execute(point1, point2, sr, null); - assertTrue(!res); - res = disjoint.execute(point2, point1, sr, null); - assertTrue(!res); - - point2.setXY(2, 3); - res = equals.execute(point1, point2, sr, null); - assertTrue(!res); - res = equals.execute(point2, point1, sr, null); - assertTrue(!res); - - res = within.execute(point1, point2, sr, null); - assertTrue(!res); - res = within.execute(point2, point1, sr, null); - assertTrue(!res); - - res = contains.execute(point1, point2, sr, null); - assertTrue(!res); - res = contains.execute(point2, point1, sr, null); - assertTrue(!res); - - res = disjoint.execute(point1, point2, sr, null); - assertTrue(res); - res = disjoint.execute(point2, point1, sr, null); - assertTrue(res); - } - - @Test - public void testPolygonPolygonDisjoint() { - OperatorDisjoint disjoint = (OperatorDisjoint) (OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.Disjoint)); - SpatialReference sr = SpatialReference.create(102100); - double tolerance = sr - .getTolerance(VertexDescription.Semantics.POSITION); - - // Polygon1 and Polygon2 are topologically equal, but have differing - // number of vertices - String str1 = "{\"rings\":[[[0,0],[0,5],[0,10],[10,10],[10,0],[0,0]],[[1,1],[9,1],[9,9],[1,9],[1,1],[1,1]]]}"; - String str2 = "{\"rings\":[[[0,10],[10,10],[10,0],[0,0],[0,10]],[[9,1],[9,6],[9,9],[1,9],[1,1],[1,1],[9,1]]]}"; - - Polygon polygon1 = (Polygon) (TestCommonMethods.fromJson(str1) - .getGeometry()); - Polygon polygon2 = (Polygon) (TestCommonMethods.fromJson(str2) - .getGeometry()); - - boolean res = disjoint.execute(polygon1, polygon2, sr, null); - assertTrue(!res); - res = disjoint.execute(polygon2, polygon1, sr, null); - assertTrue(!res); - - // Polygon1 and Polygon2 touch at a point - str1 = "{\"rings\":[[[0,0],[0,5],[0,10],[10,10],[10,0],[0,0]],[[1,1],[9,1],[9,9],[1,9],[1,1],[1,1]]]}"; - str2 = "{\"rings\":[[[10,10],[10,15],[15,15],[15,10],[10,10]]]}"; - - polygon1 = (Polygon) (TestCommonMethods.fromJson(str1).getGeometry()); - polygon2 = (Polygon) (TestCommonMethods.fromJson(str2).getGeometry()); - wiggleGeometry(polygon1, tolerance, 1982); - wiggleGeometry(polygon2, tolerance, 511); - - res = disjoint.execute(polygon1, polygon2, sr, null); - assertTrue(!res); - res = disjoint.execute(polygon2, polygon1, sr, null); - assertTrue(!res); - - // Polygon1 and Polygon2 touch along the boundary - str1 = "{\"rings\":[[[0,0],[0,5],[0,10],[10,10],[10,0],[0,0]],[[1,1],[9,1],[9,9],[1,9],[1,1],[1,1]]]}"; - str2 = "{\"rings\":[[[10,0],[10,10],[15,10],[15,0],[10,0]]]}"; - - polygon1 = (Polygon) (TestCommonMethods.fromJson(str1).getGeometry()); - polygon2 = (Polygon) (TestCommonMethods.fromJson(str2).getGeometry()); - wiggleGeometry(polygon1, tolerance, 1982); - wiggleGeometry(polygon2, tolerance, 511); - - res = disjoint.execute(polygon1, polygon2, sr, null); - assertTrue(!res); - res = disjoint.execute(polygon2, polygon1, sr, null); - assertTrue(!res); - - // Polygon2 is inside of the hole of polygon1 - str1 = "{\"rings\":[[[0,0],[0,5],[0,10],[10,10],[10,0],[0,0]],[[1,1],[9,1],[9,9],[1,9],[1,1],[1,1]]]}"; - str2 = "{\"rings\":[[[2,2],[2,8],[8,8],[8,2],[2,2]]]}"; - - polygon1 = (Polygon) (TestCommonMethods.fromJson(str1).getGeometry()); - polygon2 = (Polygon) (TestCommonMethods.fromJson(str2).getGeometry()); - - res = disjoint.execute(polygon1, polygon2, sr, null); - assertTrue(res); - res = disjoint.execute(polygon2, polygon1, sr, null); - assertTrue(res); - - // Polygon2 is inside of the hole of polygon1 - str1 = "{\"rings\":[[[0,0],[0,5],[5,5],[5,0]],[[10,0],[10,10],[20,10],[20,0]]]}"; - str2 = "{\"rings\":[[[0,-10],[0,-5],[5,-5],[5,-10]],[[11,1],[11,9],[19,9],[19,1]]]}"; - - polygon1 = (Polygon) (TestCommonMethods.fromJson(str1).getGeometry()); - polygon2 = (Polygon) (TestCommonMethods.fromJson(str2).getGeometry()); - - res = disjoint.execute(polygon1, polygon2, sr, null); - assertTrue(!res); - res = disjoint.execute(polygon2, polygon1, sr, null); - assertTrue(!res); - - polygon1 = (Polygon) OperatorDensifyByLength.local().execute(polygon1, 0.5, null); - disjoint.accelerateGeometry(polygon1, sr, GeometryAccelerationDegree.enumHot); - res = disjoint.execute(polygon1, polygon2, sr, null); - assertTrue(!res); - res = disjoint.execute(polygon2, polygon1, sr, null); - assertTrue(!res); - - polygon1.reverseAllPaths(); - polygon2.reverseAllPaths(); - res = disjoint.execute(polygon1, polygon2, sr, null); - assertTrue(!res); - res = disjoint.execute(polygon2, polygon1, sr, null); - assertTrue(!res); - - // Polygon1 contains polygon2, but polygon2 is counterclockwise. - str1 = "{\"rings\":[[[0,0],[10,0],[10,10],[0,10],[0,0]],[[11,0],[11,10],[21,10],[21,0],[11,0]]]}"; - str2 = "{\"rings\":[[[2,2],[8,2],[8,8],[2,8],[2,2]]]}"; - polygon1 = (Polygon) (TestCommonMethods.fromJson(str1).getGeometry()); - polygon2 = (Polygon) (TestCommonMethods.fromJson(str2).getGeometry()); - - - res = disjoint.execute(polygon1, polygon2, sr, null); - assertTrue(!res); - res = disjoint.execute(polygon2, polygon1, sr, null); - assertTrue(!res); - - polygon1 = (Polygon) OperatorDensifyByLength.local().execute(polygon1, 0.5, null); - disjoint.accelerateGeometry(polygon1, sr, GeometryAccelerationDegree.enumHot); - res = disjoint.execute(polygon1, polygon2, sr, null); - assertTrue(!res); - res = disjoint.execute(polygon2, polygon1, sr, null); - assertTrue(!res); - - str1 = "{\"rings\":[[[0,0],[0,10],[10,10],[10,0],[0,0]],[[0,20],[0,30],[10,30],[10,20],[0,20]],[[20,20],[20,30],[30,30],[30,20],[20,20]],[[20,0],[20,10],[30,10],[30,0],[20,0]]]}"; - str2 = "{\"rings\":[[[14,14],[14,16],[16,16],[16,14],[14,14]]]}"; - polygon1 = (Polygon) (TestCommonMethods.fromJson(str1).getGeometry()); - polygon2 = (Polygon) (TestCommonMethods.fromJson(str2).getGeometry()); - - - res = disjoint.execute(polygon1, polygon2, sr, null); - assertTrue(res); - res = disjoint.execute(polygon2, polygon1, sr, null); - assertTrue(res); - - polygon1 = (Polygon) OperatorDensifyByLength.local().execute(polygon1, 0.5, null); - disjoint.accelerateGeometry(polygon1, sr, GeometryAccelerationDegree.enumHot); - res = disjoint.execute(polygon1, polygon2, sr, null); - assertTrue(res); - res = disjoint.execute(polygon2, polygon1, sr, null); - assertTrue(res); - } - - @Test - public void testPolylinePolylineDisjoint() { - OperatorDisjoint disjoint = (OperatorDisjoint) (OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.Disjoint)); - SpatialReference sr = SpatialReference.create(102100); - double tolerance = sr - .getTolerance(VertexDescription.Semantics.POSITION); - - // Polyline1 and Polyline2 touch at a point - String str1 = "{\"paths\":[[[0,0],[0,5],[0,10],[10,10],[10,0],[0,0]],[[1,1],[9,1],[9,9],[1,9],[1,1],[1,1]]]}"; - String str2 = "{\"paths\":[[[10,10],[10,15],[15,15],[15,10],[10,10]]]}"; - - Polyline polyline1 = (Polyline) (TestCommonMethods.fromJson(str1) - .getGeometry()); - Polyline polyline2 = (Polyline) (TestCommonMethods.fromJson(str2) - .getGeometry()); - wiggleGeometry(polyline1, tolerance, 1982); - wiggleGeometry(polyline2, tolerance, 511); - - boolean res = disjoint.execute(polyline1, polyline2, sr, null); - assertTrue(!res); - res = disjoint.execute(polyline2, polyline1, sr, null); - assertTrue(!res); - - // Polyline1 and Polyline2 touch along the boundary - str1 = "{\"paths\":[[[0,0],[0,5],[0,10],[10,10],[10,0],[0,0]],[[1,1],[9,1],[9,9],[1,9],[1,1],[1,1]]]}"; - str2 = "{\"paths\":[[[10,0],[10,10],[15,10],[15,0],[10,0]]]}"; - - polyline1 = (Polyline) (TestCommonMethods.fromJson(str1).getGeometry()); - polyline2 = (Polyline) (TestCommonMethods.fromJson(str2).getGeometry()); - wiggleGeometry(polyline1, tolerance, 1982); - wiggleGeometry(polyline2, tolerance, 511); - - res = disjoint.execute(polyline1, polyline2, sr, null); - assertTrue(!res); - res = disjoint.execute(polyline2, polyline1, sr, null); - assertTrue(!res); - - // Polyline2 does not intersect with Polyline1 - str1 = "{\"paths\":[[[0,0],[0,5],[0,10],[10,10],[10,0],[0,0]],[[1,1],[9,1],[9,9],[1,9],[1,1],[1,1]]]}"; - str2 = "{\"paths\":[[[2,2],[2,8],[8,8],[8,2],[2,2]]]}"; - - polyline1 = (Polyline) (TestCommonMethods.fromJson(str1).getGeometry()); - polyline2 = (Polyline) (TestCommonMethods.fromJson(str2).getGeometry()); - - res = disjoint.execute(polyline1, polyline2, sr, null); - assertTrue(res); - res = disjoint.execute(polyline2, polyline1, sr, null); - assertTrue(res); - } - - @Test - public void testPolygonPolylineDisjoint() { - OperatorDisjoint disjoint = (OperatorDisjoint) (OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.Disjoint)); - SpatialReference sr = SpatialReference.create(102100); - @SuppressWarnings("unused") - double tolerance = sr - .getTolerance(VertexDescription.Semantics.POSITION); - - Polygon polygon1 = new Polygon(); - Polyline polyline2 = new Polyline(); - - polygon1.startPath(0, 0); - polygon1.lineTo(0, 10); - polygon1.lineTo(10, 10); - polygon1.lineTo(10, 0); - - polygon1.startPath(1, 1); - polygon1.lineTo(9, 1); - polygon1.lineTo(9, 9); - polygon1.lineTo(1, 9); - - polyline2.startPath(3, 3); - polyline2.lineTo(6, 6); - - boolean res = disjoint.execute(polyline2, polygon1, sr, null); - assertTrue(res); - res = disjoint.execute(polygon1, polyline2, sr, null); - assertTrue(res); - - polyline2.startPath(0, 0); - polyline2.lineTo(0, 5); - - res = disjoint.execute(polyline2, polygon1, sr, null); - assertTrue(!res); - res = disjoint.execute(polygon1, polyline2, sr, null); - assertTrue(!res); - - polygon1.setEmpty(); - polyline2.setEmpty(); - - polygon1.startPath(0, 0); - polygon1.lineTo(0, 10); - polygon1.lineTo(10, 10); - polygon1.lineTo(10, 0); - - polyline2.startPath(2, 2); - polyline2.lineTo(4, 4); - - OperatorFactoryLocal factory = OperatorFactoryLocal.getInstance(); - OperatorSimplify simplify_op = (OperatorSimplify) factory - .getOperator(Operator.Type.Simplify); - simplify_op.isSimpleAsFeature(polygon1, sr, null); - simplify_op.isSimpleAsFeature(polyline2, sr, null); - - res = disjoint.execute(polyline2, polygon1, sr, null); - assertTrue(!res); - res = disjoint.execute(polygon1, polyline2, sr, null); - assertTrue(!res); - } - - @Test - public void testPolylineMultiPointDisjoint() { - OperatorDisjoint disjoint = (OperatorDisjoint) (OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.Disjoint)); - SpatialReference sr = SpatialReference.create(102100); - @SuppressWarnings("unused") - double tolerance = sr - .getTolerance(VertexDescription.Semantics.POSITION); - - Polyline polyline1 = new Polyline(); - MultiPoint multipoint2 = new MultiPoint(); - - polyline1.startPath(0, 0); - polyline1.lineTo(2, 0); - polyline1.lineTo(4, 2); - - multipoint2.add(1, 1); - multipoint2.add(2, 2); - multipoint2.add(3, 0); - - boolean res = disjoint.execute(polyline1, multipoint2, sr, null); - assertTrue(res); - res = disjoint.execute(multipoint2, polyline1, sr, null); - assertTrue(res); - - multipoint2.add(3, 1); - res = disjoint.execute(polyline1, multipoint2, sr, null); - assertTrue(!res); - res = disjoint.execute(multipoint2, polyline1, sr, null); - assertTrue(!res); - - polyline1.startPath(1, -4); - polyline1.lineTo(1, -3); - polyline1.lineTo(1, -2); - polyline1.lineTo(1, -1); - polyline1.lineTo(1, 0); - polyline1.lineTo(1, 1); - - disjoint.accelerateGeometry(polyline1, sr, - Geometry.GeometryAccelerationDegree.enumHot); - res = disjoint.execute(polyline1, multipoint2, sr, null); - assertTrue(!res); - res = disjoint.execute(multipoint2, polyline1, sr, null); - assertTrue(!res); - } - - @Test - public void testPolylinePointDisjoint() { - OperatorDisjoint disjoint = (OperatorDisjoint) (OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.Disjoint)); - OperatorContains contains = (OperatorContains) (OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.Contains)); - SpatialReference sr = SpatialReference.create(102100); - @SuppressWarnings("unused") - double tolerance = sr - .getTolerance(VertexDescription.Semantics.POSITION); - - Polyline polyline1 = new Polyline(); - Point point2 = new Point(); - - polyline1.startPath(0, 0); - polyline1.lineTo(2, 0); - polyline1.lineTo(4, 2); - - point2.setXY(1, 1); - - boolean res = disjoint.execute(polyline1, point2, sr, null); - assertTrue(res); - res = disjoint.execute(point2, polyline1, sr, null); - assertTrue(res); - - res = contains.execute(polyline1, point2, sr, null); - assertTrue(!res); - res = contains.execute(point2, polyline1, sr, null); - assertTrue(!res); - - point2.setXY(4, 2); - - polyline1 = (Polyline) OperatorDensifyByLength.local().execute( - polyline1, 0.1, null); - disjoint.accelerateGeometry(polyline1, sr, - Geometry.GeometryAccelerationDegree.enumHot); - - res = disjoint.execute(polyline1, point2, sr, null); - assertTrue(!res); - res = disjoint.execute(point2, polyline1, sr, null); - assertTrue(!res); - - res = contains.execute(polyline1, point2, sr, null); - assertTrue(!res); - res = contains.execute(point2, polyline1, sr, null); - assertTrue(!res); - - polyline1.setEmpty(); - point2.setEmpty(); - - polyline1.startPath(659062.37370000035, 153070.85220000148); - polyline1.lineTo(660916.47940000147, 151481.10269999877); - point2.setXY(659927.85020000115, 152328.77430000156); - - res = contains.execute(polyline1, point2, - SpatialReference.create(54004), null); - assertTrue(res); - } - - @Test - public void testMultiPointMultiPointDisjoint() { - OperatorDisjoint disjoint = (OperatorDisjoint) (OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.Disjoint)); - SpatialReference sr = SpatialReference.create(102100); - @SuppressWarnings("unused") - double tolerance = sr - .getTolerance(VertexDescription.Semantics.POSITION); - - MultiPoint multipoint1 = new MultiPoint(); - MultiPoint multipoint2 = new MultiPoint(); - - multipoint1.add(2, 2); - multipoint1.add(2, 5); - multipoint1.add(4, 1); - multipoint1.add(4, 4); - multipoint1.add(4, 7); - multipoint1.add(6, 2); - multipoint1.add(6, 6); - multipoint1.add(4, 1); - multipoint1.add(6, 6); - - multipoint2.add(0, 1); - multipoint2.add(0, 7); - multipoint2.add(4, 2); - multipoint2.add(4, 6); - multipoint2.add(6, 4); - multipoint2.add(4, 2); - multipoint2.add(0, 1); - - boolean res = disjoint.execute(multipoint1, multipoint2, sr, null); - assertTrue(res); - res = disjoint.execute(multipoint2, multipoint1, sr, null); - assertTrue(res); - - multipoint2.add(2, 2); - res = disjoint.execute(multipoint1, multipoint2, sr, null); - assertTrue(!res); - res = disjoint.execute(multipoint2, multipoint1, sr, null); - assertTrue(!res); - } - - @Test - public void testMultiPointPointDisjoint() { - OperatorDisjoint disjoint = (OperatorDisjoint) (OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.Disjoint)); - OperatorContains contains = (OperatorContains) (OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.Contains)); - SpatialReference sr = SpatialReference.create(102100); - @SuppressWarnings("unused") - double tolerance = sr - .getTolerance(VertexDescription.Semantics.POSITION); - - MultiPoint multipoint1 = new MultiPoint(); - Point point2 = new Point(); - - multipoint1.add(2, 2); - multipoint1.add(2, 5); - multipoint1.add(4, 1); - multipoint1.add(4, 4); - multipoint1.add(4, 7); - multipoint1.add(6, 2); - multipoint1.add(6, 6); - multipoint1.add(4, 1); - multipoint1.add(6, 6); - - point2.setXY(2, 6); - - boolean res = disjoint.execute(multipoint1, point2, sr, null); - assertTrue(res); - res = disjoint.execute(point2, multipoint1, sr, null); - assertTrue(res); - - res = contains.execute(multipoint1, point2, sr, null); - assertTrue(!res); - res = contains.execute(point2, multipoint1, sr, null); - assertTrue(!res); - - multipoint1.add(2, 6); - res = disjoint.execute(multipoint1, point2, sr, null); - assertTrue(!res); - res = disjoint.execute(point2, multipoint1, sr, null); - assertTrue(!res); - - res = contains.execute(multipoint1, point2, sr, null); - assertTrue(res); - res = contains.execute(point2, multipoint1, sr, null); - assertTrue(!res); - } - - @Test - public void testPolygonMultiPointDisjoint() { - OperatorDisjoint disjoint = (OperatorDisjoint) (OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.Disjoint)); - SpatialReference sr = SpatialReference.create(102100); - @SuppressWarnings("unused") - double tolerance = sr - .getTolerance(VertexDescription.Semantics.POSITION); - - Polygon polygon1 = new Polygon(); - MultiPoint multipoint2 = new MultiPoint(); - - polygon1.startPath(0, 0); - polygon1.lineTo(0, 10); - polygon1.lineTo(10, 10); - - multipoint2.add(-1, 5); - multipoint2.add(5, 11); - multipoint2.add(11, 5); - multipoint2.add(5, -1); - - boolean res = disjoint.execute(polygon1, multipoint2, sr, null); - assertTrue(res); - res = disjoint.execute(multipoint2, polygon1, sr, null); - assertTrue(res); - - polygon1.startPath(15, 0); - polygon1.lineTo(15, 10); - polygon1.lineTo(25, 10); - polygon1.lineTo(25, 0); - - multipoint2.add(14, 5); - multipoint2.add(20, 11); - multipoint2.add(26, 5); - multipoint2.add(20, -1); - - res = disjoint.execute(polygon1, multipoint2, sr, null); - assertTrue(res); - res = disjoint.execute(multipoint2, polygon1, sr, null); - assertTrue(res); - - multipoint2.add(20, 5); - - res = disjoint.execute(polygon1, multipoint2, sr, null); - assertTrue(!res); - res = disjoint.execute(multipoint2, polygon1, sr, null); - assertTrue(!res); - } - - @Test - public void testPolygonMultiPointTouches() { - OperatorTouches touches = (OperatorTouches) (OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.Touches)); - SpatialReference sr = SpatialReference.create(102100); - @SuppressWarnings("unused") - double tolerance = sr - .getTolerance(VertexDescription.Semantics.POSITION); - - Polygon polygon1 = new Polygon(); - MultiPoint multipoint2 = new MultiPoint(); - - polygon1.startPath(0, 0); - polygon1.lineTo(0, 10); - polygon1.lineTo(10, 10); - polygon1.lineTo(10, 0); - - multipoint2.add(-1, 5); - multipoint2.add(5, 11); - multipoint2.add(11, 5); - multipoint2.add(5, -1); - - boolean res = touches.execute(polygon1, multipoint2, sr, null); - assertTrue(!res); - res = touches.execute(multipoint2, polygon1, sr, null); - assertTrue(!res); - - multipoint2.add(5, 10); - - res = touches.execute(polygon1, multipoint2, sr, null); - assertTrue(res); - res = touches.execute(multipoint2, polygon1, sr, null); - assertTrue(res); - - multipoint2.add(5, 5); - res = touches.execute(polygon1, multipoint2, sr, null); - assertTrue(!res); - res = touches.execute(multipoint2, polygon1, sr, null); - assertTrue(!res); - } - - @Test - public void testPolygonPointTouches() { - OperatorTouches touches = (OperatorTouches) (OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.Touches)); - SpatialReference sr = SpatialReference.create(102100); - @SuppressWarnings("unused") - double tolerance = sr - .getTolerance(VertexDescription.Semantics.POSITION); - - Polygon polygon1 = new Polygon(); - Point point2 = new Point(); - - polygon1.startPath(0, 0); - polygon1.lineTo(0, 10); - polygon1.lineTo(10, 10); - polygon1.lineTo(10, 0); - - point2.setXY(5, 5); - - boolean res = touches.execute(polygon1, point2, sr, null); - assertTrue(!res); - res = touches.execute(point2, polygon1, sr, null); - assertTrue(!res); - - point2.setXY(5, 10); - - res = touches.execute(polygon1, point2, sr, null); - assertTrue(res); - res = touches.execute(point2, polygon1, sr, null); - assertTrue(res); - } - - @Test - public void testPolygonPolygonTouches() { - OperatorTouches touches = (OperatorTouches) (OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.Touches)); - SpatialReference sr = SpatialReference.create(102100); - double tolerance = sr - .getTolerance(VertexDescription.Semantics.POSITION); - - // Polygon1 and Polygon2 touch at a point - String str1 = "{\"rings\":[[[0,0],[0,5],[0,10],[10,10],[10,0],[0,0]],[[1,1],[9,1],[9,9],[1,9],[1,1],[1,1]]]}"; - String str2 = "{\"rings\":[[[10,10],[10,15],[15,15],[15,10],[10,10]]]}"; - - Polygon polygon1 = (Polygon) (TestCommonMethods.fromJson(str1) - .getGeometry()); - Polygon polygon2 = (Polygon) (TestCommonMethods.fromJson(str2) - .getGeometry()); - wiggleGeometry(polygon1, tolerance, 1982); - wiggleGeometry(polygon2, tolerance, 511); - - boolean res = touches.execute(polygon1, polygon2, sr, null); - assertTrue(res); - res = touches.execute(polygon2, polygon1, sr, null); - assertTrue(res); - - // Polygon1 and Polygon2 touch along the boundary - str1 = "{\"rings\":[[[0,0],[0,5],[0,10],[10,10],[10,0],[0,0]],[[1,1],[9,1],[9,9],[1,9],[1,1],[1,1]]]}"; - str2 = "{\"rings\":[[[10,0],[10,10],[15,10],[15,0],[10,0]]]}"; - - polygon1 = (Polygon) (TestCommonMethods.fromJson(str1).getGeometry()); - polygon2 = (Polygon) (TestCommonMethods.fromJson(str2).getGeometry()); - wiggleGeometry(polygon1, tolerance, 1982); - wiggleGeometry(polygon2, tolerance, 511); - - res = touches.execute(polygon1, polygon2, sr, null); - assertTrue(res); - res = touches.execute(polygon2, polygon1, sr, null); - assertTrue(res); - - // Polygon1 and Polygon2 touch at a corner of Polygon1 and a diagonal of - // Polygon2 - str1 = "{\"rings\":[[[0,0],[0,5],[0,10],[10,10],[10,0],[0,0]],[[1,1],[9,1],[9,9],[1,9],[1,1],[1,1]]]}"; - str2 = "{\"rings\":[[[15,5],[5,15],[15,15],[15,5]]]}"; - - polygon1 = (Polygon) (TestCommonMethods.fromJson(str1).getGeometry()); - polygon2 = (Polygon) (TestCommonMethods.fromJson(str2).getGeometry()); - wiggleGeometry(polygon1, tolerance, 1982); - wiggleGeometry(polygon2, tolerance, 511); - - res = touches.execute(polygon1, polygon2, sr, null); - assertTrue(res); - res = touches.execute(polygon2, polygon1, sr, null); - assertTrue(res); - - // Polygon1 and Polygon2 do not touch - str1 = "{\"rings\":[[[0,0],[0,5],[0,10],[10,10],[10,0],[0,0]],[[1,1],[9,1],[9,9],[1,9],[1,1],[1,1]]]}"; - str2 = "{\"rings\":[[[5,5],[5,15],[15,15],[15,5],[5,5]]]}"; - - polygon1 = (Polygon) (TestCommonMethods.fromJson(str1).getGeometry()); - polygon2 = (Polygon) (TestCommonMethods.fromJson(str2).getGeometry()); - - res = touches.execute(polygon1, polygon2, sr, null); - assertTrue(!res); - res = touches.execute(polygon2, polygon1, sr, null); - assertTrue(!res); - - polygon1.setEmpty(); - polygon2.setEmpty(); - - polygon1.startPath(0, 0); - polygon1.lineTo(0, 1); - polygon1.lineTo(-1, 0); - - polygon2.startPath(0, 0); - polygon2.lineTo(0, 1); - polygon2.lineTo(1, 0); - - res = touches.execute(polygon1, polygon2, sr, null); - assertTrue(res); - res = touches.execute(polygon2, polygon1, sr, null); - assertTrue(res); - } - - @Test - public void testPolygonPolylineTouches() { - OperatorTouches touches = (OperatorTouches) (OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.Touches)); - SpatialReference sr = SpatialReference.create(102100); - double tolerance = sr - .getTolerance(VertexDescription.Semantics.POSITION); - - // Polygon1 and Polyline2 touch at a point - String str1 = "{\"rings\":[[[0,0],[0,5],[0,10],[10,10],[10,0]],[[1,1],[9,1],[9,9],[1,9],[1,1],[1,1]]]}"; - String str2 = "{\"paths\":[[[10,10],[10,15],[15,15],[15,10]]]}"; - - Polygon polygon1 = (Polygon) (TestCommonMethods.fromJson(str1) - .getGeometry()); - Polyline polyline2 = (Polyline) (TestCommonMethods.fromJson(str2) - .getGeometry()); - wiggleGeometry(polygon1, tolerance, 1982); - wiggleGeometry(polyline2, tolerance, 511); - - boolean res = touches.execute(polygon1, polyline2, sr, null); - assertTrue(res); - res = touches.execute(polyline2, polygon1, sr, null); - assertTrue(res); - - // Polygon1 and Polyline2 overlap along the boundary - str1 = "{\"rings\":[[[0,0],[0,5],[0,10],[10,10],[10,0],[0,0]],[[1,1],[9,1],[9,9],[1,9],[1,1],[1,1]]]}"; - str2 = "{\"paths\":[[[10,0],[10,10],[15,10],[15,0]]]}"; - - polygon1 = (Polygon) (TestCommonMethods.fromJson(str1).getGeometry()); - polyline2 = (Polyline) (TestCommonMethods.fromJson(str2).getGeometry()); - wiggleGeometry(polygon1, tolerance, 1982); - wiggleGeometry(polyline2, tolerance, 511); - - res = touches.execute(polygon1, polyline2, sr, null); - assertTrue(res); - res = touches.execute(polyline2, polygon1, sr, null); - assertTrue(res); - - str1 = "{\"rings\":[[[0,0],[0,5],[0,10],[10,10],[10,0],[0,0]],[[1,1],[9,1],[9,9],[1,9],[1,1],[1,1]]]}"; - str2 = "{\"paths\":[[[15,5],[5,15],[15,15],[15,5]]]}"; - - polygon1 = (Polygon) (TestCommonMethods.fromJson(str1).getGeometry()); - polyline2 = (Polyline) (TestCommonMethods.fromJson(str2).getGeometry()); - wiggleGeometry(polygon1, tolerance, 1982); - wiggleGeometry(polyline2, tolerance, 511); - - res = touches.execute(polygon1, polyline2, sr, null); - assertTrue(res); - res = touches.execute(polyline2, polygon1, sr, null); - assertTrue(res); - - str1 = "{\"rings\":[[[10,10],[10,0],[0,0],[0,10],[10,10]],[[1,1],[9,1],[9,9],[1,9],[1,1],[1,1]]]}"; - str2 = "{\"paths\":[[[15,5],[5,15],[15,15]]]}"; - - polygon1 = (Polygon) (TestCommonMethods.fromJson(str1).getGeometry()); - polyline2 = (Polyline) (TestCommonMethods.fromJson(str2).getGeometry()); - wiggleGeometry(polygon1, tolerance, 1982); - wiggleGeometry(polyline2, tolerance, 511); - - res = touches.execute(polygon1, polyline2, sr, null); - assertTrue(res); - res = touches.execute(polyline2, polygon1, sr, null); - assertTrue(res); - } - - @Test - public void testPolylinePolylineTouches() { - OperatorTouches touches = (OperatorTouches) (OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.Touches)); - SpatialReference sr = SpatialReference.create(102100); - double tolerance = sr - .getTolerance(VertexDescription.Semantics.POSITION); - - // Polyline1 and Polyline2 touch at a point - String str1 = "{\"paths\":[[[0,0],[0,5],[0,10],[10,10],[10,0]],[[1,1],[9,1],[9,9],[1,9],[1,1],[1,1]]]}"; - String str2 = "{\"paths\":[[[10,10],[10,15],[15,15],[15,10]]]}"; - - Polyline polyline1 = (Polyline) (TestCommonMethods.fromJson(str1) - .getGeometry()); - Polyline polyline2 = (Polyline) (TestCommonMethods.fromJson(str2) - .getGeometry()); - - boolean res = touches.execute(polyline1, polyline2, sr, null); - assertTrue(res); - res = touches.execute(polyline2, polyline1, sr, null); - assertTrue(res); - - // Polyline1 and Polyline2 overlap along the boundary - str1 = "{\"paths\":[[[0,0],[0,5],[0,10],[10,10],[10,0],[0,0]],[[1,1],[9,1],[9,9],[1,9],[1,1],[1,1]]]}"; - str2 = "{\"paths\":[[[10,0],[10,10],[15,10],[15,0],[10,0]]]}"; - - polyline1 = (Polyline) (TestCommonMethods.fromJson(str1).getGeometry()); - polyline2 = (Polyline) (TestCommonMethods.fromJson(str2).getGeometry()); - - res = touches.execute(polyline1, polyline2, sr, null); - assertTrue(!res); - res = touches.execute(polyline2, polyline1, sr, null); - assertTrue(!res); - - // Polyline1 and Polyline2 intersect at interiors - str1 = "{\"paths\":[[[0,0],[0,5],[0,10],[10,10],[10,0],[0,0]],[[1,1],[9,1],[9,9],[1,9],[1,1],[1,1]]]}"; - str2 = "{\"paths\":[[[15,5],[5,15],[15,15],[15,5]]]}"; - - polyline1 = (Polyline) (TestCommonMethods.fromJson(str1).getGeometry()); - polyline2 = (Polyline) (TestCommonMethods.fromJson(str2).getGeometry()); - - res = touches.execute(polyline1, polyline2, sr, null); - assertTrue(!res); - res = touches.execute(polyline2, polyline1, sr, null); - assertTrue(!res); - - // Polyline1 and Polyline2 touch at an endpoint of Polyline1 and - // interior of Polyline2 (but Polyline1 is closed) - str1 = "{\"paths\":[[[10,10],[10,0],[0,0],[0,10],[10,10]],[[1,1],[9,1],[9,9],[1,9],[1,1],[1,1]]]}"; - str2 = "{\"paths\":[[[15,5],[5,15],[15,15]]]}"; - - polyline1 = (Polyline) (TestCommonMethods.fromJson(str1).getGeometry()); - polyline2 = (Polyline) (TestCommonMethods.fromJson(str2).getGeometry()); - - res = touches.execute(polyline1, polyline2, sr, null); - assertTrue(!res); - res = touches.execute(polyline2, polyline1, sr, null); - assertTrue(!res); - - // Polyline1 and Polyline2 touch at an endpoint of Polyline1 and - // interior of Polyline2 (same as previous case, but Polyline1 is not - // closed) - str1 = "{\"paths\":[[[10,10],[10,0],[0,0],[0,10]],[[1,1],[9,1],[9,9],[1,9],[1,1],[1,1]]]}"; - str2 = "{\"paths\":[[[15,5],[5,15],[15,15]]]}"; - - polyline1 = (Polyline) (TestCommonMethods.fromJson(str1).getGeometry()); - polyline2 = (Polyline) (TestCommonMethods.fromJson(str2).getGeometry()); - - res = touches.execute(polyline1, polyline2, sr, null); - assertTrue(res); - res = touches.execute(polyline2, polyline1, sr, null); - assertTrue(res); - - str1 = "{\"paths\":[[[10,10],[10,0],[0,0],[0,10]],[[1,1],[9,1],[9,9],[1,9],[6, 9]]]}"; - str2 = "{\"paths\":[[[15,5],[5,15],[15,15],[15,5]]]}"; - - polyline1 = (Polyline) (TestCommonMethods.fromJson(str1).getGeometry()); - polyline2 = (Polyline) (TestCommonMethods.fromJson(str2).getGeometry()); - - res = touches.execute(polyline1, polyline2, sr, null); - assertTrue(res); - res = touches.execute(polyline2, polyline1, sr, null); - assertTrue(res); - - polyline1.setEmpty(); - polyline2.setEmpty(); - - polyline1.startPath(-2, -2); - polyline1.lineTo(-1, -1); - polyline1.lineTo(1, 1); - polyline1.lineTo(2, 2); - - polyline2.startPath(-2, 2); - polyline2.lineTo(-1, 1); - polyline2.lineTo(1, -1); - polyline2.lineTo(2, -2); - - res = touches.execute(polyline2, polyline1, sr, null); - assertTrue(!res); - - polyline1.setEmpty(); - polyline2.setEmpty(); - - polyline1.startPath(-2, -2); - polyline1.lineTo(-1, -1); - polyline1.lineTo(1, 1); - polyline1.lineTo(2, 2); - - polyline2.startPath(-2, 2); - polyline2.lineTo(-1, 1); - polyline2.lineTo(1, -1); - - res = touches.execute(polyline1, polyline2, sr, null); - assertTrue(!res); - - polyline1.setEmpty(); - polyline2.setEmpty(); - - polyline1.startPath(-1, -1); - polyline1.lineTo(0, 0); - polyline1.lineTo(1, 1); - - polyline2.startPath(-1, 1); - polyline2.lineTo(0, 0); - polyline2.lineTo(1, -1); - - res = touches.execute(polyline2, polyline1, sr, null); - assertTrue(!res); - - polyline1.setEmpty(); - polyline2.setEmpty(); - polyline1.startPath(0, 0); - polyline1.lineTo(0, 1); - polyline1.lineTo(0, 0); - polyline2.startPath(0, 1); - polyline2.lineTo(0, 2); - polyline2.lineTo(0, 1); - - res = touches.execute(polyline2, polyline1, sr, null); - assertTrue(!res); - - res = touches.execute(polyline1, polyline2, sr, null); - assertTrue(!res); - } - - @Test - public void testPolylineMultiPointTouches() { - OperatorTouches touches = (OperatorTouches) (OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.Touches)); - SpatialReference sr = SpatialReference.create(102100); - @SuppressWarnings("unused") - double tolerance = sr - .getTolerance(VertexDescription.Semantics.POSITION); - - Polyline polyline1 = new Polyline(); - MultiPoint multipoint2 = new MultiPoint(); - - polyline1.startPath(0, 0); - polyline1.lineTo(2, 0); - polyline1.lineTo(4, 2); - - multipoint2.add(1, 1); - multipoint2.add(2, 2); - multipoint2.add(3, 0); - - boolean res = touches.execute(polyline1, multipoint2, sr, null); - assertTrue(!res); - res = touches.execute(multipoint2, polyline1, sr, null); - assertTrue(!res); - - polyline1.startPath(1, -4); - polyline1.lineTo(1, -3); - polyline1.lineTo(1, -2); - polyline1.lineTo(1, -1); - polyline1.lineTo(1, 0); - polyline1.lineTo(1, 1); - - touches.accelerateGeometry(polyline1, sr, - Geometry.GeometryAccelerationDegree.enumHot); - res = touches.execute(polyline1, multipoint2, sr, null); - assertTrue(res); - res = touches.execute(multipoint2, polyline1, sr, null); - assertTrue(res); - - multipoint2.add(3, 1); - res = touches.execute(polyline1, multipoint2, sr, null); - assertTrue(!res); - res = touches.execute(multipoint2, polyline1, sr, null); - assertTrue(!res); - - polyline1.startPath(0, 0); - polyline1.lineTo(2, 0); - - polyline1.startPath(2, 1); - polyline1.lineTo(2, -1); - - multipoint2.add(2, 0); - - res = touches.execute(polyline1, multipoint2, sr, null); - assertTrue(!res); - res = touches.execute(multipoint2, polyline1, sr, null); - assertTrue(!res); - } - - @Test - public void testPolylineMultiPointCrosses() { - OperatorCrosses crosses = (OperatorCrosses) (OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.Crosses)); - SpatialReference sr = SpatialReference.create(102100); - double tolerance = sr - .getTolerance(VertexDescription.Semantics.POSITION); - - Polyline polyline1 = new Polyline(); - MultiPoint multipoint2 = new MultiPoint(); - - polyline1.startPath(0, 0); - polyline1.lineTo(2, 0); - polyline1.lineTo(4, 2); - - multipoint2.add(1, 1); - multipoint2.add(2, 2); - multipoint2.add(3, 0); - multipoint2.add(0, 0); - - boolean res = crosses.execute(polyline1, multipoint2, sr, null); - assertTrue(!res); - res = crosses.execute(multipoint2, polyline1, sr, null); - assertTrue(!res); - - polyline1.startPath(1, -4); - polyline1.lineTo(1, -3); - polyline1.lineTo(1, -2); - polyline1.lineTo(1, -1); - polyline1.lineTo(1, 0); - polyline1.lineTo(1, 1); - - res = crosses.execute(polyline1, multipoint2, sr, null); - assertTrue(!res); - res = crosses.execute(multipoint2, polyline1, sr, null); - assertTrue(!res); - - crosses.accelerateGeometry(polyline1, sr, - Geometry.GeometryAccelerationDegree.enumHot); - - multipoint2.add(1, 0); - res = crosses.execute(polyline1, multipoint2, sr, null); - assertTrue(res); - res = crosses.execute(multipoint2, polyline1, sr, null); - assertTrue(res); - - multipoint2.add(3, 1); - res = crosses.execute(polyline1, multipoint2, sr, null); - assertTrue(res); - res = crosses.execute(multipoint2, polyline1, sr, null); - assertTrue(res); - } - - @Test - public void testPolylinePointTouches() { - OperatorTouches touches = (OperatorTouches) (OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.Touches)); - SpatialReference sr = SpatialReference.create(102100); - @SuppressWarnings("unused") - double tolerance = sr - .getTolerance(VertexDescription.Semantics.POSITION); - - Polyline polyline1 = new Polyline(); - Point point2 = new Point(); - - polyline1.startPath(0, 0); - polyline1.lineTo(2, 0); - - polyline1.startPath(2, 1); - polyline1.lineTo(2, -1); - - point2.setXY(2, 0); - - boolean res = touches.execute(polyline1, point2, sr, null); - assertTrue(res); - res = touches.execute(point2, polyline1, sr, null); - assertTrue(res); - } - - @Test - public void testPolygonPolygonOverlaps() { - OperatorOverlaps overlaps = (OperatorOverlaps) (OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.Overlaps)); - SpatialReference sr = SpatialReference.create(102100); - double tolerance = sr - .getTolerance(VertexDescription.Semantics.POSITION); - - // Polygon1 and Polygon2 touch at a point - String str1 = "{\"rings\":[[[0,0],[0,5],[0,10],[10,10],[10,0],[0,0]],[[1,1],[9,1],[9,9],[1,9],[1,1],[1,1]]]}"; - String str2 = "{\"rings\":[[[10,10],[10,15],[15,15],[15,10],[10,10]]]}"; - - Polygon polygon1 = (Polygon) (TestCommonMethods.fromJson(str1) - .getGeometry()); - Polygon polygon2 = (Polygon) (TestCommonMethods.fromJson(str2) - .getGeometry()); - wiggleGeometry(polygon1, tolerance, 1982); - wiggleGeometry(polygon2, tolerance, 511); - - boolean res = overlaps.execute(polygon1, polygon2, sr, null); - assertTrue(!res); - res = overlaps.execute(polygon2, polygon1, sr, null); - assertTrue(!res); - - // Polygon1 and Polygon2 touch along the boundary - str1 = "{\"rings\":[[[0,0],[0,5],[0,10],[10,10],[10,0],[0,0]],[[1,1],[9,1],[9,9],[1,9],[1,1],[1,1]]]}"; - str2 = "{\"rings\":[[[10,0],[10,10],[15,10],[15,0],[10,0]]]}"; - - polygon1 = (Polygon) (TestCommonMethods.fromJson(str1).getGeometry()); - polygon2 = (Polygon) (TestCommonMethods.fromJson(str2).getGeometry()); - wiggleGeometry(polygon1, tolerance, 1982); - wiggleGeometry(polygon2, tolerance, 511); - - res = overlaps.execute(polygon1, polygon2, sr, null); - assertTrue(!res); - res = overlaps.execute(polygon2, polygon1, sr, null); - assertTrue(!res); - - // Polygon1 and Polygon2 touch at a corner of Polygon1 and a diagonal of - // Polygon2 - str1 = "{\"rings\":[[[0,0],[0,5],[0,10],[10,10],[10,0],[0,0]],[[1,1],[9,1],[9,9],[1,9],[1,1],[1,1]]]}"; - str2 = "{\"rings\":[[[15,5],[5,15],[15,15],[15,5]]]}"; - - polygon1 = (Polygon) (TestCommonMethods.fromJson(str1).getGeometry()); - polygon2 = (Polygon) (TestCommonMethods.fromJson(str2).getGeometry()); - wiggleGeometry(polygon1, tolerance, 1982); - wiggleGeometry(polygon2, tolerance, 511); - - res = overlaps.execute(polygon1, polygon2, sr, null); - assertTrue(!res); - res = overlaps.execute(polygon2, polygon1, sr, null); - assertTrue(!res); - - // Polygon1 and Polygon2 overlap at the upper right corner - str1 = "{\"rings\":[[[0,0],[0,5],[0,10],[10,10],[10,0],[0,0]],[[1,1],[9,1],[9,9],[1,9],[1,1],[1,1]]]}"; - str2 = "{\"rings\":[[[5,5],[5,15],[15,15],[15,5],[5,5]]]}"; - - polygon1 = (Polygon) (TestCommonMethods.fromJson(str1).getGeometry()); - polygon2 = (Polygon) (TestCommonMethods.fromJson(str2).getGeometry()); - - res = overlaps.execute(polygon1, polygon2, sr, null); - assertTrue(res); - res = overlaps.execute(polygon2, polygon1, sr, null); - assertTrue(res); - - str1 = "{\"rings\":[[[0,0],[0,5],[0,10],[10,10],[10,0],[0,0]],[[4,4],[6,4],[6,6],[4,6],[4,4],[4,4]]]}"; - str2 = "{\"rings\":[[[1,1],[1,9],[9,9],[9,1],[1,1]]]}"; - - polygon1 = (Polygon) (TestCommonMethods.fromJson(str1).getGeometry()); - polygon2 = (Polygon) (TestCommonMethods.fromJson(str2).getGeometry()); - - res = overlaps.execute(polygon1, polygon2, sr, null); - assertTrue(res); - res = overlaps.execute(polygon2, polygon1, sr, null); - assertTrue(res); - } - - @Test - public void testPolygonPolylineWithin() { - OperatorWithin within = (OperatorWithin) (OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.Within)); - SpatialReference sr = SpatialReference.create(102100); - @SuppressWarnings("unused") - double tolerance = sr - .getTolerance(VertexDescription.Semantics.POSITION); - - Polygon polygon1 = new Polygon(); - Polyline polyline2 = new Polyline(); - - polygon1.startPath(0, 0); - polygon1.lineTo(0, 10); - polygon1.lineTo(10, 10); - polygon1.lineTo(10, 0); - - polyline2.startPath(5, 0); - polyline2.lineTo(5, 10); - - boolean res = within.execute(polygon1, polyline2, sr, null); - assertTrue(!res); - res = within.execute(polyline2, polygon1, sr, null); - assertTrue(res); - - polyline2.setEmpty(); - polyline2.startPath(0, 1); - polyline2.lineTo(0, 9); - - res = within.execute(polyline2, polygon1, sr, null); - assertTrue(!res); - } - - @Test - public void testMultiPointMultiPointWithin() { - OperatorWithin within = (OperatorWithin) (OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.Within)); - SpatialReference sr = SpatialReference.create(102100); - @SuppressWarnings("unused") - double tolerance = sr - .getTolerance(VertexDescription.Semantics.POSITION); - - MultiPoint multipoint1 = new MultiPoint(); - MultiPoint multipoint2 = new MultiPoint(); - - multipoint1.add(0, 0); - multipoint1.add(3, 3); - multipoint1.add(0, 0); - multipoint1.add(5, 5); - multipoint1.add(3, 3); - multipoint1.add(2, 4); - multipoint1.add(2, 8); - - multipoint2.add(0, 0); - multipoint2.add(3, 3); - multipoint2.add(2, 4); - multipoint2.add(2, 8); - multipoint2.add(5, 5); - - boolean res = within.execute(multipoint1, multipoint2, sr, null); - assertTrue(res); - res = within.execute(multipoint2, multipoint1, sr, null); - assertTrue(res); - - multipoint2.add(10, 10); - multipoint2.add(10, 10); - - res = within.execute(multipoint1, multipoint2, sr, null); - assertTrue(res); - res = within.execute(multipoint2, multipoint1, sr, null); - assertTrue(!res); - - multipoint1.add(10, 10); - res = within.execute(multipoint1, multipoint2, sr, null); - assertTrue(res); - res = within.execute(multipoint2, multipoint1, sr, null); - assertTrue(res); - - multipoint1.add(-10, -10); - res = within.execute(multipoint1, multipoint2, sr, null); - assertTrue(!res); - res = within.execute(multipoint2, multipoint1, sr, null); - assertTrue(res); - } - - @Test - public void testPolylinePolylineOverlaps() { - OperatorOverlaps overlaps = (OperatorOverlaps) (OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.Overlaps)); - SpatialReference sr = SpatialReference.create(102100); - double tolerance = sr - .getTolerance(VertexDescription.Semantics.POSITION); - - Polyline polyline1 = new Polyline(); - Polyline polyline2 = new Polyline(); - polyline1.startPath(0, 0); - polyline1.lineTo(2, 0); - polyline2.startPath(1, 0); - polyline2.lineTo(3, 0); - polyline2.lineTo(1, 1); - polyline2.lineTo(1, -1); - wiggleGeometry(polyline1, tolerance, 1982); - wiggleGeometry(polyline2, tolerance, 511); - - boolean res = overlaps.execute(polyline1, polyline2, sr, null); - assertTrue(res); - res = overlaps.execute(polyline2, polyline1, sr, null); - assertTrue(res); - - polyline1.setEmpty(); - polyline2.setEmpty(); - polyline1.startPath(0, 0); - polyline1.lineTo(2, 0); - polyline2.startPath(1.9989, 0); - polyline2.lineTo(2.0011, 0); - // wiggleGeometry(polyline1, tolerance, 1982); - // wiggleGeometry(polyline2, tolerance, 511); - - res = overlaps.execute(polyline1, polyline2, sr, null); - assertTrue(res); - res = overlaps.execute(polyline2, polyline1, sr, null); - assertTrue(res); - - polyline1.setEmpty(); - polyline2.setEmpty(); - polyline1.startPath(0, 0); - polyline1.lineTo(2, 0); - polyline2.startPath(1.9989, 0); - polyline2.lineTo(2.0009, 0); - wiggleGeometry(polyline1, tolerance, 1982); - wiggleGeometry(polyline2, tolerance, 511); - - res = overlaps.execute(polyline1, polyline2, sr, null); - assertTrue(!res); - res = overlaps.execute(polyline2, polyline1, sr, null); - assertTrue(!res); - - polyline1.setEmpty(); - polyline2.setEmpty(); - polyline1.startPath(0, 0); - polyline1.lineTo(2, 0); - polyline2.startPath(0, 0); - polyline2.lineTo(2, 0); - polyline2.startPath(0, -1); - polyline2.lineTo(2, -1); - - res = overlaps.execute(polyline1, polyline2, sr, null); - assertTrue(!res); - res = overlaps.execute(polyline2, polyline1, sr, null); - assertTrue(!res); - } - - @Test - public void testMultiPointMultiPointOverlaps() { - OperatorOverlaps overlaps = (OperatorOverlaps) (OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.Overlaps)); - SpatialReference sr = SpatialReference.create(102100); - @SuppressWarnings("unused") - double tolerance = sr - .getTolerance(VertexDescription.Semantics.POSITION); - - MultiPoint multipoint1 = new MultiPoint(); - MultiPoint multipoint2 = new MultiPoint(); - - multipoint1.add(4, 4); - multipoint1.add(6, 4); - - multipoint2.add(6, 2); - multipoint2.add(2, 6); - - boolean res = overlaps.execute(multipoint1, multipoint2, sr, null); - assertTrue(!res); - res = overlaps.execute(multipoint2, multipoint1, sr, null); - assertTrue(!res); - - multipoint1.add(10, 10); - multipoint2.add(6, 2); - - res = overlaps.execute(multipoint1, multipoint2, sr, null); - assertTrue(!res); - res = overlaps.execute(multipoint2, multipoint1, sr, null); - assertTrue(!res); - - multipoint1.add(6, 2); - res = overlaps.execute(multipoint1, multipoint2, sr, null); - assertTrue(res); - res = overlaps.execute(multipoint2, multipoint1, sr, null); - assertTrue(res); - - multipoint1.add(2, 6); - res = overlaps.execute(multipoint1, multipoint2, sr, null); - assertTrue(!res); - res = overlaps.execute(multipoint2, multipoint1, sr, null); - assertTrue(!res); - - multipoint2.add(1, 1); - res = overlaps.execute(multipoint1, multipoint2, sr, null); - assertTrue(res); - res = overlaps.execute(multipoint2, multipoint1, sr, null); - assertTrue(res); - - multipoint2.add(10, 10); - multipoint2.add(4, 4); - multipoint2.add(6, 4); - res = overlaps.execute(multipoint1, multipoint2, sr, null); - assertTrue(!res); - res = overlaps.execute(multipoint2, multipoint1, sr, null); - assertTrue(!res); - } - - @Test - public void testPolygonPolygonWithin() { - OperatorWithin within = (OperatorWithin) (OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.Within)); - SpatialReference sr = SpatialReference.create(102100); - double tolerance = sr - .getTolerance(VertexDescription.Semantics.POSITION); - - // Polygon1 is within Polygon2 - String str1 = "{\"rings\":[[[0,0],[0,5],[0,10],[10,10],[10,0],[0,0]],[[1,1],[9,1],[9,9],[1,9],[1,1],[1,1]]]}"; - String str2 = "{\"rings\":[[[-1,-1],[-1,11],[11,11],[11,-1],[-1,-1]]]}"; - - Polygon polygon1 = (Polygon) (TestCommonMethods.fromJson(str1) - .getGeometry()); - Polygon polygon2 = (Polygon) (TestCommonMethods.fromJson(str2) - .getGeometry()); - - boolean res = within.execute(polygon1, polygon2, sr, null); - assertTrue(res); - res = within.execute(polygon2, polygon1, sr, null); - assertTrue(!res); - - // Polygon1 is within Polygon2, and the boundaries intersect - str1 = "{\"rings\":[[[0,0],[0,5],[0,10],[10,10],[10,0],[0,0]],[[4,4],[6,4],[6,6],[4,6],[4,4],[4,4]]]}"; - str2 = "{\"rings\":[[[1,1],[1,9],[9,9],[9,1],[1,1]]]}"; - - polygon1 = (Polygon) (TestCommonMethods.fromJson(str1).getGeometry()); - polygon2 = (Polygon) (TestCommonMethods.fromJson(str2).getGeometry()); - wiggleGeometry(polygon1, tolerance, 1982); - wiggleGeometry(polygon2, tolerance, 511); - - res = within.execute(polygon2, polygon1, sr, null); - assertTrue(!res); - - // Polygon1 is within Polygon2, and the boundaries intersect - str1 = "{\"rings\":[[[0,0],[0,5],[0,10],[10,10],[10,0],[0,0]],[[1,1],[9,1],[9,9],[1,9],[1,1],[1,1]]]}"; - str2 = "{\"rings\":[[[-1,0],[-1,11],[11,11],[11,0],[-1,0]]]}"; - - polygon1 = (Polygon) (TestCommonMethods.fromJson(str1).getGeometry()); - polygon2 = (Polygon) (TestCommonMethods.fromJson(str2).getGeometry()); - wiggleGeometry(polygon1, tolerance, 1982); - wiggleGeometry(polygon2, tolerance, 511); - - res = within.execute(polygon1, polygon2, sr, null); - assertTrue(res); - - // Polygon2 is inside of the hole of polygon1 - str1 = "{\"rings\":[[[0,0],[0,5],[0,10],[10,10],[10,0],[0,0]],[[1,1],[9,1],[9,9],[1,9],[1,1],[1,1]]]}"; - str2 = "{\"rings\":[[[2,2],[2,8],[8,8],[8,2],[2,2]]]}"; - - polygon1 = (Polygon) (TestCommonMethods.fromJson(str1).getGeometry()); - polygon2 = (Polygon) (TestCommonMethods.fromJson(str2).getGeometry()); - - res = within.execute(polygon2, polygon1, sr, null); - assertTrue(!res); - - str1 = "{\"rings\":[[[0,0],[10,0],[10,10],[0,10]]]}"; - str2 = "{\"rings\":[[[2,2],[2,8],[8,8],[8,2],[2,2],[8,2],[8,8],[2,8],[2,2]]]}"; - - polygon1 = (Polygon) (TestCommonMethods.fromJson(str1).getGeometry()); - polygon2 = (Polygon) (TestCommonMethods.fromJson(str2).getGeometry()); - - res = within.execute(polygon2, polygon1, sr, null); - assertTrue(res); - - str1 = "{\"rings\":[[[0,0],[0,10],[10,10],[10,0]],[[12,8],[12,10],[18,10],[18,8],[12,8]]]}"; - str2 = "{\"paths\":[[[2,2],[2,8],[8,8],[8,2]],[[12,2],[12,4],[18,4],[18,2]]]}"; - - polygon1 = (Polygon) (TestCommonMethods.fromJson(str1).getGeometry()); - Polyline polyline2 = (Polyline) (TestCommonMethods.fromJson(str2).getGeometry()); - - res = within.execute(polyline2, polygon1, sr, null); - assertTrue(!res); - - str1 = "{\"rings\":[[[0,0],[0,10],[10,10],[10,0],[0,0]],[[4,4],[6,4],[6,6],[4,6],[4,4]]]}"; - str2 = "{\"rings\":[[[2,2],[2,8],[8,8],[8,2],[2,2],[2,8],[8,8],[8,2],[2,2]]]}"; - - polygon1 = (Polygon) (TestCommonMethods.fromJson(str1).getGeometry()); - polygon2 = (Polygon) (TestCommonMethods.fromJson(str2).getGeometry()); - - res = within.execute(polygon2, polygon1, sr, null); - assertTrue(res); - - // Same as above, but winding fill rule - str1 = "{\"rings\":[[[0,0],[0,10],[10,10],[10,0],[0,0]],[[4,4],[6,4],[6,6],[4,6],[4,4]]]}"; - str2 = "{\"rings\":[[[2,2],[2,8],[8,8],[8,2],[2,2],[2,8],[8,8],[8,2],[2,2]]]}"; - polygon1 = (Polygon) (TestCommonMethods.fromJson(str1).getGeometry()); - polygon2 = (Polygon) (TestCommonMethods.fromJson(str2).getGeometry()); - polygon1.setFillRule(Polygon.FillRule.enumFillRuleWinding); - polygon2.setFillRule(Polygon.FillRule.enumFillRuleWinding); - - res = within.execute(polygon2, polygon1, sr, null); - assertTrue(!res); - - str1 = "{\"rings\":[[[0,0],[0,10],[10,10],[10,0],[0,0]]]}"; - str2 = "{\"paths\":[[[2,2],[2,2]]]}"; - polygon1 = (Polygon) (TestCommonMethods.fromJson(str1).getGeometry()); - polyline2 = (Polyline) (TestCommonMethods.fromJson(str2).getGeometry()); - res = within.execute(polyline2, polygon1, sr, null); - assertTrue(res); - - str1 = "{\"rings\":[[[0,0],[0,10],[10,10],[10,0],[0,0]],[[11,11],[11,20],[20,20],[20,11],[11,11]]]}"; - str2 = "{\"rings\":[[[2,2],[2,8],[8,8],[15,15],[8,8],[8,2],[2,2]]]}"; - polygon1 = (Polygon) (TestCommonMethods.fromJson(str1).getGeometry()); - polygon2 = (Polygon) (TestCommonMethods.fromJson(str2).getGeometry()); - - res = within.execute(polygon2, polygon1, sr, null); - assertTrue(!res); - - str1 = "{\"rings\":[[[0,0],[0,10],[10,10],[10,0],[0,0]],[[10,10],[10,20],[20,20],[20,10],[10,10]]]}"; - str2 = "{\"rings\":[[[2,2],[2,8],[8,8],[15,15],[8,8],[8,2],[2,2]]]}"; - polygon1 = (Polygon) (TestCommonMethods.fromJson(str1).getGeometry()); - polygon2 = (Polygon) (TestCommonMethods.fromJson(str2).getGeometry()); - - res = within.execute(polygon2, polygon1, sr, null); - assertTrue(res); - - str1 = "{\"rings\":[[[0,0],[0,10],[10,10],[10,0],[0,0]]]}"; - str2 = "{\"rings\":[[[9.9999999925,4],[9.9999999925,6],[10.0000000075,6],[10.0000000075,4],[9.9999999925,4]]]}"; - polygon1 = (Polygon) (TestCommonMethods.fromJson(str1).getGeometry()); - polygon2 = (Polygon) (TestCommonMethods.fromJson(str2).getGeometry()); - - res = within.execute(polygon2, polygon1, sr, null); - assertTrue(!res); - - res = OperatorOverlaps.local().execute(polygon1, polygon2, sr, null); - assertTrue(!res); - - res = OperatorTouches.local().execute(polygon1, polygon2, sr, null); - assertTrue(res); - - str1 = "{\"rings\":[[[0,0],[0,10],[10,10],[10,0],[0,0]],[[10,10],[10,20],[20,20],[20,10],[10,10]]]}"; - str2 = "{\"rings\":[[[2,2],[2,8],[8,8],[15,15],[8,8],[8,2],[2,2]],[[15,5],[15,5],[15,5]]]}"; - polygon1 = (Polygon) (TestCommonMethods.fromJson(str1).getGeometry()); - polygon2 = (Polygon) (TestCommonMethods.fromJson(str2).getGeometry()); - res = within.execute(polygon2, polygon1, sr, null); - assertTrue(!res); - - str1 = "{\"rings\":[[[0,0],[0,10],[10,10],[10,0],[0,0]]]}"; - str2 = "{\"rings\":[[[2,2],[2,2],[2,2]],[[3,3],[3,3],[3,3]]]}"; - polygon1 = (Polygon) (TestCommonMethods.fromJson(str1).getGeometry()); - polygon2 = (Polygon) (TestCommonMethods.fromJson(str2).getGeometry()); - res = within.execute(polygon2, polygon1, sr, null); - assertTrue(res); - - str1 = "{\"rings\":[[[0,0],[0,10],[10,10],[10,0],[0,0]]]}"; - str2 = "{\"rings\":[[[2,2],[2,2],[2,2],[2,2]],[[3,3],[3,3],[3,3],[3,3]]]}"; - polygon1 = (Polygon) (TestCommonMethods.fromJson(str1).getGeometry()); - polygon2 = (Polygon) (TestCommonMethods.fromJson(str2).getGeometry()); - res = within.execute(polygon2, polygon1, sr, null); - assertTrue(res); - - str1 = "{\"rings\":[[[0,0],[0,10],[10,10],[10,0],[0,0]]]}"; - str2 = "{\"paths\":[[[2,2],[2,2]],[[3,3],[3,3]]]}"; - polygon1 = (Polygon) (TestCommonMethods.fromJson(str1).getGeometry()); - polyline2 = (Polyline) (TestCommonMethods.fromJson(str2).getGeometry()); - res = within.execute(polyline2, polygon1, sr, null); - assertTrue(res); - - str1 = "{\"rings\":[[[0,0],[0,10],[10,10],[10,0],[0,0]],[[10,10],[10,20],[20,20],[20,10],[10,10]]]}"; - str2 = "{\"paths\":[[[2,2],[2,8]],[[15,5],[15,5]]]}"; - polygon1 = (Polygon) (TestCommonMethods.fromJson(str1).getGeometry()); - polyline2 = (Polyline) (TestCommonMethods.fromJson(str2).getGeometry()); - res = within.execute(polyline2, polygon1, sr, null); - assertTrue(!res); - - str1 = "{\"rings\":[[[0,0],[0,10],[10,10],[10,0],[0,0]],[[10,10],[10,20],[20,20],[20,10],[10,10]]]}"; - str2 = "{\"paths\":[[[2,2],[2,8]],[[15,5],[15,5],[15,5],[15,5]]]}"; - polygon1 = (Polygon) (TestCommonMethods.fromJson(str1).getGeometry()); - polyline2 = (Polyline) (TestCommonMethods.fromJson(str2).getGeometry()); - res = within.execute(polyline2, polygon1, sr, null); - assertTrue(!res); - - str1 = "{\"rings\":[[[0,0],[0,10],[10,10],[10,0],[0,0]],[[10,10],[10,20],[20,20],[20,10],[10,10]]]}"; - str2 = "{\"paths\":[[[2,2],[2,2]],[[15,5],[15,6]]]}"; - polygon1 = (Polygon) (TestCommonMethods.fromJson(str1).getGeometry()); - polyline2 = (Polyline) (TestCommonMethods.fromJson(str2).getGeometry()); - res = within.execute(polyline2, polygon1, sr, null); - assertTrue(!res); - - str1 = "{\"rings\":[[[0,0],[0,10],[10,10],[10,0],[0,0]],[[10,10],[10,20],[20,20],[20,10],[10,10]]]}"; - str2 = "{\"paths\":[[[2,2],[2,2],[2,2],[2,2]],[[15,5],[15,6]]]}"; - polygon1 = (Polygon) (TestCommonMethods.fromJson(str1).getGeometry()); - polyline2 = (Polyline) (TestCommonMethods.fromJson(str2).getGeometry()); - res = within.execute(polyline2, polygon1, sr, null); - assertTrue(!res); - } - - @Test - public void testPolylinePolylineWithin() { - OperatorWithin within = (OperatorWithin) (OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.Within)); - OperatorContains contains = (OperatorContains) (OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.Contains)); - SpatialReference sr = SpatialReference.create(102100); - @SuppressWarnings("unused") - double tolerance = sr - .getTolerance(VertexDescription.Semantics.POSITION); - - Polyline polyline1 = new Polyline(); - Polyline polyline2 = new Polyline(); - - polyline1.setEmpty(); - polyline2.setEmpty(); - polyline1.startPath(0, 0); - polyline1.lineTo(2, 0); - polyline2.startPath(1.9989, 0); - polyline2.lineTo(2.0011, 0); - - boolean res = within.execute(polyline2, polyline1, sr, null); - assertTrue(!res); - - res = contains.execute(polyline1, polyline2, sr, null); - assertTrue(!res); - - polyline1.setEmpty(); - polyline2.setEmpty(); - polyline1.startPath(0, 0); - polyline1.lineTo(2, 0); - polyline2.startPath(1.9989, 0); - polyline2.lineTo(2.001, 0); - - res = within.execute(polyline2, polyline1, sr, null); - assertTrue(res); - - res = contains.execute(polyline1, polyline2, sr, null); - assertTrue(res); - - polyline1.setEmpty(); - polyline2.setEmpty(); - polyline1.startPath(0, 0); - polyline1.lineTo(2, 0); - polyline1.lineTo(3, 0); - polyline1.lineTo(4, 0); - polyline1.lineTo(5, 0); - polyline1.lineTo(6, 0); - polyline1.lineTo(7, 0); - polyline1.lineTo(8, 0); - - polyline2.startPath(0, 0); - polyline2.lineTo(.1, 0); - polyline2.lineTo(.2, 0); - polyline2.lineTo(.4, 0); - polyline2.lineTo(1.1, 0); - polyline2.lineTo(2.5, 0); - - polyline2.startPath(2.7, 0); - polyline2.lineTo(4, 0); - - res = within.execute(polyline2, polyline1, sr, null); - assertTrue(res); - - res = contains.execute(polyline1, polyline2, sr, null); - assertTrue(res); - } - - @Test - public void testPolylineMultiPointWithin() { - OperatorWithin within = (OperatorWithin) (OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.Within)); - SpatialReference sr = SpatialReference.create(102100); - @SuppressWarnings("unused") - double tolerance = sr - .getTolerance(VertexDescription.Semantics.POSITION); - - Polyline polyline1 = new Polyline(); - MultiPoint multipoint2 = new MultiPoint(); - - polyline1.startPath(0, 0); - polyline1.lineTo(2, 0); - polyline1.lineTo(4, 2); - - multipoint2.add(1, 0); - multipoint2.add(2, 0); - multipoint2.add(3, 1); - multipoint2.add(2, 0); - - boolean res = within.execute(polyline1, multipoint2, sr, null); - assertTrue(!res); - res = within.execute(multipoint2, polyline1, sr, null); - assertTrue(res); - - polyline1.startPath(1, -2); - polyline1.lineTo(1, -1); - polyline1.lineTo(1, 0); - polyline1.lineTo(1, 1); - - res = within.execute(polyline1, multipoint2, sr, null); - assertTrue(!res); - res = within.execute(multipoint2, polyline1, sr, null); - assertTrue(res); - - multipoint2.add(1, 2); - res = within.execute(multipoint2, polyline1, sr, null); - assertTrue(!res); - - multipoint2.add(-1, -1); - multipoint2.add(4, 2); - multipoint2.add(0, 0); - - res = within.execute(multipoint2, polyline1, sr, null); - assertTrue(!res); - } - - @Test - public void testPolygonMultiPointWithin() { - OperatorWithin within = (OperatorWithin) (OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.Within)); - SpatialReference sr = SpatialReference.create(102100); - @SuppressWarnings("unused") - double tolerance = sr - .getTolerance(VertexDescription.Semantics.POSITION); - - Polygon polygon1 = new Polygon(); - MultiPoint multipoint2 = new MultiPoint(); - - polygon1.startPath(0, 0); - polygon1.lineTo(0, 10); - polygon1.lineTo(10, 10); - polygon1.lineTo(10, 0); - - multipoint2.add(5, 0); - multipoint2.add(5, 10); - multipoint2.add(5, 5); - - boolean res = within.execute(polygon1, multipoint2, sr, null); - assertTrue(!res); - res = within.execute(multipoint2, polygon1, sr, null); - assertTrue(res); - - multipoint2.add(5, 11); - res = within.execute(multipoint2, polygon1, sr, null); - assertTrue(!res); - } - - @Test - public void testPolygonPolylineCrosses() { - OperatorCrosses crosses = (OperatorCrosses) (OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.Crosses)); - SpatialReference sr = SpatialReference.create(102100); - @SuppressWarnings("unused") - double tolerance = sr - .getTolerance(VertexDescription.Semantics.POSITION); - - Polygon polygon1 = new Polygon(); - Polyline polyline2 = new Polyline(); - - polygon1.startPath(0, 0); - polygon1.lineTo(0, 10); - polygon1.lineTo(10, 10); - polygon1.lineTo(10, 0); - - polyline2.startPath(5, -5); - polyline2.lineTo(5, 15); - - boolean res = crosses.execute(polygon1, polyline2, sr, null); - assertTrue(res); - res = crosses.execute(polyline2, polygon1, sr, null); - assertTrue(res); - - polyline2.setEmpty(); - polyline2.startPath(5, 0); - polyline2.lineTo(5, 10); - - res = crosses.execute(polygon1, polyline2, sr, null); - assertTrue(!res); - res = crosses.execute(polyline2, polygon1, sr, null); - assertTrue(!res); - - polygon1.setEmpty(); - polyline2.setEmpty(); - - polygon1.startPath(0, 0); - polygon1.lineTo(0, 10); - polygon1.lineTo(10, 10); - polygon1.lineTo(10, 8); - polygon1.lineTo(15, 5); - polygon1.lineTo(10, 2); - polygon1.lineTo(10, 0); - - polyline2.startPath(10, 15); - polyline2.lineTo(10, -5); - - res = crosses.execute(polygon1, polyline2, sr, null); - assertTrue(res); - res = crosses.execute(polyline2, polygon1, sr, null); - assertTrue(res); - } - - @Test - public void testPolylinePolylineCrosses() { - OperatorCrosses crosses = (OperatorCrosses) (OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.Crosses)); - SpatialReference sr = SpatialReference.create(102100); - double tolerance = sr - .getTolerance(VertexDescription.Semantics.POSITION); - - // Polyline1 and Polyline2 touch at a point - String str1 = "{\"paths\":[[[0,0],[0,5],[0,10],[10,10],[10,0]],[[1,1],[9,1],[9,9],[1,9],[1,1],[1,1]]]}"; - String str2 = "{\"paths\":[[[10,10],[10,15],[15,15],[15,10]]]}"; - - Polyline polyline1 = (Polyline) (TestCommonMethods.fromJson(str1) - .getGeometry()); - Polyline polyline2 = (Polyline) (TestCommonMethods.fromJson(str2) - .getGeometry()); - - boolean res = crosses.execute(polyline1, polyline2, sr, null); - assertTrue(!res); - res = crosses.execute(polyline2, polyline1, sr, null); - assertTrue(!res); - - // Polyline1 and Polyline2 intersect at interiors - str1 = "{\"paths\":[[[0,0],[0,5],[0,10],[10,10],[10,0],[0,0]],[[1,1],[9,1],[9,9],[1,9],[1,1],[1,1]]]}"; - str2 = "{\"paths\":[[[15,5],[5,15],[15,15],[15,5]]]}"; - - polyline1 = (Polyline) (TestCommonMethods.fromJson(str1).getGeometry()); - polyline2 = (Polyline) (TestCommonMethods.fromJson(str2).getGeometry()); - - res = crosses.execute(polyline1, polyline2, sr, null); - assertTrue(res); - res = crosses.execute(polyline2, polyline1, sr, null); - assertTrue(res); - - // Polyline1 and Polyline2 touch at an endpoint of Polyline1 and - // interior of Polyline2 (but Polyline1 is closed) - str1 = "{\"paths\":[[[10,10],[10,0],[0,0],[0,10],[10,10]],[[1,1],[9,1],[9,9],[1,9],[1,1],[1,1]]]}"; - str2 = "{\"paths\":[[[15,5],[5,15],[15,15]]]}"; - - polyline1 = (Polyline) (TestCommonMethods.fromJson(str1).getGeometry()); - polyline2 = (Polyline) (TestCommonMethods.fromJson(str2).getGeometry()); - ; - - res = crosses.execute(polyline1, polyline2, sr, null); - assertTrue(res); - res = crosses.execute(polyline2, polyline1, sr, null); - assertTrue(res); - - // Polyline1 and Polyline2 touch at an endpoint of Polyline1 and - // interior of Polyline2 (same as previous case, but Polyline1 is not - // closed) - str1 = "{\"paths\":[[[10,10],[10,0],[0,0],[0,10]],[[1,1],[9,1],[9,9],[1,9],[1,1],[1,1]]]}"; - str2 = "{\"paths\":[[[15,5],[5,15],[15,15]]]}"; - - polyline1 = (Polyline) (TestCommonMethods.fromJson(str1).getGeometry()); - polyline2 = (Polyline) (TestCommonMethods.fromJson(str2).getGeometry()); - - res = crosses.execute(polyline1, polyline2, sr, null); - assertTrue(!res); - res = crosses.execute(polyline2, polyline1, sr, null); - assertTrue(!res); - - str1 = "{\"paths\":[[[10,11],[10,0],[0,0],[0,10]],[[1,1],[9,1],[9,9],[1,9],[6, 9]]]}"; - str2 = "{\"paths\":[[[15,5],[5,15],[15,15],[15,5]]]}"; - - polyline1 = (Polyline) (TestCommonMethods.fromJson(str1).getGeometry()); - polyline2 = (Polyline) (TestCommonMethods.fromJson(str2).getGeometry()); - - res = crosses.execute(polyline1, polyline2, sr, null); - assertTrue(res); - res = crosses.execute(polyline2, polyline1, sr, null); - assertTrue(res); - - polyline1.setEmpty(); - polyline2.setEmpty(); - - polyline1.startPath(-2, -2); - polyline1.lineTo(-1, -1); - polyline1.lineTo(1, 1); - polyline1.lineTo(2, 2); - - polyline2.startPath(-2, 2); - polyline2.lineTo(-1, 1); - polyline2.lineTo(1, -1); - polyline2.lineTo(2, -2); - - res = crosses.execute(polyline2, polyline1, sr, null); - assertTrue(res); - } - - @Test - public void testPolygonEnvelope() { - OperatorEquals equals = (OperatorEquals) (OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.Equals)); - OperatorContains contains = (OperatorContains) (OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.Contains)); - OperatorDisjoint disjoint = (OperatorDisjoint) (OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.Disjoint)); - OperatorCrosses crosses = (OperatorCrosses) (OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.Crosses)); - @SuppressWarnings("unused") - OperatorWithin within = (OperatorWithin) (OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.Within)); - OperatorOverlaps overlaps = (OperatorOverlaps) (OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.Overlaps)); - OperatorTouches touches = (OperatorTouches) (OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.Touches)); - OperatorDensifyByLength densify = (OperatorDensifyByLength) (OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.DensifyByLength)); - SpatialReference sr = SpatialReference.create(4326); - - { - Polygon polygon = (Polygon) (TestCommonMethods - .fromJson("{\"rings\":[[[0,0],[0,5],[0,10],[10,10],[10,0],[0,0]]]}") - .getGeometry()); - Polygon densified = (Polygon) (densify.execute(polygon, 1.0, null)); - Envelope envelope = (Envelope) (TestCommonMethods - .fromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":10}") - .getGeometry()); - wiggleGeometry(densified, 0.00000001, 1982); - wiggleGeometry(envelope, 0.00000001, 511); - assertTrue(equals.execute(envelope, densified, sr, null)); // they - // cover - // the - // same - // space - assertTrue(contains.execute(densified, envelope, sr, null)); - assertTrue(!disjoint.execute(envelope, densified, sr, null)); - assertTrue(!touches.execute(envelope, densified, sr, null)); - assertTrue(!overlaps.execute(envelope, densified, sr, null)); - assertTrue(!crosses.execute(envelope, densified, sr, null)); - } - - { - Polygon polygon = (Polygon) (TestCommonMethods - .fromJson("{\"rings\":[[[0,0],[0,5],[0,10],[10,10],[10,0],[0,0]]]}") - .getGeometry()); - Polygon densified = (Polygon) (densify.execute(polygon, 1.0, null)); - Envelope envelope = (Envelope) (TestCommonMethods - .fromJson("{\"xmin\":5,\"ymin\":0,\"xmax\":10,\"ymax\":10}") - .getGeometry()); - wiggleGeometry(densified, 0.00000001, 1982); - wiggleGeometry(envelope, 0.00000001, 511); - assertTrue(!equals.execute(envelope, densified, sr, null)); // the - // polygon - // contains - // the - // envelope, - // but - // they - // aren't - // equal - assertTrue(contains.execute(densified, envelope, sr, null)); - assertTrue(!disjoint.execute(envelope, densified, sr, null)); - assertTrue(!touches.execute(envelope, densified, sr, null)); - assertTrue(!overlaps.execute(envelope, densified, sr, null)); - assertTrue(!crosses.execute(envelope, densified, sr, null)); - } - - { - Polygon polygon = (Polygon) (TestCommonMethods - .fromJson("{\"rings\":[[[0,0],[0,5],[0,10],[10,10],[10,0],[0,0]]]}") - .getGeometry()); - Polygon densified = (Polygon) (densify.execute(polygon, 1.0, null)); - Envelope envelope = (Envelope) (TestCommonMethods - .fromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":15,\"ymax\":10}") - .getGeometry()); - wiggleGeometry(densified, 0.00000001, 1982); - wiggleGeometry(envelope, 0.00000001, 511); - assertTrue(!equals.execute(envelope, densified, sr, null)); // the - // envelope - // sticks - // outside - // of - // the - // polygon - // but - // they - // intersect - assertTrue(!contains.execute(densified, envelope, sr, null)); - assertTrue(!disjoint.execute(envelope, densified, sr, null)); - assertTrue(!touches.execute(envelope, densified, sr, null)); - assertTrue(!overlaps.execute(envelope, densified, sr, null)); - assertTrue(!crosses.execute(envelope, densified, sr, null)); - } - - { - Polygon polygon = (Polygon) (TestCommonMethods - .fromJson("{\"rings\":[[[0,0],[0,5],[0,10],[10,10],[10,0],[0,0]]]}") - .getGeometry()); - Polygon densified = (Polygon) (densify.execute(polygon, 1.0, null)); - Envelope envelope = (Envelope) (TestCommonMethods - .fromJson("{\"xmin\":5,\"ymin\":0,\"xmax\":15,\"ymax\":10}") - .getGeometry()); - wiggleGeometry(densified, 0.00000001, 1982); - wiggleGeometry(envelope, 0.00000001, 511); - assertTrue(!equals.execute(envelope, densified, sr, null)); // the - // envelope - // sticks - // outside - // of - // the - // polygon - // but - // they - // intersect - // and - // overlap - assertTrue(!contains.execute(densified, envelope, sr, null)); - assertTrue(!disjoint.execute(envelope, densified, sr, null)); - assertTrue(!touches.execute(envelope, densified, sr, null)); - assertTrue(overlaps.execute(envelope, densified, sr, null)); - assertTrue(!crosses.execute(envelope, densified, sr, null)); - } - - { - Polygon polygon = (Polygon) (TestCommonMethods - .fromJson("{\"rings\":[[[0,0],[0,5],[0,10],[10,10],[10,0],[0,0]]]}") - .getGeometry()); - Polygon densified = (Polygon) (densify.execute(polygon, 1.0, null)); - Envelope envelope = (Envelope) (TestCommonMethods - .fromJson("{\"xmin\":10,\"ymin\":0,\"xmax\":15,\"ymax\":5}") - .getGeometry()); - wiggleGeometry(densified, 0.00000001, 1982); - wiggleGeometry(envelope, 0.00000001, 511); - assertTrue(!equals.execute(envelope, densified, sr, null)); // the - // envelope - // rides - // the - // side - // of - // the - // polygon - // (they - // touch) - assertTrue(!contains.execute(densified, envelope, sr, null)); - assertTrue(!disjoint.execute(envelope, densified, sr, null)); - assertTrue(touches.execute(envelope, densified, sr, null)); - assertTrue(!overlaps.execute(envelope, densified, sr, null)); - assertTrue(!crosses.execute(envelope, densified, sr, null)); - } - - { - Polygon polygon = (Polygon) (TestCommonMethods - .fromJson("{\"rings\":[[[0,0],[0,5],[0,10],[10,10],[10,0],[0,0]]]}") - .getGeometry()); - Polygon densified = (Polygon) (densify.execute(polygon, 1.0, null)); - Envelope envelope = (Envelope) (TestCommonMethods - .fromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":10}") - .getGeometry()); - wiggleGeometry(densified, 0.00000001, 1982); - wiggleGeometry(envelope, 0.00000001, 511); - assertTrue(contains.execute(densified, envelope, sr, null)); // polygon - // and - // envelope - // cover - // the - // same - // space - assertTrue(!disjoint.execute(densified, envelope, sr, null)); - assertTrue(!touches.execute(envelope, densified, sr, null)); - assertTrue(!overlaps.execute(envelope, densified, sr, null)); - assertTrue(!crosses.execute(envelope, densified, sr, null)); - } - - { - Polygon polygon = (Polygon) (TestCommonMethods - .fromJson("{\"rings\":[[[0,0],[0,5],[0,10],[10,0],[0,0]]]}") - .getGeometry()); - Polygon densified = (Polygon) (densify.execute(polygon, 1.0, null)); - Envelope envelope = (Envelope) (TestCommonMethods - .fromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":10}") - .getGeometry()); - wiggleGeometry(densified, 0.00000001, 1982); - wiggleGeometry(envelope, 0.00000001, 511); - assertTrue(!equals.execute(envelope, densified, sr, null)); - assertTrue(!contains.execute(densified, envelope, sr, null)); // envelope - // sticks - // outside - // of - // polygon, - // but - // the - // envelopes - // are - // equal - assertTrue(!disjoint.execute(densified, envelope, sr, null)); - assertTrue(!touches.execute(envelope, densified, sr, null)); - assertTrue(!overlaps.execute(envelope, densified, sr, null)); - assertTrue(!crosses.execute(envelope, densified, sr, null)); - } - - { - Polygon polygon = (Polygon) (TestCommonMethods - .fromJson("{\"rings\":[[[0,0],[0,5],[0,10],[10,0],[0,0]]]}") - .getGeometry()); - Polygon densified = (Polygon) (densify.execute(polygon, 1.0, null)); - Envelope envelope = (Envelope) (TestCommonMethods - .fromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":15,\"ymax\":10}") - .getGeometry()); - wiggleGeometry(densified, 0.00000001, 1982); - wiggleGeometry(envelope, 0.00000001, 511); - assertTrue(!equals.execute(envelope, densified, sr, null)); - assertTrue(!contains.execute(densified, envelope, sr, null)); // the - // polygon - // envelope - // doesn't - // contain - // the - // envelope, - // but - // they - // intersect - assertTrue(!disjoint.execute(densified, envelope, sr, null)); - assertTrue(!touches.execute(envelope, densified, sr, null)); - assertTrue(!overlaps.execute(envelope, densified, sr, null)); - assertTrue(!crosses.execute(envelope, densified, sr, null)); - } - - { - Polygon polygon = (Polygon) (TestCommonMethods - .fromJson("{\"rings\":[[[0,0],[0,5],[0,10],[10,10],[10,0],[0,0]]]}") - .getGeometry()); - Polygon densified = (Polygon) (densify.execute(polygon, 1.0, null)); - Envelope envelope = (Envelope) (TestCommonMethods - .fromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":0,\"ymax\":0}") - .getGeometry()); - wiggleGeometry(densified, 0.00000001, 1982); - wiggleGeometry(envelope, 0.00000001, 511); - assertTrue(!equals.execute(envelope, densified, sr, null)); - assertTrue(!contains.execute(densified, envelope, sr, null)); // envelope - // degenerate - // to - // a - // point - // and - // is - // on - // border - // (i.e. - // touches) - assertTrue(!disjoint.execute(densified, envelope, sr, null)); - assertTrue(touches.execute(envelope, densified, sr, null)); - assertTrue(!overlaps.execute(envelope, densified, sr, null)); - assertTrue(!crosses.execute(envelope, densified, sr, null)); - } - - { - Polygon polygon = (Polygon) (TestCommonMethods - .fromJson("{\"rings\":[[[0,0],[0,5],[0,10],[10,10],[10,0],[0,0]]]}") - .getGeometry()); - Polygon densified = (Polygon) (densify.execute(polygon, 1.0, null)); - Envelope envelope = (Envelope) (TestCommonMethods - .fromJson("{\"xmin\":1,\"ymin\":1,\"xmax\":1,\"ymax\":1}") - .getGeometry()); - wiggleGeometry(densified, 0.00000001, 1982); - wiggleGeometry(envelope, 0.00000001, 511); - assertTrue(!equals.execute(envelope, densified, sr, null)); - assertTrue(contains.execute(densified, envelope, sr, null)); // envelope - // degenerate - // to - // a - // point - // and - // is - // properly - // inside - assertTrue(!disjoint.execute(densified, envelope, sr, null)); - assertTrue(!touches.execute(envelope, densified, sr, null)); - assertTrue(!overlaps.execute(envelope, densified, sr, null)); - assertTrue(!crosses.execute(envelope, densified, sr, null)); - } - - { - Polygon polygon = (Polygon) (TestCommonMethods - .fromJson("{\"rings\":[[[0,0],[0,5],[0,10],[10,10],[10,0],[0,0]]]}") - .getGeometry()); - Polygon densified = (Polygon) (densify.execute(polygon, 1.0, null)); - Envelope envelope = (Envelope) (TestCommonMethods - .fromJson("{\"xmin\":-1,\"ymin\":-1,\"xmax\":-1,\"ymax\":-1}") - .getGeometry()); - wiggleGeometry(densified, 0.00000001, 1982); - wiggleGeometry(envelope, 0.00000001, 511); - assertTrue(!equals.execute(envelope, densified, sr, null)); - assertTrue(!contains.execute(densified, envelope, sr, null)); // envelope - // degenerate - // to - // a - // point - // and - // is - // properly - // outside - assertTrue(disjoint.execute(densified, envelope, sr, null)); - assertTrue(!touches.execute(envelope, densified, sr, null)); - assertTrue(!overlaps.execute(envelope, densified, sr, null)); - assertTrue(!crosses.execute(envelope, densified, sr, null)); - } - - { - Polygon polygon = (Polygon) (TestCommonMethods - .fromJson("{\"rings\":[[[0,0],[0,5],[0,10],[10,10],[10,0],[0,0]]]}") - .getGeometry()); - Polygon densified = (Polygon) (densify.execute(polygon, 1.0, null)); - Envelope envelope = (Envelope) (TestCommonMethods - .fromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":1,\"ymax\":0}") - .getGeometry()); - wiggleGeometry(densified, 0.00000001, 1982); - wiggleGeometry(envelope, 0.00000001, 511); - assertTrue(!equals.execute(envelope, densified, sr, null)); - assertTrue(!contains.execute(densified, envelope, sr, null)); // envelope - // degenerate - // to - // a - // line - // and - // rides - // the - // bottom - // of - // the - // polygon - // (no - // interior - // intersection) - assertTrue(!disjoint.execute(densified, envelope, sr, null)); - assertTrue(touches.execute(envelope, densified, sr, null)); - assertTrue(!overlaps.execute(envelope, densified, sr, null)); - assertTrue(!crosses.execute(envelope, densified, sr, null)); - } - - { - Polygon polygon = (Polygon) (TestCommonMethods - .fromJson("{\"rings\":[[[0,0],[0,5],[0,10],[10,10],[10,0],[0,0]]]}") - .getGeometry()); - Polygon densified = (Polygon) (densify.execute(polygon, 1.0, null)); - Envelope envelope = (Envelope) (TestCommonMethods - .fromJson("{\"xmin\":0,\"ymin\":1,\"xmax\":1,\"ymax\":1}") - .getGeometry()); - wiggleGeometry(densified, 0.00000001, 1982); - wiggleGeometry(envelope, 0.00000001, 511); - assertTrue(!equals.execute(envelope, densified, sr, null)); - assertTrue(contains.execute(densified, envelope, sr, null)); // envelope - // degenerate - // to - // a - // line, - // touches - // the - // border - // on - // the - // inside - // yet - // has - // interior - // intersection - assertTrue(!disjoint.execute(densified, envelope, sr, null)); - assertTrue(!touches.execute(envelope, densified, sr, null)); - assertTrue(!overlaps.execute(envelope, densified, sr, null)); - assertTrue(!crosses.execute(envelope, densified, sr, null)); - } - - { - Polygon polygon = (Polygon) (TestCommonMethods - .fromJson("{\"rings\":[[[0,0],[0,5],[0,10],[10,0],[0,0]]]}") - .getGeometry()); - Polygon densified = (Polygon) (densify.execute(polygon, 1.0, null)); - Envelope envelope = (Envelope) (TestCommonMethods - .fromJson("{\"xmin\":5,\"ymin\":5,\"xmax\":6,\"ymax\":5}") - .getGeometry()); - wiggleGeometry(densified, 0.00000001, 1982); - wiggleGeometry(envelope, 0.00000001, 511); - assertTrue(!equals.execute(envelope, densified, sr, null)); - assertTrue(!contains.execute(densified, envelope, sr, null)); // envelope - // degenerate - // to - // a - // line, - // touches - // the - // boundary, - // and - // is - // outside - assertTrue(!disjoint.execute(densified, envelope, sr, null)); - assertTrue(touches.execute(envelope, densified, sr, null)); - assertTrue(!overlaps.execute(envelope, densified, sr, null)); - assertTrue(!crosses.execute(envelope, densified, sr, null)); - } - - { - Polygon polygon = (Polygon) (TestCommonMethods - .fromJson("{\"rings\":[[[0,0],[0,5],[0,10],[10,0],[0,0]]]}") - .getGeometry()); - Polygon densified = (Polygon) (densify.execute(polygon, 1.0, null)); - Envelope envelope = (Envelope) (TestCommonMethods - .fromJson("{\"xmin\":6,\"ymin\":5,\"xmax\":7,\"ymax\":5}") - .getGeometry()); - wiggleGeometry(densified, 0.00000001, 1982); - wiggleGeometry(envelope, 0.00000001, 511); - assertTrue(!equals.execute(envelope, densified, sr, null)); - assertTrue(!contains.execute(densified, envelope, sr, null)); // envelope - // degenerate - // to - // a - // line, - // and - // is - // outside - assertTrue(disjoint.execute(densified, envelope, sr, null)); - assertTrue(!touches.execute(envelope, densified, sr, null)); - assertTrue(!overlaps.execute(envelope, densified, sr, null)); - assertTrue(!crosses.execute(envelope, densified, sr, null)); - } - - { - Polygon polygon = (Polygon) (TestCommonMethods - .fromJson("{\"rings\":[[[0,0],[0,5],[0,10],[10,0],[0,0]]]}") - .getGeometry()); - Polygon densified = (Polygon) (densify.execute(polygon, 1.0, null)); - Envelope envelope = (Envelope) (TestCommonMethods - .fromJson("{\"xmin\":4,\"ymin\":5,\"xmax\":7,\"ymax\":5}") - .getGeometry()); - wiggleGeometry(densified, 0.00000001, 1982); - wiggleGeometry(envelope, 0.00000001, 511); - assertTrue(!equals.execute(envelope, densified, sr, null)); - assertTrue(!contains.execute(densified, envelope, sr, null)); // envelope - // degenerate - // to - // a - // line, - // and - // crosses - // polygon - assertTrue(!disjoint.execute(densified, envelope, sr, null)); - assertTrue(!touches.execute(envelope, densified, sr, null)); - assertTrue(!overlaps.execute(envelope, densified, sr, null)); - assertTrue(crosses.execute(envelope, densified, sr, null)); - } - } - - @Test - public void testPolylineEnvelope() { - OperatorEquals equals = (OperatorEquals) (OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.Equals)); - OperatorContains contains = (OperatorContains) (OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.Contains)); - OperatorDisjoint disjoint = (OperatorDisjoint) (OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.Disjoint)); - OperatorCrosses crosses = (OperatorCrosses) (OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.Crosses)); - @SuppressWarnings("unused") - OperatorWithin within = (OperatorWithin) (OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.Within)); - OperatorOverlaps overlaps = (OperatorOverlaps) (OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.Overlaps)); - OperatorTouches touches = (OperatorTouches) (OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.Touches)); - OperatorDensifyByLength densify = (OperatorDensifyByLength) (OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.DensifyByLength)); - - SpatialReference sr = SpatialReference.create(4326); - - { - Polyline polyline = (Polyline) (TestCommonMethods - .fromJson("{\"paths\":[[[0,0],[0,5],[0,10],[10,10],[10,0]]]}") - .getGeometry()); - Polyline densified = (Polyline) (densify.execute(polyline, 1.0, - null)); - Envelope envelope = (Envelope) (TestCommonMethods - .fromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":10}") - .getGeometry()); - wiggleGeometry(densified, 0.00000001, 1982); - wiggleGeometry(envelope, 0.00000001, 511); - assertTrue(!equals.execute(envelope, densified, sr, null)); // polyline - // straddles - // the - // envelope - // like - // a hat - assertTrue(!contains.execute(densified, envelope, sr, null)); - assertTrue(!disjoint.execute(envelope, densified, sr, null)); - assertTrue(touches.execute(envelope, densified, sr, null)); - assertTrue(!overlaps.execute(envelope, densified, sr, null)); - assertTrue(!crosses.execute(envelope, densified, sr, null)); - } - - { - Polyline polyline = (Polyline) (TestCommonMethods - .fromJson("{\"paths\":[[[-10,0],[0,10]]]}").getGeometry()); - Polyline densified = (Polyline) (densify.execute(polyline, 1.0, - null)); - Envelope envelope = (Envelope) (TestCommonMethods - .fromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":10}") - .getGeometry()); - wiggleGeometry(densified, 0.00000001, 1982); - wiggleGeometry(envelope, 0.00000001, 511); - assertTrue(!equals.execute(envelope, densified, sr, null)); - assertTrue(!contains.execute(densified, envelope, sr, null)); - assertTrue(!disjoint.execute(envelope, densified, sr, null)); - assertTrue(touches.execute(envelope, densified, sr, null)); - assertTrue(touches.execute(densified, envelope, sr, null)); - assertTrue(!overlaps.execute(envelope, densified, sr, null)); - assertTrue(!crosses.execute(envelope, densified, sr, null)); - } - - { - Polyline polyline = (Polyline) (TestCommonMethods - .fromJson("{\"paths\":[[[-11,0],[1,12]]]}").getGeometry()); - Polyline densified = (Polyline) (densify.execute(polyline, 1.0, - null)); - Envelope envelope = (Envelope) (TestCommonMethods - .fromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":10}") - .getGeometry()); - wiggleGeometry(densified, 0.00000001, 1982); - wiggleGeometry(envelope, 0.00000001, 511); - assertTrue(!equals.execute(envelope, densified, sr, null)); - assertTrue(!contains.execute(densified, envelope, sr, null)); - assertTrue(disjoint.execute(envelope, densified, sr, null)); - assertTrue(!touches.execute(envelope, densified, sr, null)); - assertTrue(!touches.execute(densified, envelope, sr, null)); - assertTrue(!overlaps.execute(envelope, densified, sr, null)); - assertTrue(!crosses.execute(envelope, densified, sr, null)); - } - - { - Polyline polyline = (Polyline) (TestCommonMethods - .fromJson("{\"paths\":[[[5,5],[6,6]]]}").getGeometry()); - Polyline densified = (Polyline) (densify.execute(polyline, 1.0, - null)); - Envelope envelope = (Envelope) (TestCommonMethods - .fromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":10}") - .getGeometry()); - wiggleGeometry(densified, 0.00000001, 1982); - wiggleGeometry(envelope, 0.00000001, 511); - assertTrue(!equals.execute(envelope, densified, sr, null)); // polyline - // properly - // inside - assertTrue(contains.execute(envelope, densified, sr, null)); - assertTrue(!disjoint.execute(envelope, densified, sr, null)); - assertTrue(!touches.execute(envelope, densified, sr, null)); - assertTrue(!overlaps.execute(envelope, densified, sr, null)); - assertTrue(!crosses.execute(envelope, densified, sr, null)); - } - - { - Polyline polyline = (Polyline) (TestCommonMethods - .fromJson("{\"paths\":[[[5,5],[10,10]]]}").getGeometry()); - Polyline densified = (Polyline) (densify.execute(polyline, 1.0, - null)); - Envelope envelope = (Envelope) (TestCommonMethods - .fromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":10}") - .getGeometry()); - wiggleGeometry(densified, 0.00000001, 1982); - wiggleGeometry(envelope, 0.00000001, 511); - assertTrue(!equals.execute(envelope, densified, sr, null)); - assertTrue(contains.execute(envelope, densified, sr, null)); - assertTrue(!disjoint.execute(envelope, densified, sr, null)); - assertTrue(!touches.execute(envelope, densified, sr, null)); - assertTrue(!overlaps.execute(envelope, densified, sr, null)); - assertTrue(!crosses.execute(envelope, densified, sr, null)); - } - - { - Polyline polyline = (Polyline) (TestCommonMethods - .fromJson("{\"paths\":[[[-5,5],[15,5]]]}").getGeometry()); - Polyline densified = (Polyline) (densify.execute(polyline, 1.0, - null)); - Envelope envelope = (Envelope) (TestCommonMethods - .fromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":10}") - .getGeometry()); - wiggleGeometry(densified, 0.00000001, 1982); - wiggleGeometry(envelope, 0.00000001, 511); - assertTrue(!equals.execute(envelope, densified, sr, null)); - assertTrue(!contains.execute(envelope, densified, sr, null)); - assertTrue(!disjoint.execute(envelope, densified, sr, null)); - assertTrue(!touches.execute(envelope, densified, sr, null)); - assertTrue(!overlaps.execute(envelope, densified, sr, null)); - assertTrue(crosses.execute(envelope, densified, sr, null)); - assertTrue(crosses.execute(densified, envelope, sr, null)); - } - - { - Polyline polyline = (Polyline) (TestCommonMethods - .fromJson("{\"paths\":[[[5,5],[5,15]]]}").getGeometry()); - Polyline densified = (Polyline) (densify.execute(polyline, 1.0, - null)); - Envelope envelope = (Envelope) (TestCommonMethods - .fromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":10}") - .getGeometry()); - wiggleGeometry(densified, 0.00000001, 1982); - wiggleGeometry(envelope, 0.00000001, 511); - assertTrue(!equals.execute(envelope, densified, sr, null)); // polyline - // slices - // through - // the - // envelope - // (interior - // and - // exterior - // intersection) - assertTrue(!contains.execute(densified, envelope, sr, null)); - assertTrue(!disjoint.execute(envelope, densified, sr, null)); - assertTrue(!touches.execute(envelope, densified, sr, null)); - assertTrue(!overlaps.execute(envelope, densified, sr, null)); - assertTrue(crosses.execute(envelope, densified, sr, null)); - } - - { - Polyline polyline = (Polyline) (TestCommonMethods - .fromJson("{\"paths\":[[[5,11],[5,15]]]}").getGeometry()); - Polyline densified = (Polyline) (densify.execute(polyline, 1.0, - null)); - Envelope envelope = (Envelope) (TestCommonMethods - .fromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":10}") - .getGeometry()); - wiggleGeometry(densified, 0.00000001, 1982); - wiggleGeometry(envelope, 0.00000001, 511); - assertTrue(!equals.execute(envelope, densified, sr, null)); // polyline - // outside - // of - // envelope - assertTrue(!contains.execute(densified, envelope, sr, null)); - assertTrue(disjoint.execute(envelope, densified, sr, null)); - assertTrue(!touches.execute(envelope, densified, sr, null)); - assertTrue(!overlaps.execute(envelope, densified, sr, null)); - assertTrue(!crosses.execute(envelope, densified, sr, null)); - } - - { - Polyline polyline = (Polyline) (TestCommonMethods - .fromJson("{\"paths\":[[[0,0],[0,5],[0,10],[10,10],[10,0]]]}") - .getGeometry()); - Polyline densified = (Polyline) (densify.execute(polyline, 1.0, - null)); - Envelope envelope = (Envelope) (TestCommonMethods - .fromJson("{\"xmin\":0,\"ymin\":10,\"xmax\":10,\"ymax\":10}") - .getGeometry()); - wiggleGeometry(densified, 0.00000001, 1982); - wiggleGeometry(envelope, 0.00000001, 511); - assertTrue(!equals.execute(envelope, densified, sr, null)); // polyline - // straddles - // the - // degenerate - // envelope - // like - // a hat - assertTrue(contains.execute(densified, envelope, sr, null)); - assertTrue(!disjoint.execute(envelope, densified, sr, null)); - assertTrue(!touches.execute(envelope, densified, sr, null)); - assertTrue(!overlaps.execute(envelope, densified, sr, null)); - assertTrue(!crosses.execute(envelope, densified, sr, null)); - } - - { - Polyline polyline = (Polyline) (TestCommonMethods - .fromJson("{\"paths\":[[[0,0],[0,5],[0,10],[10,10],[10,0]]]}") - .getGeometry()); - Polyline densified = (Polyline) (densify.execute(polyline, 1.0, - null)); - Envelope envelope = (Envelope) (TestCommonMethods - .fromJson("{\"xmin\":0,\"ymin\":-5,\"xmax\":0,\"ymax\":10}") - .getGeometry()); - wiggleGeometry(densified, 0.00000001, 1982); - wiggleGeometry(envelope, 0.00000001, 511); - assertTrue(!equals.execute(envelope, densified, sr, null)); - assertTrue(!contains.execute(densified, envelope, sr, null)); - assertTrue(!disjoint.execute(envelope, densified, sr, null)); - assertTrue(!touches.execute(envelope, densified, sr, null)); - assertTrue(overlaps.execute(envelope, densified, sr, null)); - assertTrue(!crosses.execute(envelope, densified, sr, null)); - } - - { - Polyline polyline = (Polyline) (TestCommonMethods - .fromJson("{\"paths\":[[[0,0],[0,5],[0,10],[10,10],[10,0]]]}") - .getGeometry()); - Polyline densified = (Polyline) (densify.execute(polyline, 1.0, - null)); - Envelope envelope = (Envelope) (TestCommonMethods - .fromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":0,\"ymax\":0}") - .getGeometry()); - wiggleGeometry(densified, 0.00000001, 511); - wiggleGeometry(envelope, 0.00000001, 1982); - assertTrue(!equals.execute(envelope, densified, sr, null)); // degenerate - // envelope - // is at - // the - // end - // point - // of - // polyline - assertTrue(!contains.execute(densified, envelope, sr, null)); - assertTrue(!disjoint.execute(envelope, densified, sr, null)); - assertTrue(touches.execute(envelope, densified, sr, null)); - assertTrue(!overlaps.execute(envelope, densified, sr, null)); - assertTrue(!crosses.execute(envelope, densified, sr, null)); - } - - { - Polyline polyline = (Polyline) (TestCommonMethods - .fromJson("{\"paths\":[[[0,0],[0,5],[0,10],[10,10],[10,0]]]}") - .getGeometry()); - Polyline densified = (Polyline) (densify.execute(polyline, 1.0, - null)); - Envelope envelope = (Envelope) (TestCommonMethods - .fromJson("{\"xmin\":0,\"ymin\":5,\"xmax\":0,\"ymax\":5}") - .getGeometry()); - wiggleGeometry(densified, 0.00000001, 1982); - wiggleGeometry(envelope, 0.00000001, 511); - assertTrue(!equals.execute(envelope, densified, sr, null)); // degenerate - // envelope - // is at - // the - // interior - // of - // polyline - assertTrue(contains.execute(densified, envelope, sr, null)); - assertTrue(!disjoint.execute(envelope, densified, sr, null)); - assertTrue(!touches.execute(envelope, densified, sr, null)); - assertTrue(!overlaps.execute(envelope, densified, sr, null)); - assertTrue(!crosses.execute(envelope, densified, sr, null)); - } - - { - Polyline polyline = (Polyline) (TestCommonMethods - .fromJson("{\"paths\":[[[2,-2],[2,2]]]}").getGeometry()); - Polyline densified = (Polyline) (densify.execute(polyline, 1.0, - null)); - Envelope envelope = (Envelope) (TestCommonMethods - .fromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":5,\"ymax\":0}") - .getGeometry()); - wiggleGeometry(densified, 0.00000001, 1982); - wiggleGeometry(envelope, 0.00000001, 511); - assertTrue(!equals.execute(envelope, densified, sr, null)); // degenerate - // envelope - // crosses - // polyline - assertTrue(!contains.execute(densified, envelope, sr, null)); - assertTrue(!disjoint.execute(envelope, densified, sr, null)); - assertTrue(!touches.execute(envelope, densified, sr, null)); - assertTrue(!overlaps.execute(envelope, densified, sr, null)); - assertTrue(crosses.execute(envelope, densified, sr, null)); - } - - { - Polyline polyline = (Polyline) (TestCommonMethods - .fromJson("{\"paths\":[[[2,0],[2,2]]]}").getGeometry()); - Polyline densified = (Polyline) (densify.execute(polyline, 1.0, - null)); - Envelope envelope = (Envelope) (TestCommonMethods - .fromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":5,\"ymax\":0}") - .getGeometry()); - wiggleGeometry(densified, 0.00000001, 1982); - wiggleGeometry(envelope, 0.00000001, 511); - assertTrue(!equals.execute(envelope, densified, sr, null)); // degenerate - // envelope - // crosses - // polyline - assertTrue(!contains.execute(densified, envelope, sr, null)); - assertTrue(!disjoint.execute(envelope, densified, sr, null)); - assertTrue(touches.execute(envelope, densified, sr, null)); - assertTrue(!overlaps.execute(envelope, densified, sr, null)); - assertTrue(!crosses.execute(envelope, densified, sr, null)); - } - - { - Polyline polyline = (Polyline) (TestCommonMethods - .fromJson("{\"paths\":[[[2,0],[2,2]]]}").getGeometry()); - Polyline densified = (Polyline) (densify.execute(polyline, 1.0, - null)); - Envelope envelope = (Envelope) (TestCommonMethods - .fromJson("{\"xmin\":2,\"ymin\":0,\"xmax\":2,\"ymax\":3}") - .getGeometry()); - wiggleGeometry(densified, 0.00000001, 1982); - wiggleGeometry(envelope, 0.00000001, 511); - assertTrue(!equals.execute(envelope, densified, sr, null)); // degenerate - // envelope - // contains - // polyline - assertTrue(!contains.execute(densified, envelope, sr, null)); - assertTrue(contains.execute(envelope, densified, sr, null)); - assertTrue(!disjoint.execute(envelope, densified, sr, null)); - assertTrue(!touches.execute(envelope, densified, sr, null)); - assertTrue(!overlaps.execute(envelope, densified, sr, null)); - assertTrue(!crosses.execute(envelope, densified, sr, null)); - } - - { - Polyline polyline = (Polyline) (TestCommonMethods - .fromJson("{\"paths\":[[[5,5],[6,6]]]}").getGeometry()); - Polyline densified = (Polyline) (densify.execute(polyline, 1.0, - null)); - Envelope envelope = (Envelope) (TestCommonMethods - .fromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":5,\"ymax\":5}") - .getGeometry()); - wiggleGeometry(densified, 0.00000001, 1982); - wiggleGeometry(envelope, 0.00000001, 511); - assertTrue(!equals.execute(envelope, densified, sr, null)); // polyline - // properly - // inside - assertTrue(!contains.execute(envelope, densified, sr, null)); - assertTrue(!disjoint.execute(envelope, densified, sr, null)); - assertTrue(touches.execute(envelope, densified, sr, null)); - assertTrue(!overlaps.execute(envelope, densified, sr, null)); - assertTrue(!crosses.execute(envelope, densified, sr, null)); - } - - { - Polyline polyline = (Polyline) (TestCommonMethods - .fromJson("{\"paths\":[[[5,5],[5,10]]]}").getGeometry()); - Polyline densified = (Polyline) (densify.execute(polyline, 1.0, - null)); - Envelope envelope = (Envelope) (TestCommonMethods - .fromJson("{\"xmin\":5,\"ymin\":5,\"xmax\":5,\"ymax\":10}") - .getGeometry()); - wiggleGeometry(densified, 0.00000001, 1982); - wiggleGeometry(envelope, 0.00000001, 511); - assertTrue(equals.execute(envelope, densified, sr, null)); // polyline - // properly - // inside - assertTrue(contains.execute(envelope, densified, sr, null)); - assertTrue(!disjoint.execute(envelope, densified, sr, null)); - assertTrue(!touches.execute(envelope, densified, sr, null)); - assertTrue(!overlaps.execute(envelope, densified, sr, null)); - assertTrue(!crosses.execute(envelope, densified, sr, null)); - } - } - - @Test - public void testMultiPointEnvelope() { - OperatorEquals equals = (OperatorEquals) (OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.Equals)); - OperatorContains contains = (OperatorContains) (OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.Contains)); - OperatorDisjoint disjoint = (OperatorDisjoint) (OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.Disjoint)); - OperatorCrosses crosses = (OperatorCrosses) (OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.Crosses)); - @SuppressWarnings("unused") - OperatorWithin within = (OperatorWithin) (OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.Within)); - OperatorOverlaps overlaps = (OperatorOverlaps) (OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.Overlaps)); - OperatorTouches touches = (OperatorTouches) (OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.Touches)); - @SuppressWarnings("unused") - OperatorDensifyByLength densify = (OperatorDensifyByLength) (OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.DensifyByLength)); - - SpatialReference sr = SpatialReference.create(4326); - - { - MultiPoint multi_point = (MultiPoint) (TestCommonMethods - .fromJson("{\"points\":[[0,0],[0,10],[10,10],[10,0]]}") - .getGeometry()); - Envelope envelope = (Envelope) (TestCommonMethods - .fromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":10}") - .getGeometry()); - wiggleGeometry(multi_point, 0.00000001, 1982); - wiggleGeometry(envelope, 0.00000001, 511); - assertTrue(!equals.execute(envelope, multi_point, sr, null)); // all - // points - // on - // boundary - assertTrue(!contains.execute(envelope, multi_point, sr, null)); - assertTrue(!disjoint.execute(envelope, multi_point, sr, null)); - assertTrue(touches.execute(envelope, multi_point, sr, null)); - assertTrue(!overlaps.execute(envelope, multi_point, sr, null)); - assertTrue(!crosses.execute(envelope, multi_point, sr, null)); - } - - { - MultiPoint multi_point = (MultiPoint) (TestCommonMethods - .fromJson("{\"points\":[[0,0],[0,10],[10,10],[5,5]]}") - .getGeometry()); - Envelope envelope = (Envelope) (TestCommonMethods - .fromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":10}") - .getGeometry()); - wiggleGeometry(multi_point, 0.00000001, 1982); - wiggleGeometry(envelope, 0.00000001, 511); - assertTrue(!equals.execute(envelope, multi_point, sr, null)); // points - // on - // boundary - // and - // one - // point - // in - // interior - assertTrue(contains.execute(envelope, multi_point, sr, null)); - assertTrue(!disjoint.execute(envelope, multi_point, sr, null)); - assertTrue(!touches.execute(envelope, multi_point, sr, null)); - assertTrue(!overlaps.execute(envelope, multi_point, sr, null)); - assertTrue(!crosses.execute(envelope, multi_point, sr, null)); - } - - { - MultiPoint multi_point = (MultiPoint) (TestCommonMethods - .fromJson("{\"points\":[[0,0],[0,10],[10,10],[5,5],[15,15]]}") - .getGeometry()); - Envelope envelope = (Envelope) (TestCommonMethods - .fromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":10}") - .getGeometry()); - wiggleGeometry(multi_point, 0.00000001, 1982); - wiggleGeometry(envelope, 0.00000001, 511); - assertTrue(!equals.execute(envelope, multi_point, sr, null)); // points - // on - // boundary, - // one - // interior, - // one - // exterior - assertTrue(!contains.execute(envelope, multi_point, sr, null)); - assertTrue(!disjoint.execute(envelope, multi_point, sr, null)); - assertTrue(!touches.execute(envelope, multi_point, sr, null)); - assertTrue(!overlaps.execute(envelope, multi_point, sr, null)); - assertTrue(crosses.execute(envelope, multi_point, sr, null)); - } - - { - MultiPoint multi_point = (MultiPoint) (TestCommonMethods - .fromJson("{\"points\":[[0,0],[0,10],[10,10],[15,15]]}") - .getGeometry()); - Envelope envelope = (Envelope) (TestCommonMethods - .fromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":10}") - .getGeometry()); - wiggleGeometry(multi_point, 0.00000001, 1982); - wiggleGeometry(envelope, 0.00000001, 511); - assertTrue(!equals.execute(envelope, multi_point, sr, null)); // points - // on - // boundary, - // one - // exterior - assertTrue(!contains.execute(envelope, multi_point, sr, null)); - assertTrue(!disjoint.execute(envelope, multi_point, sr, null)); - assertTrue(touches.execute(envelope, multi_point, sr, null)); - assertTrue(!overlaps.execute(envelope, multi_point, sr, null)); - assertTrue(!crosses.execute(envelope, multi_point, sr, null)); - } - - { - MultiPoint multi_point = (MultiPoint) (TestCommonMethods - .fromJson("{\"points\":[[0,-1],[0,11],[11,11],[15,15]]}") - .getGeometry()); - Envelope envelope = (Envelope) (TestCommonMethods - .fromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":10}") - .getGeometry()); - wiggleGeometry(multi_point, 0.00000001, 1982); - wiggleGeometry(envelope, 0.00000001, 511); - assertTrue(!equals.execute(envelope, multi_point, sr, null)); // all - // points - // exterior - assertTrue(!contains.execute(envelope, multi_point, sr, null)); - assertTrue(disjoint.execute(envelope, multi_point, sr, null)); - assertTrue(!touches.execute(envelope, multi_point, sr, null)); - assertTrue(!overlaps.execute(envelope, multi_point, sr, null)); - assertTrue(!crosses.execute(envelope, multi_point, sr, null)); - } - - { - MultiPoint multi_point = (MultiPoint) (TestCommonMethods - .fromJson("{\"points\":[[0,0],[0,10],[10,10],[10,0]]}") - .getGeometry()); - Envelope envelope = (Envelope) (TestCommonMethods - .fromJson("{\"xmin\":0,\"ymin\":10,\"xmax\":10,\"ymax\":10}") - .getGeometry()); - wiggleGeometry(multi_point, 0.00000001, 1982); - wiggleGeometry(envelope, 0.00000001, 511); - assertTrue(!equals.execute(envelope, multi_point, sr, null)); // degenerate - // envelope - // slices - // through - // some - // points, - // but - // some - // points - // are - // off - // the - // line - assertTrue(!contains.execute(envelope, multi_point, sr, null)); - assertTrue(!disjoint.execute(envelope, multi_point, sr, null)); - assertTrue(touches.execute(envelope, multi_point, sr, null)); - assertTrue(!overlaps.execute(envelope, multi_point, sr, null)); - assertTrue(!crosses.execute(envelope, multi_point, sr, null)); - } - - { - MultiPoint multi_point = (MultiPoint) (TestCommonMethods - .fromJson("{\"points\":[[0,0],[1,10],[10,10],[10,0]]}") - .getGeometry()); - Envelope envelope = (Envelope) (TestCommonMethods - .fromJson("{\"xmin\":0,\"ymin\":10,\"xmax\":10,\"ymax\":10}") - .getGeometry()); - wiggleGeometry(multi_point, 0.00000001, 1982); - wiggleGeometry(envelope, 0.00000001, 511); - assertTrue(!equals.execute(envelope, multi_point, sr, null)); // degenerate - // envelope - // slices - // through - // some - // points, - // but - // some - // points - // are - // off - // the - // line - assertTrue(!contains.execute(envelope, multi_point, sr, null)); - assertTrue(!disjoint.execute(envelope, multi_point, sr, null)); - assertTrue(!touches.execute(envelope, multi_point, sr, null)); - assertTrue(!overlaps.execute(envelope, multi_point, sr, null)); - assertTrue(crosses.execute(envelope, multi_point, sr, null)); - } - - { - MultiPoint multi_point = (MultiPoint) (TestCommonMethods - .fromJson("{\"points\":[[0,10],[10,10]]}").getGeometry()); - Envelope envelope = (Envelope) (TestCommonMethods - .fromJson("{\"xmin\":0,\"ymin\":10,\"xmax\":10,\"ymax\":10}") - .getGeometry()); - wiggleGeometry(multi_point, 0.00000001, 1982); - wiggleGeometry(envelope, 0.00000001, 511); - assertTrue(!equals.execute(envelope, multi_point, sr, null)); // degenerate - // envelopes - // slices - // through - // all - // the - // points, - // and - // they - // are - // at - // the - // end - // points - // of - // the - // line - assertTrue(!contains.execute(envelope, multi_point, sr, null)); - assertTrue(!disjoint.execute(envelope, multi_point, sr, null)); - assertTrue(touches.execute(envelope, multi_point, sr, null)); - assertTrue(!overlaps.execute(envelope, multi_point, sr, null)); - assertTrue(!crosses.execute(envelope, multi_point, sr, null)); - } - - { - MultiPoint multi_point = (MultiPoint) (TestCommonMethods - .fromJson("{\"points\":[[1,10],[9,10]]}").getGeometry()); - Envelope envelope = (Envelope) (TestCommonMethods - .fromJson("{\"xmin\":0,\"ymin\":10,\"xmax\":10,\"ymax\":10}") - .getGeometry()); - wiggleGeometry(multi_point, 0.00000001, 1982); - wiggleGeometry(envelope, 0.00000001, 511); - assertTrue(!equals.execute(envelope, multi_point, sr, null)); // degenerate - // envelopes - // slices - // through - // all - // the - // points, - // and - // they - // are - // in - // the - // interior - // of - // the - // line - assertTrue(contains.execute(envelope, multi_point, sr, null)); - assertTrue(!disjoint.execute(envelope, multi_point, sr, null)); - assertTrue(!touches.execute(envelope, multi_point, sr, null)); - assertTrue(!overlaps.execute(envelope, multi_point, sr, null)); - assertTrue(!crosses.execute(envelope, multi_point, sr, null)); - } - - { - MultiPoint multi_point = (MultiPoint) (TestCommonMethods - .fromJson("{\"points\":[[0,-1],[0,11],[11,11],[15,15]]}") - .getGeometry()); - Envelope envelope = (Envelope) (TestCommonMethods - .fromJson("{\"xmin\":0,\"ymin\":10,\"xmax\":10,\"ymax\":10}") - .getGeometry()); - wiggleGeometry(multi_point, 0.00000001, 1982); - wiggleGeometry(envelope, 0.00000001, 511); - assertTrue(!equals.execute(envelope, multi_point, sr, null)); // all - // points - // exterior - assertTrue(!contains.execute(envelope, multi_point, sr, null)); - assertTrue(disjoint.execute(envelope, multi_point, sr, null)); - assertTrue(!touches.execute(envelope, multi_point, sr, null)); - assertTrue(!overlaps.execute(envelope, multi_point, sr, null)); - assertTrue(!crosses.execute(envelope, multi_point, sr, null)); - } - - { - MultiPoint multi_point = (MultiPoint) (TestCommonMethods - .fromJson("{\"points\":[[0,-1],[0,11],[11,11],[15,15]]}") - .getGeometry()); - Envelope envelope = (Envelope) (TestCommonMethods - .fromJson("{\"xmin\":10,\"ymin\":10,\"xmax\":10,\"ymax\":10}") - .getGeometry()); - wiggleGeometry(multi_point, 0.00000001, 1982); - wiggleGeometry(envelope, 0.00000001, 511); - assertTrue(!equals.execute(envelope, multi_point, sr, null)); // all - // points - // exterior - assertTrue(!contains.execute(envelope, multi_point, sr, null)); - assertTrue(disjoint.execute(envelope, multi_point, sr, null)); - assertTrue(!touches.execute(envelope, multi_point, sr, null)); - assertTrue(!overlaps.execute(envelope, multi_point, sr, null)); - assertTrue(!crosses.execute(envelope, multi_point, sr, null)); - } - - { - MultiPoint multi_point = (MultiPoint) (TestCommonMethods - .fromJson("{\"points\":[[0,-1],[0,11],[11,11],[15,15]]}") - .getGeometry()); - Envelope envelope = (Envelope) (TestCommonMethods - .fromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":11,\"ymax\":11}") - .getGeometry()); - wiggleGeometry(multi_point, 0.00000001, 1982); - wiggleGeometry(envelope, 0.00000001, 511); - assertTrue(!equals.execute(envelope, multi_point, sr, null)); // all - // points - // exterior - assertTrue(!contains.execute(multi_point, envelope, sr, null)); - assertTrue(!contains.execute(envelope, multi_point, sr, null)); - assertTrue(!disjoint.execute(envelope, multi_point, sr, null)); - assertTrue(touches.execute(envelope, multi_point, sr, null)); - assertTrue(!overlaps.execute(envelope, multi_point, sr, null)); - assertTrue(!crosses.execute(envelope, multi_point, sr, null)); - } - - { - MultiPoint multi_point = (MultiPoint) (TestCommonMethods - .fromJson("{\"points\":[[0,-1],[0,-1]]}").getGeometry()); - Envelope envelope = (Envelope) (TestCommonMethods - .fromJson("{\"xmin\":0,\"ymin\":-1,\"xmax\":0,\"ymax\":-1}") - .getGeometry()); - wiggleGeometry(multi_point, 0.00000001, 1982); - wiggleGeometry(envelope, 0.00000001, 511); - assertTrue(equals.execute(envelope, multi_point, sr, null)); // all - // points - // exterior - assertTrue(contains.execute(multi_point, envelope, sr, null)); - assertTrue(contains.execute(envelope, multi_point, sr, null)); - assertTrue(!disjoint.execute(envelope, multi_point, sr, null)); - assertTrue(!touches.execute(envelope, multi_point, sr, null)); - assertTrue(!overlaps.execute(envelope, multi_point, sr, null)); - assertTrue(!crosses.execute(envelope, multi_point, sr, null)); - } - } - - @Test - public void testPointEnvelope() { - OperatorEquals equals = (OperatorEquals) (OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.Equals)); - OperatorContains contains = (OperatorContains) (OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.Contains)); - OperatorDisjoint disjoint = (OperatorDisjoint) (OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.Disjoint)); - OperatorCrosses crosses = (OperatorCrosses) (OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.Crosses)); - @SuppressWarnings("unused") - OperatorWithin within = (OperatorWithin) (OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.Within)); - OperatorOverlaps overlaps = (OperatorOverlaps) (OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.Overlaps)); - OperatorTouches touches = (OperatorTouches) (OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.Touches)); - SpatialReference sr = SpatialReference.create(4326); - - { - Point point = (Point) (TestCommonMethods - .fromJson("{\"x\":5,\"y\":6}").getGeometry()); - Envelope envelope = (Envelope) (TestCommonMethods - .fromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":10}") - .getGeometry()); - wiggleGeometry(point, 0.00000001, 1982); - wiggleGeometry(envelope, 0.00000001, 511); - assertTrue(!equals.execute(envelope, point, sr, null)); - assertTrue(contains.execute(envelope, point, sr, null)); - assertTrue(!contains.execute(point, envelope, sr, null)); - assertTrue(!disjoint.execute(envelope, point, sr, null)); - assertTrue(!touches.execute(envelope, point, sr, null)); - assertTrue(!overlaps.execute(envelope, point, sr, null)); - assertTrue(!crosses.execute(envelope, point, sr, null)); - } - - { - Point point = (Point) (TestCommonMethods - .fromJson("{\"x\":0,\"y\":10}").getGeometry()); - Envelope envelope = (Envelope) (TestCommonMethods - .fromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":10}") - .getGeometry()); - wiggleGeometry(point, 0.00000001, 1982); - wiggleGeometry(envelope, 0.00000001, 511); - assertTrue(!equals.execute(envelope, point, sr, null)); - assertTrue(!contains.execute(envelope, point, sr, null)); - assertTrue(!contains.execute(point, envelope, sr, null)); - assertTrue(!disjoint.execute(envelope, point, sr, null)); - assertTrue(touches.execute(envelope, point, sr, null)); - assertTrue(!overlaps.execute(envelope, point, sr, null)); - assertTrue(!crosses.execute(envelope, point, sr, null)); - } - - { - Point point = (Point) (TestCommonMethods - .fromJson("{\"x\":0,\"y\":11}").getGeometry()); - Envelope envelope = (Envelope) (TestCommonMethods - .fromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":10}") - .getGeometry()); - wiggleGeometry(point, 0.00000001, 1982); - wiggleGeometry(envelope, 0.00000001, 511); - assertTrue(!equals.execute(envelope, point, sr, null)); - assertTrue(!contains.execute(envelope, point, sr, null)); - assertTrue(!contains.execute(point, envelope, sr, null)); - assertTrue(disjoint.execute(envelope, point, sr, null)); - assertTrue(!touches.execute(envelope, point, sr, null)); - assertTrue(!overlaps.execute(envelope, point, sr, null)); - assertTrue(!crosses.execute(envelope, point, sr, null)); - } - - { - Point point = (Point) (TestCommonMethods - .fromJson("{\"x\":0,\"y\":0}").getGeometry()); - Envelope envelope = (Envelope) (TestCommonMethods - .fromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":0}") - .getGeometry()); - wiggleGeometry(point, 0.00000001, 1982); - wiggleGeometry(envelope, 0.00000001, 511); - assertTrue(!equals.execute(envelope, point, sr, null)); - assertTrue(!contains.execute(envelope, point, sr, null)); - assertTrue(!contains.execute(point, envelope, sr, null)); - assertTrue(!disjoint.execute(envelope, point, sr, null)); - assertTrue(touches.execute(envelope, point, sr, null)); - assertTrue(!overlaps.execute(envelope, point, sr, null)); - assertTrue(!crosses.execute(envelope, point, sr, null)); - } - - { - Point point = (Point) (TestCommonMethods - .fromJson("{\"x\":5,\"y\":0}").getGeometry()); - Envelope envelope = (Envelope) (TestCommonMethods - .fromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":0}") - .getGeometry()); - wiggleGeometry(point, 0.00000001, 1982); - wiggleGeometry(envelope, 0.00000001, 511); - assertTrue(!equals.execute(envelope, point, sr, null)); - assertTrue(contains.execute(envelope, point, sr, null)); - assertTrue(!contains.execute(point, envelope, sr, null)); - assertTrue(!disjoint.execute(envelope, point, sr, null)); - assertTrue(!touches.execute(envelope, point, sr, null)); - assertTrue(!overlaps.execute(envelope, point, sr, null)); - assertTrue(!crosses.execute(envelope, point, sr, null)); - } - - { - Point point = (Point) (TestCommonMethods - .fromJson("{\"x\":11,\"y\":0}").getGeometry()); - Envelope envelope = (Envelope) (TestCommonMethods - .fromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":0}") - .getGeometry()); - wiggleGeometry(point, 0.00000001, 1982); - wiggleGeometry(envelope, 0.00000001, 511); - assertTrue(!equals.execute(envelope, point, sr, null)); - assertTrue(!contains.execute(envelope, point, sr, null)); - assertTrue(!contains.execute(point, envelope, sr, null)); - assertTrue(disjoint.execute(envelope, point, sr, null)); - assertTrue(!touches.execute(envelope, point, sr, null)); - assertTrue(!overlaps.execute(envelope, point, sr, null)); - assertTrue(!crosses.execute(envelope, point, sr, null)); - } - - { - Point point = (Point) (TestCommonMethods - .fromJson("{\"x\":0,\"y\":0}").getGeometry()); - Envelope envelope = (Envelope) (TestCommonMethods - .fromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":0,\"ymax\":0}") - .getGeometry()); - wiggleGeometry(point, 0.00000001, 1982); - wiggleGeometry(envelope, 0.00000001, 511); - assertTrue(equals.execute(envelope, point, sr, null)); - assertTrue(contains.execute(envelope, point, sr, null)); - assertTrue(contains.execute(point, envelope, sr, null)); - assertTrue(!disjoint.execute(envelope, point, sr, null)); - assertTrue(!touches.execute(envelope, point, sr, null)); - assertTrue(!overlaps.execute(envelope, point, sr, null)); - assertTrue(!crosses.execute(envelope, point, sr, null)); - } - } - - @Test - public void testEnvelopeEnvelope() { - OperatorEquals equals = (OperatorEquals) (OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.Equals)); - OperatorContains contains = (OperatorContains) (OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.Contains)); - OperatorDisjoint disjoint = (OperatorDisjoint) (OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.Disjoint)); - OperatorCrosses crosses = (OperatorCrosses) (OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.Crosses)); - @SuppressWarnings("unused") - OperatorWithin within = (OperatorWithin) (OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.Within)); - OperatorOverlaps overlaps = (OperatorOverlaps) (OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.Overlaps)); - OperatorTouches touches = (OperatorTouches) (OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.Touches)); - SpatialReference sr = SpatialReference.create(4326); - - { - Envelope env1 = (Envelope) (TestCommonMethods - .fromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":10}") - .getGeometry()); - Envelope env2 = (Envelope) (TestCommonMethods - .fromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":10}") - .getGeometry()); - wiggleGeometry(env1, 0.00000001, 1982); - wiggleGeometry(env2, 0.00000001, 511); - assertTrue(equals.execute(env1, env2, sr, null)); - assertTrue(contains.execute(env1, env2, sr, null)); - assertTrue(contains.execute(env2, env1, sr, null)); - assertTrue(!disjoint.execute(env1, env2, sr, null)); - assertTrue(!touches.execute(env1, env2, sr, null)); - assertTrue(!touches.execute(env2, env1, sr, null)); - assertTrue(!overlaps.execute(env1, env2, sr, null)); - assertTrue(!overlaps.execute(env2, env1, sr, null)); - assertTrue(!crosses.execute(env1, env2, sr, null)); - assertTrue(!crosses.execute(env2, env1, sr, null)); - } - - { - Envelope env1 = (Envelope) (TestCommonMethods - .fromJson("{\"xmin\":5,\"ymin\":5,\"xmax\":10,\"ymax\":10}") - .getGeometry()); - Envelope env2 = (Envelope) (TestCommonMethods - .fromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":10}") - .getGeometry()); - wiggleGeometry(env1, 0.00000001, 1982); - wiggleGeometry(env2, 0.00000001, 511); - assertTrue(!equals.execute(env1, env2, sr, null)); - assertTrue(!contains.execute(env1, env2, sr, null)); - assertTrue(contains.execute(env2, env1, sr, null)); - assertTrue(!disjoint.execute(env1, env2, sr, null)); - assertTrue(!touches.execute(env1, env2, sr, null)); - assertTrue(!overlaps.execute(env1, env2, sr, null)); - assertTrue(!overlaps.execute(env2, env1, sr, null)); - assertTrue(!crosses.execute(env1, env2, sr, null)); - assertTrue(!crosses.execute(env2, env1, sr, null)); - } - - { - Envelope env1 = (Envelope) (TestCommonMethods - .fromJson("{\"xmin\":5,\"ymin\":5,\"xmax\":15,\"ymax\":15}") - .getGeometry()); - Envelope env2 = (Envelope) (TestCommonMethods - .fromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":10}") - .getGeometry()); - wiggleGeometry(env1, 0.00000001, 1982); - wiggleGeometry(env2, 0.00000001, 511); - assertTrue(!equals.execute(env1, env2, sr, null)); - assertTrue(!contains.execute(env1, env2, sr, null)); - assertTrue(!contains.execute(env2, env1, sr, null)); - assertTrue(!disjoint.execute(env1, env2, sr, null)); - assertTrue(!touches.execute(env1, env2, sr, null)); - assertTrue(overlaps.execute(env1, env2, sr, null)); - assertTrue(overlaps.execute(env2, env1, sr, null)); - assertTrue(!crosses.execute(env1, env2, sr, null)); - assertTrue(!crosses.execute(env2, env1, sr, null)); - } - - { - Envelope env1 = (Envelope) (TestCommonMethods - .fromJson("{\"xmin\":10,\"ymin\":0,\"xmax\":20,\"ymax\":10}") - .getGeometry()); - Envelope env2 = (Envelope) (TestCommonMethods - .fromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":10}") - .getGeometry()); - wiggleGeometry(env1, 0.00000001, 1982); - wiggleGeometry(env2, 0.00000001, 511); - assertTrue(!equals.execute(env1, env2, sr, null)); - assertTrue(!contains.execute(env1, env2, sr, null)); - assertTrue(!contains.execute(env2, env1, sr, null)); - assertTrue(!disjoint.execute(env1, env2, sr, null)); - assertTrue(touches.execute(env1, env2, sr, null)); - assertTrue(!overlaps.execute(env1, env2, sr, null)); - assertTrue(!overlaps.execute(env2, env1, sr, null)); - assertTrue(!crosses.execute(env1, env2, sr, null)); - assertTrue(!crosses.execute(env2, env1, sr, null)); - } - - { - Envelope env1 = (Envelope) (TestCommonMethods - .fromJson("{\"xmin\":10,\"ymin\":0,\"xmax\":20,\"ymax\":10}") - .getGeometry()); - Envelope env2 = (Envelope) (TestCommonMethods - .fromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":10}") - .getGeometry()); - wiggleGeometry(env1, 0.00000001, 1982); - wiggleGeometry(env2, 0.00000001, 511); - assertTrue(!equals.execute(env1, env2, sr, null)); - assertTrue(!contains.execute(env1, env2, sr, null)); - assertTrue(!contains.execute(env2, env1, sr, null)); - assertTrue(!disjoint.execute(env1, env2, sr, null)); - assertTrue(touches.execute(env1, env2, sr, null)); - assertTrue(!overlaps.execute(env1, env2, sr, null)); - assertTrue(!overlaps.execute(env2, env1, sr, null)); - assertTrue(!crosses.execute(env1, env2, sr, null)); - assertTrue(!crosses.execute(env2, env1, sr, null)); - } - - { - Envelope env1 = (Envelope) (TestCommonMethods - .fromJson("{\"xmin\":10,\"ymin\":0,\"xmax\":20,\"ymax\":10}") - .getGeometry()); - Envelope env2 = (Envelope) (TestCommonMethods - .fromJson("{\"xmin\":0,\"ymin\":10,\"xmax\":10,\"ymax\":20}") - .getGeometry()); - wiggleGeometry(env1, 0.00000001, 1982); - wiggleGeometry(env2, 0.00000001, 511); - assertTrue(!equals.execute(env1, env2, sr, null)); - assertTrue(!contains.execute(env1, env2, sr, null)); - assertTrue(!contains.execute(env2, env1, sr, null)); - assertTrue(!disjoint.execute(env1, env2, sr, null)); - assertTrue(touches.execute(env1, env2, sr, null)); - assertTrue(!overlaps.execute(env1, env2, sr, null)); - assertTrue(!overlaps.execute(env2, env1, sr, null)); - assertTrue(!crosses.execute(env1, env2, sr, null)); - assertTrue(!crosses.execute(env2, env1, sr, null)); - } - - { - Envelope env1 = (Envelope) (TestCommonMethods - .fromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":10}") - .getGeometry()); - Envelope env2 = (Envelope) (TestCommonMethods - .fromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":0}") - .getGeometry()); - wiggleGeometry(env1, 0.00000001, 1982); - wiggleGeometry(env2, 0.00000001, 511); - assertTrue(!equals.execute(env1, env2, sr, null)); - assertTrue(!contains.execute(env1, env2, sr, null)); - assertTrue(!contains.execute(env2, env1, sr, null)); - assertTrue(!disjoint.execute(env1, env2, sr, null)); - assertTrue(touches.execute(env1, env2, sr, null)); - assertTrue(!overlaps.execute(env1, env2, sr, null)); - assertTrue(!overlaps.execute(env2, env1, sr, null)); - assertTrue(!crosses.execute(env1, env2, sr, null)); - assertTrue(!crosses.execute(env2, env1, sr, null)); - } - - { - Envelope env1 = (Envelope) (TestCommonMethods - .fromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":10}") - .getGeometry()); - Envelope env2 = (Envelope) (TestCommonMethods - .fromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":15,\"ymax\":0}") - .getGeometry()); - wiggleGeometry(env1, 0.00000001, 1982); - wiggleGeometry(env2, 0.00000001, 511); - assertTrue(!equals.execute(env1, env2, sr, null)); - assertTrue(!contains.execute(env1, env2, sr, null)); - assertTrue(!contains.execute(env2, env1, sr, null)); - assertTrue(!disjoint.execute(env1, env2, sr, null)); - assertTrue(touches.execute(env1, env2, sr, null)); - assertTrue(!overlaps.execute(env1, env2, sr, null)); - assertTrue(!overlaps.execute(env2, env1, sr, null)); - assertTrue(!crosses.execute(env1, env2, sr, null)); - assertTrue(!crosses.execute(env2, env1, sr, null)); - } - - { - Envelope env1 = (Envelope) (TestCommonMethods - .fromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":10}") - .getGeometry()); - Envelope env2 = (Envelope) (TestCommonMethods - .fromJson("{\"xmin\":-5,\"ymin\":5,\"xmax\":0,\"ymax\":5}") - .getGeometry()); - wiggleGeometry(env1, 0.00000001, 1982); - wiggleGeometry(env2, 0.00000001, 511); - assertTrue(!equals.execute(env1, env2, sr, null)); - assertTrue(!contains.execute(env1, env2, sr, null)); - assertTrue(!contains.execute(env2, env1, sr, null)); - assertTrue(!disjoint.execute(env1, env2, sr, null)); - assertTrue(touches.execute(env1, env2, sr, null)); - assertTrue(!overlaps.execute(env1, env2, sr, null)); - assertTrue(!overlaps.execute(env2, env1, sr, null)); - assertTrue(!crosses.execute(env1, env2, sr, null)); - assertTrue(!crosses.execute(env2, env1, sr, null)); - } - - { - Envelope env1 = (Envelope) (TestCommonMethods - .fromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":10}") - .getGeometry()); - Envelope env2 = (Envelope) (TestCommonMethods - .fromJson("{\"xmin\":-5,\"ymin\":5,\"xmax\":5,\"ymax\":5}") - .getGeometry()); - wiggleGeometry(env1, 0.00000001, 1982); - wiggleGeometry(env2, 0.00000001, 511); - assertTrue(!equals.execute(env1, env2, sr, null)); - assertTrue(!contains.execute(env1, env2, sr, null)); - assertTrue(!contains.execute(env2, env1, sr, null)); - assertTrue(!disjoint.execute(env1, env2, sr, null)); - assertTrue(!touches.execute(env1, env2, sr, null)); - assertTrue(!overlaps.execute(env1, env2, sr, null)); - assertTrue(!overlaps.execute(env2, env1, sr, null)); - assertTrue(crosses.execute(env1, env2, sr, null)); - assertTrue(crosses.execute(env2, env1, sr, null)); - } - - { - Envelope env1 = (Envelope) (TestCommonMethods - .fromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":10}") - .getGeometry()); - Envelope env2 = (Envelope) (TestCommonMethods - .fromJson("{\"xmin\":3,\"ymin\":5,\"xmax\":5,\"ymax\":5}") - .getGeometry()); - wiggleGeometry(env1, 0.00000001, 1982); - wiggleGeometry(env2, 0.00000001, 511); - assertTrue(!equals.execute(env1, env2, sr, null)); - assertTrue(contains.execute(env1, env2, sr, null)); - assertTrue(!contains.execute(env2, env1, sr, null)); - assertTrue(!disjoint.execute(env1, env2, sr, null)); - assertTrue(!touches.execute(env1, env2, sr, null)); - assertTrue(!overlaps.execute(env1, env2, sr, null)); - assertTrue(!overlaps.execute(env2, env1, sr, null)); - assertTrue(!crosses.execute(env1, env2, sr, null)); - assertTrue(!crosses.execute(env2, env1, sr, null)); - } - - { - Envelope env1 = (Envelope) (TestCommonMethods - .fromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":10}") - .getGeometry()); - Envelope env2 = (Envelope) (TestCommonMethods - .fromJson("{\"xmin\":3,\"ymin\":5,\"xmax\":10,\"ymax\":5}") - .getGeometry()); - wiggleGeometry(env1, 0.00000001, 1982); - wiggleGeometry(env2, 0.00000001, 511); - assertTrue(!equals.execute(env1, env2, sr, null)); - assertTrue(contains.execute(env1, env2, sr, null)); - assertTrue(!contains.execute(env2, env1, sr, null)); - assertTrue(!disjoint.execute(env1, env2, sr, null)); - assertTrue(!touches.execute(env1, env2, sr, null)); - assertTrue(!overlaps.execute(env1, env2, sr, null)); - assertTrue(!overlaps.execute(env2, env1, sr, null)); - assertTrue(!crosses.execute(env1, env2, sr, null)); - assertTrue(!crosses.execute(env2, env1, sr, null)); - } - - { - Envelope env1 = (Envelope) (TestCommonMethods - .fromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":10}") - .getGeometry()); - Envelope env2 = (Envelope) (TestCommonMethods - .fromJson("{\"xmin\":-5,\"ymin\":0,\"xmax\":0,\"ymax\":0}") - .getGeometry()); - wiggleGeometry(env1, 0.00000001, 1982); - wiggleGeometry(env2, 0.00000001, 511); - assertTrue(!equals.execute(env1, env2, sr, null)); - assertTrue(!contains.execute(env1, env2, sr, null)); - assertTrue(!contains.execute(env2, env1, sr, null)); - assertTrue(!disjoint.execute(env1, env2, sr, null)); - assertTrue(touches.execute(env1, env2, sr, null)); - assertTrue(!overlaps.execute(env1, env2, sr, null)); - assertTrue(!overlaps.execute(env2, env1, sr, null)); - assertTrue(!crosses.execute(env1, env2, sr, null)); - assertTrue(!crosses.execute(env2, env1, sr, null)); - } - - { - Envelope env1 = (Envelope) (TestCommonMethods - .fromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":0}") - .getGeometry()); - Envelope env2 = (Envelope) (TestCommonMethods - .fromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":0}") - .getGeometry()); - wiggleGeometry(env1, 0.00000001, 1982); - wiggleGeometry(env2, 0.00000001, 511); - assertTrue(equals.execute(env1, env2, sr, null)); - assertTrue(contains.execute(env1, env2, sr, null)); - assertTrue(contains.execute(env2, env1, sr, null)); - assertTrue(!disjoint.execute(env1, env2, sr, null)); - assertTrue(!touches.execute(env1, env2, sr, null)); - assertTrue(!overlaps.execute(env1, env2, sr, null)); - assertTrue(!overlaps.execute(env2, env1, sr, null)); - assertTrue(!crosses.execute(env1, env2, sr, null)); - assertTrue(!crosses.execute(env2, env1, sr, null)); - } - - { - Envelope env1 = (Envelope) (TestCommonMethods - .fromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":15,\"ymax\":0}") - .getGeometry()); - Envelope env2 = (Envelope) (TestCommonMethods - .fromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":0}") - .getGeometry()); - wiggleGeometry(env1, 0.00000001, 1982); - wiggleGeometry(env2, 0.00000001, 511); - assertTrue(!equals.execute(env1, env2, sr, null)); - assertTrue(contains.execute(env1, env2, sr, null)); - assertTrue(!contains.execute(env2, env1, sr, null)); - assertTrue(!disjoint.execute(env1, env2, sr, null)); - assertTrue(!touches.execute(env1, env2, sr, null)); - assertTrue(!overlaps.execute(env1, env2, sr, null)); - assertTrue(!overlaps.execute(env2, env1, sr, null)); - assertTrue(!crosses.execute(env1, env2, sr, null)); - assertTrue(!crosses.execute(env2, env1, sr, null)); - } - - { - Envelope env1 = (Envelope) (TestCommonMethods - .fromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":15,\"ymax\":0}") - .getGeometry()); - Envelope env2 = (Envelope) (TestCommonMethods - .fromJson("{\"xmin\":-5,\"ymin\":0,\"xmax\":10,\"ymax\":0}") - .getGeometry()); - wiggleGeometry(env1, 0.00000001, 1982); - wiggleGeometry(env2, 0.00000001, 511); - assertTrue(!equals.execute(env1, env2, sr, null)); - assertTrue(!contains.execute(env1, env2, sr, null)); - assertTrue(!contains.execute(env2, env1, sr, null)); - assertTrue(!disjoint.execute(env1, env2, sr, null)); - assertTrue(!touches.execute(env1, env2, sr, null)); - assertTrue(overlaps.execute(env1, env2, sr, null)); - assertTrue(overlaps.execute(env2, env1, sr, null)); - assertTrue(!crosses.execute(env1, env2, sr, null)); - assertTrue(!crosses.execute(env2, env1, sr, null)); - } - - { - Envelope env1 = (Envelope) (TestCommonMethods - .fromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":5,\"ymax\":0}") - .getGeometry()); - Envelope env2 = (Envelope) (TestCommonMethods - .fromJson("{\"xmin\":-5,\"ymin\":0,\"xmax\":0,\"ymax\":0}") - .getGeometry()); - wiggleGeometry(env1, 0.00000001, 1982); - wiggleGeometry(env2, 0.00000001, 511); - assertTrue(!equals.execute(env1, env2, sr, null)); - assertTrue(!contains.execute(env1, env2, sr, null)); - assertTrue(!contains.execute(env2, env1, sr, null)); - assertTrue(!disjoint.execute(env1, env2, sr, null)); - assertTrue(touches.execute(env1, env2, sr, null)); - assertTrue(!overlaps.execute(env1, env2, sr, null)); - assertTrue(!overlaps.execute(env2, env1, sr, null)); - assertTrue(!crosses.execute(env1, env2, sr, null)); - assertTrue(!crosses.execute(env2, env1, sr, null)); - } - - { - Envelope env1 = (Envelope) (TestCommonMethods - .fromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":0}") - .getGeometry()); - Envelope env2 = (Envelope) (TestCommonMethods - .fromJson("{\"xmin\":5,\"ymin\":-5,\"xmax\":5,\"ymax\":5}") - .getGeometry()); - wiggleGeometry(env1, 0.00000001, 1982); - wiggleGeometry(env2, 0.00000001, 511); - assertTrue(!equals.execute(env1, env2, sr, null)); - assertTrue(!contains.execute(env1, env2, sr, null)); - assertTrue(!contains.execute(env2, env1, sr, null)); - assertTrue(!disjoint.execute(env1, env2, sr, null)); - assertTrue(!touches.execute(env1, env2, sr, null)); - assertTrue(!overlaps.execute(env1, env2, sr, null)); - assertTrue(!overlaps.execute(env2, env1, sr, null)); - assertTrue(crosses.execute(env1, env2, sr, null)); - assertTrue(crosses.execute(env2, env1, sr, null)); - } - - { - Envelope env1 = (Envelope) (TestCommonMethods - .fromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":0}") - .getGeometry()); - Envelope env2 = (Envelope) (TestCommonMethods - .fromJson("{\"xmin\":10,\"ymin\":0,\"xmax\":20,\"ymax\":0}") - .getGeometry()); - wiggleGeometry(env1, 0.00000001, 1982); - wiggleGeometry(env2, 0.00000001, 511); - assertTrue(!equals.execute(env1, env2, sr, null)); - assertTrue(!contains.execute(env1, env2, sr, null)); - assertTrue(!contains.execute(env2, env1, sr, null)); - assertTrue(!disjoint.execute(env1, env2, sr, null)); - assertTrue(touches.execute(env1, env2, sr, null)); - assertTrue(!overlaps.execute(env1, env2, sr, null)); - assertTrue(!overlaps.execute(env2, env1, sr, null)); - assertTrue(!crosses.execute(env1, env2, sr, null)); - assertTrue(!crosses.execute(env2, env1, sr, null)); - } - - { - Envelope env1 = (Envelope) (TestCommonMethods - .fromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":0}") - .getGeometry()); - Envelope env2 = (Envelope) (TestCommonMethods - .fromJson("{\"xmin\":5,\"ymin\":0,\"xmax\":5,\"ymax\":5}") - .getGeometry()); - wiggleGeometry(env1, 0.00000001, 1982); - wiggleGeometry(env2, 0.00000001, 511); - assertTrue(!equals.execute(env1, env2, sr, null)); - assertTrue(!contains.execute(env1, env2, sr, null)); - assertTrue(!contains.execute(env2, env1, sr, null)); - assertTrue(!disjoint.execute(env1, env2, sr, null)); - assertTrue(touches.execute(env1, env2, sr, null)); - assertTrue(touches.execute(env2, env1, sr, null)); - assertTrue(!overlaps.execute(env1, env2, sr, null)); - assertTrue(!overlaps.execute(env2, env1, sr, null)); - assertTrue(!crosses.execute(env1, env2, sr, null)); - assertTrue(!crosses.execute(env2, env1, sr, null)); - } - - { - Envelope env1 = (Envelope) (TestCommonMethods - .fromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":0,\"ymax\":0}") - .getGeometry()); - Envelope env2 = (Envelope) (TestCommonMethods - .fromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":10}") - .getGeometry()); - wiggleGeometry(env1, 0.00000001, 1982); - wiggleGeometry(env2, 0.00000001, 511); - assertTrue(!equals.execute(env1, env2, sr, null)); - assertTrue(!contains.execute(env1, env2, sr, null)); - assertTrue(!contains.execute(env2, env1, sr, null)); - assertTrue(!disjoint.execute(env1, env2, sr, null)); - assertTrue(touches.execute(env1, env2, sr, null)); - assertTrue(touches.execute(env2, env1, sr, null)); - assertTrue(!overlaps.execute(env1, env2, sr, null)); - assertTrue(!overlaps.execute(env2, env1, sr, null)); - assertTrue(!crosses.execute(env1, env2, sr, null)); - assertTrue(!crosses.execute(env2, env1, sr, null)); - } - - { - Envelope env1 = (Envelope) (TestCommonMethods - .fromJson("{\"xmin\":5,\"ymin\":0,\"xmax\":5,\"ymax\":0}") - .getGeometry()); - Envelope env2 = (Envelope) (TestCommonMethods - .fromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":10}") - .getGeometry()); - wiggleGeometry(env1, 0.00000001, 1982); - wiggleGeometry(env2, 0.00000001, 511); - assertTrue(!equals.execute(env1, env2, sr, null)); - assertTrue(!contains.execute(env1, env2, sr, null)); - assertTrue(!contains.execute(env2, env1, sr, null)); - assertTrue(!disjoint.execute(env1, env2, sr, null)); - assertTrue(touches.execute(env1, env2, sr, null)); - assertTrue(touches.execute(env2, env1, sr, null)); - assertTrue(!overlaps.execute(env1, env2, sr, null)); - assertTrue(!overlaps.execute(env2, env1, sr, null)); - assertTrue(!crosses.execute(env1, env2, sr, null)); - assertTrue(!crosses.execute(env2, env1, sr, null)); - } - - { - Envelope env1 = (Envelope) (TestCommonMethods - .fromJson("{\"xmin\":5,\"ymin\":5,\"xmax\":5,\"ymax\":5}") - .getGeometry()); - Envelope env2 = (Envelope) (TestCommonMethods - .fromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":10}") - .getGeometry()); - wiggleGeometry(env1, 0.00000001, 1982); - wiggleGeometry(env2, 0.00000001, 511); - assertTrue(!equals.execute(env1, env2, sr, null)); - assertTrue(!contains.execute(env1, env2, sr, null)); - assertTrue(contains.execute(env2, env1, sr, null)); - assertTrue(!disjoint.execute(env1, env2, sr, null)); - assertTrue(!touches.execute(env1, env2, sr, null)); - assertTrue(!touches.execute(env2, env1, sr, null)); - assertTrue(!overlaps.execute(env1, env2, sr, null)); - assertTrue(!overlaps.execute(env2, env1, sr, null)); - assertTrue(!crosses.execute(env1, env2, sr, null)); - assertTrue(!crosses.execute(env2, env1, sr, null)); - } - - { - Envelope env1 = (Envelope) (TestCommonMethods - .fromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":0,\"ymax\":0}") - .getGeometry()); - Envelope env2 = (Envelope) (TestCommonMethods - .fromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":0}") - .getGeometry()); - wiggleGeometry(env1, 0.00000001, 1982); - wiggleGeometry(env2, 0.00000001, 511); - assertTrue(!equals.execute(env1, env2, sr, null)); - assertTrue(!contains.execute(env1, env2, sr, null)); - assertTrue(!contains.execute(env2, env1, sr, null)); - assertTrue(!disjoint.execute(env1, env2, sr, null)); - assertTrue(touches.execute(env1, env2, sr, null)); - assertTrue(touches.execute(env2, env1, sr, null)); - assertTrue(!overlaps.execute(env1, env2, sr, null)); - assertTrue(!overlaps.execute(env2, env1, sr, null)); - assertTrue(!crosses.execute(env1, env2, sr, null)); - assertTrue(!crosses.execute(env2, env1, sr, null)); - } - - { - Envelope env1 = (Envelope) (TestCommonMethods - .fromJson("{\"xmin\":5,\"ymin\":0,\"xmax\":5,\"ymax\":0}") - .getGeometry()); - Envelope env2 = (Envelope) (TestCommonMethods - .fromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":0}") - .getGeometry()); - wiggleGeometry(env1, 0.00000001, 1982); - wiggleGeometry(env2, 0.00000001, 511); - assertTrue(!equals.execute(env1, env2, sr, null)); - assertTrue(!contains.execute(env1, env2, sr, null)); - assertTrue(contains.execute(env2, env1, sr, null)); - assertTrue(!disjoint.execute(env1, env2, sr, null)); - assertTrue(!touches.execute(env1, env2, sr, null)); - assertTrue(!touches.execute(env2, env1, sr, null)); - assertTrue(!overlaps.execute(env1, env2, sr, null)); - assertTrue(!overlaps.execute(env2, env1, sr, null)); - assertTrue(!crosses.execute(env1, env2, sr, null)); - assertTrue(!crosses.execute(env2, env1, sr, null)); - } - - { - Envelope env1 = (Envelope) (TestCommonMethods - .fromJson("{\"xmin\":5,\"ymin\":0,\"xmax\":5,\"ymax\":0}") - .getGeometry()); - Envelope env2 = (Envelope) (TestCommonMethods - .fromJson("{\"xmin\":5,\"ymin\":0,\"xmax\":5,\"ymax\":0}") - .getGeometry()); - wiggleGeometry(env1, 0.00000001, 1982); - wiggleGeometry(env2, 0.00000001, 511); - assertTrue(equals.execute(env1, env2, sr, null)); - assertTrue(contains.execute(env1, env2, sr, null)); - assertTrue(contains.execute(env2, env1, sr, null)); - assertTrue(!disjoint.execute(env1, env2, sr, null)); - assertTrue(!touches.execute(env1, env2, sr, null)); - assertTrue(!touches.execute(env2, env1, sr, null)); - assertTrue(!overlaps.execute(env1, env2, sr, null)); - assertTrue(!overlaps.execute(env2, env1, sr, null)); - assertTrue(!crosses.execute(env1, env2, sr, null)); - assertTrue(!crosses.execute(env2, env1, sr, null)); - } - } - - static void wiggleGeometry(Geometry geometry, double tolerance, int rand) { - int type = geometry.getType().value(); - - if (type == Geometry.GeometryType.Polygon - || type == Geometry.GeometryType.Polyline - || type == Geometry.GeometryType.MultiPoint) { - MultiVertexGeometry mvGeom = (MultiVertexGeometry) geometry; - for (int i = 0; i < mvGeom.getPointCount(); i++) { - Point2D pt = mvGeom.getXY(i); - - // create random vector and normalize it to 0.49 * tolerance - Point2D randomV = new Point2D(); - rand = NumberUtils.nextRand(rand); - randomV.x = 1.0 * rand / NumberUtils.intMax() - 0.5; - rand = NumberUtils.nextRand(rand); - randomV.y = 1.0 * rand / NumberUtils.intMax() - 0.5; - randomV.normalize(); - randomV.scale(0.45 * tolerance); - pt.add(randomV); - mvGeom.setXY(i, pt); - } - } else if (type == Geometry.GeometryType.Point) { - Point ptGeom = (Point) (geometry); - Point2D pt = ptGeom.getXY(); - // create random vector and normalize it to 0.49 * tolerance - Point2D randomV = new Point2D(); - rand = NumberUtils.nextRand(rand); - randomV.x = 1.0 * rand / NumberUtils.intMax() - 0.5; - rand = NumberUtils.nextRand(rand); - randomV.y = 1.0 * rand / NumberUtils.intMax() - 0.5; - randomV.normalize(); - randomV.scale(0.45 * tolerance); - pt.add(randomV); - ptGeom.setXY(pt); - } else if (type == Geometry.GeometryType.Envelope) { - Envelope envGeom = (Envelope) (geometry); - Envelope2D env = new Envelope2D(); - envGeom.queryEnvelope2D(env); - double xmin, xmax, ymin, ymax; - Point2D pt = new Point2D(); - env.queryLowerLeft(pt); - // create random vector and normalize it to 0.49 * tolerance - Point2D randomV = new Point2D(); - rand = NumberUtils.nextRand(rand); - randomV.x = 1.0 * rand / NumberUtils.intMax() - 0.5; - rand = NumberUtils.nextRand(rand); - randomV.y = 1.0 * rand / NumberUtils.intMax() - 0.5; - randomV.normalize(); - randomV.scale(0.45 * tolerance); - xmin = (pt.x + randomV.x); - ymin = (pt.y + randomV.y); - - env.queryUpperRight(pt); - // create random vector and normalize it to 0.49 * tolerance - rand = NumberUtils.nextRand(rand); - randomV.x = 1.0 * rand / NumberUtils.intMax() - 0.5; - rand = NumberUtils.nextRand(rand); - randomV.y = 1.0 * rand / NumberUtils.intMax() - 0.5; - randomV.normalize(); - randomV.scale(0.45 * tolerance); - xmax = (pt.x + randomV.x); - ymax = (pt.y + randomV.y); - - if (xmin > xmax) { - double swap = xmin; - xmin = xmax; - xmax = swap; - } - - if (ymin > ymax) { - double swap = ymin; - ymin = ymax; - ymax = swap; - } - - envGeom.setCoords(xmin, ymin, xmax, ymax); - } - - } - - @Test - public void testDisjointRelationFalse() { - { - OperatorDisjoint op = (OperatorDisjoint) (OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.Disjoint)); - Envelope env1 = new Envelope(50, 50, 150, 150); - Envelope env2 = new Envelope(25, 25, 175, 175); - boolean result = op.execute(env1, env2, - SpatialReference.create(4326), null); - assertTrue(!result); - } - { - OperatorIntersects op = (OperatorIntersects) (OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.Intersects)); - Envelope env1 = new Envelope(50, 50, 150, 150); - Envelope env2 = new Envelope(25, 25, 175, 175); - boolean result = op.execute(env1, env2, - SpatialReference.create(4326), null); - assertTrue(result); - } - { - OperatorContains op = (OperatorContains) (OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.Contains)); - Envelope env1 = new Envelope(100, 175, 200, 225); - Polyline polyline = new Polyline(); - polyline.startPath(200, 175); - polyline.lineTo(200, 225); - polyline.lineTo(125, 200); - boolean result = op.execute(env1, polyline, - SpatialReference.create(4326), null); - assertTrue(result); - } - { - OperatorTouches op = (OperatorTouches) (OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.Touches)); - Envelope env1 = new Envelope(100, 200, 400, 400); - Polyline polyline = new Polyline(); - polyline.startPath(300, 60); - polyline.lineTo(300, 200); - polyline.lineTo(400, 50); - boolean result = op.execute(env1, polyline, - SpatialReference.create(4326), null); - assertTrue(result); - } - - { - OperatorTouches op = (OperatorTouches) (OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.Touches)); - Envelope env1 = new Envelope(50, 50, 150, 150); - Polyline polyline = new Polyline(); - polyline.startPath(100, 20); - polyline.lineTo(100, 50); - polyline.lineTo(150, 10); - boolean result = op.execute(polyline, env1, - SpatialReference.create(4326), null); - assertTrue(result); - } - - { - OperatorDisjoint op = (OperatorDisjoint) (OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.Disjoint)); - Polygon polygon = new Polygon(); - Polyline polyline = new Polyline(); - polygon.startPath(0, 0); - polygon.lineTo(0, 10); - polygon.lineTo(10, 10); - polygon.lineTo(10, 0); - polyline.startPath(-5, 4); - polyline.lineTo(5, -6); - boolean result = op.execute(polyline, polygon, - SpatialReference.create(4326), null); - assertTrue(result); - } - } - - @Test - public void testPolylinePolylineRelate() { - OperatorRelate op = OperatorRelate.local(); - SpatialReference sr = SpatialReference.create(4326); - boolean res; - String scl; - - Polyline polyline1 = new Polyline(); - Polyline polyline2 = new Polyline(); - - polyline1.startPath(0, 0); - polyline1.lineTo(1, 1); - - polyline2.startPath(1, 1); - polyline2.lineTo(2, 0); - - scl = "FF1FT01T2"; - res = op.execute(polyline1, polyline2, sr, scl, null); - assertTrue(res); - - scl = "****TF*T*"; - res = op.execute(polyline1, polyline2, sr, scl, null); - assertTrue(!res); - - scl = "****F****"; - res = op.execute(polyline1, polyline2, sr, scl, null); - assertTrue(!res); - - scl = "**1*0*T**"; - res = op.execute(polyline1, polyline2, sr, scl, null); - assertTrue(res); - - scl = "****1****"; - res = op.execute(polyline1, polyline2, sr, scl, null); - assertTrue(!res); - - scl = "**T*001*T"; - res = op.execute(polyline1, polyline2, sr, scl, null); - assertTrue(res); - - scl = "T********"; - res = op.execute(polyline1, polyline2, sr, scl, null); - assertTrue(!res); - - scl = "F********"; - res = op.execute(polyline1, polyline2, sr, scl, null); - assertTrue(res); - - polyline1.setEmpty(); - polyline2.setEmpty(); - - polyline1.startPath(0, 0); - polyline1.lineTo(1, 0); - - polyline2.startPath(0, 0); - polyline2.lineTo(1, 0); - - scl = "1FFFTFFFT"; - res = op.execute(polyline1, polyline2, sr, scl, null); - assertTrue(res); - - scl = "1*T*T****"; - res = op.execute(polyline1, polyline2, sr, scl, null); - assertTrue(!res); - - scl = "1T**T****"; - res = op.execute(polyline1, polyline2, sr, scl, null); - assertTrue(!res); - - polyline1.setEmpty(); - polyline2.setEmpty(); - - polyline1.startPath(0, 0); - polyline1.lineTo(0.5, 0.5); - polyline1.lineTo(1, 1); - - polyline2.startPath(1, 0); - polyline2.lineTo(0.5, 0.5); - polyline2.lineTo(0, 1); - - scl = "0F1FFTT0T"; - res = op.execute(polyline1, polyline2, sr, scl, null); - assertTrue(res); - - scl = "*T*******"; - res = op.execute(polyline1, polyline2, sr, scl, null); - assertTrue(!res); - - scl = "*F*F*****"; - res = op.execute(polyline1, polyline2, sr, scl, null); - assertTrue(res); - - polyline1.setEmpty(); - polyline2.setEmpty(); - - polyline1.startPath(0, 0); - polyline1.lineTo(1, 0); + } + { + OperatorWithin operatorWithin = (OperatorWithin) (projEnv + .getOperator(Operator.Type.Within)); + boolean result = operatorWithin.execute(poly1, poly2, inputSR, + null); + assertTrue(result); + } + + { + OperatorDisjoint operatorDisjoint = (OperatorDisjoint) (projEnv + .getOperator(Operator.Type.Disjoint)); + OperatorIntersects operatorIntersects = (OperatorIntersects) (projEnv + .getOperator(Operator.Type.Intersects)); + boolean result = operatorDisjoint.execute(poly1, poly2, + inputSR, null); + assertTrue(!result); + { + result = operatorIntersects.execute(poly1, poly2, inputSR, + null); + assertTrue(result); + } + } + + { + OperatorDisjoint operatorDisjoint = (OperatorDisjoint) (projEnv + .getOperator(Operator.Type.Disjoint)); + OperatorIntersects operatorIntersects = (OperatorIntersects) (projEnv + .getOperator(Operator.Type.Intersects)); + Envelope2D env2D = new Envelope2D(); + poly2.queryEnvelope2D(env2D); + Envelope envelope = new Envelope(env2D); + boolean result = operatorDisjoint.execute(envelope, poly2, + inputSR, null); + assertTrue(!result); + { + result = operatorIntersects.execute(envelope, poly2, + inputSR, null); + assertTrue(result); + } + } + + { + OperatorDisjoint operatorDisjoint = (OperatorDisjoint) (projEnv + .getOperator(Operator.Type.Disjoint)); + OperatorIntersects operatorIntersects = (OperatorIntersects) (projEnv + .getOperator(Operator.Type.Intersects)); + Polygon poly = new Polygon(); + + Envelope2D env2D = new Envelope2D(); + env2D.setCoords(855277, 3892059, 855277 + 100, 3892059 + 100); + poly.addEnvelope(env2D, false); + env2D.setCoords(855277 + 10, 3892059 + 10, 855277 + 90, + 3892059 + 90); + poly.addEnvelope(env2D, true); + + env2D.setCoords(855277 + 20, 3892059 + 20, 855277 + 200, + 3892059 + 80); + Envelope envelope = new Envelope(env2D); + boolean result = operatorDisjoint.execute(envelope, poly, + inputSR, null); + assertTrue(!result); + { + result = operatorIntersects.execute(envelope, poly, + inputSR, null); + assertTrue(result); + } + } + + { + OperatorTouches operatorTouches = (OperatorTouches) (projEnv + .getOperator(Operator.Type.Touches)); + boolean result = operatorTouches.execute(poly1, poly2, inputSR, + null); + assertTrue(!result); + } + + } + } + + @Test + public void testOperatorDisjoint() { + { + OperatorFactoryLocal projEnv = OperatorFactoryLocal.getInstance(); + SpatialReference inputSR = SpatialReference.create(3857); + + Polygon poly1 = new Polygon(); + Envelope2D env1 = new Envelope2D(); + env1.setCoords(855277, 3892059, 855277 + 100, 3892059 + 100); + poly1.addEnvelope(env1, false); + + Polygon poly2 = new Polygon(); + Envelope2D env2 = new Envelope2D(); + env2.setCoords(855277, 3892059, 855277 + 300, 3892059 + 200); + poly2.addEnvelope(env2, false); + + Polygon poly3 = new Polygon(); + Envelope2D env3 = new Envelope2D(); + env3.setCoords(855277 + 100, 3892059 + 100, 855277 + 100 + 100, + 3892059 + 100 + 100); + poly3.addEnvelope(env3, false); + + Polygon poly4 = new Polygon(); + Envelope2D env4 = new Envelope2D(); + env4.setCoords(855277 + 200, 3892059 + 200, 855277 + 200 + 100, + 3892059 + 200 + 100); + poly4.addEnvelope(env4, false); + + Point point1 = new Point(855277, 3892059); + Point point2 = new Point(855277 + 2, 3892059 + 3); + Point point3 = new Point(855277 - 2, 3892059 - 3); + + { + OperatorDisjoint operatorDisjoint = (OperatorDisjoint) (projEnv + .getOperator(Operator.Type.Disjoint)); + boolean result = operatorDisjoint.execute(poly1, poly2, + inputSR, null); + assertTrue(!result); + result = operatorDisjoint.execute(poly1, poly3, inputSR, null); + assertTrue(!result); + result = operatorDisjoint.execute(poly1, poly4, inputSR, null); + assertTrue(result); + + result = operatorDisjoint.execute(poly1, point1, inputSR, null); + assertTrue(!result); + result = operatorDisjoint.execute(point1, poly1, inputSR, null); + assertTrue(!result); + result = operatorDisjoint.execute(poly1, point2, inputSR, null); + assertTrue(!result); + result = operatorDisjoint.execute(point2, poly1, inputSR, null); + assertTrue(!result); + result = operatorDisjoint.execute(poly1, point3, inputSR, null); + assertTrue(result); + result = operatorDisjoint.execute(point3, poly1, inputSR, null); + assertTrue(result); + } + } + } + + @Test + public void testTouchPointLineCR183227() {// Tests CR 183227 + OperatorTouches operatorTouches = (OperatorTouches) (OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Touches)); + + Geometry baseGeom = new Point(-130, 10); + Polyline pl = new Polyline(); + // pl.startPath(std::make_shared(-130, 10)); + pl.startPath(-130, 10); + pl.lineTo(-131, 15); + pl.lineTo(-140, 20); + + SpatialReference sr = SpatialReference.create(4326); + + boolean isTouched; + boolean isTouched2; + isTouched = operatorTouches.execute(baseGeom, pl, sr, null); + isTouched2 = operatorTouches.execute(pl, baseGeom, sr, null); + assertTrue(isTouched && isTouched2); + + { + baseGeom = new Point(-131, 15); + isTouched = operatorTouches.execute(baseGeom, pl, sr, null); + isTouched2 = operatorTouches.execute(pl, baseGeom, sr, null); + assertTrue(!isTouched && !isTouched2); + } + } + + @Test + public void testTouchPointLineClosed() {// Tests CR 183227 + OperatorTouches operatorTouches = (OperatorTouches) (OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Touches)); + + Geometry baseGeom = new Point(-130, 10); + Polyline pl = new Polyline(); + pl.startPath(-130, 10); + pl.lineTo(-131, 15); + pl.lineTo(-140, 20); + pl.lineTo(-130, 10); + + SpatialReference sr = SpatialReference.create(4326); + + boolean isTouched; + boolean isTouched2; + isTouched = operatorTouches.execute(baseGeom, pl, sr, null); + isTouched2 = operatorTouches.execute(pl, baseGeom, sr, null); + assertTrue(!isTouched && !isTouched2);// this may change in future + + { + baseGeom = new Point(-131, 15); + isTouched = operatorTouches.execute(baseGeom, pl, sr, null); + isTouched2 = operatorTouches.execute(pl, baseGeom, sr, null); + assertTrue(!isTouched && !isTouched2); + } + } + + @Test + public void testTouchPolygonPolygon() { + OperatorTouches operatorTouches = (OperatorTouches) (OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Touches)); + + Polygon pg = new Polygon(); + pg.startPath(-130, 10); + pg.lineTo(-131, 15); + pg.lineTo(-140, 20); + + Polygon pg2 = new Polygon(); + pg2.startPath(-130, 10); + pg2.lineTo(-131, 15); + pg2.lineTo(-120, 20); + SpatialReference sr = SpatialReference.create(4326); + + boolean isTouched; + boolean isTouched2; + isTouched = operatorTouches.execute(pg, pg2, sr, null); + isTouched2 = operatorTouches.execute(pg2, pg, sr, null); + assertTrue(isTouched && isTouched2); + } + + @Test + public void testContainsFailureCR186456() { + { + OperatorContains op = (OperatorContains) (OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Contains)); + String str = "{\"rings\":[[[406944.399999999,287461.450000001],[406947.750000011,287462.299999997],[406946.44999999,287467.450000001],[406943.050000005,287466.550000005],[406927.799999992,287456.849999994],[406926.949999996,287456.599999995],[406924.800000005,287455.999999998],[406924.300000007,287455.849999999],[406924.200000008,287456.099999997],[406923.450000011,287458.449999987],[406922.999999987,287459.800000008],[406922.29999999,287462.099999998],[406921.949999991,287463.449999992],[406921.449999993,287465.050000011],[406920.749999996,287466.700000004],[406919.800000001,287468.599999996],[406919.050000004,287469.99999999],[406917.800000009,287471.800000008],[406916.04999999,287473.550000001],[406915.449999993,287473.999999999],[406913.700000001,287475.449999993],[406913.300000002,287475.899999991],[406912.050000008,287477.250000011],[406913.450000002,287478.150000007],[406915.199999994,287478.650000005],[406915.999999991,287478.800000005],[406918.300000007,287479.200000003],[406920.649999997,287479.450000002],[406923.100000013,287479.550000001],[406925.750000001,287479.450000002],[406928.39999999,287479.150000003],[406929.80000001,287478.950000004],[406932.449999998,287478.350000006],[406935.099999987,287477.60000001],[406938.699999998,287476.349999989],[406939.649999994,287473.949999999],[406939.799999993,287473.949999999],[406941.249999987,287473.75],[406942.700000007,287473.250000002],[406943.100000005,287473.100000003],[406943.950000001,287472.750000004],[406944.799999998,287472.300000006],[406944.999999997,287472.200000007],[406946.099999992,287471.200000011],[406946.299999991,287470.950000012],[406948.00000001,287468.599999996],[406948.10000001,287468.399999997],[406950.100000001,287465.050000011],[406951.949999993,287461.450000001],[406952.049999993,287461.300000001],[406952.69999999,287459.900000007],[406953.249999987,287458.549999987],[406953.349999987,287458.299999988],[406953.650000012,287457.299999992],[406953.900000011,287456.349999996],[406954.00000001,287455.300000001],[406954.00000001,287454.750000003],[406953.850000011,287453.750000008],[406953.550000012,287452.900000011],[406953.299999987,287452.299999988],[406954.500000008,287450.299999996],[406954.00000001,287449.000000002],[406953.399999987,287447.950000006],[406953.199999988,287447.550000008],[406952.69999999,287446.850000011],[406952.149999992,287446.099999988],[406951.499999995,287445.499999991],[406951.149999996,287445.249999992],[406950.449999999,287444.849999994],[406949.600000003,287444.599999995],[406949.350000004,287444.549999995],[406948.250000009,287444.499999995],[406947.149999987,287444.699999994],[406946.849999989,287444.749999994],[406945.899999993,287444.949999993],[406944.999999997,287445.349999991],[406944.499999999,287445.64999999],[406943.650000003,287446.349999987],[406942.900000006,287447.10000001],[406942.500000008,287447.800000007],[406942.00000001,287448.700000003],[406941.600000011,287449.599999999],[406941.350000013,287450.849999994],[406941.350000013,287451.84999999],[406941.450000012,287452.850000012],[406941.750000011,287453.850000007],[406941.800000011,287454.000000007],[406942.150000009,287454.850000003],[406942.650000007,287455.6],[406943.150000005,287456.299999997],[406944.499999999,287457.299999992],[406944.899999997,287457.599999991],[406945.299999995,287457.949999989],[406944.399999999,287461.450000001],[406941.750000011,287461.999999998],[406944.399999999,287461.450000001]],[[406944.399999999,287461.450000001],[406947.750000011,287462.299999997],[406946.44999999,287467.450000001],[406943.050000005,287466.550000005],[406927.799999992,287456.849999994],[406944.399999999,287461.450000001]]]}"; + MapGeometry mg = TestCommonMethods.fromJson(str); + boolean res = op.execute((mg.getGeometry()), (mg.getGeometry()), + null, null); + assertTrue(res); + } + } + + @Test + public void testWithin() { + { + OperatorWithin op = (OperatorWithin) (OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Within)); + String str1 = "{\"rings\":[[[0,0],[0,200],[200,200],[200,0],[0,0],[0,0],[0,0]]]}"; + MapGeometry mg1 = TestCommonMethods.fromJson(str1); + String str2 = "{\"x\":100,\"y\":100}"; + MapGeometry mg2 = TestCommonMethods.fromJson(str2); + + boolean res = op.execute((mg2.getGeometry()), (mg1.getGeometry()), + null, null); + assertTrue(res); + res = op.execute((mg1.getGeometry()), (mg2.getGeometry()), null, + null); + assertTrue(!res); + + } + + {// polygon + OperatorWithin op = (OperatorWithin) (OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Within)); + String str1 = "{\"rings\":[[[0,0],[0,200],[200,200],[200,0],[100,0]]]}"; + MapGeometry mg1 = TestCommonMethods.fromJson(str1); + String str2 = "{\"rings\":[[[10,10],[10,100],[100,100],[100,10]]]}"; + MapGeometry mg2 = TestCommonMethods.fromJson(str2); + + boolean res = op.execute((mg2.getGeometry()), (mg1.getGeometry()), + null, null); + assertTrue(res); + res = op.execute((mg1.getGeometry()), (mg2.getGeometry()), null, + null); + assertTrue(!res); + } + + {// Multi_point + OperatorWithin op = (OperatorWithin) (OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Within)); + String str1 = "{\"points\":[[0,0],[0,200],[200,200]]}"; + MapGeometry mg1 = TestCommonMethods.fromJson(str1); + String str2 = "{\"points\":[[0,0],[0,200]]}"; + MapGeometry mg2 = TestCommonMethods.fromJson(str2); + boolean res = op.execute((mg2.getGeometry()), (mg1.getGeometry()), + null, null); + assertTrue(res); + res = op.execute((mg1.getGeometry()), (mg2.getGeometry()), null, + null); + assertTrue(!res); + } + + {// Multi_point + OperatorWithin op = (OperatorWithin) (OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Within)); + String str1 = "{\"points\":[[0,0],[0,200]]}"; + MapGeometry mg1 = TestCommonMethods.fromJson(str1); + String str2 = "{\"points\":[[0,0],[0,200]]}"; + MapGeometry mg2 = TestCommonMethods.fromJson(str2); + boolean res = op.execute((mg2.getGeometry()), (mg1.getGeometry()), + null, null); + assertTrue(res); + res = op.execute((mg1.getGeometry()), (mg2.getGeometry()), null, + null); + assertTrue(res); + } + + {// Multi_point + OperatorWithin op = (OperatorWithin) (OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Within)); + String str1 = "{\"points\":[[0,0],[0,200],[200,200]]}"; + MapGeometry mg1 = TestCommonMethods.fromJson(str1); + String str2 = "{\"points\":[[0,0],[0,200], [1, 1]]}"; + MapGeometry mg2 = TestCommonMethods.fromJson(str2); + boolean res = op.execute((mg2.getGeometry()), (mg1.getGeometry()), + null, null); + assertTrue(!res); + res = op.execute((mg1.getGeometry()), (mg2.getGeometry()), null, + null); + assertTrue(!res); + } + } + + @Test + public void testContains() { + { + OperatorContains op = (OperatorContains) (OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Contains)); + String str1 = "{\"rings\":[[[0,0],[0,200],[200,200],[200,0],[0,0],[0,0],[0,0]]]}"; + MapGeometry mg1 = TestCommonMethods.fromJson(str1); + String str2 = "{\"x\":100,\"y\":100}"; + MapGeometry mg2 = TestCommonMethods.fromJson(str2); + + boolean res = op.execute((mg2.getGeometry()), (mg1.getGeometry()), + null, null); + assertTrue(!res); + res = op.execute((mg1.getGeometry()), (mg2.getGeometry()), null, + null); + assertTrue(res); + } + + {// polygon + OperatorContains op = (OperatorContains) (OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Contains)); + String str1 = "{\"rings\":[[[0,0],[0,200],[200,200],[200,0],[0,0]]]}"; + MapGeometry mg1 = TestCommonMethods.fromJson(str1); + String str2 = "{\"rings\":[[[10,10],[10,100],[100,100],[10,10]]]}"; + MapGeometry mg2 = TestCommonMethods.fromJson(str2); + + boolean res = op.execute((mg2.getGeometry()), (mg1.getGeometry()), + null, null); + assertTrue(!res); + res = op.execute((mg1.getGeometry()), (mg2.getGeometry()), null, + null); + assertTrue(res); + } + + {// Multi_point + OperatorContains op = (OperatorContains) (OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Contains)); + String str1 = "{\"points\":[[0,0],[0,200],[200,200]]}"; + MapGeometry mg1 = TestCommonMethods.fromJson(str1); + String str2 = "{\"points\":[[0,0],[0,200]]}"; + MapGeometry mg2 = TestCommonMethods.fromJson(str2); + boolean res = op.execute((mg2.getGeometry()), (mg1.getGeometry()), + null, null); + assertTrue(!res); + res = op.execute((mg1.getGeometry()), (mg2.getGeometry()), null, + null); + assertTrue(res); + } + + {// Multi_point + OperatorContains op = (OperatorContains) (OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Contains)); + String str1 = "{\"points\":[[0,0],[0,200]]}"; + MapGeometry mg1 = TestCommonMethods.fromJson(str1); + String str2 = "{\"points\":[[0,0],[0,200]]}"; + MapGeometry mg2 = TestCommonMethods.fromJson(str2); + boolean res = op.execute((mg2.getGeometry()), (mg1.getGeometry()), + null, null); + assertTrue(res); + res = op.execute((mg1.getGeometry()), (mg2.getGeometry()), null, + null); + assertTrue(res); + } + + {// Multi_point + OperatorContains op = (OperatorContains) (OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Contains)); + String str1 = "{\"points\":[[0,0],[0,200],[200,200]]}"; + MapGeometry mg1 = TestCommonMethods.fromJson(str1); + String str2 = "{\"points\":[[0,0],[0,200], [1, 1]]}"; + MapGeometry mg2 = TestCommonMethods.fromJson(str2); + boolean res = op.execute((mg2.getGeometry()), (mg1.getGeometry()), + null, null); + assertTrue(!res); + res = op.execute((mg1.getGeometry()), (mg2.getGeometry()), null, + null); + assertTrue(!res); + } + } + + @Test + public void testOverlaps() { + {// empty polygon + OperatorOverlaps op = (OperatorOverlaps) (OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Overlaps)); + Polygon poly1 = new Polygon(); + Polygon poly2 = new Polygon(); + + boolean res = op.execute(poly1, poly2, null, null); + assertTrue(!res); + res = op.execute(poly1, poly2, null, null); + assertTrue(!res); + } + {// polygon + OperatorOverlaps op = (OperatorOverlaps) (OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Overlaps)); + String str1 = "{\"rings\":[[[0,0],[0,200],[200,200],[200,0],[0,0],[0,0],[0,0]]]}"; + MapGeometry mg1 = TestCommonMethods.fromJson(str1); + String str2 = "{\"x\":100,\"y\":100}"; + MapGeometry mg2 = TestCommonMethods.fromJson(str2); + + boolean res = op.execute((mg2.getGeometry()), (mg1.getGeometry()), + null, null); + assertTrue(!res); + res = op.execute((mg1.getGeometry()), (mg2.getGeometry()), null, + null); + assertTrue(!res); + } + {// polygon + OperatorOverlaps op = (OperatorOverlaps) (OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Overlaps)); + String str1 = "{\"rings\":[[[0,0],[0,200],[200,200],[200,0],[0,0]]]}"; + MapGeometry mg1 = TestCommonMethods.fromJson(str1); + MapGeometry mg2 = TestCommonMethods.fromJson(str1); + Transformation2D trans = new Transformation2D(); + trans.setShift(300, 0); + mg2.getGeometry().applyTransformation(trans); + boolean res = op.execute((mg2.getGeometry()), (mg1.getGeometry()), + null, null); + assertTrue(!res); + res = op.execute((mg1.getGeometry()), (mg2.getGeometry()), null, + null); + assertTrue(!res); + } + {// polygon + OperatorOverlaps op = (OperatorOverlaps) (OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Overlaps)); + String str1 = "{\"rings\":[[[0,0],[0,200],[200,200],[200,0],[0,0]]]}"; + MapGeometry mg1 = TestCommonMethods.fromJson(str1); + MapGeometry mg2 = TestCommonMethods.fromJson(str1); + Transformation2D trans = new Transformation2D(); + trans.setShift(30, 0); + mg2.getGeometry().applyTransformation(trans); + boolean res = op.execute((mg2.getGeometry()), (mg1.getGeometry()), + null, null); + assertTrue(res); + res = op.execute((mg1.getGeometry()), (mg2.getGeometry()), null, + null); + assertTrue(res); + } + {// polygon + OperatorOverlaps op = (OperatorOverlaps) (OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Overlaps)); + String str1 = "{\"rings\":[[[0,0],[0,200],[200,200],[200,0],[0,0]]]}"; + MapGeometry mg1 = TestCommonMethods.fromJson(str1); + MapGeometry mg2 = TestCommonMethods.fromJson(str1); + Transformation2D trans = new Transformation2D(); + trans.setShift(0, 0); + mg2.getGeometry().applyTransformation(trans); + boolean res = op.execute((mg2.getGeometry()), (mg1.getGeometry()), + null, null); + assertTrue(!res); + res = op.execute((mg1.getGeometry()), (mg2.getGeometry()), null, + null); + assertTrue(!res); + } + + {// polyline + OperatorOverlaps op = (OperatorOverlaps) (OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Overlaps)); + String str1 = "{\"paths\":[[[0,0],[100,0],[200,0]]]}"; + MapGeometry mg1 = TestCommonMethods.fromJson(str1); + MapGeometry mg2 = TestCommonMethods.fromJson(str1); + Transformation2D trans = new Transformation2D(); + trans.setShift(0, 0); + mg2.getGeometry().applyTransformation(trans); + boolean res = op.execute((mg2.getGeometry()), (mg1.getGeometry()), + null, null); + assertTrue(!res); + res = op.execute((mg1.getGeometry()), (mg2.getGeometry()), null, + null); + assertTrue(!res); + } + + {// polyline + OperatorOverlaps op = (OperatorOverlaps) (OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Overlaps)); + String str1 = "{\"paths\":[[[0,0],[100,0],[200,0]]]}"; + MapGeometry mg1 = TestCommonMethods.fromJson(str1); + MapGeometry mg2 = TestCommonMethods.fromJson(str1); + Transformation2D trans = new Transformation2D(); + trans.setShift(10, 0); + mg2.getGeometry().applyTransformation(trans); + boolean res = op.execute((mg2.getGeometry()), (mg1.getGeometry()), + null, null); + assertTrue(res); + res = op.execute((mg1.getGeometry()), (mg2.getGeometry()), null, + null); + assertTrue(res); + } + + {// polyline + OperatorOverlaps op = (OperatorOverlaps) (OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Overlaps)); + String str1 = "{\"paths\":[[[0,0],[100,0],[200,0]]]}"; + MapGeometry mg1 = TestCommonMethods.fromJson(str1); + MapGeometry mg2 = TestCommonMethods.fromJson(str1); + Transformation2D trans = new Transformation2D(); + trans.setShift(200, 0); + mg2.getGeometry().applyTransformation(trans); + boolean res = op.execute((mg2.getGeometry()), (mg1.getGeometry()), + null, null); + assertTrue(!res); + res = op.execute((mg1.getGeometry()), (mg2.getGeometry()), null, + null); + assertTrue(!res); + } + + {// Multi_point + OperatorOverlaps op = (OperatorOverlaps) (OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Overlaps)); + String str1 = "{\"points\":[[0,0],[0,200],[200,200],[200,0]]}"; + MapGeometry mg1 = TestCommonMethods.fromJson(str1); + MapGeometry mg2 = TestCommonMethods.fromJson(str1); + Transformation2D trans = new Transformation2D(); + trans.setShift(0, 0); + mg2.getGeometry().applyTransformation(trans); + boolean res = op.execute((mg2.getGeometry()), (mg1.getGeometry()), + null, null); + assertTrue(!res); + res = op.execute((mg1.getGeometry()), (mg2.getGeometry()), null, + null); + assertTrue(!res); + } + {// Multi_point + OperatorOverlaps op = (OperatorOverlaps) (OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Overlaps)); + String str1 = "{\"points\":[[0,0],[0,200],[200,200]]}"; + MapGeometry mg1 = TestCommonMethods.fromJson(str1); + String str2 = "{\"points\":[[0,0],[0,200]]}"; + MapGeometry mg2 = TestCommonMethods.fromJson(str2); + boolean res = op.execute((mg2.getGeometry()), (mg1.getGeometry()), + null, null); + assertTrue(!res); + res = op.execute((mg1.getGeometry()), (mg2.getGeometry()), null, + null); + assertTrue(!res); + } + {// Multi_point + OperatorOverlaps op = (OperatorOverlaps) (OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Overlaps)); + String str1 = "{\"points\":[[0,0],[0,200],[200,200]]}"; + MapGeometry mg1 = TestCommonMethods.fromJson(str1); + String str2 = "{\"points\":[[0,0],[0,200], [0,2]]}"; + MapGeometry mg2 = TestCommonMethods.fromJson(str2); + boolean res = op.execute((mg2.getGeometry()), (mg1.getGeometry()), + null, null); + assertTrue(res); + res = op.execute((mg1.getGeometry()), (mg2.getGeometry()), null, + null); + assertTrue(res); + } + } + + @Test + public void testPolygonPolygonEquals() { + OperatorEquals equals = (OperatorEquals) (OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Equals)); + SpatialReference sr = SpatialReference.create(102100); + @SuppressWarnings("unused") + double tolerance = sr + .getTolerance(VertexDescription.Semantics.POSITION); + + // Polygon1 and Polygon2 are topologically equal, but have differing + // number of vertices + String str1 = "{\"rings\":[[[0,0],[0,5],[0,7],[0,10],[0,10],[10,10],[10,0],[0,0]],[[1,1],[9,1],[9,9],[1,9],[1,1],[1,1]]]}"; + String str2 = "{\"rings\":[[[0,10],[10,10],[10,0],[0,0],[0,10]],[[9,1],[9,6],[9,9],[1,9],[1,1],[1,1],[9,1]]]}"; + + Polygon polygon1 = (Polygon) TestCommonMethods.fromJson(str1) + .getGeometry(); + Polygon polygon2 = (Polygon) TestCommonMethods.fromJson(str2) + .getGeometry(); + // wiggleGeometry(polygon1, tolerance, 1982); + // wiggleGeometry(polygon2, tolerance, 511); + + equals.accelerateGeometry(polygon1, sr, + Geometry.GeometryAccelerationDegree.enumHot); + equals.accelerateGeometry(polygon2, sr, + Geometry.GeometryAccelerationDegree.enumHot); + + boolean res = equals.execute(polygon1, polygon2, sr, null); + assertTrue(res); + equals.execute(polygon2, polygon1, sr, null); + assertTrue(res); + + // The outer rings of Polygon1 and Polygon2 are equal, but Polygon1 has + // a hole. + str1 = "{\"rings\":[[[0,0],[0,5],[0,10],[10,10],[10,0],[0,0]],[[1,1],[9,1],[9,9],[1,9],[1,1],[1,1]]]}"; + str2 = "{\"rings\":[[[0,10],[10,10],[5,10],[10,10],[10,0],[0,0],[0,10]]]}"; + polygon1 = (Polygon) TestCommonMethods.fromJson(str1).getGeometry(); + polygon2 = (Polygon) TestCommonMethods.fromJson(str2).getGeometry(); + + res = equals.execute(polygon1, polygon2, sr, null); + assertTrue(!res); + res = equals.execute(polygon2, polygon1, sr, null); + assertTrue(!res); + + // The rings are equal but rotated + str1 = "{\"rings\":[[[0,0],[0,10],[10,10],[10,0],[0,0]]]}"; + str2 = "{\"rings\":[[[0,10],[10,10],[10,0],[0,0],[0,10]]]}"; + + polygon1 = (Polygon) TestCommonMethods.fromJson(str1).getGeometry(); + polygon2 = (Polygon) TestCommonMethods.fromJson(str2).getGeometry(); + + res = equals.execute(polygon1, polygon2, sr, null); + assertTrue(res); + res = equals.execute(polygon2, polygon1, sr, null); + assertTrue(res); + + // The rings are equal but opposite orientation + str1 = "{\"rings\":[[[0,0],[0,10],[10,10],[10,0],[0,0]]]}"; + str2 = "{\"rings\":[[[0,0],[10,0],[10,10],[0,10],[0,0]]]}"; + + polygon1 = (Polygon) TestCommonMethods.fromJson(str1).getGeometry(); + polygon2 = (Polygon) TestCommonMethods.fromJson(str2).getGeometry(); + + res = equals.execute(polygon1, polygon2, sr, null); + assertTrue(!res); + res = equals.execute(polygon2, polygon1, sr, null); + assertTrue(!res); + + // The rings are equal but first polygon has two rings stacked + str1 = "{\"rings\":[[[0,0],[0,10],[10,10],[10,0],[0,0]],[[0,10],[10,10],[10,0],[0,0],[0,10]]]}"; + str2 = "{\"rings\":[[[0,10],[10,10],[10,0],[0,0],[0,10]]]}"; + polygon1 = (Polygon) TestCommonMethods.fromJson(str1).getGeometry(); + polygon2 = (Polygon) TestCommonMethods.fromJson(str2).getGeometry(); + + res = equals.execute(polygon1, polygon2, sr, null); + assertTrue(!res); + res = equals.execute(polygon2, polygon1, sr, null); + assertTrue(!res); + } + + @Test + public void testMultiPointMultiPointEquals() { + OperatorEquals equals = (OperatorEquals) OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Equals); + SpatialReference sr = SpatialReference.create(102100); + @SuppressWarnings("unused") + double tolerance = sr + .getTolerance(VertexDescription.Semantics.POSITION); + + MultiPoint multipoint1 = new MultiPoint(); + MultiPoint multipoint2 = new MultiPoint(); + + multipoint1.add(0, 0); + multipoint1.add(1, 1); + multipoint1.add(2, 2); + multipoint1.add(3, 3); + multipoint1.add(4, 4); + multipoint1.add(1, 1); + multipoint1.add(0, 0); + + multipoint2.add(4, 4); + multipoint2.add(3, 3); + multipoint2.add(2, 2); + multipoint2.add(1, 1); + multipoint2.add(0, 0); + multipoint2.add(2, 2); + + wiggleGeometry(multipoint1, 0.001, 123); + wiggleGeometry(multipoint2, 0.001, 5937); + boolean res = equals.execute(multipoint1, multipoint2, sr, null); + assertTrue(res); + res = equals.execute(multipoint2, multipoint1, sr, null); + assertTrue(res); + + multipoint1.add(1, 2); + res = equals.execute(multipoint1, multipoint2, sr, null); + assertTrue(!res); + res = equals.execute(multipoint2, multipoint1, sr, null); + assertTrue(!res); + } + + @Test + public void testMultiPointPointEquals() { + OperatorEquals equals = (OperatorEquals) (OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Equals)); + OperatorWithin within = (OperatorWithin) (OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Within)); + SpatialReference sr = SpatialReference.create(102100); + @SuppressWarnings("unused") + double tolerance = sr + .getTolerance(VertexDescription.Semantics.POSITION); + + MultiPoint multipoint1 = new MultiPoint(); + Point point2 = new Point(); + + multipoint1.add(2, 2); + multipoint1.add(2, 2); + + point2.setXY(2, 2); + + wiggleGeometry(multipoint1, 0.001, 123); + boolean res = equals.execute(multipoint1, point2, sr, null); + assertTrue(res); + res = equals.execute(point2, multipoint1, sr, null); + assertTrue(res); + + res = within.execute(multipoint1, point2, sr, null); + assertTrue(res); + res = within.execute(point2, multipoint1, sr, null); + assertTrue(res); + + multipoint1.add(4, 4); + res = equals.execute(multipoint1, point2, sr, null); + assertTrue(!res); + res = equals.execute(point2, multipoint1, sr, null); + assertTrue(!res); + + res = within.execute(multipoint1, point2, sr, null); + assertTrue(!res); + res = within.execute(point2, multipoint1, sr, null); + assertTrue(res); + } + + @Test + public void testPointPointEquals() { + OperatorEquals equals = (OperatorEquals) (OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Equals)); + OperatorWithin within = (OperatorWithin) (OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Within)); + OperatorContains contains = (OperatorContains) (OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Contains)); + OperatorDisjoint disjoint = (OperatorDisjoint) (OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Disjoint)); + SpatialReference sr = SpatialReference.create(102100); + @SuppressWarnings("unused") + double tolerance = sr + .getTolerance(VertexDescription.Semantics.POSITION); + + Point point1 = new Point(); + Point point2 = new Point(); + + point1.setXY(2, 2); + point2.setXY(2, 2); + + boolean res = equals.execute(point1, point2, sr, null); + assertTrue(res); + res = equals.execute(point2, point1, sr, null); + assertTrue(res); + + res = within.execute(point1, point2, sr, null); + assertTrue(res); + res = within.execute(point2, point1, sr, null); + assertTrue(res); + + res = contains.execute(point1, point2, sr, null); + assertTrue(res); + res = contains.execute(point2, point1, sr, null); + assertTrue(res); + + res = disjoint.execute(point1, point2, sr, null); + assertTrue(!res); + res = disjoint.execute(point2, point1, sr, null); + assertTrue(!res); + + point2.setXY(2, 3); + res = equals.execute(point1, point2, sr, null); + assertTrue(!res); + res = equals.execute(point2, point1, sr, null); + assertTrue(!res); + + res = within.execute(point1, point2, sr, null); + assertTrue(!res); + res = within.execute(point2, point1, sr, null); + assertTrue(!res); + + res = contains.execute(point1, point2, sr, null); + assertTrue(!res); + res = contains.execute(point2, point1, sr, null); + assertTrue(!res); + + res = disjoint.execute(point1, point2, sr, null); + assertTrue(res); + res = disjoint.execute(point2, point1, sr, null); + assertTrue(res); + } + + @Test + public void testPolygonPolygonDisjoint() { + OperatorDisjoint disjoint = (OperatorDisjoint) (OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Disjoint)); + SpatialReference sr = SpatialReference.create(102100); + double tolerance = sr + .getTolerance(VertexDescription.Semantics.POSITION); + + // Polygon1 and Polygon2 are topologically equal, but have differing + // number of vertices + String str1 = "{\"rings\":[[[0,0],[0,5],[0,10],[10,10],[10,0],[0,0]],[[1,1],[9,1],[9,9],[1,9],[1,1],[1,1]]]}"; + String str2 = "{\"rings\":[[[0,10],[10,10],[10,0],[0,0],[0,10]],[[9,1],[9,6],[9,9],[1,9],[1,1],[1,1],[9,1]]]}"; + + Polygon polygon1 = (Polygon) (TestCommonMethods.fromJson(str1) + .getGeometry()); + Polygon polygon2 = (Polygon) (TestCommonMethods.fromJson(str2) + .getGeometry()); + + boolean res = disjoint.execute(polygon1, polygon2, sr, null); + assertTrue(!res); + res = disjoint.execute(polygon2, polygon1, sr, null); + assertTrue(!res); + + // Polygon1 and Polygon2 touch at a point + str1 = "{\"rings\":[[[0,0],[0,5],[0,10],[10,10],[10,0],[0,0]],[[1,1],[9,1],[9,9],[1,9],[1,1],[1,1]]]}"; + str2 = "{\"rings\":[[[10,10],[10,15],[15,15],[15,10],[10,10]]]}"; + + polygon1 = (Polygon) (TestCommonMethods.fromJson(str1).getGeometry()); + polygon2 = (Polygon) (TestCommonMethods.fromJson(str2).getGeometry()); + wiggleGeometry(polygon1, tolerance, 1982); + wiggleGeometry(polygon2, tolerance, 511); + + res = disjoint.execute(polygon1, polygon2, sr, null); + assertTrue(!res); + res = disjoint.execute(polygon2, polygon1, sr, null); + assertTrue(!res); + + // Polygon1 and Polygon2 touch along the boundary + str1 = "{\"rings\":[[[0,0],[0,5],[0,10],[10,10],[10,0],[0,0]],[[1,1],[9,1],[9,9],[1,9],[1,1],[1,1]]]}"; + str2 = "{\"rings\":[[[10,0],[10,10],[15,10],[15,0],[10,0]]]}"; + + polygon1 = (Polygon) (TestCommonMethods.fromJson(str1).getGeometry()); + polygon2 = (Polygon) (TestCommonMethods.fromJson(str2).getGeometry()); + wiggleGeometry(polygon1, tolerance, 1982); + wiggleGeometry(polygon2, tolerance, 511); + + res = disjoint.execute(polygon1, polygon2, sr, null); + assertTrue(!res); + res = disjoint.execute(polygon2, polygon1, sr, null); + assertTrue(!res); + + // Polygon2 is inside of the hole of polygon1 + str1 = "{\"rings\":[[[0,0],[0,5],[0,10],[10,10],[10,0],[0,0]],[[1,1],[9,1],[9,9],[1,9],[1,1],[1,1]]]}"; + str2 = "{\"rings\":[[[2,2],[2,8],[8,8],[8,2],[2,2]]]}"; + + polygon1 = (Polygon) (TestCommonMethods.fromJson(str1).getGeometry()); + polygon2 = (Polygon) (TestCommonMethods.fromJson(str2).getGeometry()); + + res = disjoint.execute(polygon1, polygon2, sr, null); + assertTrue(res); + res = disjoint.execute(polygon2, polygon1, sr, null); + assertTrue(res); + + // Polygon2 is inside of the hole of polygon1 + str1 = "{\"rings\":[[[0,0],[0,5],[5,5],[5,0]],[[10,0],[10,10],[20,10],[20,0]]]}"; + str2 = "{\"rings\":[[[0,-10],[0,-5],[5,-5],[5,-10]],[[11,1],[11,9],[19,9],[19,1]]]}"; + + polygon1 = (Polygon) (TestCommonMethods.fromJson(str1).getGeometry()); + polygon2 = (Polygon) (TestCommonMethods.fromJson(str2).getGeometry()); + + res = disjoint.execute(polygon1, polygon2, sr, null); + assertTrue(!res); + res = disjoint.execute(polygon2, polygon1, sr, null); + assertTrue(!res); + + polygon1 = (Polygon) OperatorDensifyByLength.local().execute(polygon1, 0.5, null); + disjoint.accelerateGeometry(polygon1, sr, GeometryAccelerationDegree.enumHot); + res = disjoint.execute(polygon1, polygon2, sr, null); + assertTrue(!res); + res = disjoint.execute(polygon2, polygon1, sr, null); + assertTrue(!res); + + polygon1.reverseAllPaths(); + polygon2.reverseAllPaths(); + res = disjoint.execute(polygon1, polygon2, sr, null); + assertTrue(!res); + res = disjoint.execute(polygon2, polygon1, sr, null); + assertTrue(!res); + + // Polygon1 contains polygon2, but polygon2 is counterclockwise. + str1 = "{\"rings\":[[[0,0],[10,0],[10,10],[0,10],[0,0]],[[11,0],[11,10],[21,10],[21,0],[11,0]]]}"; + str2 = "{\"rings\":[[[2,2],[8,2],[8,8],[2,8],[2,2]]]}"; + polygon1 = (Polygon) (TestCommonMethods.fromJson(str1).getGeometry()); + polygon2 = (Polygon) (TestCommonMethods.fromJson(str2).getGeometry()); + + + res = disjoint.execute(polygon1, polygon2, sr, null); + assertTrue(!res); + res = disjoint.execute(polygon2, polygon1, sr, null); + assertTrue(!res); + + polygon1 = (Polygon) OperatorDensifyByLength.local().execute(polygon1, 0.5, null); + disjoint.accelerateGeometry(polygon1, sr, GeometryAccelerationDegree.enumHot); + res = disjoint.execute(polygon1, polygon2, sr, null); + assertTrue(!res); + res = disjoint.execute(polygon2, polygon1, sr, null); + assertTrue(!res); + + str1 = "{\"rings\":[[[0,0],[0,10],[10,10],[10,0],[0,0]],[[0,20],[0,30],[10,30],[10,20],[0,20]],[[20,20],[20,30],[30,30],[30,20],[20,20]],[[20,0],[20,10],[30,10],[30,0],[20,0]]]}"; + str2 = "{\"rings\":[[[14,14],[14,16],[16,16],[16,14],[14,14]]]}"; + polygon1 = (Polygon) (TestCommonMethods.fromJson(str1).getGeometry()); + polygon2 = (Polygon) (TestCommonMethods.fromJson(str2).getGeometry()); + + + res = disjoint.execute(polygon1, polygon2, sr, null); + assertTrue(res); + res = disjoint.execute(polygon2, polygon1, sr, null); + assertTrue(res); + + polygon1 = (Polygon) OperatorDensifyByLength.local().execute(polygon1, 0.5, null); + disjoint.accelerateGeometry(polygon1, sr, GeometryAccelerationDegree.enumHot); + res = disjoint.execute(polygon1, polygon2, sr, null); + assertTrue(res); + res = disjoint.execute(polygon2, polygon1, sr, null); + assertTrue(res); + } + + @Test + public void testPolylinePolylineDisjoint() { + OperatorDisjoint disjoint = (OperatorDisjoint) (OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Disjoint)); + SpatialReference sr = SpatialReference.create(102100); + double tolerance = sr + .getTolerance(VertexDescription.Semantics.POSITION); + + // Polyline1 and Polyline2 touch at a point + String str1 = "{\"paths\":[[[0,0],[0,5],[0,10],[10,10],[10,0],[0,0]],[[1,1],[9,1],[9,9],[1,9],[1,1],[1,1]]]}"; + String str2 = "{\"paths\":[[[10,10],[10,15],[15,15],[15,10],[10,10]]]}"; + + Polyline polyline1 = (Polyline) (TestCommonMethods.fromJson(str1) + .getGeometry()); + Polyline polyline2 = (Polyline) (TestCommonMethods.fromJson(str2) + .getGeometry()); + wiggleGeometry(polyline1, tolerance, 1982); + wiggleGeometry(polyline2, tolerance, 511); + + boolean res = disjoint.execute(polyline1, polyline2, sr, null); + assertTrue(!res); + res = disjoint.execute(polyline2, polyline1, sr, null); + assertTrue(!res); + + // Polyline1 and Polyline2 touch along the boundary + str1 = "{\"paths\":[[[0,0],[0,5],[0,10],[10,10],[10,0],[0,0]],[[1,1],[9,1],[9,9],[1,9],[1,1],[1,1]]]}"; + str2 = "{\"paths\":[[[10,0],[10,10],[15,10],[15,0],[10,0]]]}"; + + polyline1 = (Polyline) (TestCommonMethods.fromJson(str1).getGeometry()); + polyline2 = (Polyline) (TestCommonMethods.fromJson(str2).getGeometry()); + wiggleGeometry(polyline1, tolerance, 1982); + wiggleGeometry(polyline2, tolerance, 511); + + res = disjoint.execute(polyline1, polyline2, sr, null); + assertTrue(!res); + res = disjoint.execute(polyline2, polyline1, sr, null); + assertTrue(!res); + + // Polyline2 does not intersect with Polyline1 + str1 = "{\"paths\":[[[0,0],[0,5],[0,10],[10,10],[10,0],[0,0]],[[1,1],[9,1],[9,9],[1,9],[1,1],[1,1]]]}"; + str2 = "{\"paths\":[[[2,2],[2,8],[8,8],[8,2],[2,2]]]}"; + + polyline1 = (Polyline) (TestCommonMethods.fromJson(str1).getGeometry()); + polyline2 = (Polyline) (TestCommonMethods.fromJson(str2).getGeometry()); + + res = disjoint.execute(polyline1, polyline2, sr, null); + assertTrue(res); + res = disjoint.execute(polyline2, polyline1, sr, null); + assertTrue(res); + } + + @Test + public void testPolygonPolylineDisjoint() { + OperatorDisjoint disjoint = (OperatorDisjoint) (OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Disjoint)); + SpatialReference sr = SpatialReference.create(102100); + @SuppressWarnings("unused") + double tolerance = sr + .getTolerance(VertexDescription.Semantics.POSITION); + + Polygon polygon1 = new Polygon(); + Polyline polyline2 = new Polyline(); + + polygon1.startPath(0, 0); + polygon1.lineTo(0, 10); + polygon1.lineTo(10, 10); + polygon1.lineTo(10, 0); + + polygon1.startPath(1, 1); + polygon1.lineTo(9, 1); + polygon1.lineTo(9, 9); + polygon1.lineTo(1, 9); + + polyline2.startPath(3, 3); + polyline2.lineTo(6, 6); + + boolean res = disjoint.execute(polyline2, polygon1, sr, null); + assertTrue(res); + res = disjoint.execute(polygon1, polyline2, sr, null); + assertTrue(res); + + polyline2.startPath(0, 0); + polyline2.lineTo(0, 5); + + res = disjoint.execute(polyline2, polygon1, sr, null); + assertTrue(!res); + res = disjoint.execute(polygon1, polyline2, sr, null); + assertTrue(!res); + + polygon1.setEmpty(); + polyline2.setEmpty(); + + polygon1.startPath(0, 0); + polygon1.lineTo(0, 10); + polygon1.lineTo(10, 10); + polygon1.lineTo(10, 0); + + polyline2.startPath(2, 2); + polyline2.lineTo(4, 4); + + OperatorFactoryLocal factory = OperatorFactoryLocal.getInstance(); + OperatorSimplify simplify_op = (OperatorSimplify) factory + .getOperator(Operator.Type.Simplify); + simplify_op.isSimpleAsFeature(polygon1, sr, null); + simplify_op.isSimpleAsFeature(polyline2, sr, null); + + res = disjoint.execute(polyline2, polygon1, sr, null); + assertTrue(!res); + res = disjoint.execute(polygon1, polyline2, sr, null); + assertTrue(!res); + } + + @Test + public void testPolylineMultiPointDisjoint() { + OperatorDisjoint disjoint = (OperatorDisjoint) (OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Disjoint)); + SpatialReference sr = SpatialReference.create(102100); + @SuppressWarnings("unused") + double tolerance = sr + .getTolerance(VertexDescription.Semantics.POSITION); + + Polyline polyline1 = new Polyline(); + MultiPoint multipoint2 = new MultiPoint(); + + polyline1.startPath(0, 0); + polyline1.lineTo(2, 0); + polyline1.lineTo(4, 2); + + multipoint2.add(1, 1); + multipoint2.add(2, 2); + multipoint2.add(3, 0); + + boolean res = disjoint.execute(polyline1, multipoint2, sr, null); + assertTrue(res); + res = disjoint.execute(multipoint2, polyline1, sr, null); + assertTrue(res); + + multipoint2.add(3, 1); + res = disjoint.execute(polyline1, multipoint2, sr, null); + assertTrue(!res); + res = disjoint.execute(multipoint2, polyline1, sr, null); + assertTrue(!res); + + polyline1.startPath(1, -4); + polyline1.lineTo(1, -3); + polyline1.lineTo(1, -2); + polyline1.lineTo(1, -1); + polyline1.lineTo(1, 0); + polyline1.lineTo(1, 1); + + disjoint.accelerateGeometry(polyline1, sr, + Geometry.GeometryAccelerationDegree.enumHot); + res = disjoint.execute(polyline1, multipoint2, sr, null); + assertTrue(!res); + res = disjoint.execute(multipoint2, polyline1, sr, null); + assertTrue(!res); + } + + @Test + public void testPolylinePointDisjoint() { + OperatorDisjoint disjoint = (OperatorDisjoint) (OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Disjoint)); + OperatorContains contains = (OperatorContains) (OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Contains)); + SpatialReference sr = SpatialReference.create(102100); + @SuppressWarnings("unused") + double tolerance = sr + .getTolerance(VertexDescription.Semantics.POSITION); + + Polyline polyline1 = new Polyline(); + Point point2 = new Point(); + + polyline1.startPath(0, 0); + polyline1.lineTo(2, 0); + polyline1.lineTo(4, 2); + + point2.setXY(1, 1); + + boolean res = disjoint.execute(polyline1, point2, sr, null); + assertTrue(res); + res = disjoint.execute(point2, polyline1, sr, null); + assertTrue(res); + + res = contains.execute(polyline1, point2, sr, null); + assertTrue(!res); + res = contains.execute(point2, polyline1, sr, null); + assertTrue(!res); + + point2.setXY(4, 2); + + polyline1 = (Polyline) OperatorDensifyByLength.local().execute( + polyline1, 0.1, null); + disjoint.accelerateGeometry(polyline1, sr, + Geometry.GeometryAccelerationDegree.enumHot); + + res = disjoint.execute(polyline1, point2, sr, null); + assertTrue(!res); + res = disjoint.execute(point2, polyline1, sr, null); + assertTrue(!res); + + res = contains.execute(polyline1, point2, sr, null); + assertTrue(!res); + res = contains.execute(point2, polyline1, sr, null); + assertTrue(!res); + + polyline1.setEmpty(); + point2.setEmpty(); + + polyline1.startPath(659062.37370000035, 153070.85220000148); + polyline1.lineTo(660916.47940000147, 151481.10269999877); + point2.setXY(659927.85020000115, 152328.77430000156); + + res = contains.execute(polyline1, point2, + SpatialReference.create(54004), null); + assertTrue(res); + } + + @Test + public void testMultiPointMultiPointDisjoint() { + OperatorDisjoint disjoint = (OperatorDisjoint) (OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Disjoint)); + SpatialReference sr = SpatialReference.create(102100); + @SuppressWarnings("unused") + double tolerance = sr + .getTolerance(VertexDescription.Semantics.POSITION); + + MultiPoint multipoint1 = new MultiPoint(); + MultiPoint multipoint2 = new MultiPoint(); + + multipoint1.add(2, 2); + multipoint1.add(2, 5); + multipoint1.add(4, 1); + multipoint1.add(4, 4); + multipoint1.add(4, 7); + multipoint1.add(6, 2); + multipoint1.add(6, 6); + multipoint1.add(4, 1); + multipoint1.add(6, 6); + + multipoint2.add(0, 1); + multipoint2.add(0, 7); + multipoint2.add(4, 2); + multipoint2.add(4, 6); + multipoint2.add(6, 4); + multipoint2.add(4, 2); + multipoint2.add(0, 1); + + boolean res = disjoint.execute(multipoint1, multipoint2, sr, null); + assertTrue(res); + res = disjoint.execute(multipoint2, multipoint1, sr, null); + assertTrue(res); + + multipoint2.add(2, 2); + res = disjoint.execute(multipoint1, multipoint2, sr, null); + assertTrue(!res); + res = disjoint.execute(multipoint2, multipoint1, sr, null); + assertTrue(!res); + } + + @Test + public void testMultiPointPointDisjoint() { + OperatorDisjoint disjoint = (OperatorDisjoint) (OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Disjoint)); + OperatorContains contains = (OperatorContains) (OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Contains)); + SpatialReference sr = SpatialReference.create(102100); + @SuppressWarnings("unused") + double tolerance = sr + .getTolerance(VertexDescription.Semantics.POSITION); + + MultiPoint multipoint1 = new MultiPoint(); + Point point2 = new Point(); + + multipoint1.add(2, 2); + multipoint1.add(2, 5); + multipoint1.add(4, 1); + multipoint1.add(4, 4); + multipoint1.add(4, 7); + multipoint1.add(6, 2); + multipoint1.add(6, 6); + multipoint1.add(4, 1); + multipoint1.add(6, 6); + + point2.setXY(2, 6); + + boolean res = disjoint.execute(multipoint1, point2, sr, null); + assertTrue(res); + res = disjoint.execute(point2, multipoint1, sr, null); + assertTrue(res); + + res = contains.execute(multipoint1, point2, sr, null); + assertTrue(!res); + res = contains.execute(point2, multipoint1, sr, null); + assertTrue(!res); + + multipoint1.add(2, 6); + res = disjoint.execute(multipoint1, point2, sr, null); + assertTrue(!res); + res = disjoint.execute(point2, multipoint1, sr, null); + assertTrue(!res); + + res = contains.execute(multipoint1, point2, sr, null); + assertTrue(res); + res = contains.execute(point2, multipoint1, sr, null); + assertTrue(!res); + } + + @Test + public void testPolygonMultiPointDisjoint() { + OperatorDisjoint disjoint = (OperatorDisjoint) (OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Disjoint)); + SpatialReference sr = SpatialReference.create(102100); + @SuppressWarnings("unused") + double tolerance = sr + .getTolerance(VertexDescription.Semantics.POSITION); + + Polygon polygon1 = new Polygon(); + MultiPoint multipoint2 = new MultiPoint(); + + polygon1.startPath(0, 0); + polygon1.lineTo(0, 10); + polygon1.lineTo(10, 10); + + multipoint2.add(-1, 5); + multipoint2.add(5, 11); + multipoint2.add(11, 5); + multipoint2.add(5, -1); + + boolean res = disjoint.execute(polygon1, multipoint2, sr, null); + assertTrue(res); + res = disjoint.execute(multipoint2, polygon1, sr, null); + assertTrue(res); + + polygon1.startPath(15, 0); + polygon1.lineTo(15, 10); + polygon1.lineTo(25, 10); + polygon1.lineTo(25, 0); + + multipoint2.add(14, 5); + multipoint2.add(20, 11); + multipoint2.add(26, 5); + multipoint2.add(20, -1); + + res = disjoint.execute(polygon1, multipoint2, sr, null); + assertTrue(res); + res = disjoint.execute(multipoint2, polygon1, sr, null); + assertTrue(res); + + multipoint2.add(20, 5); + + res = disjoint.execute(polygon1, multipoint2, sr, null); + assertTrue(!res); + res = disjoint.execute(multipoint2, polygon1, sr, null); + assertTrue(!res); + } + + @Test + public void testPolygonMultiPointTouches() { + OperatorTouches touches = (OperatorTouches) (OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Touches)); + SpatialReference sr = SpatialReference.create(102100); + @SuppressWarnings("unused") + double tolerance = sr + .getTolerance(VertexDescription.Semantics.POSITION); + + Polygon polygon1 = new Polygon(); + MultiPoint multipoint2 = new MultiPoint(); + + polygon1.startPath(0, 0); + polygon1.lineTo(0, 10); + polygon1.lineTo(10, 10); + polygon1.lineTo(10, 0); + + multipoint2.add(-1, 5); + multipoint2.add(5, 11); + multipoint2.add(11, 5); + multipoint2.add(5, -1); + + boolean res = touches.execute(polygon1, multipoint2, sr, null); + assertTrue(!res); + res = touches.execute(multipoint2, polygon1, sr, null); + assertTrue(!res); + + multipoint2.add(5, 10); + + res = touches.execute(polygon1, multipoint2, sr, null); + assertTrue(res); + res = touches.execute(multipoint2, polygon1, sr, null); + assertTrue(res); + + multipoint2.add(5, 5); + res = touches.execute(polygon1, multipoint2, sr, null); + assertTrue(!res); + res = touches.execute(multipoint2, polygon1, sr, null); + assertTrue(!res); + } + + @Test + public void testPolygonPointTouches() { + OperatorTouches touches = (OperatorTouches) (OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Touches)); + SpatialReference sr = SpatialReference.create(102100); + @SuppressWarnings("unused") + double tolerance = sr + .getTolerance(VertexDescription.Semantics.POSITION); + + Polygon polygon1 = new Polygon(); + Point point2 = new Point(); + + polygon1.startPath(0, 0); + polygon1.lineTo(0, 10); + polygon1.lineTo(10, 10); + polygon1.lineTo(10, 0); + + point2.setXY(5, 5); + + boolean res = touches.execute(polygon1, point2, sr, null); + assertTrue(!res); + res = touches.execute(point2, polygon1, sr, null); + assertTrue(!res); + + point2.setXY(5, 10); + + res = touches.execute(polygon1, point2, sr, null); + assertTrue(res); + res = touches.execute(point2, polygon1, sr, null); + assertTrue(res); + } + + @Test + public void testPolygonPolygonTouches() { + OperatorTouches touches = (OperatorTouches) (OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Touches)); + SpatialReference sr = SpatialReference.create(102100); + double tolerance = sr + .getTolerance(VertexDescription.Semantics.POSITION); + + // Polygon1 and Polygon2 touch at a point + String str1 = "{\"rings\":[[[0,0],[0,5],[0,10],[10,10],[10,0],[0,0]],[[1,1],[9,1],[9,9],[1,9],[1,1],[1,1]]]}"; + String str2 = "{\"rings\":[[[10,10],[10,15],[15,15],[15,10],[10,10]]]}"; + + Polygon polygon1 = (Polygon) (TestCommonMethods.fromJson(str1) + .getGeometry()); + Polygon polygon2 = (Polygon) (TestCommonMethods.fromJson(str2) + .getGeometry()); + wiggleGeometry(polygon1, tolerance, 1982); + wiggleGeometry(polygon2, tolerance, 511); + + boolean res = touches.execute(polygon1, polygon2, sr, null); + assertTrue(res); + res = touches.execute(polygon2, polygon1, sr, null); + assertTrue(res); + + // Polygon1 and Polygon2 touch along the boundary + str1 = "{\"rings\":[[[0,0],[0,5],[0,10],[10,10],[10,0],[0,0]],[[1,1],[9,1],[9,9],[1,9],[1,1],[1,1]]]}"; + str2 = "{\"rings\":[[[10,0],[10,10],[15,10],[15,0],[10,0]]]}"; + + polygon1 = (Polygon) (TestCommonMethods.fromJson(str1).getGeometry()); + polygon2 = (Polygon) (TestCommonMethods.fromJson(str2).getGeometry()); + wiggleGeometry(polygon1, tolerance, 1982); + wiggleGeometry(polygon2, tolerance, 511); + + res = touches.execute(polygon1, polygon2, sr, null); + assertTrue(res); + res = touches.execute(polygon2, polygon1, sr, null); + assertTrue(res); + + // Polygon1 and Polygon2 touch at a corner of Polygon1 and a diagonal of + // Polygon2 + str1 = "{\"rings\":[[[0,0],[0,5],[0,10],[10,10],[10,0],[0,0]],[[1,1],[9,1],[9,9],[1,9],[1,1],[1,1]]]}"; + str2 = "{\"rings\":[[[15,5],[5,15],[15,15],[15,5]]]}"; + + polygon1 = (Polygon) (TestCommonMethods.fromJson(str1).getGeometry()); + polygon2 = (Polygon) (TestCommonMethods.fromJson(str2).getGeometry()); + wiggleGeometry(polygon1, tolerance, 1982); + wiggleGeometry(polygon2, tolerance, 511); + + res = touches.execute(polygon1, polygon2, sr, null); + assertTrue(res); + res = touches.execute(polygon2, polygon1, sr, null); + assertTrue(res); + + // Polygon1 and Polygon2 do not touch + str1 = "{\"rings\":[[[0,0],[0,5],[0,10],[10,10],[10,0],[0,0]],[[1,1],[9,1],[9,9],[1,9],[1,1],[1,1]]]}"; + str2 = "{\"rings\":[[[5,5],[5,15],[15,15],[15,5],[5,5]]]}"; + + polygon1 = (Polygon) (TestCommonMethods.fromJson(str1).getGeometry()); + polygon2 = (Polygon) (TestCommonMethods.fromJson(str2).getGeometry()); + + res = touches.execute(polygon1, polygon2, sr, null); + assertTrue(!res); + res = touches.execute(polygon2, polygon1, sr, null); + assertTrue(!res); + + polygon1.setEmpty(); + polygon2.setEmpty(); + + polygon1.startPath(0, 0); + polygon1.lineTo(0, 1); + polygon1.lineTo(-1, 0); + + polygon2.startPath(0, 0); + polygon2.lineTo(0, 1); + polygon2.lineTo(1, 0); + + res = touches.execute(polygon1, polygon2, sr, null); + assertTrue(res); + res = touches.execute(polygon2, polygon1, sr, null); + assertTrue(res); + } + + @Test + public void testPolygonPolylineTouches() { + OperatorTouches touches = (OperatorTouches) (OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Touches)); + SpatialReference sr = SpatialReference.create(102100); + double tolerance = sr + .getTolerance(VertexDescription.Semantics.POSITION); + + // Polygon1 and Polyline2 touch at a point + String str1 = "{\"rings\":[[[0,0],[0,5],[0,10],[10,10],[10,0]],[[1,1],[9,1],[9,9],[1,9],[1,1],[1,1]]]}"; + String str2 = "{\"paths\":[[[10,10],[10,15],[15,15],[15,10]]]}"; + + Polygon polygon1 = (Polygon) (TestCommonMethods.fromJson(str1) + .getGeometry()); + Polyline polyline2 = (Polyline) (TestCommonMethods.fromJson(str2) + .getGeometry()); + wiggleGeometry(polygon1, tolerance, 1982); + wiggleGeometry(polyline2, tolerance, 511); + + boolean res = touches.execute(polygon1, polyline2, sr, null); + assertTrue(res); + res = touches.execute(polyline2, polygon1, sr, null); + assertTrue(res); + + // Polygon1 and Polyline2 overlap along the boundary + str1 = "{\"rings\":[[[0,0],[0,5],[0,10],[10,10],[10,0],[0,0]],[[1,1],[9,1],[9,9],[1,9],[1,1],[1,1]]]}"; + str2 = "{\"paths\":[[[10,0],[10,10],[15,10],[15,0]]]}"; + + polygon1 = (Polygon) (TestCommonMethods.fromJson(str1).getGeometry()); + polyline2 = (Polyline) (TestCommonMethods.fromJson(str2).getGeometry()); + wiggleGeometry(polygon1, tolerance, 1982); + wiggleGeometry(polyline2, tolerance, 511); + + res = touches.execute(polygon1, polyline2, sr, null); + assertTrue(res); + res = touches.execute(polyline2, polygon1, sr, null); + assertTrue(res); + + str1 = "{\"rings\":[[[0,0],[0,5],[0,10],[10,10],[10,0],[0,0]],[[1,1],[9,1],[9,9],[1,9],[1,1],[1,1]]]}"; + str2 = "{\"paths\":[[[15,5],[5,15],[15,15],[15,5]]]}"; + + polygon1 = (Polygon) (TestCommonMethods.fromJson(str1).getGeometry()); + polyline2 = (Polyline) (TestCommonMethods.fromJson(str2).getGeometry()); + wiggleGeometry(polygon1, tolerance, 1982); + wiggleGeometry(polyline2, tolerance, 511); + + res = touches.execute(polygon1, polyline2, sr, null); + assertTrue(res); + res = touches.execute(polyline2, polygon1, sr, null); + assertTrue(res); + + str1 = "{\"rings\":[[[10,10],[10,0],[0,0],[0,10],[10,10]],[[1,1],[9,1],[9,9],[1,9],[1,1],[1,1]]]}"; + str2 = "{\"paths\":[[[15,5],[5,15],[15,15]]]}"; + + polygon1 = (Polygon) (TestCommonMethods.fromJson(str1).getGeometry()); + polyline2 = (Polyline) (TestCommonMethods.fromJson(str2).getGeometry()); + wiggleGeometry(polygon1, tolerance, 1982); + wiggleGeometry(polyline2, tolerance, 511); + + res = touches.execute(polygon1, polyline2, sr, null); + assertTrue(res); + res = touches.execute(polyline2, polygon1, sr, null); + assertTrue(res); + } + + @Test + public void testPolylinePolylineTouches() { + OperatorTouches touches = (OperatorTouches) (OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Touches)); + SpatialReference sr = SpatialReference.create(102100); + double tolerance = sr + .getTolerance(VertexDescription.Semantics.POSITION); + + // Polyline1 and Polyline2 touch at a point + String str1 = "{\"paths\":[[[0,0],[0,5],[0,10],[10,10],[10,0]],[[1,1],[9,1],[9,9],[1,9],[1,1],[1,1]]]}"; + String str2 = "{\"paths\":[[[10,10],[10,15],[15,15],[15,10]]]}"; + + Polyline polyline1 = (Polyline) (TestCommonMethods.fromJson(str1) + .getGeometry()); + Polyline polyline2 = (Polyline) (TestCommonMethods.fromJson(str2) + .getGeometry()); + + boolean res = touches.execute(polyline1, polyline2, sr, null); + assertTrue(res); + res = touches.execute(polyline2, polyline1, sr, null); + assertTrue(res); + + // Polyline1 and Polyline2 overlap along the boundary + str1 = "{\"paths\":[[[0,0],[0,5],[0,10],[10,10],[10,0],[0,0]],[[1,1],[9,1],[9,9],[1,9],[1,1],[1,1]]]}"; + str2 = "{\"paths\":[[[10,0],[10,10],[15,10],[15,0],[10,0]]]}"; + + polyline1 = (Polyline) (TestCommonMethods.fromJson(str1).getGeometry()); + polyline2 = (Polyline) (TestCommonMethods.fromJson(str2).getGeometry()); + + res = touches.execute(polyline1, polyline2, sr, null); + assertTrue(!res); + res = touches.execute(polyline2, polyline1, sr, null); + assertTrue(!res); + + // Polyline1 and Polyline2 intersect at interiors + str1 = "{\"paths\":[[[0,0],[0,5],[0,10],[10,10],[10,0],[0,0]],[[1,1],[9,1],[9,9],[1,9],[1,1],[1,1]]]}"; + str2 = "{\"paths\":[[[15,5],[5,15],[15,15],[15,5]]]}"; + + polyline1 = (Polyline) (TestCommonMethods.fromJson(str1).getGeometry()); + polyline2 = (Polyline) (TestCommonMethods.fromJson(str2).getGeometry()); + + res = touches.execute(polyline1, polyline2, sr, null); + assertTrue(!res); + res = touches.execute(polyline2, polyline1, sr, null); + assertTrue(!res); + + // Polyline1 and Polyline2 touch at an endpoint of Polyline1 and + // interior of Polyline2 (but Polyline1 is closed) + str1 = "{\"paths\":[[[10,10],[10,0],[0,0],[0,10],[10,10]],[[1,1],[9,1],[9,9],[1,9],[1,1],[1,1]]]}"; + str2 = "{\"paths\":[[[15,5],[5,15],[15,15]]]}"; + + polyline1 = (Polyline) (TestCommonMethods.fromJson(str1).getGeometry()); + polyline2 = (Polyline) (TestCommonMethods.fromJson(str2).getGeometry()); + + res = touches.execute(polyline1, polyline2, sr, null); + assertTrue(!res); + res = touches.execute(polyline2, polyline1, sr, null); + assertTrue(!res); + + // Polyline1 and Polyline2 touch at an endpoint of Polyline1 and + // interior of Polyline2 (same as previous case, but Polyline1 is not + // closed) + str1 = "{\"paths\":[[[10,10],[10,0],[0,0],[0,10]],[[1,1],[9,1],[9,9],[1,9],[1,1],[1,1]]]}"; + str2 = "{\"paths\":[[[15,5],[5,15],[15,15]]]}"; + + polyline1 = (Polyline) (TestCommonMethods.fromJson(str1).getGeometry()); + polyline2 = (Polyline) (TestCommonMethods.fromJson(str2).getGeometry()); + + res = touches.execute(polyline1, polyline2, sr, null); + assertTrue(res); + res = touches.execute(polyline2, polyline1, sr, null); + assertTrue(res); + + str1 = "{\"paths\":[[[10,10],[10,0],[0,0],[0,10]],[[1,1],[9,1],[9,9],[1,9],[6, 9]]]}"; + str2 = "{\"paths\":[[[15,5],[5,15],[15,15],[15,5]]]}"; + + polyline1 = (Polyline) (TestCommonMethods.fromJson(str1).getGeometry()); + polyline2 = (Polyline) (TestCommonMethods.fromJson(str2).getGeometry()); + + res = touches.execute(polyline1, polyline2, sr, null); + assertTrue(res); + res = touches.execute(polyline2, polyline1, sr, null); + assertTrue(res); + + polyline1.setEmpty(); + polyline2.setEmpty(); + + polyline1.startPath(-2, -2); + polyline1.lineTo(-1, -1); + polyline1.lineTo(1, 1); + polyline1.lineTo(2, 2); + + polyline2.startPath(-2, 2); + polyline2.lineTo(-1, 1); + polyline2.lineTo(1, -1); + polyline2.lineTo(2, -2); + + res = touches.execute(polyline2, polyline1, sr, null); + assertTrue(!res); + + polyline1.setEmpty(); + polyline2.setEmpty(); + + polyline1.startPath(-2, -2); + polyline1.lineTo(-1, -1); + polyline1.lineTo(1, 1); + polyline1.lineTo(2, 2); + + polyline2.startPath(-2, 2); + polyline2.lineTo(-1, 1); + polyline2.lineTo(1, -1); + + res = touches.execute(polyline1, polyline2, sr, null); + assertTrue(!res); + + polyline1.setEmpty(); + polyline2.setEmpty(); + + polyline1.startPath(-1, -1); + polyline1.lineTo(0, 0); + polyline1.lineTo(1, 1); + + polyline2.startPath(-1, 1); + polyline2.lineTo(0, 0); + polyline2.lineTo(1, -1); + + res = touches.execute(polyline2, polyline1, sr, null); + assertTrue(!res); + + polyline1.setEmpty(); + polyline2.setEmpty(); + polyline1.startPath(0, 0); + polyline1.lineTo(0, 1); + polyline1.lineTo(0, 0); + polyline2.startPath(0, 1); + polyline2.lineTo(0, 2); + polyline2.lineTo(0, 1); + + res = touches.execute(polyline2, polyline1, sr, null); + assertTrue(!res); + + res = touches.execute(polyline1, polyline2, sr, null); + assertTrue(!res); + } + + @Test + public void testPolylineMultiPointTouches() { + OperatorTouches touches = (OperatorTouches) (OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Touches)); + SpatialReference sr = SpatialReference.create(102100); + @SuppressWarnings("unused") + double tolerance = sr + .getTolerance(VertexDescription.Semantics.POSITION); + + Polyline polyline1 = new Polyline(); + MultiPoint multipoint2 = new MultiPoint(); + + polyline1.startPath(0, 0); + polyline1.lineTo(2, 0); + polyline1.lineTo(4, 2); + + multipoint2.add(1, 1); + multipoint2.add(2, 2); + multipoint2.add(3, 0); + + boolean res = touches.execute(polyline1, multipoint2, sr, null); + assertTrue(!res); + res = touches.execute(multipoint2, polyline1, sr, null); + assertTrue(!res); + + polyline1.startPath(1, -4); + polyline1.lineTo(1, -3); + polyline1.lineTo(1, -2); + polyline1.lineTo(1, -1); + polyline1.lineTo(1, 0); + polyline1.lineTo(1, 1); + + touches.accelerateGeometry(polyline1, sr, + Geometry.GeometryAccelerationDegree.enumHot); + res = touches.execute(polyline1, multipoint2, sr, null); + assertTrue(res); + res = touches.execute(multipoint2, polyline1, sr, null); + assertTrue(res); + + multipoint2.add(3, 1); + res = touches.execute(polyline1, multipoint2, sr, null); + assertTrue(!res); + res = touches.execute(multipoint2, polyline1, sr, null); + assertTrue(!res); + + polyline1.startPath(0, 0); + polyline1.lineTo(2, 0); + + polyline1.startPath(2, 1); + polyline1.lineTo(2, -1); + + multipoint2.add(2, 0); + + res = touches.execute(polyline1, multipoint2, sr, null); + assertTrue(!res); + res = touches.execute(multipoint2, polyline1, sr, null); + assertTrue(!res); + } + + @Test + public void testPolylineMultiPointCrosses() { + OperatorCrosses crosses = (OperatorCrosses) (OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Crosses)); + SpatialReference sr = SpatialReference.create(102100); + double tolerance = sr + .getTolerance(VertexDescription.Semantics.POSITION); + + Polyline polyline1 = new Polyline(); + MultiPoint multipoint2 = new MultiPoint(); + + polyline1.startPath(0, 0); + polyline1.lineTo(2, 0); + polyline1.lineTo(4, 2); + + multipoint2.add(1, 1); + multipoint2.add(2, 2); + multipoint2.add(3, 0); + multipoint2.add(0, 0); + + boolean res = crosses.execute(polyline1, multipoint2, sr, null); + assertTrue(!res); + res = crosses.execute(multipoint2, polyline1, sr, null); + assertTrue(!res); + + polyline1.startPath(1, -4); + polyline1.lineTo(1, -3); + polyline1.lineTo(1, -2); + polyline1.lineTo(1, -1); + polyline1.lineTo(1, 0); + polyline1.lineTo(1, 1); + + res = crosses.execute(polyline1, multipoint2, sr, null); + assertTrue(!res); + res = crosses.execute(multipoint2, polyline1, sr, null); + assertTrue(!res); + + crosses.accelerateGeometry(polyline1, sr, + Geometry.GeometryAccelerationDegree.enumHot); + + multipoint2.add(1, 0); + res = crosses.execute(polyline1, multipoint2, sr, null); + assertTrue(res); + res = crosses.execute(multipoint2, polyline1, sr, null); + assertTrue(res); + + multipoint2.add(3, 1); + res = crosses.execute(polyline1, multipoint2, sr, null); + assertTrue(res); + res = crosses.execute(multipoint2, polyline1, sr, null); + assertTrue(res); + } + + @Test + public void testPolylinePointTouches() { + OperatorTouches touches = (OperatorTouches) (OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Touches)); + SpatialReference sr = SpatialReference.create(102100); + @SuppressWarnings("unused") + double tolerance = sr + .getTolerance(VertexDescription.Semantics.POSITION); + + Polyline polyline1 = new Polyline(); + Point point2 = new Point(); + + polyline1.startPath(0, 0); + polyline1.lineTo(2, 0); + + polyline1.startPath(2, 1); + polyline1.lineTo(2, -1); + + point2.setXY(2, 0); + + boolean res = touches.execute(polyline1, point2, sr, null); + assertTrue(res); + res = touches.execute(point2, polyline1, sr, null); + assertTrue(res); + } + + @Test + public void testPolygonPolygonOverlaps() { + OperatorOverlaps overlaps = (OperatorOverlaps) (OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Overlaps)); + SpatialReference sr = SpatialReference.create(102100); + double tolerance = sr + .getTolerance(VertexDescription.Semantics.POSITION); + + // Polygon1 and Polygon2 touch at a point + String str1 = "{\"rings\":[[[0,0],[0,5],[0,10],[10,10],[10,0],[0,0]],[[1,1],[9,1],[9,9],[1,9],[1,1],[1,1]]]}"; + String str2 = "{\"rings\":[[[10,10],[10,15],[15,15],[15,10],[10,10]]]}"; + + Polygon polygon1 = (Polygon) (TestCommonMethods.fromJson(str1) + .getGeometry()); + Polygon polygon2 = (Polygon) (TestCommonMethods.fromJson(str2) + .getGeometry()); + wiggleGeometry(polygon1, tolerance, 1982); + wiggleGeometry(polygon2, tolerance, 511); + + boolean res = overlaps.execute(polygon1, polygon2, sr, null); + assertTrue(!res); + res = overlaps.execute(polygon2, polygon1, sr, null); + assertTrue(!res); + + // Polygon1 and Polygon2 touch along the boundary + str1 = "{\"rings\":[[[0,0],[0,5],[0,10],[10,10],[10,0],[0,0]],[[1,1],[9,1],[9,9],[1,9],[1,1],[1,1]]]}"; + str2 = "{\"rings\":[[[10,0],[10,10],[15,10],[15,0],[10,0]]]}"; + + polygon1 = (Polygon) (TestCommonMethods.fromJson(str1).getGeometry()); + polygon2 = (Polygon) (TestCommonMethods.fromJson(str2).getGeometry()); + wiggleGeometry(polygon1, tolerance, 1982); + wiggleGeometry(polygon2, tolerance, 511); + + res = overlaps.execute(polygon1, polygon2, sr, null); + assertTrue(!res); + res = overlaps.execute(polygon2, polygon1, sr, null); + assertTrue(!res); + + // Polygon1 and Polygon2 touch at a corner of Polygon1 and a diagonal of + // Polygon2 + str1 = "{\"rings\":[[[0,0],[0,5],[0,10],[10,10],[10,0],[0,0]],[[1,1],[9,1],[9,9],[1,9],[1,1],[1,1]]]}"; + str2 = "{\"rings\":[[[15,5],[5,15],[15,15],[15,5]]]}"; + + polygon1 = (Polygon) (TestCommonMethods.fromJson(str1).getGeometry()); + polygon2 = (Polygon) (TestCommonMethods.fromJson(str2).getGeometry()); + wiggleGeometry(polygon1, tolerance, 1982); + wiggleGeometry(polygon2, tolerance, 511); + + res = overlaps.execute(polygon1, polygon2, sr, null); + assertTrue(!res); + res = overlaps.execute(polygon2, polygon1, sr, null); + assertTrue(!res); + + // Polygon1 and Polygon2 overlap at the upper right corner + str1 = "{\"rings\":[[[0,0],[0,5],[0,10],[10,10],[10,0],[0,0]],[[1,1],[9,1],[9,9],[1,9],[1,1],[1,1]]]}"; + str2 = "{\"rings\":[[[5,5],[5,15],[15,15],[15,5],[5,5]]]}"; + + polygon1 = (Polygon) (TestCommonMethods.fromJson(str1).getGeometry()); + polygon2 = (Polygon) (TestCommonMethods.fromJson(str2).getGeometry()); + + res = overlaps.execute(polygon1, polygon2, sr, null); + assertTrue(res); + res = overlaps.execute(polygon2, polygon1, sr, null); + assertTrue(res); + + str1 = "{\"rings\":[[[0,0],[0,5],[0,10],[10,10],[10,0],[0,0]],[[4,4],[6,4],[6,6],[4,6],[4,4],[4,4]]]}"; + str2 = "{\"rings\":[[[1,1],[1,9],[9,9],[9,1],[1,1]]]}"; + + polygon1 = (Polygon) (TestCommonMethods.fromJson(str1).getGeometry()); + polygon2 = (Polygon) (TestCommonMethods.fromJson(str2).getGeometry()); + + res = overlaps.execute(polygon1, polygon2, sr, null); + assertTrue(res); + res = overlaps.execute(polygon2, polygon1, sr, null); + assertTrue(res); + } + + @Test + public void testPolygonPolylineWithin() { + OperatorWithin within = (OperatorWithin) (OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Within)); + SpatialReference sr = SpatialReference.create(102100); + @SuppressWarnings("unused") + double tolerance = sr + .getTolerance(VertexDescription.Semantics.POSITION); + + Polygon polygon1 = new Polygon(); + Polyline polyline2 = new Polyline(); + + polygon1.startPath(0, 0); + polygon1.lineTo(0, 10); + polygon1.lineTo(10, 10); + polygon1.lineTo(10, 0); + + polyline2.startPath(5, 0); + polyline2.lineTo(5, 10); + + boolean res = within.execute(polygon1, polyline2, sr, null); + assertTrue(!res); + res = within.execute(polyline2, polygon1, sr, null); + assertTrue(res); + + polyline2.setEmpty(); + polyline2.startPath(0, 1); + polyline2.lineTo(0, 9); + + res = within.execute(polyline2, polygon1, sr, null); + assertTrue(!res); + } + + @Test + public void testMultiPointMultiPointWithin() { + OperatorWithin within = (OperatorWithin) (OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Within)); + SpatialReference sr = SpatialReference.create(102100); + @SuppressWarnings("unused") + double tolerance = sr + .getTolerance(VertexDescription.Semantics.POSITION); + + MultiPoint multipoint1 = new MultiPoint(); + MultiPoint multipoint2 = new MultiPoint(); + + multipoint1.add(0, 0); + multipoint1.add(3, 3); + multipoint1.add(0, 0); + multipoint1.add(5, 5); + multipoint1.add(3, 3); + multipoint1.add(2, 4); + multipoint1.add(2, 8); + + multipoint2.add(0, 0); + multipoint2.add(3, 3); + multipoint2.add(2, 4); + multipoint2.add(2, 8); + multipoint2.add(5, 5); + + boolean res = within.execute(multipoint1, multipoint2, sr, null); + assertTrue(res); + res = within.execute(multipoint2, multipoint1, sr, null); + assertTrue(res); + + multipoint2.add(10, 10); + multipoint2.add(10, 10); + + res = within.execute(multipoint1, multipoint2, sr, null); + assertTrue(res); + res = within.execute(multipoint2, multipoint1, sr, null); + assertTrue(!res); + + multipoint1.add(10, 10); + res = within.execute(multipoint1, multipoint2, sr, null); + assertTrue(res); + res = within.execute(multipoint2, multipoint1, sr, null); + assertTrue(res); + + multipoint1.add(-10, -10); + res = within.execute(multipoint1, multipoint2, sr, null); + assertTrue(!res); + res = within.execute(multipoint2, multipoint1, sr, null); + assertTrue(res); + } + + @Test + public void testPolylinePolylineOverlaps() { + OperatorOverlaps overlaps = (OperatorOverlaps) (OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Overlaps)); + SpatialReference sr = SpatialReference.create(102100); + double tolerance = sr + .getTolerance(VertexDescription.Semantics.POSITION); + + Polyline polyline1 = new Polyline(); + Polyline polyline2 = new Polyline(); + polyline1.startPath(0, 0); + polyline1.lineTo(2, 0); + polyline2.startPath(1, 0); + polyline2.lineTo(3, 0); + polyline2.lineTo(1, 1); + polyline2.lineTo(1, -1); + wiggleGeometry(polyline1, tolerance, 1982); + wiggleGeometry(polyline2, tolerance, 511); + + boolean res = overlaps.execute(polyline1, polyline2, sr, null); + assertTrue(res); + res = overlaps.execute(polyline2, polyline1, sr, null); + assertTrue(res); + + polyline1.setEmpty(); + polyline2.setEmpty(); + polyline1.startPath(0, 0); + polyline1.lineTo(2, 0); + polyline2.startPath(1.9989, 0); + polyline2.lineTo(2.0011, 0); + // wiggleGeometry(polyline1, tolerance, 1982); + // wiggleGeometry(polyline2, tolerance, 511); + + res = overlaps.execute(polyline1, polyline2, sr, null); + assertTrue(res); + res = overlaps.execute(polyline2, polyline1, sr, null); + assertTrue(res); + + polyline1.setEmpty(); + polyline2.setEmpty(); + polyline1.startPath(0, 0); + polyline1.lineTo(2, 0); + polyline2.startPath(1.9989, 0); + polyline2.lineTo(2.0009, 0); + wiggleGeometry(polyline1, tolerance, 1982); + wiggleGeometry(polyline2, tolerance, 511); + + res = overlaps.execute(polyline1, polyline2, sr, null); + assertTrue(!res); + res = overlaps.execute(polyline2, polyline1, sr, null); + assertTrue(!res); + + polyline1.setEmpty(); + polyline2.setEmpty(); + polyline1.startPath(0, 0); + polyline1.lineTo(2, 0); + polyline2.startPath(0, 0); + polyline2.lineTo(2, 0); + polyline2.startPath(0, -1); + polyline2.lineTo(2, -1); + + res = overlaps.execute(polyline1, polyline2, sr, null); + assertTrue(!res); + res = overlaps.execute(polyline2, polyline1, sr, null); + assertTrue(!res); + } + + @Test + public void testMultiPointMultiPointOverlaps() { + OperatorOverlaps overlaps = (OperatorOverlaps) (OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Overlaps)); + SpatialReference sr = SpatialReference.create(102100); + @SuppressWarnings("unused") + double tolerance = sr + .getTolerance(VertexDescription.Semantics.POSITION); + + MultiPoint multipoint1 = new MultiPoint(); + MultiPoint multipoint2 = new MultiPoint(); + + multipoint1.add(4, 4); + multipoint1.add(6, 4); + + multipoint2.add(6, 2); + multipoint2.add(2, 6); + + boolean res = overlaps.execute(multipoint1, multipoint2, sr, null); + assertTrue(!res); + res = overlaps.execute(multipoint2, multipoint1, sr, null); + assertTrue(!res); + + multipoint1.add(10, 10); + multipoint2.add(6, 2); + + res = overlaps.execute(multipoint1, multipoint2, sr, null); + assertTrue(!res); + res = overlaps.execute(multipoint2, multipoint1, sr, null); + assertTrue(!res); + + multipoint1.add(6, 2); + res = overlaps.execute(multipoint1, multipoint2, sr, null); + assertTrue(res); + res = overlaps.execute(multipoint2, multipoint1, sr, null); + assertTrue(res); + + multipoint1.add(2, 6); + res = overlaps.execute(multipoint1, multipoint2, sr, null); + assertTrue(!res); + res = overlaps.execute(multipoint2, multipoint1, sr, null); + assertTrue(!res); + + multipoint2.add(1, 1); + res = overlaps.execute(multipoint1, multipoint2, sr, null); + assertTrue(res); + res = overlaps.execute(multipoint2, multipoint1, sr, null); + assertTrue(res); + + multipoint2.add(10, 10); + multipoint2.add(4, 4); + multipoint2.add(6, 4); + res = overlaps.execute(multipoint1, multipoint2, sr, null); + assertTrue(!res); + res = overlaps.execute(multipoint2, multipoint1, sr, null); + assertTrue(!res); + } + + @Test + public void testPolygonPolygonWithin() { + OperatorWithin within = (OperatorWithin) (OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Within)); + SpatialReference sr = SpatialReference.create(102100); + double tolerance = sr + .getTolerance(VertexDescription.Semantics.POSITION); + + // Polygon1 is within Polygon2 + String str1 = "{\"rings\":[[[0,0],[0,5],[0,10],[10,10],[10,0],[0,0]],[[1,1],[9,1],[9,9],[1,9],[1,1],[1,1]]]}"; + String str2 = "{\"rings\":[[[-1,-1],[-1,11],[11,11],[11,-1],[-1,-1]]]}"; + + Polygon polygon1 = (Polygon) (TestCommonMethods.fromJson(str1) + .getGeometry()); + Polygon polygon2 = (Polygon) (TestCommonMethods.fromJson(str2) + .getGeometry()); + + boolean res = within.execute(polygon1, polygon2, sr, null); + assertTrue(res); + res = within.execute(polygon2, polygon1, sr, null); + assertTrue(!res); + + // Polygon1 is within Polygon2, and the boundaries intersect + str1 = "{\"rings\":[[[0,0],[0,5],[0,10],[10,10],[10,0],[0,0]],[[4,4],[6,4],[6,6],[4,6],[4,4],[4,4]]]}"; + str2 = "{\"rings\":[[[1,1],[1,9],[9,9],[9,1],[1,1]]]}"; + + polygon1 = (Polygon) (TestCommonMethods.fromJson(str1).getGeometry()); + polygon2 = (Polygon) (TestCommonMethods.fromJson(str2).getGeometry()); + wiggleGeometry(polygon1, tolerance, 1982); + wiggleGeometry(polygon2, tolerance, 511); + + res = within.execute(polygon2, polygon1, sr, null); + assertTrue(!res); + + // Polygon1 is within Polygon2, and the boundaries intersect + str1 = "{\"rings\":[[[0,0],[0,5],[0,10],[10,10],[10,0],[0,0]],[[1,1],[9,1],[9,9],[1,9],[1,1],[1,1]]]}"; + str2 = "{\"rings\":[[[-1,0],[-1,11],[11,11],[11,0],[-1,0]]]}"; + + polygon1 = (Polygon) (TestCommonMethods.fromJson(str1).getGeometry()); + polygon2 = (Polygon) (TestCommonMethods.fromJson(str2).getGeometry()); + wiggleGeometry(polygon1, tolerance, 1982); + wiggleGeometry(polygon2, tolerance, 511); + + res = within.execute(polygon1, polygon2, sr, null); + assertTrue(res); + + // Polygon2 is inside of the hole of polygon1 + str1 = "{\"rings\":[[[0,0],[0,5],[0,10],[10,10],[10,0],[0,0]],[[1,1],[9,1],[9,9],[1,9],[1,1],[1,1]]]}"; + str2 = "{\"rings\":[[[2,2],[2,8],[8,8],[8,2],[2,2]]]}"; + + polygon1 = (Polygon) (TestCommonMethods.fromJson(str1).getGeometry()); + polygon2 = (Polygon) (TestCommonMethods.fromJson(str2).getGeometry()); + + res = within.execute(polygon2, polygon1, sr, null); + assertTrue(!res); + + str1 = "{\"rings\":[[[0,0],[10,0],[10,10],[0,10]]]}"; + str2 = "{\"rings\":[[[2,2],[2,8],[8,8],[8,2],[2,2],[8,2],[8,8],[2,8],[2,2]]]}"; + + polygon1 = (Polygon) (TestCommonMethods.fromJson(str1).getGeometry()); + polygon2 = (Polygon) (TestCommonMethods.fromJson(str2).getGeometry()); + + res = within.execute(polygon2, polygon1, sr, null); + assertTrue(res); + + str1 = "{\"rings\":[[[0,0],[0,10],[10,10],[10,0]],[[12,8],[12,10],[18,10],[18,8],[12,8]]]}"; + str2 = "{\"paths\":[[[2,2],[2,8],[8,8],[8,2]],[[12,2],[12,4],[18,4],[18,2]]]}"; + + polygon1 = (Polygon) (TestCommonMethods.fromJson(str1).getGeometry()); + Polyline polyline2 = (Polyline) (TestCommonMethods.fromJson(str2).getGeometry()); + + res = within.execute(polyline2, polygon1, sr, null); + assertTrue(!res); + + str1 = "{\"rings\":[[[0,0],[0,10],[10,10],[10,0],[0,0]],[[4,4],[6,4],[6,6],[4,6],[4,4]]]}"; + str2 = "{\"rings\":[[[2,2],[2,8],[8,8],[8,2],[2,2],[2,8],[8,8],[8,2],[2,2]]]}"; + + polygon1 = (Polygon) (TestCommonMethods.fromJson(str1).getGeometry()); + polygon2 = (Polygon) (TestCommonMethods.fromJson(str2).getGeometry()); + + res = within.execute(polygon2, polygon1, sr, null); + assertTrue(res); + + // Same as above, but winding fill rule + str1 = "{\"rings\":[[[0,0],[0,10],[10,10],[10,0],[0,0]],[[4,4],[6,4],[6,6],[4,6],[4,4]]]}"; + str2 = "{\"rings\":[[[2,2],[2,8],[8,8],[8,2],[2,2],[2,8],[8,8],[8,2],[2,2]]]}"; + polygon1 = (Polygon) (TestCommonMethods.fromJson(str1).getGeometry()); + polygon2 = (Polygon) (TestCommonMethods.fromJson(str2).getGeometry()); + polygon1.setFillRule(Polygon.FillRule.enumFillRuleWinding); + polygon2.setFillRule(Polygon.FillRule.enumFillRuleWinding); + + res = within.execute(polygon2, polygon1, sr, null); + assertTrue(!res); + + str1 = "{\"rings\":[[[0,0],[0,10],[10,10],[10,0],[0,0]]]}"; + str2 = "{\"paths\":[[[2,2],[2,2]]]}"; + polygon1 = (Polygon) (TestCommonMethods.fromJson(str1).getGeometry()); + polyline2 = (Polyline) (TestCommonMethods.fromJson(str2).getGeometry()); + res = within.execute(polyline2, polygon1, sr, null); + assertTrue(res); + + str1 = "{\"rings\":[[[0,0],[0,10],[10,10],[10,0],[0,0]],[[11,11],[11,20],[20,20],[20,11],[11,11]]]}"; + str2 = "{\"rings\":[[[2,2],[2,8],[8,8],[15,15],[8,8],[8,2],[2,2]]]}"; + polygon1 = (Polygon) (TestCommonMethods.fromJson(str1).getGeometry()); + polygon2 = (Polygon) (TestCommonMethods.fromJson(str2).getGeometry()); + + res = within.execute(polygon2, polygon1, sr, null); + assertTrue(!res); + + str1 = "{\"rings\":[[[0,0],[0,10],[10,10],[10,0],[0,0]],[[10,10],[10,20],[20,20],[20,10],[10,10]]]}"; + str2 = "{\"rings\":[[[2,2],[2,8],[8,8],[15,15],[8,8],[8,2],[2,2]]]}"; + polygon1 = (Polygon) (TestCommonMethods.fromJson(str1).getGeometry()); + polygon2 = (Polygon) (TestCommonMethods.fromJson(str2).getGeometry()); + + res = within.execute(polygon2, polygon1, sr, null); + assertTrue(res); + + str1 = "{\"rings\":[[[0,0],[0,10],[10,10],[10,0],[0,0]]]}"; + str2 = "{\"rings\":[[[9.9999999925,4],[9.9999999925,6],[10.0000000075,6],[10.0000000075,4],[9.9999999925,4]]]}"; + polygon1 = (Polygon) (TestCommonMethods.fromJson(str1).getGeometry()); + polygon2 = (Polygon) (TestCommonMethods.fromJson(str2).getGeometry()); + + res = within.execute(polygon2, polygon1, sr, null); + assertTrue(!res); + + res = OperatorOverlaps.local().execute(polygon1, polygon2, sr, null); + assertTrue(!res); + + res = OperatorTouches.local().execute(polygon1, polygon2, sr, null); + assertTrue(res); + + str1 = "{\"rings\":[[[0,0],[0,10],[10,10],[10,0],[0,0]],[[10,10],[10,20],[20,20],[20,10],[10,10]]]}"; + str2 = "{\"rings\":[[[2,2],[2,8],[8,8],[15,15],[8,8],[8,2],[2,2]],[[15,5],[15,5],[15,5]]]}"; + polygon1 = (Polygon) (TestCommonMethods.fromJson(str1).getGeometry()); + polygon2 = (Polygon) (TestCommonMethods.fromJson(str2).getGeometry()); + res = within.execute(polygon2, polygon1, sr, null); + assertTrue(!res); + + str1 = "{\"rings\":[[[0,0],[0,10],[10,10],[10,0],[0,0]]]}"; + str2 = "{\"rings\":[[[2,2],[2,2],[2,2]],[[3,3],[3,3],[3,3]]]}"; + polygon1 = (Polygon) (TestCommonMethods.fromJson(str1).getGeometry()); + polygon2 = (Polygon) (TestCommonMethods.fromJson(str2).getGeometry()); + res = within.execute(polygon2, polygon1, sr, null); + assertTrue(res); + + str1 = "{\"rings\":[[[0,0],[0,10],[10,10],[10,0],[0,0]]]}"; + str2 = "{\"rings\":[[[2,2],[2,2],[2,2],[2,2]],[[3,3],[3,3],[3,3],[3,3]]]}"; + polygon1 = (Polygon) (TestCommonMethods.fromJson(str1).getGeometry()); + polygon2 = (Polygon) (TestCommonMethods.fromJson(str2).getGeometry()); + res = within.execute(polygon2, polygon1, sr, null); + assertTrue(res); + + str1 = "{\"rings\":[[[0,0],[0,10],[10,10],[10,0],[0,0]]]}"; + str2 = "{\"paths\":[[[2,2],[2,2]],[[3,3],[3,3]]]}"; + polygon1 = (Polygon) (TestCommonMethods.fromJson(str1).getGeometry()); + polyline2 = (Polyline) (TestCommonMethods.fromJson(str2).getGeometry()); + res = within.execute(polyline2, polygon1, sr, null); + assertTrue(res); + + str1 = "{\"rings\":[[[0,0],[0,10],[10,10],[10,0],[0,0]],[[10,10],[10,20],[20,20],[20,10],[10,10]]]}"; + str2 = "{\"paths\":[[[2,2],[2,8]],[[15,5],[15,5]]]}"; + polygon1 = (Polygon) (TestCommonMethods.fromJson(str1).getGeometry()); + polyline2 = (Polyline) (TestCommonMethods.fromJson(str2).getGeometry()); + res = within.execute(polyline2, polygon1, sr, null); + assertTrue(!res); + + str1 = "{\"rings\":[[[0,0],[0,10],[10,10],[10,0],[0,0]],[[10,10],[10,20],[20,20],[20,10],[10,10]]]}"; + str2 = "{\"paths\":[[[2,2],[2,8]],[[15,5],[15,5],[15,5],[15,5]]]}"; + polygon1 = (Polygon) (TestCommonMethods.fromJson(str1).getGeometry()); + polyline2 = (Polyline) (TestCommonMethods.fromJson(str2).getGeometry()); + res = within.execute(polyline2, polygon1, sr, null); + assertTrue(!res); + + str1 = "{\"rings\":[[[0,0],[0,10],[10,10],[10,0],[0,0]],[[10,10],[10,20],[20,20],[20,10],[10,10]]]}"; + str2 = "{\"paths\":[[[2,2],[2,2]],[[15,5],[15,6]]]}"; + polygon1 = (Polygon) (TestCommonMethods.fromJson(str1).getGeometry()); + polyline2 = (Polyline) (TestCommonMethods.fromJson(str2).getGeometry()); + res = within.execute(polyline2, polygon1, sr, null); + assertTrue(!res); + + str1 = "{\"rings\":[[[0,0],[0,10],[10,10],[10,0],[0,0]],[[10,10],[10,20],[20,20],[20,10],[10,10]]]}"; + str2 = "{\"paths\":[[[2,2],[2,2],[2,2],[2,2]],[[15,5],[15,6]]]}"; + polygon1 = (Polygon) (TestCommonMethods.fromJson(str1).getGeometry()); + polyline2 = (Polyline) (TestCommonMethods.fromJson(str2).getGeometry()); + res = within.execute(polyline2, polygon1, sr, null); + assertTrue(!res); + } + + @Test + public void testPolylinePolylineWithin() { + OperatorWithin within = (OperatorWithin) (OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Within)); + OperatorContains contains = (OperatorContains) (OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Contains)); + SpatialReference sr = SpatialReference.create(102100); + @SuppressWarnings("unused") + double tolerance = sr + .getTolerance(VertexDescription.Semantics.POSITION); + + Polyline polyline1 = new Polyline(); + Polyline polyline2 = new Polyline(); + + polyline1.setEmpty(); + polyline2.setEmpty(); + polyline1.startPath(0, 0); + polyline1.lineTo(2, 0); + polyline2.startPath(1.9989, 0); + polyline2.lineTo(2.0011, 0); + + boolean res = within.execute(polyline2, polyline1, sr, null); + assertTrue(!res); + + res = contains.execute(polyline1, polyline2, sr, null); + assertTrue(!res); + + polyline1.setEmpty(); + polyline2.setEmpty(); + polyline1.startPath(0, 0); + polyline1.lineTo(2, 0); + polyline2.startPath(1.9989, 0); + polyline2.lineTo(2.001, 0); + + res = within.execute(polyline2, polyline1, sr, null); + assertTrue(res); + + res = contains.execute(polyline1, polyline2, sr, null); + assertTrue(res); + + polyline1.setEmpty(); + polyline2.setEmpty(); + polyline1.startPath(0, 0); + polyline1.lineTo(2, 0); + polyline1.lineTo(3, 0); + polyline1.lineTo(4, 0); + polyline1.lineTo(5, 0); + polyline1.lineTo(6, 0); + polyline1.lineTo(7, 0); + polyline1.lineTo(8, 0); + + polyline2.startPath(0, 0); + polyline2.lineTo(.1, 0); + polyline2.lineTo(.2, 0); + polyline2.lineTo(.4, 0); + polyline2.lineTo(1.1, 0); + polyline2.lineTo(2.5, 0); + + polyline2.startPath(2.7, 0); + polyline2.lineTo(4, 0); + + res = within.execute(polyline2, polyline1, sr, null); + assertTrue(res); + + res = contains.execute(polyline1, polyline2, sr, null); + assertTrue(res); + } + + @Test + public void testPolylineMultiPointWithin() { + OperatorWithin within = (OperatorWithin) (OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Within)); + SpatialReference sr = SpatialReference.create(102100); + @SuppressWarnings("unused") + double tolerance = sr + .getTolerance(VertexDescription.Semantics.POSITION); + + Polyline polyline1 = new Polyline(); + MultiPoint multipoint2 = new MultiPoint(); + + polyline1.startPath(0, 0); + polyline1.lineTo(2, 0); + polyline1.lineTo(4, 2); + + multipoint2.add(1, 0); + multipoint2.add(2, 0); + multipoint2.add(3, 1); + multipoint2.add(2, 0); + + boolean res = within.execute(polyline1, multipoint2, sr, null); + assertTrue(!res); + res = within.execute(multipoint2, polyline1, sr, null); + assertTrue(res); + + polyline1.startPath(1, -2); + polyline1.lineTo(1, -1); + polyline1.lineTo(1, 0); + polyline1.lineTo(1, 1); + + res = within.execute(polyline1, multipoint2, sr, null); + assertTrue(!res); + res = within.execute(multipoint2, polyline1, sr, null); + assertTrue(res); + + multipoint2.add(1, 2); + res = within.execute(multipoint2, polyline1, sr, null); + assertTrue(!res); + + multipoint2.add(-1, -1); + multipoint2.add(4, 2); + multipoint2.add(0, 0); + + res = within.execute(multipoint2, polyline1, sr, null); + assertTrue(!res); + } + + @Test + public void testPolygonMultiPointWithin() { + OperatorWithin within = (OperatorWithin) (OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Within)); + SpatialReference sr = SpatialReference.create(102100); + @SuppressWarnings("unused") + double tolerance = sr + .getTolerance(VertexDescription.Semantics.POSITION); + + Polygon polygon1 = new Polygon(); + MultiPoint multipoint2 = new MultiPoint(); + + polygon1.startPath(0, 0); + polygon1.lineTo(0, 10); + polygon1.lineTo(10, 10); + polygon1.lineTo(10, 0); + + multipoint2.add(5, 0); + multipoint2.add(5, 10); + multipoint2.add(5, 5); + + boolean res = within.execute(polygon1, multipoint2, sr, null); + assertTrue(!res); + res = within.execute(multipoint2, polygon1, sr, null); + assertTrue(res); + + multipoint2.add(5, 11); + res = within.execute(multipoint2, polygon1, sr, null); + assertTrue(!res); + } + + @Test + public void testPolygonPolylineCrosses() { + OperatorCrosses crosses = (OperatorCrosses) (OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Crosses)); + SpatialReference sr = SpatialReference.create(102100); + @SuppressWarnings("unused") + double tolerance = sr + .getTolerance(VertexDescription.Semantics.POSITION); + + Polygon polygon1 = new Polygon(); + Polyline polyline2 = new Polyline(); + + polygon1.startPath(0, 0); + polygon1.lineTo(0, 10); + polygon1.lineTo(10, 10); + polygon1.lineTo(10, 0); + + polyline2.startPath(5, -5); + polyline2.lineTo(5, 15); + + boolean res = crosses.execute(polygon1, polyline2, sr, null); + assertTrue(res); + res = crosses.execute(polyline2, polygon1, sr, null); + assertTrue(res); + + polyline2.setEmpty(); + polyline2.startPath(5, 0); + polyline2.lineTo(5, 10); + + res = crosses.execute(polygon1, polyline2, sr, null); + assertTrue(!res); + res = crosses.execute(polyline2, polygon1, sr, null); + assertTrue(!res); + + polygon1.setEmpty(); + polyline2.setEmpty(); + + polygon1.startPath(0, 0); + polygon1.lineTo(0, 10); + polygon1.lineTo(10, 10); + polygon1.lineTo(10, 8); + polygon1.lineTo(15, 5); + polygon1.lineTo(10, 2); + polygon1.lineTo(10, 0); + + polyline2.startPath(10, 15); + polyline2.lineTo(10, -5); + + res = crosses.execute(polygon1, polyline2, sr, null); + assertTrue(res); + res = crosses.execute(polyline2, polygon1, sr, null); + assertTrue(res); + } + + @Test + public void testPolylinePolylineCrosses() { + OperatorCrosses crosses = (OperatorCrosses) (OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Crosses)); + SpatialReference sr = SpatialReference.create(102100); + double tolerance = sr + .getTolerance(VertexDescription.Semantics.POSITION); + + // Polyline1 and Polyline2 touch at a point + String str1 = "{\"paths\":[[[0,0],[0,5],[0,10],[10,10],[10,0]],[[1,1],[9,1],[9,9],[1,9],[1,1],[1,1]]]}"; + String str2 = "{\"paths\":[[[10,10],[10,15],[15,15],[15,10]]]}"; + + Polyline polyline1 = (Polyline) (TestCommonMethods.fromJson(str1) + .getGeometry()); + Polyline polyline2 = (Polyline) (TestCommonMethods.fromJson(str2) + .getGeometry()); + + boolean res = crosses.execute(polyline1, polyline2, sr, null); + assertTrue(!res); + res = crosses.execute(polyline2, polyline1, sr, null); + assertTrue(!res); + + // Polyline1 and Polyline2 intersect at interiors + str1 = "{\"paths\":[[[0,0],[0,5],[0,10],[10,10],[10,0],[0,0]],[[1,1],[9,1],[9,9],[1,9],[1,1],[1,1]]]}"; + str2 = "{\"paths\":[[[15,5],[5,15],[15,15],[15,5]]]}"; + + polyline1 = (Polyline) (TestCommonMethods.fromJson(str1).getGeometry()); + polyline2 = (Polyline) (TestCommonMethods.fromJson(str2).getGeometry()); + + res = crosses.execute(polyline1, polyline2, sr, null); + assertTrue(res); + res = crosses.execute(polyline2, polyline1, sr, null); + assertTrue(res); + + // Polyline1 and Polyline2 touch at an endpoint of Polyline1 and + // interior of Polyline2 (but Polyline1 is closed) + str1 = "{\"paths\":[[[10,10],[10,0],[0,0],[0,10],[10,10]],[[1,1],[9,1],[9,9],[1,9],[1,1],[1,1]]]}"; + str2 = "{\"paths\":[[[15,5],[5,15],[15,15]]]}"; + + polyline1 = (Polyline) (TestCommonMethods.fromJson(str1).getGeometry()); + polyline2 = (Polyline) (TestCommonMethods.fromJson(str2).getGeometry()); + ; + + res = crosses.execute(polyline1, polyline2, sr, null); + assertTrue(res); + res = crosses.execute(polyline2, polyline1, sr, null); + assertTrue(res); + + // Polyline1 and Polyline2 touch at an endpoint of Polyline1 and + // interior of Polyline2 (same as previous case, but Polyline1 is not + // closed) + str1 = "{\"paths\":[[[10,10],[10,0],[0,0],[0,10]],[[1,1],[9,1],[9,9],[1,9],[1,1],[1,1]]]}"; + str2 = "{\"paths\":[[[15,5],[5,15],[15,15]]]}"; + + polyline1 = (Polyline) (TestCommonMethods.fromJson(str1).getGeometry()); + polyline2 = (Polyline) (TestCommonMethods.fromJson(str2).getGeometry()); + + res = crosses.execute(polyline1, polyline2, sr, null); + assertTrue(!res); + res = crosses.execute(polyline2, polyline1, sr, null); + assertTrue(!res); + + str1 = "{\"paths\":[[[10,11],[10,0],[0,0],[0,10]],[[1,1],[9,1],[9,9],[1,9],[6, 9]]]}"; + str2 = "{\"paths\":[[[15,5],[5,15],[15,15],[15,5]]]}"; + + polyline1 = (Polyline) (TestCommonMethods.fromJson(str1).getGeometry()); + polyline2 = (Polyline) (TestCommonMethods.fromJson(str2).getGeometry()); + + res = crosses.execute(polyline1, polyline2, sr, null); + assertTrue(res); + res = crosses.execute(polyline2, polyline1, sr, null); + assertTrue(res); + + polyline1.setEmpty(); + polyline2.setEmpty(); + + polyline1.startPath(-2, -2); + polyline1.lineTo(-1, -1); + polyline1.lineTo(1, 1); + polyline1.lineTo(2, 2); + + polyline2.startPath(-2, 2); + polyline2.lineTo(-1, 1); + polyline2.lineTo(1, -1); + polyline2.lineTo(2, -2); + + res = crosses.execute(polyline2, polyline1, sr, null); + assertTrue(res); + } + + @Test + public void testPolygonEnvelope() { + OperatorEquals equals = (OperatorEquals) (OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Equals)); + OperatorContains contains = (OperatorContains) (OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Contains)); + OperatorDisjoint disjoint = (OperatorDisjoint) (OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Disjoint)); + OperatorCrosses crosses = (OperatorCrosses) (OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Crosses)); + @SuppressWarnings("unused") + OperatorWithin within = (OperatorWithin) (OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Within)); + OperatorOverlaps overlaps = (OperatorOverlaps) (OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Overlaps)); + OperatorTouches touches = (OperatorTouches) (OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Touches)); + OperatorDensifyByLength densify = (OperatorDensifyByLength) (OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.DensifyByLength)); + SpatialReference sr = SpatialReference.create(4326); + + { + Polygon polygon = (Polygon) (TestCommonMethods + .fromJson("{\"rings\":[[[0,0],[0,5],[0,10],[10,10],[10,0],[0,0]]]}") + .getGeometry()); + Polygon densified = (Polygon) (densify.execute(polygon, 1.0, null)); + Envelope envelope = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":10}") + .getGeometry()); + wiggleGeometry(densified, 0.00000001, 1982); + wiggleGeometry(envelope, 0.00000001, 511); + assertTrue(equals.execute(envelope, densified, sr, null)); // they + // cover + // the + // same + // space + assertTrue(contains.execute(densified, envelope, sr, null)); + assertTrue(!disjoint.execute(envelope, densified, sr, null)); + assertTrue(!touches.execute(envelope, densified, sr, null)); + assertTrue(!overlaps.execute(envelope, densified, sr, null)); + assertTrue(!crosses.execute(envelope, densified, sr, null)); + } + + { + Polygon polygon = (Polygon) (TestCommonMethods + .fromJson("{\"rings\":[[[0,0],[0,5],[0,10],[10,10],[10,0],[0,0]]]}") + .getGeometry()); + Polygon densified = (Polygon) (densify.execute(polygon, 1.0, null)); + Envelope envelope = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":5,\"ymin\":0,\"xmax\":10,\"ymax\":10}") + .getGeometry()); + wiggleGeometry(densified, 0.00000001, 1982); + wiggleGeometry(envelope, 0.00000001, 511); + assertTrue(!equals.execute(envelope, densified, sr, null)); // the + // polygon + // contains + // the + // envelope, + // but + // they + // aren't + // equal + assertTrue(contains.execute(densified, envelope, sr, null)); + assertTrue(!disjoint.execute(envelope, densified, sr, null)); + assertTrue(!touches.execute(envelope, densified, sr, null)); + assertTrue(!overlaps.execute(envelope, densified, sr, null)); + assertTrue(!crosses.execute(envelope, densified, sr, null)); + } + + { + Polygon polygon = (Polygon) (TestCommonMethods + .fromJson("{\"rings\":[[[0,0],[0,5],[0,10],[10,10],[10,0],[0,0]]]}") + .getGeometry()); + Polygon densified = (Polygon) (densify.execute(polygon, 1.0, null)); + Envelope envelope = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":15,\"ymax\":10}") + .getGeometry()); + wiggleGeometry(densified, 0.00000001, 1982); + wiggleGeometry(envelope, 0.00000001, 511); + assertTrue(!equals.execute(envelope, densified, sr, null)); // the + // envelope + // sticks + // outside + // of + // the + // polygon + // but + // they + // intersect + assertTrue(!contains.execute(densified, envelope, sr, null)); + assertTrue(!disjoint.execute(envelope, densified, sr, null)); + assertTrue(!touches.execute(envelope, densified, sr, null)); + assertTrue(!overlaps.execute(envelope, densified, sr, null)); + assertTrue(!crosses.execute(envelope, densified, sr, null)); + } + + { + Polygon polygon = (Polygon) (TestCommonMethods + .fromJson("{\"rings\":[[[0,0],[0,5],[0,10],[10,10],[10,0],[0,0]]]}") + .getGeometry()); + Polygon densified = (Polygon) (densify.execute(polygon, 1.0, null)); + Envelope envelope = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":5,\"ymin\":0,\"xmax\":15,\"ymax\":10}") + .getGeometry()); + wiggleGeometry(densified, 0.00000001, 1982); + wiggleGeometry(envelope, 0.00000001, 511); + assertTrue(!equals.execute(envelope, densified, sr, null)); // the + // envelope + // sticks + // outside + // of + // the + // polygon + // but + // they + // intersect + // and + // overlap + assertTrue(!contains.execute(densified, envelope, sr, null)); + assertTrue(!disjoint.execute(envelope, densified, sr, null)); + assertTrue(!touches.execute(envelope, densified, sr, null)); + assertTrue(overlaps.execute(envelope, densified, sr, null)); + assertTrue(!crosses.execute(envelope, densified, sr, null)); + } + + { + Polygon polygon = (Polygon) (TestCommonMethods + .fromJson("{\"rings\":[[[0,0],[0,5],[0,10],[10,10],[10,0],[0,0]]]}") + .getGeometry()); + Polygon densified = (Polygon) (densify.execute(polygon, 1.0, null)); + Envelope envelope = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":10,\"ymin\":0,\"xmax\":15,\"ymax\":5}") + .getGeometry()); + wiggleGeometry(densified, 0.00000001, 1982); + wiggleGeometry(envelope, 0.00000001, 511); + assertTrue(!equals.execute(envelope, densified, sr, null)); // the + // envelope + // rides + // the + // side + // of + // the + // polygon + // (they + // touch) + assertTrue(!contains.execute(densified, envelope, sr, null)); + assertTrue(!disjoint.execute(envelope, densified, sr, null)); + assertTrue(touches.execute(envelope, densified, sr, null)); + assertTrue(!overlaps.execute(envelope, densified, sr, null)); + assertTrue(!crosses.execute(envelope, densified, sr, null)); + } + + { + Polygon polygon = (Polygon) (TestCommonMethods + .fromJson("{\"rings\":[[[0,0],[0,5],[0,10],[10,10],[10,0],[0,0]]]}") + .getGeometry()); + Polygon densified = (Polygon) (densify.execute(polygon, 1.0, null)); + Envelope envelope = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":10}") + .getGeometry()); + wiggleGeometry(densified, 0.00000001, 1982); + wiggleGeometry(envelope, 0.00000001, 511); + assertTrue(contains.execute(densified, envelope, sr, null)); // polygon + // and + // envelope + // cover + // the + // same + // space + assertTrue(!disjoint.execute(densified, envelope, sr, null)); + assertTrue(!touches.execute(envelope, densified, sr, null)); + assertTrue(!overlaps.execute(envelope, densified, sr, null)); + assertTrue(!crosses.execute(envelope, densified, sr, null)); + } + + { + Polygon polygon = (Polygon) (TestCommonMethods + .fromJson("{\"rings\":[[[0,0],[0,5],[0,10],[10,0],[0,0]]]}") + .getGeometry()); + Polygon densified = (Polygon) (densify.execute(polygon, 1.0, null)); + Envelope envelope = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":10}") + .getGeometry()); + wiggleGeometry(densified, 0.00000001, 1982); + wiggleGeometry(envelope, 0.00000001, 511); + assertTrue(!equals.execute(envelope, densified, sr, null)); + assertTrue(!contains.execute(densified, envelope, sr, null)); // envelope + // sticks + // outside + // of + // polygon, + // but + // the + // envelopes + // are + // equal + assertTrue(!disjoint.execute(densified, envelope, sr, null)); + assertTrue(!touches.execute(envelope, densified, sr, null)); + assertTrue(!overlaps.execute(envelope, densified, sr, null)); + assertTrue(!crosses.execute(envelope, densified, sr, null)); + } + + { + Polygon polygon = (Polygon) (TestCommonMethods + .fromJson("{\"rings\":[[[0,0],[0,5],[0,10],[10,0],[0,0]]]}") + .getGeometry()); + Polygon densified = (Polygon) (densify.execute(polygon, 1.0, null)); + Envelope envelope = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":15,\"ymax\":10}") + .getGeometry()); + wiggleGeometry(densified, 0.00000001, 1982); + wiggleGeometry(envelope, 0.00000001, 511); + assertTrue(!equals.execute(envelope, densified, sr, null)); + assertTrue(!contains.execute(densified, envelope, sr, null)); // the + // polygon + // envelope + // doesn't + // contain + // the + // envelope, + // but + // they + // intersect + assertTrue(!disjoint.execute(densified, envelope, sr, null)); + assertTrue(!touches.execute(envelope, densified, sr, null)); + assertTrue(!overlaps.execute(envelope, densified, sr, null)); + assertTrue(!crosses.execute(envelope, densified, sr, null)); + } + + { + Polygon polygon = (Polygon) (TestCommonMethods + .fromJson("{\"rings\":[[[0,0],[0,5],[0,10],[10,10],[10,0],[0,0]]]}") + .getGeometry()); + Polygon densified = (Polygon) (densify.execute(polygon, 1.0, null)); + Envelope envelope = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":0,\"ymax\":0}") + .getGeometry()); + wiggleGeometry(densified, 0.00000001, 1982); + wiggleGeometry(envelope, 0.00000001, 511); + assertTrue(!equals.execute(envelope, densified, sr, null)); + assertTrue(!contains.execute(densified, envelope, sr, null)); // envelope + // degenerate + // to + // a + // point + // and + // is + // on + // border + // (i.e. + // touches) + assertTrue(!disjoint.execute(densified, envelope, sr, null)); + assertTrue(touches.execute(envelope, densified, sr, null)); + assertTrue(!overlaps.execute(envelope, densified, sr, null)); + assertTrue(!crosses.execute(envelope, densified, sr, null)); + } + + { + Polygon polygon = (Polygon) (TestCommonMethods + .fromJson("{\"rings\":[[[0,0],[0,5],[0,10],[10,10],[10,0],[0,0]]]}") + .getGeometry()); + Polygon densified = (Polygon) (densify.execute(polygon, 1.0, null)); + Envelope envelope = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":1,\"ymin\":1,\"xmax\":1,\"ymax\":1}") + .getGeometry()); + wiggleGeometry(densified, 0.00000001, 1982); + wiggleGeometry(envelope, 0.00000001, 511); + assertTrue(!equals.execute(envelope, densified, sr, null)); + assertTrue(contains.execute(densified, envelope, sr, null)); // envelope + // degenerate + // to + // a + // point + // and + // is + // properly + // inside + assertTrue(!disjoint.execute(densified, envelope, sr, null)); + assertTrue(!touches.execute(envelope, densified, sr, null)); + assertTrue(!overlaps.execute(envelope, densified, sr, null)); + assertTrue(!crosses.execute(envelope, densified, sr, null)); + } + + { + Polygon polygon = (Polygon) (TestCommonMethods + .fromJson("{\"rings\":[[[0,0],[0,5],[0,10],[10,10],[10,0],[0,0]]]}") + .getGeometry()); + Polygon densified = (Polygon) (densify.execute(polygon, 1.0, null)); + Envelope envelope = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":-1,\"ymin\":-1,\"xmax\":-1,\"ymax\":-1}") + .getGeometry()); + wiggleGeometry(densified, 0.00000001, 1982); + wiggleGeometry(envelope, 0.00000001, 511); + assertTrue(!equals.execute(envelope, densified, sr, null)); + assertTrue(!contains.execute(densified, envelope, sr, null)); // envelope + // degenerate + // to + // a + // point + // and + // is + // properly + // outside + assertTrue(disjoint.execute(densified, envelope, sr, null)); + assertTrue(!touches.execute(envelope, densified, sr, null)); + assertTrue(!overlaps.execute(envelope, densified, sr, null)); + assertTrue(!crosses.execute(envelope, densified, sr, null)); + } + + { + Polygon polygon = (Polygon) (TestCommonMethods + .fromJson("{\"rings\":[[[0,0],[0,5],[0,10],[10,10],[10,0],[0,0]]]}") + .getGeometry()); + Polygon densified = (Polygon) (densify.execute(polygon, 1.0, null)); + Envelope envelope = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":1,\"ymax\":0}") + .getGeometry()); + wiggleGeometry(densified, 0.00000001, 1982); + wiggleGeometry(envelope, 0.00000001, 511); + assertTrue(!equals.execute(envelope, densified, sr, null)); + assertTrue(!contains.execute(densified, envelope, sr, null)); // envelope + // degenerate + // to + // a + // line + // and + // rides + // the + // bottom + // of + // the + // polygon + // (no + // interior + // intersection) + assertTrue(!disjoint.execute(densified, envelope, sr, null)); + assertTrue(touches.execute(envelope, densified, sr, null)); + assertTrue(!overlaps.execute(envelope, densified, sr, null)); + assertTrue(!crosses.execute(envelope, densified, sr, null)); + } + + { + Polygon polygon = (Polygon) (TestCommonMethods + .fromJson("{\"rings\":[[[0,0],[0,5],[0,10],[10,10],[10,0],[0,0]]]}") + .getGeometry()); + Polygon densified = (Polygon) (densify.execute(polygon, 1.0, null)); + Envelope envelope = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":0,\"ymin\":1,\"xmax\":1,\"ymax\":1}") + .getGeometry()); + wiggleGeometry(densified, 0.00000001, 1982); + wiggleGeometry(envelope, 0.00000001, 511); + assertTrue(!equals.execute(envelope, densified, sr, null)); + assertTrue(contains.execute(densified, envelope, sr, null)); // envelope + // degenerate + // to + // a + // line, + // touches + // the + // border + // on + // the + // inside + // yet + // has + // interior + // intersection + assertTrue(!disjoint.execute(densified, envelope, sr, null)); + assertTrue(!touches.execute(envelope, densified, sr, null)); + assertTrue(!overlaps.execute(envelope, densified, sr, null)); + assertTrue(!crosses.execute(envelope, densified, sr, null)); + } + + { + Polygon polygon = (Polygon) (TestCommonMethods + .fromJson("{\"rings\":[[[0,0],[0,5],[0,10],[10,0],[0,0]]]}") + .getGeometry()); + Polygon densified = (Polygon) (densify.execute(polygon, 1.0, null)); + Envelope envelope = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":5,\"ymin\":5,\"xmax\":6,\"ymax\":5}") + .getGeometry()); + wiggleGeometry(densified, 0.00000001, 1982); + wiggleGeometry(envelope, 0.00000001, 511); + assertTrue(!equals.execute(envelope, densified, sr, null)); + assertTrue(!contains.execute(densified, envelope, sr, null)); // envelope + // degenerate + // to + // a + // line, + // touches + // the + // boundary, + // and + // is + // outside + assertTrue(!disjoint.execute(densified, envelope, sr, null)); + assertTrue(touches.execute(envelope, densified, sr, null)); + assertTrue(!overlaps.execute(envelope, densified, sr, null)); + assertTrue(!crosses.execute(envelope, densified, sr, null)); + } + + { + Polygon polygon = (Polygon) (TestCommonMethods + .fromJson("{\"rings\":[[[0,0],[0,5],[0,10],[10,0],[0,0]]]}") + .getGeometry()); + Polygon densified = (Polygon) (densify.execute(polygon, 1.0, null)); + Envelope envelope = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":6,\"ymin\":5,\"xmax\":7,\"ymax\":5}") + .getGeometry()); + wiggleGeometry(densified, 0.00000001, 1982); + wiggleGeometry(envelope, 0.00000001, 511); + assertTrue(!equals.execute(envelope, densified, sr, null)); + assertTrue(!contains.execute(densified, envelope, sr, null)); // envelope + // degenerate + // to + // a + // line, + // and + // is + // outside + assertTrue(disjoint.execute(densified, envelope, sr, null)); + assertTrue(!touches.execute(envelope, densified, sr, null)); + assertTrue(!overlaps.execute(envelope, densified, sr, null)); + assertTrue(!crosses.execute(envelope, densified, sr, null)); + } + + { + Polygon polygon = (Polygon) (TestCommonMethods + .fromJson("{\"rings\":[[[0,0],[0,5],[0,10],[10,0],[0,0]]]}") + .getGeometry()); + Polygon densified = (Polygon) (densify.execute(polygon, 1.0, null)); + Envelope envelope = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":4,\"ymin\":5,\"xmax\":7,\"ymax\":5}") + .getGeometry()); + wiggleGeometry(densified, 0.00000001, 1982); + wiggleGeometry(envelope, 0.00000001, 511); + assertTrue(!equals.execute(envelope, densified, sr, null)); + assertTrue(!contains.execute(densified, envelope, sr, null)); // envelope + // degenerate + // to + // a + // line, + // and + // crosses + // polygon + assertTrue(!disjoint.execute(densified, envelope, sr, null)); + assertTrue(!touches.execute(envelope, densified, sr, null)); + assertTrue(!overlaps.execute(envelope, densified, sr, null)); + assertTrue(crosses.execute(envelope, densified, sr, null)); + } + } + + @Test + public void testPolylineEnvelope() { + OperatorEquals equals = (OperatorEquals) (OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Equals)); + OperatorContains contains = (OperatorContains) (OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Contains)); + OperatorDisjoint disjoint = (OperatorDisjoint) (OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Disjoint)); + OperatorCrosses crosses = (OperatorCrosses) (OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Crosses)); + @SuppressWarnings("unused") + OperatorWithin within = (OperatorWithin) (OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Within)); + OperatorOverlaps overlaps = (OperatorOverlaps) (OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Overlaps)); + OperatorTouches touches = (OperatorTouches) (OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Touches)); + OperatorDensifyByLength densify = (OperatorDensifyByLength) (OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.DensifyByLength)); + + SpatialReference sr = SpatialReference.create(4326); + + { + Polyline polyline = (Polyline) (TestCommonMethods + .fromJson("{\"paths\":[[[0,0],[0,5],[0,10],[10,10],[10,0]]]}") + .getGeometry()); + Polyline densified = (Polyline) (densify.execute(polyline, 1.0, + null)); + Envelope envelope = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":10}") + .getGeometry()); + wiggleGeometry(densified, 0.00000001, 1982); + wiggleGeometry(envelope, 0.00000001, 511); + assertTrue(!equals.execute(envelope, densified, sr, null)); // polyline + // straddles + // the + // envelope + // like + // a hat + assertTrue(!contains.execute(densified, envelope, sr, null)); + assertTrue(!disjoint.execute(envelope, densified, sr, null)); + assertTrue(touches.execute(envelope, densified, sr, null)); + assertTrue(!overlaps.execute(envelope, densified, sr, null)); + assertTrue(!crosses.execute(envelope, densified, sr, null)); + } + + { + Polyline polyline = (Polyline) (TestCommonMethods + .fromJson("{\"paths\":[[[-10,0],[0,10]]]}").getGeometry()); + Polyline densified = (Polyline) (densify.execute(polyline, 1.0, + null)); + Envelope envelope = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":10}") + .getGeometry()); + wiggleGeometry(densified, 0.00000001, 1982); + wiggleGeometry(envelope, 0.00000001, 511); + assertTrue(!equals.execute(envelope, densified, sr, null)); + assertTrue(!contains.execute(densified, envelope, sr, null)); + assertTrue(!disjoint.execute(envelope, densified, sr, null)); + assertTrue(touches.execute(envelope, densified, sr, null)); + assertTrue(touches.execute(densified, envelope, sr, null)); + assertTrue(!overlaps.execute(envelope, densified, sr, null)); + assertTrue(!crosses.execute(envelope, densified, sr, null)); + } + + { + Polyline polyline = (Polyline) (TestCommonMethods + .fromJson("{\"paths\":[[[-11,0],[1,12]]]}").getGeometry()); + Polyline densified = (Polyline) (densify.execute(polyline, 1.0, + null)); + Envelope envelope = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":10}") + .getGeometry()); + wiggleGeometry(densified, 0.00000001, 1982); + wiggleGeometry(envelope, 0.00000001, 511); + assertTrue(!equals.execute(envelope, densified, sr, null)); + assertTrue(!contains.execute(densified, envelope, sr, null)); + assertTrue(disjoint.execute(envelope, densified, sr, null)); + assertTrue(!touches.execute(envelope, densified, sr, null)); + assertTrue(!touches.execute(densified, envelope, sr, null)); + assertTrue(!overlaps.execute(envelope, densified, sr, null)); + assertTrue(!crosses.execute(envelope, densified, sr, null)); + } + + { + Polyline polyline = (Polyline) (TestCommonMethods + .fromJson("{\"paths\":[[[5,5],[6,6]]]}").getGeometry()); + Polyline densified = (Polyline) (densify.execute(polyline, 1.0, + null)); + Envelope envelope = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":10}") + .getGeometry()); + wiggleGeometry(densified, 0.00000001, 1982); + wiggleGeometry(envelope, 0.00000001, 511); + assertTrue(!equals.execute(envelope, densified, sr, null)); // polyline + // properly + // inside + assertTrue(contains.execute(envelope, densified, sr, null)); + assertTrue(!disjoint.execute(envelope, densified, sr, null)); + assertTrue(!touches.execute(envelope, densified, sr, null)); + assertTrue(!overlaps.execute(envelope, densified, sr, null)); + assertTrue(!crosses.execute(envelope, densified, sr, null)); + } + + { + Polyline polyline = (Polyline) (TestCommonMethods + .fromJson("{\"paths\":[[[5,5],[10,10]]]}").getGeometry()); + Polyline densified = (Polyline) (densify.execute(polyline, 1.0, + null)); + Envelope envelope = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":10}") + .getGeometry()); + wiggleGeometry(densified, 0.00000001, 1982); + wiggleGeometry(envelope, 0.00000001, 511); + assertTrue(!equals.execute(envelope, densified, sr, null)); + assertTrue(contains.execute(envelope, densified, sr, null)); + assertTrue(!disjoint.execute(envelope, densified, sr, null)); + assertTrue(!touches.execute(envelope, densified, sr, null)); + assertTrue(!overlaps.execute(envelope, densified, sr, null)); + assertTrue(!crosses.execute(envelope, densified, sr, null)); + } + + { + Polyline polyline = (Polyline) (TestCommonMethods + .fromJson("{\"paths\":[[[-5,5],[15,5]]]}").getGeometry()); + Polyline densified = (Polyline) (densify.execute(polyline, 1.0, + null)); + Envelope envelope = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":10}") + .getGeometry()); + wiggleGeometry(densified, 0.00000001, 1982); + wiggleGeometry(envelope, 0.00000001, 511); + assertTrue(!equals.execute(envelope, densified, sr, null)); + assertTrue(!contains.execute(envelope, densified, sr, null)); + assertTrue(!disjoint.execute(envelope, densified, sr, null)); + assertTrue(!touches.execute(envelope, densified, sr, null)); + assertTrue(!overlaps.execute(envelope, densified, sr, null)); + assertTrue(crosses.execute(envelope, densified, sr, null)); + assertTrue(crosses.execute(densified, envelope, sr, null)); + } + + { + Polyline polyline = (Polyline) (TestCommonMethods + .fromJson("{\"paths\":[[[5,5],[5,15]]]}").getGeometry()); + Polyline densified = (Polyline) (densify.execute(polyline, 1.0, + null)); + Envelope envelope = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":10}") + .getGeometry()); + wiggleGeometry(densified, 0.00000001, 1982); + wiggleGeometry(envelope, 0.00000001, 511); + assertTrue(!equals.execute(envelope, densified, sr, null)); // polyline + // slices + // through + // the + // envelope + // (interior + // and + // exterior + // intersection) + assertTrue(!contains.execute(densified, envelope, sr, null)); + assertTrue(!disjoint.execute(envelope, densified, sr, null)); + assertTrue(!touches.execute(envelope, densified, sr, null)); + assertTrue(!overlaps.execute(envelope, densified, sr, null)); + assertTrue(crosses.execute(envelope, densified, sr, null)); + } + + { + Polyline polyline = (Polyline) (TestCommonMethods + .fromJson("{\"paths\":[[[5,11],[5,15]]]}").getGeometry()); + Polyline densified = (Polyline) (densify.execute(polyline, 1.0, + null)); + Envelope envelope = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":10}") + .getGeometry()); + wiggleGeometry(densified, 0.00000001, 1982); + wiggleGeometry(envelope, 0.00000001, 511); + assertTrue(!equals.execute(envelope, densified, sr, null)); // polyline + // outside + // of + // envelope + assertTrue(!contains.execute(densified, envelope, sr, null)); + assertTrue(disjoint.execute(envelope, densified, sr, null)); + assertTrue(!touches.execute(envelope, densified, sr, null)); + assertTrue(!overlaps.execute(envelope, densified, sr, null)); + assertTrue(!crosses.execute(envelope, densified, sr, null)); + } + + { + Polyline polyline = (Polyline) (TestCommonMethods + .fromJson("{\"paths\":[[[0,0],[0,5],[0,10],[10,10],[10,0]]]}") + .getGeometry()); + Polyline densified = (Polyline) (densify.execute(polyline, 1.0, + null)); + Envelope envelope = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":0,\"ymin\":10,\"xmax\":10,\"ymax\":10}") + .getGeometry()); + wiggleGeometry(densified, 0.00000001, 1982); + wiggleGeometry(envelope, 0.00000001, 511); + assertTrue(!equals.execute(envelope, densified, sr, null)); // polyline + // straddles + // the + // degenerate + // envelope + // like + // a hat + assertTrue(contains.execute(densified, envelope, sr, null)); + assertTrue(!disjoint.execute(envelope, densified, sr, null)); + assertTrue(!touches.execute(envelope, densified, sr, null)); + assertTrue(!overlaps.execute(envelope, densified, sr, null)); + assertTrue(!crosses.execute(envelope, densified, sr, null)); + } + + { + Polyline polyline = (Polyline) (TestCommonMethods + .fromJson("{\"paths\":[[[0,0],[0,5],[0,10],[10,10],[10,0]]]}") + .getGeometry()); + Polyline densified = (Polyline) (densify.execute(polyline, 1.0, + null)); + Envelope envelope = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":0,\"ymin\":-5,\"xmax\":0,\"ymax\":10}") + .getGeometry()); + wiggleGeometry(densified, 0.00000001, 1982); + wiggleGeometry(envelope, 0.00000001, 511); + assertTrue(!equals.execute(envelope, densified, sr, null)); + assertTrue(!contains.execute(densified, envelope, sr, null)); + assertTrue(!disjoint.execute(envelope, densified, sr, null)); + assertTrue(!touches.execute(envelope, densified, sr, null)); + assertTrue(overlaps.execute(envelope, densified, sr, null)); + assertTrue(!crosses.execute(envelope, densified, sr, null)); + } + + { + Polyline polyline = (Polyline) (TestCommonMethods + .fromJson("{\"paths\":[[[0,0],[0,5],[0,10],[10,10],[10,0]]]}") + .getGeometry()); + Polyline densified = (Polyline) (densify.execute(polyline, 1.0, + null)); + Envelope envelope = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":0,\"ymax\":0}") + .getGeometry()); + wiggleGeometry(densified, 0.00000001, 511); + wiggleGeometry(envelope, 0.00000001, 1982); + assertTrue(!equals.execute(envelope, densified, sr, null)); // degenerate + // envelope + // is at + // the + // end + // point + // of + // polyline + assertTrue(!contains.execute(densified, envelope, sr, null)); + assertTrue(!disjoint.execute(envelope, densified, sr, null)); + assertTrue(touches.execute(envelope, densified, sr, null)); + assertTrue(!overlaps.execute(envelope, densified, sr, null)); + assertTrue(!crosses.execute(envelope, densified, sr, null)); + } + + { + Polyline polyline = (Polyline) (TestCommonMethods + .fromJson("{\"paths\":[[[0,0],[0,5],[0,10],[10,10],[10,0]]]}") + .getGeometry()); + Polyline densified = (Polyline) (densify.execute(polyline, 1.0, + null)); + Envelope envelope = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":0,\"ymin\":5,\"xmax\":0,\"ymax\":5}") + .getGeometry()); + wiggleGeometry(densified, 0.00000001, 1982); + wiggleGeometry(envelope, 0.00000001, 511); + assertTrue(!equals.execute(envelope, densified, sr, null)); // degenerate + // envelope + // is at + // the + // interior + // of + // polyline + assertTrue(contains.execute(densified, envelope, sr, null)); + assertTrue(!disjoint.execute(envelope, densified, sr, null)); + assertTrue(!touches.execute(envelope, densified, sr, null)); + assertTrue(!overlaps.execute(envelope, densified, sr, null)); + assertTrue(!crosses.execute(envelope, densified, sr, null)); + } + + { + Polyline polyline = (Polyline) (TestCommonMethods + .fromJson("{\"paths\":[[[2,-2],[2,2]]]}").getGeometry()); + Polyline densified = (Polyline) (densify.execute(polyline, 1.0, + null)); + Envelope envelope = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":5,\"ymax\":0}") + .getGeometry()); + wiggleGeometry(densified, 0.00000001, 1982); + wiggleGeometry(envelope, 0.00000001, 511); + assertTrue(!equals.execute(envelope, densified, sr, null)); // degenerate + // envelope + // crosses + // polyline + assertTrue(!contains.execute(densified, envelope, sr, null)); + assertTrue(!disjoint.execute(envelope, densified, sr, null)); + assertTrue(!touches.execute(envelope, densified, sr, null)); + assertTrue(!overlaps.execute(envelope, densified, sr, null)); + assertTrue(crosses.execute(envelope, densified, sr, null)); + } + + { + Polyline polyline = (Polyline) (TestCommonMethods + .fromJson("{\"paths\":[[[2,0],[2,2]]]}").getGeometry()); + Polyline densified = (Polyline) (densify.execute(polyline, 1.0, + null)); + Envelope envelope = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":5,\"ymax\":0}") + .getGeometry()); + wiggleGeometry(densified, 0.00000001, 1982); + wiggleGeometry(envelope, 0.00000001, 511); + assertTrue(!equals.execute(envelope, densified, sr, null)); // degenerate + // envelope + // crosses + // polyline + assertTrue(!contains.execute(densified, envelope, sr, null)); + assertTrue(!disjoint.execute(envelope, densified, sr, null)); + assertTrue(touches.execute(envelope, densified, sr, null)); + assertTrue(!overlaps.execute(envelope, densified, sr, null)); + assertTrue(!crosses.execute(envelope, densified, sr, null)); + } + + { + Polyline polyline = (Polyline) (TestCommonMethods + .fromJson("{\"paths\":[[[2,0],[2,2]]]}").getGeometry()); + Polyline densified = (Polyline) (densify.execute(polyline, 1.0, + null)); + Envelope envelope = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":2,\"ymin\":0,\"xmax\":2,\"ymax\":3}") + .getGeometry()); + wiggleGeometry(densified, 0.00000001, 1982); + wiggleGeometry(envelope, 0.00000001, 511); + assertTrue(!equals.execute(envelope, densified, sr, null)); // degenerate + // envelope + // contains + // polyline + assertTrue(!contains.execute(densified, envelope, sr, null)); + assertTrue(contains.execute(envelope, densified, sr, null)); + assertTrue(!disjoint.execute(envelope, densified, sr, null)); + assertTrue(!touches.execute(envelope, densified, sr, null)); + assertTrue(!overlaps.execute(envelope, densified, sr, null)); + assertTrue(!crosses.execute(envelope, densified, sr, null)); + } + + { + Polyline polyline = (Polyline) (TestCommonMethods + .fromJson("{\"paths\":[[[5,5],[6,6]]]}").getGeometry()); + Polyline densified = (Polyline) (densify.execute(polyline, 1.0, + null)); + Envelope envelope = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":5,\"ymax\":5}") + .getGeometry()); + wiggleGeometry(densified, 0.00000001, 1982); + wiggleGeometry(envelope, 0.00000001, 511); + assertTrue(!equals.execute(envelope, densified, sr, null)); // polyline + // properly + // inside + assertTrue(!contains.execute(envelope, densified, sr, null)); + assertTrue(!disjoint.execute(envelope, densified, sr, null)); + assertTrue(touches.execute(envelope, densified, sr, null)); + assertTrue(!overlaps.execute(envelope, densified, sr, null)); + assertTrue(!crosses.execute(envelope, densified, sr, null)); + } + + { + Polyline polyline = (Polyline) (TestCommonMethods + .fromJson("{\"paths\":[[[5,5],[5,10]]]}").getGeometry()); + Polyline densified = (Polyline) (densify.execute(polyline, 1.0, + null)); + Envelope envelope = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":5,\"ymin\":5,\"xmax\":5,\"ymax\":10}") + .getGeometry()); + wiggleGeometry(densified, 0.00000001, 1982); + wiggleGeometry(envelope, 0.00000001, 511); + assertTrue(equals.execute(envelope, densified, sr, null)); // polyline + // properly + // inside + assertTrue(contains.execute(envelope, densified, sr, null)); + assertTrue(!disjoint.execute(envelope, densified, sr, null)); + assertTrue(!touches.execute(envelope, densified, sr, null)); + assertTrue(!overlaps.execute(envelope, densified, sr, null)); + assertTrue(!crosses.execute(envelope, densified, sr, null)); + } + } + + @Test + public void testMultiPointEnvelope() { + OperatorEquals equals = (OperatorEquals) (OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Equals)); + OperatorContains contains = (OperatorContains) (OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Contains)); + OperatorDisjoint disjoint = (OperatorDisjoint) (OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Disjoint)); + OperatorCrosses crosses = (OperatorCrosses) (OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Crosses)); + @SuppressWarnings("unused") + OperatorWithin within = (OperatorWithin) (OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Within)); + OperatorOverlaps overlaps = (OperatorOverlaps) (OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Overlaps)); + OperatorTouches touches = (OperatorTouches) (OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Touches)); + @SuppressWarnings("unused") + OperatorDensifyByLength densify = (OperatorDensifyByLength) (OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.DensifyByLength)); + + SpatialReference sr = SpatialReference.create(4326); + + { + MultiPoint multi_point = (MultiPoint) (TestCommonMethods + .fromJson("{\"points\":[[0,0],[0,10],[10,10],[10,0]]}") + .getGeometry()); + Envelope envelope = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":10}") + .getGeometry()); + wiggleGeometry(multi_point, 0.00000001, 1982); + wiggleGeometry(envelope, 0.00000001, 511); + assertTrue(!equals.execute(envelope, multi_point, sr, null)); // all + // points + // on + // boundary + assertTrue(!contains.execute(envelope, multi_point, sr, null)); + assertTrue(!disjoint.execute(envelope, multi_point, sr, null)); + assertTrue(touches.execute(envelope, multi_point, sr, null)); + assertTrue(!overlaps.execute(envelope, multi_point, sr, null)); + assertTrue(!crosses.execute(envelope, multi_point, sr, null)); + } + + { + MultiPoint multi_point = (MultiPoint) (TestCommonMethods + .fromJson("{\"points\":[[0,0],[0,10],[10,10],[5,5]]}") + .getGeometry()); + Envelope envelope = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":10}") + .getGeometry()); + wiggleGeometry(multi_point, 0.00000001, 1982); + wiggleGeometry(envelope, 0.00000001, 511); + assertTrue(!equals.execute(envelope, multi_point, sr, null)); // points + // on + // boundary + // and + // one + // point + // in + // interior + assertTrue(contains.execute(envelope, multi_point, sr, null)); + assertTrue(!disjoint.execute(envelope, multi_point, sr, null)); + assertTrue(!touches.execute(envelope, multi_point, sr, null)); + assertTrue(!overlaps.execute(envelope, multi_point, sr, null)); + assertTrue(!crosses.execute(envelope, multi_point, sr, null)); + } + + { + MultiPoint multi_point = (MultiPoint) (TestCommonMethods + .fromJson("{\"points\":[[0,0],[0,10],[10,10],[5,5],[15,15]]}") + .getGeometry()); + Envelope envelope = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":10}") + .getGeometry()); + wiggleGeometry(multi_point, 0.00000001, 1982); + wiggleGeometry(envelope, 0.00000001, 511); + assertTrue(!equals.execute(envelope, multi_point, sr, null)); // points + // on + // boundary, + // one + // interior, + // one + // exterior + assertTrue(!contains.execute(envelope, multi_point, sr, null)); + assertTrue(!disjoint.execute(envelope, multi_point, sr, null)); + assertTrue(!touches.execute(envelope, multi_point, sr, null)); + assertTrue(!overlaps.execute(envelope, multi_point, sr, null)); + assertTrue(crosses.execute(envelope, multi_point, sr, null)); + } + + { + MultiPoint multi_point = (MultiPoint) (TestCommonMethods + .fromJson("{\"points\":[[0,0],[0,10],[10,10],[15,15]]}") + .getGeometry()); + Envelope envelope = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":10}") + .getGeometry()); + wiggleGeometry(multi_point, 0.00000001, 1982); + wiggleGeometry(envelope, 0.00000001, 511); + assertTrue(!equals.execute(envelope, multi_point, sr, null)); // points + // on + // boundary, + // one + // exterior + assertTrue(!contains.execute(envelope, multi_point, sr, null)); + assertTrue(!disjoint.execute(envelope, multi_point, sr, null)); + assertTrue(touches.execute(envelope, multi_point, sr, null)); + assertTrue(!overlaps.execute(envelope, multi_point, sr, null)); + assertTrue(!crosses.execute(envelope, multi_point, sr, null)); + } + + { + MultiPoint multi_point = (MultiPoint) (TestCommonMethods + .fromJson("{\"points\":[[0,-1],[0,11],[11,11],[15,15]]}") + .getGeometry()); + Envelope envelope = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":10}") + .getGeometry()); + wiggleGeometry(multi_point, 0.00000001, 1982); + wiggleGeometry(envelope, 0.00000001, 511); + assertTrue(!equals.execute(envelope, multi_point, sr, null)); // all + // points + // exterior + assertTrue(!contains.execute(envelope, multi_point, sr, null)); + assertTrue(disjoint.execute(envelope, multi_point, sr, null)); + assertTrue(!touches.execute(envelope, multi_point, sr, null)); + assertTrue(!overlaps.execute(envelope, multi_point, sr, null)); + assertTrue(!crosses.execute(envelope, multi_point, sr, null)); + } + + { + MultiPoint multi_point = (MultiPoint) (TestCommonMethods + .fromJson("{\"points\":[[0,0],[0,10],[10,10],[10,0]]}") + .getGeometry()); + Envelope envelope = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":0,\"ymin\":10,\"xmax\":10,\"ymax\":10}") + .getGeometry()); + wiggleGeometry(multi_point, 0.00000001, 1982); + wiggleGeometry(envelope, 0.00000001, 511); + assertTrue(!equals.execute(envelope, multi_point, sr, null)); // degenerate + // envelope + // slices + // through + // some + // points, + // but + // some + // points + // are + // off + // the + // line + assertTrue(!contains.execute(envelope, multi_point, sr, null)); + assertTrue(!disjoint.execute(envelope, multi_point, sr, null)); + assertTrue(touches.execute(envelope, multi_point, sr, null)); + assertTrue(!overlaps.execute(envelope, multi_point, sr, null)); + assertTrue(!crosses.execute(envelope, multi_point, sr, null)); + } + + { + MultiPoint multi_point = (MultiPoint) (TestCommonMethods + .fromJson("{\"points\":[[0,0],[1,10],[10,10],[10,0]]}") + .getGeometry()); + Envelope envelope = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":0,\"ymin\":10,\"xmax\":10,\"ymax\":10}") + .getGeometry()); + wiggleGeometry(multi_point, 0.00000001, 1982); + wiggleGeometry(envelope, 0.00000001, 511); + assertTrue(!equals.execute(envelope, multi_point, sr, null)); // degenerate + // envelope + // slices + // through + // some + // points, + // but + // some + // points + // are + // off + // the + // line + assertTrue(!contains.execute(envelope, multi_point, sr, null)); + assertTrue(!disjoint.execute(envelope, multi_point, sr, null)); + assertTrue(!touches.execute(envelope, multi_point, sr, null)); + assertTrue(!overlaps.execute(envelope, multi_point, sr, null)); + assertTrue(crosses.execute(envelope, multi_point, sr, null)); + } + + { + MultiPoint multi_point = (MultiPoint) (TestCommonMethods + .fromJson("{\"points\":[[0,10],[10,10]]}").getGeometry()); + Envelope envelope = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":0,\"ymin\":10,\"xmax\":10,\"ymax\":10}") + .getGeometry()); + wiggleGeometry(multi_point, 0.00000001, 1982); + wiggleGeometry(envelope, 0.00000001, 511); + assertTrue(!equals.execute(envelope, multi_point, sr, null)); // degenerate + // envelopes + // slices + // through + // all + // the + // points, + // and + // they + // are + // at + // the + // end + // points + // of + // the + // line + assertTrue(!contains.execute(envelope, multi_point, sr, null)); + assertTrue(!disjoint.execute(envelope, multi_point, sr, null)); + assertTrue(touches.execute(envelope, multi_point, sr, null)); + assertTrue(!overlaps.execute(envelope, multi_point, sr, null)); + assertTrue(!crosses.execute(envelope, multi_point, sr, null)); + } + + { + MultiPoint multi_point = (MultiPoint) (TestCommonMethods + .fromJson("{\"points\":[[1,10],[9,10]]}").getGeometry()); + Envelope envelope = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":0,\"ymin\":10,\"xmax\":10,\"ymax\":10}") + .getGeometry()); + wiggleGeometry(multi_point, 0.00000001, 1982); + wiggleGeometry(envelope, 0.00000001, 511); + assertTrue(!equals.execute(envelope, multi_point, sr, null)); // degenerate + // envelopes + // slices + // through + // all + // the + // points, + // and + // they + // are + // in + // the + // interior + // of + // the + // line + assertTrue(contains.execute(envelope, multi_point, sr, null)); + assertTrue(!disjoint.execute(envelope, multi_point, sr, null)); + assertTrue(!touches.execute(envelope, multi_point, sr, null)); + assertTrue(!overlaps.execute(envelope, multi_point, sr, null)); + assertTrue(!crosses.execute(envelope, multi_point, sr, null)); + } + + { + MultiPoint multi_point = (MultiPoint) (TestCommonMethods + .fromJson("{\"points\":[[0,-1],[0,11],[11,11],[15,15]]}") + .getGeometry()); + Envelope envelope = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":0,\"ymin\":10,\"xmax\":10,\"ymax\":10}") + .getGeometry()); + wiggleGeometry(multi_point, 0.00000001, 1982); + wiggleGeometry(envelope, 0.00000001, 511); + assertTrue(!equals.execute(envelope, multi_point, sr, null)); // all + // points + // exterior + assertTrue(!contains.execute(envelope, multi_point, sr, null)); + assertTrue(disjoint.execute(envelope, multi_point, sr, null)); + assertTrue(!touches.execute(envelope, multi_point, sr, null)); + assertTrue(!overlaps.execute(envelope, multi_point, sr, null)); + assertTrue(!crosses.execute(envelope, multi_point, sr, null)); + } + + { + MultiPoint multi_point = (MultiPoint) (TestCommonMethods + .fromJson("{\"points\":[[0,-1],[0,11],[11,11],[15,15]]}") + .getGeometry()); + Envelope envelope = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":10,\"ymin\":10,\"xmax\":10,\"ymax\":10}") + .getGeometry()); + wiggleGeometry(multi_point, 0.00000001, 1982); + wiggleGeometry(envelope, 0.00000001, 511); + assertTrue(!equals.execute(envelope, multi_point, sr, null)); // all + // points + // exterior + assertTrue(!contains.execute(envelope, multi_point, sr, null)); + assertTrue(disjoint.execute(envelope, multi_point, sr, null)); + assertTrue(!touches.execute(envelope, multi_point, sr, null)); + assertTrue(!overlaps.execute(envelope, multi_point, sr, null)); + assertTrue(!crosses.execute(envelope, multi_point, sr, null)); + } + + { + MultiPoint multi_point = (MultiPoint) (TestCommonMethods + .fromJson("{\"points\":[[0,-1],[0,11],[11,11],[15,15]]}") + .getGeometry()); + Envelope envelope = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":11,\"ymax\":11}") + .getGeometry()); + wiggleGeometry(multi_point, 0.00000001, 1982); + wiggleGeometry(envelope, 0.00000001, 511); + assertTrue(!equals.execute(envelope, multi_point, sr, null)); // all + // points + // exterior + assertTrue(!contains.execute(multi_point, envelope, sr, null)); + assertTrue(!contains.execute(envelope, multi_point, sr, null)); + assertTrue(!disjoint.execute(envelope, multi_point, sr, null)); + assertTrue(touches.execute(envelope, multi_point, sr, null)); + assertTrue(!overlaps.execute(envelope, multi_point, sr, null)); + assertTrue(!crosses.execute(envelope, multi_point, sr, null)); + } + + { + MultiPoint multi_point = (MultiPoint) (TestCommonMethods + .fromJson("{\"points\":[[0,-1],[0,-1]]}").getGeometry()); + Envelope envelope = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":0,\"ymin\":-1,\"xmax\":0,\"ymax\":-1}") + .getGeometry()); + wiggleGeometry(multi_point, 0.00000001, 1982); + wiggleGeometry(envelope, 0.00000001, 511); + assertTrue(equals.execute(envelope, multi_point, sr, null)); // all + // points + // exterior + assertTrue(contains.execute(multi_point, envelope, sr, null)); + assertTrue(contains.execute(envelope, multi_point, sr, null)); + assertTrue(!disjoint.execute(envelope, multi_point, sr, null)); + assertTrue(!touches.execute(envelope, multi_point, sr, null)); + assertTrue(!overlaps.execute(envelope, multi_point, sr, null)); + assertTrue(!crosses.execute(envelope, multi_point, sr, null)); + } + } + + @Test + public void testPointEnvelope() { + OperatorEquals equals = (OperatorEquals) (OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Equals)); + OperatorContains contains = (OperatorContains) (OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Contains)); + OperatorDisjoint disjoint = (OperatorDisjoint) (OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Disjoint)); + OperatorCrosses crosses = (OperatorCrosses) (OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Crosses)); + @SuppressWarnings("unused") + OperatorWithin within = (OperatorWithin) (OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Within)); + OperatorOverlaps overlaps = (OperatorOverlaps) (OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Overlaps)); + OperatorTouches touches = (OperatorTouches) (OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Touches)); + SpatialReference sr = SpatialReference.create(4326); + + { + Point point = (Point) (TestCommonMethods + .fromJson("{\"x\":5,\"y\":6}").getGeometry()); + Envelope envelope = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":10}") + .getGeometry()); + wiggleGeometry(point, 0.00000001, 1982); + wiggleGeometry(envelope, 0.00000001, 511); + assertTrue(!equals.execute(envelope, point, sr, null)); + assertTrue(contains.execute(envelope, point, sr, null)); + assertTrue(!contains.execute(point, envelope, sr, null)); + assertTrue(!disjoint.execute(envelope, point, sr, null)); + assertTrue(!touches.execute(envelope, point, sr, null)); + assertTrue(!overlaps.execute(envelope, point, sr, null)); + assertTrue(!crosses.execute(envelope, point, sr, null)); + } + + { + Point point = (Point) (TestCommonMethods + .fromJson("{\"x\":0,\"y\":10}").getGeometry()); + Envelope envelope = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":10}") + .getGeometry()); + wiggleGeometry(point, 0.00000001, 1982); + wiggleGeometry(envelope, 0.00000001, 511); + assertTrue(!equals.execute(envelope, point, sr, null)); + assertTrue(!contains.execute(envelope, point, sr, null)); + assertTrue(!contains.execute(point, envelope, sr, null)); + assertTrue(!disjoint.execute(envelope, point, sr, null)); + assertTrue(touches.execute(envelope, point, sr, null)); + assertTrue(!overlaps.execute(envelope, point, sr, null)); + assertTrue(!crosses.execute(envelope, point, sr, null)); + } + + { + Point point = (Point) (TestCommonMethods + .fromJson("{\"x\":0,\"y\":11}").getGeometry()); + Envelope envelope = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":10}") + .getGeometry()); + wiggleGeometry(point, 0.00000001, 1982); + wiggleGeometry(envelope, 0.00000001, 511); + assertTrue(!equals.execute(envelope, point, sr, null)); + assertTrue(!contains.execute(envelope, point, sr, null)); + assertTrue(!contains.execute(point, envelope, sr, null)); + assertTrue(disjoint.execute(envelope, point, sr, null)); + assertTrue(!touches.execute(envelope, point, sr, null)); + assertTrue(!overlaps.execute(envelope, point, sr, null)); + assertTrue(!crosses.execute(envelope, point, sr, null)); + } + + { + Point point = (Point) (TestCommonMethods + .fromJson("{\"x\":0,\"y\":0}").getGeometry()); + Envelope envelope = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":0}") + .getGeometry()); + wiggleGeometry(point, 0.00000001, 1982); + wiggleGeometry(envelope, 0.00000001, 511); + assertTrue(!equals.execute(envelope, point, sr, null)); + assertTrue(!contains.execute(envelope, point, sr, null)); + assertTrue(!contains.execute(point, envelope, sr, null)); + assertTrue(!disjoint.execute(envelope, point, sr, null)); + assertTrue(touches.execute(envelope, point, sr, null)); + assertTrue(!overlaps.execute(envelope, point, sr, null)); + assertTrue(!crosses.execute(envelope, point, sr, null)); + } + + { + Point point = (Point) (TestCommonMethods + .fromJson("{\"x\":5,\"y\":0}").getGeometry()); + Envelope envelope = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":0}") + .getGeometry()); + wiggleGeometry(point, 0.00000001, 1982); + wiggleGeometry(envelope, 0.00000001, 511); + assertTrue(!equals.execute(envelope, point, sr, null)); + assertTrue(contains.execute(envelope, point, sr, null)); + assertTrue(!contains.execute(point, envelope, sr, null)); + assertTrue(!disjoint.execute(envelope, point, sr, null)); + assertTrue(!touches.execute(envelope, point, sr, null)); + assertTrue(!overlaps.execute(envelope, point, sr, null)); + assertTrue(!crosses.execute(envelope, point, sr, null)); + } + + { + Point point = (Point) (TestCommonMethods + .fromJson("{\"x\":11,\"y\":0}").getGeometry()); + Envelope envelope = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":0}") + .getGeometry()); + wiggleGeometry(point, 0.00000001, 1982); + wiggleGeometry(envelope, 0.00000001, 511); + assertTrue(!equals.execute(envelope, point, sr, null)); + assertTrue(!contains.execute(envelope, point, sr, null)); + assertTrue(!contains.execute(point, envelope, sr, null)); + assertTrue(disjoint.execute(envelope, point, sr, null)); + assertTrue(!touches.execute(envelope, point, sr, null)); + assertTrue(!overlaps.execute(envelope, point, sr, null)); + assertTrue(!crosses.execute(envelope, point, sr, null)); + } + + { + Point point = (Point) (TestCommonMethods + .fromJson("{\"x\":0,\"y\":0}").getGeometry()); + Envelope envelope = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":0,\"ymax\":0}") + .getGeometry()); + wiggleGeometry(point, 0.00000001, 1982); + wiggleGeometry(envelope, 0.00000001, 511); + assertTrue(equals.execute(envelope, point, sr, null)); + assertTrue(contains.execute(envelope, point, sr, null)); + assertTrue(contains.execute(point, envelope, sr, null)); + assertTrue(!disjoint.execute(envelope, point, sr, null)); + assertTrue(!touches.execute(envelope, point, sr, null)); + assertTrue(!overlaps.execute(envelope, point, sr, null)); + assertTrue(!crosses.execute(envelope, point, sr, null)); + } + } + + @Test + public void testEnvelopeEnvelope() { + OperatorEquals equals = (OperatorEquals) (OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Equals)); + OperatorContains contains = (OperatorContains) (OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Contains)); + OperatorDisjoint disjoint = (OperatorDisjoint) (OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Disjoint)); + OperatorCrosses crosses = (OperatorCrosses) (OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Crosses)); + @SuppressWarnings("unused") + OperatorWithin within = (OperatorWithin) (OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Within)); + OperatorOverlaps overlaps = (OperatorOverlaps) (OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Overlaps)); + OperatorTouches touches = (OperatorTouches) (OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Touches)); + SpatialReference sr = SpatialReference.create(4326); + + { + Envelope env1 = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":10}") + .getGeometry()); + Envelope env2 = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":10}") + .getGeometry()); + wiggleGeometry(env1, 0.00000001, 1982); + wiggleGeometry(env2, 0.00000001, 511); + assertTrue(equals.execute(env1, env2, sr, null)); + assertTrue(contains.execute(env1, env2, sr, null)); + assertTrue(contains.execute(env2, env1, sr, null)); + assertTrue(!disjoint.execute(env1, env2, sr, null)); + assertTrue(!touches.execute(env1, env2, sr, null)); + assertTrue(!touches.execute(env2, env1, sr, null)); + assertTrue(!overlaps.execute(env1, env2, sr, null)); + assertTrue(!overlaps.execute(env2, env1, sr, null)); + assertTrue(!crosses.execute(env1, env2, sr, null)); + assertTrue(!crosses.execute(env2, env1, sr, null)); + } + + { + Envelope env1 = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":5,\"ymin\":5,\"xmax\":10,\"ymax\":10}") + .getGeometry()); + Envelope env2 = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":10}") + .getGeometry()); + wiggleGeometry(env1, 0.00000001, 1982); + wiggleGeometry(env2, 0.00000001, 511); + assertTrue(!equals.execute(env1, env2, sr, null)); + assertTrue(!contains.execute(env1, env2, sr, null)); + assertTrue(contains.execute(env2, env1, sr, null)); + assertTrue(!disjoint.execute(env1, env2, sr, null)); + assertTrue(!touches.execute(env1, env2, sr, null)); + assertTrue(!overlaps.execute(env1, env2, sr, null)); + assertTrue(!overlaps.execute(env2, env1, sr, null)); + assertTrue(!crosses.execute(env1, env2, sr, null)); + assertTrue(!crosses.execute(env2, env1, sr, null)); + } + + { + Envelope env1 = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":5,\"ymin\":5,\"xmax\":15,\"ymax\":15}") + .getGeometry()); + Envelope env2 = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":10}") + .getGeometry()); + wiggleGeometry(env1, 0.00000001, 1982); + wiggleGeometry(env2, 0.00000001, 511); + assertTrue(!equals.execute(env1, env2, sr, null)); + assertTrue(!contains.execute(env1, env2, sr, null)); + assertTrue(!contains.execute(env2, env1, sr, null)); + assertTrue(!disjoint.execute(env1, env2, sr, null)); + assertTrue(!touches.execute(env1, env2, sr, null)); + assertTrue(overlaps.execute(env1, env2, sr, null)); + assertTrue(overlaps.execute(env2, env1, sr, null)); + assertTrue(!crosses.execute(env1, env2, sr, null)); + assertTrue(!crosses.execute(env2, env1, sr, null)); + } + + { + Envelope env1 = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":10,\"ymin\":0,\"xmax\":20,\"ymax\":10}") + .getGeometry()); + Envelope env2 = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":10}") + .getGeometry()); + wiggleGeometry(env1, 0.00000001, 1982); + wiggleGeometry(env2, 0.00000001, 511); + assertTrue(!equals.execute(env1, env2, sr, null)); + assertTrue(!contains.execute(env1, env2, sr, null)); + assertTrue(!contains.execute(env2, env1, sr, null)); + assertTrue(!disjoint.execute(env1, env2, sr, null)); + assertTrue(touches.execute(env1, env2, sr, null)); + assertTrue(!overlaps.execute(env1, env2, sr, null)); + assertTrue(!overlaps.execute(env2, env1, sr, null)); + assertTrue(!crosses.execute(env1, env2, sr, null)); + assertTrue(!crosses.execute(env2, env1, sr, null)); + } + + { + Envelope env1 = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":10,\"ymin\":0,\"xmax\":20,\"ymax\":10}") + .getGeometry()); + Envelope env2 = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":10}") + .getGeometry()); + wiggleGeometry(env1, 0.00000001, 1982); + wiggleGeometry(env2, 0.00000001, 511); + assertTrue(!equals.execute(env1, env2, sr, null)); + assertTrue(!contains.execute(env1, env2, sr, null)); + assertTrue(!contains.execute(env2, env1, sr, null)); + assertTrue(!disjoint.execute(env1, env2, sr, null)); + assertTrue(touches.execute(env1, env2, sr, null)); + assertTrue(!overlaps.execute(env1, env2, sr, null)); + assertTrue(!overlaps.execute(env2, env1, sr, null)); + assertTrue(!crosses.execute(env1, env2, sr, null)); + assertTrue(!crosses.execute(env2, env1, sr, null)); + } + + { + Envelope env1 = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":10,\"ymin\":0,\"xmax\":20,\"ymax\":10}") + .getGeometry()); + Envelope env2 = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":0,\"ymin\":10,\"xmax\":10,\"ymax\":20}") + .getGeometry()); + wiggleGeometry(env1, 0.00000001, 1982); + wiggleGeometry(env2, 0.00000001, 511); + assertTrue(!equals.execute(env1, env2, sr, null)); + assertTrue(!contains.execute(env1, env2, sr, null)); + assertTrue(!contains.execute(env2, env1, sr, null)); + assertTrue(!disjoint.execute(env1, env2, sr, null)); + assertTrue(touches.execute(env1, env2, sr, null)); + assertTrue(!overlaps.execute(env1, env2, sr, null)); + assertTrue(!overlaps.execute(env2, env1, sr, null)); + assertTrue(!crosses.execute(env1, env2, sr, null)); + assertTrue(!crosses.execute(env2, env1, sr, null)); + } + + { + Envelope env1 = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":10}") + .getGeometry()); + Envelope env2 = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":0}") + .getGeometry()); + wiggleGeometry(env1, 0.00000001, 1982); + wiggleGeometry(env2, 0.00000001, 511); + assertTrue(!equals.execute(env1, env2, sr, null)); + assertTrue(!contains.execute(env1, env2, sr, null)); + assertTrue(!contains.execute(env2, env1, sr, null)); + assertTrue(!disjoint.execute(env1, env2, sr, null)); + assertTrue(touches.execute(env1, env2, sr, null)); + assertTrue(!overlaps.execute(env1, env2, sr, null)); + assertTrue(!overlaps.execute(env2, env1, sr, null)); + assertTrue(!crosses.execute(env1, env2, sr, null)); + assertTrue(!crosses.execute(env2, env1, sr, null)); + } + + { + Envelope env1 = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":10}") + .getGeometry()); + Envelope env2 = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":15,\"ymax\":0}") + .getGeometry()); + wiggleGeometry(env1, 0.00000001, 1982); + wiggleGeometry(env2, 0.00000001, 511); + assertTrue(!equals.execute(env1, env2, sr, null)); + assertTrue(!contains.execute(env1, env2, sr, null)); + assertTrue(!contains.execute(env2, env1, sr, null)); + assertTrue(!disjoint.execute(env1, env2, sr, null)); + assertTrue(touches.execute(env1, env2, sr, null)); + assertTrue(!overlaps.execute(env1, env2, sr, null)); + assertTrue(!overlaps.execute(env2, env1, sr, null)); + assertTrue(!crosses.execute(env1, env2, sr, null)); + assertTrue(!crosses.execute(env2, env1, sr, null)); + } + + { + Envelope env1 = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":10}") + .getGeometry()); + Envelope env2 = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":-5,\"ymin\":5,\"xmax\":0,\"ymax\":5}") + .getGeometry()); + wiggleGeometry(env1, 0.00000001, 1982); + wiggleGeometry(env2, 0.00000001, 511); + assertTrue(!equals.execute(env1, env2, sr, null)); + assertTrue(!contains.execute(env1, env2, sr, null)); + assertTrue(!contains.execute(env2, env1, sr, null)); + assertTrue(!disjoint.execute(env1, env2, sr, null)); + assertTrue(touches.execute(env1, env2, sr, null)); + assertTrue(!overlaps.execute(env1, env2, sr, null)); + assertTrue(!overlaps.execute(env2, env1, sr, null)); + assertTrue(!crosses.execute(env1, env2, sr, null)); + assertTrue(!crosses.execute(env2, env1, sr, null)); + } + + { + Envelope env1 = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":10}") + .getGeometry()); + Envelope env2 = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":-5,\"ymin\":5,\"xmax\":5,\"ymax\":5}") + .getGeometry()); + wiggleGeometry(env1, 0.00000001, 1982); + wiggleGeometry(env2, 0.00000001, 511); + assertTrue(!equals.execute(env1, env2, sr, null)); + assertTrue(!contains.execute(env1, env2, sr, null)); + assertTrue(!contains.execute(env2, env1, sr, null)); + assertTrue(!disjoint.execute(env1, env2, sr, null)); + assertTrue(!touches.execute(env1, env2, sr, null)); + assertTrue(!overlaps.execute(env1, env2, sr, null)); + assertTrue(!overlaps.execute(env2, env1, sr, null)); + assertTrue(crosses.execute(env1, env2, sr, null)); + assertTrue(crosses.execute(env2, env1, sr, null)); + } + + { + Envelope env1 = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":10}") + .getGeometry()); + Envelope env2 = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":3,\"ymin\":5,\"xmax\":5,\"ymax\":5}") + .getGeometry()); + wiggleGeometry(env1, 0.00000001, 1982); + wiggleGeometry(env2, 0.00000001, 511); + assertTrue(!equals.execute(env1, env2, sr, null)); + assertTrue(contains.execute(env1, env2, sr, null)); + assertTrue(!contains.execute(env2, env1, sr, null)); + assertTrue(!disjoint.execute(env1, env2, sr, null)); + assertTrue(!touches.execute(env1, env2, sr, null)); + assertTrue(!overlaps.execute(env1, env2, sr, null)); + assertTrue(!overlaps.execute(env2, env1, sr, null)); + assertTrue(!crosses.execute(env1, env2, sr, null)); + assertTrue(!crosses.execute(env2, env1, sr, null)); + } + + { + Envelope env1 = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":10}") + .getGeometry()); + Envelope env2 = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":3,\"ymin\":5,\"xmax\":10,\"ymax\":5}") + .getGeometry()); + wiggleGeometry(env1, 0.00000001, 1982); + wiggleGeometry(env2, 0.00000001, 511); + assertTrue(!equals.execute(env1, env2, sr, null)); + assertTrue(contains.execute(env1, env2, sr, null)); + assertTrue(!contains.execute(env2, env1, sr, null)); + assertTrue(!disjoint.execute(env1, env2, sr, null)); + assertTrue(!touches.execute(env1, env2, sr, null)); + assertTrue(!overlaps.execute(env1, env2, sr, null)); + assertTrue(!overlaps.execute(env2, env1, sr, null)); + assertTrue(!crosses.execute(env1, env2, sr, null)); + assertTrue(!crosses.execute(env2, env1, sr, null)); + } + + { + Envelope env1 = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":10}") + .getGeometry()); + Envelope env2 = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":-5,\"ymin\":0,\"xmax\":0,\"ymax\":0}") + .getGeometry()); + wiggleGeometry(env1, 0.00000001, 1982); + wiggleGeometry(env2, 0.00000001, 511); + assertTrue(!equals.execute(env1, env2, sr, null)); + assertTrue(!contains.execute(env1, env2, sr, null)); + assertTrue(!contains.execute(env2, env1, sr, null)); + assertTrue(!disjoint.execute(env1, env2, sr, null)); + assertTrue(touches.execute(env1, env2, sr, null)); + assertTrue(!overlaps.execute(env1, env2, sr, null)); + assertTrue(!overlaps.execute(env2, env1, sr, null)); + assertTrue(!crosses.execute(env1, env2, sr, null)); + assertTrue(!crosses.execute(env2, env1, sr, null)); + } + + { + Envelope env1 = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":0}") + .getGeometry()); + Envelope env2 = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":0}") + .getGeometry()); + wiggleGeometry(env1, 0.00000001, 1982); + wiggleGeometry(env2, 0.00000001, 511); + assertTrue(equals.execute(env1, env2, sr, null)); + assertTrue(contains.execute(env1, env2, sr, null)); + assertTrue(contains.execute(env2, env1, sr, null)); + assertTrue(!disjoint.execute(env1, env2, sr, null)); + assertTrue(!touches.execute(env1, env2, sr, null)); + assertTrue(!overlaps.execute(env1, env2, sr, null)); + assertTrue(!overlaps.execute(env2, env1, sr, null)); + assertTrue(!crosses.execute(env1, env2, sr, null)); + assertTrue(!crosses.execute(env2, env1, sr, null)); + } + + { + Envelope env1 = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":15,\"ymax\":0}") + .getGeometry()); + Envelope env2 = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":0}") + .getGeometry()); + wiggleGeometry(env1, 0.00000001, 1982); + wiggleGeometry(env2, 0.00000001, 511); + assertTrue(!equals.execute(env1, env2, sr, null)); + assertTrue(contains.execute(env1, env2, sr, null)); + assertTrue(!contains.execute(env2, env1, sr, null)); + assertTrue(!disjoint.execute(env1, env2, sr, null)); + assertTrue(!touches.execute(env1, env2, sr, null)); + assertTrue(!overlaps.execute(env1, env2, sr, null)); + assertTrue(!overlaps.execute(env2, env1, sr, null)); + assertTrue(!crosses.execute(env1, env2, sr, null)); + assertTrue(!crosses.execute(env2, env1, sr, null)); + } + + { + Envelope env1 = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":15,\"ymax\":0}") + .getGeometry()); + Envelope env2 = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":-5,\"ymin\":0,\"xmax\":10,\"ymax\":0}") + .getGeometry()); + wiggleGeometry(env1, 0.00000001, 1982); + wiggleGeometry(env2, 0.00000001, 511); + assertTrue(!equals.execute(env1, env2, sr, null)); + assertTrue(!contains.execute(env1, env2, sr, null)); + assertTrue(!contains.execute(env2, env1, sr, null)); + assertTrue(!disjoint.execute(env1, env2, sr, null)); + assertTrue(!touches.execute(env1, env2, sr, null)); + assertTrue(overlaps.execute(env1, env2, sr, null)); + assertTrue(overlaps.execute(env2, env1, sr, null)); + assertTrue(!crosses.execute(env1, env2, sr, null)); + assertTrue(!crosses.execute(env2, env1, sr, null)); + } + + { + Envelope env1 = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":5,\"ymax\":0}") + .getGeometry()); + Envelope env2 = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":-5,\"ymin\":0,\"xmax\":0,\"ymax\":0}") + .getGeometry()); + wiggleGeometry(env1, 0.00000001, 1982); + wiggleGeometry(env2, 0.00000001, 511); + assertTrue(!equals.execute(env1, env2, sr, null)); + assertTrue(!contains.execute(env1, env2, sr, null)); + assertTrue(!contains.execute(env2, env1, sr, null)); + assertTrue(!disjoint.execute(env1, env2, sr, null)); + assertTrue(touches.execute(env1, env2, sr, null)); + assertTrue(!overlaps.execute(env1, env2, sr, null)); + assertTrue(!overlaps.execute(env2, env1, sr, null)); + assertTrue(!crosses.execute(env1, env2, sr, null)); + assertTrue(!crosses.execute(env2, env1, sr, null)); + } + + { + Envelope env1 = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":0}") + .getGeometry()); + Envelope env2 = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":5,\"ymin\":-5,\"xmax\":5,\"ymax\":5}") + .getGeometry()); + wiggleGeometry(env1, 0.00000001, 1982); + wiggleGeometry(env2, 0.00000001, 511); + assertTrue(!equals.execute(env1, env2, sr, null)); + assertTrue(!contains.execute(env1, env2, sr, null)); + assertTrue(!contains.execute(env2, env1, sr, null)); + assertTrue(!disjoint.execute(env1, env2, sr, null)); + assertTrue(!touches.execute(env1, env2, sr, null)); + assertTrue(!overlaps.execute(env1, env2, sr, null)); + assertTrue(!overlaps.execute(env2, env1, sr, null)); + assertTrue(crosses.execute(env1, env2, sr, null)); + assertTrue(crosses.execute(env2, env1, sr, null)); + } + + { + Envelope env1 = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":0}") + .getGeometry()); + Envelope env2 = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":10,\"ymin\":0,\"xmax\":20,\"ymax\":0}") + .getGeometry()); + wiggleGeometry(env1, 0.00000001, 1982); + wiggleGeometry(env2, 0.00000001, 511); + assertTrue(!equals.execute(env1, env2, sr, null)); + assertTrue(!contains.execute(env1, env2, sr, null)); + assertTrue(!contains.execute(env2, env1, sr, null)); + assertTrue(!disjoint.execute(env1, env2, sr, null)); + assertTrue(touches.execute(env1, env2, sr, null)); + assertTrue(!overlaps.execute(env1, env2, sr, null)); + assertTrue(!overlaps.execute(env2, env1, sr, null)); + assertTrue(!crosses.execute(env1, env2, sr, null)); + assertTrue(!crosses.execute(env2, env1, sr, null)); + } + + { + Envelope env1 = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":0}") + .getGeometry()); + Envelope env2 = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":5,\"ymin\":0,\"xmax\":5,\"ymax\":5}") + .getGeometry()); + wiggleGeometry(env1, 0.00000001, 1982); + wiggleGeometry(env2, 0.00000001, 511); + assertTrue(!equals.execute(env1, env2, sr, null)); + assertTrue(!contains.execute(env1, env2, sr, null)); + assertTrue(!contains.execute(env2, env1, sr, null)); + assertTrue(!disjoint.execute(env1, env2, sr, null)); + assertTrue(touches.execute(env1, env2, sr, null)); + assertTrue(touches.execute(env2, env1, sr, null)); + assertTrue(!overlaps.execute(env1, env2, sr, null)); + assertTrue(!overlaps.execute(env2, env1, sr, null)); + assertTrue(!crosses.execute(env1, env2, sr, null)); + assertTrue(!crosses.execute(env2, env1, sr, null)); + } + + { + Envelope env1 = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":0,\"ymax\":0}") + .getGeometry()); + Envelope env2 = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":10}") + .getGeometry()); + wiggleGeometry(env1, 0.00000001, 1982); + wiggleGeometry(env2, 0.00000001, 511); + assertTrue(!equals.execute(env1, env2, sr, null)); + assertTrue(!contains.execute(env1, env2, sr, null)); + assertTrue(!contains.execute(env2, env1, sr, null)); + assertTrue(!disjoint.execute(env1, env2, sr, null)); + assertTrue(touches.execute(env1, env2, sr, null)); + assertTrue(touches.execute(env2, env1, sr, null)); + assertTrue(!overlaps.execute(env1, env2, sr, null)); + assertTrue(!overlaps.execute(env2, env1, sr, null)); + assertTrue(!crosses.execute(env1, env2, sr, null)); + assertTrue(!crosses.execute(env2, env1, sr, null)); + } + + { + Envelope env1 = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":5,\"ymin\":0,\"xmax\":5,\"ymax\":0}") + .getGeometry()); + Envelope env2 = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":10}") + .getGeometry()); + wiggleGeometry(env1, 0.00000001, 1982); + wiggleGeometry(env2, 0.00000001, 511); + assertTrue(!equals.execute(env1, env2, sr, null)); + assertTrue(!contains.execute(env1, env2, sr, null)); + assertTrue(!contains.execute(env2, env1, sr, null)); + assertTrue(!disjoint.execute(env1, env2, sr, null)); + assertTrue(touches.execute(env1, env2, sr, null)); + assertTrue(touches.execute(env2, env1, sr, null)); + assertTrue(!overlaps.execute(env1, env2, sr, null)); + assertTrue(!overlaps.execute(env2, env1, sr, null)); + assertTrue(!crosses.execute(env1, env2, sr, null)); + assertTrue(!crosses.execute(env2, env1, sr, null)); + } + + { + Envelope env1 = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":5,\"ymin\":5,\"xmax\":5,\"ymax\":5}") + .getGeometry()); + Envelope env2 = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":10}") + .getGeometry()); + wiggleGeometry(env1, 0.00000001, 1982); + wiggleGeometry(env2, 0.00000001, 511); + assertTrue(!equals.execute(env1, env2, sr, null)); + assertTrue(!contains.execute(env1, env2, sr, null)); + assertTrue(contains.execute(env2, env1, sr, null)); + assertTrue(!disjoint.execute(env1, env2, sr, null)); + assertTrue(!touches.execute(env1, env2, sr, null)); + assertTrue(!touches.execute(env2, env1, sr, null)); + assertTrue(!overlaps.execute(env1, env2, sr, null)); + assertTrue(!overlaps.execute(env2, env1, sr, null)); + assertTrue(!crosses.execute(env1, env2, sr, null)); + assertTrue(!crosses.execute(env2, env1, sr, null)); + } + + { + Envelope env1 = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":0,\"ymax\":0}") + .getGeometry()); + Envelope env2 = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":0}") + .getGeometry()); + wiggleGeometry(env1, 0.00000001, 1982); + wiggleGeometry(env2, 0.00000001, 511); + assertTrue(!equals.execute(env1, env2, sr, null)); + assertTrue(!contains.execute(env1, env2, sr, null)); + assertTrue(!contains.execute(env2, env1, sr, null)); + assertTrue(!disjoint.execute(env1, env2, sr, null)); + assertTrue(touches.execute(env1, env2, sr, null)); + assertTrue(touches.execute(env2, env1, sr, null)); + assertTrue(!overlaps.execute(env1, env2, sr, null)); + assertTrue(!overlaps.execute(env2, env1, sr, null)); + assertTrue(!crosses.execute(env1, env2, sr, null)); + assertTrue(!crosses.execute(env2, env1, sr, null)); + } + + { + Envelope env1 = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":5,\"ymin\":0,\"xmax\":5,\"ymax\":0}") + .getGeometry()); + Envelope env2 = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":0,\"ymin\":0,\"xmax\":10,\"ymax\":0}") + .getGeometry()); + wiggleGeometry(env1, 0.00000001, 1982); + wiggleGeometry(env2, 0.00000001, 511); + assertTrue(!equals.execute(env1, env2, sr, null)); + assertTrue(!contains.execute(env1, env2, sr, null)); + assertTrue(contains.execute(env2, env1, sr, null)); + assertTrue(!disjoint.execute(env1, env2, sr, null)); + assertTrue(!touches.execute(env1, env2, sr, null)); + assertTrue(!touches.execute(env2, env1, sr, null)); + assertTrue(!overlaps.execute(env1, env2, sr, null)); + assertTrue(!overlaps.execute(env2, env1, sr, null)); + assertTrue(!crosses.execute(env1, env2, sr, null)); + assertTrue(!crosses.execute(env2, env1, sr, null)); + } + + { + Envelope env1 = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":5,\"ymin\":0,\"xmax\":5,\"ymax\":0}") + .getGeometry()); + Envelope env2 = (Envelope) (TestCommonMethods + .fromJson("{\"xmin\":5,\"ymin\":0,\"xmax\":5,\"ymax\":0}") + .getGeometry()); + wiggleGeometry(env1, 0.00000001, 1982); + wiggleGeometry(env2, 0.00000001, 511); + assertTrue(equals.execute(env1, env2, sr, null)); + assertTrue(contains.execute(env1, env2, sr, null)); + assertTrue(contains.execute(env2, env1, sr, null)); + assertTrue(!disjoint.execute(env1, env2, sr, null)); + assertTrue(!touches.execute(env1, env2, sr, null)); + assertTrue(!touches.execute(env2, env1, sr, null)); + assertTrue(!overlaps.execute(env1, env2, sr, null)); + assertTrue(!overlaps.execute(env2, env1, sr, null)); + assertTrue(!crosses.execute(env1, env2, sr, null)); + assertTrue(!crosses.execute(env2, env1, sr, null)); + } + } + + static void wiggleGeometry(Geometry geometry, double tolerance, int rand) { + int type = geometry.getType().value(); + + if (type == Geometry.GeometryType.Polygon + || type == Geometry.GeometryType.Polyline + || type == Geometry.GeometryType.MultiPoint) { + MultiVertexGeometry mvGeom = (MultiVertexGeometry) geometry; + for (int i = 0; i < mvGeom.getPointCount(); i++) { + Point2D pt = mvGeom.getXY(i); + + // create random vector and normalize it to 0.49 * tolerance + Point2D randomV = new Point2D(); + rand = NumberUtils.nextRand(rand); + randomV.x = 1.0 * rand / NumberUtils.intMax() - 0.5; + rand = NumberUtils.nextRand(rand); + randomV.y = 1.0 * rand / NumberUtils.intMax() - 0.5; + randomV.normalize(); + randomV.scale(0.45 * tolerance); + pt.add(randomV); + mvGeom.setXY(i, pt); + } + } else if (type == Geometry.GeometryType.Point) { + Point ptGeom = (Point) (geometry); + Point2D pt = ptGeom.getXY(); + // create random vector and normalize it to 0.49 * tolerance + Point2D randomV = new Point2D(); + rand = NumberUtils.nextRand(rand); + randomV.x = 1.0 * rand / NumberUtils.intMax() - 0.5; + rand = NumberUtils.nextRand(rand); + randomV.y = 1.0 * rand / NumberUtils.intMax() - 0.5; + randomV.normalize(); + randomV.scale(0.45 * tolerance); + pt.add(randomV); + ptGeom.setXY(pt); + } else if (type == Geometry.GeometryType.Envelope) { + Envelope envGeom = (Envelope) (geometry); + Envelope2D env = new Envelope2D(); + envGeom.queryEnvelope2D(env); + double xmin, xmax, ymin, ymax; + Point2D pt = new Point2D(); + env.queryLowerLeft(pt); + // create random vector and normalize it to 0.49 * tolerance + Point2D randomV = new Point2D(); + rand = NumberUtils.nextRand(rand); + randomV.x = 1.0 * rand / NumberUtils.intMax() - 0.5; + rand = NumberUtils.nextRand(rand); + randomV.y = 1.0 * rand / NumberUtils.intMax() - 0.5; + randomV.normalize(); + randomV.scale(0.45 * tolerance); + xmin = (pt.x + randomV.x); + ymin = (pt.y + randomV.y); + + env.queryUpperRight(pt); + // create random vector and normalize it to 0.49 * tolerance + rand = NumberUtils.nextRand(rand); + randomV.x = 1.0 * rand / NumberUtils.intMax() - 0.5; + rand = NumberUtils.nextRand(rand); + randomV.y = 1.0 * rand / NumberUtils.intMax() - 0.5; + randomV.normalize(); + randomV.scale(0.45 * tolerance); + xmax = (pt.x + randomV.x); + ymax = (pt.y + randomV.y); + + if (xmin > xmax) { + double swap = xmin; + xmin = xmax; + xmax = swap; + } + + if (ymin > ymax) { + double swap = ymin; + ymin = ymax; + ymax = swap; + } + + envGeom.setCoords(xmin, ymin, xmax, ymax); + } + + } + + @Test + public void testDisjointRelationFalse() { + { + OperatorDisjoint op = (OperatorDisjoint) (OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Disjoint)); + Envelope env1 = new Envelope(50, 50, 150, 150); + Envelope env2 = new Envelope(25, 25, 175, 175); + boolean result = op.execute(env1, env2, + SpatialReference.create(4326), null); + assertTrue(!result); + } + { + OperatorIntersects op = (OperatorIntersects) (OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Intersects)); + Envelope env1 = new Envelope(50, 50, 150, 150); + Envelope env2 = new Envelope(25, 25, 175, 175); + boolean result = op.execute(env1, env2, + SpatialReference.create(4326), null); + assertTrue(result); + } + { + OperatorContains op = (OperatorContains) (OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Contains)); + Envelope env1 = new Envelope(100, 175, 200, 225); + Polyline polyline = new Polyline(); + polyline.startPath(200, 175); + polyline.lineTo(200, 225); + polyline.lineTo(125, 200); + boolean result = op.execute(env1, polyline, + SpatialReference.create(4326), null); + assertTrue(result); + } + { + OperatorTouches op = (OperatorTouches) (OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Touches)); + Envelope env1 = new Envelope(100, 200, 400, 400); + Polyline polyline = new Polyline(); + polyline.startPath(300, 60); + polyline.lineTo(300, 200); + polyline.lineTo(400, 50); + boolean result = op.execute(env1, polyline, + SpatialReference.create(4326), null); + assertTrue(result); + } + + { + OperatorTouches op = (OperatorTouches) (OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Touches)); + Envelope env1 = new Envelope(50, 50, 150, 150); + Polyline polyline = new Polyline(); + polyline.startPath(100, 20); + polyline.lineTo(100, 50); + polyline.lineTo(150, 10); + boolean result = op.execute(polyline, env1, + SpatialReference.create(4326), null); + assertTrue(result); + } + + { + OperatorDisjoint op = (OperatorDisjoint) (OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Disjoint)); + Polygon polygon = new Polygon(); + Polyline polyline = new Polyline(); + polygon.startPath(0, 0); + polygon.lineTo(0, 10); + polygon.lineTo(10, 10); + polygon.lineTo(10, 0); + polyline.startPath(-5, 4); + polyline.lineTo(5, -6); + boolean result = op.execute(polyline, polygon, + SpatialReference.create(4326), null); + assertTrue(result); + } + } + + @Test + public void testPolylinePolylineRelate() { + OperatorRelate op = OperatorRelate.local(); + SpatialReference sr = SpatialReference.create(4326); + boolean res; + String scl; + + Polyline polyline1 = new Polyline(); + Polyline polyline2 = new Polyline(); + + polyline1.startPath(0, 0); + polyline1.lineTo(1, 1); + + polyline2.startPath(1, 1); + polyline2.lineTo(2, 0); + + scl = "FF1FT01T2"; + res = op.execute(polyline1, polyline2, sr, scl, null); + assertTrue(res); + + scl = "****TF*T*"; + res = op.execute(polyline1, polyline2, sr, scl, null); + assertTrue(!res); + + scl = "****F****"; + res = op.execute(polyline1, polyline2, sr, scl, null); + assertTrue(!res); + + scl = "**1*0*T**"; + res = op.execute(polyline1, polyline2, sr, scl, null); + assertTrue(res); + + scl = "****1****"; + res = op.execute(polyline1, polyline2, sr, scl, null); + assertTrue(!res); + + scl = "**T*001*T"; + res = op.execute(polyline1, polyline2, sr, scl, null); + assertTrue(res); + + scl = "T********"; + res = op.execute(polyline1, polyline2, sr, scl, null); + assertTrue(!res); + + scl = "F********"; + res = op.execute(polyline1, polyline2, sr, scl, null); + assertTrue(res); + + polyline1.setEmpty(); + polyline2.setEmpty(); + + polyline1.startPath(0, 0); + polyline1.lineTo(1, 0); + + polyline2.startPath(0, 0); + polyline2.lineTo(1, 0); + + scl = "1FFFTFFFT"; + res = op.execute(polyline1, polyline2, sr, scl, null); + assertTrue(res); + + scl = "1*T*T****"; + res = op.execute(polyline1, polyline2, sr, scl, null); + assertTrue(!res); + + scl = "1T**T****"; + res = op.execute(polyline1, polyline2, sr, scl, null); + assertTrue(!res); + + polyline1.setEmpty(); + polyline2.setEmpty(); + + polyline1.startPath(0, 0); + polyline1.lineTo(0.5, 0.5); + polyline1.lineTo(1, 1); + + polyline2.startPath(1, 0); + polyline2.lineTo(0.5, 0.5); + polyline2.lineTo(0, 1); + + scl = "0F1FFTT0T"; + res = op.execute(polyline1, polyline2, sr, scl, null); + assertTrue(res); + + scl = "*T*******"; + res = op.execute(polyline1, polyline2, sr, scl, null); + assertTrue(!res); + + scl = "*F*F*****"; + res = op.execute(polyline1, polyline2, sr, scl, null); + assertTrue(res); + + polyline1.setEmpty(); + polyline2.setEmpty(); + + polyline1.startPath(0, 0); + polyline1.lineTo(1, 0); - polyline2.startPath(1, -1); - polyline2.lineTo(1, 1); + polyline2.startPath(1, -1); + polyline2.lineTo(1, 1); - scl = "FT1TF01TT"; - res = op.execute(polyline1, polyline2, sr, scl, null); - assertTrue(!res); + scl = "FT1TF01TT"; + res = op.execute(polyline1, polyline2, sr, scl, null); + assertTrue(!res); - scl = "***T*****"; - res = op.execute(polyline1, polyline2, sr, scl, null); - assertTrue(res); + scl = "***T*****"; + res = op.execute(polyline1, polyline2, sr, scl, null); + assertTrue(res); - polyline1.setEmpty(); - polyline2.setEmpty(); - - polyline1.startPath(0, 0); - polyline1.lineTo(0, 20); - polyline1.lineTo(20, 20); - polyline1.lineTo(20, 0); - polyline1.lineTo(0, 0); // has no boundary - - polyline2.startPath(3, 3); - polyline2.lineTo(5, 5); - - op.accelerateGeometry(polyline1, sr, Geometry.GeometryAccelerationDegree.enumHot); - - scl = "FF1FFF102"; - res = op.execute(polyline1, polyline2, sr, scl, null); - assertTrue(res); - - polyline1.setEmpty(); - polyline2.setEmpty(); - - polyline1.startPath(4, 0); - polyline1.lineTo(0, 4); - polyline1.lineTo(4, 8); - polyline1.lineTo(8, 4); - - polyline2.startPath(8, 1); - polyline2.lineTo(8, 2); - - op.accelerateGeometry(polyline1, sr, GeometryAccelerationDegree.enumHot); + polyline1.setEmpty(); + polyline2.setEmpty(); + + polyline1.startPath(0, 0); + polyline1.lineTo(0, 20); + polyline1.lineTo(20, 20); + polyline1.lineTo(20, 0); + polyline1.lineTo(0, 0); // has no boundary + + polyline2.startPath(3, 3); + polyline2.lineTo(5, 5); + + op.accelerateGeometry(polyline1, sr, Geometry.GeometryAccelerationDegree.enumHot); + + scl = "FF1FFF102"; + res = op.execute(polyline1, polyline2, sr, scl, null); + assertTrue(res); + + polyline1.setEmpty(); + polyline2.setEmpty(); + + polyline1.startPath(4, 0); + polyline1.lineTo(0, 4); + polyline1.lineTo(4, 8); + polyline1.lineTo(8, 4); + + polyline2.startPath(8, 1); + polyline2.lineTo(8, 2); + + op.accelerateGeometry(polyline1, sr, GeometryAccelerationDegree.enumHot); - scl = "FF1FF0102"; - res = op.execute(polyline1, polyline2, sr, scl, null); - assertTrue(res); + scl = "FF1FF0102"; + res = op.execute(polyline1, polyline2, sr, scl, null); + assertTrue(res); - polyline1.setEmpty(); - polyline2.setEmpty(); - polyline1.startPath(4, 0); - polyline1.lineTo(0, 4); - polyline2.startPath(3, 2); - polyline2.lineTo(3, 2); - assertTrue(polyline2.getBoundary().isEmpty()); - - scl = "******0F*"; - res = op.execute(polyline1, polyline2, sr, scl, null); - assertTrue(res); - - polyline2.lineTo(3, 2); - assertTrue(polyline2.getBoundary().isEmpty()); - - scl = "******0F*"; - res = op.execute(polyline1, polyline2, sr, scl, null); - assertTrue(res); - scl = "******0F*"; - - polyline2.lineTo(3, 2); - assertTrue(polyline2.getBoundary().isEmpty()); - - res = op.execute(polyline1, polyline2, sr, scl, null); - assertTrue(res); - - polyline1.setEmpty(); - polyline2.setEmpty(); - polyline1.startPath(3, 3); - polyline1.lineTo(3, 4); - polyline1.lineTo(3, 3); - polyline2.startPath(1, 1); - polyline2.lineTo(1, 1); - - scl = "FF1FFF0F2"; - res = op.execute(polyline1, polyline2, sr, scl, null); - assertTrue(res); - scl = "FF0FFF1F2"; - res = op.execute(polyline2, polyline1, sr, scl, null); - assertTrue(res); - - polyline1.setEmpty(); - polyline2.setEmpty(); - polyline1.startPath(4, 0); - polyline1.lineTo(0, 4); - polyline2.startPath(2, 2); - polyline2.lineTo(2, 2); - - scl = "0F*******"; - res = op.execute(polyline1, polyline2, sr, scl, null); - assertTrue(res); - - polyline2.lineTo(2, 2); - - scl = "0F*******"; - res = op.execute(polyline1, polyline2, sr, scl, null); - assertTrue(res); - scl = "0F*******"; - res = op.execute(polyline1, polyline2, sr, scl, null); - assertTrue(res); - } - - @Test - public void testPolygonPolylineRelate() { - OperatorRelate op = OperatorRelate.local(); - SpatialReference sr = SpatialReference.create(4326); - boolean res; - String scl; - - Polygon polygon1 = new Polygon(); - Polyline polyline2 = new Polyline(); - - polygon1.startPath(0, 0); - polygon1.lineTo(0, 10); - polygon1.lineTo(10, 10); - polygon1.lineTo(10, 0); - - polyline2.startPath(-10, 0); - polyline2.lineTo(0, 0); - - scl = "FF2F01102"; - res = op.execute(polygon1, polyline2, sr, scl, null); - assertTrue(res); - - scl = "**1*0110*"; - res = op.execute(polygon1, polyline2, sr, scl, null); - assertTrue(!res); - - scl = "T***T****"; - res = op.execute(polygon1, polyline2, sr, scl, null); - assertTrue(!res); - - scl = "FF2FT****"; - res = op.execute(polygon1, polyline2, sr, scl, null); - assertTrue(res); - - polyline2.setEmpty(); - polyline2.startPath(0, 0); - polyline2.lineTo(10, 0); - - scl = "**21*1FF*"; - res = op.execute(polygon1, polyline2, sr, scl, null); - assertTrue(res); - - scl = "F*21*1FF*"; - res = op.execute(polygon1, polyline2, sr, scl, null); - assertTrue(res); - - scl = "0**1*1FF*"; - res = op.execute(polygon1, polyline2, sr, scl, null); - assertTrue(!res); - - scl = "F**1*1TF*"; - res = op.execute(polygon1, polyline2, sr, scl, null); - assertTrue(!res); - - polyline2.setEmpty(); - polyline2.startPath(1, 1); - polyline2.lineTo(5, 5); - - scl = "TT2******"; - res = op.execute(polygon1, polyline2, sr, scl, null); - assertTrue(res); - - scl = "1T2FF1FF*"; - res = op.execute(polygon1, polyline2, sr, scl, null); - assertTrue(res); - - scl = "1T1FF1FF*"; - res = op.execute(polygon1, polyline2, sr, scl, null); - assertTrue(!res); - - polyline2.setEmpty(); - polyline2.startPath(5, 5); - polyline2.lineTo(15, 5); - - scl = "1T20F*T0T"; - res = op.execute(polygon1, polyline2, sr, scl, null); - assertTrue(res); - - polygon1.setEmpty(); - polyline2.setEmpty(); - - polygon1.startPath(2, 0); - polygon1.lineTo(0, 2); - polygon1.lineTo(2, 4); - polygon1.lineTo(4, 2); - - polyline2.startPath(1, 2); - polyline2.lineTo(3, 2); - - op.accelerateGeometry(polygon1, sr, GeometryAccelerationDegree.enumHot); - scl = "TTTFF****"; - res = op.execute(polygon1, polyline2, sr, scl, null); - assertTrue(res); - - polyline2.setEmpty(); - polyline2.startPath(5, 2); - polyline2.lineTo(7, 2); - scl = "FF2FFT***"; - res = op.execute(polygon1, polyline2, sr, scl, null); - assertTrue(res); - - polygon1.setEmpty(); - polyline2.setEmpty(); - polygon1.startPath(0, 0); - polygon1.lineTo(0, 1); - polygon1.lineTo(1, 0); - polyline2.startPath(0, 10); - polyline2.lineTo(0, 9); - polyline2.startPath(10, 0); - polyline2.lineTo(9, 0); - polyline2.startPath(0, -10); - polyline2.lineTo(0, -9); - scl = "**2******"; - res = op.execute(polygon1, polyline2, sr, scl, null); - assertTrue(res); - - polygon1.setEmpty(); - polyline2.setEmpty(); - polygon1.startPath(0, 0); - polygon1.lineTo(0, 1); - polygon1.lineTo(0, 0); - polyline2.startPath(0, 10); - polyline2.lineTo(0, 9); - scl = "**1******"; - res = op.execute(polygon1, polyline2, sr, scl, null); - assertTrue(res); - } - - @Test - public void testPolygonPolygonRelate() { - OperatorRelate op = (OperatorRelate) (OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.Relate)); - SpatialReference sr = SpatialReference.create(4326); - boolean res; - String scl; - - Polygon polygon1 = new Polygon(); - Polygon polygon2 = new Polygon(); - - polygon1.startPath(0, 0); - polygon1.lineTo(0, 10); - polygon1.lineTo(10, 10); - polygon1.lineTo(10, 0); - - polygon2.startPath(15, 0); - polygon2.lineTo(15, 10); - polygon2.lineTo(25, 10); - polygon2.lineTo(25, 0); - - scl = "FFTFFT21T"; - res = op.execute(polygon1, polygon2, sr, scl, null); - assertTrue(res); - - scl = "FFTFFT11T"; - res = op.execute(polygon1, polygon2, sr, scl, null); - assertTrue(!res); - - polygon2.setEmpty(); - polygon2.startPath(5, 0); - polygon2.lineTo(5, 10); - polygon2.lineTo(15, 10); - polygon2.lineTo(15, 0); - - scl = "21TT1121T"; - res = op.execute(polygon1, polygon2, sr, scl, null); - assertTrue(res); - - polygon2.setEmpty(); - polygon2.startPath(1, 1); - polygon2.lineTo(1, 9); - polygon2.lineTo(9, 9); - polygon2.lineTo(9, 1); - - scl = "212FF1FFT"; - res = op.execute(polygon1, polygon2, sr, scl, null); - assertTrue(res); - - polygon1.setEmpty(); - polygon2.setEmpty(); - polygon1.startPath(3, 3); - polygon1.lineTo(3, 4); - polygon1.lineTo(3, 3); - polygon2.startPath(1, 1); - polygon2.lineTo(1, 1); - - scl = "FF1FFF0F2"; - res = op.execute(polygon1, polygon2, sr, scl, null); - assertTrue(res); - scl = "FF0FFF1F2"; - res = op.execute(polygon2, polygon1, sr, scl, null); - assertTrue(res); - - polygon1.setEmpty(); - polygon2.setEmpty(); - polygon1.startPath(0, 0); - polygon1.lineTo(0, 100); - polygon1.lineTo(100, 100); - polygon1.lineTo(100, 0); - polygon2.startPath(50, 50); - polygon2.lineTo(50, 50); - polygon2.lineTo(50, 50); - - op.accelerateGeometry(polygon1, sr, GeometryAccelerationDegree.enumHot); - - scl = "0F2FF1FF2"; - res = op.execute(polygon1, polygon2, sr, scl, null); - assertTrue(res); - - polygon2.lineTo(51, 50); - scl = "1F2FF1FF2"; - res = op.execute(polygon1, polygon2, sr, scl, null); - assertTrue(res); - } - - @Test - public void testMultiPointPointRelate() { - OperatorRelate op = (OperatorRelate) (OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.Relate)); - SpatialReference sr = SpatialReference.create(4326); - boolean res; - String scl; - - MultiPoint m1 = new MultiPoint(); - Point p2 = new Point(); - - m1.add(0, 0); - p2.setXY(0, 0); - - scl = "T*F***F**"; - res = op.execute(m1, p2, sr, scl, null); - assertTrue(res); - - scl = "T*T***F**"; - res = op.execute(m1, p2, sr, scl, null); - assertTrue(!res); - - m1.add(1, 1); - res = op.execute(m1, p2, sr, scl, null); - assertTrue(res); - - m1.setEmpty(); - - m1.add(1, 1); - m1.add(2, 2); - - scl = "FF0FFFTF2"; - res = op.execute(m1, p2, sr, scl, null); - assertTrue(res); - } - - @Test - public void testPointPointRelate() { - OperatorRelate op = (OperatorRelate) (OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.Relate)); - SpatialReference sr = SpatialReference.create(4326); - boolean res; - String scl; - - Point p1 = new Point(); - Point p2 = new Point(); - - p1.setXY(0, 0); - p2.setXY(0, 0); - - scl = "T********"; - res = op.execute(p1, p2, sr, scl, null); - assertTrue(res); - - p1.setXY(0, 0); - p2.setXY(1, 0); - res = op.execute(p1, p2, null, scl, null); - assertTrue(!res); - - p1.setEmpty(); - p2.setEmpty(); - scl = "*********"; - res = op.execute(p1, p2, null, scl, null); - assertTrue(res); - scl = "FFFFFFFFF"; - res = op.execute(p1, p2, null, scl, null); - assertTrue(res); - scl = "FFFFFFFFT"; - res = op.execute(p1, p2, null, scl, null); - assertTrue(!res); - } - - @Test - public void testPolygonMultiPointRelate() { - OperatorRelate op = (OperatorRelate) (OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.Relate)); - SpatialReference sr = SpatialReference.create(4326); - boolean res; - String scl; - - Polygon polygon1 = new Polygon(); - MultiPoint multipoint2 = new MultiPoint(); - - polygon1.startPath(0, 0); - polygon1.lineTo(0, 10); - polygon1.lineTo(10, 10); - polygon1.lineTo(10, 0); - - multipoint2.add(0, 0); - multipoint2.add(5, 5); - - scl = "TFT0F1FFT"; - res = op.execute(polygon1, multipoint2, sr, scl, null); - assertTrue(res); - - scl = "T0FFFFT1T"; // transpose of above - res = op.execute(multipoint2, polygon1, sr, scl, null); - assertTrue(res); - - multipoint2.add(11, 11); - - scl = "TFT0F10FT"; - res = op.execute(polygon1, multipoint2, sr, scl, null); - assertTrue(res); - - multipoint2.add(0, 5); - - scl = "TFT0F10FT"; - res = op.execute(polygon1, multipoint2, sr, scl, null); - assertTrue(res); - - scl = "TFF0F10FT"; - res = op.execute(polygon1, multipoint2, sr, scl, null); - assertTrue(!res); - - polygon1.setEmpty(); - multipoint2.setEmpty(); - - polygon1.startPath(0, 0); - polygon1.lineTo(0, 20); - polygon1.lineTo(20, 20); - polygon1.lineTo(20, 0); - - multipoint2.add(3, 3); - multipoint2.add(5, 5); - - op.accelerateGeometry(polygon1, sr, - Geometry.GeometryAccelerationDegree.enumHot); - - scl = "TF2FF****"; - res = op.execute(polygon1, multipoint2, sr, scl, null); - assertTrue(res); - - polygon1.setEmpty(); - multipoint2.setEmpty(); - - polygon1.startPath(4, 0); - polygon1.lineTo(0, 4); - polygon1.lineTo(4, 8); - polygon1.lineTo(8, 4); - - multipoint2.add(8, 1); - multipoint2.add(8, 2); - - op.accelerateGeometry(polygon1, sr, - Geometry.GeometryAccelerationDegree.enumHot); - - scl = "FF2FF10F2"; - res = op.execute(polygon1, multipoint2, sr, scl, null); - assertTrue(res); - } - - @Test - public void testPolygonPointRelate() { - OperatorRelate op = (OperatorRelate) (OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.Relate)); - SpatialReference sr = SpatialReference.create(4326); - boolean res; - String scl; - - Polygon polygon = new Polygon(); - Point point = new Point(); - - polygon.startPath(0, 0); - polygon.lineTo(0, 10); - polygon.lineTo(10, 10); - polygon.lineTo(10, 0); - - point.setXY(0, 0); - - scl = "FF20FTFFT"; - res = op.execute(polygon, point, sr, scl, null); - assertTrue(res); - - polygon.setEmpty(); - polygon.startPath(0, 0); - polygon.lineTo(0, 0); - polygon.lineTo(0, 0); - scl = "0FFFFFFF2"; - res = op.execute(polygon, point, sr, scl, null); - assertTrue(res); + polyline1.setEmpty(); + polyline2.setEmpty(); + polyline1.startPath(4, 0); + polyline1.lineTo(0, 4); + polyline2.startPath(3, 2); + polyline2.lineTo(3, 2); + assertTrue(polyline2.getBoundary().isEmpty()); + + scl = "******0F*"; + res = op.execute(polyline1, polyline2, sr, scl, null); + assertTrue(res); + + polyline2.lineTo(3, 2); + assertTrue(polyline2.getBoundary().isEmpty()); + + scl = "******0F*"; + res = op.execute(polyline1, polyline2, sr, scl, null); + assertTrue(res); + scl = "******0F*"; + + polyline2.lineTo(3, 2); + assertTrue(polyline2.getBoundary().isEmpty()); + + res = op.execute(polyline1, polyline2, sr, scl, null); + assertTrue(res); + + polyline1.setEmpty(); + polyline2.setEmpty(); + polyline1.startPath(3, 3); + polyline1.lineTo(3, 4); + polyline1.lineTo(3, 3); + polyline2.startPath(1, 1); + polyline2.lineTo(1, 1); + + scl = "FF1FFF0F2"; + res = op.execute(polyline1, polyline2, sr, scl, null); + assertTrue(res); + scl = "FF0FFF1F2"; + res = op.execute(polyline2, polyline1, sr, scl, null); + assertTrue(res); + + polyline1.setEmpty(); + polyline2.setEmpty(); + polyline1.startPath(4, 0); + polyline1.lineTo(0, 4); + polyline2.startPath(2, 2); + polyline2.lineTo(2, 2); + + scl = "0F*******"; + res = op.execute(polyline1, polyline2, sr, scl, null); + assertTrue(res); + + polyline2.lineTo(2, 2); + + scl = "0F*******"; + res = op.execute(polyline1, polyline2, sr, scl, null); + assertTrue(res); + scl = "0F*******"; + res = op.execute(polyline1, polyline2, sr, scl, null); + assertTrue(res); + } + + @Test + public void testPolygonPolylineRelate() { + OperatorRelate op = OperatorRelate.local(); + SpatialReference sr = SpatialReference.create(4326); + boolean res; + String scl; + + Polygon polygon1 = new Polygon(); + Polyline polyline2 = new Polyline(); + + polygon1.startPath(0, 0); + polygon1.lineTo(0, 10); + polygon1.lineTo(10, 10); + polygon1.lineTo(10, 0); + + polyline2.startPath(-10, 0); + polyline2.lineTo(0, 0); + + scl = "FF2F01102"; + res = op.execute(polygon1, polyline2, sr, scl, null); + assertTrue(res); + + scl = "**1*0110*"; + res = op.execute(polygon1, polyline2, sr, scl, null); + assertTrue(!res); + + scl = "T***T****"; + res = op.execute(polygon1, polyline2, sr, scl, null); + assertTrue(!res); + + scl = "FF2FT****"; + res = op.execute(polygon1, polyline2, sr, scl, null); + assertTrue(res); + + polyline2.setEmpty(); + polyline2.startPath(0, 0); + polyline2.lineTo(10, 0); + + scl = "**21*1FF*"; + res = op.execute(polygon1, polyline2, sr, scl, null); + assertTrue(res); + + scl = "F*21*1FF*"; + res = op.execute(polygon1, polyline2, sr, scl, null); + assertTrue(res); + + scl = "0**1*1FF*"; + res = op.execute(polygon1, polyline2, sr, scl, null); + assertTrue(!res); + + scl = "F**1*1TF*"; + res = op.execute(polygon1, polyline2, sr, scl, null); + assertTrue(!res); + + polyline2.setEmpty(); + polyline2.startPath(1, 1); + polyline2.lineTo(5, 5); + + scl = "TT2******"; + res = op.execute(polygon1, polyline2, sr, scl, null); + assertTrue(res); + + scl = "1T2FF1FF*"; + res = op.execute(polygon1, polyline2, sr, scl, null); + assertTrue(res); + + scl = "1T1FF1FF*"; + res = op.execute(polygon1, polyline2, sr, scl, null); + assertTrue(!res); + + polyline2.setEmpty(); + polyline2.startPath(5, 5); + polyline2.lineTo(15, 5); + + scl = "1T20F*T0T"; + res = op.execute(polygon1, polyline2, sr, scl, null); + assertTrue(res); + + polygon1.setEmpty(); + polyline2.setEmpty(); + + polygon1.startPath(2, 0); + polygon1.lineTo(0, 2); + polygon1.lineTo(2, 4); + polygon1.lineTo(4, 2); + + polyline2.startPath(1, 2); + polyline2.lineTo(3, 2); + + op.accelerateGeometry(polygon1, sr, GeometryAccelerationDegree.enumHot); + scl = "TTTFF****"; + res = op.execute(polygon1, polyline2, sr, scl, null); + assertTrue(res); + + polyline2.setEmpty(); + polyline2.startPath(5, 2); + polyline2.lineTo(7, 2); + scl = "FF2FFT***"; + res = op.execute(polygon1, polyline2, sr, scl, null); + assertTrue(res); + + polygon1.setEmpty(); + polyline2.setEmpty(); + polygon1.startPath(0, 0); + polygon1.lineTo(0, 1); + polygon1.lineTo(1, 0); + polyline2.startPath(0, 10); + polyline2.lineTo(0, 9); + polyline2.startPath(10, 0); + polyline2.lineTo(9, 0); + polyline2.startPath(0, -10); + polyline2.lineTo(0, -9); + scl = "**2******"; + res = op.execute(polygon1, polyline2, sr, scl, null); + assertTrue(res); + + polygon1.setEmpty(); + polyline2.setEmpty(); + polygon1.startPath(0, 0); + polygon1.lineTo(0, 1); + polygon1.lineTo(0, 0); + polyline2.startPath(0, 10); + polyline2.lineTo(0, 9); + scl = "**1******"; + res = op.execute(polygon1, polyline2, sr, scl, null); + assertTrue(res); + } + + @Test + public void testPolygonPolygonRelate() { + OperatorRelate op = (OperatorRelate) (OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Relate)); + SpatialReference sr = SpatialReference.create(4326); + boolean res; + String scl; + + Polygon polygon1 = new Polygon(); + Polygon polygon2 = new Polygon(); + + polygon1.startPath(0, 0); + polygon1.lineTo(0, 10); + polygon1.lineTo(10, 10); + polygon1.lineTo(10, 0); + + polygon2.startPath(15, 0); + polygon2.lineTo(15, 10); + polygon2.lineTo(25, 10); + polygon2.lineTo(25, 0); + + scl = "FFTFFT21T"; + res = op.execute(polygon1, polygon2, sr, scl, null); + assertTrue(res); + + scl = "FFTFFT11T"; + res = op.execute(polygon1, polygon2, sr, scl, null); + assertTrue(!res); + + polygon2.setEmpty(); + polygon2.startPath(5, 0); + polygon2.lineTo(5, 10); + polygon2.lineTo(15, 10); + polygon2.lineTo(15, 0); + + scl = "21TT1121T"; + res = op.execute(polygon1, polygon2, sr, scl, null); + assertTrue(res); + + polygon2.setEmpty(); + polygon2.startPath(1, 1); + polygon2.lineTo(1, 9); + polygon2.lineTo(9, 9); + polygon2.lineTo(9, 1); + + scl = "212FF1FFT"; + res = op.execute(polygon1, polygon2, sr, scl, null); + assertTrue(res); + + polygon1.setEmpty(); + polygon2.setEmpty(); + polygon1.startPath(3, 3); + polygon1.lineTo(3, 4); + polygon1.lineTo(3, 3); + polygon2.startPath(1, 1); + polygon2.lineTo(1, 1); + + scl = "FF1FFF0F2"; + res = op.execute(polygon1, polygon2, sr, scl, null); + assertTrue(res); + scl = "FF0FFF1F2"; + res = op.execute(polygon2, polygon1, sr, scl, null); + assertTrue(res); + + polygon1.setEmpty(); + polygon2.setEmpty(); + polygon1.startPath(0, 0); + polygon1.lineTo(0, 100); + polygon1.lineTo(100, 100); + polygon1.lineTo(100, 0); + polygon2.startPath(50, 50); + polygon2.lineTo(50, 50); + polygon2.lineTo(50, 50); + + op.accelerateGeometry(polygon1, sr, GeometryAccelerationDegree.enumHot); + + scl = "0F2FF1FF2"; + res = op.execute(polygon1, polygon2, sr, scl, null); + assertTrue(res); + + polygon2.lineTo(51, 50); + scl = "1F2FF1FF2"; + res = op.execute(polygon1, polygon2, sr, scl, null); + assertTrue(res); + } + + @Test + public void testMultiPointPointRelate() { + OperatorRelate op = (OperatorRelate) (OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Relate)); + SpatialReference sr = SpatialReference.create(4326); + boolean res; + String scl; + + MultiPoint m1 = new MultiPoint(); + Point p2 = new Point(); + + m1.add(0, 0); + p2.setXY(0, 0); + + scl = "T*F***F**"; + res = op.execute(m1, p2, sr, scl, null); + assertTrue(res); + + scl = "T*T***F**"; + res = op.execute(m1, p2, sr, scl, null); + assertTrue(!res); + + m1.add(1, 1); + res = op.execute(m1, p2, sr, scl, null); + assertTrue(res); + + m1.setEmpty(); + + m1.add(1, 1); + m1.add(2, 2); + + scl = "FF0FFFTF2"; + res = op.execute(m1, p2, sr, scl, null); + assertTrue(res); + } + + @Test + public void testPointPointRelate() { + OperatorRelate op = (OperatorRelate) (OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Relate)); + SpatialReference sr = SpatialReference.create(4326); + boolean res; + String scl; + + Point p1 = new Point(); + Point p2 = new Point(); + + p1.setXY(0, 0); + p2.setXY(0, 0); + + scl = "T********"; + res = op.execute(p1, p2, sr, scl, null); + assertTrue(res); + + p1.setXY(0, 0); + p2.setXY(1, 0); + res = op.execute(p1, p2, null, scl, null); + assertTrue(!res); + + p1.setEmpty(); + p2.setEmpty(); + scl = "*********"; + res = op.execute(p1, p2, null, scl, null); + assertTrue(res); + scl = "FFFFFFFFF"; + res = op.execute(p1, p2, null, scl, null); + assertTrue(res); + scl = "FFFFFFFFT"; + res = op.execute(p1, p2, null, scl, null); + assertTrue(!res); + } + + @Test + public void testPolygonMultiPointRelate() { + OperatorRelate op = (OperatorRelate) (OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Relate)); + SpatialReference sr = SpatialReference.create(4326); + boolean res; + String scl; + + Polygon polygon1 = new Polygon(); + MultiPoint multipoint2 = new MultiPoint(); + + polygon1.startPath(0, 0); + polygon1.lineTo(0, 10); + polygon1.lineTo(10, 10); + polygon1.lineTo(10, 0); + + multipoint2.add(0, 0); + multipoint2.add(5, 5); + + scl = "TFT0F1FFT"; + res = op.execute(polygon1, multipoint2, sr, scl, null); + assertTrue(res); + + scl = "T0FFFFT1T"; // transpose of above + res = op.execute(multipoint2, polygon1, sr, scl, null); + assertTrue(res); + + multipoint2.add(11, 11); + + scl = "TFT0F10FT"; + res = op.execute(polygon1, multipoint2, sr, scl, null); + assertTrue(res); + + multipoint2.add(0, 5); + + scl = "TFT0F10FT"; + res = op.execute(polygon1, multipoint2, sr, scl, null); + assertTrue(res); + + scl = "TFF0F10FT"; + res = op.execute(polygon1, multipoint2, sr, scl, null); + assertTrue(!res); + + polygon1.setEmpty(); + multipoint2.setEmpty(); + + polygon1.startPath(0, 0); + polygon1.lineTo(0, 20); + polygon1.lineTo(20, 20); + polygon1.lineTo(20, 0); + + multipoint2.add(3, 3); + multipoint2.add(5, 5); + + op.accelerateGeometry(polygon1, sr, + Geometry.GeometryAccelerationDegree.enumHot); + + scl = "TF2FF****"; + res = op.execute(polygon1, multipoint2, sr, scl, null); + assertTrue(res); + + polygon1.setEmpty(); + multipoint2.setEmpty(); + + polygon1.startPath(4, 0); + polygon1.lineTo(0, 4); + polygon1.lineTo(4, 8); + polygon1.lineTo(8, 4); + + multipoint2.add(8, 1); + multipoint2.add(8, 2); + + op.accelerateGeometry(polygon1, sr, + Geometry.GeometryAccelerationDegree.enumHot); + + scl = "FF2FF10F2"; + res = op.execute(polygon1, multipoint2, sr, scl, null); + assertTrue(res); + } + + @Test + public void testPolygonPointRelate() { + OperatorRelate op = (OperatorRelate) (OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Relate)); + SpatialReference sr = SpatialReference.create(4326); + boolean res; + String scl; + + Polygon polygon = new Polygon(); + Point point = new Point(); + + polygon.startPath(0, 0); + polygon.lineTo(0, 10); + polygon.lineTo(10, 10); + polygon.lineTo(10, 0); + + point.setXY(0, 0); + + scl = "FF20FTFFT"; + res = op.execute(polygon, point, sr, scl, null); + assertTrue(res); + + polygon.setEmpty(); + polygon.startPath(0, 0); + polygon.lineTo(0, 0); + polygon.lineTo(0, 0); + scl = "0FFFFFFF2"; + res = op.execute(polygon, point, sr, scl, null); + assertTrue(res); - polygon.setEmpty(); - polygon.startPath(0, 0); - polygon.lineTo(0, 1); - polygon.lineTo(0, 0); - scl = "0F1FFFFF2"; - res = op.execute(polygon, point, sr, scl, null); - assertTrue(res); + polygon.setEmpty(); + polygon.startPath(0, 0); + polygon.lineTo(0, 1); + polygon.lineTo(0, 0); + scl = "0F1FFFFF2"; + res = op.execute(polygon, point, sr, scl, null); + assertTrue(res); - point.setXY(-1, 0); - - scl = "FF1FFF0F2"; - res = op.execute(polygon, point, sr, scl, null); - assertTrue(res); + point.setXY(-1, 0); + + scl = "FF1FFF0F2"; + res = op.execute(polygon, point, sr, scl, null); + assertTrue(res); - polygon.setEmpty(); - polygon.startPath(0, 0); - polygon.lineTo(0, 10); - polygon.lineTo(0, 0); - scl = "FF1FFFTFT"; - res = op.execute(polygon, point, sr, scl, null); - assertTrue(res); + polygon.setEmpty(); + polygon.startPath(0, 0); + polygon.lineTo(0, 10); + polygon.lineTo(0, 0); + scl = "FF1FFFTFT"; + res = op.execute(polygon, point, sr, scl, null); + assertTrue(res); - polygon.setEmpty(); - polygon.startPath(0, 0); - polygon.lineTo(0, 0); - polygon.lineTo(0, 0); - scl = "FF0FFF0F2"; - res = op.execute(polygon, point, sr, scl, null); - assertTrue(res); - } - - @Test - public void testPolylineMultiPointRelate() { - OperatorRelate op = OperatorRelate.local(); - SpatialReference sr = SpatialReference.create(4326); - boolean res; - String scl; - - Polyline polyline1 = new Polyline(); - MultiPoint multipoint2 = new MultiPoint(); - - polyline1.startPath(0, 0); - polyline1.lineTo(10, 0); - - multipoint2.add(0, 0); - multipoint2.add(5, 5); - - scl = "FF10F00F2"; - res = op.execute(polyline1, multipoint2, sr, scl, null); - assertTrue(res); + polygon.setEmpty(); + polygon.startPath(0, 0); + polygon.lineTo(0, 0); + polygon.lineTo(0, 0); + scl = "FF0FFF0F2"; + res = op.execute(polygon, point, sr, scl, null); + assertTrue(res); + } + + @Test + public void testPolylineMultiPointRelate() { + OperatorRelate op = OperatorRelate.local(); + SpatialReference sr = SpatialReference.create(4326); + boolean res; + String scl; + + Polyline polyline1 = new Polyline(); + MultiPoint multipoint2 = new MultiPoint(); + + polyline1.startPath(0, 0); + polyline1.lineTo(10, 0); + + multipoint2.add(0, 0); + multipoint2.add(5, 5); + + scl = "FF10F00F2"; + res = op.execute(polyline1, multipoint2, sr, scl, null); + assertTrue(res); - multipoint2.add(5, 0); - - scl = "0F10F00F2"; - res = op.execute(polyline1, multipoint2, sr, scl, null); - assertTrue(res); + multipoint2.add(5, 0); + + scl = "0F10F00F2"; + res = op.execute(polyline1, multipoint2, sr, scl, null); + assertTrue(res); - scl = "0F11F00F2"; - res = op.execute(polyline1, multipoint2, sr, scl, null); - assertTrue(!res); + scl = "0F11F00F2"; + res = op.execute(polyline1, multipoint2, sr, scl, null); + assertTrue(!res); - polyline1.setEmpty(); - multipoint2.setEmpty(); + polyline1.setEmpty(); + multipoint2.setEmpty(); - polyline1.startPath(4, 0); - polyline1.lineTo(0, 4); - polyline1.lineTo(4, 8); - polyline1.lineTo(8, 4); - polyline1.lineTo(4, 0); // has no boundary + polyline1.startPath(4, 0); + polyline1.lineTo(0, 4); + polyline1.lineTo(4, 8); + polyline1.lineTo(8, 4); + polyline1.lineTo(4, 0); // has no boundary - multipoint2.add(8, 1); - multipoint2.add(8, 2); + multipoint2.add(8, 1); + multipoint2.add(8, 2); - op.accelerateGeometry(polyline1, sr, GeometryAccelerationDegree.enumHot); + op.accelerateGeometry(polyline1, sr, GeometryAccelerationDegree.enumHot); - scl = "FF1FFF0F2"; - res = op.execute(polyline1, multipoint2, sr, scl, null); - assertTrue(res); + scl = "FF1FFF0F2"; + res = op.execute(polyline1, multipoint2, sr, scl, null); + assertTrue(res); - polyline1.setEmpty(); - multipoint2.setEmpty(); + polyline1.setEmpty(); + multipoint2.setEmpty(); - polyline1.startPath(4, 0); - polyline1.lineTo(4, 0); + polyline1.startPath(4, 0); + polyline1.lineTo(4, 0); - multipoint2.add(8, 1); - multipoint2.add(8, 2); + multipoint2.add(8, 1); + multipoint2.add(8, 2); - scl = "FF0FFF0F2"; - res = op.execute(polyline1, multipoint2, sr, scl, null); - assertTrue(res); + scl = "FF0FFF0F2"; + res = op.execute(polyline1, multipoint2, sr, scl, null); + assertTrue(res); - multipoint2.add(-2, 0); - res = op.execute(polyline1, multipoint2, sr, scl, null); - assertTrue(res); + multipoint2.add(-2, 0); + res = op.execute(polyline1, multipoint2, sr, scl, null); + assertTrue(res); - op.accelerateGeometry(polyline1, sr, GeometryAccelerationDegree.enumHot); - res = op.execute(polyline1, multipoint2, sr, scl, null); - assertTrue(res); + op.accelerateGeometry(polyline1, sr, GeometryAccelerationDegree.enumHot); + res = op.execute(polyline1, multipoint2, sr, scl, null); + assertTrue(res); - polyline1.setEmpty(); - multipoint2.setEmpty(); + polyline1.setEmpty(); + multipoint2.setEmpty(); - polyline1.startPath(10, 10); - polyline1.lineTo(10, 10); - multipoint2.add(10, 10); + polyline1.startPath(10, 10); + polyline1.lineTo(10, 10); + multipoint2.add(10, 10); - scl = "0FFFFFFF2"; - res = op.execute(polyline1, multipoint2, sr, scl, null); - assertTrue(res); + scl = "0FFFFFFF2"; + res = op.execute(polyline1, multipoint2, sr, scl, null); + assertTrue(res); - polyline1.startPath(12, 12); - polyline1.lineTo(12, 12); + polyline1.startPath(12, 12); + polyline1.lineTo(12, 12); - scl = "0F0FFFFF2"; - res = op.execute(polyline1, multipoint2, sr, scl, null); - assertTrue(res); + scl = "0F0FFFFF2"; + res = op.execute(polyline1, multipoint2, sr, scl, null); + assertTrue(res); - polyline1.setEmpty(); - multipoint2.setEmpty(); + polyline1.setEmpty(); + multipoint2.setEmpty(); - polyline1.startPath(10, 10); - polyline1.lineTo(10, 10); - multipoint2.add(0, 0); + polyline1.startPath(10, 10); + polyline1.lineTo(10, 10); + multipoint2.add(0, 0); - scl = "FF0FFF0F2"; - res = op.execute(polyline1, multipoint2, sr, scl, null); - assertTrue(res); - } + scl = "FF0FFF0F2"; + res = op.execute(polyline1, multipoint2, sr, scl, null); + assertTrue(res); + } - @Test - public void testMultiPointMultipointRelate() { - OperatorRelate op = (OperatorRelate) (OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.Relate)); - SpatialReference sr = SpatialReference.create(4326); - boolean res; - String scl; + @Test + public void testMultiPointMultipointRelate() { + OperatorRelate op = (OperatorRelate) (OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Relate)); + SpatialReference sr = SpatialReference.create(4326); + boolean res; + String scl; - MultiPoint multipoint1 = new MultiPoint(); - MultiPoint multipoint2 = new MultiPoint(); + MultiPoint multipoint1 = new MultiPoint(); + MultiPoint multipoint2 = new MultiPoint(); - multipoint1.add(0, 0); + multipoint1.add(0, 0); - multipoint2.add(0, 0); + multipoint2.add(0, 0); - scl = "TFFFFFFF2"; - res = op.execute(multipoint1, multipoint2, sr, scl, null); - assertTrue(res); + scl = "TFFFFFFF2"; + res = op.execute(multipoint1, multipoint2, sr, scl, null); + assertTrue(res); - multipoint2.add(5, 5); + multipoint2.add(5, 5); - scl = "TFFFFFTF2"; - res = op.execute(multipoint1, multipoint2, sr, scl, null); - assertTrue(res); + scl = "TFFFFFTF2"; + res = op.execute(multipoint1, multipoint2, sr, scl, null); + assertTrue(res); - multipoint1.add(-5, 0); + multipoint1.add(-5, 0); - scl = "0FTFFFTF2"; - res = op.execute(multipoint1, multipoint2, sr, scl, null); - assertTrue(res); + scl = "0FTFFFTF2"; + res = op.execute(multipoint1, multipoint2, sr, scl, null); + assertTrue(res); - res = GeometryEngine.relate(multipoint1, multipoint2, sr, scl); - assertTrue(res); + res = GeometryEngine.relate(multipoint1, multipoint2, sr, scl); + assertTrue(res); - multipoint1.setEmpty(); - multipoint2.setEmpty(); + multipoint1.setEmpty(); + multipoint2.setEmpty(); - multipoint1.add(0, 0); - multipoint2.add(1, 1); + multipoint1.add(0, 0); + multipoint2.add(1, 1); - scl = "FFTFFF0FT"; - res = op.execute(multipoint1, multipoint2, sr, scl, null); - assertTrue(res); - } + scl = "FFTFFF0FT"; + res = op.execute(multipoint1, multipoint2, sr, scl, null); + assertTrue(res); + } - @Test - public void testPolylinePointRelate() { - OperatorRelate op = OperatorRelate.local(); - SpatialReference sr = SpatialReference.create(4326); - boolean res; - String scl; + @Test + public void testPolylinePointRelate() { + OperatorRelate op = OperatorRelate.local(); + SpatialReference sr = SpatialReference.create(4326); + boolean res; + String scl; - Polyline polyline = new Polyline(); - Point point = new Point(); - - polyline.startPath(0, 2); - polyline.lineTo(0, 4); - - point.setXY(0, 3); - - scl = "0F1FF0FF2"; - res = op.execute(polyline, point, sr, scl, null); - assertTrue(res); - - point.setXY(1, 3); - - scl = "FF1FF00F2"; - res = op.execute(polyline, point, sr, scl, null); - assertTrue(res); - - polyline.lineTo(4, 4); - polyline.lineTo(4, 2); - polyline.lineTo(0, 2); // no bounadry - point.setXY(0, 3); - - scl = "0F1FFFFF2"; - res = op.execute(polyline, point, sr, scl, null); - assertTrue(res); - - scl = "0F1FFFFF2"; - res = op.execute(polyline, point, sr, scl, null); - assertTrue(res); - - point.setXY(1, 3); - - scl = "FF1FFF0F2"; - res = op.execute(polyline, point, sr, scl, null); - assertTrue(res); - - point.setXY(10, 10); - - scl = "FF1FFF0F2"; - res = op.execute(polyline, point, sr, scl, null); - assertTrue(res); - - polyline.setEmpty(); - point.setEmpty(); - - polyline.startPath(10, 10); - polyline.lineTo(10, 10); - point.setXY(10, 10); - - scl = "0FFFFFFF2"; - res = op.execute(polyline, point, sr, scl, null); - assertTrue(res); - - polyline.startPath(12, 12); - polyline.lineTo(12, 12); - - scl = "0F0FFFFF2"; - res = op.execute(polyline, point, sr, scl, null); - assertTrue(res); - - polyline.setEmpty(); - point.setEmpty(); - - polyline.startPath(10, 10); - polyline.lineTo(10, 10); - point.setXY(0, 0); - - scl = "FF0FFF0F2"; - res = op.execute(polyline, point, sr, scl, null); - assertTrue(res); - } - - @Test - public void testCrosses_github_issue_40() { - // Issue 40: Acceleration without a spatial reference changes the result - // of relation operators - Geometry geom1 = OperatorImportFromWkt.local().execute(0, - Geometry.Type.Unknown, "LINESTRING (2 0, 2 3)", null); - Geometry geom2 = OperatorImportFromWkt.local().execute(0, - Geometry.Type.Unknown, "POLYGON ((1 1, 4 1, 4 4, 1 4, 1 1))", - null); - boolean answer1 = OperatorCrosses.local().execute(geom1, geom2, null, - null); - assertTrue(answer1); - OperatorCrosses.local().accelerateGeometry(geom1, null, - GeometryAccelerationDegree.enumHot); - boolean answer2 = OperatorCrosses.local().execute(geom1, geom2, null, - null); - assertTrue(answer2); - } - - @Test - public void testDisjointCrash() { - Polygon g1 = new Polygon(); - g1.addEnvelope(Envelope2D.construct(0, 0, 10, 10), false); - Polygon g2 = new Polygon(); - g2.addEnvelope(Envelope2D.construct(10, 1, 21, 21), false); - g1 = (Polygon) OperatorDensifyByLength.local().execute(g1, 0.1, null); - OperatorDisjoint.local().accelerateGeometry(g1, SpatialReference.create(4267), GeometryAccelerationDegree.enumHot); - boolean res = OperatorDisjoint.local().execute(g1, g2, SpatialReference.create(4267), null); - assertTrue(!res); - } - - @Test - public void testDisjointFail() { - MapGeometry geometry1 = OperatorImportFromJson.local().execute(Geometry.Type.Unknown, "{\"paths\":[[[3,3],[3,3]]],\"spatialReference\":{\"wkid\":4326}}"); - MapGeometry geometry2 = OperatorImportFromJson.local().execute(Geometry.Type.Unknown, "{\"rings\":[[[2,2],[2,4],[4,4],[4,2],[2,2]]],\"spatialReference\":{\"wkid\":4326}}"); - OperatorDisjoint.local().accelerateGeometry(geometry1.getGeometry(), geometry1.getSpatialReference(), GeometryAccelerationDegree.enumMedium); - boolean res = OperatorDisjoint.local().execute(geometry1.getGeometry(), geometry2.getGeometry(), geometry1.getSpatialReference(), null); - assertTrue(!res); - } + Polyline polyline = new Polyline(); + Point point = new Point(); + + polyline.startPath(0, 2); + polyline.lineTo(0, 4); + + point.setXY(0, 3); + + scl = "0F1FF0FF2"; + res = op.execute(polyline, point, sr, scl, null); + assertTrue(res); + + point.setXY(1, 3); + + scl = "FF1FF00F2"; + res = op.execute(polyline, point, sr, scl, null); + assertTrue(res); + + polyline.lineTo(4, 4); + polyline.lineTo(4, 2); + polyline.lineTo(0, 2); // no bounadry + point.setXY(0, 3); + + scl = "0F1FFFFF2"; + res = op.execute(polyline, point, sr, scl, null); + assertTrue(res); + + scl = "0F1FFFFF2"; + res = op.execute(polyline, point, sr, scl, null); + assertTrue(res); + + point.setXY(1, 3); + + scl = "FF1FFF0F2"; + res = op.execute(polyline, point, sr, scl, null); + assertTrue(res); + + point.setXY(10, 10); + + scl = "FF1FFF0F2"; + res = op.execute(polyline, point, sr, scl, null); + assertTrue(res); + + polyline.setEmpty(); + point.setEmpty(); + + polyline.startPath(10, 10); + polyline.lineTo(10, 10); + point.setXY(10, 10); + + scl = "0FFFFFFF2"; + res = op.execute(polyline, point, sr, scl, null); + assertTrue(res); + + polyline.startPath(12, 12); + polyline.lineTo(12, 12); + + scl = "0F0FFFFF2"; + res = op.execute(polyline, point, sr, scl, null); + assertTrue(res); + + polyline.setEmpty(); + point.setEmpty(); + + polyline.startPath(10, 10); + polyline.lineTo(10, 10); + point.setXY(0, 0); + + scl = "FF0FFF0F2"; + res = op.execute(polyline, point, sr, scl, null); + assertTrue(res); + } + + @Test + public void testCrosses_github_issue_40() { + // Issue 40: Acceleration without a spatial reference changes the result + // of relation operators + Geometry geom1 = OperatorImportFromWkt.local().execute(0, + Geometry.Type.Unknown, "LINESTRING (2 0, 2 3)", null); + Geometry geom2 = OperatorImportFromWkt.local().execute(0, + Geometry.Type.Unknown, "POLYGON ((1 1, 4 1, 4 4, 1 4, 1 1))", + null); + boolean answer1 = OperatorCrosses.local().execute(geom1, geom2, null, + null); + assertTrue(answer1); + OperatorCrosses.local().accelerateGeometry(geom1, null, + GeometryAccelerationDegree.enumHot); + boolean answer2 = OperatorCrosses.local().execute(geom1, geom2, null, + null); + assertTrue(answer2); + } + + @Test + public void testDisjointCrash() { + Polygon g1 = new Polygon(); + g1.addEnvelope(Envelope2D.construct(0, 0, 10, 10), false); + Polygon g2 = new Polygon(); + g2.addEnvelope(Envelope2D.construct(10, 1, 21, 21), false); + g1 = (Polygon) OperatorDensifyByLength.local().execute(g1, 0.1, null); + OperatorDisjoint.local().accelerateGeometry(g1, SpatialReference.create(4267), GeometryAccelerationDegree.enumHot); + boolean res = OperatorDisjoint.local().execute(g1, g2, SpatialReference.create(4267), null); + assertTrue(!res); + } + + @Test + public void testDisjointFail() { + MapGeometry geometry1 = OperatorImportFromJson.local().execute(Geometry.Type.Unknown, "{\"paths\":[[[3,3],[3,3]]],\"spatialReference\":{\"wkid\":4326}}"); + MapGeometry geometry2 = OperatorImportFromJson.local().execute(Geometry.Type.Unknown, "{\"rings\":[[[2,2],[2,4],[4,4],[4,2],[2,2]]],\"spatialReference\":{\"wkid\":4326}}"); + OperatorDisjoint.local().accelerateGeometry(geometry1.getGeometry(), geometry1.getSpatialReference(), GeometryAccelerationDegree.enumMedium); + boolean res = OperatorDisjoint.local().execute(geometry1.getGeometry(), geometry2.getGeometry(), geometry1.getSpatialReference(), null); + assertTrue(!res); + } } diff --git a/src/test/java/com/esri/core/geometry/TestSerialization.java b/src/test/java/com/esri/core/geometry/TestSerialization.java index 80a673f8..0baf9e84 100644 --- a/src/test/java/com/esri/core/geometry/TestSerialization.java +++ b/src/test/java/com/esri/core/geometry/TestSerialization.java @@ -30,346 +30,346 @@ import java.io.*; public class TestSerialization extends TestCase { - @Override - protected void setUp() throws Exception { - super.setUp(); - } - - @Override - protected void tearDown() throws Exception { - super.tearDown(); - } - - @Test - public void testSerializePoint() { - try { - ByteArrayOutputStream streamOut = new ByteArrayOutputStream(); - ObjectOutputStream oo = new ObjectOutputStream(streamOut); - Point pt = new Point(10, 40); - oo.writeObject(pt); - ByteArrayInputStream streamIn = new ByteArrayInputStream( - streamOut.toByteArray()); - ObjectInputStream ii = new ObjectInputStream(streamIn); - Point ptRes = (Point) ii.readObject(); - assertTrue(ptRes.equals(pt)); - } catch (Exception ex) { - fail("Point serialization failure"); - - } - - //try - //{ - //FileOutputStream streamOut = new FileOutputStream("c:/temp/savedPoint1.txt"); - //ObjectOutputStream oo = new ObjectOutputStream(streamOut); - //Point pt = new Point(10, 40, 2); - //oo.writeObject(pt); - //} - //catch(Exception ex) - //{ - //fail("Point serialization failure"); - //} - - try { - InputStream s = TestSerialization.class - .getResourceAsStream("savedPoint.txt"); - ObjectInputStream ii = new ObjectInputStream(s); - Point ptRes = (Point) ii.readObject(); - assertTrue(ptRes.getX() == 10 && ptRes.getY() == 40); - } catch (Exception ex) { - fail("Point serialization failure"); - } - - try { - InputStream s = TestSerialization.class - .getResourceAsStream("savedPoint1.txt"); - ObjectInputStream ii = new ObjectInputStream(s); - Point ptRes = (Point) ii.readObject(); - assertTrue(ptRes.getX() == 10 && ptRes.getY() == 40 && ptRes.getZ() == 2); - } catch (Exception ex) { - fail("Point serialization failure"); - } - - } - - @Test - public void testSerializePolygon() { - try { - ByteArrayOutputStream streamOut = new ByteArrayOutputStream(); - ObjectOutputStream oo = new ObjectOutputStream(streamOut); - Polygon pt = new Polygon(); - pt.startPath(10, 10); - pt.lineTo(100, 100); - pt.lineTo(200, 100); - oo.writeObject(pt); - ByteArrayInputStream streamIn = new ByteArrayInputStream( - streamOut.toByteArray()); - ObjectInputStream ii = new ObjectInputStream(streamIn); - Polygon ptRes = (Polygon) ii.readObject(); - assertTrue(ptRes.equals(pt)); - } catch (Exception ex) { - fail("Polygon serialization failure"); - } - - try { - ByteArrayOutputStream streamOut = new ByteArrayOutputStream(); - ObjectOutputStream oo = new ObjectOutputStream(streamOut); - Polygon pt = new Polygon(); - pt.startPath(10, 10); - pt.lineTo(100, 100); - pt.lineTo(200, 100); - pt = (Polygon) GeometryEngine.simplify(pt, null); - oo.writeObject(pt); - ByteArrayInputStream streamIn = new ByteArrayInputStream( - streamOut.toByteArray()); - ObjectInputStream ii = new ObjectInputStream(streamIn); - Polygon ptRes = (Polygon) ii.readObject(); - assertTrue(ptRes.equals(pt)); - } catch (Exception ex) { - fail("Polygon serialization failure"); - } - - //try - //{ - //FileOutputStream streamOut = new FileOutputStream("c:/temp/savedPolygon1.txt"); - //ObjectOutputStream oo = new ObjectOutputStream(streamOut); - //Polygon pt = new Polygon(); - //pt.startPath(10, 10); - //pt.lineTo(100, 100); - //pt.lineTo(200, 100); - //pt = (Polygon)GeometryEngine.simplify(pt, null); - //oo.writeObject(pt); - //} - //catch(Exception ex) - //{ - //fail("Polygon serialization failure"); - //} - - try { - InputStream s = TestSerialization.class - .getResourceAsStream("savedPolygon.txt"); - ObjectInputStream ii = new ObjectInputStream(s); - Polygon ptRes = (Polygon) ii.readObject(); - assertTrue(ptRes != null); - } catch (Exception ex) { - fail("Polygon serialization failure"); - } - try { - InputStream s = TestSerialization.class - .getResourceAsStream("savedPolygon1.txt"); - ObjectInputStream ii = new ObjectInputStream(s); - Polygon ptRes = (Polygon) ii.readObject(); - assertTrue(ptRes != null); - } catch (Exception ex) { - fail("Polygon serialization failure"); - } - } - - @Test - public void testSerializePolyline() { - try { - ByteArrayOutputStream streamOut = new ByteArrayOutputStream(); - ObjectOutputStream oo = new ObjectOutputStream(streamOut); - Polyline pt = new Polyline(); - pt.startPath(10, 10); - pt.lineTo(100, 100); - pt.lineTo(200, 100); - oo.writeObject(pt); - ByteArrayInputStream streamIn = new ByteArrayInputStream( - streamOut.toByteArray()); - ObjectInputStream ii = new ObjectInputStream(streamIn); - Polyline ptRes = (Polyline) ii.readObject(); - assertTrue(ptRes.equals(pt)); - } catch (Exception ex) { - fail("Polyline serialization failure"); - } - - //try - //{ - //FileOutputStream streamOut = new FileOutputStream("c:/temp/savedPolyline1.txt"); - //ObjectOutputStream oo = new ObjectOutputStream(streamOut); - //Polyline pt = new Polyline(); - //pt.startPath(10, 10); - //pt.lineTo(100, 100); - //pt.lineTo(200, 100); - //oo.writeObject(pt); - //} - //catch(Exception ex) - //{ - //fail("Polyline serialization failure"); - //} - - try { - InputStream s = TestSerialization.class - .getResourceAsStream("savedPolyline.txt"); - ObjectInputStream ii = new ObjectInputStream(s); - Polyline ptRes = (Polyline) ii.readObject(); - assertTrue(ptRes != null); - } catch (Exception ex) { - fail("Polyline serialization failure"); - } - try { - InputStream s = TestSerialization.class - .getResourceAsStream("savedPolyline1.txt"); - ObjectInputStream ii = new ObjectInputStream(s); - Polyline ptRes = (Polyline) ii.readObject(); - assertTrue(ptRes != null); - } catch (Exception ex) { - fail("Polyline serialization failure"); - } - } - - @Test - public void testSerializeEnvelope() { - try { - ByteArrayOutputStream streamOut = new ByteArrayOutputStream(); - ObjectOutputStream oo = new ObjectOutputStream(streamOut); - Envelope pt = new Envelope(10, 10, 400, 300); - oo.writeObject(pt); - ByteArrayInputStream streamIn = new ByteArrayInputStream( - streamOut.toByteArray()); - ObjectInputStream ii = new ObjectInputStream(streamIn); - Envelope ptRes = (Envelope) ii.readObject(); - assertTrue(ptRes.equals(pt)); - } catch (Exception ex) { - fail("Envelope serialization failure"); - } - - //try - //{ - //FileOutputStream streamOut = new FileOutputStream("c:/temp/savedEnvelope1.txt"); - //ObjectOutputStream oo = new ObjectOutputStream(streamOut); - //Envelope pt = new Envelope(10, 10, 400, 300); - //oo.writeObject(pt); - //} - //catch(Exception ex) - //{ - //fail("Envelope serialization failure"); - //} - - try { - InputStream s = TestSerialization.class - .getResourceAsStream("savedEnvelope.txt"); - ObjectInputStream ii = new ObjectInputStream(s); - Envelope ptRes = (Envelope) ii.readObject(); - assertTrue(ptRes.getXMax() == 400); - } catch (Exception ex) { - fail("Envelope serialization failure"); - } - try { - InputStream s = TestSerialization.class - .getResourceAsStream("savedEnvelope1.txt"); - ObjectInputStream ii = new ObjectInputStream(s); - Envelope ptRes = (Envelope) ii.readObject(); - assertTrue(ptRes.getXMax() == 400); - } catch (Exception ex) { - fail("Envelope serialization failure"); - } - } - - @Test - public void testSerializeMultiPoint() { - try { - ByteArrayOutputStream streamOut = new ByteArrayOutputStream(); - ObjectOutputStream oo = new ObjectOutputStream(streamOut); - MultiPoint pt = new MultiPoint(); - pt.add(10, 30); - pt.add(120, 40); - oo.writeObject(pt); - ByteArrayInputStream streamIn = new ByteArrayInputStream( - streamOut.toByteArray()); - ObjectInputStream ii = new ObjectInputStream(streamIn); - MultiPoint ptRes = (MultiPoint) ii.readObject(); - assertTrue(ptRes.equals(pt)); - } catch (Exception ex) { - fail("MultiPoint serialization failure"); - } - - //try - //{ - //FileOutputStream streamOut = new FileOutputStream("c:/temp/savedMultiPoint1.txt"); - //ObjectOutputStream oo = new ObjectOutputStream(streamOut); - //MultiPoint pt = new MultiPoint(); - //pt.add(10, 30); - //pt.add(120, 40); - //oo.writeObject(pt); - //} - //catch(Exception ex) - //{ - //fail("MultiPoint serialization failure"); - //} - - try { - InputStream s = TestSerialization.class - .getResourceAsStream("savedMultiPoint.txt"); - ObjectInputStream ii = new ObjectInputStream(s); - MultiPoint ptRes = (MultiPoint) ii.readObject(); - assertTrue(ptRes.getPoint(1).getY() == 40); - } catch (Exception ex) { - fail("MultiPoint serialization failure"); - } - try { - InputStream s = TestSerialization.class - .getResourceAsStream("savedMultiPoint1.txt"); - ObjectInputStream ii = new ObjectInputStream(s); - MultiPoint ptRes = (MultiPoint) ii.readObject(); - assertTrue(ptRes.getPoint(1).getY() == 40); - } catch (Exception ex) { - fail("MultiPoint serialization failure"); - } - } - - @Test - public void testSerializeLine() { - try { - ByteArrayOutputStream streamOut = new ByteArrayOutputStream(); - ObjectOutputStream oo = new ObjectOutputStream(streamOut); - Line pt = new Line(); - pt.setStart(new Point(10, 30)); - pt.setEnd(new Point(120, 40)); - oo.writeObject(pt); - ByteArrayInputStream streamIn = new ByteArrayInputStream( - streamOut.toByteArray()); - ObjectInputStream ii = new ObjectInputStream(streamIn); - Line ptRes = (Line) ii.readObject(); - assertTrue(ptRes.equals(pt)); - } catch (Exception ex) { - // fail("Line serialization failure"); - assertEquals(ex.getMessage(), "Cannot serialize this geometry"); - } - } - - @Test - public void testSerializeSR() { - try { - ByteArrayOutputStream streamOut = new ByteArrayOutputStream(); - ObjectOutputStream oo = new ObjectOutputStream(streamOut); - SpatialReference sr = SpatialReference.create(102100); - oo.writeObject(sr); - ByteArrayInputStream streamIn = new ByteArrayInputStream( - streamOut.toByteArray()); - ObjectInputStream ii = new ObjectInputStream(streamIn); - SpatialReference ptRes = (SpatialReference) ii.readObject(); - assertTrue(ptRes.equals(sr)); - } catch (Exception ex) { - fail("Spatial Reference serialization failure"); - } - } - - @Test - public void testSerializeEnvelope2D() { - try { - ByteArrayOutputStream streamOut = new ByteArrayOutputStream(); - ObjectOutputStream oo = new ObjectOutputStream(streamOut); - Envelope2D env = new Envelope2D(1.213948734, 2.213948734, 11.213948734, 12.213948734); - oo.writeObject(env); - ByteArrayInputStream streamIn = new ByteArrayInputStream( - streamOut.toByteArray()); - ObjectInputStream ii = new ObjectInputStream(streamIn); - Envelope2D envRes = (Envelope2D) ii.readObject(); - assertTrue(envRes.equals(env)); - } catch (Exception ex) { - fail("Envelope2D serialization failure"); - } + @Override + protected void setUp() throws Exception { + super.setUp(); + } + + @Override + protected void tearDown() throws Exception { + super.tearDown(); + } + + @Test + public void testSerializePoint() { + try { + ByteArrayOutputStream streamOut = new ByteArrayOutputStream(); + ObjectOutputStream oo = new ObjectOutputStream(streamOut); + Point pt = new Point(10, 40); + oo.writeObject(pt); + ByteArrayInputStream streamIn = new ByteArrayInputStream( + streamOut.toByteArray()); + ObjectInputStream ii = new ObjectInputStream(streamIn); + Point ptRes = (Point) ii.readObject(); + assertTrue(ptRes.equals(pt)); + } catch (Exception ex) { + fail("Point serialization failure"); + + } + + //try + //{ + //FileOutputStream streamOut = new FileOutputStream("c:/temp/savedPoint1.txt"); + //ObjectOutputStream oo = new ObjectOutputStream(streamOut); + //Point pt = new Point(10, 40, 2); + //oo.writeObject(pt); + //} + //catch(Exception ex) + //{ + //fail("Point serialization failure"); + //} + + try { + InputStream s = TestSerialization.class + .getResourceAsStream("savedPoint.txt"); + ObjectInputStream ii = new ObjectInputStream(s); + Point ptRes = (Point) ii.readObject(); + assertTrue(ptRes.getX() == 10 && ptRes.getY() == 40); + } catch (Exception ex) { + fail("Point serialization failure"); + } + + try { + InputStream s = TestSerialization.class + .getResourceAsStream("savedPoint1.txt"); + ObjectInputStream ii = new ObjectInputStream(s); + Point ptRes = (Point) ii.readObject(); + assertTrue(ptRes.getX() == 10 && ptRes.getY() == 40 && ptRes.getZ() == 2); + } catch (Exception ex) { + fail("Point serialization failure"); + } + + } + + @Test + public void testSerializePolygon() { + try { + ByteArrayOutputStream streamOut = new ByteArrayOutputStream(); + ObjectOutputStream oo = new ObjectOutputStream(streamOut); + Polygon pt = new Polygon(); + pt.startPath(10, 10); + pt.lineTo(100, 100); + pt.lineTo(200, 100); + oo.writeObject(pt); + ByteArrayInputStream streamIn = new ByteArrayInputStream( + streamOut.toByteArray()); + ObjectInputStream ii = new ObjectInputStream(streamIn); + Polygon ptRes = (Polygon) ii.readObject(); + assertTrue(ptRes.equals(pt)); + } catch (Exception ex) { + fail("Polygon serialization failure"); + } + + try { + ByteArrayOutputStream streamOut = new ByteArrayOutputStream(); + ObjectOutputStream oo = new ObjectOutputStream(streamOut); + Polygon pt = new Polygon(); + pt.startPath(10, 10); + pt.lineTo(100, 100); + pt.lineTo(200, 100); + pt = (Polygon) GeometryEngine.simplify(pt, null); + oo.writeObject(pt); + ByteArrayInputStream streamIn = new ByteArrayInputStream( + streamOut.toByteArray()); + ObjectInputStream ii = new ObjectInputStream(streamIn); + Polygon ptRes = (Polygon) ii.readObject(); + assertTrue(ptRes.equals(pt)); + } catch (Exception ex) { + fail("Polygon serialization failure"); + } + + //try + //{ + //FileOutputStream streamOut = new FileOutputStream("c:/temp/savedPolygon1.txt"); + //ObjectOutputStream oo = new ObjectOutputStream(streamOut); + //Polygon pt = new Polygon(); + //pt.startPath(10, 10); + //pt.lineTo(100, 100); + //pt.lineTo(200, 100); + //pt = (Polygon)GeometryEngine.simplify(pt, null); + //oo.writeObject(pt); + //} + //catch(Exception ex) + //{ + //fail("Polygon serialization failure"); + //} + + try { + InputStream s = TestSerialization.class + .getResourceAsStream("savedPolygon.txt"); + ObjectInputStream ii = new ObjectInputStream(s); + Polygon ptRes = (Polygon) ii.readObject(); + assertTrue(ptRes != null); + } catch (Exception ex) { + fail("Polygon serialization failure"); + } + try { + InputStream s = TestSerialization.class + .getResourceAsStream("savedPolygon1.txt"); + ObjectInputStream ii = new ObjectInputStream(s); + Polygon ptRes = (Polygon) ii.readObject(); + assertTrue(ptRes != null); + } catch (Exception ex) { + fail("Polygon serialization failure"); + } + } + + @Test + public void testSerializePolyline() { + try { + ByteArrayOutputStream streamOut = new ByteArrayOutputStream(); + ObjectOutputStream oo = new ObjectOutputStream(streamOut); + Polyline pt = new Polyline(); + pt.startPath(10, 10); + pt.lineTo(100, 100); + pt.lineTo(200, 100); + oo.writeObject(pt); + ByteArrayInputStream streamIn = new ByteArrayInputStream( + streamOut.toByteArray()); + ObjectInputStream ii = new ObjectInputStream(streamIn); + Polyline ptRes = (Polyline) ii.readObject(); + assertTrue(ptRes.equals(pt)); + } catch (Exception ex) { + fail("Polyline serialization failure"); + } + + //try + //{ + //FileOutputStream streamOut = new FileOutputStream("c:/temp/savedPolyline1.txt"); + //ObjectOutputStream oo = new ObjectOutputStream(streamOut); + //Polyline pt = new Polyline(); + //pt.startPath(10, 10); + //pt.lineTo(100, 100); + //pt.lineTo(200, 100); + //oo.writeObject(pt); + //} + //catch(Exception ex) + //{ + //fail("Polyline serialization failure"); + //} + + try { + InputStream s = TestSerialization.class + .getResourceAsStream("savedPolyline.txt"); + ObjectInputStream ii = new ObjectInputStream(s); + Polyline ptRes = (Polyline) ii.readObject(); + assertTrue(ptRes != null); + } catch (Exception ex) { + fail("Polyline serialization failure"); + } + try { + InputStream s = TestSerialization.class + .getResourceAsStream("savedPolyline1.txt"); + ObjectInputStream ii = new ObjectInputStream(s); + Polyline ptRes = (Polyline) ii.readObject(); + assertTrue(ptRes != null); + } catch (Exception ex) { + fail("Polyline serialization failure"); + } + } + + @Test + public void testSerializeEnvelope() { + try { + ByteArrayOutputStream streamOut = new ByteArrayOutputStream(); + ObjectOutputStream oo = new ObjectOutputStream(streamOut); + Envelope pt = new Envelope(10, 10, 400, 300); + oo.writeObject(pt); + ByteArrayInputStream streamIn = new ByteArrayInputStream( + streamOut.toByteArray()); + ObjectInputStream ii = new ObjectInputStream(streamIn); + Envelope ptRes = (Envelope) ii.readObject(); + assertTrue(ptRes.equals(pt)); + } catch (Exception ex) { + fail("Envelope serialization failure"); + } + + //try + //{ + //FileOutputStream streamOut = new FileOutputStream("c:/temp/savedEnvelope1.txt"); + //ObjectOutputStream oo = new ObjectOutputStream(streamOut); + //Envelope pt = new Envelope(10, 10, 400, 300); + //oo.writeObject(pt); + //} + //catch(Exception ex) + //{ + //fail("Envelope serialization failure"); + //} + + try { + InputStream s = TestSerialization.class + .getResourceAsStream("savedEnvelope.txt"); + ObjectInputStream ii = new ObjectInputStream(s); + Envelope ptRes = (Envelope) ii.readObject(); + assertTrue(ptRes.getXMax() == 400); + } catch (Exception ex) { + fail("Envelope serialization failure"); + } + try { + InputStream s = TestSerialization.class + .getResourceAsStream("savedEnvelope1.txt"); + ObjectInputStream ii = new ObjectInputStream(s); + Envelope ptRes = (Envelope) ii.readObject(); + assertTrue(ptRes.getXMax() == 400); + } catch (Exception ex) { + fail("Envelope serialization failure"); + } + } + + @Test + public void testSerializeMultiPoint() { + try { + ByteArrayOutputStream streamOut = new ByteArrayOutputStream(); + ObjectOutputStream oo = new ObjectOutputStream(streamOut); + MultiPoint pt = new MultiPoint(); + pt.add(10, 30); + pt.add(120, 40); + oo.writeObject(pt); + ByteArrayInputStream streamIn = new ByteArrayInputStream( + streamOut.toByteArray()); + ObjectInputStream ii = new ObjectInputStream(streamIn); + MultiPoint ptRes = (MultiPoint) ii.readObject(); + assertTrue(ptRes.equals(pt)); + } catch (Exception ex) { + fail("MultiPoint serialization failure"); + } + + //try + //{ + //FileOutputStream streamOut = new FileOutputStream("c:/temp/savedMultiPoint1.txt"); + //ObjectOutputStream oo = new ObjectOutputStream(streamOut); + //MultiPoint pt = new MultiPoint(); + //pt.add(10, 30); + //pt.add(120, 40); + //oo.writeObject(pt); + //} + //catch(Exception ex) + //{ + //fail("MultiPoint serialization failure"); + //} + + try { + InputStream s = TestSerialization.class + .getResourceAsStream("savedMultiPoint.txt"); + ObjectInputStream ii = new ObjectInputStream(s); + MultiPoint ptRes = (MultiPoint) ii.readObject(); + assertTrue(ptRes.getPoint(1).getY() == 40); + } catch (Exception ex) { + fail("MultiPoint serialization failure"); + } + try { + InputStream s = TestSerialization.class + .getResourceAsStream("savedMultiPoint1.txt"); + ObjectInputStream ii = new ObjectInputStream(s); + MultiPoint ptRes = (MultiPoint) ii.readObject(); + assertTrue(ptRes.getPoint(1).getY() == 40); + } catch (Exception ex) { + fail("MultiPoint serialization failure"); + } + } + + @Test + public void testSerializeLine() { + try { + ByteArrayOutputStream streamOut = new ByteArrayOutputStream(); + ObjectOutputStream oo = new ObjectOutputStream(streamOut); + Line pt = new Line(); + pt.setStart(new Point(10, 30)); + pt.setEnd(new Point(120, 40)); + oo.writeObject(pt); + ByteArrayInputStream streamIn = new ByteArrayInputStream( + streamOut.toByteArray()); + ObjectInputStream ii = new ObjectInputStream(streamIn); + Line ptRes = (Line) ii.readObject(); + assertTrue(ptRes.equals(pt)); + } catch (Exception ex) { + // fail("Line serialization failure"); + assertEquals(ex.getMessage(), "Cannot serialize this geometry"); + } + } + + @Test + public void testSerializeSR() { + try { + ByteArrayOutputStream streamOut = new ByteArrayOutputStream(); + ObjectOutputStream oo = new ObjectOutputStream(streamOut); + SpatialReference sr = SpatialReference.create(102100); + oo.writeObject(sr); + ByteArrayInputStream streamIn = new ByteArrayInputStream( + streamOut.toByteArray()); + ObjectInputStream ii = new ObjectInputStream(streamIn); + SpatialReference ptRes = (SpatialReference) ii.readObject(); + assertTrue(ptRes.equals(sr)); + } catch (Exception ex) { + fail("Spatial Reference serialization failure"); + } + } + + @Test + public void testSerializeEnvelope2D() { + try { + ByteArrayOutputStream streamOut = new ByteArrayOutputStream(); + ObjectOutputStream oo = new ObjectOutputStream(streamOut); + Envelope2D env = new Envelope2D(1.213948734, 2.213948734, 11.213948734, 12.213948734); + oo.writeObject(env); + ByteArrayInputStream streamIn = new ByteArrayInputStream( + streamOut.toByteArray()); + ObjectInputStream ii = new ObjectInputStream(streamIn); + Envelope2D envRes = (Envelope2D) ii.readObject(); + assertTrue(envRes.equals(env)); + } catch (Exception ex) { + fail("Envelope2D serialization failure"); + } // try // { @@ -384,17 +384,17 @@ public void testSerializeEnvelope2D() { // fail("Envelope2D serialization failure"); // } - try { - InputStream s = TestSerialization.class - .getResourceAsStream("savedEnvelope2D.txt"); - ObjectInputStream ii = new ObjectInputStream(s); - Envelope2D e = (Envelope2D) ii - .readObject(); - assertTrue(e != null); - assertTrue(e.equals(new Envelope2D(177.123, 188.234, 999.122, 888.999))); - } catch (Exception ex) { - fail("Envelope2D serialization failure"); - } - } + try { + InputStream s = TestSerialization.class + .getResourceAsStream("savedEnvelope2D.txt"); + ObjectInputStream ii = new ObjectInputStream(s); + Envelope2D e = (Envelope2D) ii + .readObject(); + assertTrue(e != null); + assertTrue(e.equals(new Envelope2D(177.123, 188.234, 999.122, 888.999))); + } catch (Exception ex) { + fail("Envelope2D serialization failure"); + } + } } diff --git a/src/test/java/com/esri/core/geometry/TestSimplify.java b/src/test/java/com/esri/core/geometry/TestSimplify.java index 980f30c7..acad1ce4 100644 --- a/src/test/java/com/esri/core/geometry/TestSimplify.java +++ b/src/test/java/com/esri/core/geometry/TestSimplify.java @@ -34,479 +34,479 @@ import org.junit.Test; public class TestSimplify extends TestCase { - OperatorFactoryLocal factory = null; - OperatorSimplify simplifyOp = null; - OperatorSimplifyOGC simplifyOpOGC = null; - SpatialReference sr102100 = null; - SpatialReference sr4326 = null; - SpatialReference sr3857 = null; - - @Override - protected void setUp() throws Exception { - super.setUp(); - factory = OperatorFactoryLocal.getInstance(); - simplifyOp = (OperatorSimplify) factory - .getOperator(Operator.Type.Simplify); - simplifyOpOGC = (OperatorSimplifyOGC) factory - .getOperator(Operator.Type.SimplifyOGC); - sr102100 = SpatialReference.create(102100); - sr3857 = SpatialReference.create(3857);// PE_PCS_WGS_1984_WEB_MERCATOR_AUXSPHERE); - sr4326 = SpatialReference.create(4326);// enum_value2(SpatialReference, - // Code, GCS_WGS_1984)); - } - - @Override - protected void tearDown() throws Exception { - super.tearDown(); - } - - public Polygon makeNonSimplePolygon2() { - //MapGeometry mg = OperatorFactoryLocal.loadGeometryFromJSONFileDbg("c:/temp/simplify_polygon_gnomonic.txt"); - //Geometry res = OperatorSimplify.local().execute(mg.getGeometry(), mg.getSpatialReference(), true, null); - - - Polygon poly = new Polygon(); - poly.startPath(0, 0); - poly.lineTo(0, 15); - poly.lineTo(15, 15); - poly.lineTo(15, 0); - - // This is an interior ring but it is clockwise - poly.startPath(5, 5); - poly.lineTo(5, 6); - poly.lineTo(6, 6); - poly.lineTo(6, 5); - - return poly; - }// done + OperatorFactoryLocal factory = null; + OperatorSimplify simplifyOp = null; + OperatorSimplifyOGC simplifyOpOGC = null; + SpatialReference sr102100 = null; + SpatialReference sr4326 = null; + SpatialReference sr3857 = null; + + @Override + protected void setUp() throws Exception { + super.setUp(); + factory = OperatorFactoryLocal.getInstance(); + simplifyOp = (OperatorSimplify) factory + .getOperator(Operator.Type.Simplify); + simplifyOpOGC = (OperatorSimplifyOGC) factory + .getOperator(Operator.Type.SimplifyOGC); + sr102100 = SpatialReference.create(102100); + sr3857 = SpatialReference.create(3857);// PE_PCS_WGS_1984_WEB_MERCATOR_AUXSPHERE); + sr4326 = SpatialReference.create(4326);// enum_value2(SpatialReference, + // Code, GCS_WGS_1984)); + } + + @Override + protected void tearDown() throws Exception { + super.tearDown(); + } + + public Polygon makeNonSimplePolygon2() { + //MapGeometry mg = OperatorFactoryLocal.loadGeometryFromJSONFileDbg("c:/temp/simplify_polygon_gnomonic.txt"); + //Geometry res = OperatorSimplify.local().execute(mg.getGeometry(), mg.getSpatialReference(), true, null); + + + Polygon poly = new Polygon(); + poly.startPath(0, 0); + poly.lineTo(0, 15); + poly.lineTo(15, 15); + poly.lineTo(15, 0); + + // This is an interior ring but it is clockwise + poly.startPath(5, 5); + poly.lineTo(5, 6); + poly.lineTo(6, 6); + poly.lineTo(6, 5); + + return poly; + }// done /* - * ------------>---------------->--------------- | | | (1) | | | | --->--- + * ------------>---------------->--------------- | | | (1) | | | | --->--- * ------->------- | | | | | (5) | | | | | | --<-- | | | | (2) | | | | | | | * | | | | (4) | | | | | | | -->-- | | --<-- | ---<--- | | | | | | * -------<------- | | (3) | -------------<---------------<--------------- * -->-- */ - // Bowtie case with vertices at intersection - - public Polygon makeNonSimplePolygon5() { - Polygon poly = new Polygon(); - poly.startPath(10, 0); - poly.lineTo(0, 0); - poly.lineTo(5, 5); - poly.lineTo(10, 10); - poly.lineTo(0, 10); - poly.lineTo(5, 5); - - return poly; - }// done - - @Test - public void test0() { - Polygon poly1 = new Polygon(); - poly1.addEnvelope(new Envelope(10, 10, 40, 20), false); - Polygon poly2 = (Polygon) simplifyOp.execute(poly1, null, false, null); - boolean res = simplifyOp.isSimpleAsFeature(poly2, null, true, null, - null); - assertTrue(res); - // assertTrue(poly1.equals(poly2)); - }// done - - @Test - public void test0Poly() {// simple - Polygon poly1 = new Polygon(); - poly1.addEnvelope(new Envelope(10, 10, 40, 20), false); - poly1.addEnvelope(new Envelope(50, 10, 100, 20), false); - Polygon poly2 = (Polygon) simplifyOp.execute(poly1, null, false, null); - boolean res = simplifyOp.isSimpleAsFeature(poly2, null, true, null, - null); - assertTrue(res); - // assertTrue(poly1.equals(poly2)); - }// done - - @Test - public void test0Polygon_Spike1() {// non-simple (spike) - Polygon poly1 = new Polygon(); - poly1.startPath(10, 10); - poly1.lineTo(10, 20); - poly1.lineTo(40, 20); - poly1.lineTo(40, 10); - poly1.lineTo(60, 10); - poly1.lineTo(70, 10); - - boolean res = simplifyOp.isSimpleAsFeature(poly1, null, true, null, - null); - assertTrue(!res); - Polygon poly2 = (Polygon) simplifyOp.execute(poly1, null, false, null); - res = simplifyOp.isSimpleAsFeature(poly2, null, true, null, null); - assertTrue(res); - assertTrue(poly2.getPointCount() == 4); - }// done - - @Test - public void test0Polygon_Spike2() {// non-simple (spikes) - Polygon poly1 = new Polygon(); - // rectangle with a spike - poly1.startPath(10, 10); - poly1.lineTo(10, 20); - poly1.lineTo(40, 20); - poly1.lineTo(40, 10); - poly1.lineTo(60, 10); - poly1.lineTo(70, 10); - - // degenerate - poly1.startPath(100, 100); - poly1.lineTo(100, 120); - poly1.lineTo(100, 130); - - boolean res = simplifyOp.isSimpleAsFeature(poly1, null, true, null, - null); - assertTrue(!res); - Polygon poly2 = (Polygon) simplifyOp.execute(poly1, null, false, null); - res = simplifyOp.isSimpleAsFeature(poly2, null, true, null, null); - assertTrue(res); - assertTrue(poly2.getPointCount() == 4); - }// done - - @Test - public void test0Polygon_Spike3() {// non-simple (spikes) - Polygon poly1 = new Polygon(); - // degenerate - poly1.startPath(100, 100); - poly1.lineTo(100, 120); - poly1.lineTo(100, 130); - - boolean res = simplifyOp.isSimpleAsFeature(poly1, null, true, null, - null); - assertTrue(!res); - Polygon poly2 = (Polygon) simplifyOp.execute(poly1, null, false, null); - res = simplifyOp.isSimpleAsFeature(poly2, null, true, null, null); - assertTrue(res); - assertTrue(poly2.isEmpty()); - }// done - - @Test - public void test0PolygonSelfIntersect1() {// non-simple (self-intersection) - Polygon poly1 = new Polygon(); - // touch uncracked - poly1.startPath(0, 0); - poly1.lineTo(0, 100); - poly1.lineTo(100, 100); - poly1.lineTo(0, 50); - poly1.lineTo(100, 0); - - boolean res = simplifyOp.isSimpleAsFeature(poly1, null, true, null, - null); - assertTrue(!res); - Polygon poly2 = (Polygon) simplifyOp.execute(poly1, null, false, null); - res = simplifyOp.isSimpleAsFeature(poly2, null, true, null, null); - assertTrue(res); - assertTrue(!poly2.isEmpty()); - }// done - - @Test - public void test0PolygonSelfIntersect2() {// non-simple (self-intersection) - Polygon poly1 = new Polygon(); - poly1.startPath(0, 0); - poly1.lineTo(0, 100); - poly1.lineTo(100, 100); - poly1.lineTo(-100, 0); - // poly1.lineTo(100, 0); - - boolean res = simplifyOp.isSimpleAsFeature(poly1, null, true, null, - null); - assertTrue(!res); - Polygon poly2 = (Polygon) simplifyOp.execute(poly1, null, false, null); - res = simplifyOp.isSimpleAsFeature(poly2, null, true, null, null); - assertTrue(res); - assertTrue(!poly2.isEmpty()); - }// done - - @Test - public void test0PolygonSelfIntersect3() { - Polygon poly = new Polygon(); - poly.startPath(0, 0); - poly.lineTo(0, 15); - poly.lineTo(15, 15); - poly.lineTo(15, 0); - - // This part intersects with the first part - poly.startPath(10, 10); - poly.lineTo(10, 20); - poly.lineTo(20, 20); - poly.lineTo(20, 10); - - boolean res = simplifyOp - .isSimpleAsFeature(poly, null, true, null, null); - assertTrue(!res); - Polygon poly2 = (Polygon) simplifyOp.execute(poly, null, false, null); - res = simplifyOp.isSimpleAsFeature(poly2, null, true, null, null); - assertTrue(res); - assertTrue(!poly2.isEmpty()); - }// done - - @Test - public void test0PolygonInteriorRing1() { - Polygon poly = new Polygon(); - poly.startPath(0, 0); - poly.lineTo(0, 15); - poly.lineTo(15, 15); - poly.lineTo(15, 0); - - // This is an interior ring but it is clockwise - poly.startPath(5, 5); - poly.lineTo(5, 6); - poly.lineTo(6, 6); - poly.lineTo(6, 5); - - boolean res = simplifyOp - .isSimpleAsFeature(poly, null, true, null, null); - assertTrue(!res); - Polygon poly2 = (Polygon) simplifyOp.execute(poly, null, false, null); - res = simplifyOp.isSimpleAsFeature(poly2, null, true, null, null); - assertTrue(res); - assertTrue(!poly2.isEmpty()); - }// done - - @Test - public void test0PolygonInteriorRing2() { - Polygon poly = new Polygon(); - poly.startPath(0, 0); - poly.lineTo(0, 15); - poly.lineTo(15, 15); - poly.lineTo(15, 0); - - // This is an interior ring but it is clockwise - poly.startPath(5, 5); - poly.lineTo(5, 6); - poly.lineTo(6, 6); - poly.lineTo(6, 5); - - // This part intersects with the first part - poly.startPath(10, 10); - poly.lineTo(10, 20); - poly.lineTo(20, 20); - poly.lineTo(20, 10); - - boolean res = simplifyOp - .isSimpleAsFeature(poly, null, true, null, null); - assertTrue(!res); - Polygon poly2 = (Polygon) simplifyOp.execute(poly, null, false, null); - res = simplifyOp.isSimpleAsFeature(poly2, null, true, null, null); - assertTrue(res); - assertTrue(!poly2.isEmpty()); - }// done - - @Test - public void test0PolygonInteriorRingWithCommonBoundary1() { - // Two rings have common boundary - Polygon poly = new Polygon(); - poly.startPath(0, 0); - poly.lineTo(0, 10); - poly.lineTo(10, 10); - poly.lineTo(10, 0); - - poly.startPath(10, 0); - poly.lineTo(10, 10); - poly.lineTo(20, 10); - poly.lineTo(20, 0); - - boolean res = simplifyOp - .isSimpleAsFeature(poly, null, true, null, null); - assertTrue(!res); - Polygon poly2 = (Polygon) simplifyOp.execute(poly, null, false, null); - res = simplifyOp.isSimpleAsFeature(poly2, null, true, null, null); - assertTrue(res); - assertTrue(!poly2.isEmpty()); - }// done - - @Test - public void test0PolygonInteriorRingWithCommonBoundary2() { - // Two rings have common boundary - Polygon poly = new Polygon(); - poly.startPath(0, 0); - poly.lineTo(0, 10); - poly.lineTo(10, 10); - poly.lineTo(10, 0); - - poly.startPath(10, 5); - poly.lineTo(10, 6); - poly.lineTo(20, 6); - poly.lineTo(20, 5); - - boolean res = simplifyOp - .isSimpleAsFeature(poly, null, true, null, null); - assertTrue(!res); - Polygon poly2 = (Polygon) simplifyOp.execute(poly, null, false, null); - res = simplifyOp.isSimpleAsFeature(poly2, null, true, null, null); - assertTrue(res); - assertTrue(!poly2.isEmpty()); - }// done - - @Test - public void testPolygon() { - Polygon nonSimplePolygon = makeNonSimplePolygon(); - Polygon simplePolygon = (Polygon) simplifyOp.execute(nonSimplePolygon, - sr3857, false, null); - - boolean res = simplifyOp.isSimpleAsFeature(simplePolygon, sr3857, true, - null, null); - assertTrue(res); - - @SuppressWarnings("unused") - int partCount = simplePolygon.getPathCount(); - // assertTrue(partCount == 2); - - double area = simplePolygon.calculateRingArea2D(0); - assertTrue(Math.abs(area - 300) <= 0.0001); - - area = simplePolygon.calculateRingArea2D(1); - assertTrue(Math.abs(area - (-25.0)) <= 0.0001); - }// done - - @Test - public void testPolygon2() { - Polygon nonSimplePolygon2 = makeNonSimplePolygon2(); - double area = nonSimplePolygon2.calculateRingArea2D(1); - assertTrue(Math.abs(area - 1.0) <= 0.0001); - - Polygon simplePolygon2 = (Polygon) simplifyOp.execute( - nonSimplePolygon2, sr3857, false, null); - - boolean res = simplifyOp.isSimpleAsFeature(simplePolygon2, sr3857, - true, null, null); - assertTrue(res); - - area = simplePolygon2.calculateRingArea2D(0); - assertTrue(Math.abs(area - 225) <= 0.0001); - - area = simplePolygon2.calculateRingArea2D(1); - assertTrue(Math.abs(area - (-1.0)) <= 0.0001); - }// done - - @Test - public void testPolygon3() { - Polygon nonSimplePolygon3 = makeNonSimplePolygon3(); - Polygon simplePolygon3 = (Polygon) simplifyOp.execute( - nonSimplePolygon3, sr3857, false, null); - - boolean res = simplifyOp.isSimpleAsFeature(simplePolygon3, sr3857, - true, null, null); - assertTrue(res); - - double area = simplePolygon3.calculateRingArea2D(0); - assertTrue(Math.abs(area - 875) <= 0.0001); - - area = simplePolygon3.calculateRingArea2D(1); - assertTrue(Math.abs(area - (-225)) <= 0.0001 - || Math.abs(area - (-50.0)) <= 0.0001); - - area = simplePolygon3.calculateRingArea2D(2); - assertTrue(Math.abs(area - (-225)) <= 0.0001 - || Math.abs(area - (-50.0)) <= 0.0001); - - area = simplePolygon3.calculateRingArea2D(3); - assertTrue(Math.abs(area - 25) <= 0.0001); - - area = simplePolygon3.calculateRingArea2D(4); - assertTrue(Math.abs(area - 25) <= 0.0001); - }// done - - @Test - public void testPolyline() { - Polyline nonSimplePolyline = makeNonSimplePolyline(); - Polyline simplePolyline = (Polyline) simplifyOp.execute( - nonSimplePolyline, sr3857, false, null); - - int segmentCount = simplePolyline.getSegmentCount(); - assertTrue(segmentCount == 4); - }// done - - @Test - public void testPolygon4() { - Polygon nonSimplePolygon4 = makeNonSimplePolygon4(); - Polygon simplePolygon4 = (Polygon) simplifyOp.execute( - nonSimplePolygon4, sr3857, false, null); - boolean res = simplifyOp.isSimpleAsFeature(simplePolygon4, sr3857, - true, null, null); - assertTrue(res); - - assertTrue(simplePolygon4.getPointCount() == 5); - Point point = nonSimplePolygon4.getPoint(0); - assertTrue(point.getX() == 0.0 && point.getY() == 0.0); - point = nonSimplePolygon4.getPoint(1); - assertTrue(point.getX() == 0.0 && point.getY() == 10.0); - point = nonSimplePolygon4.getPoint(2); - assertTrue(point.getX() == 10.0 && point.getY() == 10.0); - point = nonSimplePolygon4.getPoint(3); - assertTrue(point.getX() == 10.0 && point.getY() == 0.0); - point = nonSimplePolygon4.getPoint(4); - assertTrue(point.getX() == 5.0 && point.getY() == 0.0); - }// done - - @Test - public void testPolygon5() { - Polygon nonSimplePolygon5 = makeNonSimplePolygon5(); - Polygon simplePolygon5 = (Polygon) simplifyOp.execute( - nonSimplePolygon5, sr3857, false, null); - assertTrue(simplePolygon5 != null); - - boolean res = simplifyOp.isSimpleAsFeature(simplePolygon5, sr3857, - true, null, null); - assertTrue(res); - - int pointCount = simplePolygon5.getPointCount(); - assertTrue(pointCount == 6); - - double area = simplePolygon5.calculateArea2D(); - assertTrue(Math.abs(area - 50.0) <= 0.001); - - }// done - - @Test - public void testPolygon6() { - Polygon nonSimplePolygon6 = makeNonSimplePolygon6(); - Polygon simplePolygon6 = (Polygon) simplifyOp.execute( - nonSimplePolygon6, sr3857, false, null); - - boolean res = simplifyOp.isSimpleAsFeature(simplePolygon6, sr3857, - true, null, null); - assertTrue(res); - } - - @Test - public void testPolygon7() { - Polygon nonSimplePolygon7 = makeNonSimplePolygon7(); - Polygon simplePolygon7 = (Polygon) simplifyOp.execute( - nonSimplePolygon7, sr3857, false, null); - - boolean res = simplifyOp.isSimpleAsFeature(simplePolygon7, sr3857, - true, null, null); - assertTrue(res); - } - - public Polygon makeNonSimplePolygon() { - Polygon poly = new Polygon(); - poly.startPath(0, 0); - poly.lineTo(0, 15); - poly.lineTo(15, 15); - poly.lineTo(15, 0); - - // This is an interior ring but it is clockwise - poly.startPath(5, 5); - poly.lineTo(5, 6); - poly.lineTo(6, 6); - poly.lineTo(6, 5); - - // This part intersects with the first part - poly.startPath(10, 10); - poly.lineTo(10, 20); - poly.lineTo(20, 20); - poly.lineTo(20, 10); - - return poly; - }// done + // Bowtie case with vertices at intersection + + public Polygon makeNonSimplePolygon5() { + Polygon poly = new Polygon(); + poly.startPath(10, 0); + poly.lineTo(0, 0); + poly.lineTo(5, 5); + poly.lineTo(10, 10); + poly.lineTo(0, 10); + poly.lineTo(5, 5); + + return poly; + }// done + + @Test + public void test0() { + Polygon poly1 = new Polygon(); + poly1.addEnvelope(new Envelope(10, 10, 40, 20), false); + Polygon poly2 = (Polygon) simplifyOp.execute(poly1, null, false, null); + boolean res = simplifyOp.isSimpleAsFeature(poly2, null, true, null, + null); + assertTrue(res); + // assertTrue(poly1.equals(poly2)); + }// done + + @Test + public void test0Poly() {// simple + Polygon poly1 = new Polygon(); + poly1.addEnvelope(new Envelope(10, 10, 40, 20), false); + poly1.addEnvelope(new Envelope(50, 10, 100, 20), false); + Polygon poly2 = (Polygon) simplifyOp.execute(poly1, null, false, null); + boolean res = simplifyOp.isSimpleAsFeature(poly2, null, true, null, + null); + assertTrue(res); + // assertTrue(poly1.equals(poly2)); + }// done + + @Test + public void test0Polygon_Spike1() {// non-simple (spike) + Polygon poly1 = new Polygon(); + poly1.startPath(10, 10); + poly1.lineTo(10, 20); + poly1.lineTo(40, 20); + poly1.lineTo(40, 10); + poly1.lineTo(60, 10); + poly1.lineTo(70, 10); + + boolean res = simplifyOp.isSimpleAsFeature(poly1, null, true, null, + null); + assertTrue(!res); + Polygon poly2 = (Polygon) simplifyOp.execute(poly1, null, false, null); + res = simplifyOp.isSimpleAsFeature(poly2, null, true, null, null); + assertTrue(res); + assertTrue(poly2.getPointCount() == 4); + }// done + + @Test + public void test0Polygon_Spike2() {// non-simple (spikes) + Polygon poly1 = new Polygon(); + // rectangle with a spike + poly1.startPath(10, 10); + poly1.lineTo(10, 20); + poly1.lineTo(40, 20); + poly1.lineTo(40, 10); + poly1.lineTo(60, 10); + poly1.lineTo(70, 10); + + // degenerate + poly1.startPath(100, 100); + poly1.lineTo(100, 120); + poly1.lineTo(100, 130); + + boolean res = simplifyOp.isSimpleAsFeature(poly1, null, true, null, + null); + assertTrue(!res); + Polygon poly2 = (Polygon) simplifyOp.execute(poly1, null, false, null); + res = simplifyOp.isSimpleAsFeature(poly2, null, true, null, null); + assertTrue(res); + assertTrue(poly2.getPointCount() == 4); + }// done + + @Test + public void test0Polygon_Spike3() {// non-simple (spikes) + Polygon poly1 = new Polygon(); + // degenerate + poly1.startPath(100, 100); + poly1.lineTo(100, 120); + poly1.lineTo(100, 130); + + boolean res = simplifyOp.isSimpleAsFeature(poly1, null, true, null, + null); + assertTrue(!res); + Polygon poly2 = (Polygon) simplifyOp.execute(poly1, null, false, null); + res = simplifyOp.isSimpleAsFeature(poly2, null, true, null, null); + assertTrue(res); + assertTrue(poly2.isEmpty()); + }// done + + @Test + public void test0PolygonSelfIntersect1() {// non-simple (self-intersection) + Polygon poly1 = new Polygon(); + // touch uncracked + poly1.startPath(0, 0); + poly1.lineTo(0, 100); + poly1.lineTo(100, 100); + poly1.lineTo(0, 50); + poly1.lineTo(100, 0); + + boolean res = simplifyOp.isSimpleAsFeature(poly1, null, true, null, + null); + assertTrue(!res); + Polygon poly2 = (Polygon) simplifyOp.execute(poly1, null, false, null); + res = simplifyOp.isSimpleAsFeature(poly2, null, true, null, null); + assertTrue(res); + assertTrue(!poly2.isEmpty()); + }// done + + @Test + public void test0PolygonSelfIntersect2() {// non-simple (self-intersection) + Polygon poly1 = new Polygon(); + poly1.startPath(0, 0); + poly1.lineTo(0, 100); + poly1.lineTo(100, 100); + poly1.lineTo(-100, 0); + // poly1.lineTo(100, 0); + + boolean res = simplifyOp.isSimpleAsFeature(poly1, null, true, null, + null); + assertTrue(!res); + Polygon poly2 = (Polygon) simplifyOp.execute(poly1, null, false, null); + res = simplifyOp.isSimpleAsFeature(poly2, null, true, null, null); + assertTrue(res); + assertTrue(!poly2.isEmpty()); + }// done + + @Test + public void test0PolygonSelfIntersect3() { + Polygon poly = new Polygon(); + poly.startPath(0, 0); + poly.lineTo(0, 15); + poly.lineTo(15, 15); + poly.lineTo(15, 0); + + // This part intersects with the first part + poly.startPath(10, 10); + poly.lineTo(10, 20); + poly.lineTo(20, 20); + poly.lineTo(20, 10); + + boolean res = simplifyOp + .isSimpleAsFeature(poly, null, true, null, null); + assertTrue(!res); + Polygon poly2 = (Polygon) simplifyOp.execute(poly, null, false, null); + res = simplifyOp.isSimpleAsFeature(poly2, null, true, null, null); + assertTrue(res); + assertTrue(!poly2.isEmpty()); + }// done + + @Test + public void test0PolygonInteriorRing1() { + Polygon poly = new Polygon(); + poly.startPath(0, 0); + poly.lineTo(0, 15); + poly.lineTo(15, 15); + poly.lineTo(15, 0); + + // This is an interior ring but it is clockwise + poly.startPath(5, 5); + poly.lineTo(5, 6); + poly.lineTo(6, 6); + poly.lineTo(6, 5); + + boolean res = simplifyOp + .isSimpleAsFeature(poly, null, true, null, null); + assertTrue(!res); + Polygon poly2 = (Polygon) simplifyOp.execute(poly, null, false, null); + res = simplifyOp.isSimpleAsFeature(poly2, null, true, null, null); + assertTrue(res); + assertTrue(!poly2.isEmpty()); + }// done + + @Test + public void test0PolygonInteriorRing2() { + Polygon poly = new Polygon(); + poly.startPath(0, 0); + poly.lineTo(0, 15); + poly.lineTo(15, 15); + poly.lineTo(15, 0); + + // This is an interior ring but it is clockwise + poly.startPath(5, 5); + poly.lineTo(5, 6); + poly.lineTo(6, 6); + poly.lineTo(6, 5); + + // This part intersects with the first part + poly.startPath(10, 10); + poly.lineTo(10, 20); + poly.lineTo(20, 20); + poly.lineTo(20, 10); + + boolean res = simplifyOp + .isSimpleAsFeature(poly, null, true, null, null); + assertTrue(!res); + Polygon poly2 = (Polygon) simplifyOp.execute(poly, null, false, null); + res = simplifyOp.isSimpleAsFeature(poly2, null, true, null, null); + assertTrue(res); + assertTrue(!poly2.isEmpty()); + }// done + + @Test + public void test0PolygonInteriorRingWithCommonBoundary1() { + // Two rings have common boundary + Polygon poly = new Polygon(); + poly.startPath(0, 0); + poly.lineTo(0, 10); + poly.lineTo(10, 10); + poly.lineTo(10, 0); + + poly.startPath(10, 0); + poly.lineTo(10, 10); + poly.lineTo(20, 10); + poly.lineTo(20, 0); + + boolean res = simplifyOp + .isSimpleAsFeature(poly, null, true, null, null); + assertTrue(!res); + Polygon poly2 = (Polygon) simplifyOp.execute(poly, null, false, null); + res = simplifyOp.isSimpleAsFeature(poly2, null, true, null, null); + assertTrue(res); + assertTrue(!poly2.isEmpty()); + }// done + + @Test + public void test0PolygonInteriorRingWithCommonBoundary2() { + // Two rings have common boundary + Polygon poly = new Polygon(); + poly.startPath(0, 0); + poly.lineTo(0, 10); + poly.lineTo(10, 10); + poly.lineTo(10, 0); + + poly.startPath(10, 5); + poly.lineTo(10, 6); + poly.lineTo(20, 6); + poly.lineTo(20, 5); + + boolean res = simplifyOp + .isSimpleAsFeature(poly, null, true, null, null); + assertTrue(!res); + Polygon poly2 = (Polygon) simplifyOp.execute(poly, null, false, null); + res = simplifyOp.isSimpleAsFeature(poly2, null, true, null, null); + assertTrue(res); + assertTrue(!poly2.isEmpty()); + }// done + + @Test + public void testPolygon() { + Polygon nonSimplePolygon = makeNonSimplePolygon(); + Polygon simplePolygon = (Polygon) simplifyOp.execute(nonSimplePolygon, + sr3857, false, null); + + boolean res = simplifyOp.isSimpleAsFeature(simplePolygon, sr3857, true, + null, null); + assertTrue(res); + + @SuppressWarnings("unused") + int partCount = simplePolygon.getPathCount(); + // assertTrue(partCount == 2); + + double area = simplePolygon.calculateRingArea2D(0); + assertTrue(Math.abs(area - 300) <= 0.0001); + + area = simplePolygon.calculateRingArea2D(1); + assertTrue(Math.abs(area - (-25.0)) <= 0.0001); + }// done + + @Test + public void testPolygon2() { + Polygon nonSimplePolygon2 = makeNonSimplePolygon2(); + double area = nonSimplePolygon2.calculateRingArea2D(1); + assertTrue(Math.abs(area - 1.0) <= 0.0001); + + Polygon simplePolygon2 = (Polygon) simplifyOp.execute( + nonSimplePolygon2, sr3857, false, null); + + boolean res = simplifyOp.isSimpleAsFeature(simplePolygon2, sr3857, + true, null, null); + assertTrue(res); + + area = simplePolygon2.calculateRingArea2D(0); + assertTrue(Math.abs(area - 225) <= 0.0001); + + area = simplePolygon2.calculateRingArea2D(1); + assertTrue(Math.abs(area - (-1.0)) <= 0.0001); + }// done + + @Test + public void testPolygon3() { + Polygon nonSimplePolygon3 = makeNonSimplePolygon3(); + Polygon simplePolygon3 = (Polygon) simplifyOp.execute( + nonSimplePolygon3, sr3857, false, null); + + boolean res = simplifyOp.isSimpleAsFeature(simplePolygon3, sr3857, + true, null, null); + assertTrue(res); + + double area = simplePolygon3.calculateRingArea2D(0); + assertTrue(Math.abs(area - 875) <= 0.0001); + + area = simplePolygon3.calculateRingArea2D(1); + assertTrue(Math.abs(area - (-225)) <= 0.0001 + || Math.abs(area - (-50.0)) <= 0.0001); + + area = simplePolygon3.calculateRingArea2D(2); + assertTrue(Math.abs(area - (-225)) <= 0.0001 + || Math.abs(area - (-50.0)) <= 0.0001); + + area = simplePolygon3.calculateRingArea2D(3); + assertTrue(Math.abs(area - 25) <= 0.0001); + + area = simplePolygon3.calculateRingArea2D(4); + assertTrue(Math.abs(area - 25) <= 0.0001); + }// done + + @Test + public void testPolyline() { + Polyline nonSimplePolyline = makeNonSimplePolyline(); + Polyline simplePolyline = (Polyline) simplifyOp.execute( + nonSimplePolyline, sr3857, false, null); + + int segmentCount = simplePolyline.getSegmentCount(); + assertTrue(segmentCount == 4); + }// done + + @Test + public void testPolygon4() { + Polygon nonSimplePolygon4 = makeNonSimplePolygon4(); + Polygon simplePolygon4 = (Polygon) simplifyOp.execute( + nonSimplePolygon4, sr3857, false, null); + boolean res = simplifyOp.isSimpleAsFeature(simplePolygon4, sr3857, + true, null, null); + assertTrue(res); + + assertTrue(simplePolygon4.getPointCount() == 5); + Point point = nonSimplePolygon4.getPoint(0); + assertTrue(point.getX() == 0.0 && point.getY() == 0.0); + point = nonSimplePolygon4.getPoint(1); + assertTrue(point.getX() == 0.0 && point.getY() == 10.0); + point = nonSimplePolygon4.getPoint(2); + assertTrue(point.getX() == 10.0 && point.getY() == 10.0); + point = nonSimplePolygon4.getPoint(3); + assertTrue(point.getX() == 10.0 && point.getY() == 0.0); + point = nonSimplePolygon4.getPoint(4); + assertTrue(point.getX() == 5.0 && point.getY() == 0.0); + }// done + + @Test + public void testPolygon5() { + Polygon nonSimplePolygon5 = makeNonSimplePolygon5(); + Polygon simplePolygon5 = (Polygon) simplifyOp.execute( + nonSimplePolygon5, sr3857, false, null); + assertTrue(simplePolygon5 != null); + + boolean res = simplifyOp.isSimpleAsFeature(simplePolygon5, sr3857, + true, null, null); + assertTrue(res); + + int pointCount = simplePolygon5.getPointCount(); + assertTrue(pointCount == 6); + + double area = simplePolygon5.calculateArea2D(); + assertTrue(Math.abs(area - 50.0) <= 0.001); + + }// done + + @Test + public void testPolygon6() { + Polygon nonSimplePolygon6 = makeNonSimplePolygon6(); + Polygon simplePolygon6 = (Polygon) simplifyOp.execute( + nonSimplePolygon6, sr3857, false, null); + + boolean res = simplifyOp.isSimpleAsFeature(simplePolygon6, sr3857, + true, null, null); + assertTrue(res); + } + + @Test + public void testPolygon7() { + Polygon nonSimplePolygon7 = makeNonSimplePolygon7(); + Polygon simplePolygon7 = (Polygon) simplifyOp.execute( + nonSimplePolygon7, sr3857, false, null); + + boolean res = simplifyOp.isSimpleAsFeature(simplePolygon7, sr3857, + true, null, null); + assertTrue(res); + } + + public Polygon makeNonSimplePolygon() { + Polygon poly = new Polygon(); + poly.startPath(0, 0); + poly.lineTo(0, 15); + poly.lineTo(15, 15); + poly.lineTo(15, 0); + + // This is an interior ring but it is clockwise + poly.startPath(5, 5); + poly.lineTo(5, 6); + poly.lineTo(6, 6); + poly.lineTo(6, 5); + + // This part intersects with the first part + poly.startPath(10, 10); + poly.lineTo(10, 20); + poly.lineTo(20, 20); + poly.lineTo(20, 10); + + return poly; + }// done /* * ------------>---------------->--------------- | | | (1) | | | | --->--- @@ -516,849 +516,849 @@ public Polygon makeNonSimplePolygon() { * -->-- */ - public Polygon makeNonSimplePolygon3() { - Polygon poly = new Polygon(); - poly.startPath(0, 0); - poly.lineTo(0, 25); - poly.lineTo(35, 25); - poly.lineTo(35, 0); - - poly.startPath(5, 5); - poly.lineTo(5, 15); - poly.lineTo(10, 15); - poly.lineTo(10, 5); - - poly.startPath(40, 0); - poly.lineTo(45, 0); - poly.lineTo(45, 5); - poly.lineTo(40, 5); - - poly.startPath(20, 10); - poly.lineTo(25, 10); - poly.lineTo(25, 15); - poly.lineTo(20, 15); - - poly.startPath(15, 5); - poly.lineTo(15, 20); - poly.lineTo(30, 20); - poly.lineTo(30, 5); - - return poly; - }// done - - public Polygon makeNonSimplePolygon4() { - Polygon poly = new Polygon(); - poly.startPath(0, 0); - poly.lineTo(0, 10); - poly.lineTo(10, 10); - poly.lineTo(10, 0); - poly.lineTo(5, 0); - poly.lineTo(5, 5); - poly.lineTo(5, 0); - - return poly; - }// done - - public Polygon makeNonSimplePolygon6() { - Polygon poly = new Polygon(); - poly.startPath(35.34407570857744, 54.00551247713412); - poly.lineTo(41.07663499357954, 20.0); - poly.lineTo(40.66372033705177, 26.217432321849017); - - poly.startPath(42.81936574509338, 20.0); - poly.lineTo(43.58226670584747, 20.0); - poly.lineTo(39.29611825817084, 22.64634933678729); - poly.lineTo(44.369873312241346, 25.81893670527215); - poly.lineTo(42.68845660737179, 20.0); - poly.lineTo(38.569549792944244, 56.47456192829393); - poly.lineTo(42.79274114188401, 45.45117792578003); - poly.lineTo(41.09512147544657, 70.0); - - return poly; - } - - public Polygon makeNonSimplePolygon7() { - Polygon poly = new Polygon(); - - poly.startPath(41.987895433319686, 53.75822619011542); - poly.lineTo(41.98789542535497, 53.75822618803151); - poly.lineTo(40.15120412113667, 68.12604154722113); - poly.lineTo(37.72272697311022, 67.92767094118877); - poly.lineTo(37.147347454283086, 49.497473094145505); - poly.lineTo(38.636627026664385, 51.036687142232736); - - poly.startPath(39.00920080789793, 62.063425518369016); - poly.lineTo(38.604912643136885, 70.0); - poly.lineTo(40.71826863485308, 43.60337143116787); - poly.lineTo(35.34407570857744, 54.005512477134126); - poly.lineTo(39.29611825817084, 22.64634933678729); - - return poly; - } - - public Polyline makeNonSimplePolyline() { - // This polyline has a short segment - Polyline poly = new Polyline(); - poly.startPath(0, 0); - poly.lineTo(10, 0); - poly.lineTo(10, 10); - poly.lineTo(10, 5); - poly.lineTo(-5, 5); - - return poly; - }// done - - @Test - public void testIsSimpleBasicsPoint() { - boolean result; - // point is always simple - Point pt = new Point(); - result = simplifyOp.isSimpleAsFeature(pt, sr4326, false, null, null); - assertTrue(result); - pt.setXY(0, 0); - result = simplifyOp.isSimpleAsFeature(pt, sr4326, false, null, null); - assertTrue(result); - pt.setXY(100000, 10000); - result = simplifyOp.isSimpleAsFeature(pt, sr4326, false, null, null); - assertTrue(result); - }// done - - @Test - public void testIsSimpleBasicsEnvelope() { - // Envelope is simple, when it's width and height are not degenerate - Envelope env = new Envelope(); - boolean result = simplifyOp.isSimpleAsFeature(env, sr4326, false, null, - null); // Empty is simple - assertTrue(result); - env.setCoords(0, 0, 10, 10); - result = simplifyOp.isSimpleAsFeature(env, sr4326, false, null, null); - assertTrue(result); - // sliver but still simple - env.setCoords(0, 0, 0 + sr4326.getTolerance() * 2, 10); - result = simplifyOp.isSimpleAsFeature(env, sr4326, false, null, null); - assertTrue(result); - // sliver and not simple - env.setCoords(0, 0, 0 + sr4326.getTolerance() * 0.5, 10); - result = simplifyOp.isSimpleAsFeature(env, sr4326, false, null, null); - assertTrue(!result); - }// done - - @Test - public void testIsSimpleBasicsLine() { - Line line = new Line(); - boolean result = simplifyOp.isSimpleAsFeature(line, sr4326, false, - null, null); - assertTrue(!result); - - line.setStart(new Point(0, 0)); - // line.setEndXY(0, 0); - result = simplifyOp.isSimpleAsFeature(line, sr4326, false, null, null); - assertTrue(!result); - line.setEnd(new Point(1, 0)); - result = simplifyOp.isSimpleAsFeature(line, sr4326, false, null, null); - assertTrue(result); - }// done - - @Test - public void testIsSimpleMultiPoint1() { - MultiPoint mp = new MultiPoint(); - boolean result = simplifyOp.isSimpleAsFeature(mp, sr4326, false, null, - null); - assertTrue(result);// empty is simple - result = simplifyOp.isSimpleAsFeature( - simplifyOp.execute(mp, sr4326, false, null), sr4326, false, - null, null); - assertTrue(result); - }// done - - @Test - public void testIsSimpleMultiPoint2FarApart() { - // Two point test: far apart - MultiPoint mp = new MultiPoint(); - mp.add(20, 10); - mp.add(100, 100); - boolean result = simplifyOp.isSimpleAsFeature(mp, sr4326, false, null, - null); - assertTrue(result); - result = simplifyOp.isSimpleAsFeature( - simplifyOp.execute(mp, sr4326, false, null), sr4326, false, - null, null); - assertTrue(result); - assertTrue(mp.getPointCount() == 2); - }// done - - @Test - public void testIsSimpleMultiPointCoincident() { - // Two point test: coincident - MultiPoint mp = new MultiPoint(); - mp.add(100, 100); - mp.add(100, 100); - boolean result = simplifyOp.isSimpleAsFeature(mp, sr4326, false, null, - null); - assertTrue(!result); - MultiPoint mpS; - result = simplifyOp.isSimpleAsFeature( - mpS = (MultiPoint) simplifyOp.execute(mp, sr4326, false, null), - sr4326, false, null, null); - assertTrue(result); - assertTrue(mpS.getPointCount() == 1); - }// done - - @Test - public void testMultiPointSR4326_CR184439() { - OperatorFactoryLocal engine = OperatorFactoryLocal.getInstance(); - OperatorSimplify simpOp = (OperatorSimplify) engine - .getOperator(Operator.Type.Simplify); - NonSimpleResult nonSimpResult = new NonSimpleResult(); - nonSimpResult.m_reason = NonSimpleResult.Reason.NotDetermined; - MultiPoint multiPoint = new MultiPoint(); - multiPoint.add(0, 0); - multiPoint.add(0, 1); - multiPoint.add(0, 0); - Boolean multiPointIsSimple = simpOp.isSimpleAsFeature(multiPoint, - SpatialReference.create(4326), true, nonSimpResult, null); - assertFalse(multiPointIsSimple); - assertTrue(nonSimpResult.m_reason == NonSimpleResult.Reason.Clustering); - assertTrue(nonSimpResult.m_vertexIndex1 == 0); - assertTrue(nonSimpResult.m_vertexIndex2 == 2); - } - - @Test - public void testIsSimpleMultiPointCloserThanTolerance() { - // Two point test: closer than tolerance - MultiPoint mp = new MultiPoint(); - MultiPoint mpS; - mp.add(100, 100); - mp.add(100, 100 + sr4326.getTolerance() * .5); - boolean result = simplifyOp.isSimpleAsFeature(mp, sr4326, false, null, - null); - assertTrue(result); - result = simplifyOp.isSimpleAsFeature( - mpS = (MultiPoint) simplifyOp.execute(mp, sr4326, false, null), - sr4326, false, null, null); - assertTrue(result); - assertTrue(mpS.getPointCount() == 2); - }// done - - @Test - public void testIsSimpleMultiPointFarApart2() { - // 5 point test: far apart - MultiPoint mp = new MultiPoint(); - mp.add(100, 100); - mp.add(100, 101); - mp.add(101, 101); - mp.add(11, 1); - mp.add(11, 14); - MultiPoint mpS; - boolean result = simplifyOp.isSimpleAsFeature(mp, sr4326, false, null, - null); - assertTrue(result); - result = simplifyOp.isSimpleAsFeature( - mpS = (MultiPoint) simplifyOp.execute(mp, sr4326, false, null), - sr4326, false, null, null); - assertTrue(result); - assertTrue(mpS.getPointCount() == 5); - }// done - - @Test - public void testIsSimpleMultiPoint_coincident2() { - // 5 point test: coincident - MultiPoint mp = new MultiPoint(); - mp.add(100, 100); - mp.add(100, 101); - mp.add(100, 100); - mp.add(11, 1); - mp.add(11, 14); - boolean result = simplifyOp.isSimpleAsFeature(mp, sr4326, false, null, - null); - assertTrue(!result); - MultiPoint mpS; - result = simplifyOp.isSimpleAsFeature( - mpS = (MultiPoint) simplifyOp.execute(mp, sr4326, false, null), - sr4326, false, null, null); - assertTrue(result); - assertTrue(mpS.getPointCount() == 4); - assertEquals(mpS.getPoint(0).getX(), 100, 1e-7); - assertEquals(mpS.getPoint(0).getY(), 100, 1e-7); - assertEquals(mpS.getPoint(1).getX(), 100, 1e-7); - assertEquals(mpS.getPoint(1).getY(), 101, 1e-7); - assertEquals(mpS.getPoint(2).getX(), 11, 1e-7); - assertEquals(mpS.getPoint(2).getY(), 1, 1e-7); - assertEquals(mpS.getPoint(3).getX(), 11, 1e-7); - assertEquals(mpS.getPoint(3).getY(), 14, 1e-7); - }// done - - @Test - public void testIsSimpleMultiPointCloserThanTolerance2() { - // 5 point test: closer than tolerance - MultiPoint mp = new MultiPoint(); - mp.add(100, 100); - mp.add(100, 101); - mp.add(100, 100 + sr4326.getTolerance() / 2); - mp.add(11, 1); - mp.add(11, 14); - MultiPoint mpS; - boolean result = simplifyOp.isSimpleAsFeature(mp, sr4326, false, null, - null); - assertTrue(result); - result = simplifyOp.isSimpleAsFeature( - mpS = (MultiPoint) simplifyOp.execute(mp, sr4326, false, null), - sr4326, false, null, null); - assertTrue(result); - assertTrue(mpS.getPointCount() == 5); - }// done - - @Test - public void testIsSimplePolyline() { - Polyline poly = new Polyline(); - boolean result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, - null, null); - assertTrue(result);// empty is simple - } - - @Test - public void testIsSimplePolylineFarApart() { - // Two point test: far apart - Polyline poly = new Polyline(); - poly.startPath(20, 10); - poly.lineTo(100, 100); - boolean result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, - null, null); - assertTrue(result); - } - - @Test - public void testIsSimplePolylineCoincident() { - // Two point test: coincident - Polyline poly = new Polyline(); - poly.startPath(100, 100); - poly.lineTo(100, 100); - boolean result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, - null, null); - assertTrue(!result); - @SuppressWarnings("unused") - Polyline polyS; - result = simplifyOp.isSimpleAsFeature( - polyS = (Polyline) simplifyOp - .execute(poly, sr4326, false, null), sr4326, false, - null, null); - assertTrue(result); - } - - @Test - public void testIsSimplePolylineCloserThanTolerance() { - // Two point test: closer than tolerance - Polyline poly = new Polyline(); - poly.startPath(100, 100); - poly.lineTo(100, 100 + sr4326.getTolerance() / 2); - boolean result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, - null, null); - assertTrue(!result); - @SuppressWarnings("unused") - Polyline polyS; - result = simplifyOp.isSimpleAsFeature( - polyS = (Polyline) simplifyOp - .execute(poly, sr4326, false, null), sr4326, false, - null, null); - assertTrue(result); - } - - @Test - public void testIsSimplePolylineFarApartSelfOverlap0() { - // 3 point test: far apart, self overlapping - Polyline poly = new Polyline(); - poly.startPath(0, 0); - poly.lineTo(100, 100); - poly.lineTo(0, 0); - boolean result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, - null, null); - assertTrue(result);// TO CONFIRM should be false - } - - @Test - public void testIsSimplePolylineSelfIntersect() { - // 4 point test: far apart, self intersecting - Polyline poly = new Polyline(); - poly.startPath(0, 0); - poly.lineTo(100, 100); - poly.lineTo(0, 100); - poly.lineTo(100, 0); - boolean result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, - null, null); - assertTrue(result);// TO CONFIRM should be false - } - - @Test - public void testIsSimplePolylineDegenerateSegment() { - // 4 point test: degenerate segment - Polyline poly = new Polyline(); - poly.startPath(0, 0); - poly.lineTo(100, 100); - poly.lineTo(100, 100 + sr4326.getTolerance() / 2); - poly.lineTo(100, 0); - boolean result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, - null, null); - assertTrue(!result); - @SuppressWarnings("unused") - Polyline polyS; - result = simplifyOp.isSimpleAsFeature( - polyS = (Polyline) simplifyOp - .execute(poly, sr4326, false, null), sr4326, false, - null, null); - assertTrue(result); - { - Polyline other = new Polyline(); - other.startPath(0, 0); - other.lineTo(100, 100); - other.lineTo(100, 0); - other.equals(poly); - } - } - - @Test - public void testIsSimplePolylineFarApartSelfOverlap() { - // 3 point test: far apart, self overlapping - Polyline poly = new Polyline(); - poly.startPath(0, 0); - poly.lineTo(100, 100); - poly.lineTo(0, 0); - boolean result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, - null, null); - assertTrue(result);// TO CONFIRM should be false - } - - @Test - public void testIsSimplePolylineFarApartIntersect() { - // 4 point 2 parts test: far apart, intersecting parts - Polyline poly = new Polyline(); - poly.startPath(0, 0); - poly.lineTo(100, 100); - poly.startPath(100, 0); - poly.lineTo(0, 100); - - boolean result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, - null, null); - assertTrue(result);// TO CONFIRM should be false - } - - @Test - public void testIsSimplePolylineFarApartOverlap2() { - // 4 point 2 parts test: far apart, overlapping parts. second part - // starts where first one ends - Polyline poly = new Polyline(); - poly.startPath(0, 0); - poly.lineTo(100, 100); - poly.startPath(100, 100); - poly.lineTo(0, 100); - - boolean result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, - null, null); - assertTrue(result);// TO CONFIRM should be false - } - - @Test - public void testIsSimplePolylineDegenerateVertical() { - // 3 point test: degenerate vertical line - Polyline poly = new Polyline(); - poly.startPath(0, 0); - poly.lineTo(new Point(100, 100)); - poly.lineTo(new Point(100, 100)); - boolean result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, - null, null); - assertTrue(!result); - Polyline polyS; - result = simplifyOp.isSimpleAsFeature( - polyS = (Polyline) simplifyOp - .execute(poly, sr4326, false, null), sr4326, false, - null, null); - assertTrue(result); - assertTrue(polyS.getPointCount() == 2); - } - - @Test - public void testIsSimplePolylineEmptyPath() { - // TODO: any way to test this? - // Empty path - // Polyline poly = new Polyline(); - // assertTrue(poly.isEmpty()); - // poly.addPath(new Polyline(), 0, true); - // assertTrue(poly.isEmpty()); - // boolean result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, - // null, null); - // assertTrue(result); - } - - @Test - public void testIsSimplePolylineSinglePointInPath() { - // Single point in path - Polyline poly = new Polyline(); - poly.startPath(0, 0); - poly.lineTo(100, 100); - poly.removePoint(0, 1); - boolean result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, - null, null); - assertTrue(!result); - Polyline polyS; - result = simplifyOp.isSimpleAsFeature( - polyS = (Polyline) simplifyOp - .execute(poly, sr4326, false, null), sr4326, false, - null, null); - assertTrue(result); - assertTrue(polyS.isEmpty()); - } - - @Test - public void testIsSimplePolygon() { - Polygon poly = new Polygon(); - boolean result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, - null, null); - assertTrue(result);// empty is simple - result = simplifyOp.isSimpleAsFeature( - simplifyOp.execute(poly, sr4326, false, null), sr4326, false, - null, null); - assertTrue(result);// empty is simple - } - - @Test - public void testIsSimplePolygonEmptyPath() { - // TODO: - // Empty path - // Polygon poly = new Polygon(); - // poly.addPath(new Polyline(), 0, true); - // assertTrue(poly.getPathCount() == 1); - // boolean result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, - // null, - // null); - // assertTrue(result); - // result = simplifyOp.isSimpleAsFeature(simplifyOp.execute(poly, - // sr4326, false, null), sr4326, false, null, null); - // assertTrue(result);// empty is simple - // assertTrue(poly.getPathCount() == 1); - } - - @Test - public void testIsSimplePolygonIncomplete1() { - // Incomplete polygon 1 - Polygon poly = new Polygon(); - poly.startPath(0, 0); - poly.lineTo(100, 100); - // poly.removePoint(0, 1);//TO CONFIRM no removePoint method in Java - boolean result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, - null, null); - assertTrue(!result); - } - - @Test - public void testIsSimplePolygonIncomplete2() { - // Incomplete polygon 2 - Polygon poly = new Polygon(); - poly.startPath(0, 0); - poly.lineTo(100, 100); - boolean result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, - null, null); - assertTrue(!result); - } - - @Test - public void testIsSimplePolygonDegenerateTriangle() { - // Degenerate triangle (self overlap) - Polygon poly = new Polygon(); - poly.startPath(0, 0); - poly.lineTo(100, 100); - poly.lineTo(0, 0); - boolean result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, - null, null); - assertTrue(!result); - } - - @Test - public void testIsSimplePolygonSelfIntersect() { - // Self intersection - cracking is needed - Polygon poly = new Polygon(); - poly.startPath(0, 0); - poly.lineTo(100, 100); - poly.lineTo(0, 100); - poly.lineTo(100, 0); - boolean result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, - null, null); - assertTrue(!result); - } - - @Test - public void testIsSimplePolygonRectangleHole() { - // Rectangle and rectangular hole that has one segment overlapping - // with the with the exterior ring. Cracking is needed. - Polygon poly = new Polygon(); - poly.addEnvelope(new Envelope(-200, -100, 200, 100), false); - poly.addEnvelope(new Envelope(-100, -100, 100, 50), true); - boolean result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, - null, null); - assertTrue(!result); - poly.reverseAllPaths(); - result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, null, null); - assertTrue(!result); - } - - @Test - public void testIsSimplePolygonRectangleHole2() { - // Rectangle and rectangular hole - Polygon poly = new Polygon(); - poly.addEnvelope(new Envelope(-200, -100, 200, 100), false); - poly.addEnvelope(new Envelope(-100, -50, 100, 50), true); - boolean result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, - null, null); - assertTrue(result); - poly.reverseAllPaths(); - result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, null, null); - assertTrue(!result); - } - - @Test - public void testIsSimplePolygonSelfIntersectAtVertex() { - // Self intersection at vertex - Polygon poly = new Polygon(); - poly.startPath(0, 0); - poly.lineTo(50, 50); - poly.lineTo(100, 100); - poly.lineTo(0, 100); - poly.lineTo(50, 50); - poly.lineTo(100, 0); - boolean result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, - null, null); - assertTrue(!result); - result = simplifyOp.isSimpleAsFeature( - simplifyOp.execute(poly, sr4326, false, null), sr4326, false, - null, null); - assertTrue(result); - } - - @Test - public void testIsSimplePolygon_2EdgesTouchAtVertex() { - // No self-intersection, but more than two edges touch at the same - // vertex. Simple for ArcGIS, not simple for OGC - Polygon poly = new Polygon(); - poly.startPath(0, 0); - poly.lineTo(50, 50); - poly.lineTo(0, 100); - poly.lineTo(100, 100); - poly.lineTo(50, 50); - poly.lineTo(100, 0); - boolean result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, - null, null); - assertTrue(result); - } - - @Test - public void testIsSimplePolygonTriangle() { - // Triangle - Polygon poly = new Polygon(); - poly.startPath(0, 0); - poly.lineTo(100, 100); - poly.lineTo(100, 0); - boolean result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, - null, null); - assertTrue(result); - poly.reverseAllPaths(); - result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, null, null); - assertTrue(!result); - } - - @Test - public void testIsSimplePolygonRectangle() { - // Rectangle - Polygon poly = new Polygon(); - poly.addEnvelope(new Envelope(-200, -100, 100, 200), false); - boolean result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, - null, null); - assertTrue(result); - poly.reverseAllPaths(); - result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, null, null); - assertTrue(!result); - } - - @Test - public void testIsSimplePolygonRectangleHoleWrongDirection() { - // Rectangle and rectangular hole that has wrong direction - Polygon poly = new Polygon(); - poly.addEnvelope(new Envelope(-200, -100, 200, 100), false); - poly.addEnvelope(new Envelope(-100, -50, 100, 50), false); - boolean result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, - null, null); - assertTrue(!result); - poly.reverseAllPaths(); - result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, null, null); - assertTrue(!result); - } - - @Test - public void testIsSimplePolygon_2RectanglesSideBySide() { - // Two rectangles side by side, simple - Polygon poly = new Polygon(); - poly.addEnvelope(new Envelope(-200, -100, 200, 100), false); - poly.addEnvelope(new Envelope(220, -50, 300, 50), false); - boolean result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, - null, null); - assertTrue(result); - poly.reverseAllPaths(); - result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, null, null); - assertTrue(!result); - } - - @Test - public void testIsSimplePolygonRectangleOneBelow() { - // Two rectangles one below another, simple - Polygon poly = new Polygon(); - poly.addEnvelope(new Envelope(50, 50, 100, 100), false); - poly.addEnvelope(new Envelope(50, 200, 100, 250), false); - boolean result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, - null, null); - assertTrue(result); - poly.reverseAllPaths(); - result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, null, null); - assertTrue(!result); - } - - @Test - public void testisSimpleOGC() { - Polyline poly = new Polyline(); - poly.startPath(0, 0); - poly.lineTo(10, 0); - boolean result = simplifyOpOGC.isSimpleOGC(poly, sr4326, true, null, - null); - assertTrue(result); - - poly = new Polyline(); - poly.startPath(0, 0); - poly.lineTo(10, 10); - poly.lineTo(0, 10); - poly.lineTo(10, 0); - NonSimpleResult nsr = new NonSimpleResult(); - result = simplifyOpOGC.isSimpleOGC(poly, sr4326, true, nsr, null); - assertTrue(!result); - assertTrue(nsr.m_reason == NonSimpleResult.Reason.Cracking); - - MultiPoint mp = new MultiPoint(); - mp.add(0, 0); - mp.add(10, 0); - result = simplifyOpOGC.isSimpleOGC(mp, sr4326, true, null, null); - assertTrue(result); - - mp = new MultiPoint(); - mp.add(10, 0); - mp.add(10, 0); - nsr = new NonSimpleResult(); - result = simplifyOpOGC.isSimpleOGC(mp, sr4326, true, nsr, null); - assertTrue(!result); - assertTrue(nsr.m_reason == NonSimpleResult.Reason.Clustering); - } - - @Test - public void testPolylineIsSimpleForOGC() { - OperatorImportFromJson importerJson = (OperatorImportFromJson) factory - .getOperator(Operator.Type.ImportFromJson); - OperatorSimplify simplify = (OperatorSimplify) factory - .getOperator(Operator.Type.Simplify); - - { - String s = "{\"paths\":[[[0, 10], [8, 5], [5, 2], [6, 0]]]}"; - Geometry g = importerJson.execute(Geometry.Type.Unknown, - JsonParserReader.createFromString(s)).getGeometry(); - boolean res = simplifyOpOGC.isSimpleOGC(g, null, true, null, null); - assertTrue(res); - } - { - String s = "{\"paths\":[[[0, 10], [6, 0], [7, 5], [0, 3]]]}";// self - // intersection - Geometry g = importerJson.execute(Geometry.Type.Unknown, - JsonParserReader.createFromString(s)).getGeometry(); - boolean res = simplifyOpOGC.isSimpleOGC(g, null, true, null, null); - assertTrue(!res); - } - - { - String s = "{\"paths\":[[[0, 10], [6, 0], [0, 3], [0, 10]]]}"; // closed - Geometry g = importerJson.execute(Geometry.Type.Unknown, - JsonParserReader.createFromString(s)).getGeometry(); - boolean res = simplifyOpOGC.isSimpleOGC(g, null, true, null, null); - assertTrue(res); - } - - { - String s = "{\"paths\":[[[0, 10], [5, 5], [6, 0], [0, 3], [5, 5], [0, 9], [0, 10]]]}"; // closed - // with - // self - // tangent - Geometry g = importerJson.execute(Geometry.Type.Unknown, - JsonParserReader.createFromString(s)).getGeometry(); - boolean res = simplifyOpOGC.isSimpleOGC(g, null, true, null, null); - assertTrue(!res); - } - - { - String s = "{\"paths\":[[[0, 10], [5, 2]], [[5, 2], [6, 0]]]}";// two - // paths - // connected - // at - // a - // point - Geometry g = importerJson.execute(Geometry.Type.Unknown, - JsonParserReader.createFromString(s)).getGeometry(); - boolean res = simplifyOpOGC.isSimpleOGC(g, null, true, null, null); - assertTrue(res); - } - - { - String s = "{\"paths\":[[[0, 0], [3, 3], [5, 0], [0, 0]], [[0, 10], [3, 3], [10, 10], [0, 10]]]}";// two - // closed - // rings - // touch - // at - // one - // point - Geometry g = importerJson.execute(Geometry.Type.Unknown, - JsonParserReader.createFromString(s)).getGeometry(); - boolean res = simplifyOpOGC.isSimpleOGC(g, null, true, null, null); - assertTrue(!res); - } - - { - String s = "{\"paths\":[[[0, 0], [10, 10]], [[0, 10], [10, 0]]]}";// two - // lines - // intersect - Geometry g = importerJson.execute(Geometry.Type.Unknown, - JsonParserReader.createFromString(s)).getGeometry(); - boolean res = simplifyOpOGC.isSimpleOGC(g, null, true, null, null); - assertTrue(!res); - } - - { - String s = "{\"paths\":[[[0, 0], [5, 5], [0, 10]], [[10, 10], [5, 5], [10, 0]]]}";// two - // paths - // share - // mid - // point. - Geometry g = importerJson.execute(Geometry.Type.Unknown, - JsonParserReader.createFromString(s)).getGeometry(); - boolean res = simplifyOpOGC.isSimpleOGC(g, null, true, null, null); - assertTrue(!res); - } - - } - - @Test - public void testFillRule() { - //self intersecting star shape - MapGeometry mg = OperatorImportFromJson.local().execute(Geometry.Type.Unknown, "{\"rings\":[[[0,0], [5,10], [10, 0], [0, 7], [10, 7], [0, 0]]]}"); - Polygon poly = (Polygon) mg.getGeometry(); - assertTrue(poly.getFillRule() == Polygon.FillRule.enumFillRuleOddEven); - poly.setFillRule(Polygon.FillRule.enumFillRuleWinding); - assertTrue(poly.getFillRule() == Polygon.FillRule.enumFillRuleWinding); - Geometry simpleResult = OperatorSimplify.local().execute(poly, null, true, null); - assertTrue(((Polygon) simpleResult).getFillRule() == Polygon.FillRule.enumFillRuleOddEven); - //solid start without holes: - MapGeometry mg1 = OperatorImportFromJson.local().execute(Geometry.Type.Unknown, "{\"rings\":[[[0,0],[2.5925925925925926,5.185185185185185],[0,7],[3.5,7],[5,10],[6.5,7],[10,7],[7.407407407407407,5.185185185185185],[10,0],[5,3.5],[0,0]]]}"); - boolean equals = OperatorEquals.local().execute(mg1.getGeometry(), simpleResult, null, null); - assertTrue(equals); - } + public Polygon makeNonSimplePolygon3() { + Polygon poly = new Polygon(); + poly.startPath(0, 0); + poly.lineTo(0, 25); + poly.lineTo(35, 25); + poly.lineTo(35, 0); + + poly.startPath(5, 5); + poly.lineTo(5, 15); + poly.lineTo(10, 15); + poly.lineTo(10, 5); + + poly.startPath(40, 0); + poly.lineTo(45, 0); + poly.lineTo(45, 5); + poly.lineTo(40, 5); + + poly.startPath(20, 10); + poly.lineTo(25, 10); + poly.lineTo(25, 15); + poly.lineTo(20, 15); + + poly.startPath(15, 5); + poly.lineTo(15, 20); + poly.lineTo(30, 20); + poly.lineTo(30, 5); + + return poly; + }// done + + public Polygon makeNonSimplePolygon4() { + Polygon poly = new Polygon(); + poly.startPath(0, 0); + poly.lineTo(0, 10); + poly.lineTo(10, 10); + poly.lineTo(10, 0); + poly.lineTo(5, 0); + poly.lineTo(5, 5); + poly.lineTo(5, 0); + + return poly; + }// done + + public Polygon makeNonSimplePolygon6() { + Polygon poly = new Polygon(); + poly.startPath(35.34407570857744, 54.00551247713412); + poly.lineTo(41.07663499357954, 20.0); + poly.lineTo(40.66372033705177, 26.217432321849017); + + poly.startPath(42.81936574509338, 20.0); + poly.lineTo(43.58226670584747, 20.0); + poly.lineTo(39.29611825817084, 22.64634933678729); + poly.lineTo(44.369873312241346, 25.81893670527215); + poly.lineTo(42.68845660737179, 20.0); + poly.lineTo(38.569549792944244, 56.47456192829393); + poly.lineTo(42.79274114188401, 45.45117792578003); + poly.lineTo(41.09512147544657, 70.0); + + return poly; + } + + public Polygon makeNonSimplePolygon7() { + Polygon poly = new Polygon(); + + poly.startPath(41.987895433319686, 53.75822619011542); + poly.lineTo(41.98789542535497, 53.75822618803151); + poly.lineTo(40.15120412113667, 68.12604154722113); + poly.lineTo(37.72272697311022, 67.92767094118877); + poly.lineTo(37.147347454283086, 49.497473094145505); + poly.lineTo(38.636627026664385, 51.036687142232736); + + poly.startPath(39.00920080789793, 62.063425518369016); + poly.lineTo(38.604912643136885, 70.0); + poly.lineTo(40.71826863485308, 43.60337143116787); + poly.lineTo(35.34407570857744, 54.005512477134126); + poly.lineTo(39.29611825817084, 22.64634933678729); + + return poly; + } + + public Polyline makeNonSimplePolyline() { + // This polyline has a short segment + Polyline poly = new Polyline(); + poly.startPath(0, 0); + poly.lineTo(10, 0); + poly.lineTo(10, 10); + poly.lineTo(10, 5); + poly.lineTo(-5, 5); + + return poly; + }// done + + @Test + public void testIsSimpleBasicsPoint() { + boolean result; + // point is always simple + Point pt = new Point(); + result = simplifyOp.isSimpleAsFeature(pt, sr4326, false, null, null); + assertTrue(result); + pt.setXY(0, 0); + result = simplifyOp.isSimpleAsFeature(pt, sr4326, false, null, null); + assertTrue(result); + pt.setXY(100000, 10000); + result = simplifyOp.isSimpleAsFeature(pt, sr4326, false, null, null); + assertTrue(result); + }// done + + @Test + public void testIsSimpleBasicsEnvelope() { + // Envelope is simple, when it's width and height are not degenerate + Envelope env = new Envelope(); + boolean result = simplifyOp.isSimpleAsFeature(env, sr4326, false, null, + null); // Empty is simple + assertTrue(result); + env.setCoords(0, 0, 10, 10); + result = simplifyOp.isSimpleAsFeature(env, sr4326, false, null, null); + assertTrue(result); + // sliver but still simple + env.setCoords(0, 0, 0 + sr4326.getTolerance() * 2, 10); + result = simplifyOp.isSimpleAsFeature(env, sr4326, false, null, null); + assertTrue(result); + // sliver and not simple + env.setCoords(0, 0, 0 + sr4326.getTolerance() * 0.5, 10); + result = simplifyOp.isSimpleAsFeature(env, sr4326, false, null, null); + assertTrue(!result); + }// done + + @Test + public void testIsSimpleBasicsLine() { + Line line = new Line(); + boolean result = simplifyOp.isSimpleAsFeature(line, sr4326, false, + null, null); + assertTrue(!result); + + line.setStart(new Point(0, 0)); + // line.setEndXY(0, 0); + result = simplifyOp.isSimpleAsFeature(line, sr4326, false, null, null); + assertTrue(!result); + line.setEnd(new Point(1, 0)); + result = simplifyOp.isSimpleAsFeature(line, sr4326, false, null, null); + assertTrue(result); + }// done + + @Test + public void testIsSimpleMultiPoint1() { + MultiPoint mp = new MultiPoint(); + boolean result = simplifyOp.isSimpleAsFeature(mp, sr4326, false, null, + null); + assertTrue(result);// empty is simple + result = simplifyOp.isSimpleAsFeature( + simplifyOp.execute(mp, sr4326, false, null), sr4326, false, + null, null); + assertTrue(result); + }// done + + @Test + public void testIsSimpleMultiPoint2FarApart() { + // Two point test: far apart + MultiPoint mp = new MultiPoint(); + mp.add(20, 10); + mp.add(100, 100); + boolean result = simplifyOp.isSimpleAsFeature(mp, sr4326, false, null, + null); + assertTrue(result); + result = simplifyOp.isSimpleAsFeature( + simplifyOp.execute(mp, sr4326, false, null), sr4326, false, + null, null); + assertTrue(result); + assertTrue(mp.getPointCount() == 2); + }// done + + @Test + public void testIsSimpleMultiPointCoincident() { + // Two point test: coincident + MultiPoint mp = new MultiPoint(); + mp.add(100, 100); + mp.add(100, 100); + boolean result = simplifyOp.isSimpleAsFeature(mp, sr4326, false, null, + null); + assertTrue(!result); + MultiPoint mpS; + result = simplifyOp.isSimpleAsFeature( + mpS = (MultiPoint) simplifyOp.execute(mp, sr4326, false, null), + sr4326, false, null, null); + assertTrue(result); + assertTrue(mpS.getPointCount() == 1); + }// done + + @Test + public void testMultiPointSR4326_CR184439() { + OperatorFactoryLocal engine = OperatorFactoryLocal.getInstance(); + OperatorSimplify simpOp = (OperatorSimplify) engine + .getOperator(Operator.Type.Simplify); + NonSimpleResult nonSimpResult = new NonSimpleResult(); + nonSimpResult.m_reason = NonSimpleResult.Reason.NotDetermined; + MultiPoint multiPoint = new MultiPoint(); + multiPoint.add(0, 0); + multiPoint.add(0, 1); + multiPoint.add(0, 0); + Boolean multiPointIsSimple = simpOp.isSimpleAsFeature(multiPoint, + SpatialReference.create(4326), true, nonSimpResult, null); + assertFalse(multiPointIsSimple); + assertTrue(nonSimpResult.m_reason == NonSimpleResult.Reason.Clustering); + assertTrue(nonSimpResult.m_vertexIndex1 == 0); + assertTrue(nonSimpResult.m_vertexIndex2 == 2); + } + + @Test + public void testIsSimpleMultiPointCloserThanTolerance() { + // Two point test: closer than tolerance + MultiPoint mp = new MultiPoint(); + MultiPoint mpS; + mp.add(100, 100); + mp.add(100, 100 + sr4326.getTolerance() * .5); + boolean result = simplifyOp.isSimpleAsFeature(mp, sr4326, false, null, + null); + assertTrue(result); + result = simplifyOp.isSimpleAsFeature( + mpS = (MultiPoint) simplifyOp.execute(mp, sr4326, false, null), + sr4326, false, null, null); + assertTrue(result); + assertTrue(mpS.getPointCount() == 2); + }// done + + @Test + public void testIsSimpleMultiPointFarApart2() { + // 5 point test: far apart + MultiPoint mp = new MultiPoint(); + mp.add(100, 100); + mp.add(100, 101); + mp.add(101, 101); + mp.add(11, 1); + mp.add(11, 14); + MultiPoint mpS; + boolean result = simplifyOp.isSimpleAsFeature(mp, sr4326, false, null, + null); + assertTrue(result); + result = simplifyOp.isSimpleAsFeature( + mpS = (MultiPoint) simplifyOp.execute(mp, sr4326, false, null), + sr4326, false, null, null); + assertTrue(result); + assertTrue(mpS.getPointCount() == 5); + }// done + + @Test + public void testIsSimpleMultiPoint_coincident2() { + // 5 point test: coincident + MultiPoint mp = new MultiPoint(); + mp.add(100, 100); + mp.add(100, 101); + mp.add(100, 100); + mp.add(11, 1); + mp.add(11, 14); + boolean result = simplifyOp.isSimpleAsFeature(mp, sr4326, false, null, + null); + assertTrue(!result); + MultiPoint mpS; + result = simplifyOp.isSimpleAsFeature( + mpS = (MultiPoint) simplifyOp.execute(mp, sr4326, false, null), + sr4326, false, null, null); + assertTrue(result); + assertTrue(mpS.getPointCount() == 4); + assertEquals(mpS.getPoint(0).getX(), 100, 1e-7); + assertEquals(mpS.getPoint(0).getY(), 100, 1e-7); + assertEquals(mpS.getPoint(1).getX(), 100, 1e-7); + assertEquals(mpS.getPoint(1).getY(), 101, 1e-7); + assertEquals(mpS.getPoint(2).getX(), 11, 1e-7); + assertEquals(mpS.getPoint(2).getY(), 1, 1e-7); + assertEquals(mpS.getPoint(3).getX(), 11, 1e-7); + assertEquals(mpS.getPoint(3).getY(), 14, 1e-7); + }// done + + @Test + public void testIsSimpleMultiPointCloserThanTolerance2() { + // 5 point test: closer than tolerance + MultiPoint mp = new MultiPoint(); + mp.add(100, 100); + mp.add(100, 101); + mp.add(100, 100 + sr4326.getTolerance() / 2); + mp.add(11, 1); + mp.add(11, 14); + MultiPoint mpS; + boolean result = simplifyOp.isSimpleAsFeature(mp, sr4326, false, null, + null); + assertTrue(result); + result = simplifyOp.isSimpleAsFeature( + mpS = (MultiPoint) simplifyOp.execute(mp, sr4326, false, null), + sr4326, false, null, null); + assertTrue(result); + assertTrue(mpS.getPointCount() == 5); + }// done + + @Test + public void testIsSimplePolyline() { + Polyline poly = new Polyline(); + boolean result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, + null, null); + assertTrue(result);// empty is simple + } + + @Test + public void testIsSimplePolylineFarApart() { + // Two point test: far apart + Polyline poly = new Polyline(); + poly.startPath(20, 10); + poly.lineTo(100, 100); + boolean result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, + null, null); + assertTrue(result); + } + + @Test + public void testIsSimplePolylineCoincident() { + // Two point test: coincident + Polyline poly = new Polyline(); + poly.startPath(100, 100); + poly.lineTo(100, 100); + boolean result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, + null, null); + assertTrue(!result); + @SuppressWarnings("unused") + Polyline polyS; + result = simplifyOp.isSimpleAsFeature( + polyS = (Polyline) simplifyOp + .execute(poly, sr4326, false, null), sr4326, false, + null, null); + assertTrue(result); + } + + @Test + public void testIsSimplePolylineCloserThanTolerance() { + // Two point test: closer than tolerance + Polyline poly = new Polyline(); + poly.startPath(100, 100); + poly.lineTo(100, 100 + sr4326.getTolerance() / 2); + boolean result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, + null, null); + assertTrue(!result); + @SuppressWarnings("unused") + Polyline polyS; + result = simplifyOp.isSimpleAsFeature( + polyS = (Polyline) simplifyOp + .execute(poly, sr4326, false, null), sr4326, false, + null, null); + assertTrue(result); + } + + @Test + public void testIsSimplePolylineFarApartSelfOverlap0() { + // 3 point test: far apart, self overlapping + Polyline poly = new Polyline(); + poly.startPath(0, 0); + poly.lineTo(100, 100); + poly.lineTo(0, 0); + boolean result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, + null, null); + assertTrue(result);// TO CONFIRM should be false + } + + @Test + public void testIsSimplePolylineSelfIntersect() { + // 4 point test: far apart, self intersecting + Polyline poly = new Polyline(); + poly.startPath(0, 0); + poly.lineTo(100, 100); + poly.lineTo(0, 100); + poly.lineTo(100, 0); + boolean result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, + null, null); + assertTrue(result);// TO CONFIRM should be false + } + + @Test + public void testIsSimplePolylineDegenerateSegment() { + // 4 point test: degenerate segment + Polyline poly = new Polyline(); + poly.startPath(0, 0); + poly.lineTo(100, 100); + poly.lineTo(100, 100 + sr4326.getTolerance() / 2); + poly.lineTo(100, 0); + boolean result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, + null, null); + assertTrue(!result); + @SuppressWarnings("unused") + Polyline polyS; + result = simplifyOp.isSimpleAsFeature( + polyS = (Polyline) simplifyOp + .execute(poly, sr4326, false, null), sr4326, false, + null, null); + assertTrue(result); + { + Polyline other = new Polyline(); + other.startPath(0, 0); + other.lineTo(100, 100); + other.lineTo(100, 0); + other.equals(poly); + } + } + + @Test + public void testIsSimplePolylineFarApartSelfOverlap() { + // 3 point test: far apart, self overlapping + Polyline poly = new Polyline(); + poly.startPath(0, 0); + poly.lineTo(100, 100); + poly.lineTo(0, 0); + boolean result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, + null, null); + assertTrue(result);// TO CONFIRM should be false + } + + @Test + public void testIsSimplePolylineFarApartIntersect() { + // 4 point 2 parts test: far apart, intersecting parts + Polyline poly = new Polyline(); + poly.startPath(0, 0); + poly.lineTo(100, 100); + poly.startPath(100, 0); + poly.lineTo(0, 100); + + boolean result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, + null, null); + assertTrue(result);// TO CONFIRM should be false + } + + @Test + public void testIsSimplePolylineFarApartOverlap2() { + // 4 point 2 parts test: far apart, overlapping parts. second part + // starts where first one ends + Polyline poly = new Polyline(); + poly.startPath(0, 0); + poly.lineTo(100, 100); + poly.startPath(100, 100); + poly.lineTo(0, 100); + + boolean result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, + null, null); + assertTrue(result);// TO CONFIRM should be false + } + + @Test + public void testIsSimplePolylineDegenerateVertical() { + // 3 point test: degenerate vertical line + Polyline poly = new Polyline(); + poly.startPath(0, 0); + poly.lineTo(new Point(100, 100)); + poly.lineTo(new Point(100, 100)); + boolean result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, + null, null); + assertTrue(!result); + Polyline polyS; + result = simplifyOp.isSimpleAsFeature( + polyS = (Polyline) simplifyOp + .execute(poly, sr4326, false, null), sr4326, false, + null, null); + assertTrue(result); + assertTrue(polyS.getPointCount() == 2); + } + + @Test + public void testIsSimplePolylineEmptyPath() { + // TODO: any way to test this? + // Empty path + // Polyline poly = new Polyline(); + // assertTrue(poly.isEmpty()); + // poly.addPath(new Polyline(), 0, true); + // assertTrue(poly.isEmpty()); + // boolean result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, + // null, null); + // assertTrue(result); + } + + @Test + public void testIsSimplePolylineSinglePointInPath() { + // Single point in path + Polyline poly = new Polyline(); + poly.startPath(0, 0); + poly.lineTo(100, 100); + poly.removePoint(0, 1); + boolean result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, + null, null); + assertTrue(!result); + Polyline polyS; + result = simplifyOp.isSimpleAsFeature( + polyS = (Polyline) simplifyOp + .execute(poly, sr4326, false, null), sr4326, false, + null, null); + assertTrue(result); + assertTrue(polyS.isEmpty()); + } + + @Test + public void testIsSimplePolygon() { + Polygon poly = new Polygon(); + boolean result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, + null, null); + assertTrue(result);// empty is simple + result = simplifyOp.isSimpleAsFeature( + simplifyOp.execute(poly, sr4326, false, null), sr4326, false, + null, null); + assertTrue(result);// empty is simple + } + + @Test + public void testIsSimplePolygonEmptyPath() { + // TODO: + // Empty path + // Polygon poly = new Polygon(); + // poly.addPath(new Polyline(), 0, true); + // assertTrue(poly.getPathCount() == 1); + // boolean result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, + // null, + // null); + // assertTrue(result); + // result = simplifyOp.isSimpleAsFeature(simplifyOp.execute(poly, + // sr4326, false, null), sr4326, false, null, null); + // assertTrue(result);// empty is simple + // assertTrue(poly.getPathCount() == 1); + } + + @Test + public void testIsSimplePolygonIncomplete1() { + // Incomplete polygon 1 + Polygon poly = new Polygon(); + poly.startPath(0, 0); + poly.lineTo(100, 100); + // poly.removePoint(0, 1);//TO CONFIRM no removePoint method in Java + boolean result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, + null, null); + assertTrue(!result); + } + + @Test + public void testIsSimplePolygonIncomplete2() { + // Incomplete polygon 2 + Polygon poly = new Polygon(); + poly.startPath(0, 0); + poly.lineTo(100, 100); + boolean result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, + null, null); + assertTrue(!result); + } + + @Test + public void testIsSimplePolygonDegenerateTriangle() { + // Degenerate triangle (self overlap) + Polygon poly = new Polygon(); + poly.startPath(0, 0); + poly.lineTo(100, 100); + poly.lineTo(0, 0); + boolean result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, + null, null); + assertTrue(!result); + } + + @Test + public void testIsSimplePolygonSelfIntersect() { + // Self intersection - cracking is needed + Polygon poly = new Polygon(); + poly.startPath(0, 0); + poly.lineTo(100, 100); + poly.lineTo(0, 100); + poly.lineTo(100, 0); + boolean result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, + null, null); + assertTrue(!result); + } + + @Test + public void testIsSimplePolygonRectangleHole() { + // Rectangle and rectangular hole that has one segment overlapping + // with the with the exterior ring. Cracking is needed. + Polygon poly = new Polygon(); + poly.addEnvelope(new Envelope(-200, -100, 200, 100), false); + poly.addEnvelope(new Envelope(-100, -100, 100, 50), true); + boolean result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, + null, null); + assertTrue(!result); + poly.reverseAllPaths(); + result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, null, null); + assertTrue(!result); + } + + @Test + public void testIsSimplePolygonRectangleHole2() { + // Rectangle and rectangular hole + Polygon poly = new Polygon(); + poly.addEnvelope(new Envelope(-200, -100, 200, 100), false); + poly.addEnvelope(new Envelope(-100, -50, 100, 50), true); + boolean result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, + null, null); + assertTrue(result); + poly.reverseAllPaths(); + result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, null, null); + assertTrue(!result); + } + + @Test + public void testIsSimplePolygonSelfIntersectAtVertex() { + // Self intersection at vertex + Polygon poly = new Polygon(); + poly.startPath(0, 0); + poly.lineTo(50, 50); + poly.lineTo(100, 100); + poly.lineTo(0, 100); + poly.lineTo(50, 50); + poly.lineTo(100, 0); + boolean result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, + null, null); + assertTrue(!result); + result = simplifyOp.isSimpleAsFeature( + simplifyOp.execute(poly, sr4326, false, null), sr4326, false, + null, null); + assertTrue(result); + } + + @Test + public void testIsSimplePolygon_2EdgesTouchAtVertex() { + // No self-intersection, but more than two edges touch at the same + // vertex. Simple for ArcGIS, not simple for OGC + Polygon poly = new Polygon(); + poly.startPath(0, 0); + poly.lineTo(50, 50); + poly.lineTo(0, 100); + poly.lineTo(100, 100); + poly.lineTo(50, 50); + poly.lineTo(100, 0); + boolean result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, + null, null); + assertTrue(result); + } + + @Test + public void testIsSimplePolygonTriangle() { + // Triangle + Polygon poly = new Polygon(); + poly.startPath(0, 0); + poly.lineTo(100, 100); + poly.lineTo(100, 0); + boolean result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, + null, null); + assertTrue(result); + poly.reverseAllPaths(); + result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, null, null); + assertTrue(!result); + } + + @Test + public void testIsSimplePolygonRectangle() { + // Rectangle + Polygon poly = new Polygon(); + poly.addEnvelope(new Envelope(-200, -100, 100, 200), false); + boolean result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, + null, null); + assertTrue(result); + poly.reverseAllPaths(); + result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, null, null); + assertTrue(!result); + } + + @Test + public void testIsSimplePolygonRectangleHoleWrongDirection() { + // Rectangle and rectangular hole that has wrong direction + Polygon poly = new Polygon(); + poly.addEnvelope(new Envelope(-200, -100, 200, 100), false); + poly.addEnvelope(new Envelope(-100, -50, 100, 50), false); + boolean result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, + null, null); + assertTrue(!result); + poly.reverseAllPaths(); + result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, null, null); + assertTrue(!result); + } + + @Test + public void testIsSimplePolygon_2RectanglesSideBySide() { + // Two rectangles side by side, simple + Polygon poly = new Polygon(); + poly.addEnvelope(new Envelope(-200, -100, 200, 100), false); + poly.addEnvelope(new Envelope(220, -50, 300, 50), false); + boolean result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, + null, null); + assertTrue(result); + poly.reverseAllPaths(); + result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, null, null); + assertTrue(!result); + } + + @Test + public void testIsSimplePolygonRectangleOneBelow() { + // Two rectangles one below another, simple + Polygon poly = new Polygon(); + poly.addEnvelope(new Envelope(50, 50, 100, 100), false); + poly.addEnvelope(new Envelope(50, 200, 100, 250), false); + boolean result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, + null, null); + assertTrue(result); + poly.reverseAllPaths(); + result = simplifyOp.isSimpleAsFeature(poly, sr4326, false, null, null); + assertTrue(!result); + } + + @Test + public void testisSimpleOGC() { + Polyline poly = new Polyline(); + poly.startPath(0, 0); + poly.lineTo(10, 0); + boolean result = simplifyOpOGC.isSimpleOGC(poly, sr4326, true, null, + null); + assertTrue(result); + + poly = new Polyline(); + poly.startPath(0, 0); + poly.lineTo(10, 10); + poly.lineTo(0, 10); + poly.lineTo(10, 0); + NonSimpleResult nsr = new NonSimpleResult(); + result = simplifyOpOGC.isSimpleOGC(poly, sr4326, true, nsr, null); + assertTrue(!result); + assertTrue(nsr.m_reason == NonSimpleResult.Reason.Cracking); + + MultiPoint mp = new MultiPoint(); + mp.add(0, 0); + mp.add(10, 0); + result = simplifyOpOGC.isSimpleOGC(mp, sr4326, true, null, null); + assertTrue(result); + + mp = new MultiPoint(); + mp.add(10, 0); + mp.add(10, 0); + nsr = new NonSimpleResult(); + result = simplifyOpOGC.isSimpleOGC(mp, sr4326, true, nsr, null); + assertTrue(!result); + assertTrue(nsr.m_reason == NonSimpleResult.Reason.Clustering); + } + + @Test + public void testPolylineIsSimpleForOGC() { + OperatorImportFromJson importerJson = (OperatorImportFromJson) factory + .getOperator(Operator.Type.ImportFromJson); + OperatorSimplify simplify = (OperatorSimplify) factory + .getOperator(Operator.Type.Simplify); + + { + String s = "{\"paths\":[[[0, 10], [8, 5], [5, 2], [6, 0]]]}"; + Geometry g = importerJson.execute(Geometry.Type.Unknown, + JsonParserReader.createFromString(s)).getGeometry(); + boolean res = simplifyOpOGC.isSimpleOGC(g, null, true, null, null); + assertTrue(res); + } + { + String s = "{\"paths\":[[[0, 10], [6, 0], [7, 5], [0, 3]]]}";// self + // intersection + Geometry g = importerJson.execute(Geometry.Type.Unknown, + JsonParserReader.createFromString(s)).getGeometry(); + boolean res = simplifyOpOGC.isSimpleOGC(g, null, true, null, null); + assertTrue(!res); + } + + { + String s = "{\"paths\":[[[0, 10], [6, 0], [0, 3], [0, 10]]]}"; // closed + Geometry g = importerJson.execute(Geometry.Type.Unknown, + JsonParserReader.createFromString(s)).getGeometry(); + boolean res = simplifyOpOGC.isSimpleOGC(g, null, true, null, null); + assertTrue(res); + } + + { + String s = "{\"paths\":[[[0, 10], [5, 5], [6, 0], [0, 3], [5, 5], [0, 9], [0, 10]]]}"; // closed + // with + // self + // tangent + Geometry g = importerJson.execute(Geometry.Type.Unknown, + JsonParserReader.createFromString(s)).getGeometry(); + boolean res = simplifyOpOGC.isSimpleOGC(g, null, true, null, null); + assertTrue(!res); + } + + { + String s = "{\"paths\":[[[0, 10], [5, 2]], [[5, 2], [6, 0]]]}";// two + // paths + // connected + // at + // a + // point + Geometry g = importerJson.execute(Geometry.Type.Unknown, + JsonParserReader.createFromString(s)).getGeometry(); + boolean res = simplifyOpOGC.isSimpleOGC(g, null, true, null, null); + assertTrue(res); + } + + { + String s = "{\"paths\":[[[0, 0], [3, 3], [5, 0], [0, 0]], [[0, 10], [3, 3], [10, 10], [0, 10]]]}";// two + // closed + // rings + // touch + // at + // one + // point + Geometry g = importerJson.execute(Geometry.Type.Unknown, + JsonParserReader.createFromString(s)).getGeometry(); + boolean res = simplifyOpOGC.isSimpleOGC(g, null, true, null, null); + assertTrue(!res); + } + + { + String s = "{\"paths\":[[[0, 0], [10, 10]], [[0, 10], [10, 0]]]}";// two + // lines + // intersect + Geometry g = importerJson.execute(Geometry.Type.Unknown, + JsonParserReader.createFromString(s)).getGeometry(); + boolean res = simplifyOpOGC.isSimpleOGC(g, null, true, null, null); + assertTrue(!res); + } + + { + String s = "{\"paths\":[[[0, 0], [5, 5], [0, 10]], [[10, 10], [5, 5], [10, 0]]]}";// two + // paths + // share + // mid + // point. + Geometry g = importerJson.execute(Geometry.Type.Unknown, + JsonParserReader.createFromString(s)).getGeometry(); + boolean res = simplifyOpOGC.isSimpleOGC(g, null, true, null, null); + assertTrue(!res); + } + + } + + @Test + public void testFillRule() { + //self intersecting star shape + MapGeometry mg = OperatorImportFromJson.local().execute(Geometry.Type.Unknown, "{\"rings\":[[[0,0], [5,10], [10, 0], [0, 7], [10, 7], [0, 0]]]}"); + Polygon poly = (Polygon) mg.getGeometry(); + assertTrue(poly.getFillRule() == Polygon.FillRule.enumFillRuleOddEven); + poly.setFillRule(Polygon.FillRule.enumFillRuleWinding); + assertTrue(poly.getFillRule() == Polygon.FillRule.enumFillRuleWinding); + Geometry simpleResult = OperatorSimplify.local().execute(poly, null, true, null); + assertTrue(((Polygon) simpleResult).getFillRule() == Polygon.FillRule.enumFillRuleOddEven); + //solid start without holes: + MapGeometry mg1 = OperatorImportFromJson.local().execute(Geometry.Type.Unknown, "{\"rings\":[[[0,0],[2.5925925925925926,5.185185185185185],[0,7],[3.5,7],[5,10],[6.5,7],[10,7],[7.407407407407407,5.185185185185185],[10,0],[5,3.5],[0,0]]]}"); + boolean equals = OperatorEquals.local().execute(mg1.getGeometry(), simpleResult, null, null); + assertTrue(equals); + } } diff --git a/src/test/java/com/esri/core/geometry/TestSpatialReference.java b/src/test/java/com/esri/core/geometry/TestSpatialReference.java index 8dbf13ea..a4170c00 100644 --- a/src/test/java/com/esri/core/geometry/TestSpatialReference.java +++ b/src/test/java/com/esri/core/geometry/TestSpatialReference.java @@ -31,201 +31,201 @@ import java.util.regex.Pattern; public class TestSpatialReference extends TestCase { - @Test - public void testEquals() { - String wktext1 = "GEOGCS[\"GCS_WGS_1984\",DATUM[\"D_WGS_1984\",SPHEROID[\"WGS_1984\",6378137.0,298.257223563]],PRIMEM[\"Greenwich\",0.0],UNIT[\"Degree\",0.0174532925199433]]"; - String wktext2 = "PROJCS[\"WGS_1984_Web_Mercator_Auxiliary_Sphere\",GEOGCS[\"GCS_WGS_1984\",DATUM[\"D_WGS_1984\",SPHEROID[\"WGS_1984\",6378137.0,298.257223563]],PRIMEM[\"Greenwich\",0.0],UNIT[\"Degree\",0.0174532925199433]],PROJECTION[\"Mercator_Auxiliary_Sphere\"],PARAMETER[\"False_Easting\",0.0],PARAMETER[\"False_Northing\",0.0],PARAMETER[\"Central_Meridian\",0.0],PARAMETER[\"Standard_Parallel_1\",0.0],PARAMETER[\"Auxiliary_Sphere_Type\",0.0],UNIT[\"Meter\",1.0]]"; - String proj4 = "+proj=utm +zone=30 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs "; - SpatialReference a1 = SpatialReference.create(wktext1); - SpatialReference b = SpatialReference.create(wktext2); - SpatialReference a2 = SpatialReference.create(wktext1); - SpatialReference c = SpatialReference.createFromProj4(proj4); - - assertTrue(a1.equals(a1)); - assertTrue(b.equals(b)); - - assertTrue(a1.equals(a2)); - - assertFalse(a1.equals(b)); - assertFalse(b.equals(a1)); - - assertFalse(c.equals(a1)); - assertFalse(c.equals(b)); - } - - @Test - public void testTolerance() { - String wktWGS84 = "GEOGCS[\"WGS 84\",DATUM[\"WGS_1984\",SPHEROID[\"WGS 84\",6378137,298.257223563,AUTHORITY[\"EPSG\",\"7030\"]],AUTHORITY[\"EPSG\",\"6326\"]],PRIMEM[\"Greenwich\",0,AUTHORITY[\"EPSG\",\"8901\"]],UNIT[\"degree\",0.0174532925199433,AUTHORITY[\"EPSG\",\"9122\"]],AUTHORITY[\"EPSG\",\"4326\"]]"; - SpatialReference a1 = SpatialReference.create(wktWGS84); - SpatialReference a2 = SpatialReference.create(4326); - assertEquals(a1.getTolerance(), a2.getTolerance()); - } - - @Test - public void prjCreateFromProj4() { - double longitude = 0.0; - double latitude = 0.0; - String proj4 = String.format( - "+proj=laea +lat_0=%f +lon_0=%f +x_0=0.0 +y_0=0.0 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs", - longitude, latitude); - - SpatialReference spatialReference = SpatialReference.createFromProj4(proj4); - assertNotNull(spatialReference); - assertEquals(spatialReference.getProj4(), proj4); - } - - @Test - public void testWKTToWkid() { - String test1 = "GEOGCS[\"WGS 84\",DATUM[\"WGS_1984\",SPHEROID[\"WGS 84\",6378137,298.257223563,AUTHORITY[\"EPSG\",\"7030\"]],AUTHORITY[\"EPSG\",\"6326\"]],PRIMEM[\"Greenwich\",0,AUTHORITY[\"EPSG\",\"8901\"]],UNIT[\"degree\",0.0174532925199433,AUTHORITY[\"EPSG\",\"9122\"]],AUTHORITY[\"EPSG\",\"4326\"]]"; - Pattern pattern = Pattern.compile("^([\\w\\W]+AUTHORITY[\\s]*\\[[\\s]*\"EPSG\"[\\s]*,[\\s]*[\"]*([\\d]+)[\"]*]])$"); - Matcher matcher = pattern.matcher(test1); - - assertTrue(matcher.find()); - assertTrue(matcher.group(2).equals("4326")); - - SpatialReference spatialReference = SpatialReference.create(test1); - assertEquals(spatialReference.getID(), 4326); - assertNotNull(spatialReference.getProj4()); - assertNull(spatialReference.getText()); - - String test2 = "GEOGCS[\n" + - "\t\"WGS 84\",\n" + - "\tDATUM[\n" + - "\t\t\"WGS_1984\",\n" + - "\t\tSPHEROID[\n" + - "\t\t\t\"WGS 84\",\n" + - "\t\t\t6378137,\n" + - "\t\t\t298.257223563,\n" + - "\t\t\tAUTHORITY[\"EPSG\",\"7030\"]\n" + - "\t\t\t],\n" + - "\t\t\tAUTHORITY[\"EPSG\",\"6326\"]\n" + - "\t\t],\n" + - "\t\tPRIMEM[\n" + - "\t\t\t\"Greenwich\",\n" + - "\t\t\t0,\n" + - "\t\t\tAUTHORITY[\"EPSG\",\"8901\"]\n" + - "\t\t],\n" + - "\t\tUNIT[\n" + - "\t\t\t\"degree\",\n" + - "\t\t\t0.0174532925199433,\n" + - "\t\t\tAUTHORITY[\"EPSG\",\"9122\"]\n" + - "\t\t],\n" + - "\t\t\n" + - "\t\tAUTHORITY\n" + - "\n" + - "\t\t[\n" + - "\n" + - "\t\t\t\"EPSG\" ,\n" + - "\t\t\t\"4326\"\n" + - "\t\t\n" + - "\t\t]\n" + - "\n" + - "\t]"; - SpatialReference spatialReference2 = SpatialReference.create(test2); - assertEquals(spatialReference2.getID(), 4326); - assertNotNull(spatialReference2.getProj4()); - assertNull(spatialReference2.getText()); - - String test3 = "GEOGCS[\"WGS 84\",DATUM[\"WGS_1984\",SPHEROID[\"WGS 84\",6378137,298.257223563,AUTHORITY[\"EPSG\",\"7030\"]],AUTHORITY[\"EPSG\",\"6326\"]],PRIMEM[\"Greenwich\",0,AUTHORITY[\"EPSG\",\"8901\"]],UNIT[\"degree\",0.01745329251994328,AUTHORITY[\"EPSG\",\"9122\"]],AUTHORITY[\"EPSG\",\"4326\"]]"; - SpatialReference spatialReference3 = SpatialReference.create(test3); - assertEquals(spatialReference3.getID(), 4326); - assertNotNull(spatialReference3.getProj4()); - assertNull(spatialReference3.getText()); - - String test4 = "PROJCS[\"WGS 84 / UTM zone 17N\",\n" + - " GEOGCS[\"WGS 84\",\n" + - " DATUM[\"WGS_1984\",\n" + - " SPHEROID[\"WGS 84\",6378137,298.257223563,\n" + - " AUTHORITY[\"EPSG\",\"7030\"]],\n" + - " AUTHORITY[\"EPSG\",\"6326\"]],\n" + - " PRIMEM[\"Greenwich\",0,\n" + - " AUTHORITY[\"EPSG\",\"8901\"]],\n" + - " UNIT[\"degree\",0.0174532925199433,\n" + - " AUTHORITY[\"EPSG\",\"9122\"]],\n" + - " AUTHORITY[\"EPSG\",\"4326\"]],\n" + - " PROJECTION[\"Transverse_Mercator\"],\n" + - " PARAMETER[\"latitude_of_origin\",0],\n" + - " PARAMETER[\"central_meridian\",-81],\n" + - " PARAMETER[\"scale_factor\",0.9996],\n" + - " PARAMETER[\"false_easting\",500000],\n" + - " PARAMETER[\"false_northing\",0],\n" + - " UNIT[\"metre\",1,\n" + - " AUTHORITY[\"EPSG\",\"9001\"]],\n" + - " AXIS[\"Easting\",EAST],\n" + - " AXIS[\"Northing\",NORTH],\n" + - " AUTHORITY[\"EPSG\",\"32617\"]]\n" + - " "; - SpatialReference spatialReference4 = SpatialReference.create(test4); - assertEquals(spatialReference4.getID(), 32617); - assertNotNull(spatialReference4.getProj4()); - assertNull(spatialReference4.getText()); - - String customWKT = "PROJCS[\"Lo27_Cape\",GEOGCS[\"GCS_Cape\",DATUM[\"D_Cape\",SPHEROID[\"Clarke_1880_Arc\",6378249.145,293.466307656]],PRIMEM[\"Greenwich\",0.0],UNIT[\"Degree\",0.0174532925199433]],PROJECTION[\"Transverse_Mercator\"],PARAMETER[\"False_Easting\",0.0],PARAMETER[\"False_Northing\",0.0],PARAMETER[\"Central_Meridian\",27.0],PARAMETER[\"Scale_Factor\",1.0],PARAMETER[\"Latitude_Of_Origin\",0.0],UNIT[\"Meter\",1.0]]"; - SpatialReference spatialReference5 = SpatialReference.create(customWKT); - assertEquals(spatialReference5.getID(), 0); - assertNull(spatialReference5.getProj4()); - assertNotNull(spatialReference5.getText()); - } - - @Test - public void testProj4EPSG() { - String epsg = "+init=epsg:26711"; - SpatialReference spatialReference = SpatialReference.createFromProj4(epsg); - assertEquals(26711, spatialReference.getID()); - assertEquals(SpatialReference.CoordinateSystemType.PROJECTED, spatialReference.getCoordinateSystemType()); - - epsg = "+init=epsg:4326"; - spatialReference = SpatialReference.createFromProj4(epsg); - assertEquals(4326, spatialReference.getID()); - assertEquals(SpatialReference.CoordinateSystemType.GEOGRAPHIC, spatialReference.getCoordinateSystemType()); - - String test4 = "PROJCS[\"WGS 84 / UTM zone 17N\",\n" + - " GEOGCS[\"WGS 84\",\n" + - " DATUM[\"WGS_1984\",\n" + - " SPHEROID[\"WGS 84\",6378137,298.257223563,\n" + - " AUTHORITY[\"EPSG\",\"7030\"]],\n" + - " AUTHORITY[\"EPSG\",\"6326\"]],\n" + - " PRIMEM[\"Greenwich\",0,\n" + - " AUTHORITY[\"EPSG\",\"8901\"]],\n" + - " UNIT[\"degree\",0.0174532925199433,\n" + - " AUTHORITY[\"EPSG\",\"9122\"]],\n" + - " AUTHORITY[\"EPSG\",\"4326\"]],\n" + - " PROJECTION[\"Transverse_Mercator\"],\n" + - " PARAMETER[\"latitude_of_origin\",0],\n" + - " PARAMETER[\"central_meridian\",-81],\n" + - " PARAMETER[\"scale_factor\",0.9996],\n" + - " PARAMETER[\"false_easting\",500000],\n" + - " PARAMETER[\"false_northing\",0],\n" + - " UNIT[\"metre\",1,\n" + - " AUTHORITY[\"EPSG\",\"9001\"]],\n" + - " AXIS[\"Easting\",EAST],\n" + - " AXIS[\"Northing\",NORTH]]]\n" + - " "; - SpatialReference spatialReference4 = SpatialReference.create(test4); - assertEquals(SpatialReference.CoordinateSystemType.PROJECTED, spatialReference4.getCoordinateSystemType()); - - spatialReference = SpatialReference.createFromProj4("+proj=longlat +datum=WGS84 +no_defs "); - assertEquals(SpatialReference.CoordinateSystemType.GEOGRAPHIC, spatialReference.getCoordinateSystemType()); - } - - @Test - public void testProj9001() { - String tet5 = "PROJCS[\"NUTM30\",GEOGCS[\"ETRS89\",DATUM[\"European_Terrestrial_Reference_System_1989\",SPHEROID[\"GRS 1980\",6378137,298.2572221010042,AUTHORITY[\"EPSG\",\"7019\"]],AUTHORITY[\"EPSG\",\"6258\"]],PRIMEM[\"Greenwich\",0],UNIT[\"degree\",0.0174532925199433],AUTHORITY[\"EPSG\",\"4258\"]],PROJECTION[\"Transverse_Mercator\"],PARAMETER[\"latitude_of_origin\",0],PARAMETER[\"central_meridian\",-3],PARAMETER[\"scale_factor\",0.9996],PARAMETER[\"false_easting\",500000],PARAMETER[\"false_northing\",0],UNIT[\"metre\",1,AUTHORITY[\"EPSG\",\"9001\"]]]"; - String epsg = "+init=epsg:4326"; - SpatialReference spatialReference = SpatialReference.createFromProj4(epsg); - assertEquals(4326, spatialReference.getID()); - assertEquals(SpatialReference.CoordinateSystemType.GEOGRAPHIC, spatialReference.getCoordinateSystemType()); - - SpatialReference spatialReference1 = SpatialReference.create(tet5); - assertEquals(9001, spatialReference1.getID()); - assertEquals("+init=epsg:9001", spatialReference1.getProj4()); - } - - @Test - public void testProjEquality() { - SpatialReference spatialReference = SpatialReference.createFromProj4("+proj=utm +zone=30 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs "); - SpatialReference spatialReference2 = SpatialReference.createFromProj4("+proj=utm +zone=30 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs "); - assertEquals(spatialReference, spatialReference2); - } + @Test + public void testEquals() { + String wktext1 = "GEOGCS[\"GCS_WGS_1984\",DATUM[\"D_WGS_1984\",SPHEROID[\"WGS_1984\",6378137.0,298.257223563]],PRIMEM[\"Greenwich\",0.0],UNIT[\"Degree\",0.0174532925199433]]"; + String wktext2 = "PROJCS[\"WGS_1984_Web_Mercator_Auxiliary_Sphere\",GEOGCS[\"GCS_WGS_1984\",DATUM[\"D_WGS_1984\",SPHEROID[\"WGS_1984\",6378137.0,298.257223563]],PRIMEM[\"Greenwich\",0.0],UNIT[\"Degree\",0.0174532925199433]],PROJECTION[\"Mercator_Auxiliary_Sphere\"],PARAMETER[\"False_Easting\",0.0],PARAMETER[\"False_Northing\",0.0],PARAMETER[\"Central_Meridian\",0.0],PARAMETER[\"Standard_Parallel_1\",0.0],PARAMETER[\"Auxiliary_Sphere_Type\",0.0],UNIT[\"Meter\",1.0]]"; + String proj4 = "+proj=utm +zone=30 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs "; + SpatialReference a1 = SpatialReference.create(wktext1); + SpatialReference b = SpatialReference.create(wktext2); + SpatialReference a2 = SpatialReference.create(wktext1); + SpatialReference c = SpatialReference.createFromProj4(proj4); + + assertTrue(a1.equals(a1)); + assertTrue(b.equals(b)); + + assertTrue(a1.equals(a2)); + + assertFalse(a1.equals(b)); + assertFalse(b.equals(a1)); + + assertFalse(c.equals(a1)); + assertFalse(c.equals(b)); + } + + @Test + public void testTolerance() { + String wktWGS84 = "GEOGCS[\"WGS 84\",DATUM[\"WGS_1984\",SPHEROID[\"WGS 84\",6378137,298.257223563,AUTHORITY[\"EPSG\",\"7030\"]],AUTHORITY[\"EPSG\",\"6326\"]],PRIMEM[\"Greenwich\",0,AUTHORITY[\"EPSG\",\"8901\"]],UNIT[\"degree\",0.0174532925199433,AUTHORITY[\"EPSG\",\"9122\"]],AUTHORITY[\"EPSG\",\"4326\"]]"; + SpatialReference a1 = SpatialReference.create(wktWGS84); + SpatialReference a2 = SpatialReference.create(4326); + assertEquals(a1.getTolerance(), a2.getTolerance()); + } + + @Test + public void prjCreateFromProj4() { + double longitude = 0.0; + double latitude = 0.0; + String proj4 = String.format( + "+proj=laea +lat_0=%f +lon_0=%f +x_0=0.0 +y_0=0.0 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs", + longitude, latitude); + + SpatialReference spatialReference = SpatialReference.createFromProj4(proj4); + assertNotNull(spatialReference); + assertEquals(spatialReference.getProj4(), proj4); + } + + @Test + public void testWKTToWkid() { + String test1 = "GEOGCS[\"WGS 84\",DATUM[\"WGS_1984\",SPHEROID[\"WGS 84\",6378137,298.257223563,AUTHORITY[\"EPSG\",\"7030\"]],AUTHORITY[\"EPSG\",\"6326\"]],PRIMEM[\"Greenwich\",0,AUTHORITY[\"EPSG\",\"8901\"]],UNIT[\"degree\",0.0174532925199433,AUTHORITY[\"EPSG\",\"9122\"]],AUTHORITY[\"EPSG\",\"4326\"]]"; + Pattern pattern = Pattern.compile("^([\\w\\W]+AUTHORITY[\\s]*\\[[\\s]*\"EPSG\"[\\s]*,[\\s]*[\"]*([\\d]+)[\"]*]])$"); + Matcher matcher = pattern.matcher(test1); + + assertTrue(matcher.find()); + assertTrue(matcher.group(2).equals("4326")); + + SpatialReference spatialReference = SpatialReference.create(test1); + assertEquals(spatialReference.getID(), 4326); + assertNotNull(spatialReference.getProj4()); + assertNull(spatialReference.getText()); + + String test2 = "GEOGCS[\n" + + "\t\"WGS 84\",\n" + + "\tDATUM[\n" + + "\t\t\"WGS_1984\",\n" + + "\t\tSPHEROID[\n" + + "\t\t\t\"WGS 84\",\n" + + "\t\t\t6378137,\n" + + "\t\t\t298.257223563,\n" + + "\t\t\tAUTHORITY[\"EPSG\",\"7030\"]\n" + + "\t\t\t],\n" + + "\t\t\tAUTHORITY[\"EPSG\",\"6326\"]\n" + + "\t\t],\n" + + "\t\tPRIMEM[\n" + + "\t\t\t\"Greenwich\",\n" + + "\t\t\t0,\n" + + "\t\t\tAUTHORITY[\"EPSG\",\"8901\"]\n" + + "\t\t],\n" + + "\t\tUNIT[\n" + + "\t\t\t\"degree\",\n" + + "\t\t\t0.0174532925199433,\n" + + "\t\t\tAUTHORITY[\"EPSG\",\"9122\"]\n" + + "\t\t],\n" + + "\t\t\n" + + "\t\tAUTHORITY\n" + + "\n" + + "\t\t[\n" + + "\n" + + "\t\t\t\"EPSG\" ,\n" + + "\t\t\t\"4326\"\n" + + "\t\t\n" + + "\t\t]\n" + + "\n" + + "\t]"; + SpatialReference spatialReference2 = SpatialReference.create(test2); + assertEquals(spatialReference2.getID(), 4326); + assertNotNull(spatialReference2.getProj4()); + assertNull(spatialReference2.getText()); + + String test3 = "GEOGCS[\"WGS 84\",DATUM[\"WGS_1984\",SPHEROID[\"WGS 84\",6378137,298.257223563,AUTHORITY[\"EPSG\",\"7030\"]],AUTHORITY[\"EPSG\",\"6326\"]],PRIMEM[\"Greenwich\",0,AUTHORITY[\"EPSG\",\"8901\"]],UNIT[\"degree\",0.01745329251994328,AUTHORITY[\"EPSG\",\"9122\"]],AUTHORITY[\"EPSG\",\"4326\"]]"; + SpatialReference spatialReference3 = SpatialReference.create(test3); + assertEquals(spatialReference3.getID(), 4326); + assertNotNull(spatialReference3.getProj4()); + assertNull(spatialReference3.getText()); + + String test4 = "PROJCS[\"WGS 84 / UTM zone 17N\",\n" + + " GEOGCS[\"WGS 84\",\n" + + " DATUM[\"WGS_1984\",\n" + + " SPHEROID[\"WGS 84\",6378137,298.257223563,\n" + + " AUTHORITY[\"EPSG\",\"7030\"]],\n" + + " AUTHORITY[\"EPSG\",\"6326\"]],\n" + + " PRIMEM[\"Greenwich\",0,\n" + + " AUTHORITY[\"EPSG\",\"8901\"]],\n" + + " UNIT[\"degree\",0.0174532925199433,\n" + + " AUTHORITY[\"EPSG\",\"9122\"]],\n" + + " AUTHORITY[\"EPSG\",\"4326\"]],\n" + + " PROJECTION[\"Transverse_Mercator\"],\n" + + " PARAMETER[\"latitude_of_origin\",0],\n" + + " PARAMETER[\"central_meridian\",-81],\n" + + " PARAMETER[\"scale_factor\",0.9996],\n" + + " PARAMETER[\"false_easting\",500000],\n" + + " PARAMETER[\"false_northing\",0],\n" + + " UNIT[\"metre\",1,\n" + + " AUTHORITY[\"EPSG\",\"9001\"]],\n" + + " AXIS[\"Easting\",EAST],\n" + + " AXIS[\"Northing\",NORTH],\n" + + " AUTHORITY[\"EPSG\",\"32617\"]]\n" + + " "; + SpatialReference spatialReference4 = SpatialReference.create(test4); + assertEquals(spatialReference4.getID(), 32617); + assertNotNull(spatialReference4.getProj4()); + assertNull(spatialReference4.getText()); + + String customWKT = "PROJCS[\"Lo27_Cape\",GEOGCS[\"GCS_Cape\",DATUM[\"D_Cape\",SPHEROID[\"Clarke_1880_Arc\",6378249.145,293.466307656]],PRIMEM[\"Greenwich\",0.0],UNIT[\"Degree\",0.0174532925199433]],PROJECTION[\"Transverse_Mercator\"],PARAMETER[\"False_Easting\",0.0],PARAMETER[\"False_Northing\",0.0],PARAMETER[\"Central_Meridian\",27.0],PARAMETER[\"Scale_Factor\",1.0],PARAMETER[\"Latitude_Of_Origin\",0.0],UNIT[\"Meter\",1.0]]"; + SpatialReference spatialReference5 = SpatialReference.create(customWKT); + assertEquals(spatialReference5.getID(), 0); + assertNull(spatialReference5.getProj4()); + assertNotNull(spatialReference5.getText()); + } + + @Test + public void testProj4EPSG() { + String epsg = "+init=epsg:26711"; + SpatialReference spatialReference = SpatialReference.createFromProj4(epsg); + assertEquals(26711, spatialReference.getID()); + assertEquals(SpatialReference.CoordinateSystemType.PROJECTED, spatialReference.getCoordinateSystemType()); + + epsg = "+init=epsg:4326"; + spatialReference = SpatialReference.createFromProj4(epsg); + assertEquals(4326, spatialReference.getID()); + assertEquals(SpatialReference.CoordinateSystemType.GEOGRAPHIC, spatialReference.getCoordinateSystemType()); + + String test4 = "PROJCS[\"WGS 84 / UTM zone 17N\",\n" + + " GEOGCS[\"WGS 84\",\n" + + " DATUM[\"WGS_1984\",\n" + + " SPHEROID[\"WGS 84\",6378137,298.257223563,\n" + + " AUTHORITY[\"EPSG\",\"7030\"]],\n" + + " AUTHORITY[\"EPSG\",\"6326\"]],\n" + + " PRIMEM[\"Greenwich\",0,\n" + + " AUTHORITY[\"EPSG\",\"8901\"]],\n" + + " UNIT[\"degree\",0.0174532925199433,\n" + + " AUTHORITY[\"EPSG\",\"9122\"]],\n" + + " AUTHORITY[\"EPSG\",\"4326\"]],\n" + + " PROJECTION[\"Transverse_Mercator\"],\n" + + " PARAMETER[\"latitude_of_origin\",0],\n" + + " PARAMETER[\"central_meridian\",-81],\n" + + " PARAMETER[\"scale_factor\",0.9996],\n" + + " PARAMETER[\"false_easting\",500000],\n" + + " PARAMETER[\"false_northing\",0],\n" + + " UNIT[\"metre\",1,\n" + + " AUTHORITY[\"EPSG\",\"9001\"]],\n" + + " AXIS[\"Easting\",EAST],\n" + + " AXIS[\"Northing\",NORTH]]]\n" + + " "; + SpatialReference spatialReference4 = SpatialReference.create(test4); + assertEquals(SpatialReference.CoordinateSystemType.PROJECTED, spatialReference4.getCoordinateSystemType()); + + spatialReference = SpatialReference.createFromProj4("+proj=longlat +datum=WGS84 +no_defs "); + assertEquals(SpatialReference.CoordinateSystemType.GEOGRAPHIC, spatialReference.getCoordinateSystemType()); + } + + @Test + public void testProj9001() { + String tet5 = "PROJCS[\"NUTM30\",GEOGCS[\"ETRS89\",DATUM[\"European_Terrestrial_Reference_System_1989\",SPHEROID[\"GRS 1980\",6378137,298.2572221010042,AUTHORITY[\"EPSG\",\"7019\"]],AUTHORITY[\"EPSG\",\"6258\"]],PRIMEM[\"Greenwich\",0],UNIT[\"degree\",0.0174532925199433],AUTHORITY[\"EPSG\",\"4258\"]],PROJECTION[\"Transverse_Mercator\"],PARAMETER[\"latitude_of_origin\",0],PARAMETER[\"central_meridian\",-3],PARAMETER[\"scale_factor\",0.9996],PARAMETER[\"false_easting\",500000],PARAMETER[\"false_northing\",0],UNIT[\"metre\",1,AUTHORITY[\"EPSG\",\"9001\"]]]"; + String epsg = "+init=epsg:4326"; + SpatialReference spatialReference = SpatialReference.createFromProj4(epsg); + assertEquals(4326, spatialReference.getID()); + assertEquals(SpatialReference.CoordinateSystemType.GEOGRAPHIC, spatialReference.getCoordinateSystemType()); + + SpatialReference spatialReference1 = SpatialReference.create(tet5); + assertEquals(9001, spatialReference1.getID()); + assertEquals("+init=epsg:9001", spatialReference1.getProj4()); + } + + @Test + public void testProjEquality() { + SpatialReference spatialReference = SpatialReference.createFromProj4("+proj=utm +zone=30 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs "); + SpatialReference spatialReference2 = SpatialReference.createFromProj4("+proj=utm +zone=30 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs "); + assertEquals(spatialReference, spatialReference2); + } } diff --git a/src/test/java/com/esri/core/geometry/TestTouch.java b/src/test/java/com/esri/core/geometry/TestTouch.java index 69ed3d08..f7761836 100644 --- a/src/test/java/com/esri/core/geometry/TestTouch.java +++ b/src/test/java/com/esri/core/geometry/TestTouch.java @@ -28,434 +28,434 @@ import org.junit.Test; public class TestTouch extends TestCase { - @Override - protected void setUp() throws Exception { - super.setUp(); - } - - @Override - protected void tearDown() throws Exception { - super.tearDown(); - } - - @Test - public void testTouchOnPointAndPolyline() { - Geometry baseGeom = new Point(-130, 10); - Polyline pl = new Polyline(); - pl.startPath(new Point(-130, 10)); - pl.lineTo(-131, 15); - pl.lineTo(-140, 20); - - SpatialReference sr = SpatialReference.create(4326); - - boolean isTouched; - boolean isTouched2; - try { - isTouched = GeometryEngine.touches(baseGeom, pl, sr); - isTouched2 = GeometryEngine.touches(pl, baseGeom, sr); - - } catch (IllegalArgumentException ex) { - isTouched = false; - isTouched2 = false; - } - assertEquals(isTouched && isTouched2, true); - } - - @Test - public void testTouchOnPointAndPolygon() { - Geometry baseGeom = new Point(-130, 10); - Polygon pg = new Polygon(); - pg.startPath(new Point(-130, 10)); - pg.lineTo(-131, 15); - pg.lineTo(-140, 20); - - SpatialReference sr = SpatialReference.create(4326); - - boolean isTouched; - boolean isTouched2; - try { - isTouched = GeometryEngine.touches(baseGeom, pg, sr); - isTouched2 = GeometryEngine.touches(pg, baseGeom, sr); - - } catch (IllegalArgumentException ex) { - isTouched = false; - isTouched2 = false; - } - assertEquals(isTouched && isTouched2, true); - } - - @Test - public void testTouchOnPolygons() { - Polygon pg = new Polygon(); - pg.startPath(new Point(-130, 10)); - pg.lineTo(-131, 15); - pg.lineTo(-140, 20); - - Polygon pg2 = new Polygon(); - pg2.startPath(new Point(-130, 10)); - pg2.lineTo(-131, 15); - pg2.lineTo(-120, 20); - - SpatialReference sr = SpatialReference.create(4326); - - boolean isTouched; - boolean isTouched2; - try { - isTouched = GeometryEngine.touches(pg, pg2, sr); - isTouched2 = GeometryEngine.touches(pg2, pg, sr); - - } catch (IllegalArgumentException ex) { - isTouched = false; - isTouched2 = false; - } - assertEquals(isTouched && isTouched2, true); - - // boolean isTouchedFromRest = GeometryUtils.isRelationTrue(pg2, pg, sr, - // GeometryUtils.SpatialRelationType.esriGeometryRelationTouch, ""); - // assertTrue(isTouchedFromRest==isTouched); - } - - @Test - public void testTouchesOnPolylines() { - SpatialReference sr = SpatialReference.create(4326); - - Polyline basePl = new Polyline(); - basePl.startPath(new Point(-117, 20)); - basePl.lineTo(new Point(-100, 20)); - - basePl.lineTo(new Point(-100, 10)); - basePl.lineTo(new Point(-117, 10)); - basePl.lineTo(new Point(-117, 20)); - - Polyline compPl = new Polyline(); - compPl.startPath(new Point(-104, 20)); - - compPl.lineTo(new Point(-108, 25)); - - compPl.lineTo(new Point(-100, 20)); - // compPl.lineTo(new Point(-100, 30)); - // compPl.lineTo(new Point(-117, 30)); - // compPl.lineTo(new Point(-117, 20)); - - boolean isTouched; - try { - isTouched = GeometryEngine.touches(basePl, compPl, sr); - - } catch (IllegalArgumentException ex) { - isTouched = false; - } - assertEquals(isTouched, true); - } - - @Test - public void testTouchesOnPolylineAndPolygon() { - SpatialReference sr = SpatialReference.create(4326); - - Polygon basePl = new Polygon(); - basePl.startPath(new Point(-117, 20)); - basePl.lineTo(new Point(-100, 20)); - - basePl.lineTo(new Point(-100, 10)); - basePl.lineTo(new Point(-117, 10)); - - Polyline compPl = new Polyline(); - - compPl.startPath(new Point(-117, 20)); - compPl.lineTo(new Point(-108, 25)); - compPl.lineTo(new Point(-100, 20)); - compPl.lineTo(new Point(-100, 30)); - - boolean isTouched; - try { - isTouched = GeometryEngine.touches(basePl, compPl, sr); - } catch (IllegalArgumentException ex) { - isTouched = false; - } - assertEquals(isTouched, true); - - } - - @Test - public void testTouchOnEnvelopes() { - // case1, not touched - // Envelope env = new Envelope(new Point(-117,20), 12, 12); - // Envelope env2 = new Envelope(-100,20,-80,30); - - // case2 touched - Envelope env = new Envelope(new Point(-117, 20), 12, 12); - Envelope env2 = new Envelope(-117, 26, -80, 30); - - SpatialReference sr = SpatialReference.create(4326); - - boolean isTouched; - try { - isTouched = GeometryEngine.touches(env, env2, sr); - } catch (IllegalArgumentException ex) { - isTouched = false; - } - assertEquals(isTouched, true); - - } - - @Test - public void testTouchesOnPolylineAndEnvelope() { - SpatialReference sr = SpatialReference.create(4326); - - Polyline basePl = new Polyline(); - basePl.startPath(new Point(-117, 20)); - basePl.lineTo(new Point(-100, 20)); - - basePl.lineTo(new Point(-100, 10)); - basePl.lineTo(new Point(-117, 10)); - basePl.lineTo(new Point(-117, 20)); - - // Envelope env = new Envelope(new Point(-117,20), 12, 12);//not touched - Envelope env = new Envelope(-100, 20, -80, 30);// touched - - boolean isTouched; - try { - isTouched = GeometryEngine.touches(basePl, env, sr); - } catch (IllegalArgumentException ex) { - isTouched = false; - } - assertEquals(isTouched, true); - - } - - @Test - public void testTouchesOnPolygonAndEnvelope() { - SpatialReference sr = SpatialReference.create(4326); - - Polygon basePl = new Polygon(); - basePl.startPath(new Point(-117, 20)); - basePl.lineTo(new Point(-100, 20)); - - basePl.lineTo(new Point(-100, 10)); - basePl.lineTo(new Point(-117, 10)); - - // Envelope env = new Envelope(new Point(-117,20), 12, 12);//not touched - Envelope env = new Envelope(-100, 20, -80, 30);// touched - - boolean isTouched; - try { - isTouched = GeometryEngine.touches(basePl, env, sr); - } catch (IllegalArgumentException ex) { - isTouched = false; - } - assertEquals(isTouched, true); - - } - - @Test - public void testTouchesOnPointAndEnvelope() { - SpatialReference sr = SpatialReference.create(4326); - - Point p = new Point(-130, 10); - - // Envelope env = new Envelope(p, 12, 12);//not touched - Envelope env = new Envelope(-130, 10, -110, 20);// touched - - boolean isTouched; - try { - isTouched = GeometryEngine.touches(p, env, sr); - } catch (IllegalArgumentException ex) { - isTouched = false; - } - assertEquals(isTouched, true); - - } - - @Test - public void testRelationTouch() { - SpatialReference sr = SpatialReference.create(4326); - Polyline basePl = new Polyline(); - basePl.startPath(2, 2); - basePl.lineTo(2, 10); - - Polyline compPl = new Polyline(); - compPl.startPath(2, 4); - compPl.lineTo(9, 4); - compPl.lineTo(9, 9); - compPl.lineTo(2, 9); - compPl.lineTo(2, 4); - - boolean isTouched = false;// GeometryEngine.relation(basePl, compPl, sr, - // "G1 TOUCH G2"); - assertEquals(isTouched, false); - - } - - @Test - /** - * test touches between point and polyline - * a point touches a polyline only if the point is - * coincident with one of the polyline end points - * */ - public void testTouchesBetweenPointAndLine() { - SpatialReference sr = SpatialReference.create(4326); - Point p = new Point(2, 4); - - Polyline compPl = new Polyline(); - compPl.startPath(2, 4); - - compPl.lineTo(9, 4); - compPl.lineTo(9, 9); - compPl.lineTo(2, 9); - compPl.lineTo(2, 4); - - boolean isTouched = GeometryEngine.touches(p, compPl, sr); - assertTrue(!isTouched); - - } - - @Test - /** - * test touches between polyline and polyline - * a polyline touches another polyline only if the end point(s) is - * coincident with the end points of another polyline - * In this test case, the end points of the first polyline are concident - * with two end points of the second polyline - * */ - public void testTouchesBetweenPolylines() { - SpatialReference sr = SpatialReference.create(4326); - Polyline pl = new Polyline(); - pl.startPath(2, 4); - pl.lineTo(9, 9); - - Polyline compPl = new Polyline(); - compPl.startPath(2, 4); - - compPl.lineTo(9, 4); - compPl.lineTo(9, 9); - compPl.lineTo(2, 9); - compPl.lineTo(2, 4); - - boolean isTouched = GeometryEngine.touches(pl, compPl, sr); - assertEquals(isTouched, true); - - } - - @Test - /** - * test touches between polyline and polygon - * a polyline touches polygon only if the end point(s) is - * coincident with the vertices of polygon - * In this test case, the end points of the polyline are co-incident - * with two vertices of the polygon which consists of two parts - * */ - public void testTouchesBetweenPolylineAndPolygon() { - SpatialReference sr = SpatialReference.create(4326); - Polyline pl = new Polyline(); - pl.startPath(2, 4); - pl.lineTo(1, 10); - pl.lineTo(6, 12); - - Polygon compPg = new Polygon(); - compPg.startPath(2, 4); - - compPg.lineTo(2, 9); - compPg.lineTo(9, 9); - compPg.lineTo(9, 4); - - compPg.startPath(2, 9); - compPg.lineTo(6, 12); - compPg.lineTo(9, 10); - - boolean isTouched = GeometryEngine.touches(pl, compPg, sr); - assertEquals(isTouched, true); - - } - - @Test - /** - * test touches between polylines who consists of two parts - * */ - public void testTouchesBetweenMultipartPolylines() { - SpatialReference sr = SpatialReference.create(4326); - Polyline pl = new Polyline(); - pl.startPath(2, 4); - pl.lineTo(1, 10); - pl.lineTo(6, 12); - - pl.startPath(6, 12); - pl.lineTo(12, 12); - pl.lineTo(9, 9); - - Polyline compPl = new Polyline(); - compPl.startPath(2, 4); - - compPl.lineTo(2, 9); - compPl.lineTo(9, 9); - compPl.lineTo(9, 4); - - compPl.startPath(2, 9); - compPl.lineTo(6, 12); - compPl.lineTo(9, 10); - - boolean isTouched = GeometryEngine.touches(pl, compPl, sr); - assertTrue(!isTouched); - - // boolean isTouchedFromRest = GeometryUtils.isRelationTrue(compPl, pl, - // sr, - // GeometryUtils.SpatialRelationType.esriGeometryRelationTouch, ""); - // assertTrue(isTouchedFromRest == isTouched); - } - - @Test - /** - * test touches between polygons who consists of two parts - * */ - public void testTouchesBetweenMultipartPolygons2() { - SpatialReference sr = SpatialReference.create(4326); - Polygon pl = new Polygon(); - pl.startPath(2, 4); - pl.lineTo(1, 9); - pl.lineTo(2, 6); - - pl.startPath(2, 9); - pl.lineTo(6, 14); - pl.lineTo(6, 12); - - Polygon compPl = new Polygon(); - compPl.startPath(2, 4); - - compPl.lineTo(2, 9); - compPl.lineTo(9, 9); - compPl.lineTo(9, 4); - - compPl.startPath(2, 9); - compPl.lineTo(6, 12); - compPl.lineTo(9, 10); - - boolean isTouched = GeometryEngine.touches(pl, compPl, sr); - assertEquals(isTouched, true); - - } - - @Test - public void testTouchPointLineCR183227() { - // Tests CR 183227 - Geometry baseGeom = new Point(-130, 10); - Polyline pl = new Polyline(); - // pl.startPath(new Point(-130, 10)); - pl.startPath(-130, 10); - pl.lineTo(-131, 15); - pl.lineTo(-140, 20); - - SpatialReference sr = SpatialReference.create(4326); - - boolean isTouched; - boolean isTouched2; - isTouched = GeometryEngine.touches(baseGeom, pl, sr); - isTouched2 = GeometryEngine.touches(pl, baseGeom, sr); - assertTrue(isTouched && isTouched2); - { - Geometry baseGeom2 = (Geometry) new Point(-131, 15); - boolean bIsTouched; - boolean bIsTouched2; - bIsTouched = GeometryEngine.touches(baseGeom2, pl, sr); - bIsTouched2 = GeometryEngine.touches(pl, baseGeom2, sr); - assertTrue(!bIsTouched && !bIsTouched2); - } - } + @Override + protected void setUp() throws Exception { + super.setUp(); + } + + @Override + protected void tearDown() throws Exception { + super.tearDown(); + } + + @Test + public void testTouchOnPointAndPolyline() { + Geometry baseGeom = new Point(-130, 10); + Polyline pl = new Polyline(); + pl.startPath(new Point(-130, 10)); + pl.lineTo(-131, 15); + pl.lineTo(-140, 20); + + SpatialReference sr = SpatialReference.create(4326); + + boolean isTouched; + boolean isTouched2; + try { + isTouched = GeometryEngine.touches(baseGeom, pl, sr); + isTouched2 = GeometryEngine.touches(pl, baseGeom, sr); + + } catch (IllegalArgumentException ex) { + isTouched = false; + isTouched2 = false; + } + assertEquals(isTouched && isTouched2, true); + } + + @Test + public void testTouchOnPointAndPolygon() { + Geometry baseGeom = new Point(-130, 10); + Polygon pg = new Polygon(); + pg.startPath(new Point(-130, 10)); + pg.lineTo(-131, 15); + pg.lineTo(-140, 20); + + SpatialReference sr = SpatialReference.create(4326); + + boolean isTouched; + boolean isTouched2; + try { + isTouched = GeometryEngine.touches(baseGeom, pg, sr); + isTouched2 = GeometryEngine.touches(pg, baseGeom, sr); + + } catch (IllegalArgumentException ex) { + isTouched = false; + isTouched2 = false; + } + assertEquals(isTouched && isTouched2, true); + } + + @Test + public void testTouchOnPolygons() { + Polygon pg = new Polygon(); + pg.startPath(new Point(-130, 10)); + pg.lineTo(-131, 15); + pg.lineTo(-140, 20); + + Polygon pg2 = new Polygon(); + pg2.startPath(new Point(-130, 10)); + pg2.lineTo(-131, 15); + pg2.lineTo(-120, 20); + + SpatialReference sr = SpatialReference.create(4326); + + boolean isTouched; + boolean isTouched2; + try { + isTouched = GeometryEngine.touches(pg, pg2, sr); + isTouched2 = GeometryEngine.touches(pg2, pg, sr); + + } catch (IllegalArgumentException ex) { + isTouched = false; + isTouched2 = false; + } + assertEquals(isTouched && isTouched2, true); + + // boolean isTouchedFromRest = GeometryUtils.isRelationTrue(pg2, pg, sr, + // GeometryUtils.SpatialRelationType.esriGeometryRelationTouch, ""); + // assertTrue(isTouchedFromRest==isTouched); + } + + @Test + public void testTouchesOnPolylines() { + SpatialReference sr = SpatialReference.create(4326); + + Polyline basePl = new Polyline(); + basePl.startPath(new Point(-117, 20)); + basePl.lineTo(new Point(-100, 20)); + + basePl.lineTo(new Point(-100, 10)); + basePl.lineTo(new Point(-117, 10)); + basePl.lineTo(new Point(-117, 20)); + + Polyline compPl = new Polyline(); + compPl.startPath(new Point(-104, 20)); + + compPl.lineTo(new Point(-108, 25)); + + compPl.lineTo(new Point(-100, 20)); + // compPl.lineTo(new Point(-100, 30)); + // compPl.lineTo(new Point(-117, 30)); + // compPl.lineTo(new Point(-117, 20)); + + boolean isTouched; + try { + isTouched = GeometryEngine.touches(basePl, compPl, sr); + + } catch (IllegalArgumentException ex) { + isTouched = false; + } + assertEquals(isTouched, true); + } + + @Test + public void testTouchesOnPolylineAndPolygon() { + SpatialReference sr = SpatialReference.create(4326); + + Polygon basePl = new Polygon(); + basePl.startPath(new Point(-117, 20)); + basePl.lineTo(new Point(-100, 20)); + + basePl.lineTo(new Point(-100, 10)); + basePl.lineTo(new Point(-117, 10)); + + Polyline compPl = new Polyline(); + + compPl.startPath(new Point(-117, 20)); + compPl.lineTo(new Point(-108, 25)); + compPl.lineTo(new Point(-100, 20)); + compPl.lineTo(new Point(-100, 30)); + + boolean isTouched; + try { + isTouched = GeometryEngine.touches(basePl, compPl, sr); + } catch (IllegalArgumentException ex) { + isTouched = false; + } + assertEquals(isTouched, true); + + } + + @Test + public void testTouchOnEnvelopes() { + // case1, not touched + // Envelope env = new Envelope(new Point(-117,20), 12, 12); + // Envelope env2 = new Envelope(-100,20,-80,30); + + // case2 touched + Envelope env = new Envelope(new Point(-117, 20), 12, 12); + Envelope env2 = new Envelope(-117, 26, -80, 30); + + SpatialReference sr = SpatialReference.create(4326); + + boolean isTouched; + try { + isTouched = GeometryEngine.touches(env, env2, sr); + } catch (IllegalArgumentException ex) { + isTouched = false; + } + assertEquals(isTouched, true); + + } + + @Test + public void testTouchesOnPolylineAndEnvelope() { + SpatialReference sr = SpatialReference.create(4326); + + Polyline basePl = new Polyline(); + basePl.startPath(new Point(-117, 20)); + basePl.lineTo(new Point(-100, 20)); + + basePl.lineTo(new Point(-100, 10)); + basePl.lineTo(new Point(-117, 10)); + basePl.lineTo(new Point(-117, 20)); + + // Envelope env = new Envelope(new Point(-117,20), 12, 12);//not touched + Envelope env = new Envelope(-100, 20, -80, 30);// touched + + boolean isTouched; + try { + isTouched = GeometryEngine.touches(basePl, env, sr); + } catch (IllegalArgumentException ex) { + isTouched = false; + } + assertEquals(isTouched, true); + + } + + @Test + public void testTouchesOnPolygonAndEnvelope() { + SpatialReference sr = SpatialReference.create(4326); + + Polygon basePl = new Polygon(); + basePl.startPath(new Point(-117, 20)); + basePl.lineTo(new Point(-100, 20)); + + basePl.lineTo(new Point(-100, 10)); + basePl.lineTo(new Point(-117, 10)); + + // Envelope env = new Envelope(new Point(-117,20), 12, 12);//not touched + Envelope env = new Envelope(-100, 20, -80, 30);// touched + + boolean isTouched; + try { + isTouched = GeometryEngine.touches(basePl, env, sr); + } catch (IllegalArgumentException ex) { + isTouched = false; + } + assertEquals(isTouched, true); + + } + + @Test + public void testTouchesOnPointAndEnvelope() { + SpatialReference sr = SpatialReference.create(4326); + + Point p = new Point(-130, 10); + + // Envelope env = new Envelope(p, 12, 12);//not touched + Envelope env = new Envelope(-130, 10, -110, 20);// touched + + boolean isTouched; + try { + isTouched = GeometryEngine.touches(p, env, sr); + } catch (IllegalArgumentException ex) { + isTouched = false; + } + assertEquals(isTouched, true); + + } + + @Test + public void testRelationTouch() { + SpatialReference sr = SpatialReference.create(4326); + Polyline basePl = new Polyline(); + basePl.startPath(2, 2); + basePl.lineTo(2, 10); + + Polyline compPl = new Polyline(); + compPl.startPath(2, 4); + compPl.lineTo(9, 4); + compPl.lineTo(9, 9); + compPl.lineTo(2, 9); + compPl.lineTo(2, 4); + + boolean isTouched = false;// GeometryEngine.relation(basePl, compPl, sr, + // "G1 TOUCH G2"); + assertEquals(isTouched, false); + + } + + @Test + /** + * test touches between point and polyline + * a point touches a polyline only if the point is + * coincident with one of the polyline end points + * */ + public void testTouchesBetweenPointAndLine() { + SpatialReference sr = SpatialReference.create(4326); + Point p = new Point(2, 4); + + Polyline compPl = new Polyline(); + compPl.startPath(2, 4); + + compPl.lineTo(9, 4); + compPl.lineTo(9, 9); + compPl.lineTo(2, 9); + compPl.lineTo(2, 4); + + boolean isTouched = GeometryEngine.touches(p, compPl, sr); + assertTrue(!isTouched); + + } + + @Test + /** + * test touches between polyline and polyline + * a polyline touches another polyline only if the end point(s) is + * coincident with the end points of another polyline + * In this test case, the end points of the first polyline are concident + * with two end points of the second polyline + * */ + public void testTouchesBetweenPolylines() { + SpatialReference sr = SpatialReference.create(4326); + Polyline pl = new Polyline(); + pl.startPath(2, 4); + pl.lineTo(9, 9); + + Polyline compPl = new Polyline(); + compPl.startPath(2, 4); + + compPl.lineTo(9, 4); + compPl.lineTo(9, 9); + compPl.lineTo(2, 9); + compPl.lineTo(2, 4); + + boolean isTouched = GeometryEngine.touches(pl, compPl, sr); + assertEquals(isTouched, true); + + } + + @Test + /** + * test touches between polyline and polygon + * a polyline touches polygon only if the end point(s) is + * coincident with the vertices of polygon + * In this test case, the end points of the polyline are co-incident + * with two vertices of the polygon which consists of two parts + * */ + public void testTouchesBetweenPolylineAndPolygon() { + SpatialReference sr = SpatialReference.create(4326); + Polyline pl = new Polyline(); + pl.startPath(2, 4); + pl.lineTo(1, 10); + pl.lineTo(6, 12); + + Polygon compPg = new Polygon(); + compPg.startPath(2, 4); + + compPg.lineTo(2, 9); + compPg.lineTo(9, 9); + compPg.lineTo(9, 4); + + compPg.startPath(2, 9); + compPg.lineTo(6, 12); + compPg.lineTo(9, 10); + + boolean isTouched = GeometryEngine.touches(pl, compPg, sr); + assertEquals(isTouched, true); + + } + + @Test + /** + * test touches between polylines who consists of two parts + * */ + public void testTouchesBetweenMultipartPolylines() { + SpatialReference sr = SpatialReference.create(4326); + Polyline pl = new Polyline(); + pl.startPath(2, 4); + pl.lineTo(1, 10); + pl.lineTo(6, 12); + + pl.startPath(6, 12); + pl.lineTo(12, 12); + pl.lineTo(9, 9); + + Polyline compPl = new Polyline(); + compPl.startPath(2, 4); + + compPl.lineTo(2, 9); + compPl.lineTo(9, 9); + compPl.lineTo(9, 4); + + compPl.startPath(2, 9); + compPl.lineTo(6, 12); + compPl.lineTo(9, 10); + + boolean isTouched = GeometryEngine.touches(pl, compPl, sr); + assertTrue(!isTouched); + + // boolean isTouchedFromRest = GeometryUtils.isRelationTrue(compPl, pl, + // sr, + // GeometryUtils.SpatialRelationType.esriGeometryRelationTouch, ""); + // assertTrue(isTouchedFromRest == isTouched); + } + + @Test + /** + * test touches between polygons who consists of two parts + * */ + public void testTouchesBetweenMultipartPolygons2() { + SpatialReference sr = SpatialReference.create(4326); + Polygon pl = new Polygon(); + pl.startPath(2, 4); + pl.lineTo(1, 9); + pl.lineTo(2, 6); + + pl.startPath(2, 9); + pl.lineTo(6, 14); + pl.lineTo(6, 12); + + Polygon compPl = new Polygon(); + compPl.startPath(2, 4); + + compPl.lineTo(2, 9); + compPl.lineTo(9, 9); + compPl.lineTo(9, 4); + + compPl.startPath(2, 9); + compPl.lineTo(6, 12); + compPl.lineTo(9, 10); + + boolean isTouched = GeometryEngine.touches(pl, compPl, sr); + assertEquals(isTouched, true); + + } + + @Test + public void testTouchPointLineCR183227() { + // Tests CR 183227 + Geometry baseGeom = new Point(-130, 10); + Polyline pl = new Polyline(); + // pl.startPath(new Point(-130, 10)); + pl.startPath(-130, 10); + pl.lineTo(-131, 15); + pl.lineTo(-140, 20); + + SpatialReference sr = SpatialReference.create(4326); + + boolean isTouched; + boolean isTouched2; + isTouched = GeometryEngine.touches(baseGeom, pl, sr); + isTouched2 = GeometryEngine.touches(pl, baseGeom, sr); + assertTrue(isTouched && isTouched2); + { + Geometry baseGeom2 = (Geometry) new Point(-131, 15); + boolean bIsTouched; + boolean bIsTouched2; + bIsTouched = GeometryEngine.touches(baseGeom2, pl, sr); + bIsTouched2 = GeometryEngine.touches(pl, baseGeom2, sr); + assertTrue(!bIsTouched && !bIsTouched2); + } + } } diff --git a/src/test/java/com/esri/core/geometry/TestTreap.java b/src/test/java/com/esri/core/geometry/TestTreap.java index 38eb5349..e1f2b5b5 100644 --- a/src/test/java/com/esri/core/geometry/TestTreap.java +++ b/src/test/java/com/esri/core/geometry/TestTreap.java @@ -28,78 +28,78 @@ import org.junit.Test; public class TestTreap extends TestCase { - @Override - protected void setUp() throws Exception { - super.setUp(); - } - - @Override - protected void tearDown() throws Exception { - super.tearDown(); - } - - @Test - public static void test1() { - Point2D[] pts = new Point2D[10]; - - for (int i = 0; i < 10; i++) { - Point2D pt = new Point2D(); - pt.x = i; - pt.y = 0; - - pts[i] = pt; - } - - TreapComparatorForTesting c = new TreapComparatorForTesting(pts); - Treap treap = new Treap(); - treap.setComparator(c); - - int[] nodes = new int[10]; - for (int i = 0; i < 10; i++) - nodes[i] = treap.addElement(i, -1); - - for (int i = 1; i < 10; i++) { - assertTrue(treap.getPrev(nodes[i]) == nodes[i - 1]); - } - - for (int i = 0; i < 9; i++) { - assertTrue(treap.getNext(nodes[i]) == nodes[i + 1]); - } - - treap.deleteNode(nodes[0], -1); - treap.deleteNode(nodes[2], -1); - treap.deleteNode(nodes[4], -1); - treap.deleteNode(nodes[6], -1); - treap.deleteNode(nodes[8], -1); - - assertTrue(treap.getPrev(nodes[3]) == nodes[1]); - assertTrue(treap.getPrev(nodes[5]) == nodes[3]); - assertTrue(treap.getPrev(nodes[7]) == nodes[5]); - assertTrue(treap.getPrev(nodes[9]) == nodes[7]); - - assertTrue(treap.getNext(nodes[1]) == nodes[3]); - assertTrue(treap.getNext(nodes[3]) == nodes[5]); - assertTrue(treap.getNext(nodes[5]) == nodes[7]); - assertTrue(treap.getNext(nodes[7]) == nodes[9]); - } + @Override + protected void setUp() throws Exception { + super.setUp(); + } + + @Override + protected void tearDown() throws Exception { + super.tearDown(); + } + + @Test + public static void test1() { + Point2D[] pts = new Point2D[10]; + + for (int i = 0; i < 10; i++) { + Point2D pt = new Point2D(); + pt.x = i; + pt.y = 0; + + pts[i] = pt; + } + + TreapComparatorForTesting c = new TreapComparatorForTesting(pts); + Treap treap = new Treap(); + treap.setComparator(c); + + int[] nodes = new int[10]; + for (int i = 0; i < 10; i++) + nodes[i] = treap.addElement(i, -1); + + for (int i = 1; i < 10; i++) { + assertTrue(treap.getPrev(nodes[i]) == nodes[i - 1]); + } + + for (int i = 0; i < 9; i++) { + assertTrue(treap.getNext(nodes[i]) == nodes[i + 1]); + } + + treap.deleteNode(nodes[0], -1); + treap.deleteNode(nodes[2], -1); + treap.deleteNode(nodes[4], -1); + treap.deleteNode(nodes[6], -1); + treap.deleteNode(nodes[8], -1); + + assertTrue(treap.getPrev(nodes[3]) == nodes[1]); + assertTrue(treap.getPrev(nodes[5]) == nodes[3]); + assertTrue(treap.getPrev(nodes[7]) == nodes[5]); + assertTrue(treap.getPrev(nodes[9]) == nodes[7]); + + assertTrue(treap.getNext(nodes[1]) == nodes[3]); + assertTrue(treap.getNext(nodes[3]) == nodes[5]); + assertTrue(treap.getNext(nodes[5]) == nodes[7]); + assertTrue(treap.getNext(nodes[7]) == nodes[9]); + } } final class TreapComparatorForTesting extends Treap.Comparator { - Point2D[] m_pts; + Point2D[] m_pts; - TreapComparatorForTesting(Point2D[] pts) { - m_pts = pts; - } + TreapComparatorForTesting(Point2D[] pts) { + m_pts = pts; + } - @Override - int compare(Treap treap, int elm, int node) { - int elm2 = treap.getElement(node); - Point2D pt1 = m_pts[elm]; - Point2D pt2 = m_pts[elm2]; + @Override + int compare(Treap treap, int elm, int node) { + int elm2 = treap.getElement(node); + Point2D pt1 = m_pts[elm]; + Point2D pt2 = m_pts[elm2]; - if (pt1.x < pt2.x) - return -1; + if (pt1.x < pt2.x) + return -1; - return 1; - } + return 1; + } } diff --git a/src/test/java/com/esri/core/geometry/TestUnion.java b/src/test/java/com/esri/core/geometry/TestUnion.java index b7ac6bea..19fe1c54 100644 --- a/src/test/java/com/esri/core/geometry/TestUnion.java +++ b/src/test/java/com/esri/core/geometry/TestUnion.java @@ -32,56 +32,56 @@ import java.util.stream.Collectors; public class TestUnion extends TestCase { - public ArrayDeque pointList = null; - public ArrayDeque bufferedPointList = null; - - @Override - protected void setUp() throws Exception { - Random random = new Random(1977); - int max_size = 10000; - pointList = new ArrayDeque<>(max_size); - bufferedPointList = new ArrayDeque<>(max_size); - for (int i = 0; i < max_size; i++) { - double x = randomWithRange(-20, 20, random); - double y = randomWithRange(-20, 20, random); - Geometry point = new Point(x, y); - pointList.add(point); - bufferedPointList.add(OperatorBufferLocal.local().execute(point, null, 2.5, null)); - } - - super.setUp(); - } - - @Override - protected void tearDown() throws Exception { - super.tearDown(); - } - - @Test - public void testUnion() { - Point pt = new Point(10, 20); - - Point pt2 = new Point(); - pt2.setXY(10, 10); - - Envelope env1 = new Envelope(10, 10, 30, 50); - Envelope env2 = new Envelope(30, 10, 60, 50); - Geometry[] geomArray = new Geometry[]{env1, env2}; - SimpleGeometryCursor inputGeometries = new SimpleGeometryCursor( - geomArray); - OperatorUnion union = (OperatorUnion) OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.Union); - - SpatialReference sr = SpatialReference.create(4326); - - GeometryCursor outputCursor = union.execute(inputGeometries, sr, null); - Geometry result = outputCursor.next(); - } - - static double randomWithRange(double min, double max, Random random) { - double range = Math.abs(max - min); - return (random.nextDouble() * range) + (min <= max ? min : max); - } + public ArrayDeque pointList = null; + public ArrayDeque bufferedPointList = null; + + @Override + protected void setUp() throws Exception { + Random random = new Random(1977); + int max_size = 10000; + pointList = new ArrayDeque<>(max_size); + bufferedPointList = new ArrayDeque<>(max_size); + for (int i = 0; i < max_size; i++) { + double x = randomWithRange(-20, 20, random); + double y = randomWithRange(-20, 20, random); + Geometry point = new Point(x, y); + pointList.add(point); + bufferedPointList.add(OperatorBufferLocal.local().execute(point, null, 2.5, null)); + } + + super.setUp(); + } + + @Override + protected void tearDown() throws Exception { + super.tearDown(); + } + + @Test + public void testUnion() { + Point pt = new Point(10, 20); + + Point pt2 = new Point(); + pt2.setXY(10, 10); + + Envelope env1 = new Envelope(10, 10, 30, 50); + Envelope env2 = new Envelope(30, 10, 60, 50); + Geometry[] geomArray = new Geometry[]{env1, env2}; + SimpleGeometryCursor inputGeometries = new SimpleGeometryCursor( + geomArray); + OperatorUnion union = (OperatorUnion) OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Union); + + SpatialReference sr = SpatialReference.create(4326); + + GeometryCursor outputCursor = union.execute(inputGeometries, sr, null); + Geometry result = outputCursor.next(); + } + + static double randomWithRange(double min, double max, Random random) { + double range = Math.abs(max - min); + return (random.nextDouble() * range) + (min <= max ? min : max); + } // @Test @@ -123,106 +123,106 @@ static double randomWithRange(double min, double max, Random random) { // assertTrue(result.calculateArea2D() > 40 * 40); // } - @Test - public void testQuadTreeIterator() { - int size = 1000; - ArrayDeque bufferedArrayDequ = new ArrayDeque<>(new ArrayList<>(bufferedPointList).subList(0, size)); - SimpleGeometryCursor simpleGeometryCursor = new SimpleGeometryCursor(bufferedArrayDequ); - - HashMap m_quadTreeMap = new HashMap<>(); - Envelope2D quad_envelope2D = new Envelope2D(); - Geometry geometry = null; - Envelope2D geometry_env = new Envelope2D(); - int count_index = 0; - while ((geometry = simpleGeometryCursor.next()) != null) { - geometry.queryEnvelope2D(geometry_env); - quad_envelope2D.merge(geometry_env); - m_quadTreeMap.put(count_index++, geometry); - } - QuadTree quadTree = new QuadTree(quad_envelope2D, 16); - for (Integer element_id : m_quadTreeMap.keySet()) { - m_quadTreeMap.get(element_id).queryEnvelope2D(geometry_env); - quadTree.insert(element_id, geometry_env); - } - - QuadTree.QuadTreeIterator quadTreeIterator = quadTree.getIterator(true); - quadTreeIterator.resetIterator(quad_envelope2D, 0.0); - int element_handle = -1; - List geometryList = new ArrayList<>(); - assertFalse(geometryList.containsAll(m_quadTreeMap.values())); - - int max_height = 0; - while ((element_handle = quadTreeIterator.next()) != -1) { - int element_id = quadTree.getElement(element_handle); - int quad_handle = quadTree.getQuad(element_handle); - - int sub_count = quadTree.getContainedSubTreeElementCount(quad_handle); - int sub_count_2 = quadTree.getSubTreeElementCount(quad_handle); - - int quad_height = quadTree.getHeight(quad_handle); - max_height = quad_height > max_height ? quad_height : max_height; - Envelope2D envelope2D = quadTree.getExtent(quad_handle); - assertTrue(quadTree.hasData(envelope2D, 0.0)); - geometryList.add(m_quadTreeMap.get(element_id)); - } - assertTrue(max_height == 3); - assertEquals(16, quadTree.getMaxHeight()); - - assertTrue(geometryList.containsAll(m_quadTreeMap.values())); - } - - - @Test - public static void testFailedUnionDifferenceTest() { - // shapely result for union - String shapelyWKT = "POLYGON ((-14.50430349127707 -22.07059031842225, -14.51281964335354 -22.0769063272619, -14.72325338383755 -22.20303545737643, -14.94503664498982 -22.30793112778376, -15.17603353276639 -22.39058313583606, -15.41401942086222 -22.45019549751362, -15.65670237507864 -22.48619411318603, -15.90174522590254 -22.49823229650554, -16.14678807672644 -22.48619411318603, -16.38947103094286 -22.45019549751362, -16.4990865892769 -22.42273822951685, -16.65632769437413 -22.44606274412307, -16.90137054519803 -22.45810092744258, -17.14641339602193 -22.44606274412307, -17.38909635023835 -22.41006412845066, -17.62708223833419 -22.35045176677311, -17.85807912611075 -22.2677997587208, -17.86833950280889 -22.2629469619552, -17.93163213536989 -22.26605632960654, -18.15038036764053 -22.25530991806244, -18.32212283929288 -22.31676035701235, -18.56010872738872 -22.37637271868991, -18.80279168160514 -22.41237133436232, -19.04783453242904 -22.42440951768183, -19.29287738325294 -22.41237133436232, -19.53556033746936 -22.37637271868991, -19.63541513605011 -22.3513603937329, -19.66115167971507 -22.35009603841869, -19.90383463393149 -22.31409742274627, -20.14182052202732 -22.25448506106872, -20.37281740980389 -22.17183305301641, -20.42744614576441 -22.14599558515562, -20.45917099079353 -22.14128964893872, -20.69715687888937 -22.08167728726117, -20.92815376666594 -21.99902527920887, -21.14993702781821 -21.89412960880153, -21.36037076830222 -21.76800047868701, -21.55742839616233 -21.62185258133749, -21.73921213871958 -21.45709340089702, -21.90397131916006 -21.27530965833976, -22.05011921650958 -21.07825203047966, -22.1762483466241 -20.86781828999564, -22.28114401703143 -20.64603502884338, -22.36379602508374 -20.41503814106681, -22.4234083867613 -20.17705225297097, -22.45940700243371 -19.93436929875455, -22.47144518575322 -19.68932644793065, -22.46272989236197 -19.51192257546279, -22.48179191062821 -19.38341688656973, -22.49383009394772 -19.13837403574583, -22.48179191062821 -18.89333118492193, -22.4457932949558 -18.65064823070551, -22.38618093327824 -18.41266234260967, -22.3612750894556 -18.34305517218451, -22.39279909447611 -18.21720428958194, -22.42879771014853 -17.97452133536552, -22.44083589346804 -17.72947848454162, -22.42879771014853 -17.48443563371772, -22.39279909447611 -17.2417526795013, -22.33318673279856 -17.00376679140546, -22.29704609402757 -16.90276047256235, -22.3734870679685 -16.68912226225063, -22.43309942964606 -16.4511363741548, -22.46909804531847 -16.20845341993838, -22.48113622863798 -15.96341056911447, -22.46909804531847 -15.71836771829057, -22.43309942964606 -15.47568476407415, -22.40572505302204 -15.36640012664985, -22.42770850858644 -15.21819970725584, -22.43543587636467 -15.18735032582566, -22.47143449203709 -14.94466737160924, -22.48347267535659 -14.69962452078534, -22.47143449203709 -14.45458166996143, -22.43543587636467 -14.21189871574502, -22.37582351468712 -13.97391282764918, -22.29317150663481 -13.74291593987261, -22.29056292959623 -13.73740056711855, -22.29289905870317 -13.68984756671441, -22.29252178711778 -13.68216802717211, -22.31377473535278 -13.62277001599185, -22.37338709703034 -13.38478412789601, -22.40938571270275 -13.14210117367959, -22.42142389602226 -12.89705832285569, -22.40938571270275 -12.65201547203179, -22.37338709703034 -12.40933251781537, -22.31377473535278 -12.17134662971954, -22.23112272730048 -11.94034974194297, -22.19817821472779 -11.87069442245198, -22.19792766193782 -11.8655943032475, -22.16192904626541 -11.62291134903108, -22.10231668458785 -11.38492546093525, -22.01966467653555 -11.15392857315868, -21.99191048529292 -11.09524726609872, -21.92842458012701 -10.91781604746789, -21.8468060799781 -10.74524822781346, -21.92173834994376 -10.5868172541232, -22.00439035799606 -10.35582036634663, -22.01967276381155 -10.29480958237682, -22.06069364785215 -10.23949932305483, -22.18682277796667 -10.02906558257082, -22.291718448374 -9.807282321418555, -22.37437045642631 -9.576285433641985, -22.43398281810386 -9.33829954554615, -22.46998143377628 -9.09561659132973, -22.48201961709579 -8.850573740505828, -22.46998143377628 -8.605530889681926, -22.4573686685416 -8.520502533151651, -22.45836137069585 -8.500295616589442, -22.44973433808717 -8.324688333623628, -22.46112672765752 -8.24788708155117, -22.47316491097703 -8.002844230727268, -22.46112672765752 -7.757801379903367, -22.4251281119851 -7.515118425686947, -22.36551575030755 -7.277132537591112, -22.28286374225524 -7.046135649814543, -22.27337292135948 -7.026068995003135, -22.26191986068643 -6.792936589265861, -22.23700130787304 -6.624949351462579, -22.27702191722613 -6.465178122962197, -22.31302053289855 -6.222495168745778, -22.32505871621806 -5.977452317921876, -22.31302053289855 -5.732409467097974, -22.29913718581248 -5.638815543489387, -22.30871515529535 -5.443851495767794, -22.29667697197584 -5.198808644943893, -22.29060748755867 -5.157891504620128, -22.33815811338089 -5.024996408232262, -22.39777047505845 -4.787010520136427, -22.41659300653852 -4.660119320014077, -22.42566673888573 -4.623894949929971, -22.46166535455815 -4.381211995713552, -22.47370353787766 -4.13616914488965, -22.46166535455815 -3.891126294065749, -22.42631024597718 -3.652781512278879, -22.4441467206235 -3.532537767370365, -22.456184903943 -3.287494916546463, -22.4441467206235 -3.042452065722562, -22.40814810495108 -2.799769111506142, -22.40429375712431 -2.784381692441201, -22.4058295706438 -2.753119490067372, -22.42652472864004 -2.613604067413182, -22.43856291195954 -2.36856121658928, -22.42889752836875 -2.171817813823874, -22.46220332702011 -1.947288357391422, -22.47424151033962 -1.702245506567521, -22.46220332702011 -1.457202655743619, -22.42620471134769 -1.214519701527199, -22.36718807520162 -0.9789120830667674, -22.35689086573897 -0.7693075692219906, -22.32089225006656 -0.5266246150055711, -22.261279888389 -0.2886387269097357, -22.20425046928654 -0.1292521779944437, -22.23865998358758 -0.03308402793069509, -22.29827234526513 0.2049018601651402, -22.33427096093755 0.4475848143815596, -22.34532001016119 0.6724933781402493, -22.35146943767771 0.7139494510634626, -22.36350762099722 0.9589923018873644, -22.3592771699918 1.045105110367362, -22.38658792514858 1.154135756656995, -22.422586540821 1.396818710873414, -22.43462472414051 1.641861561697316, -22.422586540821 1.886904412521218, -22.38658792514858 2.129587366737637, -22.37396184882762 2.1799934889219, -22.38695198811661 2.216298496123226, -22.44656434979417 2.454284384219061, -22.48256296546658 2.69696733843548, -22.49460114878609 2.942010189259382, -22.48256296546658 3.187053040083284, -22.44656434979417 3.429735994299703, -22.38695198811661 3.667721882395539, -22.38282310228573 3.679261345251841, -22.40482289476282 3.827571899165153, -22.41686107808233 4.072614749989055, -22.40482289476282 4.317657600812956, -22.36882427909041 4.560340555029376, -22.30921191741285 4.798326443125212, -22.30467603942607 4.811003372857128, -22.36363753509564 4.975789714039571, -22.42324989677319 5.213775602135406, -22.45924851244561 5.456458556351826, -22.47128669576512 5.701501407175727, -22.45924851244561 5.946544257999629, -22.42520472119385 6.176048856423258, -22.43263431641783 6.22613511901725, -22.44467249973734 6.471177969841151, -22.43263431641783 6.716220820665053, -22.39663570074542 6.958903774881473, -22.39454427325957 6.967253221432506, -22.394861026681 6.969388599547307, -22.4068992100005 7.214431450371209, -22.394861026681 7.459474301195111, -22.36111420259841 7.686976909581786, -22.37179943577599 7.904479827593563, -22.35976125245649 8.149522678417465, -22.32376263678407 8.392205632633884, -22.26415027510652 8.630191520729721, -22.2200347057179 8.75348627752272, -22.21708918363268 8.813443756967004, -22.18109056796027 9.056126711183422, -22.12147820628271 9.294112599279259, -22.03882619823041 9.525109487055827, -22.00373838951193 9.599296425506701, -22.04396671166407 9.711727054073364, -22.05359014051572 9.732074084850684, -22.13624214856803 9.963070972627253, -22.19585451024558 10.20105686072309, -22.231853125918 10.44373981493951, -22.2438913092375 10.68878266576341, -22.231853125918 10.93382551658731, -22.19678075951196 11.1702642054589, -22.25477895783326 11.29289109286717, -22.33743096588557 11.52388798064374, -22.39704332756312 11.76187386873957, -22.43304194323554 12.00455682295599, -22.44508012655504 12.2495996737799, -22.43304194323554 12.4946425246038, -22.39704332756312 12.73732547882022, -22.33743096588557 12.97531136691605, -22.25477895783326 13.20630825469262, -22.14988328742593 13.42809151584489, -22.10462297699719 13.50360378180867, -22.16716011951175 13.67838338646343, -22.22677248118931 13.91636927455927, -22.26277109686172 14.15905222877569, -22.26462335331059 14.19675577495375, -22.29302006696381 14.31012181058106, -22.32901868263623 14.55280476479748, -22.34105686595574 14.79784761562138, -22.33192658845886 14.98369868361621, -22.39050818812954 15.21756953911278, -22.42650680380195 15.4602524933292, -22.43547952265877 15.64289638152731, -22.44403823256925 15.6770646667903, -22.48003684824166 15.91974762100672, -22.49207503156117 16.16479047183062, -22.48003684824166 16.40983332265453, -22.44403823256925 16.65251627687095, -22.40636946919279 16.80289840992767, -22.41197150930309 16.84066429670454, -22.4240096926226 17.08570714752844, -22.41197150930309 17.33074999835235, -22.38181414347826 17.53405444473314, -22.38305833385616 17.55938052168966, -22.37260135853582 17.7722371442781, -22.37766358477907 17.80636390156245, -22.38970176809858 18.05140675238635, -22.37766358477907 18.29644960321025, -22.35761020545555 18.4316385054875, -22.35423385062678 18.50036578689496, -22.37025623714802 18.60837994304902, -22.38229442046752 18.85342279387292, -22.37025623714802 19.09846564469682, -22.3342576214756 19.34114859891324, -22.30537123841755 19.45646950438467, -22.32604178702352 19.5958190240056, -22.33807997034302 19.8408618748295, -22.32604178702352 20.0859047256534, -22.2900431713511 20.32858767986982, -22.23043080967355 20.56657356796565, -22.14777880162124 20.79757045574222, -22.04288313121391 21.01935371689449, -21.91675400109939 21.22978745737851, -21.77060610374987 21.42684508523861, -21.60584692330939 21.60862882779587, -21.42406318075214 21.77338800823634, -21.22700555289203 21.91953590558586, -21.01657181240802 22.04566503570039, -20.79478855125575 22.15056070610771, -20.56379166347918 22.23321271416002, -20.4307583865337 22.26653581530638, -20.41830449611981 22.27242606679497, -20.18730760834324 22.35507807484727, -19.9493217202474 22.41469043652483, -19.70663876603098 22.45068905219724, -19.46159591520708 22.46272723551675, -19.21655306438318 22.45068905219724, -18.97387011016676 22.41469043652483, -18.73588422207093 22.35507807484728, -18.64505406713305 22.32257852574262, -18.40782727181207 22.3820007445785, -18.16514431759565 22.41799936025091, -17.92010146677175 22.43003754357042, -17.67505861594785 22.41799936025091, -17.5503671284345 22.399503125313, -17.43587274407975 22.41648676288385, -17.19082989325585 22.42852494620336, -16.94578704243195 22.41648676288385, -16.89031477907391 22.40825822991765, -16.82115409860222 22.41851724775424, -16.57611124777833 22.43055543107375, -16.39788303796649 22.42179964058564, -16.35038438081903 22.43369743482768, -16.10770142660261 22.46969605050009, -15.86265857577871 22.4817342338196, -15.61761572495481 22.46969605050009, -15.37493277073839 22.43369743482768, -15.13694688264255 22.37408507315013, -14.90594999486599 22.29143306509782, -14.8206581025195 22.25109300434882, -14.71557451981399 22.22477093715091, -14.65976147368973 22.20480070992366, -14.48458465159365 22.1788156830242, -14.45234634580798 22.17074040780624, -14.29831807419653 22.20932248134571, -14.05563511998011 22.24532109701812, -14.02162334345316 22.24699198845396, -13.99875298466096 22.2527207151063, -13.86995584245114 22.27182596638811, -13.81075298923094 22.29300908598844, -13.57276710113511 22.352621447666, -13.33008414691869 22.38862006333841, -13.08504129609479 22.40065824665792, -12.83999844527089 22.38862006333841, -12.60371436525269 22.35357063098932, -12.54382727719349 22.37499857372977, -12.30584138909765 22.43461093540733, -12.06315843488124 22.47060955107974, -11.81811558405734 22.48264773439925, -11.57307273323344 22.47060955107974, -11.33038977901702 22.43461093540733, -11.29538315086152 22.42584223153411, -11.23826008938479 22.42864850759365, -10.99321723856089 22.41661032427414, -10.75053428434447 22.38061170860173, -10.51254839624864 22.32099934692418, -10.33580272181398 22.25775873339386, -10.18534084118114 22.31159485512403, -9.947354953085307 22.37120721680159, -9.704671998868889 22.407205832474, -9.459629148044989 22.41924401579351, -9.214586297221089 22.407205832474, -9.04471806358278 22.38200826028587, -9.005397594027519 22.38393995108657, -8.793185646901277 22.43709627663851, -8.550502692684859 22.47309489231092, -8.305459841860959 22.48513307563043, -8.060416991037059 22.47309489231092, -7.81773403682064 22.43709627663851, -7.624786122499572 22.38876534010497, -7.42410576579648 22.39862413384041, -7.179062914972579 22.3865859505209, -6.93637996075616 22.35058733484848, -6.698394072660326 22.29097497317093, -6.541298596980119 22.23476531317993, -6.535699926618456 22.2350403582177, -6.290657075794555 22.22300217489819, -6.047974121578137 22.18700355922577, -6.045368531132135 22.18635089279545, -5.849769324545464 22.23534594346918, -5.740285956380461 22.25158626700496, -5.519323966714309 22.30693436411426, -5.276641012497891 22.34293297978667, -5.03159816167399 22.35497116310618, -4.78655531085009 22.34293297978667, -4.543872356633671 22.30693436411426, -4.462919229365399 22.28665666134685, -4.385824222448885 22.30596795527529, -4.143141268232466 22.34196657094771, -3.898098417408566 22.35400475426722, -3.653055566584665 22.34196657094771, -3.410372612368247 22.30596795527529, -3.383074108523836 22.29913003602953, -3.198321286899955 22.32653552827561, -2.953278436076054 22.33857371159512, -2.765340328481469 22.32934090441736, -2.64678250665151 22.37176157137471, -2.408796618555676 22.43137393305226, -2.166113664339258 22.46737254872468, -1.921070813515357 22.47941073204419, -1.676027962691456 22.46737254872468, -1.433345008475038 22.43137393305226, -1.195359120379203 22.37176157137471, -1.142651755126477 22.35290257453187, -0.9413867209710468 22.32304772693349, -0.7244596559357803 22.26871032582958, -0.5518773903080429 22.27718874886595, -0.3068345394841422 22.26515056554645, -0.0641515852677238 22.22915194987403, -0.03697761262200187 22.22234522406968, 0.0234066550944344 22.23747069573335, 0.2660896093108527 22.27346931140577, 0.5111324601347533 22.28550749472527, 0.756175310958654 22.27346931140577, 0.9988582651750725 22.23747069573335, 1.124606770200215 22.205972334961, 1.299512951183388 22.28869679765171, 1.530509838959956 22.37134880570402, 1.540541675798155 22.37386165001876, 1.580150144942097 22.38803378689097, 1.818136033037931 22.44764614856852, 2.06081898725435 22.48364476424094, 2.305861838078251 22.49568294756045, 2.550904688902151 22.48364476424094, 2.585818149549957 22.47846584157736, 2.636750143146877 22.48096796997526, 2.881792993970778 22.46892978665575, 3.124475948187196 22.43293117098334, 3.362461836283031 22.37331880930579, 3.405605077979647 22.35788191059068, 3.646083997305915 22.36969588233315, 3.891126848129816 22.35765769901364, 4.133809802346234 22.32165908334122, 4.371795690442069 22.26204672166367, 4.602792578218637 22.17939471361137, 4.74349612212289 22.11284689350158, 4.862426595900769 22.16909681837858, 5.093423483677336 22.25174882643089, 5.33140937177317 22.31136118810844, 5.574092325989588 22.34735980378086, 5.71264440274838 22.35416643084103, 5.788609578672952 22.38134720540751, 6.026595466768787 22.44095956708507, 6.269278420985205 22.47695818275748, 6.514321271809106 22.48899636607699, 6.759364122633007 22.47695818275748, 7.002047076849425 22.44095956708507, 7.045062493122757 22.43018476622139, 7.172998475183696 22.44916227646209, 7.418041326007597 22.4612004597816, 7.663084176831497 22.44916227646209, 7.905767131047916 22.41316366078968, 8.14375301914375 22.35355129911213, 8.374749906920318 22.27089929105982, 8.502525895102208 22.21046574944508, 8.674222684091824 22.14903165601327, 8.730173203256816 22.12256903125539, 8.755476828124921 22.12890725933043, 8.928144125486533 22.15452003340014, 9.056924723896049 22.21542872026604, 9.287921611672617 22.29808072831835, 9.525907499768453 22.35769308999591, 9.768590453984871 22.39369170566832, 10.01363330480877 22.40572988898783, 10.25867615563267 22.39369170566832, 10.38434897519156 22.37504990387232, 10.54562876847384 22.38297307204775, 10.79067161929774 22.37093488872825, 11.03335457351416 22.33493627305583, 11.27134046160999 22.27532391137828, 11.50233734938656 22.19267190332598, 11.5620735362037 22.1644187911154, 11.62238858930415 22.1614557025629, 11.65303800255386 22.15690929158103, 11.69067400012796 22.15875822957978, 11.93571685095186 22.14672004626027, 12.17839980516828 22.11072143058786, 12.41638569326412 22.05110906891031, 12.60921408285645 21.98211396788231, 12.61413707018288 21.98444236747987, 12.84513395795945 22.06709437553218, 13.08311984605528 22.12670673720973, 13.3258028002717 22.16270535288215, 13.5708456510956 22.17474353620165, 13.8158885019195 22.16270535288215, 14.0115966916425 22.13367478529024, 14.24606942960151 22.19240714866933, 14.48875238381793 22.22840576434175, 14.73379523464183 22.24044394766126, 14.83509011495141 22.23546764929387, 14.84302222492603 22.23830580362489, 15.08100811302187 22.29791816530244, 15.32369106723829 22.33391678097486, 15.56873391806219 22.34595496429436, 15.81377676888609 22.33391678097486, 16.05645972310251 22.29791816530244, 16.19915562030759 22.26217470377977, 16.35886794836413 22.31932068852286, 16.59685383645996 22.37893305020042, 16.83953679067638 22.41493166587283, 17.08457964150028 22.42696984919234, 17.32962249232418 22.41493166587283, 17.35406316190014 22.41130623501518, 17.36686412505037 22.41193510600877, 17.61190697587427 22.39989692268926, 17.85458993009069 22.36389830701685, 18.01708231883724 22.3231960825055, 18.18753309746713 22.38418434630179, 18.42551898556296 22.44379670797935, 18.66820193977938 22.47979532365176, 18.91324479060328 22.49183350697127, 19.15828764142718 22.47979532365176, 19.4009705956436 22.44379670797935, 19.63895648373943 22.38418434630179, 19.869953371516 22.30153233824949, 19.96551028205333 22.25633728547234, 19.9853426039062 22.24924116724643, 20.20712586505847 22.1443454968391, 20.41755960554248 22.01821636672458, 20.61461723340259 21.87206846937505, 20.79640097595984 21.70730928893459, 20.96116015640032 21.52552554637733, 20.97836787083578 21.50232362935326, 21.02993804195481 21.46407658377149, 21.1524925145701 21.35299968446598, 21.17995449857986 21.3365396046974, 21.37701212643996 21.19039170734787, 21.55879586899722 21.0256325269074, 21.72355504943769 20.84384878435015, 21.86970294678721 20.64679115649004, 21.99583207690174 20.43635741600603, 22.10072774730907 20.21457415485376, 22.18337975536138 19.9835772670772, 22.24299211703893 19.74559137898136, 22.27899073271135 19.50290842476495, 22.29102891603086 19.25786557394105, 22.29102891603086 19.25786557394103, 22.27899073271135 19.01282272311713, 22.25965275264398 18.88245665087605, 22.30806847593428 18.68917024884808, 22.3440670916067 18.44648729463166, 22.35610527492621 18.20144444380776, 22.35610527492621 18.20144444380774, 22.3440670916067 17.95640159298384, 22.32364979959007 17.81875939060629, 22.32637898934617 17.8111318162174, 22.38599135102372 17.57314592812157, 22.42198996669614 17.33046297390515, 22.43402815001565 17.08542012308125, 22.43402815001565 17.08542012308123, 22.42198996669614 16.84037727225733, 22.38599135102373 16.59769431804091, 22.32637898934617 16.35970842994507, 22.24372698129387 16.1287115421685, 22.13883131088654 15.90692828101623, 22.06904229209649 15.79049233750846, 22.10726844121714 15.70966993577967, 22.18992044926945 15.47867304800311, 22.24953281094701 15.24068715990727, 22.28553142661942 14.99800420569085, 22.29756960993893 14.75296135486695, 22.29756960993893 14.75296135486693, 22.28553142661942 14.50791850404303, 22.25769045232944 14.3202299009467, 22.29909198906613 14.15494570130185, 22.33509060473855 13.91226274708543, 22.34712878805806 13.66721989626153, 22.34712878805806 13.66721989626151, 22.33509060473855 13.42217704543761, 22.29909198906613 13.17949409122119, 22.23947962738858 12.94150820312535, 22.15682761933628 12.71051131534878, 22.1439806270021 12.68334863222938, 22.19843347304785 12.46596068515155, 22.23443208872027 12.22327773093513, 22.24647027203978 11.97823488011123, 22.24647027203978 11.97823488011121, 22.23443208872027 11.73319202928731, 22.22014157634299 11.63685322024689, 22.29170552965104 11.48554395377313, 22.37435753770335 11.25454706599656, 22.4339698993809 11.01656117790073, 22.46996851505332 10.77387822368431, 22.48200669837283 10.52883537286041, 22.48200669837283 10.52883537286039, 22.46996851505332 10.28379252203649, 22.43396989938091 10.04110956782007, 22.37435753770335 9.803123679724234, 22.34931049101803 9.733121873734932, 22.34643056114344 9.674499554410479, 22.31043194547102 9.431816600194061, 22.29103373003705 9.354374583175842, 22.30688482479567 9.310073741086473, 22.36649718647323 9.07208785299064, 22.40249580214564 8.829404898774222, 22.41453398546516 8.584362047950322, 22.41453398546516 8.5843620479503, 22.41010307540168 8.494168801603886, 22.43814828827776 8.382206035739104, 22.47414690395017 8.139523081522686, 22.48618508726968 7.894480230698785, 22.48618508726968 7.894480230698765, 22.47414690395017 7.649437379874864, 22.43814828827776 7.406754425658445, 22.3785359266002 7.16876853756261, 22.36084912120376 7.119337227936076, 22.36638055208554 7.082047349912735, 22.37841873540505 6.837004499088834, 22.37841873540505 6.837004499088813, 22.36638055208554 6.591961648264912, 22.35609034175698 6.522590685093183, 22.36222582230357 6.397700109463268, 22.36222582230357 6.397700109463248, 22.35018763898406 6.152657258639347, 22.31418902331165 5.909974304422928, 22.25457666163409 5.671988416327093, 22.17192465358179 5.440991528550525, 22.14187238184646 5.377451338525399, 22.13631459835018 5.264320056500067, 22.11639472283721 5.130031163807564, 22.14119387758969 5.060722169358346, 22.20080623926725 4.822736281262513, 22.20182312370297 4.815881003237257, 22.28678781110266 4.636238265065981, 22.36943981915496 4.405241377289414, 22.42905218083252 4.167255489193581, 22.46505079650493 3.924572534977163, 22.47708897982444 3.679529684153262, 22.47708897982444 3.679529684153241, 22.46505079650494 3.43448683332934, 22.42905218083252 3.191803879112921, 22.42622722872951 3.18052603813927, 22.41556802494946 2.963552960686345, 22.37956940927705 2.720870006469926, 22.37655704668347 2.708843980878516, 22.37665731548945 2.70816802349524, 22.38869549880896 2.463125172671339, 22.38869549880896 2.463125172671319, 22.37665731548946 2.218082321847417, 22.34065869981704 1.975399367630999, 22.31304697431726 1.86516718037985, 22.32294718632245 1.837497941417382, 22.38255954800001 1.599512053321549, 22.41855816367242 1.356829099105131, 22.43059634699193 1.11178624828123, 22.43059634699193 1.111786248281209, 22.41855816367243 0.866743397457308, 22.38255954800001 0.6240604432408892, 22.37015764437406 0.5745492686213595, 22.37034038738471 0.5740385361211262, 22.42995274906226 0.3360526480252923, 22.46595136473468 0.09336969380887428, 22.46983284362373 0.0143603759458221, 22.47762299880658 -0.03815658445019221, 22.48966118212609 -0.2831994352740928, 22.48966118212609 -0.2831994352741134, 22.47762299880658 -0.5282422860980147, 22.44162438313417 -0.7709252403144335, 22.38201202145661 -1.008911128410269, 22.33733720919316 -1.1337688643328, 22.35960416230565 -1.222663524140609, 22.39560277797807 -1.465346478357027, 22.40764096129758 -1.710389329180928, 22.40764096129758 -1.710389329180948, 22.39560277797807 -1.95543218000485, 22.35960416230566 -2.198115134221269, 22.2999918006281 -2.436101022317104, 22.2173397925758 -2.667097910093672, 22.12083454788258 -2.871141106569248, 22.10890457508578 -3.113981281601384, 22.09753928949359 -3.190599813495125, 22.11425511370344 -3.225942454346874, 22.19690712175575 -3.456939342123441, 22.2565194834333 -3.694925230219274, 22.27796733187449 -3.83951487968404, 22.30329211813286 -3.893059641470262, 22.38594412618517 -4.124056529246829, 22.44555648786272 -4.362042417342662, 22.48155510353514 -4.604725371559081, 22.49359328685465 -4.849768222382981, 22.49359328685465 -4.849768222383002, 22.48155510353514 -5.094811073206903, 22.46639370225972 -5.197020938304527, 22.4742428718067 -5.35679445490105, 22.4742428718067 -5.35679445490107, 22.47305251707211 -5.381024682163306, 22.47387421054751 -5.386564089619583, 22.48591239386703 -5.631606940443484, 22.48591239386703 -5.631606940443504, 22.47387421054752 -5.876649791267405, 22.4378755948751 -6.119332745483824, 22.37826323319755 -6.357318633579659, 22.37581970840857 -6.364147827869776, 22.33416054986185 -6.530460511431846, 22.25150854180955 -6.761457399208414, 22.14661287140222 -6.983240660360684, 22.14070790264234 -6.993092505548669, 22.14375134269476 -7.001598350679505, 22.20336370437232 -7.239584238775338, 22.23936232004473 -7.482267192991756, 22.25140050336424 -7.727310043815657, 22.25140050336424 -7.727310043815677, 22.23936232004474 -7.972352894639578, 22.20336370437232 -8.215035848855997, 22.17904866103241 -8.312106943180988, 22.25649042501777 -8.528542174291447, 22.31610278669532 -8.76652806238728, 22.32621521933611 -8.834700545738135, 22.32751997111001 -8.838347082660768, 22.38713233278757 -9.076332970756601, 22.42313094845998 -9.319015924973019, 22.4351691317795 -9.564058775796919, 22.4351691317795 -9.564058775796941, 22.42313094845999 -9.809101626620842, 22.38713233278757 -10.05178458083726, 22.32751997111002 -10.2897704689331, 22.24486796305771 -10.52076735670966, 22.13997229265038 -10.74255061786193, 22.04314691805991 -10.90409399590901, 22.03406561057082 -10.96531519812561, 22.06337928494946 -11.16293193877699, 22.06660106476485 -11.22851277350407, 22.08298043995721 -11.27429006049709, 22.14259280163477 -11.51227594859293, 22.17739333308725 -11.74688207506299, 22.23461413935799 -11.90680351528065, 22.29422650103554 -12.14478940337648, 22.33022511670796 -12.3874723575929, 22.34226330002747 -12.6325152084168, 22.34226330002747 -12.63251520841682, 22.33022511670796 -12.87755805924072, 22.30844502746572 -13.02438749425295, 22.34721627930011 -13.13274588408515, 22.40682864097767 -13.37073177218098, 22.44282725665008 -13.6134147263974, 22.45486543996959 -13.8584575772213, 22.45486543996959 -13.85845757722132, 22.44282725665009 -14.10350042804522, 22.40682864097767 -14.34618338226164, 22.40667909943321 -14.3467803855721, 22.41507202806971 -14.3802868348902, 22.45107064374212 -14.62296978910662, 22.46310882706163 -14.86801263993052, 22.46310882706163 -14.86801263993054, 22.45107064374212 -15.11305549075445, 22.41507202806971 -15.35573844497086, 22.37320910548004 -15.52286460019147, 22.36618663574583 -15.66581025551359, 22.33018802007341 -15.90849320973001, 22.30832089851852 -15.99579165248403, 22.31582939514044 -16.01677649474516, 22.375441756818 -16.254762382841, 22.41144037249041 -16.49744533705741, 22.42347855580992 -16.74248818788131, 22.42347855580992 -16.74248818788134, 22.41144037249041 -16.98753103870524, 22.375441756818 -17.23021399292166, 22.31582939514044 -17.46819988101749, 22.311953937675 -17.47903105904116, 22.3018715319233 -17.68426314290183, 22.26587291625088 -17.92694609711825, 22.20998447691467 -18.15006525387409, 22.19944723477361 -18.36455574513298, 22.1634486191012 -18.6072386993494, 22.10383625742364 -18.84522458744524, 22.04941193378315 -18.99733038228982, 22.04364672206227 -19.11468396282544, 22.00764810638986 -19.35736691704186, 21.97709163753492 -19.47935517875256, 21.97514755279599 -19.51892793271611, 21.93914893712358 -19.76161088693253, 21.87953657544602 -19.99959677502837, 21.79688456739372 -20.23059366280494, 21.69198889698639 -20.45237692395721, 21.64536097446595 -20.53017091284831, 21.58631018238545 -20.69520682078059, 21.48141451197812 -20.91699008193286, 21.35528538186359 -21.12742382241687, 21.20913748451407 -21.32448145027698, 21.0443783040736 -21.50626519283423, 20.86259456151634 -21.67102437327471, 20.66553693365623 -21.81717227062423, 20.45510319317222 -21.94330140073875, 20.23331993201996 -22.04819707114608, 20.00232304424339 -22.13084907919839, 19.76433715614755 -22.19046144087594, 19.52165420193113 -22.22646005654836, 19.27661135110723 -22.23849823986787, 19.03156850028333 -22.22646005654836, 18.78888554606691 -22.19046144087594, 18.73887089664221 -22.17793342337652, 18.4973901430078 -22.18979661208253, 18.41784477514719 -22.18588879874579, 18.41349373531924 -22.18697867748596, 18.17081078110282 -22.22297729315838, 17.98787095563432 -22.23196455048102, 17.79768532119148 -22.27960357192466, 17.55500236697506 -22.31560218759708, 17.30995951615116 -22.32764037091659, 17.06491666532726 -22.31560218759708, 16.82223371111084 -22.27960357192466, 16.584247823015 -22.21999121024711, 16.4666967485392 -22.177930763253, 16.4099384081795 -22.1695114587847, 16.28838315841296 -22.13906345377539, 16.17480211986003 -22.19278328422008, 15.94380523208346 -22.27543529227239, 15.70581934398762 -22.33504765394994, 15.46313638977121 -22.37104626962236, 15.2180935389473 -22.38308445294187, 14.9730506881234 -22.37104626962236, 14.73036773390698 -22.33504765394994, 14.65305301275748 -22.31568132447116, 14.45362419243909 -22.3452637954767, 14.20858134161519 -22.35730197879621, 13.96353849079129 -22.3452637954767, 13.73523415567763 -22.31139804646833, 13.59984162375042 -22.33148163139707, 13.35479877292652 -22.34351981471658, 13.10975592210262 -22.33148163139707, 12.8670729678862 -22.29548301572466, 12.62908707979036 -22.2358706540471, 12.61296722408551 -22.23010287744914, 12.51509107260605 -22.26512352442874, 12.27710518451022 -22.3247358861063, 12.0344222302938 -22.36073450177871, 11.7893793794699 -22.37277268509822, 11.544336528646 -22.36073450177871, 11.42614034228609 -22.34320175375175, 11.41367609013224 -22.34505065090392, 11.17312139811863 -22.35686834511981, 11.0505742524468 -22.38756480711927, 10.96704756934117 -22.39995482014355, 10.89076207090825 -22.41906334275269, 10.64807911669183 -22.4550619584251, 10.40303626586793 -22.46710014174461, 10.15799341504403 -22.4550619584251, 9.915310460827609 -22.41906334275269, 9.678390165997076 -22.35971789829296, 9.461434530251323 -22.39190022677332, 9.216391679427421 -22.40393841009282, 8.971348828603521 -22.39190022677332, 8.859745401813472 -22.37534542224775, 8.640809946866888 -22.43018589883542, 8.39812699265047 -22.46618451450784, 8.153084141826568 -22.47822269782734, 7.908041291002667 -22.46618451450784, 7.665358336786248 -22.43018589883542, 7.654227093690167 -22.42739766758913, 7.631127550771891 -22.42853247536382, 7.386084699947989 -22.41649429204432, 7.14340174573157 -22.3804956763719, 6.961547070081421 -22.33494345147172, 6.84664652751557 -22.31789956602152, 6.757417223308805 -22.33113548297839, 6.512374372484904 -22.3431736662979, 6.267331521661002 -22.33113548297839, 6.024648567444583 -22.29513686730598, 5.786662679348748 -22.23552450562843, 5.679434149468312 -22.19715752414906, 5.639137942656076 -22.19517789845072, 5.527756450060979 -22.2116997821483, 5.309290253954825 -22.22243233814412, 5.171949898293681 -22.28738948868023, 4.940953010517113 -22.37004149673253, 4.702967122421279 -22.42965385841009, 4.46028416820486 -22.4656524740825, 4.215241317380959 -22.47769065740201, 3.970198466557057 -22.4656524740825, 3.750904670430335 -22.43312331227301, 3.67246061444501 -22.44475938878411, 3.427417763621108 -22.45679757210362, 3.182374912797207 -22.44475938878411, 2.939691958580788 -22.40876077311169, 2.701706070484953 -22.34914841143414, 2.470709182708385 -22.26649640338184, 2.400539054657536 -22.233308404494, 2.382213028549373 -22.23058999531359, 2.144227140453538 -22.17097763363604, 1.91323025267697 -22.08832562558374, 1.772685063718212 -22.02185270178525, 1.740138581807164 -22.04599081787381, 1.529704841323152 -22.17211994798834, 1.307921580170883 -22.27701561839567, 1.076924692394315 -22.35966762644798, 0.8389388042984798 -22.41927998812553, 0.5962558500820608 -22.45527860379795, 0.3512129992581597 -22.46731678711745, 0.1061701484342584 -22.45527860379795, -0.04851836590371944 -22.43233273026278, -0.2461837018222049 -22.42262205500048, -0.4888666560386239 -22.38662343932807, -0.726852544134459 -22.32701107765052, -0.9578494319110273 -22.24435906959821, -1.125708636819748 -22.16496757836726, -1.139764547714639 -22.17161552911361, -1.370761435491207 -22.25426753716592, -1.608747323587042 -22.31387989884347, -1.851430277803461 -22.34987851451589, -2.096473128627362 -22.3619166978354, -2.341515979451263 -22.34987851451589, -2.455727455302357 -22.33293684245731, -2.55442301821249 -22.34757694624723, -2.617898317366901 -22.37028877144683, -2.855884205462736 -22.42990113312439, -3.098567159679154 -22.4658997487968, -3.343610010503056 -22.47793793211631, -3.588652861326957 -22.4658997487968, -3.831335815543376 -22.42990113312439, -3.907954521318272 -22.41070914642104, -3.975896008872092 -22.41404689767321, -4.183715500617316 -22.44487400721096, -4.428758351441217 -22.45691219053047, -4.673801202265119 -22.44487400721096, -4.916484156481538 -22.40887539153854, -5.020084557130864 -22.38292484210529, -5.020419078409338 -22.38304453573263, -5.258404966505172 -22.44265689741019, -5.501087920721591 -22.4786555130826, -5.746130771545492 -22.49069369640211, -5.991173622369394 -22.4786555130826, -6.123693431593027 -22.45899805631302, -6.264189488772653 -22.46590018500729, -6.509232339596555 -22.45386200168778, -6.751915293812974 -22.41786338601537, -6.989901181908809 -22.35825102433781, -7.220898069685378 -22.27559901628551, -7.347294079158904 -22.2158181559913, -7.532152849094219 -22.18839694779783, -7.598927497924632 -22.17167076899446, -7.804850114037394 -22.22325169913918, -8.047533068253813 -22.2592503148116, -8.292575919077715 -22.27128849813111, -8.537618769901615 -22.2592503148116, -8.565029213837116 -22.25518435954156, -8.633110179045801 -22.27954411840584, -8.871096067141636 -22.3391564800834, -9.113779021358054 -22.37515509575582, -9.358821872181956 -22.38719327907532, -9.603864723005856 -22.37515509575582, -9.846547677222276 -22.3391564800834, -10.08453356531811 -22.27954411840584, -10.09791748181934 -22.27475527650811, -10.14016295582498 -22.29473589765361, -10.37115984360155 -22.37738790570592, -10.60914573169738 -22.43700026738347, -10.8518286859138 -22.47299888305589, -11.0968715367377 -22.48503706637539, -11.3419143875616 -22.47299888305589, -11.58459734177802 -22.43700026738347, -11.75310292370707 -22.3947918163908, -11.90532691377749 -22.43292194093173, -12.1480098679939 -22.46892055660414, -12.39305271881781 -22.48095873992365, -12.63809556964171 -22.46892055660414, -12.88077852385813 -22.43292194093173, -13.11876441195396 -22.37330957925418, -13.20218180754802 -22.34346235785346, -13.35693885800207 -22.35106508422189, -13.60198170882597 -22.33902690090238, -13.84466466304239 -22.30302828522996, -14.08265055113823 -22.24341592355241, -14.31364743891479 -22.16076391550011, -14.50430349127707 -22.07059031842225))"; - - // epl result for union - String eplWKT = "MULTIPOLYGON (((-15.90174522590254 -22.49823229650554, -15.65670237507864 -22.48619411318603, -15.41401942086222 -22.45019549751362, -15.17603353276639 -22.39058313583606, -14.94503664498982 -22.30793112778376, -14.72325338383755 -22.20303545737643, -14.51281964335354 -22.0769063272619, -14.50430349127707 -22.07059031842225, -14.31364743891479 -22.16076391550011, -14.08265055113823 -22.24341592355241, -13.84466466304239 -22.30302828522996, -13.60198170882597 -22.33902690090238, -13.35693885800207 -22.35106508422189, -13.20218180754802 -22.34346235785346, -13.11876441195396 -22.37330957925418, -12.88077852385813 -22.43292194093173, -12.63809556964171 -22.46892055660414, -12.39305271881781 -22.48095873992365, -12.1480098679939 -22.46892055660414, -11.90532691377749 -22.43292194093173, -11.75310292370707 -22.3947918163908, -11.58459734177802 -22.43700026738347, -11.3419143875616 -22.47299888305589, -11.0968715367377 -22.48503706637539, -10.8518286859138 -22.47299888305589, -10.60914573169738 -22.43700026738347, -10.37115984360155 -22.37738790570592, -10.14016295582498 -22.29473589765361, -10.09791748181934 -22.27475527650811, -10.08453356531811 -22.27954411840584, -9.846547677222276 -22.3391564800834, -9.603864723005856 -22.37515509575582, -9.358821872181956 -22.38719327907532, -9.113779021358054 -22.37515509575582, -8.871096067141636 -22.3391564800834, -8.633110179045801 -22.27954411840584, -8.565029213837116 -22.25518435954156, -8.537618769901615 -22.2592503148116, -8.292575919077715 -22.27128849813111, -8.047533068253813 -22.2592503148116, -7.804850114037394 -22.22325169913918, -7.598927497924632 -22.17167076899446, -7.532152849094219 -22.18839694779783, -7.347294079158904 -22.2158181559913, -7.220898069685378 -22.27559901628551, -6.989901181908809 -22.35825102433781, -6.751915293812974 -22.41786338601537, -6.509232339596555 -22.45386200168778, -6.264189488772653 -22.46590018500729, -6.123693431593027 -22.45899805631302, -5.991173622369394 -22.4786555130826, -5.746130771545492 -22.49069369640211, -5.501087920721591 -22.4786555130826, -5.258404966505172 -22.44265689741019, -5.020419078409338 -22.38304453573263, -5.020084557130864 -22.38292484210529, -4.916484156481538 -22.40887539153854, -4.673801202265119 -22.44487400721096, -4.428758351441217 -22.45691219053047, -4.183715500617316 -22.44487400721096, -3.975896008872092 -22.41404689767321, -3.907954521318273 -22.41070914642104, -3.831335815543376 -22.42990113312439, -3.588652861326957 -22.4658997487968, -3.343610010503056 -22.47793793211631, -3.098567159679154 -22.4658997487968, -2.855884205462736 -22.42990113312439, -2.617898317366901 -22.37028877144683, -2.55442301821249 -22.34757694624723, -2.455727455302357 -22.33293684245731, -2.341515979451263 -22.34987851451589, -2.096473128627362 -22.3619166978354, -1.851430277803461 -22.34987851451589, -1.608747323587042 -22.31387989884347, -1.370761435491207 -22.25426753716592, -1.139764547714639 -22.17161552911361, -1.125708636819748 -22.16496757836726, -0.9578494319110273 -22.24435906959821, -0.726852544134459 -22.32701107765052, -0.4888666560386239 -22.38662343932807, -0.2461837018222049 -22.42262205500048, -0.04851836590371948 -22.43233273026278, 0.1061701484342584 -22.45527860379795, 0.3512129992581597 -22.46731678711745, 0.5962558500820608 -22.45527860379795, 0.8389388042984798 -22.41927998812553, 1.076924692394315 -22.35966762644798, 1.307921580170883 -22.27701561839567, 1.529704841323152 -22.17211994798834, 1.740138581807164 -22.04599081787381, 1.772685063718212 -22.02185270178525, 1.91323025267697 -22.08832562558374, 2.144227140453538 -22.17097763363604, 2.382213028549373 -22.23058999531359, 2.400539054657536 -22.233308404494, 2.470709182708385 -22.26649640338184, 2.701706070484953 -22.34914841143414, 2.939691958580788 -22.40876077311169, 3.182374912797207 -22.44475938878411, 3.427417763621108 -22.45679757210362, 3.67246061444501 -22.44475938878411, 3.750904670430335 -22.43312331227301, 3.970198466557057 -22.4656524740825, 4.215241317380959 -22.47769065740201, 4.46028416820486 -22.4656524740825, 4.702967122421279 -22.42965385841009, 4.940953010517113 -22.37004149673253, 5.171949898293681 -22.28738948868023, 5.309290253954825 -22.22243233814412, 5.527756450060979 -22.2116997821483, 5.63913794265608 -22.19517789845072, 5.679434149468313 -22.19715752414906, 5.786662679348748 -22.23552450562843, 6.024648567444583 -22.29513686730598, 6.267331521661002 -22.33113548297839, 6.512374372484904 -22.3431736662979, 6.757417223308805 -22.33113548297839, 6.846646527515573 -22.31789956602152, 6.961547070081421 -22.33494345147172, 7.14340174573157 -22.3804956763719, 7.386084699947989 -22.41649429204432, 7.631127550771891 -22.42853247536382, 7.654227093690167 -22.42739766758913, 7.665358336786248 -22.43018589883542, 7.908041291002667 -22.46618451450784, 8.153084141826568 -22.47822269782734, 8.39812699265047 -22.46618451450784, 8.640809946866888 -22.43018589883542, 8.859745401813472 -22.37534542224775, 8.971348828603521 -22.39190022677332, 9.216391679427421 -22.40393841009282, 9.461434530251323 -22.39190022677332, 9.678390165997076 -22.35971789829296, 9.915310460827609 -22.41906334275269, 10.15799341504403 -22.4550619584251, 10.40303626586793 -22.46710014174461, 10.64807911669183 -22.4550619584251, 10.89076207090825 -22.41906334275269, 10.96704756934117 -22.39995482014355, 11.0505742524468 -22.38756480711927, 11.17312139811863 -22.35686834511981, 11.41367609013224 -22.34505065090392, 11.42614034228609 -22.34320175375175, 11.544336528646 -22.36073450177871, 11.7893793794699 -22.37277268509822, 12.0344222302938 -22.36073450177871, 12.27710518451022 -22.3247358861063, 12.51509107260605 -22.26512352442874, 12.61296722408551 -22.23010287744914, 12.62908707979036 -22.2358706540471, 12.8670729678862 -22.29548301572466, 13.10975592210262 -22.33148163139707, 13.35479877292652 -22.34351981471658, 13.59984162375042 -22.33148163139707, 13.73523415567763 -22.31139804646833, 13.96353849079129 -22.3452637954767, 14.20858134161519 -22.35730197879621, 14.45362419243909 -22.3452637954767, 14.65305301275748 -22.31568132447116, 14.73036773390698 -22.33504765394994, 14.9730506881234 -22.37104626962236, 15.2180935389473 -22.38308445294187, 15.46313638977121 -22.37104626962236, 15.70581934398762 -22.33504765394994, 15.94380523208346 -22.27543529227239, 16.17480211986003 -22.19278328422008, 16.28838315841296 -22.13906345377539, 16.4099384081795 -22.1695114587847, 16.4666967485392 -22.177930763253, 16.584247823015 -22.21999121024711, 16.82223371111084 -22.27960357192466, 17.06491666532726 -22.31560218759708, 17.30995951615116 -22.32764037091659, 17.55500236697506 -22.31560218759708, 17.79768532119148 -22.27960357192466, 17.98787095563432 -22.23196455048102, 18.17081078110282 -22.22297729315838, 18.41349373531924 -22.18697867748596, 18.41784477514719 -22.18588879874579, 18.4973901430078 -22.18979661208253, 18.73887089664221 -22.17793342337652, 18.78888554606691 -22.19046144087594, 19.03156850028333 -22.22646005654836, 19.27661135110723 -22.23849823986787, 19.52165420193113 -22.22646005654836, 19.76433715614755 -22.19046144087594, 20.00232304424339 -22.13084907919839, 20.23331993201996 -22.04819707114608, 20.45510319317222 -21.94330140073875, 20.66553693365623 -21.81717227062423, 20.86259456151634 -21.67102437327471, 21.0443783040736 -21.50626519283423, 21.20913748451407 -21.32448145027698, 21.35528538186359 -21.12742382241687, 21.48141451197812 -20.91699008193286, 21.58631018238545 -20.69520682078059, 21.64536097446595 -20.53017091284831, 21.69198889698639 -20.45237692395721, 21.79688456739372 -20.23059366280494, 21.87953657544602 -19.99959677502837, 21.93914893712358 -19.76161088693253, 21.97514755279599 -19.51892793271611, 21.97709163753492 -19.47935517875256, 22.00764810638986 -19.35736691704186, 22.04364672206227 -19.11468396282544, 22.04941193378315 -18.99733038228982, 22.10383625742364 -18.84522458744524, 22.1634486191012 -18.6072386993494, 22.19944723477361 -18.36455574513298, 22.20998447691467 -18.15006525387409, 22.26587291625088 -17.92694609711825, 22.3018715319233 -17.68426314290183, 22.311953937675 -17.47903105904117, 22.31582939514044 -17.46819988101749, 22.375441756818 -17.23021399292166, 22.41144037249041 -16.98753103870524, 22.42347855580992 -16.74248818788132, 22.41144037249041 -16.49744533705741, 22.375441756818 -16.254762382841, 22.31582939514044 -16.01677649474516, 22.30832089851852 -15.99579165248403, 22.33018802007341 -15.90849320973001, 22.36618663574583 -15.66581025551359, 22.37320910548004 -15.52286460019147, 22.41507202806971 -15.35573844497086, 22.45107064374212 -15.11305549075445, 22.46310882706163 -14.86801263993053, 22.45107064374212 -14.62296978910662, 22.41507202806971 -14.3802868348902, 22.40667909943321 -14.3467803855721, 22.40682864097767 -14.34618338226164, 22.44282725665009 -14.10350042804522, 22.45486543996959 -13.85845757722131, 22.44282725665008 -13.6134147263974, 22.40682864097767 -13.37073177218098, 22.34721627930011 -13.13274588408515, 22.30844502746573 -13.02438749425295, 22.33022511670796 -12.87755805924072, 22.34226330002747 -12.63251520841681, 22.33022511670796 -12.3874723575929, 22.29422650103554 -12.14478940337648, 22.23461413935799 -11.90680351528065, 22.17739333308725 -11.74688207506299, 22.14259280163477 -11.51227594859293, 22.08298043995721 -11.27429006049709, 22.06660106476485 -11.22851277350407, 22.06337928494946 -11.16293193877699, 22.03406561057082 -10.96531519812561, 22.04314691805991 -10.90409399590901, 22.13997229265038 -10.74255061786193, 22.24486796305771 -10.52076735670966, 22.32751997111002 -10.2897704689331, 22.38713233278757 -10.05178458083726, 22.42313094845999 -9.809101626620842, 22.4351691317795 -9.56405877579693, 22.42313094845998 -9.319015924973019, 22.38713233278757 -9.076332970756601, 22.32751997111001 -8.838347082660768, 22.32621521933611 -8.834700545738135, 22.31610278669532 -8.76652806238728, 22.25649042501777 -8.528542174291447, 22.17904866103241 -8.312106943180988, 22.20336370437232 -8.215035848855997, 22.23936232004474 -7.972352894639578, 22.25140050336424 -7.727310043815667, 22.23936232004473 -7.482267192991756, 22.20336370437232 -7.239584238775338, 22.14375134269476 -7.001598350679505, 22.14070790264234 -6.993092505548669, 22.14661287140222 -6.983240660360684, 22.25150854180955 -6.761457399208414, 22.33416054986185 -6.530460511431846, 22.37581970840857 -6.364147827869776, 22.37826323319755 -6.357318633579659, 22.4378755948751 -6.119332745483824, 22.47387421054752 -5.876649791267405, 22.48591239386703 -5.631606940443493, 22.47387421054751 -5.386564089619583, 22.47305251707211 -5.381024682163304, 22.4742428718067 -5.35679445490106, 22.46639370225972 -5.197020938304531, 22.48155510353514 -5.094811073206903, 22.49359328685465 -4.849768222382991, 22.48155510353514 -4.604725371559081, 22.44555648786272 -4.362042417342662, 22.38594412618517 -4.124056529246829, 22.30329211813286 -3.893059641470262, 22.27796733187449 -3.83951487968404, 22.2565194834333 -3.694925230219274, 22.19690712175575 -3.456939342123441, 22.11425511370344 -3.225942454346874, 22.09753928949359 -3.190599813495125, 22.10890457508578 -3.113981281601384, 22.12083454788258 -2.87114110656925, 22.2173397925758 -2.667097910093672, 22.2999918006281 -2.436101022317104, 22.35960416230566 -2.198115134221269, 22.39560277797807 -1.95543218000485, 22.40764096129758 -1.710389329180938, 22.39560277797807 -1.465346478357027, 22.35960416230565 -1.222663524140609, 22.33733720919316 -1.1337688643328, 22.38201202145661 -1.008911128410269, 22.44162438313417 -0.7709252403144335, 22.47762299880658 -0.5282422860980147, 22.48966118212609 -0.2831994352741031, 22.47762299880658 -0.03815658445019221, 22.46983284362372 0.01436037594582373, 22.46595136473468 0.09336969380887428, 22.42995274906226 0.3360526480252923, 22.37034038738471 0.5740385361211262, 22.37015764437406 0.5745492686213597, 22.38255954800001 0.6240604432408892, 22.41855816367243 0.866743397457308, 22.43059634699193 1.11178624828122, 22.41855816367242 1.356829099105131, 22.38255954800001 1.599512053321549, 22.32294718632245 1.837497941417382, 22.31304697431726 1.86516718037985, 22.34065869981704 1.975399367630999, 22.37665731548946 2.218082321847417, 22.38869549880896 2.463125172671329, 22.37665731548945 2.70816802349524, 22.37655704668347 2.708843980878516, 22.37956940927705 2.720870006469926, 22.41556802494946 2.963552960686345, 22.42622722872951 3.180526038139268, 22.42905218083252 3.191803879112921, 22.46505079650494 3.43448683332934, 22.47708897982444 3.679529684153251, 22.46505079650493 3.924572534977163, 22.42905218083252 4.167255489193581, 22.36943981915496 4.405241377289414, 22.28678781110266 4.636238265065981, 22.20182312370297 4.815881003237257, 22.20080623926725 4.822736281262513, 22.14119387758969 5.060722169358346, 22.11639472283721 5.130031163807564, 22.13631459835018 5.264320056500067, 22.14187238184646 5.377451338525397, 22.17192465358179 5.440991528550525, 22.25457666163409 5.671988416327093, 22.31418902331165 5.909974304422928, 22.35018763898406 6.152657258639347, 22.36222582230357 6.397700109463258, 22.35609034175698 6.522590685093183, 22.36638055208554 6.591961648264912, 22.37841873540505 6.837004499088824, 22.36638055208554 7.082047349912735, 22.36084912120376 7.119337227936076, 22.3785359266002 7.16876853756261, 22.43814828827776 7.406754425658445, 22.47414690395017 7.649437379874864, 22.48618508726968 7.894480230698775, 22.47414690395017 8.139523081522686, 22.43814828827776 8.382206035739104, 22.41010307540168 8.494168801603886, 22.41453398546516 8.584362047950311, 22.40249580214564 8.829404898774222, 22.36649718647323 9.07208785299064, 22.30688482479567 9.310073741086473, 22.29103373003705 9.354374583175842, 22.31043194547102 9.431816600194061, 22.34643056114344 9.674499554410479, 22.34931049101803 9.73312187373493, 22.37435753770335 9.803123679724234, 22.43396989938091 10.04110956782007, 22.46996851505332 10.28379252203649, 22.48200669837283 10.5288353728604, 22.46996851505332 10.77387822368431, 22.4339698993809 11.01656117790073, 22.37435753770335 11.25454706599656, 22.29170552965104 11.48554395377313, 22.22014157634299 11.63685322024689, 22.23443208872027 11.73319202928731, 22.24647027203978 11.97823488011122, 22.23443208872027 12.22327773093513, 22.19843347304785 12.46596068515155, 22.1439806270021 12.68334863222938, 22.15682761933628 12.71051131534878, 22.23947962738858 12.94150820312535, 22.29909198906613 13.17949409122119, 22.33509060473855 13.42217704543761, 22.34712878805806 13.66721989626152, 22.33509060473855 13.91226274708543, 22.29909198906613 14.15494570130185, 22.25769045232944 14.3202299009467, 22.28553142661942 14.50791850404303, 22.29756960993893 14.75296135486694, 22.28553142661942 14.99800420569085, 22.24953281094701 15.24068715990727, 22.18992044926945 15.47867304800311, 22.10726844121714 15.70966993577967, 22.06904229209649 15.79049233750846, 22.13883131088654 15.90692828101623, 22.24372698129387 16.1287115421685, 22.32637898934617 16.35970842994507, 22.38599135102373 16.59769431804091, 22.42198996669614 16.84037727225733, 22.43402815001565 17.08542012308124, 22.42198996669614 17.33046297390515, 22.38599135102372 17.57314592812157, 22.32637898934617 17.8111318162174, 22.32364979959007 17.81875939060629, 22.3440670916067 17.95640159298384, 22.35610527492621 18.20144444380775, 22.3440670916067 18.44648729463166, 22.30806847593428 18.68917024884808, 22.25965275264398 18.88245665087605, 22.27899073271135 19.01282272311713, 22.29102891603086 19.25786557394104, 22.27899073271135 19.50290842476495, 22.24299211703893 19.74559137898136, 22.18337975536138 19.9835772670772, 22.10072774730907 20.21457415485376, 21.99583207690174 20.43635741600603, 21.86970294678721 20.64679115649004, 21.72355504943769 20.84384878435015, 21.55879586899722 21.0256325269074, 21.37701212643996 21.19039170734787, 21.17995449857986 21.3365396046974, 21.1524925145701 21.35299968446598, 21.02993804195481 21.46407658377149, 20.97836787083578 21.50232362935326, 20.96116015640032 21.52552554637733, 20.79640097595984 21.70730928893459, 20.61461723340259 21.87206846937505, 20.41755960554248 22.01821636672458, 20.20712586505847 22.1443454968391, 19.9853426039062 22.24924116724643, 19.96551028205333 22.25633728547234, 19.869953371516 22.30153233824949, 19.63895648373943 22.38418434630179, 19.4009705956436 22.44379670797935, 19.15828764142718 22.47979532365176, 18.91324479060328 22.49183350697127, 18.66820193977938 22.47979532365176, 18.42551898556296 22.44379670797935, 18.18753309746713 22.38418434630179, 18.01708231883724 22.3231960825055, 17.85458993009069 22.36389830701685, 17.61190697587427 22.39989692268926, 17.36686412505037 22.41193510600877, 17.35406316190014 22.41130623501518, 17.32962249232418 22.41493166587283, 17.08457964150028 22.42696984919234, 16.83953679067638 22.41493166587283, 16.59685383645996 22.37893305020042, 16.35886794836413 22.31932068852286, 16.19915562030759 22.26217470377977, 16.05645972310251 22.29791816530244, 15.81377676888609 22.33391678097486, 15.56873391806219 22.34595496429436, 15.32369106723829 22.33391678097486, 15.08100811302187 22.29791816530244, 14.84302222492603 22.23830580362489, 14.83509011495141 22.23546764929387, 14.73379523464183 22.24044394766126, 14.48875238381793 22.22840576434175, 14.24606942960151 22.19240714866933, 14.0115966916425 22.13367478529024, 13.8158885019195 22.16270535288215, 13.5708456510956 22.17474353620165, 13.3258028002717 22.16270535288215, 13.08311984605528 22.12670673720973, 12.84513395795945 22.06709437553218, 12.61413707018288 21.98444236747987, 12.60921408285645 21.98211396788231, 12.41638569326412 22.05110906891031, 12.17839980516828 22.11072143058786, 11.93571685095186 22.14672004626027, 11.69067400012796 22.15875822957978, 11.65303800255386 22.15690929158103, 11.62238858930415 22.1614557025629, 11.5620735362037 22.1644187911154, 11.50233734938656 22.19267190332598, 11.27134046160999 22.27532391137828, 11.03335457351416 22.33493627305583, 10.79067161929774 22.37093488872825, 10.54562876847384 22.38297307204775, 10.38434897519156 22.37504990387232, 10.25867615563267 22.39369170566832, 10.01363330480877 22.40572988898783, 9.768590453984871 22.39369170566832, 9.525907499768453 22.35769308999591, 9.287921611672617 22.29808072831835, 9.056924723896049 22.21542872026604, 8.928144125486533 22.15452003340014, 8.755476828124921 22.12890725933043, 8.730173203256818 22.12256903125539, 8.674222684091824 22.14903165601327, 8.502525895102208 22.21046574944508, 8.374749906920318 22.27089929105982, 8.14375301914375 22.35355129911213, 7.905767131047916 22.41316366078968, 7.663084176831497 22.44916227646209, 7.418041326007597 22.4612004597816, 7.172998475183696 22.44916227646209, 7.045062493122757 22.43018476622139, 7.002047076849425 22.44095956708507, 6.759364122633007 22.47695818275748, 6.514321271809106 22.48899636607699, 6.269278420985205 22.47695818275748, 6.026595466768787 22.44095956708507, 5.788609578672952 22.38134720540751, 5.71264440274838 22.35416643084103, 5.574092325989588 22.34735980378086, 5.33140937177317 22.31136118810844, 5.093423483677336 22.25174882643089, 4.862426595900769 22.16909681837858, 4.743496122122889 22.11284689350158, 4.602792578218637 22.17939471361137, 4.371795690442069 22.26204672166367, 4.133809802346234 22.32165908334122, 3.891126848129816 22.35765769901364, 3.646083997305915 22.36969588233315, 3.405605077979647 22.35788191059068, 3.362461836283031 22.37331880930579, 3.124475948187196 22.43293117098334, 2.881792993970778 22.46892978665575, 2.636750143146877 22.48096796997526, 2.585818149549957 22.47846584157736, 2.550904688902151 22.48364476424094, 2.305861838078251 22.49568294756045, 2.06081898725435 22.48364476424094, 1.818136033037931 22.44764614856852, 1.580150144942097 22.38803378689097, 1.540541675798155 22.37386165001876, 1.530509838959956 22.37134880570402, 1.299512951183388 22.28869679765171, 1.124606770200216 22.205972334961, 0.9988582651750725 22.23747069573335, 0.756175310958654 22.27346931140577, 0.5111324601347533 22.28550749472527, 0.2660896093108527 22.27346931140577, 0.0234066550944344 22.23747069573335, -0.03697761262200187 22.22234522406968, -0.0641515852677238 22.22915194987403, -0.3068345394841422 22.26515056554645, -0.5518773903080429 22.27718874886595, -0.7244596559357803 22.26871032582958, -0.9413867209710468 22.32304772693349, -1.142651755126477 22.35290257453187, -1.195359120379203 22.37176157137471, -1.433345008475038 22.43137393305226, -1.676027962691456 22.46737254872468, -1.921070813515357 22.47941073204419, -2.166113664339258 22.46737254872468, -2.408796618555676 22.43137393305226, -2.64678250665151 22.37176157137471, -2.765340328481469 22.32934090441736, -2.953278436076054 22.33857371159512, -3.198321286899955 22.32653552827561, -3.383074108523836 22.29913003602953, -3.410372612368247 22.30596795527529, -3.653055566584665 22.34196657094771, -3.898098417408566 22.35400475426722, -4.143141268232466 22.34196657094771, -4.385824222448885 22.30596795527529, -4.462919229365399 22.28665666134685, -4.543872356633671 22.30693436411426, -4.78655531085009 22.34293297978667, -5.03159816167399 22.35497116310618, -5.276641012497891 22.34293297978667, -5.519323966714309 22.30693436411426, -5.740285956380461 22.25158626700496, -5.849769324545464 22.23534594346918, -6.045368531132135 22.18635089279545, -6.047974121578137 22.18700355922577, -6.290657075794555 22.22300217489819, -6.535699926618456 22.2350403582177, -6.541298596980119 22.23476531317993, -6.698394072660326 22.29097497317093, -6.93637996075616 22.35058733484848, -7.179062914972579 22.3865859505209, -7.42410576579648 22.39862413384041, -7.624786122499572 22.38876534010497, -7.81773403682064 22.43709627663851, -8.060416991037059 22.47309489231092, -8.305459841860959 22.48513307563043, -8.550502692684859 22.47309489231092, -8.793185646901277 22.43709627663851, -9.005397594027517 22.38393995108657, -9.044718063582776 22.38200826028587, -9.214586297221089 22.407205832474, -9.459629148044989 22.41924401579351, -9.704671998868889 22.407205832474, -9.947354953085307 22.37120721680159, -10.18534084118114 22.31159485512403, -10.33580272181398 22.25775873339386, -10.51254839624864 22.32099934692418, -10.75053428434447 22.38061170860173, -10.99321723856089 22.41661032427414, -11.23826008938479 22.42864850759365, -11.29538315086152 22.42584223153411, -11.33038977901702 22.43461093540733, -11.57307273323344 22.47060955107974, -11.81811558405734 22.48264773439925, -12.06315843488124 22.47060955107974, -12.30584138909765 22.43461093540733, -12.54382727719349 22.37499857372977, -12.60371436525269 22.35357063098932, -12.83999844527089 22.38862006333841, -13.08504129609479 22.40065824665792, -13.33008414691869 22.38862006333841, -13.57276710113511 22.352621447666, -13.81075298923094 22.29300908598844, -13.86995584245114 22.27182596638811, -13.99875298466096 22.2527207151063, -14.02162334345316 22.24699198845396, -14.05563511998011 22.24532109701812, -14.29831807419653 22.20932248134571, -14.45234634580798 22.17074040780624, -14.48458465159365 22.1788156830242, -14.65976147368973 22.20480070992366, -14.71557451981399 22.22477093715091, -14.8206581025195 22.25109300434882, -14.90594999486599 22.29143306509782, -15.13694688264255 22.37408507315013, -15.37493277073839 22.43369743482768, -15.61761572495481 22.46969605050009, -15.86265857577871 22.4817342338196, -16.10770142660261 22.46969605050009, -16.35038438081903 22.43369743482768, -16.39788303796649 22.42179964058564, -16.57611124777833 22.43055543107375, -16.82115409860222 22.41851724775424, -16.89031477907391 22.40825822991765, -16.94578704243195 22.41648676288385, -17.19082989325585 22.42852494620336, -17.43587274407975 22.41648676288385, -17.5503671284345 22.399503125313, -17.67505861594785 22.41799936025091, -17.92010146677175 22.43003754357042, -18.16514431759565 22.41799936025091, -18.40782727181207 22.3820007445785, -18.64505406713305 22.32257852574262, -18.73588422207093 22.35507807484728, -18.97387011016676 22.41469043652483, -19.21655306438318 22.45068905219724, -19.46159591520708 22.46272723551675, -19.70663876603098 22.45068905219724, -19.9493217202474 22.41469043652483, -20.18730760834324 22.35507807484727, -20.41830449611981 22.27242606679497, -20.4307583865337 22.26653581530638, -20.56379166347918 22.23321271416002, -20.79478855125575 22.15056070610771, -21.01657181240802 22.04566503570039, -21.22700555289203 21.91953590558586, -21.42406318075214 21.77338800823634, -21.60584692330939 21.60862882779587, -21.77060610374987 21.42684508523861, -21.91675400109939 21.22978745737851, -22.04288313121391 21.01935371689449, -22.14777880162124 20.79757045574222, -22.23043080967355 20.56657356796565, -22.2900431713511 20.32858767986982, -22.32604178702352 20.0859047256534, -22.33807997034302 19.8408618748295, -22.32604178702352 19.5958190240056, -22.30537123841755 19.45646950438467, -22.3342576214756 19.34114859891324, -22.37025623714802 19.09846564469682, -22.38229442046752 18.85342279387292, -22.37025623714802 18.60837994304902, -22.35423385062678 18.50036578689495, -22.35761020545555 18.43163850548751, -22.37766358477907 18.29644960321025, -22.38970176809858 18.05140675238635, -22.37766358477907 17.80636390156245, -22.37260135853582 17.7722371442781, -22.38305833385616 17.55938052168966, -22.38181414347826 17.53405444473314, -22.41197150930309 17.33074999835235, -22.4240096926226 17.08570714752844, -22.41197150930309 16.84066429670454, -22.40636946919279 16.80289840992767, -22.44403823256925 16.65251627687095, -22.48003684824166 16.40983332265453, -22.49207503156117 16.16479047183062, -22.48003684824166 15.91974762100672, -22.44403823256925 15.6770646667903, -22.43547952265877 15.64289638152731, -22.42650680380195 15.4602524933292, -22.39050818812954 15.21756953911278, -22.33192658845886 14.98369868361621, -22.34105686595574 14.79784761562138, -22.32901868263623 14.55280476479748, -22.29302006696381 14.31012181058106, -22.26462335331059 14.19675577495375, -22.26277109686172 14.15905222877569, -22.22677248118931 13.91636927455927, -22.16716011951175 13.67838338646343, -22.10462297699719 13.50360378180867, -22.14988328742593 13.42809151584489, -22.25477895783326 13.20630825469262, -22.33743096588557 12.97531136691605, -22.39704332756312 12.73732547882022, -22.43304194323554 12.4946425246038, -22.44508012655504 12.2495996737799, -22.43304194323554 12.00455682295599, -22.39704332756312 11.76187386873957, -22.33743096588557 11.52388798064374, -22.25477895783326 11.29289109286717, -22.19678075951196 11.1702642054589, -22.231853125918 10.93382551658731, -22.2438913092375 10.68878266576341, -22.231853125918 10.44373981493951, -22.19585451024558 10.20105686072309, -22.13624214856803 9.963070972627253, -22.05359014051572 9.732074084850684, -22.04396671166407 9.711727054073368, -22.00373838951193 9.599296425506699, -22.03882619823041 9.525109487055827, -22.12147820628271 9.294112599279259, -22.18109056796027 9.056126711183422, -22.21708918363268 8.813443756967004, -22.2200347057179 8.75348627752272, -22.26415027510652 8.630191520729721, -22.32376263678407 8.392205632633884, -22.35976125245649 8.149522678417465, -22.37179943577599 7.904479827593563, -22.36111420259841 7.686976909581786, -22.394861026681 7.459474301195111, -22.4068992100005 7.214431450371209, -22.394861026681 6.969388599547307, -22.39454427325957 6.967253221432506, -22.39663570074542 6.958903774881473, -22.43263431641783 6.716220820665053, -22.44467249973734 6.471177969841151, -22.43263431641783 6.22613511901725, -22.42520472119385 6.176048856423258, -22.45924851244561 5.946544257999629, -22.47128669576512 5.701501407175727, -22.45924851244561 5.456458556351826, -22.42324989677319 5.213775602135406, -22.36363753509564 4.975789714039571, -22.30467603942607 4.811003372857127, -22.30921191741285 4.798326443125212, -22.36882427909041 4.560340555029376, -22.40482289476282 4.317657600812956, -22.41686107808233 4.072614749989055, -22.40482289476282 3.827571899165153, -22.38282310228573 3.679261345251841, -22.38695198811661 3.667721882395539, -22.44656434979417 3.429735994299703, -22.48256296546658 3.187053040083284, -22.49460114878609 2.942010189259382, -22.48256296546658 2.69696733843548, -22.44656434979417 2.454284384219061, -22.38695198811661 2.216298496123226, -22.37396184882762 2.1799934889219, -22.38658792514858 2.129587366737637, -22.422586540821 1.886904412521218, -22.43462472414051 1.641861561697316, -22.422586540821 1.396818710873414, -22.38658792514858 1.154135756656995, -22.3592771699918 1.045105110367365, -22.36350762099722 0.9589923018873644, -22.35146943767771 0.7139494510634626, -22.34532001016119 0.6724933781402493, -22.33427096093755 0.4475848143815596, -22.29827234526513 0.2049018601651402, -22.23865998358758 -0.03308402793069509, -22.20425046928654 -0.1292521779944436, -22.261279888389 -0.2886387269097357, -22.32089225006656 -0.5266246150055711, -22.35689086573897 -0.7693075692219906, -22.36718807520162 -0.9789120830667672, -22.42620471134769 -1.214519701527199, -22.46220332702011 -1.457202655743619, -22.47424151033962 -1.702245506567521, -22.46220332702011 -1.947288357391422, -22.42889752836875 -2.171817813823874, -22.43856291195954 -2.36856121658928, -22.42652472864004 -2.613604067413182, -22.4058295706438 -2.753119490067378, -22.40429375712431 -2.784381692441204, -22.40814810495108 -2.799769111506142, -22.4441467206235 -3.042452065722562, -22.456184903943 -3.287494916546463, -22.4441467206235 -3.532537767370365, -22.42631024597718 -3.652781512278879, -22.46166535455815 -3.891126294065749, -22.47370353787766 -4.13616914488965, -22.46166535455815 -4.381211995713552, -22.42566673888573 -4.623894949929971, -22.41659300653852 -4.660119320014077, -22.39777047505845 -4.787010520136427, -22.33815811338089 -5.024996408232262, -22.29060748755867 -5.157891504620128, -22.29667697197584 -5.198808644943893, -22.30871515529535 -5.443851495767794, -22.29913718581248 -5.638815543489387, -22.31302053289855 -5.732409467097974, -22.32505871621806 -5.977452317921876, -22.31302053289855 -6.222495168745778, -22.27702191722613 -6.465178122962197, -22.23700130787304 -6.624949351462579, -22.26191986068643 -6.792936589265861, -22.27337292135948 -7.026068995003135, -22.28286374225524 -7.046135649814543, -22.36551575030755 -7.277132537591112, -22.4251281119851 -7.515118425686947, -22.46112672765752 -7.757801379903367, -22.47316491097703 -8.002844230727268, -22.46112672765752 -8.24788708155117, -22.44973433808717 -8.324688333623625, -22.45836137069585 -8.500295616589442, -22.4573686685416 -8.520502533151651, -22.46998143377628 -8.605530889681926, -22.48201961709579 -8.850573740505828, -22.46998143377628 -9.09561659132973, -22.43398281810386 -9.33829954554615, -22.37437045642631 -9.576285433641985, -22.291718448374 -9.807282321418555, -22.18682277796667 -10.02906558257082, -22.06069364785215 -10.23949932305483, -22.01967276381155 -10.29480958237682, -22.00439035799606 -10.35582036634663, -21.92173834994376 -10.5868172541232, -21.8468060799781 -10.74524822781346, -21.92842458012701 -10.91781604746789, -21.99191048529292 -11.09524726609872, -22.01966467653555 -11.15392857315868, -22.10231668458785 -11.38492546093525, -22.16192904626541 -11.62291134903108, -22.19792766193782 -11.8655943032475, -22.19817821472779 -11.87069442245198, -22.23112272730048 -11.94034974194297, -22.31377473535278 -12.17134662971954, -22.37338709703034 -12.40933251781537, -22.40938571270275 -12.65201547203179, -22.42142389602226 -12.89705832285569, -22.40938571270275 -13.14210117367959, -22.37338709703034 -13.38478412789601, -22.31377473535278 -13.62277001599185, -22.29252178711778 -13.68216802717211, -22.29289905870317 -13.68984756671441, -22.29056292959623 -13.73740056711855, -22.29317150663481 -13.74291593987261, -22.37582351468712 -13.97391282764918, -22.43543587636467 -14.21189871574502, -22.47143449203709 -14.45458166996143, -22.48347267535659 -14.69962452078534, -22.47143449203709 -14.94466737160924, -22.43543587636467 -15.18735032582566, -22.42770850858644 -15.21819970725584, -22.40572505302204 -15.36640012664985, -22.43309942964606 -15.47568476407415, -22.46909804531847 -15.71836771829057, -22.48113622863798 -15.96341056911447, -22.46909804531847 -16.20845341993838, -22.43309942964606 -16.4511363741548, -22.3734870679685 -16.68912226225063, -22.29704609402757 -16.90276047256235, -22.33318673279856 -17.00376679140546, -22.39279909447611 -17.2417526795013, -22.42879771014853 -17.48443563371772, -22.44083589346804 -17.72947848454162, -22.42879771014853 -17.97452133536552, -22.39279909447611 -18.21720428958194, -22.3612750894556 -18.34305517218451, -22.38618093327824 -18.41266234260967, -22.4457932949558 -18.65064823070551, -22.48179191062821 -18.89333118492193, -22.49383009394772 -19.13837403574583, -22.48179191062821 -19.38341688656973, -22.46272989236197 -19.51192257546279, -22.47144518575322 -19.68932644793065, -22.45940700243371 -19.93436929875455, -22.4234083867613 -20.17705225297097, -22.36379602508374 -20.41503814106681, -22.28114401703143 -20.64603502884338, -22.1762483466241 -20.86781828999564, -22.05011921650958 -21.07825203047966, -21.90397131916006 -21.27530965833976, -21.73921213871958 -21.45709340089702, -21.55742839616233 -21.62185258133749, -21.36037076830222 -21.76800047868701, -21.14993702781821 -21.89412960880153, -20.92815376666594 -21.99902527920887, -20.69715687888937 -22.08167728726117, -20.45917099079353 -22.14128964893872, -20.42744614576441 -22.14599558515562, -20.37281740980389 -22.17183305301641, -20.14182052202732 -22.25448506106872, -19.90383463393149 -22.31409742274627, -19.66115167971507 -22.35009603841869, -19.63541513605011 -22.3513603937329, -19.53556033746936 -22.37637271868991, -19.29287738325294 -22.41237133436232, -19.04783453242904 -22.42440951768183, -18.80279168160514 -22.41237133436232, -18.56010872738872 -22.37637271868991, -18.32212283929288 -22.31676035701235, -18.15038036764053 -22.25530991806244, -17.93163213536989 -22.26605632960654, -17.86833950280889 -22.2629469619552, -17.85807912611075 -22.2677997587208, -17.62708223833419 -22.35045176677311, -17.38909635023835 -22.41006412845066, -17.14641339602193 -22.44606274412307, -16.90137054519803 -22.45810092744258, -16.65632769437413 -22.44606274412307, -16.4990865892769 -22.42273822951685, -16.38947103094286 -22.45019549751362, -16.14678807672644 -22.48619411318603, -15.90174522590254 -22.49823229650554)))"; - Geometry shapelyGeom = OperatorImportFromWkt.local().execute(0, Geometry.Type.Unknown, shapelyWKT, null); - Geometry eplGeom = OperatorImportFromWkt.local().execute(0, Geometry.Type.Unknown, eplWKT, null); - NonSimpleResult nonSimpleResult = new NonSimpleResult(); - boolean is_simple = OperatorSimplify.local().isSimpleAsFeature(shapelyGeom, null, true, nonSimpleResult, null); - assertFalse(is_simple); - assertTrue(OperatorSimplify.local().isSimpleAsFeature(eplGeom, null, true, nonSimpleResult, null)); - } - - @Test - public void testIDPass() { - int size = 1000; - List points = new ArrayList<>(size); - List pointList = new ArrayList<>(size); - for (int i = 0; i < size; i++) { - double x = randomWithRange(-20, 20); - double y = randomWithRange(-20, 20); - points.add(String.format("Point(%f %f)", x, y)); - pointList.add(new Point(x, y)); - } - - SimpleStringCursor simpleStringCursor = new SimpleStringCursor(points); - OperatorImportFromWktCursor operatorImportFromWktCursor = new OperatorImportFromWktCursor(0, simpleStringCursor); - double [] distances = new double[]{2.5}; - - GeometryCursor operatorBufferCursor = OperatorBuffer.local().execute(operatorImportFromWktCursor, - null, - distances, - Double.NaN, - 0, - true, - null); - - OperatorExportToWkbCursor operatorExportToGeoJsonCursor = new OperatorExportToWkbCursor(0, operatorBufferCursor); - - while (operatorExportToGeoJsonCursor.hasNext()) { - ByteBuffer wkb = operatorExportToGeoJsonCursor.next(); - long id = operatorExportToGeoJsonCursor.getByteBufferID(); - } - } - - static double randomWithRange(double min, double max) { - double range = Math.abs(max - min); - return (Math.random() * range) + (min <= max ? min : max); - } + @Test + public void testQuadTreeIterator() { + int size = 1000; + ArrayDeque bufferedArrayDequ = new ArrayDeque<>(new ArrayList<>(bufferedPointList).subList(0, size)); + SimpleGeometryCursor simpleGeometryCursor = new SimpleGeometryCursor(bufferedArrayDequ); + + HashMap m_quadTreeMap = new HashMap<>(); + Envelope2D quad_envelope2D = new Envelope2D(); + Geometry geometry = null; + Envelope2D geometry_env = new Envelope2D(); + int count_index = 0; + while ((geometry = simpleGeometryCursor.next()) != null) { + geometry.queryEnvelope2D(geometry_env); + quad_envelope2D.merge(geometry_env); + m_quadTreeMap.put(count_index++, geometry); + } + QuadTree quadTree = new QuadTree(quad_envelope2D, 16); + for (Integer element_id : m_quadTreeMap.keySet()) { + m_quadTreeMap.get(element_id).queryEnvelope2D(geometry_env); + quadTree.insert(element_id, geometry_env); + } + + QuadTree.QuadTreeIterator quadTreeIterator = quadTree.getIterator(true); + quadTreeIterator.resetIterator(quad_envelope2D, 0.0); + int element_handle = -1; + List geometryList = new ArrayList<>(); + assertFalse(geometryList.containsAll(m_quadTreeMap.values())); + + int max_height = 0; + while ((element_handle = quadTreeIterator.next()) != -1) { + int element_id = quadTree.getElement(element_handle); + int quad_handle = quadTree.getQuad(element_handle); + + int sub_count = quadTree.getContainedSubTreeElementCount(quad_handle); + int sub_count_2 = quadTree.getSubTreeElementCount(quad_handle); + + int quad_height = quadTree.getHeight(quad_handle); + max_height = quad_height > max_height ? quad_height : max_height; + Envelope2D envelope2D = quadTree.getExtent(quad_handle); + assertTrue(quadTree.hasData(envelope2D, 0.0)); + geometryList.add(m_quadTreeMap.get(element_id)); + } + assertTrue(max_height == 3); + assertEquals(16, quadTree.getMaxHeight()); + + assertTrue(geometryList.containsAll(m_quadTreeMap.values())); + } + + + @Test + public static void testFailedUnionDifferenceTest() { + // shapely result for union + String shapelyWKT = "POLYGON ((-14.50430349127707 -22.07059031842225, -14.51281964335354 -22.0769063272619, -14.72325338383755 -22.20303545737643, -14.94503664498982 -22.30793112778376, -15.17603353276639 -22.39058313583606, -15.41401942086222 -22.45019549751362, -15.65670237507864 -22.48619411318603, -15.90174522590254 -22.49823229650554, -16.14678807672644 -22.48619411318603, -16.38947103094286 -22.45019549751362, -16.4990865892769 -22.42273822951685, -16.65632769437413 -22.44606274412307, -16.90137054519803 -22.45810092744258, -17.14641339602193 -22.44606274412307, -17.38909635023835 -22.41006412845066, -17.62708223833419 -22.35045176677311, -17.85807912611075 -22.2677997587208, -17.86833950280889 -22.2629469619552, -17.93163213536989 -22.26605632960654, -18.15038036764053 -22.25530991806244, -18.32212283929288 -22.31676035701235, -18.56010872738872 -22.37637271868991, -18.80279168160514 -22.41237133436232, -19.04783453242904 -22.42440951768183, -19.29287738325294 -22.41237133436232, -19.53556033746936 -22.37637271868991, -19.63541513605011 -22.3513603937329, -19.66115167971507 -22.35009603841869, -19.90383463393149 -22.31409742274627, -20.14182052202732 -22.25448506106872, -20.37281740980389 -22.17183305301641, -20.42744614576441 -22.14599558515562, -20.45917099079353 -22.14128964893872, -20.69715687888937 -22.08167728726117, -20.92815376666594 -21.99902527920887, -21.14993702781821 -21.89412960880153, -21.36037076830222 -21.76800047868701, -21.55742839616233 -21.62185258133749, -21.73921213871958 -21.45709340089702, -21.90397131916006 -21.27530965833976, -22.05011921650958 -21.07825203047966, -22.1762483466241 -20.86781828999564, -22.28114401703143 -20.64603502884338, -22.36379602508374 -20.41503814106681, -22.4234083867613 -20.17705225297097, -22.45940700243371 -19.93436929875455, -22.47144518575322 -19.68932644793065, -22.46272989236197 -19.51192257546279, -22.48179191062821 -19.38341688656973, -22.49383009394772 -19.13837403574583, -22.48179191062821 -18.89333118492193, -22.4457932949558 -18.65064823070551, -22.38618093327824 -18.41266234260967, -22.3612750894556 -18.34305517218451, -22.39279909447611 -18.21720428958194, -22.42879771014853 -17.97452133536552, -22.44083589346804 -17.72947848454162, -22.42879771014853 -17.48443563371772, -22.39279909447611 -17.2417526795013, -22.33318673279856 -17.00376679140546, -22.29704609402757 -16.90276047256235, -22.3734870679685 -16.68912226225063, -22.43309942964606 -16.4511363741548, -22.46909804531847 -16.20845341993838, -22.48113622863798 -15.96341056911447, -22.46909804531847 -15.71836771829057, -22.43309942964606 -15.47568476407415, -22.40572505302204 -15.36640012664985, -22.42770850858644 -15.21819970725584, -22.43543587636467 -15.18735032582566, -22.47143449203709 -14.94466737160924, -22.48347267535659 -14.69962452078534, -22.47143449203709 -14.45458166996143, -22.43543587636467 -14.21189871574502, -22.37582351468712 -13.97391282764918, -22.29317150663481 -13.74291593987261, -22.29056292959623 -13.73740056711855, -22.29289905870317 -13.68984756671441, -22.29252178711778 -13.68216802717211, -22.31377473535278 -13.62277001599185, -22.37338709703034 -13.38478412789601, -22.40938571270275 -13.14210117367959, -22.42142389602226 -12.89705832285569, -22.40938571270275 -12.65201547203179, -22.37338709703034 -12.40933251781537, -22.31377473535278 -12.17134662971954, -22.23112272730048 -11.94034974194297, -22.19817821472779 -11.87069442245198, -22.19792766193782 -11.8655943032475, -22.16192904626541 -11.62291134903108, -22.10231668458785 -11.38492546093525, -22.01966467653555 -11.15392857315868, -21.99191048529292 -11.09524726609872, -21.92842458012701 -10.91781604746789, -21.8468060799781 -10.74524822781346, -21.92173834994376 -10.5868172541232, -22.00439035799606 -10.35582036634663, -22.01967276381155 -10.29480958237682, -22.06069364785215 -10.23949932305483, -22.18682277796667 -10.02906558257082, -22.291718448374 -9.807282321418555, -22.37437045642631 -9.576285433641985, -22.43398281810386 -9.33829954554615, -22.46998143377628 -9.09561659132973, -22.48201961709579 -8.850573740505828, -22.46998143377628 -8.605530889681926, -22.4573686685416 -8.520502533151651, -22.45836137069585 -8.500295616589442, -22.44973433808717 -8.324688333623628, -22.46112672765752 -8.24788708155117, -22.47316491097703 -8.002844230727268, -22.46112672765752 -7.757801379903367, -22.4251281119851 -7.515118425686947, -22.36551575030755 -7.277132537591112, -22.28286374225524 -7.046135649814543, -22.27337292135948 -7.026068995003135, -22.26191986068643 -6.792936589265861, -22.23700130787304 -6.624949351462579, -22.27702191722613 -6.465178122962197, -22.31302053289855 -6.222495168745778, -22.32505871621806 -5.977452317921876, -22.31302053289855 -5.732409467097974, -22.29913718581248 -5.638815543489387, -22.30871515529535 -5.443851495767794, -22.29667697197584 -5.198808644943893, -22.29060748755867 -5.157891504620128, -22.33815811338089 -5.024996408232262, -22.39777047505845 -4.787010520136427, -22.41659300653852 -4.660119320014077, -22.42566673888573 -4.623894949929971, -22.46166535455815 -4.381211995713552, -22.47370353787766 -4.13616914488965, -22.46166535455815 -3.891126294065749, -22.42631024597718 -3.652781512278879, -22.4441467206235 -3.532537767370365, -22.456184903943 -3.287494916546463, -22.4441467206235 -3.042452065722562, -22.40814810495108 -2.799769111506142, -22.40429375712431 -2.784381692441201, -22.4058295706438 -2.753119490067372, -22.42652472864004 -2.613604067413182, -22.43856291195954 -2.36856121658928, -22.42889752836875 -2.171817813823874, -22.46220332702011 -1.947288357391422, -22.47424151033962 -1.702245506567521, -22.46220332702011 -1.457202655743619, -22.42620471134769 -1.214519701527199, -22.36718807520162 -0.9789120830667674, -22.35689086573897 -0.7693075692219906, -22.32089225006656 -0.5266246150055711, -22.261279888389 -0.2886387269097357, -22.20425046928654 -0.1292521779944437, -22.23865998358758 -0.03308402793069509, -22.29827234526513 0.2049018601651402, -22.33427096093755 0.4475848143815596, -22.34532001016119 0.6724933781402493, -22.35146943767771 0.7139494510634626, -22.36350762099722 0.9589923018873644, -22.3592771699918 1.045105110367362, -22.38658792514858 1.154135756656995, -22.422586540821 1.396818710873414, -22.43462472414051 1.641861561697316, -22.422586540821 1.886904412521218, -22.38658792514858 2.129587366737637, -22.37396184882762 2.1799934889219, -22.38695198811661 2.216298496123226, -22.44656434979417 2.454284384219061, -22.48256296546658 2.69696733843548, -22.49460114878609 2.942010189259382, -22.48256296546658 3.187053040083284, -22.44656434979417 3.429735994299703, -22.38695198811661 3.667721882395539, -22.38282310228573 3.679261345251841, -22.40482289476282 3.827571899165153, -22.41686107808233 4.072614749989055, -22.40482289476282 4.317657600812956, -22.36882427909041 4.560340555029376, -22.30921191741285 4.798326443125212, -22.30467603942607 4.811003372857128, -22.36363753509564 4.975789714039571, -22.42324989677319 5.213775602135406, -22.45924851244561 5.456458556351826, -22.47128669576512 5.701501407175727, -22.45924851244561 5.946544257999629, -22.42520472119385 6.176048856423258, -22.43263431641783 6.22613511901725, -22.44467249973734 6.471177969841151, -22.43263431641783 6.716220820665053, -22.39663570074542 6.958903774881473, -22.39454427325957 6.967253221432506, -22.394861026681 6.969388599547307, -22.4068992100005 7.214431450371209, -22.394861026681 7.459474301195111, -22.36111420259841 7.686976909581786, -22.37179943577599 7.904479827593563, -22.35976125245649 8.149522678417465, -22.32376263678407 8.392205632633884, -22.26415027510652 8.630191520729721, -22.2200347057179 8.75348627752272, -22.21708918363268 8.813443756967004, -22.18109056796027 9.056126711183422, -22.12147820628271 9.294112599279259, -22.03882619823041 9.525109487055827, -22.00373838951193 9.599296425506701, -22.04396671166407 9.711727054073364, -22.05359014051572 9.732074084850684, -22.13624214856803 9.963070972627253, -22.19585451024558 10.20105686072309, -22.231853125918 10.44373981493951, -22.2438913092375 10.68878266576341, -22.231853125918 10.93382551658731, -22.19678075951196 11.1702642054589, -22.25477895783326 11.29289109286717, -22.33743096588557 11.52388798064374, -22.39704332756312 11.76187386873957, -22.43304194323554 12.00455682295599, -22.44508012655504 12.2495996737799, -22.43304194323554 12.4946425246038, -22.39704332756312 12.73732547882022, -22.33743096588557 12.97531136691605, -22.25477895783326 13.20630825469262, -22.14988328742593 13.42809151584489, -22.10462297699719 13.50360378180867, -22.16716011951175 13.67838338646343, -22.22677248118931 13.91636927455927, -22.26277109686172 14.15905222877569, -22.26462335331059 14.19675577495375, -22.29302006696381 14.31012181058106, -22.32901868263623 14.55280476479748, -22.34105686595574 14.79784761562138, -22.33192658845886 14.98369868361621, -22.39050818812954 15.21756953911278, -22.42650680380195 15.4602524933292, -22.43547952265877 15.64289638152731, -22.44403823256925 15.6770646667903, -22.48003684824166 15.91974762100672, -22.49207503156117 16.16479047183062, -22.48003684824166 16.40983332265453, -22.44403823256925 16.65251627687095, -22.40636946919279 16.80289840992767, -22.41197150930309 16.84066429670454, -22.4240096926226 17.08570714752844, -22.41197150930309 17.33074999835235, -22.38181414347826 17.53405444473314, -22.38305833385616 17.55938052168966, -22.37260135853582 17.7722371442781, -22.37766358477907 17.80636390156245, -22.38970176809858 18.05140675238635, -22.37766358477907 18.29644960321025, -22.35761020545555 18.4316385054875, -22.35423385062678 18.50036578689496, -22.37025623714802 18.60837994304902, -22.38229442046752 18.85342279387292, -22.37025623714802 19.09846564469682, -22.3342576214756 19.34114859891324, -22.30537123841755 19.45646950438467, -22.32604178702352 19.5958190240056, -22.33807997034302 19.8408618748295, -22.32604178702352 20.0859047256534, -22.2900431713511 20.32858767986982, -22.23043080967355 20.56657356796565, -22.14777880162124 20.79757045574222, -22.04288313121391 21.01935371689449, -21.91675400109939 21.22978745737851, -21.77060610374987 21.42684508523861, -21.60584692330939 21.60862882779587, -21.42406318075214 21.77338800823634, -21.22700555289203 21.91953590558586, -21.01657181240802 22.04566503570039, -20.79478855125575 22.15056070610771, -20.56379166347918 22.23321271416002, -20.4307583865337 22.26653581530638, -20.41830449611981 22.27242606679497, -20.18730760834324 22.35507807484727, -19.9493217202474 22.41469043652483, -19.70663876603098 22.45068905219724, -19.46159591520708 22.46272723551675, -19.21655306438318 22.45068905219724, -18.97387011016676 22.41469043652483, -18.73588422207093 22.35507807484728, -18.64505406713305 22.32257852574262, -18.40782727181207 22.3820007445785, -18.16514431759565 22.41799936025091, -17.92010146677175 22.43003754357042, -17.67505861594785 22.41799936025091, -17.5503671284345 22.399503125313, -17.43587274407975 22.41648676288385, -17.19082989325585 22.42852494620336, -16.94578704243195 22.41648676288385, -16.89031477907391 22.40825822991765, -16.82115409860222 22.41851724775424, -16.57611124777833 22.43055543107375, -16.39788303796649 22.42179964058564, -16.35038438081903 22.43369743482768, -16.10770142660261 22.46969605050009, -15.86265857577871 22.4817342338196, -15.61761572495481 22.46969605050009, -15.37493277073839 22.43369743482768, -15.13694688264255 22.37408507315013, -14.90594999486599 22.29143306509782, -14.8206581025195 22.25109300434882, -14.71557451981399 22.22477093715091, -14.65976147368973 22.20480070992366, -14.48458465159365 22.1788156830242, -14.45234634580798 22.17074040780624, -14.29831807419653 22.20932248134571, -14.05563511998011 22.24532109701812, -14.02162334345316 22.24699198845396, -13.99875298466096 22.2527207151063, -13.86995584245114 22.27182596638811, -13.81075298923094 22.29300908598844, -13.57276710113511 22.352621447666, -13.33008414691869 22.38862006333841, -13.08504129609479 22.40065824665792, -12.83999844527089 22.38862006333841, -12.60371436525269 22.35357063098932, -12.54382727719349 22.37499857372977, -12.30584138909765 22.43461093540733, -12.06315843488124 22.47060955107974, -11.81811558405734 22.48264773439925, -11.57307273323344 22.47060955107974, -11.33038977901702 22.43461093540733, -11.29538315086152 22.42584223153411, -11.23826008938479 22.42864850759365, -10.99321723856089 22.41661032427414, -10.75053428434447 22.38061170860173, -10.51254839624864 22.32099934692418, -10.33580272181398 22.25775873339386, -10.18534084118114 22.31159485512403, -9.947354953085307 22.37120721680159, -9.704671998868889 22.407205832474, -9.459629148044989 22.41924401579351, -9.214586297221089 22.407205832474, -9.04471806358278 22.38200826028587, -9.005397594027519 22.38393995108657, -8.793185646901277 22.43709627663851, -8.550502692684859 22.47309489231092, -8.305459841860959 22.48513307563043, -8.060416991037059 22.47309489231092, -7.81773403682064 22.43709627663851, -7.624786122499572 22.38876534010497, -7.42410576579648 22.39862413384041, -7.179062914972579 22.3865859505209, -6.93637996075616 22.35058733484848, -6.698394072660326 22.29097497317093, -6.541298596980119 22.23476531317993, -6.535699926618456 22.2350403582177, -6.290657075794555 22.22300217489819, -6.047974121578137 22.18700355922577, -6.045368531132135 22.18635089279545, -5.849769324545464 22.23534594346918, -5.740285956380461 22.25158626700496, -5.519323966714309 22.30693436411426, -5.276641012497891 22.34293297978667, -5.03159816167399 22.35497116310618, -4.78655531085009 22.34293297978667, -4.543872356633671 22.30693436411426, -4.462919229365399 22.28665666134685, -4.385824222448885 22.30596795527529, -4.143141268232466 22.34196657094771, -3.898098417408566 22.35400475426722, -3.653055566584665 22.34196657094771, -3.410372612368247 22.30596795527529, -3.383074108523836 22.29913003602953, -3.198321286899955 22.32653552827561, -2.953278436076054 22.33857371159512, -2.765340328481469 22.32934090441736, -2.64678250665151 22.37176157137471, -2.408796618555676 22.43137393305226, -2.166113664339258 22.46737254872468, -1.921070813515357 22.47941073204419, -1.676027962691456 22.46737254872468, -1.433345008475038 22.43137393305226, -1.195359120379203 22.37176157137471, -1.142651755126477 22.35290257453187, -0.9413867209710468 22.32304772693349, -0.7244596559357803 22.26871032582958, -0.5518773903080429 22.27718874886595, -0.3068345394841422 22.26515056554645, -0.0641515852677238 22.22915194987403, -0.03697761262200187 22.22234522406968, 0.0234066550944344 22.23747069573335, 0.2660896093108527 22.27346931140577, 0.5111324601347533 22.28550749472527, 0.756175310958654 22.27346931140577, 0.9988582651750725 22.23747069573335, 1.124606770200215 22.205972334961, 1.299512951183388 22.28869679765171, 1.530509838959956 22.37134880570402, 1.540541675798155 22.37386165001876, 1.580150144942097 22.38803378689097, 1.818136033037931 22.44764614856852, 2.06081898725435 22.48364476424094, 2.305861838078251 22.49568294756045, 2.550904688902151 22.48364476424094, 2.585818149549957 22.47846584157736, 2.636750143146877 22.48096796997526, 2.881792993970778 22.46892978665575, 3.124475948187196 22.43293117098334, 3.362461836283031 22.37331880930579, 3.405605077979647 22.35788191059068, 3.646083997305915 22.36969588233315, 3.891126848129816 22.35765769901364, 4.133809802346234 22.32165908334122, 4.371795690442069 22.26204672166367, 4.602792578218637 22.17939471361137, 4.74349612212289 22.11284689350158, 4.862426595900769 22.16909681837858, 5.093423483677336 22.25174882643089, 5.33140937177317 22.31136118810844, 5.574092325989588 22.34735980378086, 5.71264440274838 22.35416643084103, 5.788609578672952 22.38134720540751, 6.026595466768787 22.44095956708507, 6.269278420985205 22.47695818275748, 6.514321271809106 22.48899636607699, 6.759364122633007 22.47695818275748, 7.002047076849425 22.44095956708507, 7.045062493122757 22.43018476622139, 7.172998475183696 22.44916227646209, 7.418041326007597 22.4612004597816, 7.663084176831497 22.44916227646209, 7.905767131047916 22.41316366078968, 8.14375301914375 22.35355129911213, 8.374749906920318 22.27089929105982, 8.502525895102208 22.21046574944508, 8.674222684091824 22.14903165601327, 8.730173203256816 22.12256903125539, 8.755476828124921 22.12890725933043, 8.928144125486533 22.15452003340014, 9.056924723896049 22.21542872026604, 9.287921611672617 22.29808072831835, 9.525907499768453 22.35769308999591, 9.768590453984871 22.39369170566832, 10.01363330480877 22.40572988898783, 10.25867615563267 22.39369170566832, 10.38434897519156 22.37504990387232, 10.54562876847384 22.38297307204775, 10.79067161929774 22.37093488872825, 11.03335457351416 22.33493627305583, 11.27134046160999 22.27532391137828, 11.50233734938656 22.19267190332598, 11.5620735362037 22.1644187911154, 11.62238858930415 22.1614557025629, 11.65303800255386 22.15690929158103, 11.69067400012796 22.15875822957978, 11.93571685095186 22.14672004626027, 12.17839980516828 22.11072143058786, 12.41638569326412 22.05110906891031, 12.60921408285645 21.98211396788231, 12.61413707018288 21.98444236747987, 12.84513395795945 22.06709437553218, 13.08311984605528 22.12670673720973, 13.3258028002717 22.16270535288215, 13.5708456510956 22.17474353620165, 13.8158885019195 22.16270535288215, 14.0115966916425 22.13367478529024, 14.24606942960151 22.19240714866933, 14.48875238381793 22.22840576434175, 14.73379523464183 22.24044394766126, 14.83509011495141 22.23546764929387, 14.84302222492603 22.23830580362489, 15.08100811302187 22.29791816530244, 15.32369106723829 22.33391678097486, 15.56873391806219 22.34595496429436, 15.81377676888609 22.33391678097486, 16.05645972310251 22.29791816530244, 16.19915562030759 22.26217470377977, 16.35886794836413 22.31932068852286, 16.59685383645996 22.37893305020042, 16.83953679067638 22.41493166587283, 17.08457964150028 22.42696984919234, 17.32962249232418 22.41493166587283, 17.35406316190014 22.41130623501518, 17.36686412505037 22.41193510600877, 17.61190697587427 22.39989692268926, 17.85458993009069 22.36389830701685, 18.01708231883724 22.3231960825055, 18.18753309746713 22.38418434630179, 18.42551898556296 22.44379670797935, 18.66820193977938 22.47979532365176, 18.91324479060328 22.49183350697127, 19.15828764142718 22.47979532365176, 19.4009705956436 22.44379670797935, 19.63895648373943 22.38418434630179, 19.869953371516 22.30153233824949, 19.96551028205333 22.25633728547234, 19.9853426039062 22.24924116724643, 20.20712586505847 22.1443454968391, 20.41755960554248 22.01821636672458, 20.61461723340259 21.87206846937505, 20.79640097595984 21.70730928893459, 20.96116015640032 21.52552554637733, 20.97836787083578 21.50232362935326, 21.02993804195481 21.46407658377149, 21.1524925145701 21.35299968446598, 21.17995449857986 21.3365396046974, 21.37701212643996 21.19039170734787, 21.55879586899722 21.0256325269074, 21.72355504943769 20.84384878435015, 21.86970294678721 20.64679115649004, 21.99583207690174 20.43635741600603, 22.10072774730907 20.21457415485376, 22.18337975536138 19.9835772670772, 22.24299211703893 19.74559137898136, 22.27899073271135 19.50290842476495, 22.29102891603086 19.25786557394105, 22.29102891603086 19.25786557394103, 22.27899073271135 19.01282272311713, 22.25965275264398 18.88245665087605, 22.30806847593428 18.68917024884808, 22.3440670916067 18.44648729463166, 22.35610527492621 18.20144444380776, 22.35610527492621 18.20144444380774, 22.3440670916067 17.95640159298384, 22.32364979959007 17.81875939060629, 22.32637898934617 17.8111318162174, 22.38599135102372 17.57314592812157, 22.42198996669614 17.33046297390515, 22.43402815001565 17.08542012308125, 22.43402815001565 17.08542012308123, 22.42198996669614 16.84037727225733, 22.38599135102373 16.59769431804091, 22.32637898934617 16.35970842994507, 22.24372698129387 16.1287115421685, 22.13883131088654 15.90692828101623, 22.06904229209649 15.79049233750846, 22.10726844121714 15.70966993577967, 22.18992044926945 15.47867304800311, 22.24953281094701 15.24068715990727, 22.28553142661942 14.99800420569085, 22.29756960993893 14.75296135486695, 22.29756960993893 14.75296135486693, 22.28553142661942 14.50791850404303, 22.25769045232944 14.3202299009467, 22.29909198906613 14.15494570130185, 22.33509060473855 13.91226274708543, 22.34712878805806 13.66721989626153, 22.34712878805806 13.66721989626151, 22.33509060473855 13.42217704543761, 22.29909198906613 13.17949409122119, 22.23947962738858 12.94150820312535, 22.15682761933628 12.71051131534878, 22.1439806270021 12.68334863222938, 22.19843347304785 12.46596068515155, 22.23443208872027 12.22327773093513, 22.24647027203978 11.97823488011123, 22.24647027203978 11.97823488011121, 22.23443208872027 11.73319202928731, 22.22014157634299 11.63685322024689, 22.29170552965104 11.48554395377313, 22.37435753770335 11.25454706599656, 22.4339698993809 11.01656117790073, 22.46996851505332 10.77387822368431, 22.48200669837283 10.52883537286041, 22.48200669837283 10.52883537286039, 22.46996851505332 10.28379252203649, 22.43396989938091 10.04110956782007, 22.37435753770335 9.803123679724234, 22.34931049101803 9.733121873734932, 22.34643056114344 9.674499554410479, 22.31043194547102 9.431816600194061, 22.29103373003705 9.354374583175842, 22.30688482479567 9.310073741086473, 22.36649718647323 9.07208785299064, 22.40249580214564 8.829404898774222, 22.41453398546516 8.584362047950322, 22.41453398546516 8.5843620479503, 22.41010307540168 8.494168801603886, 22.43814828827776 8.382206035739104, 22.47414690395017 8.139523081522686, 22.48618508726968 7.894480230698785, 22.48618508726968 7.894480230698765, 22.47414690395017 7.649437379874864, 22.43814828827776 7.406754425658445, 22.3785359266002 7.16876853756261, 22.36084912120376 7.119337227936076, 22.36638055208554 7.082047349912735, 22.37841873540505 6.837004499088834, 22.37841873540505 6.837004499088813, 22.36638055208554 6.591961648264912, 22.35609034175698 6.522590685093183, 22.36222582230357 6.397700109463268, 22.36222582230357 6.397700109463248, 22.35018763898406 6.152657258639347, 22.31418902331165 5.909974304422928, 22.25457666163409 5.671988416327093, 22.17192465358179 5.440991528550525, 22.14187238184646 5.377451338525399, 22.13631459835018 5.264320056500067, 22.11639472283721 5.130031163807564, 22.14119387758969 5.060722169358346, 22.20080623926725 4.822736281262513, 22.20182312370297 4.815881003237257, 22.28678781110266 4.636238265065981, 22.36943981915496 4.405241377289414, 22.42905218083252 4.167255489193581, 22.46505079650493 3.924572534977163, 22.47708897982444 3.679529684153262, 22.47708897982444 3.679529684153241, 22.46505079650494 3.43448683332934, 22.42905218083252 3.191803879112921, 22.42622722872951 3.18052603813927, 22.41556802494946 2.963552960686345, 22.37956940927705 2.720870006469926, 22.37655704668347 2.708843980878516, 22.37665731548945 2.70816802349524, 22.38869549880896 2.463125172671339, 22.38869549880896 2.463125172671319, 22.37665731548946 2.218082321847417, 22.34065869981704 1.975399367630999, 22.31304697431726 1.86516718037985, 22.32294718632245 1.837497941417382, 22.38255954800001 1.599512053321549, 22.41855816367242 1.356829099105131, 22.43059634699193 1.11178624828123, 22.43059634699193 1.111786248281209, 22.41855816367243 0.866743397457308, 22.38255954800001 0.6240604432408892, 22.37015764437406 0.5745492686213595, 22.37034038738471 0.5740385361211262, 22.42995274906226 0.3360526480252923, 22.46595136473468 0.09336969380887428, 22.46983284362373 0.0143603759458221, 22.47762299880658 -0.03815658445019221, 22.48966118212609 -0.2831994352740928, 22.48966118212609 -0.2831994352741134, 22.47762299880658 -0.5282422860980147, 22.44162438313417 -0.7709252403144335, 22.38201202145661 -1.008911128410269, 22.33733720919316 -1.1337688643328, 22.35960416230565 -1.222663524140609, 22.39560277797807 -1.465346478357027, 22.40764096129758 -1.710389329180928, 22.40764096129758 -1.710389329180948, 22.39560277797807 -1.95543218000485, 22.35960416230566 -2.198115134221269, 22.2999918006281 -2.436101022317104, 22.2173397925758 -2.667097910093672, 22.12083454788258 -2.871141106569248, 22.10890457508578 -3.113981281601384, 22.09753928949359 -3.190599813495125, 22.11425511370344 -3.225942454346874, 22.19690712175575 -3.456939342123441, 22.2565194834333 -3.694925230219274, 22.27796733187449 -3.83951487968404, 22.30329211813286 -3.893059641470262, 22.38594412618517 -4.124056529246829, 22.44555648786272 -4.362042417342662, 22.48155510353514 -4.604725371559081, 22.49359328685465 -4.849768222382981, 22.49359328685465 -4.849768222383002, 22.48155510353514 -5.094811073206903, 22.46639370225972 -5.197020938304527, 22.4742428718067 -5.35679445490105, 22.4742428718067 -5.35679445490107, 22.47305251707211 -5.381024682163306, 22.47387421054751 -5.386564089619583, 22.48591239386703 -5.631606940443484, 22.48591239386703 -5.631606940443504, 22.47387421054752 -5.876649791267405, 22.4378755948751 -6.119332745483824, 22.37826323319755 -6.357318633579659, 22.37581970840857 -6.364147827869776, 22.33416054986185 -6.530460511431846, 22.25150854180955 -6.761457399208414, 22.14661287140222 -6.983240660360684, 22.14070790264234 -6.993092505548669, 22.14375134269476 -7.001598350679505, 22.20336370437232 -7.239584238775338, 22.23936232004473 -7.482267192991756, 22.25140050336424 -7.727310043815657, 22.25140050336424 -7.727310043815677, 22.23936232004474 -7.972352894639578, 22.20336370437232 -8.215035848855997, 22.17904866103241 -8.312106943180988, 22.25649042501777 -8.528542174291447, 22.31610278669532 -8.76652806238728, 22.32621521933611 -8.834700545738135, 22.32751997111001 -8.838347082660768, 22.38713233278757 -9.076332970756601, 22.42313094845998 -9.319015924973019, 22.4351691317795 -9.564058775796919, 22.4351691317795 -9.564058775796941, 22.42313094845999 -9.809101626620842, 22.38713233278757 -10.05178458083726, 22.32751997111002 -10.2897704689331, 22.24486796305771 -10.52076735670966, 22.13997229265038 -10.74255061786193, 22.04314691805991 -10.90409399590901, 22.03406561057082 -10.96531519812561, 22.06337928494946 -11.16293193877699, 22.06660106476485 -11.22851277350407, 22.08298043995721 -11.27429006049709, 22.14259280163477 -11.51227594859293, 22.17739333308725 -11.74688207506299, 22.23461413935799 -11.90680351528065, 22.29422650103554 -12.14478940337648, 22.33022511670796 -12.3874723575929, 22.34226330002747 -12.6325152084168, 22.34226330002747 -12.63251520841682, 22.33022511670796 -12.87755805924072, 22.30844502746572 -13.02438749425295, 22.34721627930011 -13.13274588408515, 22.40682864097767 -13.37073177218098, 22.44282725665008 -13.6134147263974, 22.45486543996959 -13.8584575772213, 22.45486543996959 -13.85845757722132, 22.44282725665009 -14.10350042804522, 22.40682864097767 -14.34618338226164, 22.40667909943321 -14.3467803855721, 22.41507202806971 -14.3802868348902, 22.45107064374212 -14.62296978910662, 22.46310882706163 -14.86801263993052, 22.46310882706163 -14.86801263993054, 22.45107064374212 -15.11305549075445, 22.41507202806971 -15.35573844497086, 22.37320910548004 -15.52286460019147, 22.36618663574583 -15.66581025551359, 22.33018802007341 -15.90849320973001, 22.30832089851852 -15.99579165248403, 22.31582939514044 -16.01677649474516, 22.375441756818 -16.254762382841, 22.41144037249041 -16.49744533705741, 22.42347855580992 -16.74248818788131, 22.42347855580992 -16.74248818788134, 22.41144037249041 -16.98753103870524, 22.375441756818 -17.23021399292166, 22.31582939514044 -17.46819988101749, 22.311953937675 -17.47903105904116, 22.3018715319233 -17.68426314290183, 22.26587291625088 -17.92694609711825, 22.20998447691467 -18.15006525387409, 22.19944723477361 -18.36455574513298, 22.1634486191012 -18.6072386993494, 22.10383625742364 -18.84522458744524, 22.04941193378315 -18.99733038228982, 22.04364672206227 -19.11468396282544, 22.00764810638986 -19.35736691704186, 21.97709163753492 -19.47935517875256, 21.97514755279599 -19.51892793271611, 21.93914893712358 -19.76161088693253, 21.87953657544602 -19.99959677502837, 21.79688456739372 -20.23059366280494, 21.69198889698639 -20.45237692395721, 21.64536097446595 -20.53017091284831, 21.58631018238545 -20.69520682078059, 21.48141451197812 -20.91699008193286, 21.35528538186359 -21.12742382241687, 21.20913748451407 -21.32448145027698, 21.0443783040736 -21.50626519283423, 20.86259456151634 -21.67102437327471, 20.66553693365623 -21.81717227062423, 20.45510319317222 -21.94330140073875, 20.23331993201996 -22.04819707114608, 20.00232304424339 -22.13084907919839, 19.76433715614755 -22.19046144087594, 19.52165420193113 -22.22646005654836, 19.27661135110723 -22.23849823986787, 19.03156850028333 -22.22646005654836, 18.78888554606691 -22.19046144087594, 18.73887089664221 -22.17793342337652, 18.4973901430078 -22.18979661208253, 18.41784477514719 -22.18588879874579, 18.41349373531924 -22.18697867748596, 18.17081078110282 -22.22297729315838, 17.98787095563432 -22.23196455048102, 17.79768532119148 -22.27960357192466, 17.55500236697506 -22.31560218759708, 17.30995951615116 -22.32764037091659, 17.06491666532726 -22.31560218759708, 16.82223371111084 -22.27960357192466, 16.584247823015 -22.21999121024711, 16.4666967485392 -22.177930763253, 16.4099384081795 -22.1695114587847, 16.28838315841296 -22.13906345377539, 16.17480211986003 -22.19278328422008, 15.94380523208346 -22.27543529227239, 15.70581934398762 -22.33504765394994, 15.46313638977121 -22.37104626962236, 15.2180935389473 -22.38308445294187, 14.9730506881234 -22.37104626962236, 14.73036773390698 -22.33504765394994, 14.65305301275748 -22.31568132447116, 14.45362419243909 -22.3452637954767, 14.20858134161519 -22.35730197879621, 13.96353849079129 -22.3452637954767, 13.73523415567763 -22.31139804646833, 13.59984162375042 -22.33148163139707, 13.35479877292652 -22.34351981471658, 13.10975592210262 -22.33148163139707, 12.8670729678862 -22.29548301572466, 12.62908707979036 -22.2358706540471, 12.61296722408551 -22.23010287744914, 12.51509107260605 -22.26512352442874, 12.27710518451022 -22.3247358861063, 12.0344222302938 -22.36073450177871, 11.7893793794699 -22.37277268509822, 11.544336528646 -22.36073450177871, 11.42614034228609 -22.34320175375175, 11.41367609013224 -22.34505065090392, 11.17312139811863 -22.35686834511981, 11.0505742524468 -22.38756480711927, 10.96704756934117 -22.39995482014355, 10.89076207090825 -22.41906334275269, 10.64807911669183 -22.4550619584251, 10.40303626586793 -22.46710014174461, 10.15799341504403 -22.4550619584251, 9.915310460827609 -22.41906334275269, 9.678390165997076 -22.35971789829296, 9.461434530251323 -22.39190022677332, 9.216391679427421 -22.40393841009282, 8.971348828603521 -22.39190022677332, 8.859745401813472 -22.37534542224775, 8.640809946866888 -22.43018589883542, 8.39812699265047 -22.46618451450784, 8.153084141826568 -22.47822269782734, 7.908041291002667 -22.46618451450784, 7.665358336786248 -22.43018589883542, 7.654227093690167 -22.42739766758913, 7.631127550771891 -22.42853247536382, 7.386084699947989 -22.41649429204432, 7.14340174573157 -22.3804956763719, 6.961547070081421 -22.33494345147172, 6.84664652751557 -22.31789956602152, 6.757417223308805 -22.33113548297839, 6.512374372484904 -22.3431736662979, 6.267331521661002 -22.33113548297839, 6.024648567444583 -22.29513686730598, 5.786662679348748 -22.23552450562843, 5.679434149468312 -22.19715752414906, 5.639137942656076 -22.19517789845072, 5.527756450060979 -22.2116997821483, 5.309290253954825 -22.22243233814412, 5.171949898293681 -22.28738948868023, 4.940953010517113 -22.37004149673253, 4.702967122421279 -22.42965385841009, 4.46028416820486 -22.4656524740825, 4.215241317380959 -22.47769065740201, 3.970198466557057 -22.4656524740825, 3.750904670430335 -22.43312331227301, 3.67246061444501 -22.44475938878411, 3.427417763621108 -22.45679757210362, 3.182374912797207 -22.44475938878411, 2.939691958580788 -22.40876077311169, 2.701706070484953 -22.34914841143414, 2.470709182708385 -22.26649640338184, 2.400539054657536 -22.233308404494, 2.382213028549373 -22.23058999531359, 2.144227140453538 -22.17097763363604, 1.91323025267697 -22.08832562558374, 1.772685063718212 -22.02185270178525, 1.740138581807164 -22.04599081787381, 1.529704841323152 -22.17211994798834, 1.307921580170883 -22.27701561839567, 1.076924692394315 -22.35966762644798, 0.8389388042984798 -22.41927998812553, 0.5962558500820608 -22.45527860379795, 0.3512129992581597 -22.46731678711745, 0.1061701484342584 -22.45527860379795, -0.04851836590371944 -22.43233273026278, -0.2461837018222049 -22.42262205500048, -0.4888666560386239 -22.38662343932807, -0.726852544134459 -22.32701107765052, -0.9578494319110273 -22.24435906959821, -1.125708636819748 -22.16496757836726, -1.139764547714639 -22.17161552911361, -1.370761435491207 -22.25426753716592, -1.608747323587042 -22.31387989884347, -1.851430277803461 -22.34987851451589, -2.096473128627362 -22.3619166978354, -2.341515979451263 -22.34987851451589, -2.455727455302357 -22.33293684245731, -2.55442301821249 -22.34757694624723, -2.617898317366901 -22.37028877144683, -2.855884205462736 -22.42990113312439, -3.098567159679154 -22.4658997487968, -3.343610010503056 -22.47793793211631, -3.588652861326957 -22.4658997487968, -3.831335815543376 -22.42990113312439, -3.907954521318272 -22.41070914642104, -3.975896008872092 -22.41404689767321, -4.183715500617316 -22.44487400721096, -4.428758351441217 -22.45691219053047, -4.673801202265119 -22.44487400721096, -4.916484156481538 -22.40887539153854, -5.020084557130864 -22.38292484210529, -5.020419078409338 -22.38304453573263, -5.258404966505172 -22.44265689741019, -5.501087920721591 -22.4786555130826, -5.746130771545492 -22.49069369640211, -5.991173622369394 -22.4786555130826, -6.123693431593027 -22.45899805631302, -6.264189488772653 -22.46590018500729, -6.509232339596555 -22.45386200168778, -6.751915293812974 -22.41786338601537, -6.989901181908809 -22.35825102433781, -7.220898069685378 -22.27559901628551, -7.347294079158904 -22.2158181559913, -7.532152849094219 -22.18839694779783, -7.598927497924632 -22.17167076899446, -7.804850114037394 -22.22325169913918, -8.047533068253813 -22.2592503148116, -8.292575919077715 -22.27128849813111, -8.537618769901615 -22.2592503148116, -8.565029213837116 -22.25518435954156, -8.633110179045801 -22.27954411840584, -8.871096067141636 -22.3391564800834, -9.113779021358054 -22.37515509575582, -9.358821872181956 -22.38719327907532, -9.603864723005856 -22.37515509575582, -9.846547677222276 -22.3391564800834, -10.08453356531811 -22.27954411840584, -10.09791748181934 -22.27475527650811, -10.14016295582498 -22.29473589765361, -10.37115984360155 -22.37738790570592, -10.60914573169738 -22.43700026738347, -10.8518286859138 -22.47299888305589, -11.0968715367377 -22.48503706637539, -11.3419143875616 -22.47299888305589, -11.58459734177802 -22.43700026738347, -11.75310292370707 -22.3947918163908, -11.90532691377749 -22.43292194093173, -12.1480098679939 -22.46892055660414, -12.39305271881781 -22.48095873992365, -12.63809556964171 -22.46892055660414, -12.88077852385813 -22.43292194093173, -13.11876441195396 -22.37330957925418, -13.20218180754802 -22.34346235785346, -13.35693885800207 -22.35106508422189, -13.60198170882597 -22.33902690090238, -13.84466466304239 -22.30302828522996, -14.08265055113823 -22.24341592355241, -14.31364743891479 -22.16076391550011, -14.50430349127707 -22.07059031842225))"; + + // epl result for union + String eplWKT = "MULTIPOLYGON (((-15.90174522590254 -22.49823229650554, -15.65670237507864 -22.48619411318603, -15.41401942086222 -22.45019549751362, -15.17603353276639 -22.39058313583606, -14.94503664498982 -22.30793112778376, -14.72325338383755 -22.20303545737643, -14.51281964335354 -22.0769063272619, -14.50430349127707 -22.07059031842225, -14.31364743891479 -22.16076391550011, -14.08265055113823 -22.24341592355241, -13.84466466304239 -22.30302828522996, -13.60198170882597 -22.33902690090238, -13.35693885800207 -22.35106508422189, -13.20218180754802 -22.34346235785346, -13.11876441195396 -22.37330957925418, -12.88077852385813 -22.43292194093173, -12.63809556964171 -22.46892055660414, -12.39305271881781 -22.48095873992365, -12.1480098679939 -22.46892055660414, -11.90532691377749 -22.43292194093173, -11.75310292370707 -22.3947918163908, -11.58459734177802 -22.43700026738347, -11.3419143875616 -22.47299888305589, -11.0968715367377 -22.48503706637539, -10.8518286859138 -22.47299888305589, -10.60914573169738 -22.43700026738347, -10.37115984360155 -22.37738790570592, -10.14016295582498 -22.29473589765361, -10.09791748181934 -22.27475527650811, -10.08453356531811 -22.27954411840584, -9.846547677222276 -22.3391564800834, -9.603864723005856 -22.37515509575582, -9.358821872181956 -22.38719327907532, -9.113779021358054 -22.37515509575582, -8.871096067141636 -22.3391564800834, -8.633110179045801 -22.27954411840584, -8.565029213837116 -22.25518435954156, -8.537618769901615 -22.2592503148116, -8.292575919077715 -22.27128849813111, -8.047533068253813 -22.2592503148116, -7.804850114037394 -22.22325169913918, -7.598927497924632 -22.17167076899446, -7.532152849094219 -22.18839694779783, -7.347294079158904 -22.2158181559913, -7.220898069685378 -22.27559901628551, -6.989901181908809 -22.35825102433781, -6.751915293812974 -22.41786338601537, -6.509232339596555 -22.45386200168778, -6.264189488772653 -22.46590018500729, -6.123693431593027 -22.45899805631302, -5.991173622369394 -22.4786555130826, -5.746130771545492 -22.49069369640211, -5.501087920721591 -22.4786555130826, -5.258404966505172 -22.44265689741019, -5.020419078409338 -22.38304453573263, -5.020084557130864 -22.38292484210529, -4.916484156481538 -22.40887539153854, -4.673801202265119 -22.44487400721096, -4.428758351441217 -22.45691219053047, -4.183715500617316 -22.44487400721096, -3.975896008872092 -22.41404689767321, -3.907954521318273 -22.41070914642104, -3.831335815543376 -22.42990113312439, -3.588652861326957 -22.4658997487968, -3.343610010503056 -22.47793793211631, -3.098567159679154 -22.4658997487968, -2.855884205462736 -22.42990113312439, -2.617898317366901 -22.37028877144683, -2.55442301821249 -22.34757694624723, -2.455727455302357 -22.33293684245731, -2.341515979451263 -22.34987851451589, -2.096473128627362 -22.3619166978354, -1.851430277803461 -22.34987851451589, -1.608747323587042 -22.31387989884347, -1.370761435491207 -22.25426753716592, -1.139764547714639 -22.17161552911361, -1.125708636819748 -22.16496757836726, -0.9578494319110273 -22.24435906959821, -0.726852544134459 -22.32701107765052, -0.4888666560386239 -22.38662343932807, -0.2461837018222049 -22.42262205500048, -0.04851836590371948 -22.43233273026278, 0.1061701484342584 -22.45527860379795, 0.3512129992581597 -22.46731678711745, 0.5962558500820608 -22.45527860379795, 0.8389388042984798 -22.41927998812553, 1.076924692394315 -22.35966762644798, 1.307921580170883 -22.27701561839567, 1.529704841323152 -22.17211994798834, 1.740138581807164 -22.04599081787381, 1.772685063718212 -22.02185270178525, 1.91323025267697 -22.08832562558374, 2.144227140453538 -22.17097763363604, 2.382213028549373 -22.23058999531359, 2.400539054657536 -22.233308404494, 2.470709182708385 -22.26649640338184, 2.701706070484953 -22.34914841143414, 2.939691958580788 -22.40876077311169, 3.182374912797207 -22.44475938878411, 3.427417763621108 -22.45679757210362, 3.67246061444501 -22.44475938878411, 3.750904670430335 -22.43312331227301, 3.970198466557057 -22.4656524740825, 4.215241317380959 -22.47769065740201, 4.46028416820486 -22.4656524740825, 4.702967122421279 -22.42965385841009, 4.940953010517113 -22.37004149673253, 5.171949898293681 -22.28738948868023, 5.309290253954825 -22.22243233814412, 5.527756450060979 -22.2116997821483, 5.63913794265608 -22.19517789845072, 5.679434149468313 -22.19715752414906, 5.786662679348748 -22.23552450562843, 6.024648567444583 -22.29513686730598, 6.267331521661002 -22.33113548297839, 6.512374372484904 -22.3431736662979, 6.757417223308805 -22.33113548297839, 6.846646527515573 -22.31789956602152, 6.961547070081421 -22.33494345147172, 7.14340174573157 -22.3804956763719, 7.386084699947989 -22.41649429204432, 7.631127550771891 -22.42853247536382, 7.654227093690167 -22.42739766758913, 7.665358336786248 -22.43018589883542, 7.908041291002667 -22.46618451450784, 8.153084141826568 -22.47822269782734, 8.39812699265047 -22.46618451450784, 8.640809946866888 -22.43018589883542, 8.859745401813472 -22.37534542224775, 8.971348828603521 -22.39190022677332, 9.216391679427421 -22.40393841009282, 9.461434530251323 -22.39190022677332, 9.678390165997076 -22.35971789829296, 9.915310460827609 -22.41906334275269, 10.15799341504403 -22.4550619584251, 10.40303626586793 -22.46710014174461, 10.64807911669183 -22.4550619584251, 10.89076207090825 -22.41906334275269, 10.96704756934117 -22.39995482014355, 11.0505742524468 -22.38756480711927, 11.17312139811863 -22.35686834511981, 11.41367609013224 -22.34505065090392, 11.42614034228609 -22.34320175375175, 11.544336528646 -22.36073450177871, 11.7893793794699 -22.37277268509822, 12.0344222302938 -22.36073450177871, 12.27710518451022 -22.3247358861063, 12.51509107260605 -22.26512352442874, 12.61296722408551 -22.23010287744914, 12.62908707979036 -22.2358706540471, 12.8670729678862 -22.29548301572466, 13.10975592210262 -22.33148163139707, 13.35479877292652 -22.34351981471658, 13.59984162375042 -22.33148163139707, 13.73523415567763 -22.31139804646833, 13.96353849079129 -22.3452637954767, 14.20858134161519 -22.35730197879621, 14.45362419243909 -22.3452637954767, 14.65305301275748 -22.31568132447116, 14.73036773390698 -22.33504765394994, 14.9730506881234 -22.37104626962236, 15.2180935389473 -22.38308445294187, 15.46313638977121 -22.37104626962236, 15.70581934398762 -22.33504765394994, 15.94380523208346 -22.27543529227239, 16.17480211986003 -22.19278328422008, 16.28838315841296 -22.13906345377539, 16.4099384081795 -22.1695114587847, 16.4666967485392 -22.177930763253, 16.584247823015 -22.21999121024711, 16.82223371111084 -22.27960357192466, 17.06491666532726 -22.31560218759708, 17.30995951615116 -22.32764037091659, 17.55500236697506 -22.31560218759708, 17.79768532119148 -22.27960357192466, 17.98787095563432 -22.23196455048102, 18.17081078110282 -22.22297729315838, 18.41349373531924 -22.18697867748596, 18.41784477514719 -22.18588879874579, 18.4973901430078 -22.18979661208253, 18.73887089664221 -22.17793342337652, 18.78888554606691 -22.19046144087594, 19.03156850028333 -22.22646005654836, 19.27661135110723 -22.23849823986787, 19.52165420193113 -22.22646005654836, 19.76433715614755 -22.19046144087594, 20.00232304424339 -22.13084907919839, 20.23331993201996 -22.04819707114608, 20.45510319317222 -21.94330140073875, 20.66553693365623 -21.81717227062423, 20.86259456151634 -21.67102437327471, 21.0443783040736 -21.50626519283423, 21.20913748451407 -21.32448145027698, 21.35528538186359 -21.12742382241687, 21.48141451197812 -20.91699008193286, 21.58631018238545 -20.69520682078059, 21.64536097446595 -20.53017091284831, 21.69198889698639 -20.45237692395721, 21.79688456739372 -20.23059366280494, 21.87953657544602 -19.99959677502837, 21.93914893712358 -19.76161088693253, 21.97514755279599 -19.51892793271611, 21.97709163753492 -19.47935517875256, 22.00764810638986 -19.35736691704186, 22.04364672206227 -19.11468396282544, 22.04941193378315 -18.99733038228982, 22.10383625742364 -18.84522458744524, 22.1634486191012 -18.6072386993494, 22.19944723477361 -18.36455574513298, 22.20998447691467 -18.15006525387409, 22.26587291625088 -17.92694609711825, 22.3018715319233 -17.68426314290183, 22.311953937675 -17.47903105904117, 22.31582939514044 -17.46819988101749, 22.375441756818 -17.23021399292166, 22.41144037249041 -16.98753103870524, 22.42347855580992 -16.74248818788132, 22.41144037249041 -16.49744533705741, 22.375441756818 -16.254762382841, 22.31582939514044 -16.01677649474516, 22.30832089851852 -15.99579165248403, 22.33018802007341 -15.90849320973001, 22.36618663574583 -15.66581025551359, 22.37320910548004 -15.52286460019147, 22.41507202806971 -15.35573844497086, 22.45107064374212 -15.11305549075445, 22.46310882706163 -14.86801263993053, 22.45107064374212 -14.62296978910662, 22.41507202806971 -14.3802868348902, 22.40667909943321 -14.3467803855721, 22.40682864097767 -14.34618338226164, 22.44282725665009 -14.10350042804522, 22.45486543996959 -13.85845757722131, 22.44282725665008 -13.6134147263974, 22.40682864097767 -13.37073177218098, 22.34721627930011 -13.13274588408515, 22.30844502746573 -13.02438749425295, 22.33022511670796 -12.87755805924072, 22.34226330002747 -12.63251520841681, 22.33022511670796 -12.3874723575929, 22.29422650103554 -12.14478940337648, 22.23461413935799 -11.90680351528065, 22.17739333308725 -11.74688207506299, 22.14259280163477 -11.51227594859293, 22.08298043995721 -11.27429006049709, 22.06660106476485 -11.22851277350407, 22.06337928494946 -11.16293193877699, 22.03406561057082 -10.96531519812561, 22.04314691805991 -10.90409399590901, 22.13997229265038 -10.74255061786193, 22.24486796305771 -10.52076735670966, 22.32751997111002 -10.2897704689331, 22.38713233278757 -10.05178458083726, 22.42313094845999 -9.809101626620842, 22.4351691317795 -9.56405877579693, 22.42313094845998 -9.319015924973019, 22.38713233278757 -9.076332970756601, 22.32751997111001 -8.838347082660768, 22.32621521933611 -8.834700545738135, 22.31610278669532 -8.76652806238728, 22.25649042501777 -8.528542174291447, 22.17904866103241 -8.312106943180988, 22.20336370437232 -8.215035848855997, 22.23936232004474 -7.972352894639578, 22.25140050336424 -7.727310043815667, 22.23936232004473 -7.482267192991756, 22.20336370437232 -7.239584238775338, 22.14375134269476 -7.001598350679505, 22.14070790264234 -6.993092505548669, 22.14661287140222 -6.983240660360684, 22.25150854180955 -6.761457399208414, 22.33416054986185 -6.530460511431846, 22.37581970840857 -6.364147827869776, 22.37826323319755 -6.357318633579659, 22.4378755948751 -6.119332745483824, 22.47387421054752 -5.876649791267405, 22.48591239386703 -5.631606940443493, 22.47387421054751 -5.386564089619583, 22.47305251707211 -5.381024682163304, 22.4742428718067 -5.35679445490106, 22.46639370225972 -5.197020938304531, 22.48155510353514 -5.094811073206903, 22.49359328685465 -4.849768222382991, 22.48155510353514 -4.604725371559081, 22.44555648786272 -4.362042417342662, 22.38594412618517 -4.124056529246829, 22.30329211813286 -3.893059641470262, 22.27796733187449 -3.83951487968404, 22.2565194834333 -3.694925230219274, 22.19690712175575 -3.456939342123441, 22.11425511370344 -3.225942454346874, 22.09753928949359 -3.190599813495125, 22.10890457508578 -3.113981281601384, 22.12083454788258 -2.87114110656925, 22.2173397925758 -2.667097910093672, 22.2999918006281 -2.436101022317104, 22.35960416230566 -2.198115134221269, 22.39560277797807 -1.95543218000485, 22.40764096129758 -1.710389329180938, 22.39560277797807 -1.465346478357027, 22.35960416230565 -1.222663524140609, 22.33733720919316 -1.1337688643328, 22.38201202145661 -1.008911128410269, 22.44162438313417 -0.7709252403144335, 22.47762299880658 -0.5282422860980147, 22.48966118212609 -0.2831994352741031, 22.47762299880658 -0.03815658445019221, 22.46983284362372 0.01436037594582373, 22.46595136473468 0.09336969380887428, 22.42995274906226 0.3360526480252923, 22.37034038738471 0.5740385361211262, 22.37015764437406 0.5745492686213597, 22.38255954800001 0.6240604432408892, 22.41855816367243 0.866743397457308, 22.43059634699193 1.11178624828122, 22.41855816367242 1.356829099105131, 22.38255954800001 1.599512053321549, 22.32294718632245 1.837497941417382, 22.31304697431726 1.86516718037985, 22.34065869981704 1.975399367630999, 22.37665731548946 2.218082321847417, 22.38869549880896 2.463125172671329, 22.37665731548945 2.70816802349524, 22.37655704668347 2.708843980878516, 22.37956940927705 2.720870006469926, 22.41556802494946 2.963552960686345, 22.42622722872951 3.180526038139268, 22.42905218083252 3.191803879112921, 22.46505079650494 3.43448683332934, 22.47708897982444 3.679529684153251, 22.46505079650493 3.924572534977163, 22.42905218083252 4.167255489193581, 22.36943981915496 4.405241377289414, 22.28678781110266 4.636238265065981, 22.20182312370297 4.815881003237257, 22.20080623926725 4.822736281262513, 22.14119387758969 5.060722169358346, 22.11639472283721 5.130031163807564, 22.13631459835018 5.264320056500067, 22.14187238184646 5.377451338525397, 22.17192465358179 5.440991528550525, 22.25457666163409 5.671988416327093, 22.31418902331165 5.909974304422928, 22.35018763898406 6.152657258639347, 22.36222582230357 6.397700109463258, 22.35609034175698 6.522590685093183, 22.36638055208554 6.591961648264912, 22.37841873540505 6.837004499088824, 22.36638055208554 7.082047349912735, 22.36084912120376 7.119337227936076, 22.3785359266002 7.16876853756261, 22.43814828827776 7.406754425658445, 22.47414690395017 7.649437379874864, 22.48618508726968 7.894480230698775, 22.47414690395017 8.139523081522686, 22.43814828827776 8.382206035739104, 22.41010307540168 8.494168801603886, 22.41453398546516 8.584362047950311, 22.40249580214564 8.829404898774222, 22.36649718647323 9.07208785299064, 22.30688482479567 9.310073741086473, 22.29103373003705 9.354374583175842, 22.31043194547102 9.431816600194061, 22.34643056114344 9.674499554410479, 22.34931049101803 9.73312187373493, 22.37435753770335 9.803123679724234, 22.43396989938091 10.04110956782007, 22.46996851505332 10.28379252203649, 22.48200669837283 10.5288353728604, 22.46996851505332 10.77387822368431, 22.4339698993809 11.01656117790073, 22.37435753770335 11.25454706599656, 22.29170552965104 11.48554395377313, 22.22014157634299 11.63685322024689, 22.23443208872027 11.73319202928731, 22.24647027203978 11.97823488011122, 22.23443208872027 12.22327773093513, 22.19843347304785 12.46596068515155, 22.1439806270021 12.68334863222938, 22.15682761933628 12.71051131534878, 22.23947962738858 12.94150820312535, 22.29909198906613 13.17949409122119, 22.33509060473855 13.42217704543761, 22.34712878805806 13.66721989626152, 22.33509060473855 13.91226274708543, 22.29909198906613 14.15494570130185, 22.25769045232944 14.3202299009467, 22.28553142661942 14.50791850404303, 22.29756960993893 14.75296135486694, 22.28553142661942 14.99800420569085, 22.24953281094701 15.24068715990727, 22.18992044926945 15.47867304800311, 22.10726844121714 15.70966993577967, 22.06904229209649 15.79049233750846, 22.13883131088654 15.90692828101623, 22.24372698129387 16.1287115421685, 22.32637898934617 16.35970842994507, 22.38599135102373 16.59769431804091, 22.42198996669614 16.84037727225733, 22.43402815001565 17.08542012308124, 22.42198996669614 17.33046297390515, 22.38599135102372 17.57314592812157, 22.32637898934617 17.8111318162174, 22.32364979959007 17.81875939060629, 22.3440670916067 17.95640159298384, 22.35610527492621 18.20144444380775, 22.3440670916067 18.44648729463166, 22.30806847593428 18.68917024884808, 22.25965275264398 18.88245665087605, 22.27899073271135 19.01282272311713, 22.29102891603086 19.25786557394104, 22.27899073271135 19.50290842476495, 22.24299211703893 19.74559137898136, 22.18337975536138 19.9835772670772, 22.10072774730907 20.21457415485376, 21.99583207690174 20.43635741600603, 21.86970294678721 20.64679115649004, 21.72355504943769 20.84384878435015, 21.55879586899722 21.0256325269074, 21.37701212643996 21.19039170734787, 21.17995449857986 21.3365396046974, 21.1524925145701 21.35299968446598, 21.02993804195481 21.46407658377149, 20.97836787083578 21.50232362935326, 20.96116015640032 21.52552554637733, 20.79640097595984 21.70730928893459, 20.61461723340259 21.87206846937505, 20.41755960554248 22.01821636672458, 20.20712586505847 22.1443454968391, 19.9853426039062 22.24924116724643, 19.96551028205333 22.25633728547234, 19.869953371516 22.30153233824949, 19.63895648373943 22.38418434630179, 19.4009705956436 22.44379670797935, 19.15828764142718 22.47979532365176, 18.91324479060328 22.49183350697127, 18.66820193977938 22.47979532365176, 18.42551898556296 22.44379670797935, 18.18753309746713 22.38418434630179, 18.01708231883724 22.3231960825055, 17.85458993009069 22.36389830701685, 17.61190697587427 22.39989692268926, 17.36686412505037 22.41193510600877, 17.35406316190014 22.41130623501518, 17.32962249232418 22.41493166587283, 17.08457964150028 22.42696984919234, 16.83953679067638 22.41493166587283, 16.59685383645996 22.37893305020042, 16.35886794836413 22.31932068852286, 16.19915562030759 22.26217470377977, 16.05645972310251 22.29791816530244, 15.81377676888609 22.33391678097486, 15.56873391806219 22.34595496429436, 15.32369106723829 22.33391678097486, 15.08100811302187 22.29791816530244, 14.84302222492603 22.23830580362489, 14.83509011495141 22.23546764929387, 14.73379523464183 22.24044394766126, 14.48875238381793 22.22840576434175, 14.24606942960151 22.19240714866933, 14.0115966916425 22.13367478529024, 13.8158885019195 22.16270535288215, 13.5708456510956 22.17474353620165, 13.3258028002717 22.16270535288215, 13.08311984605528 22.12670673720973, 12.84513395795945 22.06709437553218, 12.61413707018288 21.98444236747987, 12.60921408285645 21.98211396788231, 12.41638569326412 22.05110906891031, 12.17839980516828 22.11072143058786, 11.93571685095186 22.14672004626027, 11.69067400012796 22.15875822957978, 11.65303800255386 22.15690929158103, 11.62238858930415 22.1614557025629, 11.5620735362037 22.1644187911154, 11.50233734938656 22.19267190332598, 11.27134046160999 22.27532391137828, 11.03335457351416 22.33493627305583, 10.79067161929774 22.37093488872825, 10.54562876847384 22.38297307204775, 10.38434897519156 22.37504990387232, 10.25867615563267 22.39369170566832, 10.01363330480877 22.40572988898783, 9.768590453984871 22.39369170566832, 9.525907499768453 22.35769308999591, 9.287921611672617 22.29808072831835, 9.056924723896049 22.21542872026604, 8.928144125486533 22.15452003340014, 8.755476828124921 22.12890725933043, 8.730173203256818 22.12256903125539, 8.674222684091824 22.14903165601327, 8.502525895102208 22.21046574944508, 8.374749906920318 22.27089929105982, 8.14375301914375 22.35355129911213, 7.905767131047916 22.41316366078968, 7.663084176831497 22.44916227646209, 7.418041326007597 22.4612004597816, 7.172998475183696 22.44916227646209, 7.045062493122757 22.43018476622139, 7.002047076849425 22.44095956708507, 6.759364122633007 22.47695818275748, 6.514321271809106 22.48899636607699, 6.269278420985205 22.47695818275748, 6.026595466768787 22.44095956708507, 5.788609578672952 22.38134720540751, 5.71264440274838 22.35416643084103, 5.574092325989588 22.34735980378086, 5.33140937177317 22.31136118810844, 5.093423483677336 22.25174882643089, 4.862426595900769 22.16909681837858, 4.743496122122889 22.11284689350158, 4.602792578218637 22.17939471361137, 4.371795690442069 22.26204672166367, 4.133809802346234 22.32165908334122, 3.891126848129816 22.35765769901364, 3.646083997305915 22.36969588233315, 3.405605077979647 22.35788191059068, 3.362461836283031 22.37331880930579, 3.124475948187196 22.43293117098334, 2.881792993970778 22.46892978665575, 2.636750143146877 22.48096796997526, 2.585818149549957 22.47846584157736, 2.550904688902151 22.48364476424094, 2.305861838078251 22.49568294756045, 2.06081898725435 22.48364476424094, 1.818136033037931 22.44764614856852, 1.580150144942097 22.38803378689097, 1.540541675798155 22.37386165001876, 1.530509838959956 22.37134880570402, 1.299512951183388 22.28869679765171, 1.124606770200216 22.205972334961, 0.9988582651750725 22.23747069573335, 0.756175310958654 22.27346931140577, 0.5111324601347533 22.28550749472527, 0.2660896093108527 22.27346931140577, 0.0234066550944344 22.23747069573335, -0.03697761262200187 22.22234522406968, -0.0641515852677238 22.22915194987403, -0.3068345394841422 22.26515056554645, -0.5518773903080429 22.27718874886595, -0.7244596559357803 22.26871032582958, -0.9413867209710468 22.32304772693349, -1.142651755126477 22.35290257453187, -1.195359120379203 22.37176157137471, -1.433345008475038 22.43137393305226, -1.676027962691456 22.46737254872468, -1.921070813515357 22.47941073204419, -2.166113664339258 22.46737254872468, -2.408796618555676 22.43137393305226, -2.64678250665151 22.37176157137471, -2.765340328481469 22.32934090441736, -2.953278436076054 22.33857371159512, -3.198321286899955 22.32653552827561, -3.383074108523836 22.29913003602953, -3.410372612368247 22.30596795527529, -3.653055566584665 22.34196657094771, -3.898098417408566 22.35400475426722, -4.143141268232466 22.34196657094771, -4.385824222448885 22.30596795527529, -4.462919229365399 22.28665666134685, -4.543872356633671 22.30693436411426, -4.78655531085009 22.34293297978667, -5.03159816167399 22.35497116310618, -5.276641012497891 22.34293297978667, -5.519323966714309 22.30693436411426, -5.740285956380461 22.25158626700496, -5.849769324545464 22.23534594346918, -6.045368531132135 22.18635089279545, -6.047974121578137 22.18700355922577, -6.290657075794555 22.22300217489819, -6.535699926618456 22.2350403582177, -6.541298596980119 22.23476531317993, -6.698394072660326 22.29097497317093, -6.93637996075616 22.35058733484848, -7.179062914972579 22.3865859505209, -7.42410576579648 22.39862413384041, -7.624786122499572 22.38876534010497, -7.81773403682064 22.43709627663851, -8.060416991037059 22.47309489231092, -8.305459841860959 22.48513307563043, -8.550502692684859 22.47309489231092, -8.793185646901277 22.43709627663851, -9.005397594027517 22.38393995108657, -9.044718063582776 22.38200826028587, -9.214586297221089 22.407205832474, -9.459629148044989 22.41924401579351, -9.704671998868889 22.407205832474, -9.947354953085307 22.37120721680159, -10.18534084118114 22.31159485512403, -10.33580272181398 22.25775873339386, -10.51254839624864 22.32099934692418, -10.75053428434447 22.38061170860173, -10.99321723856089 22.41661032427414, -11.23826008938479 22.42864850759365, -11.29538315086152 22.42584223153411, -11.33038977901702 22.43461093540733, -11.57307273323344 22.47060955107974, -11.81811558405734 22.48264773439925, -12.06315843488124 22.47060955107974, -12.30584138909765 22.43461093540733, -12.54382727719349 22.37499857372977, -12.60371436525269 22.35357063098932, -12.83999844527089 22.38862006333841, -13.08504129609479 22.40065824665792, -13.33008414691869 22.38862006333841, -13.57276710113511 22.352621447666, -13.81075298923094 22.29300908598844, -13.86995584245114 22.27182596638811, -13.99875298466096 22.2527207151063, -14.02162334345316 22.24699198845396, -14.05563511998011 22.24532109701812, -14.29831807419653 22.20932248134571, -14.45234634580798 22.17074040780624, -14.48458465159365 22.1788156830242, -14.65976147368973 22.20480070992366, -14.71557451981399 22.22477093715091, -14.8206581025195 22.25109300434882, -14.90594999486599 22.29143306509782, -15.13694688264255 22.37408507315013, -15.37493277073839 22.43369743482768, -15.61761572495481 22.46969605050009, -15.86265857577871 22.4817342338196, -16.10770142660261 22.46969605050009, -16.35038438081903 22.43369743482768, -16.39788303796649 22.42179964058564, -16.57611124777833 22.43055543107375, -16.82115409860222 22.41851724775424, -16.89031477907391 22.40825822991765, -16.94578704243195 22.41648676288385, -17.19082989325585 22.42852494620336, -17.43587274407975 22.41648676288385, -17.5503671284345 22.399503125313, -17.67505861594785 22.41799936025091, -17.92010146677175 22.43003754357042, -18.16514431759565 22.41799936025091, -18.40782727181207 22.3820007445785, -18.64505406713305 22.32257852574262, -18.73588422207093 22.35507807484728, -18.97387011016676 22.41469043652483, -19.21655306438318 22.45068905219724, -19.46159591520708 22.46272723551675, -19.70663876603098 22.45068905219724, -19.9493217202474 22.41469043652483, -20.18730760834324 22.35507807484727, -20.41830449611981 22.27242606679497, -20.4307583865337 22.26653581530638, -20.56379166347918 22.23321271416002, -20.79478855125575 22.15056070610771, -21.01657181240802 22.04566503570039, -21.22700555289203 21.91953590558586, -21.42406318075214 21.77338800823634, -21.60584692330939 21.60862882779587, -21.77060610374987 21.42684508523861, -21.91675400109939 21.22978745737851, -22.04288313121391 21.01935371689449, -22.14777880162124 20.79757045574222, -22.23043080967355 20.56657356796565, -22.2900431713511 20.32858767986982, -22.32604178702352 20.0859047256534, -22.33807997034302 19.8408618748295, -22.32604178702352 19.5958190240056, -22.30537123841755 19.45646950438467, -22.3342576214756 19.34114859891324, -22.37025623714802 19.09846564469682, -22.38229442046752 18.85342279387292, -22.37025623714802 18.60837994304902, -22.35423385062678 18.50036578689495, -22.35761020545555 18.43163850548751, -22.37766358477907 18.29644960321025, -22.38970176809858 18.05140675238635, -22.37766358477907 17.80636390156245, -22.37260135853582 17.7722371442781, -22.38305833385616 17.55938052168966, -22.38181414347826 17.53405444473314, -22.41197150930309 17.33074999835235, -22.4240096926226 17.08570714752844, -22.41197150930309 16.84066429670454, -22.40636946919279 16.80289840992767, -22.44403823256925 16.65251627687095, -22.48003684824166 16.40983332265453, -22.49207503156117 16.16479047183062, -22.48003684824166 15.91974762100672, -22.44403823256925 15.6770646667903, -22.43547952265877 15.64289638152731, -22.42650680380195 15.4602524933292, -22.39050818812954 15.21756953911278, -22.33192658845886 14.98369868361621, -22.34105686595574 14.79784761562138, -22.32901868263623 14.55280476479748, -22.29302006696381 14.31012181058106, -22.26462335331059 14.19675577495375, -22.26277109686172 14.15905222877569, -22.22677248118931 13.91636927455927, -22.16716011951175 13.67838338646343, -22.10462297699719 13.50360378180867, -22.14988328742593 13.42809151584489, -22.25477895783326 13.20630825469262, -22.33743096588557 12.97531136691605, -22.39704332756312 12.73732547882022, -22.43304194323554 12.4946425246038, -22.44508012655504 12.2495996737799, -22.43304194323554 12.00455682295599, -22.39704332756312 11.76187386873957, -22.33743096588557 11.52388798064374, -22.25477895783326 11.29289109286717, -22.19678075951196 11.1702642054589, -22.231853125918 10.93382551658731, -22.2438913092375 10.68878266576341, -22.231853125918 10.44373981493951, -22.19585451024558 10.20105686072309, -22.13624214856803 9.963070972627253, -22.05359014051572 9.732074084850684, -22.04396671166407 9.711727054073368, -22.00373838951193 9.599296425506699, -22.03882619823041 9.525109487055827, -22.12147820628271 9.294112599279259, -22.18109056796027 9.056126711183422, -22.21708918363268 8.813443756967004, -22.2200347057179 8.75348627752272, -22.26415027510652 8.630191520729721, -22.32376263678407 8.392205632633884, -22.35976125245649 8.149522678417465, -22.37179943577599 7.904479827593563, -22.36111420259841 7.686976909581786, -22.394861026681 7.459474301195111, -22.4068992100005 7.214431450371209, -22.394861026681 6.969388599547307, -22.39454427325957 6.967253221432506, -22.39663570074542 6.958903774881473, -22.43263431641783 6.716220820665053, -22.44467249973734 6.471177969841151, -22.43263431641783 6.22613511901725, -22.42520472119385 6.176048856423258, -22.45924851244561 5.946544257999629, -22.47128669576512 5.701501407175727, -22.45924851244561 5.456458556351826, -22.42324989677319 5.213775602135406, -22.36363753509564 4.975789714039571, -22.30467603942607 4.811003372857127, -22.30921191741285 4.798326443125212, -22.36882427909041 4.560340555029376, -22.40482289476282 4.317657600812956, -22.41686107808233 4.072614749989055, -22.40482289476282 3.827571899165153, -22.38282310228573 3.679261345251841, -22.38695198811661 3.667721882395539, -22.44656434979417 3.429735994299703, -22.48256296546658 3.187053040083284, -22.49460114878609 2.942010189259382, -22.48256296546658 2.69696733843548, -22.44656434979417 2.454284384219061, -22.38695198811661 2.216298496123226, -22.37396184882762 2.1799934889219, -22.38658792514858 2.129587366737637, -22.422586540821 1.886904412521218, -22.43462472414051 1.641861561697316, -22.422586540821 1.396818710873414, -22.38658792514858 1.154135756656995, -22.3592771699918 1.045105110367365, -22.36350762099722 0.9589923018873644, -22.35146943767771 0.7139494510634626, -22.34532001016119 0.6724933781402493, -22.33427096093755 0.4475848143815596, -22.29827234526513 0.2049018601651402, -22.23865998358758 -0.03308402793069509, -22.20425046928654 -0.1292521779944436, -22.261279888389 -0.2886387269097357, -22.32089225006656 -0.5266246150055711, -22.35689086573897 -0.7693075692219906, -22.36718807520162 -0.9789120830667672, -22.42620471134769 -1.214519701527199, -22.46220332702011 -1.457202655743619, -22.47424151033962 -1.702245506567521, -22.46220332702011 -1.947288357391422, -22.42889752836875 -2.171817813823874, -22.43856291195954 -2.36856121658928, -22.42652472864004 -2.613604067413182, -22.4058295706438 -2.753119490067378, -22.40429375712431 -2.784381692441204, -22.40814810495108 -2.799769111506142, -22.4441467206235 -3.042452065722562, -22.456184903943 -3.287494916546463, -22.4441467206235 -3.532537767370365, -22.42631024597718 -3.652781512278879, -22.46166535455815 -3.891126294065749, -22.47370353787766 -4.13616914488965, -22.46166535455815 -4.381211995713552, -22.42566673888573 -4.623894949929971, -22.41659300653852 -4.660119320014077, -22.39777047505845 -4.787010520136427, -22.33815811338089 -5.024996408232262, -22.29060748755867 -5.157891504620128, -22.29667697197584 -5.198808644943893, -22.30871515529535 -5.443851495767794, -22.29913718581248 -5.638815543489387, -22.31302053289855 -5.732409467097974, -22.32505871621806 -5.977452317921876, -22.31302053289855 -6.222495168745778, -22.27702191722613 -6.465178122962197, -22.23700130787304 -6.624949351462579, -22.26191986068643 -6.792936589265861, -22.27337292135948 -7.026068995003135, -22.28286374225524 -7.046135649814543, -22.36551575030755 -7.277132537591112, -22.4251281119851 -7.515118425686947, -22.46112672765752 -7.757801379903367, -22.47316491097703 -8.002844230727268, -22.46112672765752 -8.24788708155117, -22.44973433808717 -8.324688333623625, -22.45836137069585 -8.500295616589442, -22.4573686685416 -8.520502533151651, -22.46998143377628 -8.605530889681926, -22.48201961709579 -8.850573740505828, -22.46998143377628 -9.09561659132973, -22.43398281810386 -9.33829954554615, -22.37437045642631 -9.576285433641985, -22.291718448374 -9.807282321418555, -22.18682277796667 -10.02906558257082, -22.06069364785215 -10.23949932305483, -22.01967276381155 -10.29480958237682, -22.00439035799606 -10.35582036634663, -21.92173834994376 -10.5868172541232, -21.8468060799781 -10.74524822781346, -21.92842458012701 -10.91781604746789, -21.99191048529292 -11.09524726609872, -22.01966467653555 -11.15392857315868, -22.10231668458785 -11.38492546093525, -22.16192904626541 -11.62291134903108, -22.19792766193782 -11.8655943032475, -22.19817821472779 -11.87069442245198, -22.23112272730048 -11.94034974194297, -22.31377473535278 -12.17134662971954, -22.37338709703034 -12.40933251781537, -22.40938571270275 -12.65201547203179, -22.42142389602226 -12.89705832285569, -22.40938571270275 -13.14210117367959, -22.37338709703034 -13.38478412789601, -22.31377473535278 -13.62277001599185, -22.29252178711778 -13.68216802717211, -22.29289905870317 -13.68984756671441, -22.29056292959623 -13.73740056711855, -22.29317150663481 -13.74291593987261, -22.37582351468712 -13.97391282764918, -22.43543587636467 -14.21189871574502, -22.47143449203709 -14.45458166996143, -22.48347267535659 -14.69962452078534, -22.47143449203709 -14.94466737160924, -22.43543587636467 -15.18735032582566, -22.42770850858644 -15.21819970725584, -22.40572505302204 -15.36640012664985, -22.43309942964606 -15.47568476407415, -22.46909804531847 -15.71836771829057, -22.48113622863798 -15.96341056911447, -22.46909804531847 -16.20845341993838, -22.43309942964606 -16.4511363741548, -22.3734870679685 -16.68912226225063, -22.29704609402757 -16.90276047256235, -22.33318673279856 -17.00376679140546, -22.39279909447611 -17.2417526795013, -22.42879771014853 -17.48443563371772, -22.44083589346804 -17.72947848454162, -22.42879771014853 -17.97452133536552, -22.39279909447611 -18.21720428958194, -22.3612750894556 -18.34305517218451, -22.38618093327824 -18.41266234260967, -22.4457932949558 -18.65064823070551, -22.48179191062821 -18.89333118492193, -22.49383009394772 -19.13837403574583, -22.48179191062821 -19.38341688656973, -22.46272989236197 -19.51192257546279, -22.47144518575322 -19.68932644793065, -22.45940700243371 -19.93436929875455, -22.4234083867613 -20.17705225297097, -22.36379602508374 -20.41503814106681, -22.28114401703143 -20.64603502884338, -22.1762483466241 -20.86781828999564, -22.05011921650958 -21.07825203047966, -21.90397131916006 -21.27530965833976, -21.73921213871958 -21.45709340089702, -21.55742839616233 -21.62185258133749, -21.36037076830222 -21.76800047868701, -21.14993702781821 -21.89412960880153, -20.92815376666594 -21.99902527920887, -20.69715687888937 -22.08167728726117, -20.45917099079353 -22.14128964893872, -20.42744614576441 -22.14599558515562, -20.37281740980389 -22.17183305301641, -20.14182052202732 -22.25448506106872, -19.90383463393149 -22.31409742274627, -19.66115167971507 -22.35009603841869, -19.63541513605011 -22.3513603937329, -19.53556033746936 -22.37637271868991, -19.29287738325294 -22.41237133436232, -19.04783453242904 -22.42440951768183, -18.80279168160514 -22.41237133436232, -18.56010872738872 -22.37637271868991, -18.32212283929288 -22.31676035701235, -18.15038036764053 -22.25530991806244, -17.93163213536989 -22.26605632960654, -17.86833950280889 -22.2629469619552, -17.85807912611075 -22.2677997587208, -17.62708223833419 -22.35045176677311, -17.38909635023835 -22.41006412845066, -17.14641339602193 -22.44606274412307, -16.90137054519803 -22.45810092744258, -16.65632769437413 -22.44606274412307, -16.4990865892769 -22.42273822951685, -16.38947103094286 -22.45019549751362, -16.14678807672644 -22.48619411318603, -15.90174522590254 -22.49823229650554)))"; + Geometry shapelyGeom = OperatorImportFromWkt.local().execute(0, Geometry.Type.Unknown, shapelyWKT, null); + Geometry eplGeom = OperatorImportFromWkt.local().execute(0, Geometry.Type.Unknown, eplWKT, null); + NonSimpleResult nonSimpleResult = new NonSimpleResult(); + boolean is_simple = OperatorSimplify.local().isSimpleAsFeature(shapelyGeom, null, true, nonSimpleResult, null); + assertFalse(is_simple); + assertTrue(OperatorSimplify.local().isSimpleAsFeature(eplGeom, null, true, nonSimpleResult, null)); + } + + @Test + public void testIDPass() { + int size = 1000; + List points = new ArrayList<>(size); + List pointList = new ArrayList<>(size); + for (int i = 0; i < size; i++) { + double x = randomWithRange(-20, 20); + double y = randomWithRange(-20, 20); + points.add(String.format("Point(%f %f)", x, y)); + pointList.add(new Point(x, y)); + } + + SimpleStringCursor simpleStringCursor = new SimpleStringCursor(points); + OperatorImportFromWktCursor operatorImportFromWktCursor = new OperatorImportFromWktCursor(0, simpleStringCursor); + double[] distances = new double[]{2.5}; + + GeometryCursor operatorBufferCursor = OperatorBuffer.local().execute(operatorImportFromWktCursor, + null, + distances, + Double.NaN, + 0, + true, + null); + + OperatorExportToWkbCursor operatorExportToGeoJsonCursor = new OperatorExportToWkbCursor(0, operatorBufferCursor); + + while (operatorExportToGeoJsonCursor.hasNext()) { + ByteBuffer wkb = operatorExportToGeoJsonCursor.next(); + long id = operatorExportToGeoJsonCursor.getByteBufferID(); + } + } + + static double randomWithRange(double min, double max) { + double range = Math.abs(max - min); + return (Math.random() * range) + (min <= max ? min : max); + } // @Test // @Ignore // public void testGeodesicBufferUnionPoint() { diff --git a/src/test/java/com/esri/core/geometry/TestWKBSupport.java b/src/test/java/com/esri/core/geometry/TestWKBSupport.java index 01861b08..1525a3b9 100644 --- a/src/test/java/com/esri/core/geometry/TestWKBSupport.java +++ b/src/test/java/com/esri/core/geometry/TestWKBSupport.java @@ -32,79 +32,79 @@ //import com.vividsolutions.jts.io.WKBReader; public class TestWKBSupport extends TestCase { - @Override - protected void setUp() throws Exception { - super.setUp(); - } - - @Override - protected void tearDown() throws Exception { - super.tearDown(); - } - - @Test - public void testWKB() { - // JSON -> GEOM -> WKB - - String strPolygon1 = "{\"xmin\":-1.1663479012889031E7,\"ymin\":4919777.494405342,\"xmax\":-1.1658587043078788E7,\"ymax\":4924669.464215587,\"spatialReference\":{\"wkid\":102100}}"; - // String strPolygon1 = - // "{\"rings\":[[[-119.152450421001,38.4118009590513],[-119.318825070203,38.5271086243914],[-119.575687062955,38.7029101298904],[-119.889341639399,38.9222515603984],[-119.995254694357,38.9941061536377],[-119.995150114198,39.0634913594691],[-119.994541258334,39.1061318056708],[-119.995527335641,39.1587132866355],[-119.995304181493,39.3115454332125],[-119.996011479298,39.4435009764511],[-119.996165311172,39.7206108077274],[-119.996324660047,41.1775662656441],[-119.993459369715,41.9892049531992],[-119.351692186077,41.9888529749781],[-119.3109421304,41.9891353872811],[-118.185316829038,41.9966370981387],[-117.018864363596,41.9947941808341],[-116.992313337997,41.9947945094663],[-115.947544658193,41.9945994628997],[-115.024862911148,41.996506455953],[-114.269471632824,41.9959242345073],[-114.039072662345,41.9953908974688],[-114.038151248682,40.9976868405942],[-114.038108189376,40.1110466529553],[-114.039844684228,39.9087788600023],[-114.040105338584,39.5386849268845],[-114.044267501155,38.6789958815881],[-114.045090206153,38.5710950539539],[-114.047272999176,38.1376524399918],[-114.047260595159,37.5984784866001],[-114.043939384154,36.9965379371421],[-114.043716435713,36.8418489458647],[-114.037392074194,36.2160228969702],[-114.045105557286,36.1939778840226],[-114.107775185788,36.1210907070504],[-114.12902308363,36.041730493896],[-114.206768869568,36.0172554164834],[-114.233472615347,36.0183310595897],[-114.307587598189,36.0622330993643],[-114.303857056018,36.0871084040611],[-114.316095374696,36.1114380366653],[-114.344233941709,36.1374802520568],[-114.380803116644,36.1509912717765],[-114.443945697733,36.1210532841897],[-114.466613475422,36.1247112590539],[-114.530573568745,36.1550902046725],[-114.598935242024,36.1383354528834],[-114.621610747198,36.1419666834504],[-114.712761724737,36.1051810523675],[-114.728150311069,36.0859627711604],[-114.728966012834,36.0587530361083],[-114.717673567756,36.0367580437018],[-114.736212493583,35.9876483502758],[-114.699275906446,35.9116119537412],[-114.661600122152,35.8804735854242],[-114.662462095522,35.8709599070091],[-114.689867343369,35.8474424944766],[-114.682739704595,35.7647034175617],[-114.688820027649,35.7325957399896],[-114.665091345861,35.6930994107107],[-114.668486064922,35.6563989882404],[-114.654065925137,35.6465840800053],[-114.6398667219,35.6113485698329],[-114.653134321223,35.5848331056108],[-114.649792053474,35.5466373866597],[-114.672215155693,35.5157541647721],[-114.645396168451,35.4507608261463],[-114.589584275424,35.3583787306827],[-114.587889840369,35.30476812919],[-114.559583045727,35.2201828714608],[-114.561039964054,35.1743461616313],[-114.572255261053,35.1400677445931],[-114.582616239058,35.1325604694085],[-114.626440825485,35.1339067529872],[-114.6359090842,35.1186557767895],[-114.595631971944,35.0760579746697],[-114.633779872695,35.0418633504303],[-114.621068606189,34.9989144286133],[-115.626197382816,35.7956983148418],[-115.88576934392,36.0012259572723],[-117.160423771838,36.9595941441767],[-117.838686423167,37.457298239715],[-118.417419755966,37.8866767486211],[-119.152450421001,38.4118009590513]]], \"spatialReference\":{\"wkid\":4326}}"; - - MapGeometry mapGeom = GeometryEngine.jsonToGeometry(strPolygon1); - Geometry geom = mapGeom.getGeometry(); - OperatorExportToWkb operatorExport = (OperatorExportToWkb) OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.ExportToWkb); - ByteBuffer byteBuffer = operatorExport.execute(0, geom, null); - byte[] wkb = byteBuffer.array(); - - // WKB -> GEOM -> JSON - OperatorImportFromWkb operatorImport = (OperatorImportFromWkb) OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.ImportFromWkb); - geom = operatorImport.execute(0, Geometry.Type.Polygon, - ByteBuffer.wrap(wkb), null); - // geom = operatorImport.execute(0, Geometry.Type.Polygon, - // byteBuffer); - String outputPolygon1 = GeometryEngine.geometryToJson(-1, geom); - } - - @Test - public void testWKB2() throws Exception { - // JSON -> GEOM -> WKB - - // String strPolygon1 = - // "{\"xmin\":-1.16605115291E7,\"ymin\":4925189.941699997,\"xmax\":-1.16567772126E7,\"ymax\":4928658.771399997,\"spatialReference\":{\"wkid\":102100}}"; - String strPolygon1 = "{\"rings\" : [ [ [-1.16605115291E7,4925189.941699997], [-1.16567772126E7,4925189.941699997], [-1.16567772126E7,4928658.771399997], [-1.16605115291E7,4928658.771399997], [-1.16605115291E7,4925189.941699997] ] ], \"spatialReference\" : {\"wkid\" : 102100}}"; - - MapGeometry mapGeom = GeometryEngine.jsonToGeometry(strPolygon1); - Geometry geom = mapGeom.getGeometry(); - - // simplifying geom - OperatorSimplify operatorSimplify = (OperatorSimplify) OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.Simplify); - SpatialReference sr = SpatialReference.create(102100); - geom = operatorSimplify.execute(geom, sr, true, null); - - OperatorExportToWkb operatorExport = (OperatorExportToWkb) OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.ExportToWkb); - ByteBuffer byteBuffer = operatorExport.execute(0, geom, null); - byte[] wkb = byteBuffer.array(); - - // // checking WKB correctness - // WKBReader jtsReader = new WKBReader(); - // com.vividsolutions.jts.geom.Geometry jtsGeom = jtsReader.read(wkb); - // System.out.println("jtsGeom = " + jtsGeom); - - // WKB -> GEOM -> JSON - OperatorImportFromWkb operatorImport = (OperatorImportFromWkb) OperatorFactoryLocal - .getInstance().getOperator(Operator.Type.ImportFromWkb); - geom = operatorImport.execute(0, Geometry.Type.Polygon, - ByteBuffer.wrap(wkb), null); - assertTrue(!geom.isEmpty()); - // geom = operatorImport.execute(0, Geometry.Type.Polygon, byteBuffer); - // String outputPolygon1 = GeometryEngine.geometryToJson(-1, geom); - // System.out.println(strPolygon1); - // System.out.println(outputPolygon1); - - } + @Override + protected void setUp() throws Exception { + super.setUp(); + } + + @Override + protected void tearDown() throws Exception { + super.tearDown(); + } + + @Test + public void testWKB() { + // JSON -> GEOM -> WKB + + String strPolygon1 = "{\"xmin\":-1.1663479012889031E7,\"ymin\":4919777.494405342,\"xmax\":-1.1658587043078788E7,\"ymax\":4924669.464215587,\"spatialReference\":{\"wkid\":102100}}"; + // String strPolygon1 = + // "{\"rings\":[[[-119.152450421001,38.4118009590513],[-119.318825070203,38.5271086243914],[-119.575687062955,38.7029101298904],[-119.889341639399,38.9222515603984],[-119.995254694357,38.9941061536377],[-119.995150114198,39.0634913594691],[-119.994541258334,39.1061318056708],[-119.995527335641,39.1587132866355],[-119.995304181493,39.3115454332125],[-119.996011479298,39.4435009764511],[-119.996165311172,39.7206108077274],[-119.996324660047,41.1775662656441],[-119.993459369715,41.9892049531992],[-119.351692186077,41.9888529749781],[-119.3109421304,41.9891353872811],[-118.185316829038,41.9966370981387],[-117.018864363596,41.9947941808341],[-116.992313337997,41.9947945094663],[-115.947544658193,41.9945994628997],[-115.024862911148,41.996506455953],[-114.269471632824,41.9959242345073],[-114.039072662345,41.9953908974688],[-114.038151248682,40.9976868405942],[-114.038108189376,40.1110466529553],[-114.039844684228,39.9087788600023],[-114.040105338584,39.5386849268845],[-114.044267501155,38.6789958815881],[-114.045090206153,38.5710950539539],[-114.047272999176,38.1376524399918],[-114.047260595159,37.5984784866001],[-114.043939384154,36.9965379371421],[-114.043716435713,36.8418489458647],[-114.037392074194,36.2160228969702],[-114.045105557286,36.1939778840226],[-114.107775185788,36.1210907070504],[-114.12902308363,36.041730493896],[-114.206768869568,36.0172554164834],[-114.233472615347,36.0183310595897],[-114.307587598189,36.0622330993643],[-114.303857056018,36.0871084040611],[-114.316095374696,36.1114380366653],[-114.344233941709,36.1374802520568],[-114.380803116644,36.1509912717765],[-114.443945697733,36.1210532841897],[-114.466613475422,36.1247112590539],[-114.530573568745,36.1550902046725],[-114.598935242024,36.1383354528834],[-114.621610747198,36.1419666834504],[-114.712761724737,36.1051810523675],[-114.728150311069,36.0859627711604],[-114.728966012834,36.0587530361083],[-114.717673567756,36.0367580437018],[-114.736212493583,35.9876483502758],[-114.699275906446,35.9116119537412],[-114.661600122152,35.8804735854242],[-114.662462095522,35.8709599070091],[-114.689867343369,35.8474424944766],[-114.682739704595,35.7647034175617],[-114.688820027649,35.7325957399896],[-114.665091345861,35.6930994107107],[-114.668486064922,35.6563989882404],[-114.654065925137,35.6465840800053],[-114.6398667219,35.6113485698329],[-114.653134321223,35.5848331056108],[-114.649792053474,35.5466373866597],[-114.672215155693,35.5157541647721],[-114.645396168451,35.4507608261463],[-114.589584275424,35.3583787306827],[-114.587889840369,35.30476812919],[-114.559583045727,35.2201828714608],[-114.561039964054,35.1743461616313],[-114.572255261053,35.1400677445931],[-114.582616239058,35.1325604694085],[-114.626440825485,35.1339067529872],[-114.6359090842,35.1186557767895],[-114.595631971944,35.0760579746697],[-114.633779872695,35.0418633504303],[-114.621068606189,34.9989144286133],[-115.626197382816,35.7956983148418],[-115.88576934392,36.0012259572723],[-117.160423771838,36.9595941441767],[-117.838686423167,37.457298239715],[-118.417419755966,37.8866767486211],[-119.152450421001,38.4118009590513]]], \"spatialReference\":{\"wkid\":4326}}"; + + MapGeometry mapGeom = GeometryEngine.jsonToGeometry(strPolygon1); + Geometry geom = mapGeom.getGeometry(); + OperatorExportToWkb operatorExport = (OperatorExportToWkb) OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.ExportToWkb); + ByteBuffer byteBuffer = operatorExport.execute(0, geom, null); + byte[] wkb = byteBuffer.array(); + + // WKB -> GEOM -> JSON + OperatorImportFromWkb operatorImport = (OperatorImportFromWkb) OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.ImportFromWkb); + geom = operatorImport.execute(0, Geometry.Type.Polygon, + ByteBuffer.wrap(wkb), null); + // geom = operatorImport.execute(0, Geometry.Type.Polygon, + // byteBuffer); + String outputPolygon1 = GeometryEngine.geometryToJson(-1, geom); + } + + @Test + public void testWKB2() throws Exception { + // JSON -> GEOM -> WKB + + // String strPolygon1 = + // "{\"xmin\":-1.16605115291E7,\"ymin\":4925189.941699997,\"xmax\":-1.16567772126E7,\"ymax\":4928658.771399997,\"spatialReference\":{\"wkid\":102100}}"; + String strPolygon1 = "{\"rings\" : [ [ [-1.16605115291E7,4925189.941699997], [-1.16567772126E7,4925189.941699997], [-1.16567772126E7,4928658.771399997], [-1.16605115291E7,4928658.771399997], [-1.16605115291E7,4925189.941699997] ] ], \"spatialReference\" : {\"wkid\" : 102100}}"; + + MapGeometry mapGeom = GeometryEngine.jsonToGeometry(strPolygon1); + Geometry geom = mapGeom.getGeometry(); + + // simplifying geom + OperatorSimplify operatorSimplify = (OperatorSimplify) OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.Simplify); + SpatialReference sr = SpatialReference.create(102100); + geom = operatorSimplify.execute(geom, sr, true, null); + + OperatorExportToWkb operatorExport = (OperatorExportToWkb) OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.ExportToWkb); + ByteBuffer byteBuffer = operatorExport.execute(0, geom, null); + byte[] wkb = byteBuffer.array(); + + // // checking WKB correctness + // WKBReader jtsReader = new WKBReader(); + // com.vividsolutions.jts.geom.Geometry jtsGeom = jtsReader.read(wkb); + // System.out.println("jtsGeom = " + jtsGeom); + + // WKB -> GEOM -> JSON + OperatorImportFromWkb operatorImport = (OperatorImportFromWkb) OperatorFactoryLocal + .getInstance().getOperator(Operator.Type.ImportFromWkb); + geom = operatorImport.execute(0, Geometry.Type.Polygon, + ByteBuffer.wrap(wkb), null); + assertTrue(!geom.isEmpty()); + // geom = operatorImport.execute(0, Geometry.Type.Polygon, byteBuffer); + // String outputPolygon1 = GeometryEngine.geometryToJson(-1, geom); + // System.out.println(strPolygon1); + // System.out.println(outputPolygon1); + + } } diff --git a/src/test/java/com/esri/core/geometry/TestWkbImportOnPostgresST.java b/src/test/java/com/esri/core/geometry/TestWkbImportOnPostgresST.java index 59691c18..56b59d56 100644 --- a/src/test/java/com/esri/core/geometry/TestWkbImportOnPostgresST.java +++ b/src/test/java/com/esri/core/geometry/TestWkbImportOnPostgresST.java @@ -34,40 +34,40 @@ import java.sql.ResultSet; public class TestWkbImportOnPostgresST extends TestCase { - @Override - protected void setUp() throws Exception { - super.setUp(); - } + @Override + protected void setUp() throws Exception { + super.setUp(); + } - @Override - protected void tearDown() throws Exception { - super.tearDown(); - } + @Override + protected void tearDown() throws Exception { + super.tearDown(); + } - @Test - public static void testWkbImportOnPostgresST() throws Exception { - try { - Connection con = DriverManager.getConnection( - "jdbc:postgresql://tb.esri.com:5432/new_gdb", "tb", "tb"); - OperatorFactoryLocal factory = OperatorFactoryLocal.getInstance(); - OperatorImportFromWkb operatorImport = (OperatorImportFromWkb) factory - .getOperator(Operator.Type.ImportFromWkb); - String stmt = "SELECT objectid,sde.st_asbinary(shape) FROM new_gdb.tb.interstates a WHERE objectid IN (2) AND (a.shape IS NULL OR sde.st_geometrytype(shape)::text IN ('ST_MULTILINESTRING','ST_LINESTRING')) LIMIT 1000"; - PreparedStatement ps = con.prepareStatement(stmt); - ResultSet rs = ps.executeQuery(); - while (rs.next()) { - byte[] rsWkbGeom = rs.getBytes(2); - @SuppressWarnings("unused") - Geometry geomBorg = null; - if (rsWkbGeom != null) { - geomBorg = operatorImport.execute(0, Geometry.Type.Unknown, - ByteBuffer.wrap(rsWkbGeom), null); - } - } + @Test + public static void testWkbImportOnPostgresST() throws Exception { + try { + Connection con = DriverManager.getConnection( + "jdbc:postgresql://tb.esri.com:5432/new_gdb", "tb", "tb"); + OperatorFactoryLocal factory = OperatorFactoryLocal.getInstance(); + OperatorImportFromWkb operatorImport = (OperatorImportFromWkb) factory + .getOperator(Operator.Type.ImportFromWkb); + String stmt = "SELECT objectid,sde.st_asbinary(shape) FROM new_gdb.tb.interstates a WHERE objectid IN (2) AND (a.shape IS NULL OR sde.st_geometrytype(shape)::text IN ('ST_MULTILINESTRING','ST_LINESTRING')) LIMIT 1000"; + PreparedStatement ps = con.prepareStatement(stmt); + ResultSet rs = ps.executeQuery(); + while (rs.next()) { + byte[] rsWkbGeom = rs.getBytes(2); + @SuppressWarnings("unused") + Geometry geomBorg = null; + if (rsWkbGeom != null) { + geomBorg = operatorImport.execute(0, Geometry.Type.Unknown, + ByteBuffer.wrap(rsWkbGeom), null); + } + } - ps.close(); - con.close(); - } catch (Exception e) { - } - } + ps.close(); + con.close(); + } catch (Exception e) { + } + } } diff --git a/src/test/java/com/esri/core/geometry/TestWkid.java b/src/test/java/com/esri/core/geometry/TestWkid.java index a50dfca5..08154143 100644 --- a/src/test/java/com/esri/core/geometry/TestWkid.java +++ b/src/test/java/com/esri/core/geometry/TestWkid.java @@ -28,27 +28,27 @@ import org.junit.Test; public class TestWkid extends TestCase { - @Test - public void test() { - SpatialReference sr = SpatialReference.create(102100); - assertTrue(sr.getID() == 102100); - assertTrue(sr.getLatestID() == 3857); - assertTrue(sr.getOldID() == 102100); - assertTrue(sr.getTolerance() == 0.001); - - SpatialReference sr84 = SpatialReference.create(4326); - double tol84 = sr84.getTolerance(); - assertTrue(Math.abs(tol84 - 8.983152841195213E-9) < 8.983152841195213E-9 * 8.983152841195213E-9); - } - - - @Test - public void test_80() { - SpatialReference sr = SpatialReference.create(3857); - assertTrue(sr.getID() == 3857); - assertTrue(sr.getLatestID() == 3857); - assertTrue(sr.getOldID() == 102100); - assertTrue(sr.getTolerance() == 0.001); - } + @Test + public void test() { + SpatialReference sr = SpatialReference.create(102100); + assertTrue(sr.getID() == 102100); + assertTrue(sr.getLatestID() == 3857); + assertTrue(sr.getOldID() == 102100); + assertTrue(sr.getTolerance() == 0.001); + + SpatialReference sr84 = SpatialReference.create(4326); + double tol84 = sr84.getTolerance(); + assertTrue(Math.abs(tol84 - 8.983152841195213E-9) < 8.983152841195213E-9 * 8.983152841195213E-9); + } + + + @Test + public void test_80() { + SpatialReference sr = SpatialReference.create(3857); + assertTrue(sr.getID() == 3857); + assertTrue(sr.getLatestID() == 3857); + assertTrue(sr.getOldID() == 102100); + assertTrue(sr.getTolerance() == 0.001); + } } diff --git a/src/test/java/com/esri/core/geometry/TestWktParser.java b/src/test/java/com/esri/core/geometry/TestWktParser.java index cde7b98d..a534333c 100644 --- a/src/test/java/com/esri/core/geometry/TestWktParser.java +++ b/src/test/java/com/esri/core/geometry/TestWktParser.java @@ -29,1088 +29,1088 @@ public class TestWktParser extends TestCase { - @Test - public void testGeometryCollection() { - String s = " geometrycollection emPty "; - WktParser wktParser = new WktParser(); - wktParser.resetParser(s); + @Test + public void testGeometryCollection() { + String s = " geometrycollection emPty "; + WktParser wktParser = new WktParser(); + wktParser.resetParser(s); - int currentToken; - double value; + int currentToken; + double value; - currentToken = wktParser.nextToken(); - assertTrue(currentToken == WktParser.WktToken.geometrycollection); + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.geometrycollection); - currentToken = wktParser.nextToken(); - assertTrue(currentToken == WktParser.WktToken.empty); + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.empty); - currentToken = wktParser.nextToken(); - assertTrue(currentToken == WktParser.WktToken.not_available); + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.not_available); - s = " geometrycollection zm ( geometrycollection zm ( POINT ZM ( 5. +1.e+0004 13 17) ), LineString zm emPty, MULTIpolyGON zM (((1 1 1 1))) ) "; - wktParser.resetParser(s); + s = " geometrycollection zm ( geometrycollection zm ( POINT ZM ( 5. +1.e+0004 13 17) ), LineString zm emPty, MULTIpolyGON zM (((1 1 1 1))) ) "; + wktParser.resetParser(s); - currentToken = wktParser.nextToken(); - assertTrue(currentToken == WktParser.WktToken.geometrycollection); + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.geometrycollection); - currentToken = wktParser.nextToken(); - assertTrue(currentToken == WktParser.WktToken.attribute_zm); + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.attribute_zm); - currentToken = wktParser.nextToken(); - assertTrue(currentToken == WktParser.WktToken.left_paren); + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.left_paren); - currentToken = wktParser.nextToken(); - assertTrue(currentToken == WktParser.WktToken.geometrycollection); + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.geometrycollection); - currentToken = wktParser.nextToken(); - assertTrue(currentToken == WktParser.WktToken.attribute_zm); + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.attribute_zm); - currentToken = wktParser.nextToken(); - assertTrue(currentToken == WktParser.WktToken.left_paren); + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.left_paren); - currentToken = wktParser.nextToken(); - assertTrue(currentToken == WktParser.WktToken.point); + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.point); - currentToken = wktParser.nextToken(); - assertTrue(currentToken == WktParser.WktToken.attribute_zm); + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.attribute_zm); - currentToken = wktParser.nextToken(); - assertTrue(currentToken == WktParser.WktToken.left_paren); + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.left_paren); - currentToken = wktParser.nextToken(); - assertTrue(currentToken == WktParser.WktToken.x_literal); + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.x_literal); - value = wktParser.currentNumericLiteral(); - assertTrue(value == 5.0); + value = wktParser.currentNumericLiteral(); + assertTrue(value == 5.0); - currentToken = wktParser.nextToken(); - assertTrue(currentToken == WktParser.WktToken.y_literal); + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.y_literal); - value = wktParser.currentNumericLiteral(); - assertTrue(value == 1.0e4); + value = wktParser.currentNumericLiteral(); + assertTrue(value == 1.0e4); - currentToken = wktParser.nextToken(); - assertTrue(currentToken == WktParser.WktToken.z_literal); + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.z_literal); - value = wktParser.currentNumericLiteral(); - assertTrue(value == 13); + value = wktParser.currentNumericLiteral(); + assertTrue(value == 13); - currentToken = wktParser.nextToken(); - assertTrue(currentToken == WktParser.WktToken.m_literal); + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.m_literal); - value = wktParser.currentNumericLiteral(); - assertTrue(value == 17); + value = wktParser.currentNumericLiteral(); + assertTrue(value == 17); - currentToken = wktParser.nextToken(); - assertTrue(currentToken == WktParser.WktToken.right_paren); + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.right_paren); - currentToken = wktParser.nextToken(); - assertTrue(currentToken == WktParser.WktToken.right_paren); + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.right_paren); - currentToken = wktParser.nextToken(); - assertTrue(currentToken == WktParser.WktToken.linestring); + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.linestring); - currentToken = wktParser.nextToken(); - assertTrue(currentToken == WktParser.WktToken.attribute_zm); + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.attribute_zm); - currentToken = wktParser.nextToken(); - assertTrue(currentToken == WktParser.WktToken.empty); + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.empty); - currentToken = wktParser.nextToken(); - assertTrue(currentToken == WktParser.WktToken.multipolygon); + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.multipolygon); - currentToken = wktParser.nextToken(); - assertTrue(currentToken == WktParser.WktToken.attribute_zm); + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.attribute_zm); - currentToken = wktParser.nextToken(); - assertTrue(currentToken == WktParser.WktToken.left_paren); + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.left_paren); - currentToken = wktParser.nextToken(); - assertTrue(currentToken == WktParser.WktToken.left_paren); + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.left_paren); - currentToken = wktParser.nextToken(); - assertTrue(currentToken == WktParser.WktToken.left_paren); + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.left_paren); - currentToken = wktParser.nextToken(); - assertTrue(currentToken == WktParser.WktToken.x_literal); + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.x_literal); - currentToken = wktParser.nextToken(); - assertTrue(currentToken == WktParser.WktToken.y_literal); + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.y_literal); - currentToken = wktParser.nextToken(); - assertTrue(currentToken == WktParser.WktToken.z_literal); + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.z_literal); - currentToken = wktParser.nextToken(); - assertTrue(currentToken == WktParser.WktToken.m_literal); + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.m_literal); - currentToken = wktParser.nextToken(); - assertTrue(currentToken == WktParser.WktToken.right_paren); + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.right_paren); - currentToken = wktParser.nextToken(); - assertTrue(currentToken == WktParser.WktToken.right_paren); + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.right_paren); - currentToken = wktParser.nextToken(); - assertTrue(currentToken == WktParser.WktToken.right_paren); + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.right_paren); - currentToken = wktParser.nextToken(); - assertTrue(currentToken == WktParser.WktToken.right_paren); + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.right_paren); - currentToken = wktParser.nextToken(); - assertTrue(currentToken == WktParser.WktToken.not_available); - } + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.not_available); + } - @Test - public void testMultiPolygon() { - String s = " MultIPolYgOn emPty "; - WktParser wktParser = new WktParser(); - wktParser.resetParser(s); + @Test + public void testMultiPolygon() { + String s = " MultIPolYgOn emPty "; + WktParser wktParser = new WktParser(); + wktParser.resetParser(s); - int currentToken; - double value; + int currentToken; + double value; - currentToken = wktParser.nextToken(); - assertTrue(currentToken == WktParser.WktToken.multipolygon); + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.multipolygon); - currentToken = wktParser.nextToken(); - assertTrue(currentToken == WktParser.WktToken.empty); + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.empty); - s = " MULTIpolyGON zM ( empty , ( empty, ( 5. +1.e+0004 13 17, -.1e07 .006 13 17 ) , empty , (4 003. 13 17, 02E-3 .3e2 13 17) ) , empty, ( ( 5. +1.e+0004 13 17, -.1e07 .006 13 17) , (4 003. 13 17 , 02E-3 .3e2 13 17) ) ) "; - wktParser.resetParser(s); + s = " MULTIpolyGON zM ( empty , ( empty, ( 5. +1.e+0004 13 17, -.1e07 .006 13 17 ) , empty , (4 003. 13 17, 02E-3 .3e2 13 17) ) , empty, ( ( 5. +1.e+0004 13 17, -.1e07 .006 13 17) , (4 003. 13 17 , 02E-3 .3e2 13 17) ) ) "; + wktParser.resetParser(s); - currentToken = wktParser.nextToken(); - assertTrue(currentToken == WktParser.WktToken.multipolygon); + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.multipolygon); - currentToken = wktParser.nextToken(); - assertTrue(currentToken == WktParser.WktToken.attribute_zm); + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.attribute_zm); - // Start first polygon - currentToken = wktParser.nextToken(); - assertTrue(currentToken == WktParser.WktToken.left_paren); + // Start first polygon + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.left_paren); - currentToken = wktParser.nextToken(); - assertTrue(currentToken == WktParser.WktToken.empty); + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.empty); - currentToken = wktParser.nextToken(); - assertTrue(currentToken == WktParser.WktToken.left_paren); + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.left_paren); - currentToken = wktParser.nextToken(); - assertTrue(currentToken == WktParser.WktToken.empty); + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.empty); - currentToken = wktParser.nextToken(); - assertTrue(currentToken == WktParser.WktToken.left_paren); + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.left_paren); - currentToken = wktParser.nextToken(); - assertTrue(currentToken == WktParser.WktToken.x_literal); + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.x_literal); - value = wktParser.currentNumericLiteral(); - assertTrue(value == 5.0); + value = wktParser.currentNumericLiteral(); + assertTrue(value == 5.0); - currentToken = wktParser.nextToken(); - assertTrue(currentToken == WktParser.WktToken.y_literal); + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.y_literal); - value = wktParser.currentNumericLiteral(); - assertTrue(value == 1.0e4); + value = wktParser.currentNumericLiteral(); + assertTrue(value == 1.0e4); - currentToken = wktParser.nextToken(); - assertTrue(currentToken == WktParser.WktToken.z_literal); + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.z_literal); - value = wktParser.currentNumericLiteral(); - assertTrue(value == 13); + value = wktParser.currentNumericLiteral(); + assertTrue(value == 13); - currentToken = wktParser.nextToken(); - assertTrue(currentToken == WktParser.WktToken.m_literal); + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.m_literal); - value = wktParser.currentNumericLiteral(); - assertTrue(value == 17); + value = wktParser.currentNumericLiteral(); + assertTrue(value == 17); - currentToken = wktParser.nextToken(); - assertTrue(currentToken == WktParser.WktToken.x_literal); + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.x_literal); - value = wktParser.currentNumericLiteral(); - assertTrue(value == -0.1e7); + value = wktParser.currentNumericLiteral(); + assertTrue(value == -0.1e7); - currentToken = wktParser.nextToken(); - assertTrue(currentToken == WktParser.WktToken.y_literal); + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.y_literal); - value = wktParser.currentNumericLiteral(); - assertTrue(value == 0.006); + value = wktParser.currentNumericLiteral(); + assertTrue(value == 0.006); - currentToken = wktParser.nextToken(); - assertTrue(currentToken == WktParser.WktToken.z_literal); + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.z_literal); - value = wktParser.currentNumericLiteral(); - assertTrue(value == 13); + value = wktParser.currentNumericLiteral(); + assertTrue(value == 13); - currentToken = wktParser.nextToken(); - assertTrue(currentToken == WktParser.WktToken.m_literal); + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.m_literal); - value = wktParser.currentNumericLiteral(); - assertTrue(value == 17); + value = wktParser.currentNumericLiteral(); + assertTrue(value == 17); - currentToken = wktParser.nextToken(); - assertTrue(currentToken == WktParser.WktToken.right_paren); + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.right_paren); - currentToken = wktParser.nextToken(); - assertTrue(currentToken == WktParser.WktToken.empty); + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.empty); - currentToken = wktParser.nextToken(); - assertTrue(currentToken == WktParser.WktToken.left_paren); + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.left_paren); - currentToken = wktParser.nextToken(); - assertTrue(currentToken == WktParser.WktToken.x_literal); + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.x_literal); - value = wktParser.currentNumericLiteral(); - assertTrue(value == 4.0); + value = wktParser.currentNumericLiteral(); + assertTrue(value == 4.0); - currentToken = wktParser.nextToken(); - assertTrue(currentToken == WktParser.WktToken.y_literal); + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.y_literal); - value = wktParser.currentNumericLiteral(); - assertTrue(value == 3.0); + value = wktParser.currentNumericLiteral(); + assertTrue(value == 3.0); - currentToken = wktParser.nextToken(); - assertTrue(currentToken == WktParser.WktToken.z_literal); + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.z_literal); - value = wktParser.currentNumericLiteral(); - assertTrue(value == 13); + value = wktParser.currentNumericLiteral(); + assertTrue(value == 13); - currentToken = wktParser.nextToken(); - assertTrue(currentToken == WktParser.WktToken.m_literal); + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.m_literal); - value = wktParser.currentNumericLiteral(); - assertTrue(value == 17); + value = wktParser.currentNumericLiteral(); + assertTrue(value == 17); - currentToken = wktParser.nextToken(); - assertTrue(currentToken == WktParser.WktToken.x_literal); + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.x_literal); - value = wktParser.currentNumericLiteral(); - assertTrue(value == 2.0e-3); + value = wktParser.currentNumericLiteral(); + assertTrue(value == 2.0e-3); - currentToken = wktParser.nextToken(); - assertTrue(currentToken == WktParser.WktToken.y_literal); + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.y_literal); - value = wktParser.currentNumericLiteral(); - assertTrue(value == 0.3e2); + value = wktParser.currentNumericLiteral(); + assertTrue(value == 0.3e2); - currentToken = wktParser.nextToken(); - assertTrue(currentToken == WktParser.WktToken.z_literal); + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.z_literal); - value = wktParser.currentNumericLiteral(); - assertTrue(value == 13); + value = wktParser.currentNumericLiteral(); + assertTrue(value == 13); - currentToken = wktParser.nextToken(); - assertTrue(currentToken == WktParser.WktToken.m_literal); + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.m_literal); - value = wktParser.currentNumericLiteral(); - assertTrue(value == 17); + value = wktParser.currentNumericLiteral(); + assertTrue(value == 17); - currentToken = wktParser.nextToken(); - assertTrue(currentToken == WktParser.WktToken.right_paren); + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.right_paren); - currentToken = wktParser.nextToken(); - assertTrue(currentToken == WktParser.WktToken.right_paren); + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.right_paren); - currentToken = wktParser.nextToken(); - assertTrue(currentToken == WktParser.WktToken.empty); + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.empty); - // End of First polygon + // End of First polygon - // Start Second Polygon - currentToken = wktParser.nextToken(); - assertTrue(currentToken == WktParser.WktToken.left_paren); + // Start Second Polygon + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.left_paren); - currentToken = wktParser.nextToken(); - assertTrue(currentToken == WktParser.WktToken.left_paren); + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.left_paren); - currentToken = wktParser.nextToken(); - assertTrue(currentToken == WktParser.WktToken.x_literal); + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.x_literal); - value = wktParser.currentNumericLiteral(); - assertTrue(value == 5.0); + value = wktParser.currentNumericLiteral(); + assertTrue(value == 5.0); - currentToken = wktParser.nextToken(); - assertTrue(currentToken == WktParser.WktToken.y_literal); + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.y_literal); - value = wktParser.currentNumericLiteral(); - assertTrue(value == 1.0e4); + value = wktParser.currentNumericLiteral(); + assertTrue(value == 1.0e4); - currentToken = wktParser.nextToken(); - assertTrue(currentToken == WktParser.WktToken.z_literal); + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.z_literal); - value = wktParser.currentNumericLiteral(); - assertTrue(value == 13); + value = wktParser.currentNumericLiteral(); + assertTrue(value == 13); - currentToken = wktParser.nextToken(); - assertTrue(currentToken == WktParser.WktToken.m_literal); + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.m_literal); - value = wktParser.currentNumericLiteral(); - assertTrue(value == 17); + value = wktParser.currentNumericLiteral(); + assertTrue(value == 17); - currentToken = wktParser.nextToken(); - assertTrue(currentToken == WktParser.WktToken.x_literal); + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.x_literal); - value = wktParser.currentNumericLiteral(); - assertTrue(value == -0.1e7); + value = wktParser.currentNumericLiteral(); + assertTrue(value == -0.1e7); - currentToken = wktParser.nextToken(); - assertTrue(currentToken == WktParser.WktToken.y_literal); + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.y_literal); - value = wktParser.currentNumericLiteral(); - assertTrue(value == 0.006); + value = wktParser.currentNumericLiteral(); + assertTrue(value == 0.006); - currentToken = wktParser.nextToken(); - assertTrue(currentToken == WktParser.WktToken.z_literal); + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.z_literal); - value = wktParser.currentNumericLiteral(); - assertTrue(value == 13); + value = wktParser.currentNumericLiteral(); + assertTrue(value == 13); - currentToken = wktParser.nextToken(); - assertTrue(currentToken == WktParser.WktToken.m_literal); + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.m_literal); - value = wktParser.currentNumericLiteral(); - assertTrue(value == 17); + value = wktParser.currentNumericLiteral(); + assertTrue(value == 17); - currentToken = wktParser.nextToken(); - assertTrue(currentToken == WktParser.WktToken.right_paren); + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.right_paren); - currentToken = wktParser.nextToken(); - assertTrue(currentToken == WktParser.WktToken.left_paren); + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.left_paren); - currentToken = wktParser.nextToken(); - assertTrue(currentToken == WktParser.WktToken.x_literal); + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.x_literal); - value = wktParser.currentNumericLiteral(); - assertTrue(value == 4.0); + value = wktParser.currentNumericLiteral(); + assertTrue(value == 4.0); - currentToken = wktParser.nextToken(); - assertTrue(currentToken == WktParser.WktToken.y_literal); + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.y_literal); - value = wktParser.currentNumericLiteral(); - assertTrue(value == 3.0); + value = wktParser.currentNumericLiteral(); + assertTrue(value == 3.0); - currentToken = wktParser.nextToken(); - assertTrue(currentToken == WktParser.WktToken.z_literal); + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.z_literal); - value = wktParser.currentNumericLiteral(); - assertTrue(value == 13); + value = wktParser.currentNumericLiteral(); + assertTrue(value == 13); - currentToken = wktParser.nextToken(); - assertTrue(currentToken == WktParser.WktToken.m_literal); + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.m_literal); - value = wktParser.currentNumericLiteral(); - assertTrue(value == 17); + value = wktParser.currentNumericLiteral(); + assertTrue(value == 17); - currentToken = wktParser.nextToken(); - assertTrue(currentToken == WktParser.WktToken.x_literal); + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.x_literal); - value = wktParser.currentNumericLiteral(); - assertTrue(value == 2.0e-3); + value = wktParser.currentNumericLiteral(); + assertTrue(value == 2.0e-3); - currentToken = wktParser.nextToken(); - assertTrue(currentToken == WktParser.WktToken.y_literal); + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.y_literal); - value = wktParser.currentNumericLiteral(); - assertTrue(value == 0.3e2); + value = wktParser.currentNumericLiteral(); + assertTrue(value == 0.3e2); - currentToken = wktParser.nextToken(); - assertTrue(currentToken == WktParser.WktToken.z_literal); + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.z_literal); - value = wktParser.currentNumericLiteral(); - assertTrue(value == 13); + value = wktParser.currentNumericLiteral(); + assertTrue(value == 13); - currentToken = wktParser.nextToken(); - assertTrue(currentToken == WktParser.WktToken.m_literal); + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.m_literal); - value = wktParser.currentNumericLiteral(); - assertTrue(value == 17); + value = wktParser.currentNumericLiteral(); + assertTrue(value == 17); - currentToken = wktParser.nextToken(); - assertTrue(currentToken == WktParser.WktToken.right_paren); + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.right_paren); - currentToken = wktParser.nextToken(); - assertTrue(currentToken == WktParser.WktToken.right_paren); + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.right_paren); - currentToken = wktParser.nextToken(); - assertTrue(currentToken == WktParser.WktToken.right_paren); + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.right_paren); - currentToken = wktParser.nextToken(); - assertTrue(currentToken == WktParser.WktToken.not_available); - } + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.not_available); + } - @Test - public void testMultiLineString() { - String s = " MultiLineString emPty "; - WktParser wktParser = new WktParser(); - wktParser.resetParser(s); + @Test + public void testMultiLineString() { + String s = " MultiLineString emPty "; + WktParser wktParser = new WktParser(); + wktParser.resetParser(s); - int currentToken; - double value; + int currentToken; + double value; - currentToken = wktParser.nextToken(); - assertTrue(currentToken == WktParser.WktToken.multilinestring); + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.multilinestring); - currentToken = wktParser.nextToken(); - assertTrue(currentToken == WktParser.WktToken.empty); + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.empty); - s = " MultiLineString Z ( empty, ( 5. +1.e+0004 13, -.1e07 .006 13 ) , empty , (4 003. 13 , 02E-3 .3e2 13 ) , empty, ( 5. +1.e+0004 13 , -.1e07 .006 13) , (4 003. 13 , 02E-3 .3e2 13 ) ) "; - wktParser.resetParser(s); + s = " MultiLineString Z ( empty, ( 5. +1.e+0004 13, -.1e07 .006 13 ) , empty , (4 003. 13 , 02E-3 .3e2 13 ) , empty, ( 5. +1.e+0004 13 , -.1e07 .006 13) , (4 003. 13 , 02E-3 .3e2 13 ) ) "; + wktParser.resetParser(s); - currentToken = wktParser.nextToken(); - assertTrue(currentToken == WktParser.WktToken.multilinestring); + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.multilinestring); - currentToken = wktParser.nextToken(); - assertTrue(currentToken == WktParser.WktToken.attribute_z); + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.attribute_z); - currentToken = wktParser.nextToken(); - assertTrue(currentToken == WktParser.WktToken.left_paren); + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.left_paren); - currentToken = wktParser.nextToken(); - assertTrue(currentToken == WktParser.WktToken.empty); + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.empty); - currentToken = wktParser.nextToken(); - assertTrue(currentToken == WktParser.WktToken.left_paren); + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.left_paren); - currentToken = wktParser.nextToken(); - assertTrue(currentToken == WktParser.WktToken.x_literal); + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.x_literal); - value = wktParser.currentNumericLiteral(); - assertTrue(value == 5.0); + value = wktParser.currentNumericLiteral(); + assertTrue(value == 5.0); - currentToken = wktParser.nextToken(); - assertTrue(currentToken == WktParser.WktToken.y_literal); + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.y_literal); - value = wktParser.currentNumericLiteral(); - assertTrue(value == 1.0e4); + value = wktParser.currentNumericLiteral(); + assertTrue(value == 1.0e4); - currentToken = wktParser.nextToken(); - assertTrue(currentToken == WktParser.WktToken.z_literal); + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.z_literal); - value = wktParser.currentNumericLiteral(); - assertTrue(value == 13); + value = wktParser.currentNumericLiteral(); + assertTrue(value == 13); - currentToken = wktParser.nextToken(); - assertTrue(currentToken == WktParser.WktToken.x_literal); + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.x_literal); - value = wktParser.currentNumericLiteral(); - assertTrue(value == -0.1e7); + value = wktParser.currentNumericLiteral(); + assertTrue(value == -0.1e7); - currentToken = wktParser.nextToken(); - assertTrue(currentToken == WktParser.WktToken.y_literal); + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.y_literal); - value = wktParser.currentNumericLiteral(); - assertTrue(value == 0.006); + value = wktParser.currentNumericLiteral(); + assertTrue(value == 0.006); - currentToken = wktParser.nextToken(); - assertTrue(currentToken == WktParser.WktToken.z_literal); + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.z_literal); - value = wktParser.currentNumericLiteral(); - assertTrue(value == 13); + value = wktParser.currentNumericLiteral(); + assertTrue(value == 13); - currentToken = wktParser.nextToken(); - assertTrue(currentToken == WktParser.WktToken.right_paren); + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.right_paren); - currentToken = wktParser.nextToken(); - assertTrue(currentToken == WktParser.WktToken.empty); + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.empty); - currentToken = wktParser.nextToken(); - assertTrue(currentToken == WktParser.WktToken.left_paren); + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.left_paren); - currentToken = wktParser.nextToken(); - assertTrue(currentToken == WktParser.WktToken.x_literal); + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.x_literal); - value = wktParser.currentNumericLiteral(); - assertTrue(value == 4.0); + value = wktParser.currentNumericLiteral(); + assertTrue(value == 4.0); - currentToken = wktParser.nextToken(); - assertTrue(currentToken == WktParser.WktToken.y_literal); + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.y_literal); - value = wktParser.currentNumericLiteral(); - assertTrue(value == 3.0); + value = wktParser.currentNumericLiteral(); + assertTrue(value == 3.0); - currentToken = wktParser.nextToken(); - assertTrue(currentToken == WktParser.WktToken.z_literal); + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.z_literal); - value = wktParser.currentNumericLiteral(); - assertTrue(value == 13); + value = wktParser.currentNumericLiteral(); + assertTrue(value == 13); - currentToken = wktParser.nextToken(); - assertTrue(currentToken == WktParser.WktToken.x_literal); + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.x_literal); - value = wktParser.currentNumericLiteral(); - assertTrue(value == 2.0e-3); + value = wktParser.currentNumericLiteral(); + assertTrue(value == 2.0e-3); - currentToken = wktParser.nextToken(); - assertTrue(currentToken == WktParser.WktToken.y_literal); + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.y_literal); - value = wktParser.currentNumericLiteral(); - assertTrue(value == 0.3e2); + value = wktParser.currentNumericLiteral(); + assertTrue(value == 0.3e2); - currentToken = wktParser.nextToken(); - assertTrue(currentToken == WktParser.WktToken.z_literal); + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.z_literal); - value = wktParser.currentNumericLiteral(); - assertTrue(value == 13); + value = wktParser.currentNumericLiteral(); + assertTrue(value == 13); - currentToken = wktParser.nextToken(); - assertTrue(currentToken == WktParser.WktToken.right_paren); + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.right_paren); - currentToken = wktParser.nextToken(); - assertTrue(currentToken == WktParser.WktToken.empty); + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.empty); - currentToken = wktParser.nextToken(); - assertTrue(currentToken == WktParser.WktToken.left_paren); + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.left_paren); - currentToken = wktParser.nextToken(); - assertTrue(currentToken == WktParser.WktToken.x_literal); + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.x_literal); - value = wktParser.currentNumericLiteral(); - assertTrue(value == 5.0); + value = wktParser.currentNumericLiteral(); + assertTrue(value == 5.0); - currentToken = wktParser.nextToken(); - assertTrue(currentToken == WktParser.WktToken.y_literal); + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.y_literal); - value = wktParser.currentNumericLiteral(); - assertTrue(value == 1.0e4); + value = wktParser.currentNumericLiteral(); + assertTrue(value == 1.0e4); - currentToken = wktParser.nextToken(); - assertTrue(currentToken == WktParser.WktToken.z_literal); + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.z_literal); - value = wktParser.currentNumericLiteral(); - assertTrue(value == 13); + value = wktParser.currentNumericLiteral(); + assertTrue(value == 13); - currentToken = wktParser.nextToken(); - assertTrue(currentToken == WktParser.WktToken.x_literal); + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.x_literal); - value = wktParser.currentNumericLiteral(); - assertTrue(value == -0.1e7); + value = wktParser.currentNumericLiteral(); + assertTrue(value == -0.1e7); - currentToken = wktParser.nextToken(); - assertTrue(currentToken == WktParser.WktToken.y_literal); + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.y_literal); - value = wktParser.currentNumericLiteral(); - assertTrue(value == 0.006); + value = wktParser.currentNumericLiteral(); + assertTrue(value == 0.006); - currentToken = wktParser.nextToken(); - assertTrue(currentToken == WktParser.WktToken.z_literal); + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.z_literal); - value = wktParser.currentNumericLiteral(); - assertTrue(value == 13); + value = wktParser.currentNumericLiteral(); + assertTrue(value == 13); - currentToken = wktParser.nextToken(); - assertTrue(currentToken == WktParser.WktToken.right_paren); + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.right_paren); - currentToken = wktParser.nextToken(); - assertTrue(currentToken == WktParser.WktToken.left_paren); + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.left_paren); - currentToken = wktParser.nextToken(); - assertTrue(currentToken == WktParser.WktToken.x_literal); + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.x_literal); - value = wktParser.currentNumericLiteral(); - assertTrue(value == 4.0); + value = wktParser.currentNumericLiteral(); + assertTrue(value == 4.0); - currentToken = wktParser.nextToken(); - assertTrue(currentToken == WktParser.WktToken.y_literal); + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.y_literal); - value = wktParser.currentNumericLiteral(); - assertTrue(value == 3.0); + value = wktParser.currentNumericLiteral(); + assertTrue(value == 3.0); - currentToken = wktParser.nextToken(); - assertTrue(currentToken == WktParser.WktToken.z_literal); + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.z_literal); - value = wktParser.currentNumericLiteral(); - assertTrue(value == 13); + value = wktParser.currentNumericLiteral(); + assertTrue(value == 13); - currentToken = wktParser.nextToken(); - assertTrue(currentToken == WktParser.WktToken.x_literal); + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.x_literal); - value = wktParser.currentNumericLiteral(); - assertTrue(value == 2.0e-3); + value = wktParser.currentNumericLiteral(); + assertTrue(value == 2.0e-3); - currentToken = wktParser.nextToken(); - assertTrue(currentToken == WktParser.WktToken.y_literal); + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.y_literal); - value = wktParser.currentNumericLiteral(); - assertTrue(value == 0.3e2); + value = wktParser.currentNumericLiteral(); + assertTrue(value == 0.3e2); - currentToken = wktParser.nextToken(); - assertTrue(currentToken == WktParser.WktToken.z_literal); + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.z_literal); - value = wktParser.currentNumericLiteral(); - assertTrue(value == 13); + value = wktParser.currentNumericLiteral(); + assertTrue(value == 13); - currentToken = wktParser.nextToken(); - assertTrue(currentToken == WktParser.WktToken.right_paren); + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.right_paren); - currentToken = wktParser.nextToken(); - assertTrue(currentToken == WktParser.WktToken.right_paren); + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.right_paren); - currentToken = wktParser.nextToken(); - assertTrue(currentToken == WktParser.WktToken.not_available); - } + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.not_available); + } - @Test - public void testMultiPoint() { - String s = " MultipoInt emPty "; - WktParser wktParser = new WktParser(); - wktParser.resetParser(s); + @Test + public void testMultiPoint() { + String s = " MultipoInt emPty "; + WktParser wktParser = new WktParser(); + wktParser.resetParser(s); - int currentToken; - double value; + int currentToken; + double value; - currentToken = wktParser.nextToken(); - assertTrue(currentToken == WktParser.WktToken.multipoint); + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.multipoint); - currentToken = wktParser.nextToken(); - assertTrue(currentToken == WktParser.WktToken.empty); + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.empty); - s = " Multipoint Z ( empty, ( 5. +1.e+0004 13 ), (-.1e07 .006 13 ) , empty , (4 003. 13 ), (02E-3 .3e2 13 ) ) "; - wktParser.resetParser(s); + s = " Multipoint Z ( empty, ( 5. +1.e+0004 13 ), (-.1e07 .006 13 ) , empty , (4 003. 13 ), (02E-3 .3e2 13 ) ) "; + wktParser.resetParser(s); - currentToken = wktParser.nextToken(); // bug here - assertTrue(currentToken == WktParser.WktToken.multipoint); + currentToken = wktParser.nextToken(); // bug here + assertTrue(currentToken == WktParser.WktToken.multipoint); - currentToken = wktParser.nextToken(); - assertTrue(currentToken == WktParser.WktToken.attribute_z); + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.attribute_z); - currentToken = wktParser.nextToken(); - assertTrue(currentToken == WktParser.WktToken.left_paren); + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.left_paren); - currentToken = wktParser.nextToken(); - assertTrue(currentToken == WktParser.WktToken.empty); + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.empty); - currentToken = wktParser.nextToken(); - assertTrue(currentToken == WktParser.WktToken.left_paren); + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.left_paren); - currentToken = wktParser.nextToken(); - assertTrue(currentToken == WktParser.WktToken.x_literal); + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.x_literal); - value = wktParser.currentNumericLiteral(); - assertTrue(value == 5.0); + value = wktParser.currentNumericLiteral(); + assertTrue(value == 5.0); - currentToken = wktParser.nextToken(); - assertTrue(currentToken == WktParser.WktToken.y_literal); + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.y_literal); - value = wktParser.currentNumericLiteral(); - assertTrue(value == 1.0e4); + value = wktParser.currentNumericLiteral(); + assertTrue(value == 1.0e4); - currentToken = wktParser.nextToken(); - assertTrue(currentToken == WktParser.WktToken.z_literal); + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.z_literal); - value = wktParser.currentNumericLiteral(); - assertTrue(value == 13); + value = wktParser.currentNumericLiteral(); + assertTrue(value == 13); - currentToken = wktParser.nextToken(); - assertTrue(currentToken == WktParser.WktToken.right_paren); + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.right_paren); - currentToken = wktParser.nextToken(); - assertTrue(currentToken == WktParser.WktToken.left_paren); + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.left_paren); - currentToken = wktParser.nextToken(); - assertTrue(currentToken == WktParser.WktToken.x_literal); + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.x_literal); - value = wktParser.currentNumericLiteral(); - assertTrue(value == -0.1e7); + value = wktParser.currentNumericLiteral(); + assertTrue(value == -0.1e7); - currentToken = wktParser.nextToken(); - assertTrue(currentToken == WktParser.WktToken.y_literal); + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.y_literal); - value = wktParser.currentNumericLiteral(); - assertTrue(value == 0.006); + value = wktParser.currentNumericLiteral(); + assertTrue(value == 0.006); - currentToken = wktParser.nextToken(); - assertTrue(currentToken == WktParser.WktToken.z_literal); + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.z_literal); - value = wktParser.currentNumericLiteral(); - assertTrue(value == 13); + value = wktParser.currentNumericLiteral(); + assertTrue(value == 13); - currentToken = wktParser.nextToken(); - assertTrue(currentToken == WktParser.WktToken.right_paren); + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.right_paren); - currentToken = wktParser.nextToken(); - assertTrue(currentToken == WktParser.WktToken.empty); + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.empty); - currentToken = wktParser.nextToken(); - assertTrue(currentToken == WktParser.WktToken.left_paren); + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.left_paren); - currentToken = wktParser.nextToken(); - assertTrue(currentToken == WktParser.WktToken.x_literal); + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.x_literal); - value = wktParser.currentNumericLiteral(); - assertTrue(value == 4.0); + value = wktParser.currentNumericLiteral(); + assertTrue(value == 4.0); - currentToken = wktParser.nextToken(); - assertTrue(currentToken == WktParser.WktToken.y_literal); + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.y_literal); - value = wktParser.currentNumericLiteral(); - assertTrue(value == 3.0); + value = wktParser.currentNumericLiteral(); + assertTrue(value == 3.0); - currentToken = wktParser.nextToken(); - assertTrue(currentToken == WktParser.WktToken.z_literal); + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.z_literal); - value = wktParser.currentNumericLiteral(); - assertTrue(value == 13); + value = wktParser.currentNumericLiteral(); + assertTrue(value == 13); - currentToken = wktParser.nextToken(); - assertTrue(currentToken == WktParser.WktToken.right_paren); + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.right_paren); - currentToken = wktParser.nextToken(); - assertTrue(currentToken == WktParser.WktToken.left_paren); + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.left_paren); - currentToken = wktParser.nextToken(); - assertTrue(currentToken == WktParser.WktToken.x_literal); + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.x_literal); - value = wktParser.currentNumericLiteral(); - assertTrue(value == 2.0e-3); + value = wktParser.currentNumericLiteral(); + assertTrue(value == 2.0e-3); - currentToken = wktParser.nextToken(); - assertTrue(currentToken == WktParser.WktToken.y_literal); + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.y_literal); - value = wktParser.currentNumericLiteral(); - assertTrue(value == 0.3e2); + value = wktParser.currentNumericLiteral(); + assertTrue(value == 0.3e2); - currentToken = wktParser.nextToken(); - assertTrue(currentToken == WktParser.WktToken.z_literal); + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.z_literal); - value = wktParser.currentNumericLiteral(); - assertTrue(value == 13); + value = wktParser.currentNumericLiteral(); + assertTrue(value == 13); - currentToken = wktParser.nextToken(); - assertTrue(currentToken == WktParser.WktToken.right_paren); + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.right_paren); - currentToken = wktParser.nextToken(); - assertTrue(currentToken == WktParser.WktToken.right_paren); + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.right_paren); - currentToken = wktParser.nextToken(); - assertTrue(currentToken == WktParser.WktToken.not_available); - } + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.not_available); + } - @Test - public void testPolygon() { - String s = " Polygon emPty "; - WktParser wktParser = new WktParser(); - wktParser.resetParser(s); + @Test + public void testPolygon() { + String s = " Polygon emPty "; + WktParser wktParser = new WktParser(); + wktParser.resetParser(s); - int currentToken; - double value; + int currentToken; + double value; - currentToken = wktParser.nextToken(); - assertTrue(currentToken == WktParser.WktToken.polygon); + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.polygon); - currentToken = wktParser.nextToken(); - assertTrue(currentToken == WktParser.WktToken.empty); + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.empty); - s = " polyGON M ( empty, ( 5. +1.e+0004 13, -.1e07 .006 13 ) , empty , (4 003. 13 , 02E-3 .3e2 13 ) , empty, ( 5. +1.e+0004 13 , -.1e07 .006 13) , (4 003. 13 , 02E-3 .3e2 13 ) ) "; - wktParser.resetParser(s); + s = " polyGON M ( empty, ( 5. +1.e+0004 13, -.1e07 .006 13 ) , empty , (4 003. 13 , 02E-3 .3e2 13 ) , empty, ( 5. +1.e+0004 13 , -.1e07 .006 13) , (4 003. 13 , 02E-3 .3e2 13 ) ) "; + wktParser.resetParser(s); - currentToken = wktParser.nextToken(); - assertTrue(currentToken == WktParser.WktToken.polygon); + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.polygon); - currentToken = wktParser.nextToken(); - assertTrue(currentToken == WktParser.WktToken.attribute_m); + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.attribute_m); - currentToken = wktParser.nextToken(); - assertTrue(currentToken == WktParser.WktToken.left_paren); + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.left_paren); - currentToken = wktParser.nextToken(); - assertTrue(currentToken == WktParser.WktToken.empty); + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.empty); - currentToken = wktParser.nextToken(); - assertTrue(currentToken == WktParser.WktToken.left_paren); + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.left_paren); - currentToken = wktParser.nextToken(); - assertTrue(currentToken == WktParser.WktToken.x_literal); + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.x_literal); - value = wktParser.currentNumericLiteral(); - assertTrue(value == 5.0); + value = wktParser.currentNumericLiteral(); + assertTrue(value == 5.0); - currentToken = wktParser.nextToken(); - assertTrue(currentToken == WktParser.WktToken.y_literal); + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.y_literal); - value = wktParser.currentNumericLiteral(); - assertTrue(value == 1.0e4); + value = wktParser.currentNumericLiteral(); + assertTrue(value == 1.0e4); - currentToken = wktParser.nextToken(); - assertTrue(currentToken == WktParser.WktToken.m_literal); + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.m_literal); - value = wktParser.currentNumericLiteral(); - assertTrue(value == 13); + value = wktParser.currentNumericLiteral(); + assertTrue(value == 13); - currentToken = wktParser.nextToken(); - assertTrue(currentToken == WktParser.WktToken.x_literal); + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.x_literal); - value = wktParser.currentNumericLiteral(); - assertTrue(value == -0.1e7); + value = wktParser.currentNumericLiteral(); + assertTrue(value == -0.1e7); - currentToken = wktParser.nextToken(); - assertTrue(currentToken == WktParser.WktToken.y_literal); + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.y_literal); - value = wktParser.currentNumericLiteral(); - assertTrue(value == 0.006); + value = wktParser.currentNumericLiteral(); + assertTrue(value == 0.006); - currentToken = wktParser.nextToken(); - assertTrue(currentToken == WktParser.WktToken.m_literal); + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.m_literal); - value = wktParser.currentNumericLiteral(); - assertTrue(value == 13); + value = wktParser.currentNumericLiteral(); + assertTrue(value == 13); - currentToken = wktParser.nextToken(); - assertTrue(currentToken == WktParser.WktToken.right_paren); + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.right_paren); - currentToken = wktParser.nextToken(); - assertTrue(currentToken == WktParser.WktToken.empty); + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.empty); - currentToken = wktParser.nextToken(); - assertTrue(currentToken == WktParser.WktToken.left_paren); + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.left_paren); - currentToken = wktParser.nextToken(); - assertTrue(currentToken == WktParser.WktToken.x_literal); + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.x_literal); - value = wktParser.currentNumericLiteral(); - assertTrue(value == 4.0); + value = wktParser.currentNumericLiteral(); + assertTrue(value == 4.0); - currentToken = wktParser.nextToken(); - assertTrue(currentToken == WktParser.WktToken.y_literal); + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.y_literal); - value = wktParser.currentNumericLiteral(); - assertTrue(value == 3.0); + value = wktParser.currentNumericLiteral(); + assertTrue(value == 3.0); - currentToken = wktParser.nextToken(); - assertTrue(currentToken == WktParser.WktToken.m_literal); + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.m_literal); - value = wktParser.currentNumericLiteral(); - assertTrue(value == 13); + value = wktParser.currentNumericLiteral(); + assertTrue(value == 13); - currentToken = wktParser.nextToken(); - assertTrue(currentToken == WktParser.WktToken.x_literal); + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.x_literal); - value = wktParser.currentNumericLiteral(); - assertTrue(value == 2.0e-3); + value = wktParser.currentNumericLiteral(); + assertTrue(value == 2.0e-3); - currentToken = wktParser.nextToken(); - assertTrue(currentToken == WktParser.WktToken.y_literal); + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.y_literal); - value = wktParser.currentNumericLiteral(); - assertTrue(value == 0.3e2); + value = wktParser.currentNumericLiteral(); + assertTrue(value == 0.3e2); - currentToken = wktParser.nextToken(); - assertTrue(currentToken == WktParser.WktToken.m_literal); + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.m_literal); - value = wktParser.currentNumericLiteral(); - assertTrue(value == 13); + value = wktParser.currentNumericLiteral(); + assertTrue(value == 13); - currentToken = wktParser.nextToken(); - assertTrue(currentToken == WktParser.WktToken.right_paren); + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.right_paren); - currentToken = wktParser.nextToken(); - assertTrue(currentToken == WktParser.WktToken.empty); + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.empty); - currentToken = wktParser.nextToken(); - assertTrue(currentToken == WktParser.WktToken.left_paren); + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.left_paren); - currentToken = wktParser.nextToken(); - assertTrue(currentToken == WktParser.WktToken.x_literal); + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.x_literal); - value = wktParser.currentNumericLiteral(); - assertTrue(value == 5.0); + value = wktParser.currentNumericLiteral(); + assertTrue(value == 5.0); - currentToken = wktParser.nextToken(); - assertTrue(currentToken == WktParser.WktToken.y_literal); + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.y_literal); - value = wktParser.currentNumericLiteral(); - assertTrue(value == 1.0e4); + value = wktParser.currentNumericLiteral(); + assertTrue(value == 1.0e4); - currentToken = wktParser.nextToken(); - assertTrue(currentToken == WktParser.WktToken.m_literal); + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.m_literal); - value = wktParser.currentNumericLiteral(); - assertTrue(value == 13); + value = wktParser.currentNumericLiteral(); + assertTrue(value == 13); - currentToken = wktParser.nextToken(); - assertTrue(currentToken == WktParser.WktToken.x_literal); + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.x_literal); - value = wktParser.currentNumericLiteral(); - assertTrue(value == -0.1e7); + value = wktParser.currentNumericLiteral(); + assertTrue(value == -0.1e7); - currentToken = wktParser.nextToken(); - assertTrue(currentToken == WktParser.WktToken.y_literal); + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.y_literal); - value = wktParser.currentNumericLiteral(); - assertTrue(value == 0.006); + value = wktParser.currentNumericLiteral(); + assertTrue(value == 0.006); - currentToken = wktParser.nextToken(); - assertTrue(currentToken == WktParser.WktToken.m_literal); + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.m_literal); - value = wktParser.currentNumericLiteral(); - assertTrue(value == 13); + value = wktParser.currentNumericLiteral(); + assertTrue(value == 13); - currentToken = wktParser.nextToken(); - assertTrue(currentToken == WktParser.WktToken.right_paren); + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.right_paren); - currentToken = wktParser.nextToken(); - assertTrue(currentToken == WktParser.WktToken.left_paren); + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.left_paren); - currentToken = wktParser.nextToken(); - assertTrue(currentToken == WktParser.WktToken.x_literal); + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.x_literal); - value = wktParser.currentNumericLiteral(); - assertTrue(value == 4.0); + value = wktParser.currentNumericLiteral(); + assertTrue(value == 4.0); - currentToken = wktParser.nextToken(); - assertTrue(currentToken == WktParser.WktToken.y_literal); + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.y_literal); - value = wktParser.currentNumericLiteral(); - assertTrue(value == 3.0); + value = wktParser.currentNumericLiteral(); + assertTrue(value == 3.0); - currentToken = wktParser.nextToken(); - assertTrue(currentToken == WktParser.WktToken.m_literal); + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.m_literal); - value = wktParser.currentNumericLiteral(); - assertTrue(value == 13); + value = wktParser.currentNumericLiteral(); + assertTrue(value == 13); - currentToken = wktParser.nextToken(); - assertTrue(currentToken == WktParser.WktToken.x_literal); + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.x_literal); - value = wktParser.currentNumericLiteral(); - assertTrue(value == 2.0e-3); + value = wktParser.currentNumericLiteral(); + assertTrue(value == 2.0e-3); - currentToken = wktParser.nextToken(); - assertTrue(currentToken == WktParser.WktToken.y_literal); + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.y_literal); - value = wktParser.currentNumericLiteral(); - assertTrue(value == 0.3e2); + value = wktParser.currentNumericLiteral(); + assertTrue(value == 0.3e2); - currentToken = wktParser.nextToken(); - assertTrue(currentToken == WktParser.WktToken.m_literal); + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.m_literal); - value = wktParser.currentNumericLiteral(); - assertTrue(value == 13); + value = wktParser.currentNumericLiteral(); + assertTrue(value == 13); - currentToken = wktParser.nextToken(); - assertTrue(currentToken == WktParser.WktToken.right_paren); + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.right_paren); - currentToken = wktParser.nextToken(); - assertTrue(currentToken == WktParser.WktToken.right_paren); + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.right_paren); - currentToken = wktParser.nextToken(); - assertTrue(currentToken == WktParser.WktToken.not_available); - } + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.not_available); + } - @Test - public void testLineString() { - String s = " LineString emPty "; - WktParser wktParser = new WktParser(); - wktParser.resetParser(s); + @Test + public void testLineString() { + String s = " LineString emPty "; + WktParser wktParser = new WktParser(); + wktParser.resetParser(s); - int currentToken; - double value; + int currentToken; + double value; - currentToken = wktParser.nextToken(); - assertTrue(currentToken == WktParser.WktToken.linestring); + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.linestring); - currentToken = wktParser.nextToken(); - assertTrue(currentToken == WktParser.WktToken.empty); + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.empty); - s = " LineString ( 5. +1.e+0004 , -.1e07 .006 ) "; - wktParser.resetParser(s); + s = " LineString ( 5. +1.e+0004 , -.1e07 .006 ) "; + wktParser.resetParser(s); - currentToken = wktParser.nextToken(); - assertTrue(currentToken == WktParser.WktToken.linestring); + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.linestring); - currentToken = wktParser.nextToken(); - assertTrue(currentToken == WktParser.WktToken.left_paren); + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.left_paren); - currentToken = wktParser.nextToken(); - assertTrue(currentToken == WktParser.WktToken.x_literal); + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.x_literal); - value = wktParser.currentNumericLiteral(); - assertTrue(value == 5.0); + value = wktParser.currentNumericLiteral(); + assertTrue(value == 5.0); - currentToken = wktParser.nextToken(); - assertTrue(currentToken == WktParser.WktToken.y_literal); + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.y_literal); - value = wktParser.currentNumericLiteral(); - assertTrue(value == 1.0e4); + value = wktParser.currentNumericLiteral(); + assertTrue(value == 1.0e4); - currentToken = wktParser.nextToken(); - assertTrue(currentToken == WktParser.WktToken.x_literal); + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.x_literal); - value = wktParser.currentNumericLiteral(); - assertTrue(value == -0.1e7); + value = wktParser.currentNumericLiteral(); + assertTrue(value == -0.1e7); - currentToken = wktParser.nextToken(); - assertTrue(currentToken == WktParser.WktToken.y_literal); + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.y_literal); - value = wktParser.currentNumericLiteral(); - assertTrue(value == 0.006); + value = wktParser.currentNumericLiteral(); + assertTrue(value == 0.006); - currentToken = wktParser.nextToken(); - assertTrue(currentToken == WktParser.WktToken.right_paren); + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.right_paren); - currentToken = wktParser.nextToken(); - assertTrue(currentToken == WktParser.WktToken.not_available); - } + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.not_available); + } - @Test - public void testPoint() { - String s = " PoInT emPty "; - WktParser wktParser = new WktParser(); - wktParser.resetParser(s); + @Test + public void testPoint() { + String s = " PoInT emPty "; + WktParser wktParser = new WktParser(); + wktParser.resetParser(s); - int currentToken; - double value; + int currentToken; + double value; - currentToken = wktParser.nextToken(); - assertTrue(currentToken == WktParser.WktToken.point); + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.point); - currentToken = wktParser.nextToken(); - assertTrue(currentToken == WktParser.WktToken.empty); + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.empty); - s = " POINT ZM ( 5. +1.e+0004 13 17) "; - wktParser.resetParser(s); + s = " POINT ZM ( 5. +1.e+0004 13 17) "; + wktParser.resetParser(s); - currentToken = wktParser.nextToken(); - assertTrue(currentToken == WktParser.WktToken.point); + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.point); - currentToken = wktParser.nextToken(); - assertTrue(currentToken == WktParser.WktToken.attribute_zm); + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.attribute_zm); - currentToken = wktParser.nextToken(); - assertTrue(currentToken == WktParser.WktToken.left_paren); + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.left_paren); - currentToken = wktParser.nextToken(); - assertTrue(currentToken == WktParser.WktToken.x_literal); + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.x_literal); - value = wktParser.currentNumericLiteral(); - assertTrue(value == 5.0); + value = wktParser.currentNumericLiteral(); + assertTrue(value == 5.0); - currentToken = wktParser.nextToken(); - assertTrue(currentToken == WktParser.WktToken.y_literal); + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.y_literal); - value = wktParser.currentNumericLiteral(); - assertTrue(value == 1.0e4); + value = wktParser.currentNumericLiteral(); + assertTrue(value == 1.0e4); - currentToken = wktParser.nextToken(); - assertTrue(currentToken == WktParser.WktToken.z_literal); + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.z_literal); - value = wktParser.currentNumericLiteral(); - assertTrue(value == 13); + value = wktParser.currentNumericLiteral(); + assertTrue(value == 13); - currentToken = wktParser.nextToken(); - assertTrue(currentToken == WktParser.WktToken.m_literal); + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.m_literal); - value = wktParser.currentNumericLiteral(); - assertTrue(value == 17); + value = wktParser.currentNumericLiteral(); + assertTrue(value == 17); - currentToken = wktParser.nextToken(); - assertTrue(currentToken == WktParser.WktToken.right_paren); + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.right_paren); - currentToken = wktParser.nextToken(); - assertTrue(currentToken == WktParser.WktToken.not_available); + currentToken = wktParser.nextToken(); + assertTrue(currentToken == WktParser.WktToken.not_available); - s = " PoInt "; - wktParser.resetParser(s); + s = " PoInt "; + wktParser.resetParser(s); - wktParser.nextToken(); - } + wktParser.nextToken(); + } } diff --git a/src/test/java/com/esri/core/geometry/Utils.java b/src/test/java/com/esri/core/geometry/Utils.java index 309deb1c..e1e1c4c5 100644 --- a/src/test/java/com/esri/core/geometry/Utils.java +++ b/src/test/java/com/esri/core/geometry/Utils.java @@ -25,8 +25,8 @@ package com.esri.core.geometry; public class Utils { - static void showProjectedGeometryInfo(MapGeometry mapGeom) { - return; + static void showProjectedGeometryInfo(MapGeometry mapGeom) { + return; /* System.out.println("\n"); MapGeometry geom = mapGeom; @@ -94,6 +94,6 @@ static void showProjectedGeometryInfo(MapGeometry mapGeom) { System.out.println("wkid: " + wkid); }*/ - } + } }

?@Ib4$R$ZbA6MIsF(ZEk4Ii=sfRBa)&Yo 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�^9Ew!K(j87!gFtE+SC|A#I#UVt_@yA-l7(**V4qD^4ptMm z6GaRn1&jJ%az?1}^NJ_fR#63mp@PL8qZJ-fl9^Ug znbf4ZM++F6Mxu@f{WX?}8F6pD4Os@KQz4oDFW^U%p3?6mFk zDmt(c%0mC$KZ4KBC&x-%E}>7`DOI4mW9c3+GCu{r?Z@>w3uOD9KOicqZo zS^VviU_O4M>Cy3%4g(?ei7I31`xGlLPc@Sqd3htVfS2Z2?TUHGkZ9SHB{xmw<4rXD z(J4t%6TShX7IZ|EtGaa}O3f)oTfxYIMY!Y=To^4Wsi{qvhJ;rYc3VMgO>ueUJ)uK4 zF{72#z>h>DsiqF|ARu(&C^TS)e$tvm2{yZ?rk%J;>Imisiv{* zH^>ed>z=V+vS^)jMr(b#9-X7wl9Fl^D0z#2^zxB=>@Ehq#;m-CDDb+QYjIuJL2`LP zyvCfAQT237NV)k&Ad0d~5RMeaXJf~h76GFwo8acxP5)T~a*dxV$WIbH{7ICE%AHZk z{R-yc)el}7M=$I2f)YHTPVW;}A+y9Qnbu-u?woT@s-HYJ0{g4A(aK>Zx7+&Ao?V0_YJihBm1}_5v*=hW(-#R(Z z9yQEw`}{vYVEqidGX+pESkX{JcMO@Yh5|z4L+e9Dkhf>`NoU|Xz!9jRtF_~26=CW2 z!$V70yj#GzZ+Vf`Os-#vvy)Gmnf@1D?;ITKyKM`{wr$&5v2EM7R-6^vwr$(CZQIF; zlP|w>&OLkI`u44Q8})W||IyEUFvlEYOb<3c{M``82ToW%$z1T6i9=9C?pvh~>46y& zN0kA3mQD&G|E|Ck(0$IdZlZk^*cuBr9Ra7WLIny{ng!Kbr+a^gu~FqrK}Kec+AIaz z8IiUCfE%zGJ0i6Q1XP9NKmITYn{o-^yY(ssOY=qWIh88lZe@{&P@iXVre*A zX!B41h{epD>O|wDl_Vvo^m}Gm_qYRcl)-2eH&1B+mbULoAea11;*I7p!LiJ8noQ;@ zjRWmC>Z^pKN(F8!b;w*t7nWBMse9;&2*X8vgy^rIcx6E^p|}a zt}v1hhKeaK5EX2md!tDQvdfGcifvA)MYgjk)ha99>j^AoRcUX4^JPaMx=V8lutFRr zn5xCmPFG_RqX`kgg>eRJqlI4l{kOCR#Smi5TZ<21KNLqurcYl`w-@boMrz^~9DsWa zcGjq#og?={qmenv4rl@0v2RY@P!_nhhbn`2&fVZ|E*KcQAmh#Uwhch_|Hvjma4E2; z*EG+*^N%Djuy~akX;kiXWR05^Kt|29Kb*hE1jjgkz%_8;p)&?(kZm1HzrvOKb9-F_ zJ@^;t>D9pS{DQV1A;+GHmXsWX_SrE;FRiFd_gU|DNK)aoTU9zX8Ox1-Kh^Hht?0L5 zn$O@Mh`BSIyMO}R6>d{M6ezRFD_9RInRF>(#nI1dPF?aip>}1?mB|Z&4M$JqTX60avDBVS~k>R>TVgmLIqYT%P=n!T6FdK9gp z>ecKN^Ca$$Z-$nS0|>5EA+Okin5HH27$aXXP<+mioIfi(XxA31mwy=|>JI+&mX*2`JMt$DM+wEn)~u`(s)n`G)06 zk&2}$RN>D)HzAPW-(z@U&?F`UU%ls~K=b{aqr!G|2zY+e%d6>4A3d+tuOFxx{OaO{ zV#GZc(qRvySA4a2*$_X+f#;kSZ2&Y9#zF8&7+Cg(nc5t}#3Noj@PSLz5H~aWNKLm8 zVn1$1HA%eh!W31+6Rgq&`TK@J69aT^fUg zYywm7K^-4QnPN~6*eqe$okP*ZAX^t$UkC@qGXiN;pA>lf5jg2K$nV-5^KD5o3O_Lp zL%nC2wnR6tZ!}^r$rq&fzgDM;x9|LNlUwfj#r8-7dtR_+-r#umi2MGxG4IbjzTBdjBSLAVdD2jMKPKO4rG(OGE&BQM(>3qXP?=#Nzf0z+TMyVgjup>T z9bf0yUD4yk^Znj0@DhcJdC3M)e1sc+v z`#i4+gx_BhA0eORR@PH= zd^*M>cdx?uc*cYjvXm$1xhTi>cQpGAuPwMs(c!yY1co?fdgY$08LN_0a2i1%z^%nse7)b{^x=LH!WgoJVjK?|>GlU8c%`Do1OecMJ23{5aw9DpK~4}p8mbEN2J%)0 zQk;ZA)IhNZp3hN}dp75>Vw(zVR#YspLE6l%>~e!W;e zBV36f8b+WsCoO8OIW2c<$>co{w_%FxKtmL2a-NedooCfswU=c~m;SUnMnrVPWz;wK z46a_TfC=#R#=<_ zRjf#Qox9#RYGl9`Z0efog)m5}SuANfn zcLI}r)LP3%YCX0(oERw~JvV4e%rL0^hK8y)D!yACnc_CYaF*y%wRYH^TJ8kR?xI6n zU1y~#8KBejd3>9i0^{V>&gWA!xBJ92;9S2uc=)Z8z-qJY*W&7i%X{odEB;Bvet9$) z|Cgu=HymyNmT)>dFHtr;3z{k2S;x&XM>%4S9p3FUpg1e;2-&JY35eLhZr&+!8xCTx zh&i(xK+0@-Pr__w@6)h&JKro3NkG}3OkDU=m{~U4jT{!a6*g5hGk>xXNqJPr73X&) z6DOBQCHW_6_6%Fwj&pQIpd+|GB2(S(6yq9Z_j%T|>-3~fBUd02y1iemr!2Fl^Ki-Sftj^bAmW4X;)+p#Z%@UsY#TGHDkrqGK=}|)*Owkm6&HWDT+kM5U-=L| z4@Y-gr*aq-+IIeh4F6MI^r)1HcKewu;0OQ!hppEkTAe&-YD8>+&%(6`~ z%zz0BSjjLIZ6l3B8V7OmIb20kd@{U>w7J31DuPfc0u3k1(SC4ObdRtDX2f0_hFm!l z968d@WEJiqxp%cq$i~&(b7)o`$bhtiF%ht6< z@*yjnHq}~FMp@7}3n8jJ|Ke!bozqIQVq}R0)RQ_SLp_aNT?Hjg0JK2gA(1urrZsKJ zC+Z)iaL12pZYt(Xs8r#h~s9M#Eo5Be(T>Tq8vR-;sSJ)Ta<7 zX47vTxNiiTv#SJ~ zv*)y3G!$RF2i@lerblI-YV_z-y+`d;wT}(EV<@Q+o@%-th3YRz{}k!>aTu{^ zxP`jLHB!9{J!zW{ymd_`QjP;*4f)5x^^k+8^_s3|l*U%$J*mi()I|^t!=Yzq zm$v34Uj;2R4x(Uk&63l%ORGWbIv%HS6!J5XjZY-qVJ0vQB%UMkFf#2$QRJ4SwteXO z2m~<~HFM;e0RJmgPs=f4rjXO1k^!UoccpkAN0TU`7d!yTU!m7F~_2hV`Bo} zJf*9`eF?u|Wh`-(02?pJf*^Hpe=SVb}P_&1?FQ}aDXM!hP5>8h#S+iXXuxF4qqZIPP zoor~nm=mE(xkhAIo1G%Z15`qDllad08)ibO?MVsLwiFfQ0M3=T?#pZ9!>i#?vi7y7 z+Gt$?m+Goc5W|ONh-ns=4|B7{jAfme`+qu8U_<>D=)-u|85WhKOu^f27fEcugH^`SkiW6CKE@sg((dz1_ zqvgKRZS$`axPq#O^$s!A^$!tjK=b!M@Vh1qk8s^O!Bh5Mdhp3Ujj4YCCFjsO)@LxD zQ`*<_@<+;VAQ%nxu-sjOr~2Bf4Y-Tzvt|ar5o65^JDIPK7>K}8i?#dokwVEHFwv!> zencnZ=BZnSrvDDq8sMz-0B_P7%72>d@wLlpD9(BUGUe)*j`)riJ+6o_xHicB%`_PR zGlv)5Kku_XDoCkgaht>2oHj`H2gVOMO!`b ziPB->7r_%90P<_lCG?@{1MZ-?x7KjiWvKT6gtr#p_NrCr&7~xm$) z!bOs2BBc9U8Zc|MtD_wgMP8^#C}r@U0KDbrZUI=r5;SW@_%oTD%zw9Xe*DU;^}R8K zQduW5qqvW4X!Pg$CF4TBvE5=IHCam{^brW1L~Ss`m4#5;{fN3ICU zJ55r3<}W%z;~Cg|Zh+fAduvs24_En?&hdn*tN(%qY(Nyg#N)IC+TN>*k4^;NySI+G z=ubcEPm3D?1UyJc=3NlR5DAQ40)QsLB3a#JzVDO3B}URzGN_Yl4)vGl8dr#hbX6c- zMa1|?9Q^g)aekpBHx_%#3kPz4(HOL8vfFD4pi;$Mbvw@eV*m~(iN+0XMWJv5c4k0< zwXQjOWN>9wk#L^sfpb??QLFW8PM^EBd|nxm{BD-l?@WYgn(dS63on2ETZ)Is-=-S$ z_)PAo>|DWav)_RQ??L|6d1wB^L{4lmCqX1u3~7;ma4)Xn#EwuRO|5 ze5tuGoi2+9L3(N~dZ-z+Dh=IMVF&dWEppUw0AKHq$6)mj%{{~VSC&sq+S$m-#;-q6 z%M0TA)58$QHsMfcXf!lT#@B6Hmry$=Hhl~l7q_0eu5Fen7kB{>=mux|{x5e0)ZnW3Bj&YsckZM$!rSE}O@_>oeaI#GC%Msvb_sV=+xs zE6#A7j($RL*)v&k{FSFS7_JM1G;nv&d)U8*zLFIF(?+1*QyQrCS8V3h0R4SX4+}%(MF#^q!FXX z?$g|3#Hfqzc$NLzF#eOb1)$sU$Ndar*UvCg{fC+SzlO;FByT3h|7MFE6({v0ct;7E z5qDpr3Wa)E9>f?FHjJc>g1n0}52aK73mnOzzHYpJF6UT59SPk+_M>^vn%|8G(wW?B zb2{!g+3xo3>;ziv4-W$|l^J9#DR@Oei&vL63?saNByH0{A5W-ua&a8(mBk!}(;j(f zJ6LJM4dr;lHxj>;5PN%vshju6>f^B2@+VYEh~|K9hs~pNA1IctK4ycQfRsIt#%7HpitrD*w^0DOL^KxD6f4b~x@)pEdq=Su$30 z!?O!9`GOerlG1+Y{re7C&JM$TndiO-R!$jR#P*AMa{V=nrN9boTN~s59;Ng2eM1BFKH1hBwbC~_F zxSY9rY(>rkK+#tR0O>$AryZQAV2GpoYv=B2UJ5W_CBIVAXG>QaTs~JFp0%Hx#j|2t@t-c{jY-Txi zF7n{4B@!8tBi}xjYE48&_aW@a+jA$gMPnUcz$&V&i@r zLpL4%4D_6y@NLSE!oipHrUz~>15x=P2%#Hwcqj0#MezQL;fpR1ec(kFBP?VLkfZ~RG&B-a!-$VHPSa?cjpeDY2W}R?P~^N{Z6l9 z^d+SKQ;g}YF7iuDy$h%Bdzakc!w%vbz0Z}KIDs!}_lL?pt9@7k#7TjwYLck{nC2rO z+80Zeas=fb*iS{1P!Y&R6f-VqQcC5ntd=jLgQmb*t}1?h*q|$pbvQ=>lY?xK6H~h6 zR;^g2AS1X!6O&wP30u*`gc*0^BKT0x-Hj;9rluA}9#eGC);V-lhve@U zckt@WwBoc#^o@*p5skOkOzPJudO`a30jes4yakRvhe_%0=i zWZ_ud=WnQm%wSAmBX628w+n+U>Cr}-@`uf%J^s&kk(mojZONFsZmI)0Q>0Ai`&mmw z9@Y$BMT0%ydVv1Xn27fD(5T=G|jZ9{y_&_cwk8`8R zT}hBkS}#kXOi!h*^FuX}vK7o)T#b$k2P6*AE3Kkv2zlmrg&)cfKj=MmTHB06pJ5bA{g+2XM z%M~|I){Vq3;IW$WCqTyZOc4x8SsIK?;v{U=BZ|e2jOs3g5>-`_)*Q>U2;*bbU^=*L zJRwb$<3Nd`8v;Z+M1us2S2mC*oZWVi_tz(rNN9SqWXnDwySBJ>fPAjLYhhNy+Fw}U zD!Xi9YFHn*qlrRhIS9iHLc^C^-J=AE(OO1>a&{BRPfR0=!&0S?azq$AK9Qb)%y5K6 z?6%@wvm`0|Y%DUj%v}Wa_pj=aZ_dx&kc62%vl3U(bHbxyT?_y95o7Z>P@K(oc~ROv zZ(+zr2+XK#G**ubBU5w<1y?=B$#gw~GP%8n-STxe-=rP*meBHgdSvI9tr$kX7uUrr z`GrIW$r-9aDdbrTmZ>$RbUYl{r`Vo=_ zx9@sSv$DA8ceZ+sjJ58apwv5FqG9ua+PO4&tkj&2nv6$XsS_3t9-m{=s560arAQuB z4;n{3Xzbbe8Il63=+4Arv#nBFQ|015eeIJD&1Lm)DRp8t#P6!G-MzUE^(n`3g7$}M zq{H=#iYilNtct_p6|6B`rS%HkY(L{T8TzW-_%HvLI91xMWT$P=02dr$&sb#&H&gbQ z7NnD}qGqHe`szkth#zt;G@Ew5fRQ^T+FQF96RV9T*mii!+@9unX&zezEtu>9%oSEv zm|4M@w6iQV!V`EuQ z5z%ZfXQpDPZn>|rIvk0@1PA+jzy1+F<7ciaBHw?Fyox{B)jCQ42 zZXAWKnB{Uh5-*|&{$-iK<^2Zf3wRUZJG6+Vr4@@ygiCg%Tb8}O2`~A1)d~T@9;kSx z;!!4zclC1xlbj3th=7bUAG9BAQ=OHhFfLW=Mx_@Zn^SXVkrq;h+eyr0rihMqLt?P3 zg@3UubJp<{SFq2)SglL3mt?^3kW55jSjRDGb}Rj6_G4gKi*4B!0kt8@v;@ypg`%9; zy8}yLyfEbu`TDuJDvaX6#9L4w(RZl#?;((a)8$(*&vUV0$|Qugq9gN)7j=V8y=3^gx^P4osQd4wVYj7KwkY?SHs|vwWL*7sCxbuGudR<64M)?{YIE(ttoIGZ%aTC zTSzB;n%ziIORx_M?egJSCB31y;SIjGV>#d08s0uTd#a3ah8gCB47t=Va$VKx@p|@EQX|+7n)2(swd$7E>Z<&75aN0sdrEb zX$5tbNyET7+wkhj)h6Mq-k&e^^~yGWTa9j)c<2RbvYd3h8I$ADD6G)dfo`vU)A3B! zg?S=YS~IiZzWL~Y4Rt5SyTZ*8;z&X2lq~6E)8w`eocd4ainIwWi?9nIAsH8jghR-_ zurlEHRIN$&!-Fy!HbVfg3SsE+myu1_+bQemwv`KqOsfhKIS1}3|9jfR2Y5G1S2#;& zyo`})Vg?tqzV>@ifzr8~mOO47w8!88DBfS=j977FTn}JSn+g8=4{VRE-_(LUlcAdgo3x zBK4Fr;>N+v8W<>YBi24~vmrreh)x?lD4%FpV@4rXq#&bep=&H#EZ~(W-Q-3nEgGpd z#H`bl&hU7JD8zW)q2b9-6!_piJG8`uZE!l)5WC?jLmIB@0=L+1wUH&OPQN#!> zn}?v>_x^hIRNHfV0(lMM{I-rfhT;jYmNg9+b8n0q?mWwFAi<@{eTOk?$r75Ow0Bzy?YL3Pl!!2KE^`)A|S zYuO=JCdsLccW}vH2~-K+l_6&@ zJ`G%^{x2fB4Ea-EN#<>;HsEA5!nV2BDB!~Y$emd-U_%zbO;Yyu+;zkzsp=&)XQ5N$ zMZbP7{VIm`GeLR>R=}^f38T8>`-W8+=}mML0$Qwm5hwS>I6p{Wv$=t1I__c<%)_@M zmN{T9mt`Yl?>ENr+Vwk&KWWo{%TDJ{#y7>By(S^~=SGg>7KT)pdH1kx5h zG<*Hk;VpB%qVZF!pFkJXN;ej`?<*$$j5UYwrSeAaxWIp*h;=$}>9@M}~4T--9MZ2{*2l_IbfuLpi0sb=4VPAj32u1-eP6OVCT1$>>a^(YKbEv^j?RinW z5E7ZYBkJi6aCxEA3b?U|GtATz%fipK1%&GKJAIEtYgj;Rh-~x~77jlyaTrD{7V~Wo z$feL3Quzy>4Wx37Mxh&?HTC>Jfml+a9&?(H_c;BPP?{Qocyavl_8PIH`?zEo^?_eM z{1wXhXf!-9u^ODyiq*t~zJV1~x3p9`!s-Q&T_M&4)>-{aaO?CB)20NWErk#xlg~KK z2Qxg@09{LM0L)KMqq3WmO%S$I{c57+F-`SBl{y1eR6Kj-R&DA`?xNIRyeGHq%X=oF zd#n1e7oyH#cS(DDrmdSWnZLbW+|lKK;ot{nLA}T$mUzTq4^(60kAQ-IioDy=Bo_{u z^}t?|6Ufr(Ta4~0Ns+Phh`4%XnUdjY7?&pauZPC2g9zE&F;u%4ypb%l(ymSvqWwt) znKt*Wt>ApY7%r>@nGqjudqsIW^4=wB?jZd}>o|;&MC(k7u_Y&Fqd5a)skmd39&SG} zWT+$AGZm9y}QVva9j-9c{h9j@Gi`E1jqD)`BBwN)ht0lf7M)nohq( zc)`RIUyqq9qE~W<@W^sjT)hHXo~hOLo!CRaM>*0?3}>2-lVnwPWMf??ON5ds#vp(B zM9uSJ;b>yzXiS~ptIL@Q|0{E2^p2p$L7`4>>k!D6FAOZYgf~J6nxk`5po0gzsqLTZ zGp+a=>PrJWqTl1!>KFdMaI1gnZka`W=~O>%a$-Lw)s+7sFBY=1u{UtEaI&-guXq1A zhWvMi9HpwGglvZ5!v-m#zJfsHk0dOzh|pcAX#-k?!CwG4C$Cwjnrw`W_TblneFXl3 z?lG6)+L*PM-&M-&-+C2h+rQ6YR?+8P1l)F6GFx(RKd8x)?#aQ*Q5r>lVq?UvLjV$vIRc*WXt6g@eW zTk#GSui^cvVQtr5~2;^9;$yKP1=JuW11?|Wcl&r&0L}eMXVv(L^b>X3a+Fa zbz_ctI$yUO6H2qBm>D`juK1)F1;_FAvi~pu6f_Q~jcYhKfssnYOvNvr?79DOxINAZ zMiP}u`ZzOP*U)yG3F;WkX-!kWz7Mj&kZT2%1nCUk&#M$uT^T`Ki*VPh2jqO`hit;-r#O)Mz$ZSgAB>S~rw!i%A>Z^DXTZ$Usvu z*A(;vGr`llQbJCQBPbY9d&wT`6Ki_EFxW}jmI&29FYix&@XVTl|Couk;;ak8+Elv6 zI5ryXDlu#Y18b(3FN76Vc77pYU*1G%>}Optm96=U{}_pFU~TmtCJiiGY1da=93tqf zywQ?Vr@Z|!v1EO92-ONCLC6ZVB$is#ALyR%+cjG7@ zixv>M3({$)dv5wCNQdCFX@GcPa0#k7UVbyNd)!20FX-;WS+tL_Jz9|MP%R zfNt`TqLXhBagAv!ToFE^oC)7x$u(Z#sv0IiLesEuuuMaEa=RcPTgB!?uFi_l1|jv~ zi+BX+D}LkeF5>N?@S;X_iVwB@A*Tq3ULBEB0Q$;G-z{l)Azuu+v8O1uIbb-5gt0A% znl|UH?Tm7rA2t+jb}&w^;@fTQ6kv6ZH7K=>T#l+F4JnxCP^K@(7RdJo*@x*nAF-jj zz>`_Z<#ZG%(N^~aMRgECVG$4VB7?fWYY|Dl%c6i5C;!w6wHMQQ1mtp^pA;oY(eEhD zb*Q7;bb;Te2Z@tvr=J78M@G^o@U04o>l9?V&i1}UP9gB*4qTVoxt22d?ex4;7>PJz zN2nRHE_MU3NS*}XD*yY9@)gzJG3f=R+9uGyC#a?BzpB1nrVESUv!S0Nj+7DZq z%kHa{{kA24pgRXK8h1lj>XHLK4d9hW$_ISQ)@oy`Z)F4zCC?TLBOP0{|Mreur`Stm zV-lD3BH@tdaCJg)NT~|=o}FuWYZ8AmIiCL8gYPfcpK@_-A)}rqyT)RWHL34O_ktom zC!}kW_*RrD&z+6XZ;#x=j(5prV-`@Qeyca%|9io`K-^4n_*rm+VE_PV{=xLB z<&Zq5=52nMW~Z^Eiv;|D2gclh32?#NWw>98OezMmy~<5ZTJ3GPZkJ^~W{t|OOJ^Rq zg}yd6SyO)?ph-TS5Ez-vWndLMGg&pUiM}c}oJz02dT(ZP-g*YmQnsT_8kS16SxMG} zHxJOhe{Zw!`kmL=O94}o0x86IApfheo_(w`(A-yAq|y7GgxYj@hQ+hTN59E+Occe; zO?IqPw<9tWXB8KyWQTQjCK^R_a}<;?(itRI>3It-Yib&<+I1+7oPy#)jx16X3E6c+ ze<+`gC(v=?z2{=#WTBUzdgB#izLcC4sI2h&!nHCti!*a{!KPcxeS9^~3G?T)SI&Ag z9u=8tBZ#Zrb&z~Qb$mBeKr%_m)nXuj+!j@Bfv@Ox>|LZ?$-Z|W#b={b01~~?yL3|- z!5;(fQiWK*zD7H_^>I}3qlcO#dde6W7v6MS(~Oj0WH&jmKuHJ51eFO?8N6aQZGy>+%B_FK>ufsX^i~}7lb5FHb%oR+?@CvT)P9GcE=^38N zkyEa%>V6fcbl&lhD@rjYn5k(#vO$^6WEWDUHSndD@$=5dV(>%lm174sS1Y&@W?RLq z2A5=+pqvE*+-X5Fk8*mAopaCJPx#NptGOWj7&)LN+-sO zvJ^k5lHV|FPGHdQ+v;VHnf~{e-u{r6(cTd>-nmrXAv2ogsnXij#B!FJk-my+PB6IVM&kddzN*2TCboQSTCbJEiZEU4#$A4YU-1xBkY&$e)v%coK;N* zzbp5k-Vt-p0RuCGgRXJ7c;zU)SRSQWiPWYk&$>c7 zf!CwehSYYU)jqDAZ%^0_4OeMd`cd=c%{mG6q$dmW*vc)bvV)MtO#n_B4GOY>7VK!( zhk%>gw7w?nRucmIktI$_(bancpryKcckoTX59cl{?tQ;mFT39hR@gevmuQMPT{8m;N-oLa`!g#EmHJc-Y+$qV9EN zREo6@5LQb#HjVc}0=G~8asM=e@r>``ma|9Qd)4J0@Me5kzoU@_o%M8@^=z00<(f+h z;uV2MoQ9n!#n%!X&>vxPjUEc^c0lalQq5JKr^RTDygs+*g20ECaRS7;#gP-P^}wT_ zt%p%L9mZUTBAFEmgL^{{u8eg4I_(nO?9g za>-S7|H*`wzBjC1eFk0TA7au>9mv*GaQ>~crpl;nzAks-fj7uvmQeC*K=1nBNuB@H zkj__2zT1ELsVZOq0O0(GrTf2&ga1=NO0iJaK0+P&wo^B4g&B;kHTHvn9G~PLOjXYK zqoFQ}0ZBqgCKz=Lgr3SUxl5z5xSZacZrS7-!PsQkQdp7R+`qV37(rIGWV^mp{Z{q4 z=wsRQc*c3--L~1b$>=`*FzKD*dHwyF^* zdgn_wIN$AV2+)wtoEuA*NG<`wCbj%(90n^0+Oz0^sGh0is`&L>h|!|bTn5IRk>`_f z;zoG`0}rAM^K?S>L1ahfgath2^O!ep$wO73&6kGNgf+1*{T zpEDB{z8&tq0*{g?RZ+}@0h&5QVkF6a!JIP-B07Q)i#5NGilt@9lX|$u@T0kO?z{;M z+uyWdo!=&-+WH9*X;me@FS(5Iu4e|6H4et<=fmc7)%Cft=foCRBDswAMe!+pV;jUg z*kPC+UL)Ed*frwee3cG$mzS4^YWYgJ=8K_4E{^26Tw9j|d$o=54Oy1T9jhWl^E86> zfDPBCtRyE*6#B`wXjfN?C0SLI?HMX!A&uwy)c<_OKu`s?I$qhK@$@KOf!EA*VREc^ zE>-x>#CQ_lAY_xv{`P^mC;4T=nq?xw|v9Z(UGmH)G*Q*B@ZpSG39S9QIm4k zmh%W1DZkP0j*qS861Lb;60u18#F#)urwhFa*T?dOR9P(Ic4MvXU&wM|C6Qfo%8Ai2 zS9fkiRS(r<^;8+RAM91U)_tn@uDLTIbb;QdpjC}v&j-PY{f9M->q*jN@gJ1lPsE*< z16!+SLVT_sx8VRF=UG6wVPzIv>x#vKWEQt8E%)q?t)WtQAFClbyDEMa)|p{2ZGFjn zCKDU0_pEH5S#Y!N4fZs7C2Qwq)-E03e9wF6FQn|(kGpD{ZUNW>c%5TWdQL`*0Xy~w zEt?q6yT3PIc-Y}QZozFkhoUI)iv{*7TxF#^$*{0lFUK~<|enb4S*n0;}p2T(YNoVwxq^lZI#cyG6saw^v zcx$O;+pw`Mh-rDThrfQ>vvbQp8`vcOD0jvS)Ks}Y03zPZA#a9-jbhYH)S~W z$BaX^z9lOjh-7nm?VN+-;SqSw;mzEyC}Li4*sRO8g~8{N6g zL;~MqA*^jo1I?aCb-7LErh@`)B=V&-bBw?C#}fHw6XeTEm@tQ^@v23^3OLCbESG`> z+98=LqBbiVD=8PH2I0uOBz=ZO5S*)wW^X_Y(ph1wnUq?t4|&{hu2Y;7>QqC5NaZ$P z6iOv+xD|6Nz8HijG!{f^JD-F-9u{7WM%7qhH&9as!v|ZrPFcT57n({s*;)(Fu)Lp{ zg2rCKk^~%>t=uu9d^7*trG(1avI$|yrCQlpojNFk67Qp$$r-ZfM4p8sVjo&t@sbA} z$DqTn-<3GbranxSiXSa2qu1j#_*k@b6&ibq4QWmG*m;@y0VJ%!%VvSpErULeSO$I1 z$}-#U8PYN^J-vYvGh7_qQ%6Z-B2OBgsah7vM!Vl%ypYy3qKIq-A6@Ck>+>cL_bN_?gd%;v9{B$=P{GX#5=DI5a*Yc*tda6!m< zHyAtA%M7!QB^fbFII`=TkCHm0>wmENs9mO>d`sr9$aRWop{u41MR-27UHcXq*K+E@ zQZ!ynSSA4}bwvko-NRAL8$>K}lZ^{fH>fv- z`_L86Ae}k6OAw=32#3YctP)d;cP&%C1I0~qi2rl%Do%lYo~ysoiO@SteCC9dl}1r0 zF1jtg+I75Jy0C^r95!N6zVN`d(1+-$Beq+#u;zp`t5%#G#bPn3ahviTEUx>E;Jr6&6^d|`iI;4OV}~~O1wGyfzr}PG zIz+Hkiz8PTs{JzgmMHvoj2)((ZzahZ)`6Y}myEA=u>8zmyYY3L4=Lvb&Aa&XSiWrB3@7y+&V1ZfTKMX|&;0h}_~y*4x(d zVn&)InOyPw4|6Zp`IaedoG`+TeC>7@3)v~!D~mb%0`l3TU6p-Zo)uc9;pgc|9&hnU zc2QN(rUT~OfLbI=6}dTXdBCv=&m1w3s+t{r024*!#bXItmjD%e5*-@Zw+^+*5SuOZ zs(Fr`s>D)3<~20!2B!(g#ozh`3c6?G)cbsh%cm|5FYqoq9J_RWG70_ydEtPZT)}C%?zrtVqmJ-u|5sgYt*V z(FnPSkIrEfvhTPJp^;1Qw$n)NlHc!CsO@wFt8M};&gMIWrWH4or|Ex>+7e4xb$$6p_ZS0T)5PZs&#~w(FSAScmSvCTIA-5Gq&tbSA6h zZiDjb@`y)-A*+hAAN&SsDBQEFV z>8QSh7@Cyw-=2zS8ftqU2_~1#J{?^6x{2Da9aYTxgw%RDBe+XK;l_&W6Azd+Os9Be z3KUB<+!RY#Xp_}UQa0RvuC8sAE?Q8lSq6BptWFHH(VpO;x}4&iYO4B%x$0RBxQWK< z*p=c|LC^J7tUs6==%JWJ(1Sg~kr$1o`46ZhqYJf?>sF0G!?9x9QKv~I4hfGM##=WXYiiZ$G^*w%H4P7f%jlJpU@0b`NqFHT1EL$G zz;yV0kmIw=vCRqZd@2lgOi0EhTeAK7<#h5xM&n_Ae^^<$0dTFWAdJGcD7059Fi;wx zwJvMT5|j`EGC@T}-Jo(+-ZYr~6*Dv%|97@QC&ZxZdc1n=0e6GB=!QcuUFjVjGd{+8 zeZz<%nJzdv1Ln@Vg%-onWdjlXE0@$bpydrkqp}0-hP|yZzy&4Jv*r?UK-GJ4EzXNz zyZyfD^%At|hJA2!0MKHfr(TF(7Cj@+hXB9=bsg6AtZ!~5EK$|3PqW8w+- z1|C)=JZW6cg5(YcyNKBY_%ILS)tla$jBBzKRAx}v>Y zBkamq4?9;ii*?8_ApC5n9gH`p^H`3h{1x56@iz|mKz~`_NmX|3Xz7PLZ=A2wqf{@A>j+-DW0u@ftON_f1id_%&Bs#@G6HzLp zq#1crEedH!3F$x#!t$4YYi9dT#@lFJ{&(Rg(^dVU#1j37jQ1bbq_l;Tv&oN-;D6`6 zE^-~R1N;b?uw<^2LWPlu@PEk2Ezv{$m6It#=k+c@ttYb@e-D3)_JQ9jg#HqPfQ+Mc zRV_$c+nT9)h6h;TkaI}ikBmKQ??$c@RM$zivBRpt6Y06vKN7K7ea*Li%23T>le2uF zah>gmj;Np|gx_wmZP!h;cbfeS>2ir4VqX@fzDbF3pv>UXgopsvP+)b^jo7)k2j7qy zx`-7@6|1(+G#jW7XGc!{9{MncK<~-fy3wruNaRl?P>n~3jt%s)k30*dtVc9M3}Axy z_D`_;*W~<%AZm{u9w#-qFt4#K_se@c-Do{d%%n;ROQ&69U6?1(S6J zqZ0-D>!1G-zj%lw3T6sB#UKhsw-SE1=pR4%(P`)kW=p`w(bGu5xkSrMo)jlQ0FckY zNJ*|jPhCe77bn0~!9w34%vVLh$lk==L!pH)0VxkG9_J%#sN3G{tBAIWWe_MYE~?m+2`JtNqS~})||s14IbTp z80mklIsc=-`!87f|B%_E{!y>;qws7bg9$dGq8ti=($1ohpdbbkh0prS4q@_drRy(6IkB%Cs!quQmQo2p$X4_NmLDd+(!VClrgxkEm`Ax(OmL2%~v@<`k1NUu! zQRuPu2*V6Mgzb^jv*07|QiJumg#I0cJ|SqBEc4dTUpvQkT1}wn2@}UUf&f$JZxK;7 zKG$vk`?XsnIon_9a2fBnOj`{$F_SqMgE_uU1`Ex#yFbkt+ku4fDaIDB#=~YK6`ezI z0_X_s8EdCjTx0vy%dwxvVfWdp#}+e$8fWVtnZ~gE^22WQp*j{1sAI}U{%oeRq@X^A zbzxr}H<1+>20!Z-2NQia5B6N2Ufi6HZWQ_7nc-R}?ncd*F)NUnvJgTT(v;QAk?-*WLyKI4`W81ck zj?uAg+qP|WY}@vVZCf3i9ix+*-h1DDzH{$+o;822AN9^rHR~;mQ5Wh0$9ceJO(++$ zWGjKG=yvP~FvO6a!MG2~R|~Ds+N3{CjWEHqlZ$p#LjF({pdYKsjM<#ctInj(w44J+ zeGS1zBhN}&!nLY&!n{%{JC6w=koX?|)^o3LHFkq}q}8ml5QGvWS^*bvNikLVCBLF` zth_Dah}+5Q1e?9d=vZ?uA6uGv%(0R5z0J7)yJZ+=8yIz02hZA$V-(V;7&7G0euLPz zjBG;TnH~;J<^PjSM&R*A74yhLIt0cu1Gc ze4Dx!bRZ!l^<(Bu(qLP_pOYvK5$2!}{o5!tlGE~Ur8;hJIt zAxzZ6F{Lvp-%hX2=n0}JcT8$Re7=Tq$FDqBE7|RvY z1hIrR&7Ck(9_#8cqSBs5PRyP{e}=l*2uh}`?7N1&F^{9jLtPz&=bOR_Av67%bqTR^ z4dzeC@#>FD@Cl8as|$S($o%dGVak0|%$=99XMd_#>+sft&At2K-!!2#4y(BV^Czb* zE*^##Yc)EY%W+icNukjt&roQDyu}$+Ub(*72m)Yio}m@GXFRRqM(NqdQ1*udI#_7t zAVj$=Rz_l`lU(fE3-TtB-XRrZkSBr3_o*`Mg%q-%8n04wsiM@HKuyFZVW?O`lOH&% zEINv5`AUV5mh$0c3o-9N#|8VL>w%OGt|=G%l+ zcdXOucU;RP2$9?5ei1aAx`kEcRgp5)@0gH|S+$Upg{H~LDx!O%Bp+7~=)?X8 z0RLLlq^_oM_hr%Gmqlp*^``#-@P8YlY!qksJUs*i`ajmZfCSU}eY_iBf)(nY1c5-n zze6Z~VtoXaf9j(nQhpd480+sC&VHi;K7@jZ4S_AdWyZ%r$F)8}OmXC7G%!Gk@^45C z58W*UvFY5CV8+J4XltS7a65Ij)^>C+2z8qy_z5MaQ+kF|OVf_kHs};8YP+4D-d8U6 zb{RsksvgSYltW)YZ`|n=PPn)`- z#U}G-({aD}M~5!4M{6p1#FfJqn+$`u-NTffQgtmoJg@qb-t>^CRz64&+dod}ubXD3 z8+^cgx#`gVO1%DaO8;6Xbg}*4iIl(Z-+0JFCxcJ4o#4CFrKGe>l=9CZ3m~3Pc8b zfkXS8h-ik2HE%o7buP_d2bPa#g6y$8id>s2go_I_ha*!Ztuaky({A0ajT(%|XzJNq4ybC;mH98*uIsq6>vI<=+PDGEYOY)X%RXy9&bietl|wC+wpv@C zFIQ8>%vjVRdRKq&kGX$&e9W}p^#kO1g@mJCq{!LkwM?ATwo~S~$wfaY7W7W}zy0XzsKRVeBu7{%;z*qsO%My2R+6qbD(6zm zV8y8Nz~07Xbnlovbj-=WlMbqst|)Ya%_+XKPDHnFo_3OkzST}Vc8D2Ez3~nT;#wK+ zls%Sm>7Lw~kkK53k=7NzKEY)^m8?7Pgn!ezwjz6>XbkUzBUUTiTsF36X*v`EXGnQT zVD1jAWqqbjtUa=V?N+6%WoZ-HmO8AL9!;Gv59y;3X;I~D8{b(XpRSqnsP3~Nx7l^! zs%0l#10ZybFWv?%TnE+qSvE4=Mz|W@}s7;X_*a-li80;k;ey z6kXo%_WEdJ1K{3hv03eGu~IR^QOYy&yVq~x#Fv3(b>l8qH`J$68p<#xz7$58el+JX zV$t}crRds|-GI{Nno&N^o9JM8dqh`$5SQ&}=!$&|{0Ag^ZXny03lFc3`(n_)@n^(F zU!9-64(-)yxh=)I7myWqdh|!EaT5Xt^GoUwGLspYl0}&>cVY|qT9{U;>mahLnyGzl z_B#3&POHeOu^nqSZ${+Urq*JX4-=%yLOnkS`Bb?yu|a8un%KhQckWAT6{S&C@DRmV zS|*h+p1luc=UKiaX*~0knBp@>v3O;)QvHmCmTahHMAM;3qRH{F-&CcrsJPj96+LlO zr6q+Bmr#Sne+hJ`yp&tbQTo=UYnML&Z=Tr9vq= zmHlyJ4U7e{m%*ZUT?-MMUcvdf?aiX3vz^X%YtFTm?7U3I649Ntkt)mT_~B`^%~e2b z`@}VM84)B-NwkVC^P|Q4^?hP}-@%IzTW={!K>8R(M#9W6_HrCJpVz3IbD>GlVd&sY zp!D)W>hUnpBT)KkTXC!xYRuQzoOag?^|ubX?zh)dS01jUmbaE6@Jkhom6E-sn1>-k zSqNE3GGZqZe+SBB)GgIwaONP_ehI0L3+l6iu_{mvwpE0%5)4j~SrSPAICFe5=jMVS zHyQ1_YyVQ!Jl{o8L6>aK4-8as;CPb zym+a#B!B_U9+|d%nyJ!oq7(;-%PRyJurY@fmSk7 z>FuF)Kw8T?e+`=t!H4j^<3B~iGQ4-3PvB&j0rjOXbZ=5P+b&F zVMCY*!@}0=ouJ5SK~H|FJrf0OM=riTaa1CY@vRK;_;m!(z&%=&QdY|epy3g?YZTR+ zU%z$LfWt^N-$&KV&>@+7VXV@61ux+qXaHzlK|*>>{-XQD5-JF^XJn=}$<5nB?(#n`>C<(EXCGXJ4kFL>Y8< zoVzFX05AAa{KEudy=>$Oy8i0#P~9s9{Q3M~1%@7i!QM$%0$i+*n)hb+Rnxgm&r0tW z&owM5visKZ_fU@=SAnrye#3ijNny+Df99k&q2L1$xXj&|O>WIjW!&q>_GWJXp!4_U zww`?N&=J)GGXBnHaQwq_u0ZWXBaap6`x5D>`K?fc{-Ia}rcU$*YZ8p7L_(oaz+wF1 zk9=#t<$|}H9fscu**{~z=7o)yUJRlLcHh#EdJs@!$@IR-AJ$I^Q9xCT(eX`F(7qD! zTFuRzcqL-CA(ABYhpG&H6UWSMdS(g;xHFs`@0U&}Qf(On(@o^CgtG^Wk*u$-cI6_8 z)77zV+>j~J2=TI|Sx*Ac`9Qjv8OrRhl5v$)pAePm59}Z1$xyB;)NhiWv3jh{9eh`m zv3(As_0{e)f)_BYhW1W+X;?xoWv#pJ23{iA1egEQus3y>dhX z$h?%Is9luP1+EJrNB6Zzlg~_;4-+1KBKL+uvpbj*r5H;8XnQh=^aVlN1a)-Jty+zO zhuignxcn{hpo9M|=~dL$W@BR&-v}UYR({$1nmJ_=OgwG=v0D>qn zC|h#xS_~!O=0#Z*vR*v+y2WVFQ4?P3s#8>1-?tKJ)*O0s&SB>bP9*$T!u2`uByzF@n2a** z0gkRw=8E&U$vvpkw?l50E)wzpHTW>WRKB;HqYs!S-pjK_13RuKtOZ@=_zHQiTR1#& zdw%e76`IPMkMJggGX38ZLUY4vhSr|(a76~X_0k-V{?LW_=F~Bk-x{R}j*lOb`z_Dn ztYDK^YWt=D>^)E_PqB6$GKE#x^V@*Se0(RmlBIbAOVSdOgF?%WblbjXRbuM=iLoSb z1S6ryBF{qHN7^g=31pOi=ktqhEVo7?fJ44zS#If z#d(So-A=31#Cxir6V&q1HX0lW#q+clA=2oA(Q$@~hsd7N#`hMlu&ZUr=k)+!yY_Jm z-vsZFD&n|OA~}2lAKBB&b(x<(5R$2nV$4RLc<4C9K5`n1g0POhk;tczIeqcJZ|zcQ zq{Zw2RYo1f5zLtKU?`B$g+isw0*kB2sr}fV8E_{%%gD5k33oo_tT@3crZTfQD^aL` zg(xwhLY$tzH(=&Izxa)=|+b(>y5yjOYucI88+)hm+q@V!d$fwC&GSc9iO+pu^o zdth;CDXo~3PP6%u=Y@n$^BH$aWM>Ly(fBmM67FZOH%|lVq$1r<=fg*aA?jG&l-4mF zxPS?%+!8Pi%~dw(Z}tN=$CQf;_9HJY>q&kaqaQfdb}YQcLIoi^<_RdLoz7UY5Tnq4bI;x^a?tlJ24RMB#;-#3%SRgax$y1mwa%q^B49 zx$Q4}y!)vK+(t?sN;v1e`3eE)2baJ~j4b=(>Op)4Wk)d*K)omqQcEc62l_{^1Uvcz zlVtD*ea`l9OoFh|sR|d|8M62vAYKVQkkqieh%a_;y{GxL;&JK|Ua7+NqPud+YRuxw z<6qoCH4>($9r4=YZz^ruxv9=*?dLQnIQOWJY+Pc$?pOa{m||};UD4_&pm-cPtfQ1` zAe)cP&liSc8$UD{XfjAmEG1~Xl2&7uR2rKUYib~Rl!$KJ1q(oLL#C+?y1!s)qN!&W zv$-YzW<1TiN}BVcz!?^?$)|G1AGk0u-XpnrEUa61Q1(X=SV~;=gZ01XaJ++B5AX(j zOGgL01o9?23rRldx@YaALXS8^jhSP)r=wH{Ubh?AGyLHliA7fng%pk`@&3B30<$m3=o2c3 zAIo*)H}~J9=ONuxH2zWWBYCMjTcPKUMU`Ts+;YQG}X}9>*XNl)tJpk&Z|&zNPpN( z`N4rZp_Sf2k98pqy1#YX1>Sl1ug~-yvfX&#O-hp^%K*6rZ@1-nD%win@YP~2`CumSKRG@zEQ6p^# zQQ@BcdcA?(o&c#|#n8j0SgG^U^gsHR9ml8o-*ORU_O{Qog_LDJfj8r$%*vL_K)r$Q zYoVjYPr@Lv)!GJh^+9Lm;KWyOxToi?az-`Q7qCP{Vhbo%HXSH)O26z-zj*6IvtwE3cc1kSD&_AIV8Gb081U1^-7Gbj7W~QA7sueD}q2k?* zl3Gm1Zv|a-_nhOx-LvKt`^;k-Q!Ov-dBvJ4zlmCW)<5|!6Ow)Q-gxl?m$g0*kPv@B zUYX~?olKqaz}18{M^ui^H0BNARvfe|Pu-QpT9pM@onnh_jHk|n!_SfH3S@W2eRt}b zw+A`60L>-HFL*`k|4`THM25Vnm~Y0WyOB$(ae>TUIq}QW0;w35Ln^}NK^M;32Bs;~ z>*M)@Q7Ig{0(`iCqTm{;iaP;c#gu~?A(_c3%X_^XASn;2HxWd$ zv~g#sIVg3isViFP!C5`zPSSd`K2K)fjLM}@C`3i+!H@7l6*4VAA4_B%Rcg7!`Z1Ka zSd*^{0bd{-Jx?SpcYF_9CZF*W5M=yYGuQ{m)wjIp_1EV=%Hw}KN6z*{SO{O$WZ186 zHR*o~dM55)O~Jp#WT$_L`UsmC**O~gcX`YwVas+w05N3dXvEbfv5Cw^Nizc#ngbp& zppB*?IFOo1gs@rV8m|#fR!K{9qgTk`8!~D#48DJu;H0gpZFs@>yt|p}(FRN7zk5bG zyAg0O04YX?nLgwzsYbKJx87kP9_HQjle8PG3Ns3xDa>VveV*8c?Q}@`=ThOUCu8s2 zK6fmIcYlKgt*m)Yld}87r>uOwaKn%9J(!LT$gmWs8hE4zjGS=7q&|36#smdP`PClp zPQ&}Gl6%VE^zImuex!u^q?ZqB3=qcj7xI{z)2TWPE_gF98A3Lju69!;=A)B)WR1!5 zi*ue*d1bR3hwu!KC+tfL--hS!x`6NR7|mva-j=b)D7JI%tvNf(q}RE-KRP5t#Sw^F zqJO$PDKqP|opjXgwzUPZVaQl_nCnjs8ewRyny$8ch&wyone|92(Tl4Y#ZxYk%UHa0 z6uS*DA+TJ`-_`Y=6whuJlX^;kSL9dk=8AvSVKBi4+Xx7bl_UIfUCA7gnFIDGFmW3p)5aYq3T#OhHOc@ z|M^Ru)J`-?#k@TVPv_UdgNg>f-o+HETGU?3xaB{`tpCt7XO%Kr;c zKa89$T>n+J`(KP&*-A2YU!d~A0XSrEkkTnE2;^JX6S=Hs_;UnM=SND42Y|$kugRng z4>gndfqozzOTt0I+aHS&ZEK=e$P1HNdziW&O^r-P|DCzP6Cik-V5}<_*NU%ZmON3m zM^MFbtFzhZ4uAkIEq3rH#!mL5ri61 z$>X>OcxUTojhmn3ye?M3_s5^-w#AoDw=1e0!U*(wV}UYr-EVQEEULqtH@|4&nU|` z$ul#=1CT$?8U|rtkjkrB;QoS}&e|nSxKhvS8kOtap$lD;!ip#gGqCE33~pab(|TjZ=olZ4(4#+67B;t5y$}8!K(fs@xaVT)Rpb>2kd_}!#PH&$XVGSh znl%nS%9XZKCw4JEXBi;Td|mC|w@T;sFokPnuH9HATzLuGE@pQ(lLcA3`^Ut>U+~!9 zxDKCvfd}p@8WH|q;Q3qaRWfmQvHy3MD@5_%pxJM8Wg;iDp<*ox@dbJj5bm;vIx7Z) zq9PQQLL8|EfMKlQRIfDoMZt&@-hwjgn{Th8W_!2u16#3F@ z+cs9QGfAN!vB{yonD9i22gOES{s`)v8y_82-;GY6DDoI-lU!him|-C1WL0hd85Zn8 z60)s!OiRNd-yVAqwfd-q*ZOdv?2Sa^J=vIbx@C8bC;+KfQoiUFG0L32Rwc%lL8NJp za<e@>;G_RuxkQ7T38ujTonq`Z?yKfet0yy;A+cL$8f8~!$W2Hs+JF8JqPM>|(I2S{ zN&X5j7+(tNcMI`dX)m+E(Ls~KEc^shOg?Hs6<6ccs}Y%LF|IqcWpiiN zv{~L9He$cIck)hCbYVbkQ{9S!R7_wD|$esfsRR|{Iq#&TfX}WD3%<6 z8)zWMm3Oho0TrF*fIN_0=w9c~&$)EfO^Z;u0KH;)DZqMHp1((9PdXGKhO>1CbXOMm z_c@t0Wf4?|m~tC^SKfI69Pu2o2&^1WR`q`1u?&MG&BmcqV(%++^Wi+Z=hqd zgk>V@+Fth|88*;dZ|w3gm94#&!vhwt;LJ6yYc4u4L;LoAHrgfTunOGC9?HlweRd*A zw}utA<>+Tt5;OF=m)$v0$ug1QJ1>}?h*$9Kaaeno=>fJ#=oN3e410S#;htNx2oD|#B5)N)FwjB|La zY=mDfdMOWj4}O#!Mq;gox#_GPPw>*TVUi0t(Ho?t(lO(=^|c}`;CV{@bz`MLY1oe> z1um{TZt$G6Xz&$Vynfq4giO{FreF`^&F`Z_`)TNo;Oq_)qzO#8P@Yc)=w!o34*=}O zF^**GTQyV1@ci6ednezQx7xG5Z`q;ZOp&dIXHd(?Z37F;GZ-F?y`r0Bn_a4bshDhG zKVdz-qq7dR^Wdx`Ra{0ydu8xnZSJE?GJ+_oLnRF!~_B>4jKyayhyILAy8_i+#C_+=wV?zgUFe zdM^>@sC-Mg;#2ErhrM}d6XO!;Z<`q33Ai?CB<5CNwYDUBC8ktkF{wA!>h;ajyNxawdQ?5>aw%2YU&CIXTSt?eV^8Wq?Zui`p$6F$igK3E!buzQX+RLCKJXA{4!bGYq1On5F09pGDdP}Q z>=<4gRl-}bRp2X25o^{|HjCYM$i1oMGnvF_hjCYaURNhI&HHv5nCI^M4|RsW7}Y+D=GHPH_B>AR zcHQ-N!uD$N{o`$)!XJxAWj4qgHiRP*S}^TInEh(>peT?ksfZnKAUaTXGr6zo{%Eg_ z;vzyX8e1=e1_ zE=sJy64vWo$au|%DnojQ3PW?Fa(>lXj?y6mTzT_TQxTO$bpZkb*08K=O&WRa`B8$> z6ZR%D!(&A!l{r4FkYg*Oao!7ZbLcU&O_Ws>%zBfsXHfx$1ey?L>~4J|^N~ulfRT7^ z@#7@RB(~c@4Hz0bQ;i{#T)5pf6bE|QezI9VqqnWjQSM#Eaa`-{hz z_v7yf6kfNH$I11Ky3CLE?nk=I$g?T(R!i7KjJ_Nxu-_Ay#jSHfGof=r`D^hQL_G+9 zj3Fp1NTdnksAQXZ9SP=K!e>pHHw6#Wn4a+P^emWyQP)tpMqT4&1o+zdfN@DjSxuV1 zLk?l}J~gz-(iNo^W+1*eTmD$!w-o@fyH+02+iGlwc>@k1YPgT%9^h`YxPpuNjW!95g zq?4DOODr^lQ4=-um_m|B>15_rf>YB|P1nby(>xZM%F4nC2^%6L<9(nC1hw>$DbeM> z(bcqFNA7jgL>BD$U3I2QFRUch>RkgS+CSH3+7kzA`CK7;9c5@nn%n5ZE!uQP;C{H< zchT}84@z#TPp^r&c+-Xh@zHOvpRUsU!ZB&n2$ef&BMTpYpRQ&G(1{Ojlb^-lP|#F0}PXF!=C5I}DUAHnMQ{Xu^41c!mR3!6KAiyrgT z4iz(Tr}Yow!bczu!~uD(@jJ3FKthq0M(NfKCWuHweFG^dpE;wj#XX<(}=FG|a70q_3 z4sXZFkS7l|;&8d80VYe_DJ#lQy5vWL{x2+NShZK_bH*_T*Rnb&D!#I=EnwUNMgl7ESyHMC5qjsT{b48 zmpUTF4|#7dB^IVjJUm_$`lWreNO_CSY0$vORYw@i9v1z4J$f=*F; z`XFO*LjT0yklyGoYFP}Aj&^qh1VTfqu&+s)WR%!U6Bx z`T=o44rtqFjBv&_9&Lb7a+a>6WC1HLO5hEaQ`ebx$c(!Fxy_kFF&?1>so^s^ZajIJ zM3h#4VYmeMSk!P@)=W;%smF4Qvn@sw<_q`{T}d-Fg3W&;pko{j}C7GTTPk3c|k1|!vcnx@x!>Fg18Wf}dixDiibHbJ5_AYTNGuD9F{0^X zAZx+vCh-rNfZ}aW9IoKX5lJacyVi>u!Ty<~NI&QNN(T>5)NQnggZNl^H_WEx5Dk|2 z^u^5OhwHimRs@rBL-Nuo^N*qUYlW!I^|VZ$g-8>pEdkwZ_*2-mkLZD}n*73n2UDA% zx9(rK_@)$dv(FvI_KuCsnE0~6woUU`=uo$M(iI*R`taGR(-5+&*#U2fMRQZ`O5#fK z;^|B(+}UB9{rhD~?wcyI>xSCmKo4SfQHk0)(e#vk5$wUC%Z1mSwo~=~{J^TeZ!bmC zouQ`B9Jr?SB{-CF4%DYyw8ma-v|CfFR`N2OzGKDWZCV`R)35;Vg3J!fx9BNUA7UP0 zsKeqJWDTUpmkLpIEY^ZPvOpB-NPtKq5Coe^8N+TfJ(Gjbk@Px(DW_7n`Wv^vpXXDJ zI^m776(LpVyTg9iA0VBexTD=r_I+s^J7%c{V$m9c*9Qz6Tr{RX{FY_}xXUT0`633; z4bl;a*(dgTN)Bp^Fv&5@xpBSL#s88KyRbfTi$XT9G_1EtSVencDFUvCaAW~oezY$Vfv{d*c#_iSo$foXSSYiU;$GV$ejje=9g3d(+ zo{TeQ0AsuQkd8JKk{!xx)^wkx_-n4}Gw{NwV$RohHVI1=L$fNMG6 znFg>>qP zCLIpel^e6k5|7hMauPH~G)7_555is?(e~X&`i~!bfg?Gx-mFka~yd*`=KuBKrrS4D2@<+JBS@X>*Keh9YE9k)Slj= zN#j*3Kv$bf`63wMQuIkvdBhgBr?c_&VM3!$)52IVG~ubHl(V5SCz6$j?$_00X#sC%xqSX7NAtBHzj9lz(u-l#z~PBfwT#=q#S96~ z5fKU7M#9AiUTS1+u-+3Z3^!##x)2i510!Edk0n)^L4HM@>e#xR&jZ%Zw?M(>G6YF? zUVY-#X}p_}&f7AILHjz-RM>IAHG)p2_Hse3EEEX=p*hTdVYE?g!iFGw^Kh1si=2rhY&Rz>*gxQ)g z%klZ;mh<$sIm0sZ=X$L4Th#~*)MliYW*ycObUVWjN6FDYg4^^5--xcoce35(M*s(( zTMnHR-$0&D+-u+YNpY_em)=mGf%qs7gt+)F9}=k$^;Q<0yUyNT2EmTuYbeop+(img3= z4voFzqCcTLZ7bVPh^0VwN0wu=^Du`wpa9sNeI*U0F5cX+W_!=FxWASVbGdfbx(_YP z33x5>Sy);!+qW9={5GFxolFHdTH0Usq6>fE%*}R=xmx=*hMJs;f8_kZHr&{hkQ#Vb z*rlixSUK=p*->lvG!Oj zcyTW?U8u^k5HB8pQOe;o!gK55X4a_Ofp`LUjliGESy!$n(Xhx@b8dKqb(wQjup!&= zw@~WfW|=sTvG`%7a?DNVr1-_F;--QS#xwI^K5$lfM*40O>9^W+CqiA?C;xW2oRoam zu7ktXe6sN}8K;z0&$G~~W2R5aF_Ss%9GcUTZ2?xgw&>gM(%M09$58E*E<4dK;-ER% zQrga6l<~~SZ?HQ74wVLS@f+d znz$UZStMeT5@t`58d_Y#cFOk-wJ!{x3vT1mxm)kS5GzTx+gbzer3G!89jOWj$T zn8KWSddvHl`lHO3n6AuLR+jQusew)t9|+sd8cB|qwjs81`OFIPs!|hjugYP!MYg{uV<__KPiNqDJxSZ>~VFhT@&f9D*CN;jMzy z#!#E3Ao_FK^Fclkp;7GB&`WUe`lVjJ3wC>;S|)q>(damhPO8{ztY^LkY;-$pobfB1 zjYiHxKzY{rbBj{b!B%7R@}{{h-MUAz$Q1JHN77#jW^mj#aNl5pk=v|*+?`Tz-_YDf zWxD!>UZT$BUWS9`R7c!gY5;dUuc3$PS(}Eucu&EbqW5dM-7XFhHa7GhhxhefyPM8{ zUwdxYs=8zbaMA|ry$VEbgfr*7!JoVzgv=l2JKuS%cH{~!bK%~>{Mc^TdE{!k01}E8 zAbx7&rGZ7jXof=bo@I+SJEJGv_4&8c)3OTj*DzGyH+?HsTga2Cp9I%h9SQE|Ka_Dl z8E@p?CwhBs2?XJ8v5p#L2O?h+1B*3XKDQ$ZD|nrS*LW>4yeAYwuoSeM+o-oYNyxCb zf-#0Lt@MWNhZNGl6`Q-Y=s$-}N6+*W{G#P+&|)(W4=0fD`18|Rz-)3sq6@Wh zD`KBDA{`JTrgHa0D76vv`975K%|&@zeKqV@=43abK1@7z;FsVImeW!ITD zp%K!r9G_gEBziJR7O8{Gg5v!Q3)qex!>CST->akGB(@Y;BU7^9bSs5l?^cf*2~{X5X5M+t1STMBKDJ3}b$B3Gg3w;<4E5e%LZ}jFM@`Q=H0J_*!1glzWgUN}e1ou>sq-KRX zvYf&A1=Wx%f&^8mn5YC1^N35bv46?BYjj~7Nk%vr3k7VnrMkg*NzF=`$M+Z)1TR*_ zSw*c*SIr>CCi;OlULicE2YZ<^M+EzCn;=cRPzflMWLBgaT(!h; zA9S^>$o$fgQK|rT&o}eAiNRkCxtXY1vy+k5CiTj98)YZ@JCJP4i`273>O;;!wP)vpC;+GKXW9$FhO= z$aBSsPW%Rfd4zhq7gn{js;$CxGx4lICYB?T)9t5>7ZTZXM3fx6iY90*nXSy|mT5ec zqg<5WmP7Tsl$UXW9*Ao|vJi_bP1wD1StmHo_3_VLsf2i^`5eNtiAP?2(p+vSl`75x zSkMr%%9RoLj|CB6Lw$-bk#(D2`-a-q=<+{rBil^Term&MYk}3!S>t4Q+lJa>HA8Hi zd$3N2q}QA}$Ph(?BZS=irfe5U4gS3*Anc5%dA&?3S5P1g%ohpG8DPXwvwjV=i51aF z1>7!vV6-Ca%d%Y%cipcgbN~k(=9-mhbfLq>w?M7030YDPlSC97Dp98C^&4FR5Oh3e zPwAQ_WdGx}p74+?*_e7dTlGot_Kl-aR^j$-ZI7UAVQ<1wuHwz@^B<|l-|aQINm@rYs zCSOiV$k<{3K}Y|{({GUvhgS%q?+6iDQ^%EZKN}BMoL?09YSai;8s(U$PG)9KeHb%Dj zBD5*;+&@`=6;&2%S0DOh*esti$X(q*@bwZf995dMNU+Ogk`8oxyru=NZ$!Jx;IlU# z@|?Dvz-L0=j+A%G@fA%ZnVSlBrmv`Mq+OP?xjcxbXsSF?5RrZ=xy>s#@S3O7oD^-} z{Q~!7sfgQ}?nsCA9X-!ZsaW@*er~P2Q!1}iouhIztJ)}Y`>lNG9JDn&zUEZOR-GZ7 z*{Viso=i~0Yg7F(0u^QSooje1dlI3-b6K^8?b_G+>{v88)>%Eo#IluTaM68t#$fIx z4=3i%-kcB$`BRC#);8J2C`YDPlkQ1IXWp?Ko6fMl72`s_nx~?K?WJ5^h899waM(58 zjyvJ8nu5CEVg91n;!s1w5c)t2c9#`hzpl8sV^u1luwyY9o!U<3f!1N&{Zfmj^))Z@ zk8^Kwh=z$uE2F$_*62aJps@GHi~jEcx!-uFnMvhD-^)v@+)9C=LsV?K>}Kwbs>qZg zI%|BnRXDA7t=9(xYIb+Nk zw>5uPFO#~5<8jbqKP2r(eRQ6H+5AB=;1CxB>?PRy&5A?$KyM^VdUUvjkFEX6UT0Z0 z_dWeNEfT-_(`)=u0S&cRm$XI@(L4DJ=aJZy+`ph8&A*G6Q~Q!X-= zd0*-lciRg)s#i6yLoshV(N^?oD}&0HLk?cOU@5EEVfIrxI%P{Q(B>!V=uv@6gsbTBO< zzJ5=A*mSeWb!B=h5BaHBCFlaMl#r$ZNa?u7`XIz@?q5^a#@Ej8nKtNJ)-+L1{l%y8+k$ zZD7|If4>LnA#hb3`^FHpfqhxZDFK3Yfipwpz1||DtUw*r%yDB{V)@}i>Z5W-ODpJU zKbIoraCGMN79y4AtZC83L)G|?b4kz=sUviW^Dyu4K9mJqfekzG$xkx+6(2R3o9g56 zMIb#k+rgOvGJfDNrvNF*e0=V^r`vf zA8vG#M+5;3P?(u`7=ft{9E5y0%Izh)39^LW&zuGs(jspBGrim+Fj^L%?Dl(E@8vu4}e}kuzK1P1e+OP`xY9obui5-f2Cyq`5^ZsxT&~lxP7wr(V&=4eUBI~7`0}19F zMF7o(+0km_)CY1X%=m+gf%^}M_YO`ncZ~f1r?u+hjuq)Ds8~=`Y=9sl z9Yhf<5dua-f(fCBEuyICf*nQIin4Y^bQKFLxULQCy&_lu``YVwPC`N^cWwgyf1W1` zy59HnGiPSb%nb}Yk@DRAk9gaqlJ&no9XLiTc^ud${Zm`xxDjpZzj?ZC?Vf?t-LL-_ zeX!-CTW)+y(+2ukITnMzES%RYW4PV%FLgOX4|YgvTXKW*Z1(Z@_ZBCuE}E*Jm$1+) ziJQEs-sRi7qixUI#}%a|cFOKKb6ou>(~+M`3&%c=PI+6}=~eu|_lY^XTeqAzE0c3T zlsWVd`(P8ZC#|nt@8vc}(848Z-{upkyFwG{rS{!uR>1A|I%a3EUZnH+ccXedE85m> z;-ODb2Ho2Re(!MJYxmzS#}k?c*{^$hckYK}k`4)1y>A^Q#U zwvBTRn(P)dAb-$KXFey#a@I#T+Yp1juZv>i3tTeJxr$mGT^wV-dnYeDIw--cf%IU9 zVw;eEJvXjj;u!Gqa%hOm(E9w}4LiMUzH<7J1?jgvx4~;fM8#3FcAOd>8hQHU@$bjB z1P#jH5p9=ee(Gz@{i3cv=W<=T?VQ}MJd(R^x_!&C0p+Q063-l3(C}EjQQ{mIL5s83 z{u0W>&HL9f&puPUD|p59kX;@dhdkd) z%Zx79jl-NT{qC32G+@hm)8w8D_FvlJ?qgJx+|#*>VerwMAxkgaH4C`fv1e)6-yb%_ zw>BGc=9bMmy>DYh=@;F9OBuJI)VEVuOE`q^V&#m7oCL4HN6o*#5vKRK)$KrW5PyD{ zmv4f5jp^HLW(&CoQtY#8zPGwD?AAD=GGb9xBtckk$8_IXC8&+*Q8_jmI4w3#+eu=#RyZSK&=Wr2nV0v7q4nCR`| z@Y#3o6ia8Qxo0>WeP!!^IOL4&{$Bd&xto6bvO;*fr1P>fds53S&b1iFTPY}+cWvr_ zCRZLmdmGtg+yi}~;mF~AgOlw|mWPW&|(o4TToOa-7?8sPn zpUA@e+%4z6F7VjfFG$aIuV2P`W1~*iLf+eTM%tDlx?bBs}^q#luK zjz>n%Z1+rW$-BjS4_&N%&c0UW?DYAct;YU#-{gZ;IB)f|kc{?CkFT3|eZko@yWuZO z1b>8Qn>-HvC#_ZCwA*unXGXoZHLm@d`>F1=4Li;(on`o=_af21CansGe>zZe`KP`? zXPfPKIVjU2dc$95v+QcW*IPZHp#5yGqFz7kS3DdTbfJY)#@C{fBG(I({_LILI3nA8 zX8#fCt-pp0y%+WBTg}1?%g#M(F-y;`?MRQ4U!_C;NyxEjf9a-mzTNZ=-^%VaecHmg z*7v}dOFTO!96H-~`QebY7jM=W;~4zaYm?E{DTlW7>?C@B$$RmLb&bk)#~7p?OpAJ9 zXH^p5{B213>a0oTtA<$j>N2idY^_JvZycWzYx-^e_c75UlD!&!dUL<|i2hCvjXNH? z5Yqbd*hhsUju|-Jb4h&W-tk6ZuB2y=(LD|=Y_;OmB7RrPSpA;v7o*xqZZyC6qpagP zKZnovZyi}+vvzljPy42}eB#jl?6g}yGh_dp`$uHRmkqc5qzzv_GxuNgZr-V>slo)k zEf@B_`m%G)f`?_zKGqC1{Py^UVZ%2@hOb6E-DJ74cYs^`jiap>raXyW4r^=w<<~hb z9sjbl*_Up{vKse1k4cKJHJSa2e{}z~hjYiAXf@&0%lI8RWmg&*?JIk*`1rDfCgZNg z?CN>rBZ&=^|v(Nf%&7A zZw#vYdfnqgbr#(cU*D2)CZL0T=E2M-b&mBdI6m>W!L|JIEeB-fdjIS{w~gERznZx| zsBOBveM3*1*q1MZj$KU}?bW(oXzp&Yo=dH6FMgBW-uRpTrn36hcP9B>ceug-tmpqV zJ;!NjVZp?Z;n$a)8?B#q)Az-eHLrim_pDKL;@kDV^L*b0IgP(jKJ@hEiGzg4j}#pe zK06;<*z4y^>y;hDtsm$&$_p?rs%fv+D{M&CsI(oimKMX~j(n`K;b4QJ{&C4ME)O~t zopLoU4Vt{pX1&L$8@!zv-7X(}vZiy#dTxE29qHhe=y1&~ElbDQVQwe1{JbIBvgJeRjVv2Fd=x?DZD^)IXL0iV*`N3gY+|Qn+~3O>dM4vqntjED}NH)a#HTwnx^-MdrxufCAs1DNt{&J z*6cqA1N%c^gIuzV^*oBtwl$uaWcG4v6z|2$9Gg=c7Y6T;UFW9vdDg{*~t@B_Zh@LyJZGGra#H`QaF zj$XrDUHk*Odb(C3a5y(%{X_W#{)FbaiccfXcgOeeyGR_NMw7$A#v|pw!Ji5N@Hc$I z2)Z=!3HjQI*5_csrQ(Cn4I-WsO9ea$@icA`DgQnGgk7SF4?Z2K@w5=`ebN5BiSltd z*MCr-hj(OgBYCjn#GlFwBe>Dj0lYh);)C4Wp`t~K!gvA#MN_7xN)S0ujiHyZQj?L~ zXn13c4xw=IO7TH9@V3!PfQq|u)NPOVpeY}~pT?>`;Nw$hk4JKa{3xk_8x4oR2w5~W zLSpTrVF^Xi{BV9K6H8Msm0pc}l@eGnTme4}&!B1LhSY2u=pwFEjC@Z~$R$mqDGt6F zLh(TsJm>&bvQZl)kle^gbR>nbw0p2A0hFs9NOwTtc?rb_CH$csP7)1`w2?evbahyC zg4{w;yHZx&@j)6v#sbHiB83KDk}>V(JVHmBAmt0fbcLLeQfqQ^z9*1)aZ!*vP)!=% zrRm`K?s~-sb&qfEtJD(#BIZe?0u9+T!AJ*-U)?68o(coDkACr!MI9Zwk! z>WNyDIe5C_gZ@$mf2{6sYoMomCLG7OSdn-#;R~7pqpu_=_l5qdrzxX~CbQ(A6rp-l ztutR;R_&BmsvfWC45@syE(a+$i(9eqEiju5V{j`K(;QB=>;ybo=^D-gu0-MoWrm_T zS`#MhzeA-@3^|;0;9k&Nt@>ok%AiN35=Q;)Kbz}MSAy-7DdHkonYZC5QR>rOZJVk zuJ<*_?oB`r3j#6)GKVJQ5WWO2J@yt3;Z2Z6&=L3Np1l4R7!*PBvmy|;fY)mxy1*H7 zF$}lR(j$_JLs|4+ELppI6>!N1F3kzdJElxbJkc>yQ6MSL|EOycU263|XJYDu2edcP z)MPd&R+Gs9USt#;Z5PM8if{;E%8iiS>a@C*0>#Kk$YrE)YILDM!ayLBMq4;3Min}w zslyd3ys)_ya9UYGp}jBLG9mq>LiAt>6AOLEix)BVAYi=(_DDnD>%qiQsSqkWnhp{1 z&YRwTXTZ?$U>Fm^DA#&15#iohDDQY;8XBrEcdzM>jgY$DK!WH%pxw1+qPf6W8VZMZ zT;f7`QCNL4g+|aq*HiC}LFiQQMJEC@Z(tQbEf{3{m6Z;Y9;zhI-FG&YUeV}U1iitI zOnPW7=ZRcCg?R*<@$$u-LBKp#He?|6cVU9Lh{YnYvq&HSy@;q<#)}-1cD;#R4~&(; zm-MLIp-fEl0`{bMXmb4IJz)PaATWuQ?#9GY+MlktwC`ixw}YI<4Kx)u*PV%_%4~En zzl^Zy8$rO!@HK_6@{=tScxwS;^UN!?gTwa$Z~K5ZJXWLpWXn8!m~a7#z@~FH)h^eT z3+mbj{sA$f`ee)cj9@}Byw6jS)L3%{TKJUlw9{Jw7;ly}Q4zqL@q84MgeNb0q9}}R zV+^34KbkK321x6HY0{xD1~JhpWTz<-9eQm-$bwe@b_u{b60Fq=VS;In3~Z?X$O`(n zK$xUMVlyL92aaW;swXzOjgB6e{Hy>t+`||V^T#vs$gra$mK-rI8U@^Sfz)P7aCkY4 ziKya`Hqf9N#~ttA{xJyb1x^IRfa;Si<4RV8`oN#&jTCH z1p;JHuZd)WBd=aiFfy@_(a?a%-@O2*FUW4AViR4lzepVmswQcqJc|eNMX;ElHLWA? z-{WR%SqCQ4gLN@y0{F@lbzsJedn_vdM)j(5iy{2*aBwy;xAMj3>g56wY-%-$Y%l~gi}&c55KO<0SI1CR{Eh+vs+TgH<0~o-?;F<95zvML zTIE~{bt_Jwp($=rk^fg->#~&zPxhF8as^y-hW)=zqv0yv8Nswa^7!cwi_3+8wGlK& z4q($~(6D^CVwfC583W68v}i}n!q39iuONWbdW~hM(E8vrB2%z;ES(poQvB6pLYEmep;s?F}H?Ly==ippRWnLsz6` ze~}oXmI8^&Flv7D-vVIxI77ip(`b-{f_u=F5G!8R2QOE#W>g)USL?*=Nykh+|9#JV zz-$kE4kkpuw1x#!O|c^+yh`0FyaV2c1Vv6fo!YG;$FUtj0JX9OA%WipU6^#lNmizlu7ge%gHA|i+q$2INOxqmFyR#Jlk@=e7N7@m zj5vFMhOSaDu5DMkGM)A4TgFeH-(@d& zqFsFsrw>7DD!i&geV(RR<0nbUhf;p@#7dHMfi5lRGa>;f6d7?i?gW)t$8=MnTV|vV zx;U&X_OY+A43u~fe888W8+!WxhptxTUio9?T|dZ1V&^HAshFo_GKUG&@xsB3b%0#}Uzky=KH0L8 zYwR=>PUa80C1Eh#qGWz#f5^)X+zF5_QuH~Tfhu6k`~~3XOgB@o?=>5egb=7F*JL z?63;$uudp&kl}u^DFJujgoC4!{Pr$p2X+*~ehydd6BEy3If3Tg=o(S} zR?llTfhi%3Go(T?U$H~0JdUYl1ohqda7{1Zw+s|wuTlqU`DKk7tRbTUwU8T7QI1#M z8L83uU#G}s);QpQGQ~W zz@O$TK({#wTIx<{>4qUYm0@tufDc(@s(dq4KE-4Tflgx(_#qbOzLx-=9Ojdnu)}NU zhn7(gytK@xJPFo3PPf%8SX(xyfvZMYtRa~EVxAS>9GEx;_}u~gniA?LGEs-DoTgO_ z4RpBmL4J!KK@RB&tGHyPH{DzvPPG#&p9oZ}qOjD|0q*xYW7onW_`XR+o+(8`^ zJ;B0DB&5vtFg)UKX57CG4@U!xg$e}2<4s3(7_qztvUTMPpv>1gTF;wb)YuV{!yk<_ zM{9j+3ZmkSEK_gDTym{fXGQ^IA~3};g1H6VC{S{(fk|L3ILw1D#Gy{YQ{dx-n)qum zi?`wqh|NdCLG{7%>mcQm=5m8tRdbZJt)M`@0!ckXpeT)+UrF=@Jls-&+FbR?maXhf z!PFuOQ&q_Gg^|8H=L2dse9ctoXh-|O6#OAPNvN1Vq4Ld;>il8^ED;!b=7A0_!Z1zh z;3o}F-7jEEmCobyL^J>8kjBnH^+FD%bznNnrdNNcTrkJ*!dyk-NIJ4pV##a|aFqVw zC}f3LJe-CscV;FO3f|Xr<4*`6PCbAk7nUY@(xI?|W@4dHE{Q%~Yy?=F;Mx1*MhPxfXEL?v|A0OTNsE?xxbLRKt~Fm-{rr*E6d&Vh$)gAsz{xaOFu;nGG%M80rBuyPMr ztudf?C9G#OP8YJmdhDKIoU)hm4siQ0^xR~8)v#$?V+6WCc2D^abi5g+dnN?eJ`*U& z6{!-AuZQC1xOyVP#9Js{KFJ&Si~!4#QzjXof~KCx;-UD%;m%7G?JSnx!KWn-u@br4 z|LTpk!0|cwlDiO{CsSxu3>@9*j3157zf%j;&1h>o=`V8ii!padIm zF*1yYh$&cv66EC)C8MKq>)Y9f;L;6liJ20n8UO_aBco%vBn5@iERS%x-edMc!r}-4 z(u9Cp8bg8eQ}j&H(9jwoygqaLfWW6giR7}rNgM@@=<8!G=P<1L{xdOmAAp{Qxe8g$ zZcNYx3=>&!C;1LD(^Lq))K!@N8FVlYR7zIj>t|O3oTe0nZ)+E|_&vZ&8Sqc%R0p1t zeG$If$@2CHk9)x+_crsY1J9Czi_OF4U}K3XI0!kQiWXD>URk191Wbf@4)f=RQ<4UX z@pjbYu4jP8RIoBB#(U_{VfZ!SI%*cSpsdp3Bz^Vc2D3~-Lx0!ja2yCf*H6&}?;kG= zohTLwu^<9cN|ET!6xnRkR|6ol1S-$|1fh9LbrDjR12v`HZN2v$PWuX$*~l>btCiKE z)Qc$y#`4CG11`4!_-*hd4c~XQE_mEojN*kk#lr%{0!ZNecN6?BC_oDIfvUAdPa(e?Og+YB5F&G79nF_8C7t zKs^s%D?*6SY&voUAdfx;LCp2RBiG}eeh@4@I1`w=5705Gicwo8)gi>ZHeSJapYdX_ z1X*uI9H|CD#j=$ST0wSGFKh-{JsDP)9yVhxb8BlIXap^KCX znswa-EhXI4xv(P$a{#PGt|4Z?o0lucoEYV@y=|qXR1}yli_jwRpWC(p(_i6B?%^4o z)&;E=9h5>9(UUhGi&zP&nF*>PXBh_2cBh^3N_0Bs|Crh)XMj?iK`A8mYlspWwvTjz zfFD{FlTP2z=)h*s$bI;l!&mvqmOZ?nj*XdzE9Of?!iuVpR$Jk)zQ1P9mgW$ zFHT-m2OJh56w#(DsLZ6GlP#YECo;fjqvQyFX3O+1t7ACv!U01rCS7yM+lSS73J=CI znCWqa3B<&}QCD*9%$q29ei~Q+R<>22Y? zHFX3>Y$j%r29xe)*Y`3jz~BHUew-q6c#EkGu&CnoDdz(0z@J(-)NF2x zzNwDIw0q^>$bX~v;ovx*-tT-Fu19$TyJWz9y{Aq>)zj}12b}_H@M1pbgU_Wabq+Va z!6^v1%}~|#C6v120To@ft_*drC>H1Ro;-6PC4H428;9h-$J|G^p5~bLQeWgg;Z}NV)`R;S$}Y9Q6X^G5CY4B;PO_@nlvrQSBCuKC2;18iHJrk z8dUE?H0bm>xS$V#SXzsUD3&iAOKj!HJ^RQL7L@(i2j(r1zo9|?SB_y^D13zx>?%Te zxN~OsW`QiRpeC}UI9FFUbvt$9l#v-j%q;amJt57a*)`2<0tls1+PX3k3nDRnT?r8FS@fG#t8I$hlr`I9y8ccBNIsc&fk_ zn}ij+%v!1~q`{wHIkEvJw|x~zSEv+bO)UFoDBiBSdk>tX zir3w&3N$DN1jSa0haC|7P^NfDdd11~kG>F3O<-lkiO}qL%U>j|wdv%MS*OXnfF3s7 zjw9$b?(~cF)J27TTZOdVVn=~QR1xyD(_bjO>{_E?cR}wXz^#T8%s6!UMP}sP4?6uh zDYkPM5}pWz$vir%+bN_5Y|me$j>L3ikIU8r2W0`Z zCP0~7T62STlV8bFv}pyJ_42HVWjye)Cs2otAmj^x4&Yy+1g}69z?4Q9A@?ZlSp!+{ ztee{NBB<9N4D3y?(7E3)vY_r+?DoZh=3UszGHhzk0r|;Mj6Xmtabgss43Q{V+Zahd zKai?kW6hnB4X}%-MY+hcTzkw!giH^g=gbs=bAv@3t1IDhSLmMSeI8X>h%K?A4T}MB{30t6F4yM)L4*M7Niv)Ygt%t zmCsO_(8zlC+-p}4f*;ifXtMTi7OWLoqCD3ou|+E0LLEnW4)qA@f;&pq;JoBS%siwz zgy>@Y6+UImeK7ELn39qcu@lfeq{RYkVlc^|A!>iiDz5|M^&|*@UPSbLm{e6V;7Ia0 zHf19Z4RWW2)9JrKN0-56$+Ex;ns2Hm36N0OoI(S|xcans;cSTGE)Z^{(I<$jN<(qf zOv@rZ3KMo)SWo~)j|5_5%iMsNsuCI|j1f(SI+EU)hg529+MK!rD6D~@-;uD&cv#l1 zn#xEYX00@k+zx)k*?~ar2565Q+$T-@HFC~efdJP6u)>hviox_gB-|`B?@%nb#szQ< za+=b3mR7=yG(($7U=DT<{?ytH#2RO)8Kk#oYo(wtkCuTNCwY0{Q3E5uM*grQM|Q+U zC2K`zPV(pq`rUc+QxRCEF^JHaaD}MlJ~#rD;W0 zANiO}qt3HRx2A*moR$o$3N~tmu2Rn@GQYwjvHmKafbhX5XqRN%eJ(;|t zMGrS?o46AcT?=BI%!jSEYei;SlvZ;g^C?Mn%pU$1u2aNDI88q2G@ej>o6r#pB8tW-1EFoZoskJrg820s{xR^3`ci z709@7LnmUH!@l#2M7HhXtXc)KyarhY5$-&4xCIw1X>LRzZr6z0+;2$8Uh$5wOWZr0x;z z*w{LTl^`2By6b`NqP!$Pt_2HOWR4CwMn$jIQC3`nYjP5IWU^*&78^?zgmdT}0cw${@)ASPTXhNvC=Xd5eNJL3#6(VuLG85fxf` zZTHMxvwSJ=s|Wm&(d>6sI|W5;p|xQ0ws3LdK$_?TF+om`x?k50Ua_wVQy8{OPcY{8 zrO(gF0p_ECc`~A9ceLYIsD~9DmA`WGRnIn{{MMj+QZ)zb#B13?!Z*`(;=!nwzhXS0^Yp@X3Fy-SuS;S|)uOhQz6qLKr3m41-aPI{CB!3U$B+WJFtjW2J!2w{TUC0LLk| z&RNt?;?+|&)(`~KAfm}4Y(ChXg?+q9&yySH28M-0>K1U+K)%YZbK|O1Jo@|!Sa?c? zb4MMC^kG=LmJVXhtep=tcq0?G16nzJ$uv=BpbJ$g0?WAgnlHQN2CC=542|4TdSF;p zX!&}dyc2|e3=pkQN9w-{FS@{XXlp_-Gn?w7gNk9x5S{O5#Gi!>nhr9MQz--RDDBo& z;L?z>VA{E%m}3jbkKs#d#;m1QWM`375C#(iDD%RQgpMVdrdQs7C;-P#G1M%~wzbkq zr_w*^Q8k#8T+;;9vJOm3dQC3;HH&IAqlyWQx(Tq$zuFqy{4a2Gav90mf)!eIU8B5h z9&h3G=SK*+(Qs^>Nd?nF+Y-m(ui!JoK>)IIF}X2))5@sTO4v zKvLqBnNx!xzlmWbgj|4o-TfCyF=fIy;C+0OZvq~Li*TgBZt7P>LX1uQ;Uz2I^`ToDf7z-500zf)MHwA6z zs4RyU8!XW(e?f!~Ci0$@t8Vnl3sW)2!Wnd-f5jE5bnklS`KVJLkOgOtWL!=e_AAtw zR8Xeau!O@iFpoFXjLVn25UY9<-}eZBE}2hHOsNJr z7ojwgJ!sJ>-s(HP=mvT z2#zA?TK}$_E+sF*EdYwDRem21?0U%qQA_D650N%SS?<6^0&qbtqMN5yfeU5YWDgJY zpDv4s?uSiK&RnQ#TnJI~;NmToyry=CH^?B6hQW4#@>XxS5?1~<5=S8=O(V(Ldnfo&KVd$yiYpG7QO>WH2bYg$)jKgZwfQ@{1#(z^t^Y zkWtfI#jo*-9RoT!lPf|897_j*&725wL7S>cjuep1SoM#&;>Ax(U@?;{5$a@AgI>Ir zy;Agv`E}Mi)7|hmoKDy6h76y8%RF_sp|8=v>s>&T(Aud{nJ(K|6_QG8G3|r`nWg$> zgMv7wFzq2{R|c@!qgx;)I&mVJPRhRUOkEQoBWbF+-mITh4N}zGGtk4WosOJ^%Pc4^ zH$A6%_)4QFg`>+e!~934EvSwUr2#p!?s$}yp0X9!1 zJ`|>TO`!$MM8yl9QL#Kv$6{ON!;PKL&!@Y?swc-6)MiScZNEZCt5{!US^=#-b*E=a z96;$c(%dGybDasN*cL%?SJpc)qDw z=keamoggARY{;8;Ywb-U!18+WV86f zr<-@6F05g$dE(Ten2C#Gv)m+zhFQDiL^|HwITQw+PJ|Bq-!j3}_8D;vk$!RrVapre zT`>hVgH8{fKCTcYXbCXJtkFaa!nRq^8SaJ$wxnKu% z`Q^Gr?gbFL1Y(@*IS7V>`-=CYR3@@Yu`A+RZf{x~p}*f47}$3Ouz3i+`^0w{sWwofYTg)DnHq>lg%`6Xb>pUn$_GN4L~F~ zS*zg{Wz97p}}8)xo7hP_Kg^bj`Hpz zWRo>hqZ*@L$DAqyRPcO`iP8-4Gh1ffg@P)_<3&rO=sb@9cgI|q6L5^6in1cmhTBrm zG1Y9!wEqYQgRUFb{3MIaymvTD`EOQGa23xB>*)q3Y6g+6;mR}r% zxJ(AobDT?j@|VCEQV$m8p_oy9vSki%wUWXx6)z1!5NNWcdJXb+%OECf0O)RP6HROA zIcq>E#&h|;C9O!nDDytwd2}FD9Z@h0foH2e*|OQgYjZq?IlFlIyL6E=*flyX+GjX! TY(h;@!?}i$7z!3V8^-w`q)kqw diff --git a/build.xml b/build.xml index 5cf6c1b8..a4bc51b1 100644 --- a/build.xml +++ b/build.xml @@ -14,10 +14,14 @@ - + + + - + + + diff --git a/pom.xml b/pom.xml index f9e62a0c..d55f9fcf 100755 --- a/pom.xml +++ b/pom.xml @@ -98,9 +98,9 @@ 1.6 - 2.6.5 + 2.9.4 4.12 - 0.2 + 0.9 2.3.1 @@ -115,14 +115,14 @@ ${jackson.version} false + + junit junit ${junit.version} test - - org.openjdk.jol jol-core diff --git a/src/main/java/com/esri/core/geometry/CombineOperator.java b/src/main/java/com/esri/core/geometry/CombineOperator.java index 43e5ac2c..e3d1bcc1 100644 --- a/src/main/java/com/esri/core/geometry/CombineOperator.java +++ b/src/main/java/com/esri/core/geometry/CombineOperator.java @@ -36,7 +36,8 @@ public interface CombineOperator { * Operation on two geometries, returning a third. Examples include * Intersection, Difference, and so forth. * - * @param geom1 and geom2 are the geometry instances to be operated on. + * @param geom1 is the geometry instance to be operated on. + * @param geom2 is the geometry instance to be operated on. * @param sr The spatial reference to get the tolerance value from. * When sr is null, the tolerance is calculated from the input geometries. * @param progressTracker ProgressTracker instance that is used to cancel the lengthy operation. Can be null. diff --git a/src/main/java/com/esri/core/geometry/Envelope.java b/src/main/java/com/esri/core/geometry/Envelope.java index ca884b55..98c46738 100644 --- a/src/main/java/com/esri/core/geometry/Envelope.java +++ b/src/main/java/com/esri/core/geometry/Envelope.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2015 Esri + Copyright 1995-2018 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -1118,28 +1118,28 @@ public void setYMax(double y) { m_envelope.ymax = y; } - @Override - public Geometry getBoundary() { - return Boundary.calculate(this, null); - } - - @Override - public void replaceNaNs(int semantics, double value) { - addAttribute(semantics); - if (isEmpty()) - return; - - int ncomps = VertexDescription.getComponentCount(semantics); - for (int i = 0; i < ncomps; i++) { - Envelope1D interval = queryInterval(semantics, i); - if (interval.isEmpty()) { - interval.vmin = value; - interval.vmax = value; - setInterval(semantics, i, interval); - } - } - } - + @Override + public Geometry getBoundary() { + return Boundary.calculate(this, null); + } + + @Override + public void replaceNaNs(int semantics, double value) { + addAttribute(semantics); + if (isEmpty()) + return; + + int ncomps = VertexDescription.getComponentCount(semantics); + for (int i = 0; i < ncomps; i++) { + Envelope1D interval = queryInterval(semantics, i); + if (interval.isEmpty()) { + interval.vmin = value; + interval.vmax = value; + setInterval(semantics, i, interval); + } + } + } + /** * The output of this method can be only used for debugging. It is subject to change without notice. */ diff --git a/src/main/java/com/esri/core/geometry/Envelope1D.java b/src/main/java/com/esri/core/geometry/Envelope1D.java index 96540895..c9d0d259 100644 --- a/src/main/java/com/esri/core/geometry/Envelope1D.java +++ b/src/main/java/com/esri/core/geometry/Envelope1D.java @@ -133,8 +133,10 @@ public boolean contains(double v) { /** * Returns True if the envelope contains the other envelope (boundary * inclusive). Note: Will return false if either envelope is empty. + * @param other The other envelope. + * @return Return true if this contains the other. */ - public boolean contains(/* const */Envelope1D other) /* const */ + public boolean contains(Envelope1D other) { return other.vmin >= vmin && other.vmax <= vmax; } diff --git a/src/main/java/com/esri/core/geometry/Envelope2D.java b/src/main/java/com/esri/core/geometry/Envelope2D.java index fa41db68..8e44dd33 100644 --- a/src/main/java/com/esri/core/geometry/Envelope2D.java +++ b/src/main/java/com/esri/core/geometry/Envelope2D.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2015 Esri + Copyright 1995-2018 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -131,6 +131,7 @@ public Envelope2D getInflated(double dx, double dy) { /** * Sets the envelope from the array of points. The envelope will be set to * empty if the array is null. + * @param points The points to set the envelope from. No element in the array can be null. */ public void setFromPoints(Point2D[] points) { if (points == null || points.length == 0) { @@ -198,6 +199,8 @@ else if (ymax < y) /** * Merges a point with this envelope without checking if the envelope is * empty. Use with care. + * @param x The x coord of the point + * @param y the y coord in the point */ public void mergeNE(double x, double y) { if (xmin > x) @@ -258,6 +261,7 @@ public void zoom(double factorX, double factorY) { /** * Checks if this envelope intersects the other. + * @param other The other envelope. * @return True if this envelope intersects the other. */ public boolean isIntersecting(Envelope2D other) { @@ -274,6 +278,7 @@ public boolean isIntersecting(Envelope2D other) { /** * Checks if this envelope intersects the other assuming neither one is empty. + * @param other The other envelope. * @return True if this envelope intersects the other. Assumes this and * other envelopes are not empty. */ @@ -289,6 +294,10 @@ public boolean isIntersectingNE(Envelope2D other) { /** * Checks if this envelope intersects the other. + * @param xmin_ + * @param ymin_ + * @param xmax_ + * @param ymax_ * @return True if this envelope intersects the other. */ public boolean isIntersecting(double xmin_, double ymin_, double xmax_, double ymax_) { @@ -307,7 +316,7 @@ public boolean isIntersecting(double xmin_, double ymin_, double xmax_, double y /** * Intersects this envelope with the other and stores result in this * envelope. - * + * @param other The other envelope. * @return True if this envelope intersects the other, otherwise sets this * envelope to empty state and returns False. */ @@ -370,6 +379,7 @@ public Point2D queryCorner(int index) { /** * Queries corners into a given array. The array length must be at least * 4. Starts from the lower left corner and goes clockwise. + * @param corners The array of four points. */ public void queryCorners(Point2D[] corners) { if ((corners == null) || (corners.length < 4)) @@ -399,6 +409,7 @@ public void queryCorners(Point2D[] corners) { * Queries corners into a given array in reversed order. The array length * must be at least 4. Starts from the lower left corner and goes * counterclockwise. + * @param corners The array of four points. */ public void queryCornersReversed(Point2D[] corners) { if (corners == null || ((corners != null) && (corners.length < 4))) @@ -500,6 +511,8 @@ public double getHeight() { /** * Moves the Envelope by given distance. + * @param dx + * @param dy */ public void move(double dx, double dy) { if (isEmpty()) @@ -558,6 +571,7 @@ public void queryUpperRight(Point2D pt) { /** * Returns True if this envelope is valid (empty, or has xmin less or equal * to xmax, or ymin less or equal to ymax). + * @return True if the envelope is valid. */ public boolean isValid() { return isEmpty() || (xmin <= xmax && ymin <= ymax); @@ -621,6 +635,8 @@ public boolean contains(double x, double y) { /** * Returns True if the envelope contains the other envelope (boundary * inclusive). + * @param other The other envelope. + * @return True if this contains the other. */ public boolean contains(Envelope2D other) {// Note: Will return False, if // either envelope is empty. @@ -630,7 +646,10 @@ public boolean contains(Envelope2D other) {// Note: Will return False, if /** * Returns True if the envelope contains the point (boundary exclusive). - */ + * @param x + * @param y + * @return True if this contains the point. + * */ public boolean containsExclusive(double x, double y) { // Note: This will return False, if envelope is empty, thus no need to // call is_empty(). @@ -647,6 +666,8 @@ public boolean containsExclusive(Point2D pt) { /** * Returns True if the envelope contains the other envelope (boundary * exclusive). + * @param other The other envelope + * @return True if this contains the other, boundary exclusive. */ boolean containsExclusive(Envelope2D other) { // Note: This will return False, if either envelope is empty, thus no @@ -1075,8 +1096,10 @@ public boolean isPointOnBoundary(Point2D pt, double tolerance) { /** * Calculates minimum distance from this envelope to the other. * Returns 0 for empty envelopes. + * @param other The other envelope. + * @return Returns the distance */ - public double distance(/* const */Envelope2D other) + public double distance(Envelope2D other) { return Math.sqrt(sqrDistance(other)); } @@ -1084,6 +1107,8 @@ public double distance(/* const */Envelope2D other) /** * Calculates minimum distance from this envelope to the point. * Returns 0 for empty envelopes. + * @param pt2D The other point. + * @return Returns the distance */ public double distance(Point2D pt2D) { @@ -1093,6 +1118,8 @@ public double distance(Point2D pt2D) /** * Calculates minimum squared distance from this envelope to the other. * Returns 0 for empty envelopes. + * @param other The other envelope. + * @return Returns the squared distance */ public double sqrDistance(Envelope2D other) { @@ -1122,6 +1149,11 @@ public double sqrDistance(Envelope2D other) /** * Calculates minimum squared distance from this envelope to the other. * Returns 0 for empty envelopes. + * @param xmin_ + * @param ymin_ + * @param xmax_ + * @param ymax_ + * @return Returns the squared distance. */ public double sqrDistance(double xmin_, double ymin_, double xmax_, double ymax_) { @@ -1178,6 +1210,8 @@ public double sqrMaxDistance(Envelope2D other) { /** * Calculates minimum squared distance from this envelope to the point. * Returns 0 for empty envelopes. + * @param pt2D The point. + * @return Returns the squared distance */ public double sqrDistance(Point2D pt2D) { diff --git a/src/main/java/com/esri/core/geometry/Geometry.java b/src/main/java/com/esri/core/geometry/Geometry.java index 01614b14..8a71a236 100644 --- a/src/main/java/com/esri/core/geometry/Geometry.java +++ b/src/main/java/com/esri/core/geometry/Geometry.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2015 Esri + Copyright 1995-2018 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -167,7 +167,8 @@ protected static long estimateMemorySize(double[] attributes) } /** - * Returns the VertexDescription of this geomtry. + * Returns the VertexDescription of this geometry. + * @return VertexDescription */ public VertexDescription getDescription() { return m_description; @@ -176,6 +177,7 @@ public VertexDescription getDescription() { /** * Assigns the new VertexDescription by adding or dropping attributes. The * Geometry will have the src description as a result. + * @param src VertexDescription to assign. */ public void assignVertexDescription(VertexDescription src) { _touch(); @@ -191,6 +193,7 @@ public void assignVertexDescription(VertexDescription src) { * Merges the new VertexDescription by adding missing attributes from the * src. The Geometry will have a union of the current and the src * descriptions. + * @param src VertexDescription to merge. */ public void mergeVertexDescription(VertexDescription src) { _touch(); @@ -207,6 +210,8 @@ public void mergeVertexDescription(VertexDescription src) { /** * A shortcut for getDescription().hasAttribute() + * @param semantics The VertexDescription.Semantics to check. + * @return Return true if the attribute is present. */ public boolean hasAttribute(int semantics) { return getDescription().hasAttribute(semantics); @@ -215,7 +220,7 @@ public boolean hasAttribute(int semantics) { /** * Adds a new attribute to the Geometry. * - * @param semantics + * @param semantics The VertexDescription.Semantics to add. */ public void addAttribute(int semantics) { _touch(); @@ -231,6 +236,7 @@ public void addAttribute(int semantics) { * equivalent to setting the attribute to the default value for each vertex, * However, it is faster and the result Geometry has smaller memory * footprint and smaller size when persisted. + * @param semantics The VertexDescription.Semantics to drop. */ public void dropAttribute(int semantics) { _touch(); @@ -250,7 +256,10 @@ public void dropAllAttributes() { } /** - * Returns the min and max attribute values at the ordinate of the Geometry + * Returns the min and max attribute values at the ordinate of the Geometry. + * @param semantics The semantics of the interval. + * @param ordinate The ordinate of the interval. + * @return The interval. */ public abstract Envelope1D queryInterval(int semantics, int ordinate); @@ -262,19 +271,17 @@ public void dropAllAttributes() { */ public abstract void queryEnvelope(Envelope env); - // { - // Envelope2D e2d = new Envelope2D(); - // queryEnvelope2D(e2d); - // env.setEnvelope2D(e2d); - // } - /** * Returns tight bbox of the Geometry in X, Y plane. + * @param env + * The envelope to return the result in. */ public abstract void queryEnvelope2D(Envelope2D env); /** * Returns tight bbox of the Geometry in 3D. + * @param env + * The envelope to return the result in. */ abstract void queryEnvelope3D(Envelope3D env); @@ -282,6 +289,8 @@ public void dropAllAttributes() { * Returns the conservative bbox of the Geometry in X, Y plane. This is a * faster method than QueryEnvelope2D. However, the bbox could be larger * than the tight box. + * @param env + * The envelope to return the result in. */ public void queryLooseEnvelope2D(Envelope2D env) { queryEnvelope2D(env); @@ -291,6 +300,8 @@ public void queryLooseEnvelope2D(Envelope2D env) { * Returns tight conservative box of the Geometry in 3D. This is a faster * method than the QueryEnvelope3D. However, the box could be larger than * the tight box. + * @param env + * The envelope to return the result in. */ void queryLooseEnvelope3D(Envelope3D env) { queryEnvelope3D(env); @@ -328,13 +339,14 @@ void queryLooseEnvelope3D(Envelope3D env) { /** * Creates an instance of an empty geometry of the same type. + * @return The new instance. */ public abstract Geometry createInstance(); /** * Copies this geometry to another geometry of the same type. The result * geometry is an exact copy. - * + * @param dst The geometry instance to copy to. * @exception GeometryException * invalid_argument if the geometry is of different type. */ @@ -525,20 +537,22 @@ public Geometry copy() { return geom; } - /** - * Returns boundary of this geometry. - * - * Polygon and Envelope boundary is a Polyline. For Polyline and Line, the - * boundary is a Multi_point consisting of path endpoints. For Multi_point - * and Point NULL is returned. - */ - public abstract Geometry getBoundary(); - + /** + * Returns boundary of this geometry. + * + * Polygon and Envelope boundary is a Polyline. For Polyline and Line, the + * boundary is a Multi_point consisting of path end points. For Multi_point and + * Point null is returned. + * @return The boundary geometry. + */ + public abstract Geometry getBoundary(); + /** * Replaces NaNs in the attribute with the given value. * If the geometry is not empty, it adds the attribute if geometry does not have it yet, and replaces the values. * If the geometry is empty, it adds the attribute and does not set any values. - * + * @param semantics The semantics for which to replace the NaNs. + * @param value The value to replace NaNs with. */ public abstract void replaceNaNs(int semantics, double value); @@ -629,30 +643,31 @@ public String toString() { } } - /** - *Returns count of geometry vertices: - *1 for Point, 4 for Envelope, get_point_count for MultiVertexGeometry types, - *2 for segment types - *Returns 0 if geometry is empty. - */ - public static int vertex_count(Geometry geom) { - Geometry.Type gt = geom.getType(); - if (Geometry.isMultiVertex(gt.value())) - return ((MultiVertexGeometry)geom).getPointCount(); + /** + * Returns count of geometry vertices: 1 for Point, 4 for Envelope, + * get_point_count for MultiVertexGeometry types, 2 for segment types Returns 0 + * if geometry is empty. + * @param geom The geometry to get the vertex count for. + * @return The vertex count. + */ + public static int vertex_count(Geometry geom) { + Geometry.Type gt = geom.getType(); + if (Geometry.isMultiVertex(gt.value())) + return ((MultiVertexGeometry) geom).getPointCount(); - if (geom.isEmpty()) - return 0; + if (geom.isEmpty()) + return 0; - if (gt == Geometry.Type.Envelope) - return 4; + if (gt == Geometry.Type.Envelope) + return 4; - if (gt == Geometry.Type.Point) - return 1; + if (gt == Geometry.Type.Point) + return 1; - if (Geometry.isSegment(gt.value())) - return 2; + if (Geometry.isSegment(gt.value())) + return 2; + + throw new GeometryException("missing type"); + } - throw new GeometryException("missing type"); - } - } diff --git a/src/main/java/com/esri/core/geometry/GeometryEngine.java b/src/main/java/com/esri/core/geometry/GeometryEngine.java index 6f729cea..99466fd2 100644 --- a/src/main/java/com/esri/core/geometry/GeometryEngine.java +++ b/src/main/java/com/esri/core/geometry/GeometryEngine.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2017 Esri + Copyright 1995-2018 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -88,8 +88,6 @@ public static MapGeometry jsonToGeometry(JsonReader json) { * reference). * @return The MapGeometry instance containing the imported geometry and its * spatial reference. - * @throws IOException - * @throws JsonParseException */ public static MapGeometry jsonToGeometry(String json) { MapGeometry geom = OperatorImportFromJson.local().execute(Geometry.Type.Unknown, json); @@ -153,8 +151,6 @@ public static String geometryToGeoJson(Geometry geometry) { * reference). * @return The MapGeometry instance containing the imported geometry and its * spatial reference. - * @throws IOException - * @throws JsonParseException */ public static MapGeometry geoJsonToGeometry(String json, int importFlags, Geometry.Type type) { MapGeometry geom = OperatorImportFromGeoJson.local().execute(importFlags, type, json, null); @@ -254,7 +250,7 @@ public static byte[] geometryToEsriShape(Geometry geometry) { * @param geometryType The required type of the Geometry to be imported. Use Geometry.Type.Unknown if the geometry type needs to be determined from the WKT context. * @return The geometry. * @throws GeometryException when the geometryType is not Geometry.Type.Unknown and the WKT contains a geometry that cannot be converted to the given geometryType. - * @throws IllegalArgument exception if an error is found while parsing the WKT string. + * @throws IllegalArgumentException if an error is found while parsing the WKT string. */ public static Geometry geometryFromWkt(String wkt, int importFlags, Geometry.Type geometryType) { diff --git a/src/main/java/com/esri/core/geometry/MapGeometry.java b/src/main/java/com/esri/core/geometry/MapGeometry.java index d7161d52..dbd90646 100644 --- a/src/main/java/com/esri/core/geometry/MapGeometry.java +++ b/src/main/java/com/esri/core/geometry/MapGeometry.java @@ -1,5 +1,5 @@ /* - Copyright 1995-2015 Esri + Copyright 1995-2018 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -25,6 +25,8 @@ package com.esri.core.geometry; +import static com.esri.core.geometry.SizeOf.SIZE_OF_MAPGEOMETRY; + import java.io.Serializable; /** @@ -132,6 +134,22 @@ public boolean equals(Object other) { return true; } + /** + * Returns an estimate of this object size in bytes. + *

8N0%(^J`W)eJeY_C!xAfs+FdIb=Y*)7#S~XBFj6bO8@Appu zdzJvLS?Rbp$d_mWwS=TkU>n6zbG9RoQtf?n(F& zbij@&#(SN6h`{J9Zs=yd!Hvw7e%PAUYOF+BfT7OPM5HMep)Dy&izz$I2P6gic`guK`ad`i=6X%SQ_7$=+!mH>{@e7Fx7qOUx>0jJPj)^G9Dy1t?2j=b2L z?C~?ns~)|BOO!ml%X5c~PGV=KL3(7Xif+B~Z(322(0aC1@<~eAvCj|QXdw|eblZ_q zG$TXbTa-xgU*#|gjgkyRYgxy`D?m!JJ8y(+F!PuM(Cfrp5#%101t>%^xeDbk#L^*^ z#}`tWJ#ylB@XEY`H-7nd1ld^iPlj;S+ zcUzbe)VTv3L#q{U$vj8lk`YJuAp>iYzSVVwPqx6Q@uShp5K2ao3WXKtep~i5noH~d zx^ou)-G(}HmjI{(C^987$9&=BwUj{ki3pPq(n_<5`oj)6aEdOAfS_Zy#_-mt zjZ<1RtBe z$`{WI41!H=3#A1SkETHCtkL=_<@l0%*OX)A0rNPUde`VJ`#2Ic61ACn*Wj%UfJ`IX z&?EhTi+Z}&JM};VaHQTfeCq?itL2`)1sR9a@Ey1{0yI$5)xQTHlu+{-c|;tT0CF1M z0}oQEYic{jZm9rz4ey}`EPy2qU#x+x>Q0+yx_Y4x=N1qo~;=JfXd`pL(8`pWCZ>((p!$ePbejY*$;0On^fQ^Nd&n$&@ONQ*dF^Xm| z5ira;bPVTi&JvTWW!GRJ$!+4E4cYI8y1S&Zb53Ig9hJUVGuyUek6=Fk3q)}>N75^3 zBn5c>NSt)pEYgMRHFQFl9x5Dj;#mBR$l`vv>kr;$wbJCiO^;eJ19t4-JJj@jJ7Umb zZQiV9h?XXf+cJb$1b>RhPIL;WwEq)QXy-VSS<3|OfZbnbp8XV2vWE@42|O^KeZ90H6qh}M9%bgBm)?x_b4 zoU@2px-0HpI7SSiqb|uC59Xja%T1dwSU};$9ux$ghB-){FrW&=V8EoHiMEp~QA|4q zO9g3H43@zwTA}QK^LXbkPFs;rL#6wLpokQ?3N- zSTt)>4sT{Omp2PB`LBgPm`lC6I9Z%`)+2*H-#9Sm$Snat%b9b)r)5@5op;EQYuo_P zu6}9I8!b0i;>_7sMRd;rc+-eIWghY|T4x*b8UF2-X_BcI?X<;CcaP6gI1dR(x~H!W z((uJ65PqSm7QGX}%%^O0G3Lvbhx?1iTb)GD1NRco)KPsMWl8GFjy{6@m_F)MKmIsJ zL==0LF&c~DK}<6&_95!Mzc*p;nA?PV@y(iXX3EwqgZK)kJe!JFRr~shk~O4+(22wu z7MXWsaL8Sr`{E3mjS5y^0j(oJtf>|dX*8k{<~CuCJS}|S5~no#6GaY@YVceHxd8g7 z4BSviW7V@>V7AS3+&!$tS|5%oeU)#Zr13f;5a7_dny~#3lw~n$54v`!+nRp$P?%r< zZOZI;fGz_xbFAj^QU$T|N*Q&VGoZvp9)2 zi>V+%l$hp9&E`s)IY#8|;}@s>my7Rr+6#`RQ)o^iGvn%O>_uW=T6yKhc5WdQZs1i= z@~75x*eqn=i?Qf$hOqTjP}N09AE5DR^Wj@L8mkyV`zkGjkSQ(tGBd2GhllT4Y`;3_|AYU#Q)nj&%fWZ)B#1)qskO z>Ja~c19r&hyU#cYnwK7C3kUHDi0=Av+%I__Ml{nxJ`-hf-^ssKBE0p@k`_PMhBmo1 z+ukPfz=@Wl6S4h%8xLr=)UX0iPN*d5BE zIY6(4h(2OJU+w=^FG-)PkM*TnBk8}2$mJ(nb0+>$LI9CWFT z9Ulew3Iu#w3!n7l;1hVVMY=$m(3X)%k(sB!@I`H*fp*ZMr#nf2+S}g1!fP9N4eH?2 zxpzDEwv0n}1#hLMK=aCP2a2JX7&LXPQ`v7;KppvHchOIJ7>%`7Cd}3Vf4oxfGO`M3 zTbev$J^S2Y@BQmyaSXaEWQ#AyNISM{%rC2BRZsr&E4}3%PwnQ^WOJ+M%}aN)m9h2A zc$?4v`G`7@i#Mo-E@9`Z@2Y!xc4==+>s`^~&sPuNi_#{|H;PXjj>5(wep{14#gar) z>)fK*o1qVY#6;byt6Pf*t(vtLNOocna>qI{K3U{gCA!l=H=A12rz@<7aEF)Y1{oOI z9l<*HSQYj4?*u3&DZecn0thGt`=44gjQ^hrko>oQhRJ`+eo_Du&d4grp>`qhvTnGL zN&NgKlK2*(qQT){^o2?0hEnmtxw-m9Z$<5ivMQD3u>-&;*!H;gJ;30ZDH@?p+_=*Y zdxC7X>#YuZLorxvxM;XnRh|-LbTy^+j<;?L6<*zLTRtteojIRxk6u8nz_f$-zr^z8 z;il&noQS8BI{0r1N2Q4Ak~#!$DMzh{#UymeJc9OR5?jP$6FOvWe~q>iUnO9ZdF1T> zNQ9H%CFBseWf&zR&PntZz2z81Bi2jw7P(~^l}J<~E|HiL-nRZZWg(xvb^<)ih=fDV<-E4zS^#E}@eQT4SjsD*S?X*R2v{J(GM7qXkZ$Rs zpC7okY-S86tWZ!@9eFvg<$5uwpB}=91vObZ_p;pXJ<|}{M@x=x3u0f+8C$?w{xCc{ zzc{kk8iAG-S~J-x9+`@*vpPL3*;N>l(Nz37Wd(1s*QQnL;8VH5{3mPTEVkbvt84~) z5n^e`UxMsBX0b$|bORk>Zwx&)=c#n3(euU-S4~eu-fISBelun2MnESB1-RMzrKep=kSOxVBf3N!v!Ut*^E;Te@(g5=VnokQ8ay)rOssBl6g z$Ja+)Zr_E8su}H~xa1BH6j!n^D`{8-KIc6m5{wjJSE~p^CNa&F4Ll?-QlwOCW81c| z2sEq?038&>c(k?$lxJQ_J7=U!Ic;RKt7-(>ErF8_t{<$1rrAXIfO>C57T+KK(bcr9 zK^`l`5M=_-Qfk{HrTCeR5d$GsMra>fXwvrXUm@@=Dg14)9@Gbbf(PEnUZZwMnK)_A zc6Csy#rV5$C1Xi1#4YK-bavR$*o-E|_4Zl|^uf%gg*s&Rc>DtLgtl&Nyyj3(-7>fx zIQy=jw~shDtZZz|x9eySG0*%q?|eYLMicA$lGJqF$RG|YJ&6)0qSK@f>is7&8Z~Df z&~opv&k9S~L5Pi05U&VGqe*vuj?GVsg)s>F6E33OF?{Gzgac1!Kdbxm7%tp{6gA;X ze3=ES*ftKLz0LorR>RG`AFQS+!`A3ScfzQ$#h|~I&!5Jb($-g|ad$lpX8PGK2aL&L zt?V_D1)HS~Mp%T=tOs54Fxq>lc)V5O z9Ua%;njG8s`lpC}_#wNr6E~!sfnRsJT?JT_5MpWX7=A&%+{ztC{{MRrJ+ z_jhV*f7*7s!O6?EO38b=Mynpi4D%q~+5D6!NJsvZK#&cAFnhgx)}YA!DcfPI&5vRP ztiq2}9R_0l>nA`4%vM-m2m*c!1$dp1gxXP2)6j^&UR2?E2z0+od&&pt&|C}XlC^cC zf2SP_UPC#ylI3Rz4u5A1g9jE@@>=+$(1jksn;Ydv-Bm8i3!C)4nWNz&|IbHCbhjNp zirDi3KWqI{bJG!zm6~WUubkCxMO~ThIX}0Vca__Bz#JY|^>u1JgVI!J)CxH{#&WHQ zXc4;Us1-Woh$)8V#elZe-=~GMG@6EAltwFP$i~E(UwL& z#e|#!wzycVvL`rE$NHv=(^>SYllJl|yYLr5(p(6WL+~6}Dl|>ApiFi&_$M{VkIuZw zq2rJ(7YRA4$MdMU(@Hg##wxD_HQaj?8mg0)o`uMvrzMOk6K^Wdro}7qgf!gO6^EOi z8wXxFk`KTZmJm7gx`XSluS<0I=9Cc6mgG^h2^Jh^lAOM@zCWCAx#*bo;6)BC*BD6R zmuY3Zj0cQ&G`#KL&5ICaX6zE)#P~2h0}lt<^nk*6MrWcmsMN*@NU8tY%B$Np(5q#I zRGlzekl}6m2#WR_Mlgahgyg>P;Q66-I0aPO=&C;JKiu9MxC2Z@+r< z`PSvT^5NAbnhuR?`O1Ps*faSDj$Qt9C2zD4>o4gm!yFFIV5?M{lUsjkf~rlXpzR*1_1E=AN^tUtU6?Z5&qRylzAbW+S+NNS|W zzco^4U^L;M9M=>TU_r+104Hg5W@ma8xf zT9ew<+NBCq_O>fb(`U=St9xKMMr@(P@BuA4iH{e8400h}y0J*?kBL@h)~BXfBug-n zydtn&nLcFjiTdkfUZ5ZYbB?Ll-8L%~0egQyQ5vKPhCQa*2|%3-zY59_nW72E1OmV- zLdt#{Z7uip?8hJFBgT4&ut7vzubPL@W%g)h-xZ7FJJZH*RI1(6c=aXNSbMlcdzwHp zcSOLkbOeyXPMRYedt^x)rNXdm$nw~7-28c znjsrc}RiTNi+F86bg-~gNzEux>m*8}T?p~m2BnOigR98WC; z)#_J(&$^CiG((T>MmQ{Y;7p9m-{Ub(L%9BVyHPi8;6jgjEKFzS9)2vI$)cp?I9L3} zU-U@B*x+g1!Cz#E6F`-x-GYC@T#2+bv-#8Fz5<`UrE{{>qF2PkD|rGqzaW~>O!jsD zo=xZ8TjyT#o_gNH64zjobSvzz73vv2%rr!~W6fk6v?kJJFrx z0$yKvDY7empIwZY4}=}#1)&WUv2vh{2t{1j9+`dM zS4Szrwan!{kV}gHlWEKrJli7irw+3<2iq6p;(#W{2Cpn~ZL}u4bW2}EdoQyGr5jao zJ(Z-+xC_J;V+wJ?b~{q$kAV4eKMTP(BAd$(n#wGr+!5@sRgPs2hb*TPSpAk)E7F|m zAdPFOn_BZYE<6@d`wjH!H7RuVS1LL%wPYMQn-Bi>8!-;LYFBPAb};5N9Wb5D^8_la z_$AKaiII60av+O=ufncJ8cAMC@38ipTggRv&bq6uxo}pPoJXFiIy76E-U$R@h(1!% zd$9aDW4bs77s~k3uS&fKCNT?*M9;nIf`tJ`5X3VyFY(Cygd@NtswZW>%H>E2w??ZX zUBJ*UL_Fsb%t^U3sAs?oAP`Ov3^aAvg56*jED*l`i{P|R;nS|Tg+Mx?CalVSqDTN^mDzQN%f1^F`2tRv@b1lU$V0W2E- z={-T5%p!*Kpe@TD6(~fg^{e)eAAp7^bmFTlibT!}Ic5r7rI`%WM!1WO4mTQRx~476 z?vh+Mnq%z6h37J`_k=*+3ynPlnHV~XxnsS5WhM@l!#;xF^oIKH3jBYbU8VfT>RZ{w z-T6Oy690+JDh*iYe_82WvN5F@qbI^M2FF9nzd$1iGLOh5{1ifDPX8GUgqUnB4#vow zmdAtyx~fw8rJ|sALHYcn5p${NKujE-eiu)@NqJ-AQ@qL6ZLO*K(Qa+S$@S^4&*q!Y z-K6ozW9`xT&c5$c`&Q@G+vbta=2MrO``I*ED3Eq2pGPf_mlHkSTj9!;!JW*B4;A?I zwg%kmO=ibu74x*zo!cCC{NdW1x0ot+@?m@jE?^Tu<1r9i%t3kxjJtFo7krSNb~1|b zAQ+46;6RyGH}NPQi_S|va-Q)C#X{G`C@;E$bn>kJ5SDGqogTKN5F;_60k_qF~6 z8Xrg}ZG+^OHb#VK5@g4TmSTC~*PEH*O?hGjwQCleUz^CxB#T3ay*4sC?ty)ds-bKm zfe~Hm;(6|c98rr43+6=|z0ZYG&pX8BwbIx=6-oSH&fG$Wu*%v%RTf;@Tp+{V7&mWXNS+siX8 z3VzXHh*o_Om!X?9khbL0xauo7(IIWf>n3NXm{=weRJN}(vqYO%Hq(_+wisP=ATw&N z1c91UGJV{N)<*1eDTNUgl@7)6tW=0lE)kov1C;1efBs@D>E=HO?#ko^pS!ON#VD84 z12$8iR^4$X|22}zWg|5yW9bO65P8X(6&Dm<8cG5~159F%9?9lGa-f)M53((DOJ_6| z(yh-IB}@|@G5^`97k|1{j7+R#;3%XgeJJDoi*X72NFFVRIg2IjtVRoISKlSyT4G>j zW-diI0pz*|-rp>bcQG%$12`*VM2=uG*36G6L~97tY7QpUsej|V2{_%)>A^XVd6b}M z_YBfPi4?U@JMk9d7J#YGN8L7pu+$OdMhOcRYD%cCpnX4V*YCn`esS>1)VGmzjtbE< zAXq_&bu)t+i+pspP?t!~a4Dg0bdjQ;OW|PPLXde~*g%Vb{sjYLpw_=AOg++t%M@Zl zri6NszS}yuX2vv)W-kz@n=ip-E>OzeGyyyceeH?SE(cJ`e?WlYr);zAw_i{4ht| zD@dnVI}vp-cLs1Fu)g2bd}%{E>wKo*b=}M9Xhq_?%LkU~d^Ssapj$p4oJM#sq{?5H zEZ!O{)+6W7J{yhx3c0)WNJTS9c&vFAcK)1;3cE3(dkkOiOK3eGMD<>Yk?VX$@x2%% zBHUp(cidy5^9|bayy2>QczXUplO>0+^&uDp( z6qYKv_f0-rJdB7mj0swMa%FRDP%=zNdK6y=qf5>F3b9-c+m{^Sq!T{d!SBP|LQq`z zn8a6|S8e^wNA(v&WkStVC=RtWi$tTOLJB-538msC$YtU?`8VhY@3HX1umV&WOcFi$ zCxAXRt|aLe0$j)IPF)jCHpIgiB+a4gX%dsXYhFYQ5louLZ;glukvR%B8B`M_G+R+W zXjc~#b%Uy`l_5nBUMdrOv^^5@Wvn==S_r6xbF9*xx824kN@Q``xepT0E`m`pzOyQ_ z2FN45qBpg>I7sQInx-x`jkEMBH&V(E?doANrwbSLURRPWfW!JW6CFJSiamA^0y!Vp zut8^TicP@Z?&YCo6prPo7;n%DcE%~~&;|7$$D0q#8N*nM`ICsgRM?)XCfnrLW;>8D zJu7h(-cDHodYgr7$nwM6i4~1 zrNodTN;q?;{dOOy^o>Tz$vQ~or?5eo2F11+j;C`6yh(x6lbU#<%mV2aoYc(UvzfnY zq<@mi_+=D#2Vp$p`nbn3+|rKT;jX;~4`|C4fy zuwFqCf3k-+vxqmhh&Q_kXMX0f`buE)5l-(XjrTWE$2b zU#J;_p2(rVx266NGsS=Pe0-2~-s&NLZdrKK)W3|S6A~Ggle2iiSdLtv)mo z*X=NaV8;7_PFC~sK7PWkZJIc&H-ecVTr8wMr0&B?D?HiG`@jnrPh^Q;gyh65ejyUH(1w zCfmggsBFG_)jc>vK)`GqnoI%JK>yT-v$+7|#T~AsEHa$c6I@>n+B!|OE^${i?Mloj zFrS-({9K&3RlxX;oNVU}vE&lqnG1}ePUP4F%n9c%lT?9h(&;6Wcga_q@QU{ch%{#i z4b@Zy*^DEyAr#DhG<_|r{Ob;ms!@8D%qSodSUMs6uv#&`O21W*1*zWTe#24Gn#8nJ zRk>W{cg^B9=R)X$CC*|Q)@=3SUPbv2w=&q}BF;KFmz`Xwou6+dD(FXlO?4mgF*hq9 zcLbsZqn#&q^OPdk3uYoVoD&Q2(R{k2d*{T!Du;fp7aBR^Opj(%DZ6aCPO4_mRaD+@ zBk?h(cf*uO6CbH#Bmkum`hBy zvSlEP&*2N_r-VBdu6VV$aYC=kmP^X8YPTTkGL=}9f+1m8f=OaMH+N=%7HkN8yqV+( zcjRHFss#kuO#M3CPE?_|0M4e7-^zOo`s<4hdJ2Iwq3}#$osP-R51Uv^m#>*M1CU{D zS`*`kwe7!XoSd%bhp;fWsw&7Fa657LV@?Z}q@#DuYgpzW26JP0)Gm6X&s*1{-ubQM ziCw%|bPP*?Sj9@AiI3i8f4B8D$-D}F^q z@%`xZbP>Nfkx!m3DMU%yy6zzQ1LhC~Ao!HJxZ;Yjw{vj+krhztCSTGOSFzBpUbHEc z;hB^0X5?H3=RV34S5um^E&D)lse-p7@Swz<&)ZDI8cDbPYe)~!W6=LZzQY6^A$B3S zt56Yt%@q7ok!+81%9+K$eU@%8#;&^vf$^`;OC{_XC}dSEWvjz-5tL$&j&B&Qodcx2eL|) zuh@rEzL~PmXxj&lU1775Jo_AjZG!#)J9KH!(D6|ADNJt28(geip30?Ei}0t~T^52( zBO&h!Sce?go24+Ed`7=6$Q$yA5`MnoGb<1$V_ayghU4>Lbl|E?g;X0c9ZH&$^c8gI z7KLzbBW$P`!f3W3qjH64sDhs=L&97!sEiSV)T$FL%?zQbF@*|pTB0;v$5BFa>)P|$ zqbJpknbj4BMwHq^%)|%m$+t za9kMHt=D^#(&58dj$66U@D`40%6b1(Ej#>N-JI+n7`H%3s>UgQL{q*n8FljD6Gc;g z`DjdCTs-EQ#lC6orw$~>`$cs4GXm8JHcvas+6d>vhsYHD?BwW?`Nz@Pk!Q-mp0#JQ z5Egp^TVhyc#&rLvB;Di{Hy@nIGY@Cr7VOK%wB|d77)khA;m=am(aHSWL1T8Igokwd zExkF0X_L)Ou{US4V=cv&XKhpYR}ZG@YYru?l0xr48^sP51aTVZE%5$Y5}!E4U$Ti` zR=@SvLnVf?qG#Iams+u+VrmMeTG7U%-)1e(`!xHhNiC(NY%ZG^{Gz}~XncfzB!^ku zu&o9mslzm$Y1&X8p0p=LZQ(UMeK2&lR{EGZD(9R@y5gatzkQpa z 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 sc^g^m7M-Jkk0+RkQFO>YU`8qwzbXzaVr6 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 z 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 z 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�Ua>bx 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 z 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 sc^g^m7M-Jkk0+RkQFO>YU`8qwzbXzaVr6 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 z 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 z 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�Ua>bx zja$S;%?2eO9S51{WWTB_2w@bl9}!l}EM>Ougo)elXu79v+&=M0Qw`)dv<@Ko$JN<; z1T1zUA;5OP=qz8Cv&U+EruD%KmCz_ zzN0ZK^M5=arownFil&YDBzVu*#Ea_zx$jpY3xET!)Y2S)fWr{8!t0=t(>1Req1BJY z2Tlb8LJqNZ>BLl!dAK$y(a0s8*+VLsTQh_lpJp|!eScCKOl7Y-t+wwwg z^idJk_9*KX@l6|}7%)%nm6F)aS}Siu$^EmWI8uS!ySxpsYRK}Vy*_GK8$NaK9}mO6K1#lRra>qGSO4BIef z*6Qg*GY~dO#-D8G>pZNYn9^HiJ6hYF4o!w`ZqK>?hEw$!>~)}q<%*-$F@Ht|mL}r8 zv*!M|X%JXi2J}B0+1fq@cL!Hk*_2sP+UsAf3CR*_&(KboS|8v~cpBOVbtVhRdOh|| z|8b*4w3e+e%q?mmO5Z@2CXwP%JdtuGeMKp5c6zv~Ji|LFI}0y)-S@2E@KC%h_j4jt zCkgm%#}f7#JlWcJmt1{vWv0xy)=*&K&5?AnD$isI<(+v_;)=vT!6?{Wf*C?>OYgkd zg>&i#rkc>egL8SJgK{|`MZr7QL!A_UWxbLS9Rqzrxov?}7U<7wQCXF?d}Y*WD%ze9 z<|Z4W-L}5SvgLRcO?nK43NEnG@JC3Nx#_9cj69K`INX%?Te>1o|4O_&!g}WTC?8y|;=X?L4A*FQS;qzuUw*{7Pto1SnmaP{Z(po>ic(c0W@ zUkF!g)NwLY&83)0zKU~Nz#?btS!j*eO3`@n3`-U$JywzxT5m`ludlEgb}#fK^i4B3 zn4FEmqZ(o_CJ~rjNPpl{n&giLIfPz*FJfO#`;`8JFQPq^hr?=(^$x4X(+baH1((4E zQl&R(BTe16g|3NHBCJ6LG1;~PZ|~deQ~~psyQlW&`hm{Vt+?!w6kS6)`kbe%qap-8 zhCBHmLH3iz`ds&Y3@-vlWRYXCM%`vo-Kjnj!v~USbXW;FgYO3nFBFrqI0-q!@6M?_ z={?ykbS=TBRt*eC!_2fT?#9h=93gF#3USrpBe^o`#g0?X=1{7$R>>w*dbns%&#kL@ zQu`oj!&~7EqaN7N393!ooW)&eOKPd+c+-tN-@9{Y)GE($1$Lk_hw>^zcI>l#7hI0e z7gli$Ild*Q)hMlV8aVE-zsS8vahY)NOh%3%TVuxXRo zWnjPUnFo3tbZ|{DkHMiCUD>!=Q*dzimbXT%U&&jJ;5R=QID;rEcaMXYXFt?U;1@N( zIL*o$G!2TAppHqN7fHyIT>ZB0(+UU{9NXE=L8Vn}hGPG?EGM6Yc{?W>b!l8sd;f4Z zo@SLj{&{WiO3)#t5M4Wg;KJFDE4p$H{)WA4CAxAA4)*LrFXw_S3kGyGUE4!=0vE)s zWU5JiL84tqce3OT1{<=;ACqAR0+c;%=Q3LWhszG=GrPa$`V0&h{KD>YylV%09x--~ zfRq-)WHS_s`CYxW2nH|(xwOqYG}t>t4o-tK57^nNDx7}Xn?cf{?%j_2g@ zA#)boZTTTL3F$u9C1@{M<-Iq`K+{DFt_s$DuyUlCGHo}V%K^BhU!A#E{gP)oot3cr zD33pMltph1ABlAo>GsIZoI9(x`ehsHZyKn1w`$3bxJ-R`ZAKJu*RZDzu#y>)CfcZX zH%f?Nh?K*VJ}Sowbq>4<9^DjbH?kRAlhN2#nT}>0a_6%7sp%f=yHQ7@GO=b)rNHH>x8%imZV>SomF1-rhkGq6cWZ_z7!0Hov3 zd664WicLCD0`g7QVQ>P*9r<}(7ET~ruTtf($l3p!{R2htPc5e*fwn8Md%)*z?$_u| zMzQ{^E+Xf#_RyPO*}fak37cqHe_+;kuZTsIl|qw{C)Y)c{*hOka628*np7vvJcsxY z*iS2HOMX!e{W|XEl$Grb1}B#C_rj{p!v?S=Q+#cXf~i)hR6d8LWTD3O8=+F0FeItYd%mOObOOPf3dEF%c0EUQU-W&jKE$wU^Lbq0XoAEKofz{ep4u5XF zV-by_ua3H(-wRp@#6LX*T$s>f3CN1CO)k%4RqMwo-c~mUdZkotoB*?N3eAXA&GY9W z8s@hZR$=2y@M(ZA#0Uey*jZ3_L~DiJCrC3ZUV&!y2f|Zk`yEj=s`f3ycIHcHjeF0Z zt%`NJ9+Nkk70OHZUG`Q+{OLd4$z*X|FAUFgA>Q7r7kT??_Mm>N#!D~gJoG6o4VrY^ zpidB!m=I01mb0fOqi!(->?iNq#U3zfgZ>fZ{}EkWtPMJ~^B$-tU4ofzUQ3 zxlXZe@wYe#+U%T9^N(Ee}Jg+37ID+Pr3! z>;=2L(=r>V>I{0Z47afmOoaXBI4-Yfmizk6$~fO)G=SnrwOuSH_5GZr+PdVQ3}^*g zzpU04prDI5*M8WHj#JHe!eu>L7--0nflzbtr~gXazwU=T8FZ>2GO3(^r<@04HfOs&RksuP=8enc3c>>}=$ zcb*$4_F?Nt;h9{QK>N*Z5#fBIzW^mYhsXd!X)IFOyxfj(-bqzSJ^$$8GH4f89ezNS zIk9yyE2+uG8Pyuu8s!%07G;+4yO~ciY{sHlxo63D3A>_A<-XFQ;zb3jGP;3d%VO3d zZwW$$OoOIg!>Vb}qHKw=BB4^InPccosbZm0$JDuf>AY&XmSe%9ZAo2)s&f8tQ6eMT z(79UK2EaMHx_J6GR2h_0D&{t;vwz0w2eeTNPWMgdYN>$c!s};wFFPD_hN=KYm@L>S%s5tyT-Ac zrZcT`)O11hQhK}0vEuJ@KntOm0-M?WGA!rVc5yTX+c~7t4S+LLZJ|wz?omY@y9+GDX)DibWajr1aT^Qtej`i9g}P&PQmA zm~8ROrNc!Y?ud3tRXIfu)+Sw5g#`~-XTF)Dt7YZI^+&EVT<)B9Rl3}(1%k7X3us;| z=bj}Xm23L+m;nD6@GImE5_IGZnrEawZhu~s4XS6RK7zg+s4FBdr5iN=7zkJ>9@^)? z9Xb#nl4r}l4}V!mTzG5*F7oHZody3Ha9l)e6l^4HG%o7r${k1l87LWWTm&!v8{xif za2d!?l^c@26p#|&5)f117|3qI=klG(9frPr{~CyFcrFTdvgdX^u^U(adw*N-Pn{d- zK3?!{lIP|fJdiKq=ja`ZK35P~sBW_7?j4N227f&8Y$Pta=e!+|z6gIj2pz;uYB%AV z@|_$IUer#~XS6<9;2Nk;j?XBbuQ31Z(VpnlN~V<~enzvG5ylHQRfS-Vs3UWmrI@O#m8^+}tMh0_+B=N7pOmJ7(o3Zoa+ z%yn}F3yhCNCnom|2^^;25}?Ri1E~qgQ`sR6p}K)e_GHt-h>Z3Fy|0`<0RDPvJe!W_ zAo?EN*wFv!squdj6#hNF30m1YemhPR|Ml(cVEnJ~D@c}GQji}$GwL?WorguwFpW@n z{-Af6ksLS>8Q|?+ZGfBBb(crzD?Ny}7ms8staI7gBslBayu6#22Vld1au!kx{v^n* z!R=zHB7S%xiu0&Z(r;@$_s$1fkA)3 zFd7oLLq@L^-g+6y3=eahbr?brQGw1`r|s1=QHVtQA6g=mZRlENxX*=JanGzncmh{_ zM$+$aMM?z?`yqyfb5ND*D6r(8!A(yfn@g`CrZdLi{tDIa=bpHk@9~rLAE7!j+?b-$mw_J!GXgy~CpBuKsOT@`rTR;H& z_+l==M^L6)vWXvNGv82#tLm`x%kX1?T6+|6X&a>`ybV>j!h^qh$2dRvZ%j{3#;u!X zi-(6qqU{Q65BuDUWTtz1@Bkxzy&YZ(wz1R;U}~=e!qp*3zq0kuW;TPtA& z0sAP$P!^$GrdG1KQ>K-J(mV^)Idph_lt~)tw#`V|4^9i|1vKQ2x&+yrIk6t1L_v~V zOs@vnetM;#P2_=RJ;Jp@PT}(gc57+xKlV=&^AZleXM8S$3&Sa=7xbuK7PIMl2hBeR z0k$Zna}ur3G90%Co@Iu`i@ZcMAthcyCpDp!!l#-PsXVvQnbQ@gh>p(s@T&)m*16G1 zC2{xd%-|HUmVxA&HW#uIPjrdzFzToxE9f0i1j&)wq~0M7sUmfRw}Ha%`Xj+%onuR>=zh7|&%!(8exOC-;|$#W znr6R&5aoEWs1n}(7FVi9we7inbGRb@(P`{|+R*m*trs!Ycly5Vj{G)8-yU{$#tu&A z#*Y6w*hz|-k{f);JccbcPM-3b>lW6M6iVLW7IkQ~$je3wsDDHdhh7nfFBpu*WQ-UQ z-YvaP;7(#FxmS%>cg6+q5bwNyZX3gn<@EIY0_qiYQw*u%uwq)VcV}Y1bvtQ%KeEpV zG0k#0aeN!J1o`GTHHPeIsFlUm9a<)hK8N-RawfE)D&&MW@&V~Es^zS;a@Q(^L$J22$E5#AgV5156>BN;OgBuzkJ$0l2z1?ZlD$>i0Mw4I-?HlJKE~B z`O5smC+MA|oi{JOdOY!U_(&U!erm5}c;qy&^P%m2FJN(M8WcV&tzgl{tu3VpYf?3- zFx(qiAD4tSkC{JyI~>j!l#uRPj$XMLZFu9yC3QznhHFGYm$1^AiJmGG9}$5(BT^P4 zpBH~e+z~-yQ!FB2iB139=@zfmMZLf^gfQ-)jBxbxGN`X)sK2{EKrp>W2DB7hh@B5( zmk|h5@_}km59(?ztIu0NvR0~9rw?>D2~vk-qjU#Mu3|3bM@*bx6lbgbB z4&qjz>BHpCORD?r-+6EXkJ)zpI|s)6BX~{yPkB(%*j>=p+D_j=-^unr^thalw!e8m zK|zT?`CUMHTtI)hfC7ksHZS(i4`v#OfI7qER1Ve)ePj-HI(dH;RxTbOT1p{Ik&A#% z?@T^)4#v;tE8%Mr4X$H`^6gP$gbiUtLg%YN&p!aK0V_Znu&=vm>QmMY7<-$Gn?!dz z{<7gWkT($j6$dkm%xNhCTD|i=pMRj#GKrnb6xgXqg&^_3_ll0O7=Q2h7$JH8(4Y_z zu1NKma37&SP@Gq4Vg%3Ek`~7goUvpT2^|S7#Wx8JEkcT#8-tp~I$G)0hy%CCIpVW@`;(j%HQ zU`bKeBli2_Rh($FLOq;F!Nf2jilbv2Zyh~vAKh=Bn`!H=)jqJfcrM^o>?lG|@dj?n z&^2NDq=5@=+5>6uXr?WE1L^?WWXZ=vcV4kS1W!y@3I)u1DHtX}^uk}H8NL@C&BiA0 zOr%yIrVpb+~M#j#P zJZ7MUizK(1l?l6R_;=PTLVrxkMZ;BTYULt1yVs0j_)WsAvnkUnU|jt?-RHJ{<-$*2 zqDi1b#4&zTR2`K@xoBk3&4O2zRM})rJ7@zcOH3=t_=uZI>J@KqBqpd6=Y;TNGnHR{ z49IY*&lfz~@Rd!M{JG@}!6axHfF+qwnTOTA4O2m_oK}!mrZp#%COV%#HqfLM0R@P& zskS&5JmSn1R^n`hG>>#L8#@;_gbOoi=NQ%C#1)do0a_qD9K*f{R8j0E;JjoMl^$^PqUaE{kCYu^(rct~OR3 zs}Gsc_UZ_^CY+8U;@4?bqX-w&3UdUki^_PU46_WP zE1Pc2jL>yHI>ZguCPaAT>kdjr7Gn@mXPGs!inzzdE2yLyQScH?7s>m(&Q|Tl1`oV{ zMbN4~kCbugs@ufhu$l+iD?O};mcIQ+zZMJ}w7_}HkJfd^OiOpY8oG3uz%$-N+?=JE zW6Vj~`AV8<!@!`WW43Ry=;uF@Mfv~Fva`68@&Bs< zC{oh4otOJw$>N8qc6`PfqPQ<-f456c&x_bc0+rSd{b&GPBgwrgkxNpPdRy}#Al-Qb z_FNEoeHso#JiI5o9pkw5efmm;W3^s-o>wQc`#jhI9%kzs zRjyHez6+1{cJ4;Sv!(e_r?Um0?C<>SI~w<;YgWon;SB)!th`Ui$QTBnD1ww3mQImy z!yqfyg`=$YR#|JoY)%xP%U%`s*!4PinoclpN0>g=ul?LPFShQ4Y zC*-~zT@Bq(r#*FmEjxl0e}Xz!+M%vC%8_wlEGh{P+O6=MC+{1f{-51~{wIOz|J-o>-CZae zJN>J{$WoDZL=uJfE=Y1woq(M1N{vM~z!rSaS7!wlAT)#qH5{rJH%NhPi&|DR!Eygx zsX{EH91irtUKkFPIf^$r1Xjve+)p4(i~TGsKG)EZOj8M@kYrhGTKx8y!M17H_40Md z`(xU0)i;bTp4rPBKPcW$Tx-tm5gsu$Il>Zjdau3FB%XOAc3B#x0n>(|^b@CpfpJ={ zQtis4Zq6y(vfnavD*wr_3dyLNVk0ctoVA}2SxCG8m(c2 za`1Cx2O;_^Wit_K#yQI_&AmpJOSF(DEWe>h-CeQaZFWtRIY!uaD6zSy^Hj2L89QIx zE33^twHk0yDGQiD&$o3A?>?l?O170r4o;;8C_f`=OfOQ7G$BgyiB`J;Ni=O#uuBw4 z^2&XWG&IVX5BA%C_B~3l=ERfm@(Sz&zXzSVlbW$6Y7Tf_aD~6V7F>kGKcg8`WXB=s zCXBXEAG!M?yd^^%SNTLighKRruq}n=oJEDInNvmI^!o(Xc&8goq6rTLa|b*5LP-Rh zP1KE5`Zc6~$OcW>1IFb*;4mntftO$rxro`1;U$m;7B&DrJ%>X)L69!!GsgJv+IX0h zw_sIUL;N*R8=M*Z-neMk=$xTZ=-zXXv(10^2+*G==K~B3CUA_}_ZhmAZS;YFk;d+) zrW;$sht1hJ!WvoSy0yRS*$wvS8OZ+ovp?jI#c-@ql4#+>{u96SL=O~#MUp{Lvjy!J zV5K8Y@L2^7gOZns@)Y1XAuU3`4(zv~=D=~E8wQyfE@_zbYZ{@kv>xPuMf|S;srl|` zi1fJS?Y}cZ5K13N>G$ON;~%+|eE-`O?XS7xzZSGC6-mW)6?E?3s`&IrFf#55ccIqM zq(@1<3~(dxXi{r?@dB@*^pxO{gQE_h?bA6Yq}uhT`B{D@#aR#w(rKqT=4_kJ0zFg| zpaLDe*>hd0+xuJhS=VBBc(y+7K=|P`RCA$)Xwqt>gM_#d>xpXK#K9!?9n`^%ktlSO zb@=;XXv&1I(fcoI!(=*(9Lcg>iA|J5MEgt_meBFlxEhSvjEN-96I=#2MNZl*NIyw( zRPUh;C;l(a-Z97$_sJ5jF59+k+qP}nHgDOsZM&tocNY{N@Ky*4SJvc}q%2~Da#9Dp@Ct*$INFvhBYcdf|Z#@Kq?Az$2E#UUOED(#yu zdJllQ;FxdiBhLX@VNX%`xth%Qb>b+vMfIx-mXXfLeO!_OksQmdpks(DCFr%Z4>2Nb zDnXH9S<59&$E2Lwj%VS z#_jTrZfn%3ny!LK{I++DF`~rFK|SA75rqAr5`>G>$3hWU9LTp@*_wCDKZP*4u)ih` zkO9~HK98o>JC<6*3$T1%8EOba!G&+k?!%gNrXERLZutYzMEtJBsf8C!6{_^*_B349 zjivffZ@|s`zLE9ch+%;2soFii&r)#qBU4PuoE6}gjv15cC5C~eS=yWZn9UA!*kxsl zK`#lW&okJdE^?ddZW*w2a_f>@emFTJYjF%e`;w7%`}{b`M`rz`}xoZvA1K@@Dx9|B>bDq7s{MP47G}Jo<%1?@0!(qN^08 zE-3^C@871W@@=ABfy&eLwB9Gq;bulsr5k80Te2;=KkD^?U!f+3h(4Aq<8=;tI z!K8FXwq75o)=_j6B zUByN-W^vP)2}~2_ksxqtf8g)^s?4k`S=AyG2?61>cq01(sMS;$^7su(e{%#e%nq~6 z);`}u)*e--p$|l4kqNnj!k}shU%7JoBlNOLZj}DeP_G&c`jF4);d5dDsF`18B5uxG zP;}aaU4}bQ`6W!6AEDVo7)qnfsj~QgzOXFG9%_UR6oG)?lvlhqLVD*A28AmPLPUNN zwcJ5=y)a(iQ4aWm7W<+m`@%x;$D#2D!3G4nea_K4Fa0`4Uk8wu*%F0~lVI-_u3I6L zL%&@;amqPmy=;*Uj@vSrw_&i{sFW2{K_z_=EGa2$7X^vW7l>;#Fim8OZ(YqhnBL9x zNZKx~$u1+SxLJJ3SWZKor;Ef$A`yWI6z^G$!I9e;ShwbX1_vpK96K%DW4YdHJ>GG_ z-g}990jsX8n5sH13xcSh>?Ndv zEiLLT2XH!7KWKmWI;Oxgc$!4H=P>s$IE4d(LZyOLK2!NMEqwpg0^rV$aDrXQc#h>w zUO(qHpX6N58teoBSr%oAVZUK%OKVxKr5_8S$KPs-Xo-Xo;pK4&Rn8dxv30Npb6LfP z5kQ2QeDXD#7O^nKixwNrp6QqsnGKi>Y4-44AwEq!;5N!Gv?3o$gMhUjeWS!=_lcv~ zo-G&Idwz8k&b!L9co+|Si)%3{r(QH2L(=0wMZyZ1chlSRAm@bywQEZvlogwK4LakQNgu)h-8O>$w%iS91hovC7Rc<*?v(gW8l zi;7(ZYuJ#y!fu3wt;CY@(4~tF8$0BA9>Pk$xuDl4Xt zZa%9_L^5}m`wXxI)fQ=&#G%g1!wh6!B&eG7NT%hiP_HZ4+dpy=fGI%BbpFM3&U8Tc z7jPEJ1X8$!XOaCJ< z(Up-cts7UYM{(LJz60~`Zj7Np12xFG2-bFb$07g>r9A;wv6gyV%!_{z85I>pEke)R7~&lAy|;FI z36^*@2T}mhW%1tENWAbW!;k64dlBo|>-5#Z)A2rSb^~>zQICh$&=QNvgoXsX^uiAj z_5x%R?WX8V^F@T;eXN=3mi9WP!$F3}iJDms(2C?iJP#uVJ6iCptMQ^24iQvvg>iOI z$xtvK+lF3w(%knL{%z7W=@VK|`BdFJ@M<|~i~NmKIsJ<;#- zp7B1?X}AJ0I0_pCv!(@2UMs?UD``qggkBC{e(rf5#yX?3*lj#cU*uX6UH~JZf2gyc zO6SwqOWQ*~Sy>#YhKGI6omW&hurx2IA{JfvL6H3GmRIl5m~1MgwL~bv#Q7BPnFCIg zIBRLX@MAbgwfBQ26zQgtHs~EMUG`j`S~BIECweAiJX~}MZ>42w+HBe4)})`DO4O-c z>nBvE{RX%!f0@Is<>C&tMOc+wEtNOASR?c@`PEDX<%psKN+x8GME2bB9m@HQ_jky~ zU%*9A2t{dx+eHi^n_`Mx9!ahehvU+?nN&J?R%f%3pc>X5XZy4(*L(a#IW9-Yb+!ZQ z1B05^yWHb=l&%AUiM*SnN=p?P;P{kS-eYi=LT_;Y8W*!ybZ|3%hv)4-hUb5`zxKb) zn*ZK$)@nofsxEha&GfRkSdb?tfC9rpfijwAjuZbtYLbEkC6*FI7ThwF96`*G6*ey_ zaDw<_h0(rYY*)?CHY`zHK&vj){maL_eR*Z&sn_1U+3)wZ4;{kvTfoUSmkSG2(%i{x zj?Z=HiT}yl=Z=5)>h{+F6mb=*hqqO%FW{TQm2ej**Yj}(T=W9ixf8kaFO z!s5#>*X{EYBLI=}Yu3pyW3ojvZu&0R&uN9?R^v8}DI}NICt!c={y|=b)(BzzsF${k z{Ipv{NfaTa2@$NFvGdHBaKi3^?aKSV}2Cgv+BO7jhzm$94 zI)Y7Kdt$X!UwdSAvZqD29iNzitC3){;XNWDz>b%g!El73kW<+WYcSyJ-PArQNrh?SK2 zxw*Xo&bbj?hPoVp@DzM?0yx`hITonKaL(Y|*a6(6wAXptGl14D>)6*09LpKMw>FWYY$5-6JoL7nMmSsm zywqi7xeU$DC9S*PkWm@(FsZTudThVvuRZZn6t|W*^;1)A18d&(Jt!9t&tcqLNB3~9 z;o0OIqQkk}cS*wA%sO0XggXvE zS9UaRgbDDh3-f@c#x_}8!-Zu3xVE(L)F_@p9hQ@mokoL_=-wx^a2-{Dd~DPJV!xc- zNIXw0Xbch3ng+%=AQq8sBxSK0x<=cb8szaru{_hmJrw2mLaDpHT=lLv9UVTtD*z0dl!}K@~9H779*@Bv5zE z^;(vLjha@G74_wWC|j5#fFB+_XlsFkns#6uWQbUjAZF)YuM`5bAH}3@_Yb0OdKXRn za{kO%pTpNdkG(UNyTz?koKL`HrmrlhViC-|U+B*%tPGes z6-0;_+VDvayWrs&%temP+rA)FO5;jAEU-TlkNf#~>lRG^_T@~JjZu<4x~F8|(E@|` zIFL11HaYX&ZwzGz+~zJiKonSC90%4I`k-T zK~1(OxrD#9OhdJC9lh)dk*Or<$=Cgf_1bq^=;98DXFrANf`7Md2Ytplns9{6eQACg z%sBi#CGVP*;DK6U<&f&5LWc4PBmVQD2f`Nt1g$Qjdx4^7oK4i_7m>ho+ZBOXG)hra zl%D_iIwY^ZEsw{4US?A;{s1Y>e_qlM&p%NkRuP`rvo5h|*^l_ak5nA+G@pJt$-g1A z`cjeZXqf}~zcD-SIJQrmLjWN5cj(*xE58E?q*u89 z?9+Jif*V^_ONU^H_hTRA)t(S<5Vl zb7K`~AVv9{KA*GHyQpTY&vsNf4ky$tC z11Ql$ey|rQUAEYbH-#lz@@@@>WfDanu&p|;*fvr4*+2lVUZP%i`~2<42c$XDqMz^A z`z&T>Woy*bIN%=?Pjzl6g}N-UFH_YPv{F}J99o-{aDW2kSc;0&>SnV$AFzR?AE(F_ zaB%%vjJWZ}8jqzTvWNPU_nj&^+KZ3Fzg-hf1Sx?FeLsvStD1ingE-$~Bt!X=B>8!@ zD;EcmXq%B;2=`WUGgjYAb0Fx9f*C-Xoh={Gq|S6#TxV3UqVQV%vmx0Fgv3S`) ztQv}a(jKOxNhkm8XF6{Gf%qrCl`=3=hjn#*0ye}3Ua^ErRnA7Tyww$}wwN_bUSpAs z)RG0sss-$sXtOuyt#+yKy7^RHKDeV#ecNLXzcTiv&)#eLkTm+DItToSPq}o10F2L6 ze9GbEvTnsh6~MJXHgeJ63{?lL!N>9dWN_*oL*I#7u7~M&Y_cK8vFm*hkt($Ba3eWG z2y1MNrJk#a1Bpw9gcvS0>6g-EKb5eAa1bHCeg(5r9LQlFa>?gX-li&V|(y$@RGBXpe&{zDGQa7 zM>Z3}sX?S^2l7$VK73mazbj4#+@iGr1u^(3kLoYf)x3csI^${VzGS+h`_;-^?SL8I^r+j+(` zeq-xWyuVuf@<)=M4aWh0I9uYrOe=hc3ZZY3`<+PF%cD`*nvf^jE%D5R&(wV55wYdb z@SYt*7m3gA%qW6<-hwLpX_3|&d=_32$xr^?)>);-lfI4ibp&Y^e7U)z>A0J${L9qI zyBY@MB%d3qaHI-W^Po;>s;^j-%?*&z3R=0@Nd zgxqjMGyRx5As#&novgAhPVNgV(5EU%bQCcciwoJmZ(l}hk~e9;a&RH}!RkZ9-k#SL zTi3%FZAB?`M7^Fk&4njUiTU&lCLjt#n&2y%L|vxG++4SFOt(uijf&5bE)#S3sIbts zT4RkqSd_K0`ROk(9#Qur8!ja^H^4e2M%qZ2JVM=7#>;d}?c7DT2(+JAETQ-*JY$@n ziH3e`o=H$TL^J_&vZ5%Nmx*gi^0m$Z#&OZ4jtSrY#6sG*q^LYJf26_pOS#Jo6y^Vl z5MN31>ffjQ+5$HZ=1E;lrzDG~ES6Q0R}ll7M#b28X_nw#>_~bcJ;1>>g-NLoW*+^i z4U=M6dB3-yMDuH*JP=Jp?Jx24g4GTsTPCbN?1`@Ix-rzN7#dmxwhm>T8tjU6GMe)U zxedZ_Yn0k{tHISqydJDIxEFKbw$#hyidxSL?vo!s5`!o=WupMQNXvVg;2Bbu9tX;n zMGmL93p$r)+CfE)^WX2C^;CHeJO%}wECj`sLCSghj-k*`iQ(lBhs^`jCv&&i(hI_e zg2*=!Q2Mt`U7SqU}_SBy8CW2%@~>h2>feJ_W<| z!ThCzU2v<3Sj$ZzN@1lBTwu#1C_nlGY-W>PgmJGE{nqtIo?T!$&aEv3FHeQED3AgI zTa-QyZ#C+_4jgozHkd+baJlLKye2vICqtO;y-Ltc-f1_~;6l)1kQ$E5e$z`zX%zkU zJ;+^_Xif2uy8|ce3!Fg|JCQ12ffKVWJ3H0^Bhq32I?Q3>vBD-lg^Sk!;y^Uyu|i@d zeMLt5%DnKp=AN|8T!3@~Q7f~37ZUvgZN`z87VwEc1dJDGqH@tOEv=*bZh7U@9k5Vm z+;}CUf|!%CqZcpk`ia|aX@tQJ-LV!i{{t?4x*BIVxO`|^VXdPzXH5Le14^1{C;UR= z#35&-eZI(e;K{c!itF;TeId?m5mg#~a9V;gnc-2Z+85APqYvvlO1DUbdHFl5$UE+a zK3uR%nf^t=mb%0>rfa4!pQ(Xd3kI33+zL=F5bk}e^rb)$yY*CM5c2C9p5$vMvFC&$ zo{H0J4cB;@uD4oqWAqDScr0lQDYq#BIjFFl{v8@ibh)sl5OCUP_E;Grq0$5^_b^X6 zIb2n2PnA9Wq{|WDQzc1X^oARV%UPhOkiZW_!+<>OA(+5V!qzAg*G^9*;a+KAr81!H zlK(jN8bIxoffsf_E??}UQJzrNAyFItVpknS?C_c=w0tf z#*l|0N`119Z#whPvt^Vg2=P~-^RN!9#Ct;2P`qW3=jKWngDNpS=)&sQt*X#Y0b_@& z3YDJu(fJ_J1_jW6$$Ss-Qmp!f32t$yk@`x*_C0RXJ@y#-%2W3dmSX%ahVoS{se@Ym zHU3Ttr##qxyz^NVqU|ms&h%0Eg>;930_>24DEK7cbUqt4ZI`WH@d*cp__t4d zkvLX7N;@k)>r;ms#oSm$ns6*v2H`bxe@ML$gX89ev%gx?v_3Ty({2T0?$44jf66D%G&t|>kxdMOeJHw3d+bgelDl*_SCl@c z*yhA4)7zl&-!~_#OVZblsaGke_b9Xpx599x$IgAOTQv=rzg{MovGT|9Vlk!xOrt-5 zM+hp3ZN~0Ld}g#vGqr50Gt*%+dlVgNMSDw~HBxIe`)vWm4~)~dJRf!CR!LIh4lQs* z!(1QWzMASd7;k#BQXK4@>we%K(DCY$VjE$HOUEi%>QXALU=68Zu0d^(ZO%-#W=yqn zY2P?r6uZ@Oi&%AGgt`RN74k+cU8#^A*6%8|e)F_NJ?&!WOK5Edylj_0e9*srRKwae z)I8fOpc(3@d+;9HV1Y3D*`{5y9Bq@YiNsc6<9yU=M;-8$9h`v-@uo>m7}H`B)$(N{ zEiBRM=%1}J0F^xiChw8T+ws4!SEu5^`y`Lz9p6wxNu2FNG4i^>gV1A>Th<*K#DDuH z9!ZAm1Y*(hXLIZMlN8FA@6auu-FIu{0#qrYBq|ne>X-q5{Qg|!6STm@7Ifwo^?OSj zDv|D(GMm%Y7ItXss}@Bah3M8;)oMNY$yT`*rS4$ijno#A?x?@5)o12*ELY2IbG_OT z{6%haAlejsHDOlyD`aXuXbqb1V9nO+B?M|!eyCV!YS}^rgbBJEgnr!m?zfG4p zg*L2~Ibp1_Rhy#GRmxSHjA&?ArN}7c4au@y1FBBHZ2%THODr3-8oFf0jT3DD)5n;QeV}D>WSCjIYqHOcM5Y4xXX=mih8#U$_ zP-^MSv!+C4LdwVRIHhIa$7C3Xmh_`om(DxW&61sLE4o3^^Q!p1}S2Q-e!q! z7l1WwZLK5A_9T&RICWQu8cPJ=LH(@S-%_O;da7}+#x=`U9@W$o>E_aO+=M!vDRD%) zRhK4}HNNZo)TKFDax zZRvQVR3-?IsL+v;2UZIA1SHKqhR6%v`|Asf7p{w4uly(G@e6pbgtEfCh*qRq!&R5F z#JH3h@g{Q1m(=Z{MoPu2HYV)$3&{QB58!1Ef>?hpnl`Woe|?0KO+VKTwJNfT_^H(^ zyFDOsX$LbGWC$pOzMe!A*4R-auq5n~gCRjV2hRopz}-rVL0HUaBxK31SW2wo2NCl_ zeH8?Q6y{;CtU`l`sOR`)$Ts|`7xN0GuL7B+QC$)qIF!3Ye^m)-^$kybUNI;oWzB-u z+VW+#7%>(~#dqiSt$oI|etvh;6El54hg+knm97@@`bCd&-;`kg6QwGrw%M# zDZV&nU3;EJxKbtQ;vYl2T?M>)#m;)=WL<+#N`=zvdZ{tI!ZY|ORN)p3lO03N0%IMA zg|Ef@)T)(DCDQJ?ksR@pF^oG;O26dhu94&D%sF2bVggi?`i!OYUFFA1cgJ@`n=;}2 zkcXy1GqhAIEH*`Hm5I@*kfKu}#H5Fa$@XiM{`0Dp79mymDH_lgYCZJkTl7p;wTX%- zah7yqITY^nVZDiAdsD%%rhj3Y{A9rR=|;ODLw5C7bwh_-@U4&W^I5YcU1|favW4gS z9?;O1{AoV_Q%{_V_!_w7cvp%!_8I*OMBL!f>k9qsQyR zb1X?7T$KgWX#{4hxlKG!(?Se`ge97S`EJ(f zGfdKl|J>zsyrN3Olr?9T_#+Op+Nnaa#*PA3M8o2v1(kt-=dGPGFfT|}1>Bk0{1ho(!%n;v4a57=ZmC!j! zlrBe2wH*3E4qb>~gQ&jc)}Wrl*}fjbwz1P`^ZmNc2`UMVK$?qUR)eRX0t!)dAvK?3 z&Zh=JKK`jqk18p}ec~*S4s&3CIVwfJ(A7?N%A%bNs{86TDv?9A+?D7}e=msXjb%62 z4y5bx{E)!wW#E+aJSE;slCl6t8w-#sq%oAWenV?LU}jueUr@ldx+x~Ypv1l>W2Nse zf&%QT|9M+szJQ=gXN^c9=y>F?{oKVqLZC+zYJ+*k>`O28;}6@)KKR=^MaNr(+bxJK zVCogA&5l>Pa$>TEnJx!jQ&$ptWil^IfGr;``R<@Vkmd z!y6;jOVvFk_q`_rtW^7ip?VIO>@sG-j z|NF82fBkv>zxs=BA{34U;-|PaUoUgSnsXo~O`si75SimGh!I(vv2AdJUXXHXva}ky z6)emGeXF+8Y0^%DjlXbQC9@cHxa!O+2^Uv6DTTlSOSgVYqY7Ec;FIjLd%%aBN4$Dp z-{+AA5R50X1a2wk%Yd)1Xa*AvU*o|QYai3(W=R2ISebI# ztok2NwElSt&g0dt|Knq2gy+>G?pBO1Yp)ZJ6WR{7|Mx(nNZUoL`Avr8h8Bm_#pP& zVnH(4m3o6I`e^@a9pH3K1F&4INuO>r$`_o0k*ro23;LPnQcexFfSB)1D2Tv@4m+qU zlZ1PBW~5#D)gqwKeRK9kP{fvpm!FHH7Kii_*+Qs1Kt8We2oP`za?j_?dr7z@^eDz8 zF>S;dl~~}3i*A9L_zg-gL}V_QrcS~9J!m*EnGINY$R`#EKVju#RS%`t^q0#Tb0K=e z74l9Yp)^K1M+&A$nU${St3VA%{){iF7twlcRBennx}-7)4qQVzF|wAoG-o^mgfJc8IbtDW?23o^2-0h z&PdgBKo&v;>}VOb@2(i@L>nNfs`97Mt!Trog(7CN7qAC83d~7$Lw1{bFdZ43g|Xia z{A;U4UZIQ*GO4^^PwWst3~RIVn3ZSQ9#_aY zUZ%Zwu2J)L;~RjdC8o|%mx-CXjU=Yf;wdHzQ|;eVelGwIen=tFm%vG9a3sLVXL&ok*F?vsZp zg7wHZi7ekbs;4pM&K8mNh??A_la0=ZEi1YdygNCqUAazehe00>rL(`bXvio!q5~T# z+qQj_|4MZ)i~TB&b6hywTPQ-E^w3N0H0I;BKYy@Nc-QzF4X?f=2pS%oM{^uF*Vakb z>c~?MUJOthh`NqyA#<|aUauOjW&E0dLao5Q(hx2$ahcJCmlS}YoIr@jq`TM{SdsI4 zRcQzim)%jiM<#MKQ9IFq63Ud_4v+(+a&<1kAypVIH8gdO!ot;KZYxWG&!o@zk@Qcx z>d2N3-I6zemYjYroqC(ybKR;$ z*&Z^xe+4u+jm14V(Lp07-JIN07(O)1=X$^Ja>*=EHQmvML5On37yexk`S~It8J{2T z6q3iaP(ZAJj9OC|7>Yg2*M#(qIUL}Zyp@tXdeVs5UX|#n%lclO$BI*AP=5f1*i5VmC8~$@eM?8p^#(-Dd$n& z0>ma3!8^!%G48W*%kWD21xUL`clNBkJI82z{?1Pj&Ec#d)N2b%D$Kb%L3{#i&(NZ{ zq&Q{~YF@`+;Z&pE-K6wn6Qm3$2dQ86`#Z9gs<{ot7-jptHZg+R- z2v(sQ_B!O(3_aCVi8b-S?G`sNU|~#=Zpe%4ZZ~^)NT-b_FYb)O-{N>jrRB<|7!~Hs zDTzr#)?M}X9N@hWA(?+FkC!?|Njjuu!|~&NNflHA?^xcfZ033wGvkQ53>%F#sJ3W> zd`51HTt8hs{|-70Go8NV@=Fg#$uF8gqZ2;${9do9P`e>*Rw*VYjjMj7Y~ooZnfUQu z&29BN947UFRw)Y`b!zQ_++V?#r^#aSOG3?bPYyGKBT-!Z+CI}0SVv-66pjn88kT{9 zQxSHPI`jd$txnZHi=4|w$Np5oD$Dpa7+e7igHK+}zwVt;56=%N31ct|7}Tq4(zWYI zlbvQ$yqgrcDf-bZ!*wHaCQyqyK&(jBd{ojuvWI>?EBHM||DfqH!ABdTLGu1Q##~Cg zpY1S$tkU6>)b4BnsfA?9?=!r^Eh>XwM0RxO;!$Q-9Nl?k-~#nK zn{Q#inB8poy|*;d5t#;URC;9oz5`<%O_!(>IF^^zRLUipw8^TfEX<5fC;$PSn$iE? zIB|HTISuUJfhzuwdKcOM$3XqhO#ELHaILzY%BDCf-zq!0d&(}dBPF7+GQ}T=VL|Ai zHO`VaC{&S+-2`?&WMdFuo%?UHyT4riM$cp6mEc(m%e0Bb-(v1g8kmQ@W3k6y)xVp& zPjG+xc+>`bejG3Wsl1R!=^v78O-VT6g;C-g5e0MN%Ur^&qmsn%CH5u-4^tLVQhH*C zMFs12khas(yf8;)3Yr3y9J`b%Vt*mJ+pF^e7jACDmv{iD+=^#dZ zX5Oc!>Y9;g7>cLHa?OINWEn>zqP)2k@)+AJZ%}K?EfUQ!6J1_n#twlRZNyVfhCTqS zIKc*N7cfMuLlCL8b>6r6cu7{WTIGy1TBfNLE2&@#J<6dh_hp(Ah3x z9m}~aAI6tTspB##U9sZaYpFcNfqva<{P>A0+ZBegmY*<9x)-E?K5*cBWaOpcQ*2h9 z)tT$p);T#!s&N{s9UwO z#^cI>(GBOic(8%IYXn7M2PEjvA2=t;H|&cW@~U)v{*0W02v+h9NWUO~XN5)%-ZMQx?p+>`_~F1D^YbIz`XK&Q2`(yhHlcnK$D{s{IR5`y3I3mW zx~X>G8dB8JFAQ$zHLe^NYhO%xRcL7=7bOsSrVcYuSB4SK03!~a#+DL<$rtsk3K`!$ zh+7e?+1llCfI}BX@5$cw=gb*b`PIKaoP!Uv%AOg8r3qnC5(~aLzfe%sQdCp0ls^;C z(5hf@a)Vfyd-~Y{1Am5wFgfcOfp=PB+A>&k=QFyuJhtMj0uUj1ws^cpN8Nx~Wj7x= zu=LSQkRI_v7F^*$2Z665o_|#^Y|-E79BN&1g4qA*u#x6*re-z+yyCY5&)hPru z)+=c%DF8Nk%@xFhxLJ0&G4Z8@rsWBzr8}P>E^h6?${SKxR8*S(ILGd8x68^mMhbIQWv`U*XC| z{P&al`|&jX$H(*k-iqQspUZELj{mTt(9m*K8O8b(Uon=#*kr=g;gpA*6HTMFZP+Jg zUn>QNXzYxPRDlGoD}_N%BTF!Cu<8n>l}mP&N^U8YfwWTYnnSO^0H!OYIcagQQ+Qhl zZ$seY^SUG{mHP^~{$tr_X?ApKdA*ex@cL=?8F20J>2ib}_@`rUgt}*JPzJwd#KP+Z z7a{*-4~D+yG6=@Nb@3J&q5l3hEBv%?sF~s677aoEpt|RYjr^0_#eXntl}|H@g}!G` z2t;7kdoF@S_K6%}$8@FL^NGFs3mxIa^M$?MFPzM8HSDI-aXaj$`xfK}q-CP%g5)#B zqwL5SB<*_AR9QK(7D=szij_CP3JQw&_3!TAirw_@iUs}>yj9pkXQrwD)W@f20wJxz%= zeL&t}t6a^iZdm4zji6kC76`EUtEN{uPk5Y5tV-7M0bdus?3Lu@O5y;2q{?hG`1s6T z@8#(yoh}zEktAcu%D5Uc2>nJ>7-{YWCJHbukj3Mx9Rz;*RE=?5P7*d*9eRmK+^&r$ zg?%!mmj1agZt`eP_ib401U>nYswvcAgGDMai9eq83E~uOvT4uAYoQB1O#SE zYY-9GryByAhEbHq%^mM+LYDBLi%*@Lua)8N_v*x`#ffS6Ic99!UT1rs6J@wQV|pIv zTTMPmldqJ8FGyCLC4IL}(FoMElg!DC`sh8U-rvbUInmJO{R8su9 zfF5h&e4c3|-MG?jPMI}z>io`Bd_^t@oCXz&&#S}F@h}2u`l&_P&KZ~E`2xc}83P{Y zFxp3^F^#l*fpAVUD#uv9s|ZS@JV{Ap_Zx;p0}rs#FPf@Hk#HH0eKpaQaE|hxxxxgX zi3lw^?-8R-E%1g_>Zl%TX}97>YfV?D!oL9*W7LV70%DbEse(yR=9x%vm>veKNR7n- zDDkYfW1shbNy_-gPh!j9_-r(>tCb@)Ut522`c2Svv|l)Rl*HaGIhjO*+7Vgbr{8DD zHUW}&fa$&#M}cW%qEta?H%#0&+3@i2fDux3kYIcWCL8744?}BBjLx9Ldh0L74gpuc zVIIsohO}??g)4H@RTEuio?uJK7nDp0XTr{|H%;^=fmvONYxY7s1qVY;kBx8;cs$p{ zTAPe18Ukgo{fG9;y~d1_0a#Pk8H35#MG>8jT7x{8^NwH|Z10!?^N0|5i9e-Vj@5`) z{NxN($c&?)mBg5#t&;)CW0o(V{FQr*#@PN7Bl;E&?=j)|CVc>d-E?gKFawqDYC)pk zIq0dPOX|=~;n0d?=5;8T6`}P@CnA~V7@nGjR*_A_4ShY-`mbby0x3rhz$Xz-l*0tL z%lGPxgaRS&iIOzG+pnZdk~ReO8rAKQM#&#R^=_*xX>=k;$aGxH_=IF|M6otWWe9f? zF(@RoGpcIL)m52SrD7j)BVq|9>`!na_)r9-w%sfw7RAj~*`*~`dK|f|#Oyadnkr{B z-s$UJRL~0BabNf~b;6wx{M_ox6fW5+2B2%ic&2%P)*9f;J60CnKJI&A{^+_}=_}Z% z`dHP5Z^$ZPLu5g|dTn8Uq&S;JlJUh&&d#GV>0I`%p#RudU7pJr^CoF{cUg1-t64Wy_ zq<*F5A=MN0uzsZ{KQ&M3zTWq~b2HzB7>~3i-<%i$A!Tw0lXpV?rv4Y)Ws#_JBe7JM zK@#NYgs@{Q=;n|=%H}VZIoNb$x4iMzz5wCB!9ytQT&Q{2XffES*lf%Q)rT=m8xQZQ z#f&SGrtUE7_Sgi&j8f-`+a6Mx)gT&s8zv~iQ-$8(#EiLbH@QJR;CaR!^#$nl3x!^C z1Iw~Z=vzU-Xec8lYbI15ba%~8xxZ|1w0^N}bqn));ZzGR-fnwgX{Ym{ehaYke<6E^ z0)%BW4P#p@uEbe~vNJ*T-t{l)E4Wi$l>fh(&68jP0m$QNI zRK9OS`mHJSSDm&92)TBWa-4Oe@?|>-Vw=Bl^bCZBa5ynIontn_`lNSccr$u}JZq(0 z*|N~-tgy#s;W)0%w%?t1K5z3u1R%+&Bt)7k`EbWy->99W&i~-1>=*Oy4mEQXu_=Sm zhME58kd3^BFC9jc4w-bttgHN6^owG;Eis60C5Dh10`@*^hTGVD3j86)F7yJOwW;-* zT(?cfao%-ZE|U!$UhDOv$*i3LF-PpTTZemFV(GrHjv=~7gA_&z*reHE00f7>LeMM7 z(Hy~elE4w3P`>c-fer?hbMeeTu8c!|*!5c^E+Rw6n z-1Et?+djk(gK?06$;sO?Fs$to=6A>u+HhFSEMAR9SB1ctr6w5Zwq%c?ZQjg0FlN3q z*~KW(*n?724$1Y&Xfp!M&`Z+TO64Y;(#taA^+vHVesL3QESV<(s(X3wCnW)BwlxbX zovF(kcTnFrts2f)?kw42FLV!ncX|8vxv#orRSrDX&bN(IT6J_apu|ueT#;VMVlC5M z43tyU(RrFO(m8#VQt3+m&x}OHH&f^J!x#j?)Ev1nX{C5XH;6iXkPL?fKkBI|FJUL= za;CHHh|fS-DQ*gX=>hOb1QpgiRsR+;GCV6=Yo5;Ex1tefjq)4G*8@RDQ4=0vhyvu12t5d&V9#i;8PswJ&o888}e!46e^r@rU^^NZSmN*Z@ zhd6f}z+z669o=Au6k0r985+jJP9oTvU~<u1Kq~V!cT36YfD+kl z5{U>^=aS1oUH+OY^{vBmzE}p|iM~A(Q?6hpZPhN(z=n(gyCMxun58VYRHe-M7VHXN zYYi#cxZ~$vTIP>Y!GQ?dL#)QK79W8dBzx{cF|)_W^=z{y~xUr#6-#TwZ=vKxl(mfOwwMGy2i|V0WecFVFdO zVGQ&iwE{8qIL#E21rMtQK4SlTV+6~65!5?KvKqnBpM=te0!8L+PkK9TE-levvdSpwjU!Igq2u|J3 zG2zG$m?zB1v(9R1!_cztVt?ZuR;4N%Wd7aqj@nd|?qABllYcZ&WcsHVlykFnwQ@K5 z7OI+l_ZCuij{gyktr}21s>@jZv$h`0nGK-Gn1aAW{Rv@)7-2sO5r2S434#O#7Nn1V zBcf9!EGUvo>U4Db!RqK7s@h;erPy7h;;3`fE!y2{{2y>1aJ>8r&pAFWE*8w>jgy0e zofJE-Gta#zI@>PC&GkEdu)91j5TV2x0|auiHu^3trH9S}<*nIHI58L=i&wx5eGEtJSoyuT)|ZP(X)N8m+;f;v-#nH0P(gq zg3LHG75MtL2>pkGUeh#U#u}mwrrhsbpO__dKZTs6eSm3-xf}M$PTE}ZI*ts?602x( zZtK~#=oNn#o3~Bzc#6_yZsf2HPWM6P+K;p5TBVz+o69H$j0#AO%SD7!zE+6E*2eHUV6ZjaHTO^{}4GsHM`<`VWx+n4uMdnId`28wcX7D%F2E zm+{zaHr64kRj7Jb@3pgRoQBhut7NIgIk-yLN%Hkv5{Ja)7jJEbEhWI(>NN43G=H~P z5X90OaK(9Mg1dyUhZ@CXHNB5zQR7pg&tj3>k_`PZQHuBZQHhO+sTP-+s2LU+5zmq5Ju>FNEisYB?rBL=1r7XlkRRhPt>fuGL2Dn5`G2n!v{{1 za|GTQTJjpYp58810RG64BsfyaPCho5a--WbL%uNk;1A{9fW2DG=<)JWBxD;~O+~^h zt0DTQTD9#lXy#8h63K$ys(;YYi1=(=vae5pv>Ayb>hE9Qxnu>ehD-yi?(DM)-{C6) zNdGXNM8&a?;lIH!;1g;#lnJm#f2Z$$(w6kIHVm*Fs z^y5$!e5GU0Y}AQrE=fKSkJ=?acXV6R^+1N=+^H6MzTS_=56bRr-n{@dUivAopN5_VBa z%yh1is|ecX^CUBg11IL8{3f?m?(%)0DudC;V}c*gKM7RhT5=9x+hnS^SaEnRD9tE; zrt$MxecQ1AEjv*ADm0pUnVbl{HY7edyS)(c12GhbBh};2ePmNugXci$`*$WZ15v@j z6;|VRTbq@*^}hlFePDy6;J;>Xu=7ST0E zJ*ie?J|7p^SN`m4@t%-5Y!ySbg~4I7Sy}wB`>7Ut{vw-Bdo~g^Yn?11a8FYOuCOR_ ziUL|$XD9?0$2K2odxBU^m1{B+F3%W@1j^=MWAK4}RvoFqcE7T>^<-(WD8`p}=dF@` zQk21r;ht&;(~A7gMFUY<9E$822Hf5=AOpAYG1Rp+@*Wi0HZ{b}wxD#3*nClR^rMxL zttLGd4?fwHZGs8MG$WRY1=y4l!!$B9G25Ylpljq#N_3J1ys+ffe50}Z8e zD>(h!C^Y#YvL}0DRWz$B@{JF@qgG*tQFe{;M3WLGJv3+NxUnH45nYOujekfzUD_tZ zt&Sw%4-$|Iie?24Q-B?o;EXfKYJc!3W}h#tK{n9khd-zThI}oT{71AEm<}IRW+g9y zkye#Av{dFh`5O>5K`k_0eeDCu06ie=tT#w)=){R)TMnk4rPQ1pt|;@{sEZ+{=< z2nxT2JsG7;aI`fqmd<#2ci^WZ2rM=8`@?up*|3mG9Tactu>klh$^Av4$>WVWa}ZG& zJFhoFyb2urbY4yZGg{?jbgXdRw(*A7`lYAc8~Vm%9DB2M*v}KCV}$)=xr$Ig#P@7H z^?PnyC(+Ta5ZbRS>X%TYQZLY7xK3tCssafr%cWDzs_{Y2VwR4uH9ZbC2HufdNAwL8 znb9QLVP?>!(w`bfxq7!5lw*6pfOx2ke(2OyGXieyw%p|6kp0Tn zpLNN}_IVGVf5bNDgk#YG=dui$-{cf1r!EkE#J$=1J9oeFFllXY(FJcf4bjY-)~dR? zGL>D;RUdvV&EgMg)D6?dU$jonD}&NCwy`xf=I{NQoEf#^9PSj0G*eQ>XEX6^%+mX> zhjPUpZQ^R1%8r@iOJi|!6u}{qj6QB?6c1VQn=l?T@s+VKfX8hAbXluau{ zu~-U_7hQ!Au9d$$>Zdr#gP^i z3Jnq#)d0~JMHW~2ZdHqh!cn_V#8bVG#B)AsJnVrhPsu^V&R4Nd#Z$A3m6Bwxs7~~XL!G)CZ=;tyEe)-w=Abmpl&z!~{afjXrqOdLZl!qY0(x7)L5$Gf-T&Pzumlg{ zk;IGAY(zK#q%-B`r9#z)JyN;l+Sc~=GWROR_6pl7``pUTGUw{=jji9y8~lU{f1<=5 zS1rnCTc=hjMbVo_jG?sS*1R7q77#U>i)wt|{{X)FX;p!Vvmq7*^Eu2r9ICLGuWW%m z1K)TU&6#31G+)8|DOq z?PNlT9Va2lY?-moSsx4s*R3;J4JFHq!nP7|u^6lQ?%Ve;;ZQQEc9{WC)l*KhE!+5Q zBQ9N68bg?E(;hL@Lr9fpO^;&*a=x4Wf+G?Q0ZuBJXK^qr5IUkU4jgQ`%~;gJ1RIHZ zmvZYFs%NdUF;FUA&`2gcmM|)w(#(*T;cIXgt#Y#O+NhT*H>E*spo=VbA>S=m)XW1q z62=Xrh9wHAa=NIzsqmzCzSO{Yp|=%p=;;_6D`=5dFEcsOxiTR&q0#_y#;c3(6eF>8 zkM~D;m?-5r@|n3K4~*cSyoHIiZQ8dDS5WLYVC%@ z`pdBS2I+ulzKZf$Sc($XDhuH9m`zz__F;~bv;D`167TC!eQNe_dkiB>kj47juzLnQ zOZzqhEH%O(r{Ljs4uCsu#=ut);67LBe0$0b3I1Sc_~0$Gd;}1bgezAMRGsTQ#~QL@ z^Qio?aN?K3`Q4b2u>DcIXO49Yj6Wiq#kxs5*!<*=XTZ7bhJeRHT-_py4-c*4(LcVu znNuo|Ps%>rgkHO@>+~zi6yKCE)VP(Ay6yeyWhuE9HiVakJgY4-#1>J9F)kjThR;sp zN#qUowmkF_O@3(+jIaJVXGOT>Bo2iGLCS8-aEL))OUk@?#_I~;T*+5s~9iS>khV|lMR{< zrmehuXzZL>mJDEc!CUXt!FG1al^wd;iFx-0*fvfd@!FFJ$u@r{Pn+v|1Bv#&bOp&O zD?r#i`v!o)$$GXZQ|5NxmB9u^*KDqNrx(;Kgn`zq!RO{zfo#kJa}=pv=1+rZj0OKn zXoZd}#pA9JC)TD;KwFsCRX-K)391k3im=7#-MacoIk&fa`7oflySv!vNPpA!iYdz@ zmo1NY63NNYwZPHE>+m!+>?sdzUKhl--)#;rw@pj+mPGZo^359{CRaqy7Lt4&$Wu5U ztfRcJc!3keP~d|qF$9mEDMP9PEK1UjP(fIxRDpTdr+pgjcdd+@L{`GSe#teyVyLwG zq`)B#@Ri2kRN{q@g-V+%G9ngdOjxQ+8{EEfX85p8Dp+QADs74<5s&ifrBQ8}E3quj z8bLL+cMcX5F|Sgdt%`9;VPoH?${L<+kJI?G$P|zt7@|#ry#V|9j8A=LTFo}-^-k}X z;3IH!zi1)^PxD{T+T|h1qtB;V2;!xAU4zh)In@rWVVP z^3;_0;E3l_!p`0T)$56B))#q&Rnr$qV4-V#u`h{-?Ku;V5y z$@3qrij`fQz8f(~n%mhsnF@dR!u{WRMf{gsA?ds6kRsyf-!g#>I_-*0P{j_a=5LEP zFhS_3M`R&o{Pp;;jmF&M8dLQ)?a=E(7)Gi{@)2|XWXG8`*#uLfkno%gUhey>j2uq? zudf#{1Ee}+2_vFFN){p~p~Fy(m_12gC>}H;$%rG&T&Q39@fwkatYIgxQl?cOwGXA8 z%4Ql=SWgAZHkw*F+_HYvSgnICsn%@GQ5WfIwpC{Hl8W|swthOz2>fS&KkGBt!p#O+ zrCt4VDKnQ$E*Mt`*#&hbTHTvmn4g>(^-YaEtNl(oA8DB&V@(~B4u zDB*s0wQJ8ERs7>q@zo}P{+A`Bfdcc@x=VS&qu=5MMSt%_9Q32SJ=PD+nzCGDG%O0kqrwP<1h&Ee7|s1>p%Lt}N{n%k z;j7t2c_BiDvrnCCSjNTU%-qE~**lFpXqMs69H@=1_EmNJdCLn+NGf}z*@;e*49jki zuYJ`kW_uZ-O|vlVhnOhx)2Pw$Qhf=hfNhqA7?;^is#Pc~B-q55i9y^jiOO7%FjEec zzl#X;@c>AZ)>sJ~-5uatX(qqb``d$1)~GHfI=B&vU*vi8NNG9tMciYWDcMl7MT3$1 zJ%^ANK$=L6mJ^0UxrC0N$onSY9Aps{vgdB=6tyUVE`?L3{v_*Sw2;%EE42%O)E*Jy z1|iskF>&jqL&UC1g~w#{_<(yW88-(>1d1BXMv>S934I_nJIGQ&Q!Z~Nx-MWZf8yIO z)L%OVxns5*gU92ha-|bSdgr76!A2jpYkRW4u~D^uEMi8z5 zgxP28ekBMrwpZt1usWoJkgGsC{bT@)5B0U4vVgRJ)-bYwwKmO#rhhjM_7D?z+)2=Q zHG5fFtF)1$2ut}~CP9sB;iy|{sv>M=CucZ-wJZzv)}^N#lbNPuwc9LRi#3%2N2}WC zY*=HN>0#lLvnDf>Zr(_VwIxz&exa^vI7%&DAx#YH+hSKe&FXkGx6jUpnmv>7V3b52 zcvN{HqS&F)fR?EsSurRXe-R;5|yI#@jP?0pLFk8)l3Dtl0nV(I&}ahG~@oQM?O654|Ef?$jWGwKz%k=Z`QL!JaW# znWI=g#cItiD4*&LmJ)1MrgPD*vroUMKHFLovklFI0lRMWQibBru%yi!B(QMD5W~aU zYwp2Wu5U@m?H97xUO57y{Bg+9puC4f-!;DEdTfA1_2%wQ2=k?5ECeeG=wzv0N5se`JJ*4G7Qp}2A(j=7ntv? z?{3X6lQD2hTC3GB#;jGXw`xgVYmntSCG*`$P!VMJ1eIW40=;Xm@Mai7&#gW$6>SJ);O9M-&R{mNBCEq^JQKY_Zzi@&FPS z&Fyl8B{`%DnZ^{&p0n;>eftD_F>*C!ss$Af?u%$f2m58mM-?-;5PbzYNwiTYHXr6q8R=>G>S)W zsIN}mN}2pX{R`Zi7%sr6Lgpz|d6%R^Pr+MdDky}Qtx=hok@M&5tLNK;@$=$}p%=Iv zia9_}Cd!aHfUuNoN+KPuFMM3hgK;IwlLu9pJOIojbT=Vn6uAdFUl!M9aAkTd(vvZO zLIb(#=!Xdp+LIb-wbV;kf1Zl#k`$xIz@Rkjwo66+&t`KK-ElRyWi;c2I_u`&8B|9d ztwV6CJlt7&*2=6{W=yXOa*Nfc^K`>MQ?@L6Oep80O04FyP0PJ*3mV@oe5J>=rFE(( zokepFE$ygb(5990B7Y44Gf-e24_=QaPYf; zha6l--O?0gqY+nd+dSM=Qe55c#!N5 zUds58{gcBG(`O}czpX%_n;|a@t6COe&0Pt#+C5PxTcY?>#Giyge4w8TVf^=HsFwE3 z{;O?N+f~f@@7e`>GM~}8MCE9%Hfr!dkHqW<_IiiFWEG&LlOsqo<`Oq8R+)+G9QQGM zTjbw=F>qsH8}1+Rs^Eaez)XXcU+Gn8rj0kc{Jz_}=!M?A0K1=5!#IltfpjPhkJ0?D zq%6U?b^@t`@@C=_$iwVOa|Gl6sZ%*kBCH-voZJ8|XG&^}=ZOWTU}`%?X`~z$B^wrK zT1f=HE}uJ$BF z!IFe@2pg?F2Je(fiW267eO$fdU+S0Rr->>b0h^u%qh;08?k zyYX`%=-^;1PGR%-boJe4qdsfZBADxY0r%#dk@uEvk(SCdCxzl72@VlS5xyR!LfPR_ zW8rV&I|fiIY7)h=>&?3R2T>5GUDu@>j`NdgwFbeqbP^B-*r+U!;jCnr-Q0s;4bd>j4LB ze1n7ySOVbcXWjC2*itUtIC@VdAHZ7ET}2Bw96Nx36W1%YsF(dGI~VC znF$0CS)e^gn_C2xOJO%bJNC5Tc8*?P6YWqN`Ib?=b9>y!sVUdvvc<6uf#8hk+Cx=- z{@w!kYgso>nBOug-^R1RjOz#3J# z2w^b;`|%!YS#H>tD*ikg4?D&K91H4(jg!t}BSb|NL7n(^)$~7D7s>(6HMZWOHtKW` zUj^sE)#n}hRc@0P9y#@Zb!N`rG@UY+O7i-ZnTOl1eV1M3LtbQ+m}gwZU0_a6m-4&S z-3x`aM-6Bad^L?LU~hkS96ZrD8hY?6&=M0`N@ZIQ+II-Wm+=tr-e(x9jiGre9|`8U zZs&dGr!~xN*6hUnCulioudpnJlkVZZ29ltSG!Pp{SoyJFu36nE7+=N$%jpd!5g!&%=eY5#C%Qma>BRD!v*jDt_RWF z_RgS%x-0u27lc`}2#q00iHlzPM&oWG)42GFC-t^$nv+dxF*~o#>0$NtRHQOGfqZ=h zpTM}u?yJ-%oT~e2MXGGsByx>NdU{+5S9)8FE1KVy&f`2fEZu_hQ2}6X;V3-Q$vL(; ztuvBH0It4_HU9NCXw+&f5!oNth6bjeB6uf|?jy~g%N#Nsw$9dvYlM3gn)vFO@M}sG z@97r%1W!UxnElDw^hJmxfHWT#7Z&zGNohiC17S;8w58NT4+{y6AQ*4$MY(Q3raW{_*&>F)X5z$Ho@D9z&=c#=kSOMIKv=P1L}k~SBY zCV5XIazo8pR&K4xJ;<(t-q!Jj+628~IYvNEM|)VUxn%VguF87(<4*wbpxX+ zAx-r4uR(j9L_ITmBmIDMb%Hsl`0X1J75(8K)r}9snS56!{2Hk6B}4g_<%rC9FoMs- zC=<}cHFJi!=!h>gdN2PGLE#FUAdNSOutwRSlyDQmE%o6&)Kk{geBO@)?i*PDa@i}V z&h;!SxsQLB_~!$Hc%R;qt}ZN@e3Gb9j8o*pDT0(GZ&_~KirNVRNlie6X>CkvOB{SQ zscbxc{NRHOii385v({-fhKEeQqz=U-?p(R2c&JiFh+q zOR{t-o{HII0yf>sphm*(;>=vcv2^|oUW*&~=h6VP{#SF9w+LekF)Q|sGjak!eFi1} zq5QT46xfMSA*ug-6bhd4xdLd6j#5kvM2wH@0pp(y(-@acvQ2O0b?6?lA;i7U3JWMk zsG6XRa+6ZwMuiC(6{aGJ!o-Xm+zmXFLt^Kdu&Ltu$QAa8l`NBEI;MLz6yNZLYsD`R zt4-plYm^*>idQ68d*yfIr$vgW97!wuW6*of4EWWABeU?!RU&RS?$-#T6RSQsl)%Io zODkllT;hj=dsMnJ$_h>?S^X~;N`AzNI3+zMw)%>oDosia3ZU}-R$td> zX>IL66D(F`+#d*~149xmpj=g0vBce=vQ%0aTJK$m4T9nCi$k_AwL&9KANI}ec%I@E z-rm;N1G+Sp6@_)CK5oc2Q0zw*E+n?uQ^hJARthF^yWDgLJIkzp4c)PIQfJRZCtAxi z##S425S{Z6VyvTkgUw?7eP-pmv&^-5bzfk{MZe-BuaUX!meY^b=Cw)3ECV01!dLbV zfa{fi7a!IdZD(egtk+&2|26KN>u~8rsR1pDwSOEz^LNxUXlL8$w=w01+s{kij)-%v z>5}t8Q=HsuWG97r1mUyFy`ac^Qpv^6gD!G@G$8xq@YgK^x>ycm=+xgD{nUda{(7+% zBkX!?+zV3lBA}dz2WeZcR4NN#dj7oon8RF!55}PSptOjRiUo2-Q zLEaLIGI8MfMnO(dZ<3kNsdbd%loV}kxriOxe)(L~&#IAWk!8f3&_$dwhFVb)#KLHU zQ|$f$CKVHsa7`*lrm8RhHX|62PF;`q_UzgChDrbXD*Hdm)Ku+k?M*DrEDeopO#g4# z=y!i)_eB-dubJj4Ry-+dB*Bt7XlKCzReENX{N@F>^{5;`D`nQ zQpSKsSoi~GKAzpd6Q7qL1Y`m>#?duZ}LI zs&xrfCk6verjZ<@%&}MFp*3vq?;3DL+-l0q?nO(xF)!711xY3)%O$cF@-yfi?g7F3 z1f!oiwTs%a^QRX3sns}lwXxfbXsS9(nKfZ|bDgLT=k)~6ZNI|34^)5f>gAoCRaZ^r z$hB}EM-|#So&SDPRZMA?b@ViruFWdR581)?puN6p2ZhIi*Wf7@qV=i1g*~>Bx~N>V zP=Kk|}yjdDJ`bB2W)PPc5G*0t`PiLYp5QC4Cx4#eR^ z2bb3p_SRw@)oUE1OZjPBjjDngoRxSjTysfk5u* z{Hx~X19!qRhX~2O#14zWVUoWJwL&uPg{4%xQfg0KD-*5drpT$7MpBd6)lL>zKQV{d zXOwWtGP7E~D$&qkWCJMc<4ay^WkM0FDZ_~S1he*OCD{-TWrK5;Fb$&?0>PT7u>!L2 zOK;;rs;X$^NHesTbhIe1vI$qIk8u<2h6^xP)im2#iIwBlRg&do+(CDNEI%|Qr)zf~ z8qkCYmOTmvgyAUJXWC7%3r$cfWHR;!LS+Dd4|NI*J69Sc3WR3^qcPGQkkw9}AHvk@ z9lN(r)7`O{y>&}G>J3U8NC8_!R3hMurxvCn1;LGvQcaFI>-s1lA@9L(HR#G(ScXE3 zV=`t=D>v8X11loXitJN!-2$yNhQU){I4?UcR^q$}b;*m8% zJKcu|YXnX;wa;+{zp;)<0k1)W^I3dtGr-Gz&LqEZj+gD^p_<9*b;k}{%{#hFKyRJ> z5a1bYySePRmdFvC?V2l+tHv1Zu5UfCMBT0>y3k0+A>8Qx-fHmb-x%;V*s$A!@-DMS zF%-q%Ko~~Q8B^{Rluwjpx{v$==MFsIo5!8ndIaiO8 zqDB>h@1n2F_yr~7&lDRktY#V!jsxMv&(+s%IVLZ}cUr?=amYRM>QM3>WFLIvcAY)+ zoIPTiH$w-eb3wigNk9ztd#!`Fk_do05{}Zdv%9B=l{QPSg(t5N^&5!x3)E4(#Cm_j z_Yvi3ZyP~=%)hmqPqUnOOeXFTeK#;+os)ao2$u>!+{Q3+!4l`wm~iPdq{ht@=_srPdmciN8j={>*$=TGC!Sb7? z&j9l882+!vzQgywum3)VlYM{xfBOwV2TNH~XJMF^~4+tRof=CpoDWal% z2(XOA3FEMifQ$^KI|%U_Nbu0R21`pxuNxKqop2yb82Xp}(Vyj!y67Q%Gq=+F?W~Ng zm9`U%9}ugH1x4ir$^DH0P_@P}Bh|r70811#rM1S20+EcnCt^KY9V7R{2gHEm)>Eh2 zoO=!YGsu?#XYMra?U>MBhZ--u7JTmuP&IO+BiS?5#zDPO~A;_~VqGlkbC;&0nBMZ^e zlGWipaYo5EhZq7!9A$|e6V29?vBau0Qu2WI)H*z}#=~>6NbUU41q`kHIdtG=h5S$@ zsWWMdBsLl_u4TbdE`#b|fNQle-i5{}*Kz~=#QmnXt1lVviC#%x+52dm%S^|K&+N4) zr+8xT{f2n&2g6E6*51@ z6?|k~_>(NMKl6GHGC#o;7;-P-l_+vAW{v#5uf-?wwKz`v%a3)zNQ447DIbjWEo6iO zyi^ZNTakSd3x)&_R9l&S$7ZO=Yr!t{{r5S5)CZl#v-C%e#SZyxTam2P2cCs$WL$x) zqz`Tl`7Km)5;A`P8{KWN1%f0`u$$DrW;17`e9f4PuKdJpZG4HM+{@|x3$B1yFc#N|FBtRNbhiuzzr^?T#k}i=dV`*N(EWOc2>K*E>O%=e5my%ndMDPf zl(h=-<*Y!gD$^%t9a>3}CuASdrPj8CwR-ZWAIzMnSd)5W=t`Z}w&P#TXp7*~dD@ej zI*ujiX4g8s%;xWIRPJBTY&t!N%wu^ko*kACXJf6iTS6B!>2xx&f8ROk#59S%341U4 zVDDtb$_|5^0h`lQsY}btntKJ`jE2zm328GmT?SfwGo!*t#x)8Vu;7Lg2LoReiutjk zUVI%-u_8zZKC^Y_P`JwR)x4Rl+#1=L+!UnI+-vL92{aldr{(PgA2KyLw3R6awGs_m zsnn**nv#C*QEz`*`I*hgD)YZ?nO|J=CGfQ`dZQA;If^7!j5xcNE^EVtDvB81-Ov)O=y|=%M z1Fx5k^u^w5@D*x04T=>Zj8j`-qj~;Gz~7)R4RSc?OM-Y2L6j&LH&W~)P@{v|0lmU{ zwyeme?Y;11se_V8yv){pPkvqj!OZ%| z#?^Q5k{%1(GKi!=z=;x{fQo;GAOZ|xP$SQnF#kQixq=ZS_^)Rif&(20u^%W7ibzKKG>lA0kwfc2 zsLN(dRmZ+0IPf7y9~%@Tt+LpbBtZ87a)Di-d+BJdF`9Pr`hCB%9yu6bs(NOKeYnUf zbM(AcF7z@fRm@c{S+a2hr4(z?^Wn^+$CsB(`~vljA`LM?W&*X*&Go|h-ci%LdlC|g z89>K3`Jn-jBf)&UxNJ|^0296NjCF4bu0_Np$f&PN^gVJvu~33teKZ&63P)a~`3db&IHXsR;DeNn@_g#cBN9A*&&vdaf@@kPAu%7t@;OHxW5%`4 zZoCfd4-6IX_I)gIfaS{PAbEJjEsB)3Tc-_8k&kfVpH~40-?@=|PToYie!)!ik<@E4Tl~ni7{+?< zUzMA6iYA=3a8oys_a7btlPAueWLe74C5&VUB&E&!ft&3?5u&FVV=0XX3L=#Z^GyB` z{IpF7jR@nH#+T_+@;6Bi13Fqkv1sPW;%o88{RpSnzRbaV`<*ROxMdT#Uswl--d~LVfnY>{kJYk<@ ztVB<#Qt#G+oH)|JgGL+NE9Rq3N_@03)S@8_Xu|tJe(vM6KQ+3L7s*y)B_BI@;o>Gv zl;!G6?&}`4ZS+uj!~|fUBo&LEVpChI90~t0`*yQpgO_69`C8Sz~_t;S$Wn zTT_nZF!>S_B$=Y-dPmSeFepysePhy>!{1MqhIyabsBG2aATVvBFdqEvWRk%f{@d;J zeQB6|B}b|V!7s{NQ@6pAInnC*lLzp)D{>qkmEl)voQ!9uV4j@JRa5$~^(z4LqH!Hb zbUKTOy*6g=E9zCp5ZeixPn24@!;!8?uQeagvDz3Bzm>iLoM2p6+uvS zNZh|593Ls$^J+nfD^9AXI9#toOXlb(QLlaHhq<;XeZZ~z}B&us-05r`U|NQpkiQ`t63kJbh6Z9iyH}qCX3P)_`F0@@zJ2gG0 z^WfEJ^SrQ8-uCMd&ua!SEr478+RX(5l*hW2L1!uuAjgYqBdN^TfT130pjgfO10!>B zsCX*vY=14cMHPEHG_DpYPYAPP*k@e9h||vCHAHJ@krq{|KFF_;y0W3N$2_BM4ndNJ z%aO}BeM$ z$C&&BO$K7vY-3_))=+V5w{dZ7Z`p>$$gZaZWw^Mci5->SHGRg=EXal0%N8Iwn=HfW&z{~t-N^j+b;Yoy(YxN28zY!&udt6c(;j8|C;xs1Y<_uTPkln!2>ba?4yU2#X8Qh_|#4RN1U=Gpf z2XqX##JhKjYPqEhxV4ptR~$;e&FB!QPb<462|i zDJ1%PL}6M-MC&PZLEo7R46LD=%XC4#6T@^)fL}o{#`rOWo)F__I|cl*!gQ{IU$bEj z-9zGWAjdFt2=fYw*gFM!PJv%BgvMk+4=3s4cS`P!n}q$kg^ow{qwa)>%JoR@4=lCn z_00P{ilPnt$xv;i>9F&ziC3RjpQqQJ22eb>Z-@`qrd2V>fSYCWIyXGVGS{tjdApC7 zjOk5&>t{t@hFL=Z>{;pADc012I)1Y`RN$qguP5x%T6rUs-^nCk1biuYt$$On%Myn3Ukt#!LsR z$~WFmiC!JJ54_~f1>To(0iP6hf}C6r2i}_r#q@tqIoCD0@v>X8T$cGGvR(Q5F-USA zl{@8hc9kfeIu|4TQI{iOpqh)Nb3Agxy2rqnj(}g~g~p2K!@__R|Aj(WkL^M55cY1sgoa2YrT1)FTSI zW}{xFeHSA@CZQ6K_(f?AgTAW)N2Z-TBIlF{@{79oU(!1GVvq}E=ky4@g|iRRpyqGC zZ^ry#W*#UVOZh<#^b2y(oE?^%ikrT~534zd`tOUI{AA6w2FQNvIgB^|MKfE+2fiXp zlkrYLx2X#@U`Pu3%#*111zoBtHs~z?!*NBrWX0(Bqd~o-Yq5Z%qYwHe2Ggoft1;*! z_0xj{@v;VV$w=zw2S0Gsku5xJ4ykl-#L*S#cN{87pqG_=URbQJ++G;U8Y(%%13eq@ zl1BNG21E0zbUtN$;0njLM-SV^4D1@@e7Mo)Du2r;yf6px8QDwB_XDgf&w}&E*X7a3#JjtIWKU@ZJw$aTc{1iqai*E{Ops-4gpTvb z?r!fUPT@6oF}ONB#};?-61$&3RT>v}ai3K)KsJ76k0|dt z2b7oWM{vnRIO=a9wZx#GMhN5|+>B)a2Lh0RbAoPln+2F%As;-pfOE*uPnm@BDK5}0 zICGrRx{5cI>8V!eWIEiD+xZgjUL7Ot*?wE; zvOPx5DO+7Cimv7CujK`ddu*LTJ$)TizXDzCdA<$V3UmcH_5isLuz$h-3x5jVyq}>( z{_(?6@_&UU-Tx10^1p)>mH$szu|&=P+t(LS*AQeN;s<;PZ~>y0U*;I7lmrYBN2?6f zKw&GRqvZwkyMu^nWP)jFvwo$d8SrnJcc8ZduB{|Ak})iiO8(}~-+wmT?Vsh|PImbJ zf(L+=hY&()smSUTq?dNhV!)S+}F!r zA@F=iT*>pGmVahIq^t+KQQj#OjuqptCx7d~h?Xj~$R%4WvX-eJCY|Q1MXFdy zyhN(0Ib+Yw6qj0dRJ566%P-;KT^fC<972QPSCTYvWnKk|(8XG6tisId91+uV#~l%4 zvd9Jm+p4ihRjY6jX{3`J5#55EDof@YxUrh`Emh(1b*4*I0go2P8qS<*jI-%}g1*YB zC9&x`5i}Kt$6`cD@}|ZNoDnj!FSZcK>6K#^C5U0n4 z#*pnHZV$R|V&sg*S7Erl(0YOr6jh3ALGmG(hmxfIXd@U^j@rVi)|c*yFL^#PdCC^* zvim??9)uVBueEYno>V98QoW>t3q3%+TO>u~e$|?ta>q}aX_P!mOfsLb%@cq_+EZNh z5VFgA%)8^hdUU6^Oc;~dG<9OJ*laKUEEa_l6R;1EKJ-s$V-Eq#PPvIndUV+X1)At3 zlo}D*n7T8v`JcA80TH6n<;@)&&;he zGxwbN-}8RhpLW%*+RN*=p7ku@1dV>hadb0KuT&ADrDkc$gWse?ZdE< z3xr|TyiIX+-0;#>MVdqmG>S=jC?~k>v-o6u%@$z_+eEWMERq-XG?igpX`F*k6ZKP3 zU$A%8bEz`v%JMQS!ouSUHIjKx#zuMphQct&7Wago?~y8oKhYFNQ{CTF3~GMB?gut0 zkk@eRYnk5cUoL)jcMkae0Jeiu?zSJdo}bcVO9&AAL5DohU5tj2FB2^%)Mz{N04U~( zcZtj%o_{diJedvnxC)s%;F-a+0;=SO{vD1UmdT9E?C5JRG6H~ybEjXj9sU(M$dlSBbfehfW3zsgryeU(yhx*) zDo0GVtd!f*e)mdTkXv#Pb~)%D0~~S@Kj!@{6Wgo}7zWCKR@XX2=bz}63HL<$gu88|a_c{*@=@u_^%czO2C2-F{winZh zl@QFcN3RB5)0Wi@(yHbdf;!Tw#bL81!soq}i)Y(i>Zs!qif1hzAcg@7F3VOm_z#Z2 zEo~uiVtxnxr8tgzBy5Sv9b6&Axd2k;=|C{ecRr)WN}r+a*RNBQH&@ns z>^e>r>{qy8w>x=E7(UQ6mpeZ1Fru4+Yp0^#g3%}&(5WNc!y&tZjT;6uLK{*)&?DTI$HP%Y>K4`~TrDxN7z|@+Hr^)5qz=?@v2L;D^0Z{>m5Ohfd$({+rbEJJ@#xB!iHS}vk#u+C=jH#06dxx-($)CVi_>N}@}4FKJ96^hw?mbpkoOSIH|t0b1?LkvGNY&z256(O^r}VeQ$v-(ngd;aVo17h2!^DB z#WHm{W$z+H>&75!B1Ys!n%~O(siB$WVEc^%!dWw~C3)TsnPL9cCc|YG8e`7oN1C{R z5vYVL-y9|!#aFMwc%#X#8Z+mxNo(POew4KpZNwfN>xb=G?nLibW#kZ8lzCmiM<5Yc zWNUkHslBn9%jG*r-5zJMj%XyLXH89Dr{0^<5Hnz{FrzSig-VFC0RZAE=-53ET=Hb5 zw1LN~$Ug9h^t|APE1#or%FJx>Qz8ax1Wt05*32&R7u`u>@eLvp5x|YiRcB>|gZ+%L znfuh^ptnWg6#QvNi6Mgssv4a26`TxtjQqMahiL6|OPJ#VDT1QsaH{MDk8B;7@|_G5 zj^3iAfl$&3#`62-MQv}KM3TrbM$S?=5q;&uw}?14a#`=~?`-RE#euDkIeLj%^ zqlRW~DUt|QW(>F`0hgXDku_u3xk7GxH_e}-2d_JRp#Q3zXmj{{%0KwnlKyhxmv!6W=3lb2n-ZYYO@Pqqcki4jpCi_LJ@Wzz8?E| z&0^d4GL7IdKLT|{M#m^M6U!8C3P#QIvg0!)9$I;MWY$>s!i1B9NgNcQ?@+ycROMF)4%?#!-kZf_D7UUpSYaL=n&u$k9Jaceyp8ve2Xi&<-24P|0ezGYgRZk$MZ5jef9pVyWNJM z#;aQuS$ENW;DKa4Ga%cVI9?5f_QVYky1!0G;)6G;1`rxJb-A((7MZp`#!>`GL> zFnW7z>UHOSd$Ew>_=0@JG{#TzByktAt~jT`ehn!S5fJx)yi7A&b7ZszGl`FnX>ok5 z`dr>!Z5>`q`390f(IJWA`}rw?SoS*3%@x`W zfuwhYggjlLdpdj)bc=m%W{lX;3o@-hI9kX+r-s+W6viAK@{^64f2jT4KPJyax~13Q(z-~MWrNE>=W~5;8hQA#H|3mol83sVWat@rvs99D+T409 zfn?m0D~WUzK?>gNP+*SiUeZ>3d?BW)FMM=oyJSK%#N)hzRl2aW-I%O#;1t9ydKn7% zG}z!&RNWW?E)K;qI0iu%1?Z4K*qJdI9DXDbljd8Pti+761hS&ybz{T+hRlSR;#iss zZd!rrlUj$8Vn)r&x>_@2P?pce<`lwo33CNnKRo6hB@w({r_F1SZ=$)Ag#@LhbOZwR z)&vQe(-vkR&Egnw-F}80C`0CIcrnH9IWfy{2oTKVciWdrir5skD$_JS0%fSIW*Q=)gvg#c`f)Ua>r_26w zDa64A4i^DRseI6U(Hq|()>tpaly%7nQ?gOyy*hn*uJikQvtI&O0T`&zJbi{xu%@W3 zy&UH^i5rRz9?$}~O~Ki`qKjfl^9fjU>Z&r8fVW<^=TWvjn#h*kIJHG;%OXtCVUA^v zuWG1V)GYVjgz0-^)@hBxw=lycfDMkLVA?y4?kDP{6<%KxvXq3!bB!=pM3E=Dq69bfzbJq$hG6=#a*ho*Nrz(7!aL0J70xURXHB<3vB+5HvHF5nr! zNQx8fc8g%gdH8^=)c#}JBsjc|=!ghX;8I8-^iDu_e4pUX0jc{nvRR2!P@@1I;x~S< zEP=@hl@EB-{9lM~|Bjr){@+EGm93$^mAQw$lew+Ue}$l<`G5SD^dCR`frI`XN%uid zAO)m=!IL8!y_#wwYuprN!V23c1C-=%rYMeBE)qFsjFARDPE@PnOQ8t@I@6Aa>TO7S zW{GVhV=#b?3)=%Ub28XojxQMBdakp)X5X4Tcy-P%&at14%QpXW!O7H!cT7T@VRrd6qX1zePN`z$|(h_0O;Y66E6dXItlclX z!Ol%j-R@rO1J_>y{AsMHVikzbR`P5I%=M&&Q6kEACRmWzU?M?#@ie#i^zbxqP0Lqx zvF;T7me+u|WX(+0TH}cYA&qGv#^g=f;_oA?b4SNNEq(n)#FeBrZOro+`HEaPoHk84 z3}m{UX_(~*XQ}(kAD?S!B!oXyz4Gz-&j3f}|GRqr0LJ~Ur6k2Mo4Jp51Vb+Sg!aZl z*aHnS_j!AB*ich=tp40n>@Wz7VuXZ zS72NZ8?LCg>913IPVfT+LSrx^mwWkfT@NGEO(uMw+9lB1ff31er(uZ~eTS9! zpf(PF2!7l*BVI?|Z}xC9(~dITpPOh>+T>f_vsH#~Dhh^-0J{@@mIg{W6P-z>e(py0 zT#*l?IGH~e-O2tA;zk5xj%FfXCodADuU{V! zq+#xI`jfS<_B|%BTl27C4(MDIeCb35Q;$BcfRtwTwn8F>GM*|NY;z7|7=lHv1%A2| zDATd)3kMdF+2jJ^-2`6hOn0vtM+Q+MFCzaiP!<(2>^ku%4N|8JqpSE2+GKlp)4Hg= zyQLA(vMz{HtFT&WW9L!kq%3?U&TD>_hYY0t0SV-idDrw&7*laZ*AvA=Ad(}dyO>0? zQ5g+Xyz**QI1E?gdHtd$nd^(!|R`@jp(P_lN z^cg*w!Q_X?>f(BF`uq8t4z$)gSH*g%^q*dXPgz~5--xdY4OK99zs|<>;tDli-L|V6 zhMs>-5yoXIB!bhLXupzm-+(*#oUq1nA(7+?Z`hl3hdKIjsLZ+h;23u2DZgqBiptg< zUt5f=9g+bq@X}FAxec>bC*##j7soOgq8%c5j~8sJ8yjZZdaHC=Bl=4`dO<@PuhIoV-d&_du7bI~Y|>Z^3Zw$S{e-l_g?@#TNEoRU=69FdJtep)xI$jk;AtO5uOEP63*!Nl{L z#ni=4B6|l25jeeElaaCjUNnE!o_fl<&AR2<^4WTO8&m`MbfpQPy^Pp%zl_Px(*A~h zU)o9rP{Y;Mf+AHrHf7R+u~D+?GsA}u@=&^r)gyRJ=>beGl4JG^no7_N?uCJ}CPXP+ z$Bx~@ORN$JQVX-eTPU1^Hc<)>5491Qs!nNzssZ^u+o)Ym?h}%Kn7(&6?%1jcW3t`i zqJ(-QnW6fLRq>SBNbz$*FiBnq{{`*FCyOO9iqdtPVrkC+5V^u)E&mRN4Y~39z%)kA zdZJEIlaS3*O~mO9k0F%X*L*AZG+~{jW2u;dN-IiMDpIP*ADA=4VC#7nm@~}ijo$sJ?Xg|6Q%6(VyQAdZ4LQCk#|Nneh}-JG*3xF zvwYlB#3w2<1rv#OLI2|N0EgwxDl(7QUwCC))hcU*tpNDd?D*O{ zhn)}lgzQ+mH(yhY{scG4etqt$JRCMlc7IcGiF^!T>L4H3sa^6k-l;1~^?(c&?71uK z?3CJlMn6M=Gu&y++ILAVZ4Q}>7b=#HFX$q-NJ6?(Cu43R{S|t=YU362CZOX7Gk1yt zA`GS}4l-R1^(uBI#m^>=Un>h7hqHx`h7N_-st=O~Odvn8q%#GqAQBmC<+odg->?bS zt%TWAed~9Iq#|;3wg~SHh0Z6@Rg{Tq!$oV;tX6g_lGvzUOWm%U1Vqy-tO|BMZK>}} z1}2N1Q)PzBo}u2bq27J?$u6$8`E8UCoWf$^Al5o^rOr#ExtNKl{o5M9kDV4`B_SjQ zRLyZgfoQp|ppy=qj9OQqB_UQ#W7Wk{f`*XrkM0}pz3?g7uZGIk{pu5P_K^O&?c3z! zHsB9B;=5@2T4O7klHzCHO$o82@;7w_O2e!@xa#nzSn^YGhoc1H3umCB)G>9)xMHlz zT!3=c8>X?A-GPsrkmNZ6ogf&2LuN2uhAnD2oY&cd&)%eTJ<7bttme zwcY?39mZ*`%@h)e0l58Iala)98NAx>;0+hCw>j5X6nLQbEv-#<5OqqEr7$?DW^*{a z7D*ASYbYMM6ro?!)CWzf z@bHQnLDbet%&vI}pZ`|o=H9>^MjEh{{$|Tf+`?pv5*bS~Q#n`Ivd}n+!lgdYqoU+6 z>m4T=46*s(0KKkZ!K)_*S~mgf3?AVjMirmnOUzt=m_1U+6<~HBrVp#b-L9qN+qY{u z_HbQ_H?Hhq4vWwZTro@r)OEq?XESpK8<7t-WkKGpV0Kr{<2B9;kFZl$Q7B%6!@=s& zPHdZ!CB`b(__3%&uKrFgH>jXR?!tl|u8-Ogm6(T5Xy}&M5Zb+P7k;)WuA`#wad{vP zJfkhA*FQqNINv!u^S-=QX;3?pqnlOS$uV5z=w%7aaI8wz^ru{@-?PG7A?W1a;U~f_6V=qw5+pZ`I23QNr5$1mK{4SwU zHbP<*2@?q;4TInF4af-+7&>&Y2Z}fiWBIpTD%YEznvoxWpyK26uU5n5Z=eUdfBzT7 zf8(1LHnuW0)ps(Mcd&Ia|KOkgqsxdBkm~102-=!YXRceM^aqF6;Z}#&zA_V}4JU^P zpOCn!9U>qu%&5(ne_rDL0sN}4`w6iUb)cW|CHS%vGxfKPDzRmUx(kDCgO5ub#pROP zSbYz@#2#Ol4(TxTwyEPhSu=w>I6+{1^0crUba>NC~QH@&1~ zTx!oWR*m7yhb$<}gqkCdYhZmPmQD)Wf(_i6di!jJH1u;VuJ$pcmjHqDp=udeA!T~7 zTqRlsr?vUz>9%fhk(XQREqRN7>U#6HyV4f~4N>49<<{CiwsZfc-1=pSf0WdMwnpx1 zwhopb%W%KTcOmhsY(G6pa7-KOXwiX>set%7Kr)6z5Qw-mKJlk8K!(&sVOoMREA-i6@#%%5Jo*L!nszyOtffGA^f^JZh&*BBaolHO>Ajt7oj!~ zCA*^zQa%YK8Q^ZgM5q{UXbdmSJKzUZFWqqYNIwVXBr@x5(AN|}a05l8kp{!!@j`Q3 z_}gu{AHJA3$z-`O%SUzA=&4)Z>po zFC9Uji#0Rt-Yu}pfz&$DW~rLqxrbEjx~L_6_!%AgjirRt8(P^B%oo(6fao1idG8MN zyC7HTOv)80=d)zTN^uSh?D{K0&Eb5Vt3cGa7d0l(0^KE7gcR!}j^EcDt9p1Q&tWG< zJXIq3(_wdIDNozL0B0)D%6_Q+%%srLzQU7KzWO25oEuk;`;Pcs=v_M(Y zLG4&7;CH#Q>g~;&;3MLX1-P|$_}>Y{ML458FhmJ?!aZPyh8%$8# zM<1qu5WJPJTC8Ed5`wNTBEHdxu;Gqsj(}Q4)|GNMm8GJ(84T?_S2Fb7bA9j z6=WvyBUbv5k(DVcLGWzWk*d%(dD{Iq#!<|K1rl+p>Cqg9V>dbYtV14K9$DNna35|y zKQI<>a3D1wdY6LbRw5{(^tKt{wx6Bs=aN-mv$Hks8+f)-BL`k{7*Mv2k%R^*P!Y(u zFGM$0{3O@qK`qkMZKag>wSCubG3L#Pn5q?(iahL}b@rSU0Ivt0#HK1F9}qk^jjiL6Ykc@< zjq8ge4DuQ2vQ(`cyH}#-MA4C!&bM6eL_f%YGzKf%MyF^T0CuD)lYDsat%Mjca(5NC#Q8nRX-6yB4ddY0P$=oWONbrk^;K`27 zrc`V@tyal>phOnU(YisNss?kS81F+!wC|8vjh&@(_)fKy-w~h1^Eac*15h?{X!<7~ zvGu4XsX5#0JPYoie;Iq_X}06hhbc$=V>tXbkB@(s++Pz;Q}oma%Lc+wcUe=N%5;UC z-ujX}PmvPh;t-; z)z!@fuY#{XuIxR|7FPs@-MUr?Ghch(amldXhNK|^5m`kI`{0HcwxU)b5{`8#`!`K9 zeSXW=){Nh`cRz%;^pC=${(nb`za*1?RV6kqzX}stQLy*B`S|Vl{PB*v-1&jTc|y@( z?msG&%z+N4j~ZoQ*T9n4FMqx`-o0JDGyL6N&DOxbhZM1@FAJu-M);Od{xuvcFBy_ATJCntC5Mdda6$uUlg- zrGA?diY?4Uj0`G^18x{on~p$1CicH3pR6NG^~S1=al+=q&|disB4S``1OH3@vVW2P z-!t5QaQZ*wFJx=u=;WYpZewhuZ2O1c;7ZHZLEmw=)7^^~X`@Z*v=LYFS;g93j zB6a#7oAZEcXgu!Q$X)54=JDn7QKsJ#M8JL5U&cIaz%w!Zcv##5$TS=l8WdXSZ|2u8 zmlgzC*h_d2mq}tCg)^QjWw(Jq@$@iDk|HEc?+%NYb3uqU&Q#~fCofKmB-6Cu%I_I5 zF*8J`ur}WgdAAabl44Ev&_@DwDuAV;;!ACqr>$L+shl(Pn610OYOiN?8Z{>cedv3h)#^3bqC%OT2PSpN<_3mFAx0^_kU?D_K{x(a+n zt-2pyLM#_>PVDFBbbfaN&*PFW{4{gj%Zl3k_``JrzBD(lpHW&h4=KT842`o<6?Pkj z<|$oo=@0^0Dh-LMATr1ep?`q<@`mg>dsn6pO)&jq^7=PN6#s0)f7XVwyWJnAoFv!# zi|i6a--fM0g14AQ!(bf9p9@?dyu6-L;IyN=lq-ZgMT;=Tg#mH%KppGdguy)sF~ zbmRM4V++udT8OdB+^gZwK+v@$+5#WAbLJosisn)TvG*D4e!fcT07`?kGV`c8enOEG z=Z8S#D%-~u5_jXJK3CECQTHZPpMoIPrgF`?+MyO%hmG>uL6?FHh?9u(&qcV-$E`p1 zQkQPDO+x7jKmRO9qlc835x=F(9@7gi#<*(H4^5%MG&IRS>N)<7eAhb+0BN`$;fT#( z;t3ob_i&xCne+zqOMJ+(=k41c#w`Ai#!UW?!9>Kt!Pdc%nBkA~AE^NKF*Ox7ZTe&l z{Qx2I%6g1i=#RG>LKey}pO`8>3#Bsw55Ju^fC9e__jRmi+|xIk4#09BCX#@pP;AM) zta+7OP}Tj?OPzI>7NTKPa?ii{UZ)A&)#Mw5<4HBQQ!?8q`2F@yaa(g#Jk8G}bSfZ- zr%BBNHqpRbpuFR_bqKB7{xUh_QQyM+$V@;Vrbt02NYQIDjA5>*j3Jo&>Qt}aTA4{# zz^1~F|F!Y)`B%I6cQ>Z}zx?xG{jsydA6642#|8C^gJX}(a<}CQ01d6qzE^Qj^rQ6Y z`>Y3BK|Bz3_Nli*!wul0@HZ7|Gi^}H$PCd)()hN?h(^|noaf@z z6lUy(jkxaIsI;4EEbCo zs-C*DCI;o~<9wSLp^IyI$MxTUu=v>W_9Gu6;QvSSVEVrZ={GC++w@NR*9{6D*RM`_ zR(W6M;pOAL%+l0;!6Qd7^f!?BAt_H-8fU%H7*RC1VQ^P+9KegI^SCA=vvf$f{c z>`@f8bG=TznpU6fuKEC$sRt-3n_{DizTArT>|9Gn*;p!I$a`W^4lz=3KO3j6eWPp{ zE=wq&B7CaHr}l)S!bJGWK(-t{D-dyKv}~9Dr4&GirUGc&|~c+O}2i?*}_S|loF{p!%f4WUDuplKClcD zDLT@Ohhj-etj_37{iB0X~ix1L||@ z*~ERrQ!En07&7eYBWsN^F3+l-Wp~cChdN3_e)atVoxDymMc$`>3$7_aN-51~#;+DM zN{uDH14Fga1#I8CP`f%$!!k(u(}8}F-_EYS-+T*y zJ5R*Y7LlMSevhn(_QFXM_;Bvb`(eyaQw9`o z;Bzh<}=SZfhBXgh2XalbaX8yeFnAK70NsN zaSUH!&V4cUOK}G^kit`DvFp;i-ORr6RO)Y}zTjfF(sDK~`9cj(*FFk=j(TlLlROX} z@9?Tcp%${dHij9Do+)T6MP0kosO%~A8CjR;lGXsk{Xp*kuE3Uev~b9uXBAAZJw@(f z8jH+-t4;ua4rJyBoq~icJ=$0vFzylMct#E9xp5!-x?O=h{i#~PW*M&3fIe$;l4CjT zoL)LQ%gX{Y-F+A|ARyS7e#Uvd_GPm%p`})`OW+N)F?8t_N^_aeH;LQg0Pk12-p2wi zU;l{3^dHL!|2b*>8)Bh9RXe|@>)-9AO2gYrc@UK+!Ro=9H3k4s5C|k2#7+ZJ z7!g=BKKG0K6|gRA%zm$l>xMUAoodzM;-ac`Q!~Crg{tOhODsrPelwb-R>hmVrB>Cd zMMc%>s%n+8hsoyJgiXEQ)yqA+$>GF0$KzzD>*PADocHTA5x`-1JHF<0iM}fWs9Pxp z!CBnIqw#I66>c1~r}3>BByRtnz~mdh&%1ndb}S(ulmW$qxf^4s!f}EkA8oJFk1#8r z@*Pu$=Mw-Q+80wGw`PpJk9*NRm_0T7S4$4>9FTWj0CXVNSRJK1EsBGe{;AfNPSeUm1({FNV@g?-4r zxCZ>-dPnT>bU4TXe5Ly03x4GTfCqA=5AZWi)f=V9XXU5bs26sRU+xzKKm`PZphk%w z%nHObCv$%-P>%H9uQQlkSb6Q6({Q{m{sMOS9;} zHz2|O;GKhTa(4vUOJ_^X7+W>gGVYESGT} zwqR2qNo*=&O{%p#Uk0Pvou}+9Ghg|Rg@!P{WK7C-6Dk8FLNOd^_qlzuIsbgDN|^g( zxekuD8uf~ePOHAqzO5&adSb>+44lLwV?|3M|B0qZRIh^ym8Gq{p2#jv@7KbM}_5w%Je#kPb^ly!}vqUmeJgk&}IT7D*lQf|d~Ax(FuM9!S~ zTyt57s)%W&BThlpX>=RW&-TKkdTc*Ls=^%2@j!4aY2vE6glmXSO8NNElFacGx<=J> zjqgsRdl^hKi^6q1%Yjsh2?&{RRTB=i;T^){we27l@JwCnCUQhZXnF~TBr;^QhAbAj zxZx$MAe0S2jeQb~U1Y9nGcKsQ6S6d{I837oYMe!5U?WPk+)F>TkWMhDv2*XQ9Fr>x z!tfiGxBmWw zKA!TMjR2R>>`k(t8LAEGT)-l1jQA2gytOYw=-#*I8^lFI0SMp@lZ5AKZ zrrw>LXg8VpUO+~AaS+7{o&BK}Zb5fP{1L03LvTCtv(iuahZ6(J=F@!5_6_#Lp!QVK z;?vL({w*>}r-3-V3)qR-4v)78nVHfBeNhcy_7$W#_u@k1WeOrR*DPB%x^V7%945s$ zx4!tNHtpf1Ygint!uL`un*o_wCMGBQLWpbqC>1KjE90cq_(k)$5`YM^0y*qaJN zF^7GlS|Sta>J|wQiQZ@#uC*g^l1LqS!LNNl!{YRF0aE0mT+FdOVWOVv4ps7ir(0`N zBH&`BO5uj0=?ozSO+#x5`U?ROYMz-x(vl4{Otfi+1s!G$-;RP$Fd&)CT^LT{K`dHm z_lfk1q~lV`k-nd_@VG+1qnexhATrQpFxDp|v$055)Mv*bvOK z&r*pD0sD9_%VeVI?A4$q0#A$WSZPlopWJrAl~hl+PfN3~L5iuy+aM#u1fG&s)W+m> zN~AC*U4BzvN40aMe@9kT{e#pW^el4sW_tWJsdCZ0ShETmVn3qH#){mQ<1j|E4z45t zn27*bIDAs=Gj`!SEc^9cnxnH3)K++&4~~N6=&F%~sakOTWL+q?_^lBGxwo5@S#CF+ zcECbeR{C&(S1p$jpi1dzY39wyZD3;clo~Irno8cvCNBsxRZtvoIC;8gfM(hqFn0E) zBSMHA6bp`(`zrz{c$D3)6t!Xjjs$pRs%W7QgXwYW(TjcrXW%qYPYk1mz!lq13m??& zgx6sM!KkSlk7SfHvTtf>-tuVhm`|*e&>97`P3(@eUf6)5t>}au;GmXJo(iqdU3R^+ zC_x-IAs?Jm8#OL9YOOxax2?83qIt-BKcc0#nHWLg^8={bBZg76&R1e*RHo?KibMLj zc1{Jpe0sJ&$Pw>JsRaq6#^woVU(YUKjbW z$8%1y!@{-mm?ee>&pj}BMmN$&HG=J5|k^sY)Y7eGY6A=eg~L(Q*I#^1|ckon#8=lHHaZzqtO*!wCZH=_~Ti=!KEOw zaJ%B}dI*=zo5vUot?H}{>gTgwMB$7EaHXYhp7a&y3;F1C z4<=!9Q;0bP1&2x(oKa5zpNIE!rUHDTY}x^vD(esRv1EH(n0HC3`)N8_w|0}hMvZuZ zXEg$E{D7QZ`&wUgv#mGT?KzHBAh`BmUK6izgw^RRCx@hMf^iwEuBW91;t9h0v zf+`gx$8dWicVnb0^!BLijN?WjFx~WAXazU$I3sN$GixD$euGu2G0?R@G}l>mg4e7I z=PFLwWU8yi*+pt{94>e8S%^=R0C z@qlKUQ@nXD%63|8M6%xB;NTAke?DS4RQu%B)3|y=P_RD~vp6ETwHGlqTbL5IHG9Py zxg4;{Vk~x_Z&M?MZTQ*YTY48=Ka@09vmEjRCg-ch$JOPV_uJHH^wbMF69}qMq9kB# zGSmV+K1wqaju1;mX7#E{z((Hj^TC-L#xQCquP~TvMm8%P1EG-~*cyj^%k^68P}pIZ z^(#)cj$YVTea2&B0$m>ie2Q*~;jX0H`fpTVL3Rltc5ucV{|oB{tS8_ml!m>UFQv{q$rCMCIO5exOo#<@@bT(+VQO;3iQ+(lP#)KfiE-2vTC+A1KP+)y z0pH2@l#zD$cS##=pS#jF(rPYaXQtE6$@Z~YU?2i?+o}_79?jpGzbEY{K@?WNRoD z4Rcrr2|sT9`rOdz#~87b^jFom9^vF17EYKo;e#E3%e1UGibhk#8otfjUgxahl#I1^ zef5_LhOQla9pZ}>*ru`_%k)&s0g-+PbY-d)n#3}fxwN7BANM8%?O0?TYZWl9Zv7=6>bK_z5>5P zIJGOxs;T@Tta%?GT*4`Nn#%KQ#W`fcR*v#|1G()Xv)9bqn#D(plo zLT+lXOekuxCYRM}oByp;M7+`lqvMz7C!SmWzAy=nG7tQwNEphIr?%(8BKK#FGLgZu zq~}4g=I_33!oA%LMA!bpmG&<1M4||7Li3D=(lUT~n2pdGP2&2cI zJ4(2IT{g+SciE*kw0`EfDvJH6n=Q|JnRYghD%?VjMw@a6{>czjA* zMpguQfiWGN8StU2-!uwUQO;m0v=qE4@M#(BM4lLmt)Lio$!I3Kd-i5R`wY=8|E|Pp zRKC_{NME7oP_w(*HAPXzskq`S2P>SRCkgh-sQwn5hD)>{?>)5xE{IO)sLM&E)=_ zsePDTBSEz!5s@T^mgh?nYj9{HYe?v3$TVa<2O&`P$R@%oT0(09WMxOM+5Q*$Sw`x} zM)^QdUZXTZ^H^eU#=}^F9n@dyPM%V=ar#g<8r8psey)+df~muNB`J8H0cQD>oPD9R``=2)n*YR9#* zg(80a(x0o2#tvmQ-EtD#Kzqi_G}j=wn{m%UJzXeZbWx9G!Of81ruxt9uUoW`rjBI6 z&CKqBcSlewXC;ZRR%~Tn(!K_XXRX^fFMR5ttxr6b8{+*}lr*bC3t#O|_>4Z646QFk zjX&BL7w^qy+d1wCBDlR8;&yK~(}ksMJtZw$*WPx+8YIKTldQ-CUff(c=sw%sn}}!h zxI_+sDB6z6>Ro1_z#!sJgYPi0^Xow%p}}j-gA<{V=`;5kqQ&$J2a@5)inxvLnx}?c zRa%=bjrHVulbxsdtbyt6{Dw&Tyrsj{_+c7H|A<7)!(paODt0RbjqBPRrL8ErBt}f4~rYe&(6AZN&W7^l9EuN$8^x|zN z$PN9VD_4;AqUU`nh;(&XN|wtb38ZIwoLqODTyN`qd6|mI0Z{xFgxDtyv5^WZ(3?qr z)NfSm-%5KJgl_|4&lIW!Gj4)GTU~}#uBZ^DGPp}e`MDwKMm5P414v_I`W(Ao{Iymn zj0CJqui7rSE8pg7`2OD5CTZaS${j4cxL7q=Sqi=RDDf=CBZ@$4e%!f8l`WCQX5mM& zV#W7xn@Y%i5gVR^n$r#w=;)AWB@jBz!{Dwynt0Ymtn_I2t4Fyn88k;s2M(lSKQ=1X zY0XWN5YTtSI#bil?ZH!DRpz#Xt5DvnGRXyx2?EhT`$OEg$U*07pNg|q;#?Q#rL~&= zKgzy2y3#iLvMQ+9xfR>CZQHEa&W%;EZQHh!if!9Q#j0TPeZTHCGrgvJRBzUHE7sHE3@oYBVtJCJA|;qiPEyj8SdTeve+Wi(=h@hS+_q)aaYe{Dm7o; z=hkD`2*FpGB4lmnSKQJE+~kl z$VBe-W!mU)Q8}4)kPDmvsoi<$8!FGW7~a^|(i3YSi+W$Ed1-Wf7nr^@2ekxSRVi=K zExNs{LCeqZ$qj=R;dO7ZzL+C>mX>1C5w^p1UpRS1{zK!?P)-v3rL2&{k*qt~sZix< zQSrTTIqIpoKr#>CnW9rc@et_CA3^D5^kru=A*J(w|$AW@|5C_zl8(`rhU^- zpZ~Rg{^KqC&y?;jdAYH}ztIwfbqn+(9OtPGNcAXP#ZCWV#jq1fr)ihSa634LIhn;RreV$%Eo**ql%!BB~NRxyG0ar83MP7sv)m3N#dNx#~1*zswg<5ng z0QXkp1Md1lU77|ao)3Wk!-8wCvFd8$jZVIW^Iu}32u7I+<8lXQE$7zKCtt*)CPxy! zbZ_=I%L$9l1t<_{NVMQ{!u5CC^tDfo#u$W(e)vX^eQ@mSI)ub1Mvr_7COdtr8$_oB zgCH?F{4T7R-$?Y=p^)I~_@2b`zlk6}>e|}&C-(ejH4)!`L?L_T&NiY|=d3hAcxr(S#nB@8At=R>Wrxp%dQLldZO>){>~C76xBiS~}2S zakASC>wdoEo=Q+#l6ofDYCvwePI*gHs67kVK+KU;WrG&6Ne{yJ+U@V(G#*=@R%;c} z`~y#@lqNjY0G~cyh@u>V+JnGa`Go{>>;l}zl%-xcf+@DPFXI!xf>U-${=zRK3o27I z1t4N?*SDbP*tr}d>Au5sXD=b;K6>kgMHwN9-!v!_p3G)d<#S!svnR7GH_KtHoOzUe zSGC9#aww+sG-Yv(Di;$>jl^ruRfT1U^WD#~qmKBi8EWAG#aSV1-d#*LBE!jp=c}zX zwRSr)oAtv2^rC+Hi7TqqtaPpLr4W9X@F*I^UPQ;!cQfSdcbscH%)ABmzQ6F0mJRO|j7r+z zj;0aVsRV{H67hlhKNr22)#@k5Dr7(9mUo+p@mFZ@0u612lkc^SQAl-t&RRo$STG2G@@)L@$4lD4KPDR4b7LkIQmEZ{AbSm&2Ra-Bg%o1&c!2F zE!22q7lK~&oVej1!fUji!9AvD(%&AXzahnw`iDrx3rNRcxZpU+D=xVg>&>PXzyA$% zVj9e(!hf+$sQ)u_`&aoT*8ls$elo4U3^f0h{_@{w<^TDpzd-Zzk)Kr6#N5=$;qTc9 zf46x5hm&N+Ov!>Ud}@h7Xu}}G{IaAwp2=!M8FcB1C7EZeI`~_X8)-?V(=KUG4c!>8 zw?Fg2L34%~a_w{dqT7kPrE0GaS1(YzfC*%1br=2o5O*ZFplxOI#3uC9_8oa?Fgz%n zTUm?5sB{mWAi=eD$smi8hK`XzFZ1?1KElH9Y2p#?y_9M`p@G00ulD&yYSwvUO;xeg_cSb+_+Yof$qMZMRFAKt@PhiV2 zlm*sl0w)oZbx(Qg)fZgJjM+pkl$$>P{Z76qw4XGBcwNyy5&PR%De#O&0yM^pVrfbv zCfdZ)0_Zo&8|GEZUi%kROnFHIRLX377bQ`xsQCgqrS#iPuZc9QAn^ml3YX7E`!$;a zN>sub6%fz^rH0J{v*3={o(azllgX8B(gio4J6 zzpqdT{WrgZfWcp)S3{t*zPp!cdQ&z+H*r-ompO}ydCE(AE83Zv| ziJ=aVr;<<-s-x(cpe6kZhId`p8X&{w((Lkk5Nu2J?9W@tJOs4n=bS@HL2{ZWv-s~? zJwxpIbJq+}QL>pc_IJ_Fh_789wyf#rv@*fthg?UVM_gOaUe%71ov!yYw_nn(V88{` zp;-x(L^~BoZQ~h=a{6~7m^jAwFQhkn_A{k7N%l6NDPyj8c-Fg;NB*F$2R|}B|BU$K z^@PvF*T08wD~d-kBz9fv;EA2BcO!w3z1u3s^oNO&w|v|2sUw1p{??b#6G;q}JA{tn ziLtAFF;pCDrwi2S2pv|*Lc9Q`J(nf5ml5I1yzxteE zKnd$L?%=7mipjRCnjXOFqf38-hGu{84(CDa$`$9&X&8p(PR!)M&-SMg6pD*`BqeD% zKh++$UAo$dTz0b3hA*Z`n#Lrsv2U+FNJ_pPPKfP-1{l{yg)J2YHitpPUXgC@#hoHM z;BNyRlj^(%V1pcO6MWHRk3ZB$mz&r!|0o<=&2#LAN+t;9y_PxFCZ7%uvvp{kS?Z}|9c zDHCa3tEGN!l}PF;!T?&*PD;(`vOGAK86*z1skfpkC7E)wA!k5li_U-vbLsMEr`jGo zpt^^Zv-&%ywFhZdja+Y8mb{OLkV}E(Vh&J#p!pgHur(lov88nF#a*!r!A_!Y?#oSt zW?g;&h%R{|kS%xRaW|GOaRq%CNl~`{_D5NIhZ9!#Nbo7R^leulK{-6#9k+a727KeOkG6;q@5L4g_+tb4oulg^p@>XRO~WJ z%}+?2v(Ba}Q<`hO=rJGQ-f={=-Ui9Wy4lzxiwfRKl>;ZkBEo}wr68$!eM-R^QMaU# zF<+k>n+T#0Opv$mRcG~QkS?dt_D9vJ;mcrYL|apIml{ID96UA|wijI$euCQ^YD8+? zFi&!c-Kc(FVxJPQk@+ztP!E}gA)uoGb~~QySW5YU!5{1`AP-!ah1wEANRb!Bn}G|z zjBw~$-q`6K9>)=;ZK#B$QAS-o|6|;PMYJTsfmX3pQ=PW$S26^tJ3Ws;YG&DseLmsr zavX{Z;bS~r$arcmJolbjXv~(jC{4b9Zrv#htNcwQ@e0T54CHY@OmY8Sxg&UdI_QZ) zxv`wBt+<_Oo(nPb0RbrDBF0Iwu7J;h7GDLnE`(3a{5@EOT`n2^Bds1XarvUu@%z71Yabf-&5Ayy&)I&jPizEGbZ8TW2 zi{oUXHc%8~g1Ja4;U&>CdX+|}aGS3!wBEr5xloFO%bP}_ExdjYJ}IS{JBP#W8zH{TufBdP_TV@#Q+dv>sY6}-Z(yS!SY$b1n56Q!>W7&>-NP2$YW&T< zKwIAsckD7GgbvxqvqvZHf>`u3FNR?}4Jer*${B{YgxG9Ote|ks7;4R)ua5IHsV?(0 zEg3#88_*HX|G1pI5N2+#fp#Vp%2FXPR56- zJSg{Jb_Nv7b94|js6tyzlFfKuiS==R3~sqd7fh)Zj_^ceulH{&ul4E!Z^%<4U)Bz5 zY+{cW>e60rP#5Lr<+MErx2Y?3Qnfg)igR&W9}uZb>11-9e}`Hk1Qgq?5vg;9I*HZv zp0pzyE2;B^s;x*k?xw|3xrWb?sl6Npu#H1CqkXT+M%iR1)4t%5pv4&^AH0@q=Mnw! zw62?H1q3lPU~xNZMormLP~{DVzV=WK=gX0Ojb*%+Tc&)E;Jrrc@BhI-{T`$c{j24P z3qJhFzWWIR7xklGVHXGGqu2i0mYe!L9OD`!i(r%Fnvgb}%2oCi;p1A|)z?*xo5Z=s z?W0dU`puPS;DVi1{`$=v#Z5%=MjZyB2J6l}i0hQJ>2t@9DPf z3Ev_)J;QKrft}AxU8o8*Z)>G~$OC#Z@0n>ZySQ>FZXB-D!(&rRCEhYnrPoj63?8I& zzAi;5;xV2puZ`xm?$DhqIBt!pZ&6-?*6-ztGlG-%J+bTK?{3r0Dvp%KXIY~5b0Yq~ zXEOh$74R=K`yY(%AD+~I(7bdmEOSVSf%fS+0 z!Uh#kq_Gkkc(PkZJEiFdWuy}HJSn^g=Y`^Ji#_ee^Xadf7J;w&EutRkKV*4nSZ1Di ze!LxE{WPv1&e@BN(gpz?K1-3}S8^5@A{6qRaG>_dBuGl%(4S^qPt!xEQja44<^doW z>o8Cv*0C#J-`ts1+?vQa>~7``Dm~i8L9ps3-uG1jXm!P`yBtWJ;Ht9_V8m%f5$y*} z@W{!(spg(uV(4z8!x8YM^kL$oP?@f74UmfH1t=qRX(@`vAYt1P#H{y zenBeDO{bki+p-zYMa)kE4|t}&0u>i+il$6@ZQz>>o1F!$m70vHn40BXAIa&iH-6}o z%+jy?vUkxfgS3!Cjtah1t0M>Ysc$Nbbw`(n&6WoXN?vP;K_xGT?!W(B;9=1yIDnRh z^*Jsrww0L0qxaUp`}dru*8Sqc78R5$V&1?}o*siO{|H>GRV-yPprbveX5_wbNy2dG z6g+nA{SqX~2hsUxU29Z^#lGdilZ05@uEWC+C(-V=3H9PGnv`Hj?*sP0Ass!?7UsL;cncGc$t8HM1aUcq=t5FvwM%J> zoQmj%s-aCJe4sM~cdD`}v!1sVyF10~h~zWxmd;r`jQ0!Y<}#~LoJ*B?x%7Cckg`qe zKKAj;T&`CLL!H}hxFKo({K)1~5CxmRhQajkWW1O3_dOw>zx!XIhTeDXpD9ei|24?? zU+pdY{|$Zri?$0{={q|9^SuAhIWJVTRznd(^(MutYpVcvQ@VmfvgEgmEo=NXCmN@0 zC2oaWJDc2M^<7=sd2T`!v~r;<<2~fv*L%TH^Pu6VVe3!+>gI|S2yyJ-i@xdQdaL8n z=26%9`~9QsXMt$|0XgWpHUO!BBM~V!aeFA!zd?PsPX{V{J1fMv>nQ;tTjB}(hxqVL z7oy^)m?)d_C}XB!n4;1(S|zgB)&LVgqQihPE3m!7mhvDjH^J#o zZtjU>5$h0K{5iUQb>pI562ZNa_rsFbm=l%?C}_hGNsy+yyv9h#R6TXjv`$r z^cQ~=YrVS@m7v+E~ ziSRgp2(hk~xzi`YJASi+!>etO;*KM;uV7SozFc;AMZH>Dhj&!Bq{##4Qq`Whx)G{k zAK^EFY*Z}={nR*wy#Q$ODCXVfoQ5K(FnS&#y?V=|4QZmT>_apGiin4QJ)_(c8ctl@oJtcEetfH&Xg*uYjr)9WgN9*S=Lmm%m{V1m8nsp zFSSyZA>taOn~+auZ|=^=4L029F5$GoQjDGH7&Yh?mVdPi~{FN(jC1zA%kk))3o{!h-#CGVi=k3$wy}>oIrnq+wW9I;s2c8doGtWIS|c2El4@ zxUj-rNov0|XWx{qOxMpVls2E>k4-7y8^<#YNT|dE706L2YP0?J3Lz5m4<}op&B5Gk zBo_@Oy6L@I51l4O(?_>+?_@8a*w+g<8$&Y-EU{&#I=vJtoIU}UE@{3}Pub}Yifon_ zmiufD1wOLyhPQW-jfg_0lrx;?YnDD6SHb1gI;kk8$;GI!FHiF$}Vk4aJj(slhL)US}G zduiBu*zb|Ri!GA@aS*=St;xcpnYJkXIhu&%F&xlWka^7ck6#ox{4`(wb{xyFDi-Q~ z8i&?CMNR*nzzF=eST1MqmlynBz+BD41!V~E5h?btd#{MaXJ_{_CgCSFsGlBWWGITE zRF7~{2(gghD1)V2?b0xnabw*?c}$ZVt4z9l8viV-R~y1=3~GeLDz_#s%>=~p&~Y{o z563iDJC%lbojv@~`Q&=4$I+zM)&>?E-wW6;p(nvX&WKU zT$VpYPQ*`YD2-b5F5VGo~*hz?}=HlXuv(p^JG`%(?u#)W6V)S&? z>cxOECF$tco-N=uuuoCzLxkRCjNG51!+|I&Z-mXo~;C$p!XEnCcL6i5$$CQhlgmE<6#?C;?4= zX-#5G*&q=F5hk~f3NKCC&`?+m+vcP4Re{|&{@x4%hjFffr@kXaj@~$`R26+c!s{^%&|31GCBatKX-Ob@_XDB$~XFm zO&LMz_4;(qfK)0{wy@9fkGRNvCJYTs6@Mr*#5=2cB8 zf_Ec@%L>qgLkkRa%}i7K_%Ii2H$P8>%eL)-5>NSld^=uH zM*z>CUeLV#*?=d3!yit?IjS|F8_=}dc>(6&$@D}!b$-!TwE^AaPgJ}LR}{QTR~+wy zH4g7rYF94fjTur!Wj0e)C5lhg8~WT>xFIjR$Fgc)JRC`7&mtVI~A zUpGuThNkV&jDtt7b->}k1@LT4X1Q)_>zo2m#nNryP2`bG9I3k$ zM_N%m#`)b0cfFKkPB6k4ip@E&@$qy~oEHS6X^KKc0$@!uS7jOAWF;kghlwG@A{_om zJP(TjjBIXLPo#O}2)71&*Se%(7GPxImY#hUVqTN!YK4`>T8cx(@@UW7*}Ot2aOe2u z7c#lUgl3B@GzB^ROsjsG86Rra^9Xnr!jp>sn03cH$%PN6D|*?O8P3|w;s@emha!Lx zz3t!zSsP0;cE{e}@n;0+Ez>uv7X}MU9$2l>TD3cSccWpKpGF1vcN&ca=}**kXWkL5 z#+2*WLrjf5>B;H2;IE@V4nedD>C`Us6SK=t!nS`m3IfL@E#6_x-N7Mj2a@p`6p;pu zSvPjxQXobM|7qI={c9Pzv3l&*@2Iy!Q|*2*U!xDAezD!E7(d zpKu(d2Z>Qv)hnb?R_CfpCo2rtefRb&98)LWmh_$!8?!K#GI@$XZU18YEu1dT;dBv@ z?RaGgqU3?3U2z6x>7G2Eqz@V{ocEY`oiyuxZNFy5zf7RBMs+`mP~L6yU0NdN|?MTA5Yo zQHs zDJYjfgl$T-YjC{&?$)3WJDiV$V09Y4p3~MIOxLu=tD|L1n>tzENk7lH3$pdE-U+3z z=;_2yWpgK2Fky!N< znNUBaNLr^MVx=vM1h0BVy1{ma&)X3y9KtN2rW~5!a{7y;W`$9nWcjM{!6_47r3-AB zeLLw3Mwu3f5o{X?e~~&82|vHk^l_9%t&tk_mT@7F#v!ZxJ;#3V%j8z%Apsex$8VTqx>54P7l3~E~-lgmi=k@Pgz z$hu20kPz@I`pKAC6U^|^q=#x4EG|x_1^LVdm#L|^9gD<>gOex{fq%+t9n0hiy$ z6OkndSL%$DlBAa&rkrw5D_4aJ94h$ELqBTNLv9DHl~JTkEdiD+lr}lMoZ&xbL6SAGj`9N$j#h) zeXDK58%|n~?dOOiIoqlWjh!+<^)jT-ZOo>eCb4!b>ju-Nc=r~h$n#xpwG-(Esc+Sqz%0Io)d}2JfgCLjm!0r@jizrY9{RJ8yMUT(iCIcO_ zc(Hx(RURWcJzL1?CcSs>Oj+8!S`%B;@H$zVzAshXa_Z%`NbcqTGzsA3K?^UbC+Y zLUG=jPTeeLcG(2{kiV9RN^V1cFs%AF9h~33l%7$nCcEU&Oz{P!t$T#%(Y7YLCoL0} z%SPxWP>aRJSVj&Z`Zc^^m)(H-jV2!}cL@qVV|I?Rp(}QYNV+X=m&6@#j4()cLniPs z6zKE!o!!asd~j`?y}!oLWuKtjw1@e!P(O!3Ge&LqeI z2^w8aR#;a=86v`sT#)QrS#MvXT)pMEeek3B^5j+o6C5~8xCIs6sNJ&2-XM)_Qu5%Q zHqS&9kov%T4decNN#OgmU*FlbsT#rNqkdE8iArhE3n`BEM> zN}YyPO-nXQOG~~=!rn)|$4<9flN&#Yi{E0Koxe97r<|ua9y9&~WoLL^K?pPd(7hm% z0T%7e40PIfgFVmmMUSx{?XsTdFA(mcDAcm(SfQ+W83ZqvhEjPl3u>PL)a*SP)wKIA z8o8etWVDX}OMl3zc_)9fxekzZ?22YT6+9)Vd8Q5V^@Z@A0kUtyrkIA&p5DS=pW@Wk z`?0+^ggiw&7s$FuZ6*L;S?6TUolU`magg$+De6X&5f@i7? zX4o95@#x&86?JFpTknJE8bg1)UhdSb-J$~Zas$Lh39kMl*nN- z;YDAc?X4?ClRe#&y}HhlWg*Mm?qSGqj}h_R&)8fu;aMZ9F)~eE?;X9mX5n>jul%^# zQ+s`CB-OoA2yTf}>q;T;?i+oHY)N!_MCQ|d2;BG#-W=i7V?L#e1iaDA3gT~;u85ZNqILXYo9$dx$BPw!7mlb$IMLxh^OY-^8{ zS1IPhX6;Rr1~8ik&Rj-2i>5_PFY=cwpfCu&i_m%Okaqfs-nQhS;>n^VDAjUcLp^H|apR;TC8(~|Sg1JV=h!zUzcjx{<;U`y znw#2&NW6b{8x^#`i3DAGBYLnr>CB$3u&Hb8%>?DC&tY<2^Am)0IJI{;`QYW-jUYwYV8h z&+VWWqU?1=wcj%$f#;%`-4NkNWKq-%KiVV3DO3}<%v8VmX9jg^U8gn0$MEg92jR&s0r_maKHQ&go%6Hob5yy$F5vZvNI-DN2qF z=?oJ$=nQFT01GB%iCNa%jLCyEUR(NPn2|DyHG_KHtotn#6;s9p{}dV%eDKWAdPdgJ z8rD)amnwcU1_@qNsFFVs8P2I!7@5U?hku&C2Ed%lGY!?(oDl}-q(wZbfl_We9>~2r zy);AffD31^jIlpHXY4yhO1P*aphq1d9f~xQDPgiokNjLG2ZDa^r35a3C?z)YYm3sX z>v;3niZ{xGxtN?)wMiR_6eDF*jvw(kEq28Ge#6LPcDAWeGVM)B7 zHZd}GJM`r&%M1Pz!@8yyUoyJsk?g*A*a@rUO-T5#Gcf9&A|$R>_3sEG{61dAK8J0n zW3hBdz|Xe6P)~>8;zd}Fz>!$wCP{5ew~m(78)C+EhSdr(D1I1+WTgsSL#Lk(RTVJy zKjjKVVFSyuIjHNZxe%~pgAlUNyOgKs=V_SM!LXqo=64aaI=KuLFE$ne=%<$ z>Rkc7GX&ogAkxbyZIGqPk;CBD9n5ut*sxQi#z59b8{h4>`vdB8CBi}llt3)WgfV=C ziJx+p)fbIh@{i#LLyVp@g0V(#bw800v|U(#Bk8O{n^5yGp%i&7X&B4xzO>37h=~=AP=l?L z_+IiGrRJ>kU6I)BgaW}33BrT&*Jkx?k2-eR)-~0LaL%oOV)FMp!ofcWuxiFSI|NPp zoeRSDbjS>Pqs^QagxZE>E8 zXD3h)>O&S7nS|LLDa)%KvEuiu@S7nL7)T$Gl_lh=rIZKzcGy^qWczT{5c^w@xyVAU zo$n{5$w-dIJU-C`PVP4Ah!F}wdk1Dn;20h8d}#(sunG=lXi!^ZQ~z$}H3KmkN+ms|?_|Rq zdJ+kwR`0wpvp>Ji2cMEZl`p~7uTx1CtbExd0(+FD#&*MEQGNT3riaP_?w}mKqI{4| zswOhdP6vx}_N)WeOUj?5j`NYnFlpl|BIZlf;TVhU1&wo@E=tfV2bAk5`MyF?NF;a$ zlGGv3Y04XQj5#*p$dhv%9g=9KVS$`p;a!#C-)hr-j5)anWi6bbIwwL0yK|o1k-jM< zyE(vG*y=N!LwM<@?I&Qr!qcKre#@fVX8(!{p2}-c9BV zIdO={ir^ZfWn7O@3&1E7Q*R3*nZcZvrBg{-oT?=a+DIMES5otvAvAq^Mar+#)54$q zS}})y2IK(so9y{fO3DP}Q2M?bla@cJp)(mL!r3WD_A65YWWhKN}X`LBT$3#$U<@HfScn- zJQ1*!Kk4{Jx)t%LbdC;qTHKeWT`*R^Zy=Drm)1<%19W4prjFl>4WGYqBCj{Kw*E?= zPzp!Bbb!pEk}T!sKR6F;b{1Xx1~O}TC#FLie{}57S}|>0SYhDtgwQnBaYt?LpgnnY zP|IQQqgT4}lwOk#3wVYKXbG(TG9{g|kNx>k3T)FRD0gaAteQ3FyO=rAUF}<%q;&<8 z;p}|Z53w?IyR^qV@f))a<|rO>G=`(&i^e-Tt4qR#K&uT8@w?P3$jPKj)zGq?)nXt7 zwNYi}&`fs9^RK)tAU$(h;U`WmbOYX!EHzR9oSSqiCC@Qs!kx#LSsz zb?Z_dZ=rRl(Q3}94pcaCa)W{H5zQ_@ZPTNdJ{%`+BT5ZWLk7w`f+b^iD^)m*r#rkV zFq)5&gPfdC_dEI%lC=Cvjj9?+601~j$MT!MX41vkVws(}UwVod;;w|jP2zUyq!y5v zy-LlBVM{{#G?syc)AhA()h%RT50a7(!dBFh88zhG4%=P>G#)*{A8JJ|;{K}yrnHI< zJVaZjHZds~DD8Zwx`CGn_Fh~cx)z+WSXLs-{Q_i!jRw3>0@*PE!&gCPWJt%Q`K5qf zf!HfFUuGY<=pM9W5}_9hlfv)RV#Mfc7F{p0^og0Mq0QnfX$c({*mq85!>P3LkoUo1`w@I+c19m!@tg>&aZ;c{0Q>f z=8hTLi1ZU9tK{h4%k)f(&DHf)Q0wN1r!mf9GV3X)xllYUwfz`Ky?Ez-DRtKJ^y2X< z$3H>McIDc&_(Km;Xf0D9^VVzDRE^zFnAjL;cxkE#y^W@+NPIu?!(Z8*)m(@zv=M3Z z(^??Y5M7y`eB?dLl-kQCRzidVqj4r+5x)Gimr7GQU5a`3$=<5;phZ0iA>ETWBckck z1sK0P6PZ<|%YNC0>6J1>vX-_=kK;uoAV+PJt@&y)R9x|*u9KGqYpD@8)H2{|zK`Ly!}70k$O# ztjWD(K%h`J1MZ!3Qv44)M21~&#~!h^6=3yqy$qqmk$qS<9Q1VnC>I<6#R3_+EoNqt zRMleR&=`odW-(8vpc(ITtjDOaXVDiJNNZmBhsBt4X;kH~QL~q)k>~~9VU0Dn<`8&U zeobm+YI^@AhOWI=VUm6xDNgOPQ=U={Gjp;VohbVF{UK*Wq8;b>w1;2=rNm$2sk!F) zU~zs?wYsci!)!1$QikS!0)Tc?t?p+%c&eAfrHCXUPWxoWpVk+Ygaw%$e4WO+Y>~sG z7m&8>CKI(2xMx_R#>QGvsiCmZQu{d_uXN-^W-=IDSgYtxRlY)SrI&y$b=N{_WhE3#M4Uz^5YuRfhP6e!7ZX3Z-Jq-Z`r2_)$rKuXH>X1V?hkr4Y zO)>*>H1jIIUzcDia}=F$$h3FxgWkF99ZBV{Ke|I6EStX_I<_JoJ+OZtxPDaab;AB8 zKLTueR62KC)VBzNu6sdWe%VCO+$&j94v&-R$uDc(!7Vu1-*W&X+8P>`+V3G(uw*rV z+rP-a%j3OSz{Pj_$@n6^B{Da|CrG3TuVAt`%!?WLte}y9O+NSo&-h-!*}nSIhMycx zfq$OQjq)wW5R6HxAt6Ztz7X-_$q{*8Opg}jJOY$U>R5mLJ!Uc&FE$&u2TS3r9+Nq( zs0PK+H1jx59)06%bmA|u>tv}LFq)a;8Ko3tvuAtQxV%F~5j;I$;(iJ=w6Lv$JzwUGoRa&LoHhfC1e!<@ZAxNe z15}&37?z6VsnNH>y=V4)Z@;h;GG$4*Sxk3m`6-ao;_0!eV|Vw-MVYTO5X5J>Dc?cf zB|^@Eld}AhtTsIP&vH;Avv@^xBh=g6zL9_KuHZ%3_*nsoont;EPRA=f2YyjBzpD$$ zl5g{%pGHyAwNDR#UL~QNP9>=!5;4xo^F-*UmEz$apfNU&&KZ&8k*+5(ks59Ayg+X} z6nfxjH}6uEDy=IGg-=g|Y25dCG0Rfi_uSUS-)8i{A$1K0eH{n8I`0HJd0X(&tjhrU zda9qKPl`o?PbyF~o0GqD4a;C>WN04ME*%uVxzZUYxXm1T;B89HsUwXS-i7#Puv_l=K^MI z0?hBS$a5Y=C^ErhzLpgWle`Yn6_}w=j5cBFQ^4IpQ(u~1&DIA(us#wOhDoMXaMJ@` zCsiH}_4{i55FWi{r~1C#3ZuOFc|(DwlUtmq-C2si`We@g&WE#pYUN6j>Bm^LM2N(~ z5Xgn{lUZIaC@hGo8Mn7mL2Hcee*@ss@NM-+Qci{_>NWG#wumZ`7rio|3jo*eU!;8C zBo&k36_kE)&hT9Qu1C7C2(KSSygCi5ADx-!s>qqu2bUUeVWYF8@bJ>02AMpJ!laCb zOI#MPe_+7m;B7Klir3CED2O8=*K>)ggITda$PI&8+`TUe0&s9evHY=4nwqyoOH1j0}4Y)Ojtj489>CDef(QLpE4qKogcnoNppco>#Uxa z$*V{FySXN~tTsYA$%5uOYHJp3BYM`)pX{|Z4~EPqkOvL@Db-AozMZd#3w+wJXtu|V zj}!Z?38X#xCSh*M)Q5AD_C*s+$Gr+33%=}P;En=J_OkVk5(AFkdN5Se2)Ok@Hv?SK z38_WKuwra7g-jnQ&pv!97>?;w?{1{=!N3lxj^DCplTc39AW=wX6BnyYuB!~h%EIbY zQ_|BSyh1@ek|KG=!2hJ*;lcc2iOIBf;J(uYU8uI9;1x!j_m$l{-*mT;#QkPQIr)M8 z^I)5JtTr*Nba&VR5X(3K4e@*}X&>O^c1PO@Q6~nV+C@#00_Phz_Mr+M`_-vY(x1q$9-j;t0qYfy?ovu59wnvPvxmkcGZ?MSyflD0|QSpm*= zMlRMsP78FbZKM$cQ11rDFH-dQOP`TSy^bV3StdT6u|x4I){M>etm-zhSNX$D_mJeY zL2+Kqsn@0shO!33i$eK2`a03CEcO^lLC~plr^(ZOF5|T@M)m9RR|3ohDq|O`V`qzU zovCx7ky;)pUb$g3%&{&kL68H?6J%l3V%5^VAIEQEKz7S>yDw{6%|thimC%KX}kuHBV2NCJ%SaTv_$XC zDFsUX6~>iO#WYcq)6^-_6Vqg{3_x)6q~iS7wBWziS4xv%Jd@+&(bQ1~>-;i?9KzSz zaLtaK9O{q~<~gy?kp`>~l0_-v>mNlseJ_J#-O6WHg5?`e=RtzqG_@S;PZ7XSR6`G! zR%cM%PhVAVSJYH!!ncl0JyDlVHymKcdgrguMBM|Bp-{`(sPv?O?h&jiE5Cku4maWB zPY;*G%~{OTo&uf!ek27Bo(e8ePoZ-tUo>85l`8h^5RsYuW%ST7W8#RtaND&;Ial5O z+}@6R^3YWqWC$gZcIJ7ZrlY9iU*YEq;aB~g>1 zEQ2<*?Qs4_SSk)GaKrC~;z{VVr@If>THyL+${@l4!|i~BS%J1#P{Vv}R@f+gZU9yu zM__M2+DNjFrhj9BWD29aK?(r z`hoL=wFfKRBxznAE_+t#_RaH<>3Hj#c$%Bgf+Y0<+Gc8Z_gInQ6qw439ERLzXZG+i z?;GOaSsG>;XLh438qDSmD&CABkC#kM2mSO2W&>I&ivUapT$WeSEw5 z<}UB;7d47?g7IwL2*WeI&B_TQYX|Ra2C73#IkSgVd32uXHo2L4Bn)75DRp1?DW%Yv zXvZFXT8Vtrt1@>?VYKWqeK;eu%l*ro{f8~eQT~w=Hk5iOEO%2KJXJKMng}u!ndcqY z?kQ9nK?j+gQAQ3k!x{U@kbaSm(jiss@ql(cuJg=KSlyMt4!nBaWwEdlG-F&U_{73q zY1{p`p(gLR%5A0lTFz$Tv$^17CUVCy?ko{;#9N+LO>atz-l-;PKcvD7o_mC<_fAiS zyVf|vf}Sp{$Jq;Ga|Sed)hY3#RFm%Hs?SBGaB8`kplOKD(JobJ0oiB6a(V+1#E9>^ND$Zq&dgHUf(N<10GLk>kqw4^Fpc0pkKLq*O<}0<;iOs|!L@%Z1Sr z)_g1TJTZD_IQNON<}F;0h0|*u^DwWCL}VtDyJc;Kfc$`+y7=FzvXr2Y{A{*;>`Qw6 ztZLvtg0nc97a5mSm~T+k8ExCM7W+9-%hOuqQD`tf#00Kl<|H9zJq8gZ#nmlIYobU7gvQ&mfI14BXdsjq`FcNI=w z6Miz>_t9V0GVscQxV}D&#Qz*Z`ppL`*ZUPTB7LWubkKq-@F!U>gwnbOlN@kDwDjvi zylUuT*~3C;hYWhsG72iJ34DyvcyB01%*6v#_HdA0@_1w<4=YwU%EJOxQwV%`B#R`G zFv%f6lh11hzwY;q_sgz8T9Qi9>Zte2tUwvNAY0Esw`WY)@A_vI45SU&wwwL8n6l&| zeAr_3K@|Rx@eAWC>%3>Z=knJRlZ521&#kFpM;m6g*9G~a)5loDr(K~BtPRcio&KI5 zfA3g!pST5a{4_RSewsf2w+!)LozI_p3cpzBJL|jtN0alvMtyb&{}JWl_+JHnN+SPv zpu>NT{nyST!N0@`*x6Z`8~*K}me&WG{ZF$oFy`-tHbGmZr86%o1L3F&+^C)CSEr@H z(0+IXk$$eLK?F7{HiC7f57lm05KsC25(uiq+xn(k##wGICcNF7d|$NnriP-TtD-%^XN}bRCOrSJdzSdDg(ZGuF3TGyjnIOHZ-TLeQJAjk}@a&}{EjD(K~Afu#_$ zxdFbtcQAbQ8!WuRcN=X3FMisL%c_&m>ZoUEJ^Na~DK{ZiekT^c=0+Z?eZT%v(<*%b{O{5K ziRZ7KLw_Cp`ysxc#rvCd8#^=ozt{ZvNe{ojL`US&Um!+=@K32M!ccI#v`CbyNh_13AOPYXaR9kV%GiqR2BpEBajWyakO$sIagC zJ;@j-uN8CC)a1$5YU50!FFK$w?l)0fzH9Wl!Ld1lr_Bv&@B@b5!XLVKT=}MALm_v^ zda}jN$Bn?YRl?Sr)!%9?5-?rvd*Tndq9qDvEJt}OTpTdyRWUUsFe_d|Zt#yhBzZC$ z!Hi*v=(np*s7EwH^T}``H{*6S;nCbbREJuJTDyTkMl2u+ke1?&-2}#&sLd(I9pLkm zXyGp~N{f|)5t_RFeu(}BTQ!5?9q-$J#Qa_3@DI!_OpM+I$*i@_{LYo-Kj%gPpHq8n=h+e$Xw!bwR$-Kc1~ZPC}K+`^Abp`{^cUZEz*S%Hpht5JtwtG3B~>U5*?HL){wRGWhx` z*;NGliHF|+W8bHYBEN&%@s9wb{sHheeldPI{(t#~-zM$<=F>&u$g{lvZqwRE2o0rT zg`K`%=)%p_;x0f$MFPG8Vij$EN$vH)2w2es5g#B zS2!9s_!3gElSoEPP}8=Guq90W%`7@&B(y2?D#eC)ME2U3LYk2 zQKMIs$RKH-g$a6rqd?N{@-6_ptbIVTrYRa7sy~&v%c|(nn86GvYjX@8qD0H>prr7@ zarcK;h8epzYD!^_v|K2qscwM~-Ld;`7+u;Q7Jm7L{C|WI>rWW}f44eHiNDKiBBt(? zcxz^2UTe`4!MD!$loSyClGzm5*)%dr*Bh*6I6>a|%g=^ZVEOVPz_QUTFwrfzX|7>s zd^jDqAGfyLURTO&j%LO8y1g0E!d0A{nf$pDBp#cz21!q375{BoHkqT0s&znY$q1`y zWCf^z$DmYYQZCOpe~^x-ezJHz7wEfc*rmm~KW$RX&gJlCQ-zUmpZ}4fj;z};=;}D^ z3wLJU^Vh@65*Ig*4p-@AxX2wKh19cWU9^4`eiKI4&MDn_>_;GCr_B{N(G>!_hBu1w z<=8L-$l%frC+#;C{)mlaf>dkmk zc;HeppAfc+V-aLz^7GTOTaqEMI|69aX#^O>Q}tfylCILIV;-KJ-#%D|$U=%k_9IJx z6vr@&0v-B&0iOmS_1Gk@QM}YqTkh(M1duyJO<5vKs}?Cev4+Z%S#o|21w0sQKY2i_ zJ`Q?cw{jbu2%?c!T(J;mBau=vQTJHtFW)Zm39_bhCms1L>GM^bK{d{@o*Lq{8VNQp zQwJS;z+Hx0@py0yy}7LmpizcSXtbm5%Pk{BQPM?A-E9TT4{mt z0tYYywf(l`K*TIif_IqL{}Jk6;x7Ll=L9UQ&HmlvM)JHH^83DE?kw-%LdWy}wdDFJ zkX+K4kODu`foOZtMam(~t6y92&P9s*s+I5%WWJ^I0!#)Z(|JJiS7s#w@l=JZb{^CeCo|`@z=^4C~fH z39!745BBV9Z=b;@l}2Wv7B{YB=~110W2`qGV1Ct7nI;$lQu72o|&?FmvDi-X_K1QRMl{kX!x{ zU8FzI737!uml6MR5btYWKV}7DXKK8Ah(1eOgo;$LQXQS%>BHVB8RAAgBjF$X4}a_M z)_%U=-XvhtJgZ?__zTr=4p?yl zm<4c(q{>#0$fxOKN8C&l&Q*RUFUk3e@|KbYHT@=ZW+uq_)_iK9nVkmTQO(vv+XO!U z9Ps{d%UbJGYf=uNETV$&Nn@oFS77m=g2|h2CLBJfM~1pNrKl6ouV<@YD zO@}Wkm?Cla?qp%lBhd2^FnK$lLYRmButh3FOabnD}@RpDUHN7UV(lkF) z%_)?C!yOy&G%l^y8(9P9WJ?|Jg1ET3wMjxa<8q5kQ-*0pU0`u0%|`XH9mWivP~^pV zGR07{W32`#n`5`%?$qo+6Y$|3zs!Hc@K0X#*MjhWzvWSXTjQ^IRfxLPd2fYdUG-ZQ znj2^`_Ln&ljYR)@ypo0O$_ffdOZv%vmNIxech^ts>5{6#TyLq9MJS~{NP_P*Yxcc1 zlH5p8Cg66OKqLSbD9k98@Z*hqkC+X&wL}m(LQ~7IwwDd{lmO*{a-K1=wBpgcP>xO- zgqkVZP`lU3FabgLDpRiwaWj2k1nY_;NFzOdNUJ(`|Q*3A}dW5%io>$ zMwh*akJ_+F1e@3~Hb|oPo*AGv#QJ5du7zFKuD5HhEKVGZ=&8|AdqJ^M-p9m7}v8+N;4VBZsKzKqiB;kSTqu1R;y zde@^o!34X;KisO%B4KgEyBsgU3U7XRkBEPkJL zmj9N8Kx%M+idBuzM+#7<*K32TMvDtG*1i1Yyf1ovPH^9|X#cxiXp6PKF3YtU<^prQ zRoA#maBeKrOUlxP8U?7NZ}`LXwn#rL)QXY9ZWLy*FZ;WMf!j`qTCPJ}(gp=doph0_ zyk9cmW<@7oRR5&Zt-RZsxE@&@_#C8ge)t(k_@2Lc$2gIDGW+CY zcN7~@Td5>iZq^4GdanEa&F?|MvwF2Y344~?O;80hJ;CiL?9}(O2?rliN(W7;@VhOg zG1{BDJ-xDcDufHZBm4Lv7kyf6H`EQdPnHW{pT$UqS3g#$RP5x0DQ@n;b^a|@zf~1w z_A4g-a}x3=xs#NV6PA>a{g+dJ=NrGA`Wv5ia3Ki_2Wl7%rrBm(Ob}fpvq0bVX3H+)L1+sRWM^~rB&F>2ZAIWV1=iKYXLI3@2jjELyFc$Oib zx{rd?gu0AkF-r2Eb7>hST}{M{^W=*L!M?-#eNjoSiahcS+;RO`&sRv5JGv5=uroTx zuCKeb6ACB?bzg=)14G$i)j6sRNi}=p`&6VbILSlpeB9sKL7ZCp%dLz3q?@R-N3L`e z*Mzu!(&0U-P(EX1>g}5FMG$4CE^B{CW%QD=a)abA(Q;u>khZysTe?v*-$Uz}=Lfj- z22XTw!UTWq-uUfj?guw|f!={E`bR(WXK0j?l>HaG_`gCU2pf?STl0SnjkW>aUIf%m z8QW2=l)v)ijd8yI?zRA6UYEr8(8%!~8qs~b@BavmYp5|QM)JniU4w2$zVQYW+ZMb% zv=}w5MD)8dD5q^##(O5|)m0ZdeK1VGT&DpIzBR^xD!Y09QTsPsAGs2}jZ72{&K?IZ zNoEy*6N*LCfb~SfUl?K*CyWu=&UP-&N+wy-Z}h-Rkdwc;i6(FtHnn+G&q!&8LDR3e zZrF1qEV+K7Q8CmlQKPA^@>!w%S#<~ftIU%m&d2lr4vl{jG%5LiK^Xa830k^XX=e{a zQEx~=ohH!m09>+qkknz3%#j}aCljM@Sp7-_@|p#>*; z<)|4pNPd=y8a&794bGp})fG)q37ZKpw^5_&*QSxYkQWM36CIa>h2=3eu{$V?JotY% zRj*C1=@{vVc=W;OGGYK!BPh{oyS(9aPZL~-a8qM)eES6XQuzY~)&>I{!`3x{T*Z+};V=-roc*mJ z$3jhxw!6>~fO5KULj|k-#N*1sjrvy^gg+;OoobU~2; zk%UsMa`xi}i563m#0u!CuBn~v4ip64HU8eiuRlUCMjp6q>`*u946B5ZZWS2a7e=AD zI=HTwU#Y+Ecd~E`Pxs@~`(6HLn)YWZ_6x~>4?F*7Di$yF7fG8mJ^X{Dt-q5rx2x)_ zk>S(7RD^kc*S;_UeEVbw{0Oe8DgdZ_ACDc|-e5Nif`S#G7CX22m@!>^bN-cz&Az8%Ou{h> zY6AY2DrjvBycyhu7e!Z#gLvHgmZU7DdTB6Ia5&%X=0aT~#edAbIyB<;Ihu;=dr=g7g3 zp&_@o*X-ubPZtljFzfGI59>MPIHo}^>Ga{@3)50z%!!QibEWMmo~S16U2&zFYTp-) zAB;2ZOB42i>m5~7NgW3WBaX*0no`^4SjK`NX5^s({b=oB<;ZgZ9A#zk3sG2}mqa|` z>J4*B=K{hSk5;XNDza%Dt(Labl;8c3xKnO=@0w#M2xtdBPkRKwq* zZ25A0SRh+hSIX9mZCn}bT80vo1gf6Xy}6yfOruGVtp8Qe-Bseo=_Vg;C$N70u#JX< ziu4JNhTT9BRlhPAZw_Cf+)DksytyY-mT#Et(l?e?Jh{pn&|lv5yV5n&|GS|3Cli*o z)3RC(QE zw-0=Oup;MQB-%7X$QAF-fr>K2*y!G*X4vKT(>R0*msYdVplxhRraziFMu`-d5jlPJLr)!#kh=)HCS%giRo%!=Vhm?=J}} zZLy9J9vsAU#&9+HQ}r!zTul(VpDtag>!9!Y27M8PS6D(BSrVp`hhGhR2HUQR@P=1S z9CZ&=UjVS=cZ^sa(z=;G!F`4C3turTQKw(^+_ryY0Hl9p8jAWl|JL{UW1ntZoT|-~ z0Al~!QHm-4l$d8M+-I3+&m3myrrKSJRILtraf4X6M**Z7U?6-K0u0B!?sEtzGg`NO zaynO}HsJHMI?I)wCa1pd)T~cz-``vm9Qlw zozsJJhJJIuiT7+Ij#M?g*B-u6H8F3cjFD=0 z$RGzg;Pw~ii~+(Akz7GJC4bU4{G6q30y1fz9iRv*GsnJWvH}-~l0ORq4;dz?cGaD| zH=K8SWrtSUd}KEYMa5_wdPWNwo-_FECPZsYpES3un_Rw~KlpP^?;y0iML zcWjSzYQNT*=<6w>pJWnQxE$tQv85iC;w36|GzR4)O7E7^^6L{d0uUN3mpT9l4%#WG zJzo%V3dy=30c(OAV5#@-DCilFb;&2Q>S_s!rem1l|;twVzt3;8HO~%HuG-eK9v(T5>CAF@{B@nu2)#wh}sK(Sw zIM?b^{{kIbI4Ti3IxRepIefM&w5XT`P8*UozF!7|PT>|ZuAJl!0X;=KfU(-EM?op5 zQk1Pjz>3y8?KEYfeq`i}!s-`BtWF|4B^Gb}(t}r(l{|$REVcZQVX1z)FQ=xyF5(%? zOD(Cb5=<(b!THW2 z>!=XNH7`T@R=~2KD1fK9d5Psh;Z=wPysT$!-4W0XCilL+=>Zx!q-|CTo6M(EIfD}Y z$4moIn4(T#Jw)zn+&cOhT;txa+4X90OEmjgT`NH4#X%AB$sAqo+$C{f#X2q&}w1Q{k&95 zr7Gg!RsMKYRPHt36 z8yZ{^-^OO71|d1;XD|Rzzce|mual$t=mOPkw{YEL9^kEgvbQEuAh&GM3jW3f9XGF$q?(6T@gQV9*5zuBH2Rfb51qy7yGOn%8JU<$Ag|kKZ{w4KPGi=Aq;=yWZ^+ss-n7n{5$4rn>8zm&5&xx(r??g>FqSp{5h_ z;&$8ONm5{No`87`%Ebe5I!mqOyJ>O321E~{IVaUsdUGRcMq@gZbSduivIKX z@&DWL^J(iE{t`;JwYD(*>-^&sO-wOokX}dI`hFzY*`N4=h=BzGbS;D!x54M3p~Dd{ zO#7?EfpC38=eBMVSCr}KOfV;TNzD#`rSgioAdIgd>++&JWr*1cJmUVW?{Pe#&yk-gedK@ZIA1E=TgiasLtN@<)7dq!4d*d?De;YMgu= zB%XK>Cj~eY;M*>Rb+G(mEfzofv2GjhQj*44-p^23w=L)H%8?demm`Q89U`TKxz27gbrQfqBe}UboGUlCJ)8Bv6s63Cs1(FQvwsysl~lS99}0p5!6No zG+ZZ2{>qrlLqx&KXp<0-rjhaFYg}hyY)Bi%KFUc|^i$~-O8UGmO~!1pkJSpayK0sg z38h0rdCtPwrDd4p5X(8o{a6WVEWVX=k$xa*U=i= z>kw@tUIMme5)I*^XgmN0@fCD3qxv!%<0my@@`t_S7X7^&Bw{Xk20%{f6Ea5_40FeU zDm|L%IJG6<%G@r1EMHe%glfMhQyYC$nzy#%4GNc0E(u`=lTOSnTP1y_49%>*Cpx@J zgoiwBf`ac{2Gs@`N<#mM)vEDzIic9yINdU{qRH{)Xt7`2#VdO-F%n&abYB$4A?Ew5 zK9)!~7HsHe;{K_WIy>SlYifULE(c`%9u_~#AU(C9=B!s)mH64>M}rjm}499+za$7HnGiinqn|IT@;sAA?$i z>*@@m;EpxDn#z+~Wv3i{KA%3$*0B84WBHA|CL|1)h{@@ZY}DJlYO9l1I6Z#aHqRKT z(K(xC;n8S?9J7$w(}(PoCnMOMr`fU~7~pLSIf*|&#p4XR;PWKs`gx7e^r@Ygr&nT` ze_yR+nPzyBD#%Kz3fiG)K$~exc?o;txv80KbjyW|YCSht@!Smw-2myCyBGEXAE{!K zAw5WI)3>$3eg)3H`~j5`T>BtVzBJx$st6!WwbCL_`b2qb<^%w79>Bqo0?*G9?7KQT1$YC3Iuy*X9CZ7$L}Xn|1BE35$TY+m(7;Tj7Y(j3u3 z|KihA61dOEjN;(Ny80betfGm6`m~iWw(Z(|6W?(y}{f z;2cPppe*pb9Pnd(Esr=>rg_bOWu<32aUr zQPWVn6fpO${aNA=pw0-oQ9HjdxiO)h_5(kQZSp`>5`Wwd1c`p7lxc#k!u4L`b8A2* z*)W%wVG-7a)7;vVe(h1Zg!5<{m1=3Zd`48cSUwxKXIS5LO-pI);SF^aLdVuW2^=4_ zvl=p4$X7dHUqV!eZE#YV^ic|$4eaRW^CIx!j_37)3IG=V6S zn>V)x3@EMl#8;dsoJhrm-8y9M9?Rg42w$L}PhQXThyytr+}S)h?OG}1l%Ch3IJ&3_ z_)LC<7~dg*g+2Ulm-`JpHdz_V@hAy&?z79U?A?JZ_&P1_)1Hh0J(RQ^z9pi7%hASKA-m?^<7vMtySC~j;yw+o1L3(=qfEmQd>&n_XdR*wuW`I&@)3|!j&o>8^$kj zB{vNf3S~*>#=YJ%kEvBJIsA3adUaTk;!5B|&tv1hW4^%B-rTRfjBn;2a|MA|FdzuN zeBiB;!ijkQM?pNL*fwjcKqJk;x4}~(4|HaabUr&&*&r$7g4;$lXO5@sXPO;3TnMkS zM-&k3`dqR~I?X-gg8@0PtjO-s$`3z!dx(!iC*6#M(k339sldHU?UPh#;vu*0i_&`7 zjv4QwGcpZqi6hi7zN)}g8agq18PenPTTwo+U7(_lS!i)lgBFj zPxIGNY?QFJ1TMv8Gjl4sqz21?xzkeWCVxr`p)lqvD@~3Ou|-))m&5X0364(JE=9#e zY1jO)B*qM3+L-GkFuE@pL9_@rNfdO-)EcuPdm_({Rdw;In{HtecmnuB4e)Q>gi*skvxk6S|36HP1DB?+=7nTE0nMo^2MKv#NJQ9N?`IKywuUx%1mN zrtDUU@T(C6XH4R~auYtM4c(`boi;9?%>1yYcJ%_iU4;l9;on2}pVMT2PK^9!aR0H9&o6RB;&)TXQ;J9KS$V(fz6Qr? zi@;o+K?V;TtY{{zBx^M?npSs_wCjDX#z$9@Er71xRFClP@?h&4OVaXK#U1R2Ip*ui zz4v~m7xz^sqOw>k0ioAy*+hK&t#LlG$T~Act@!SB?Dil5p{9v$oOtLcsKlr6sG>>P zqSsHfG7GWCVccd}liv8$Lrpk&EJhe*8bKR<^RO@4aG+V-X-6xC!{Dv%U)P-#(&3_a zKDbx4U%}Ec8o^hfUU&6B6>>p=j1E((BcPn>UUywb@x@IG zA-prBzJJVv|7_X(4ezh%vA?)cnS!bfB0u7*vqen}8#pem)(3EF@IfN&j~75qhClgr zc)dXscw)sEyHEXFdsdWx=0vEpJ?O+SU3*=2N=1pncS6R)sywDpTpXn@;3_lX#LFk0 zau$p|j2_suosML9ti7OheA&h`>?`!2>p~6b2(SX&Fp~y=vxp6C#A0ehSTa)vkdmec z>3kkF78-a616%E339{8FUkzRdT8-~!L#Ki;MMuX%V`g*T)zP`Xp5EW~^+ZJ~#Gu#~ ztxsw|FsL_uND6}k8&gpx*I$hHH}ZN!h-WcD3Q$Y)q)8rA?d>d8CQ~N#;@h&>MY6$t z)gaZN=q&?fb8k*MckI;NqUL%Pdtl(NH)tFl4{1}8e zkiz24NS5O``?FwE)}AIt(u&%VKV6#phYpfx5^wtKRel;W*a)(3((dH zEbueah@qgj)hEars1_skmLZq(6o{(?%=HDKW5O%~GmI zws&H7TdZ+E(kD{vyv5gvJ$AJ7#>6L+9dEzO9H4UoBA5mB9C3|CmP|jvmgZ5o@YY#~ zx(Jqi@;8dKbViLD7QuKq0pwOo%tS5;f!m4PFq)!ZdQeRnke=A$;UpY4+?X%?q_w?T zY$5)>TbjEvWtBeQm(+A)YRxp5@10g3{Ov^CKz;TrtO7o*JPAt7tdPJh?IGq(CdgOI z5BNYXSe{`9gatLgy>cwycg!kKL891CkA@R!ln)qn*U^q?DivX#Do40e)J_t*Kh_R! zRC=Av>*E9|dzoRxE^}iGq2y0SY<+rP1+}gZWhjI(pf%c|uLp`=96@e*K1g-I8Y=RV zViLuNd@NHHS%O+;Hjr8T*f3+fAW$kD5yw5nszFK!t%7aCXdeZabC@P3v$(W+(a021 zs2a^5QgYY~RnHaMsXI}OEoGgRKE|Gz1*%#F4J0XjDZNM~%-tkE_~Rjp$+&ecZ4is$ z%wdO-VQwPe+vYsa0qp&kP1mpW5EBn5qZ$y*z~`5h(wy#3xPP3Z875PIVR9wq*m|UYl}NW5 zog_p35vIi;<_s-1J|bCJSC`5+GkV3477_IYmdzwYeiV`+&Ba&%Mp4kcJ>SMc{Cn%- zgHQ6PTl$rB!|RuV*A&-6fqO@2-X6aF3QV>A+IT1udc#)e-SFDdXN$s^DG2m7o(?Yc zi2zKuLX`?9@|CLb63VV{&f^0I3TGx;lr@-jI+2Vk&3;j9=;E>g(p-ZS)iHo_&fiPFpq*`00~X&4q{+Ql)3XNhi z!>;y_c0b;+5{Y;(-g#zeMW)vl9`%A9tu}bdU?|bDUY_~I=(53^q9s$501vzlVPowZRy}-erI@ z|3e1&&&(I+!+$A!@SEEj**eK-fB!q}RZvyLkVkx#Fpq%(&WqLA!K17N1CLe-kpr8C zn9f!+<}sE|0CeH{!XU6F)i%agyaqmIwmo$D6oOKlI5|z!J&RKvIY{__prI^*WvTg| z;nvoAcXz^ae%H?R2C)v$$*8?q93<;F)ltX1O8ceH(R&oUPONyx8KBX#^vS%ilGJV; zfN&OHW~4t-=&6ig(^wRL2ek|z$XS^djcAyx(SSW~)NgNd;IuaUqv?y+0H4ts0-Pw& z1=7Jl>WS!k*$|p3w-v87sDoHbys7-U5AZ`NmftG`-HiODGVwOpsA;FbB2JB~9rsQ$ zl?$RnE$G2@wC*KZuy$dOJ)H$UgVK?fW6<$!3Y3kBR^VKT=xq0e#%rqe?X1QkDlHsE zA@RP)b~UahFHHY{a(*~`H!rd-jelvlqR{>)$&q7?y#^&RjIX&g(hX1?xp*n zeX#uXPs)nnQm$;AF29|d96ESYYfp&R?ONJSQ?i7! zI?VFYNvIljLL!f5u5>+Vjm{R>7;VU_RY&&m2kUGgK`A)`_>ldrmO4HopK1+u1>6f| zRwYAwdF`bv;iEo5C6#Ds>%Qgf73D|~@<~$e7#poPi3JfvUD56!yDg1emi=frYpjSW z98s2>EH<}zwv>OgM`b}Sdo=D~Da3$ox!@HMSHUGD$tV+%1+z6=6BQcI-&Y%(u#wOB z5UNMk^0_LoRKO&c^< z=^Xgf332DM-1JjsH1dh(acKF9+Vtn+=d|r~mlhyzo%ZcE^aL-Z{0qIMWU>><>5_gb z30EJX`_r~HM-Y6O+ggC!LeHZo5Dg;>;T@^JgCW>w?_%C-&5af?by4^zVq+2|NTXdb zoJv04W%kD*gl;5xs?P{I@x-|_zQ#tJYy@Fw+u^z(Ngp=6%Y$>S@Fmi{n?mx>zN}3= z0z7T$4CR0$vc!SGUkhjgW|cO7kF9)wQ$*qfd7nxCk1Fn;6rSL}`65AmbA4+g-M?JY z|M(+W41H90Mi53{GYp9=%KBVHdIB#J6<8$c;A<@wxy;Yblllw(W;TbX`P>}STaE=1 ztzBppC;Du9&E7jQ1Gef~dDt z3=H^Iq6#1u_~qel6^EL@>&dDotCJqWC-+wrGfmqnjC`NWQJ$&RNEm0<)w?WKr!BHr zD#aFonj$b{Tm3Oyo9|9oVq&k-lgICq-T>{w&nucVGW{%{ETziCMsDgK+M*3BY8au2 zag(N3@9UaG5R;%#! z=9-41BE_F^ms@AkFpuqv3$F89w?kkf=o^5pzF0j)DkOZ(Ss7zf4s!X0;oel9OnWWX zDhA9ewAA+nZ_*x1+Dv+`Jm|y_S5eMPcXfbuN20ex!uALA$C2F}!wT67dbYe|V~Gav zll-=IfN2LJ78y1N1AB{cSYm$jCW{cd2fd^sE<$T9ps^KgC7l$>P8MO7_ZT=jvXp4C z!H&(pbg~73pLhO}?-D0u2aynJ8l*{l{VR<`g`})WOGO_$e@Wgc;^4I%;I6MhctWU# z#v*x<4C=+HIO2{40SP;qL#+(EdX61^UAHM(zyA`Hjj^KtGS5_@XOMiqj|UVKQ}L@eK7*ay*R+2o{d+KAYY=}RY`%LVU_a)&77$P zKAiflQ;P#0HBvB$T(ZL6Vz{LfNp?DiwxnnMy@U6-RN4U!&!2%;uH|0%qELAtNTVdZ z^6FScCD@y#v*}C=4mPVX1j0$7OC0OV206bOMjxHNE^40Rw;EAng~SIy=V!!77CroA*dlm^v*~*J{p&h~!ya50ri$f9K;wXKLS%>j zwKRrZw@n5}Y8)i7eAdyO9mh1ZWh>+)!Y8N!p<%~WVWRVPZaZo&G_~u9pf(9tUgrHb z&s3G~aI0GDoe~7@yt>BF;z5Xmrjh$ZwmALjSUT)EjibTIAVyzuM?wh$gno`}BcNRe z2F`?(+Gd7MW=a}WhRTowNhW5G<`HZWW=`bT1#-bOi;AKf6gv;0$V6n7W>em<#SXe( z&Q}Gmw_?)>JdDa8=ezFdz40AKTQVmKoq|WNa3u39Oo+$q%G*xnDks)962Xois<7}I zBk*5B()>&Y9%i3H%5Di_boI1%9Z+}-MC2rDRV8=?*Y*g;zYm=o+Dq(0VZWoc!qQ~n zPd1=Is&R|IF&rn%w4B*=X3R&~#EK0*le?#y$w-POU=7IyYJ6zY-BmvGeT^ z_trA^^VuUq5bNGKL&nbAhVPj*Sn@UJityj-pA%AsTh*XrblHYkJ%75_AKYjgw zZMX@&kMaIBIsNw|z_7R@`=xL1^G%2dB}UP^GAa;xYAPZSjZ`}dhC*MebwU%F5w*{LWa`e3m<19YkcgLek=Al;qXPHYxz`i}0;JYJte0#7Hjla^_nLU+Z6 zs~dm?N=EozV-*9~PZiyg1gzdZ=J1mT^+)$x2fz|{Wts_yI`PgKCz`FnKs|;}^x}`$ zQ&#LY+W9QfRT(wkgex~dUrLW3gFsiDu+sqd+dxCe!A=>^s}I}w)X&iub{i^tog9KX z(Az0kp}0fL*dOt9XZAbieg`lt>ehah_h~iT=G2xePWmXZwx#!(sQJ00(3ES{ezD$S zJR%Y!J%aqTXTY+)nUbz@JagEP|4XVFi+$MURLD(QCvolc%= zEn*HsUuYkB>w7H_XyTzeUGVg6ZCVh>u=c( z5Dk^4(tkCu)#Z?MS9oz#6Yrn-D$HNUfHt&W^jLSJ;_555*Pb)F?^GW`ZmI2`g(}>} zg-AIO;&|>#u&!_S68d9}gU~!xTVI9n$nrrcfap=uQInzZ##==&)z>vqQHk3xn<(e# zyGHx|;6Bi1fg7qi>nag-$QrPBDZvAwjDkXV@3!nJdr_-le!uHBA>GoIJFPHZ6%zD@ zP806V{(jn7A%(!41fdDdK%(8%Ei6CDjKR=uA)|zSp%QRL3B1`5#L3bj8Qh>o>d($l0x60bi6Rd^amH%F zrdj80i$r4r(#822#ss=Ww*x$Rz52{jq9B6ToPpmeHU+H;P5EqMl*DvrJJRmwVx+}6 zYn$9(-8~6@SzNu$>_J}vOcRNwdxbLt+S^Ov4{iV$( zL1u;;*(1$g*F6#LKJW&SP)hQI`U?d z0t1)#Kbu^yo__w!Xy@|y$jnU{0tNBOmYpuLFFah`hn*il2+#*0`HQ(83;4PDkKBU4 z4U8R2Mgaus5q!H&1HFb_G6b_FoS^3%w)p%{Hd}fozwkAqL4kBzFWvM$T^MQla;)AB z)J(!)o&qk$;uy>riVmiR^y%=xu93TxWUUr8aUNNoUr)9Ay#^;n#kvKZ0??ipXp3Gy7?E>})Ul z0utdF)_ z8P(-)5Y1LfJaJYG&kYeRqRpDak5yW+1#awkoQ%iS+ujQS^5vw%O9mD0yl2OWZDRyM z)Z0I*7~@I`suf(-elSq3hPFkKxu)tOum~?j8Q=dHYQ4jz^zgaXf_|#sxB@BjR;U6ntgA84sm#G?>yeOP>jU